@pleri/olam-cli 0.1.143 → 0.1.145

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 (52) hide show
  1. package/dist/commands/doctor.d.ts +53 -23
  2. package/dist/commands/doctor.d.ts.map +1 -1
  3. package/dist/commands/doctor.js +117 -46
  4. package/dist/commands/doctor.js.map +1 -1
  5. package/dist/commands/logs.d.ts +17 -3
  6. package/dist/commands/logs.d.ts.map +1 -1
  7. package/dist/commands/logs.js +38 -35
  8. package/dist/commands/logs.js.map +1 -1
  9. package/dist/commands/memory/bridge.d.ts +57 -0
  10. package/dist/commands/memory/bridge.d.ts.map +1 -0
  11. package/dist/commands/memory/bridge.js +156 -0
  12. package/dist/commands/memory/bridge.js.map +1 -0
  13. package/dist/commands/memory/index.d.ts +3 -0
  14. package/dist/commands/memory/index.d.ts.map +1 -1
  15. package/dist/commands/memory/index.js +10 -1
  16. package/dist/commands/memory/index.js.map +1 -1
  17. package/dist/commands/memory/reclassify.d.ts +56 -0
  18. package/dist/commands/memory/reclassify.d.ts.map +1 -0
  19. package/dist/commands/memory/reclassify.js +177 -0
  20. package/dist/commands/memory/reclassify.js.map +1 -0
  21. package/dist/commands/memory/stats.d.ts +69 -0
  22. package/dist/commands/memory/stats.d.ts.map +1 -0
  23. package/dist/commands/memory/stats.js +164 -0
  24. package/dist/commands/memory/stats.js.map +1 -0
  25. package/dist/commands/skills-source.d.ts +12 -0
  26. package/dist/commands/skills-source.d.ts.map +1 -0
  27. package/dist/commands/skills-source.js +133 -0
  28. package/dist/commands/skills-source.js.map +1 -0
  29. package/dist/commands/skills.d.ts +11 -0
  30. package/dist/commands/skills.d.ts.map +1 -0
  31. package/dist/commands/skills.js +163 -0
  32. package/dist/commands/skills.js.map +1 -0
  33. package/dist/commands/status.d.ts +27 -0
  34. package/dist/commands/status.d.ts.map +1 -1
  35. package/dist/commands/status.js +102 -1
  36. package/dist/commands/status.js.map +1 -1
  37. package/dist/commands/upgrade.d.ts +24 -0
  38. package/dist/commands/upgrade.d.ts.map +1 -1
  39. package/dist/commands/upgrade.js +73 -0
  40. package/dist/commands/upgrade.js.map +1 -1
  41. package/dist/image-digests.json +7 -7
  42. package/dist/index.js +2093 -538
  43. package/dist/index.js.map +1 -1
  44. package/dist/lib/health-probes.d.ts +72 -0
  45. package/dist/lib/health-probes.d.ts.map +1 -1
  46. package/dist/lib/health-probes.js +218 -0
  47. package/dist/lib/health-probes.js.map +1 -1
  48. package/dist/mcp-server.js +1248 -353
  49. package/host-cp/src/agent-runtime-trigger.mjs +262 -0
  50. package/host-cp/src/engine-identity.mjs +32 -0
  51. package/host-cp/src/server.mjs +246 -2
  52. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -492,8 +492,8 @@ var init_parseUtil = __esm({
492
492
  init_errors();
493
493
  init_en();
494
494
  makeIssue = (params) => {
495
- const { data, path: path60, errorMaps, issueData } = params;
496
- const fullPath = [...path60, ...issueData.path || []];
495
+ const { data, path: path66, errorMaps, issueData } = params;
496
+ const fullPath = [...path66, ...issueData.path || []];
497
497
  const fullIssue = {
498
498
  ...issueData,
499
499
  path: fullPath
@@ -801,11 +801,11 @@ var init_types = __esm({
801
801
  init_parseUtil();
802
802
  init_util();
803
803
  ParseInputLazyPath = class {
804
- constructor(parent, value, path60, key) {
804
+ constructor(parent, value, path66, key) {
805
805
  this._cachedPath = [];
806
806
  this.parent = parent;
807
807
  this.data = value;
808
- this._path = path60;
808
+ this._path = path66;
809
809
  this._key = key;
810
810
  }
811
811
  get path() {
@@ -4286,7 +4286,7 @@ import YAML from "yaml";
4286
4286
  function bootstrapStepCmd(entry) {
4287
4287
  return typeof entry === "string" ? entry : entry.cmd;
4288
4288
  }
4289
- function refineForbiddenKeys(value, path60, ctx, rejectSource) {
4289
+ function refineForbiddenKeys(value, path66, ctx, rejectSource) {
4290
4290
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
4291
4291
  return;
4292
4292
  }
@@ -4294,12 +4294,12 @@ function refineForbiddenKeys(value, path60, ctx, rejectSource) {
4294
4294
  if (FORBIDDEN_KEYS.has(key)) {
4295
4295
  ctx.addIssue({
4296
4296
  code: external_exports.ZodIssueCode.custom,
4297
- path: [...path60, key],
4297
+ path: [...path66, key],
4298
4298
  message: `forbidden key "${key}" (prototype-pollution surface)`
4299
4299
  });
4300
4300
  continue;
4301
4301
  }
4302
- if (rejectSource && path60.length === 0 && key === "source") {
4302
+ if (rejectSource && path66.length === 0 && key === "source") {
4303
4303
  ctx.addIssue({
4304
4304
  code: external_exports.ZodIssueCode.custom,
4305
4305
  path: ["source"],
@@ -4307,21 +4307,21 @@ function refineForbiddenKeys(value, path60, ctx, rejectSource) {
4307
4307
  });
4308
4308
  continue;
4309
4309
  }
4310
- refineForbiddenKeys(value[key], [...path60, key], ctx, false);
4310
+ refineForbiddenKeys(value[key], [...path66, key], ctx, false);
4311
4311
  }
4312
4312
  }
4313
- function rejectForbiddenKeys(value, path60, rejectSource) {
4313
+ function rejectForbiddenKeys(value, path66, rejectSource) {
4314
4314
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
4315
4315
  return;
4316
4316
  }
4317
4317
  for (const key of Object.keys(value)) {
4318
4318
  if (FORBIDDEN_KEYS.has(key)) {
4319
- throw new Error(`[manifest] ${path60}: forbidden key "${key}" (prototype-pollution surface)`);
4319
+ throw new Error(`[manifest] ${path66}: forbidden key "${key}" (prototype-pollution surface)`);
4320
4320
  }
4321
4321
  if (rejectSource && key === "source") {
4322
- throw new Error(`[manifest] ${path60}: top-level "source" is loader-stamped \u2014 manifests must not author it`);
4322
+ throw new Error(`[manifest] ${path66}: top-level "source" is loader-stamped \u2014 manifests must not author it`);
4323
4323
  }
4324
- rejectForbiddenKeys(value[key], `${path60}.${key}`, false);
4324
+ rejectForbiddenKeys(value[key], `${path66}.${key}`, false);
4325
4325
  }
4326
4326
  }
4327
4327
  function unknownTopLevelKeys(parsed) {
@@ -5500,8 +5500,8 @@ var init_client = __esm({
5500
5500
  throw new Error(`failed to report rate-limit for ${accountId} (HTTP ${res.status})`);
5501
5501
  }
5502
5502
  }
5503
- async request(method, path60, body, attempt = 0) {
5504
- const url2 = `${this.baseUrl}${path60}`;
5503
+ async request(method, path66, body, attempt = 0) {
5504
+ const url2 = `${this.baseUrl}${path66}`;
5505
5505
  const controller = new AbortController();
5506
5506
  const timer = setTimeout(() => controller.abort(), this.timeoutMs);
5507
5507
  const headers = {};
@@ -5519,7 +5519,7 @@ var init_client = __esm({
5519
5519
  } catch (err) {
5520
5520
  if (attempt < RETRY_COUNT && isTransient(err)) {
5521
5521
  await sleep(RETRY_BACKOFF_MS * (attempt + 1));
5522
- return this.request(method, path60, body, attempt + 1);
5522
+ return this.request(method, path66, body, attempt + 1);
5523
5523
  }
5524
5524
  throw err;
5525
5525
  } finally {
@@ -7292,8 +7292,8 @@ var init_provider3 = __esm({
7292
7292
  // -----------------------------------------------------------------------
7293
7293
  // Internal fetch helper
7294
7294
  // -----------------------------------------------------------------------
7295
- async request(path60, method, body) {
7296
- const url2 = `${this.config.workerUrl}${path60}`;
7295
+ async request(path66, method, body) {
7296
+ const url2 = `${this.config.workerUrl}${path66}`;
7297
7297
  const bearer = await this.config.mintToken();
7298
7298
  const headers = {
7299
7299
  Authorization: `Bearer ${bearer}`
@@ -8640,17 +8640,17 @@ function kgRoot() {
8640
8640
  function worldsRoot() {
8641
8641
  return join16(olamHome(), "worlds");
8642
8642
  }
8643
- function assertWithinPrefix(path60, prefix, label) {
8644
- if (!path60.startsWith(prefix + "/")) {
8645
- throw new Error(`${label} escape: ${path60} not under ${prefix}/`);
8643
+ function assertWithinPrefix(path66, prefix, label) {
8644
+ if (!path66.startsWith(prefix + "/")) {
8645
+ throw new Error(`${label} escape: ${path66} not under ${prefix}/`);
8646
8646
  }
8647
8647
  }
8648
8648
  function kgPristinePath(workspace) {
8649
8649
  validateWorkspaceName(workspace);
8650
8650
  const root = kgRoot();
8651
- const path60 = resolve5(join16(root, workspace));
8652
- assertWithinPrefix(path60, root, "kgPristinePath");
8653
- return path60;
8651
+ const path66 = resolve5(join16(root, workspace));
8652
+ assertWithinPrefix(path66, root, "kgPristinePath");
8653
+ return path66;
8654
8654
  }
8655
8655
  var KG_PATHS_INTERNALS;
8656
8656
  var init_storage_paths = __esm({
@@ -8765,8 +8765,8 @@ import { execFileSync as execFileSync4 } from "node:child_process";
8765
8765
  import * as fs15 from "node:fs";
8766
8766
  import * as os9 from "node:os";
8767
8767
  import * as path16 from "node:path";
8768
- function expandHome2(p, homedir36) {
8769
- return p.replace(/^~(?=$|\/|\\)/, homedir36());
8768
+ function expandHome2(p, homedir40) {
8769
+ return p.replace(/^~(?=$|\/|\\)/, homedir40());
8770
8770
  }
8771
8771
  function sanitizeRepoFilename(name) {
8772
8772
  const sanitized = name.replace(/[^A-Za-z0-9._-]/g, "_");
@@ -8789,7 +8789,7 @@ ${stderr}`;
8789
8789
  }
8790
8790
  function snapshotBaselineDiff(repos, workspacePath, deps = {}) {
8791
8791
  const exec = deps.exec ?? ((cmd, args, opts) => execFileSync4(cmd, args, opts));
8792
- const homedir36 = deps.homedir ?? (() => os9.homedir());
8792
+ const homedir40 = deps.homedir ?? (() => os9.homedir());
8793
8793
  const baselineDir = path16.join(workspacePath, ".olam", "baseline");
8794
8794
  try {
8795
8795
  fs15.mkdirSync(baselineDir, { recursive: true });
@@ -8805,7 +8805,7 @@ function snapshotBaselineDiff(repos, workspacePath, deps = {}) {
8805
8805
  continue;
8806
8806
  const filename = `${sanitizeRepoFilename(repo.name)}.diff`;
8807
8807
  const outPath = path16.join(baselineDir, filename);
8808
- const repoPath = expandHome2(repo.path, homedir36);
8808
+ const repoPath = expandHome2(repo.path, homedir40);
8809
8809
  if (!fs15.existsSync(repoPath)) {
8810
8810
  writeBaselineFile(outPath, `# repo: ${repo.name}
8811
8811
  # (skipped: path ${repoPath} does not exist)
@@ -8925,21 +8925,21 @@ function extractStderr(err) {
8925
8925
  }
8926
8926
  function carryUncommittedEdits(repos, workspacePath, deps = {}) {
8927
8927
  const exec = deps.exec ?? ((cmd, args, opts) => execFileSync4(cmd, args, opts));
8928
- const homedir36 = deps.homedir ?? (() => os9.homedir());
8929
- const existsSync64 = deps.existsSync ?? ((p) => fs15.existsSync(p));
8930
- const copyFileSync8 = deps.copyFileSync ?? ((src, dest) => fs15.copyFileSync(src, dest));
8931
- const mkdirSync36 = deps.mkdirSync ?? ((dirPath, opts) => {
8928
+ const homedir40 = deps.homedir ?? (() => os9.homedir());
8929
+ const existsSync71 = deps.existsSync ?? ((p) => fs15.existsSync(p));
8930
+ const copyFileSync9 = deps.copyFileSync ?? ((src, dest) => fs15.copyFileSync(src, dest));
8931
+ const mkdirSync39 = deps.mkdirSync ?? ((dirPath, opts) => {
8932
8932
  fs15.mkdirSync(dirPath, opts);
8933
8933
  });
8934
8934
  const plans = [];
8935
8935
  for (const repo of repos) {
8936
8936
  if (!repo.path)
8937
8937
  continue;
8938
- const repoPath = expandHome2(repo.path, homedir36);
8938
+ const repoPath = expandHome2(repo.path, homedir40);
8939
8939
  const worktreePath = path16.join(workspacePath, repo.name);
8940
- if (!existsSync64(repoPath))
8940
+ if (!existsSync71(repoPath))
8941
8941
  continue;
8942
- if (!existsSync64(worktreePath)) {
8942
+ if (!existsSync71(worktreePath)) {
8943
8943
  console.warn(`[carry] ${repo.name}: world worktree ${worktreePath} missing; skipping carry for this repo`);
8944
8944
  continue;
8945
8945
  }
@@ -8999,11 +8999,11 @@ function carryUncommittedEdits(repos, workspacePath, deps = {}) {
8999
8999
  for (const rel of plan.diff.untracked) {
9000
9000
  const src = path16.join(plan.repoPath, rel);
9001
9001
  const dest = path16.join(plan.worktreePath, rel);
9002
- if (!existsSync64(src))
9002
+ if (!existsSync71(src))
9003
9003
  continue;
9004
9004
  try {
9005
- mkdirSync36(path16.dirname(dest), { recursive: true });
9006
- copyFileSync8(src, dest);
9005
+ mkdirSync39(path16.dirname(dest), { recursive: true });
9006
+ copyFileSync9(src, dest);
9007
9007
  } catch (err) {
9008
9008
  const msg = err instanceof Error ? err.message : String(err);
9009
9009
  console.warn(`[carry] ${plan.name}: copy untracked ${rel} failed: ${msg}`);
@@ -9066,10 +9066,10 @@ import * as fs16 from "node:fs";
9066
9066
  import * as path17 from "node:path";
9067
9067
  function injectWorldContext(opts) {
9068
9068
  const { world } = opts;
9069
- const claudeDir = path17.join(world.workspacePath, ".claude");
9070
- fs16.mkdirSync(claudeDir, { recursive: true });
9069
+ const claudeDir2 = path17.join(world.workspacePath, ".claude");
9070
+ fs16.mkdirSync(claudeDir2, { recursive: true });
9071
9071
  const content = WORLD_CLAUDE_MD.replace("{{worldName}}", world.name).replace("{{worldId}}", world.id).replace("{{branch}}", world.branch).replace("{{taskBlock}}", buildTaskBlock(opts)).replace("{{reposList}}", buildReposList(world)).replace("{{servicesLine}}", buildServicesLine(opts.services)).replace("{{pleriPlaneLine}}", buildPleriPlaneLine(opts.pleriPlaneUrl)).replace("{{planFileBlock}}", buildPlanFileBlock(world)).replace("{{extraContextBlock}}", buildExtraContextBlock(opts.claudeMdExtra));
9072
- fs16.writeFileSync(path17.join(claudeDir, "CLAUDE.md"), content);
9072
+ fs16.writeFileSync(path17.join(claudeDir2, "CLAUDE.md"), content);
9073
9073
  writeOlamDocs(world.workspacePath);
9074
9074
  }
9075
9075
  function buildTaskBlock(opts) {
@@ -10589,6 +10589,26 @@ var init_auto_dispatch_task = __esm({
10589
10589
  }
10590
10590
  });
10591
10591
 
10592
+ // ../core/dist/skill-sources/schema.js
10593
+ var SKILL_SOURCE_ID_LENGTH, NAME_PATTERN, URL_PATTERN, SkillSourceSchema;
10594
+ var init_schema3 = __esm({
10595
+ "../core/dist/skill-sources/schema.js"() {
10596
+ "use strict";
10597
+ init_v3();
10598
+ SKILL_SOURCE_ID_LENGTH = 12;
10599
+ NAME_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/;
10600
+ URL_PATTERN = /^(?:https?:\/\/|git@|ssh:\/\/|file:\/\/|\/).+/;
10601
+ SkillSourceSchema = external_exports.object({
10602
+ id: external_exports.string().length(SKILL_SOURCE_ID_LENGTH, `skill-source id must be exactly ${SKILL_SOURCE_ID_LENGTH} chars (sha256-of-url prefix)`).regex(/^[a-f0-9]+$/, "skill-source id must be lowercase hex"),
10603
+ name: external_exports.string().regex(NAME_PATTERN, "skill-source name must be ASCII lowercase + digits + dash (1-64 chars, no leading/trailing dash)"),
10604
+ gitUrl: external_exports.string().min(1, "gitUrl must not be empty").regex(URL_PATTERN, "gitUrl must look like a git URL (https://, git@, ssh://, file://, or absolute path)"),
10605
+ branch: external_exports.string().min(1, "branch must not be empty").default("main"),
10606
+ addedAt: external_exports.number().int().nonnegative(),
10607
+ lastPulledSha: external_exports.string().regex(/^[a-f0-9]{40}$/, "lastPulledSha must be a 40-char lowercase hex git SHA").optional()
10608
+ });
10609
+ }
10610
+ });
10611
+
10592
10612
  // ../core/dist/global-config/schema.js
10593
10613
  function isAbsoluteOrTilde(p) {
10594
10614
  return p.startsWith("/") || p === "~" || p.startsWith("~/");
@@ -10597,11 +10617,12 @@ function hasNoTraversalComponents(p) {
10597
10617
  return !p.split("/").some((seg) => seg === "..");
10598
10618
  }
10599
10619
  var RepoEntrySchema, PortMapSchema, SeedSqlFileSchema, SeedCommandSchema, SeedFixtureCopySchema, SeedSchema, RunbookSchema, GlobalConfigSchema, DEFAULT_GLOBAL_CONFIG;
10600
- var init_schema3 = __esm({
10620
+ var init_schema4 = __esm({
10601
10621
  "../core/dist/global-config/schema.js"() {
10602
10622
  "use strict";
10603
10623
  init_v3();
10604
10624
  init_schema();
10625
+ init_schema3();
10605
10626
  RepoEntrySchema = external_exports.object({
10606
10627
  name: external_exports.string().regex(REPO_NAME_PATTERN, "repo name must be ASCII lowercase + digits + dash (1-64 chars, no leading/trailing dash)"),
10607
10628
  path: external_exports.string().min(1, "path must not be empty").refine(isAbsoluteOrTilde, {
@@ -10648,7 +10669,8 @@ var init_schema3 = __esm({
10648
10669
  GlobalConfigSchema = external_exports.object({
10649
10670
  schemaVersion: external_exports.literal(1),
10650
10671
  repos: external_exports.array(RepoEntrySchema).optional().default([]),
10651
- runbooks: external_exports.array(RunbookSchema).optional().default([])
10672
+ runbooks: external_exports.array(RunbookSchema).optional().default([]),
10673
+ skillSources: external_exports.array(SkillSourceSchema).optional().default([])
10652
10674
  }).strip().superRefine((val, ctx) => {
10653
10675
  const repoNames = val.repos.map((r) => r.name);
10654
10676
  const repoDupes = repoNames.filter((n, i) => repoNames.indexOf(n) !== i);
@@ -10660,11 +10682,22 @@ var init_schema3 = __esm({
10660
10682
  for (const d of rbDupes) {
10661
10683
  ctx.addIssue({ code: external_exports.ZodIssueCode.custom, message: `duplicate runbook name: "${d}"`, path: ["runbooks"] });
10662
10684
  }
10685
+ const skillIds = val.skillSources.map((s) => s.id);
10686
+ const skillIdDupes = skillIds.filter((n, i) => skillIds.indexOf(n) !== i);
10687
+ for (const d of skillIdDupes) {
10688
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, message: `duplicate skill-source id: "${d}"`, path: ["skillSources"] });
10689
+ }
10690
+ const skillNames = val.skillSources.map((s) => s.name);
10691
+ const skillNameDupes = skillNames.filter((n, i) => skillNames.indexOf(n) !== i);
10692
+ for (const d of skillNameDupes) {
10693
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, message: `duplicate skill-source name: "${d}"`, path: ["skillSources"] });
10694
+ }
10663
10695
  });
10664
10696
  DEFAULT_GLOBAL_CONFIG = {
10665
10697
  schemaVersion: 1,
10666
10698
  repos: [],
10667
- runbooks: []
10699
+ runbooks: [],
10700
+ skillSources: []
10668
10701
  };
10669
10702
  }
10670
10703
  });
@@ -10715,7 +10748,7 @@ var GlobalConfigReadError;
10715
10748
  var init_store2 = __esm({
10716
10749
  "../core/dist/global-config/store.js"() {
10717
10750
  "use strict";
10718
- init_schema3();
10751
+ init_schema4();
10719
10752
  GlobalConfigReadError = class extends Error {
10720
10753
  constructor(configPath, cause) {
10721
10754
  const msg = cause instanceof Error ? cause.message : String(cause);
@@ -11701,7 +11734,7 @@ function applyPostgresTemplateClone(worldId, enrichedRepos, options = {}) {
11701
11734
  if (seedTemplates.length === 0) {
11702
11735
  return { worldDbNames: [] };
11703
11736
  }
11704
- const spawn11 = options.spawn ?? spawnSync4;
11737
+ const spawn12 = options.spawn ?? spawnSync4;
11705
11738
  const seedToWorldDb = /* @__PURE__ */ new Map();
11706
11739
  for (const seed of seedTemplates) {
11707
11740
  seedToWorldDb.set(seed, deriveWorldDbName(seed, worldId));
@@ -11709,7 +11742,7 @@ function applyPostgresTemplateClone(worldId, enrichedRepos, options = {}) {
11709
11742
  const created = [];
11710
11743
  for (const seed of seedTemplates) {
11711
11744
  const worldDb = seedToWorldDb.get(seed);
11712
- const exists = spawn11("docker", [
11745
+ const exists = spawn12("docker", [
11713
11746
  "exec",
11714
11747
  container,
11715
11748
  "psql",
@@ -11721,7 +11754,7 @@ function applyPostgresTemplateClone(worldId, enrichedRepos, options = {}) {
11721
11754
  const dbAlreadyExists = exists.status === 0 && (exists.stdout || "").trim() === "1";
11722
11755
  if (!dbAlreadyExists) {
11723
11756
  const sql = `CREATE DATABASE "${worldDb}" TEMPLATE "${seed}"`;
11724
- const create = spawn11("docker", ["exec", container, "psql", "-U", user, "-d", "postgres", "-v", "ON_ERROR_STOP=1", "-c", sql], { encoding: "utf-8" });
11757
+ const create = spawn12("docker", ["exec", container, "psql", "-U", user, "-d", "postgres", "-v", "ON_ERROR_STOP=1", "-c", sql], { encoding: "utf-8" });
11725
11758
  if (create.status !== 0) {
11726
11759
  throw new Error(`failed to CREATE DATABASE "${worldDb}" TEMPLATE "${seed}": ${(create.stderr || "").trim()}`);
11727
11760
  }
@@ -11731,7 +11764,7 @@ function applyPostgresTemplateClone(worldId, enrichedRepos, options = {}) {
11731
11764
  if (stmts !== void 0 && stmts.length > 0) {
11732
11765
  for (const rawStmt of stmts) {
11733
11766
  const stmt = interpolatePostCloneSql(rawStmt, worldId, seedToWorldDb);
11734
- const fixup = spawn11("docker", ["exec", container, "psql", "-U", user, "-d", worldDb, "-v", "ON_ERROR_STOP=1", "-c", stmt], { encoding: "utf-8" });
11767
+ const fixup = spawn12("docker", ["exec", container, "psql", "-U", user, "-d", worldDb, "-v", "ON_ERROR_STOP=1", "-c", stmt], { encoding: "utf-8" });
11735
11768
  if (fixup.status !== 0) {
11736
11769
  throw new Error(`post_clone_sql against "${worldDb}" failed (statement: ${truncate(stmt, 120)}): ${(fixup.stderr || "").trim()}`);
11737
11770
  }
@@ -11759,11 +11792,11 @@ function applyPostgresTestTemplateClone(worldId, enrichedRepos, options = {}) {
11759
11792
  if (testTemplates.length === 0) {
11760
11793
  return { worldTestDbNames: [] };
11761
11794
  }
11762
- const spawn11 = options.spawn ?? spawnSync4;
11795
+ const spawn12 = options.spawn ?? spawnSync4;
11763
11796
  const created = [];
11764
11797
  for (const seed of testTemplates) {
11765
11798
  const worldDb = deriveWorldDbName(seed, worldId);
11766
- const exists = spawn11("docker", [
11799
+ const exists = spawn12("docker", [
11767
11800
  "exec",
11768
11801
  container,
11769
11802
  "psql",
@@ -11775,7 +11808,7 @@ function applyPostgresTestTemplateClone(worldId, enrichedRepos, options = {}) {
11775
11808
  const dbAlreadyExists = exists.status === 0 && (exists.stdout || "").trim() === "1";
11776
11809
  if (!dbAlreadyExists) {
11777
11810
  const sql = `CREATE DATABASE "${worldDb}" TEMPLATE "${seed}"`;
11778
- const create = spawn11("docker", ["exec", container, "psql", "-U", user, "-d", "postgres", "-v", "ON_ERROR_STOP=1", "-c", sql], { encoding: "utf-8" });
11811
+ const create = spawn12("docker", ["exec", container, "psql", "-U", user, "-d", "postgres", "-v", "ON_ERROR_STOP=1", "-c", sql], { encoding: "utf-8" });
11779
11812
  if (create.status !== 0) {
11780
11813
  throw new Error(`failed to CREATE DATABASE "${worldDb}" TEMPLATE "${seed}": ${(create.stderr || "").trim()}`);
11781
11814
  }
@@ -11816,13 +11849,13 @@ function applyPostgresWorldRole(worldId, worldDbNames, options = {}) {
11816
11849
  if (worldDbNames.length === 0) {
11817
11850
  throw new Error(`applyPostgresWorldRole called with empty worldDbNames for "${worldId}" \u2014 should only run when applyPostgresTemplateClone produced clones`);
11818
11851
  }
11819
- const spawn11 = options.spawn ?? spawnSync4;
11852
+ const spawn12 = options.spawn ?? spawnSync4;
11820
11853
  const container = options.singletonContainer ?? "olam-postgres";
11821
11854
  const user = options.postgresUser ?? "development";
11822
11855
  const genPassword = options.generatePassword ?? defaultPasswordGenerator;
11823
11856
  const worldRoleName = deriveWorldRoleName(worldId);
11824
11857
  const password = genPassword();
11825
- const exists = spawn11("docker", [
11858
+ const exists = spawn12("docker", [
11826
11859
  "exec",
11827
11860
  container,
11828
11861
  "psql",
@@ -11834,12 +11867,12 @@ function applyPostgresWorldRole(worldId, worldDbNames, options = {}) {
11834
11867
  const roleExists = exists.status === 0 && (exists.stdout || "").trim() === "1";
11835
11868
  const escapedPassword = escapeSqlLiteral(password);
11836
11869
  const roleDdl = roleExists ? `ALTER ROLE "${worldRoleName}" WITH LOGIN PASSWORD '${escapedPassword}' NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION` : `CREATE ROLE "${worldRoleName}" WITH LOGIN PASSWORD '${escapedPassword}' NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION`;
11837
- const dml = spawn11("docker", ["exec", container, "psql", "-U", user, "-d", "postgres", "-v", "ON_ERROR_STOP=1", "-c", roleDdl], { encoding: "utf-8" });
11870
+ const dml = spawn12("docker", ["exec", container, "psql", "-U", user, "-d", "postgres", "-v", "ON_ERROR_STOP=1", "-c", roleDdl], { encoding: "utf-8" });
11838
11871
  if (dml.status !== 0) {
11839
11872
  throw new Error(`failed to ${roleExists ? "ALTER" : "CREATE"} ROLE "${worldRoleName}": ` + redactCreateRolePassword((dml.stderr || "").trim()));
11840
11873
  }
11841
11874
  for (const worldDb of worldDbNames) {
11842
- const grantConnect = spawn11("docker", [
11875
+ const grantConnect = spawn12("docker", [
11843
11876
  "exec",
11844
11877
  container,
11845
11878
  "psql",
@@ -11864,7 +11897,7 @@ function applyPostgresWorldRole(worldId, worldDbNames, options = {}) {
11864
11897
  `ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO "${worldRoleName}"`,
11865
11898
  `ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO "${worldRoleName}"`
11866
11899
  ].join("; ");
11867
- const grantResult = spawn11("docker", ["exec", container, "psql", "-U", user, "-d", worldDb, "-v", "ON_ERROR_STOP=1", "-c", perDbGrants], { encoding: "utf-8" });
11900
+ const grantResult = spawn12("docker", ["exec", container, "psql", "-U", user, "-d", worldDb, "-v", "ON_ERROR_STOP=1", "-c", perDbGrants], { encoding: "utf-8" });
11868
11901
  if (grantResult.status !== 0) {
11869
11902
  throw new Error(`failed to GRANT privileges on "${worldDb}" to "${worldRoleName}": ${(grantResult.stderr || "").trim()}`);
11870
11903
  }
@@ -11884,11 +11917,11 @@ function dropPostgresWorldDbs(worldId, worldDbNames, options = {}) {
11884
11917
  if (worldDbNames.length === 0)
11885
11918
  return;
11886
11919
  assertSafeWorldId(worldId);
11887
- const spawn11 = options.spawn ?? spawnSync4;
11920
+ const spawn12 = options.spawn ?? spawnSync4;
11888
11921
  const container = options.container ?? "olam-postgres";
11889
11922
  const user = options.user ?? "development";
11890
11923
  for (const db of worldDbNames) {
11891
- const drop = spawn11("docker", [
11924
+ const drop = spawn12("docker", [
11892
11925
  "exec",
11893
11926
  container,
11894
11927
  "psql",
@@ -11908,10 +11941,10 @@ function dropPostgresWorldRole(worldId, worldRoleName, options = {}) {
11908
11941
  if (!worldRoleName)
11909
11942
  return;
11910
11943
  assertSafeWorldId(worldId);
11911
- const spawn11 = options.spawn ?? spawnSync4;
11944
+ const spawn12 = options.spawn ?? spawnSync4;
11912
11945
  const container = options.container ?? "olam-postgres";
11913
11946
  const user = options.user ?? "development";
11914
- const cleanup = spawn11("docker", [
11947
+ const cleanup = spawn12("docker", [
11915
11948
  "exec",
11916
11949
  container,
11917
11950
  "psql",
@@ -15062,10 +15095,10 @@ async function readHostCpToken2() {
15062
15095
  if (!fs26.existsSync(tp)) return null;
15063
15096
  return fs26.readFileSync(tp, "utf-8").trim();
15064
15097
  }
15065
- async function callHostCpProxy(method, worldId, path60, body) {
15098
+ async function callHostCpProxy(method, worldId, path66, body) {
15066
15099
  const token = await readHostCpToken2();
15067
15100
  if (!token) return { ok: false, status: 0, error: "no token (host CP not started)" };
15068
- const url2 = `http://127.0.0.1:${HOST_CP_PORT}/api/world/${encodeURIComponent(worldId)}${path60}`;
15101
+ const url2 = `http://127.0.0.1:${HOST_CP_PORT}/api/world/${encodeURIComponent(worldId)}${path66}`;
15069
15102
  try {
15070
15103
  const headers = {
15071
15104
  Authorization: `Bearer ${token}`
@@ -16102,9 +16135,9 @@ var UnknownArchetypeError = class extends Error {
16102
16135
  };
16103
16136
  var ArchetypeCycleError = class extends Error {
16104
16137
  path;
16105
- constructor(path60) {
16106
- super(`Archetype inheritance cycle detected: ${path60.join(" \u2192 ")} \u2192 ${path60[0] ?? "?"}`);
16107
- this.path = path60;
16138
+ constructor(path66) {
16139
+ super(`Archetype inheritance cycle detected: ${path66.join(" \u2192 ")} \u2192 ${path66[0] ?? "?"}`);
16140
+ this.path = path66;
16108
16141
  this.name = "ArchetypeCycleError";
16109
16142
  }
16110
16143
  };
@@ -17692,9 +17725,9 @@ function formatFreshnessWarning(result, image = DEFAULT_DEVBOX_IMAGE) {
17692
17725
  "These source files have changed since the image was built; the",
17693
17726
  "changes will NOT take effect in fresh worlds until you rebuild:"
17694
17727
  ];
17695
- for (const { path: path60, mtimeMs } of result.newerSources) {
17728
+ for (const { path: path66, mtimeMs } of result.newerSources) {
17696
17729
  const when = new Date(mtimeMs).toISOString();
17697
- lines.push(` \u2022 ${path60} (modified ${when})`);
17730
+ lines.push(` \u2022 ${path66} (modified ${when})`);
17698
17731
  }
17699
17732
  lines.push("");
17700
17733
  lines.push("Rebuild with:");
@@ -17862,56 +17895,56 @@ var SECRET_LEN_BYTES = 32;
17862
17895
  function generateSecret() {
17863
17896
  return randomBytes6(SECRET_LEN_BYTES).toString("hex");
17864
17897
  }
17865
- function writeSecretAtPath(path60, value) {
17866
- mkdirSync18(dirname19(path60), { recursive: true });
17867
- const tmp = `${path60}.tmp.${process.pid}`;
17898
+ function writeSecretAtPath(path66, value) {
17899
+ mkdirSync18(dirname19(path66), { recursive: true });
17900
+ const tmp = `${path66}.tmp.${process.pid}`;
17868
17901
  writeFileSync13(tmp, value, { mode: 384 });
17869
17902
  chmodSync3(tmp, 384);
17870
- renameSync4(tmp, path60);
17903
+ renameSync4(tmp, path66);
17871
17904
  }
17872
- function readSecretAtPathOrNull(path60) {
17873
- if (!existsSync28(path60)) return null;
17874
- const mode = statSync7(path60).mode & 511;
17905
+ function readSecretAtPathOrNull(path66) {
17906
+ if (!existsSync28(path66)) return null;
17907
+ const mode = statSync7(path66).mode & 511;
17875
17908
  if (mode !== 384) {
17876
17909
  process.stderr.write(
17877
- `warn: ${path60} has mode 0${mode.toString(8)}; expected 0600. Run 'olam memory secret rotate' to regenerate.
17910
+ `warn: ${path66} has mode 0${mode.toString(8)}; expected 0600. Run 'olam memory secret rotate' to regenerate.
17878
17911
  `
17879
17912
  );
17880
17913
  }
17881
- return readFileSync20(path60, "utf8").trim();
17914
+ return readFileSync20(path66, "utf8").trim();
17882
17915
  }
17883
- function readSecretAtPath(path60) {
17884
- const v = readSecretAtPathOrNull(path60);
17916
+ function readSecretAtPath(path66) {
17917
+ const v = readSecretAtPathOrNull(path66);
17885
17918
  if (v === null) {
17886
17919
  throw new Error(
17887
- `Secret not found at ${path60}. Run 'olam memory start' to generate it.`
17920
+ `Secret not found at ${path66}. Run 'olam memory start' to generate it.`
17888
17921
  );
17889
17922
  }
17890
17923
  return v;
17891
17924
  }
17892
- function ensureMemorySecret(path60 = MEMORY_SECRET_PATH) {
17893
- const existing = readSecretAtPathOrNull(path60);
17925
+ function ensureMemorySecret(path66 = MEMORY_SECRET_PATH) {
17926
+ const existing = readSecretAtPathOrNull(path66);
17894
17927
  if (existing) return existing;
17895
17928
  const fresh = generateSecret();
17896
- writeSecretAtPath(path60, fresh);
17929
+ writeSecretAtPath(path66, fresh);
17897
17930
  return fresh;
17898
17931
  }
17899
- function readMemorySecretOrNull(path60 = MEMORY_SECRET_PATH) {
17900
- return readSecretAtPathOrNull(path60);
17932
+ function readMemorySecretOrNull(path66 = MEMORY_SECRET_PATH) {
17933
+ return readSecretAtPathOrNull(path66);
17901
17934
  }
17902
- function readMemorySecret(path60 = MEMORY_SECRET_PATH) {
17903
- return readSecretAtPath(path60);
17935
+ function readMemorySecret(path66 = MEMORY_SECRET_PATH) {
17936
+ return readSecretAtPath(path66);
17904
17937
  }
17905
- function rotateMemorySecret(path60 = MEMORY_SECRET_PATH) {
17938
+ function rotateMemorySecret(path66 = MEMORY_SECRET_PATH) {
17906
17939
  const fresh = generateSecret();
17907
- writeSecretAtPath(path60, fresh);
17940
+ writeSecretAtPath(path66, fresh);
17908
17941
  return fresh;
17909
17942
  }
17910
- function hasMemorySecret(path60 = MEMORY_SECRET_PATH) {
17911
- return existsSync28(path60);
17943
+ function hasMemorySecret(path66 = MEMORY_SECRET_PATH) {
17944
+ return existsSync28(path66);
17912
17945
  }
17913
- function writeCloudMemorySecret(value, path60 = CLOUD_MEMORY_SECRET_PATH) {
17914
- writeSecretAtPath(path60, value);
17946
+ function writeCloudMemorySecret(value, path66 = CLOUD_MEMORY_SECRET_PATH) {
17947
+ writeSecretAtPath(path66, value);
17915
17948
  }
17916
17949
 
17917
17950
  // src/lib/world-mcp-register.ts
@@ -17983,15 +18016,15 @@ var AGENTMEMORY_LOCAL_URL = "http://host.docker.internal:3111";
17983
18016
  var HOST_CP_URL = "http://127.0.0.1:19000";
17984
18017
  async function readHostCpTokenForCreate() {
17985
18018
  try {
17986
- const { default: fs56 } = await import("node:fs");
17987
- const { default: os32 } = await import("node:os");
17988
- const { default: path60 } = await import("node:path");
17989
- const tp = path60.join(
17990
- process.env.OLAM_HOME ?? path60.join(os32.homedir(), ".olam"),
18019
+ const { default: fs62 } = await import("node:fs");
18020
+ const { default: os36 } = await import("node:os");
18021
+ const { default: path66 } = await import("node:path");
18022
+ const tp = path66.join(
18023
+ process.env.OLAM_HOME ?? path66.join(os36.homedir(), ".olam"),
17991
18024
  "host-cp.token"
17992
18025
  );
17993
- if (!fs56.existsSync(tp)) return null;
17994
- return fs56.readFileSync(tp, "utf-8").trim();
18026
+ if (!fs62.existsSync(tp)) return null;
18027
+ return fs62.readFileSync(tp, "utf-8").trim();
17995
18028
  } catch {
17996
18029
  return null;
17997
18030
  }
@@ -18399,12 +18432,12 @@ function defaultNameFromPrompt(prompt) {
18399
18432
  }
18400
18433
  async function readHostCpToken3() {
18401
18434
  try {
18402
- const { default: fs56 } = await import("node:fs");
18403
- const { default: os32 } = await import("node:os");
18404
- const { default: path60 } = await import("node:path");
18405
- const tp = path60.join(os32.homedir(), ".olam", "host-cp.token");
18406
- if (!fs56.existsSync(tp)) return null;
18407
- const raw = fs56.readFileSync(tp, "utf-8").trim();
18435
+ const { default: fs62 } = await import("node:fs");
18436
+ const { default: os36 } = await import("node:os");
18437
+ const { default: path66 } = await import("node:path");
18438
+ const tp = path66.join(os36.homedir(), ".olam", "host-cp.token");
18439
+ if (!fs62.existsSync(tp)) return null;
18440
+ const raw = fs62.readFileSync(tp, "utf-8").trim();
18408
18441
  return raw.length > 0 ? raw : null;
18409
18442
  } catch {
18410
18443
  return null;
@@ -18617,10 +18650,72 @@ function registerList(program2) {
18617
18650
  // src/commands/status.ts
18618
18651
  init_output();
18619
18652
  import * as fs30 from "node:fs";
18653
+ import * as http3 from "node:http";
18620
18654
  import * as os16 from "node:os";
18621
18655
  import * as path31 from "node:path";
18622
18656
  var CLI_VERSION2 = process.env["OLAM_CLI_VERSION"] ?? "0.0.0";
18623
18657
  var HOST_CP_PORT2 = 19e3;
18658
+ var STATE_ENUM = [
18659
+ "running",
18660
+ "starting",
18661
+ "stopped",
18662
+ "crashed",
18663
+ "unknown"
18664
+ ];
18665
+ function parseRuntimeStatus(raw) {
18666
+ if (!raw || typeof raw !== "object") return null;
18667
+ const obj = raw;
18668
+ const state = obj.state;
18669
+ if (typeof state !== "string") return null;
18670
+ if (!STATE_ENUM.includes(state)) return null;
18671
+ const ready_replicas = typeof obj.ready_replicas === "number" ? obj.ready_replicas : 0;
18672
+ const restarts = typeof obj.restarts === "number" ? obj.restarts : 0;
18673
+ const last_event_type = typeof obj.last_event_type === "string" ? obj.last_event_type : null;
18674
+ const last_event_age_seconds = typeof obj.last_event_age_seconds === "number" ? obj.last_event_age_seconds : null;
18675
+ return {
18676
+ state,
18677
+ ready_replicas,
18678
+ restarts,
18679
+ last_event_type,
18680
+ last_event_age_seconds
18681
+ };
18682
+ }
18683
+ var fetchWorldRuntimeStatus = (worldId, token) => new Promise((resolve15) => {
18684
+ const opts = {
18685
+ host: "127.0.0.1",
18686
+ port: HOST_CP_PORT2,
18687
+ path: `/v1/worlds/${encodeURIComponent(worldId)}/status`,
18688
+ method: "GET",
18689
+ headers: { Authorization: `Bearer ${token}` },
18690
+ timeout: 2e3
18691
+ };
18692
+ const req = http3.request(opts, (res) => {
18693
+ if (res.statusCode !== 200) {
18694
+ res.resume();
18695
+ return resolve15(null);
18696
+ }
18697
+ let body = "";
18698
+ res.setEncoding("utf-8");
18699
+ res.on("data", (c) => {
18700
+ body += c;
18701
+ });
18702
+ res.on("end", () => {
18703
+ try {
18704
+ const parsed = parseRuntimeStatus(JSON.parse(body));
18705
+ resolve15(parsed);
18706
+ } catch {
18707
+ resolve15(null);
18708
+ }
18709
+ });
18710
+ res.on("error", () => resolve15(null));
18711
+ });
18712
+ req.on("error", () => resolve15(null));
18713
+ req.on("timeout", () => {
18714
+ req.destroy();
18715
+ resolve15(null);
18716
+ });
18717
+ req.end();
18718
+ });
18624
18719
  async function getMachineStatus(_probe, _loadCtx, _readToken) {
18625
18720
  const probe2 = _probe ?? (async () => {
18626
18721
  const { probeHostCp: probeHostCp2 } = await Promise.resolve().then(() => (init_host_cp(), host_cp_exports));
@@ -18673,7 +18768,7 @@ async function getMachineStatus(_probe, _loadCtx, _readToken) {
18673
18768
  };
18674
18769
  }
18675
18770
  function registerStatus(program2) {
18676
- program2.command("status").description("Show status (machine status when no world given; world details with a world ID)").argument("[world]", "World ID \u2014 omit to show machine status").option("--json", "Output as JSON").action(async (worldId, opts) => {
18771
+ program2.command("status").description("Show status (machine status when no world given; world details with a world ID)").argument("[world]", "World ID \u2014 omit to show machine status").option("--json", "Output as JSON").option("--pretty", "Render world status as a human-readable table (engine-aware view)").action(async (worldId, opts) => {
18677
18772
  if (!worldId) {
18678
18773
  const ms = await getMachineStatus();
18679
18774
  if (opts.json) {
@@ -18710,8 +18805,13 @@ function registerStatus(program2) {
18710
18805
  }
18711
18806
  const cost = ctx.costTracker.getWorldCost(worldId);
18712
18807
  const dashboardUrl = `http://localhost:${19080 + world.portOffset}`;
18808
+ const { readToken: readToken2 } = await Promise.resolve().then(() => (init_host_cp(), host_cp_exports));
18809
+ const token = readToken2();
18810
+ const runtime = token ? await fetchWorldRuntimeStatus(worldId, token) : null;
18713
18811
  if (opts.json) {
18714
- console.log(JSON.stringify({ ...world, dashboardUrl, cost }, null, 2));
18812
+ const payload = { ...world, dashboardUrl, cost };
18813
+ if (runtime) payload.runtime = runtime;
18814
+ console.log(JSON.stringify(payload, null, 2));
18715
18815
  return;
18716
18816
  }
18717
18817
  printHeader(`${world.name} (${world.id})`);
@@ -18732,6 +18832,15 @@ function registerStatus(program2) {
18732
18832
  printInfo("Created", world.createdAt);
18733
18833
  printInfo("Uptime", formatAge(world.createdAt));
18734
18834
  printInfo("Updated", world.updatedAt);
18835
+ if (runtime) {
18836
+ printInfo("Runtime state", runtime.state);
18837
+ printInfo("Ready replicas", String(runtime.ready_replicas));
18838
+ printInfo("Restarts", String(runtime.restarts));
18839
+ if (runtime.last_event_type) {
18840
+ const ageSuffix = runtime.last_event_age_seconds != null ? ` (${runtime.last_event_age_seconds}s ago)` : "";
18841
+ printInfo("Last event", `${runtime.last_event_type}${ageSuffix}`);
18842
+ }
18843
+ }
18735
18844
  });
18736
18845
  }
18737
18846
 
@@ -19726,7 +19835,7 @@ var DEFAULT_TIMEOUT_MS2 = 3e3;
19726
19835
  function worldBaseUrl(portOffset) {
19727
19836
  return `http://127.0.0.1:${HOST_CONTROL_PLANE_BASE2 + portOffset}`;
19728
19837
  }
19729
- async function request(url2, init = {}, timeoutMs = DEFAULT_TIMEOUT_MS2) {
19838
+ async function request2(url2, init = {}, timeoutMs = DEFAULT_TIMEOUT_MS2) {
19730
19839
  const controller = new AbortController();
19731
19840
  const timer = setTimeout(() => controller.abort(), timeoutMs);
19732
19841
  try {
@@ -19736,13 +19845,13 @@ async function request(url2, init = {}, timeoutMs = DEFAULT_TIMEOUT_MS2) {
19736
19845
  }
19737
19846
  }
19738
19847
  async function listGates(portOffset) {
19739
- const res = await request(`${worldBaseUrl(portOffset)}/api/pr-gate`);
19848
+ const res = await request2(`${worldBaseUrl(portOffset)}/api/pr-gate`);
19740
19849
  if (!res.ok)
19741
19850
  throw new Error(`list gates failed: HTTP ${res.status}`);
19742
19851
  return await res.json();
19743
19852
  }
19744
19853
  async function getGate(portOffset, id) {
19745
- const res = await request(`${worldBaseUrl(portOffset)}/api/pr-gate/${encodeURIComponent(id)}`);
19854
+ const res = await request2(`${worldBaseUrl(portOffset)}/api/pr-gate/${encodeURIComponent(id)}`);
19746
19855
  if (res.status === 404)
19747
19856
  return null;
19748
19857
  if (!res.ok)
@@ -19750,7 +19859,7 @@ async function getGate(portOffset, id) {
19750
19859
  return await res.json();
19751
19860
  }
19752
19861
  async function decideGate(portOffset, id, payload) {
19753
- const res = await request(`${worldBaseUrl(portOffset)}/api/pr-gate/${encodeURIComponent(id)}/decision`, {
19862
+ const res = await request2(`${worldBaseUrl(portOffset)}/api/pr-gate/${encodeURIComponent(id)}/decision`, {
19754
19863
  method: "POST",
19755
19864
  headers: { "Content-Type": "application/json" },
19756
19865
  body: JSON.stringify(payload)
@@ -22135,11 +22244,11 @@ function zodIssueToError(issue, doc, lineCounter) {
22135
22244
  suggestion: deriveSuggestion(issue)
22136
22245
  };
22137
22246
  }
22138
- function formatJsonPath(path60) {
22139
- if (path60.length === 0)
22247
+ function formatJsonPath(path66) {
22248
+ if (path66.length === 0)
22140
22249
  return "<root>";
22141
22250
  let out = "";
22142
- for (const seg of path60) {
22251
+ for (const seg of path66) {
22143
22252
  if (typeof seg === "number") {
22144
22253
  out += `[${seg}]`;
22145
22254
  } else {
@@ -22148,11 +22257,11 @@ function formatJsonPath(path60) {
22148
22257
  }
22149
22258
  return out;
22150
22259
  }
22151
- function resolveYamlLocation(path60, doc, lineCounter) {
22260
+ function resolveYamlLocation(path66, doc, lineCounter) {
22152
22261
  let bestLine = 0;
22153
22262
  let bestColumn = 0;
22154
- for (let depth = path60.length; depth >= 0; depth -= 1) {
22155
- const segment = path60.slice(0, depth);
22263
+ for (let depth = path66.length; depth >= 0; depth -= 1) {
22264
+ const segment = path66.slice(0, depth);
22156
22265
  try {
22157
22266
  const node = doc.getIn(segment, true);
22158
22267
  if (node && typeof node === "object" && "range" in node) {
@@ -22370,11 +22479,11 @@ function topoSort(nodes) {
22370
22479
  }
22371
22480
  function traceCycle(start, byId) {
22372
22481
  const seen = /* @__PURE__ */ new Set();
22373
- const path60 = [];
22482
+ const path66 = [];
22374
22483
  let current = start;
22375
22484
  while (current && !seen.has(current)) {
22376
22485
  seen.add(current);
22377
- path60.push(current);
22486
+ path66.push(current);
22378
22487
  const node = byId.get(current);
22379
22488
  const next = node?.dependsOn[0];
22380
22489
  if (next === void 0)
@@ -22382,10 +22491,10 @@ function traceCycle(start, byId) {
22382
22491
  current = next;
22383
22492
  }
22384
22493
  if (current && seen.has(current)) {
22385
- const idx = path60.indexOf(current);
22386
- return [...path60.slice(idx), current];
22494
+ const idx = path66.indexOf(current);
22495
+ return [...path66.slice(idx), current];
22387
22496
  }
22388
- return path60;
22497
+ return path66;
22389
22498
  }
22390
22499
 
22391
22500
  // ../core/dist/executor/types.js
@@ -25071,6 +25180,32 @@ async function runUpgradePullByDigest(deps = {}) {
25071
25180
  return { exitCode: EXIT_GENERIC_ERROR, summary: "socket-proxy recreate failed" };
25072
25181
  }
25073
25182
  proxySpinner.succeed("docker-socket-proxy recreated");
25183
+ const inspectContainer = deps.inspectContainerImpl ?? defaultInspectContainerLabels;
25184
+ const removeContainer = deps.removeContainerImpl ?? defaultRemoveContainer;
25185
+ const orphanCheck = inspectContainer("olam-host-cp");
25186
+ if (!orphanCheck.ok) {
25187
+ process.stderr.write(
25188
+ `${pc18.red("error")} docker inspect olam-host-cp failed:
25189
+ ${orphanCheck.stderr.split("\n").slice(0, 3).join("\n ")}
25190
+ `
25191
+ );
25192
+ return { exitCode: EXIT_GENERIC_ERROR, summary: "orphan inspect failed" };
25193
+ }
25194
+ if (orphanCheck.exists && orphanCheck.service !== "olam-host-cp") {
25195
+ process.stderr.write(
25196
+ `${pc18.yellow("info")} Removing pre-rename orphan container olam-host-cp (old project=${orphanCheck.project || "<none>"}, old service=${orphanCheck.service || "<none>"})
25197
+ `
25198
+ );
25199
+ const rm = removeContainer("olam-host-cp");
25200
+ if (!rm.ok) {
25201
+ process.stderr.write(
25202
+ `${pc18.red("error")} docker rm -f olam-host-cp failed:
25203
+ ${rm.stderr.split("\n").slice(0, 3).join("\n ")}
25204
+ `
25205
+ );
25206
+ return { exitCode: EXIT_GENERIC_ERROR, summary: "orphan cleanup failed" };
25207
+ }
25208
+ }
25074
25209
  const composeSpinner = ora7("docker compose recreate olam-host-cp").start();
25075
25210
  const composeResult = composeRunner(
25076
25211
  ["up", "-d", "--force-recreate", "--no-deps", "olam-host-cp"],
@@ -25175,6 +25310,37 @@ async function defaultRecreateAuthForUpgrade() {
25175
25310
  };
25176
25311
  }
25177
25312
  }
25313
+ function defaultInspectContainerLabels(containerName) {
25314
+ const result = spawnSync15(
25315
+ "docker",
25316
+ [
25317
+ "inspect",
25318
+ "--format",
25319
+ '{{index .Config.Labels "com.docker.compose.project"}}|{{index .Config.Labels "com.docker.compose.service"}}',
25320
+ containerName
25321
+ ],
25322
+ { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }
25323
+ );
25324
+ const stderr = result.stderr ?? "";
25325
+ if (result.status === 0) {
25326
+ const [project = "", service = ""] = (result.stdout ?? "").trim().split("|");
25327
+ return { ok: true, exists: true, project, service, stderr: "" };
25328
+ }
25329
+ if (/No such (object|container)/i.test(stderr)) {
25330
+ return { ok: true, exists: false, project: "", service: "", stderr: "" };
25331
+ }
25332
+ if (result.status === null) {
25333
+ return { ok: true, exists: false, project: "", service: "", stderr: "" };
25334
+ }
25335
+ return { ok: false, exists: false, project: "", service: "", stderr };
25336
+ }
25337
+ function defaultRemoveContainer(containerName) {
25338
+ const result = spawnSync15("docker", ["rm", "-f", containerName], {
25339
+ encoding: "utf-8",
25340
+ stdio: ["ignore", "pipe", "pipe"]
25341
+ });
25342
+ return { ok: result.status === 0, stderr: result.stderr ?? "" };
25343
+ }
25178
25344
  async function handleUpgrade(opts) {
25179
25345
  const cwd = process.cwd();
25180
25346
  const rootCheck = validateRepoRoot(cwd);
@@ -25721,7 +25887,7 @@ function registerUpgrade(program2) {
25721
25887
  init_host_cp();
25722
25888
  init_context();
25723
25889
  init_output();
25724
- import * as http3 from "node:http";
25890
+ import * as http4 from "node:http";
25725
25891
  import pc19 from "picocolors";
25726
25892
  var HOST_CP_PORT3 = 19e3;
25727
25893
  function colorLine(line) {
@@ -25730,25 +25896,16 @@ function colorLine(line) {
25730
25896
  if (/\bINFO\b/.test(line)) return pc19.dim(line);
25731
25897
  return line;
25732
25898
  }
25733
- function formatLine(line, service, showService) {
25734
- const prefix = showService && service ? `${pc19.cyan(`[${service}]`)} ` : "";
25735
- return prefix + colorLine(line);
25736
- }
25737
- function parseSseEvent(raw) {
25738
- if (!raw.startsWith("data: ")) return null;
25739
- try {
25740
- const parsed = JSON.parse(raw.slice(6));
25741
- if (!parsed || typeof parsed !== "object") return null;
25742
- const ev = parsed;
25743
- if (ev.type === "replay" && Array.isArray(ev.lines)) return ev;
25744
- if (ev.type === "line" && typeof ev.line === "string") return ev;
25745
- return null;
25746
- } catch {
25747
- return null;
25748
- }
25899
+ function buildLogsUrl(worldId, opts, port2 = HOST_CP_PORT3) {
25900
+ const qs = new URLSearchParams({
25901
+ tail: String(opts.tail),
25902
+ follow: opts.follow ? "1" : "0"
25903
+ });
25904
+ if (opts.service) qs.set("service", opts.service);
25905
+ return `http://127.0.0.1:${port2}/v1/worlds/${encodeURIComponent(worldId)}/logs?${qs.toString()}`;
25749
25906
  }
25750
25907
  function registerLogs(program2) {
25751
- program2.command("logs").description("Stream application logs from a world").argument("<world>", "World ID").option("--service <name>", "Stream a specific service (default: all services)").option("--follow", "Stream indefinitely until Ctrl-C", false).option("--tail <n>", "Number of lines to display before exiting", "200").action(async (worldId, opts) => {
25908
+ program2.command("logs").description("Stream application logs from a world (engine-agnostic)").argument("<world>", "World ID").option("--service <name>", "Stream a specific container within the world").option("--follow", "Stream indefinitely until Ctrl-C", false).option("--tail <n>", "Number of lines to display before exiting", "200").action(async (worldId, opts) => {
25752
25909
  const { ctx, error } = await loadContext();
25753
25910
  if (!ctx) {
25754
25911
  printError(error?.message ?? "Olam is not configured. Run `olam init` first.");
@@ -25768,10 +25925,11 @@ function registerLogs(program2) {
25768
25925
  return;
25769
25926
  }
25770
25927
  const tailLimit = Math.max(1, parseInt(opts.tail, 10) || 200);
25771
- const showService = opts.service === void 0;
25772
- const subPath = opts.service ? `/api/logs/${encodeURIComponent(opts.service)}` : "/api/logs";
25773
- const url2 = `http://127.0.0.1:${HOST_CP_PORT3}/api/world/${encodeURIComponent(worldId)}${subPath}`;
25774
- let lineCount = 0;
25928
+ const url2 = buildLogsUrl(worldId, {
25929
+ tail: tailLimit,
25930
+ follow: opts.follow,
25931
+ ...opts.service ? { service: opts.service } : {}
25932
+ });
25775
25933
  let done = false;
25776
25934
  let resolveStream;
25777
25935
  const streamDone = new Promise((r) => {
@@ -25783,16 +25941,7 @@ function registerLogs(program2) {
25783
25941
  resolveStream();
25784
25942
  }
25785
25943
  };
25786
- const emit2 = (line, service) => {
25787
- process.stdout.write(formatLine(line, service, showService) + "\n");
25788
- lineCount++;
25789
- if (!opts.follow && lineCount >= tailLimit) {
25790
- finish();
25791
- return false;
25792
- }
25793
- return true;
25794
- };
25795
- const req = http3.get(url2, { headers: { Authorization: `Bearer ${token}` } }, (res) => {
25944
+ const req = http4.get(url2, { headers: { Authorization: `Bearer ${token}` } }, (res) => {
25796
25945
  if (res.statusCode !== 200) {
25797
25946
  printError(`Log stream returned HTTP ${res.statusCode ?? "unknown"}`);
25798
25947
  process.exitCode = 1;
@@ -25808,21 +25957,14 @@ function registerLogs(program2) {
25808
25957
  buf = rawLines.pop() ?? "";
25809
25958
  for (const raw of rawLines) {
25810
25959
  if (done) break;
25811
- const ev = parseSseEvent(raw);
25812
- if (!ev) continue;
25813
- if (ev.type === "replay") {
25814
- for (const l of ev.lines) {
25815
- if (!emit2(l, ev.service)) {
25816
- req.destroy();
25817
- break;
25818
- }
25819
- }
25820
- } else if (ev.type === "line") {
25821
- if (!emit2(ev.line, ev.service)) req.destroy();
25822
- }
25960
+ if (!raw) continue;
25961
+ process.stdout.write(colorLine(raw) + "\n");
25823
25962
  }
25824
25963
  });
25825
- res.on("end", () => finish());
25964
+ res.on("end", () => {
25965
+ if (buf) process.stdout.write(colorLine(buf) + "\n");
25966
+ finish();
25967
+ });
25826
25968
  res.on("error", (err) => {
25827
25969
  if (!done) printError(`Stream error: ${err.message}`);
25828
25970
  finish();
@@ -26845,6 +26987,11 @@ var HARD_CAP_BYTES = 5e9;
26845
26987
 
26846
26988
  // src/lib/health-probes.ts
26847
26989
  var HEALTH_TIMEOUT_MS2 = 5e3;
26990
+ var COLIMA_010_WARN_TEXT = (
26991
+ // eslint-disable-next-line @typescript-eslint/quotes
26992
+ "Colima 0.10.1's --kubernetes mode is broken upstream. Either switch to OLAM_HOST_CP_ENGINE=docker (recommended) or downgrade Colima to 0.9.x. Track abiosoft/colima issues for fix."
26993
+ );
26994
+ var COLIMA_010_RANGE = /^v?0\.10\.\d+$/;
26848
26995
  var defaultDockerExec2 = (cmd, args) => {
26849
26996
  const r = spawnSync18(cmd, [...args], {
26850
26997
  encoding: "utf-8",
@@ -27068,6 +27215,146 @@ function formatBytes4(n) {
27068
27215
  if (n < 1024 * 1024 * 1024) return `${(n / 1024 / 1024).toFixed(1)} MB`;
27069
27216
  return `${(n / 1024 / 1024 / 1024).toFixed(2)} GB`;
27070
27217
  }
27218
+ async function probeEngine(fetchImpl = defaultFetch) {
27219
+ const controller = new AbortController();
27220
+ const timeout = setTimeout(() => controller.abort(), HEALTH_TIMEOUT_MS2);
27221
+ try {
27222
+ const res = await fetchImpl("http://127.0.0.1:19000/health", { signal: controller.signal });
27223
+ await res.text().catch(() => "");
27224
+ if (res.status !== 200) {
27225
+ return {
27226
+ ok: false,
27227
+ message: `host-cp /health returned ${res.status}; engine unknown`,
27228
+ remedy: "Restart host-cp via `olam host-cp start`; verify port 19000 is bound to 127.0.0.1."
27229
+ };
27230
+ }
27231
+ const engineHeader = res.headers.get("x-olam-engine") ?? res.headers.get("X-Olam-Engine");
27232
+ if (!engineHeader) {
27233
+ return {
27234
+ ok: true,
27235
+ message: "engine unknown (X-Olam-Engine header absent \u2014 older host-cp?)"
27236
+ };
27237
+ }
27238
+ return {
27239
+ ok: true,
27240
+ message: `engine ${engineHeader}`,
27241
+ engineName: engineHeader
27242
+ };
27243
+ } catch (err) {
27244
+ const e = err;
27245
+ const reason = e.name === "AbortError" ? `timeout (${HEALTH_TIMEOUT_MS2}ms)` : e.message;
27246
+ return {
27247
+ ok: false,
27248
+ message: `engine probe unreachable: ${reason}`,
27249
+ remedy: "Restart host-cp via `olam host-cp start`; verify port 19000 is bound to 127.0.0.1."
27250
+ };
27251
+ } finally {
27252
+ clearTimeout(timeout);
27253
+ }
27254
+ }
27255
+ async function probeColimaVersion(dockerExec = defaultDockerExec2) {
27256
+ const r = dockerExec("colima", ["version"]);
27257
+ if (r.status === null || r.status !== 0 && /not found|ENOENT|not.found/i.test(r.stderr)) {
27258
+ return { ok: true, message: "colima not installed (not in use)" };
27259
+ }
27260
+ if (r.status !== 0) {
27261
+ return {
27262
+ ok: true,
27263
+ message: `colima present but version probe failed: ${(r.stderr || "").trim()}`
27264
+ };
27265
+ }
27266
+ const tokens = r.stdout.split(/\s+/);
27267
+ const version = tokens.find((t) => /^v?\d+\.\d+\.\d+/.test(t)) ?? "";
27268
+ if (!version) {
27269
+ return {
27270
+ ok: true,
27271
+ message: `colima present but version unrecognised: ${r.stdout.trim().slice(0, 80)}`
27272
+ };
27273
+ }
27274
+ if (COLIMA_010_RANGE.test(version)) {
27275
+ return {
27276
+ ok: true,
27277
+ warn: true,
27278
+ message: `colima ${version} detected \u2014 known kubernetes-mode regression`,
27279
+ remedy: COLIMA_010_WARN_TEXT
27280
+ };
27281
+ }
27282
+ return { ok: true, message: `colima ${version} (clear)` };
27283
+ }
27284
+ async function probeK8sApiReachable(exec = defaultDockerExec2) {
27285
+ const r = exec("kubectl", ["version", "--output=json", "--request-timeout=2s"]);
27286
+ if (r.status === 0 && r.stdout.length > 0) {
27287
+ let parsed;
27288
+ try {
27289
+ parsed = JSON.parse(r.stdout);
27290
+ } catch {
27291
+ parsed = null;
27292
+ }
27293
+ const obj = parsed && typeof parsed === "object" ? parsed : {};
27294
+ const serverInfo = obj.serverVersion;
27295
+ const gitVersion = typeof serverInfo?.gitVersion === "string" ? serverInfo.gitVersion : "unknown";
27296
+ return { ok: true, message: `api server ${gitVersion} reachable` };
27297
+ }
27298
+ return {
27299
+ ok: false,
27300
+ message: "api server not reachable",
27301
+ remedy: "Confirm the cluster is up (e.g. `k3d cluster list`) and your context points at it."
27302
+ };
27303
+ }
27304
+ async function probeK8sImagePresence(refs, exec = defaultDockerExec2) {
27305
+ const r = exec("kubectl", ["get", "nodes", "-o", "json", "--request-timeout=2s"]);
27306
+ if (r.status !== 0) {
27307
+ return {
27308
+ ok: false,
27309
+ message: "unable to list cluster nodes for image presence check",
27310
+ remedy: "Confirm the cluster is up and your context points at it; re-run `olam doctor`."
27311
+ };
27312
+ }
27313
+ let parsed;
27314
+ try {
27315
+ parsed = JSON.parse(r.stdout);
27316
+ } catch {
27317
+ return {
27318
+ ok: false,
27319
+ message: "cluster node list returned malformed output",
27320
+ remedy: "Restart your cluster; if persistent, re-create it."
27321
+ };
27322
+ }
27323
+ const nodes = parsed && typeof parsed === "object" ? parsed.items : void 0;
27324
+ if (!Array.isArray(nodes) || nodes.length === 0) {
27325
+ return {
27326
+ ok: false,
27327
+ message: "cluster has no nodes",
27328
+ remedy: "Start the cluster and try again."
27329
+ };
27330
+ }
27331
+ const presentImages = /* @__PURE__ */ new Set();
27332
+ for (const node of nodes) {
27333
+ const status2 = node.status;
27334
+ const images = status2?.images;
27335
+ if (!Array.isArray(images)) continue;
27336
+ for (const img of images) {
27337
+ const names = img.names;
27338
+ if (!Array.isArray(names)) continue;
27339
+ for (const n of names) presentImages.add(n);
27340
+ }
27341
+ }
27342
+ for (const ref of refs) {
27343
+ if (!hasImageRef(presentImages, ref)) {
27344
+ return {
27345
+ ok: false,
27346
+ message: `image ${ref} not present on any cluster node`,
27347
+ remedy: "Run `olam bootstrap` to import the published image into the cluster."
27348
+ };
27349
+ }
27350
+ }
27351
+ return { ok: true, message: `${refs.length} image(s) present on cluster` };
27352
+ }
27353
+ function hasImageRef(present, ref) {
27354
+ if (present.has(ref)) return true;
27355
+ const variants = [`docker.io/library/${ref}`, `docker.io/${ref}`];
27356
+ return variants.some((v) => present.has(v));
27357
+ }
27071
27358
 
27072
27359
  // src/commands/doctor.ts
27073
27360
  init_output();
@@ -27091,27 +27378,56 @@ async function runDoctor(opts, deps = {}) {
27091
27378
  const fetchImpl = deps.fetchImpl;
27092
27379
  const olamHomeOverride = deps.olamHomeOverride;
27093
27380
  const registry = deps.registry ?? DEFAULT_REGISTRY;
27094
- const dockerResult = await probeDockerDaemon(dockerExec);
27095
- const rows = [{ name: "docker daemon", result: dockerResult }];
27096
- if (!dockerResult.ok) {
27097
- return emit({ rows, summary: "FAILED", failureCount: 1 }, opts);
27381
+ const engine = deps.engine ?? resolveEngineFromEnv();
27382
+ const isK8s = engine === "kubernetes";
27383
+ const slot1Result = isK8s ? await probeK8sApiReachable(dockerExec) : await probeDockerDaemon(dockerExec);
27384
+ const slot1Name = isK8s ? "api server" : "docker daemon";
27385
+ const rows = [{ name: slot1Name, result: slot1Result }];
27386
+ if (!slot1Result.ok) {
27387
+ const [engineRes, colimaRes] = await Promise.all([
27388
+ probeEngine(fetchImpl),
27389
+ probeColimaVersion(dockerExec)
27390
+ ]);
27391
+ rows.push(
27392
+ { name: "engine", result: engineRes },
27393
+ { name: "colima version", result: colimaRes }
27394
+ );
27395
+ return emit(makeReport(rows), opts);
27098
27396
  }
27099
27397
  const imageRefs = buildRequiredImageRefs(registry);
27100
- const [imageResult, hostCpResult, authResult, kgResult] = await Promise.all([
27101
- probeImagePresence(imageRefs, dockerExec),
27398
+ const slot2Promise = isK8s ? probeK8sImagePresence(imageRefs, dockerExec) : probeImagePresence(imageRefs, dockerExec);
27399
+ const [imageResult, hostCpResult, authResult, kgResult, engineResult, colimaResult] = await Promise.all([
27400
+ slot2Promise,
27102
27401
  probeHostCpHealth(fetchImpl),
27103
27402
  probeAuthVault(olamHomeOverride),
27104
- probeKgStorage(olamHomeOverride)
27403
+ probeKgStorage(olamHomeOverride),
27404
+ probeEngine(fetchImpl),
27405
+ probeColimaVersion(dockerExec)
27105
27406
  ]);
27106
27407
  rows.push(
27107
27408
  { name: "images", result: imageResult },
27108
27409
  { name: "host-cp /health", result: hostCpResult },
27109
27410
  { name: "auth vault", result: authResult },
27110
- { name: "KG storage", result: kgResult }
27411
+ { name: "KG storage", result: kgResult },
27412
+ { name: "engine", result: engineResult },
27413
+ { name: "colima version", result: colimaResult }
27111
27414
  );
27415
+ return emit(makeReport(rows), opts);
27416
+ }
27417
+ function makeReport(rows) {
27112
27418
  const failureCount = rows.filter((r) => !r.result.ok).length;
27419
+ const warnCount = rows.filter((r) => isWarn(r.result)).length;
27113
27420
  const summary = failureCount === 0 ? "OK" : "FAILED";
27114
- return emit({ rows, summary, failureCount }, opts);
27421
+ return { rows, summary, failureCount, warnCount };
27422
+ }
27423
+ function resolveEngineFromEnv() {
27424
+ const explicit = process.env.OLAM_HOST_CP_ENGINE;
27425
+ if (explicit === "kubernetes") return "kubernetes";
27426
+ if (explicit === "docker") return "docker";
27427
+ return process.env.KUBERNETES_SERVICE_HOST ? "kubernetes" : "docker";
27428
+ }
27429
+ function isWarn(r) {
27430
+ return r.ok === true && r.warn === true;
27115
27431
  }
27116
27432
  function emit(report, opts) {
27117
27433
  if (opts.json) {
@@ -27120,12 +27436,19 @@ function emit(report, opts) {
27120
27436
  {
27121
27437
  summary: report.summary,
27122
27438
  failureCount: report.failureCount,
27123
- probes: report.rows.map((r) => ({
27124
- name: r.name,
27125
- ok: r.result.ok,
27126
- message: r.result.message,
27127
- ...r.result.ok ? {} : { remedy: r.result.remedy }
27128
- }))
27439
+ warnCount: report.warnCount,
27440
+ probes: report.rows.map((r) => {
27441
+ const isFail = !r.result.ok;
27442
+ const warn = isWarn(r.result);
27443
+ const base = {
27444
+ name: r.name,
27445
+ ok: r.result.ok,
27446
+ message: r.result.message
27447
+ };
27448
+ if (warn) base.warn = true;
27449
+ if (isFail || warn) base.remedy = r.result.remedy;
27450
+ return base;
27451
+ })
27129
27452
  },
27130
27453
  null,
27131
27454
  2
@@ -27142,23 +27465,28 @@ function renderHuman(report) {
27142
27465
  const namePad = Math.max(...report.rows.map((r) => r.name.length));
27143
27466
  for (const row of report.rows) {
27144
27467
  const label = row.name.padEnd(namePad);
27145
- if (row.result.ok) {
27146
- printSuccess(`${label} ${row.result.message}`);
27468
+ if (isWarn(row.result)) {
27469
+ printWarning(`[WARN] ${label} ${row.result.message}`);
27470
+ const remedy = row.result.remedy;
27471
+ if (remedy) printWarning(`${"".padEnd(namePad + 7)} remedy: ${remedy}`);
27472
+ } else if (row.result.ok) {
27473
+ printSuccess(`[PASS] ${label} ${row.result.message}`);
27147
27474
  } else {
27148
- printError(`${label} ${row.result.message}`);
27149
- printWarning(`${"".padEnd(namePad)} remedy: ${row.result.remedy}`);
27475
+ printError(`[FAIL] ${label} ${row.result.message}`);
27476
+ printWarning(`${"".padEnd(namePad + 7)} remedy: ${row.result.remedy}`);
27150
27477
  }
27151
27478
  }
27152
27479
  process.stdout.write("\n");
27153
27480
  if (report.summary === "OK") {
27154
- printSuccess(`OK \u2014 all ${report.rows.length} probes green`);
27481
+ const warnNote = report.warnCount > 0 ? ` (${report.warnCount} warn)` : "";
27482
+ printSuccess(`OK \u2014 all ${report.rows.length} probes green${warnNote}`);
27155
27483
  } else {
27156
27484
  printError(`FAILED \u2014 ${report.failureCount} of ${report.rows.length} probes failed`);
27157
27485
  }
27158
27486
  }
27159
27487
  function registerDoctor(program2) {
27160
27488
  program2.command("doctor").description(
27161
- "Pass/fail host-health check. Exits 0 on a healthy host; 1 on any failed probe. Runs Docker daemon / image presence / host-cp /health / auth vault / KG storage probes."
27489
+ "Pass/fail host-health check. Exits 0 on a healthy host (including WARN-only); 1 on any FAIL probe. Runs daemon / image / host-cp /health / auth / KG / engine / colima probes (7 total)."
27162
27490
  ).option("--json", "emit the report as JSON instead of a human-readable table").action(async (opts) => {
27163
27491
  const r = await runDoctor(opts);
27164
27492
  if (r.exitCode !== 0) process.exitCode = r.exitCode;
@@ -27913,11 +28241,11 @@ function registerBegin(program2) {
27913
28241
  }
27914
28242
 
27915
28243
  // src/commands/config.ts
27916
- import * as fs46 from "node:fs";
28244
+ import * as fs51 from "node:fs";
27917
28245
  import { createRequire as createRequire4 } from "node:module";
27918
28246
 
27919
28247
  // ../core/dist/global-config/index.js
27920
- init_schema3();
28248
+ init_schema4();
27921
28249
  init_store2();
27922
28250
 
27923
28251
  // ../core/dist/global-config/repos.js
@@ -27991,88 +28319,714 @@ init_runbooks();
27991
28319
  init_port_validator();
27992
28320
  init_bridge();
27993
28321
 
27994
- // src/commands/config.ts
28322
+ // ../core/dist/skill-sources/index.js
28323
+ init_schema3();
28324
+
28325
+ // ../core/dist/skill-sources/store.js
27995
28326
  init_store2();
27996
- var _require4 = createRequire4(import.meta.url);
27997
- var { parse: parseWithMap } = _require4("json-source-map");
27998
- function registerConfig(program2) {
27999
- const config = program2.command("config").description("Manage global olam configuration");
28000
- config.command("validate [path]").description("Validate ~/.olam/config.json (or a custom path) against the schema").action((filePath) => {
28001
- const resolvedPath = filePath ?? globalConfigPath();
28002
- if (!fs46.existsSync(resolvedPath)) {
28003
- process.stderr.write(`config file not found: ${resolvedPath}
28004
- `);
28005
- process.exit(1);
28006
- }
28007
- let raw;
28008
- try {
28009
- raw = fs46.readFileSync(resolvedPath, "utf-8");
28010
- } catch (err) {
28011
- const msg = err instanceof Error ? err.message : String(err);
28012
- process.stderr.write(`cannot read ${resolvedPath}: ${msg}
28013
- `);
28014
- process.exit(1);
28015
- }
28016
- let parsed;
28017
- let pointers;
28018
- try {
28019
- const result2 = parseWithMap(raw);
28020
- parsed = result2.data;
28021
- pointers = result2.pointers;
28022
- } catch (err) {
28023
- const msg = err instanceof Error ? err.message : String(err);
28024
- process.stderr.write(`${resolvedPath}: invalid JSON \u2014 ${msg}
28025
- `);
28026
- process.exit(1);
28027
- }
28028
- const result = GlobalConfigSchema.safeParse(parsed);
28029
- if (result.success) {
28030
- process.exit(0);
28031
- }
28032
- for (const issue of result.error.issues) {
28033
- const pointer = "/" + issue.path.join("/");
28034
- const entry = pointers[pointer];
28035
- const rawLine = entry?.value?.line ?? entry?.key?.line ?? -1;
28036
- const lineStr = rawLine >= 0 ? `Line ${rawLine + 1}: ` : "";
28037
- process.stderr.write(`${lineStr}${pointer}: ${issue.message}
28038
- `);
28039
- }
28040
- process.exit(1);
28041
- });
28327
+ init_schema3();
28328
+ import * as crypto7 from "node:crypto";
28329
+ function deriveSkillSourceId(gitUrl) {
28330
+ return crypto7.createHash("sha256").update(gitUrl).digest("hex").slice(0, SKILL_SOURCE_ID_LENGTH);
28042
28331
  }
28043
-
28044
- // src/commands/repos.ts
28045
- import pc25 from "picocolors";
28046
- init_output();
28047
- function asMessage(err) {
28048
- return err instanceof Error ? err.message : String(err);
28332
+ function listSkillSources() {
28333
+ return readGlobalConfig().skillSources;
28049
28334
  }
28050
- function registerRepos(program2) {
28051
- const repos = program2.command("repos").description("Manage the global repo registry");
28052
- repos.command("list").description("List all registered repos").action(() => {
28053
- const all = listRepos();
28054
- if (all.length === 0) {
28055
- console.log(pc25.dim("0 repo(s) registered. Add one with: olam repos add --name <n> --path <p>"));
28056
- return;
28057
- }
28058
- printHeader(`${all.length} repo(s)`);
28059
- for (const r of all) {
28060
- const when = new Date(r.addedAt).toISOString().slice(0, 10);
28061
- console.log(
28062
- ` ${pc25.bold(r.name.padEnd(24))} ${r.path.padEnd(48)} ${pc25.dim(when)}`
28063
- );
28064
- }
28335
+ function getSkillSource(id) {
28336
+ return readGlobalConfig().skillSources.find((s) => s.id === id);
28337
+ }
28338
+ function addSkillSource(entry) {
28339
+ const config = readGlobalConfig();
28340
+ const id = deriveSkillSourceId(entry.gitUrl);
28341
+ if (config.skillSources.some((s) => s.id === id)) {
28342
+ throw new Error(`skill-source "${entry.gitUrl}" already registered (id "${id}"). Use "olam skills source list" to inspect.`);
28343
+ }
28344
+ if (config.skillSources.some((s) => s.name === entry.name)) {
28345
+ throw new Error(`skill-source name "${entry.name}" already in use. Pick a different display name.`);
28346
+ }
28347
+ const now = Date.now();
28348
+ const newEntry = {
28349
+ id,
28350
+ name: entry.name,
28351
+ gitUrl: entry.gitUrl,
28352
+ branch: entry.branch ?? "main",
28353
+ addedAt: now
28354
+ };
28355
+ writeGlobalConfig({ ...config, skillSources: [...config.skillSources, newEntry] });
28356
+ return newEntry;
28357
+ }
28358
+ function removeSkillSource(id) {
28359
+ const config = readGlobalConfig();
28360
+ if (!config.skillSources.some((s) => s.id === id)) {
28361
+ throw new Error(`skill-source "${id}" is not registered. Run "olam skills source list" to see registered sources.`);
28362
+ }
28363
+ writeGlobalConfig({
28364
+ ...config,
28365
+ skillSources: config.skillSources.filter((s) => s.id !== id)
28065
28366
  });
28066
- repos.command("add").description("Register a local repo path").requiredOption("--name <name>", "Repo name (lowercase, digits, dash)").requiredOption("--path <path>", "Absolute or ~/... path to the local repo").option("--description <desc>", "Optional human-readable description").option("--default-branch <branch>", "Default branch name (e.g. main)").action((opts) => {
28067
- try {
28068
- const entry = addRepo({
28069
- name: opts.name,
28070
- path: opts.path,
28071
- description: opts.description,
28072
- defaultBranch: opts.defaultBranch
28073
- });
28074
- printSuccess(`registered repo "${entry.name}" at ${entry.path}`);
28075
- } catch (err) {
28367
+ }
28368
+ function updateSkillSource(id, patch) {
28369
+ const config = readGlobalConfig();
28370
+ const idx = config.skillSources.findIndex((s) => s.id === id);
28371
+ if (idx === -1) {
28372
+ throw new Error(`skill-source "${id}" is not registered. Run "olam skills source list" to see registered sources.`);
28373
+ }
28374
+ if (patch.name !== void 0) {
28375
+ const collision = config.skillSources.find((s, i) => i !== idx && s.name === patch.name);
28376
+ if (collision !== void 0) {
28377
+ throw new Error(`skill-source name "${patch.name}" already in use by id "${collision.id}".`);
28378
+ }
28379
+ }
28380
+ const existing = config.skillSources[idx];
28381
+ const updated = {
28382
+ ...existing,
28383
+ ...patch.name !== void 0 ? { name: patch.name } : {},
28384
+ ...patch.branch !== void 0 ? { branch: patch.branch } : {},
28385
+ ...patch.lastPulledSha !== void 0 ? { lastPulledSha: patch.lastPulledSha } : {}
28386
+ };
28387
+ const next = [...config.skillSources];
28388
+ next[idx] = updated;
28389
+ writeGlobalConfig({ ...config, skillSources: next });
28390
+ return updated;
28391
+ }
28392
+
28393
+ // ../core/dist/skill-sources/clone.js
28394
+ import { execFileSync as execFileSync12 } from "node:child_process";
28395
+ import * as fs46 from "node:fs";
28396
+ import * as os27 from "node:os";
28397
+ import * as path51 from "node:path";
28398
+ function skillSourcesRootDir() {
28399
+ const override = process.env["OLAM_SKILL_SOURCES_DIR"];
28400
+ if (override && override.length > 0)
28401
+ return override;
28402
+ return path51.join(os27.homedir(), ".olam", "state", "skill-sources");
28403
+ }
28404
+ function skillSourceClonePath(id) {
28405
+ return path51.join(skillSourcesRootDir(), id);
28406
+ }
28407
+ var SkillSourceGitError = class extends Error {
28408
+ op;
28409
+ gitUrl;
28410
+ constructor(op, gitUrl, cause) {
28411
+ const msg = cause instanceof Error ? cause.message : String(cause);
28412
+ super(`git ${op} failed for "${gitUrl}": ${msg}`);
28413
+ this.op = op;
28414
+ this.gitUrl = gitUrl;
28415
+ this.name = "SkillSourceGitError";
28416
+ this.cause = cause;
28417
+ }
28418
+ };
28419
+ function runGit(args, cwd) {
28420
+ try {
28421
+ return execFileSync12("git", args, {
28422
+ cwd,
28423
+ encoding: "utf-8",
28424
+ stdio: ["ignore", "pipe", "pipe"]
28425
+ });
28426
+ } catch (err) {
28427
+ throw err;
28428
+ }
28429
+ }
28430
+ function cloneSkillSource(opts) {
28431
+ const clonePath = skillSourceClonePath(opts.id);
28432
+ if (fs46.existsSync(clonePath)) {
28433
+ throw new Error(`clone path "${clonePath}" already exists. Remove the existing skill-source first.`);
28434
+ }
28435
+ fs46.mkdirSync(skillSourcesRootDir(), { recursive: true });
28436
+ try {
28437
+ runGit(["clone", "--depth", "1", "--branch", opts.branch, opts.gitUrl, clonePath]);
28438
+ } catch (err) {
28439
+ if (fs46.existsSync(clonePath)) {
28440
+ fs46.rmSync(clonePath, { recursive: true, force: true });
28441
+ }
28442
+ throw new SkillSourceGitError("clone", opts.gitUrl, err);
28443
+ }
28444
+ let headSha;
28445
+ try {
28446
+ headSha = runGit(["rev-parse", "HEAD"], clonePath).trim();
28447
+ } catch (err) {
28448
+ throw new SkillSourceGitError("rev-parse", opts.gitUrl, err);
28449
+ }
28450
+ return { clonePath, headSha };
28451
+ }
28452
+ function pullSkillSource(opts) {
28453
+ const clonePath = skillSourceClonePath(opts.id);
28454
+ if (!fs46.existsSync(clonePath)) {
28455
+ throw new Error(`clone path "${clonePath}" does not exist. Run "olam skills source add" first.`);
28456
+ }
28457
+ try {
28458
+ runGit(["fetch", "origin", opts.branch], clonePath);
28459
+ runGit(["reset", "--hard", `origin/${opts.branch}`], clonePath);
28460
+ } catch (err) {
28461
+ throw new SkillSourceGitError("fetch/reset", opts.gitUrl, err);
28462
+ }
28463
+ try {
28464
+ const headSha = runGit(["rev-parse", "HEAD"], clonePath).trim();
28465
+ return { headSha };
28466
+ } catch (err) {
28467
+ throw new SkillSourceGitError("rev-parse", opts.gitUrl, err);
28468
+ }
28469
+ }
28470
+ function removeSkillSourceClone(id) {
28471
+ const clonePath = skillSourceClonePath(id);
28472
+ if (fs46.existsSync(clonePath)) {
28473
+ fs46.rmSync(clonePath, { recursive: true, force: true });
28474
+ }
28475
+ }
28476
+
28477
+ // ../core/dist/skill-sync/artifact-resolver.js
28478
+ import * as fs47 from "node:fs";
28479
+ import * as path52 from "node:path";
28480
+ function resolveSubscriptions(opts) {
28481
+ const { clonePath, atlasUser } = opts;
28482
+ if (atlasUser) {
28483
+ const subsPath = path52.join(clonePath, "members", atlasUser, "subscriptions.json");
28484
+ if (fs47.existsSync(subsPath)) {
28485
+ try {
28486
+ const parsed = JSON.parse(fs47.readFileSync(subsPath, "utf-8"));
28487
+ if (Array.isArray(parsed?.categories)) {
28488
+ return {
28489
+ categories: parsed.categories.filter((c) => typeof c === "string"),
28490
+ fromSubscriptionsFile: true,
28491
+ atlasUser
28492
+ };
28493
+ }
28494
+ } catch {
28495
+ }
28496
+ }
28497
+ }
28498
+ const catsPath = path52.join(clonePath, "shared", "categories.json");
28499
+ if (fs47.existsSync(catsPath)) {
28500
+ try {
28501
+ const parsed = JSON.parse(fs47.readFileSync(catsPath, "utf-8"));
28502
+ if (Array.isArray(parsed?.categories)) {
28503
+ return {
28504
+ categories: parsed.categories.map((c) => c.id).filter((id) => typeof id === "string"),
28505
+ fromSubscriptionsFile: false,
28506
+ atlasUser
28507
+ };
28508
+ }
28509
+ } catch {
28510
+ }
28511
+ }
28512
+ const sharedDir = path52.join(clonePath, "shared");
28513
+ const cats = [];
28514
+ if (fs47.existsSync(sharedDir)) {
28515
+ for (const name of fs47.readdirSync(sharedDir)) {
28516
+ const dir = path52.join(sharedDir, name);
28517
+ if (!fs47.statSync(dir).isDirectory())
28518
+ continue;
28519
+ if (fs47.existsSync(path52.join(dir, "skills")) || fs47.existsSync(path52.join(dir, "agents"))) {
28520
+ cats.push(name);
28521
+ }
28522
+ }
28523
+ }
28524
+ return { categories: cats, fromSubscriptionsFile: false, atlasUser };
28525
+ }
28526
+ function listDirSafe(dir) {
28527
+ if (!fs47.existsSync(dir))
28528
+ return [];
28529
+ return fs47.readdirSync(dir);
28530
+ }
28531
+ function resolveSkillsDir(opts) {
28532
+ const { sourceId, baseDir } = opts;
28533
+ const out = [];
28534
+ for (const name of listDirSafe(baseDir)) {
28535
+ const subdir = path52.join(baseDir, name);
28536
+ if (!fs47.statSync(subdir).isDirectory())
28537
+ continue;
28538
+ if (!fs47.existsSync(path52.join(subdir, "SKILL.md")))
28539
+ continue;
28540
+ out.push({ kind: "skill", sourceId, sourcePath: subdir, deployBasename: name });
28541
+ const subagentsDir = path52.join(subdir, "references", "agents");
28542
+ if (fs47.existsSync(subagentsDir) && fs47.statSync(subagentsDir).isDirectory()) {
28543
+ for (const f of listDirSafe(subagentsDir)) {
28544
+ if (!f.endsWith(".md"))
28545
+ continue;
28546
+ out.push({
28547
+ kind: "subagent",
28548
+ sourceId,
28549
+ sourcePath: path52.join(subagentsDir, f),
28550
+ deployBasename: f,
28551
+ parentSkill: name
28552
+ });
28553
+ }
28554
+ }
28555
+ }
28556
+ return out;
28557
+ }
28558
+ function resolveAgentsDir(opts) {
28559
+ const { sourceId, baseDir } = opts;
28560
+ const out = [];
28561
+ for (const name of listDirSafe(baseDir)) {
28562
+ const full = path52.join(baseDir, name);
28563
+ const stat = fs47.statSync(full);
28564
+ if (stat.isFile() && name.endsWith(".md")) {
28565
+ out.push({ kind: "agent", sourceId, sourcePath: full, deployBasename: name });
28566
+ } else if (stat.isDirectory()) {
28567
+ for (const f of listDirSafe(full)) {
28568
+ if (!f.endsWith(".md"))
28569
+ continue;
28570
+ out.push({
28571
+ kind: "agent",
28572
+ sourceId,
28573
+ sourcePath: path52.join(full, f),
28574
+ deployBasename: `${name}-${f}`
28575
+ });
28576
+ }
28577
+ }
28578
+ }
28579
+ return out;
28580
+ }
28581
+ function resolveScriptsDir(opts) {
28582
+ const { sourceId, baseDir } = opts;
28583
+ const out = [];
28584
+ for (const name of listDirSafe(baseDir)) {
28585
+ const full = path52.join(baseDir, name);
28586
+ const stat = fs47.statSync(full);
28587
+ if (stat.isFile() && name.endsWith(".sh")) {
28588
+ out.push({ kind: "script", sourceId, sourcePath: full, deployBasename: name });
28589
+ } else if (stat.isDirectory()) {
28590
+ out.push({ kind: "script", sourceId, sourcePath: full, deployBasename: name });
28591
+ }
28592
+ }
28593
+ return out;
28594
+ }
28595
+ function resolveRulesDir(opts) {
28596
+ const { sourceId, baseDir } = opts;
28597
+ const out = [];
28598
+ for (const name of listDirSafe(baseDir)) {
28599
+ if (!name.endsWith(".md"))
28600
+ continue;
28601
+ const full = path52.join(baseDir, name);
28602
+ if (!fs47.statSync(full).isFile())
28603
+ continue;
28604
+ out.push({ kind: "rule", sourceId, sourcePath: full, deployBasename: name });
28605
+ }
28606
+ return out;
28607
+ }
28608
+ function resolveJsonDir(opts) {
28609
+ const { sourceId, baseDir, kind } = opts;
28610
+ const out = [];
28611
+ for (const name of listDirSafe(baseDir)) {
28612
+ if (!name.endsWith(".json"))
28613
+ continue;
28614
+ const full = path52.join(baseDir, name);
28615
+ if (!fs47.statSync(full).isFile())
28616
+ continue;
28617
+ out.push({ kind, sourceId, sourcePath: full, deployBasename: name });
28618
+ }
28619
+ return out;
28620
+ }
28621
+ function resolveSourceArtifacts(opts) {
28622
+ const { sourceId, clonePath, atlasUser } = opts;
28623
+ const subscription = resolveSubscriptions({ clonePath, atlasUser });
28624
+ const artifacts = [];
28625
+ for (const cat of subscription.categories) {
28626
+ const catDir = path52.join(clonePath, "shared", cat);
28627
+ if (!fs47.existsSync(catDir))
28628
+ continue;
28629
+ artifacts.push(...resolveSkillsDir({ sourceId, baseDir: path52.join(catDir, "skills") }));
28630
+ artifacts.push(...resolveAgentsDir({ sourceId, baseDir: path52.join(catDir, "agents") }));
28631
+ artifacts.push(...resolveScriptsDir({ sourceId, baseDir: path52.join(catDir, "scripts") }));
28632
+ artifacts.push(...resolveRulesDir({ sourceId, baseDir: path52.join(catDir, "rules") }));
28633
+ artifacts.push(...resolveJsonDir({ sourceId, baseDir: path52.join(catDir, "hooks"), kind: "hook" }));
28634
+ artifacts.push(...resolveJsonDir({ sourceId, baseDir: path52.join(catDir, "permissions"), kind: "permission" }));
28635
+ }
28636
+ if (atlasUser) {
28637
+ const memberRoot = path52.join(clonePath, "members", atlasUser);
28638
+ if (fs47.existsSync(memberRoot)) {
28639
+ artifacts.push(...resolveSkillsDir({ sourceId, baseDir: path52.join(memberRoot, "skills") }));
28640
+ artifacts.push(...resolveAgentsDir({ sourceId, baseDir: path52.join(memberRoot, "agents") }));
28641
+ artifacts.push(...resolveScriptsDir({ sourceId, baseDir: path52.join(memberRoot, "scripts") }));
28642
+ artifacts.push(...resolveRulesDir({ sourceId, baseDir: path52.join(memberRoot, "rules") }));
28643
+ artifacts.push(...resolveJsonDir({ sourceId, baseDir: path52.join(memberRoot, "hooks"), kind: "hook" }));
28644
+ artifacts.push(...resolveJsonDir({ sourceId, baseDir: path52.join(memberRoot, "permissions"), kind: "permission" }));
28645
+ }
28646
+ }
28647
+ return { subscription, artifacts };
28648
+ }
28649
+
28650
+ // ../core/dist/skill-sync/symlink-deployer.js
28651
+ import * as fs48 from "node:fs";
28652
+ import * as os28 from "node:os";
28653
+ import * as path53 from "node:path";
28654
+ function claudeDir() {
28655
+ const override = process.env["OLAM_CLAUDE_DIR"];
28656
+ if (override && override.length > 0)
28657
+ return override;
28658
+ return path53.join(os28.homedir(), ".claude");
28659
+ }
28660
+ var BUCKETS = ["commands", "agents", "skills", "scripts", "rules"];
28661
+ function bucketFor(kind) {
28662
+ switch (kind) {
28663
+ case "skill":
28664
+ return "skills";
28665
+ case "agent":
28666
+ return "agents";
28667
+ case "subagent":
28668
+ return "agents";
28669
+ case "script":
28670
+ return "scripts";
28671
+ case "rule":
28672
+ return "rules";
28673
+ case "hook":
28674
+ case "permission":
28675
+ return void 0;
28676
+ }
28677
+ }
28678
+ function cleanManagedSymlinks(claude) {
28679
+ for (const bucket of BUCKETS) {
28680
+ const dir = path53.join(claude, bucket);
28681
+ if (!fs48.existsSync(dir))
28682
+ continue;
28683
+ for (const name of fs48.readdirSync(dir)) {
28684
+ const p = path53.join(dir, name);
28685
+ try {
28686
+ const stat = fs48.lstatSync(p);
28687
+ if (stat.isSymbolicLink()) {
28688
+ fs48.unlinkSync(p);
28689
+ }
28690
+ } catch {
28691
+ }
28692
+ }
28693
+ }
28694
+ }
28695
+ function shadowBackup(link) {
28696
+ const epoch = Math.floor(Date.now() / 1e3);
28697
+ const backup2 = `${link}.shadow-backup-${epoch}`;
28698
+ fs48.renameSync(link, backup2);
28699
+ return backup2;
28700
+ }
28701
+ function linkIfNeeded(target, link) {
28702
+ try {
28703
+ const existing = fs48.readlinkSync(link);
28704
+ if (existing === target)
28705
+ return { created: false };
28706
+ } catch {
28707
+ }
28708
+ let isLink = false;
28709
+ try {
28710
+ isLink = fs48.lstatSync(link).isSymbolicLink();
28711
+ } catch {
28712
+ }
28713
+ let backup2;
28714
+ if (isLink) {
28715
+ fs48.unlinkSync(link);
28716
+ } else if (fs48.existsSync(link)) {
28717
+ backup2 = shadowBackup(link);
28718
+ }
28719
+ fs48.symlinkSync(target, link);
28720
+ return { created: true, shadowBackup: backup2 };
28721
+ }
28722
+ function deployArtifacts(artifacts) {
28723
+ const claude = claudeDir();
28724
+ for (const bucket of BUCKETS) {
28725
+ fs48.mkdirSync(path53.join(claude, bucket), { recursive: true });
28726
+ }
28727
+ cleanManagedSymlinks(claude);
28728
+ const result = { linked: 0, shadowBackups: [], subagentCollisions: [] };
28729
+ const seenAgentBasenames = /* @__PURE__ */ new Map();
28730
+ const collisionLosers = /* @__PURE__ */ new Map();
28731
+ for (const artifact of artifacts) {
28732
+ const bucket = bucketFor(artifact.kind);
28733
+ if (!bucket)
28734
+ continue;
28735
+ if (artifact.kind === "agent" || artifact.kind === "subagent") {
28736
+ const prior = seenAgentBasenames.get(artifact.deployBasename);
28737
+ if (prior) {
28738
+ const losers = collisionLosers.get(artifact.deployBasename) ?? [];
28739
+ losers.push(artifact.sourcePath);
28740
+ collisionLosers.set(artifact.deployBasename, losers);
28741
+ continue;
28742
+ }
28743
+ seenAgentBasenames.set(artifact.deployBasename, artifact.sourcePath);
28744
+ }
28745
+ const linkPath = path53.join(claude, bucket, artifact.deployBasename);
28746
+ const { created, shadowBackup: backup2 } = linkIfNeeded(artifact.sourcePath, linkPath);
28747
+ if (created)
28748
+ result.linked += 1;
28749
+ if (backup2)
28750
+ result.shadowBackups.push(backup2);
28751
+ }
28752
+ for (const [name, losers] of collisionLosers.entries()) {
28753
+ const winner = seenAgentBasenames.get(name);
28754
+ result.subagentCollisions.push({ name, winner, losers });
28755
+ }
28756
+ return result;
28757
+ }
28758
+
28759
+ // ../core/dist/skill-sync/settings-merger.js
28760
+ import * as fs49 from "node:fs";
28761
+ import * as os29 from "node:os";
28762
+ import * as path54 from "node:path";
28763
+ var OLAM_SKILLS_MARKER = "_olamSkillsManaged";
28764
+ var BACKUP_RETENTION = 30;
28765
+ function claudeSettingsPath() {
28766
+ const override = process.env["OLAM_CLAUDE_SETTINGS_PATH"];
28767
+ if (override && override.length > 0)
28768
+ return override;
28769
+ return path54.join(claudeDirInternal(), "settings.json");
28770
+ }
28771
+ function claudeDirInternal() {
28772
+ const override = process.env["OLAM_CLAUDE_DIR"];
28773
+ if (override && override.length > 0)
28774
+ return override;
28775
+ return path54.join(os29.homedir(), ".claude");
28776
+ }
28777
+ function settingsBackupDir() {
28778
+ const override = process.env["OLAM_SETTINGS_BACKUP_DIR"];
28779
+ if (override && override.length > 0)
28780
+ return override;
28781
+ return path54.join(os29.homedir(), ".olam", "state", "settings-backups");
28782
+ }
28783
+ function dedupeByMatcher(entries) {
28784
+ const map = /* @__PURE__ */ new Map();
28785
+ for (const e of entries) {
28786
+ map.set(e.matcher ?? "", e);
28787
+ }
28788
+ return [...map.values()];
28789
+ }
28790
+ function tagOlam(entry) {
28791
+ return { ...entry, [OLAM_SKILLS_MARKER]: true };
28792
+ }
28793
+ function readJson(file) {
28794
+ return JSON.parse(fs49.readFileSync(file, "utf-8"));
28795
+ }
28796
+ function rotateBackups(backupDir) {
28797
+ if (!fs49.existsSync(backupDir))
28798
+ return;
28799
+ const files = fs49.readdirSync(backupDir).filter((f) => f.endsWith(".json")).map((f) => ({ name: f, full: path54.join(backupDir, f), mtime: fs49.statSync(path54.join(backupDir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
28800
+ for (const f of files.slice(BACKUP_RETENTION)) {
28801
+ try {
28802
+ fs49.unlinkSync(f.full);
28803
+ } catch {
28804
+ }
28805
+ }
28806
+ }
28807
+ function backupSettings() {
28808
+ const src = claudeSettingsPath();
28809
+ if (!fs49.existsSync(src))
28810
+ return void 0;
28811
+ const dir = settingsBackupDir();
28812
+ fs49.mkdirSync(dir, { recursive: true });
28813
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
28814
+ const dest = path54.join(dir, `settings-${stamp}.json`);
28815
+ fs49.copyFileSync(src, dest);
28816
+ rotateBackups(dir);
28817
+ return dest;
28818
+ }
28819
+ function mergeSettings(input) {
28820
+ const settingsPath = claudeSettingsPath();
28821
+ const backupPath = backupSettings();
28822
+ let base = {};
28823
+ if (fs49.existsSync(settingsPath)) {
28824
+ try {
28825
+ base = readJson(settingsPath);
28826
+ } catch {
28827
+ base = {};
28828
+ }
28829
+ }
28830
+ const olamHooksByCategory = /* @__PURE__ */ new Map();
28831
+ for (const file of input.hookFiles) {
28832
+ let parsed;
28833
+ try {
28834
+ parsed = readJson(file);
28835
+ } catch {
28836
+ continue;
28837
+ }
28838
+ if (!parsed.hooks || typeof parsed.hooks !== "object")
28839
+ continue;
28840
+ for (const [category, entries] of Object.entries(parsed.hooks)) {
28841
+ if (!Array.isArray(entries))
28842
+ continue;
28843
+ const list = olamHooksByCategory.get(category) ?? [];
28844
+ list.push(...entries.map(tagOlam));
28845
+ olamHooksByCategory.set(category, list);
28846
+ }
28847
+ }
28848
+ const mergedHooks = {};
28849
+ const existingHooks = base.hooks && typeof base.hooks === "object" ? base.hooks : {};
28850
+ const categories = /* @__PURE__ */ new Set([
28851
+ ...Object.keys(existingHooks),
28852
+ ...olamHooksByCategory.keys()
28853
+ ]);
28854
+ let hooksAdded = 0;
28855
+ for (const cat of categories) {
28856
+ const existing = Array.isArray(existingHooks[cat]) ? existingHooks[cat] : [];
28857
+ const preserved = existing.filter((e) => !e[OLAM_SKILLS_MARKER]);
28858
+ const olam = olamHooksByCategory.get(cat) ?? [];
28859
+ const combined = [...preserved, ...olam];
28860
+ mergedHooks[cat] = dedupeByMatcher(combined);
28861
+ hooksAdded += olam.length;
28862
+ }
28863
+ const permSet = /* @__PURE__ */ new Set();
28864
+ for (const file of input.permissionFiles) {
28865
+ let parsed;
28866
+ try {
28867
+ parsed = readJson(file);
28868
+ } catch {
28869
+ continue;
28870
+ }
28871
+ const allow = parsed.permissions?.allow;
28872
+ if (Array.isArray(allow)) {
28873
+ for (const p of allow)
28874
+ if (typeof p === "string")
28875
+ permSet.add(p);
28876
+ }
28877
+ }
28878
+ const next = {
28879
+ ...base,
28880
+ hooks: mergedHooks,
28881
+ permissions: {
28882
+ ...base.permissions ?? {},
28883
+ ...input.permissionFiles.length > 0 ? { allow: [...permSet] } : {}
28884
+ }
28885
+ };
28886
+ fs49.mkdirSync(path54.dirname(settingsPath), { recursive: true });
28887
+ const tmp = `${settingsPath}.tmp-${process.pid}`;
28888
+ fs49.writeFileSync(tmp, JSON.stringify(next, null, 2) + "\n", { mode: 420 });
28889
+ fs49.renameSync(tmp, settingsPath);
28890
+ return { backupPath, hooksAdded, permissionsCount: permSet.size };
28891
+ }
28892
+
28893
+ // ../core/dist/skill-sync/engine.js
28894
+ import * as fs50 from "node:fs";
28895
+ import * as os30 from "node:os";
28896
+ import * as path55 from "node:path";
28897
+ function resolveAtlasUser(override) {
28898
+ if (override)
28899
+ return override;
28900
+ const claudeDir2 = process.env["OLAM_CLAUDE_DIR"] || path55.join(os30.homedir(), ".claude");
28901
+ const f = path55.join(claudeDir2, ".atlas-user");
28902
+ if (fs50.existsSync(f)) {
28903
+ return fs50.readFileSync(f, "utf-8").trim() || void 0;
28904
+ }
28905
+ return void 0;
28906
+ }
28907
+ async function syncSkills(opts = {}) {
28908
+ const atlasUser = resolveAtlasUser(opts.atlasUser);
28909
+ const sources = listSkillSources();
28910
+ const allArtifacts = [];
28911
+ const perSource = [];
28912
+ for (const source of sources) {
28913
+ const clonePath = skillSourceClonePath(source.id);
28914
+ if (!fs50.existsSync(clonePath))
28915
+ continue;
28916
+ const { artifacts, subscription } = resolveSourceArtifacts({
28917
+ sourceId: source.id,
28918
+ clonePath,
28919
+ atlasUser
28920
+ });
28921
+ allArtifacts.push(...artifacts);
28922
+ perSource.push({
28923
+ sourceId: source.id,
28924
+ name: source.name,
28925
+ artifactCount: artifacts.length,
28926
+ categories: subscription.categories,
28927
+ fromSubscriptionsFile: subscription.fromSubscriptionsFile
28928
+ });
28929
+ }
28930
+ const hookFiles = allArtifacts.filter((a) => a.kind === "hook").map((a) => a.sourcePath);
28931
+ const permissionFiles = allArtifacts.filter((a) => a.kind === "permission").map((a) => a.sourcePath);
28932
+ const summary = {
28933
+ sourceCount: sources.length,
28934
+ artifactCount: allArtifacts.length,
28935
+ hookFileCount: hookFiles.length,
28936
+ permissionFileCount: permissionFiles.length,
28937
+ deploy: void 0,
28938
+ merge: void 0,
28939
+ perSource
28940
+ };
28941
+ if (opts.dryRun)
28942
+ return summary;
28943
+ summary.deploy = deployArtifacts(allArtifacts);
28944
+ summary.merge = mergeSettings({ hookFiles, permissionFiles });
28945
+ return summary;
28946
+ }
28947
+
28948
+ // src/commands/config.ts
28949
+ init_store2();
28950
+ var _require4 = createRequire4(import.meta.url);
28951
+ var { parse: parseWithMap } = _require4("json-source-map");
28952
+ function registerConfig(program2) {
28953
+ const config = program2.command("config").description("Manage global olam configuration");
28954
+ config.command("validate [path]").description("Validate ~/.olam/config.json (or a custom path) against the schema").action((filePath) => {
28955
+ const resolvedPath = filePath ?? globalConfigPath();
28956
+ if (!fs51.existsSync(resolvedPath)) {
28957
+ process.stderr.write(`config file not found: ${resolvedPath}
28958
+ `);
28959
+ process.exit(1);
28960
+ }
28961
+ let raw;
28962
+ try {
28963
+ raw = fs51.readFileSync(resolvedPath, "utf-8");
28964
+ } catch (err) {
28965
+ const msg = err instanceof Error ? err.message : String(err);
28966
+ process.stderr.write(`cannot read ${resolvedPath}: ${msg}
28967
+ `);
28968
+ process.exit(1);
28969
+ }
28970
+ let parsed;
28971
+ let pointers;
28972
+ try {
28973
+ const result2 = parseWithMap(raw);
28974
+ parsed = result2.data;
28975
+ pointers = result2.pointers;
28976
+ } catch (err) {
28977
+ const msg = err instanceof Error ? err.message : String(err);
28978
+ process.stderr.write(`${resolvedPath}: invalid JSON \u2014 ${msg}
28979
+ `);
28980
+ process.exit(1);
28981
+ }
28982
+ const result = GlobalConfigSchema.safeParse(parsed);
28983
+ if (result.success) {
28984
+ process.exit(0);
28985
+ }
28986
+ for (const issue of result.error.issues) {
28987
+ const pointer = "/" + issue.path.join("/");
28988
+ const entry = pointers[pointer];
28989
+ const rawLine = entry?.value?.line ?? entry?.key?.line ?? -1;
28990
+ const lineStr = rawLine >= 0 ? `Line ${rawLine + 1}: ` : "";
28991
+ process.stderr.write(`${lineStr}${pointer}: ${issue.message}
28992
+ `);
28993
+ }
28994
+ process.exit(1);
28995
+ });
28996
+ }
28997
+
28998
+ // src/commands/repos.ts
28999
+ import pc25 from "picocolors";
29000
+ init_output();
29001
+ function asMessage(err) {
29002
+ return err instanceof Error ? err.message : String(err);
29003
+ }
29004
+ function registerRepos(program2) {
29005
+ const repos = program2.command("repos").description("Manage the global repo registry");
29006
+ repos.command("list").description("List all registered repos").action(() => {
29007
+ const all = listRepos();
29008
+ if (all.length === 0) {
29009
+ console.log(pc25.dim("0 repo(s) registered. Add one with: olam repos add --name <n> --path <p>"));
29010
+ return;
29011
+ }
29012
+ printHeader(`${all.length} repo(s)`);
29013
+ for (const r of all) {
29014
+ const when = new Date(r.addedAt).toISOString().slice(0, 10);
29015
+ console.log(
29016
+ ` ${pc25.bold(r.name.padEnd(24))} ${r.path.padEnd(48)} ${pc25.dim(when)}`
29017
+ );
29018
+ }
29019
+ });
29020
+ repos.command("add").description("Register a local repo path").requiredOption("--name <name>", "Repo name (lowercase, digits, dash)").requiredOption("--path <path>", "Absolute or ~/... path to the local repo").option("--description <desc>", "Optional human-readable description").option("--default-branch <branch>", "Default branch name (e.g. main)").action((opts) => {
29021
+ try {
29022
+ const entry = addRepo({
29023
+ name: opts.name,
29024
+ path: opts.path,
29025
+ description: opts.description,
29026
+ defaultBranch: opts.defaultBranch
29027
+ });
29028
+ printSuccess(`registered repo "${entry.name}" at ${entry.path}`);
29029
+ } catch (err) {
28076
29030
  printError(asMessage(err));
28077
29031
  process.exitCode = 1;
28078
29032
  }
@@ -28183,74 +29137,309 @@ function registerRunbooks(program2) {
28183
29137
  removeRunbook(name);
28184
29138
  printSuccess(`removed runbook "${name}"`);
28185
29139
  } catch (err) {
28186
- printError(asMessage2(err));
29140
+ printError(asMessage2(err));
29141
+ process.exitCode = 1;
29142
+ }
29143
+ });
29144
+ runbooks.command("apply").description("Create a world from a runbook (delegates to olam create)").argument("<name>", "Runbook name").option("--name <world>", "World name override").option("--task <task>", "Initial task to dispatch").action((name, opts) => {
29145
+ try {
29146
+ const rb = getRunbook(name);
29147
+ const { conflicts } = validateRunbookPorts(rb);
29148
+ if (conflicts.length > 0) {
29149
+ printError(`port conflicts detected for runbook "${name}":`);
29150
+ for (const c of conflicts) {
29151
+ console.error(formatConflict(c));
29152
+ }
29153
+ console.error(
29154
+ `
29155
+ ${conflicts.length} port conflict(s). Stop the conflicting processes or update portMap in runbook "${name}".`
29156
+ );
29157
+ process.exitCode = 1;
29158
+ return;
29159
+ }
29160
+ console.log(
29161
+ pc26.dim(
29162
+ `olam runbooks apply: port validation passed for "${name}" (repos: ${rb.repos.join(", ")}).`
29163
+ )
29164
+ );
29165
+ console.log(
29166
+ pc26.dim(
29167
+ `Hint: use "olam create --repos ${rb.repos.join(" ")}${opts.task ? ` --task "${opts.task}"` : ""}${opts.name ? ` --name ${opts.name}` : ""}" to create the world.`
29168
+ )
29169
+ );
29170
+ console.log(
29171
+ pc26.yellow('Phase D will wire --runbook directly. For now, use "olam create --repos ..." above.')
29172
+ );
29173
+ } catch (err) {
29174
+ printError(asMessage2(err));
29175
+ process.exitCode = 1;
29176
+ }
29177
+ });
29178
+ runbooks.command("check-ports").description("Check if runbook ports are available").argument("<name>", "Runbook name").action((name) => {
29179
+ try {
29180
+ const rb = getRunbook(name);
29181
+ const { conflicts } = validateRunbookPorts(rb);
29182
+ if (!rb.portMap || Object.keys(rb.portMap).length === 0) {
29183
+ console.log(pc26.dim(`runbook "${name}" declares no ports.`));
29184
+ return;
29185
+ }
29186
+ for (const [repoName, svcMap] of Object.entries(rb.portMap ?? {})) {
29187
+ for (const [svcName, port2] of Object.entries(svcMap)) {
29188
+ const conflict = conflicts.find((c) => c.port === port2);
29189
+ if (conflict) {
29190
+ console.log(formatConflict(conflict));
29191
+ } else {
29192
+ console.log(` ${pc26.green("\u2713")} ${repoName}.${svcName}:${port2} \u2014 free`);
29193
+ }
29194
+ }
29195
+ }
29196
+ if (conflicts.length > 0) {
29197
+ console.error(
29198
+ `
29199
+ ${conflicts.length} port conflict(s). Stop the conflicting processes or update portMap in runbook "${name}".`
29200
+ );
29201
+ process.exitCode = 1;
29202
+ } else {
29203
+ console.log(pc26.green("\n\u2713 all ports free"));
29204
+ }
29205
+ } catch (err) {
29206
+ printError(asMessage2(err));
29207
+ process.exitCode = 1;
29208
+ }
29209
+ });
29210
+ }
29211
+
29212
+ // src/commands/skills-source.ts
29213
+ import pc27 from "picocolors";
29214
+ init_output();
29215
+ function asMessage3(err) {
29216
+ return err instanceof Error ? err.message : String(err);
29217
+ }
29218
+ function registerSkillsSource(program2) {
29219
+ const skills = program2.command("skills").description("Manage skill sources and synchronization");
29220
+ const source = skills.command("source").description("Manage registered skill sources");
29221
+ source.command("list").description("List all registered skill sources").action(() => {
29222
+ const all = listSkillSources();
29223
+ if (all.length === 0) {
29224
+ console.log(
29225
+ pc27.dim("0 skill source(s) registered. Add one with: olam skills source add --name <n> --git-url <url>")
29226
+ );
29227
+ return;
29228
+ }
29229
+ printHeader(`${all.length} skill source(s)`);
29230
+ for (const s of all) {
29231
+ const when = new Date(s.addedAt).toISOString().slice(0, 10);
29232
+ const sha = s.lastPulledSha ? s.lastPulledSha.slice(0, 8) : pc27.dim("(unpulled)");
29233
+ console.log(
29234
+ ` ${pc27.bold(s.id.padEnd(14))} ${s.name.padEnd(24)} ${s.branch.padEnd(12)} ${sha.padEnd(12)} ${pc27.dim(when)} ${pc27.dim(s.gitUrl)}`
29235
+ );
29236
+ }
29237
+ });
29238
+ source.command("add").description("Register and clone a skill source").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").action((opts) => {
29239
+ let entry;
29240
+ try {
29241
+ entry = addSkillSource({ name: opts.name, gitUrl: opts.gitUrl, branch: opts.branch });
29242
+ } catch (err) {
29243
+ printError(asMessage3(err));
29244
+ process.exitCode = 1;
29245
+ return;
29246
+ }
29247
+ try {
29248
+ const result = cloneSkillSource({
29249
+ gitUrl: entry.gitUrl,
29250
+ branch: entry.branch,
29251
+ id: entry.id
29252
+ });
29253
+ updateSkillSource(entry.id, { lastPulledSha: result.headSha });
29254
+ printSuccess(
29255
+ `registered skill source "${entry.name}" (${entry.id}) at ${result.clonePath} @ ${result.headSha.slice(0, 8)}`
29256
+ );
29257
+ } catch (err) {
29258
+ try {
29259
+ removeSkillSource(entry.id);
29260
+ } catch {
29261
+ }
29262
+ printError(asMessage3(err));
29263
+ process.exitCode = 1;
29264
+ }
29265
+ });
29266
+ source.command("remove").description("Remove a registered skill source (deletes its clone)").argument("<id>", 'Skill source id (from "olam skills source list")').action((id) => {
29267
+ try {
29268
+ removeSkillSourceClone(id);
29269
+ removeSkillSource(id);
29270
+ printSuccess(`removed skill source "${id}"`);
29271
+ } catch (err) {
29272
+ printError(asMessage3(err));
29273
+ process.exitCode = 1;
29274
+ }
29275
+ });
29276
+ source.command("pull").description("Fetch + reset the clone to upstream HEAD").argument("<id>", "Skill source id").action((id) => {
29277
+ try {
29278
+ const entry = getSkillSource(id);
29279
+ if (!entry) {
29280
+ printError(`skill source "${id}" is not registered`);
29281
+ process.exitCode = 1;
29282
+ return;
29283
+ }
29284
+ const result = pullSkillSource({
29285
+ gitUrl: entry.gitUrl,
29286
+ branch: entry.branch,
29287
+ id: entry.id
29288
+ });
29289
+ updateSkillSource(entry.id, { lastPulledSha: result.headSha });
29290
+ printSuccess(`pulled "${entry.name}" \u2192 ${result.headSha.slice(0, 8)}`);
29291
+ } catch (err) {
29292
+ printError(asMessage3(err));
29293
+ process.exitCode = 1;
29294
+ }
29295
+ });
29296
+ source.command("show").description("Show details for a single skill source").argument("<id>", "Skill source id").action((id) => {
29297
+ const entry = getSkillSource(id);
29298
+ if (!entry) {
29299
+ printError(`skill source "${id}" is not registered`);
29300
+ process.exitCode = 1;
29301
+ return;
29302
+ }
29303
+ const when = new Date(entry.addedAt).toISOString();
29304
+ console.log(`${pc27.bold("id:")} ${entry.id}`);
29305
+ console.log(`${pc27.bold("name:")} ${entry.name}`);
29306
+ console.log(`${pc27.bold("gitUrl:")} ${entry.gitUrl}`);
29307
+ console.log(`${pc27.bold("branch:")} ${entry.branch}`);
29308
+ console.log(`${pc27.bold("addedAt:")} ${when}`);
29309
+ console.log(`${pc27.bold("lastPulledSha:")} ${entry.lastPulledSha ?? pc27.dim("(unpulled)")}`);
29310
+ console.log(`${pc27.bold("clonePath:")} ${skillSourceClonePath(entry.id)}`);
29311
+ });
29312
+ }
29313
+
29314
+ // src/commands/skills.ts
29315
+ import * as fs52 from "node:fs";
29316
+ import * as path56 from "node:path";
29317
+ import pc28 from "picocolors";
29318
+ init_output();
29319
+ function asMessage4(err) {
29320
+ return err instanceof Error ? err.message : String(err);
29321
+ }
29322
+ function listDeployed() {
29323
+ const dir = claudeDir();
29324
+ const sources = listSkillSources();
29325
+ const sourcePaths = new Map(
29326
+ sources.map((s) => [skillSourceClonePath(s.id), s.id])
29327
+ );
29328
+ const entries = [];
29329
+ for (const bucket of ["skills", "agents", "scripts", "rules", "commands"]) {
29330
+ const bucketDir = path56.join(dir, bucket);
29331
+ if (!fs52.existsSync(bucketDir)) continue;
29332
+ for (const name of fs52.readdirSync(bucketDir)) {
29333
+ const full = path56.join(bucketDir, name);
29334
+ try {
29335
+ const stat = fs52.lstatSync(full);
29336
+ if (!stat.isSymbolicLink()) continue;
29337
+ const target = fs52.readlinkSync(full);
29338
+ let sourceId;
29339
+ for (const [clonePath, id] of sourcePaths.entries()) {
29340
+ if (target.startsWith(clonePath)) {
29341
+ sourceId = id;
29342
+ break;
29343
+ }
29344
+ }
29345
+ entries.push({ bucket, name, target, sourceId });
29346
+ } catch {
29347
+ }
29348
+ }
29349
+ }
29350
+ return entries;
29351
+ }
29352
+ function printSyncSummary(summary, dryRun) {
29353
+ printHeader(dryRun ? "dry-run summary" : "sync summary");
29354
+ console.log(` sources: ${summary.sourceCount}`);
29355
+ console.log(` artifacts: ${summary.artifactCount}`);
29356
+ console.log(` hook files: ${summary.hookFileCount}`);
29357
+ console.log(` permission files:${summary.permissionFileCount}`);
29358
+ if (summary.deploy) {
29359
+ console.log(` symlinks made: ${summary.deploy.linked}`);
29360
+ if (summary.deploy.shadowBackups.length > 0) {
29361
+ console.log(` ${pc28.yellow("shadow backups:")} ${summary.deploy.shadowBackups.length}`);
29362
+ for (const p of summary.deploy.shadowBackups) console.log(` ${pc28.dim(p)}`);
29363
+ }
29364
+ if (summary.deploy.subagentCollisions.length > 0) {
29365
+ console.log(` ${pc28.yellow("agent name collisions:")} ${summary.deploy.subagentCollisions.length}`);
29366
+ for (const c of summary.deploy.subagentCollisions) {
29367
+ console.log(` ${pc28.bold(c.name)} \u2192 winner: ${pc28.dim(c.winner)}`);
29368
+ for (const l of c.losers) console.log(` ${pc28.dim("(skipped)")} ${l}`);
29369
+ }
29370
+ }
29371
+ }
29372
+ if (summary.merge) {
29373
+ console.log(` hooks added: ${summary.merge.hooksAdded}`);
29374
+ console.log(` permissions: ${summary.merge.permissionsCount}`);
29375
+ if (summary.merge.backupPath) {
29376
+ console.log(` settings backup: ${pc28.dim(summary.merge.backupPath)}`);
29377
+ }
29378
+ }
29379
+ console.log();
29380
+ for (const s of summary.perSource) {
29381
+ const sub = s.fromSubscriptionsFile ? "(subscribed)" : "(all categories)";
29382
+ console.log(` ${pc28.bold(s.name.padEnd(24))} ${s.artifactCount} artifacts \xB7 ${s.categories.join(", ") || "(none)"} ${pc28.dim(sub)}`);
29383
+ }
29384
+ }
29385
+ function registerSkills(program2) {
29386
+ const skills = program2.commands.find((c) => c.name() === "skills") ?? program2.command("skills").description("Manage skill sources and synchronization");
29387
+ skills.command("sync").description("Sync registered skill sources to ~/.claude/").option("--dry-run", "Resolve artifacts but do not deploy or merge").option("--atlas-user <user>", "Override atlas-user (defaults to ~/.claude/.atlas-user)").action(async (opts) => {
29388
+ try {
29389
+ const summary = await syncSkills({ dryRun: opts.dryRun, atlasUser: opts.atlasUser });
29390
+ printSyncSummary(summary, !!opts.dryRun);
29391
+ if (!opts.dryRun) {
29392
+ printSuccess(`synced ${summary.sourceCount} source(s), ${summary.artifactCount} artifact(s)`);
29393
+ }
29394
+ } catch (err) {
29395
+ printError(asMessage4(err));
28187
29396
  process.exitCode = 1;
28188
29397
  }
28189
29398
  });
28190
- runbooks.command("apply").description("Create a world from a runbook (delegates to olam create)").argument("<name>", "Runbook name").option("--name <world>", "World name override").option("--task <task>", "Initial task to dispatch").action((name, opts) => {
29399
+ skills.command("diff").description("Show what `olam skills sync` would deploy (no on-disk changes)").option("--atlas-user <user>", "Override atlas-user").action(async (opts) => {
28191
29400
  try {
28192
- const rb = getRunbook(name);
28193
- const { conflicts } = validateRunbookPorts(rb);
28194
- if (conflicts.length > 0) {
28195
- printError(`port conflicts detected for runbook "${name}":`);
28196
- for (const c of conflicts) {
28197
- console.error(formatConflict(c));
28198
- }
28199
- console.error(
28200
- `
28201
- ${conflicts.length} port conflict(s). Stop the conflicting processes or update portMap in runbook "${name}".`
28202
- );
28203
- process.exitCode = 1;
28204
- return;
28205
- }
28206
- console.log(
28207
- pc26.dim(
28208
- `olam runbooks apply: port validation passed for "${name}" (repos: ${rb.repos.join(", ")}).`
28209
- )
28210
- );
28211
- console.log(
28212
- pc26.dim(
28213
- `Hint: use "olam create --repos ${rb.repos.join(" ")}${opts.task ? ` --task "${opts.task}"` : ""}${opts.name ? ` --name ${opts.name}` : ""}" to create the world.`
28214
- )
28215
- );
28216
- console.log(
28217
- pc26.yellow('Phase D will wire --runbook directly. For now, use "olam create --repos ..." above.')
28218
- );
29401
+ const summary = await syncSkills({ dryRun: true, atlasUser: opts.atlasUser });
29402
+ printSyncSummary(summary, true);
28219
29403
  } catch (err) {
28220
- printError(asMessage2(err));
29404
+ printError(asMessage4(err));
28221
29405
  process.exitCode = 1;
28222
29406
  }
28223
29407
  });
28224
- runbooks.command("check-ports").description("Check if runbook ports are available").argument("<name>", "Runbook name").action((name) => {
28225
- try {
28226
- const rb = getRunbook(name);
28227
- const { conflicts } = validateRunbookPorts(rb);
28228
- if (!rb.portMap || Object.keys(rb.portMap).length === 0) {
28229
- console.log(pc26.dim(`runbook "${name}" declares no ports.`));
28230
- return;
28231
- }
28232
- for (const [repoName, svcMap] of Object.entries(rb.portMap ?? {})) {
28233
- for (const [svcName, port2] of Object.entries(svcMap)) {
28234
- const conflict = conflicts.find((c) => c.port === port2);
28235
- if (conflict) {
28236
- console.log(formatConflict(conflict));
28237
- } else {
28238
- console.log(` ${pc26.green("\u2713")} ${repoName}.${svcName}:${port2} \u2014 free`);
28239
- }
28240
- }
28241
- }
28242
- if (conflicts.length > 0) {
28243
- console.error(
28244
- `
28245
- ${conflicts.length} port conflict(s). Stop the conflicting processes or update portMap in runbook "${name}".`
28246
- );
28247
- process.exitCode = 1;
28248
- } else {
28249
- console.log(pc26.green("\n\u2713 all ports free"));
29408
+ skills.command("list").description("List artifacts currently deployed to ~/.claude/").action(() => {
29409
+ const entries = listDeployed();
29410
+ if (entries.length === 0) {
29411
+ console.log(pc28.dim('0 deployed artifact(s). Run "olam skills sync" first.'));
29412
+ return;
29413
+ }
29414
+ const byBucket = /* @__PURE__ */ new Map();
29415
+ for (const e of entries) {
29416
+ const list = byBucket.get(e.bucket) ?? [];
29417
+ list.push(e);
29418
+ byBucket.set(e.bucket, list);
29419
+ }
29420
+ printHeader(`${entries.length} deployed artifact(s)`);
29421
+ for (const [bucket, list] of byBucket.entries()) {
29422
+ console.log(` ${pc28.bold(bucket)} (${list.length})`);
29423
+ for (const e of list) {
29424
+ const src = e.sourceId ? `[${e.sourceId}]` : pc28.yellow("[unknown]");
29425
+ console.log(` ${e.name.padEnd(40)} ${pc28.dim(src)} \u2192 ${pc28.dim(e.target)}`);
28250
29426
  }
28251
- } catch (err) {
28252
- printError(asMessage2(err));
29427
+ }
29428
+ });
29429
+ skills.command("show").description("Show details for a deployed artifact (by name)").argument("<name>", "Artifact name (e.g. plan-hard, codex-second-opinion.md)").action((name) => {
29430
+ const entries = listDeployed();
29431
+ const matches2 = entries.filter((e) => e.name === name);
29432
+ if (matches2.length === 0) {
29433
+ printError(`no deployed artifact matches "${name}"`);
28253
29434
  process.exitCode = 1;
29435
+ return;
29436
+ }
29437
+ for (const e of matches2) {
29438
+ console.log(`${pc28.bold("bucket:")} ${e.bucket}`);
29439
+ console.log(`${pc28.bold("name:")} ${e.name}`);
29440
+ console.log(`${pc28.bold("target:")} ${e.target}`);
29441
+ console.log(`${pc28.bold("source:")} ${e.sourceId ?? pc28.dim("(unknown \u2014 not from a registered olam source)")}`);
29442
+ console.log();
28254
29443
  }
28255
29444
  });
28256
29445
  }
@@ -28329,17 +29518,17 @@ function registerWorldUpgrade(program2) {
28329
29518
 
28330
29519
  // src/commands/mcp/serve.ts
28331
29520
  init_output();
28332
- import { existsSync as existsSync52 } from "node:fs";
28333
- import { dirname as dirname27, resolve as resolve13 } from "node:path";
29521
+ import { existsSync as existsSync58 } from "node:fs";
29522
+ import { dirname as dirname28, resolve as resolve13 } from "node:path";
28334
29523
  import { fileURLToPath as fileURLToPath5 } from "node:url";
28335
- var here = dirname27(fileURLToPath5(import.meta.url));
29524
+ var here = dirname28(fileURLToPath5(import.meta.url));
28336
29525
  var BUNDLE_PATH_CANDIDATES = [
28337
29526
  // bundled (`dist/index.js` after bundle-cli.mjs) — sibling
28338
29527
  resolve13(here, "mcp-server.js"),
28339
29528
  // dev / tsc-only (`dist/commands/mcp/serve.js`) — up two levels
28340
29529
  resolve13(here, "..", "..", "mcp-server.js")
28341
29530
  ];
28342
- function resolveBundlePath(candidates2 = BUNDLE_PATH_CANDIDATES, exists = existsSync52) {
29531
+ function resolveBundlePath(candidates2 = BUNDLE_PATH_CANDIDATES, exists = existsSync58) {
28343
29532
  return candidates2.find(exists) ?? null;
28344
29533
  }
28345
29534
  var MISSING_BUNDLE_REMEDY = "olam mcp server bundle missing. Searched: " + BUNDLE_PATH_CANDIDATES.join(", ") + ". For local dev, run: node packages/cli/scripts/bundle-mcp-server.mjs. A fresh `npm install -g @pleri/olam-cli@latest` should always include the bundle (see prepublishOnly in packages/cli/package.json); file an issue if it does not.";
@@ -28496,8 +29685,8 @@ var SECRET = process.env["OLAM_MCP_AUTH_SECRET"] ?? "";
28496
29685
  function authHeaders() {
28497
29686
  return SECRET ? { "X-Olam-Mcp-Secret": SECRET } : {};
28498
29687
  }
28499
- async function apiFetch(path60, init = {}) {
28500
- const res = await fetch(`${BASE_URL}${path60}`, {
29688
+ async function apiFetch(path66, init = {}) {
29689
+ const res = await fetch(`${BASE_URL}${path66}`, {
28501
29690
  ...init,
28502
29691
  headers: {
28503
29692
  "Content-Type": "application/json",
@@ -28649,7 +29838,7 @@ function registerMcpAdd(cmd) {
28649
29838
 
28650
29839
  // src/commands/mcp/list.ts
28651
29840
  init_output();
28652
- import pc27 from "picocolors";
29841
+ import pc29 from "picocolors";
28653
29842
  function registerMcpList(cmd) {
28654
29843
  cmd.command("list").description("List all registered MCP credentials").action(async () => {
28655
29844
  const client = getMcpAuthClient();
@@ -28664,8 +29853,8 @@ function registerMcpList(cmd) {
28664
29853
  }
28665
29854
  const mcps = data.mcps ?? [];
28666
29855
  if (mcps.length === 0) {
28667
- console.log(pc27.dim("No MCP credentials registered."));
28668
- console.log(pc27.dim("Add one with: olam mcp add <service>"));
29856
+ console.log(pc29.dim("No MCP credentials registered."));
29857
+ console.log(pc29.dim("Add one with: olam mcp add <service>"));
28669
29858
  return;
28670
29859
  }
28671
29860
  const [c0, c1, c2, c3] = [16, 20, 10, 24];
@@ -28676,12 +29865,12 @@ function registerMcpList(cmd) {
28676
29865
  "ENV VAR".padEnd(c3),
28677
29866
  "STATUS"
28678
29867
  ].join(" ");
28679
- console.log(pc27.dim(header));
28680
- console.log(pc27.dim("\u2500".repeat(header.length)));
29868
+ console.log(pc29.dim(header));
29869
+ console.log(pc29.dim("\u2500".repeat(header.length)));
28681
29870
  for (const m of mcps) {
28682
- const status2 = !m.validated ? pc27.yellow("unvalidated") : m.expired ? pc27.red("expired") : pc27.green("active");
29871
+ const status2 = !m.validated ? pc29.yellow("unvalidated") : m.expired ? pc29.red("expired") : pc29.green("active");
28683
29872
  const row = [
28684
- pc27.cyan(m.service.padEnd(c0)),
29873
+ pc29.cyan(m.service.padEnd(c0)),
28685
29874
  m.label.padEnd(c1).slice(0, c1),
28686
29875
  m.type.padEnd(c2),
28687
29876
  (m.envName ?? "\u2014").padEnd(c3).slice(0, c3),
@@ -28709,13 +29898,13 @@ function registerMcpRemove(cmd) {
28709
29898
 
28710
29899
  // src/commands/mcp/status.ts
28711
29900
  init_output();
28712
- import pc28 from "picocolors";
29901
+ import pc30 from "picocolors";
28713
29902
  function formatExpiry(expiresAt) {
28714
29903
  if (!expiresAt) return "\u2014";
28715
29904
  const ms = expiresAt - Date.now();
28716
- if (ms <= 0) return pc28.red("expired");
29905
+ if (ms <= 0) return pc30.red("expired");
28717
29906
  const hours = ms / (1e3 * 60 * 60);
28718
- if (hours < 1) return pc28.yellow(`${Math.ceil(ms / 6e4)}m`);
29907
+ if (hours < 1) return pc30.yellow(`${Math.ceil(ms / 6e4)}m`);
28719
29908
  return `${hours.toFixed(1)}h`;
28720
29909
  }
28721
29910
  function registerMcpStatus(cmd) {
@@ -28732,14 +29921,14 @@ function registerMcpStatus(cmd) {
28732
29921
  const mcps = data.mcps ?? [];
28733
29922
  printHeader(`MCP Credentials (${mcps.length})`);
28734
29923
  if (mcps.length === 0) {
28735
- console.log(pc28.dim("No credentials registered."));
29924
+ console.log(pc30.dim("No credentials registered."));
28736
29925
  return;
28737
29926
  }
28738
29927
  for (const m of mcps) {
28739
- const stateColor = !m.validated ? pc28.yellow : m.expired ? pc28.red : pc28.green;
29928
+ const stateColor = !m.validated ? pc30.yellow : m.expired ? pc30.red : pc30.green;
28740
29929
  const stateLabel = !m.validated ? "unvalidated" : m.expired ? "expired" : "active";
28741
29930
  console.log(`
28742
- ${pc28.cyan(m.service)} ${stateColor(`[${stateLabel}]`)}`);
29931
+ ${pc30.cyan(m.service)} ${stateColor(`[${stateLabel}]`)}`);
28743
29932
  console.log(` label: ${m.label}`);
28744
29933
  console.log(` type: ${m.type}`);
28745
29934
  if (m.envName) console.log(` env var: ${m.envName}`);
@@ -28754,15 +29943,15 @@ function registerMcpStatus(cmd) {
28754
29943
  // src/commands/mcp/import.ts
28755
29944
  init_output();
28756
29945
  import * as readline3 from "node:readline";
28757
- import pc29 from "picocolors";
29946
+ import pc31 from "picocolors";
28758
29947
 
28759
29948
  // src/commands/mcp/import-discovery.ts
28760
- import * as fs47 from "node:fs";
28761
- import * as os27 from "node:os";
28762
- import * as path51 from "node:path";
29949
+ import * as fs53 from "node:fs";
29950
+ import * as os31 from "node:os";
29951
+ import * as path57 from "node:path";
28763
29952
  function readJsonFile(filePath) {
28764
29953
  try {
28765
- const raw = fs47.readFileSync(filePath, "utf-8");
29954
+ const raw = fs53.readFileSync(filePath, "utf-8");
28766
29955
  return JSON.parse(raw);
28767
29956
  } catch {
28768
29957
  return null;
@@ -28791,24 +29980,24 @@ function extractMcpServers(obj, source, sourceLabel) {
28791
29980
  }
28792
29981
  function getClaudeDesktopPath() {
28793
29982
  if (process.platform === "darwin") {
28794
- return path51.join(os27.homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
29983
+ return path57.join(os31.homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
28795
29984
  }
28796
29985
  if (process.platform === "win32") {
28797
- const appData = process.env["APPDATA"] ?? path51.join(os27.homedir(), "AppData", "Roaming");
28798
- return path51.join(appData, "Claude", "claude_desktop_config.json");
29986
+ const appData = process.env["APPDATA"] ?? path57.join(os31.homedir(), "AppData", "Roaming");
29987
+ return path57.join(appData, "Claude", "claude_desktop_config.json");
28799
29988
  }
28800
- return path51.join(os27.homedir(), ".config", "Claude", "claude_desktop_config.json");
29989
+ return path57.join(os31.homedir(), ".config", "Claude", "claude_desktop_config.json");
28801
29990
  }
28802
29991
  function getOlamRepoPaths() {
28803
29992
  const configPaths = [
28804
- path51.join(os27.homedir(), ".olam", "config.yaml"),
28805
- path51.join(process.cwd(), ".olam", "config.yaml")
29993
+ path57.join(os31.homedir(), ".olam", "config.yaml"),
29994
+ path57.join(process.cwd(), ".olam", "config.yaml")
28806
29995
  ];
28807
29996
  const paths = [];
28808
29997
  for (const configPath of configPaths) {
28809
- if (!fs47.existsSync(configPath)) continue;
29998
+ if (!fs53.existsSync(configPath)) continue;
28810
29999
  try {
28811
- const raw = fs47.readFileSync(configPath, "utf-8");
30000
+ const raw = fs53.readFileSync(configPath, "utf-8");
28812
30001
  const repoMatches = [...raw.matchAll(/path:\s*["']?([^\s"'\n]+)/g)];
28813
30002
  for (const m of repoMatches) {
28814
30003
  if (m[1]) paths.push(m[1]);
@@ -28824,7 +30013,7 @@ async function discoverMcpSources(repoPaths) {
28824
30013
  const sources = [];
28825
30014
  const sourceDefs = [
28826
30015
  {
28827
- path: path51.join(os27.homedir(), ".claude.json"),
30016
+ path: path57.join(os31.homedir(), ".claude.json"),
28828
30017
  label: "Claude Code (~/.claude.json)"
28829
30018
  },
28830
30019
  {
@@ -28832,19 +30021,19 @@ async function discoverMcpSources(repoPaths) {
28832
30021
  label: "Claude Desktop"
28833
30022
  },
28834
30023
  {
28835
- path: path51.join(os27.homedir(), ".cursor", "mcp.json"),
30024
+ path: path57.join(os31.homedir(), ".cursor", "mcp.json"),
28836
30025
  label: "Cursor (~/.cursor/mcp.json)"
28837
30026
  },
28838
30027
  {
28839
- path: path51.join(os27.homedir(), ".codeium", "windsurf", "mcp_config.json"),
30028
+ path: path57.join(os31.homedir(), ".codeium", "windsurf", "mcp_config.json"),
28840
30029
  label: "Windsurf (~/.codeium/windsurf/mcp_config.json)"
28841
30030
  }
28842
30031
  ];
28843
30032
  const resolvedRepoPaths = repoPaths ?? getOlamRepoPaths();
28844
30033
  for (const repoPath of resolvedRepoPaths) {
28845
30034
  sourceDefs.push({
28846
- path: path51.join(repoPath, ".mcp.json"),
28847
- label: `.mcp.json (${path51.basename(repoPath)})`
30035
+ path: path57.join(repoPath, ".mcp.json"),
30036
+ label: `.mcp.json (${path57.basename(repoPath)})`
28848
30037
  });
28849
30038
  }
28850
30039
  const reads = await Promise.all(
@@ -28924,13 +30113,13 @@ async function validateMcpEntry(entry) {
28924
30113
  // src/commands/mcp/import.ts
28925
30114
  async function multiSelectPicker(entries) {
28926
30115
  if (entries.length === 0) return [];
28927
- console.log("\n" + pc29.bold("Discovered MCP servers:"));
30116
+ console.log("\n" + pc31.bold("Discovered MCP servers:"));
28928
30117
  entries.forEach((e, i) => {
28929
30118
  console.log(
28930
- ` ${pc29.dim(`[${i + 1}]`)} ${pc29.cyan(e.name.padEnd(20))} ${pc29.dim(e.sourceLabel)}`
30119
+ ` ${pc31.dim(`[${i + 1}]`)} ${pc31.cyan(e.name.padEnd(20))} ${pc31.dim(e.sourceLabel)}`
28931
30120
  );
28932
30121
  });
28933
- console.log("\n" + pc29.dim('Enter numbers to import (e.g. 1,2,3 or "all" or Enter to skip):'));
30122
+ console.log("\n" + pc31.dim('Enter numbers to import (e.g. 1,2,3 or "all" or Enter to skip):'));
28934
30123
  const answer = await new Promise((resolve15) => {
28935
30124
  const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
28936
30125
  rl.question("> ", (ans) => {
@@ -28957,7 +30146,7 @@ function registerMcpImport(cmd) {
28957
30146
  const repoPaths = opts.repoPaths ? opts.repoPaths.split(",").map((s) => s.trim()) : void 0;
28958
30147
  const { entries, sources, durationMs } = await discoverMcpSources(repoPaths);
28959
30148
  if (entries.length === 0) {
28960
- console.log(pc29.dim("No MCP servers found in any source path."));
30149
+ console.log(pc31.dim("No MCP servers found in any source path."));
28961
30150
  return;
28962
30151
  }
28963
30152
  printInfo("Sources", sources.length > 0 ? sources.join(", ") : "none");
@@ -28970,15 +30159,15 @@ function registerMcpImport(cmd) {
28970
30159
  candidates2 = filtered;
28971
30160
  }
28972
30161
  if (skippedCount > 0) {
28973
- console.log(pc29.dim(`skipped: ${skippedCount} already registered`));
30162
+ console.log(pc31.dim(`skipped: ${skippedCount} already registered`));
28974
30163
  }
28975
30164
  if (candidates2.length === 0) {
28976
- console.log(pc29.dim("Nothing new to import. Use --reimport to force."));
30165
+ console.log(pc31.dim("Nothing new to import. Use --reimport to force."));
28977
30166
  return;
28978
30167
  }
28979
30168
  const selected = await multiSelectPicker(candidates2);
28980
30169
  if (selected.length === 0) {
28981
- console.log(pc29.dim("No servers selected."));
30170
+ console.log(pc31.dim("No servers selected."));
28982
30171
  return;
28983
30172
  }
28984
30173
  console.log(`
@@ -28989,16 +30178,16 @@ Importing ${selected.length} server(s)\u2026`);
28989
30178
  let validated = false;
28990
30179
  let validationReason = "skipped";
28991
30180
  if (opts.validate !== false) {
28992
- process.stdout.write(` ${pc29.dim("\u2192")} ${entry.name} validating\u2026 `);
30181
+ process.stdout.write(` ${pc31.dim("\u2192")} ${entry.name} validating\u2026 `);
28993
30182
  const vr = await validateMcpEntry(entry);
28994
30183
  validated = vr.validated;
28995
30184
  validationReason = vr.reason;
28996
30185
  process.stdout.write(
28997
- validated ? pc29.green("ok\n") : pc29.yellow(`unvalidated (${vr.reason})
30186
+ validated ? pc31.green("ok\n") : pc31.yellow(`unvalidated (${vr.reason})
28998
30187
  `)
28999
30188
  );
29000
30189
  } else {
29001
- console.log(` ${pc29.dim("\u2192")} ${entry.name} ${pc29.dim("(validation skipped)")}`);
30190
+ console.log(` ${pc31.dim("\u2192")} ${entry.name} ${pc31.dim("(validation skipped)")}`);
29002
30191
  }
29003
30192
  if (validated) validatedCount++;
29004
30193
  const result = await client.staticAdd({
@@ -29013,21 +30202,21 @@ Importing ${selected.length} server(s)\u2026`);
29013
30202
  }
29014
30203
  }
29015
30204
  console.log("");
29016
- console.log(pc29.green(`\u2713 Imported ${importedCount}/${selected.length}`));
30205
+ console.log(pc31.green(`\u2713 Imported ${importedCount}/${selected.length}`));
29017
30206
  if (validatedCount > 0) {
29018
- console.log(pc29.dim(` ${validatedCount} validated, ${importedCount - validatedCount} unvalidated`));
30207
+ console.log(pc31.dim(` ${validatedCount} validated, ${importedCount - validatedCount} unvalidated`));
29019
30208
  }
29020
30209
  });
29021
30210
  }
29022
30211
 
29023
30212
  // src/commands/mcp/revoke.ts
29024
30213
  init_output();
29025
- import pc30 from "picocolors";
30214
+ import pc32 from "picocolors";
29026
30215
  function registerMcpRevoke(cmd) {
29027
30216
  cmd.command("revoke <user> <world> <service>").description("Revoke a user's MCP service entitlement (multi-tenant mode only)").action(async (user, world, service) => {
29028
30217
  const multiTenant = (process.env["OLAM_MCP_MULTI_TENANT"] ?? "").toLowerCase() === "true";
29029
30218
  if (!multiTenant) {
29030
- console.warn(pc30.yellow("\u26A0 revocation only meaningful in multi_tenant mode \u2014 no-op"));
30219
+ console.warn(pc32.yellow("\u26A0 revocation only meaningful in multi_tenant mode \u2014 no-op"));
29031
30220
  return;
29032
30221
  }
29033
30222
  const baseUrl = process.env["OLAM_MCP_AUTH_SERVICE_URL"] ?? "http://127.0.0.1:9998";
@@ -29056,8 +30245,8 @@ function registerMcpRevoke(cmd) {
29056
30245
  process.exitCode = 1;
29057
30246
  return;
29058
30247
  }
29059
- console.log(pc30.green(`\u2713 Revoked: ${user} / ${world} / ${service}`));
29060
- console.log(pc30.dim(" Next fetch-creds call returns 403; token cleared on next merge."));
30248
+ console.log(pc32.green(`\u2713 Revoked: ${user} / ${world} / ${service}`));
30249
+ console.log(pc32.dim(" Next fetch-creds call returns 403; token cleared on next merge."));
29061
30250
  });
29062
30251
  }
29063
30252
 
@@ -29079,34 +30268,34 @@ function registerMcp(program2) {
29079
30268
  // src/commands/memory/start.ts
29080
30269
  init_output();
29081
30270
  import { spawn as spawn8 } from "node:child_process";
29082
- import { existsSync as existsSync54, mkdirSync as mkdirSync31, openSync as openSync3, readFileSync as readFileSync38, writeFileSync as writeFileSync24 } from "node:fs";
29083
- import { join as join52 } from "node:path";
30271
+ import { existsSync as existsSync60, mkdirSync as mkdirSync34, openSync as openSync3, readFileSync as readFileSync41, writeFileSync as writeFileSync25 } from "node:fs";
30272
+ import { join as join58 } from "node:path";
29084
30273
  import { pathToFileURL } from "node:url";
29085
30274
 
29086
30275
  // src/commands/memory/_paths.ts
29087
- import { homedir as homedir30 } from "node:os";
29088
- import { join as join51, dirname as dirname28 } from "node:path";
30276
+ import { homedir as homedir34 } from "node:os";
30277
+ import { join as join57, dirname as dirname29 } from "node:path";
29089
30278
  import { fileURLToPath as fileURLToPath6 } from "node:url";
29090
- var OLAM_HOME = join51(homedir30(), ".olam");
29091
- var OLAM_BIN_DIR = join51(OLAM_HOME, "bin");
29092
- var III_BINARY_PATH = join51(OLAM_BIN_DIR, "iii");
29093
- var MEMORY_PID_PATH = join51(OLAM_HOME, "memory.pid");
29094
- var MEMORY_LOG_PATH = join51(OLAM_HOME, "memory-service.log");
29095
- var MEMORY_DATA_DIR = join51(OLAM_HOME, "memory-data");
30279
+ var OLAM_HOME = join57(homedir34(), ".olam");
30280
+ var OLAM_BIN_DIR = join57(OLAM_HOME, "bin");
30281
+ var III_BINARY_PATH = join57(OLAM_BIN_DIR, "iii");
30282
+ var MEMORY_PID_PATH = join57(OLAM_HOME, "memory.pid");
30283
+ var MEMORY_LOG_PATH = join57(OLAM_HOME, "memory-service.log");
30284
+ var MEMORY_DATA_DIR = join57(OLAM_HOME, "memory-data");
29096
30285
  var MEMORY_REST_PORT = 3111;
29097
30286
  var MEMORY_LIVEZ_URL = `http://localhost:${MEMORY_REST_PORT}/agentmemory/livez`;
29098
- var here2 = dirname28(fileURLToPath6(import.meta.url));
30287
+ var here2 = dirname29(fileURLToPath6(import.meta.url));
29099
30288
  var candidates = [
29100
30289
  // 1. Workspace dev (built): packages/cli/dist/commands/memory/_paths.js → packages/cli → packages/memory-service
29101
- join51(here2, "..", "..", "..", "..", "memory-service"),
30290
+ join57(here2, "..", "..", "..", "..", "memory-service"),
29102
30291
  // 2a. Workspace bundled: packages/cli/dist/index.js → packages/cli → packages/memory-service
29103
- join51(here2, "..", "..", "memory-service"),
30292
+ join57(here2, "..", "..", "memory-service"),
29104
30293
  // 2b. Published tarball: <prefix>/node_modules/@pleri/olam-cli/dist/index.js
29105
30294
  // → <prefix>/node_modules/@pleri/olam-cli/memory-service-bundle
29106
30295
  // (copied at publish time by bundle-cli.mjs)
29107
- join51(here2, "..", "memory-service-bundle"),
30296
+ join57(here2, "..", "memory-service-bundle"),
29108
30297
  // 3. CWD fallback
29109
- join51(process.cwd(), "packages", "memory-service")
30298
+ join57(process.cwd(), "packages", "memory-service")
29110
30299
  ];
29111
30300
  var MEMORY_SERVICE_CANDIDATES = candidates;
29112
30301
 
@@ -29115,7 +30304,7 @@ var READINESS_TIMEOUT_MS = 3e4;
29115
30304
  var READINESS_POLL_MS = 500;
29116
30305
  function resolveMemoryServiceDir() {
29117
30306
  for (const c of MEMORY_SERVICE_CANDIDATES) {
29118
- if (existsSync54(c)) return c;
30307
+ if (existsSync60(c)) return c;
29119
30308
  }
29120
30309
  throw new Error(
29121
30310
  `Could not find packages/memory-service/. Searched: ${MEMORY_SERVICE_CANDIDATES.join(", ")}. If running from a published @pleri/olam-cli tarball, this is a packaging bug \u2014 please file an issue.`
@@ -29123,12 +30312,12 @@ function resolveMemoryServiceDir() {
29123
30312
  }
29124
30313
  function resolveAgentMemoryBin(serviceDir) {
29125
30314
  const candidates2 = [
29126
- join52(serviceDir, "node_modules", ".bin", "agentmemory"),
29127
- join52(serviceDir, "..", "..", "node_modules", ".bin", "agentmemory"),
29128
- join52(serviceDir, "..", "..", "..", "node_modules", ".bin", "agentmemory")
30315
+ join58(serviceDir, "node_modules", ".bin", "agentmemory"),
30316
+ join58(serviceDir, "..", "..", "node_modules", ".bin", "agentmemory"),
30317
+ join58(serviceDir, "..", "..", "..", "node_modules", ".bin", "agentmemory")
29129
30318
  ];
29130
30319
  for (const c of candidates2) {
29131
- if (existsSync54(c)) return c;
30320
+ if (existsSync60(c)) return c;
29132
30321
  }
29133
30322
  throw new Error(
29134
30323
  `Could not find agentmemory bin. Searched: ${candidates2.join(", ")}. Run 'npm install' from the repo root.`
@@ -29143,8 +30332,8 @@ function isProcessAlive(pid) {
29143
30332
  }
29144
30333
  }
29145
30334
  function readPidFromFile() {
29146
- if (!existsSync54(MEMORY_PID_PATH)) return null;
29147
- const raw = readFileSync38(MEMORY_PID_PATH, "utf8").trim();
30335
+ if (!existsSync60(MEMORY_PID_PATH)) return null;
30336
+ const raw = readFileSync41(MEMORY_PID_PATH, "utf8").trim();
29148
30337
  const pid = parseInt(raw, 10);
29149
30338
  if (!Number.isFinite(pid) || pid <= 0) return null;
29150
30339
  return pid;
@@ -29171,7 +30360,7 @@ async function waitForReady(secret) {
29171
30360
  return false;
29172
30361
  }
29173
30362
  async function autoEnsureIiiBinary(serviceDir) {
29174
- const helperPath = join52(serviceDir, "scripts", "ensure-iii-engine.mjs");
30363
+ const helperPath = join58(serviceDir, "scripts", "ensure-iii-engine.mjs");
29175
30364
  const mod = await import(pathToFileURL(helperPath).href);
29176
30365
  const result = await mod.ensureIiiEngine();
29177
30366
  if (!result.ok) {
@@ -29187,7 +30376,7 @@ async function runMemoryStart() {
29187
30376
  printError(err instanceof Error ? err.message : String(err));
29188
30377
  return 1;
29189
30378
  }
29190
- if (!existsSync54(III_BINARY_PATH)) {
30379
+ if (!existsSync60(III_BINARY_PATH)) {
29191
30380
  printInfo("iii binary", `missing at ${III_BINARY_PATH} \u2014 auto-fetching v0.11.2`);
29192
30381
  try {
29193
30382
  await autoEnsureIiiBinary(serviceDir);
@@ -29215,8 +30404,8 @@ async function runMemoryStart() {
29215
30404
  );
29216
30405
  return 1;
29217
30406
  }
29218
- mkdirSync31(OLAM_HOME, { recursive: true });
29219
- mkdirSync31(MEMORY_DATA_DIR, { recursive: true });
30407
+ mkdirSync34(OLAM_HOME, { recursive: true });
30408
+ mkdirSync34(MEMORY_DATA_DIR, { recursive: true });
29220
30409
  const logFd = openSync3(MEMORY_LOG_PATH, "a");
29221
30410
  const child = spawn8(agentmemoryBin, [], {
29222
30411
  cwd: OLAM_HOME,
@@ -29239,7 +30428,7 @@ async function runMemoryStart() {
29239
30428
  printError("spawn returned no pid (process failed to start)");
29240
30429
  return 1;
29241
30430
  }
29242
- writeFileSync24(MEMORY_PID_PATH, `${child.pid}
30431
+ writeFileSync25(MEMORY_PID_PATH, `${child.pid}
29243
30432
  `, { mode: 420 });
29244
30433
  printInfo("pid", `${child.pid}`);
29245
30434
  printInfo("readiness", `polling ${MEMORY_LIVEZ_URL}`);
@@ -29262,7 +30451,7 @@ function registerMemoryStart(cmd) {
29262
30451
 
29263
30452
  // src/commands/memory/stop.ts
29264
30453
  init_output();
29265
- import { existsSync as existsSync55, readFileSync as readFileSync39, unlinkSync as unlinkSync9 } from "node:fs";
30454
+ import { existsSync as existsSync61, readFileSync as readFileSync42, unlinkSync as unlinkSync11 } from "node:fs";
29266
30455
  var SIGTERM_GRACE_MS = 1e4;
29267
30456
  var POLL_MS = 250;
29268
30457
  function isAlive(pid) {
@@ -29275,19 +30464,19 @@ function isAlive(pid) {
29275
30464
  }
29276
30465
  async function runMemoryStop() {
29277
30466
  printHeader("olam memory stop");
29278
- if (!existsSync55(MEMORY_PID_PATH)) {
30467
+ if (!existsSync61(MEMORY_PID_PATH)) {
29279
30468
  printSuccess("no pidfile present (nothing to stop)");
29280
30469
  return 0;
29281
30470
  }
29282
- const pid = parseInt(readFileSync39(MEMORY_PID_PATH, "utf8").trim(), 10);
30471
+ const pid = parseInt(readFileSync42(MEMORY_PID_PATH, "utf8").trim(), 10);
29283
30472
  if (!Number.isFinite(pid) || pid <= 0) {
29284
30473
  printWarning(`pidfile contained invalid value; removing`);
29285
- unlinkSync9(MEMORY_PID_PATH);
30474
+ unlinkSync11(MEMORY_PID_PATH);
29286
30475
  return 0;
29287
30476
  }
29288
30477
  if (!isAlive(pid)) {
29289
30478
  printSuccess(`pid ${pid} is not running (stale pidfile); cleaned up`);
29290
- unlinkSync9(MEMORY_PID_PATH);
30479
+ unlinkSync11(MEMORY_PID_PATH);
29291
30480
  return 0;
29292
30481
  }
29293
30482
  printInfo("pid", `${pid}`);
@@ -29310,8 +30499,8 @@ async function runMemoryStop() {
29310
30499
  }
29311
30500
  await new Promise((r) => setTimeout(r, 500));
29312
30501
  }
29313
- if (existsSync55(MEMORY_PID_PATH)) {
29314
- unlinkSync9(MEMORY_PID_PATH);
30502
+ if (existsSync61(MEMORY_PID_PATH)) {
30503
+ unlinkSync11(MEMORY_PID_PATH);
29315
30504
  }
29316
30505
  printSuccess(`stopped (pid ${pid})`);
29317
30506
  return 0;
@@ -29325,7 +30514,7 @@ function registerMemoryStop(cmd) {
29325
30514
 
29326
30515
  // src/commands/memory/status.ts
29327
30516
  init_output();
29328
- import { existsSync as existsSync56, readFileSync as readFileSync40 } from "node:fs";
30517
+ import { existsSync as existsSync62, readFileSync as readFileSync43 } from "node:fs";
29329
30518
  function isAlive2(pid) {
29330
30519
  try {
29331
30520
  process.kill(pid, 0);
@@ -29349,8 +30538,8 @@ async function probe(secret) {
29349
30538
  }
29350
30539
  async function collectMemoryStatus() {
29351
30540
  let pid = null;
29352
- if (existsSync56(MEMORY_PID_PATH)) {
29353
- const raw = readFileSync40(MEMORY_PID_PATH, "utf8").trim();
30541
+ if (existsSync62(MEMORY_PID_PATH)) {
30542
+ const raw = readFileSync43(MEMORY_PID_PATH, "utf8").trim();
29354
30543
  const parsed = parseInt(raw, 10);
29355
30544
  pid = Number.isFinite(parsed) && parsed > 0 ? parsed : null;
29356
30545
  }
@@ -29362,7 +30551,7 @@ async function collectMemoryStatus() {
29362
30551
  alive,
29363
30552
  livez,
29364
30553
  secretSet: hasMemorySecret(),
29365
- iiiBinary: existsSync56(III_BINARY_PATH) ? III_BINARY_PATH : null,
30554
+ iiiBinary: existsSync62(III_BINARY_PATH) ? III_BINARY_PATH : null,
29366
30555
  port: MEMORY_REST_PORT
29367
30556
  };
29368
30557
  }
@@ -29405,10 +30594,10 @@ function registerMemoryStatus(cmd) {
29405
30594
 
29406
30595
  // src/commands/memory/logs.ts
29407
30596
  init_output();
29408
- import { existsSync as existsSync57 } from "node:fs";
30597
+ import { existsSync as existsSync63 } from "node:fs";
29409
30598
  import { spawn as spawn9 } from "node:child_process";
29410
30599
  async function runMemoryLogs(opts) {
29411
- if (!existsSync57(MEMORY_LOG_PATH)) {
30600
+ if (!existsSync63(MEMORY_LOG_PATH)) {
29412
30601
  printWarning(`no log at ${MEMORY_LOG_PATH} (start the service first via 'olam memory start')`);
29413
30602
  return 1;
29414
30603
  }
@@ -29434,7 +30623,7 @@ function registerMemoryLogs(cmd) {
29434
30623
  }
29435
30624
 
29436
30625
  // src/commands/memory/secret.ts
29437
- import { existsSync as existsSync58 } from "node:fs";
30626
+ import { existsSync as existsSync64 } from "node:fs";
29438
30627
  init_output();
29439
30628
  async function runMemorySecretShow() {
29440
30629
  if (!hasMemorySecret()) {
@@ -29449,7 +30638,7 @@ async function runMemorySecretShow() {
29449
30638
  }
29450
30639
  async function runMemorySecretRotate() {
29451
30640
  printHeader("olam memory secret rotate");
29452
- const wasRunning = existsSync58(MEMORY_PID_PATH);
30641
+ const wasRunning = existsSync64(MEMORY_PID_PATH);
29453
30642
  if (wasRunning) {
29454
30643
  printInfo("current state", "service running; will restart with new secret");
29455
30644
  const stopRc = await runMemoryStop();
@@ -29631,14 +30820,14 @@ function registerMemoryUninstall(cmd) {
29631
30820
  // src/commands/memory/mode.ts
29632
30821
  init_schema2();
29633
30822
  init_output();
29634
- import { existsSync as existsSync59, readFileSync as readFileSync41, writeFileSync as writeFileSync25 } from "node:fs";
29635
- import { join as join53 } from "node:path";
30823
+ import { existsSync as existsSync65, readFileSync as readFileSync44, writeFileSync as writeFileSync26 } from "node:fs";
30824
+ import { join as join59 } from "node:path";
29636
30825
  import * as readline4 from "node:readline/promises";
29637
30826
  import { parse as parseYaml6, stringify as stringifyYaml6 } from "yaml";
29638
30827
  var CONFIG_REL = ".olam/config.yaml";
29639
30828
  function locateConfig(cwd) {
29640
- const absPath = join53(cwd, CONFIG_REL);
29641
- if (!existsSync59(absPath)) {
30829
+ const absPath = join59(cwd, CONFIG_REL);
30830
+ if (!existsSync65(absPath)) {
29642
30831
  throw new Error(
29643
30832
  `No ${CONFIG_REL} at ${cwd}. Run \`olam init\` in your workspace root first.`
29644
30833
  );
@@ -29646,7 +30835,7 @@ function locateConfig(cwd) {
29646
30835
  return { absPath };
29647
30836
  }
29648
30837
  function readConfigYaml(absPath) {
29649
- const raw = readFileSync41(absPath, "utf-8");
30838
+ const raw = readFileSync44(absPath, "utf-8");
29650
30839
  const parsed = parseYaml6(raw) ?? {};
29651
30840
  if (typeof parsed !== "object" || parsed === null) {
29652
30841
  throw new Error(`${absPath} is not a YAML object`);
@@ -29655,7 +30844,7 @@ function readConfigYaml(absPath) {
29655
30844
  }
29656
30845
  function writeConfigYaml(absPath, parsed) {
29657
30846
  const out = stringifyYaml6(parsed, { aliasDuplicateObjects: false });
29658
- writeFileSync25(absPath, out, "utf-8");
30847
+ writeFileSync26(absPath, out, "utf-8");
29659
30848
  }
29660
30849
  async function defaultPromptText(question) {
29661
30850
  const rl = readline4.createInterface({ input: process.stdin, output: process.stdout });
@@ -29771,10 +30960,371 @@ function registerMemoryMode(cmd) {
29771
30960
  });
29772
30961
  }
29773
30962
 
30963
+ // src/commands/memory/bridge.ts
30964
+ init_output();
30965
+ import { spawn as spawn10 } from "node:child_process";
30966
+ import { existsSync as existsSync66 } from "node:fs";
30967
+ import { join as join60 } from "node:path";
30968
+ var DEFAULT_PORT2 = 8788;
30969
+ function resolveMemoryServiceDir2() {
30970
+ for (const c of MEMORY_SERVICE_CANDIDATES) {
30971
+ if (existsSync66(c)) return c;
30972
+ }
30973
+ throw new Error(
30974
+ `Could not find packages/memory-service/. Searched: ${MEMORY_SERVICE_CANDIDATES.join(", ")}. If running from a published @pleri/olam-cli tarball, this is a packaging bug \u2014 please file an issue.`
30975
+ );
30976
+ }
30977
+ function resolveLocalBridgeScript(serviceDir) {
30978
+ const path66 = join60(serviceDir, "scripts", "local-bridge-server.mjs");
30979
+ if (!existsSync66(path66)) {
30980
+ throw new Error(
30981
+ `Could not find local-bridge-server.mjs at ${path66}. Verify packages/memory-service ships the scripts/ directory.`
30982
+ );
30983
+ }
30984
+ return path66;
30985
+ }
30986
+ function validateBridgeOpts(opts) {
30987
+ const local = opts.local ?? false;
30988
+ if (!local) {
30989
+ throw new Error(
30990
+ `'olam memory bridge serve' currently requires --local. Remote-bridge orchestration is reserved for future flags.`
30991
+ );
30992
+ }
30993
+ const port2 = opts.port ?? DEFAULT_PORT2;
30994
+ if (!Number.isInteger(port2) || port2 < 0 || port2 > 65535) {
30995
+ throw new Error(`--port must be an integer 0..65535 (got: ${opts.port})`);
30996
+ }
30997
+ const result = {
30998
+ port: port2,
30999
+ local
31000
+ };
31001
+ if (typeof opts.persistTo === "string" && opts.persistTo.length > 0) {
31002
+ result.persistTo = opts.persistTo;
31003
+ }
31004
+ return result;
31005
+ }
31006
+ async function runBridgeServe(opts, deps = {}) {
31007
+ printHeader("olam memory bridge serve --local");
31008
+ const validated = validateBridgeOpts(opts);
31009
+ printInfo("port", String(validated.port));
31010
+ if (validated.persistTo) {
31011
+ printInfo("persist-to", validated.persistTo);
31012
+ } else {
31013
+ printInfo("persist-to", "(in-memory \u2014 DO state lost on restart)");
31014
+ }
31015
+ const serviceDir = (deps.resolveServiceDir ?? resolveMemoryServiceDir2)();
31016
+ const scriptPath = resolveLocalBridgeScript(serviceDir);
31017
+ printInfo("entry", scriptPath);
31018
+ const secret = (deps.ensureSecret ?? ensureMemorySecret)();
31019
+ const args = ["--port", String(validated.port)];
31020
+ if (validated.persistTo) {
31021
+ args.push("--persist-to", validated.persistTo);
31022
+ }
31023
+ const env = {
31024
+ ...process.env,
31025
+ AGENTMEMORY_SECRET: secret
31026
+ };
31027
+ const spawner = deps.spawnChild ?? ((command, spawnArgs, spawnEnv) => spawn10(command, [...spawnArgs], {
31028
+ env: spawnEnv,
31029
+ stdio: "inherit"
31030
+ }));
31031
+ const child = spawner(process.execPath, [scriptPath, ...args], env);
31032
+ return new Promise((resolve15) => {
31033
+ let resolved = false;
31034
+ const finish = (code) => {
31035
+ if (resolved) return;
31036
+ resolved = true;
31037
+ resolve15(code);
31038
+ };
31039
+ const forward = (signal) => () => {
31040
+ if (!child.killed) {
31041
+ try {
31042
+ child.kill(signal);
31043
+ } catch {
31044
+ }
31045
+ }
31046
+ };
31047
+ process.on("SIGTERM", forward("SIGTERM"));
31048
+ process.on("SIGINT", forward("SIGINT"));
31049
+ child.on("error", (err) => {
31050
+ printError(`failed to spawn local-bridge-server.mjs: ${err.message}`);
31051
+ finish(1);
31052
+ });
31053
+ child.on("exit", (code, signal) => {
31054
+ if (signal) {
31055
+ finish(0);
31056
+ } else {
31057
+ finish(code ?? 1);
31058
+ }
31059
+ });
31060
+ });
31061
+ }
31062
+ function registerMemoryBridge(cmd) {
31063
+ const bridge = cmd.command("bridge").description("Local bridge service (Miniflare-embedded) for the agent-memory engine");
31064
+ bridge.command("serve").description(
31065
+ "Run the agent-memory bridge as a foreground Node process (suitable for systemd/launchd)"
31066
+ ).option("--local", "Use the embedded Miniflare runtime (currently required)").option("--port <n>", "TCP port to bind on", (v) => parseInt(v, 10), DEFAULT_PORT2).option("--persist-to <path>", "DO SQLite persistence directory").action(async (opts) => {
31067
+ try {
31068
+ const rc = await runBridgeServe(opts);
31069
+ if (rc !== 0) process.exitCode = rc;
31070
+ } catch (err) {
31071
+ printError(err instanceof Error ? err.message : String(err));
31072
+ process.exitCode = 1;
31073
+ }
31074
+ });
31075
+ }
31076
+
31077
+ // src/commands/memory/reclassify.ts
31078
+ init_output();
31079
+ var DEFAULT_BRIDGE_URL = "https://olam-agent-memory.ernestcodes.workers.dev";
31080
+ var RECLASSIFY_TIMEOUT_MS = 1e4;
31081
+ function validateReclassifyOptions(opts) {
31082
+ const hasHash = typeof opts.contentHash === "string" && opts.contentHash.length > 0;
31083
+ const hasVersion = typeof opts.sinceVersion === "string" && opts.sinceVersion.length > 0;
31084
+ if (!hasHash && !hasVersion) {
31085
+ return "must specify one of --content-hash or --since-version";
31086
+ }
31087
+ if (hasHash && hasVersion) {
31088
+ return "--content-hash and --since-version are mutually exclusive";
31089
+ }
31090
+ if (hasHash && !/^sha256:[0-9a-f]{64}$/.test(opts.contentHash)) {
31091
+ return "--content-hash must be sha256:<64hex>";
31092
+ }
31093
+ return null;
31094
+ }
31095
+ function resolveBridgeUrl(opts) {
31096
+ return opts.bridgeUrl ?? process.env["AGENTMEMORY_BRIDGE_URL"] ?? DEFAULT_BRIDGE_URL;
31097
+ }
31098
+ function resolveBearer(opts) {
31099
+ const fromOpt = opts.bearer;
31100
+ if (fromOpt && fromOpt.length > 0) return fromOpt;
31101
+ const fromEnv = process.env["AGENTMEMORY_BRIDGE_SECRET"];
31102
+ if (fromEnv && fromEnv.length > 0) return fromEnv;
31103
+ return null;
31104
+ }
31105
+ async function runReclassify(opts, fetchImpl = fetch) {
31106
+ const validationError = validateReclassifyOptions(opts);
31107
+ if (validationError) {
31108
+ return { ok: false, busted: 0, mode: opts.contentHash ? "hash" : "version", error: validationError };
31109
+ }
31110
+ if (opts.dryRun) {
31111
+ return {
31112
+ ok: true,
31113
+ busted: 0,
31114
+ mode: "dry-run"
31115
+ };
31116
+ }
31117
+ const bridgeUrl = resolveBridgeUrl(opts);
31118
+ const bearer = resolveBearer(opts);
31119
+ if (!bearer) {
31120
+ return {
31121
+ ok: false,
31122
+ busted: 0,
31123
+ mode: opts.contentHash ? "hash" : "version",
31124
+ error: "AGENTMEMORY_BRIDGE_SECRET not set (or --bearer not passed)"
31125
+ };
31126
+ }
31127
+ const body = opts.contentHash ? { content_hash: opts.contentHash } : { since_version: opts.sinceVersion };
31128
+ try {
31129
+ const res = await fetchImpl(`${bridgeUrl}/classify/cache-bust`, {
31130
+ method: "POST",
31131
+ headers: {
31132
+ authorization: `Bearer ${bearer}`,
31133
+ "content-type": "application/json"
31134
+ },
31135
+ body: JSON.stringify(body),
31136
+ signal: AbortSignal.timeout(RECLASSIFY_TIMEOUT_MS)
31137
+ });
31138
+ if (!res.ok) {
31139
+ const detail = await res.text().catch(() => "");
31140
+ return {
31141
+ ok: false,
31142
+ busted: 0,
31143
+ mode: opts.contentHash ? "hash" : "version",
31144
+ error: `bridge returned ${res.status} ${res.statusText}${detail ? `: ${detail.slice(0, 200)}` : ""}`
31145
+ };
31146
+ }
31147
+ const payload = await res.json();
31148
+ return {
31149
+ ok: true,
31150
+ busted: payload.busted,
31151
+ mode: payload.mode,
31152
+ memoryId: payload.memoryId
31153
+ };
31154
+ } catch (err) {
31155
+ return {
31156
+ ok: false,
31157
+ busted: 0,
31158
+ mode: opts.contentHash ? "hash" : "version",
31159
+ error: err instanceof Error ? err.message : String(err)
31160
+ };
31161
+ }
31162
+ }
31163
+ async function runReclassifyCli(opts) {
31164
+ const result = await runReclassify(opts);
31165
+ if (opts.json) {
31166
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
31167
+ return result.ok ? 0 : 1;
31168
+ }
31169
+ printHeader("olam memory reclassify");
31170
+ if (opts.contentHash) {
31171
+ printInfo("content-hash", opts.contentHash);
31172
+ }
31173
+ if (opts.sinceVersion) {
31174
+ printInfo("since-version", opts.sinceVersion);
31175
+ }
31176
+ printInfo("mode", result.mode);
31177
+ if (opts.dryRun) {
31178
+ printInfo("dry-run", "no HTTP call made");
31179
+ return 0;
31180
+ }
31181
+ if (!result.ok) {
31182
+ printError(`reclassify failed: ${result.error ?? "(unknown error)"}`);
31183
+ return 1;
31184
+ }
31185
+ if (result.busted === 0) {
31186
+ printWarning(`no rows busted (hash not in cache OR no rows matched the version)`);
31187
+ } else {
31188
+ printInfo("busted", `${result.busted}`);
31189
+ if (result.memoryId) {
31190
+ printInfo("memory-id", result.memoryId);
31191
+ }
31192
+ }
31193
+ return 0;
31194
+ }
31195
+ function registerMemoryReclassify(cmd) {
31196
+ cmd.command("reclassify").description(
31197
+ "Bust the bridge classifier cache (single entry by content hash OR bulk by classifier_version) so the next batch re-runs the LLM"
31198
+ ).option("--content-hash <hash>", "Bust one entry by sha256:<64hex> content hash").option("--since-version <version>", 'Bulk bust by classifier_version (e.g. "haiku-v1+prompt-v1")').option("--dry-run", "Show what would be busted without calling the bridge", false).option("--bridge-url <url>", "Override AGENTMEMORY_BRIDGE_URL").option("--bearer <token>", "Override AGENTMEMORY_BRIDGE_SECRET").option("--json", "Machine-readable JSON output", false).action(async (opts) => {
31199
+ const rc = await runReclassifyCli(opts);
31200
+ if (rc !== 0) process.exitCode = rc;
31201
+ });
31202
+ }
31203
+
31204
+ // src/commands/memory/stats.ts
31205
+ init_output();
31206
+ var DEFAULT_BRIDGE_URL2 = "https://olam-agent-memory.ernestcodes.workers.dev";
31207
+ var STATS_TIMEOUT_MS = 1e4;
31208
+ function computeCacheErrorRate(counters) {
31209
+ if (counters.writes_observed <= 0) return 0;
31210
+ const busted = counters.cache_busted_hash + counters.cache_busted_version;
31211
+ return busted / counters.writes_observed;
31212
+ }
31213
+ function resolveBridgeUrl2(opts) {
31214
+ return opts.bridgeUrl ?? process.env["AGENTMEMORY_BRIDGE_URL"] ?? DEFAULT_BRIDGE_URL2;
31215
+ }
31216
+ function resolveBearer2(opts) {
31217
+ const fromOpt = opts.bearer;
31218
+ if (fromOpt && fromOpt.length > 0) return fromOpt;
31219
+ const fromEnv = process.env["AGENTMEMORY_BRIDGE_SECRET"];
31220
+ if (fromEnv && fromEnv.length > 0) return fromEnv;
31221
+ return null;
31222
+ }
31223
+ function intOrZero(v) {
31224
+ if (typeof v !== "number" || !Number.isFinite(v)) return 0;
31225
+ return Math.trunc(v);
31226
+ }
31227
+ function extractCounters(stats) {
31228
+ return {
31229
+ writes_observed: intOrZero(stats.classifier_writes_observed),
31230
+ writes_deduped_hash: intOrZero(stats.classifier_writes_deduped_hash),
31231
+ writes_deduped_semantic: intOrZero(stats.classifier_writes_deduped_semantic),
31232
+ recalls_observed: intOrZero(stats.classifier_recalls_observed),
31233
+ cache_busted_hash: intOrZero(stats.classifier_cache_busted_hash),
31234
+ cache_busted_version: intOrZero(stats.classifier_cache_busted_version)
31235
+ };
31236
+ }
31237
+ async function runStats(opts, fetchImpl = fetch) {
31238
+ const bridgeUrl = resolveBridgeUrl2(opts);
31239
+ const bearer = resolveBearer2(opts);
31240
+ if (!bearer) {
31241
+ return {
31242
+ ok: false,
31243
+ error: "AGENTMEMORY_BRIDGE_SECRET not set (or --bearer not passed)"
31244
+ };
31245
+ }
31246
+ try {
31247
+ const res = await fetchImpl(`${bridgeUrl}/bridge/stats`, {
31248
+ method: "GET",
31249
+ headers: {
31250
+ authorization: `Bearer ${bearer}`
31251
+ },
31252
+ signal: AbortSignal.timeout(STATS_TIMEOUT_MS)
31253
+ });
31254
+ if (!res.ok) {
31255
+ const detail = await res.text().catch(() => "");
31256
+ return {
31257
+ ok: false,
31258
+ error: `bridge returned ${res.status} ${res.statusText}${detail ? `: ${detail.slice(0, 200)}` : ""}`
31259
+ };
31260
+ }
31261
+ const payload = await res.json();
31262
+ if (!payload || typeof payload !== "object" || !payload.stats || typeof payload.stats !== "object") {
31263
+ return {
31264
+ ok: false,
31265
+ error: "bridge response missing stats envelope"
31266
+ };
31267
+ }
31268
+ const counters = extractCounters(payload.stats);
31269
+ const cacheErrorRate = computeCacheErrorRate(counters);
31270
+ return {
31271
+ ok: true,
31272
+ counters,
31273
+ cacheErrorRate,
31274
+ raw: payload
31275
+ };
31276
+ } catch (err) {
31277
+ return {
31278
+ ok: false,
31279
+ error: err instanceof Error ? err.message : String(err)
31280
+ };
31281
+ }
31282
+ }
31283
+ function formatPct(numerator, denominator) {
31284
+ if (denominator <= 0) return "0.0%";
31285
+ return `${(numerator / denominator * 100).toFixed(1)}%`;
31286
+ }
31287
+ async function runStatsCli(opts) {
31288
+ const result = await runStats(opts);
31289
+ if (opts.json) {
31290
+ const out = result.ok && result.raw !== void 0 ? result.raw : result;
31291
+ process.stdout.write(JSON.stringify(out, null, 2) + "\n");
31292
+ return result.ok ? 0 : 1;
31293
+ }
31294
+ printHeader("olam memory stats \u2014 bridge counters");
31295
+ if (!result.ok) {
31296
+ printError(`stats failed: ${result.error ?? "(unknown error)"}`);
31297
+ return 1;
31298
+ }
31299
+ const c = result.counters;
31300
+ printInfo("writes_observed", String(c.writes_observed));
31301
+ printInfo(
31302
+ "writes_deduped_hash",
31303
+ `${c.writes_deduped_hash} (${formatPct(c.writes_deduped_hash, c.writes_observed)})`
31304
+ );
31305
+ printInfo(
31306
+ "writes_deduped_semantic",
31307
+ `${c.writes_deduped_semantic} (${formatPct(c.writes_deduped_semantic, c.writes_observed)})`
31308
+ );
31309
+ printInfo("recalls_observed", String(c.recalls_observed));
31310
+ printInfo("cache_busted_hash", String(c.cache_busted_hash));
31311
+ printInfo("cache_busted_version", String(c.cache_busted_version));
31312
+ printInfo("cache_error_rate", `${(result.cacheErrorRate * 100).toFixed(1)}%`);
31313
+ return 0;
31314
+ }
31315
+ function registerMemoryStats(cmd) {
31316
+ cmd.command("stats").description(
31317
+ "Fetch /bridge/stats from the agent-memory bridge and format classifier counters (writes_observed, dedup rates, recalls, cache busts, derived cache_error_rate)"
31318
+ ).option("--bridge-url <url>", "Override AGENTMEMORY_BRIDGE_URL").option("--bearer <token>", "Override AGENTMEMORY_BRIDGE_SECRET").option("--json", "Machine-readable JSON output (raw bridge envelope)", false).action(async (opts) => {
31319
+ const rc = await runStatsCli(opts);
31320
+ if (rc !== 0) process.exitCode = rc;
31321
+ });
31322
+ }
31323
+
29774
31324
  // src/commands/memory/index.ts
29775
31325
  function registerMemory(program2) {
29776
31326
  const memory = program2.command("memory").description(
29777
- "Host-process agent-memory service for the olam fleet (start, stop, status, logs, secret, install, uninstall)"
31327
+ "Host-process agent-memory service for the olam fleet (start, stop, status, logs, secret, install, uninstall, bridge, reclassify, stats)"
29778
31328
  );
29779
31329
  registerMemoryStart(memory);
29780
31330
  registerMemoryStop(memory);
@@ -29784,14 +31334,17 @@ function registerMemory(program2) {
29784
31334
  registerMemoryInstall(memory);
29785
31335
  registerMemoryUninstall(memory);
29786
31336
  registerMemoryMode(memory);
31337
+ registerMemoryBridge(memory);
31338
+ registerMemoryReclassify(memory);
31339
+ registerMemoryStats(memory);
29787
31340
  }
29788
31341
 
29789
31342
  // src/commands/kg-build.ts
29790
31343
  init_storage_paths();
29791
31344
  init_workspace_name();
29792
- import * as fs53 from "node:fs";
29793
- import * as os30 from "node:os";
29794
- import * as path57 from "node:path";
31345
+ import * as fs59 from "node:fs";
31346
+ import * as os34 from "node:os";
31347
+ import * as path63 from "node:path";
29795
31348
 
29796
31349
  // ../core/dist/kg/kg-service-client.js
29797
31350
  var KG_SERVICE_PORT_DEFAULT = 9997;
@@ -29802,8 +31355,8 @@ function port() {
29802
31355
  const n = Number.parseInt(env, 10);
29803
31356
  return Number.isFinite(n) && n > 0 ? n : KG_SERVICE_PORT_DEFAULT;
29804
31357
  }
29805
- function url(path60) {
29806
- return `http://127.0.0.1:${port()}${path60}`;
31358
+ function url(path66) {
31359
+ return `http://127.0.0.1:${port()}${path66}`;
29807
31360
  }
29808
31361
  function kgServiceHealthUrl() {
29809
31362
  return url("/health");
@@ -29881,40 +31434,40 @@ init_output();
29881
31434
  // src/commands/kg-status.ts
29882
31435
  init_storage_paths();
29883
31436
  init_workspace_name();
29884
- import fs48 from "node:fs";
29885
- import { homedir as homedir31 } from "node:os";
29886
- import path52 from "node:path";
31437
+ import fs54 from "node:fs";
31438
+ import { homedir as homedir35 } from "node:os";
31439
+ import path58 from "node:path";
29887
31440
  init_output();
29888
31441
  function olamHome4() {
29889
- return process.env.OLAM_HOME ?? path52.join(homedir31(), ".olam");
31442
+ return process.env.OLAM_HOME ?? path58.join(homedir35(), ".olam");
29890
31443
  }
29891
31444
  function kgRoot2() {
29892
- return path52.join(olamHome4(), "kg");
31445
+ return path58.join(olamHome4(), "kg");
29893
31446
  }
29894
31447
  function worldsRoot2() {
29895
- return path52.join(olamHome4(), "worlds");
31448
+ return path58.join(olamHome4(), "worlds");
29896
31449
  }
29897
31450
  function dirSizeBytes2(dir) {
29898
- if (!fs48.existsSync(dir)) return 0;
31451
+ if (!fs54.existsSync(dir)) return 0;
29899
31452
  let total = 0;
29900
31453
  const stack = [dir];
29901
31454
  while (stack.length > 0) {
29902
31455
  const cur = stack.pop();
29903
31456
  let entries;
29904
31457
  try {
29905
- entries = fs48.readdirSync(cur, { withFileTypes: true });
31458
+ entries = fs54.readdirSync(cur, { withFileTypes: true });
29906
31459
  } catch {
29907
31460
  continue;
29908
31461
  }
29909
31462
  for (const entry of entries) {
29910
- const full = path52.join(cur, entry.name);
31463
+ const full = path58.join(cur, entry.name);
29911
31464
  if (entry.isSymbolicLink()) continue;
29912
31465
  if (entry.isDirectory()) {
29913
31466
  stack.push(full);
29914
31467
  continue;
29915
31468
  }
29916
31469
  try {
29917
- total += fs48.statSync(full).size;
31470
+ total += fs54.statSync(full).size;
29918
31471
  } catch {
29919
31472
  }
29920
31473
  }
@@ -29928,10 +31481,10 @@ function formatBytes5(n) {
29928
31481
  return `${(n / 1024 / 1024 / 1024).toFixed(2)} GB`;
29929
31482
  }
29930
31483
  function readFreshness(workspace) {
29931
- const file = path52.join(kgPristinePath(workspace), "freshness.json");
29932
- if (!fs48.existsSync(file)) return null;
31484
+ const file = path58.join(kgPristinePath(workspace), "freshness.json");
31485
+ if (!fs54.existsSync(file)) return null;
29933
31486
  try {
29934
- const raw = JSON.parse(fs48.readFileSync(file, "utf-8"));
31487
+ const raw = JSON.parse(fs54.readFileSync(file, "utf-8"));
29935
31488
  if (raw && typeof raw === "object") return raw;
29936
31489
  return null;
29937
31490
  } catch {
@@ -29939,10 +31492,10 @@ function readFreshness(workspace) {
29939
31492
  }
29940
31493
  }
29941
31494
  function readOverlayNodeCount(graphifyOutDir) {
29942
- const graphPath = path52.join(graphifyOutDir, "graph.json");
29943
- if (!fs48.existsSync(graphPath)) return null;
31495
+ const graphPath = path58.join(graphifyOutDir, "graph.json");
31496
+ if (!fs54.existsSync(graphPath)) return null;
29944
31497
  try {
29945
- const raw = JSON.parse(fs48.readFileSync(graphPath, "utf-8"));
31498
+ const raw = JSON.parse(fs54.readFileSync(graphPath, "utf-8"));
29946
31499
  if (raw && typeof raw === "object") {
29947
31500
  const nodes = raw.nodes;
29948
31501
  if (Array.isArray(nodes)) return nodes.length;
@@ -29954,28 +31507,28 @@ function readOverlayNodeCount(graphifyOutDir) {
29954
31507
  }
29955
31508
  function listOverlays() {
29956
31509
  const root = worldsRoot2();
29957
- if (!fs48.existsSync(root)) return [];
31510
+ if (!fs54.existsSync(root)) return [];
29958
31511
  const records = [];
29959
31512
  let worldDirs;
29960
31513
  try {
29961
- worldDirs = fs48.readdirSync(root, { withFileTypes: true });
31514
+ worldDirs = fs54.readdirSync(root, { withFileTypes: true });
29962
31515
  } catch {
29963
31516
  return [];
29964
31517
  }
29965
31518
  for (const worldEntry of worldDirs) {
29966
31519
  if (!worldEntry.isDirectory()) continue;
29967
31520
  const worldId = worldEntry.name;
29968
- const worldDir = path52.join(root, worldId);
31521
+ const worldDir = path58.join(root, worldId);
29969
31522
  let cloneDirs;
29970
31523
  try {
29971
- cloneDirs = fs48.readdirSync(worldDir, { withFileTypes: true });
31524
+ cloneDirs = fs54.readdirSync(worldDir, { withFileTypes: true });
29972
31525
  } catch {
29973
31526
  continue;
29974
31527
  }
29975
31528
  for (const cloneEntry of cloneDirs) {
29976
31529
  if (!cloneEntry.isDirectory()) continue;
29977
- const graphifyOut = path52.join(worldDir, cloneEntry.name, "graphify-out");
29978
- if (!fs48.existsSync(graphifyOut)) continue;
31530
+ const graphifyOut = path58.join(worldDir, cloneEntry.name, "graphify-out");
31531
+ if (!fs54.existsSync(graphifyOut)) continue;
29979
31532
  records.push({
29980
31533
  world_id: worldId,
29981
31534
  clone_dir: cloneEntry.name,
@@ -29989,11 +31542,11 @@ function listOverlays() {
29989
31542
  }
29990
31543
  function listPristines(overlays) {
29991
31544
  const root = kgRoot2();
29992
- if (!fs48.existsSync(root)) return [];
31545
+ if (!fs54.existsSync(root)) return [];
29993
31546
  const records = [];
29994
31547
  let entries;
29995
31548
  try {
29996
- entries = fs48.readdirSync(root, { withFileTypes: true });
31549
+ entries = fs54.readdirSync(root, { withFileTypes: true });
29997
31550
  } catch {
29998
31551
  return [];
29999
31552
  }
@@ -30006,7 +31559,7 @@ function listPristines(overlays) {
30006
31559
  continue;
30007
31560
  }
30008
31561
  const fresh = readFreshness(workspace);
30009
- const graphifyOut = path52.join(kgPristinePath(workspace), "graphify-out");
31562
+ const graphifyOut = path58.join(kgPristinePath(workspace), "graphify-out");
30010
31563
  const size = dirSizeBytes2(graphifyOut);
30011
31564
  const worldCount = overlays.filter((o) => o.clone_dir === workspace).length;
30012
31565
  records.push({
@@ -30141,11 +31694,11 @@ function registerKgStatusCommand(kg) {
30141
31694
  init_storage_paths();
30142
31695
  init_workspace_name();
30143
31696
  init_output();
30144
- import { spawn as spawn10 } from "node:child_process";
30145
- import fs49 from "node:fs";
30146
- import path53 from "node:path";
31697
+ import { spawn as spawn11 } from "node:child_process";
31698
+ import fs55 from "node:fs";
31699
+ import path59 from "node:path";
30147
31700
  function pidFilePath(workspace) {
30148
- return path53.join(kgPristinePath(workspace), ".watch.pid");
31701
+ return path59.join(kgPristinePath(workspace), ".watch.pid");
30149
31702
  }
30150
31703
  function isPidAlive3(pid) {
30151
31704
  if (!Number.isInteger(pid) || pid <= 0) return false;
@@ -30160,39 +31713,39 @@ function isPidAlive3(pid) {
30160
31713
  }
30161
31714
  function readAndClassifyPid(workspace) {
30162
31715
  const file = pidFilePath(workspace);
30163
- if (!fs49.existsSync(file)) return { status: "no-pidfile", pid: null };
31716
+ if (!fs55.existsSync(file)) return { status: "no-pidfile", pid: null };
30164
31717
  let pid;
30165
31718
  try {
30166
- const raw = fs49.readFileSync(file, "utf-8").trim();
31719
+ const raw = fs55.readFileSync(file, "utf-8").trim();
30167
31720
  pid = Number.parseInt(raw, 10);
30168
31721
  } catch {
30169
- fs49.rmSync(file, { force: true });
31722
+ fs55.rmSync(file, { force: true });
30170
31723
  return { status: "stale-reclaimed", pid: null };
30171
31724
  }
30172
31725
  if (!Number.isInteger(pid) || pid <= 0) {
30173
- fs49.rmSync(file, { force: true });
31726
+ fs55.rmSync(file, { force: true });
30174
31727
  return { status: "stale-reclaimed", pid: null };
30175
31728
  }
30176
31729
  if (isPidAlive3(pid)) return { status: "active", pid };
30177
- fs49.rmSync(file, { force: true });
31730
+ fs55.rmSync(file, { force: true });
30178
31731
  return { status: "stale-reclaimed", pid: null };
30179
31732
  }
30180
31733
  function writePidFile(workspace, pid) {
30181
31734
  const file = pidFilePath(workspace);
30182
- const dir = path53.dirname(file);
30183
- fs49.mkdirSync(dir, { recursive: true });
30184
- fs49.writeFileSync(file, String(pid), { encoding: "utf-8" });
31735
+ const dir = path59.dirname(file);
31736
+ fs55.mkdirSync(dir, { recursive: true });
31737
+ fs55.writeFileSync(file, String(pid), { encoding: "utf-8" });
30185
31738
  }
30186
31739
  function removePidFile(workspace) {
30187
31740
  const file = pidFilePath(workspace);
30188
31741
  try {
30189
- fs49.rmSync(file, { force: true });
31742
+ fs55.rmSync(file, { force: true });
30190
31743
  } catch {
30191
31744
  }
30192
31745
  }
30193
31746
  async function runKgWatch(workspaceArg, opts, deps = {}) {
30194
31747
  const cwd = deps.cwd ?? opts.cwd ?? process.cwd();
30195
- const name = workspaceArg ?? path53.basename(cwd).toLowerCase();
31748
+ const name = workspaceArg ?? path59.basename(cwd).toLowerCase();
30196
31749
  try {
30197
31750
  validateWorkspaceName(name);
30198
31751
  } catch (err) {
@@ -30200,7 +31753,7 @@ async function runKgWatch(workspaceArg, opts, deps = {}) {
30200
31753
  return { exitCode: 1, pidWritten: false };
30201
31754
  }
30202
31755
  const pristinePath = kgPristinePath(name);
30203
- const graphPath = path53.join(pristinePath, "graphify-out", "graph.json");
31756
+ const graphPath = path59.join(pristinePath, "graphify-out", "graph.json");
30204
31757
  const pidState = readAndClassifyPid(name);
30205
31758
  if (pidState.status === "active") {
30206
31759
  printError(
@@ -30211,7 +31764,7 @@ async function runKgWatch(workspaceArg, opts, deps = {}) {
30211
31764
  if (pidState.status === "stale-reclaimed") {
30212
31765
  printInfo("stale-pid", `reclaimed dead PID file at ${pidFilePath(name)}`);
30213
31766
  }
30214
- const spawnFn = deps.spawnImpl ?? spawn10;
31767
+ const spawnFn = deps.spawnImpl ?? spawn11;
30215
31768
  const child = spawnFn(
30216
31769
  "graphify",
30217
31770
  [cwd, "--watch", "--update", "--graph", graphPath],
@@ -30263,7 +31816,7 @@ function registerKgWatchCommand(kg) {
30263
31816
  }
30264
31817
 
30265
31818
  // src/commands/kg-classify.ts
30266
- import pc31 from "picocolors";
31819
+ import pc33 from "picocolors";
30267
31820
  init_output();
30268
31821
  function registerKgClassifyCommand(kg) {
30269
31822
  kg.command("classify <question>").description("Route a question to kg | grep | both via the 4-layer classifier (kg-service required)").option("--workspace <name>", "Scope the L2 probe gate to a specific workspace graph").option("--json", "Emit raw JSON instead of the formatted summary").action(async (question, opts) => {
@@ -30273,7 +31826,7 @@ function registerKgClassifyCommand(kg) {
30273
31826
  process.stdout.write(JSON.stringify(result, null, 2) + "\n");
30274
31827
  return;
30275
31828
  }
30276
- const routeStr = result.route === "kg" ? pc31.cyan("kg") : result.route === "grep" ? pc31.magenta("grep") : pc31.yellow("both");
31829
+ const routeStr = result.route === "kg" ? pc33.cyan("kg") : result.route === "grep" ? pc33.magenta("grep") : pc33.yellow("both");
30277
31830
  printInfo("route", `${routeStr} (layer ${result.layer}, took ${result.took_ms}ms)`);
30278
31831
  printInfo("confidence", String(result.confidence));
30279
31832
  printInfo("reason", result.reason);
@@ -30295,7 +31848,7 @@ function registerKgClassifyCommand(kg) {
30295
31848
  }
30296
31849
 
30297
31850
  // src/commands/kg-doctor.ts
30298
- import pc32 from "picocolors";
31851
+ import pc34 from "picocolors";
30299
31852
  init_output();
30300
31853
  async function runProbes() {
30301
31854
  const results = [];
@@ -30414,12 +31967,12 @@ function registerKgDoctorCommand(kg) {
30414
31967
  } else {
30415
31968
  printHeader("kg-service doctor");
30416
31969
  for (const p of probes) {
30417
- const badge = p.status === "ok" ? pc32.green("\u2713") : p.status === "warn" ? pc32.yellow("\u2298") : pc32.red("\u2717");
31970
+ const badge = p.status === "ok" ? pc34.green("\u2713") : p.status === "warn" ? pc34.yellow("\u2298") : pc34.red("\u2717");
30418
31971
  const label = `${badge} ${p.name}`;
30419
31972
  const detail = p.detail ?? "";
30420
31973
  printInfo(label, detail);
30421
31974
  if (p.remedy) {
30422
- process.stderr.write(` ${pc32.dim("remedy:")} ${p.remedy}
31975
+ process.stderr.write(` ${pc34.dim("remedy:")} ${p.remedy}
30423
31976
  `);
30424
31977
  }
30425
31978
  }
@@ -30437,14 +31990,14 @@ function registerKgDoctorCommand(kg) {
30437
31990
  }
30438
31991
 
30439
31992
  // src/commands/kg-install-hook.ts
30440
- import * as fs51 from "node:fs";
30441
- import * as path55 from "node:path";
30442
- import * as os28 from "node:os";
31993
+ import * as fs57 from "node:fs";
31994
+ import * as path61 from "node:path";
31995
+ import * as os32 from "node:os";
30443
31996
 
30444
31997
  // ../core/dist/world/merge-settings.js
30445
- import * as fs50 from "node:fs";
30446
- import * as path54 from "node:path";
30447
- import * as crypto7 from "node:crypto";
31998
+ import * as fs56 from "node:fs";
31999
+ import * as path60 from "node:path";
32000
+ import * as crypto8 from "node:crypto";
30448
32001
  function mergeHomeSettingsJson(filePath, options) {
30449
32002
  let settings;
30450
32003
  try {
@@ -30503,10 +32056,10 @@ function mergeHomeSettingsJson(filePath, options) {
30503
32056
  return { status: "installed", message: `settings.json updated at ${filePath}` };
30504
32057
  }
30505
32058
  function readSettings(filePath) {
30506
- if (!fs50.existsSync(filePath)) {
32059
+ if (!fs56.existsSync(filePath)) {
30507
32060
  return {};
30508
32061
  }
30509
- const raw = fs50.readFileSync(filePath, "utf-8");
32062
+ const raw = fs56.readFileSync(filePath, "utf-8");
30510
32063
  if (!raw.trim())
30511
32064
  return {};
30512
32065
  return JSON.parse(raw);
@@ -30527,13 +32080,13 @@ function isHookSentinelPresent(matchers, sentinel) {
30527
32080
  return false;
30528
32081
  }
30529
32082
  function atomicWriteJson(filePath, data) {
30530
- const dir = path54.dirname(filePath);
30531
- fs50.mkdirSync(dir, { recursive: true });
30532
- const rand = crypto7.randomBytes(6).toString("hex");
32083
+ const dir = path60.dirname(filePath);
32084
+ fs56.mkdirSync(dir, { recursive: true });
32085
+ const rand = crypto8.randomBytes(6).toString("hex");
30533
32086
  const tmp = `${filePath}.tmp.${process.pid}.${rand}`;
30534
32087
  const json = JSON.stringify(data, null, 2) + "\n";
30535
- fs50.writeFileSync(tmp, json, { mode: 420 });
30536
- fs50.renameSync(tmp, filePath);
32088
+ fs56.writeFileSync(tmp, json, { mode: 420 });
32089
+ fs56.renameSync(tmp, filePath);
30537
32090
  }
30538
32091
 
30539
32092
  // ../core/dist/kg/hook-template.js
@@ -30560,11 +32113,11 @@ try:
30560
32113
  sys.stderr.write(f"\\x1b[32m\\u2713 KG hit\\x1b[0m \\"{q}\\" \\u2192 L{layer}/{route} \\u00b7 {nodes} nodes \\u00b7 ~{saved_k}k tokens saved\\n")
30561
32114
  print(json.dumps({"hookSpecificOutput":{"hookEventName":"PreToolUse","additionalContext":f"[kg-classifier L{layer}|{route}] {label[:160]}"}}))
30562
32115
  except Exception: pass' 2>/dev/null`;
32116
+ const curlPost = `RESP=$(curl -s --max-time 1 -X POST -H 'Content-Type: application/json' -d "{\\"q\\":\\"$(echo \\"$CMD\\" | head -c 200 | tr '\\"' ' ')\\"}" ${url2} 2>/dev/null)`;
30563
32117
  return [
30564
32118
  `KG_SENTINEL=${KG_HOOK_SENTINEL}`,
30565
32119
  `CMD=$(${extractCmd})`,
30566
- `case "$CMD" in *grep*|*rg\\ *|*ripgrep*|*find\\ *|*fd\\ *|*ack\\ *|*ag\\ *)`,
30567
- `RESP=$(curl -s --max-time 1 -X POST -H 'Content-Type: application/json' -d "{\\"q\\":\\"$(echo \\"$CMD\\" | head -c 200 | tr '\\"' ' ')\\"}" ${url2} 2>/dev/null)`,
32120
+ `case "$CMD" in *grep*|*rg\\ *|*ripgrep*|*find\\ *|*fd\\ *|*ack\\ *|*ag\\ *) ${curlPost}`,
30568
32121
  `KG_QUERY="$(echo \\"$CMD\\" | head -c 60 | tr '\\"' ' ')" echo "$RESP" | ${emitContext}`,
30569
32122
  `;; esac`
30570
32123
  ].join("; ");
@@ -30585,15 +32138,15 @@ function buildHookMatcherEntry(opts) {
30585
32138
  init_output();
30586
32139
  function settingsPathFor(scope) {
30587
32140
  if (scope === "user") {
30588
- return path55.join(os28.homedir(), ".claude", "settings.json");
32141
+ return path61.join(os32.homedir(), ".claude", "settings.json");
30589
32142
  }
30590
- return path55.join(process.cwd(), ".claude", "settings.json");
32143
+ return path61.join(process.cwd(), ".claude", "settings.json");
30591
32144
  }
30592
32145
  function backup(filePath) {
30593
- if (!fs51.existsSync(filePath)) return null;
32146
+ if (!fs57.existsSync(filePath)) return null;
30594
32147
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
30595
32148
  const backupPath = `${filePath}.olam-bak.${ts}`;
30596
- fs51.copyFileSync(filePath, backupPath);
32149
+ fs57.copyFileSync(filePath, backupPath);
30597
32150
  return backupPath;
30598
32151
  }
30599
32152
  function registerKgInstallHookCommand(kg) {
@@ -30601,9 +32154,9 @@ function registerKgInstallHookCommand(kg) {
30601
32154
  const scope = opts.scope === "user" ? "user" : "project";
30602
32155
  const filePath = settingsPathFor(scope);
30603
32156
  try {
30604
- fs51.mkdirSync(path55.dirname(filePath), { recursive: true });
32157
+ fs57.mkdirSync(path61.dirname(filePath), { recursive: true });
30605
32158
  } catch (err) {
30606
- printError(`could not create ${path55.dirname(filePath)}: ${err instanceof Error ? err.message : String(err)}`);
32159
+ printError(`could not create ${path61.dirname(filePath)}: ${err instanceof Error ? err.message : String(err)}`);
30607
32160
  process.exitCode = 1;
30608
32161
  return;
30609
32162
  }
@@ -30627,7 +32180,7 @@ function registerKgInstallHookCommand(kg) {
30627
32180
  printInfo("kg-service hook", `already installed at ${filePath}`);
30628
32181
  if (backupPath) {
30629
32182
  try {
30630
- fs51.unlinkSync(backupPath);
32183
+ fs57.unlinkSync(backupPath);
30631
32184
  } catch {
30632
32185
  }
30633
32186
  }
@@ -30645,15 +32198,15 @@ function registerKgInstallHookCommand(kg) {
30645
32198
  }
30646
32199
 
30647
32200
  // src/commands/kg-uninstall-hook.ts
30648
- import * as fs52 from "node:fs";
30649
- import * as path56 from "node:path";
30650
- import * as os29 from "node:os";
32201
+ import * as fs58 from "node:fs";
32202
+ import * as path62 from "node:path";
32203
+ import * as os33 from "node:os";
30651
32204
  init_output();
30652
32205
  function settingsPathFor2(scope) {
30653
32206
  if (scope === "user") {
30654
- return path56.join(os29.homedir(), ".claude", "settings.json");
32207
+ return path62.join(os33.homedir(), ".claude", "settings.json");
30655
32208
  }
30656
- return path56.join(process.cwd(), ".claude", "settings.json");
32209
+ return path62.join(process.cwd(), ".claude", "settings.json");
30657
32210
  }
30658
32211
  function dropSentinel(matchers) {
30659
32212
  let changed = false;
@@ -30683,13 +32236,13 @@ function registerKgUninstallHookCommand(kg) {
30683
32236
  kg.command("uninstall-hook").description("Remove kg-service PreToolUse hook from .claude/settings.json (sentinel-matched; surgical)").option("--scope <scope>", "project (default) or user", "project").action((opts) => {
30684
32237
  const scope = opts.scope === "user" ? "user" : "project";
30685
32238
  const filePath = settingsPathFor2(scope);
30686
- if (!fs52.existsSync(filePath)) {
32239
+ if (!fs58.existsSync(filePath)) {
30687
32240
  printInfo("kg-service hook", `no settings.json at ${filePath} \u2014 nothing to remove`);
30688
32241
  return;
30689
32242
  }
30690
32243
  let settings;
30691
32244
  try {
30692
- const raw = fs52.readFileSync(filePath, "utf-8");
32245
+ const raw = fs58.readFileSync(filePath, "utf-8");
30693
32246
  settings = raw.trim() ? JSON.parse(raw) : {};
30694
32247
  } catch (err) {
30695
32248
  printError(`could not parse ${filePath}: ${err instanceof Error ? err.message : String(err)}`);
@@ -30709,7 +32262,7 @@ function registerKgUninstallHookCommand(kg) {
30709
32262
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
30710
32263
  const backupPath = `${filePath}.olam-bak.${ts}`;
30711
32264
  try {
30712
- fs52.copyFileSync(filePath, backupPath);
32265
+ fs58.copyFileSync(filePath, backupPath);
30713
32266
  } catch {
30714
32267
  }
30715
32268
  const next = {
@@ -30728,7 +32281,7 @@ function registerKgUninstallHookCommand(kg) {
30728
32281
  }
30729
32282
  }
30730
32283
  try {
30731
- fs52.writeFileSync(filePath, JSON.stringify(next, null, 2) + "\n");
32284
+ fs58.writeFileSync(filePath, JSON.stringify(next, null, 2) + "\n");
30732
32285
  printSuccess(`kg-service hook removed from ${filePath}`);
30733
32286
  printInfo("backup", backupPath);
30734
32287
  } catch (err) {
@@ -30802,20 +32355,20 @@ function registerKgSavingsCommand(kg) {
30802
32355
  // src/commands/kg-build.ts
30803
32356
  function resolveWorkspace(arg) {
30804
32357
  const cwd = process.cwd();
30805
- const name = arg ?? path57.basename(cwd).toLowerCase();
32358
+ const name = arg ?? path63.basename(cwd).toLowerCase();
30806
32359
  validateWorkspaceName(name);
30807
32360
  return { name, sourcePath: cwd };
30808
32361
  }
30809
32362
  function toContainerPath(hostPath) {
30810
- const home = os30.homedir();
30811
- const resolved = path57.resolve(hostPath);
30812
- if (!resolved.startsWith(home + path57.sep) && resolved !== home) {
32363
+ const home = os34.homedir();
32364
+ const resolved = path63.resolve(hostPath);
32365
+ if (!resolved.startsWith(home + path63.sep) && resolved !== home) {
30813
32366
  throw new Error(
30814
32367
  `source path "${resolved}" is not under $HOME (${home}). kg-service can only build repos that live under your home dir (it bind-mounts $HOME:/host-home:ro at start). Move the repo or set OLAM_HOME if you need a different root.`
30815
32368
  );
30816
32369
  }
30817
- const rel = path57.relative(home, resolved);
30818
- return rel === "" ? "/host-home" : path57.posix.join("/host-home", rel.split(path57.sep).join("/"));
32370
+ const rel = path63.relative(home, resolved);
32371
+ return rel === "" ? "/host-home" : path63.posix.join("/host-home", rel.split(path63.sep).join("/"));
30819
32372
  }
30820
32373
  async function runKgBuild(workspaceArg, options = {}) {
30821
32374
  let workspace;
@@ -30833,7 +32386,7 @@ async function runKgBuild(workspaceArg, options = {}) {
30833
32386
  return { exitCode: 2 };
30834
32387
  }
30835
32388
  const outDir = kgPristinePath(workspace.name);
30836
- fs53.mkdirSync(outDir, { recursive: true });
32389
+ fs59.mkdirSync(outDir, { recursive: true });
30837
32390
  const human = !options.json;
30838
32391
  if (human) {
30839
32392
  printInfo("kg build", `workspace=${workspace.name} source=${workspace.sourcePath}`);
@@ -30866,12 +32419,12 @@ async function runKgBuild(workspaceArg, options = {}) {
30866
32419
  workspace: workspace.name,
30867
32420
  graphify_path: "container"
30868
32421
  };
30869
- fs53.writeFileSync(
30870
- path57.join(outDir, "freshness.json"),
32422
+ fs59.writeFileSync(
32423
+ path63.join(outDir, "freshness.json"),
30871
32424
  JSON.stringify(freshness, null, 2) + "\n",
30872
32425
  "utf-8"
30873
32426
  );
30874
- const finalOut = path57.join(outDir, "graphify-out");
32427
+ const finalOut = path63.join(outDir, "graphify-out");
30875
32428
  if (options.json) {
30876
32429
  process.stdout.write(JSON.stringify(freshness) + "\n");
30877
32430
  } else {
@@ -31148,17 +32701,17 @@ init_manager();
31148
32701
  init_context();
31149
32702
  init_output();
31150
32703
  import { spawnSync as defaultSpawnSync } from "node:child_process";
31151
- import * as fs54 from "node:fs";
31152
- import * as os31 from "node:os";
31153
- import * as path58 from "node:path";
32704
+ import * as fs60 from "node:fs";
32705
+ import * as os35 from "node:os";
32706
+ import * as path64 from "node:path";
31154
32707
  function devboxContainerName(worldId) {
31155
32708
  return `olam-${worldId}-devbox`;
31156
32709
  }
31157
32710
  function olamHomeDir() {
31158
- return process.env["OLAM_HOME"] ?? path58.join(os31.homedir(), ".olam");
32711
+ return process.env["OLAM_HOME"] ?? path64.join(os35.homedir(), ".olam");
31159
32712
  }
31160
- function defaultRestartContainer(name, spawn11 = defaultSpawnSync) {
31161
- const r = spawn11("docker", ["restart", "--time", "30", name], {
32713
+ function defaultRestartContainer(name, spawn12 = defaultSpawnSync) {
32714
+ const r = spawn12("docker", ["restart", "--time", "30", name], {
31162
32715
  encoding: "utf-8",
31163
32716
  stdio: ["ignore", "pipe", "pipe"]
31164
32717
  });
@@ -31168,8 +32721,8 @@ function defaultRestartContainer(name, spawn11 = defaultSpawnSync) {
31168
32721
  };
31169
32722
  }
31170
32723
  function defaultAppendAuditLog(homeDir, line) {
31171
- fs54.mkdirSync(homeDir, { recursive: true });
31172
- fs54.appendFileSync(path58.join(homeDir, "usage.log"), line.endsWith("\n") ? line : line + "\n", {
32724
+ fs60.mkdirSync(homeDir, { recursive: true });
32725
+ fs60.appendFileSync(path64.join(homeDir, "usage.log"), line.endsWith("\n") ? line : line + "\n", {
31173
32726
  encoding: "utf-8"
31174
32727
  });
31175
32728
  }
@@ -31214,19 +32767,19 @@ async function doRekey(worldId, deps) {
31214
32767
  );
31215
32768
  const rotatedAt = deps.now().toISOString();
31216
32769
  const homeDir = deps.olamHomeDir();
31217
- const worldDir = path58.join(homeDir, "worlds", worldId);
31218
- fs54.mkdirSync(worldDir, { recursive: true });
31219
- const credentialsPath = path58.join(worldDir, "credentials.json");
32770
+ const worldDir = path64.join(homeDir, "worlds", worldId);
32771
+ fs60.mkdirSync(worldDir, { recursive: true });
32772
+ const credentialsPath = path64.join(worldDir, "credentials.json");
31220
32773
  const payload = {
31221
32774
  worldRoleName,
31222
32775
  password,
31223
32776
  rotatedAt
31224
32777
  };
31225
- fs54.writeFileSync(credentialsPath, JSON.stringify(payload, null, 2) + "\n", {
32778
+ fs60.writeFileSync(credentialsPath, JSON.stringify(payload, null, 2) + "\n", {
31226
32779
  encoding: "utf-8",
31227
32780
  mode: 384
31228
32781
  });
31229
- fs54.chmodSync(credentialsPath, 384);
32782
+ fs60.chmodSync(credentialsPath, 384);
31230
32783
  const restart = deps.restartContainer(devboxContainerName(worldId));
31231
32784
  deps.appendAuditLog(`${rotatedAt} ${worldId} rekey`);
31232
32785
  if (!restart.ok) {
@@ -31290,18 +32843,18 @@ function registerRekey(program2) {
31290
32843
  }
31291
32844
 
31292
32845
  // src/pleri-config.ts
31293
- import * as fs55 from "node:fs";
31294
- import * as path59 from "node:path";
32846
+ import * as fs61 from "node:fs";
32847
+ import * as path65 from "node:path";
31295
32848
  function isPleriConfigured(configDir = process.env.OLAM_CONFIG_DIR ?? ".olam") {
31296
32849
  if (process.env.PLERI_BASE_URL) {
31297
32850
  return true;
31298
32851
  }
31299
- const configPath = path59.join(configDir, "config.yaml");
31300
- if (!fs55.existsSync(configPath)) {
32852
+ const configPath = path65.join(configDir, "config.yaml");
32853
+ if (!fs61.existsSync(configPath)) {
31301
32854
  return false;
31302
32855
  }
31303
32856
  try {
31304
- const contents = fs55.readFileSync(configPath, "utf8");
32857
+ const contents = fs61.readFileSync(configPath, "utf8");
31305
32858
  return /^[^#\n]*\bpleri:/m.test(contents);
31306
32859
  } catch {
31307
32860
  return false;
@@ -31366,4 +32919,6 @@ registerKg(program);
31366
32919
  registerConfig(program);
31367
32920
  registerRepos(program);
31368
32921
  registerRunbooks(program);
32922
+ registerSkillsSource(program);
32923
+ registerSkills(program);
31369
32924
  program.parse();