@join-pantheon/cli 0.33.5 → 0.33.6
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 +34 -2
- package/dist/agents.js +114 -13
- package/dist/board.js +4 -0
- package/dist/claude-confine.js +46 -2
- package/dist/cli.js +194 -23
- package/dist/client.js +143 -6
- package/dist/doctor.js +49 -7
- package/dist/file-lock.js +2 -2
- package/dist/git-safe.js +5 -1
- package/dist/home-guard.js +57 -2
- package/dist/home-safety.js +2 -0
- package/dist/linux-sandbox.js +71 -0
- package/dist/machine.js +18 -0
- package/dist/mcp.js +13 -5
- package/dist/notifier.js +57 -7
- package/dist/persona.js +34 -10
- package/dist/pickup.js +82 -0
- package/dist/profile.js +24 -1
- package/dist/protocol.js +19 -2
- package/dist/relay.js +128 -20
- package/dist/removed.js +132 -0
- package/dist/run-failure.js +13 -0
- package/dist/setup-guide.js +27 -4
- package/dist/store.js +59 -0
- package/dist/tray.js +45 -7
- package/dist/usage.js +97 -0
- package/dist/which.js +3 -2
- package/dist/worker-fleet.js +33 -3
- package/dist/worker-policy.js +58 -0
- package/dist/worker.js +215 -29
- package/helper/macos/prebuilt/manifest.json +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -22,12 +22,13 @@ import { Worker } from "./worker.js";
|
|
|
22
22
|
import { DEFAULT_REMINDER_MS, ensureNativeNotifier, HumanNotifier, runNotifiers, sendDesktopNotification } from "./notifier.js";
|
|
23
23
|
import { linuxDesktopReachable } from "./notice-linux.js";
|
|
24
24
|
import { desktopUnavailable, hostEnv } from "./host-env.js";
|
|
25
|
-
import {
|
|
25
|
+
import { commandState } from "./command-state.js";
|
|
26
|
+
import { AGENT_SURFACES, applyProfileInit, cleanList, dropCatchAllClaims, isAgentSurface, loadProfile, organizationProfile, profileInitialized, saveProfile, teamProfile, wakeDefault, wakeMode } from "./profile.js";
|
|
26
27
|
import { runGuide, terminalIO, questionOrNull, parseSetupFlags, questionnaire, optOutsFrom, decisionAreasProblem } from "./setup-guide.js";
|
|
27
28
|
import { selectActs, formatAct, resolveRef } from "./history-view.js";
|
|
28
29
|
import { doctor, render, shareBlock } from "./doctor.js";
|
|
29
30
|
import { FEEDBACK_ADDRESS } from "./feedback.js";
|
|
30
|
-
import { menubarProc, notifierProc, relayProc, workerProc, wiring, logFileFor, dataSummary, stopProc, statFile, elapsed, windowsProcessInfo } from "./machine.js";
|
|
31
|
+
import { menubarProc, notifierProc, relayProc, runBy, workerProc, workerFlagRunMinutes, wiring, logFileFor, dataSummary, stopProc, statFile, elapsed, windowsProcessInfo } from "./machine.js";
|
|
31
32
|
import { assess } from "./untrusted.js";
|
|
32
33
|
import { report as spendReport, recentRuns } from "./spend.js";
|
|
33
34
|
import { renderBrief, renderTeam } from "./brief.js";
|
|
@@ -39,14 +40,16 @@ import { removeHook, decideStop, writeIntent, clearIntent, readIntent, repairNot
|
|
|
39
40
|
import { pending, render as renderPending } from "./pending.js";
|
|
40
41
|
import { signedRead } from "./reads.js";
|
|
41
42
|
import { start as qsStart, join as qsJoin, wireUp, teamNameFor, deviceLabel, bindableFolder, startReconnects } from "./quickstart.js";
|
|
42
|
-
import { recordedAgent } from "./agents.js";
|
|
43
|
+
import { recordedAgent, unattendedReadiness } from "./agents.js";
|
|
44
|
+
import { pickupEstimate } from "./pickup.js";
|
|
45
|
+
import { loadPolicy, requestReadinessRecheck, unsandboxedAllowed } from "./worker-policy.js";
|
|
43
46
|
import { alreadyMemberLine, askedLine, configureLines, fetchSeats, inUseLine, offerSeats, seatsForTeam, seatsToMove, startGate, RECOVER_WAITING_EXIT } from "./recover-offer.js";
|
|
44
47
|
import { shellArg, teamNameProblem } from "./team-name.js";
|
|
45
48
|
import { folderRefusalHere, isTemporaryRefusal, projectFolderAnswer } from "./folder-guard.js";
|
|
46
49
|
import { installService, serviceInstalled, serviceLogFile, uninstallService } from "./service.js";
|
|
47
50
|
import { LEGACY_TASK_NAME, hostAlive, hostFiles, readHostState, recordedTaskName } from "./win-host.js";
|
|
48
51
|
import { ACTS, LIMITS } from "./protocol.js";
|
|
49
|
-
import { loadPersonas, personaByName, describePersona, initPersona, personaDir } from "./persona.js";
|
|
52
|
+
import { loadPersonas, personaByName, describePersona, initPersona, personaDir, olderTeammatesNote } from "./persona.js";
|
|
50
53
|
import { fingerprint, newKeypair, signAccount } from "./identity.js";
|
|
51
54
|
import { VERSION } from "./version.js";
|
|
52
55
|
import { connectFailure, signInHint } from "./refusal.js";
|
|
@@ -840,6 +843,33 @@ function unattendedProject() {
|
|
|
840
843
|
return { project: resolve(flag("project", ".")), how: "explicit" };
|
|
841
844
|
return { project: resolve("."), how: "default" };
|
|
842
845
|
}
|
|
846
|
+
function agentReadiness(cfg) {
|
|
847
|
+
let unsandboxedOk = false;
|
|
848
|
+
try {
|
|
849
|
+
unsandboxedOk = unsandboxedAllowed(loadPolicy());
|
|
850
|
+
}
|
|
851
|
+
catch { }
|
|
852
|
+
let commandsOn = false;
|
|
853
|
+
try {
|
|
854
|
+
commandsOn = commandState({ project: process.cwd() }).level !== "off";
|
|
855
|
+
}
|
|
856
|
+
catch { }
|
|
857
|
+
return unattendedReadiness({ prefer: loadProfile(cfg).agent.primary_surface, unsandboxedOk, commandsOn, inContainer: hostEnv().kind === "container" });
|
|
858
|
+
}
|
|
859
|
+
function sayAgentReadiness(cfg) {
|
|
860
|
+
if (!cfg || inWokenRun())
|
|
861
|
+
return;
|
|
862
|
+
requestReadinessRecheck();
|
|
863
|
+
try {
|
|
864
|
+
if (loadProfile(cfg).agent.wake_enabled === false)
|
|
865
|
+
return;
|
|
866
|
+
const r = agentReadiness(cfg);
|
|
867
|
+
if (r.ok)
|
|
868
|
+
return;
|
|
869
|
+
console.error(`\n\x1b[33m! Before teammates send you work:\x1b[0m ${r.says}\n ${r.fix}\n ${D("Messages and decisions work without it; pantheon doctor checks again.")}`);
|
|
870
|
+
}
|
|
871
|
+
catch { }
|
|
872
|
+
}
|
|
843
873
|
async function guidedSetup(cfg, project, opts = {}) {
|
|
844
874
|
if (argv.includes("--no-guide"))
|
|
845
875
|
return;
|
|
@@ -868,11 +898,14 @@ async function guidedSetup(cfg, project, opts = {}) {
|
|
|
868
898
|
catch {
|
|
869
899
|
return [];
|
|
870
900
|
} },
|
|
901
|
+
readiness: () => agentReadiness(cfg),
|
|
871
902
|
publish: async (p, keep) => {
|
|
872
903
|
const c = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
873
904
|
try {
|
|
874
905
|
await c.connect();
|
|
875
|
-
await c.publishProfile(organizationProfile(p, cfg.spaceId, cfg.space), keep);
|
|
906
|
+
const r = await c.publishProfile(organizationProfile(p, cfg.spaceId, cfg.space), keep);
|
|
907
|
+
if (r.note)
|
|
908
|
+
console.error(D(` the relay ${r.note}`));
|
|
876
909
|
return [...c.profiles.values()];
|
|
877
910
|
}
|
|
878
911
|
finally {
|
|
@@ -1152,6 +1185,8 @@ ${B("control")}
|
|
|
1152
1185
|
pantheon worker stay answerable while you are away
|
|
1153
1186
|
(--agent claude-code|codex|cursor|copilot|kiro|auto, --persona <name>)
|
|
1154
1187
|
pantheon policy what this machine's worker may do, in one screen (grant/revoke self-upgrade)
|
|
1188
|
+
pantheon policy set run-minutes <5-180> how long one woken run may take here (default 60)
|
|
1189
|
+
pantheon policy set idle-seconds <0-900> how long your session must be quiet before your worker takes a request (default 120)
|
|
1155
1190
|
pantheon persona how a woken worker should behave: list / init <name> / show <name>
|
|
1156
1191
|
pantheon notifier alert you when a task requires your decision
|
|
1157
1192
|
pantheon menubar the menu bar item: status at a glance, pause/resume, decisions
|
|
@@ -1159,6 +1194,7 @@ ${B("control")}
|
|
|
1159
1194
|
pantheon sessions your devices & active sessions; revoke <id> to sign one out
|
|
1160
1195
|
pantheon logout sign this machine out · pantheon account delete remove your account
|
|
1161
1196
|
pantheon audit [--space X] the log: who did what, when (--json to export, --since <hours>)
|
|
1197
|
+
pantheon usage [--space X] the team's daily message and goal counts, last 30 days (founder only; --days N)
|
|
1162
1198
|
pantheon profile show, create (init), or set your agent and attention profile
|
|
1163
1199
|
pantheon directory organization roles and decision authority
|
|
1164
1200
|
pantheon approvals decisions waiting on you; show or answer one
|
|
@@ -1255,13 +1291,14 @@ const COMMAND_FLAGS = {
|
|
|
1255
1291
|
say: "<ACT> <text> --to <member>[,<member>] --goal <id> --ref <id | seq | last> --summary '<one line>' --domains <a,b> --branch --commit --remote (DELIVERED) --space <team>",
|
|
1256
1292
|
goal: "list | open \"<title>\" --with <member>[,…] [--acceptance \"…\"] [--deadline <ISO time>] [--list] | close <id> \"<summary>\"",
|
|
1257
1293
|
history: "--goal <id> --from <member> --last <n> --since <seq> --short --presence --json",
|
|
1294
|
+
usage: "--space <team> --days <n> --json",
|
|
1258
1295
|
network: "--proxy <url> [--no-proxy <hosts>] | --direct | --clear",
|
|
1259
1296
|
leave: "[<team>] --yes --keep-identity --stop-relay --project <dir>",
|
|
1260
1297
|
uninstall: "--yes --forget-identity --stop-relay --project <dir>",
|
|
1261
1298
|
stop: "[relay|worker|notifier|menubar|all] --port <n>",
|
|
1262
1299
|
logs: "[relay|worker|notifier|menubar] --follow --n <lines>",
|
|
1263
|
-
worker: "--agent <claude-code|codex|cursor|copilot|kiro|auto> --persona <name> --project <dir> --allow-bash --max-runs <n> --max-runs-day <n> --grace <s> --once",
|
|
1264
|
-
service: "install [relay|worker|notifier|menubar] | restart <kind> | uninstall <kind> --project <dir> --allow-bash",
|
|
1300
|
+
worker: "--agent <claude-code|codex|cursor|copilot|kiro|auto> --persona <name> --project <dir> --allow-bash --max-runs <n> --max-runs-day <n> --run-minutes <5-180> --grace <s> --once",
|
|
1301
|
+
service: "install [relay|worker|notifier|menubar] | restart <kind> | uninstall <kind> --project <dir> --allow-bash --run-minutes <5-180>",
|
|
1265
1302
|
doctor: "--share --project <dir>",
|
|
1266
1303
|
relay: "--port <n> --host <address> --data <dir>",
|
|
1267
1304
|
use: "<team> --here --yes · pantheon use --unbind (this folder or repo bound to no team again)",
|
|
@@ -1536,6 +1573,8 @@ async function main() {
|
|
|
1536
1573
|
printReady(r.tools);
|
|
1537
1574
|
if (r.claimed)
|
|
1538
1575
|
await guidedSetup(load() ?? r.cfg, repoInfo(process.cwd())?.root ?? null, { preset: setupFlags.preset, force: Object.keys(setupFlags.preset).length > 0 || argv.includes("--yes"), founder: true });
|
|
1576
|
+
if (r.claimed)
|
|
1577
|
+
sayAgentReadiness(load() ?? r.cfg);
|
|
1539
1578
|
if (r.relay) {
|
|
1540
1579
|
console.error(D(`\nKeeping the relay running here. Ctrl-C stops it (your teammate goes offline too).`));
|
|
1541
1580
|
await new Promise(() => { });
|
|
@@ -1819,6 +1858,7 @@ async function main() {
|
|
|
1819
1858
|
const joined = load();
|
|
1820
1859
|
if (joined)
|
|
1821
1860
|
await guidedSetup(joined, repoInfo(process.cwd())?.root ?? null, { preset: setupFlags.preset, force: Object.keys(setupFlags.preset).length > 0 || argv.includes("--yes") });
|
|
1861
|
+
sayAgentReadiness(joined);
|
|
1822
1862
|
}
|
|
1823
1863
|
console.error(`\n${D('Then ask your agent: "check pantheon_status and say hello to the team".')}`);
|
|
1824
1864
|
return;
|
|
@@ -2020,6 +2060,7 @@ async function main() {
|
|
|
2020
2060
|
die(errors.join("\n") + "\n pantheon setup --questions lists every answer");
|
|
2021
2061
|
const cfg = need();
|
|
2022
2062
|
await guidedSetup(cfg, repoInfo(process.cwd())?.root ?? null, { force: true, preset });
|
|
2063
|
+
sayAgentReadiness(load() ?? cfg);
|
|
2023
2064
|
return;
|
|
2024
2065
|
}
|
|
2025
2066
|
case "version": {
|
|
@@ -2039,6 +2080,7 @@ async function main() {
|
|
|
2039
2080
|
});
|
|
2040
2081
|
await new Promise((r) => setTimeout(r, 350));
|
|
2041
2082
|
const peers = client.roster.filter((m) => m.id !== cfg.member);
|
|
2083
|
+
const { termSafe } = await import("./board.js");
|
|
2042
2084
|
console.log(`${B(cfg.space)} you are ${C(cfg.member)} ${D(cfg.relay)} ${D(`${client.head} acts`)}\n`);
|
|
2043
2085
|
if (client.plan)
|
|
2044
2086
|
console.log(D(` ${client.plan.name} plan · ${client.plan.usage.spaces} of ${fmtLimit(client.plan.limits.spaces)} spaces created · ${client.plan.usage.members} of ${fmtLimit(client.plan.limits.members)} people here${client.plan.limits.wake ? "" : " · no unattended workers"}`) + "\n");
|
|
@@ -2060,6 +2102,11 @@ async function main() {
|
|
|
2060
2102
|
? `\x1b[33m${recent ? `their agent has not seen your last ${behind} act${behind === 1 ? "" : "s"}` : `their agent is ${behind} act${behind === 1 ? "" : "s"} behind the team`}\x1b[0m`
|
|
2061
2103
|
: lag > 0 ? D("their agent has seen everything you sent") : D("their agent is caught up");
|
|
2062
2104
|
console.log(` ${p.online ? C("●") : D("○")} ${p.id.padEnd(14)} ${D(p.agent.padEnd(12))} ${reach}`);
|
|
2105
|
+
{
|
|
2106
|
+
const est = pickupEstimate(p, { now: client.relayNow() });
|
|
2107
|
+
if (est.kind === "unavailable")
|
|
2108
|
+
console.log(` ${" ".repeat(16)}\x1b[33m${termSafe(est.says)}\x1b[0m`);
|
|
2109
|
+
}
|
|
2063
2110
|
}
|
|
2064
2111
|
const goals = [...client.goals.values()];
|
|
2065
2112
|
console.log(`\n${B("goals")}`);
|
|
@@ -2094,8 +2141,12 @@ async function main() {
|
|
|
2094
2141
|
console.log(`\n${B("between delegates")}`);
|
|
2095
2142
|
if (!between.length)
|
|
2096
2143
|
console.log(` ${D("(nothing owed)")}`);
|
|
2144
|
+
const myMayWake = client.members.find((m) => m.member === cfg.member)?.may_wake;
|
|
2097
2145
|
for (const o of between) {
|
|
2098
2146
|
console.log(` ${o.kind.padEnd(7)} ${o.owner.padEnd(14)}${ageOf(o.created_at).padEnd(6)} ${o.body.split("\n")[0].slice(0, 110)} ${D(o.ref)}`);
|
|
2147
|
+
const peer = o.owner !== cfg.member && o.kind !== "DELIVER" ? client.roster.find((m) => m.id === o.owner) ?? { id: o.owner, sessions: [] } : null;
|
|
2148
|
+
if (peer)
|
|
2149
|
+
console.log(` ${" ".repeat(7)} ${D(termSafe(pickupEstimate(peer, { now: client.relayNow(), about: { ref: o.ref, goal: o.goal }, ...(myMayWake !== undefined ? { mayWake: myMayWake } : {}) }).says))}`);
|
|
2099
2150
|
}
|
|
2100
2151
|
if (human.length || between.length || goals.some((g) => g.state !== "CLOSED")) {
|
|
2101
2152
|
console.log(D(`\n read in full: pantheon show <id> · a thread: pantheon history --goal <goal> · reply: pantheon say ANSWER "…" --ref <id>`));
|
|
@@ -2388,7 +2439,7 @@ async function main() {
|
|
|
2388
2439
|
const upP = client ? client.connect().then(() => true, () => false) : Promise.resolve(false);
|
|
2389
2440
|
console.log(B("this machine"));
|
|
2390
2441
|
if (relay.running) {
|
|
2391
|
-
const how = relay.managedBy === "launchd"
|
|
2442
|
+
const how = relay.managedBy === "launchd" || relay.managedBy === "systemd"
|
|
2392
2443
|
? `service ${relay.label}`
|
|
2393
2444
|
: "started by hand — it dies when that terminal closes";
|
|
2394
2445
|
console.log(` relay ${C("running")} ${how} pid ${relay.pid}${relay.since ? ` up ${relay.since}` : ""}`);
|
|
@@ -2415,7 +2466,7 @@ async function main() {
|
|
|
2415
2466
|
}
|
|
2416
2467
|
}
|
|
2417
2468
|
if (worker.running) {
|
|
2418
|
-
console.log(` worker ${C("running")} ${
|
|
2469
|
+
console.log(` worker ${C("running")} ${runBy(worker)} pid ${worker.pid}`);
|
|
2419
2470
|
console.log(` ${D("a teammate approved on this machine (pantheon wake list) can start an agent run here")}`);
|
|
2420
2471
|
}
|
|
2421
2472
|
else {
|
|
@@ -2440,19 +2491,24 @@ async function main() {
|
|
|
2440
2491
|
}
|
|
2441
2492
|
}
|
|
2442
2493
|
else if (menubar.running) {
|
|
2443
|
-
console.log(` menu bar ${C("running")} ${menubar
|
|
2494
|
+
console.log(` menu bar ${C("running")} ${runBy(menubar)} pid ${menubar.pid}`);
|
|
2444
2495
|
}
|
|
2445
2496
|
else {
|
|
2446
2497
|
const why = desktopUnavailable(hostEnv(), "the menu bar");
|
|
2447
2498
|
console.log(` menu bar ${D("not running")} ${D(why ?? "pantheon service install menubar — status and pause/resume in the menu bar")}`);
|
|
2448
2499
|
}
|
|
2449
2500
|
if (notifier.running) {
|
|
2450
|
-
console.log(` notifier ${C("running")} ${
|
|
2501
|
+
console.log(` notifier ${C("running")} ${runBy(notifier)} pid ${notifier.pid}`);
|
|
2451
2502
|
console.log(` ${D("native alerts are on for decisions that require you")}`);
|
|
2452
2503
|
}
|
|
2453
2504
|
else {
|
|
2454
2505
|
console.log(` notifier ${D("not running")} ${D("human escalations only appear in pantheon status")}`);
|
|
2455
2506
|
}
|
|
2507
|
+
const RM = await import("./removed.js");
|
|
2508
|
+
for (const { team, record } of RM.removals(RM.teamsOf(loadAll()))) {
|
|
2509
|
+
const r = RM.removalWords(team, record.by);
|
|
2510
|
+
console.log(` removed from "${r.name}"${r.by ? ` by ${r.by}` : ""} ${D(`on ${new Date(record.at).toISOString().slice(0, 10)} — this machine stopped connecting to it (checks now and then, at most every 6 hours); coming back needs a new invite: pantheon join <invite>`)}`);
|
|
2511
|
+
}
|
|
2456
2512
|
console.log(`\n${B("wired into")}`);
|
|
2457
2513
|
for (const w of wiring())
|
|
2458
2514
|
console.log(` ${w.tool.padEnd(14)}${w.state.startsWith("not wired") ? D(w.state) : w.state}`);
|
|
@@ -2500,7 +2556,7 @@ async function main() {
|
|
|
2500
2556
|
? D("no signal — no turn reported since the relay came up; your next prompt reports one")
|
|
2501
2557
|
: w.busy
|
|
2502
2558
|
? `a turn is in flight${w.busy_since ? ` (for ${ago(w.busy_since)})` : ""} ${D(`the worker waits; a turn past ${LIMITS.activityBusyTtlMs / 60_000}m with no Stop is read as ended`)}`
|
|
2503
|
-
: `idle for ${ago(w.idle_since)} ${D(`the worker takes work after ${
|
|
2559
|
+
: `idle for ${ago(w.idle_since)} ${D(await (async () => { const W = await import("./worker-policy.js"); const n = W.effectiveIdleSeconds({ policy: loadPolicy() }).seconds; return n ? `the worker takes work after ${W.describeIdleSeconds(n)} idle` : "the worker takes work as soon as no turn is in flight"; })())}`;
|
|
2504
2560
|
console.log(` your activity: ${activity}`);
|
|
2505
2561
|
for (const p of client.roster.filter((m) => m.id !== cfg.member)) {
|
|
2506
2562
|
console.log(` ${p.id.padEnd(16)}${p.online ? C("online") : D("offline")} ${p.sessions.length} window(s)`);
|
|
@@ -2856,6 +2912,10 @@ async function main() {
|
|
|
2856
2912
|
` Delete it instead: pantheon space delete ${m.name} --yes\n Leave it anyway: pantheon leave ${m.name} --yes`);
|
|
2857
2913
|
}
|
|
2858
2914
|
removeMembership(m.id);
|
|
2915
|
+
try {
|
|
2916
|
+
(await import("./removed.js")).clearRemoval({ space: m.name, spaceId: m.id, relay: m.relay });
|
|
2917
|
+
}
|
|
2918
|
+
catch { }
|
|
2859
2919
|
console.error(`${B("left")} "${m.name}" — its token is gone from this machine${bound.length ? `, and ${bound.length} binding${bound.length === 1 ? "" : "s"} with it (${bound.join(", ")})` : ""}.`);
|
|
2860
2920
|
sayDefaultAfter(all, m);
|
|
2861
2921
|
if (others === null)
|
|
@@ -3389,6 +3449,37 @@ async function main() {
|
|
|
3389
3449
|
console.log(D(`\nRevoke one: pantheon sessions revoke <id> · remove a device: pantheon sessions revoke <device-id> --device`));
|
|
3390
3450
|
return;
|
|
3391
3451
|
}
|
|
3452
|
+
case "usage": {
|
|
3453
|
+
let cfg;
|
|
3454
|
+
if (flag("space")) {
|
|
3455
|
+
const pf = loadAll() ?? die("not configured — pantheon join <invite> first");
|
|
3456
|
+
const m = findMembership(pf, flag("space")) ?? die(`you are not in a team called "${flag("space")}"`);
|
|
3457
|
+
cfg = configFor(pf, m, null);
|
|
3458
|
+
}
|
|
3459
|
+
else
|
|
3460
|
+
cfg = need();
|
|
3461
|
+
const { USAGE_SHOW_DAYS, USAGE_KEEP_DAYS, usageTable } = await import("./usage.js");
|
|
3462
|
+
const daysRaw = flag("days");
|
|
3463
|
+
const days = daysRaw === undefined ? USAGE_SHOW_DAYS : /^\d+$/.test(daysRaw) && Number(daysRaw) >= 1 && Number(daysRaw) <= USAGE_KEEP_DAYS ? Number(daysRaw) : die(`--days takes a whole number from 1 to ${USAGE_KEEP_DAYS}`);
|
|
3464
|
+
let res;
|
|
3465
|
+
try {
|
|
3466
|
+
res = await signedRead(cfg, "/usage", { days }, { timeoutMs: 15_000 });
|
|
3467
|
+
}
|
|
3468
|
+
catch (e) {
|
|
3469
|
+
die(connectFailure(e, cfg.relay));
|
|
3470
|
+
}
|
|
3471
|
+
const body = await res.json().then((b) => (b && typeof b === "object" ? b : null), () => null);
|
|
3472
|
+
if (!body || typeof body.usage_v !== "number")
|
|
3473
|
+
die(`"${cfg.space}"'s relay runs a Pantheon older than 0.33.6, which keeps no usage counts — nothing to show. Its operator can upgrade it.`);
|
|
3474
|
+
if (!res.ok || !Array.isArray(body.days))
|
|
3475
|
+
die(body.error ?? `the relay refused (HTTP ${res.status})`);
|
|
3476
|
+
if (argv.includes("--json")) {
|
|
3477
|
+
console.log(JSON.stringify(body, null, 2));
|
|
3478
|
+
return;
|
|
3479
|
+
}
|
|
3480
|
+
console.log(usageTable(body.space ?? cfg.space, body.days, days));
|
|
3481
|
+
return;
|
|
3482
|
+
}
|
|
3392
3483
|
case "audit": {
|
|
3393
3484
|
const cfg = identity() ?? die("not set up on this machine — pantheon signup first");
|
|
3394
3485
|
if (!cfg.account)
|
|
@@ -3482,6 +3573,11 @@ async function main() {
|
|
|
3482
3573
|
console.log(`${B(describePersona(p))} ${D(p.file)}`);
|
|
3483
3574
|
for (const w of p.warnings)
|
|
3484
3575
|
console.log(` \x1b[33m! ${w}\x1b[0m`);
|
|
3576
|
+
{
|
|
3577
|
+
const n = olderTeammatesNote(p);
|
|
3578
|
+
if (n)
|
|
3579
|
+
console.log(` \x1b[33m! ${n}\x1b[0m`);
|
|
3580
|
+
}
|
|
3485
3581
|
console.log("");
|
|
3486
3582
|
console.log(p.instructions || D("(no instructions — policy only)"));
|
|
3487
3583
|
return;
|
|
@@ -3497,6 +3593,11 @@ async function main() {
|
|
|
3497
3593
|
console.log(` ${describePersona(p)} ${D(p.file)}`);
|
|
3498
3594
|
for (const w of p.warnings)
|
|
3499
3595
|
console.log(` \x1b[33m! ${w}\x1b[0m`);
|
|
3596
|
+
{
|
|
3597
|
+
const n = olderTeammatesNote(p);
|
|
3598
|
+
if (n)
|
|
3599
|
+
console.log(` \x1b[33m! ${n}\x1b[0m`);
|
|
3600
|
+
}
|
|
3500
3601
|
}
|
|
3501
3602
|
return;
|
|
3502
3603
|
}
|
|
@@ -3537,7 +3638,9 @@ async function main() {
|
|
|
3537
3638
|
const client = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
3538
3639
|
try {
|
|
3539
3640
|
await client.connect();
|
|
3540
|
-
await client.publishProfile(organizationProfile(profile, cfg.spaceId, cfg.space), keep);
|
|
3641
|
+
const r = await client.publishProfile(organizationProfile(profile, cfg.spaceId, cfg.space), keep);
|
|
3642
|
+
if (r.note)
|
|
3643
|
+
console.error(D(` the relay ${r.note}`));
|
|
3541
3644
|
}
|
|
3542
3645
|
finally {
|
|
3543
3646
|
client.close();
|
|
@@ -3580,7 +3683,12 @@ async function main() {
|
|
|
3580
3683
|
const keywords = flag("keywords");
|
|
3581
3684
|
const publicChange = name !== undefined || title !== undefined || responsibilities !== undefined ||
|
|
3582
3685
|
humanRequired !== undefined || agentCanDecide !== undefined || capabilities !== undefined || keywords !== undefined;
|
|
3583
|
-
|
|
3686
|
+
const vague = dropCatchAllClaims(profile);
|
|
3687
|
+
if (vague.dropped.length) {
|
|
3688
|
+
profile = { ...vague.profile, updated_at: Date.now() };
|
|
3689
|
+
console.error(`${C("dropped")} ${vague.dropped.map((d) => `"${d}"`).join(", ")} from your decision claims — not a decision area, so it never routed anything`);
|
|
3690
|
+
}
|
|
3691
|
+
if (!surface && !publicChange && !vague.dropped.length) {
|
|
3584
3692
|
die("usage: pantheon profile set [--surface codex|claude-code|cursor|copilot|kiro] [--name name] [--title title] [--responsibilities a,b] [--human-required a,b] [--agent-can-decide a,b] [--keywords 'domain=word|word;domain=word']");
|
|
3585
3693
|
}
|
|
3586
3694
|
if (flag("space")) {
|
|
@@ -3605,8 +3713,8 @@ async function main() {
|
|
|
3605
3713
|
const tcl = new PantheonClient(tc, undefined, { role: "cli" });
|
|
3606
3714
|
try {
|
|
3607
3715
|
await tcl.connect();
|
|
3608
|
-
await tcl.publishProfile(organizationProfile(profile, m.id, m.name), [...(humanRequired ?? []), ...(agentCanDecide ?? [])]);
|
|
3609
|
-
console.error(`${C("published")} into "${m.name}"`);
|
|
3716
|
+
const r = await tcl.publishProfile(organizationProfile(profile, m.id, m.name), [...(humanRequired ?? []), ...(agentCanDecide ?? [])]);
|
|
3717
|
+
console.error(`${C("published")} into "${m.name}"${r.note ? D(` — the relay ${r.note}`) : ""}`);
|
|
3610
3718
|
}
|
|
3611
3719
|
catch (e) {
|
|
3612
3720
|
console.error(`\x1b[33mnot published\x1b[0m — ${e.message}`);
|
|
@@ -3636,7 +3744,7 @@ async function main() {
|
|
|
3636
3744
|
};
|
|
3637
3745
|
saveProfile(profile);
|
|
3638
3746
|
console.error(`${C("saved")} — ${surface ? `decision notifications open ${surface}` : "local organization profile updated"}`);
|
|
3639
|
-
if (publicChange) {
|
|
3747
|
+
if (publicChange || vague.dropped.length) {
|
|
3640
3748
|
try {
|
|
3641
3749
|
await publish([...(humanRequired ?? []), ...(agentCanDecide ?? [])]);
|
|
3642
3750
|
console.error(`${C("published")} — the organization can now route decisions using this authority`);
|
|
@@ -4070,9 +4178,52 @@ async function main() {
|
|
|
4070
4178
|
}
|
|
4071
4179
|
}
|
|
4072
4180
|
case "policy": {
|
|
4073
|
-
const { loadPolicy, savePolicy, grant, revoke, inWokenRun, CAPABILITY_ARGS } = await import("./worker-policy.js");
|
|
4181
|
+
const { loadPolicy, savePolicy, grant, revoke, inWokenRun, CAPABILITY_ARGS, parseRunMinutes, runMinutesProblem, effectiveRunMinutes, describeRunMinutes, RUN_MINUTES, parseIdleSeconds, idleSecondsProblem, effectiveIdleSeconds, describeIdleSeconds, IDLE_SECONDS } = await import("./worker-policy.js");
|
|
4074
4182
|
const { COMMAND_GRANT_SCOPE } = await import("./agents.js");
|
|
4075
4183
|
const sub = argv[1];
|
|
4184
|
+
if (sub === "set") {
|
|
4185
|
+
const key = argv[2];
|
|
4186
|
+
if (key !== "run-minutes" && key !== "idle-seconds")
|
|
4187
|
+
die(`usage: pantheon policy set run-minutes <${RUN_MINUTES.min}-${RUN_MINUTES.max} | default> (how long one woken run may take on this machine)\n pantheon policy set idle-seconds <${IDLE_SECONDS.min}-${IDLE_SECONDS.max} | default> (how long your session must be quiet before your worker takes a request)`);
|
|
4188
|
+
if (key === "idle-seconds") {
|
|
4189
|
+
if (inWokenRun())
|
|
4190
|
+
die("a woken run cannot change how long the worker waits for you — a person has to run this here");
|
|
4191
|
+
const raw = argv[3];
|
|
4192
|
+
const p = loadPolicy();
|
|
4193
|
+
if (raw === "default")
|
|
4194
|
+
delete p.idleSeconds;
|
|
4195
|
+
else {
|
|
4196
|
+
const n = parseIdleSeconds(raw);
|
|
4197
|
+
if (n === null)
|
|
4198
|
+
die(idleSecondsProblem(raw));
|
|
4199
|
+
p.idleSeconds = n;
|
|
4200
|
+
}
|
|
4201
|
+
savePolicy(p);
|
|
4202
|
+
const now = effectiveIdleSeconds({ policy: p });
|
|
4203
|
+
console.log(`${C("set")} idle wait — your worker takes a teammate's request ${now.seconds ? `after your session has been quiet ${describeIdleSeconds(now.seconds)}` : "with no extra wait once your session is quiet (a turn in flight still holds it)"}${now.from === "default" ? " (the default)" : ""} ${D("— from its next look, no restart; teammates are told the wait")}`);
|
|
4204
|
+
if (workerProc().running)
|
|
4205
|
+
console.log(D(` a worker started with --idle keeps its own value`));
|
|
4206
|
+
return;
|
|
4207
|
+
}
|
|
4208
|
+
if (inWokenRun())
|
|
4209
|
+
die("a woken run cannot change how long runs may take — a person has to run this here");
|
|
4210
|
+
const raw = argv[3];
|
|
4211
|
+
const p = loadPolicy();
|
|
4212
|
+
if (raw === "default")
|
|
4213
|
+
delete p.runMinutes;
|
|
4214
|
+
else {
|
|
4215
|
+
const n = parseRunMinutes(raw);
|
|
4216
|
+
if (n === null)
|
|
4217
|
+
die(runMinutesProblem(raw));
|
|
4218
|
+
p.runMinutes = n;
|
|
4219
|
+
}
|
|
4220
|
+
savePolicy(p);
|
|
4221
|
+
const now = effectiveRunMinutes({ policy: p });
|
|
4222
|
+
console.log(`${C("set")} run limit — ${describeRunMinutes(now)} ${D("— from the next run; a run already going keeps the limit it started with")}`);
|
|
4223
|
+
if (workerProc().running)
|
|
4224
|
+
console.log(D(` a worker started with --run-minutes keeps its own value; personas with max_run_minutes still lower it for what they handle`));
|
|
4225
|
+
return;
|
|
4226
|
+
}
|
|
4076
4227
|
const capArg = (argv[2] ?? "").replace(/-/g, "_");
|
|
4077
4228
|
const caps = Object.keys(CAPABILITY_ARGS);
|
|
4078
4229
|
if (sub === "grant" || sub === "revoke") {
|
|
@@ -4096,7 +4247,7 @@ async function main() {
|
|
|
4096
4247
|
return;
|
|
4097
4248
|
}
|
|
4098
4249
|
if (sub && sub !== "show")
|
|
4099
|
-
die(`usage: pantheon policy [show | grant <capability> | revoke <capability>] capabilities: ${caps.map((c) => CAPABILITY_ARGS[c]).join(", ")}`);
|
|
4250
|
+
die(`usage: pantheon policy [show | grant <capability> | revoke <capability> | set run-minutes <${RUN_MINUTES.min}-${RUN_MINUTES.max}|default> | set idle-seconds <${IDLE_SECONDS.min}-${IDLE_SECONDS.max}|default>] capabilities: ${caps.map((c) => CAPABILITY_ARGS[c]).join(", ")}`);
|
|
4100
4251
|
const p = loadPolicy();
|
|
4101
4252
|
const w = workerProc();
|
|
4102
4253
|
console.log(`${B("What this machine's worker may do")}\n`);
|
|
@@ -4132,7 +4283,19 @@ async function main() {
|
|
|
4132
4283
|
console.log(` ${D(`ignored "${r.line}" — ${r.why}`)}`);
|
|
4133
4284
|
}
|
|
4134
4285
|
console.log(` runs ${D("12/hour, 40/day, one at a time, 2 failed attempts then parked (defaults; personas only lower them)")}`);
|
|
4135
|
-
|
|
4286
|
+
{
|
|
4287
|
+
const lim = effectiveRunMinutes({ policy: p });
|
|
4288
|
+
const lowered = pers.filter((x) => x.maxRunMinutes !== undefined && x.maxRunMinutes < lim.minutes);
|
|
4289
|
+
console.log(` run limit ${describeRunMinutes(lim)}${D(` — a run still going then is stopped, tried once more, then handed to you (change: pantheon policy set run-minutes <${RUN_MINUTES.min}-${RUN_MINUTES.max}>)`)}`);
|
|
4290
|
+
if (lowered.length)
|
|
4291
|
+
console.log(` ${D(`lower for what ${lowered.map((x) => `${x.name} (${x.maxRunMinutes} min)`).join(", ")} handle${lowered.length === 1 ? "s" : ""}`)}`);
|
|
4292
|
+
const wf = workerFlagRunMinutes();
|
|
4293
|
+
if (wf !== null)
|
|
4294
|
+
console.log(` \x1b[33mthe running worker was started with --run-minutes ${wf}, which it uses instead\x1b[0m`);
|
|
4295
|
+
const idle = effectiveIdleSeconds({ policy: p });
|
|
4296
|
+
console.log(` idle wait ${idle.seconds ? `${describeIdleSeconds(idle.seconds)} of quiet in your session before your worker takes a request` : "none — your worker takes a request as soon as no turn is in flight in your session"}${D(` (${idle.from === "default" ? "the default" : "set on this machine"}; change: pantheon policy set idle-seconds <${IDLE_SECONDS.min}-${IDLE_SECONDS.max}>)`)}`);
|
|
4297
|
+
}
|
|
4298
|
+
console.log(`\n${D("Change it: pantheon policy grant|revoke self-upgrade|run-commands|unsandboxed-runs · pantheon policy set run-minutes <n> | idle-seconds <n> · personas: pantheon persona init · who may wake this machine: pantheon wake allow|deny <who>")}`);
|
|
4136
4299
|
return;
|
|
4137
4300
|
}
|
|
4138
4301
|
case "notice-show": {
|
|
@@ -4852,6 +5015,7 @@ async function main() {
|
|
|
4852
5015
|
return;
|
|
4853
5016
|
}
|
|
4854
5017
|
case "worker": {
|
|
5018
|
+
const runMinutesFlag = flag("run-minutes") === undefined ? undefined : (await import("./worker-policy.js")).parseRunMinutes(flag("run-minutes")) ?? die((await import("./worker-policy.js")).runMinutesProblem(flag("run-minutes")).replace("pantheon policy set run-minutes default", "leave --run-minutes off"));
|
|
4855
5019
|
(await import("./machine.js")).recordWorkerPid();
|
|
4856
5020
|
const allCfg = loadAll();
|
|
4857
5021
|
const bindings = Object.values(allCfg?.bindings ?? {});
|
|
@@ -4862,10 +5026,11 @@ async function main() {
|
|
|
4862
5026
|
...(flag("persona") ? { persona: flag("persona") } : {}),
|
|
4863
5027
|
continuations: !argv.includes("--no-continue"),
|
|
4864
5028
|
allowBash: argv.includes("--allow-bash"),
|
|
4865
|
-
idleMs: numFlag("idle", LIMITS.workerIdleMs / 1000) * 1000,
|
|
5029
|
+
...(flag("idle") !== undefined ? { idleMs: numFlag("idle", LIMITS.workerIdleMs / 1000) * 1000 } : {}),
|
|
4866
5030
|
maxRunsPerHour: numFlag("max-runs", 12),
|
|
4867
5031
|
maxRunsPerDay: numFlag("max-runs-day", 40),
|
|
4868
5032
|
graceMs: numFlag("grace", LIMITS.workerGraceMs / 1000) * 1000,
|
|
5033
|
+
...(runMinutesFlag !== undefined ? { runMinutes: runMinutesFlag } : {}),
|
|
4869
5034
|
once: argv.includes("--once"),
|
|
4870
5035
|
...(argv.includes("--no-notify") ? {} : {
|
|
4871
5036
|
notify: (n) => { try {
|
|
@@ -4893,10 +5058,11 @@ async function main() {
|
|
|
4893
5058
|
...(flag("persona") ? { persona: flag("persona") } : {}),
|
|
4894
5059
|
continuations: !argv.includes("--no-continue"),
|
|
4895
5060
|
allowBash: argv.includes("--allow-bash"),
|
|
4896
|
-
idleMs: numFlag("idle", LIMITS.workerIdleMs / 1000) * 1000,
|
|
5061
|
+
...(flag("idle") !== undefined ? { idleMs: numFlag("idle", LIMITS.workerIdleMs / 1000) * 1000 } : {}),
|
|
4897
5062
|
maxRunsPerHour: numFlag("max-runs", 12),
|
|
4898
5063
|
maxRunsPerDay: numFlag("max-runs-day", 40),
|
|
4899
5064
|
graceMs: numFlag("grace", LIMITS.workerGraceMs / 1000) * 1000,
|
|
5065
|
+
...(runMinutesFlag !== undefined ? { runMinutes: runMinutesFlag } : {}),
|
|
4900
5066
|
once: argv.includes("--once"),
|
|
4901
5067
|
...(argv.includes("--no-notify") ? {} : {
|
|
4902
5068
|
notify: (n) => { try {
|
|
@@ -5204,7 +5370,12 @@ async function main() {
|
|
|
5204
5370
|
extra.push("--allow-bash");
|
|
5205
5371
|
if (argv.includes("--no-continue"))
|
|
5206
5372
|
extra.push("--no-continue");
|
|
5207
|
-
|
|
5373
|
+
if (flag("run-minutes") !== undefined) {
|
|
5374
|
+
const { parseRunMinutes, runMinutesProblem } = await import("./worker-policy.js");
|
|
5375
|
+
if (parseRunMinutes(flag("run-minutes")) === null)
|
|
5376
|
+
die(runMinutesProblem(flag("run-minutes")).replace("pantheon policy set run-minutes default", "leave --run-minutes off"));
|
|
5377
|
+
}
|
|
5378
|
+
for (const f of ["idle", "grace", "max-runs", "max-runs-day", "run-minutes", "scratch", "agent-cmd", "agent", "persona"]) {
|
|
5208
5379
|
if (flag(f))
|
|
5209
5380
|
extra.push(`--${f}`, flag(f));
|
|
5210
5381
|
}
|