Files
Codeman/CLAUDE.md
T
arkonandClaude Opus 4.5 d516609af5 docs: update all docs for hooks integration with elicitation_dialog
- Add hook:elicitation_dialog SSE handler in frontend (critical urgency)
- Add Desktop Notifications section to README with hook event table
- Add hooks API endpoint to README API reference
- Update CLAUDE.md SSE events and API routes for elicitation_dialog
- Add session log entry for hooks feature

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 03:20:03 +01:00

824 lines
39 KiB
Markdown

# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## ⚠️ CRITICAL: Screen Session Safety
**You may be running inside a Claudeman-managed screen session.** Before killing ANY screen or Claude process:
1. **Check environment**: `echo $CLAUDEMAN_SCREEN` - if it returns `1`, you're in a managed session
2. **NEVER run** `screen -X quit`, `pkill screen`, or `pkill claude` without first confirming you're not killing yourself
3. **Safe debugging**: Use `screen -ls` to LIST sessions, but don't kill them blindly
4. **If you need to kill screens**: Use the web UI or `./scripts/screen-manager.sh` instead of direct commands
**Why this matters**: Killing your own screen terminates your session mid-work, losing context and potentially corrupting state.
## 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
**Key Dependencies**: fastify (REST API), node-pty (PTY spawning), ink/react (TUI), xterm.js (web terminal), @modelcontextprotocol/sdk (MCP server for spawn protocol)
**Requirements**: Node.js 18+, Claude CLI (`claude`) in PATH, GNU Screen (`apt install screen` / `brew install screen`)
## First-Time Setup
```bash
npm install
```
## Commands
**CRITICAL**: `npm run dev` runs CLI help, NOT the web server. Use `npx tsx src/index.ts web` for development.
```bash
npm run build # Compile TS + copy static files + templates + make bins executable
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
npx tsx src/index.ts web --https # Dev mode with self-signed TLS (enables browser notifications)
npm run web # After npm run build (shorthand)
node dist/index.js web # After npm run build
claudeman web # After npm link
# Start TUI (terminal user interface):
npx tsx src/index.ts tui # Dev mode - prompts to start web if not running
claudeman tui # After npm link
claudeman tui --with-web # Auto-start web server if not running (no prompt)
claudeman tui --no-web # Skip web server check entirely
claudeman tui -p 8080 # Specify web server port
# Testing (vitest)
# Note: globals: true configured - no imports needed for describe/it/expect
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 port allocation (integration tests spawn servers):
# 3099: quick-start.test.ts
# 3102: session.test.ts
# 3105: scheduled-runs.test.ts
# 3107: sse-events.test.ts
# 3110: edge-cases.test.ts
# 3115: integration-flows.test.ts
# 3120: session-cleanup.test.ts
# 3125: ralph-integration.test.ts
# Unit tests (no port needed): respawn-controller, ralph-tracker, pty-interactive, task-queue, task, ralph-loop, session-manager, state-store, types, templates, ralph-config, spawn-detector, spawn-types, spawn-orchestrator, hooks-config
# Next available: 3127+
# Tests mock PTY - no real Claude CLI spawned
# Test timeout: 30s (configured in vitest.config.ts)
# Global test utilities (describe/it/expect) available without imports (globals: true)
# Tests run sequentially (fileParallelism: false) to respect screen session limits
# Global setup (test/setup.ts) enforces max 10 concurrent screens + orphan cleanup
#
# ✅ TEST SAFETY: test/setup.ts protects its own process tree during cleanup.
# You can safely run tests from within a Claudeman-managed session - the cleanup
# will not kill your own Claude instance. The respawn-controller tests use
# MockSession (not real screens).
# TypeScript checking
npm run typecheck # Type check without building (or: npx tsc --noEmit)
# Note: No ESLint/Prettier configured - rely on TypeScript strict mode
# MCP Server (for Claude Code to call spawn tools directly):
# Configure in Claude Code's MCP settings:
# command: "node", args: ["dist/mcp-server.js"]
# env: { CLAUDEMAN_API_URL: "http://localhost:3000", CLAUDEMAN_SESSION_ID: "<id>" }
npx tsx src/mcp-server.ts # Dev mode (stdio transport)
# Debugging
screen -ls # List GNU screen sessions
screen -r <name> # Attach to screen session (Ctrl+A D to detach)
curl localhost:3000/api/sessions # Check active sessions
curl localhost:3000/api/status | jq . # Full app state including respawn
cat ~/.claudeman/state.json | jq . # View main state
cat ~/.claudeman/state-inner.json | jq . # View Ralph loop state
# Kill stuck screen sessions
screen -X -S <name> quit # Graceful quit
pkill -f "SCREEN.*claudeman" # Force kill all claudeman screens
```
## CLI Commands
```bash
claudeman session [s] # Manage Claude sessions
start # Start new session
stop <id> # Stop session
list [ls] # List all
logs <id> # View output
claudeman task [t] # Manage tasks
add <prompt> # Add task
list [ls] # List tasks
status <id> # Task details
remove [rm] <id> # Remove task
clear # Clear completed/failed
claudeman ralph [r] # Control Ralph loop
start # Start loop
stop # Stop loop
status # Show status
claudeman web # Start web interface
claudeman tui # Start TUI
claudeman status # Overall status
claudeman reset # Reset all state
```
## Architecture
### Key Files
| File | Purpose |
|------|---------|
| `src/session.ts` | Core PTY wrapper for Claude CLI. Modes: `runPrompt()`, `startInteractive()`, `startShell()` |
| `src/respawn-controller.ts` | State machine for autonomous session cycling |
| `src/screen-manager.ts` | GNU screen persistence, ghost discovery, 4-strategy kill |
| `src/ralph-tracker.ts` | Detects `<promise>PHRASE</promise>`, todos, loop status in output |
| `src/ralph-config.ts` | Parses `.claude/ralph-loop.local.md` and CLAUDE.md for Ralph config |
| `src/task-tracker.ts` | Parses background task output (agent IDs, status) from Claude CLI |
| `src/session-manager.ts` | Manages session lifecycle, task assignment, and cleanup |
| `src/state-store.ts` | JSON persistence to `~/.claudeman/` with debounced writes |
| `src/web/server.ts` | Fastify REST API + SSE at `/api/events` |
| `src/web/public/app.js` | Frontend: SSE handling, xterm.js, tab management |
| `src/tui/App.tsx` | TUI main component: tabs, terminal viewport, status bar (Ink/React) |
| `src/tui/components/*.tsx` | TUI components: StartScreen, TabBar, TerminalView, StatusBar, RalphPanel, HelpOverlay |
| `src/tui/hooks/useSessionManager.ts` | TUI session state, screen polling, input handling |
| `src/hooks-config.ts` | Generates .claude/settings.local.json with Claude Code hooks for desktop notifications |
| `src/types.ts` | All TypeScript interfaces |
| `src/templates/claude-md.ts` | CLAUDE.md template generation with placeholder support |
| `src/templates/case-template.md` | Default CLAUDE.md template for new cases (with placeholders) |
| `src/spawn-types.ts` | Types, YAML parser, factory functions for spawn1337 protocol |
| `src/spawn-detector.ts` | Detects `<spawn1337>` tags in terminal output (legacy, replaced by MCP) |
| `src/spawn-orchestrator.ts` | Full agent lifecycle: spawn, monitor, budget, queue, cleanup |
| `src/spawn-claude-md.ts` | Generates CLAUDE.md for spawned agent sessions |
| `src/mcp-server.ts` | MCP server binary (`claudeman-mcp`) exposing spawn tools to Claude Code |
| `src/tui/DirectAttach.ts` | Full-screen console attach with tab switching between sessions |
### 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. Full session state (settings, tokens, respawn config, Ralph state) persists to `~/.claudeman/state.json` via **StateStore**
5. Screen metadata persists separately to `~/.claudeman/screens.json` for session recovery
### Respawn State Machine
```
WATCHING → CONFIRMING_IDLE → SENDING_UPDATE → WAITING_UPDATE → SENDING_CLEAR → WAITING_CLEAR
↑ │ (new output) │
│ ↓ ▼
│ (reset) SENDING_INIT → WAITING_INIT → MONITORING_INIT
│ │
│ (if no work triggered) ▼
└────────────────────────────────────── SENDING_KICKSTART ← WAITING_KICKSTART ◄┘
```
**States**: `watching`, `confirming_idle`, `sending_update`, `waiting_update`, `sending_clear`, `waiting_clear`, `sending_init`, `waiting_init`, `monitoring_init`, `sending_kickstart`, `waiting_kickstart`, `stopped`
Steps can be skipped via config (`sendClear: false`, `sendInit: false`). Optional `kickstartPrompt` triggers if `/init` doesn't start work. Multi-layer idle detection triggers state transitions.
**Step confirmation**: After sending each step (update, clear, init, kickstart), the controller waits for `completionConfirmMs` (5s) of output silence before proceeding to the next step. This prevents sending commands while Claude is still processing.
### Spawn1337 Protocol (Autonomous Agents)
Spawned agents are full-power Claude sessions running in their own screen sessions. They communicate via a filesystem-based message bus and signal completion via RalphTracker's `<promise>` mechanism.
**Primary Interface: MCP Server** (`claudeman-mcp` binary). Claude Code calls spawn tools directly via MCP protocol, replacing the legacy terminal-tag-parsing approach (SpawnDetector).
**MCP Tools:**
- `spawn_agent` - Spawn a new autonomous agent (builds task spec from parameters)
- `list_agents` - List all agents (active + completed + queued)
- `get_agent_status` - Get detailed agent status + progress
- `get_agent_result` - Read a completed agent's result
- `send_agent_message` - Send a message to a running agent
- `cancel_agent` - Cancel a running agent
**MCP Environment Variables:**
- `CLAUDEMAN_API_URL` - Base URL for the Claudeman API (default: `http://localhost:3000`)
- `CLAUDEMAN_SESSION_ID` - Session ID of the calling Claude session
**Protocol Flow (via MCP):**
```
Claude calls spawn_agent MCP tool
→ MCP server builds task spec YAML
→ POST /api/spawn/trigger with task spec
→ SpawnOrchestrator creates agent directory: ~/claudeman-cases/spawn-<agentId>/
→ Spawns interactive Claude session in screen
→ Injects initial prompt via writeViaScreen()
→ Agent works autonomously, writes progress to spawn-comms/
→ RalphTracker detects <promise>PHRASE</promise> on child
→ Orchestrator reads result.md, notifies parent via SSE
```
**Legacy Tag Patterns** (detected by `SpawnDetector`, still functional but superseded by MCP):
- `<spawn1337>path/to/task.md</spawn1337>` - Spawn request
- `<spawn1337-status agentId="id"/>` - Status query
- `<spawn1337-cancel agentId="id"/>` - Cancel request
- `<spawn1337-message agentId="id">content</spawn1337-message>` - Message to child
**Agent Communication Directory:**
```
~/claudeman-cases/spawn-<agentId>/
├── CLAUDE.md # Auto-generated agent instructions
├── spawn-comms/
│ ├── task.md # Copy of original task spec
│ ├── progress.json # Agent updates periodically
│ ├── result.md # Final result (YAML frontmatter + body)
│ └── messages/ # Bidirectional message files (NNN-parent.md, NNN-agent.md)
└── workspace/ # Symlinked context files
```
**Task Spec Format** (YAML frontmatter in .md file):
```yaml
---
agentId: my-agent-001
name: My Agent
type: explore # explore|implement|test|review|refactor|research|generate|fix|general
priority: high # low|normal|high|critical
maxTokens: 150000
maxCost: 0.50
timeoutMinutes: 15
canModifyParentFiles: false
contextFiles: [src/auth.ts, src/types.ts]
dependsOn: [other-agent-id]
completionPhrase: MY_AGENT_DONE
outputFormat: structured # markdown|json|code|structured|freeform
---
Task instructions here...
```
**Resource Governance:**
- Budget warning at 80% of token/cost limit
- Graceful shutdown message at 100%
- Force kill at 110% (or timeout + 60s grace)
- Max concurrent agents: 5 (configurable)
- Max spawn depth: 3 (prevents infinite recursion)
- Default timeout: 30 minutes (max: 120)
**Session Integration:** The MCP server communicates with the Claudeman API over HTTP. Legacy SpawnDetector (alongside RalphTracker) can still forward terminal data for tag-based spawning. Spawn events (`spawnRequested`, `spawnStatusRequested`, `spawnCancelRequested`, `spawnMessageToChild`) are emitted on the session and wired to the orchestrator in server.ts.
**Agent Tree:** Agents can spawn children (up to `maxSpawnDepth`). Sessions track `parentAgentId` and `childAgentIds`. Cancelling a parent cascades to all children.
### 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
### Screen-Aware Sessions
All Claude sessions spawned by Claudeman receive environment variables indicating they're running in a managed screen:
| Variable | Value | Purpose |
|----------|-------|---------|
| `CLAUDEMAN_SCREEN` | `1` | Indicates session is managed by Claudeman |
| `CLAUDEMAN_SESSION_ID` | `<uuid>` | Unique session identifier |
| `CLAUDEMAN_SCREEN_NAME` | `claudeman-<name>` | GNU screen session name |
This prevents Claude from accidentally killing its own screen session. The default CLAUDE.md template includes guidance about this.
**Implementation**: Set in `screen-manager.ts:createScreen()` for screen-based sessions and `session.ts:startInteractive()`/`startShell()` for PTY-only sessions.
## Code Patterns
### 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, '');
}
```
### Claude Message Parsing
Claude CLI outputs newline-delimited JSON. Strip ANSI codes before parsing:
```typescript
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
```typescript
// 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'], { ... })
// Shell mode (debugging/testing - no Claude CLI)
pty.spawn('bash', [], { ... })
```
### Sending Input to Sessions
There are two methods for sending input to Claude sessions:
#### 1. `session.write(data)` - Direct PTY write
Used by the `/api/sessions/:id/input` API endpoint. Writes directly to PTY.
```typescript
session.write('hello world'); // Text only, no Enter
session.write('\r'); // Enter key separately
```
#### 2. `session.writeViaScreen(data)` - Via GNU screen (RECOMMENDED for programmatic input)
Used by RespawnController, auto-compact, auto-clear. More reliable for Ink/Claude CLI.
```typescript
// Append \r to include Enter - the method handles splitting automatically
session.writeViaScreen('your command here\r');
session.writeViaScreen('/clear\r');
session.writeViaScreen('/init\r');
```
**How `writeViaScreen` works internally** (in `screen-manager.ts:sendInput`):
1. Splits input into text and `\r` (carriage return)
2. Sends text first: `screen -S name -p 0 -X stuff "text"`
3. Sends Enter separately: `screen -S name -p 0 -X stuff "$(printf '\015')"`
**Why separate commands?** Claude CLI uses [Ink](https://github.com/vadimdemedes/ink) (React for terminals) which requires text and Enter as separate `screen -X stuff` commands. Combining them doesn't work. This is a critical implementation detail when debugging input issues.
#### API Usage
```bash
# Send text (won't submit until Enter is sent)
curl -X POST localhost:3000/api/sessions/:id/input \
-H "Content-Type: application/json" \
-d '{"input": "your prompt here"}'
# Send Enter separately to submit
curl -X POST localhost:3000/api/sessions/:id/input \
-H "Content-Type: application/json" \
-d '{"input": "\r"}'
```
**Note**: The API uses `session.write()` which goes to PTY directly. For reliability with Ink, consider using the respawn controller pattern or adding an API endpoint that uses `writeViaScreen()`.
### Idle Detection
**RespawnController (Claude Code 2024+)**: Multi-layer detection with confidence scoring:
1. **Completion message**: Primary signal - detects "for Xm Xs" time patterns (e.g., "Worked for 2m 46s")
2. **Output silence**: Confirms idle after `completionConfirmMs` (5s) of no new output
3. **Token stability**: Tokens haven't changed
4. **Working patterns absent**: No `Thinking`, `Writing`, spinner chars
Uses `confirming_idle` state to prevent false positives. Fallback: `noOutputTimeoutMs` (30s) if no output at all.
**Step Confirmation**: After sending each respawn step (update, init, kickstart), the controller waits for the same `completionConfirmMs` silence before proceeding. This ensures Claude finishes processing each prompt before the next command is sent.
**Session**: emits `idle`/`working` events on prompt detection + 2s activity timeout.
**Auto-Accept Plan Mode** (enabled by default): When Claude enters plan mode and presents a plan for approval, output stops without a completion message. After `autoAcceptDelayMs` (8s) of silence with no completion message and no `elicitation_dialog` hook signal detected, sends Enter to accept the plan. Does NOT auto-accept AskUserQuestion prompts — those are blocked via the `elicitation_dialog` notification hook which signals the respawn controller to skip auto-accept. Safety: only fires once per silence period, requires prior output, won't fire during active respawn cycles.
### Token Tracking
- **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
### 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()`.
### Ralph / Todo Tracking
Detects Ralph loops and todos inside Claude sessions. **Disabled by default** but auto-enables when any of these patterns are detected in terminal output:
- `/ralph-loop:ralph-loop` command
- `<promise>PHRASE</promise>` completion phrases (supports hyphens: `TESTS-PASS`, underscores: `ALL_DONE`, numbers: `TASK_123`)
- `TodoWrite` tool usage (including checkmark format: `✔ Task #N created:`, `✔ Task #N updated: status →`)
- Iteration patterns (`Iteration 5/50`, `[5/50]`)
- Todo checkboxes (`- [ ]`/`- [x]`) or indicator icons (`☐`/`◐`/`✓`)
- "All tasks complete" messages
- Individual task completion signals (`Task 8 is done`)
See `ralph-tracker.ts:shouldAutoEnable()` for detection logic.
**Auto-Configuration from Ralph Plugin State**: When a session starts, Claudeman reads `.claude/ralph-loop.local.md` (the official Ralph Wiggum plugin state file) to auto-configure the tracker:
```yaml
---
enabled: true
iteration: 5
max-iterations: 50
completion-promise: "COMPLETE"
---
```
Priority order for configuration:
1. `.claude/ralph-loop.local.md` (official Ralph plugin state)
2. `CLAUDE.md` `<promise>` tags (fallback)
See `src/ralph-config.ts` for parsing logic.
**Completion Detection**: Uses multi-strategy detection:
- 1st occurrence of `<promise>PHRASE</promise>`: Stores as expected phrase (likely in prompt)
- 2nd occurrence: Emits `completionDetected` event (actual completion)
- **Bare phrase detection**: Also detects phrase without tags once expected phrase is known
- **All complete detection**: When "All X files/tasks created/completed" detected, marks all todos complete and emits completion
- If loop is already active (via `/ralph-loop:ralph-loop`): Emits immediately on first occurrence
**Session Lifecycle**: Each session has its own independent tracker:
- New session → Fresh tracker (no carryover)
- Close tab → Tracker state cleared, UI panel hides
- Switch tabs → Panel shows tracker for active session
- `tracker.reset()` → Clears todos/state, keeps enabled status
- `tracker.fullReset()` → Complete reset to initial state
**API**:
- `GET /api/sessions/:id/ralph-state` - Get loop state and todos
- `POST /api/sessions/:id/ralph-config` - Configure tracker:
- `{ enabled: boolean }` - Enable/disable
- `{ reset: true }` - Soft reset (keep enabled)
- `{ reset: "full" }` - Full reset
UI: Collapsible panel below tabs, shows progress ring and todo list.
### 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:`, `spawn:`, `hook:`, `scheduled:`, `case:`, `screen:`, `init`.
Key events for frontend handling (see `app.js:handleSSEEvent()`):
- `session:idle`, `session:working` - Status indicator updates
- `session:terminal`, `session:clearTerminal` - Terminal content
- `session:completion`, `session:autoClear`, `session:autoCompact` - Lifecycle events
- `session:ralphLoopUpdate`, `session:ralphTodoUpdate`, `session:ralphCompletionDetected` - Ralph tracking
- `respawn:detectionUpdate` - Multi-layer idle detection status (confidence level, waiting state)
- `spawn:queued`, `spawn:started`, `spawn:completed`, `spawn:failed`, `spawn:timeout`, `spawn:cancelled` - Agent lifecycle
- `spawn:progress`, `spawn:message`, `spawn:budgetWarning`, `spawn:stateUpdate` - Agent monitoring
- `hook:idle_prompt`, `hook:permission_prompt`, `hook:elicitation_dialog`, `hook:stop` - Claude Code hooks (desktop notifications)
### Frontend (app.js)
Vanilla JS + xterm.js. Key functions:
- `handleSSEEvent()` - Dispatches events to appropriate handlers
- `switchToSession()` - Tab management and terminal focus
- `createSessionTab()` - Tab creation and xterm setup
**60fps Rendering Pipeline**:
- Server batches terminal data every 16ms before broadcasting via SSE
- Client uses `requestAnimationFrame` to batch xterm.js writes
- Prevents UI jank during high-throughput Claude output
### HTTPS & Browser Notifications
**HTTPS**: The `--https` flag generates/reuses self-signed certificates in `~/.claudeman/certs/` (`server.key`, `server.crt`). Required for the Web Notification API in browsers.
**Notification Layers** (in `app.js`, `NotificationManager` class):
1. In-app drawer with notification list
2. Tab title flashing with unread count (when tab unfocused)
3. Web Notification API (browser push notifications)
4. Audio alerts (critical level only)
Notifications triggered for: session events, respawn updates, spawn agent lifecycle. Preferences persist to server-side `state.json` per session.
### State Store
Writes debounced (500ms) to `~/.claudeman/state.json`. The web server persists full session state via `persistSessionState()` on every meaningful change:
**Persistence triggers**: session create, delete, rename, settings change (auto-compact, auto-clear, Ralph config), respawn start/stop/update/expire, completion, exit.
**Per-session fields stored** (`SessionState` in `types.ts`):
| Field | Description |
|-------|-------------|
| `id`, `pid`, `status` | Core session identity |
| `name`, `mode` | Display name, 'claude' or 'shell' |
| `workingDir`, `createdAt`, `lastActivityAt` | Location and timestamps |
| `autoClearEnabled`, `autoClearThreshold` | Auto-clear settings |
| `autoCompactEnabled`, `autoCompactThreshold`, `autoCompactPrompt` | Auto-compact settings |
| `ralphEnabled`, `ralphCompletionPhrase` | Ralph / Todo tracker state |
| `respawnEnabled` | Whether respawn controller is currently running |
| `respawnConfig` | Full respawn config including `durationMinutes` |
| `totalCost`, `inputTokens`, `outputTokens` | Token and cost tracking |
| `parentAgentId`, `childAgentIds` | Spawn agent tree relationships |
**CLI visibility**: The `claudeman status` and `claudeman session list` commands read from `state.json` to display web server-managed sessions, even though they run in a separate process.
### Timing Constants
| Constant | Value | Location |
|----------|-------|----------|
| State save debounce | 500ms | `state-store.ts` |
| State update debounce | 500ms | `server.ts` |
| Line buffer flush | 100ms | `session.ts` |
| Terminal batch interval | 16ms | `server.ts` (60fps) |
| Output batch interval | 50ms | `server.ts` |
| Task update batch interval | 100ms | `server.ts` |
| Ralph loop event debounce | 50ms | `ralph-tracker.ts` |
| Session tabs render debounce | 100ms | `app.js` |
| Ralph panel render debounce | 50ms | `app.js` |
| Task panel render debounce | 100ms | `app.js` |
| Input batch interval | 16ms | `app.js` (60fps) |
| Idle activity timeout | 2s | `session.ts` |
| Respawn idle timeout | 10s default | `RespawnConfig.idleTimeoutMs` |
| Respawn completion confirm | 5s | `RespawnConfig.completionConfirmMs` |
| Respawn auto-accept delay | 8s | `RespawnConfig.autoAcceptDelayMs` |
| Respawn no-output fallback | 30s | `RespawnConfig.noOutputTimeoutMs` |
| Spawn event debounce | 50ms | `spawn-detector.ts` |
| Spawn line buffer max | 64KB | `spawn-detector.ts` |
| Spawn progress poll | 5s | `spawn-orchestrator.ts` |
| Spawn default timeout | 30 min | `spawn-orchestrator.ts` |
| Spawn max timeout | 120 min | `spawn-orchestrator.ts` |
| Spawn max concurrent | 5 agents | `spawn-orchestrator.ts` |
| Spawn max depth | 3 levels | `spawn-orchestrator.ts` |
| Spawn budget warning | 80% | `spawn-orchestrator.ts` |
| Spawn budget grace | 60s | `spawn-orchestrator.ts` |
### TypeScript Config
Module resolution: NodeNext. Target: ES2022. Strict mode with additional checks:
| Setting | Effect |
|---------|--------|
| `noUnusedLocals` | Error on unused local variables |
| `noUnusedParameters` | Error on unused function parameters |
| `noImplicitReturns` | All code paths must return a value |
| `noImplicitOverride` | Require `override` keyword for overridden methods |
| `noFallthroughCasesInSwitch` | Require break/return in switch cases |
| `allowUnreachableCode: false` | Error on unreachable code |
| `allowUnusedLabels: false` | Error on unused labels |
TUI uses React JSX (`jsxImportSource: react`) for Ink components.
## Adding New Features
- **API endpoint**: Add types in `types.ts`, route in `server.ts:buildServer()`, use `createErrorResponse()` for errors
- **SSE event**: Emit via `broadcast()` in server.ts, handle in `app.js:handleSSEEvent()` switch
- **Session event**: Add to `SessionEvents` interface in `session.ts`, emit via `this.emit()`, subscribe in server.ts, handle in frontend
- **Session setting**: Add field to `SessionState` in `types.ts`, include in `session.toState()`, call `this.persistSessionState(session)` in server.ts after the change
- **MCP tool**: Add tool definition in `mcp-server.ts` using `server.tool()`, use `apiRequest()` to call Claudeman REST API
- **New test file**: Create `test/<name>.test.ts`, pick unique port (next available: 3127+), add to port allocation comment above
### API Error Codes
Use `createErrorResponse(code, details?)` from `types.ts`:
| Code | Use Case |
|------|----------|
| `NOT_FOUND` | Session/resource doesn't exist |
| `INVALID_INPUT` | Bad request parameters |
| `SESSION_BUSY` | Session is currently processing |
| `OPERATION_FAILED` | Action couldn't complete |
| `ALREADY_EXISTS` | Duplicate resource |
| `INTERNAL_ERROR` | Unexpected server error |
## Session Lifecycle & Cleanup
- **Limit**: Web server: `MAX_CONCURRENT_SESSIONS = 50` (`server.ts:56`), UI tab limit: 20, CLI default: 5 (`types.ts:DEFAULT_CONFIG`)
- **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, removes from `state.json`
- **State sync**: Every session create/delete/update calls `persistSessionState()` which writes full state (including respawn config from controller) to `state.json`
- **Recovery on restart**: Server reads `state.json` first (has all settings), falls back to `screens.json` for any sessions not found. Settings (auto-compact, auto-clear, respawn config, Ralph state) restored to live session objects after screen reattachment.
## TUI Architecture (Ink/React)
The TUI (`claudeman tui`) is built with [Ink](https://github.com/vadimdemedes/ink) (React for terminals).
**Component Hierarchy:**
```
App.tsx
├── StartScreen.tsx # Session/case list, navigation
│ ├── List navigation (↑/↓, Enter, a/d/D)
│ ├── Case creation flow
│ └── Tab switcher menu
├── DirectAttach.ts # Full-screen console attach with tab switching
├── TabBar.tsx # Session tabs (when attached)
├── TerminalView.tsx # Viewport into screen session
├── StatusBar.tsx # Bottom bar with status/tokens
├── RalphPanel.tsx # Ralph loop progress display
└── HelpOverlay.tsx # Keyboard shortcuts modal
```
**Key Hook:** `useSessionManager.ts` handles:
- API polling for sessions/screens
- Screen attach/detach flow
- Keyboard input routing
- State synchronization with web server
**TUI ↔ Web Server:** The TUI is a client to the web server. It doesn't manage sessions directly - it uses `/api/*` endpoints and attaches to screens via GNU screen commands.
## Buffer Limits
Long-running sessions are supported with automatic trimming:
| Buffer | Max Size | Trim To |
|--------|----------|---------|
| Terminal | 2MB | 1.5MB |
| Text output | 1MB | 768KB |
| Messages | 1000 | 800 |
| Line buffer | 64KB | (flushed every 100ms) |
| Respawn buffer | 1MB | 512KB |
**Performance optimizations:**
- Tab switch uses `tail=256KB` for fast initial load, then chunked writes
- Large buffers written in 64KB chunks via `requestAnimationFrame` to avoid UI jank
- Truncation indicator shown when earlier output is cut
## 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 (merges pre-saved config) |
| POST | `/api/sessions/:id/respawn/stop` | Stop respawn controller |
| POST | `/api/sessions/:id/respawn/enable` | Enable respawn with config + optional timer |
| GET | `/api/sessions/:id/respawn/config` | Get current or pre-saved respawn config |
| PUT | `/api/sessions/:id/respawn/config` | Update config (works with or without running controller) |
| POST | `/api/sessions/:id/ralph-config` | Configure Ralph / Todo Tracker settings |
| GET | `/api/sessions/:id/ralph-state` | Get Ralph loop state + todos |
| POST | `/api/sessions/:id/auto-compact` | Configure auto-compact threshold |
| POST | `/api/sessions/:id/auto-clear` | Configure auto-clear threshold |
| POST | `/api/quick-start` | Create case + start session. Body: `{mode?: 'claude'\|'shell'}` |
| GET | `/api/cases` | List available cases |
| POST | `/api/cases` | Create new case |
| GET | `/api/screens` | List screen sessions with stats |
| GET | `/api/spawn/agents` | List all spawn agents (active + completed + queued) |
| GET | `/api/spawn/agents/:agentId` | Detailed agent status + progress |
| GET | `/api/spawn/agents/:agentId/result` | Read agent's result.md |
| GET | `/api/spawn/agents/:agentId/messages` | List messages in channel |
| POST | `/api/spawn/agents/:agentId/message` | Send message to agent |
| POST | `/api/spawn/agents/:agentId/cancel` | Cancel agent (graceful stop) |
| DELETE | `/api/spawn/agents/:agentId` | Force kill + cleanup |
| GET | `/api/spawn/status` | Orchestrator status (counts, config) |
| PUT | `/api/spawn/config` | Update orchestrator config |
| POST | `/api/spawn/trigger` | Programmatic spawn (bypass terminal detection) |
| POST | `/api/hook-event` | Receive Claude Code hook callbacks (idle_prompt, permission_prompt, elicitation_dialog, stop) |
## Keyboard Shortcuts (Web UI)
| Shortcut | Action |
|----------|--------|
| `Ctrl+Enter` | Run Claude (create case + interactive session) |
| `Ctrl+W` | Close current session |
| `Ctrl+Tab` | Switch to next session |
| `Ctrl+K` | Kill all sessions |
| `Ctrl+L` | Clear terminal |
| `Ctrl++/-` | Increase/decrease font size |
| `Ctrl+?` | Show keyboard shortcuts help |
| `Escape` | Close panels and modals |
## TUI Keyboard Shortcuts
**Start Screen - Sessions:**
| Key | Action |
|-----|--------|
| `↑`/`↓` | Navigate list |
| `Enter` | Open tab switcher menu, then full-screen attach |
| `a` | Direct attach (skip tab menu, Ctrl+A D returns to TUI) |
| `d` | Delete/kill selected session |
| `D` (Shift+d) | Delete ALL screens & Claude processes |
| `c` | Switch to cases view |
| `n` | Quick-start new session |
| `r` | Refresh list |
| `q` | Quit TUI |
**Start Screen - Cases:**
| Key | Action |
|-----|--------|
| `↑`/`↓` | Navigate list |
| `Enter` | Start Claude session with selected case |
| `h` | Start Shell session with selected case |
| `m` | Multi-start (1-20 sessions at once) |
| `n` | Create new case |
| `s` | Switch to sessions view |
| `r` | Refresh list |
**Tab Switcher Menu (between attaches):**
| Key | Action |
|-----|--------|
| `1-9` | Select and attach to session N |
| `Enter` | Attach to current session |
| `q` / `Esc` | Return to TUI start screen |
**While attached to screen:**
| Key | Action |
|-----|--------|
| `Ctrl+A D` | Detach and return to tab switcher |
## State Files
| File | Purpose |
|------|---------|
| `~/.claudeman/state.json` | Full session state (all settings, tokens, respawn config, Ralph state), tasks, app config |
| `~/.claudeman/state-inner.json` | Ralph loop/todo state per session (separate to reduce writes) |
| `~/.claudeman/screens.json` | Screen session metadata (for recovery after restart) |
| `~/.claudeman/settings.json` | User preferences (lastUsedCase, custom template path) |
| `~/.claudeman/certs/` | Self-signed TLS certificates for `--https` mode |
**State lifecycle**:
- Web server creates → session added to `state.json` + `screens.json`
- Settings change → `state.json` updated (debounced 500ms)
- Session deleted → removed from `state.json` (screen may survive if `killScreen: false`)
- Server shutdown → sessions preserved in `state.json` (not wiped), screens preserved in `screens.json`
- Server restart → sessions restored from `state.json` (primary) with `screens.json` as fallback
**Recovery strategy** (double redundancy):
1. **Primary**: `state.json` retains full session state across restarts (settings, tokens, respawn config)
2. **Fallback**: `screens.json` provides screen metadata if `state.json` is missing a session
3. After restoration, all settings (auto-compact, auto-clear, respawn, Ralph) are re-applied to live sessions
4. `persistSessionState()` called after all restorations complete to sync final state
Cases created in `~/claudeman-cases/` by default.
### CLAUDE.md Templates
New cases get a CLAUDE.md generated from `src/templates/case-template.md` (bundled with the project). Template resolution order:
1. Custom path from `~/.claudeman/settings.json` (`defaultClaudeMdPath` field)
2. Bundled `case-template.md` (copied to `dist/templates/` during build)
3. Minimal fallback (if bundled template is missing)
Placeholders replaced:
- `[PROJECT_NAME]` → Case name
- `[PROJECT_DESCRIPTION]` → Description
- `[DATE]` → Current date (YYYY-MM-DD)
## Screen Session Manager (CLI Tool)
Interactive bash tool for managing claudeman screen sessions directly from the terminal.
```bash
./scripts/screen-manager.sh # Interactive mode
./scripts/screen-manager.sh list # List all sessions
./scripts/screen-manager.sh attach 1 # Attach to session #1
./scripts/screen-manager.sh kill 2,3 # Kill sessions 2 and 3
./scripts/screen-manager.sh kill-all # Kill all sessions
./scripts/screen-manager.sh info 1 # Show session #1 details
```
**Interactive Controls:**
| Key | Action |
|-----|--------|
| `↑`/`↓` or `j`/`k` | Navigate sessions |
| `Enter` | Attach to selected session |
| `d` | Delete selected session |
| `D` | Delete ALL sessions |
| `i` | Show session info |
| `q`/`Esc` | Quit |
**Features:**
- Reads from `~/.claudeman/screens.json` (claudeman's authoritative source)
- Shows session name, running time, alive/dead status, mode
- Flicker-free navigation (only updates changed rows)
- Requires `jq` and `screen` to be installed
## Documentation
Extended documentation is available in the `docs/` directory:
| Document | Description |
|----------|-------------|
| [`docs/ralph-wiggum-guide.md`](docs/ralph-wiggum-guide.md) | Complete Ralph Wiggum loop guide: official plugin reference, best practices, prompt templates, troubleshooting |
| [`docs/claude-code-hooks-reference.md`](docs/claude-code-hooks-reference.md) | Official Claude Code hooks documentation: all events, configuration, examples |
### Quick Reference: Ralph Wiggum Loops
**Core Pattern**: `<promise>PHRASE</promise>` - The completion signal that tells the loop to stop.
**Skill Commands**:
```bash
/ralph-loop:ralph-loop # Start Ralph Loop in current session
/ralph-loop:cancel-ralph # Cancel active Ralph Loop
/ralph-loop:help # Show help and usage
```
**Claudeman Implementation**: The `RalphTracker` class (`src/ralph-tracker.ts`) detects Ralph patterns in Claude output and tracks loop state, todos, and completion phrases. It auto-enables when Ralph-related patterns are detected.
See [`docs/ralph-wiggum-guide.md`](docs/ralph-wiggum-guide.md) for full documentation on best practices, prompt templates, and troubleshooting.