@join-pantheon/cli 0.21.1 → 0.21.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/README.md +1 -1
- package/dist/cli.js +18 -5
- package/dist/relay.js +6 -1
- package/dist/setup-guide.js +86 -4
- 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
|
}
|
|
@@ -722,8 +728,15 @@ async function main() {
|
|
|
722
728
|
return;
|
|
723
729
|
}
|
|
724
730
|
case "setup": {
|
|
731
|
+
if (argv.includes("--questions")) {
|
|
732
|
+
console.log(questionnaire());
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
const { preset, errors } = parseSetupFlags(argv);
|
|
736
|
+
if (errors.length)
|
|
737
|
+
die(errors.join("\n") + "\n pantheon setup --questions lists every answer");
|
|
725
738
|
const cfg = need();
|
|
726
|
-
await guidedSetup(cfg, repoInfo(process.cwd())?.root ?? null, { force: true });
|
|
739
|
+
await guidedSetup(cfg, repoInfo(process.cwd())?.root ?? null, { force: true, preset });
|
|
727
740
|
return;
|
|
728
741
|
}
|
|
729
742
|
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@join-pantheon/cli",
|
|
3
|
-
"version": "0.21.
|
|
3
|
+
"version": "0.21.2",
|
|
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": {
|