This document is the comprehensive feature reference for Codeman v0.5.4 (SGudbrandsson fork). It covers every major feature with configuration details, technical specifics, and relevant API endpoints. For installation and quick-start, see README.md. For architecture and development guidance, see CLAUDE.md.
- Multi-Session Dashboard
- Respawn Controller
- Ralph Loop / Todo Tracking
- Zero-Lag Input Overlay
- Mobile-Optimized UI
- Voice Input
- Live Agent Visualization
- Multi-Layer Notifications
- Remote Access & QR Authentication
- Agent Teams (Experimental)
- Token Management & Auto-Compact
- Slash Commands & Plugin System
- Keyboard Shortcuts
- Case Management
- Run Summary & Analytics
- SSH Session Chooser (
sc) - Plan Mode Auto-Accept
- Terminal Anti-Flicker Pipeline
- Command Panel
- Security
- API Reference
- Session Harnesses
- Fork Differences vs Ark0N/Codeman v0.3.7
Codeman's primary view manages up to 50 concurrent agent sessions from a single browser tab — Claude Code, OpenCode, Codex, Pi, or a plain shell, mixed freely. Each session is a full xterm.js terminal backed by a persistent tmux pane, rendering at 60fps via SSE streaming.
- Up to 50 concurrent sessions — configurable limit in
src/config/map-limits.ts(MAX_CONCURRENT_SESSIONS) - Real-time xterm.js terminals — full ANSI/VT100 support, 500-line scrollback (configurable per session)
- Tab-based navigation — each session gets a tab with live status indicators: token count, cost, agent count badge, and blink alerts for attention-required states
- Per-session token and cost tracking — reads Claude Code's JSON message stream; tracks input tokens, output tokens, cache tokens, and cumulative USD cost
- tmux-backed persistence — sessions survive server restarts, network drops, and machine sleep; auto-recovered on startup
- Pause / resume (Claude sessions only) — park a session from the gear menu: Claude and its tmux session are stopped (freeing the memory a long-lived session was holding) while the session entry, name, worktree and conversation id are kept. Resume relaunches with
--resumeand picks up where it left off. Paused sessions survive a server restart and are skipped by every background loop (respawn, Ralph, orchestrator, idle checks), so parking 20 sessions costs nothing until you come back to them - Five session harnesses — Claude Code, OpenCode, Codex, Pi or a plain shell, chosen per session; see Session Harnesses
- Ghost session discovery — finds orphaned tmux sessions from previous runs and reattaches them
- Managed session tagging —
CODEMAN_MUX=1environment variable prevents the agent from killing its own tmux session - Quick-start —
Ctrl+Enteror the Start button creates a session from the last-used case
The respawn controller is the engine for unattended autonomous work. When a session goes idle, it runs a configurable command sequence to summarize progress, reset context, re-initialize, and continue — enabling 24+ hour continuous runs.
WATCHING → CONFIRMING_IDLE → AI_CHECKING → SENDING_UPDATE → WAITING_UPDATE
→ SENDING_CLEAR → WAITING_CLEAR → SENDING_INIT → WAITING_INIT
→ MONITORING_INIT → SENDING_KICKSTART → WAITING_KICKSTART → (back to WATCHING)
The controller uses six independent signals to confirm genuine idle state before triggering the respawn sequence:
- Completion message — detects the "Worked for Xm Xs" time pattern (requires "Worked" prefix to avoid false positives)
- AI idle check — spawns a fresh
claude-opus-4-5-20251101session in tmux to analyze the last 16,000 characters of terminal output and return an IDLE/WORKING verdict; conservative by design (defaults to WORKING when uncertain); 90s timeout, 3-minute cooldown after WORKING, auto-disables after 3 consecutive errors - Output silence — waits for
completionConfirmMs(10s) of no new output - Token stability — token count unchanged
- Working patterns absent — no
Thinking,Writing,Running, spinner chars, or 20+ other Ink activity patterns for at least 8 seconds, checked in a rolling 300-character window - Session.isWorking check — final gate: if the Session class reports
isWorking=true, idle confirmation is rejected
After idle is confirmed, the controller runs (in order, each step waits for 10s of output silence before proceeding):
| Step | Config flag | Default |
|---|---|---|
| Send update prompt | always | Summarize progress |
Send /clear |
sendClear: true |
Resets context |
Send /init |
sendInit: true |
Reinitializes with CLAUDE.md |
| Send kickstart prompt | kickstartPrompt |
Continue from context |
| Preset ID | Name | Idle Timeout | Max Duration | Use Case |
|---|---|---|---|---|
solo-work |
Solo | 3s | 60 min | Single Claude working alone |
subagent-workflow |
Subagents | 45s | 240 min | Lead session with Task tool |
team-lead |
Team | 90s | 480 min | Agent team coordination |
ralph-todo |
Ralph/Todo | 8s | 480 min | Ralph Loop with task list |
overnight-autonomous |
Overnight | 10s | 480 min | Unattended overnight runs |
Prevents respawn thrashing when Claude is genuinely stuck:
- CLOSED → normal operation
- HALF_OPEN → monitoring after issues
- OPEN → respawn suspended; manual reset via
POST /api/sessions/:id/ralph-circuit-breaker/reset
Tracks consecutive no-progress cycles and repeated errors to determine circuit state.
0–100 composite score with component scores for: cycle success rate, circuit breaker state, iteration progress toward goal, and stuck-recovery history. Visible in the session tab and run summary.
POST /api/sessions/:id/respawn/enable # Enable with config + timer
POST /api/sessions/:id/respawn/stop # Stop controller
PUT /api/sessions/:id/respawn/config # Update config live
Deeper reference: docs/respawn-state-machine.md
Codeman implements first-class tracking for the Ralph Wiggum autonomous loop technique — detecting promise tags, todo progress, and iteration counts from terminal output, and surfacing them as a real-time progress panel.
Ralph Wiggum is an autonomous iteration technique: Claude processes a prompt, a Stop hook intercepts exit, checks for a completion promise tag, and restarts the prompt if not found. Previous work persists in files, enabling autonomous improvement. Created by Geoffrey Huntley, formalized as an official Anthropic plugin.
The RalphTracker class (src/ralph-tracker.ts) automatically enables when it detects any of:
| Signal | Example |
|---|---|
| Ralph command | /ralph-loop:ralph-loop |
| Promise tag | <promise>COMPLETE</promise> |
| TodoWrite output | Todos have been modified |
| Iteration counter | Iteration 5/50 or [5/50] |
| Todo checkbox | - [ ] Task name |
| Todo indicator | Todo: ☐ Task name |
- Markdown checkboxes:
- [ ],- [x],- [X] - Status indicators:
Todo: ☐,Todo: ◐,Todo: ✓ - Parenthetical status:
(pending),(in_progress),(completed) - Native checkboxes:
☐,◐,☒ - Claude Code TodoWrite output:
✔ Task #1 created: ...,✔ Task #1 updated: status → completed
Occurrence-based detection distinguishes the completion phrase in the prompt from actual completion output:
- 1st occurrence → stores as expected phrase (likely in the prompt itself)
- 2nd occurrence → emits
completionDetected(actual completion) - If loop is already active → first occurrence triggers immediately
/ralph-loop:ralph-loop # Start Ralph Loop in current session
/ralph-loop:cancel-ralph # Cancel active loop
/ralph-loop:help # Show usageGET /api/sessions/:id/ralph-state # Loop state + todo list + stats
POST /api/sessions/:id/ralph-config # Configure: enable, reset, completionPhrase
| Event | When |
|---|---|
session:ralphLoopUpdate |
Loop state changes |
session:ralphTodoUpdate |
Todos detected or updated |
session:ralphCompletionDetected |
Completion phrase found |
Deeper reference: docs/ralph-wiggum-guide.md
Over high-latency connections (VPN, Tailscale, SSH tunnel), keystroke round-trips add 200–300ms of perceived lag. The zero-lag input overlay eliminates this by rendering typed characters instantly as a pixel-perfect DOM overlay inside xterm.js.
- Mosh-inspired local echo — characters appear at 0ms; PTY forwarding happens in 50ms debounced batches in the background
- Ink-proof architecture — implemented as a
<span>atz-index: 7inside.xterm-screen; completely immune to Ink's constant full-screen redraws (two previous approaches usingterminal.write()failed because Ink corrupts injected buffer content) - Font-matched rendering — reads
fontFamily,fontSize,fontWeight, andletterSpacingfrom xterm.js computed styles; overlay text is visually indistinguishable from real terminal output - Full editing support — backspace, retype, paste (multi-char), cursor tracking, multi-line wrap when input exceeds terminal width
- Seamless hand-off — when the server echo arrives 200–300ms later, the overlay disappears and real terminal text takes over; transition is invisible
- Persistent across reconnects — unsent input survives page reloads via localStorage
- Enabled by default — works on desktop and mobile, during idle and busy sessions
The overlay is extracted as a standalone npm package:
npm install xterm-zerolag-inputSee the xterm-zerolag-input package for standalone usage.
Codeman's mobile interface is purpose-built for real remote work from a phone — not a desktop UI squeezed onto a small screen.
A persistent bar above the virtual keyboard provides quick-action buttons:
- Default buttons:
tab,scroll-up,scroll-down,commands,paste,copy - Configurable via
hotbarButtonsin localStorage settings - Destructive commands require double-press confirmation (first tap arms, second tap executes) — prevents accidental firing on a bumpy commute
barHeight= 124px when accessory bar visible, 40px when hidden
Tap the pencil icon to open a full-width auto-growing textarea above the keyboard (z-index: 52, position: fixed):
- Auto-grow textarea — expands as you type, collapses when cleared
- Image attachments — tap the
+button (bottom-left inset) for an action sheet: Take Photo, Photo Library, or Attach File; multiple images attach as thumbnails with tap-to-preview, long-press-to-replace, and×to remove - Send button — dispatches all queued images then the text; clears the panel and sends a trailing
\r - Slash command popup — type
/to see built-in Claude commands merged with session-specific plugin commands; supports substring and subsequence matching (see Section 12)
Slide-out session navigation drawer (this fork only):
- Swipe navigation — swipe left/right on the terminal to switch sessions (80px threshold, 300ms)
- Session drawer — slide-out panel showing all sessions with status indicators
- Drawer tab swiping — swipe left/right inside the drawer to switch between Sessions and Agents tabs with a sliding animation (30px threshold, velocity-aware)
- Uses
visualViewportAPI with 100px threshold to detect iOS address bar drift - Toolbar and terminal shift up when the keyboard opens via three layout paths:
updateLayout,resetLayout, and resize-mode detection for Android - Keyboard state is preserved on tab switch (this fork fixes a double-accounting bug in the upstream)
- Safe area support: respects iPhone notch and home indicator via
env(safe-area-inset-*)
- 44px touch targets — all buttons meet iOS Human Interface Guidelines minimum sizes
- Bottom sheet case picker — slide-up modal replaces the desktop dropdown
- Native momentum scrolling —
-webkit-overflow-scrolling: touch - GSD Status Strip — pinned persistent strip above the accessory bar showing current task status (this fork only)
- Optional persistent compose panel — toggle to always show the input panel rather than auto-hide it
Access over HTTPS (required for non-localhost):
codeman web --https
# Or use Tailscale for private network access without TLS certificatesDictate commands and prompts using the browser's Web Speech API — useful for hands-free operation or when typing on mobile is inconvenient.
- Web Speech API — uses the browser's native speech recognition (no external API key required)
- Microphone button — appears in the input area; tap to start, tap again to stop
- Auto-send option — configurable to automatically send the transcribed text when speech ends
- Language support — inherits the browser's configured speech recognition language
- Mobile optimized — integrated into the mobile compose panel and accessory bar
Enable: tap the microphone icon in the compose panel or accessory bar. Requires browser microphone permission.
Every assistant message in the transcript has a speaker button, and the files sheet can read a markdown document aloud. Both run on the same engine (tts-engine.js) and the same controller (ReadAloud).
- Paragraph streaming — the rendered text is split into blocks (one paragraph, heading or list item) and synthesised as a pipeline: the first block starts playing while the rest are still being fetched, so a long reply starts speaking in ~100 ms instead of after the whole answer is generated
- Natural cut points — blocks are never merged, so the pause between two paragraphs is a real pause. A block longer than one request splits only at sentence boundaries; nothing is cut mid-sentence
- Playback bar — appears while reading: play/pause, previous/next block, click-to-seek and a block counter. Position is counted in blocks, not seconds, because per-block durations aren't known until each block is fetched
- Start anywhere — select text in a message or document and a ▶ Play from here pill appears; tap it to start at that block. (Selecting first and then pressing the message's play button, or the files sheet's ▶ Listen, does the same thing.) If something is already playing and the selected block is part of it, the pill seeks instead of restarting — so it doubles as a precise scrub
- Block highlighting — the block being spoken is highlighted and scrolled into view, in both the transcript and the files sheet
- OS media controls — hardware keys, the lock screen and Chrome's media popup control playback via the MediaSession API (audio providers only; the browser-speech fallback has no media element)
- Deepgram Aura voices — if a Deepgram API key is set (the same key used for dictation), synthesis goes straight from the browser to
api.deepgram.com; pick a voice under Settings → Voice - Automatic fallback — Deepgram → the server's
/api/ttsedge-tts proxy → the browser's built-in speech engine. The working provider is pinned for the rest of that session, and a single failed block is skipped rather than stranding the text - Reads what you see — inline code is spoken as part of the sentence; fenced code blocks are skipped; YAML frontmatter is stripped from markdown documents before rendering, so metadata is neither displayed nor read
- Offline — set Settings → Voice → Engine to "Browser speech" to skip the network entirely
Configure: Settings → Voice → Read Aloud. Server-side voice for the edge-tts fallback is set with CODEMAN_TTS_URL, CODEMAN_TTS_KEY and CODEMAN_TTS_VOICE.
Watch background subagents work in real-time. Codeman monitors Claude Code's subagent transcript files and renders each agent as a draggable floating window with animated connection lines back to the parent session.
- Floating terminal windows — draggable, resizable panels for each agent showing a live activity log of every tool call, file read, and progress update
- Connection lines — animated green SVG lines linking parent sessions to child agents; update in real-time as agents spawn and complete
- Status & model badges — green (active), yellow (idle), blue (completed) color coding; Haiku/Sonnet/Opus model color coding
- Auto-behavior — windows auto-open on spawn, auto-minimize on completion; tab badge shows "AGENT" or "AGENTS (n)"
- Nested agent hierarchies — supports 3-level nesting: lead session → teammate agents → sub-subagents
- Z-index layers — subagent windows at 1000, plan agents at 1100, log viewers at 2000, image popups at 3000
SubagentWatcher polls ~/.claude/projects/*/subagents/agent-*.jsonl files, parsing the JSONL transcript to extract tool calls, tool results, and status. Updates are broadcast via SSE to all connected clients.
GET /api/subagents # List all background agents
GET /api/subagents/:id # Agent info and current status
GET /api/subagents/:id/transcript # Full JSONL activity transcript
DELETE /api/subagents/:id # Kill agent process
Codeman delivers alerts through multiple channels so you never miss a session that needs attention.
- Tab blink alerts — red blink for
permission_promptandelicitation_dialog(requires immediate action); yellow blink foridle_prompt; click any blinking tab to jump directly to it - Browser push notifications — Web Push API; subscribe via Settings; notifies even when the browser tab is closed or in the background
- Browser tab title — title flashes with the session name when attention is required
- Notification list — in-app notification panel with up to 100 entries; grouped by session within a 5-second window; auto-closes browser notifications after 8 seconds
- Rate limiting — browser notifications rate-limited to one per 3 seconds to avoid flooding
- Hook-triggered — permission prompts and elicitation dialogs arrive via Claude Code hooks at
/api/hook-event; hooks are auto-configured per case directory
# Generate VAPID keys (one-time)
POST /api/push/generate-keys
# Subscribe a device
POST /api/push/subscribe
# Test delivery
POST /api/push/testAccess Codeman from outside your local network using a free Cloudflare quick tunnel — no port forwarding, no DNS, no static IP required.
./scripts/tunnel.sh start # Start tunnel, prints public URL
./scripts/tunnel.sh url # Show current URL
./scripts/tunnel.sh stop # Stop tunnel
./scripts/tunnel.sh status # Service status + URLThe script auto-installs a systemd user service on first run. The tunnel URL is a randomly generated *.trycloudflare.com address.
Always set CODEMAN_PASSWORD before exposing via tunnel.
Alternatively, Tailscale provides a private mesh network — access via http://<tailscale-ip>:3001 without TLS certificates.
Typing passwords on a phone keyboard is miserable. Codeman replaces it with cryptographically secure single-use QR tokens.
Flow:
Desktop displays QR → Phone scans → GET /q/Xk9mQ3 → Server validates
→ Token atomically consumed → Session cookie issued → 302 to /
→ Desktop notified: "Device authenticated via QR" → New QR auto-generated
Token design:
- 256-bit secret (
crypto.randomBytes(32)) paired with a 6-character base62 short code - Short code is an opaque lookup key — the real secret never appears in browser history,
Refererheaders, or Cloudflare edge logs - Auto-rotates every 60 seconds; previous token valid for 90-second grace period
- Atomically consumed on first scan — replay attacks always fail
Security properties:
| USENIX Security 2025 Flaw | Mitigation |
|---|---|
| Missing single-use enforcement | Atomic consumption — replays always fail |
| Long-lived tokens | 60s TTL, 90s grace, auto-rotation |
| Predictable token generation | crypto.randomBytes(32), rejection sampling eliminates modulo bias |
| Client-side generation | Server-side only — token never leaves server until embedded in QR |
| Missing status notification | Desktop toast with IP + browser: "Not you? [Revoke]" |
| Inadequate session binding | IP + User-Agent audit, HttpOnly/Secure/SameSite=lax, manual revocation |
Rate limiting (dual layer):
- Per-IP: 10 failed QR attempts → 429 block (15-minute decay)
- Global: 30 QR attempts per minute across all IPs
With 62^6 = 56.8 billion possible codes and ~2 valid at any time, brute force is computationally infeasible.
Full security analysis: docs/qr-auth-plan.md
- HTTP Basic Auth via
CODEMAN_USERNAME/CODEMAN_PASSWORDenvironment variables - On success:
codeman_sessioncookie (24h TTL, auto-extends on activity) - 10 failed attempts per IP → 429 rate limit (15-minute decay)
Claude Code's experimental Agent Teams feature lets a lead session spawn and coordinate named teammates via a shared task list and filesystem inbox messaging. Codeman has first-class support for monitoring and visualizing these teams.
Enable:
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1
# Or in .claude/settings.local.json:
{ "env": { "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1" } }- In-process threads — all teammates run as threads within the single
claudeprocess (not separate OS processes); only 1 claude process per Codeman session regardless of team size - Filesystem inboxes —
~/.claude/teams/{name}/inboxes/{teammate}.json; lead and teammates communicate viaSendMessagetool writing JSON arrays - Shared task list —
~/.claude/tasks/{team-name}/*.json; atomic file-based locking; task states:pending→in_progress→completed - Subagent compatibility — teammates appear as standard subagents in
SubagentWatcher; visible in/api/subagentsand the floating window UI - TeamWatcher — polls
~/.claude/teams/and links to Codeman sessions vialeadSessionId
| Resource | Path |
|---|---|
| Team config | ~/.claude/teams/{name}/config.json |
| Teammate inboxes | ~/.claude/teams/{name}/inboxes/{name}.json |
| Shared tasks | ~/.claude/tasks/{team-name}/ |
| Teammate transcripts | ~/.claude/projects/{hash}/{leadSessionId}/subagents/agent-{id}.jsonl |
Two additional hook types for quality gates:
- TeammateIdle — fires when a teammate is about to idle; exit code 2 sends feedback and keeps the teammate working
- TaskCompleted — fires when a task is being marked complete; exit code 2 prevents completion and sends feedback
Use the team-lead preset (90s idle timeout, 480-minute duration) when leading an agent team. The kickstart prompt instructs Claude to check the task list and teammate inboxes before assigning new work.
- No session resumption with in-process teammates (
/resumedoesn't restore them) - One team per session, no nested teams
- Lead cannot be reassigned
- Shutdown can be slow if a teammate is mid-tool-call
Reference: agent-teams/README.md
Codeman tracks Claude Code's token usage in real-time and automatically manages context window size for long-running sessions.
| Threshold | Default | Action |
|---|---|---|
| Auto-compact | 110,000 tokens | Sends /compact — context summarized, work continues |
| Auto-clear | 140,000 tokens | Sends /clear then /init — fresh context start |
Both thresholds are configurable per session. Valid range: 1,000–500,000 tokens. Changes take effect immediately.
SessionAutoOps (src/session-auto-ops.ts) monitors token counts after each Claude response:
- Waits for Claude to be idle before sending commands
- Mutual exclusion: compact and clear never run simultaneously
- 10-second cooldown after compact before re-enabling
- 5-second cooldown after clear before re-enabling
- Retry logic with 2-second intervals if Claude is still working
Each session tab shows live token count and cumulative USD cost. The respawn controller's health score incorporates token progression toward configured duration limits.
POST /api/sessions/:id/auto-compact
Body: { "enabled": true, "threshold": 110000, "prompt": "optional custom compact message" }
POST /api/sessions/:id/auto-clear
Body: { "enabled": true, "threshold": 140000 }
Codeman surfaces Claude Code's built-in slash commands and session-specific plugin commands through an inline popup in the compose panel, with intelligent fuzzy matching.
| Command | Description |
|---|---|
/compact |
Compact conversation context |
/clear |
Clear conversation history |
/help |
Show Claude Code help |
/bug |
Report a bug |
/cost |
Show session cost |
/doctor |
Run diagnostics |
/init |
Initialize with CLAUDE.md |
/login |
Log in to Anthropic |
/logout |
Log out |
/memory |
Manage memory |
/model |
Switch model |
/pr_comments |
Review PR comments |
/release-notes |
View release notes |
/review |
Code review |
/status |
Show session status |
/terminal-setup |
Configure terminal |
/vim |
Open vim mode |
Session-specific plugin commands (e.g., GSD skills like /ralph-loop:ralph-loop) are loaded from the active session's Claude Code plugin configuration and merged with the built-in list. Session commands take priority in deduplication.
The slash popup supports two matching strategies:
- Substring matching —
/clematches/clear - Subsequence matching —
/cmpctmatches/compact(characters appear in order but not consecutively)
This makes it fast to invoke commands on mobile with abbreviated typing.
The accessory bar's commands button opens a dynamic drawer showing GSD skills and plugin commands (not just hardcoded /clear and /compact as in upstream). Commands are fetched from the active session's plugin list at open time.
| Shortcut | Action |
|---|---|
Ctrl+Enter |
Quick-start a new session |
Ctrl+W |
Kill current session |
Ctrl+Tab |
Next session |
Ctrl+K |
Kill all sessions |
Ctrl+L |
Clear terminal |
Ctrl+Shift+R |
Restore terminal size |
Ctrl/Cmd+C |
Copy terminal selection (falls through to SIGINT when nothing is selected) |
Shift+drag |
Select text when the app has mouse reporting on |
Ctrl/Cmd+Shift+X |
Copy terminal text (whole-buffer panel) |
Ctrl/Cmd+X |
Copy the current xterm selection |
Ctrl/Cmd + |
Increase font size |
Ctrl/Cmd - |
Decrease font size |
Escape |
Close panels / dismiss overlays |
Ctrl+? |
Show keyboard shortcut help |
All shortcuts are active when the terminal has focus. Escape and Ctrl+? work globally.
Selecting straight out of the buffer is the primary path; the copy panel is the fallback for more scrollback than fits on one screen.
Desktop. Drag to select, then Ctrl/Cmd+C — with nothing selected the key
still falls through to the PTY as SIGINT, so Ctrl+C keeps interrupting. A
Copy selection chip appears in the terminal's top-right while a selection is
live, so the shortcut is discoverable.
Claude Code's TUI turns on mouse reporting (mouseTrackingMode: any), which
means a plain drag is forwarded to the app and selects nothing. xterm honours
Shift+drag as a force-selection modifier; a plain drag that is about to
come up empty raises a transient "Hold ⇧ Shift to select text" hint at the
pointer (throttled to once per 30s). Shell sessions have mouse reporting off, so
a plain drag selects normally.
Touch. The DOM renderer puts real text in .xterm-rows, so body.touch-device
CSS re-enables native selection there — a long-press gives the OS selection
handles and its own Copy callout. The session-switch swipe is suppressed while a
selection is live, since dragging the handles is horizontal too.
Copy panel (overflow ⋮ menu → Copy Terminal Text, Ctrl/Cmd+Shift+X, or
the mobile accessory copy button when nothing is selected). Native selection only
reaches the rows xterm currently has in the DOM, so this is how you grab more:
- Screen / All toggle — just the visible rows, or the whole scrollback
- Copy all — one tap, with an
execCommandfallback for the plain-HTTP LAN/tailscale origins where the async clipboard API is disabled - Wrapped rows are rejoined so long commands and paths copy as one line
Cases are project workspaces that group sessions by directory. Each case stores the working directory, environment, and Claude Code hook configuration.
- Case picker — dropdown on desktop, bottom-sheet slide-up on mobile; lists all cases with session counts
- Quick-start from case —
POST /api/quick-startcreates a case and starts a session in one request - Hook auto-configuration — when starting a session from a case, Claude Code hooks (
hooks-config.ts:generateHooksConfig()) are written to the case's.claude/directory; hooks coverpermission_prompt,elicitation_dialog,idle_prompt,stop,teammate_idle, andtask_completed - Case-scoped settings — separate
settings.local.jsonper case allows different models, environment variables, and plugin configurations - Respawn config per case — saved respawn presets are associated with cases
GET /api/cases # List cases
POST /api/cases # Create case
GET /api/cases/:id # Case details
PUT /api/cases/:id # Update case
DELETE /api/cases/:id # Delete case
POST /api/quick-start # Create case + start session atomically
Click the chart icon on any session tab to open the run summary — a timeline of everything that happened in the session.
- Event timeline — respawn cycles, token milestones, auto-compact/clear triggers, idle/working transitions, hook events, circuit breaker state changes, errors
- Cycle statistics — total cycles, successful cycles, average cycle duration, tokens per cycle
- Health score history — chart of session health over time
- Cost breakdown — cumulative spend with per-cycle attribution
- Export — raw event data available via API
GET /api/sessions/:id/run-summary # Timeline + aggregate stats
State persists in ~/.codeman/session-lifecycle.jsonl (append-only audit log) and ~/.codeman/state.json.
For users who prefer SSH-based access (Termius, Blink Shell, etc.), sc is a thumb-friendly interactive session chooser for tmux.
sc # Interactive chooser (single-digit 1-9 selection)
sc 2 # Quick attach to session 2 directly
sc -l # List all sessions with status- Color-coded status indicators
- Token count and cost display per session
- Auto-refresh
- Detach with
Ctrl+A D
The sc alias is configured in the shell profile during Codeman installation.
When Claude Code is in plan mode (showing a proposed plan and waiting for approval), Codeman can automatically send Enter to accept — useful for fully unattended runs.
After autoAcceptDelayMs (8 seconds) of output silence with no completion message and no elicitation_dialog signal:
- The AI Plan Checker (
src/ai-plan-checker.ts) optionally verifies the terminal is actually showing a plan mode approval prompt before sending Enter - Model:
claude-opus-4-5-20251101; max context: 8,000 characters; verdicts:PLAN_MODE(safe) orNOT_PLAN_MODE(skip) - 30-second cooldown after a NOT_PLAN_MODE verdict; auto-disables after 3 consecutive errors
- If verified (or checker disabled), sends Enter to accept
- Does not auto-accept
AskUserQuestionprompts — theelicitation_dialoghook signals the respawn controller to skip auto-accept for those - Uses a temp file for the prompt payload to avoid
E2BIGerrors with large terminal buffers
Auto-accept is enabled by default when respawn is configured with autoAcceptPrompts: true (set in all five built-in presets). Can be toggled per session via the respawn settings panel.
Claude Code uses Ink (React for terminals), which redraws the entire screen on every state change. Without mitigation, this produces constant flickering. Codeman implements a 6-layer anti-flicker pipeline delivering smooth 60fps output.
PTY Output → Server Batching → DEC 2026 Wrap → SSE → Client rAF → Sync Parser → xterm.js
| Layer | Location | Technique | Latency |
|---|---|---|---|
| Server batching | server.ts |
Adaptive 16–50ms collection window | 16–50ms |
| DEC Mode 2026 | server.ts |
Wraps output with \x1b[?2026h...\x1b[?2026l |
0ms |
| SSE broadcast | server.ts |
JSON serialized once, sent to all clients | 0ms |
| Client rAF | app.js |
requestAnimationFrame batching |
0–16ms |
| Sync block parser | app.js |
Strips DEC 2026 markers, waits for complete blocks | 0–50ms |
| Chunked loading | app.js |
64KB/frame for large buffers (session switch, reconnect) | variable |
Event gap < 10ms → 50ms batch window (rapid-fire Ink redraws); gap < 20ms → 32ms; otherwise 16ms (60fps). Flushes immediately when batch exceeds 32KB.
Per-session toggle in Session Settings. Adds ~50ms latency but eliminates remaining flicker on problematic terminals. Detects and buffers screen-clear patterns (ESC[2J, ESC[H ESC[J, Ink cursor-up sequences) for 50ms before flushing atomically.
16–32ms (server batch + rAF). Worst case ~115ms (rare, requires several simultaneous delays).
Reference: docs/terminal-anti-flicker.md
A natural language command interface to Codeman, allowing you to interact with sessions and perform administrative tasks through a conversational UI. Built-in Claude model handles requests via API, with persistent conversation history.
- Multiple conversation threads — create, switch between, rename, and delete independent conversations
- Persistent storage — conversations stored as JSON files in
~/.codeman/data/conversations/, survive server restarts - Conversation sidebar — collapsible sidebar showing history sorted by most recent, with message counts and relative timestamps
- Auto-title generation — first user message automatically becomes the conversation title (first 60 chars, markdown stripped)
- Keyboard shortcut —
Ctrl+Shift+Hto toggle sidebar when panel is open - File attachments — support for images and file uploads in conversation
- Multimodal support — handles text and image content seamlessly
- Press
Ctrl+Mor click the command button (⌘) in the header to open the panel - Type a natural language command or question
- Click the hamburger icon (☰) to open the conversation sidebar and view history
- Click a conversation in the sidebar to switch to it
- Double-click a conversation title to rename it
- Click the × on hover to delete a conversation
- Press
Ctrl+Shift+Hto quickly toggle the sidebar
Each conversation is stored as a JSON file:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "Help with deployment issue",
"messages": [
{ "role": "user", "content": "..." },
{ "role": "assistant", "content": "..." }
],
"createdAt": "2026-03-26T11:15:22.000Z",
"updatedAt": "2026-03-26T11:20:45.000Z",
"lastActivity": 1711353645000
}- Maximum messages per conversation — 40 messages (≈20 turns)
- Conversation memory — indefinite (persisted to disk)
- Message size — 64KB per API request (standard Codeman limit)
| Layer | Detail |
|---|---|
| HTTP Basic Auth | CODEMAN_USERNAME / CODEMAN_PASSWORD env vars; optional but required for tunnel exposure |
| Session cookie | codeman_session, 24h TTL, HttpOnly, Secure (HTTPS), SameSite=lax; auto-extends on activity |
| QR token auth | Single-use, 60s TTL, 256-bit entropy; see Section 9 |
| Rate limiting | 10 failed Basic Auth attempts per IP → 429 (15-min decay); separate QR rate limits |
| Layer | Detail |
|---|---|
| CORS | Localhost-only |
| CSP | Content-Security-Policy header on all responses |
| X-Frame-Options | Set on all responses |
| HSTS | Applied when running with --https |
- Zod v4 schemas on all API request bodies (
src/web/schemas.ts) - Path allowlist regex for file access endpoints
CLAUDE_CODE_*environment variable prefix allowlist for session environment injection- Max input length: 64KB per API request
/api/hook-event is exempt from authentication (Claude Code hooks call it from localhost during sessions). Protected by localhost-only binding and strict Zod schema validation — unknown fields are rejected.
Device context (IP, User-Agent) stored on authentication. Full lifecycle log at ~/.codeman/session-lifecycle.jsonl. Manual session revocation: POST /api/auth/revoke.
Codeman exposes ~111 HTTP endpoints across 12 route modules. All responses follow { success: boolean, data?: ..., error?: string } shape.
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/sessions |
List all sessions |
POST |
/api/quick-start |
Create case + start session |
GET |
/api/sessions/:id |
Session state |
DELETE |
/api/sessions/:id |
Stop and delete session |
POST |
/api/sessions/:id/input |
Send raw input |
POST |
/api/sessions/:id/resize |
Resize terminal (cols × rows) |
GET |
/api/sessions/:id/buffer |
Terminal buffer (last 128KB) |
POST |
/api/sessions/:id/restart |
Restart Claude process |
POST |
/api/sessions/:id/pause |
Park session (Claude only) |
POST |
/api/sessions/:id/resume |
Relaunch with --resume |
POST |
/api/sessions/:id/kill |
SIGKILL Claude process |
| Method | Endpoint | Description |
|---|---|---|
POST |
.../respawn/enable |
Enable with config + timer |
POST |
.../respawn/stop |
Stop controller |
PUT |
.../respawn/config |
Update config live |
GET |
.../respawn/state |
Current state + health score |
POST |
.../ralph-circuit-breaker/reset |
Reset circuit breaker |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/sessions/:id/ralph-state |
Loop state + todos + stats |
POST |
/api/sessions/:id/ralph-config |
Configure tracking |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/subagents |
List all background agents |
GET |
/api/subagents/:id |
Agent info and status |
GET |
/api/subagents/:id/transcript |
Full JSONL activity transcript |
DELETE |
/api/subagents/:id |
Kill agent process |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/events |
SSE event stream |
GET |
/api/status |
Full app state |
POST |
/api/hook-event |
Claude Code hook callbacks |
GET |
/api/sessions/:id/run-summary |
Timeline + stats |
GET |
/api/harnesses |
Harness registry + caps |
GET |
/api/harness/:id/status |
One harness's availability |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/cases |
List cases |
POST |
/api/cases |
Create case |
PUT |
/api/cases/:id |
Update case |
DELETE |
/api/cases/:id |
Delete case |
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/push/generate-keys |
Generate VAPID keys |
POST |
/api/push/subscribe |
Subscribe device |
DELETE |
/api/push/subscribe |
Unsubscribe device |
POST |
/api/push/test |
Send test notification |
| Method | Endpoint | Description |
|---|---|---|
GET |
/q/:code |
QR token redemption |
POST |
/api/auth/revoke |
Revoke all sessions |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/command/status |
Check if command panel is available |
GET |
/api/command/conversations |
List all saved conversations |
GET |
/api/command/conversations/:id |
Get full conversation with messages |
POST |
/api/command |
Send a message (creates/updates conversation) |
POST |
/api/command/confirm |
Confirm tool use or other action |
DELETE |
/api/command/conversations/:id |
Delete a conversation |
PATCH |
/api/command/conversations/:id |
Rename a conversation (body: { title: string }) |
Every session runs exactly one harness — the CLI that occupies its tmux pane. Codeman ships
five, declared in a capability registry under src/harnesses/:
| Harness | Mode id | Badge | Binary | Install |
|---|---|---|---|---|
| Claude Code | claude |
— | claude |
curl -fsSL https://claude.ai/install.sh | bash |
| OpenCode | opencode |
oc |
opencode |
curl -fsSL https://opencode.ai/install | bash |
| Codex | codex |
cx |
codex |
npm i -g @openai/codex |
| Pi | pi |
pi |
pi |
npm i -g @earendil-works/pi-coding-agent |
| Shell | shell |
sh |
— | (your $SHELL) |
The badge is what codeman list prints next to a session and what the session tab shows; Claude
sessions carry no badge.
Each harness declares what it supports, and every route, background loop and UI element reads those flags rather than branching on the mode string. This is what a harness gets:
| Capability | claude | opencode | codex | pi | shell |
|---|---|---|---|---|---|
| Pause / resume | ✅ | ❌ | ❌ | ❌ | ❌ |
| Respawn controller | ✅ | ❌ | ❌ | ❌ | ❌ |
| Ralph / todo tracking | ✅ | ❌ | ❌ | ❌ | ❌ |
| Transcript view | ✅ | ❌ | ✅ (no thinking) | ✅ (with thinking) | ❌ |
Claude transcript state + --resume |
✅ | ❌ | ❌ | ❌ | ❌ |
| Token / cost parsers | ✅ | ❌ | ❌ | ❌ | ❌ |
| Claude Code hooks | ✅ | ❌ | ❌ | ❌ | ❌ |
| Claude model defaults | ✅ | ❌ | ❌ | ❌ | ❌ |
| Conversation restore | ✅ --resume |
❌ | ✅ codex resume |
✅ --session-id |
❌ |
| Busy / idle source | transcript | terminal output | rollout records | pi extension | terminal output |
| Auto-compact-and-continue | ✅ | ❌ | ❌ | ❌ | ❌ |
Anything a harness does not support is refused with copy that names it — pausing a shell session
answers Shell sessions cannot be paused — and the corresponding UI (Ralph tab, Respawn tab,
transcript controls, Park menu item) is hidden.
Transcript view for Codex and Pi. The transcript capability (claude, codex, pi) offers the
web transcript view; claudeTranscript (claude only) is the separate Claude JSONL / --resume /
hooks / state-machine flag. Each harness has a locator and a record adapter in
src/harnesses/transcripts/: codex reads $CODEX_HOME/sessions/**/rollout-*-<id>.jsonl (once its
id is discovered after the first turn), pi reads
~/.pi/agent/sessions/--<cwd>--/<ts>_<id>.jsonl, and both refuse any path that resolves outside
their root. Pi's plaintext reasoning renders as a collapsed Thinking block; codex reasoning is
encrypted at source and never shown. Codex and pi watchers are view-only — completion, plan-mode
and AskUserQuestion detection stay Claude-only. Neither harness writes a transcript before the
first submitted turn, so a fresh session shows "No transcript yet".
Each harness declares where its busy/idle status comes from (activity in its definition):
- Claude —
ClaudeActivityMonitortails Claude's transcript JSONL (unchanged). - Codex — the rollout's own turn records:
task_startedis busy,task_completeandturn_abortedare idle. On attach Codeman reads the rollout backward from its end to find the current state, then follows appends, replacement and truncation. - Pi — a small Codeman pi extension, loaded with
pi -e <codeman-activity-extension>, postsharness_activityevents (agent_start,agent_settled, compaction start/end, and a 30 s heartbeat) toPOST /api/hook-event. Each report carries the per-processCODEMAN_ACTIVITY_TOKENexported at spawn plus ordering counters, so reports from any other process, a superseded pi runtime or a delayed heartbeat are ignored. The token survives a Codeman restart, so a pi process that kept running is still accepted. These events are activity-only: nohook:*broadcast, push notification, run-summary hook record, vault capture or orchestrator call. - OpenCode and shell — the terminal-output heuristics as before.
Idle events carry a reason. completed is an authoritative end of turn; stale means tracking was
lost mid-turn (pi silent for 90 s, or codex silent for 5 minutes while busy). A stale idle updates
the UI but never counts as a completed run, and the real completion still registers when it arrives.
The pi extension also reports pi's session file. Codeman accepts it only under pi's sessions root
($PI_CODING_AGENT_DIR or ~/.pi/agent, plus /sessions), persists it and streams the transcript
from it, so a new pi session's first turn appears live and the view follows /new and /resume.
Every transcript stream carries a transcriptId (SSE transcript:block / clear / ready and the
X-Transcript-Id header on GET /api/sessions/:id/transcript), and the client uses it to avoid
mixing blocks from two files.
Known limitations: pi sessions started before this feature have no extension until restarted
(their transcript view still works through the old file lookup). Codeman and pi must resolve the
same pi agent directory. After a mux rebind, codex activity is not tracked until the session
restarts. Auto-compact-and-continue types Claude's /compact and is therefore Claude-only.
Behaviour change. Shell sessions used to receive the Ralph tracker, the respawn controller, the Claude transcript wiring and the Claude output parsers, because the guards that gated those features read "not opencode" rather than "is claude". They no longer do, and shell sessions can no longer be paused.
Bypass-permission flags are fixed per harness; the model is the one knob exposed. Pass
openCodeConfig, codexConfig or piConfig ({ "model": "…" }) to POST /api/sessions or
POST /api/quick-start. Claude's own model defaults apply only to Claude sessions. A worktree
created from an existing session inherits that session's harness config.
Codeman tracks a neutral harnessSessionId per session and uses it to bring a session back after
a restart:
- Claude — the transcript-filename hook publishes the conversation id; restore spawns
claude --resume <id>. - Codex — the codex TUI writes
~/.codex/sessions/**/rollout-*.jsonlwhen the first turn is submitted, not at spawn. Codeman watches that tree (fs.watch, with a backing-off poll as a fallback) and matches on the rollout'scwdand a start-time floor; the id typically lands within a few hundred milliseconds of that first Enter. Restore spawnscodex resume <id>. Known limitation: the recursive watch costs roughly 550 inotify watch descriptors per active codex session on Linux, so many concurrent codex sessions can approachfs.inotify.max_user_watches(65536 by default, shared with every other watcher). - Pi — Codeman assigns the session id at spawn (
pi --session-id <id>) and records it immediately. - OpenCode and shell — no identity, no conversation restore; the pane is simply respawned.
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/harnesses |
All harnesses: id, label, shortLabel, installHint, available, caps |
GET |
/api/harness/:id/status |
{ available, path } for one harness; 404 on an unknown id |
GET |
/api/opencode/status |
Retained alias of /api/harness/opencode/status, same response shape |
A harness whose binary is not on PATH (or in its known install directories) reports
available: false, and session creation refuses with that harness's installHint instead of
failing at spawn time.
This fork (SGudbrandsson/Codeman v0.5.4) is 33 commits ahead of upstream Ark0N/Codeman v0.3.7. The last upstream merge was commit 11150a4 (performance fixes).
| Area | Upstream (v0.3.7) | This Fork (v0.5.4) |
|---|---|---|
| Compose Panel | Basic textarea | Auto-grow, multi-image attachments, slash popup with 17 built-in Claude commands + plugin commands |
| Slash Command Matching | N/A | Substring + subsequence matching (e.g., /cmpct → /compact) |
| Mobile Navigation | Tab strip only | Slide-out session navigation drawer |
| Android Keyboard | Double-accounting bug | Resize-mode detection, correct 124px padding, keyboard state preserved on tab switch |
| GSD Status Strip | None | Pinned persistent strip above accessory bar |
| Commands Drawer | Hardcoded /clear + /compact buttons |
Dynamic drawer showing GSD skills + plugin commands |
| Terminal Tab Switch | Flash on switch | Eliminated (single hide/show cycle, keyboard state preserved) |
| Hamburger Menu | Mobile only | Desktop header + mobile accessory bar |
| Input Panel | Auto-hide only | Optional persistent toggle for always-on mobile input panel |
The compose panel received a complete redesign. The upstream provided a basic textarea. This fork adds auto-growth as you type, multi-image attachment with previews, and the slash command popup. The popup merges 17 built-in Claude Code commands (defined in app.js as BUILTIN_CLAUDE_COMMANDS) with session-specific plugin commands from app._sessionCommands. Plugin commands take priority in deduplication. Matching supports both substring and subsequence strategies — useful for typing abbreviated commands on a mobile keyboard. See Section 12.
The upstream uses a tab strip for session navigation on mobile. This fork adds a slide-out session navigation drawer for easier access when managing multiple sessions. The drawer shows all sessions with status indicators and is accessible via a swipe gesture or button. See Section 5.
The upstream had a double-accounting bug where Android keyboard resize events were counted twice, causing incorrect terminal padding and layout shifts. This fork introduces resize-mode detection to distinguish Android keyboard resize events from genuine viewport changes. The correct barHeight values (124px when accessory bar visible, 40px when hidden) are applied consistently. Keyboard state is now preserved on tab switch, eliminating a flash that occurred when switching sessions with the keyboard open.
An entirely new UI element in this fork: a persistent strip pinned above the accessory bar displaying the current GSD (Get Stuff Done) task status. Not present in upstream.
The upstream hardcoded /clear and /compact as the only quick-action buttons. This fork replaces that with a dynamic commands drawer that fetches GSD skills and plugin commands from the active session at open time, showing all available commands.
The upstream exhibited a visible flash when switching between session tabs, caused by multiple show/hide cycles. This fork eliminates it by using a single hide/show cycle and preserving keyboard state across the switch.
The upstream showed the hamburger menu only on mobile. This fork adds it to the desktop header as well, providing consistent access to settings and navigation on all screen sizes.








