mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-04 14:39:42 +02:00
365 lines
16 KiB
Markdown
365 lines
16 KiB
Markdown
# 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
|