2026-01-18 04:50:46 +01:00
# CLAUDE.md
2026-01-18 07:31:19 +01:00
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
2026-01-18 04:50:46 +01:00
## Project Overview
2026-01-18 05:17:06 +01:00
2026-01-18 07:31:19 +01:00
Claudeman is a Claude Code session manager with a web interface and autonomous Ralph Loop. It spawns Claude CLI processes via PTY, streams output in real-time via SSE, and supports scheduled/timed runs.
2026-01-18 04:50:46 +01:00
2026-01-19 05:28:45 +01:00
**Tech Stack** : TypeScript (ES2022/NodeNext), Node.js, Fastify, Server-Sent Events, node-pty
2026-01-18 05:17:06 +01:00
2026-01-19 02:06:37 +01:00
**Requirements** : Node.js 18+, Claude CLI (`claude` ) installed and available in PATH
2026-01-18 07:31:19 +01:00
## Commands
2026-01-18 05:17:06 +01:00
```bash
2026-01-18 07:31:19 +01:00
npm run build # Compile TypeScript + copy static files to dist/web/
2026-01-18 07:38:19 +01:00
npm run clean # Remove dist/
2026-01-18 05:17:06 +01:00
2026-01-19 03:43:59 +01:00
# Start web server (pick one):
npx tsx src/index.ts web # Dev mode - no build needed (RECOMMENDED)
npx tsx src/index.ts web -p 8080 # Dev mode with custom port
node dist/index.js web # After npm run build
claudeman web # After npm link
2026-01-19 07:33:33 +01:00
# IMPORTANT: `npm run dev` runs the CLI help, NOT the web server
# Always use `npx tsx src/index.ts web` for development
2026-01-19 05:28:45 +01:00
2026-01-19 07:33:33 +01:00
# Testing (vitest)
2026-01-19 05:28:45 +01:00
npm run test # Run all tests once
npm run test:watch # Watch mode
npm run test:coverage # With coverage report
npx vitest run test/session.test.ts # Single file
npx vitest run -t "should create session" # By pattern
2026-01-18 05:17:06 +01:00
```
2026-01-19 07:33:33 +01:00
### Test Files
| File | Coverage |
|------|----------|
| `session.test.ts` | Session creation, PTY modes, token tracking |
| `respawn-controller.test.ts` | State machine transitions, config updates |
| `scheduled-runs.test.ts` | Timed runs, iteration cleanup |
| `quick-start.test.ts` | Case creation + session startup |
| `sse-events.test.ts` | Event broadcasting, client reconnection |
| `integration-flows.test.ts` | Multi-step workflows |
| `edge-cases.test.ts` | Error handling, boundary conditions |
| `session-cleanup.test.ts` | Process termination, buffer management |
| `pty-interactive.test.ts` | Terminal resize, input handling |
2026-01-18 07:31:19 +01:00
## Architecture
2026-01-18 05:17:06 +01:00
2026-01-18 07:38:19 +01:00
```
src/
├── index.ts # CLI entry point (commander)
├── cli.ts # CLI command implementations
2026-01-19 00:49:30 +01:00
├── session.ts # Core: PTY wrapper for Claude CLI + token tracking
2026-01-18 07:38:19 +01:00
├── session-manager.ts # Manages multiple sessions
2026-01-19 03:02:04 +01:00
├── screen-manager.ts # GNU screen session persistence + process stats
2026-01-18 07:38:19 +01:00
├── respawn-controller.ts # Auto-respawn state machine
2026-01-19 00:49:30 +01:00
├── task-tracker.ts # Background task detection and tree display
2026-01-18 07:38:19 +01:00
├── ralph-loop.ts # Autonomous task assignment
├── task.ts / task-queue.ts # Priority queue with dependencies
├── state-store.ts # Persistence to ~/.claudeman/state.json
├── types.ts # All TypeScript interfaces
├── web/
2026-01-19 03:02:04 +01:00
│ ├── server.ts # Fastify REST API + SSE + session restoration
2026-01-18 07:38:19 +01:00
│ └── public/ # Static frontend files
└── templates/
└── claude-md.ts # CLAUDE.md generator for new cases
```
2026-01-18 07:31:19 +01:00
2026-01-18 07:38:19 +01:00
### Data Flow
1. **Session** spawns `claude -p --dangerously-skip-permissions` via `node-pty`
2. PTY output is buffered, ANSI stripped, and parsed for JSON messages
3. **WebServer** broadcasts events to SSE clients at `/api/events`
2026-01-18 07:31:19 +01:00
4. State persists to `~/.claudeman/state.json` via **StateStore**
### Key Components
2026-01-19 07:33:33 +01:00
- **Session** (`src/session.ts` ): Wraps Claude CLI as PTY subprocess. Two modes: `runPrompt(prompt)` for one-shot execution, `startInteractive()` for persistent terminal. Emits `output` , `terminal` , `message` , `completion` , `exit` , `idle` , `working` , `autoClear` , `clearTerminal` events. Maintains terminal buffer for reconnections. Includes buffer management for long-running sessions (12-24+ hours) with automatic trimming. Tracks input/output tokens and supports auto-clear at configurable threshold.
2026-01-18 07:31:19 +01:00
2026-01-19 00:49:30 +01:00
- **TaskTracker** (`src/task-tracker.ts` ): Detects Claude's background Task tool usage from JSON output. Builds a tree of parent-child task relationships. Emits `taskCreated` , `taskUpdated` , `taskCompleted` , `taskFailed` events. Used by Session to track background work.
- **RespawnController** (`src/respawn-controller.ts` ): State machine that keeps interactive sessions productive. Detects idle → sends update prompt → optionally `/clear` → optionally `/init` → repeats. Configurable timeouts, prompts, and step toggles.
2026-01-18 07:31:19 +01:00
2026-01-18 07:38:19 +01:00
- **RalphLoop** (`src/ralph-loop.ts` ): Autonomous task assignment controller. Monitors sessions for idle state, assigns tasks from queue, detects completion via `<promise>PHRASE</promise>` markers. Supports time-aware loops with minimum duration.
2026-01-18 07:31:19 +01:00
2026-01-19 03:02:04 +01:00
- **WebServer** (`src/web/server.ts` ): Fastify server with REST API + SSE. Manages sessions, scheduled runs, respawn controllers, and case directories. Broadcasts all events to connected clients. Restores screen sessions on startup.
- **ScreenManager** (`src/screen-manager.ts` ): Manages GNU screen sessions for persistent terminals. Tracks screens in `~/.claudeman/screens.json` . Provides process stats (memory, CPU, children) and reconciliation for dead screens. Screens survive server restarts.
2026-01-18 07:31:19 +01:00
2026-01-19 02:07:46 +01:00
### Type Definitions
All TypeScript interfaces are centralized in `src/types.ts` :
- `SessionState` , `TaskState` , `RalphLoopState` - Core state types
- `RespawnConfig` , `AppConfig` - Configuration types
- `ApiErrorCode` , `createErrorResponse()` - Consistent API error handling
- Request/Response types for API endpoints (`CreateSessionRequest` , `QuickStartResponse` , etc.)
2026-01-18 07:38:19 +01:00
### Session Modes
2026-01-18 07:31:19 +01:00
2026-01-18 07:38:19 +01:00
**One-Shot Mode** (`runPrompt(prompt)` ):
- Execute a single prompt and receive completion event
- Used for scheduled runs and quick API calls
- Session exits after prompt completes
2026-01-18 07:31:19 +01:00
2026-01-18 07:38:19 +01:00
**Interactive Mode** (`startInteractive()` ):
- Persistent PTY terminal with full Claude CLI access
- Supports terminal resize for proper formatting
- Terminal buffer persisted for client reconnections
- Works with RespawnController for autonomous cycling
2026-01-18 05:17:06 +01:00
2026-01-19 01:42:47 +01:00
**Shell Mode** (`startShell()` ):
- Plain bash/zsh terminal without Claude
- Useful for running commands alongside Claude sessions
- Same PTY features (resize, buffer persistence)
2026-01-18 05:17:06 +01:00
## Code Patterns
2026-01-18 07:31:19 +01:00
### Claude Message Parsing
Claude CLI outputs newline-delimited JSON. Strip ANSI codes before parsing:
2026-01-18 05:17:06 +01:00
```typescript
2026-01-18 07:31:19 +01:00
const cleanLine = line . replace ( /\x1b\[[0-9;]*m/g , '' );
const msg = JSON . parse ( cleanLine ) as ClaudeMessage ;
2026-01-18 21:37:03 +01:00
// msg.type: 'system' | 'assistant' | 'user' | 'result'
2026-01-18 07:31:19 +01:00
// msg.message?.content: Array<{ type: 'text', text: string }>
// msg.total_cost_usd: number (on result messages)
2026-01-18 05:17:06 +01:00
```
2026-01-18 21:37:03 +01:00
### PTY Spawn Modes
2026-01-19 04:45:51 +01:00
**One-shot mode** (prompt execution with JSON output for token tracking):
2026-01-18 21:37:03 +01:00
```typescript
2026-01-19 04:45:51 +01:00
pty . spawn ( 'claude' , [ '-p' , '--dangerously-skip-permissions' , '--output-format' , 'stream-json' , prompt ], { ... })
2026-01-18 21:37:03 +01:00
```
2026-01-19 04:45:51 +01:00
**Interactive mode** (persistent terminal, tokens parsed from status line):
2026-01-18 21:37:03 +01:00
```typescript
pty . spawn ( 'claude' , [ '--dangerously-skip-permissions' ], { ... })
```
2026-01-18 07:31:19 +01:00
### Idle Detection
2026-01-18 07:38:19 +01:00
Session detects idle by watching for prompt character (`❯ ` or `\u276f` ) and waiting 2 seconds without activity. RespawnController uses the same patterns plus spinner characters to detect working state.
2026-01-18 07:31:19 +01:00
2026-01-18 21:37:03 +01:00
### Long-Running Session Support
Sessions are optimized for 12-24+ hour runs with automatic buffer management:
**Buffer Limits:**
- Terminal buffer: 5MB max, trims to 4MB when exceeded
- Text output: 2MB max, trims to 1.5MB when exceeded
- Messages: 1000 max, keeps most recent 800 when exceeded
2026-01-19 06:51:25 +01:00
- Line buffer: 64KB max with 100ms periodic flush (prevents unbounded growth from long lines)
- Completed tasks: 100 max in TaskTracker (auto-removes oldest)
- Respawn terminal buffer: 1MB max, trims to 512KB
2026-01-18 21:37:03 +01:00
**Performance Optimizations:**
- Server-side terminal batching at 60fps (16ms intervals)
2026-01-19 06:51:25 +01:00
- SSE event batching: `session:output` at 50ms, `task:updated` at 100ms
2026-01-18 21:37:03 +01:00
- Client-side requestAnimationFrame batching for smooth rendering
2026-01-19 06:51:25 +01:00
- Frontend `renderSessionTabs()` debounced at 100ms
- Parallel screen stats fetching via `Promise.all()`
- Configurable xterm scrollback (default 5000 lines)
2026-01-18 21:37:03 +01:00
- Buffer statistics available via session details for monitoring
2026-01-19 06:51:25 +01:00
**Automatic Cleanup:**
- Scheduled runs auto-deleted after 1 hour of completion
- Sessions cleaned up after each scheduled run iteration (prevents leaks)
2026-01-18 21:37:03 +01:00
**Buffer Stats Response:**
```typescript
{
bufferStats : {
terminalBufferSize : number ; // Current terminal buffer size in bytes
textOutputSize : number ; // Current text output size in bytes
messageCount : number ; // Number of parsed messages
maxTerminalBuffer : number ; // Max allowed terminal buffer
maxTextOutput : number ; // Max allowed text output
maxMessages : number ; // Max allowed messages
}
}
```
2026-01-18 07:31:19 +01:00
### Respawn Controller State Machine
```
WATCHING → SENDING_UPDATE → WAITING_UPDATE → SENDING_CLEAR → WAITING_CLEAR → SENDING_INIT → WAITING_INIT → WATCHING
2026-01-18 05:17:06 +01:00
```
2026-01-18 04:50:46 +01:00
2026-01-18 07:38:19 +01:00
Default config (`RespawnConfig` in `src/types.ts` ):
- `idleTimeoutMs` : 5000 (5s after prompt)
- `updatePrompt` : "update all the docs and CLAUDE.md"
- `interStepDelayMs` : 1000 (1s between steps)
2026-01-19 00:49:30 +01:00
- `sendClear` : true (send /clear after update)
- `sendInit` : true (send /init after /clear)
### Token Tracking & Auto-Clear
2026-01-19 04:45:51 +01:00
Session tracks input/output tokens differently depending on mode:
**One-shot mode (`runPrompt`)** : Uses `--output-format stream-json` to get JSON output with detailed token usage from `msg.message.usage.input_tokens` and `output_tokens` .
**Interactive mode (`startInteractive`)** : Parses tokens from Claude's status line display (e.g., "123.4k tokens"). Since only total is shown, estimates 60/40 input/output split.
2026-01-19 00:49:30 +01:00
```typescript
{
tokens : {
input : number ; // Total input tokens used
output : number ; // Total output tokens used
total : number ; // Combined total
},
autoClear : {
enabled : boolean ; // Whether auto-clear is active
threshold : number ; // Token threshold (default 100000)
}
}
```
When enabled, auto-clear waits for idle state, sends `/clear` , and resets token counts.
2026-01-18 04:50:46 +01:00
2026-01-19 07:33:33 +01:00
### Screen Session Initialization
GNU screen creates blank space at the top when initializing a session. This is handled after attaching to the screen:
- **Claude sessions**: After 100ms, clears terminal buffer and emits `clearTerminal` event. Client receives `session:clearTerminal` via SSE and clears/resets its xterm.
- **Shell sessions**: After 100ms, clears terminal buffer and sends `clear\n` command to the shell.
2026-01-19 07:54:56 +01:00
### Tab Switching
When switching between Claude session tabs, the terminal buffer may have been rendered at a different terminal size. To fix the "squished" display:
1. Clear and reset xterm
2. Write the terminal buffer
3. Send resize to update PTY dimensions
4. Send Ctrl+L (`\x0c` ) to trigger Claude CLI to redraw at the correct size
This only applies to Claude sessions (not shell sessions) since Claude CLI responds to Ctrl+L by redrawing its interface.
2026-01-19 05:28:45 +01:00
### SSE Events
2026-01-18 07:31:19 +01:00
2026-01-19 05:28:45 +01:00
All events broadcast to `/api/events` with format: `{ type: string, sessionId?: string, data: any }` .
2026-01-18 07:31:19 +01:00
2026-01-19 07:33:33 +01:00
Event categories (prefixes): `session:` , `task:` , `respawn:` , `scheduled:` , `case:` , `init` . Key events include `session:idle` , `session:working` , `session:terminal` , `session:clearTerminal` , `session:completion` , `respawn:stateChanged` . See `src/web/server.ts` for the full event catalog.
2026-01-18 07:38:19 +01:00
## API Endpoints
2026-01-19 05:28:45 +01:00
REST API served by Fastify at `src/web/server.ts` . All endpoints are under `/api/` .
2026-01-18 07:38:19 +01:00
2026-01-19 05:28:45 +01:00
**Sessions:**
- `GET /api/sessions` - List all sessions
- `POST /api/sessions` - Create session `{ workingDir, mode?, name? }`
- `GET /api/sessions/:id` - Get session details (includes bufferStats)
2026-01-19 06:40:18 +01:00
- `GET /api/sessions/:id/output` - Get text output buffer
2026-01-19 05:28:45 +01:00
- `DELETE /api/sessions/:id` - Kill and remove session
- `DELETE /api/sessions` - Kill all sessions
- `PUT /api/sessions/:id/name` - Rename session `{ name }`
- `POST /api/sessions/:id/interactive` - Start interactive Claude terminal
- `POST /api/sessions/:id/shell` - Start shell terminal (no Claude)
- `POST /api/sessions/:id/input` - Send input to session `{ input }`
- `POST /api/sessions/:id/resize` - Resize terminal `{ cols, rows }`
2026-01-19 06:40:18 +01:00
- `POST /api/sessions/:id/run` - Run one-shot prompt `{ prompt }`
2026-01-19 05:28:45 +01:00
- `GET /api/sessions/:id/terminal` - Get terminal buffer
2026-01-18 07:38:19 +01:00
2026-01-19 05:28:45 +01:00
**Respawn Controller:**
- `GET /api/sessions/:id/respawn` - Get respawn state
- `POST /api/sessions/:id/respawn/start` - Start respawn `{ config? }`
- `POST /api/sessions/:id/respawn/stop` - Stop respawn
- `PUT /api/sessions/:id/respawn/config` - Update config
- `POST /api/sessions/:id/respawn/enable` - Enable on running session `{ config?, durationMinutes? }`
- `POST /api/sessions/:id/auto-clear` - Configure auto-clear `{ enabled, threshold? }`
2026-01-18 07:38:19 +01:00
2026-01-19 05:28:45 +01:00
**Cases & Quick Start:**
- `GET /api/cases` - List cases in `~/claudeman-cases/`
- `POST /api/cases` - Create case `{ name, description? }`
- `GET /api/cases/:name` - Get case info
- `POST /api/quick-start` - Create case + interactive session `{ caseName? }`
2026-01-18 07:38:19 +01:00
2026-01-19 05:28:45 +01:00
**Scheduled Runs:**
- `GET /api/scheduled` - List scheduled runs
- `POST /api/scheduled` - Create scheduled run `{ prompt, workingDir?, durationMinutes }`
- `GET /api/scheduled/:id` - Get run status
- `DELETE /api/scheduled/:id` - Cancel run
2026-01-18 07:38:19 +01:00
2026-01-19 05:28:45 +01:00
**Screen Management:**
- `GET /api/screens` - List screen sessions with stats
- `DELETE /api/screens/:sessionId` - Kill screen session
- `POST /api/screens/reconcile` - Clean up dead screens
- `POST /api/screens/stats/start` - Start resource monitoring
- `POST /api/screens/stats/stop` - Stop resource monitoring
2026-01-18 07:38:19 +01:00
2026-01-19 06:51:25 +01:00
**System:**
- `GET /api/system/stats` - Get CPU and memory usage `{ cpu, memory: { usedMB, totalMB, percent } }`
2026-01-19 05:28:45 +01:00
**Other:**
- `GET /api/events` - SSE stream for real-time updates
- `GET /api/status` - Full state snapshot
- `GET /api/settings` - Get app settings
- `PUT /api/settings` - Update settings
2026-01-19 06:40:18 +01:00
- `POST /api/run` - Quick run prompt without creating persistent session `{ prompt, workingDir? }`
2026-01-18 07:38:19 +01:00
2026-01-19 05:28:45 +01:00
## E2E Testing with agent-browser
2026-01-19 03:40:57 +01:00
2026-01-19 04:55:51 +01:00
For UI testing, use [agent-browser ](https://github.com/vercel-labs/agent-browser ). A full E2E test plan is documented in `.claude/skills/e2e-test.md` .
2026-01-19 03:40:57 +01:00
```bash
2026-01-19 04:55:51 +01:00
# Setup
npx agent-browser install # Download Chromium (one-time)
npx tsx src/index.ts web & # Start server
2026-01-19 03:40:57 +01:00
2026-01-19 04:55:51 +01:00
# Basic test flow
2026-01-19 03:40:57 +01:00
npx agent-browser open http://localhost:3000
2026-01-19 04:55:51 +01:00
npx agent-browser snapshot # Get accessibility tree with element refs
npx agent-browser click @e5 # Click by element ref
npx agent-browser find text "Run Claude" click # Or use semantic locators
npx agent-browser screenshot /tmp/test.png
2026-01-19 03:40:57 +01:00
npx agent-browser close
```
2026-01-19 02:06:37 +01:00
## Frontend
The web UI (`src/web/public/` ) uses vanilla JavaScript with:
2026-01-19 06:51:25 +01:00
- **xterm.js**: Terminal emulator with configurable scrollback (default 5000 lines)
2026-01-19 02:06:37 +01:00
- **xterm-addon-fit**: Auto-resize terminal to container
- **Server-Sent Events**: Real-time updates from `/api/events`
2026-01-19 06:51:25 +01:00
- **System Stats**: CPU and memory usage displayed in header (2s polling)
2026-01-19 02:06:37 +01:00
- **No build step**: Static files served directly by Fastify
2026-01-19 05:28:45 +01:00
Key files:
- `app.js` - Main application logic, SSE handling, session management
- `index.html` - Single page with embedded styles
2026-01-19 06:51:25 +01:00
- `styles.css` - All CSS styles
2026-01-19 05:28:45 +01:00
- Libraries loaded from CDN (xterm.js, addons)
2026-01-19 07:33:33 +01:00
## Adding New Features
### New API Endpoint
1. Add types to `src/types.ts` (request/response interfaces)
2. Add route in `src/web/server.ts` within the `buildServer()` function
3. Follow existing patterns: use `createErrorResponse()` for errors
### New SSE Event
1. Add event type constant in `src/web/server.ts` (see `broadcast()` calls)
2. Emit from appropriate component (Session, RespawnController, etc.)
3. Handle in `src/web/public/app.js` `handleSSEEvent()` switch
### New Session Event
1. Add to `SessionEvents` interface in `src/session.ts`
2. Emit in `src/session.ts` via `this.emit()`
3. Subscribe in `src/web/server.ts` when wiring session to SSE
4. Handle in `src/web/public/app.js` SSE event listener
2026-01-19 04:55:51 +01:00
## Notes
2026-01-19 01:42:47 +01:00
2026-01-19 04:55:51 +01:00
- State persists to `~/.claudeman/state.json` and `~/.claudeman/screens.json`
- Cases are created in `~/claudeman-cases/` by default
- Sessions are wrapped in GNU screen for persistence across server restarts
2026-01-19 07:33:33 +01:00
- Tests use vitest with mocking via `vi.mock()` - no real Claude CLI spawned