create-egregore 0.21.0-runtime-mvp.20 → 0.21.0-runtime-mvp.22

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.20
2
+ // egregore-launcher-version: 0.21.0-runtime-mvp.22
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.20";
38
+ const VERSION = "0.21.0-runtime-mvp.22";
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) {
@@ -2489,20 +2499,33 @@ function formatTelemetryLines(status) {
2489
2499
  function upgradeStatus(inst, { fetch = false } = {}) {
2490
2500
  const args = ["status"]; if (fetch) args.push("--fetch");
2491
2501
  const r = runUpgrade(inst, args);
2492
- if (!r || r.status !== 0 || !r.stdout) return null;
2493
- try { return JSON.parse(r.stdout); } catch { return null; }
2502
+ if (!r || r.status !== 0 || !r.stdout) {
2503
+ const detail = String((r && (r.stderr || r.stdout)) || "Runtime status probe failed")
2504
+ .trim().split("\n")[0];
2505
+ return { status: "unavailable", failure: { message: detail } };
2506
+ }
2507
+ try { return JSON.parse(r.stdout); } catch {
2508
+ return { status: "unavailable", failure: { message: "Runtime status returned an unreadable response" } };
2509
+ }
2494
2510
  }
2495
2511
 
2496
2512
  function upgradeStatusAsync(inst, cb) {
2497
2513
  const bridge = resolveUpgradeBridge(inst);
2498
- if (!bridge) return cb(null);
2499
- let buf = "";
2514
+ if (!bridge) return cb({ status: "unavailable", failure: { message: "No Runtime update engine is installed" } });
2515
+ let buf = ""; let err = "";
2500
2516
  try {
2501
- const child = spawn("bash", [bridge.script, "status"], { cwd: inst.path, stdio: ["ignore", "pipe", "ignore"], env: { ...process.env, ...bridge.env } });
2517
+ const child = spawn("bash", [bridge.script, "status"], { cwd: inst.path, stdio: ["ignore", "pipe", "pipe"], env: { ...process.env, ...bridge.env } });
2502
2518
  child.stdout.on("data", (d) => { buf += d; });
2503
- child.on("close", () => { try { cb(JSON.parse(buf)); } catch { cb(null); } });
2504
- child.on("error", () => cb(null));
2505
- } catch { cb(null); }
2519
+ child.stderr.on("data", (d) => { err += d; });
2520
+ child.on("close", (code) => {
2521
+ if (code === 0) {
2522
+ try { cb(JSON.parse(buf)); return; } catch {}
2523
+ }
2524
+ const detail = String(err || buf || "Runtime status probe failed").trim().split("\n")[0];
2525
+ cb({ status: "unavailable", failure: { message: detail } });
2526
+ });
2527
+ child.on("error", (error) => cb({ status: "unavailable", failure: { message: error.message } }));
2528
+ } catch (error) { cb({ status: "unavailable", failure: { message: error.message } }); }
2506
2529
  }
2507
2530
 
2508
2531
  // Local retrieval health for the settings surface. Package-update state,
@@ -2622,6 +2645,7 @@ const UPGRADE_STATUS_LABEL = {
2622
2645
  active: "active",
2623
2646
  cancelled: "cancelled",
2624
2647
  "rolled-back": "rolled back",
2648
+ unavailable: "unavailable",
2625
2649
  };
2626
2650
 
2627
2651
  // Settings-row truth for the Runtime update line. The row must say outright
@@ -2635,6 +2659,7 @@ function runtimeUpdateRowValue(st, candidate) {
2635
2659
  if (candidate && candidate !== v) return `${v} active ${YELLOW}· update available ${candidate}${RESET}`;
2636
2660
  return `${GREEN}${v} active${RESET}${candidate ? `${DIM} · up to date${RESET}` : ""}`;
2637
2661
  }
2662
+ if (st.status === "unavailable") return `${YELLOW}update unavailable${RESET}`;
2638
2663
  if (st.status === "ready-to-activate") return `${GREEN}${upgradeSummary(st)}${RESET}`;
2639
2664
  if (st.status === "failed" || st.status === "interrupted") return `${YELLOW}${upgradeSummary(st)}${RESET}`;
2640
2665
  if (st.status === "preparing" || st.status === "pending") return `${DIM}${upgradeSummary(st)}${RESET}`;
@@ -2675,7 +2700,7 @@ function formatUpgradeLines(inst, st, { candidate = "", retrieval = null } = {})
2675
2700
  if (d.candidate_digest) lines.push(`Package digest: sha256 ${String(d.candidate_digest).slice(0, 16)}…`);
2676
2701
  // A recorded failure only needs attention while the transaction is stuck
2677
2702
  // on it; once a later prepare succeeds the line would contradict "ready".
2678
- if (d.failure && d.failure.message && (d.status === "failed" || d.status === "interrupted")) {
2703
+ if (d.failure && d.failure.message && (["failed", "interrupted", "unavailable"].includes(d.status))) {
2679
2704
  lines.push(`Attention: ${d.failure.message}`);
2680
2705
  }
2681
2706
  if (d.rollback_version && d.status !== "none") lines.push(`Rollback target: ${d.rollback_version}`);
@@ -2750,6 +2775,7 @@ async function cmdSettingsRuntime(inst, args) {
2750
2775
  const st = upgradeStatus(inst, { fetch: fetchFlag });
2751
2776
  if (st === null) { process.stderr.write("Runtime update state is unavailable for this egregore.\n"); process.exitCode = 1; return; }
2752
2777
  emit(st);
2778
+ if (st.status === "unavailable") process.exitCode = 1;
2753
2779
  if (action === "review" || (st.status !== "active" && st.status !== "none")) {
2754
2780
  process.stdout.write("\nWhat stays:\n");
2755
2781
  for (const s of UPGRADE_STAYS) process.stdout.write(` · ${s}\n`);
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.20",
3
+ "version": "0.21.0-runtime-mvp.22",
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": {
@@ -6,7 +6,12 @@ set -euo pipefail
6
6
  # legacy Local instances create opaque local IDs once. Network failure remains
7
7
  # non-fatal to startup and is retried next session.
8
8
 
9
- ROOT="$(cd "$(dirname "$0")/.." && pwd)"
9
+ RUNTIME_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
10
+ # A launcher-carried bridge runs outside the selected instance. Keep code
11
+ # rooted in the packaged Runtime while resolving and persisting identity in
12
+ # exactly the instance the launcher selected.
13
+ ROOT="${EGREGORE_INSTANCE_ROOT:-$RUNTIME_ROOT}"
14
+ ROOT="$(cd "$ROOT" && pwd)"
10
15
  CONFIG="$ROOT/egregore.json"
11
16
  STATE="$ROOT/.egregore-state.json"
12
17
 
@@ -34,7 +39,9 @@ case "${1:-ensure}" in
34
39
  resolved && exit 0
35
40
  mode="$(jq -r 'if (.mode // "local") == "connected" or (.api_url // "") != "" then "connected" else "local" end' "$CONFIG" 2>/dev/null || echo local)"
36
41
  if [ "$mode" = "local" ]; then
37
- EGREGORE_ROOT="$ROOT" python3 -m egregore_runtime.identity_cli initialize-local >/dev/null
42
+ EGREGORE_ROOT="$ROOT" \
43
+ PYTHONPATH="$RUNTIME_ROOT${PYTHONPATH:+:$PYTHONPATH}" \
44
+ python3 -m egregore_runtime.identity_cli initialize-local >/dev/null
38
45
  else
39
46
  api_url="$(jq -r '.api_url // empty' "$CONFIG" 2>/dev/null)"
40
47
  api_key="$(grep '^EGREGORE_API_KEY=' "$ROOT/.env" 2>/dev/null | cut -d= -f2-)"
@@ -58,7 +65,9 @@ case "${1:-ensure}" in
58
65
  --argjson organization "$control_plane" \
59
66
  --argjson roster "$roster" \
60
67
  '{organization:$organization,roster:$roster}' | \
61
- EGREGORE_ROOT="$ROOT" python3 -m egregore_runtime.identity_cli \
68
+ EGREGORE_ROOT="$ROOT" \
69
+ PYTHONPATH="$RUNTIME_ROOT${PYTHONPATH:+:$PYTHONPATH}" \
70
+ python3 -m egregore_runtime.identity_cli \
62
71
  reconcile-records >/dev/null
63
72
  fi
64
73
  resolved
@@ -28,13 +28,35 @@ _runtime() {
28
28
  python3 -m egregore_runtime.harness_cli "$@"
29
29
  }
30
30
 
31
+ _ensure_identity() {
32
+ # Pre-Runtime instances can predate stable organization identity. Resolve it
33
+ # through the existing verified Local/Connected migration before computing
34
+ # the instance-scoped upgrade key; never infer identity from path or slug.
35
+ if jq -e '
36
+ .org_id | type == "string" and length > 0
37
+ ' "$INSTANCE_ROOT/egregore.json" >/dev/null 2>&1; then
38
+ return 0
39
+ fi
40
+ EGREGORE_INSTANCE_ROOT="$INSTANCE_ROOT" \
41
+ PYTHONPATH="$SCRIPT_DIR${PYTHONPATH:+:$PYTHONPATH}" \
42
+ bash "$SCRIPT_DIR/bin/runtime-identity.sh" ensure
43
+ }
44
+
45
+ _run_upgrade() {
46
+ _ensure_identity || {
47
+ echo "Runtime update could not resolve this Egregore's stable identity." >&2
48
+ return 1
49
+ }
50
+ _runtime upgrade "$@"
51
+ }
52
+
31
53
  case "${1:-}" in
32
- status) shift; _runtime upgrade status "$@" ;;
33
- init) shift; _runtime upgrade init "$@" ;;
34
- prepare) shift; _runtime upgrade prepare "$@" ;;
35
- activate) shift; _runtime upgrade activate "$@" ;;
36
- cancel) shift; _runtime upgrade cancel "$@" ;;
37
- rollback) shift; _runtime upgrade rollback "$@" ;;
54
+ status) shift; _run_upgrade status "$@" ;;
55
+ init) shift; _run_upgrade init "$@" ;;
56
+ prepare) shift; _run_upgrade prepare "$@" ;;
57
+ activate) shift; _run_upgrade activate "$@" ;;
58
+ cancel) shift; _run_upgrade cancel "$@" ;;
59
+ rollback) shift; _run_upgrade rollback "$@" ;;
38
60
  team-sync) shift; _runtime team-sync "$@" ;;
39
61
  *)
40
62
  echo "usage: bash bin/runtime-upgrade.sh {status|init --candidate <version>|prepare|activate|cancel|rollback|team-sync}" >&2
@@ -6,7 +6,12 @@ set -euo pipefail
6
6
  # legacy Local instances create opaque local IDs once. Network failure remains
7
7
  # non-fatal to startup and is retried next session.
8
8
 
9
- ROOT="$(cd "$(dirname "$0")/.." && pwd)"
9
+ RUNTIME_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
10
+ # A launcher-carried bridge runs outside the selected instance. Keep code
11
+ # rooted in the packaged Runtime while resolving and persisting identity in
12
+ # exactly the instance the launcher selected.
13
+ ROOT="${EGREGORE_INSTANCE_ROOT:-$RUNTIME_ROOT}"
14
+ ROOT="$(cd "$ROOT" && pwd)"
10
15
  CONFIG="$ROOT/egregore.json"
11
16
  STATE="$ROOT/.egregore-state.json"
12
17
 
@@ -34,7 +39,9 @@ case "${1:-ensure}" in
34
39
  resolved && exit 0
35
40
  mode="$(jq -r 'if (.mode // "local") == "connected" or (.api_url // "") != "" then "connected" else "local" end' "$CONFIG" 2>/dev/null || echo local)"
36
41
  if [ "$mode" = "local" ]; then
37
- EGREGORE_ROOT="$ROOT" python3 -m egregore_runtime.identity_cli initialize-local >/dev/null
42
+ EGREGORE_ROOT="$ROOT" \
43
+ PYTHONPATH="$RUNTIME_ROOT${PYTHONPATH:+:$PYTHONPATH}" \
44
+ python3 -m egregore_runtime.identity_cli initialize-local >/dev/null
38
45
  else
39
46
  api_url="$(jq -r '.api_url // empty' "$CONFIG" 2>/dev/null)"
40
47
  api_key="$(grep '^EGREGORE_API_KEY=' "$ROOT/.env" 2>/dev/null | cut -d= -f2-)"
@@ -58,7 +65,9 @@ case "${1:-ensure}" in
58
65
  --argjson organization "$control_plane" \
59
66
  --argjson roster "$roster" \
60
67
  '{organization:$organization,roster:$roster}' | \
61
- EGREGORE_ROOT="$ROOT" python3 -m egregore_runtime.identity_cli \
68
+ EGREGORE_ROOT="$ROOT" \
69
+ PYTHONPATH="$RUNTIME_ROOT${PYTHONPATH:+:$PYTHONPATH}" \
70
+ python3 -m egregore_runtime.identity_cli \
62
71
  reconcile-records >/dev/null
63
72
  fi
64
73
  resolved
@@ -28,13 +28,35 @@ _runtime() {
28
28
  python3 -m egregore_runtime.harness_cli "$@"
29
29
  }
30
30
 
31
+ _ensure_identity() {
32
+ # Pre-Runtime instances can predate stable organization identity. Resolve it
33
+ # through the existing verified Local/Connected migration before computing
34
+ # the instance-scoped upgrade key; never infer identity from path or slug.
35
+ if jq -e '
36
+ .org_id | type == "string" and length > 0
37
+ ' "$INSTANCE_ROOT/egregore.json" >/dev/null 2>&1; then
38
+ return 0
39
+ fi
40
+ EGREGORE_INSTANCE_ROOT="$INSTANCE_ROOT" \
41
+ PYTHONPATH="$SCRIPT_DIR${PYTHONPATH:+:$PYTHONPATH}" \
42
+ bash "$SCRIPT_DIR/bin/runtime-identity.sh" ensure
43
+ }
44
+
45
+ _run_upgrade() {
46
+ _ensure_identity || {
47
+ echo "Runtime update could not resolve this Egregore's stable identity." >&2
48
+ return 1
49
+ }
50
+ _runtime upgrade "$@"
51
+ }
52
+
31
53
  case "${1:-}" in
32
- status) shift; _runtime upgrade status "$@" ;;
33
- init) shift; _runtime upgrade init "$@" ;;
34
- prepare) shift; _runtime upgrade prepare "$@" ;;
35
- activate) shift; _runtime upgrade activate "$@" ;;
36
- cancel) shift; _runtime upgrade cancel "$@" ;;
37
- rollback) shift; _runtime upgrade rollback "$@" ;;
54
+ status) shift; _run_upgrade status "$@" ;;
55
+ init) shift; _run_upgrade init "$@" ;;
56
+ prepare) shift; _run_upgrade prepare "$@" ;;
57
+ activate) shift; _run_upgrade activate "$@" ;;
58
+ cancel) shift; _run_upgrade cancel "$@" ;;
59
+ rollback) shift; _run_upgrade rollback "$@" ;;
38
60
  team-sync) shift; _runtime team-sync "$@" ;;
39
61
  *)
40
62
  echo "usage: bash bin/runtime-upgrade.sh {status|init --candidate <version>|prepare|activate|cancel|rollback|team-sync}" >&2
@@ -6,7 +6,12 @@ set -euo pipefail
6
6
  # legacy Local instances create opaque local IDs once. Network failure remains
7
7
  # non-fatal to startup and is retried next session.
8
8
 
9
- ROOT="$(cd "$(dirname "$0")/.." && pwd)"
9
+ RUNTIME_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
10
+ # A launcher-carried bridge runs outside the selected instance. Keep code
11
+ # rooted in the packaged Runtime while resolving and persisting identity in
12
+ # exactly the instance the launcher selected.
13
+ ROOT="${EGREGORE_INSTANCE_ROOT:-$RUNTIME_ROOT}"
14
+ ROOT="$(cd "$ROOT" && pwd)"
10
15
  CONFIG="$ROOT/egregore.json"
11
16
  STATE="$ROOT/.egregore-state.json"
12
17
 
@@ -34,7 +39,9 @@ case "${1:-ensure}" in
34
39
  resolved && exit 0
35
40
  mode="$(jq -r 'if (.mode // "local") == "connected" or (.api_url // "") != "" then "connected" else "local" end' "$CONFIG" 2>/dev/null || echo local)"
36
41
  if [ "$mode" = "local" ]; then
37
- EGREGORE_ROOT="$ROOT" python3 -m egregore_runtime.identity_cli initialize-local >/dev/null
42
+ EGREGORE_ROOT="$ROOT" \
43
+ PYTHONPATH="$RUNTIME_ROOT${PYTHONPATH:+:$PYTHONPATH}" \
44
+ python3 -m egregore_runtime.identity_cli initialize-local >/dev/null
38
45
  else
39
46
  api_url="$(jq -r '.api_url // empty' "$CONFIG" 2>/dev/null)"
40
47
  api_key="$(grep '^EGREGORE_API_KEY=' "$ROOT/.env" 2>/dev/null | cut -d= -f2-)"
@@ -58,7 +65,9 @@ case "${1:-ensure}" in
58
65
  --argjson organization "$control_plane" \
59
66
  --argjson roster "$roster" \
60
67
  '{organization:$organization,roster:$roster}' | \
61
- EGREGORE_ROOT="$ROOT" python3 -m egregore_runtime.identity_cli \
68
+ EGREGORE_ROOT="$ROOT" \
69
+ PYTHONPATH="$RUNTIME_ROOT${PYTHONPATH:+:$PYTHONPATH}" \
70
+ python3 -m egregore_runtime.identity_cli \
62
71
  reconcile-records >/dev/null
63
72
  fi
64
73
  resolved
@@ -28,13 +28,35 @@ _runtime() {
28
28
  python3 -m egregore_runtime.harness_cli "$@"
29
29
  }
30
30
 
31
+ _ensure_identity() {
32
+ # Pre-Runtime instances can predate stable organization identity. Resolve it
33
+ # through the existing verified Local/Connected migration before computing
34
+ # the instance-scoped upgrade key; never infer identity from path or slug.
35
+ if jq -e '
36
+ .org_id | type == "string" and length > 0
37
+ ' "$INSTANCE_ROOT/egregore.json" >/dev/null 2>&1; then
38
+ return 0
39
+ fi
40
+ EGREGORE_INSTANCE_ROOT="$INSTANCE_ROOT" \
41
+ PYTHONPATH="$SCRIPT_DIR${PYTHONPATH:+:$PYTHONPATH}" \
42
+ bash "$SCRIPT_DIR/bin/runtime-identity.sh" ensure
43
+ }
44
+
45
+ _run_upgrade() {
46
+ _ensure_identity || {
47
+ echo "Runtime update could not resolve this Egregore's stable identity." >&2
48
+ return 1
49
+ }
50
+ _runtime upgrade "$@"
51
+ }
52
+
31
53
  case "${1:-}" in
32
- status) shift; _runtime upgrade status "$@" ;;
33
- init) shift; _runtime upgrade init "$@" ;;
34
- prepare) shift; _runtime upgrade prepare "$@" ;;
35
- activate) shift; _runtime upgrade activate "$@" ;;
36
- cancel) shift; _runtime upgrade cancel "$@" ;;
37
- rollback) shift; _runtime upgrade rollback "$@" ;;
54
+ status) shift; _run_upgrade status "$@" ;;
55
+ init) shift; _run_upgrade init "$@" ;;
56
+ prepare) shift; _run_upgrade prepare "$@" ;;
57
+ activate) shift; _run_upgrade activate "$@" ;;
58
+ cancel) shift; _run_upgrade cancel "$@" ;;
59
+ rollback) shift; _run_upgrade rollback "$@" ;;
38
60
  team-sync) shift; _runtime team-sync "$@" ;;
39
61
  *)
40
62
  echo "usage: bash bin/runtime-upgrade.sh {status|init --candidate <version>|prepare|activate|cancel|rollback|team-sync}" >&2
@@ -6,7 +6,12 @@ set -euo pipefail
6
6
  # legacy Local instances create opaque local IDs once. Network failure remains
7
7
  # non-fatal to startup and is retried next session.
8
8
 
9
- ROOT="$(cd "$(dirname "$0")/.." && pwd)"
9
+ RUNTIME_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
10
+ # A launcher-carried bridge runs outside the selected instance. Keep code
11
+ # rooted in the packaged Runtime while resolving and persisting identity in
12
+ # exactly the instance the launcher selected.
13
+ ROOT="${EGREGORE_INSTANCE_ROOT:-$RUNTIME_ROOT}"
14
+ ROOT="$(cd "$ROOT" && pwd)"
10
15
  CONFIG="$ROOT/egregore.json"
11
16
  STATE="$ROOT/.egregore-state.json"
12
17
 
@@ -34,7 +39,9 @@ case "${1:-ensure}" in
34
39
  resolved && exit 0
35
40
  mode="$(jq -r 'if (.mode // "local") == "connected" or (.api_url // "") != "" then "connected" else "local" end' "$CONFIG" 2>/dev/null || echo local)"
36
41
  if [ "$mode" = "local" ]; then
37
- EGREGORE_ROOT="$ROOT" python3 -m egregore_runtime.identity_cli initialize-local >/dev/null
42
+ EGREGORE_ROOT="$ROOT" \
43
+ PYTHONPATH="$RUNTIME_ROOT${PYTHONPATH:+:$PYTHONPATH}" \
44
+ python3 -m egregore_runtime.identity_cli initialize-local >/dev/null
38
45
  else
39
46
  api_url="$(jq -r '.api_url // empty' "$CONFIG" 2>/dev/null)"
40
47
  api_key="$(grep '^EGREGORE_API_KEY=' "$ROOT/.env" 2>/dev/null | cut -d= -f2-)"
@@ -58,7 +65,9 @@ case "${1:-ensure}" in
58
65
  --argjson organization "$control_plane" \
59
66
  --argjson roster "$roster" \
60
67
  '{organization:$organization,roster:$roster}' | \
61
- EGREGORE_ROOT="$ROOT" python3 -m egregore_runtime.identity_cli \
68
+ EGREGORE_ROOT="$ROOT" \
69
+ PYTHONPATH="$RUNTIME_ROOT${PYTHONPATH:+:$PYTHONPATH}" \
70
+ python3 -m egregore_runtime.identity_cli \
62
71
  reconcile-records >/dev/null
63
72
  fi
64
73
  resolved
@@ -28,13 +28,35 @@ _runtime() {
28
28
  python3 -m egregore_runtime.harness_cli "$@"
29
29
  }
30
30
 
31
+ _ensure_identity() {
32
+ # Pre-Runtime instances can predate stable organization identity. Resolve it
33
+ # through the existing verified Local/Connected migration before computing
34
+ # the instance-scoped upgrade key; never infer identity from path or slug.
35
+ if jq -e '
36
+ .org_id | type == "string" and length > 0
37
+ ' "$INSTANCE_ROOT/egregore.json" >/dev/null 2>&1; then
38
+ return 0
39
+ fi
40
+ EGREGORE_INSTANCE_ROOT="$INSTANCE_ROOT" \
41
+ PYTHONPATH="$SCRIPT_DIR${PYTHONPATH:+:$PYTHONPATH}" \
42
+ bash "$SCRIPT_DIR/bin/runtime-identity.sh" ensure
43
+ }
44
+
45
+ _run_upgrade() {
46
+ _ensure_identity || {
47
+ echo "Runtime update could not resolve this Egregore's stable identity." >&2
48
+ return 1
49
+ }
50
+ _runtime upgrade "$@"
51
+ }
52
+
31
53
  case "${1:-}" in
32
- status) shift; _runtime upgrade status "$@" ;;
33
- init) shift; _runtime upgrade init "$@" ;;
34
- prepare) shift; _runtime upgrade prepare "$@" ;;
35
- activate) shift; _runtime upgrade activate "$@" ;;
36
- cancel) shift; _runtime upgrade cancel "$@" ;;
37
- rollback) shift; _runtime upgrade rollback "$@" ;;
54
+ status) shift; _run_upgrade status "$@" ;;
55
+ init) shift; _run_upgrade init "$@" ;;
56
+ prepare) shift; _run_upgrade prepare "$@" ;;
57
+ activate) shift; _run_upgrade activate "$@" ;;
58
+ cancel) shift; _run_upgrade cancel "$@" ;;
59
+ rollback) shift; _run_upgrade rollback "$@" ;;
38
60
  team-sync) shift; _runtime team-sync "$@" ;;
39
61
  *)
40
62
  echo "usage: bash bin/runtime-upgrade.sh {status|init --candidate <version>|prepare|activate|cancel|rollback|team-sync}" >&2