@join-pantheon/cli 0.32.0 → 0.33.0-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/dist/accounts.js +1 -1
- package/dist/board.js +1417 -0
- package/dist/claude-config.js +189 -0
- package/dist/cli.js +298 -58
- package/dist/command-state.js +2 -1
- package/dist/dbus.js +92 -13
- package/dist/doctor.js +36 -5
- package/dist/folder-guard.js +89 -0
- package/dist/git-safe.js +3 -3
- package/dist/host-env.js +47 -1
- package/dist/install.js +76 -10
- package/dist/installer.js +2 -1
- package/dist/ledger.js +2 -2
- package/dist/machine.js +38 -3
- package/dist/managed.js +27 -1
- package/dist/notice-linux.js +501 -0
- package/dist/notice-windows.js +323 -0
- package/dist/notifier.js +12 -3
- package/dist/quickstart.js +28 -11
- package/dist/relay.js +75 -9
- package/dist/service.js +455 -29
- package/dist/setup-guide.js +22 -5
- package/dist/store.js +18 -2
- package/dist/upgrade.js +249 -4
- package/dist/uri-handler.js +335 -0
- package/dist/win-host.js +969 -0
- package/dist/worker-policy.js +3 -0
- package/dist/worker.js +11 -11
- package/helper/macos/prebuilt/manifest.json +2 -2
- package/helper/macos/prebuilt/pantheon-hud +0 -0
- package/helper/windows/pantheon-host.cs +170 -0
- package/helper/windows/pantheon-notice.ps1 +127 -0
- package/helper/windows/pantheon-tray.ps1 +731 -0
- package/helper/windows/pantheon.ico +0 -0
- package/installer/install.ps1 +234 -18
- package/installer/install.sh +21 -0
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -9,24 +9,25 @@ process.emitWarning = ((warning, ...rest) => {
|
|
|
9
9
|
import { randomBytes } from "node:crypto";
|
|
10
10
|
import { gitConfigValue, suggestUsername } from "./onboard-defaults.js";
|
|
11
11
|
import { existsSync, mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
12
|
-
import { basename, dirname, join, resolve } from "node:path";
|
|
12
|
+
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
13
13
|
import { PantheonClient } from "./client.js";
|
|
14
14
|
import { agentSpawn, CmdLineUnsafe } from "./which.js";
|
|
15
15
|
import { fromInvite, looksLikeInviteCode, resolveInviteCode, load, save, toInvite, toJoinLink, resolveFor, loadAll, identity, repoInfo, bindRepo, bindFolder, unbindRepo, findMembership, setDefaultSpace, removeMembership, configFor, CONFIG_DIR, CONFIG_PATH, DEFAULT_RELAY, resolveForHook } from "./config.js";
|
|
16
16
|
import { fmtLimit } from "./plans.js";
|
|
17
17
|
import { validEmail, validUsername } from "./accounts.js";
|
|
18
|
-
import { install, launcher, installForUser, windowsRepoOverrides } from "./install.js";
|
|
18
|
+
import { install, launcher, installForUser, windowsRepoOverrides, agentAsksOnce } from "./install.js";
|
|
19
19
|
import { Relay } from "./relay.js";
|
|
20
20
|
import { runMcp } from "./mcp.js";
|
|
21
21
|
import { Worker } from "./worker.js";
|
|
22
22
|
import { DEFAULT_REMINDER_MS, ensureNativeNotifier, HumanNotifier, runNotifiers, sendDesktopNotification } from "./notifier.js";
|
|
23
|
+
import { linuxDesktopReachable } from "./notice-linux.js";
|
|
23
24
|
import { desktopUnavailable, hostEnv } from "./host-env.js";
|
|
24
25
|
import { AGENT_SURFACES, applyProfileInit, cleanList, isAgentSurface, loadProfile, organizationProfile, profileInitialized, saveProfile, teamProfile, wakeDefault, wakeMode } from "./profile.js";
|
|
25
26
|
import { runGuide, terminalIO, questionOrNull, parseSetupFlags, questionnaire, optOutsFrom } from "./setup-guide.js";
|
|
26
27
|
import { selectActs, formatAct, resolveRef } from "./history-view.js";
|
|
27
28
|
import { doctor, render, shareBlock } from "./doctor.js";
|
|
28
29
|
import { FEEDBACK_ADDRESS } from "./feedback.js";
|
|
29
|
-
import { menubarProc, notifierProc, relayProc, workerProc, wiring, logFileFor, dataSummary, stopProc, statFile } from "./machine.js";
|
|
30
|
+
import { menubarProc, notifierProc, relayProc, workerProc, wiring, logFileFor, dataSummary, stopProc, statFile, elapsed, windowsProcessInfo } from "./machine.js";
|
|
30
31
|
import { assess } from "./untrusted.js";
|
|
31
32
|
import { report as spendReport, recentRuns } from "./spend.js";
|
|
32
33
|
import { renderBrief, renderTeam } from "./brief.js";
|
|
@@ -38,7 +39,9 @@ import { removeHook, decideStop, writeIntent, clearIntent, readIntent, installHo
|
|
|
38
39
|
import { pending, render as renderPending } from "./pending.js";
|
|
39
40
|
import { signedRead } from "./reads.js";
|
|
40
41
|
import { start as qsStart, join as qsJoin, wireUp, teamNameFor, deviceLabel } from "./quickstart.js";
|
|
41
|
-
import {
|
|
42
|
+
import { folderRefusalHere, isTemporaryRefusal } from "./folder-guard.js";
|
|
43
|
+
import { installService, serviceInstalled, serviceLogFile, uninstallService } from "./service.js";
|
|
44
|
+
import { LEGACY_TASK_NAME, hostAlive, hostFiles, readHostState, recordedTaskName } from "./win-host.js";
|
|
42
45
|
import { ACTS, LIMITS } from "./protocol.js";
|
|
43
46
|
import { loadPersonas, personaByName, describePersona, initPersona, personaDir } from "./persona.js";
|
|
44
47
|
import { fingerprint, newKeypair, signAccount } from "./identity.js";
|
|
@@ -47,7 +50,8 @@ import { connectFailure, signInHint } from "./refusal.js";
|
|
|
47
50
|
import * as mcpWindows from "./mcp-windows.js";
|
|
48
51
|
import { updateBanner } from "./update-check.js";
|
|
49
52
|
import { inWokenRun } from "./worker-policy.js";
|
|
50
|
-
import { joinInstructions, managedLayout, relayServesInstaller } from "./managed.js";
|
|
53
|
+
import { childNodeFlags, joinInstructions, managedLayout, relayServesInstaller } from "./managed.js";
|
|
54
|
+
import { spawn } from "node:child_process";
|
|
51
55
|
const argv = process.argv.slice(2);
|
|
52
56
|
for (let i = argv.length - 1; i >= 1; i--) {
|
|
53
57
|
const m = /^(--[a-z][a-z0-9-]*)=(.*)$/i.exec(argv[i]);
|
|
@@ -822,24 +826,10 @@ function printReady(tools) {
|
|
|
822
826
|
console.error(D(` While you're away, a teammate you approve to wake this machine can hand work to it —`));
|
|
823
827
|
if (workerOn)
|
|
824
828
|
console.error(D(` isolated branch, no secrets in the worktree. Pause any time from the menu bar.`));
|
|
825
|
-
const oneTime =
|
|
826
|
-
|
|
827
|
-
if (/unavailable|not found/i.test(t.status))
|
|
828
|
-
continue;
|
|
829
|
-
if (/claude/i.test(t.tool))
|
|
830
|
-
oneTime.set("claude", `claude say yes once if it asks to trust this folder`);
|
|
831
|
-
if (/cursor/i.test(t.tool))
|
|
832
|
-
oneTime.set("cursor", `cursor enable the "pantheon" MCP server if it asks`);
|
|
833
|
-
if (/codex/i.test(t.tool))
|
|
834
|
-
oneTime.set("codex", `codex run /hooks inside codex once to approve the wake-up hook`);
|
|
835
|
-
if (/kiro/i.test(t.tool))
|
|
836
|
-
oneTime.set("kiro", `kiro run kiro-cli chat --agent pantheon (or kiro-cli agent set-default pantheon) to keep working while a teammate waits; stay signed in — a woken run never starts Kiro signed out, since Kiro would open a browser`);
|
|
837
|
-
if (/copilot/i.test(t.tool))
|
|
838
|
-
oneTime.set("copilot", `copilot say yes once when it asks to trust this folder; on a Copilot Business or Enterprise plan an org owner must have the "MCP servers in Copilot" policy on`);
|
|
839
|
-
}
|
|
840
|
-
if (oneTime.size) {
|
|
829
|
+
const oneTime = agentAsksOnce(tools);
|
|
830
|
+
if (oneTime.length) {
|
|
841
831
|
console.error(`\n${B("Your agents may each ask once")} ${D("(their own security, not ours — one yes each):")}`);
|
|
842
|
-
for (const l of oneTime
|
|
832
|
+
for (const l of oneTime)
|
|
843
833
|
console.error(D(` ${l}`));
|
|
844
834
|
}
|
|
845
835
|
}
|
|
@@ -909,12 +899,16 @@ ${B("control")}
|
|
|
909
899
|
pantheon persona how a woken worker should behave: list / init <name> / show <name>
|
|
910
900
|
pantheon notifier alert you when a task requires your decision
|
|
911
901
|
pantheon menubar the menu bar item: status at a glance, pause/resume, decisions
|
|
912
|
-
pantheon
|
|
902
|
+
pantheon tray Windows: show the tray icon again after you quit it (it also returns at your next logon)
|
|
903
|
+
pantheon sessions your devices & active sessions; revoke <id> to sign one out
|
|
913
904
|
pantheon logout sign this machine out · pantheon account delete remove your account
|
|
914
905
|
pantheon audit [--space X] the log: who did what, when (--json to export, --since <hours>)
|
|
915
906
|
pantheon profile show, create (init), or set your agent and attention profile
|
|
916
907
|
pantheon directory organization roles and decision authority
|
|
917
908
|
pantheon approvals decisions waiting on you; show or answer one
|
|
909
|
+
pantheon board your board in the browser, on any OS: what needs you, what your agents are on, and act on it
|
|
910
|
+
(--open start or reuse it and open it · --url print its address
|
|
911
|
+
· --watch the same lanes in this terminal, for SSH · --keep never stop when idle · --stop)
|
|
918
912
|
pantheon stop [relay|worker|notifier|menubar|all] stop it
|
|
919
913
|
pantheon hook install your agent keeps working when a peer is waiting on it
|
|
920
914
|
(auto-detects claude / codex / cursor / copilot / kiro on this machine)
|
|
@@ -930,6 +924,7 @@ ${B("control")}
|
|
|
930
924
|
(--questions prints them for an agent to ask you; answers go back as flags,
|
|
931
925
|
to setup --yes or straight to start / join)
|
|
932
926
|
pantheon upgrade pull the latest pantheon, rebuild, and re-wire your agents
|
|
927
|
+
pantheon upgrade <version> install exactly that release (e.g. to try one before your team does)
|
|
933
928
|
|
|
934
929
|
${D("more: pantheon help --all")}
|
|
935
930
|
`;
|
|
@@ -938,6 +933,7 @@ ${B("less common")}
|
|
|
938
933
|
pantheon relay run only the relay, in this terminal
|
|
939
934
|
pantheon service install [relay|worker|notifier|menubar] | restart | uninstall
|
|
940
935
|
run a long-lived component in the background, from login
|
|
936
|
+
(Windows: service start <worker|notifier> resumes one stopped with pantheon stop)
|
|
941
937
|
pantheon members the full list, with key fingerprints
|
|
942
938
|
pantheon pending what a live agent window has not yet seen
|
|
943
939
|
pantheon goal open "<title>" --with <who> open a goal to work in (goal list · goal close <id> "<summary>")
|
|
@@ -951,7 +947,32 @@ ${B("less common")}
|
|
|
951
947
|
pantheon limits the guard values in force
|
|
952
948
|
pantheon version the pantheon version this machine is running
|
|
953
949
|
`;
|
|
954
|
-
|
|
950
|
+
let workerAlerts = null;
|
|
951
|
+
let workerAlertsSaid = false;
|
|
952
|
+
function notifyFromWorker(n) {
|
|
953
|
+
if (process.platform === "linux") {
|
|
954
|
+
if (!workerAlerts || Date.now() - workerAlerts.at > 60_000)
|
|
955
|
+
workerAlerts = { at: Date.now(), ok: linuxDesktopReachable() };
|
|
956
|
+
if (!workerAlerts.ok) {
|
|
957
|
+
if (!workerAlertsSaid) {
|
|
958
|
+
workerAlertsSaid = true;
|
|
959
|
+
console.error(D("alerts: none — no graphical session on this machine; run notices wait in pantheon brief and pantheon board --watch"));
|
|
960
|
+
}
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
if (process.platform !== "win32") {
|
|
965
|
+
sendDesktopNotification(n);
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
const c = spawn(process.execPath, [...childNodeFlags(), process.argv[1], "notice-show"], {
|
|
969
|
+
env: { ...process.env, PANTHEON_NOTICE_B64: Buffer.from(JSON.stringify(n), "utf8").toString("base64") },
|
|
970
|
+
detached: true, stdio: "ignore", windowsHide: true,
|
|
971
|
+
});
|
|
972
|
+
c.on("error", () => { });
|
|
973
|
+
c.unref();
|
|
974
|
+
}
|
|
975
|
+
const QUIET = new Set(["mcp", "hook", "pending", "version", "tray-status", "handle-uri", "notice-show"]);
|
|
955
976
|
function usageFor(command, help) {
|
|
956
977
|
const plain = help.replace(/\x1b\[[0-9;]*m/g, "").split("\n");
|
|
957
978
|
const out = [];
|
|
@@ -990,6 +1011,7 @@ const COMMAND_FLAGS = {
|
|
|
990
1011
|
signup: "--email <address> --username <name> --relay <url> --label <device name>",
|
|
991
1012
|
audit: "--space <team> --since <hours> --json",
|
|
992
1013
|
setup: "--questions (and every answer as a flag, with --yes)",
|
|
1014
|
+
board: "--open --url --watch --keep --stop",
|
|
993
1015
|
};
|
|
994
1016
|
const HELP_OPAQUE = new Set(["mcp", "hook", "pending", "tray-status", "help", "version", "-v", "--help", "-h"]);
|
|
995
1017
|
function rememberWorkingProxy() {
|
|
@@ -1031,13 +1053,13 @@ function slipDistance(a, b) {
|
|
|
1031
1053
|
}
|
|
1032
1054
|
return d[a.length][b.length];
|
|
1033
1055
|
}
|
|
1034
|
-
const EXTRA_FLAGS = "--agent-cmd --approve-all --ask-before-sending --child --code --data --device --display --force --help --host --idle --keywords --local " +
|
|
1035
|
-
"--no-continue --no-guide --no-notify --path --peek --poll --portable --publish --queued --quiet --remind --scratch --session --surface --token --unattended " +
|
|
1056
|
+
const EXTRA_FLAGS = "--action-id --agent-cmd --approve-all --ask-before-sending --child --code --data --device --display --force --help --host --idle --keywords --local " +
|
|
1057
|
+
"--no-browser --no-continue --no-guide --no-notify --only-action --path --peek --poll --portable --publish --queued --quiet --remind --scratch --serve --session --surface --token --unattended " +
|
|
1036
1058
|
"--until --user --version --all --json " +
|
|
1037
1059
|
"--name --title --wake --commands --unsandboxed --alerts --decides --playbook --base --responsibilities --human-required --agent-can-decide --capabilities";
|
|
1038
1060
|
const KNOWN_FLAGS = new Set([...`${Object.values(COMMAND_FLAGS).join(" ")} ${EXTRA_FLAGS}`.matchAll(/--([a-z][a-z0-9-]*)/g)].map((m) => m[1]));
|
|
1039
1061
|
function refuseUnknownFlags() {
|
|
1040
|
-
if (["mcp", "hook", "tray-status", "_menubar"].includes(cmd))
|
|
1062
|
+
if (["mcp", "hook", "tray-status", "_menubar", "handle-uri"].includes(cmd))
|
|
1041
1063
|
return;
|
|
1042
1064
|
for (const a of argv.slice(1)) {
|
|
1043
1065
|
if (!/^--[a-z][a-z0-9-]*(?:=\S*)?$/i.test(a))
|
|
@@ -1522,14 +1544,28 @@ async function main() {
|
|
|
1522
1544
|
}
|
|
1523
1545
|
case "upgrade": {
|
|
1524
1546
|
const { upgrade } = await import("./upgrade.js");
|
|
1525
|
-
|
|
1526
|
-
|
|
1547
|
+
const asked = argv[1] && !argv[1].startsWith("-") ? argv[1] : undefined;
|
|
1548
|
+
if (asked) {
|
|
1549
|
+
const { isVersion } = await import("./installer.js");
|
|
1550
|
+
if (!isVersion(asked))
|
|
1551
|
+
die(`usage: pantheon upgrade [version] ("${asked}" is not a version, e.g. 0.32.2)`);
|
|
1552
|
+
}
|
|
1553
|
+
console.error(`${B("pantheon upgrade")}${asked ? ` ${asked}` : ""}\n`);
|
|
1554
|
+
const r = upgrade(undefined, asked ? { version: asked, exact: true } : {});
|
|
1527
1555
|
for (const s of r.steps)
|
|
1528
1556
|
console.error(` ${s.ok ? C("ok") : "\x1b[31mFAIL\x1b[0m"} ${s.name.padEnd(6)} ${s.detail}`);
|
|
1529
1557
|
if (!r.ok) {
|
|
1530
1558
|
console.error(`\n${B("upgrade stopped")} — nothing was changed.${r.needsPerson ? "" : " Fix the above and run `pantheon upgrade` again."}`);
|
|
1531
1559
|
process.exit(1);
|
|
1532
1560
|
}
|
|
1561
|
+
if (r.alreadyCurrent && r.steps.some((x) => /another pantheon upgrade is running/.test(x.detail))) {
|
|
1562
|
+
console.error(`\n${B("nothing installed")} — another upgrade is running on this machine (most likely the worker's own). Try again in a minute.`);
|
|
1563
|
+
process.exit(1);
|
|
1564
|
+
}
|
|
1565
|
+
if (asked && r.alreadyCurrent && r.steps.some((x) => /not on npm yet/.test(x.detail))) {
|
|
1566
|
+
console.error(`\n${B("nothing installed")} — npm has no ${asked}. Check the version (\`npm view @join-pantheon/cli versions\`).`);
|
|
1567
|
+
process.exit(1);
|
|
1568
|
+
}
|
|
1533
1569
|
if (r.alreadyCurrent) {
|
|
1534
1570
|
console.error(`\n${B("already up to date")} ${D(`(${r.from})`)}`);
|
|
1535
1571
|
if (process.platform === "darwin") {
|
|
@@ -1998,14 +2034,26 @@ async function main() {
|
|
|
1998
2034
|
console.log(` ${D("start it with: pantheon start")}`);
|
|
1999
2035
|
}
|
|
2000
2036
|
if (worker.running) {
|
|
2001
|
-
console.log(` worker ${C("running")} ${worker.managedBy === "launchd" ? `service ${worker.label}` : "started by hand"} pid ${worker.pid}`);
|
|
2037
|
+
console.log(` worker ${C("running")} ${worker.managedBy === "launchd" ? `service ${worker.label}` : worker.managedBy === "host" ? `background host (${worker.label})` : "started by hand"} pid ${worker.pid}`);
|
|
2002
2038
|
console.log(` ${D("a teammate approved on this machine (pantheon wake list) can start an agent run here")}`);
|
|
2003
2039
|
}
|
|
2004
2040
|
else {
|
|
2005
|
-
|
|
2041
|
+
const paused = worker.managedBy === "host" && existsSync(hostFiles().disabled("worker"));
|
|
2042
|
+
console.log(` worker ${D(paused ? "paused" : "not running")} ${D(paused ? "pantheon service start worker resumes it" : "nobody can start an agent run on this machine")}`);
|
|
2006
2043
|
}
|
|
2007
2044
|
const menubar = menubarProc();
|
|
2008
|
-
if (
|
|
2045
|
+
if (process.platform === "win32") {
|
|
2046
|
+
const st = readHostState();
|
|
2047
|
+
if (hostAlive(st)) {
|
|
2048
|
+
console.log(` host ${C("running")} task ${recordedTaskName() ?? LEGACY_TASK_NAME} pid ${st.pid} up ${elapsed(Date.now() - st.started)}`);
|
|
2049
|
+
const tray = st.children.tray;
|
|
2050
|
+
const quit = existsSync(hostFiles().disabled("tray"));
|
|
2051
|
+
console.log(` tray ${tray?.phase === "running" ? `${C("running")} pid ${tray.pid}` : D(quit ? "quit — back at your next logon, or now: pantheon tray" : tray?.gaveUp === "clm" ? "not shown — PowerShell is in Constrained Language Mode here (pantheon board instead)" : "not running")}`);
|
|
2052
|
+
}
|
|
2053
|
+
else
|
|
2054
|
+
console.log(` host ${D("not running")} ${D(serviceInstalled("worker") || serviceInstalled("notifier") ? "pantheon service start worker (or log out and back in)" : "pantheon service install worker — the worker, alerts and the tray in the background")}`);
|
|
2055
|
+
}
|
|
2056
|
+
else if (menubar.running) {
|
|
2009
2057
|
console.log(` menu bar ${C("running")} ${menubar.managedBy === "launchd" ? `service ${menubar.label}` : "started by hand"} pid ${menubar.pid}`);
|
|
2010
2058
|
}
|
|
2011
2059
|
else {
|
|
@@ -2013,7 +2061,7 @@ async function main() {
|
|
|
2013
2061
|
console.log(` menu bar ${D("not running")} ${D(why ?? "pantheon service install menubar — status and pause/resume in the menu bar")}`);
|
|
2014
2062
|
}
|
|
2015
2063
|
if (notifier.running) {
|
|
2016
|
-
console.log(` notifier ${C("running")} ${notifier.managedBy === "launchd" ? `service ${notifier.label}` : "started by hand"} pid ${notifier.pid}`);
|
|
2064
|
+
console.log(` notifier ${C("running")} ${notifier.managedBy === "launchd" || notifier.managedBy === "systemd" ? `service ${notifier.label}` : notifier.managedBy === "host" ? `background host (${notifier.label})` : "started by hand"} pid ${notifier.pid}`);
|
|
2017
2065
|
console.log(` ${D("native alerts are on for decisions that require you")}`);
|
|
2018
2066
|
}
|
|
2019
2067
|
else {
|
|
@@ -2093,7 +2141,7 @@ async function main() {
|
|
|
2093
2141
|
const proc = kind === "relay" ? relayProc(Number(flag("port", "8787"))) : kind === "worker" ? workerProc() : notifierProc();
|
|
2094
2142
|
const f = logFileFor(kind, proc);
|
|
2095
2143
|
if (!f)
|
|
2096
|
-
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" :
|
|
2144
|
+
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" : `The Pantheon host writes it to ${serviceLogFile(kind)} once it has started it (its own log: ${hostFiles().log})`}; if you started it by hand, its output went to that terminal.`);
|
|
2097
2145
|
console.error(D(`${f} (${statFile(f)})\n`));
|
|
2098
2146
|
const follow = argv.includes("-f") || argv.includes("--follow");
|
|
2099
2147
|
const { tailLines, followFile } = await import("./machine.js");
|
|
@@ -2501,6 +2549,13 @@ async function main() {
|
|
|
2501
2549
|
console.error(` perms:${l}`);
|
|
2502
2550
|
removed.push(l.trim().replace(/^removed /, ""));
|
|
2503
2551
|
}
|
|
2552
|
+
if (process.platform === "win32") {
|
|
2553
|
+
const { unregisterUriHandler, realRegIo } = await import("./uri-handler.js");
|
|
2554
|
+
const { borrowedProfile } = await import("./service.js");
|
|
2555
|
+
const u = unregisterUriHandler(realRegIo(() => borrowedProfile()));
|
|
2556
|
+
removed.push(...u.removed);
|
|
2557
|
+
left.push(...u.left);
|
|
2558
|
+
}
|
|
2504
2559
|
const { unlinkSync, existsSync: exists, rmSync, readdirSync } = await import("node:fs");
|
|
2505
2560
|
if (all && exists(CONFIG_PATH)) {
|
|
2506
2561
|
if (keepIdentity) {
|
|
@@ -3418,6 +3473,64 @@ async function main() {
|
|
|
3418
3473
|
console.log(`\n${D("Change it: pantheon policy grant|revoke self-upgrade|run-commands|unsandboxed-runs · personas: pantheon persona init · who may wake this machine: pantheon wake allow|deny <who>")}`);
|
|
3419
3474
|
return;
|
|
3420
3475
|
}
|
|
3476
|
+
case "notice-show": {
|
|
3477
|
+
const raw = process.env.PANTHEON_NOTICE_B64;
|
|
3478
|
+
if (!raw)
|
|
3479
|
+
return;
|
|
3480
|
+
try {
|
|
3481
|
+
sendDesktopNotification(JSON.parse(Buffer.from(raw, "base64").toString("utf8")));
|
|
3482
|
+
}
|
|
3483
|
+
catch { }
|
|
3484
|
+
return;
|
|
3485
|
+
}
|
|
3486
|
+
case "handle-uri": {
|
|
3487
|
+
const { handleUri, parseHandleUriArgs, logUri } = await import("./uri-handler.js");
|
|
3488
|
+
const { spawn, spawnSync } = await import("node:child_process");
|
|
3489
|
+
const { childNodeFlags } = await import("./managed.js");
|
|
3490
|
+
const { statSync } = await import("node:fs");
|
|
3491
|
+
const self = (args) => [...childNodeFlags(), process.argv[1], ...args];
|
|
3492
|
+
const hasBoard = /\n\s*pantheon board\b/.test(HELP_ALL);
|
|
3493
|
+
const parsed = parseHandleUriArgs(argv.slice(1), isAbsolute, (p) => resolve(p));
|
|
3494
|
+
const isDir = (p) => { try {
|
|
3495
|
+
return statSync(p).isDirectory();
|
|
3496
|
+
}
|
|
3497
|
+
catch {
|
|
3498
|
+
return false;
|
|
3499
|
+
} };
|
|
3500
|
+
if ("error" in parsed || (parsed.home && !isDir(parsed.home))) {
|
|
3501
|
+
const why = "error" in parsed ? parsed.error : `its --home (${parsed.home}) is not a folder on this machine`;
|
|
3502
|
+
console.log(`Pantheon did not act on this link: ${why}`);
|
|
3503
|
+
logUri(CONFIG_DIR, `refused (${why})`);
|
|
3504
|
+
process.exit(2);
|
|
3505
|
+
}
|
|
3506
|
+
const uriHome = parsed.home ?? CONFIG_DIR;
|
|
3507
|
+
const childEnv = parsed.home ? { ...process.env, PANTHEON_HOME: parsed.home } : process.env;
|
|
3508
|
+
const code = await handleUri(parsed.rest, {
|
|
3509
|
+
home: uriHome,
|
|
3510
|
+
wokenRun: inWokenRun(),
|
|
3511
|
+
runQueued: (actionId) => spawnSync(process.execPath, self(["explain", "--queued", "--only-action", "--action-id", actionId]), { stdio: "inherit", env: childEnv }).status ?? 1,
|
|
3512
|
+
openBoard: hasBoard ? () => new Promise((done) => {
|
|
3513
|
+
const c = spawn(process.execPath, self(["board", "--open"]), { detached: true, stdio: "ignore", windowsHide: true, env: childEnv });
|
|
3514
|
+
const t = setTimeout(() => { c.unref(); done(true); }, 4_000);
|
|
3515
|
+
c.on("error", () => { clearTimeout(t); done(false); });
|
|
3516
|
+
c.on("exit", (status) => { clearTimeout(t); done(status === 0); });
|
|
3517
|
+
}) : null,
|
|
3518
|
+
runBrief: () => spawnSync(process.execPath, self(["brief"]), { stdio: "inherit", env: childEnv }).status ?? 1,
|
|
3519
|
+
print: (l) => console.log(l),
|
|
3520
|
+
pause: async () => {
|
|
3521
|
+
if (!process.stdin.isTTY)
|
|
3522
|
+
return;
|
|
3523
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
3524
|
+
try {
|
|
3525
|
+
await questionOrNull(rl, D("\npress Enter to close "));
|
|
3526
|
+
}
|
|
3527
|
+
finally {
|
|
3528
|
+
rl.close();
|
|
3529
|
+
}
|
|
3530
|
+
},
|
|
3531
|
+
});
|
|
3532
|
+
process.exit(code);
|
|
3533
|
+
}
|
|
3421
3534
|
case "explain": {
|
|
3422
3535
|
const { readFileSync, rmSync } = await import("node:fs");
|
|
3423
3536
|
const CONFIG_DIR = dirname(CONFIG_PATH);
|
|
@@ -3428,18 +3541,48 @@ async function main() {
|
|
|
3428
3541
|
refuse("a woken run cannot act for the menu bar — a person has to click it");
|
|
3429
3542
|
const f = join(CONFIG_DIR, "bin", ".hud-action.json");
|
|
3430
3543
|
const { statSync } = await import("node:fs");
|
|
3544
|
+
const want = flag("action-id");
|
|
3545
|
+
if (want !== undefined && !/^[0-9a-f]{16,64}$/.test(want))
|
|
3546
|
+
refuse("that is not an action id");
|
|
3431
3547
|
let st = null;
|
|
3432
3548
|
let raw = null;
|
|
3433
3549
|
try {
|
|
3434
3550
|
st = statSync(f);
|
|
3435
3551
|
raw = readFileSync(f, "utf8");
|
|
3436
|
-
rmSync(f, { force: true });
|
|
3437
3552
|
}
|
|
3438
3553
|
catch {
|
|
3439
3554
|
st = null;
|
|
3440
3555
|
raw = null;
|
|
3441
3556
|
}
|
|
3557
|
+
if (raw !== null) {
|
|
3558
|
+
let nonce;
|
|
3559
|
+
try {
|
|
3560
|
+
nonce = JSON.parse(raw).nonce;
|
|
3561
|
+
}
|
|
3562
|
+
catch {
|
|
3563
|
+
nonce = undefined;
|
|
3564
|
+
}
|
|
3565
|
+
if (want !== undefined && nonce !== want)
|
|
3566
|
+
refuse("another click replaced that action before it ran — nothing was sent from here; click it again");
|
|
3567
|
+
if (want === undefined && typeof nonce === "string")
|
|
3568
|
+
refuse("another Pantheon window is sending an action right now — click it again in a moment");
|
|
3569
|
+
}
|
|
3570
|
+
if (raw !== null) {
|
|
3571
|
+
try {
|
|
3572
|
+
rmSync(f, { force: true });
|
|
3573
|
+
}
|
|
3574
|
+
catch { }
|
|
3575
|
+
}
|
|
3442
3576
|
if (st && raw !== null) {
|
|
3577
|
+
let parsed = null;
|
|
3578
|
+
try {
|
|
3579
|
+
parsed = JSON.parse(raw);
|
|
3580
|
+
}
|
|
3581
|
+
catch {
|
|
3582
|
+
parsed = null;
|
|
3583
|
+
}
|
|
3584
|
+
if (want !== undefined && parsed && typeof parsed.nonce === "string" && /^[0-9a-f]{16,64}$/.test(parsed.nonce))
|
|
3585
|
+
console.log(`action ${parsed.nonce}`);
|
|
3443
3586
|
const age = Date.now() - st.mtimeMs;
|
|
3444
3587
|
const { runInFlight } = await import("./worker.js");
|
|
3445
3588
|
const run = runInFlight(CONFIG_DIR);
|
|
@@ -3447,13 +3590,10 @@ async function main() {
|
|
|
3447
3590
|
refuse(`that menu-bar action was written ${Math.round(age / 1000)}s ago — click it again`);
|
|
3448
3591
|
if (run && st.mtimeMs >= run.at - 1_000)
|
|
3449
3592
|
refuse("that menu-bar action was written while a woken run was working on this machine, so it is not trusted — click it again once the run is done");
|
|
3450
|
-
|
|
3451
|
-
hud = JSON.parse(raw);
|
|
3452
|
-
}
|
|
3453
|
-
catch {
|
|
3454
|
-
hud = null;
|
|
3455
|
-
}
|
|
3593
|
+
hud = parsed;
|
|
3456
3594
|
}
|
|
3595
|
+
if (argv.includes("--only-action") && !hud)
|
|
3596
|
+
refuse("that action was already taken or is no longer there — click it again");
|
|
3457
3597
|
}
|
|
3458
3598
|
const cfg = (() => {
|
|
3459
3599
|
if (!hud?.space)
|
|
@@ -3477,7 +3617,7 @@ async function main() {
|
|
|
3477
3617
|
const { pickExplainAgent, decisionPrompt: dp, allDecisionsPrompt: adp, decisionTitle: dt, sessionName } = await import("./decision-ui.js");
|
|
3478
3618
|
const { loadProfile } = await import("./profile.js");
|
|
3479
3619
|
const { detectAvailable, launchArgs } = await import("./agents.js");
|
|
3480
|
-
let id = argv.find((a, i) => i > 0 && !a.startsWith("--"));
|
|
3620
|
+
let id = argv.find((a, i) => i > 0 && !a.startsWith("--") && argv[i - 1] !== "--action-id");
|
|
3481
3621
|
if (argv.includes("--queued")) {
|
|
3482
3622
|
const queued = join(CONFIG_DIR, "bin", ".explain-id");
|
|
3483
3623
|
try {
|
|
@@ -3978,7 +4118,7 @@ async function main() {
|
|
|
3978
4118
|
once: argv.includes("--once"),
|
|
3979
4119
|
...(argv.includes("--no-notify") ? {} : {
|
|
3980
4120
|
notify: (n) => { try {
|
|
3981
|
-
|
|
4121
|
+
notifyFromWorker(n);
|
|
3982
4122
|
}
|
|
3983
4123
|
catch { } },
|
|
3984
4124
|
}),
|
|
@@ -4009,7 +4149,7 @@ async function main() {
|
|
|
4009
4149
|
once: argv.includes("--once"),
|
|
4010
4150
|
...(argv.includes("--no-notify") ? {} : {
|
|
4011
4151
|
notify: (n) => { try {
|
|
4012
|
-
|
|
4152
|
+
notifyFromWorker(n);
|
|
4013
4153
|
}
|
|
4014
4154
|
catch { } },
|
|
4015
4155
|
}),
|
|
@@ -4024,6 +4164,58 @@ async function main() {
|
|
|
4024
4164
|
await worker.run();
|
|
4025
4165
|
return;
|
|
4026
4166
|
}
|
|
4167
|
+
case "board": {
|
|
4168
|
+
const b = await import("./board.js");
|
|
4169
|
+
if (inWokenRun())
|
|
4170
|
+
die("a woken run cannot open the board — a person has to run this here");
|
|
4171
|
+
if (argv.includes("--watch")) {
|
|
4172
|
+
await b.watchBoard();
|
|
4173
|
+
return;
|
|
4174
|
+
}
|
|
4175
|
+
if (argv.includes("--serve")) {
|
|
4176
|
+
await b.serveBoard({ keep: argv.includes("--keep") });
|
|
4177
|
+
process.exit(0);
|
|
4178
|
+
}
|
|
4179
|
+
if (argv.includes("--stop")) {
|
|
4180
|
+
const s = await b.runningBoard();
|
|
4181
|
+
if (!s) {
|
|
4182
|
+
console.error(D("no board is running"));
|
|
4183
|
+
return;
|
|
4184
|
+
}
|
|
4185
|
+
await b.boardPost(s, "/api/stop").catch(() => null);
|
|
4186
|
+
console.error(D("the board is stopped"));
|
|
4187
|
+
return;
|
|
4188
|
+
}
|
|
4189
|
+
const r = await b.ensureBoard({ keep: argv.includes("--keep") }).catch((e) => die(e.message));
|
|
4190
|
+
if (argv.includes("--url")) {
|
|
4191
|
+
console.log(r.url);
|
|
4192
|
+
console.error(D(" good once, within 30 seconds — pantheon board --url makes another"));
|
|
4193
|
+
if (!argv.includes("--open"))
|
|
4194
|
+
return;
|
|
4195
|
+
}
|
|
4196
|
+
const url = argv.includes("--url") ? await b.boardLinkFor(r.state).catch((e) => die(e.message)) : r.url;
|
|
4197
|
+
const opened = argv.includes("--no-browser") ? false : await b.openBrowser(url).catch(() => false);
|
|
4198
|
+
console.error(`${B("pantheon board")} ${D(r.reused ? "(already running)" : "(started)")}`);
|
|
4199
|
+
if (!opened)
|
|
4200
|
+
console.error(` open this in your browser within 30 seconds (it works once): ${C(url)}`);
|
|
4201
|
+
else
|
|
4202
|
+
console.error(D(" opened in your browser · pantheon board --url prints its address · --watch shows it in this terminal"));
|
|
4203
|
+
console.error(D(r.state.keep ? " it stays running" : " it stops itself after 30 minutes with no open tab"));
|
|
4204
|
+
return;
|
|
4205
|
+
}
|
|
4206
|
+
case "tray": {
|
|
4207
|
+
if (process.platform !== "win32")
|
|
4208
|
+
die(process.platform === "darwin" ? "the tray icon is Windows'; on macOS the menu bar is: pantheon menubar" : "the tray icon is Windows'; on Linux: pantheon board");
|
|
4209
|
+
const { showTray } = await import("./service.js");
|
|
4210
|
+
try {
|
|
4211
|
+
for (const l of showTray())
|
|
4212
|
+
console.error(l);
|
|
4213
|
+
}
|
|
4214
|
+
catch (e) {
|
|
4215
|
+
die(e.message);
|
|
4216
|
+
}
|
|
4217
|
+
return;
|
|
4218
|
+
}
|
|
4027
4219
|
case "tray-status": {
|
|
4028
4220
|
const { trayStatus } = await import("./tray.js");
|
|
4029
4221
|
console.log(JSON.stringify(await trayStatus()));
|
|
@@ -4100,6 +4292,19 @@ async function main() {
|
|
|
4100
4292
|
await new Promise((r) => child.on("close", () => r()));
|
|
4101
4293
|
return;
|
|
4102
4294
|
}
|
|
4295
|
+
case "host": {
|
|
4296
|
+
const { runHost, provenOrphan } = await import("./win-host.js");
|
|
4297
|
+
const { childNodeFlags, managedLauncher } = await import("./managed.js");
|
|
4298
|
+
const layout = managedLayout();
|
|
4299
|
+
runHost({
|
|
4300
|
+
node: process.execPath,
|
|
4301
|
+
flags: childNodeFlags(),
|
|
4302
|
+
managed: layout ? { pkgDir: layout.pkgDir, prevDir: layout.prevDir } : null,
|
|
4303
|
+
...(managedLauncher() ? { pantheonCommand: managedLauncher() } : {}),
|
|
4304
|
+
isOurs: (pid, r) => process.platform === "win32" && provenOrphan(r, windowsProcessInfo(pid)),
|
|
4305
|
+
});
|
|
4306
|
+
return;
|
|
4307
|
+
}
|
|
4103
4308
|
case "notifier": {
|
|
4104
4309
|
const nf = loadAll();
|
|
4105
4310
|
const teams = nf ? Object.values(nf.spaces ?? {}).map((m) => configFor(nf, m, null)) : [];
|
|
@@ -4110,6 +4315,15 @@ async function main() {
|
|
|
4110
4315
|
log: (line) => console.error(cfgs.length > 1 ? `[${c.space}] ${line}` : line),
|
|
4111
4316
|
}));
|
|
4112
4317
|
console.error(`${B("pantheon notifier")} ${D(`(${cfgs.length === 1 ? "" : cfgs.length + " teams · "}ctrl-c to stop)`)}`);
|
|
4318
|
+
if (process.platform === "win32") {
|
|
4319
|
+
try {
|
|
4320
|
+
const { ensureUriHandler } = await import("./notice-windows.js");
|
|
4321
|
+
console.error(D(` ${ensureUriHandler().line}`));
|
|
4322
|
+
}
|
|
4323
|
+
catch (e) {
|
|
4324
|
+
console.error(D(` pantheon: links not registered (${e.message.split("\n")[0]})`));
|
|
4325
|
+
}
|
|
4326
|
+
}
|
|
4113
4327
|
const stopAll = () => { for (const n of notifiers)
|
|
4114
4328
|
n.stop(); process.exit(0); };
|
|
4115
4329
|
process.on("SIGINT", stopAll);
|
|
@@ -4148,6 +4362,17 @@ async function main() {
|
|
|
4148
4362
|
console.error(l);
|
|
4149
4363
|
return;
|
|
4150
4364
|
}
|
|
4365
|
+
if (sub === "start" && process.platform === "win32" && (kind === "worker" || kind === "notifier")) {
|
|
4366
|
+
const { enableService } = await import("./service.js");
|
|
4367
|
+
try {
|
|
4368
|
+
for (const l of enableService(kind))
|
|
4369
|
+
console.error(l);
|
|
4370
|
+
}
|
|
4371
|
+
catch (e) {
|
|
4372
|
+
die(e.message);
|
|
4373
|
+
}
|
|
4374
|
+
return;
|
|
4375
|
+
}
|
|
4151
4376
|
if (kind === "relay") {
|
|
4152
4377
|
const data = resolveDataDir(flag("data")).dir;
|
|
4153
4378
|
const port = flag("port", "8787");
|
|
@@ -4184,12 +4409,30 @@ async function main() {
|
|
|
4184
4409
|
if (flag("remind"))
|
|
4185
4410
|
extraN.push("--remind", flag("remind"));
|
|
4186
4411
|
console.error(`${B("installing human notifications as a background service")}`);
|
|
4187
|
-
|
|
4188
|
-
const
|
|
4189
|
-
|
|
4412
|
+
if (process.platform === "linux") {
|
|
4413
|
+
const { linuxGraphicalSession } = await import("./host-env.js");
|
|
4414
|
+
if (!linuxGraphicalSession(process.env))
|
|
4415
|
+
console.error(D(" no graphical session in this shell (SSH or headless?) — alerts appear once you are logged in to the desktop; pantheon doctor says which way they are shown"));
|
|
4416
|
+
let lines = [];
|
|
4417
|
+
try {
|
|
4418
|
+
lines = installService(kind, homedir(), extraN);
|
|
4419
|
+
}
|
|
4420
|
+
catch (e) {
|
|
4421
|
+
die(e.message);
|
|
4422
|
+
}
|
|
4423
|
+
for (const l of lines)
|
|
4424
|
+
console.error(l);
|
|
4425
|
+
console.error(`\n${D("Alerts come from your desktop's own notifications. Done answers a decision from the alert; a click opens the board. No agent run or wake permission is involved.")}`);
|
|
4426
|
+
return;
|
|
4190
4427
|
}
|
|
4191
|
-
|
|
4192
|
-
|
|
4428
|
+
if (process.platform !== "win32") {
|
|
4429
|
+
try {
|
|
4430
|
+
const binary = ensureNativeNotifier();
|
|
4431
|
+
console.error(D(` actionable HUD ${binary}`));
|
|
4432
|
+
}
|
|
4433
|
+
catch (e) {
|
|
4434
|
+
die(`could not build the native notification helper: ${e.message}`);
|
|
4435
|
+
}
|
|
4193
4436
|
}
|
|
4194
4437
|
for (const l of installService(kind, homedir(), extraN))
|
|
4195
4438
|
console.error(l);
|
|
@@ -4266,15 +4509,12 @@ async function main() {
|
|
|
4266
4509
|
if (dirname(d) === d)
|
|
4267
4510
|
break;
|
|
4268
4511
|
}
|
|
4269
|
-
const
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
const here = real(process.cwd());
|
|
4276
|
-
if ((here === real(homedir()) || dirname(here) === here) && !argv.includes("--yes")) {
|
|
4277
|
-
die(`this is ${dirname(here) === here ? "the root of the disk" : "your home folder"}: binding it puts every folder under it in "${m.name}", and an agent opened anywhere there would speak for that team.\n Bind a project folder instead, or if you mean it: pantheon use ${m.name} --here --yes`);
|
|
4512
|
+
const refused = folderRefusalHere(process.cwd());
|
|
4513
|
+
if (isTemporaryRefusal(refused))
|
|
4514
|
+
die(`${refused.why.replace("<team>", m.name)}.`);
|
|
4515
|
+
if (refused && ["root", "home", "above-home"].includes(refused.kind) && !argv.includes("--yes")) {
|
|
4516
|
+
const what = refused.kind === "root" ? "the root of the disk" : refused.kind === "home" ? "your home folder" : "a folder that holds your home folder";
|
|
4517
|
+
die(`this is ${what}: binding it puts every folder under it in "${m.name}", and an agent opened anywhere there would speak for that team.\n Bind a project folder instead, or if you mean it: pantheon use ${m.name} --here --yes`);
|
|
4278
4518
|
}
|
|
4279
4519
|
if ((await import("./repo-find.js")).holdsRepo(process.cwd())) {
|
|
4280
4520
|
const n = await bindReposIn(process.cwd(), all, m, argv.includes("--yes"), `pantheon use ${m.name} --here --yes`);
|
package/dist/command-state.js
CHANGED
|
@@ -5,6 +5,7 @@ import { chooseAgent, claudeSandboxable, NOT_FOR_UNATTENDED_RUNS, parseRepoComma
|
|
|
5
5
|
import { detectClaudeFeatures } from "./claude-confine.js";
|
|
6
6
|
import { loadPersonas } from "./persona.js";
|
|
7
7
|
import { plistPath, existingUnitPath } from "./service.js";
|
|
8
|
+
import { readDefinition } from "./win-host.js";
|
|
8
9
|
import { loadPolicy, unsandboxedAllowed } from "./worker-policy.js";
|
|
9
10
|
export function workerServiceAllowsBash(plat = platform()) {
|
|
10
11
|
try {
|
|
@@ -17,7 +18,7 @@ export function workerServiceAllowsBash(plat = platform()) {
|
|
|
17
18
|
return args.includes("--allow-bash");
|
|
18
19
|
}
|
|
19
20
|
if (plat === "win32")
|
|
20
|
-
return null;
|
|
21
|
+
return readDefinition("worker")?.args.includes("--allow-bash") ?? null;
|
|
21
22
|
const u = existingUnitPath("worker");
|
|
22
23
|
if (!u)
|
|
23
24
|
return null;
|