@cabane/cli 0.1.3 → 0.1.5

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 +104 -92
  2. package/dist/cli.js +284 -730
  3. package/package.json +4 -5
package/dist/cli.js CHANGED
@@ -3,38 +3,13 @@
3
3
  // src/cli.ts
4
4
  import { Command } from "commander";
5
5
 
6
- // src/commands/bridge.ts
7
- import { existsSync as existsSync2, readFileSync as readFileSync2, rmSync } from "fs";
8
- import { join as join2 } from "path";
6
+ // src/commands/doctor.ts
7
+ import { writeSync } from "fs";
8
+ import { homedir as homedir2 } from "os";
9
9
 
10
- // src/log.ts
11
- var BOLD = "\x1B[1m";
12
- var RED = "\x1B[31m";
13
- var GREEN = "\x1B[32m";
14
- var YELLOW = "\x1B[33m";
15
- var RESET = "\x1B[0m";
16
- var useColor = () => process.stderr.isTTY === true && !process.env.NO_COLOR;
17
- var paint = (code, s) => useColor() ? `${code}${s}${RESET}` : s;
18
- function step(msg) {
19
- process.stderr.write(`${paint(BOLD, "\u203A")} ${msg}
20
- `);
21
- }
22
- function info(msg) {
23
- process.stderr.write(` ${msg}
24
- `);
25
- }
26
- function ok(msg) {
27
- process.stderr.write(`${paint(GREEN, "\u2713")} ${msg}
28
- `);
29
- }
30
- function warn(msg) {
31
- process.stderr.write(`${paint(YELLOW, "\u26A0")} ${msg}
32
- `);
33
- }
34
- function error(msg) {
35
- process.stderr.write(`${paint(RED, "\u2717")} ${msg}
36
- `);
37
- }
10
+ // src/config.ts
11
+ import { mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
12
+ import { dirname } from "path";
38
13
 
39
14
  // src/paths.ts
40
15
  import { existsSync, readFileSync } from "fs";
@@ -52,7 +27,7 @@ function classifyHome(dir) {
52
27
  }
53
28
  if (cfg) {
54
29
  if (typeof cfg.distBase === "string" || typeof cfg.pgPort === "number") return "selfhost";
55
- if (typeof cfg.baseUrl === "string" || typeof cfg.deviceToken === "string") return "bridge";
30
+ if (typeof cfg.baseUrl === "string" || typeof cfg.deviceToken === "string") return "companion";
56
31
  }
57
32
  return "empty";
58
33
  }
@@ -75,19 +50,17 @@ function resolvePaths(root = cabaneHome(), dataDir) {
75
50
  dataDir: data,
76
51
  pgDataDir: join(data, "pg"),
77
52
  storageRoot: join(data, "storage"),
78
- bridgeHome: join(root, "bridge"),
79
53
  logsDir: join(root, "logs"),
80
54
  serverLog: join(root, "logs", "server.log"),
81
- bridgeLog: join(root, "logs", "bridge.log"),
82
55
  runDir: join(root, "run"),
83
56
  supervisorPidFile: join(root, "run", "supervisor.pid"),
84
57
  stateFile: join(root, "run", "state.json")
85
58
  };
86
59
  }
87
- function assertNotForeignBridge(root = cabaneHome()) {
88
- if (classifyHome(root) !== "bridge") return;
60
+ function assertNotForeignCompanion(root = cabaneHome()) {
61
+ if (classifyHome(root) !== "companion") return;
89
62
  throw new Error(
90
- `${root} holds a standalone @cabane/bridge config (a device pairing), not a Cabane self-host install \u2014 refusing to write here and overwrite it.
63
+ `${root} holds a standalone @cabane/companion config (a device pairing), not a Cabane self-host install \u2014 refusing to write here and overwrite it.
91
64
  Self-host defaults to ~/.cabane-selfhost; unset CABANE_HOME, or point it at a different directory.`
92
65
  );
93
66
  }
@@ -95,6 +68,49 @@ function appDirFor(paths, version) {
95
68
  return join(paths.appsDir, version);
96
69
  }
97
70
 
71
+ // src/config.ts
72
+ var DEFAULT_DIST_BASE = "https://dist.cabane.ai";
73
+ var DEFAULT_PORT = 3e3;
74
+ var DEFAULT_PG_PORT = 55432;
75
+ function defaultConfig() {
76
+ return {
77
+ distBase: DEFAULT_DIST_BASE,
78
+ port: DEFAULT_PORT,
79
+ pgPort: DEFAULT_PG_PORT
80
+ };
81
+ }
82
+ function loadConfig(root) {
83
+ const file = resolvePaths(root).configFile;
84
+ try {
85
+ const parsed = JSON.parse(readFileSync2(file, "utf8"));
86
+ return { ...defaultConfig(), ...parsed };
87
+ } catch {
88
+ return defaultConfig();
89
+ }
90
+ }
91
+ function saveConfig(config, root) {
92
+ const file = resolvePaths(root).configFile;
93
+ mkdirSync(dirname(file), { recursive: true });
94
+ writeFileSync(file, JSON.stringify(config, null, 2) + "\n", { mode: 384 });
95
+ }
96
+ function updateConfig(mutate, root) {
97
+ const config = loadConfig(root);
98
+ mutate(config);
99
+ saveConfig(config, root);
100
+ return config;
101
+ }
102
+ function pathsFor(config, root) {
103
+ return resolvePaths(root, config.dataDir);
104
+ }
105
+
106
+ // src/doctor/checks.ts
107
+ import { createRequire } from "module";
108
+ import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
109
+ import { mkdtempSync, rmSync, writeFileSync as writeFileSync2 } from "fs";
110
+ import { statfs } from "fs/promises";
111
+ import { dirname as dirname2, join as join2 } from "path";
112
+ import { pathToFileURL } from "url";
113
+
98
114
  // src/proc.ts
99
115
  import { spawn } from "child_process";
100
116
  function run(cmd, args, opts = {}) {
@@ -145,92 +161,6 @@ async function portInUse(port) {
145
161
  });
146
162
  }
147
163
 
148
- // src/commands/bridge.ts
149
- var STATE_DIRS = ["cursors", "dispatched", "completed", "resume-attempts"];
150
- async function bridgeReset(opts = {}) {
151
- const paths = resolvePaths();
152
- const bridgeCabane = join2(paths.bridgeHome, ".cabane");
153
- const running = bridgeRunning(paths.stateFile);
154
- if (running && !opts.force) {
155
- warn("the bridge is running \u2014 a reset only takes effect on the next boot.");
156
- info("stop it first (`cabane down`), or re-run with `--force` to clear anyway.");
157
- return;
158
- }
159
- let cleared = 0;
160
- for (const dir of STATE_DIRS) {
161
- const target = join2(bridgeCabane, dir);
162
- if (existsSync2(target)) {
163
- rmSync(target, { recursive: true, force: true });
164
- cleared += 1;
165
- }
166
- }
167
- if (cleared === 0) {
168
- ok("bridge resume state already clean \u2014 nothing to reset.");
169
- } else {
170
- ok(`cleared bridge resume state (${cleared} of ${STATE_DIRS.length} stores).`);
171
- info(
172
- "the bridge will start fresh (no interrupted turn to replay) on the next `cabane up`."
173
- );
174
- }
175
- }
176
- function bridgeRunning(stateFile) {
177
- try {
178
- const state = JSON.parse(readFileSync2(stateFile, "utf8"));
179
- return state.bridgePid ? pidAlive(state.bridgePid) : false;
180
- } catch {
181
- return false;
182
- }
183
- }
184
-
185
- // src/commands/doctor.ts
186
- import { writeSync } from "fs";
187
- import { homedir as homedir2 } from "os";
188
-
189
- // src/config.ts
190
- import { mkdirSync, readFileSync as readFileSync3, writeFileSync } from "fs";
191
- import { dirname } from "path";
192
- var DEFAULT_DIST_BASE = "https://dist.cabane.ai";
193
- var DEFAULT_PORT = 3e3;
194
- var DEFAULT_PG_PORT = 55432;
195
- function defaultConfig() {
196
- return {
197
- distBase: DEFAULT_DIST_BASE,
198
- port: DEFAULT_PORT,
199
- pgPort: DEFAULT_PG_PORT
200
- };
201
- }
202
- function loadConfig(root) {
203
- const file = resolvePaths(root).configFile;
204
- try {
205
- const parsed = JSON.parse(readFileSync3(file, "utf8"));
206
- return { ...defaultConfig(), ...parsed };
207
- } catch {
208
- return defaultConfig();
209
- }
210
- }
211
- function saveConfig(config, root) {
212
- const file = resolvePaths(root).configFile;
213
- mkdirSync(dirname(file), { recursive: true });
214
- writeFileSync(file, JSON.stringify(config, null, 2) + "\n", { mode: 384 });
215
- }
216
- function updateConfig(mutate, root) {
217
- const config = loadConfig(root);
218
- mutate(config);
219
- saveConfig(config, root);
220
- return config;
221
- }
222
- function pathsFor(config, root) {
223
- return resolvePaths(root, config.dataDir);
224
- }
225
-
226
- // src/doctor/checks.ts
227
- import { createRequire } from "module";
228
- import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
229
- import { mkdtempSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
230
- import { statfs } from "fs/promises";
231
- import { dirname as dirname2, join as join3 } from "path";
232
- import { pathToFileURL } from "url";
233
-
234
164
  // src/doctor/types.ts
235
165
  var pass = (id, name, detail) => ({
236
166
  id,
@@ -238,7 +168,7 @@ var pass = (id, name, detail) => ({
238
168
  status: "pass",
239
169
  detail
240
170
  });
241
- var warn2 = (id, name, detail, fix) => ({
171
+ var warn = (id, name, detail, fix) => ({
242
172
  id,
243
173
  name,
244
174
  status: "warn",
@@ -269,7 +199,7 @@ function evaluateNode(current, floor) {
269
199
  const cur = parseMajor(current);
270
200
  const min = parseMajor(floor);
271
201
  if (Number.isNaN(cur) || Number.isNaN(min)) {
272
- return warn2("node", "Node", `could not compare v${current} against floor "${floor}"`);
202
+ return warn("node", "Node", `could not compare v${current} against floor "${floor}"`);
273
203
  }
274
204
  if (cur >= min) return pass("node", "Node", `v${current} (\u2265 ${min} required)`);
275
205
  return fail(
@@ -299,7 +229,7 @@ async function checkPort(ctx) {
299
229
  }
300
230
  function ownSupervisorPid(paths) {
301
231
  try {
302
- const pid = Number(readFileSync4(paths.supervisorPidFile, "utf8").trim());
232
+ const pid = Number(readFileSync3(paths.supervisorPidFile, "utf8").trim());
303
233
  return pid > 0 && pidAlive(pid) ? pid : null;
304
234
  } catch {
305
235
  return null;
@@ -319,7 +249,7 @@ function evaluateDisk(freeBytes, where) {
319
249
  );
320
250
  }
321
251
  if (freeBytes < DISK_WARN_BYTES) {
322
- return warn2(
252
+ return warn(
323
253
  "disk",
324
254
  "Disk",
325
255
  `${gb} GB free on ${where} \u2014 low`,
@@ -336,7 +266,7 @@ async function checkDisk(ctx) {
336
266
  }
337
267
  function checkStorage(ctx) {
338
268
  const root = ctx.paths.storageRoot;
339
- const target = existsSync3(root) ? root : nearestExisting(root);
269
+ const target = existsSync2(root) ? root : nearestExisting(root);
340
270
  if (!isWritableDir(target)) {
341
271
  return fail(
342
272
  "storage",
@@ -345,7 +275,7 @@ function checkStorage(ctx) {
345
275
  `Make ${target} writable, or point Cabane elsewhere with \`CABANE_HOME=<dir>\`.`
346
276
  );
347
277
  }
348
- const note = existsSync3(root) ? "" : " (will be created on first `cabane up`)";
278
+ const note = existsSync2(root) ? "" : " (will be created on first `cabane up`)";
349
279
  return pass("storage", "Storage root", `${root} \u2014 writable${note}`);
350
280
  }
351
281
  async function resolvePgBinaries(platform, arch) {
@@ -394,7 +324,7 @@ async function checkPostgres(ctx) {
394
324
  );
395
325
  }
396
326
  const dataDir = ctx.paths.pgDataDir;
397
- if (!existsSync3(join3(dataDir, "PG_VERSION"))) {
327
+ if (!existsSync2(join2(dataDir, "PG_VERSION"))) {
398
328
  return pass(
399
329
  "postgres",
400
330
  "Embedded Postgres",
@@ -403,7 +333,7 @@ async function checkPostgres(ctx) {
403
333
  }
404
334
  const stale = stalePostmaster(dataDir);
405
335
  if (stale) {
406
- return warn2(
336
+ return warn(
407
337
  "postgres",
408
338
  "Embedded Postgres",
409
339
  `data dir initialised but holds a stale postmaster.pid (pid ${stale}, not running) \u2014 ${dataDir}`,
@@ -415,7 +345,7 @@ async function checkPostgres(ctx) {
415
345
  function stalePostmaster(dataDir) {
416
346
  try {
417
347
  const pid = Number(
418
- readFileSync4(join3(dataDir, "postmaster.pid"), "utf8").split("\n")[0]?.trim()
348
+ readFileSync3(join2(dataDir, "postmaster.pid"), "utf8").split("\n")[0]?.trim()
419
349
  );
420
350
  return pid > 0 && !pidAlive(pid) ? pid : null;
421
351
  } catch {
@@ -445,154 +375,12 @@ async function hasQuarantine(file) {
445
375
  return false;
446
376
  }
447
377
  }
448
- var CREDENTIAL_SOURCE = {
449
- subscription: "Claude subscription login (~/.claude)",
450
- "env-api-key": "ANTHROPIC_API_KEY (env or `cabane set-env`)",
451
- "env-auth-token": "ANTHROPIC_AUTH_TOKEN (env or `cabane set-env`)",
452
- "settings-api-key": "ANTHROPIC_API_KEY in Claude settings.json"
453
- };
454
- function evaluateExecutor(p) {
455
- const name = "Executor (Claude Code)";
456
- if (!p.binaryFound) {
457
- return fail(
458
- "executor",
459
- name,
460
- `\`${p.binaryName}\` is not on PATH \u2014 the house bridge has no executor to run`,
461
- "Install Claude Code (or your chosen executor) and make sure it is on PATH."
462
- );
463
- }
464
- const apiKeyOnly = p.credential === "env-api-key" || p.credential === "env-auth-token" || p.credential === "settings-api-key";
465
- if (p.forceLoginMethod === "claudeai" && apiKeyOnly) {
466
- return fail(
467
- "executor",
468
- name,
469
- "a Claude Code managed policy forces subscription login (`forceLoginMethod: claudeai`), which refuses your API key",
470
- "Log in with the required method (`claude` \u2192 /login), or remove the managed-settings force-login policy."
471
- );
472
- }
473
- if (!p.credential) {
474
- return fail(
475
- "executor",
476
- name,
477
- "no executor credential is reachable by the house bridge",
478
- "Log in to Claude Code, or persist a key with `cabane set-env ANTHROPIC_API_KEY=\u2026`, or export it before `cabane up`."
479
- );
480
- }
481
- return pass(
482
- "executor",
483
- name,
484
- `\`${p.binaryName}\` present; credential reachable via ${CREDENTIAL_SOURCE[p.credential]} (reachability only \u2014 not verified against the provider)`
485
- );
486
- }
487
- function checkExecutor(ctx) {
488
- return evaluateExecutor(probeExecutor(ctx));
489
- }
490
- function probeExecutor(ctx) {
491
- const { env, homeDir } = ctx.sys;
492
- const cred = (key) => {
493
- const v = env[key] ?? ctx.config.bridgeEnv?.[key];
494
- return typeof v === "string" && v.length > 0;
495
- };
496
- const configDir = env.CLAUDE_CONFIG_DIR ?? join3(homeDir, ".claude");
497
- let credential = null;
498
- if (cred("ANTHROPIC_API_KEY")) credential = "env-api-key";
499
- else if (cred("ANTHROPIC_AUTH_TOKEN")) credential = "env-auth-token";
500
- else if (existsSync3(join3(configDir, ".credentials.json"))) credential = "subscription";
501
- else if (settingsHasApiKey(join3(configDir, "settings.json"))) credential = "settings-api-key";
502
- return {
503
- binaryName: "claude",
504
- binaryFound: onPath("claude", env),
505
- forceLoginMethod: readForceLoginMethod(ctx.sys),
506
- credential
507
- };
508
- }
509
- function settingsHasApiKey(file) {
510
- try {
511
- const s = JSON.parse(readFileSync4(file, "utf8"));
512
- return Boolean(s.env?.ANTHROPIC_API_KEY || s.apiKeyHelper);
513
- } catch {
514
- return false;
515
- }
516
- }
517
- function managedSettingsPath(sys) {
518
- switch (sys.platform) {
519
- case "darwin":
520
- return "/Library/Application Support/ClaudeCode/managed-settings.json";
521
- case "linux":
522
- return "/etc/claude-code/managed-settings.json";
523
- case "win32":
524
- return join3(sys.env.PROGRAMDATA ?? "C:\\ProgramData", "ClaudeCode", "managed-settings.json");
525
- default:
526
- return null;
527
- }
528
- }
529
- function readForceLoginMethod(sys) {
530
- const path = managedSettingsPath(sys);
531
- if (!path) return null;
532
- try {
533
- const s = JSON.parse(readFileSync4(path, "utf8"));
534
- return s.forceLoginMethod ?? null;
535
- } catch {
536
- return null;
537
- }
538
- }
539
- function onPath(bin, env) {
540
- const dirs = (env.PATH ?? "").split(process.platform === "win32" ? ";" : ":");
541
- const names = process.platform === "win32" ? [bin, `${bin}.exe`, `${bin}.cmd`] : [bin];
542
- for (const dir of dirs) {
543
- if (!dir) continue;
544
- for (const name of names) if (existsSync3(join3(dir, name))) return true;
545
- }
546
- return false;
547
- }
548
- function evaluateVersions(input) {
549
- const name = "Bridge/server version";
550
- const { serverVersion, bridgeVersion } = input;
551
- if (!serverVersion) {
552
- return skip("versions", name, "no server artifact installed yet \u2014 run `cabane install`");
553
- }
554
- if (!bridgeVersion) {
555
- return warn2(
556
- "versions",
557
- name,
558
- `the installed artifact ships no bundled bridge to check against (server ${serverVersion})`,
559
- "Reinstall the server artifact \u2014 a current build bundles its own bridge (`cabane install`)."
560
- );
561
- }
562
- if (bridgeVersion === serverVersion) {
563
- return pass("versions", name, `bundled bridge and server both ${serverVersion} (lockstep)`);
564
- }
565
- return fail(
566
- "versions",
567
- name,
568
- `bundled bridge ${bridgeVersion} \u2260 server ${serverVersion} \u2014 a version skew (they must ship from one build)`,
569
- "Reinstall a single matching artifact with `cabane install` so the bundled bridge and server are the same version."
570
- );
571
- }
572
- function checkVersions(ctx) {
573
- return evaluateVersions({
574
- serverVersion: ctx.config.currentVersion ?? null,
575
- bridgeVersion: readBundledBridgeVersion(ctx)
576
- });
577
- }
578
- function readBundledBridgeVersion(ctx) {
579
- const version = ctx.config.currentVersion;
580
- if (!version) return null;
581
- try {
582
- const pkg = JSON.parse(
583
- readFileSync4(join3(appDirFor(ctx.paths, version), "bridge", "package.json"), "utf8")
584
- );
585
- return pkg.version ?? null;
586
- } catch {
587
- return null;
588
- }
589
- }
590
378
  function installedNodeFloor(ctx) {
591
379
  const version = ctx.config.currentVersion;
592
380
  if (!version) return null;
593
381
  try {
594
382
  const manifest = JSON.parse(
595
- readFileSync4(join3(appDirFor(ctx.paths, version), "manifest.json"), "utf8")
383
+ readFileSync3(join2(appDirFor(ctx.paths, version), "manifest.json"), "utf8")
596
384
  );
597
385
  return manifest.nodeFloor ?? null;
598
386
  } catch {
@@ -601,7 +389,7 @@ function installedNodeFloor(ctx) {
601
389
  }
602
390
  function nearestExisting(path) {
603
391
  let cur = path;
604
- while (!existsSync3(cur)) {
392
+ while (!existsSync2(cur)) {
605
393
  const parent = dirname2(cur);
606
394
  if (parent === cur) break;
607
395
  cur = parent;
@@ -610,9 +398,9 @@ function nearestExisting(path) {
610
398
  }
611
399
  function isWritableDir(dir) {
612
400
  try {
613
- const probe = mkdtempSync(join3(dir, ".cabane-doctor-"));
614
- writeFileSync2(join3(probe, "w"), "");
615
- rmSync2(probe, { recursive: true, force: true });
401
+ const probe = mkdtempSync(join2(dir, ".cabane-doctor-"));
402
+ writeFileSync2(join2(probe, "w"), "");
403
+ rmSync(probe, { recursive: true, force: true });
616
404
  return true;
617
405
  } catch {
618
406
  return false;
@@ -661,11 +449,9 @@ function rollup(s) {
661
449
  // src/commands/doctor.ts
662
450
  var CHECKS = [
663
451
  { run: checkNode },
664
- { run: checkExecutor },
665
452
  { run: checkPort },
666
453
  { run: checkPostgres },
667
454
  { run: checkStorage },
668
- { run: checkVersions },
669
455
  { run: checkDisk },
670
456
  { run: checkGatekeeper }
671
457
  ];
@@ -711,17 +497,48 @@ var COLOR = {
711
497
  [STATUS_GLYPH.skip]: "[2m"
712
498
  // dim
713
499
  };
714
- var RESET2 = "[0m";
500
+ var RESET = "[0m";
715
501
  function colorize(line) {
716
502
  if (process.stdout.isTTY !== true || process.env.NO_COLOR) return line;
717
503
  const glyph = line[0];
718
504
  const code = glyph ? COLOR[glyph] : void 0;
719
- return code ? `${code}${glyph}${RESET2}${line.slice(1)}` : line;
505
+ return code ? `${code}${glyph}${RESET}${line.slice(1)}` : line;
506
+ }
507
+
508
+ // src/commands/down.ts
509
+ import { readFileSync as readFileSync4, rmSync as rmSync2 } from "fs";
510
+ import { join as join3 } from "path";
511
+
512
+ // src/log.ts
513
+ var BOLD = "\x1B[1m";
514
+ var RED = "\x1B[31m";
515
+ var GREEN = "\x1B[32m";
516
+ var YELLOW = "\x1B[33m";
517
+ var RESET2 = "\x1B[0m";
518
+ var useColor = () => process.stderr.isTTY === true && !process.env.NO_COLOR;
519
+ var paint = (code, s) => useColor() ? `${code}${s}${RESET2}` : s;
520
+ function step(msg) {
521
+ process.stderr.write(`${paint(BOLD, "\u203A")} ${msg}
522
+ `);
523
+ }
524
+ function info(msg) {
525
+ process.stderr.write(` ${msg}
526
+ `);
527
+ }
528
+ function ok(msg) {
529
+ process.stderr.write(`${paint(GREEN, "\u2713")} ${msg}
530
+ `);
531
+ }
532
+ function warn2(msg) {
533
+ process.stderr.write(`${paint(YELLOW, "\u26A0")} ${msg}
534
+ `);
535
+ }
536
+ function error(msg) {
537
+ process.stderr.write(`${paint(RED, "\u2717")} ${msg}
538
+ `);
720
539
  }
721
540
 
722
541
  // src/commands/down.ts
723
- import { readFileSync as readFileSync5, rmSync as rmSync3 } from "fs";
724
- import { join as join4 } from "path";
725
542
  async function down() {
726
543
  const config = loadConfig();
727
544
  const paths = pathsFor(config);
@@ -735,9 +552,9 @@ async function down() {
735
552
  info("no live supervisor \u2014 reaping any orphaned children.");
736
553
  }
737
554
  for (const pid of [
738
- state.bridgePid,
555
+ state.companionPid,
739
556
  state.serverPid,
740
- readPid(join4(paths.pgDataDir, "postmaster.pid"))
557
+ readPid(join3(paths.pgDataDir, "postmaster.pid"))
741
558
  ]) {
742
559
  if (pid && pidAlive(pid)) await killPid(pid);
743
560
  }
@@ -745,10 +562,10 @@ async function down() {
745
562
  timeoutMs: 15e3,
746
563
  intervalMs: 300
747
564
  });
748
- rmSync3(paths.supervisorPidFile, { force: true });
749
- rmSync3(paths.stateFile, { force: true });
565
+ rmSync2(paths.supervisorPidFile, { force: true });
566
+ rmSync2(paths.stateFile, { force: true });
750
567
  if (free) ok("stopped");
751
- else warn(`stopped, but port ${port} still looks busy \u2014 check \`cabane status\`.`);
568
+ else warn2(`stopped, but port ${port} still looks busy \u2014 check \`cabane status\`.`);
752
569
  }
753
570
  async function killPid(pid) {
754
571
  try {
@@ -766,14 +583,14 @@ async function killPid(pid) {
766
583
  }
767
584
  function readState(file) {
768
585
  try {
769
- return JSON.parse(readFileSync5(file, "utf8"));
586
+ return JSON.parse(readFileSync4(file, "utf8"));
770
587
  } catch {
771
588
  return {};
772
589
  }
773
590
  }
774
591
  function readPid(file) {
775
592
  try {
776
- const pid = Number(readFileSync5(file, "utf8").trim());
593
+ const pid = Number(readFileSync4(file, "utf8").trim());
777
594
  return pid > 0 ? pid : null;
778
595
  } catch {
779
596
  return null;
@@ -781,13 +598,13 @@ function readPid(file) {
781
598
  }
782
599
 
783
600
  // src/commands/init.ts
784
- import { existsSync as existsSync5 } from "fs";
601
+ import { existsSync as existsSync4 } from "fs";
785
602
  import { homedir as homedir3 } from "os";
786
- import { isAbsolute, join as join5, resolve } from "path";
603
+ import { isAbsolute, join as join4, resolve } from "path";
787
604
 
788
605
  // src/artifact.ts
789
606
  import { execFileSync } from "child_process";
790
- import { existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
607
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
791
608
  function readTarballManifest(tarball) {
792
609
  const raw = execFileSync("tar", ["-xzOf", tarball, "./manifest.json"], {
793
610
  maxBuffer: 16 * 1024 * 1024
@@ -805,7 +622,7 @@ async function npmInstall(appDir) {
805
622
  });
806
623
  }
807
624
  function isInstalled(appDir) {
808
- return existsSync4(appDir) && existsSync4(`${appDir}/node_modules`) && existsSync4(`${appDir}/server.js`);
625
+ return existsSync3(appDir) && existsSync3(`${appDir}/node_modules`) && existsSync3(`${appDir}/server.js`);
809
626
  }
810
627
 
811
628
  // src/prompt.ts
@@ -846,14 +663,13 @@ async function promptSecret(question) {
846
663
  }
847
664
 
848
665
  // src/commands/init.ts
849
- var EXECUTOR_KEY = "ANTHROPIC_API_KEY";
850
666
  async function init() {
851
667
  if (!isInteractive()) {
852
668
  throw new Error(
853
669
  "`cabane init` is an interactive wizard \u2014 run it in a terminal, or configure non-interactively with `cabane install --port \u2026` + `cabane set-env KEY=VALUE`."
854
670
  );
855
671
  }
856
- assertNotForeignBridge();
672
+ assertNotForeignCompanion();
857
673
  const root = cabaneHome();
858
674
  const config = loadConfig();
859
675
  step("Configure your self-hosted Cabane");
@@ -869,9 +685,9 @@ async function init() {
869
685
  const defaultDataDir = resolvePaths(root).dataDir;
870
686
  config.dataDir = dataDir === defaultDataDir ? void 0 : dataDir;
871
687
  const oldPg = pathsFor(loadConfig(), root).pgDataDir;
872
- const newPg = join5(dataDir, "pg");
873
- if (oldPg !== newPg && existsSync5(join5(oldPg, "PG_VERSION"))) {
874
- warn(`existing database at ${oldPg} will NOT move \u2014 a fresh one initializes at ${newPg}.`);
688
+ const newPg = join4(dataDir, "pg");
689
+ if (oldPg !== newPg && existsSync4(join4(oldPg, "PG_VERSION"))) {
690
+ warn2(`existing database at ${oldPg} will NOT move \u2014 a fresh one initializes at ${newPg}.`);
875
691
  info(
876
692
  "to keep your current data, copy that directory over, or leave the location unchanged."
877
693
  );
@@ -888,15 +704,6 @@ async function init() {
888
704
  }
889
705
  config.publicUrl = urlAnswer.replace(/\/$/, "");
890
706
  }
891
- info("");
892
- info(
893
- `Executor API key \u2014 for API-key billing (stored as ${EXECUTOR_KEY}). Leave blank if your`
894
- );
895
- info("executor is already logged in (e.g. a Claude subscription under ~/.claude).");
896
- const key = await promptSecret(`${EXECUTOR_KEY} (hidden, blank to skip): `);
897
- if (key) {
898
- config.bridgeEnv = { ...config.bridgeEnv ?? {}, [EXECUTOR_KEY]: key };
899
- }
900
707
  saveConfig(config, root);
901
708
  const paths = pathsFor(config, root);
902
709
  info("");
@@ -906,9 +713,6 @@ async function init() {
906
713
  info(` database ${paths.pgDataDir}`);
907
714
  info(` port ${config.port}`);
908
715
  info(` url ${config.publicUrl ?? `http://localhost:${config.port}`}`);
909
- info(
910
- ` ${EXECUTOR_KEY} ${config.bridgeEnv?.[EXECUTOR_KEY] ? mask(config.bridgeEnv[EXECUTOR_KEY]) : "(not set \u2014 using on-disk executor auth)"}`
911
- );
912
716
  info("");
913
717
  const installed = config.currentVersion && isInstalled(appDirFor(paths, config.currentVersion));
914
718
  if (installed) {
@@ -920,7 +724,7 @@ async function init() {
920
724
  function expandPath(input, root) {
921
725
  let p = input;
922
726
  if (p === "~") p = homedir3();
923
- else if (p.startsWith("~/")) p = join5(homedir3(), p.slice(2));
727
+ else if (p.startsWith("~/")) p = join4(homedir3(), p.slice(2));
924
728
  return isAbsolute(p) ? p : resolve(root, p);
925
729
  }
926
730
  async function promptPort(current) {
@@ -930,20 +734,16 @@ async function promptPort(current) {
930
734
  if (!answer) return current;
931
735
  const n = Number(answer);
932
736
  if (Number.isInteger(n) && n > 0 && n <= 65535) return n;
933
- warn(`invalid port: ${answer} \u2014 enter a number between 1 and 65535.`);
737
+ warn2(`invalid port: ${answer} \u2014 enter a number between 1 and 65535.`);
934
738
  }
935
739
  throw new Error("no valid port after several tries \u2014 re-run `cabane init`.");
936
740
  }
937
- function mask(value) {
938
- if (value.length <= 8) return "\u2022\u2022\u2022\u2022";
939
- return `${value.slice(0, 4)}\u2026${value.slice(-4)}`;
940
- }
941
741
 
942
742
  // src/commands/install.ts
943
743
  import { randomBytes } from "crypto";
944
- import { existsSync as existsSync6, mkdirSync as mkdirSync3, rmSync as rmSync4 } from "fs";
744
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3, rmSync as rmSync3 } from "fs";
945
745
  import { tmpdir } from "os";
946
- import { join as join6 } from "path";
746
+ import { join as join5 } from "path";
947
747
 
948
748
  // src/dist.ts
949
749
  import { createHash } from "crypto";
@@ -994,10 +794,10 @@ async function downloadArtifact(distBase, token, manifest, destPath) {
994
794
 
995
795
  // src/commands/install.ts
996
796
  async function install(opts) {
997
- assertNotForeignBridge();
797
+ assertNotForeignCompanion();
998
798
  const config = loadConfig();
999
799
  const paths = pathsFor(config);
1000
- const dataExisted = existsSync6(paths.dataDir);
800
+ const dataExisted = existsSync5(paths.dataDir);
1001
801
  if (opts.distBase) config.distBase = opts.distBase.replace(/\/$/, "");
1002
802
  if (opts.port) config.port = opts.port;
1003
803
  mkdirSync3(paths.appsDir, { recursive: true });
@@ -1005,7 +805,7 @@ async function install(opts) {
1005
805
  let version;
1006
806
  let cleanup = null;
1007
807
  if (opts.from) {
1008
- if (!existsSync6(opts.from)) throw new Error(`--from tarball not found: ${opts.from}`);
808
+ if (!existsSync5(opts.from)) throw new Error(`--from tarball not found: ${opts.from}`);
1009
809
  tarball = opts.from;
1010
810
  version = readTarballManifest(tarball).version;
1011
811
  step(`installing from local tarball \u2192 version ${version}`);
@@ -1021,18 +821,18 @@ async function install(opts) {
1021
821
  }
1022
822
  version = manifest.version;
1023
823
  info(`resolved ${version} (${(manifest.bytes / 1024 / 1024).toFixed(1)} MB)`);
1024
- const tmp = join6(tmpdir(), `cabane-artifact-${Date.now().toString(36)}.tgz`);
824
+ const tmp = join5(tmpdir(), `cabane-artifact-${Date.now().toString(36)}.tgz`);
1025
825
  await downloadArtifact(config.distBase, token, manifest, tmp);
1026
826
  ok("downloaded + sha256 verified");
1027
827
  tarball = tmp;
1028
- cleanup = () => rmSync4(tmp, { force: true });
828
+ cleanup = () => rmSync3(tmp, { force: true });
1029
829
  }
1030
830
  try {
1031
831
  const appDir = appDirFor(paths, version);
1032
832
  if (isInstalled(appDir) && !opts.force) {
1033
833
  info(`version ${version} already installed \u2014 reusing (pass --force to reinstall)`);
1034
834
  } else {
1035
- rmSync4(appDir, { recursive: true, force: true });
835
+ rmSync3(appDir, { recursive: true, force: true });
1036
836
  step(`unpacking \u2192 ${appDir}`);
1037
837
  unpackArtifact(tarball, appDir);
1038
838
  step("npm install --omit=dev (runtime deps)\u2026");
@@ -1055,11 +855,11 @@ async function resolveToken(opts, config) {
1055
855
  if (opts.token) return opts.token.trim();
1056
856
  if (config.distToken) return config.distToken;
1057
857
  if (isInteractive()) {
1058
- const t = await promptSecret("Paste your Cabane access token (cabdist_\u2026): ");
858
+ const t = await promptSecret("Paste your Cabane install token (cabdist_\u2026): ");
1059
859
  if (t) return t;
1060
860
  }
1061
861
  throw new Error(
1062
- "no distribution token. Pass --token <cabdist_\u2026>, or --from <tarball> to install a local build."
862
+ "no install token. Pass --token <cabdist_\u2026>, or --from <tarball> to install a local build."
1063
863
  );
1064
864
  }
1065
865
  function persistInstall(config, version) {
@@ -1072,49 +872,45 @@ function persistInstall(config, version) {
1072
872
  }
1073
873
 
1074
874
  // src/commands/logs.ts
1075
- import { existsSync as existsSync7 } from "fs";
875
+ import { existsSync as existsSync6 } from "fs";
1076
876
  async function logs(opts) {
1077
877
  const paths = resolvePaths();
1078
878
  const n = String(opts.lines ?? 80);
1079
- const targets = [];
1080
- if (opts.server || !opts.bridge) targets.push(paths.serverLog);
1081
- if (opts.bridge || !opts.server) targets.push(paths.bridgeLog);
1082
- const present = targets.filter((t) => existsSync7(t));
1083
- if (present.length === 0) {
879
+ if (!existsSync6(paths.serverLog)) {
1084
880
  info("no logs yet \u2014 start the stack with `cabane up`.");
1085
881
  return;
1086
882
  }
1087
- const args = opts.follow ? ["-n", n, "-F", ...present] : ["-n", n, ...present];
883
+ const args = opts.follow ? ["-n", n, "-F", paths.serverLog] : ["-n", n, paths.serverLog];
1088
884
  await run("tail", args, { stdio: ["ignore", "inherit", "inherit"] });
1089
885
  }
1090
886
 
1091
887
  // src/commands/set-env.ts
1092
888
  var KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
1093
- function mask2(value) {
889
+ function mask(value) {
1094
890
  if (value.length <= 8) return "\u2022\u2022\u2022\u2022";
1095
891
  return `${value.slice(0, 4)}\u2026${value.slice(-4)}`;
1096
892
  }
1097
893
  function printList() {
1098
- const env = loadConfig().bridgeEnv ?? {};
894
+ const env = loadConfig().companionEnv ?? {};
1099
895
  const keys = Object.keys(env);
1100
896
  if (keys.length === 0) {
1101
- info("no persisted bridge env. Add one: `cabane set-env ANTHROPIC_API_KEY=\u2026`");
897
+ info("no persisted server env. Add one: `cabane set-env KEY=VALUE`");
1102
898
  return;
1103
899
  }
1104
- step("persisted bridge env:");
1105
- for (const key of keys) info(`${key}=${mask2(env[key])}`);
900
+ step("persisted server env:");
901
+ for (const key of keys) info(`${key}=${mask(env[key])}`);
1106
902
  }
1107
903
  async function setEnv(pairs, opts) {
1108
904
  if (opts.list || pairs.length === 0 && !opts.unset?.length) {
1109
905
  printList();
1110
906
  return;
1111
907
  }
1112
- assertNotForeignBridge();
908
+ assertNotForeignCompanion();
1113
909
  if (opts.unset?.length) {
1114
910
  updateConfig((config) => {
1115
- if (!config.bridgeEnv) return;
1116
- for (const key of opts.unset) delete config.bridgeEnv[key];
1117
- if (Object.keys(config.bridgeEnv).length === 0) delete config.bridgeEnv;
911
+ if (!config.companionEnv) return;
912
+ for (const key of opts.unset) delete config.companionEnv[key];
913
+ if (Object.keys(config.companionEnv).length === 0) delete config.companionEnv;
1118
914
  });
1119
915
  ok(`unset ${opts.unset.join(", ")}`);
1120
916
  }
@@ -1141,16 +937,13 @@ async function setEnv(pairs, opts) {
1141
937
  }
1142
938
  if (Object.keys(parsed).length > 0) {
1143
939
  updateConfig((config) => {
1144
- config.bridgeEnv = { ...config.bridgeEnv ?? {}, ...parsed };
940
+ config.companionEnv = { ...config.companionEnv ?? {}, ...parsed };
1145
941
  });
1146
- for (const key of Object.keys(parsed)) ok(`set ${key}=${mask2(parsed[key])}`);
942
+ for (const key of Object.keys(parsed)) ok(`set ${key}=${mask(parsed[key])}`);
1147
943
  info("restart to apply: `cabane down && cabane up --daemon`");
1148
944
  }
1149
945
  }
1150
946
 
1151
- // src/commands/setup-agent.ts
1152
- import { existsSync as existsSync10 } from "fs";
1153
-
1154
947
  // src/api.ts
1155
948
  var ApiError = class extends Error {
1156
949
  constructor(message, status2, body) {
@@ -1196,10 +989,13 @@ function makeClient(base, token) {
1196
989
  }
1197
990
 
1198
991
  // src/env-compose.ts
1199
- import { join as join7 } from "path";
992
+ import { join as join6 } from "path";
1200
993
  function composeServerEnv(opts) {
1201
- const dataDir = join7(opts.appDir, "data");
994
+ const dataDir = join6(opts.appDir, "data");
1202
995
  return {
996
+ // Persisted operator creds FIRST so the composed infra vars below always win —
997
+ // an operator can't shadow DATABASE_URL / NODE_ENV.
998
+ ...opts.persistedEnv ?? {},
1203
999
  NODE_ENV: "production",
1204
1000
  PORT: String(opts.port),
1205
1001
  DATABASE_URL: opts.databaseUrl,
@@ -1209,24 +1005,11 @@ function composeServerEnv(opts) {
1209
1005
  PUBLIC_BASE_URL: opts.publicBaseUrl?.replace(/\/$/, "") || `http://localhost:${opts.port}`,
1210
1006
  VISION_URL_SIGNING_SECRET: opts.visionSecret,
1211
1007
  // Runtime file-read overrides → the artifact's own data/ tree.
1212
- CABANE_API_ROOT: join7(dataDir, "api"),
1213
- CABANE_SKILLS_DIR: join7(dataDir, "skills"),
1214
- CABANE_DOCS_CONTENT_DIR: join7(dataDir, "docs-content"),
1215
- CABANE_WELCOME_DIR: join7(dataDir, "welcome"),
1216
- ...opts.houseOwnerUserId ? { CABANE_HOUSE_OWNER_USER_ID: opts.houseOwnerUserId } : {},
1008
+ CABANE_API_ROOT: join6(dataDir, "api"),
1009
+ CABANE_SKILLS_DIR: join6(dataDir, "skills"),
1217
1010
  ...opts.appVersion ? { CABANE_APP_VERSION: opts.appVersion } : {}
1218
1011
  };
1219
1012
  }
1220
- function composeBridgeEnv(opts) {
1221
- return {
1222
- ...opts.persistedEnv,
1223
- ...opts.parentEnv,
1224
- CABANE_BRIDGE_HOME: opts.bridgeHome,
1225
- ...opts.claudeConfigDir ? { CLAUDE_CONFIG_DIR: opts.claudeConfigDir } : {},
1226
- BRIDGE_NO_OPEN: "1",
1227
- CABANE_BRIDGE_CLASS: "house"
1228
- };
1229
- }
1230
1013
  function localBaseUrl(port) {
1231
1014
  return `http://localhost:${port}`;
1232
1015
  }
@@ -1234,123 +1017,112 @@ function embeddedDatabaseUrl(port) {
1234
1017
  return `postgres://cabane:cabane@127.0.0.1:${port}/cabane`;
1235
1018
  }
1236
1019
 
1237
- // src/supervisor/house-bridge.ts
1238
- import { spawn as spawn2 } from "child_process";
1239
- import { mkdirSync as mkdirSync4, openSync, readFileSync as readFileSync6, rmSync as rmSync5, writeFileSync as writeFileSync3 } from "fs";
1240
- import { dirname as dirname3, join as join8 } from "path";
1241
- var PAIRING_VERSION = 1;
1242
- function resolveBridgeBin(appDir) {
1243
- const pkgPath = join8(appDir, "bridge", "package.json");
1244
- const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
1245
- const rel = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.["cabane-bridge"];
1246
- if (!rel) {
1247
- throw new Error(
1248
- `the installed artifact's bridge (${pkgPath}) exposes no cabane-bridge bin \u2014 reinstall.`
1020
+ // src/commands/setup-agent.ts
1021
+ async function setupAgent(opts) {
1022
+ const config = loadConfig();
1023
+ const base = localBaseUrl(config.port);
1024
+ const instanceUrl = config.publicUrl ?? base;
1025
+ if (!await healthy(config.port)) {
1026
+ throw new Error(`server is not up on ${base} \u2014 run \`cabane up\` and sign up first.`);
1027
+ }
1028
+ const token = await resolvePat(opts, base);
1029
+ const client = makeClient(base, token);
1030
+ const me = await client.get("/api/auth/me");
1031
+ ok(`owner resolved (${me.user.id})`);
1032
+ const agentName = await defaultAgentName(client);
1033
+ ok("setup complete");
1034
+ info("");
1035
+ info(`Your agent (@${agentName}) has no executor yet \u2014 agents run on a companion you pair,`);
1036
+ info("the same way they do on hosted Cabane. To bring it to life:");
1037
+ info(" 1. Install the Companion \u2014 the desktop app, or the headless `@cabane/companion`.");
1038
+ info(` 2. Sign in against this instance (${instanceUrl}) and pair the device.`);
1039
+ info(` 3. Assign @${agentName} to one of its connectors, then message it in the app.`);
1040
+ }
1041
+ async function healthy(port) {
1042
+ try {
1043
+ return (await fetch(`http://127.0.0.1:${port}/healthz`)).ok;
1044
+ } catch {
1045
+ return false;
1046
+ }
1047
+ }
1048
+ async function resolvePat(opts, base) {
1049
+ if (opts.token) return opts.token.trim();
1050
+ if (isInteractive()) {
1051
+ info(`mint a token in the app: ${base} \u2192 Settings \u2192 Developer \u2192 tokens`);
1052
+ const t = await promptSecret("Paste a Cabane access token (cab_\u2026): ");
1053
+ if (t) return t;
1054
+ }
1055
+ throw new Error("no access token. Pass --token <cab_\u2026> (mint it in Settings \u2192 Developer).");
1056
+ }
1057
+ async function defaultAgentName(client) {
1058
+ try {
1059
+ const { workspaces } = await client.get("/api/workspaces");
1060
+ const workspace = workspaces.find((w) => w.role === "owner") ?? workspaces[0];
1061
+ if (!workspace) return "cabane";
1062
+ const { agents } = await client.get(
1063
+ `/api/workspaces/${workspace.id}/agents`
1249
1064
  );
1065
+ const agent = workspace.defaultAgentId && agents.find((a) => a.id === workspace.defaultAgentId) || agents.find((a) => a.username === "cabane") || agents[0];
1066
+ return agent?.username ?? "cabane";
1067
+ } catch {
1068
+ return "cabane";
1250
1069
  }
1251
- return join8(dirname3(pkgPath), rel);
1252
1070
  }
1253
- function encodePairingString(input) {
1254
- const payload = {
1255
- v: PAIRING_VERSION,
1256
- baseUrl: input.baseUrl,
1257
- deviceToken: input.deviceToken,
1258
- ...input.deviceId ? { deviceId: input.deviceId } : {},
1259
- ...input.deviceLabel ? { deviceLabel: input.deviceLabel } : {}
1071
+
1072
+ // src/commands/status.ts
1073
+ import { readFileSync as readFileSync5 } from "fs";
1074
+ async function status() {
1075
+ const config = loadConfig();
1076
+ const paths = pathsFor(config);
1077
+ const state = readState2(paths.stateFile);
1078
+ const supervisorPid = readSupervisorPid(paths.supervisorPidFile) ?? state.supervisorPid;
1079
+ const supervisorUp = supervisorPid ? pidAlive(supervisorPid) : false;
1080
+ const serverUp = await healthy2(config.port);
1081
+ const line = (label, value) => {
1082
+ process.stdout.write(` ${label.padEnd(12)}${value}
1083
+ `);
1260
1084
  };
1261
- return Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
1085
+ process.stdout.write("cabane\n");
1086
+ line("version", config.currentVersion ?? "(not installed)");
1087
+ if (config.previousVersion) line("previous", config.previousVersion);
1088
+ line("port", String(config.port));
1089
+ line("home", paths.root);
1090
+ line("data", `${paths.dataDir} (pg + storage \xB7 kept across upgrades)`);
1091
+ line("supervisor", supervisorUp ? `up (pid ${supervisorPid})` : "down");
1092
+ line("server", serverUp ? `up \xB7 http://localhost:${config.port}` : "down");
1093
+ if (!supervisorUp) info("start it: cabane up");
1262
1094
  }
1263
- async function pairBridge(opts) {
1264
- mkdirSync4(opts.bridgeHome, { recursive: true });
1265
- const pairingString = encodePairingString(opts);
1266
- const tmp = join8(opts.bridgeHome, `.pairing-${process.pid}.txt`);
1267
- writeFileSync3(tmp, pairingString, { mode: 384 });
1095
+ function readState2(file) {
1268
1096
  try {
1269
- await run("node", [resolveBridgeBin(opts.appDir), "pair", "--file", tmp], {
1270
- env: {
1271
- PATH: process.env.PATH ?? "",
1272
- ...process.env.HOME ? { HOME: process.env.HOME } : {},
1273
- CABANE_BRIDGE_HOME: opts.bridgeHome
1274
- }
1275
- });
1276
- } finally {
1277
- rmSync5(tmp, { force: true });
1097
+ return JSON.parse(readFileSync5(file, "utf8"));
1098
+ } catch {
1099
+ return {};
1278
1100
  }
1279
- ok("bridge paired");
1280
1101
  }
1281
- function bridgeConfigPath(bridgeHome) {
1282
- return join8(bridgeHome, ".cabane", "config.json");
1283
- }
1284
- function readBridgeConfig(bridgeHome) {
1102
+ function readSupervisorPid(file) {
1285
1103
  try {
1286
- return JSON.parse(readFileSync6(bridgeConfigPath(bridgeHome), "utf8"));
1104
+ const pid = Number(readFileSync5(file, "utf8").trim());
1105
+ return pid > 0 ? pid : null;
1287
1106
  } catch {
1288
1107
  return null;
1289
1108
  }
1290
1109
  }
1291
- var BridgeChild = class {
1292
- constructor(appDir, bridgeHome, claudeConfigDir, logFile, persistedEnv) {
1293
- this.appDir = appDir;
1294
- this.bridgeHome = bridgeHome;
1295
- this.claudeConfigDir = claudeConfigDir;
1296
- this.logFile = logFile;
1297
- this.persistedEnv = persistedEnv;
1298
- }
1299
- appDir;
1300
- bridgeHome;
1301
- claudeConfigDir;
1302
- logFile;
1303
- persistedEnv;
1304
- child = null;
1305
- /** Spawn `cabane-bridge start --no-open` with the forwarded shell env under the
1306
- * house-class overrides (CT480). */
1307
- start() {
1308
- const out = openSync(this.logFile, "a");
1309
- this.child = spawn2("node", [resolveBridgeBin(this.appDir), "start", "--no-open"], {
1310
- env: composeBridgeEnv({
1311
- parentEnv: process.env,
1312
- bridgeHome: this.bridgeHome,
1313
- claudeConfigDir: this.claudeConfigDir,
1314
- persistedEnv: this.persistedEnv
1315
- }),
1316
- stdio: ["ignore", out, out]
1317
- });
1318
- this.child.on("exit", (code, signal) => {
1319
- if (code && code !== 0)
1320
- warn(`bridge exited (code ${code}${signal ? `, ${signal}` : ""})`);
1321
- this.child = null;
1322
- });
1323
- ok("house bridge started");
1324
- }
1325
- get running() {
1326
- return this.child !== null;
1327
- }
1328
- get pid() {
1329
- return this.child?.pid;
1330
- }
1331
- async stop() {
1332
- const child = this.child;
1333
- if (!child) return;
1334
- this.child = null;
1335
- await new Promise((resolve2) => {
1336
- const kill9 = setTimeout(() => child.kill("SIGKILL"), 5e3);
1337
- child.once("exit", () => {
1338
- clearTimeout(kill9);
1339
- resolve2();
1340
- });
1341
- child.kill("SIGTERM");
1342
- });
1110
+ async function healthy2(port) {
1111
+ try {
1112
+ return (await fetch(`http://127.0.0.1:${port}/healthz`)).ok;
1113
+ } catch {
1114
+ return false;
1343
1115
  }
1344
- };
1116
+ }
1345
1117
 
1346
1118
  // src/commands/up.ts
1347
- import { spawn as spawn4 } from "child_process";
1348
- import { existsSync as existsSync9, mkdirSync as mkdirSync5, openSync as openSync3, readFileSync as readFileSync7, rmSync as rmSync6, writeFileSync as writeFileSync4 } from "fs";
1349
- import { join as join10 } from "path";
1119
+ import { spawn as spawn3 } from "child_process";
1120
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, openSync as openSync2, readFileSync as readFileSync6, rmSync as rmSync4, writeFileSync as writeFileSync3 } from "fs";
1121
+ import { join as join8 } from "path";
1350
1122
 
1351
1123
  // src/supervisor/postgres.ts
1352
- import { existsSync as existsSync8 } from "fs";
1353
- import { join as join9 } from "path";
1124
+ import { existsSync as existsSync7 } from "fs";
1125
+ import { join as join7 } from "path";
1354
1126
  import EmbeddedPostgres from "embedded-postgres";
1355
1127
  var EmbeddedPg = class {
1356
1128
  constructor(dataDir, port) {
@@ -1373,11 +1145,11 @@ var EmbeddedPg = class {
1373
1145
  onLog: () => {
1374
1146
  },
1375
1147
  // quiet — pg writes its own logs under the data dir
1376
- onError: (err) => warn(`[pg] ${err instanceof Error ? err.message : String(err)}`)
1148
+ onError: (err) => warn2(`[pg] ${err instanceof Error ? err.message : String(err)}`)
1377
1149
  });
1378
1150
  }
1379
1151
  initialised() {
1380
- return existsSync8(join9(this.dataDir, "PG_VERSION"));
1152
+ return existsSync7(join7(this.dataDir, "PG_VERSION"));
1381
1153
  }
1382
1154
  /** Init (first run) + start the postmaster + ensure the app database exists. */
1383
1155
  async start() {
@@ -1405,8 +1177,8 @@ var EmbeddedPg = class {
1405
1177
  };
1406
1178
 
1407
1179
  // src/supervisor/server.ts
1408
- import { openSync as openSync2 } from "fs";
1409
- import { spawn as spawn3 } from "child_process";
1180
+ import { openSync } from "fs";
1181
+ import { spawn as spawn2 } from "child_process";
1410
1182
  var ServerChild = class {
1411
1183
  constructor(appDir, env, port, logFile) {
1412
1184
  this.appDir = appDir;
@@ -1439,15 +1211,15 @@ var ServerChild = class {
1439
1211
  }
1440
1212
  /** Spawn `node server.js` and wait for /healthz. */
1441
1213
  async start() {
1442
- const out = openSync2(this.logFile, "a");
1443
- this.child = spawn3("node", ["server.js"], {
1214
+ const out = openSync(this.logFile, "a");
1215
+ this.child = spawn2("node", ["server.js"], {
1444
1216
  cwd: this.appDir,
1445
1217
  env: this.childEnv(),
1446
1218
  stdio: ["ignore", out, out]
1447
1219
  });
1448
1220
  this.child.on("exit", (code, signal) => {
1449
1221
  if (code && code !== 0)
1450
- warn(`server exited (code ${code}${signal ? `, ${signal}` : ""})`);
1222
+ warn2(`server exited (code ${code}${signal ? `, ${signal}` : ""})`);
1451
1223
  this.child = null;
1452
1224
  });
1453
1225
  const healthy3 = await waitUntil(() => this.healthy(), { timeoutMs: 45e3, intervalMs: 500 });
@@ -1512,18 +1284,18 @@ async function up(opts) {
1512
1284
  await new Supervisor(paths, appDir, config.port).run();
1513
1285
  }
1514
1286
  async function launchDaemon(paths, config) {
1515
- if (existsSync9(paths.supervisorPidFile)) {
1516
- const pid = Number(readFileSync7(paths.supervisorPidFile, "utf8").trim());
1287
+ if (existsSync8(paths.supervisorPidFile)) {
1288
+ const pid = Number(readFileSync6(paths.supervisorPidFile, "utf8").trim());
1517
1289
  if (pid && pidAlive(pid)) {
1518
- warn(`already running (supervisor pid ${pid}). Use \`cabane down\` to stop it.`);
1290
+ warn2(`already running (supervisor pid ${pid}). Use \`cabane down\` to stop it.`);
1519
1291
  return;
1520
1292
  }
1521
1293
  }
1522
- mkdirSync5(paths.logsDir, { recursive: true });
1294
+ mkdirSync4(paths.logsDir, { recursive: true });
1523
1295
  const entry = process.argv[1];
1524
1296
  if (!entry) throw new Error("cannot locate the cabane CLI entry to run in the background.");
1525
- const out = openSync3(paths.serverLog, "a");
1526
- const child = spawn4(process.execPath, [entry, "up", "--port", String(config.port)], {
1297
+ const out = openSync2(paths.serverLog, "a");
1298
+ const child = spawn3(process.execPath, [entry, "up", "--port", String(config.port)], {
1527
1299
  detached: true,
1528
1300
  stdio: ["ignore", out, out]
1529
1301
  });
@@ -1544,18 +1316,13 @@ async function launchDaemon(paths, config) {
1544
1316
  throw new Error(`background start did not come healthy within 60s \u2014 see ${paths.serverLog}`);
1545
1317
  }
1546
1318
  ok(`running in the background on http://localhost:${config.port}`);
1547
- for (const line of daemonFooter(config)) info(line);
1319
+ for (const line of daemonFooter()) info(line);
1548
1320
  }
1549
- function daemonFooter(config) {
1550
- const lines = [];
1551
- if (!config.houseOwnerUserId) {
1552
- lines.push("next: open the URL, sign up, then `cabane setup-agent`");
1553
- }
1554
- lines.push("logs: cabane logs \xB7 stop: cabane down");
1555
- return lines;
1556
- }
1557
- function resolveClaudeConfigDir(env = process.env) {
1558
- return env.CLAUDE_CONFIG_DIR;
1321
+ function daemonFooter() {
1322
+ return [
1323
+ "next: open the URL, sign up, then pair a companion so your agents can answer \u2014 `cabane setup-agent` explains how",
1324
+ "logs: cabane logs \xB7 stop: cabane down"
1325
+ ];
1559
1326
  }
1560
1327
  var Supervisor = class {
1561
1328
  constructor(paths, appDir, port) {
@@ -1569,7 +1336,6 @@ var Supervisor = class {
1569
1336
  port;
1570
1337
  pg;
1571
1338
  server = null;
1572
- bridge = null;
1573
1339
  shuttingDown = false;
1574
1340
  buildServer() {
1575
1341
  const config = loadConfig();
@@ -1580,9 +1346,11 @@ var Supervisor = class {
1580
1346
  storageRoot: this.paths.storageRoot,
1581
1347
  visionSecret: config.visionSecret,
1582
1348
  ...config.publicUrl ? { publicBaseUrl: config.publicUrl } : {},
1583
- ...config.houseOwnerUserId ? { houseOwnerUserId: config.houseOwnerUserId } : {},
1584
- // CT484: the artifact's build version → the server reports it on the bridge
1585
- // heartbeat so a bridge/server skew is detectable. Read from the installed
1349
+ // Persisted `cabane set-env` creds (generic server env) are layered beneath
1350
+ // the composed infra vars.
1351
+ ...config.companionEnv ? { persistedEnv: config.companionEnv } : {},
1352
+ // CT484: the artifact's build version → the server reports it on the companion
1353
+ // heartbeat so a companion/server skew is detectable. Read from the installed
1586
1354
  // tree's package.json (stamped at build); best-effort.
1587
1355
  ...(() => {
1588
1356
  const v = readAppVersion(this.appDir);
@@ -1591,51 +1359,25 @@ var Supervisor = class {
1591
1359
  });
1592
1360
  return new ServerChild(this.appDir, env, this.port, this.paths.serverLog);
1593
1361
  }
1594
- /** Start the bridge child iff it is configured (registered + paired) and not
1595
- * already running. */
1596
- ensureBridge() {
1597
- if (this.bridge?.running) return;
1598
- const config = loadConfig();
1599
- const paired = readBridgeConfig(this.paths.bridgeHome)?.deviceId;
1600
- if (!config.houseDeviceId || !paired) return;
1601
- const claudeConfigDir = resolveClaudeConfigDir();
1602
- this.bridge = new BridgeChild(
1603
- this.appDir,
1604
- this.paths.bridgeHome,
1605
- claudeConfigDir,
1606
- this.paths.bridgeLog,
1607
- config.bridgeEnv
1608
- );
1609
- try {
1610
- this.bridge.start();
1611
- } catch (err) {
1612
- this.bridge = null;
1613
- warn(`house bridge could not start: ${err instanceof Error ? err.message : String(err)}`);
1614
- info(
1615
- "the server is up; agents will not answer until this is resolved (try `cabane update`)."
1616
- );
1617
- }
1618
- }
1619
1362
  /** Publish the supervisor pid as early as possible so `status` / `down` /
1620
1363
  * `setup-agent` can find us the instant `up --daemon` returns (the daemon
1621
1364
  * launcher only waits on /healthz, which comes up before the children are all
1622
1365
  * wired). */
1623
1366
  writePid() {
1624
- mkdirSync5(this.paths.runDir, { recursive: true });
1625
- writeFileSync4(this.paths.supervisorPidFile, `${process.pid}
1367
+ mkdirSync4(this.paths.runDir, { recursive: true });
1368
+ writeFileSync3(this.paths.supervisorPidFile, `${process.pid}
1626
1369
  `);
1627
1370
  }
1628
1371
  writeState() {
1629
- mkdirSync5(this.paths.runDir, { recursive: true });
1630
- writeFileSync4(this.paths.supervisorPidFile, `${process.pid}
1372
+ mkdirSync4(this.paths.runDir, { recursive: true });
1373
+ writeFileSync3(this.paths.supervisorPidFile, `${process.pid}
1631
1374
  `);
1632
- writeFileSync4(
1375
+ writeFileSync3(
1633
1376
  this.paths.stateFile,
1634
1377
  JSON.stringify(
1635
1378
  {
1636
1379
  supervisorPid: process.pid,
1637
1380
  serverPid: this.server?.pid ?? null,
1638
- bridgePid: this.bridge?.pid ?? null,
1639
1381
  port: this.port,
1640
1382
  // Bumped on every (re)write — a completed reload advances it, which is
1641
1383
  // how `setup-agent` knows a restart it requested actually finished
@@ -1648,8 +1390,8 @@ var Supervisor = class {
1648
1390
  );
1649
1391
  }
1650
1392
  async run() {
1651
- mkdirSync5(this.paths.logsDir, { recursive: true });
1652
- mkdirSync5(this.paths.storageRoot, { recursive: true });
1393
+ mkdirSync4(this.paths.logsDir, { recursive: true });
1394
+ mkdirSync4(this.paths.storageRoot, { recursive: true });
1653
1395
  this.writePid();
1654
1396
  step("starting embedded Postgres\u2026");
1655
1397
  await this.pg.start();
@@ -1657,27 +1399,25 @@ var Supervisor = class {
1657
1399
  step("starting server\u2026");
1658
1400
  await this.server.migrate();
1659
1401
  await this.server.start();
1660
- this.ensureBridge();
1661
1402
  this.writeState();
1662
1403
  process.on("SIGINT", () => void this.teardown("SIGINT"));
1663
1404
  process.on("SIGTERM", () => void this.teardown("SIGTERM"));
1664
1405
  process.on("exit", () => {
1665
- for (const pid of [this.bridge?.pid, this.server?.pid]) {
1666
- if (pid) {
1667
- try {
1668
- process.kill(pid, "SIGKILL");
1669
- } catch {
1670
- }
1406
+ const pid = this.server?.pid;
1407
+ if (pid) {
1408
+ try {
1409
+ process.kill(pid, "SIGKILL");
1410
+ } catch {
1671
1411
  }
1672
1412
  }
1673
1413
  });
1674
1414
  process.on(
1675
1415
  "uncaughtException",
1676
- (err) => warn(`ignoring uncaught error: ${err instanceof Error ? err.message : String(err)}`)
1416
+ (err) => warn2(`ignoring uncaught error: ${err instanceof Error ? err.message : String(err)}`)
1677
1417
  );
1678
1418
  process.on(
1679
1419
  "unhandledRejection",
1680
- (err) => warn(`ignoring unhandled rejection: ${err instanceof Error ? err.message : String(err)}`)
1420
+ (err) => warn2(`ignoring unhandled rejection: ${err instanceof Error ? err.message : String(err)}`)
1681
1421
  );
1682
1422
  ok("cabane is up \u2014 Ctrl-C to stop");
1683
1423
  await new Promise(() => {
@@ -1687,207 +1427,23 @@ var Supervisor = class {
1687
1427
  if (this.shuttingDown) return;
1688
1428
  this.shuttingDown = true;
1689
1429
  step(`${signal} \u2014 stopping children\u2026`);
1690
- await this.bridge?.stop();
1691
1430
  await this.server?.stop();
1692
1431
  await this.pg.stop();
1693
- rmSync6(this.paths.supervisorPidFile, { force: true });
1694
- rmSync6(this.paths.stateFile, { force: true });
1432
+ rmSync4(this.paths.supervisorPidFile, { force: true });
1433
+ rmSync4(this.paths.stateFile, { force: true });
1695
1434
  ok("stopped cleanly");
1696
1435
  process.exit(0);
1697
1436
  }
1698
1437
  };
1699
1438
  function readAppVersion(appDir) {
1700
1439
  try {
1701
- const pkg = JSON.parse(readFileSync7(join10(appDir, "package.json"), "utf8"));
1440
+ const pkg = JSON.parse(readFileSync6(join8(appDir, "package.json"), "utf8"));
1702
1441
  return pkg.version ?? null;
1703
1442
  } catch {
1704
1443
  return null;
1705
1444
  }
1706
1445
  }
1707
1446
 
1708
- // src/commands/setup-agent.ts
1709
- var DEFAULT_AGENT_MODEL = "anthropic/claude-opus-4-8";
1710
- var DEVICE_LABEL = "cabane-house-bridge";
1711
- async function setupAgent(opts) {
1712
- const paths = resolvePaths();
1713
- const config = loadConfig();
1714
- const base = localBaseUrl(config.port);
1715
- const model = opts.model ?? DEFAULT_AGENT_MODEL;
1716
- if (!await healthy(config.port)) {
1717
- throw new Error(`server is not up on ${base} \u2014 run \`cabane up\` and sign up first.`);
1718
- }
1719
- if (!existsSync10(paths.supervisorPidFile)) {
1720
- throw new Error("no running supervisor found \u2014 start the stack with `cabane up` first.");
1721
- }
1722
- const restart = async () => {
1723
- await down();
1724
- await up({ daemon: true, port: config.port });
1725
- };
1726
- const token = await resolvePat(opts, base);
1727
- const client = makeClient(base, token);
1728
- const me = await client.get("/api/auth/me");
1729
- const ownerId = me.user.id;
1730
- ok(`owner resolved (${ownerId})`);
1731
- if (config.houseOwnerUserId !== ownerId) {
1732
- config.houseOwnerUserId = ownerId;
1733
- saveConfig(config);
1734
- step("applying ops identity to the server (restart)\u2026");
1735
- await restart();
1736
- }
1737
- const appDir = appDirFor(paths, config.currentVersion ?? "");
1738
- const deviceId = await ensureHouseDevice(client, config, appDir, paths.bridgeHome, base);
1739
- await assignDefaultAgent(client, deviceId, model);
1740
- step("starting the house bridge + assigning agents (restart)\u2026");
1741
- await restart();
1742
- const beating = await waitForHeartbeat(client, deviceId);
1743
- if (beating) ok("house bridge heartbeating \u2014 agents are live");
1744
- else warn("no bridge heartbeat yet \u2014 check `cabane logs`; it may need a minute");
1745
- ok("setup complete");
1746
- info("message your agent in the app \u2014 it should answer.");
1747
- }
1748
- async function healthy(port) {
1749
- try {
1750
- return (await fetch(`http://127.0.0.1:${port}/healthz`)).ok;
1751
- } catch {
1752
- return false;
1753
- }
1754
- }
1755
- async function resolvePat(opts, base) {
1756
- if (opts.token) return opts.token.trim();
1757
- if (isInteractive()) {
1758
- info(`mint a token in the app: ${base} \u2192 Settings \u2192 Developer \u2192 tokens`);
1759
- const t = await promptSecret("Paste a Cabane access token (cab_\u2026): ");
1760
- if (t) return t;
1761
- }
1762
- throw new Error("no access token. Pass --token <cab_\u2026> (mint it in Settings \u2192 Developer).");
1763
- }
1764
- async function ensureHouseDevice(client, config, appDir, bridgeHome, base) {
1765
- step("registering the house device\u2026");
1766
- let deviceId;
1767
- let deviceToken;
1768
- try {
1769
- const res = await client.post(
1770
- "/api/bridge-devices",
1771
- { label: DEVICE_LABEL, class: "house" }
1772
- );
1773
- deviceId = res.device.id;
1774
- deviceToken = res.deviceToken;
1775
- } catch (err) {
1776
- if (err instanceof ApiError && err.status === 409 && config.houseDeviceId) {
1777
- info(`reusing already-registered house device ${config.houseDeviceId}`);
1778
- return config.houseDeviceId;
1779
- }
1780
- throw err;
1781
- }
1782
- config.houseDeviceId = deviceId;
1783
- saveConfig(config);
1784
- await pairBridge({
1785
- appDir,
1786
- bridgeHome,
1787
- baseUrl: base,
1788
- deviceToken,
1789
- deviceId,
1790
- deviceLabel: DEVICE_LABEL
1791
- });
1792
- return deviceId;
1793
- }
1794
- async function assignDefaultAgent(client, deviceId, model) {
1795
- const { workspaces } = await client.get("/api/workspaces");
1796
- const workspace = workspaces.find((w) => w.role === "owner") ?? workspaces[0];
1797
- if (!workspace) throw new Error("the account owns no workspace \u2014 create one in the app first.");
1798
- const wsPath = `/api/workspaces/${workspace.id}`;
1799
- const { agents } = await client.get(
1800
- `${wsPath}/agents`
1801
- );
1802
- const agent = workspace.defaultAgentId && agents.find((a) => a.id === workspace.defaultAgentId) || agents.find((a) => a.username === "cabane") || agents[0];
1803
- if (!agent) throw new Error("the workspace has no agent to assign.");
1804
- const { runConfig } = await client.get(
1805
- `${wsPath}/agents/${agent.id}/run-config`
1806
- );
1807
- if (!runConfig?.model) {
1808
- info(`setting @${agent.username} model \u2192 ${model}`);
1809
- await client.put(`${wsPath}/agents/${agent.id}/run-config`, { ...runConfig ?? {}, model });
1810
- }
1811
- info(`assigning @${agent.username} \u2192 the house bridge`);
1812
- await client.put(`${wsPath}/agents/${agent.id}/executor`, { executor: "bridge", deviceId });
1813
- }
1814
- async function waitForHeartbeat(client, deviceId) {
1815
- return waitUntil(
1816
- async () => {
1817
- const { devices } = await client.get(
1818
- "/api/bridge-devices"
1819
- );
1820
- return Boolean(devices.find((d) => d.id === deviceId)?.lastSeenAt);
1821
- },
1822
- { timeoutMs: 9e4, intervalMs: 3e3 }
1823
- );
1824
- }
1825
-
1826
- // src/commands/status.ts
1827
- import { existsSync as existsSync11, readFileSync as readFileSync8 } from "fs";
1828
- import { join as join11 } from "path";
1829
- function readBridgeCrash(bridgeHome) {
1830
- try {
1831
- const path = join11(bridgeHome, ".cabane", "last-error.json");
1832
- if (!existsSync11(path)) return null;
1833
- return JSON.parse(readFileSync8(path, "utf8"));
1834
- } catch {
1835
- return null;
1836
- }
1837
- }
1838
- async function status() {
1839
- const config = loadConfig();
1840
- const paths = pathsFor(config);
1841
- const state = readState2(paths.stateFile);
1842
- const supervisorPid = readSupervisorPid(paths.supervisorPidFile) ?? state.supervisorPid;
1843
- const supervisorUp = supervisorPid ? pidAlive(supervisorPid) : false;
1844
- const serverUp = await healthy2(config.port);
1845
- const bridgeUp = state.bridgePid ? pidAlive(state.bridgePid) : false;
1846
- const line = (label, value) => {
1847
- process.stdout.write(` ${label.padEnd(12)}${value}
1848
- `);
1849
- };
1850
- process.stdout.write("cabane\n");
1851
- line("version", config.currentVersion ?? "(not installed)");
1852
- if (config.previousVersion) line("previous", config.previousVersion);
1853
- line("port", String(config.port));
1854
- line("home", paths.root);
1855
- line("data", `${paths.dataDir} (pg + storage \xB7 kept across upgrades)`);
1856
- line("supervisor", supervisorUp ? `up (pid ${supervisorPid})` : "down");
1857
- line("server", serverUp ? `up \xB7 http://localhost:${config.port}` : "down");
1858
- const crash = bridgeUp ? null : readBridgeCrash(paths.bridgeHome);
1859
- line(
1860
- "bridge",
1861
- config.houseDeviceId ? bridgeUp ? "up" : crash ? `crashed \u2014 ${crash.reason}` : "configured \xB7 down" : "not set up (cabane setup-agent)"
1862
- );
1863
- if (!bridgeUp && crash) {
1864
- info("see why: cabane logs --bridge");
1865
- }
1866
- if (!supervisorUp) info("start it: cabane up");
1867
- }
1868
- function readState2(file) {
1869
- try {
1870
- return JSON.parse(readFileSync8(file, "utf8"));
1871
- } catch {
1872
- return {};
1873
- }
1874
- }
1875
- function readSupervisorPid(file) {
1876
- try {
1877
- const pid = Number(readFileSync8(file, "utf8").trim());
1878
- return pid > 0 ? pid : null;
1879
- } catch {
1880
- return null;
1881
- }
1882
- }
1883
- async function healthy2(port) {
1884
- try {
1885
- return (await fetch(`http://127.0.0.1:${port}/healthz`)).ok;
1886
- } catch {
1887
- return false;
1888
- }
1889
- }
1890
-
1891
1447
  // src/commands/update.ts
1892
1448
  async function update(opts) {
1893
1449
  const config = loadConfig();
@@ -1922,20 +1478,18 @@ var parsePort = (raw) => {
1922
1478
  if (!Number.isInteger(n) || n <= 0 || n > 65535) throw new Error(`invalid port: ${raw}`);
1923
1479
  return n;
1924
1480
  };
1925
- program.command("init").description("interactive first-run wizard: data location, port, and the executor credential").action(init);
1481
+ program.command("init").description("interactive first-run wizard: data location, port, and public URL").action(init);
1926
1482
  program.command("install").description("install the server artifact (token \u2192 gated download, or --from a local tarball)").option("--token <token>", "distribution token (cabdist_\u2026) \u2014 prompted if omitted").option("--from <tarball>", "install from a local artifact tarball instead of downloading").option("--dist-base <url>", "distribution origin to fetch from (default https://dist.cabane.ai)").option("--version <version>", "install a specific published version (default: latest)").option("--port <port>", "local port the server will listen on (default 3000)", parsePort).option("--force", "reinstall even if this version is already present").action(install);
1927
- program.command("up").description("start + supervise the stack (Postgres, server, house bridge)").option("--daemon", "run in the background (terminal returns; stop with `cabane down`)").option("--port <port>", "override the configured port", parsePort).action(up);
1928
- program.command("setup-agent").description("after browser signup: register the house bridge + put your agent on it").option("--token <token>", "a Cabane access token (cab_\u2026) \u2014 prompted if omitted").option("--model <model>", "model the agent runs (default anthropic/claude-opus-4-8)").action(setupAgent);
1483
+ program.command("up").description("start + supervise the stack (Postgres + server)").option("--daemon", "run in the background (terminal returns; stop with `cabane down`)").option("--port <port>", "override the configured port", parsePort).action(up);
1484
+ program.command("setup-agent").description("after browser signup: confirm your account and print companion-pairing guidance").option("--token <token>", "a Cabane access token (cab_\u2026) \u2014 prompted if omitted").action(setupAgent);
1929
1485
  program.command("update").description("install a newer published artifact (keeps the previous for rollback)").option("--token <token>", "distribution token (cabdist_\u2026) \u2014 defaults to the stored one").option("--dist-base <url>", "distribution origin to fetch from").option("--version <version>", "install a specific version instead of latest").action(update);
1930
1486
  program.command("set-env [pairs...]").description(
1931
- "persist credentials forwarded to the house bridge \u2014 `KEY=VALUE`, or a bare `KEY` to be prompted for the value (kept off shell history)"
1487
+ "persist generic credentials forwarded to the server \u2014 `KEY=VALUE`, or a bare `KEY` to be prompted for the value (kept off shell history)"
1932
1488
  ).option("--unset <keys...>", "remove one or more persisted keys").option("--list", "list persisted keys (values masked)").action(setEnv);
1933
1489
  program.command("down").description("stop a running stack").action(down);
1934
- var bridge = program.command("bridge").description("house-bridge maintenance");
1935
- bridge.command("reset").description("clear the house bridge\u2019s resume state (cursors + dispatched/completed dedupe)").option("--force", "clear even while the bridge is running (takes effect next boot)").action(bridgeReset);
1936
1490
  program.command("status").description("show what is installed + running").action(status);
1937
1491
  program.command("doctor").description("preflight: diagnose common self-host failure classes (runs with the stack down)").action(doctor);
1938
- program.command("logs").description("tail the server + bridge logs").option("-n, --lines <n>", "how many lines to show (default 80)", (v) => Number(v)).option("-f, --follow", "follow the logs (Ctrl-C to stop)").option("--server", "only the server log").option("--bridge", "only the bridge log").action(logs);
1492
+ program.command("logs").description("tail the server log").option("-n, --lines <n>", "how many lines to show (default 80)", (v) => Number(v)).option("-f, --follow", "follow the logs (Ctrl-C to stop)").action(logs);
1939
1493
  program.parseAsync(process.argv).catch((err) => {
1940
1494
  error(err instanceof Error ? err.message : String(err));
1941
1495
  process.exitCode = 1;