@cabane/companion 0.6.159 → 0.6.160

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
@@ -9,166 +9,11 @@ var __export = (target, all) => {
9
9
  __defProp(target, name, { get: all[name], enumerable: true });
10
10
  };
11
11
 
12
- // src/bundled-binaries.ts
13
- import { realpathSync, statSync } from "fs";
14
- import { createRequire } from "module";
12
+ // src/install-kind.ts
13
+ import { realpathSync } from "fs";
15
14
  import { homedir } from "os";
16
15
  import { dirname, join, sep } from "path";
17
16
  import { fileURLToPath } from "url";
18
- function nodeResolveSelf(specifier) {
19
- const here = fileURLToPath(import.meta.url);
20
- if (typeof import.meta.resolve === "function") {
21
- try {
22
- return fileURLToPath(import.meta.resolve(specifier));
23
- } catch {
24
- }
25
- }
26
- try {
27
- return createRequire(here).resolve(specifier);
28
- } catch {
29
- }
30
- return walkNodeModules(specifier, here);
31
- }
32
- function walkNodeModules(pkg2, fromPath) {
33
- let dir2 = dirname(fromPath);
34
- for (; ; ) {
35
- const manifest = join(dir2, "node_modules", pkg2, "package.json");
36
- if (nodeIsFile(manifest)) {
37
- try {
38
- return realpathSync(manifest);
39
- } catch {
40
- return manifest;
41
- }
42
- }
43
- const parent = dirname(dir2);
44
- if (parent === dir2) return null;
45
- dir2 = parent;
46
- }
47
- }
48
- function nodeResolve(specifier, fromPath) {
49
- try {
50
- return createRequire(fromPath).resolve(specifier);
51
- } catch {
52
- return null;
53
- }
54
- }
55
- function nodeIsFile(path) {
56
- try {
57
- return statSync(path).isFile();
58
- } catch {
59
- return false;
60
- }
61
- }
62
- function detectPreferMusl(platform = process.platform) {
63
- if (platform !== "linux") return false;
64
- const report = typeof process.report?.getReport === "function" ? process.report.getReport() : null;
65
- const header = report?.header;
66
- return report != null && header?.glibcVersionRuntime === void 0;
67
- }
68
- function defaultResolveEnv() {
69
- return {
70
- platform: process.platform,
71
- arch: process.arch,
72
- preferMusl: detectPreferMusl(),
73
- // Both resolutions start from this module, which tsup bundles into
74
- // `dist/cli.js` and `dist/postinstall.js` — both at the package's `dist/`
75
- // root — so one environment serves the runtime checks and the install
76
- // verifier alike.
77
- resolveSelf: nodeResolveSelf,
78
- resolve: nodeResolve,
79
- isFile: nodeIsFile
80
- };
81
- }
82
- function claudePlatformPackages(platform, arch, preferMusl) {
83
- if (platform === "android") return [`${CLAUDE_SDK}-linux-${arch}-android`];
84
- if (platform === "linux") {
85
- return preferMusl ? [`${CLAUDE_SDK}-linux-${arch}-musl`, `${CLAUDE_SDK}-linux-${arch}`] : [`${CLAUDE_SDK}-linux-${arch}`, `${CLAUDE_SDK}-linux-${arch}-musl`];
86
- }
87
- return [`${CLAUDE_SDK}-${platform}-${arch}`];
88
- }
89
- function resolveClaude(env) {
90
- const candidates = claudePlatformPackages(env.platform, env.arch, env.preferMusl);
91
- const base = {
92
- runtime: "claude-code",
93
- packageName: candidates[0] ?? null,
94
- platform: env.platform,
95
- arch: env.arch
96
- };
97
- const sdkEntry = env.resolveSelf(CLAUDE_SDK);
98
- if (!sdkEntry) return { ...base, status: "platform_package_missing", path: null };
99
- let sawPackage = null;
100
- for (const pkg2 of candidates) {
101
- const manifest = env.resolve(`${pkg2}/package.json`, sdkEntry);
102
- if (!manifest) continue;
103
- sawPackage ??= pkg2;
104
- const exe = join(dirname(manifest), env.platform === "win32" ? "claude.exe" : "claude");
105
- if (env.isFile(exe)) {
106
- return { ...base, packageName: pkg2, status: "present", path: exe };
107
- }
108
- }
109
- return sawPackage ? { ...base, packageName: sawPackage, status: "executable_missing", path: null } : { ...base, status: "platform_package_missing", path: null };
110
- }
111
- function codexTargetTriple(platform, arch) {
112
- if (platform === "linux" || platform === "android") {
113
- if (arch === "x64") return "x86_64-unknown-linux-musl";
114
- if (arch === "arm64") return "aarch64-unknown-linux-musl";
115
- return null;
116
- }
117
- if (platform === "darwin") {
118
- if (arch === "x64") return "x86_64-apple-darwin";
119
- if (arch === "arm64") return "aarch64-apple-darwin";
120
- return null;
121
- }
122
- if (platform === "win32") {
123
- if (arch === "x64") return "x86_64-pc-windows-msvc";
124
- if (arch === "arm64") return "aarch64-pc-windows-msvc";
125
- return null;
126
- }
127
- return null;
128
- }
129
- function resolveCodex(env) {
130
- const triple = codexTargetTriple(env.platform, env.arch);
131
- const pkg2 = triple ? CODEX_PACKAGE_BY_TRIPLE[triple] : void 0;
132
- const base = {
133
- runtime: "codex",
134
- packageName: pkg2 ?? null,
135
- platform: env.platform,
136
- arch: env.arch
137
- };
138
- if (!triple || !pkg2) return { ...base, status: "platform_package_missing", path: null };
139
- const sdkEntry = env.resolveSelf(CODEX_SDK);
140
- if (!sdkEntry) return { ...base, status: "platform_package_missing", path: null };
141
- const cliManifest = env.resolve(`${CODEX_CLI}/package.json`, sdkEntry);
142
- if (!cliManifest) return { ...base, status: "platform_package_missing", path: null };
143
- const platformManifest = env.resolve(`${pkg2}/package.json`, cliManifest);
144
- if (!platformManifest) return { ...base, status: "platform_package_missing", path: null };
145
- const packageRoot = join(dirname(platformManifest), "vendor", triple);
146
- const exeName = env.platform === "win32" ? "codex.exe" : "codex";
147
- const current = join(packageRoot, "bin", exeName);
148
- if (env.isFile(current) && env.isFile(join(packageRoot, "codex-package.json"))) {
149
- return { ...base, status: "present", path: current };
150
- }
151
- const legacy = join(packageRoot, "codex", exeName);
152
- if (env.isFile(legacy)) return { ...base, status: "present", path: legacy };
153
- return { ...base, status: "executable_missing", path: null };
154
- }
155
- function resolveBundledBinary(runtime, env = defaultResolveEnv()) {
156
- try {
157
- return runtime === "codex" ? resolveCodex(env) : resolveClaude(env);
158
- } catch {
159
- return {
160
- runtime,
161
- status: "platform_package_missing",
162
- path: null,
163
- packageName: null,
164
- platform: env.platform,
165
- arch: env.arch
166
- };
167
- }
168
- }
169
- function missingNoun(status2) {
170
- return status2 === "executable_missing" ? "executable" : "platform package";
171
- }
172
17
  function companionInstall(execPath = process.execPath, home = homedir()) {
173
18
  const real = (path) => {
174
19
  try {
@@ -180,47 +25,21 @@ function companionInstall(execPath = process.execPath, home = homedir()) {
180
25
  const scriptNode = join(real(home), ".cabane", "node") + sep;
181
26
  return real(execPath).startsWith(scriptNode) ? "script" : "npm";
182
27
  }
183
- function repairCommands(install = companionInstall()) {
184
- return install === "script" ? [INSTALL_SCRIPT_COMMAND] : ["npm i -g @cabane/companion --include=optional"];
185
- }
186
- function repairAction(install = companionInstall()) {
187
- return `Reinstall with \`${repairCommands(install)[0]}\`.`;
188
- }
189
- function startupWarning(r) {
190
- const label = HARNESS_LABEL[r.runtime];
191
- return `${label} is ${HARNESS_INTENT_WORD[r.runtime]}, but its installation is incomplete (${missingNoun(r.status)} is missing). It cannot run turns until repaired. ${repairAction()} Then restart the companion.`;
28
+ function repairRoute(packageRoot = ownRoot(), install = companionInstall()) {
29
+ let root = packageRoot;
30
+ try {
31
+ root = realpathSync(packageRoot);
32
+ } catch {
33
+ }
34
+ return root.includes(`${sep}.cabane${sep}versions${sep}`) ? "managed" : install;
192
35
  }
193
- function harnessIssueNote(runtime, status2, install = companionInstall()) {
194
- const intent = HARNESS_INTENT_WORD[runtime];
195
- const capitalized = `${intent[0].toUpperCase()}${intent.slice(1)}`;
196
- return `${capitalized}, but @cabane/companion's bundled ${HARNESS_LABEL[runtime]} ${missingNoun(status2)} is missing. ${repairAction(install)} Then restart the companion.`;
36
+ function ownRoot() {
37
+ return dirname(dirname(fileURLToPath(import.meta.url)));
197
38
  }
198
- function turnIncompleteCopy(runtime) {
199
- return `**This device's ${HARNESS_LABEL[runtime]} runtime is incomplete.** ${repairAction()} Restart it, then try again.`;
200
- }
201
- var CLAUDE_SDK, CODEX_SDK, CODEX_CLI, CODEX_PACKAGE_BY_TRIPLE, HARNESS_LABEL, HARNESS_INTENT_WORD, INSTALL_SCRIPT_COMMAND;
202
- var init_bundled_binaries = __esm({
203
- "src/bundled-binaries.ts"() {
39
+ var INSTALL_SCRIPT_COMMAND;
40
+ var init_install_kind = __esm({
41
+ "src/install-kind.ts"() {
204
42
  "use strict";
205
- CLAUDE_SDK = "@anthropic-ai/claude-agent-sdk";
206
- CODEX_SDK = "@openai/codex-sdk";
207
- CODEX_CLI = "@openai/codex";
208
- CODEX_PACKAGE_BY_TRIPLE = {
209
- "x86_64-unknown-linux-musl": `${CODEX_CLI}-linux-x64`,
210
- "aarch64-unknown-linux-musl": `${CODEX_CLI}-linux-arm64`,
211
- "x86_64-apple-darwin": `${CODEX_CLI}-darwin-x64`,
212
- "aarch64-apple-darwin": `${CODEX_CLI}-darwin-arm64`,
213
- "x86_64-pc-windows-msvc": `${CODEX_CLI}-win32-x64`,
214
- "aarch64-pc-windows-msvc": `${CODEX_CLI}-win32-arm64`
215
- };
216
- HARNESS_LABEL = {
217
- "claude-code": "Claude Code",
218
- codex: "Codex"
219
- };
220
- HARNESS_INTENT_WORD = {
221
- "claude-code": "connected",
222
- codex: "enabled"
223
- };
224
43
  INSTALL_SCRIPT_COMMAND = "curl -fsSL https://cabane.ai/install.sh | bash";
225
44
  }
226
45
  });
@@ -358,16 +177,37 @@ var init_term = __esm({
358
177
  // src/cabane-command.ts
359
178
  import { accessSync, constants, realpathSync as realpathSync2 } from "fs";
360
179
  import { homedir as homedir3 } from "os";
361
- import { delimiter, join as join2, sep as sep2 } from "path";
180
+ import { basename, delimiter, dirname as dirname2, join as join2, sep as sep2 } from "path";
181
+ function invokedCommand(argv1) {
182
+ if (!argv1 || argv1.includes(VERSIONS_SEGMENT)) return null;
183
+ const name = basename(argv1);
184
+ if (name === "cabane" || name === "cabane-companion") return join2(dirname2(argv1), "cabane");
185
+ return npmBinFor(argv1);
186
+ }
187
+ function rememberInvokedCommand(env = process.env, argv1 = process.argv[1]) {
188
+ if (env[INVOKED_COMMAND_ENV]) return;
189
+ const invoked = invokedCommand(argv1);
190
+ if (invoked) env[INVOKED_COMMAND_ENV] = invoked;
191
+ }
362
192
  function installedCommandPath(deps = {}) {
363
193
  const install = deps.install ?? companionInstall();
364
194
  const home = deps.home ?? homedir3();
365
195
  if (install === "script") return join2(home, ".cabane", "bin", "cabane");
366
196
  const entry = deps.cliEntry ?? companionCliEntry();
197
+ const own = npmBinFor(entry);
198
+ if (own) return own;
199
+ if (!entry.includes(VERSIONS_SEGMENT)) return null;
200
+ const isExecutable = deps.isExecutable ?? executable;
201
+ const invoked = "invoked" in deps ? deps.invoked : process.env[INVOKED_COMMAND_ENV];
202
+ if (invoked && isExecutable(invoked)) return invoked;
203
+ const besideNode = join2(dirname2(dirname2(deps.execPath ?? process.execPath)), "bin", "cabane");
204
+ return isExecutable(besideNode) ? besideNode : null;
205
+ }
206
+ function npmBinFor(path) {
367
207
  const marker = `${sep2}lib${sep2}node_modules${sep2}@cabane${sep2}companion${sep2}`;
368
- const at = entry.indexOf(marker);
208
+ const at = path.indexOf(marker);
369
209
  if (at < 0) return null;
370
- return join2(entry.slice(0, at), "bin", "cabane");
210
+ return join2(path.slice(0, at), "bin", "cabane");
371
211
  }
372
212
  function cabaneCommand(deps = {}) {
373
213
  const own = installedCommandPath(deps);
@@ -383,6 +223,9 @@ function cabaneCommand(deps = {}) {
383
223
  function cabane(rest, deps) {
384
224
  return `${cabaneCommand(deps)} ${rest}`;
385
225
  }
226
+ function mayNeedUpdate(deps) {
227
+ return `This companion may need an update: run ${cabane("update", deps)}`;
228
+ }
386
229
  function firstOnPath(name, path, isExecutable = executable) {
387
230
  for (const dir2 of (path ?? "").split(delimiter)) {
388
231
  if (!dir2) continue;
@@ -406,12 +249,15 @@ function safeRealpath(path) {
406
249
  return null;
407
250
  }
408
251
  }
252
+ var INVOKED_COMMAND_ENV, VERSIONS_SEGMENT;
409
253
  var init_cabane_command = __esm({
410
254
  "src/cabane-command.ts"() {
411
255
  "use strict";
412
- init_bundled_binaries();
256
+ init_install_kind();
413
257
  init_cli_entry();
414
258
  init_term();
259
+ INVOKED_COMMAND_ENV = "CABANE_INVOKED_COMMAND";
260
+ VERSIONS_SEGMENT = `${sep2}.cabane${sep2}versions${sep2}`;
415
261
  }
416
262
  });
417
263
 
@@ -641,7 +487,7 @@ import {
641
487
  existsSync as existsSync2
642
488
  } from "fs";
643
489
  import { homedir as homedir4, userInfo } from "os";
644
- import { dirname as dirname2, join as join3 } from "path";
490
+ import { dirname as dirname3, join as join3 } from "path";
645
491
  import { z as z2 } from "zod";
646
492
  function realAccountHome() {
647
493
  if (realHomeCache === void 0) {
@@ -835,7 +681,7 @@ function loadConfigTolerant() {
835
681
  }
836
682
  function saveConfig(cfg) {
837
683
  const path = configPath();
838
- mkdirSync(dirname2(path), { recursive: true });
684
+ mkdirSync(dirname3(path), { recursive: true });
839
685
  try {
840
686
  chmodSync(cabaneDir(), 448);
841
687
  } catch {
@@ -1032,6 +878,226 @@ var init_config = __esm({
1032
878
  }
1033
879
  });
1034
880
 
881
+ // src/bundled-binaries.ts
882
+ import { realpathSync as realpathSync3, statSync } from "fs";
883
+ import { createRequire } from "module";
884
+ import { dirname as dirname4, join as join4 } from "path";
885
+ import { fileURLToPath as fileURLToPath3 } from "url";
886
+ function nodeResolveSelf(specifier) {
887
+ const here = fileURLToPath3(import.meta.url);
888
+ if (typeof import.meta.resolve === "function") {
889
+ try {
890
+ return fileURLToPath3(import.meta.resolve(specifier));
891
+ } catch {
892
+ }
893
+ }
894
+ try {
895
+ return createRequire(here).resolve(specifier);
896
+ } catch {
897
+ }
898
+ return walkNodeModules(specifier, here);
899
+ }
900
+ function walkNodeModules(pkg2, fromPath) {
901
+ let dir2 = dirname4(fromPath);
902
+ for (; ; ) {
903
+ const manifest = join4(dir2, "node_modules", pkg2, "package.json");
904
+ if (nodeIsFile(manifest)) {
905
+ try {
906
+ return realpathSync3(manifest);
907
+ } catch {
908
+ return manifest;
909
+ }
910
+ }
911
+ const parent = dirname4(dir2);
912
+ if (parent === dir2) return null;
913
+ dir2 = parent;
914
+ }
915
+ }
916
+ function nodeResolve(specifier, fromPath) {
917
+ try {
918
+ return createRequire(fromPath).resolve(specifier);
919
+ } catch {
920
+ return null;
921
+ }
922
+ }
923
+ function nodeIsFile(path) {
924
+ try {
925
+ return statSync(path).isFile();
926
+ } catch {
927
+ return false;
928
+ }
929
+ }
930
+ function detectPreferMusl(platform = process.platform) {
931
+ if (platform !== "linux") return false;
932
+ const report = typeof process.report?.getReport === "function" ? process.report.getReport() : null;
933
+ const header = report?.header;
934
+ return report != null && header?.glibcVersionRuntime === void 0;
935
+ }
936
+ function defaultResolveEnv() {
937
+ return {
938
+ platform: process.platform,
939
+ arch: process.arch,
940
+ preferMusl: detectPreferMusl(),
941
+ // Both resolutions start from this module, which tsup bundles into
942
+ // `dist/cli.js` and `dist/postinstall.js` — both at the package's `dist/`
943
+ // root — so one environment serves the runtime checks and the install
944
+ // verifier alike.
945
+ resolveSelf: nodeResolveSelf,
946
+ resolve: nodeResolve,
947
+ isFile: nodeIsFile
948
+ };
949
+ }
950
+ function claudePlatformPackages(platform, arch, preferMusl) {
951
+ if (platform === "android") return [`${CLAUDE_SDK}-linux-${arch}-android`];
952
+ if (platform === "linux") {
953
+ return preferMusl ? [`${CLAUDE_SDK}-linux-${arch}-musl`, `${CLAUDE_SDK}-linux-${arch}`] : [`${CLAUDE_SDK}-linux-${arch}`, `${CLAUDE_SDK}-linux-${arch}-musl`];
954
+ }
955
+ return [`${CLAUDE_SDK}-${platform}-${arch}`];
956
+ }
957
+ function resolveClaude(env) {
958
+ const candidates = claudePlatformPackages(env.platform, env.arch, env.preferMusl);
959
+ const base = {
960
+ runtime: "claude-code",
961
+ packageName: candidates[0] ?? null,
962
+ platform: env.platform,
963
+ arch: env.arch
964
+ };
965
+ const sdkEntry = env.resolveSelf(CLAUDE_SDK);
966
+ if (!sdkEntry) return { ...base, status: "platform_package_missing", path: null };
967
+ let sawPackage = null;
968
+ for (const pkg2 of candidates) {
969
+ const manifest = env.resolve(`${pkg2}/package.json`, sdkEntry);
970
+ if (!manifest) continue;
971
+ sawPackage ??= pkg2;
972
+ const exe = join4(dirname4(manifest), env.platform === "win32" ? "claude.exe" : "claude");
973
+ if (env.isFile(exe)) {
974
+ return { ...base, packageName: pkg2, status: "present", path: exe };
975
+ }
976
+ }
977
+ return sawPackage ? { ...base, packageName: sawPackage, status: "executable_missing", path: null } : { ...base, status: "platform_package_missing", path: null };
978
+ }
979
+ function codexTargetTriple(platform, arch) {
980
+ if (platform === "linux" || platform === "android") {
981
+ if (arch === "x64") return "x86_64-unknown-linux-musl";
982
+ if (arch === "arm64") return "aarch64-unknown-linux-musl";
983
+ return null;
984
+ }
985
+ if (platform === "darwin") {
986
+ if (arch === "x64") return "x86_64-apple-darwin";
987
+ if (arch === "arm64") return "aarch64-apple-darwin";
988
+ return null;
989
+ }
990
+ if (platform === "win32") {
991
+ if (arch === "x64") return "x86_64-pc-windows-msvc";
992
+ if (arch === "arm64") return "aarch64-pc-windows-msvc";
993
+ return null;
994
+ }
995
+ return null;
996
+ }
997
+ function codexPlatformPackage(platform, arch) {
998
+ const triple = codexTargetTriple(platform, arch);
999
+ return triple ? CODEX_PACKAGE_BY_TRIPLE[triple] ?? null : null;
1000
+ }
1001
+ function resolveCodex(env) {
1002
+ const triple = codexTargetTriple(env.platform, env.arch);
1003
+ const pkg2 = triple ? CODEX_PACKAGE_BY_TRIPLE[triple] : void 0;
1004
+ const base = {
1005
+ runtime: "codex",
1006
+ packageName: pkg2 ?? null,
1007
+ platform: env.platform,
1008
+ arch: env.arch
1009
+ };
1010
+ if (!triple || !pkg2) return { ...base, status: "platform_package_missing", path: null };
1011
+ const sdkEntry = env.resolveSelf(CODEX_SDK);
1012
+ if (!sdkEntry) return { ...base, status: "platform_package_missing", path: null };
1013
+ const cliManifest = env.resolve(`${CODEX_CLI}/package.json`, sdkEntry);
1014
+ if (!cliManifest) return { ...base, status: "platform_package_missing", path: null };
1015
+ const platformManifest = env.resolve(`${pkg2}/package.json`, cliManifest);
1016
+ if (!platformManifest) return { ...base, status: "platform_package_missing", path: null };
1017
+ const packageRoot = join4(dirname4(platformManifest), "vendor", triple);
1018
+ const exeName = env.platform === "win32" ? "codex.exe" : "codex";
1019
+ const current = join4(packageRoot, "bin", exeName);
1020
+ if (env.isFile(current) && env.isFile(join4(packageRoot, "codex-package.json"))) {
1021
+ return { ...base, status: "present", path: current };
1022
+ }
1023
+ const legacy = join4(packageRoot, "codex", exeName);
1024
+ if (env.isFile(legacy)) return { ...base, status: "present", path: legacy };
1025
+ return { ...base, status: "executable_missing", path: null };
1026
+ }
1027
+ function resolveBundledBinary(runtime, env = defaultResolveEnv()) {
1028
+ try {
1029
+ return runtime === "codex" ? resolveCodex(env) : resolveClaude(env);
1030
+ } catch {
1031
+ return {
1032
+ runtime,
1033
+ status: "platform_package_missing",
1034
+ path: null,
1035
+ packageName: null,
1036
+ platform: env.platform,
1037
+ arch: env.arch
1038
+ };
1039
+ }
1040
+ }
1041
+ function missingNoun(status2) {
1042
+ return status2 === "executable_missing" ? "executable" : "platform package";
1043
+ }
1044
+ function repairCommands(route = repairRoute(), spell = cabane) {
1045
+ if (route === "managed") return [spell("stop"), spell("update")];
1046
+ if (route === "script") return [spell("stop"), INSTALL_SCRIPT_COMMAND];
1047
+ return ["npm i -g @cabane/companion --include=optional"];
1048
+ }
1049
+ function repairAction(route = repairRoute(), spell = cabane) {
1050
+ const [first, ...rest] = repairCommands(route, spell);
1051
+ if (rest.length === 0) return `Reinstall with \`${first}\`.`;
1052
+ return `Run \`${first}\`, then \`${rest.join("`, then `")}\`. ${STOP_WARNING}`;
1053
+ }
1054
+ function repairSteps(route = repairRoute()) {
1055
+ return repairCommands(route).join(", then ");
1056
+ }
1057
+ function repairCost(route = repairRoute()) {
1058
+ return repairCommands(route).length > 1 ? STOP_WARNING : "";
1059
+ }
1060
+ function startupWarning(r) {
1061
+ const label = HARNESS_LABEL[r.runtime];
1062
+ return `${label} is ${HARNESS_INTENT_WORD[r.runtime]}, but its installation is incomplete (${missingNoun(r.status)} is missing). It cannot run turns until repaired. ${repairAction()} Then restart the companion.`;
1063
+ }
1064
+ function harnessIssueNote(runtime, status2, route = repairRoute(), spell = cabane) {
1065
+ const intent = HARNESS_INTENT_WORD[runtime];
1066
+ const capitalized = `${intent[0].toUpperCase()}${intent.slice(1)}`;
1067
+ return `${capitalized}, but @cabane/companion's bundled ${HARNESS_LABEL[runtime]} ${missingNoun(status2)} is missing. ${repairAction(route, spell)} Then restart the companion.`;
1068
+ }
1069
+ function turnIncompleteCopy(runtime) {
1070
+ return `**This device's ${HARNESS_LABEL[runtime]} runtime is incomplete.** ${repairAction()} Restart it, then try again.`;
1071
+ }
1072
+ var CLAUDE_SDK, CODEX_SDK, CODEX_CLI, CODEX_PACKAGE_BY_TRIPLE, HARNESS_LABEL, HARNESS_INTENT_WORD, STOP_WARNING;
1073
+ var init_bundled_binaries = __esm({
1074
+ "src/bundled-binaries.ts"() {
1075
+ "use strict";
1076
+ init_cabane_command();
1077
+ init_install_kind();
1078
+ CLAUDE_SDK = "@anthropic-ai/claude-agent-sdk";
1079
+ CODEX_SDK = "@openai/codex-sdk";
1080
+ CODEX_CLI = "@openai/codex";
1081
+ CODEX_PACKAGE_BY_TRIPLE = {
1082
+ "x86_64-unknown-linux-musl": `${CODEX_CLI}-linux-x64`,
1083
+ "aarch64-unknown-linux-musl": `${CODEX_CLI}-linux-arm64`,
1084
+ "x86_64-apple-darwin": `${CODEX_CLI}-darwin-x64`,
1085
+ "aarch64-apple-darwin": `${CODEX_CLI}-darwin-arm64`,
1086
+ "x86_64-pc-windows-msvc": `${CODEX_CLI}-win32-x64`,
1087
+ "aarch64-pc-windows-msvc": `${CODEX_CLI}-win32-arm64`
1088
+ };
1089
+ HARNESS_LABEL = {
1090
+ "claude-code": "Claude Code",
1091
+ codex: "Codex"
1092
+ };
1093
+ HARNESS_INTENT_WORD = {
1094
+ "claude-code": "connected",
1095
+ codex: "enabled"
1096
+ };
1097
+ STOP_WARNING = "Stopping ends any turn in progress.";
1098
+ }
1099
+ });
1100
+
1035
1101
  // packages/agent-runtime/src/failure.ts
1036
1102
  import { z as z4 } from "zod";
1037
1103
  function encodeFailureReason(failure) {
@@ -1302,6 +1368,7 @@ var init_api = __esm({
1302
1368
  "src/api.ts"() {
1303
1369
  "use strict";
1304
1370
  init_api_error_shape();
1371
+ init_cabane_command();
1305
1372
  init_errors();
1306
1373
  DEVICE_HEADER = "x-cabane-device";
1307
1374
  RETRY_BACKOFF_MS = [250, 750];
@@ -1436,7 +1503,7 @@ var init_api = __esm({
1436
1503
  }
1437
1504
  this.opts.log?.warn(
1438
1505
  { kind: entry.kind, turnId: entry.turnId, seq: entry.seq, ...apiErrorLogFields(err) },
1439
- "Cabane rejected a saved update; it could not be delivered. This companion may need an update: npm i -g @cabane/companion@latest"
1506
+ `Cabane rejected a saved update; it could not be delivered. ${mayNeedUpdate()}`
1440
1507
  );
1441
1508
  outbox.remove(entry.turnId, entry.seq);
1442
1509
  progressed = true;
@@ -7503,10 +7570,10 @@ var init_turn_containment = __esm({
7503
7570
 
7504
7571
  // src/secrets.ts
7505
7572
  import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
7506
- import { join as join12 } from "path";
7573
+ import { join as join13 } from "path";
7507
7574
  import { z as z14 } from "zod";
7508
7575
  function secretsPath() {
7509
- return join12(cabaneDir(), "secrets.json");
7576
+ return join13(cabaneDir(), "secrets.json");
7510
7577
  }
7511
7578
  function loadSecretStore() {
7512
7579
  const path = secretsPath();
@@ -8556,10 +8623,10 @@ var init_build_options = __esm({
8556
8623
  // src/codex-instructions.ts
8557
8624
  import { mkdtemp, rm, writeFile } from "fs/promises";
8558
8625
  import { tmpdir } from "os";
8559
- import { join as join13 } from "path";
8626
+ import { join as join14 } from "path";
8560
8627
  async function writeCodexInstructionsFile(contents) {
8561
- const dir2 = await mkdtemp(join13(tmpdir(), PREFIX));
8562
- const path = join13(dir2, "instructions.md");
8628
+ const dir2 = await mkdtemp(join14(tmpdir(), PREFIX));
8629
+ const path = join14(dir2, "instructions.md");
8563
8630
  await writeFile(path, contents, { encoding: "utf8", mode: 384 });
8564
8631
  return {
8565
8632
  path,
@@ -8579,13 +8646,13 @@ var init_codex_instructions = __esm({
8579
8646
  // src/codex-images.ts
8580
8647
  import { mkdtemp as mkdtemp2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
8581
8648
  import { tmpdir as tmpdir2 } from "os";
8582
- import { join as join14 } from "path";
8649
+ import { join as join15 } from "path";
8583
8650
  function createCodexImagePreparer(opts) {
8584
8651
  const doFetch = opts.fetch ?? fetch;
8585
8652
  const timeoutMs = opts.timeoutMs ?? FETCH_TIMEOUT_MS;
8586
8653
  return async (urls, signal) => {
8587
8654
  signal.throwIfAborted();
8588
- const dir2 = await mkdtemp2(join14(tmpdir2(), `cabane-codex-images-${opts.turnId}-`));
8655
+ const dir2 = await mkdtemp2(join15(tmpdir2(), `cabane-codex-images-${opts.turnId}-`));
8589
8656
  const cleanup = async () => {
8590
8657
  try {
8591
8658
  await rm2(dir2, { recursive: true, force: true });
@@ -8606,7 +8673,7 @@ function createCodexImagePreparer(opts) {
8606
8673
  const ext = EXTENSIONS[type];
8607
8674
  if (!ext) throw new Error(`not a vision image type: ${type || "none"}`);
8608
8675
  const bytes = new Uint8Array(await res.arrayBuffer());
8609
- const path = join14(dir2, `image-${index + 1}.${ext}`);
8676
+ const path = join15(dir2, `image-${index + 1}.${ext}`);
8610
8677
  await writeFile2(path, bytes, { mode: 384 });
8611
8678
  return path;
8612
8679
  } catch (err) {
@@ -8752,15 +8819,15 @@ var init_prepare_failure = __esm({
8752
8819
 
8753
8820
  // src/prepared.ts
8754
8821
  import { mkdirSync as mkdirSync7, readFileSync as readFileSync7, rmSync as rmSync5, writeFileSync as writeFileSync5, existsSync as existsSync9 } from "fs";
8755
- import { join as join15 } from "path";
8822
+ import { join as join16 } from "path";
8756
8823
  function dirFor(workspaceId) {
8757
- return join15(cabaneDir(), "prepared", encodeURIComponent(workspaceId));
8824
+ return join16(cabaneDir(), "prepared", encodeURIComponent(workspaceId));
8758
8825
  }
8759
8826
  function conversationDir(workspaceId, conversationId) {
8760
- return join15(dirFor(workspaceId), encodeURIComponent(conversationId));
8827
+ return join16(dirFor(workspaceId), encodeURIComponent(conversationId));
8761
8828
  }
8762
8829
  function pathFor2(workspaceId, conversationId, agentId) {
8763
- return join15(conversationDir(workspaceId, conversationId), `${encodeURIComponent(agentId)}.json`);
8830
+ return join16(conversationDir(workspaceId, conversationId), `${encodeURIComponent(agentId)}.json`);
8764
8831
  }
8765
8832
  function readPrepared(workspaceId, conversationId, agentId) {
8766
8833
  const path = pathFor2(workspaceId, conversationId, agentId);
@@ -8798,9 +8865,9 @@ var init_prepared = __esm({
8798
8865
 
8799
8866
  // src/transcript-writer.ts
8800
8867
  import { appendFileSync, chmodSync as chmodSync4, copyFileSync, mkdirSync as mkdirSync8, readdirSync, rmSync as rmSync6 } from "fs";
8801
- import { basename, dirname as dirname6, join as join16 } from "path";
8868
+ import { basename as basename2, dirname as dirname8, join as join17 } from "path";
8802
8869
  function transcriptsDir() {
8803
- return join16(runnerStateDir(), "transcripts");
8870
+ return join17(runnerStateDir(), "transcripts");
8804
8871
  }
8805
8872
  function fileName(meta) {
8806
8873
  const d = new Date(meta.ts);
@@ -8826,7 +8893,7 @@ function pruneOld(dir2, retain) {
8826
8893
  const drop = files.sort().slice(0, files.length - retain);
8827
8894
  for (const f of drop) {
8828
8895
  try {
8829
- rmSync6(join16(dir2, f), { force: true });
8896
+ rmSync6(join17(dir2, f), { force: true });
8830
8897
  } catch {
8831
8898
  }
8832
8899
  }
@@ -8844,7 +8911,7 @@ var init_transcript_writer = __esm({
8844
8911
  onWarn;
8845
8912
  constructor(dir2, meta, onWarn) {
8846
8913
  this.onWarn = onWarn;
8847
- this.path = join16(dir2, fileName(meta));
8914
+ this.path = join17(dir2, fileName(meta));
8848
8915
  try {
8849
8916
  mkdirSync8(dir2, { recursive: true });
8850
8917
  try {
@@ -8873,10 +8940,10 @@ var init_transcript_writer = __esm({
8873
8940
  preserveAnomaly() {
8874
8941
  if (this.broken) return;
8875
8942
  try {
8876
- const dir2 = join16(dirname6(this.path), "anomalies");
8943
+ const dir2 = join17(dirname8(this.path), "anomalies");
8877
8944
  mkdirSync8(dir2, { recursive: true, mode: 448 });
8878
8945
  chmodSync4(dir2, 448);
8879
- const target = join16(dir2, basename(this.path));
8946
+ const target = join17(dir2, basename2(this.path));
8880
8947
  copyFileSync(this.path, target);
8881
8948
  chmodSync4(target, 384);
8882
8949
  pruneOld(dir2, ANOMALY_RETAIN);
@@ -8906,6 +8973,7 @@ var TurnCommitter;
8906
8973
  var init_turn_committer = __esm({
8907
8974
  "src/turn-committer.ts"() {
8908
8975
  "use strict";
8976
+ init_cabane_command();
8909
8977
  init_src();
8910
8978
  init_api_error_shape();
8911
8979
  TurnCommitter = class {
@@ -8915,7 +8983,7 @@ var init_turn_committer = __esm({
8915
8983
  this.onError = (err, hook) => {
8916
8984
  deps.log.warn(
8917
8985
  { ...apiErrorLogFields(err), hook },
8918
- `Couldn't save ${String(deps.log.bindings().agentName ?? "the agent")}'s ${hook === "text" ? "reply" : "reply or activity"} to Cabane. This companion may need an update: npm i -g @cabane/companion@latest`
8986
+ `Couldn't save ${String(deps.log.bindings().agentName ?? "the agent")}'s ${hook === "text" ? "reply" : "reply or activity"} to Cabane. ${mayNeedUpdate()}`
8919
8987
  );
8920
8988
  deps.onCommitFailed?.(err);
8921
8989
  };
@@ -9057,7 +9125,7 @@ async function postIncompleteNotice(ctx2, harness, resolution) {
9057
9125
  } catch (err) {
9058
9126
  ctx2.log.warn(
9059
9127
  { ...apiErrorLogFields(err) },
9060
- "Couldn't save the explanation to Cabane. This companion may need an update: npm i -g @cabane/companion@latest"
9128
+ `Couldn't save the explanation to Cabane. ${mayNeedUpdate()}`
9061
9129
  );
9062
9130
  return false;
9063
9131
  }
@@ -9119,6 +9187,7 @@ var init_turn_runtime_integrity = __esm({
9119
9187
  "src/turn-runtime-integrity.ts"() {
9120
9188
  "use strict";
9121
9189
  init_api_error_shape();
9190
+ init_cabane_command();
9122
9191
  init_bundled_binaries();
9123
9192
  }
9124
9193
  });
@@ -9167,6 +9236,7 @@ var init_turn_execution = __esm({
9167
9236
  "src/turn-execution.ts"() {
9168
9237
  "use strict";
9169
9238
  init_term();
9239
+ init_cabane_command();
9170
9240
  init_boot_id();
9171
9241
  init_src();
9172
9242
  init_build_options();
@@ -9405,7 +9475,7 @@ var init_turn_execution = __esm({
9405
9475
  } else {
9406
9476
  turnLog.warn(
9407
9477
  { ...apiErrorLogFields(err), turnId },
9408
- "Couldn't save the turn's result to Cabane. This companion may need an update: npm i -g @cabane/companion@latest"
9478
+ `Couldn't save the turn's result to Cabane. ${mayNeedUpdate()}`
9409
9479
  );
9410
9480
  }
9411
9481
  }
@@ -9883,7 +9953,7 @@ var init_turn_execution = __esm({
9883
9953
  } catch (postErr) {
9884
9954
  turnLog.warn(
9885
9955
  { ...apiErrorLogFields(postErr) },
9886
- "Couldn't save the explanation to Cabane. This companion may need an update: npm i -g @cabane/companion@latest"
9956
+ `Couldn't save the explanation to Cabane. ${mayNeedUpdate()}`
9887
9957
  );
9888
9958
  const reason = `runtime_unavailable:${err.runtime}`;
9889
9959
  throw this.concluded(reason, reason);
@@ -10092,7 +10162,7 @@ var init_turn_execution = __esm({
10092
10162
  stateLength: event.state.length,
10093
10163
  runtime: turnRuntime
10094
10164
  },
10095
- "Couldn't save the agent's memory in Cabane. The next turn will start a new session. This companion may need an update: npm i -g @cabane/companion@latest"
10165
+ `Couldn't save the agent's memory in Cabane. The next turn will start a new session. ${mayNeedUpdate()}`
10096
10166
  );
10097
10167
  } else {
10098
10168
  turnLog.warn(
@@ -10256,7 +10326,7 @@ var init_turn_execution = __esm({
10256
10326
  } else {
10257
10327
  turnLog.warn(
10258
10328
  { ...apiErrorLogFields(err) },
10259
- "Couldn't save the turn's result to Cabane. This companion may need an update: npm i -g @cabane/companion@latest"
10329
+ `Couldn't save the turn's result to Cabane. ${mayNeedUpdate()}`
10260
10330
  );
10261
10331
  }
10262
10332
  }
@@ -10310,7 +10380,7 @@ var init_turn_execution = __esm({
10310
10380
  // src/dispatcher.ts
10311
10381
  import { randomUUID as randomUUID4 } from "crypto";
10312
10382
  import { existsSync as existsSync11, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
10313
- import { join as join17 } from "path";
10383
+ import { join as join18 } from "path";
10314
10384
  function checkoutState(cwd) {
10315
10385
  if (!cwd) return { ok: false, reason: "no working directory was resolved for this turn" };
10316
10386
  if (!existsSync11(cwd)) return { ok: false, reason: `the working directory is gone (${cwd})` };
@@ -10323,7 +10393,7 @@ function checkoutState(cwd) {
10323
10393
  if (entries.length === 0) {
10324
10394
  return { ok: false, reason: `${cwd} is empty \u2014 a recreated shell, not a prepared directory` };
10325
10395
  }
10326
- const gitPath = join17(cwd, ".git");
10396
+ const gitPath = join18(cwd, ".git");
10327
10397
  if (!existsSync11(gitPath)) return { ok: true, reason: "usable" };
10328
10398
  let stat;
10329
10399
  try {
@@ -10331,7 +10401,7 @@ function checkoutState(cwd) {
10331
10401
  } catch (error) {
10332
10402
  return { ok: false, reason: `${gitPath} is unreadable (${error.message})` };
10333
10403
  }
10334
- if (stat.isDirectory() && !existsSync11(join17(gitPath, "HEAD")))
10404
+ if (stat.isDirectory() && !existsSync11(join18(gitPath, "HEAD")))
10335
10405
  return { ok: false, reason: `${gitPath} has no HEAD \u2014 an empty shell, not a checkout` };
10336
10406
  return { ok: true, reason: "usable" };
10337
10407
  }
@@ -10703,15 +10773,15 @@ import {
10703
10773
  mkdirSync as mkdirSync11,
10704
10774
  mkdtempSync,
10705
10775
  readFileSync as readFileSync10,
10706
- realpathSync as realpathSync4,
10776
+ realpathSync as realpathSync5,
10707
10777
  rmSync as rmSync10,
10708
10778
  writeFileSync as writeFileSync7
10709
10779
  } from "fs";
10710
10780
  import { createRequire as createRequire2 } from "module";
10711
10781
  import { isIP } from "net";
10712
10782
  import { homedir as homedir6, networkInterfaces, tmpdir as tmpdir4, userInfo as userInfo2 } from "os";
10713
- import { dirname as dirname8, join as join21, sep as sep4 } from "path";
10714
- import { fileURLToPath as fileURLToPath5 } from "url";
10783
+ import { dirname as dirname10, join as join22, sep as sep4 } from "path";
10784
+ import { fileURLToPath as fileURLToPath6 } from "url";
10715
10785
  import { SandboxManager } from "@anthropic-ai/sandbox-runtime";
10716
10786
  function accountHome() {
10717
10787
  try {
@@ -10742,23 +10812,23 @@ async function resolvedAddresses(baseUrl) {
10742
10812
  }
10743
10813
  }
10744
10814
  function companionPackageRoot(modulePath) {
10745
- for (let dir2 = dirname8(modulePath); ; dir2 = dirname8(dir2)) {
10815
+ for (let dir2 = dirname10(modulePath); ; dir2 = dirname10(dir2)) {
10746
10816
  try {
10747
- const manifest = JSON.parse(readFileSync10(join21(dir2, "package.json"), "utf8"));
10817
+ const manifest = JSON.parse(readFileSync10(join22(dir2, "package.json"), "utf8"));
10748
10818
  if (manifest.name === "@cabane/companion") return dir2;
10749
10819
  } catch {
10750
10820
  }
10751
- if (dirname8(dir2) === dir2) return null;
10821
+ if (dirname10(dir2) === dir2) return null;
10752
10822
  }
10753
10823
  }
10754
10824
  function harnessRuntimePaths() {
10755
- const paths = [dirname8(dirname8(process.execPath))];
10756
- const root = companionPackageRoot(fileURLToPath5(import.meta.url));
10825
+ const paths = [dirname10(dirname10(process.execPath))];
10826
+ const root = companionPackageRoot(fileURLToPath6(import.meta.url));
10757
10827
  if (root) paths.push(root);
10758
10828
  try {
10759
10829
  const require2 = createRequire2(import.meta.url);
10760
10830
  paths.push(
10761
- dirname8(realpathSync4(require2.resolve("@anthropic-ai/sandbox-runtime/package.json")))
10831
+ dirname10(realpathSync5(require2.resolve("@anthropic-ai/sandbox-runtime/package.json")))
10762
10832
  );
10763
10833
  } catch {
10764
10834
  }
@@ -10766,7 +10836,7 @@ function harnessRuntimePaths() {
10766
10836
  const bundled = resolveBundledBinary(runtime).path;
10767
10837
  if (bundled) {
10768
10838
  try {
10769
- paths.push(dirname8(realpathSync4(bundled)));
10839
+ paths.push(dirname10(realpathSync5(bundled)));
10770
10840
  } catch {
10771
10841
  }
10772
10842
  }
@@ -10777,9 +10847,9 @@ function ownAddresses() {
10777
10847
  return Object.values(networkInterfaces()).flat().flatMap((i) => i ? [i.address] : []);
10778
10848
  }
10779
10849
  function storeWalkRoots(homes, cabaneDir2) {
10780
- const companionHome = dirname8(cabaneDir2);
10781
- const parent = dirname8(companionHome);
10782
- const beside = homes.includes(companionHome) || dirname8(parent) === parent ? [] : [parent];
10850
+ const companionHome = dirname10(cabaneDir2);
10851
+ const parent = dirname10(companionHome);
10852
+ const beside = homes.includes(companionHome) || dirname10(parent) === parent ? [] : [parent];
10783
10853
  const candidates = [.../* @__PURE__ */ new Set([...homes, ...beside])];
10784
10854
  return candidates.filter(
10785
10855
  (root) => !candidates.some((other) => other !== root && isAtOrUnder(root, other))
@@ -10817,19 +10887,19 @@ async function walkCompanionStores(roots, opts = {}) {
10817
10887
  }
10818
10888
  function companionStoreDenies(input) {
10819
10889
  const { homes, cabaneDir: cabaneDir2, foundStores } = input;
10820
- const parent = dirname8(dirname8(cabaneDir2));
10890
+ const parent = dirname10(dirname10(cabaneDir2));
10821
10891
  const always = [
10822
10892
  cabaneDir2,
10823
10893
  ...homes.flatMap((home) => [
10824
- join21(home, ".cabane"),
10825
- join21(home, "*", ".cabane"),
10826
- join21(home, "*", "*", ".cabane")
10894
+ join22(home, ".cabane"),
10895
+ join22(home, "*", ".cabane"),
10896
+ join22(home, "*", "*", ".cabane")
10827
10897
  ]),
10828
10898
  // A glob anchored at the root has no directory to start from.
10829
- ...dirname8(parent) === parent ? [] : [join21(parent, "*", ".cabane")]
10899
+ ...dirname10(parent) === parent ? [] : [join22(parent, "*", ".cabane")]
10830
10900
  ];
10831
- const companionHome = dirname8(cabaneDir2);
10832
- const otherLogins = foundStores.map((store) => dirname8(store)).filter((home) => home !== companionHome).flatMap((home) => [join21(home, ".claude"), join21(home, ".codex")]).filter((path) => !input.ownState.some((own) => isAtOrUnder(own, path)));
10901
+ const companionHome = dirname10(cabaneDir2);
10902
+ const otherLogins = foundStores.map((store) => dirname10(store)).filter((home) => home !== companionHome).flatMap((home) => [join22(home, ".claude"), join22(home, ".codex")]).filter((path) => !input.ownState.some((own) => isAtOrUnder(own, path)));
10833
10903
  return [.../* @__PURE__ */ new Set([...always, ...foundStores, ...otherLogins])];
10834
10904
  }
10835
10905
  function companionStoreWatch(log) {
@@ -10846,7 +10916,7 @@ function isAtOrUnder(path, dir2) {
10846
10916
  }
10847
10917
  function expandHome(path, home) {
10848
10918
  if (path === "~") return home;
10849
- return path.startsWith("~/") ? join21(home, path.slice(2)) : path;
10919
+ return path.startsWith("~/") ? join22(home, path.slice(2)) : path;
10850
10920
  }
10851
10921
  function stripPort(pattern) {
10852
10922
  return pattern.startsWith("[") ? pattern.replace(/^\[([^\]]+)\](:\d+)?$/, "$1") : pattern.replace(/:\d+$/, "");
@@ -10864,8 +10934,8 @@ function patternsOverlap(a, b) {
10864
10934
  }
10865
10935
  function harnessSandboxConfig(input) {
10866
10936
  const { policy } = input;
10867
- const claudeDir = input.claudeConfigDir ?? join21(input.home, ".claude");
10868
- const codexDir = input.codexHome ?? join21(input.home, ".codex");
10937
+ const claudeDir = input.claudeConfigDir ?? join22(input.home, ".claude");
10938
+ const codexDir = input.codexHome ?? join22(input.home, ".codex");
10869
10939
  const homes = [.../* @__PURE__ */ new Set([input.home, input.accountHome ?? input.home])];
10870
10940
  const expand = (paths) => (paths ?? []).map((p) => expandHome(p, input.home));
10871
10941
  const cabane2 = cabaneHostEntries(input.baseUrl, input.resolved, input.ownAddresses);
@@ -10882,7 +10952,7 @@ function harnessSandboxConfig(input) {
10882
10952
  foundStores: input.foundStores,
10883
10953
  ownState: [claudeDir, codexDir]
10884
10954
  }),
10885
- ...homes.flatMap((home) => SECRET_PATHS_UNDER_HOME.map((p) => join21(home, p))),
10955
+ ...homes.flatMap((home) => SECRET_PATHS_UNDER_HOME.map((p) => join22(home, p))),
10886
10956
  ...expand(policy.filesystem?.denyRead)
10887
10957
  ];
10888
10958
  const strict = policy.filesystem?.mode === "strict";
@@ -10890,7 +10960,7 @@ function harnessSandboxConfig(input) {
10890
10960
  const allowRead = strict ? [
10891
10961
  ...input.cwd ? [input.cwd] : [],
10892
10962
  claudeDir,
10893
- join21(input.home, ".claude.json"),
10963
+ join22(input.home, ".claude.json"),
10894
10964
  codexDir,
10895
10965
  ...input.runtimePaths ?? [],
10896
10966
  ...expand(policy.filesystem?.allowRead)
@@ -10930,7 +11000,7 @@ function harnessSandboxConfig(input) {
10930
11000
  ...input.cwd ? [input.cwd] : [],
10931
11001
  input.tmp,
10932
11002
  claudeDir,
10933
- join21(input.home, ".claude.json"),
11003
+ join22(input.home, ".claude.json"),
10934
11004
  codexDir,
10935
11005
  ...expand(policy.filesystem?.allowWrite)
10936
11006
  ],
@@ -11032,9 +11102,9 @@ async function prepareHarnessSandbox(input) {
11032
11102
  } finally {
11033
11103
  for (const name of held) delete process.env[name];
11034
11104
  }
11035
- mkdirSync11(join21(cabaneDir(), "sandbox"), { recursive: true });
11036
- const dir2 = mkdtempSync(join21(cabaneDir(), "sandbox", "turn-"));
11037
- const script = join21(dir2, "exec-sandboxed");
11105
+ mkdirSync11(join22(cabaneDir(), "sandbox"), { recursive: true });
11106
+ const dir2 = mkdtempSync(join22(cabaneDir(), "sandbox", "turn-"));
11107
+ const script = join22(dir2, "exec-sandboxed");
11038
11108
  writeFileSync7(
11039
11109
  script,
11040
11110
  [
@@ -11050,7 +11120,7 @@ async function prepareHarnessSandbox(input) {
11050
11120
  return {
11051
11121
  wrap: (command, args) => ({ command: script, args: [command, ...args] }),
11052
11122
  executableFor: (real) => {
11053
- const shim = join21(dir2, `exec-${++shims}`);
11123
+ const shim = join22(dir2, `exec-${++shims}`);
11054
11124
  writeFileSync7(shim, `#!/bin/bash
11055
11125
  exec ${shellQuote(script)} ${shellQuote(real)} "$@"
11056
11126
  `);
@@ -11143,7 +11213,7 @@ import {
11143
11213
  rmSync as rmSync11,
11144
11214
  writeFileSync as writeFileSync8
11145
11215
  } from "fs";
11146
- import { join as join22 } from "path";
11216
+ import { join as join23 } from "path";
11147
11217
  var MAX_ENTRIES, MAX_AGE_MS, Outbox;
11148
11218
  var init_outbox = __esm({
11149
11219
  "src/outbox.ts"() {
@@ -11161,10 +11231,10 @@ var init_outbox = __esm({
11161
11231
  // Resolved lazily (per call) so tests that swap HOME between
11162
11232
  // cases route writes at the right tmpdir.
11163
11233
  dir() {
11164
- return join22(runnerStateDir(), "outbox", encodeURIComponent(this.workspaceId));
11234
+ return join23(runnerStateDir(), "outbox", encodeURIComponent(this.workspaceId));
11165
11235
  }
11166
11236
  fileFor(turnId, seq) {
11167
- return join22(this.dir(), `${encodeURIComponent(turnId)}__${seq}.json`);
11237
+ return join23(this.dir(), `${encodeURIComponent(turnId)}__${seq}.json`);
11168
11238
  }
11169
11239
  // Persist a commit for later draining. Atomic (temp file + rename) so a
11170
11240
  // concurrent `list()` never reads a half-written entry, then enforces the
@@ -11206,7 +11276,7 @@ var init_outbox = __esm({
11206
11276
  const entries = [];
11207
11277
  for (const name of names) {
11208
11278
  if (!name.endsWith(".json")) continue;
11209
- const full = join22(dir2, name);
11279
+ const full = join23(dir2, name);
11210
11280
  try {
11211
11281
  const parsed = JSON.parse(readFileSync11(full, "utf8"));
11212
11282
  if (parsed && typeof parsed.turnId === "string" && typeof parsed.seq === "number" && typeof parsed.path === "string") {
@@ -11346,7 +11416,7 @@ var init_image_manifest = __esm({
11346
11416
 
11347
11417
  // src/runner/exec-shim.ts
11348
11418
  import { chmodSync as chmodSync8, mkdirSync as mkdirSync20, mkdtempSync as mkdtempSync2, rmSync as rmSync17, writeFileSync as writeFileSync13 } from "fs";
11349
- import { join as join31 } from "path";
11419
+ import { join as join32 } from "path";
11350
11420
  function shellQuote2(value) {
11351
11421
  return `'${value.replace(/'/g, `'\\''`)}'`;
11352
11422
  }
@@ -11356,10 +11426,10 @@ function createShimDir() {
11356
11426
  return {
11357
11427
  write(argv) {
11358
11428
  if (!dir2) {
11359
- mkdirSync20(join31(cabaneDir(), "runner"), { recursive: true });
11360
- dir2 = mkdtempSync2(join31(cabaneDir(), "runner", "turn-"));
11429
+ mkdirSync20(join32(cabaneDir(), "runner"), { recursive: true });
11430
+ dir2 = mkdtempSync2(join32(cabaneDir(), "runner", "turn-"));
11361
11431
  }
11362
- const path = join31(dir2, `exec-${++count}`);
11432
+ const path = join32(dir2, `exec-${++count}`);
11363
11433
  writeFileSync13(path, `#!/bin/sh
11364
11434
  exec ${argv.map(shellQuote2).join(" ")} "$@"
11365
11435
  `);
@@ -11716,13 +11786,13 @@ init_config();
11716
11786
  import { createHash } from "crypto";
11717
11787
  import { chmodSync as chmodSync2, existsSync as existsSync3, rmSync as rmSync2, mkdirSync as mkdirSync2 } from "fs";
11718
11788
  import { createServer, connect } from "net";
11719
- import { join as join5 } from "path";
11789
+ import { join as join6 } from "path";
11720
11790
 
11721
11791
  // src/harness-copy.ts
11722
11792
  init_bundled_binaries();
11723
11793
  init_cabane_command();
11724
11794
  import { homedir as homedir5 } from "os";
11725
- import { join as join4 } from "path";
11795
+ import { join as join5 } from "path";
11726
11796
 
11727
11797
  // src/harness-status.ts
11728
11798
  init_bundled_binaries();
@@ -11883,7 +11953,12 @@ function runtimeIssuesFor(gates) {
11883
11953
  for (const name of ["claude-code", "codex"]) {
11884
11954
  const gate = name === "codex" ? gates.codex : gates.claudeCode;
11885
11955
  if (!gate.intended || gate.bundled === "present") continue;
11886
- issues.push({ name, reason: gate.bundled, install: gates.install });
11956
+ issues.push({
11957
+ name,
11958
+ reason: gate.bundled,
11959
+ install: gates.install,
11960
+ ...gates.managed ? { managed: true } : {}
11961
+ });
11887
11962
  }
11888
11963
  return issues;
11889
11964
  }
@@ -12152,16 +12227,16 @@ function failureText(runtime, reason) {
12152
12227
  case "timeout":
12153
12228
  return `Couldn't check whether ${label} is signed in: the check didn't answer in time. Try again.`;
12154
12229
  case "spawn":
12155
- return `Couldn't check whether ${label} is signed in: Cabane's bundled ${label} wouldn't start. Repair it with ${repairCommands()[0]} , then try again.`;
12230
+ return `Couldn't check whether ${label} is signed in: Cabane's bundled ${label} wouldn't start. Repair it with ${repairSteps()} and try again.${spaced(repairCost())}`;
12156
12231
  case "config":
12157
- return `Couldn't check whether ${label} is signed in: its configuration file didn't load${runtime === "codex" ? ` (${join4(homedir5(), ".codex", "config.toml").replace(homedir5(), "~")})` : ""}. Fix it, then try again.`;
12232
+ return `Couldn't check whether ${label} is signed in: its configuration file didn't load${runtime === "codex" ? ` (${join5(homedir5(), ".codex", "config.toml").replace(homedir5(), "~")})` : ""}. Fix it, then try again.`;
12158
12233
  case "unreadable":
12159
12234
  return `Couldn't check whether ${label} is signed in: it answered in a way Cabane couldn't read. Try again, and if it keeps happening, update Cabane.`;
12160
12235
  }
12161
12236
  }
12162
12237
  function bundleMissingText(runtime) {
12163
12238
  const label = HARNESS_LABELS[runtime];
12164
- return `This Cabane install is missing its bundled ${label}. Repair it with ${repairCommands()[0]} , then try again.`;
12239
+ return `This Cabane install is missing its bundled ${label}. Repair it with ${repairSteps()} and try again.${spaced(repairCost())}`;
12165
12240
  }
12166
12241
  function unusableReason(runtime, check) {
12167
12242
  if (check.status === "bundle_missing") return bundleMissingText(runtime);
@@ -12235,6 +12310,9 @@ function connectLines(outcome, opts = {}) {
12235
12310
  ]
12236
12311
  };
12237
12312
  }
12313
+ function spaced(sentence) {
12314
+ return sentence ? ` ${sentence}` : "";
12315
+ }
12238
12316
 
12239
12317
  // src/control-socket.ts
12240
12318
  var CONTROL_TIMEOUT_MS = 1e3;
@@ -12245,11 +12323,11 @@ function controlSocketPath() {
12245
12323
  if (process.platform === "win32") {
12246
12324
  return `\\\\.\\pipe\\cabane-companion-${key}`;
12247
12325
  }
12248
- const besideRuntime = join5(dir2, "companion.sock");
12326
+ const besideRuntime = join6(dir2, "companion.sock");
12249
12327
  if (Buffer.byteLength(besideRuntime) <= MAX_UNIX_SOCKET_PATH_BYTES) return besideRuntime;
12250
12328
  const filename = `cabane-companion-${key}.sock`;
12251
- const xdgPath = process.env.XDG_RUNTIME_DIR && existsSync3(process.env.XDG_RUNTIME_DIR) ? join5(process.env.XDG_RUNTIME_DIR, filename) : null;
12252
- return xdgPath && Buffer.byteLength(xdgPath) <= MAX_UNIX_SOCKET_PATH_BYTES ? xdgPath : join5("/tmp", filename);
12329
+ const xdgPath = process.env.XDG_RUNTIME_DIR && existsSync3(process.env.XDG_RUNTIME_DIR) ? join6(process.env.XDG_RUNTIME_DIR, filename) : null;
12330
+ return xdgPath && Buffer.byteLength(xdgPath) <= MAX_UNIX_SOCKET_PATH_BYTES ? xdgPath : join6("/tmp", filename);
12253
12331
  }
12254
12332
  async function startControlServer(handlers) {
12255
12333
  const path = controlSocketPath();
@@ -12424,10 +12502,10 @@ import {
12424
12502
  openSync,
12425
12503
  closeSync
12426
12504
  } from "fs";
12427
- import { join as join6 } from "path";
12505
+ import { join as join7 } from "path";
12428
12506
  var PROBE_TIMEOUT_MS2 = 1e3;
12429
12507
  function runtimePath() {
12430
- return join6(cabaneDir(), "runtime.json");
12508
+ return join7(cabaneDir(), "runtime.json");
12431
12509
  }
12432
12510
  function serialize(state) {
12433
12511
  return JSON.stringify(state, null, 2) + "\n";
@@ -12642,10 +12720,10 @@ import {
12642
12720
  rmSync as rmSync4,
12643
12721
  writeFileSync as writeFileSync3
12644
12722
  } from "fs";
12645
- import { dirname as dirname3, join as join7 } from "path";
12723
+ import { dirname as dirname5, join as join8 } from "path";
12646
12724
  import { z as z3 } from "zod";
12647
12725
  function credentialsPath() {
12648
- return join7(cabaneDir(), "credentials.json");
12726
+ return join8(cabaneDir(), "credentials.json");
12649
12727
  }
12650
12728
  var credentialStoreSchema = z3.record(z3.string(), z3.string());
12651
12729
  function load() {
@@ -12667,7 +12745,7 @@ function load() {
12667
12745
  }
12668
12746
  function save(map) {
12669
12747
  const path = credentialsPath();
12670
- mkdirSync4(dirname3(path), { recursive: true });
12748
+ mkdirSync4(dirname5(path), { recursive: true });
12671
12749
  try {
12672
12750
  chmodSync3(cabaneDir(), 448);
12673
12751
  } catch {
@@ -12962,11 +13040,11 @@ import {
12962
13040
  renameSync as renameSync3,
12963
13041
  statSync as statSync2
12964
13042
  } from "fs";
12965
- import { dirname as dirname4, join as join8 } from "path";
13043
+ import { dirname as dirname6, join as join9 } from "path";
12966
13044
  import { stripVTControlCharacters } from "util";
12967
13045
  import pino from "pino";
12968
13046
  function companionLogPath() {
12969
- return join8(cabaneDir(), "companion.log");
13047
+ return join9(cabaneDir(), "companion.log");
12970
13048
  }
12971
13049
  function humanText(value) {
12972
13050
  return stripVTControlCharacters(String(value ?? "")).replace(
@@ -13080,7 +13158,7 @@ function configureLogger(log, config) {
13080
13158
  }
13081
13159
  function createLogger(destinations = {}, options = {}) {
13082
13160
  const path = companionLogPath();
13083
- if (!destinations.file) mkdirSync5(dirname4(path), { recursive: true });
13161
+ if (!destinations.file) mkdirSync5(dirname6(path), { recursive: true });
13084
13162
  const file = destinations.file ?? new RotatingLog(path);
13085
13163
  const state = { format: options.logFormat ?? "human" };
13086
13164
  const streams = [];
@@ -13133,15 +13211,15 @@ import { randomUUID as randomUUID7 } from "crypto";
13133
13211
 
13134
13212
  // src/dashboard/server.ts
13135
13213
  init_errors();
13136
- import { dirname as dirname5, join as join10 } from "path";
13137
- import { fileURLToPath as fileURLToPath3 } from "url";
13214
+ import { dirname as dirname7, join as join11 } from "path";
13215
+ import { fileURLToPath as fileURLToPath4 } from "url";
13138
13216
  import { serve } from "@hono/node-server";
13139
13217
  import { Hono } from "hono";
13140
13218
 
13141
13219
  // src/dashboard/routes.ts
13142
13220
  import { openSync as openSync3, readSync, closeSync as closeSync2, fstatSync, existsSync as existsSync6 } from "fs";
13143
13221
  import { readFile } from "fs/promises";
13144
- import { extname, join as join9, normalize } from "path";
13222
+ import { extname, join as join10, normalize } from "path";
13145
13223
  import { streamSSE } from "hono/streaming";
13146
13224
 
13147
13225
  // src/state.ts
@@ -13390,13 +13468,13 @@ var CONTENT_TYPES = {
13390
13468
  function registerRoutes(app, deps) {
13391
13469
  const { supervisor, hub, staticDir } = deps;
13392
13470
  app.get("/", async (c) => {
13393
- const html = await readFile(join9(staticDir, "index.html"), "utf8");
13471
+ const html = await readFile(join10(staticDir, "index.html"), "utf8");
13394
13472
  return c.html(html);
13395
13473
  });
13396
13474
  app.get("/static/:file", async (c) => {
13397
13475
  const file = c.req.param("file");
13398
13476
  const safe2 = normalize(file).replace(/^(\.\.[/\\])+/, "");
13399
- const full = join9(staticDir, safe2);
13477
+ const full = join10(staticDir, safe2);
13400
13478
  if (!full.startsWith(staticDir) || !existsSync6(full)) return c.notFound();
13401
13479
  const body = await readFile(full);
13402
13480
  const type = CONTENT_TYPES[extname(full).toLowerCase()] ?? "application/octet-stream";
@@ -13608,7 +13686,7 @@ function isAddrInUse(err) {
13608
13686
  return Boolean(err && typeof err === "object" && "code" in err && err.code === "EADDRINUSE");
13609
13687
  }
13610
13688
  function resolveStaticDir() {
13611
- return join10(dirname5(fileURLToPath3(import.meta.url)), "static");
13689
+ return join11(dirname7(fileURLToPath4(import.meta.url)), "static");
13612
13690
  }
13613
13691
 
13614
13692
  // src/supervisor.ts
@@ -13617,6 +13695,7 @@ import { hostname as hostname2 } from "os";
13617
13695
 
13618
13696
  // src/log-copy.ts
13619
13697
  init_failure();
13698
+ init_bundled_binaries();
13620
13699
  function turnFailureCopy(reason, runtime) {
13621
13700
  const failure = decodeFailureReason(reason);
13622
13701
  if (failure?.kind === "auth_expired") {
@@ -13635,7 +13714,7 @@ function turnFailureCopy(reason, runtime) {
13635
13714
  if (reason?.startsWith("missing_secret:"))
13636
13715
  return `A required secret is missing: ${humanText(reason.slice(15))}. Add it to ~/.cabane/secrets.json, then reply in Cabane to try again.`;
13637
13716
  if (reason?.startsWith("runtime_incomplete:"))
13638
- return "The agent installation is incomplete. Reinstall with npm i -g @cabane/companion@latest, then restart the companion.";
13717
+ return `The agent installation is incomplete. ${repairAction()} Then restart the companion.`;
13639
13718
  if (/^(runtime|codex|opencode)_unavailable:/.test(reason ?? ""))
13640
13719
  return "The agent is unavailable on this machine. Enable it here or choose another connector in Cabane.";
13641
13720
  if (reason?.startsWith("model_unavailable:"))
@@ -13775,13 +13854,13 @@ init_config();
13775
13854
  // src/dispatch-dedupe.ts
13776
13855
  init_config();
13777
13856
  import { mkdirSync as mkdirSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync4, renameSync as renameSync4, existsSync as existsSync7 } from "fs";
13778
- import { join as join11 } from "path";
13857
+ import { join as join12 } from "path";
13779
13858
  var MAX_IDS = 256;
13780
13859
  function dir(log) {
13781
- return join11(cabaneDir(), log);
13860
+ return join12(cabaneDir(), log);
13782
13861
  }
13783
13862
  function pathFor(log, workspaceId) {
13784
- return join11(dir(log), encodeURIComponent(workspaceId));
13863
+ return join12(dir(log), encodeURIComponent(workspaceId));
13785
13864
  }
13786
13865
  function readIds(log, workspaceId) {
13787
13866
  const path = pathFor(log, workspaceId);
@@ -13818,10 +13897,10 @@ function markCompleted(workspaceId, eventId) {
13818
13897
  }
13819
13898
  var MAX_RESUME_ATTEMPTS = 3;
13820
13899
  function resumeDir() {
13821
- return join11(cabaneDir(), "resume-attempts");
13900
+ return join12(cabaneDir(), "resume-attempts");
13822
13901
  }
13823
13902
  function resumePathFor(workspaceId) {
13824
- return join11(resumeDir(), encodeURIComponent(workspaceId));
13903
+ return join12(resumeDir(), encodeURIComponent(workspaceId));
13825
13904
  }
13826
13905
  function readResumeCounts(workspaceId) {
13827
13906
  const out = /* @__PURE__ */ new Map();
@@ -13857,10 +13936,10 @@ function bumpResumeAttempt(workspaceId, eventId) {
13857
13936
  return next;
13858
13937
  }
13859
13938
  function turnDir() {
13860
- return join11(cabaneDir(), "turns");
13939
+ return join12(cabaneDir(), "turns");
13861
13940
  }
13862
13941
  function turnPathFor(workspaceId) {
13863
- return join11(turnDir(), encodeURIComponent(workspaceId));
13942
+ return join12(turnDir(), encodeURIComponent(workspaceId));
13864
13943
  }
13865
13944
  var TURN_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
13866
13945
  function readTurnIds(workspaceId) {
@@ -13912,6 +13991,7 @@ function noResume() {
13912
13991
 
13913
13992
  // src/supervisor.ts
13914
13993
  init_bundled_binaries();
13994
+ init_install_kind();
13915
13995
 
13916
13996
  // src/connector-health.ts
13917
13997
  init_src();
@@ -14093,7 +14173,7 @@ import {
14093
14173
  statSync as statSync4,
14094
14174
  writeFileSync as writeFileSync6
14095
14175
  } from "fs";
14096
- import { join as join20 } from "path";
14176
+ import { join as join21 } from "path";
14097
14177
  import { Writable } from "stream";
14098
14178
 
14099
14179
  // src/providers/docker-cli.ts
@@ -14166,7 +14246,7 @@ import {
14166
14246
  renameSync as renameSync5,
14167
14247
  rmSync as rmSync8
14168
14248
  } from "fs";
14169
- import { join as join18 } from "path";
14249
+ import { join as join19 } from "path";
14170
14250
  function routes(t) {
14171
14251
  const conv = `/api/workspaces/${t.workspaceId}/conversations/${t.conversationId}`;
14172
14252
  const agent = `${conv}/participants/agents/${t.agentId}`;
@@ -14212,7 +14292,7 @@ function publishStaged(from, to, move = atomicMove) {
14212
14292
  const staged = readdirSync3(from).filter((n) => n.endsWith(".json")).map((name) => {
14213
14293
  let entry = {};
14214
14294
  try {
14215
- entry = JSON.parse(readFileSync8(join18(from, name), "utf8"));
14295
+ entry = JSON.parse(readFileSync8(join19(from, name), "utf8"));
14216
14296
  } catch {
14217
14297
  }
14218
14298
  return { name, entry };
@@ -14225,7 +14305,7 @@ function publishStaged(from, to, move = atomicMove) {
14225
14305
  let published = 0;
14226
14306
  for (const { name } of staged) {
14227
14307
  try {
14228
- move(join18(from, name), join18(to, name));
14308
+ move(join19(from, name), join19(to, name));
14229
14309
  } catch {
14230
14310
  return { published, complete: false };
14231
14311
  }
@@ -14237,9 +14317,9 @@ function publishStaged(from, to, move = atomicMove) {
14237
14317
 
14238
14318
  // src/providers/runner-base.ts
14239
14319
  import { createHash as createHash3 } from "crypto";
14240
- import { existsSync as existsSync13, realpathSync as realpathSync3 } from "fs";
14241
- import { dirname as dirname7, join as join19, sep as sep3 } from "path";
14242
- import { fileURLToPath as fileURLToPath4 } from "url";
14320
+ import { existsSync as existsSync13, realpathSync as realpathSync4 } from "fs";
14321
+ import { dirname as dirname9, join as join20, sep as sep3 } from "path";
14322
+ import { fileURLToPath as fileURLToPath5 } from "url";
14243
14323
  var COMPANION_MOUNT = "/opt/cabane/companion";
14244
14324
  var BASE_DOCKERFILE = `FROM node:22-slim
14245
14325
  RUN apt-get update \\
@@ -14283,17 +14363,17 @@ async function ensureBaseImage(docker, image, log) {
14283
14363
  }
14284
14364
  return true;
14285
14365
  }
14286
- function ownInstall(moduleFile = fileURLToPath4(import.meta.url)) {
14287
- let dir2 = dirname7(moduleFile);
14366
+ function ownInstall(moduleFile = fileURLToPath5(import.meta.url)) {
14367
+ let dir2 = dirname9(moduleFile);
14288
14368
  for (let i = 0; i < 4; i++) {
14289
- if (existsSync13(join19(dir2, "dist", "cli.js")) && existsSync13(join19(dir2, "package.json"))) break;
14290
- dir2 = dirname7(dir2);
14369
+ if (existsSync13(join20(dir2, "dist", "cli.js")) && existsSync13(join20(dir2, "package.json"))) break;
14370
+ dir2 = dirname9(dir2);
14291
14371
  }
14292
- if (!existsSync13(join19(dir2, "dist", "cli.js"))) return null;
14293
- const sdk = join19(dir2, "node_modules", "@anthropic-ai", "claude-agent-sdk");
14372
+ if (!existsSync13(join20(dir2, "dist", "cli.js"))) return null;
14373
+ const sdk = join20(dir2, "node_modules", "@anthropic-ai", "claude-agent-sdk");
14294
14374
  if (!existsSync13(sdk)) return null;
14295
- const root = realpathSync3(dir2);
14296
- return realpathSync3(sdk).startsWith(root + sep3) ? root : null;
14375
+ const root = realpathSync4(dir2);
14376
+ return realpathSync4(sdk).startsWith(root + sep3) ? root : null;
14297
14377
  }
14298
14378
  function companionSource(input) {
14299
14379
  if (input.configured) {
@@ -14429,39 +14509,39 @@ function networkCreateError(stderr) {
14429
14509
  }
14430
14510
  function sessionArchivePath(deviceDir, attemptId) {
14431
14511
  if (!UUID_RE.test(attemptId)) throw new Error(`docker: not an attempt id: ${attemptId}`);
14432
- return join20(deviceDir, "attempts", attemptId, "session.tar");
14512
+ return join21(deviceDir, "attempts", attemptId, "session.tar");
14433
14513
  }
14434
14514
  function pruneOtherAttempts(deviceDir, pair2, keep) {
14435
- const root = join20(deviceDir, "attempts");
14515
+ const root = join21(deviceDir, "attempts");
14436
14516
  if (!existsSync14(root)) return;
14437
14517
  for (const id of readdirSync4(root)) {
14438
14518
  if (id === keep || !UUID_RE.test(id)) continue;
14439
14519
  let pairOf = "";
14440
14520
  try {
14441
- pairOf = readFileSync9(join20(root, id, "pair"), "utf8");
14521
+ pairOf = readFileSync9(join21(root, id, "pair"), "utf8");
14442
14522
  } catch {
14443
14523
  continue;
14444
14524
  }
14445
14525
  if (pairOf === `${pair2.conversationId}/${pair2.agentId}`) {
14446
- rmSync9(join20(root, id), { recursive: true, force: true });
14526
+ rmSync9(join21(root, id), { recursive: true, force: true });
14447
14527
  }
14448
14528
  }
14449
14529
  }
14450
14530
  var IDLE_SESSION_ARCHIVE_MS = 30 * 24 * 60 * 6e4;
14451
14531
  function pruneIdleSessionArchives(deviceDir, now = Date.now(), maxAgeMs = IDLE_SESSION_ARCHIVE_MS) {
14452
- const root = join20(deviceDir, "attempts");
14532
+ const root = join21(deviceDir, "attempts");
14453
14533
  if (!existsSync14(root)) return [];
14454
14534
  const removed = [];
14455
14535
  for (const id of readdirSync4(root)) {
14456
14536
  if (!UUID_RE.test(id)) continue;
14457
14537
  let kept;
14458
14538
  try {
14459
- kept = statSync4(join20(root, id)).mtimeMs;
14539
+ kept = statSync4(join21(root, id)).mtimeMs;
14460
14540
  } catch {
14461
14541
  continue;
14462
14542
  }
14463
14543
  if (now - kept < maxAgeMs) continue;
14464
- rmSync9(join20(root, id), { recursive: true, force: true });
14544
+ rmSync9(join21(root, id), { recursive: true, force: true });
14465
14545
  removed.push(id);
14466
14546
  }
14467
14547
  return removed;
@@ -14662,11 +14742,11 @@ function createDockerProvider(opts) {
14662
14742
  await must(["network", "disconnect", "-f", other, name], "moving the container");
14663
14743
  if (others.includes(sharedNetwork)) await retireSharedNetwork();
14664
14744
  };
14665
- const socketRoot = opts.socketRoot ?? join20(
14745
+ const socketRoot = opts.socketRoot ?? join21(
14666
14746
  process.env.XDG_RUNTIME_DIR && existsSync14(process.env.XDG_RUNTIME_DIR) ? process.env.XDG_RUNTIME_DIR : tmpdir3(),
14667
14747
  `cabane-runners-${opts.deviceKey}`
14668
14748
  );
14669
- const socketDir = (name) => join20(socketRoot, createHash4("sha256").update(name).digest("hex").slice(0, 12));
14749
+ const socketDir = (name) => join21(socketRoot, createHash4("sha256").update(name).digest("hex").slice(0, 12));
14670
14750
  const create = async (name, attempt) => {
14671
14751
  mkdirSync10(socketRoot, { recursive: true, mode: 448 });
14672
14752
  mkdirSync10(socketDir(name), { recursive: true, mode: 457 });
@@ -14808,7 +14888,7 @@ function createDockerProvider(opts) {
14808
14888
  }
14809
14889
  };
14810
14890
  const removeIn = (name, from, names) => names.length ? execIn(name, ["sh", "-c", 'cd "$1" && shift && rm -f -- "$@"', "sh", from, ...names]) : Promise.resolve(null);
14811
- const pendingPath = (name) => join20(opts.deviceDir, "runners-pending", `${name}.json`);
14891
+ const pendingPath = (name) => join21(opts.deviceDir, "runners-pending", `${name}.json`);
14812
14892
  const readPending = (name) => {
14813
14893
  try {
14814
14894
  const ids = JSON.parse(readFileSync9(pendingPath(name), "utf8"));
@@ -14822,12 +14902,12 @@ function createDockerProvider(opts) {
14822
14902
  rmSync9(pendingPath(name), { force: true });
14823
14903
  return;
14824
14904
  }
14825
- mkdirSync10(join20(opts.deviceDir, "runners-pending"), { recursive: true });
14905
+ mkdirSync10(join21(opts.deviceDir, "runners-pending"), { recursive: true });
14826
14906
  writeFileSync6(pendingPath(name), JSON.stringify([...new Set(ids)]));
14827
14907
  };
14828
14908
  const keepSession = async (name, attempt, attemptId) => {
14829
14909
  const archive = sessionArchivePath(opts.deviceDir, attemptId);
14830
- mkdirSync10(join20(archive, ".."), { recursive: true });
14910
+ mkdirSync10(join21(archive, ".."), { recursive: true });
14831
14911
  const partial = `${archive}.${process.pid}.partial`;
14832
14912
  const file = new CappedFile(partial, sessionCap);
14833
14913
  const r = await execIn(
@@ -14844,7 +14924,7 @@ function createDockerProvider(opts) {
14844
14924
  );
14845
14925
  if (r.code === 0 && !file.over) {
14846
14926
  renameSync6(partial, archive);
14847
- writeFileSync6(join20(archive, "..", "pair"), `${attempt.conversationId}/${attempt.agentId}`);
14927
+ writeFileSync6(join21(archive, "..", "pair"), `${attempt.conversationId}/${attempt.agentId}`);
14848
14928
  pruneOtherAttempts(opts.deviceDir, attempt, attemptId);
14849
14929
  await writeMarker(name, attemptId);
14850
14930
  log.debug({ container: name, attemptId, bytes: file.bytes }, "runners: session files kept");
@@ -14858,7 +14938,7 @@ function createDockerProvider(opts) {
14858
14938
  file.over ? "runners: the session files are over the size cap and weren't kept; the next new container starts the session over" : "runners: couldn't keep the session files; the next new container starts the session over"
14859
14939
  );
14860
14940
  };
14861
- const stagingDir = (name) => join20(opts.deviceDir, "runners-pending", `${name}.outbox`);
14941
+ const stagingDir = (name) => join21(opts.deviceDir, "runners-pending", `${name}.outbox`);
14862
14942
  const ensureRunning = async (name) => {
14863
14943
  const seen = await docker.run(["inspect", "-f", "{{.State.Running}}", name]);
14864
14944
  if (seen.code !== 0) return false;
@@ -14866,7 +14946,7 @@ function createDockerProvider(opts) {
14866
14946
  return (await docker.run(["start", name])).code === 0;
14867
14947
  };
14868
14948
  const dropArchive = (attemptId) => {
14869
- rmSync9(join20(sessionArchivePath(opts.deviceDir, attemptId), ".."), {
14949
+ rmSync9(join21(sessionArchivePath(opts.deviceDir, attemptId), ".."), {
14870
14950
  recursive: true,
14871
14951
  force: true
14872
14952
  });
@@ -14935,7 +15015,7 @@ function createDockerProvider(opts) {
14935
15015
  if (seen.stdout.trim().split(" ")[0] !== "true")
14936
15016
  await must(["start", name], "starting the container");
14937
15017
  const git = await serveGitCredentials({
14938
- socketPath: join20(socketDir(name), "git.sock"),
15018
+ socketPath: join21(socketDir(name), "git.sock"),
14939
15019
  hosts: opts.gitHosts,
14940
15020
  fill: opts.gitFill,
14941
15021
  log
@@ -15084,15 +15164,15 @@ function createDockerProvider(opts) {
15084
15164
  const transcripts = await readDir(name, `${CONTAINER_STATE}/transcripts`);
15085
15165
  if (transcripts) {
15086
15166
  const kept = transcripts.files.filter((f) => SAFE_NAME_RE.test(f.name));
15087
- if (kept.length) mkdirSync10(join20(opts.hostStateDir, "transcripts"), { recursive: true });
15088
- for (const f of kept) writeFileSync6(join20(opts.hostStateDir, "transcripts", f.name), f.data);
15167
+ if (kept.length) mkdirSync10(join21(opts.hostStateDir, "transcripts"), { recursive: true });
15168
+ for (const f of kept) writeFileSync6(join21(opts.hostStateDir, "transcripts", f.name), f.data);
15089
15169
  await removeIn(
15090
15170
  name,
15091
15171
  `${CONTAINER_STATE}/transcripts`,
15092
15172
  kept.map((f) => f.name)
15093
15173
  );
15094
15174
  }
15095
- const outboxRel = join20("outbox", encodeURIComponent(attempt.agentId));
15175
+ const outboxRel = join21("outbox", encodeURIComponent(attempt.agentId));
15096
15176
  const outbox = await readDir(name, `${CONTAINER_STATE}/${outboxRel}`);
15097
15177
  let unpublished = false;
15098
15178
  if (outbox) {
@@ -15118,7 +15198,7 @@ function createDockerProvider(opts) {
15118
15198
  const staged = stagingDir(name);
15119
15199
  mkdirSync10(staged, { recursive: true });
15120
15200
  writeFileSync6(
15121
- join20(staged, `${encodeURIComponent(entry.turnId)}__${entry.seq}.json`),
15201
+ join21(staged, `${encodeURIComponent(entry.turnId)}__${entry.seq}.json`),
15122
15202
  `${JSON.stringify(entry)}
15123
15203
  `
15124
15204
  );
@@ -15131,7 +15211,7 @@ function createDockerProvider(opts) {
15131
15211
  if (outbox.complete) {
15132
15212
  const published = publishStaged(
15133
15213
  stagingDir(name),
15134
- join20(opts.hostStateDir, outboxRel),
15214
+ join21(opts.hostStateDir, outboxRel),
15135
15215
  opts.moveFile
15136
15216
  );
15137
15217
  queued = published.published;
@@ -15546,19 +15626,19 @@ init_transcript_writer();
15546
15626
  // src/updater.ts
15547
15627
  import { spawn as spawn8 } from "child_process";
15548
15628
  import { copyFileSync as copyFileSync3, existsSync as existsSync17, mkdirSync as mkdirSync16, renameSync as renameSync11, rmSync as rmSync14 } from "fs";
15549
- import { dirname as dirname12, join as join26 } from "path";
15629
+ import { dirname as dirname14, join as join27 } from "path";
15550
15630
 
15551
15631
  // src/boot-rule.ts
15552
15632
  import {
15553
15633
  mkdirSync as mkdirSync13,
15554
15634
  readFileSync as readFileSync12,
15555
- realpathSync as realpathSync5,
15635
+ realpathSync as realpathSync6,
15556
15636
  renameSync as renameSync8,
15557
15637
  rmSync as rmSync12,
15558
15638
  symlinkSync,
15559
15639
  writeFileSync as writeFileSync9
15560
15640
  } from "fs";
15561
- import { dirname as dirname9, join as join23 } from "path";
15641
+ import { dirname as dirname11, join as join24 } from "path";
15562
15642
  var UNCONFIRMED_WINDOW_MS = 10 * 6e4;
15563
15643
  var MAX_STAGE_ATTEMPTS = 3;
15564
15644
  function superseded(pending, ...running) {
@@ -15587,37 +15667,37 @@ import {
15587
15667
  mkdirSync as mkdirSync14,
15588
15668
  readFileSync as readFileSync13,
15589
15669
  readdirSync as readdirSync6,
15590
- realpathSync as realpathSync6,
15670
+ realpathSync as realpathSync7,
15591
15671
  renameSync as renameSync9,
15592
15672
  rmSync as rmSync13,
15593
15673
  symlinkSync as symlinkSync2
15594
15674
  } from "fs";
15595
- import { dirname as dirname10, join as join24 } from "path";
15596
- import { fileURLToPath as fileURLToPath6 } from "url";
15675
+ import { dirname as dirname12, join as join25 } from "path";
15676
+ import { fileURLToPath as fileURLToPath7 } from "url";
15597
15677
  function versionsDir() {
15598
- return join24(cabaneDir(), "versions");
15678
+ return join25(cabaneDir(), "versions");
15599
15679
  }
15600
15680
  function currentLink() {
15601
- return join24(cabaneDir(), "current");
15681
+ return join25(cabaneDir(), "current");
15602
15682
  }
15603
15683
  function bootGuardPath() {
15604
- return join24(cabaneDir(), "boot-guard.mjs");
15684
+ return join25(cabaneDir(), "boot-guard.mjs");
15605
15685
  }
15606
15686
  function pinnedNodeDir() {
15607
- return join24(cabaneDir(), "node");
15687
+ return join25(cabaneDir(), "node");
15608
15688
  }
15609
15689
  function pinnedNodePath() {
15610
- return join24(pinnedNodeDir(), "bin", "node");
15690
+ return join25(pinnedNodeDir(), "bin", "node");
15611
15691
  }
15612
15692
  function versionPackageRoot(version) {
15613
- return join24(versionsDir(), version, "node_modules", "@cabane", "companion");
15693
+ return join25(versionsDir(), version, "node_modules", "@cabane", "companion");
15614
15694
  }
15615
15695
  function ownPackageRoot(moduleUrl = import.meta.url) {
15616
- return dirname10(dirname10(fileURLToPath6(moduleUrl)));
15696
+ return dirname12(dirname12(fileURLToPath7(moduleUrl)));
15617
15697
  }
15618
15698
  function packageVersion(root) {
15619
15699
  try {
15620
- const pkg2 = JSON.parse(readFileSync13(join24(root, "package.json"), "utf8"));
15700
+ const pkg2 = JSON.parse(readFileSync13(join25(root, "package.json"), "utf8"));
15621
15701
  return typeof pkg2.version === "string" ? pkg2.version : null;
15622
15702
  } catch {
15623
15703
  return null;
@@ -15625,15 +15705,15 @@ function packageVersion(root) {
15625
15705
  }
15626
15706
  function currentRoot() {
15627
15707
  try {
15628
- const root = realpathSync6(currentLink());
15629
- return existsSync16(join24(root, "dist", "cli.js")) ? root : null;
15708
+ const root = realpathSync7(currentLink());
15709
+ return existsSync16(join25(root, "dist", "cli.js")) ? root : null;
15630
15710
  } catch {
15631
15711
  return null;
15632
15712
  }
15633
15713
  }
15634
15714
  function pointCurrentAt(root) {
15635
15715
  const link = currentLink();
15636
- mkdirSync14(dirname10(link), { recursive: true });
15716
+ mkdirSync14(dirname12(link), { recursive: true });
15637
15717
  const fresh = `${link}.${process.pid}.${Date.now()}`;
15638
15718
  symlinkSync2(root, fresh);
15639
15719
  try {
@@ -15649,7 +15729,7 @@ function pruneVersions(keep) {
15649
15729
  const removed = [];
15650
15730
  for (const name of readdirSync6(dir2)) {
15651
15731
  if (keep.includes(name)) continue;
15652
- rmSync13(join24(dir2, name), { recursive: true, force: true });
15732
+ rmSync13(join25(dir2, name), { recursive: true, force: true });
15653
15733
  removed.push(name);
15654
15734
  }
15655
15735
  return removed;
@@ -15669,7 +15749,7 @@ function compareVersions(a, b) {
15669
15749
  // src/update-state.ts
15670
15750
  init_config();
15671
15751
  import { mkdirSync as mkdirSync15, readFileSync as readFileSync14, renameSync as renameSync10, writeFileSync as writeFileSync10 } from "fs";
15672
- import { dirname as dirname11, join as join25 } from "path";
15752
+ import { dirname as dirname13, join as join26 } from "path";
15673
15753
  import { z as z18 } from "zod";
15674
15754
  var resultSchema = z18.object({
15675
15755
  kind: z18.enum(["updated", "rolled_back", "failed"]),
@@ -15693,7 +15773,7 @@ var stateSchema = z18.object({
15693
15773
  failed: z18.object({ version: z18.string(), attempts: z18.number().int(), at: z18.string() }).optional()
15694
15774
  });
15695
15775
  function updateStatePath(dir2 = cabaneDir()) {
15696
- return join25(dir2, "update.json");
15776
+ return join26(dir2, "update.json");
15697
15777
  }
15698
15778
  function readUpdateState(path = updateStatePath()) {
15699
15779
  try {
@@ -15704,7 +15784,7 @@ function readUpdateState(path = updateStatePath()) {
15704
15784
  }
15705
15785
  }
15706
15786
  function writeUpdateState(state, path = updateStatePath()) {
15707
- mkdirSync15(dirname11(path), { recursive: true });
15787
+ mkdirSync15(dirname13(path), { recursive: true });
15708
15788
  const tmp = `${path}.${process.pid}.tmp`;
15709
15789
  writeFileSync10(tmp, `${JSON.stringify(state, null, 2)}
15710
15790
  `, { mode: 384 });
@@ -15742,10 +15822,10 @@ function rollBack(pending, reason, now = /* @__PURE__ */ new Date()) {
15742
15822
  return updateUpdateState((state) => rolledBackState(state, pending, reason, now));
15743
15823
  }
15744
15824
  function writeBootGuard(root = ownPackageRoot()) {
15745
- const built = join26(root, "dist", "boot-guard.js");
15825
+ const built = join27(root, "dist", "boot-guard.js");
15746
15826
  if (!existsSync17(built)) return false;
15747
15827
  const target = bootGuardPath();
15748
- mkdirSync16(dirname12(target), { recursive: true });
15828
+ mkdirSync16(dirname14(target), { recursive: true });
15749
15829
  const tmp = `${target}.${process.pid}.tmp`;
15750
15830
  copyFileSync3(built, tmp);
15751
15831
  renameSync11(tmp, target);
@@ -15782,17 +15862,17 @@ var runProcess = (command, args, opts = {}) => new Promise((resolve2) => {
15782
15862
  async function stageVersion(version, harnesses, deps = {}) {
15783
15863
  const run = deps.run ?? runProcess;
15784
15864
  const node = deps.execPath ?? process.execPath;
15785
- const target = join26(versionsDir(), version);
15865
+ const target = join27(versionsDir(), version);
15786
15866
  const placed = versionPackageRoot(version);
15787
15867
  if (existsSync17(placed)) {
15788
15868
  if (!await verifyInstall(placed, version, harnesses, { run, execPath: node }))
15789
15869
  return { ok: true, root: placed };
15790
15870
  }
15791
- const npm = join26(dirname12(node), "npm");
15871
+ const npm = join27(dirname14(node), "npm");
15792
15872
  if (!existsSync17(npm))
15793
- return { ok: false, reason: `there is no npm beside Node at ${dirname12(node)}` };
15873
+ return { ok: false, reason: `there is no npm beside Node at ${dirname14(node)}` };
15794
15874
  mkdirSync16(versionsDir(), { recursive: true });
15795
- const staging = join26(versionsDir(), `.${version}.staging-${process.pid}`);
15875
+ const staging = join27(versionsDir(), `.${version}.staging-${process.pid}`);
15796
15876
  rmSync14(staging, { recursive: true, force: true });
15797
15877
  const installed = await run(
15798
15878
  npm,
@@ -15809,7 +15889,7 @@ async function stageVersion(version, harnesses, deps = {}) {
15809
15889
  ],
15810
15890
  {
15811
15891
  // npm's install scripts run on this Node, not whichever `node` is first on PATH.
15812
- env: { ...process.env, PATH: `${dirname12(node)}:${process.env.PATH ?? ""}` },
15892
+ env: { ...process.env, PATH: `${dirname14(node)}:${process.env.PATH ?? ""}` },
15813
15893
  timeoutMs: 15 * 6e4
15814
15894
  }
15815
15895
  );
@@ -15821,7 +15901,7 @@ async function stageVersion(version, harnesses, deps = {}) {
15821
15901
  return { ok: false, reason: `version ${version} isn't published yet`, unpublished: true };
15822
15902
  return { ok: false, reason: `npm couldn't install it: ${lastLine(installed.stderr)}` };
15823
15903
  }
15824
- const root = join26(staging, "node_modules", "@cabane", "companion");
15904
+ const root = join27(staging, "node_modules", "@cabane", "companion");
15825
15905
  const verdict = await verifyInstall(root, version, harnesses, { run, execPath: node });
15826
15906
  if (verdict) {
15827
15907
  rmSync14(staging, { recursive: true, force: true });
@@ -15833,7 +15913,7 @@ async function stageVersion(version, harnesses, deps = {}) {
15833
15913
  }
15834
15914
  async function verifyInstall(root, version, harnesses, deps) {
15835
15915
  if (packageVersion(root) !== version) return `the installed package isn't version ${version}`;
15836
- const answered = await deps.run(deps.execPath, [join26(root, "dist", "bin.js"), "--version"], {
15916
+ const answered = await deps.run(deps.execPath, [join27(root, "dist", "bin.js"), "--version"], {
15837
15917
  timeoutMs: 6e4,
15838
15918
  env: { ...process.env, CABANE_LAUNCH_SELF: "1" }
15839
15919
  });
@@ -15841,7 +15921,7 @@ async function verifyInstall(root, version, harnesses, deps) {
15841
15921
  return `it doesn't start: ${lastLine(answered.stderr) || "no version printed"}`;
15842
15922
  }
15843
15923
  if (harnesses.length === 0) return null;
15844
- const probed = await deps.run(deps.execPath, [join26(root, "dist", "postinstall.js"), "--json"], {
15924
+ const probed = await deps.run(deps.execPath, [join27(root, "dist", "postinstall.js"), "--json"], {
15845
15925
  timeoutMs: 6e4
15846
15926
  });
15847
15927
  let resolutions = {};
@@ -16419,7 +16499,7 @@ var CompanionSupervisor = class {
16419
16499
  this.versionSkewWarned = true;
16420
16500
  this.log.warn(
16421
16501
  { companionVersion: COMPANION_VERSION, serverVersion },
16422
- "This companion and Cabane are on different versions. If turns fail, update with npm i -g @cabane/companion@latest"
16502
+ `This companion and Cabane are on different versions. If turns fail, update with ${cabane("update")}`
16423
16503
  );
16424
16504
  }
16425
16505
  // Pull assignments and reconcile the live runner set against them. Re-entrancy
@@ -16648,7 +16728,8 @@ var CompanionSupervisor = class {
16648
16728
  bundled: this.bundledStatus("claude-code")
16649
16729
  },
16650
16730
  codex: { intended: isCodexEnabled(this.config), bundled: this.bundledStatus("codex") },
16651
- install: companionInstall()
16731
+ install: companionInstall(),
16732
+ managed: repairRoute() === "managed"
16652
16733
  });
16653
16734
  }
16654
16735
  // A missing binary is reported ONCE, and again only when the answer CHANGES,
@@ -17607,9 +17688,9 @@ function handleUncaught(log, err, origin) {
17607
17688
  // src/crash-marker.ts
17608
17689
  init_config();
17609
17690
  import { existsSync as existsSync18, mkdirSync as mkdirSync17, readFileSync as readFileSync15, rmSync as rmSync15, writeFileSync as writeFileSync11 } from "fs";
17610
- import { join as join27 } from "path";
17691
+ import { join as join28 } from "path";
17611
17692
  function crashMarkerPath() {
17612
- return join27(cabaneDir(), "last-error.json");
17693
+ return join28(cabaneDir(), "last-error.json");
17613
17694
  }
17614
17695
  function recordCrash(rec2) {
17615
17696
  try {
@@ -17800,7 +17881,7 @@ import {
17800
17881
  writeFileSync as writeFileSync12
17801
17882
  } from "fs";
17802
17883
  import { homedir as homedir7, userInfo as userInfo3 } from "os";
17803
- import { dirname as dirname13, join as join28, sep as sep5 } from "path";
17884
+ import { dirname as dirname15, join as join29, sep as sep5 } from "path";
17804
17885
  function servicePlatform(platform = process.platform) {
17805
17886
  if (platform === "linux") return "systemd";
17806
17887
  if (platform === "darwin") return "launchd";
@@ -17818,15 +17899,15 @@ function serviceNode(execPath = process.execPath) {
17818
17899
  return existsSync19(pinnedNodePath()) ? pinnedNodePath() : execPath;
17819
17900
  }
17820
17901
  function servicePaths(platform, env = process.env, home = homedir7(), node = serviceNode()) {
17821
- const unitFile = platform === "systemd" ? join28(
17822
- env.XDG_CONFIG_HOME || join28(home, ".config"),
17902
+ const unitFile = platform === "systemd" ? join29(
17903
+ env.XDG_CONFIG_HOME || join29(home, ".config"),
17823
17904
  "systemd",
17824
17905
  "user",
17825
17906
  `${serviceName(env)}.service`
17826
- ) : join28(home, "Library", "LaunchAgents", `${launchdLabel(env)}.plist`);
17907
+ ) : join29(home, "Library", "LaunchAgents", `${launchdLabel(env)}.plist`);
17827
17908
  return {
17828
17909
  unitFile,
17829
- envFile: join28(cabaneDir(), "service.env"),
17910
+ envFile: join29(cabaneDir(), "service.env"),
17830
17911
  node,
17831
17912
  entry: bootGuardPath()
17832
17913
  };
@@ -17893,7 +17974,7 @@ function renderLaunchdPlist(paths, env = process.env, servicePath) {
17893
17974
  " <key>ThrottleInterval</key><integer>5</integer>",
17894
17975
  // The companion writes its own log; the console copy would only grow a second one.
17895
17976
  " <key>StandardOutPath</key><string>/dev/null</string>",
17896
- ` <key>StandardErrorPath</key><string>${xml(join28(cabaneDir(), "service-error.log"))}</string>`,
17977
+ ` <key>StandardErrorPath</key><string>${xml(join29(cabaneDir(), "service-error.log"))}</string>`,
17897
17978
  "</dict>",
17898
17979
  "</plist>",
17899
17980
  ""
@@ -17948,11 +18029,11 @@ async function installService(deps = {}) {
17948
18029
  rewriteScriptWrappers();
17949
18030
  const paths = servicePaths(platform, env, deps.home, node);
17950
18031
  const servicePath = await loginShellPath(run, env);
17951
- mkdirSync18(dirname13(paths.envFile), { recursive: true });
18032
+ mkdirSync18(dirname15(paths.envFile), { recursive: true });
17952
18033
  const existing = existsSync19(paths.envFile) ? readFileSync16(paths.envFile, "utf8") : "";
17953
18034
  writeFileSync12(paths.envFile, withServicePath(existing, servicePath), { mode: 384 });
17954
18035
  chmodSync7(paths.envFile, 384);
17955
- mkdirSync18(dirname13(paths.unitFile), { recursive: true });
18036
+ mkdirSync18(dirname15(paths.unitFile), { recursive: true });
17956
18037
  const outcome = await writeAndStartUnit(platform, paths, env, servicePath, run);
17957
18038
  if (!outcome.ok) await removeUnit(platform, paths, env, run);
17958
18039
  return outcome;
@@ -18033,15 +18114,15 @@ async function seedManagedInstall(deps) {
18033
18114
  }
18034
18115
  const target = versionPackageRoot(version);
18035
18116
  if (!existsSync19(target)) {
18036
- const staging = join28(versionsDir(), `.${version}.seed-${process.pid}`);
18117
+ const staging = join29(versionsDir(), `.${version}.seed-${process.pid}`);
18037
18118
  rmSync16(staging, { recursive: true, force: true });
18038
18119
  try {
18039
- cpSync(own, join28(staging, "node_modules", "@cabane", "companion"), {
18120
+ cpSync(own, join29(staging, "node_modules", "@cabane", "companion"), {
18040
18121
  recursive: true,
18041
18122
  verbatimSymlinks: true
18042
18123
  });
18043
- rmSync16(join28(versionsDir(), version), { recursive: true, force: true });
18044
- renameSync12(staging, join28(versionsDir(), version));
18124
+ rmSync16(join29(versionsDir(), version), { recursive: true, force: true });
18125
+ renameSync12(staging, join29(versionsDir(), version));
18045
18126
  } catch (err) {
18046
18127
  rmSync16(staging, { recursive: true, force: true });
18047
18128
  return {
@@ -18052,7 +18133,7 @@ async function seedManagedInstall(deps) {
18052
18133
  }
18053
18134
  const unrunnable = await verifyInstall(target, version, [], deps);
18054
18135
  if (unrunnable) {
18055
- rmSync16(join28(versionsDir(), version), { recursive: true, force: true });
18136
+ rmSync16(join29(versionsDir(), version), { recursive: true, force: true });
18056
18137
  return { ok: false, reason: `The copy of Cabane for the service can't run: ${unrunnable}.` };
18057
18138
  }
18058
18139
  pointCurrentAt(target);
@@ -18134,9 +18215,9 @@ function userName() {
18134
18215
  return process.env.USER ?? "";
18135
18216
  }
18136
18217
  }
18137
- function rewriteScriptWrappers(dir2 = join28(cabaneDir(), "bin")) {
18218
+ function rewriteScriptWrappers(dir2 = join29(cabaneDir(), "bin")) {
18138
18219
  for (const name of ["cabane", "cabane-companion"]) {
18139
- const path = join28(dir2, name);
18220
+ const path = join29(dir2, name);
18140
18221
  let text;
18141
18222
  try {
18142
18223
  text = readFileSync16(path, "utf8");
@@ -19129,10 +19210,11 @@ async function logs(opts = {}) {
19129
19210
 
19130
19211
  // src/commands/status.ts
19131
19212
  init_bundled_binaries();
19213
+ init_install_kind();
19132
19214
  init_cabane_command();
19133
- import { readdirSync as readdirSync7, realpathSync as realpathSync7, existsSync as existsSync20 } from "fs";
19215
+ import { readdirSync as readdirSync7, realpathSync as realpathSync8, existsSync as existsSync20 } from "fs";
19134
19216
  import { homedir as homedir8 } from "os";
19135
- import { dirname as dirname14, join as join29 } from "path";
19217
+ import { dirname as dirname16, join as join30 } from "path";
19136
19218
  init_config();
19137
19219
  init_sandbox_policy();
19138
19220
  init_secrets();
@@ -19198,10 +19280,10 @@ function versionLine() {
19198
19280
  const install = companionInstall();
19199
19281
  const own = installedCommandPath({ install });
19200
19282
  if (install === "script") {
19201
- return `Cabane ${COMPANION_VERSION} \xB7 installed by the install script (${tildePath(join29(homedir8(), ".cabane"))})`;
19283
+ return `Cabane ${COMPANION_VERSION} \xB7 installed by the install script (${tildePath(join30(homedir8(), ".cabane"))})`;
19202
19284
  }
19203
19285
  if (own)
19204
- return `Cabane ${COMPANION_VERSION} \xB7 installed with npm (${tildePath(dirname14(dirname14(own)))})`;
19286
+ return `Cabane ${COMPANION_VERSION} \xB7 installed with npm (${tildePath(dirname16(dirname16(own)))})`;
19205
19287
  return `Cabane ${COMPANION_VERSION}`;
19206
19288
  }
19207
19289
  function otherInstallLines(running) {
@@ -19218,13 +19300,13 @@ function otherInstallLines(running) {
19218
19300
  function otherInstall(install, own) {
19219
19301
  const real = (p) => {
19220
19302
  try {
19221
- return realpathSync7(p);
19303
+ return realpathSync8(p);
19222
19304
  } catch {
19223
19305
  return null;
19224
19306
  }
19225
19307
  };
19226
19308
  if (install === "npm") {
19227
- const script = join29(
19309
+ const script = join30(
19228
19310
  homedir8(),
19229
19311
  ".cabane",
19230
19312
  "node",
@@ -19233,12 +19315,12 @@ function otherInstall(install, own) {
19233
19315
  "@cabane",
19234
19316
  "companion"
19235
19317
  );
19236
- return existsSync20(script) ? join29(homedir8(), ".cabane") : null;
19318
+ return existsSync20(script) ? join30(homedir8(), ".cabane") : null;
19237
19319
  }
19238
19320
  const found = firstOnPath("cabane-companion", process.env.PATH);
19239
19321
  if (!found) return null;
19240
- const ownAlias = own ? real(join29(dirname14(own), "cabane-companion")) : null;
19241
- return real(found) === ownAlias ? null : dirname14(found);
19322
+ const ownAlias = own ? real(join30(dirname16(own), "cabane-companion")) : null;
19323
+ return real(found) === ownAlias ? null : dirname16(found);
19242
19324
  }
19243
19325
  async function liveState() {
19244
19326
  const running = readLiveRuntimeState();
@@ -19298,7 +19380,7 @@ function signInText(runtime, status2, version) {
19298
19380
  case "signed_out":
19299
19381
  return `signed out \u2014 run ${loginCommand(runtime)}`;
19300
19382
  case "bundle_missing":
19301
- return `this install is missing it \u2014 repair with ${repairCommands()[0]}`;
19383
+ return `this install is missing it \u2014 repair with ${repairSteps()}${repairCost() ? `. ${repairCost()}` : ""}`;
19302
19384
  case "check_failed":
19303
19385
  return "connected, but its sign-in couldn't be checked";
19304
19386
  default:
@@ -19357,6 +19439,8 @@ init_config();
19357
19439
  init_term();
19358
19440
  init_version2();
19359
19441
  init_cabane_command();
19442
+ init_bundled_binaries();
19443
+ init_install_kind();
19360
19444
  var UPDATE_TIMEOUT_MS = 20 * 6e4;
19361
19445
  async function update(opts = {}) {
19362
19446
  const running = readLiveRuntimeState();
@@ -19374,13 +19458,13 @@ async function update(opts = {}) {
19374
19458
  write(`${INDENT}${r.ok ? tick(r.message) : r.message}`);
19375
19459
  return;
19376
19460
  }
19377
- const cfg2 = loadConfig();
19378
- if (!cfg2) {
19461
+ const cfg = loadConfig();
19462
+ if (!cfg) {
19379
19463
  write(`${INDENT}This computer isn't set up yet. Run ${cabane("start")} first.`);
19380
19464
  process.exitCode = 1;
19381
19465
  return;
19382
19466
  }
19383
- saveConfig({ ...cfg2, update: { auto } });
19467
+ saveConfig({ ...cfg, update: { auto } });
19384
19468
  write(
19385
19469
  INDENT + tick(
19386
19470
  auto ? "Cabane will keep itself up to date. Updates install between turns when they can, and roll back if they fail." : "Cabane won't update itself."
@@ -19388,6 +19472,29 @@ async function update(opts = {}) {
19388
19472
  );
19389
19473
  return;
19390
19474
  }
19475
+ const harnesses = connectedHarnesses();
19476
+ const latest = readUpdateState().server?.latest;
19477
+ if (!latest || compareVersions(latest, COMPANION_VERSION) <= 0) {
19478
+ const repair = await repairOwnCopy({ running: Boolean(running) });
19479
+ if (repair.kind === "running") {
19480
+ write(
19481
+ `${INDENT}This copy of Cabane ${COMPANION_VERSION} is incomplete: ${repair.reason}. Run ${cabane("stop")}, then ${cabane("update")} again to repair it. Stopping ends any turn in progress.`
19482
+ );
19483
+ process.exitCode = 1;
19484
+ return;
19485
+ }
19486
+ if (repair.kind === "failed") {
19487
+ write(`${INDENT}Couldn't repair Cabane ${COMPANION_VERSION}: ${repair.reason}.`);
19488
+ process.exitCode = 1;
19489
+ return;
19490
+ }
19491
+ if (repair.kind === "repaired") {
19492
+ write(
19493
+ INDENT + tick(`Repaired Cabane ${COMPANION_VERSION}. Start it with ${cabane("start")}`)
19494
+ );
19495
+ return;
19496
+ }
19497
+ }
19391
19498
  if (running?.socket) {
19392
19499
  const r = await controlRequest(
19393
19500
  running.socket,
@@ -19398,7 +19505,6 @@ async function update(opts = {}) {
19398
19505
  if (!r.ok) process.exitCode = 1;
19399
19506
  return;
19400
19507
  }
19401
- const latest = readUpdateState().server?.latest;
19402
19508
  if (!latest) {
19403
19509
  write(
19404
19510
  `${INDENT}Cabane hasn't said which version is latest yet. Run ${cabane("start")}, and it updates from there.`
@@ -19410,11 +19516,6 @@ async function update(opts = {}) {
19410
19516
  write(INDENT + tick(`Cabane ${COMPANION_VERSION} is up to date.`));
19411
19517
  return;
19412
19518
  }
19413
- const cfg = loadConfig();
19414
- const harnesses = cfg ? [
19415
- ...isClaudeCodeConnected(cfg) ? ["claude-code"] : [],
19416
- ...isCodexEnabled(cfg) ? ["codex"] : []
19417
- ] : [];
19418
19519
  write(
19419
19520
  `${INDENT}Installing Cabane ${latest}. It brings its own copies of the agents, so this can take a few minutes.`
19420
19521
  );
@@ -19454,6 +19555,31 @@ async function update(opts = {}) {
19454
19555
  pointCurrentAt(outcome.root);
19455
19556
  write(INDENT + tick(`Updated to ${latest}. Start it with ${cabane("start")}`));
19456
19557
  }
19558
+ function bundledHere(platform, arch) {
19559
+ return ["claude-code", ...codexPlatformPackage(platform, arch) ? ["codex"] : []];
19560
+ }
19561
+ function connectedHarnesses() {
19562
+ const cfg = loadConfig();
19563
+ if (!cfg) return [];
19564
+ return [
19565
+ ...isClaudeCodeConnected(cfg) ? ["claude-code"] : [],
19566
+ ...isCodexEnabled(cfg) ? ["codex"] : []
19567
+ ];
19568
+ }
19569
+ async function repairOwnCopy(deps) {
19570
+ if ((deps.route ?? repairRoute()) !== "managed") return { kind: "healthy" };
19571
+ const harnesses = deps.harnesses ?? bundledHere(deps.platform ?? process.platform, deps.arch ?? process.arch);
19572
+ const root = deps.root ?? ownPackageRoot();
19573
+ const version = deps.version ?? COMPANION_VERSION;
19574
+ const verify = deps.verify ?? ((r, v, h) => verifyInstall(r, v, h, { run: runProcess, execPath: process.execPath }));
19575
+ const broken = await verify(root, version, harnesses);
19576
+ if (!broken) return { kind: "healthy" };
19577
+ if (deps.running) return { kind: "running", reason: broken };
19578
+ const outcome = await (deps.stage ?? stageVersion)(version, harnesses);
19579
+ if (!outcome.ok) return { kind: "failed", reason: outcome.reason };
19580
+ (deps.point ?? pointCurrentAt)(outcome.root);
19581
+ return { kind: "repaired" };
19582
+ }
19457
19583
 
19458
19584
  // src/commands/transcript.ts
19459
19585
  init_cabane_command();
@@ -19461,7 +19587,7 @@ init_errors();
19461
19587
  init_src();
19462
19588
  init_transcript_writer();
19463
19589
  import { existsSync as existsSync21, readFileSync as readFileSync17, readdirSync as readdirSync8 } from "fs";
19464
- import { isAbsolute, join as join30 } from "path";
19590
+ import { isAbsolute, join as join31 } from "path";
19465
19591
  async function transcript(opts = {}) {
19466
19592
  const dir2 = transcriptsDir();
19467
19593
  if (opts.follow) {
@@ -19478,7 +19604,7 @@ async function transcript(opts = {}) {
19478
19604
  process.stdout.write(emptyMessage(dir2));
19479
19605
  return;
19480
19606
  }
19481
- process.stdout.write(renderFile(join30(dir2, newest)) + "\n");
19607
+ process.stdout.write(renderFile(join31(dir2, newest)) + "\n");
19482
19608
  return;
19483
19609
  }
19484
19610
  printList(dir2);
@@ -19561,7 +19687,7 @@ function isComplete(content) {
19561
19687
  async function followTranscripts(dir2) {
19562
19688
  const follower = new TranscriptFollower({
19563
19689
  listFiles: () => listFiles(dir2),
19564
- read: (f) => readFileSync17(join30(dir2, f), "utf8"),
19690
+ read: (f) => readFileSync17(join31(dir2, f), "utf8"),
19565
19691
  write: (s) => process.stdout.write(s),
19566
19692
  // CSI: cursor up `n` lines, then erase from cursor to end of screen.
19567
19693
  clearLines: (n) => process.stdout.write(`\x1B[${n}A\x1B[0J`),
@@ -19601,7 +19727,7 @@ function printList(dir2) {
19601
19727
 
19602
19728
  `);
19603
19729
  for (const f of files.slice(0, 20)) {
19604
- const { meta, outcome } = peek(join30(dir2, f));
19730
+ const { meta, outcome } = peek(join31(dir2, f));
19605
19731
  const when = fmtTime(rec(meta)?.ts);
19606
19732
  const ws = str2(rec(meta)?.workspaceSlug);
19607
19733
  const o = rec(outcome);
@@ -19639,10 +19765,10 @@ function resolveTarget(dir2, target) {
19639
19765
  if (existsSync21(target)) return target;
19640
19766
  throw new CompanionError(`no transcript at ${target}.`);
19641
19767
  }
19642
- const exact = join30(dir2, target);
19768
+ const exact = join31(dir2, target);
19643
19769
  if (existsSync21(exact)) return exact;
19644
19770
  const matches = listFiles(dir2).filter((f) => f.includes(target));
19645
- if (matches.length === 1) return join30(dir2, matches[0]);
19771
+ if (matches.length === 1) return join31(dir2, matches[0]);
19646
19772
  if (matches.length === 0) {
19647
19773
  throw new CompanionError(
19648
19774
  `no transcript matching "${target}" in ${dir2}. Run \`${cabane("transcript")}\` to list them.`
@@ -19806,6 +19932,8 @@ They are written per dispatch while \`${cabane("start")}\` is running.
19806
19932
  // src/cli.ts
19807
19933
  init_errors();
19808
19934
  init_version2();
19935
+ init_cabane_command();
19936
+ rememberInvokedCommand();
19809
19937
  var program = new Command();
19810
19938
  program.name("cabane").description("Run your Cabane agents on this computer, with Claude Code, Codex or OpenCode.").version(COMPANION_VERSION);
19811
19939
  program.command("pair").description(