- Fixed respawn controller immediately sending commands when Claude becomes idle, which prevented users from typing. Now waits for idleTimeoutMs (default 10s) before triggering the respawn cycle. - Removed Directory field from Session Options modal for cleaner UI - Compacted CLAUDE.md documentation for better readability Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
9.7 KiB
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, strict mode), Node.js, Fastify, Server-Sent Events, node-pty
Requirements: Node.js 18+, Claude CLI (claude) installed and available in PATH
Commands
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
# ⚠️ GOTCHA: `npm run dev` runs CLI help, NOT the web server!
# Always use `npx tsx src/index.ts web` for development
# Testing (vitest - tests run against WebServer, no real Claude CLI spawned)
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
# Tests use ports 3101-3108 to avoid conflicts with dev server (3000)
# TypeScript checking (no linter configured)
npx tsc --noEmit # Type check without building
# Debugging
screen -ls # List GNU screen sessions
screen -r <name> # Attach to screen session
curl localhost:3000/api/sessions # Check active sessions
Architecture
src/
├── index.ts # CLI entry (commander)
├── cli.ts # CLI commands
├── session.ts # Core: PTY wrapper for Claude CLI + token tracking
├── session-manager.ts # Manages multiple sessions
├── screen-manager.ts # GNU screen persistence + process stats
├── respawn-controller.ts # Auto-respawn state machine
├── ralph-loop.ts # Autonomous task assignment
├── task.ts # Task class implementation
├── task-queue.ts # Priority queue with dependencies
├── task-tracker.ts # Background task detection from terminal output
├── inner-loop-tracker.ts # Detect Ralph loops and todos inside Claude sessions
├── state-store.ts # Persistence to ~/.claudeman/state.json
├── types.ts # All TypeScript interfaces
├── web/
│ ├── server.ts # Fastify REST API + SSE + session restoration
│ └── 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
└── templates/
└── claude-md.ts # CLAUDE.md generator for new cases
Data Flow
- Session spawns
claude -p --dangerously-skip-permissionsvianode-pty - PTY output is buffered, ANSI stripped, and parsed for JSON messages
- WebServer broadcasts events to SSE clients at
/api/events - State persists to
~/.claudeman/state.jsonvia StateStore
Key Components
| 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 |
Code Patterns
Pre-compiled Regex Patterns
For performance, regex patterns that are used frequently should be compiled once at module level:
// 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, '');
}
Claude Message Parsing
Claude CLI outputs newline-delimited JSON. Strip ANSI codes before parsing:
const cleanLine = line.replace(ANSI_ESCAPE_PATTERN, '');
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 (JSON output for token tracking)
pty.spawn('claude', ['-p', '--dangerously-skip-permissions', '--output-format', 'stream-json', prompt], { ... })
// Interactive mode (tokens parsed from status line)
pty.spawn('claude', ['--dangerously-skip-permissions'], { ... })
Idle Detection
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.
Token Tracking
- One-shot mode: Uses
--output-format stream-jsonfor 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
Auto-Compact & Auto-Clear
| Feature | Default Threshold | Action |
|---|---|---|
| Auto-Compact | 110k tokens | /compact with optional prompt |
| Auto-Clear | 140k tokens | /clear to reset context |
Both wait for idle. Configure via session.setAutoCompact() / session.setAutoClear().
Inner Loop Tracking
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.
Terminal Display Fix
Tab switch/new session fix: clear xterm → write buffer → resize PTY → Ctrl+L redraw. Uses pendingCtrlL Set, triggered on session:idle/session:working events.
SSE Events
All events broadcast to /api/events with format: { type: string, sessionId?: string, data: any }.
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.
Frontend (app.js)
Vanilla JS + xterm.js. handleSSEEvent() dispatches events, switchToSession() manages tabs. 60fps: server batches 16ms, client uses requestAnimationFrame.
State Store
Writes debounced (100ms) to ~/.claudeman/state.json. Batches rapid changes.
Adding New Features
New API Endpoint
- Add types to
src/types.ts - Add route in
src/web/server.tswithinbuildServer() - Use
createErrorResponse()for errors
New SSE Event
- Emit from component via
broadcast()in server.ts - Handle in
src/web/public/app.jshandleSSEEvent()switch
New Session Event
- Add to
SessionEventsinterface insrc/session.ts - Emit via
this.emit() - Subscribe in
src/web/server.tswhen wiring session to SSE - Handle in frontend SSE listener
Session Lifecycle & Cleanup
- 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
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 |
E2E Testing
Uses agent-browser for web UI automation. Full test plan: .claude/skills/e2e-test.md
npx agent-browser open http://localhost:3000 && npx agent-browser snapshot
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 |
Notes
- State persists to
~/.claudeman/state.json,~/.claudeman/state-inner.json, and~/.claudeman/screens.json - Inner loop/todo state persists separately in
state-inner.jsonto reduce write frequency - Cases created in
~/claudeman-cases/by default