create-egregore 0.21.0-runtime-mvp.4 → 0.21.0-runtime-mvp.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.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // egregore-launcher-version: 0.21.0-runtime-mvp.4
2
+ // egregore-launcher-version: 0.21.0-runtime-mvp.6
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.4";
38
+ const VERSION = "0.21.0-runtime-mvp.6";
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",
@@ -2576,6 +2624,26 @@ const UPGRADE_STATUS_LABEL = {
2576
2624
  "rolled-back": "rolled back",
2577
2625
  };
2578
2626
 
2627
+ // Settings-row truth for the Runtime update line. The row must say outright
2628
+ // whether this egregore runs the new Runtime — "no update staged" is
2629
+ // transaction state, not the condition the user needs to see.
2630
+ function runtimeUpdateRowValue(st, candidate) {
2631
+ if (st === undefined) return `${DIM}checking…${RESET}`;
2632
+ if (st === null) return `${DIM}unavailable${RESET}`;
2633
+ if (st.status === "active") {
2634
+ const v = st.active_version || "?";
2635
+ if (candidate && candidate !== v) return `${v} active ${YELLOW}· update available ${candidate}${RESET}`;
2636
+ return `${GREEN}${v} active${RESET}${candidate ? `${DIM} · up to date${RESET}` : ""}`;
2637
+ }
2638
+ if (st.status === "ready-to-activate") return `${GREEN}${upgradeSummary(st)}${RESET}`;
2639
+ if (st.status === "failed" || st.status === "interrupted") return `${YELLOW}${upgradeSummary(st)}${RESET}`;
2640
+ if (st.status === "preparing" || st.status === "pending") return `${DIM}${upgradeSummary(st)}${RESET}`;
2641
+ // No activation record (or cancelled/rolled back): this egregore is not on
2642
+ // the new Runtime. Say so, and name the candidate when we know it.
2643
+ const base = `${YELLOW}not updated to the new Runtime${RESET}`;
2644
+ return candidate ? `${base}${DIM} · ${candidate} available${RESET}` : base;
2645
+ }
2646
+
2579
2647
  function upgradeSummary(st) {
2580
2648
  // "none" means no activation record — a first-time state, not freshness.
2581
2649
  if (!st || st.status === "none") return "no update staged";
@@ -3086,7 +3154,7 @@ function renderUpgradePanel({ inst, data, candidate, retrieval, mode, notice, wi
3086
3154
  return lines;
3087
3155
  }
3088
3156
 
3089
- function renderSettingsPanel({ inst, data, cursor, view, domain, items, listCursor, mode, input, notice, width, upgrade, retrieval }) {
3157
+ function renderSettingsPanel({ inst, data, cursor, view, domain, items, listCursor, mode, input, notice, width, upgrade, retrieval, candidate }) {
3090
3158
  const title = inst.slug || inst.name || path.basename(inst.path);
3091
3159
  const outerWidth = Math.min(Math.max(50, width - 2), 126);
3092
3160
  const innerWidth = Math.max(48, outerWidth - 2);
@@ -3231,15 +3299,7 @@ function renderSettingsPanel({ inst, data, cursor, view, domain, items, listCurs
3231
3299
  lines.push(boxLine(`${DIM}↑↓ nav · ${RESET}${BOLD}a${RESET}${DIM} add · ${RESET}${BOLD}d${RESET}${DIM} delete · ← back · q quit${RESET}`, innerWidth, margin));
3232
3300
  }
3233
3301
  } else {
3234
- const upgradeValue = upgrade === undefined
3235
- ? `${DIM}checking…${RESET}`
3236
- : upgrade === null
3237
- ? `${DIM}unavailable${RESET}`
3238
- : upgrade.status === "ready-to-activate"
3239
- ? `${GREEN}${upgradeSummary(upgrade)}${RESET}`
3240
- : upgrade.status === "failed" || upgrade.status === "interrupted"
3241
- ? `${YELLOW}${upgradeSummary(upgrade)}${RESET}`
3242
- : `${DIM}${upgradeSummary(upgrade)}${RESET}`;
3302
+ const upgradeValue = runtimeUpdateRowValue(upgrade, candidate);
3243
3303
  const rows = [
3244
3304
  {
3245
3305
  name: "Runtime update",
@@ -3427,6 +3487,7 @@ async function browser(args = {}) {
3427
3487
  width: w,
3428
3488
  upgrade: state.settingsUpgrade,
3429
3489
  retrieval: state.settingsRetrieval,
3490
+ candidate: state.upgradeCandidate,
3430
3491
  });
3431
3492
  for (const line of settingsLines) out.push(line + ANSI.clearLine + "\n");
3432
3493
  const remainingRows = Math.max(0, h - settingsLines.length - 1);
@@ -3771,8 +3832,36 @@ async function browser(args = {}) {
3771
3832
  && !["none", "cancelled", "rolled-back"].includes(status)
3772
3833
  && state.upgradeData.candidate_version;
3773
3834
  if (!staged) {
3774
- state.upgradeNotice = `${DIM}Stage a candidate package first: egregore settings ${state.upgradeInst.slug} runtime init <version> <candidate.tgz>${RESET}`;
3775
- render(); return;
3835
+ const candidate = state.upgradeCandidate;
3836
+ if (!candidate) {
3837
+ state.upgradeNotice = `${DIM}No published candidate found. Stage one manually: egregore settings ${state.upgradeInst.slug} runtime init --candidate <version> --tarball <candidate.tgz>${RESET}`;
3838
+ render(); return;
3839
+ }
3840
+ if (state.upgradeFetching) { render(); return; }
3841
+ state.upgradeFetching = true;
3842
+ const inst = state.upgradeInst;
3843
+ state.upgradeNotice = `${DIM}Downloading ${candidate} from npm…${RESET}`;
3844
+ render();
3845
+ fetchCandidateTarball(candidate, (err, tgz) => {
3846
+ state.upgradeFetching = false;
3847
+ if (!state.upgradeInst || state.upgradeInst !== inst) return;
3848
+ if (err) {
3849
+ state.upgradeNotice = `${YELLOW}${err.message}${RESET}`;
3850
+ render(); return;
3851
+ }
3852
+ const r = runUpgrade(inst, ["init", "--candidate", candidate, "--tarball", tgz]);
3853
+ if (r === null || r.status !== 0) {
3854
+ const detail = ((r && (r.stderr || r.stdout)) || "staging failed").trim().split("\n")[0];
3855
+ state.upgradeNotice = `${YELLOW}${detail}${RESET}`;
3856
+ render(); return;
3857
+ }
3858
+ refreshUpgrade();
3859
+ state.upgradeData = upgradeStatus(inst) || state.upgradeData;
3860
+ state.upgradeNotice = "";
3861
+ state.upgradeMode = "confirm-prepare";
3862
+ render();
3863
+ });
3864
+ return;
3776
3865
  }
3777
3866
  state.upgradeMode = "confirm-prepare"; render(); return;
3778
3867
  }
@@ -4256,6 +4345,12 @@ async function browser(args = {}) {
4256
4345
  state.settingsRetrieval = health;
4257
4346
  if (state.settingsInst === pick && !state.upgradeInst) render();
4258
4347
  });
4348
+ if (!state.upgradeCandidate) {
4349
+ fetchRuntimeMvpCandidate().then((v) => {
4350
+ state.upgradeCandidate = v || "";
4351
+ if (state.settingsInst === pick && !state.upgradeInst) render();
4352
+ });
4353
+ }
4259
4354
  state.settingsNotice = "";
4260
4355
  render();
4261
4356
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-egregore",
3
- "version": "0.21.0-runtime-mvp.4",
3
+ "version": "0.21.0-runtime-mvp.6",
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": {