@cabane/companion 0.6.34 → 0.6.36

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 (4) hide show
  1. package/README.md +128 -36
  2. package/dist/cli.js +233 -696
  3. package/dist/runtime.js +50 -18
  4. package/package.json +1 -1
package/dist/runtime.js CHANGED
@@ -461,21 +461,36 @@ function consoleMessageFormat(log, messageKey) {
461
461
  return short ? `${short} ${msg}` : msg;
462
462
  }
463
463
  var cached = null;
464
- function getLogger() {
465
- if (cached) return cached;
464
+ var consoleLogging = true;
465
+ function createLogger(destinations = {}) {
466
466
  const path = companionLogPath();
467
- mkdirSync2(dirname2(path), { recursive: true });
467
+ if (!destinations.file) mkdirSync2(dirname2(path), { recursive: true });
468
468
  const streams = [];
469
469
  if (process.env.CABANE_COMPANION_DAEMON !== "1") {
470
470
  const consoleStream = pretty({
471
471
  colorize: true,
472
472
  ignore: CONSOLE_IGNORE,
473
- messageFormat: (log, messageKey) => consoleMessageFormat(log, messageKey)
473
+ messageFormat: (log, messageKey) => consoleMessageFormat(log, messageKey),
474
+ ...destinations.console ? { destination: destinations.console } : {}
475
+ });
476
+ streams.push({
477
+ level: "info",
478
+ stream: {
479
+ write(chunk) {
480
+ if (consoleLogging) consoleStream.write(chunk);
481
+ }
482
+ }
474
483
  });
475
- streams.push({ level: "info", stream: consoleStream });
476
484
  }
477
- streams.push({ level: "debug", stream: createWriteStream(path, { flags: "a" }) });
478
- cached = pino({ level: "debug" }, pino.multistream(streams));
485
+ streams.push({
486
+ level: "debug",
487
+ stream: destinations.file ?? createWriteStream(path, { flags: "a" })
488
+ });
489
+ return pino({ level: "debug" }, pino.multistream(streams));
490
+ }
491
+ function getLogger() {
492
+ if (cached) return cached;
493
+ cached = createLogger();
479
494
  return cached;
480
495
  }
481
496
 
@@ -1269,8 +1284,12 @@ async function warnAboutHarnessReadiness(cfg, deps = {}) {
1269
1284
  ...claudeInstalled ? ["Claude Code"] : [],
1270
1285
  ...codexInstalled ? ["Codex"] : []
1271
1286
  ];
1287
+ const connectCommands = [
1288
+ ...claudeInstalled ? ["`cabane-companion connect claude-code`"] : [],
1289
+ ...codexInstalled ? ["`cabane-companion connect codex`"] : []
1290
+ ];
1272
1291
  warn(
1273
- "No harness is connected on this device yet, so no agent turn can run here. " + (installed.length > 0 ? `We found ${installed.join(" and ")} on this machine \u2014 connect ${installed.length > 1 ? "one" : "it"} in the Companion dashboard (or in cabane, Settings \u2192 Devices) and turns start routing here.` : "Install a harness and sign in \u2014 Claude Code (`npm i -g @anthropic-ai/claude-code`), the Codex CLI (`codex login`), or `opencode serve` \u2014 then connect it in the Companion dashboard.")
1292
+ "No harness is connected on this device yet, so no agent turn can run here. " + (installed.length > 0 ? `We found ${installed.join(" and ")} on this machine \u2014 connect ${installed.length > 1 ? "one with " : "it with "}${connectCommands.join(" or ")} and turns start routing here.` : "Install a harness and sign in \u2014 Claude Code (`npm i -g @anthropic-ai/claude-code`), the Codex CLI (`codex login`), or `opencode serve` \u2014 then connect it with `cabane-companion connect <harness>` (pass `--url <url>` for opencode).")
1274
1293
  );
1275
1294
  }
1276
1295
 
@@ -1465,7 +1484,7 @@ async function shakeOutHarness(runtime, cfg, deps = {}) {
1465
1484
  try {
1466
1485
  if (runtime === "opencode") {
1467
1486
  const url = cfg.opencode?.serverUrl;
1468
- if (!url) return "failed";
1487
+ if (!url) return "absent";
1469
1488
  return await probeOpencode(url) !== null ? "ok" : "failed";
1470
1489
  }
1471
1490
  const { auth, presence } = runtime === "codex" ? {
@@ -1475,11 +1494,13 @@ async function shakeOutHarness(runtime, cfg, deps = {}) {
1475
1494
  auth: ["claude", ["auth", "status"]],
1476
1495
  presence: ["claude", ["--version"]]
1477
1496
  };
1497
+ const presenceRun = await run(presence[0], [...presence[1]]);
1498
+ if (presenceRun.error === "spawn") return "absent";
1499
+ if (presenceRun.code !== 0) return "unverified";
1478
1500
  const authRun = await run(auth[0], [...auth[1]]);
1479
1501
  if (authRun.code === 0) return "ok";
1480
1502
  if (looksUnsupported(authRun.output)) {
1481
- const presenceRun = await run(presence[0], [...presence[1]]);
1482
- return presenceRun.code === 0 ? "unverified" : "failed";
1503
+ return "unverified";
1483
1504
  }
1484
1505
  return "failed";
1485
1506
  } catch {
@@ -1487,6 +1508,7 @@ async function shakeOutHarness(runtime, cfg, deps = {}) {
1487
1508
  }
1488
1509
  }
1489
1510
  function connectedLine(runtime, verdict) {
1511
+ if (verdict === "absent") throw new Error("an absent harness cannot be connected");
1490
1512
  const label = HARNESS_LABELS[runtime];
1491
1513
  if (verdict !== "failed") return `${label} connected.`;
1492
1514
  return `${label} connected \u2014 ${FAILED_SUFFIX[runtime]}`;
@@ -1496,6 +1518,14 @@ var FAILED_SUFFIX = {
1496
1518
  codex: "it doesn\u2019t look signed in yet. Run `codex login` once, then it\u2019s ready.",
1497
1519
  opencode: "its server isn\u2019t answering. Start `opencode serve`, then it\u2019s ready."
1498
1520
  };
1521
+ function absentLine(runtime) {
1522
+ if (runtime === "opencode") {
1523
+ return "No opencode server is configured \u2014 run `opencode serve` and connect with `--url`.";
1524
+ }
1525
+ const label = HARNESS_LABELS[runtime];
1526
+ const login = runtime === "codex" ? "`codex login`" : "`claude`";
1527
+ return `${label} isn\u2019t installed on this machine \u2014 install it and sign in (${login}), then run this again.`;
1528
+ }
1499
1529
  function looksUnsupported(output) {
1500
1530
  return /unrecognized|unknown (sub)?command|unexpected argument|invalid (sub)?command|no such (sub)?command|usage:|did you mean/i.test(
1501
1531
  output
@@ -1504,17 +1534,17 @@ function looksUnsupported(output) {
1504
1534
  function runBounded(command, args) {
1505
1535
  return new Promise((resolve) => {
1506
1536
  let settled = false;
1507
- const done = (code, output) => {
1537
+ const done = (result) => {
1508
1538
  if (settled) return;
1509
1539
  settled = true;
1510
1540
  clearTimeout(timer);
1511
- resolve({ code, output });
1541
+ resolve(result);
1512
1542
  };
1513
1543
  let child;
1514
1544
  try {
1515
1545
  child = spawn4(command, args, { stdio: ["ignore", "pipe", "pipe"] });
1516
1546
  } catch {
1517
- resolve({ code: null, output: "" });
1547
+ resolve({ code: null, output: "", error: "spawn" });
1518
1548
  return;
1519
1549
  }
1520
1550
  let out = "";
@@ -1525,11 +1555,11 @@ function runBounded(command, args) {
1525
1555
  child.stderr?.on("data", capture);
1526
1556
  const timer = setTimeout(() => {
1527
1557
  child.kill("SIGKILL");
1528
- done(null, out);
1558
+ done({ code: null, output: out, error: "timeout" });
1529
1559
  }, CHECK_TIMEOUT_MS);
1530
1560
  timer.unref?.();
1531
- child.once("error", () => done(null, out));
1532
- child.once("exit", (code) => done(code, out));
1561
+ child.once("error", () => done({ code: null, output: out, error: "spawn" }));
1562
+ child.once("exit", (code) => done({ code, output: out }));
1533
1563
  });
1534
1564
  }
1535
1565
 
@@ -9268,11 +9298,13 @@ async function createCompanionRuntime(opts = {}) {
9268
9298
  });
9269
9299
  await supervisor.start();
9270
9300
  const connectHarness = async (runtime, serverUrl) => {
9301
+ const candidate = runtime === "opencode" ? { ...supervisor.currentConfig(), opencode: { serverUrl: serverUrl ?? "" } } : supervisor.currentConfig();
9302
+ const verdict = await shakeOutHarness(runtime, candidate);
9303
+ if (verdict === "absent") return { ok: false, error: absentLine(runtime) };
9271
9304
  const result = await supervisor.enableHarness(
9272
9305
  runtime === "opencode" ? { runtime: "opencode", serverUrl: serverUrl ?? "" } : { runtime }
9273
9306
  );
9274
9307
  if (!result.ok) return { ok: false, error: result.error };
9275
- const verdict = await shakeOutHarness(runtime, supervisor.currentConfig());
9276
9308
  return { ok: true, message: connectedLine(runtime, verdict) };
9277
9309
  };
9278
9310
  const control = await startControlServer({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cabane/companion",
3
- "version": "0.6.34",
3
+ "version": "0.6.36",
4
4
  "type": "module",
5
5
  "description": "The Cabane Companion (headless): connect a coding agent on your machine to your Cabane workspace as a responder — drive work against your own codebase, files, and MCP servers without putting any of it in Cabane.",
6
6
  "license": "UNLICENSED",