@cabane/companion 0.6.46 → 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 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 serverUrl = cfg.opencode?.serverUrl;
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
- serverUrl ? withTimeout(probeOpencode(serverUrl), null) : Promise.resolve(null)
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: !!serverUrl,
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), PROBE_TIMEOUT_MS);
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 serverUrl = opts.serverUrl?.trim();
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
  );
@@ -10056,11 +10083,13 @@ async function createCompanionRuntime(opts = {}) {
10056
10083
  });
10057
10084
  await supervisor.start();
10058
10085
  const connectHarness = async (runtime, serverUrl) => {
10059
- const candidate = runtime === "opencode" ? { ...supervisor.currentConfig(), opencode: { serverUrl: serverUrl ?? "" } } : supervisor.currentConfig();
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;
10060
10089
  const verdict = await shakeOutHarness(runtime, candidate);
10061
10090
  if (verdict === "absent") return { ok: false, error: absentLine(runtime) };
10062
10091
  const result = await supervisor.enableHarness(
10063
- runtime === "opencode" ? { runtime: "opencode", serverUrl: serverUrl ?? "" } : { runtime }
10092
+ runtime === "opencode" ? { runtime: "opencode", serverUrl: resolvedServerUrl ?? "" } : { runtime }
10064
10093
  );
10065
10094
  if (!result.ok) return { ok: false, error: result.error };
10066
10095
  return { ok: true, message: connectedLine(runtime, verdict) };
@@ -10330,17 +10359,22 @@ function reportAlreadyRunning(pid) {
10330
10359
  write("Connect a harness to the running companion: cabane-companion connect claude-code");
10331
10360
  }
10332
10361
  async function collectHarnessChoices(interactive) {
10333
- const opencodeUrl = "http://127.0.0.1:4096";
10334
10362
  const [claudePresent, claudeVersion, codexVersion, opencodeVersion] = await Promise.all([
10335
10363
  claudeOnPath().catch(() => false),
10336
10364
  probeCliVersion("claude").catch(() => null),
10337
10365
  probeCliVersion("codex").catch(() => null),
10338
- probeOpencodeVersion(opencodeUrl).catch(() => null)
10366
+ probeDefaultOpencodeVersion()
10339
10367
  ]);
10340
10368
  const found = [
10341
10369
  ...claudePresent ? [{ runtime: "claude-code", version: claudeVersion }] : [],
10342
10370
  ...codexVersion ? [{ runtime: "codex", version: codexVersion }] : [],
10343
- ...opencodeVersion ? [{ runtime: "opencode", version: opencodeVersion, serverUrl: opencodeUrl }] : []
10371
+ ...opencodeVersion ? [
10372
+ {
10373
+ runtime: "opencode",
10374
+ version: opencodeVersion,
10375
+ serverUrl: DEFAULT_OPENCODE_SERVER_URL
10376
+ }
10377
+ ] : []
10344
10378
  ].sort((a, b) => OFFER_ORDER.indexOf(a.runtime) - OFFER_ORDER.indexOf(b.runtime));
10345
10379
  if (found.length === 0) {
10346
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 serverUrl = cfg.opencode?.serverUrl;
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
- serverUrl ? withTimeout(probeOpencode(serverUrl), null) : Promise.resolve(null)
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: !!serverUrl,
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), PROBE_TIMEOUT_MS);
1487
+ const timer = setTimeout(() => done(fallback), timeoutMs);
1462
1488
  timer.unref?.();
1463
1489
  promise.then(
1464
1490
  (v) => {
@@ -9565,11 +9591,13 @@ async function createCompanionRuntime(opts = {}) {
9565
9591
  });
9566
9592
  await supervisor.start();
9567
9593
  const connectHarness = async (runtime, serverUrl) => {
9568
- const candidate = runtime === "opencode" ? { ...supervisor.currentConfig(), opencode: { serverUrl: serverUrl ?? "" } } : supervisor.currentConfig();
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;
9569
9597
  const verdict = await shakeOutHarness(runtime, candidate);
9570
9598
  if (verdict === "absent") return { ok: false, error: absentLine(runtime) };
9571
9599
  const result = await supervisor.enableHarness(
9572
- runtime === "opencode" ? { runtime: "opencode", serverUrl: serverUrl ?? "" } : { runtime }
9600
+ runtime === "opencode" ? { runtime: "opencode", serverUrl: resolvedServerUrl ?? "" } : { runtime }
9573
9601
  );
9574
9602
  if (!result.ok) return { ok: false, error: result.error };
9575
9603
  return { ok: true, message: connectedLine(runtime, verdict) };
@@ -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.46",
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",