create-egregore 0.21.0-runtime-mvp.5 → 0.21.0-runtime-mvp.7

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.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // egregore-launcher-version: 0.21.0-runtime-mvp.5
2
+ // egregore-launcher-version: 0.21.0-runtime-mvp.7
3
3
  //
4
4
  // egregore — launch and browse Egregore instances from your terminal.
5
5
  // Installed by create-egregore. Reads ~/.egregore/instances.json.
@@ -35,7 +35,7 @@ const crypto = require("node:crypto");
35
35
  const { spawnSync, spawn } = require("node:child_process");
36
36
  const readline = require("node:readline");
37
37
 
38
- const VERSION = "0.21.0-runtime-mvp.5";
38
+ const VERSION = "0.21.0-runtime-mvp.7";
39
39
  const REGISTRY = path.join(os.homedir(), ".egregore", "instances.json");
40
40
  const DEFAULT_FILE = path.join(os.homedir(), ".egregore", "default");
41
41
  const UPDATE_CACHE_FILE = path.join(os.homedir(), ".egregore", "update-check.json");
@@ -2564,6 +2564,54 @@ async function fetchRuntimeMvpCandidate() {
2564
2564
  }
2565
2565
  }
2566
2566
 
2567
+ // Fetch the published candidate tarball from the npm registry and verify its
2568
+ // integrity before staging. The first-timer path: `p prepare` with nothing
2569
+ // staged must not dead-end into a CLI instruction referencing a tarball the
2570
+ // user does not have — the registry has it, digest-verified.
2571
+ function fetchCandidateTarball(version, cb) {
2572
+ const metaUrl = new URL(`https://registry.npmjs.org/${PUBLIC_NPX_PACKAGE}/${version}`);
2573
+ requestJson({
2574
+ hostname: metaUrl.hostname,
2575
+ path: metaUrl.pathname,
2576
+ method: "GET",
2577
+ protocol: metaUrl.protocol,
2578
+ timeoutMs: 10000,
2579
+ headers: { Accept: "application/json" },
2580
+ }).then((meta) => {
2581
+ const dist = meta && meta.status === 200 && meta.data && meta.data.dist;
2582
+ if (!dist || !dist.tarball) return cb(new Error(`candidate ${version} not found on the registry`));
2583
+ const dest = path.join(os.tmpdir(), `egregore-candidate-${version}.tgz`);
2584
+ const fetchTarball = (url, redirects) => {
2585
+ https.get(url, { headers: { "User-Agent": "egregore-launcher" } }, (res) => {
2586
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location && redirects > 0) {
2587
+ res.resume();
2588
+ return fetchTarball(res.headers.location, redirects - 1);
2589
+ }
2590
+ if (res.statusCode !== 200) {
2591
+ res.resume();
2592
+ return cb(new Error(`tarball download failed (HTTP ${res.statusCode})`));
2593
+ }
2594
+ const hash = crypto.createHash("sha512");
2595
+ const out = fs.createWriteStream(dest);
2596
+ res.on("data", (d) => hash.update(d));
2597
+ res.pipe(out);
2598
+ out.on("finish", () => {
2599
+ out.close(() => {
2600
+ const digest = `sha512-${hash.digest("base64")}`;
2601
+ if (dist.integrity && digest !== dist.integrity) {
2602
+ fs.rmSync(dest, { force: true });
2603
+ return cb(new Error("candidate integrity mismatch — refusing to stage"));
2604
+ }
2605
+ cb(null, dest);
2606
+ });
2607
+ });
2608
+ out.on("error", (err) => cb(err));
2609
+ }).on("error", (err) => cb(err));
2610
+ };
2611
+ fetchTarball(dist.tarball, 3);
2612
+ }).catch((err) => cb(err));
2613
+ }
2614
+
2567
2615
  const UPGRADE_STATUS_LABEL = {
2568
2616
  none: "no update staged",
2569
2617
  pending: "staged, not started",
@@ -3121,6 +3169,40 @@ function renderSettingsPanel({ inst, data, cursor, view, domain, items, listCurs
3121
3169
  emptyBoxLine(innerWidth, margin),
3122
3170
  ];
3123
3171
 
3172
+ if (view === "workflow") {
3173
+ const current = (d.workflow === "simple" || d.workflow === "staged") ? d.workflow : "custom";
3174
+ lines.push(boxLine(`${BOLD}Git workflow${RESET}`, innerWidth, margin));
3175
+ lines.push(emptyBoxLine(innerWidth, margin));
3176
+ const options = [
3177
+ { key: "simple", name: "Simple", detail: "pull requests target main — one branch, no release staging" },
3178
+ { key: "staged", name: "Staged", detail: "pull requests target develop; releases promote to main" },
3179
+ ];
3180
+ options.forEach((option, i) => {
3181
+ const sel = i === listCursor;
3182
+ const marker = sel ? `${CYAN}❯${RESET}` : " ";
3183
+ const active = option.key === current ? ` ${GREEN}● current${RESET}` : "";
3184
+ lines.push(boxLine(`${marker} ${sel ? BOLD : ""}${option.name}${RESET}${active}`, innerWidth, margin));
3185
+ lines.push(boxLine(` ${DIM}${option.detail}${RESET}`, innerWidth, margin));
3186
+ });
3187
+ if (current === "custom") {
3188
+ lines.push(emptyBoxLine(innerWidth, margin));
3189
+ lines.push(boxLine(`${YELLOW}● current: CUSTOM${RESET} ${DIM}— pull requests target ${d.base_branch || "?"}${RESET}`, innerWidth, margin));
3190
+ lines.push(boxLine(` ${DIM}selecting an option above replaces this for the whole org${RESET}`, innerWidth, margin));
3191
+ }
3192
+ lines.push(emptyBoxLine(innerWidth, margin));
3193
+ if (mode === "confirm") {
3194
+ const target = listCursor === 0 ? "SIMPLE — pull requests target main" : "STAGED — pull requests target develop";
3195
+ lines.push(boxLine(`${YELLOW}Switch ${BOLD}${title}${RESET}${YELLOW} to ${target}?${RESET}`, innerWidth, margin));
3196
+ lines.push(boxLine(`${DIM}This edits the org's committed egregore.json for everyone.${RESET} ${DIM}y / N${RESET}`, innerWidth, margin));
3197
+ } else {
3198
+ if (notice) lines.push(boxLine(notice, innerWidth, margin));
3199
+ lines.push(boxLine(`${DIM}↑↓ nav · ${RESET}${BOLD}enter${RESET}${DIM} select · ← back · q quit${RESET}`, innerWidth, margin));
3200
+ }
3201
+ lines.push(emptyBoxLine(innerWidth, margin));
3202
+ lines.push(`${margin}${DIM}└${"─".repeat(innerWidth)}┘${RESET}`);
3203
+ return lines;
3204
+ }
3205
+
3124
3206
  if (view === "privacy" || view === "telemetry") {
3125
3207
  lines.push(boxLine(`${BOLD}${view === "privacy" ? "Privacy & access" : "Telemetry"}${RESET}`, innerWidth, margin));
3126
3208
  lines.push(emptyBoxLine(innerWidth, margin));
@@ -3282,7 +3364,7 @@ function renderSettingsPanel({ inst, data, cursor, view, domain, items, listCurs
3282
3364
  });
3283
3365
  lines.push(emptyBoxLine(innerWidth, margin));
3284
3366
  if (notice) lines.push(boxLine(notice, innerWidth, margin));
3285
- lines.push(boxLine(`${DIM}↑↓ nav · ${RESET}${BOLD}enter${RESET}${DIM} ${cursor === 3 || cursor === 4 ? "toggle" : "open"} · ← back · q quit${RESET}`, innerWidth, margin));
3367
+ lines.push(boxLine(`${DIM}↑↓ nav · ${RESET}${BOLD}enter${RESET}${DIM} ${cursor === 3 ? "toggle" : "open"} · ← back · q quit${RESET}`, innerWidth, margin));
3286
3368
  }
3287
3369
 
3288
3370
  lines.push(emptyBoxLine(innerWidth, margin));
@@ -3784,8 +3866,36 @@ async function browser(args = {}) {
3784
3866
  && !["none", "cancelled", "rolled-back"].includes(status)
3785
3867
  && state.upgradeData.candidate_version;
3786
3868
  if (!staged) {
3787
- state.upgradeNotice = `${DIM}Stage a candidate package first: egregore settings ${state.upgradeInst.slug} runtime init <version> <candidate.tgz>${RESET}`;
3788
- render(); return;
3869
+ const candidate = state.upgradeCandidate;
3870
+ if (!candidate) {
3871
+ state.upgradeNotice = `${DIM}No published candidate found. Stage one manually: egregore settings ${state.upgradeInst.slug} runtime init --candidate <version> --tarball <candidate.tgz>${RESET}`;
3872
+ render(); return;
3873
+ }
3874
+ if (state.upgradeFetching) { render(); return; }
3875
+ state.upgradeFetching = true;
3876
+ const inst = state.upgradeInst;
3877
+ state.upgradeNotice = `${DIM}Downloading ${candidate} from npm…${RESET}`;
3878
+ render();
3879
+ fetchCandidateTarball(candidate, (err, tgz) => {
3880
+ state.upgradeFetching = false;
3881
+ if (!state.upgradeInst || state.upgradeInst !== inst) return;
3882
+ if (err) {
3883
+ state.upgradeNotice = `${YELLOW}${err.message}${RESET}`;
3884
+ render(); return;
3885
+ }
3886
+ const r = runUpgrade(inst, ["init", "--candidate", candidate, "--tarball", tgz]);
3887
+ if (r === null || r.status !== 0) {
3888
+ const detail = ((r && (r.stderr || r.stdout)) || "staging failed").trim().split("\n")[0];
3889
+ state.upgradeNotice = `${YELLOW}${detail}${RESET}`;
3890
+ render(); return;
3891
+ }
3892
+ refreshUpgrade();
3893
+ state.upgradeData = upgradeStatus(inst) || state.upgradeData;
3894
+ state.upgradeNotice = "";
3895
+ state.upgradeMode = "confirm-prepare";
3896
+ render();
3897
+ });
3898
+ return;
3789
3899
  }
3790
3900
  state.upgradeMode = "confirm-prepare"; render(); return;
3791
3901
  }
@@ -3800,6 +3910,36 @@ async function browser(args = {}) {
3800
3910
  if (key.ctrl && key.name === "c") { cleanup(); process.exit(130); }
3801
3911
  if (key.name === "q") { cleanup(); resolve(); return; }
3802
3912
 
3913
+ if (S.settingsView === "workflow") {
3914
+ if (S.settingsMode === "confirm") {
3915
+ if (chunk === "y" || chunk === "Y") {
3916
+ settingsRunVerb(["workflow", S.settingsListCursor === 0 ? "simple" : "staged"]);
3917
+ S.settingsMode = "browse";
3918
+ S.settingsView = "menu";
3919
+ render(); return;
3920
+ }
3921
+ if (key.name === "escape" || chunk === "n" || chunk === "N" || key.name === "return") {
3922
+ S.settingsMode = "browse"; render(); return;
3923
+ }
3924
+ return;
3925
+ }
3926
+ if (key.name === "escape" || key.name === "left") {
3927
+ S.settingsView = "menu"; S.settingsNotice = ""; render(); return;
3928
+ }
3929
+ if (key.name === "up" || key.name === "k") { S.settingsListCursor = (S.settingsListCursor + 1) % 2; render(); return; }
3930
+ if (key.name === "down" || key.name === "j") { S.settingsListCursor = (S.settingsListCursor + 1) % 2; render(); return; }
3931
+ if (key.name === "return") {
3932
+ const chosen = S.settingsListCursor === 0 ? "simple" : "staged";
3933
+ const current = (S.settingsData && S.settingsData.workflow) || "custom";
3934
+ if (chosen === current) {
3935
+ S.settingsNotice = `${DIM}already on ${chosen.toUpperCase()}${RESET}`;
3936
+ render(); return;
3937
+ }
3938
+ S.settingsMode = "confirm"; render(); return;
3939
+ }
3940
+ return;
3941
+ }
3942
+
3803
3943
  if (S.settingsView === "privacy" || S.settingsView === "telemetry") {
3804
3944
  const backToMenu = () => {
3805
3945
  S.settingsView = "menu"; S.settingsMode = "browse"; S.settingsInput = ""; S.settingsNotice = ""; render();
@@ -3996,8 +4136,9 @@ async function browser(args = {}) {
3996
4136
  settingsRunVerb(["hosting", on ? "off" : "on"]);
3997
4137
  render();
3998
4138
  } else if (S.settingsCursor === 4) {
3999
- const simple = (S.settingsData && S.settingsData.workflow) === "simple";
4000
- settingsRunVerb(["workflow", simple ? "staged" : "simple"]);
4139
+ S.settingsView = "workflow"; S.settingsMode = "browse";
4140
+ S.settingsListCursor = (S.settingsData && S.settingsData.workflow) === "simple" ? 0 : 1;
4141
+ S.settingsNotice = "";
4001
4142
  render();
4002
4143
  } else {
4003
4144
  const domain = S.settingsCursor === 5 ? "repo" : S.settingsCursor === 6 ? "admin" : "people";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-egregore",
3
- "version": "0.21.0-runtime-mvp.5",
3
+ "version": "0.21.0-runtime-mvp.7",
4
4
  "description": "Set up Egregore for your team in one command — installs the egregore launcher and your choice of Claude Code, Codex, or Pi.",
5
5
  "license": "MIT",
6
6
  "bin": {