@desplega.ai/agent-swarm 1.0.2 → 1.2.1

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/deploy/install.ts CHANGED
@@ -7,10 +7,14 @@ const SERVICE_FILE = "/etc/systemd/system/agent-swarm.service";
7
7
  const SCRIPT_DIR = import.meta.dir;
8
8
  const PROJECT_DIR = `${SCRIPT_DIR}/..`;
9
9
 
10
+ // Detect bun path
11
+ const bunPath = (await $`which bun`.text()).trim();
12
+ console.log(`Using bun at: ${bunPath}`);
13
+
10
14
  // Copy project files
11
15
  await $`mkdir -p ${APP_DIR}`;
12
16
  await $`cp -r ${PROJECT_DIR}/src ${APP_DIR}/`;
13
- await $`cp ${PROJECT_DIR}/package.json ${PROJECT_DIR}/bun.lock ${APP_DIR}/`;
17
+ await $`cp ${PROJECT_DIR}/package.json ${PROJECT_DIR}/bun.lock ${PROJECT_DIR}/tsconfig.json ${APP_DIR}/`;
14
18
 
15
19
  // Install dependencies
16
20
  await $`cd ${APP_DIR} && bun install --frozen-lockfile --production`;
@@ -25,11 +29,57 @@ API_KEY=
25
29
  }
26
30
 
27
31
  // Set ownership
28
- await $`chown -R www-data:www-data ${APP_DIR}`;
32
+ await $`chown -R root:root ${APP_DIR}`;
33
+
34
+ // Install systemd service with detected bun path
35
+ const serviceContent = `[Unit]
36
+ Description=Agent Swarm MCP Server
37
+ After=network.target
38
+
39
+ [Service]
40
+ Type=simple
41
+ User=root
42
+ Group=root
43
+ WorkingDirectory=${APP_DIR}
44
+ ExecStart=${bunPath} run start:http
45
+ ExecStartPost=/bin/sh -c 'sleep 2 && curl -sf http://localhost:3013/health || exit 1'
46
+ Restart=always
47
+ RestartSec=5
48
+ EnvironmentFile=${APP_DIR}/.env
49
+
50
+ [Install]
51
+ WantedBy=multi-user.target
52
+ `;
53
+
54
+ await Bun.write(SERVICE_FILE, serviceContent);
55
+
56
+ // Healthcheck service (runs curl, restarts main service on failure)
57
+ const healthcheckService = `[Unit]
58
+ Description=Agent Swarm Health Check
59
+
60
+ [Service]
61
+ Type=oneshot
62
+ ExecStart=/bin/sh -c 'curl -sf http://localhost:3013/health || systemctl restart agent-swarm'
63
+ `;
64
+
65
+ // Timer to run healthcheck every 30 seconds
66
+ const healthcheckTimer = `[Unit]
67
+ Description=Agent Swarm Health Check Timer
68
+
69
+ [Timer]
70
+ OnBootSec=30s
71
+ OnUnitActiveSec=30s
72
+
73
+ [Install]
74
+ WantedBy=timers.target
75
+ `;
76
+
77
+ await Bun.write("/etc/systemd/system/agent-swarm-healthcheck.service", healthcheckService);
78
+ await Bun.write("/etc/systemd/system/agent-swarm-healthcheck.timer", healthcheckTimer);
29
79
 
30
- // Install systemd service
31
- await $`cp ${SCRIPT_DIR}/agent-swarm.service ${SERVICE_FILE}`;
32
80
  await $`systemctl daemon-reload`;
33
- await $`systemctl enable agent-swarm`;
81
+ await $`systemctl enable agent-swarm agent-swarm-healthcheck.timer`;
82
+ await $`systemctl restart agent-swarm`;
83
+ await $`systemctl start agent-swarm-healthcheck.timer`;
34
84
 
35
- console.log("Installed. Edit /opt/agent-swarm/.env then run: systemctl start agent-swarm");
85
+ console.log("Installed and running with health checks every 30s.");
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { $ } from "bun";
4
+ import * as readline from "node:readline";
5
+
6
+ const DB_PATH = "/opt/agent-swarm/agent-swarm-db.sqlite";
7
+ const SSH_HOST = process.argv[2] || "hetzner";
8
+
9
+ console.log(`Connected to ${SSH_HOST}:${DB_PATH}`);
10
+ console.log("Type SQL queries or .tables, .schema, etc. Ctrl+C to exit.\n");
11
+
12
+ const rl = readline.createInterface({
13
+ input: process.stdin,
14
+ output: process.stdout,
15
+ prompt: "sqlite> ",
16
+ });
17
+
18
+ rl.prompt();
19
+
20
+ rl.on("line", async (line) => {
21
+ const query = line.trim();
22
+ if (!query) {
23
+ rl.prompt();
24
+ return;
25
+ }
26
+
27
+ try {
28
+ // Escape single quotes in query and wrap in single quotes for remote shell
29
+ const escaped = query.replace(/'/g, "'\\''");
30
+ const result = await $`ssh ${SSH_HOST} sqlite3 -header -column ${DB_PATH} ${"'" + escaped + "'"}`.text();
31
+ if (result) console.log(result);
32
+ } catch (e: any) {
33
+ console.error(e.stderr?.toString() || e.message);
34
+ }
35
+
36
+ rl.prompt();
37
+ });
38
+
39
+ rl.on("close", () => {
40
+ console.log("\nBye!");
41
+ process.exit(0);
42
+ });
@@ -2,9 +2,11 @@
2
2
 
3
3
  import { $ } from "bun";
4
4
 
5
- await $`systemctl stop agent-swarm`.nothrow();
6
- await $`systemctl disable agent-swarm`.nothrow();
5
+ await $`systemctl stop agent-swarm agent-swarm-healthcheck.timer`.nothrow();
6
+ await $`systemctl disable agent-swarm agent-swarm-healthcheck.timer`.nothrow();
7
7
  await $`rm -f /etc/systemd/system/agent-swarm.service`;
8
+ await $`rm -f /etc/systemd/system/agent-swarm-healthcheck.service`;
9
+ await $`rm -f /etc/systemd/system/agent-swarm-healthcheck.timer`;
8
10
  await $`systemctl daemon-reload`;
9
11
 
10
12
  console.log("Service removed. Data remains at /opt/agent-swarm");
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { $ } from "bun";
4
+
5
+ const APP_DIR = "/opt/agent-swarm";
6
+ const SCRIPT_DIR = import.meta.dir;
7
+ const PROJECT_DIR = `${SCRIPT_DIR}/..`;
8
+
9
+ console.log("Updating agent-swarm...");
10
+
11
+ // Copy project files
12
+ await $`cp -r ${PROJECT_DIR}/src ${APP_DIR}/`;
13
+ await $`cp ${PROJECT_DIR}/package.json ${PROJECT_DIR}/bun.lock ${PROJECT_DIR}/tsconfig.json ${APP_DIR}/`;
14
+
15
+ // Install dependencies
16
+ await $`cd ${APP_DIR} && bun install --frozen-lockfile --production`;
17
+
18
+ // Restart service
19
+ await $`systemctl restart agent-swarm`;
20
+
21
+ console.log("Updated and restarted.");
@@ -0,0 +1,35 @@
1
+ # Docker Compose for Agent Swarm Worker
2
+ #
3
+ # Usage:
4
+ # docker-compose -f docker-compose.worker.yml up --build
5
+ #
6
+ # Required environment variables (in .env.docker or passed directly):
7
+ # - CLAUDE_CODE_OAUTH_TOKEN: OAuth token for Claude CLI
8
+ # - API_KEY: API key for MCP server authentication
9
+ #
10
+ # Optional environment variables:
11
+ # - AGENT_ID: UUID for agent identification (assigned on join if not set)
12
+ # - MCP_BASE_URL: MCP server URL (default: http://host.docker.internal:3013)
13
+ # - SESSION_ID: Folder name for logs (auto-generated if not provided)
14
+ # - WORKER_YOLO: If "true", continue on errors (default: false)
15
+
16
+ services:
17
+ worker:
18
+ build:
19
+ context: .
20
+ dockerfile: Dockerfile.worker
21
+ environment:
22
+ - CLAUDE_CODE_OAUTH_TOKEN=${CLAUDE_CODE_OAUTH_TOKEN}
23
+ - API_KEY=${API_KEY}
24
+ - AGENT_ID=${AGENT_ID:-}
25
+ # Use host.docker.internal to reach host's localhost on Mac/Windows
26
+ # On Linux, use --add-host=host.docker.internal:host-gateway or --network host
27
+ - MCP_BASE_URL=${MCP_BASE_URL:-http://host.docker.internal:3013}
28
+ - SESSION_ID=${SESSION_ID:-}
29
+ - WORKER_YOLO=${WORKER_YOLO:-false}
30
+ volumes:
31
+ - ./logs:/logs
32
+ # Optional: mount a workspace directory for persistent work
33
+ # - ./workspace:/workspace
34
+ restart: unless-stopped
35
+ # No port mappings needed - worker only makes outbound connections
@@ -0,0 +1,62 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ # Validate required environment variables
5
+ if [ -z "$CLAUDE_CODE_OAUTH_TOKEN" ]; then
6
+ echo "Error: CLAUDE_CODE_OAUTH_TOKEN environment variable is required"
7
+ exit 1
8
+ fi
9
+
10
+ if [ -z "$API_KEY" ]; then
11
+ echo "Error: API_KEY environment variable is required"
12
+ exit 1
13
+ fi
14
+
15
+ MCP_URL="${MCP_BASE_URL:-http://host.docker.internal:3013}"
16
+
17
+ echo "=== Agent Swarm Worker ==="
18
+ echo "Agent ID: ${AGENT_ID:-<not set>}"
19
+ echo "MCP Base URL: $MCP_URL"
20
+ echo "YOLO Mode: ${WORKER_YOLO:-false}"
21
+ echo "Session ID: ${SESSION_ID:-<auto-generated>}"
22
+ echo "Working Directory: /workspace"
23
+ echo "=========================="
24
+
25
+ # Create .mcp.json in /workspace (project-level config)
26
+ echo "Creating MCP config in /workspace..."
27
+ if [ -n "$AGENT_ID" ]; then
28
+ # With AGENT_ID header
29
+ cat > /workspace/.mcp.json << EOF
30
+ {
31
+ "mcpServers": {
32
+ "agent-swarm": {
33
+ "type": "http",
34
+ "url": "${MCP_URL}/mcp",
35
+ "headers": {
36
+ "Authorization": "Bearer ${API_KEY}",
37
+ "X-Agent-ID": "${AGENT_ID}"
38
+ }
39
+ }
40
+ }
41
+ }
42
+ EOF
43
+ else
44
+ # Without AGENT_ID header
45
+ cat > /workspace/.mcp.json << EOF
46
+ {
47
+ "mcpServers": {
48
+ "agent-swarm": {
49
+ "type": "http",
50
+ "url": "${MCP_URL}/mcp",
51
+ "headers": {
52
+ "Authorization": "Bearer ${API_KEY}"
53
+ }
54
+ }
55
+ }
56
+ }
57
+ EOF
58
+ fi
59
+
60
+ # Run the worker using compiled binary
61
+ echo "Starting worker..."
62
+ exec /usr/local/bin/agent-swarm worker "$@"
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@desplega.ai/agent-swarm",
3
- "version": "1.0.2",
3
+ "version": "1.2.1",
4
4
  "description": "Agent orchestration layer MCP for Claude Code, Codex, Gemini CLI, and more!",
5
- "module": "src/stdio.ts",
5
+ "module": "src/http.ts",
6
6
  "type": "module",
7
7
  "bin": {
8
8
  "agent-swarm": "./src/cli.tsx"
@@ -17,6 +17,8 @@
17
17
  "hook": "bun src/hooks/hook.ts",
18
18
  "claude": "bun src/cli.tsx claude",
19
19
  "claude:headless": "bun src/cli.tsx claude --headless",
20
+ "worker": "bun src/cli.tsx worker",
21
+ "worker:yolo": "bun src/cli.tsx worker --yolo",
20
22
  "dev": "bun --hot src/stdio.ts",
21
23
  "dev:http": "bun --hot src/http.ts",
22
24
  "start": "bun src/stdio.ts",
@@ -25,7 +27,15 @@
25
27
  "inspector:http": "bunx @modelcontextprotocol/inspector --transport http http://localhost:3013/mcp",
26
28
  "lint": "biome check src",
27
29
  "lint:fix": "biome check --write src",
28
- "format": "biome format --write src"
30
+ "format": "biome format --write src",
31
+ "build:binary": "bun build ./src/cli.tsx --compile --target=bun-linux-x64 --outfile ./dist/agent-swarm",
32
+ "build:binary:arm64": "bun build ./src/cli.tsx --compile --target=bun-linux-arm64 --outfile ./dist/agent-swarm",
33
+ "docker:build:worker": "docker build -f Dockerfile.worker -t agent-swarm-worker:latest .",
34
+ "docker:run:worker": "docker run --rm -it --env-file .env.docker -v ./logs:/logs -v ./work:/workspace agent-swarm-worker:latest",
35
+ "deploy:install": "bun deploy/install.ts",
36
+ "deploy:update": "bun deploy/update.ts",
37
+ "deploy:uninstall": "bun deploy/uninstall.ts",
38
+ "deploy:docker": "bun deploy/docker-push.ts"
29
39
  },
30
40
  "devDependencies": {
31
41
  "@biomejs/biome": "^2.3.9",
@@ -41,6 +51,7 @@
41
51
  "date-fns": "^4.1.0",
42
52
  "ink": "^6.5.1",
43
53
  "react": "^19.2.3",
54
+ "react-devtools-core": "^7.0.1",
44
55
  "zod": "^4.2.1"
45
56
  }
46
57
  }
package/src/be/db.ts CHANGED
@@ -1,5 +1,13 @@
1
1
  import { Database } from "bun:sqlite";
2
- import type { Agent, AgentLog, AgentLogEventType, AgentStatus, AgentTask, AgentTaskStatus, AgentWithTasks } from "../types";
2
+ import type {
3
+ Agent,
4
+ AgentLog,
5
+ AgentLogEventType,
6
+ AgentStatus,
7
+ AgentTask,
8
+ AgentTaskStatus,
9
+ AgentWithTasks,
10
+ } from "../types";
3
11
 
4
12
  let db: Database | null = null;
5
13
 
@@ -141,7 +149,12 @@ export function updateAgentStatus(id: string, status: AgentStatus): Agent | null
141
149
  const row = agentQueries.updateStatus().get(status, id);
142
150
  if (row && oldAgent) {
143
151
  try {
144
- createLogEntry({ eventType: "agent_status_change", agentId: id, oldValue: oldAgent.status, newValue: status });
152
+ createLogEntry({
153
+ eventType: "agent_status_change",
154
+ agentId: id,
155
+ oldValue: oldAgent.status,
156
+ newValue: status,
157
+ });
145
158
  } catch {}
146
159
  }
147
160
  return row ? rowToAgent(row) : null;
@@ -262,7 +275,13 @@ export function startTask(taskId: string): AgentTask | null {
262
275
  .get(taskId);
263
276
  if (row && oldTask) {
264
277
  try {
265
- createLogEntry({ eventType: "task_status_change", taskId, agentId: row.agentId, oldValue: oldTask.status, newValue: "in_progress" });
278
+ createLogEntry({
279
+ eventType: "task_status_change",
280
+ taskId,
281
+ agentId: row.agentId,
282
+ oldValue: oldTask.status,
283
+ newValue: "in_progress",
284
+ });
266
285
  } catch {}
267
286
  }
268
287
  return row ? rowToAgentTask(row) : null;
@@ -308,7 +327,13 @@ export function completeTask(id: string, output?: string): AgentTask | null {
308
327
 
309
328
  if (row && oldTask) {
310
329
  try {
311
- createLogEntry({ eventType: "task_status_change", taskId: id, agentId: row.agentId, oldValue: oldTask.status, newValue: "completed" });
330
+ createLogEntry({
331
+ eventType: "task_status_change",
332
+ taskId: id,
333
+ agentId: row.agentId,
334
+ oldValue: oldTask.status,
335
+ newValue: "completed",
336
+ });
312
337
  } catch {}
313
338
  }
314
339
 
@@ -321,7 +346,14 @@ export function failTask(id: string, reason: string): AgentTask | null {
321
346
  const row = taskQueries.setFailure().get(reason, finishedAt, id);
322
347
  if (row && oldTask) {
323
348
  try {
324
- createLogEntry({ eventType: "task_status_change", taskId: id, agentId: row.agentId, oldValue: oldTask.status, newValue: "failed", metadata: { reason } });
349
+ createLogEntry({
350
+ eventType: "task_status_change",
351
+ taskId: id,
352
+ agentId: row.agentId,
353
+ oldValue: oldTask.status,
354
+ newValue: "failed",
355
+ metadata: { reason },
356
+ });
325
357
  } catch {}
326
358
  }
327
359
  return row ? rowToAgentTask(row) : null;
@@ -336,7 +368,12 @@ export function updateTaskProgress(id: string, progress: string): AgentTask | nu
336
368
  const row = taskQueries.setProgress().get(progress, id);
337
369
  if (row) {
338
370
  try {
339
- createLogEntry({ eventType: "task_progress", taskId: id, agentId: row.agentId, newValue: progress });
371
+ createLogEntry({
372
+ eventType: "task_progress",
373
+ taskId: id,
374
+ agentId: row.agentId,
375
+ newValue: progress,
376
+ });
340
377
  } catch {}
341
378
  }
342
379
  return row ? rowToAgentTask(row) : null;
@@ -400,7 +437,10 @@ function rowToAgentLog(row: AgentLogRow): AgentLog {
400
437
 
401
438
  export const logQueries = {
402
439
  insert: () =>
403
- getDb().prepare<AgentLogRow, [string, string, string | null, string | null, string | null, string | null, string | null]>(
440
+ getDb().prepare<
441
+ AgentLogRow,
442
+ [string, string, string | null, string | null, string | null, string | null, string | null]
443
+ >(
404
444
  `INSERT INTO agent_log (id, eventType, agentId, taskId, oldValue, newValue, metadata, createdAt)
405
445
  VALUES (?, ?, ?, ?, ?, ?, ?, strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) RETURNING *`,
406
446
  ),
@@ -420,10 +460,7 @@ export const logQueries = {
420
460
  "SELECT * FROM agent_log WHERE eventType = ? ORDER BY createdAt DESC",
421
461
  ),
422
462
 
423
- getAll: () =>
424
- getDb().prepare<AgentLogRow, []>(
425
- "SELECT * FROM agent_log ORDER BY createdAt DESC",
426
- ),
463
+ getAll: () => getDb().prepare<AgentLogRow, []>("SELECT * FROM agent_log ORDER BY createdAt DESC"),
427
464
  };
428
465
 
429
466
  export function createLogEntry(entry: {
@@ -435,15 +472,17 @@ export function createLogEntry(entry: {
435
472
  metadata?: Record<string, unknown>;
436
473
  }): AgentLog {
437
474
  const id = crypto.randomUUID();
438
- const row = logQueries.insert().get(
439
- id,
440
- entry.eventType,
441
- entry.agentId ?? null,
442
- entry.taskId ?? null,
443
- entry.oldValue ?? null,
444
- entry.newValue ?? null,
445
- entry.metadata ? JSON.stringify(entry.metadata) : null,
446
- );
475
+ const row = logQueries
476
+ .insert()
477
+ .get(
478
+ id,
479
+ entry.eventType,
480
+ entry.agentId ?? null,
481
+ entry.taskId ?? null,
482
+ entry.oldValue ?? null,
483
+ entry.newValue ?? null,
484
+ entry.metadata ? JSON.stringify(entry.metadata) : null,
485
+ );
447
486
  if (!row) throw new Error("Failed to create log entry");
448
487
  return rowToAgentLog(row);
449
488
  }
@@ -456,6 +495,15 @@ export function getLogsByTaskId(taskId: string): AgentLog[] {
456
495
  return logQueries.getByTaskId().all(taskId).map(rowToAgentLog);
457
496
  }
458
497
 
498
+ export function getLogsByTaskIdChronological(taskId: string): AgentLog[] {
499
+ return getDb()
500
+ .prepare<AgentLogRow, [string]>(
501
+ "SELECT * FROM agent_log WHERE taskId = ? ORDER BY createdAt ASC",
502
+ )
503
+ .all(taskId)
504
+ .map(rowToAgentLog);
505
+ }
506
+
459
507
  export function getAllLogs(limit?: number): AgentLog[] {
460
508
  if (limit) {
461
509
  return getDb()
package/src/cli.tsx CHANGED
@@ -6,6 +6,7 @@ import pkg from "../package.json";
6
6
  import { runClaude } from "./claude.ts";
7
7
  import { runHook } from "./commands/hook.ts";
8
8
  import { Setup } from "./commands/setup.tsx";
9
+ import { runWorker } from "./commands/worker.ts";
9
10
 
10
11
  // Get CLI name from bin field (assumes single key)
11
12
  const binName = Object.keys(pkg.bin)[0];
@@ -26,6 +27,8 @@ interface ParsedArgs {
26
27
  headless: boolean;
27
28
  dryRun: boolean;
28
29
  restore: boolean;
30
+ yes: boolean;
31
+ yolo: boolean;
29
32
  additionalArgs: string[];
30
33
  }
31
34
 
@@ -37,6 +40,8 @@ function parseArgs(args: string[]): ParsedArgs {
37
40
  let headless = false;
38
41
  let dryRun = false;
39
42
  let restore = false;
43
+ let yes = false;
44
+ let yolo = false;
40
45
  let additionalArgs: string[] = [];
41
46
 
42
47
  // Find if there's a "--" separator for additional args
@@ -61,10 +66,14 @@ function parseArgs(args: string[]): ParsedArgs {
61
66
  dryRun = true;
62
67
  } else if (arg === "--restore") {
63
68
  restore = true;
69
+ } else if (arg === "-y" || arg === "--yes") {
70
+ yes = true;
71
+ } else if (arg === "--yolo") {
72
+ yolo = true;
64
73
  }
65
74
  }
66
75
 
67
- return { command, port, key, msg, headless, dryRun, restore, additionalArgs };
76
+ return { command, port, key, msg, headless, dryRun, restore, yes, yolo, additionalArgs };
68
77
  }
69
78
 
70
79
  function Help() {
@@ -121,6 +130,12 @@ function Help() {
121
130
  </Box>
122
131
  <Text>Run Claude CLI</Text>
123
132
  </Box>
133
+ <Box>
134
+ <Box width={12}>
135
+ <Text color="green">worker</Text>
136
+ </Box>
137
+ <Text>Run Claude in headless loop mode</Text>
138
+ </Box>
124
139
  <Box>
125
140
  <Box width={12}>
126
141
  <Text color="green">version</Text>
@@ -149,6 +164,12 @@ function Help() {
149
164
  </Box>
150
165
  <Text>Restore files from .bak backups</Text>
151
166
  </Box>
167
+ <Box>
168
+ <Box width={24}>
169
+ <Text color="yellow">-y, --yes</Text>
170
+ </Box>
171
+ <Text>Non-interactive mode (use env vars)</Text>
172
+ </Box>
152
173
  </Box>
153
174
 
154
175
  <Box marginTop={1} flexDirection="column">
@@ -189,38 +210,82 @@ function Help() {
189
210
  </Box>
190
211
  </Box>
191
212
 
213
+ <Box marginTop={1} flexDirection="column">
214
+ <Text bold>Options for 'worker':</Text>
215
+ <Box>
216
+ <Box width={24}>
217
+ <Text color="yellow">-m, --msg {"<prompt>"}</Text>
218
+ </Box>
219
+ <Text>Custom prompt (default: /agent-swarm:start-worker)</Text>
220
+ </Box>
221
+ <Box>
222
+ <Box width={24}>
223
+ <Text color="yellow">--yolo</Text>
224
+ </Box>
225
+ <Text>Continue on errors instead of stopping</Text>
226
+ </Box>
227
+ <Box>
228
+ <Box width={24}>
229
+ <Text color="yellow">-- {"<args...>"}</Text>
230
+ </Box>
231
+ <Text>Additional arguments to pass to Claude CLI</Text>
232
+ </Box>
233
+ </Box>
234
+
192
235
  <Box marginTop={1} flexDirection="column">
193
236
  <Text bold>Examples:</Text>
194
237
  <Text dimColor> {binName} setup</Text>
195
238
  <Text dimColor> {binName} setup --dry-run</Text>
239
+ <Text dimColor> {binName} setup -y</Text>
196
240
  <Text dimColor> {binName} mcp</Text>
197
241
  <Text dimColor> {binName} mcp --port 8080</Text>
198
242
  <Text dimColor> {binName} mcp -p 8080 -k my-secret-key</Text>
199
243
  <Text dimColor> {binName} claude</Text>
200
244
  <Text dimColor> {binName} claude --headless -m "Hello"</Text>
201
245
  <Text dimColor> {binName} claude -- --resume</Text>
246
+ <Text dimColor> {binName} worker</Text>
247
+ <Text dimColor> {binName} worker --yolo</Text>
248
+ <Text dimColor> {binName} worker -m "Custom prompt"</Text>
202
249
  </Box>
203
250
 
204
251
  <Box marginTop={1} flexDirection="column">
205
252
  <Text bold>Environment variables:</Text>
206
253
  <Box>
207
- <Box width={16}>
254
+ <Box width={24}>
208
255
  <Text color="magenta">PORT</Text>
209
256
  </Box>
210
257
  <Text>Default port for the MCP server</Text>
211
258
  </Box>
212
259
  <Box>
213
- <Box width={16}>
260
+ <Box width={24}>
214
261
  <Text color="magenta">API_KEY</Text>
215
262
  </Box>
216
263
  <Text>API key for authentication (Bearer token)</Text>
217
264
  </Box>
218
265
  <Box>
219
- <Box width={16}>
266
+ <Box width={24}>
220
267
  <Text color="magenta">MCP_BASE_URL</Text>
221
268
  </Box>
222
269
  <Text>Base URL for the MCP server (used by setup)</Text>
223
270
  </Box>
271
+ <Box>
272
+ <Box width={24}>
273
+ <Text color="magenta">AGENT_ID</Text>
274
+ </Box>
275
+ <Text>UUID for agent identification</Text>
276
+ </Box>
277
+ <Box>
278
+ <Box width={24}>
279
+ <Text color="magenta">SESSION_ID</Text>
280
+ </Box>
281
+ <Text>Folder name for worker logs (auto-generated)</Text>
282
+ </Box>
283
+ <Box>
284
+ <Box width={24}>
285
+ <Text color="magenta">WORKER_YOLO</Text>
286
+ </Box>
287
+ <Text>If "true", worker continues on errors</Text>
288
+ </Box>
224
289
  </Box>
225
290
  </Box>
226
291
  );
@@ -298,6 +363,27 @@ function ClaudeRunner({ msg, headless, additionalArgs }: ClaudeRunnerProps) {
298
363
  return null;
299
364
  }
300
365
 
366
+ interface WorkerRunnerProps {
367
+ prompt: string;
368
+ yolo: boolean;
369
+ additionalArgs: string[];
370
+ }
371
+
372
+ function WorkerRunner({ prompt, yolo, additionalArgs }: WorkerRunnerProps) {
373
+ const { exit } = useApp();
374
+
375
+ useEffect(() => {
376
+ runWorker({
377
+ prompt: prompt || undefined,
378
+ yolo,
379
+ additionalArgs,
380
+ }).catch((err) => exit(err));
381
+ // Note: runWorker runs indefinitely, so we don't call exit() on success
382
+ }, [prompt, yolo, additionalArgs, exit]);
383
+
384
+ return null;
385
+ }
386
+
301
387
  function UnknownCommand({ command }: { command: string }) {
302
388
  const { exit } = useApp();
303
389
  useEffect(() => {
@@ -328,15 +414,17 @@ function Version() {
328
414
  }
329
415
 
330
416
  function App({ args }: { args: ParsedArgs }) {
331
- const { command, port, key, msg, headless, dryRun, restore, additionalArgs } = args;
417
+ const { command, port, key, msg, headless, dryRun, restore, yes, yolo, additionalArgs } = args;
332
418
 
333
419
  switch (command) {
334
420
  case "setup":
335
- return <Setup dryRun={dryRun} restore={restore} />;
421
+ return <Setup dryRun={dryRun} restore={restore} yes={yes} />;
336
422
  case "mcp":
337
423
  return <McpServer port={port} apiKey={key} />;
338
424
  case "claude":
339
425
  return <ClaudeRunner msg={msg} headless={headless} additionalArgs={additionalArgs} />;
426
+ case "worker":
427
+ return <WorkerRunner prompt={msg} yolo={yolo} additionalArgs={additionalArgs} />;
340
428
  case "version":
341
429
  return <Version />;
342
430
  case "help":
@@ -351,7 +439,7 @@ const args = parseArgs(process.argv.slice(2));
351
439
 
352
440
  // Handle hook command separately (no UI needed)
353
441
  if (args.command === "hook") {
354
- runHook();
442
+ runHook();
355
443
  } else {
356
- render(<App args={args} />);
444
+ render(<App args={args} />);
357
445
  }
@@ -1,6 +1,6 @@
1
1
  import { handleHook } from "../hooks/hook";
2
2
 
3
3
  export async function runHook(): Promise<void> {
4
- await handleHook();
5
- process.exit(0);
4
+ await handleHook();
5
+ process.exit(0);
6
6
  }