Files
Codeman/CLAUDE.md
T
2026-01-19 07:54:56 +01:00

365 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 `<promise>PHRASE</promise>` 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