@join-pantheon/cli 0.21.1 → 0.21.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 +1 -1
- package/dist/cli.js +47 -5
- package/dist/relay.js +6 -1
- package/dist/setup-guide.js +86 -4
- package/dist/worker.js +38 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -70,7 +70,7 @@ default when run inside a git repo; `--no-worker` opts out — and a peer still
|
|
|
70
70
|
ends with a "You're set" summary, then a short guided setup on a terminal: whether teammates may wake
|
|
71
71
|
your machine while you are away, what a woken run may execute, who you are and which decisions are
|
|
72
72
|
yours alone (so agents stop and ask you), and which alerts you want. Plain questions, mostly options;
|
|
73
|
-
`pantheon setup` re-runs it any time. Restart your agent once and ask it *"check pantheon_status"*.
|
|
73
|
+
`pantheon setup` re-runs it any time — and an agent setting you up can run `pantheon setup --questions`, ask you in chat, and pass your answers back as flags, so nobody has to open a terminal. Restart your agent once and ask it *"check pantheon_status"*.
|
|
74
74
|
|
|
75
75
|
Already a member — new laptop, or just pulled a newer build? The same full setup is one command:
|
|
76
76
|
`pantheon install --user` (from your project's repo).
|
package/dist/cli.js
CHANGED
|
@@ -19,7 +19,7 @@ import { runMcp } from "./mcp.js";
|
|
|
19
19
|
import { Worker } from "./worker.js";
|
|
20
20
|
import { DEFAULT_REMINDER_MS, ensureNativeNotifier, HumanNotifier, sendDesktopNotification } from "./notifier.js";
|
|
21
21
|
import { AGENT_SURFACES, applyProfileInit, cleanList, isAgentSurface, loadProfile, organizationProfile, profileInitialized, saveProfile, wakeDefault } from "./profile.js";
|
|
22
|
-
import { runGuide, terminalIO } from "./setup-guide.js";
|
|
22
|
+
import { runGuide, terminalIO, parseSetupFlags, questionnaire } from "./setup-guide.js";
|
|
23
23
|
import { doctor, render } from "./doctor.js";
|
|
24
24
|
import { menubarProc, notifierProc, relayProc, workerProc, wiring, logFileFor, dataSummary, stopProc, statFile } from "./machine.js";
|
|
25
25
|
import { assess } from "./untrusted.js";
|
|
@@ -272,12 +272,13 @@ function unattendedProject() {
|
|
|
272
272
|
async function guidedSetup(cfg, project, opts = {}) {
|
|
273
273
|
if (argv.includes("--no-guide"))
|
|
274
274
|
return;
|
|
275
|
-
const
|
|
275
|
+
const preset = opts.preset ?? {};
|
|
276
|
+
const interactive = !!process.stdin.isTTY && !argv.includes("--yes") && !Object.keys(preset).length;
|
|
276
277
|
if (!interactive && !opts.force)
|
|
277
278
|
return;
|
|
278
279
|
const rl = interactive ? createInterface({ input: process.stdin, output: process.stderr }) : null;
|
|
279
280
|
const io = rl ? terminalIO(rl, (l) => console.error(l)) : null;
|
|
280
|
-
console.error(`\n${B("A few choices, then you're done.")}${io ? "" : D(" (no terminal — taking the defaults)")}`);
|
|
281
|
+
console.error(`\n${B("A few choices, then you're done.")}${io ? "" : Object.keys(preset).length ? D(" (applying the answers you passed)") : D(" (no terminal — taking the defaults)")}`);
|
|
281
282
|
try {
|
|
282
283
|
const r = await runGuide(io, { cfg, project }, {
|
|
283
284
|
publish: async (p) => {
|
|
@@ -308,7 +309,7 @@ async function guidedSetup(cfg, project, opts = {}) {
|
|
|
308
309
|
bindRepo(info, cfg.spaceId);
|
|
309
310
|
},
|
|
310
311
|
} : {}),
|
|
311
|
-
});
|
|
312
|
+
}, preset);
|
|
312
313
|
console.error("");
|
|
313
314
|
for (const l of r.applied)
|
|
314
315
|
console.error(` ${C("✓")} ${l}`);
|
|
@@ -417,6 +418,7 @@ ${B("control")}
|
|
|
417
418
|
pantheon doctor check this machine end to end
|
|
418
419
|
pantheon setup the guided choices: who may wake this machine, what a run may execute,
|
|
419
420
|
what you personally decide, which alerts you want — re-run any time
|
|
421
|
+
(--questions prints them for an agent to ask you; answers go back as flags)
|
|
420
422
|
pantheon upgrade pull the latest pantheon, rebuild, and re-wire your agents
|
|
421
423
|
|
|
422
424
|
${D("more: pantheon help --all")}
|
|
@@ -489,6 +491,8 @@ async function main() {
|
|
|
489
491
|
console.error(` ${C(`pantheon join ${r.invite}`)}\n`);
|
|
490
492
|
console.error(D(` Tell them to run it from their project's git repo. Single use, expires in ${Math.round(Number(flag("ttl", "1440")) / 60)}h.`));
|
|
491
493
|
console.error(D(` Redeeming it admits them — no separate approval step. Need another? \`pantheon invite\``));
|
|
494
|
+
console.error(D(`\n Not technical? Forward them this instead, and their agent does the rest:`));
|
|
495
|
+
console.error(` ${D("Tell your agent: run")} ${C("curl -fsSL https://www.joinpantheon.network/skills/join.md")} ${D("and follow it. Invite:")} ${C(`pantheon join ${r.invite}`)}`);
|
|
492
496
|
}
|
|
493
497
|
if (!r.reach.shareable && r.reach.hint) {
|
|
494
498
|
console.error(`\n\x1b[33m That address only works on this machine.\x1b[0m`);
|
|
@@ -513,6 +517,8 @@ async function main() {
|
|
|
513
517
|
const inv = await client.makeInvite(mayWake, ttl).catch((e) => die(e.message));
|
|
514
518
|
console.log(`pantheon join ${toInvite({ ...cfg, token: inv.url })}`);
|
|
515
519
|
console.error(D(`\nSingle use, expires ${new Date(inv.expires_at).toLocaleString()}.`));
|
|
520
|
+
console.error(D(`For a teammate who is not technical, forward this and their agent does the rest:`));
|
|
521
|
+
console.error(` ${D("Tell your agent: run")} ${C("curl -fsSL https://www.joinpantheon.network/skills/join.md")} ${D("and follow it. Invite:")} ${C(`pantheon join ${toInvite({ ...cfg, token: inv.url })}`)}`);
|
|
516
522
|
client.close();
|
|
517
523
|
return;
|
|
518
524
|
}
|
|
@@ -679,6 +685,35 @@ async function main() {
|
|
|
679
685
|
}
|
|
680
686
|
if (r.alreadyCurrent) {
|
|
681
687
|
console.error(`\n${B("already up to date")} ${D(`(${r.from})`)}`);
|
|
688
|
+
if (process.platform === "darwin") {
|
|
689
|
+
const { trayStale, ensureTrayBinary, writeTrayCommands } = await import("./tray.js");
|
|
690
|
+
const { notifierStale, ensureNativeNotifier } = await import("./notifier.js");
|
|
691
|
+
const { restartService } = await import("./service.js");
|
|
692
|
+
if (trayStale()) {
|
|
693
|
+
try {
|
|
694
|
+
ensureTrayBinary();
|
|
695
|
+
writeTrayCommands();
|
|
696
|
+
for (const l of restartService("menubar"))
|
|
697
|
+
console.error(l);
|
|
698
|
+
}
|
|
699
|
+
catch (e) {
|
|
700
|
+
console.error(` menu bar: could not rebuild (${e.message.split("\n")[0]})`);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
if (notifierStale()) {
|
|
704
|
+
try {
|
|
705
|
+
ensureNativeNotifier();
|
|
706
|
+
for (const l of restartService("notifier"))
|
|
707
|
+
console.error(l);
|
|
708
|
+
}
|
|
709
|
+
catch (e) {
|
|
710
|
+
console.error(` alerts: could not rebuild (${e.message.split("\n")[0]})`);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
const cur = load();
|
|
715
|
+
if (cur && loadProfile(cur).agent.wake_default === undefined)
|
|
716
|
+
await guidedSetup(cur, repoInfo(process.cwd())?.root ?? null);
|
|
682
717
|
return;
|
|
683
718
|
}
|
|
684
719
|
console.error(`\n${B(`upgraded ${r.from} → ${r.to}`)}`);
|
|
@@ -722,8 +757,15 @@ async function main() {
|
|
|
722
757
|
return;
|
|
723
758
|
}
|
|
724
759
|
case "setup": {
|
|
760
|
+
if (argv.includes("--questions")) {
|
|
761
|
+
console.log(questionnaire());
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
const { preset, errors } = parseSetupFlags(argv);
|
|
765
|
+
if (errors.length)
|
|
766
|
+
die(errors.join("\n") + "\n pantheon setup --questions lists every answer");
|
|
725
767
|
const cfg = need();
|
|
726
|
-
await guidedSetup(cfg, repoInfo(process.cwd())?.root ?? null, { force: true });
|
|
768
|
+
await guidedSetup(cfg, repoInfo(process.cwd())?.root ?? null, { force: true, preset });
|
|
727
769
|
return;
|
|
728
770
|
}
|
|
729
771
|
case "version": {
|
package/dist/relay.js
CHANGED
|
@@ -1774,7 +1774,7 @@ pantheon signup --email you@work.com --username you</code></pre>
|
|
|
1774
1774
|
message: `over ${LIMITS.rateActs} acts in ${LIMITS.rateWindowMs / 1000}s. This is a backstop against a stuck loop, not a hard stop — wait a few seconds and send again. If you are genuinely producing this much, you are probably doing work that belongs in a commit and a DELIVERED, not in acts.` };
|
|
1775
1775
|
}
|
|
1776
1776
|
for (const id of to ?? []) {
|
|
1777
|
-
if (!sp.members.has(id)) {
|
|
1777
|
+
if (!sp.members.has(id) && !sp.allow.get(id)?.admitted) {
|
|
1778
1778
|
return { code: "NO_SUCH_MEMBER", message: `nobody here is called "${id}" — members: ${[...sp.members.keys()].join(", ")}` };
|
|
1779
1779
|
}
|
|
1780
1780
|
}
|
|
@@ -2211,6 +2211,11 @@ pantheon signup --email you@work.com --username you</code></pre>
|
|
|
2211
2211
|
lastTouch: Date.now(),
|
|
2212
2212
|
};
|
|
2213
2213
|
this.spaces.set(name, sp);
|
|
2214
|
+
for (const e of allow.all()) {
|
|
2215
|
+
if (e.admitted && !sp.members.has(e.member)) {
|
|
2216
|
+
sp.members.set(e.member, { id: e.member, display: e.member, agent: "unknown", last_seen: e.first_seen, sessions: new Map(), activity: new Map(), delivered_seq: 0, stamps: [] });
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2214
2219
|
const open = [...sp.goals.values()].filter((g) => g.state !== "CLOSED").length;
|
|
2215
2220
|
this.log(`[${name}] space ready (seq ${sp.ledger.head}, ${sp.goals.size} goals — ${open} still open, ${sp.obligations.size} obligations restored)`);
|
|
2216
2221
|
this.compactSpace(sp);
|
package/dist/setup-guide.js
CHANGED
|
@@ -55,14 +55,95 @@ export function detectBaseBranch(project) {
|
|
|
55
55
|
const cur = git("rev-parse", "--abbrev-ref", "HEAD");
|
|
56
56
|
return cur && cur !== "HEAD" ? cur : null;
|
|
57
57
|
}
|
|
58
|
-
export
|
|
58
|
+
export function questionnaire() {
|
|
59
|
+
const opt = (flag, id, label) => ` ${label} → --${flag} ${id}`;
|
|
60
|
+
return [
|
|
61
|
+
"pantheon setup — the questions, and the flag for each answer.",
|
|
62
|
+
"Ask your human each one in plain words, then run: pantheon setup --yes <flags>",
|
|
63
|
+
"",
|
|
64
|
+
"1. While they're away, may teammates hand work to this machine? (their agent runs here on its own,",
|
|
65
|
+
" in a separate copy of the repo, and delivers on a branch for review)",
|
|
66
|
+
...WAKE_OPTIONS.map((o) => opt("wake", o.id, o.label)),
|
|
67
|
+
"2. When a teammate's request wakes this machine, what may the run do? (skip if --wake no)",
|
|
68
|
+
opt("commands", "no", "Edit files only — it can answer and draft, but cannot run tests, commit, or push."),
|
|
69
|
+
opt("commands", "yes", "Run commands too — needed to run tests and to commit and push work for review."),
|
|
70
|
+
"3. Their name, as teammates will see it. → --name \"…\"",
|
|
71
|
+
`4. Their role, in a word (${TITLE_OPTIONS.slice(0, -1).join(", ")}, or their own). → --title \"…\"`,
|
|
72
|
+
"5. Which of these do they decide personally? Agents will stop and ask them first. Any number, or none.",
|
|
73
|
+
...DECISION_AREAS.map((x) => ` ${x.label} → ${x.id}`),
|
|
74
|
+
" pass them comma-separated: --decides deployment,pricing (or --decides none)",
|
|
75
|
+
"6. Which desktop alerts do they want?",
|
|
76
|
+
...ALERT_OPTIONS.map((o) => opt("alerts", o.id, o.label)),
|
|
77
|
+
"7. Should their worker follow a short written playbook for this repo? (skip if --wake no)",
|
|
78
|
+
opt("playbook", "yes", "Yes — write a starter playbook now (recommended)"),
|
|
79
|
+
opt("playbook", "no", "Not now"),
|
|
80
|
+
"8. Which branch should workers start from, and deliveries be checked against? Usually the branch this",
|
|
81
|
+
" project deploys or releases from (often the default branch). → --base <branch>",
|
|
82
|
+
"",
|
|
83
|
+
"Example: pantheon setup --yes --wake anyone --commands yes --name \"Praneet Sinha\" --title Founder \\",
|
|
84
|
+
" --decides deployment --alerts decisions --playbook yes --base main",
|
|
85
|
+
].join("\n");
|
|
86
|
+
}
|
|
87
|
+
export function parseSetupFlags(argv) {
|
|
88
|
+
const preset = {};
|
|
89
|
+
const errors = [];
|
|
90
|
+
const val = (name) => { const i = argv.indexOf(`--${name}`); return i >= 0 ? argv[i + 1] : undefined; };
|
|
91
|
+
const yn = (name) => { const v = val(name); if (v === undefined)
|
|
92
|
+
return undefined; if (/^(yes|true|on|1)$/i.test(v))
|
|
93
|
+
return true; if (/^(no|false|off|0)$/i.test(v))
|
|
94
|
+
return false; errors.push(`--${name} takes yes or no, not "${v}"`); return undefined; };
|
|
95
|
+
const wake = val("wake");
|
|
96
|
+
if (wake !== undefined) {
|
|
97
|
+
if (WAKE_OPTIONS.some((o) => o.id === wake))
|
|
98
|
+
preset.wake = wake;
|
|
99
|
+
else
|
|
100
|
+
errors.push(`--wake takes ${WAKE_OPTIONS.map((o) => o.id).join(", ")}, not "${wake}"`);
|
|
101
|
+
}
|
|
102
|
+
const commands = yn("commands");
|
|
103
|
+
if (commands !== undefined)
|
|
104
|
+
preset.commands = commands;
|
|
105
|
+
const name = val("name");
|
|
106
|
+
if (name !== undefined)
|
|
107
|
+
preset.name = name.trim();
|
|
108
|
+
const title = val("title");
|
|
109
|
+
if (title !== undefined)
|
|
110
|
+
preset.title = title.trim();
|
|
111
|
+
const decides = val("decides");
|
|
112
|
+
if (decides !== undefined) {
|
|
113
|
+
const ids = decides.split(",").map((x) => x.trim().toLowerCase()).filter((x) => x && x !== "none");
|
|
114
|
+
const bad = ids.filter((id) => !DECISION_AREAS.some((x) => x.id === id));
|
|
115
|
+
if (bad.length)
|
|
116
|
+
errors.push(`--decides: unknown area${bad.length === 1 ? "" : "s"} ${bad.join(", ")} — use ${DECISION_AREAS.map((x) => x.id).join(", ")} or none`);
|
|
117
|
+
else
|
|
118
|
+
preset.decides = [...new Set(ids)];
|
|
119
|
+
}
|
|
120
|
+
const alerts = val("alerts");
|
|
121
|
+
if (alerts !== undefined) {
|
|
122
|
+
if (ALERT_OPTIONS.some((o) => o.id === alerts))
|
|
123
|
+
preset.notifications = alerts;
|
|
124
|
+
else
|
|
125
|
+
errors.push(`--alerts takes ${ALERT_OPTIONS.map((o) => o.id).join(", ")}, not "${alerts}"`);
|
|
126
|
+
}
|
|
127
|
+
const playbook = yn("playbook");
|
|
128
|
+
if (playbook !== undefined)
|
|
129
|
+
preset.persona = playbook;
|
|
130
|
+
const base = val("base");
|
|
131
|
+
if (base !== undefined && base.trim())
|
|
132
|
+
preset.base = base.trim();
|
|
133
|
+
return { preset, errors };
|
|
134
|
+
}
|
|
135
|
+
export async function runGuide(io, ctx, deps = {}, preset = {}) {
|
|
59
136
|
const install = deps.installService ?? realInstall;
|
|
60
137
|
const uninstall = deps.uninstallService ?? realUninstall;
|
|
61
138
|
const installed = deps.serviceInstalled ?? realInstalled;
|
|
62
139
|
const file = deps.profileFile ?? profilePath();
|
|
63
140
|
const profile = loadProfile(ctx.cfg, file);
|
|
64
|
-
const d = guideDefaults(profile, ctx.cfg);
|
|
141
|
+
const d = { ...guideDefaults(profile, ctx.cfg), ...preset };
|
|
65
142
|
const a = { ...d };
|
|
143
|
+
if (a.wake === "no") {
|
|
144
|
+
a.commands = false;
|
|
145
|
+
a.persona = false;
|
|
146
|
+
}
|
|
66
147
|
const applied = [], warnings = [];
|
|
67
148
|
if (io) {
|
|
68
149
|
io.say("A few choices only you can make. Pick a number; Enter keeps the suggested one.\n");
|
|
@@ -90,8 +171,9 @@ export async function runGuide(io, ctx, deps = {}) {
|
|
|
90
171
|
}
|
|
91
172
|
const detected = ctx.project && deps.setRepoBase ? detectBaseBranch(ctx.project) : null;
|
|
92
173
|
if (detected) {
|
|
93
|
-
const
|
|
94
|
-
|
|
174
|
+
const suggested = preset.base ?? detected;
|
|
175
|
+
const b = await io.choose("Which branch should your worker start from, and deliveries be checked against? Usually the branch this project deploys or releases from.", [`${suggested}${suggested === detected ? " (what origin calls its default)" : ""}`, "A different branch"], 0);
|
|
176
|
+
a.base = b === 0 ? suggested : ((await io.ask("Branch name", suggested)).trim() || suggested);
|
|
95
177
|
}
|
|
96
178
|
}
|
|
97
179
|
let next = a.title ? applyProfileInit(profile, { name: a.name, title: a.title }) : { ...profile, human: { ...profile.human, display_name: a.name } };
|
package/dist/worker.js
CHANGED
|
@@ -216,6 +216,43 @@ export class Worker {
|
|
|
216
216
|
this.cfg = cfg;
|
|
217
217
|
this.opts = opts;
|
|
218
218
|
}
|
|
219
|
+
async bringMachineAlong() {
|
|
220
|
+
if (this.opts.selfUpgrade?.run)
|
|
221
|
+
return;
|
|
222
|
+
try {
|
|
223
|
+
const { restartService, serviceInstalled } = await import("./service.js");
|
|
224
|
+
if (process.platform === "darwin") {
|
|
225
|
+
const { trayStale, ensureTrayBinary, writeTrayCommands } = await import("./tray.js");
|
|
226
|
+
const { notifierStale, ensureNativeNotifier } = await import("./notifier.js");
|
|
227
|
+
if (trayStale()) {
|
|
228
|
+
try {
|
|
229
|
+
ensureTrayBinary();
|
|
230
|
+
writeTrayCommands();
|
|
231
|
+
this.opts.log(" menu bar rebuilt from the new source");
|
|
232
|
+
}
|
|
233
|
+
catch (e) {
|
|
234
|
+
this.opts.log(` menu bar not rebuilt: ${e.message.split("\n")[0]}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (notifierStale()) {
|
|
238
|
+
try {
|
|
239
|
+
ensureNativeNotifier();
|
|
240
|
+
this.opts.log(" alerts helper rebuilt from the new source");
|
|
241
|
+
}
|
|
242
|
+
catch (e) {
|
|
243
|
+
this.opts.log(` alerts helper not rebuilt: ${e.message.split("\n")[0]}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
for (const kind of ["notifier", "menubar"])
|
|
248
|
+
if (serviceInstalled(kind))
|
|
249
|
+
for (const l of restartService(kind))
|
|
250
|
+
this.opts.log(l);
|
|
251
|
+
}
|
|
252
|
+
catch (e) {
|
|
253
|
+
this.opts.log(` could not bring the other services along: ${e.message.split("\n")[0]}`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
219
256
|
repoBase() {
|
|
220
257
|
return this.client?.repos.find((r) => r.fingerprint === this.cfg.repo)?.base ?? null;
|
|
221
258
|
}
|
|
@@ -661,6 +698,7 @@ export class Worker {
|
|
|
661
698
|
summary: `self-upgraded pantheon ${result.from} -> ${result.to}; worker restarting on the new build`,
|
|
662
699
|
body: `This machine's worker upgraded pantheon ${result.from} -> ${result.to} (fast-forward pull + rebuild, granted by its human) and is restarting on the new build.`,
|
|
663
700
|
}).catch((e) => this.opts.log(`! could not report the upgrade on the ledger: ${e.message}`));
|
|
701
|
+
await this.bringMachineAlong();
|
|
664
702
|
this.opts.log(`* self-upgrade done: ${result.from} -> ${result.to}; restarting worker to load it`);
|
|
665
703
|
(this.opts.selfUpgrade?.restart ?? (() => process.exit(0)))();
|
|
666
704
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@join-pantheon/cli",
|
|
3
|
-
"version": "0.21.
|
|
3
|
+
"version": "0.21.3",
|
|
4
4
|
"description": "Let your coding agent collaborate directly with your teammates' agents — ask, agree an interface, and deliver, without a human relaying messages.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|