Start, prompt and read AI coding agent sessions (Claude Code, Codex, Gemini CLI and more) in Codeman
Runs on your machine from its package; the steps are below.

At most: moves money, runs code or commands, deletes data, or sends messages for you.
Odel charges nothing per call. The publisher may charge for its own service.
Runs locally over stdio
This server isn't hosted — your MCP client launches it from a package registry. Use one of the commands below, or drop the config into your client (e.g. Claude Desktop).
Run
npx -y aicodeman
MCP client config
{
"mcpServers": {
"codeman": {
"command": "npx",
"args": [
"-y",
"aicodeman"
]
}
}
}
Claude Code • OpenCode • Codex • Antigravity • Gemini • Pi • Grok • DeepSeek • OMP • Terminal - One Dashboard • Any Device
English • 简体中文
Codeman is a self-hosted mission control for AI coding agents. It spawns Claude Code, OpenCode, Codex, Antigravity, Gemini, Pi, Grok, DeepSeek Harness, or OMP inside persistent tmux sessions, streams the real terminal to any browser, and keeps agents productive after you walk away: it re-prompts on idle, resumes when a usage limit resets, runs scheduled jobs, and shows every background agent working in real time.
Get started in one line (macOS & Linux, Windows via WSL):
curl -fsSL https://getcodeman.com/install | bash
codeman web
# Open http://localhost:3000 and start your first session
The installer asks before every system change, and re-running the same line updates in place. Full details: Quick Start - Installation.
codeman agent and an MCP server let one agent start workers in any CLI, prompt them, wait for them and collect their answers
curl -fsSL https://getcodeman.com/install | bash
This installs Node.js, tmux and a build toolchain if missing (node-pty ships no Linux prebuilds, so it compiles from source), clones Codeman to ~/.codeman/app, and builds it. It looks at what is already on the machine, asks at most three questions, then does all the work unattended and ends on the URL with a QR code for your phone. A few things worth knowing:
tailscale serve, so you get https://<machine>.<tailnet>.ts.net with a real certificate and your tailnet as the login, no password needed), any device on your network (0.0.0.0, with a strongly recommended password prompt), or this machine only (127.0.0.1, safest). Skipping the password on a network bind requires an explicit confirmation and ends with a loud warning. The highlighted default reflects what is already on the machine (Tailscale when it is already connected, your existing binding on a re-run), and a bare Enter never pulls in new software. If another app already owns :443 on your node, Codeman goes under https://<machine>.<tailnet>.ts.net/codeman or on a second port instead of replacing it. A bare codeman web started by hand still defaults to loopback.codeman-<hostname> (which also renames it for SSH, so the default is no); install.sh name does it later.~/.codeman/app are stashed (never discarded), and a running service is restarted and verified. If a first install was interrupted, re-running resumes the full setup instead. install.sh status prints the URLs and the QR code again; install.sh update, install.sh tailscale and install.sh uninstall also exist.curl -fsSL https://getcodeman.com/install | bash -s -- --tailscale --service answers the questions from the command line (--lan, --local, --run, --no-start, --name <n>, --port <n>, --yes too). CI / headless: without a terminal attached, steps that would change your system abort with instructions instead of running silently; set CODEMAN_NONINTERACTIVE=1 to approve them for automation.You'll need at least one AI coding CLI installed — Claude Code, OpenCode, Codex, Antigravity, Gemini CLI, Pi, Grok Build, DeepSeek Harness, or OMP (any combination works; Gemini CLI is enterprise-only since Google's consumer cutover, and Antigravity is its successor). The installer detects whichever of the nine is present; if none is found, it offers to install any of them from a menu (DeepSeek excepted, since its npm package installs only a launcher with no runnable profile), or you can skip and install one yourself later. After install:
codeman web
# Open http://localhost:3000 and start your first session
Sharing with a small team? Start it in multi-user mode instead: each person gets their own login and workspace.
codeman users add alice --admin # create the first admin account
codeman web --multiuser # named logins + per-user case spaces
Prefer Docker Compose? A local-image Compose deployment ships in docker/: copy docker/.env.example to docker/.env, set CODEMAN_PASSWORD, then run bash docker/Start-Codeman.sh on Linux. Codeman runs in a container and spawns Docker cases as sibling containers through the host socket. After updating, run the script again rather than a plain docker compose up, so the rebuilt image, refreshed volumes and entrypoint arrive together. See the Docker deployment guide for direct Compose commands, storage and networking options.
Details in Multi-User Mode below.
To outlive the shell you started it in, without setting anything up:
codeman web -d # detach; logs to ~/.codeman/web.log
codeman web --status # is it up, and on which pid
codeman web --stop # graceful SIGTERM; agents keep running in tmux
-d waits until the server actually answers before reporting success, and refuses to start a second one on the same data dir (two servers sharing a tmux socket attach to each other's sessions).
To have it come back after a reboot, install it as a service instead. The installer's final menu does this for you (option 2); codeman service is the equivalent for an npm i -g aicodeman install:
codeman service install # systemd user unit (Linux) or LaunchAgent (macOS)
codeman service status
codeman service uninstall
service install writes the unit with your current PATH baked in, which matters more than it sounds: launchd hands a job /usr/bin:/bin:/usr/sbin:/sbin, so a Homebrew or nvm node, tmux or claude is invisible to a hand-written plist. It never copies CODEMAN_PASSWORD into the unit file; add that yourself if the service needs auth.
To write the unit by hand instead:
Linux (systemd):
mkdir -p ~/.config/systemd/user
cat > ~/.config/systemd/user/codeman-web.service << EOF
[Unit]
Description=Codeman Web Server
After=network.target
[Service]
Type=simple
ExecStart=$(which node) $HOME/.codeman/app/dist/index.js web
Restart=always
RestartSec=10
[Install]
WantedBy=default.target
EOF
systemctl --user daemon-reload
systemctl --user enable --now codeman-web
loginctl enable-linger $USER
macOS (launchd):
mkdir -p ~/Library/LaunchAgents
cat > ~/Library/LaunchAgents/com.codeman.web.plist << EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.codeman.web</string>
<key>ProgramArguments</key>
<array>
<string>$(which node)</string>
<string>$HOME/.codeman/app/dist/index.js</string>
<string>web</string>
</array>
<key>RunAtLoad</key><true/>
<key>KeepAlive</key><true/>
<key>StandardOutPath</key>
<string>/tmp/codeman.log</string>
<key>StandardErrorPath</key>
<string>/tmp/codeman.log</string>
</dict>
</plist>
EOF
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.codeman.web.plist
wsl bash -c "curl -fsSL https://getcodeman.com/install | bash"
Codeman requires tmux, so Windows users need WSL. 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, OpenCode, Codex, Antigravity, Gemini CLI, Pi, Grok Build, DeepSeek Harness, or OMP). After installing, http://localhost:3000 is accessible from your Windows browser.
The 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.
![]() | ![]() |
| Answering prompts by touch | Accessory bar + dedicated Enter button |
| Terminal Apps | Codeman Mobile |
|---|---|
| 200-300ms input lag over remote | Local echo — instant feedback |
| Tiny text, no context | Full xterm.js terminal |
| No session management | Swipe between sessions |
| No notifications | Push alerts for approvals and idle |
| Manual reconnect | tmux persistence |
| No agent visibility | Background agents in real-time |
| Copy-paste slash commands | One-tap /init, /clear, /compact |
| Password typing on phone | QR code scan — instant auth |
/init, /clear, /compact quick-action buttons above the virtual keyboard; destructive commands require a double-press to confirm, so you never fire one by accident; on Codex sessions the bar also shows ⇧← / ⇧→ (Shift+Left / Shift+Right: edit the last queued message / return through the prompt stack)visualViewport API)codeman web --https
# Open on your phone: https://<your-ip>:3000
localhostworks over plain HTTP. Use--httpswhen accessing from another device, or use Tailscale (recommended): the installer can set it up for you (choose Tailscale at the network-access prompt, or runbash ~/.codeman/app/install.sh tailscaleon an existing install). That gives youhttps://<your-machine>.<tailnet>.ts.netwith a real certificate: private to your tailnet, no password required, and PWA install + push notifications work on your phone. The installer ends on that URL with a QR code to scan, andbash ~/.codeman/app/install.sh statusprints it again any time.
Typing 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.
Each 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.
The security design addresses all 6 critical QR auth flaws identified in "Demystifying the (In)Security of QR Code-based Login" (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
A start-to-finish walkthrough for driving Codeman from the browser. If you just installed, this is where to begin.
codeman web # localhost:3000 (loopback only — safe default)
codeman web --port 8080 # custom port (or set CODEMAN_PORT)
codeman web --https # self-signed TLS (only needed for remote access)
codeman web -H 0.0.0.0 # bind LAN — REQUIRES CODEMAN_PASSWORD (see Security)
codeman web -d # detach: survives closing the shell (--status, --stop)
codeman service install # systemd/launchd service: comes back after reboots
Open the printed URL. The page is a single dashboard; everything below happens there.
Click + New Session (or Quick Start). A session is one AI CLI running in its own tmux-backed terminal. You choose:
| Field | What it does |
|---|---|
| Working directory / case | The folder the agent operates in. A "case" is just a named working dir Codeman remembers. Add Case creates one from scratch, links an existing folder, or clones a GitHub repo straight into one (Clone Repo). |
| CLI / run mode | Claude (default), OpenCode, Codex, Antigravity, Gemini, Pi, Grok, DeepSeek, OMP, or Terminal (plain shell). |
| Model | Per-session model (App Settings → Models → New Claude sessions). A soft default — /model still works in-session. |
| Effort / Ultracode | Reasoning effort (low–max) or ultracode for dynamic multi-agent workflows. Switchable anytime with /effort. |
Hit start — Codeman spawns the CLI via a real PTY and streams it to your browser over SSE.
Alt+1-9 to jump, Ctrl+Tab for next, drag to reorder (tab order syncs across your devices). Prefer a list? App Settings → Appearance → Tabs moves it into a left sidebar with a filter box (Alt+B collapses it) or a vertical rail whose rows sort by activity: blocked on you first, then longest running, then most recently quiet. The rail also has a Search sessions box, named tab groups (a group's menu starts a New session straight into it), and a Focus section of shortcuts to the tabs you keep coming back to.xterm.js terminal; full TUIs render correctly. Type directly and press Enter to send. Shift+Enter inserts a newline..codeman-uploads/ folder in the workspace (it ignores itself in git and is swept after 7 days), and the file's path lands on your prompt, unsent.Ctrl+Shift+V (Deepgram Nova-3, or this machine's Claude Code login with no API key; auto-silence stop).~/ and relative paths and paths broken across lines. An .html report opens as a real page, Markdown opens rendered, and a folder opens as a grid of its files.Shift+drag selects text even while the CLI owns the mouse, right-click copies it, and Auto Copy (opt-in) copies a selection the moment you release it. A program that copies with OSC 52 (Claude Code's /copy, a tmux yank, vim) puts the text on your browser's clipboard too.| Mode | Use it for | Where |
|---|---|---|
| Respawn | Long unattended runs — auto-restarts the CLI on idle/limit, with adaptive timing. Presets: solo-work, overnight-autonomous, … | Respawn tab |
| Orchestrator | Turn one goal into a phased plan and drive it to completion across agents. | Orchestrator panel |
| Cron | Saved, named jobs on a schedule (once/interval/daily/weekly) that spawn a session and send a prompt when due. | ⏰ Cron button (opt-in: App Settings → Header & Panels → Scheduling) |
| Auto-resume | Automatically continue after a subscription rate-limit resets. | Respawn tab (top) |
./scripts/tunnel.sh start opens a Cloudflare tunnel (set CODEMAN_PASSWORD first).codeman tui is a full-screen dashboard in the terminal (codeman tui --list to list, codeman tui 2 to attach straight to one).codeman web -d detaches from your shell (--status, --stop); codeman service install makes it a systemd user unit / macOS LaunchAgent that survives reboots. Both verify the server actually answers before reporting success, and both refuse to start a second server on one data dir. See Keep it running in the background.⚠️ Safety: if you're working inside a Codeman-managed session (
echo $CODEMAN_MUX→1), never runtmux kill-session/pkill claudedirectly — use the web UI or./scripts/tmux-manager.sh.
When 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.
A 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.
<span> 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)fontFamily, fontSize, fontWeight, and letterSpacing from xterm.js computed styles so overlay text is visually indistinguishable from real terminal outputExtracted as a standalone library:
xterm-zerolag-input— see Published Packages.
Watch 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.
Multi-agent Workflow runs ("ultracode") get the same treatment: a floating run window tracks the whole workflow live, with phases, per-agent token counts, and the current tool of every agent:
Agent Teams — first-class support for Claude Code's native multi-agent teams (CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1). TeamWatcher polls ~/.claude/teams/, matches teammates to their lead session, and surfaces them as live subagent windows with team-aware idle detection — so the Respawn Controller won't fire while teammates are still working. See docs/agent-teams/.
The 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.
WATCHING → IDLE DETECTED → SEND UPDATE → /clear → /init → CONTINUE → WATCHING
continue — so an overnight run survives the 5-hour window instead of stalling until morning. Recognizes every Claude Code limit-message format, retries if still limited, survives Codeman restarts, and holds respawn cycles while paused so /clear can't wipe the waiting conversation. Enable per session at the top of the Respawn tabsolo-work (3s idle, 60min), subagent-workflow (45s, 240min), team-lead (90s, 480min), ralph-todo (8s, 480min), overnight-autonomous (10s, 480min)Beyond single-session respawn, the Orchestrator turns a high-level goal into a phased plan and drives it to completion across multiple agents — a state machine that runs idle → planning → approval → executing → verifying → (replanning) → completed.
orchestrator key in state.json, so it survives restartsPOST /api/orchestrator/start → /approve → /status (10 endpoints)Full design:
docs/orchestrator-loop-architecture.md.
Run 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.
Watch and drive up to six sessions side by side in one window. Click Tiles in the header (or press Ctrl+Shift+G) and your sessions open as a grid of live terminals: every tile takes your keystrokes and shows its agent's logo, model and state in its header. Right-click Tiles to choose 2, 4 or 6 tiles, and drag a tile by its header to move it. Click Tiles again to return to a single session. Desktop only (a window about 1180px wide or more). Full guide: Tile Grid.
Shift+wheel scrolls the tile's local history.Every 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.
Ctrl/Cmd/Alt+K opens a fuzzy session palette; Browse all sessions opens the Session Manager: one deduped list of everything Codeman knows about (live sessions, past sessions from state and lifecycle history, and Claude transcripts), each row showing its first and most recent prompt.
Running Codeman on multiple hosts (laptop, dev box, NAS)? The browser tab title is codeman:<hostname> so you can tell which backend each tab points at without clicking in:
codeman web # codeman:<os.hostname()>
codeman web --title-hostname dev-box # codeman:dev-box (manual override for noisy hostnames)
The 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:<host>) and to OS-level desktop notifications (codeman:<host>: <event>), so cross-host alerts in the system notification center are also unambiguous.
| Threshold | Action | Result |
|---|---|---|
| 110k tokens | Auto /compact | Context summarized, work continues |
| 140k tokens | Auto /clear | Fresh start with /init |
Every tab tells you its state at a glance. A running session keeps its green status dot. When a session stops and waits for input, its tab turns yellow: steady ring, tinted background, yellow dot, with a slow breathing glow on top. When a permission prompt or question is blocking the agent, the tab turns red with a faster pulse. The base tint never blinks off, so even a split-second glance (or a screenshot) reads the true state; the ring stays visible while the tab is selected, and a page reload re-arms pending alerts from the server, so a blocked session can never hide behind a fresh-looking tab.
Real-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.
Click 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.
Terminal-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:
PTY Output → 16ms Server Batch → DEC 2026 Wrap → SSE → Client rAF → xterm.js (60fps)
codeman web -d runs the server detached with a pidfile, ~/.codeman/web.log, and verified startup (it polls the server until it answers, so a port clash never reads as success); codeman service install writes a systemd user unit (Linux) or LaunchAgent (macOS) with your shell's PATH baked in, so an nvm or Homebrew node, tmux and claude are actually found. Secrets are never written into unit filescodeman doctor on the server and lists Node, tmux, every agent CLI and the optional office tools with versions, paths and install hints. Besides the PATH, it also looks in each CLI's usual install directories (~/.local/bin, ~/.npm-global/bin and the like), so most installs are found under a service with a minimal PATH. Admin only in multi-user mode.● 3 uncommitted files, ↑ 2 commits not pushed, ⚠ merge conflicts, ✓ all committed and pushed. Click it for a draggable window listing the staged, not-staged, untracked and conflicted files (grouped under collapsed folders, or as a flat list if you turn that setting off) and the unpushed commits; click a file to see its diff (new files as all additions, deleted files as all removals), with Open file to jump to the viewer. A folder that holds several projects gets one collapsible section per repository found up to two levels down, all collapsed until you open them. Read-only and offline (Codeman never fetches or changes the repo); not shown for Docker or remote sessions.~/codeman-cases/<name> and registers it as a normal case, ready to run an agent in. It preflights the URL while you type (tells you whether it can be cloned anonymously and offers the repo's real branches and tags for the optional branch/tag field), fills the case name in from the URL, and lets you pick which CLI the Run button should use. Public repositories over https://; Codeman never collects or stores credentials~/codeman-cases. The target must be a new or empty folder; system directories, your home folder itself, credential trees such as ~/.ssh, and Codeman's own data folder are refused. Admin only in multi-user mode.CLAUDE_CODE_* vs OPENCODE_* vs CODEX_* vs ANTIGRAVITY_* vs GEMINI_*/GOOGLE_* vs PI_* vs GROK_*/XAI_* vs DSH_*/DEEPSEEK_* vs OMP_*). See docs/opencode-integration.md, docs/pi-integration.md, docs/grok-integration.md, docs/deepseek-integration.md and docs/omp-integration.mdPOST /api/model-endpoints; its models are discovered from /v1/models), apply it to a session (POST /api/sessions/:id/custom-model), and the CLI restarts in place on that endpoint. Verified live for Claude, OpenCode, Pi, Grok and OMP; Codex, Gemini and DeepSeek have documented gaps, Antigravity has no mechanism. A toolbar picker is the follow-up. See docs/custom-model-endpoints.mdhttp:// target works from a phone over HTTPS and through the tunnel, single-page apps route on their own paths, and a frame that reloads recovers itself. A localhost link an agent prints opens as a web tab automatically. See docs/web-tabs.md.tar.gz to move it to another machine. See docs/docker-cases.mddocs/remote-sessions.mdlow–max) or enable ultracode (dynamic multi-agent workflows). Soft defaults only — switchable anytime with /effort in-session. Extended-thinking budget is configurable tooCtrl+Shift+V)CODEMAN_GESTURE=1 + App Settings → Terminal & Input.html path an agent printed (a test report, a coverage page, a dashboard) and the File Viewer shows it as a real page, with the scripts, styles, images and data files from its folder; the Page pill switches to the source and its Edit button. The page runs sandboxed with an opaque origin, so it cannot read Codeman's cookies, call Codeman's API or read files outside its own folder. Network access stays open, so a dashboard that loads Chart.js or fonts from a CDN works as it would from disk. Broad folders (/, your home, /tmp) are refused, so give a report a folder of its own. A .md path clicked in the terminal opens rendered too~/…, relative to the session folder, or broken across terminal lines are clickable in the terminal and the Response Viewer, and so are folders: a folder opens a grid of its files (renders, screenshots, reports) that you page through with the arrow keys/copy, a tmux yank, vim) puts the text on your browser's clipboard, in the main terminal and in every tile. Clipboard read requests are refused, and replaying a buffer never copies.codeman-bak. App Settings → Agents & CLIs; admin only in multi-user modeclaude at spawn and never writes into your settings files, plus Codex limits from its own app-server; per device, on for desktops and off for phonescache:until HH:MM) or cold, in the footer (when you have no statusline of your own) and in Session Options → Respawn, with the cost of a re-cache and a note after a compaction. Display only; it survives a restartcodeman:<host> so multi-host setups stay unambiguousRun a case inside its own hardened Docker container instead of directly on your host — for security isolation, reproducible toolchains, and one-click portability.
docker exec into the same container; killing one session never tears the container out from under the others.--cap-drop ALL, no-new-privileges, PID/memory caps, never --privileged or the docker socket; a sealed profile (no host credentials, network off) is one toggle away.execs into it and never starts, stops, restarts or removes it; one adopted container can back several cases at different directories, and copy an existing case pre-fills the form from a sibling. Admin-only in multi-user mode, since the container's mounts belong to whoever started it..tar.gz, docker load it on the other side, and import it into a fresh case.Prerequisite: just Docker (or Podman). The agent base image builds itself automatically on first use, with progress streamed to the UI (or pre-build it with node scripts/build-agent-image.mjs). Full guide: docs/docker-cases.md.
Point a case at another machine and run the agent there, over SSH, with the same dashboard, mobile UI, and autonomy features. Your laptop is just a window onto a session that lives on the remote host.
codeman-* sessions already running on a host (started by that machine's own Codeman, or by another operator) and attach to one. Attached sessions you don't own detach on tab close, never kill.realpath + stat probe, then a streamed cat, Range seeking included), so a clicked path opens the file on the machine the agent is on. Nothing is copied to the Codeman host; editing and Office previews answer a clear 400 instead of a misleading 404.Set it up under New Case → Remote (host, user, identity file, optional jump host). Full design: docs/remote-sessions.md.
Share one Codeman with a small trusted team, each person getting their own login and workspace. Off by default — without the flag, nothing changes.
Enable with codeman web --multiuser (or CODEMAN_MULTIUSER=1). Create the first admin, then manage users from the CLI or the Users tab in App Settings:
codeman users add alice --admin # prompts for a password (or --password-stdin)
codeman users add bob # a regular user
codeman users list
~/codeman-users/<name>/cases; sessions, cases, search, and real-time events are scoped to their owner. Admins see everything.~/.codeman/users.json; disable, reset (one-time password), or delete an account at any time. Admin actions are audited to ~/.codeman/admin-audit.jsonl.--permission-mode auto (Anthropic's classifier-guarded mode); raw shell sessions, cron launchCommand, and skip-permissions require an explicit per-user grant.⚠️ This separates workspaces; it does not sandbox users from each other. Every session runs as the same OS account, so a determined user's agent can still reach another user's files. For real isolation, pair users with Docker cases or run separate instances under separate OS accounts. See
docs/multi-user-plan.mdand the multi-user section ofdocs/security-architecture.md.
Access Codeman from your phone or any device outside your local network using a free Cloudflare quick tunnel — no port forwarding, no DNS, no static IP required.
Browser (phone/tablet) → Cloudflare Edge (HTTPS) → cloudflared → localhost:3000
Prerequisites: Install cloudflared and set CODEMAN_PASSWORD in your environment.
# Quick start
./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 + URL
The 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.
# Enable as a persistent service
systemctl --user enable codeman-tunnel
loginctl enable-linger $USER
# Or via the Codeman web UI: App Settings → System → Remote access → Cloudflare Tunnel
admin or CODEMAN_USERNAME)codeman_session cookie (24h TTL, auto-extends on activity)Always set CODEMAN_PASSWORD before exposing via tunnel — without it, anyone with the URL has full access to your sessions.
Typing 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.
Desktop displays QR → Phone scans → GET /q/Xk9mQ3 → Server validates
→ Token atomically consumed (single-use) → Session cookie issued → 302 to /
→ Desktop notified: "Device authenticated via QR" → New QR auto-generated
Someone 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.
The 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.
Every 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.
The design is informed by "Demystifying the (In)Security of QR Code-based Login" (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:
| USENIX Flaw | Mitigation |
|---|---|
| Flaw-1: Missing single-use enforcement | Token atomically consumed on first scan — replays always fail |
| Flaw-2: Long-lived tokens | 60s TTL with 90s grace, auto-rotation via timer |
| Flaw-3: Predictable token generation | crypto.randomBytes(32) — 256-bit entropy. Short codes use rejection sampling to eliminate modulo bias |
| Flaw-4: Client-side token generation | Server-side only — tokens never leave the server until embedded in the QR |
| Flaw-5: Missing status notification | Desktop toast: "Device [IP] authenticated via QR (Safari). Not you? [Revoke]" — real-time QRLjacking detection |
| Flaw-6: Inadequate session binding | IP + User-Agent stored for audit. Manual session revocation via API. HttpOnly + Secure + SameSite=lax cookies |
Short codes are stored in a Map<shortCode, TokenRecord>. 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.
QR auth has its own rate limiting, completely independent from password auth:
The 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.
The 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).
When 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.
| Threat | Why it doesn't work |
|---|---|
| QR screenshot shared | Single-use: consumed on first scan. 60s TTL: expired before the attacker can act. Desktop notification alerts you immediately. |
| Replay attack | Atomic single-use consumption + 60s TTL. Old URLs always return 401. |
| 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. |
| Brute force | 56.8 billion combinations, ~2 valid at any time, dual-layer rate limiting blocks well before statistical feasibility. |
| QRLjacking | 60s rotation forces real-time relay. Desktop toast provides instant detection. Self-hosted single-user context makes phishing implausible. |
| Timing attack | Hash-based Map lookup — no string comparison timing leak. |
| Session cookie theft | HttpOnly + Secure + SameSite=lax + 24h TTL. Manual revocation at POST /api/auth/revoke. |
| Platform | Model | Comparison |
|---|---|---|
| Discord | Long-lived token, no confirmation, repeatedly exploited | Codeman: single-use + TTL + notification |
| WhatsApp Web | Phone confirms "Link device?", ~60s rotation | Comparable rotation; WhatsApp adds explicit confirmation (acceptable tradeoff for single-user) |
| Signal | Ephemeral public key, E2E encrypted channel | Stronger crypto, but exploited by Russian state actors in 2025 via social engineering despite it |
Full design rationale, security analysis, and implementation details:
docs/qr-auth-plan.md
By default Codeman launches sessions with --dangerously-skip-permissions, so the web UI is by design a remote-code-execution surface for whoever can reach it — the whole security model exists to control who that is. (The startup permission mode is configurable; see below.) Recent hardening (v0.9.0 + v0.9.5) closes the browser-driven attack paths that bite self-hosted dev tools. Full model: docs/security-architecture.md. Found a vulnerability? See SECURITY.md for private disclosure and the list of known limitations.
127.0.0.1, reachable only from the same machine, so the no-password default is safe out of the box (the guided installer asks about network access and configures the binding + password for you). Binding a non-loopback host without CODEMAN_PASSWORD starts but prints a loud warning with three concrete fixes (set a password, loopback + an authenticated tunnel, or explici