@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/cli.js CHANGED
@@ -806,8 +806,12 @@ async function warnAboutHarnessReadiness(cfg, deps = {}) {
806
806
  ...claudeInstalled ? ["Claude Code"] : [],
807
807
  ...codexInstalled ? ["Codex"] : []
808
808
  ];
809
+ const connectCommands = [
810
+ ...claudeInstalled ? ["`cabane-companion connect claude-code`"] : [],
811
+ ...codexInstalled ? ["`cabane-companion connect codex`"] : []
812
+ ];
809
813
  warn(
810
- "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.")
814
+ "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).")
811
815
  );
812
816
  }
813
817
 
@@ -1010,7 +1014,7 @@ async function shakeOutHarness(runtime, cfg, deps = {}) {
1010
1014
  try {
1011
1015
  if (runtime === "opencode") {
1012
1016
  const url = cfg.opencode?.serverUrl;
1013
- if (!url) return "failed";
1017
+ if (!url) return "absent";
1014
1018
  return await probeOpencode(url) !== null ? "ok" : "failed";
1015
1019
  }
1016
1020
  const { auth, presence } = runtime === "codex" ? {
@@ -1020,11 +1024,13 @@ async function shakeOutHarness(runtime, cfg, deps = {}) {
1020
1024
  auth: ["claude", ["auth", "status"]],
1021
1025
  presence: ["claude", ["--version"]]
1022
1026
  };
1027
+ const presenceRun = await run(presence[0], [...presence[1]]);
1028
+ if (presenceRun.error === "spawn") return "absent";
1029
+ if (presenceRun.code !== 0) return "unverified";
1023
1030
  const authRun = await run(auth[0], [...auth[1]]);
1024
1031
  if (authRun.code === 0) return "ok";
1025
1032
  if (looksUnsupported(authRun.output)) {
1026
- const presenceRun = await run(presence[0], [...presence[1]]);
1027
- return presenceRun.code === 0 ? "unverified" : "failed";
1033
+ return "unverified";
1028
1034
  }
1029
1035
  return "failed";
1030
1036
  } catch {
@@ -1032,6 +1038,7 @@ async function shakeOutHarness(runtime, cfg, deps = {}) {
1032
1038
  }
1033
1039
  }
1034
1040
  function connectedLine(runtime, verdict) {
1041
+ if (verdict === "absent") throw new Error("an absent harness cannot be connected");
1035
1042
  const label = HARNESS_LABELS[runtime];
1036
1043
  if (verdict !== "failed") return `${label} connected.`;
1037
1044
  return `${label} connected \u2014 ${FAILED_SUFFIX[runtime]}`;
@@ -1041,6 +1048,14 @@ var FAILED_SUFFIX = {
1041
1048
  codex: "it doesn\u2019t look signed in yet. Run `codex login` once, then it\u2019s ready.",
1042
1049
  opencode: "its server isn\u2019t answering. Start `opencode serve`, then it\u2019s ready."
1043
1050
  };
1051
+ function absentLine(runtime) {
1052
+ if (runtime === "opencode") {
1053
+ return "No opencode server is configured \u2014 run `opencode serve` and connect with `--url`.";
1054
+ }
1055
+ const label = HARNESS_LABELS[runtime];
1056
+ const login = runtime === "codex" ? "`codex login`" : "`claude`";
1057
+ return `${label} isn\u2019t installed on this machine \u2014 install it and sign in (${login}), then run this again.`;
1058
+ }
1044
1059
  function looksUnsupported(output) {
1045
1060
  return /unrecognized|unknown (sub)?command|unexpected argument|invalid (sub)?command|no such (sub)?command|usage:|did you mean/i.test(
1046
1061
  output
@@ -1049,17 +1064,17 @@ function looksUnsupported(output) {
1049
1064
  function runBounded(command, args) {
1050
1065
  return new Promise((resolve) => {
1051
1066
  let settled = false;
1052
- const done = (code, output) => {
1067
+ const done = (result) => {
1053
1068
  if (settled) return;
1054
1069
  settled = true;
1055
1070
  clearTimeout(timer);
1056
- resolve({ code, output });
1071
+ resolve(result);
1057
1072
  };
1058
1073
  let child;
1059
1074
  try {
1060
1075
  child = spawn4(command, args, { stdio: ["ignore", "pipe", "pipe"] });
1061
1076
  } catch {
1062
- resolve({ code: null, output: "" });
1077
+ resolve({ code: null, output: "", error: "spawn" });
1063
1078
  return;
1064
1079
  }
1065
1080
  let out = "";
@@ -1070,11 +1085,11 @@ function runBounded(command, args) {
1070
1085
  child.stderr?.on("data", capture);
1071
1086
  const timer = setTimeout(() => {
1072
1087
  child.kill("SIGKILL");
1073
- done(null, out);
1088
+ done({ code: null, output: out, error: "timeout" });
1074
1089
  }, CHECK_TIMEOUT_MS);
1075
1090
  timer.unref?.();
1076
- child.once("error", () => done(null, out));
1077
- child.once("exit", (code) => done(code, out));
1091
+ child.once("error", () => done({ code: null, output: out, error: "spawn" }));
1092
+ child.once("exit", (code) => done({ code, output: out }));
1078
1093
  });
1079
1094
  }
1080
1095
 
@@ -1271,11 +1286,23 @@ async function connect2(raw, opts = {}) {
1271
1286
  }
1272
1287
  const live = await liveSocket();
1273
1288
  if (live) {
1274
- const result = await controlRequest(live, {
1275
- cmd: "connect",
1276
- runtime,
1277
- ...opts.serverUrl ? { serverUrl: opts.serverUrl } : {}
1278
- });
1289
+ let result;
1290
+ try {
1291
+ result = await controlRequest(
1292
+ live,
1293
+ {
1294
+ cmd: "connect",
1295
+ runtime,
1296
+ ...opts.serverUrl ? { serverUrl: opts.serverUrl } : {}
1297
+ },
1298
+ 1e4
1299
+ );
1300
+ } catch (err) {
1301
+ const detail = err instanceof Error ? err.message : String(err);
1302
+ throw new CompanionError(
1303
+ `Couldn\u2019t reach the running companion \u2014 ${detail}; try again, or \`cabane-companion stop\` and \`start\`.`
1304
+ );
1305
+ }
1279
1306
  if (!result.ok) throw new CompanionError(result.message);
1280
1307
  write(INDENT + tick(result.message));
1281
1308
  return;
@@ -1307,8 +1334,9 @@ async function connect2(raw, opts = {}) {
1307
1334
  }
1308
1335
  next = { ...cfg, opencode: { serverUrl } };
1309
1336
  }
1310
- saveConfig(next);
1311
1337
  const verdict = await shakeOutHarness(runtime, next);
1338
+ if (verdict === "absent") throw new CompanionError(absentLine(runtime));
1339
+ saveConfig(next);
1312
1340
  write(INDENT + tick(connectedLine(runtime, verdict)));
1313
1341
  write(`${INDENT}Not running yet \u2014 start it with: cabane-companion start`);
1314
1342
  }
@@ -1639,30 +1667,10 @@ async function pair(opts = {}) {
1639
1667
  }
1640
1668
 
1641
1669
  // src/cli.ts
1642
- import { readFileSync as readFileSync12 } from "fs";
1670
+ import { readFileSync as readFileSync11 } from "fs";
1643
1671
 
1644
- // src/service/index.ts
1645
- import { dirname as dirname5 } from "path";
1646
-
1647
- // src/service/host.ts
1648
- import { spawnSync } from "child_process";
1649
- import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync4, rmSync as rmSync5, writeFileSync as writeFileSync4 } from "fs";
1650
- import { homedir as homedir3 } from "os";
1651
-
1652
- // src/cli-entry.ts
1653
- import { existsSync as existsSync5 } from "fs";
1654
- import { fileURLToPath } from "url";
1655
- var RELATIVE_CANDIDATES = ["./cli.js", "../dist/cli.js", "../cli.js"];
1656
- function companionCliEntry(deps = {}) {
1657
- const exists = deps.exists ?? existsSync5;
1658
- const candidates = deps.candidates ?? RELATIVE_CANDIDATES.map((rel) => fileURLToPath(new URL(rel, import.meta.url)));
1659
- for (const candidate of candidates) {
1660
- if (exists(candidate)) return candidate;
1661
- }
1662
- const argv1 = "argv1" in deps ? deps.argv1 : process.argv[1];
1663
- if (argv1 && exists(argv1)) return argv1;
1664
- return candidates[0] ?? "";
1665
- }
1672
+ // src/commands/start.ts
1673
+ import { hostname as hostname2 } from "os";
1666
1674
 
1667
1675
  // src/logger.ts
1668
1676
  import { createWriteStream, mkdirSync as mkdirSync5 } from "fs";
@@ -1692,473 +1700,55 @@ function consoleMessageFormat(log, messageKey) {
1692
1700
  return short ? `${short} ${msg}` : msg;
1693
1701
  }
1694
1702
  var cached = null;
1695
- function getLogger() {
1696
- if (cached) return cached;
1703
+ var consoleLogging = true;
1704
+ function setConsoleLogging(enabled) {
1705
+ consoleLogging = enabled;
1706
+ }
1707
+ function createLogger(destinations = {}) {
1697
1708
  const path = companionLogPath();
1698
- mkdirSync5(dirname3(path), { recursive: true });
1709
+ if (!destinations.file) mkdirSync5(dirname3(path), { recursive: true });
1699
1710
  const streams = [];
1700
1711
  if (process.env.CABANE_COMPANION_DAEMON !== "1") {
1701
1712
  const consoleStream = pretty({
1702
1713
  colorize: true,
1703
1714
  ignore: CONSOLE_IGNORE,
1704
- messageFormat: (log, messageKey) => consoleMessageFormat(log, messageKey)
1715
+ messageFormat: (log, messageKey) => consoleMessageFormat(log, messageKey),
1716
+ ...destinations.console ? { destination: destinations.console } : {}
1705
1717
  });
1706
- streams.push({ level: "info", stream: consoleStream });
1707
- }
1708
- streams.push({ level: "debug", stream: createWriteStream(path, { flags: "a" }) });
1709
- cached = pino({ level: "debug" }, pino.multistream(streams));
1710
- return cached;
1711
- }
1712
-
1713
- // src/service/host.ts
1714
- var RUN_TIMEOUT_MS = 1e4;
1715
- function defaultServiceHost() {
1716
- if (process.env.VITEST) {
1717
- throw new Error(
1718
- "defaultServiceHost() was reached during a test run \u2014 it shells out to the real service manager. Inject a fake host (apps/companion/test/service-host-fake.ts) instead."
1719
- );
1720
- }
1721
- return {
1722
- platform: process.platform,
1723
- env: process.env,
1724
- home: homedir3(),
1725
- uid: process.getuid?.() ?? 0,
1726
- execPath: process.execPath,
1727
- cliPath: companionCliEntry(),
1728
- logPath: companionLogPath(),
1729
- fs: {
1730
- read: (path) => {
1731
- try {
1732
- return readFileSync4(path, "utf8");
1733
- } catch {
1734
- return null;
1718
+ streams.push({
1719
+ level: "info",
1720
+ stream: {
1721
+ write(chunk) {
1722
+ if (consoleLogging) consoleStream.write(chunk);
1735
1723
  }
1736
- },
1737
- write: (path, contents) => writeFileSync4(path, contents, "utf8"),
1738
- remove: (path) => rmSync5(path, { force: true }),
1739
- exists: (path) => existsSync6(path),
1740
- mkdirp: (dir2) => {
1741
- mkdirSync6(dir2, { recursive: true });
1742
1724
  }
1743
- },
1744
- run: (cmd, args) => {
1745
- const res = spawnSync(cmd, args, { encoding: "utf8", timeout: RUN_TIMEOUT_MS });
1746
- return {
1747
- ok: res.status === 0,
1748
- stdout: res.stdout ?? "",
1749
- stderr: res.stderr ?? (res.error ? res.error.message : "")
1750
- };
1751
- }
1752
- };
1753
- }
1754
-
1755
- // src/service/launchd.ts
1756
- import { join as join6 } from "path";
1757
- var LAUNCHD_LABEL = "ai.cabane.companion";
1758
- function launchAgentPath(home) {
1759
- return join6(home, "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
1760
- }
1761
- function renderPlist(input) {
1762
- const args = input.programArguments.map((a) => ` <string>${xml(a)}</string>`).join("\n");
1763
- const env = Object.entries(input.environment).map(([k, v]) => ` <key>${xml(k)}</key>
1764
- <string>${xml(v)}</string>`).join("\n");
1765
- return [
1766
- '<?xml version="1.0" encoding="UTF-8"?>',
1767
- '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
1768
- '<plist version="1.0">',
1769
- "<dict>",
1770
- " <key>Label</key>",
1771
- ` <string>${LAUNCHD_LABEL}</string>`,
1772
- " <key>ProgramArguments</key>",
1773
- " <array>",
1774
- args,
1775
- " </array>",
1776
- " <key>RunAtLoad</key>",
1777
- " <true/>",
1778
- " <key>KeepAlive</key>",
1779
- " <dict>",
1780
- " <key>SuccessfulExit</key>",
1781
- " <false/>",
1782
- " </dict>",
1783
- " <key>EnvironmentVariables</key>",
1784
- " <dict>",
1785
- env,
1786
- " </dict>",
1787
- " <key>StandardOutPath</key>",
1788
- ` <string>${xml(input.logPath)}</string>`,
1789
- " <key>StandardErrorPath</key>",
1790
- ` <string>${xml(input.logPath)}</string>`,
1791
- "</dict>",
1792
- "</plist>",
1793
- ""
1794
- ].join("\n");
1795
- }
1796
- function domain(host) {
1797
- return `gui/${host.uid}`;
1798
- }
1799
- function target(host) {
1800
- return `${domain(host)}/${LAUNCHD_LABEL}`;
1801
- }
1802
- function launchdStart(host, plistPath) {
1803
- host.run("launchctl", ["bootout", target(host)]);
1804
- const res = host.run("launchctl", ["bootstrap", domain(host), plistPath]);
1805
- return res.ok ? { ok: true } : { ok: false, detail: firstLine(res.stderr || res.stdout) };
1806
- }
1807
- function launchdStop(host) {
1808
- const res = host.run("launchctl", ["bootout", target(host)]);
1809
- if (res.ok || notLoaded(res.stderr + res.stdout)) return { ok: true };
1810
- return { ok: false, detail: firstLine(res.stderr || res.stdout) };
1811
- }
1812
- function launchdProbe(host) {
1813
- const res = host.run("launchctl", ["print", target(host)]);
1814
- if (res.ok) {
1815
- const state = /state\s*=\s*(\w+)/.exec(res.stdout)?.[1];
1816
- return { ownership: "held", running: state ? state === "running" : "unknown" };
1817
- }
1818
- return notLoaded(res.stderr + res.stdout) ? { ownership: "clear", running: false } : { ownership: "unknown", running: "unknown" };
1819
- }
1820
- function launchdRemove(host, plistPath) {
1821
- const stopped = launchdStop(host);
1822
- if (!stopped.ok) return stopped;
1823
- host.fs.remove(plistPath);
1824
- return { ok: true };
1825
- }
1826
- var NOT_LOADED = new RegExp(
1827
- `no such process|(could not find|not find service)[^\\n]*${LAUNCHD_LABEL.replace(
1828
- /[.*+?^${}()|[\]\\]/g,
1829
- "\\$&"
1830
- )}`,
1831
- "i"
1832
- );
1833
- function notLoaded(output) {
1834
- return NOT_LOADED.test(output);
1835
- }
1836
- function firstLine(s) {
1837
- return s.trim().split("\n")[0] ?? "";
1838
- }
1839
- function xml(value) {
1840
- return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1841
- }
1842
-
1843
- // src/service/systemd.ts
1844
- import { dirname as dirname4, join as join7 } from "path";
1845
- var SYSTEMD_UNIT = "cabane-companion.service";
1846
- function systemdUnitPath(host) {
1847
- const configHome = host.env.XDG_CONFIG_HOME?.trim() ? host.env.XDG_CONFIG_HOME.trim() : join7(host.home, ".config");
1848
- return join7(configHome, "systemd", "user", SYSTEMD_UNIT);
1849
- }
1850
- function renderUnit(input) {
1851
- const [exec, ...rest] = input.programArguments;
1852
- const execStart = [quote(exec ?? ""), ...rest.map(quote)].join(" ");
1853
- const env = Object.entries(input.environment).map(([k, v]) => `Environment=${k}=${v}`);
1854
- return [
1855
- "[Unit]",
1856
- "Description=Cabane Companion \u2014 keeps this device answering while you are logged in",
1857
- "After=network-online.target",
1858
- "Wants=network-online.target",
1859
- "",
1860
- "[Service]",
1861
- "Type=simple",
1862
- ...env,
1863
- `ExecStart=${execStart}`,
1864
- "Restart=on-failure",
1865
- "RestartSec=5",
1866
- "",
1867
- "[Install]",
1868
- "WantedBy=default.target"
1869
- ].join("\n") + "\n";
1870
- }
1871
- function systemdReload(host) {
1872
- host.run("systemctl", ["--user", "daemon-reload"]);
1873
- }
1874
- function systemdStart(host) {
1875
- systemdReload(host);
1876
- const enabled = host.run("systemctl", ["--user", "enable", SYSTEMD_UNIT]);
1877
- if (!enabled.ok) return { ok: false, detail: firstLine2(enabled.stderr || enabled.stdout) };
1878
- const started = host.run("systemctl", ["--user", "restart", SYSTEMD_UNIT]);
1879
- if (!started.ok) return { ok: false, detail: firstLine2(started.stderr || started.stdout) };
1880
- return { ok: true };
1881
- }
1882
- function systemdStop(host) {
1883
- const res = host.run("systemctl", ["--user", "stop", SYSTEMD_UNIT]);
1884
- if (res.ok || notLoaded2(res.stderr + res.stdout)) return { ok: true };
1885
- return { ok: false, detail: firstLine2(res.stderr || res.stdout) };
1886
- }
1887
- function systemdProbe(host) {
1888
- const state = host.run("systemctl", ["--user", "is-active", SYSTEMD_UNIT]).stdout.trim();
1889
- if (state === "active") return { ownership: "held", running: true };
1890
- if (state === "activating" || state === "reloading" || state === "deactivating") {
1891
- return { ownership: "held", running: "transitional" };
1892
- }
1893
- if (state === "inactive" || state === "failed") return { ownership: "clear", running: false };
1894
- return { ownership: "unknown", running: "unknown" };
1895
- }
1896
- function systemdWantsLinkPath(host) {
1897
- return join7(dirname4(systemdUnitPath(host)), "default.target.wants", SYSTEMD_UNIT);
1898
- }
1899
- function systemdInstalled(host) {
1900
- return host.fs.exists(systemdUnitPath(host)) || host.fs.exists(systemdWantsLinkPath(host));
1901
- }
1902
- function systemdRemove(host, unitPath) {
1903
- const stopped = host.run("systemctl", ["--user", "disable", "--now", SYSTEMD_UNIT]);
1904
- if (!(stopped.ok || notLoaded2(stopped.stderr + stopped.stdout))) {
1905
- return { ok: false, detail: firstLine2(stopped.stderr || stopped.stdout) };
1906
- }
1907
- host.fs.remove(unitPath);
1908
- const link = systemdWantsLinkPath(host);
1909
- if (host.fs.exists(link)) host.fs.remove(link);
1910
- systemdReload(host);
1911
- return { ok: true };
1912
- }
1913
- var NOT_LOADED2 = new RegExp(
1914
- `unit (file )?${SYSTEMD_UNIT.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")} (not loaded|does not exist|not found|could not be found)`,
1915
- "i"
1916
- );
1917
- function notLoaded2(output) {
1918
- return NOT_LOADED2.test(output);
1919
- }
1920
- function readLinger(host) {
1921
- const res = host.run("loginctl", ["show-user", String(host.uid), "-p", "Linger"]);
1922
- if (!res.ok) return "unknown";
1923
- const match = /Linger=(\w+)/.exec(res.stdout);
1924
- if (!match) return "unknown";
1925
- return match[1] === "yes" ? "yes" : "no";
1926
- }
1927
- function tryEnableLinger(host) {
1928
- host.run("loginctl", ["enable-linger", String(host.uid)]);
1929
- return readLinger(host);
1930
- }
1931
- function isRemoteSession(env) {
1932
- return Boolean(env.SSH_CONNECTION || env.SSH_TTY || env.SSH_CLIENT);
1933
- }
1934
- function quote(value) {
1935
- return `"${value.replace(/(["\\])/g, "\\$1")}"`;
1936
- }
1937
- function firstLine2(s) {
1938
- return s.trim().split("\n")[0] ?? "";
1939
- }
1940
-
1941
- // src/service/index.ts
1942
- function detectServiceManager(host = defaultServiceHost()) {
1943
- if (host.platform === "darwin") return "launchd";
1944
- if (host.platform !== "linux") return "none";
1945
- return host.run("systemctl", ["--user", "show-environment"]).ok ? "systemd-user" : "none";
1946
- }
1947
- function programArguments(host, _opts) {
1948
- return [host.execPath, host.cliPath, "start", "--foreground"];
1949
- }
1950
- function environment(host) {
1951
- return {
1952
- // PATH CAPTURE. launchd's default PATH is `/usr/bin:/bin:/usr/sbin:/sbin`
1953
- // and a systemd user manager's is barely better — neither has `claude`,
1954
- // `codex` or `opencode` on it. A companion that runs but can't find its
1955
- // harness is worse than one that isn't running, because the device reads
1956
- // Online. So we bake in the PATH of the shell that ran `start`, and because
1957
- // it's part of the rendered content, a PATH that later drifts re-renders on
1958
- // the next `start` like any other change.
1959
- PATH: host.env.PATH ?? "",
1960
- CABANE_COMPANION_DAEMON: "1"
1961
- };
1962
- }
1963
- function unitPathFor(host, manager) {
1964
- if (manager === "launchd") return launchAgentPath(host.home);
1965
- if (manager === "systemd-user") return systemdUnitPath(host);
1966
- return null;
1967
- }
1968
- function renderFor(host, manager, opts) {
1969
- const input = { programArguments: programArguments(host, opts), environment: environment(host) };
1970
- return manager === "launchd" ? renderPlist({ ...input, logPath: host.logPath }) : renderUnit(input);
1971
- }
1972
- function installService(opts = {}, host = defaultServiceHost()) {
1973
- const manager = detectServiceManager(host);
1974
- if (manager === "none") return { installed: false, reason: "unsupported" };
1975
- const path = unitPathFor(host, manager);
1976
- if (!path) return { installed: false, reason: "unsupported" };
1977
- const desired = renderFor(host, manager, opts);
1978
- const existing = host.fs.read(path);
1979
- const hadUnit = existing !== null;
1980
- const changed = existing !== desired;
1981
- if (changed) {
1982
- host.fs.mkdirp(dirname5(path));
1983
- host.fs.write(path, desired);
1984
- }
1985
- if (manager === "systemd-user") {
1986
- const linger = ensureLinger(host);
1987
- if (linger !== "yes" && isRemoteSession(host.env)) {
1988
- systemdRemove(host, path);
1989
- return failedInstall(
1990
- host,
1991
- manager,
1992
- "linger-unavailable",
1993
- "a systemd --user service would stop when this SSH session ends (lingering is off and could not be enabled)"
1994
- );
1995
- }
1996
- }
1997
- const started = manager === "launchd" ? launchdStart(host, path) : systemdStart(host);
1998
- if (!started.ok) {
1999
- if (!hadUnit) removeService(host, manager, path);
2000
- return failedInstall(host, manager, "command-failed", started.detail);
2001
- }
2002
- return { installed: true, manager, changed };
2003
- }
2004
- function failedInstall(host, manager, reason, detail) {
2005
- return {
2006
- installed: false,
2007
- reason,
2008
- ...detail ? { detail } : {},
2009
- ...managerOwnership(host, manager) === "clear" ? {} : { leftBehind: manager }
2010
- };
2011
- }
2012
- function managerProbe(host, manager) {
2013
- if (manager === "launchd") return launchdProbe(host);
2014
- if (manager === "systemd-user") return systemdProbe(host);
2015
- return { ownership: "clear", running: false };
2016
- }
2017
- function managerOwnership(host, manager) {
2018
- return managerProbe(host, manager).ownership;
2019
- }
2020
- function refreshInstalledService(opts = {}, host = defaultServiceHost()) {
2021
- const manager = detectServiceManager(host);
2022
- const path = unitPathFor(host, manager);
2023
- if (!path || !host.fs.exists(path)) return false;
2024
- const desired = renderFor(host, manager, opts);
2025
- if (host.fs.read(path) === desired) return false;
2026
- host.fs.write(path, desired);
2027
- if (manager === "systemd-user") systemdReload(host);
2028
- return true;
2029
- }
2030
- function stopService(host = defaultServiceHost()) {
2031
- const manager = detectServiceManager(host);
2032
- const path = unitPathFor(host, manager);
2033
- if (manager === "none" || !path || !managerHasClaim(host, manager, path)) {
2034
- return { handled: false, ok: true };
2035
- }
2036
- const res = manager === "launchd" ? launchdStop(host) : systemdStop(host);
2037
- return {
2038
- handled: true,
2039
- ok: res.ok,
2040
- manager,
2041
- ...res.detail ? { detail: res.detail } : {}
2042
- };
2043
- }
2044
- function disableService(host = defaultServiceHost()) {
2045
- const manager = detectServiceManager(host);
2046
- const path = unitPathFor(host, manager);
2047
- if (manager === "none" || !path) return { handled: false, ok: true };
2048
- if (!managerHasClaim(host, manager, path)) return { handled: false, ok: true, manager };
2049
- const res = removeService(host, manager, path);
2050
- return { handled: true, ok: res.ok, manager, ...res.detail ? { detail: res.detail } : {} };
2051
- }
2052
- function managerHasClaim(host, manager, path) {
2053
- if (manager === "none") return false;
2054
- if (host.fs.exists(path)) return true;
2055
- if (manager === "systemd-user" && systemdInstalled(host)) return true;
2056
- return managerOwnership(host, manager) !== "clear";
2057
- }
2058
- function removeService(host, manager, path) {
2059
- return manager === "launchd" ? launchdRemove(host, path) : systemdRemove(host, path);
2060
- }
2061
- function serviceStatus(host = defaultServiceHost()) {
2062
- const manager = detectServiceManager(host);
2063
- const path = unitPathFor(host, manager);
2064
- const probe = managerProbe(host, manager);
2065
- const installed = manager === "none" || !path ? false : host.fs.exists(path) || probe.ownership === "held" || manager === "systemd-user" && systemdInstalled(host);
2066
- const running = manager === "none" ? false : probe.running;
2067
- return {
2068
- manager,
2069
- unitPath: path,
2070
- installed,
2071
- running,
2072
- linger: manager === "systemd-user" ? readLinger(host) : null,
2073
- remoteSession: isRemoteSession(host.env)
2074
- };
2075
- }
2076
- function ensureLinger(host) {
2077
- const current = readLinger(host);
2078
- if (current === "yes") return current;
2079
- return tryEnableLinger(host);
2080
- }
2081
-
2082
- // src/commands/service.ts
2083
- function serviceStatusCommand(deps = {}) {
2084
- const status2 = (deps.status ?? serviceStatus)();
2085
- if (status2.manager === "none") {
2086
- process.stdout.write(
2087
- "service: not supported on this machine yet \u2014 `cabane-companion start` runs the companion detached instead (it stops at the next reboot).\n"
2088
- );
2089
- return;
2090
- }
2091
- process.stdout.write(`manager: ${status2.manager}
2092
- `);
2093
- process.stdout.write(
2094
- `unit: ${status2.unitPath}${status2.installed ? "" : " (not written)"}
2095
- `
2096
- );
2097
- process.stdout.write(`installed: ${status2.installed ? "yes" : "no"}
2098
- `);
2099
- process.stdout.write(`running: ${runningLine(status2.running)}
2100
- `);
2101
- if (status2.manager === "systemd-user") {
2102
- process.stdout.write(`linger: ${status2.linger}${lingerNote(status2)}
2103
- `);
2104
- }
2105
- if (status2.installed) {
2106
- process.stdout.write("disable: cabane-companion service disable\n");
2107
- } else {
2108
- process.stdout.write("install: cabane-companion start (installs it for you)\n");
2109
- }
2110
- }
2111
- function serviceDisableCommand(deps = {}) {
2112
- const disable = deps.disable ?? disableService;
2113
- const res = disable();
2114
- if (!res.handled) {
2115
- process.stdout.write("service: nothing installed (nothing to remove).\n");
2116
- return;
2117
- }
2118
- if (!res.ok) {
2119
- process.stdout.write(
2120
- `service: nothing was removed \u2014 ${res.detail ?? "the service manager reported an error"}.
2121
- The unit is still installed and may still be running; \`cabane-companion service status\` shows where it stands.
2122
- `
2123
- );
2124
- process.exitCode = 1;
2125
- return;
2126
- }
2127
- process.stdout.write(
2128
- "service: stopped and removed. The companion no longer starts at login; `cabane-companion start` sets it up again.\n"
2129
- );
2130
- }
2131
- function runningLine(running) {
2132
- if (running === "transitional") {
2133
- return "starting or stopping \u2014 the manager reported a state in transition; ask again in a moment";
1725
+ });
2134
1726
  }
2135
- if (running === "unknown") return "unknown \u2014 the service manager didn't answer";
2136
- return running ? "yes" : "no";
1727
+ streams.push({
1728
+ level: "debug",
1729
+ stream: destinations.file ?? createWriteStream(path, { flags: "a" })
1730
+ });
1731
+ return pino({ level: "debug" }, pino.multistream(streams));
2137
1732
  }
2138
- function lingerNote(status2) {
2139
- if (status2.linger === "yes") return " (survives logout)";
2140
- if (status2.remoteSession) {
2141
- return " \u2014 without lingering a --user service stops when this SSH session ends";
2142
- }
2143
- return " \u2014 the service runs while you are logged in";
1733
+ function getLogger() {
1734
+ if (cached) return cached;
1735
+ cached = createLogger();
1736
+ return cached;
2144
1737
  }
2145
1738
 
2146
- // src/commands/start.ts
2147
- import { hostname as hostname2 } from "os";
2148
-
2149
1739
  // src/runtime.ts
2150
1740
  import { randomUUID as randomUUID2 } from "crypto";
2151
1741
 
2152
1742
  // src/dashboard/server.ts
2153
- import { dirname as dirname6, join as join9 } from "path";
2154
- import { fileURLToPath as fileURLToPath2 } from "url";
1743
+ import { dirname as dirname4, join as join7 } from "path";
1744
+ import { fileURLToPath } from "url";
2155
1745
  import { serve } from "@hono/node-server";
2156
1746
  import { Hono } from "hono";
2157
1747
 
2158
1748
  // src/dashboard/routes.ts
2159
- import { openSync as openSync2, readSync, closeSync as closeSync2, fstatSync, existsSync as existsSync7 } from "fs";
1749
+ import { openSync as openSync2, readSync, closeSync as closeSync2, fstatSync, existsSync as existsSync5 } from "fs";
2160
1750
  import { readFile } from "fs/promises";
2161
- import { extname, join as join8, normalize } from "path";
1751
+ import { extname, join as join6, normalize } from "path";
2162
1752
  import { streamSSE } from "hono/streaming";
2163
1753
 
2164
1754
  // src/state.ts
@@ -2407,14 +1997,14 @@ var CONTENT_TYPES = {
2407
1997
  function registerRoutes(app, deps) {
2408
1998
  const { supervisor, hub, staticDir } = deps;
2409
1999
  app.get("/", async (c) => {
2410
- const html = await readFile(join8(staticDir, "index.html"), "utf8");
2000
+ const html = await readFile(join6(staticDir, "index.html"), "utf8");
2411
2001
  return c.html(html);
2412
2002
  });
2413
2003
  app.get("/static/:file", async (c) => {
2414
2004
  const file = c.req.param("file");
2415
2005
  const safe3 = normalize(file).replace(/^(\.\.[/\\])+/, "");
2416
- const full = join8(staticDir, safe3);
2417
- if (!full.startsWith(staticDir) || !existsSync7(full)) return c.notFound();
2006
+ const full = join6(staticDir, safe3);
2007
+ if (!full.startsWith(staticDir) || !existsSync5(full)) return c.notFound();
2418
2008
  const body = await readFile(full);
2419
2009
  const type = CONTENT_TYPES[extname(full).toLowerCase()] ?? "application/octet-stream";
2420
2010
  c.header("content-type", type);
@@ -2541,7 +2131,7 @@ function clampLimit(raw, fallback, max = 200) {
2541
2131
  return Math.min(Math.floor(n), max);
2542
2132
  }
2543
2133
  function tailFile(path, lines) {
2544
- if (!existsSync7(path)) return [];
2134
+ if (!existsSync5(path)) return [];
2545
2135
  const MAX_BYTES = 256 * 1024;
2546
2136
  let fd;
2547
2137
  try {
@@ -2630,7 +2220,7 @@ function isAddrInUse(err) {
2630
2220
  return Boolean(err && typeof err === "object" && "code" in err && err.code === "EADDRINUSE");
2631
2221
  }
2632
2222
  function resolveStaticDir() {
2633
- return join9(dirname6(fileURLToPath2(import.meta.url)), "static");
2223
+ return join7(dirname4(fileURLToPath(import.meta.url)), "static");
2634
2224
  }
2635
2225
 
2636
2226
  // src/api.ts
@@ -3106,21 +2696,21 @@ function errorMessage2(status2, body) {
3106
2696
  }
3107
2697
 
3108
2698
  // src/cursor.ts
3109
- import { mkdirSync as mkdirSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync5, existsSync as existsSync8 } from "fs";
3110
- import { join as join10 } from "path";
2699
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync4, existsSync as existsSync6 } from "fs";
2700
+ import { join as join8 } from "path";
3111
2701
  function pathFor(workspaceId) {
3112
- return join10(cabaneDir(), "cursors", encodeURIComponent(workspaceId));
2702
+ return join8(cabaneDir(), "cursors", encodeURIComponent(workspaceId));
3113
2703
  }
3114
2704
  function readCursor(workspaceId) {
3115
2705
  const path = pathFor(workspaceId);
3116
- if (!existsSync8(path)) return null;
3117
- const raw = readFileSync5(path, "utf8").trim();
2706
+ if (!existsSync6(path)) return null;
2707
+ const raw = readFileSync4(path, "utf8").trim();
3118
2708
  return raw.length > 0 ? raw : null;
3119
2709
  }
3120
2710
  function writeCursor(workspaceId, eventId) {
3121
2711
  const path = pathFor(workspaceId);
3122
- mkdirSync7(join10(cabaneDir(), "cursors"), { recursive: true });
3123
- writeFileSync5(path, eventId + "\n", "utf8");
2712
+ mkdirSync6(join8(cabaneDir(), "cursors"), { recursive: true });
2713
+ writeFileSync4(path, eventId + "\n", "utf8");
3124
2714
  }
3125
2715
 
3126
2716
  // src/cursor-tracker.ts
@@ -3163,20 +2753,20 @@ var CursorTracker = class {
3163
2753
  };
3164
2754
 
3165
2755
  // src/dispatch-dedupe.ts
3166
- import { mkdirSync as mkdirSync8, readFileSync as readFileSync6, writeFileSync as writeFileSync6, existsSync as existsSync9 } from "fs";
3167
- import { join as join11 } from "path";
2756
+ import { mkdirSync as mkdirSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync5, existsSync as existsSync7 } from "fs";
2757
+ import { join as join9 } from "path";
3168
2758
  var MAX_IDS = 256;
3169
2759
  function dir(log) {
3170
- return join11(cabaneDir(), log);
2760
+ return join9(cabaneDir(), log);
3171
2761
  }
3172
2762
  function pathFor2(log, workspaceId) {
3173
- return join11(dir(log), encodeURIComponent(workspaceId));
2763
+ return join9(dir(log), encodeURIComponent(workspaceId));
3174
2764
  }
3175
2765
  function readIds(log, workspaceId) {
3176
2766
  const path = pathFor2(log, workspaceId);
3177
- if (!existsSync9(path)) return [];
2767
+ if (!existsSync7(path)) return [];
3178
2768
  try {
3179
- return readFileSync6(path, "utf8").split("\n").map((s) => s.trim()).filter((s) => s.length > 0);
2769
+ return readFileSync5(path, "utf8").split("\n").map((s) => s.trim()).filter((s) => s.length > 0);
3180
2770
  } catch {
3181
2771
  return [];
3182
2772
  }
@@ -3189,8 +2779,8 @@ function mark(log, workspaceId, eventId) {
3189
2779
  if (ids.includes(eventId)) return;
3190
2780
  ids.push(eventId);
3191
2781
  const trimmed = ids.length > MAX_IDS ? ids.slice(-MAX_IDS) : ids;
3192
- mkdirSync8(dir(log), { recursive: true });
3193
- writeFileSync6(pathFor2(log, workspaceId), trimmed.join("\n") + "\n", "utf8");
2782
+ mkdirSync7(dir(log), { recursive: true });
2783
+ writeFileSync5(pathFor2(log, workspaceId), trimmed.join("\n") + "\n", "utf8");
3194
2784
  }
3195
2785
  function hasDispatched(workspaceId, eventId) {
3196
2786
  return has("dispatched", workspaceId, eventId);
@@ -3206,17 +2796,17 @@ function markCompleted(workspaceId, eventId) {
3206
2796
  }
3207
2797
  var MAX_RESUME_ATTEMPTS = 3;
3208
2798
  function resumeDir() {
3209
- return join11(cabaneDir(), "resume-attempts");
2799
+ return join9(cabaneDir(), "resume-attempts");
3210
2800
  }
3211
2801
  function resumePathFor(workspaceId) {
3212
- return join11(resumeDir(), encodeURIComponent(workspaceId));
2802
+ return join9(resumeDir(), encodeURIComponent(workspaceId));
3213
2803
  }
3214
2804
  function readResumeCounts(workspaceId) {
3215
2805
  const out = /* @__PURE__ */ new Map();
3216
2806
  const path = resumePathFor(workspaceId);
3217
- if (!existsSync9(path)) return out;
2807
+ if (!existsSync7(path)) return out;
3218
2808
  try {
3219
- for (const line of readFileSync6(path, "utf8").split("\n")) {
2809
+ for (const line of readFileSync5(path, "utf8").split("\n")) {
3220
2810
  const trimmed = line.trim();
3221
2811
  if (!trimmed) continue;
3222
2812
  const tab = trimmed.lastIndexOf(" ");
@@ -3236,8 +2826,8 @@ function bumpResumeAttempt(workspaceId, eventId) {
3236
2826
  counts.set(eventId, next);
3237
2827
  const entries = [...counts.entries()];
3238
2828
  const trimmed = entries.length > MAX_IDS ? entries.slice(-MAX_IDS) : entries;
3239
- mkdirSync8(resumeDir(), { recursive: true });
3240
- writeFileSync6(
2829
+ mkdirSync7(resumeDir(), { recursive: true });
2830
+ writeFileSync5(
3241
2831
  resumePathFor(workspaceId),
3242
2832
  trimmed.map(([id, c]) => `${id} ${c}`).join("\n") + "\n",
3243
2833
  "utf8"
@@ -6935,8 +6525,8 @@ var ConnectorHealthStore = class {
6935
6525
 
6936
6526
  // src/dispatcher.ts
6937
6527
  import { randomUUID } from "crypto";
6938
- import { appendFileSync as appendFileSync2, existsSync as existsSync12, mkdirSync as mkdirSync11, readdirSync as readdirSync2, statSync } from "fs";
6939
- import { join as join16 } from "path";
6528
+ import { appendFileSync as appendFileSync2, existsSync as existsSync10, mkdirSync as mkdirSync10, readdirSync as readdirSync2, statSync } from "fs";
6529
+ import { join as join14 } from "path";
6940
6530
 
6941
6531
  // src/summon.ts
6942
6532
  import { z as z12 } from "zod";
@@ -7269,11 +6859,11 @@ function trimSlash2(s) {
7269
6859
  // src/codex-instructions.ts
7270
6860
  import { mkdtemp, rm, writeFile } from "fs/promises";
7271
6861
  import { tmpdir } from "os";
7272
- import { join as join12 } from "path";
6862
+ import { join as join10 } from "path";
7273
6863
  var PREFIX = "cabane-codex-instructions-";
7274
6864
  async function writeCodexInstructionsFile(contents) {
7275
- const dir2 = await mkdtemp(join12(tmpdir(), PREFIX));
7276
- const path = join12(dir2, "instructions.md");
6865
+ const dir2 = await mkdtemp(join10(tmpdir(), PREFIX));
6866
+ const path = join10(dir2, "instructions.md");
7277
6867
  await writeFile(path, contents, { encoding: "utf8", mode: 384 });
7278
6868
  return {
7279
6869
  path,
@@ -7284,22 +6874,22 @@ async function writeCodexInstructionsFile(contents) {
7284
6874
  }
7285
6875
 
7286
6876
  // src/prepared.ts
7287
- import { mkdirSync as mkdirSync9, readFileSync as readFileSync7, rmSync as rmSync6, writeFileSync as writeFileSync7, existsSync as existsSync10 } from "fs";
7288
- import { join as join13 } from "path";
6877
+ import { mkdirSync as mkdirSync8, readFileSync as readFileSync6, rmSync as rmSync5, writeFileSync as writeFileSync6, existsSync as existsSync8 } from "fs";
6878
+ import { join as join11 } from "path";
7289
6879
  function dirFor(workspaceId) {
7290
- return join13(cabaneDir(), "prepared", encodeURIComponent(workspaceId));
6880
+ return join11(cabaneDir(), "prepared", encodeURIComponent(workspaceId));
7291
6881
  }
7292
6882
  function conversationDir(workspaceId, conversationId) {
7293
- return join13(dirFor(workspaceId), encodeURIComponent(conversationId));
6883
+ return join11(dirFor(workspaceId), encodeURIComponent(conversationId));
7294
6884
  }
7295
6885
  function pathFor3(workspaceId, conversationId, agentId) {
7296
- return join13(conversationDir(workspaceId, conversationId), `${encodeURIComponent(agentId)}.json`);
6886
+ return join11(conversationDir(workspaceId, conversationId), `${encodeURIComponent(agentId)}.json`);
7297
6887
  }
7298
6888
  function readPrepared(workspaceId, conversationId, agentId) {
7299
6889
  const path = pathFor3(workspaceId, conversationId, agentId);
7300
- if (!existsSync10(path)) return null;
6890
+ if (!existsSync8(path)) return null;
7301
6891
  try {
7302
- const parsed = JSON.parse(readFileSync7(path, "utf8"));
6892
+ const parsed = JSON.parse(readFileSync6(path, "utf8"));
7303
6893
  if (parsed && typeof parsed.cwd === "string" && parsed.cwd.length > 0) {
7304
6894
  return {
7305
6895
  cwd: parsed.cwd,
@@ -7312,32 +6902,32 @@ function readPrepared(workspaceId, conversationId, agentId) {
7312
6902
  }
7313
6903
  }
7314
6904
  function writePrepared(workspaceId, conversationId, agentId, result) {
7315
- mkdirSync9(conversationDir(workspaceId, conversationId), { recursive: true });
7316
- writeFileSync7(
6905
+ mkdirSync8(conversationDir(workspaceId, conversationId), { recursive: true });
6906
+ writeFileSync6(
7317
6907
  pathFor3(workspaceId, conversationId, agentId),
7318
6908
  JSON.stringify(result) + "\n",
7319
6909
  "utf8"
7320
6910
  );
7321
6911
  }
7322
6912
  function clearPrepared(workspaceId, conversationId, agentId) {
7323
- rmSync6(pathFor3(workspaceId, conversationId, agentId), { force: true });
6913
+ rmSync5(pathFor3(workspaceId, conversationId, agentId), { force: true });
7324
6914
  }
7325
6915
 
7326
6916
  // src/secrets.ts
7327
- import { existsSync as existsSync11, readFileSync as readFileSync8 } from "fs";
7328
- import { join as join14 } from "path";
6917
+ import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
6918
+ import { join as join12 } from "path";
7329
6919
  import { z as z13 } from "zod";
7330
6920
  var PLACEHOLDER_RE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
7331
6921
  function secretsPath() {
7332
- return join14(cabaneDir(), "secrets.json");
6922
+ return join12(cabaneDir(), "secrets.json");
7333
6923
  }
7334
6924
  var secretStoreSchema = z13.record(z13.string(), z13.string());
7335
6925
  function loadSecretStore() {
7336
6926
  const path = secretsPath();
7337
- if (!existsSync11(path)) return makeStore({});
6927
+ if (!existsSync9(path)) return makeStore({});
7338
6928
  let raw;
7339
6929
  try {
7340
- raw = readFileSync8(path, "utf8");
6930
+ raw = readFileSync7(path, "utf8");
7341
6931
  } catch (err) {
7342
6932
  throw new ConfigError(
7343
6933
  `couldn't read ${path}: ${err instanceof Error ? err.message : String(err)}`
@@ -7415,10 +7005,10 @@ function resolveMcpSecrets(mcpServers, store) {
7415
7005
  }
7416
7006
 
7417
7007
  // src/transcript-writer.ts
7418
- import { appendFileSync, chmodSync as chmodSync3, mkdirSync as mkdirSync10, readdirSync, rmSync as rmSync7 } from "fs";
7419
- import { join as join15 } from "path";
7008
+ import { appendFileSync, chmodSync as chmodSync3, mkdirSync as mkdirSync9, readdirSync, rmSync as rmSync6 } from "fs";
7009
+ import { join as join13 } from "path";
7420
7010
  function transcriptsDir() {
7421
- return join15(cabaneDir(), "transcripts");
7011
+ return join13(cabaneDir(), "transcripts");
7422
7012
  }
7423
7013
  var RETAIN = 200;
7424
7014
  var TranscriptWriter = class {
@@ -7427,9 +7017,9 @@ var TranscriptWriter = class {
7427
7017
  onWarn;
7428
7018
  constructor(dir2, meta, onWarn) {
7429
7019
  this.onWarn = onWarn;
7430
- this.path = join15(dir2, fileName(meta));
7020
+ this.path = join13(dir2, fileName(meta));
7431
7021
  try {
7432
- mkdirSync10(dir2, { recursive: true });
7022
+ mkdirSync9(dir2, { recursive: true });
7433
7023
  try {
7434
7024
  chmodSync3(dir2, 448);
7435
7025
  } catch {
@@ -7486,7 +7076,7 @@ function pruneOld(dir2, retain) {
7486
7076
  const drop = files.sort().slice(0, files.length - retain);
7487
7077
  for (const f of drop) {
7488
7078
  try {
7489
- rmSync7(join15(dir2, f), { force: true });
7079
+ rmSync6(join13(dir2, f), { force: true });
7490
7080
  } catch {
7491
7081
  }
7492
7082
  }
@@ -7609,9 +7199,9 @@ var TurnCommitter = class {
7609
7199
  // commit. A self-target is stripped here (mirror of the in-app self-strip); the
7610
7200
  // server strips it again and resolves / ignores an unknown id.
7611
7201
  summonField() {
7612
- const target2 = this.deps.summonState.agentId;
7613
- if (!target2 || target2 === this.deps.agentId) return {};
7614
- return { dispatch: target2 };
7202
+ const target = this.deps.summonState.agentId;
7203
+ if (!target || target === this.deps.agentId) return {};
7204
+ return { dispatch: target };
7615
7205
  }
7616
7206
  // CT326: resolve the per-turn ask into the `ask` field for a `final` commit.
7617
7207
  // The server validates the target (must be a workspace member/owner) and
@@ -7832,7 +7422,7 @@ var DEFAULT_AGENT_IDLE_TIMEOUT_MS = 10 * 6e4;
7832
7422
  var DEFAULT_AGENT_TOTAL_TIMEOUT_MS = 6 * 60 * 6e4;
7833
7423
  function checkoutState(cwd) {
7834
7424
  if (!cwd) return { ok: false, reason: "no working directory was resolved for this turn" };
7835
- if (!existsSync12(cwd)) return { ok: false, reason: `the working directory is gone (${cwd})` };
7425
+ if (!existsSync10(cwd)) return { ok: false, reason: `the working directory is gone (${cwd})` };
7836
7426
  let entries;
7837
7427
  try {
7838
7428
  entries = readdirSync2(cwd);
@@ -7842,15 +7432,15 @@ function checkoutState(cwd) {
7842
7432
  if (entries.length === 0) {
7843
7433
  return { ok: false, reason: `${cwd} is empty \u2014 a recreated shell, not a prepared directory` };
7844
7434
  }
7845
- const gitPath = join16(cwd, ".git");
7846
- if (!existsSync12(gitPath)) return { ok: true, reason: "usable" };
7435
+ const gitPath = join14(cwd, ".git");
7436
+ if (!existsSync10(gitPath)) return { ok: true, reason: "usable" };
7847
7437
  let stat;
7848
7438
  try {
7849
7439
  stat = statSync(gitPath);
7850
7440
  } catch (error) {
7851
7441
  return { ok: false, reason: `${gitPath} is unreadable (${error.message})` };
7852
7442
  }
7853
- if (stat.isDirectory() && !existsSync12(join16(gitPath, "HEAD")))
7443
+ if (stat.isDirectory() && !existsSync10(join14(gitPath, "HEAD")))
7854
7444
  return { ok: false, reason: `${gitPath} has no HEAD \u2014 an empty shell, not a checkout` };
7855
7445
  return { ok: true, reason: "usable" };
7856
7446
  }
@@ -8041,7 +7631,7 @@ var Dispatcher = class {
8041
7631
  let seqCounter = 0;
8042
7632
  const nextSeq = () => ++seqCounter;
8043
7633
  let effectiveCwd = localCwd ?? cabaneCwd;
8044
- if (effectiveCwd && !existsSync12(effectiveCwd)) {
7634
+ if (effectiveCwd && !existsSync10(effectiveCwd)) {
8045
7635
  turnLog.warn(
8046
7636
  { cwd: effectiveCwd },
8047
7637
  "dispatcher: configured working directory does not exist on this device \u2014 falling back to the process cwd"
@@ -8305,11 +7895,11 @@ ${reason}`,
8305
7895
  const totalTimeoutMs = this.opts.totalTimeoutMs ?? DEFAULT_AGENT_TOTAL_TIMEOUT_MS;
8306
7896
  const closeTurnReceipt = (ok, reason) => {
8307
7897
  if (!turnReceiptPath) return;
8308
- const target2 = turnReceiptPath;
7898
+ const target = turnReceiptPath;
8309
7899
  turnReceiptPath = null;
8310
7900
  try {
8311
7901
  appendFileSync2(
8312
- target2,
7902
+ target,
8313
7903
  `${JSON.stringify({
8314
7904
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
8315
7905
  event: "settled",
@@ -8349,7 +7939,7 @@ ${reason}`,
8349
7939
  }
8350
7940
  return this.concludeBeforeRun(payload, turnLog, startedAt, reason);
8351
7941
  }
8352
- const receiptPath = join16(effectiveCwd, ".git", "cabane", "readiness.jsonl");
7942
+ const receiptPath = join14(effectiveCwd, ".git", "cabane", "readiness.jsonl");
8353
7943
  const receiptLine = (fields) => `${JSON.stringify({
8354
7944
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
8355
7945
  taskId: hookEnv.CABANE_TASK_ID,
@@ -8373,7 +7963,7 @@ ${reason}`,
8373
7963
  })}
8374
7964
  `;
8375
7965
  try {
8376
- mkdirSync11(join16(effectiveCwd, ".git", "cabane"), { recursive: true });
7966
+ mkdirSync10(join14(effectiveCwd, ".git", "cabane"), { recursive: true });
8377
7967
  appendFileSync2(
8378
7968
  receiptPath,
8379
7969
  // `starting` is the honest classification before the proof has run. The
@@ -8844,15 +8434,15 @@ async function enumerateOpencodeModels(serverUrl, fetchImpl = fetch) {
8844
8434
 
8845
8435
  // src/outbox.ts
8846
8436
  import {
8847
- existsSync as existsSync13,
8848
- mkdirSync as mkdirSync12,
8437
+ existsSync as existsSync11,
8438
+ mkdirSync as mkdirSync11,
8849
8439
  readdirSync as readdirSync3,
8850
- readFileSync as readFileSync9,
8440
+ readFileSync as readFileSync8,
8851
8441
  renameSync as renameSync3,
8852
- rmSync as rmSync8,
8853
- writeFileSync as writeFileSync8
8442
+ rmSync as rmSync7,
8443
+ writeFileSync as writeFileSync7
8854
8444
  } from "fs";
8855
- import { join as join17 } from "path";
8445
+ import { join as join15 } from "path";
8856
8446
  var MAX_ENTRIES = 2e3;
8857
8447
  var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
8858
8448
  var Outbox = class {
@@ -8865,25 +8455,25 @@ var Outbox = class {
8865
8455
  // Resolved lazily (per call, like cursor.ts) so tests that swap HOME between
8866
8456
  // cases route writes at the right tmpdir.
8867
8457
  dir() {
8868
- return join17(cabaneDir(), "outbox", encodeURIComponent(this.workspaceId));
8458
+ return join15(cabaneDir(), "outbox", encodeURIComponent(this.workspaceId));
8869
8459
  }
8870
8460
  fileFor(turnId, seq) {
8871
- return join17(this.dir(), `${encodeURIComponent(turnId)}__${seq}.json`);
8461
+ return join15(this.dir(), `${encodeURIComponent(turnId)}__${seq}.json`);
8872
8462
  }
8873
8463
  // Persist a commit for later draining. Atomic (temp file + rename) so a
8874
8464
  // concurrent `list()` never reads a half-written entry, then enforces the
8875
8465
  // per-workspace bounds.
8876
8466
  persist(entry) {
8877
8467
  const dir2 = this.dir();
8878
- mkdirSync12(dir2, { recursive: true });
8879
- const target2 = this.fileFor(entry.turnId, entry.seq);
8880
- const tmp = `${target2}.${process.pid}.tmp`;
8468
+ mkdirSync11(dir2, { recursive: true });
8469
+ const target = this.fileFor(entry.turnId, entry.seq);
8470
+ const tmp = `${target}.${process.pid}.tmp`;
8881
8471
  try {
8882
- writeFileSync8(tmp, JSON.stringify(entry) + "\n", "utf8");
8883
- renameSync3(tmp, target2);
8472
+ writeFileSync7(tmp, JSON.stringify(entry) + "\n", "utf8");
8473
+ renameSync3(tmp, target);
8884
8474
  } catch (err) {
8885
8475
  try {
8886
- rmSync8(tmp, { force: true });
8476
+ rmSync7(tmp, { force: true });
8887
8477
  } catch {
8888
8478
  }
8889
8479
  this.log?.warn(
@@ -8900,7 +8490,7 @@ var Outbox = class {
8900
8490
  // wedging the drain.
8901
8491
  list() {
8902
8492
  const dir2 = this.dir();
8903
- if (!existsSync13(dir2)) return [];
8493
+ if (!existsSync11(dir2)) return [];
8904
8494
  let names;
8905
8495
  try {
8906
8496
  names = readdirSync3(dir2);
@@ -8910,9 +8500,9 @@ var Outbox = class {
8910
8500
  const entries = [];
8911
8501
  for (const name of names) {
8912
8502
  if (!name.endsWith(".json")) continue;
8913
- const full = join17(dir2, name);
8503
+ const full = join15(dir2, name);
8914
8504
  try {
8915
- const parsed = JSON.parse(readFileSync9(full, "utf8"));
8505
+ const parsed = JSON.parse(readFileSync8(full, "utf8"));
8916
8506
  if (parsed && typeof parsed.turnId === "string" && typeof parsed.seq === "number" && typeof parsed.path === "string") {
8917
8507
  entries.push(parsed);
8918
8508
  } else {
@@ -8930,13 +8520,13 @@ var Outbox = class {
8930
8520
  // Remove a delivered (or terminally-discarded) entry. No-op if already gone.
8931
8521
  remove(turnId, seq) {
8932
8522
  try {
8933
- rmSync8(this.fileFor(turnId, seq), { force: true });
8523
+ rmSync7(this.fileFor(turnId, seq), { force: true });
8934
8524
  } catch {
8935
8525
  }
8936
8526
  }
8937
8527
  size() {
8938
8528
  const dir2 = this.dir();
8939
- if (!existsSync13(dir2)) return 0;
8529
+ if (!existsSync11(dir2)) return 0;
8940
8530
  try {
8941
8531
  return readdirSync3(dir2).filter((n) => n.endsWith(".json")).length;
8942
8532
  } catch {
@@ -8949,7 +8539,7 @@ var Outbox = class {
8949
8539
  "companion outbox: dropping unreadable entry"
8950
8540
  );
8951
8541
  try {
8952
- rmSync8(full, { force: true });
8542
+ rmSync7(full, { force: true });
8953
8543
  } catch {
8954
8544
  }
8955
8545
  }
@@ -10111,22 +9701,22 @@ function handleUncaught(log, err, origin) {
10111
9701
  }
10112
9702
 
10113
9703
  // src/crash-marker.ts
10114
- import { existsSync as existsSync14, mkdirSync as mkdirSync13, readFileSync as readFileSync10, rmSync as rmSync9, writeFileSync as writeFileSync9 } from "fs";
10115
- import { join as join18 } from "path";
9704
+ import { existsSync as existsSync12, mkdirSync as mkdirSync12, readFileSync as readFileSync9, rmSync as rmSync8, writeFileSync as writeFileSync8 } from "fs";
9705
+ import { join as join16 } from "path";
10116
9706
  function crashMarkerPath() {
10117
- return join18(cabaneDir(), "last-error.json");
9707
+ return join16(cabaneDir(), "last-error.json");
10118
9708
  }
10119
9709
  function recordCrash(rec2) {
10120
9710
  try {
10121
- mkdirSync13(cabaneDir(), { recursive: true });
10122
- writeFileSync9(crashMarkerPath(), JSON.stringify(rec2, null, 2) + "\n");
9711
+ mkdirSync12(cabaneDir(), { recursive: true });
9712
+ writeFileSync8(crashMarkerPath(), JSON.stringify(rec2, null, 2) + "\n");
10123
9713
  } catch {
10124
9714
  }
10125
9715
  }
10126
9716
  function clearCrash() {
10127
9717
  try {
10128
9718
  const path = crashMarkerPath();
10129
- if (existsSync14(path)) rmSync9(path, { force: true });
9719
+ if (existsSync12(path)) rmSync8(path, { force: true });
10130
9720
  } catch {
10131
9721
  }
10132
9722
  }
@@ -10201,11 +9791,13 @@ async function createCompanionRuntime(opts = {}) {
10201
9791
  });
10202
9792
  await supervisor.start();
10203
9793
  const connectHarness = async (runtime, serverUrl) => {
9794
+ const candidate = runtime === "opencode" ? { ...supervisor.currentConfig(), opencode: { serverUrl: serverUrl ?? "" } } : supervisor.currentConfig();
9795
+ const verdict = await shakeOutHarness(runtime, candidate);
9796
+ if (verdict === "absent") return { ok: false, error: absentLine(runtime) };
10204
9797
  const result = await supervisor.enableHarness(
10205
9798
  runtime === "opencode" ? { runtime: "opencode", serverUrl: serverUrl ?? "" } : { runtime }
10206
9799
  );
10207
9800
  if (!result.ok) return { ok: false, error: result.error };
10208
- const verdict = await shakeOutHarness(runtime, supervisor.currentConfig());
10209
9801
  return { ok: true, message: connectedLine(runtime, verdict) };
10210
9802
  };
10211
9803
  const control = await startControlServer({
@@ -10276,7 +9868,24 @@ async function closeSurfaces(control, dashboard) {
10276
9868
 
10277
9869
  // src/commands/daemon.ts
10278
9870
  import { spawn as spawn5 } from "child_process";
10279
- import { closeSync as closeSync3, mkdirSync as mkdirSync14, openSync as openSync3 } from "fs";
9871
+ import { closeSync as closeSync3, mkdirSync as mkdirSync13, openSync as openSync3 } from "fs";
9872
+
9873
+ // src/cli-entry.ts
9874
+ import { existsSync as existsSync13 } from "fs";
9875
+ import { fileURLToPath as fileURLToPath2 } from "url";
9876
+ var RELATIVE_CANDIDATES = ["./cli.js", "../dist/cli.js", "../cli.js"];
9877
+ function companionCliEntry(deps = {}) {
9878
+ const exists = deps.exists ?? existsSync13;
9879
+ const candidates = deps.candidates ?? RELATIVE_CANDIDATES.map((rel) => fileURLToPath2(new URL(rel, import.meta.url)));
9880
+ for (const candidate of candidates) {
9881
+ if (exists(candidate)) return candidate;
9882
+ }
9883
+ const argv1 = "argv1" in deps ? deps.argv1 : process.argv[1];
9884
+ if (argv1 && exists(argv1)) return argv1;
9885
+ return candidates[0] ?? "";
9886
+ }
9887
+
9888
+ // src/commands/daemon.ts
10280
9889
  var STARTUP_TIMEOUT_MS = 8e3;
10281
9890
  var POLL_INTERVAL_MS = 150;
10282
9891
  async function startDaemon(opts = {}, deps = {}) {
@@ -10286,11 +9895,6 @@ async function startDaemon(opts = {}, deps = {}) {
10286
9895
  const spawnDetached = deps.spawnDetached ?? defaultSpawnDetached;
10287
9896
  const sleep4 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
10288
9897
  const now = deps.now ?? (() => Date.now());
10289
- const install = deps.installService ?? ((o) => installService(o));
10290
- const refresh = deps.refreshService ?? ((o) => refreshInstalledService(o));
10291
- const disable = deps.disableService ?? (() => disableService());
10292
- const isTty = deps.isTty ?? (() => Boolean(process.stdin.isTTY));
10293
- const serviceOpts = {};
10294
9898
  const quiet = opts.report === "failures";
10295
9899
  const { cfg, claudeOnPath: claudeOnPath2 } = await requireStartConfig({
10296
9900
  // Unset → `requireStartConfig`'s own `requireConfig`, the real one.
@@ -10318,22 +9922,16 @@ async function startDaemon(opts = {}, deps = {}) {
10318
9922
  const existing = readState();
10319
9923
  if (existing) {
10320
9924
  if (await verify(existing) !== "stale") {
10321
- const rerendered = isTty() ? refresh(serviceOpts) : false;
10322
9925
  process.stdout.write(
10323
9926
  `Cabane Companion is already running (pid ${existing.pid}).
10324
9927
  Stop it first with \`cabane-companion stop\` if you want to relaunch.
10325
9928
  Connect a harness to the running companion: cabane-companion connect claude-code
10326
- ` + (rerendered ? "Autostart: the login service was updated for this install \u2014 it takes effect at the next login, or now with `cabane-companion stop` then `start`.\n" : "")
9929
+ `
10327
9930
  );
10328
- return {
10329
- started: false,
10330
- service: { installed: false, reason: "unsupported" },
10331
- state: existing
10332
- };
9931
+ return { started: false, state: existing };
10333
9932
  }
10334
9933
  clearRuntimeState();
10335
9934
  }
10336
- let service2 = isTty() ? install(serviceOpts) : { installed: false, reason: "unsupported" };
10337
9935
  const args = ["start", "--foreground"];
10338
9936
  const launchDetached = () => {
10339
9937
  const spawned = spawnDetached(args);
@@ -10350,29 +9948,8 @@ Connect a harness to the running companion: cabane-companion connect claude-code
10350
9948
  }
10351
9949
  return ready(seen) ? seen : null;
10352
9950
  };
10353
- if (!service2.installed && service2.leftBehind) {
10354
- refuseDouble(service2.leftBehind, service2.detail);
10355
- process.exitCode = 1;
10356
- return { started: false, service: service2, state: null };
10357
- }
10358
- let child = service2.installed ? null : launchDetached();
10359
- let state = await waitForReady();
10360
- if (!state && service2.installed) {
10361
- process.stdout.write(
10362
- `${service2.manager} started the companion but it didn't report ready within ${STARTUP_TIMEOUT_MS / 1e3}s \u2014 removing the login service.
10363
- `
10364
- );
10365
- const removed = disable();
10366
- if (!removed.handled || !removed.ok) {
10367
- refuseDouble(service2.manager, removed.detail);
10368
- process.exitCode = 1;
10369
- return { started: false, service: service2, state: null };
10370
- }
10371
- process.stdout.write("Launching it directly instead.\n");
10372
- service2 = { installed: false, reason: "command-failed", detail: "the service never came up" };
10373
- child = launchDetached();
10374
- state = await waitForReady();
10375
- }
9951
+ const child = launchDetached();
9952
+ const state = await waitForReady();
10376
9953
  if (!state) {
10377
9954
  process.stdout.write(
10378
9955
  `Cabane Companion was launched (pid ${child?.pid ?? "?"}) but didn't report ready within ${STARTUP_TIMEOUT_MS / 1e3}s.
@@ -10380,9 +9957,9 @@ Check ${companionLogPath()} for errors, or run \`cabane-companion status\`.
10380
9957
  `
10381
9958
  );
10382
9959
  process.exitCode = 1;
10383
- return { started: false, service: service2, state: null };
9960
+ return { started: false, state: null };
10384
9961
  }
10385
- if (quiet) return { started: true, service: service2, state };
9962
+ if (quiet) return { started: true, state };
10386
9963
  process.stdout.write(
10387
9964
  `Cabane Companion started in the background (pid ${state.pid}).
10388
9965
  Logs: ${companionLogPath()}
@@ -10390,27 +9967,11 @@ Status: cabane-companion status
10390
9967
  Stop: cabane-companion stop
10391
9968
  `
10392
9969
  );
10393
- if (service2.installed) {
10394
- process.stdout.write(`Autostart: enabled (${service2.manager}) \u2014 it starts again at login.
10395
- `);
10396
- } else if (service2.reason !== "unsupported") {
10397
- process.stdout.write(
10398
- `Autostart: not enabled \u2014 ${service2.detail ?? "the service manager refused the install"}. Running detached instead; it won't come back after a reboot.
10399
- `
10400
- );
10401
- }
10402
- return { started: true, service: service2, state };
10403
- }
10404
- function refuseDouble(manager, detail) {
10405
- process.stdout.write(
10406
- `${manager} still has the login service${detail ? ` (${detail})` : ""} \u2014 not launching a second companion beside a service that may still own one.
10407
- Check \`cabane-companion service status\`, then \`cabane-companion service disable\`, and run \`cabane-companion start --daemon\` again.
10408
- `
10409
- );
9970
+ return { started: true, state };
10410
9971
  }
10411
9972
  function defaultSpawnDetached(args) {
10412
9973
  const cliPath = companionCliEntry();
10413
- mkdirSync14(cabaneDir(), { recursive: true });
9974
+ mkdirSync13(cabaneDir(), { recursive: true });
10414
9975
  const logFd = openSync3(companionLogPath(), "a");
10415
9976
  try {
10416
9977
  return spawn5(process.execPath, [cliPath, ...args], {
@@ -10430,6 +9991,15 @@ var DEPLOY_GRACE_MS = 6.5 * 60 * 60 * 1e3;
10430
9991
  var MAX_CODES = 3;
10431
9992
  async function start(opts = {}) {
10432
9993
  const interactive = isInteractive();
9994
+ if (!interactive) return startScript(opts, false);
9995
+ setConsoleLogging(false);
9996
+ try {
9997
+ await startScript(opts, true);
9998
+ } finally {
9999
+ setConsoleLogging(true);
10000
+ }
10001
+ }
10002
+ async function startScript(opts, interactive) {
10433
10003
  const running = await liveCompanion();
10434
10004
  if (running) {
10435
10005
  reportAlreadyRunning(running.pid);
@@ -10439,7 +10009,9 @@ async function start(opts = {}) {
10439
10009
  let paired = null;
10440
10010
  if (!isDevicePaired()) {
10441
10011
  paired = await pairHere(opts, interactive);
10442
- if (!paired) return;
10012
+ if (!paired) {
10013
+ return;
10014
+ }
10443
10015
  const { note } = writePairedConfig(paired);
10444
10016
  if (note) getLogger().warn({ note }, "companion: salvaged an older config on pairing");
10445
10017
  }
@@ -10470,6 +10042,7 @@ async function start(opts = {}) {
10470
10042
  write(` Stop: Ctrl-C Logs: ${tildePath(companionLogPath())}`);
10471
10043
  blank();
10472
10044
  write(`${INDENT}Listening for messages\u2026`);
10045
+ setConsoleLogging(true);
10473
10046
  } else {
10474
10047
  if (!offered.printedSomething) blank();
10475
10048
  write(
@@ -10619,11 +10192,7 @@ async function handOffToBackground(runtime, ctx) {
10619
10192
  return;
10620
10193
  }
10621
10194
  if (ctx.spaceAbove) blank();
10622
- write(
10623
- INDENT + tick(
10624
- outcome.service.installed ? "Cabane companion is running in the background, and will start again when you log in." : "Cabane companion is running in the background."
10625
- )
10626
- );
10195
+ write(INDENT + tick("Cabane companion is running in the background."));
10627
10196
  write(` Stop: cabane-companion stop Logs: ${tildePath(companionLogPath())}`);
10628
10197
  if (ctx.justPaired) {
10629
10198
  blank();
@@ -10687,8 +10256,7 @@ companion: deploy drain requested (${graceMs}ms grace)\u2026
10687
10256
 
10688
10257
  // src/commands/status.ts
10689
10258
  import { readdirSync as readdirSync4 } from "fs";
10690
- async function status(deps = {}) {
10691
- const readService = deps.service ?? serviceStatus;
10259
+ async function status() {
10692
10260
  const cfg = loadConfig();
10693
10261
  if (!cfg || !cfg.deviceToken) {
10694
10262
  process.stdout.write(
@@ -10724,13 +10292,6 @@ async function status(deps = {}) {
10724
10292
  "companion: not running \u2014 `cabane-companion start` (backgrounds itself; `--foreground` stays attached)\n"
10725
10293
  );
10726
10294
  }
10727
- const svc = readService();
10728
- if (svc.installed) {
10729
- process.stdout.write(
10730
- `autostart: ${svc.manager} \u2014 starts at login` + (svc.manager === "systemd-user" && svc.linger !== "yes" ? ", while you are logged in" : "") + ` (details: cabane-companion service status)
10731
- `
10732
- );
10733
- }
10734
10295
  process.stdout.write(
10735
10296
  "agents: pulled from cabane at runtime \u2014 see this device in Cabane for its assigned agents, their run state, and any missing-secret warnings.\n"
10736
10297
  );
@@ -10777,34 +10338,12 @@ function formatUptime(startedAt) {
10777
10338
  // src/commands/stop.ts
10778
10339
  var TERM_GRACE_MS = 6e3;
10779
10340
  var POLL_INTERVAL_MS2 = 150;
10780
- var SERVICE_STOP_GRACE_MS = 3e3;
10781
10341
  async function stop(deps = {}) {
10782
10342
  const readState = deps.readState ?? readLiveRuntimeState;
10783
10343
  const verify = deps.verify ?? ((s) => verifyRuntime(s));
10784
10344
  const kill = deps.kill ?? ((pid2, signal) => process.kill(pid2, signal));
10785
10345
  const sleep4 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
10786
10346
  const now = deps.now ?? (() => Date.now());
10787
- const stopSvc = deps.stopService ?? (() => stopService());
10788
- const svc = stopSvc();
10789
- if (svc.handled) {
10790
- if (!svc.ok) {
10791
- process.stdout.write(
10792
- `companion: ${svc.manager} wouldn't stop the service${svc.detail ? ` (${svc.detail})` : ""}.
10793
- The service manager owns this process \u2014 stopping it directly would just be restarted.
10794
- Check \`cabane-companion service status\`, or remove it with \`cabane-companion service disable\`.
10795
- `
10796
- );
10797
- process.exitCode = 1;
10798
- return;
10799
- }
10800
- const deadline2 = now() + SERVICE_STOP_GRACE_MS;
10801
- while (readState() && now() < deadline2) await sleep4(POLL_INTERVAL_MS2);
10802
- if (!readState()) {
10803
- process.stdout.write(`companion: stopped (${svc.manager} service stopped).
10804
- `);
10805
- return;
10806
- }
10807
- }
10808
10347
  const state = readState();
10809
10348
  if (!state) {
10810
10349
  process.stdout.write("companion: not running (nothing to stop).\n");
@@ -10854,8 +10393,8 @@ function isAlive(kill, pid) {
10854
10393
  }
10855
10394
 
10856
10395
  // src/commands/transcript.ts
10857
- import { existsSync as existsSync15, readFileSync as readFileSync11, readdirSync as readdirSync5 } from "fs";
10858
- import { isAbsolute, join as join19 } from "path";
10396
+ import { existsSync as existsSync14, readFileSync as readFileSync10, readdirSync as readdirSync5 } from "fs";
10397
+ import { isAbsolute, join as join17 } from "path";
10859
10398
  async function transcript(opts = {}) {
10860
10399
  const dir2 = transcriptsDir();
10861
10400
  if (opts.follow) {
@@ -10872,7 +10411,7 @@ async function transcript(opts = {}) {
10872
10411
  process.stdout.write(emptyMessage(dir2));
10873
10412
  return;
10874
10413
  }
10875
- process.stdout.write(renderFile(join19(dir2, newest)) + "\n");
10414
+ process.stdout.write(renderFile(join17(dir2, newest)) + "\n");
10876
10415
  return;
10877
10416
  }
10878
10417
  printList(dir2);
@@ -10955,7 +10494,7 @@ function isComplete(content) {
10955
10494
  async function followTranscripts(dir2) {
10956
10495
  const follower = new TranscriptFollower({
10957
10496
  listFiles: () => listFiles(dir2),
10958
- read: (f) => readFileSync11(join19(dir2, f), "utf8"),
10497
+ read: (f) => readFileSync10(join17(dir2, f), "utf8"),
10959
10498
  write: (s) => process.stdout.write(s),
10960
10499
  // CSI: cursor up `n` lines, then erase from cursor to end of screen.
10961
10500
  clearLines: (n) => process.stdout.write(`\x1B[${n}A\x1B[0J`),
@@ -10995,7 +10534,7 @@ function printList(dir2) {
10995
10534
 
10996
10535
  `);
10997
10536
  for (const f of files.slice(0, 20)) {
10998
- const { meta, outcome } = peek(join19(dir2, f));
10537
+ const { meta, outcome } = peek(join17(dir2, f));
10999
10538
  const when = fmtTime(rec(meta)?.ts);
11000
10539
  const ws = str2(rec(meta)?.workspaceSlug);
11001
10540
  const o = rec(outcome);
@@ -11016,7 +10555,7 @@ function peek(path) {
11016
10555
  let meta;
11017
10556
  let outcome;
11018
10557
  try {
11019
- for (const line of readFileSync11(path, "utf8").split("\n")) {
10558
+ for (const line of readFileSync10(path, "utf8").split("\n")) {
11020
10559
  if (!line.trim()) continue;
11021
10560
  const o = safeParse(line);
11022
10561
  const t = str2(rec(o)?.type);
@@ -11027,29 +10566,29 @@ function peek(path) {
11027
10566
  }
11028
10567
  return { meta, outcome };
11029
10568
  }
11030
- function resolveTarget(dir2, target2) {
11031
- if (isAbsolute(target2) || target2.includes("/")) {
11032
- if (existsSync15(target2)) return target2;
11033
- throw new CompanionError(`no transcript at ${target2}.`);
10569
+ function resolveTarget(dir2, target) {
10570
+ if (isAbsolute(target) || target.includes("/")) {
10571
+ if (existsSync14(target)) return target;
10572
+ throw new CompanionError(`no transcript at ${target}.`);
11034
10573
  }
11035
- const exact = join19(dir2, target2);
11036
- if (existsSync15(exact)) return exact;
11037
- const matches = listFiles(dir2).filter((f) => f.includes(target2));
11038
- if (matches.length === 1) return join19(dir2, matches[0]);
10574
+ const exact = join17(dir2, target);
10575
+ if (existsSync14(exact)) return exact;
10576
+ const matches = listFiles(dir2).filter((f) => f.includes(target));
10577
+ if (matches.length === 1) return join17(dir2, matches[0]);
11039
10578
  if (matches.length === 0) {
11040
10579
  throw new CompanionError(
11041
- `no transcript matching "${target2}" in ${dir2}. Run \`cabane-companion transcript\` to list them.`
10580
+ `no transcript matching "${target}" in ${dir2}. Run \`cabane-companion transcript\` to list them.`
11042
10581
  );
11043
10582
  }
11044
10583
  throw new CompanionError(
11045
- `"${target2}" matches ${matches.length} transcripts \u2014 be more specific:
10584
+ `"${target}" matches ${matches.length} transcripts \u2014 be more specific:
11046
10585
  ` + matches.slice(0, 10).map((m) => ` ${m}`).join("\n")
11047
10586
  );
11048
10587
  }
11049
10588
  function renderFile(path) {
11050
10589
  let content;
11051
10590
  try {
11052
- content = readFileSync11(path, "utf8");
10591
+ content = readFileSync10(path, "utf8");
11053
10592
  } catch (err) {
11054
10593
  throw new CompanionError(
11055
10594
  `couldn't read ${path}: ${err instanceof Error ? err.message : String(err)}`
@@ -11186,11 +10725,11 @@ program.command("pair").description(
11186
10725
  });
11187
10726
  });
11188
10727
  program.command("write-paired-config", { hidden: true }).description("persist an already-completed device enrollment payload from stdin.").action(() => {
11189
- writeCompletedPairing(readFileSync12(0, "utf8"));
10728
+ writeCompletedPairing(readFileSync11(0, "utf8"));
11190
10729
  });
11191
10730
  program.command("start").description(
11192
10731
  "pair this device if needed, connect a coding agent on it, and run in the background."
11193
- ).option("--foreground", "run attached in this terminal (never installs the login service)").option("--daemon", "run in the background (the default; kept for compatibility)").option("--server <url>", "the cabane instance to pair with, if this device isn\u2019t paired yet").addOption(new Option("--open").hideHelp()).addOption(new Option("--no-open").hideHelp()).addOption(new Option("--port <port>").hideHelp()).action(async (opts) => {
10732
+ ).option("--foreground", "run attached in this terminal (for terminals and supervisors)").option("--daemon", "run in the background (the default; kept for compatibility)").option("--server <url>", "the cabane instance to pair with, if this device isn\u2019t paired yet").addOption(new Option("--open").hideHelp()).addOption(new Option("--no-open").hideHelp()).addOption(new Option("--port <port>").hideHelp()).action(async (opts) => {
11194
10733
  await start({
11195
10734
  ...opts.foreground ? { foreground: true } : {},
11196
10735
  ...opts.server !== void 0 ? { server: opts.server } : {}
@@ -11205,13 +10744,6 @@ program.command("stop").description("stop a running companion (SIGTERM, then for
11205
10744
  program.command("status").description("print the companion's local state (pairing, secrets, log path).").action(async () => {
11206
10745
  await status();
11207
10746
  });
11208
- var service = program.command("service").description("inspect or remove the login service that starts the companion when you log in.");
11209
- service.command("status").description("print the service manager, whether the unit is installed and running.").action(() => {
11210
- serviceStatusCommand();
11211
- });
11212
- service.command("disable").description("stop the companion and remove the login service, leaving the machine clean.").action(() => {
11213
- serviceDisableCommand();
11214
- });
11215
10747
  program.command("transcript").description("show the full agent transcript for a recent dispatch (the agent's whole turn).").argument("[file]", "a transcript filename or substring; omit to list recent transcripts").option("--last", "render the most recent transcript").option("-f, --follow", "watch for new turns and live-render them as they land (Ctrl-C to stop)").action(async (file, opts) => {
11216
10748
  await transcript({
11217
10749
  ...file !== void 0 ? { target: file } : {},
@@ -11242,7 +10774,12 @@ program.parseAsync(process.argv).catch((err) => {
11242
10774
  process.exitCode = 130;
11243
10775
  return;
11244
10776
  }
11245
- process.stderr.write(`${err instanceof Error ? err.stack ?? err.message : String(err)}
10777
+ setConsoleLogging(false);
10778
+ try {
10779
+ getLogger().error({ err }, "companion: command failed");
10780
+ } catch {
10781
+ }
10782
+ process.stderr.write(`${err instanceof Error ? err.message : String(err)}
11246
10783
  `);
11247
10784
  process.exitCode = 1;
11248
10785
  });