@join-pantheon/cli 0.23.1 → 0.24.0
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 +5 -3
- package/dist/agents.js +14 -2
- package/dist/cli.js +142 -13
- package/dist/doctor.js +8 -0
- package/dist/mcp-supervisor.js +403 -0
- package/dist/mcp-windows.js +135 -0
- package/dist/mcp.js +12 -2
- package/dist/notice.js +12 -3
- package/dist/notifier.js +28 -6
- package/dist/relay.js +281 -186
- package/dist/worker.js +5 -3
- package/helper/macos/main.swift +25 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -521,6 +521,8 @@ pantheon upgrade # installs the latest release and finishes the job
|
|
|
521
521
|
Every command prints its version and nudges you when a newer release exists. `pantheon upgrade`
|
|
522
522
|
installs the new release, re-wires your agents, restarts pantheon's own background services (the
|
|
523
523
|
worker, alerts, menu bar) so they load the new code, and asks any setup question the new version
|
|
524
|
-
has that you have never answered.
|
|
525
|
-
|
|
526
|
-
|
|
524
|
+
has that you have never answered. Open agent windows come along too: the server each window runs
|
|
525
|
+
notices the new version and switches to it within a minute, with no disconnect and nothing to
|
|
526
|
+
reopen (windows opened on a pantheon older than 0.24 need one `/mcp` reconnect; `pantheon doctor`
|
|
527
|
+
names them). A self-hosted relay is shared with your teammates, so it prints the restart for that
|
|
528
|
+
and leaves the timing to you.
|
package/dist/agents.js
CHANGED
|
@@ -17,6 +17,11 @@ const sh = (cmd) => {
|
|
|
17
17
|
return null;
|
|
18
18
|
}
|
|
19
19
|
};
|
|
20
|
+
export function cliOnPath(name) {
|
|
21
|
+
if (!binaryCache.has(`path:${name}`))
|
|
22
|
+
binaryCache.set(`path:${name}`, sh(`command -v ${name}`));
|
|
23
|
+
return binaryCache.get(`path:${name}`) ?? null;
|
|
24
|
+
}
|
|
20
25
|
export function agentById(id) {
|
|
21
26
|
return AGENTS.find((a) => a.id === id || a.binaries.includes(id));
|
|
22
27
|
}
|
|
@@ -57,8 +62,15 @@ export function detectOnPath() {
|
|
|
57
62
|
const binaryCache = new Map();
|
|
58
63
|
export function resolveBinary(backend) {
|
|
59
64
|
for (const b of backend.binaries) {
|
|
60
|
-
if (!binaryCache.has(b))
|
|
61
|
-
|
|
65
|
+
if (!binaryCache.has(b)) {
|
|
66
|
+
let found = sh(`command -v ${b}`) ? b : null;
|
|
67
|
+
if (found && b === "agent") {
|
|
68
|
+
const v = `${sh("agent --version 2>/dev/null | head -1") ?? ""} ${sh("agent --help 2>/dev/null | head -3") ?? ""}`;
|
|
69
|
+
if (!/cursor/i.test(v))
|
|
70
|
+
found = null;
|
|
71
|
+
}
|
|
72
|
+
binaryCache.set(b, found);
|
|
73
|
+
}
|
|
62
74
|
if (binaryCache.get(b))
|
|
63
75
|
return b;
|
|
64
76
|
}
|
package/dist/cli.js
CHANGED
|
@@ -7,7 +7,7 @@ process.emitWarning = ((warning, ...rest) => {
|
|
|
7
7
|
return _emitWarning(warning, ...rest);
|
|
8
8
|
});
|
|
9
9
|
import { randomBytes } from "node:crypto";
|
|
10
|
-
import { readFileSync } from "node:fs";
|
|
10
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
11
11
|
import { basename, dirname, join, resolve } from "node:path";
|
|
12
12
|
import { PantheonClient } from "./client.js";
|
|
13
13
|
import { fromInvite, load, save, toInvite, toJoinLink, resolveFor, loadAll, repoInfo, bindRepo, findMembership, setDefaultSpace, removeMembership, configFor, CONFIG_DIR, CONFIG_PATH, DEFAULT_RELAY, resolveForHook } from "./config.js";
|
|
@@ -38,6 +38,7 @@ import { LIMITS } from "./protocol.js";
|
|
|
38
38
|
import { loadPersonas, personaByName, describePersona, initPersona, personaDir } from "./persona.js";
|
|
39
39
|
import { fingerprint, newKeypair, signAccount } from "./identity.js";
|
|
40
40
|
import { VERSION } from "./version.js";
|
|
41
|
+
import * as mcpWindows from "./mcp-windows.js";
|
|
41
42
|
import { updateBanner } from "./update-check.js";
|
|
42
43
|
const argv = process.argv.slice(2);
|
|
43
44
|
const cmd = argv[0] ?? "help";
|
|
@@ -339,6 +340,39 @@ function workerMidRun() {
|
|
|
339
340
|
return false;
|
|
340
341
|
}
|
|
341
342
|
}
|
|
343
|
+
function staleWindowMessage(cwd) {
|
|
344
|
+
const { scanMcpWindows, RECONNECT_HINT } = mcpWindows;
|
|
345
|
+
const legacy = scanMcpWindows().filter((w) => w.kind === "legacy" && w.cwd === cwd);
|
|
346
|
+
if (!legacy.length)
|
|
347
|
+
return null;
|
|
348
|
+
const marker = join(CONFIG_DIR, "mcp", "nudged.json");
|
|
349
|
+
let seen = {};
|
|
350
|
+
try {
|
|
351
|
+
seen = JSON.parse(readFileSync(marker, "utf8"));
|
|
352
|
+
}
|
|
353
|
+
catch { }
|
|
354
|
+
const last = seen[cwd] ?? 0;
|
|
355
|
+
if (Date.now() - last < 60 * 60_000)
|
|
356
|
+
return null;
|
|
357
|
+
seen[cwd] = Date.now();
|
|
358
|
+
try {
|
|
359
|
+
mkdirSync(dirname(marker), { recursive: true });
|
|
360
|
+
writeFileSync(marker, JSON.stringify(seen));
|
|
361
|
+
}
|
|
362
|
+
catch { }
|
|
363
|
+
return `pantheon: this window's pantheon server is from before the installed version (${VERSION}) and cannot reach your team any more — ${RECONNECT_HINT}. Windows opened from now on update themselves.`;
|
|
364
|
+
}
|
|
365
|
+
function reportWindows(installed) {
|
|
366
|
+
const { scanMcpWindows, describeWindows, killOrphanWindows } = mcpWindows;
|
|
367
|
+
const windows = scanMcpWindows();
|
|
368
|
+
const ended = killOrphanWindows(windows);
|
|
369
|
+
const w = describeWindows(windows, installed);
|
|
370
|
+
console.error(`\n${B("agent windows:")} ${w.detail}`);
|
|
371
|
+
if (w.fix)
|
|
372
|
+
console.error(` ${D("->")} ${w.fix}`);
|
|
373
|
+
if (ended.length)
|
|
374
|
+
console.error(D(` ended ${ended.length} pantheon server${ended.length > 1 ? "s" : ""} left behind by windows that no longer exist`));
|
|
375
|
+
}
|
|
342
376
|
function printReady(tools) {
|
|
343
377
|
const workerOn = tools.some((t) => t.tool === "worker" && t.status.startsWith("on"));
|
|
344
378
|
console.error(`\n${B("You're set.")} Restart your agent once (quit and reopen) and it is connected.`);
|
|
@@ -781,6 +815,7 @@ async function main() {
|
|
|
781
815
|
const cur = load();
|
|
782
816
|
if (cur && loadProfile(cur).agent.wake_default === undefined)
|
|
783
817
|
await guidedSetup(cur, repoInfo(process.cwd())?.root ?? null);
|
|
818
|
+
reportWindows(r.from);
|
|
784
819
|
return;
|
|
785
820
|
}
|
|
786
821
|
console.error(`\n${B(`upgraded ${r.from} → ${r.to}`)}`);
|
|
@@ -820,7 +855,7 @@ async function main() {
|
|
|
820
855
|
else
|
|
821
856
|
console.error(D(`\n Your setup answers still apply — pantheon setup to change them.`));
|
|
822
857
|
}
|
|
823
|
-
|
|
858
|
+
reportWindows(r.to);
|
|
824
859
|
return;
|
|
825
860
|
}
|
|
826
861
|
case "setup": {
|
|
@@ -1260,6 +1295,17 @@ async function main() {
|
|
|
1260
1295
|
}
|
|
1261
1296
|
}
|
|
1262
1297
|
catch { }
|
|
1298
|
+
try {
|
|
1299
|
+
let cwd = process.cwd();
|
|
1300
|
+
try {
|
|
1301
|
+
cwd = JSON.parse(raw).cwd ?? cwd;
|
|
1302
|
+
}
|
|
1303
|
+
catch { }
|
|
1304
|
+
const msg = staleWindowMessage(cwd);
|
|
1305
|
+
if (msg)
|
|
1306
|
+
process.stdout.write(JSON.stringify({ systemMessage: msg }) + "\n");
|
|
1307
|
+
}
|
|
1308
|
+
catch { }
|
|
1263
1309
|
process.exit(0);
|
|
1264
1310
|
}
|
|
1265
1311
|
if (sub === "end") {
|
|
@@ -2122,6 +2168,13 @@ async function main() {
|
|
|
2122
2168
|
if (hud?.action === "details") {
|
|
2123
2169
|
const acts = await fetchHistory(cfg).catch(() => []);
|
|
2124
2170
|
const a = hud.act ? acts.find((x) => x.id === hud.act) : undefined;
|
|
2171
|
+
let o;
|
|
2172
|
+
if (hud.decision) {
|
|
2173
|
+
const c2 = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
2174
|
+
await c2.connect().catch((e) => die(e.message));
|
|
2175
|
+
o = c2.obligations.find((x) => x.id === hud.decision);
|
|
2176
|
+
c2.close();
|
|
2177
|
+
}
|
|
2125
2178
|
if (a) {
|
|
2126
2179
|
console.log(formatAct(a));
|
|
2127
2180
|
if (a.ref) {
|
|
@@ -2130,17 +2183,80 @@ async function main() {
|
|
|
2130
2183
|
console.log(D("\nin reply to:\n") + formatAct(r, { short: true }));
|
|
2131
2184
|
}
|
|
2132
2185
|
}
|
|
2133
|
-
else if (!
|
|
2186
|
+
else if (!o)
|
|
2134
2187
|
console.log(D("that act is no longer in what the relay holds"));
|
|
2135
|
-
if (
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2188
|
+
if (o)
|
|
2189
|
+
console.log(`\n${B("Decision required")} ${o.id}\n ${o.body}`);
|
|
2190
|
+
else if (hud.decision)
|
|
2191
|
+
console.log(D("\nthat decision is no longer waiting on you"));
|
|
2192
|
+
if (!process.stdin.isTTY || (!a && !o))
|
|
2193
|
+
return;
|
|
2194
|
+
const p0 = loadProfile(cfg);
|
|
2195
|
+
const agent = pickExplainAgent(hud.tool ?? p0.agent.primary_surface, detectAvailable());
|
|
2196
|
+
const goalId = a?.goal ?? o?.goal ?? null;
|
|
2197
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
2198
|
+
const ask = (q) => new Promise((res) => rl.question(q, (v) => res(v.trim())));
|
|
2199
|
+
try {
|
|
2200
|
+
for (;;) {
|
|
2201
|
+
console.error(`\n${B("Next")}${agent ? ` ${C("1")} open in ${agent.id} with this loaded` : ""} ${C("2")} reply from here${goalId ? ` ${C("3")} show the whole thread` : ""} ${D("Enter) close")}`);
|
|
2202
|
+
const choice = await ask(" choose: ");
|
|
2203
|
+
if (!choice)
|
|
2204
|
+
break;
|
|
2205
|
+
if (choice === "1" && agent) {
|
|
2206
|
+
rl.close();
|
|
2207
|
+
const prompt = o ? dp(o.id) : `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.`;
|
|
2208
|
+
console.error(D(`opening ${agent.id}…`));
|
|
2209
|
+
const { spawnSync } = await import("node:child_process");
|
|
2210
|
+
spawnSync(agent.binary, [prompt], { stdio: "inherit" });
|
|
2211
|
+
return;
|
|
2212
|
+
}
|
|
2213
|
+
if (choice === "2") {
|
|
2214
|
+
let kind = "NOTE";
|
|
2215
|
+
let ref = null;
|
|
2216
|
+
let to = null;
|
|
2217
|
+
if (o) {
|
|
2218
|
+
kind = "ANSWER";
|
|
2219
|
+
ref = o.ref;
|
|
2220
|
+
}
|
|
2221
|
+
else if (a) {
|
|
2222
|
+
ref = a.id;
|
|
2223
|
+
to = a.from === cfg.member ? null : [a.from];
|
|
2224
|
+
if (["ASK", "ESCALATE", "HUMAN_REQUIRED"].includes(a.act))
|
|
2225
|
+
kind = "ANSWER";
|
|
2226
|
+
else if (["DELIVERED", "PROPOSE", "CONTRACT"].includes(a.act)) {
|
|
2227
|
+
const v = (await ask(" accept or object? (a/o): ")).toLowerCase();
|
|
2228
|
+
kind = v.startsWith("o") ? "OBJECT" : "ACCEPT";
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
const text = await ask(` your ${kind} (one line): `);
|
|
2232
|
+
if (!text) {
|
|
2233
|
+
console.error(D(" nothing sent"));
|
|
2234
|
+
continue;
|
|
2235
|
+
}
|
|
2236
|
+
const c3 = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
2237
|
+
try {
|
|
2238
|
+
await c3.connect();
|
|
2239
|
+
const sent = await c3.send({ act: kind, body: text, goal: o ? null : goalId, ref, to });
|
|
2240
|
+
console.error(` ${C("sent")} ${kind} id=${sent.id}`);
|
|
2241
|
+
}
|
|
2242
|
+
catch (e) {
|
|
2243
|
+
console.error(` ${e.message}`);
|
|
2244
|
+
}
|
|
2245
|
+
finally {
|
|
2246
|
+
c3.close();
|
|
2247
|
+
}
|
|
2248
|
+
continue;
|
|
2249
|
+
}
|
|
2250
|
+
if (choice === "3" && goalId) {
|
|
2251
|
+
for (const t of selectActs(acts, { goal: goalId, last: 40 }))
|
|
2252
|
+
console.log(formatAct(t, { short: true }));
|
|
2253
|
+
continue;
|
|
2254
|
+
}
|
|
2255
|
+
console.error(D(" 1, 2, 3 or Enter"));
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
finally {
|
|
2259
|
+
rl.close();
|
|
2144
2260
|
}
|
|
2145
2261
|
return;
|
|
2146
2262
|
}
|
|
@@ -2150,6 +2266,14 @@ async function main() {
|
|
|
2150
2266
|
saveProfile({ ...p, agent: { ...p.agent, primary_surface: hud.tool }, updated_at: Date.now() });
|
|
2151
2267
|
console.error(D(`${hud.tool} is now where your decisions open`));
|
|
2152
2268
|
}
|
|
2269
|
+
if (hud.prompt && hud.tool === "grok") {
|
|
2270
|
+
const { cliOnPath } = await import("./agents.js");
|
|
2271
|
+
const bin = cliOnPath("grok") ?? die("grok is not on PATH here");
|
|
2272
|
+
console.error(D("opening grok…"));
|
|
2273
|
+
const { spawnSync } = await import("node:child_process");
|
|
2274
|
+
spawnSync(bin, [hud.prompt], { stdio: "inherit" });
|
|
2275
|
+
return;
|
|
2276
|
+
}
|
|
2153
2277
|
if (hud.prompt) {
|
|
2154
2278
|
const agent = pickExplainAgent(hud.tool ?? p.agent.primary_surface, detectAvailable());
|
|
2155
2279
|
if (!agent)
|
|
@@ -2537,7 +2661,12 @@ async function main() {
|
|
|
2537
2661
|
return;
|
|
2538
2662
|
}
|
|
2539
2663
|
case "mcp":
|
|
2540
|
-
|
|
2664
|
+
if (argv.includes("--child") || process.env.PANTHEON_MCP_SUPERVISED)
|
|
2665
|
+
await runMcp();
|
|
2666
|
+
else {
|
|
2667
|
+
const { superviseMcp } = await import("./mcp-supervisor.js");
|
|
2668
|
+
await superviseMcp();
|
|
2669
|
+
}
|
|
2541
2670
|
return;
|
|
2542
2671
|
case "limits":
|
|
2543
2672
|
console.log(JSON.stringify(LIMITS, null, 2));
|
package/dist/doctor.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { describeWindows, scanMcpWindows } from "./mcp-windows.js";
|
|
2
3
|
import { loadProfile, profileInitialized } from "./profile.js";
|
|
3
4
|
import { existsSync, readFileSync } from "node:fs";
|
|
4
5
|
import { join } from "node:path";
|
|
@@ -221,6 +222,13 @@ export async function doctor(projectDir) {
|
|
|
221
222
|
if (hookStates.some((s) => s.agent === "codex")) {
|
|
222
223
|
add("warn", "codex hooks", "installed — but codex skips new or changed hooks until they are approved", "run /hooks inside codex once to approve them (needs codex ≥ 0.115; sessions still report presence over MCP either way)");
|
|
223
224
|
}
|
|
225
|
+
const windows = scanMcpWindows();
|
|
226
|
+
const w = describeWindows(windows, VERSION);
|
|
227
|
+
add(w.level, "agent windows", w.detail, w.fix);
|
|
228
|
+
const orphans = windows.filter((x) => x.kind === "orphan");
|
|
229
|
+
if (orphans.length) {
|
|
230
|
+
add("warn", "orphans", `${orphans.length} pantheon server${orphans.length > 1 ? "s" : ""} left behind by windows that no longer exist (pid ${orphans.map((x) => x.pid).join(", ")})`, `harmless but pointless — pantheon upgrade ends them, or: kill ${orphans.map((x) => x.pid).join(" ")}`);
|
|
231
|
+
}
|
|
224
232
|
const personas = loadPersonas(projectDir);
|
|
225
233
|
if (personas.length) {
|
|
226
234
|
const broken = personas.filter((p) => p.warnings.length);
|