@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/README.md +128 -36
- package/dist/cli.js +149 -673
- package/dist/runtime.js +5 -1
- 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"}
|
|
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
|
|
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/
|
|
1677
|
-
import {
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
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
|
|
2196
|
-
import { fileURLToPath
|
|
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
|
|
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
|
|
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(
|
|
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 =
|
|
2459
|
-
if (!full.startsWith(staticDir) || !
|
|
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 (!
|
|
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
|
|
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
|
|
3152
|
-
import { join as
|
|
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
|
|
2702
|
+
return join8(cabaneDir(), "cursors", encodeURIComponent(workspaceId));
|
|
3155
2703
|
}
|
|
3156
2704
|
function readCursor(workspaceId) {
|
|
3157
2705
|
const path = pathFor(workspaceId);
|
|
3158
|
-
if (!
|
|
3159
|
-
const raw =
|
|
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
|
-
|
|
3165
|
-
|
|
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
|
|
3209
|
-
import { join as
|
|
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
|
|
2760
|
+
return join9(cabaneDir(), log);
|
|
3213
2761
|
}
|
|
3214
2762
|
function pathFor2(log, workspaceId) {
|
|
3215
|
-
return
|
|
2763
|
+
return join9(dir(log), encodeURIComponent(workspaceId));
|
|
3216
2764
|
}
|
|
3217
2765
|
function readIds(log, workspaceId) {
|
|
3218
2766
|
const path = pathFor2(log, workspaceId);
|
|
3219
|
-
if (!
|
|
2767
|
+
if (!existsSync7(path)) return [];
|
|
3220
2768
|
try {
|
|
3221
|
-
return
|
|
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
|
-
|
|
3235
|
-
|
|
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
|
|
2799
|
+
return join9(cabaneDir(), "resume-attempts");
|
|
3252
2800
|
}
|
|
3253
2801
|
function resumePathFor(workspaceId) {
|
|
3254
|
-
return
|
|
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 (!
|
|
2807
|
+
if (!existsSync7(path)) return out;
|
|
3260
2808
|
try {
|
|
3261
|
-
for (const line of
|
|
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
|
-
|
|
3282
|
-
|
|
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
|
|
6981
|
-
import { join as
|
|
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
|
|
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(
|
|
7318
|
-
const path =
|
|
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
|
|
7330
|
-
import { join as
|
|
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
|
|
6880
|
+
return join11(cabaneDir(), "prepared", encodeURIComponent(workspaceId));
|
|
7333
6881
|
}
|
|
7334
6882
|
function conversationDir(workspaceId, conversationId) {
|
|
7335
|
-
return
|
|
6883
|
+
return join11(dirFor(workspaceId), encodeURIComponent(conversationId));
|
|
7336
6884
|
}
|
|
7337
6885
|
function pathFor3(workspaceId, conversationId, agentId) {
|
|
7338
|
-
return
|
|
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 (!
|
|
6890
|
+
if (!existsSync8(path)) return null;
|
|
7343
6891
|
try {
|
|
7344
|
-
const parsed = JSON.parse(
|
|
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
|
-
|
|
7358
|
-
|
|
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
|
-
|
|
6913
|
+
rmSync5(pathFor3(workspaceId, conversationId, agentId), { force: true });
|
|
7366
6914
|
}
|
|
7367
6915
|
|
|
7368
6916
|
// src/secrets.ts
|
|
7369
|
-
import { existsSync as
|
|
7370
|
-
import { join as
|
|
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
|
|
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 (!
|
|
6927
|
+
if (!existsSync9(path)) return makeStore({});
|
|
7380
6928
|
let raw;
|
|
7381
6929
|
try {
|
|
7382
|
-
raw =
|
|
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
|
|
7461
|
-
import { join as
|
|
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
|
|
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 =
|
|
7020
|
+
this.path = join13(dir2, fileName(meta));
|
|
7473
7021
|
try {
|
|
7474
|
-
|
|
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
|
-
|
|
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
|
|
7655
|
-
if (!
|
|
7656
|
-
return { dispatch:
|
|
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 (!
|
|
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 =
|
|
7888
|
-
if (!
|
|
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() && !
|
|
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 && !
|
|
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
|
|
7898
|
+
const target = turnReceiptPath;
|
|
8351
7899
|
turnReceiptPath = null;
|
|
8352
7900
|
try {
|
|
8353
7901
|
appendFileSync2(
|
|
8354
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
8890
|
-
mkdirSync as
|
|
8437
|
+
existsSync as existsSync11,
|
|
8438
|
+
mkdirSync as mkdirSync11,
|
|
8891
8439
|
readdirSync as readdirSync3,
|
|
8892
|
-
readFileSync as
|
|
8440
|
+
readFileSync as readFileSync8,
|
|
8893
8441
|
renameSync as renameSync3,
|
|
8894
|
-
rmSync as
|
|
8895
|
-
writeFileSync as
|
|
8442
|
+
rmSync as rmSync7,
|
|
8443
|
+
writeFileSync as writeFileSync7
|
|
8896
8444
|
} from "fs";
|
|
8897
|
-
import { join as
|
|
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
|
|
8458
|
+
return join15(cabaneDir(), "outbox", encodeURIComponent(this.workspaceId));
|
|
8911
8459
|
}
|
|
8912
8460
|
fileFor(turnId, seq) {
|
|
8913
|
-
return
|
|
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
|
-
|
|
8921
|
-
const
|
|
8922
|
-
const 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
|
-
|
|
8925
|
-
renameSync3(tmp,
|
|
8472
|
+
writeFileSync7(tmp, JSON.stringify(entry) + "\n", "utf8");
|
|
8473
|
+
renameSync3(tmp, target);
|
|
8926
8474
|
} catch (err) {
|
|
8927
8475
|
try {
|
|
8928
|
-
|
|
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 (!
|
|
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 =
|
|
8503
|
+
const full = join15(dir2, name);
|
|
8956
8504
|
try {
|
|
8957
|
-
const parsed = JSON.parse(
|
|
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
|
-
|
|
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 (!
|
|
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
|
-
|
|
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
|
|
10157
|
-
import { join as
|
|
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
|
|
9707
|
+
return join16(cabaneDir(), "last-error.json");
|
|
10160
9708
|
}
|
|
10161
9709
|
function recordCrash(rec2) {
|
|
10162
9710
|
try {
|
|
10163
|
-
|
|
10164
|
-
|
|
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 (
|
|
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
|
|
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
|
-
`
|
|
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
|
-
|
|
10398
|
-
|
|
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,
|
|
9960
|
+
return { started: false, state: null };
|
|
10428
9961
|
}
|
|
10429
|
-
if (quiet) return { started: true,
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
10914
|
-
import { isAbsolute, join as
|
|
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(
|
|
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) =>
|
|
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(
|
|
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
|
|
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,
|
|
11087
|
-
if (isAbsolute(
|
|
11088
|
-
if (
|
|
11089
|
-
throw new CompanionError(`no transcript at ${
|
|
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 =
|
|
11092
|
-
if (
|
|
11093
|
-
const matches = listFiles(dir2).filter((f) => f.includes(
|
|
11094
|
-
if (matches.length === 1) return
|
|
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 "${
|
|
10580
|
+
`no transcript matching "${target}" in ${dir2}. Run \`cabane-companion transcript\` to list them.`
|
|
11098
10581
|
);
|
|
11099
10582
|
}
|
|
11100
10583
|
throw new CompanionError(
|
|
11101
|
-
`"${
|
|
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 =
|
|
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(
|
|
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 (
|
|
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 } : {},
|