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 11:19:13 +01:00
**Tech Stack** : TypeScript (ES2022/NodeNext, strict mode), 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
2026-01-19 16:45:32 +01:00
**GOTCHA** : `npm run dev` runs CLI help, NOT the web server. Always use `npx tsx src/index.ts web` for development.
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 12:52:29 +01:00
# Testing (vitest - tests run against WebServer, no real Claude CLI spawned)
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-19 13:41:27 +01:00
# Tests use ports 3101-3108 to avoid conflicts with dev server (3000)
# TypeScript checking (no linter configured)
npx tsc --noEmit # Type check without building
2026-01-19 11:19:13 +01:00
# Debugging
screen -ls # List GNU screen sessions
screen -r <name> # Attach to screen session
curl localhost:3000/api/sessions # Check active sessions
2026-01-18 05:17:06 +01:00
```
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/
2026-01-19 11:19:13 +01:00
├── index.ts # CLI entry (commander)
├── cli.ts # CLI commands
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 11:19:13 +01:00
├── screen-manager.ts # GNU screen persistence + process stats
2026-01-18 07:38:19 +01:00
├── respawn-controller.ts # Auto-respawn state machine
├── ralph-loop.ts # Autonomous task assignment
2026-01-19 15:00:49 +01:00
├── task.ts # Task class implementation
2026-01-19 11:19:13 +01:00
├── task-queue.ts # Priority queue with dependencies
2026-01-19 12:52:29 +01:00
├── task-tracker.ts # Background task detection from terminal output
2026-01-19 13:24:45 +01:00
├── inner-loop-tracker.ts # Detect Ralph loops and todos inside Claude sessions
2026-01-18 07:38:19 +01:00
├── 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-19 11:19:13 +01:00
│ └── public/ # Vanilla JS frontend (xterm.js, no bundler)
│ ├── app.js # Main app logic, SSE handling, tab management
│ ├── styles.css # All styles including responsive/mobile
│ └── index.html # Single page with modal templates
2026-01-18 07:38:19 +01:00
└── templates/
└── claude-md.ts # CLAUDE.md generator for new cases
2026-01-19 16:45:32 +01:00
test/
├── session.test.ts # Core session creation, lifecycle, PTY behavior
├── pty-interactive.test.ts # Interactive mode, terminal input/output
├── respawn-controller.test.ts # Respawn state machine, idle detection
├── inner-loop-tracker.test.ts # Ralph loop and todo detection parsing
├── quick-start.test.ts # Quick-start API endpoint
├── scheduled-runs.test.ts # Timed/scheduled session runs
├── sse-events.test.ts # Server-Sent Events broadcasting
├── integration-flows.test.ts # Multi-step workflow tests
├── session-cleanup.test.ts # Resource cleanup, buffer trimming
└── edge-cases.test.ts # Error handling, boundary conditions
2026-01-18 07:38:19 +01:00
```
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 15:00:49 +01:00
| Component | File | Purpose |
|-----------|------|---------|
| Session | `session.ts` | PTY wrapper for Claude CLI. Modes: `runPrompt()` , `startInteractive()` , `startShell()` |
| RespawnController | `respawn-controller.ts` | State machine: watching → update → clear → init → monitor → kickstart → repeat |
| ScreenManager | `screen-manager.ts` | GNU screen persistence, ghost discovery, 4-strategy kill |
| WebServer | `web/server.ts` | Fastify REST + SSE at `/api/events` |
| InnerLoopTracker | `inner-loop-tracker.ts` | Detects `<promise>PHRASE</promise>` , todos, loop status in output |
2026-01-19 01:42:47 +01:00
2026-01-19 16:45:32 +01:00
### Session Modes
Sessions have a `mode` property (`SessionMode` type):
- **`'claude'` **: Runs Claude CLI for AI interactions (default)
- **`'shell'` **: Runs a plain bash shell for debugging/testing
2026-01-18 05:17:06 +01:00
## Code Patterns
2026-01-19 12:52:29 +01:00
### Pre-compiled Regex Patterns
For performance, regex patterns that are used frequently should be compiled once at module level:
```typescript
// Good - compile once
const ANSI_ESCAPE_PATTERN = /\x1b\[[0-9;]*m/g ;
const TOKEN_PATTERN = /(\d+(?:\.\d+)?)\s*([kKmM])?\s*tokens/ ;
// Bad - recompiles on each call
function parse ( line : string ) {
return line . replace ( /\x1b\[[0-9;]*m/g , '' );
}
```
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-19 12:52:29 +01:00
const cleanLine = line . replace ( ANSI_ESCAPE_PATTERN , '' );
2026-01-18 07:31:19 +01:00
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
```typescript
2026-01-19 10:36:30 +01:00
// One-shot mode (JSON output for token tracking)
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 10:36:30 +01:00
// Interactive mode (tokens parsed from status line)
2026-01-18 21:37:03 +01:00
pty . spawn ( 'claude' , [ '--dangerously-skip-permissions' ], { ... })
```
2026-01-19 16:35:20 +01:00
### Screen Input for Ink/Claude CLI
Claude CLI uses Ink (React for terminals) which has specific input handling. When sending programmatic input via `screen -X stuff` :
**IMPORTANT** : Text and Enter key MUST be sent as separate commands:
```bash
# Correct - two separate commands
screen -S claudeman-xxx -p 0 -X stuff "hello world"
screen -S claudeman-xxx -p 0 -X stuff " $( printf '\015' ) "
# Wrong - doesn't work with Ink
screen -S claudeman-xxx -p 0 -X stuff " $( printf 'hello world\015' ) "
```
The `\015` (octal) is carriage return (ASCII 13), which Ink interprets as `key.return` for submission. Use `writeViaScreen()` method for programmatic input that needs Enter key.
2026-01-18 07:31:19 +01:00
### Idle Detection
2026-01-19 15:00:49 +01:00
**RespawnController** : Primary `↵ send` indicator, fallback prompt chars (`❯ ` , `⏵` ) + 10s timeout. Working patterns: `Thinking` , `Writing` , `Running` .
**Session** : emits `idle` /`working` events on prompt detection + 2s activity timeout.
2026-01-18 07:31:19 +01:00
2026-01-19 10:36:30 +01:00
### Token Tracking
2026-01-18 21:37:03 +01:00
2026-01-19 10:36:30 +01:00
- **One-shot mode**: Uses `--output-format stream-json` for detailed token usage from JSON
- **Interactive mode**: Parses tokens from Claude's status line (e.g., "123.4k tokens"), estimates 60/40 input/output split
2026-01-18 21:37:03 +01:00
2026-01-19 13:08:53 +01:00
### Auto-Compact & Auto-Clear
2026-01-19 15:00:49 +01:00
| Feature | Default Threshold | Action |
|---------|------------------|--------|
| Auto-Compact | 110k tokens | `/compact` with optional prompt |
| Auto-Clear | 140k tokens | `/clear` to reset context |
2026-01-19 13:08:53 +01:00
2026-01-19 15:00:49 +01:00
Both wait for idle. Configure via `session.setAutoCompact()` / `session.setAutoClear()` .
2026-01-19 13:08:53 +01:00
2026-01-19 13:24:45 +01:00
### Inner Loop Tracking
2026-01-19 15:00:49 +01:00
Detects Ralph loops and todos inside Claude sessions. Patterns: `<promise>PHRASE</promise>` , checkbox todos (`- [ ]` /`- [x]` ), icons (`☐` /`◐` /`✓` ), cycle counts. API: `GET /api/sessions/:id/inner-state` . UI: collapsible panel below tabs, auto-hides when empty.
2026-01-19 13:24:45 +01:00
2026-01-19 15:00:49 +01:00
### Terminal Display Fix
2026-01-19 13:24:45 +01:00
2026-01-19 15:00:49 +01:00
Tab switch/new session fix: clear xterm → write buffer → resize PTY → Ctrl+L redraw. Uses `pendingCtrlL` Set, triggered on `session:idle` /`session:working` events.
2026-01-19 11:19:13 +01:00
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 13:24:45 +01:00
Event prefixes: `session:` , `task:` , `respawn:` , `scheduled:` , `case:` , `screen:` , `init` . Key events: `session:idle` , `session:working` , `session:terminal` , `session:clearTerminal` , `session:completion` , `session:autoClear` , `session:autoCompact` , `session:innerLoopUpdate` , `session:innerTodoUpdate` , `session:innerCompletionDetected` .
2026-01-19 11:19:13 +01:00
### Frontend (app.js)
2026-01-19 15:00:49 +01:00
Vanilla JS + xterm.js. `handleSSEEvent()` dispatches events, `switchToSession()` manages tabs. 60fps: server batches 16ms, client uses `requestAnimationFrame` .
2026-01-19 05:28:45 +01:00
2026-01-19 15:00:49 +01:00
### State Store
2026-01-19 12:55:08 +01:00
2026-01-19 15:00:49 +01:00
Writes debounced (100ms) to `~/.claudeman/state.json` . Batches rapid changes.
2026-01-19 12:55:08 +01:00
2026-01-19 07:33:33 +01:00
## Adding New Features
### New API Endpoint
2026-01-19 10:36:30 +01:00
1. Add types to `src/types.ts`
2. Add route in `src/web/server.ts` within `buildServer()`
3. Use `createErrorResponse()` for errors
2026-01-19 07:33:33 +01:00
### New SSE Event
2026-01-19 10:36:30 +01:00
1. Emit from component via `broadcast()` in server.ts
2. Handle in `src/web/public/app.js` `handleSSEEvent()` switch
2026-01-19 07:33:33 +01:00
### New Session Event
1. Add to `SessionEvents` interface in `src/session.ts`
2026-01-19 10:36:30 +01:00
2. Emit via `this.emit()`
2026-01-19 07:33:33 +01:00
3. Subscribe in `src/web/server.ts` when wiring session to SSE
2026-01-19 10:36:30 +01:00
4. Handle in frontend SSE listener
2026-01-19 07:33:33 +01:00
2026-01-19 11:31:32 +01:00
## Session Lifecycle & Cleanup
2026-01-19 15:00:49 +01:00
- **Limit**: `MAX_CONCURRENT_SESSIONS = 50`
- **Kill** (`killScreen()` ): child PIDs → process group → screen quit → SIGKILL
- **Ghost discovery**: `reconcileScreens()` finds orphaned screens on startup
- **Cleanup** (`cleanupSession()` ): stops respawn, clears buffers/timers, kills screen
2026-01-19 11:31:32 +01:00
2026-01-19 12:52:29 +01:00
## Buffer Limits
Long-running sessions are supported with automatic trimming:
| Buffer | Max Size | Trim To |
|--------|----------|---------|
| Terminal | 5MB | 4MB |
| Text output | 2MB | 1.5MB |
| Messages | 1000 | 800 |
| Line buffer | 64KB | (flushed every 100ms) |
| Respawn buffer | 1MB | 512KB |
2026-01-19 15:00:49 +01:00
## E2E Testing
2026-01-19 12:55:08 +01:00
2026-01-19 15:00:49 +01:00
Uses `agent-browser` for web UI automation. Full test plan: `.claude/skills/e2e-test.md`
2026-01-19 12:55:08 +01:00
```bash
2026-01-19 15:00:49 +01:00
npx agent-browser open http://localhost:3000 && npx agent-browser snapshot
2026-01-19 12:55:08 +01:00
```
2026-01-19 13:41:27 +01:00
## API Routes Quick Reference
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/events` | SSE stream for real-time updates |
| GET | `/api/status` | Full application state |
| GET/POST/DELETE | `/api/sessions` | List/create/kill-all sessions |
| GET/DELETE | `/api/sessions/:id` | Get/delete specific session |
| POST | `/api/sessions/:id/input` | Send input to session PTY |
| POST | `/api/sessions/:id/resize` | Resize terminal (cols, rows) |
| POST | `/api/sessions/:id/interactive` | Start interactive mode |
| POST | `/api/sessions/:id/respawn/start` | Start respawn controller |
| POST | `/api/sessions/:id/respawn/stop` | Stop respawn controller |
| POST | `/api/quick-start` | Create case + start interactive session |
| GET | `/api/cases` | List available cases |
| POST | `/api/cases` | Create new case |
| GET | `/api/screens` | List screen sessions with stats |
2026-01-19 04:55:51 +01:00
## Notes
2026-01-19 01:42:47 +01:00
2026-01-19 13:24:45 +01:00
- State persists to `~/.claudeman/state.json` , `~/.claudeman/state-inner.json` , and `~/.claudeman/screens.json`
- Inner loop/todo state persists separately in `state-inner.json` to reduce write frequency
2026-01-19 10:36:30 +01:00
- Cases created in `~/claudeman-cases/` by default