create-egregore 0.21.0-runtime-mvp.19 → 0.21.0-runtime-mvp.21

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.19
2
+ // egregore-launcher-version: 0.21.0-runtime-mvp.21
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.19";
38
+ const VERSION = "0.21.0-runtime-mvp.21";
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");
@@ -2341,8 +2341,18 @@ function connectedModeSuffix(instPath) {
2341
2341
  function packagedUpgradeScript() {
2342
2342
  const seeded = process.env.EGREGORE_RUNTIME_PACKAGED_BRIDGE;
2343
2343
  if (seeded !== undefined) return seeded || null;
2344
- const script = path.join(__dirname, "..", "runtime", "claude", "bin", "runtime-upgrade.sh");
2345
- return fs.existsSync(script) ? script : null;
2344
+ // The launcher is copied into a PATH directory as a standalone executable,
2345
+ // so __dirname normally points at /usr/local/bin or ~/.local/bin rather than
2346
+ // the npm package that supplied it. installLauncherBinary keeps the matching
2347
+ // Runtime engine in a versioned per-user directory outside every Egregore
2348
+ // checkout. The package-adjacent fallback keeps direct source/package runs
2349
+ // working without a separate install step.
2350
+ const runtimeBase = process.env.EGREGORE_LAUNCHER_RUNTIME_DIR
2351
+ || path.join(os.homedir(), ".egregore", "launcher-runtime");
2352
+ const installed = path.join(runtimeBase, VERSION, "claude", "bin", "runtime-upgrade.sh");
2353
+ if (fs.existsSync(installed)) return installed;
2354
+ const adjacent = path.join(__dirname, "..", "runtime", "claude", "bin", "runtime-upgrade.sh");
2355
+ return fs.existsSync(adjacent) ? adjacent : null;
2346
2356
  }
2347
2357
 
2348
2358
  function resolveUpgradeBridge(inst) {
package/bin/cli.js CHANGED
@@ -471,6 +471,10 @@ async function main() {
471
471
  ui.error("Could not refresh the Egregore CLI.");
472
472
  process.exit(1);
473
473
  }
474
+ if (!alias.runtimeBridgePath || !fs.existsSync(alias.runtimeBridgePath)) {
475
+ ui.error("Could not refresh the Runtime update engine.");
476
+ process.exit(1);
477
+ }
474
478
  ui.success(`Egregore CLI is current at ${alias.binaryPath}`);
475
479
  return;
476
480
  }
package/lib/setup.js CHANGED
@@ -1283,6 +1283,7 @@ async function launchOrInstruct(egregoreDir, alias, ui, ctx = {}) {
1283
1283
  // browser, better rendering, and richer keybinds.
1284
1284
  const LAUNCHER_TEMPLATE_JS = path.join(__dirname, "..", "assets", "egregore-launcher.js");
1285
1285
  const LAUNCHER_TEMPLATE_SH = path.join(__dirname, "..", "assets", "egregore-launcher.sh");
1286
+ const LAUNCHER_RUNTIME_TEMPLATE = path.join(__dirname, "..", "runtime", "claude");
1286
1287
 
1287
1288
  function readLauncherTemplate() {
1288
1289
  // Prefer the Node launcher; fall back to bash only if the .js is missing.
@@ -1294,6 +1295,50 @@ function currentTemplatePath() {
1294
1295
  return fs.existsSync(LAUNCHER_TEMPLATE_JS) ? LAUNCHER_TEMPLATE_JS : LAUNCHER_TEMPLATE_SH;
1295
1296
  }
1296
1297
 
1298
+ function launcherRuntimeRoot(version) {
1299
+ const base = process.env.EGREGORE_LAUNCHER_RUNTIME_DIR
1300
+ || path.join(os.homedir(), ".egregore", "launcher-runtime");
1301
+ return version ? path.join(base, version) : null;
1302
+ }
1303
+
1304
+ function launcherRuntimeBridge(version) {
1305
+ const root = launcherRuntimeRoot(version);
1306
+ return root ? path.join(root, "claude", "bin", "runtime-upgrade.sh") : null;
1307
+ }
1308
+
1309
+ // The PATH launcher is a copied standalone file, not an npm shim, so it cannot
1310
+ // reach runtime/ through __dirname after installation. Keep the matching
1311
+ // upgrade engine in a versioned per-user directory. This is deliberately
1312
+ // outside registered Egregore checkouts: refreshing the global CLI must never
1313
+ // spray framework files into an instance or dirty its Git working tree.
1314
+ function installLauncherRuntime(version, ui) {
1315
+ const destination = launcherRuntimeRoot(version);
1316
+ const bridge = launcherRuntimeBridge(version);
1317
+ if (!destination || !fs.existsSync(LAUNCHER_RUNTIME_TEMPLATE)) return null;
1318
+ if (bridge && fs.existsSync(bridge)) return bridge;
1319
+
1320
+ const parent = path.dirname(destination);
1321
+ const temporary = path.join(parent, `.${path.basename(destination)}.tmp-${process.pid}`);
1322
+ try {
1323
+ fs.mkdirSync(parent, { recursive: true, mode: 0o700 });
1324
+ fs.rmSync(temporary, { recursive: true, force: true });
1325
+ fs.mkdirSync(temporary, { recursive: true, mode: 0o700 });
1326
+ fs.cpSync(LAUNCHER_RUNTIME_TEMPLATE, path.join(temporary, "claude"), {
1327
+ recursive: true,
1328
+ dereference: false,
1329
+ });
1330
+ const stagedBridge = path.join(temporary, "claude", "bin", "runtime-upgrade.sh");
1331
+ if (!fs.existsSync(stagedBridge)) throw new Error("packaged runtime-upgrade.sh is missing");
1332
+ fs.rmSync(destination, { recursive: true, force: true });
1333
+ fs.renameSync(temporary, destination);
1334
+ return bridge;
1335
+ } catch (err) {
1336
+ try { fs.rmSync(temporary, { recursive: true, force: true }); } catch {}
1337
+ ui.warn(`Could not install the Runtime update engine: ${err.message}`);
1338
+ return null;
1339
+ }
1340
+ }
1341
+
1297
1342
  // Parse a semver from a version-stamp comment. Accepts both styles so we can
1298
1343
  // upgrade a bash launcher to the Node launcher across a major line:
1299
1344
  // # egregore-launcher-version: X.Y.Z (bash)
@@ -1400,6 +1445,8 @@ function appendLocalBinToRc(ui) {
1400
1445
  */
1401
1446
  async function installLauncherBinary(egregoreDir, ui) {
1402
1447
  const template = readLauncherTemplate();
1448
+ const templateVer = launcherVersion(template);
1449
+ const packagedBridge = installLauncherRuntime(templateVer, ui);
1403
1450
  // Tests, containers, and managed installations can pin the launcher to an
1404
1451
  // isolated directory. When explicitly configured, never fall through to a
1405
1452
  // machine-wide path: isolation is the reason the override exists.
@@ -1444,7 +1491,7 @@ async function installLauncherBinary(egregoreDir, ui) {
1444
1491
  try { fs.chmodSync(binaryPath, 0o755); } catch {}
1445
1492
  if (chosen.location === "user" && !dirOnPath(chosen.dir)) appendLocalBinToRc(ui);
1446
1493
  removeShadowingEgregoreAliases(ui);
1447
- return { aliasName: "egregore", binaryPath, location: chosen.location, replaced: false };
1494
+ return { aliasName: "egregore", binaryPath, location: chosen.location, replaced: false, runtimeBridgePath: packagedBridge };
1448
1495
  }
1449
1496
 
1450
1497
  // Different content — decide upgrade vs preserve via version stamps.
@@ -1458,18 +1505,18 @@ async function installLauncherBinary(egregoreDir, ui) {
1458
1505
  ui.success(`Upgraded ${ui.dim("egregore")} at ${binaryPath} ${ui.dim(`(${existingVer} → ${templateVer})`)}`);
1459
1506
  if (chosen.location === "user" && !dirOnPath(chosen.dir)) appendLocalBinToRc(ui);
1460
1507
  removeShadowingEgregoreAliases(ui);
1461
- return { aliasName: "egregore", binaryPath, location: chosen.location, replaced: true };
1508
+ return { aliasName: "egregore", binaryPath, location: chosen.location, replaced: true, runtimeBridgePath: packagedBridge };
1462
1509
  }
1463
1510
  if (compareVersions(templateVer, existingVer) < 0) {
1464
1511
  // Template is older — don't downgrade. Warn lightly.
1465
1512
  ui.warn(`Existing ${binaryPath} (v${existingVer}) is newer than this installer's template (v${templateVer}) — leaving it alone.`);
1466
1513
  removeShadowingEgregoreAliases(ui);
1467
- return { aliasName: "egregore", binaryPath, location: chosen.location, replaced: false };
1514
+ return { aliasName: "egregore", binaryPath, location: chosen.location, replaced: false, runtimeBridgePath: launcherRuntimeBridge(existingVer) };
1468
1515
  }
1469
1516
  // Equal stamps but different content → user edited the file. Don't clobber.
1470
1517
  ui.warn(`Existing ${binaryPath} has been edited locally — not overwriting.`);
1471
1518
  ui.info(` To reset: cp ${currentTemplatePath()} ${binaryPath}`);
1472
- return { aliasName: "egregore", binaryPath, location: chosen.location, replaced: false };
1519
+ return { aliasName: "egregore", binaryPath, location: chosen.location, replaced: false, runtimeBridgePath: packagedBridge };
1473
1520
  }
1474
1521
 
1475
1522
  // No version stamp on existing. If it looks like one of OUR old
@@ -1485,11 +1532,11 @@ async function installLauncherBinary(egregoreDir, ui) {
1485
1532
  ui.success(`Upgraded ${ui.dim("egregore")} at ${binaryPath} ${ui.dim(`(pre-0.4.1 → ${templateVer})`)}`);
1486
1533
  if (chosen.location === "user" && !dirOnPath(chosen.dir)) appendLocalBinToRc(ui);
1487
1534
  removeShadowingEgregoreAliases(ui);
1488
- return { aliasName: "egregore", binaryPath, location: chosen.location, replaced: true };
1535
+ return { aliasName: "egregore", binaryPath, location: chosen.location, replaced: true, runtimeBridgePath: packagedBridge };
1489
1536
  }
1490
1537
  ui.warn(`Existing ${binaryPath} doesn't look like an egregore launcher — not overwriting.`);
1491
1538
  ui.info(` If you meant to install: rm ${binaryPath} and re-run create-egregore.`);
1492
- return { aliasName: "egregore", binaryPath, location: chosen.location, replaced: false };
1539
+ return { aliasName: "egregore", binaryPath, location: chosen.location, replaced: false, runtimeBridgePath: null };
1493
1540
  } catch {
1494
1541
  // Fall through to write
1495
1542
  }
@@ -1509,7 +1556,7 @@ async function installLauncherBinary(egregoreDir, ui) {
1509
1556
 
1510
1557
  removeShadowingEgregoreAliases(ui);
1511
1558
  ui.success(`Installed ${ui.dim("egregore")} at ${binaryPath}`);
1512
- return { aliasName: "egregore", binaryPath, location: chosen.location, replaced };
1559
+ return { aliasName: "egregore", binaryPath, location: chosen.location, replaced, runtimeBridgePath: packagedBridge };
1513
1560
  }
1514
1561
 
1515
1562
  // Strip legacy `alias egregore=...` lines from common shell rc files. These
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-egregore",
3
- "version": "0.21.0-runtime-mvp.19",
3
+ "version": "0.21.0-runtime-mvp.21",
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": {
@@ -8,8 +8,8 @@
8
8
  # memory files right after a clean search), so this hook enforces it.
9
9
  #
10
10
  # Scope: a retrieval episode. The guard engages only while this session's
11
- # evidence packet is fresh (written within the last EGREGORE_RETRIEVAL_WINDOW_MIN
12
- # minutes, default 15). Outside an episode — editing a decision record, a skill
11
+ # retrieval episode is fresh (a search or open within the last
12
+ # EGREGORE_RETRIEVAL_WINDOW_MIN minutes, default 15). Outside an episode — editing a decision record, a skill
13
13
  # reading memory, ordinary work — memory reads are untouched. Edit and Write
14
14
  # are never the guard's business. Searching memory with grep passes.
15
15
  #
@@ -26,10 +26,14 @@ case "$TOOL" in Bash|Read) ;; *) exit 0 ;; esac
26
26
  # --- Retrieval episode: this session's packet exists and is fresh ---------
27
27
  SESSION=$(cat "$PROJECT_DIR/.egregore-session-id" 2>/dev/null | tr -cd 'A-Za-z0-9_.-')
28
28
  [ -n "$SESSION" ] || exit 0
29
- PACKET="${TMPDIR:-/tmp}/egregore-retrieval-context/$SESSION.context"
30
- [ -f "$PACKET" ] || exit 0
29
+ # The packet itself is consumed by the PostToolUse hook the moment it is
30
+ # attached, so the episode is recorded by a marker that bin/search.sh and the
31
+ # PostToolUse hook both touch. The packet is checked too, for the window
32
+ # between its write and its consumption.
33
+ CTX_DIR="${TMPDIR:-/tmp}/egregore-retrieval-context"
31
34
  WINDOW_MIN="${EGREGORE_RETRIEVAL_WINDOW_MIN:-15}"
32
- [ -n "$(find "$PACKET" -mmin "-$WINDOW_MIN" 2>/dev/null)" ] || exit 0
35
+ _fresh() { [ -f "$1" ] && [ -n "$(find "$1" -mmin "-$WINDOW_MIN" 2>/dev/null)" ]; }
36
+ _fresh "$CTX_DIR/$SESSION.episode" || _fresh "$CTX_DIR/$SESSION.context" || exit 0
33
37
 
34
38
  _block() {
35
39
  printf 'Egregore: memory sources are opened through the private evidence packet, not printed into the transcript.\n' >&2
@@ -39,5 +39,8 @@ jq -Rs '{
39
39
  )
40
40
  }
41
41
  }' "$PACKET"
42
+ # The packet is one-shot, but the retrieval episode is not: leave a marker so
43
+ # memory-read-guard.sh keeps routing raw memory reads through the opener.
44
+ touch "${PACKET%.context}.episode" 2>/dev/null || true
42
45
  rm -f "$PACKET"
43
46
  exit 0
@@ -283,6 +283,10 @@ cmd_query() {
283
283
  }
284
284
  umask 077
285
285
  printf '%s\n' "$out" > "$packet"
286
+ # The PostToolUse hook consumes the packet the moment it attaches it; the
287
+ # episode marker outlives that so the memory-read guard knows a retrieval
288
+ # just happened.
289
+ touch "${packet%.context}.episode" 2>/dev/null || true
286
290
  _banner_and_emit "$label" "$t0" "$emit" "$out" 1
287
291
  else
288
292
  _banner_and_emit "$label" "$t0" "$emit" "$out"
@@ -343,6 +347,7 @@ case "${1:-}" in
343
347
  umask 077
344
348
  if _runtime open "$OPEN_PATH" > "$packet" 2>"$packet.err"; then
345
349
  rm -f "$packet.err"
350
+ touch "${packet%.context}.episode" 2>/dev/null || true
346
351
  echo "↳ opened $OPEN_PATH · content attached privately"
347
352
  else
348
353
  cat "$packet.err" >&2 2>/dev/null
@@ -283,6 +283,10 @@ cmd_query() {
283
283
  }
284
284
  umask 077
285
285
  printf '%s\n' "$out" > "$packet"
286
+ # The PostToolUse hook consumes the packet the moment it attaches it; the
287
+ # episode marker outlives that so the memory-read guard knows a retrieval
288
+ # just happened.
289
+ touch "${packet%.context}.episode" 2>/dev/null || true
286
290
  _banner_and_emit "$label" "$t0" "$emit" "$out" 1
287
291
  else
288
292
  _banner_and_emit "$label" "$t0" "$emit" "$out"
@@ -343,6 +347,7 @@ case "${1:-}" in
343
347
  umask 077
344
348
  if _runtime open "$OPEN_PATH" > "$packet" 2>"$packet.err"; then
345
349
  rm -f "$packet.err"
350
+ touch "${packet%.context}.episode" 2>/dev/null || true
346
351
  echo "↳ opened $OPEN_PATH · content attached privately"
347
352
  else
348
353
  cat "$packet.err" >&2 2>/dev/null
@@ -283,6 +283,10 @@ cmd_query() {
283
283
  }
284
284
  umask 077
285
285
  printf '%s\n' "$out" > "$packet"
286
+ # The PostToolUse hook consumes the packet the moment it attaches it; the
287
+ # episode marker outlives that so the memory-read guard knows a retrieval
288
+ # just happened.
289
+ touch "${packet%.context}.episode" 2>/dev/null || true
286
290
  _banner_and_emit "$label" "$t0" "$emit" "$out" 1
287
291
  else
288
292
  _banner_and_emit "$label" "$t0" "$emit" "$out"
@@ -343,6 +347,7 @@ case "${1:-}" in
343
347
  umask 077
344
348
  if _runtime open "$OPEN_PATH" > "$packet" 2>"$packet.err"; then
345
349
  rm -f "$packet.err"
350
+ touch "${packet%.context}.episode" 2>/dev/null || true
346
351
  echo "↳ opened $OPEN_PATH · content attached privately"
347
352
  else
348
353
  cat "$packet.err" >&2 2>/dev/null
@@ -283,6 +283,10 @@ cmd_query() {
283
283
  }
284
284
  umask 077
285
285
  printf '%s\n' "$out" > "$packet"
286
+ # The PostToolUse hook consumes the packet the moment it attaches it; the
287
+ # episode marker outlives that so the memory-read guard knows a retrieval
288
+ # just happened.
289
+ touch "${packet%.context}.episode" 2>/dev/null || true
286
290
  _banner_and_emit "$label" "$t0" "$emit" "$out" 1
287
291
  else
288
292
  _banner_and_emit "$label" "$t0" "$emit" "$out"
@@ -343,6 +347,7 @@ case "${1:-}" in
343
347
  umask 077
344
348
  if _runtime open "$OPEN_PATH" > "$packet" 2>"$packet.err"; then
345
349
  rm -f "$packet.err"
350
+ touch "${packet%.context}.episode" 2>/dev/null || true
346
351
  echo "↳ opened $OPEN_PATH · content attached privately"
347
352
  else
348
353
  cat "$packet.err" >&2 2>/dev/null