claude-b 0.3.2

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.
@@ -0,0 +1,1643 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ TelegramConfigManager,
4
+ detectClaude,
5
+ envTemplate,
6
+ getDataDir,
7
+ loadEnv
8
+ } from "../chunk-6D3ICY25.js";
9
+
10
+ // src/cli/index.ts
11
+ import { Command } from "commander";
12
+ import chalk2 from "chalk";
13
+
14
+ // src/daemon/client.ts
15
+ import { createConnection } from "net";
16
+ import { homedir } from "os";
17
+ import { EventEmitter } from "events";
18
+ var DaemonClient = class extends EventEmitter {
19
+ socket = null;
20
+ socketPath;
21
+ messageId = 0;
22
+ pendingRequests = /* @__PURE__ */ new Map();
23
+ buffer = "";
24
+ constructor(socketPath) {
25
+ super();
26
+ this.socketPath = socketPath || `${homedir()}/.claude-b/daemon.sock`;
27
+ }
28
+ async connect() {
29
+ if (this.socket?.writable) {
30
+ return this.socket;
31
+ }
32
+ return new Promise((resolve, reject) => {
33
+ const socket = createConnection(this.socketPath);
34
+ socket.on("connect", () => {
35
+ this.socket = socket;
36
+ resolve(socket);
37
+ });
38
+ socket.on("data", (data) => {
39
+ this.handleData(data.toString());
40
+ });
41
+ socket.on("error", (error) => {
42
+ this.socket = null;
43
+ reject(error);
44
+ });
45
+ socket.on("close", () => {
46
+ this.socket = null;
47
+ this.emit("close");
48
+ });
49
+ });
50
+ }
51
+ handleData(data) {
52
+ this.buffer += data;
53
+ const lines = this.buffer.split("\n");
54
+ this.buffer = lines.pop() || "";
55
+ for (const line of lines) {
56
+ if (!line.trim()) continue;
57
+ try {
58
+ const message = JSON.parse(line);
59
+ if (message.type === "output") {
60
+ this.emit("output", message.data);
61
+ continue;
62
+ }
63
+ if (message.type === "status") {
64
+ this.emit("status", message.data);
65
+ continue;
66
+ }
67
+ if (message.id !== void 0) {
68
+ const pending = this.pendingRequests.get(message.id);
69
+ if (pending) {
70
+ this.pendingRequests.delete(message.id);
71
+ pending.resolve(message);
72
+ }
73
+ }
74
+ } catch {
75
+ }
76
+ }
77
+ }
78
+ async send(message) {
79
+ const socket = await this.connect();
80
+ const id = ++this.messageId;
81
+ return new Promise((resolve, reject) => {
82
+ this.pendingRequests.set(id, { resolve, reject });
83
+ const payload = JSON.stringify({ ...message, id }) + "\n";
84
+ socket.write(payload, (error) => {
85
+ if (error) {
86
+ this.pendingRequests.delete(id);
87
+ reject(error);
88
+ }
89
+ });
90
+ setTimeout(() => {
91
+ if (this.pendingRequests.has(id)) {
92
+ this.pendingRequests.delete(id);
93
+ reject(new Error("Request timeout"));
94
+ }
95
+ }, 3e4);
96
+ });
97
+ }
98
+ async attach(sessionId) {
99
+ const socket = await this.connect();
100
+ await this.send({ method: "session.attach", params: { sessionId } });
101
+ process.stdout.on("resize", () => {
102
+ this.send({ method: "terminal.resize", params: {
103
+ cols: process.stdout.columns,
104
+ rows: process.stdout.rows
105
+ } }).catch(() => {
106
+ });
107
+ });
108
+ this.on("output", (data) => {
109
+ process.stdout.write(data.content);
110
+ });
111
+ process.stdin.setRawMode?.(true);
112
+ process.stdin.resume();
113
+ process.stdin.on("data", (data) => {
114
+ if (data.toString() === "") {
115
+ this.send({ method: "session.detach" }).finally(() => {
116
+ process.stdin.setRawMode?.(false);
117
+ process.exit(0);
118
+ });
119
+ return;
120
+ }
121
+ socket.write(JSON.stringify({ method: "stdin", params: { data: data.toString() } }) + "\n");
122
+ });
123
+ await new Promise((resolve) => {
124
+ this.on("close", resolve);
125
+ });
126
+ }
127
+ async watch() {
128
+ await this.connect();
129
+ await this.send({ method: "session.watch" });
130
+ this.on("output", (data) => {
131
+ process.stdout.write(data.content);
132
+ });
133
+ this.on("status", (data) => {
134
+ if (data.status === "processing") {
135
+ process.stdout.write(`\x1B[33m[Processing prompt ${data.promptId || ""}...]\x1B[0m
136
+ `);
137
+ } else if (data.status === "completed") {
138
+ process.stdout.write(`\x1B[32m[Completed]\x1B[0m
139
+ `);
140
+ } else if (data.status === "error") {
141
+ process.stdout.write(`\x1B[31m[Error]\x1B[0m
142
+ `);
143
+ }
144
+ });
145
+ process.on("SIGINT", () => {
146
+ this.send({ method: "session.unwatch" }).finally(() => {
147
+ process.exit(0);
148
+ });
149
+ });
150
+ await new Promise((resolve) => {
151
+ this.on("close", resolve);
152
+ });
153
+ }
154
+ close() {
155
+ this.socket?.end();
156
+ this.socket = null;
157
+ }
158
+ };
159
+
160
+ // src/utils/version.ts
161
+ var version = "0.1.0";
162
+
163
+ // src/cli/init.ts
164
+ import { createInterface } from "readline";
165
+ import { writeFile, mkdir, readFile, chmod } from "fs/promises";
166
+ import { existsSync } from "fs";
167
+ import { join } from "path";
168
+ import chalk from "chalk";
169
+ function createPrompts() {
170
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
171
+ function ask(question, opts = {}) {
172
+ const suffix = opts.default ? chalk.gray(` [${opts.default}]`) : "";
173
+ return new Promise((resolve) => {
174
+ rl.question(`${question}${suffix} `, (answer) => {
175
+ resolve(answer.trim() || opts.default || "");
176
+ });
177
+ });
178
+ }
179
+ async function confirm(question, defaultYes) {
180
+ const hint = defaultYes ? "Y/n" : "y/N";
181
+ const answer = (await ask(`${question} ${chalk.gray(`(${hint})`)}`)).toLowerCase();
182
+ if (!answer) return defaultYes;
183
+ return answer.startsWith("y");
184
+ }
185
+ return { ask, confirm, close: () => rl.close() };
186
+ }
187
+ async function pingTelegramBot(token) {
188
+ try {
189
+ const res = await fetch(`https://api.telegram.org/bot${token}/getMe`);
190
+ const json = await res.json();
191
+ if (!json.ok) return { ok: false, error: json.description || "Invalid token" };
192
+ return { ok: true, username: json.result?.username };
193
+ } catch (err) {
194
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
195
+ }
196
+ }
197
+ async function waitForFirstChat(token, timeoutMs) {
198
+ const deadline = Date.now() + timeoutMs;
199
+ let offset = 0;
200
+ try {
201
+ const drain = await fetch(`https://api.telegram.org/bot${token}/getUpdates?offset=-1`);
202
+ const drainJson = await drain.json();
203
+ if (drainJson.ok && drainJson.result && drainJson.result.length > 0) {
204
+ offset = drainJson.result[drainJson.result.length - 1].update_id + 1;
205
+ }
206
+ } catch {
207
+ }
208
+ while (Date.now() < deadline) {
209
+ const remaining = Math.max(1, Math.floor((deadline - Date.now()) / 1e3));
210
+ const pollSeconds = Math.min(25, remaining);
211
+ try {
212
+ const res = await fetch(
213
+ `https://api.telegram.org/bot${token}/getUpdates?offset=${offset}&timeout=${pollSeconds}`
214
+ );
215
+ const json = await res.json();
216
+ if (!json.ok || !json.result) continue;
217
+ for (const update of json.result) {
218
+ offset = update.update_id + 1;
219
+ const chat = update.message?.chat;
220
+ if (chat && (chat.type === "private" || chat.type === "group" || chat.type === "supergroup")) {
221
+ return String(chat.id);
222
+ }
223
+ }
224
+ } catch {
225
+ }
226
+ }
227
+ return null;
228
+ }
229
+ async function sendTelegramMessage(token, chatId, text) {
230
+ try {
231
+ await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
232
+ method: "POST",
233
+ headers: { "Content-Type": "application/json" },
234
+ body: JSON.stringify({ chat_id: chatId, text, parse_mode: "HTML" })
235
+ });
236
+ } catch {
237
+ }
238
+ }
239
+ function printBanner() {
240
+ console.log("");
241
+ console.log(chalk.bold.cyan(" \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E"));
242
+ console.log(chalk.bold.cyan(" \u2502 Claude-B setup wizard \u2502"));
243
+ console.log(chalk.bold.cyan(" \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F"));
244
+ console.log("");
245
+ console.log(chalk.gray(" Writes config to ~/.claude-b/.env and optionally"));
246
+ console.log(chalk.gray(" configures a Telegram bot for remote control."));
247
+ console.log("");
248
+ }
249
+ function printBotFatherScript() {
250
+ console.log("");
251
+ console.log(chalk.bold(" Step 1: create a Telegram bot"));
252
+ console.log("");
253
+ console.log(` 1. Open Telegram and message ${chalk.cyan("@BotFather")}`);
254
+ console.log(` 2. Send ${chalk.yellow("/newbot")}`);
255
+ console.log(` 3. Pick a name, e.g. ${chalk.gray('"My Claude-B"')}`);
256
+ console.log(` 4. Pick a username ending in ${chalk.gray('"bot"')}, e.g. ${chalk.gray('"my_claude_b_bot"')}`);
257
+ console.log(` 5. BotFather replies with a token \u2014 copy it (looks like ${chalk.gray("123456:ABC...")})`);
258
+ console.log("");
259
+ }
260
+ function mergeEnvFile(existing, updates) {
261
+ const lines = existing.split(/\r?\n/);
262
+ const seen = /* @__PURE__ */ new Set();
263
+ const out = lines.map((line) => {
264
+ const match = line.match(/^\s*#?\s*([A-Z_][A-Z0-9_]*)\s*=/);
265
+ if (!match) return line;
266
+ const key = match[1];
267
+ if (!(key in updates)) return line;
268
+ seen.add(key);
269
+ return `${key}=${updates[key]}`;
270
+ });
271
+ const missing = Object.entries(updates).filter(([k]) => !seen.has(k));
272
+ if (missing.length > 0) {
273
+ if (out.length && out[out.length - 1] !== "") out.push("");
274
+ out.push("# Added by `cb init`");
275
+ for (const [k, v] of missing) out.push(`${k}=${v}`);
276
+ out.push("");
277
+ }
278
+ return out.join("\n");
279
+ }
280
+ async function runInit() {
281
+ printBanner();
282
+ const dataDir = getDataDir();
283
+ await mkdir(dataDir, { recursive: true });
284
+ const envPath = join(dataDir, ".env");
285
+ const firstRun = !existsSync(envPath);
286
+ if (!firstRun) {
287
+ console.log(chalk.yellow(` ~/.claude-b/.env already exists \u2014 updating in place.`));
288
+ console.log("");
289
+ }
290
+ const prompts = createPrompts();
291
+ const collected = {};
292
+ try {
293
+ const existingAnthropic = process.env.ANTHROPIC_API_KEY;
294
+ if (existingAnthropic) {
295
+ console.log(chalk.green(` \u2713 ANTHROPIC_API_KEY already set (${existingAnthropic.slice(0, 8)}\u2026)`));
296
+ } else {
297
+ console.log(chalk.bold(" Anthropic API key"));
298
+ console.log(chalk.gray(" Get one at https://console.anthropic.com/settings/keys"));
299
+ const key = await prompts.ask(" Paste ANTHROPIC_API_KEY (or empty to skip):");
300
+ if (key) collected.ANTHROPIC_API_KEY = key;
301
+ console.log("");
302
+ }
303
+ const wantTelegram = await prompts.confirm(" Set up Telegram bot for remote control?", true);
304
+ console.log("");
305
+ let telegramToken;
306
+ let telegramChatId;
307
+ if (wantTelegram) {
308
+ printBotFatherScript();
309
+ while (true) {
310
+ const token = await prompts.ask(" Paste bot token:");
311
+ if (!token) {
312
+ console.log(chalk.yellow(" Skipped."));
313
+ break;
314
+ }
315
+ console.log(chalk.gray(" Verifying token..."));
316
+ const ping = await pingTelegramBot(token);
317
+ if (!ping.ok) {
318
+ console.log(chalk.red(` \u2717 ${ping.error}`));
319
+ const retry = await prompts.confirm(" Try another token?", true);
320
+ if (!retry) break;
321
+ continue;
322
+ }
323
+ console.log(chalk.green(` \u2713 Connected as @${ping.username}`));
324
+ telegramToken = token;
325
+ console.log("");
326
+ console.log(chalk.bold(" Step 2: register your chat"));
327
+ console.log(chalk.gray(` Open @${ping.username} in Telegram and send ${chalk.yellow("/start")}.`));
328
+ console.log(chalk.gray(" Waiting up to 3 minutes for your first message..."));
329
+ console.log("");
330
+ const chatId = await waitForFirstChat(token, 3 * 60 * 1e3);
331
+ if (!chatId) {
332
+ console.log(chalk.yellow(" Timed out waiting for /start."));
333
+ console.log(chalk.gray(" You can run `cb --telegram <token>` later to retry."));
334
+ break;
335
+ }
336
+ telegramChatId = chatId;
337
+ console.log(chalk.green(` \u2713 Registered chat id ${chatId}`));
338
+ await sendTelegramMessage(
339
+ token,
340
+ chatId,
341
+ "<b>Claude-B</b> is connected \u{1F389}\nRun <code>cb --telegram-status</code> locally to confirm."
342
+ );
343
+ break;
344
+ }
345
+ console.log("");
346
+ }
347
+ if (wantTelegram) {
348
+ const wantVoice = await prompts.confirm(
349
+ " Enable voice notes (Whisper STT + TTS playback)?",
350
+ false
351
+ );
352
+ console.log("");
353
+ if (wantVoice) {
354
+ const existingOpenAI = process.env.OPENAI_API_KEY;
355
+ if (existingOpenAI) {
356
+ console.log(chalk.green(` \u2713 OPENAI_API_KEY already set`));
357
+ } else {
358
+ const key = await prompts.ask(" Paste OPENAI_API_KEY (or empty to skip):");
359
+ if (key) collected.OPENAI_API_KEY = key;
360
+ }
361
+ console.log("");
362
+ }
363
+ }
364
+ if (telegramToken) {
365
+ const tgConfig = new TelegramConfigManager(dataDir);
366
+ await tgConfig.load();
367
+ await tgConfig.setToken(telegramToken);
368
+ if (telegramChatId) await tgConfig.addChatId(telegramChatId);
369
+ if (collected.OPENAI_API_KEY || process.env.OPENAI_API_KEY) {
370
+ const openaiKey = collected.OPENAI_API_KEY || process.env.OPENAI_API_KEY;
371
+ await tgConfig.setSTTProvider({ provider: "openai", apiKey: openaiKey });
372
+ await tgConfig.setAIProvider({ provider: "anthropic", apiKey: collected.ANTHROPIC_API_KEY || process.env.ANTHROPIC_API_KEY || "" });
373
+ }
374
+ collected.TELEGRAM_BOT_TOKEN = telegramToken;
375
+ if (telegramChatId) collected.TELEGRAM_ALLOWED_CHAT_IDS = telegramChatId;
376
+ }
377
+ let existingEnv = "";
378
+ if (existsSync(envPath)) {
379
+ existingEnv = await readFile(envPath, "utf-8");
380
+ } else {
381
+ existingEnv = envTemplate();
382
+ }
383
+ const merged = mergeEnvFile(existingEnv, collected);
384
+ await writeFile(envPath, merged);
385
+ await chmod(envPath, 384);
386
+ console.log(chalk.bold.green(` \u2713 Wrote ${envPath}`));
387
+ console.log("");
388
+ console.log(chalk.bold(" Next steps"));
389
+ console.log(` \u2022 Start the daemon: ${chalk.yellow("cb")} ${chalk.gray('"hello"')} ${chalk.gray("(auto-starts on first use)")}`);
390
+ if (telegramToken) {
391
+ console.log(` \u2022 Telegram bot: ${chalk.yellow("cb --telegram-status")}`);
392
+ }
393
+ console.log(` \u2022 REST API: ${chalk.yellow("cb -r")}`);
394
+ console.log("");
395
+ } finally {
396
+ prompts.close();
397
+ }
398
+ }
399
+
400
+ // src/cli/index.ts
401
+ loadEnv();
402
+ var program = new Command();
403
+ program.name("cb").description("Claude-B: Background Claude Code with async workflows").version(version);
404
+ program.command("init").description("Interactive setup: writes ~/.claude-b/.env, connects Telegram bot").action(async () => {
405
+ try {
406
+ await runInit();
407
+ process.exit(0);
408
+ } catch (err) {
409
+ console.error(chalk2.red(err instanceof Error ? err.message : String(err)));
410
+ process.exit(1);
411
+ }
412
+ });
413
+ program.argument("[prompt...]", "Prompt to send to Claude").option("-l, --last", "Show status and output of last prompt").option("-s, --sess", "List all sessions").option("-a, --attach <id>", "Attach to session (foreground mode)").option("-d, --detach", "Detach from current session").option("-n, --new [name]", "Create new session").option("-m, --model <model>", "Claude model to use").option("-k, --kill <id>", "Kill/terminate session").option("-w, --watch", "Watch live output (tail -f style)").option("-x, --select <id>", "Select session for subsequent commands").option("-c, --current", "Show current selected session").option("-r, --rest [port]", "Start REST API server").option("--rest-stop", "Stop REST API server").option("--status", "Daemon status and health").option("--logs", "View daemon logs").option("--hook <event> <cmd>", "Register shell hook for event").option("--hook-session <id>", "Only trigger hook for this session (use with --hook)").option("--unhook <id>", "Remove a shell hook").option("--hooks", "List all shell hooks").option("--webhook <url>", "Register webhook for notifications").option("--webhook-event <event>", "Event type for webhook (default: *)").option("--webhook-session <id>", "Only trigger webhook for this session (use with --webhook)").option("--unwebhook <id>", "Remove a webhook").option("--webhooks", "List all webhooks").option("--hook-stats", "Show hook statistics").option("--api-key", "Generate/show API key for REST access").option("--config", "Edit configuration").option("--export <id>", "Export session transcript").option("--import <file>", "Import session from file").option("--remote-add <url>", "Add a remote Claude-B host").option("--remote-key <apiKey>", "API key for remote host (use with --remote-add)").option("--remote-name <name>", "Name for remote host (use with --remote-add)").option("--remote-priority <n>", "Priority for remote host (use with --remote-add)").option("--remote-remove <id>", "Remove a remote host").option("--remote-toggle <id>", "Toggle remote host enabled/disabled").option("--remote-hosts", "List all remote hosts").option("--remote-health", "Show health status of remote hosts").option("--remote-stats", "Show orchestration statistics").option("--remote <hostId>", "Send prompt to specific remote host").option("-f, --fire", "Fire and forget (launch task in background, no watching)").option("-g, --goal <description>", "Goal/objective for fire-and-forget task (use with -f)").option("-i, --inbox", "Show notification inbox (completed tasks)").option("--inbox-clear", "Mark all notifications as read").option("--inbox-count", "Show unread notification count").option("--remote-fire <hostId>", "Fire and forget to remote host").option("--telegram <token>", "Set up Telegram bot with token").option("--telegram-stop", "Disable Telegram notifications").option("--telegram-status", "Show Telegram bot status").option("--telegram-forward <on|off>", "Toggle forwarding all session completions to Telegram").option("--voice-setup <provider>", "Configure STT provider: speechmatics, deepgram, or openai").option("--ai-provider <config>", 'Set AI provider: "anthropic <key>" or "openrouter <key>"').option("--voice-status", "Show voice pipeline status").option("--shell-init", "Output shell hook for inbox notifications (add to .bashrc/.zshrc)").action(async (promptParts, options) => {
414
+ if (options.shellInit) {
415
+ printShellInit();
416
+ return;
417
+ }
418
+ const client = new DaemonClient();
419
+ try {
420
+ if (options.last) {
421
+ await showLastOutput(client);
422
+ return;
423
+ }
424
+ if (options.sess) {
425
+ await listSessions(client);
426
+ return;
427
+ }
428
+ if (options.attach) {
429
+ await attachSession(client, options.attach);
430
+ return;
431
+ }
432
+ if (options.detach) {
433
+ await detachSession(client);
434
+ return;
435
+ }
436
+ if (options.new !== void 0) {
437
+ const name = typeof options.new === "string" ? options.new : void 0;
438
+ await createSession(client, name, options.model);
439
+ return;
440
+ }
441
+ if (options.kill) {
442
+ await killSession(client, options.kill);
443
+ return;
444
+ }
445
+ if (options.watch) {
446
+ await watchSession(client);
447
+ return;
448
+ }
449
+ if (options.select) {
450
+ await selectSession(client, options.select);
451
+ return;
452
+ }
453
+ if (options.current) {
454
+ await showCurrentSession(client);
455
+ return;
456
+ }
457
+ if (options.rest !== void 0) {
458
+ const port = typeof options.rest === "string" ? parseInt(options.rest, 10) : 3847;
459
+ await startRestServer(client, port);
460
+ return;
461
+ }
462
+ if (options.restStop) {
463
+ await stopRestServer(client);
464
+ return;
465
+ }
466
+ if (options.apiKey) {
467
+ await showApiKey(client);
468
+ return;
469
+ }
470
+ if (options.status) {
471
+ await showStatus(client);
472
+ return;
473
+ }
474
+ if (options.logs) {
475
+ await showLogs();
476
+ return;
477
+ }
478
+ if (options.hook) {
479
+ const args = promptParts;
480
+ if (args.length < 1) {
481
+ console.error(chalk2.red("Usage: cb --hook <event> <command>"));
482
+ console.error(chalk2.gray("Events: session.created, session.destroyed, prompt.received, prompt.completed, prompt.error, daemon.started, daemon.stopped, rest.started, rest.stopped, * (all)"));
483
+ console.error(chalk2.gray("Use --hook-session <id> to filter by session"));
484
+ return;
485
+ }
486
+ const event = options.hook;
487
+ const command = args.join(" ");
488
+ await addShellHook(client, event, command, options.hookSession);
489
+ return;
490
+ }
491
+ if (options.unhook) {
492
+ await removeShellHook(client, options.unhook);
493
+ return;
494
+ }
495
+ if (options.hooks) {
496
+ await listShellHooks(client);
497
+ return;
498
+ }
499
+ if (options.webhook) {
500
+ const event = options.webhookEvent || "*";
501
+ await addWebhook(client, event, options.webhook, options.webhookSession);
502
+ return;
503
+ }
504
+ if (options.unwebhook) {
505
+ await removeWebhook(client, options.unwebhook);
506
+ return;
507
+ }
508
+ if (options.webhooks) {
509
+ await listWebhooks(client);
510
+ return;
511
+ }
512
+ if (options.hookStats) {
513
+ await showHookStats(client);
514
+ return;
515
+ }
516
+ if (options.remoteAdd) {
517
+ if (!options.remoteKey) {
518
+ console.error(chalk2.red("API key required. Use --remote-key <apiKey>"));
519
+ return;
520
+ }
521
+ await addRemoteHost(client, options.remoteAdd, options.remoteKey, {
522
+ name: options.remoteName,
523
+ priority: options.remotePriority ? parseInt(options.remotePriority, 10) : void 0
524
+ });
525
+ return;
526
+ }
527
+ if (options.remoteRemove) {
528
+ await removeRemoteHost(client, options.remoteRemove);
529
+ return;
530
+ }
531
+ if (options.remoteToggle) {
532
+ await toggleRemoteHost(client, options.remoteToggle);
533
+ return;
534
+ }
535
+ if (options.remoteHosts) {
536
+ await listRemoteHosts(client);
537
+ return;
538
+ }
539
+ if (options.remoteHealth) {
540
+ await showRemoteHealth(client);
541
+ return;
542
+ }
543
+ if (options.remoteStats) {
544
+ await showOrchestrationStats(client);
545
+ return;
546
+ }
547
+ if (options.remote) {
548
+ if (promptParts.length === 0) {
549
+ console.error(chalk2.red("Prompt required for remote execution"));
550
+ return;
551
+ }
552
+ const prompt = promptParts.join(" ");
553
+ await sendRemotePrompt(client, options.remote, prompt);
554
+ return;
555
+ }
556
+ if (options.inbox) {
557
+ await showInbox(client);
558
+ return;
559
+ }
560
+ if (options.inboxClear) {
561
+ await clearInbox(client);
562
+ return;
563
+ }
564
+ if (options.inboxCount) {
565
+ await showInboxCount(client);
566
+ return;
567
+ }
568
+ if (options.telegram) {
569
+ await setupTelegram(client, options.telegram);
570
+ return;
571
+ }
572
+ if (options.telegramStop) {
573
+ await stopTelegram(client);
574
+ return;
575
+ }
576
+ if (options.telegramStatus) {
577
+ await showTelegramStatus(client);
578
+ return;
579
+ }
580
+ if (options.telegramForward) {
581
+ const enabled = options.telegramForward === "on";
582
+ await setTelegramForward(client, enabled);
583
+ return;
584
+ }
585
+ if (options.voiceSetup) {
586
+ const apiKey = promptParts[0];
587
+ if (!apiKey) {
588
+ console.error(chalk2.red("Usage: cb --voice-setup <speechmatics|deepgram|openai> <api-key>"));
589
+ process.exit(1);
590
+ }
591
+ await setupVoicePipeline(client, options.voiceSetup, apiKey);
592
+ return;
593
+ }
594
+ if (options.aiProvider) {
595
+ const provider = options.aiProvider;
596
+ const apiKey = promptParts[0];
597
+ const model = promptParts[1];
598
+ if (!apiKey) {
599
+ console.error(chalk2.red("Usage: cb --ai-provider <anthropic|openrouter> <api-key> [model]"));
600
+ process.exit(1);
601
+ }
602
+ await setupVoiceAI(client, provider, apiKey, model);
603
+ return;
604
+ }
605
+ if (options.voiceStatus) {
606
+ await showVoiceStatus(client);
607
+ return;
608
+ }
609
+ if (options.remoteFire) {
610
+ if (promptParts.length === 0) {
611
+ console.error(chalk2.red("Prompt required for remote fire-and-forget"));
612
+ return;
613
+ }
614
+ const prompt = promptParts.join(" ");
615
+ await fireRemotePrompt(client, options.remoteFire, prompt, options.goal);
616
+ return;
617
+ }
618
+ if (promptParts.length > 0) {
619
+ const prompt = promptParts.join(" ");
620
+ if (options.fire) {
621
+ await fireAndForgetPrompt(client, prompt, options.goal);
622
+ } else {
623
+ await sendPrompt(client, prompt, options.model);
624
+ }
625
+ return;
626
+ }
627
+ program.help();
628
+ } catch (error) {
629
+ if (error instanceof Error) {
630
+ if (error.message.includes("ECONNREFUSED") || error.message.includes("ENOENT")) {
631
+ console.error(chalk2.yellow("Daemon not running. Starting daemon..."));
632
+ await startDaemon();
633
+ await waitForDaemon();
634
+ console.log(chalk2.green("Daemon started. Retrying..."));
635
+ await program.parseAsync(process.argv);
636
+ return;
637
+ } else {
638
+ console.error(chalk2.red(`Error: ${error.message}`));
639
+ }
640
+ }
641
+ process.exit(1);
642
+ }
643
+ });
644
+ async function showLastOutput(client) {
645
+ const result = await client.send({ method: "session.last" });
646
+ client.close();
647
+ if (result.error) {
648
+ console.error(chalk2.red(result.error));
649
+ process.exit(1);
650
+ }
651
+ const data = result.data;
652
+ if (!data) {
653
+ console.log(chalk2.gray("No output available"));
654
+ process.exit(0);
655
+ }
656
+ console.log(chalk2.bold(`Session: ${data.sessionId}`));
657
+ console.log(chalk2.gray(`Status: ${data.status}`));
658
+ console.log("");
659
+ console.log(data.output);
660
+ process.exit(0);
661
+ }
662
+ async function listSessions(client) {
663
+ const result = await client.send({ method: "session.list" });
664
+ client.close();
665
+ if (result.error) {
666
+ console.error(chalk2.red(result.error));
667
+ process.exit(1);
668
+ }
669
+ const data = result.data;
670
+ const sessions = data?.sessions || [];
671
+ if (sessions.length === 0) {
672
+ console.log(chalk2.gray("No active sessions"));
673
+ process.exit(0);
674
+ }
675
+ console.log(chalk2.bold("Sessions:"));
676
+ for (const session of sessions) {
677
+ const marker = session.selected ? chalk2.green("*") : " ";
678
+ const status = session.status === "busy" ? chalk2.yellow("busy") : chalk2.gray("idle");
679
+ console.log(`${marker} ${chalk2.cyan(session.id)} ${session.name ? chalk2.gray(`(${session.name})`) : ""} [${status}]`);
680
+ }
681
+ process.exit(0);
682
+ }
683
+ async function attachSession(client, sessionId) {
684
+ console.log(chalk2.green(`Attaching to session ${sessionId}...`));
685
+ console.log(chalk2.gray("Press Ctrl+D to detach"));
686
+ await client.attach(sessionId);
687
+ }
688
+ async function detachSession(client) {
689
+ await client.send({ method: "session.detach" });
690
+ client.close();
691
+ console.log(chalk2.green("Detached from session"));
692
+ process.exit(0);
693
+ }
694
+ async function createSession(client, name, model) {
695
+ const result = await client.send({ method: "session.create", params: { name, model } });
696
+ client.close();
697
+ if (result.error) {
698
+ console.error(chalk2.red(result.error));
699
+ process.exit(1);
700
+ }
701
+ const data = result.data;
702
+ console.log(chalk2.green(`Created session: ${data?.sessionId}`));
703
+ if (name) {
704
+ console.log(chalk2.gray(`Name: ${name}`));
705
+ }
706
+ if (model) {
707
+ console.log(chalk2.gray(`Model: ${model}`));
708
+ }
709
+ process.exit(0);
710
+ }
711
+ async function killSession(client, sessionId) {
712
+ const result = await client.send({ method: "session.kill", params: { sessionId } });
713
+ client.close();
714
+ if (result.error) {
715
+ console.error(chalk2.red(result.error));
716
+ process.exit(1);
717
+ }
718
+ console.log(chalk2.green(`Killed session: ${sessionId}`));
719
+ process.exit(0);
720
+ }
721
+ async function watchSession(client) {
722
+ console.log(chalk2.gray("Watching session output (Ctrl+C to stop)..."));
723
+ await client.watch();
724
+ }
725
+ async function selectSession(client, sessionId) {
726
+ const result = await client.send({ method: "session.select", params: { sessionId } });
727
+ client.close();
728
+ if (result.error) {
729
+ console.error(chalk2.red(result.error));
730
+ process.exit(1);
731
+ }
732
+ console.log(chalk2.green(`Selected session: ${sessionId}`));
733
+ process.exit(0);
734
+ }
735
+ async function showCurrentSession(client) {
736
+ const result = await client.send({ method: "session.current" });
737
+ client.close();
738
+ if (result.error) {
739
+ console.error(chalk2.red(result.error));
740
+ process.exit(1);
741
+ }
742
+ const data = result.data;
743
+ if (!data?.sessionId) {
744
+ console.log(chalk2.gray("No session selected"));
745
+ process.exit(0);
746
+ }
747
+ console.log(chalk2.cyan(data.sessionId));
748
+ if (data.name) {
749
+ console.log(chalk2.gray(`Name: ${data.name}`));
750
+ }
751
+ console.log(chalk2.gray(`Status: ${data.status}`));
752
+ process.exit(0);
753
+ }
754
+ async function startRestServer(client, port) {
755
+ console.log(chalk2.gray(`Starting REST API server on port ${port}...`));
756
+ const result = await client.send({ method: "rest.start", params: { port } });
757
+ client.close();
758
+ if (result.error) {
759
+ console.error(chalk2.red(result.error));
760
+ process.exit(1);
761
+ }
762
+ const data = result.data;
763
+ console.log(chalk2.green(`REST API server started!`));
764
+ console.log(` Address: ${chalk2.cyan(data?.address)}`);
765
+ console.log(` API Key: ${chalk2.cyan(data?.apiKey)}`);
766
+ console.log("");
767
+ console.log(chalk2.gray("Example usage:"));
768
+ console.log(chalk2.gray(` # Get token`));
769
+ console.log(chalk2.gray(` curl -X POST http://localhost:${port}/api/auth/token \\`));
770
+ console.log(chalk2.gray(` -H "Content-Type: application/json" \\`));
771
+ console.log(chalk2.gray(` -d '{"api_key": "${data?.apiKey}"}'`));
772
+ console.log("");
773
+ console.log(chalk2.gray(` # List sessions`));
774
+ console.log(chalk2.gray(` curl http://localhost:${port}/api/sessions \\`));
775
+ console.log(chalk2.gray(` -H "Authorization: Bearer <token>"`));
776
+ process.exit(0);
777
+ }
778
+ async function stopRestServer(client) {
779
+ const statusResult = await client.send({ method: "rest.status" });
780
+ const statusData = statusResult.data;
781
+ if (!statusData?.running) {
782
+ client.close();
783
+ console.log(chalk2.gray("REST server is not running."));
784
+ process.exit(0);
785
+ }
786
+ const result = await client.send({ method: "rest.stop" });
787
+ client.close();
788
+ if (result.error) {
789
+ console.error(chalk2.red(result.error));
790
+ process.exit(1);
791
+ }
792
+ console.log(chalk2.green("REST API server stopped"));
793
+ process.exit(0);
794
+ }
795
+ async function showApiKey(client) {
796
+ const statusResult = await client.send({ method: "rest.status" });
797
+ const statusData = statusResult.data;
798
+ if (!statusData?.running) {
799
+ client.close();
800
+ console.log(chalk2.yellow("REST server is not running."));
801
+ console.log(chalk2.gray("Start it with: cb -r [port]"));
802
+ process.exit(0);
803
+ }
804
+ const result = await client.send({ method: "rest.apikey" });
805
+ client.close();
806
+ if (result.error) {
807
+ console.error(chalk2.red(result.error));
808
+ process.exit(1);
809
+ }
810
+ const data = result.data;
811
+ console.log(chalk2.bold("API Key:"));
812
+ console.log(chalk2.cyan(data?.apiKey));
813
+ process.exit(0);
814
+ }
815
+ async function showStatus(client) {
816
+ const result = await client.send({ method: "daemon.status" });
817
+ client.close();
818
+ if (result.error) {
819
+ console.error(chalk2.red(result.error));
820
+ process.exit(1);
821
+ }
822
+ const data = result.data;
823
+ if (!data) {
824
+ console.error(chalk2.red("No status data available"));
825
+ process.exit(1);
826
+ }
827
+ console.log(chalk2.bold("Daemon Status:"));
828
+ console.log(` PID: ${chalk2.cyan(data.pid)}`);
829
+ console.log(` Uptime: ${chalk2.cyan(data.uptime)}`);
830
+ console.log(` Sessions: ${chalk2.cyan(data.sessionCount)}`);
831
+ console.log(` Memory: ${chalk2.cyan(data.memoryUsage)}`);
832
+ const claude = detectClaude();
833
+ if (claude) {
834
+ const version2 = claude.version ? ` v${claude.version}` : "";
835
+ console.log(` Claude: ${chalk2.green(claude.path)}${chalk2.gray(version2)} (${claude.type})`);
836
+ } else {
837
+ console.log(` Claude: ${chalk2.red("Not found")} - set CLAUDE_PATH or install Claude Code`);
838
+ }
839
+ process.exit(0);
840
+ }
841
+ async function showLogs() {
842
+ const { homedir: homedir2 } = await import("os");
843
+ const logFile = `${homedir2()}/.claude-b/daemon.log`;
844
+ console.log(chalk2.gray(`Log file: ${logFile}`));
845
+ console.log(chalk2.gray("(Log viewing implementation pending)"));
846
+ process.exit(0);
847
+ }
848
+ async function sendPrompt(client, prompt, model) {
849
+ console.log(chalk2.gray("Sending prompt..."));
850
+ const result = await client.send({ method: "prompt.send", params: { prompt, model } });
851
+ if (result.error) {
852
+ console.error(chalk2.red(result.error));
853
+ return;
854
+ }
855
+ const data = result.data;
856
+ const sessionInfo = data?.sessionId ? ` on ${chalk2.cyan(data.sessionId)}` : "";
857
+ console.log(chalk2.green(`Prompt queued (ID: ${data?.promptId})${sessionInfo}`));
858
+ console.log(chalk2.gray("Watching output (Ctrl+C to detach)...\n"));
859
+ await client.send({ method: "session.watch" });
860
+ client.on("status", (statusData) => {
861
+ if (statusData.status === "processing") {
862
+ console.log(chalk2.yellow(`[Processing prompt ${statusData.promptId || ""}...]`));
863
+ } else if (statusData.status === "completed") {
864
+ console.log(chalk2.green(`
865
+ [Completed]`));
866
+ } else if (statusData.status === "error") {
867
+ console.log(chalk2.red(`
868
+ [Error]`));
869
+ }
870
+ });
871
+ let jsonAccumulator = "";
872
+ client.on("output", (outputData) => {
873
+ jsonAccumulator += outputData.content;
874
+ try {
875
+ const parsed = JSON.parse(jsonAccumulator.trim());
876
+ if (parsed.result !== void 0) {
877
+ process.stdout.write(parsed.result);
878
+ jsonAccumulator = "";
879
+ return;
880
+ }
881
+ } catch {
882
+ }
883
+ process.stdout.write(outputData.content);
884
+ });
885
+ process.on("SIGINT", () => {
886
+ console.log(chalk2.gray('\n\nDetaching... (use "cb -l" to check result or "cb -w" to resume watching)'));
887
+ client.send({ method: "session.unwatch" }).finally(() => {
888
+ process.exit(0);
889
+ });
890
+ });
891
+ await new Promise((resolve) => {
892
+ client.on("close", resolve);
893
+ });
894
+ }
895
+ async function startDaemon() {
896
+ const { spawn } = await import("child_process");
897
+ const { homedir: homedir2 } = await import("os");
898
+ const { mkdir: mkdir2 } = await import("fs/promises");
899
+ const configDir = `${homedir2()}/.claude-b`;
900
+ await mkdir2(configDir, { recursive: true });
901
+ const daemon = spawn("node", ["dist/daemon/index.js"], {
902
+ cwd: process.cwd(),
903
+ detached: true,
904
+ stdio: "ignore"
905
+ });
906
+ daemon.unref();
907
+ }
908
+ async function waitForDaemon(maxWaitMs = 5e3) {
909
+ const interval = 200;
910
+ const maxAttempts = Math.ceil(maxWaitMs / interval);
911
+ for (let i = 0; i < maxAttempts; i++) {
912
+ try {
913
+ const testClient = new DaemonClient();
914
+ const result = await testClient.send({ method: "status" });
915
+ testClient.close();
916
+ if (result.data) return;
917
+ } catch {
918
+ }
919
+ await new Promise((resolve) => setTimeout(resolve, interval));
920
+ }
921
+ throw new Error("Daemon failed to start within timeout");
922
+ }
923
+ async function addShellHook(client, event, command, sessionFilter) {
924
+ const result = await client.send({
925
+ method: "hook.shell.add",
926
+ params: { event, command, sessionFilter }
927
+ });
928
+ client.close();
929
+ if (result.error) {
930
+ console.error(chalk2.red(result.error));
931
+ process.exit(1);
932
+ }
933
+ const data = result.data;
934
+ console.log(chalk2.green(`Shell hook added: ${data.hook?.id}`));
935
+ console.log(chalk2.gray(` Event: ${event}`));
936
+ console.log(chalk2.gray(` Command: ${command}`));
937
+ if (sessionFilter) {
938
+ console.log(chalk2.gray(` Session filter: ${sessionFilter}`));
939
+ }
940
+ process.exit(0);
941
+ }
942
+ async function removeShellHook(client, id) {
943
+ const result = await client.send({
944
+ method: "hook.shell.remove",
945
+ params: { id }
946
+ });
947
+ client.close();
948
+ if (result.error) {
949
+ console.error(chalk2.red(result.error));
950
+ process.exit(1);
951
+ }
952
+ console.log(chalk2.green(`Shell hook removed: ${id}`));
953
+ process.exit(0);
954
+ }
955
+ async function listShellHooks(client) {
956
+ const result = await client.send({ method: "hook.shell.list" });
957
+ client.close();
958
+ if (result.error) {
959
+ console.error(chalk2.red(result.error));
960
+ process.exit(1);
961
+ }
962
+ const data = result.data;
963
+ const hooks = data.hooks || [];
964
+ if (hooks.length === 0) {
965
+ console.log(chalk2.gray("No shell hooks registered"));
966
+ process.exit(0);
967
+ }
968
+ console.log(chalk2.bold("Shell Hooks:"));
969
+ for (const hook of hooks) {
970
+ const status = hook.enabled ? chalk2.green("enabled") : chalk2.gray("disabled");
971
+ console.log(` ${chalk2.cyan(hook.id)} [${status}]`);
972
+ console.log(` Event: ${chalk2.yellow(hook.event)}`);
973
+ console.log(` Command: ${chalk2.gray(hook.command)}`);
974
+ if (hook.sessionFilter) {
975
+ console.log(` Session: ${chalk2.magenta(hook.sessionFilter)}`);
976
+ }
977
+ }
978
+ process.exit(0);
979
+ }
980
+ async function addWebhook(client, event, url, sessionFilter) {
981
+ const result = await client.send({
982
+ method: "hook.webhook.add",
983
+ params: { event, url, sessionFilter }
984
+ });
985
+ client.close();
986
+ if (result.error) {
987
+ console.error(chalk2.red(result.error));
988
+ process.exit(1);
989
+ }
990
+ const data = result.data;
991
+ console.log(chalk2.green(`Webhook added: ${data.webhook?.id}`));
992
+ console.log(chalk2.gray(` Event: ${event}`));
993
+ console.log(chalk2.gray(` URL: ${url}`));
994
+ if (sessionFilter) {
995
+ console.log(chalk2.gray(` Session filter: ${sessionFilter}`));
996
+ }
997
+ process.exit(0);
998
+ }
999
+ async function removeWebhook(client, id) {
1000
+ const result = await client.send({
1001
+ method: "hook.webhook.remove",
1002
+ params: { id }
1003
+ });
1004
+ client.close();
1005
+ if (result.error) {
1006
+ console.error(chalk2.red(result.error));
1007
+ process.exit(1);
1008
+ }
1009
+ console.log(chalk2.green(`Webhook removed: ${id}`));
1010
+ process.exit(0);
1011
+ }
1012
+ async function listWebhooks(client) {
1013
+ const result = await client.send({ method: "hook.webhook.list" });
1014
+ client.close();
1015
+ if (result.error) {
1016
+ console.error(chalk2.red(result.error));
1017
+ process.exit(1);
1018
+ }
1019
+ const data = result.data;
1020
+ const webhooks = data.webhooks || [];
1021
+ if (webhooks.length === 0) {
1022
+ console.log(chalk2.gray("No webhooks registered"));
1023
+ process.exit(0);
1024
+ }
1025
+ console.log(chalk2.bold("Webhooks:"));
1026
+ for (const webhook of webhooks) {
1027
+ const status = webhook.enabled ? chalk2.green("enabled") : chalk2.gray("disabled");
1028
+ console.log(` ${chalk2.cyan(webhook.id)} [${status}]`);
1029
+ console.log(` Event: ${chalk2.yellow(webhook.event)}`);
1030
+ console.log(` URL: ${chalk2.gray(webhook.url)}`);
1031
+ if (webhook.sessionFilter) {
1032
+ console.log(` Session: ${chalk2.magenta(webhook.sessionFilter)}`);
1033
+ }
1034
+ }
1035
+ process.exit(0);
1036
+ }
1037
+ async function showHookStats(client) {
1038
+ const result = await client.send({ method: "hook.stats" });
1039
+ client.close();
1040
+ if (result.error) {
1041
+ console.error(chalk2.red(result.error));
1042
+ process.exit(1);
1043
+ }
1044
+ const data = result.data;
1045
+ console.log(chalk2.bold("Hook Statistics:"));
1046
+ console.log(` Shell Hooks: ${chalk2.cyan(data.enabledShellHooks)}/${data.shellHooks} enabled`);
1047
+ console.log(` Webhooks: ${chalk2.cyan(data.enabledWebhooks)}/${data.webhooks} enabled`);
1048
+ console.log(` Events Processed: ${chalk2.cyan(data.eventCount)}`);
1049
+ process.exit(0);
1050
+ }
1051
+ async function addRemoteHost(client, url, apiKey, options) {
1052
+ const result = await client.send({
1053
+ method: "orchestration.host.add",
1054
+ params: { url, apiKey, name: options.name, priority: options.priority }
1055
+ });
1056
+ client.close();
1057
+ if (result.error) {
1058
+ console.error(chalk2.red(result.error));
1059
+ process.exit(1);
1060
+ }
1061
+ const data = result.data;
1062
+ console.log(chalk2.green(`Remote host added: ${data.host?.id}`));
1063
+ console.log(chalk2.gray(` Name: ${data.host?.name}`));
1064
+ console.log(chalk2.gray(` URL: ${url}`));
1065
+ console.log(chalk2.gray(` Priority: ${data.host?.priority}`));
1066
+ process.exit(0);
1067
+ }
1068
+ async function removeRemoteHost(client, hostId) {
1069
+ const result = await client.send({
1070
+ method: "orchestration.host.remove",
1071
+ params: { hostId }
1072
+ });
1073
+ client.close();
1074
+ if (result.error) {
1075
+ console.error(chalk2.red(result.error));
1076
+ process.exit(1);
1077
+ }
1078
+ console.log(chalk2.green(`Remote host removed: ${hostId}`));
1079
+ process.exit(0);
1080
+ }
1081
+ async function toggleRemoteHost(client, hostId) {
1082
+ const listResult = await client.send({ method: "orchestration.host.list" });
1083
+ if (listResult.error) {
1084
+ client.close();
1085
+ console.error(chalk2.red(listResult.error));
1086
+ process.exit(1);
1087
+ }
1088
+ const hosts = listResult.data.hosts || [];
1089
+ const host = hosts.find((h) => h.id === hostId);
1090
+ if (!host) {
1091
+ client.close();
1092
+ console.error(chalk2.red("Host not found"));
1093
+ process.exit(1);
1094
+ }
1095
+ const newEnabled = !host.enabled;
1096
+ const result = await client.send({
1097
+ method: "orchestration.host.toggle",
1098
+ params: { hostId, enabled: newEnabled }
1099
+ });
1100
+ client.close();
1101
+ if (result.error) {
1102
+ console.error(chalk2.red(result.error));
1103
+ process.exit(1);
1104
+ }
1105
+ console.log(chalk2.green(`Remote host ${hostId}: ${newEnabled ? "enabled" : "disabled"}`));
1106
+ process.exit(0);
1107
+ }
1108
+ async function listRemoteHosts(client) {
1109
+ const result = await client.send({ method: "orchestration.host.list" });
1110
+ client.close();
1111
+ if (result.error) {
1112
+ console.error(chalk2.red(result.error));
1113
+ process.exit(1);
1114
+ }
1115
+ const hosts = result.data.hosts || [];
1116
+ if (hosts.length === 0) {
1117
+ console.log(chalk2.gray("No remote hosts configured"));
1118
+ console.log(chalk2.gray("Add one with: cb --remote-add <url> --remote-key <apiKey>"));
1119
+ process.exit(0);
1120
+ }
1121
+ console.log(chalk2.bold("Remote Hosts:"));
1122
+ for (const host of hosts) {
1123
+ const status = host.enabled ? chalk2.green("enabled") : chalk2.gray("disabled");
1124
+ console.log(` ${chalk2.cyan(host.id)} [${status}] - ${chalk2.yellow(host.name)}`);
1125
+ console.log(` URL: ${chalk2.gray(host.url)}`);
1126
+ console.log(` Priority: ${chalk2.gray(String(host.priority))}`);
1127
+ }
1128
+ process.exit(0);
1129
+ }
1130
+ async function showRemoteHealth(client) {
1131
+ const result = await client.send({ method: "orchestration.health" });
1132
+ client.close();
1133
+ if (result.error) {
1134
+ console.error(chalk2.red(result.error));
1135
+ process.exit(1);
1136
+ }
1137
+ const data = result.data;
1138
+ const overallStatus = data.overall.healthy ? chalk2.green("HEALTHY") : chalk2.red("UNHEALTHY");
1139
+ console.log(chalk2.bold(`Overall Status: ${overallStatus}`));
1140
+ console.log(` Healthy: ${chalk2.cyan(data.overall.healthyPercentage.toFixed(1))}%`);
1141
+ console.log("");
1142
+ if (data.hosts.length === 0) {
1143
+ console.log(chalk2.gray("No hosts to show"));
1144
+ process.exit(0);
1145
+ }
1146
+ console.log(chalk2.bold("Host Health:"));
1147
+ for (const host of data.hosts) {
1148
+ const status = host.healthy ? chalk2.green("healthy") : chalk2.red("unhealthy");
1149
+ console.log(` ${chalk2.cyan(host.hostId)} [${status}]`);
1150
+ console.log(` Uptime: ${chalk2.gray(host.uptime.toFixed(1) + "%")}`);
1151
+ console.log(` Avg Latency: ${chalk2.gray(host.avgLatency.toFixed(0) + "ms")}`);
1152
+ console.log(` P99 Latency: ${chalk2.gray(host.p99Latency.toFixed(0) + "ms")}`);
1153
+ console.log(` Success Rate: ${chalk2.gray(host.successRate.toFixed(1) + "%")}`);
1154
+ if (host.lastError) {
1155
+ console.log(` Last Error: ${chalk2.red(host.lastError)}`);
1156
+ }
1157
+ }
1158
+ process.exit(0);
1159
+ }
1160
+ async function showOrchestrationStats(client) {
1161
+ const result = await client.send({ method: "orchestration.stats" });
1162
+ client.close();
1163
+ if (result.error) {
1164
+ console.error(chalk2.red(result.error));
1165
+ process.exit(1);
1166
+ }
1167
+ const data = result.data;
1168
+ console.log(chalk2.bold("Orchestration Statistics:"));
1169
+ console.log("");
1170
+ console.log(chalk2.bold(" Hosts:"));
1171
+ console.log(` Total: ${chalk2.cyan(data.hosts.total)}`);
1172
+ console.log(` Healthy: ${chalk2.green(data.hosts.healthy)}`);
1173
+ console.log(` Unhealthy: ${data.hosts.unhealthy > 0 ? chalk2.red(data.hosts.unhealthy) : chalk2.gray("0")}`);
1174
+ console.log("");
1175
+ console.log(chalk2.bold(" Requests:"));
1176
+ console.log(` Total: ${chalk2.cyan(data.requests.total)}`);
1177
+ console.log(` Successful: ${chalk2.green(data.requests.successful)}`);
1178
+ console.log(` Failed: ${data.requests.failed > 0 ? chalk2.red(data.requests.failed) : chalk2.gray("0")}`);
1179
+ console.log(` Failovers: ${chalk2.yellow(data.requests.failovers)}`);
1180
+ console.log("");
1181
+ console.log(chalk2.bold(" Pipelines:"));
1182
+ console.log(` Total: ${chalk2.cyan(data.pipelines.total)}`);
1183
+ console.log(` Completed: ${chalk2.green(data.pipelines.completed)}`);
1184
+ console.log(` Failed: ${data.pipelines.failed > 0 ? chalk2.red(data.pipelines.failed) : chalk2.gray("0")}`);
1185
+ console.log(` Partial: ${chalk2.yellow(data.pipelines.partial)}`);
1186
+ const cbEntries = Object.entries(data.circuitBreakers);
1187
+ if (cbEntries.length > 0) {
1188
+ console.log("");
1189
+ console.log(chalk2.bold(" Circuit Breakers:"));
1190
+ for (const [hostId, cb] of cbEntries) {
1191
+ const stateColor = cb.state === "closed" ? chalk2.green : cb.state === "open" ? chalk2.red : chalk2.yellow;
1192
+ console.log(` ${hostId}: ${stateColor(cb.state)} (${cb.failures} failures)`);
1193
+ }
1194
+ }
1195
+ process.exit(0);
1196
+ }
1197
+ async function sendRemotePrompt(client, hostId, prompt) {
1198
+ console.log(chalk2.gray(`Sending prompt to remote host ${hostId}...`));
1199
+ const result = await client.send({
1200
+ method: "orchestration.prompt",
1201
+ params: { hostId, prompt }
1202
+ });
1203
+ client.close();
1204
+ if (result.error) {
1205
+ console.error(chalk2.red(result.error));
1206
+ process.exit(1);
1207
+ }
1208
+ const data = result.data;
1209
+ console.log(chalk2.green(`Prompt completed on ${data.host}`));
1210
+ console.log(chalk2.gray(` Session: ${data.sessionId}`));
1211
+ console.log(chalk2.gray(` Status: ${data.status}`));
1212
+ console.log(chalk2.gray(` Latency: ${data.latency}ms`));
1213
+ if (data.output) {
1214
+ console.log("");
1215
+ console.log(chalk2.bold("Output:"));
1216
+ console.log(data.output);
1217
+ }
1218
+ if (data.error) {
1219
+ console.log("");
1220
+ console.error(chalk2.red(`Error: ${data.error}`));
1221
+ }
1222
+ process.exit(0);
1223
+ }
1224
+ async function fireAndForgetPrompt(client, prompt, goal) {
1225
+ const result = await client.send({
1226
+ method: "prompt.fire",
1227
+ params: { prompt, goal }
1228
+ });
1229
+ client.close();
1230
+ if (result.error) {
1231
+ console.error(chalk2.red(result.error));
1232
+ process.exit(1);
1233
+ }
1234
+ const data = result.data;
1235
+ console.log(chalk2.green("Task launched in background"));
1236
+ console.log(` Session: ${chalk2.cyan(data.sessionId)}`);
1237
+ console.log(` Goal: ${chalk2.gray(data.goal)}`);
1238
+ console.log("");
1239
+ console.log(chalk2.gray("Check status:"));
1240
+ console.log(` ${chalk2.yellow("cb -i")} ${chalk2.gray("# notification inbox")}`);
1241
+ console.log(` ${chalk2.yellow("cb -l")} ${chalk2.gray("# view output")}`);
1242
+ console.log(` ${chalk2.yellow("cb -w")} ${chalk2.gray("# watch live")}`);
1243
+ process.exit(0);
1244
+ }
1245
+ async function fireRemotePrompt(client, hostId, prompt, goal) {
1246
+ const result = await client.send({
1247
+ method: "orchestration.fire",
1248
+ params: { hostId, prompt, goal }
1249
+ });
1250
+ client.close();
1251
+ if (result.error) {
1252
+ console.error(chalk2.red(result.error));
1253
+ process.exit(1);
1254
+ }
1255
+ const data = result.data;
1256
+ console.log(chalk2.green("Remote task dispatched"));
1257
+ console.log(` Host: ${chalk2.cyan(hostId)}`);
1258
+ console.log(` Goal: ${chalk2.gray(data.goal)}`);
1259
+ console.log("");
1260
+ console.log(` ${chalk2.yellow("cb -i")} ${chalk2.gray("# check when done")}`);
1261
+ process.exit(0);
1262
+ }
1263
+ function renderMarkdown(text) {
1264
+ const lines = text.split("\n");
1265
+ const result = [];
1266
+ let inCodeBlock = false;
1267
+ for (const line of lines) {
1268
+ if (line.startsWith("```")) {
1269
+ inCodeBlock = !inCodeBlock;
1270
+ result.push(inCodeBlock ? chalk2.gray(" \u250C\u2500") : chalk2.gray(" \u2514\u2500"));
1271
+ continue;
1272
+ }
1273
+ if (inCodeBlock) {
1274
+ result.push(chalk2.gray(` \u2502 ${line}`));
1275
+ continue;
1276
+ }
1277
+ if (line.startsWith("### ")) {
1278
+ result.push(chalk2.bold.dim(line.slice(4)));
1279
+ continue;
1280
+ }
1281
+ if (line.startsWith("## ")) {
1282
+ result.push(chalk2.bold(line.slice(3)));
1283
+ continue;
1284
+ }
1285
+ if (line.startsWith("# ")) {
1286
+ result.push(chalk2.bold.underline(line.slice(2)));
1287
+ continue;
1288
+ }
1289
+ if (line.startsWith("> ")) {
1290
+ result.push(chalk2.gray(` \u2502 ${line.slice(2)}`));
1291
+ continue;
1292
+ }
1293
+ if (/^[-*] /.test(line)) {
1294
+ result.push(` \u2022 ${renderInline(line.slice(2))}`);
1295
+ continue;
1296
+ }
1297
+ const orderedMatch = line.match(/^(\d+)\. (.*)$/);
1298
+ if (orderedMatch) {
1299
+ result.push(` ${orderedMatch[1]}. ${renderInline(orderedMatch[2])}`);
1300
+ continue;
1301
+ }
1302
+ result.push(` ${renderInline(line)}`);
1303
+ }
1304
+ return result.join("\n");
1305
+ }
1306
+ function renderInline(text) {
1307
+ text = text.replace(/\*\*(.+?)\*\*/g, (_, m) => chalk2.bold(m));
1308
+ text = text.replace(/(?<!\*)\*([^*]+?)\*(?!\*)/g, (_, m) => chalk2.italic(m));
1309
+ text = text.replace(/`([^`]+?)`/g, (_, m) => chalk2.cyan(m));
1310
+ return text;
1311
+ }
1312
+ async function showInbox(client) {
1313
+ const result = await client.send({ method: "notification.list", params: { unreadOnly: false } });
1314
+ if (result.error) {
1315
+ client.close();
1316
+ console.error(chalk2.red(result.error));
1317
+ process.exit(1);
1318
+ }
1319
+ const data = result.data;
1320
+ const notifications = [...data.notifications || []].reverse();
1321
+ if (notifications.length === 0) {
1322
+ client.close();
1323
+ console.log(chalk2.gray("No notifications"));
1324
+ process.exit(0);
1325
+ }
1326
+ let currentIndex = 0;
1327
+ function render() {
1328
+ const rows = process.stdout.rows || 24;
1329
+ const cols = process.stdout.columns || 80;
1330
+ const n = notifications[currentIndex];
1331
+ const total = notifications.length;
1332
+ const unreadCount = notifications.filter((x) => !x.read).length;
1333
+ process.stdout.write("\x1B[2J\x1B[H");
1334
+ const headerLeft = `\u2500\u2500 Inbox (${currentIndex + 1}/${total})${n.read ? "" : " * unread"}`;
1335
+ const headerRight = unreadCount > 0 ? `${unreadCount} unread \u2500\u2500` : "\u2500\u2500";
1336
+ console.log(chalk2.bold(headerLeft) + chalk2.gray(` ${"\u2500".repeat(Math.max(2, cols - headerLeft.length - headerRight.length - 2))} ${headerRight}`));
1337
+ console.log("");
1338
+ const statusIcon = n.type === "prompt.completed" ? chalk2.green("OK") : chalk2.red("ERR");
1339
+ const duration = n.durationMs ? `${(n.durationMs / 1e3).toFixed(1)}s` : "";
1340
+ const cost = n.costUsd ? `$${n.costUsd.toFixed(4)}` : "";
1341
+ const time = new Date(n.timestamp).toLocaleTimeString();
1342
+ console.log(` ${statusIcon} ${chalk2.cyan.bold(n.sessionName || n.sessionId)} ${chalk2.gray(time)} ${chalk2.gray(duration)} ${chalk2.gray(cost)}`);
1343
+ if (n.goal) {
1344
+ console.log(` ${chalk2.gray("Goal:")} ${n.goal}`);
1345
+ }
1346
+ console.log("");
1347
+ const bodyText = n.resultFull || n.resultPreview || "";
1348
+ if (bodyText) {
1349
+ const rendered = renderMarkdown(bodyText);
1350
+ const maxBodyLines = rows - 10;
1351
+ const bodyLines = rendered.split("\n");
1352
+ if (bodyLines.length > maxBodyLines) {
1353
+ console.log(bodyLines.slice(0, maxBodyLines).join("\n"));
1354
+ console.log(chalk2.gray(` ... (${bodyLines.length - maxBodyLines} more lines)`));
1355
+ } else {
1356
+ console.log(rendered);
1357
+ }
1358
+ } else {
1359
+ console.log(chalk2.gray(" (no output)"));
1360
+ }
1361
+ console.log("");
1362
+ if (n.claudeSessionId) {
1363
+ console.log(` ${chalk2.gray("Resume:")} ${chalk2.yellow('cb "your follow-up here"')}`);
1364
+ } else {
1365
+ console.log(` ${chalk2.gray("View:")} ${chalk2.yellow("cb -l")}`);
1366
+ }
1367
+ console.log("");
1368
+ const footer = "\u2500\u2500 n=next p=prev r=read d=delete q=quit \u2500\u2500";
1369
+ console.log(chalk2.gray(footer));
1370
+ }
1371
+ function cleanup() {
1372
+ if (process.stdin.isTTY) {
1373
+ process.stdin.setRawMode(false);
1374
+ }
1375
+ process.stdout.write("\x1B[2J\x1B[H");
1376
+ client.close();
1377
+ }
1378
+ if (process.stdin.isTTY) {
1379
+ process.stdin.setRawMode(true);
1380
+ }
1381
+ process.stdin.resume();
1382
+ process.stdin.setEncoding("utf8");
1383
+ render();
1384
+ process.stdin.on("data", async (key) => {
1385
+ switch (key) {
1386
+ case "n":
1387
+ case "j":
1388
+ case "\x1B[C":
1389
+ case "\x1B[B":
1390
+ if (currentIndex < notifications.length - 1) {
1391
+ currentIndex++;
1392
+ render();
1393
+ }
1394
+ break;
1395
+ case "p":
1396
+ case "k":
1397
+ case "\x1B[D":
1398
+ case "\x1B[A":
1399
+ if (currentIndex > 0) {
1400
+ currentIndex--;
1401
+ render();
1402
+ }
1403
+ break;
1404
+ case "r": {
1405
+ const n = notifications[currentIndex];
1406
+ if (!n.read) {
1407
+ await client.send({ method: "notification.markRead", params: { id: n.id } });
1408
+ n.read = true;
1409
+ render();
1410
+ }
1411
+ break;
1412
+ }
1413
+ case "d": {
1414
+ const n = notifications[currentIndex];
1415
+ await client.send({ method: "notification.delete", params: { id: n.id } });
1416
+ notifications.splice(currentIndex, 1);
1417
+ if (notifications.length === 0) {
1418
+ cleanup();
1419
+ console.log(chalk2.gray("No more notifications"));
1420
+ process.exit(0);
1421
+ }
1422
+ if (currentIndex >= notifications.length) {
1423
+ currentIndex = notifications.length - 1;
1424
+ }
1425
+ render();
1426
+ break;
1427
+ }
1428
+ case "q":
1429
+ case "":
1430
+ cleanup();
1431
+ process.exit(0);
1432
+ break;
1433
+ default:
1434
+ if (key === "\x1B") {
1435
+ cleanup();
1436
+ process.exit(0);
1437
+ }
1438
+ break;
1439
+ }
1440
+ });
1441
+ await new Promise(() => {
1442
+ });
1443
+ }
1444
+ async function clearInbox(client) {
1445
+ const result = await client.send({ method: "notification.clear" });
1446
+ client.close();
1447
+ if (result.error) {
1448
+ console.error(chalk2.red(result.error));
1449
+ process.exit(1);
1450
+ }
1451
+ const data = result.data;
1452
+ console.log(chalk2.green(`Marked ${data.cleared} notification${data.cleared !== 1 ? "s" : ""} as read`));
1453
+ process.exit(0);
1454
+ }
1455
+ async function showInboxCount(client) {
1456
+ const result = await client.send({ method: "notification.count" });
1457
+ client.close();
1458
+ if (result.error) {
1459
+ console.error(chalk2.red(result.error));
1460
+ process.exit(1);
1461
+ }
1462
+ const data = result.data;
1463
+ if (data.unread > 0) {
1464
+ console.log(chalk2.yellow(`${data.unread} unread notification${data.unread !== 1 ? "s" : ""} (${data.total} total)`));
1465
+ console.log(chalk2.gray("View with: cb -i"));
1466
+ } else {
1467
+ console.log(chalk2.gray(`No unread notifications (${data.total} total)`));
1468
+ }
1469
+ process.exit(0);
1470
+ }
1471
+ async function setupTelegram(client, token) {
1472
+ console.log(chalk2.gray("Setting up Telegram bot..."));
1473
+ const result = await client.send({ method: "telegram.setup", params: { token } });
1474
+ client.close();
1475
+ if (result.error) {
1476
+ console.error(chalk2.red(result.error));
1477
+ process.exit(1);
1478
+ }
1479
+ const data = result.data;
1480
+ console.log(chalk2.green("Telegram bot started!"));
1481
+ if (data.username) {
1482
+ console.log(` Bot: ${chalk2.cyan(`@${data.username}`)}`);
1483
+ }
1484
+ console.log("");
1485
+ console.log(chalk2.gray("Send /start to your bot in Telegram to register."));
1486
+ process.exit(0);
1487
+ }
1488
+ async function stopTelegram(client) {
1489
+ const result = await client.send({ method: "telegram.stop" });
1490
+ client.close();
1491
+ if (result.error) {
1492
+ console.error(chalk2.red(result.error));
1493
+ process.exit(1);
1494
+ }
1495
+ console.log(chalk2.green("Telegram bot stopped and token cleared."));
1496
+ process.exit(0);
1497
+ }
1498
+ async function showTelegramStatus(client) {
1499
+ const result = await client.send({ method: "telegram.status" });
1500
+ client.close();
1501
+ if (result.error) {
1502
+ console.error(chalk2.red(result.error));
1503
+ process.exit(1);
1504
+ }
1505
+ const data = result.data;
1506
+ const status = data.running ? chalk2.green("running") : chalk2.gray("stopped");
1507
+ console.log(chalk2.bold("Telegram Bot:"));
1508
+ console.log(` Status: ${status}`);
1509
+ console.log(` Enabled: ${data.enabled ? chalk2.green("yes") : chalk2.gray("no")}`);
1510
+ if (data.chatIds.length > 0) {
1511
+ console.log(` Registered chats: ${chalk2.cyan(data.chatIds.length.toString())}`);
1512
+ } else {
1513
+ console.log(chalk2.gray(" No registered chats (send /start to your bot)"));
1514
+ }
1515
+ if (data.running) {
1516
+ console.log(` Forwarding: ${data.forwardAllSessions !== false ? chalk2.green("all sessions") : chalk2.gray("fire-and-forget only")}`);
1517
+ }
1518
+ if (!data.running && !data.enabled) {
1519
+ console.log("");
1520
+ console.log(chalk2.gray("Set up with: cb --telegram <token>"));
1521
+ }
1522
+ process.exit(0);
1523
+ }
1524
+ async function setTelegramForward(client, enabled) {
1525
+ const result = await client.send({ method: "telegram.setForward", params: { enabled } });
1526
+ client.close();
1527
+ if (result.error) {
1528
+ console.error(chalk2.red(result.error));
1529
+ process.exit(1);
1530
+ }
1531
+ const status = enabled ? chalk2.green("enabled") : chalk2.gray("disabled");
1532
+ console.log(`Telegram session forwarding: ${status}`);
1533
+ process.exit(0);
1534
+ }
1535
+ function printShellInit() {
1536
+ const script = `
1537
+ # Claude-B inbox notification hook
1538
+ _cb_check_inbox() {
1539
+ local marker="$HOME/.claude-b/inbox-new"
1540
+ if [ -f "$marker" ]; then
1541
+ local msg
1542
+ msg=$(cat "$marker" 2>/dev/null)
1543
+ rm -f "$marker"
1544
+ printf '\\e[33m[Claude-B]\\e[0m %s \u2014 run \\e[1mcb -i\\e[0m to view\\n' "$msg"
1545
+ fi
1546
+ }
1547
+
1548
+ # Install into the appropriate shell
1549
+ if [ -n "$ZSH_VERSION" ]; then
1550
+ autoload -Uz add-zsh-hook 2>/dev/null
1551
+ if typeset -f add-zsh-hook >/dev/null 2>&1; then
1552
+ add-zsh-hook precmd _cb_check_inbox
1553
+ else
1554
+ precmd_functions+=(_cb_check_inbox)
1555
+ fi
1556
+ elif [ -n "$BASH_VERSION" ]; then
1557
+ if [[ ! "$PROMPT_COMMAND" =~ _cb_check_inbox ]]; then
1558
+ PROMPT_COMMAND="_cb_check_inbox\${PROMPT_COMMAND:+;$PROMPT_COMMAND}"
1559
+ fi
1560
+ fi
1561
+ `.trim();
1562
+ console.log(script);
1563
+ }
1564
+ async function setupVoicePipeline(client, provider, apiKey) {
1565
+ console.log(chalk2.gray(`Configuring STT provider (${provider})...`));
1566
+ const result = await client.send({ method: "voice.setup", params: { provider, apiKey } });
1567
+ client.close();
1568
+ if (result.error) {
1569
+ console.error(chalk2.red(result.error));
1570
+ process.exit(1);
1571
+ }
1572
+ console.log(chalk2.green(`STT provider configured: ${provider}`));
1573
+ console.log("");
1574
+ console.log(chalk2.gray("Next: set up an AI provider for prompt optimization:"));
1575
+ console.log(chalk2.gray(" cb --ai-provider anthropic <api-key>"));
1576
+ console.log(chalk2.gray(" cb --ai-provider openrouter <api-key>"));
1577
+ process.exit(0);
1578
+ }
1579
+ async function setupVoiceAI(client, provider, apiKey, model) {
1580
+ console.log(chalk2.gray(`Configuring AI provider (${provider})...`));
1581
+ const result = await client.send({ method: "voice.ai", params: { provider, apiKey, model } });
1582
+ client.close();
1583
+ if (result.error) {
1584
+ console.error(chalk2.red(result.error));
1585
+ process.exit(1);
1586
+ }
1587
+ const data = result.data;
1588
+ console.log(chalk2.green("AI provider configured!"));
1589
+ console.log(` Provider: ${chalk2.cyan(data.provider || provider)}`);
1590
+ console.log(` Model: ${chalk2.cyan(data.model || "default")}`);
1591
+ console.log("");
1592
+ console.log(chalk2.gray("Voice pipeline is ready. Send a voice message in Telegram!"));
1593
+ process.exit(0);
1594
+ }
1595
+ async function showVoiceStatus(client) {
1596
+ const result = await client.send({ method: "voice.status" });
1597
+ client.close();
1598
+ if (result.error) {
1599
+ console.error(chalk2.red(result.error));
1600
+ process.exit(1);
1601
+ }
1602
+ const data = result.data;
1603
+ console.log(chalk2.bold("Voice Pipeline:"));
1604
+ if (data.sttProvider) {
1605
+ console.log(` STT Provider: ${chalk2.green(data.sttProvider.provider)}`);
1606
+ } else {
1607
+ console.log(` STT Provider: ${chalk2.gray("not configured")}`);
1608
+ }
1609
+ if (data.aiProvider) {
1610
+ console.log(` AI Provider: ${chalk2.green(data.aiProvider.provider)} (${chalk2.cyan(data.aiProvider.model)})`);
1611
+ } else {
1612
+ console.log(` AI Provider: ${chalk2.gray("not configured")}`);
1613
+ }
1614
+ console.log(` Pipeline: ${data.pipelineActive ? chalk2.green("active") : chalk2.yellow("inactive")}`);
1615
+ if (!data.sttProvider || !data.aiProvider) {
1616
+ console.log("");
1617
+ if (!data.sttProvider) {
1618
+ console.log(chalk2.gray(" Set up STT: cb --voice-setup <speechmatics|deepgram|openai> <api-key>"));
1619
+ }
1620
+ if (!data.aiProvider) {
1621
+ console.log(chalk2.gray(" Set up AI: cb --ai-provider <anthropic|openrouter> <api-key>"));
1622
+ }
1623
+ }
1624
+ process.exit(0);
1625
+ }
1626
+ var hasArgs = process.argv.length > 2;
1627
+ if (!process.stdin.isTTY && !hasArgs) {
1628
+ let input = "";
1629
+ process.stdin.setEncoding("utf8");
1630
+ process.stdin.on("data", (chunk) => {
1631
+ input += chunk;
1632
+ });
1633
+ process.stdin.on("end", () => {
1634
+ if (input.trim()) {
1635
+ const client = new DaemonClient();
1636
+ sendPrompt(client, input.trim()).catch(console.error);
1637
+ } else {
1638
+ program.parse();
1639
+ }
1640
+ });
1641
+ } else {
1642
+ program.parse();
1643
+ }