cyberine
Providers

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-json
  • makeClaudeAdapter — claude -p stream-json (parseClaudeStreamJson scans for type:"result")
  • makeCodexHeadlessAdapter — codex exec PLAIN TEXT (stdout for the answer; usage comes from the tokens used summary 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

CLIbinheadless invocationoutputusage?authmirrorverdict
Aideraider--message <p> --yes-always --no-auto-commits --no-stream --no-prettyplain textnoAPI key (litellm; any provider, OpenAI-compat base)codex-execADD
Qwen Codeqwen-p <p> --output-format stream-json --yolo -m <model>stream-jsonyesAPI key (DASHSCOPE_API_KEY); OAuth free tier dead Apr-2026geminiADD (T48)
Crushcrushrun --cwd <dir> --model <prov/model> --quiet <p>plain textnoAPI key (crush.json / env; OpenAI-compat providers)codex-execADD
Cursor Agentagent-p --force --workspace <dir> --output-format stream-json <p>stream-jsonnoCURSOR_API_KEY or browser login (subscription)claudeADD (caveats)
Ampamp-x <p> --stream-jsonstream-jsonyesAMP_API_KEY (pay-as-you-go)claudeADD
Goosegooserun --text <p> --no-session --output-format stream-json --quiet --with-builtin developerstream-jsonyesAPI key per provider (NOT the ACP/OAuth providers)codex-exec argv + json parseADD
OpenHandsopenhands--headless --always-approve --json -t <p>plain text / JSON envelopenomodel-agnostic; LLM configured at first-run (~/.openhands/agent_settings.json); CYBERINE_OPENHANDS_MODEL per-run overridecodex-exec (plain text fallback)ADD
MiniMax mmx (text)mmxtext chat --message <p> --model <m> --stream [--api-key <key>]plain text streamnoMMX_TOKEN_KEY or mmx CLI login sessioncodex-execADD

Aider — aider (API-key, plain-text, mirror codex-exec)

  • Install: pip/uv/pipx install aider-chat or curl -LsSf https://aider.chat/install.sh | sh.
  • Detect: aider --version; configured = any of OPENAI_API_KEY/ANTHROPIC_API_KEY/DEEPSEEK_API_KEY/OPENROUTER_API_KEY/GEMINI_API_KEY set, 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-always MANDATORY (else stalls on "add file?/create repo?/apply?"). --no-auto-commits so DiffJudge captures the raw diff. --no-pretty for clean stdout (no ANSI). --no-stream one-shot write.
  • Cwd: no --dir flag — uses spawn cwd (must be a git repo; --yes-always auto-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_BASE for OpenAI-compat; litellm under the hood (any provider). API-key only — compliant.
  • cliConfigDirs: ~/.aider, ~/.config/aider.
  • Mirror: makeCodexHeadlessAdapter (plain text). Factory opts: model, optional editFormat, optional files.

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.json security.auth.selectedType != qwen-oauth.
  • Headless argv: qwen -p "<prompt>" --output-format stream-json --yolo -m <model> (default e.g. qwen3-coder-plus). --yolo MANDATORY (auto-approve). Set env QWEN_CODE_UNATTENDED_RETRY=1 for 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 has result (text) + usage. Shape mirrors Claude Code; parseClaudeStreamJson reusable. VERIFY usage inner 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_MODEL env 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 run exits non-zero "no providers configured" if unkeyed — check ~/.local/share/crush/crush.json or a provider env var.
  • Headless argv: crush run --cwd <task.cwd> --model <provider/model> --quiet "<prompt>". --cwd/-c IS a real flag (persistent). No --yolo on run, but RunNonInteractive auto-approves all tool perms BY DESIGN (more reliable than opencode).
  • Output: plain streaming text to stdout (no --format json yet — issue #2412 open). parse: { output: stdout.trim() }, usage undefined.
  • Model/auth: crush.json providers incl. "type":"openai-compat" (base_url + $ENV key expansion) — broad provider coverage. API-key only — compliant. (crush login = Charm Hyper/Copilot OAuth, NOT needed.)
  • cliConfigDirs: ~/.local/share/crush, ~/.config/crush. SQLite — add isOpenCodeTransientError-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_KEY env, else browser-login session under ~/.cursor/.
  • Headless argv: agent -p --force --workspace <task.cwd> --output-format stream-json "<prompt>". --force/--yolo MANDATORY for actual file edits (without it, may only PROPOSE — exit still 0). --workspace pins repo root.
  • Output: stream-json; {type:"result",subtype:"success",result:"..."} like Claude. parseClaudeStreamJson adaptable. NO usage/cost fields (always undefined).
  • Model/auth: NO per-invocation --model flag — 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 agent binary 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 -p hang 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/cli npm; @sourcegraph/amp alias deprecating 2026-06-15).
  • Detect: which amp; auth = AMP_API_KEY env (or amp usage shows balance when authed). No amp 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 --model flag → defaults to expensive smart mode (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. Scrub AMP_API_KEY from 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 separate goose formula) or the official curl script. Binary goose (no prefix). v1.37+.
  • Detect: goose --version; configured via ~/.config/goose/config.yaml (Linux) / ~/Library/Application Support/Block/goose/ (macOS); goose info --check pings the active provider.
  • Headless argv: goose run --text "<prompt>" --no-session --output-format stream-json --quiet [--provider <p> --model <m>] --with-builtin developer
    • --no-session REQUIRED for automation (no session file, no cwd-drift check). --with-builtin developer gives file-edit capability if the profile lacks it. NO bypass flag needed — set env GOOSE_MODE=auto (the default; auto-approves tools when stdin closed). GOOSE_MODE=approve/smart_approve would immediately error headless, so FORCE GOOSE_MODE=auto in 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}; notification events interleaved (filter by type). Parse = concat text of last assistant message + read complete for usage. Exit 0 != success (confirm non-empty assistant message, opencode caveat).
  • Model/auth: --provider/--model per-run (or GOOSE_PROVIDER/GOOSE_MODEL). 40+ providers incl. Ollama/Vertex/Bedrock/OpenRouter; OpenAI-compat via OPENAI_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: makeCodexHeadlessAdapter argv shape (no bypass flag) + an opencode-style JSON message-array parse.

OpenHands — openhands (All-Hands AI, model-agnostic, headless)

  • Install: pip install openhands-ai or brew install openhands. Binary openhands.
  • Detect: which openhands; configured = ~/.openhands/agent_settings.json exists with a non-empty llm block (set by running openhands once interactively). CYBERINE_OPENHANDS_MODEL is a per-run override injected via the LLM_MODEL env var.
  • Headless argv: openhands --headless --always-approve --json -t "<prompt>".
    • --headless runs in CI/automation mode (no TUI; reads task via -t, writes result to stdout).
    • --always-approve MANDATORY for non-interactive operation — without it the first action prompt stalls the subprocess until the timeout.
    • --json requests 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 --dir flag).
  • 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 returns stdout.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 injects LLM_MODEL / LLM_API_KEY / LLM_BASE_URL from env only when the caller explicitly sets them; the persisted config wins otherwise (the CLI requires --override-with-envs to prefer env, and the adapter does not pass that flag). CYBERINE_OPENHANDS_MODEL provides 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 as LLM_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 uses mmx text chat only.
  • Detect: which mmx; configured = MMX_TOKEN_KEY set in env OR mmx CLI is on PATH and has an active login session (the CLI carries its own Token Plan session). Auth via mmx login session is checked at call time; the adapter omits --api-key when MMX_TOKEN_KEY is absent and lets the CLI use its session.
  • Headless argv: mmx text chat --message "<prompt>" --model <model> --stream [--api-key <key>].
    • --stream emits the response as a plain-text stream to stdout.
    • --api-key is included only when MMX_TOKEN_KEY is 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_MODEL selects the model (default MiniMax-Text-01). Auth via MMX_TOKEN_KEY (Token Plan API key) or the mmx CLI'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.

WorkerReads for itselfEvidenceInjected by the adapter
claude (and the seats)CLAUDE.md + AGENTS.mdit 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
codexAGENTS.md onlystatic 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 runthe project CLAUDE.md chain, when discovery yields one
museAGENTS.md, falling back to CLAUDE.md only when no AGENTS.md existsmuse-cli.md's measured tablenothing — its fallback rule is the same first-match-wins rule discovery applies, so it has already read whatever discovery would hand over
command-codeAGENTS.md onlycanary runs 2026-09-19 (command-code.md): a CLAUDE.md-only repo answered NONE, an AGENTS.md-only repo answered its canary wordthe 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.