claude-code-controller 0.1.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/LICENSE +21 -0
- package/README.md +373 -0
- package/dist/index.cjs +1156 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +523 -0
- package/dist/index.d.ts +523 -0
- package/dist/index.js +1110 -0
- package/dist/index.js.map +1 -0
- package/package.json +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 The Vibe Company
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
# claude-code-controller
|
|
2
|
+
|
|
3
|
+
**Spawn, orchestrate, and control Claude Code agents from your own code.**
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/claude-code-controller)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](https://nodejs.org)
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
Your Code
|
|
11
|
+
│
|
|
12
|
+
▼
|
|
13
|
+
┌─────────────────────┐
|
|
14
|
+
│ ClaudeCodeController│
|
|
15
|
+
│ ┌───────────────┐ │
|
|
16
|
+
│ │ TeamManager │ │ ┌─────────┐
|
|
17
|
+
│ │ TaskManager │──┼────▶│ Agent 1 │ claude CLI (PTY)
|
|
18
|
+
│ │ ProcessMgr │ │ └─────────┘
|
|
19
|
+
│ │ InboxPoller │──┼────▶│ Agent 2 │ claude CLI (PTY)
|
|
20
|
+
│ └───────────────┘ │ └─────────┘
|
|
21
|
+
└─────────────────────┘ │ Agent N │ claude CLI (PTY)
|
|
22
|
+
└─────────┘
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
No Agent SDK. No `-p` mode. Full programmatic control over real Claude Code instances through the internal filesystem-based teams protocol.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Why?
|
|
30
|
+
|
|
31
|
+
Claude Code is powerful interactively, but there's no clean way to control it programmatically for automation. The Agent SDK is a separate thing. The `-p` flag is limited to single prompts.
|
|
32
|
+
|
|
33
|
+
This library reverse-engineers Claude Code's internal team communication protocol (filesystem-based inboxes, team configs, task files) and wraps it in a clean TypeScript API. You get:
|
|
34
|
+
|
|
35
|
+
- **Real Claude Code agents** with all their tools (Bash, Read, Write, Glob, Grep...)
|
|
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
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Install
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm install claude-code-controller
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
> **Prerequisites:** Claude Code CLI v2.1.34+ installed
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Quick Start
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import { ClaudeCodeController } from "claude-code-controller";
|
|
56
|
+
|
|
57
|
+
const ctrl = new ClaudeCodeController({ teamName: "my-project" });
|
|
58
|
+
await ctrl.init();
|
|
59
|
+
|
|
60
|
+
const agent = await ctrl.spawnAgent({
|
|
61
|
+
name: "coder",
|
|
62
|
+
type: "general-purpose",
|
|
63
|
+
model: "sonnet",
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Wait for the agent to boot up
|
|
67
|
+
await new Promise((r) => setTimeout(r, 10_000));
|
|
68
|
+
|
|
69
|
+
// Ask something and get the answer back
|
|
70
|
+
const answer = await agent.ask(
|
|
71
|
+
"Read package.json and tell me the project name. Reply using SendMessage.",
|
|
72
|
+
{ timeout: 60_000 }
|
|
73
|
+
);
|
|
74
|
+
console.log(answer);
|
|
75
|
+
|
|
76
|
+
await ctrl.shutdown();
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Real-World Examples
|
|
82
|
+
|
|
83
|
+
### Multi-Agent Code Review
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
const ctrl = new ClaudeCodeController({ teamName: "review" });
|
|
87
|
+
await ctrl.init();
|
|
88
|
+
|
|
89
|
+
// Spawn specialized reviewers in parallel
|
|
90
|
+
const [security, perf, style] = await Promise.all([
|
|
91
|
+
ctrl.spawnAgent({ name: "security", type: "general-purpose", model: "opus" }),
|
|
92
|
+
ctrl.spawnAgent({ name: "perf", type: "general-purpose", model: "sonnet" }),
|
|
93
|
+
ctrl.spawnAgent({ name: "style", type: "general-purpose", model: "haiku" }),
|
|
94
|
+
]);
|
|
95
|
+
|
|
96
|
+
await new Promise((r) => setTimeout(r, 12_000));
|
|
97
|
+
|
|
98
|
+
// Give each reviewer a different focus
|
|
99
|
+
const reviews = await Promise.all([
|
|
100
|
+
security.ask("Review src/ for security vulnerabilities. Reply with SendMessage."),
|
|
101
|
+
perf.ask("Review src/ for performance issues. Reply with SendMessage."),
|
|
102
|
+
style.ask("Review src/ for code style issues. Reply with SendMessage."),
|
|
103
|
+
]);
|
|
104
|
+
|
|
105
|
+
console.log("Security:", reviews[0]);
|
|
106
|
+
console.log("Performance:", reviews[1]);
|
|
107
|
+
console.log("Style:", reviews[2]);
|
|
108
|
+
|
|
109
|
+
await ctrl.shutdown();
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Task-Based Workflow
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
const ctrl = new ClaudeCodeController({ teamName: "tasks" });
|
|
116
|
+
await ctrl.init();
|
|
117
|
+
|
|
118
|
+
const worker = await ctrl.spawnAgent({ name: "worker", model: "sonnet" });
|
|
119
|
+
await new Promise((r) => setTimeout(r, 10_000));
|
|
120
|
+
|
|
121
|
+
// Create and assign a task
|
|
122
|
+
const taskId = await ctrl.createTask({
|
|
123
|
+
subject: "Add input validation",
|
|
124
|
+
description: "Add zod validation to all API route handlers in src/routes/",
|
|
125
|
+
owner: "worker",
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Wait for the agent to complete it
|
|
129
|
+
const result = await ctrl.waitForTask(taskId, 120_000);
|
|
130
|
+
console.log(`Task ${result.status}: ${result.subject}`);
|
|
131
|
+
|
|
132
|
+
await ctrl.shutdown();
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Custom API Provider
|
|
136
|
+
|
|
137
|
+
Route agents through any OpenAI-compatible endpoint:
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
const ctrl = new ClaudeCodeController({
|
|
141
|
+
teamName: "custom",
|
|
142
|
+
env: {
|
|
143
|
+
ANTHROPIC_BASE_URL: "https://your-proxy.example.com/api/anthropic",
|
|
144
|
+
ANTHROPIC_AUTH_TOKEN: process.env.MY_API_KEY!,
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Per-agent overrides take precedence:
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
const agent = await ctrl.spawnAgent({
|
|
153
|
+
name: "worker",
|
|
154
|
+
env: { ANTHROPIC_AUTH_TOKEN: "different-key-for-this-agent" },
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Event-Driven Control
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
const ctrl = new ClaudeCodeController({ teamName: "events" });
|
|
162
|
+
await ctrl.init();
|
|
163
|
+
|
|
164
|
+
// Auto-approve all plan requests
|
|
165
|
+
ctrl.on("plan:approval_request", (agent, msg) => {
|
|
166
|
+
ctrl.sendPlanApproval(agent, msg.requestId, true);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Auto-approve safe tool use, reject dangerous ones
|
|
170
|
+
ctrl.on("permission:request", (agent, msg) => {
|
|
171
|
+
const safe = ["Read", "Glob", "Grep"].includes(msg.toolName);
|
|
172
|
+
ctrl.sendPermissionResponse(agent, msg.requestId, safe);
|
|
173
|
+
});
|
|
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
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## API Reference
|
|
188
|
+
|
|
189
|
+
### `ClaudeCodeController`
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
const ctrl = new ClaudeCodeController({
|
|
193
|
+
teamName?: string, // default: auto-generated
|
|
194
|
+
cwd?: string, // working directory for agents
|
|
195
|
+
claudeBinary?: string, // path to claude CLI (default: "claude")
|
|
196
|
+
env?: Record<string, string>, // default env vars for all agents
|
|
197
|
+
logLevel?: "debug" | "info" | "warn" | "error" | "silent",
|
|
198
|
+
});
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
#### Lifecycle
|
|
202
|
+
|
|
203
|
+
| Method | Description |
|
|
204
|
+
|---|---|
|
|
205
|
+
| `ctrl.init()` | Initialize controller. Must call first. |
|
|
206
|
+
| `ctrl.shutdown()` | Graceful shutdown: request stop, wait, kill stragglers, clean up files. |
|
|
207
|
+
|
|
208
|
+
#### Agents
|
|
209
|
+
|
|
210
|
+
| Method | Returns | Description |
|
|
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 |
|
|
215
|
+
|
|
216
|
+
`SpawnAgentOptions`:
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
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
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
#### Messaging
|
|
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
|
|
239
|
+
|
|
240
|
+
| Method | Returns | Description |
|
|
241
|
+
|---|---|---|
|
|
242
|
+
| `ctrl.createTask({ subject, description, owner? })` | `string` (task ID) | Create a task, optionally assign it |
|
|
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
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Events
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
ctrl.on("message", (agentName, message) => { ... });
|
|
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) => { ... });
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## How It Works
|
|
291
|
+
|
|
292
|
+
Claude Code has an internal "teammate" system that communicates through the filesystem:
|
|
293
|
+
|
|
294
|
+
| Component | Path | Purpose |
|
|
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
|
+
```
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## Web UI
|
|
314
|
+
|
|
315
|
+
A built-in web dashboard lets you spawn agents, see their messages in real-time, and handle plan/permission approvals — all from your browser.
|
|
316
|
+
|
|
317
|
+

|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
cd web
|
|
321
|
+
bun install
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
**Development** (two terminals):
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
bun run dev # backend on :3456
|
|
328
|
+
bun run dev:vite # frontend on :5174 (proxies API to :3456)
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
Open http://localhost:5174
|
|
332
|
+
|
|
333
|
+
**Production:**
|
|
334
|
+
|
|
335
|
+
```bash
|
|
336
|
+
bun run build # build frontend
|
|
337
|
+
bun run start # serve everything on :3456
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Open http://localhost:3456
|
|
341
|
+
|
|
342
|
+
The UI provides:
|
|
343
|
+
- **Session management** — initialize/shutdown the controller
|
|
344
|
+
- **Agent spawning** — configure name, type, model, and environment variables
|
|
345
|
+
- **Live message feed** — real-time messages via WebSocket
|
|
346
|
+
- **Approval prompts** — interactive plan and permission approval banners
|
|
347
|
+
- **Agent controls** — shutdown or kill agents individually
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## Development
|
|
352
|
+
|
|
353
|
+
```bash
|
|
354
|
+
bun install # install deps
|
|
355
|
+
bun test # run tests (89 tests)
|
|
356
|
+
bun run typecheck # type check
|
|
357
|
+
bun run build # build for distribution
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
## Roadmap
|
|
363
|
+
|
|
364
|
+
- **Tmux session per agent** — Spawn each agent in its own tmux pane so you can attach to it (`tmux attach -t <agent>`) and watch it work in real time: tool calls, file edits, reasoning — like watching someone use Claude Code interactively
|
|
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
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
## License
|
|
372
|
+
|
|
373
|
+
MIT
|