@cabane/companion 0.6.45 → 0.6.47
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 +54 -14
- package/dist/runtime.js +43 -9
- package/dist/static/app.js +1 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -823,6 +823,7 @@ var HARNESS_LABELS = {
|
|
|
823
823
|
};
|
|
824
824
|
var LABELS = HARNESS_LABELS;
|
|
825
825
|
var OFFER_ORDER = ["claude-code", "codex", "opencode"];
|
|
826
|
+
var DEFAULT_OPENCODE_SERVER_URL = "http://127.0.0.1:4096";
|
|
826
827
|
function parseHarnessRuntime(raw) {
|
|
827
828
|
const key = raw.trim().toLowerCase().replace(/[\s_]+/g, "-");
|
|
828
829
|
if (key === "claude-code" || key === "claudecode" || key === "claude") return "claude-code";
|
|
@@ -941,6 +942,15 @@ function deriveOpencode(signals, manifestHas) {
|
|
|
941
942
|
enable: null
|
|
942
943
|
};
|
|
943
944
|
}
|
|
945
|
+
if (signals.opencodeReachable && signals.opencodeDetectedUrl) {
|
|
946
|
+
return {
|
|
947
|
+
...base,
|
|
948
|
+
state: "detected_not_exposed",
|
|
949
|
+
version: signals.opencodeVersion,
|
|
950
|
+
detail: signals.opencodeDetectedUrl,
|
|
951
|
+
enable: "opencode"
|
|
952
|
+
};
|
|
953
|
+
}
|
|
944
954
|
return {
|
|
945
955
|
...base,
|
|
946
956
|
state: "not_detected",
|
|
@@ -950,17 +960,32 @@ function deriveOpencode(signals, manifestHas) {
|
|
|
950
960
|
};
|
|
951
961
|
}
|
|
952
962
|
var PROBE_TIMEOUT_MS = 4e3;
|
|
963
|
+
var DEFAULT_OPENCODE_PROBE_TIMEOUT_MS = 1e3;
|
|
964
|
+
function probeDefaultOpencodeVersion(probeOpencode = (url) => probeOpencodeVersion(url)) {
|
|
965
|
+
return withTimeout(
|
|
966
|
+
probeOpencode(DEFAULT_OPENCODE_SERVER_URL),
|
|
967
|
+
null,
|
|
968
|
+
DEFAULT_OPENCODE_PROBE_TIMEOUT_MS
|
|
969
|
+
);
|
|
970
|
+
}
|
|
971
|
+
async function resolveOpencodeServerUrl(configuredServerUrl, requestedServerUrl, probeDefault = probeDefaultOpencodeVersion) {
|
|
972
|
+
const requested = requestedServerUrl?.trim();
|
|
973
|
+
if (requested) return requested;
|
|
974
|
+
if (configuredServerUrl) return configuredServerUrl;
|
|
975
|
+
return await probeDefault() !== null ? DEFAULT_OPENCODE_SERVER_URL : null;
|
|
976
|
+
}
|
|
953
977
|
async function probeHarnessSignals(cfg, deps = {}) {
|
|
954
978
|
const probeClaudePresence = deps.probeClaudePresence ?? claudeOnPath;
|
|
955
979
|
const probeClaudeVersion = deps.probeClaudeVersion ?? (() => probeCliVersion("claude"));
|
|
956
980
|
const probeCodexVersion = deps.probeCodexVersion ?? (() => probeCliVersion("codex"));
|
|
957
981
|
const probeOpencode = deps.probeOpencode ?? ((url) => probeOpencodeVersion(url));
|
|
958
|
-
const
|
|
982
|
+
const configuredServerUrl = cfg.opencode?.serverUrl;
|
|
983
|
+
const opencodeProbeUrl = configuredServerUrl ?? DEFAULT_OPENCODE_SERVER_URL;
|
|
959
984
|
const [claudeOnPathResult, claudeVersion, codexVersion, opencodeVersion] = await Promise.all([
|
|
960
985
|
withTimeout(probeClaudePresence(), false),
|
|
961
986
|
withTimeout(probeClaudeVersion(), null),
|
|
962
987
|
withTimeout(probeCodexVersion(), null),
|
|
963
|
-
|
|
988
|
+
configuredServerUrl ? withTimeout(probeOpencode(opencodeProbeUrl), null) : probeDefaultOpencodeVersion(probeOpencode)
|
|
964
989
|
]);
|
|
965
990
|
return {
|
|
966
991
|
claudeOnPath: claudeOnPathResult,
|
|
@@ -973,13 +998,14 @@ async function probeHarnessSignals(cfg, deps = {}) {
|
|
|
973
998
|
codexOnPath: codexVersion !== null,
|
|
974
999
|
codexVersion,
|
|
975
1000
|
codexEnabled: isCodexEnabled(cfg),
|
|
976
|
-
opencodeConfigured: !!
|
|
1001
|
+
opencodeConfigured: !!configuredServerUrl,
|
|
977
1002
|
// A version came back ⟺ the serve answered its health endpoint (CT584).
|
|
978
1003
|
opencodeReachable: opencodeVersion !== null,
|
|
979
|
-
opencodeVersion
|
|
1004
|
+
opencodeVersion,
|
|
1005
|
+
opencodeDetectedUrl: opencodeVersion !== null ? opencodeProbeUrl : null
|
|
980
1006
|
};
|
|
981
1007
|
}
|
|
982
|
-
function withTimeout(promise, fallback) {
|
|
1008
|
+
function withTimeout(promise, fallback, timeoutMs = PROBE_TIMEOUT_MS) {
|
|
983
1009
|
return new Promise((resolve) => {
|
|
984
1010
|
let settled = false;
|
|
985
1011
|
const done = (v) => {
|
|
@@ -988,7 +1014,7 @@ function withTimeout(promise, fallback) {
|
|
|
988
1014
|
resolve(v);
|
|
989
1015
|
}
|
|
990
1016
|
};
|
|
991
|
-
const timer = setTimeout(() => done(fallback),
|
|
1017
|
+
const timer = setTimeout(() => done(fallback), timeoutMs);
|
|
992
1018
|
timer.unref?.();
|
|
993
1019
|
promise.then(
|
|
994
1020
|
(v) => {
|
|
@@ -1316,13 +1342,14 @@ async function connect2(raw, opts = {}) {
|
|
|
1316
1342
|
if (runtime === "claude-code") next = { ...cfg, claudeCode: { enabled: true } };
|
|
1317
1343
|
else if (runtime === "codex") next = { ...cfg, codex: { enabled: true } };
|
|
1318
1344
|
else {
|
|
1319
|
-
const
|
|
1345
|
+
const requestedServerUrl = opts.serverUrl?.trim();
|
|
1346
|
+
const serverUrl = await resolveOpencodeServerUrl(void 0, requestedServerUrl);
|
|
1320
1347
|
if (!serverUrl) {
|
|
1321
1348
|
throw new CompanionError(
|
|
1322
1349
|
"opencode is addressed by URL \u2014 pass it: `cabane-companion connect opencode --url http://127.0.0.1:4096`."
|
|
1323
1350
|
);
|
|
1324
1351
|
}
|
|
1325
|
-
if (await probeOpencodeVersion(serverUrl) === null) {
|
|
1352
|
+
if (requestedServerUrl && await probeOpencodeVersion(serverUrl) === null) {
|
|
1326
1353
|
throw new CompanionError(
|
|
1327
1354
|
`Couldn\u2019t reach an opencode server at ${serverUrl}. Start \`opencode serve\` and check the URL.`
|
|
1328
1355
|
);
|
|
@@ -9004,6 +9031,7 @@ var CompanionSupervisor = class {
|
|
|
9004
9031
|
// NOT the cached beat signal — someone connecting right after installing Claude
|
|
9005
9032
|
// Code shouldn't be refused by a snapshot up to a heartbeat old.
|
|
9006
9033
|
probeClaudePresence;
|
|
9034
|
+
probeCodexPresence;
|
|
9007
9035
|
exitFn;
|
|
9008
9036
|
reexecFn;
|
|
9009
9037
|
dispatcherFactory;
|
|
@@ -9036,6 +9064,7 @@ var CompanionSupervisor = class {
|
|
|
9036
9064
|
this.hub = opts.hub;
|
|
9037
9065
|
this.claudeCode = opts.claudeCode ?? true;
|
|
9038
9066
|
this.probeClaudePresence = opts.probeClaudePresence ?? claudeOnPath;
|
|
9067
|
+
this.probeCodexPresence = opts.probeCodexPresence ?? codexOnPath;
|
|
9039
9068
|
this.harnessVersions = opts.harnessVersions ?? emptyHarnessVersions();
|
|
9040
9069
|
this.exitFn = opts.exit ?? ((code) => process.exit(code));
|
|
9041
9070
|
this.reexecFn = opts.reexec ?? defaultReexec;
|
|
@@ -9783,7 +9812,8 @@ var CompanionSupervisor = class {
|
|
|
9783
9812
|
// - claude-code (CT1082): set `claudeCode.enabled`, after checking `claude` is
|
|
9784
9813
|
// on PATH. This is the callable the terminal offer ("We found Claude Code.
|
|
9785
9814
|
// Connect it? [Y/n]") and the web pairing flow both wire to.
|
|
9786
|
-
// - codex: set `codex.enabled`
|
|
9815
|
+
// - codex: set `codex.enabled` after checking `codex` is on PATH (the CLI +
|
|
9816
|
+
// `codex login` remain the user's).
|
|
9787
9817
|
// - opencode: set `opencode.serverUrl` — but ONLY after health-probing the URL,
|
|
9788
9818
|
// so we never advertise a serve that isn't there. An unreachable URL is a
|
|
9789
9819
|
// typed failure the UI shows in place, not a silent bad write.
|
|
@@ -9801,6 +9831,9 @@ var CompanionSupervisor = class {
|
|
|
9801
9831
|
}
|
|
9802
9832
|
next = { ...this.config, claudeCode: { enabled: true } };
|
|
9803
9833
|
} else if (input.runtime === "codex") {
|
|
9834
|
+
if (!await this.probeCodexPresence()) {
|
|
9835
|
+
return { ok: false, error: absentLine("codex") };
|
|
9836
|
+
}
|
|
9804
9837
|
next = { ...this.config, codex: { enabled: true } };
|
|
9805
9838
|
} else {
|
|
9806
9839
|
const serverUrl = input.serverUrl.trim();
|
|
@@ -10050,11 +10083,13 @@ async function createCompanionRuntime(opts = {}) {
|
|
|
10050
10083
|
});
|
|
10051
10084
|
await supervisor.start();
|
|
10052
10085
|
const connectHarness = async (runtime, serverUrl) => {
|
|
10053
|
-
const
|
|
10086
|
+
const currentConfig = supervisor.currentConfig();
|
|
10087
|
+
const resolvedServerUrl = runtime === "opencode" ? await resolveOpencodeServerUrl(currentConfig.opencode?.serverUrl, serverUrl) : null;
|
|
10088
|
+
const candidate = runtime === "opencode" ? { ...currentConfig, opencode: { serverUrl: resolvedServerUrl ?? "" } } : currentConfig;
|
|
10054
10089
|
const verdict = await shakeOutHarness(runtime, candidate);
|
|
10055
10090
|
if (verdict === "absent") return { ok: false, error: absentLine(runtime) };
|
|
10056
10091
|
const result = await supervisor.enableHarness(
|
|
10057
|
-
runtime === "opencode" ? { runtime: "opencode", serverUrl:
|
|
10092
|
+
runtime === "opencode" ? { runtime: "opencode", serverUrl: resolvedServerUrl ?? "" } : { runtime }
|
|
10058
10093
|
);
|
|
10059
10094
|
if (!result.ok) return { ok: false, error: result.error };
|
|
10060
10095
|
return { ok: true, message: connectedLine(runtime, verdict) };
|
|
@@ -10324,17 +10359,22 @@ function reportAlreadyRunning(pid) {
|
|
|
10324
10359
|
write("Connect a harness to the running companion: cabane-companion connect claude-code");
|
|
10325
10360
|
}
|
|
10326
10361
|
async function collectHarnessChoices(interactive) {
|
|
10327
|
-
const opencodeUrl = "http://127.0.0.1:4096";
|
|
10328
10362
|
const [claudePresent, claudeVersion, codexVersion, opencodeVersion] = await Promise.all([
|
|
10329
10363
|
claudeOnPath().catch(() => false),
|
|
10330
10364
|
probeCliVersion("claude").catch(() => null),
|
|
10331
10365
|
probeCliVersion("codex").catch(() => null),
|
|
10332
|
-
|
|
10366
|
+
probeDefaultOpencodeVersion()
|
|
10333
10367
|
]);
|
|
10334
10368
|
const found = [
|
|
10335
10369
|
...claudePresent ? [{ runtime: "claude-code", version: claudeVersion }] : [],
|
|
10336
10370
|
...codexVersion ? [{ runtime: "codex", version: codexVersion }] : [],
|
|
10337
|
-
...opencodeVersion ? [
|
|
10371
|
+
...opencodeVersion ? [
|
|
10372
|
+
{
|
|
10373
|
+
runtime: "opencode",
|
|
10374
|
+
version: opencodeVersion,
|
|
10375
|
+
serverUrl: DEFAULT_OPENCODE_SERVER_URL
|
|
10376
|
+
}
|
|
10377
|
+
] : []
|
|
10338
10378
|
].sort((a, b) => OFFER_ORDER.indexOf(a.runtime) - OFFER_ORDER.indexOf(b.runtime));
|
|
10339
10379
|
if (found.length === 0) {
|
|
10340
10380
|
write(
|
package/dist/runtime.js
CHANGED
|
@@ -1300,6 +1300,7 @@ var HARNESS_LABELS = {
|
|
|
1300
1300
|
opencode: "OpenCode"
|
|
1301
1301
|
};
|
|
1302
1302
|
var LABELS = HARNESS_LABELS;
|
|
1303
|
+
var DEFAULT_OPENCODE_SERVER_URL = "http://127.0.0.1:4096";
|
|
1303
1304
|
function deriveHarnessSnapshot(signals) {
|
|
1304
1305
|
const advertised = new Set(
|
|
1305
1306
|
buildCompanionManifest({
|
|
@@ -1411,6 +1412,15 @@ function deriveOpencode(signals, manifestHas) {
|
|
|
1411
1412
|
enable: null
|
|
1412
1413
|
};
|
|
1413
1414
|
}
|
|
1415
|
+
if (signals.opencodeReachable && signals.opencodeDetectedUrl) {
|
|
1416
|
+
return {
|
|
1417
|
+
...base,
|
|
1418
|
+
state: "detected_not_exposed",
|
|
1419
|
+
version: signals.opencodeVersion,
|
|
1420
|
+
detail: signals.opencodeDetectedUrl,
|
|
1421
|
+
enable: "opencode"
|
|
1422
|
+
};
|
|
1423
|
+
}
|
|
1414
1424
|
return {
|
|
1415
1425
|
...base,
|
|
1416
1426
|
state: "not_detected",
|
|
@@ -1420,17 +1430,32 @@ function deriveOpencode(signals, manifestHas) {
|
|
|
1420
1430
|
};
|
|
1421
1431
|
}
|
|
1422
1432
|
var PROBE_TIMEOUT_MS = 4e3;
|
|
1433
|
+
var DEFAULT_OPENCODE_PROBE_TIMEOUT_MS = 1e3;
|
|
1434
|
+
function probeDefaultOpencodeVersion(probeOpencode = (url) => probeOpencodeVersion(url)) {
|
|
1435
|
+
return withTimeout(
|
|
1436
|
+
probeOpencode(DEFAULT_OPENCODE_SERVER_URL),
|
|
1437
|
+
null,
|
|
1438
|
+
DEFAULT_OPENCODE_PROBE_TIMEOUT_MS
|
|
1439
|
+
);
|
|
1440
|
+
}
|
|
1441
|
+
async function resolveOpencodeServerUrl(configuredServerUrl, requestedServerUrl, probeDefault = probeDefaultOpencodeVersion) {
|
|
1442
|
+
const requested = requestedServerUrl?.trim();
|
|
1443
|
+
if (requested) return requested;
|
|
1444
|
+
if (configuredServerUrl) return configuredServerUrl;
|
|
1445
|
+
return await probeDefault() !== null ? DEFAULT_OPENCODE_SERVER_URL : null;
|
|
1446
|
+
}
|
|
1423
1447
|
async function probeHarnessSignals(cfg, deps = {}) {
|
|
1424
1448
|
const probeClaudePresence = deps.probeClaudePresence ?? claudeOnPath;
|
|
1425
1449
|
const probeClaudeVersion = deps.probeClaudeVersion ?? (() => probeCliVersion("claude"));
|
|
1426
1450
|
const probeCodexVersion = deps.probeCodexVersion ?? (() => probeCliVersion("codex"));
|
|
1427
1451
|
const probeOpencode = deps.probeOpencode ?? ((url) => probeOpencodeVersion(url));
|
|
1428
|
-
const
|
|
1452
|
+
const configuredServerUrl = cfg.opencode?.serverUrl;
|
|
1453
|
+
const opencodeProbeUrl = configuredServerUrl ?? DEFAULT_OPENCODE_SERVER_URL;
|
|
1429
1454
|
const [claudeOnPathResult, claudeVersion, codexVersion, opencodeVersion] = await Promise.all([
|
|
1430
1455
|
withTimeout(probeClaudePresence(), false),
|
|
1431
1456
|
withTimeout(probeClaudeVersion(), null),
|
|
1432
1457
|
withTimeout(probeCodexVersion(), null),
|
|
1433
|
-
|
|
1458
|
+
configuredServerUrl ? withTimeout(probeOpencode(opencodeProbeUrl), null) : probeDefaultOpencodeVersion(probeOpencode)
|
|
1434
1459
|
]);
|
|
1435
1460
|
return {
|
|
1436
1461
|
claudeOnPath: claudeOnPathResult,
|
|
@@ -1443,13 +1468,14 @@ async function probeHarnessSignals(cfg, deps = {}) {
|
|
|
1443
1468
|
codexOnPath: codexVersion !== null,
|
|
1444
1469
|
codexVersion,
|
|
1445
1470
|
codexEnabled: isCodexEnabled(cfg),
|
|
1446
|
-
opencodeConfigured: !!
|
|
1471
|
+
opencodeConfigured: !!configuredServerUrl,
|
|
1447
1472
|
// A version came back ⟺ the serve answered its health endpoint (CT584).
|
|
1448
1473
|
opencodeReachable: opencodeVersion !== null,
|
|
1449
|
-
opencodeVersion
|
|
1474
|
+
opencodeVersion,
|
|
1475
|
+
opencodeDetectedUrl: opencodeVersion !== null ? opencodeProbeUrl : null
|
|
1450
1476
|
};
|
|
1451
1477
|
}
|
|
1452
|
-
function withTimeout(promise, fallback) {
|
|
1478
|
+
function withTimeout(promise, fallback, timeoutMs = PROBE_TIMEOUT_MS) {
|
|
1453
1479
|
return new Promise((resolve) => {
|
|
1454
1480
|
let settled = false;
|
|
1455
1481
|
const done = (v) => {
|
|
@@ -1458,7 +1484,7 @@ function withTimeout(promise, fallback) {
|
|
|
1458
1484
|
resolve(v);
|
|
1459
1485
|
}
|
|
1460
1486
|
};
|
|
1461
|
-
const timer = setTimeout(() => done(fallback),
|
|
1487
|
+
const timer = setTimeout(() => done(fallback), timeoutMs);
|
|
1462
1488
|
timer.unref?.();
|
|
1463
1489
|
promise.then(
|
|
1464
1490
|
(v) => {
|
|
@@ -8513,6 +8539,7 @@ var CompanionSupervisor = class {
|
|
|
8513
8539
|
// NOT the cached beat signal — someone connecting right after installing Claude
|
|
8514
8540
|
// Code shouldn't be refused by a snapshot up to a heartbeat old.
|
|
8515
8541
|
probeClaudePresence;
|
|
8542
|
+
probeCodexPresence;
|
|
8516
8543
|
exitFn;
|
|
8517
8544
|
reexecFn;
|
|
8518
8545
|
dispatcherFactory;
|
|
@@ -8545,6 +8572,7 @@ var CompanionSupervisor = class {
|
|
|
8545
8572
|
this.hub = opts.hub;
|
|
8546
8573
|
this.claudeCode = opts.claudeCode ?? true;
|
|
8547
8574
|
this.probeClaudePresence = opts.probeClaudePresence ?? claudeOnPath;
|
|
8575
|
+
this.probeCodexPresence = opts.probeCodexPresence ?? codexOnPath;
|
|
8548
8576
|
this.harnessVersions = opts.harnessVersions ?? emptyHarnessVersions();
|
|
8549
8577
|
this.exitFn = opts.exit ?? ((code) => process.exit(code));
|
|
8550
8578
|
this.reexecFn = opts.reexec ?? defaultReexec;
|
|
@@ -9292,7 +9320,8 @@ var CompanionSupervisor = class {
|
|
|
9292
9320
|
// - claude-code (CT1082): set `claudeCode.enabled`, after checking `claude` is
|
|
9293
9321
|
// on PATH. This is the callable the terminal offer ("We found Claude Code.
|
|
9294
9322
|
// Connect it? [Y/n]") and the web pairing flow both wire to.
|
|
9295
|
-
// - codex: set `codex.enabled`
|
|
9323
|
+
// - codex: set `codex.enabled` after checking `codex` is on PATH (the CLI +
|
|
9324
|
+
// `codex login` remain the user's).
|
|
9296
9325
|
// - opencode: set `opencode.serverUrl` — but ONLY after health-probing the URL,
|
|
9297
9326
|
// so we never advertise a serve that isn't there. An unreachable URL is a
|
|
9298
9327
|
// typed failure the UI shows in place, not a silent bad write.
|
|
@@ -9310,6 +9339,9 @@ var CompanionSupervisor = class {
|
|
|
9310
9339
|
}
|
|
9311
9340
|
next = { ...this.config, claudeCode: { enabled: true } };
|
|
9312
9341
|
} else if (input.runtime === "codex") {
|
|
9342
|
+
if (!await this.probeCodexPresence()) {
|
|
9343
|
+
return { ok: false, error: absentLine("codex") };
|
|
9344
|
+
}
|
|
9313
9345
|
next = { ...this.config, codex: { enabled: true } };
|
|
9314
9346
|
} else {
|
|
9315
9347
|
const serverUrl = input.serverUrl.trim();
|
|
@@ -9559,11 +9591,13 @@ async function createCompanionRuntime(opts = {}) {
|
|
|
9559
9591
|
});
|
|
9560
9592
|
await supervisor.start();
|
|
9561
9593
|
const connectHarness = async (runtime, serverUrl) => {
|
|
9562
|
-
const
|
|
9594
|
+
const currentConfig = supervisor.currentConfig();
|
|
9595
|
+
const resolvedServerUrl = runtime === "opencode" ? await resolveOpencodeServerUrl(currentConfig.opencode?.serverUrl, serverUrl) : null;
|
|
9596
|
+
const candidate = runtime === "opencode" ? { ...currentConfig, opencode: { serverUrl: resolvedServerUrl ?? "" } } : currentConfig;
|
|
9563
9597
|
const verdict = await shakeOutHarness(runtime, candidate);
|
|
9564
9598
|
if (verdict === "absent") return { ok: false, error: absentLine(runtime) };
|
|
9565
9599
|
const result = await supervisor.enableHarness(
|
|
9566
|
-
runtime === "opencode" ? { runtime: "opencode", serverUrl:
|
|
9600
|
+
runtime === "opencode" ? { runtime: "opencode", serverUrl: resolvedServerUrl ?? "" } : { runtime }
|
|
9567
9601
|
);
|
|
9568
9602
|
if (!result.ok) return { ok: false, error: result.error };
|
|
9569
9603
|
return { ok: true, message: connectedLine(runtime, verdict) };
|
package/dist/static/app.js
CHANGED
|
@@ -158,6 +158,7 @@ function renderHarnesses() {
|
|
|
158
158
|
const input = el('input');
|
|
159
159
|
input.type = 'text';
|
|
160
160
|
input.placeholder = 'opencode serve URL (e.g. http://127.0.0.1:4096)';
|
|
161
|
+
if (h.state === 'detected_not_exposed') input.value = h.detail;
|
|
161
162
|
const btn = el('button', 'primary small', 'Add opencode');
|
|
162
163
|
const submit = () => {
|
|
163
164
|
const url = input.value.trim();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cabane/companion",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.47",
|
|
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",
|