@cabane/companion 0.6.154 → 0.6.156
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 +73 -67
- package/dist/bin.js +13 -0
- package/dist/cli.js +2640 -2097
- package/dist/pairing-config.js +160 -23
- package/dist/postinstall.js +18 -5
- package/dist/runtime.js +956 -833
- package/dist/static/app.js +2 -3
- package/package.json +4 -3
package/dist/pairing-config.js
CHANGED
|
@@ -1,3 +1,108 @@
|
|
|
1
|
+
// src/cabane-command.ts
|
|
2
|
+
import { accessSync, constants, realpathSync as realpathSync2 } from "fs";
|
|
3
|
+
import { homedir as homedir3 } from "os";
|
|
4
|
+
import { delimiter, join as join2, sep as sep2 } from "path";
|
|
5
|
+
|
|
6
|
+
// src/bundled-binaries.ts
|
|
7
|
+
import { realpathSync, statSync } from "fs";
|
|
8
|
+
import { createRequire } from "module";
|
|
9
|
+
import { homedir } from "os";
|
|
10
|
+
import { dirname, join, sep } from "path";
|
|
11
|
+
import { fileURLToPath } from "url";
|
|
12
|
+
var CODEX_CLI = "@openai/codex";
|
|
13
|
+
var CODEX_PACKAGE_BY_TRIPLE = {
|
|
14
|
+
"x86_64-unknown-linux-musl": `${CODEX_CLI}-linux-x64`,
|
|
15
|
+
"aarch64-unknown-linux-musl": `${CODEX_CLI}-linux-arm64`,
|
|
16
|
+
"x86_64-apple-darwin": `${CODEX_CLI}-darwin-x64`,
|
|
17
|
+
"aarch64-apple-darwin": `${CODEX_CLI}-darwin-arm64`,
|
|
18
|
+
"x86_64-pc-windows-msvc": `${CODEX_CLI}-win32-x64`,
|
|
19
|
+
"aarch64-pc-windows-msvc": `${CODEX_CLI}-win32-arm64`
|
|
20
|
+
};
|
|
21
|
+
function companionInstall(execPath = process.execPath, home = homedir()) {
|
|
22
|
+
const real = (path) => {
|
|
23
|
+
try {
|
|
24
|
+
return realpathSync(path);
|
|
25
|
+
} catch {
|
|
26
|
+
return path;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
const scriptNode = join(real(home), ".cabane", "node") + sep;
|
|
30
|
+
return real(execPath).startsWith(scriptNode) ? "script" : "npm";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// src/cli-entry.ts
|
|
34
|
+
import { existsSync } from "fs";
|
|
35
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
36
|
+
var RELATIVE_CANDIDATES = ["./cli.js", "../dist/cli.js", "../cli.js"];
|
|
37
|
+
function companionCliEntry(deps = {}) {
|
|
38
|
+
const exists = deps.exists ?? existsSync;
|
|
39
|
+
const candidates = deps.candidates ?? RELATIVE_CANDIDATES.map((rel) => fileURLToPath2(new URL(rel, import.meta.url)));
|
|
40
|
+
for (const candidate of candidates) {
|
|
41
|
+
if (exists(candidate)) return candidate;
|
|
42
|
+
}
|
|
43
|
+
const argv1 = "argv1" in deps ? deps.argv1 : process.argv[1];
|
|
44
|
+
if (argv1 && exists(argv1)) return argv1;
|
|
45
|
+
return candidates[0] ?? "";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/term.ts
|
|
49
|
+
import { createInterface } from "readline";
|
|
50
|
+
import { homedir as homedir2 } from "os";
|
|
51
|
+
import pc from "picocolors";
|
|
52
|
+
function tildePath(path, home = homedir2()) {
|
|
53
|
+
if (home && path.startsWith(home + "/")) return `~${path.slice(home.length)}`;
|
|
54
|
+
return path;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/cabane-command.ts
|
|
58
|
+
function installedCommandPath(deps = {}) {
|
|
59
|
+
const install = deps.install ?? companionInstall();
|
|
60
|
+
const home = deps.home ?? homedir3();
|
|
61
|
+
if (install === "script") return join2(home, ".cabane", "bin", "cabane");
|
|
62
|
+
const entry = deps.cliEntry ?? companionCliEntry();
|
|
63
|
+
const marker = `${sep2}lib${sep2}node_modules${sep2}@cabane${sep2}companion${sep2}`;
|
|
64
|
+
const at = entry.indexOf(marker);
|
|
65
|
+
if (at < 0) return null;
|
|
66
|
+
return join2(entry.slice(0, at), "bin", "cabane");
|
|
67
|
+
}
|
|
68
|
+
function cabaneCommand(deps = {}) {
|
|
69
|
+
const own = installedCommandPath(deps);
|
|
70
|
+
if (!own) return "cabane";
|
|
71
|
+
const realpath = deps.realpath ?? safeRealpath;
|
|
72
|
+
const isExecutable = deps.isExecutable ?? executable;
|
|
73
|
+
const ownReal = realpath(own);
|
|
74
|
+
if (!ownReal) return "cabane";
|
|
75
|
+
const onPath = firstOnPath("cabane", "path" in deps ? deps.path : process.env.PATH, isExecutable);
|
|
76
|
+
if (onPath && realpath(onPath) === ownReal) return "cabane";
|
|
77
|
+
return tildePath(own, deps.home);
|
|
78
|
+
}
|
|
79
|
+
function cabane(rest, deps) {
|
|
80
|
+
return `${cabaneCommand(deps)} ${rest}`;
|
|
81
|
+
}
|
|
82
|
+
function firstOnPath(name, path, isExecutable = executable) {
|
|
83
|
+
for (const dir of (path ?? "").split(delimiter)) {
|
|
84
|
+
if (!dir) continue;
|
|
85
|
+
const candidate = join2(dir, name);
|
|
86
|
+
if (isExecutable(candidate)) return candidate;
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
function executable(path) {
|
|
91
|
+
try {
|
|
92
|
+
accessSync(path, constants.X_OK);
|
|
93
|
+
return true;
|
|
94
|
+
} catch {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function safeRealpath(path) {
|
|
99
|
+
try {
|
|
100
|
+
return realpathSync2(path);
|
|
101
|
+
} catch {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
1
106
|
// src/config.ts
|
|
2
107
|
import {
|
|
3
108
|
chmodSync,
|
|
@@ -6,10 +111,10 @@ import {
|
|
|
6
111
|
renameSync,
|
|
7
112
|
rmSync,
|
|
8
113
|
writeFileSync,
|
|
9
|
-
existsSync
|
|
114
|
+
existsSync as existsSync2
|
|
10
115
|
} from "fs";
|
|
11
|
-
import { homedir, userInfo } from "os";
|
|
12
|
-
import { dirname, join } from "path";
|
|
116
|
+
import { homedir as homedir4, userInfo } from "os";
|
|
117
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
13
118
|
import { z as z2 } from "zod";
|
|
14
119
|
|
|
15
120
|
// src/errors.ts
|
|
@@ -102,10 +207,10 @@ function realAccountHome() {
|
|
|
102
207
|
return realHomeCache;
|
|
103
208
|
}
|
|
104
209
|
function cabaneDir() {
|
|
105
|
-
const dir =
|
|
210
|
+
const dir = join3(process.env.CABANE_COMPANION_HOME || homedir4(), ".cabane");
|
|
106
211
|
if (process.env.VITEST) {
|
|
107
212
|
const real = realAccountHome();
|
|
108
|
-
if (real && dir ===
|
|
213
|
+
if (real && dir === join3(real, ".cabane")) {
|
|
109
214
|
throw new Error(
|
|
110
215
|
`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).`
|
|
111
216
|
);
|
|
@@ -114,7 +219,7 @@ function cabaneDir() {
|
|
|
114
219
|
return dir;
|
|
115
220
|
}
|
|
116
221
|
function configPath() {
|
|
117
|
-
return
|
|
222
|
+
return join3(cabaneDir(), "config.json");
|
|
118
223
|
}
|
|
119
224
|
var localAgentConfigSchema = z2.object({
|
|
120
225
|
cwd: z2.string().optional(),
|
|
@@ -188,15 +293,24 @@ var companionConfigSchema = z2.object({
|
|
|
188
293
|
// the Agent SDK's bundled executable resolves (not `claude` on PATH, which the
|
|
189
294
|
// SDK never runs).
|
|
190
295
|
//
|
|
191
|
-
//
|
|
192
|
-
//
|
|
193
|
-
//
|
|
296
|
+
// Absent means one of two things, told apart by `harnessesChosen`: a legacy
|
|
297
|
+
// config whose user was never asked (`migrateConnectedHarnesses`), or a
|
|
298
|
+
// device that has never been offered Claude Code. `enabled: false` is a
|
|
299
|
+
// decline, and `start` doesn't offer it again.
|
|
194
300
|
//
|
|
195
301
|
// Not the per-agent `agents.<key>.claudeCode.autoMemory`, one agent's memory
|
|
196
302
|
// switch.
|
|
197
303
|
claudeCode: z2.object({
|
|
198
304
|
enabled: z2.boolean().optional()
|
|
199
305
|
}).strict().optional(),
|
|
306
|
+
// Written by every pairing that asks which agents to connect. With it, an
|
|
307
|
+
// absent `claudeCode` block means "never offered"; without it, the block's
|
|
308
|
+
// absence marks a config from before connectors were chosen.
|
|
309
|
+
harnessesChosen: z2.boolean().optional(),
|
|
310
|
+
// OpenCode's decline. Its block is an address, so a decline can't live there
|
|
311
|
+
// the way `claudeCode`/`codex` `enabled: false` does; without it a declined
|
|
312
|
+
// serve would be offered again on every `start`.
|
|
313
|
+
opencodeDeclined: z2.boolean().optional(),
|
|
200
314
|
// Codex is a local CLI the SDK spawns per turn, so this is an opt-in flag, not
|
|
201
315
|
// an address. The operator logs in (`codex login` or `CODEX_API_KEY`; Cabane
|
|
202
316
|
// never sees the key). The block's presence enables it; `enabled: false` turns
|
|
@@ -250,7 +364,7 @@ var companionConfigSchema = z2.object({
|
|
|
250
364
|
});
|
|
251
365
|
function loadConfig() {
|
|
252
366
|
const path = configPath();
|
|
253
|
-
if (!
|
|
367
|
+
if (!existsSync2(path)) return null;
|
|
254
368
|
let raw;
|
|
255
369
|
try {
|
|
256
370
|
raw = readFileSync(path, "utf8");
|
|
@@ -277,7 +391,7 @@ function loadConfig() {
|
|
|
277
391
|
);
|
|
278
392
|
}
|
|
279
393
|
throw new ConfigError(
|
|
280
|
-
`${path} is from an incompatible or older version of the companion, or was hand-edited. Run
|
|
394
|
+
`${path} is from an incompatible or older version of the companion, or was hand-edited. Run \`${cabane("pair")}\` to re-pair, or \`${cabane("logout --purge")}\` to reset.`
|
|
281
395
|
);
|
|
282
396
|
}
|
|
283
397
|
return result.data;
|
|
@@ -286,7 +400,7 @@ function loadConfigTolerant() {
|
|
|
286
400
|
const path = configPath();
|
|
287
401
|
const empty = {};
|
|
288
402
|
const fresh = { local: empty, note: null, hadPriorConfig: false };
|
|
289
|
-
if (!
|
|
403
|
+
if (!existsSync2(path)) return fresh;
|
|
290
404
|
let raw;
|
|
291
405
|
try {
|
|
292
406
|
raw = readFileSync(path, "utf8");
|
|
@@ -306,7 +420,19 @@ function loadConfigTolerant() {
|
|
|
306
420
|
}
|
|
307
421
|
const strict = companionConfigSchema.safeParse(parsed);
|
|
308
422
|
if (strict.success) {
|
|
309
|
-
const {
|
|
423
|
+
const {
|
|
424
|
+
agents: agents2,
|
|
425
|
+
prepareHook: prepareHook2,
|
|
426
|
+
dashboardPort,
|
|
427
|
+
autoOpen,
|
|
428
|
+
logLevel,
|
|
429
|
+
logFormat,
|
|
430
|
+
claudeCode: claudeCode2,
|
|
431
|
+
codex: codex2,
|
|
432
|
+
opencode: opencode2,
|
|
433
|
+
opencodeDeclined,
|
|
434
|
+
harnessesChosen
|
|
435
|
+
} = strict.data;
|
|
310
436
|
return {
|
|
311
437
|
local: {
|
|
312
438
|
...agents2 !== void 0 ? { agents: agents2 } : {},
|
|
@@ -315,7 +441,11 @@ function loadConfigTolerant() {
|
|
|
315
441
|
...autoOpen !== void 0 ? { autoOpen } : {},
|
|
316
442
|
...logLevel !== void 0 ? { logLevel } : {},
|
|
317
443
|
...logFormat !== void 0 ? { logFormat } : {},
|
|
318
|
-
...claudeCode2 !== void 0 ? { claudeCode: claudeCode2 } : {}
|
|
444
|
+
...claudeCode2 !== void 0 ? { claudeCode: claudeCode2 } : {},
|
|
445
|
+
...codex2 !== void 0 ? { codex: codex2 } : {},
|
|
446
|
+
...opencode2 !== void 0 ? { opencode: opencode2 } : {},
|
|
447
|
+
...opencodeDeclined !== void 0 ? { opencodeDeclined } : {},
|
|
448
|
+
...harnessesChosen !== void 0 ? { harnessesChosen } : {}
|
|
319
449
|
},
|
|
320
450
|
note: null,
|
|
321
451
|
hadPriorConfig: true
|
|
@@ -335,6 +465,12 @@ function loadConfigTolerant() {
|
|
|
335
465
|
}
|
|
336
466
|
const claudeCode = companionConfigSchema.shape.claudeCode.safeParse(obj.claudeCode);
|
|
337
467
|
if (claudeCode.success && claudeCode.data !== void 0) local.claudeCode = claudeCode.data;
|
|
468
|
+
const codex = companionConfigSchema.shape.codex.safeParse(obj.codex);
|
|
469
|
+
if (codex.success && codex.data !== void 0) local.codex = codex.data;
|
|
470
|
+
const opencode = companionConfigSchema.shape.opencode.safeParse(obj.opencode);
|
|
471
|
+
if (opencode.success && opencode.data !== void 0) local.opencode = opencode.data;
|
|
472
|
+
if (obj.opencodeDeclined === true) local.opencodeDeclined = true;
|
|
473
|
+
if (obj.harnessesChosen === true) local.harnessesChosen = true;
|
|
338
474
|
return {
|
|
339
475
|
local,
|
|
340
476
|
note: `the existing ${path} was from an older or incompatible companion; re-pairing rewrote it.`,
|
|
@@ -343,7 +479,7 @@ function loadConfigTolerant() {
|
|
|
343
479
|
}
|
|
344
480
|
function saveConfig(cfg) {
|
|
345
481
|
const path = configPath();
|
|
346
|
-
mkdirSync(
|
|
482
|
+
mkdirSync(dirname2(path), { recursive: true });
|
|
347
483
|
try {
|
|
348
484
|
chmodSync(cabaneDir(), 448);
|
|
349
485
|
} catch {
|
|
@@ -406,7 +542,7 @@ async function postJson(baseUrl, path, body) {
|
|
|
406
542
|
if (res.status >= 400) {
|
|
407
543
|
if (res.status === 404 && path.endsWith("/code")) {
|
|
408
544
|
throw new CompanionError(
|
|
409
|
-
`no device-flow pairing endpoint at ${baseUrl}. Either that server predates
|
|
545
|
+
`no device-flow pairing endpoint at ${baseUrl}. Either that server predates \`${cabane("pair")}\`, or it isn't a Cabane API origin \u2014 the hosted app is https://app.cabane.ai (pass \`--server <url>\` for your own instance).`
|
|
410
546
|
);
|
|
411
547
|
}
|
|
412
548
|
const msg = parsed && typeof parsed === "object" && "error" in parsed ? String(parsed.error) : `${res.status}`;
|
|
@@ -481,7 +617,7 @@ function resolvePairBaseUrl(server) {
|
|
|
481
617
|
const raw = (server ?? process.env.CABANE_BASE_URL ?? DEFAULT_BASE_URL).trim();
|
|
482
618
|
if (!isAllowedBaseUrl(raw)) {
|
|
483
619
|
throw new CompanionError(
|
|
484
|
-
`invalid server URL "${raw}": must be an https URL (loopback http is allowed for local dev). Pass it as
|
|
620
|
+
`invalid server URL "${raw}": must be an https URL (loopback http is allowed for local dev). Pass it as \`${cabane("start --server https://your-cabane")}\`.`
|
|
485
621
|
);
|
|
486
622
|
}
|
|
487
623
|
return raw;
|
|
@@ -489,12 +625,13 @@ function resolvePairBaseUrl(server) {
|
|
|
489
625
|
function writePairedConfig(paired) {
|
|
490
626
|
const { local, note, hadPriorConfig } = loadConfigTolerant();
|
|
491
627
|
const config = {
|
|
492
|
-
// A device pairs connected to NOTHING
|
|
493
|
-
//
|
|
494
|
-
//
|
|
495
|
-
//
|
|
496
|
-
//
|
|
497
|
-
|
|
628
|
+
// A device pairs connected to NOTHING, and its agents are chosen from here
|
|
629
|
+
// on: `harnessesChosen` tells the harness migration that an absent
|
|
630
|
+
// `claudeCode` block means "never offered", not a legacy Claude device.
|
|
631
|
+
// Only when there was no config before: stamped on a RE-pair, an upgrading
|
|
632
|
+
// user's working device would be disconnected. `local` spreads after,
|
|
633
|
+
// keeping a prior answer.
|
|
634
|
+
...hadPriorConfig ? {} : { harnessesChosen: true },
|
|
498
635
|
...local,
|
|
499
636
|
baseUrl: paired.baseUrl,
|
|
500
637
|
deviceToken: paired.deviceToken,
|
package/dist/postinstall.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// src/bundled-binaries.ts
|
|
2
2
|
import { realpathSync, statSync } from "fs";
|
|
3
3
|
import { createRequire } from "module";
|
|
4
|
-
import {
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
import { dirname, join, sep } from "path";
|
|
5
6
|
import { fileURLToPath } from "url";
|
|
6
7
|
function nodeResolveSelf(specifier) {
|
|
7
8
|
const here = fileURLToPath(import.meta.url);
|
|
@@ -178,11 +179,23 @@ var HARNESS_LABEL = {
|
|
|
178
179
|
function missingNoun(status) {
|
|
179
180
|
return status === "executable_missing" ? "executable" : "platform package";
|
|
180
181
|
}
|
|
181
|
-
|
|
182
|
-
|
|
182
|
+
var INSTALL_SCRIPT_COMMAND = "curl -fsSL https://cabane.ai/install.sh | bash";
|
|
183
|
+
function companionInstall(execPath = process.execPath, home = homedir()) {
|
|
184
|
+
const real = (path) => {
|
|
185
|
+
try {
|
|
186
|
+
return realpathSync(path);
|
|
187
|
+
} catch {
|
|
188
|
+
return path;
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
const scriptNode = join(real(home), ".cabane", "node") + sep;
|
|
192
|
+
return real(execPath).startsWith(scriptNode) ? "script" : "npm";
|
|
193
|
+
}
|
|
194
|
+
function repairCommands(install = companionInstall()) {
|
|
195
|
+
return install === "script" ? [INSTALL_SCRIPT_COMMAND] : ["npm i -g @cabane/companion --include=optional"];
|
|
183
196
|
}
|
|
184
|
-
function repairAction() {
|
|
185
|
-
return `Reinstall with \`${repairCommands()[0]}\`.`;
|
|
197
|
+
function repairAction(install = companionInstall()) {
|
|
198
|
+
return `Reinstall with \`${repairCommands(install)[0]}\`.`;
|
|
186
199
|
}
|
|
187
200
|
function installWarning(r) {
|
|
188
201
|
return `warning: @cabane/companion is incomplete: its bundled ${HARNESS_LABEL[r.runtime]} ${missingNoun(r.status)} for ${r.platform}-${r.arch} is missing. ${repairAction()}`;
|