Files
Codeman/docs/grok-integration-plan.md
Codeman maintainer 3f8c8e99d1 feat(grok): add Grok Build (xAI) as a seventh CLI run mode
SessionMode gains 'grok', a first-class backend alongside Claude Code,
shell, OpenCode, Codex, Gemini, Antigravity and Pi: its own PTY, tmux
session, charcoal tab identity ('gk' badge), welcome button, run-mode
entry, cron agentType, Docker and remote-SSH command defaults, and
clone-repo Brain option. Flag surface verified live against grok 1.0.5.

Grok mixes two existing shapes and the wiring follows from that:

- Codex-shaped on permissions: the bypass switch is GrokConfig.alwaysApprove
  (--always-approve, grok's bypassPermissions mode; config-level deny rules
  still apply on top). The Run button sends it true, like runAntigravity(),
  and clampExternalCliBypassForOwner() puts grok in the only-if-sent branch:
  a bare grok spawn is grok's own ask-mode default, which is already safe,
  so only a sent config needs the flag forced off. Cron needs nothing for
  the same reason.
- OpenCode-shaped on rendering: grok is a fullscreen alternate-screen TUI
  with mouse support, so it stays OUT of isAltScreenStripMode() and lands
  on the narrow tmux-attach strip and the 'buffer' local-echo fallthrough
  (unmeasured against an authenticated composer; documented fallback is the
  'off' branch).
- Pi-shaped on resolution: 'grok' has npm squatters (@vibe-kit/grok-cli
  also installs a grok bin), so grok-cli-resolver.ts version-probes every
  candidate (grok --version, killSignal SIGKILL, VITEST-gated) and
  GET /api/grok/status surfaces path AND version; GROK_VERSION_REGEX is
  shared with the dependency registry so doctor and run mode cannot drift.

Env allowlist gains GROK_* plus the XAI_* vendor namespace (XAI_API_KEY is
grok's documented headless auth var), the same narrow-vendor reasoning as
GOOGLE_* for gemini. Resume is id-regexed on purpose: grok's own --resume
also matches session titles, which are arbitrary user strings that must
never reach the bash -c spawn line.

Docker: grok is not on npm, so the agent image installs it in its own step
(xAI's installer has no --dir override; the binary is copied to
/usr/local/bin and root's ~/.grok dropped in the same layer), and
credentials are seeded per-file (auth.json, config.toml, pager.toml; the
dir also holds sessions/, memory/ and the ~160MB binary). Remote SSH routes
through the login-shell wrapper like the other agent CLIs.

Verified end to end on an isolated CODEMAN_INSTANCE with grok 1.0.5
installed: /api/grok/status resolves and reports the probed version,
quick-start spawns a pane whose command line ends in 'grok
--always-approve', the real TUI renders (OAuth device screen on an
unauthenticated box), and grokConfig round-trips through state.json.
Docs: docs/grok-integration.md (user guide) + docs/grok-integration-plan.md
(decisions, verification record, follow-ups).

Tests: test/grok-mode.test.ts, test/grok-cli-resolver.test.ts, plus
extended clamp/system-routes/render-index-html/run-mode-ui/mobile-overview/
local-echo-gating coverage. npm test (the CI gate) green: 5910 tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-23 08:39:03 +02:00

9.6 KiB

Grok Build (xAI) integration plan

Status: Executed. This document records the plan, the decision behind each wiring point, and what was and was not verified. The user-facing guide is grok-integration.md; the per-decision invariants live in architecture-invariants.md#external-cli-modes-opencode-codex-gemini-antigravity-pi-grok. Template: the pi integration (c5b5963, pi-integration-plan.md), which was itself calibrated against the four follow-up commits the antigravity integration needed. All of grok's facts below were verified against grok 1.0.5 (grok 1.0.5 (5115b46bc9)), installed live during the work.

1. What Grok Build is

xai-org/grok-build is xAI's coding agent: a Rust fullscreen-TUI binary named grok, installed by curl -fsSL https://x.ai/cli/install.sh | bash into ~/.grok/bin (with symlinks into ~/.local/bin; the installer also ships an agent alias). Config lives in ~/.grok/config.toml, TUI appearance in ~/.grok/pager.toml, credentials in ~/.grok/auth.json (0600), sessions under ~/.grok/sessions/. Auth is browser OAuth on first launch, grok login --device-auth for SSH boxes, or XAI_API_KEY for headless use. It has Claude-style permission modes (default/acceptEdits/auto/ dontAsk/bypassPermissions/plan), allow/deny rules, hooks, MCP, subagents, and a headless -p mode.

2. Shape decisions (why grok is wired the way it is)

Grok is a seventh run mode, alongside Claude Code, shell, OpenCode, Codex, Gemini, Antigravity and Pi. Never a location overlay, never a web tab. Its wiring mixes two existing shapes:

Question Decision Why
Permission bypass GrokConfig.alwaysApprove -> --always-approve Grok's real flag (verified via --help): "Auto-approve all tool executions", i.e. its bypassPermissions mode. Config-level deny rules still apply on top. The Run button sends true, matching runAntigravity() and Claude's own --dangerously-skip-permissions default: Codeman sessions exist for autonomous work.
Multi-user clamp branch only-if-sent (codex/antigravity branch) A bare grok spawn is grok's own ask-mode default, which is already safe, so the clamp only needs to force a SENT alwaysApprove off. Contrast pi, whose absent default is an answerable prompt and therefore needs the materialize branch. Cron needs nothing for grok for the same reason (clampCronExternalCliConfigs).
Alt-screen strip OUT of isAltScreenStripMode() Grok is a fullscreen alternate-screen TUI with mouse support (its own scrollback pane, pager.toml [terminal] alt_screen), i.e. the opencode case, not the Ink repaint case. It falls through to the narrow tmux-attach strip like opencode/antigravity/pi.
Resolver version probe, like pi grok has npm squatters (the unrelated @vibe-kit/grok-cli installs a grok bin). Candidates must pass grok --version; GROK_VERSION_REGEX is exported and shared with the dependency registry so doctor and run mode cannot disagree. The probe cannot tell two version-printing groks apart, so GET /api/grok/status surfaces path AND version. Search dirs: ~/.grok/bin first (installer target), then ~/.local/bin, /usr/local/bin, ~/bin.
Env allowlist GROK_* + XAI_* prefixes GROK_* covers grok's documented inputs (GROK_HOME, GROK_CONFIG/GROK_CONFIG_PATH, GROK_MEMORY, GROK_WORKFLOWS, GROK_SANDBOX, GROK_OIDC_*, GROK_AUTH_PROVIDER_COMMAND). XAI_* is xAI's vendor namespace and carries XAI_API_KEY, grok's documented headless auth var: the same narrow-vendor-namespace reasoning that admitted GOOGLE_* for gemini. Foreign provider keys stay out, as always.
Resume --resume <id> / --continue, id-regexed Grok's --resume also matches session TITLES (arbitrary user strings, case-insensitive). The ^[a-zA-Z0-9._-]+$ regex doubles as the no-titles rule, so nothing free-form can reach the bash -c spawn line. A valid explicit id wins over -c, mirroring pi.
Local echo 'buffer' via the _updateLocalEchoState fallthrough UNMEASURED against an authenticated session (see §4). If grok's composer turns out per-keystroke reactive like codex's, the fallback is one 'off' branch; teaching PredictiveEchoAddon grok's composer row is the larger follow-up.
Truecolor COLORTERM=truecolor + unset NO_COLOR Rust TUI with themes; joins the codex/gemini/antigravity/pi list in buildEnvExports() and buildMuxAttachEnv().
Docker credentials per-file seed: auth.json, config.toml, pager.toml ~/.grok also holds sessions/, memory/, completions/, docs/ and the ~160MB binary under downloads/; a whole-dir seed would copy all of it on every container start. Same trade-off as pi: in-container sessions are invisible host-side, so grok -c in a Docker case sees only that container's history.
Docker install own Dockerfile step Not an npm package. xAI's installer has no --dir override, so the step copies /root/.grok/bin/grok (through the symlink, cp -L) into /usr/local/bin and removes root's ~/.grok in the same layer.
Remote SSH exec "$SHELL" -i -l -c 'grok' sshd's remote-command PATH does not include ~/.grok/bin; same login-shell fix as every other agent CLI.
What is NOT wired --permission-mode, --allow/--deny, -p headless, --worktree, --sandbox, --reasoning-effort, -s/--session-id, --fork-session, --agent, --output-format Follow-ups. The flag surface is kept minimal on purpose; grok is pre-1.0-style fast-moving and every flag added is a flag validated forever.

3. Touch points (the checklist)

Backend: types/session.ts (SessionMode + GrokConfig + SessionState), utils/grok-cli-resolver.ts (new)

  • barrel, tmux-manager.ts (buildGrokCommand, dispatch, resume flag, PATH export, truecolor, availability error, plumbing), session.ts (external-mode gate, label, config plumbing, tmux-required error, attach env), mux-interface.ts, schemas.ts (prefixes, GrokConfigSchema, both mode enums, remote command overrides, cron agentType), session-routes.ts (clamp + both create paths), system-routes.ts (GET /api/grok/status), server.ts (availability inject + mux restore), docker-hosts.ts, remote-hosts.ts, config/dependency-registry.ts, cron/cron-service.ts (comment), response-viewer-transcript.ts, tui/tui-client.ts + tui-app.ts.

Frontend: index.html (welcome button, run-mode entry, cron option, clone Brain option), session-ui.js (runGrok(), dispatch, availability, "Run GK" label, external-CLI gates, runMode setter), app.js (label, gk tab badge, kill-menu), settings-ui.js, mobile-overview.js, home-sessions.js, panels-ui.js, i18n.js, styles.css + mobile.css (charcoal monochrome identity; the non-og skin block and the mobile !important pair are both load-bearing, see the pi plan's §2.9 cascade trap).

Meta: docker/agent.Dockerfile, install.sh, package.json keyword, changeset, skills/codeman/reference/*, CLAUDE.md, READMEs, architecture-invariants.md, remote-sessions.md, security-architecture.md, docker-cases.md, cron-guide.md.

Tests: test/grok-mode.test.ts + test/grok-cli-resolver.test.ts (new); external-cli-bypass-clamp, system-routes, render-index-html, run-mode-ui, mobile-overview, local-echo-codex-gating (extended).

4. Verification performed

On this box, with grok 1.0.5 really installed and an isolated CODEMAN_INSTANCE=grokwt server (own data dir, own tmux socket, port 5077):

  1. npm test (the CI gate): green, 5900+ tests. typecheck, lint, format:check, check:frontend-syntax, check:public-assets, check:lockfile: green.
  2. GET /api/grok/status -> {available: true, path: "/home/arkon/.local/bin", version: "1.0.5"} through the real resolver and probe.
  3. POST /api/quick-start {mode: "grok", grokConfig: {alwaysApprove: true}} -> session created, tmux pane spawned, real spawn line verified to end in grok --always-approve, and the actual grok TUI rendered its OAuth device-approval screen in the pane (unauthenticated box, so sign-in is exactly where a first run lands).
  4. grokConfig persisted into the instance's state.json.
  5. Session deleted by exact id; instance data dir and throwaway case removed.

Not verified (honest gaps, all requiring an xAI account or more hardware): an authenticated conversation end to end; the local-echo buffer policy against grok's real composer (§2); scrollback/repaint behavior of the fullscreen TUI under the narrow strip during a long session; a Docker case with mode: 'grok' (needs a --no-cache agent-image rebuild); a remote-SSH grok case; cron readiness degradation (expected: same slow-start-then-send as pi, documented in cron-guide.md).

5. Follow-ups

  • Idle/completion signal: grok has a hooks system (user-guide 10-hooks.md); a hook POSTing to /api/hook-event could give grok sessions real idle detection instead of output-stabilization. Highest-value follow-up, same slot as pi's agent_settled idea.
  • Response viewer: sessions are ACP JSONL under ~/.grok/sessions/<encoded-cwd>/<id>/updates.jsonl; grok -p ... --output-format json | jq -r '.sessionId' exists for correlation.
  • Permission-mode picker (--permission-mode, --allow/--deny) in Session Options.
  • Measure the local-echo policy and the fullscreen-TUI scrollback behavior against an authenticated session; pin the result in local-echo-codex-gating the way pi did.
  • grok doctor is a built-in terminal-support check worth pointing users at when a pane renders oddly.