@join-pantheon/cli 0.33.1-rc.1 → 0.33.2-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -5
- package/dist/accounts.js +10 -0
- package/dist/agents.js +11 -0
- package/dist/board.js +55 -9
- package/dist/claude-confine.js +1 -1
- package/dist/cli.js +556 -89
- package/dist/client.js +8 -4
- package/dist/config.js +18 -4
- package/dist/continued.js +5 -0
- package/dist/desktop-app.js +103 -0
- package/dist/doctor.js +84 -32
- package/dist/folder-guard.js +12 -0
- package/dist/folder-snapshot.js +432 -0
- package/dist/install.js +15 -8
- package/dist/machine.js +84 -4
- package/dist/managed.js +2 -2
- package/dist/mcp.js +5 -3
- package/dist/network.js +156 -0
- package/dist/notice-linux.js +6 -5
- package/dist/notice-windows.js +2 -2
- package/dist/notice.js +13 -0
- package/dist/notifier.js +29 -2
- package/dist/notify.js +11 -0
- package/dist/onboard-defaults.js +37 -1
- package/dist/pins.js +7 -1
- package/dist/plans.js +23 -0
- package/dist/protocol.js +5 -0
- package/dist/quickstart.js +17 -11
- package/dist/refusal.js +4 -4
- package/dist/relay.js +332 -22
- package/dist/repo-find.js +6 -3
- package/dist/roster-file.js +50 -5
- package/dist/tray.js +12 -1
- package/dist/uninstall.js +38 -3
- package/dist/uri-handler.js +3 -2
- package/dist/verify-act.js +17 -0
- package/dist/worker-fleet.js +140 -5
- package/dist/worker.js +201 -63
- package/helper/macos/prebuilt/manifest.json +1 -1
- package/installer/install.ps1 +251 -15
- package/installer/install.sh +147 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -194,11 +194,12 @@ this machine does not have runs nothing: the asker is told it waits, and it is n
|
|
|
194
194
|
another repo's code. `pantheon spaces` and `doctor` say what the worker serves and what is not here.
|
|
195
195
|
|
|
196
196
|
**No git at all (a PM, ops, a founder's docs).** `start` or `join` from a plain folder binds it
|
|
197
|
-
(never your home folder). The worker serves it too
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
197
|
+
(never your home folder). The worker serves it too, with any agent you have installed (Claude Code,
|
|
198
|
+
Codex, Cursor or Copilot): each run works in a copy of the folder made for it, so the folder itself is
|
|
199
|
+
never changed. The copy leaves out links, `.git`, `node_modules` and secret-looking files (`.env`,
|
|
200
|
+
keys, credentials), and a folder over 5,000 files or 200 MB is not copied: the request waits for you,
|
|
201
|
+
and the teammate is told why. Anything the run writes or changes in its copy is kept as drafts, and
|
|
202
|
+
you are told which files and where; a copy with no drafts is deleted when the run ends.
|
|
202
203
|
|
|
203
204
|
### If something is wrong
|
|
204
205
|
|
package/dist/accounts.js
CHANGED
|
@@ -133,6 +133,7 @@ export class Accounts {
|
|
|
133
133
|
CREATE INDEX IF NOT EXISTS idx_sessions_account ON sessions(account_id);
|
|
134
134
|
CREATE INDEX IF NOT EXISTS idx_audit_ts ON audit(ts);
|
|
135
135
|
CREATE INDEX IF NOT EXISTS idx_audit_space ON audit(space);
|
|
136
|
+
CREATE INDEX IF NOT EXISTS idx_audit_actor ON audit(actor_account, event);
|
|
136
137
|
CREATE TABLE IF NOT EXISTS blocks (
|
|
137
138
|
pattern TEXT PRIMARY KEY,
|
|
138
139
|
kind TEXT NOT NULL,
|
|
@@ -433,6 +434,15 @@ export class Accounts {
|
|
|
433
434
|
deviceLive(pubkey) {
|
|
434
435
|
return this.deviceByPubkey(pubkey) !== null;
|
|
435
436
|
}
|
|
437
|
+
wasDeviceOf(accountId, fp, before) {
|
|
438
|
+
if (this.db.prepare("SELECT 1 FROM devices WHERE account_id = ? AND fingerprint = ? AND created_at <= ?").get(accountId, fp, before))
|
|
439
|
+
return true;
|
|
440
|
+
return !!this.db.prepare("SELECT 1 FROM audit WHERE actor_account = ? AND event = 'device_added' AND target = ? AND ts <= ?").get(accountId, fp, before);
|
|
441
|
+
}
|
|
442
|
+
lastEmailProof(accountId, fp) {
|
|
443
|
+
const r = this.db.prepare("SELECT MAX(ts) ts FROM audit WHERE actor_account = ? AND event IN ('login', 'email_verified') AND target = ?").get(accountId, fp);
|
|
444
|
+
return r?.ts ?? null;
|
|
445
|
+
}
|
|
436
446
|
deleteAccount(accountId, ip = "") {
|
|
437
447
|
const acct = this.accountById(accountId);
|
|
438
448
|
if (!acct)
|
package/dist/agents.js
CHANGED
|
@@ -137,6 +137,17 @@ export function agentsNeedingUnsandboxed(available = detectAvailable()) {
|
|
|
137
137
|
return [];
|
|
138
138
|
return [...new Set(available.filter((a) => !NOT_FOR_UNATTENDED_RUNS.has(a.backend.id)).map((a) => a.backend.label))];
|
|
139
139
|
}
|
|
140
|
+
export const FOLDER_READERS = AGENTS.map((a) => a.id);
|
|
141
|
+
export function readsFolders(id) {
|
|
142
|
+
return !!id && FOLDER_READERS.includes(id);
|
|
143
|
+
}
|
|
144
|
+
export function folderReaderLine(available) {
|
|
145
|
+
const takers = available.filter((a) => !NOT_FOR_UNATTENDED_RUNS.has(a.backend.id));
|
|
146
|
+
if (takers.length) {
|
|
147
|
+
return { ok: true, line: `any installed, signed-in agent here (${takers.map((a) => a.backend.label).join(", ")}) answers requests about bound folders while you are away, working in a copy — the folder itself is never changed` };
|
|
148
|
+
}
|
|
149
|
+
return { ok: false, line: `requests about bound folders wait for you: no agent here takes runs while you are away${available.length ? ` (${KIRO_UNATTENDED_REFUSAL})` : ""}` };
|
|
150
|
+
}
|
|
140
151
|
export const claudeSandboxable = (f, os = process.platform) => os !== "win32" && !!f.settings && versionAtLeast(f.version, "2.1.83");
|
|
141
152
|
export function kiroSignedIn(binary, run = runStatus, env = process.env, home = homedir(), os = platform()) {
|
|
142
153
|
if (env.KIRO_API_KEY)
|
package/dist/board.js
CHANGED
|
@@ -51,6 +51,12 @@ export function clearBoardState(pid, home = CONFIG_DIR) {
|
|
|
51
51
|
}
|
|
52
52
|
export const LINK_TTL_MS = 30_000;
|
|
53
53
|
export const boardLink = (port, nonce) => `http://127.0.0.1:${port}/#open=${nonce}`;
|
|
54
|
+
export const FOCUS_RE = /^[ao]_[A-Za-z0-9]{6,48}$/;
|
|
55
|
+
export function focusLink(link, focus) {
|
|
56
|
+
if (!FOCUS_RE.test(focus))
|
|
57
|
+
throw new Error("not an act or decision id");
|
|
58
|
+
return `${link}~focus=${focus}`;
|
|
59
|
+
}
|
|
54
60
|
const TOKEN_RE = /^[0-9a-f]{64}$/;
|
|
55
61
|
const ID_RE = /^[A-Za-z0-9_-]{1,80}$/;
|
|
56
62
|
const MAX_BODY_BYTES = 64 * 1024;
|
|
@@ -215,6 +221,11 @@ export function hudActionFor(op, item) {
|
|
|
215
221
|
if (op.kind === "ANSWER" && item.kind !== "decision")
|
|
216
222
|
return { error: "only the person it waits on can answer it — add a note or ask back" };
|
|
217
223
|
return { action: "reply", kind: op.kind, body: op.body, ...goal, ...space, ...(item.kind === "decision" ? { decision: item.id } : {}), ...ref };
|
|
224
|
+
case "wake-allow":
|
|
225
|
+
case "wake-deny":
|
|
226
|
+
if (item.kind !== "decision" || !item.wake || !item.ref)
|
|
227
|
+
return { error: "only a teammate waiting on your approval can be approved or declined here" };
|
|
228
|
+
return { action: "wake", allow: op.op === "wake-allow", member: item.wake, decision: item.id, act: item.ref, ...space };
|
|
218
229
|
case "open":
|
|
219
230
|
return { action: "open", prompt: item.prompt, ...space, ...goal, ...(item.kind === "decision" ? { decision: item.id } : {}) };
|
|
220
231
|
}
|
|
@@ -238,6 +249,8 @@ export function parseBoardAction(raw) {
|
|
|
238
249
|
switch (v.op) {
|
|
239
250
|
case "done": return only([]) ? { op: "done", id, ...sp } : null;
|
|
240
251
|
case "open": return only([]) ? { op: "open", id, ...sp } : null;
|
|
252
|
+
case "wake-allow":
|
|
253
|
+
case "wake-deny": return only([]) ? { op: v.op, id, ...sp } : null;
|
|
241
254
|
case "choose":
|
|
242
255
|
return only(["index"]) && Number.isInteger(v.index) && v.index >= 0 && v.index < 10
|
|
243
256
|
? { op: "choose", id, ...sp, index: v.index } : null;
|
|
@@ -561,6 +574,10 @@ export async function startBoardServer(o = {}) {
|
|
|
561
574
|
}
|
|
562
575
|
const q = await runQueued(hud);
|
|
563
576
|
const r = explainOutcome(q.r, q.nonce);
|
|
577
|
+
if (r.ok && hud.action === "wake") {
|
|
578
|
+
sendJson(res, 200, { ok: true, message: hud.allow ? `${hud.member} may start work on this machine — their waiting request runs now` : `Declined — ${hud.member}'s requests wait for you (pantheon wake allow ${hud.member} changes it)` });
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
564
581
|
if (r.sent) {
|
|
565
582
|
sent.set(r.sent.id, { ...(item.space ? { space: item.space } : {}), at: deps.now() });
|
|
566
583
|
for (const [k, v] of sent)
|
|
@@ -601,7 +618,7 @@ export async function startBoardServer(o = {}) {
|
|
|
601
618
|
}
|
|
602
619
|
return { port, token, get url() { return mintLink(); }, link: mintLink, server, get keep() { return keep; }, close, lastSeen: () => lastSeen };
|
|
603
620
|
}
|
|
604
|
-
const LINK_RE = /^http:\/\/127\.0\.0\.1:\d{1,5}\/#open=[0-9a-f]{64}
|
|
621
|
+
const LINK_RE = /^http:\/\/127\.0\.0\.1:\d{1,5}\/#open=[0-9a-f]{64}(?:~focus=[ao]_[A-Za-z0-9]{6,48})?$/;
|
|
605
622
|
export function inServiceCgroup(read = (p) => readFileSync(p, "utf8"), os = platform()) {
|
|
606
623
|
if (os !== "linux")
|
|
607
624
|
return false;
|
|
@@ -681,9 +698,13 @@ export function terminalCommand(home, actionId, os = platform(), env = process.e
|
|
|
681
698
|
return { cmd: "open", args: [file], opts };
|
|
682
699
|
}
|
|
683
700
|
if (os === "win32") {
|
|
684
|
-
|
|
701
|
+
const file = join(home, "bin", "pantheon-board-explain.cmd");
|
|
702
|
+
if ([...argv, file].some((a) => /["%\r\n]/.test(a)))
|
|
685
703
|
return { error: "the path to Pantheon holds a character the Windows console cannot pass safely" };
|
|
686
|
-
const
|
|
704
|
+
const text = ["@echo off", "%*", "if errorlevel 1 (", " echo.", " echo Pantheon: the agent did not start, or stopped with an error ^(above^). Press a key to close this window.", " pause >nul", ")", "exit", ""].join("\r\n");
|
|
705
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
706
|
+
writeFileSync(file, text);
|
|
707
|
+
const line = `start "Pantheon" "${file}" ${[...argv.slice(0, -1).map((a) => `"${a}"`), actionId].join(" ")}`;
|
|
687
708
|
return { cmd: "cmd.exe", args: ["/d", "/s", "/c", `"${line}"`], opts: { ...opts, windowsVerbatimArguments: true, windowsHide: true } };
|
|
688
709
|
}
|
|
689
710
|
if (!env.DISPLAY && !env.WAYLAND_DISPLAY)
|
|
@@ -1074,9 +1095,11 @@ const LANES = ${JSON.stringify(LANES.map((l) => l.id))};
|
|
|
1074
1095
|
const SESSION_KEY = "pantheon-board-session";
|
|
1075
1096
|
let link = null;
|
|
1076
1097
|
let session = null;
|
|
1098
|
+
// The item an alert opened the board on ("Approve or decline"): shown first, once.
|
|
1099
|
+
let focus = null;
|
|
1077
1100
|
{
|
|
1078
|
-
const m = /^#open=([0-9a-f]{64})
|
|
1079
|
-
if (m) link = m[1];
|
|
1101
|
+
const m = /^#open=([0-9a-f]{64})(?:~focus=([ao]_[A-Za-z0-9]{6,48}))?$/.exec(location.hash);
|
|
1102
|
+
if (m) { link = m[1]; focus = m[2] || null; }
|
|
1080
1103
|
// The nonce leaves the address bar and this history entry before anything else runs.
|
|
1081
1104
|
history.replaceState(null, "", "/");
|
|
1082
1105
|
}
|
|
@@ -1195,7 +1218,12 @@ function row(item, card) {
|
|
|
1195
1218
|
li.append(meta);
|
|
1196
1219
|
if (item.context) li.append(el("p", "ctx", "For: " + item.context));
|
|
1197
1220
|
const acts = el("div", "actions");
|
|
1198
|
-
if (card) {
|
|
1221
|
+
if (card && item.wake) {
|
|
1222
|
+
// A teammate waiting on this person's approval: Approve / Just this once /
|
|
1223
|
+
// Decline, and no "Mark done" — that would settle the question unanswered.
|
|
1224
|
+
wakeButtons(item).forEach((b) => acts.append(b));
|
|
1225
|
+
acts.append(tag(button("Reply…", () => openDetail(item, true)), item, "reply"));
|
|
1226
|
+
} else if (card) {
|
|
1199
1227
|
(item.choices || []).forEach((c, idx) => acts.append(tag(button(c.label, (ev) => act({ op: "choose", id: item.id, space: item.space, index: idx }, ev.currentTarget, c.label + " — sent."), idx === 0 ? "primary" : ""), item, "c" + idx)));
|
|
1200
1228
|
if (item.kind === "decision") acts.append(tag(button("Mark done", (ev) => act({ op: "done", id: item.id, space: item.space }, ev.currentTarget), ""), item, "done"));
|
|
1201
1229
|
acts.append(tag(button("Reply…", () => openDetail(item, true)), item, "reply"));
|
|
@@ -1208,6 +1236,17 @@ function row(item, card) {
|
|
|
1208
1236
|
return li;
|
|
1209
1237
|
}
|
|
1210
1238
|
|
|
1239
|
+
// Approve and Decline are pantheon wake allow|deny for the teammate the
|
|
1240
|
+
// question is about; "Just this once" is the question's own one-run answer.
|
|
1241
|
+
function wakeButtons(item, after) {
|
|
1242
|
+
const done = (r) => { if (after && r && r.ok) after(); };
|
|
1243
|
+
const out = [tag(button("Approve " + item.wake, (ev) => act({ op: "wake-allow", id: item.id, space: item.space }, ev.currentTarget).then(done), "primary"), item, "wake-allow")];
|
|
1244
|
+
const once = (item.choices || []).findIndex((c) => c.answer === "allow once");
|
|
1245
|
+
if (once >= 0) out.push(tag(button("Just this once", (ev) => act({ op: "choose", id: item.id, space: item.space, index: once }, ev.currentTarget, "Just this once — their request runs now.").then(done)), item, "wake-once"));
|
|
1246
|
+
out.push(tag(button("Decline", (ev) => act({ op: "wake-deny", id: item.id, space: item.space }, ev.currentTarget).then(done)), item, "wake-deny"));
|
|
1247
|
+
return out;
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1211
1250
|
async function copy(text) {
|
|
1212
1251
|
try { await navigator.clipboard.writeText(text); toast("Copied — paste it into your agent."); return; }
|
|
1213
1252
|
catch { /* fall back to a box the person copies from */ }
|
|
@@ -1265,15 +1304,16 @@ async function openDetail(item, focusReply) {
|
|
|
1265
1304
|
blocks($("d-body"), [{ kind: "para", text: item.detail || "" }]);
|
|
1266
1305
|
$("d-options").hidden = true; $("d-options").replaceChildren();
|
|
1267
1306
|
const ch = $("d-choices"); ch.replaceChildren();
|
|
1268
|
-
(item.
|
|
1269
|
-
ch.
|
|
1307
|
+
if (item.wake) wakeButtons(item, () => d.close()).forEach((b) => ch.append(b));
|
|
1308
|
+
else (item.choices || []).forEach((c, idx) => ch.append(button(c.label, (ev) => act({ op: "choose", id: item.id, space: item.space, index: idx }, ev.currentTarget, c.label + " — sent.").then((r) => { if (r && r.ok) d.close(); }), idx === 0 ? "primary" : "")));
|
|
1309
|
+
ch.hidden = !(item.wake || (item.choices && item.choices.length));
|
|
1270
1310
|
const kind = $("d-kind"); kind.replaceChildren();
|
|
1271
1311
|
for (const [v, label] of (item.kind === "decision" ? [["ANSWER", "Answer"], ["NOTE", "Note"], ["ASK", "Ask back"]] : [["NOTE", "Note"], ["ASK", "Ask back"]])) { const o = el("option", "", label); o.value = v; kind.append(o); }
|
|
1272
1312
|
$("d-text").value = "";
|
|
1273
1313
|
setStatus("");
|
|
1274
1314
|
$("d-retract").hidden = true;
|
|
1275
1315
|
const actions = $("d-actions"); actions.replaceChildren();
|
|
1276
|
-
if (item.kind === "decision") actions.append(button("Mark done", (ev) => act({ op: "done", id: item.id, space: item.space }, ev.currentTarget).then((r) => { if (r && r.ok) d.close(); })));
|
|
1316
|
+
if (item.kind === "decision" && !item.wake) actions.append(button("Mark done", (ev) => act({ op: "done", id: item.id, space: item.space }, ev.currentTarget).then((r) => { if (r && r.ok) d.close(); })));
|
|
1277
1317
|
actions.append(button("Explain in " + surface(), (ev) => act({ op: "open", id: item.id, space: item.space }, ev.currentTarget)));
|
|
1278
1318
|
if (item.goal && item.space) actions.append(button("Show the thread", () => thread(item)));
|
|
1279
1319
|
actions.append(button("Copy the question", () => copy(item.prompt)));
|
|
@@ -1394,6 +1434,12 @@ document.addEventListener("visibilitychange", () => { if (!document.hidden) refr
|
|
|
1394
1434
|
}
|
|
1395
1435
|
if (!session) { $("loading").hidden = true; banner("This board needs its link. Run pantheon board to open it.", true); return; }
|
|
1396
1436
|
await refresh();
|
|
1437
|
+
if (focus && state) {
|
|
1438
|
+
const want = focus;
|
|
1439
|
+
focus = null;
|
|
1440
|
+
const hit = (state.items || []).find((i) => i.id === want || i.ref === want);
|
|
1441
|
+
if (hit) openDetail(hit); else toast("That is no longer waiting on you.");
|
|
1442
|
+
}
|
|
1397
1443
|
schedule();
|
|
1398
1444
|
})();
|
|
1399
1445
|
})();
|
package/dist/claude-confine.js
CHANGED
|
@@ -139,7 +139,7 @@ function userExcludedCommands(file) {
|
|
|
139
139
|
return [];
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
|
-
const SECRET_FILE_GLOBS = [".env", ".env.*", "*.pem", "*.key", "*.p12", "*.pfx", "id_rsa*", "id_ed25519*", ".npmrc", ".netrc", "credentials*", "*.keystore", "service-account*.json"];
|
|
142
|
+
export const SECRET_FILE_GLOBS = [".env", ".env.*", "*.pem", "*.key", "*.p12", "*.pfx", "id_rsa*", "id_ed25519*", ".npmrc", ".netrc", "credentials*", "*.keystore", "service-account*.json"];
|
|
143
143
|
const PANTHEON_HOME_ENTRIES = ["config.json", "config.json.bak", "config.json.lock", "worker-policy.json", "profile.json", "continued.json", "bin", "runtime", "ops", "run-homes", "logs", "mcp", "data"];
|
|
144
144
|
function aroundWorktree(top, keeps, named) {
|
|
145
145
|
const keep = (typeof keeps === "string" ? [keeps] : [...keeps]).map((k) => resolve(k));
|