{"id":46092890,"url":"https://github.com/ark0n/codeman","last_synced_at":"2026-05-31T05:01:15.996Z","repository":{"id":333915750,"uuid":"1139051534","full_name":"Ark0N/Codeman","owner":"Ark0N","description":"Manage Claude Code \u0026 Opencode in Tmux Sessions in a modern WebUI","archived":false,"fork":false,"pushed_at":"2026-05-19T10:13:03.000Z","size":72550,"stargazers_count":393,"open_issues_count":0,"forks_count":49,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-05-19T11:21:21.451Z","etag":null,"topics":["claude","claude-ai","claude-api","claude-cli","claude-code","claude-code-plugin","claudecode","claudecode-monitoring","claudecodeing","local-echo","mosh","opencode","opencode-ai","opencode-plugin","ralph-loop","tmux","todowrite","xterm-zerolag-input"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Ark0N.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-21T13:09:45.000Z","updated_at":"2026-05-19T10:11:52.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/Ark0N/Codeman","commit_stats":null,"previous_names":["ark0n/claudeman","ark0n/codeman"],"tags_count":47,"template":false,"template_full_name":null,"purl":"pkg:github/Ark0N/Codeman","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ark0N%2FCodeman","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ark0N%2FCodeman/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ark0N%2FCodeman/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ark0N%2FCodeman/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Ark0N","download_url":"https://codeload.github.com/Ark0N/Codeman/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ark0N%2FCodeman/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33719601,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-31T02:00:06.040Z","response_time":95,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["claude","claude-ai","claude-api","claude-cli","claude-code","claude-code-plugin","claudecode","claudecode-monitoring","claudecodeing","local-echo","mosh","opencode","opencode-ai","opencode-plugin","ralph-loop","tmux","todowrite","xterm-zerolag-input"],"created_at":"2026-03-01T18:05:22.833Z","updated_at":"2026-05-31T05:01:15.985Z","avatar_url":"https://github.com/Ark0N.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/images/codeman-title.svg\" alt=\"Codeman\" height=\"60\"\u003e\n\u003c/p\u003e\n\n\u003ch2 align=\"center\"\u003eThe missing control plane for AI coding agents\u003c/h2\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cem\u003eAgent Visualization \u0026bull; Zero-Lag Input Overlay \u0026bull; Mobile-First UI \u0026bull; Respawn Controller \u0026bull; Multi-Session Dashboard \u003c/em\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://opensource.org/licenses/MIT\"\u003e\u003cimg src=\"https://img.shields.io/badge/License-MIT-1e3a5f?style=flat-square\" alt=\"License: MIT\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://nodejs.org/\"\u003e\u003cimg src=\"https://img.shields.io/badge/Node.js-18%2B-22c55e?style=flat-square\u0026logo=node.js\u0026logoColor=white\" alt=\"Node.js 18+\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://www.typescriptlang.org/\"\u003e\u003cimg src=\"https://img.shields.io/badge/TypeScript-5.9-3b82f6?style=flat-square\u0026logo=typescript\u0026logoColor=white\" alt=\"TypeScript 5.9\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://fastify.dev/\"\u003e\u003cimg src=\"https://img.shields.io/badge/Fastify-5.x-1e3a5f?style=flat-square\u0026logo=fastify\u0026logoColor=white\" alt=\"Fastify\"\u003e\u003c/a\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Tests-1435%20total-22c55e?style=flat-square\" alt=\"Tests\"\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/images/subagent-demo.gif\" alt=\"Codeman — parallel subagent visualization\" width=\"900\"\u003e\n\u003c/p\u003e\n\n---\n\n## Quick Start - Installation\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/Ark0N/Codeman/master/install.sh | bash\n```\n\nThis installs Node.js and tmux if missing, clones Codeman to `~/.codeman/app`, and builds it.\n\nYou'll need at least one AI coding CLI installed — [Claude Code](https://docs.anthropic.com/en/docs/claude-code) or [OpenCode](https://opencode.ai) (or both). After install:\n\n```bash\ncodeman web\n# Open http://localhost:3000 — press Ctrl+Enter to start your first session\n```\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eRun as a background service\u003c/strong\u003e\u003c/summary\u003e\n\n**Linux (systemd):**\n```bash\nmkdir -p ~/.config/systemd/user\ncat \u003e ~/.config/systemd/user/codeman-web.service \u003c\u003c EOF\n[Unit]\nDescription=Codeman Web Server\nAfter=network.target\n\n[Service]\nType=simple\nExecStart=$(which node) $HOME/.codeman/app/dist/index.js web\nRestart=always\nRestartSec=10\n\n[Install]\nWantedBy=default.target\nEOF\nsystemctl --user daemon-reload\nsystemctl --user enable --now codeman-web\nloginctl enable-linger $USER\n```\n\n**macOS (launchd):**\n```bash\nmkdir -p ~/Library/LaunchAgents\ncat \u003e ~/Library/LaunchAgents/com.codeman.web.plist \u003c\u003c EOF\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003c!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\"\n  \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\"\u003e\n\u003cplist version=\"1.0\"\u003e\n\u003cdict\u003e\n  \u003ckey\u003eLabel\u003c/key\u003e\n  \u003cstring\u003ecom.codeman.web\u003c/string\u003e\n  \u003ckey\u003eProgramArguments\u003c/key\u003e\n  \u003carray\u003e\n    \u003cstring\u003e$(which node)\u003c/string\u003e\n    \u003cstring\u003e$HOME/.codeman/app/dist/index.js\u003c/string\u003e\n    \u003cstring\u003eweb\u003c/string\u003e\n  \u003c/array\u003e\n  \u003ckey\u003eRunAtLoad\u003c/key\u003e\u003ctrue/\u003e\n  \u003ckey\u003eKeepAlive\u003c/key\u003e\u003ctrue/\u003e\n  \u003ckey\u003eStandardOutPath\u003c/key\u003e\n  \u003cstring\u003e/tmp/codeman.log\u003c/string\u003e\n  \u003ckey\u003eStandardErrorPath\u003c/key\u003e\n  \u003cstring\u003e/tmp/codeman.log\u003c/string\u003e\n\u003c/dict\u003e\n\u003c/plist\u003e\nEOF\nlaunchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.codeman.web.plist\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eWindows (WSL)\u003c/strong\u003e\u003c/summary\u003e\n\n```powershell\nwsl bash -c \"curl -fsSL https://raw.githubusercontent.com/Ark0N/Codeman/master/install.sh | bash\"\n```\n\nCodeman requires tmux, so Windows users need [WSL](https://learn.microsoft.com/en-us/windows/wsl/install). If you don't have WSL yet: run `wsl --install` in an admin PowerShell, reboot, open Ubuntu, then install your preferred AI coding CLI inside WSL ([Claude Code](https://docs.anthropic.com/en/docs/claude-code) or [OpenCode](https://opencode.ai)). After installing, `http://localhost:3000` is accessible from your Windows browser.\n\u003c/details\u003e\n\n---\n\n## Mobile-Optimized Web UI\n\nThe most responsive AI coding agent experience on any phone. Full xterm.js terminal with local echo, swipe navigation, and a touch-optimized interface designed for real remote work — not a desktop UI crammed onto a small screen.\n\n\u003ctable\u003e\n\u003ctr\u003e\n\u003ctd align=\"center\" width=\"33%\"\u003e\u003cimg src=\"docs/screenshots/mobile-landing-qr.png\" alt=\"Mobile — landing page with QR auth\" width=\"260\"\u003e\u003c/td\u003e\n\u003ctd align=\"center\" width=\"33%\"\u003e\u003cimg src=\"docs/screenshots/mobile-session-idle.png\" alt=\"Mobile — idle session with keyboard accessory\" width=\"260\"\u003e\u003c/td\u003e\n\u003ctd align=\"center\" width=\"33%\"\u003e\u003cimg src=\"docs/screenshots/mobile-session-active.png\" alt=\"Mobile — active agent session\" width=\"260\"\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd align=\"center\"\u003e\u003cem\u003eLanding page with QR auth\u003c/em\u003e\u003c/td\u003e\n\u003ctd align=\"center\"\u003e\u003cem\u003eKeyboard accessory bar\u003c/em\u003e\u003c/td\u003e\n\u003ctd align=\"center\"\u003e\u003cem\u003eAgent working in real-time\u003c/em\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\n\u003ctable\u003e\n\u003ctr\u003e\n\u003cth\u003eTerminal Apps\u003c/th\u003e\n\u003cth\u003eCodeman Mobile\u003c/th\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e200-300ms input lag over remote\u003c/td\u003e\u003ctd\u003e\u003cb\u003eLocal echo — instant feedback\u003c/b\u003e\u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003eTiny text, no context\u003c/td\u003e\u003ctd\u003eFull xterm.js terminal\u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003eNo session management\u003c/td\u003e\u003ctd\u003eSwipe between sessions\u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003eNo notifications\u003c/td\u003e\u003ctd\u003ePush alerts for approvals and idle\u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003eManual reconnect\u003c/td\u003e\u003ctd\u003etmux persistence\u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003eNo agent visibility\u003c/td\u003e\u003ctd\u003eBackground agents in real-time\u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003eCopy-paste slash commands\u003c/td\u003e\u003ctd\u003eOne-tap \u003ccode\u003e/init\u003c/code\u003e, \u003ccode\u003e/clear\u003c/code\u003e, \u003ccode\u003e/compact\u003c/code\u003e\u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003ePassword typing on phone\u003c/td\u003e\u003ctd\u003e\u003cb\u003eQR code scan — instant auth\u003c/b\u003e\u003c/td\u003e\u003c/tr\u003e\n\u003c/table\u003e\n\n### Secure QR Code Authentication\n\nTyping passwords on a phone keyboard is miserable. Codeman replaces it with **cryptographically secure single-use QR tokens** — scan the code displayed on your desktop and your phone is authenticated instantly.\n\nEach QR encodes a URL containing a 6-character short code that maps to a 256-bit secret (`crypto.randomBytes(32)`) on the server. Tokens auto-rotate every **60 seconds**, are **atomically consumed on first scan** (replays always fail), and use **hash-based `Map.get()` lookup** that leaks nothing through response timing. The short code is an opaque pointer — the real secret never appears in browser history, `Referer` headers, or Cloudflare edge logs.\n\nThe security design addresses all 6 critical QR auth flaws identified in [\"Demystifying the (In)Security of QR Code-based Login\"](https://www.usenix.org/conference/usenixsecurity25/presentation/zhang-xin) (USENIX Security 2025, which found 47 of the top-100 websites vulnerable): single-use enforcement, short TTL, cryptographic randomness, server-side generation, real-time desktop notification on scan (QRLjacking detection), and IP + User-Agent session binding with manual revocation. Dual-layer rate limiting (per-IP + global) makes brute force infeasible across 62^6 = 56.8 billion possible codes. Full security analysis: [`docs/qr-auth-plan.md`](docs/qr-auth-plan.md)\n\n### Touch-Optimized Interface\n\n- **Keyboard accessory bar** — `/init`, `/clear`, `/compact` quick-action buttons above the virtual keyboard. Destructive commands (`/clear`, `/compact`) require a double-press to confirm — first tap arms the button, second tap executes — so you never fire one by accident on a bumpy commute\n- **Swipe navigation** — left/right on the terminal to switch sessions (80px threshold, 300ms)\n- **Smart keyboard handling** — toolbar and terminal shift up when keyboard opens (uses `visualViewport` API with 100px threshold for iOS address bar drift)\n- **Safe area support** — respects iPhone notch and home indicator via `env(safe-area-inset-*)`\n- **44px touch targets** — all buttons meet iOS Human Interface Guidelines minimum sizes\n- **Bottom sheet case picker** — slide-up modal replaces the desktop dropdown\n- **Native momentum scrolling** — `-webkit-overflow-scrolling: touch` for buttery scroll\n\n```bash\ncodeman web --https\n# Open on your phone: https://\u003cyour-ip\u003e:3000\n```\n\n\u003e `localhost` works over plain HTTP. Use `--https` when accessing from another device, or use [Tailscale](https://tailscale.com/) (recommended) — it provides a private network so you can access `http://\u003ctailscale-ip\u003e:3000` from your phone without TLS certificates.\n\n---\n\n## Live Agent Visualization\n\nWatch background agents work in real-time. Codeman monitors agent activity and displays each agent in a draggable floating window with animated Matrix-style connection lines back to the parent session.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/images/subagent-spawn.png\" alt=\"Subagent Visualization\" width=\"900\"\u003e\n\u003c/p\u003e\n\n- **Floating terminal windows** — draggable, resizable panels for each agent with a live activity log showing every tool call, file read, and progress update as it happens\n- **Connection lines** — animated green lines linking parent sessions to their child agents, updating in real-time as agents spawn and complete\n- **Status \u0026 model badges** — green (active), yellow (idle), blue (completed) indicators with Haiku/Sonnet/Opus model color coding\n- **Auto-behavior** — windows auto-open on spawn, auto-minimize on completion, tab badge shows \"AGENT\" or \"AGENTS (n)\" count\n- **Nested agents** — supports 3-level hierarchies (lead session -\u003e teammate agents -\u003e sub-subagents)\n\n---\n\n## Zero-Lag Input Overlay\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/images/zerolag-demo.gif\" alt=\"Zerolag Demo — local echo vs server echo side-by-side\" width=\"900\"\u003e\n\u003c/p\u003e\n\nWhen accessing your coding agent remotely (VPN, Tailscale, SSH tunnel), every keystroke normally takes 200-300ms to round-trip. Codeman implements a **Mosh-inspired local echo system** that makes typing feel instant regardless of latency.\n\nA pixel-perfect DOM overlay inside xterm.js renders keystrokes at 0ms. Background forwarding silently sends every character to the PTY in 50ms debounced batches, so Tab completion, `Ctrl+R` history search, and all shell features work normally. When the server echo arrives 200-300ms later, the overlay seamlessly disappears and the real terminal text takes over — the transition is invisible.\n\n- **Ink-proof architecture** — lives as a `\u003cspan\u003e` at z-index 7 inside `.xterm-screen`, completely immune to Ink's constant screen redraws (two previous attempts using `terminal.write()` failed because Ink corrupts injected buffer content)\n- **Font-matched rendering** — reads `fontFamily`, `fontSize`, `fontWeight`, and `letterSpacing` from xterm.js computed styles so overlay text is visually indistinguishable from real terminal output\n- **Full editing** — backspace, retype, paste (multi-char), cursor tracking, multi-line wrap when input exceeds terminal width\n- **Persistent across reconnects** — unsent input survives page reloads via localStorage\n- **Enabled by default** — works on both desktop and mobile, during idle and busy sessions\n\n\u003e Extracted as a standalone library: [`xterm-zerolag-input`](https://www.npmjs.com/package/xterm-zerolag-input) — see [Published Packages](#published-packages).\n\n---\n\n## Respawn Controller\n\nThe core of autonomous work. When the agent goes idle, the Respawn Controller detects it, sends a continue prompt, cycles context management commands for fresh context, and resumes — running **24+ hours** completely unattended.\n\n```\nWATCHING → IDLE DETECTED → SEND UPDATE → /clear → /init → CONTINUE → WATCHING\n```\n\n- **Multi-layer idle detection** — completion messages, AI-powered idle check, output silence, token stability\n- **Circuit breaker** — prevents respawn thrashing when Claude is stuck (CLOSED -\u003e HALF_OPEN -\u003e OPEN states, tracks consecutive no-progress and repeated errors)\n- **Health scoring** — 0-100 health score with component scores for cycle success, circuit breaker state, iteration progress, and stuck recovery\n- **Built-in presets** — `solo-work` (3s idle, 60min), `subagent-workflow` (45s, 240min), `team-lead` (90s, 480min), `ralph-todo` (8s, 480min), `overnight-autonomous` (10s, 480min)\n\n---\n\n## Multi-Session Dashboard\n\nRun **20 parallel sessions** with full visibility — real-time xterm.js terminals at 60fps, per-session token and cost tracking, tab-based navigation, and one-click management.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/screenshots/multi-session-dashboard.png\" alt=\"Multi-Session Dashboard\" width=\"800\"\u003e\n\u003c/p\u003e\n\n### Persistent Sessions\n\nEvery session runs inside **tmux** — sessions survive server restarts, network drops, and machine sleep. Auto-recovery on startup with dual redundancy. Ghost session discovery finds orphaned tmux sessions. Managed sessions are environment-tagged so the agent won't kill its own session.\n\n### Hostname-Aware Window Title\n\nRunning Codeman on multiple hosts (laptop, dev box, NAS)? The browser tab title is `codeman:\u003chostname\u003e` so you can tell which backend each tab points at without clicking in:\n\n```bash\ncodeman web                                # codeman:\u003cos.hostname()\u003e\ncodeman web --title-hostname dev-box       # codeman:dev-box (manual override for noisy hostnames)\n```\n\nThe title is templated into the served HTML on first byte, so it's correct from the very first paint and works without JavaScript. The same hostname prefix is applied to the tab-flash format (`⚠️ (N) codeman:\u003chost\u003e`) and to OS-level desktop notifications (`codeman:\u003chost\u003e: \u003cevent\u003e`), so cross-host alerts in the system notification center are also unambiguous.\n\n### Smart Token Management\n\n| Threshold | Action | Result |\n|-----------|--------|--------|\n| **110k tokens** | Auto `/compact` | Context summarized, work continues |\n| **140k tokens** | Auto `/clear` | Fresh start with `/init` |\n\n### Notifications\n\nReal-time desktop alerts when sessions need attention — `permission_prompt` and `elicitation_dialog` trigger critical red tab blinks, `idle_prompt` triggers yellow blinks. Click any notification to jump directly to the affected session. Hooks auto-configured per case directory.\n\n### Ralph / Todo Tracking\n\nAuto-detects Ralph Loops, `\u003cpromise\u003e` tags, TodoWrite progress (`4/9 complete`), and iteration counters (`[5/50]`) with real-time progress rings and elapsed time tracking.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/images/ralph-tracker-8tasks-44percent.png\" alt=\"Ralph Loop Tracking\" width=\"800\"\u003e\n\u003c/p\u003e\n\n### Run Summary\n\nClick the chart icon on any session tab to see a timeline of everything that happened — respawn cycles, token milestones, auto-compact triggers, idle/working transitions, hook events, errors, and more.\n\n### Zero-Flicker Terminal\n\nTerminal-based AI agents (Claude Code's Ink, OpenCode's Bubble Tea) redraw the screen on every state change. Codeman implements a 6-layer anti-flicker pipeline for smooth 60fps output across all sessions:\n\n```\nPTY Output → 16ms Server Batch → DEC 2026 Wrap → SSE → Client rAF → xterm.js (60fps)\n```\n\n---\n\n## Remote Access — Cloudflare Tunnel\n\nAccess Codeman from your phone or any device outside your local network using a free [Cloudflare quick tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/do-more-with-tunnels/trycloudflare/) — no port forwarding, no DNS, no static IP required.\n\n```\nBrowser (phone/tablet) → Cloudflare Edge (HTTPS) → cloudflared → localhost:3000\n```\n\n**Prerequisites:** Install [`cloudflared`](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/) and set `CODEMAN_PASSWORD` in your environment.\n\n```bash\n# Quick start\n./scripts/tunnel.sh start      # Start tunnel, prints public URL\n./scripts/tunnel.sh url        # Show current URL\n./scripts/tunnel.sh stop       # Stop tunnel\n./scripts/tunnel.sh status     # Service status + URL\n```\n\nThe script auto-installs a systemd user service on first run. The tunnel URL is a randomly generated `*.trycloudflare.com` address that changes each time the tunnel restarts.\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003ePersistent tunnel (survives reboots)\u003c/strong\u003e\u003c/summary\u003e\n\n```bash\n# Enable as a persistent service\nsystemctl --user enable codeman-tunnel\nloginctl enable-linger $USER\n\n# Or via the Codeman web UI: Settings → Tunnel → Toggle On\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eAuthentication\u003c/strong\u003e\u003c/summary\u003e\n\n1. First request → browser shows Basic Auth prompt (username: `admin` or `CODEMAN_USERNAME`)\n2. On success → server issues a `codeman_session` cookie (24h TTL, auto-extends on activity)\n3. Subsequent requests authenticate silently via cookie\n4. 10 failed attempts per IP → 429 rate limit (15-minute decay)\n\n**Always set `CODEMAN_PASSWORD`** before exposing via tunnel — without it, anyone with the URL has full access to your sessions.\n\n\u003c/details\u003e\n\n### QR Code Authentication\n\nTyping a password on a phone keyboard is terrible. Codeman solves this with **ephemeral single-use QR tokens** — scan the code on your desktop, and your phone is instantly authenticated. No password prompt, no typing, no clipboard.\n\n```\nDesktop displays QR  →  Phone scans  →  GET /q/Xk9mQ3  →  Server validates\n→  Token atomically consumed (single-use)  →  Session cookie issued  →  302 to /\n→  Desktop notified: \"Device authenticated via QR\"  →  New QR auto-generated\n```\n\nSomeone who only has the bare tunnel URL (without the QR) still hits the standard password prompt. The QR is the fast path; the password is the fallback.\n\n#### How It Works\n\nThe server maintains a rotating pool of short-lived, single-use tokens. Each token consists of a 256-bit secret (`crypto.randomBytes(32)`) paired with a 6-character base62 short code used as an opaque lookup key in the URL path. The QR code encodes a URL like `https://abc-xyz.trycloudflare.com/q/Xk9mQ3` — the short code is a pointer, not the secret itself, so it never leaks through browser history, `Referer` headers, or Cloudflare edge logs.\n\nEvery **60 seconds**, the server automatically rotates to a fresh token. The previous token remains valid for a **90-second grace period** to handle the race where you scan right as rotation happens — after that, it's dead. Each token is **single-use**: the moment a phone successfully scans it, the token is atomically consumed and a new one is immediately generated for the desktop display.\n\n#### Security Design\n\nThe design is informed by [\"Demystifying the (In)Security of QR Code-based Login\"](https://www.usenix.org/conference/usenixsecurity25/presentation/zhang-xin) (USENIX Security 2025), which found 47 of the top-100 websites vulnerable to QR auth attacks due to 6 critical design flaws across 42 CVEs. Codeman addresses all six:\n\n| USENIX Flaw | Mitigation |\n|-------------|------------|\n| **Flaw-1**: Missing single-use enforcement | Token atomically consumed on first scan — replays always fail |\n| **Flaw-2**: Long-lived tokens | 60s TTL with 90s grace, auto-rotation via timer |\n| **Flaw-3**: Predictable token generation | `crypto.randomBytes(32)` — 256-bit entropy. Short codes use rejection sampling to eliminate modulo bias |\n| **Flaw-4**: Client-side token generation | Server-side only — tokens never leave the server until embedded in the QR |\n| **Flaw-5**: Missing status notification | Desktop toast: *\"Device [IP] authenticated via QR (Safari). Not you? [Revoke]\"* — real-time QRLjacking detection |\n| **Flaw-6**: Inadequate session binding | IP + User-Agent stored for audit. Manual session revocation via API. HttpOnly + Secure + SameSite=lax cookies |\n\n#### Timing-Safe Lookup\n\nShort codes are stored in a `Map\u003cshortCode, TokenRecord\u003e`. Validation uses `Map.get()` — a hash-based O(1) lookup that reveals nothing about the target string through response timing. There is no character-by-character string comparison anywhere in the hot path, eliminating timing side-channel attacks entirely.\n\n#### Rate Limiting (Dual Layer)\n\nQR auth has its own rate limiting, completely independent from password auth:\n\n- **Per-IP**: 10 failed QR attempts per IP trigger a 429 block (15-minute decay window) — separate counter from Basic Auth failures, so a fat-fingered password doesn't burn your QR budget\n- **Global**: 30 QR attempts per minute across all IPs combined — defends against distributed brute force. With 62^6 = 56.8 billion possible short codes and only ~2 valid at any time, brute force is computationally infeasible regardless\n\n#### QR Code Size Optimization\n\nThe URL is kept deliberately short (`/q/` path + 6-char code = ~53-56 total characters) to target **QR Version 4** (33x33 modules) instead of Version 5 (37x37). Smaller QR codes scan faster on budget phones — modern devices read Version 4 in 100-300ms. The `/q/` prefix saves 7 bytes compared to `/qr-auth/`, which alone is the difference between QR versions.\n\n#### Desktop Experience\n\nThe QR display auto-refreshes every 60 seconds via SSE with the SVG embedded directly in the event payload (~2-5KB) — no extra HTTP fetch, sub-50ms refresh. A countdown timer shows time remaining. A \"Regenerate\" button instantly invalidates all existing tokens and creates a fresh one (useful if you suspect the QR was photographed).\n\nWhen someone authenticates via QR, the desktop shows a notification toast with the device's IP and browser — if it wasn't you, one click revokes all sessions.\n\n#### Threat Coverage\n\n| Threat | Why it doesn't work |\n|--------|-------------------|\n| **QR screenshot shared** | Single-use: consumed on first scan. 60s TTL: expired before the attacker can act. Desktop notification alerts you immediately. |\n| **Replay attack** | Atomic single-use consumption + 60s TTL. Old URLs always return 401. |\n| **Cloudflare edge logs** | Short code is an opaque 6-char lookup key, not the real 256-bit token. Single-use means replaying from logs always fails. |\n| **Brute force** | 56.8 billion combinations, ~2 valid at any time, dual-layer rate limiting blocks well before statistical feasibility. |\n| **QRLjacking** | 60s rotation forces real-time relay. Desktop toast provides instant detection. Self-hosted single-user context makes phishing implausible. |\n| **Timing attack** | Hash-based Map lookup — no string comparison timing leak. |\n| **Session cookie theft** | HttpOnly + Secure + SameSite=lax + 24h TTL. Manual revocation at `POST /api/auth/revoke`. |\n\n#### How It Compares\n\n| Platform | Model | Comparison |\n|----------|-------|------------|\n| **Discord** | Long-lived token, no confirmation, [repeatedly exploited](https://owasp.org/www-community/attacks/Qrljacking) | Codeman: single-use + TTL + notification |\n| **WhatsApp Web** | Phone confirms \"Link device?\", ~60s rotation | Comparable rotation; WhatsApp adds explicit confirmation (acceptable tradeoff for single-user) |\n| **Signal** | Ephemeral public key, E2E encrypted channel | Stronger crypto, but [exploited by Russian state actors in 2025](https://cloud.google.com/blog/topics/threat-intelligence/russia-targeting-signal-messenger) via social engineering despite it |\n\n\u003e Full design rationale, security analysis, and implementation details: [`docs/qr-auth-plan.md`](docs/qr-auth-plan.md)\n\n---\n\n## SSH Alternative (`sc`)\n\nIf you prefer SSH (Termius, Blink, etc.), the `sc` command is a thumb-friendly session chooser:\n\n```bash\nsc              # Interactive chooser\nsc 2            # Quick attach to session 2\nsc -l           # List sessions\n```\n\nSingle-digit selection (1-9), color-coded status, token counts, auto-refresh. Detach with `Ctrl+A D`.\n\n---\n\n## Keyboard Shortcuts\n\n| Shortcut | Action |\n|----------|--------|\n| `Ctrl+Enter` | Quick-start session |\n| `Ctrl+W` | Close session |\n| `Ctrl+Tab` | Next session |\n| `Alt+1`–`Alt+9` | Switch to tab N |\n| `Ctrl+Shift+{` / `Ctrl+Shift+}` | Move active tab left / right |\n| `Ctrl+K` | Kill all sessions |\n| `Ctrl+L` | Clear terminal |\n| `Ctrl+Shift+R` | Restore terminal size |\n| `Ctrl+Shift+V` | Toggle voice input |\n| `Ctrl/Cmd +/-` | Font size |\n| `Escape` | Close panels |\n\n---\n\n## API\n\n### Sessions\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| `GET` | `/api/sessions` | List all |\n| `POST` | `/api/quick-start` | Create case + start session |\n| `DELETE` | `/api/sessions/:id` | Delete session |\n| `POST` | `/api/sessions/:id/input` | Send input |\n\n### Respawn\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| `POST` | `/api/sessions/:id/respawn/enable` | Enable with config + timer |\n| `POST` | `/api/sessions/:id/respawn/stop` | Stop controller |\n| `PUT` | `/api/sessions/:id/respawn/config` | Update config |\n\n### Ralph / Todo\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| `GET` | `/api/sessions/:id/ralph-state` | Get loop state + todos |\n| `POST` | `/api/sessions/:id/ralph-config` | Configure tracking |\n\n### Subagents\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| `GET` | `/api/subagents` | List all background agents |\n| `GET` | `/api/subagents/:id` | Agent info and status |\n| `GET` | `/api/subagents/:id/transcript` | Full activity transcript |\n| `DELETE` | `/api/subagents/:id` | Kill agent process |\n\n### System\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| `GET` | `/api/events` | SSE stream |\n| `GET` | `/api/status` | Full app state |\n| `POST` | `/api/hook-event` | Hook callbacks |\n| `POST` | `/api/clipboard` | Push text to all connected browsers (`{text}`) |\n| `GET` | `/api/sessions/:id/run-summary` | Timeline + stats |\n\n---\n\n## Architecture\n\n```mermaid\nflowchart TB\n    subgraph Codeman[\"CODEMAN\"]\n        subgraph Frontend[\"Frontend Layer\"]\n            UI[\"Web UI\u003cbr/\u003e\u003csmall\u003exterm.js + Agent Windows\u003c/small\u003e\"]\n            API[\"REST API\u003cbr/\u003e\u003csmall\u003eFastify\u003c/small\u003e\"]\n            SSE[\"SSE Events\u003cbr/\u003e\u003csmall\u003e/api/events\u003c/small\u003e\"]\n        end\n\n        subgraph Core[\"Core Layer\"]\n            SM[\"Session Manager\"]\n            S1[\"Session (PTY)\"]\n            S2[\"Session (PTY)\"]\n            RC[\"Respawn Controller\"]\n        end\n\n        subgraph Detection[\"Detection Layer\"]\n            RT[\"Ralph Tracker\"]\n            SW[\"Subagent Watcher\u003cbr/\u003e\u003csmall\u003e~/.claude/projects/*/subagents\u003c/small\u003e\"]\n        end\n\n        subgraph Persistence[\"Persistence Layer\"]\n            SCR[\"Mux Manager\u003cbr/\u003e\u003csmall\u003e(tmux)\u003c/small\u003e\"]\n            SS[\"State Store\u003cbr/\u003e\u003csmall\u003estate.json\u003c/small\u003e\"]\n        end\n\n        subgraph External[\"External\"]\n            CLI[\"AI CLI\u003cbr/\u003e\u003csmall\u003eClaude Code / OpenCode\u003c/small\u003e\"]\n            BG[\"Background Agents\u003cbr/\u003e\u003csmall\u003e(Task tool)\u003c/small\u003e\"]\n        end\n    end\n\n    UI \u003c--\u003e API\n    API \u003c--\u003e SSE\n    API --\u003e SM\n    SM --\u003e S1\n    SM --\u003e S2\n    SM --\u003e RC\n    SM --\u003e SS\n    S1 --\u003e RT\n    S1 --\u003e SCR\n    S2 --\u003e SCR\n    RC --\u003e SCR\n    SCR --\u003e CLI\n    SW --\u003e BG\n    SW --\u003e SSE\n```\n\n---\n\n## Development\n\n```bash\nnpm install\nnpx tsx src/index.ts web    # Dev mode\nnpm run build               # Production build\nnpm test                    # Run tests\n```\n\nSee [CLAUDE.md](./CLAUDE.md) for full documentation.\n\n---\n\n## Codebase Quality\n\nThe codebase went through a comprehensive 7-phase refactoring that eliminated god objects, centralized configuration, and established modular architecture:\n\n| Phase | What changed | Impact |\n|-------|-------------|--------|\n| **Performance** | Cached endpoints, SSE adaptive batching, buffer chunking | Sub-16ms terminal latency |\n| **Route extraction** | `server.ts` split into 13 domain route modules + auth middleware + port interfaces | **−60%** server.ts LOC (6,736 → 2,697) |\n| **Domain splitting** | `types.ts` → 14 domain files, `ralph-tracker` → 7 files, `respawn-controller` → 5 files, `session` → 6 files | No more god files |\n| **Frontend modules** | `app.js` → 9 extracted modules (constants, mobile, voice, notifications, keyboard, CJK input, API, Ralph wizard, subagent windows) | **−24%** app.js LOC (15.2K → 11.5K) |\n| **Config consolidation** | ~70 scattered magic numbers → 9 domain-focused config files | Zero cross-file duplicates |\n| **Test infrastructure** | Shared mock library, 12 route test files, consolidated MockSession | Testable route handlers via `app.inject()` |\n\nFull details: [`docs/code-structure-findings.md`](docs/code-structure-findings.md)\n\n---\n\n## Published Packages\n\n### [`xterm-zerolag-input`](https://www.npmjs.com/package/xterm-zerolag-input)\n\n[![npm](https://img.shields.io/npm/v/xterm-zerolag-input?style=flat-square\u0026color=22c55e)](https://www.npmjs.com/package/xterm-zerolag-input)\n\nInstant keystroke feedback overlay for xterm.js. Eliminates perceived input latency over high-RTT connections by rendering typed characters immediately as a pixel-perfect DOM overlay. Zero dependencies, configurable prompt detection, full state machine with 78 tests.\n\n```bash\nnpm install xterm-zerolag-input\n```\n\n[Full documentation](packages/xterm-zerolag-input/README.md)\n\n---\n\n## License\n\nMIT — see [LICENSE](LICENSE)\n\n---\n\n\u003cp align=\"center\"\u003e\n  \u003cstrong\u003eTrack sessions. Visualize agents. Control respawn. Let it run while you sleep.\u003c/strong\u003e\n\u003c/p\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fark0n%2Fcodeman","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fark0n%2Fcodeman","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fark0n%2Fcodeman/lists"}