acp-cli 0.2.2

Headless CLI client for the Agent Client Protocol (ACP)
Documentation
# 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.2.2
- 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 @zed-industries/claude-agent-acp` | npm adapter |
| codex | `npx @zed-industries/codex-acp` | npm adapter |
| gemini | `gemini --acp` | native |
| copilot | `copilot --acp --stdio` | native |
| cursor | `cursor-agent acp` | native |
| goose | `goose acp` | native |
| kiro | `kiro-cli acp` | native |
| pi | `npx pi-acp` | 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 |

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 <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 <mode>                 # change agent mode
acp-cli [agent] set <key> <value>               # change config option
acp-cli config show                             # print config
```

## Global Flags

| Flag | Default | Description |
|------|---------|-------------|
| `-s, --session <name>` | — | Named session |
| `--approve-all` | — | Auto-approve all tool calls |
| `--approve-reads` | default | Approve read-only tools only |
| `--deny-all` | — | Deny all tool calls |
| `--cwd <dir>` | `.` | Working directory |
| `--format text\|json\|quiet` | `text` | Output format |
| `--timeout <seconds>` | — | Max wait |
| `-f, --file <path>` | — | Read prompt from file (`-` for stdin) |
| `--no-wait` | — | Fire-and-forget (queue and return) |
| `--agent-override <cmd>` | — | Raw ACP command |
| `--verbose` | — | Debug to stderr |

## 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/<key>.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.2.2"
# 4. Tag + push (triggers publish.yml → crates.io)
git tag -a v0.2.2 -m "v0.2.2 — summary"
git push origin main v0.2.2
```