@beevibe/daemon 0.0.1-rc2

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.
Files changed (3) hide show
  1. package/README.md +354 -0
  2. package/dist/main.js +1385 -0
  3. package/package.json +28 -0
package/dist/main.js ADDED
@@ -0,0 +1,1385 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/setup.ts
4
+ import { hostname, userInfo } from "node:os";
5
+ import { spawnSync } from "node:child_process";
6
+
7
+ // src/config.ts
8
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
9
+ import { homedir } from "node:os";
10
+ import { dirname, join } from "node:path";
11
+ var CONFIG_DIR = join(homedir(), ".beevibe");
12
+ var CONFIG_PATH = join(CONFIG_DIR, "config.json");
13
+ function loadConfig() {
14
+ if (!existsSync(CONFIG_PATH))
15
+ return;
16
+ try {
17
+ return JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
18
+ } catch (err) {
19
+ throw new Error(`Daemon config at ${CONFIG_PATH} is malformed: ${err instanceof Error ? err.message : String(err)}`);
20
+ }
21
+ }
22
+ function saveConfig(cfg) {
23
+ mkdirSync(dirname(CONFIG_PATH), { recursive: true, mode: 448 });
24
+ writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2) + `
25
+ `, { mode: 384 });
26
+ }
27
+
28
+ // src/setup.ts
29
+ var KNOWN_CLIS = ["claude", "codex", "opencode"];
30
+ async function runSetup(options) {
31
+ if (!/^https?:\/\//.test(options.apiUrl)) {
32
+ throw new Error("--api must be an http(s) URL");
33
+ }
34
+ if (!options.userToken.startsWith("bv_u_")) {
35
+ throw new Error("--user-token must start with bv_u_");
36
+ }
37
+ const externalId = options.externalId ?? hostname();
38
+ const deviceName = options.deviceName ?? `${userInfo().username}@${hostname()}`;
39
+ const runtimes = options.detectedClis ?? detectClis();
40
+ if (runtimes.length === 0) {
41
+ throw new Error(`No supported CLIs detected on PATH. beevibe currently looks for: ${KNOWN_CLIS.join(", ")}`);
42
+ }
43
+ const res = await fetch(`${options.apiUrl}/runtime/register`, {
44
+ method: "POST",
45
+ headers: {
46
+ "content-type": "application/json",
47
+ authorization: `Bearer ${options.userToken}`
48
+ },
49
+ body: JSON.stringify({
50
+ external_id: externalId,
51
+ device_name: deviceName,
52
+ runtimes
53
+ })
54
+ });
55
+ if (res.status !== 200 && res.status !== 201) {
56
+ throw new Error(`/runtime/register failed: ${res.status} ${await res.text()}`);
57
+ }
58
+ const body = await res.json();
59
+ const config = {
60
+ api_url: options.apiUrl,
61
+ external_id: externalId,
62
+ daemon_id: body.daemon_id,
63
+ daemon_token: body.daemon_token,
64
+ runtimes: body.runtimes
65
+ };
66
+ saveConfig(config);
67
+ return config;
68
+ }
69
+ function detectClis() {
70
+ const out = [];
71
+ for (const cli of KNOWN_CLIS) {
72
+ const which = spawnSync("which", [cli], { encoding: "utf8" });
73
+ if (which.status !== 0 || !which.stdout.trim())
74
+ continue;
75
+ const version = spawnSync(cli, ["--version"], { encoding: "utf8" });
76
+ out.push({
77
+ cli,
78
+ cli_version: version.status === 0 ? version.stdout.trim().split(`
79
+ `)[0] : undefined
80
+ });
81
+ }
82
+ return out;
83
+ }
84
+
85
+ // ../core/dist/adapters/local-workspace/manager.js
86
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
87
+ import { homedir as homedir2 } from "node:os";
88
+ import { join as join2 } from "node:path";
89
+
90
+ // ../core/dist/services/skills/sync.js
91
+ import { promises as fs } from "node:fs";
92
+ import path from "node:path";
93
+ async function syncSkills(opts) {
94
+ const { sourceDir, targetDir, filter, namespacePrefix } = opts;
95
+ const result = { added: [], updated: [], removed: [], unchanged: [] };
96
+ await fs.mkdir(targetDir, { recursive: true });
97
+ const targetEntries = await fs.readdir(targetDir, { withFileTypes: true });
98
+ const targetSkills = new Set;
99
+ for (const entry of targetEntries) {
100
+ if (!entry.isDirectory())
101
+ continue;
102
+ const name = entry.name;
103
+ if (name === namespacePrefix || name.startsWith(`${namespacePrefix}-`)) {
104
+ targetSkills.add(name);
105
+ }
106
+ }
107
+ for (const name of targetSkills) {
108
+ if (!filter.has(name)) {
109
+ await fs.rm(path.join(targetDir, name), { recursive: true, force: true });
110
+ result.removed.push(name);
111
+ }
112
+ }
113
+ for (const name of filter) {
114
+ const src = path.join(sourceDir, name);
115
+ const tgt = path.join(targetDir, name);
116
+ const srcExists = await pathExists(src);
117
+ if (!srcExists) {
118
+ if (targetSkills.has(name)) {
119
+ await fs.rm(tgt, { recursive: true, force: true });
120
+ result.removed.push(name);
121
+ }
122
+ continue;
123
+ }
124
+ if (!targetSkills.has(name)) {
125
+ await copyDir(src, tgt);
126
+ result.added.push(name);
127
+ continue;
128
+ }
129
+ const changed = await syncDirIfChanged(src, tgt);
130
+ if (changed)
131
+ result.updated.push(name);
132
+ else
133
+ result.unchanged.push(name);
134
+ }
135
+ return result;
136
+ }
137
+ async function pathExists(p) {
138
+ try {
139
+ await fs.access(p);
140
+ return true;
141
+ } catch {
142
+ return false;
143
+ }
144
+ }
145
+ async function copyDir(src, dst) {
146
+ await fs.mkdir(dst, { recursive: true });
147
+ const entries = await fs.readdir(src, { withFileTypes: true });
148
+ for (const entry of entries) {
149
+ const s = path.join(src, entry.name);
150
+ const d = path.join(dst, entry.name);
151
+ if (entry.isDirectory()) {
152
+ await copyDir(s, d);
153
+ } else if (entry.isFile() || entry.isSymbolicLink()) {
154
+ await fs.copyFile(s, d);
155
+ }
156
+ }
157
+ }
158
+ async function syncDirIfChanged(src, tgt) {
159
+ let changed = false;
160
+ const srcFiles = await listFilesRecursive(src);
161
+ const tgtFiles = await listFilesRecursive(tgt);
162
+ const srcRel = new Set(srcFiles.map((f) => path.relative(src, f)));
163
+ for (const srcFile of srcFiles) {
164
+ const rel = path.relative(src, srcFile);
165
+ const tgtFile = path.join(tgt, rel);
166
+ const srcStat = await fs.stat(srcFile);
167
+ let tgtStat;
168
+ try {
169
+ tgtStat = await fs.stat(tgtFile);
170
+ } catch {
171
+ tgtStat = null;
172
+ }
173
+ const needsCopy = !tgtStat || tgtStat.mtimeMs < srcStat.mtimeMs || tgtStat.size !== srcStat.size;
174
+ if (needsCopy) {
175
+ await fs.mkdir(path.dirname(tgtFile), { recursive: true });
176
+ await fs.copyFile(srcFile, tgtFile);
177
+ changed = true;
178
+ }
179
+ }
180
+ for (const tgtFile of tgtFiles) {
181
+ const rel = path.relative(tgt, tgtFile);
182
+ if (!srcRel.has(rel)) {
183
+ await fs.rm(tgtFile, { force: true });
184
+ changed = true;
185
+ }
186
+ }
187
+ return changed;
188
+ }
189
+ async function listFilesRecursive(dir) {
190
+ const out = [];
191
+ async function walk(d) {
192
+ const entries = await fs.readdir(d, { withFileTypes: true });
193
+ for (const entry of entries) {
194
+ const p = path.join(d, entry.name);
195
+ if (entry.isDirectory())
196
+ await walk(p);
197
+ else if (entry.isFile() || entry.isSymbolicLink())
198
+ out.push(p);
199
+ }
200
+ }
201
+ if (await pathExists(dir))
202
+ await walk(dir);
203
+ return out;
204
+ }
205
+ // ../core/dist/services/skills/tier-filter.js
206
+ var UNIVERSAL_SKILLS = ["beevibe-pre-task-setup"];
207
+ var TEAM_ONLY_SKILLS = ["beevibe-team-mesh-negotiation"];
208
+ function tierFilterFor(level) {
209
+ if (level === "ic")
210
+ return new Set(UNIVERSAL_SKILLS);
211
+ return new Set([...UNIVERSAL_SKILLS, ...TEAM_ONLY_SKILLS]);
212
+ }
213
+ // ../core/dist/adapters/local-workspace/manager.js
214
+ class LocalWorkspaceManager {
215
+ config;
216
+ root;
217
+ constructor(config) {
218
+ this.config = config;
219
+ this.root = config.workspaceRoot ?? join2(homedir2(), ".beevibe", "workspaces");
220
+ }
221
+ async ensureWorkspace({ agent }) {
222
+ const path2 = join2(this.root, agent.id);
223
+ mkdirSync2(path2, { recursive: true, mode: 448 });
224
+ const configPath = join2(path2, "mcp-config.json");
225
+ if (!existsSync2(configPath)) {
226
+ if (!agent.api_key) {
227
+ throw new Error(`Cannot write mcp-config.json for agent ${agent.id}: agent.api_key is missing`);
228
+ }
229
+ writeFileSync2(configPath, buildMcpConfig(agent.api_key, this.config.mcpServerUrl), {
230
+ mode: 384
231
+ });
232
+ }
233
+ const workspace = { path: path2 };
234
+ const runtime = this.config.runtimeRegistry[agent.runtime_config.type];
235
+ if (!runtime) {
236
+ throw new Error(`No runtime registered for agent ${agent.id} (runtime_config.type='${agent.runtime_config.type}')`);
237
+ }
238
+ await syncSkills({
239
+ sourceDir: this.config.skillsSourceDir,
240
+ targetDir: runtime.skillsDir(workspace),
241
+ filter: tierFilterFor(agent.hierarchy_level),
242
+ namespacePrefix: "beevibe"
243
+ });
244
+ return workspace;
245
+ }
246
+ async removeWorkspace(workspace) {
247
+ rmSync(workspace.path, { recursive: true, force: true });
248
+ }
249
+ }
250
+ function buildMcpConfig(apiKey, mcpServerUrl) {
251
+ return JSON.stringify({
252
+ mcpServers: {
253
+ beevibe: {
254
+ type: "http",
255
+ url: mcpServerUrl,
256
+ headers: {
257
+ Authorization: `Bearer ${apiKey}`,
258
+ "X-Beevibe-Session": "${BEEVIBE_SESSION_ID}"
259
+ }
260
+ }
261
+ }
262
+ }, null, 2) + `
263
+ `;
264
+ }
265
+ // ../core/dist/adapters/claude-code/runtime.js
266
+ import { tmpdir } from "node:os";
267
+ import { join as join3 } from "node:path";
268
+
269
+ // ../core/dist/adapters/claude-code/spawn.js
270
+ import { spawn } from "node:child_process";
271
+ var MAX_CAPTURE_BYTES = 4 * 1024 * 1024;
272
+
273
+ class CappedBuffer {
274
+ chunks = [];
275
+ len = 0;
276
+ truncated = false;
277
+ append(chunk) {
278
+ if (this.len >= MAX_CAPTURE_BYTES) {
279
+ this.truncated = true;
280
+ return;
281
+ }
282
+ const remaining = MAX_CAPTURE_BYTES - this.len;
283
+ if (chunk.length > remaining) {
284
+ this.chunks.push(chunk.slice(0, remaining));
285
+ this.len = MAX_CAPTURE_BYTES;
286
+ this.truncated = true;
287
+ } else {
288
+ this.chunks.push(chunk);
289
+ this.len += chunk.length;
290
+ }
291
+ }
292
+ join() {
293
+ return this.chunks.join("");
294
+ }
295
+ }
296
+ function runCliProcess(options) {
297
+ if (process.platform === "win32") {
298
+ throw new Error("runCliProcess: Windows is not supported in v1. Process-group " + "signaling is POSIX-only; half-working Windows behavior would leak " + "child processes on abort.");
299
+ }
300
+ const graceMs = options.graceMs ?? 20000;
301
+ return new Promise((resolve) => {
302
+ const stdout = new CappedBuffer;
303
+ const stderr = new CappedBuffer;
304
+ let settled = false;
305
+ const proc = spawn(options.command, options.args ?? [], {
306
+ env: options.env ?? process.env,
307
+ cwd: options.cwd,
308
+ stdio: ["pipe", "pipe", "pipe"],
309
+ detached: true,
310
+ shell: false
311
+ });
312
+ const pid = proc.pid ?? null;
313
+ if (pid !== null && options.onSpawn) {
314
+ options.onSpawn({ pid, process_group_id: pid });
315
+ }
316
+ function killProc(signal) {
317
+ try {
318
+ if (proc.pid)
319
+ process.kill(-proc.pid, signal);
320
+ } catch {}
321
+ }
322
+ function settleWith(partial) {
323
+ if (settled)
324
+ return;
325
+ settled = true;
326
+ if (timer)
327
+ clearTimeout(timer);
328
+ resolve({
329
+ ...partial,
330
+ stdout: stdout.join(),
331
+ stderr: stderr.join(),
332
+ pid,
333
+ process_group_id: pid,
334
+ truncated: stdout.truncated || stderr.truncated
335
+ });
336
+ }
337
+ proc.stdin.on("error", () => {});
338
+ if (options.stdin !== undefined) {
339
+ proc.stdin.write(options.stdin);
340
+ }
341
+ proc.stdin.end();
342
+ let asyncMode = false;
343
+ let logChain = Promise.resolve();
344
+ function invokeLog(kind, text) {
345
+ if (!options.onLog)
346
+ return;
347
+ const cb = options.onLog;
348
+ if (asyncMode) {
349
+ logChain = logChain.then(() => {
350
+ try {
351
+ return cb(kind, text);
352
+ } catch {
353
+ return;
354
+ }
355
+ }).catch(() => {
356
+ return;
357
+ });
358
+ return;
359
+ }
360
+ let ret;
361
+ try {
362
+ ret = cb(kind, text);
363
+ } catch {
364
+ return;
365
+ }
366
+ if (ret && typeof ret.then === "function") {
367
+ asyncMode = true;
368
+ logChain = ret.catch(() => {
369
+ return;
370
+ });
371
+ }
372
+ }
373
+ function attachStreamHandler(stream, kind, buf) {
374
+ stream.on("data", (chunk) => {
375
+ const text = chunk.toString();
376
+ buf.append(text);
377
+ invokeLog(kind, text);
378
+ });
379
+ }
380
+ attachStreamHandler(proc.stdout, "stdout", stdout);
381
+ attachStreamHandler(proc.stderr, "stderr", stderr);
382
+ let abortFired = false;
383
+ let timeoutFired = false;
384
+ let timer = null;
385
+ if (options.timeoutMs) {
386
+ timer = setTimeout(() => {
387
+ timeoutFired = true;
388
+ killProc("SIGTERM");
389
+ setTimeout(() => {
390
+ if (!settled)
391
+ killProc("SIGKILL");
392
+ }, graceMs);
393
+ }, options.timeoutMs);
394
+ }
395
+ if (options.abortSignal) {
396
+ options.abortSignal.addEventListener("abort", () => {
397
+ abortFired = true;
398
+ killProc("SIGTERM");
399
+ setTimeout(() => {
400
+ if (!settled)
401
+ killProc("SIGKILL");
402
+ }, graceMs);
403
+ }, { once: true });
404
+ }
405
+ proc.on("close", (code) => {
406
+ settleWith({ exitCode: code, timedOut: timeoutFired, aborted: abortFired });
407
+ });
408
+ proc.on("error", (err) => {
409
+ stderr.append(err.message);
410
+ settleWith({ exitCode: null, timedOut: timeoutFired, aborted: abortFired });
411
+ });
412
+ });
413
+ }
414
+
415
+ // ../core/dist/adapters/claude-code/stream-json.js
416
+ var STREAM_TYPE = {
417
+ System: "system",
418
+ Assistant: "assistant",
419
+ ToolUse: "tool_use",
420
+ ToolResult: "tool_result",
421
+ Result: "result",
422
+ ContentBlockStart: "content_block_start"
423
+ };
424
+ var BLOCK_TYPE = {
425
+ Text: "text",
426
+ ToolUse: "tool_use"
427
+ };
428
+ function parseStreamJsonLine(line) {
429
+ const trimmed = line.trim();
430
+ if (!trimmed || !trimmed.startsWith("{"))
431
+ return null;
432
+ try {
433
+ return JSON.parse(trimmed);
434
+ } catch {
435
+ return null;
436
+ }
437
+ }
438
+ function extractStepEvents(msg) {
439
+ const now = new Date().toISOString();
440
+ if (msg.type === STREAM_TYPE.ToolUse || msg.subtype === STREAM_TYPE.ToolUse) {
441
+ return [
442
+ {
443
+ kind: "tool_call",
444
+ tool: msg.name ?? "unknown",
445
+ description: describeToolInput(msg.input ?? {}),
446
+ timestamp: now
447
+ }
448
+ ];
449
+ }
450
+ if (msg.type === STREAM_TYPE.ContentBlockStart && msg.content_block?.type === BLOCK_TYPE.ToolUse) {
451
+ const block = msg.content_block;
452
+ return [
453
+ {
454
+ kind: "tool_call",
455
+ tool: block.name ?? "unknown",
456
+ description: describeToolInput(block.input ?? {}),
457
+ timestamp: now
458
+ }
459
+ ];
460
+ }
461
+ if (msg.type === STREAM_TYPE.Assistant && msg.message && Array.isArray(msg.message.content)) {
462
+ const out = [];
463
+ for (const block of msg.message.content) {
464
+ if (block.type === BLOCK_TYPE.Text && typeof block.text === "string" && block.text.trim().length > 0) {
465
+ out.push({
466
+ kind: "agent",
467
+ description: block.text,
468
+ timestamp: now
469
+ });
470
+ } else if (block.type === BLOCK_TYPE.ToolUse) {
471
+ out.push({
472
+ kind: "tool_call",
473
+ tool: block.name ?? "unknown",
474
+ description: describeToolInput(block.input ?? {}),
475
+ timestamp: now
476
+ });
477
+ }
478
+ }
479
+ return out;
480
+ }
481
+ return [];
482
+ }
483
+ var PREFERRED_INPUT_FIELDS = [
484
+ "file_path",
485
+ "command",
486
+ "query",
487
+ "pattern",
488
+ "path",
489
+ "url",
490
+ "question",
491
+ "answer",
492
+ "intent",
493
+ "feedback",
494
+ "proposal",
495
+ "blocker_summary",
496
+ "summary"
497
+ ];
498
+ function describeToolInput(input) {
499
+ for (const k of PREFERRED_INPUT_FIELDS) {
500
+ const v = input[k];
501
+ if (typeof v === "string")
502
+ return v.slice(0, 200);
503
+ }
504
+ if (typeof input.name === "string" && typeof input.persona === "string") {
505
+ return input.name.slice(0, 80);
506
+ }
507
+ const keys = Object.keys(input);
508
+ if (keys.length === 1 && typeof input[keys[0]] === "string") {
509
+ return input[keys[0]].slice(0, 200);
510
+ }
511
+ return JSON.stringify(input).slice(0, 200);
512
+ }
513
+ function parseClaudeMessages(messages, exitCode) {
514
+ const toolUseNames = new Map;
515
+ for (const msg of messages) {
516
+ if (msg.type === STREAM_TYPE.Assistant && Array.isArray(msg.message?.content)) {
517
+ for (const block of msg.message.content) {
518
+ if (block.type === BLOCK_TYPE.ToolUse && block.id) {
519
+ toolUseNames.set(block.id, block.name ?? "unknown");
520
+ }
521
+ }
522
+ }
523
+ }
524
+ const transcriptParts = [];
525
+ let output = "";
526
+ let sessionId;
527
+ let costUsd;
528
+ let inputTokens = 0;
529
+ let outputTokens = 0;
530
+ let cacheCreationTokens = 0;
531
+ let cacheReadTokens = 0;
532
+ let model;
533
+ for (const msg of messages) {
534
+ if (msg.type === STREAM_TYPE.Assistant && msg.message) {
535
+ const content = msg.message.content;
536
+ if (typeof content === "string") {
537
+ transcriptParts.push(`[assistant] ${content}
538
+ `);
539
+ output = content;
540
+ } else if (Array.isArray(content)) {
541
+ const texts = [];
542
+ for (const block of content) {
543
+ if (block.type === BLOCK_TYPE.Text && typeof block.text === "string") {
544
+ transcriptParts.push(`[assistant] ${block.text}
545
+ `);
546
+ texts.push(block.text);
547
+ } else if (block.type === BLOCK_TYPE.ToolUse) {
548
+ transcriptParts.push(`[tool_call] ${block.name ?? "unknown"}
549
+ `);
550
+ }
551
+ }
552
+ if (texts.length > 0)
553
+ output = texts.join(`
554
+ `);
555
+ }
556
+ } else if (msg.type === STREAM_TYPE.ToolUse) {
557
+ transcriptParts.push(`[tool_call] ${msg.name ?? "unknown"}
558
+ `);
559
+ } else if (msg.type === STREAM_TYPE.ToolResult) {
560
+ const toolName = msg.tool_use_id ? toolUseNames.get(msg.tool_use_id) : undefined;
561
+ const resultContent = typeof msg.content === "string" ? msg.content.slice(0, 200).replace(/\n/g, " ") : "";
562
+ if (toolName) {
563
+ transcriptParts.push(resultContent ? `[tool_result from ${toolName}] ${resultContent}
564
+ ` : `[tool_result from ${toolName}]
565
+ `);
566
+ } else {
567
+ transcriptParts.push(`[tool_result]
568
+ `);
569
+ }
570
+ } else if (msg.type === STREAM_TYPE.Result) {
571
+ sessionId = msg.session_id;
572
+ costUsd = msg.total_cost_usd ?? msg.cost_usd;
573
+ if (msg.result)
574
+ output = msg.result;
575
+ if (msg.model)
576
+ model = msg.model;
577
+ if (msg.usage) {
578
+ inputTokens = msg.usage.input_tokens ?? 0;
579
+ outputTokens = msg.usage.output_tokens ?? 0;
580
+ cacheCreationTokens = msg.usage.cache_creation_input_tokens ?? 0;
581
+ cacheReadTokens = msg.usage.cache_read_input_tokens ?? 0;
582
+ }
583
+ }
584
+ }
585
+ const succeeded = exitCode === 0;
586
+ const transcript = transcriptParts.join("");
587
+ const usage = inputTokens || outputTokens || costUsd !== undefined || cacheCreationTokens || cacheReadTokens ? {
588
+ input_tokens: inputTokens,
589
+ output_tokens: outputTokens,
590
+ cache_creation_input_tokens: cacheCreationTokens,
591
+ cache_read_input_tokens: cacheReadTokens,
592
+ cost_usd: costUsd ?? 0,
593
+ model: model ?? "unknown"
594
+ } : undefined;
595
+ return {
596
+ status: succeeded ? "completed" : "failed",
597
+ output: output || (succeeded ? "Session completed." : `CLI exited with code ${exitCode}`),
598
+ transcript: transcript || undefined,
599
+ usage,
600
+ cli_session_id: sessionId
601
+ };
602
+ }
603
+
604
+ // ../core/dist/adapters/claude-code/runtime.js
605
+ var NESTING_GUARD_VARS = [
606
+ "CLAUDECODE",
607
+ "CLAUDE_CODE_ENTRYPOINT",
608
+ "CLAUDE_CODE_SESSION",
609
+ "CLAUDE_CODE_PARENT_SESSION"
610
+ ];
611
+ var ANTHROPIC_AUTH_VARS = [
612
+ "ANTHROPIC_API_KEY",
613
+ "ANTHROPIC_AUTH_TOKEN"
614
+ ];
615
+
616
+ class ClaudeCodeRuntime {
617
+ config;
618
+ type = "claude";
619
+ constructor(config = {}) {
620
+ this.config = config;
621
+ }
622
+ async execute(context) {
623
+ const cwd = context.workspace.path;
624
+ const mcpConfigPath = join3(cwd, "mcp-config.json");
625
+ const args = [
626
+ "--print",
627
+ "-",
628
+ "--output-format",
629
+ "stream-json",
630
+ "--verbose",
631
+ "--dangerously-skip-permissions",
632
+ "--strict-mcp-config",
633
+ "--mcp-config",
634
+ mcpConfigPath
635
+ ];
636
+ const model = context.model ?? this.config.model;
637
+ if (model)
638
+ args.push("--model", model);
639
+ const maxTurns = context.max_turns ?? this.config.maxTurns;
640
+ if (maxTurns)
641
+ args.push("--max-turns", String(maxTurns));
642
+ if (context.resume_session_id)
643
+ args.push("--resume", context.resume_session_id);
644
+ if (context.system_prompt_append.length > 0) {
645
+ args.push("--append-system-prompt", context.system_prompt_append);
646
+ }
647
+ const env = { ...process.env };
648
+ for (const key of NESTING_GUARD_VARS)
649
+ delete env[key];
650
+ for (const key of ANTHROPIC_AUTH_VARS)
651
+ delete env[key];
652
+ if (context.env)
653
+ Object.assign(env, context.env);
654
+ const messages = [];
655
+ let pending = "";
656
+ const handleLine = (line) => {
657
+ const msg = parseStreamJsonLine(line);
658
+ if (!msg)
659
+ return;
660
+ messages.push(msg);
661
+ if (context.onStep) {
662
+ for (const step of extractStepEvents(msg)) {
663
+ context.onStep(step);
664
+ }
665
+ }
666
+ };
667
+ const result = await runCliProcess({
668
+ command: this.config.command ?? "claude",
669
+ args,
670
+ cwd,
671
+ env,
672
+ stdin: context.intent,
673
+ abortSignal: context.abort_signal,
674
+ onSpawn: ({ pid, process_group_id }) => {
675
+ context.onSpawn?.({ process_pid: pid, process_group_id });
676
+ },
677
+ onLog: (stream, chunk) => {
678
+ if (stream !== "stdout")
679
+ return;
680
+ pending += chunk;
681
+ let nl;
682
+ while ((nl = pending.indexOf(`
683
+ `)) !== -1) {
684
+ handleLine(pending.slice(0, nl));
685
+ pending = pending.slice(nl + 1);
686
+ }
687
+ }
688
+ });
689
+ if (pending)
690
+ handleLine(pending);
691
+ if (result.truncated) {
692
+ console.warn("[ClaudeCodeRuntime] stdout truncated at 4MB — result parsing may be incomplete");
693
+ }
694
+ if (result.aborted) {
695
+ return {
696
+ status: "cancelled",
697
+ output: "Session cancelled.",
698
+ process_pid: result.pid ?? undefined,
699
+ process_group_id: result.process_group_id ?? undefined
700
+ };
701
+ }
702
+ const parsed = parseClaudeMessages(messages, result.exitCode);
703
+ return {
704
+ ...parsed,
705
+ process_pid: result.pid ?? undefined,
706
+ process_group_id: result.process_group_id ?? undefined
707
+ };
708
+ }
709
+ async healthCheck() {
710
+ try {
711
+ const result = await runCliProcess({
712
+ command: this.config.command ?? "claude",
713
+ args: ["--version"],
714
+ cwd: tmpdir(),
715
+ timeoutMs: 5000,
716
+ graceMs: 0
717
+ });
718
+ return { healthy: result.exitCode === 0 };
719
+ } catch {
720
+ return {
721
+ healthy: false,
722
+ error: `Command not found: ${this.config.command ?? "claude"}`
723
+ };
724
+ }
725
+ }
726
+ async shutdown() {}
727
+ skillsDir(workspace) {
728
+ return join3(workspace.path, ".claude", "skills");
729
+ }
730
+ }
731
+
732
+ // ../core/dist/adapters/runtime-registry.js
733
+ function createDefaultRuntimeRegistry() {
734
+ return {
735
+ claude: new ClaudeCodeRuntime({})
736
+ };
737
+ }
738
+
739
+ // src/api-client.ts
740
+ import WebSocket from "ws";
741
+
742
+ class ApiClient {
743
+ cfg;
744
+ constructor(cfg) {
745
+ this.cfg = cfg;
746
+ }
747
+ async get(path2) {
748
+ const res = await fetch(this.url(path2), {
749
+ headers: { authorization: `Bearer ${this.cfg.daemonToken}` }
750
+ });
751
+ if (res.status === 204 || res.status >= 400)
752
+ return;
753
+ return await res.json();
754
+ }
755
+ async post(path2, body) {
756
+ const res = await fetch(this.url(path2), {
757
+ method: "POST",
758
+ headers: {
759
+ "content-type": "application/json",
760
+ authorization: `Bearer ${this.cfg.daemonToken}`
761
+ },
762
+ body: JSON.stringify(body)
763
+ });
764
+ if (res.status === 204)
765
+ return { status: 204, body: undefined };
766
+ const text = await res.text();
767
+ if (!text)
768
+ return { status: res.status, body: undefined };
769
+ try {
770
+ return { status: res.status, body: JSON.parse(text) };
771
+ } catch {
772
+ return { status: res.status, body: undefined };
773
+ }
774
+ }
775
+ async claim(runtimeId) {
776
+ const res = await fetch(`${this.url("/runtime/claim")}?runtime_id=${encodeURIComponent(runtimeId)}`, {
777
+ method: "POST",
778
+ headers: { authorization: `Bearer ${this.cfg.daemonToken}` }
779
+ });
780
+ if (res.status === 204)
781
+ return;
782
+ if (res.status >= 400)
783
+ return;
784
+ return await res.json();
785
+ }
786
+ openWebSocket(runtimeIds) {
787
+ const wsUrl = this.cfg.apiUrl.replace(/^http/, "ws");
788
+ const url = `${wsUrl}/runtime/ws?runtime_ids=${runtimeIds.map(encodeURIComponent).join(",")}`;
789
+ return new WebSocket(url, {
790
+ headers: { authorization: `Bearer ${this.cfg.daemonToken}` }
791
+ });
792
+ }
793
+ url(path2) {
794
+ return `${this.cfg.apiUrl}${path2}`;
795
+ }
796
+ }
797
+ // src/spawner.ts
798
+ async function runDispatch(deps, payload, abortSignal) {
799
+ const syntheticAgent = {
800
+ id: payload.agent_id,
801
+ api_key: payload.agent_api_key,
802
+ hierarchy_level: payload.agent_hierarchy_level,
803
+ runtime_config: { type: "claude" }
804
+ };
805
+ const ws = await deps.workspaceManager.ensureWorkspace({ agent: syntheticAgent });
806
+ const runtime = deps.runtime ?? new ClaudeCodeRuntime;
807
+ const buffer = [];
808
+ let flushTimer;
809
+ const flush = async () => {
810
+ if (buffer.length === 0)
811
+ return;
812
+ const events = buffer.splice(0);
813
+ try {
814
+ await deps.api.post("/runtime/events", { events });
815
+ } catch (err) {
816
+ console.warn("[daemon/spawner] /runtime/events POST failed; events dropped:", err instanceof Error ? err.message : String(err));
817
+ }
818
+ };
819
+ const scheduleFlush = () => {
820
+ if (flushTimer)
821
+ return;
822
+ flushTimer = setTimeout(() => {
823
+ flushTimer = undefined;
824
+ flush();
825
+ }, 250);
826
+ };
827
+ const onStep = (step) => {
828
+ buffer.push({
829
+ session_id: payload.session_id,
830
+ kind: step.kind,
831
+ content: step.description,
832
+ tool_name: step.tool
833
+ });
834
+ if (buffer.length >= 16)
835
+ flush();
836
+ else
837
+ scheduleFlush();
838
+ };
839
+ let result;
840
+ let runError;
841
+ try {
842
+ result = await runtime.execute({
843
+ intent: payload.intent,
844
+ workspace: ws,
845
+ system_prompt_append: payload.system_prompt_append,
846
+ model: payload.model,
847
+ max_turns: payload.max_turns,
848
+ env: payload.env,
849
+ resume_session_id: payload.resume_session_id,
850
+ abort_signal: abortSignal,
851
+ onStep
852
+ });
853
+ } catch (err) {
854
+ runError = err instanceof Error ? err : new Error(String(err));
855
+ }
856
+ if (flushTimer) {
857
+ clearTimeout(flushTimer);
858
+ flushTimer = undefined;
859
+ }
860
+ await flush();
861
+ const status = runError ? "failed" : result?.status === "completed" ? "succeeded" : result?.status === "cancelled" ? "cancelled" : "failed";
862
+ const done = {
863
+ session_id: payload.session_id,
864
+ status,
865
+ cli_session_id: result?.cli_session_id,
866
+ result_summary: result?.output ?? "",
867
+ exit_code: status === "succeeded" ? 0 : 1,
868
+ error: runError?.message,
869
+ usage: result?.usage
870
+ };
871
+ try {
872
+ await deps.api.post("/runtime/done", done);
873
+ } catch (err) {
874
+ console.error("[daemon/spawner] /runtime/done POST failed:", err instanceof Error ? err.message : String(err));
875
+ }
876
+ }
877
+
878
+ // src/claimer.ts
879
+ var DEFAULT_POLL_MS = 30000;
880
+ var DEFAULT_HEARTBEAT_MS = 15000;
881
+ var DEFAULT_WS_RECONNECT_MAX_MS = 30000;
882
+
883
+ class Claimer {
884
+ cfg;
885
+ running = false;
886
+ ws;
887
+ pollTimer;
888
+ heartbeatTimer;
889
+ wsReconnectAttempts = 0;
890
+ wsReconnectTimer;
891
+ pollIntervalMs;
892
+ heartbeatIntervalMs;
893
+ wsReconnectMaxDelayMs;
894
+ constructor(cfg) {
895
+ this.cfg = cfg;
896
+ this.pollIntervalMs = cfg.pollIntervalMs ?? DEFAULT_POLL_MS;
897
+ this.heartbeatIntervalMs = cfg.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_MS;
898
+ this.wsReconnectMaxDelayMs = cfg.wsReconnectMaxDelayMs ?? DEFAULT_WS_RECONNECT_MAX_MS;
899
+ }
900
+ start() {
901
+ if (this.running)
902
+ return;
903
+ this.running = true;
904
+ this.heartbeat();
905
+ this.heartbeatTimer = setInterval(() => void this.heartbeat(), this.heartbeatIntervalMs);
906
+ this.pollAll();
907
+ this.pollTimer = setInterval(() => void this.pollAll(), this.pollIntervalMs);
908
+ this.connectWs();
909
+ }
910
+ async stop() {
911
+ this.running = false;
912
+ if (this.pollTimer) {
913
+ clearInterval(this.pollTimer);
914
+ this.pollTimer = undefined;
915
+ }
916
+ if (this.heartbeatTimer) {
917
+ clearInterval(this.heartbeatTimer);
918
+ this.heartbeatTimer = undefined;
919
+ }
920
+ if (this.wsReconnectTimer) {
921
+ clearTimeout(this.wsReconnectTimer);
922
+ this.wsReconnectTimer = undefined;
923
+ }
924
+ if (this.ws) {
925
+ this.ws.removeAllListeners();
926
+ this.ws.close();
927
+ this.ws = undefined;
928
+ }
929
+ this.cfg.supervisor.cancelAll();
930
+ }
931
+ connectWs() {
932
+ if (!this.running)
933
+ return;
934
+ const ws = this.cfg.api.openWebSocket(this.cfg.runtimeIds);
935
+ this.ws = ws;
936
+ ws.on("open", () => {
937
+ this.wsReconnectAttempts = 0;
938
+ console.log(`[daemon] connected: ${this.cfg.runtimeIds.length} runtime(s) subscribed`);
939
+ });
940
+ ws.on("message", (raw) => {
941
+ let msg;
942
+ try {
943
+ msg = JSON.parse(raw.toString());
944
+ } catch {
945
+ return;
946
+ }
947
+ if (msg.type === "task_available" && msg.runtime_id) {
948
+ this.pollRuntime(msg.runtime_id);
949
+ } else if (msg.type === "cancel" && msg.session_id) {
950
+ this.cfg.supervisor.cancel(msg.session_id);
951
+ }
952
+ });
953
+ ws.on("close", () => {
954
+ if (!this.running)
955
+ return;
956
+ this.scheduleWsReconnect();
957
+ });
958
+ ws.on("error", (err) => {
959
+ console.warn("[daemon] ws error:", err.message);
960
+ });
961
+ }
962
+ scheduleWsReconnect() {
963
+ if (!this.running)
964
+ return;
965
+ this.wsReconnectAttempts += 1;
966
+ const delay = Math.min(1000 * Math.pow(2, this.wsReconnectAttempts - 1), this.wsReconnectMaxDelayMs);
967
+ console.warn(`[daemon] ws disconnected; reconnecting in ${delay}ms`);
968
+ this.wsReconnectTimer = setTimeout(() => {
969
+ this.wsReconnectTimer = undefined;
970
+ this.connectWs();
971
+ }, delay);
972
+ }
973
+ async heartbeat() {
974
+ if (!this.running)
975
+ return;
976
+ try {
977
+ await this.cfg.api.post("/runtime/heartbeat", {
978
+ runtime_ids: this.cfg.runtimeIds
979
+ });
980
+ } catch (err) {
981
+ console.warn("[daemon] heartbeat failed:", err instanceof Error ? err.message : String(err));
982
+ }
983
+ }
984
+ async pollAll() {
985
+ if (!this.running)
986
+ return;
987
+ await Promise.all(this.cfg.runtimeIds.map((rid) => this.pollRuntime(rid)));
988
+ }
989
+ async pollRuntime(runtimeId) {
990
+ while (this.running && this.cfg.supervisor.hasCapacity()) {
991
+ const payload = await this.cfg.api.claim(runtimeId);
992
+ if (!payload)
993
+ return;
994
+ const ctrl = this.cfg.supervisor.start(payload.session_id);
995
+ runDispatch({ api: this.cfg.api, workspaceManager: this.cfg.workspaceManager }, payload, ctrl.signal).catch((err) => console.error(`[daemon] dispatch ${payload.session_id} failed:`, err instanceof Error ? err.message : String(err))).finally(() => this.cfg.supervisor.finish(payload.session_id));
996
+ }
997
+ }
998
+ }
999
+
1000
+ // src/skills-cache.ts
1001
+ import { promises as fs2 } from "node:fs";
1002
+ import { homedir as homedir3 } from "node:os";
1003
+ import { join as join4 } from "node:path";
1004
+ function skillsCacheDir() {
1005
+ return join4(homedir3(), ".beevibe", "skills");
1006
+ }
1007
+ var VERSION_FILE = ".version";
1008
+ async function readCachedVersion() {
1009
+ try {
1010
+ return (await fs2.readFile(join4(skillsCacheDir(), VERSION_FILE), "utf8")).trim();
1011
+ } catch {
1012
+ return;
1013
+ }
1014
+ }
1015
+ async function syncSkillsCache(api) {
1016
+ const cache = skillsCacheDir();
1017
+ const cached = await readCachedVersion();
1018
+ const res = await api.get("/runtime/skills");
1019
+ if (!res) {
1020
+ if (cached)
1021
+ return cache;
1022
+ throw new Error("/runtime/skills returned no body and no local cache");
1023
+ }
1024
+ if (cached === res.version)
1025
+ return cache;
1026
+ await fs2.mkdir(cache, { recursive: true, mode: 448 });
1027
+ for (const dirent of await fs2.readdir(cache, { withFileTypes: true })) {
1028
+ if (!dirent.isDirectory())
1029
+ continue;
1030
+ if (dirent.name === "beevibe" || dirent.name.startsWith("beevibe-")) {
1031
+ await fs2.rm(join4(cache, dirent.name), { recursive: true, force: true });
1032
+ }
1033
+ }
1034
+ for (const skill of res.skills) {
1035
+ const skillDir = join4(cache, skill.name);
1036
+ await fs2.mkdir(skillDir, { recursive: true, mode: 448 });
1037
+ for (const file of skill.files) {
1038
+ const filePath = join4(skillDir, file.path);
1039
+ await fs2.mkdir(join4(filePath, ".."), { recursive: true });
1040
+ await fs2.writeFile(filePath, file.content, { mode: 384 });
1041
+ }
1042
+ }
1043
+ await fs2.writeFile(join4(cache, VERSION_FILE), res.version, { mode: 384 });
1044
+ return cache;
1045
+ }
1046
+
1047
+ // src/supervisor.ts
1048
+ var DEFAULT_MAX_CONCURRENT = 10;
1049
+
1050
+ class Supervisor {
1051
+ maxConcurrent;
1052
+ active = new Map;
1053
+ constructor(maxConcurrent = readMaxFromEnv()) {
1054
+ this.maxConcurrent = maxConcurrent;
1055
+ }
1056
+ hasCapacity() {
1057
+ return this.active.size < this.maxConcurrent;
1058
+ }
1059
+ inFlight() {
1060
+ return this.active.size;
1061
+ }
1062
+ start(sessionId) {
1063
+ if (!this.hasCapacity()) {
1064
+ throw new Error("supervisor at capacity");
1065
+ }
1066
+ const ctrl = new AbortController;
1067
+ this.active.set(sessionId, ctrl);
1068
+ return ctrl;
1069
+ }
1070
+ finish(sessionId) {
1071
+ this.active.delete(sessionId);
1072
+ }
1073
+ cancel(sessionId) {
1074
+ const ctrl = this.active.get(sessionId);
1075
+ if (!ctrl)
1076
+ return false;
1077
+ ctrl.abort();
1078
+ return true;
1079
+ }
1080
+ cancelAll() {
1081
+ for (const ctrl of this.active.values())
1082
+ ctrl.abort();
1083
+ this.active.clear();
1084
+ }
1085
+ }
1086
+ function readMaxFromEnv() {
1087
+ const raw = process.env.BEEVIBE_DAEMON_MAX_CONCURRENT;
1088
+ if (!raw)
1089
+ return DEFAULT_MAX_CONCURRENT;
1090
+ const n = Number.parseInt(raw, 10);
1091
+ if (!Number.isFinite(n) || n < 1)
1092
+ return DEFAULT_MAX_CONCURRENT;
1093
+ return n;
1094
+ }
1095
+
1096
+ // src/start.ts
1097
+ async function runStart() {
1098
+ const cfg = loadConfig();
1099
+ if (!cfg) {
1100
+ throw new Error("No daemon config found. Run `beevibe-daemon setup --api <url> --user-token <bv_u_…>` first.");
1101
+ }
1102
+ const api = new ApiClient({
1103
+ apiUrl: cfg.api_url,
1104
+ daemonToken: cfg.daemon_token
1105
+ });
1106
+ const skillsSourceDir = await syncSkillsCache(api).catch((err) => {
1107
+ console.warn("[daemon] skills sync failed; continuing without skills:", err instanceof Error ? err.message : String(err));
1108
+ return;
1109
+ });
1110
+ const runtimeRegistry = createDefaultRuntimeRegistry();
1111
+ const workspaceManager = new LocalWorkspaceManager({
1112
+ mcpServerUrl: `${cfg.api_url}/mcp`,
1113
+ runtimeRegistry,
1114
+ skillsSourceDir: skillsSourceDir ?? "/dev/null",
1115
+ workspaceRoot: process.env.WORKSPACE_ROOT
1116
+ });
1117
+ const supervisor = new Supervisor;
1118
+ const claimer = new Claimer({
1119
+ api,
1120
+ supervisor,
1121
+ workspaceManager,
1122
+ runtimeIds: cfg.runtimes.map((r) => r.id)
1123
+ });
1124
+ claimer.start();
1125
+ console.log(`[daemon] started (${cfg.daemon_id} → ${cfg.api_url}, ${cfg.runtimes.length} runtime(s))`);
1126
+ let stopped = false;
1127
+ const stop = async (signal) => {
1128
+ if (stopped)
1129
+ return;
1130
+ stopped = true;
1131
+ console.log(`[daemon] received ${signal}; stopping`);
1132
+ await claimer.stop();
1133
+ process.exit(0);
1134
+ };
1135
+ process.on("SIGINT", () => void stop("SIGINT"));
1136
+ process.on("SIGTERM", () => void stop("SIGTERM"));
1137
+ await new Promise(() => {
1138
+ return;
1139
+ });
1140
+ }
1141
+
1142
+ // src/update.ts
1143
+ import { createHash } from "node:crypto";
1144
+ import { createWriteStream, mkdtempSync, rmSync as rmSync2, chmodSync, renameSync } from "node:fs";
1145
+ import { tmpdir as tmpdir2 } from "node:os";
1146
+ import { join as join5 } from "node:path";
1147
+ import { Readable } from "node:stream";
1148
+ import { pipeline } from "node:stream/promises";
1149
+ import { createInterface } from "node:readline/promises";
1150
+ var RELEASES_API_URL = "https://api.github.com/repos/beevibe-ai/beevibe/releases/latest";
1151
+ var DOWNLOAD_BASE = "https://github.com/beevibe-ai/beevibe/releases/download";
1152
+ var FETCH_TIMEOUT_MS = 60000;
1153
+ var PLATFORM_ASSETS = {
1154
+ "darwin-arm64": "beevibe-daemon-darwin-arm64",
1155
+ "darwin-x64": "beevibe-daemon-darwin-x64",
1156
+ "linux-x64": "beevibe-daemon-linux-x64",
1157
+ "linux-arm64": "beevibe-daemon-linux-arm64"
1158
+ };
1159
+ function currentVersion() {
1160
+ return "0.0.1-rc2";
1161
+ }
1162
+ function isCompiledBinary() {
1163
+ if (!process.versions.bun)
1164
+ return false;
1165
+ return !/(bun|node)(?:\.exe)?$/.test(process.execPath);
1166
+ }
1167
+ function platformAsset() {
1168
+ return PLATFORM_ASSETS[`${process.platform}-${process.arch}`];
1169
+ }
1170
+ function compareSemver(a, b) {
1171
+ const norm = (s) => {
1172
+ const parts = s.replace(/^v/, "").split(".").map((p) => Number.parseInt(p, 10) || 0);
1173
+ return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];
1174
+ };
1175
+ const [a1, a2, a3] = norm(a);
1176
+ const [b1, b2, b3] = norm(b);
1177
+ if (a1 !== b1)
1178
+ return a1 - b1;
1179
+ if (a2 !== b2)
1180
+ return a2 - b2;
1181
+ return a3 - b3;
1182
+ }
1183
+ async function fetchLatestRelease() {
1184
+ const res = await fetch(RELEASES_API_URL, {
1185
+ headers: { Accept: "application/vnd.github+json", "User-Agent": "beevibe-daemon-updater" },
1186
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
1187
+ });
1188
+ if (res.status === 404)
1189
+ return null;
1190
+ if (!res.ok) {
1191
+ throw new Error(`GitHub API returned ${res.status}: ${await res.text()}`);
1192
+ }
1193
+ return await res.json();
1194
+ }
1195
+ async function downloadChecksum(version, assetName) {
1196
+ const url = `${DOWNLOAD_BASE}/${version}/${assetName}.sha256`;
1197
+ const res = await fetch(url, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
1198
+ if (!res.ok)
1199
+ throw new Error(`checksum ${url} failed: ${res.status}`);
1200
+ const text = await res.text();
1201
+ const hex = text.split(/\s+/)[0] ?? "";
1202
+ if (!/^[0-9a-f]{64}$/i.test(hex))
1203
+ throw new Error(`bad checksum format: ${text}`);
1204
+ return hex.toLowerCase();
1205
+ }
1206
+ async function downloadAndHash(url, destPath) {
1207
+ const res = await fetch(url, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
1208
+ if (!res.ok)
1209
+ throw new Error(`download ${url} failed: ${res.status}`);
1210
+ if (!res.body)
1211
+ throw new Error(`download ${url} returned no body`);
1212
+ const hash = createHash("sha256");
1213
+ const source = Readable.fromWeb(res.body);
1214
+ await pipeline(source, async function* (chunks) {
1215
+ for await (const chunk of chunks) {
1216
+ hash.update(chunk);
1217
+ yield chunk;
1218
+ }
1219
+ }, createWriteStream(destPath));
1220
+ return hash.digest("hex");
1221
+ }
1222
+ async function promptYesNo(question) {
1223
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
1224
+ try {
1225
+ const answer = await rl.question(`${question} [y/N] `);
1226
+ return /^y(es)?$/i.test(answer.trim());
1227
+ } finally {
1228
+ rl.close();
1229
+ }
1230
+ }
1231
+ async function runUpdate(opts = {}) {
1232
+ const current = currentVersion();
1233
+ if (!current) {
1234
+ console.log("Could not determine current daemon version.");
1235
+ console.log("If you installed via npm: npm update -g @beevibe/daemon");
1236
+ console.log("If you installed from source: git pull && pnpm install && pnpm build");
1237
+ process.exit(2);
1238
+ }
1239
+ if (!isCompiledBinary()) {
1240
+ console.log(`Current version: ${current}`);
1241
+ console.log("This binary was not produced by the standalone build path.");
1242
+ console.log("If you installed via npm: npm update -g @beevibe/daemon");
1243
+ console.log("If you installed from source: git pull && pnpm install && pnpm build");
1244
+ return;
1245
+ }
1246
+ const asset = platformAsset();
1247
+ if (!asset) {
1248
+ console.error(`Unsupported platform: ${process.platform}/${process.arch}`);
1249
+ console.error("Pre-built binaries are only published for darwin-arm64, darwin-x64, linux-x64, linux-arm64.");
1250
+ process.exit(2);
1251
+ }
1252
+ console.log(`Current version: ${current}`);
1253
+ console.log("Checking for updates…");
1254
+ const release = await fetchLatestRelease();
1255
+ if (!release) {
1256
+ console.log("No releases published yet — nothing to update to.");
1257
+ return;
1258
+ }
1259
+ const latest = release.tag_name;
1260
+ console.log(`Latest version: ${latest}`);
1261
+ if (compareSemver(current, latest) >= 0) {
1262
+ console.log("Already on the latest version.");
1263
+ return;
1264
+ }
1265
+ console.log(`Update available: ${current} → ${latest}`);
1266
+ if (!opts.skipPrompt) {
1267
+ const proceed = await promptYesNo("Install this update now?");
1268
+ if (!proceed) {
1269
+ console.log("Update cancelled.");
1270
+ return;
1271
+ }
1272
+ }
1273
+ const stagingDir = mkdtempSync(join5(tmpdir2(), "beevibe-daemon-update-"));
1274
+ const stagingPath = join5(stagingDir, asset);
1275
+ try {
1276
+ console.log(`Downloading ${asset}…`);
1277
+ const downloadUrl = `${DOWNLOAD_BASE}/${latest}/${asset}`;
1278
+ const [actualSha, expectedSha] = await Promise.all([
1279
+ downloadAndHash(downloadUrl, stagingPath),
1280
+ downloadChecksum(latest, asset)
1281
+ ]);
1282
+ if (actualSha !== expectedSha) {
1283
+ console.error(`Checksum mismatch — refusing to install.`);
1284
+ console.error(` expected: ${expectedSha}`);
1285
+ console.error(` actual: ${actualSha}`);
1286
+ process.exit(3);
1287
+ }
1288
+ chmodSync(stagingPath, 493);
1289
+ try {
1290
+ renameSync(stagingPath, process.execPath);
1291
+ } catch (err) {
1292
+ const msg = err instanceof Error ? err.message : String(err);
1293
+ console.error(`Failed to replace ${process.execPath}: ${msg}`);
1294
+ console.error(`The new binary is at ${stagingPath} — install manually if needed.`);
1295
+ return;
1296
+ }
1297
+ console.log(`Updated to ${latest}. Restart the daemon to pick up the new binary.`);
1298
+ } finally {
1299
+ rmSync2(stagingDir, { recursive: true, force: true });
1300
+ }
1301
+ }
1302
+
1303
+ // src/main.ts
1304
+ function parseFlags(argv) {
1305
+ const flags = {};
1306
+ for (let i = 0;i < argv.length; i++) {
1307
+ const arg = argv[i];
1308
+ const next = argv[i + 1];
1309
+ if ((arg === "--api" || arg === "-a") && next) {
1310
+ flags.api = next;
1311
+ i += 1;
1312
+ } else if ((arg === "--user-token" || arg === "-t") && next) {
1313
+ flags.userToken = next;
1314
+ i += 1;
1315
+ } else if (arg === "--device-name" && next) {
1316
+ flags.deviceName = next;
1317
+ i += 1;
1318
+ } else if (arg === "--external-id" && next) {
1319
+ flags.externalId = next;
1320
+ i += 1;
1321
+ }
1322
+ }
1323
+ return flags;
1324
+ }
1325
+ function printHelp() {
1326
+ console.log([
1327
+ "Usage: beevibe-daemon <command> [flags]",
1328
+ "",
1329
+ "Commands:",
1330
+ " setup Register this machine with a beevibe api server.",
1331
+ " start Run the daemon: claim pending sessions and spawn the CLI.",
1332
+ " update Check for and install a newer daemon binary (brew/curl installs).",
1333
+ "",
1334
+ "setup flags:",
1335
+ " --api, -a <url> beevibe api base URL (e.g. http://localhost:3000)",
1336
+ " --user-token, -t <bv_u_…> human bv_u_ token (one-time, used to mint a bv_d_)",
1337
+ " --device-name <name> optional friendly name (defaults to user@hostname)",
1338
+ " --external-id <id> optional stable per-machine id (defaults to hostname)",
1339
+ "",
1340
+ "update flags:",
1341
+ " --yes, -y skip the install-this-update prompt"
1342
+ ].join(`
1343
+ `));
1344
+ }
1345
+ async function main() {
1346
+ const [command, ...rest] = process.argv.slice(2);
1347
+ if (!command || command === "--help" || command === "-h") {
1348
+ printHelp();
1349
+ return;
1350
+ }
1351
+ if (command === "setup") {
1352
+ const flags = parseFlags(rest);
1353
+ if (!flags.api || !flags.userToken) {
1354
+ console.error("setup requires --api and --user-token");
1355
+ printHelp();
1356
+ process.exit(2);
1357
+ }
1358
+ const cfg = await runSetup({
1359
+ apiUrl: flags.api,
1360
+ userToken: flags.userToken,
1361
+ deviceName: flags.deviceName,
1362
+ externalId: flags.externalId
1363
+ });
1364
+ console.log(`Registered as ${cfg.daemon_id}`);
1365
+ console.log(`Runtimes: ${cfg.runtimes.map((r) => `${r.cli} (${r.id})`).join(", ")}`);
1366
+ console.log("Config saved to ~/.beevibe/config.json");
1367
+ return;
1368
+ }
1369
+ if (command === "start") {
1370
+ await runStart();
1371
+ return;
1372
+ }
1373
+ if (command === "update") {
1374
+ const skipPrompt = rest.includes("--yes") || rest.includes("-y");
1375
+ await runUpdate({ skipPrompt });
1376
+ return;
1377
+ }
1378
+ console.error(`Unknown command: ${command}`);
1379
+ printHelp();
1380
+ process.exit(2);
1381
+ }
1382
+ main().catch((err) => {
1383
+ console.error(err instanceof Error ? err.stack ?? err.message : String(err));
1384
+ process.exit(1);
1385
+ });