create-egregore 0.19.12 → 0.19.13

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.8.0
2
+ // egregore-launcher-version: 0.19.13
3
3
  //
4
4
  // egregore — launch and browse Egregore instances from your terminal.
5
5
  // Installed by create-egregore. Reads ~/.egregore/instances.json.
@@ -33,10 +33,17 @@ const crypto = require("node:crypto");
33
33
  const { spawnSync, spawn } = require("node:child_process");
34
34
  const readline = require("node:readline");
35
35
 
36
- const VERSION = "0.8.0";
36
+ const VERSION = "0.19.13";
37
37
  const REGISTRY = path.join(os.homedir(), ".egregore", "instances.json");
38
38
  const DEFAULT_FILE = path.join(os.homedir(), ".egregore", "default");
39
+ const UPDATE_CACHE_FILE = path.join(os.homedir(), ".egregore", "update-check.json");
39
40
  const PUBLIC_NPX_PACKAGE = "create-egregore";
41
+ const UPDATE_REGISTRY_URL =
42
+ process.env.EGREGORE_UPDATE_REGISTRY_URL ||
43
+ `https://registry.npmjs.org/${PUBLIC_NPX_PACKAGE}/latest`;
44
+ const UPDATE_CACHE_TTL_MS = 6 * 60 * 60 * 1000;
45
+ const UPDATE_FAILURE_TTL_MS = 30 * 60 * 1000;
46
+ const UPDATE_CHECK_TIMEOUT_MS = 1500;
40
47
  const CONNECT_API_URL = (
41
48
  process.env.EGREGORE_CONNECT_API_URL ||
42
49
  "https://egregore-production-55f2.up.railway.app"
@@ -976,11 +983,145 @@ function requestJson(options, body) {
976
983
  });
977
984
  });
978
985
  req.on("error", (err) => resolve({ status: 0, data: { message: err.message } }));
986
+ if (options.unref) {
987
+ req.on("socket", (socket) => socket.unref());
988
+ }
989
+ if (options.timeoutMs) {
990
+ req.setTimeout(options.timeoutMs, () => req.destroy(new Error("request timed out")));
991
+ }
979
992
  if (payload) req.write(payload);
980
993
  req.end();
981
994
  });
982
995
  }
983
996
 
997
+ function parseReleaseVersion(value) {
998
+ const match = String(value || "").trim().match(/^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/);
999
+ if (!match) return null;
1000
+ return {
1001
+ parts: match.slice(1, 4).map(Number),
1002
+ prerelease: match[4] || "",
1003
+ };
1004
+ }
1005
+
1006
+ function compareReleaseVersions(a, b) {
1007
+ const left = parseReleaseVersion(a);
1008
+ const right = parseReleaseVersion(b);
1009
+ if (!left || !right) return 0;
1010
+ for (let i = 0; i < 3; i++) {
1011
+ if (left.parts[i] !== right.parts[i]) return left.parts[i] > right.parts[i] ? 1 : -1;
1012
+ }
1013
+ if (left.prerelease === right.prerelease) return 0;
1014
+ if (!left.prerelease) return 1;
1015
+ if (!right.prerelease) return -1;
1016
+ return left.prerelease.localeCompare(right.prerelease);
1017
+ }
1018
+
1019
+ function cliUpdateState(latest) {
1020
+ if (!parseReleaseVersion(latest)) return null;
1021
+ return {
1022
+ current: VERSION,
1023
+ latest,
1024
+ available: compareReleaseVersions(latest, VERSION) > 0,
1025
+ };
1026
+ }
1027
+
1028
+ function readUpdateCache() {
1029
+ try {
1030
+ const cache = JSON.parse(fs.readFileSync(UPDATE_CACHE_FILE, "utf-8"));
1031
+ if (!cache || !cache.checked_at) return null;
1032
+ const age = Date.now() - new Date(cache.checked_at).getTime();
1033
+ const ttl = cache.latest ? UPDATE_CACHE_TTL_MS : UPDATE_FAILURE_TTL_MS;
1034
+ if (!Number.isFinite(age) || age < 0 || age > ttl) return null;
1035
+ if (!cache.latest) return { current: VERSION, latest: null, available: false };
1036
+ return cliUpdateState(cache.latest);
1037
+ } catch {
1038
+ return null;
1039
+ }
1040
+ }
1041
+
1042
+ function writeUpdateCache(latest) {
1043
+ try {
1044
+ fs.mkdirSync(path.dirname(UPDATE_CACHE_FILE), { recursive: true });
1045
+ atomicWrite(
1046
+ UPDATE_CACHE_FILE,
1047
+ JSON.stringify({ checked_at: new Date().toISOString(), latest: latest || null }, null, 2) + "\n",
1048
+ 0o600,
1049
+ );
1050
+ } catch {}
1051
+ }
1052
+
1053
+ async function checkForCliUpdate() {
1054
+ if (process.env.EGREGORE_DISABLE_UPDATE_CHECK === "1") return null;
1055
+ if (process.env.EGREGORE_UPDATE_LATEST_VERSION) {
1056
+ return cliUpdateState(process.env.EGREGORE_UPDATE_LATEST_VERSION);
1057
+ }
1058
+
1059
+ const cached = readUpdateCache();
1060
+ if (cached) return cached;
1061
+
1062
+ try {
1063
+ const url = new URL(UPDATE_REGISTRY_URL);
1064
+ const result = await requestJson({
1065
+ hostname: url.hostname,
1066
+ port: url.port || undefined,
1067
+ path: `${url.pathname}${url.search}`,
1068
+ method: "GET",
1069
+ protocol: url.protocol,
1070
+ timeoutMs: UPDATE_CHECK_TIMEOUT_MS,
1071
+ unref: true,
1072
+ headers: { Accept: "application/json" },
1073
+ });
1074
+ const latest = result.status === 200 && result.data && result.data.version;
1075
+ const update = cliUpdateState(latest);
1076
+ writeUpdateCache(update?.latest || null);
1077
+ return update;
1078
+ } catch {
1079
+ return null;
1080
+ }
1081
+ }
1082
+
1083
+ function cmdCliUpdate(latest) {
1084
+ if (!parseReleaseVersion(latest)) {
1085
+ console.error(`\n ${YELLOW}!${RESET} Could not verify the CLI release version.\n`);
1086
+ process.exitCode = 1;
1087
+ return;
1088
+ }
1089
+
1090
+ console.log("");
1091
+ console.log(` ${BOLD}${CYAN}↑ CLI UPDATE${RESET}`);
1092
+ console.log(` ${DIM}${VERSION} → ${latest}${RESET}`);
1093
+ console.log("");
1094
+ process.stdout.write(` [1/2] Installing ${PUBLIC_NPX_PACKAGE}@${latest}…`);
1095
+ const result = spawnSync(
1096
+ "npx",
1097
+ ["-y", `${PUBLIC_NPX_PACKAGE}@${latest}`, "--refresh-launcher"],
1098
+ { encoding: "utf-8", stdio: ["inherit", "pipe", "pipe"], env: { ...process.env } },
1099
+ );
1100
+ if (result.status !== 0) {
1101
+ console.log(`\r [1/2] ${YELLOW}!${RESET} Install failed`);
1102
+ const detail = String(result.stderr || result.stdout || "").trim().split("\n").filter(Boolean).pop();
1103
+ if (detail) console.error(` ${DIM}${detail}${RESET}`);
1104
+ console.error(` Retry: npx -y ${PUBLIC_NPX_PACKAGE}@${latest} --refresh-launcher\n`);
1105
+ process.exitCode = 1;
1106
+ return;
1107
+ }
1108
+ console.log(`\r [1/2] ${GREEN}✓${RESET} CLI ${latest} installed`);
1109
+
1110
+ if (process.env.EGREGORE_UPDATE_NO_RELAUNCH === "1") {
1111
+ console.log(` [2/2] ${GREEN}✓${RESET} Ready for the next launch\n`);
1112
+ return;
1113
+ }
1114
+
1115
+ console.log(` [2/2] ${GREEN}✓${RESET} Reopening Egregore\n`);
1116
+ const relaunched = spawnSync(process.execPath, [process.argv[1], "browse"], {
1117
+ stdio: "inherit",
1118
+ env: { ...process.env },
1119
+ });
1120
+ if (relaunched.error) {
1121
+ console.error(` ${YELLOW}!${RESET} Updated successfully. Run ${BOLD}egregore${RESET} to reopen.\n`);
1122
+ }
1123
+ }
1124
+
984
1125
  function githubRequest(token, method, apiPath, body) {
985
1126
  return requestJson({
986
1127
  hostname: "api.github.com",
@@ -1689,7 +1830,7 @@ function selectedWorkflowLine(pv, width) {
1689
1830
  return joinShort(available.map(skillToken), Math.max(12, width));
1690
1831
  }
1691
1832
 
1692
- function renderHeroPanel({ selected, pv, width, height, countSuffix, filter, filterMode }) {
1833
+ function renderHeroPanel({ selected, pv, width, height, countSuffix, filter, filterMode, cliUpdate }) {
1693
1834
  const outerWidth = Math.min(Math.max(50, width - 2), 126);
1694
1835
  const innerWidth = Math.max(48, outerWidth - 2);
1695
1836
  const bodyWidth = Math.max(8, innerWidth - 2);
@@ -1741,7 +1882,17 @@ function renderHeroPanel({ selected, pv, width, height, countSuffix, filter, fil
1741
1882
  lines.push(mid);
1742
1883
  lines.push(boxLine(`${BOLD}${title}${RESET} ${DIM}· ${org} · ${runtime} · ${countSuffix} · ${filterText}${RESET}`, innerWidth, margin));
1743
1884
  lines.push(boxLine(`${DIM}skills${RESET} ${skillStatus}`, innerWidth, margin));
1744
- lines.push(emptyBoxLine(innerWidth, margin));
1885
+ if (cliUpdate?.available) {
1886
+ lines.push(boxLine(
1887
+ `${YELLOW}→ CLI update ${cliUpdate.latest} available${RESET} ${DIM}· installed ${cliUpdate.current} · ${RESET}${BOLD}U${RESET}${DIM} install${RESET}`,
1888
+ innerWidth,
1889
+ margin,
1890
+ ));
1891
+ } else {
1892
+ // Reserve the status row so the list never jumps when the async version
1893
+ // check resolves and reveals an available release.
1894
+ lines.push(emptyBoxLine(innerWidth, margin));
1895
+ }
1745
1896
 
1746
1897
  const sigilWidth = Math.max(...EGREGORE_SIGIL.map((line) => line.length));
1747
1898
  const twoColumn = bodyWidth >= 68;
@@ -1938,6 +2089,7 @@ async function browser(args = {}) {
1938
2089
  message: null, // ephemeral info line (e.g. "default set to X")
1939
2090
  messageKind: "info", // "info" | "success" | "warn"
1940
2091
  messageUntil: 0,
2092
+ cliUpdate: null,
1941
2093
  };
1942
2094
 
1943
2095
  const newAction = {
@@ -2041,6 +2193,7 @@ async function browser(args = {}) {
2041
2193
  countSuffix,
2042
2194
  filter: state.filter,
2043
2195
  filterMode: state.filterMode,
2196
+ cliUpdate: state.cliUpdate,
2044
2197
  });
2045
2198
  for (const line of heroLines) out.push(line + ANSI.clearLine + "\n");
2046
2199
  out.push(ANSI.clearLine + "\n");
@@ -2571,6 +2724,12 @@ async function browser(args = {}) {
2571
2724
  resolve(cmdConnect([pick.slug]));
2572
2725
  return;
2573
2726
  }
2727
+ if (chunk === "U" && state.cliUpdate?.available) {
2728
+ const latest = state.cliUpdate.latest;
2729
+ cleanup();
2730
+ resolve(cmdCliUpdate(latest));
2731
+ return;
2732
+ }
2574
2733
  if (chunk === "i") {
2575
2734
  const pick = visible()[state.cursor]?.e;
2576
2735
  if (!pick) return;
@@ -2657,6 +2816,11 @@ async function browser(args = {}) {
2657
2816
  });
2658
2817
 
2659
2818
  render();
2819
+ checkForCliUpdate().then((update) => {
2820
+ if (done || !update) return;
2821
+ state.cliUpdate = update;
2822
+ if (update.available) render();
2823
+ });
2660
2824
  });
2661
2825
  }
2662
2826
 
package/bin/cli.js CHANGED
@@ -223,6 +223,8 @@ function parseArgs(argv) {
223
223
  // already served as the entry screen — a second alt-screen welcome
224
224
  // would otherwise demand a redundant Enter and churn the alt buffer.
225
225
  args.noWelcome = true;
226
+ } else if (argv[i] === "--refresh-launcher") {
227
+ args.refreshLauncher = true;
226
228
  } else if (argv[i] === "--codex") {
227
229
  args.runtime = "codex";
228
230
  } else if (argv[i] === "--pi") {
@@ -395,6 +397,7 @@ function showHelp() {
395
397
  ui.info(" --force Skip the in-repo detection check");
396
398
  ui.info(" --no-autolaunch Don't launch the harness at the end (for CI)");
397
399
  ui.info(" --no-screens Skip the full-screen welcome/ready bookends");
400
+ ui.info(" --refresh-launcher Refresh only the Egregore CLI, then exit");
398
401
  ui.info(" -h, --help Show this help");
399
402
  ui.info("");
400
403
  }
@@ -443,6 +446,21 @@ async function main() {
443
446
  }
444
447
  }
445
448
 
449
+ if (args.refreshLauncher) {
450
+ const instances = readLocalInstances();
451
+ if (instances.length === 0) {
452
+ ui.error("No installed Egregore instance was found. Run create-egregore without --refresh-launcher.");
453
+ process.exit(1);
454
+ }
455
+ const alias = await installLauncherBinary(instances[0].path, ui);
456
+ if (!alias || !alias.binaryPath) {
457
+ ui.error("Could not refresh the Egregore CLI.");
458
+ process.exit(1);
459
+ }
460
+ ui.success(`Egregore CLI is current at ${alias.binaryPath}`);
461
+ return;
462
+ }
463
+
446
464
  // Local-first product surface: if the user already has egregores installed
447
465
  // and didn't pass an explicit mode flag, refresh the launcher and open the
448
466
  // same TUI as bare `egregore`. With no installed instances, make bare
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-egregore",
3
- "version": "0.19.12",
3
+ "version": "0.19.13",
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": {
@@ -12,7 +12,7 @@
12
12
  "scripts": {
13
13
  "build:runtime": "node scripts/build-runtime-bundle.mjs && node scripts/build-pi-runtime-bundle.mjs",
14
14
  "build:pi-runtime": "node scripts/build-pi-runtime-bundle.mjs",
15
- "test": "node scripts/build-runtime-bundle.mjs --check && node scripts/build-pi-runtime-bundle.mjs --check && node --test test/context.test.js test/parse-join-target.test.js test/skip-repos.test.js test/onboarding-shortcircuit.test.js test/welcome-screen.test.js test/runtime-selection.test.js test/command-generation.test.js test/auth-gh.test.js test/gauntlet-fixes.test.js test/codex-runtime.test.js test/pi-runtime.test.js test/pi-product-workflows.test.js test/installer-runtime-source.test.js test/scroll-repro.test.js test/launcher-refocus-redraw.test.js test/multiselect-remote-search.test.js test/back-navigation.test.js test/manifest-form.test.js test/launcher-delete.test.js test/launcher-connect.test.js",
15
+ "test": "node scripts/build-runtime-bundle.mjs --check && node scripts/build-pi-runtime-bundle.mjs --check && node --test test/context.test.js test/parse-join-target.test.js test/skip-repos.test.js test/onboarding-shortcircuit.test.js test/welcome-screen.test.js test/runtime-selection.test.js test/command-generation.test.js test/auth-gh.test.js test/gauntlet-fixes.test.js test/codex-runtime.test.js test/pi-runtime.test.js test/pi-product-workflows.test.js test/installer-runtime-source.test.js test/scroll-repro.test.js test/launcher-refocus-redraw.test.js test/multiselect-remote-search.test.js test/back-navigation.test.js test/manifest-form.test.js test/launcher-delete.test.js test/launcher-connect.test.js test/launcher-update.test.js",
16
16
  "test:e2e": "node --test test/e2e-local.test.js",
17
17
  "test:all": "node scripts/build-runtime-bundle.mjs --check && node scripts/build-pi-runtime-bundle.mjs --check && node --test test/"
18
18
  },