@join-pantheon/cli 0.25.3 → 0.26.1
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 +43 -5
- package/bin/pantheon.js +7 -2
- package/dist/agents.js +159 -18
- package/dist/cli.js +39 -27
- package/dist/config.js +26 -1
- package/dist/doctor.js +23 -3
- package/dist/hooks-install.js +60 -2
- package/dist/install.js +94 -30
- package/dist/machine.js +11 -0
- package/dist/measure.js +17 -3
- package/dist/notice.js +4 -1
- package/dist/notifier.js +1 -0
- package/dist/notify.js +21 -5
- package/dist/persona.js +1 -1
- package/dist/profile.js +4 -1
- package/dist/quickstart.js +2 -1
- package/dist/run-failure.js +15 -0
- package/dist/service.js +3 -0
- package/dist/setup-guide.js +1 -1
- package/dist/tray.js +1 -1
- package/dist/update-check.js +2 -0
- package/dist/which.js +73 -0
- package/dist/worker.js +17 -7
- package/package.json +6 -4
package/dist/which.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { accessSync, constants, existsSync, statSync } from "node:fs";
|
|
3
|
+
import { win32 } from "node:path";
|
|
4
|
+
import { platform } from "node:os";
|
|
5
|
+
export const DEFAULT_PATHEXT = ".COM;.EXE;.BAT;.CMD;.VBS;.JS;.PS1";
|
|
6
|
+
const isWindows = (os = platform()) => os === "win32";
|
|
7
|
+
function runnable(file) {
|
|
8
|
+
try {
|
|
9
|
+
if (!existsSync(file) || !statSync(file).isFile())
|
|
10
|
+
return false;
|
|
11
|
+
if (isWindows())
|
|
12
|
+
return true;
|
|
13
|
+
accessSync(file, constants.X_OK);
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function whichWindows(name, env = process.env, exists = runnable) {
|
|
21
|
+
if (!name)
|
|
22
|
+
return null;
|
|
23
|
+
const exts = (env.PATHEXT || DEFAULT_PATHEXT).split(";").map((e) => e.trim()).filter(Boolean);
|
|
24
|
+
const ext = win32.extname(name);
|
|
25
|
+
const hasExt = !!ext && exts.some((e) => e.toLowerCase() === ext.toLowerCase());
|
|
26
|
+
const candidates = (base) => hasExt ? [base] : exts.flatMap((e) => (e === e.toLowerCase() ? [base + e] : [base + e.toLowerCase(), base + e]));
|
|
27
|
+
if (win32.isAbsolute(name)) {
|
|
28
|
+
for (const c of candidates(name))
|
|
29
|
+
if (exists(c))
|
|
30
|
+
return c;
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
for (const dir of (env.PATH || env.Path || "").split(";")) {
|
|
34
|
+
const d = dir.replace(/^"|"$/g, "").trim();
|
|
35
|
+
if (!d)
|
|
36
|
+
continue;
|
|
37
|
+
for (const c of candidates(win32.join(d, name)))
|
|
38
|
+
if (exists(c))
|
|
39
|
+
return c;
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
export function which(name, os = platform(), env = process.env) {
|
|
44
|
+
if (isWindows(os))
|
|
45
|
+
return whichWindows(name, env);
|
|
46
|
+
try {
|
|
47
|
+
const out = execFileSync("sh", ["-lc", `command -v ${name}`], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
48
|
+
if (!out)
|
|
49
|
+
return null;
|
|
50
|
+
return out.startsWith("/") && !runnable(out) ? null : out;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export const onPathNamed = (name, os = platform(), env = process.env) => which(name, os, env) !== null;
|
|
57
|
+
export function spawnShape(binary, os = platform()) {
|
|
58
|
+
if (!isWindows(os))
|
|
59
|
+
return { command: binary, shell: false };
|
|
60
|
+
return { command: binary, shell: /\.(cmd|bat)$/i.test(binary) };
|
|
61
|
+
}
|
|
62
|
+
const cmdQuote = (a) => (/[\s"&|<>^()]/.test(a) || a === "" ? `"${a.replace(/"/g, '\\"')}"` : a);
|
|
63
|
+
export function agentSpawn(binary, args, os = platform()) {
|
|
64
|
+
if (!isWindows(os) || !/\.(cmd|bat)$/i.test(binary))
|
|
65
|
+
return { command: binary, args: [...args], options: {} };
|
|
66
|
+
const line = [binary, ...args].map(cmdQuote).join(" ");
|
|
67
|
+
return {
|
|
68
|
+
command: process.env.ComSpec || "cmd.exe",
|
|
69
|
+
args: ["/d", "/s", "/c", `"${line}"`],
|
|
70
|
+
options: { windowsVerbatimArguments: true },
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=which.js.map
|
package/dist/worker.js
CHANGED
|
@@ -4,7 +4,8 @@ import { mkdirSync, writeFileSync, existsSync, readFileSync } from "node:fs";
|
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { join, resolve } from "node:path";
|
|
6
6
|
import { PantheonClient } from "./client.js";
|
|
7
|
-
import { headlessSpawn, materializeSpawnFiles,
|
|
7
|
+
import { headlessSpawn, materializeSpawnFiles, chooseAgent, describeChoice } from "./agents.js";
|
|
8
|
+
import { agentSpawn } from "./which.js";
|
|
8
9
|
import { VERSION } from "./version.js";
|
|
9
10
|
import { applyAnswer, loadPolicy, mayAsk, needsSelfUpgrade, recordAsk, savePolicy, useGrant, } from "./worker-policy.js";
|
|
10
11
|
import { agentHookStates, repairHook } from "./notify.js";
|
|
@@ -191,6 +192,7 @@ export function answerWindow(askSeq, hint, fullScanDue) {
|
|
|
191
192
|
export class Worker {
|
|
192
193
|
cfg;
|
|
193
194
|
opts;
|
|
195
|
+
lastAgentSaid = "";
|
|
194
196
|
running = false;
|
|
195
197
|
done = [];
|
|
196
198
|
stopped = false;
|
|
@@ -329,8 +331,7 @@ export class Worker {
|
|
|
329
331
|
const agentLabel = this.opts.agentCmd
|
|
330
332
|
? this.opts.agentCmd
|
|
331
333
|
: (() => { try {
|
|
332
|
-
|
|
333
|
-
return `${r.backend.label} (${r.binary})`;
|
|
334
|
+
return describeChoice(chooseAgent(this.opts.agent, { prefer: loadProfile(this.cfg).agent.primary_surface }));
|
|
334
335
|
}
|
|
335
336
|
catch {
|
|
336
337
|
return "auto";
|
|
@@ -580,10 +581,12 @@ export class Worker {
|
|
|
580
581
|
const startedAt = Date.now();
|
|
581
582
|
writeFileSync(join(worktree, ".pantheon-task.md"), brief);
|
|
582
583
|
let command;
|
|
584
|
+
let agentId;
|
|
583
585
|
let args;
|
|
584
586
|
let env = { ...process.env };
|
|
585
587
|
try {
|
|
586
588
|
const runHome = this.runHome();
|
|
589
|
+
agentId = this.opts.agentCmd ? "claude-code" : undefined;
|
|
587
590
|
if (this.opts.agentCmd) {
|
|
588
591
|
const plan = headlessSpawn({
|
|
589
592
|
backend: { id: "claude-code", label: "Claude Code", binaries: ["claude"] },
|
|
@@ -594,8 +597,14 @@ export class Worker {
|
|
|
594
597
|
({ command, args, env } = { command: plan.command, args: plan.args, env: { ...env, ...plan.env } });
|
|
595
598
|
}
|
|
596
599
|
else {
|
|
597
|
-
const
|
|
598
|
-
|
|
600
|
+
const choice = chooseAgent(policy.agent ?? this.opts.agent, { prefer: loadProfile(this.cfg).agent.primary_surface });
|
|
601
|
+
agentId = choice.backend.id;
|
|
602
|
+
const said = describeChoice(choice);
|
|
603
|
+
if (said !== this.lastAgentSaid) {
|
|
604
|
+
this.opts.log(` agent ${said}`);
|
|
605
|
+
this.lastAgentSaid = said;
|
|
606
|
+
}
|
|
607
|
+
const plan = headlessSpawn({ backend: choice.backend, binary: choice.binary, prompt: brief, worktree, cfg: this.cfg, branch, allowBash, runHome });
|
|
599
608
|
materializeSpawnFiles(worktree, plan);
|
|
600
609
|
({ command, args, env } = { command: plan.command, args: plan.args, env: { ...env, ...plan.env } });
|
|
601
610
|
this.opts.log(` spawn ${command} ${args.slice(0, 3).join(" ")}…`);
|
|
@@ -608,7 +617,8 @@ export class Worker {
|
|
|
608
617
|
return { obligation: o, branch, worktree, ok: false, detail };
|
|
609
618
|
}
|
|
610
619
|
const detail = await new Promise((res) => {
|
|
611
|
-
const
|
|
620
|
+
const sp = agentSpawn(command, args);
|
|
621
|
+
const child = procSpawn(sp.command, sp.args, { cwd: worktree, stdio: ["ignore", "pipe", "pipe"], env, ...sp.options });
|
|
612
622
|
let out = "";
|
|
613
623
|
child.stdout.on("data", (b) => { out += b.toString(); });
|
|
614
624
|
child.stderr.on("data", (b) => { out += b.toString(); });
|
|
@@ -629,7 +639,7 @@ export class Worker {
|
|
|
629
639
|
}, this.opts.home);
|
|
630
640
|
const sent = client.mineSince(startedAt, o.goal);
|
|
631
641
|
const quiet = ok && !requireReply && sent.length === 0;
|
|
632
|
-
const diagnosis = ok ? undefined : diagnoseRun({ exited, replied, allowBash, branch, project: this.opts.project, worktree, detail, runLog }, this.opts.probe ?? gitProbe);
|
|
642
|
+
const diagnosis = ok ? undefined : diagnoseRun({ exited, replied, allowBash, branch, project: this.opts.project, worktree, detail, runLog, ...(agentId ? { agent: agentId } : {}) }, this.opts.probe ?? gitProbe);
|
|
633
643
|
if (this.opts.notify && !quiet && (!ok || loadProfile(this.cfg).notifications?.worker_done)) {
|
|
634
644
|
try {
|
|
635
645
|
const reply = sent[sent.length - 1] ?? null;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@join-pantheon/cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Let your coding agent collaborate directly with your teammates' agents
|
|
3
|
+
"version": "0.26.1",
|
|
4
|
+
"description": "Let your coding agent collaborate directly with your teammates' agents \u2014 ask, agree an interface, and deliver, without a human relaying messages.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"pantheon": "bin/pantheon.js"
|
|
@@ -10,13 +10,15 @@
|
|
|
10
10
|
"node": ">=22"
|
|
11
11
|
},
|
|
12
12
|
"scripts": {
|
|
13
|
-
"build": "
|
|
13
|
+
"build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p .",
|
|
14
14
|
"dev": "tsc -p . --watch",
|
|
15
15
|
"relay": "node dist/cli.js relay",
|
|
16
16
|
"test": "PANTHEON_HOME=\"$(mktemp -d)\" HOME=\"$(mktemp -d)\" node --test --test-force-exit dist/test/*.test.js",
|
|
17
17
|
"prepare": "npm run build",
|
|
18
18
|
"prepublishOnly": "npm run build && npm test",
|
|
19
|
-
"start": "node dist/cli.js relay"
|
|
19
|
+
"start": "node dist/cli.js relay",
|
|
20
|
+
"test:ci": "node scripts/test.mjs",
|
|
21
|
+
"smoke": "node scripts/smoke.mjs"
|
|
20
22
|
},
|
|
21
23
|
"dependencies": {
|
|
22
24
|
"@modelcontextprotocol/sdk": "^1.0.0",
|