@cabane/companion 0.6.236 → 0.6.238
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -8
- package/dist/bin.js +5 -1
- package/dist/cli.js +129 -294
- package/dist/launch.js +3 -4
- package/dist/postinstall.js +338 -16
- package/package.json +1 -3
- package/dist/pairing-config.js +0 -566
- package/dist/runtime.js +0 -9642
package/dist/launch.js
CHANGED
|
@@ -133,7 +133,7 @@ var companionConfigSchema = z2.object({
|
|
|
133
133
|
// the rest of the config; `pair` always writes one.
|
|
134
134
|
deviceToken: z2.string().optional(),
|
|
135
135
|
// Identity hints, learned from the device flow and refreshed on the first
|
|
136
|
-
// assignments pull. Cosmetic — used for `status
|
|
136
|
+
// assignments pull. Cosmetic — used for `status` display only.
|
|
137
137
|
deviceId: z2.string().optional(),
|
|
138
138
|
deviceLabel: z2.string().optional(),
|
|
139
139
|
// Optional per-agent machine-local overrides (cwd / prepareHook), keyed by
|
|
@@ -145,9 +145,8 @@ var companionConfigSchema = z2.object({
|
|
|
145
145
|
// silent roster: an agent nobody listed gets no hook and no error, and starts
|
|
146
146
|
// in the operator's `$HOME`.
|
|
147
147
|
prepareHook: prepareHookSchema.optional(),
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
// startup. `logLevel` is pino's level, live-editable.
|
|
148
|
+
// Nothing reads either dashboard setting any more; both stay parseable so an
|
|
149
|
+
// older config file still loads. `logLevel` is pino's level, live-editable.
|
|
151
150
|
dashboardPort: z2.number().int().min(1).max(65535).optional(),
|
|
152
151
|
autoOpen: z2.boolean().optional(),
|
|
153
152
|
logLevel: z2.enum(["warn", "info", "debug"]).optional(),
|
package/dist/postinstall.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/bundled-binaries.ts
|
|
2
2
|
import { realpathSync as realpathSync3, statSync } from "fs";
|
|
3
3
|
import { createRequire } from "module";
|
|
4
|
-
import { dirname as
|
|
4
|
+
import { dirname as dirname4, join as join5 } from "path";
|
|
5
5
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
6
6
|
|
|
7
7
|
// src/cabane-command.ts
|
|
@@ -14,7 +14,13 @@ import { realpathSync } from "fs";
|
|
|
14
14
|
import { homedir } from "os";
|
|
15
15
|
import { dirname, join, sep } from "path";
|
|
16
16
|
import { fileURLToPath } from "url";
|
|
17
|
-
|
|
17
|
+
function installScriptCommand(address) {
|
|
18
|
+
return `curl -fsSL ${address.replace(/\/+$/, "")}/install.sh | bash`;
|
|
19
|
+
}
|
|
20
|
+
function installLineWords(install = companionInstall()) {
|
|
21
|
+
const line = install === "script" ? "installer line" : "npm line";
|
|
22
|
+
return `the ${line} from Your settings \u2192 Devices \u2192 Add a device in your Cabane`;
|
|
23
|
+
}
|
|
18
24
|
function companionInstall(execPath = process.execPath, home = homedir()) {
|
|
19
25
|
const real = (path) => {
|
|
20
26
|
try {
|
|
@@ -57,6 +63,307 @@ function cabane(rest) {
|
|
|
57
63
|
return `cabane ${rest}`;
|
|
58
64
|
}
|
|
59
65
|
|
|
66
|
+
// src/config.ts
|
|
67
|
+
import {
|
|
68
|
+
chmodSync,
|
|
69
|
+
mkdirSync,
|
|
70
|
+
readFileSync as readFileSync2,
|
|
71
|
+
renameSync,
|
|
72
|
+
rmSync,
|
|
73
|
+
writeFileSync,
|
|
74
|
+
existsSync as existsSync3
|
|
75
|
+
} from "fs";
|
|
76
|
+
import { homedir as homedir5, userInfo } from "os";
|
|
77
|
+
import { dirname as dirname3, isAbsolute, join as join4 } from "path";
|
|
78
|
+
import { z as z2 } from "zod";
|
|
79
|
+
|
|
80
|
+
// src/errors.ts
|
|
81
|
+
var CompanionError = class extends Error {
|
|
82
|
+
constructor(message) {
|
|
83
|
+
super(message);
|
|
84
|
+
this.name = "CompanionError";
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
var ConfigError = class extends CompanionError {
|
|
88
|
+
constructor(message) {
|
|
89
|
+
super(message);
|
|
90
|
+
this.name = "ConfigError";
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// src/pairing.ts
|
|
95
|
+
function isAllowedBaseUrl(raw) {
|
|
96
|
+
let url;
|
|
97
|
+
try {
|
|
98
|
+
url = new URL(raw);
|
|
99
|
+
} catch {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
if (url.protocol === "https:") return true;
|
|
103
|
+
if (url.protocol === "http:") {
|
|
104
|
+
const host = url.hostname.toLowerCase();
|
|
105
|
+
return host === "localhost" || host.endsWith(".localhost") || host === "127.0.0.1" || host === "::1" || host === "[::1]";
|
|
106
|
+
}
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// src/prepare-hook.ts
|
|
111
|
+
import { spawn } from "child_process";
|
|
112
|
+
import { z } from "zod";
|
|
113
|
+
var prepareHookSchema = z.object({
|
|
114
|
+
command: z.string().min(1),
|
|
115
|
+
args: z.array(z.string()).optional(),
|
|
116
|
+
// Extra env handed to the hook process itself (merged over process.env).
|
|
117
|
+
env: z.record(z.string(), z.string()).optional(),
|
|
118
|
+
// Wall-clock cap for the hook. Provisioning is slow (minutes), so the
|
|
119
|
+
// default is generous; a hook that hangs past this is killed and the turn
|
|
120
|
+
// fails with a clear timeout message rather than pinning the companion.
|
|
121
|
+
timeoutMs: z.number().int().positive().optional()
|
|
122
|
+
}).strict();
|
|
123
|
+
var DEFAULT_TIMEOUT_MS = 10 * 6e4;
|
|
124
|
+
var prepareResultSchema = z.object({
|
|
125
|
+
cwd: z.string().min(1),
|
|
126
|
+
env: z.record(z.string(), z.string()).optional()
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// src/config.ts
|
|
130
|
+
var HARNESS_RUNTIMES = ["claude-code", "codex", "opencode"];
|
|
131
|
+
var realHomeCache;
|
|
132
|
+
function realAccountHome() {
|
|
133
|
+
if (realHomeCache === void 0) {
|
|
134
|
+
try {
|
|
135
|
+
realHomeCache = userInfo().homedir;
|
|
136
|
+
} catch {
|
|
137
|
+
realHomeCache = null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return realHomeCache;
|
|
141
|
+
}
|
|
142
|
+
function cabaneDir() {
|
|
143
|
+
const dir = join4(process.env.CABANE_COMPANION_HOME || homedir5(), ".cabane");
|
|
144
|
+
if (process.env.VITEST) {
|
|
145
|
+
const real = realAccountHome();
|
|
146
|
+
if (real && dir === join4(real, ".cabane")) {
|
|
147
|
+
throw new Error(
|
|
148
|
+
`cabaneDir() resolved to the real ${dir} during a test run. Companion tests must swap process.env.HOME to a tmp dir before touching the config dir; this guard prevents wiping the operator's real companion config (see apps/companion/test/setup-home.ts).`
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return dir;
|
|
153
|
+
}
|
|
154
|
+
function configPath() {
|
|
155
|
+
return join4(cabaneDir(), "config.json");
|
|
156
|
+
}
|
|
157
|
+
var localAgentConfigSchema = z2.object({
|
|
158
|
+
cwd: z2.string().optional(),
|
|
159
|
+
prepareHook: prepareHookSchema.optional(),
|
|
160
|
+
// The Claude Code auto-memory escape hatch. Auto-memory is forced OFF by
|
|
161
|
+
// default (memory belongs in the Cabane workspace, and a shared companion
|
|
162
|
+
// would pool one cwd-keyed memory dir across users). On a companion you run
|
|
163
|
+
// yourself, `claudeCode: { autoMemory: true }` hands auto-memory back to your
|
|
164
|
+
// own `~/.claude/settings.json` — Cabane then stops
|
|
165
|
+
// injecting the off switch and your normal Claude Code memory workflow applies
|
|
166
|
+
// (the user and project settings load on every Claude Code turn).
|
|
167
|
+
claudeCode: z2.object({ autoMemory: z2.boolean().optional() }).strict().optional()
|
|
168
|
+
}).strict();
|
|
169
|
+
var agentsConfigSchema = z2.record(z2.string(), localAgentConfigSchema);
|
|
170
|
+
function isRootPath(value) {
|
|
171
|
+
return isAbsolute(value) || value === "~" || value.startsWith("~/");
|
|
172
|
+
}
|
|
173
|
+
var companionConfigSchema = z2.object({
|
|
174
|
+
// https-enforced (loopback exempt), so a hand-edited config can't put the
|
|
175
|
+
// device token and prompt on a plaintext channel.
|
|
176
|
+
baseUrl: z2.string().url().refine(isAllowedBaseUrl, {
|
|
177
|
+
message: "baseUrl must be https (loopback http is allowed for local dev only)"
|
|
178
|
+
}),
|
|
179
|
+
// The `cabdev_` device token plaintext — the companion's one durable credential,
|
|
180
|
+
// presented as `Authorization: Bearer <token>` on the device-facing pull +
|
|
181
|
+
// heartbeat endpoints. Optional so `cabane-companion logout` can strip it (a
|
|
182
|
+
// "paired but logged out" state the supervisor refuses to run) while keeping
|
|
183
|
+
// the rest of the config; `pair` always writes one.
|
|
184
|
+
deviceToken: z2.string().optional(),
|
|
185
|
+
// Identity hints, learned from the device flow and refreshed on the first
|
|
186
|
+
// assignments pull. Cosmetic — used for `status` display only.
|
|
187
|
+
deviceId: z2.string().optional(),
|
|
188
|
+
deviceLabel: z2.string().optional(),
|
|
189
|
+
// Optional per-agent machine-local overrides (cwd / prepareHook), keyed by
|
|
190
|
+
// agentId / username / `slug/username`. Hand-added by the operator; the companion
|
|
191
|
+
// never writes this (it only persists credentials + the device, elsewhere).
|
|
192
|
+
agents: agentsConfigSchema.optional(),
|
|
193
|
+
// The DEVICE-LEVEL prepare hook, for every agent assigned here unless its own
|
|
194
|
+
// `agents` row names one. Per-agent hooks alone make the config a second
|
|
195
|
+
// silent roster: an agent nobody listed gets no hook and no error, and starts
|
|
196
|
+
// in the operator's `$HOME`.
|
|
197
|
+
prepareHook: prepareHookSchema.optional(),
|
|
198
|
+
// Nothing reads either dashboard setting any more; both stay parseable so an
|
|
199
|
+
// older config file still loads. `logLevel` is pino's level, live-editable.
|
|
200
|
+
dashboardPort: z2.number().int().min(1).max(65535).optional(),
|
|
201
|
+
autoOpen: z2.boolean().optional(),
|
|
202
|
+
logLevel: z2.enum(["warn", "info", "debug"]).optional(),
|
|
203
|
+
logFormat: z2.enum(["human", "json"]).optional(),
|
|
204
|
+
// Where this device's `opencode serve` answers, when it isn't the default
|
|
205
|
+
// address (`DEFAULT_OPENCODE_SERVER_URL`). Detection probes this one; a serve
|
|
206
|
+
// that answers is connected. Auth is opencode's own; Cabane never sees
|
|
207
|
+
// provider keys.
|
|
208
|
+
opencode: z2.object({
|
|
209
|
+
serverUrl: z2.string().url()
|
|
210
|
+
}).strict().optional(),
|
|
211
|
+
// The one knob on agents: an allowlist. Agents are detected, never offered
|
|
212
|
+
// (`harness-detect.ts`): whatever is signed in here is connected. Someone with
|
|
213
|
+
// Codex on the device who doesn't want Cabane to see it lists the ones it may:
|
|
214
|
+
// `"harnesses": ["claude-code"]`. Absent means all three.
|
|
215
|
+
harnesses: z2.array(z2.enum(HARNESS_RUNTIMES)).optional(),
|
|
216
|
+
// Whether this companion keeps itself current: installs the version Cabane names, between
|
|
217
|
+
// turns, and rolls back one that doesn't come up (`updater.ts`). Absent means never asked,
|
|
218
|
+
// which `start` offers; off either way until someone says yes.
|
|
219
|
+
update: z2.object({
|
|
220
|
+
auto: z2.boolean()
|
|
221
|
+
}).strict().optional(),
|
|
222
|
+
// The answer to `start`'s "start at boot?" when it was no, so it isn't asked again.
|
|
223
|
+
// `cabane service install` still installs it.
|
|
224
|
+
serviceDeclined: z2.boolean().optional(),
|
|
225
|
+
// Where agents stand on this device. Every (agent, conversation) pair gets a directory under it,
|
|
226
|
+
// and an agent with no folder of its own and no prepare hook works there
|
|
227
|
+
// (`turn-execution.ts`, `prepareEnvironment`). Default `~/.cabane/work`: hidden, so pairing a
|
|
228
|
+
// laptop creates no new visible folder; set it to make the work visible.
|
|
229
|
+
root: z2.string().min(1).refine(isRootPath, { message: "must be an absolute path, or start with ~/" }).optional(),
|
|
230
|
+
// An argv prefix the operator puts in front of every turn's runner: the companion starts
|
|
231
|
+
// `<prefix…> <its own run-turn command…>` instead of the runner bare. What the prefix does
|
|
232
|
+
// is the operator's (a container or VM per turn, say); the companion knows nothing of it
|
|
233
|
+
// beyond handing the runner over, with the turn's inputs in its environment and the
|
|
234
|
+
// credential and secrets on its stdin (`runner/launch.ts`, `wrapped`). The README lists
|
|
235
|
+
// what a wrapper can rely on.
|
|
236
|
+
runner: z2.object({
|
|
237
|
+
command: z2.array(z2.string().min(1)).min(1),
|
|
238
|
+
// The external wrapper supplies only broker placeholders and removes
|
|
239
|
+
// credential mounts. Do not hand provider logins to that runner on stdin.
|
|
240
|
+
brokeredCredentials: z2.boolean().optional()
|
|
241
|
+
}).strict().optional()
|
|
242
|
+
});
|
|
243
|
+
var RETIRED_ROW_KEYS = ["sandbox"];
|
|
244
|
+
var RETIRED_HOOK_KEYS = ["validateCached", "failureReporter"];
|
|
245
|
+
function isObject(value) {
|
|
246
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
247
|
+
}
|
|
248
|
+
function splitRetired(raw) {
|
|
249
|
+
const keys = [];
|
|
250
|
+
const without = (obj, names, path) => {
|
|
251
|
+
const out = {};
|
|
252
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
253
|
+
if (names.includes(k)) keys.push(`${path}${k}`);
|
|
254
|
+
else out[k] = v;
|
|
255
|
+
}
|
|
256
|
+
return out;
|
|
257
|
+
};
|
|
258
|
+
const hook = (row, path) => isObject(row.prepareHook) ? { ...row, prepareHook: without(row.prepareHook, RETIRED_HOOK_KEYS, `${path}prepareHook.`) } : row;
|
|
259
|
+
const kept = hook(without(raw, ["runners"], ""), "");
|
|
260
|
+
if (isObject(kept.agents)) {
|
|
261
|
+
const agents = {};
|
|
262
|
+
for (const [key, row] of Object.entries(kept.agents)) {
|
|
263
|
+
const path = `agents.${key}.`;
|
|
264
|
+
agents[key] = isObject(row) ? hook(without(row, RETIRED_ROW_KEYS, path), path) : row;
|
|
265
|
+
}
|
|
266
|
+
kept.agents = agents;
|
|
267
|
+
}
|
|
268
|
+
return { keys, kept };
|
|
269
|
+
}
|
|
270
|
+
function withoutRetiredKeys(raw) {
|
|
271
|
+
return isObject(raw) ? splitRetired(raw).kept : raw;
|
|
272
|
+
}
|
|
273
|
+
var legacyOfferSchema = z2.object({
|
|
274
|
+
claudeCode: z2.object({ enabled: z2.boolean().optional() }).passthrough().optional(),
|
|
275
|
+
codex: z2.object({ enabled: z2.boolean().optional() }).passthrough().optional(),
|
|
276
|
+
harnessesChosen: z2.boolean().optional(),
|
|
277
|
+
opencodeDeclined: z2.boolean().optional()
|
|
278
|
+
});
|
|
279
|
+
function migrateHarnessAllowlist(raw) {
|
|
280
|
+
const legacy = legacyOfferSchema.safeParse(raw);
|
|
281
|
+
if (!legacy.success || !legacy.data.harnessesChosen) return null;
|
|
282
|
+
const declined = /* @__PURE__ */ new Set();
|
|
283
|
+
if (legacy.data.claudeCode?.enabled === false) declined.add("claude-code");
|
|
284
|
+
if (legacy.data.codex?.enabled === false) declined.add("codex");
|
|
285
|
+
if (legacy.data.opencodeDeclined === true) declined.add("opencode");
|
|
286
|
+
if (declined.size === 0) return null;
|
|
287
|
+
return HARNESS_RUNTIMES.filter((runtime) => !declined.has(runtime));
|
|
288
|
+
}
|
|
289
|
+
function carriesLegacyOffer(raw) {
|
|
290
|
+
if (!raw || typeof raw !== "object") return false;
|
|
291
|
+
const obj = raw;
|
|
292
|
+
return "harnessesChosen" in obj || "opencodeDeclined" in obj || typeof obj.claudeCode === "object" && obj.claudeCode !== null && "enabled" in obj.claudeCode || obj.codex !== void 0;
|
|
293
|
+
}
|
|
294
|
+
function loadConfig() {
|
|
295
|
+
const path = configPath();
|
|
296
|
+
if (!existsSync3(path)) return null;
|
|
297
|
+
let raw;
|
|
298
|
+
try {
|
|
299
|
+
raw = readFileSync2(path, "utf8");
|
|
300
|
+
} catch (err) {
|
|
301
|
+
throw new ConfigError(
|
|
302
|
+
`couldn't read ${path}: ${err instanceof Error ? err.message : String(err)}`
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
if (raw.trim().length === 0) return null;
|
|
306
|
+
let parsed;
|
|
307
|
+
try {
|
|
308
|
+
parsed = withoutRetiredKeys(JSON.parse(raw));
|
|
309
|
+
} catch (err) {
|
|
310
|
+
throw new ConfigError(
|
|
311
|
+
`${path} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
const result = companionConfigSchema.safeParse(parsed);
|
|
315
|
+
if (!result.success) {
|
|
316
|
+
const agentIssue = result.error.issues.find((i) => i.path[0] === "agents");
|
|
317
|
+
if (agentIssue) {
|
|
318
|
+
throw new ConfigError(
|
|
319
|
+
`${path}: invalid "agents" config at \`${agentIssue.path.join(".")}\` \u2014 ${agentIssue.message}. Fix the agents block in the config and retry.`
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
const rootIssue = result.error.issues.find((i) => i.path[0] === "root");
|
|
323
|
+
if (rootIssue) {
|
|
324
|
+
throw new ConfigError(
|
|
325
|
+
`${path}: invalid "root" \u2014 ${rootIssue.message}. Fix it in the config and retry.`
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
throw new ConfigError(
|
|
329
|
+
`${path} is from an incompatible or older version of the companion, or was hand-edited. Run \`${cabane("setup")}\` to pair again, or \`${cabane("logout --purge")}\` to reset.`
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
if (carriesLegacyOffer(parsed)) {
|
|
333
|
+
const allowlist = migrateHarnessAllowlist(parsed);
|
|
334
|
+
const migrated = {
|
|
335
|
+
...result.data,
|
|
336
|
+
...allowlist && !result.data.harnesses ? { harnesses: allowlist } : {}
|
|
337
|
+
};
|
|
338
|
+
saveConfig(migrated);
|
|
339
|
+
return migrated;
|
|
340
|
+
}
|
|
341
|
+
return result.data;
|
|
342
|
+
}
|
|
343
|
+
function saveConfig(cfg) {
|
|
344
|
+
const path = configPath();
|
|
345
|
+
mkdirSync(dirname3(path), { recursive: true });
|
|
346
|
+
try {
|
|
347
|
+
chmodSync(cabaneDir(), 448);
|
|
348
|
+
} catch {
|
|
349
|
+
}
|
|
350
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
351
|
+
try {
|
|
352
|
+
writeFileSync(tmp, JSON.stringify(cfg, null, 2) + "\n", { mode: 384 });
|
|
353
|
+
try {
|
|
354
|
+
chmodSync(tmp, 384);
|
|
355
|
+
} catch {
|
|
356
|
+
}
|
|
357
|
+
renameSync(tmp, path);
|
|
358
|
+
} catch (err) {
|
|
359
|
+
try {
|
|
360
|
+
rmSync(tmp, { force: true });
|
|
361
|
+
} catch {
|
|
362
|
+
}
|
|
363
|
+
throw err;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
60
367
|
// src/bundled-binaries.ts
|
|
61
368
|
function nodeResolveSelf(specifier) {
|
|
62
369
|
const here = fileURLToPath3(import.meta.url);
|
|
@@ -73,9 +380,9 @@ function nodeResolveSelf(specifier) {
|
|
|
73
380
|
return walkNodeModules(specifier, here);
|
|
74
381
|
}
|
|
75
382
|
function walkNodeModules(pkg, fromPath) {
|
|
76
|
-
let dir =
|
|
383
|
+
let dir = dirname4(fromPath);
|
|
77
384
|
for (; ; ) {
|
|
78
|
-
const manifest =
|
|
385
|
+
const manifest = join5(dir, "node_modules", pkg, "package.json");
|
|
79
386
|
if (nodeIsFile(manifest)) {
|
|
80
387
|
try {
|
|
81
388
|
return realpathSync3(manifest);
|
|
@@ -83,7 +390,7 @@ function walkNodeModules(pkg, fromPath) {
|
|
|
83
390
|
return manifest;
|
|
84
391
|
}
|
|
85
392
|
}
|
|
86
|
-
const parent =
|
|
393
|
+
const parent = dirname4(dir);
|
|
87
394
|
if (parent === dir) return null;
|
|
88
395
|
dir = parent;
|
|
89
396
|
}
|
|
@@ -145,7 +452,7 @@ function resolveClaude(env) {
|
|
|
145
452
|
const manifest = env.resolve(`${pkg}/package.json`, sdkEntry);
|
|
146
453
|
if (!manifest) continue;
|
|
147
454
|
sawPackage ??= pkg;
|
|
148
|
-
const exe =
|
|
455
|
+
const exe = join5(dirname4(manifest), env.platform === "win32" ? "claude.exe" : "claude");
|
|
149
456
|
if (env.isFile(exe)) {
|
|
150
457
|
return { ...base, packageName: pkg, status: "present", path: exe };
|
|
151
458
|
}
|
|
@@ -196,13 +503,13 @@ function resolveCodex(env) {
|
|
|
196
503
|
if (!cliManifest) return { ...base, status: "platform_package_missing", path: null };
|
|
197
504
|
const platformManifest = env.resolve(`${pkg}/package.json`, cliManifest);
|
|
198
505
|
if (!platformManifest) return { ...base, status: "platform_package_missing", path: null };
|
|
199
|
-
const packageRoot =
|
|
506
|
+
const packageRoot = join5(dirname4(platformManifest), "vendor", triple);
|
|
200
507
|
const exeName = env.platform === "win32" ? "codex.exe" : "codex";
|
|
201
|
-
const current =
|
|
202
|
-
if (env.isFile(current) && env.isFile(
|
|
508
|
+
const current = join5(packageRoot, "bin", exeName);
|
|
509
|
+
if (env.isFile(current) && env.isFile(join5(packageRoot, "codex-package.json"))) {
|
|
203
510
|
return { ...base, status: "present", path: current };
|
|
204
511
|
}
|
|
205
|
-
const legacy =
|
|
512
|
+
const legacy = join5(packageRoot, "codex", exeName);
|
|
206
513
|
if (env.isFile(legacy)) return { ...base, status: "present", path: legacy };
|
|
207
514
|
return { ...base, status: "executable_missing", path: null };
|
|
208
515
|
}
|
|
@@ -233,15 +540,30 @@ var HARNESS_LABEL = {
|
|
|
233
540
|
function missingNoun(status) {
|
|
234
541
|
return status === "executable_missing" ? "executable" : "platform package";
|
|
235
542
|
}
|
|
236
|
-
function repairCommands(route = repairRoute(), spell = cabane) {
|
|
543
|
+
function repairCommands(route = repairRoute(), spell = cabane, address = configuredAddress()) {
|
|
237
544
|
if (route === "managed") return [spell("stop"), spell("update")];
|
|
238
|
-
if (route === "script")
|
|
545
|
+
if (route === "script")
|
|
546
|
+
return [
|
|
547
|
+
spell("stop"),
|
|
548
|
+
address ? installScriptCommand(address) : { words: installLineWords("script") }
|
|
549
|
+
];
|
|
239
550
|
return ["npm i -g @cabane/companion --include=optional"];
|
|
240
551
|
}
|
|
241
|
-
function
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
552
|
+
function configuredAddress() {
|
|
553
|
+
try {
|
|
554
|
+
return loadConfig()?.baseUrl ?? null;
|
|
555
|
+
} catch {
|
|
556
|
+
return null;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
function quoteStep(step, quote) {
|
|
560
|
+
return typeof step === "string" ? quote(step) : step.words;
|
|
561
|
+
}
|
|
562
|
+
var backticks = (command) => `\`${command}\``;
|
|
563
|
+
function repairAction(route = repairRoute(), spell = cabane, address = configuredAddress()) {
|
|
564
|
+
const [first, ...rest] = repairCommands(route, spell, address);
|
|
565
|
+
if (rest.length === 0) return `Reinstall with ${quoteStep(first, backticks)}.`;
|
|
566
|
+
return `Run ${quoteStep(first, backticks)}, then ${rest.map((step) => quoteStep(step, backticks)).join(", then ")}. ${STOP_WARNING}`;
|
|
245
567
|
}
|
|
246
568
|
var STOP_WARNING = "Stopping ends any turn in progress.";
|
|
247
569
|
function installWarning(r) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cabane/companion",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.238",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The Cabane Companion (headless): connect an agent on your machine to your Cabane workspace as a responder — drive work against your own codebase, files, and MCP servers without putting any of it in Cabane.",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -16,8 +16,6 @@
|
|
|
16
16
|
"main": "./dist/cli.js",
|
|
17
17
|
"exports": {
|
|
18
18
|
".": "./dist/cli.js",
|
|
19
|
-
"./runtime": "./dist/runtime.js",
|
|
20
|
-
"./pairing": "./dist/pairing-config.js",
|
|
21
19
|
"./credential-broker": "./dist/credential-broker.js",
|
|
22
20
|
"./package.json": "./package.json",
|
|
23
21
|
"./*": "./*"
|