@cabane/companion 0.6.34 → 0.6.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +86 -25
- package/dist/runtime.js +45 -17
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1010,7 +1010,7 @@ async function shakeOutHarness(runtime, cfg, deps = {}) {
|
|
|
1010
1010
|
try {
|
|
1011
1011
|
if (runtime === "opencode") {
|
|
1012
1012
|
const url = cfg.opencode?.serverUrl;
|
|
1013
|
-
if (!url) return "
|
|
1013
|
+
if (!url) return "absent";
|
|
1014
1014
|
return await probeOpencode(url) !== null ? "ok" : "failed";
|
|
1015
1015
|
}
|
|
1016
1016
|
const { auth, presence } = runtime === "codex" ? {
|
|
@@ -1020,11 +1020,13 @@ async function shakeOutHarness(runtime, cfg, deps = {}) {
|
|
|
1020
1020
|
auth: ["claude", ["auth", "status"]],
|
|
1021
1021
|
presence: ["claude", ["--version"]]
|
|
1022
1022
|
};
|
|
1023
|
+
const presenceRun = await run(presence[0], [...presence[1]]);
|
|
1024
|
+
if (presenceRun.error === "spawn") return "absent";
|
|
1025
|
+
if (presenceRun.code !== 0) return "unverified";
|
|
1023
1026
|
const authRun = await run(auth[0], [...auth[1]]);
|
|
1024
1027
|
if (authRun.code === 0) return "ok";
|
|
1025
1028
|
if (looksUnsupported(authRun.output)) {
|
|
1026
|
-
|
|
1027
|
-
return presenceRun.code === 0 ? "unverified" : "failed";
|
|
1029
|
+
return "unverified";
|
|
1028
1030
|
}
|
|
1029
1031
|
return "failed";
|
|
1030
1032
|
} catch {
|
|
@@ -1032,6 +1034,7 @@ async function shakeOutHarness(runtime, cfg, deps = {}) {
|
|
|
1032
1034
|
}
|
|
1033
1035
|
}
|
|
1034
1036
|
function connectedLine(runtime, verdict) {
|
|
1037
|
+
if (verdict === "absent") throw new Error("an absent harness cannot be connected");
|
|
1035
1038
|
const label = HARNESS_LABELS[runtime];
|
|
1036
1039
|
if (verdict !== "failed") return `${label} connected.`;
|
|
1037
1040
|
return `${label} connected \u2014 ${FAILED_SUFFIX[runtime]}`;
|
|
@@ -1041,6 +1044,14 @@ var FAILED_SUFFIX = {
|
|
|
1041
1044
|
codex: "it doesn\u2019t look signed in yet. Run `codex login` once, then it\u2019s ready.",
|
|
1042
1045
|
opencode: "its server isn\u2019t answering. Start `opencode serve`, then it\u2019s ready."
|
|
1043
1046
|
};
|
|
1047
|
+
function absentLine(runtime) {
|
|
1048
|
+
if (runtime === "opencode") {
|
|
1049
|
+
return "No opencode server is configured \u2014 run `opencode serve` and connect with `--url`.";
|
|
1050
|
+
}
|
|
1051
|
+
const label = HARNESS_LABELS[runtime];
|
|
1052
|
+
const login = runtime === "codex" ? "`codex login`" : "`claude`";
|
|
1053
|
+
return `${label} isn\u2019t installed on this machine \u2014 install it and sign in (${login}), then run this again.`;
|
|
1054
|
+
}
|
|
1044
1055
|
function looksUnsupported(output) {
|
|
1045
1056
|
return /unrecognized|unknown (sub)?command|unexpected argument|invalid (sub)?command|no such (sub)?command|usage:|did you mean/i.test(
|
|
1046
1057
|
output
|
|
@@ -1049,17 +1060,17 @@ function looksUnsupported(output) {
|
|
|
1049
1060
|
function runBounded(command, args) {
|
|
1050
1061
|
return new Promise((resolve) => {
|
|
1051
1062
|
let settled = false;
|
|
1052
|
-
const done = (
|
|
1063
|
+
const done = (result) => {
|
|
1053
1064
|
if (settled) return;
|
|
1054
1065
|
settled = true;
|
|
1055
1066
|
clearTimeout(timer);
|
|
1056
|
-
resolve(
|
|
1067
|
+
resolve(result);
|
|
1057
1068
|
};
|
|
1058
1069
|
let child;
|
|
1059
1070
|
try {
|
|
1060
1071
|
child = spawn4(command, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
1061
1072
|
} catch {
|
|
1062
|
-
resolve({ code: null, output: "" });
|
|
1073
|
+
resolve({ code: null, output: "", error: "spawn" });
|
|
1063
1074
|
return;
|
|
1064
1075
|
}
|
|
1065
1076
|
let out = "";
|
|
@@ -1070,11 +1081,11 @@ function runBounded(command, args) {
|
|
|
1070
1081
|
child.stderr?.on("data", capture);
|
|
1071
1082
|
const timer = setTimeout(() => {
|
|
1072
1083
|
child.kill("SIGKILL");
|
|
1073
|
-
done(null, out);
|
|
1084
|
+
done({ code: null, output: out, error: "timeout" });
|
|
1074
1085
|
}, CHECK_TIMEOUT_MS);
|
|
1075
1086
|
timer.unref?.();
|
|
1076
|
-
child.once("error", () => done(null, out));
|
|
1077
|
-
child.once("exit", (code) => done(code, out));
|
|
1087
|
+
child.once("error", () => done({ code: null, output: out, error: "spawn" }));
|
|
1088
|
+
child.once("exit", (code) => done({ code, output: out }));
|
|
1078
1089
|
});
|
|
1079
1090
|
}
|
|
1080
1091
|
|
|
@@ -1271,11 +1282,23 @@ async function connect2(raw, opts = {}) {
|
|
|
1271
1282
|
}
|
|
1272
1283
|
const live = await liveSocket();
|
|
1273
1284
|
if (live) {
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1285
|
+
let result;
|
|
1286
|
+
try {
|
|
1287
|
+
result = await controlRequest(
|
|
1288
|
+
live,
|
|
1289
|
+
{
|
|
1290
|
+
cmd: "connect",
|
|
1291
|
+
runtime,
|
|
1292
|
+
...opts.serverUrl ? { serverUrl: opts.serverUrl } : {}
|
|
1293
|
+
},
|
|
1294
|
+
1e4
|
|
1295
|
+
);
|
|
1296
|
+
} catch (err) {
|
|
1297
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
1298
|
+
throw new CompanionError(
|
|
1299
|
+
`Couldn\u2019t reach the running companion \u2014 ${detail}; try again, or \`cabane-companion stop\` and \`start\`.`
|
|
1300
|
+
);
|
|
1301
|
+
}
|
|
1279
1302
|
if (!result.ok) throw new CompanionError(result.message);
|
|
1280
1303
|
write(INDENT + tick(result.message));
|
|
1281
1304
|
return;
|
|
@@ -1307,8 +1330,9 @@ async function connect2(raw, opts = {}) {
|
|
|
1307
1330
|
}
|
|
1308
1331
|
next = { ...cfg, opencode: { serverUrl } };
|
|
1309
1332
|
}
|
|
1310
|
-
saveConfig(next);
|
|
1311
1333
|
const verdict = await shakeOutHarness(runtime, next);
|
|
1334
|
+
if (verdict === "absent") throw new CompanionError(absentLine(runtime));
|
|
1335
|
+
saveConfig(next);
|
|
1312
1336
|
write(INDENT + tick(connectedLine(runtime, verdict)));
|
|
1313
1337
|
write(`${INDENT}Not running yet \u2014 start it with: cabane-companion start`);
|
|
1314
1338
|
}
|
|
@@ -1692,21 +1716,39 @@ function consoleMessageFormat(log, messageKey) {
|
|
|
1692
1716
|
return short ? `${short} ${msg}` : msg;
|
|
1693
1717
|
}
|
|
1694
1718
|
var cached = null;
|
|
1695
|
-
|
|
1696
|
-
|
|
1719
|
+
var consoleLogging = true;
|
|
1720
|
+
function setConsoleLogging(enabled) {
|
|
1721
|
+
consoleLogging = enabled;
|
|
1722
|
+
}
|
|
1723
|
+
function createLogger(destinations = {}) {
|
|
1697
1724
|
const path = companionLogPath();
|
|
1698
|
-
mkdirSync5(dirname3(path), { recursive: true });
|
|
1725
|
+
if (!destinations.file) mkdirSync5(dirname3(path), { recursive: true });
|
|
1699
1726
|
const streams = [];
|
|
1700
1727
|
if (process.env.CABANE_COMPANION_DAEMON !== "1") {
|
|
1701
1728
|
const consoleStream = pretty({
|
|
1702
1729
|
colorize: true,
|
|
1703
1730
|
ignore: CONSOLE_IGNORE,
|
|
1704
|
-
messageFormat: (log, messageKey) => consoleMessageFormat(log, messageKey)
|
|
1731
|
+
messageFormat: (log, messageKey) => consoleMessageFormat(log, messageKey),
|
|
1732
|
+
...destinations.console ? { destination: destinations.console } : {}
|
|
1733
|
+
});
|
|
1734
|
+
streams.push({
|
|
1735
|
+
level: "info",
|
|
1736
|
+
stream: {
|
|
1737
|
+
write(chunk) {
|
|
1738
|
+
if (consoleLogging) consoleStream.write(chunk);
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1705
1741
|
});
|
|
1706
|
-
streams.push({ level: "info", stream: consoleStream });
|
|
1707
1742
|
}
|
|
1708
|
-
streams.push({
|
|
1709
|
-
|
|
1743
|
+
streams.push({
|
|
1744
|
+
level: "debug",
|
|
1745
|
+
stream: destinations.file ?? createWriteStream(path, { flags: "a" })
|
|
1746
|
+
});
|
|
1747
|
+
return pino({ level: "debug" }, pino.multistream(streams));
|
|
1748
|
+
}
|
|
1749
|
+
function getLogger() {
|
|
1750
|
+
if (cached) return cached;
|
|
1751
|
+
cached = createLogger();
|
|
1710
1752
|
return cached;
|
|
1711
1753
|
}
|
|
1712
1754
|
|
|
@@ -10201,11 +10243,13 @@ async function createCompanionRuntime(opts = {}) {
|
|
|
10201
10243
|
});
|
|
10202
10244
|
await supervisor.start();
|
|
10203
10245
|
const connectHarness = async (runtime, serverUrl) => {
|
|
10246
|
+
const candidate = runtime === "opencode" ? { ...supervisor.currentConfig(), opencode: { serverUrl: serverUrl ?? "" } } : supervisor.currentConfig();
|
|
10247
|
+
const verdict = await shakeOutHarness(runtime, candidate);
|
|
10248
|
+
if (verdict === "absent") return { ok: false, error: absentLine(runtime) };
|
|
10204
10249
|
const result = await supervisor.enableHarness(
|
|
10205
10250
|
runtime === "opencode" ? { runtime: "opencode", serverUrl: serverUrl ?? "" } : { runtime }
|
|
10206
10251
|
);
|
|
10207
10252
|
if (!result.ok) return { ok: false, error: result.error };
|
|
10208
|
-
const verdict = await shakeOutHarness(runtime, supervisor.currentConfig());
|
|
10209
10253
|
return { ok: true, message: connectedLine(runtime, verdict) };
|
|
10210
10254
|
};
|
|
10211
10255
|
const control = await startControlServer({
|
|
@@ -10430,6 +10474,15 @@ var DEPLOY_GRACE_MS = 6.5 * 60 * 60 * 1e3;
|
|
|
10430
10474
|
var MAX_CODES = 3;
|
|
10431
10475
|
async function start(opts = {}) {
|
|
10432
10476
|
const interactive = isInteractive();
|
|
10477
|
+
if (!interactive) return startScript(opts, false);
|
|
10478
|
+
setConsoleLogging(false);
|
|
10479
|
+
try {
|
|
10480
|
+
await startScript(opts, true);
|
|
10481
|
+
} finally {
|
|
10482
|
+
setConsoleLogging(true);
|
|
10483
|
+
}
|
|
10484
|
+
}
|
|
10485
|
+
async function startScript(opts, interactive) {
|
|
10433
10486
|
const running = await liveCompanion();
|
|
10434
10487
|
if (running) {
|
|
10435
10488
|
reportAlreadyRunning(running.pid);
|
|
@@ -10439,7 +10492,9 @@ async function start(opts = {}) {
|
|
|
10439
10492
|
let paired = null;
|
|
10440
10493
|
if (!isDevicePaired()) {
|
|
10441
10494
|
paired = await pairHere(opts, interactive);
|
|
10442
|
-
if (!paired)
|
|
10495
|
+
if (!paired) {
|
|
10496
|
+
return;
|
|
10497
|
+
}
|
|
10443
10498
|
const { note } = writePairedConfig(paired);
|
|
10444
10499
|
if (note) getLogger().warn({ note }, "companion: salvaged an older config on pairing");
|
|
10445
10500
|
}
|
|
@@ -10470,6 +10525,7 @@ async function start(opts = {}) {
|
|
|
10470
10525
|
write(` Stop: Ctrl-C Logs: ${tildePath(companionLogPath())}`);
|
|
10471
10526
|
blank();
|
|
10472
10527
|
write(`${INDENT}Listening for messages\u2026`);
|
|
10528
|
+
setConsoleLogging(true);
|
|
10473
10529
|
} else {
|
|
10474
10530
|
if (!offered.printedSomething) blank();
|
|
10475
10531
|
write(
|
|
@@ -11242,7 +11298,12 @@ program.parseAsync(process.argv).catch((err) => {
|
|
|
11242
11298
|
process.exitCode = 130;
|
|
11243
11299
|
return;
|
|
11244
11300
|
}
|
|
11245
|
-
|
|
11301
|
+
setConsoleLogging(false);
|
|
11302
|
+
try {
|
|
11303
|
+
getLogger().error({ err }, "companion: command failed");
|
|
11304
|
+
} catch {
|
|
11305
|
+
}
|
|
11306
|
+
process.stderr.write(`${err instanceof Error ? err.message : String(err)}
|
|
11246
11307
|
`);
|
|
11247
11308
|
process.exitCode = 1;
|
|
11248
11309
|
});
|
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
|
-
|
|
465
|
-
|
|
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({
|
|
478
|
-
|
|
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
|
|
|
@@ -1465,7 +1480,7 @@ async function shakeOutHarness(runtime, cfg, deps = {}) {
|
|
|
1465
1480
|
try {
|
|
1466
1481
|
if (runtime === "opencode") {
|
|
1467
1482
|
const url = cfg.opencode?.serverUrl;
|
|
1468
|
-
if (!url) return "
|
|
1483
|
+
if (!url) return "absent";
|
|
1469
1484
|
return await probeOpencode(url) !== null ? "ok" : "failed";
|
|
1470
1485
|
}
|
|
1471
1486
|
const { auth, presence } = runtime === "codex" ? {
|
|
@@ -1475,11 +1490,13 @@ async function shakeOutHarness(runtime, cfg, deps = {}) {
|
|
|
1475
1490
|
auth: ["claude", ["auth", "status"]],
|
|
1476
1491
|
presence: ["claude", ["--version"]]
|
|
1477
1492
|
};
|
|
1493
|
+
const presenceRun = await run(presence[0], [...presence[1]]);
|
|
1494
|
+
if (presenceRun.error === "spawn") return "absent";
|
|
1495
|
+
if (presenceRun.code !== 0) return "unverified";
|
|
1478
1496
|
const authRun = await run(auth[0], [...auth[1]]);
|
|
1479
1497
|
if (authRun.code === 0) return "ok";
|
|
1480
1498
|
if (looksUnsupported(authRun.output)) {
|
|
1481
|
-
|
|
1482
|
-
return presenceRun.code === 0 ? "unverified" : "failed";
|
|
1499
|
+
return "unverified";
|
|
1483
1500
|
}
|
|
1484
1501
|
return "failed";
|
|
1485
1502
|
} catch {
|
|
@@ -1487,6 +1504,7 @@ async function shakeOutHarness(runtime, cfg, deps = {}) {
|
|
|
1487
1504
|
}
|
|
1488
1505
|
}
|
|
1489
1506
|
function connectedLine(runtime, verdict) {
|
|
1507
|
+
if (verdict === "absent") throw new Error("an absent harness cannot be connected");
|
|
1490
1508
|
const label = HARNESS_LABELS[runtime];
|
|
1491
1509
|
if (verdict !== "failed") return `${label} connected.`;
|
|
1492
1510
|
return `${label} connected \u2014 ${FAILED_SUFFIX[runtime]}`;
|
|
@@ -1496,6 +1514,14 @@ var FAILED_SUFFIX = {
|
|
|
1496
1514
|
codex: "it doesn\u2019t look signed in yet. Run `codex login` once, then it\u2019s ready.",
|
|
1497
1515
|
opencode: "its server isn\u2019t answering. Start `opencode serve`, then it\u2019s ready."
|
|
1498
1516
|
};
|
|
1517
|
+
function absentLine(runtime) {
|
|
1518
|
+
if (runtime === "opencode") {
|
|
1519
|
+
return "No opencode server is configured \u2014 run `opencode serve` and connect with `--url`.";
|
|
1520
|
+
}
|
|
1521
|
+
const label = HARNESS_LABELS[runtime];
|
|
1522
|
+
const login = runtime === "codex" ? "`codex login`" : "`claude`";
|
|
1523
|
+
return `${label} isn\u2019t installed on this machine \u2014 install it and sign in (${login}), then run this again.`;
|
|
1524
|
+
}
|
|
1499
1525
|
function looksUnsupported(output) {
|
|
1500
1526
|
return /unrecognized|unknown (sub)?command|unexpected argument|invalid (sub)?command|no such (sub)?command|usage:|did you mean/i.test(
|
|
1501
1527
|
output
|
|
@@ -1504,17 +1530,17 @@ function looksUnsupported(output) {
|
|
|
1504
1530
|
function runBounded(command, args) {
|
|
1505
1531
|
return new Promise((resolve) => {
|
|
1506
1532
|
let settled = false;
|
|
1507
|
-
const done = (
|
|
1533
|
+
const done = (result) => {
|
|
1508
1534
|
if (settled) return;
|
|
1509
1535
|
settled = true;
|
|
1510
1536
|
clearTimeout(timer);
|
|
1511
|
-
resolve(
|
|
1537
|
+
resolve(result);
|
|
1512
1538
|
};
|
|
1513
1539
|
let child;
|
|
1514
1540
|
try {
|
|
1515
1541
|
child = spawn4(command, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
1516
1542
|
} catch {
|
|
1517
|
-
resolve({ code: null, output: "" });
|
|
1543
|
+
resolve({ code: null, output: "", error: "spawn" });
|
|
1518
1544
|
return;
|
|
1519
1545
|
}
|
|
1520
1546
|
let out = "";
|
|
@@ -1525,11 +1551,11 @@ function runBounded(command, args) {
|
|
|
1525
1551
|
child.stderr?.on("data", capture);
|
|
1526
1552
|
const timer = setTimeout(() => {
|
|
1527
1553
|
child.kill("SIGKILL");
|
|
1528
|
-
done(null, out);
|
|
1554
|
+
done({ code: null, output: out, error: "timeout" });
|
|
1529
1555
|
}, CHECK_TIMEOUT_MS);
|
|
1530
1556
|
timer.unref?.();
|
|
1531
|
-
child.once("error", () => done(null, out));
|
|
1532
|
-
child.once("exit", (code) => done(code, out));
|
|
1557
|
+
child.once("error", () => done({ code: null, output: out, error: "spawn" }));
|
|
1558
|
+
child.once("exit", (code) => done({ code, output: out }));
|
|
1533
1559
|
});
|
|
1534
1560
|
}
|
|
1535
1561
|
|
|
@@ -9268,11 +9294,13 @@ async function createCompanionRuntime(opts = {}) {
|
|
|
9268
9294
|
});
|
|
9269
9295
|
await supervisor.start();
|
|
9270
9296
|
const connectHarness = async (runtime, serverUrl) => {
|
|
9297
|
+
const candidate = runtime === "opencode" ? { ...supervisor.currentConfig(), opencode: { serverUrl: serverUrl ?? "" } } : supervisor.currentConfig();
|
|
9298
|
+
const verdict = await shakeOutHarness(runtime, candidate);
|
|
9299
|
+
if (verdict === "absent") return { ok: false, error: absentLine(runtime) };
|
|
9271
9300
|
const result = await supervisor.enableHarness(
|
|
9272
9301
|
runtime === "opencode" ? { runtime: "opencode", serverUrl: serverUrl ?? "" } : { runtime }
|
|
9273
9302
|
);
|
|
9274
9303
|
if (!result.ok) return { ok: false, error: result.error };
|
|
9275
|
-
const verdict = await shakeOutHarness(runtime, supervisor.currentConfig());
|
|
9276
9304
|
return { ok: true, message: connectedLine(runtime, verdict) };
|
|
9277
9305
|
};
|
|
9278
9306
|
const control = await startControlServer({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cabane/companion",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.35",
|
|
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",
|