CLI Coding Agents — adapter integration reference
Pre-crawled, fact-checked reference for adding headless CLI coding agents as Cyberine workers
(CliAgentAdapter spawns the official binary non-interactively, parses stdout into a Result).
Researched 2026-06-11 (fan-out of per-CLI research agents). Verify the exact flags against the
installed version before implementing — CLI surfaces drift.
Template factories to mirror live in src/adapters/CliAgentAdapter.ts:
makeOpenCodeAdapter—opencode run --format json(JSONL events: text + step_finish w/ tokens+cost)makeGeminiAdapter—gemini -p <prompt>+ JSON/stream-jsonmakeClaudeAdapter—claude -pstream-json (parseClaudeStreamJsonscans fortype:"result")makeCodexHeadlessAdapter—codex execPLAIN TEXT (stdout for the answer; usage comes from thetokens usedsummary codex prints on STDERR —parseCodexHumanUsage, T694. Copy this factory for a plain-text CLI, but do not copy "plain text means no usage": check the CLI's human output for a token line first, and read its source rather than trusting a docstring.)
Working-dir pin: most of these have NO --dir flag and rely on the spawn cwd (the adapter
already sets cwd: task.cwd). Add a cliConfigDirs() case per agent for seatbelt/bubblewrap
allow-listing, else the sandbox crashes on the agent's config/SQLite dir (the opencode WAL lesson).
ToS note (carry the project rule): an official CLI binary the user logged into themselves is the SANCTIONED client — spawning it is fine. What is forbidden is reusing a CLI's OAuth client_id to hit the provider API ourselves (see oauth-subscription revert). API-key auth is always compliant.
Summary
| CLI | bin | headless invocation | output | usage? | auth | mirror | verdict |
|---|---|---|---|---|---|---|---|
| Aider | aider | --message <p> --yes-always --no-auto-commits --no-stream --no-pretty | plain text | no | API key (litellm; any provider, OpenAI-compat base) | codex-exec | ADD |
| Qwen Code | qwen | -p <p> --output-format stream-json --yolo -m <model> | stream-json | yes | API key (DASHSCOPE_API_KEY); OAuth free tier dead Apr-2026 | gemini | ADD (T48) |
| Crush | crush | run --cwd <dir> --model <prov/model> --quiet <p> | plain text | no | API key (crush.json / env; OpenAI-compat providers) | codex-exec | ADD |
| Cursor Agent | agent | -p --force --workspace <dir> --output-format stream-json <p> | stream-json | no | CURSOR_API_KEY or browser login (subscription) | claude | ADD (caveats) |
| Amp | amp | -x <p> --stream-json | stream-json | yes | AMP_API_KEY (pay-as-you-go) | claude | ADD |
| Goose | goose | run --text <p> --no-session --output-format stream-json --quiet --with-builtin developer | stream-json | yes | API key per provider (NOT the ACP/OAuth providers) | codex-exec argv + json parse | ADD |
| OpenHands | openhands | --headless --always-approve --json -t <p> | plain text / JSON envelope | no | model-agnostic; LLM configured at first-run (~/.openhands/agent_settings.json); CYBERINE_OPENHANDS_MODEL per-run override | codex-exec (plain text fallback) | ADD |
| MiniMax mmx (text) | mmx | text chat --message <p> --model <m> --stream [--api-key <key>] | plain text stream | no | MMX_TOKEN_KEY or mmx CLI login session | codex-exec | ADD |
Aider — aider (API-key, plain-text, mirror codex-exec)
- Install:
pip/uv/pipx install aider-chatorcurl -LsSf https://aider.chat/install.sh | sh. - Detect:
aider --version; configured = any ofOPENAI_API_KEY/ANTHROPIC_API_KEY/DEEPSEEK_API_KEY/OPENROUTER_API_KEY/GEMINI_API_KEYset, or~/.aider.conf.yml. - Headless argv:
aider --model <m> --message "<prompt>" --yes-always --no-auto-commits --no-stream --no-pretty --no-suggest-shell-commands --no-check-update [files...]--yes-alwaysMANDATORY (else stalls on "add file?/create repo?/apply?").--no-auto-commitsso DiffJudge captures the raw diff.--no-prettyfor clean stdout (no ANSI).--no-streamone-shot write.
- Cwd: no
--dirflag — uses spawn cwd (must be a git repo;--yes-alwaysauto-creates one). - Output: human prose only, NO JSON mode.
parse: { output: stdout.trim() }, usage always undefined. Real product is the file diff. - Model/auth:
--model <m>;--openai-api-base/OPENAI_API_BASEfor OpenAI-compat; litellm under the hood (any provider). API-key only — compliant. - cliConfigDirs:
~/.aider,~/.config/aider. - Mirror:
makeCodexHeadlessAdapter(plain text). Factory opts:model, optionaleditFormat, optionalfiles.
Qwen Code — qwen (API-key, stream-json, mirror gemini) [task T48]
- Install:
npm i -g @qwen-code/qwen-code@latest(Node 22+) / brew. gemini-cli fork. - Detect:
which qwen; configured =DASHSCOPE_API_KEY|BAILIAN_CODING_PLAN_API_KEY|OPENAI_API_KEY, or~/.qwen/settings.jsonsecurity.auth.selectedType!=qwen-oauth. - Headless argv:
qwen -p "<prompt>" --output-format stream-json --yolo -m <model>(default e.g.qwen3-coder-plus).--yoloMANDATORY (auto-approve). Set envQWEN_CODE_UNATTENDED_RETRY=1for 429/529 backoff. Exit 55 = budget cap hit (--max-wall-time/--max-tool-calls). - Cwd: spawn cwd (no flag). cliConfigDirs:
~/.qwen. - Output: stream-json — last
{type:"result"}event hasresult(text) +usage. Shape mirrors Claude Code;parseClaudeStreamJsonreusable. VERIFYusageinner field names (input_tokens/output_tokens?) against a live run; fall back to{output}no-usage if wrong. - Auth/ToS: DASHSCOPE_API_KEY (OpenAI-compat at
dashscope...compatible-mode/v1) — compliant. Qwen OAuth free tier discontinued 2026-04-15, so NO subscription-OAuth path remains. - Mirror:
makeGeminiAdapter.CYBERINE_QWEN_MODELenv override.
Crush — crush (API-key, plain-text, mirror codex-exec)
- Install:
brew install charmbracelet/tap/crush/npm i -g @charmland/crush/go install. - Detect:
crush --version;crush runexits non-zero "no providers configured" if unkeyed — check~/.local/share/crush/crush.jsonor a provider env var. - Headless argv:
crush run --cwd <task.cwd> --model <provider/model> --quiet "<prompt>".--cwd/-cIS a real flag (persistent). No--yoloonrun, butRunNonInteractiveauto-approves all tool perms BY DESIGN (more reliable than opencode). - Output: plain streaming text to stdout (no
--format jsonyet — issue #2412 open).parse: { output: stdout.trim() }, usage undefined. - Model/auth:
crush.jsonproviders incl."type":"openai-compat"(base_url +$ENVkey expansion) — broad provider coverage. API-key only — compliant. (crush login= Charm Hyper/Copilot OAuth, NOT needed.) - cliConfigDirs:
~/.local/share/crush,~/.config/crush. SQLite — addisOpenCodeTransientError-style retry. - Mirror:
makeCodexHeadlessAdapter.
Cursor Agent — agent (subscription, stream-json, mirror claude) [ToS caveats]
- Binary:
agent(primary;cursor-agent= back-compat alias). Install:curl https://cursor.com/install -fsS | bash. - Detect:
agent --version; auth =CURSOR_API_KEYenv, else browser-login session under~/.cursor/. - Headless argv:
agent -p --force --workspace <task.cwd> --output-format stream-json "<prompt>".--force/--yoloMANDATORY for actual file edits (without it, may only PROPOSE — exit still 0).--workspacepins repo root. - Output: stream-json;
{type:"result",subtype:"success",result:"..."}like Claude.parseClaudeStreamJsonadaptable. NO usage/cost fields (always undefined). - Model/auth: NO per-invocation
--modelflag — inherits the account's configured model (cannot fan out model-vs-model). CURSOR_API_KEY or subscription session. - ToS: Cursor is a paid SUBSCRIPTION; CLI draws the SAME credit pool as the IDE (no separate lane — heavy automation burns it fast, $200-500/mo reported). BUT spawning the official
agentbinary with the user's own creds is the sanctioned client; Cursor's docs+ToS (Jan-2026, "non-transferable" removed) explicitly bless CI/automation/orchestrator use. No OAuth-to-API reuse. Compliant, but DOCUMENT the credit-burn + no-model-select caveats. - cliConfigDirs:
~/.cursor. Mirror:makeClaudeAdapter. Historical-phang fixed ~Mar-2026; use 60-120s timeout.
Amp — amp (API-key pay-as-you-go, stream-json+usage, mirror claude)
- Install:
curl -fsSL https://ampcode.com/install.sh | bash(or@ampcode/clinpm;@sourcegraph/ampalias deprecating 2026-06-15). - Detect:
which amp; auth =AMP_API_KEYenv (oramp usageshows balance when authed). Noamp whoami. - Headless argv:
amp -x "<prompt>" --stream-json(-x/--execute; also auto-activates when stdout redirected). Optionally--settings-file <clean>to avoid a user's strict global permission rules. Tools auto-run by default (no bypass flag needed). - Output: stream-json;
{type:"result",subtype:"success",result,usage:{input_tokens,output_tokens,cache_*}}. Has token usage (no $ cost).parseClaudeStreamJson-style. - Model/auth:
AMP_API_KEY(Amp account, pay-as-you-go credits, proxies all providers, no markup) — compliant, NOT subscription-OAuth. NO public--modelflag → defaults to expensivesmartmode (Opus-4.7-class); undocumented--model rush=anthropic:...exists but fragile. DOCUMENT the default-cost caveat. - cliConfigDirs:
~/.config/amp,~/.local/share/amp,~/.cache/amp. Mirror:makeClaudeAdapter. ScrubAMP_API_KEYfrom env when absent.
Goose — goose (Block) (API-key, stream-json+usage, mirror codex-exec argv / opencode parse)
- Install:
brew install block-goose-cli(note: conflicts with a separategooseformula) or the official curl script. Binarygoose(no prefix). v1.37+. - Detect:
goose --version; configured via~/.config/goose/config.yaml(Linux) /~/Library/Application Support/Block/goose/(macOS);goose info --checkpings the active provider. - Headless argv:
goose run --text "<prompt>" --no-session --output-format stream-json --quiet [--provider <p> --model <m>] --with-builtin developer--no-sessionREQUIRED for automation (no session file, no cwd-drift check).--with-builtin developergives file-edit capability if the profile lacks it. NO bypass flag needed — set envGOOSE_MODE=auto(the default; auto-approves tools when stdin closed).GOOSE_MODE=approve/smart_approvewould immediately error headless, so FORCEGOOSE_MODE=autoin the child env to override any user config.
- Cwd: spawn cwd (no flag). cliConfigDirs:
~/.config/goose,~/Library/Application Support/Block/goose. No SQLite — no retry needed (flat-file sessions). - Output: stream-json JSONL —
{type:"message",message:{role,content[]}}events + final{type:"complete",total_tokens,input_tokens,output_tokens}; errors as{type:"error",error};notificationevents interleaved (filter bytype). Parse = concat text of last assistantmessage+ readcompletefor usage. Exit 0 != success (confirm non-empty assistant message, opencode caveat). - Model/auth:
--provider/--modelper-run (orGOOSE_PROVIDER/GOOSE_MODEL). 40+ providers incl. Ollama/Vertex/Bedrock/OpenRouter; OpenAI-compat viaOPENAI_HOST; LiteLLM first-class.CYBERINE_GOOSE_PROVIDER(default anthropic) +CYBERINE_GOOSE_MODEL. - ToS — IMPORTANT: API-key providers are compliant. BUT goose ships ACP providers (
claude_acp,codex_acp,copilot_acp,pi_acp) that use SUBSCRIPTION OAuth — the adapter MUST NOT select those provider names (forbidden by the Cyberine subscription-OAuth-to-API rule). Pin a metered-key provider only. - Mirror:
makeCodexHeadlessAdapterargv shape (no bypass flag) + an opencode-style JSON message-array parse.
OpenHands — openhands (All-Hands AI, model-agnostic, headless)
- Install:
pip install openhands-aiorbrew install openhands. Binaryopenhands. - Detect:
which openhands; configured =~/.openhands/agent_settings.jsonexists with a non-emptyllmblock (set by runningopenhandsonce interactively).CYBERINE_OPENHANDS_MODELis a per-run override injected via theLLM_MODELenv var. - Headless argv:
openhands --headless --always-approve --json -t "<prompt>".--headlessruns in CI/automation mode (no TUI; reads task via-t, writes result to stdout).--always-approveMANDATORY for non-interactive operation — without it the first action prompt stalls the subprocess until the timeout.--jsonrequests a JSON envelope on stdout; the adapter falls back to raw stdout text if the JSON shape drifts across versions.- Stdin is closed; cwd is set via spawn (no
--dirflag).
- Cwd: spawn cwd (no flag). cliConfigDirs:
~/.openhands. - Output: plain text by default; with
--json, a JSON envelope whose inner shape is not stable across versions. The adapter returnsstdout.trim()directly, falling back to stderr if stdout is empty. No usage data available. - Model/auth: Model-agnostic — the LLM (DeepSeek, Qwen, Ollama, vLLM, or any OpenAI-compatible endpoint) is configured at first-run and persisted to
~/.openhands/agent_settings.json. The adapter injectsLLM_MODEL/LLM_API_KEY/LLM_BASE_URLfrom env only when the caller explicitly sets them; the persisted config wins otherwise (the CLI requires--override-with-envsto prefer env, and the adapter does not pass that flag).CYBERINE_OPENHANDS_MODELprovides the per-run model override. No subscription-OAuth path — fully API-key compliant. - Worker profile env:
CYBERINE_OPENHANDS_MODEL(optional per-run model override, injected asLLM_MODEL). - Mirror:
makeOpenHandsAdapter(plain text + optional JSON fallback).
MiniMax mmx (text) — mmx (text chat, API-key or CLI login)
- Install: install via the MiniMax mmx CLI distribution; binary
mmx. Separate from the multimedia (mmx image,mmx speech,mmx video) commands — this worker usesmmx text chatonly. - Detect:
which mmx; configured =MMX_TOKEN_KEYset in env ORmmxCLI is on PATH and has an active login session (the CLI carries its own Token Plan session). Auth viammxlogin session is checked at call time; the adapter omits--api-keywhenMMX_TOKEN_KEYis absent and lets the CLI use its session. - Headless argv:
mmx text chat --message "<prompt>" --model <model> --stream [--api-key <key>].--streamemits the response as a plain-text stream to stdout.--api-keyis included only whenMMX_TOKEN_KEYis set; omitted otherwise so the CLI falls back to its own login session.
- Cwd: spawn cwd (no flag).
- Output: plain text stream; no JSON mode.
parse: { output: stdout.trim() }, usage always undefined. The real work product is the response text. - Model/auth:
CYBERINE_MMX_MODELselects the model (defaultMiniMax-Text-01). Auth viaMMX_TOKEN_KEY(Token Plan API key) or themmxCLI's own login session. API-key only — compliant. - Worker profile env:
CYBERINE_MMX_MODEL(model override),MMX_TOKEN_KEY(Token Plan key; optional when CLI session is active). - Mirror:
makeMmxTextAdapter(plain text, codex-exec parse style).
Instruction-file coverage per CLI worker (kx3gg, measured 2026-09-21)
discoverInstructionFiles is first-match-wins PER DIRECTORY with AGENTS.md ahead of CLAUDE.md, so a repo carrying both hands every consumer the same AGENTS.md. The gap is the CLAUDE.md-ONLY repo: a CLI that reads only AGENTS.md then runs with no repo instructions at all, because Cyberine injected instruction files into LoopAgentAdapter prompts only.
| Worker | Reads for itself | Evidence | Injected by the adapter |
|---|---|---|---|
claude (and the seats) | CLAUDE.md + AGENTS.md | it is Claude Code; --bare is the flag that would skip them and the worker never passes it, while --safe-mode isolates the GLOBAL ~/.claude only (claude-code-cli.md) | nothing — injecting would double what it already read |
codex | AGENTS.md only | static read of the real binary, codex-cli 0.154.0: 61 AGENTS.md strings, including its whole "AGENTS.md spec" instruction block, against 2 CLAUDE.md strings that sit in its external-agent-session import / connector-detection table, not its instruction load path. Zero-spend evidence, not a canary run | the project CLAUDE.md chain, when discovery yields one |
muse | AGENTS.md, falling back to CLAUDE.md only when no AGENTS.md exists | muse-cli.md's measured table | nothing — its fallback rule is the same first-match-wins rule discovery applies, so it has already read whatever discovery would hand over |
command-code | AGENTS.md only | canary runs 2026-09-19 (command-code.md): a CLAUDE.md-only repo answered NONE, an AGENTS.md-only repo answered its canary word | the project CLAUDE.md chain, when discovery yields one |
Injection is PROJECT tier only and never the GLOBAL tier: handing a CLI worker the operator's own ~/.claude/CLAUDE.md is precisely the inheritance --safe-mode (claude) and --no-skills (command-code) exist to prevent.
Workers not in this machine's fleet (gemini, cursor-agent, crush, amp, goose, openhands) are unmeasured and set no selfReadsInstructionFilenames, so they inject nothing — the pre-kx3gg behaviour.
Cloud-Native Providers (Bedrock, Vertex AI, GitHub Copilot)
These three workers use cloud-platform auth rather than a plain API key. None of them routes through the LiteLLM gateway; each has a dedicated adapter in src/adapters/.
Claude Code CLI (headless)
Default model: claude-sonnet-5-5 (CYBERINECLAUDEMODEL; was claude-sonnet-5 until 2026-10-01, kvhxs). Auth dir allow-listed by the sandbox: ~/.claude.