@pleri/olam-cli 0.1.162 → 0.1.167

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.
Files changed (49) hide show
  1. package/README.md +4 -2
  2. package/dist/commands/bootstrap.d.ts +6 -0
  3. package/dist/commands/bootstrap.d.ts.map +1 -1
  4. package/dist/commands/bootstrap.js +15 -0
  5. package/dist/commands/bootstrap.js.map +1 -1
  6. package/dist/commands/doctor.js +4 -4
  7. package/dist/commands/doctor.js.map +1 -1
  8. package/dist/commands/init.d.ts +4 -3
  9. package/dist/commands/init.d.ts.map +1 -1
  10. package/dist/commands/init.js +103 -81
  11. package/dist/commands/init.js.map +1 -1
  12. package/dist/commands/memory-service-container.d.ts +8 -0
  13. package/dist/commands/memory-service-container.d.ts.map +1 -1
  14. package/dist/commands/memory-service-container.js +16 -1
  15. package/dist/commands/memory-service-container.js.map +1 -1
  16. package/dist/commands/setup.d.ts +62 -14
  17. package/dist/commands/setup.d.ts.map +1 -1
  18. package/dist/commands/setup.js +373 -42
  19. package/dist/commands/setup.js.map +1 -1
  20. package/dist/commands/skills-source.d.ts.map +1 -1
  21. package/dist/commands/skills-source.js +89 -4
  22. package/dist/commands/skills-source.js.map +1 -1
  23. package/dist/image-digests.json +8 -7
  24. package/dist/index.js +672 -168
  25. package/dist/lib/bootstrap-kubernetes.d.ts.map +1 -1
  26. package/dist/lib/bootstrap-kubernetes.js +163 -106
  27. package/dist/lib/bootstrap-kubernetes.js.map +1 -1
  28. package/dist/lib/health-probes.d.ts +16 -0
  29. package/dist/lib/health-probes.d.ts.map +1 -1
  30. package/dist/lib/health-probes.js +49 -0
  31. package/dist/lib/health-probes.js.map +1 -1
  32. package/dist/lib/peripheral-registry.d.ts +9 -3
  33. package/dist/lib/peripheral-registry.d.ts.map +1 -1
  34. package/dist/lib/peripheral-registry.js +4 -4
  35. package/dist/lib/peripheral-registry.js.map +1 -1
  36. package/dist/lib/port-forward.js +1 -1
  37. package/dist/lib/port-forward.js.map +1 -1
  38. package/dist/lib/upgrade-kubernetes.d.ts +1 -1
  39. package/dist/lib/upgrade-kubernetes.d.ts.map +1 -1
  40. package/dist/lib/upgrade-kubernetes.js +35 -21
  41. package/dist/lib/upgrade-kubernetes.js.map +1 -1
  42. package/dist/mcp-server.js +990 -331
  43. package/hermes-bundle/version.json +1 -1
  44. package/host-cp/k8s/manifests/50-deployment.yaml +1 -1
  45. package/host-cp/k8s/manifests/auth-service/50-deployment.yaml +1 -1
  46. package/host-cp/k8s/manifests/kg-service/50-deployment.yaml +1 -1
  47. package/host-cp/k8s/manifests/mcp-auth-service/50-deployment.yaml +1 -1
  48. package/host-cp/k8s/manifests/memory-service/50-deployment.yaml +1 -1
  49. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5397,7 +5397,7 @@ var init_client = __esm({
5397
5397
  "use strict";
5398
5398
  init_secret();
5399
5399
  DEFAULT_BASE_URL = "http://127.0.0.1:9999";
5400
- DEFAULT_TIMEOUT_MS = 3e3;
5400
+ DEFAULT_TIMEOUT_MS = 1e4;
5401
5401
  RETRY_COUNT = 2;
5402
5402
  RETRY_BACKOFF_MS = 250;
5403
5403
  AuthClient = class {
@@ -7884,6 +7884,38 @@ function hasImageRef(present, ref) {
7884
7884
  const variants = [`docker.io/library/${ref}`, `docker.io/${ref}`];
7885
7885
  return variants.some((v) => present.has(v));
7886
7886
  }
7887
+ async function probeKubectl(exec = defaultDockerExec) {
7888
+ const r = exec("kubectl", ["version", "--client", "--output=json"]);
7889
+ if (r.status === 0 && r.stdout.length > 0) {
7890
+ let clientVersion = "unknown";
7891
+ try {
7892
+ const parsed = JSON.parse(r.stdout);
7893
+ const clientInfo = parsed.clientVersion;
7894
+ if (typeof clientInfo?.gitVersion === "string") {
7895
+ clientVersion = clientInfo.gitVersion;
7896
+ }
7897
+ } catch {
7898
+ }
7899
+ return { ok: true, message: `kubectl ${clientVersion} on PATH` };
7900
+ }
7901
+ return {
7902
+ ok: false,
7903
+ message: "kubectl not found on PATH",
7904
+ remedy: "Install kubectl: macOS: `brew install kubectl`; Linux: https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/"
7905
+ };
7906
+ }
7907
+ async function probeK3d(exec = defaultDockerExec) {
7908
+ const r = exec("k3d", ["version"]);
7909
+ if (r.status === 0 && r.stdout.length > 0) {
7910
+ const versionLine = r.stdout.trim().split("\n")[0] ?? "";
7911
+ return { ok: true, message: `k3d ${versionLine} on PATH` };
7912
+ }
7913
+ return {
7914
+ ok: false,
7915
+ message: "k3d not found on PATH",
7916
+ remedy: "Install k3d: `brew install k3d` (macOS) or curl -s https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash"
7917
+ };
7918
+ }
7887
7919
  var HEALTH_TIMEOUT_MS, COLIMA_010_WARN_TEXT, COLIMA_010_RANGE, defaultDockerExec, defaultFetch;
7888
7920
  var init_health_probes = __esm({
7889
7921
  "src/lib/health-probes.ts"() {
@@ -11067,7 +11099,19 @@ var init_schema3 = __esm({
11067
11099
  * use canonical names; `olam flywheel migrate-overlays --push` enforces
11068
11100
  * this via the reverse validator.
11069
11101
  */
11070
- prefix: external_exports.string().regex(PREFIX_PATTERN, "skill-source prefix must be ASCII lowercase + digits + dash/underscore (1-39 chars, no leading/trailing dash)").optional()
11102
+ prefix: external_exports.string().regex(PREFIX_PATTERN, "skill-source prefix must be ASCII lowercase + digits + dash/underscore (1-39 chars, no leading/trailing dash)").optional(),
11103
+ /**
11104
+ * Which artifact kinds get renamed when `prefix` is set. Optional; defaults
11105
+ * to `['skill', 'agent']` for back-compat with the original Phase A semantic
11106
+ * (Decision 6 in docs/decisions/019-skill-prefix-rules.md). Operators who
11107
+ * want to rebrand ONLY skills (keeping `@architect` etc. canonical) set
11108
+ * `['skill']`; ones who want only agents set `['agent']`.
11109
+ *
11110
+ * Empty array `[]` = same as omitting `prefix` entirely (no renaming).
11111
+ *
11112
+ * NOT applicable when `prefix` is undefined.
11113
+ */
11114
+ prefixScope: external_exports.array(external_exports.enum(["skill", "agent"])).optional()
11071
11115
  });
11072
11116
  }
11073
11117
  });
@@ -16413,29 +16457,26 @@ function updateSkillSource(id, patch) {
16413
16457
  }
16414
16458
  }
16415
16459
  const existing = config.skillSources[idx];
16416
- let prefixUpdate = {};
16460
+ let working = { ...existing };
16417
16461
  if (patch.prefix === null) {
16418
- const { prefix: _dropped, ...rest } = existing;
16419
- void _dropped;
16420
- const updated2 = {
16421
- ...rest,
16422
- ...patch.name !== void 0 ? { name: patch.name } : {},
16423
- ...patch.branch !== void 0 ? { branch: patch.branch } : {},
16424
- ...patch.lastPulledSha !== void 0 ? { lastPulledSha: patch.lastPulledSha } : {}
16425
- };
16426
- const next2 = [...config.skillSources];
16427
- next2[idx] = updated2;
16428
- writeGlobalConfig({ ...config, skillSources: next2 });
16429
- return updated2;
16462
+ const { prefix: _p, ...rest } = working;
16463
+ void _p;
16464
+ working = rest;
16430
16465
  } else if (patch.prefix !== void 0) {
16431
- prefixUpdate = { prefix: patch.prefix };
16466
+ working = { ...working, prefix: patch.prefix };
16467
+ }
16468
+ if (patch.prefixScope === null) {
16469
+ const { prefixScope: _s, ...rest } = working;
16470
+ void _s;
16471
+ working = rest;
16472
+ } else if (patch.prefixScope !== void 0) {
16473
+ working = { ...working, prefixScope: [...patch.prefixScope] };
16432
16474
  }
16433
16475
  const updated = {
16434
- ...existing,
16476
+ ...working,
16435
16477
  ...patch.name !== void 0 ? { name: patch.name } : {},
16436
16478
  ...patch.branch !== void 0 ? { branch: patch.branch } : {},
16437
- ...patch.lastPulledSha !== void 0 ? { lastPulledSha: patch.lastPulledSha } : {},
16438
- ...prefixUpdate
16479
+ ...patch.lastPulledSha !== void 0 ? { lastPulledSha: patch.lastPulledSha } : {}
16439
16480
  };
16440
16481
  const next = [...config.skillSources];
16441
16482
  next[idx] = updated;
@@ -17249,7 +17290,7 @@ function detectCollisions(artifacts) {
17249
17290
  }
17250
17291
  return { winners, collisions };
17251
17292
  }
17252
- function cleanManagedSymlinks(claude, installedOlamVersion, overlayReferences) {
17293
+ function cleanManagedSymlinks(claude, installedOlamVersion, overlayReferences, expectedAgentWinnerNames) {
17253
17294
  const shadowBackups = [];
17254
17295
  for (const bucket of BUCKETS) {
17255
17296
  const dir = path59.join(claude, bucket);
@@ -17283,8 +17324,11 @@ function cleanManagedSymlinks(claude, installedOlamVersion, overlayReferences) {
17283
17324
  }
17284
17325
  fs58.unlinkSync(p);
17285
17326
  } else if (bucket === "agents" && stat.isFile() && !name.includes(".shadow-backup-")) {
17286
- const backup3 = shadowBackup(p);
17287
- shadowBackups.push(backup3);
17327
+ const hasWinner = expectedAgentWinnerNames !== void 0 ? expectedAgentWinnerNames.has(name) : true;
17328
+ if (hasWinner) {
17329
+ const backup3 = shadowBackup(p);
17330
+ shadowBackups.push(backup3);
17331
+ }
17288
17332
  }
17289
17333
  } catch {
17290
17334
  }
@@ -17324,8 +17368,9 @@ function deployArtifacts(artifacts, opts) {
17324
17368
  for (const bucket of BUCKETS) {
17325
17369
  fs58.mkdirSync(path59.join(claude, bucket), { recursive: true });
17326
17370
  }
17327
- const sweepShadowBackups = cleanManagedSymlinks(claude, opts?.installedOlamVersion, opts?.overlayReferences);
17328
17371
  const { winners, collisions } = detectCollisions(artifacts);
17372
+ const expectedAgentWinnerNames = new Set(winners.filter((a) => a.kind === "agent").map((a) => a.deployBasename));
17373
+ const sweepShadowBackups = cleanManagedSymlinks(claude, opts?.installedOlamVersion, opts?.overlayReferences, expectedAgentWinnerNames);
17329
17374
  const result = { linked: 0, shadowBackups: [...sweepShadowBackups], collisions };
17330
17375
  for (const artifact of winners) {
17331
17376
  const bucket = bucketFor(artifact.kind);
@@ -18860,15 +18905,18 @@ import * as fs68 from "node:fs";
18860
18905
  import * as path67 from "node:path";
18861
18906
  function buildSourcePrefixMap(sources) {
18862
18907
  const byId = /* @__PURE__ */ new Map();
18908
+ const scopeById = /* @__PURE__ */ new Map();
18863
18909
  const prefixes = /* @__PURE__ */ new Set();
18864
18910
  for (const s of sources) {
18865
18911
  if (s.prefix !== void 0 && s.prefix.length > 0) {
18866
18912
  byId.set(s.id, s.prefix);
18913
+ scopeById.set(s.id, s.prefixScope ?? DEFAULT_SCOPE);
18867
18914
  prefixes.add(s.prefix);
18868
18915
  }
18869
18916
  }
18870
18917
  return {
18871
18918
  get: (sourceId) => byId.get(sourceId),
18919
+ getScope: (sourceId) => scopeById.get(sourceId) ?? DEFAULT_SCOPE,
18872
18920
  registeredPrefixes: Array.from(prefixes)
18873
18921
  };
18874
18922
  }
@@ -18880,6 +18928,9 @@ function applyPrefixRewrites(baseArtifacts, sourceMap, claudeDir2, dryRun) {
18880
18928
  const prefix = sourceMap.get(artifact.sourceId);
18881
18929
  if (prefix === void 0)
18882
18930
  continue;
18931
+ const scope = sourceMap.getScope(artifact.sourceId);
18932
+ if (!scope.includes(artifact.kind))
18933
+ continue;
18883
18934
  const canonical = artifact.deployBasename;
18884
18935
  const otherPrefixes = sourceMap.registeredPrefixes.filter((p) => p !== prefix);
18885
18936
  const renamed = applyPrefix(canonical, prefix, otherPrefixes);
@@ -18946,11 +18997,13 @@ function detectPrefixCollisions(sources) {
18946
18997
  }
18947
18998
  return collisions;
18948
18999
  }
19000
+ var DEFAULT_SCOPE;
18949
19001
  var init_prefix_deploy = __esm({
18950
19002
  "../core/dist/skill-sync/prefix-deploy.js"() {
18951
19003
  "use strict";
18952
19004
  init_prefix_rules();
18953
19005
  init_managed_merge();
19006
+ DEFAULT_SCOPE = ["skill", "agent"];
18954
19007
  }
18955
19008
  });
18956
19009
 
@@ -19940,7 +19993,7 @@ __export(project_sweep_exports, {
19940
19993
  walkProjectRoot: () => walkProjectRoot
19941
19994
  });
19942
19995
  import * as path72 from "node:path";
19943
- import { readdirSync as readdirSync24, lstatSync as lstatSync6, statSync as statSync25, existsSync as existsSync81 } from "node:fs";
19996
+ import { readdirSync as readdirSync25, lstatSync as lstatSync6, statSync as statSync25, existsSync as existsSync81 } from "node:fs";
19944
19997
  function isSkipped(basename16, extra) {
19945
19998
  if (DEFAULT_SKIP.has(basename16))
19946
19999
  return true;
@@ -20004,7 +20057,7 @@ function walk2(currentPath, depth, maxDepth, extraExclude, fsAdapter, results) {
20004
20057
  }
20005
20058
  function makeRealFsAdapter() {
20006
20059
  return {
20007
- readdirSync: (p) => readdirSync24(p, { encoding: "utf-8" }),
20060
+ readdirSync: (p) => readdirSync25(p, { encoding: "utf-8" }),
20008
20061
  // lstatSync does NOT follow symlinks — used for descent decisions.
20009
20062
  lstatSync: (p) => {
20010
20063
  const s = lstatSync6(p, { throwIfNoEntry: true });
@@ -20188,6 +20241,7 @@ init_output();
20188
20241
  import * as fs6 from "node:fs";
20189
20242
  import * as path6 from "node:path";
20190
20243
  import { execSync } from "node:child_process";
20244
+ import { randomUUID as randomUUID2 } from "node:crypto";
20191
20245
  import pc3 from "picocolors";
20192
20246
 
20193
20247
  // src/commands/workspace.ts
@@ -20442,9 +20496,11 @@ function writeConfig(updates, opts = {}) {
20442
20496
 
20443
20497
  // src/commands/init.ts
20444
20498
  function detectProjectType(root) {
20445
- if (fs6.existsSync(path6.join(root, "Gemfile")) || fs6.existsSync(path6.join(root, "config", "routes.rb"))) return "rails";
20499
+ if (fs6.existsSync(path6.join(root, "Gemfile")) || fs6.existsSync(path6.join(root, "config", "routes.rb")))
20500
+ return "rails";
20446
20501
  if (fs6.existsSync(path6.join(root, "package.json"))) return "node";
20447
- if (fs6.existsSync(path6.join(root, "pyproject.toml")) || fs6.existsSync(path6.join(root, "requirements.txt"))) return "python";
20502
+ if (fs6.existsSync(path6.join(root, "pyproject.toml")) || fs6.existsSync(path6.join(root, "requirements.txt")))
20503
+ return "python";
20448
20504
  return "generic";
20449
20505
  }
20450
20506
  function getRepoName(root) {
@@ -20461,7 +20517,9 @@ function getRepoName(root) {
20461
20517
  }
20462
20518
  function detectK3dOlamContexts(execFn = (cmd, opts) => execSync(cmd, opts)) {
20463
20519
  try {
20464
- const raw = execFn("kubectl config get-contexts -o name", { encoding: "utf-8" });
20520
+ const raw = execFn("kubectl config get-contexts -o name", {
20521
+ encoding: "utf-8"
20522
+ });
20465
20523
  const contexts = raw.split("\n").map((l) => l.trim()).filter((l) => /^k3d-olam-/.test(l));
20466
20524
  return { contexts };
20467
20525
  } catch {
@@ -20472,11 +20530,14 @@ function applyKubectlContextPin(contexts, opts = {}) {
20472
20530
  const configPath = opts.configPath ?? OLAM_CONFIG_PATH;
20473
20531
  const readFn = opts.readFileSyncFn ?? fs6.readFileSync;
20474
20532
  const writeFn = opts.writeFileSyncFn ?? fs6.writeFileSync;
20533
+ const mkdirFn = opts.mkdirSyncFn ?? fs6.mkdirSync;
20475
20534
  if (contexts.length === 0) {
20476
20535
  return { skipped: "no ^k3d-olam- contexts found" };
20477
20536
  }
20478
20537
  if (contexts.length > 1) {
20479
- return { refused: `multiple ^k3d-olam- contexts found: ${contexts.join(", ")} \u2014 set host.kubectl_context_pinned manually` };
20538
+ return {
20539
+ refused: `multiple ^k3d-olam- contexts found: ${contexts.join(", ")} \u2014 set host.kubectl_context_pinned manually`
20540
+ };
20480
20541
  }
20481
20542
  const context = contexts[0];
20482
20543
  let parsed = {};
@@ -20484,17 +20545,26 @@ function applyKubectlContextPin(contexts, opts = {}) {
20484
20545
  const raw = readFn(configPath, "utf8");
20485
20546
  parsed = JSON.parse(raw);
20486
20547
  } catch {
20487
- parsed = {};
20548
+ parsed = {
20549
+ "config.schema": 1,
20550
+ host: { substrate: "kubernetes" },
20551
+ install_id: randomUUID2()
20552
+ };
20488
20553
  }
20489
20554
  const host = typeof parsed.host === "object" && parsed.host !== null ? parsed.host : {};
20490
20555
  if (typeof host["kubectl_context_pinned"] === "string" && host["kubectl_context_pinned"].length > 0) {
20491
- return { skipped: `kubectl_context_pinned already set to ${host["kubectl_context_pinned"]}` };
20556
+ return {
20557
+ skipped: `kubectl_context_pinned already set to ${host["kubectl_context_pinned"]}`
20558
+ };
20492
20559
  }
20493
20560
  const next = {
20494
20561
  ...parsed,
20495
20562
  host: { ...host, kubectl_context_pinned: context }
20496
20563
  };
20497
- writeFn(configPath, JSON.stringify(next, null, 2) + "\n", { encoding: "utf8" });
20564
+ mkdirFn(path6.dirname(configPath), { recursive: true });
20565
+ writeFn(configPath, JSON.stringify(next, null, 2) + "\n", {
20566
+ encoding: "utf8"
20567
+ });
20498
20568
  return { pinned: context };
20499
20569
  }
20500
20570
  function findProjectRoot(startDir) {
@@ -20570,14 +20640,19 @@ function registerInit(program2) {
20570
20640
  printInfo("Project", `${projectType} (detected)`);
20571
20641
  printInfo("Repo", repoName);
20572
20642
  try {
20573
- const result = ensureProjectWorkspaceFromConfig(projectRoot, repoName);
20643
+ const result = ensureProjectWorkspaceFromConfig(
20644
+ projectRoot,
20645
+ repoName
20646
+ );
20574
20647
  if (result.created) {
20575
20648
  printInfo("Workspace", `${repoName} \u2192 ${result.file}`);
20576
20649
  } else {
20577
20650
  printInfo("Workspace", `${repoName} (already registered)`);
20578
20651
  }
20579
20652
  } catch (err) {
20580
- printError(`Workspace auto-register failed: ${err instanceof Error ? err.message : String(err)}`);
20653
+ printError(
20654
+ `Workspace auto-register failed: ${err instanceof Error ? err.message : String(err)}`
20655
+ );
20581
20656
  }
20582
20657
  const { contexts } = detectK3dOlamContexts();
20583
20658
  if (contexts !== null) {
@@ -20585,12 +20660,16 @@ function registerInit(program2) {
20585
20660
  if ("pinned" in pinResult) {
20586
20661
  printInfo("kubectl context", `${pinResult.pinned} (auto-pinned)`);
20587
20662
  } else if ("refused" in pinResult) {
20588
- process.stderr.write(`${pc3.yellow("warn:")} ${pinResult.refused}
20589
- `);
20663
+ process.stderr.write(
20664
+ `${pc3.yellow("warn:")} ${pinResult.refused}
20665
+ `
20666
+ );
20590
20667
  }
20591
20668
  }
20592
- console.log(`
20593
- ${pc3.dim(`Next: olam create --name my-world --workspace ${repoName} --task "..."`)}`);
20669
+ console.log(
20670
+ `
20671
+ ${pc3.dim(`Next: olam create --name my-world --workspace ${repoName} --task "..."`)}`
20672
+ );
20594
20673
  } catch (err) {
20595
20674
  printError(err instanceof Error ? err.message : String(err));
20596
20675
  process.exitCode = 1;
@@ -20950,7 +21029,13 @@ import pc7 from "picocolors";
20950
21029
  init_output();
20951
21030
  init_install_root();
20952
21031
  import { spawnSync as spawnSync10 } from "node:child_process";
20953
- import { existsSync as existsSync29, mkdirSync as mkdirSync19, writeFileSync as writeFileSync15, chmodSync as chmodSync4 } from "node:fs";
21032
+ import {
21033
+ existsSync as existsSync29,
21034
+ mkdirSync as mkdirSync19,
21035
+ readdirSync as readdirSync9,
21036
+ writeFileSync as writeFileSync15,
21037
+ chmodSync as chmodSync4
21038
+ } from "node:fs";
20954
21039
  import { join as join34 } from "node:path";
20955
21040
  import { homedir as homedir19, platform } from "node:os";
20956
21041
  import { randomBytes as randomBytes7 } from "node:crypto";
@@ -21394,8 +21479,14 @@ var REQUIRED_TOOLS = [
21394
21479
  { name: "kubectl", brew: "kubectl" },
21395
21480
  { name: "helm", brew: "helm" },
21396
21481
  { name: "jq", brew: "jq" },
21397
- { name: "curl", hint: "expected on macOS by default; `apt install curl` on linux" },
21398
- { name: "openssl", hint: "expected on macOS by default; `apt install openssl` on linux" },
21482
+ {
21483
+ name: "curl",
21484
+ hint: "expected on macOS by default; `apt install curl` on linux"
21485
+ },
21486
+ {
21487
+ name: "openssl",
21488
+ hint: "expected on macOS by default; `apt install openssl` on linux"
21489
+ },
21399
21490
  { name: "gh", brew: "gh", hint: "after install: `gh auth login`" },
21400
21491
  { name: "docker", hint: "Docker Desktop, or colima on macOS" }
21401
21492
  ];
@@ -21413,7 +21504,10 @@ var OBSERVABILITY_SCRIPTS = [
21413
21504
  "kyverno-cardinality-mutate.sh"
21414
21505
  ];
21415
21506
  function hasTool(tool) {
21416
- const result = spawnSync10("command", ["-v", tool], { shell: true, stdio: "pipe" });
21507
+ const result = spawnSync10("command", ["-v", tool], {
21508
+ shell: true,
21509
+ stdio: "pipe"
21510
+ });
21417
21511
  return result.status === 0;
21418
21512
  }
21419
21513
  function detectMissingTools() {
@@ -21445,8 +21539,15 @@ function preflight(opts) {
21445
21539
  } else if (opts.autoInstall && platform() === "darwin") {
21446
21540
  const brewable = missing.filter((t) => t.brew);
21447
21541
  if (brewable.length > 0) {
21448
- printInfo("auto-install", `${brewable.length} brew formula(s): ${brewable.map((t) => t.brew).join(", ")}`);
21449
- runOrFail("brew", ["install", ...brewable.map((t) => t.brew)], "brew install");
21542
+ printInfo(
21543
+ "auto-install",
21544
+ `${brewable.length} brew formula(s): ${brewable.map((t) => t.brew).join(", ")}`
21545
+ );
21546
+ runOrFail(
21547
+ "brew",
21548
+ ["install", ...brewable.map((t) => t.brew)],
21549
+ "brew install"
21550
+ );
21450
21551
  }
21451
21552
  const stillMissing = detectMissingTools();
21452
21553
  if (stillMissing.length > 0) {
@@ -21465,7 +21566,9 @@ function preflight(opts) {
21465
21566
  process.stderr.write(` - ${pc6.bold(t.name)}: ${hint}
21466
21567
  `);
21467
21568
  }
21468
- process.stderr.write("\nRe-run with --auto-install on macOS to install brew-formulae automatically.\n");
21569
+ process.stderr.write(
21570
+ "\nRe-run with --auto-install on macOS to install brew-formulae automatically.\n"
21571
+ );
21469
21572
  process.exit(1);
21470
21573
  }
21471
21574
  if (!runCapture("gh", ["auth", "status"]).ok) {
@@ -21501,16 +21604,33 @@ function ensureColima() {
21501
21604
  printWarning("colima not running \u2014 starting");
21502
21605
  runOrFail(
21503
21606
  "colima",
21504
- ["start", "--cpu", "4", "--memory", "8", "--vm-type=vz", "--mount-type=virtiofs"],
21607
+ [
21608
+ "start",
21609
+ "--cpu",
21610
+ "4",
21611
+ "--memory",
21612
+ "8",
21613
+ "--vm-type=vz",
21614
+ "--mount-type=virtiofs"
21615
+ ],
21505
21616
  "colima start"
21506
21617
  );
21507
21618
  }
21508
21619
  printSuccess("colima running");
21509
- const chmod = runCapture("colima", ["ssh", "--", "sudo", "chmod", "666", "/var/run/docker.sock"]);
21620
+ const chmod = runCapture("colima", [
21621
+ "ssh",
21622
+ "--",
21623
+ "sudo",
21624
+ "chmod",
21625
+ "666",
21626
+ "/var/run/docker.sock"
21627
+ ]);
21510
21628
  if (chmod.ok) {
21511
21629
  printSuccess("docker.sock chmod applied inside colima VM");
21512
21630
  } else {
21513
- printWarning("colima ssh chmod failed \u2014 k3d cluster create may still succeed");
21631
+ printWarning(
21632
+ "colima ssh chmod failed \u2014 k3d cluster create may still succeed"
21633
+ );
21514
21634
  }
21515
21635
  }
21516
21636
  function ensureCluster(opts) {
@@ -21526,14 +21646,25 @@ function ensureCluster(opts) {
21526
21646
  const ghConfigBind = `${homedir19()}/.config/gh:/host/.config/gh`;
21527
21647
  runOrFail(
21528
21648
  "k3d",
21529
- ["cluster", "create", opts.cluster, "--volume", ghConfigBind, "--wait", "--timeout", "90s"],
21649
+ [
21650
+ "cluster",
21651
+ "create",
21652
+ opts.cluster,
21653
+ "--volume",
21654
+ ghConfigBind,
21655
+ "--wait",
21656
+ "--timeout",
21657
+ "90s"
21658
+ ],
21530
21659
  `k3d cluster create ${opts.cluster}`
21531
21660
  );
21532
21661
  if (!runCapture("kubectl", ["cluster-info"]).ok) {
21533
21662
  printError("kubectl cannot reach the cluster \u2014 check kubeconfig context");
21534
21663
  process.exit(1);
21535
21664
  }
21536
- printSuccess(`cluster ready (${runCapture("kubectl", ["config", "current-context"]).stdout.trim()})`);
21665
+ printSuccess(
21666
+ `cluster ready (${runCapture("kubectl", ["config", "current-context"]).stdout.trim()})`
21667
+ );
21537
21668
  }
21538
21669
  function resolveObservabilityScriptsDir() {
21539
21670
  const installed = join34(installRoot(), "host-cp", "observability");
@@ -21570,12 +21701,14 @@ function installObservability(opts) {
21570
21701
  step("4/6 \u2014 observability (skipped via --skip-observability)");
21571
21702
  return;
21572
21703
  }
21573
- step("4/6 \u2014 observability stack (Loki + Promtail + Grafana + Prometheus + Kyverno)");
21704
+ step(
21705
+ "4/6 \u2014 observability stack (Loki + Promtail + Grafana + Prometheus + Kyverno)"
21706
+ );
21574
21707
  const observabilityDir = resolveObservabilityScriptsDir();
21575
21708
  if (!observabilityDir) {
21576
- printWarning("bundled observability scripts not found \u2014 skipping");
21577
- printWarning(" (expected in published CLI installs; missing only in dev tarballs without bundling)");
21578
- return;
21709
+ throw new Error(
21710
+ `Bundled observability assets missing. This indicates a corrupt or incomplete @pleri/olam-cli installation. Remediation: reinstall with 'npm install -g @pleri/olam-cli@latest', or open an issue at https://github.com/pleri/olam/issues with this error.`
21711
+ );
21579
21712
  }
21580
21713
  const bundleRoot = resolveBundleRoot();
21581
21714
  const scriptEnv = { ...process.env };
@@ -21589,10 +21722,15 @@ function installObservability(opts) {
21589
21722
  continue;
21590
21723
  }
21591
21724
  printInfo("run", script);
21592
- const result = spawnSync10("bash", [path94], { stdio: "inherit", env: scriptEnv });
21725
+ const result = spawnSync10("bash", [path94], {
21726
+ stdio: "inherit",
21727
+ env: scriptEnv
21728
+ });
21593
21729
  if (result.status !== 0) {
21594
21730
  if (script === "loki-ingest.sh") {
21595
- printWarning("loki-ingest non-zero \u2014 likely Promtail scrub-wait flake; continuing");
21731
+ printWarning(
21732
+ "loki-ingest non-zero \u2014 likely Promtail scrub-wait flake; continuing"
21733
+ );
21596
21734
  } else {
21597
21735
  printError(`${script} failed \u2014 observability stack not ready`);
21598
21736
  process.exit(1);
@@ -21603,7 +21741,9 @@ function installObservability(opts) {
21603
21741
  }
21604
21742
  }
21605
21743
  function applyPeripheralServicesManifests() {
21606
- step("5/6 \u2014 peripheral-services manifests (IngressRoutes + ServiceMonitors + recording rules + dashboards + Kyverno)");
21744
+ step(
21745
+ "5/6 \u2014 peripheral-services manifests (IngressRoutes + ServiceMonitors + recording rules + dashboards + Kyverno)"
21746
+ );
21607
21747
  const dir = resolvePeripheralServicesDir();
21608
21748
  if (!dir) {
21609
21749
  printWarning("bundled peripheral-services manifests not found \u2014 skipping");
@@ -21611,21 +21751,38 @@ function applyPeripheralServicesManifests() {
21611
21751
  }
21612
21752
  const manifestsDir = join34(dir, "manifests");
21613
21753
  if (!existsSync29(manifestsDir)) {
21614
- printWarning(`peripheral-services/manifests not found at ${manifestsDir} \u2014 skipping`);
21754
+ printWarning(
21755
+ `peripheral-services/manifests not found at ${manifestsDir} \u2014 skipping`
21756
+ );
21615
21757
  return;
21616
21758
  }
21617
- printInfo("apply", manifestsDir);
21618
- const result = spawnSync10("kubectl", ["apply", "-f", manifestsDir], { stdio: "inherit" });
21619
- if (result.status !== 0) {
21620
- printError("kubectl apply failed \u2014 peripheral-services manifests not applied");
21621
- process.exit(result.status ?? 1);
21759
+ const manifestFiles = readdirSync9(manifestsDir).filter(
21760
+ (f) => (f.endsWith(".yaml") || f.endsWith(".yml")) && !/deploy/i.test(f)
21761
+ ).map((f) => join34(manifestsDir, f));
21762
+ if (manifestFiles.length === 0) {
21763
+ printWarning(
21764
+ "no non-deployment peripheral-services manifests found \u2014 skipping"
21765
+ );
21766
+ return;
21767
+ }
21768
+ for (const manifest of manifestFiles) {
21769
+ printInfo("apply", manifest.replace(manifestsDir + "/", ""));
21770
+ const r = spawnSync10("kubectl", ["apply", "-f", manifest], {
21771
+ stdio: "inherit"
21772
+ });
21773
+ if (r.status !== 0) {
21774
+ printError(`kubectl apply failed for ${manifest}`);
21775
+ process.exit(r.status ?? 1);
21776
+ }
21622
21777
  }
21623
21778
  printSuccess("peripheral-services manifests applied");
21624
21779
  }
21625
21780
  function delegateToUpgrade() {
21626
21781
  step("6/6 \u2014 apply host-cp + peripherals + rollout (olam upgrade)");
21627
21782
  const olamBin = process.argv[1] ?? "olam";
21628
- const result = spawnSync10(process.execPath, [olamBin, "upgrade", "-y"], { stdio: "inherit" });
21783
+ const result = spawnSync10(process.execPath, [olamBin, "upgrade", "-y"], {
21784
+ stdio: "inherit"
21785
+ });
21629
21786
  if (result.status !== 0) {
21630
21787
  printError("olam upgrade failed \u2014 see output above");
21631
21788
  process.exit(result.status ?? 1);
@@ -21657,7 +21814,10 @@ async function runBootstrapKubernetes(rawOpts) {
21657
21814
  skipClusterCreate: rawOpts.skipClusterCreate ?? false
21658
21815
  };
21659
21816
  printHeader("olam setup \u2014 k3s mode");
21660
- printInfo("mode", "one-command bring-up of olam peripherals + observability on a local k3d cluster");
21817
+ printInfo(
21818
+ "mode",
21819
+ "one-command bring-up of olam peripherals + observability on a local k3d cluster"
21820
+ );
21661
21821
  preflight(opts);
21662
21822
  ensureSecrets();
21663
21823
  ensureColima();
@@ -21750,6 +21910,9 @@ async function runBootstrap2(opts, deps = {}) {
21750
21910
  if (digests["kg-service"] && !opts.skipKgService) {
21751
21911
  imageRefs.push({ name: "kg-service", ref: `${registry}/olam-kg-service@${digests["kg-service"]}` });
21752
21912
  }
21913
+ if (digests["memory-service"] && !opts.skipMemory) {
21914
+ imageRefs.push({ name: "memory-service", ref: `${registry}/olam-memory-service@${digests["memory-service"]}` });
21915
+ }
21753
21916
  if (digests["devbox-base"]) {
21754
21917
  imageRefs.push({
21755
21918
  name: "devbox-base",
@@ -21827,6 +21990,10 @@ async function runBootstrap2(opts, deps = {}) {
21827
21990
  if (kgServiceRef) {
21828
21991
  tagPlan.push({ name: "kg-service", from: kgServiceRef.ref, to: "olam-kg-service:local" });
21829
21992
  }
21993
+ const memoryServiceRef = imageRefs.find((r) => r.name === "memory-service");
21994
+ if (memoryServiceRef) {
21995
+ tagPlan.push({ name: "memory-service", from: memoryServiceRef.ref, to: "olam-memory-service:local" });
21996
+ }
21830
21997
  const devboxBaseRef = imageRefs.find((r) => r.name === "devbox-base");
21831
21998
  if (devboxBaseRef) {
21832
21999
  tagPlan.push({ name: "devbox-base (bare)", from: devboxBaseRef.ref, to: "olam-devbox:base" });
@@ -22617,6 +22784,7 @@ function writeCloudMemorySecret(value, path94 = CLOUD_MEMORY_SECRET_PATH) {
22617
22784
  }
22618
22785
 
22619
22786
  // src/commands/memory-service-container.ts
22787
+ init_output();
22620
22788
  var MEMORY_SERVICE_PORT = 3111;
22621
22789
  var MEMORY_SERVICE_CONTAINER = "olam-memory-service";
22622
22790
  var MEMORY_SERVICE_LOCAL_TAG = "olam-memory-service:local";
@@ -22651,13 +22819,27 @@ var MemoryServiceContainerController = class {
22651
22819
  }
22652
22820
  /**
22653
22821
  * Resolve the first available image tag: local → dev → published.
22822
+ *
22823
+ * The preferred path is :local (populated by `olam bootstrap` pulling the
22824
+ * digest-pinned image and tagging it). The :published fallback is `:latest`
22825
+ * which is NOT digest-pinned — Docker will pull whatever tag-head is on
22826
+ * the registry at pull time. This is the degraded-mode path for operators
22827
+ * whose image-digests.json predates the G1 memory-service digest fix; it
22828
+ * is intentionally loud so they know they are not on the pinned path.
22829
+ *
22654
22830
  * Throws if none exist (operator should run `olam bootstrap` to pull, or
22655
22831
  * `node packages/cli/scripts/build-memory-service-image.mjs` to build locally).
22656
22832
  */
22657
22833
  resolveImage() {
22658
- for (const tag of [MEMORY_SERVICE_LOCAL_TAG, MEMORY_SERVICE_DEV_TAG, MEMORY_SERVICE_PUBLISHED_TAG]) {
22834
+ for (const tag of [MEMORY_SERVICE_LOCAL_TAG, MEMORY_SERVICE_DEV_TAG]) {
22659
22835
  if (this.imageExists(tag)) return tag;
22660
22836
  }
22837
+ if (this.imageExists(MEMORY_SERVICE_PUBLISHED_TAG)) {
22838
+ printWarning(
22839
+ `memory-service: using tag-pulled fallback image (${MEMORY_SERVICE_PUBLISHED_TAG}). This image was NOT pulled by digest. Run \`olam bootstrap\` to pull the digest-pinned image and eliminate this warning.`
22840
+ );
22841
+ return MEMORY_SERVICE_PUBLISHED_TAG;
22842
+ }
22661
22843
  throw new Error(
22662
22844
  `memory-service image not found. Tried: ${MEMORY_SERVICE_LOCAL_TAG}, ${MEMORY_SERVICE_DEV_TAG}, ${MEMORY_SERVICE_PUBLISHED_TAG}. Run \`olam bootstrap\` to pull the published image, or \`node packages/cli/scripts/build-memory-service-image.mjs\` to build locally.`
22663
22845
  );
@@ -22773,7 +22955,7 @@ var PERIPHERALS = [
22773
22955
  {
22774
22956
  name: "auth-service",
22775
22957
  port: 9999,
22776
- k8sServiceName: "auth-service",
22958
+ k8sResourceName: "olam-auth-service",
22777
22959
  composeContainerName: "olam-auth-service",
22778
22960
  configMapKeyInHostCp: "OLAM_AUTH_SERVICE_URL",
22779
22961
  healthPath: "/health"
@@ -22781,7 +22963,7 @@ var PERIPHERALS = [
22781
22963
  {
22782
22964
  name: "mcp-auth-service",
22783
22965
  port: 9998,
22784
- k8sServiceName: "mcp-auth-service",
22966
+ k8sResourceName: "olam-mcp-auth-service",
22785
22967
  composeContainerName: "olam-mcp-auth-service",
22786
22968
  configMapKeyInHostCp: "OLAM_MCP_AUTH_SERVICE_URL",
22787
22969
  healthPath: "/health"
@@ -22789,7 +22971,7 @@ var PERIPHERALS = [
22789
22971
  {
22790
22972
  name: "kg-service",
22791
22973
  port: 9997,
22792
- k8sServiceName: "kg-service",
22974
+ k8sResourceName: "olam-kg-service",
22793
22975
  composeContainerName: "olam-kg-service",
22794
22976
  configMapKeyInHostCp: "OLAM_KG_SERVICE_URL",
22795
22977
  healthPath: "/health"
@@ -22797,7 +22979,7 @@ var PERIPHERALS = [
22797
22979
  {
22798
22980
  name: "memory-service",
22799
22981
  port: 3111,
22800
- k8sServiceName: "memory-service",
22982
+ k8sResourceName: "olam-memory-service",
22801
22983
  composeContainerName: "olam-memory-service",
22802
22984
  configMapKeyInHostCp: "OLAM_MEMORY_SERVICE_URL",
22803
22985
  healthPath: "/agentmemory/livez"
@@ -23455,7 +23637,7 @@ async function spawnPeripheralPortForward(peripheral, context, namespace, deps =
23455
23637
  "port-forward",
23456
23638
  "-n",
23457
23639
  namespace,
23458
- `service/${peripheral.k8sServiceName}`,
23640
+ `service/${peripheral.k8sResourceName}`,
23459
23641
  `${peripheral.port}:${peripheral.port}`
23460
23642
  ],
23461
23643
  {
@@ -23654,8 +23836,8 @@ function appendAuditEntry(entry, auditLogPath, writeFileSyncImpl) {
23654
23836
  }
23655
23837
  async function runManifestRefresh(manifestsDir, acceptRegression, deps = {}, peripheral) {
23656
23838
  const auditLogPath = deps.auditLogPath ?? MANIFEST_REFRESH_AUDIT_LOG;
23657
- const readdirSync32 = deps.readdirSync ?? fs31.readdirSync;
23658
- const readFileSync92 = deps.readFileSync ?? fs31.readFileSync;
23839
+ const readdirSync33 = deps.readdirSync ?? fs31.readdirSync;
23840
+ const readFileSync93 = deps.readFileSync ?? fs31.readFileSync;
23659
23841
  const writeFileSyncImpl = deps.writeFileSync ?? fs31.writeFileSync;
23660
23842
  const existsSync112 = deps.existsSync ?? fs31.existsSync;
23661
23843
  const now = deps.now ? deps.now() : /* @__PURE__ */ new Date();
@@ -23668,7 +23850,7 @@ async function runManifestRefresh(manifestsDir, acceptRegression, deps = {}, per
23668
23850
  }
23669
23851
  let files;
23670
23852
  try {
23671
- const entries = readdirSync32(targetDir, { withFileTypes: true });
23853
+ const entries = readdirSync33(targetDir, { withFileTypes: true });
23672
23854
  files = entries.filter((e) => e.isFile() && (e.name.endsWith(".yaml") || e.name.endsWith(".json"))).map((e) => e.name);
23673
23855
  } catch (err) {
23674
23856
  return {
@@ -23681,7 +23863,7 @@ async function runManifestRefresh(manifestsDir, acceptRegression, deps = {}, per
23681
23863
  const filePath = path32.join(targetDir, file);
23682
23864
  let content;
23683
23865
  try {
23684
- content = readFileSync92(filePath, "utf8");
23866
+ content = readFileSync93(filePath, "utf8");
23685
23867
  } catch {
23686
23868
  continue;
23687
23869
  }
@@ -23831,9 +24013,8 @@ var PERIPHERAL_SECRETS = [
23831
24013
  { name: "memory-service", secretName: "olam-memory-service-secret", keys: ["OLAM_MEMORY_BEARER_SECRET"] }
23832
24014
  ];
23833
24015
  var K8S_DNS_SUFFIX = "olam.svc.cluster.local";
23834
- function buildK8sDnsUrl(k8sServiceName, port2) {
23835
- const prefixed = k8sServiceName.startsWith("olam-") ? k8sServiceName : `olam-${k8sServiceName}`;
23836
- return `http://${prefixed}.${K8S_DNS_SUFFIX}:${port2}`;
24016
+ function buildK8sDnsUrl(k8sResourceName, port2) {
24017
+ return `http://${k8sResourceName}.${K8S_DNS_SUFFIX}:${port2}`;
23837
24018
  }
23838
24019
  function appendSubstrateAuditEntry(entry, stderr) {
23839
24020
  try {
@@ -23933,11 +24114,11 @@ async function checkSecretPreCondition(context, deps) {
23933
24114
  }
23934
24115
  async function applyConfigMapSubstitution(context, manifestsDir, deps) {
23935
24116
  const wrap = deps.kubectlWrapImpl ?? kubectlWrap;
23936
- const readFileSync92 = deps.readFileSyncImpl ?? fs32.readFileSync;
24117
+ const readFileSync93 = deps.readFileSyncImpl ?? fs32.readFileSync;
23937
24118
  const configMapPath = path33.join(manifestsDir, "30-configmap.yaml");
23938
24119
  let rawYaml;
23939
24120
  try {
23940
- rawYaml = readFileSync92(configMapPath, "utf8");
24121
+ rawYaml = readFileSync93(configMapPath, "utf8");
23941
24122
  } catch (err) {
23942
24123
  return `Failed to read ConfigMap at ${configMapPath}: ${err instanceof Error ? err.message : String(err)}`;
23943
24124
  }
@@ -23949,7 +24130,7 @@ async function applyConfigMapSubstitution(context, manifestsDir, deps) {
23949
24130
  }
23950
24131
  const data = parsed["data"] ?? {};
23951
24132
  for (const peripheral of PERIPHERALS) {
23952
- data[peripheral.configMapKeyInHostCp] = buildK8sDnsUrl(peripheral.k8sServiceName, peripheral.port);
24133
+ data[peripheral.configMapKeyInHostCp] = buildK8sDnsUrl(peripheral.k8sResourceName, peripheral.port);
23953
24134
  }
23954
24135
  parsed["data"] = data;
23955
24136
  const patchedYaml = yamlStringify(parsed);
@@ -24302,10 +24483,10 @@ async function runUpgradeKubernetes(opts = {}, deps = {}) {
24302
24483
  return { exitCode: 1, summary: "configmap substitution failed" };
24303
24484
  }
24304
24485
  step35Spinner.succeed("ConfigMap patched with K8s DNS URLs (survives bulk apply)");
24305
- const step4Spinner = ora3("Waiting for rollout (all 5 deployments, 90s each)").start();
24486
+ const step4Spinner = ora3("Waiting for rollout (all 5 deployments, 300s each)").start();
24306
24487
  const deploymentNames = [
24307
24488
  HOST_CP_DEPLOYMENT_NAME,
24308
- ...PERIPHERALS.map((p) => p.name)
24489
+ ...PERIPHERALS.map((p) => p.k8sResourceName)
24309
24490
  ];
24310
24491
  const rolloutResults = await Promise.all(
24311
24492
  deploymentNames.map(
@@ -24318,18 +24499,28 @@ async function runUpgradeKubernetes(opts = {}, deps = {}) {
24318
24499
  `deployment/${deploymentName}`,
24319
24500
  "-n",
24320
24501
  K8S_NAMESPACE3,
24321
- "--timeout=90s"
24502
+ "--timeout=300s"
24322
24503
  ],
24323
- { timeout: 95e3 }
24504
+ { timeout: 305e3 }
24324
24505
  )
24325
24506
  )
24326
24507
  );
24327
24508
  const failedDeployments = deploymentNames.filter((_, i) => !rolloutResults[i]?.ok);
24328
24509
  if (failedDeployments.length > 0) {
24329
24510
  step4Spinner.fail(`Rollout failed for: ${failedDeployments.join(", ")}`);
24330
- for (const name of failedDeployments) {
24511
+ for (const [i, name] of failedDeployments.entries()) {
24512
+ const result = rolloutResults[deploymentNames.indexOf(name)];
24331
24513
  stderr.write(`${pc10.red("error:")} rollout status failed for deployment/${name}.
24332
24514
  `);
24515
+ if (result && (result.stdout || result.stderr)) {
24516
+ stderr.write(`${pc10.dim("--- kubectl rollout status output ---")}
24517
+ `);
24518
+ if (result.stdout) stderr.write(result.stdout + "\n");
24519
+ if (result.stderr) stderr.write(result.stderr + "\n");
24520
+ stderr.write(`${pc10.dim(`exit code: ${result.exitCode ?? "(none)"}, reason: ${"reason" in result ? result.reason : "ok"}`)}
24521
+ `);
24522
+ }
24523
+ void i;
24333
24524
  stderr.write(`${pc10.dim(`--- kubectl get pods -n ${K8S_NAMESPACE3} -o wide ---`)}
24334
24525
  `);
24335
24526
  const podsResult = await wrap(
@@ -34550,7 +34741,7 @@ async function runPeripheralProbes(peripheral, startPosition, kubectlContext, wr
34550
34741
  [
34551
34742
  ...ctxArgs,
34552
34743
  "exec",
34553
- `deploy/${peripheral.name}`,
34744
+ `deploy/${peripheral.k8sResourceName}`,
34554
34745
  "-n",
34555
34746
  K8S_NAMESPACE5,
34556
34747
  "--",
@@ -34567,7 +34758,7 @@ async function runPeripheralProbes(peripheral, startPosition, kubectlContext, wr
34567
34758
  return {
34568
34759
  ok: false,
34569
34760
  message: `${peripheral.name} pod not reachable`,
34570
- remedy: `Check pod logs: kubectl logs deploy/${peripheral.name} -n ${K8S_NAMESPACE5}. stderr: ${errLine}`
34761
+ remedy: `Check pod logs: kubectl logs deploy/${peripheral.k8sResourceName} -n ${K8S_NAMESPACE5}. stderr: ${errLine}`
34571
34762
  };
34572
34763
  })();
34573
34764
  rows.push({ name: `${peripheral.name} reachable`, result: reachableResult, position: startPosition });
@@ -34612,7 +34803,7 @@ async function runPeripheralProbes(peripheral, startPosition, kubectlContext, wr
34612
34803
  "-n",
34613
34804
  K8S_NAMESPACE5,
34614
34805
  "-l",
34615
- `app=${peripheral.name}`,
34806
+ `app=${peripheral.k8sResourceName}`,
34616
34807
  "-o",
34617
34808
  `jsonpath={.items[0].status.containerStatuses[0].image}`
34618
34809
  ],
@@ -34626,7 +34817,7 @@ async function runPeripheralProbes(peripheral, startPosition, kubectlContext, wr
34626
34817
  ok: true,
34627
34818
  warn: true,
34628
34819
  message: `${peripheral.name} image not detected`,
34629
- remedy: `Verify pod is running: kubectl get pods -n ${K8S_NAMESPACE5} -l app=${peripheral.name}`
34820
+ remedy: `Verify pod is running: kubectl get pods -n ${K8S_NAMESPACE5} -l app=${peripheral.k8sResourceName}`
34630
34821
  };
34631
34822
  })();
34632
34823
  rows.push({ name: `${peripheral.name} image`, result: imagePresentResult, position: startPosition + 2 });
@@ -35314,10 +35505,10 @@ function parseTracker(path94) {
35314
35505
  }
35315
35506
 
35316
35507
  // ../cli-plugin-tasks/dist/commands/workers.js
35317
- import { randomUUID as randomUUID2 } from "node:crypto";
35508
+ import { randomUUID as randomUUID3 } from "node:crypto";
35318
35509
  var SUBSTRATES = ["mac-mini", "cloudflare-container", "gcp-vm"];
35319
35510
  function generateProvisionPlan(opts) {
35320
- const workerId = opts.workerId ?? `worker-${randomUUID2().slice(0, 8)}`;
35511
+ const workerId = opts.workerId ?? `worker-${randomUUID3().slice(0, 8)}`;
35321
35512
  const hostCpUrl = opts.hostCpUrl ?? "http://localhost:19000";
35322
35513
  const nodeId = opts.olamNodeId ?? `<OLAM_NODE_ID>`;
35323
35514
  const sessionId = opts.sessionId ?? `<OLAM_SESSION_ID>`;
@@ -35667,8 +35858,8 @@ function registerCompletion(program2) {
35667
35858
  // src/commands/setup.ts
35668
35859
  init_cli_version();
35669
35860
  init_health_probes();
35670
- import { spawn as spawn7 } from "node:child_process";
35671
- import { existsSync as existsSync84 } from "node:fs";
35861
+ import { spawn as spawn7, spawnSync as spawnSync26 } from "node:child_process";
35862
+ import { existsSync as existsSync84, readFileSync as readFileSync68 } from "node:fs";
35672
35863
  import { homedir as homedir43 } from "node:os";
35673
35864
  import path75 from "node:path";
35674
35865
  import { createInterface as createInterface3 } from "node:readline";
@@ -35996,10 +36187,17 @@ async function runProjectSweepPhase(opts, deps, sweepDeps = {}) {
35996
36187
 
35997
36188
  // src/commands/setup.ts
35998
36189
  var REQUIRED_NODE_MAJOR = 20;
35999
- var NEXT_STEPS_DOCS = [
36000
- "docs/architecture/devbox-contract.md \u2014 image contract",
36001
- "docs/architecture/manifest-spec.md \u2014 per-repo .adb.yaml schema",
36002
- "docs/architecture/config-spec.md \u2014 workspace .olam/config.yaml schema"
36190
+ var SETUP_K3D_CLUSTER_NAME = "olam-dev";
36191
+ var NEXT_STEPS_DOCS_DOCKER = [
36192
+ "https://github.com/pleri/olam/blob/main/docs/architecture/devbox-contract.md \u2014 image contract",
36193
+ "https://github.com/pleri/olam/blob/main/docs/architecture/manifest-spec.md \u2014 per-repo .adb.yaml schema",
36194
+ "https://github.com/pleri/olam/blob/main/docs/architecture/config-spec.md \u2014 workspace .olam/config.yaml schema"
36195
+ ];
36196
+ var NEXT_STEPS_DOCS_KUBERNETES = [
36197
+ "https://github.com/pleri/olam/blob/main/docs/architecture/devbox-contract.md \u2014 image contract",
36198
+ "https://github.com/pleri/olam/blob/main/docs/architecture/manifest-spec.md \u2014 per-repo .adb.yaml schema",
36199
+ "https://github.com/pleri/olam/blob/main/docs/architecture/config-spec.md \u2014 workspace .olam/config.yaml schema",
36200
+ "https://github.com/pleri/olam/blob/main/docs/k8s/SETUP.md \u2014 k3d operator guide"
36003
36201
  ];
36004
36202
  var defaultSpawn = (cmd, args) => new Promise((resolve27) => {
36005
36203
  const child = spawn7(cmd, [...args], { stdio: "inherit" });
@@ -36028,14 +36226,61 @@ var defaultPrompt = (question, defaultYes) => {
36028
36226
  rl.on("close", () => resolve27(defaultYes));
36029
36227
  });
36030
36228
  };
36031
- async function phase1SystemCheck(deps) {
36229
+ var DOCKER_MIGRATION_HINT = "olam: detected existing docker stack \u2014 continuing on docker. To migrate to k3d, run: olam upgrade --substrate=kubernetes (available in a future release).";
36230
+ function resolveSubstrate(opts, deps) {
36231
+ if (opts.substrate === "kubernetes") return "kubernetes";
36232
+ if (opts.substrate === "docker") return "docker";
36233
+ const configPath = deps.configPath ?? OLAM_CONFIG_PATH;
36234
+ if (existsSync84(configPath)) {
36235
+ try {
36236
+ const raw = readFileSync68(configPath, "utf8");
36237
+ const parsed = JSON.parse(raw);
36238
+ const host = parsed.host;
36239
+ if (host?.substrate === "kubernetes") return "kubernetes";
36240
+ if (host?.substrate === "compose") {
36241
+ process.stderr.write(DOCKER_MIGRATION_HINT + "\n");
36242
+ return "docker";
36243
+ }
36244
+ } catch {
36245
+ }
36246
+ }
36247
+ return "kubernetes";
36248
+ }
36249
+ async function phase1SystemCheck(substrate, deps) {
36250
+ const nodeVersion = deps.nodeVersion ?? process.version;
36251
+ const nodeMajor = parseNodeMajor(nodeVersion);
36252
+ const nodeOk = nodeMajor !== null && nodeMajor >= REQUIRED_NODE_MAJOR;
36253
+ if (substrate === "kubernetes") {
36254
+ const kubectlProbe = await probeKubectl(deps.dockerExec);
36255
+ if (!kubectlProbe.ok) {
36256
+ return phaseFromProbe(kubectlProbe);
36257
+ }
36258
+ const dockerProbe2 = await probeDockerDaemon(deps.dockerExec);
36259
+ if (!dockerProbe2.ok) {
36260
+ return {
36261
+ ok: false,
36262
+ message: "docker daemon required for k3d: " + dockerProbe2.message,
36263
+ remedy: dockerProbe2.remedy
36264
+ };
36265
+ }
36266
+ if (!nodeOk) {
36267
+ return {
36268
+ ok: false,
36269
+ message: `Node.js ${nodeVersion} is too old (need \u2265${REQUIRED_NODE_MAJOR})`,
36270
+ remedy: `Install Node.js ${REQUIRED_NODE_MAJOR}+ LTS via nvm/fnm/asdf and re-run \`olam setup\`.`
36271
+ };
36272
+ }
36273
+ return { ok: true, message: `kubectl on PATH; docker ready; node ${nodeVersion}` };
36274
+ }
36032
36275
  const dockerProbe = await probeDockerDaemon(deps.dockerExec);
36033
36276
  if (!dockerProbe.ok) {
36034
36277
  return phaseFromProbe(dockerProbe);
36035
36278
  }
36036
- const nodeVersion = deps.nodeVersion ?? process.version;
36037
- const nodeMajor = parseNodeMajor(nodeVersion);
36038
- if (nodeMajor === null || nodeMajor < REQUIRED_NODE_MAJOR) {
36279
+ const composeProbe = await probeComposePlugin(deps.dockerExec);
36280
+ if (!composeProbe.ok) {
36281
+ return phaseFromProbe(composeProbe);
36282
+ }
36283
+ if (!nodeOk) {
36039
36284
  return {
36040
36285
  ok: false,
36041
36286
  message: `Node.js ${nodeVersion} is too old (need \u2265${REQUIRED_NODE_MAJOR})`,
@@ -36044,9 +36289,68 @@ async function phase1SystemCheck(deps) {
36044
36289
  }
36045
36290
  return {
36046
36291
  ok: true,
36047
- message: `${dockerProbe.message}; node ${nodeVersion}`
36292
+ message: `${dockerProbe.message}; ${composeProbe.message}; node ${nodeVersion}`
36048
36293
  };
36049
36294
  }
36295
+ async function phase1_5InstallSubstrate(substrate, opts, deps) {
36296
+ if (substrate === "docker") {
36297
+ return { ok: true, skipped: true, message: "no-op for docker substrate" };
36298
+ }
36299
+ const spawnFn = deps.spawnSubprocess ?? defaultSpawn;
36300
+ const promptFn = deps.prompt ?? defaultPrompt;
36301
+ const k3dProbe = await probeK3d(deps.dockerExec);
36302
+ if (k3dProbe.ok) {
36303
+ return { ok: true, message: `k3d already present: ${k3dProbe.message}` };
36304
+ }
36305
+ const hasBrew = spawnSync26("command", ["-v", "brew"], { shell: true, stdio: "pipe" }).status === 0;
36306
+ const useBrewMsg = hasBrew ? "Homebrew" : "upstream install script";
36307
+ if (!opts.yes) {
36308
+ const confirmed = await promptFn(
36309
+ `k3d is not installed. Install via ${useBrewMsg}?`,
36310
+ true
36311
+ );
36312
+ if (!confirmed) {
36313
+ return {
36314
+ ok: false,
36315
+ message: "k3d install declined; required for kubernetes substrate",
36316
+ remedy: "Install manually: `brew install k3d` or curl -s https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash, then re-run `olam setup --substrate=kubernetes`."
36317
+ };
36318
+ }
36319
+ }
36320
+ if (hasBrew) {
36321
+ process.stdout.write("Installing k3d via Homebrew...\n");
36322
+ const r = await spawnFn("brew", ["install", "k3d"]);
36323
+ if (r.status !== 0) {
36324
+ return {
36325
+ ok: false,
36326
+ message: `brew install k3d failed (exit ${r.status ?? "signal"})`,
36327
+ remedy: "Try manually: `brew install k3d` or curl -s https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash"
36328
+ };
36329
+ }
36330
+ } else {
36331
+ process.stdout.write("Installing k3d via upstream install script...\n");
36332
+ const r = await spawnFn("bash", [
36333
+ "-c",
36334
+ "curl -s https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash"
36335
+ ]);
36336
+ if (r.status !== 0) {
36337
+ return {
36338
+ ok: false,
36339
+ message: `k3d upstream install failed (exit ${r.status ?? "signal"})`,
36340
+ remedy: "Install manually: https://k3d.io/v5.x/installation/"
36341
+ };
36342
+ }
36343
+ }
36344
+ const k3dCheck = await probeK3d(deps.dockerExec);
36345
+ if (!k3dCheck.ok) {
36346
+ return {
36347
+ ok: false,
36348
+ message: "k3d still not on PATH after install \u2014 shell PATH may need updating",
36349
+ remedy: "Close and re-open your terminal, then re-run `olam setup --substrate=kubernetes`."
36350
+ };
36351
+ }
36352
+ return { ok: true, message: `k3d installed: ${k3dCheck.message}` };
36353
+ }
36050
36354
  function parseNodeMajor(version) {
36051
36355
  const m = version.match(/^v?(\d+)\./);
36052
36356
  if (!m) return null;
@@ -36057,6 +36361,65 @@ function phaseFromProbe(probe2) {
36057
36361
  if (probe2.ok) return { ok: true, message: probe2.message };
36058
36362
  return { ok: false, message: probe2.message, remedy: probe2.remedy };
36059
36363
  }
36364
+ async function phase2_5ProvisionCluster(substrate, opts, deps) {
36365
+ if (substrate === "docker") {
36366
+ return { ok: true, skipped: true, message: "no-op for docker substrate" };
36367
+ }
36368
+ const spawnFn = deps.spawnSubprocess ?? defaultSpawn;
36369
+ const listResult = await captureSpawn("k3d", ["cluster", "list", "--output", "json"], deps.dockerExec);
36370
+ if (listResult.ok) {
36371
+ const exists = clusterExistsInList(listResult.stdout, SETUP_K3D_CLUSTER_NAME);
36372
+ if (exists) {
36373
+ return {
36374
+ ok: true,
36375
+ message: `cluster ${SETUP_K3D_CLUSTER_NAME} already exists; skipping create`
36376
+ };
36377
+ }
36378
+ }
36379
+ process.stdout.write(`Creating k3d cluster ${SETUP_K3D_CLUSTER_NAME}...
36380
+ `);
36381
+ const ghConfigBind = `${homedir43()}/.config/gh:/host/.config/gh`;
36382
+ const createResult = await spawnFn("k3d", [
36383
+ "cluster",
36384
+ "create",
36385
+ SETUP_K3D_CLUSTER_NAME,
36386
+ "--volume",
36387
+ ghConfigBind,
36388
+ "--wait",
36389
+ "--timeout",
36390
+ "90s"
36391
+ ]);
36392
+ if (createResult.status !== 0) {
36393
+ return {
36394
+ ok: false,
36395
+ message: `k3d cluster create ${SETUP_K3D_CLUSTER_NAME} failed (exit ${createResult.status ?? "signal"})`,
36396
+ remedy: `Run manually: k3d cluster create ${SETUP_K3D_CLUSTER_NAME} --wait --timeout 90s`
36397
+ };
36398
+ }
36399
+ const ctxResult = captureSpawnSync("kubectl", ["config", "current-context"]);
36400
+ const ctx = ctxResult.ok ? ctxResult.stdout.trim() : "(unknown)";
36401
+ return { ok: true, message: `cluster ${SETUP_K3D_CLUSTER_NAME} created; context: ${ctx}` };
36402
+ }
36403
+ async function captureSpawn(cmd, args, dockerExec) {
36404
+ const exec = dockerExec ?? ((c, a) => {
36405
+ const r2 = spawnSync26(c, [...a], { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] });
36406
+ return { status: r2.status, stdout: r2.stdout ?? "", stderr: r2.stderr ?? "" };
36407
+ });
36408
+ const r = exec(cmd, args);
36409
+ return { ok: r.status === 0, stdout: r.stdout, stderr: r.stderr };
36410
+ }
36411
+ function captureSpawnSync(cmd, args) {
36412
+ const r = spawnSync26(cmd, [...args], { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] });
36413
+ return { ok: r.status === 0, stdout: r.stdout ?? "" };
36414
+ }
36415
+ function clusterExistsInList(json, clusterName) {
36416
+ try {
36417
+ const parsed = JSON.parse(json);
36418
+ return Array.isArray(parsed) && parsed.some((c) => c.name === clusterName);
36419
+ } catch {
36420
+ return false;
36421
+ }
36422
+ }
36060
36423
  async function phase2CliSanity(deps) {
36061
36424
  const version = deps.olamCliVersion ?? safeReadCliVersion();
36062
36425
  if (!version || version === "unknown") {
@@ -36075,11 +36438,36 @@ function safeReadCliVersion() {
36075
36438
  return null;
36076
36439
  }
36077
36440
  }
36078
- async function phase3Bootstrap(deps) {
36441
+ async function phase2_6PinKubectlContext(substrate, deps) {
36442
+ if (substrate !== "kubernetes") {
36443
+ return { ok: true, skipped: true, message: "no-op for docker substrate" };
36444
+ }
36445
+ const expectedContext = `k3d-${SETUP_K3D_CLUSTER_NAME}`;
36446
+ writeConfig({ host: { substrate: "kubernetes" } }, { configPath: deps.configPath });
36447
+ const result = applyKubectlContextPin([expectedContext], {
36448
+ configPath: deps.configPath
36449
+ });
36450
+ if ("pinned" in result) {
36451
+ return { ok: true, message: `kubectl context pinned: ${result.pinned}` };
36452
+ }
36453
+ if ("skipped" in result) {
36454
+ return { ok: true, message: `kubectl context already pinned (${result.skipped})` };
36455
+ }
36456
+ return {
36457
+ ok: false,
36458
+ message: `kubectl context pin refused: ${result.refused}`,
36459
+ remedy: `Set host.kubectl_context_pinned = ${expectedContext} in ~/.olam/config.json and re-run.`
36460
+ };
36461
+ }
36462
+ async function phase3Bootstrap(substrate, deps) {
36079
36463
  const spawnFn = deps.spawnSubprocess ?? defaultSpawn;
36080
- const r = await spawnFn("olam", ["bootstrap", "--skip-auth-login"]);
36464
+ const bootstrapArgs = ["bootstrap", "--skip-auth-login"];
36465
+ if (substrate === "kubernetes") {
36466
+ bootstrapArgs.push("--skip-cluster-create");
36467
+ }
36468
+ const r = await spawnFn("olam", bootstrapArgs);
36081
36469
  if (r.status === 0) {
36082
- return { ok: true, message: "olam bootstrap succeeded" };
36470
+ return { ok: true, message: `olam bootstrap succeeded (substrate: ${substrate})` };
36083
36471
  }
36084
36472
  return {
36085
36473
  ok: false,
@@ -36191,7 +36579,10 @@ async function phase6Auth(opts, deps) {
36191
36579
  message: `olam auth login exited ${r.status}; re-run \`olam auth login\` after resolving`
36192
36580
  };
36193
36581
  }
36194
- async function phase7Verify(deps) {
36582
+ async function phase7Verify(opts, deps) {
36583
+ if (opts.skipDoctor) {
36584
+ return { ok: true, skipped: true, message: "skipped via --skip-doctor" };
36585
+ }
36195
36586
  const spawnFn = deps.spawnSubprocess ?? defaultSpawn;
36196
36587
  const r = await spawnFn("olam", ["doctor"]);
36197
36588
  if (r.status === 0) {
@@ -36203,9 +36594,12 @@ async function phase7Verify(deps) {
36203
36594
  remedy: "Inspect doctor output above; the specific failing probe names its remedy."
36204
36595
  };
36205
36596
  }
36206
- var PHASE_TITLES = [
36597
+ var PHASE_TITLES_DOCKER = [
36207
36598
  "Phase 1: System check",
36599
+ "Phase 1.5: Substrate tools install",
36208
36600
  "Phase 2: olam CLI sanity",
36601
+ "Phase 2.5: Cluster provision",
36602
+ "Phase 2.6: Pin kubectl context",
36209
36603
  "Phase 3: Bootstrap",
36210
36604
  "Phase 4: Shell init",
36211
36605
  "Phase 5: Init project",
@@ -36215,22 +36609,29 @@ var PHASE_TITLES = [
36215
36609
  "Phase 7: Final verification"
36216
36610
  ];
36217
36611
  async function runSetup(opts, deps = {}) {
36218
- printHeader("olam setup");
36612
+ const substrate = resolveSubstrate(opts, deps);
36613
+ const bannerSubstrate = substrate === "kubernetes" ? "Kubernetes (k3d)" : "Docker Compose";
36614
+ printHeader(`olam setup \u2014 Olam local stack on ${bannerSubstrate}`);
36615
+ process.stdout.write(`substrate: ${substrate}
36616
+ `);
36219
36617
  const phaseFns = [
36220
- () => phase1SystemCheck(deps),
36618
+ () => phase1SystemCheck(substrate, deps),
36619
+ () => phase1_5InstallSubstrate(substrate, opts, deps),
36221
36620
  () => phase2CliSanity(deps),
36222
- () => phase3Bootstrap(deps),
36621
+ () => phase2_5ProvisionCluster(substrate, opts, deps),
36622
+ () => phase2_6PinKubectlContext(substrate, deps),
36623
+ () => phase3Bootstrap(substrate, deps),
36223
36624
  () => phase4ShellInit(opts, deps),
36224
36625
  () => phase5InitProject(opts, deps),
36225
36626
  () => phase5aSkillSource(opts, deps),
36226
36627
  () => phase5bProjectSweep(opts, deps),
36227
36628
  () => phase6Auth(opts, deps),
36228
- () => phase7Verify(deps)
36629
+ () => phase7Verify(opts, deps)
36229
36630
  ];
36230
36631
  const results = [];
36231
36632
  let failureAt = null;
36232
36633
  for (let i = 0; i < phaseFns.length; i += 1) {
36233
- const name = PHASE_TITLES[i];
36634
+ const name = PHASE_TITLES_DOCKER[i];
36234
36635
  process.stdout.write(`
36235
36636
  ${name}
36236
36637
  `);
@@ -36254,7 +36655,8 @@ ${name}
36254
36655
  }
36255
36656
  printSuccess("Setup complete.");
36256
36657
  process.stdout.write("\nNext steps \u2014 read these in order for the 3-contract pattern:\n");
36257
- for (const line of NEXT_STEPS_DOCS) {
36658
+ const nextStepsDocs = substrate === "kubernetes" ? NEXT_STEPS_DOCS_KUBERNETES : NEXT_STEPS_DOCS_DOCKER;
36659
+ for (const line of nextStepsDocs) {
36258
36660
  printInfo("docs", line);
36259
36661
  }
36260
36662
  process.stdout.write("\n");
@@ -36262,9 +36664,31 @@ ${name}
36262
36664
  }
36263
36665
  function registerSetup(program2) {
36264
36666
  program2.command("setup").description(
36265
- "Fresh-host onboarding wizard. Runs 7 phases (system check \u2192 bootstrap \u2192 shell init \u2192 init project \u2192 auth prompt \u2192 doctor verification). Idempotent; safe to re-run."
36266
- ).option("--skip-shell-init", "Skip Phase 4 (do not append to ~/.zshrc / ~/.bashrc)").option("--skip-auth", "Skip Phase 6 (do not prompt for `olam auth login`)").option("--skip-skill-source", "Skip Phase 5a (do not pick a skill source; run `olam skills source add` later)").option("--skill-source <id-or-url>", "Non-interactive Phase 5a: curated name (e.g. atlas-toolbox) or git URL").option("--skip-project-sweep", "Skip Phase 5b (do not walk projects directory for repo discovery)").option("--projects <path>", "Phase 5b: project root path to walk (default: ~/Projects)").option("--dry-run", "Phase 5b: print matches without registering or building (no side effects)").option("--exclude <glob...>", "Phase 5b: additional skip patterns for project sweep").option("--skip-kg", "Phase 5b: skip KG eager-build sub-step (sources still registered)").option("-y, --yes", "Auto-affirm every prompt (non-interactive)").action(async (opts) => {
36267
- const report = await runSetup(opts);
36667
+ "Fresh-host onboarding wizard. Default substrate=kubernetes (k3d on all platforms): installs k3d (no sudo needed \u2014 only requires docker), provisions the olam-dev cluster, then runs bootstrap end-to-end. Existing docker/compose installs are protected \u2014 they continue on docker with a migration hint. Idempotent; safe to re-run."
36668
+ ).option(
36669
+ "--substrate <substrate>",
36670
+ "Target substrate: kubernetes (default, alias: k3s) or docker. Auto-detected from ~/.olam/config.json when not specified."
36671
+ ).option("--skip-shell-init", "Skip Phase 4 (do not append to ~/.zshrc / ~/.bashrc)").option("--skip-auth", "Skip Phase 6 (do not prompt for `olam auth login`)").option("--skip-skill-source", "Skip Phase 5a (do not pick a skill source; run `olam skills source add` later)").option("--skill-source <id-or-url>", "Non-interactive Phase 5a: curated name (e.g. atlas-toolbox) or git URL").option("--skip-project-sweep", "Skip Phase 5b (do not walk projects directory for repo discovery)").option("--projects <path>", "Phase 5b: project root path to walk (default: ~/Projects)").option("--dry-run", "Phase 5b: print matches without registering or building (no side effects)").option("--exclude <glob...>", "Phase 5b: additional skip patterns for project sweep").option("--skip-kg", "Phase 5b: skip KG eager-build sub-step (sources still registered)").option(
36672
+ "--skip-doctor",
36673
+ "Skip Phase 7 final `olam doctor` verification (for CI or minimal setups)"
36674
+ ).option("-y, --yes", "Auto-affirm every prompt (non-interactive)").action(async (rawOpts) => {
36675
+ let substrate;
36676
+ const rawSubstrate = rawOpts.substrate;
36677
+ if (rawSubstrate === "k3s" || rawSubstrate === "kubernetes") {
36678
+ substrate = "kubernetes";
36679
+ } else if (rawSubstrate === "docker") {
36680
+ substrate = "docker";
36681
+ } else if (rawSubstrate !== void 0) {
36682
+ process.stderr.write(
36683
+ `[olam setup] Unknown --substrate value '${rawSubstrate}'. Valid values: docker, kubernetes (alias: k3s).
36684
+ `
36685
+ );
36686
+ process.exitCode = 1;
36687
+ return;
36688
+ }
36689
+ const { substrate: _drop, ...restOpts } = rawOpts;
36690
+ void _drop;
36691
+ const report = await runSetup({ ...restOpts, substrate });
36268
36692
  if (report.exitCode !== 0) process.exitCode = report.exitCode;
36269
36693
  });
36270
36694
  }
@@ -37036,7 +37460,7 @@ import * as readline2 from "node:readline";
37036
37460
  import pc32 from "picocolors";
37037
37461
 
37038
37462
  // src/commands/flywheel/install-shims.ts
37039
- import { copyFileSync as copyFileSync9, existsSync as existsSync88, mkdirSync as mkdirSync53, readFileSync as readFileSync71, writeFileSync as writeFileSync44 } from "node:fs";
37463
+ import { copyFileSync as copyFileSync9, existsSync as existsSync88, mkdirSync as mkdirSync53, readFileSync as readFileSync72, writeFileSync as writeFileSync44 } from "node:fs";
37040
37464
  import { homedir as homedir46 } from "node:os";
37041
37465
  import { dirname as dirname48, join as join85 } from "node:path";
37042
37466
 
@@ -37140,7 +37564,7 @@ function installOne(spec, targetDir, opts) {
37140
37564
  }
37141
37565
  return { basename: spec.basename, action: "written", targetPath };
37142
37566
  }
37143
- const existing = readFileSync71(targetPath, "utf8");
37567
+ const existing = readFileSync72(targetPath, "utf8");
37144
37568
  if (existing === newContent) {
37145
37569
  return { basename: spec.basename, action: "unchanged", targetPath };
37146
37570
  }
@@ -37252,6 +37676,15 @@ ${pc32.bold("Sync now?")} ${pc32.dim("[Y/n] ")}`);
37252
37676
  });
37253
37677
  });
37254
37678
  }
37679
+ function parsePrefixScope(raw) {
37680
+ const tokens = raw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
37681
+ if (tokens.length === 0) return [];
37682
+ const valid = /* @__PURE__ */ new Set(["skill", "agent"]);
37683
+ for (const t of tokens) {
37684
+ if (!valid.has(t)) return null;
37685
+ }
37686
+ return [...new Set(tokens)];
37687
+ }
37255
37688
  function registerSkillsSource(program2) {
37256
37689
  const skills = program2.command("skills").description("Manage skill sources and synchronization");
37257
37690
  const source = skills.command("source").description("Manage registered skill sources");
@@ -37264,20 +37697,29 @@ function registerSkillsSource(program2) {
37264
37697
  return;
37265
37698
  }
37266
37699
  printHeader(`${all.length} skill source(s)`);
37700
+ const DEFAULT_SCOPE_REPR = "skill,agent";
37701
+ const showScopeCol = all.some(
37702
+ (s) => s.prefixScope !== void 0 && s.prefixScope.slice().sort().join(",") !== "agent,skill"
37703
+ );
37267
37704
  for (let i = 0; i < all.length; i += 1) {
37268
37705
  const s = all[i];
37269
37706
  const when = new Date(s.addedAt).toISOString().slice(0, 10);
37270
37707
  const sha = s.lastPulledSha ? s.lastPulledSha.slice(0, 8) : pc32.dim("(unpulled)");
37271
37708
  const ord = pc32.dim(`[${i + 1}]`);
37272
37709
  const prefixCol = s.prefix ? pc32.cyan(s.prefix.padEnd(12)) : pc32.dim("\u2014".padEnd(12));
37710
+ const scopeDisplay = s.prefixScope !== void 0 ? s.prefixScope.join(",") : DEFAULT_SCOPE_REPR;
37711
+ const scopeCol = showScopeCol ? ` ${pc32.yellow(scopeDisplay.padEnd(12))}` : "";
37273
37712
  console.log(
37274
- ` ${ord} ${pc32.bold(s.id.padEnd(14))} ${s.name.padEnd(24)} ${s.branch.padEnd(12)} ${sha.padEnd(12)} ${prefixCol} ${pc32.dim(when)} ${pc32.dim(s.gitUrl)}`
37713
+ ` ${ord} ${pc32.bold(s.id.padEnd(14))} ${s.name.padEnd(24)} ${s.branch.padEnd(12)} ${sha.padEnd(12)} ${prefixCol}${scopeCol} ${pc32.dim(when)} ${pc32.dim(s.gitUrl)}`
37275
37714
  );
37276
37715
  }
37277
37716
  });
37278
37717
  source.command("add").description("Register and clone a skill source (T6 capability-class: requires --trust OR interactive ack)").requiredOption("--name <name>", "Display name (lowercase, digits, dash)").requiredOption("--git-url <url>", "git URL (https://, git@, ssh://, file://, or absolute path)").option("--branch <branch>", "Branch to track", "main").option(
37279
37718
  "--prefix <prefix>",
37280
37719
  'Deploy prefix: skills+agents from this source deploy as <prefix>:<canonical-name> (e.g. "atl", "pln"). Must match /^[a-z0-9][a-z0-9_-]{0,38}$/'
37720
+ ).option(
37721
+ "--prefix-scope <scope>",
37722
+ 'Comma-separated list of artifact kinds to rename when prefix is set. Valid values: skill, agent. Default: skill,agent. E.g. "--prefix-scope=skill" to rename only skills.'
37281
37723
  ).option("--trust", "Acknowledge that registering grants symlink-into-~/.claude permission (skips interactive picker)").option("--sync-now", "Run `olam skills sync` immediately after add (skips interactive prompt)").option("--no-sync-now", "Skip the post-add sync (skips interactive prompt)").option("--install-hook", "Install the SessionStart hook after add (skips interactive prompt)").option("--no-install-hook", "Skip installing the SessionStart hook (skips interactive prompt)").option(
37282
37724
  "--hook-scope <scope>",
37283
37725
  "When installing the hook (post-add): project (<cwd>/.claude) or user (~/.claude)",
@@ -37291,6 +37733,18 @@ function registerSkillsSource(program2) {
37291
37733
  process.exitCode = 1;
37292
37734
  return;
37293
37735
  }
37736
+ let parsedPrefixScope;
37737
+ if (opts.prefixScope !== void 0) {
37738
+ const parsed = parsePrefixScope(opts.prefixScope);
37739
+ if (parsed === null) {
37740
+ printError(
37741
+ `--prefix-scope must be a comma-separated list of "skill" and/or "agent" \u2014 got "${opts.prefixScope}"`
37742
+ );
37743
+ process.exitCode = 1;
37744
+ return;
37745
+ }
37746
+ parsedPrefixScope = parsed;
37747
+ }
37294
37748
  const isTTY = Boolean(process.stdout.isTTY);
37295
37749
  const testPromptFn = globalThis.__olamTestTrustPrompt;
37296
37750
  let decision;
@@ -37328,8 +37782,11 @@ function registerSkillsSource(program2) {
37328
37782
  branch: opts.branch,
37329
37783
  trustMethod: decision.method
37330
37784
  });
37331
- if (opts.prefix !== void 0) {
37332
- updateSkillSource(entry.id, { prefix: opts.prefix });
37785
+ if (opts.prefix !== void 0 || parsedPrefixScope !== void 0) {
37786
+ updateSkillSource(entry.id, {
37787
+ ...opts.prefix !== void 0 ? { prefix: opts.prefix } : {},
37788
+ ...parsedPrefixScope !== void 0 ? { prefixScope: parsedPrefixScope } : {}
37789
+ });
37333
37790
  }
37334
37791
  } catch (err) {
37335
37792
  try {
@@ -37576,6 +38033,53 @@ function registerSkillsSource(program2) {
37576
38033
  process.exitCode = 1;
37577
38034
  }
37578
38035
  });
38036
+ source.command("set-prefix-scope").description(
38037
+ "Set which artifact kinds are renamed by the prefix (comma-separated: skill, agent, or skill,agent)"
38038
+ ).argument("<name>", 'Display name of the skill source (from "olam skills source list")').argument("<scope>", 'Comma-separated artifact kinds: "skill", "agent", or "skill,agent". Empty string = no renaming.').action((name, scopeRaw) => {
38039
+ const entry = getSkillSourceByName(name);
38040
+ if (entry === null) {
38041
+ printError(
38042
+ `skill source "${name}" is not registered. Run "olam skills source list" to see registered sources.`
38043
+ );
38044
+ process.exitCode = 1;
38045
+ return;
38046
+ }
38047
+ const parsed = parsePrefixScope(scopeRaw);
38048
+ if (parsed === null) {
38049
+ printError(
38050
+ `scope must be a comma-separated list of "skill" and/or "agent" \u2014 got "${scopeRaw}"`
38051
+ );
38052
+ process.exitCode = 1;
38053
+ return;
38054
+ }
38055
+ try {
38056
+ updateSkillSource(entry.id, { prefixScope: parsed });
38057
+ const display = parsed.length === 0 ? "(none \u2014 prefix disabled)" : parsed.join(",");
38058
+ printSuccess(`set prefix-scope "${display}" on source "${name}" (${entry.id})`);
38059
+ } catch (err) {
38060
+ printError(asMessage3(err));
38061
+ process.exitCode = 1;
38062
+ }
38063
+ });
38064
+ source.command("unset-prefix-scope").description(
38065
+ "Remove the prefix-scope override from a registered skill source (reverts to default: both skill and agent are renamed)"
38066
+ ).argument("<name>", 'Display name of the skill source (from "olam skills source list")').action((name) => {
38067
+ const entry = getSkillSourceByName(name);
38068
+ if (entry === null) {
38069
+ printError(
38070
+ `skill source "${name}" is not registered. Run "olam skills source list" to see registered sources.`
38071
+ );
38072
+ process.exitCode = 1;
38073
+ return;
38074
+ }
38075
+ try {
38076
+ updateSkillSource(entry.id, { prefixScope: null });
38077
+ printSuccess(`unset prefix-scope on source "${name}" (${entry.id}) \u2014 reverted to default (skill,agent)`);
38078
+ } catch (err) {
38079
+ printError(asMessage3(err));
38080
+ process.exitCode = 1;
38081
+ }
38082
+ });
37579
38083
  }
37580
38084
 
37581
38085
  // src/commands/skills.ts
@@ -37595,8 +38099,8 @@ import {
37595
38099
  existsSync as existsSync89,
37596
38100
  lstatSync as lstatSync8,
37597
38101
  mkdirSync as mkdirSync54,
37598
- readdirSync as readdirSync26,
37599
- readFileSync as readFileSync72,
38102
+ readdirSync as readdirSync27,
38103
+ readFileSync as readFileSync73,
37600
38104
  readlinkSync as readlinkSync4,
37601
38105
  rmSync as rmSync9,
37602
38106
  statSync as statSync27,
@@ -37679,7 +38183,7 @@ function ensureRealDir(p) {
37679
38183
  rmSync9(p);
37680
38184
  mkdirSync54(p, { recursive: true });
37681
38185
  if (existsSync89(target) && statSync27(target).isDirectory()) {
37682
- for (const entry of readdirSync26(target)) {
38186
+ for (const entry of readdirSync27(target)) {
37683
38187
  const src = join86(target, entry);
37684
38188
  const dest = join86(p, entry);
37685
38189
  const srcStat = lstatSync8(src);
@@ -37703,8 +38207,8 @@ function postMergeSanitize(mergedText, label) {
37703
38207
  return { ok: false, reason: `[post-merge-sanitize] ${label} merged output failed sanitizer: ${result.reason}` };
37704
38208
  }
37705
38209
  function mergeOne(upstreamPath, overlayPath, destPath2, label, dryRun, messages) {
37706
- const upstreamText = readFileSync72(upstreamPath, "utf8");
37707
- const overlayText = readFileSync72(overlayPath, "utf8");
38210
+ const upstreamText = readFileSync73(upstreamPath, "utf8");
38211
+ const overlayText = readFileSync73(overlayPath, "utf8");
37708
38212
  const result = mergeMarkdown(upstreamText, overlayText, label, upstreamPath, overlayPath);
37709
38213
  if ("error" in result) {
37710
38214
  messages.push(`ERROR ${result.error.reason}`);
@@ -37724,7 +38228,7 @@ function mergeOne(upstreamPath, overlayPath, destPath2, label, dryRun, messages)
37724
38228
  }
37725
38229
  function isNewAgentOverlay(overlayPath) {
37726
38230
  try {
37727
- const text = readFileSync72(overlayPath, "utf8");
38231
+ const text = readFileSync73(overlayPath, "utf8");
37728
38232
  const { fm } = parseFrontmatter3(text);
37729
38233
  return fm["overlay-intent"] === "new-agent";
37730
38234
  } catch {
@@ -37740,7 +38244,7 @@ function copyNewAgent(overlayPath, destPath2, label, dryRun, messages) {
37740
38244
  }
37741
38245
  function walkSkillsOverlays(skillsOverridesDir, skillsDir, opts, result) {
37742
38246
  if (!existsSync89(skillsOverridesDir)) return;
37743
- for (const skillName of readdirSync26(skillsOverridesDir)) {
38247
+ for (const skillName of readdirSync27(skillsOverridesDir)) {
37744
38248
  const overlaySkillDir = join86(skillsOverridesDir, skillName);
37745
38249
  if (!statSync27(overlaySkillDir).isDirectory()) continue;
37746
38250
  const upstreamSkillDir = join86(skillsDir, skillName);
@@ -37756,7 +38260,7 @@ function walkOverlayTree(overlayRoot, upstreamRoot, labelPrefix, opts, result, i
37756
38260
  const stack = [overlayRoot];
37757
38261
  while (stack.length > 0) {
37758
38262
  const current = stack.pop();
37759
- for (const entry of readdirSync26(current)) {
38263
+ for (const entry of readdirSync27(current)) {
37760
38264
  const overlayPath = join86(current, entry);
37761
38265
  const stat = lstatSync8(overlayPath);
37762
38266
  if (stat.isDirectory()) {
@@ -39743,9 +40247,9 @@ function registerMcpServe(cmd) {
39743
40247
  init_output();
39744
40248
 
39745
40249
  // src/commands/mcp/install-shared.ts
39746
- import { spawnSync as spawnSync27 } from "node:child_process";
40250
+ import { spawnSync as spawnSync28 } from "node:child_process";
39747
40251
  var DEFAULT_CLAUDE_SHELL_DEPS = {
39748
- spawn: spawnSync27,
40252
+ spawn: spawnSync28,
39749
40253
  log: (msg) => console.log(msg)
39750
40254
  };
39751
40255
  function isOnPath(deps, bin) {
@@ -40462,8 +40966,8 @@ function registerMcp(program2) {
40462
40966
  init_output();
40463
40967
 
40464
40968
  // src/lib/memory-host-process-migration.ts
40465
- import { existsSync as existsSync100, readFileSync as readFileSync80, unlinkSync as unlinkSync23 } from "node:fs";
40466
- import { spawnSync as spawnSync28 } from "node:child_process";
40969
+ import { existsSync as existsSync100, readFileSync as readFileSync81, unlinkSync as unlinkSync23 } from "node:fs";
40970
+ import { spawnSync as spawnSync29 } from "node:child_process";
40467
40971
 
40468
40972
  // src/commands/memory/_paths.ts
40469
40973
  import { homedir as homedir51 } from "node:os";
@@ -40531,7 +41035,7 @@ function migrateFromHostProcess(opts = {}) {
40531
41035
  }
40532
41036
  function readPidFromFile(pidPath2) {
40533
41037
  try {
40534
- const raw = readFileSync80(pidPath2, "utf8").trim();
41038
+ const raw = readFileSync81(pidPath2, "utf8").trim();
40535
41039
  const pid = parseInt(raw, 10);
40536
41040
  if (!Number.isFinite(pid) || pid <= 0) return null;
40537
41041
  return pid;
@@ -40548,7 +41052,7 @@ function isProcessAlive(pid) {
40548
41052
  }
40549
41053
  }
40550
41054
  function processCommName(pid) {
40551
- const r = spawnSync28("ps", ["-p", String(pid), "-o", "comm="], { encoding: "utf-8" });
41055
+ const r = spawnSync29("ps", ["-p", String(pid), "-o", "comm="], { encoding: "utf-8" });
40552
41056
  if (r.status !== 0) return null;
40553
41057
  const comm = r.stdout.trim();
40554
41058
  return comm.split("/").pop() ?? null;
@@ -40562,7 +41066,7 @@ function terminateProcess(pid) {
40562
41066
  const deadline = Date.now() + KILL_TIMEOUT_MS;
40563
41067
  while (Date.now() < deadline) {
40564
41068
  if (!isProcessAlive(pid)) return true;
40565
- spawnSync28("sleep", ["0.1"]);
41069
+ spawnSync29("sleep", ["0.1"]);
40566
41070
  }
40567
41071
  try {
40568
41072
  process.kill(pid, "SIGKILL");
@@ -40964,7 +41468,7 @@ function registerMemoryUninstall(cmd) {
40964
41468
  // src/commands/memory/mode.ts
40965
41469
  init_schema2();
40966
41470
  init_output();
40967
- import { existsSync as existsSync103, readFileSync as readFileSync81, writeFileSync as writeFileSync51 } from "node:fs";
41471
+ import { existsSync as existsSync103, readFileSync as readFileSync82, writeFileSync as writeFileSync51 } from "node:fs";
40968
41472
  import { join as join94 } from "node:path";
40969
41473
  import * as readline7 from "node:readline/promises";
40970
41474
  import { parse as parseYaml7, stringify as stringifyYaml6 } from "yaml";
@@ -40979,7 +41483,7 @@ function locateConfig(cwd) {
40979
41483
  return { absPath };
40980
41484
  }
40981
41485
  function readConfigYaml(absPath) {
40982
- const raw = readFileSync81(absPath, "utf-8");
41486
+ const raw = readFileSync82(absPath, "utf-8");
40983
41487
  const parsed = parseYaml7(raw) ?? {};
40984
41488
  if (typeof parsed !== "object" || parsed === null) {
40985
41489
  throw new Error(`${absPath} is not a YAML object`);
@@ -41466,7 +41970,7 @@ function registerMemoryStats(cmd) {
41466
41970
  }
41467
41971
 
41468
41972
  // src/commands/memory/install-hooks.ts
41469
- import { copyFileSync as copyFileSync12, existsSync as existsSync105, mkdirSync as mkdirSync60, readFileSync as readFileSync82, writeFileSync as writeFileSync52 } from "node:fs";
41973
+ import { copyFileSync as copyFileSync12, existsSync as existsSync105, mkdirSync as mkdirSync60, readFileSync as readFileSync83, writeFileSync as writeFileSync52 } from "node:fs";
41470
41974
  import { homedir as homedir52 } from "node:os";
41471
41975
  import { dirname as dirname56, join as join96, resolve as resolve25 } from "node:path";
41472
41976
  import { fileURLToPath as fileURLToPath9 } from "node:url";
@@ -41489,7 +41993,7 @@ function installOne2(basename16, sourceDir, targetDir, opts) {
41489
41993
  if (!existsSync105(sourcePath)) {
41490
41994
  throw new Error(`canonical hook source missing at ${sourcePath} \u2014 olam install corrupt or sourceDir is wrong`);
41491
41995
  }
41492
- const newContent = readFileSync82(sourcePath, "utf8");
41996
+ const newContent = readFileSync83(sourcePath, "utf8");
41493
41997
  if (!existsSync105(targetPath)) {
41494
41998
  if (opts.dryRun !== true) {
41495
41999
  mkdirSync60(dirname56(targetPath), { recursive: true });
@@ -41497,7 +42001,7 @@ function installOne2(basename16, sourceDir, targetDir, opts) {
41497
42001
  }
41498
42002
  return { basename: basename16, action: "written", targetPath };
41499
42003
  }
41500
- const existing = readFileSync82(targetPath, "utf8");
42004
+ const existing = readFileSync83(targetPath, "utf8");
41501
42005
  if (existing === newContent) {
41502
42006
  return { basename: basename16, action: "unchanged", targetPath };
41503
42007
  }
@@ -42824,14 +43328,14 @@ init_file_lock();
42824
43328
  import { mkdirSync as mkdirSync63, appendFileSync as appendFileSync6 } from "node:fs";
42825
43329
  import { homedir as homedir57 } from "node:os";
42826
43330
  import { dirname as dirname59, join as join100 } from "node:path";
42827
- import { randomUUID as randomUUID3 } from "node:crypto";
43331
+ import { randomUUID as randomUUID4 } from "node:crypto";
42828
43332
  var VALID_SEVERITIES = /* @__PURE__ */ new Set(["critical", "high", "medium", "low", "info", "warn"]);
42829
43333
  var PROMPT_FEEDING_FIELDS = ["extracted_pattern", "severity", "affected_persona", "proposed_edit"];
42830
43334
  var BREADCRUMBS_BASE = join100(homedir57(), ".local", "share", "claude", "breadcrumbs");
42831
43335
  var LOCK_FILENAME = ".flywheel-emit.lock";
42832
43336
  function buildRecord(opts) {
42833
43337
  const rec = {
42834
- breadcrumb_id: randomUUID3().replace(/-/g, ""),
43338
+ breadcrumb_id: randomUUID4().replace(/-/g, ""),
42835
43339
  emitted_at: (/* @__PURE__ */ new Date()).toISOString(),
42836
43340
  emitted_by_skill: opts.skill,
42837
43341
  pass_number: opts.pass !== void 0 ? Number.parseInt(opts.pass, 10) : 0,
@@ -42975,7 +43479,7 @@ function registerFlywheelK5Score(parent) {
42975
43479
  }
42976
43480
 
42977
43481
  // src/commands/flywheel/k5-validate.ts
42978
- import { readFileSync as readFileSync86, statSync as statSync30 } from "node:fs";
43482
+ import { readFileSync as readFileSync87, statSync as statSync30 } from "node:fs";
42979
43483
  import { parse as parseYAML } from "yaml";
42980
43484
  var K5_DIMS = ["direction", "approach", "open_questions", "constraints", "reuse"];
42981
43485
  var MAX_PLAN_BYTES = 1048576;
@@ -43045,7 +43549,7 @@ function validatePlan(path94) {
43045
43549
  }
43046
43550
  let text;
43047
43551
  try {
43048
- text = readFileSync86(path94, "utf8");
43552
+ text = readFileSync87(path94, "utf8");
43049
43553
  } catch (err) {
43050
43554
  return { ok: false, message: `FAIL cannot read ${path94}: ${err instanceof Error ? err.message : "unknown"}` };
43051
43555
  }
@@ -43127,7 +43631,7 @@ function registerFlywheelK5Validate(parent) {
43127
43631
  }
43128
43632
 
43129
43633
  // src/commands/flywheel/k10-measure.ts
43130
- import { readFileSync as readFileSync87 } from "node:fs";
43634
+ import { readFileSync as readFileSync88 } from "node:fs";
43131
43635
 
43132
43636
  // ../core/dist/lib/k10-budget.js
43133
43637
  var K10_TOKEN_CAP = 5500;
@@ -43184,7 +43688,7 @@ function registerFlywheelK10Measure(parent) {
43184
43688
  parent.command("k10-measure").description("Measure K10 token budget for upstream + optional overlay; emit PASS/REJECT verdict").requiredOption("--upstream <path>", "path to upstream file (e.g. persona prompt)").option("--overlay <path>", "path to overlay file (omit if none \u2014 PASS without enforcement)").option("--json", "emit verdict as JSON instead of human-readable").action((opts) => {
43185
43689
  let upstreamText;
43186
43690
  try {
43187
- upstreamText = readFileSync87(opts.upstream, "utf8");
43691
+ upstreamText = readFileSync88(opts.upstream, "utf8");
43188
43692
  } catch (err) {
43189
43693
  process.stderr.write(
43190
43694
  `[k10-measure-error] cannot read upstream ${opts.upstream}: ${err instanceof Error ? err.message : "unknown"}
@@ -43196,7 +43700,7 @@ function registerFlywheelK10Measure(parent) {
43196
43700
  let overlayTokens = null;
43197
43701
  if (opts.overlay !== void 0) {
43198
43702
  try {
43199
- const overlayText = readFileSync87(opts.overlay, "utf8");
43703
+ const overlayText = readFileSync88(opts.overlay, "utf8");
43200
43704
  overlayTokens = tokensFromText(overlayText);
43201
43705
  } catch (err) {
43202
43706
  process.stderr.write(
@@ -43230,7 +43734,7 @@ function registerFlywheelK10Measure(parent) {
43230
43734
  }
43231
43735
 
43232
43736
  // src/commands/flywheel/check-persona-skeleton.ts
43233
- import { existsSync as existsSync109, readFileSync as readFileSync88, statSync as statSync31 } from "node:fs";
43737
+ import { existsSync as existsSync109, readFileSync as readFileSync89, statSync as statSync31 } from "node:fs";
43234
43738
  import { homedir as homedir58 } from "node:os";
43235
43739
  import { basename as basename14, join as join101 } from "node:path";
43236
43740
  import { parse as parseYAML2 } from "yaml";
@@ -43279,7 +43783,7 @@ function checkFile(filepath) {
43279
43783
  if (!existsSync109(filepath) || !statSync31(filepath).isFile()) {
43280
43784
  return { passed: false, failures: [`file not found: ${filepath}`], tokens: 0, mergedSkipped: false };
43281
43785
  }
43282
- const text = readFileSync88(filepath, "utf8");
43786
+ const text = readFileSync89(filepath, "utf8");
43283
43787
  const { fm, body } = parseFile(text);
43284
43788
  const failures = [];
43285
43789
  for (const key of REQUIRED_FRONTMATTER_KEYS) {
@@ -43345,7 +43849,7 @@ Results: ${passCount} passed, ${failCount} failed
43345
43849
  }
43346
43850
 
43347
43851
  // src/commands/flywheel/diversity-check.ts
43348
- import { readFileSync as readFileSync89 } from "node:fs";
43852
+ import { readFileSync as readFileSync90 } from "node:fs";
43349
43853
  import { basename as basename15 } from "node:path";
43350
43854
  import { globSync as globSync2 } from "node:fs";
43351
43855
 
@@ -43454,7 +43958,7 @@ function registerFlywheelDiversityCheck(parent) {
43454
43958
  const personas = /* @__PURE__ */ new Map();
43455
43959
  for (const filepath of files) {
43456
43960
  try {
43457
- const body = readFileSync89(filepath, "utf8");
43961
+ const body = readFileSync90(filepath, "utf8");
43458
43962
  if (body.trim().length > 0) {
43459
43963
  personas.set(basename15(filepath, ".md"), body);
43460
43964
  }
@@ -43547,12 +44051,12 @@ import {
43547
44051
  copyFileSync as copyFileSync15,
43548
44052
  existsSync as existsSync110,
43549
44053
  mkdirSync as mkdirSync65,
43550
- readFileSync as readFileSync90,
43551
- readdirSync as readdirSync31,
44054
+ readFileSync as readFileSync91,
44055
+ readdirSync as readdirSync32,
43552
44056
  statSync as statSync32,
43553
44057
  writeFileSync as writeFileSync57
43554
44058
  } from "node:fs";
43555
- import { spawnSync as spawnSync29 } from "node:child_process";
44059
+ import { spawnSync as spawnSync30 } from "node:child_process";
43556
44060
  import { homedir as homedir60 } from "node:os";
43557
44061
  import { dirname as dirname61, join as join103, relative as relative7 } from "node:path";
43558
44062
  function escapeRegex(s) {
@@ -43586,7 +44090,7 @@ function walkOverlayFiles(dir) {
43586
44090
  const found = [];
43587
44091
  let entries;
43588
44092
  try {
43589
- entries = readdirSync31(dir);
44093
+ entries = readdirSync32(dir);
43590
44094
  } catch {
43591
44095
  return [];
43592
44096
  }
@@ -43633,7 +44137,7 @@ function resolveAtlasUser2(opts) {
43633
44137
  const claudeDir2 = opts._testClaudeDir ?? process.env["OLAM_CLAUDE_DIR"] ?? join103(homedir60(), ".claude");
43634
44138
  const f = join103(claudeDir2, ".atlas-user");
43635
44139
  if (existsSync110(f)) {
43636
- const v = readFileSync90(f, "utf-8").trim();
44140
+ const v = readFileSync91(f, "utf-8").trim();
43637
44141
  if (v.length === 0) return null;
43638
44142
  assertValidAtlasUser(v);
43639
44143
  return v;
@@ -43641,7 +44145,7 @@ function resolveAtlasUser2(opts) {
43641
44145
  return null;
43642
44146
  }
43643
44147
  function runGit2(args, cwd) {
43644
- const result = spawnSync29("git", args, {
44148
+ const result = spawnSync30("git", args, {
43645
44149
  cwd,
43646
44150
  encoding: "utf-8",
43647
44151
  stdio: ["ignore", "pipe", "pipe"]
@@ -43775,7 +44279,7 @@ function pushOverlays(opts) {
43775
44279
  const basename16 = relPath.split("/").pop() ?? relPath;
43776
44280
  let content;
43777
44281
  try {
43778
- content = readFileSync90(srcFile);
44282
+ content = readFileSync91(srcFile);
43779
44283
  } catch {
43780
44284
  continue;
43781
44285
  }
@@ -43823,12 +44327,12 @@ function pushOverlays(opts) {
43823
44327
  const targetFile = join103(targetDir, relPath);
43824
44328
  let srcBuf;
43825
44329
  try {
43826
- srcBuf = readFileSync90(srcFile);
44330
+ srcBuf = readFileSync91(srcFile);
43827
44331
  } catch {
43828
44332
  continue;
43829
44333
  }
43830
44334
  if (existsSync110(targetFile)) {
43831
- const dstBuf = readFileSync90(targetFile);
44335
+ const dstBuf = readFileSync91(targetFile);
43832
44336
  if (srcBuf.equals(dstBuf)) {
43833
44337
  wouldUnchange += 1;
43834
44338
  process.stdout.write(` [skip] ${overlayKind}.overrides/${relPath} (unchanged)
@@ -43879,13 +44383,13 @@ function pushOverlays(opts) {
43879
44383
  const targetFile = join103(membersBase, `${overlayKind}.overrides`, relPath);
43880
44384
  let srcBuf;
43881
44385
  try {
43882
- srcBuf = readFileSync90(srcFile);
44386
+ srcBuf = readFileSync91(srcFile);
43883
44387
  } catch {
43884
44388
  continue;
43885
44389
  }
43886
44390
  const targetExists = existsSync110(targetFile);
43887
44391
  if (targetExists) {
43888
- const dstBuf = readFileSync90(targetFile);
44392
+ const dstBuf = readFileSync91(targetFile);
43889
44393
  if (srcBuf.equals(dstBuf)) {
43890
44394
  filesUnchanged += 1;
43891
44395
  continue;
@@ -44094,7 +44598,7 @@ function migrateOverlays(opts = {}) {
44094
44598
  for (const filePath of allFiles) {
44095
44599
  let original;
44096
44600
  try {
44097
- original = readFileSync90(filePath, "utf8");
44601
+ original = readFileSync91(filePath, "utf8");
44098
44602
  } catch {
44099
44603
  continue;
44100
44604
  }
@@ -44254,7 +44758,7 @@ function registerFlywheel(program2) {
44254
44758
 
44255
44759
  // src/commands/seed.ts
44256
44760
  init_output();
44257
- import { spawnSync as spawnSync30, spawn as spawnAsync2 } from "node:child_process";
44761
+ import { spawnSync as spawnSync31, spawn as spawnAsync2 } from "node:child_process";
44258
44762
  var DEFAULT_SINGLETON_CONTAINER = "olam-postgres";
44259
44763
  var DEFAULT_SINGLETON_USER = "development";
44260
44764
  function assertValidSeedName(name) {
@@ -44265,7 +44769,7 @@ function assertValidSeedName(name) {
44265
44769
  }
44266
44770
  }
44267
44771
  function singletonDocker(container, user, args, stdin) {
44268
- return spawnSync30(
44772
+ return spawnSync31(
44269
44773
  "docker",
44270
44774
  ["exec", "-i", container, "psql", "-U", user, ...args],
44271
44775
  { encoding: "utf-8", input: stdin }
@@ -44320,7 +44824,7 @@ async function handleBake(opts) {
44320
44824
  if (sources.length > 1) {
44321
44825
  throw new Error("multiple sources specified \u2014 pass exactly one of --source-container, --source-url, --source-local");
44322
44826
  }
44323
- const ping = spawnSync30("docker", ["inspect", "--format", "{{.State.Status}}", singleton], { encoding: "utf-8" });
44827
+ const ping = spawnSync31("docker", ["inspect", "--format", "{{.State.Status}}", singleton], { encoding: "utf-8" });
44324
44828
  if (ping.status !== 0 || (ping.stdout || "").trim() !== "running") {
44325
44829
  throw new Error(`singleton container "${singleton}" not running \u2014 run \`olam bootstrap\` first`);
44326
44830
  }