@join-pantheon/cli 0.32.1 → 0.33.0-rc.2
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 +351 -101
- package/dist/client.js +16 -0
- 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 +174 -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 +64 -9
- package/dist/service.js +455 -29
- package/dist/setup-guide.js +22 -5
- package/dist/store.js +18 -2
- package/dist/uninstall.js +1 -1
- package/dist/upgrade.js +249 -4
- package/dist/uri-handler.js +335 -0
- package/dist/win-host.js +993 -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 +238 -19
- package/installer/install.sh +36 -1
- 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,18 +899,23 @@ ${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)
|
|
921
915
|
pantheon spend woken runs on this machine, and what pantheon cannot see
|
|
922
916
|
pantheon leave remove what pantheon put on this machine
|
|
923
|
-
pantheon uninstall leave, then remove Pantheon itself: its own Node, the pantheon command, the PATH lines
|
|
917
|
+
pantheon uninstall leave, then remove Pantheon itself: its own Node, the pantheon command, the PATH lines,
|
|
918
|
+
and all it keeps in ~/.pantheon (files of yours there stay)
|
|
924
919
|
pantheon panic emergency stop: ends the run in flight and the worker, and switches off wake rights in every team you are in
|
|
925
920
|
pantheon doctor [--share] check this machine end to end; --share prints it without names or paths, to paste to us
|
|
926
921
|
pantheon network [--proxy <url> | --direct | --clear] how this machine reaches the relay; save a proxy for every Pantheon process here
|
|
@@ -930,6 +925,7 @@ ${B("control")}
|
|
|
930
925
|
(--questions prints them for an agent to ask you; answers go back as flags,
|
|
931
926
|
to setup --yes or straight to start / join)
|
|
932
927
|
pantheon upgrade pull the latest pantheon, rebuild, and re-wire your agents
|
|
928
|
+
pantheon upgrade <version> install exactly that release (e.g. to try one before your team does)
|
|
933
929
|
|
|
934
930
|
${D("more: pantheon help --all")}
|
|
935
931
|
`;
|
|
@@ -938,6 +934,7 @@ ${B("less common")}
|
|
|
938
934
|
pantheon relay run only the relay, in this terminal
|
|
939
935
|
pantheon service install [relay|worker|notifier|menubar] | restart | uninstall
|
|
940
936
|
run a long-lived component in the background, from login
|
|
937
|
+
(Windows: service start <worker|notifier> resumes one stopped with pantheon stop)
|
|
941
938
|
pantheon members the full list, with key fingerprints
|
|
942
939
|
pantheon pending what a live agent window has not yet seen
|
|
943
940
|
pantheon goal open "<title>" --with <who> open a goal to work in (goal list · goal close <id> "<summary>")
|
|
@@ -951,7 +948,32 @@ ${B("less common")}
|
|
|
951
948
|
pantheon limits the guard values in force
|
|
952
949
|
pantheon version the pantheon version this machine is running
|
|
953
950
|
`;
|
|
954
|
-
|
|
951
|
+
let workerAlerts = null;
|
|
952
|
+
let workerAlertsSaid = false;
|
|
953
|
+
function notifyFromWorker(n) {
|
|
954
|
+
if (process.platform === "linux") {
|
|
955
|
+
if (!workerAlerts || Date.now() - workerAlerts.at > 60_000)
|
|
956
|
+
workerAlerts = { at: Date.now(), ok: linuxDesktopReachable() };
|
|
957
|
+
if (!workerAlerts.ok) {
|
|
958
|
+
if (!workerAlertsSaid) {
|
|
959
|
+
workerAlertsSaid = true;
|
|
960
|
+
console.error(D("alerts: none — no graphical session on this machine; run notices wait in pantheon brief and pantheon board --watch"));
|
|
961
|
+
}
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
if (process.platform !== "win32") {
|
|
966
|
+
sendDesktopNotification(n);
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
const c = spawn(process.execPath, [...childNodeFlags(), process.argv[1], "notice-show"], {
|
|
970
|
+
env: { ...process.env, PANTHEON_NOTICE_B64: Buffer.from(JSON.stringify(n), "utf8").toString("base64") },
|
|
971
|
+
detached: true, stdio: "ignore", windowsHide: true,
|
|
972
|
+
});
|
|
973
|
+
c.on("error", () => { });
|
|
974
|
+
c.unref();
|
|
975
|
+
}
|
|
976
|
+
const QUIET = new Set(["mcp", "hook", "pending", "version", "tray-status", "handle-uri", "notice-show"]);
|
|
955
977
|
function usageFor(command, help) {
|
|
956
978
|
const plain = help.replace(/\x1b\[[0-9;]*m/g, "").split("\n");
|
|
957
979
|
const out = [];
|
|
@@ -990,6 +1012,7 @@ const COMMAND_FLAGS = {
|
|
|
990
1012
|
signup: "--email <address> --username <name> --relay <url> --label <device name>",
|
|
991
1013
|
audit: "--space <team> --since <hours> --json",
|
|
992
1014
|
setup: "--questions (and every answer as a flag, with --yes)",
|
|
1015
|
+
board: "--open --url --watch --keep --stop",
|
|
993
1016
|
};
|
|
994
1017
|
const HELP_OPAQUE = new Set(["mcp", "hook", "pending", "tray-status", "help", "version", "-v", "--help", "-h"]);
|
|
995
1018
|
function rememberWorkingProxy() {
|
|
@@ -1016,7 +1039,7 @@ async function teamStanding(all, m) {
|
|
|
1016
1039
|
return { others: null, founder: false };
|
|
1017
1040
|
}
|
|
1018
1041
|
finally {
|
|
1019
|
-
c.
|
|
1042
|
+
c.dispose();
|
|
1020
1043
|
}
|
|
1021
1044
|
}
|
|
1022
1045
|
function slipDistance(a, b) {
|
|
@@ -1031,13 +1054,13 @@ function slipDistance(a, b) {
|
|
|
1031
1054
|
}
|
|
1032
1055
|
return d[a.length][b.length];
|
|
1033
1056
|
}
|
|
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 " +
|
|
1057
|
+
const EXTRA_FLAGS = "--action-id --agent-cmd --approve-all --ask-before-sending --child --code --data --device --display --force --help --host --idle --keywords --local " +
|
|
1058
|
+
"--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
1059
|
"--until --user --version --all --json " +
|
|
1037
1060
|
"--name --title --wake --commands --unsandboxed --alerts --decides --playbook --base --responsibilities --human-required --agent-can-decide --capabilities";
|
|
1038
1061
|
const KNOWN_FLAGS = new Set([...`${Object.values(COMMAND_FLAGS).join(" ")} ${EXTRA_FLAGS}`.matchAll(/--([a-z][a-z0-9-]*)/g)].map((m) => m[1]));
|
|
1039
1062
|
function refuseUnknownFlags() {
|
|
1040
|
-
if (["mcp", "hook", "tray-status", "_menubar"].includes(cmd))
|
|
1063
|
+
if (["mcp", "hook", "tray-status", "_menubar", "handle-uri"].includes(cmd))
|
|
1041
1064
|
return;
|
|
1042
1065
|
for (const a of argv.slice(1)) {
|
|
1043
1066
|
if (!/^--[a-z][a-z0-9-]*(?:=\S*)?$/i.test(a))
|
|
@@ -1058,7 +1081,7 @@ async function main() {
|
|
|
1058
1081
|
await installNetwork();
|
|
1059
1082
|
}
|
|
1060
1083
|
catch { }
|
|
1061
|
-
if (!QUIET.has(cmd)) {
|
|
1084
|
+
if (!QUIET.has(cmd) && cmd !== "uninstall") {
|
|
1062
1085
|
try {
|
|
1063
1086
|
console.error(D(updateBanner(VERSION)));
|
|
1064
1087
|
}
|
|
@@ -1522,14 +1545,28 @@ async function main() {
|
|
|
1522
1545
|
}
|
|
1523
1546
|
case "upgrade": {
|
|
1524
1547
|
const { upgrade } = await import("./upgrade.js");
|
|
1525
|
-
|
|
1526
|
-
|
|
1548
|
+
const asked = argv[1] && !argv[1].startsWith("-") ? argv[1] : undefined;
|
|
1549
|
+
if (asked) {
|
|
1550
|
+
const { isVersion } = await import("./installer.js");
|
|
1551
|
+
if (!isVersion(asked))
|
|
1552
|
+
die(`usage: pantheon upgrade [version] ("${asked}" is not a version, e.g. 0.32.2)`);
|
|
1553
|
+
}
|
|
1554
|
+
console.error(`${B("pantheon upgrade")}${asked ? ` ${asked}` : ""}\n`);
|
|
1555
|
+
const r = upgrade(undefined, asked ? { version: asked, exact: true } : {});
|
|
1527
1556
|
for (const s of r.steps)
|
|
1528
1557
|
console.error(` ${s.ok ? C("ok") : "\x1b[31mFAIL\x1b[0m"} ${s.name.padEnd(6)} ${s.detail}`);
|
|
1529
1558
|
if (!r.ok) {
|
|
1530
1559
|
console.error(`\n${B("upgrade stopped")} — nothing was changed.${r.needsPerson ? "" : " Fix the above and run `pantheon upgrade` again."}`);
|
|
1531
1560
|
process.exit(1);
|
|
1532
1561
|
}
|
|
1562
|
+
if (r.alreadyCurrent && r.steps.some((x) => /another pantheon upgrade is running/.test(x.detail))) {
|
|
1563
|
+
console.error(`\n${B("nothing installed")} — another upgrade is running on this machine (most likely the worker's own). Try again in a minute.`);
|
|
1564
|
+
process.exit(1);
|
|
1565
|
+
}
|
|
1566
|
+
if (asked && r.alreadyCurrent && r.steps.some((x) => /not on npm yet/.test(x.detail))) {
|
|
1567
|
+
console.error(`\n${B("nothing installed")} — npm has no ${asked}. Check the version (\`npm view @join-pantheon/cli versions\`).`);
|
|
1568
|
+
process.exit(1);
|
|
1569
|
+
}
|
|
1533
1570
|
if (r.alreadyCurrent) {
|
|
1534
1571
|
console.error(`\n${B("already up to date")} ${D(`(${r.from})`)}`);
|
|
1535
1572
|
if (process.platform === "darwin") {
|
|
@@ -1998,14 +2035,26 @@ async function main() {
|
|
|
1998
2035
|
console.log(` ${D("start it with: pantheon start")}`);
|
|
1999
2036
|
}
|
|
2000
2037
|
if (worker.running) {
|
|
2001
|
-
console.log(` worker ${C("running")} ${worker.managedBy === "launchd" ? `service ${worker.label}` : "started by hand"} pid ${worker.pid}`);
|
|
2038
|
+
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
2039
|
console.log(` ${D("a teammate approved on this machine (pantheon wake list) can start an agent run here")}`);
|
|
2003
2040
|
}
|
|
2004
2041
|
else {
|
|
2005
|
-
|
|
2042
|
+
const paused = worker.managedBy === "host" && existsSync(hostFiles().disabled("worker"));
|
|
2043
|
+
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
2044
|
}
|
|
2007
2045
|
const menubar = menubarProc();
|
|
2008
|
-
if (
|
|
2046
|
+
if (process.platform === "win32") {
|
|
2047
|
+
const st = readHostState();
|
|
2048
|
+
if (hostAlive(st)) {
|
|
2049
|
+
console.log(` host ${C("running")} task ${recordedTaskName() ?? LEGACY_TASK_NAME} pid ${st.pid} up ${elapsed(Date.now() - st.started)}`);
|
|
2050
|
+
const tray = st.children.tray;
|
|
2051
|
+
const quit = existsSync(hostFiles().disabled("tray"));
|
|
2052
|
+
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")}`);
|
|
2053
|
+
}
|
|
2054
|
+
else
|
|
2055
|
+
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")}`);
|
|
2056
|
+
}
|
|
2057
|
+
else if (menubar.running) {
|
|
2009
2058
|
console.log(` menu bar ${C("running")} ${menubar.managedBy === "launchd" ? `service ${menubar.label}` : "started by hand"} pid ${menubar.pid}`);
|
|
2010
2059
|
}
|
|
2011
2060
|
else {
|
|
@@ -2013,7 +2062,7 @@ async function main() {
|
|
|
2013
2062
|
console.log(` menu bar ${D("not running")} ${D(why ?? "pantheon service install menubar — status and pause/resume in the menu bar")}`);
|
|
2014
2063
|
}
|
|
2015
2064
|
if (notifier.running) {
|
|
2016
|
-
console.log(` notifier ${C("running")} ${notifier.managedBy === "launchd" ? `service ${notifier.label}` : "started by hand"} pid ${notifier.pid}`);
|
|
2065
|
+
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
2066
|
console.log(` ${D("native alerts are on for decisions that require you")}`);
|
|
2018
2067
|
}
|
|
2019
2068
|
else {
|
|
@@ -2093,7 +2142,7 @@ async function main() {
|
|
|
2093
2142
|
const proc = kind === "relay" ? relayProc(Number(flag("port", "8787"))) : kind === "worker" ? workerProc() : notifierProc();
|
|
2094
2143
|
const f = logFileFor(kind, proc);
|
|
2095
2144
|
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" :
|
|
2145
|
+
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
2146
|
console.error(D(`${f} (${statFile(f)})\n`));
|
|
2098
2147
|
const follow = argv.includes("-f") || argv.includes("--follow");
|
|
2099
2148
|
const { tailLines, followFile } = await import("./machine.js");
|
|
@@ -2459,7 +2508,8 @@ async function main() {
|
|
|
2459
2508
|
if (uninstalling && !argv.includes("--yes")) {
|
|
2460
2509
|
const teams = plan.teams.length && !keepIdentity ? `leaves ${plan.teams.map((t) => `"${t.name}"`).join(", ")} (this machine's key goes), ` : "";
|
|
2461
2510
|
const what = `this removes Pantheon from this machine: it ${teams}uninstalls its services, hooks and agent entries` +
|
|
2462
|
-
(install?.present ? `, and deletes Pantheon's own Node in ${install.runtime}, the pantheon command and the PATH lines the installer added
|
|
2511
|
+
(install?.present ? `, and deletes Pantheon's own Node in ${install.runtime}, the pantheon command and the PATH lines the installer added` : "") +
|
|
2512
|
+
`. Everything Pantheon keeps in ${CONFIG_DIR} goes${keepIdentity ? " (but your identity, --keep-identity)" : ""}; files of your own there stay.`;
|
|
2463
2513
|
if (!process.stdin.isTTY)
|
|
2464
2514
|
die(`${what}\n If you mean it: pantheon uninstall --yes`);
|
|
2465
2515
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
@@ -2501,53 +2551,64 @@ async function main() {
|
|
|
2501
2551
|
console.error(` perms:${l}`);
|
|
2502
2552
|
removed.push(l.trim().replace(/^removed /, ""));
|
|
2503
2553
|
}
|
|
2554
|
+
if (process.platform === "win32") {
|
|
2555
|
+
const { unregisterUriHandler, realRegIo } = await import("./uri-handler.js");
|
|
2556
|
+
const { borrowedProfile } = await import("./service.js");
|
|
2557
|
+
const u = unregisterUriHandler(realRegIo(() => borrowedProfile()));
|
|
2558
|
+
removed.push(...u.removed);
|
|
2559
|
+
left.push(...u.left);
|
|
2560
|
+
}
|
|
2504
2561
|
const { unlinkSync, existsSync: exists, rmSync, readdirSync } = await import("node:fs");
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
}
|
|
2509
|
-
|
|
2562
|
+
const spare = [];
|
|
2563
|
+
const hasConfig = !!all && exists(CONFIG_PATH);
|
|
2564
|
+
if (hasConfig && keepIdentity) {
|
|
2565
|
+
left.push(`your identity and ${plan.teams.length} team membership${plan.teams.length === 1 ? "" : "s"}, ${CONFIG_PATH} (--keep-identity)`);
|
|
2566
|
+
}
|
|
2567
|
+
else if (!keepIdentity && (hasConfig || uninstalling)) {
|
|
2568
|
+
if (hasConfig) {
|
|
2510
2569
|
unlinkSync(CONFIG_PATH);
|
|
2511
2570
|
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`);
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2571
|
+
}
|
|
2572
|
+
const runHomes = join(CONFIG_PATH, "..", "run-homes");
|
|
2573
|
+
const { restorePendingQuarantines, pendingQuarantines } = await import("./agents.js");
|
|
2574
|
+
const put = restorePendingQuarantines(runHomes);
|
|
2575
|
+
if (put.length)
|
|
2576
|
+
removed.push(`nothing of your repos' — what interrupted runs had held aside: ${put.join("; ")}`);
|
|
2577
|
+
const still = pendingQuarantines(runHomes);
|
|
2578
|
+
const keptAside = (dir) => { try {
|
|
2579
|
+
return readdirSync(join(dir, "quarantine-kept")).length > 0;
|
|
2580
|
+
}
|
|
2581
|
+
catch {
|
|
2582
|
+
return false;
|
|
2583
|
+
} };
|
|
2584
|
+
let homes = [];
|
|
2585
|
+
try {
|
|
2586
|
+
homes = readdirSync(runHomes);
|
|
2587
|
+
}
|
|
2588
|
+
catch { }
|
|
2589
|
+
const keep = new Set([...still.map((x) => x.runHome), ...homes.map((h) => join(runHomes, h)).filter(keptAside)]);
|
|
2590
|
+
for (const h of homes) {
|
|
2591
|
+
const dir = join(runHomes, h);
|
|
2592
|
+
if (keep.has(dir))
|
|
2593
|
+
continue;
|
|
2525
2594
|
try {
|
|
2526
|
-
|
|
2595
|
+
rmSync(dir, { recursive: true, force: true });
|
|
2527
2596
|
}
|
|
2528
2597
|
catch { }
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
continue;
|
|
2534
|
-
try {
|
|
2535
|
-
rmSync(dir, { recursive: true, force: true });
|
|
2536
|
-
}
|
|
2537
|
-
catch { }
|
|
2538
|
-
}
|
|
2539
|
-
if (!keep.size) {
|
|
2540
|
-
try {
|
|
2541
|
-
rmSync(runHomes, { recursive: true, force: true });
|
|
2542
|
-
}
|
|
2543
|
-
catch { }
|
|
2598
|
+
}
|
|
2599
|
+
if (!keep.size) {
|
|
2600
|
+
try {
|
|
2601
|
+
rmSync(runHomes, { recursive: true, force: true });
|
|
2544
2602
|
}
|
|
2545
|
-
|
|
2546
|
-
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`);
|
|
2547
|
-
for (const dir of keep)
|
|
2548
|
-
if (keptAside(dir) && !still.some((x) => x.runHome === dir))
|
|
2549
|
-
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`);
|
|
2603
|
+
catch { }
|
|
2550
2604
|
}
|
|
2605
|
+
else
|
|
2606
|
+
spare.push(runHomes);
|
|
2607
|
+
for (const x of still)
|
|
2608
|
+
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`);
|
|
2609
|
+
for (const dir of keep)
|
|
2610
|
+
if (keptAside(dir) && !still.some((x) => x.runHome === dir))
|
|
2611
|
+
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`);
|
|
2551
2612
|
}
|
|
2552
2613
|
{
|
|
2553
2614
|
const { removePantheonWorktrees, removeHomeLeftovers } = await import("./machine.js");
|
|
@@ -2571,6 +2632,10 @@ async function main() {
|
|
|
2571
2632
|
}
|
|
2572
2633
|
if (!runningManaged)
|
|
2573
2634
|
left.push(`this pantheon itself (${process.argv[1] ?? "the one you ran"}), which npm installed — remove it with: ${NPM_UNINSTALL}`);
|
|
2635
|
+
const { sweepPantheonHome } = await import("./machine.js");
|
|
2636
|
+
const swept = sweepPantheonHome(CONFIG_DIR, { keepIdentity, spare });
|
|
2637
|
+
removed.push(...swept.removed);
|
|
2638
|
+
left.push(...swept.left);
|
|
2574
2639
|
}
|
|
2575
2640
|
console.error(`\n${B("Removed")}`);
|
|
2576
2641
|
for (const r of removed)
|
|
@@ -3418,6 +3483,64 @@ async function main() {
|
|
|
3418
3483
|
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
3484
|
return;
|
|
3420
3485
|
}
|
|
3486
|
+
case "notice-show": {
|
|
3487
|
+
const raw = process.env.PANTHEON_NOTICE_B64;
|
|
3488
|
+
if (!raw)
|
|
3489
|
+
return;
|
|
3490
|
+
try {
|
|
3491
|
+
sendDesktopNotification(JSON.parse(Buffer.from(raw, "base64").toString("utf8")));
|
|
3492
|
+
}
|
|
3493
|
+
catch { }
|
|
3494
|
+
return;
|
|
3495
|
+
}
|
|
3496
|
+
case "handle-uri": {
|
|
3497
|
+
const { handleUri, parseHandleUriArgs, logUri } = await import("./uri-handler.js");
|
|
3498
|
+
const { spawn, spawnSync } = await import("node:child_process");
|
|
3499
|
+
const { childNodeFlags } = await import("./managed.js");
|
|
3500
|
+
const { statSync } = await import("node:fs");
|
|
3501
|
+
const self = (args) => [...childNodeFlags(), process.argv[1], ...args];
|
|
3502
|
+
const hasBoard = /\n\s*pantheon board\b/.test(HELP_ALL);
|
|
3503
|
+
const parsed = parseHandleUriArgs(argv.slice(1), isAbsolute, (p) => resolve(p));
|
|
3504
|
+
const isDir = (p) => { try {
|
|
3505
|
+
return statSync(p).isDirectory();
|
|
3506
|
+
}
|
|
3507
|
+
catch {
|
|
3508
|
+
return false;
|
|
3509
|
+
} };
|
|
3510
|
+
if ("error" in parsed || (parsed.home && !isDir(parsed.home))) {
|
|
3511
|
+
const why = "error" in parsed ? parsed.error : `its --home (${parsed.home}) is not a folder on this machine`;
|
|
3512
|
+
console.log(`Pantheon did not act on this link: ${why}`);
|
|
3513
|
+
logUri(CONFIG_DIR, `refused (${why})`);
|
|
3514
|
+
process.exit(2);
|
|
3515
|
+
}
|
|
3516
|
+
const uriHome = parsed.home ?? CONFIG_DIR;
|
|
3517
|
+
const childEnv = parsed.home ? { ...process.env, PANTHEON_HOME: parsed.home } : process.env;
|
|
3518
|
+
const code = await handleUri(parsed.rest, {
|
|
3519
|
+
home: uriHome,
|
|
3520
|
+
wokenRun: inWokenRun(),
|
|
3521
|
+
runQueued: (actionId) => spawnSync(process.execPath, self(["explain", "--queued", "--only-action", "--action-id", actionId]), { stdio: "inherit", env: childEnv }).status ?? 1,
|
|
3522
|
+
openBoard: hasBoard ? () => new Promise((done) => {
|
|
3523
|
+
const c = spawn(process.execPath, self(["board", "--open"]), { detached: true, stdio: "ignore", windowsHide: true, env: childEnv });
|
|
3524
|
+
const t = setTimeout(() => { c.unref(); done(true); }, 4_000);
|
|
3525
|
+
c.on("error", () => { clearTimeout(t); done(false); });
|
|
3526
|
+
c.on("exit", (status) => { clearTimeout(t); done(status === 0); });
|
|
3527
|
+
}) : null,
|
|
3528
|
+
runBrief: () => spawnSync(process.execPath, self(["brief"]), { stdio: "inherit", env: childEnv }).status ?? 1,
|
|
3529
|
+
print: (l) => console.log(l),
|
|
3530
|
+
pause: async () => {
|
|
3531
|
+
if (!process.stdin.isTTY)
|
|
3532
|
+
return;
|
|
3533
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
3534
|
+
try {
|
|
3535
|
+
await questionOrNull(rl, D("\npress Enter to close "));
|
|
3536
|
+
}
|
|
3537
|
+
finally {
|
|
3538
|
+
rl.close();
|
|
3539
|
+
}
|
|
3540
|
+
},
|
|
3541
|
+
});
|
|
3542
|
+
process.exit(code);
|
|
3543
|
+
}
|
|
3421
3544
|
case "explain": {
|
|
3422
3545
|
const { readFileSync, rmSync } = await import("node:fs");
|
|
3423
3546
|
const CONFIG_DIR = dirname(CONFIG_PATH);
|
|
@@ -3428,18 +3551,48 @@ async function main() {
|
|
|
3428
3551
|
refuse("a woken run cannot act for the menu bar — a person has to click it");
|
|
3429
3552
|
const f = join(CONFIG_DIR, "bin", ".hud-action.json");
|
|
3430
3553
|
const { statSync } = await import("node:fs");
|
|
3554
|
+
const want = flag("action-id");
|
|
3555
|
+
if (want !== undefined && !/^[0-9a-f]{16,64}$/.test(want))
|
|
3556
|
+
refuse("that is not an action id");
|
|
3431
3557
|
let st = null;
|
|
3432
3558
|
let raw = null;
|
|
3433
3559
|
try {
|
|
3434
3560
|
st = statSync(f);
|
|
3435
3561
|
raw = readFileSync(f, "utf8");
|
|
3436
|
-
rmSync(f, { force: true });
|
|
3437
3562
|
}
|
|
3438
3563
|
catch {
|
|
3439
3564
|
st = null;
|
|
3440
3565
|
raw = null;
|
|
3441
3566
|
}
|
|
3567
|
+
if (raw !== null) {
|
|
3568
|
+
let nonce;
|
|
3569
|
+
try {
|
|
3570
|
+
nonce = JSON.parse(raw).nonce;
|
|
3571
|
+
}
|
|
3572
|
+
catch {
|
|
3573
|
+
nonce = undefined;
|
|
3574
|
+
}
|
|
3575
|
+
if (want !== undefined && nonce !== want)
|
|
3576
|
+
refuse("another click replaced that action before it ran — nothing was sent from here; click it again");
|
|
3577
|
+
if (want === undefined && typeof nonce === "string")
|
|
3578
|
+
refuse("another Pantheon window is sending an action right now — click it again in a moment");
|
|
3579
|
+
}
|
|
3580
|
+
if (raw !== null) {
|
|
3581
|
+
try {
|
|
3582
|
+
rmSync(f, { force: true });
|
|
3583
|
+
}
|
|
3584
|
+
catch { }
|
|
3585
|
+
}
|
|
3442
3586
|
if (st && raw !== null) {
|
|
3587
|
+
let parsed = null;
|
|
3588
|
+
try {
|
|
3589
|
+
parsed = JSON.parse(raw);
|
|
3590
|
+
}
|
|
3591
|
+
catch {
|
|
3592
|
+
parsed = null;
|
|
3593
|
+
}
|
|
3594
|
+
if (want !== undefined && parsed && typeof parsed.nonce === "string" && /^[0-9a-f]{16,64}$/.test(parsed.nonce))
|
|
3595
|
+
console.log(`action ${parsed.nonce}`);
|
|
3443
3596
|
const age = Date.now() - st.mtimeMs;
|
|
3444
3597
|
const { runInFlight } = await import("./worker.js");
|
|
3445
3598
|
const run = runInFlight(CONFIG_DIR);
|
|
@@ -3447,13 +3600,10 @@ async function main() {
|
|
|
3447
3600
|
refuse(`that menu-bar action was written ${Math.round(age / 1000)}s ago — click it again`);
|
|
3448
3601
|
if (run && st.mtimeMs >= run.at - 1_000)
|
|
3449
3602
|
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
|
-
}
|
|
3603
|
+
hud = parsed;
|
|
3456
3604
|
}
|
|
3605
|
+
if (argv.includes("--only-action") && !hud)
|
|
3606
|
+
refuse("that action was already taken or is no longer there — click it again");
|
|
3457
3607
|
}
|
|
3458
3608
|
const cfg = (() => {
|
|
3459
3609
|
if (!hud?.space)
|
|
@@ -3477,7 +3627,7 @@ async function main() {
|
|
|
3477
3627
|
const { pickExplainAgent, decisionPrompt: dp, allDecisionsPrompt: adp, decisionTitle: dt, sessionName } = await import("./decision-ui.js");
|
|
3478
3628
|
const { loadProfile } = await import("./profile.js");
|
|
3479
3629
|
const { detectAvailable, launchArgs } = await import("./agents.js");
|
|
3480
|
-
let id = argv.find((a, i) => i > 0 && !a.startsWith("--"));
|
|
3630
|
+
let id = argv.find((a, i) => i > 0 && !a.startsWith("--") && argv[i - 1] !== "--action-id");
|
|
3481
3631
|
if (argv.includes("--queued")) {
|
|
3482
3632
|
const queued = join(CONFIG_DIR, "bin", ".explain-id");
|
|
3483
3633
|
try {
|
|
@@ -3978,7 +4128,7 @@ async function main() {
|
|
|
3978
4128
|
once: argv.includes("--once"),
|
|
3979
4129
|
...(argv.includes("--no-notify") ? {} : {
|
|
3980
4130
|
notify: (n) => { try {
|
|
3981
|
-
|
|
4131
|
+
notifyFromWorker(n);
|
|
3982
4132
|
}
|
|
3983
4133
|
catch { } },
|
|
3984
4134
|
}),
|
|
@@ -4009,7 +4159,7 @@ async function main() {
|
|
|
4009
4159
|
once: argv.includes("--once"),
|
|
4010
4160
|
...(argv.includes("--no-notify") ? {} : {
|
|
4011
4161
|
notify: (n) => { try {
|
|
4012
|
-
|
|
4162
|
+
notifyFromWorker(n);
|
|
4013
4163
|
}
|
|
4014
4164
|
catch { } },
|
|
4015
4165
|
}),
|
|
@@ -4024,6 +4174,58 @@ async function main() {
|
|
|
4024
4174
|
await worker.run();
|
|
4025
4175
|
return;
|
|
4026
4176
|
}
|
|
4177
|
+
case "board": {
|
|
4178
|
+
const b = await import("./board.js");
|
|
4179
|
+
if (inWokenRun())
|
|
4180
|
+
die("a woken run cannot open the board — a person has to run this here");
|
|
4181
|
+
if (argv.includes("--watch")) {
|
|
4182
|
+
await b.watchBoard();
|
|
4183
|
+
return;
|
|
4184
|
+
}
|
|
4185
|
+
if (argv.includes("--serve")) {
|
|
4186
|
+
await b.serveBoard({ keep: argv.includes("--keep") });
|
|
4187
|
+
process.exit(0);
|
|
4188
|
+
}
|
|
4189
|
+
if (argv.includes("--stop")) {
|
|
4190
|
+
const s = await b.runningBoard();
|
|
4191
|
+
if (!s) {
|
|
4192
|
+
console.error(D("no board is running"));
|
|
4193
|
+
return;
|
|
4194
|
+
}
|
|
4195
|
+
await b.boardPost(s, "/api/stop").catch(() => null);
|
|
4196
|
+
console.error(D("the board is stopped"));
|
|
4197
|
+
return;
|
|
4198
|
+
}
|
|
4199
|
+
const r = await b.ensureBoard({ keep: argv.includes("--keep") }).catch((e) => die(e.message));
|
|
4200
|
+
if (argv.includes("--url")) {
|
|
4201
|
+
console.log(r.url);
|
|
4202
|
+
console.error(D(" good once, within 30 seconds — pantheon board --url makes another"));
|
|
4203
|
+
if (!argv.includes("--open"))
|
|
4204
|
+
return;
|
|
4205
|
+
}
|
|
4206
|
+
const url = argv.includes("--url") ? await b.boardLinkFor(r.state).catch((e) => die(e.message)) : r.url;
|
|
4207
|
+
const opened = argv.includes("--no-browser") ? false : await b.openBrowser(url).catch(() => false);
|
|
4208
|
+
console.error(`${B("pantheon board")} ${D(r.reused ? "(already running)" : "(started)")}`);
|
|
4209
|
+
if (!opened)
|
|
4210
|
+
console.error(` open this in your browser within 30 seconds (it works once): ${C(url)}`);
|
|
4211
|
+
else
|
|
4212
|
+
console.error(D(" opened in your browser · pantheon board --url prints its address · --watch shows it in this terminal"));
|
|
4213
|
+
console.error(D(r.state.keep ? " it stays running" : " it stops itself after 30 minutes with no open tab"));
|
|
4214
|
+
return;
|
|
4215
|
+
}
|
|
4216
|
+
case "tray": {
|
|
4217
|
+
if (process.platform !== "win32")
|
|
4218
|
+
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");
|
|
4219
|
+
const { showTray } = await import("./service.js");
|
|
4220
|
+
try {
|
|
4221
|
+
for (const l of showTray())
|
|
4222
|
+
console.error(l);
|
|
4223
|
+
}
|
|
4224
|
+
catch (e) {
|
|
4225
|
+
die(e.message);
|
|
4226
|
+
}
|
|
4227
|
+
return;
|
|
4228
|
+
}
|
|
4027
4229
|
case "tray-status": {
|
|
4028
4230
|
const { trayStatus } = await import("./tray.js");
|
|
4029
4231
|
console.log(JSON.stringify(await trayStatus()));
|
|
@@ -4100,6 +4302,19 @@ async function main() {
|
|
|
4100
4302
|
await new Promise((r) => child.on("close", () => r()));
|
|
4101
4303
|
return;
|
|
4102
4304
|
}
|
|
4305
|
+
case "host": {
|
|
4306
|
+
const { runHost, provenOrphan } = await import("./win-host.js");
|
|
4307
|
+
const { childNodeFlags, managedLauncher } = await import("./managed.js");
|
|
4308
|
+
const layout = managedLayout();
|
|
4309
|
+
runHost({
|
|
4310
|
+
node: process.execPath,
|
|
4311
|
+
flags: childNodeFlags(),
|
|
4312
|
+
managed: layout ? { pkgDir: layout.pkgDir, prevDir: layout.prevDir } : null,
|
|
4313
|
+
...(managedLauncher() ? { pantheonCommand: managedLauncher() } : {}),
|
|
4314
|
+
isOurs: (pid, r) => process.platform === "win32" && provenOrphan(r, windowsProcessInfo(pid)),
|
|
4315
|
+
});
|
|
4316
|
+
return;
|
|
4317
|
+
}
|
|
4103
4318
|
case "notifier": {
|
|
4104
4319
|
const nf = loadAll();
|
|
4105
4320
|
const teams = nf ? Object.values(nf.spaces ?? {}).map((m) => configFor(nf, m, null)) : [];
|
|
@@ -4110,6 +4325,15 @@ async function main() {
|
|
|
4110
4325
|
log: (line) => console.error(cfgs.length > 1 ? `[${c.space}] ${line}` : line),
|
|
4111
4326
|
}));
|
|
4112
4327
|
console.error(`${B("pantheon notifier")} ${D(`(${cfgs.length === 1 ? "" : cfgs.length + " teams · "}ctrl-c to stop)`)}`);
|
|
4328
|
+
if (process.platform === "win32") {
|
|
4329
|
+
try {
|
|
4330
|
+
const { ensureUriHandler } = await import("./notice-windows.js");
|
|
4331
|
+
console.error(D(` ${ensureUriHandler().line}`));
|
|
4332
|
+
}
|
|
4333
|
+
catch (e) {
|
|
4334
|
+
console.error(D(` pantheon: links not registered (${e.message.split("\n")[0]})`));
|
|
4335
|
+
}
|
|
4336
|
+
}
|
|
4113
4337
|
const stopAll = () => { for (const n of notifiers)
|
|
4114
4338
|
n.stop(); process.exit(0); };
|
|
4115
4339
|
process.on("SIGINT", stopAll);
|
|
@@ -4148,6 +4372,17 @@ async function main() {
|
|
|
4148
4372
|
console.error(l);
|
|
4149
4373
|
return;
|
|
4150
4374
|
}
|
|
4375
|
+
if (sub === "start" && process.platform === "win32" && (kind === "worker" || kind === "notifier")) {
|
|
4376
|
+
const { enableService } = await import("./service.js");
|
|
4377
|
+
try {
|
|
4378
|
+
for (const l of enableService(kind))
|
|
4379
|
+
console.error(l);
|
|
4380
|
+
}
|
|
4381
|
+
catch (e) {
|
|
4382
|
+
die(e.message);
|
|
4383
|
+
}
|
|
4384
|
+
return;
|
|
4385
|
+
}
|
|
4151
4386
|
if (kind === "relay") {
|
|
4152
4387
|
const data = resolveDataDir(flag("data")).dir;
|
|
4153
4388
|
const port = flag("port", "8787");
|
|
@@ -4184,12 +4419,30 @@ async function main() {
|
|
|
4184
4419
|
if (flag("remind"))
|
|
4185
4420
|
extraN.push("--remind", flag("remind"));
|
|
4186
4421
|
console.error(`${B("installing human notifications as a background service")}`);
|
|
4187
|
-
|
|
4188
|
-
const
|
|
4189
|
-
|
|
4422
|
+
if (process.platform === "linux") {
|
|
4423
|
+
const { linuxGraphicalSession } = await import("./host-env.js");
|
|
4424
|
+
if (!linuxGraphicalSession(process.env))
|
|
4425
|
+
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"));
|
|
4426
|
+
let lines = [];
|
|
4427
|
+
try {
|
|
4428
|
+
lines = installService(kind, homedir(), extraN);
|
|
4429
|
+
}
|
|
4430
|
+
catch (e) {
|
|
4431
|
+
die(e.message);
|
|
4432
|
+
}
|
|
4433
|
+
for (const l of lines)
|
|
4434
|
+
console.error(l);
|
|
4435
|
+
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.")}`);
|
|
4436
|
+
return;
|
|
4190
4437
|
}
|
|
4191
|
-
|
|
4192
|
-
|
|
4438
|
+
if (process.platform !== "win32") {
|
|
4439
|
+
try {
|
|
4440
|
+
const binary = ensureNativeNotifier();
|
|
4441
|
+
console.error(D(` actionable HUD ${binary}`));
|
|
4442
|
+
}
|
|
4443
|
+
catch (e) {
|
|
4444
|
+
die(`could not build the native notification helper: ${e.message}`);
|
|
4445
|
+
}
|
|
4193
4446
|
}
|
|
4194
4447
|
for (const l of installService(kind, homedir(), extraN))
|
|
4195
4448
|
console.error(l);
|
|
@@ -4266,15 +4519,12 @@ async function main() {
|
|
|
4266
4519
|
if (dirname(d) === d)
|
|
4267
4520
|
break;
|
|
4268
4521
|
}
|
|
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`);
|
|
4522
|
+
const refused = folderRefusalHere(process.cwd());
|
|
4523
|
+
if (isTemporaryRefusal(refused))
|
|
4524
|
+
die(`${refused.why.replace("<team>", m.name)}.`);
|
|
4525
|
+
if (refused && ["root", "home", "above-home"].includes(refused.kind) && !argv.includes("--yes")) {
|
|
4526
|
+
const what = refused.kind === "root" ? "the root of the disk" : refused.kind === "home" ? "your home folder" : "a folder that holds your home folder";
|
|
4527
|
+
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
4528
|
}
|
|
4279
4529
|
if ((await import("./repo-find.js")).holdsRepo(process.cwd())) {
|
|
4280
4530
|
const n = await bindReposIn(process.cwd(), all, m, argv.includes("--yes"), `pantheon use ${m.name} --here --yes`);
|