@join-pantheon/cli 0.27.0 → 0.29.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/dist/accounts.js +13 -2
- package/dist/agents.js +377 -9
- package/dist/cli.js +419 -182
- package/dist/client.js +91 -8
- package/dist/config.js +15 -0
- package/dist/doctor.js +35 -6
- 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 +273 -54
- package/dist/mcp-supervisor.js +5 -0
- package/dist/mcp.js +33 -6
- package/dist/network.js +595 -0
- package/dist/pending.js +14 -7
- package/dist/quickstart.js +180 -21
- package/dist/refusal.js +29 -0
- package/dist/relay.js +686 -440
- package/dist/roster-file.js +19 -3
- package/dist/run-failure.js +18 -7
- package/dist/service.js +173 -40
- package/dist/setup-guide.js +21 -4
- package/dist/store.js +9 -3
- package/dist/update-check.js +2 -1
- package/dist/upgrade.js +2 -1
- package/dist/which.js +71 -7
- package/dist/workdir.js +7 -1
- package/dist/worker.js +524 -99
- package/helper/macos/menubar.swift +11 -3
- package/package.json +2 -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";
|
|
@@ -29,17 +29,19 @@ import { menubarProc, notifierProc, relayProc, workerProc, wiring, logFileFor, d
|
|
|
29
29
|
import { assess } from "./untrusted.js";
|
|
30
30
|
import { report as spendReport, recentRuns } from "./spend.js";
|
|
31
31
|
import { renderBrief, renderTeam } from "./brief.js";
|
|
32
|
-
import { resolveDataDir } from "./config.js";
|
|
32
|
+
import { resolveDataDir, saveAll } from "./config.js";
|
|
33
|
+
import { currentPlan, installNetwork, redactProxy, redactText, normalizeProxy, proxyToRemember, describeNetError } from "./network.js";
|
|
33
34
|
import { createInterface } from "node:readline";
|
|
34
35
|
import { homedir } from "node:os";
|
|
35
36
|
import { removeHook, decideStop, writeIntent, clearIntent, readIntent, installHooks, agentHookStates } from "./notify.js";
|
|
36
37
|
import { pending, render as renderPending } from "./pending.js";
|
|
37
|
-
import { start as qsStart, join as qsJoin, wireUp } from "./quickstart.js";
|
|
38
|
+
import { start as qsStart, join as qsJoin, wireUp, teamNameFor } from "./quickstart.js";
|
|
38
39
|
import { installService, uninstallService } from "./service.js";
|
|
39
40
|
import { LIMITS } from "./protocol.js";
|
|
40
41
|
import { loadPersonas, personaByName, describePersona, initPersona, personaDir } from "./persona.js";
|
|
41
42
|
import { fingerprint, newKeypair, signAccount } from "./identity.js";
|
|
42
43
|
import { VERSION } from "./version.js";
|
|
44
|
+
import { connectFailure } from "./refusal.js";
|
|
43
45
|
import * as mcpWindows from "./mcp-windows.js";
|
|
44
46
|
import { updateBanner } from "./update-check.js";
|
|
45
47
|
const argv = process.argv.slice(2);
|
|
@@ -61,7 +63,7 @@ async function signedFetch(cfg, method, path, params = {}) {
|
|
|
61
63
|
return res;
|
|
62
64
|
}
|
|
63
65
|
function requireSignInOnHostedRelay(relayFlag) {
|
|
64
|
-
const cfg =
|
|
66
|
+
const cfg = identity();
|
|
65
67
|
if (cfg?.account)
|
|
66
68
|
return;
|
|
67
69
|
const relay = relayFlag ?? cfg?.relay ?? process.env.PANTHEON_RELAY ?? DEFAULT_RELAY;
|
|
@@ -71,7 +73,7 @@ function requireSignInOnHostedRelay(relayFlag) {
|
|
|
71
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.`);
|
|
72
74
|
}
|
|
73
75
|
async function interactiveOnboard(relayArg) {
|
|
74
|
-
const prior =
|
|
76
|
+
const prior = identity();
|
|
75
77
|
const relay = relayArg ?? prior?.relay ?? process.env.PANTHEON_RELAY ?? DEFAULT_RELAY;
|
|
76
78
|
const base = relay.replace(/^ws/, "http").replace(/\/+$/, "");
|
|
77
79
|
if (!process.stdin.isTTY) {
|
|
@@ -151,7 +153,7 @@ async function interactiveOnboard(relayArg) {
|
|
|
151
153
|
console.error(D(` could not resend: ${rs?.j.error ?? "the relay did not send an email"} — try again in a minute.`));
|
|
152
154
|
continue;
|
|
153
155
|
}
|
|
154
|
-
const v = await postJson("/auth/verify", { email, code });
|
|
156
|
+
const v = await postJson("/auth/verify", { email, code, pubkey: kp.publicKey });
|
|
155
157
|
if (v.status === 200) {
|
|
156
158
|
const acct = v.j.account;
|
|
157
159
|
const cfg = { ...(prior ?? {}), relay, member: acct.username, space: prior?.space ?? "", token: prior?.token ?? "",
|
|
@@ -251,10 +253,44 @@ function targets() {
|
|
|
251
253
|
const r = resolveFor(process.cwd());
|
|
252
254
|
if (r.cfg && r.cfg.space && r.reason !== "default")
|
|
253
255
|
return [r.cfg];
|
|
254
|
-
if (r.reason === "default" && f)
|
|
256
|
+
if ((r.reason === "default" || r.reason === "unbound") && f && Object.keys(f.spaces ?? {}).length)
|
|
255
257
|
return Object.values(f.spaces ?? {}).map((m) => configFor(f, m, null));
|
|
256
258
|
return [need()];
|
|
257
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
|
+
}
|
|
258
294
|
function needStrict() {
|
|
259
295
|
const r = resolveFor(process.cwd(), { strict: true });
|
|
260
296
|
return r.cfg && r.cfg.space ? r.cfg : null;
|
|
@@ -285,7 +321,7 @@ async function guidedSetup(cfg, project, opts = {}) {
|
|
|
285
321
|
const io = rl ? terminalIO(rl, (l) => console.error(l)) : null;
|
|
286
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)")}`);
|
|
287
323
|
try {
|
|
288
|
-
const r = await runGuide(io, { cfg, project }, {
|
|
324
|
+
const r = await runGuide(io, { cfg, project, optOut: optOutsFrom(argv) }, {
|
|
289
325
|
publish: async (p) => {
|
|
290
326
|
const c = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
291
327
|
try {
|
|
@@ -335,23 +371,27 @@ async function workdirFor(cfg, hud) {
|
|
|
335
371
|
const bindings = all && m ? Object.values(all.bindings ?? {}).filter((b) => b.space === m.id) : [];
|
|
336
372
|
let hints = [];
|
|
337
373
|
if (bindings.length > 1 && hud.goal) {
|
|
338
|
-
const acts = await fetchHistory(cfg).catch(() => []);
|
|
374
|
+
const acts = await fetchHistory(cfg, { goal: hud.goal }).catch(() => []);
|
|
339
375
|
hints = acts.filter((x) => x.goal === hud.goal).flatMap((x) => [x.body, JSON.stringify(x.meta ?? {})]);
|
|
340
376
|
}
|
|
341
377
|
return pickWorkdir(bindings, hints, team);
|
|
342
378
|
}
|
|
343
|
-
async function fetchHistory(cfg,
|
|
379
|
+
async function fetchHistory(cfg, opts = {}) {
|
|
344
380
|
const base = cfg.relay.replace(/^ws/, "http").replace(/\/+$/, "");
|
|
345
|
-
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) });
|
|
346
384
|
if (!res.ok)
|
|
347
385
|
throw new Error(`relay answered ${res.status} for the ledger`);
|
|
348
386
|
return (await res.json()).acts ?? [];
|
|
349
387
|
}
|
|
350
|
-
async function fetchAct(cfg, id) {
|
|
388
|
+
async function fetchAct(cfg, id, budgetMs = Number.POSITIVE_INFINITY) {
|
|
351
389
|
const base = cfg.relay.replace(/^ws/, "http").replace(/\/+$/, "");
|
|
352
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()));
|
|
353
393
|
try {
|
|
354
|
-
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()) });
|
|
355
395
|
if (res.ok) {
|
|
356
396
|
const body = await res.json();
|
|
357
397
|
if (body.act)
|
|
@@ -363,7 +403,9 @@ async function fetchAct(cfg, id) {
|
|
|
363
403
|
catch { }
|
|
364
404
|
let head = 0;
|
|
365
405
|
try {
|
|
366
|
-
|
|
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();
|
|
367
409
|
head = h.spaces?.[0]?.seq ?? 0;
|
|
368
410
|
}
|
|
369
411
|
catch { }
|
|
@@ -371,8 +413,10 @@ async function fetchAct(cfg, id) {
|
|
|
371
413
|
if (windows[windows.length - 1] !== 0)
|
|
372
414
|
windows.push(0);
|
|
373
415
|
for (const since of windows) {
|
|
416
|
+
if (Date.now() >= deadline)
|
|
417
|
+
return null;
|
|
374
418
|
try {
|
|
375
|
-
const acts = await fetchHistory(cfg, since);
|
|
419
|
+
const acts = await fetchHistory(cfg, { since });
|
|
376
420
|
const act = acts.find((a) => a.id === id || String(a.seq) === id);
|
|
377
421
|
if (act)
|
|
378
422
|
return { act };
|
|
@@ -521,6 +565,7 @@ ${B("control")}
|
|
|
521
565
|
pantheon leave remove what pantheon put on this machine
|
|
522
566
|
pantheon panic cut off all unattended work on this machine, now
|
|
523
567
|
pantheon doctor [--share] check this machine end to end; --share prints it without names or paths, to paste to us
|
|
568
|
+
pantheon network [--proxy <url> | --direct | --clear] how this machine reaches the relay; save a proxy for every Pantheon process here
|
|
524
569
|
pantheon setup three questions — your name, your role, may a woken run execute commands —
|
|
525
570
|
then one screen of defaults to keep or change: who may wake this machine,
|
|
526
571
|
alerts, what you personally decide, playbook, base branch. Re-run any time.
|
|
@@ -549,7 +594,39 @@ ${B("less common")}
|
|
|
549
594
|
pantheon version the pantheon version this machine is running
|
|
550
595
|
`;
|
|
551
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"]);
|
|
611
|
+
function rememberWorkingProxy() {
|
|
612
|
+
try {
|
|
613
|
+
const all = loadAll();
|
|
614
|
+
if (!all)
|
|
615
|
+
return;
|
|
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`));
|
|
622
|
+
}
|
|
623
|
+
catch { }
|
|
624
|
+
}
|
|
552
625
|
async function main() {
|
|
626
|
+
try {
|
|
627
|
+
await installNetwork();
|
|
628
|
+
}
|
|
629
|
+
catch { }
|
|
553
630
|
if (!QUIET.has(cmd)) {
|
|
554
631
|
try {
|
|
555
632
|
console.error(D(updateBanner(VERSION)));
|
|
@@ -557,14 +634,51 @@ async function main() {
|
|
|
557
634
|
catch { }
|
|
558
635
|
}
|
|
559
636
|
if (argv.length === 0) {
|
|
560
|
-
const cfg =
|
|
637
|
+
const cfg = identity();
|
|
561
638
|
if (cfg?.account) {
|
|
562
639
|
console.error(`${B("pantheon")} — signed in as ${C(cfg.account.username)}. ${D(`Your plate: pantheon brief · all commands: pantheon help`)}`);
|
|
563
640
|
return;
|
|
564
641
|
}
|
|
565
642
|
return interactiveOnboard();
|
|
566
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
|
+
}
|
|
567
652
|
switch (cmd) {
|
|
653
|
+
case "network": {
|
|
654
|
+
const all = loadAll() ?? die("not configured — sign up or join a team first");
|
|
655
|
+
const set = flag("proxy");
|
|
656
|
+
if (set !== undefined) {
|
|
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)`);
|
|
658
|
+
saveAll({ ...all, network: { proxy: p, ...(flag("no-proxy") ? { no_proxy: flag("no-proxy") } : {}), source: "pantheon network", saved_at: Date.now() } });
|
|
659
|
+
console.error(`${B("saved")} every Pantheon process on this machine now reaches the relay through ${redactProxy(p)}`);
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
if (argv.includes("--direct")) {
|
|
663
|
+
saveAll({ ...all, network: { proxy: "", source: "pantheon network --direct", saved_at: Date.now() } });
|
|
664
|
+
console.error(`${B("saved")} direct — set PANTHEON_PROXY=none in a shell to do the same for one command`);
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
if (argv.includes("--clear")) {
|
|
668
|
+
const { network: _drop, ...rest } = all;
|
|
669
|
+
saveAll(rest);
|
|
670
|
+
console.error(`${B("cleared")} — Pantheon follows HTTPS_PROXY / NO_PROXY and the system settings again`);
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
const plan = currentPlan();
|
|
674
|
+
const via = plan.https ?? plan.http;
|
|
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") : ""}`);
|
|
676
|
+
if (via)
|
|
677
|
+
console.log(D(` not for: ${plan.noProxy.join(", ")}`));
|
|
678
|
+
if (plan.pac)
|
|
679
|
+
console.log(D(` this machine uses an auto-config script (${plan.pac}), which Pantheon cannot read — if the relay is unreachable, save the proxy it points to: pantheon network --proxy http://host:port`));
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
568
682
|
case "start": {
|
|
569
683
|
const port = Number(flag("port", "8787"));
|
|
570
684
|
const setupFlags = parseSetupFlags(argv);
|
|
@@ -574,7 +688,7 @@ async function main() {
|
|
|
574
688
|
requireSignInOnHostedRelay(flag("relay"));
|
|
575
689
|
console.error(`${B("pantheon start")}\n`);
|
|
576
690
|
if (!flag("space") && !repoInfo(process.cwd()))
|
|
577
|
-
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`));
|
|
578
692
|
const r = await qsStart({
|
|
579
693
|
port,
|
|
580
694
|
dataDir: resolveDataDir(flag("data")).dir,
|
|
@@ -583,6 +697,7 @@ async function main() {
|
|
|
583
697
|
...(flag("space") ? { space: flag("space") } : {}),
|
|
584
698
|
...(flag("me") ? { me: flag("me") } : {}),
|
|
585
699
|
...(flag("relay") ? { relay: flag("relay") } : {}),
|
|
700
|
+
...(setupFlags.preset.wake ? { wake: setupFlags.preset.wake } : {}),
|
|
586
701
|
hook: !argv.includes("--no-hook"),
|
|
587
702
|
notifications: !argv.includes("--no-notifications"),
|
|
588
703
|
menubar: !argv.includes("--no-menubar"),
|
|
@@ -595,6 +710,7 @@ async function main() {
|
|
|
595
710
|
for (const w of r.warnings)
|
|
596
711
|
console.error(`\x1b[33m ! ${w}\x1b[0m`);
|
|
597
712
|
printGranted(r.granted);
|
|
713
|
+
rememberWorkingProxy();
|
|
598
714
|
if (!r.claimed) {
|
|
599
715
|
console.error(`\n\x1b[31m No team was created.\x1b[0m ${D("Fix the warning above, then run `pantheon start` again.")}`);
|
|
600
716
|
if (r.relay)
|
|
@@ -634,7 +750,7 @@ async function main() {
|
|
|
634
750
|
case "invite": {
|
|
635
751
|
const cfg = need();
|
|
636
752
|
const client = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
637
|
-
await client.connect().catch((e) => die(client.pending ?? e.
|
|
753
|
+
await client.connect().catch((e) => die(client.pending ?? connectFailure(e, cfg.relay)));
|
|
638
754
|
await new Promise((r) => setTimeout(r, 250));
|
|
639
755
|
const revokeAt = argv.indexOf("--revoke");
|
|
640
756
|
const revokeArg = revokeAt >= 0 ? (argv[revokeAt + 1] && !argv[revokeAt + 1].startsWith("--") ? argv[revokeAt + 1] : "all") : undefined;
|
|
@@ -677,6 +793,7 @@ async function main() {
|
|
|
677
793
|
return;
|
|
678
794
|
}
|
|
679
795
|
case "relay": {
|
|
796
|
+
process.on("unhandledRejection", (e) => { console.error(`! unhandled rejection: ${e?.stack ?? String(e)}`); });
|
|
680
797
|
const relay = new Relay({
|
|
681
798
|
port: Number(flag("port", process.env.PORT ?? "8787")),
|
|
682
799
|
host: flag("host", "0.0.0.0"),
|
|
@@ -706,16 +823,18 @@ async function main() {
|
|
|
706
823
|
return;
|
|
707
824
|
}
|
|
708
825
|
case "init": {
|
|
709
|
-
const
|
|
826
|
+
const had = argv.includes("--reset") ? null : identity();
|
|
827
|
+
const kp = had?.publicKey && had.privateKey ? { publicKey: had.publicKey, privateKey: had.privateKey } : newKeypair();
|
|
710
828
|
const cfg = {
|
|
711
829
|
relay: flag("relay", "ws://localhost:8787"),
|
|
712
830
|
space: flag("space", "team"),
|
|
713
831
|
token: flag("token", randomBytes(16).toString("hex")),
|
|
714
|
-
member: flag("me"
|
|
832
|
+
member: flag("me") ?? had?.member ?? process.env.USER ?? "me",
|
|
715
833
|
publicKey: kp.publicKey,
|
|
716
834
|
privateKey: kp.privateKey,
|
|
717
835
|
display: flag("display"),
|
|
718
836
|
agent: flag("agent", "unknown"),
|
|
837
|
+
...(had?.account ? { account: had.account } : {}),
|
|
719
838
|
};
|
|
720
839
|
save(cfg);
|
|
721
840
|
console.error(`${B("space claimed.")} config: ${D(CONFIG_PATH)}\n`);
|
|
@@ -762,7 +881,7 @@ async function main() {
|
|
|
762
881
|
parsed = await resolveInviteCode(invite, candidates).catch((err) => die(err.message.replace(/^INVITE_CODE_[A-Z]+:\s*/, "")));
|
|
763
882
|
console.error(D(`That was just the code — it opens "${parsed.space}" on ${new URL(parsed.relay).host}. Joining that team.\n`));
|
|
764
883
|
}
|
|
765
|
-
if (!
|
|
884
|
+
if (!identity()?.account && sameHost(parsed.relay, DEFAULT_RELAY)) {
|
|
766
885
|
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.`);
|
|
767
886
|
}
|
|
768
887
|
const wp = unattendedProject();
|
|
@@ -794,6 +913,7 @@ async function main() {
|
|
|
794
913
|
for (const w of r.warnings)
|
|
795
914
|
console.error(`\x1b[33m ! ${w}\x1b[0m`);
|
|
796
915
|
printGranted(r.granted);
|
|
916
|
+
rememberWorkingProxy();
|
|
797
917
|
printReady(r.tools);
|
|
798
918
|
{
|
|
799
919
|
const joined = load();
|
|
@@ -836,6 +956,22 @@ async function main() {
|
|
|
836
956
|
}
|
|
837
957
|
case "rewire": {
|
|
838
958
|
const rows = installForUser().map((t) => ({ tool: t.tool, status: t.status }));
|
|
959
|
+
try {
|
|
960
|
+
const { refreshServiceDefinition, serviceInstalled: installed, restartService: restart } = await import("./service.js");
|
|
961
|
+
for (const kind of ["worker", "notifier", "menubar"]) {
|
|
962
|
+
if (!installed(kind))
|
|
963
|
+
continue;
|
|
964
|
+
const fresh = refreshServiceDefinition(kind);
|
|
965
|
+
if (fresh.line)
|
|
966
|
+
console.error(fresh.line);
|
|
967
|
+
if (fresh.changed && kind !== "worker")
|
|
968
|
+
for (const l of restart(kind, undefined, { reload: true }))
|
|
969
|
+
console.error(l);
|
|
970
|
+
else if (fresh.changed)
|
|
971
|
+
console.error(D(" worker: the new definition applies at its next restart"));
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
catch { }
|
|
839
975
|
if (argv.includes("--json")) {
|
|
840
976
|
console.log(JSON.stringify(rows));
|
|
841
977
|
return;
|
|
@@ -906,6 +1042,7 @@ async function main() {
|
|
|
906
1042
|
}
|
|
907
1043
|
}
|
|
908
1044
|
}
|
|
1045
|
+
const { refreshServiceDefinition } = await import("./service.js");
|
|
909
1046
|
for (const kind of ["worker", "notifier", "menubar"]) {
|
|
910
1047
|
if (!serviceInstalled(kind))
|
|
911
1048
|
continue;
|
|
@@ -913,7 +1050,10 @@ async function main() {
|
|
|
913
1050
|
console.error(` ${D("worker: a run is in progress — left alone. When it is done:")} pantheon service restart worker`);
|
|
914
1051
|
continue;
|
|
915
1052
|
}
|
|
916
|
-
|
|
1053
|
+
const fresh = refreshServiceDefinition(kind);
|
|
1054
|
+
if (fresh.line)
|
|
1055
|
+
console.error(fresh.line);
|
|
1056
|
+
for (const l of restartService(kind, undefined, { reload: fresh.changed }))
|
|
917
1057
|
console.error(l);
|
|
918
1058
|
}
|
|
919
1059
|
if (r.restart.includes("relay"))
|
|
@@ -953,7 +1093,7 @@ async function main() {
|
|
|
953
1093
|
console.error(client.pending);
|
|
954
1094
|
process.exit(2);
|
|
955
1095
|
}
|
|
956
|
-
die(
|
|
1096
|
+
die(connectFailure(e, cfg.relay));
|
|
957
1097
|
});
|
|
958
1098
|
await new Promise((r) => setTimeout(r, 350));
|
|
959
1099
|
const peers = client.roster.filter((m) => m.id !== cfg.member);
|
|
@@ -1021,7 +1161,7 @@ async function main() {
|
|
|
1021
1161
|
for (const cfg of targets()) {
|
|
1022
1162
|
const profile = loadProfile(cfg);
|
|
1023
1163
|
const client = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
1024
|
-
await client.connect().catch((e) => die(client.pending ?? e.
|
|
1164
|
+
await client.connect().catch((e) => die(client.pending ?? connectFailure(e, cfg.relay)));
|
|
1025
1165
|
await new Promise((r) => setTimeout(r, 350));
|
|
1026
1166
|
process.stdout.write(renderBrief({
|
|
1027
1167
|
space: cfg.space,
|
|
@@ -1051,7 +1191,7 @@ async function main() {
|
|
|
1051
1191
|
}
|
|
1052
1192
|
const cfg = need();
|
|
1053
1193
|
const client = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
1054
|
-
await client.connect().catch((e) => die(client.pending ?? e.
|
|
1194
|
+
await client.connect().catch((e) => die(client.pending ?? connectFailure(e, cfg.relay)));
|
|
1055
1195
|
await new Promise((r) => setTimeout(r, 350));
|
|
1056
1196
|
process.stdout.write(renderTeam({
|
|
1057
1197
|
space: cfg.space,
|
|
@@ -1079,7 +1219,7 @@ async function main() {
|
|
|
1079
1219
|
};
|
|
1080
1220
|
const base = cfg.relay.replace(/^ws/, "http").replace(/\/+$/, "");
|
|
1081
1221
|
const q = `space=${encodeURIComponent(cfg.space)}&token=${encodeURIComponent(cfg.token)}`;
|
|
1082
|
-
const first = await fetch(`${base}/history?${q}`).catch((e) => die(
|
|
1222
|
+
const first = await fetch(`${base}/history?${q}`).catch((e) => die(describeNetError(e, cfg.relay)));
|
|
1083
1223
|
if (!first.ok)
|
|
1084
1224
|
die(`relay answered ${first.status} — check the space name and token`);
|
|
1085
1225
|
let since = ((await first.json()).acts ?? []).at(-1)?.seq ?? 0;
|
|
@@ -1108,7 +1248,8 @@ async function main() {
|
|
|
1108
1248
|
}
|
|
1109
1249
|
case "history": {
|
|
1110
1250
|
const cfg = need();
|
|
1111
|
-
const acts = await fetchHistory(cfg,
|
|
1251
|
+
const acts = await fetchHistory(cfg, flag("goal") ? { goal: flag("goal") } : flag("since") ? { since: numFlag("since", 0) } : {})
|
|
1252
|
+
.catch((e) => die(`cannot read the ledger — ${e.message}`));
|
|
1112
1253
|
const picked = selectActs(acts, {
|
|
1113
1254
|
...(flag("goal") ? { goal: flag("goal") } : {}), ...(flag("from") ? { from: flag("from") } : {}),
|
|
1114
1255
|
last: numFlag("last", 20), presence: argv.includes("--presence"),
|
|
@@ -1140,11 +1281,11 @@ async function main() {
|
|
|
1140
1281
|
case "show": {
|
|
1141
1282
|
const cfg = need();
|
|
1142
1283
|
const key = argv[1] ?? die("usage: pantheon show <act id | sequence number>");
|
|
1143
|
-
const
|
|
1144
|
-
const a =
|
|
1145
|
-
console.log(formatAct(a));
|
|
1284
|
+
const found = await fetchAct(cfg, key);
|
|
1285
|
+
const a = found?.act ?? die(`no act ${key} in what the relay still holds (pantheon history lists them)`);
|
|
1286
|
+
console.log(formatAct(a, found?.goalTitle ? { goalTitle: found.goalTitle } : {}));
|
|
1146
1287
|
if (a.ref) {
|
|
1147
|
-
const r =
|
|
1288
|
+
const r = (await fetchAct(cfg, a.ref))?.act;
|
|
1148
1289
|
if (r)
|
|
1149
1290
|
console.log(D(`\nin reply to:\n`) + formatAct(r, { short: true }));
|
|
1150
1291
|
}
|
|
@@ -1159,7 +1300,7 @@ async function main() {
|
|
|
1159
1300
|
die("nothing to say — pantheon say NOTE 'your message here'");
|
|
1160
1301
|
const summary = flag("summary");
|
|
1161
1302
|
const client = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
1162
|
-
await client.connect().catch((e) => die(
|
|
1303
|
+
await client.connect().catch((e) => die(connectFailure(e, cfg.relay)));
|
|
1163
1304
|
const to = flag("to");
|
|
1164
1305
|
const meta = {};
|
|
1165
1306
|
for (const k of ["branch", "commit", "remote"]) {
|
|
@@ -1173,7 +1314,15 @@ async function main() {
|
|
|
1173
1314
|
let goalArg = flag("goal") ?? null;
|
|
1174
1315
|
let ref = flag("ref") ?? null;
|
|
1175
1316
|
if (ref !== null || ["ANSWER", "ACCEPT", "OBJECT"].includes(act)) {
|
|
1176
|
-
|
|
1317
|
+
let acts;
|
|
1318
|
+
if (ref && ref !== "last") {
|
|
1319
|
+
const hit = (await fetchAct(cfg, ref))?.act;
|
|
1320
|
+
acts = hit ? [hit] : [];
|
|
1321
|
+
if (!hit && /^a_/.test(ref))
|
|
1322
|
+
console.error(D(` ${ref} is not in what this relay holds — sending it as given`));
|
|
1323
|
+
}
|
|
1324
|
+
else
|
|
1325
|
+
acts = await fetchHistory(cfg, goalArg ? { goal: goalArg } : {}).catch(() => []);
|
|
1177
1326
|
const rr = resolveRef(acts, ref, act, goalArg, cfg.member);
|
|
1178
1327
|
if (rr.error) {
|
|
1179
1328
|
client.close();
|
|
@@ -1299,23 +1448,17 @@ async function main() {
|
|
|
1299
1448
|
return;
|
|
1300
1449
|
}
|
|
1301
1450
|
case "stop": {
|
|
1302
|
-
const what = (argv[1]
|
|
1451
|
+
const what = (argv[1] && !argv[1].startsWith("--") ? argv[1] : "all").toLowerCase();
|
|
1452
|
+
if (!["all", "relay", "worker", "notifier", "menubar"].includes(what))
|
|
1453
|
+
die(`usage: pantheon stop [relay|worker|notifier|menubar|all]`);
|
|
1454
|
+
const { stopMachine } = await import("./machine.js");
|
|
1303
1455
|
console.log(B("stopping"));
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
if (what === "notifier" || what === "all")
|
|
1311
|
-
for (const l of stopProc("notifier"))
|
|
1312
|
-
console.log(` ${l}`);
|
|
1313
|
-
if (what === "menubar" || what === "all")
|
|
1314
|
-
for (const l of stopProc("menubar"))
|
|
1315
|
-
console.log(` ${l}`);
|
|
1316
|
-
if (what === "relay" || what === "all") {
|
|
1317
|
-
console.log(D(`\n Your teammates are offline until a relay is running again.`));
|
|
1318
|
-
}
|
|
1456
|
+
const everyTeam = Object.values(loadAll()?.spaces ?? {}).map((m) => ({ name: m.name, relay: m.relay }));
|
|
1457
|
+
const res = stopMachine(what, { port: Number(flag("port", "8787")), teams: everyTeam.length ? everyTeam : (load()?.relay ? [{ name: load().space, relay: load().relay }] : []) });
|
|
1458
|
+
for (const l of res.lines)
|
|
1459
|
+
console.log(` ${l}`);
|
|
1460
|
+
if (res.note)
|
|
1461
|
+
console.log(D(`\n ${res.note}`));
|
|
1319
1462
|
return;
|
|
1320
1463
|
}
|
|
1321
1464
|
case "logs": {
|
|
@@ -1323,7 +1466,7 @@ async function main() {
|
|
|
1323
1466
|
const proc = kind === "relay" ? relayProc(Number(flag("port", "8787"))) : kind === "worker" ? workerProc() : notifierProc();
|
|
1324
1467
|
const f = logFileFor(kind, proc);
|
|
1325
1468
|
if (!f)
|
|
1326
|
-
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.`);
|
|
1469
|
+
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.`);
|
|
1327
1470
|
console.error(D(`${f} (${statFile(f)})\n`));
|
|
1328
1471
|
const follow = argv.includes("-f") || argv.includes("--follow");
|
|
1329
1472
|
const { spawn } = await import("node:child_process");
|
|
@@ -1612,7 +1755,7 @@ async function main() {
|
|
|
1612
1755
|
if (!argv.includes("--yes"))
|
|
1613
1756
|
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`);
|
|
1614
1757
|
const client = new PantheonClient(configFor(all, m, null), undefined, { role: "cli" });
|
|
1615
|
-
await client.connect().catch((e) => die(client.pending ?? e.
|
|
1758
|
+
await client.connect().catch((e) => die(client.pending ?? connectFailure(e, m.relay)));
|
|
1616
1759
|
await client.deleteSpace(m.name).catch((e) => { client.close(); die(e.message); });
|
|
1617
1760
|
client.close();
|
|
1618
1761
|
const bound = Object.values(all.bindings ?? {}).filter((b) => b.space === m.id).length;
|
|
@@ -1630,53 +1773,103 @@ async function main() {
|
|
|
1630
1773
|
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.`));
|
|
1631
1774
|
return;
|
|
1632
1775
|
}
|
|
1633
|
-
const
|
|
1776
|
+
const all = loadAll();
|
|
1777
|
+
const me = identity(all);
|
|
1778
|
+
const { leavePlan } = await import("./machine.js");
|
|
1779
|
+
const plan = leavePlan(all);
|
|
1634
1780
|
const project = resolve(flag("project", "."));
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1781
|
+
const keepIdentity = argv.includes("--keep-identity");
|
|
1782
|
+
if (plan.teams.length > 1 && !keepIdentity && !argv.includes("--yes")) {
|
|
1783
|
+
const list = plan.teams.map((t) => `"${t.name}"`).join(", ");
|
|
1784
|
+
if (!process.stdin.isTTY)
|
|
1785
|
+
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>`);
|
|
1786
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
1787
|
+
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())));
|
|
1788
|
+
rl.close();
|
|
1789
|
+
if (answer.toLowerCase() !== "yes")
|
|
1790
|
+
die(`nothing was changed. Leave one team with: pantheon leave <team>`);
|
|
1791
|
+
}
|
|
1792
|
+
console.error(`${B("leaving pantheon")}${plan.teams.length ? D(` — ${plan.teams.map((t) => t.name).join(", ")}`) : ""}\n`);
|
|
1793
|
+
const { uninstallMachine, realUninstallIo } = await import("./machine.js");
|
|
1794
|
+
const { removeUserMcpEntries } = await import("./install.js");
|
|
1795
|
+
const done = uninstallMachine({ project, stopRelay: argv.includes("--stop-relay") }, realUninstallIo({
|
|
1796
|
+
port: Number(flag("port", "8787")),
|
|
1797
|
+
removeMcpEntries: () => removeUserMcpEntries({ project }),
|
|
1798
|
+
hasIntent: () => readIntent() !== null,
|
|
1799
|
+
clearIntent: () => clearIntent(),
|
|
1800
|
+
}));
|
|
1801
|
+
for (const l of done.lines)
|
|
1641
1802
|
console.error(` ${l}`);
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
console.error(` ${l}`);
|
|
1645
|
-
else if (relayProc(Number(flag("port", "8787"))).running) {
|
|
1646
|
-
console.error(` relay: left running — others may be using it. Stop it with: pantheon stop relay`);
|
|
1647
|
-
}
|
|
1803
|
+
const removed = [...done.removed];
|
|
1804
|
+
const left = [...done.left];
|
|
1648
1805
|
for (const l of removeHook(project))
|
|
1649
1806
|
console.error(` hook: ${l.trim()}`);
|
|
1650
1807
|
const { revokeToolPermissions } = await import("./grants.js");
|
|
1651
|
-
for (const l of revokeToolPermissions())
|
|
1808
|
+
for (const l of revokeToolPermissions()) {
|
|
1652
1809
|
console.error(` perms:${l}`);
|
|
1653
|
-
|
|
1654
|
-
const l = removeCodexUserScope();
|
|
1655
|
-
if (l)
|
|
1656
|
-
console.error(` perms:${l}`);
|
|
1810
|
+
removed.push(l.trim().replace(/^removed /, ""));
|
|
1657
1811
|
}
|
|
1658
|
-
const { unlinkSync, existsSync: exists, rmSync } = await import("node:fs");
|
|
1659
|
-
if (
|
|
1660
|
-
if (
|
|
1661
|
-
|
|
1812
|
+
const { unlinkSync, existsSync: exists, rmSync, readdirSync } = await import("node:fs");
|
|
1813
|
+
if (all && exists(CONFIG_PATH)) {
|
|
1814
|
+
if (keepIdentity) {
|
|
1815
|
+
left.push(`your identity and ${plan.teams.length} team membership${plan.teams.length === 1 ? "" : "s"}, ${CONFIG_PATH} (--keep-identity)`);
|
|
1662
1816
|
}
|
|
1663
1817
|
else {
|
|
1664
1818
|
unlinkSync(CONFIG_PATH);
|
|
1819
|
+
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`);
|
|
1820
|
+
const runHomes = join(CONFIG_PATH, "..", "run-homes");
|
|
1821
|
+
const { restorePendingQuarantines, pendingQuarantines } = await import("./agents.js");
|
|
1822
|
+
const put = restorePendingQuarantines(runHomes);
|
|
1823
|
+
if (put.length)
|
|
1824
|
+
removed.push(`nothing of your repos' — what interrupted runs had held aside: ${put.join("; ")}`);
|
|
1825
|
+
const still = pendingQuarantines(runHomes);
|
|
1826
|
+
const keptAside = (dir) => { try {
|
|
1827
|
+
return readdirSync(join(dir, "quarantine-kept")).length > 0;
|
|
1828
|
+
}
|
|
1829
|
+
catch {
|
|
1830
|
+
return false;
|
|
1831
|
+
} };
|
|
1832
|
+
let homes = [];
|
|
1665
1833
|
try {
|
|
1666
|
-
|
|
1834
|
+
homes = readdirSync(runHomes);
|
|
1667
1835
|
}
|
|
1668
1836
|
catch { }
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1837
|
+
const keep = new Set([...still.map((x) => x.runHome), ...homes.map((h) => join(runHomes, h)).filter(keptAside)]);
|
|
1838
|
+
for (const h of homes) {
|
|
1839
|
+
const dir = join(runHomes, h);
|
|
1840
|
+
if (keep.has(dir))
|
|
1841
|
+
continue;
|
|
1842
|
+
try {
|
|
1843
|
+
rmSync(dir, { recursive: true, force: true });
|
|
1844
|
+
}
|
|
1845
|
+
catch { }
|
|
1846
|
+
}
|
|
1847
|
+
if (!keep.size) {
|
|
1848
|
+
try {
|
|
1849
|
+
rmSync(runHomes, { recursive: true, force: true });
|
|
1850
|
+
}
|
|
1851
|
+
catch { }
|
|
1852
|
+
}
|
|
1853
|
+
for (const x of still)
|
|
1854
|
+
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`);
|
|
1855
|
+
for (const dir of keep)
|
|
1856
|
+
if (keptAside(dir) && !still.some((x) => x.runHome === dir))
|
|
1857
|
+
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`);
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
console.error(`\n${B("Removed")}`);
|
|
1861
|
+
for (const r of removed)
|
|
1862
|
+
console.error(` ${C("✓")} ${r}`);
|
|
1863
|
+
if (!removed.length)
|
|
1864
|
+
console.error(` ${D("nothing — Pantheon had nothing installed here")}`);
|
|
1865
|
+
console.error(`\n${B("Left")}`);
|
|
1866
|
+
for (const l of left)
|
|
1867
|
+
console.error(` • ${l}`);
|
|
1868
|
+
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")}`);
|
|
1869
|
+
console.error(` • ${D("the relay's ledger on disk, and anything you pushed to git")}`);
|
|
1870
|
+
if (plan.teams.length) {
|
|
1871
|
+
console.error(`\n ${D(`Your teammates still list you in ${plan.teams.map((t) => `"${t.name}"`).join(", ")}. Ask someone in each to run:`)}`);
|
|
1872
|
+
console.error(` ${D(`pantheon admit ${me?.member ?? plan.member ?? "<your name>"} --revoke`)}`);
|
|
1680
1873
|
}
|
|
1681
1874
|
console.error(`\n ${D(`If Pantheon let you down, one line on why to ${FEEDBACK_ADDRESS} would help more than you might think.`)}`);
|
|
1682
1875
|
return;
|
|
@@ -1689,35 +1882,42 @@ async function main() {
|
|
|
1689
1882
|
return;
|
|
1690
1883
|
}
|
|
1691
1884
|
case "panic": {
|
|
1692
|
-
const cfg = need();
|
|
1693
1885
|
console.error(`${B("PANIC — locking down this machine")}\n`);
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
const
|
|
1697
|
-
const
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1886
|
+
const { panicMachine, waitStopped } = await import("./machine.js");
|
|
1887
|
+
const all = loadAll();
|
|
1888
|
+
const teams = Object.values(all?.spaces ?? {});
|
|
1889
|
+
const r = await panicMachine({
|
|
1890
|
+
stopWorker: () => stopProc("worker"),
|
|
1891
|
+
workerDown: () => waitStopped("worker", 5000),
|
|
1892
|
+
teams,
|
|
1893
|
+
revoke: async (m) => {
|
|
1894
|
+
const cfg = configFor(all, m, null);
|
|
1895
|
+
const client = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
1896
|
+
try {
|
|
1897
|
+
await client.connect();
|
|
1898
|
+
await new Promise((res) => setTimeout(res, 300));
|
|
1899
|
+
const wakers = client.members.filter((x) => x.admitted && x.may_wake && x.member !== cfg.member);
|
|
1900
|
+
for (const w of wakers)
|
|
1901
|
+
await client.admit(w.member, false, false).catch(() => { });
|
|
1902
|
+
return wakers.map((w) => w.member);
|
|
1903
|
+
}
|
|
1904
|
+
finally {
|
|
1905
|
+
client.close();
|
|
1906
|
+
}
|
|
1907
|
+
},
|
|
1908
|
+
});
|
|
1909
|
+
for (const l of r.lines)
|
|
1910
|
+
console.error(l);
|
|
1713
1911
|
console.error(D(` Your peers can still message you; only unattended runs are off.`));
|
|
1714
1912
|
console.error(D(` To undo: pantheon service install worker and pantheon admit <who> --may-wake`));
|
|
1913
|
+
if (!r.ok)
|
|
1914
|
+
process.exitCode = 1;
|
|
1715
1915
|
return;
|
|
1716
1916
|
}
|
|
1717
1917
|
case "members": {
|
|
1718
1918
|
const cfg = need();
|
|
1719
1919
|
const client = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
1720
|
-
await client.connect().catch((e) => die(client.pending ?? e.
|
|
1920
|
+
await client.connect().catch((e) => die(client.pending ?? connectFailure(e, cfg.relay)));
|
|
1721
1921
|
await new Promise((r) => setTimeout(r, 300));
|
|
1722
1922
|
console.log(`${B(`who is in ${cfg.space}`)}\n`);
|
|
1723
1923
|
for (const e of client.members) {
|
|
@@ -1738,7 +1938,7 @@ async function main() {
|
|
|
1738
1938
|
const cfg = need();
|
|
1739
1939
|
const who = argv[1] ?? die("usage: pantheon admit <member> [--may-wake] [--revoke] [--forget]");
|
|
1740
1940
|
const client = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
1741
|
-
await client.connect().catch((e) => die(client.pending ?? e.
|
|
1941
|
+
await client.connect().catch((e) => die(client.pending ?? connectFailure(e, cfg.relay)));
|
|
1742
1942
|
await new Promise((r) => setTimeout(r, 250));
|
|
1743
1943
|
const forget = argv.includes("--forget");
|
|
1744
1944
|
const revoke = argv.includes("--revoke");
|
|
@@ -1785,16 +1985,21 @@ async function main() {
|
|
|
1785
1985
|
return;
|
|
1786
1986
|
}
|
|
1787
1987
|
case "whoami": {
|
|
1788
|
-
const cfg =
|
|
1988
|
+
const cfg = identity() ?? die("not set up on this machine — pantheon signup, or pantheon join <invite>");
|
|
1989
|
+
const team = resolveFor(process.cwd()).cfg;
|
|
1789
1990
|
if (cfg.account) {
|
|
1790
1991
|
console.log(`account ${C(cfg.account.username)} ${D(`<${cfg.account.email}>`)} ${D("verified")}`);
|
|
1791
1992
|
}
|
|
1993
|
+
else if (cfg.pendingAuth) {
|
|
1994
|
+
console.log(`account ${D(`not signed in yet — a code was sent to ${cfg.pendingAuth.email}: pantheon verify <code>`)}`);
|
|
1995
|
+
}
|
|
1792
1996
|
else {
|
|
1793
1997
|
console.log(`account ${D("not signed in")} ${D("— pantheon signup / pantheon login")}`);
|
|
1794
1998
|
}
|
|
1795
1999
|
console.log(`member ${C(cfg.member)}`);
|
|
1796
|
-
|
|
1797
|
-
console.log(`
|
|
2000
|
+
const teams = Object.values(loadAll()?.spaces ?? {}).map((m) => m.name);
|
|
2001
|
+
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>"))}`);
|
|
2002
|
+
console.log(`relay ${team?.space ? team.relay : cfg.relay}`);
|
|
1798
2003
|
console.log(`this device ${C(fingerprint(cfg.publicKey))}`);
|
|
1799
2004
|
console.log(D(`\nManage your devices and sessions: pantheon sessions`));
|
|
1800
2005
|
return;
|
|
@@ -1806,7 +2011,7 @@ async function main() {
|
|
|
1806
2011
|
return interactiveOnboard(flag("relay"));
|
|
1807
2012
|
const email = flag("email") ?? die(`usage: pantheon ${purpose} --email you@work.com${purpose === "signup" ? " --username <name>" : ""} [--relay wss://...]`);
|
|
1808
2013
|
const username = purpose === "signup" ? (flag("username") ?? die("usage: pantheon signup --email you@work.com --username <name>")) : undefined;
|
|
1809
|
-
const prior =
|
|
2014
|
+
const prior = identity();
|
|
1810
2015
|
const relay = flag("relay") ?? prior?.relay ?? process.env.PANTHEON_RELAY ?? DEFAULT_RELAY;
|
|
1811
2016
|
const kp = prior?.publicKey && prior.privateKey ? { publicKey: prior.publicKey, privateKey: prior.privateKey } : newKeypair();
|
|
1812
2017
|
const label = flag("label") ?? `${process.env.USER ?? "a"}'s ${process.platform === "darwin" ? "mac" : process.platform}`;
|
|
@@ -1814,7 +2019,7 @@ async function main() {
|
|
|
1814
2019
|
const body = purpose === "signup" ? { email, username, pubkey: kp.publicKey, label } : { email, pubkey: kp.publicKey, label };
|
|
1815
2020
|
const r = await fetch(`${base}/auth/${purpose}`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) })
|
|
1816
2021
|
.then(async (x) => ({ status: x.status, j: await x.json() }))
|
|
1817
|
-
.catch((e) => die(
|
|
2022
|
+
.catch((e) => die(describeNetError(e, relay)));
|
|
1818
2023
|
if (r.status !== 200)
|
|
1819
2024
|
die(String(r.j.error ?? `signup failed (${r.status})`));
|
|
1820
2025
|
save({ ...(prior ?? {}), relay, member: username ?? prior?.member ?? "me", space: prior?.space ?? "", token: prior?.token ?? "",
|
|
@@ -1831,7 +2036,7 @@ async function main() {
|
|
|
1831
2036
|
return;
|
|
1832
2037
|
}
|
|
1833
2038
|
case "verify": {
|
|
1834
|
-
const cfg =
|
|
2039
|
+
const cfg = identity() ?? die("run `pantheon signup` or `pantheon login` first");
|
|
1835
2040
|
const pending = cfg.pendingAuth ?? die("nothing to verify — run `pantheon signup` or `pantheon login` first");
|
|
1836
2041
|
let code = (argv[1] ?? flag("code") ?? "").trim();
|
|
1837
2042
|
if (!code && process.stdin.isTTY) {
|
|
@@ -1842,9 +2047,9 @@ async function main() {
|
|
|
1842
2047
|
if (!code)
|
|
1843
2048
|
die("usage: pantheon verify <6-digit-code>");
|
|
1844
2049
|
const base = cfg.relay.replace(/^ws/, "http").replace(/\/+$/, "");
|
|
1845
|
-
const r = await fetch(`${base}/auth/verify`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: pending.email, code }) })
|
|
2050
|
+
const r = await fetch(`${base}/auth/verify`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: pending.email, code, pubkey: cfg.publicKey }) })
|
|
1846
2051
|
.then(async (x) => ({ status: x.status, j: await x.json() }))
|
|
1847
|
-
.catch((e) => die(
|
|
2052
|
+
.catch((e) => die(describeNetError(e, cfg.relay)));
|
|
1848
2053
|
if (r.status !== 200)
|
|
1849
2054
|
die(String(r.j.error ?? "verification failed"));
|
|
1850
2055
|
const acct = r.j.account;
|
|
@@ -1858,7 +2063,7 @@ async function main() {
|
|
|
1858
2063
|
return;
|
|
1859
2064
|
}
|
|
1860
2065
|
case "sessions": {
|
|
1861
|
-
const cfg =
|
|
2066
|
+
const cfg = identity() ?? die("not set up on this machine — pantheon signup first");
|
|
1862
2067
|
if (!cfg.account)
|
|
1863
2068
|
die("not signed in — run `pantheon signup` or `pantheon login` first");
|
|
1864
2069
|
const sub = argv[1];
|
|
@@ -1870,7 +2075,7 @@ async function main() {
|
|
|
1870
2075
|
const me = await signedFetch(cfg, "GET", "/account/sessions").then((x) => x.json()).catch(() => ({}));
|
|
1871
2076
|
thisDevice = me.this_device ?? "";
|
|
1872
2077
|
}
|
|
1873
|
-
const r = await signedFetch(cfg, "POST", "/account/revoke", { kind, id }).then((x) => x.json()).catch((e) => die(e.
|
|
2078
|
+
const r = await signedFetch(cfg, "POST", "/account/revoke", { kind, id }).then((x) => x.json()).catch((e) => die(describeNetError(e, cfg.relay)));
|
|
1874
2079
|
if (kind === "device" && id === thisDevice) {
|
|
1875
2080
|
const cl = { ...cfg };
|
|
1876
2081
|
delete cl.account;
|
|
@@ -1879,7 +2084,7 @@ async function main() {
|
|
|
1879
2084
|
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)" : ""}`);
|
|
1880
2085
|
return;
|
|
1881
2086
|
}
|
|
1882
|
-
const r = await signedFetch(cfg, "GET", "/account/sessions").then((x) => x.json()).catch((e) => die(e.
|
|
2087
|
+
const r = await signedFetch(cfg, "GET", "/account/sessions").then((x) => x.json()).catch((e) => die(describeNetError(e, cfg.relay)));
|
|
1883
2088
|
console.log(`${B(r.account.username)} ${D(`<${r.account.email}>`)}\n`);
|
|
1884
2089
|
console.log(B("devices"));
|
|
1885
2090
|
for (const d of r.devices)
|
|
@@ -1894,12 +2099,12 @@ async function main() {
|
|
|
1894
2099
|
return;
|
|
1895
2100
|
}
|
|
1896
2101
|
case "audit": {
|
|
1897
|
-
const cfg =
|
|
2102
|
+
const cfg = identity() ?? die("not set up on this machine — pantheon signup first");
|
|
1898
2103
|
if (!cfg.account)
|
|
1899
2104
|
die("not signed in — run `pantheon signup` first");
|
|
1900
2105
|
const space = flag("space");
|
|
1901
2106
|
const since = flag("since") ? String(Date.now() - Number(flag("since")) * 3_600_000) : undefined;
|
|
1902
|
-
const r = await signedFetch(cfg, "GET", "/audit", { ...(space ? { space } : {}), ...(since ? { since } : {}) }).then((x) => x.json()).catch((e) => die(e.
|
|
2107
|
+
const r = await signedFetch(cfg, "GET", "/audit", { ...(space ? { space } : {}), ...(since ? { since } : {}) }).then((x) => x.json()).catch((e) => die(describeNetError(e, cfg.relay)));
|
|
1903
2108
|
if (r.error)
|
|
1904
2109
|
die(r.error);
|
|
1905
2110
|
if (argv.includes("--json")) {
|
|
@@ -1914,22 +2119,34 @@ async function main() {
|
|
|
1914
2119
|
return;
|
|
1915
2120
|
}
|
|
1916
2121
|
case "logout": {
|
|
1917
|
-
const cfg =
|
|
2122
|
+
const cfg = identity() ?? die("not set up on this machine — pantheon signup first");
|
|
1918
2123
|
if (!cfg.account) {
|
|
1919
2124
|
console.error(D("not signed in — nothing to log out"));
|
|
1920
2125
|
return;
|
|
1921
2126
|
}
|
|
1922
|
-
const list = await signedFetch(cfg, "GET", "/account/sessions").then((x) => x.json()
|
|
1923
|
-
|
|
1924
|
-
|
|
2127
|
+
const list = await signedFetch(cfg, "GET", "/account/sessions").then(async (x) => ({ ok: x.ok, status: x.status, j: await x.json().catch(() => ({})) }))
|
|
2128
|
+
.catch((e) => die(describeNetError(e, cfg.relay)));
|
|
2129
|
+
if (!list.ok && list.status !== 403)
|
|
2130
|
+
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`);
|
|
2131
|
+
let revoked = list.status === 403;
|
|
2132
|
+
if (list.ok && list.j.this_device) {
|
|
2133
|
+
const r = await signedFetch(cfg, "POST", "/account/revoke", { kind: "device", id: list.j.this_device })
|
|
2134
|
+
.then(async (x) => ({ ok: x.ok, j: await x.json().catch(() => ({})) }))
|
|
2135
|
+
.catch((e) => die(describeNetError(e, cfg.relay)));
|
|
2136
|
+
if (!r.ok)
|
|
2137
|
+
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`);
|
|
2138
|
+
revoked = true;
|
|
2139
|
+
}
|
|
2140
|
+
else if (list.ok)
|
|
2141
|
+
revoked = true;
|
|
1925
2142
|
const cleared = { ...cfg };
|
|
1926
2143
|
delete cleared.account;
|
|
1927
2144
|
save(cleared);
|
|
1928
|
-
console.error(`${C("logged out")} — this device is signed out. Sign back in with ${C("pantheon login --email " + cfg.account.email)}.`);
|
|
2145
|
+
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)}.`);
|
|
1929
2146
|
return;
|
|
1930
2147
|
}
|
|
1931
2148
|
case "account": {
|
|
1932
|
-
const cfg =
|
|
2149
|
+
const cfg = identity() ?? die("not set up on this machine — pantheon signup first");
|
|
1933
2150
|
if (!cfg.account)
|
|
1934
2151
|
die("not signed in");
|
|
1935
2152
|
const sub = argv[1];
|
|
@@ -1937,7 +2154,7 @@ async function main() {
|
|
|
1937
2154
|
die("usage: pantheon account delete");
|
|
1938
2155
|
if (!argv.includes("--yes"))
|
|
1939
2156
|
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.`);
|
|
1940
|
-
const r = await signedFetch(cfg, "POST", "/account/delete").then((x) => x.json()).catch((e) => die(e.
|
|
2157
|
+
const r = await signedFetch(cfg, "POST", "/account/delete").then((x) => x.json()).catch((e) => die(describeNetError(e, cfg.relay)));
|
|
1941
2158
|
if (!r.ok)
|
|
1942
2159
|
die(String(r.error ?? "delete failed"));
|
|
1943
2160
|
const cleared = { ...cfg };
|
|
@@ -2127,7 +2344,7 @@ async function main() {
|
|
|
2127
2344
|
if (overlap)
|
|
2128
2345
|
die("an overlapping delegation already exists for one of these domains; remove it first");
|
|
2129
2346
|
const client = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
2130
|
-
await client.connect().catch((e) => die(e.
|
|
2347
|
+
await client.connect().catch((e) => die(connectFailure(e, cfg.relay)));
|
|
2131
2348
|
if (!client.profiles.has(to)) {
|
|
2132
2349
|
client.close();
|
|
2133
2350
|
die(`${to} has no published profile; delegation would have nowhere verifiable to land`);
|
|
@@ -2169,7 +2386,7 @@ async function main() {
|
|
|
2169
2386
|
case "directory": {
|
|
2170
2387
|
const cfg = need();
|
|
2171
2388
|
const client = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
2172
|
-
await client.connect().catch((e) => die(e.
|
|
2389
|
+
await client.connect().catch((e) => die(connectFailure(e, cfg.relay)));
|
|
2173
2390
|
if (!client.profiles.size)
|
|
2174
2391
|
console.log(`${D("no organization profiles published yet")}`);
|
|
2175
2392
|
for (const p of client.profiles.values()) {
|
|
@@ -2226,12 +2443,41 @@ async function main() {
|
|
|
2226
2443
|
return;
|
|
2227
2444
|
}
|
|
2228
2445
|
case "explain": {
|
|
2229
|
-
const cfg = need();
|
|
2230
2446
|
const { readFileSync, rmSync } = await import("node:fs");
|
|
2447
|
+
const CONFIG_DIR = dirname(CONFIG_PATH);
|
|
2448
|
+
let hud = null;
|
|
2449
|
+
if (argv.includes("--queued")) {
|
|
2450
|
+
const f = join(CONFIG_DIR, "bin", ".hud-action.json");
|
|
2451
|
+
try {
|
|
2452
|
+
hud = JSON.parse(readFileSync(f, "utf8"));
|
|
2453
|
+
rmSync(f, { force: true });
|
|
2454
|
+
}
|
|
2455
|
+
catch {
|
|
2456
|
+
hud = null;
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
const cfg = (() => {
|
|
2460
|
+
if (!hud?.space)
|
|
2461
|
+
return need();
|
|
2462
|
+
const f = loadAll();
|
|
2463
|
+
const m = f ? findMembership(f, hud.space) : undefined;
|
|
2464
|
+
if (f && m)
|
|
2465
|
+
return configFor(f, m, null);
|
|
2466
|
+
const here = resolveFor(process.cwd()).cfg;
|
|
2467
|
+
if (here?.space === hud.space)
|
|
2468
|
+
return here;
|
|
2469
|
+
const why = `this machine is no longer in the team "${hud.space}"`;
|
|
2470
|
+
if (hud.action === "reply" || hud.action === "retract") {
|
|
2471
|
+
console.log(`not sent: ${why}`);
|
|
2472
|
+
process.exit(1);
|
|
2473
|
+
}
|
|
2474
|
+
return die(why);
|
|
2475
|
+
})();
|
|
2476
|
+
if (hud && hud.goal === "")
|
|
2477
|
+
delete hud.goal;
|
|
2231
2478
|
const { pickExplainAgent, decisionPrompt: dp, allDecisionsPrompt: adp, decisionTitle: dt, sessionName } = await import("./decision-ui.js");
|
|
2232
2479
|
const { loadProfile } = await import("./profile.js");
|
|
2233
2480
|
const { detectAvailable, launchArgs } = await import("./agents.js");
|
|
2234
|
-
const CONFIG_DIR = dirname(CONFIG_PATH);
|
|
2235
2481
|
let id = argv.find((a, i) => i > 0 && !a.startsWith("--"));
|
|
2236
2482
|
if (argv.includes("--queued")) {
|
|
2237
2483
|
const queued = join(CONFIG_DIR, "bin", ".explain-id");
|
|
@@ -2243,17 +2489,6 @@ async function main() {
|
|
|
2243
2489
|
if (id === "all")
|
|
2244
2490
|
id = undefined;
|
|
2245
2491
|
}
|
|
2246
|
-
let hud = null;
|
|
2247
|
-
if (argv.includes("--queued")) {
|
|
2248
|
-
const f = join(CONFIG_DIR, "bin", ".hud-action.json");
|
|
2249
|
-
try {
|
|
2250
|
-
hud = JSON.parse(readFileSync(f, "utf8"));
|
|
2251
|
-
rmSync(f, { force: true });
|
|
2252
|
-
}
|
|
2253
|
-
catch {
|
|
2254
|
-
hud = null;
|
|
2255
|
-
}
|
|
2256
|
-
}
|
|
2257
2492
|
if (hud?.action === "fix") {
|
|
2258
2493
|
const dir = hud.dir && existsSync(hud.dir) ? hud.dir : process.cwd();
|
|
2259
2494
|
const steps = hud.lines ?? [];
|
|
@@ -2301,22 +2536,31 @@ async function main() {
|
|
|
2301
2536
|
if (!body)
|
|
2302
2537
|
notSent("nothing to send");
|
|
2303
2538
|
const c2 = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
2304
|
-
await c2.connect().catch((e) => notSent(e.
|
|
2539
|
+
await c2.connect().catch((e) => notSent(connectFailure(e, cfg.relay)));
|
|
2305
2540
|
const o = hud.decision ? c2.obligations.find((x) => x.id === hud.decision) : undefined;
|
|
2306
|
-
const
|
|
2307
|
-
const target =
|
|
2308
|
-
|
|
2541
|
+
const targetId = o?.ref ?? hud.act;
|
|
2542
|
+
const target = targetId ? (await fetchAct(cfg, targetId, 8_000))?.act : undefined;
|
|
2543
|
+
let goal = o?.goal ?? target?.goal ?? hud.goal ?? null;
|
|
2309
2544
|
if (kind === "ANSWER" && !o && !target) {
|
|
2310
2545
|
c2.close();
|
|
2311
2546
|
notSent("that decision is no longer waiting — nothing to answer");
|
|
2312
2547
|
}
|
|
2548
|
+
if (goal && c2.goals.get(goal)?.state === "CLOSED") {
|
|
2549
|
+
if (kind === "ASK") {
|
|
2550
|
+
c2.close();
|
|
2551
|
+
notSent("that goal is closed — open a new one to ask more");
|
|
2552
|
+
}
|
|
2553
|
+
goal = null;
|
|
2554
|
+
}
|
|
2313
2555
|
const ref = kind === "ANSWER" ? (o?.ref ?? target?.id ?? null) : kind === "ASK" ? (target?.id ?? null) : null;
|
|
2314
2556
|
const to = kind === "ASK" ? (target && target.from !== cfg.member ? [target.from] : null) : null;
|
|
2315
2557
|
if (kind === "ASK" && !to) {
|
|
2316
2558
|
c2.close();
|
|
2317
2559
|
notSent("cannot tell who raised this — use Show the thread and reply from there");
|
|
2318
2560
|
}
|
|
2319
|
-
const
|
|
2561
|
+
const summary = body.length > LIMITS.summaryRequiredOver
|
|
2562
|
+
? (body.split(/\n|(?<=[.!?])\s/)[0] ?? body).trim().slice(0, LIMITS.summaryChars) : undefined;
|
|
2563
|
+
const sent = await c2.send({ act: kind, body, goal, ref, to, after_seq: c2.readSeq, meta: { surface: "menubar" }, ...(summary ? { summary } : {}) })
|
|
2320
2564
|
.catch((e) => notSent(e.message));
|
|
2321
2565
|
c2.close();
|
|
2322
2566
|
console.log(`sent ${kind} id=${sent.id} seq=${sent.seq}`);
|
|
@@ -2327,7 +2571,7 @@ async function main() {
|
|
|
2327
2571
|
if (!hud.act)
|
|
2328
2572
|
notSent("nothing to retract");
|
|
2329
2573
|
const c2 = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
2330
|
-
await c2.connect().catch((e) => notSent(e.
|
|
2574
|
+
await c2.connect().catch((e) => notSent(connectFailure(e, cfg.relay)));
|
|
2331
2575
|
const sent = await c2.send({ act: "RETRACT", body: "Retracted from the menu bar.", goal: null, ref: hud.act, to: null, meta: { surface: "menubar" } })
|
|
2332
2576
|
.catch((e) => notSent(e.message));
|
|
2333
2577
|
c2.close();
|
|
@@ -2336,14 +2580,15 @@ async function main() {
|
|
|
2336
2580
|
}
|
|
2337
2581
|
if (hud?.action === "done") {
|
|
2338
2582
|
const c2 = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
2339
|
-
await c2.connect().catch((e) => die(e.
|
|
2583
|
+
await c2.connect().catch((e) => die(connectFailure(e, cfg.relay)));
|
|
2340
2584
|
const o = hud.decision ? c2.obligations.find((x) => x.id === hud.decision) : undefined;
|
|
2341
2585
|
if (!o) {
|
|
2342
2586
|
c2.close();
|
|
2343
2587
|
console.error(D("that decision is no longer waiting on you"));
|
|
2344
2588
|
return;
|
|
2345
2589
|
}
|
|
2346
|
-
|
|
2590
|
+
const open = o.goal && c2.goals.get(o.goal)?.state !== "CLOSED" ? o.goal : null;
|
|
2591
|
+
await c2.send({ act: "ANSWER", ref: o.ref, goal: open, after_seq: c2.readSeq,
|
|
2347
2592
|
body: "Marked done from the alert — handled outside this thread." }).catch((e) => die(e.message));
|
|
2348
2593
|
c2.close();
|
|
2349
2594
|
console.error(D(`done — ${o.id} is no longer waiting on you`));
|
|
@@ -2359,19 +2604,18 @@ async function main() {
|
|
|
2359
2604
|
}
|
|
2360
2605
|
if (wd)
|
|
2361
2606
|
console.error(D(` ${wd.note}`));
|
|
2362
|
-
const
|
|
2363
|
-
const a = hud.act ? acts.find((x) => x.id === hud.act) : undefined;
|
|
2607
|
+
const a = hud.act ? (await fetchAct(cfg, hud.act))?.act : undefined;
|
|
2364
2608
|
let o;
|
|
2365
2609
|
if (hud.decision) {
|
|
2366
2610
|
const c2 = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
2367
|
-
await c2.connect().catch((e) => die(e.
|
|
2611
|
+
await c2.connect().catch((e) => die(connectFailure(e, cfg.relay)));
|
|
2368
2612
|
o = c2.obligations.find((x) => x.id === hud.decision);
|
|
2369
2613
|
c2.close();
|
|
2370
2614
|
}
|
|
2371
2615
|
if (a) {
|
|
2372
2616
|
console.log(formatAct(a));
|
|
2373
2617
|
if (a.ref) {
|
|
2374
|
-
const r =
|
|
2618
|
+
const r = (await fetchAct(cfg, a.ref))?.act;
|
|
2375
2619
|
if (r)
|
|
2376
2620
|
console.log(D("\nin reply to:\n") + formatAct(r, { short: true }));
|
|
2377
2621
|
}
|
|
@@ -2399,11 +2643,7 @@ async function main() {
|
|
|
2399
2643
|
rl.close();
|
|
2400
2644
|
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.`;
|
|
2401
2645
|
console.error(D(`opening ${agent.id}…`));
|
|
2402
|
-
|
|
2403
|
-
{
|
|
2404
|
-
const sp = agentSpawn(agent.binary, launchArgs(agent.id, prompt, sessionName(prompt)));
|
|
2405
|
-
spawnSync(sp.command, sp.args, { stdio: "inherit", ...sp.options });
|
|
2406
|
-
}
|
|
2646
|
+
await launchAgent(agent.binary, (p) => launchArgs(agent.id, p, sessionName(prompt)), prompt);
|
|
2407
2647
|
return;
|
|
2408
2648
|
}
|
|
2409
2649
|
if (choice === "2") {
|
|
@@ -2444,7 +2684,8 @@ async function main() {
|
|
|
2444
2684
|
continue;
|
|
2445
2685
|
}
|
|
2446
2686
|
if (choice === "3" && goalId) {
|
|
2447
|
-
|
|
2687
|
+
const thread = await fetchHistory(cfg, { goal: goalId }).catch(() => []);
|
|
2688
|
+
for (const t of selectActs(thread, { goal: goalId, last: 40 }))
|
|
2448
2689
|
console.log(formatAct(t, { short: true }));
|
|
2449
2690
|
continue;
|
|
2450
2691
|
}
|
|
@@ -2471,10 +2712,7 @@ async function main() {
|
|
|
2471
2712
|
const bin = cliOnPath("grok") ?? die("grok is not on PATH here");
|
|
2472
2713
|
console.error(D("opening grok…"));
|
|
2473
2714
|
const { spawnSync } = await import("node:child_process");
|
|
2474
|
-
|
|
2475
|
-
const sp = agentSpawn(bin, [hud.prompt]);
|
|
2476
|
-
spawnSync(sp.command, sp.args, { stdio: "inherit", ...(cwd ? { cwd } : {}), ...sp.options });
|
|
2477
|
-
}
|
|
2715
|
+
await launchAgent(bin, (p) => [p], hud.prompt, cwd ?? undefined);
|
|
2478
2716
|
return;
|
|
2479
2717
|
}
|
|
2480
2718
|
if (hud.prompt) {
|
|
@@ -2483,10 +2721,7 @@ async function main() {
|
|
|
2483
2721
|
die("no agent CLI found on this machine (claude, cursor-agent, codex, copilot, or kiro-cli)");
|
|
2484
2722
|
console.error(D(`opening ${agent.id}…`));
|
|
2485
2723
|
const { spawnSync } = await import("node:child_process");
|
|
2486
|
-
|
|
2487
|
-
const sp = agentSpawn(agent.binary, launchArgs(agent.id, hud.prompt, sessionName(hud.prompt)));
|
|
2488
|
-
spawnSync(sp.command, sp.args, { stdio: "inherit", ...(cwd ? { cwd } : {}), ...sp.options });
|
|
2489
|
-
}
|
|
2724
|
+
await launchAgent(agent.binary, (p) => launchArgs(agent.id, p, sessionName(hud.prompt)), hud.prompt, cwd ?? undefined);
|
|
2490
2725
|
return;
|
|
2491
2726
|
}
|
|
2492
2727
|
if (hud.decision)
|
|
@@ -2499,7 +2734,7 @@ async function main() {
|
|
|
2499
2734
|
}
|
|
2500
2735
|
}
|
|
2501
2736
|
const client = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
2502
|
-
await client.connect().catch((e) => die(e.
|
|
2737
|
+
await client.connect().catch((e) => die(connectFailure(e, cfg.relay)));
|
|
2503
2738
|
const waiting = client.obligations.filter((o) => o.kind === "HUMAN" && o.owner === `human:${cfg.member}`);
|
|
2504
2739
|
client.close();
|
|
2505
2740
|
const one = id ? waiting.find((o) => o.id === id) : waiting.length === 1 ? waiting[0] : undefined;
|
|
@@ -2515,10 +2750,7 @@ async function main() {
|
|
|
2515
2750
|
die("no agent CLI found on this machine (claude, cursor-agent, codex, copilot, or kiro-cli)");
|
|
2516
2751
|
console.error(D(`opening ${agent.id} with the question…`));
|
|
2517
2752
|
const { spawnSync } = await import("node:child_process");
|
|
2518
|
-
|
|
2519
|
-
const sp = agentSpawn(agent.binary, launchArgs(agent.id, prompt, sessionName(prompt)));
|
|
2520
|
-
spawnSync(sp.command, sp.args, { stdio: "inherit", ...sp.options });
|
|
2521
|
-
}
|
|
2753
|
+
await launchAgent(agent.binary, (p) => launchArgs(agent.id, p, sessionName(prompt)), prompt);
|
|
2522
2754
|
return;
|
|
2523
2755
|
}
|
|
2524
2756
|
case "approvals": {
|
|
@@ -2527,7 +2759,7 @@ async function main() {
|
|
|
2527
2759
|
const conns = [];
|
|
2528
2760
|
for (const c of targets()) {
|
|
2529
2761
|
const cl = new PantheonClient(c, undefined, { role: "cli" });
|
|
2530
|
-
await cl.connect().catch((e) => die(`${c.space}: ${e.
|
|
2762
|
+
await cl.connect().catch((e) => die(`${c.space}: ${connectFailure(e, c.relay)}`));
|
|
2531
2763
|
conns.push({ cfg: c, client: cl });
|
|
2532
2764
|
}
|
|
2533
2765
|
const closeAll = () => { for (const c of conns)
|
|
@@ -2572,7 +2804,7 @@ async function main() {
|
|
|
2572
2804
|
const cfg = need();
|
|
2573
2805
|
const sub = argv[1] ?? "list";
|
|
2574
2806
|
const client = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
2575
|
-
await client.connect().catch((e) => die(e.
|
|
2807
|
+
await client.connect().catch((e) => die(connectFailure(e, cfg.relay)));
|
|
2576
2808
|
const waiting = client.obligations.filter((o) => o.kind === "HUMAN" && o.owner === `human:${cfg.member}`);
|
|
2577
2809
|
const id = argv[2];
|
|
2578
2810
|
if (sub === "answer") {
|
|
@@ -2885,7 +3117,7 @@ async function main() {
|
|
|
2885
3117
|
const sub = argv[1] ?? "list";
|
|
2886
3118
|
const { repo: _r, repoOrigin: _o, ...plain } = cfg;
|
|
2887
3119
|
const client = new PantheonClient(plain, undefined, { role: "cli" });
|
|
2888
|
-
await client.connect().catch((e) => die(client.pending ?? e.
|
|
3120
|
+
await client.connect().catch((e) => die(client.pending ?? connectFailure(e, plain.relay)));
|
|
2889
3121
|
if (sub === "add") {
|
|
2890
3122
|
const r = repoInfo(flag("path") ?? process.cwd()) ?? die("not inside a git repo");
|
|
2891
3123
|
const base = flag("base") ?? null;
|
|
@@ -2929,5 +3161,10 @@ async function main() {
|
|
|
2929
3161
|
process.exit(cmd === "help" || cmd === "--help" ? 0 : 1);
|
|
2930
3162
|
}
|
|
2931
3163
|
}
|
|
2932
|
-
main().catch((e) => die(e
|
|
3164
|
+
main().catch((e) => die(describeNetError(e, (() => { try {
|
|
3165
|
+
return load()?.relay;
|
|
3166
|
+
}
|
|
3167
|
+
catch {
|
|
3168
|
+
return undefined;
|
|
3169
|
+
} })())));
|
|
2933
3170
|
//# sourceMappingURL=cli.js.map
|