Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
11c8247
feat(cli-registry): add data-driven CLI registry core (phase 0)
opticon454 Aug 20, 2026
3be38e8
refactor(cli-resolver): unify the six CLI resolvers onto the registry…
opticon454 Aug 20, 2026
5fd75d8
refactor(session): back the mode capability predicates with the regis…
opticon454 Aug 20, 2026
138d6ef
refactor(tmux-manager): render spawn commands through the CLI registr…
opticon454 Aug 20, 2026
0dfe275
refactor(hosts): drive remote/docker default commands from the regist…
opticon454 Aug 20, 2026
e4b76c6
feat(api): expose the CLI registry over HTTP and de-duplicate route l…
opticon454 Aug 20, 2026
4771236
refactor(frontend): collapse the five run<X>() methods into runCli(id…
opticon454 Aug 20, 2026
6d1340b
feat(api): add CLI registry write endpoints (phase 8, backend)
opticon454 Aug 20, 2026
5858f00
feat(settings): add the Installed CLIs management UI (phase 8, frontend)
opticon454 Aug 20, 2026
c0ede80
feat(cli-registry): drive install.sh, Docker agent image, and session…
opticon454 Aug 20, 2026
92b2582
feat(cli-registry): add GitHub Copilot CLI (disabled by default) and …
opticon454 Aug 21, 2026
533ae20
fix(frontend): rebuild the Run menu from the live CLI registry
opticon454 Aug 21, 2026
0e4b8a4
fix(frontend): tie the welcome-screen CLI buttons to enabled/disabled…
opticon454 Aug 22, 2026
3940c60
fix(frontend): include Shell in the dynamic welcome-screen buttons
opticon454 Aug 22, 2026
79901c6
feat(cli-registry): add Grok Build (xAI) + port upstream's resolver b…
opticon454 Aug 25, 2026
4e7ccb1
Merge origin/master: reconcile upstream Grok/TUI/tab-layout work with…
opticon454 Aug 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .changeset/72f92691.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
"aicodeman": minor
---

CLI backends are now a data-driven registry instead of a hardcoded set. Every CLI (Claude
Code, Terminal/Shell, OpenCode, Codex, Gemini, Antigravity, Pi, or a custom one you add) is
a `CliEntry` in a central registry — a shipped stock catalog layered with user overrides in
`~/.codeman/clis.json`. Adding, removing, reordering, or reconfiguring a CLI is now a
settings change, not a code change.

- New App Settings → Agents & CLIs → **Installed CLIs** panel: enable/disable, reorder,
and add/remove custom CLI entries.
- New API: `GET /api/clis`, `PUT /api/clis/:id/enabled`, `PUT /api/clis/order`,
`POST /api/clis/:id`, `DELETE /api/clis/:id`, plus the generic
`GET /api/cli/:id/status` (the five legacy `/api/<mode>/status` routes are kept as
aliases, so nothing breaks).
- `install.sh` and the Docker agent-image build now read the same registry (via a
generated `config/clis.stock.json` export) instead of keeping their own hardcoded
per-CLI search paths and install steps, so a new stock CLI needs no installer or
Dockerfile change.
- `SessionMode`/`agentType` validation is now built from the live, enabled registry
rather than a fixed literal enum, so a custom CLI added through the settings UI is
immediately usable as a session `mode`, not just visible in menus.
- Internally: the five per-CLI resolvers, command builders, and capability checks
(`isExternalCliMode`, `isAltScreenStripMode`, `hooksAvailableForMode`, and friends)
now read capability flags off the registry instead of branching on the CLI's name.
Verified byte-identical against the previous hand-written command builders for the
stock catalog (`test/cli-registry-argv-parity.test.ts`), and a static guard
(`test/cli-registry-no-id-branching.test.ts`) keeps per-CLI-id branching out of every
file except the stock catalog itself.
- New docs: [`docs/cli-registry.md`](../docs/cli-registry.md).

No behavior change for existing installs — the stock catalog reproduces every existing
CLI's launch command, environment handling, and capabilities exactly.
18 changes: 18 additions & 0 deletions .changeset/a4c76a4a.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
"aicodeman": minor
---

Added GitHub Copilot CLI (`copilot`, npm `@github/copilot`) to the stock CLI registry,
shipped **disabled by default** since it is new to the catalog.

Also: enabling any CLI whose binary isn't installed yet now installs it automatically.
Previously a disabled entry's binary was never checked, and switching it on left the
operator to run its install command by hand. Now `PUT /api/clis/:id/enabled
{"enabled":true}` (what the settings UI's toggle calls) kicks off that entry's install
command in the background if needed, exposing progress as `installStatus` on both that
response and `GET /api/clis` (`{state: 'installing'|'success'|'error', command, message?}`);
the settings UI shows "Installing…" / "Install failed: …" inline and polls until it
resolves. This only ever runs as the direct result of that explicit API call, and the
command that runs is exactly the one already shown as the entry's install hint — see
`docs/cli-registry.md`'s "Enabling a CLI auto-installs it" section for the full trust
model.
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ CI runs `npm run check:lockfile` on every push/PR, so lockfile drift fails the b

Codeman is a Claude Code session manager with web interface and autonomous Ralph Loop. Spawns Claude CLI via PTY, streams via SSE, supports respawn cycling for 24+ hour autonomous runs.

**Tech Stack**: TypeScript (ES2022/NodeNext, strict mode), Node.js, Fastify, node-pty, xterm.js. Supports Claude Code, OpenCode, Codex (OpenAI), Gemini (Google, enterprise-only since Google's June 2026 consumer cutover), Antigravity (`agy`, Google), Pi (pi.dev) and Grok Build (`grok`, xAI) CLIs via pluggable CLI resolvers (`SessionMode = 'claude' | 'shell' | 'opencode' | 'codex' | 'gemini' | 'antigravity' | 'pi' | 'grok'`).
**Tech Stack**: TypeScript (ES2022/NodeNext, strict mode), Node.js, Fastify, node-pty, xterm.js. Ships stock support for Claude Code, OpenCode, Codex (OpenAI), Gemini (Google, enterprise-only since Google's June 2026 consumer cutover), Antigravity (`agy`, Google), Pi (pi.dev) and Grok Build (`grok`, xAI), but the set of CLI backends is **data, not code**: every one is a `CliEntry` in the CLI registry (`src/config/cli-registry/`, overrides in `~/.codeman/clis.json`), and `SessionMode` (`src/types/session.ts`) is a string id resolved against it rather than a fixed set of names. Adding, removing, or reconfiguring a CLI — including a custom one, e.g. GitHub Copilot CLI — needs no code change; see [`docs/cli-registry.md`](docs/cli-registry.md).

**TypeScript Strictness** (see `tsconfig.json`): `noUnusedLocals`, `noUnusedParameters`, `noImplicitReturns`, `noImplicitOverride`, `noFallthroughCasesInSwitch`, `allowUnreachableCode: false`, `allowUnusedLabels: false`.

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ codeman web

The installer asks before every system change, and re-running the same line updates in place. Full details: [Quick Start - Installation](#quick-start---installation).

- **One dashboard, seven CLIs** - run [Claude Code, OpenCode, Codex, Antigravity, Gemini, Pi, or Grok](#more-features) per session (plus plain shell), locally, [in Docker](#isolated-docker-sessions), or [over SSH](#remote-ssh-sessions)
- **One dashboard, any CLI** - run [Claude Code, OpenCode, Codex, Antigravity, Gemini, Pi, or Grok](#more-features) per session (plus plain shell), locally, [in Docker](#isolated-docker-sessions), or [over SSH](#remote-ssh-sessions) — the set of CLIs is a config file, not a fixed list, so adding another agent CLI is a settings change, not a code change ([`docs/cli-registry.md`](docs/cli-registry.md))
- **Truly phone-friendly** - a [touch-optimized terminal](#mobile-optimized-web-ui) with instant local echo, QR login, swipe navigation, and push notifications
- **Runs while you sleep** - [idle detection + respawn cycling](#respawn-controller) and auto-resume when a subscription limit resets, for 24+ hour unattended runs
- **See your agents think** - [live floating windows](#live-agent-visualization) for every subagent and teammate, with real-time transcripts
Expand Down Expand Up @@ -437,7 +437,7 @@ PTY Output → 16ms Server Batch → DEC 2026 Wrap → SSE → Client rAF → xt
- **Background daemon & service install** — `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 files
- **Self-update** — git-clone installs under systemd/launchd update in place from **App Settings → System → Updates**: it detects the latest release, auto-stashes a dirty tree, and streams build progress across the service restart (npm installs report as non-updatable)
- **Clone a GitHub repo as a case** — paste a repository URL into **Add Case → Clone Repo** and Codeman clones it into `~/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
- **Multi-CLI** — run **Claude Code**, **OpenCode**, **Codex**, **Antigravity**, **Gemini**, **Pi**, or **Grok** per session; env-var prefixes auto-gate (`CLAUDE_CODE_*` vs `OPENCODE_*` vs `CODEX_*` vs `ANTIGRAVITY_*` vs `GEMINI_*`/`GOOGLE_*` vs `PI_*` vs `GROK_*`/`XAI_*`). See [`docs/opencode-integration.md`](docs/opencode-integration.md), [`docs/pi-integration.md`](docs/pi-integration.md) and [`docs/grok-integration.md`](docs/grok-integration.md)
- **Multi-CLI, extensible** — run **Claude Code**, **OpenCode**, **Codex**, **Antigravity**, **Gemini**, **Pi**, or **Grok** per session, or add your own (App Settings → Agents & CLIs, or edit `~/.codeman/clis.json` — see [`docs/cli-registry.md`](docs/cli-registry.md)); env-var prefixes auto-gate (`CLAUDE_CODE_*` vs `OPENCODE_*` vs `CODEX_*` vs `ANTIGRAVITY_*` vs `GEMINI_*`/`GOOGLE_*` vs `PI_*` vs `GROK_*`/`XAI_*`). See [`docs/opencode-integration.md`](docs/opencode-integration.md), [`docs/pi-integration.md`](docs/pi-integration.md) and [`docs/grok-integration.md`](docs/grok-integration.md)
- **Docker sessions** — run a case inside an isolated, hardened container. One checkbox on **Create New** spins up a container with sensible defaults and starts the agent inside it; multiple sessions share one per-case container; export a container + its workspace to a portable `.tar.gz` to move it to another machine. See [`docs/docker-cases.md`](docs/docker-cases.md)
- **Remote SSH sessions** — point a case at another machine and run the agent there inside a durable remote tmux: survives SSH drops, auto-reconnects, and can discover + attach sessions already running on the host. See [`docs/remote-sessions.md`](docs/remote-sessions.md)
- **Effort & Ultracode** — set a per-session default effort (`low`–`max`) or enable **ultracode** (dynamic multi-agent workflows). Soft defaults only — switchable anytime with `/effort` in-session. Extended-thinking budget is configurable too
Expand Down
192 changes: 192 additions & 0 deletions config/clis.stock.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
[
{
"id": "claude",
"label": "Claude",
"stock": true,
"discovery": {
"binaries": ["claude"],
"searchDirs": ["~/.local/bin", "~/.claude/local", "/usr/local/bin", "~/.npm-global/bin", "~/bin"],
"version": {
"arg": "--version",
"regex": "(\\d+\\.\\d+\\.\\d+)",
"retryOnTransientFailure": true
},
"install": {
"command": {
"linux": "curl -fsSL https://claude.ai/install.sh | bash",
"darwin": "curl -fsSL https://claude.ai/install.sh | bash",
"wsl": "curl -fsSL https://claude.ai/install.sh | bash"
},
"npmPackage": "@anthropic-ai/claude-code",
"docsUrl": "https://docs.claude.com/claude-code"
}
}
},
{
"id": "shell",
"label": "Shell",
"stock": true,
"discovery": {
"binaries": [],
"searchDirs": [],
"install": {
"command": {}
}
}
},
{
"id": "opencode",
"label": "OpenCode",
"stock": true,
"discovery": {
"binaries": ["opencode"],
"searchDirs": [
"~/.opencode/bin",
"~/.local/bin",
"/usr/local/bin",
"~/go/bin",
"~/.bun/bin",
"~/.npm-global/bin",
"~/bin"
],
"version": {
"arg": "--version",
"regex": "(\\d+\\.\\d+\\.\\d+)"
},
"install": {
"command": {
"linux": "curl -fsSL https://opencode.ai/install | bash",
"darwin": "curl -fsSL https://opencode.ai/install | bash"
},
"npmPackage": "opencode-ai",
"docsUrl": "https://opencode.ai/docs"
}
}
},
{
"id": "codex",
"label": "Codex",
"stock": true,
"discovery": {
"binaries": ["codex"],
"searchDirs": ["~/.codex/bin", "~/.local/bin", "/usr/local/bin", "~/.bun/bin", "~/.npm-global/bin", "~/bin"],
"version": {
"arg": "--version",
"regex": "(\\d+\\.\\d+\\.\\d+)"
},
"install": {
"command": {
"linux": "npm install -g @openai/codex",
"darwin": "npm install -g @openai/codex"
},
"npmPackage": "@openai/codex",
"docsUrl": "https://developers.openai.com/codex/cli"
}
}
},
{
"id": "gemini",
"label": "Gemini",
"stock": true,
"discovery": {
"binaries": ["gemini"],
"searchDirs": ["~/.gemini/bin", "~/.local/bin", "/usr/local/bin", "~/.bun/bin", "~/.npm-global/bin", "~/bin"],
"version": {
"arg": "--version",
"regex": "(\\d+\\.\\d+\\.\\d+)"
},
"install": {
"command": {
"linux": "npm install -g @google/gemini-cli",
"darwin": "npm install -g @google/gemini-cli"
},
"npmPackage": "@google/gemini-cli",
"docsUrl": "https://github.com/google-gemini/gemini-cli"
}
}
},
{
"id": "antigravity",
"label": "Antigravity",
"stock": true,
"discovery": {
"binaries": ["agy"],
"searchDirs": ["~/.local/bin", "~/.antigravity/bin", "/usr/local/bin", "~/bin"],
"version": {
"arg": "--version",
"regex": "(\\d+\\.\\d+\\.\\d+)"
},
"install": {
"command": {
"linux": "curl -fsSL https://antigravity.google/cli/install.sh | bash",
"darwin": "curl -fsSL https://antigravity.google/cli/install.sh | bash"
},
"docsUrl": "https://antigravity.google/cli"
}
}
},
{
"id": "pi",
"label": "Pi",
"stock": true,
"discovery": {
"binaries": ["pi"],
"searchDirs": ["~/.local/bin", "/usr/local/bin", "~/.bun/bin", "~/.npm-global/bin", "~/bin"],
"version": {
"arg": "--version",
"regex": "(?:^|\\s)(\\d+\\.\\d+\\.\\d+)",
"requireVersionMatch": true
},
"install": {
"command": {
"linux": "npm install -g --ignore-scripts @earendil-works/pi-coding-agent",
"darwin": "npm install -g --ignore-scripts @earendil-works/pi-coding-agent"
},
"npmPackage": "@earendil-works/pi-coding-agent",
"docsUrl": "https://pi.dev"
}
}
},
{
"id": "copilot",
"label": "GitHub Copilot",
"stock": true,
"discovery": {
"binaries": ["copilot"],
"searchDirs": ["~/.local/bin", "/usr/local/bin", "~/.npm-global/bin", "~/bin"],
"version": {
"arg": "--version",
"regex": "(\\d+\\.\\d+\\.\\d+)"
},
"install": {
"command": {
"linux": "npm install -g @github/copilot",
"darwin": "npm install -g @github/copilot"
},
"npmPackage": "@github/copilot",
"docsUrl": "https://docs.github.com/en/copilot/how-tos/copilot-cli/set-up-copilot-cli/install-copilot-cli"
}
}
},
{
"id": "grok",
"label": "Grok",
"stock": true,
"discovery": {
"binaries": ["grok"],
"searchDirs": ["~/.grok/bin", "~/.local/bin", "/usr/local/bin", "~/bin"],
"version": {
"arg": "--version",
"regex": "(?:^|\\s)(\\d+\\.\\d+\\.\\d+)",
"requireVersionMatch": true
},
"install": {
"command": {
"linux": "curl -fsSL https://x.ai/cli/install.sh | bash",
"darwin": "curl -fsSL https://x.ai/cli/install.sh | bash"
},
"docsUrl": "https://github.com/xai-org/grok-build"
}
}
}
]
57 changes: 33 additions & 24 deletions docker/agent.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,41 +26,50 @@ RUN apt-get update \
openssh-client \
&& rm -rf /var/lib/apt/lists/*

# The npm-published agent CLIs. Pinning is left to the rebuild cadence (see
# docs/docker-cases-plan.md, user-decision 2).
RUN npm install -g \
@anthropic-ai/claude-code \
@openai/codex \
@google/gemini-cli \
opencode-ai \
# The npm-published agent CLIs. Package list is a build ARG, populated by
# scripts/build-agent-image.mjs from the live CLI registry (config/cli-registry) —
# a new registry entry with a plain `npm install -g <pkg>` install command (the
# common case, e.g. a future GitHub Copilot CLI entry) is picked up here with NO
# Dockerfile edit. Defaults preserve today's four CLIs for a hand-run
# `docker build` that skips the wrapper script. Pinning is left to the rebuild
# cadence (see docs/docker-cases-plan.md, user-decision 2).
ARG CLI_NPM_PACKAGES="@anthropic-ai/claude-code @openai/codex @google/gemini-cli opencode-ai"
RUN npm install -g ${CLI_NPM_PACKAGES} \
&& npm cache clean --force

# Antigravity (`agy`) is NOT on npm — Google ships a standalone binary through its
# own installer, so it needs its own step. `--dir /usr/local/bin` is load-bearing:
# the installer's default target is `$HOME/.local/bin`, which at build time is
# root's home and would be unreachable by the `agent` user the container runs as.
# Antigravity (`agy`) has no npmPackage in the registry — it is NOT on npm, Google
# ships a standalone binary through its own installer — so it stays a documented
# Dockerfile special case rather than a generic npm-install line (the sanctioned
# per-CLI exception; see docs/cli-registry.md). `--dir /usr/local/bin` is
# load-bearing: the installer's default target is `$HOME/.local/bin`, which at
# build time is root's home and would be unreachable by the `agent` user the
# container runs as.
# ⚠️ This binary is ~190MB on its own; it is the single largest layer in the image.
RUN curl -fsSL https://antigravity.google/cli/install.sh | bash -s -- --dir /usr/local/bin \
ARG CLI_ANTIGRAVITY_INSTALL_URL="https://antigravity.google/cli/install.sh"
RUN curl -fsSL "${CLI_ANTIGRAVITY_INSTALL_URL}" | bash -s -- --dir /usr/local/bin \
&& chmod 755 /usr/local/bin/agy \
&& agy --version

# Pi (pi.dev). Upstream documents --ignore-scripts (pi needs no lifecycle scripts);
# kept out of the shared npm block above so the flag cannot silently change how the
# other four CLIs install.
RUN npm install -g --ignore-scripts @earendil-works/pi-coding-agent \
# other four CLIs install. Package name is still a build ARG from the registry.
ARG CLI_PI_NPM_PACKAGE="@earendil-works/pi-coding-agent"
RUN npm install -g --ignore-scripts ${CLI_PI_NPM_PACKAGE} \
&& npm cache clean --force \
&& pi --version

# Grok Build (`grok`, xAI) is NOT on npm: a standalone ~160MB Rust binary through
# xAI's installer, which targets $HOME/.grok/bin with no --dir override. At build
# time that is root's home and unreachable by the `agent` user, so copy the binary
# into /usr/local/bin and drop root's ~/.grok in the same layer so the image does
# not carry the download twice. The staging cp -T is what makes this survive the
# installer's own behavior EITHER way: newer installers already symlink
# /usr/local/bin/grok -> /root/.grok/bin/grok, and a direct `cp -L` onto that
# symlink fails with "same file" (2026-08-24 rebuild), while removing the link
# first and copying fresh works for both old and new installers.
RUN curl -fsSL https://x.ai/cli/install.sh | bash \
# Grok Build (`grok`, xAI) has no npmPackage in the registry — it is NOT on npm, a
# standalone ~160MB Rust binary through xAI's own installer, which targets
# $HOME/.grok/bin with no --dir override. At build time that is root's home and
# unreachable by the `agent` user, so copy the binary into /usr/local/bin and drop
# root's ~/.grok in the same layer so the image does not carry the download twice.
# The staging cp -T is what makes this survive the installer's own behavior EITHER
# way: newer installers already symlink /usr/local/bin/grok -> /root/.grok/bin/grok,
# and a direct `cp -L` onto that symlink fails with "same file" (2026-08-24
# rebuild), while removing the link first and copying fresh works for both old and
# new installers.
ARG CLI_GROK_INSTALL_URL="https://x.ai/cli/install.sh"
RUN curl -fsSL "${CLI_GROK_INSTALL_URL}" | bash \
&& cp -L /root/.grok/bin/grok /usr/local/bin/grok.real \
&& rm -f /usr/local/bin/grok \
&& mv /usr/local/bin/grok.real /usr/local/bin/grok \
Expand Down
Loading