diff --git a/docs/byocli.md b/docs/byocli.md new file mode 100644 index 0000000..b5626a6 --- /dev/null +++ b/docs/byocli.md @@ -0,0 +1,360 @@ +# Bring Your Own CLI (BYOCLI) + +openab works with any CLI that speaks ACP (Agent Client Protocol) over stdio. This guide explains how to build, configure, and test your own ACP-compatible CLI. + +## How it works + +``` +Discord user ──▶ openab ──stdin/stdout──▶ Your CLI ──▶ Any AI backend + (JSON-RPC 2.0) +``` + +openab spawns your CLI as a child process and communicates via **newline-delimited JSON-RPC 2.0** over stdin/stdout. Your CLI needs to implement three methods and emit streaming notifications. + +## ACP protocol requirements + +### Message format + +Every message is a single line of JSON followed by `\n`. No Content-Length framing — just newline-delimited JSON. + +### Methods your CLI must handle + +#### 1. `initialize` + +Called once after spawn. Handshake to exchange capabilities. + +**Request (stdin):** +```json +{ + "jsonrpc": "2.0", + "id": 1, + "method": "initialize", + "params": { + "protocolVersion": 1, + "clientCapabilities": {}, + "clientInfo": { "name": "openab", "version": "0.1.0" } + } +} +``` + +**Response (stdout):** +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": { + "agentInfo": { "name": "my-agent", "version": "1.0.0" }, + "capabilities": {} + } +} +``` + +**Timeout:** 30 seconds. + +#### 2. `session/new` + +Creates a new conversation session. + +**Request:** +```json +{ + "jsonrpc": "2.0", + "id": 2, + "method": "session/new", + "params": { + "cwd": "/path/to/working/directory", + "mcpServers": [] + } +} +``` + +**Response:** +```json +{ + "jsonrpc": "2.0", + "id": 2, + "result": { + "sessionId": "unique-session-id" + } +} +``` + +**Timeout:** 120 seconds (longer due to potential model loading). + +#### 3. `session/prompt` + +Sends a user message and receives streaming responses. + +**Request:** +```json +{ + "jsonrpc": "2.0", + "id": 3, + "method": "session/prompt", + "params": { + "sessionId": "unique-session-id", + "prompt": [ + { "type": "text", "text": "Hello, help me with this code" } + ] + } +} +``` + +**Response:** Your CLI should emit **notifications** (see below) as it processes, then send a final response: +```json +{ + "jsonrpc": "2.0", + "id": 3, + "result": { "status": "completed" } +} +``` + +### Notifications your CLI should emit + +During `session/prompt` processing, emit these notifications to stdout (no `id` field): + +#### Text output (streaming) +```json +{ + "jsonrpc": "2.0", + "method": "session/notify", + "params": { + "update": { + "sessionUpdate": "agent_message_chunk", + "content": { "text": "chunk of response text" } + } + } +} +``` + +openab collects these chunks and live-updates the Discord message every 1.5 seconds. + +#### Thinking indicator +```json +{ + "jsonrpc": "2.0", + "method": "session/notify", + "params": { + "update": { "sessionUpdate": "agent_thought_chunk" } + } +} +``` + +Triggers the 🤔 reaction in Discord. + +#### Tool call start +```json +{ + "jsonrpc": "2.0", + "method": "session/notify", + "params": { + "update": { + "sessionUpdate": "tool_call", + "title": "tool_name" + } + } +} +``` + +Triggers tool-specific reactions (🔥 for general, 👨‍💻 for coding, ⚡ for web). + +#### Tool call completion +```json +{ + "jsonrpc": "2.0", + "method": "session/notify", + "params": { + "update": { + "sessionUpdate": "tool_call_update", + "title": "tool_name", + "status": "completed" + } + } +} +``` + +`status` can be `"completed"` or `"failed"`. + +### Permission requests (optional) + +If your CLI needs to request permission for sensitive operations, send: + +```json +{ + "jsonrpc": "2.0", + "id": 100, + "method": "session/request_permission", + "params": { + "toolCall": { "title": "tool_name" } + } +} +``` + +openab auto-replies with: +```json +{ + "jsonrpc": "2.0", + "id": 100, + "result": { "optionId": "allow_always" } +} +``` + +## Configuring openab + +Add your CLI to `config.toml`: + +```toml +[agent] +command = "my-cli" # executable name or full path +args = ["--some-flag"] # command-line arguments +working_dir = "/tmp" # working directory for the agent +env = { MY_KEY = "value" } # extra environment variables +``` + +Environment variables support `${VAR}` expansion from the host environment. + +## Testing your CLI + +### Step 1: Manual JSON-RPC test + +Run your CLI directly and paste JSON-RPC messages: + +```bash +./my-cli +``` + +Paste line by line: + +```json +{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":1,"clientCapabilities":{},"clientInfo":{"name":"test","version":"0.1.0"}}} +``` + +Expected: a response with `agentInfo`. + +```json +{"jsonrpc":"2.0","id":2,"method":"session/new","params":{"cwd":"/tmp","mcpServers":[]}} +``` + +Expected: a response with `sessionId`. + +Then use the session ID: + +```json +{"jsonrpc":"2.0","id":3,"method":"session/prompt","params":{"sessionId":"YOUR_SESSION_ID","prompt":[{"type":"text","text":"Say hello"}]}} +``` + +Expected: streaming `agent_message_chunk` notifications, followed by a final response with `id: 3`. + +### Step 2: Test with openab + +```bash +export DISCORD_BOT_TOKEN="your-token" + +cat > config.toml <