@join-pantheon/cli 0.26.0 → 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/bin/pantheon.js +7 -2
- package/dist/agents.js +7 -4
- package/dist/cli.js +17 -4
- package/dist/doctor.js +2 -1
- package/dist/hooks-install.js +2 -2
- package/dist/install.js +23 -24
- package/dist/notify.js +9 -3
- package/dist/quickstart.js +2 -1
- package/dist/service.js +3 -0
- package/dist/update-check.js +2 -0
- package/dist/which.js +73 -0
- package/dist/worker.js +3 -1
- package/package.json +6 -4
package/bin/pantheon.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { existsSync } from "node:fs";
|
|
11
11
|
import { dirname, resolve } from "node:path";
|
|
12
|
-
import { fileURLToPath } from "node:url";
|
|
12
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
13
13
|
|
|
14
14
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
15
15
|
const root = resolve(here, "..");
|
|
@@ -25,4 +25,9 @@ if (!existsSync(entry)) {
|
|
|
25
25
|
process.exit(1);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
// A path is not a URL, and on Windows that is fatal rather than pedantic: an
|
|
29
|
+
// absolute POSIX path happens to work as an import specifier, while `D:\...`
|
|
30
|
+
// is read as the scheme "d:" and Node refuses it — so `pantheon --version`,
|
|
31
|
+
// the first command anyone runs, died with ERR_UNSUPPORTED_ESM_URL_SCHEME on
|
|
32
|
+
// every Windows machine. A real Windows runner is what said so.
|
|
33
|
+
await import(pathToFileURL(entry).href);
|
package/dist/agents.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
2
|
import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { homedir } from "node:os";
|
|
3
|
+
import { homedir, platform } from "node:os";
|
|
4
4
|
import { join, dirname, isAbsolute } from "node:path";
|
|
5
5
|
import { launcher } from "./install.js";
|
|
6
|
+
import { agentSpawn, which } from "./which.js";
|
|
6
7
|
import { PANTHEON_TOOL_NAMES, PANTHEON_TOOLS } from "./tools.js";
|
|
7
8
|
export function launchArgs(agent, prompt, name) {
|
|
8
9
|
if (agent === "claude-code")
|
|
@@ -38,7 +39,7 @@ const sh = (cmd) => {
|
|
|
38
39
|
};
|
|
39
40
|
export function cliOnPath(name) {
|
|
40
41
|
if (!binaryCache.has(`path:${name}`))
|
|
41
|
-
binaryCache.set(`path:${name}`,
|
|
42
|
+
binaryCache.set(`path:${name}`, which(name));
|
|
42
43
|
return binaryCache.get(`path:${name}`) ?? null;
|
|
43
44
|
}
|
|
44
45
|
export function agentById(id) {
|
|
@@ -87,7 +88,8 @@ const binaryCache = new Map();
|
|
|
87
88
|
export function resolveBinary(backend) {
|
|
88
89
|
for (const b of backend.binaries) {
|
|
89
90
|
if (!binaryCache.has(b)) {
|
|
90
|
-
|
|
91
|
+
const hit = which(b);
|
|
92
|
+
let found = hit ? (platform() === "win32" ? hit : b) : null;
|
|
91
93
|
if (found && b === "agent") {
|
|
92
94
|
const v = `${sh("agent --version 2>/dev/null | head -1") ?? ""} ${sh("agent --help 2>/dev/null | head -3") ?? ""}`;
|
|
93
95
|
if (!/cursor/i.test(v))
|
|
@@ -119,7 +121,8 @@ export function copilotSignedIn(run = runStatus, env = process.env, gh = () => !
|
|
|
119
121
|
}
|
|
120
122
|
const runStatus = (binary, args) => {
|
|
121
123
|
try {
|
|
122
|
-
const
|
|
124
|
+
const sp = agentSpawn(binary, args);
|
|
125
|
+
const out = execFileSync(sp.command, sp.args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 8_000, env: { ...process.env, NO_COLOR: "1" }, ...sp.options });
|
|
123
126
|
return { status: 0, out };
|
|
124
127
|
}
|
|
125
128
|
catch (e) {
|
package/dist/cli.js
CHANGED
|
@@ -10,6 +10,7 @@ import { randomBytes } from "node:crypto";
|
|
|
10
10
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
11
11
|
import { basename, dirname, join, resolve } from "node:path";
|
|
12
12
|
import { PantheonClient } from "./client.js";
|
|
13
|
+
import { agentSpawn } from "./which.js";
|
|
13
14
|
import { fromInvite, looksLikeInviteCode, resolveInviteCode, load, save, toInvite, toJoinLink, resolveFor, loadAll, repoInfo, bindRepo, findMembership, setDefaultSpace, removeMembership, configFor, CONFIG_DIR, CONFIG_PATH, DEFAULT_RELAY, resolveForHook } from "./config.js";
|
|
14
15
|
import { fmtLimit } from "./plans.js";
|
|
15
16
|
import { validEmail, validUsername } from "./accounts.js";
|
|
@@ -2318,7 +2319,10 @@ async function main() {
|
|
|
2318
2319
|
const prompt = o ? dp(o.id, dt(o.body)) : `Read Pantheon act ${a.id}${goalId ? ` and the thread of goal ${goalId}` : ""}. Explain in plain words what happened and whether anything is owed by me, then give me numbered next steps. Do not act until I choose.`;
|
|
2319
2320
|
console.error(D(`opening ${agent.id}…`));
|
|
2320
2321
|
const { spawnSync } = await import("node:child_process");
|
|
2321
|
-
|
|
2322
|
+
{
|
|
2323
|
+
const sp = agentSpawn(agent.binary, launchArgs(agent.id, prompt, sessionName(prompt)));
|
|
2324
|
+
spawnSync(sp.command, sp.args, { stdio: "inherit", ...sp.options });
|
|
2325
|
+
}
|
|
2322
2326
|
return;
|
|
2323
2327
|
}
|
|
2324
2328
|
if (choice === "2") {
|
|
@@ -2382,7 +2386,10 @@ async function main() {
|
|
|
2382
2386
|
const bin = cliOnPath("grok") ?? die("grok is not on PATH here");
|
|
2383
2387
|
console.error(D("opening grok…"));
|
|
2384
2388
|
const { spawnSync } = await import("node:child_process");
|
|
2385
|
-
|
|
2389
|
+
{
|
|
2390
|
+
const sp = agentSpawn(bin, [hud.prompt]);
|
|
2391
|
+
spawnSync(sp.command, sp.args, { stdio: "inherit", ...sp.options });
|
|
2392
|
+
}
|
|
2386
2393
|
return;
|
|
2387
2394
|
}
|
|
2388
2395
|
if (hud.prompt) {
|
|
@@ -2391,7 +2398,10 @@ async function main() {
|
|
|
2391
2398
|
die("no agent CLI found on this machine (claude, cursor-agent, codex, or copilot)");
|
|
2392
2399
|
console.error(D(`opening ${agent.id}…`));
|
|
2393
2400
|
const { spawnSync } = await import("node:child_process");
|
|
2394
|
-
|
|
2401
|
+
{
|
|
2402
|
+
const sp = agentSpawn(agent.binary, launchArgs(agent.id, hud.prompt, sessionName(hud.prompt)));
|
|
2403
|
+
spawnSync(sp.command, sp.args, { stdio: "inherit", ...sp.options });
|
|
2404
|
+
}
|
|
2395
2405
|
return;
|
|
2396
2406
|
}
|
|
2397
2407
|
if (hud.decision)
|
|
@@ -2414,7 +2424,10 @@ async function main() {
|
|
|
2414
2424
|
die("no agent CLI found on this machine (claude, cursor-agent, codex, or copilot)");
|
|
2415
2425
|
console.error(D(`opening ${agent.id} with the question…`));
|
|
2416
2426
|
const { spawnSync } = await import("node:child_process");
|
|
2417
|
-
|
|
2427
|
+
{
|
|
2428
|
+
const sp = agentSpawn(agent.binary, launchArgs(agent.id, prompt, sessionName(prompt)));
|
|
2429
|
+
spawnSync(sp.command, sp.args, { stdio: "inherit", ...sp.options });
|
|
2430
|
+
}
|
|
2418
2431
|
return;
|
|
2419
2432
|
}
|
|
2420
2433
|
case "approvals": {
|
package/dist/doctor.js
CHANGED
|
@@ -14,6 +14,7 @@ import { hookState, readIntent, agentHookStates } from "./notify.js";
|
|
|
14
14
|
import { loadPersonas } from "./persona.js";
|
|
15
15
|
import { workerProc, menubarProc, notifierProc } from "./machine.js";
|
|
16
16
|
import { chooseAgent, describeChoice } from "./agents.js";
|
|
17
|
+
import { onPathNamed } from "./which.js";
|
|
17
18
|
import { VERSION } from "./version.js";
|
|
18
19
|
import { serviceInstalled } from "./service.js";
|
|
19
20
|
import { hostEnv } from "./host-env.js";
|
|
@@ -135,7 +136,7 @@ export async function doctor(projectDir) {
|
|
|
135
136
|
if (branch)
|
|
136
137
|
add("pass", "git branch", branch);
|
|
137
138
|
}
|
|
138
|
-
const agents = ["claude", "codex", "cursor-agent", "copilot"].filter((a) =>
|
|
139
|
+
const agents = ["claude", "codex", "cursor-agent", "copilot"].filter((a) => onPathNamed(a));
|
|
139
140
|
agents.length
|
|
140
141
|
? add("pass", "agent cli", agents.join(", "), undefined, agents.join(", "))
|
|
141
142
|
: add("warn", "agent cli", "found none of claude / codex / cursor-agent / copilot on PATH", "fine if you only drive agents through a GUI (Conductor, Cursor, VS Code), but `pantheon worker` needs a CLI to spawn", "none of claude / codex / cursor-agent / copilot on PATH");
|
package/dist/hooks-install.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, rmSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { MAX_CONSECUTIVE_BLOCKS, isOurHookCommand } from "./notify.js";
|
|
5
5
|
import { copilotHome } from "./agents.js";
|
|
6
6
|
function ours(cmd) {
|
|
7
|
-
return
|
|
7
|
+
return isOurHookCommand(cmd);
|
|
8
8
|
}
|
|
9
9
|
function readJson(file) {
|
|
10
10
|
if (!existsSync(file))
|
package/dist/install.js
CHANGED
|
@@ -7,18 +7,15 @@ import { grantToolPermissions, claudeSettingsFile, cursorCliConfigFile } from ".
|
|
|
7
7
|
import { PANTHEON_TOOL_NAMES, WRITE_TOOL_NAMES } from "./tools.js";
|
|
8
8
|
import { load } from "./config.js";
|
|
9
9
|
import { loadProfile } from "./profile.js";
|
|
10
|
+
import { agentSpawn, onPathNamed, which } from "./which.js";
|
|
10
11
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
11
12
|
export function onPath() {
|
|
12
|
-
|
|
13
|
-
execFileSync("sh", ["-lc", "command -v pantheon"], { stdio: ["ignore", "ignore", "ignore"] });
|
|
14
|
-
return true;
|
|
15
|
-
}
|
|
16
|
-
catch {
|
|
17
|
-
return false;
|
|
18
|
-
}
|
|
13
|
+
return onPathNamed("pantheon");
|
|
19
14
|
}
|
|
20
|
-
export function launcher(force) {
|
|
15
|
+
export function launcher(force, os = platform()) {
|
|
21
16
|
const portable = force === "portable" || (force !== "local" && onPath());
|
|
17
|
+
if (os === "win32")
|
|
18
|
+
return { command: process.execPath, args: [resolve(HERE, "cli.js"), "mcp"], portable: false };
|
|
22
19
|
return portable
|
|
23
20
|
? { command: "pantheon", args: ["mcp"], portable: true }
|
|
24
21
|
: { command: process.execPath, args: [resolve(HERE, "cli.js"), "mcp"], portable: false };
|
|
@@ -159,9 +156,17 @@ export function install(projectDir, cfg, force) {
|
|
|
159
156
|
function localEntry() {
|
|
160
157
|
return { command: process.execPath, args: [resolve(HERE, "cli.js"), "mcp"] };
|
|
161
158
|
}
|
|
162
|
-
function userEntry() {
|
|
159
|
+
function userEntry(os = platform()) {
|
|
160
|
+
if (os === "win32") {
|
|
161
|
+
const node = process.execPath;
|
|
162
|
+
const entry = resolve(HERE, "cli.js");
|
|
163
|
+
if (existsSync(entry))
|
|
164
|
+
return { command: node, args: [entry, "mcp"] };
|
|
165
|
+
const abs = which("pantheon");
|
|
166
|
+
return abs ? { command: abs, args: ["mcp"] } : { command: "pantheon", args: ["mcp"] };
|
|
167
|
+
}
|
|
163
168
|
try {
|
|
164
|
-
const abs =
|
|
169
|
+
const abs = which("pantheon") ?? "";
|
|
165
170
|
if (abs && !isVersionManagedPath(abs))
|
|
166
171
|
return { command: abs, args: ["mcp"] };
|
|
167
172
|
if (abs)
|
|
@@ -176,18 +181,17 @@ function isVersionManagedPath(p) {
|
|
|
176
181
|
}
|
|
177
182
|
function claudeUserScope(entry) {
|
|
178
183
|
const tool = "claude code";
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
catch {
|
|
184
|
+
const claudeBin = which("claude");
|
|
185
|
+
if (!claudeBin)
|
|
183
186
|
return { tool, where: "-", status: "skipped (claude not on PATH)" };
|
|
184
|
-
}
|
|
185
187
|
try {
|
|
188
|
+
const remove = agentSpawn(claudeBin, ["mcp", "remove", "pantheon", "-s", "user"]);
|
|
189
|
+
const add = agentSpawn(claudeBin, ["mcp", "add", "pantheon", "-s", "user", "--", entry.command, ...entry.args]);
|
|
186
190
|
try {
|
|
187
|
-
execFileSync(
|
|
191
|
+
execFileSync(remove.command, remove.args, { stdio: "ignore", ...remove.options });
|
|
188
192
|
}
|
|
189
193
|
catch { }
|
|
190
|
-
execFileSync(
|
|
194
|
+
execFileSync(add.command, add.args, { stdio: "ignore", ...add.options });
|
|
191
195
|
return { tool, where: "user scope", status: "ready in every project and every Conductor window" };
|
|
192
196
|
}
|
|
193
197
|
catch (e) {
|
|
@@ -207,13 +211,8 @@ function copilotUserScope(entry) {
|
|
|
207
211
|
const tool = "copilot";
|
|
208
212
|
const file = copilotMcpConfigFile();
|
|
209
213
|
let present = existsSync(dirname(file));
|
|
210
|
-
if (!present)
|
|
211
|
-
|
|
212
|
-
execFileSync("sh", ["-lc", "command -v copilot"], { stdio: "ignore" });
|
|
213
|
-
present = true;
|
|
214
|
-
}
|
|
215
|
-
catch { }
|
|
216
|
-
}
|
|
214
|
+
if (!present && onPathNamed("copilot"))
|
|
215
|
+
present = true;
|
|
217
216
|
if (!present)
|
|
218
217
|
return { tool, where: "-", status: "skipped (copilot not on PATH)" };
|
|
219
218
|
try {
|
package/dist/notify.js
CHANGED
|
@@ -5,6 +5,9 @@ import { whichPantheon } from "./service.js";
|
|
|
5
5
|
import { detectForHooks, agentById } from "./agents.js";
|
|
6
6
|
import { codexHookState, cursorHookState, copilotHookState, installCodexHooks, installCursorHooks, installCopilotHooks, removeCodexHooks, removeCursorHooks, removeCopilotHooks, } from "./hooks-install.js";
|
|
7
7
|
export const HOOK_MARKER = "pantheon hook";
|
|
8
|
+
export const HOOK_RE = /pantheon(?:\.cmd|\.exe|\.bat)?"?\s+hook\s/i;
|
|
9
|
+
export const isOurHookCommand = (cmd) => !!cmd && (cmd.includes(HOOK_MARKER) || HOOK_RE.test(cmd));
|
|
10
|
+
export const hookBin = (bin) => (/\s/.test(bin) ? `"${bin}"` : bin);
|
|
8
11
|
export function settingsPath(scope, projectDir = process.cwd()) {
|
|
9
12
|
return scope === "user"
|
|
10
13
|
? join(homedir(), ".claude", "settings.json")
|
|
@@ -25,7 +28,7 @@ function write(file, doc) {
|
|
|
25
28
|
writeFileSync(file, JSON.stringify(doc, null, 2) + "\n");
|
|
26
29
|
}
|
|
27
30
|
function ours(g) {
|
|
28
|
-
return g.hooks?.some((h) => h.command
|
|
31
|
+
return g.hooks?.some((h) => isOurHookCommand(h.command)) ?? false;
|
|
29
32
|
}
|
|
30
33
|
export function hookState(projectDir = process.cwd(), only) {
|
|
31
34
|
for (const scope of (only ? [only] : ["project", "user"])) {
|
|
@@ -46,7 +49,7 @@ export function hookState(projectDir = process.cwd(), only) {
|
|
|
46
49
|
if (!hit)
|
|
47
50
|
continue;
|
|
48
51
|
events.push(event);
|
|
49
|
-
command ??= hit.hooks.find((h) => h.command
|
|
52
|
+
command ??= hit.hooks.find((h) => isOurHookCommand(h.command)).command;
|
|
50
53
|
}
|
|
51
54
|
if (events.length)
|
|
52
55
|
return { installed: true, scope, file, command, events };
|
|
@@ -137,7 +140,10 @@ export function repairHook(home) {
|
|
|
137
140
|
function installClaudeHook(scope, projectDir = process.cwd()) {
|
|
138
141
|
const file = settingsPath(scope, projectDir);
|
|
139
142
|
const doc = read(file);
|
|
140
|
-
const
|
|
143
|
+
const found = whichPantheon();
|
|
144
|
+
if (!found)
|
|
145
|
+
throw new Error("pantheon binary not found on PATH");
|
|
146
|
+
const bin = hookBin(found);
|
|
141
147
|
const events = {
|
|
142
148
|
Stop: `${bin} hook stop`,
|
|
143
149
|
UserPromptSubmit: `${bin} hook prompt`,
|
package/dist/quickstart.js
CHANGED
|
@@ -16,6 +16,7 @@ import { desktopUnavailable, ephemeralWarning, hostEnv } from "./host-env.js";
|
|
|
16
16
|
import { existsSync } from "node:fs";
|
|
17
17
|
import { grant as grantCap, loadPolicy, policyPath, savePolicy } from "./worker-policy.js";
|
|
18
18
|
import { Relay } from "./relay.js";
|
|
19
|
+
import { which } from "./which.js";
|
|
19
20
|
const sh = (cmd) => {
|
|
20
21
|
try {
|
|
21
22
|
return execFileSync("sh", ["-lc", cmd], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim() || null;
|
|
@@ -126,7 +127,7 @@ export function findReach(port) {
|
|
|
126
127
|
if (ts && /^\d+\.\d+\.\d+\.\d+$/.test(ts)) {
|
|
127
128
|
return { url: `ws://${ts}:${port}`, how: `tailscale (${ts})`, shareable: true };
|
|
128
129
|
}
|
|
129
|
-
if (
|
|
130
|
+
if (which("cloudflared")) {
|
|
130
131
|
return {
|
|
131
132
|
url: `ws://127.0.0.1:${port}`, how: "localhost", shareable: false,
|
|
132
133
|
hint: `cloudflared is installed. To let a teammate reach you:\n` +
|
package/dist/service.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { writeFileSync, mkdirSync, existsSync, unlinkSync } from "node:fs";
|
|
2
2
|
import { execFileSync } from "node:child_process";
|
|
3
3
|
import { homedir, platform } from "node:os";
|
|
4
|
+
import { which } from "./which.js";
|
|
4
5
|
import { dirname, join, resolve } from "node:path";
|
|
5
6
|
import { fileURLToPath } from "node:url";
|
|
6
7
|
import { launcher } from "./install.js";
|
|
@@ -29,6 +30,8 @@ function argvFor(kind, args) {
|
|
|
29
30
|
return abs ? [abs, kind, ...args] : [process.execPath, resolve(HERE, "cli.js"), kind, ...args];
|
|
30
31
|
}
|
|
31
32
|
export function whichPantheon() {
|
|
33
|
+
if (platform() === "win32")
|
|
34
|
+
return which("pantheon");
|
|
32
35
|
try {
|
|
33
36
|
const p = execFileSync("/usr/bin/which", ["pantheon"], { encoding: "utf8" }).trim();
|
|
34
37
|
return p.startsWith("/") && existsSync(p) ? p : null;
|
package/dist/update-check.js
CHANGED
|
@@ -43,6 +43,8 @@ export function updateStatus(deps) {
|
|
|
43
43
|
export function versionLine(version, behind, latest) {
|
|
44
44
|
if (behind <= 0)
|
|
45
45
|
return `pantheon ${version}`;
|
|
46
|
+
if (latest && !semverGt(latest, version))
|
|
47
|
+
return `pantheon ${version}`;
|
|
46
48
|
return latest
|
|
47
49
|
? `pantheon ${version} · ${latest} available — run \`pantheon upgrade\``
|
|
48
50
|
: `pantheon ${version} · ${behind} update${behind === 1 ? "" : "s"} behind — run \`pantheon upgrade\``;
|
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
|
@@ -5,6 +5,7 @@ import { homedir } from "node:os";
|
|
|
5
5
|
import { join, resolve } from "node:path";
|
|
6
6
|
import { PantheonClient } from "./client.js";
|
|
7
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";
|
|
@@ -616,7 +617,8 @@ export class Worker {
|
|
|
616
617
|
return { obligation: o, branch, worktree, ok: false, detail };
|
|
617
618
|
}
|
|
618
619
|
const detail = await new Promise((res) => {
|
|
619
|
-
const
|
|
620
|
+
const sp = agentSpawn(command, args);
|
|
621
|
+
const child = procSpawn(sp.command, sp.args, { cwd: worktree, stdio: ["ignore", "pipe", "pipe"], env, ...sp.options });
|
|
620
622
|
let out = "";
|
|
621
623
|
child.stdout.on("data", (b) => { out += b.toString(); });
|
|
622
624
|
child.stderr.on("data", (b) => { out += b.toString(); });
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@join-pantheon/cli",
|
|
3
|
-
"version": "0.26.
|
|
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",
|