@join-pantheon/cli 0.25.1 → 0.25.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -1
- package/dist/admin-stats.js +21 -7
- package/dist/agents.js +3 -0
- package/dist/cli.js +10 -11
- package/dist/decision-ui.js +13 -3
- package/dist/doctor.js +26 -25
- package/dist/grants.js +40 -7
- package/dist/install.js +115 -32
- package/dist/mcp-instructions.js +1 -1
- package/dist/mcp.js +17 -5
- package/dist/measure.js +43 -17
- package/dist/notice.js +1 -1
- package/dist/relay.js +7 -4
- package/dist/setup-guide.js +11 -2
- package/dist/tray.js +5 -3
- package/helper/macos/menubar.swift +394 -30
- package/package.json +1 -1
package/dist/measure.js
CHANGED
|
@@ -2,7 +2,7 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { resolve, dirname, join } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import {
|
|
5
|
+
import { claudeGrantedEffective, cursorGrantedEffective, claudeAllow, cursorAllow, isServerWide } from "./grants.js";
|
|
6
6
|
import { codexApprovalState } from "./install.js";
|
|
7
7
|
import { PANTHEON_TOOL_NAMES, WRITE_TOOL_NAMES } from "./tools.js";
|
|
8
8
|
const ROOT = process.env.PANTHEON_MCP_ROOT ?? resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
@@ -71,29 +71,55 @@ export function measureMcp(opts = {}) {
|
|
|
71
71
|
});
|
|
72
72
|
});
|
|
73
73
|
}
|
|
74
|
-
function agentLine(name, seen, wanted, strip) {
|
|
74
|
+
function agentLine(name, seen, wanted, strip, approval) {
|
|
75
75
|
if (seen.state === "absent")
|
|
76
76
|
return { text: `${name} not set up here`, drift: false };
|
|
77
77
|
if (seen.state === "invalid")
|
|
78
78
|
return { text: `${name} settings unreadable`, drift: true };
|
|
79
|
-
const
|
|
79
|
+
const wide = seen.tools.filter(isServerWide);
|
|
80
|
+
const named = seen.tools.filter((t) => !isServerWide(t)).map(strip);
|
|
81
|
+
const known = named.filter((t) => PANTHEON_TOOL_NAMES.includes(t));
|
|
82
|
+
const unknown = named.length - known.length;
|
|
80
83
|
const want = wanted.map(strip);
|
|
81
|
-
const missing = want.filter((t) => !
|
|
82
|
-
const extra =
|
|
83
|
-
if (!missing.length && !extra.length)
|
|
84
|
-
return { text: `${name} allows ${have.length}`, drift: false };
|
|
84
|
+
const missing = wide.length ? [] : want.filter((t) => !known.includes(t));
|
|
85
|
+
const extra = known.filter((t) => !want.includes(t));
|
|
85
86
|
const parts = [];
|
|
86
|
-
|
|
87
|
+
let drift = false;
|
|
88
|
+
if (wide.length && approval === "ask-before-sending") {
|
|
89
|
+
parts.push(`a server-wide rule (${wide.join(", ")}) allows every tool, against your choice`);
|
|
90
|
+
drift = true;
|
|
91
|
+
}
|
|
92
|
+
if (missing.length) {
|
|
87
93
|
parts.push(`${missing.length} would prompt on every call (${missing.map((t) => t.replace(/^pantheon_/, "")).join(", ")})`);
|
|
88
|
-
|
|
94
|
+
drift = true;
|
|
95
|
+
}
|
|
96
|
+
if (extra.length) {
|
|
89
97
|
parts.push(`${extra.length} allowed against your choice (${extra.map((t) => t.replace(/^pantheon_/, "")).join(", ")})`);
|
|
90
|
-
|
|
98
|
+
drift = true;
|
|
99
|
+
}
|
|
100
|
+
const deniedKnown = (seen.denied ?? []).filter((t) => !isServerWide(t)).map(strip).filter((t) => PANTHEON_TOOL_NAMES.includes(t));
|
|
101
|
+
const deniedWide = (seen.denied ?? []).filter(isServerWide);
|
|
102
|
+
if (deniedWide.length) {
|
|
103
|
+
parts.push(`a deny rule (${deniedWide.join(", ")}) refuses every pantheon tool`);
|
|
104
|
+
drift = true;
|
|
105
|
+
}
|
|
106
|
+
else if (deniedKnown.length) {
|
|
107
|
+
parts.push(`${deniedKnown.length} refused by a deny rule (${deniedKnown.map((t) => t.replace(/^pantheon_/, "")).join(", ")})`);
|
|
108
|
+
drift = true;
|
|
109
|
+
}
|
|
110
|
+
const count = wide.length && approval !== "ask-before-sending" ? `allows all ${want.length} via a server-wide rule` : `allows ${known.length}`;
|
|
111
|
+
const fromProject = seen.fromProject?.length ? ` (${seen.fromProject.length} of them from this repo's own settings)` : "";
|
|
112
|
+
const rest = unknown ? `, plus ${unknown} entr${unknown === 1 ? "y" : "ies"} that ${unknown === 1 ? "is" : "are"} not a pantheon tool (left as ${unknown === 1 ? "it is" : "they are"})` : "";
|
|
113
|
+
return { text: `${name} ${count}${fromProject}${rest}${parts.length ? ` — ${parts.join("; ")}` : ""}`, drift };
|
|
91
114
|
}
|
|
92
|
-
export function toolsReport(registered, seen, approval) {
|
|
115
|
+
export function toolsReport(registered, seen, approval, opts = {}) {
|
|
93
116
|
const problems = [];
|
|
94
117
|
const expected = [...PANTHEON_TOOL_NAMES].sort();
|
|
95
118
|
let head;
|
|
96
|
-
if (
|
|
119
|
+
if (opts.unbound) {
|
|
120
|
+
head = `${expected.length} tools in this build; the server offers pantheon_status alone in a folder bound to no team, so the list was not compared`;
|
|
121
|
+
}
|
|
122
|
+
else if (registered === null) {
|
|
97
123
|
head = `${expected.length} tools`;
|
|
98
124
|
}
|
|
99
125
|
else if (JSON.stringify(registered) !== JSON.stringify(expected)) {
|
|
@@ -105,8 +131,8 @@ export function toolsReport(registered, seen, approval) {
|
|
|
105
131
|
}
|
|
106
132
|
const stripClaude = (t) => t.replace(/^mcp__pantheon__/, "");
|
|
107
133
|
const stripCursor = (t) => t.replace(/^Mcp\(pantheon:(.*)\)$/, "$1");
|
|
108
|
-
const claude = agentLine("Claude Code", seen.claude, claudeAllow(approval), stripClaude);
|
|
109
|
-
const cursor = agentLine("Cursor", seen.cursor, cursorAllow(approval), stripCursor);
|
|
134
|
+
const claude = agentLine("Claude Code", seen.claude, claudeAllow(approval), stripClaude, approval);
|
|
135
|
+
const cursor = agentLine("Cursor", seen.cursor, cursorAllow(approval), stripCursor, approval);
|
|
110
136
|
if (claude.drift)
|
|
111
137
|
problems.push("Claude Code's allow-list does not match");
|
|
112
138
|
if (cursor.drift)
|
|
@@ -134,9 +160,9 @@ export function toolsReport(registered, seen, approval) {
|
|
|
134
160
|
const detail = `${head}; ${claude.text}, ${cursor.text}, ${codex}`;
|
|
135
161
|
if (!problems.length)
|
|
136
162
|
return { level: "pass", detail };
|
|
137
|
-
return { level: "warn", detail, fix: `${problems.join("; ")} — pantheon install --user rewrites every grant to match this build and your approval choice` };
|
|
163
|
+
return { level: "warn", detail, fix: `${problems.join("; ")} — pantheon install --user rewrites every grant in your own settings to match this build and your approval choice; a rule in this repo's .claude/settings*.json or .cursor/cli.json is yours to edit by hand` };
|
|
138
164
|
}
|
|
139
|
-
export function grantsSeen() {
|
|
140
|
-
return { claude:
|
|
165
|
+
export function grantsSeen(projectDir = null) {
|
|
166
|
+
return { claude: claudeGrantedEffective(projectDir), cursor: cursorGrantedEffective(projectDir), codex: codexApprovalState() };
|
|
141
167
|
}
|
|
142
168
|
//# sourceMappingURL=measure.js.map
|
package/dist/notice.js
CHANGED
|
@@ -95,7 +95,7 @@ export function composeDecision(o, act, goal, peer, ctx) {
|
|
|
95
95
|
body: fit(body, BUDGET.body),
|
|
96
96
|
ttl: 0,
|
|
97
97
|
sound: true,
|
|
98
|
-
primary: primaryFor(ctx.tools, ctx.preferred, decisionPrompt(o.id)),
|
|
98
|
+
primary: primaryFor(ctx.tools, ctx.preferred, decisionPrompt(o.id, decisionTitle(o.body))),
|
|
99
99
|
details: { decision: o.id, ...(act ? { act: act.id } : {}) },
|
|
100
100
|
};
|
|
101
101
|
}
|
package/dist/relay.js
CHANGED
|
@@ -405,10 +405,11 @@ export class Relay {
|
|
|
405
405
|
if (!authed)
|
|
406
406
|
return this.json(res, 401, { error: "admin token required" });
|
|
407
407
|
const rows = this.fleetHealth();
|
|
408
|
+
const relayAgeMs = Date.now() - this.startedAt;
|
|
408
409
|
if (url.searchParams.get("format") === "json")
|
|
409
|
-
return this.json(res, 200, { relay: VERSION, generated: Date.now(), spaces: rows });
|
|
410
|
+
return this.json(res, 200, { relay: VERSION, relay_age_ms: relayAgeMs, generated: Date.now(), spaces: rows });
|
|
410
411
|
res.writeHead(200, { "content-type": "text/plain; charset=utf-8" });
|
|
411
|
-
return void res.end(fleetReport(rows, VERSION) + "\n");
|
|
412
|
+
return void res.end(fleetReport(rows, VERSION, Date.now(), relayAgeMs) + "\n");
|
|
412
413
|
}
|
|
413
414
|
if (url.pathname === "/admin/export") {
|
|
414
415
|
if (!authed)
|
|
@@ -762,9 +763,11 @@ button:hover{background:#1d4ed8}.err{color:var(--bad);margin-top:12px}
|
|
|
762
763
|
const safeOf = (name) => name.replace(/[^a-z0-9._-]/gi, "_");
|
|
763
764
|
for (const sp of this.spaces.values()) {
|
|
764
765
|
seen.add(safeOf(sp.name));
|
|
766
|
+
const sessions = [...sp.members.values()].flatMap((m) => [...m.sessions.values()]);
|
|
765
767
|
out.push(spaceHealth({
|
|
766
768
|
name: sp.name, records: sp.ledger.all(), members: sp.allow.all(),
|
|
767
|
-
online:
|
|
769
|
+
online: sessions.length, interactive: sessions.filter((x) => x.role === "interactive").length,
|
|
770
|
+
relayPkg: VERSION, now, relayAgeMs: now - this.startedAt,
|
|
768
771
|
}));
|
|
769
772
|
}
|
|
770
773
|
let files = [];
|
|
@@ -783,7 +786,7 @@ button:hover{background:#1d4ed8}.err{color:var(--bad);margin-top:12px}
|
|
|
783
786
|
members = new Allowlist(this.dataDir, safe).all();
|
|
784
787
|
}
|
|
785
788
|
catch { }
|
|
786
|
-
out.push(spaceHealth({ name: safe, records: this.sleepingRecords(safe), members, online: 0, relayPkg: VERSION, now }));
|
|
789
|
+
out.push(spaceHealth({ name: safe, records: this.sleepingRecords(safe), members, online: 0, interactive: 0, relayPkg: VERSION, now, relayAgeMs: now - this.startedAt }));
|
|
787
790
|
}
|
|
788
791
|
return out;
|
|
789
792
|
}
|
package/dist/setup-guide.js
CHANGED
|
@@ -268,8 +268,17 @@ export async function runGuide(io, ctx, deps = {}, preset = {}) {
|
|
|
268
268
|
}
|
|
269
269
|
const wiring = deps.applyAgentWiring ?? ((ap) => installForUser(ap).map((t) => ({ tool: t.tool, status: t.status })));
|
|
270
270
|
try {
|
|
271
|
-
const
|
|
272
|
-
|
|
271
|
+
const rows = wiring(a.approval);
|
|
272
|
+
const failed = rows.filter((t) => /^failed/.test(t.status));
|
|
273
|
+
const codex = rows.find((t) => t.tool === "codex");
|
|
274
|
+
if (failed.length) {
|
|
275
|
+
for (const f of failed)
|
|
276
|
+
warnings.push(`${f.tool}: ${f.status} — that agent keeps its previous permissions until this is fixed; then run pantheon install --user`);
|
|
277
|
+
applied.push(`approval: "${a.approval}" is saved but NOT applied everywhere — see the warnings; nothing about it should be relied on yet`);
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
applied.push(`approval: ${approvalSummary(a.approval)}${codex ? ` — codex: ${codex.status}` : ""}`);
|
|
281
|
+
}
|
|
273
282
|
}
|
|
274
283
|
catch (e) {
|
|
275
284
|
warnings.push(`agent wiring not re-applied (${e.message}) — run pantheon install --user`);
|
package/dist/tray.js
CHANGED
|
@@ -86,6 +86,7 @@ export async function trayStatus(cfg = load(), f = loadAll()) {
|
|
|
86
86
|
return {
|
|
87
87
|
...first,
|
|
88
88
|
space: ok.map((p) => p.space).join(" · "),
|
|
89
|
+
spaces: ok.map((p) => p.space),
|
|
89
90
|
relay_ok: ok.every((p) => p.relay_ok),
|
|
90
91
|
decisions_ok: ok.every((p) => p.decisions_ok),
|
|
91
92
|
unavailable_spaces: ok.flatMap((p) => p.unavailable_spaces ?? []),
|
|
@@ -117,11 +118,11 @@ async function trayStatusOne(cfg) {
|
|
|
117
118
|
const mineHuman = all.filter((o) => o.owner === `human:${cfg.member}`);
|
|
118
119
|
const decisionsOk = ok;
|
|
119
120
|
const decisions = mineHuman
|
|
120
|
-
.map((o) => ({ id: o.id, body: decisionTitle(o.body), detail: o.body, prompt: decisionPrompt(o.id) }));
|
|
121
|
+
.map((o) => ({ id: o.id, body: decisionTitle(o.body), detail: o.body, prompt: decisionPrompt(o.id, decisionTitle(o.body)) }));
|
|
121
122
|
const senderOf = (o) => stripCeremony(o.body).from;
|
|
122
123
|
const row = (o, kind, who, prompt) => ({
|
|
123
124
|
id: o.id, kind, title: decisionTitle(o.body), headline: decisionTitle(o.body, 200), detail: o.body, prompt, who,
|
|
124
|
-
when: o.created_at ?? 0,
|
|
125
|
+
when: o.created_at ?? 0, space: cfg.space,
|
|
125
126
|
...(o.goal ? { goal: o.goal } : {}),
|
|
126
127
|
...(o.ref ? { ref: o.ref } : {}),
|
|
127
128
|
});
|
|
@@ -130,7 +131,7 @@ async function trayStatusOne(cfg) {
|
|
|
130
131
|
const items = [
|
|
131
132
|
...mineHuman.map((o) => {
|
|
132
133
|
const from = senderOf(o);
|
|
133
|
-
return row(o, "decision", !from || from === cfg.member ? "your agent" : from, decisionPrompt(o.id));
|
|
134
|
+
return row(o, "decision", !from || from === cfg.member ? "your agent" : from, decisionPrompt(o.id, decisionTitle(o.body)));
|
|
134
135
|
}),
|
|
135
136
|
...owed.map((o) => row(o, "owed", "you", owedPrompt(decisionTitle(o.body), o.goal))),
|
|
136
137
|
...waiting.map((o) => {
|
|
@@ -149,6 +150,7 @@ async function trayStatusOne(cfg) {
|
|
|
149
150
|
configured: true,
|
|
150
151
|
version: VERSION,
|
|
151
152
|
space: cfg.space,
|
|
153
|
+
spaces: [cfg.space],
|
|
152
154
|
member: cfg.member,
|
|
153
155
|
relay: cfg.relay,
|
|
154
156
|
relay_ok: !!space,
|