# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview 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. **Tech Stack**: TypeScript (ES2022/NodeNext), Node.js, Fastify, Server-Sent Events, node-pty **Requirements**: Node.js 18+, Claude CLI (`claude`) installed and available in PATH ## Commands ```bash npm run build # Compile TypeScript + copy static files to dist/web/ npm run clean # Remove dist/ # 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 # IMPORTANT: `npm run dev` runs the CLI help, NOT the web server # Always use `npx tsx src/index.ts web` for development # Testing (vitest) 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 ``` ### 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 | ## Architecture ``` src/ ├── index.ts # CLI entry point (commander) ├── cli.ts # CLI command implementations ├── session.ts # Core: PTY wrapper for Claude CLI + token tracking ├── session-manager.ts # Manages multiple sessions ├── screen-manager.ts # GNU screen session persistence + process stats ├── respawn-controller.ts # Auto-respawn state machine ├── task-tracker.ts # Background task detection and tree display ├── 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/ │ ├── server.ts # Fastify REST API + SSE + session restoration │ └── public/ # Static frontend files └── templates/ └── claude-md.ts # CLAUDE.md generator for new cases ``` ### 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` 4. State persists to `~/.claudeman/state.json` via **StateStore** ### Key Components - **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. - **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. - **RalphLoop** (`src/ralph-loop.ts`): Autonomous task assignment controller. Monitors sessions for idle state, assigns tasks from queue, detects completion via `PHRASE` markers. Supports time-aware loops with minimum duration. - **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. ### 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.) ### Session Modes **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 **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 **Shell Mode** (`startShell()`): - Plain bash/zsh terminal without Claude - Useful for running commands alongside Claude sessions - Same PTY features (resize, buffer persistence) ## Code Patterns ### Claude Message Parsing Claude CLI outputs newline-delimited JSON. Strip ANSI codes before parsing: ```typescript const cleanLine = line.replace(/\x1b\[[0-9;]*m/g, ''); const msg = JSON.parse(cleanLine) as ClaudeMessage; // msg.type: 'system' | 'assistant' | 'user' | 'result' // msg.message?.content: Array<{ type: 'text', text: string }> // msg.total_cost_usd: number (on result messages) ``` ### PTY Spawn Modes **One-shot mode** (prompt execution with JSON output for token tracking): ```typescript pty.spawn('claude', ['-p', '--dangerously-skip-permissions', '--output-format', 'stream-json', prompt], { ... }) ``` **Interactive mode** (persistent terminal, tokens parsed from status line): ```typescript pty.spawn('claude', ['--dangerously-skip-permissions'], { ... }) ``` ### Idle Detection 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. ### 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 - 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 **Performance Optimizations:** - Server-side terminal batching at 60fps (16ms intervals) - SSE event batching: `session:output` at 50ms, `task:updated` at 100ms - Client-side requestAnimationFrame batching for smooth rendering - Frontend `renderSessionTabs()` debounced at 100ms - Parallel screen stats fetching via `Promise.all()` - Configurable xterm scrollback (default 5000 lines) - Buffer statistics available via session details for monitoring **Automatic Cleanup:** - Scheduled runs auto-deleted after 1 hour of completion - Sessions cleaned up after each scheduled run iteration (prevents leaks) **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 } } ``` ### Respawn Controller State Machine ``` WATCHING → SENDING_UPDATE → WAITING_UPDATE → SENDING_CLEAR → WAITING_CLEAR → SENDING_INIT → WAITING_INIT → WATCHING ``` 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) - `sendClear`: true (send /clear after update) - `sendInit`: true (send /init after /clear) ### Token Tracking & Auto-Clear 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. ```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. ### 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. ### 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. ### SSE Events All events broadcast to `/api/events` with format: `{ type: string, sessionId?: string, data: any }`. 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. ## API Endpoints REST API served by Fastify at `src/web/server.ts`. All endpoints are under `/api/`. **Sessions:** - `GET /api/sessions` - List all sessions - `POST /api/sessions` - Create session `{ workingDir, mode?, name? }` - `GET /api/sessions/:id` - Get session details (includes bufferStats) - `GET /api/sessions/:id/output` - Get text output buffer - `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 }` - `POST /api/sessions/:id/run` - Run one-shot prompt `{ prompt }` - `GET /api/sessions/:id/terminal` - Get terminal buffer **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? }` **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? }` **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 **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 **System:** - `GET /api/system/stats` - Get CPU and memory usage `{ cpu, memory: { usedMB, totalMB, percent } }` **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 - `POST /api/run` - Quick run prompt without creating persistent session `{ prompt, workingDir? }` ## E2E Testing with agent-browser 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`. ```bash # Setup npx agent-browser install # Download Chromium (one-time) npx tsx src/index.ts web & # Start server # Basic test flow npx agent-browser open http://localhost:3000 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 npx agent-browser close ``` ## Frontend The web UI (`src/web/public/`) uses vanilla JavaScript with: - **xterm.js**: Terminal emulator with configurable scrollback (default 5000 lines) - **xterm-addon-fit**: Auto-resize terminal to container - **Server-Sent Events**: Real-time updates from `/api/events` - **System Stats**: CPU and memory usage displayed in header (2s polling) - **No build step**: Static files served directly by Fastify Key files: - `app.js` - Main application logic, SSE handling, session management - `index.html` - Single page with embedded styles - `styles.css` - All CSS styles - Libraries loaded from CDN (xterm.js, addons) ## 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 ## Notes - 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 - Tests use vitest with mocking via `vi.mock()` - no real Claude CLI spawned