@pleri/olam-cli 0.1.144 → 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 (48) 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/image-digests.json +7 -7
  38. package/dist/index.js +2027 -529
  39. package/dist/index.js.map +1 -1
  40. package/dist/lib/health-probes.d.ts +72 -0
  41. package/dist/lib/health-probes.d.ts.map +1 -1
  42. package/dist/lib/health-probes.js +218 -0
  43. package/dist/lib/health-probes.js.map +1 -1
  44. package/dist/mcp-server.js +1246 -351
  45. package/host-cp/src/agent-runtime-trigger.mjs +74 -4
  46. package/host-cp/src/engine-identity.mjs +32 -0
  47. package/host-cp/src/server.mjs +188 -3
  48. 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
@@ -25778,7 +25887,7 @@ function registerUpgrade(program2) {
25778
25887
  init_host_cp();
25779
25888
  init_context();
25780
25889
  init_output();
25781
- import * as http3 from "node:http";
25890
+ import * as http4 from "node:http";
25782
25891
  import pc19 from "picocolors";
25783
25892
  var HOST_CP_PORT3 = 19e3;
25784
25893
  function colorLine(line) {
@@ -25787,25 +25896,16 @@ function colorLine(line) {
25787
25896
  if (/\bINFO\b/.test(line)) return pc19.dim(line);
25788
25897
  return line;
25789
25898
  }
25790
- function formatLine(line, service, showService) {
25791
- const prefix = showService && service ? `${pc19.cyan(`[${service}]`)} ` : "";
25792
- return prefix + colorLine(line);
25793
- }
25794
- function parseSseEvent(raw) {
25795
- if (!raw.startsWith("data: ")) return null;
25796
- try {
25797
- const parsed = JSON.parse(raw.slice(6));
25798
- if (!parsed || typeof parsed !== "object") return null;
25799
- const ev = parsed;
25800
- if (ev.type === "replay" && Array.isArray(ev.lines)) return ev;
25801
- if (ev.type === "line" && typeof ev.line === "string") return ev;
25802
- return null;
25803
- } catch {
25804
- return null;
25805
- }
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()}`;
25806
25906
  }
25807
25907
  function registerLogs(program2) {
25808
- 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) => {
25809
25909
  const { ctx, error } = await loadContext();
25810
25910
  if (!ctx) {
25811
25911
  printError(error?.message ?? "Olam is not configured. Run `olam init` first.");
@@ -25825,10 +25925,11 @@ function registerLogs(program2) {
25825
25925
  return;
25826
25926
  }
25827
25927
  const tailLimit = Math.max(1, parseInt(opts.tail, 10) || 200);
25828
- const showService = opts.service === void 0;
25829
- const subPath = opts.service ? `/api/logs/${encodeURIComponent(opts.service)}` : "/api/logs";
25830
- const url2 = `http://127.0.0.1:${HOST_CP_PORT3}/api/world/${encodeURIComponent(worldId)}${subPath}`;
25831
- let lineCount = 0;
25928
+ const url2 = buildLogsUrl(worldId, {
25929
+ tail: tailLimit,
25930
+ follow: opts.follow,
25931
+ ...opts.service ? { service: opts.service } : {}
25932
+ });
25832
25933
  let done = false;
25833
25934
  let resolveStream;
25834
25935
  const streamDone = new Promise((r) => {
@@ -25840,16 +25941,7 @@ function registerLogs(program2) {
25840
25941
  resolveStream();
25841
25942
  }
25842
25943
  };
25843
- const emit2 = (line, service) => {
25844
- process.stdout.write(formatLine(line, service, showService) + "\n");
25845
- lineCount++;
25846
- if (!opts.follow && lineCount >= tailLimit) {
25847
- finish();
25848
- return false;
25849
- }
25850
- return true;
25851
- };
25852
- const req = http3.get(url2, { headers: { Authorization: `Bearer ${token}` } }, (res) => {
25944
+ const req = http4.get(url2, { headers: { Authorization: `Bearer ${token}` } }, (res) => {
25853
25945
  if (res.statusCode !== 200) {
25854
25946
  printError(`Log stream returned HTTP ${res.statusCode ?? "unknown"}`);
25855
25947
  process.exitCode = 1;
@@ -25865,21 +25957,14 @@ function registerLogs(program2) {
25865
25957
  buf = rawLines.pop() ?? "";
25866
25958
  for (const raw of rawLines) {
25867
25959
  if (done) break;
25868
- const ev = parseSseEvent(raw);
25869
- if (!ev) continue;
25870
- if (ev.type === "replay") {
25871
- for (const l of ev.lines) {
25872
- if (!emit2(l, ev.service)) {
25873
- req.destroy();
25874
- break;
25875
- }
25876
- }
25877
- } else if (ev.type === "line") {
25878
- if (!emit2(ev.line, ev.service)) req.destroy();
25879
- }
25960
+ if (!raw) continue;
25961
+ process.stdout.write(colorLine(raw) + "\n");
25880
25962
  }
25881
25963
  });
25882
- res.on("end", () => finish());
25964
+ res.on("end", () => {
25965
+ if (buf) process.stdout.write(colorLine(buf) + "\n");
25966
+ finish();
25967
+ });
25883
25968
  res.on("error", (err) => {
25884
25969
  if (!done) printError(`Stream error: ${err.message}`);
25885
25970
  finish();
@@ -26902,6 +26987,11 @@ var HARD_CAP_BYTES = 5e9;
26902
26987
 
26903
26988
  // src/lib/health-probes.ts
26904
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+$/;
26905
26995
  var defaultDockerExec2 = (cmd, args) => {
26906
26996
  const r = spawnSync18(cmd, [...args], {
26907
26997
  encoding: "utf-8",
@@ -27125,6 +27215,146 @@ function formatBytes4(n) {
27125
27215
  if (n < 1024 * 1024 * 1024) return `${(n / 1024 / 1024).toFixed(1)} MB`;
27126
27216
  return `${(n / 1024 / 1024 / 1024).toFixed(2)} GB`;
27127
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
+ }
27128
27358
 
27129
27359
  // src/commands/doctor.ts
27130
27360
  init_output();
@@ -27148,27 +27378,56 @@ async function runDoctor(opts, deps = {}) {
27148
27378
  const fetchImpl = deps.fetchImpl;
27149
27379
  const olamHomeOverride = deps.olamHomeOverride;
27150
27380
  const registry = deps.registry ?? DEFAULT_REGISTRY;
27151
- const dockerResult = await probeDockerDaemon(dockerExec);
27152
- const rows = [{ name: "docker daemon", result: dockerResult }];
27153
- if (!dockerResult.ok) {
27154
- 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);
27155
27396
  }
27156
27397
  const imageRefs = buildRequiredImageRefs(registry);
27157
- const [imageResult, hostCpResult, authResult, kgResult] = await Promise.all([
27158
- 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,
27159
27401
  probeHostCpHealth(fetchImpl),
27160
27402
  probeAuthVault(olamHomeOverride),
27161
- probeKgStorage(olamHomeOverride)
27403
+ probeKgStorage(olamHomeOverride),
27404
+ probeEngine(fetchImpl),
27405
+ probeColimaVersion(dockerExec)
27162
27406
  ]);
27163
27407
  rows.push(
27164
27408
  { name: "images", result: imageResult },
27165
27409
  { name: "host-cp /health", result: hostCpResult },
27166
27410
  { name: "auth vault", result: authResult },
27167
- { name: "KG storage", result: kgResult }
27411
+ { name: "KG storage", result: kgResult },
27412
+ { name: "engine", result: engineResult },
27413
+ { name: "colima version", result: colimaResult }
27168
27414
  );
27415
+ return emit(makeReport(rows), opts);
27416
+ }
27417
+ function makeReport(rows) {
27169
27418
  const failureCount = rows.filter((r) => !r.result.ok).length;
27419
+ const warnCount = rows.filter((r) => isWarn(r.result)).length;
27170
27420
  const summary = failureCount === 0 ? "OK" : "FAILED";
27171
- 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;
27172
27431
  }
27173
27432
  function emit(report, opts) {
27174
27433
  if (opts.json) {
@@ -27177,12 +27436,19 @@ function emit(report, opts) {
27177
27436
  {
27178
27437
  summary: report.summary,
27179
27438
  failureCount: report.failureCount,
27180
- probes: report.rows.map((r) => ({
27181
- name: r.name,
27182
- ok: r.result.ok,
27183
- message: r.result.message,
27184
- ...r.result.ok ? {} : { remedy: r.result.remedy }
27185
- }))
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
+ })
27186
27452
  },
27187
27453
  null,
27188
27454
  2
@@ -27199,23 +27465,28 @@ function renderHuman(report) {
27199
27465
  const namePad = Math.max(...report.rows.map((r) => r.name.length));
27200
27466
  for (const row of report.rows) {
27201
27467
  const label = row.name.padEnd(namePad);
27202
- if (row.result.ok) {
27203
- 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}`);
27204
27474
  } else {
27205
- printError(`${label} ${row.result.message}`);
27206
- printWarning(`${"".padEnd(namePad)} remedy: ${row.result.remedy}`);
27475
+ printError(`[FAIL] ${label} ${row.result.message}`);
27476
+ printWarning(`${"".padEnd(namePad + 7)} remedy: ${row.result.remedy}`);
27207
27477
  }
27208
27478
  }
27209
27479
  process.stdout.write("\n");
27210
27480
  if (report.summary === "OK") {
27211
- 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}`);
27212
27483
  } else {
27213
27484
  printError(`FAILED \u2014 ${report.failureCount} of ${report.rows.length} probes failed`);
27214
27485
  }
27215
27486
  }
27216
27487
  function registerDoctor(program2) {
27217
27488
  program2.command("doctor").description(
27218
- "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)."
27219
27490
  ).option("--json", "emit the report as JSON instead of a human-readable table").action(async (opts) => {
27220
27491
  const r = await runDoctor(opts);
27221
27492
  if (r.exitCode !== 0) process.exitCode = r.exitCode;
@@ -27970,11 +28241,11 @@ function registerBegin(program2) {
27970
28241
  }
27971
28242
 
27972
28243
  // src/commands/config.ts
27973
- import * as fs46 from "node:fs";
28244
+ import * as fs51 from "node:fs";
27974
28245
  import { createRequire as createRequire4 } from "node:module";
27975
28246
 
27976
28247
  // ../core/dist/global-config/index.js
27977
- init_schema3();
28248
+ init_schema4();
27978
28249
  init_store2();
27979
28250
 
27980
28251
  // ../core/dist/global-config/repos.js
@@ -28048,83 +28319,709 @@ init_runbooks();
28048
28319
  init_port_validator();
28049
28320
  init_bridge();
28050
28321
 
28051
- // src/commands/config.ts
28322
+ // ../core/dist/skill-sources/index.js
28323
+ init_schema3();
28324
+
28325
+ // ../core/dist/skill-sources/store.js
28052
28326
  init_store2();
28053
- var _require4 = createRequire4(import.meta.url);
28054
- var { parse: parseWithMap } = _require4("json-source-map");
28055
- function registerConfig(program2) {
28056
- const config = program2.command("config").description("Manage global olam configuration");
28057
- config.command("validate [path]").description("Validate ~/.olam/config.json (or a custom path) against the schema").action((filePath) => {
28058
- const resolvedPath = filePath ?? globalConfigPath();
28059
- if (!fs46.existsSync(resolvedPath)) {
28060
- process.stderr.write(`config file not found: ${resolvedPath}
28061
- `);
28062
- process.exit(1);
28063
- }
28064
- let raw;
28065
- try {
28066
- raw = fs46.readFileSync(resolvedPath, "utf-8");
28067
- } catch (err) {
28068
- const msg = err instanceof Error ? err.message : String(err);
28069
- process.stderr.write(`cannot read ${resolvedPath}: ${msg}
28070
- `);
28071
- process.exit(1);
28072
- }
28073
- let parsed;
28074
- let pointers;
28075
- try {
28076
- const result2 = parseWithMap(raw);
28077
- parsed = result2.data;
28078
- pointers = result2.pointers;
28079
- } catch (err) {
28080
- const msg = err instanceof Error ? err.message : String(err);
28081
- process.stderr.write(`${resolvedPath}: invalid JSON \u2014 ${msg}
28082
- `);
28083
- process.exit(1);
28084
- }
28085
- const result = GlobalConfigSchema.safeParse(parsed);
28086
- if (result.success) {
28087
- process.exit(0);
28088
- }
28089
- for (const issue of result.error.issues) {
28090
- const pointer = "/" + issue.path.join("/");
28091
- const entry = pointers[pointer];
28092
- const rawLine = entry?.value?.line ?? entry?.key?.line ?? -1;
28093
- const lineStr = rawLine >= 0 ? `Line ${rawLine + 1}: ` : "";
28094
- process.stderr.write(`${lineStr}${pointer}: ${issue.message}
28095
- `);
28096
- }
28097
- process.exit(1);
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);
28331
+ }
28332
+ function listSkillSources() {
28333
+ return readGlobalConfig().skillSources;
28334
+ }
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)
28098
28366
  });
28099
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
+ }
28100
28392
 
28101
- // src/commands/repos.ts
28102
- import pc25 from "picocolors";
28103
- init_output();
28104
- function asMessage(err) {
28105
- return err instanceof Error ? err.message : String(err);
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
+ }
28106
28429
  }
28107
- function registerRepos(program2) {
28108
- const repos = program2.command("repos").description("Manage the global repo registry");
28109
- repos.command("list").description("List all registered repos").action(() => {
28110
- const all = listRepos();
28111
- if (all.length === 0) {
28112
- console.log(pc25.dim("0 repo(s) registered. Add one with: olam repos add --name <n> --path <p>"));
28113
- return;
28114
- }
28115
- printHeader(`${all.length} repo(s)`);
28116
- for (const r of all) {
28117
- const when = new Date(r.addedAt).toISOString().slice(0, 10);
28118
- console.log(
28119
- ` ${pc25.bold(r.name.padEnd(24))} ${r.path.padEnd(48)} ${pc25.dim(when)}`
28120
- );
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 });
28121
28441
  }
28122
- });
28123
- 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) => {
28124
- try {
28125
- const entry = addRepo({
28126
- name: opts.name,
28127
- path: opts.path,
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,
28128
29025
  description: opts.description,
28129
29026
  defaultBranch: opts.defaultBranch
28130
29027
  });
@@ -28240,74 +29137,309 @@ function registerRunbooks(program2) {
28240
29137
  removeRunbook(name);
28241
29138
  printSuccess(`removed runbook "${name}"`);
28242
29139
  } catch (err) {
28243
- 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));
28244
29396
  process.exitCode = 1;
28245
29397
  }
28246
29398
  });
28247
- 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) => {
28248
29400
  try {
28249
- const rb = getRunbook(name);
28250
- const { conflicts } = validateRunbookPorts(rb);
28251
- if (conflicts.length > 0) {
28252
- printError(`port conflicts detected for runbook "${name}":`);
28253
- for (const c of conflicts) {
28254
- console.error(formatConflict(c));
28255
- }
28256
- console.error(
28257
- `
28258
- ${conflicts.length} port conflict(s). Stop the conflicting processes or update portMap in runbook "${name}".`
28259
- );
28260
- process.exitCode = 1;
28261
- return;
28262
- }
28263
- console.log(
28264
- pc26.dim(
28265
- `olam runbooks apply: port validation passed for "${name}" (repos: ${rb.repos.join(", ")}).`
28266
- )
28267
- );
28268
- console.log(
28269
- pc26.dim(
28270
- `Hint: use "olam create --repos ${rb.repos.join(" ")}${opts.task ? ` --task "${opts.task}"` : ""}${opts.name ? ` --name ${opts.name}` : ""}" to create the world.`
28271
- )
28272
- );
28273
- console.log(
28274
- pc26.yellow('Phase D will wire --runbook directly. For now, use "olam create --repos ..." above.')
28275
- );
29401
+ const summary = await syncSkills({ dryRun: true, atlasUser: opts.atlasUser });
29402
+ printSyncSummary(summary, true);
28276
29403
  } catch (err) {
28277
- printError(asMessage2(err));
29404
+ printError(asMessage4(err));
28278
29405
  process.exitCode = 1;
28279
29406
  }
28280
29407
  });
28281
- runbooks.command("check-ports").description("Check if runbook ports are available").argument("<name>", "Runbook name").action((name) => {
28282
- try {
28283
- const rb = getRunbook(name);
28284
- const { conflicts } = validateRunbookPorts(rb);
28285
- if (!rb.portMap || Object.keys(rb.portMap).length === 0) {
28286
- console.log(pc26.dim(`runbook "${name}" declares no ports.`));
28287
- return;
28288
- }
28289
- for (const [repoName, svcMap] of Object.entries(rb.portMap ?? {})) {
28290
- for (const [svcName, port2] of Object.entries(svcMap)) {
28291
- const conflict = conflicts.find((c) => c.port === port2);
28292
- if (conflict) {
28293
- console.log(formatConflict(conflict));
28294
- } else {
28295
- console.log(` ${pc26.green("\u2713")} ${repoName}.${svcName}:${port2} \u2014 free`);
28296
- }
28297
- }
28298
- }
28299
- if (conflicts.length > 0) {
28300
- console.error(
28301
- `
28302
- ${conflicts.length} port conflict(s). Stop the conflicting processes or update portMap in runbook "${name}".`
28303
- );
28304
- process.exitCode = 1;
28305
- } else {
28306
- 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)}`);
28307
29426
  }
28308
- } catch (err) {
28309
- 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}"`);
28310
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();
28311
29443
  }
28312
29444
  });
28313
29445
  }
@@ -28386,17 +29518,17 @@ function registerWorldUpgrade(program2) {
28386
29518
 
28387
29519
  // src/commands/mcp/serve.ts
28388
29520
  init_output();
28389
- import { existsSync as existsSync52 } from "node:fs";
28390
- 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";
28391
29523
  import { fileURLToPath as fileURLToPath5 } from "node:url";
28392
- var here = dirname27(fileURLToPath5(import.meta.url));
29524
+ var here = dirname28(fileURLToPath5(import.meta.url));
28393
29525
  var BUNDLE_PATH_CANDIDATES = [
28394
29526
  // bundled (`dist/index.js` after bundle-cli.mjs) — sibling
28395
29527
  resolve13(here, "mcp-server.js"),
28396
29528
  // dev / tsc-only (`dist/commands/mcp/serve.js`) — up two levels
28397
29529
  resolve13(here, "..", "..", "mcp-server.js")
28398
29530
  ];
28399
- function resolveBundlePath(candidates2 = BUNDLE_PATH_CANDIDATES, exists = existsSync52) {
29531
+ function resolveBundlePath(candidates2 = BUNDLE_PATH_CANDIDATES, exists = existsSync58) {
28400
29532
  return candidates2.find(exists) ?? null;
28401
29533
  }
28402
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.";
@@ -28553,8 +29685,8 @@ var SECRET = process.env["OLAM_MCP_AUTH_SECRET"] ?? "";
28553
29685
  function authHeaders() {
28554
29686
  return SECRET ? { "X-Olam-Mcp-Secret": SECRET } : {};
28555
29687
  }
28556
- async function apiFetch(path60, init = {}) {
28557
- const res = await fetch(`${BASE_URL}${path60}`, {
29688
+ async function apiFetch(path66, init = {}) {
29689
+ const res = await fetch(`${BASE_URL}${path66}`, {
28558
29690
  ...init,
28559
29691
  headers: {
28560
29692
  "Content-Type": "application/json",
@@ -28706,7 +29838,7 @@ function registerMcpAdd(cmd) {
28706
29838
 
28707
29839
  // src/commands/mcp/list.ts
28708
29840
  init_output();
28709
- import pc27 from "picocolors";
29841
+ import pc29 from "picocolors";
28710
29842
  function registerMcpList(cmd) {
28711
29843
  cmd.command("list").description("List all registered MCP credentials").action(async () => {
28712
29844
  const client = getMcpAuthClient();
@@ -28721,8 +29853,8 @@ function registerMcpList(cmd) {
28721
29853
  }
28722
29854
  const mcps = data.mcps ?? [];
28723
29855
  if (mcps.length === 0) {
28724
- console.log(pc27.dim("No MCP credentials registered."));
28725
- 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>"));
28726
29858
  return;
28727
29859
  }
28728
29860
  const [c0, c1, c2, c3] = [16, 20, 10, 24];
@@ -28733,12 +29865,12 @@ function registerMcpList(cmd) {
28733
29865
  "ENV VAR".padEnd(c3),
28734
29866
  "STATUS"
28735
29867
  ].join(" ");
28736
- console.log(pc27.dim(header));
28737
- console.log(pc27.dim("\u2500".repeat(header.length)));
29868
+ console.log(pc29.dim(header));
29869
+ console.log(pc29.dim("\u2500".repeat(header.length)));
28738
29870
  for (const m of mcps) {
28739
- 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");
28740
29872
  const row = [
28741
- pc27.cyan(m.service.padEnd(c0)),
29873
+ pc29.cyan(m.service.padEnd(c0)),
28742
29874
  m.label.padEnd(c1).slice(0, c1),
28743
29875
  m.type.padEnd(c2),
28744
29876
  (m.envName ?? "\u2014").padEnd(c3).slice(0, c3),
@@ -28766,13 +29898,13 @@ function registerMcpRemove(cmd) {
28766
29898
 
28767
29899
  // src/commands/mcp/status.ts
28768
29900
  init_output();
28769
- import pc28 from "picocolors";
29901
+ import pc30 from "picocolors";
28770
29902
  function formatExpiry(expiresAt) {
28771
29903
  if (!expiresAt) return "\u2014";
28772
29904
  const ms = expiresAt - Date.now();
28773
- if (ms <= 0) return pc28.red("expired");
29905
+ if (ms <= 0) return pc30.red("expired");
28774
29906
  const hours = ms / (1e3 * 60 * 60);
28775
- if (hours < 1) return pc28.yellow(`${Math.ceil(ms / 6e4)}m`);
29907
+ if (hours < 1) return pc30.yellow(`${Math.ceil(ms / 6e4)}m`);
28776
29908
  return `${hours.toFixed(1)}h`;
28777
29909
  }
28778
29910
  function registerMcpStatus(cmd) {
@@ -28789,14 +29921,14 @@ function registerMcpStatus(cmd) {
28789
29921
  const mcps = data.mcps ?? [];
28790
29922
  printHeader(`MCP Credentials (${mcps.length})`);
28791
29923
  if (mcps.length === 0) {
28792
- console.log(pc28.dim("No credentials registered."));
29924
+ console.log(pc30.dim("No credentials registered."));
28793
29925
  return;
28794
29926
  }
28795
29927
  for (const m of mcps) {
28796
- 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;
28797
29929
  const stateLabel = !m.validated ? "unvalidated" : m.expired ? "expired" : "active";
28798
29930
  console.log(`
28799
- ${pc28.cyan(m.service)} ${stateColor(`[${stateLabel}]`)}`);
29931
+ ${pc30.cyan(m.service)} ${stateColor(`[${stateLabel}]`)}`);
28800
29932
  console.log(` label: ${m.label}`);
28801
29933
  console.log(` type: ${m.type}`);
28802
29934
  if (m.envName) console.log(` env var: ${m.envName}`);
@@ -28811,15 +29943,15 @@ function registerMcpStatus(cmd) {
28811
29943
  // src/commands/mcp/import.ts
28812
29944
  init_output();
28813
29945
  import * as readline3 from "node:readline";
28814
- import pc29 from "picocolors";
29946
+ import pc31 from "picocolors";
28815
29947
 
28816
29948
  // src/commands/mcp/import-discovery.ts
28817
- import * as fs47 from "node:fs";
28818
- import * as os27 from "node:os";
28819
- 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";
28820
29952
  function readJsonFile(filePath) {
28821
29953
  try {
28822
- const raw = fs47.readFileSync(filePath, "utf-8");
29954
+ const raw = fs53.readFileSync(filePath, "utf-8");
28823
29955
  return JSON.parse(raw);
28824
29956
  } catch {
28825
29957
  return null;
@@ -28848,24 +29980,24 @@ function extractMcpServers(obj, source, sourceLabel) {
28848
29980
  }
28849
29981
  function getClaudeDesktopPath() {
28850
29982
  if (process.platform === "darwin") {
28851
- 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");
28852
29984
  }
28853
29985
  if (process.platform === "win32") {
28854
- const appData = process.env["APPDATA"] ?? path51.join(os27.homedir(), "AppData", "Roaming");
28855
- 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");
28856
29988
  }
28857
- return path51.join(os27.homedir(), ".config", "Claude", "claude_desktop_config.json");
29989
+ return path57.join(os31.homedir(), ".config", "Claude", "claude_desktop_config.json");
28858
29990
  }
28859
29991
  function getOlamRepoPaths() {
28860
29992
  const configPaths = [
28861
- path51.join(os27.homedir(), ".olam", "config.yaml"),
28862
- path51.join(process.cwd(), ".olam", "config.yaml")
29993
+ path57.join(os31.homedir(), ".olam", "config.yaml"),
29994
+ path57.join(process.cwd(), ".olam", "config.yaml")
28863
29995
  ];
28864
29996
  const paths = [];
28865
29997
  for (const configPath of configPaths) {
28866
- if (!fs47.existsSync(configPath)) continue;
29998
+ if (!fs53.existsSync(configPath)) continue;
28867
29999
  try {
28868
- const raw = fs47.readFileSync(configPath, "utf-8");
30000
+ const raw = fs53.readFileSync(configPath, "utf-8");
28869
30001
  const repoMatches = [...raw.matchAll(/path:\s*["']?([^\s"'\n]+)/g)];
28870
30002
  for (const m of repoMatches) {
28871
30003
  if (m[1]) paths.push(m[1]);
@@ -28881,7 +30013,7 @@ async function discoverMcpSources(repoPaths) {
28881
30013
  const sources = [];
28882
30014
  const sourceDefs = [
28883
30015
  {
28884
- path: path51.join(os27.homedir(), ".claude.json"),
30016
+ path: path57.join(os31.homedir(), ".claude.json"),
28885
30017
  label: "Claude Code (~/.claude.json)"
28886
30018
  },
28887
30019
  {
@@ -28889,19 +30021,19 @@ async function discoverMcpSources(repoPaths) {
28889
30021
  label: "Claude Desktop"
28890
30022
  },
28891
30023
  {
28892
- path: path51.join(os27.homedir(), ".cursor", "mcp.json"),
30024
+ path: path57.join(os31.homedir(), ".cursor", "mcp.json"),
28893
30025
  label: "Cursor (~/.cursor/mcp.json)"
28894
30026
  },
28895
30027
  {
28896
- path: path51.join(os27.homedir(), ".codeium", "windsurf", "mcp_config.json"),
30028
+ path: path57.join(os31.homedir(), ".codeium", "windsurf", "mcp_config.json"),
28897
30029
  label: "Windsurf (~/.codeium/windsurf/mcp_config.json)"
28898
30030
  }
28899
30031
  ];
28900
30032
  const resolvedRepoPaths = repoPaths ?? getOlamRepoPaths();
28901
30033
  for (const repoPath of resolvedRepoPaths) {
28902
30034
  sourceDefs.push({
28903
- path: path51.join(repoPath, ".mcp.json"),
28904
- label: `.mcp.json (${path51.basename(repoPath)})`
30035
+ path: path57.join(repoPath, ".mcp.json"),
30036
+ label: `.mcp.json (${path57.basename(repoPath)})`
28905
30037
  });
28906
30038
  }
28907
30039
  const reads = await Promise.all(
@@ -28981,13 +30113,13 @@ async function validateMcpEntry(entry) {
28981
30113
  // src/commands/mcp/import.ts
28982
30114
  async function multiSelectPicker(entries) {
28983
30115
  if (entries.length === 0) return [];
28984
- console.log("\n" + pc29.bold("Discovered MCP servers:"));
30116
+ console.log("\n" + pc31.bold("Discovered MCP servers:"));
28985
30117
  entries.forEach((e, i) => {
28986
30118
  console.log(
28987
- ` ${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)}`
28988
30120
  );
28989
30121
  });
28990
- 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):'));
28991
30123
  const answer = await new Promise((resolve15) => {
28992
30124
  const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
28993
30125
  rl.question("> ", (ans) => {
@@ -29014,7 +30146,7 @@ function registerMcpImport(cmd) {
29014
30146
  const repoPaths = opts.repoPaths ? opts.repoPaths.split(",").map((s) => s.trim()) : void 0;
29015
30147
  const { entries, sources, durationMs } = await discoverMcpSources(repoPaths);
29016
30148
  if (entries.length === 0) {
29017
- 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."));
29018
30150
  return;
29019
30151
  }
29020
30152
  printInfo("Sources", sources.length > 0 ? sources.join(", ") : "none");
@@ -29027,15 +30159,15 @@ function registerMcpImport(cmd) {
29027
30159
  candidates2 = filtered;
29028
30160
  }
29029
30161
  if (skippedCount > 0) {
29030
- console.log(pc29.dim(`skipped: ${skippedCount} already registered`));
30162
+ console.log(pc31.dim(`skipped: ${skippedCount} already registered`));
29031
30163
  }
29032
30164
  if (candidates2.length === 0) {
29033
- 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."));
29034
30166
  return;
29035
30167
  }
29036
30168
  const selected = await multiSelectPicker(candidates2);
29037
30169
  if (selected.length === 0) {
29038
- console.log(pc29.dim("No servers selected."));
30170
+ console.log(pc31.dim("No servers selected."));
29039
30171
  return;
29040
30172
  }
29041
30173
  console.log(`
@@ -29046,16 +30178,16 @@ Importing ${selected.length} server(s)\u2026`);
29046
30178
  let validated = false;
29047
30179
  let validationReason = "skipped";
29048
30180
  if (opts.validate !== false) {
29049
- process.stdout.write(` ${pc29.dim("\u2192")} ${entry.name} validating\u2026 `);
30181
+ process.stdout.write(` ${pc31.dim("\u2192")} ${entry.name} validating\u2026 `);
29050
30182
  const vr = await validateMcpEntry(entry);
29051
30183
  validated = vr.validated;
29052
30184
  validationReason = vr.reason;
29053
30185
  process.stdout.write(
29054
- validated ? pc29.green("ok\n") : pc29.yellow(`unvalidated (${vr.reason})
30186
+ validated ? pc31.green("ok\n") : pc31.yellow(`unvalidated (${vr.reason})
29055
30187
  `)
29056
30188
  );
29057
30189
  } else {
29058
- console.log(` ${pc29.dim("\u2192")} ${entry.name} ${pc29.dim("(validation skipped)")}`);
30190
+ console.log(` ${pc31.dim("\u2192")} ${entry.name} ${pc31.dim("(validation skipped)")}`);
29059
30191
  }
29060
30192
  if (validated) validatedCount++;
29061
30193
  const result = await client.staticAdd({
@@ -29070,21 +30202,21 @@ Importing ${selected.length} server(s)\u2026`);
29070
30202
  }
29071
30203
  }
29072
30204
  console.log("");
29073
- console.log(pc29.green(`\u2713 Imported ${importedCount}/${selected.length}`));
30205
+ console.log(pc31.green(`\u2713 Imported ${importedCount}/${selected.length}`));
29074
30206
  if (validatedCount > 0) {
29075
- console.log(pc29.dim(` ${validatedCount} validated, ${importedCount - validatedCount} unvalidated`));
30207
+ console.log(pc31.dim(` ${validatedCount} validated, ${importedCount - validatedCount} unvalidated`));
29076
30208
  }
29077
30209
  });
29078
30210
  }
29079
30211
 
29080
30212
  // src/commands/mcp/revoke.ts
29081
30213
  init_output();
29082
- import pc30 from "picocolors";
30214
+ import pc32 from "picocolors";
29083
30215
  function registerMcpRevoke(cmd) {
29084
30216
  cmd.command("revoke <user> <world> <service>").description("Revoke a user's MCP service entitlement (multi-tenant mode only)").action(async (user, world, service) => {
29085
30217
  const multiTenant = (process.env["OLAM_MCP_MULTI_TENANT"] ?? "").toLowerCase() === "true";
29086
30218
  if (!multiTenant) {
29087
- 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"));
29088
30220
  return;
29089
30221
  }
29090
30222
  const baseUrl = process.env["OLAM_MCP_AUTH_SERVICE_URL"] ?? "http://127.0.0.1:9998";
@@ -29113,8 +30245,8 @@ function registerMcpRevoke(cmd) {
29113
30245
  process.exitCode = 1;
29114
30246
  return;
29115
30247
  }
29116
- console.log(pc30.green(`\u2713 Revoked: ${user} / ${world} / ${service}`));
29117
- 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."));
29118
30250
  });
29119
30251
  }
29120
30252
 
@@ -29136,34 +30268,34 @@ function registerMcp(program2) {
29136
30268
  // src/commands/memory/start.ts
29137
30269
  init_output();
29138
30270
  import { spawn as spawn8 } from "node:child_process";
29139
- import { existsSync as existsSync54, mkdirSync as mkdirSync31, openSync as openSync3, readFileSync as readFileSync38, writeFileSync as writeFileSync24 } from "node:fs";
29140
- 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";
29141
30273
  import { pathToFileURL } from "node:url";
29142
30274
 
29143
30275
  // src/commands/memory/_paths.ts
29144
- import { homedir as homedir30 } from "node:os";
29145
- 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";
29146
30278
  import { fileURLToPath as fileURLToPath6 } from "node:url";
29147
- var OLAM_HOME = join51(homedir30(), ".olam");
29148
- var OLAM_BIN_DIR = join51(OLAM_HOME, "bin");
29149
- var III_BINARY_PATH = join51(OLAM_BIN_DIR, "iii");
29150
- var MEMORY_PID_PATH = join51(OLAM_HOME, "memory.pid");
29151
- var MEMORY_LOG_PATH = join51(OLAM_HOME, "memory-service.log");
29152
- 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");
29153
30285
  var MEMORY_REST_PORT = 3111;
29154
30286
  var MEMORY_LIVEZ_URL = `http://localhost:${MEMORY_REST_PORT}/agentmemory/livez`;
29155
- var here2 = dirname28(fileURLToPath6(import.meta.url));
30287
+ var here2 = dirname29(fileURLToPath6(import.meta.url));
29156
30288
  var candidates = [
29157
30289
  // 1. Workspace dev (built): packages/cli/dist/commands/memory/_paths.js → packages/cli → packages/memory-service
29158
- join51(here2, "..", "..", "..", "..", "memory-service"),
30290
+ join57(here2, "..", "..", "..", "..", "memory-service"),
29159
30291
  // 2a. Workspace bundled: packages/cli/dist/index.js → packages/cli → packages/memory-service
29160
- join51(here2, "..", "..", "memory-service"),
30292
+ join57(here2, "..", "..", "memory-service"),
29161
30293
  // 2b. Published tarball: <prefix>/node_modules/@pleri/olam-cli/dist/index.js
29162
30294
  // → <prefix>/node_modules/@pleri/olam-cli/memory-service-bundle
29163
30295
  // (copied at publish time by bundle-cli.mjs)
29164
- join51(here2, "..", "memory-service-bundle"),
30296
+ join57(here2, "..", "memory-service-bundle"),
29165
30297
  // 3. CWD fallback
29166
- join51(process.cwd(), "packages", "memory-service")
30298
+ join57(process.cwd(), "packages", "memory-service")
29167
30299
  ];
29168
30300
  var MEMORY_SERVICE_CANDIDATES = candidates;
29169
30301
 
@@ -29172,7 +30304,7 @@ var READINESS_TIMEOUT_MS = 3e4;
29172
30304
  var READINESS_POLL_MS = 500;
29173
30305
  function resolveMemoryServiceDir() {
29174
30306
  for (const c of MEMORY_SERVICE_CANDIDATES) {
29175
- if (existsSync54(c)) return c;
30307
+ if (existsSync60(c)) return c;
29176
30308
  }
29177
30309
  throw new Error(
29178
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.`
@@ -29180,12 +30312,12 @@ function resolveMemoryServiceDir() {
29180
30312
  }
29181
30313
  function resolveAgentMemoryBin(serviceDir) {
29182
30314
  const candidates2 = [
29183
- join52(serviceDir, "node_modules", ".bin", "agentmemory"),
29184
- join52(serviceDir, "..", "..", "node_modules", ".bin", "agentmemory"),
29185
- 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")
29186
30318
  ];
29187
30319
  for (const c of candidates2) {
29188
- if (existsSync54(c)) return c;
30320
+ if (existsSync60(c)) return c;
29189
30321
  }
29190
30322
  throw new Error(
29191
30323
  `Could not find agentmemory bin. Searched: ${candidates2.join(", ")}. Run 'npm install' from the repo root.`
@@ -29200,8 +30332,8 @@ function isProcessAlive(pid) {
29200
30332
  }
29201
30333
  }
29202
30334
  function readPidFromFile() {
29203
- if (!existsSync54(MEMORY_PID_PATH)) return null;
29204
- 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();
29205
30337
  const pid = parseInt(raw, 10);
29206
30338
  if (!Number.isFinite(pid) || pid <= 0) return null;
29207
30339
  return pid;
@@ -29228,7 +30360,7 @@ async function waitForReady(secret) {
29228
30360
  return false;
29229
30361
  }
29230
30362
  async function autoEnsureIiiBinary(serviceDir) {
29231
- const helperPath = join52(serviceDir, "scripts", "ensure-iii-engine.mjs");
30363
+ const helperPath = join58(serviceDir, "scripts", "ensure-iii-engine.mjs");
29232
30364
  const mod = await import(pathToFileURL(helperPath).href);
29233
30365
  const result = await mod.ensureIiiEngine();
29234
30366
  if (!result.ok) {
@@ -29244,7 +30376,7 @@ async function runMemoryStart() {
29244
30376
  printError(err instanceof Error ? err.message : String(err));
29245
30377
  return 1;
29246
30378
  }
29247
- if (!existsSync54(III_BINARY_PATH)) {
30379
+ if (!existsSync60(III_BINARY_PATH)) {
29248
30380
  printInfo("iii binary", `missing at ${III_BINARY_PATH} \u2014 auto-fetching v0.11.2`);
29249
30381
  try {
29250
30382
  await autoEnsureIiiBinary(serviceDir);
@@ -29272,8 +30404,8 @@ async function runMemoryStart() {
29272
30404
  );
29273
30405
  return 1;
29274
30406
  }
29275
- mkdirSync31(OLAM_HOME, { recursive: true });
29276
- mkdirSync31(MEMORY_DATA_DIR, { recursive: true });
30407
+ mkdirSync34(OLAM_HOME, { recursive: true });
30408
+ mkdirSync34(MEMORY_DATA_DIR, { recursive: true });
29277
30409
  const logFd = openSync3(MEMORY_LOG_PATH, "a");
29278
30410
  const child = spawn8(agentmemoryBin, [], {
29279
30411
  cwd: OLAM_HOME,
@@ -29296,7 +30428,7 @@ async function runMemoryStart() {
29296
30428
  printError("spawn returned no pid (process failed to start)");
29297
30429
  return 1;
29298
30430
  }
29299
- writeFileSync24(MEMORY_PID_PATH, `${child.pid}
30431
+ writeFileSync25(MEMORY_PID_PATH, `${child.pid}
29300
30432
  `, { mode: 420 });
29301
30433
  printInfo("pid", `${child.pid}`);
29302
30434
  printInfo("readiness", `polling ${MEMORY_LIVEZ_URL}`);
@@ -29319,7 +30451,7 @@ function registerMemoryStart(cmd) {
29319
30451
 
29320
30452
  // src/commands/memory/stop.ts
29321
30453
  init_output();
29322
- 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";
29323
30455
  var SIGTERM_GRACE_MS = 1e4;
29324
30456
  var POLL_MS = 250;
29325
30457
  function isAlive(pid) {
@@ -29332,19 +30464,19 @@ function isAlive(pid) {
29332
30464
  }
29333
30465
  async function runMemoryStop() {
29334
30466
  printHeader("olam memory stop");
29335
- if (!existsSync55(MEMORY_PID_PATH)) {
30467
+ if (!existsSync61(MEMORY_PID_PATH)) {
29336
30468
  printSuccess("no pidfile present (nothing to stop)");
29337
30469
  return 0;
29338
30470
  }
29339
- const pid = parseInt(readFileSync39(MEMORY_PID_PATH, "utf8").trim(), 10);
30471
+ const pid = parseInt(readFileSync42(MEMORY_PID_PATH, "utf8").trim(), 10);
29340
30472
  if (!Number.isFinite(pid) || pid <= 0) {
29341
30473
  printWarning(`pidfile contained invalid value; removing`);
29342
- unlinkSync9(MEMORY_PID_PATH);
30474
+ unlinkSync11(MEMORY_PID_PATH);
29343
30475
  return 0;
29344
30476
  }
29345
30477
  if (!isAlive(pid)) {
29346
30478
  printSuccess(`pid ${pid} is not running (stale pidfile); cleaned up`);
29347
- unlinkSync9(MEMORY_PID_PATH);
30479
+ unlinkSync11(MEMORY_PID_PATH);
29348
30480
  return 0;
29349
30481
  }
29350
30482
  printInfo("pid", `${pid}`);
@@ -29367,8 +30499,8 @@ async function runMemoryStop() {
29367
30499
  }
29368
30500
  await new Promise((r) => setTimeout(r, 500));
29369
30501
  }
29370
- if (existsSync55(MEMORY_PID_PATH)) {
29371
- unlinkSync9(MEMORY_PID_PATH);
30502
+ if (existsSync61(MEMORY_PID_PATH)) {
30503
+ unlinkSync11(MEMORY_PID_PATH);
29372
30504
  }
29373
30505
  printSuccess(`stopped (pid ${pid})`);
29374
30506
  return 0;
@@ -29382,7 +30514,7 @@ function registerMemoryStop(cmd) {
29382
30514
 
29383
30515
  // src/commands/memory/status.ts
29384
30516
  init_output();
29385
- import { existsSync as existsSync56, readFileSync as readFileSync40 } from "node:fs";
30517
+ import { existsSync as existsSync62, readFileSync as readFileSync43 } from "node:fs";
29386
30518
  function isAlive2(pid) {
29387
30519
  try {
29388
30520
  process.kill(pid, 0);
@@ -29406,8 +30538,8 @@ async function probe(secret) {
29406
30538
  }
29407
30539
  async function collectMemoryStatus() {
29408
30540
  let pid = null;
29409
- if (existsSync56(MEMORY_PID_PATH)) {
29410
- const raw = readFileSync40(MEMORY_PID_PATH, "utf8").trim();
30541
+ if (existsSync62(MEMORY_PID_PATH)) {
30542
+ const raw = readFileSync43(MEMORY_PID_PATH, "utf8").trim();
29411
30543
  const parsed = parseInt(raw, 10);
29412
30544
  pid = Number.isFinite(parsed) && parsed > 0 ? parsed : null;
29413
30545
  }
@@ -29419,7 +30551,7 @@ async function collectMemoryStatus() {
29419
30551
  alive,
29420
30552
  livez,
29421
30553
  secretSet: hasMemorySecret(),
29422
- iiiBinary: existsSync56(III_BINARY_PATH) ? III_BINARY_PATH : null,
30554
+ iiiBinary: existsSync62(III_BINARY_PATH) ? III_BINARY_PATH : null,
29423
30555
  port: MEMORY_REST_PORT
29424
30556
  };
29425
30557
  }
@@ -29462,10 +30594,10 @@ function registerMemoryStatus(cmd) {
29462
30594
 
29463
30595
  // src/commands/memory/logs.ts
29464
30596
  init_output();
29465
- import { existsSync as existsSync57 } from "node:fs";
30597
+ import { existsSync as existsSync63 } from "node:fs";
29466
30598
  import { spawn as spawn9 } from "node:child_process";
29467
30599
  async function runMemoryLogs(opts) {
29468
- if (!existsSync57(MEMORY_LOG_PATH)) {
30600
+ if (!existsSync63(MEMORY_LOG_PATH)) {
29469
30601
  printWarning(`no log at ${MEMORY_LOG_PATH} (start the service first via 'olam memory start')`);
29470
30602
  return 1;
29471
30603
  }
@@ -29491,7 +30623,7 @@ function registerMemoryLogs(cmd) {
29491
30623
  }
29492
30624
 
29493
30625
  // src/commands/memory/secret.ts
29494
- import { existsSync as existsSync58 } from "node:fs";
30626
+ import { existsSync as existsSync64 } from "node:fs";
29495
30627
  init_output();
29496
30628
  async function runMemorySecretShow() {
29497
30629
  if (!hasMemorySecret()) {
@@ -29506,7 +30638,7 @@ async function runMemorySecretShow() {
29506
30638
  }
29507
30639
  async function runMemorySecretRotate() {
29508
30640
  printHeader("olam memory secret rotate");
29509
- const wasRunning = existsSync58(MEMORY_PID_PATH);
30641
+ const wasRunning = existsSync64(MEMORY_PID_PATH);
29510
30642
  if (wasRunning) {
29511
30643
  printInfo("current state", "service running; will restart with new secret");
29512
30644
  const stopRc = await runMemoryStop();
@@ -29688,14 +30820,14 @@ function registerMemoryUninstall(cmd) {
29688
30820
  // src/commands/memory/mode.ts
29689
30821
  init_schema2();
29690
30822
  init_output();
29691
- import { existsSync as existsSync59, readFileSync as readFileSync41, writeFileSync as writeFileSync25 } from "node:fs";
29692
- 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";
29693
30825
  import * as readline4 from "node:readline/promises";
29694
30826
  import { parse as parseYaml6, stringify as stringifyYaml6 } from "yaml";
29695
30827
  var CONFIG_REL = ".olam/config.yaml";
29696
30828
  function locateConfig(cwd) {
29697
- const absPath = join53(cwd, CONFIG_REL);
29698
- if (!existsSync59(absPath)) {
30829
+ const absPath = join59(cwd, CONFIG_REL);
30830
+ if (!existsSync65(absPath)) {
29699
30831
  throw new Error(
29700
30832
  `No ${CONFIG_REL} at ${cwd}. Run \`olam init\` in your workspace root first.`
29701
30833
  );
@@ -29703,7 +30835,7 @@ function locateConfig(cwd) {
29703
30835
  return { absPath };
29704
30836
  }
29705
30837
  function readConfigYaml(absPath) {
29706
- const raw = readFileSync41(absPath, "utf-8");
30838
+ const raw = readFileSync44(absPath, "utf-8");
29707
30839
  const parsed = parseYaml6(raw) ?? {};
29708
30840
  if (typeof parsed !== "object" || parsed === null) {
29709
30841
  throw new Error(`${absPath} is not a YAML object`);
@@ -29712,7 +30844,7 @@ function readConfigYaml(absPath) {
29712
30844
  }
29713
30845
  function writeConfigYaml(absPath, parsed) {
29714
30846
  const out = stringifyYaml6(parsed, { aliasDuplicateObjects: false });
29715
- writeFileSync25(absPath, out, "utf-8");
30847
+ writeFileSync26(absPath, out, "utf-8");
29716
30848
  }
29717
30849
  async function defaultPromptText(question) {
29718
30850
  const rl = readline4.createInterface({ input: process.stdin, output: process.stdout });
@@ -29828,10 +30960,371 @@ function registerMemoryMode(cmd) {
29828
30960
  });
29829
30961
  }
29830
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
+
29831
31324
  // src/commands/memory/index.ts
29832
31325
  function registerMemory(program2) {
29833
31326
  const memory = program2.command("memory").description(
29834
- "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)"
29835
31328
  );
29836
31329
  registerMemoryStart(memory);
29837
31330
  registerMemoryStop(memory);
@@ -29841,14 +31334,17 @@ function registerMemory(program2) {
29841
31334
  registerMemoryInstall(memory);
29842
31335
  registerMemoryUninstall(memory);
29843
31336
  registerMemoryMode(memory);
31337
+ registerMemoryBridge(memory);
31338
+ registerMemoryReclassify(memory);
31339
+ registerMemoryStats(memory);
29844
31340
  }
29845
31341
 
29846
31342
  // src/commands/kg-build.ts
29847
31343
  init_storage_paths();
29848
31344
  init_workspace_name();
29849
- import * as fs53 from "node:fs";
29850
- import * as os30 from "node:os";
29851
- 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";
29852
31348
 
29853
31349
  // ../core/dist/kg/kg-service-client.js
29854
31350
  var KG_SERVICE_PORT_DEFAULT = 9997;
@@ -29859,8 +31355,8 @@ function port() {
29859
31355
  const n = Number.parseInt(env, 10);
29860
31356
  return Number.isFinite(n) && n > 0 ? n : KG_SERVICE_PORT_DEFAULT;
29861
31357
  }
29862
- function url(path60) {
29863
- return `http://127.0.0.1:${port()}${path60}`;
31358
+ function url(path66) {
31359
+ return `http://127.0.0.1:${port()}${path66}`;
29864
31360
  }
29865
31361
  function kgServiceHealthUrl() {
29866
31362
  return url("/health");
@@ -29938,40 +31434,40 @@ init_output();
29938
31434
  // src/commands/kg-status.ts
29939
31435
  init_storage_paths();
29940
31436
  init_workspace_name();
29941
- import fs48 from "node:fs";
29942
- import { homedir as homedir31 } from "node:os";
29943
- 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";
29944
31440
  init_output();
29945
31441
  function olamHome4() {
29946
- return process.env.OLAM_HOME ?? path52.join(homedir31(), ".olam");
31442
+ return process.env.OLAM_HOME ?? path58.join(homedir35(), ".olam");
29947
31443
  }
29948
31444
  function kgRoot2() {
29949
- return path52.join(olamHome4(), "kg");
31445
+ return path58.join(olamHome4(), "kg");
29950
31446
  }
29951
31447
  function worldsRoot2() {
29952
- return path52.join(olamHome4(), "worlds");
31448
+ return path58.join(olamHome4(), "worlds");
29953
31449
  }
29954
31450
  function dirSizeBytes2(dir) {
29955
- if (!fs48.existsSync(dir)) return 0;
31451
+ if (!fs54.existsSync(dir)) return 0;
29956
31452
  let total = 0;
29957
31453
  const stack = [dir];
29958
31454
  while (stack.length > 0) {
29959
31455
  const cur = stack.pop();
29960
31456
  let entries;
29961
31457
  try {
29962
- entries = fs48.readdirSync(cur, { withFileTypes: true });
31458
+ entries = fs54.readdirSync(cur, { withFileTypes: true });
29963
31459
  } catch {
29964
31460
  continue;
29965
31461
  }
29966
31462
  for (const entry of entries) {
29967
- const full = path52.join(cur, entry.name);
31463
+ const full = path58.join(cur, entry.name);
29968
31464
  if (entry.isSymbolicLink()) continue;
29969
31465
  if (entry.isDirectory()) {
29970
31466
  stack.push(full);
29971
31467
  continue;
29972
31468
  }
29973
31469
  try {
29974
- total += fs48.statSync(full).size;
31470
+ total += fs54.statSync(full).size;
29975
31471
  } catch {
29976
31472
  }
29977
31473
  }
@@ -29985,10 +31481,10 @@ function formatBytes5(n) {
29985
31481
  return `${(n / 1024 / 1024 / 1024).toFixed(2)} GB`;
29986
31482
  }
29987
31483
  function readFreshness(workspace) {
29988
- const file = path52.join(kgPristinePath(workspace), "freshness.json");
29989
- if (!fs48.existsSync(file)) return null;
31484
+ const file = path58.join(kgPristinePath(workspace), "freshness.json");
31485
+ if (!fs54.existsSync(file)) return null;
29990
31486
  try {
29991
- const raw = JSON.parse(fs48.readFileSync(file, "utf-8"));
31487
+ const raw = JSON.parse(fs54.readFileSync(file, "utf-8"));
29992
31488
  if (raw && typeof raw === "object") return raw;
29993
31489
  return null;
29994
31490
  } catch {
@@ -29996,10 +31492,10 @@ function readFreshness(workspace) {
29996
31492
  }
29997
31493
  }
29998
31494
  function readOverlayNodeCount(graphifyOutDir) {
29999
- const graphPath = path52.join(graphifyOutDir, "graph.json");
30000
- if (!fs48.existsSync(graphPath)) return null;
31495
+ const graphPath = path58.join(graphifyOutDir, "graph.json");
31496
+ if (!fs54.existsSync(graphPath)) return null;
30001
31497
  try {
30002
- const raw = JSON.parse(fs48.readFileSync(graphPath, "utf-8"));
31498
+ const raw = JSON.parse(fs54.readFileSync(graphPath, "utf-8"));
30003
31499
  if (raw && typeof raw === "object") {
30004
31500
  const nodes = raw.nodes;
30005
31501
  if (Array.isArray(nodes)) return nodes.length;
@@ -30011,28 +31507,28 @@ function readOverlayNodeCount(graphifyOutDir) {
30011
31507
  }
30012
31508
  function listOverlays() {
30013
31509
  const root = worldsRoot2();
30014
- if (!fs48.existsSync(root)) return [];
31510
+ if (!fs54.existsSync(root)) return [];
30015
31511
  const records = [];
30016
31512
  let worldDirs;
30017
31513
  try {
30018
- worldDirs = fs48.readdirSync(root, { withFileTypes: true });
31514
+ worldDirs = fs54.readdirSync(root, { withFileTypes: true });
30019
31515
  } catch {
30020
31516
  return [];
30021
31517
  }
30022
31518
  for (const worldEntry of worldDirs) {
30023
31519
  if (!worldEntry.isDirectory()) continue;
30024
31520
  const worldId = worldEntry.name;
30025
- const worldDir = path52.join(root, worldId);
31521
+ const worldDir = path58.join(root, worldId);
30026
31522
  let cloneDirs;
30027
31523
  try {
30028
- cloneDirs = fs48.readdirSync(worldDir, { withFileTypes: true });
31524
+ cloneDirs = fs54.readdirSync(worldDir, { withFileTypes: true });
30029
31525
  } catch {
30030
31526
  continue;
30031
31527
  }
30032
31528
  for (const cloneEntry of cloneDirs) {
30033
31529
  if (!cloneEntry.isDirectory()) continue;
30034
- const graphifyOut = path52.join(worldDir, cloneEntry.name, "graphify-out");
30035
- if (!fs48.existsSync(graphifyOut)) continue;
31530
+ const graphifyOut = path58.join(worldDir, cloneEntry.name, "graphify-out");
31531
+ if (!fs54.existsSync(graphifyOut)) continue;
30036
31532
  records.push({
30037
31533
  world_id: worldId,
30038
31534
  clone_dir: cloneEntry.name,
@@ -30046,11 +31542,11 @@ function listOverlays() {
30046
31542
  }
30047
31543
  function listPristines(overlays) {
30048
31544
  const root = kgRoot2();
30049
- if (!fs48.existsSync(root)) return [];
31545
+ if (!fs54.existsSync(root)) return [];
30050
31546
  const records = [];
30051
31547
  let entries;
30052
31548
  try {
30053
- entries = fs48.readdirSync(root, { withFileTypes: true });
31549
+ entries = fs54.readdirSync(root, { withFileTypes: true });
30054
31550
  } catch {
30055
31551
  return [];
30056
31552
  }
@@ -30063,7 +31559,7 @@ function listPristines(overlays) {
30063
31559
  continue;
30064
31560
  }
30065
31561
  const fresh = readFreshness(workspace);
30066
- const graphifyOut = path52.join(kgPristinePath(workspace), "graphify-out");
31562
+ const graphifyOut = path58.join(kgPristinePath(workspace), "graphify-out");
30067
31563
  const size = dirSizeBytes2(graphifyOut);
30068
31564
  const worldCount = overlays.filter((o) => o.clone_dir === workspace).length;
30069
31565
  records.push({
@@ -30198,11 +31694,11 @@ function registerKgStatusCommand(kg) {
30198
31694
  init_storage_paths();
30199
31695
  init_workspace_name();
30200
31696
  init_output();
30201
- import { spawn as spawn10 } from "node:child_process";
30202
- import fs49 from "node:fs";
30203
- 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";
30204
31700
  function pidFilePath(workspace) {
30205
- return path53.join(kgPristinePath(workspace), ".watch.pid");
31701
+ return path59.join(kgPristinePath(workspace), ".watch.pid");
30206
31702
  }
30207
31703
  function isPidAlive3(pid) {
30208
31704
  if (!Number.isInteger(pid) || pid <= 0) return false;
@@ -30217,39 +31713,39 @@ function isPidAlive3(pid) {
30217
31713
  }
30218
31714
  function readAndClassifyPid(workspace) {
30219
31715
  const file = pidFilePath(workspace);
30220
- if (!fs49.existsSync(file)) return { status: "no-pidfile", pid: null };
31716
+ if (!fs55.existsSync(file)) return { status: "no-pidfile", pid: null };
30221
31717
  let pid;
30222
31718
  try {
30223
- const raw = fs49.readFileSync(file, "utf-8").trim();
31719
+ const raw = fs55.readFileSync(file, "utf-8").trim();
30224
31720
  pid = Number.parseInt(raw, 10);
30225
31721
  } catch {
30226
- fs49.rmSync(file, { force: true });
31722
+ fs55.rmSync(file, { force: true });
30227
31723
  return { status: "stale-reclaimed", pid: null };
30228
31724
  }
30229
31725
  if (!Number.isInteger(pid) || pid <= 0) {
30230
- fs49.rmSync(file, { force: true });
31726
+ fs55.rmSync(file, { force: true });
30231
31727
  return { status: "stale-reclaimed", pid: null };
30232
31728
  }
30233
31729
  if (isPidAlive3(pid)) return { status: "active", pid };
30234
- fs49.rmSync(file, { force: true });
31730
+ fs55.rmSync(file, { force: true });
30235
31731
  return { status: "stale-reclaimed", pid: null };
30236
31732
  }
30237
31733
  function writePidFile(workspace, pid) {
30238
31734
  const file = pidFilePath(workspace);
30239
- const dir = path53.dirname(file);
30240
- fs49.mkdirSync(dir, { recursive: true });
30241
- 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" });
30242
31738
  }
30243
31739
  function removePidFile(workspace) {
30244
31740
  const file = pidFilePath(workspace);
30245
31741
  try {
30246
- fs49.rmSync(file, { force: true });
31742
+ fs55.rmSync(file, { force: true });
30247
31743
  } catch {
30248
31744
  }
30249
31745
  }
30250
31746
  async function runKgWatch(workspaceArg, opts, deps = {}) {
30251
31747
  const cwd = deps.cwd ?? opts.cwd ?? process.cwd();
30252
- const name = workspaceArg ?? path53.basename(cwd).toLowerCase();
31748
+ const name = workspaceArg ?? path59.basename(cwd).toLowerCase();
30253
31749
  try {
30254
31750
  validateWorkspaceName(name);
30255
31751
  } catch (err) {
@@ -30257,7 +31753,7 @@ async function runKgWatch(workspaceArg, opts, deps = {}) {
30257
31753
  return { exitCode: 1, pidWritten: false };
30258
31754
  }
30259
31755
  const pristinePath = kgPristinePath(name);
30260
- const graphPath = path53.join(pristinePath, "graphify-out", "graph.json");
31756
+ const graphPath = path59.join(pristinePath, "graphify-out", "graph.json");
30261
31757
  const pidState = readAndClassifyPid(name);
30262
31758
  if (pidState.status === "active") {
30263
31759
  printError(
@@ -30268,7 +31764,7 @@ async function runKgWatch(workspaceArg, opts, deps = {}) {
30268
31764
  if (pidState.status === "stale-reclaimed") {
30269
31765
  printInfo("stale-pid", `reclaimed dead PID file at ${pidFilePath(name)}`);
30270
31766
  }
30271
- const spawnFn = deps.spawnImpl ?? spawn10;
31767
+ const spawnFn = deps.spawnImpl ?? spawn11;
30272
31768
  const child = spawnFn(
30273
31769
  "graphify",
30274
31770
  [cwd, "--watch", "--update", "--graph", graphPath],
@@ -30320,7 +31816,7 @@ function registerKgWatchCommand(kg) {
30320
31816
  }
30321
31817
 
30322
31818
  // src/commands/kg-classify.ts
30323
- import pc31 from "picocolors";
31819
+ import pc33 from "picocolors";
30324
31820
  init_output();
30325
31821
  function registerKgClassifyCommand(kg) {
30326
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) => {
@@ -30330,7 +31826,7 @@ function registerKgClassifyCommand(kg) {
30330
31826
  process.stdout.write(JSON.stringify(result, null, 2) + "\n");
30331
31827
  return;
30332
31828
  }
30333
- 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");
30334
31830
  printInfo("route", `${routeStr} (layer ${result.layer}, took ${result.took_ms}ms)`);
30335
31831
  printInfo("confidence", String(result.confidence));
30336
31832
  printInfo("reason", result.reason);
@@ -30352,7 +31848,7 @@ function registerKgClassifyCommand(kg) {
30352
31848
  }
30353
31849
 
30354
31850
  // src/commands/kg-doctor.ts
30355
- import pc32 from "picocolors";
31851
+ import pc34 from "picocolors";
30356
31852
  init_output();
30357
31853
  async function runProbes() {
30358
31854
  const results = [];
@@ -30471,12 +31967,12 @@ function registerKgDoctorCommand(kg) {
30471
31967
  } else {
30472
31968
  printHeader("kg-service doctor");
30473
31969
  for (const p of probes) {
30474
- 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");
30475
31971
  const label = `${badge} ${p.name}`;
30476
31972
  const detail = p.detail ?? "";
30477
31973
  printInfo(label, detail);
30478
31974
  if (p.remedy) {
30479
- process.stderr.write(` ${pc32.dim("remedy:")} ${p.remedy}
31975
+ process.stderr.write(` ${pc34.dim("remedy:")} ${p.remedy}
30480
31976
  `);
30481
31977
  }
30482
31978
  }
@@ -30494,14 +31990,14 @@ function registerKgDoctorCommand(kg) {
30494
31990
  }
30495
31991
 
30496
31992
  // src/commands/kg-install-hook.ts
30497
- import * as fs51 from "node:fs";
30498
- import * as path55 from "node:path";
30499
- 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";
30500
31996
 
30501
31997
  // ../core/dist/world/merge-settings.js
30502
- import * as fs50 from "node:fs";
30503
- import * as path54 from "node:path";
30504
- 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";
30505
32001
  function mergeHomeSettingsJson(filePath, options) {
30506
32002
  let settings;
30507
32003
  try {
@@ -30560,10 +32056,10 @@ function mergeHomeSettingsJson(filePath, options) {
30560
32056
  return { status: "installed", message: `settings.json updated at ${filePath}` };
30561
32057
  }
30562
32058
  function readSettings(filePath) {
30563
- if (!fs50.existsSync(filePath)) {
32059
+ if (!fs56.existsSync(filePath)) {
30564
32060
  return {};
30565
32061
  }
30566
- const raw = fs50.readFileSync(filePath, "utf-8");
32062
+ const raw = fs56.readFileSync(filePath, "utf-8");
30567
32063
  if (!raw.trim())
30568
32064
  return {};
30569
32065
  return JSON.parse(raw);
@@ -30584,13 +32080,13 @@ function isHookSentinelPresent(matchers, sentinel) {
30584
32080
  return false;
30585
32081
  }
30586
32082
  function atomicWriteJson(filePath, data) {
30587
- const dir = path54.dirname(filePath);
30588
- fs50.mkdirSync(dir, { recursive: true });
30589
- 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");
30590
32086
  const tmp = `${filePath}.tmp.${process.pid}.${rand}`;
30591
32087
  const json = JSON.stringify(data, null, 2) + "\n";
30592
- fs50.writeFileSync(tmp, json, { mode: 420 });
30593
- fs50.renameSync(tmp, filePath);
32088
+ fs56.writeFileSync(tmp, json, { mode: 420 });
32089
+ fs56.renameSync(tmp, filePath);
30594
32090
  }
30595
32091
 
30596
32092
  // ../core/dist/kg/hook-template.js
@@ -30642,15 +32138,15 @@ function buildHookMatcherEntry(opts) {
30642
32138
  init_output();
30643
32139
  function settingsPathFor(scope) {
30644
32140
  if (scope === "user") {
30645
- return path55.join(os28.homedir(), ".claude", "settings.json");
32141
+ return path61.join(os32.homedir(), ".claude", "settings.json");
30646
32142
  }
30647
- return path55.join(process.cwd(), ".claude", "settings.json");
32143
+ return path61.join(process.cwd(), ".claude", "settings.json");
30648
32144
  }
30649
32145
  function backup(filePath) {
30650
- if (!fs51.existsSync(filePath)) return null;
32146
+ if (!fs57.existsSync(filePath)) return null;
30651
32147
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
30652
32148
  const backupPath = `${filePath}.olam-bak.${ts}`;
30653
- fs51.copyFileSync(filePath, backupPath);
32149
+ fs57.copyFileSync(filePath, backupPath);
30654
32150
  return backupPath;
30655
32151
  }
30656
32152
  function registerKgInstallHookCommand(kg) {
@@ -30658,9 +32154,9 @@ function registerKgInstallHookCommand(kg) {
30658
32154
  const scope = opts.scope === "user" ? "user" : "project";
30659
32155
  const filePath = settingsPathFor(scope);
30660
32156
  try {
30661
- fs51.mkdirSync(path55.dirname(filePath), { recursive: true });
32157
+ fs57.mkdirSync(path61.dirname(filePath), { recursive: true });
30662
32158
  } catch (err) {
30663
- 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)}`);
30664
32160
  process.exitCode = 1;
30665
32161
  return;
30666
32162
  }
@@ -30684,7 +32180,7 @@ function registerKgInstallHookCommand(kg) {
30684
32180
  printInfo("kg-service hook", `already installed at ${filePath}`);
30685
32181
  if (backupPath) {
30686
32182
  try {
30687
- fs51.unlinkSync(backupPath);
32183
+ fs57.unlinkSync(backupPath);
30688
32184
  } catch {
30689
32185
  }
30690
32186
  }
@@ -30702,15 +32198,15 @@ function registerKgInstallHookCommand(kg) {
30702
32198
  }
30703
32199
 
30704
32200
  // src/commands/kg-uninstall-hook.ts
30705
- import * as fs52 from "node:fs";
30706
- import * as path56 from "node:path";
30707
- 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";
30708
32204
  init_output();
30709
32205
  function settingsPathFor2(scope) {
30710
32206
  if (scope === "user") {
30711
- return path56.join(os29.homedir(), ".claude", "settings.json");
32207
+ return path62.join(os33.homedir(), ".claude", "settings.json");
30712
32208
  }
30713
- return path56.join(process.cwd(), ".claude", "settings.json");
32209
+ return path62.join(process.cwd(), ".claude", "settings.json");
30714
32210
  }
30715
32211
  function dropSentinel(matchers) {
30716
32212
  let changed = false;
@@ -30740,13 +32236,13 @@ function registerKgUninstallHookCommand(kg) {
30740
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) => {
30741
32237
  const scope = opts.scope === "user" ? "user" : "project";
30742
32238
  const filePath = settingsPathFor2(scope);
30743
- if (!fs52.existsSync(filePath)) {
32239
+ if (!fs58.existsSync(filePath)) {
30744
32240
  printInfo("kg-service hook", `no settings.json at ${filePath} \u2014 nothing to remove`);
30745
32241
  return;
30746
32242
  }
30747
32243
  let settings;
30748
32244
  try {
30749
- const raw = fs52.readFileSync(filePath, "utf-8");
32245
+ const raw = fs58.readFileSync(filePath, "utf-8");
30750
32246
  settings = raw.trim() ? JSON.parse(raw) : {};
30751
32247
  } catch (err) {
30752
32248
  printError(`could not parse ${filePath}: ${err instanceof Error ? err.message : String(err)}`);
@@ -30766,7 +32262,7 @@ function registerKgUninstallHookCommand(kg) {
30766
32262
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
30767
32263
  const backupPath = `${filePath}.olam-bak.${ts}`;
30768
32264
  try {
30769
- fs52.copyFileSync(filePath, backupPath);
32265
+ fs58.copyFileSync(filePath, backupPath);
30770
32266
  } catch {
30771
32267
  }
30772
32268
  const next = {
@@ -30785,7 +32281,7 @@ function registerKgUninstallHookCommand(kg) {
30785
32281
  }
30786
32282
  }
30787
32283
  try {
30788
- fs52.writeFileSync(filePath, JSON.stringify(next, null, 2) + "\n");
32284
+ fs58.writeFileSync(filePath, JSON.stringify(next, null, 2) + "\n");
30789
32285
  printSuccess(`kg-service hook removed from ${filePath}`);
30790
32286
  printInfo("backup", backupPath);
30791
32287
  } catch (err) {
@@ -30859,20 +32355,20 @@ function registerKgSavingsCommand(kg) {
30859
32355
  // src/commands/kg-build.ts
30860
32356
  function resolveWorkspace(arg) {
30861
32357
  const cwd = process.cwd();
30862
- const name = arg ?? path57.basename(cwd).toLowerCase();
32358
+ const name = arg ?? path63.basename(cwd).toLowerCase();
30863
32359
  validateWorkspaceName(name);
30864
32360
  return { name, sourcePath: cwd };
30865
32361
  }
30866
32362
  function toContainerPath(hostPath) {
30867
- const home = os30.homedir();
30868
- const resolved = path57.resolve(hostPath);
30869
- 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) {
30870
32366
  throw new Error(
30871
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.`
30872
32368
  );
30873
32369
  }
30874
- const rel = path57.relative(home, resolved);
30875
- 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("/"));
30876
32372
  }
30877
32373
  async function runKgBuild(workspaceArg, options = {}) {
30878
32374
  let workspace;
@@ -30890,7 +32386,7 @@ async function runKgBuild(workspaceArg, options = {}) {
30890
32386
  return { exitCode: 2 };
30891
32387
  }
30892
32388
  const outDir = kgPristinePath(workspace.name);
30893
- fs53.mkdirSync(outDir, { recursive: true });
32389
+ fs59.mkdirSync(outDir, { recursive: true });
30894
32390
  const human = !options.json;
30895
32391
  if (human) {
30896
32392
  printInfo("kg build", `workspace=${workspace.name} source=${workspace.sourcePath}`);
@@ -30923,12 +32419,12 @@ async function runKgBuild(workspaceArg, options = {}) {
30923
32419
  workspace: workspace.name,
30924
32420
  graphify_path: "container"
30925
32421
  };
30926
- fs53.writeFileSync(
30927
- path57.join(outDir, "freshness.json"),
32422
+ fs59.writeFileSync(
32423
+ path63.join(outDir, "freshness.json"),
30928
32424
  JSON.stringify(freshness, null, 2) + "\n",
30929
32425
  "utf-8"
30930
32426
  );
30931
- const finalOut = path57.join(outDir, "graphify-out");
32427
+ const finalOut = path63.join(outDir, "graphify-out");
30932
32428
  if (options.json) {
30933
32429
  process.stdout.write(JSON.stringify(freshness) + "\n");
30934
32430
  } else {
@@ -31205,17 +32701,17 @@ init_manager();
31205
32701
  init_context();
31206
32702
  init_output();
31207
32703
  import { spawnSync as defaultSpawnSync } from "node:child_process";
31208
- import * as fs54 from "node:fs";
31209
- import * as os31 from "node:os";
31210
- 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";
31211
32707
  function devboxContainerName(worldId) {
31212
32708
  return `olam-${worldId}-devbox`;
31213
32709
  }
31214
32710
  function olamHomeDir() {
31215
- return process.env["OLAM_HOME"] ?? path58.join(os31.homedir(), ".olam");
32711
+ return process.env["OLAM_HOME"] ?? path64.join(os35.homedir(), ".olam");
31216
32712
  }
31217
- function defaultRestartContainer(name, spawn11 = defaultSpawnSync) {
31218
- const r = spawn11("docker", ["restart", "--time", "30", name], {
32713
+ function defaultRestartContainer(name, spawn12 = defaultSpawnSync) {
32714
+ const r = spawn12("docker", ["restart", "--time", "30", name], {
31219
32715
  encoding: "utf-8",
31220
32716
  stdio: ["ignore", "pipe", "pipe"]
31221
32717
  });
@@ -31225,8 +32721,8 @@ function defaultRestartContainer(name, spawn11 = defaultSpawnSync) {
31225
32721
  };
31226
32722
  }
31227
32723
  function defaultAppendAuditLog(homeDir, line) {
31228
- fs54.mkdirSync(homeDir, { recursive: true });
31229
- 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", {
31230
32726
  encoding: "utf-8"
31231
32727
  });
31232
32728
  }
@@ -31271,19 +32767,19 @@ async function doRekey(worldId, deps) {
31271
32767
  );
31272
32768
  const rotatedAt = deps.now().toISOString();
31273
32769
  const homeDir = deps.olamHomeDir();
31274
- const worldDir = path58.join(homeDir, "worlds", worldId);
31275
- fs54.mkdirSync(worldDir, { recursive: true });
31276
- 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");
31277
32773
  const payload = {
31278
32774
  worldRoleName,
31279
32775
  password,
31280
32776
  rotatedAt
31281
32777
  };
31282
- fs54.writeFileSync(credentialsPath, JSON.stringify(payload, null, 2) + "\n", {
32778
+ fs60.writeFileSync(credentialsPath, JSON.stringify(payload, null, 2) + "\n", {
31283
32779
  encoding: "utf-8",
31284
32780
  mode: 384
31285
32781
  });
31286
- fs54.chmodSync(credentialsPath, 384);
32782
+ fs60.chmodSync(credentialsPath, 384);
31287
32783
  const restart = deps.restartContainer(devboxContainerName(worldId));
31288
32784
  deps.appendAuditLog(`${rotatedAt} ${worldId} rekey`);
31289
32785
  if (!restart.ok) {
@@ -31347,18 +32843,18 @@ function registerRekey(program2) {
31347
32843
  }
31348
32844
 
31349
32845
  // src/pleri-config.ts
31350
- import * as fs55 from "node:fs";
31351
- import * as path59 from "node:path";
32846
+ import * as fs61 from "node:fs";
32847
+ import * as path65 from "node:path";
31352
32848
  function isPleriConfigured(configDir = process.env.OLAM_CONFIG_DIR ?? ".olam") {
31353
32849
  if (process.env.PLERI_BASE_URL) {
31354
32850
  return true;
31355
32851
  }
31356
- const configPath = path59.join(configDir, "config.yaml");
31357
- if (!fs55.existsSync(configPath)) {
32852
+ const configPath = path65.join(configDir, "config.yaml");
32853
+ if (!fs61.existsSync(configPath)) {
31358
32854
  return false;
31359
32855
  }
31360
32856
  try {
31361
- const contents = fs55.readFileSync(configPath, "utf8");
32857
+ const contents = fs61.readFileSync(configPath, "utf8");
31362
32858
  return /^[^#\n]*\bpleri:/m.test(contents);
31363
32859
  } catch {
31364
32860
  return false;
@@ -31423,4 +32919,6 @@ registerKg(program);
31423
32919
  registerConfig(program);
31424
32920
  registerRepos(program);
31425
32921
  registerRunbooks(program);
32922
+ registerSkillsSource(program);
32923
+ registerSkills(program);
31426
32924
  program.parse();