claude-code-controller 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +455 -196
- package/dist/api/index.cjs +1588 -0
- package/dist/api/index.cjs.map +1 -0
- package/dist/api/index.d.cts +173 -0
- package/dist/api/index.d.ts +173 -0
- package/dist/api/index.js +1562 -0
- package/dist/api/index.js.map +1 -0
- package/dist/controller-C9bh_59H.d.cts +397 -0
- package/dist/controller-C9bh_59H.d.ts +397 -0
- package/dist/index.d.cts +4 -396
- package/dist/index.d.ts +4 -396
- package/package.json +16 -1
package/README.md
CHANGED
|
@@ -1,55 +1,119 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<h1 align="center">claude-code-controller</h1>
|
|
3
|
+
<p align="center">
|
|
4
|
+
<strong>Spawn, orchestrate, and control Claude Code agents — programmatically.</strong>
|
|
5
|
+
</p>
|
|
6
|
+
<p align="center">
|
|
7
|
+
<a href="https://www.npmjs.com/package/claude-code-controller"><img src="https://img.shields.io/npm/v/claude-code-controller" alt="npm" /></a>
|
|
8
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="MIT License" /></a>
|
|
9
|
+
<a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-%3E%3D18-brightgreen" alt="Node >= 18" /></a>
|
|
10
|
+
</p>
|
|
11
|
+
</p>
|
|
2
12
|
|
|
3
|
-
|
|
13
|
+
<br />
|
|
4
14
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
15
|
+
<p align="center">
|
|
16
|
+
<img src="screenshot.png" alt="Claude Code Controller — Web Dashboard" width="100%" />
|
|
17
|
+
</p>
|
|
8
18
|
|
|
19
|
+
<br />
|
|
20
|
+
|
|
21
|
+
> Control real Claude Code instances through a **REST API**, a **TypeScript SDK**, or a **Web Dashboard**.
|
|
22
|
+
> Spawn agents, send them messages, assign tasks, approve plans — from your code or your browser.
|
|
23
|
+
|
|
24
|
+
<br />
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
<br />
|
|
29
|
+
|
|
30
|
+
## Why this instead of the Agent SDK?
|
|
31
|
+
|
|
32
|
+
This runs **real Claude Code processes**. Not a wrapper around the API. Not a simplified `-p` mode. Actual Claude Code — the same one you use in your terminal every day.
|
|
33
|
+
|
|
34
|
+
That means:
|
|
35
|
+
|
|
36
|
+
- **Uses your Claude Code subscription** — No separate API key. No usage-based billing surprise. If you have a Max plan, your agents run on it.
|
|
37
|
+
- **Day 0 features** — When Anthropic ships a new Claude Code feature (new tools, new models, better context handling), you get it immediately. No library update needed. No waiting for SDK support.
|
|
38
|
+
- **Full tool access** — Bash, Read, Write, Edit, Glob, Grep, WebSearch, Task sub-agents... everything Claude Code can do, your agents can do.
|
|
39
|
+
- **Real terminal environment** — Agents run in a PTY. They can install packages, run tests, use git, call APIs. They work in your actual project directory.
|
|
40
|
+
- **Battle-tested agent loop** — Claude Code's agent loop is production-hardened. You get all of that for free: retries, error handling, tool orchestration, context management.
|
|
41
|
+
|
|
42
|
+
<br />
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
<br />
|
|
47
|
+
|
|
48
|
+
## What you can do
|
|
49
|
+
|
|
50
|
+
**Spawn multiple agents on the same codebase**, each with a different role. One reviews security, another writes tests, another refactors — all in parallel, all through a simple API.
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# Spawn an agent via the REST API
|
|
54
|
+
curl -X POST http://localhost:3000/agents \
|
|
55
|
+
-H "Content-Type: application/json" \
|
|
56
|
+
-d '{"name": "security-reviewer", "model": "opus"}'
|
|
57
|
+
|
|
58
|
+
# Give it work
|
|
59
|
+
curl -X POST http://localhost:3000/agents/security-reviewer/messages \
|
|
60
|
+
-H "Content-Type: application/json" \
|
|
61
|
+
-d '{"message": "Audit src/auth/ for vulnerabilities. Reply with SendMessage."}'
|
|
9
62
|
```
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
63
|
+
|
|
64
|
+
**Build automation on top of Claude Code.** A webhook that triggers a code fix when CI fails. A Slack bot that assigns tasks to agents. A cron job that runs nightly code reviews. If you can make an HTTP call, you can control Claude Code.
|
|
65
|
+
|
|
66
|
+
**Monitor and control agents from a web dashboard.** See what each agent is doing, approve or reject their plans, grant tool permissions, kill runaway agents — all in real-time from your browser.
|
|
67
|
+
|
|
68
|
+
**Manage work with tasks.** Create tasks, assign them to agents, track progress, define dependencies between tasks. Agents pick up their assignments and report back when done.
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
const taskId = await ctrl.createTask({
|
|
72
|
+
subject: "Add input validation to all API routes",
|
|
73
|
+
description: "Use zod schemas for request body validation in src/routes/",
|
|
74
|
+
});
|
|
75
|
+
await ctrl.assignTask(taskId, "coder");
|
|
76
|
+
await ctrl.waitForTask(taskId); // blocks until done
|
|
23
77
|
```
|
|
24
78
|
|
|
25
|
-
|
|
79
|
+
<br />
|
|
26
80
|
|
|
27
81
|
---
|
|
28
82
|
|
|
29
|
-
|
|
83
|
+
<br />
|
|
30
84
|
|
|
31
|
-
|
|
85
|
+
## Features
|
|
32
86
|
|
|
33
|
-
|
|
87
|
+
- **REST API** — Control everything over HTTP. Spawn agents, send messages, manage tasks. Works from any language, any platform.
|
|
88
|
+
- **TypeScript SDK** — Full programmatic control with type safety and an event-driven architecture.
|
|
89
|
+
- **Web Dashboard** — Real-time monitoring, agent management, and interactive approvals from your browser.
|
|
90
|
+
- **Multi-Agent** — Run multiple agents in parallel, each with their own role, model, and permissions.
|
|
91
|
+
- **Task Management** — Create tasks, assign them, track status, define blocking dependencies.
|
|
92
|
+
- **Plan & Permission Approval** — Agents ask before acting. You approve or reject — programmatically or from the UI.
|
|
93
|
+
- **Any Provider** — Point agents at any Anthropic-compatible endpoint. Per-agent environment and API key overrides.
|
|
94
|
+
- **Your Subscription** — Runs on your existing Claude Code plan. No separate API costs.
|
|
34
95
|
|
|
35
|
-
|
|
36
|
-
- **Multi-agent orchestration** with message passing and task assignment
|
|
37
|
-
- **Full event system** for plan approvals, permission requests, shutdowns
|
|
38
|
-
- **Custom environment routing** to use any API provider
|
|
96
|
+
<br />
|
|
39
97
|
|
|
40
98
|
---
|
|
41
99
|
|
|
100
|
+
<br />
|
|
101
|
+
|
|
42
102
|
## Install
|
|
43
103
|
|
|
44
104
|
```bash
|
|
45
105
|
npm install claude-code-controller
|
|
46
106
|
```
|
|
47
107
|
|
|
48
|
-
> **
|
|
108
|
+
> **Prerequisite:** [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) v2.1.34+
|
|
109
|
+
|
|
110
|
+
<br />
|
|
49
111
|
|
|
50
112
|
---
|
|
51
113
|
|
|
52
|
-
|
|
114
|
+
<br />
|
|
115
|
+
|
|
116
|
+
## Quick Start — 30 seconds to your first agent
|
|
53
117
|
|
|
54
118
|
```typescript
|
|
55
119
|
import { ClaudeCodeController } from "claude-code-controller";
|
|
@@ -59,14 +123,11 @@ await ctrl.init();
|
|
|
59
123
|
|
|
60
124
|
const agent = await ctrl.spawnAgent({
|
|
61
125
|
name: "coder",
|
|
62
|
-
type: "general-purpose",
|
|
63
126
|
model: "sonnet",
|
|
64
127
|
});
|
|
65
128
|
|
|
66
|
-
// Wait for the agent to boot up
|
|
67
129
|
await new Promise((r) => setTimeout(r, 10_000));
|
|
68
130
|
|
|
69
|
-
// Ask something and get the answer back
|
|
70
131
|
const answer = await agent.ask(
|
|
71
132
|
"Read package.json and tell me the project name. Reply using SendMessage.",
|
|
72
133
|
{ timeout: 60_000 }
|
|
@@ -76,26 +137,302 @@ console.log(answer);
|
|
|
76
137
|
await ctrl.shutdown();
|
|
77
138
|
```
|
|
78
139
|
|
|
140
|
+
<br />
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
<br />
|
|
145
|
+
|
|
146
|
+
## REST API
|
|
147
|
+
|
|
148
|
+
The API lets you control Claude Code agents from **any language, any platform** — just HTTP.
|
|
149
|
+
|
|
150
|
+
Start a server in a few lines:
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
import { createApi } from "claude-code-controller/api";
|
|
154
|
+
import { serve } from "bun"; // or any Hono-compatible runtime
|
|
155
|
+
|
|
156
|
+
const app = createApi(); // lazy mode — init via POST /session/init
|
|
157
|
+
serve({ port: 3000, fetch: app.fetch.bind(app) });
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Or attach to an existing controller:
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
import { ClaudeCodeController } from "claude-code-controller";
|
|
164
|
+
import { createApi } from "claude-code-controller/api";
|
|
165
|
+
|
|
166
|
+
const ctrl = new ClaudeCodeController({ teamName: "my-team" });
|
|
167
|
+
await ctrl.init();
|
|
168
|
+
|
|
169
|
+
const app = createApi(ctrl); // pre-initialized mode
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
<br />
|
|
173
|
+
|
|
174
|
+
### Endpoints
|
|
175
|
+
|
|
176
|
+
#### Session
|
|
177
|
+
|
|
178
|
+
| Method | Endpoint | Description |
|
|
179
|
+
|--------|----------|-------------|
|
|
180
|
+
| `GET` | `/health` | Server health & uptime |
|
|
181
|
+
| `GET` | `/session` | Current session info |
|
|
182
|
+
| `POST` | `/session/init` | Initialize a new controller session |
|
|
183
|
+
| `POST` | `/session/shutdown` | Shut down the controller |
|
|
184
|
+
|
|
185
|
+
#### Agents
|
|
186
|
+
|
|
187
|
+
| Method | Endpoint | Description |
|
|
188
|
+
|--------|----------|-------------|
|
|
189
|
+
| `GET` | `/agents` | List all agents |
|
|
190
|
+
| `POST` | `/agents` | Spawn a new agent |
|
|
191
|
+
| `GET` | `/agents/:name` | Get agent details |
|
|
192
|
+
| `POST` | `/agents/:name/messages` | Send a message to an agent |
|
|
193
|
+
| `POST` | `/agents/:name/kill` | Force-kill an agent |
|
|
194
|
+
| `POST` | `/agents/:name/shutdown` | Request graceful shutdown |
|
|
195
|
+
| `POST` | `/agents/:name/approve-plan` | Approve or reject a plan |
|
|
196
|
+
| `POST` | `/agents/:name/approve-permission` | Approve or deny tool use |
|
|
197
|
+
|
|
198
|
+
#### Tasks
|
|
199
|
+
|
|
200
|
+
| Method | Endpoint | Description |
|
|
201
|
+
|--------|----------|-------------|
|
|
202
|
+
| `GET` | `/tasks` | List all tasks |
|
|
203
|
+
| `POST` | `/tasks` | Create a new task |
|
|
204
|
+
| `GET` | `/tasks/:id` | Get task details |
|
|
205
|
+
| `PATCH` | `/tasks/:id` | Update a task |
|
|
206
|
+
| `DELETE` | `/tasks/:id` | Delete a task |
|
|
207
|
+
| `POST` | `/tasks/:id/assign` | Assign task to an agent |
|
|
208
|
+
|
|
209
|
+
#### Actions (for dashboards & UIs)
|
|
210
|
+
|
|
211
|
+
| Method | Endpoint | Description |
|
|
212
|
+
|--------|----------|-------------|
|
|
213
|
+
| `GET` | `/actions` | All pending actions (approvals, idle agents, unassigned tasks) |
|
|
214
|
+
| `GET` | `/actions/approvals` | Pending approval requests |
|
|
215
|
+
| `GET` | `/actions/tasks` | Unassigned tasks |
|
|
216
|
+
| `GET` | `/actions/idle-agents` | Idle agents waiting for work |
|
|
217
|
+
|
|
218
|
+
#### Broadcasting
|
|
219
|
+
|
|
220
|
+
| Method | Endpoint | Description |
|
|
221
|
+
|--------|----------|-------------|
|
|
222
|
+
| `POST` | `/broadcast` | Send a message to all agents |
|
|
223
|
+
|
|
224
|
+
<br />
|
|
225
|
+
|
|
226
|
+
### API Examples
|
|
227
|
+
|
|
228
|
+
**Initialize a session:**
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
curl -X POST http://localhost:3000/session/init \
|
|
232
|
+
-H "Content-Type: application/json" \
|
|
233
|
+
-d '{"teamName": "my-team", "cwd": "/path/to/project"}'
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**Spawn an agent:**
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
curl -X POST http://localhost:3000/agents \
|
|
240
|
+
-H "Content-Type: application/json" \
|
|
241
|
+
-d '{
|
|
242
|
+
"name": "reviewer",
|
|
243
|
+
"type": "general-purpose",
|
|
244
|
+
"model": "sonnet",
|
|
245
|
+
"permissions": ["Bash", "Read", "Write"]
|
|
246
|
+
}'
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
**Send a message:**
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
curl -X POST http://localhost:3000/agents/reviewer/messages \
|
|
253
|
+
-H "Content-Type: application/json" \
|
|
254
|
+
-d '{"message": "Review src/auth.ts for security vulnerabilities. Reply with SendMessage."}'
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
**Create and assign a task:**
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
# Create
|
|
261
|
+
curl -X POST http://localhost:3000/tasks \
|
|
262
|
+
-H "Content-Type: application/json" \
|
|
263
|
+
-d '{"subject": "Fix login bug", "description": "Users cannot login with SSO"}'
|
|
264
|
+
|
|
265
|
+
# Assign
|
|
266
|
+
curl -X POST http://localhost:3000/tasks/1/assign \
|
|
267
|
+
-H "Content-Type: application/json" \
|
|
268
|
+
-d '{"agent": "reviewer"}'
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
**Check pending actions:**
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
curl http://localhost:3000/actions
|
|
275
|
+
# → { "pending": 2, "approvals": [...], "unassignedTasks": [...], "idleAgents": [...] }
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
<br />
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
<br />
|
|
283
|
+
|
|
284
|
+
## TypeScript SDK
|
|
285
|
+
|
|
286
|
+
The SDK gives you full programmatic control with type safety and an event-driven architecture.
|
|
287
|
+
|
|
288
|
+
### Controller
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
import { ClaudeCodeController } from "claude-code-controller";
|
|
292
|
+
|
|
293
|
+
const ctrl = new ClaudeCodeController({
|
|
294
|
+
teamName: "my-team", // auto-generated if omitted
|
|
295
|
+
cwd: "/path/to/project", // working directory for agents
|
|
296
|
+
claudeBinary: "claude", // path to CLI binary
|
|
297
|
+
env: { // default env vars for all agents
|
|
298
|
+
ANTHROPIC_BASE_URL: "https://your-proxy.example.com",
|
|
299
|
+
},
|
|
300
|
+
logLevel: "info", // "debug" | "info" | "warn" | "error" | "silent"
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
await ctrl.init();
|
|
304
|
+
// ... use the controller ...
|
|
305
|
+
await ctrl.shutdown();
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Spawning Agents
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
const agent = await ctrl.spawnAgent({
|
|
312
|
+
name: "coder",
|
|
313
|
+
type: "general-purpose", // "general-purpose" | "Bash" | "Explore" | "Plan"
|
|
314
|
+
model: "sonnet", // "sonnet" | "opus" | "haiku" | full model ID
|
|
315
|
+
cwd: "/specific/directory",
|
|
316
|
+
permissions: ["Bash", "Read", "Write", "Glob", "Grep"],
|
|
317
|
+
env: { MY_VAR: "value" }, // per-agent env overrides
|
|
318
|
+
});
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### AgentHandle
|
|
322
|
+
|
|
323
|
+
Every spawned agent returns an `AgentHandle` — a convenient wrapper for interacting with it.
|
|
324
|
+
|
|
325
|
+
```typescript
|
|
326
|
+
// Send and receive
|
|
327
|
+
await agent.send("Analyze the codebase structure.");
|
|
328
|
+
const response = await agent.receive({ timeout: 30_000 });
|
|
329
|
+
|
|
330
|
+
// Or use ask() for send + receive in one call
|
|
331
|
+
const answer = await agent.ask("What framework is this project using?", {
|
|
332
|
+
timeout: 60_000,
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// Stream events
|
|
336
|
+
for await (const msg of agent.events()) {
|
|
337
|
+
console.log(`[${agent.name}]`, msg.text);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Lifecycle
|
|
341
|
+
agent.isRunning; // boolean
|
|
342
|
+
agent.pid; // process ID
|
|
343
|
+
await agent.shutdown(); // graceful
|
|
344
|
+
await agent.kill(); // force
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### Messaging
|
|
348
|
+
|
|
349
|
+
```typescript
|
|
350
|
+
// Direct messaging
|
|
351
|
+
await ctrl.send("agent-name", "Your instructions here", "optional summary");
|
|
352
|
+
|
|
353
|
+
// Broadcast to all agents
|
|
354
|
+
await ctrl.broadcast("Everyone stop and report status.");
|
|
355
|
+
|
|
356
|
+
// Wait for a response
|
|
357
|
+
const messages = await ctrl.receive("agent-name", {
|
|
358
|
+
timeout: 60_000,
|
|
359
|
+
pollInterval: 500,
|
|
360
|
+
all: true, // get all unread messages
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
// Wait for any agent to respond
|
|
364
|
+
const msg = await ctrl.receiveAny({ timeout: 30_000 });
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Task Management
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
// Create a task
|
|
371
|
+
const taskId = await ctrl.createTask({
|
|
372
|
+
subject: "Add input validation",
|
|
373
|
+
description: "Add zod schemas to all API endpoints in src/routes/",
|
|
374
|
+
owner: "coder", // optional — assign immediately
|
|
375
|
+
metadata: { priority: "high" },
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
// Assign later
|
|
379
|
+
await ctrl.assignTask(taskId, "coder");
|
|
380
|
+
|
|
381
|
+
// Wait for completion
|
|
382
|
+
const task = await ctrl.waitForTask(taskId, 120_000);
|
|
383
|
+
console.log(task.status); // "completed"
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
### Events
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
// Agent messages
|
|
390
|
+
ctrl.on("message", (agentName, message) => {
|
|
391
|
+
console.log(`[${agentName}] ${message.text}`);
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
// Plan approval — agent wants to execute a plan
|
|
395
|
+
ctrl.on("plan:approval_request", (agentName, msg) => {
|
|
396
|
+
console.log(`${agentName} wants to execute a plan:`, msg.planContent);
|
|
397
|
+
ctrl.sendPlanApproval(agentName, msg.requestId, true);
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
// Permission request — agent wants to use a tool
|
|
401
|
+
ctrl.on("permission:request", (agentName, msg) => {
|
|
402
|
+
const safe = ["Read", "Glob", "Grep"].includes(msg.toolName);
|
|
403
|
+
ctrl.sendPermissionResponse(agentName, msg.requestId, safe);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
// Lifecycle events
|
|
407
|
+
ctrl.on("agent:spawned", (name, pid) => console.log(`${name} started (pid: ${pid})`));
|
|
408
|
+
ctrl.on("agent:exited", (name, code) => console.log(`${name} exited (code: ${code})`));
|
|
409
|
+
ctrl.on("idle", (name) => console.log(`${name} is idle`));
|
|
410
|
+
ctrl.on("task:completed", (task) => console.log(`Task done: ${task.subject}`));
|
|
411
|
+
ctrl.on("error", (err) => console.error("Controller error:", err));
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
<br />
|
|
415
|
+
|
|
79
416
|
---
|
|
80
417
|
|
|
418
|
+
<br />
|
|
419
|
+
|
|
81
420
|
## Real-World Examples
|
|
82
421
|
|
|
83
|
-
###
|
|
422
|
+
### Parallel Code Review
|
|
84
423
|
|
|
85
424
|
```typescript
|
|
86
425
|
const ctrl = new ClaudeCodeController({ teamName: "review" });
|
|
87
426
|
await ctrl.init();
|
|
88
427
|
|
|
89
|
-
// Spawn specialized reviewers in parallel
|
|
90
428
|
const [security, perf, style] = await Promise.all([
|
|
91
|
-
ctrl.spawnAgent({ name: "security",
|
|
92
|
-
ctrl.spawnAgent({ name: "perf",
|
|
93
|
-
ctrl.spawnAgent({ name: "style",
|
|
429
|
+
ctrl.spawnAgent({ name: "security", model: "opus" }),
|
|
430
|
+
ctrl.spawnAgent({ name: "perf", model: "sonnet" }),
|
|
431
|
+
ctrl.spawnAgent({ name: "style", model: "haiku" }),
|
|
94
432
|
]);
|
|
95
433
|
|
|
96
434
|
await new Promise((r) => setTimeout(r, 12_000));
|
|
97
435
|
|
|
98
|
-
// Give each reviewer a different focus
|
|
99
436
|
const reviews = await Promise.all([
|
|
100
437
|
security.ask("Review src/ for security vulnerabilities. Reply with SendMessage."),
|
|
101
438
|
perf.ask("Review src/ for performance issues. Reply with SendMessage."),
|
|
@@ -118,14 +455,12 @@ await ctrl.init();
|
|
|
118
455
|
const worker = await ctrl.spawnAgent({ name: "worker", model: "sonnet" });
|
|
119
456
|
await new Promise((r) => setTimeout(r, 10_000));
|
|
120
457
|
|
|
121
|
-
// Create and assign a task
|
|
122
458
|
const taskId = await ctrl.createTask({
|
|
123
459
|
subject: "Add input validation",
|
|
124
460
|
description: "Add zod validation to all API route handlers in src/routes/",
|
|
125
461
|
owner: "worker",
|
|
126
462
|
});
|
|
127
463
|
|
|
128
|
-
// Wait for the agent to complete it
|
|
129
464
|
const result = await ctrl.waitForTask(taskId, 120_000);
|
|
130
465
|
console.log(`Task ${result.status}: ${result.subject}`);
|
|
131
466
|
|
|
@@ -134,8 +469,6 @@ await ctrl.shutdown();
|
|
|
134
469
|
|
|
135
470
|
### Custom API Provider
|
|
136
471
|
|
|
137
|
-
Route agents through any OpenAI-compatible endpoint:
|
|
138
|
-
|
|
139
472
|
```typescript
|
|
140
473
|
const ctrl = new ClaudeCodeController({
|
|
141
474
|
teamName: "custom",
|
|
@@ -144,210 +477,128 @@ const ctrl = new ClaudeCodeController({
|
|
|
144
477
|
ANTHROPIC_AUTH_TOKEN: process.env.MY_API_KEY!,
|
|
145
478
|
},
|
|
146
479
|
});
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
Per-agent overrides take precedence:
|
|
150
480
|
|
|
151
|
-
|
|
481
|
+
// Per-agent overrides
|
|
152
482
|
const agent = await ctrl.spawnAgent({
|
|
153
483
|
name: "worker",
|
|
154
484
|
env: { ANTHROPIC_AUTH_TOKEN: "different-key-for-this-agent" },
|
|
155
485
|
});
|
|
156
486
|
```
|
|
157
487
|
|
|
158
|
-
###
|
|
488
|
+
### Auto-Approve Everything (YOLO mode)
|
|
159
489
|
|
|
160
490
|
```typescript
|
|
161
|
-
const ctrl = new ClaudeCodeController({ teamName: "events" });
|
|
162
|
-
await ctrl.init();
|
|
163
|
-
|
|
164
|
-
// Auto-approve all plan requests
|
|
165
491
|
ctrl.on("plan:approval_request", (agent, msg) => {
|
|
166
492
|
ctrl.sendPlanApproval(agent, msg.requestId, true);
|
|
167
493
|
});
|
|
168
494
|
|
|
169
|
-
// Auto-approve safe tool use, reject dangerous ones
|
|
170
495
|
ctrl.on("permission:request", (agent, msg) => {
|
|
171
|
-
|
|
172
|
-
ctrl.sendPermissionResponse(agent, msg.requestId, safe);
|
|
496
|
+
ctrl.sendPermissionResponse(agent, msg.requestId, true);
|
|
173
497
|
});
|
|
174
|
-
|
|
175
|
-
// React to agent messages
|
|
176
|
-
ctrl.on("message", (agent, msg) => {
|
|
177
|
-
console.log(`[${agent}] ${msg.text}`);
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
// Track lifecycle
|
|
181
|
-
ctrl.on("agent:spawned", (name, pid) => console.log(`${name} started (${pid})`));
|
|
182
|
-
ctrl.on("agent:exited", (name, code) => console.log(`${name} exited (${code})`));
|
|
183
498
|
```
|
|
184
499
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
## API Reference
|
|
188
|
-
|
|
189
|
-
### `ClaudeCodeController`
|
|
500
|
+
### Selective Permission Control
|
|
190
501
|
|
|
191
502
|
```typescript
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
503
|
+
const SAFE_TOOLS = ["Read", "Glob", "Grep", "Task"];
|
|
504
|
+
const NEEDS_REVIEW = ["Bash", "Write", "Edit"];
|
|
505
|
+
|
|
506
|
+
ctrl.on("permission:request", (agent, msg) => {
|
|
507
|
+
if (SAFE_TOOLS.includes(msg.toolName)) {
|
|
508
|
+
ctrl.sendPermissionResponse(agent, msg.requestId, true);
|
|
509
|
+
} else if (NEEDS_REVIEW.includes(msg.toolName)) {
|
|
510
|
+
console.log(`[REVIEW] ${agent} wants to use ${msg.toolName}: ${msg.description}`);
|
|
511
|
+
// Implement your own review logic here
|
|
512
|
+
ctrl.sendPermissionResponse(agent, msg.requestId, true);
|
|
513
|
+
} else {
|
|
514
|
+
ctrl.sendPermissionResponse(agent, msg.requestId, false);
|
|
515
|
+
}
|
|
198
516
|
});
|
|
199
517
|
```
|
|
200
518
|
|
|
201
|
-
|
|
519
|
+
<br />
|
|
202
520
|
|
|
203
|
-
|
|
204
|
-
|---|---|
|
|
205
|
-
| `ctrl.init()` | Initialize controller. Must call first. |
|
|
206
|
-
| `ctrl.shutdown()` | Graceful shutdown: request stop, wait, kill stragglers, clean up files. |
|
|
521
|
+
---
|
|
207
522
|
|
|
208
|
-
|
|
523
|
+
<br />
|
|
209
524
|
|
|
210
|
-
|
|
211
|
-
|---|---|---|
|
|
212
|
-
| `ctrl.spawnAgent(opts)` | `AgentHandle` | Spawn a Claude Code agent |
|
|
213
|
-
| `ctrl.isAgentRunning(name)` | `boolean` | Check if agent process is alive |
|
|
214
|
-
| `ctrl.killAgent(name)` | `void` | Force-kill an agent |
|
|
525
|
+
## Web Dashboard
|
|
215
526
|
|
|
216
|
-
|
|
527
|
+
A built-in web UI for real-time agent management — no code required.
|
|
217
528
|
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
name: string,
|
|
221
|
-
type?: "general-purpose" | "Bash" | "Explore" | "Plan" | string,
|
|
222
|
-
model?: "sonnet" | "opus" | "haiku" | string,
|
|
223
|
-
cwd?: string,
|
|
224
|
-
permissions?: string[], // e.g. ["Bash", "Read", "Write"]
|
|
225
|
-
env?: Record<string, string>,
|
|
226
|
-
}
|
|
529
|
+
```bash
|
|
530
|
+
cd web && bun install
|
|
227
531
|
```
|
|
228
532
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
| Method | Returns | Description |
|
|
232
|
-
|---|---|---|
|
|
233
|
-
| `ctrl.send(agent, message, summary?)` | `void` | Send message to an agent |
|
|
234
|
-
| `ctrl.broadcast(message, summary?)` | `void` | Send to all agents |
|
|
235
|
-
| `ctrl.receive(agent, opts?)` | `InboxMessage[]` | Wait for messages from agent |
|
|
236
|
-
| `ctrl.receiveAny(opts?)` | `InboxMessage` | Wait for message from any agent |
|
|
237
|
-
|
|
238
|
-
#### Tasks
|
|
533
|
+
**Development:**
|
|
239
534
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
| `ctrl.assignTask(taskId, agentName)` | `void` | Assign task to agent |
|
|
244
|
-
| `ctrl.waitForTask(taskId, timeout?)` | `TaskFile` | Block until task completes |
|
|
245
|
-
|
|
246
|
-
#### Protocol
|
|
247
|
-
|
|
248
|
-
| Method | Description |
|
|
249
|
-
|---|---|
|
|
250
|
-
| `ctrl.sendPlanApproval(agent, requestId, approve, feedback?)` | Respond to plan approval request |
|
|
251
|
-
| `ctrl.sendPermissionResponse(agent, requestId, approve)` | Respond to permission request |
|
|
252
|
-
| `ctrl.sendShutdownRequest(agent)` | Request graceful shutdown |
|
|
253
|
-
|
|
254
|
-
### `AgentHandle`
|
|
255
|
-
|
|
256
|
-
Convenience wrapper returned by `spawnAgent()`.
|
|
257
|
-
|
|
258
|
-
```typescript
|
|
259
|
-
await agent.send(message, summary?) // Send a message
|
|
260
|
-
await agent.receive(opts?) // Wait for response text
|
|
261
|
-
await agent.ask(question, opts?) // Send + receive in one call
|
|
262
|
-
await agent.shutdown() // Request graceful shutdown
|
|
263
|
-
await agent.kill() // Force kill
|
|
264
|
-
agent.isRunning // Check if alive (getter)
|
|
265
|
-
agent.name // Agent name
|
|
266
|
-
agent.pid // Process PID
|
|
267
|
-
|
|
268
|
-
// Async iterator for streaming events
|
|
269
|
-
for await (const msg of agent.events()) {
|
|
270
|
-
console.log(msg.text);
|
|
271
|
-
}
|
|
535
|
+
```bash
|
|
536
|
+
bun run dev # backend on :3456
|
|
537
|
+
bun run dev:vite # frontend on :5174
|
|
272
538
|
```
|
|
273
539
|
|
|
274
|
-
|
|
540
|
+
**Production:**
|
|
275
541
|
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
ctrl.on("idle", (agentName) => { ... });
|
|
279
|
-
ctrl.on("agent:spawned", (agentName, pid) => { ... });
|
|
280
|
-
ctrl.on("agent:exited", (agentName, code) => { ... });
|
|
281
|
-
ctrl.on("task:completed", (task) => { ... });
|
|
282
|
-
ctrl.on("shutdown:approved", (agentName, message) => { ... });
|
|
283
|
-
ctrl.on("plan:approval_request",(agentName, message) => { ... });
|
|
284
|
-
ctrl.on("permission:request", (agentName, message) => { ... });
|
|
285
|
-
ctrl.on("error", (error) => { ... });
|
|
542
|
+
```bash
|
|
543
|
+
bun run build && bun run start # everything on :3456
|
|
286
544
|
```
|
|
287
545
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
## How It Works
|
|
546
|
+
The dashboard gives you:
|
|
291
547
|
|
|
292
|
-
|
|
548
|
+
- **Session management** — Initialize and shut down the controller
|
|
549
|
+
- **Agent spawning** — Configure name, type, model, and environment variables
|
|
550
|
+
- **Live message feed** — Real-time messages via WebSocket
|
|
551
|
+
- **Approval prompts** — Interactive plan and permission approval banners
|
|
552
|
+
- **Agent controls** — Shutdown or kill agents individually
|
|
293
553
|
|
|
294
|
-
|
|
295
|
-
|---|---|---|
|
|
296
|
-
| Teams | `~/.claude/teams/{name}/config.json` | Team membership & config |
|
|
297
|
-
| Inboxes | `~/.claude/teams/{name}/inboxes/{agent}.json` | Message passing |
|
|
298
|
-
| Tasks | `~/.claude/tasks/{name}/{id}.json` | Task tracking |
|
|
299
|
-
|
|
300
|
-
This library creates those files, spawns real Claude Code CLI processes via PTY, and communicates with them through the inbox protocol. Agents think they're in a normal team and respond naturally.
|
|
301
|
-
|
|
302
|
-
```
|
|
303
|
-
ClaudeCodeController
|
|
304
|
-
├── TeamManager — team config CRUD
|
|
305
|
-
├── TaskManager — task lifecycle management
|
|
306
|
-
├── ProcessManager — PTY-based process spawning
|
|
307
|
-
├── InboxPoller — polls controller inbox for agent messages
|
|
308
|
-
└── AgentHandle — per-agent convenience wrapper
|
|
309
|
-
```
|
|
554
|
+
<br />
|
|
310
555
|
|
|
311
556
|
---
|
|
312
557
|
|
|
313
|
-
|
|
558
|
+
<br />
|
|
314
559
|
|
|
315
|
-
|
|
560
|
+
## How It Works
|
|
316
561
|
|
|
317
|
-
|
|
562
|
+
Claude Code has an internal "teammate" protocol that uses the filesystem for communication. This library creates the required files, spawns real Claude Code CLI processes via PTY, and communicates with them through inbox files. Agents think they're in a normal team and behave naturally.
|
|
318
563
|
|
|
319
|
-
```bash
|
|
320
|
-
cd web
|
|
321
|
-
bun install
|
|
322
564
|
```
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
565
|
+
~/.claude/
|
|
566
|
+
├── teams/{teamName}/
|
|
567
|
+
│ ├── config.json # Team membership & config
|
|
568
|
+
│ └── inboxes/
|
|
569
|
+
│ ├── controller.json # Messages TO controller FROM agents
|
|
570
|
+
│ ├── agent-1.json # Messages TO agent-1 FROM controller
|
|
571
|
+
│ └── agent-2.json # Messages TO agent-2 FROM controller
|
|
572
|
+
└── tasks/{teamName}/
|
|
573
|
+
├── 1.json # Task files
|
|
574
|
+
└── 2.json
|
|
329
575
|
```
|
|
330
576
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
**Production:**
|
|
577
|
+
**Architecture:**
|
|
334
578
|
|
|
335
|
-
```
|
|
336
|
-
|
|
337
|
-
|
|
579
|
+
```
|
|
580
|
+
ClaudeCodeController
|
|
581
|
+
├── TeamManager → Team config CRUD
|
|
582
|
+
├── TaskManager → Task lifecycle management
|
|
583
|
+
├── ProcessManager → PTY-based process spawning
|
|
584
|
+
├── InboxPoller → Polls controller inbox for agent messages
|
|
585
|
+
└── AgentHandle[] → Per-agent convenience wrappers
|
|
338
586
|
```
|
|
339
587
|
|
|
340
|
-
|
|
588
|
+
**The flow:**
|
|
341
589
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
590
|
+
1. **Spawn** — Calls `claude --teammate-mode auto --agent-id name@team ...` via a PTY wrapper
|
|
591
|
+
2. **Register** — Agent is registered in the team config with its role, model, and permissions
|
|
592
|
+
3. **Communicate** — Controller writes to `inboxes/{agent}.json`, agent writes to `inboxes/controller.json`
|
|
593
|
+
4. **Poll** — InboxPoller reads the controller inbox every 500ms and fires events
|
|
594
|
+
5. **Lock** — All file operations use `proper-lockfile` to prevent corruption from concurrent access
|
|
595
|
+
|
|
596
|
+
<br />
|
|
348
597
|
|
|
349
598
|
---
|
|
350
599
|
|
|
600
|
+
<br />
|
|
601
|
+
|
|
351
602
|
## Development
|
|
352
603
|
|
|
353
604
|
```bash
|
|
@@ -357,17 +608,25 @@ bun run typecheck # type check
|
|
|
357
608
|
bun run build # build for distribution
|
|
358
609
|
```
|
|
359
610
|
|
|
611
|
+
<br />
|
|
612
|
+
|
|
360
613
|
---
|
|
361
614
|
|
|
615
|
+
<br />
|
|
616
|
+
|
|
362
617
|
## Roadmap
|
|
363
618
|
|
|
364
|
-
- **Tmux session per agent** — Spawn each agent in its own tmux pane
|
|
365
|
-
- **Task management in the UI** — Create, assign, and track tasks from the web dashboard
|
|
366
|
-
- **Agent-to-agent messaging** — Let agents communicate directly with each other
|
|
367
|
-
- **Persistent sessions** — Resume a team session after server restart
|
|
619
|
+
- **Tmux session per agent** — Spawn each agent in its own tmux pane. Attach with `tmux attach -t <agent>` and watch it work: tool calls, file edits, reasoning — like watching someone use Claude Code interactively.
|
|
620
|
+
- **Task management in the UI** — Create, assign, and track tasks from the web dashboard.
|
|
621
|
+
- **Agent-to-agent messaging** — Let agents communicate directly with each other.
|
|
622
|
+
- **Persistent sessions** — Resume a team session after server restart.
|
|
623
|
+
|
|
624
|
+
<br />
|
|
368
625
|
|
|
369
626
|
---
|
|
370
627
|
|
|
628
|
+
<br />
|
|
629
|
+
|
|
371
630
|
## License
|
|
372
631
|
|
|
373
632
|
MIT
|