@join-pantheon/cli 0.28.0 → 0.29.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/dist/accounts.js +13 -2
- package/dist/agents.js +377 -9
- package/dist/cli.js +378 -192
- package/dist/client.js +73 -12
- package/dist/config.js +15 -0
- package/dist/doctor.js +13 -10
- package/dist/grants.js +16 -1
- package/dist/hooks-install.js +4 -2
- package/dist/host-env.js +51 -6
- package/dist/install.js +216 -16
- package/dist/kiro-files.js +23 -6
- package/dist/ledger.js +8 -2
- package/dist/machine.js +286 -60
- package/dist/mcp-supervisor.js +5 -0
- package/dist/mcp.js +33 -6
- package/dist/network.js +258 -57
- package/dist/pending.js +14 -7
- package/dist/quickstart.js +186 -22
- package/dist/refusal.js +29 -0
- package/dist/relay.js +738 -447
- package/dist/roster-file.js +19 -3
- package/dist/run-failure.js +18 -7
- package/dist/service.js +192 -40
- package/dist/setup-guide.js +21 -4
- package/dist/store.js +9 -3
- package/dist/which.js +66 -4
- package/dist/workdir.js +7 -1
- package/dist/worker.js +522 -101
- package/helper/macos/menubar.swift +11 -3
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -7,21 +7,21 @@ process.emitWarning = ((warning, ...rest) => {
|
|
|
7
7
|
return _emitWarning(warning, ...rest);
|
|
8
8
|
});
|
|
9
9
|
import { randomBytes } from "node:crypto";
|
|
10
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
11
|
-
import {
|
|
10
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { dirname, join, resolve } from "node:path";
|
|
12
12
|
import { PantheonClient } from "./client.js";
|
|
13
|
-
import { agentSpawn } from "./which.js";
|
|
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";
|
|
13
|
+
import { agentSpawn, CmdLineUnsafe } from "./which.js";
|
|
14
|
+
import { fromInvite, looksLikeInviteCode, resolveInviteCode, load, save, toInvite, toJoinLink, resolveFor, loadAll, identity, repoInfo, bindRepo, findMembership, setDefaultSpace, removeMembership, configFor, CONFIG_DIR, CONFIG_PATH, DEFAULT_RELAY, resolveForHook } from "./config.js";
|
|
15
15
|
import { fmtLimit } from "./plans.js";
|
|
16
16
|
import { validEmail, validUsername } from "./accounts.js";
|
|
17
|
-
import { install, launcher, installForUser
|
|
17
|
+
import { install, launcher, installForUser } from "./install.js";
|
|
18
18
|
import { Relay } from "./relay.js";
|
|
19
19
|
import { runMcp } from "./mcp.js";
|
|
20
20
|
import { Worker } from "./worker.js";
|
|
21
21
|
import { DEFAULT_REMINDER_MS, ensureNativeNotifier, HumanNotifier, sendDesktopNotification } from "./notifier.js";
|
|
22
22
|
import { desktopUnavailable, hostEnv } from "./host-env.js";
|
|
23
23
|
import { AGENT_SURFACES, applyProfileInit, cleanList, isAgentSurface, loadProfile, organizationProfile, profileInitialized, saveProfile, wakeDefault } from "./profile.js";
|
|
24
|
-
import { runGuide, terminalIO, parseSetupFlags, questionnaire } from "./setup-guide.js";
|
|
24
|
+
import { runGuide, terminalIO, parseSetupFlags, questionnaire, optOutsFrom } from "./setup-guide.js";
|
|
25
25
|
import { selectActs, formatAct, resolveRef } from "./history-view.js";
|
|
26
26
|
import { doctor, render, shareBlock } from "./doctor.js";
|
|
27
27
|
import { FEEDBACK_ADDRESS } from "./feedback.js";
|
|
@@ -30,17 +30,18 @@ import { assess } from "./untrusted.js";
|
|
|
30
30
|
import { report as spendReport, recentRuns } from "./spend.js";
|
|
31
31
|
import { renderBrief, renderTeam } from "./brief.js";
|
|
32
32
|
import { resolveDataDir, saveAll } from "./config.js";
|
|
33
|
-
import { currentPlan, installNetwork, redactProxy, normalizeProxy,
|
|
33
|
+
import { currentPlan, installNetwork, redactProxy, redactText, normalizeProxy, proxyToRemember, describeNetError } from "./network.js";
|
|
34
34
|
import { createInterface } from "node:readline";
|
|
35
35
|
import { homedir } from "node:os";
|
|
36
36
|
import { removeHook, decideStop, writeIntent, clearIntent, readIntent, installHooks, agentHookStates } from "./notify.js";
|
|
37
37
|
import { pending, render as renderPending } from "./pending.js";
|
|
38
|
-
import { start as qsStart, join as qsJoin, wireUp } from "./quickstart.js";
|
|
38
|
+
import { start as qsStart, join as qsJoin, wireUp, teamNameFor } from "./quickstart.js";
|
|
39
39
|
import { installService, uninstallService } from "./service.js";
|
|
40
40
|
import { LIMITS } from "./protocol.js";
|
|
41
41
|
import { loadPersonas, personaByName, describePersona, initPersona, personaDir } from "./persona.js";
|
|
42
42
|
import { fingerprint, newKeypair, signAccount } from "./identity.js";
|
|
43
43
|
import { VERSION } from "./version.js";
|
|
44
|
+
import { connectFailure } from "./refusal.js";
|
|
44
45
|
import * as mcpWindows from "./mcp-windows.js";
|
|
45
46
|
import { updateBanner } from "./update-check.js";
|
|
46
47
|
const argv = process.argv.slice(2);
|
|
@@ -62,7 +63,7 @@ async function signedFetch(cfg, method, path, params = {}) {
|
|
|
62
63
|
return res;
|
|
63
64
|
}
|
|
64
65
|
function requireSignInOnHostedRelay(relayFlag) {
|
|
65
|
-
const cfg =
|
|
66
|
+
const cfg = identity();
|
|
66
67
|
if (cfg?.account)
|
|
67
68
|
return;
|
|
68
69
|
const relay = relayFlag ?? cfg?.relay ?? process.env.PANTHEON_RELAY ?? DEFAULT_RELAY;
|
|
@@ -72,7 +73,7 @@ function requireSignInOnHostedRelay(relayFlag) {
|
|
|
72
73
|
die(`sign in first — this relay verifies email.\n New here: pantheon signup --email you@work.com --username you\n Have an account: pantheon login --email you@work.com\n Then run pantheon start again.`);
|
|
73
74
|
}
|
|
74
75
|
async function interactiveOnboard(relayArg) {
|
|
75
|
-
const prior =
|
|
76
|
+
const prior = identity();
|
|
76
77
|
const relay = relayArg ?? prior?.relay ?? process.env.PANTHEON_RELAY ?? DEFAULT_RELAY;
|
|
77
78
|
const base = relay.replace(/^ws/, "http").replace(/\/+$/, "");
|
|
78
79
|
if (!process.stdin.isTTY) {
|
|
@@ -152,7 +153,7 @@ async function interactiveOnboard(relayArg) {
|
|
|
152
153
|
console.error(D(` could not resend: ${rs?.j.error ?? "the relay did not send an email"} — try again in a minute.`));
|
|
153
154
|
continue;
|
|
154
155
|
}
|
|
155
|
-
const v = await postJson("/auth/verify", { email, code });
|
|
156
|
+
const v = await postJson("/auth/verify", { email, code, pubkey: kp.publicKey });
|
|
156
157
|
if (v.status === 200) {
|
|
157
158
|
const acct = v.j.account;
|
|
158
159
|
const cfg = { ...(prior ?? {}), relay, member: acct.username, space: prior?.space ?? "", token: prior?.token ?? "",
|
|
@@ -252,10 +253,44 @@ function targets() {
|
|
|
252
253
|
const r = resolveFor(process.cwd());
|
|
253
254
|
if (r.cfg && r.cfg.space && r.reason !== "default")
|
|
254
255
|
return [r.cfg];
|
|
255
|
-
if (r.reason === "default" && f)
|
|
256
|
+
if ((r.reason === "default" || r.reason === "unbound") && f && Object.keys(f.spaces ?? {}).length)
|
|
256
257
|
return Object.values(f.spaces ?? {}).map((m) => configFor(f, m, null));
|
|
257
258
|
return [need()];
|
|
258
259
|
}
|
|
260
|
+
async function launchAgent(binary, args, prompt, cwd) {
|
|
261
|
+
const { spawnSync } = await import("node:child_process");
|
|
262
|
+
let sp;
|
|
263
|
+
let handoff = null;
|
|
264
|
+
try {
|
|
265
|
+
sp = agentSpawn(binary, args(prompt));
|
|
266
|
+
}
|
|
267
|
+
catch (e) {
|
|
268
|
+
if (!(e instanceof CmdLineUnsafe))
|
|
269
|
+
throw e;
|
|
270
|
+
const where = cwd ?? process.cwd();
|
|
271
|
+
const rel = join(".pantheon", "handoff", `${Date.now()}-${randomBytes(3).toString("hex")}.md`);
|
|
272
|
+
handoff = join(where, rel);
|
|
273
|
+
mkdirSync(dirname(handoff), { recursive: true });
|
|
274
|
+
writeFileSync(handoff, prompt + "\n");
|
|
275
|
+
try {
|
|
276
|
+
const { excludeRunFiles } = await import("./worker.js");
|
|
277
|
+
excludeRunFiles(where);
|
|
278
|
+
}
|
|
279
|
+
catch { }
|
|
280
|
+
console.error(D(` the question is in ${rel} — the agent is asked to read it`));
|
|
281
|
+
sp = agentSpawn(binary, args(`Read the file ${rel.split(/[\\/]/).join("/")} in this folder and do what it asks.`));
|
|
282
|
+
}
|
|
283
|
+
try {
|
|
284
|
+
spawnSync(sp.command, sp.args, { stdio: "inherit", ...(cwd ? { cwd } : {}), ...sp.options });
|
|
285
|
+
}
|
|
286
|
+
finally {
|
|
287
|
+
if (handoff)
|
|
288
|
+
try {
|
|
289
|
+
rmSync(handoff, { force: true });
|
|
290
|
+
}
|
|
291
|
+
catch { }
|
|
292
|
+
}
|
|
293
|
+
}
|
|
259
294
|
function needStrict() {
|
|
260
295
|
const r = resolveFor(process.cwd(), { strict: true });
|
|
261
296
|
return r.cfg && r.cfg.space ? r.cfg : null;
|
|
@@ -286,7 +321,7 @@ async function guidedSetup(cfg, project, opts = {}) {
|
|
|
286
321
|
const io = rl ? terminalIO(rl, (l) => console.error(l)) : null;
|
|
287
322
|
console.error(`\n${B("A few choices, then you're done.")}${io ? "" : Object.keys(preset).length ? D(" (applying the answers you passed)") : D(" (no terminal — taking the defaults)")}`);
|
|
288
323
|
try {
|
|
289
|
-
const r = await runGuide(io, { cfg, project }, {
|
|
324
|
+
const r = await runGuide(io, { cfg, project, optOut: optOutsFrom(argv) }, {
|
|
290
325
|
publish: async (p) => {
|
|
291
326
|
const c = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
292
327
|
try {
|
|
@@ -336,23 +371,27 @@ async function workdirFor(cfg, hud) {
|
|
|
336
371
|
const bindings = all && m ? Object.values(all.bindings ?? {}).filter((b) => b.space === m.id) : [];
|
|
337
372
|
let hints = [];
|
|
338
373
|
if (bindings.length > 1 && hud.goal) {
|
|
339
|
-
const acts = await fetchHistory(cfg).catch(() => []);
|
|
374
|
+
const acts = await fetchHistory(cfg, { goal: hud.goal }).catch(() => []);
|
|
340
375
|
hints = acts.filter((x) => x.goal === hud.goal).flatMap((x) => [x.body, JSON.stringify(x.meta ?? {})]);
|
|
341
376
|
}
|
|
342
377
|
return pickWorkdir(bindings, hints, team);
|
|
343
378
|
}
|
|
344
|
-
async function fetchHistory(cfg,
|
|
379
|
+
async function fetchHistory(cfg, opts = {}) {
|
|
345
380
|
const base = cfg.relay.replace(/^ws/, "http").replace(/\/+$/, "");
|
|
346
|
-
const
|
|
381
|
+
const q = `space=${encodeURIComponent(cfg.space)}&token=${encodeURIComponent(cfg.token)}`
|
|
382
|
+
+ (opts.goal ? `&goal=${encodeURIComponent(opts.goal)}` : opts.since !== undefined ? `&since=${opts.since}` : "");
|
|
383
|
+
const res = await fetch(`${base}/history?${q}`, { signal: AbortSignal.timeout(8000) });
|
|
347
384
|
if (!res.ok)
|
|
348
385
|
throw new Error(`relay answered ${res.status} for the ledger`);
|
|
349
386
|
return (await res.json()).acts ?? [];
|
|
350
387
|
}
|
|
351
|
-
async function fetchAct(cfg, id) {
|
|
388
|
+
async function fetchAct(cfg, id, budgetMs = Number.POSITIVE_INFINITY) {
|
|
352
389
|
const base = cfg.relay.replace(/^ws/, "http").replace(/\/+$/, "");
|
|
353
390
|
const q = `space=${encodeURIComponent(cfg.space)}&token=${encodeURIComponent(cfg.token)}`;
|
|
391
|
+
const deadline = Date.now() + budgetMs;
|
|
392
|
+
const left = () => Math.max(1, Math.min(8000, deadline - Date.now()));
|
|
354
393
|
try {
|
|
355
|
-
const res = await fetch(`${base}/act?${q}&id=${encodeURIComponent(id)}`, { signal: AbortSignal.timeout(
|
|
394
|
+
const res = await fetch(`${base}/act?${q}&id=${encodeURIComponent(id)}`, { signal: AbortSignal.timeout(left()) });
|
|
356
395
|
if (res.ok) {
|
|
357
396
|
const body = await res.json();
|
|
358
397
|
if (body.act)
|
|
@@ -364,7 +403,9 @@ async function fetchAct(cfg, id) {
|
|
|
364
403
|
catch { }
|
|
365
404
|
let head = 0;
|
|
366
405
|
try {
|
|
367
|
-
|
|
406
|
+
if (Date.now() >= deadline)
|
|
407
|
+
return null;
|
|
408
|
+
const h = await (await fetch(`${base}/health?space=${encodeURIComponent(cfg.space)}`, { signal: AbortSignal.timeout(left()) })).json();
|
|
368
409
|
head = h.spaces?.[0]?.seq ?? 0;
|
|
369
410
|
}
|
|
370
411
|
catch { }
|
|
@@ -372,8 +413,10 @@ async function fetchAct(cfg, id) {
|
|
|
372
413
|
if (windows[windows.length - 1] !== 0)
|
|
373
414
|
windows.push(0);
|
|
374
415
|
for (const since of windows) {
|
|
416
|
+
if (Date.now() >= deadline)
|
|
417
|
+
return null;
|
|
375
418
|
try {
|
|
376
|
-
const acts = await fetchHistory(cfg, since);
|
|
419
|
+
const acts = await fetchHistory(cfg, { since });
|
|
377
420
|
const act = acts.find((a) => a.id === id || String(a.seq) === id);
|
|
378
421
|
if (act)
|
|
379
422
|
return { act };
|
|
@@ -551,18 +594,31 @@ ${B("less common")}
|
|
|
551
594
|
pantheon version the pantheon version this machine is running
|
|
552
595
|
`;
|
|
553
596
|
const QUIET = new Set(["mcp", "hook", "pending", "version", "tray-status"]);
|
|
597
|
+
function usageFor(command, help) {
|
|
598
|
+
const plain = help.replace(/\x1b\[[0-9;]*m/g, "").split("\n");
|
|
599
|
+
const out = [];
|
|
600
|
+
const names = new RegExp(`(^\\s+|·\\s+)pantheon ${command.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(\\s|$)`);
|
|
601
|
+
for (let i = 0; i < plain.length; i++) {
|
|
602
|
+
if (!names.test(plain[i]))
|
|
603
|
+
continue;
|
|
604
|
+
out.push(plain[i]);
|
|
605
|
+
while (i + 1 < plain.length && /^\s{20,}\S/.test(plain[i + 1]) && !/^\s+pantheon /.test(plain[i + 1]))
|
|
606
|
+
out.push(plain[++i]);
|
|
607
|
+
}
|
|
608
|
+
return out.length ? out.join("\n") : null;
|
|
609
|
+
}
|
|
610
|
+
const HELP_OPAQUE = new Set(["mcp", "hook", "pending", "tray-status", "help", "version", "-v", "--help", "-h"]);
|
|
554
611
|
function rememberWorkingProxy() {
|
|
555
612
|
try {
|
|
556
|
-
const plan = currentPlan();
|
|
557
|
-
const via = plan.https ?? plan.http;
|
|
558
|
-
if (!via || (plan.source !== "env" && !(plan.fallbackOnly && usedSystemFallback())))
|
|
559
|
-
return;
|
|
560
613
|
const all = loadAll();
|
|
561
|
-
if (!all
|
|
614
|
+
if (!all)
|
|
562
615
|
return;
|
|
563
|
-
const
|
|
564
|
-
|
|
565
|
-
|
|
616
|
+
const latest = Object.values(all.spaces ?? {}).sort((a, b) => (b.joined_at ?? 0) - (a.joined_at ?? 0))[0];
|
|
617
|
+
const keep = proxyToRemember(latest?.relay ?? all.relay, currentPlan(), all.network);
|
|
618
|
+
if (!keep)
|
|
619
|
+
return;
|
|
620
|
+
saveAll({ ...all, network: { ...keep, saved_at: Date.now() } });
|
|
621
|
+
console.error(D(` saved the proxy ${redactProxy(keep.proxy)} for this machine's other Pantheon processes (worker, editors) — change it with pantheon network`));
|
|
566
622
|
}
|
|
567
623
|
catch { }
|
|
568
624
|
}
|
|
@@ -578,19 +634,27 @@ async function main() {
|
|
|
578
634
|
catch { }
|
|
579
635
|
}
|
|
580
636
|
if (argv.length === 0) {
|
|
581
|
-
const cfg =
|
|
637
|
+
const cfg = identity();
|
|
582
638
|
if (cfg?.account) {
|
|
583
639
|
console.error(`${B("pantheon")} — signed in as ${C(cfg.account.username)}. ${D(`Your plate: pantheon brief · all commands: pantheon help`)}`);
|
|
584
640
|
return;
|
|
585
641
|
}
|
|
586
642
|
return interactiveOnboard();
|
|
587
643
|
}
|
|
644
|
+
if (!HELP_OPAQUE.has(cmd)) {
|
|
645
|
+
const helpAt = cmd === "say" ? [argv[1]] : argv.slice(1);
|
|
646
|
+
if (helpAt.some((a) => a === "--help" || a === "-h")) {
|
|
647
|
+
const u = usageFor(cmd, HELP_ALL);
|
|
648
|
+
console.error(u ? `${u}\n\n${D("nothing was run · all commands: pantheon help --all")}` : HELP);
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
588
652
|
switch (cmd) {
|
|
589
653
|
case "network": {
|
|
590
654
|
const all = loadAll() ?? die("not configured — sign up or join a team first");
|
|
591
655
|
const set = flag("proxy");
|
|
592
656
|
if (set !== undefined) {
|
|
593
|
-
const p = normalizeProxy(set) ?? die(`"${set}" is not a proxy address — use http://host:port (add user:password@ if it needs a login)`);
|
|
657
|
+
const p = normalizeProxy(set) ?? die(`"${redactText(set)}" is not a proxy address — use http://host:port (add user:password@ if it needs a login)`);
|
|
594
658
|
saveAll({ ...all, network: { proxy: p, ...(flag("no-proxy") ? { no_proxy: flag("no-proxy") } : {}), source: "pantheon network", saved_at: Date.now() } });
|
|
595
659
|
console.error(`${B("saved")} every Pantheon process on this machine now reaches the relay through ${redactProxy(p)}`);
|
|
596
660
|
return;
|
|
@@ -608,7 +672,7 @@ async function main() {
|
|
|
608
672
|
}
|
|
609
673
|
const plan = currentPlan();
|
|
610
674
|
const via = plan.https ?? plan.http;
|
|
611
|
-
console.log(`${B("network")} ${via ? `through ${redactProxy(via)}` : "direct"}${plan.source !== "none" ? D(` from ${plan.source}${plan.fallbackOnly ? ", used only if a direct connection fails" : ""}`) : ""}`);
|
|
675
|
+
console.log(`${B("network")} ${via ? `through ${redactProxy(via)}` : "direct"}${plan.source !== "none" ? D(` from ${plan.source}${plan.fallbackOnly ? ", used only if a direct connection fails" : plan.retryDirect ? " (saved by start/join; direct is tried if it cannot be reached)" : ""}`) : all.network?.proxy === "" ? D(" saved by pantheon network --direct") : ""}`);
|
|
612
676
|
if (via)
|
|
613
677
|
console.log(D(` not for: ${plan.noProxy.join(", ")}`));
|
|
614
678
|
if (plan.pac)
|
|
@@ -624,7 +688,7 @@ async function main() {
|
|
|
624
688
|
requireSignInOnHostedRelay(flag("relay"));
|
|
625
689
|
console.error(`${B("pantheon start")}\n`);
|
|
626
690
|
if (!flag("space") && !repoInfo(process.cwd()))
|
|
627
|
-
console.error(D(` No git repo here, so the team will be named after this folder: "${
|
|
691
|
+
console.error(D(` No git repo here, so the team will be named after this folder: "${teamNameFor(process.cwd())}". Pass --space <name> to choose.\n`));
|
|
628
692
|
const r = await qsStart({
|
|
629
693
|
port,
|
|
630
694
|
dataDir: resolveDataDir(flag("data")).dir,
|
|
@@ -633,6 +697,7 @@ async function main() {
|
|
|
633
697
|
...(flag("space") ? { space: flag("space") } : {}),
|
|
634
698
|
...(flag("me") ? { me: flag("me") } : {}),
|
|
635
699
|
...(flag("relay") ? { relay: flag("relay") } : {}),
|
|
700
|
+
...(setupFlags.preset.wake ? { wake: setupFlags.preset.wake } : {}),
|
|
636
701
|
hook: !argv.includes("--no-hook"),
|
|
637
702
|
notifications: !argv.includes("--no-notifications"),
|
|
638
703
|
menubar: !argv.includes("--no-menubar"),
|
|
@@ -685,7 +750,7 @@ async function main() {
|
|
|
685
750
|
case "invite": {
|
|
686
751
|
const cfg = need();
|
|
687
752
|
const client = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
688
|
-
await client.connect().catch((e) => die(client.pending ?? e.
|
|
753
|
+
await client.connect().catch((e) => die(client.pending ?? connectFailure(e, cfg.relay)));
|
|
689
754
|
await new Promise((r) => setTimeout(r, 250));
|
|
690
755
|
const revokeAt = argv.indexOf("--revoke");
|
|
691
756
|
const revokeArg = revokeAt >= 0 ? (argv[revokeAt + 1] && !argv[revokeAt + 1].startsWith("--") ? argv[revokeAt + 1] : "all") : undefined;
|
|
@@ -728,6 +793,8 @@ async function main() {
|
|
|
728
793
|
return;
|
|
729
794
|
}
|
|
730
795
|
case "relay": {
|
|
796
|
+
process.on("unhandledRejection", (e) => { console.error(`! unhandled rejection: ${e?.stack ?? String(e)}`); });
|
|
797
|
+
process.on("uncaughtException", (e) => { console.error(`! uncaught exception (kept serving): ${e?.stack ?? String(e)}`); });
|
|
731
798
|
const relay = new Relay({
|
|
732
799
|
port: Number(flag("port", process.env.PORT ?? "8787")),
|
|
733
800
|
host: flag("host", "0.0.0.0"),
|
|
@@ -757,16 +824,18 @@ async function main() {
|
|
|
757
824
|
return;
|
|
758
825
|
}
|
|
759
826
|
case "init": {
|
|
760
|
-
const
|
|
827
|
+
const had = argv.includes("--reset") ? null : identity();
|
|
828
|
+
const kp = had?.publicKey && had.privateKey ? { publicKey: had.publicKey, privateKey: had.privateKey } : newKeypair();
|
|
761
829
|
const cfg = {
|
|
762
830
|
relay: flag("relay", "ws://localhost:8787"),
|
|
763
831
|
space: flag("space", "team"),
|
|
764
832
|
token: flag("token", randomBytes(16).toString("hex")),
|
|
765
|
-
member: flag("me"
|
|
833
|
+
member: flag("me") ?? had?.member ?? process.env.USER ?? "me",
|
|
766
834
|
publicKey: kp.publicKey,
|
|
767
835
|
privateKey: kp.privateKey,
|
|
768
836
|
display: flag("display"),
|
|
769
837
|
agent: flag("agent", "unknown"),
|
|
838
|
+
...(had?.account ? { account: had.account } : {}),
|
|
770
839
|
};
|
|
771
840
|
save(cfg);
|
|
772
841
|
console.error(`${B("space claimed.")} config: ${D(CONFIG_PATH)}\n`);
|
|
@@ -813,7 +882,7 @@ async function main() {
|
|
|
813
882
|
parsed = await resolveInviteCode(invite, candidates).catch((err) => die(err.message.replace(/^INVITE_CODE_[A-Z]+:\s*/, "")));
|
|
814
883
|
console.error(D(`That was just the code — it opens "${parsed.space}" on ${new URL(parsed.relay).host}. Joining that team.\n`));
|
|
815
884
|
}
|
|
816
|
-
if (!
|
|
885
|
+
if (!identity()?.account && sameHost(parsed.relay, DEFAULT_RELAY)) {
|
|
817
886
|
die(`sign in first — this relay verifies email.\n New here: pantheon signup --email you@work.com --username you\n Have an account: pantheon login --email you@work.com\n Then run this same \`pantheon join\` command again — the invite is still good.`);
|
|
818
887
|
}
|
|
819
888
|
const wp = unattendedProject();
|
|
@@ -888,6 +957,22 @@ async function main() {
|
|
|
888
957
|
}
|
|
889
958
|
case "rewire": {
|
|
890
959
|
const rows = installForUser().map((t) => ({ tool: t.tool, status: t.status }));
|
|
960
|
+
try {
|
|
961
|
+
const { refreshServiceDefinition, serviceInstalled: installed, restartService: restart } = await import("./service.js");
|
|
962
|
+
for (const kind of ["worker", "notifier", "menubar"]) {
|
|
963
|
+
if (!installed(kind))
|
|
964
|
+
continue;
|
|
965
|
+
const fresh = refreshServiceDefinition(kind);
|
|
966
|
+
if (fresh.line)
|
|
967
|
+
console.error(fresh.line);
|
|
968
|
+
if (fresh.changed && kind !== "worker")
|
|
969
|
+
for (const l of restart(kind, undefined, { reload: true }))
|
|
970
|
+
console.error(l);
|
|
971
|
+
else if (fresh.changed)
|
|
972
|
+
console.error(D(" worker: the new definition applies at its next restart"));
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
catch { }
|
|
891
976
|
if (argv.includes("--json")) {
|
|
892
977
|
console.log(JSON.stringify(rows));
|
|
893
978
|
return;
|
|
@@ -958,6 +1043,7 @@ async function main() {
|
|
|
958
1043
|
}
|
|
959
1044
|
}
|
|
960
1045
|
}
|
|
1046
|
+
const { refreshServiceDefinition } = await import("./service.js");
|
|
961
1047
|
for (const kind of ["worker", "notifier", "menubar"]) {
|
|
962
1048
|
if (!serviceInstalled(kind))
|
|
963
1049
|
continue;
|
|
@@ -965,7 +1051,10 @@ async function main() {
|
|
|
965
1051
|
console.error(` ${D("worker: a run is in progress — left alone. When it is done:")} pantheon service restart worker`);
|
|
966
1052
|
continue;
|
|
967
1053
|
}
|
|
968
|
-
|
|
1054
|
+
const fresh = refreshServiceDefinition(kind);
|
|
1055
|
+
if (fresh.line)
|
|
1056
|
+
console.error(fresh.line);
|
|
1057
|
+
for (const l of restartService(kind, undefined, { reload: fresh.changed }))
|
|
969
1058
|
console.error(l);
|
|
970
1059
|
}
|
|
971
1060
|
if (r.restart.includes("relay"))
|
|
@@ -1005,7 +1094,7 @@ async function main() {
|
|
|
1005
1094
|
console.error(client.pending);
|
|
1006
1095
|
process.exit(2);
|
|
1007
1096
|
}
|
|
1008
|
-
die(
|
|
1097
|
+
die(connectFailure(e, cfg.relay));
|
|
1009
1098
|
});
|
|
1010
1099
|
await new Promise((r) => setTimeout(r, 350));
|
|
1011
1100
|
const peers = client.roster.filter((m) => m.id !== cfg.member);
|
|
@@ -1073,7 +1162,7 @@ async function main() {
|
|
|
1073
1162
|
for (const cfg of targets()) {
|
|
1074
1163
|
const profile = loadProfile(cfg);
|
|
1075
1164
|
const client = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
1076
|
-
await client.connect().catch((e) => die(client.pending ?? e.
|
|
1165
|
+
await client.connect().catch((e) => die(client.pending ?? connectFailure(e, cfg.relay)));
|
|
1077
1166
|
await new Promise((r) => setTimeout(r, 350));
|
|
1078
1167
|
process.stdout.write(renderBrief({
|
|
1079
1168
|
space: cfg.space,
|
|
@@ -1103,7 +1192,7 @@ async function main() {
|
|
|
1103
1192
|
}
|
|
1104
1193
|
const cfg = need();
|
|
1105
1194
|
const client = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
1106
|
-
await client.connect().catch((e) => die(client.pending ?? e.
|
|
1195
|
+
await client.connect().catch((e) => die(client.pending ?? connectFailure(e, cfg.relay)));
|
|
1107
1196
|
await new Promise((r) => setTimeout(r, 350));
|
|
1108
1197
|
process.stdout.write(renderTeam({
|
|
1109
1198
|
space: cfg.space,
|
|
@@ -1131,7 +1220,7 @@ async function main() {
|
|
|
1131
1220
|
};
|
|
1132
1221
|
const base = cfg.relay.replace(/^ws/, "http").replace(/\/+$/, "");
|
|
1133
1222
|
const q = `space=${encodeURIComponent(cfg.space)}&token=${encodeURIComponent(cfg.token)}`;
|
|
1134
|
-
const first = await fetch(`${base}/history?${q}`).catch((e) => die(
|
|
1223
|
+
const first = await fetch(`${base}/history?${q}`).catch((e) => die(describeNetError(e, cfg.relay)));
|
|
1135
1224
|
if (!first.ok)
|
|
1136
1225
|
die(`relay answered ${first.status} — check the space name and token`);
|
|
1137
1226
|
let since = ((await first.json()).acts ?? []).at(-1)?.seq ?? 0;
|
|
@@ -1160,7 +1249,8 @@ async function main() {
|
|
|
1160
1249
|
}
|
|
1161
1250
|
case "history": {
|
|
1162
1251
|
const cfg = need();
|
|
1163
|
-
const acts = await fetchHistory(cfg,
|
|
1252
|
+
const acts = await fetchHistory(cfg, flag("goal") ? { goal: flag("goal") } : flag("since") ? { since: numFlag("since", 0) } : {})
|
|
1253
|
+
.catch((e) => die(`cannot read the ledger — ${e.message}`));
|
|
1164
1254
|
const picked = selectActs(acts, {
|
|
1165
1255
|
...(flag("goal") ? { goal: flag("goal") } : {}), ...(flag("from") ? { from: flag("from") } : {}),
|
|
1166
1256
|
last: numFlag("last", 20), presence: argv.includes("--presence"),
|
|
@@ -1192,11 +1282,11 @@ async function main() {
|
|
|
1192
1282
|
case "show": {
|
|
1193
1283
|
const cfg = need();
|
|
1194
1284
|
const key = argv[1] ?? die("usage: pantheon show <act id | sequence number>");
|
|
1195
|
-
const
|
|
1196
|
-
const a =
|
|
1197
|
-
console.log(formatAct(a));
|
|
1285
|
+
const found = await fetchAct(cfg, key);
|
|
1286
|
+
const a = found?.act ?? die(`no act ${key} in what the relay still holds (pantheon history lists them)`);
|
|
1287
|
+
console.log(formatAct(a, found?.goalTitle ? { goalTitle: found.goalTitle } : {}));
|
|
1198
1288
|
if (a.ref) {
|
|
1199
|
-
const r =
|
|
1289
|
+
const r = (await fetchAct(cfg, a.ref))?.act;
|
|
1200
1290
|
if (r)
|
|
1201
1291
|
console.log(D(`\nin reply to:\n`) + formatAct(r, { short: true }));
|
|
1202
1292
|
}
|
|
@@ -1211,7 +1301,7 @@ async function main() {
|
|
|
1211
1301
|
die("nothing to say — pantheon say NOTE 'your message here'");
|
|
1212
1302
|
const summary = flag("summary");
|
|
1213
1303
|
const client = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
1214
|
-
await client.connect().catch((e) => die(
|
|
1304
|
+
await client.connect().catch((e) => die(connectFailure(e, cfg.relay)));
|
|
1215
1305
|
const to = flag("to");
|
|
1216
1306
|
const meta = {};
|
|
1217
1307
|
for (const k of ["branch", "commit", "remote"]) {
|
|
@@ -1225,7 +1315,15 @@ async function main() {
|
|
|
1225
1315
|
let goalArg = flag("goal") ?? null;
|
|
1226
1316
|
let ref = flag("ref") ?? null;
|
|
1227
1317
|
if (ref !== null || ["ANSWER", "ACCEPT", "OBJECT"].includes(act)) {
|
|
1228
|
-
|
|
1318
|
+
let acts;
|
|
1319
|
+
if (ref && ref !== "last") {
|
|
1320
|
+
const hit = (await fetchAct(cfg, ref))?.act;
|
|
1321
|
+
acts = hit ? [hit] : [];
|
|
1322
|
+
if (!hit && /^a_/.test(ref))
|
|
1323
|
+
console.error(D(` ${ref} is not in what this relay holds — sending it as given`));
|
|
1324
|
+
}
|
|
1325
|
+
else
|
|
1326
|
+
acts = await fetchHistory(cfg, goalArg ? { goal: goalArg } : {}).catch(() => []);
|
|
1229
1327
|
const rr = resolveRef(acts, ref, act, goalArg, cfg.member);
|
|
1230
1328
|
if (rr.error) {
|
|
1231
1329
|
client.close();
|
|
@@ -1351,23 +1449,17 @@ async function main() {
|
|
|
1351
1449
|
return;
|
|
1352
1450
|
}
|
|
1353
1451
|
case "stop": {
|
|
1354
|
-
const what = (argv[1]
|
|
1452
|
+
const what = (argv[1] && !argv[1].startsWith("--") ? argv[1] : "all").toLowerCase();
|
|
1453
|
+
if (!["all", "relay", "worker", "notifier", "menubar"].includes(what))
|
|
1454
|
+
die(`usage: pantheon stop [relay|worker|notifier|menubar|all]`);
|
|
1455
|
+
const { stopMachine } = await import("./machine.js");
|
|
1355
1456
|
console.log(B("stopping"));
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
if (what === "notifier" || what === "all")
|
|
1363
|
-
for (const l of stopProc("notifier"))
|
|
1364
|
-
console.log(` ${l}`);
|
|
1365
|
-
if (what === "menubar" || what === "all")
|
|
1366
|
-
for (const l of stopProc("menubar"))
|
|
1367
|
-
console.log(` ${l}`);
|
|
1368
|
-
if (what === "relay" || what === "all") {
|
|
1369
|
-
console.log(D(`\n Your teammates are offline until a relay is running again.`));
|
|
1370
|
-
}
|
|
1457
|
+
const everyTeam = Object.values(loadAll()?.spaces ?? {}).map((m) => ({ name: m.name, relay: m.relay }));
|
|
1458
|
+
const res = stopMachine(what, { port: Number(flag("port", "8787")), teams: everyTeam.length ? everyTeam : (load()?.relay ? [{ name: load().space, relay: load().relay }] : []) });
|
|
1459
|
+
for (const l of res.lines)
|
|
1460
|
+
console.log(` ${l}`);
|
|
1461
|
+
if (res.note)
|
|
1462
|
+
console.log(D(`\n ${res.note}`));
|
|
1371
1463
|
return;
|
|
1372
1464
|
}
|
|
1373
1465
|
case "logs": {
|
|
@@ -1375,7 +1467,7 @@ async function main() {
|
|
|
1375
1467
|
const proc = kind === "relay" ? relayProc(Number(flag("port", "8787"))) : kind === "worker" ? workerProc() : notifierProc();
|
|
1376
1468
|
const f = logFileFor(kind, proc);
|
|
1377
1469
|
if (!f)
|
|
1378
|
-
die(`no ${kind} log found. If it runs as a service, the plist names the path; if you started it by hand, its output went to that terminal.`);
|
|
1470
|
+
die(`no ${kind} log found. ${process.platform === "linux" ? `If it runs as a service: journalctl --user -u pantheon-${kind}` : process.platform === "darwin" ? "If it runs as a service, the plist names the path" : "Services are not used on Windows"}; if you started it by hand, its output went to that terminal.`);
|
|
1379
1471
|
console.error(D(`${f} (${statFile(f)})\n`));
|
|
1380
1472
|
const follow = argv.includes("-f") || argv.includes("--follow");
|
|
1381
1473
|
const { spawn } = await import("node:child_process");
|
|
@@ -1664,7 +1756,7 @@ async function main() {
|
|
|
1664
1756
|
if (!argv.includes("--yes"))
|
|
1665
1757
|
die(`this deletes "${m.name}" for everyone in it — its history, goals and invites — and cannot be undone.\n If you mean it: pantheon space delete ${m.name} --yes`);
|
|
1666
1758
|
const client = new PantheonClient(configFor(all, m, null), undefined, { role: "cli" });
|
|
1667
|
-
await client.connect().catch((e) => die(client.pending ?? e.
|
|
1759
|
+
await client.connect().catch((e) => die(client.pending ?? connectFailure(e, m.relay)));
|
|
1668
1760
|
await client.deleteSpace(m.name).catch((e) => { client.close(); die(e.message); });
|
|
1669
1761
|
client.close();
|
|
1670
1762
|
const bound = Object.values(all.bindings ?? {}).filter((b) => b.space === m.id).length;
|
|
@@ -1682,53 +1774,103 @@ async function main() {
|
|
|
1682
1774
|
console.error(D(`Your teammates still list you in "${m.name}". Ask one of them to run: pantheon admit ${m.name === all.member ? "you" : all.member} --revoke if you are not coming back.`));
|
|
1683
1775
|
return;
|
|
1684
1776
|
}
|
|
1685
|
-
const
|
|
1777
|
+
const all = loadAll();
|
|
1778
|
+
const me = identity(all);
|
|
1779
|
+
const { leavePlan } = await import("./machine.js");
|
|
1780
|
+
const plan = leavePlan(all);
|
|
1686
1781
|
const project = resolve(flag("project", "."));
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1782
|
+
const keepIdentity = argv.includes("--keep-identity");
|
|
1783
|
+
if (plan.teams.length > 1 && !keepIdentity && !argv.includes("--yes")) {
|
|
1784
|
+
const list = plan.teams.map((t) => `"${t.name}"`).join(", ");
|
|
1785
|
+
if (!process.stdin.isTTY)
|
|
1786
|
+
die(`this machine is in ${plan.teams.length} teams — ${list}. Leaving removes its key, so it leaves ALL of them.\n If you mean it: pantheon leave --yes\n Just one team: pantheon leave <team>`);
|
|
1787
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
1788
|
+
const answer = await new Promise((res) => rl.question(`This machine is in ${plan.teams.length} teams: ${list}. Leaving removes its key, so it leaves ALL of them. Type "yes" to continue: `, (x) => res(x.trim())));
|
|
1789
|
+
rl.close();
|
|
1790
|
+
if (answer.toLowerCase() !== "yes")
|
|
1791
|
+
die(`nothing was changed. Leave one team with: pantheon leave <team>`);
|
|
1792
|
+
}
|
|
1793
|
+
console.error(`${B("leaving pantheon")}${plan.teams.length ? D(` — ${plan.teams.map((t) => t.name).join(", ")}`) : ""}\n`);
|
|
1794
|
+
const { uninstallMachine, realUninstallIo } = await import("./machine.js");
|
|
1795
|
+
const { removeUserMcpEntries } = await import("./install.js");
|
|
1796
|
+
const done = uninstallMachine({ project, stopRelay: argv.includes("--stop-relay") }, realUninstallIo({
|
|
1797
|
+
port: Number(flag("port", "8787")),
|
|
1798
|
+
removeMcpEntries: () => removeUserMcpEntries({ project }),
|
|
1799
|
+
hasIntent: () => readIntent() !== null,
|
|
1800
|
+
clearIntent: () => clearIntent(),
|
|
1801
|
+
}));
|
|
1802
|
+
for (const l of done.lines)
|
|
1693
1803
|
console.error(` ${l}`);
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
console.error(` ${l}`);
|
|
1697
|
-
else if (relayProc(Number(flag("port", "8787"))).running) {
|
|
1698
|
-
console.error(` relay: left running — others may be using it. Stop it with: pantheon stop relay`);
|
|
1699
|
-
}
|
|
1804
|
+
const removed = [...done.removed];
|
|
1805
|
+
const left = [...done.left];
|
|
1700
1806
|
for (const l of removeHook(project))
|
|
1701
1807
|
console.error(` hook: ${l.trim()}`);
|
|
1702
1808
|
const { revokeToolPermissions } = await import("./grants.js");
|
|
1703
|
-
for (const l of revokeToolPermissions())
|
|
1809
|
+
for (const l of revokeToolPermissions()) {
|
|
1704
1810
|
console.error(` perms:${l}`);
|
|
1705
|
-
|
|
1706
|
-
const l = removeCodexUserScope();
|
|
1707
|
-
if (l)
|
|
1708
|
-
console.error(` perms:${l}`);
|
|
1811
|
+
removed.push(l.trim().replace(/^removed /, ""));
|
|
1709
1812
|
}
|
|
1710
|
-
const { unlinkSync, existsSync: exists, rmSync } = await import("node:fs");
|
|
1711
|
-
if (
|
|
1712
|
-
if (
|
|
1713
|
-
|
|
1813
|
+
const { unlinkSync, existsSync: exists, rmSync, readdirSync } = await import("node:fs");
|
|
1814
|
+
if (all && exists(CONFIG_PATH)) {
|
|
1815
|
+
if (keepIdentity) {
|
|
1816
|
+
left.push(`your identity and ${plan.teams.length} team membership${plan.teams.length === 1 ? "" : "s"}, ${CONFIG_PATH} (--keep-identity)`);
|
|
1714
1817
|
}
|
|
1715
1818
|
else {
|
|
1716
1819
|
unlinkSync(CONFIG_PATH);
|
|
1820
|
+
removed.push(`${CONFIG_PATH} — your key and ${plan.teams.length ? `the token${plan.teams.length === 1 ? "" : "s"} for ${plan.teams.map((t) => `"${t.name}"`).join(", ")}` : "account"}; rejoining means a new invite`);
|
|
1821
|
+
const runHomes = join(CONFIG_PATH, "..", "run-homes");
|
|
1822
|
+
const { restorePendingQuarantines, pendingQuarantines } = await import("./agents.js");
|
|
1823
|
+
const put = restorePendingQuarantines(runHomes);
|
|
1824
|
+
if (put.length)
|
|
1825
|
+
removed.push(`nothing of your repos' — what interrupted runs had held aside: ${put.join("; ")}`);
|
|
1826
|
+
const still = pendingQuarantines(runHomes);
|
|
1827
|
+
const keptAside = (dir) => { try {
|
|
1828
|
+
return readdirSync(join(dir, "quarantine-kept")).length > 0;
|
|
1829
|
+
}
|
|
1830
|
+
catch {
|
|
1831
|
+
return false;
|
|
1832
|
+
} };
|
|
1833
|
+
let homes = [];
|
|
1717
1834
|
try {
|
|
1718
|
-
|
|
1835
|
+
homes = readdirSync(runHomes);
|
|
1719
1836
|
}
|
|
1720
1837
|
catch { }
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1838
|
+
const keep = new Set([...still.map((x) => x.runHome), ...homes.map((h) => join(runHomes, h)).filter(keptAside)]);
|
|
1839
|
+
for (const h of homes) {
|
|
1840
|
+
const dir = join(runHomes, h);
|
|
1841
|
+
if (keep.has(dir))
|
|
1842
|
+
continue;
|
|
1843
|
+
try {
|
|
1844
|
+
rmSync(dir, { recursive: true, force: true });
|
|
1845
|
+
}
|
|
1846
|
+
catch { }
|
|
1847
|
+
}
|
|
1848
|
+
if (!keep.size) {
|
|
1849
|
+
try {
|
|
1850
|
+
rmSync(runHomes, { recursive: true, force: true });
|
|
1851
|
+
}
|
|
1852
|
+
catch { }
|
|
1853
|
+
}
|
|
1854
|
+
for (const x of still)
|
|
1855
|
+
left.push(`${x.runHome} — it still holds ${x.paths.join(", ")} from ${x.worktree}, which could not be put back safely; compare and move them back by hand, then delete the folder`);
|
|
1856
|
+
for (const dir of keep)
|
|
1857
|
+
if (keptAside(dir) && !still.some((x) => x.runHome === dir))
|
|
1858
|
+
left.push(`${join(dir, "quarantine-kept")} — agent config an interrupted run held, set aside because the repo had moved on; check it, then delete the folder`);
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
console.error(`\n${B("Removed")}`);
|
|
1862
|
+
for (const r of removed)
|
|
1863
|
+
console.error(` ${C("✓")} ${r}`);
|
|
1864
|
+
if (!removed.length)
|
|
1865
|
+
console.error(` ${D("nothing — Pantheon had nothing installed here")}`);
|
|
1866
|
+
console.error(`\n${B("Left")}`);
|
|
1867
|
+
for (const l of left)
|
|
1868
|
+
console.error(` • ${l}`);
|
|
1869
|
+
console.error(` • ${D("files committed in your repos (.mcp.json, .cursor/mcp.json, .vscode/mcp.json, the brief in AGENTS.md / CLAUDE.md) — your team's; delete them in a commit if the whole team is leaving")}`);
|
|
1870
|
+
console.error(` • ${D("the relay's ledger on disk, and anything you pushed to git")}`);
|
|
1871
|
+
if (plan.teams.length) {
|
|
1872
|
+
console.error(`\n ${D(`Your teammates still list you in ${plan.teams.map((t) => `"${t.name}"`).join(", ")}. Ask someone in each to run:`)}`);
|
|
1873
|
+
console.error(` ${D(`pantheon admit ${me?.member ?? plan.member ?? "<your name>"} --revoke`)}`);
|
|
1732
1874
|
}
|
|
1733
1875
|
console.error(`\n ${D(`If Pantheon let you down, one line on why to ${FEEDBACK_ADDRESS} would help more than you might think.`)}`);
|
|
1734
1876
|
return;
|
|
@@ -1741,35 +1883,42 @@ async function main() {
|
|
|
1741
1883
|
return;
|
|
1742
1884
|
}
|
|
1743
1885
|
case "panic": {
|
|
1744
|
-
const cfg = need();
|
|
1745
1886
|
console.error(`${B("PANIC — locking down this machine")}\n`);
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
const
|
|
1749
|
-
const
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1887
|
+
const { panicMachine, waitStopped } = await import("./machine.js");
|
|
1888
|
+
const all = loadAll();
|
|
1889
|
+
const teams = Object.values(all?.spaces ?? {});
|
|
1890
|
+
const r = await panicMachine({
|
|
1891
|
+
stopWorker: () => stopProc("worker"),
|
|
1892
|
+
workerDown: () => waitStopped("worker", 5000),
|
|
1893
|
+
teams,
|
|
1894
|
+
revoke: async (m) => {
|
|
1895
|
+
const cfg = configFor(all, m, null);
|
|
1896
|
+
const client = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
1897
|
+
try {
|
|
1898
|
+
await client.connect();
|
|
1899
|
+
await new Promise((res) => setTimeout(res, 300));
|
|
1900
|
+
const wakers = client.members.filter((x) => x.admitted && x.may_wake && x.member !== cfg.member);
|
|
1901
|
+
for (const w of wakers)
|
|
1902
|
+
await client.admit(w.member, false, false).catch(() => { });
|
|
1903
|
+
return wakers.map((w) => w.member);
|
|
1904
|
+
}
|
|
1905
|
+
finally {
|
|
1906
|
+
client.close();
|
|
1907
|
+
}
|
|
1908
|
+
},
|
|
1909
|
+
});
|
|
1910
|
+
for (const l of r.lines)
|
|
1911
|
+
console.error(l);
|
|
1765
1912
|
console.error(D(` Your peers can still message you; only unattended runs are off.`));
|
|
1766
1913
|
console.error(D(` To undo: pantheon service install worker and pantheon admit <who> --may-wake`));
|
|
1914
|
+
if (!r.ok)
|
|
1915
|
+
process.exitCode = 1;
|
|
1767
1916
|
return;
|
|
1768
1917
|
}
|
|
1769
1918
|
case "members": {
|
|
1770
1919
|
const cfg = need();
|
|
1771
1920
|
const client = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
1772
|
-
await client.connect().catch((e) => die(client.pending ?? e.
|
|
1921
|
+
await client.connect().catch((e) => die(client.pending ?? connectFailure(e, cfg.relay)));
|
|
1773
1922
|
await new Promise((r) => setTimeout(r, 300));
|
|
1774
1923
|
console.log(`${B(`who is in ${cfg.space}`)}\n`);
|
|
1775
1924
|
for (const e of client.members) {
|
|
@@ -1790,7 +1939,7 @@ async function main() {
|
|
|
1790
1939
|
const cfg = need();
|
|
1791
1940
|
const who = argv[1] ?? die("usage: pantheon admit <member> [--may-wake] [--revoke] [--forget]");
|
|
1792
1941
|
const client = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
1793
|
-
await client.connect().catch((e) => die(client.pending ?? e.
|
|
1942
|
+
await client.connect().catch((e) => die(client.pending ?? connectFailure(e, cfg.relay)));
|
|
1794
1943
|
await new Promise((r) => setTimeout(r, 250));
|
|
1795
1944
|
const forget = argv.includes("--forget");
|
|
1796
1945
|
const revoke = argv.includes("--revoke");
|
|
@@ -1837,16 +1986,21 @@ async function main() {
|
|
|
1837
1986
|
return;
|
|
1838
1987
|
}
|
|
1839
1988
|
case "whoami": {
|
|
1840
|
-
const cfg =
|
|
1989
|
+
const cfg = identity() ?? die("not set up on this machine — pantheon signup, or pantheon join <invite>");
|
|
1990
|
+
const team = resolveFor(process.cwd()).cfg;
|
|
1841
1991
|
if (cfg.account) {
|
|
1842
1992
|
console.log(`account ${C(cfg.account.username)} ${D(`<${cfg.account.email}>`)} ${D("verified")}`);
|
|
1843
1993
|
}
|
|
1994
|
+
else if (cfg.pendingAuth) {
|
|
1995
|
+
console.log(`account ${D(`not signed in yet — a code was sent to ${cfg.pendingAuth.email}: pantheon verify <code>`)}`);
|
|
1996
|
+
}
|
|
1844
1997
|
else {
|
|
1845
1998
|
console.log(`account ${D("not signed in")} ${D("— pantheon signup / pantheon login")}`);
|
|
1846
1999
|
}
|
|
1847
2000
|
console.log(`member ${C(cfg.member)}`);
|
|
1848
|
-
|
|
1849
|
-
console.log(`
|
|
2001
|
+
const teams = Object.values(loadAll()?.spaces ?? {}).map((m) => m.name);
|
|
2002
|
+
console.log(`space ${team?.space || (teams.length ? D(`none picked here — you are in ${teams.join(", ")}`) : D("none yet — pantheon start, or pantheon join <invite>"))}`);
|
|
2003
|
+
console.log(`relay ${team?.space ? team.relay : cfg.relay}`);
|
|
1850
2004
|
console.log(`this device ${C(fingerprint(cfg.publicKey))}`);
|
|
1851
2005
|
console.log(D(`\nManage your devices and sessions: pantheon sessions`));
|
|
1852
2006
|
return;
|
|
@@ -1858,7 +2012,7 @@ async function main() {
|
|
|
1858
2012
|
return interactiveOnboard(flag("relay"));
|
|
1859
2013
|
const email = flag("email") ?? die(`usage: pantheon ${purpose} --email you@work.com${purpose === "signup" ? " --username <name>" : ""} [--relay wss://...]`);
|
|
1860
2014
|
const username = purpose === "signup" ? (flag("username") ?? die("usage: pantheon signup --email you@work.com --username <name>")) : undefined;
|
|
1861
|
-
const prior =
|
|
2015
|
+
const prior = identity();
|
|
1862
2016
|
const relay = flag("relay") ?? prior?.relay ?? process.env.PANTHEON_RELAY ?? DEFAULT_RELAY;
|
|
1863
2017
|
const kp = prior?.publicKey && prior.privateKey ? { publicKey: prior.publicKey, privateKey: prior.privateKey } : newKeypair();
|
|
1864
2018
|
const label = flag("label") ?? `${process.env.USER ?? "a"}'s ${process.platform === "darwin" ? "mac" : process.platform}`;
|
|
@@ -1866,7 +2020,7 @@ async function main() {
|
|
|
1866
2020
|
const body = purpose === "signup" ? { email, username, pubkey: kp.publicKey, label } : { email, pubkey: kp.publicKey, label };
|
|
1867
2021
|
const r = await fetch(`${base}/auth/${purpose}`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) })
|
|
1868
2022
|
.then(async (x) => ({ status: x.status, j: await x.json() }))
|
|
1869
|
-
.catch((e) => die(
|
|
2023
|
+
.catch((e) => die(describeNetError(e, relay)));
|
|
1870
2024
|
if (r.status !== 200)
|
|
1871
2025
|
die(String(r.j.error ?? `signup failed (${r.status})`));
|
|
1872
2026
|
save({ ...(prior ?? {}), relay, member: username ?? prior?.member ?? "me", space: prior?.space ?? "", token: prior?.token ?? "",
|
|
@@ -1883,7 +2037,7 @@ async function main() {
|
|
|
1883
2037
|
return;
|
|
1884
2038
|
}
|
|
1885
2039
|
case "verify": {
|
|
1886
|
-
const cfg =
|
|
2040
|
+
const cfg = identity() ?? die("run `pantheon signup` or `pantheon login` first");
|
|
1887
2041
|
const pending = cfg.pendingAuth ?? die("nothing to verify — run `pantheon signup` or `pantheon login` first");
|
|
1888
2042
|
let code = (argv[1] ?? flag("code") ?? "").trim();
|
|
1889
2043
|
if (!code && process.stdin.isTTY) {
|
|
@@ -1894,9 +2048,9 @@ async function main() {
|
|
|
1894
2048
|
if (!code)
|
|
1895
2049
|
die("usage: pantheon verify <6-digit-code>");
|
|
1896
2050
|
const base = cfg.relay.replace(/^ws/, "http").replace(/\/+$/, "");
|
|
1897
|
-
const r = await fetch(`${base}/auth/verify`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: pending.email, code }) })
|
|
2051
|
+
const r = await fetch(`${base}/auth/verify`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: pending.email, code, pubkey: cfg.publicKey }) })
|
|
1898
2052
|
.then(async (x) => ({ status: x.status, j: await x.json() }))
|
|
1899
|
-
.catch((e) => die(
|
|
2053
|
+
.catch((e) => die(describeNetError(e, cfg.relay)));
|
|
1900
2054
|
if (r.status !== 200)
|
|
1901
2055
|
die(String(r.j.error ?? "verification failed"));
|
|
1902
2056
|
const acct = r.j.account;
|
|
@@ -1910,7 +2064,7 @@ async function main() {
|
|
|
1910
2064
|
return;
|
|
1911
2065
|
}
|
|
1912
2066
|
case "sessions": {
|
|
1913
|
-
const cfg =
|
|
2067
|
+
const cfg = identity() ?? die("not set up on this machine — pantheon signup first");
|
|
1914
2068
|
if (!cfg.account)
|
|
1915
2069
|
die("not signed in — run `pantheon signup` or `pantheon login` first");
|
|
1916
2070
|
const sub = argv[1];
|
|
@@ -1922,7 +2076,7 @@ async function main() {
|
|
|
1922
2076
|
const me = await signedFetch(cfg, "GET", "/account/sessions").then((x) => x.json()).catch(() => ({}));
|
|
1923
2077
|
thisDevice = me.this_device ?? "";
|
|
1924
2078
|
}
|
|
1925
|
-
const r = await signedFetch(cfg, "POST", "/account/revoke", { kind, id }).then((x) => x.json()).catch((e) => die(e.
|
|
2079
|
+
const r = await signedFetch(cfg, "POST", "/account/revoke", { kind, id }).then((x) => x.json()).catch((e) => die(describeNetError(e, cfg.relay)));
|
|
1926
2080
|
if (kind === "device" && id === thisDevice) {
|
|
1927
2081
|
const cl = { ...cfg };
|
|
1928
2082
|
delete cl.account;
|
|
@@ -1931,7 +2085,7 @@ async function main() {
|
|
|
1931
2085
|
console.error(`${C("revoked")} — ${r.revoked ?? 0} session(s)${r.device_removed ? ", device removed" : ""}${kind === "device" && id === thisDevice ? " (this device — you are now signed out)" : ""}`);
|
|
1932
2086
|
return;
|
|
1933
2087
|
}
|
|
1934
|
-
const r = await signedFetch(cfg, "GET", "/account/sessions").then((x) => x.json()).catch((e) => die(e.
|
|
2088
|
+
const r = await signedFetch(cfg, "GET", "/account/sessions").then((x) => x.json()).catch((e) => die(describeNetError(e, cfg.relay)));
|
|
1935
2089
|
console.log(`${B(r.account.username)} ${D(`<${r.account.email}>`)}\n`);
|
|
1936
2090
|
console.log(B("devices"));
|
|
1937
2091
|
for (const d of r.devices)
|
|
@@ -1946,12 +2100,12 @@ async function main() {
|
|
|
1946
2100
|
return;
|
|
1947
2101
|
}
|
|
1948
2102
|
case "audit": {
|
|
1949
|
-
const cfg =
|
|
2103
|
+
const cfg = identity() ?? die("not set up on this machine — pantheon signup first");
|
|
1950
2104
|
if (!cfg.account)
|
|
1951
2105
|
die("not signed in — run `pantheon signup` first");
|
|
1952
2106
|
const space = flag("space");
|
|
1953
2107
|
const since = flag("since") ? String(Date.now() - Number(flag("since")) * 3_600_000) : undefined;
|
|
1954
|
-
const r = await signedFetch(cfg, "GET", "/audit", { ...(space ? { space } : {}), ...(since ? { since } : {}) }).then((x) => x.json()).catch((e) => die(e.
|
|
2108
|
+
const r = await signedFetch(cfg, "GET", "/audit", { ...(space ? { space } : {}), ...(since ? { since } : {}) }).then((x) => x.json()).catch((e) => die(describeNetError(e, cfg.relay)));
|
|
1955
2109
|
if (r.error)
|
|
1956
2110
|
die(r.error);
|
|
1957
2111
|
if (argv.includes("--json")) {
|
|
@@ -1966,22 +2120,34 @@ async function main() {
|
|
|
1966
2120
|
return;
|
|
1967
2121
|
}
|
|
1968
2122
|
case "logout": {
|
|
1969
|
-
const cfg =
|
|
2123
|
+
const cfg = identity() ?? die("not set up on this machine — pantheon signup first");
|
|
1970
2124
|
if (!cfg.account) {
|
|
1971
2125
|
console.error(D("not signed in — nothing to log out"));
|
|
1972
2126
|
return;
|
|
1973
2127
|
}
|
|
1974
|
-
const list = await signedFetch(cfg, "GET", "/account/sessions").then((x) => x.json()
|
|
1975
|
-
|
|
1976
|
-
|
|
2128
|
+
const list = await signedFetch(cfg, "GET", "/account/sessions").then(async (x) => ({ ok: x.ok, status: x.status, j: await x.json().catch(() => ({})) }))
|
|
2129
|
+
.catch((e) => die(describeNetError(e, cfg.relay)));
|
|
2130
|
+
if (!list.ok && list.status !== 403)
|
|
2131
|
+
die(`the relay did not sign this device out (${list.j.error ?? list.status}) — nothing changed. Try again, or remove it from another device: pantheon sessions`);
|
|
2132
|
+
let revoked = list.status === 403;
|
|
2133
|
+
if (list.ok && list.j.this_device) {
|
|
2134
|
+
const r = await signedFetch(cfg, "POST", "/account/revoke", { kind: "device", id: list.j.this_device })
|
|
2135
|
+
.then(async (x) => ({ ok: x.ok, j: await x.json().catch(() => ({})) }))
|
|
2136
|
+
.catch((e) => die(describeNetError(e, cfg.relay)));
|
|
2137
|
+
if (!r.ok)
|
|
2138
|
+
die(`the relay did not sign this device out (${r.j.error ?? "refused"}) — nothing changed. Try again, or remove it from another device: pantheon sessions`);
|
|
2139
|
+
revoked = true;
|
|
2140
|
+
}
|
|
2141
|
+
else if (list.ok)
|
|
2142
|
+
revoked = true;
|
|
1977
2143
|
const cleared = { ...cfg };
|
|
1978
2144
|
delete cleared.account;
|
|
1979
2145
|
save(cleared);
|
|
1980
|
-
console.error(`${C("logged out")} — this device is signed out. Sign back in with ${C("pantheon login --email " + cfg.account.email)}.`);
|
|
2146
|
+
console.error(`${C("logged out")} — ${revoked ? "this device is signed out" : "signed out here"}. Sign back in with ${C("pantheon login --email " + cfg.account.email)}.`);
|
|
1981
2147
|
return;
|
|
1982
2148
|
}
|
|
1983
2149
|
case "account": {
|
|
1984
|
-
const cfg =
|
|
2150
|
+
const cfg = identity() ?? die("not set up on this machine — pantheon signup first");
|
|
1985
2151
|
if (!cfg.account)
|
|
1986
2152
|
die("not signed in");
|
|
1987
2153
|
const sub = argv[1];
|
|
@@ -1989,7 +2155,7 @@ async function main() {
|
|
|
1989
2155
|
die("usage: pantheon account delete");
|
|
1990
2156
|
if (!argv.includes("--yes"))
|
|
1991
2157
|
die(`This permanently deletes your account (${cfg.account.username} <${cfg.account.email}>), all your devices, and all your sessions. Re-run with --yes to confirm.`);
|
|
1992
|
-
const r = await signedFetch(cfg, "POST", "/account/delete").then((x) => x.json()).catch((e) => die(e.
|
|
2158
|
+
const r = await signedFetch(cfg, "POST", "/account/delete").then((x) => x.json()).catch((e) => die(describeNetError(e, cfg.relay)));
|
|
1993
2159
|
if (!r.ok)
|
|
1994
2160
|
die(String(r.error ?? "delete failed"));
|
|
1995
2161
|
const cleared = { ...cfg };
|
|
@@ -2179,7 +2345,7 @@ async function main() {
|
|
|
2179
2345
|
if (overlap)
|
|
2180
2346
|
die("an overlapping delegation already exists for one of these domains; remove it first");
|
|
2181
2347
|
const client = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
2182
|
-
await client.connect().catch((e) => die(e.
|
|
2348
|
+
await client.connect().catch((e) => die(connectFailure(e, cfg.relay)));
|
|
2183
2349
|
if (!client.profiles.has(to)) {
|
|
2184
2350
|
client.close();
|
|
2185
2351
|
die(`${to} has no published profile; delegation would have nowhere verifiable to land`);
|
|
@@ -2221,7 +2387,7 @@ async function main() {
|
|
|
2221
2387
|
case "directory": {
|
|
2222
2388
|
const cfg = need();
|
|
2223
2389
|
const client = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
2224
|
-
await client.connect().catch((e) => die(e.
|
|
2390
|
+
await client.connect().catch((e) => die(connectFailure(e, cfg.relay)));
|
|
2225
2391
|
if (!client.profiles.size)
|
|
2226
2392
|
console.log(`${D("no organization profiles published yet")}`);
|
|
2227
2393
|
for (const p of client.profiles.values()) {
|
|
@@ -2278,12 +2444,41 @@ async function main() {
|
|
|
2278
2444
|
return;
|
|
2279
2445
|
}
|
|
2280
2446
|
case "explain": {
|
|
2281
|
-
const cfg = need();
|
|
2282
2447
|
const { readFileSync, rmSync } = await import("node:fs");
|
|
2448
|
+
const CONFIG_DIR = dirname(CONFIG_PATH);
|
|
2449
|
+
let hud = null;
|
|
2450
|
+
if (argv.includes("--queued")) {
|
|
2451
|
+
const f = join(CONFIG_DIR, "bin", ".hud-action.json");
|
|
2452
|
+
try {
|
|
2453
|
+
hud = JSON.parse(readFileSync(f, "utf8"));
|
|
2454
|
+
rmSync(f, { force: true });
|
|
2455
|
+
}
|
|
2456
|
+
catch {
|
|
2457
|
+
hud = null;
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
const cfg = (() => {
|
|
2461
|
+
if (!hud?.space)
|
|
2462
|
+
return need();
|
|
2463
|
+
const f = loadAll();
|
|
2464
|
+
const m = f ? findMembership(f, hud.space) : undefined;
|
|
2465
|
+
if (f && m)
|
|
2466
|
+
return configFor(f, m, null);
|
|
2467
|
+
const here = resolveFor(process.cwd()).cfg;
|
|
2468
|
+
if (here?.space === hud.space)
|
|
2469
|
+
return here;
|
|
2470
|
+
const why = `this machine is no longer in the team "${hud.space}"`;
|
|
2471
|
+
if (hud.action === "reply" || hud.action === "retract") {
|
|
2472
|
+
console.log(`not sent: ${why}`);
|
|
2473
|
+
process.exit(1);
|
|
2474
|
+
}
|
|
2475
|
+
return die(why);
|
|
2476
|
+
})();
|
|
2477
|
+
if (hud && hud.goal === "")
|
|
2478
|
+
delete hud.goal;
|
|
2283
2479
|
const { pickExplainAgent, decisionPrompt: dp, allDecisionsPrompt: adp, decisionTitle: dt, sessionName } = await import("./decision-ui.js");
|
|
2284
2480
|
const { loadProfile } = await import("./profile.js");
|
|
2285
2481
|
const { detectAvailable, launchArgs } = await import("./agents.js");
|
|
2286
|
-
const CONFIG_DIR = dirname(CONFIG_PATH);
|
|
2287
2482
|
let id = argv.find((a, i) => i > 0 && !a.startsWith("--"));
|
|
2288
2483
|
if (argv.includes("--queued")) {
|
|
2289
2484
|
const queued = join(CONFIG_DIR, "bin", ".explain-id");
|
|
@@ -2295,17 +2490,6 @@ async function main() {
|
|
|
2295
2490
|
if (id === "all")
|
|
2296
2491
|
id = undefined;
|
|
2297
2492
|
}
|
|
2298
|
-
let hud = null;
|
|
2299
|
-
if (argv.includes("--queued")) {
|
|
2300
|
-
const f = join(CONFIG_DIR, "bin", ".hud-action.json");
|
|
2301
|
-
try {
|
|
2302
|
-
hud = JSON.parse(readFileSync(f, "utf8"));
|
|
2303
|
-
rmSync(f, { force: true });
|
|
2304
|
-
}
|
|
2305
|
-
catch {
|
|
2306
|
-
hud = null;
|
|
2307
|
-
}
|
|
2308
|
-
}
|
|
2309
2493
|
if (hud?.action === "fix") {
|
|
2310
2494
|
const dir = hud.dir && existsSync(hud.dir) ? hud.dir : process.cwd();
|
|
2311
2495
|
const steps = hud.lines ?? [];
|
|
@@ -2353,22 +2537,31 @@ async function main() {
|
|
|
2353
2537
|
if (!body)
|
|
2354
2538
|
notSent("nothing to send");
|
|
2355
2539
|
const c2 = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
2356
|
-
await c2.connect().catch((e) => notSent(e.
|
|
2540
|
+
await c2.connect().catch((e) => notSent(connectFailure(e, cfg.relay)));
|
|
2357
2541
|
const o = hud.decision ? c2.obligations.find((x) => x.id === hud.decision) : undefined;
|
|
2358
|
-
const
|
|
2359
|
-
const target =
|
|
2360
|
-
|
|
2542
|
+
const targetId = o?.ref ?? hud.act;
|
|
2543
|
+
const target = targetId ? (await fetchAct(cfg, targetId, 8_000))?.act : undefined;
|
|
2544
|
+
let goal = o?.goal ?? target?.goal ?? hud.goal ?? null;
|
|
2361
2545
|
if (kind === "ANSWER" && !o && !target) {
|
|
2362
2546
|
c2.close();
|
|
2363
2547
|
notSent("that decision is no longer waiting — nothing to answer");
|
|
2364
2548
|
}
|
|
2549
|
+
if (goal && c2.goals.get(goal)?.state === "CLOSED") {
|
|
2550
|
+
if (kind === "ASK") {
|
|
2551
|
+
c2.close();
|
|
2552
|
+
notSent("that goal is closed — open a new one to ask more");
|
|
2553
|
+
}
|
|
2554
|
+
goal = null;
|
|
2555
|
+
}
|
|
2365
2556
|
const ref = kind === "ANSWER" ? (o?.ref ?? target?.id ?? null) : kind === "ASK" ? (target?.id ?? null) : null;
|
|
2366
2557
|
const to = kind === "ASK" ? (target && target.from !== cfg.member ? [target.from] : null) : null;
|
|
2367
2558
|
if (kind === "ASK" && !to) {
|
|
2368
2559
|
c2.close();
|
|
2369
2560
|
notSent("cannot tell who raised this — use Show the thread and reply from there");
|
|
2370
2561
|
}
|
|
2371
|
-
const
|
|
2562
|
+
const summary = body.length > LIMITS.summaryRequiredOver
|
|
2563
|
+
? (body.split(/\n|(?<=[.!?])\s/)[0] ?? body).trim().slice(0, LIMITS.summaryChars) : undefined;
|
|
2564
|
+
const sent = await c2.send({ act: kind, body, goal, ref, to, after_seq: c2.readSeq, meta: { surface: "menubar" }, ...(summary ? { summary } : {}) })
|
|
2372
2565
|
.catch((e) => notSent(e.message));
|
|
2373
2566
|
c2.close();
|
|
2374
2567
|
console.log(`sent ${kind} id=${sent.id} seq=${sent.seq}`);
|
|
@@ -2379,7 +2572,7 @@ async function main() {
|
|
|
2379
2572
|
if (!hud.act)
|
|
2380
2573
|
notSent("nothing to retract");
|
|
2381
2574
|
const c2 = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
2382
|
-
await c2.connect().catch((e) => notSent(e.
|
|
2575
|
+
await c2.connect().catch((e) => notSent(connectFailure(e, cfg.relay)));
|
|
2383
2576
|
const sent = await c2.send({ act: "RETRACT", body: "Retracted from the menu bar.", goal: null, ref: hud.act, to: null, meta: { surface: "menubar" } })
|
|
2384
2577
|
.catch((e) => notSent(e.message));
|
|
2385
2578
|
c2.close();
|
|
@@ -2388,14 +2581,15 @@ async function main() {
|
|
|
2388
2581
|
}
|
|
2389
2582
|
if (hud?.action === "done") {
|
|
2390
2583
|
const c2 = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
2391
|
-
await c2.connect().catch((e) => die(e.
|
|
2584
|
+
await c2.connect().catch((e) => die(connectFailure(e, cfg.relay)));
|
|
2392
2585
|
const o = hud.decision ? c2.obligations.find((x) => x.id === hud.decision) : undefined;
|
|
2393
2586
|
if (!o) {
|
|
2394
2587
|
c2.close();
|
|
2395
2588
|
console.error(D("that decision is no longer waiting on you"));
|
|
2396
2589
|
return;
|
|
2397
2590
|
}
|
|
2398
|
-
|
|
2591
|
+
const open = o.goal && c2.goals.get(o.goal)?.state !== "CLOSED" ? o.goal : null;
|
|
2592
|
+
await c2.send({ act: "ANSWER", ref: o.ref, goal: open, after_seq: c2.readSeq,
|
|
2399
2593
|
body: "Marked done from the alert — handled outside this thread." }).catch((e) => die(e.message));
|
|
2400
2594
|
c2.close();
|
|
2401
2595
|
console.error(D(`done — ${o.id} is no longer waiting on you`));
|
|
@@ -2411,19 +2605,18 @@ async function main() {
|
|
|
2411
2605
|
}
|
|
2412
2606
|
if (wd)
|
|
2413
2607
|
console.error(D(` ${wd.note}`));
|
|
2414
|
-
const
|
|
2415
|
-
const a = hud.act ? acts.find((x) => x.id === hud.act) : undefined;
|
|
2608
|
+
const a = hud.act ? (await fetchAct(cfg, hud.act))?.act : undefined;
|
|
2416
2609
|
let o;
|
|
2417
2610
|
if (hud.decision) {
|
|
2418
2611
|
const c2 = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
2419
|
-
await c2.connect().catch((e) => die(e.
|
|
2612
|
+
await c2.connect().catch((e) => die(connectFailure(e, cfg.relay)));
|
|
2420
2613
|
o = c2.obligations.find((x) => x.id === hud.decision);
|
|
2421
2614
|
c2.close();
|
|
2422
2615
|
}
|
|
2423
2616
|
if (a) {
|
|
2424
2617
|
console.log(formatAct(a));
|
|
2425
2618
|
if (a.ref) {
|
|
2426
|
-
const r =
|
|
2619
|
+
const r = (await fetchAct(cfg, a.ref))?.act;
|
|
2427
2620
|
if (r)
|
|
2428
2621
|
console.log(D("\nin reply to:\n") + formatAct(r, { short: true }));
|
|
2429
2622
|
}
|
|
@@ -2451,11 +2644,7 @@ async function main() {
|
|
|
2451
2644
|
rl.close();
|
|
2452
2645
|
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.`;
|
|
2453
2646
|
console.error(D(`opening ${agent.id}…`));
|
|
2454
|
-
|
|
2455
|
-
{
|
|
2456
|
-
const sp = agentSpawn(agent.binary, launchArgs(agent.id, prompt, sessionName(prompt)));
|
|
2457
|
-
spawnSync(sp.command, sp.args, { stdio: "inherit", ...sp.options });
|
|
2458
|
-
}
|
|
2647
|
+
await launchAgent(agent.binary, (p) => launchArgs(agent.id, p, sessionName(prompt)), prompt);
|
|
2459
2648
|
return;
|
|
2460
2649
|
}
|
|
2461
2650
|
if (choice === "2") {
|
|
@@ -2496,7 +2685,8 @@ async function main() {
|
|
|
2496
2685
|
continue;
|
|
2497
2686
|
}
|
|
2498
2687
|
if (choice === "3" && goalId) {
|
|
2499
|
-
|
|
2688
|
+
const thread = await fetchHistory(cfg, { goal: goalId }).catch(() => []);
|
|
2689
|
+
for (const t of selectActs(thread, { goal: goalId, last: 40 }))
|
|
2500
2690
|
console.log(formatAct(t, { short: true }));
|
|
2501
2691
|
continue;
|
|
2502
2692
|
}
|
|
@@ -2523,10 +2713,7 @@ async function main() {
|
|
|
2523
2713
|
const bin = cliOnPath("grok") ?? die("grok is not on PATH here");
|
|
2524
2714
|
console.error(D("opening grok…"));
|
|
2525
2715
|
const { spawnSync } = await import("node:child_process");
|
|
2526
|
-
|
|
2527
|
-
const sp = agentSpawn(bin, [hud.prompt]);
|
|
2528
|
-
spawnSync(sp.command, sp.args, { stdio: "inherit", ...(cwd ? { cwd } : {}), ...sp.options });
|
|
2529
|
-
}
|
|
2716
|
+
await launchAgent(bin, (p) => [p], hud.prompt, cwd ?? undefined);
|
|
2530
2717
|
return;
|
|
2531
2718
|
}
|
|
2532
2719
|
if (hud.prompt) {
|
|
@@ -2535,10 +2722,7 @@ async function main() {
|
|
|
2535
2722
|
die("no agent CLI found on this machine (claude, cursor-agent, codex, copilot, or kiro-cli)");
|
|
2536
2723
|
console.error(D(`opening ${agent.id}…`));
|
|
2537
2724
|
const { spawnSync } = await import("node:child_process");
|
|
2538
|
-
|
|
2539
|
-
const sp = agentSpawn(agent.binary, launchArgs(agent.id, hud.prompt, sessionName(hud.prompt)));
|
|
2540
|
-
spawnSync(sp.command, sp.args, { stdio: "inherit", ...(cwd ? { cwd } : {}), ...sp.options });
|
|
2541
|
-
}
|
|
2725
|
+
await launchAgent(agent.binary, (p) => launchArgs(agent.id, p, sessionName(hud.prompt)), hud.prompt, cwd ?? undefined);
|
|
2542
2726
|
return;
|
|
2543
2727
|
}
|
|
2544
2728
|
if (hud.decision)
|
|
@@ -2551,7 +2735,7 @@ async function main() {
|
|
|
2551
2735
|
}
|
|
2552
2736
|
}
|
|
2553
2737
|
const client = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
2554
|
-
await client.connect().catch((e) => die(e.
|
|
2738
|
+
await client.connect().catch((e) => die(connectFailure(e, cfg.relay)));
|
|
2555
2739
|
const waiting = client.obligations.filter((o) => o.kind === "HUMAN" && o.owner === `human:${cfg.member}`);
|
|
2556
2740
|
client.close();
|
|
2557
2741
|
const one = id ? waiting.find((o) => o.id === id) : waiting.length === 1 ? waiting[0] : undefined;
|
|
@@ -2567,10 +2751,7 @@ async function main() {
|
|
|
2567
2751
|
die("no agent CLI found on this machine (claude, cursor-agent, codex, copilot, or kiro-cli)");
|
|
2568
2752
|
console.error(D(`opening ${agent.id} with the question…`));
|
|
2569
2753
|
const { spawnSync } = await import("node:child_process");
|
|
2570
|
-
|
|
2571
|
-
const sp = agentSpawn(agent.binary, launchArgs(agent.id, prompt, sessionName(prompt)));
|
|
2572
|
-
spawnSync(sp.command, sp.args, { stdio: "inherit", ...sp.options });
|
|
2573
|
-
}
|
|
2754
|
+
await launchAgent(agent.binary, (p) => launchArgs(agent.id, p, sessionName(prompt)), prompt);
|
|
2574
2755
|
return;
|
|
2575
2756
|
}
|
|
2576
2757
|
case "approvals": {
|
|
@@ -2579,7 +2760,7 @@ async function main() {
|
|
|
2579
2760
|
const conns = [];
|
|
2580
2761
|
for (const c of targets()) {
|
|
2581
2762
|
const cl = new PantheonClient(c, undefined, { role: "cli" });
|
|
2582
|
-
await cl.connect().catch((e) => die(`${c.space}: ${e.
|
|
2763
|
+
await cl.connect().catch((e) => die(`${c.space}: ${connectFailure(e, c.relay)}`));
|
|
2583
2764
|
conns.push({ cfg: c, client: cl });
|
|
2584
2765
|
}
|
|
2585
2766
|
const closeAll = () => { for (const c of conns)
|
|
@@ -2624,7 +2805,7 @@ async function main() {
|
|
|
2624
2805
|
const cfg = need();
|
|
2625
2806
|
const sub = argv[1] ?? "list";
|
|
2626
2807
|
const client = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
2627
|
-
await client.connect().catch((e) => die(e.
|
|
2808
|
+
await client.connect().catch((e) => die(connectFailure(e, cfg.relay)));
|
|
2628
2809
|
const waiting = client.obligations.filter((o) => o.kind === "HUMAN" && o.owner === `human:${cfg.member}`);
|
|
2629
2810
|
const id = argv[2];
|
|
2630
2811
|
if (sub === "answer") {
|
|
@@ -2937,7 +3118,7 @@ async function main() {
|
|
|
2937
3118
|
const sub = argv[1] ?? "list";
|
|
2938
3119
|
const { repo: _r, repoOrigin: _o, ...plain } = cfg;
|
|
2939
3120
|
const client = new PantheonClient(plain, undefined, { role: "cli" });
|
|
2940
|
-
await client.connect().catch((e) => die(client.pending ?? e.
|
|
3121
|
+
await client.connect().catch((e) => die(client.pending ?? connectFailure(e, plain.relay)));
|
|
2941
3122
|
if (sub === "add") {
|
|
2942
3123
|
const r = repoInfo(flag("path") ?? process.cwd()) ?? die("not inside a git repo");
|
|
2943
3124
|
const base = flag("base") ?? null;
|
|
@@ -2981,5 +3162,10 @@ async function main() {
|
|
|
2981
3162
|
process.exit(cmd === "help" || cmd === "--help" ? 0 : 1);
|
|
2982
3163
|
}
|
|
2983
3164
|
}
|
|
2984
|
-
main().catch((e) => die(e
|
|
3165
|
+
main().catch((e) => die(describeNetError(e, (() => { try {
|
|
3166
|
+
return load()?.relay;
|
|
3167
|
+
}
|
|
3168
|
+
catch {
|
|
3169
|
+
return undefined;
|
|
3170
|
+
} })())));
|
|
2985
3171
|
//# sourceMappingURL=cli.js.map
|