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 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
+ [![npm](https://img.shields.io/npm/v/claude-code-controller)](https://www.npmjs.com/package/claude-code-controller)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
7
+ [![Node >= 18](https://img.shields.io/badge/node-%3E%3D18-brightgreen)](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
+ ![Claude Code Controller UI](screenshot.png)
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