@cabane/companion 0.6.35 → 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.
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
 
@@ -1663,30 +1667,10 @@ async function pair(opts = {}) {
1663
1667
  }
1664
1668
 
1665
1669
  // src/cli.ts
1666
- import { readFileSync as readFileSync12 } from "fs";
1667
-
1668
- // src/service/index.ts
1669
- import { dirname as dirname5 } from "path";
1670
-
1671
- // src/service/host.ts
1672
- import { spawnSync } from "child_process";
1673
- import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync4, rmSync as rmSync5, writeFileSync as writeFileSync4 } from "fs";
1674
- import { homedir as homedir3 } from "os";
1670
+ import { readFileSync as readFileSync11 } from "fs";
1675
1671
 
1676
- // src/cli-entry.ts
1677
- import { existsSync as existsSync5 } from "fs";
1678
- import { fileURLToPath } from "url";
1679
- var RELATIVE_CANDIDATES = ["./cli.js", "../dist/cli.js", "../cli.js"];
1680
- function companionCliEntry(deps = {}) {
1681
- const exists = deps.exists ?? existsSync5;
1682
- const candidates = deps.candidates ?? RELATIVE_CANDIDATES.map((rel) => fileURLToPath(new URL(rel, import.meta.url)));
1683
- for (const candidate of candidates) {
1684
- if (exists(candidate)) return candidate;
1685
- }
1686
- const argv1 = "argv1" in deps ? deps.argv1 : process.argv[1];
1687
- if (argv1 && exists(argv1)) return argv1;
1688
- return candidates[0] ?? "";
1689
- }
1672
+ // src/commands/start.ts
1673
+ import { hostname as hostname2 } from "os";
1690
1674
 
1691
1675
  // src/logger.ts
1692
1676
  import { createWriteStream, mkdirSync as mkdirSync5 } from "fs";
@@ -1752,455 +1736,19 @@ function getLogger() {
1752
1736
  return cached;
1753
1737
  }
1754
1738
 
1755
- // src/service/host.ts
1756
- var RUN_TIMEOUT_MS = 1e4;
1757
- function defaultServiceHost() {
1758
- if (process.env.VITEST) {
1759
- throw new Error(
1760
- "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."
1761
- );
1762
- }
1763
- return {
1764
- platform: process.platform,
1765
- env: process.env,
1766
- home: homedir3(),
1767
- uid: process.getuid?.() ?? 0,
1768
- execPath: process.execPath,
1769
- cliPath: companionCliEntry(),
1770
- logPath: companionLogPath(),
1771
- fs: {
1772
- read: (path) => {
1773
- try {
1774
- return readFileSync4(path, "utf8");
1775
- } catch {
1776
- return null;
1777
- }
1778
- },
1779
- write: (path, contents) => writeFileSync4(path, contents, "utf8"),
1780
- remove: (path) => rmSync5(path, { force: true }),
1781
- exists: (path) => existsSync6(path),
1782
- mkdirp: (dir2) => {
1783
- mkdirSync6(dir2, { recursive: true });
1784
- }
1785
- },
1786
- run: (cmd, args) => {
1787
- const res = spawnSync(cmd, args, { encoding: "utf8", timeout: RUN_TIMEOUT_MS });
1788
- return {
1789
- ok: res.status === 0,
1790
- stdout: res.stdout ?? "",
1791
- stderr: res.stderr ?? (res.error ? res.error.message : "")
1792
- };
1793
- }
1794
- };
1795
- }
1796
-
1797
- // src/service/launchd.ts
1798
- import { join as join6 } from "path";
1799
- var LAUNCHD_LABEL = "ai.cabane.companion";
1800
- function launchAgentPath(home) {
1801
- return join6(home, "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
1802
- }
1803
- function renderPlist(input) {
1804
- const args = input.programArguments.map((a) => ` <string>${xml(a)}</string>`).join("\n");
1805
- const env = Object.entries(input.environment).map(([k, v]) => ` <key>${xml(k)}</key>
1806
- <string>${xml(v)}</string>`).join("\n");
1807
- return [
1808
- '<?xml version="1.0" encoding="UTF-8"?>',
1809
- '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
1810
- '<plist version="1.0">',
1811
- "<dict>",
1812
- " <key>Label</key>",
1813
- ` <string>${LAUNCHD_LABEL}</string>`,
1814
- " <key>ProgramArguments</key>",
1815
- " <array>",
1816
- args,
1817
- " </array>",
1818
- " <key>RunAtLoad</key>",
1819
- " <true/>",
1820
- " <key>KeepAlive</key>",
1821
- " <dict>",
1822
- " <key>SuccessfulExit</key>",
1823
- " <false/>",
1824
- " </dict>",
1825
- " <key>EnvironmentVariables</key>",
1826
- " <dict>",
1827
- env,
1828
- " </dict>",
1829
- " <key>StandardOutPath</key>",
1830
- ` <string>${xml(input.logPath)}</string>`,
1831
- " <key>StandardErrorPath</key>",
1832
- ` <string>${xml(input.logPath)}</string>`,
1833
- "</dict>",
1834
- "</plist>",
1835
- ""
1836
- ].join("\n");
1837
- }
1838
- function domain(host) {
1839
- return `gui/${host.uid}`;
1840
- }
1841
- function target(host) {
1842
- return `${domain(host)}/${LAUNCHD_LABEL}`;
1843
- }
1844
- function launchdStart(host, plistPath) {
1845
- host.run("launchctl", ["bootout", target(host)]);
1846
- const res = host.run("launchctl", ["bootstrap", domain(host), plistPath]);
1847
- return res.ok ? { ok: true } : { ok: false, detail: firstLine(res.stderr || res.stdout) };
1848
- }
1849
- function launchdStop(host) {
1850
- const res = host.run("launchctl", ["bootout", target(host)]);
1851
- if (res.ok || notLoaded(res.stderr + res.stdout)) return { ok: true };
1852
- return { ok: false, detail: firstLine(res.stderr || res.stdout) };
1853
- }
1854
- function launchdProbe(host) {
1855
- const res = host.run("launchctl", ["print", target(host)]);
1856
- if (res.ok) {
1857
- const state = /state\s*=\s*(\w+)/.exec(res.stdout)?.[1];
1858
- return { ownership: "held", running: state ? state === "running" : "unknown" };
1859
- }
1860
- return notLoaded(res.stderr + res.stdout) ? { ownership: "clear", running: false } : { ownership: "unknown", running: "unknown" };
1861
- }
1862
- function launchdRemove(host, plistPath) {
1863
- const stopped = launchdStop(host);
1864
- if (!stopped.ok) return stopped;
1865
- host.fs.remove(plistPath);
1866
- return { ok: true };
1867
- }
1868
- var NOT_LOADED = new RegExp(
1869
- `no such process|(could not find|not find service)[^\\n]*${LAUNCHD_LABEL.replace(
1870
- /[.*+?^${}()|[\]\\]/g,
1871
- "\\$&"
1872
- )}`,
1873
- "i"
1874
- );
1875
- function notLoaded(output) {
1876
- return NOT_LOADED.test(output);
1877
- }
1878
- function firstLine(s) {
1879
- return s.trim().split("\n")[0] ?? "";
1880
- }
1881
- function xml(value) {
1882
- return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1883
- }
1884
-
1885
- // src/service/systemd.ts
1886
- import { dirname as dirname4, join as join7 } from "path";
1887
- var SYSTEMD_UNIT = "cabane-companion.service";
1888
- function systemdUnitPath(host) {
1889
- const configHome = host.env.XDG_CONFIG_HOME?.trim() ? host.env.XDG_CONFIG_HOME.trim() : join7(host.home, ".config");
1890
- return join7(configHome, "systemd", "user", SYSTEMD_UNIT);
1891
- }
1892
- function renderUnit(input) {
1893
- const [exec, ...rest] = input.programArguments;
1894
- const execStart = [quote(exec ?? ""), ...rest.map(quote)].join(" ");
1895
- const env = Object.entries(input.environment).map(([k, v]) => `Environment=${k}=${v}`);
1896
- return [
1897
- "[Unit]",
1898
- "Description=Cabane Companion \u2014 keeps this device answering while you are logged in",
1899
- "After=network-online.target",
1900
- "Wants=network-online.target",
1901
- "",
1902
- "[Service]",
1903
- "Type=simple",
1904
- ...env,
1905
- `ExecStart=${execStart}`,
1906
- "Restart=on-failure",
1907
- "RestartSec=5",
1908
- "",
1909
- "[Install]",
1910
- "WantedBy=default.target"
1911
- ].join("\n") + "\n";
1912
- }
1913
- function systemdReload(host) {
1914
- host.run("systemctl", ["--user", "daemon-reload"]);
1915
- }
1916
- function systemdStart(host) {
1917
- systemdReload(host);
1918
- const enabled = host.run("systemctl", ["--user", "enable", SYSTEMD_UNIT]);
1919
- if (!enabled.ok) return { ok: false, detail: firstLine2(enabled.stderr || enabled.stdout) };
1920
- const started = host.run("systemctl", ["--user", "restart", SYSTEMD_UNIT]);
1921
- if (!started.ok) return { ok: false, detail: firstLine2(started.stderr || started.stdout) };
1922
- return { ok: true };
1923
- }
1924
- function systemdStop(host) {
1925
- const res = host.run("systemctl", ["--user", "stop", SYSTEMD_UNIT]);
1926
- if (res.ok || notLoaded2(res.stderr + res.stdout)) return { ok: true };
1927
- return { ok: false, detail: firstLine2(res.stderr || res.stdout) };
1928
- }
1929
- function systemdProbe(host) {
1930
- const state = host.run("systemctl", ["--user", "is-active", SYSTEMD_UNIT]).stdout.trim();
1931
- if (state === "active") return { ownership: "held", running: true };
1932
- if (state === "activating" || state === "reloading" || state === "deactivating") {
1933
- return { ownership: "held", running: "transitional" };
1934
- }
1935
- if (state === "inactive" || state === "failed") return { ownership: "clear", running: false };
1936
- return { ownership: "unknown", running: "unknown" };
1937
- }
1938
- function systemdWantsLinkPath(host) {
1939
- return join7(dirname4(systemdUnitPath(host)), "default.target.wants", SYSTEMD_UNIT);
1940
- }
1941
- function systemdInstalled(host) {
1942
- return host.fs.exists(systemdUnitPath(host)) || host.fs.exists(systemdWantsLinkPath(host));
1943
- }
1944
- function systemdRemove(host, unitPath) {
1945
- const stopped = host.run("systemctl", ["--user", "disable", "--now", SYSTEMD_UNIT]);
1946
- if (!(stopped.ok || notLoaded2(stopped.stderr + stopped.stdout))) {
1947
- return { ok: false, detail: firstLine2(stopped.stderr || stopped.stdout) };
1948
- }
1949
- host.fs.remove(unitPath);
1950
- const link = systemdWantsLinkPath(host);
1951
- if (host.fs.exists(link)) host.fs.remove(link);
1952
- systemdReload(host);
1953
- return { ok: true };
1954
- }
1955
- var NOT_LOADED2 = new RegExp(
1956
- `unit (file )?${SYSTEMD_UNIT.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")} (not loaded|does not exist|not found|could not be found)`,
1957
- "i"
1958
- );
1959
- function notLoaded2(output) {
1960
- return NOT_LOADED2.test(output);
1961
- }
1962
- function readLinger(host) {
1963
- const res = host.run("loginctl", ["show-user", String(host.uid), "-p", "Linger"]);
1964
- if (!res.ok) return "unknown";
1965
- const match = /Linger=(\w+)/.exec(res.stdout);
1966
- if (!match) return "unknown";
1967
- return match[1] === "yes" ? "yes" : "no";
1968
- }
1969
- function tryEnableLinger(host) {
1970
- host.run("loginctl", ["enable-linger", String(host.uid)]);
1971
- return readLinger(host);
1972
- }
1973
- function isRemoteSession(env) {
1974
- return Boolean(env.SSH_CONNECTION || env.SSH_TTY || env.SSH_CLIENT);
1975
- }
1976
- function quote(value) {
1977
- return `"${value.replace(/(["\\])/g, "\\$1")}"`;
1978
- }
1979
- function firstLine2(s) {
1980
- return s.trim().split("\n")[0] ?? "";
1981
- }
1982
-
1983
- // src/service/index.ts
1984
- function detectServiceManager(host = defaultServiceHost()) {
1985
- if (host.platform === "darwin") return "launchd";
1986
- if (host.platform !== "linux") return "none";
1987
- return host.run("systemctl", ["--user", "show-environment"]).ok ? "systemd-user" : "none";
1988
- }
1989
- function programArguments(host, _opts) {
1990
- return [host.execPath, host.cliPath, "start", "--foreground"];
1991
- }
1992
- function environment(host) {
1993
- return {
1994
- // PATH CAPTURE. launchd's default PATH is `/usr/bin:/bin:/usr/sbin:/sbin`
1995
- // and a systemd user manager's is barely better — neither has `claude`,
1996
- // `codex` or `opencode` on it. A companion that runs but can't find its
1997
- // harness is worse than one that isn't running, because the device reads
1998
- // Online. So we bake in the PATH of the shell that ran `start`, and because
1999
- // it's part of the rendered content, a PATH that later drifts re-renders on
2000
- // the next `start` like any other change.
2001
- PATH: host.env.PATH ?? "",
2002
- CABANE_COMPANION_DAEMON: "1"
2003
- };
2004
- }
2005
- function unitPathFor(host, manager) {
2006
- if (manager === "launchd") return launchAgentPath(host.home);
2007
- if (manager === "systemd-user") return systemdUnitPath(host);
2008
- return null;
2009
- }
2010
- function renderFor(host, manager, opts) {
2011
- const input = { programArguments: programArguments(host, opts), environment: environment(host) };
2012
- return manager === "launchd" ? renderPlist({ ...input, logPath: host.logPath }) : renderUnit(input);
2013
- }
2014
- function installService(opts = {}, host = defaultServiceHost()) {
2015
- const manager = detectServiceManager(host);
2016
- if (manager === "none") return { installed: false, reason: "unsupported" };
2017
- const path = unitPathFor(host, manager);
2018
- if (!path) return { installed: false, reason: "unsupported" };
2019
- const desired = renderFor(host, manager, opts);
2020
- const existing = host.fs.read(path);
2021
- const hadUnit = existing !== null;
2022
- const changed = existing !== desired;
2023
- if (changed) {
2024
- host.fs.mkdirp(dirname5(path));
2025
- host.fs.write(path, desired);
2026
- }
2027
- if (manager === "systemd-user") {
2028
- const linger = ensureLinger(host);
2029
- if (linger !== "yes" && isRemoteSession(host.env)) {
2030
- systemdRemove(host, path);
2031
- return failedInstall(
2032
- host,
2033
- manager,
2034
- "linger-unavailable",
2035
- "a systemd --user service would stop when this SSH session ends (lingering is off and could not be enabled)"
2036
- );
2037
- }
2038
- }
2039
- const started = manager === "launchd" ? launchdStart(host, path) : systemdStart(host);
2040
- if (!started.ok) {
2041
- if (!hadUnit) removeService(host, manager, path);
2042
- return failedInstall(host, manager, "command-failed", started.detail);
2043
- }
2044
- return { installed: true, manager, changed };
2045
- }
2046
- function failedInstall(host, manager, reason, detail) {
2047
- return {
2048
- installed: false,
2049
- reason,
2050
- ...detail ? { detail } : {},
2051
- ...managerOwnership(host, manager) === "clear" ? {} : { leftBehind: manager }
2052
- };
2053
- }
2054
- function managerProbe(host, manager) {
2055
- if (manager === "launchd") return launchdProbe(host);
2056
- if (manager === "systemd-user") return systemdProbe(host);
2057
- return { ownership: "clear", running: false };
2058
- }
2059
- function managerOwnership(host, manager) {
2060
- return managerProbe(host, manager).ownership;
2061
- }
2062
- function refreshInstalledService(opts = {}, host = defaultServiceHost()) {
2063
- const manager = detectServiceManager(host);
2064
- const path = unitPathFor(host, manager);
2065
- if (!path || !host.fs.exists(path)) return false;
2066
- const desired = renderFor(host, manager, opts);
2067
- if (host.fs.read(path) === desired) return false;
2068
- host.fs.write(path, desired);
2069
- if (manager === "systemd-user") systemdReload(host);
2070
- return true;
2071
- }
2072
- function stopService(host = defaultServiceHost()) {
2073
- const manager = detectServiceManager(host);
2074
- const path = unitPathFor(host, manager);
2075
- if (manager === "none" || !path || !managerHasClaim(host, manager, path)) {
2076
- return { handled: false, ok: true };
2077
- }
2078
- const res = manager === "launchd" ? launchdStop(host) : systemdStop(host);
2079
- return {
2080
- handled: true,
2081
- ok: res.ok,
2082
- manager,
2083
- ...res.detail ? { detail: res.detail } : {}
2084
- };
2085
- }
2086
- function disableService(host = defaultServiceHost()) {
2087
- const manager = detectServiceManager(host);
2088
- const path = unitPathFor(host, manager);
2089
- if (manager === "none" || !path) return { handled: false, ok: true };
2090
- if (!managerHasClaim(host, manager, path)) return { handled: false, ok: true, manager };
2091
- const res = removeService(host, manager, path);
2092
- return { handled: true, ok: res.ok, manager, ...res.detail ? { detail: res.detail } : {} };
2093
- }
2094
- function managerHasClaim(host, manager, path) {
2095
- if (manager === "none") return false;
2096
- if (host.fs.exists(path)) return true;
2097
- if (manager === "systemd-user" && systemdInstalled(host)) return true;
2098
- return managerOwnership(host, manager) !== "clear";
2099
- }
2100
- function removeService(host, manager, path) {
2101
- return manager === "launchd" ? launchdRemove(host, path) : systemdRemove(host, path);
2102
- }
2103
- function serviceStatus(host = defaultServiceHost()) {
2104
- const manager = detectServiceManager(host);
2105
- const path = unitPathFor(host, manager);
2106
- const probe = managerProbe(host, manager);
2107
- const installed = manager === "none" || !path ? false : host.fs.exists(path) || probe.ownership === "held" || manager === "systemd-user" && systemdInstalled(host);
2108
- const running = manager === "none" ? false : probe.running;
2109
- return {
2110
- manager,
2111
- unitPath: path,
2112
- installed,
2113
- running,
2114
- linger: manager === "systemd-user" ? readLinger(host) : null,
2115
- remoteSession: isRemoteSession(host.env)
2116
- };
2117
- }
2118
- function ensureLinger(host) {
2119
- const current = readLinger(host);
2120
- if (current === "yes") return current;
2121
- return tryEnableLinger(host);
2122
- }
2123
-
2124
- // src/commands/service.ts
2125
- function serviceStatusCommand(deps = {}) {
2126
- const status2 = (deps.status ?? serviceStatus)();
2127
- if (status2.manager === "none") {
2128
- process.stdout.write(
2129
- "service: not supported on this machine yet \u2014 `cabane-companion start` runs the companion detached instead (it stops at the next reboot).\n"
2130
- );
2131
- return;
2132
- }
2133
- process.stdout.write(`manager: ${status2.manager}
2134
- `);
2135
- process.stdout.write(
2136
- `unit: ${status2.unitPath}${status2.installed ? "" : " (not written)"}
2137
- `
2138
- );
2139
- process.stdout.write(`installed: ${status2.installed ? "yes" : "no"}
2140
- `);
2141
- process.stdout.write(`running: ${runningLine(status2.running)}
2142
- `);
2143
- if (status2.manager === "systemd-user") {
2144
- process.stdout.write(`linger: ${status2.linger}${lingerNote(status2)}
2145
- `);
2146
- }
2147
- if (status2.installed) {
2148
- process.stdout.write("disable: cabane-companion service disable\n");
2149
- } else {
2150
- process.stdout.write("install: cabane-companion start (installs it for you)\n");
2151
- }
2152
- }
2153
- function serviceDisableCommand(deps = {}) {
2154
- const disable = deps.disable ?? disableService;
2155
- const res = disable();
2156
- if (!res.handled) {
2157
- process.stdout.write("service: nothing installed (nothing to remove).\n");
2158
- return;
2159
- }
2160
- if (!res.ok) {
2161
- process.stdout.write(
2162
- `service: nothing was removed \u2014 ${res.detail ?? "the service manager reported an error"}.
2163
- The unit is still installed and may still be running; \`cabane-companion service status\` shows where it stands.
2164
- `
2165
- );
2166
- process.exitCode = 1;
2167
- return;
2168
- }
2169
- process.stdout.write(
2170
- "service: stopped and removed. The companion no longer starts at login; `cabane-companion start` sets it up again.\n"
2171
- );
2172
- }
2173
- function runningLine(running) {
2174
- if (running === "transitional") {
2175
- return "starting or stopping \u2014 the manager reported a state in transition; ask again in a moment";
2176
- }
2177
- if (running === "unknown") return "unknown \u2014 the service manager didn't answer";
2178
- return running ? "yes" : "no";
2179
- }
2180
- function lingerNote(status2) {
2181
- if (status2.linger === "yes") return " (survives logout)";
2182
- if (status2.remoteSession) {
2183
- return " \u2014 without lingering a --user service stops when this SSH session ends";
2184
- }
2185
- return " \u2014 the service runs while you are logged in";
2186
- }
2187
-
2188
- // src/commands/start.ts
2189
- import { hostname as hostname2 } from "os";
2190
-
2191
1739
  // src/runtime.ts
2192
1740
  import { randomUUID as randomUUID2 } from "crypto";
2193
1741
 
2194
1742
  // src/dashboard/server.ts
2195
- import { dirname as dirname6, join as join9 } from "path";
2196
- import { fileURLToPath as fileURLToPath2 } from "url";
1743
+ import { dirname as dirname4, join as join7 } from "path";
1744
+ import { fileURLToPath } from "url";
2197
1745
  import { serve } from "@hono/node-server";
2198
1746
  import { Hono } from "hono";
2199
1747
 
2200
1748
  // src/dashboard/routes.ts
2201
- 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";
2202
1750
  import { readFile } from "fs/promises";
2203
- import { extname, join as join8, normalize } from "path";
1751
+ import { extname, join as join6, normalize } from "path";
2204
1752
  import { streamSSE } from "hono/streaming";
2205
1753
 
2206
1754
  // src/state.ts
@@ -2449,14 +1997,14 @@ var CONTENT_TYPES = {
2449
1997
  function registerRoutes(app, deps) {
2450
1998
  const { supervisor, hub, staticDir } = deps;
2451
1999
  app.get("/", async (c) => {
2452
- const html = await readFile(join8(staticDir, "index.html"), "utf8");
2000
+ const html = await readFile(join6(staticDir, "index.html"), "utf8");
2453
2001
  return c.html(html);
2454
2002
  });
2455
2003
  app.get("/static/:file", async (c) => {
2456
2004
  const file = c.req.param("file");
2457
2005
  const safe3 = normalize(file).replace(/^(\.\.[/\\])+/, "");
2458
- const full = join8(staticDir, safe3);
2459
- 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();
2460
2008
  const body = await readFile(full);
2461
2009
  const type = CONTENT_TYPES[extname(full).toLowerCase()] ?? "application/octet-stream";
2462
2010
  c.header("content-type", type);
@@ -2583,7 +2131,7 @@ function clampLimit(raw, fallback, max = 200) {
2583
2131
  return Math.min(Math.floor(n), max);
2584
2132
  }
2585
2133
  function tailFile(path, lines) {
2586
- if (!existsSync7(path)) return [];
2134
+ if (!existsSync5(path)) return [];
2587
2135
  const MAX_BYTES = 256 * 1024;
2588
2136
  let fd;
2589
2137
  try {
@@ -2672,7 +2220,7 @@ function isAddrInUse(err) {
2672
2220
  return Boolean(err && typeof err === "object" && "code" in err && err.code === "EADDRINUSE");
2673
2221
  }
2674
2222
  function resolveStaticDir() {
2675
- return join9(dirname6(fileURLToPath2(import.meta.url)), "static");
2223
+ return join7(dirname4(fileURLToPath(import.meta.url)), "static");
2676
2224
  }
2677
2225
 
2678
2226
  // src/api.ts
@@ -3148,21 +2696,21 @@ function errorMessage2(status2, body) {
3148
2696
  }
3149
2697
 
3150
2698
  // src/cursor.ts
3151
- import { mkdirSync as mkdirSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync5, existsSync as existsSync8 } from "fs";
3152
- 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";
3153
2701
  function pathFor(workspaceId) {
3154
- return join10(cabaneDir(), "cursors", encodeURIComponent(workspaceId));
2702
+ return join8(cabaneDir(), "cursors", encodeURIComponent(workspaceId));
3155
2703
  }
3156
2704
  function readCursor(workspaceId) {
3157
2705
  const path = pathFor(workspaceId);
3158
- if (!existsSync8(path)) return null;
3159
- const raw = readFileSync5(path, "utf8").trim();
2706
+ if (!existsSync6(path)) return null;
2707
+ const raw = readFileSync4(path, "utf8").trim();
3160
2708
  return raw.length > 0 ? raw : null;
3161
2709
  }
3162
2710
  function writeCursor(workspaceId, eventId) {
3163
2711
  const path = pathFor(workspaceId);
3164
- mkdirSync7(join10(cabaneDir(), "cursors"), { recursive: true });
3165
- writeFileSync5(path, eventId + "\n", "utf8");
2712
+ mkdirSync6(join8(cabaneDir(), "cursors"), { recursive: true });
2713
+ writeFileSync4(path, eventId + "\n", "utf8");
3166
2714
  }
3167
2715
 
3168
2716
  // src/cursor-tracker.ts
@@ -3205,20 +2753,20 @@ var CursorTracker = class {
3205
2753
  };
3206
2754
 
3207
2755
  // src/dispatch-dedupe.ts
3208
- import { mkdirSync as mkdirSync8, readFileSync as readFileSync6, writeFileSync as writeFileSync6, existsSync as existsSync9 } from "fs";
3209
- 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";
3210
2758
  var MAX_IDS = 256;
3211
2759
  function dir(log) {
3212
- return join11(cabaneDir(), log);
2760
+ return join9(cabaneDir(), log);
3213
2761
  }
3214
2762
  function pathFor2(log, workspaceId) {
3215
- return join11(dir(log), encodeURIComponent(workspaceId));
2763
+ return join9(dir(log), encodeURIComponent(workspaceId));
3216
2764
  }
3217
2765
  function readIds(log, workspaceId) {
3218
2766
  const path = pathFor2(log, workspaceId);
3219
- if (!existsSync9(path)) return [];
2767
+ if (!existsSync7(path)) return [];
3220
2768
  try {
3221
- 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);
3222
2770
  } catch {
3223
2771
  return [];
3224
2772
  }
@@ -3231,8 +2779,8 @@ function mark(log, workspaceId, eventId) {
3231
2779
  if (ids.includes(eventId)) return;
3232
2780
  ids.push(eventId);
3233
2781
  const trimmed = ids.length > MAX_IDS ? ids.slice(-MAX_IDS) : ids;
3234
- mkdirSync8(dir(log), { recursive: true });
3235
- 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");
3236
2784
  }
3237
2785
  function hasDispatched(workspaceId, eventId) {
3238
2786
  return has("dispatched", workspaceId, eventId);
@@ -3248,17 +2796,17 @@ function markCompleted(workspaceId, eventId) {
3248
2796
  }
3249
2797
  var MAX_RESUME_ATTEMPTS = 3;
3250
2798
  function resumeDir() {
3251
- return join11(cabaneDir(), "resume-attempts");
2799
+ return join9(cabaneDir(), "resume-attempts");
3252
2800
  }
3253
2801
  function resumePathFor(workspaceId) {
3254
- return join11(resumeDir(), encodeURIComponent(workspaceId));
2802
+ return join9(resumeDir(), encodeURIComponent(workspaceId));
3255
2803
  }
3256
2804
  function readResumeCounts(workspaceId) {
3257
2805
  const out = /* @__PURE__ */ new Map();
3258
2806
  const path = resumePathFor(workspaceId);
3259
- if (!existsSync9(path)) return out;
2807
+ if (!existsSync7(path)) return out;
3260
2808
  try {
3261
- for (const line of readFileSync6(path, "utf8").split("\n")) {
2809
+ for (const line of readFileSync5(path, "utf8").split("\n")) {
3262
2810
  const trimmed = line.trim();
3263
2811
  if (!trimmed) continue;
3264
2812
  const tab = trimmed.lastIndexOf(" ");
@@ -3278,8 +2826,8 @@ function bumpResumeAttempt(workspaceId, eventId) {
3278
2826
  counts.set(eventId, next);
3279
2827
  const entries = [...counts.entries()];
3280
2828
  const trimmed = entries.length > MAX_IDS ? entries.slice(-MAX_IDS) : entries;
3281
- mkdirSync8(resumeDir(), { recursive: true });
3282
- writeFileSync6(
2829
+ mkdirSync7(resumeDir(), { recursive: true });
2830
+ writeFileSync5(
3283
2831
  resumePathFor(workspaceId),
3284
2832
  trimmed.map(([id, c]) => `${id} ${c}`).join("\n") + "\n",
3285
2833
  "utf8"
@@ -6977,8 +6525,8 @@ var ConnectorHealthStore = class {
6977
6525
 
6978
6526
  // src/dispatcher.ts
6979
6527
  import { randomUUID } from "crypto";
6980
- import { appendFileSync as appendFileSync2, existsSync as existsSync12, mkdirSync as mkdirSync11, readdirSync as readdirSync2, statSync } from "fs";
6981
- 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";
6982
6530
 
6983
6531
  // src/summon.ts
6984
6532
  import { z as z12 } from "zod";
@@ -7311,11 +6859,11 @@ function trimSlash2(s) {
7311
6859
  // src/codex-instructions.ts
7312
6860
  import { mkdtemp, rm, writeFile } from "fs/promises";
7313
6861
  import { tmpdir } from "os";
7314
- import { join as join12 } from "path";
6862
+ import { join as join10 } from "path";
7315
6863
  var PREFIX = "cabane-codex-instructions-";
7316
6864
  async function writeCodexInstructionsFile(contents) {
7317
- const dir2 = await mkdtemp(join12(tmpdir(), PREFIX));
7318
- const path = join12(dir2, "instructions.md");
6865
+ const dir2 = await mkdtemp(join10(tmpdir(), PREFIX));
6866
+ const path = join10(dir2, "instructions.md");
7319
6867
  await writeFile(path, contents, { encoding: "utf8", mode: 384 });
7320
6868
  return {
7321
6869
  path,
@@ -7326,22 +6874,22 @@ async function writeCodexInstructionsFile(contents) {
7326
6874
  }
7327
6875
 
7328
6876
  // src/prepared.ts
7329
- import { mkdirSync as mkdirSync9, readFileSync as readFileSync7, rmSync as rmSync6, writeFileSync as writeFileSync7, existsSync as existsSync10 } from "fs";
7330
- 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";
7331
6879
  function dirFor(workspaceId) {
7332
- return join13(cabaneDir(), "prepared", encodeURIComponent(workspaceId));
6880
+ return join11(cabaneDir(), "prepared", encodeURIComponent(workspaceId));
7333
6881
  }
7334
6882
  function conversationDir(workspaceId, conversationId) {
7335
- return join13(dirFor(workspaceId), encodeURIComponent(conversationId));
6883
+ return join11(dirFor(workspaceId), encodeURIComponent(conversationId));
7336
6884
  }
7337
6885
  function pathFor3(workspaceId, conversationId, agentId) {
7338
- return join13(conversationDir(workspaceId, conversationId), `${encodeURIComponent(agentId)}.json`);
6886
+ return join11(conversationDir(workspaceId, conversationId), `${encodeURIComponent(agentId)}.json`);
7339
6887
  }
7340
6888
  function readPrepared(workspaceId, conversationId, agentId) {
7341
6889
  const path = pathFor3(workspaceId, conversationId, agentId);
7342
- if (!existsSync10(path)) return null;
6890
+ if (!existsSync8(path)) return null;
7343
6891
  try {
7344
- const parsed = JSON.parse(readFileSync7(path, "utf8"));
6892
+ const parsed = JSON.parse(readFileSync6(path, "utf8"));
7345
6893
  if (parsed && typeof parsed.cwd === "string" && parsed.cwd.length > 0) {
7346
6894
  return {
7347
6895
  cwd: parsed.cwd,
@@ -7354,32 +6902,32 @@ function readPrepared(workspaceId, conversationId, agentId) {
7354
6902
  }
7355
6903
  }
7356
6904
  function writePrepared(workspaceId, conversationId, agentId, result) {
7357
- mkdirSync9(conversationDir(workspaceId, conversationId), { recursive: true });
7358
- writeFileSync7(
6905
+ mkdirSync8(conversationDir(workspaceId, conversationId), { recursive: true });
6906
+ writeFileSync6(
7359
6907
  pathFor3(workspaceId, conversationId, agentId),
7360
6908
  JSON.stringify(result) + "\n",
7361
6909
  "utf8"
7362
6910
  );
7363
6911
  }
7364
6912
  function clearPrepared(workspaceId, conversationId, agentId) {
7365
- rmSync6(pathFor3(workspaceId, conversationId, agentId), { force: true });
6913
+ rmSync5(pathFor3(workspaceId, conversationId, agentId), { force: true });
7366
6914
  }
7367
6915
 
7368
6916
  // src/secrets.ts
7369
- import { existsSync as existsSync11, readFileSync as readFileSync8 } from "fs";
7370
- import { join as join14 } from "path";
6917
+ import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
6918
+ import { join as join12 } from "path";
7371
6919
  import { z as z13 } from "zod";
7372
6920
  var PLACEHOLDER_RE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
7373
6921
  function secretsPath() {
7374
- return join14(cabaneDir(), "secrets.json");
6922
+ return join12(cabaneDir(), "secrets.json");
7375
6923
  }
7376
6924
  var secretStoreSchema = z13.record(z13.string(), z13.string());
7377
6925
  function loadSecretStore() {
7378
6926
  const path = secretsPath();
7379
- if (!existsSync11(path)) return makeStore({});
6927
+ if (!existsSync9(path)) return makeStore({});
7380
6928
  let raw;
7381
6929
  try {
7382
- raw = readFileSync8(path, "utf8");
6930
+ raw = readFileSync7(path, "utf8");
7383
6931
  } catch (err) {
7384
6932
  throw new ConfigError(
7385
6933
  `couldn't read ${path}: ${err instanceof Error ? err.message : String(err)}`
@@ -7457,10 +7005,10 @@ function resolveMcpSecrets(mcpServers, store) {
7457
7005
  }
7458
7006
 
7459
7007
  // src/transcript-writer.ts
7460
- import { appendFileSync, chmodSync as chmodSync3, mkdirSync as mkdirSync10, readdirSync, rmSync as rmSync7 } from "fs";
7461
- 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";
7462
7010
  function transcriptsDir() {
7463
- return join15(cabaneDir(), "transcripts");
7011
+ return join13(cabaneDir(), "transcripts");
7464
7012
  }
7465
7013
  var RETAIN = 200;
7466
7014
  var TranscriptWriter = class {
@@ -7469,9 +7017,9 @@ var TranscriptWriter = class {
7469
7017
  onWarn;
7470
7018
  constructor(dir2, meta, onWarn) {
7471
7019
  this.onWarn = onWarn;
7472
- this.path = join15(dir2, fileName(meta));
7020
+ this.path = join13(dir2, fileName(meta));
7473
7021
  try {
7474
- mkdirSync10(dir2, { recursive: true });
7022
+ mkdirSync9(dir2, { recursive: true });
7475
7023
  try {
7476
7024
  chmodSync3(dir2, 448);
7477
7025
  } catch {
@@ -7528,7 +7076,7 @@ function pruneOld(dir2, retain) {
7528
7076
  const drop = files.sort().slice(0, files.length - retain);
7529
7077
  for (const f of drop) {
7530
7078
  try {
7531
- rmSync7(join15(dir2, f), { force: true });
7079
+ rmSync6(join13(dir2, f), { force: true });
7532
7080
  } catch {
7533
7081
  }
7534
7082
  }
@@ -7651,9 +7199,9 @@ var TurnCommitter = class {
7651
7199
  // commit. A self-target is stripped here (mirror of the in-app self-strip); the
7652
7200
  // server strips it again and resolves / ignores an unknown id.
7653
7201
  summonField() {
7654
- const target2 = this.deps.summonState.agentId;
7655
- if (!target2 || target2 === this.deps.agentId) return {};
7656
- return { dispatch: target2 };
7202
+ const target = this.deps.summonState.agentId;
7203
+ if (!target || target === this.deps.agentId) return {};
7204
+ return { dispatch: target };
7657
7205
  }
7658
7206
  // CT326: resolve the per-turn ask into the `ask` field for a `final` commit.
7659
7207
  // The server validates the target (must be a workspace member/owner) and
@@ -7874,7 +7422,7 @@ var DEFAULT_AGENT_IDLE_TIMEOUT_MS = 10 * 6e4;
7874
7422
  var DEFAULT_AGENT_TOTAL_TIMEOUT_MS = 6 * 60 * 6e4;
7875
7423
  function checkoutState(cwd) {
7876
7424
  if (!cwd) return { ok: false, reason: "no working directory was resolved for this turn" };
7877
- 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})` };
7878
7426
  let entries;
7879
7427
  try {
7880
7428
  entries = readdirSync2(cwd);
@@ -7884,15 +7432,15 @@ function checkoutState(cwd) {
7884
7432
  if (entries.length === 0) {
7885
7433
  return { ok: false, reason: `${cwd} is empty \u2014 a recreated shell, not a prepared directory` };
7886
7434
  }
7887
- const gitPath = join16(cwd, ".git");
7888
- if (!existsSync12(gitPath)) return { ok: true, reason: "usable" };
7435
+ const gitPath = join14(cwd, ".git");
7436
+ if (!existsSync10(gitPath)) return { ok: true, reason: "usable" };
7889
7437
  let stat;
7890
7438
  try {
7891
7439
  stat = statSync(gitPath);
7892
7440
  } catch (error) {
7893
7441
  return { ok: false, reason: `${gitPath} is unreadable (${error.message})` };
7894
7442
  }
7895
- if (stat.isDirectory() && !existsSync12(join16(gitPath, "HEAD")))
7443
+ if (stat.isDirectory() && !existsSync10(join14(gitPath, "HEAD")))
7896
7444
  return { ok: false, reason: `${gitPath} has no HEAD \u2014 an empty shell, not a checkout` };
7897
7445
  return { ok: true, reason: "usable" };
7898
7446
  }
@@ -8083,7 +7631,7 @@ var Dispatcher = class {
8083
7631
  let seqCounter = 0;
8084
7632
  const nextSeq = () => ++seqCounter;
8085
7633
  let effectiveCwd = localCwd ?? cabaneCwd;
8086
- if (effectiveCwd && !existsSync12(effectiveCwd)) {
7634
+ if (effectiveCwd && !existsSync10(effectiveCwd)) {
8087
7635
  turnLog.warn(
8088
7636
  { cwd: effectiveCwd },
8089
7637
  "dispatcher: configured working directory does not exist on this device \u2014 falling back to the process cwd"
@@ -8347,11 +7895,11 @@ ${reason}`,
8347
7895
  const totalTimeoutMs = this.opts.totalTimeoutMs ?? DEFAULT_AGENT_TOTAL_TIMEOUT_MS;
8348
7896
  const closeTurnReceipt = (ok, reason) => {
8349
7897
  if (!turnReceiptPath) return;
8350
- const target2 = turnReceiptPath;
7898
+ const target = turnReceiptPath;
8351
7899
  turnReceiptPath = null;
8352
7900
  try {
8353
7901
  appendFileSync2(
8354
- target2,
7902
+ target,
8355
7903
  `${JSON.stringify({
8356
7904
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
8357
7905
  event: "settled",
@@ -8391,7 +7939,7 @@ ${reason}`,
8391
7939
  }
8392
7940
  return this.concludeBeforeRun(payload, turnLog, startedAt, reason);
8393
7941
  }
8394
- const receiptPath = join16(effectiveCwd, ".git", "cabane", "readiness.jsonl");
7942
+ const receiptPath = join14(effectiveCwd, ".git", "cabane", "readiness.jsonl");
8395
7943
  const receiptLine = (fields) => `${JSON.stringify({
8396
7944
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
8397
7945
  taskId: hookEnv.CABANE_TASK_ID,
@@ -8415,7 +7963,7 @@ ${reason}`,
8415
7963
  })}
8416
7964
  `;
8417
7965
  try {
8418
- mkdirSync11(join16(effectiveCwd, ".git", "cabane"), { recursive: true });
7966
+ mkdirSync10(join14(effectiveCwd, ".git", "cabane"), { recursive: true });
8419
7967
  appendFileSync2(
8420
7968
  receiptPath,
8421
7969
  // `starting` is the honest classification before the proof has run. The
@@ -8886,15 +8434,15 @@ async function enumerateOpencodeModels(serverUrl, fetchImpl = fetch) {
8886
8434
 
8887
8435
  // src/outbox.ts
8888
8436
  import {
8889
- existsSync as existsSync13,
8890
- mkdirSync as mkdirSync12,
8437
+ existsSync as existsSync11,
8438
+ mkdirSync as mkdirSync11,
8891
8439
  readdirSync as readdirSync3,
8892
- readFileSync as readFileSync9,
8440
+ readFileSync as readFileSync8,
8893
8441
  renameSync as renameSync3,
8894
- rmSync as rmSync8,
8895
- writeFileSync as writeFileSync8
8442
+ rmSync as rmSync7,
8443
+ writeFileSync as writeFileSync7
8896
8444
  } from "fs";
8897
- import { join as join17 } from "path";
8445
+ import { join as join15 } from "path";
8898
8446
  var MAX_ENTRIES = 2e3;
8899
8447
  var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
8900
8448
  var Outbox = class {
@@ -8907,25 +8455,25 @@ var Outbox = class {
8907
8455
  // Resolved lazily (per call, like cursor.ts) so tests that swap HOME between
8908
8456
  // cases route writes at the right tmpdir.
8909
8457
  dir() {
8910
- return join17(cabaneDir(), "outbox", encodeURIComponent(this.workspaceId));
8458
+ return join15(cabaneDir(), "outbox", encodeURIComponent(this.workspaceId));
8911
8459
  }
8912
8460
  fileFor(turnId, seq) {
8913
- return join17(this.dir(), `${encodeURIComponent(turnId)}__${seq}.json`);
8461
+ return join15(this.dir(), `${encodeURIComponent(turnId)}__${seq}.json`);
8914
8462
  }
8915
8463
  // Persist a commit for later draining. Atomic (temp file + rename) so a
8916
8464
  // concurrent `list()` never reads a half-written entry, then enforces the
8917
8465
  // per-workspace bounds.
8918
8466
  persist(entry) {
8919
8467
  const dir2 = this.dir();
8920
- mkdirSync12(dir2, { recursive: true });
8921
- const target2 = this.fileFor(entry.turnId, entry.seq);
8922
- 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`;
8923
8471
  try {
8924
- writeFileSync8(tmp, JSON.stringify(entry) + "\n", "utf8");
8925
- renameSync3(tmp, target2);
8472
+ writeFileSync7(tmp, JSON.stringify(entry) + "\n", "utf8");
8473
+ renameSync3(tmp, target);
8926
8474
  } catch (err) {
8927
8475
  try {
8928
- rmSync8(tmp, { force: true });
8476
+ rmSync7(tmp, { force: true });
8929
8477
  } catch {
8930
8478
  }
8931
8479
  this.log?.warn(
@@ -8942,7 +8490,7 @@ var Outbox = class {
8942
8490
  // wedging the drain.
8943
8491
  list() {
8944
8492
  const dir2 = this.dir();
8945
- if (!existsSync13(dir2)) return [];
8493
+ if (!existsSync11(dir2)) return [];
8946
8494
  let names;
8947
8495
  try {
8948
8496
  names = readdirSync3(dir2);
@@ -8952,9 +8500,9 @@ var Outbox = class {
8952
8500
  const entries = [];
8953
8501
  for (const name of names) {
8954
8502
  if (!name.endsWith(".json")) continue;
8955
- const full = join17(dir2, name);
8503
+ const full = join15(dir2, name);
8956
8504
  try {
8957
- const parsed = JSON.parse(readFileSync9(full, "utf8"));
8505
+ const parsed = JSON.parse(readFileSync8(full, "utf8"));
8958
8506
  if (parsed && typeof parsed.turnId === "string" && typeof parsed.seq === "number" && typeof parsed.path === "string") {
8959
8507
  entries.push(parsed);
8960
8508
  } else {
@@ -8972,13 +8520,13 @@ var Outbox = class {
8972
8520
  // Remove a delivered (or terminally-discarded) entry. No-op if already gone.
8973
8521
  remove(turnId, seq) {
8974
8522
  try {
8975
- rmSync8(this.fileFor(turnId, seq), { force: true });
8523
+ rmSync7(this.fileFor(turnId, seq), { force: true });
8976
8524
  } catch {
8977
8525
  }
8978
8526
  }
8979
8527
  size() {
8980
8528
  const dir2 = this.dir();
8981
- if (!existsSync13(dir2)) return 0;
8529
+ if (!existsSync11(dir2)) return 0;
8982
8530
  try {
8983
8531
  return readdirSync3(dir2).filter((n) => n.endsWith(".json")).length;
8984
8532
  } catch {
@@ -8991,7 +8539,7 @@ var Outbox = class {
8991
8539
  "companion outbox: dropping unreadable entry"
8992
8540
  );
8993
8541
  try {
8994
- rmSync8(full, { force: true });
8542
+ rmSync7(full, { force: true });
8995
8543
  } catch {
8996
8544
  }
8997
8545
  }
@@ -10153,22 +9701,22 @@ function handleUncaught(log, err, origin) {
10153
9701
  }
10154
9702
 
10155
9703
  // src/crash-marker.ts
10156
- import { existsSync as existsSync14, mkdirSync as mkdirSync13, readFileSync as readFileSync10, rmSync as rmSync9, writeFileSync as writeFileSync9 } from "fs";
10157
- 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";
10158
9706
  function crashMarkerPath() {
10159
- return join18(cabaneDir(), "last-error.json");
9707
+ return join16(cabaneDir(), "last-error.json");
10160
9708
  }
10161
9709
  function recordCrash(rec2) {
10162
9710
  try {
10163
- mkdirSync13(cabaneDir(), { recursive: true });
10164
- writeFileSync9(crashMarkerPath(), JSON.stringify(rec2, null, 2) + "\n");
9711
+ mkdirSync12(cabaneDir(), { recursive: true });
9712
+ writeFileSync8(crashMarkerPath(), JSON.stringify(rec2, null, 2) + "\n");
10165
9713
  } catch {
10166
9714
  }
10167
9715
  }
10168
9716
  function clearCrash() {
10169
9717
  try {
10170
9718
  const path = crashMarkerPath();
10171
- if (existsSync14(path)) rmSync9(path, { force: true });
9719
+ if (existsSync12(path)) rmSync8(path, { force: true });
10172
9720
  } catch {
10173
9721
  }
10174
9722
  }
@@ -10320,7 +9868,24 @@ async function closeSurfaces(control, dashboard) {
10320
9868
 
10321
9869
  // src/commands/daemon.ts
10322
9870
  import { spawn as spawn5 } from "child_process";
10323
- 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
10324
9889
  var STARTUP_TIMEOUT_MS = 8e3;
10325
9890
  var POLL_INTERVAL_MS = 150;
10326
9891
  async function startDaemon(opts = {}, deps = {}) {
@@ -10330,11 +9895,6 @@ async function startDaemon(opts = {}, deps = {}) {
10330
9895
  const spawnDetached = deps.spawnDetached ?? defaultSpawnDetached;
10331
9896
  const sleep4 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
10332
9897
  const now = deps.now ?? (() => Date.now());
10333
- const install = deps.installService ?? ((o) => installService(o));
10334
- const refresh = deps.refreshService ?? ((o) => refreshInstalledService(o));
10335
- const disable = deps.disableService ?? (() => disableService());
10336
- const isTty = deps.isTty ?? (() => Boolean(process.stdin.isTTY));
10337
- const serviceOpts = {};
10338
9898
  const quiet = opts.report === "failures";
10339
9899
  const { cfg, claudeOnPath: claudeOnPath2 } = await requireStartConfig({
10340
9900
  // Unset → `requireStartConfig`'s own `requireConfig`, the real one.
@@ -10362,22 +9922,16 @@ async function startDaemon(opts = {}, deps = {}) {
10362
9922
  const existing = readState();
10363
9923
  if (existing) {
10364
9924
  if (await verify(existing) !== "stale") {
10365
- const rerendered = isTty() ? refresh(serviceOpts) : false;
10366
9925
  process.stdout.write(
10367
9926
  `Cabane Companion is already running (pid ${existing.pid}).
10368
9927
  Stop it first with \`cabane-companion stop\` if you want to relaunch.
10369
9928
  Connect a harness to the running companion: cabane-companion connect claude-code
10370
- ` + (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
+ `
10371
9930
  );
10372
- return {
10373
- started: false,
10374
- service: { installed: false, reason: "unsupported" },
10375
- state: existing
10376
- };
9931
+ return { started: false, state: existing };
10377
9932
  }
10378
9933
  clearRuntimeState();
10379
9934
  }
10380
- let service2 = isTty() ? install(serviceOpts) : { installed: false, reason: "unsupported" };
10381
9935
  const args = ["start", "--foreground"];
10382
9936
  const launchDetached = () => {
10383
9937
  const spawned = spawnDetached(args);
@@ -10394,29 +9948,8 @@ Connect a harness to the running companion: cabane-companion connect claude-code
10394
9948
  }
10395
9949
  return ready(seen) ? seen : null;
10396
9950
  };
10397
- if (!service2.installed && service2.leftBehind) {
10398
- refuseDouble(service2.leftBehind, service2.detail);
10399
- process.exitCode = 1;
10400
- return { started: false, service: service2, state: null };
10401
- }
10402
- let child = service2.installed ? null : launchDetached();
10403
- let state = await waitForReady();
10404
- if (!state && service2.installed) {
10405
- process.stdout.write(
10406
- `${service2.manager} started the companion but it didn't report ready within ${STARTUP_TIMEOUT_MS / 1e3}s \u2014 removing the login service.
10407
- `
10408
- );
10409
- const removed = disable();
10410
- if (!removed.handled || !removed.ok) {
10411
- refuseDouble(service2.manager, removed.detail);
10412
- process.exitCode = 1;
10413
- return { started: false, service: service2, state: null };
10414
- }
10415
- process.stdout.write("Launching it directly instead.\n");
10416
- service2 = { installed: false, reason: "command-failed", detail: "the service never came up" };
10417
- child = launchDetached();
10418
- state = await waitForReady();
10419
- }
9951
+ const child = launchDetached();
9952
+ const state = await waitForReady();
10420
9953
  if (!state) {
10421
9954
  process.stdout.write(
10422
9955
  `Cabane Companion was launched (pid ${child?.pid ?? "?"}) but didn't report ready within ${STARTUP_TIMEOUT_MS / 1e3}s.
@@ -10424,9 +9957,9 @@ Check ${companionLogPath()} for errors, or run \`cabane-companion status\`.
10424
9957
  `
10425
9958
  );
10426
9959
  process.exitCode = 1;
10427
- return { started: false, service: service2, state: null };
9960
+ return { started: false, state: null };
10428
9961
  }
10429
- if (quiet) return { started: true, service: service2, state };
9962
+ if (quiet) return { started: true, state };
10430
9963
  process.stdout.write(
10431
9964
  `Cabane Companion started in the background (pid ${state.pid}).
10432
9965
  Logs: ${companionLogPath()}
@@ -10434,27 +9967,11 @@ Status: cabane-companion status
10434
9967
  Stop: cabane-companion stop
10435
9968
  `
10436
9969
  );
10437
- if (service2.installed) {
10438
- process.stdout.write(`Autostart: enabled (${service2.manager}) \u2014 it starts again at login.
10439
- `);
10440
- } else if (service2.reason !== "unsupported") {
10441
- process.stdout.write(
10442
- `Autostart: not enabled \u2014 ${service2.detail ?? "the service manager refused the install"}. Running detached instead; it won't come back after a reboot.
10443
- `
10444
- );
10445
- }
10446
- return { started: true, service: service2, state };
10447
- }
10448
- function refuseDouble(manager, detail) {
10449
- process.stdout.write(
10450
- `${manager} still has the login service${detail ? ` (${detail})` : ""} \u2014 not launching a second companion beside a service that may still own one.
10451
- Check \`cabane-companion service status\`, then \`cabane-companion service disable\`, and run \`cabane-companion start --daemon\` again.
10452
- `
10453
- );
9970
+ return { started: true, state };
10454
9971
  }
10455
9972
  function defaultSpawnDetached(args) {
10456
9973
  const cliPath = companionCliEntry();
10457
- mkdirSync14(cabaneDir(), { recursive: true });
9974
+ mkdirSync13(cabaneDir(), { recursive: true });
10458
9975
  const logFd = openSync3(companionLogPath(), "a");
10459
9976
  try {
10460
9977
  return spawn5(process.execPath, [cliPath, ...args], {
@@ -10675,11 +10192,7 @@ async function handOffToBackground(runtime, ctx) {
10675
10192
  return;
10676
10193
  }
10677
10194
  if (ctx.spaceAbove) blank();
10678
- write(
10679
- INDENT + tick(
10680
- 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."
10681
- )
10682
- );
10195
+ write(INDENT + tick("Cabane companion is running in the background."));
10683
10196
  write(` Stop: cabane-companion stop Logs: ${tildePath(companionLogPath())}`);
10684
10197
  if (ctx.justPaired) {
10685
10198
  blank();
@@ -10743,8 +10256,7 @@ companion: deploy drain requested (${graceMs}ms grace)\u2026
10743
10256
 
10744
10257
  // src/commands/status.ts
10745
10258
  import { readdirSync as readdirSync4 } from "fs";
10746
- async function status(deps = {}) {
10747
- const readService = deps.service ?? serviceStatus;
10259
+ async function status() {
10748
10260
  const cfg = loadConfig();
10749
10261
  if (!cfg || !cfg.deviceToken) {
10750
10262
  process.stdout.write(
@@ -10780,13 +10292,6 @@ async function status(deps = {}) {
10780
10292
  "companion: not running \u2014 `cabane-companion start` (backgrounds itself; `--foreground` stays attached)\n"
10781
10293
  );
10782
10294
  }
10783
- const svc = readService();
10784
- if (svc.installed) {
10785
- process.stdout.write(
10786
- `autostart: ${svc.manager} \u2014 starts at login` + (svc.manager === "systemd-user" && svc.linger !== "yes" ? ", while you are logged in" : "") + ` (details: cabane-companion service status)
10787
- `
10788
- );
10789
- }
10790
10295
  process.stdout.write(
10791
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"
10792
10297
  );
@@ -10833,34 +10338,12 @@ function formatUptime(startedAt) {
10833
10338
  // src/commands/stop.ts
10834
10339
  var TERM_GRACE_MS = 6e3;
10835
10340
  var POLL_INTERVAL_MS2 = 150;
10836
- var SERVICE_STOP_GRACE_MS = 3e3;
10837
10341
  async function stop(deps = {}) {
10838
10342
  const readState = deps.readState ?? readLiveRuntimeState;
10839
10343
  const verify = deps.verify ?? ((s) => verifyRuntime(s));
10840
10344
  const kill = deps.kill ?? ((pid2, signal) => process.kill(pid2, signal));
10841
10345
  const sleep4 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
10842
10346
  const now = deps.now ?? (() => Date.now());
10843
- const stopSvc = deps.stopService ?? (() => stopService());
10844
- const svc = stopSvc();
10845
- if (svc.handled) {
10846
- if (!svc.ok) {
10847
- process.stdout.write(
10848
- `companion: ${svc.manager} wouldn't stop the service${svc.detail ? ` (${svc.detail})` : ""}.
10849
- The service manager owns this process \u2014 stopping it directly would just be restarted.
10850
- Check \`cabane-companion service status\`, or remove it with \`cabane-companion service disable\`.
10851
- `
10852
- );
10853
- process.exitCode = 1;
10854
- return;
10855
- }
10856
- const deadline2 = now() + SERVICE_STOP_GRACE_MS;
10857
- while (readState() && now() < deadline2) await sleep4(POLL_INTERVAL_MS2);
10858
- if (!readState()) {
10859
- process.stdout.write(`companion: stopped (${svc.manager} service stopped).
10860
- `);
10861
- return;
10862
- }
10863
- }
10864
10347
  const state = readState();
10865
10348
  if (!state) {
10866
10349
  process.stdout.write("companion: not running (nothing to stop).\n");
@@ -10910,8 +10393,8 @@ function isAlive(kill, pid) {
10910
10393
  }
10911
10394
 
10912
10395
  // src/commands/transcript.ts
10913
- import { existsSync as existsSync15, readFileSync as readFileSync11, readdirSync as readdirSync5 } from "fs";
10914
- 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";
10915
10398
  async function transcript(opts = {}) {
10916
10399
  const dir2 = transcriptsDir();
10917
10400
  if (opts.follow) {
@@ -10928,7 +10411,7 @@ async function transcript(opts = {}) {
10928
10411
  process.stdout.write(emptyMessage(dir2));
10929
10412
  return;
10930
10413
  }
10931
- process.stdout.write(renderFile(join19(dir2, newest)) + "\n");
10414
+ process.stdout.write(renderFile(join17(dir2, newest)) + "\n");
10932
10415
  return;
10933
10416
  }
10934
10417
  printList(dir2);
@@ -11011,7 +10494,7 @@ function isComplete(content) {
11011
10494
  async function followTranscripts(dir2) {
11012
10495
  const follower = new TranscriptFollower({
11013
10496
  listFiles: () => listFiles(dir2),
11014
- read: (f) => readFileSync11(join19(dir2, f), "utf8"),
10497
+ read: (f) => readFileSync10(join17(dir2, f), "utf8"),
11015
10498
  write: (s) => process.stdout.write(s),
11016
10499
  // CSI: cursor up `n` lines, then erase from cursor to end of screen.
11017
10500
  clearLines: (n) => process.stdout.write(`\x1B[${n}A\x1B[0J`),
@@ -11051,7 +10534,7 @@ function printList(dir2) {
11051
10534
 
11052
10535
  `);
11053
10536
  for (const f of files.slice(0, 20)) {
11054
- const { meta, outcome } = peek(join19(dir2, f));
10537
+ const { meta, outcome } = peek(join17(dir2, f));
11055
10538
  const when = fmtTime(rec(meta)?.ts);
11056
10539
  const ws = str2(rec(meta)?.workspaceSlug);
11057
10540
  const o = rec(outcome);
@@ -11072,7 +10555,7 @@ function peek(path) {
11072
10555
  let meta;
11073
10556
  let outcome;
11074
10557
  try {
11075
- for (const line of readFileSync11(path, "utf8").split("\n")) {
10558
+ for (const line of readFileSync10(path, "utf8").split("\n")) {
11076
10559
  if (!line.trim()) continue;
11077
10560
  const o = safeParse(line);
11078
10561
  const t = str2(rec(o)?.type);
@@ -11083,29 +10566,29 @@ function peek(path) {
11083
10566
  }
11084
10567
  return { meta, outcome };
11085
10568
  }
11086
- function resolveTarget(dir2, target2) {
11087
- if (isAbsolute(target2) || target2.includes("/")) {
11088
- if (existsSync15(target2)) return target2;
11089
- 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}.`);
11090
10573
  }
11091
- const exact = join19(dir2, target2);
11092
- if (existsSync15(exact)) return exact;
11093
- const matches = listFiles(dir2).filter((f) => f.includes(target2));
11094
- 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]);
11095
10578
  if (matches.length === 0) {
11096
10579
  throw new CompanionError(
11097
- `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.`
11098
10581
  );
11099
10582
  }
11100
10583
  throw new CompanionError(
11101
- `"${target2}" matches ${matches.length} transcripts \u2014 be more specific:
10584
+ `"${target}" matches ${matches.length} transcripts \u2014 be more specific:
11102
10585
  ` + matches.slice(0, 10).map((m) => ` ${m}`).join("\n")
11103
10586
  );
11104
10587
  }
11105
10588
  function renderFile(path) {
11106
10589
  let content;
11107
10590
  try {
11108
- content = readFileSync11(path, "utf8");
10591
+ content = readFileSync10(path, "utf8");
11109
10592
  } catch (err) {
11110
10593
  throw new CompanionError(
11111
10594
  `couldn't read ${path}: ${err instanceof Error ? err.message : String(err)}`
@@ -11242,11 +10725,11 @@ program.command("pair").description(
11242
10725
  });
11243
10726
  });
11244
10727
  program.command("write-paired-config", { hidden: true }).description("persist an already-completed device enrollment payload from stdin.").action(() => {
11245
- writeCompletedPairing(readFileSync12(0, "utf8"));
10728
+ writeCompletedPairing(readFileSync11(0, "utf8"));
11246
10729
  });
11247
10730
  program.command("start").description(
11248
10731
  "pair this device if needed, connect a coding agent on it, and run in the background."
11249
- ).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) => {
11250
10733
  await start({
11251
10734
  ...opts.foreground ? { foreground: true } : {},
11252
10735
  ...opts.server !== void 0 ? { server: opts.server } : {}
@@ -11261,13 +10744,6 @@ program.command("stop").description("stop a running companion (SIGTERM, then for
11261
10744
  program.command("status").description("print the companion's local state (pairing, secrets, log path).").action(async () => {
11262
10745
  await status();
11263
10746
  });
11264
- var service = program.command("service").description("inspect or remove the login service that starts the companion when you log in.");
11265
- service.command("status").description("print the service manager, whether the unit is installed and running.").action(() => {
11266
- serviceStatusCommand();
11267
- });
11268
- service.command("disable").description("stop the companion and remove the login service, leaving the machine clean.").action(() => {
11269
- serviceDisableCommand();
11270
- });
11271
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) => {
11272
10748
  await transcript({
11273
10749
  ...file !== void 0 ? { target: file } : {},