# acp-cli > Headless CLI client for the Agent Client Protocol (ACP). Rust port of ACPX. Talk to coding agents (Claude, Codex, Gemini, etc.) over structured JSON-RPC instead of terminal scraping. - Version 0.3.1 - GitHub: https://github.com/motosan-dev/acp-cli - crates.io: https://crates.io/crates/acp-cli ## Install ```bash cargo install acp-cli ``` ## Setup ```bash acp-cli init ``` Detects Claude Code, finds existing auth tokens (env var → config → ~/.claude.json → Keychain), writes `~/.acp-cli/config.json`. ## Quick Start ```bash # Prompt Claude acp-cli claude "fix the auth bug" --approve-all # One-shot (no session persistence) acp-cli claude exec "explain this function" # JSON output for automation acp-cli claude "list TODOs" --format json # Quiet (final text only) acp-cli claude "what is 2+2?" --format quiet # From file acp-cli claude -f prompt.md --approve-all # Stdin pipe echo "fix the bug" | acp-cli claude --approve-all ``` ## Supported Agents | Name | Command | Type | |------|---------|------| | claude | `npx @agentclientprotocol/claude-agent-acp@^0.24.2` | npm adapter | | codex | `npx @zed-industries/codex-acp@^0.10.0` | npm adapter | | gemini | `gemini --acp` | native | | copilot | `copilot --acp --stdio` | native | | cursor | `cursor-agent acp` | native | | goose | `goose acp` | native | | kiro | `kiro-cli-chat acp` | native | | pi | `npx pi-acp@^0.0.22` | npm adapter | | openclaw | `openclaw acp` | native | | opencode | `npx opencode-ai acp` | npm adapter | | kimi | `kimi acp` | native | | qwen | `qwen --acp` | native | | droid | `droid exec --output-format acp` | native | | kilocode | `npx @kilocode/cli acp` | npm adapter | | iflow | `iflow --experimental-acp` | native | | qoder | `qodercli --acp` | native | | trae | `traecli acp serve` | native | Unknown agent names are treated as raw commands. ## Commands ```bash acp-cli init # interactive setup acp-cli [agent] [prompt...] # persistent session prompt acp-cli [agent] exec [prompt...] # one-shot (no persistence) acp-cli [agent] sessions new [--name ] # create named session acp-cli [agent] sessions list # list sessions acp-cli [agent] sessions show # session details acp-cli [agent] sessions close # soft-close session acp-cli [agent] sessions history # conversation log acp-cli [agent] cancel # cancel running prompt acp-cli [agent] status # check session state acp-cli [agent] set-mode # change agent mode acp-cli [agent] set # change config option acp-cli config show # print config ``` ## Global Flags | Flag | Default | Description | |------|---------|-------------| | `-s, --session ` | — | Named session | | `--approve-all` | — | Auto-approve all tool calls | | `--approve-reads` | default | Approve read-only tools only | | `--deny-all` | — | Deny all tool calls | | `--cwd ` | `.` | Working directory | | `--format text\|json\|quiet` | `text` | Output format | | `--timeout ` | — | Max wait | | `-f, --file ` | — | Read prompt from file (`-` for stdin) | | `--no-wait` | — | Fire-and-forget (queue and return) | | `--agent-override ` | — | Raw ACP command | | `--verbose` | — | Debug to stderr | | `--prompt-retries ` | `0` | Retry transient failures with exponential backoff | | `--suppress-reads` | — | Hide file-read body content in output | ## Permission Modes | Mode | Behavior | |------|----------| | `--approve-all` | Select first allow option for every permission request | | `--approve-reads` | Approve: Read, Glob, Grep, WebSearch, WebFetch, LSP. Deny: Edit, Write, Bash, etc. | | `--deny-all` | Cancel all permission requests | ## Output Formats - **text** — streaming text to stdout, tool status spinner to stderr - **json** — NDJSON, one event per line: `{"type":"text","content":"..."}`, `{"type":"tool","name":"Read"}`, `{"type":"done"}` - **quiet** — final text only, no status ## Session Scoping Session key: `SHA-256(agent + "\0" + directory + "\0" + name)`. Directory resolved by walking from `--cwd` up to git root. Named sessions (`-s`) are independent — `None` matches only unnamed sessions. ## Queue System First `acp-cli` process for a session becomes the **queue owner**: - Holds the ACP agent connection - Listens on Unix socket (`~/.acp-cli/sessions/.sock`) - Executes prompts sequentially (FIFO) - Heartbeat every 5s, TTL 300s (configurable) Subsequent processes connect as **queue clients** via the socket. ## Config ### Global: `~/.acp-cli/config.json` ```json { "default_agent": "claude", "default_permissions": "approve_reads", "timeout": 60, "auth_token": "sk-ant-...", "agents": { "my-agent": { "command": "./custom", "args": ["--flag"] } } } ``` ### Project: `.acp-cli.json` (in git root) Same format. Merge order: global → project → CLI flags. ### Auth Token Resolution Token for Claude agent resolved in order: 1. `ANTHROPIC_AUTH_TOKEN` env var 2. `~/.acp-cli/config.json` → `auth_token` 3. `~/.claude.json` → `oauthAccount.accessToken` 4. macOS Keychain (`Claude Code` service) OAuth tokens (`sk-ant-oat01-*`) are detected but NOT injected via env var — the SDK resolves them from Keychain using the correct auth flow with `anthropic-beta: oauth-2025-04-20` header. ## Architecture Multi-threaded tokio runtime with `spawn_blocking` + `LocalSet` bridge for `!Send` ACP futures. ``` Main thread (Send) ACP thread (!Send, LocalSet) ├── CLI parsing ├── AcpConnection (spawn_local) ├── Output rendering ├── ClientSideConnection I/O ├── Permission resolution └── BridgedAcpClient callbacks ├── Signal handling └── Queue IPC server Channel bridge (mpsc + oneshot) ``` ## Exit Codes | Code | Meaning | |------|---------| | 0 | Success | | 1 | Agent/runtime error | | 2 | CLI usage error | | 3 | Timeout | | 4 | No session found | | 5 | Permission denied | | 130 | Interrupted (SIGINT) | ## Release ```bash # 1. Bump version in Cargo.toml # 2. Update CHANGELOG.md # 3. Commit git commit -m "chore: release v0.3.0" # 4. Tag + push (triggers publish.yml → crates.io) git tag -a v0.3.0 -m "v0.3.0 — summary" git push origin main v0.3.0 ```