# agent-core-cli

Headless CLI and HTTP server wrapping [`@accomplish_ai/agent-core`](https://github.com/accomplish-ai/accomplish/tree/main/packages/agent-core). A drop-in replacement for the sandbox mock agent that runs AI tasks autonomously — no Electron, no GUI.

## Architecture

```
Client  ──POST /jobs──>  Hono HTTP Server
                              |
                         JobRunner
                              |
                    TaskManager (agent-core)
                              |
                         OpenCode CLI
                              |
                    ┌─────────┴─────────┐
                    v                   v
              EventBus              StorageAPI
                |                       |
        ┌───────┼───────┐          SQLite DB
        v       v       v
       SSE     WS    stdout
```

## Prerequisites

- Node.js >= 20
- `ANTHROPIC_API_KEY` environment variable

## Installation

```bash
npm install -g @accomplish_ai/agent-core-cli
```

`@accomplish_ai/agent-core` is a **peer dependency** — npm 7+ installs it automatically. To pin a specific agent-core version (e.g. in a Dockerfile), install both:

```bash
npm install -g @accomplish_ai/agent-core-cli @accomplish_ai/agent-core@0.3.0
```

### From source

```bash
npm install
npm run build
```

## Commands

### `run` — One-shot execution

Run a single prompt and stream output to stdout, then exit.

```bash
agent-core-cli run --prompt "Write a hello world in Python" \
  --model anthropic/claude-sonnet-4-5 \
  --workdir ./my-project \
  --data-dir /app/data
```

| Option | Default | Description |
|--------|---------|-------------|
| `--prompt` (required) | — | Task prompt |
| `--model` | `anthropic/claude-sonnet-4-5` | Model ID |
| `--workdir` | Current directory | Working directory for the agent |
| `--data-dir` | `/app/data` | SQLite database directory |

### `serve` — HTTP server

Start the HTTP API server with SSE and WebSocket support.

```bash
agent-core-cli serve --port 8080 \
  --model anthropic/claude-sonnet-4-5 \
  --workdir /workspace \
  --data-dir /app/data
```

| Option | Default | Description |
|--------|---------|-------------|
| `--port` | `8080` | HTTP port (1-65535) |
| `--model` | `anthropic/claude-sonnet-4-5` | Model ID |
| `--workdir` | `/workspace` | Working directory for the agent |
| `--data-dir` | `/app/data` | SQLite database directory |

### `interactive` — REPL mode

Start a multi-turn conversation with session continuity.

```bash
agent-core-cli interactive \
  --model anthropic/claude-sonnet-4-5 \
  --workdir ./my-project
```

| Option | Default | Description |
|--------|---------|-------------|
| `--model` | `anthropic/claude-sonnet-4-5` | Model ID |
| `--workdir` | Current directory | Working directory for the agent |
| `--data-dir` | `/app/data` | SQLite database directory |

## HTTP API

### `GET /health`

```json
{ "status": "ok" }
```

### `POST /jobs`

Create and start a new agent job. Returns immediately; the job runs in the background.

**Request:**
```json
{ "id": "optional-custom-id", "prompt": "Build a REST API" }
```

**Response (201):**
```json
{ "id": "job_1707400000_abc1234", "status": "pending" }
```

### `GET /jobs/:id`

Poll job status.

```json
{
  "id": "job_123",
  "prompt": "Build a REST API",
  "status": "running",
  "result": null,
  "created_at": "2026-02-08T12:00:00.000Z",
  "started_at": "2026-02-08T12:00:01.000Z",
  "completed_at": null
}
```

Status values: `pending`, `running`, `completed`, `failed`.

### `GET /jobs/:id/stream`

Server-Sent Events stream. Sends an initial heartbeat, replays historical messages on connect, then streams live updates.

```
data: {"type":"connected","jobId":"job_123"}
data: {"type":"assistant","content":"I'll create the API..."}
data: {"type":"tool_use","content":"{\"tool\":\"bash\",\"input\":{...}}"}
data: {"type":"tool_result","content":"server.py created"}
data: {"type":"done","content":"Task completed"}
```

### `WS /jobs/:id/ws`

WebSocket endpoint. Same message format as SSE. Replays history on connect, streams live updates, closes automatically when the job completes.

## TaskManagerAPI Surface

The CLI wraps `@accomplish_ai/agent-core`'s `TaskManagerAPI`. Currently only `startTask` and `dispose` are used by the HTTP server. The full API surface is documented below for consumers and contributors.

### Task Lifecycle

| Method | Description | Used by CLI |
|--------|-------------|:-----------:|
| `startTask(taskId, config, callbacks)` | Start a task with prompt, config, and event callbacks | Yes |
| `interruptTask(taskId)` | Soft stop — sends Ctrl+C to the agent process for graceful shutdown | No |
| `cancelTask(taskId)` | Hard stop — kills the agent process immediately | No |
| `cancelQueuedTask(taskId)` | Cancel a task waiting in the queue (before it starts running) | No |
| `cancelAllTasks()` | Cancel all running and queued tasks | No |
| `sendResponse(taskId, response)` | Send a response to a task waiting for input (e.g. permission prompt) | No |
| `dispose()` | Dispose resources and clean up all processes | Yes |

### Task State Queries

| Method | Description | Used by CLI |
|--------|-------------|:-----------:|
| `isTaskRunning(taskId)` | Check if a specific task is currently running | No |
| `hasActiveTask(taskId)` | Check if a task is active (running or queued) | No |
| `hasRunningTask()` | Check if any task is currently running | No |
| `isTaskQueued(taskId)` | Check if a task is waiting in the queue | No |
| `getQueueLength()` | Get number of queued tasks | No |
| `getActiveTaskIds()` | Get IDs of all active tasks | No |
| `getActiveTaskId()` | Get ID of the currently running task | No |
| `getActiveTaskCount()` | Get count of active tasks (running + queued) | No |
| `getIsFirstTask()` | Check if this is the first task in the session | No |
| `getSessionId(taskId)` | Get the session ID for a task | No |

### Task Callbacks

Callbacks passed to `startTask()` for receiving lifecycle events.

| Callback | Required | Description | Used by CLI |
|----------|:--------:|-------------|:-----------:|
| `onMessage(message)` | No | Raw message from the agent (streamed as-is) | Yes |
| `onBatchedMessages(messages)` | No | Batched/processed messages (50ms batches) | No |
| `onProgress(progress)` | Yes | Task progress updates | Yes |
| `onPermissionRequest(request)` | Yes | Agent needs permission to proceed | Yes |
| `onComplete(result)` | Yes | Task finished (success or failure) | Yes |
| `onError(error)` | Yes | Unrecoverable error during execution | Yes |
| `onStatusChange(status)` | No | Task status transitions | No |
| `onDebug(info)` | No | Debug/diagnostic information | No |
| `onTodoUpdate(todos)` | No | Agent's internal todo list changes | No |
| `onAuthError(error)` | No | Authentication/API key errors | No |

### Configuration

Factory options passed to `createTaskManager()`.

| Option | Required | Description |
|--------|:--------:|-------------|
| `adapterOptions` | Yes | Platform, CLI command, environment builder, CLI args builder |
| `defaultWorkingDirectory` | Yes | Default working directory for tasks |
| `maxConcurrentTasks` | No | Max parallel tasks (CLI sets `1`) |
| `isCliAvailable` | Yes | Async check that the OpenCode CLI is installed |
| `onBeforeTaskStart` | No | Hook called before each task starts |

## Development

```bash
npm run dev      # Watch mode
npm run build    # Compile TypeScript
npm test         # Run tests
```

## License

MIT
