Deprecation note (2026-05-22): Google is sunsetting Gemini CLI on 2026-06-18 in favour of Antigravity CLI (
agy). cuekit ships anantigravityadapter in parallel for the migration window; this design stays as the reference for the still-shippinggeminiadapter until removal in a follow-up after 2026-06-18.
cuekit currently has tmux-pane adapters for claude-code, opencode, pi, and jcode. Adding a gemini adapter (Google Gemini CLI) lets parents delegate child work to a fourth coding agent runtime without changing the cuekit surface. We want all of cuekit's existing adapter UX:
- parents can attach to the live child pane with tmux;
- parents can steer interactive children with
steer_task; - parents can opt into a
batchrun mode for short single-shot prompts; - child output is captured in the normal cuekit transcript path and exit-code sentinel;
- terminal status inference works the same way as for the other pane adapters;
- permission-bypass behaves consistently with the existing
dangerously_skip_permissionssemantics.
Observed from local gemini --help (Gemini CLI):
gemini [options] [query..] # default subcommand: launches Gemini CLI
gemini mcp # MCP server management (out of scope)
gemini extensions / skills / hooks # extension management (out of scope)
gemini gemma # local Gemma routing (out of scope)
# global flags relevant to cuekit:
-m, --model <model> # model selection
-p, --prompt <text> # non-interactive (headless) mode
-y, --yolo # auto-approve all tools (permission bypass)
--approval-mode <mode> # default | auto_edit | yolo | plan
-o, --output-format <fmt> # text | json | stream-json
--acp # Agent Communication Protocol mode (out of scope for v0)
--skip-trust # trust the workspace for this session
--session-id <uuid> # start a new session with manual UUID
-r, --resume <id|"latest"> # resume a previous session
Key facts:
- The CLI defaults to interactive when invoked with a positional query;
-p/--promptswitches to a single-shot non-interactive run. -y/--approval-mode yolois the permission-bypass equivalent ofclaude --dangerously-skip-permissionsandopencode --dangerously-skip-permissions.--output-format jsonandstream-jsonexist for structured output, useful for futureresult.json/task_eventsprojection but not required for v0.
Implement createGeminiAdapter as a normal createPaneAdapter consumer, supporting both run modes from day one because the CLI exposes them first-class:
| Run mode | Launch shape |
|---|---|
interactive (default) |
gemini [-y] [-m '<model>'] '<rendered prompt>' (positional) |
batch |
gemini [-y] [-m '<model>'] -p '<rendered prompt>' |
-y is added by default (bypass enabled), matching shouldDangerouslySkipPermissions semantics shared with claude-code and opencode. Callers explicitly opt out with adapter_options.dangerously_skip_permissions: false. When opted out, the adapter omits -y and lets Gemini's default approval prompts drive — that path will stall an unattended pane, so the opt-out is intentionally only for trusted local sessions or when a human is ready to attach.
The launch command must still go through wrapLaunchCommandWithExitCode (shared by all pane adapters) so terminal status inference and exit-code sentinel handling work the same way as claude-code.
{
agent_kind: "gemini",
supports_steering: true,
supports_attach: true,
supports_model_selection: true,
supports_artifacts: true,
supports_live_progress: false,
default_mode: "interactive",
supported_modes: ["interactive", "batch"],
}Per the adapter run-modes design, the task status view for a batch task must report supports_steering: false and steer_task must reject batch tasks with steering_unsupported. This is already handled by the shared pane adapter when supported_modes is wired correctly, so no Gemini-specific code is needed for that branch.
A pure builder lives in packages/adapters/src/gemini-adapter.ts:
export function buildGeminiLaunchCommand(
spec: TaskSpec,
options: { geminiBin?: string } = {},
): string {
const bin = options.geminiBin ?? "gemini";
const parts: string[] = [bin, "--skip-trust"];
if (shouldDangerouslySkipPermissions(spec)) {
parts.push("-y");
}
if (spec.model) {
parts.push("-m", shellQuote(spec.model));
}
const prompt = shellQuote(renderTaskSpecPrompt(spec));
if (adapterRunModeFor(spec) === "batch") {
parts.push("-p", prompt);
} else {
parts.push(prompt);
}
return parts.join(" ");
}Implementation must ensure:
- the rendered prompt is shell-quoted (a prompt starting with
-must not be parsed as an option — the-pform passes the prompt as the value, and the positional form is unambiguous because it follows other flags); - model values are shell-quoted;
- the command runs under the shared
wrapLaunchCommandWithExitCode; --skip-trustis added unconditionally so unattended panes never stall on the trusted-folder prompt (see "Trusted-folder handling" below);-yis added by default, removable only via explicitdangerously_skip_permissions: false;- batch tasks are advertised with
supports_steering: falsevia the shared run-mode capability projection; - nothing in the adapter assumes Gemini-specific session paths in
~/until verified during implementation (unlikejcode, which had a known~/.jcode/active_pids/cleanup requirement).
Gemini CLI gates execution on a per-directory trust check. In an untrusted directory it prints:
Gemini CLI is not running in a trusted directory. To proceed, either use
--skip-trust, set theGEMINI_CLI_TRUST_WORKSPACE=trueenvironment variable, or trust this directory in interactive mode.
This affects both interactive and batch modes — unlike Claude Code, Gemini does not auto-skip trust when stdout is non-TTY. Without an opt-out, an unattended cuekit pane will hang on the gate.
The adapter therefore always passes --skip-trust. This mirrors the rationale for -y being on by default (see adapter permission bypass design): cuekit's invariant is that delegated panes must make progress without prompts, so all sources of stalling are removed at submit time. There is no dangerously_skip_permissions: false style opt-out for the trust flag — if a caller wants the trust check to apply, they should not be using cuekit's unattended pane delegation in the first place.
GEMINI_CLI_TRUST_WORKSPACE=true via env is an equivalent path but is rejected in favor of the explicit flag because (a) flag presence is visible in the captured transcript and tmux inspection, (b) env var injection is slightly more invasive on the pane shell. Implementation may revisit this if a future Gemini CLI version drops the flag.
Claude Code has no --skip-trust flag (verified against claude --help flag set). Its trust dialog is auto-skipped only when running in non-interactive -p mode or with non-TTY stdout. In cuekit's interactive pane the dialog would appear in untrusted directories, but in practice users have already trusted the directory through their host-side claude usage and the trust state in ~/.claude/ is shared with the cuekit-spawned child.
The claude-code adapter is therefore left unchanged in this design. If a real cuekit user hits the dialog in an unfamiliar directory, the right fix is documentation in cuekit doctor ("trust this directory once with normal claude first"), not adapter-level trust bypass — there is no flag to add, and writing to ~/.claude/ from cuekit would silently expand the trust scope behind the user's back.
Interactive mode runs Gemini's normal REPL on a TTY in the tmux pane, so steer_task works the same way as for claude-code and opencode: cuekit calls tmux send-keys to deliver the message and a literal C-m newline.
Implementation must verify in a real-runtime smoke test that:
tmux send-keysreliably appends a message to a running interactive Gemini REPL prompt;- Gemini's prompt does not eat or echo the trailing newline in a way that breaks subsequent steering;
- transitioning between Gemini's busy state and prompt-ready state does not require a special keystroke (e.g.
Esc) before send-keys.
If (3) turns out to be necessary, follow the same pattern opencode uses (a small pre-key sequence before the message), not a Gemini-specific bespoke path.
Gemini CLI typically requires either:
- an interactive
gemini auth/ browser-based login for the free tier, or - a
GEMINI_API_KEY(or equivalent env var) for headless / batch runs.
This is out of adapter scope — the adapter assumes the binary is already authenticated. cuekit doctor should:
- detect the
geminibinary onPATH; - run a cheap auth probe (e.g.
gemini --versionor a documented status command) and surface a clear hint when auth is missing; - not attempt to write or refresh credentials itself.
The auth probe is added in the same PR as the adapter so cuekit doctor output is useful out of the box, but the adapter does not block submission on missing auth — Gemini's own error messages surface in the captured transcript.
Expected files:
- Create
packages/adapters/src/gemini-adapter.ts. - Export
createGeminiAdapterandbuildGeminiLaunchCommandfrompackages/adapters/src/index.ts. - Register the adapter in MCP wiring alongside
claude-code,opencode,pi, andjcode. - Add
geminito theagent_kindunion in@cuekit/core(Zod schema + types). - Add
packages/adapters/__tests__/gemini-adapter.test.tsfor build/launch and capability shape. - Update MCP / stdio integration tests that assert adapter list length or exact agent kinds.
- Update the
geminirow in the adapter run-modes design command matrix. - Update README and AGENTS.md adapter lists.
- Add a
cuekit doctorGemini probe.
Adapter-level tests:
createGeminiAdapter(...).capabilities()returnsagent_kind: "gemini",supports_attach: true,supports_steering: true,supports_model_selection: true,default_mode: "interactive",supported_modes: ["interactive", "batch"].buildGeminiLaunchCommand({ agent_kind: "gemini", objective: "x" })defaults togemini --skip-trust -y '<prompt>'.--skip-trustis always present, including whendangerously_skip_permissions: falseis set (the trust flag is not gated by the permissions option).- Adding
model: "gemini-2.5-flash"producesgemini --skip-trust -y -m 'gemini-2.5-flash' '<prompt>'. - Adding
adapter_options.mode: "batch"producesgemini --skip-trust -y -m '<model>' -p '<prompt>'. - Adding
adapter_options.dangerously_skip_permissions: falseomits-ybut keeps--skip-trust. - Prompts starting with
-and prompts containing single quotes are correctly shell-quoted. - Submit → status returns a running view with tmux
attach_hintunder the fake pane backend. steer()sends keys to the tmux pane when the task is interactive and running; rejects withsteering_unsupportedwhenmetadata.adapter_mode: "batch".
MCP-level tests:
list({ kind: "adapters" })includesgeminiand reports the new run-mode capability fields.submit_taskacceptsagent_kind: "gemini"in the fake-backed test harness.- Stdio integration adapter count / expected agent kinds are updated.
Validation:
bun test packages/adapters
bun test packages/mcp
bun run typecheck
bun run check
bun testA real-runtime smoke test (similar to the one for jcode repl in docs/guides/jcode-adapter.md) must cover:
- submit a task with
--agent_kind gemini --model <known model>; tmux attach-session -t cuekit-task-<id>shows the live REPL;cuekit task steer ...reaches the running child;- transcript appears at
<cwd>/.cuekit/tasks/<task_id>/transcript.txt; cancel_taskscleans up the tmux session;- batch mode (
adapter_options: '{"mode":"batch"}') returnssupports_steering: falseintask status, andsteer_taskrejects withsteering_unsupported.
Add the smoke recipe to docs/guides/gemini-adapter.md once the adapter ships.
-y(yolo) approves every tool call, including potentially destructive ones. The default-on choice mirrorsclaude-code's andopencode's existing v0 behavior, where unattended panes need to make progress without prompts. Project config (.cuekit.yaml) keeps the right to forcedangerously_skip_permissions: falsefor prompt-safe defaults — this still applies to the Gemini adapter unchanged.--approval-mode plan(read-only) and--approval-mode auto_editare now exposed viaadapter_options.approval_mode(4 valid values:default/auto_edit/yolo/plan). When set to a valid value, it takes precedence overdangerously_skip_permissionsand the launch command emits--approval-mode <value>directly, dropping-y. Invalid values fall back to the binary path.--skip-truststays unconditional regardless. The reviewer use case (role: reviewer + approval_mode: "plan") is documented in the gemini guide. Profile-level integration that auto-setsapproval_mode: "plan"forrole: reviewer + agent_kind: geminiis intentionally not done yet; the explicit per-task opt-in is enough for v0.
- Advertised models: should
availableModelsdefault to a curated list (gemini-2.5-pro,gemini-2.5-flash, ...) the wayclaude-codedoes (haiku/sonnet/opus)? Pick the names the CLI itself accepts; do not invent aliases. Confirm during implementation by listing the CLI's supported model strings. - Structured
result.json: should the batch path also writegemini -p --output-format jsonstdout into<worktree>/.cuekit/tasks/<task_id>/result.jsonfor cleaner result normalization? Defer to a follow-up because the same improvement applies symmetrically to other adapters and should not be built one-off here. --acpmode: Gemini CLI supports the Agent Communication Protocol natively. This is conceptually closer to cuekit than tmux pane I/O, but v0 keeps every adapter on the shared pane backend. Reconsider only if--acpbecomes a clearly better steering channel thantmux send-keysfor at least one shipped runtime.--session-id/--resume: useful for cross-task continuity (resume a parent's prior child session). Not needed for v0 — defer until a feature explicitly asks for it.- Doctor auth probe shape: which command does
cuekit doctorinvoke to detect missing auth without side effects? Pick during implementation; if no clean probe exists, settle for binary detection plus a documentation hint.