@pleri/olam-cli 0.1.119 → 0.1.125

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 (60) hide show
  1. package/dist/commands/bootstrap.d.ts +4 -0
  2. package/dist/commands/bootstrap.d.ts.map +1 -1
  3. package/dist/commands/bootstrap.js +64 -0
  4. package/dist/commands/bootstrap.js.map +1 -1
  5. package/dist/commands/mcp/install-shared.d.ts +2 -0
  6. package/dist/commands/mcp/install-shared.d.ts.map +1 -1
  7. package/dist/commands/mcp/install-shared.js +13 -0
  8. package/dist/commands/mcp/install-shared.js.map +1 -1
  9. package/dist/commands/mcp/uninstall.d.ts.map +1 -1
  10. package/dist/commands/mcp/uninstall.js +1 -10
  11. package/dist/commands/mcp/uninstall.js.map +1 -1
  12. package/dist/commands/memory/_paths.d.ts +15 -0
  13. package/dist/commands/memory/_paths.d.ts.map +1 -0
  14. package/dist/commands/memory/_paths.js +34 -0
  15. package/dist/commands/memory/_paths.js.map +1 -0
  16. package/dist/commands/memory/index.d.ts +17 -0
  17. package/dist/commands/memory/index.d.ts.map +1 -0
  18. package/dist/commands/memory/index.js +34 -0
  19. package/dist/commands/memory/index.js.map +1 -0
  20. package/dist/commands/memory/install.d.ts +57 -0
  21. package/dist/commands/memory/install.d.ts.map +1 -0
  22. package/dist/commands/memory/install.js +114 -0
  23. package/dist/commands/memory/install.js.map +1 -0
  24. package/dist/commands/memory/logs.d.ts +15 -0
  25. package/dist/commands/memory/logs.d.ts.map +1 -0
  26. package/dist/commands/memory/logs.js +45 -0
  27. package/dist/commands/memory/logs.js.map +1 -0
  28. package/dist/commands/memory/secret.d.ts +16 -0
  29. package/dist/commands/memory/secret.d.ts.map +1 -0
  30. package/dist/commands/memory/secret.js +79 -0
  31. package/dist/commands/memory/secret.js.map +1 -0
  32. package/dist/commands/memory/start.d.ts +23 -0
  33. package/dist/commands/memory/start.d.ts.map +1 -0
  34. package/dist/commands/memory/start.js +166 -0
  35. package/dist/commands/memory/start.js.map +1 -0
  36. package/dist/commands/memory/status.d.ts +25 -0
  37. package/dist/commands/memory/status.d.ts.map +1 -0
  38. package/dist/commands/memory/status.js +101 -0
  39. package/dist/commands/memory/status.js.map +1 -0
  40. package/dist/commands/memory/stop.d.ts +12 -0
  41. package/dist/commands/memory/stop.d.ts.map +1 -0
  42. package/dist/commands/memory/stop.js +81 -0
  43. package/dist/commands/memory/stop.js.map +1 -0
  44. package/dist/commands/memory/uninstall.d.ts +19 -0
  45. package/dist/commands/memory/uninstall.d.ts.map +1 -0
  46. package/dist/commands/memory/uninstall.js +60 -0
  47. package/dist/commands/memory/uninstall.js.map +1 -0
  48. package/dist/commands/seed.d.ts +27 -0
  49. package/dist/commands/seed.d.ts.map +1 -0
  50. package/dist/commands/seed.js +303 -0
  51. package/dist/commands/seed.js.map +1 -0
  52. package/dist/image-digests.json +1 -1
  53. package/dist/index.js +1643 -183
  54. package/dist/index.js.map +1 -1
  55. package/dist/lib/memory-secret.d.ts +48 -0
  56. package/dist/lib/memory-secret.d.ts.map +1 -0
  57. package/dist/lib/memory-secret.js +92 -0
  58. package/dist/lib/memory-secret.js.map +1 -0
  59. package/dist/mcp-server.js +481 -65
  60. package/package.json +4 -2
package/dist/index.js CHANGED
@@ -21,11 +21,11 @@ import * as path from "node:path";
21
21
  import { fileURLToPath } from "node:url";
22
22
  function readCliVersion() {
23
23
  try {
24
- const here2 = path.dirname(fileURLToPath(import.meta.url));
24
+ const here3 = path.dirname(fileURLToPath(import.meta.url));
25
25
  for (const candidate of [
26
- path.join(here2, "package.json"),
27
- path.join(here2, "..", "package.json"),
28
- path.join(here2, "..", "..", "package.json")
26
+ path.join(here3, "package.json"),
27
+ path.join(here3, "..", "package.json"),
28
+ path.join(here3, "..", "..", "package.json")
29
29
  ]) {
30
30
  if (fs.existsSync(candidate)) {
31
31
  const pkg = JSON.parse(fs.readFileSync(candidate, "utf-8"));
@@ -4276,7 +4276,7 @@ var init_version2 = __esm({
4276
4276
 
4277
4277
  // ../core/dist/world/repo-manifest.js
4278
4278
  import { existsSync as existsSync2, lstatSync, readFileSync as readFileSync2 } from "node:fs";
4279
- import { join as join2 } from "node:path";
4279
+ import { join as join2, resolve, sep } from "node:path";
4280
4280
  import YAML from "yaml";
4281
4281
  function bootstrapStepCmd(entry) {
4282
4282
  return typeof entry === "string" ? entry : entry.cmd;
@@ -4322,6 +4322,23 @@ function rejectForbiddenKeys(value, path56, rejectSource) {
4322
4322
  function unknownTopLevelKeys(parsed) {
4323
4323
  return Object.keys(parsed).filter((k) => !KNOWN_TOP_LEVEL_KEYS.has(k));
4324
4324
  }
4325
+ function deepMergeManifest(base, overlay) {
4326
+ const out = { ...base };
4327
+ for (const key of Object.keys(overlay)) {
4328
+ if (FORBIDDEN_KEYS.has(key))
4329
+ continue;
4330
+ const overlayValue = overlay[key];
4331
+ if (overlayValue === void 0)
4332
+ continue;
4333
+ const baseValue = base[key];
4334
+ const bothObjects = isPlainObject(baseValue) && isPlainObject(overlayValue);
4335
+ out[key] = bothObjects ? deepMergeManifest(baseValue, overlayValue) : overlayValue;
4336
+ }
4337
+ return out;
4338
+ }
4339
+ function isPlainObject(value) {
4340
+ return value !== null && typeof value === "object" && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
4341
+ }
4325
4342
  function loadRepoManifest(repoDir) {
4326
4343
  const olamPath = join2(repoDir, ".olam.yaml");
4327
4344
  const adbPath = join2(repoDir, ".adb.yaml");
@@ -4352,7 +4369,37 @@ function loadRepoManifest(repoDir) {
4352
4369
  throw new Error(`[manifest] ${manifestPath2}: expected a YAML mapping at the top level`);
4353
4370
  }
4354
4371
  rejectForbiddenKeys(parsed, manifestPath2, true);
4355
- const body = RepoManifestSchema.parse(parsed);
4372
+ let mergedParsed = parsed;
4373
+ const inheritsRaw = parsed["inherits"];
4374
+ if (typeof inheritsRaw === "string" && inheritsRaw.length > 0) {
4375
+ if (source !== "olam") {
4376
+ throw new Error(`[manifest] ${manifestPath2}: \`inherits\` only valid in .olam.yaml (found in .adb.yaml \u2014 drop the field)`);
4377
+ }
4378
+ const inheritedAbs = resolve(repoDir, inheritsRaw);
4379
+ const repoDirAbs = resolve(repoDir);
4380
+ if (!inheritedAbs.startsWith(repoDirAbs + sep) && inheritedAbs !== repoDirAbs) {
4381
+ throw new Error(`[manifest] ${manifestPath2}: inherits target "${inheritsRaw}" escapes repo dir`);
4382
+ }
4383
+ if (!existsSync2(inheritedAbs)) {
4384
+ throw new Error(`[manifest] ${manifestPath2}: inherits target "${inheritsRaw}" not found`);
4385
+ }
4386
+ const inheritedStat = lstatSync(inheritedAbs);
4387
+ if (inheritedStat.isSymbolicLink()) {
4388
+ throw new Error(`[manifest] ${manifestPath2}: inherits target "${inheritsRaw}" is a symlink (not permitted)`);
4389
+ }
4390
+ const inheritedRaw = readFileSync2(inheritedAbs, "utf-8");
4391
+ const inheritedParsed = YAML.parse(inheritedRaw, { maxAliasCount: 100 });
4392
+ if (inheritedParsed !== null && inheritedParsed !== void 0) {
4393
+ if (typeof inheritedParsed !== "object" || Array.isArray(inheritedParsed)) {
4394
+ throw new Error(`[manifest] ${inheritedAbs}: expected a YAML mapping at the top level`);
4395
+ }
4396
+ rejectForbiddenKeys(inheritedParsed, inheritedAbs, true);
4397
+ const overlay = { ...parsed };
4398
+ delete overlay["inherits"];
4399
+ mergedParsed = deepMergeManifest(inheritedParsed, overlay);
4400
+ }
4401
+ }
4402
+ const body = RepoManifestSchema.parse(mergedParsed);
4356
4403
  if (parsed["version"] === void 0) {
4357
4404
  console.warn(`[manifest] ${manifestPath2}: missing "version: ${MANIFEST_VERSION}" field \u2014 add it to suppress this warning (backward-compat: file still parses)`);
4358
4405
  }
@@ -4390,7 +4437,44 @@ var init_repo_manifest = __esm({
4390
4437
  // - 'world' (default): container is per-world (today's behaviour).
4391
4438
  // - 'shared': container is shared across worlds (consumed by later phases).
4392
4439
  // TODO(phase-D): enforce shared-service deduplication in planManifestPipeline.
4393
- scope: external_exports.enum(["world", "shared"]).optional()
4440
+ scope: external_exports.enum(["world", "shared"]).optional(),
4441
+ // olam-hybrid-shared-postgres Phase A task A8.
4442
+ // Declares which postgres template DB(s) Olam should clone at world
4443
+ // create time. Consumed by `applyPostgresTemplateClone` (task A3 in
4444
+ // manager.ts) which runs `CREATE DATABASE <world-db> TEMPLATE <name>`
4445
+ // per entry. Accepts either a single string (single-DB repo) or an
4446
+ // array (atlas-core → ['atlas_common_seed', 'atlas_merchant_seed'];
4447
+ // atlas-pay → adds 'atlas_pay_seed' per Phase B Decision 17).
4448
+ seed_template: external_exports.union([external_exports.string().min(1), external_exports.array(external_exports.string().min(1)).nonempty()]).optional(),
4449
+ // Optional hash bound to the seed_template content for cross-machine
4450
+ // drift detection. The A1 bake script writes this value via .adb.yaml
4451
+ // post-bake (operator pastes from seed/seed-hash.txt; auto-write CI
4452
+ // tool is a Phase D follow-up per the plan's Out of scope).
4453
+ seed_template_hash: external_exports.string().optional(),
4454
+ // olam-hybrid-shared-postgres Phase A task A5 (closes OQ15 plan-killer).
4455
+ // Per-clone SQL hook: after `CREATE DATABASE <world-db> TEMPLATE <seed>`,
4456
+ // run these statements against the cloned world-DB. Atlas-core uses this
4457
+ // to UPDATE `merchants.identifier` so `Current.merchant` (which resolves
4458
+ // via `Merchant.find_by(identifier: ENV['POSTGRESQL_MERCHANT_DATABASE'])`)
4459
+ // matches the per-world merchant DB name.
4460
+ //
4461
+ // Shape: { <seed_template_name>: [<sql statement>, ...] }
4462
+ // The seed_template_name key selects which cloned DB the statements run
4463
+ // in. Order is preserved within each seed's list.
4464
+ //
4465
+ // Interpolation in statements:
4466
+ // ${WORLD_ID} → world id (e.g. frost-oak-5916)
4467
+ // ${WORLD_DB:<seed>} → corresponding world-DB name for that seed
4468
+ // (e.g. ${WORLD_DB:atlas_merchant_seed} →
4469
+ // atlas_merchant_world_frost-oak-5916)
4470
+ //
4471
+ // SECURITY INVARIANT: statements come from the operator's own checked-out
4472
+ // `.olam.yaml` (same trust class as `BootstrapStep.cmd`). They run as the
4473
+ // singleton's `development` superuser today; A4's per-world role lands
4474
+ // later in the seam. Statements are NOT exposed to operator-controlled
4475
+ // env variables — only the two whitelisted placeholders above. No shell
4476
+ // expansion, no DOLLAR-name expansion.
4477
+ post_clone_sql: external_exports.record(external_exports.string().min(1), external_exports.array(external_exports.string().min(1))).optional()
4394
4478
  }).passthrough();
4395
4479
  deploySchema = external_exports.object({
4396
4480
  tags: external_exports.array(external_exports.string()).optional()
@@ -4442,7 +4526,12 @@ var init_repo_manifest = __esm({
4442
4526
  "secrets",
4443
4527
  "bootstrap",
4444
4528
  "start",
4445
- "deploy"
4529
+ "deploy",
4530
+ // F-7 (olam-hybrid-shared-postgres dogfood finding): native inheritance —
4531
+ // `.olam.yaml` may declare `inherits: .adb.yaml` to deep-merge an adb-shaped
4532
+ // base manifest into itself. Retires the atlas-one `bin/olam-create-wrap.sh`
4533
+ // stopgap. See the inheritance resolver in `loadRepoManifest`.
4534
+ "inherits"
4446
4535
  ]);
4447
4536
  FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
4448
4537
  }
@@ -5198,7 +5287,7 @@ async function safeText(res) {
5198
5287
  }
5199
5288
  }
5200
5289
  function sleep(ms) {
5201
- return new Promise((resolve12) => setTimeout(resolve12, ms));
5290
+ return new Promise((resolve14) => setTimeout(resolve14, ms));
5202
5291
  }
5203
5292
  var DEFAULT_BASE_URL, DEFAULT_TIMEOUT_MS, RETRY_COUNT, RETRY_BACKOFF_MS, AuthClient;
5204
5293
  var init_client = __esm({
@@ -5345,12 +5434,12 @@ import { existsSync as existsSync9 } from "node:fs";
5345
5434
  import * as path9 from "node:path";
5346
5435
  import { fileURLToPath as fileURLToPath2 } from "node:url";
5347
5436
  function resolveAuthServicePath() {
5348
- const here2 = fileURLToPath2(import.meta.url);
5349
- const pkgsDir = path9.resolve(path9.dirname(here2), "..", "..", "..");
5437
+ const here3 = fileURLToPath2(import.meta.url);
5438
+ const pkgsDir = path9.resolve(path9.dirname(here3), "..", "..", "..");
5350
5439
  return path9.join(pkgsDir, "auth-service");
5351
5440
  }
5352
5441
  function sleep2(ms) {
5353
- return new Promise((resolve12) => setTimeout(resolve12, ms));
5442
+ return new Promise((resolve14) => setTimeout(resolve14, ms));
5354
5443
  }
5355
5444
  var DEFAULT_PORT, DEFAULT_VOLUME, DEFAULT_CONTAINER, DEFAULT_IMAGE, AuthContainerController;
5356
5445
  var init_container = __esm({
@@ -5648,7 +5737,7 @@ var init_network = __esm({
5648
5737
  // ../adapters/dist/docker/pull.js
5649
5738
  import { spawn } from "node:child_process";
5650
5739
  function spawnAsync(cmd, args, opts = {}) {
5651
- return new Promise((resolve12) => {
5740
+ return new Promise((resolve14) => {
5652
5741
  const child = spawn(cmd, [...args], {
5653
5742
  stdio: ["ignore", "pipe", "pipe"],
5654
5743
  signal: opts.signal
@@ -5662,10 +5751,10 @@ function spawnAsync(cmd, args, opts = {}) {
5662
5751
  stderr += chunk.toString();
5663
5752
  });
5664
5753
  child.on("error", (err) => {
5665
- resolve12({ exitCode: -1, stdout, stderr: stderr + err.message });
5754
+ resolve14({ exitCode: -1, stdout, stderr: stderr + err.message });
5666
5755
  });
5667
5756
  child.on("close", (code) => {
5668
- resolve12({ exitCode: code ?? -1, stdout, stderr });
5757
+ resolve14({ exitCode: code ?? -1, stdout, stderr });
5669
5758
  });
5670
5759
  });
5671
5760
  }
@@ -6050,7 +6139,7 @@ var demuxStream, execInContainer;
6050
6139
  var init_exec = __esm({
6051
6140
  "../adapters/dist/docker/exec.js"() {
6052
6141
  "use strict";
6053
- demuxStream = (stream) => new Promise((resolve12, reject) => {
6142
+ demuxStream = (stream) => new Promise((resolve14, reject) => {
6054
6143
  const stdoutChunks = [];
6055
6144
  const stderrChunks = [];
6056
6145
  const stdout = new PassThrough();
@@ -6064,7 +6153,7 @@ var init_exec = __esm({
6064
6153
  stream.pipe(stdout);
6065
6154
  }
6066
6155
  stream.on("end", () => {
6067
- resolve12({
6156
+ resolve14({
6068
6157
  stdout: Buffer.concat(stdoutChunks).toString("utf-8"),
6069
6158
  stderr: Buffer.concat(stderrChunks).toString("utf-8")
6070
6159
  });
@@ -6260,6 +6349,19 @@ var init_provider = __esm({
6260
6349
  }
6261
6350
  const mergedEnv = { ...serviceEnv, ...config.env };
6262
6351
  const container = await createWorldContainer(this.docker, id, name, config.image, mergedEnv, config.resources, config.workspacePath, config.portOffset, config.appPorts, config.cacheArch);
6352
+ for (const networkName3 of config.extraNetworks ?? []) {
6353
+ try {
6354
+ const network = this.docker.getNetwork(networkName3);
6355
+ await network.connect({ Container: container.id });
6356
+ } catch (err) {
6357
+ const statusCode = err.statusCode;
6358
+ if (statusCode === 404) {
6359
+ throw new Error(`extra network "${networkName3}" not found \u2014 run \`olam bootstrap\` to recreate it`);
6360
+ }
6361
+ if (statusCode !== 403)
6362
+ throw err;
6363
+ }
6364
+ }
6263
6365
  return new DockerWorld(id, name, container, "running");
6264
6366
  }
6265
6367
  // -----------------------------------------------------------------------
@@ -6496,7 +6598,7 @@ var init_connection = __esm({
6496
6598
  // -----------------------------------------------------------------------
6497
6599
  async exec(host, command) {
6498
6600
  const client = await this.getConnection(host);
6499
- return new Promise((resolve12, reject) => {
6601
+ return new Promise((resolve14, reject) => {
6500
6602
  client.exec(command, (err, stream) => {
6501
6603
  if (err) {
6502
6604
  reject(new Error(`SSH exec failed on ${host}: ${err.message}`));
@@ -6511,7 +6613,7 @@ var init_connection = __esm({
6511
6613
  stderr += data.toString();
6512
6614
  });
6513
6615
  stream.on("close", (code) => {
6514
- resolve12({
6616
+ resolve14({
6515
6617
  exitCode: code ?? 0,
6516
6618
  stdout: stdout.trimEnd(),
6517
6619
  stderr: stderr.trimEnd()
@@ -6542,10 +6644,10 @@ var init_connection = __esm({
6542
6644
  throw new Error(`No SSH configuration found for host: ${host}`);
6543
6645
  }
6544
6646
  const client = new SSHClient();
6545
- return new Promise((resolve12, reject) => {
6647
+ return new Promise((resolve14, reject) => {
6546
6648
  client.on("ready", () => {
6547
6649
  this.connections.set(host, client);
6548
- resolve12(client);
6650
+ resolve14(client);
6549
6651
  }).on("error", (err) => {
6550
6652
  this.connections.delete(host);
6551
6653
  reject(new Error(`SSH connection to ${host} failed: ${err.message}`));
@@ -7270,6 +7372,7 @@ function rowToMetadata(row) {
7270
7372
  let readinessChain;
7271
7373
  let expectedServices;
7272
7374
  let appPortUrls;
7375
+ let worldDbNames;
7273
7376
  try {
7274
7377
  if (row.readiness_chain)
7275
7378
  readinessChain = JSON.parse(row.readiness_chain);
@@ -7285,6 +7388,11 @@ function rowToMetadata(row) {
7285
7388
  appPortUrls = JSON.parse(row.app_port_urls);
7286
7389
  } catch {
7287
7390
  }
7391
+ try {
7392
+ if (row.world_db_names)
7393
+ worldDbNames = JSON.parse(row.world_db_names);
7394
+ } catch {
7395
+ }
7288
7396
  return {
7289
7397
  id: row.id,
7290
7398
  name: row.name,
@@ -7307,7 +7415,9 @@ function rowToMetadata(row) {
7307
7415
  autoDestroyOnMerge: (row.auto_destroy_on_merge ?? 1) !== 0,
7308
7416
  ...readinessChain !== void 0 ? { readinessChain } : {},
7309
7417
  ...expectedServices !== void 0 ? { expectedServices } : {},
7310
- ...appPortUrls !== void 0 ? { appPortUrls } : {}
7418
+ ...appPortUrls !== void 0 ? { appPortUrls } : {},
7419
+ ...worldDbNames !== void 0 ? { worldDbNames } : {},
7420
+ ...row.world_role_name ? { worldRoleName: row.world_role_name } : {}
7311
7421
  };
7312
7422
  }
7313
7423
  var _require, _Database, SCHEMA_VERSION, CREATE_TABLE, CREATE_META, WorldRegistry;
@@ -7359,7 +7469,17 @@ CREATE TABLE IF NOT EXISTS meta (
7359
7469
  this.db.pragma("foreign_keys = ON");
7360
7470
  this.db.exec(CREATE_TABLE);
7361
7471
  this.db.exec(CREATE_META);
7362
- for (const col of ["readiness_chain TEXT", "expected_services TEXT", "app_port_urls TEXT", "tailscale_paths TEXT"]) {
7472
+ for (const col of [
7473
+ "readiness_chain TEXT",
7474
+ "expected_services TEXT",
7475
+ "app_port_urls TEXT",
7476
+ "tailscale_paths TEXT",
7477
+ // olam-hybrid-shared-postgres Phase A task A7. JSON-serialised string for
7478
+ // world_db_names (SQLite has no native array; matches repos column shape).
7479
+ // TEXT[] would fail at DDL time (closes OQ13/OQ22 / FS-03).
7480
+ "world_db_names TEXT",
7481
+ "world_role_name TEXT"
7482
+ ]) {
7363
7483
  try {
7364
7484
  this.db.exec(`ALTER TABLE worlds ADD COLUMN ${col}`);
7365
7485
  } catch {
@@ -7385,8 +7505,9 @@ CREATE TABLE IF NOT EXISTS meta (
7385
7505
  (id, name, status, repos, branch, port_offset, workspace_path,
7386
7506
  compute_provider, total_cost_usd, thought_count, created_at, updated_at,
7387
7507
  pr_url, pr_number, pr_repo, pr_created_at, pr_state, pr_merged_at, auto_destroy_on_merge,
7388
- readiness_chain, expected_services, app_port_urls)
7389
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(world.id, world.name, world.status, JSON.stringify(world.repos), world.branch, world.portOffset, world.workspacePath, world.computeProvider, world.totalCostUsd, world.thoughtCount, world.createdAt, world.updatedAt, world.prUrl ?? null, world.prNumber ?? null, world.prRepo ?? null, world.prCreatedAt ?? null, world.prState ?? "none", world.prMergedAt ?? null, world.autoDestroyOnMerge === false ? 0 : 1, world.readinessChain !== void 0 ? JSON.stringify(world.readinessChain) : null, world.expectedServices !== void 0 ? JSON.stringify(world.expectedServices) : null, world.appPortUrls !== void 0 ? JSON.stringify(world.appPortUrls) : null);
7508
+ readiness_chain, expected_services, app_port_urls,
7509
+ world_db_names, world_role_name)
7510
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(world.id, world.name, world.status, JSON.stringify(world.repos), world.branch, world.portOffset, world.workspacePath, world.computeProvider, world.totalCostUsd, world.thoughtCount, world.createdAt, world.updatedAt, world.prUrl ?? null, world.prNumber ?? null, world.prRepo ?? null, world.prCreatedAt ?? null, world.prState ?? "none", world.prMergedAt ?? null, world.autoDestroyOnMerge === false ? 0 : 1, world.readinessChain !== void 0 ? JSON.stringify(world.readinessChain) : null, world.expectedServices !== void 0 ? JSON.stringify(world.expectedServices) : null, world.appPortUrls !== void 0 ? JSON.stringify(world.appPortUrls) : null, world.worldDbNames !== void 0 ? JSON.stringify(world.worldDbNames) : null, world.worldRoleName ?? null);
7390
7511
  }
7391
7512
  update(worldId, updates) {
7392
7513
  const setClauses = [];
@@ -7411,9 +7532,12 @@ CREATE TABLE IF NOT EXISTS meta (
7411
7532
  autoDestroyOnMerge: "auto_destroy_on_merge",
7412
7533
  readinessChain: "readiness_chain",
7413
7534
  expectedServices: "expected_services",
7414
- appPortUrls: "app_port_urls"
7535
+ appPortUrls: "app_port_urls",
7536
+ // olam-hybrid-shared-postgres Phase A task A7.
7537
+ worldDbNames: "world_db_names",
7538
+ worldRoleName: "world_role_name"
7415
7539
  };
7416
- const jsonColumns = /* @__PURE__ */ new Set(["repos", "readinessChain", "expectedServices", "appPortUrls"]);
7540
+ const jsonColumns = /* @__PURE__ */ new Set(["repos", "readinessChain", "expectedServices", "appPortUrls", "worldDbNames"]);
7417
7541
  for (const [key, col] of Object.entries(columnMap)) {
7418
7542
  const val = updates[key];
7419
7543
  if (val !== void 0) {
@@ -8300,7 +8424,7 @@ var init_workspace_name = __esm({
8300
8424
 
8301
8425
  // ../core/dist/kg/storage-paths.js
8302
8426
  import { homedir as homedir9 } from "node:os";
8303
- import { join as join16, resolve as resolve4 } from "node:path";
8427
+ import { join as join16, resolve as resolve5 } from "node:path";
8304
8428
  function olamHome() {
8305
8429
  return process.env.OLAM_HOME ?? join16(homedir9(), ".olam");
8306
8430
  }
@@ -8318,7 +8442,7 @@ function assertWithinPrefix(path56, prefix, label) {
8318
8442
  function kgPristinePath(workspace) {
8319
8443
  validateWorkspaceName(workspace);
8320
8444
  const root = kgRoot();
8321
- const path56 = resolve4(join16(root, workspace));
8445
+ const path56 = resolve5(join16(root, workspace));
8322
8446
  assertWithinPrefix(path56, root, "kgPristinePath");
8323
8447
  return path56;
8324
8448
  }
@@ -8471,8 +8595,8 @@ import { execFileSync as execFileSync5 } from "node:child_process";
8471
8595
  import * as fs15 from "node:fs";
8472
8596
  import * as os9 from "node:os";
8473
8597
  import * as path16 from "node:path";
8474
- function expandHome(p, homedir30) {
8475
- return p.replace(/^~(?=$|\/|\\)/, homedir30());
8598
+ function expandHome(p, homedir32) {
8599
+ return p.replace(/^~(?=$|\/|\\)/, homedir32());
8476
8600
  }
8477
8601
  function sanitizeRepoFilename(name) {
8478
8602
  const sanitized = name.replace(/[^A-Za-z0-9._-]/g, "_");
@@ -8495,7 +8619,7 @@ ${stderr}`;
8495
8619
  }
8496
8620
  function snapshotBaselineDiff(repos, workspacePath, deps = {}) {
8497
8621
  const exec = deps.exec ?? ((cmd, args, opts) => execFileSync5(cmd, args, opts));
8498
- const homedir30 = deps.homedir ?? (() => os9.homedir());
8622
+ const homedir32 = deps.homedir ?? (() => os9.homedir());
8499
8623
  const baselineDir = path16.join(workspacePath, ".olam", "baseline");
8500
8624
  try {
8501
8625
  fs15.mkdirSync(baselineDir, { recursive: true });
@@ -8511,7 +8635,7 @@ function snapshotBaselineDiff(repos, workspacePath, deps = {}) {
8511
8635
  continue;
8512
8636
  const filename = `${sanitizeRepoFilename(repo.name)}.diff`;
8513
8637
  const outPath = path16.join(baselineDir, filename);
8514
- const repoPath = expandHome(repo.path, homedir30);
8638
+ const repoPath = expandHome(repo.path, homedir32);
8515
8639
  if (!fs15.existsSync(repoPath)) {
8516
8640
  writeBaselineFile(outPath, `# repo: ${repo.name}
8517
8641
  # (skipped: path ${repoPath} does not exist)
@@ -9411,8 +9535,8 @@ function computeGemsFingerprint(repoDir, imageDigest) {
9411
9535
  return hashBuffers(entries);
9412
9536
  }
9413
9537
  function computeNodeFingerprint(repoDir, imageDigest) {
9414
- const candidates = ["yarn.lock", "pnpm-lock.yaml", "package-lock.json"];
9415
- for (const name of candidates) {
9538
+ const candidates2 = ["yarn.lock", "pnpm-lock.yaml", "package-lock.json"];
9539
+ for (const name of candidates2) {
9416
9540
  const lockfile = path18.join(repoDir, name);
9417
9541
  if (fs17.existsSync(lockfile)) {
9418
9542
  const entries = [
@@ -10566,7 +10690,7 @@ async function startSupervisedApps(containerName, worldId, repos, exec, options
10566
10690
  const probeTimeoutMs = options.probeTimeoutMs ?? 3e4;
10567
10691
  const probeIntervalMs = options.probeIntervalMs ?? 1e3;
10568
10692
  const clock = options.clock ?? Date.now;
10569
- const sleep4 = options.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
10693
+ const sleep5 = options.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
10570
10694
  for (const repo of repos) {
10571
10695
  if (isPortBound(exec, containerName, repo.hostPort)) {
10572
10696
  throw new PortInUseError(repo.hostPort, repo.name, repo.manifestPath);
@@ -10591,7 +10715,7 @@ async function startSupervisedApps(containerName, worldId, repos, exec, options
10591
10715
  probeTimeoutMs,
10592
10716
  probeIntervalMs,
10593
10717
  clock,
10594
- sleep: sleep4
10718
+ sleep: sleep5
10595
10719
  })));
10596
10720
  return {
10597
10721
  sessionName,
@@ -10658,16 +10782,20 @@ __export(manager_exports, {
10658
10782
  WorkspaceNotFoundError: () => WorkspaceNotFoundError,
10659
10783
  WorldManager: () => WorldManager,
10660
10784
  applyPostgresNetworkOverrides: () => applyPostgresNetworkOverrides,
10785
+ applyPostgresTemplateClone: () => applyPostgresTemplateClone,
10786
+ applyPostgresWorldRole: () => applyPostgresWorldRole,
10661
10787
  buildManifestRuntimeForTest: () => buildManifestRuntime,
10662
10788
  cleanupWorldTailscale: () => cleanupWorldTailscale,
10663
10789
  deriveReadinessChain: () => deriveReadinessChain,
10790
+ deriveWorldRoleName: () => deriveWorldRoleName,
10664
10791
  exposeWorldOverTailscale: () => exposeWorldOverTailscale,
10665
10792
  getTokenScopes: () => getTokenScopes,
10666
10793
  planManifestPipeline: () => planManifestPipeline,
10794
+ redactCreateRolePassword: () => redactCreateRolePassword,
10667
10795
  runManifestRuntime: () => runManifestRuntime
10668
10796
  });
10669
10797
  import * as crypto5 from "node:crypto";
10670
- import { execSync as execSync5 } from "node:child_process";
10798
+ import { execSync as execSync5, spawnSync as spawnSync4 } from "node:child_process";
10671
10799
  import * as fs23 from "node:fs";
10672
10800
  import * as os13 from "node:os";
10673
10801
  import * as path24 from "node:path";
@@ -11059,13 +11187,13 @@ function cleanupWorldTailscale(worldId, registry, _exec = execSync5) {
11059
11187
  }
11060
11188
  }
11061
11189
  function resolveTailscaleBin(_exec = execSync5) {
11062
- const candidates = [
11190
+ const candidates2 = [
11063
11191
  process.env["TAILSCALE_BIN"],
11064
11192
  "/Applications/Tailscale.app/Contents/MacOS/Tailscale",
11065
11193
  "/usr/local/bin/tailscale",
11066
11194
  "/opt/homebrew/bin/tailscale"
11067
11195
  ];
11068
- for (const c of candidates) {
11196
+ for (const c of candidates2) {
11069
11197
  if (!c)
11070
11198
  continue;
11071
11199
  try {
@@ -11076,16 +11204,268 @@ function resolveTailscaleBin(_exec = execSync5) {
11076
11204
  }
11077
11205
  return void 0;
11078
11206
  }
11079
- function applyPostgresNetworkOverrides(worldEnv, enrichedRepos) {
11207
+ function applyPostgresNetworkOverrides(worldEnv, enrichedRepos, worldId) {
11080
11208
  const hasPostgres = enrichedRepos.some((r) => r.manifest?.services?.["postgres"] !== void 0);
11081
11209
  if (!hasPostgres)
11082
11210
  return;
11083
- worldEnv["POSTGRESQL_HOST"] = "postgres";
11211
+ const hasSeedTemplate = enrichedRepos.some((r) => {
11212
+ const pg = r.manifest?.services?.["postgres"];
11213
+ return pg?.seed_template !== void 0;
11214
+ });
11215
+ const host = hasSeedTemplate ? "olam-postgres" : "postgres";
11216
+ worldEnv["POSTGRESQL_HOST"] = host;
11084
11217
  worldEnv["POSTGRESQL_PORT"] = "5432";
11085
- if ("POSTGRESQL_COMMON_HOST" in worldEnv)
11086
- worldEnv["POSTGRESQL_COMMON_HOST"] = "postgres";
11087
- if ("POSTGRESQL_COMMON_PORT" in worldEnv)
11218
+ if (hasSeedTemplate) {
11219
+ worldEnv["POSTGRESQL_COMMON_HOST"] = host;
11088
11220
  worldEnv["POSTGRESQL_COMMON_PORT"] = "5432";
11221
+ if (worldId !== void 0) {
11222
+ assertSafeWorldId(worldId);
11223
+ const seedTemplates = /* @__PURE__ */ new Set();
11224
+ for (const repo of enrichedRepos) {
11225
+ const pg = repo.manifest?.services?.["postgres"];
11226
+ const raw = pg?.seed_template;
11227
+ if (typeof raw === "string")
11228
+ seedTemplates.add(raw);
11229
+ else if (Array.isArray(raw)) {
11230
+ for (const s of raw)
11231
+ if (typeof s === "string")
11232
+ seedTemplates.add(s);
11233
+ }
11234
+ }
11235
+ for (const seed of seedTemplates) {
11236
+ const match2 = seed.match(/^atlas_([a-z][a-z0-9_]*?)_seed$/i);
11237
+ if (match2 && match2[1] !== void 0) {
11238
+ const purpose = match2[1].toUpperCase();
11239
+ const envKey = `POSTGRESQL_${purpose}_DATABASE`;
11240
+ worldEnv[envKey] = deriveWorldDbName(seed, worldId);
11241
+ }
11242
+ }
11243
+ }
11244
+ } else {
11245
+ if ("POSTGRESQL_COMMON_HOST" in worldEnv)
11246
+ worldEnv["POSTGRESQL_COMMON_HOST"] = "postgres";
11247
+ if ("POSTGRESQL_COMMON_PORT" in worldEnv)
11248
+ worldEnv["POSTGRESQL_COMMON_PORT"] = "5432";
11249
+ }
11250
+ }
11251
+ function applyPostgresTemplateClone(worldId, enrichedRepos, options = {}) {
11252
+ assertSafeWorldId(worldId);
11253
+ const container = options.singletonContainer ?? "olam-postgres";
11254
+ const user = options.postgresUser ?? "development";
11255
+ const seedTemplates = [];
11256
+ const postCloneSqlBySeed = /* @__PURE__ */ new Map();
11257
+ for (const repo of enrichedRepos) {
11258
+ const pg = repo.manifest?.services?.["postgres"];
11259
+ if (!pg?.seed_template)
11260
+ continue;
11261
+ const list = Array.isArray(pg.seed_template) ? pg.seed_template : [pg.seed_template];
11262
+ for (const t of list) {
11263
+ if (typeof t === "string" && t.length > 0 && !seedTemplates.includes(t)) {
11264
+ seedTemplates.push(t);
11265
+ }
11266
+ }
11267
+ if (pg.post_clone_sql && typeof pg.post_clone_sql === "object") {
11268
+ for (const [seedKey, sqlList] of Object.entries(pg.post_clone_sql)) {
11269
+ if (!Array.isArray(sqlList))
11270
+ continue;
11271
+ const existing = postCloneSqlBySeed.get(seedKey) ?? [];
11272
+ for (const stmt of sqlList) {
11273
+ if (typeof stmt === "string" && stmt.length > 0)
11274
+ existing.push(stmt);
11275
+ }
11276
+ postCloneSqlBySeed.set(seedKey, existing);
11277
+ }
11278
+ }
11279
+ }
11280
+ if (seedTemplates.length === 0) {
11281
+ return { worldDbNames: [] };
11282
+ }
11283
+ const spawn11 = options.spawn ?? spawnSync4;
11284
+ const seedToWorldDb = /* @__PURE__ */ new Map();
11285
+ for (const seed of seedTemplates) {
11286
+ seedToWorldDb.set(seed, deriveWorldDbName(seed, worldId));
11287
+ }
11288
+ const created = [];
11289
+ for (const seed of seedTemplates) {
11290
+ const worldDb = seedToWorldDb.get(seed);
11291
+ const exists = spawn11("docker", [
11292
+ "exec",
11293
+ container,
11294
+ "psql",
11295
+ "-U",
11296
+ user,
11297
+ "-tAc",
11298
+ `SELECT 1 FROM pg_database WHERE datname='${worldDb}'`
11299
+ ], { encoding: "utf-8" });
11300
+ const dbAlreadyExists = exists.status === 0 && (exists.stdout || "").trim() === "1";
11301
+ if (!dbAlreadyExists) {
11302
+ const sql = `CREATE DATABASE "${worldDb}" TEMPLATE "${seed}"`;
11303
+ const create = spawn11("docker", ["exec", container, "psql", "-U", user, "-d", "postgres", "-v", "ON_ERROR_STOP=1", "-c", sql], { encoding: "utf-8" });
11304
+ if (create.status !== 0) {
11305
+ throw new Error(`failed to CREATE DATABASE "${worldDb}" TEMPLATE "${seed}": ${(create.stderr || "").trim()}`);
11306
+ }
11307
+ }
11308
+ created.push(worldDb);
11309
+ const stmts = postCloneSqlBySeed.get(seed);
11310
+ if (stmts !== void 0 && stmts.length > 0) {
11311
+ for (const rawStmt of stmts) {
11312
+ const stmt = interpolatePostCloneSql(rawStmt, worldId, seedToWorldDb);
11313
+ const fixup = spawn11("docker", ["exec", container, "psql", "-U", user, "-d", worldDb, "-v", "ON_ERROR_STOP=1", "-c", stmt], { encoding: "utf-8" });
11314
+ if (fixup.status !== 0) {
11315
+ throw new Error(`post_clone_sql against "${worldDb}" failed (statement: ${truncate(stmt, 120)}): ${(fixup.stderr || "").trim()}`);
11316
+ }
11317
+ }
11318
+ }
11319
+ }
11320
+ return { worldDbNames: created };
11321
+ }
11322
+ function interpolatePostCloneSql(stmt, worldId, seedToWorldDb) {
11323
+ return stmt.replace(/\$\{([^}]+)\}/g, (_, expr) => {
11324
+ if (expr === "WORLD_ID")
11325
+ return worldId;
11326
+ const dbMatch = expr.match(/^WORLD_DB:(.+)$/);
11327
+ if (dbMatch && dbMatch[1] !== void 0) {
11328
+ const target = seedToWorldDb.get(dbMatch[1]);
11329
+ if (target === void 0) {
11330
+ throw new Error(`post_clone_sql references seed "${dbMatch[1]}" not declared in seed_template`);
11331
+ }
11332
+ return target;
11333
+ }
11334
+ throw new Error(`post_clone_sql contains unknown placeholder "\${${expr}}" \u2014 only \${WORLD_ID} and \${WORLD_DB:<seed>} are supported`);
11335
+ });
11336
+ }
11337
+ function truncate(s, max) {
11338
+ return s.length <= max ? s : s.slice(0, max - 1) + "\u2026";
11339
+ }
11340
+ function deriveWorldRoleName(worldId) {
11341
+ return `olam_world_${worldId}`;
11342
+ }
11343
+ function applyPostgresWorldRole(worldId, worldDbNames, options = {}) {
11344
+ assertSafeWorldId(worldId);
11345
+ if (worldDbNames.length === 0) {
11346
+ throw new Error(`applyPostgresWorldRole called with empty worldDbNames for "${worldId}" \u2014 should only run when applyPostgresTemplateClone produced clones`);
11347
+ }
11348
+ const spawn11 = options.spawn ?? spawnSync4;
11349
+ const container = options.singletonContainer ?? "olam-postgres";
11350
+ const user = options.postgresUser ?? "development";
11351
+ const genPassword = options.generatePassword ?? defaultPasswordGenerator;
11352
+ const worldRoleName = deriveWorldRoleName(worldId);
11353
+ const password = genPassword();
11354
+ const exists = spawn11("docker", [
11355
+ "exec",
11356
+ container,
11357
+ "psql",
11358
+ "-U",
11359
+ user,
11360
+ "-tAc",
11361
+ `SELECT 1 FROM pg_roles WHERE rolname='${escapeSqlLiteral(worldRoleName)}'`
11362
+ ], { encoding: "utf-8" });
11363
+ const roleExists = exists.status === 0 && (exists.stdout || "").trim() === "1";
11364
+ const escapedPassword = escapeSqlLiteral(password);
11365
+ 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`;
11366
+ const dml = spawn11("docker", ["exec", container, "psql", "-U", user, "-d", "postgres", "-v", "ON_ERROR_STOP=1", "-c", roleDdl], { encoding: "utf-8" });
11367
+ if (dml.status !== 0) {
11368
+ throw new Error(`failed to ${roleExists ? "ALTER" : "CREATE"} ROLE "${worldRoleName}": ` + redactCreateRolePassword((dml.stderr || "").trim()));
11369
+ }
11370
+ for (const worldDb of worldDbNames) {
11371
+ const grantConnect = spawn11("docker", [
11372
+ "exec",
11373
+ container,
11374
+ "psql",
11375
+ "-U",
11376
+ user,
11377
+ "-d",
11378
+ "postgres",
11379
+ "-v",
11380
+ "ON_ERROR_STOP=1",
11381
+ "-c",
11382
+ `GRANT CONNECT ON DATABASE "${worldDb}" TO "${worldRoleName}"`
11383
+ ], { encoding: "utf-8" });
11384
+ if (grantConnect.status !== 0) {
11385
+ throw new Error(`failed to GRANT CONNECT on "${worldDb}" to "${worldRoleName}": ${(grantConnect.stderr || "").trim()}`);
11386
+ }
11387
+ const perDbGrants = [
11388
+ `GRANT USAGE ON SCHEMA public TO "${worldRoleName}"`,
11389
+ `GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO "${worldRoleName}"`,
11390
+ `GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO "${worldRoleName}"`,
11391
+ // DEFAULT PRIVILEGES for future objects — Rails migrations create new
11392
+ // tables/sequences post-spawn and they need to be grantable.
11393
+ `ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO "${worldRoleName}"`,
11394
+ `ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO "${worldRoleName}"`
11395
+ ].join("; ");
11396
+ const grantResult = spawn11("docker", ["exec", container, "psql", "-U", user, "-d", worldDb, "-v", "ON_ERROR_STOP=1", "-c", perDbGrants], { encoding: "utf-8" });
11397
+ if (grantResult.status !== 0) {
11398
+ throw new Error(`failed to GRANT privileges on "${worldDb}" to "${worldRoleName}": ${(grantResult.stderr || "").trim()}`);
11399
+ }
11400
+ }
11401
+ return { worldRoleName, password };
11402
+ }
11403
+ function defaultPasswordGenerator() {
11404
+ return crypto5.randomBytes(24).toString("base64url");
11405
+ }
11406
+ function escapeSqlLiteral(s) {
11407
+ return s.replace(/'/g, "''");
11408
+ }
11409
+ function redactCreateRolePassword(s) {
11410
+ return s.replace(/PASSWORD\s+'[^']*'/g, "PASSWORD '<scrubbed>'");
11411
+ }
11412
+ function dropPostgresWorldDbs(worldId, worldDbNames, options = {}) {
11413
+ if (worldDbNames.length === 0)
11414
+ return;
11415
+ assertSafeWorldId(worldId);
11416
+ const spawn11 = options.spawn ?? spawnSync4;
11417
+ const container = options.container ?? "olam-postgres";
11418
+ const user = options.user ?? "development";
11419
+ for (const db of worldDbNames) {
11420
+ const drop = spawn11("docker", [
11421
+ "exec",
11422
+ container,
11423
+ "psql",
11424
+ "-U",
11425
+ user,
11426
+ "-d",
11427
+ "postgres",
11428
+ "-c",
11429
+ `DROP DATABASE IF EXISTS "${db}" WITH (FORCE)`
11430
+ ], { encoding: "utf-8" });
11431
+ if (drop.status !== 0) {
11432
+ console.warn(`[manager] destroyWorld(${worldId}): failed to DROP "${db}" from singleton: ${(drop.stderr || "").trim()}`);
11433
+ }
11434
+ }
11435
+ }
11436
+ function dropPostgresWorldRole(worldId, worldRoleName, options = {}) {
11437
+ if (!worldRoleName)
11438
+ return;
11439
+ assertSafeWorldId(worldId);
11440
+ const spawn11 = options.spawn ?? spawnSync4;
11441
+ const container = options.container ?? "olam-postgres";
11442
+ const user = options.user ?? "development";
11443
+ const cleanup = spawn11("docker", [
11444
+ "exec",
11445
+ container,
11446
+ "psql",
11447
+ "-U",
11448
+ user,
11449
+ "-d",
11450
+ "postgres",
11451
+ "-c",
11452
+ `DROP OWNED BY "${worldRoleName}" CASCADE; DROP ROLE IF EXISTS "${worldRoleName}"`
11453
+ ], { encoding: "utf-8" });
11454
+ if (cleanup.status !== 0) {
11455
+ console.warn(`[manager] destroyWorld(${worldId}): failed to DROP role "${worldRoleName}" from singleton: ${(cleanup.stderr || "").trim()}`);
11456
+ }
11457
+ }
11458
+ function assertSafeWorldId(worldId) {
11459
+ if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(worldId)) {
11460
+ throw new Error(`world id "${worldId}" contains characters outside [a-z0-9-] \u2014 hybrid-postgres + post_clone_sql interpolation require the name-generator's [a-z0-9-]+ alphabet`);
11461
+ }
11462
+ }
11463
+ function deriveWorldDbName(seed, worldId) {
11464
+ if (seed.endsWith("_seed")) {
11465
+ const prefix = seed.slice(0, -"_seed".length);
11466
+ return `${prefix}_world_${worldId}`;
11467
+ }
11468
+ return `${seed}_world_${worldId}`;
11089
11469
  }
11090
11470
  var BotIdentityError, ADJECTIVES, NOUNS, AuthPreflightError, WorkspaceNotFoundError, WorldManager;
11091
11471
  var init_manager = __esm({
@@ -11472,6 +11852,8 @@ ${detail}`);
11472
11852
  const hostPort = override !== void 0 ? override : ap.port + 1e4 + portOffset;
11473
11853
  return { repoName: ap.name, internalPort: ap.port, hostPort, url: `http://localhost:${hostPort}` };
11474
11854
  });
11855
+ let worldDbNames = void 0;
11856
+ let worldRoleName = void 0;
11475
11857
  try {
11476
11858
  const worldEnv = {};
11477
11859
  if (opts.task)
@@ -11572,7 +11954,27 @@ ${detail}`);
11572
11954
  }
11573
11955
  }
11574
11956
  }
11575
- applyPostgresNetworkOverrides(worldEnv, enrichedRepos);
11957
+ applyPostgresNetworkOverrides(worldEnv, enrichedRepos, worldId);
11958
+ const cloneResult = applyPostgresTemplateClone(worldId, enrichedRepos);
11959
+ worldDbNames = cloneResult.worldDbNames.length > 0 ? cloneResult.worldDbNames : void 0;
11960
+ if (worldDbNames !== void 0) {
11961
+ const roleResult = applyPostgresWorldRole(worldId, worldDbNames);
11962
+ worldRoleName = roleResult.worldRoleName;
11963
+ worldEnv["POSTGRESQL_USERNAME"] = roleResult.worldRoleName;
11964
+ worldEnv["POSTGRESQL_PASSWORD"] = roleResult.password;
11965
+ worldEnv["POSTGRESQL_COMMON_USERNAME"] = roleResult.worldRoleName;
11966
+ worldEnv["POSTGRESQL_COMMON_PASSWORD"] = roleResult.password;
11967
+ worldEnv["POSTGRESQL_MERCHANT_USERNAME"] = roleResult.worldRoleName;
11968
+ worldEnv["POSTGRESQL_MERCHANT_PASSWORD"] = roleResult.password;
11969
+ }
11970
+ if (worldDbNames !== void 0 || worldRoleName !== void 0) {
11971
+ this.registry.update(worldId, {
11972
+ ...worldDbNames !== void 0 ? { worldDbNames: [...worldDbNames] } : {},
11973
+ ...worldRoleName !== void 0 ? { worldRoleName } : {}
11974
+ });
11975
+ }
11976
+ const hybridActive = worldDbNames !== void 0;
11977
+ const extraNetworks = hybridActive ? ["olam-shared"] : void 0;
11576
11978
  await this.provider.createWorld({
11577
11979
  id: worldId,
11578
11980
  name: opts.name,
@@ -11582,6 +11984,7 @@ ${detail}`);
11582
11984
  portOffset,
11583
11985
  workspacePath,
11584
11986
  appPorts: appPorts.length > 0 ? appPorts : void 0,
11987
+ ...extraNetworks ? { extraNetworks } : {},
11585
11988
  // Phase 7 A1: per-world cost ceiling sourced from config.cost.
11586
11989
  // Cloudflare provider forwards this to /session/start so the DO
11587
11990
  // can enforce kill-switch on `cost_update` events. Other providers
@@ -11605,7 +12008,16 @@ ${detail}`);
11605
12008
  sm.transition("running");
11606
12009
  this.registry.update(worldId, {
11607
12010
  status: "running",
11608
- ...appPortUrls.length > 0 ? { appPortUrls } : {}
12011
+ ...appPortUrls.length > 0 ? { appPortUrls } : {},
12012
+ // olam-hybrid-shared-postgres A3: persist the cloned per-world DB
12013
+ // names so olam destroy + olam gc can find them later (A7's
12014
+ // world_db_names TEXT JSON column).
12015
+ ...worldDbNames !== void 0 ? { worldDbNames: [...worldDbNames] } : {},
12016
+ // olam-hybrid-shared-postgres A4: persist the role name so olam destroy
12017
+ // can DROP it after the world's DBs are gone (A7's world_role_name
12018
+ // column). Password is NOT persisted — it lives only in the world
12019
+ // container's env and the in-memory return value.
12020
+ ...worldRoleName !== void 0 ? { worldRoleName } : {}
11609
12021
  });
11610
12022
  const containerName = `olam-${worldId}-devbox`;
11611
12023
  try {
@@ -11905,6 +12317,14 @@ ${opts.task}`;
11905
12317
  cleanupWorldTailscale(worldId, this.registry);
11906
12318
  } catch {
11907
12319
  }
12320
+ const worldDbNames = world.worldDbNames;
12321
+ if (worldDbNames !== void 0 && worldDbNames.length > 0) {
12322
+ dropPostgresWorldDbs(worldId, worldDbNames);
12323
+ }
12324
+ const worldRoleName = world.worldRoleName;
12325
+ if (typeof worldRoleName === "string" && worldRoleName.length > 0) {
12326
+ dropPostgresWorldRole(worldId, worldRoleName);
12327
+ }
11908
12328
  try {
11909
12329
  await this.provider.destroyWorld(worldId);
11910
12330
  } catch {
@@ -13306,7 +13726,7 @@ function isCloudflaredAvailable() {
13306
13726
  }
13307
13727
  }
13308
13728
  function startTunnel(port) {
13309
- return new Promise((resolve12, reject) => {
13729
+ return new Promise((resolve14, reject) => {
13310
13730
  const child = spawn3("cloudflared", ["tunnel", "--url", `http://localhost:${port}`], {
13311
13731
  stdio: ["ignore", "pipe", "pipe"],
13312
13732
  detached: false
@@ -13328,7 +13748,7 @@ function startTunnel(port) {
13328
13748
  if (match2) {
13329
13749
  resolved = true;
13330
13750
  clearTimeout(timeout);
13331
- resolve12(match2[0]);
13751
+ resolve14(match2[0]);
13332
13752
  }
13333
13753
  }
13334
13754
  child.stdout?.on("data", scan);
@@ -13415,8 +13835,8 @@ var init_dashboard = __esm({
13415
13835
  }
13416
13836
  throw err;
13417
13837
  }
13418
- await new Promise((resolve12, reject) => {
13419
- this.server.on("listening", resolve12);
13838
+ await new Promise((resolve14, reject) => {
13839
+ this.server.on("listening", resolve14);
13420
13840
  this.server.on("error", reject);
13421
13841
  });
13422
13842
  this.info = { localUrl: `http://localhost:${port}` };
@@ -13462,8 +13882,8 @@ var init_dashboard = __esm({
13462
13882
  async stop() {
13463
13883
  stopTunnel();
13464
13884
  if (this.server) {
13465
- await new Promise((resolve12) => {
13466
- this.server.close(() => resolve12());
13885
+ await new Promise((resolve14) => {
13886
+ this.server.close(() => resolve14());
13467
13887
  });
13468
13888
  this.server = null;
13469
13889
  }
@@ -13689,10 +14109,10 @@ import * as crypto6 from "node:crypto";
13689
14109
  import * as fs26 from "node:fs";
13690
14110
  import * as os15 from "node:os";
13691
14111
  import * as path28 from "node:path";
13692
- import { spawnSync as spawnSync4 } from "node:child_process";
14112
+ import { spawnSync as spawnSync5 } from "node:child_process";
13693
14113
  import Dockerode2 from "dockerode";
13694
14114
  function findComposeFile() {
13695
- const candidates = [
14115
+ const candidates2 = [
13696
14116
  // Bundled path: dist/index.js lives at <pkg>/dist/; host-cp/ is a sibling of dist/
13697
14117
  path28.resolve(path28.dirname(new URL(import.meta.url).pathname), "../host-cp/compose.yaml"),
13698
14118
  // Source-mode: cwd is monorepo root
@@ -13700,7 +14120,7 @@ function findComposeFile() {
13700
14120
  // Source-mode: cwd is one level inside the monorepo
13701
14121
  path28.resolve(process.cwd(), "../packages/host-cp/compose.yaml")
13702
14122
  ];
13703
- for (const c of candidates) {
14123
+ for (const c of candidates2) {
13704
14124
  if (fs26.existsSync(c)) return c;
13705
14125
  }
13706
14126
  return path28.resolve(process.cwd(), "packages/host-cp/compose.yaml");
@@ -13875,7 +14295,7 @@ async function probeHealth() {
13875
14295
  }
13876
14296
  }
13877
14297
  function runCompose(args, composeFile, extraEnv = {}) {
13878
- const result = spawnSync4("docker", ["compose", "-f", composeFile, ...args], {
14298
+ const result = spawnSync5("docker", ["compose", "-f", composeFile, ...args], {
13879
14299
  encoding: "utf-8",
13880
14300
  stdio: ["ignore", "pipe", "pipe"],
13881
14301
  env: { ...process.env, ...extraEnv }
@@ -13902,7 +14322,7 @@ function buildComposeEnv(authSecret, ghToken) {
13902
14322
  }
13903
14323
  function captureGhToken() {
13904
14324
  try {
13905
- const result = spawnSync4("gh", ["auth", "token"], {
14325
+ const result = spawnSync5("gh", ["auth", "token"], {
13906
14326
  encoding: "utf-8",
13907
14327
  stdio: ["ignore", "pipe", "pipe"]
13908
14328
  });
@@ -14282,15 +14702,15 @@ __export(install_root_exports, {
14282
14702
  resolveBuildScript: () => resolveBuildScript
14283
14703
  });
14284
14704
  import { existsSync as existsSync24 } from "node:fs";
14285
- import { dirname as dirname17, join as join30, resolve as resolve9 } from "node:path";
14705
+ import { dirname as dirname17, join as join30, resolve as resolve10 } from "node:path";
14286
14706
  import { fileURLToPath as fileURLToPath5 } from "node:url";
14287
14707
  function installRoot(metaUrl = import.meta.url) {
14288
- const here2 = fileURLToPath5(metaUrl);
14289
- return resolve9(dirname17(here2), "..");
14708
+ const here3 = fileURLToPath5(metaUrl);
14709
+ return resolve10(dirname17(here3), "..");
14290
14710
  }
14291
14711
  function isDevMode(env = process.env, installRootDir = installRoot()) {
14292
14712
  if (env.OLAM_DEV !== "1") return false;
14293
- const repoRoot = resolve9(installRootDir, "..", "..");
14713
+ const repoRoot = resolve10(installRootDir, "..", "..");
14294
14714
  return existsSync24(join30(repoRoot, "packages")) && existsSync24(join30(repoRoot, "package.json"));
14295
14715
  }
14296
14716
  function resolveBuildScript(input) {
@@ -14298,7 +14718,7 @@ function resolveBuildScript(input) {
14298
14718
  if (!isDevMode(env, installRootDir)) {
14299
14719
  throw new MissingBuildScriptError(scriptRelPath);
14300
14720
  }
14301
- const repoRoot = resolve9(installRootDir, "..", "..");
14721
+ const repoRoot = resolve10(installRootDir, "..", "..");
14302
14722
  return join30(repoRoot, scriptRelPath);
14303
14723
  }
14304
14724
  var MissingBuildScriptError;
@@ -14329,7 +14749,7 @@ __export(protocol_version_exports, {
14329
14749
  inspectImageProtocolVersions: () => inspectImageProtocolVersions,
14330
14750
  parseProtocolVersionsLabel: () => parseProtocolVersionsLabel
14331
14751
  });
14332
- import { spawnSync as spawnSync5 } from "node:child_process";
14752
+ import { spawnSync as spawnSync6 } from "node:child_process";
14333
14753
  function parseProtocolVersionsLabel(labelValue) {
14334
14754
  if (!labelValue) return [];
14335
14755
  const parts = labelValue.split(",").map((s) => s.trim()).filter(Boolean);
@@ -14400,7 +14820,7 @@ var init_protocol_version = __esm({
14400
14820
  "use strict";
14401
14821
  OLAM_PROTOCOL_VERSIONS_SUPPORTED = [1];
14402
14822
  realDockerInspect = (imageRef) => {
14403
- const result = spawnSync5(
14823
+ const result = spawnSync6(
14404
14824
  "docker",
14405
14825
  ["inspect", imageRef, "--format", '{{ index .Config.Labels "olam.protocol.versions" }}'],
14406
14826
  { encoding: "utf8", timeout: 1e4 }
@@ -14414,6 +14834,158 @@ var init_protocol_version = __esm({
14414
14834
  }
14415
14835
  });
14416
14836
 
14837
+ // ../core/dist/auth/postgres-init-helpers.js
14838
+ var postgres_init_helpers_exports = {};
14839
+ __export(postgres_init_helpers_exports, {
14840
+ DEFAULT_POSTGRES_CONTAINER: () => DEFAULT_POSTGRES_CONTAINER,
14841
+ DEFAULT_POSTGRES_DB: () => DEFAULT_POSTGRES_DB,
14842
+ DEFAULT_POSTGRES_HOST_PORT: () => DEFAULT_POSTGRES_HOST_PORT,
14843
+ DEFAULT_POSTGRES_IMAGE: () => DEFAULT_POSTGRES_IMAGE,
14844
+ DEFAULT_POSTGRES_NETWORK: () => DEFAULT_POSTGRES_NETWORK,
14845
+ DEFAULT_POSTGRES_PASSWORD: () => DEFAULT_POSTGRES_PASSWORD,
14846
+ DEFAULT_POSTGRES_READY_TIMEOUT_MS: () => DEFAULT_POSTGRES_READY_TIMEOUT_MS,
14847
+ DEFAULT_POSTGRES_USER: () => DEFAULT_POSTGRES_USER,
14848
+ ensureOlamPostgresSingleton: () => ensureOlamPostgresSingleton,
14849
+ ensureOlamSharedNetwork: () => ensureOlamSharedNetwork,
14850
+ pullPostgresImage: () => pullPostgresImage,
14851
+ startPostgresSingleton: () => startPostgresSingleton,
14852
+ statusPostgresSingleton: () => statusPostgresSingleton,
14853
+ waitPostgresReady: () => waitPostgresReady
14854
+ });
14855
+ import { spawnSync as spawnSync7 } from "node:child_process";
14856
+ function resolve11(options) {
14857
+ return {
14858
+ network: options.network ?? DEFAULT_POSTGRES_NETWORK,
14859
+ containerName: options.containerName ?? DEFAULT_POSTGRES_CONTAINER,
14860
+ image: options.image ?? DEFAULT_POSTGRES_IMAGE,
14861
+ hostPort: options.hostPort ?? DEFAULT_POSTGRES_HOST_PORT,
14862
+ user: options.user ?? DEFAULT_POSTGRES_USER,
14863
+ password: options.password ?? DEFAULT_POSTGRES_PASSWORD,
14864
+ db: options.db ?? DEFAULT_POSTGRES_DB,
14865
+ readyTimeoutMs: options.readyTimeoutMs ?? DEFAULT_POSTGRES_READY_TIMEOUT_MS,
14866
+ spawn: options.spawn ?? spawnSync7
14867
+ };
14868
+ }
14869
+ function statusPostgresSingleton(options = {}) {
14870
+ const opts = resolve11(options);
14871
+ const inspect = opts.spawn("docker", ["inspect", "--format", "{{.State.Status}}", opts.containerName], { encoding: "utf-8" });
14872
+ if (inspect.status !== 0)
14873
+ return "missing";
14874
+ const raw = (inspect.stdout || "").trim();
14875
+ return raw === "running" ? "running" : "stopped";
14876
+ }
14877
+ function ensureOlamSharedNetwork(options = {}) {
14878
+ const opts = resolve11(options);
14879
+ const create = opts.spawn("docker", ["network", "create", opts.network], {
14880
+ encoding: "utf-8"
14881
+ });
14882
+ if (create.status !== 0 && !(create.stderr || "").includes("already exists")) {
14883
+ throw new Error(`failed to create docker network ${opts.network}: ${(create.stderr || "").trim()}`);
14884
+ }
14885
+ const inspect = opts.spawn("docker", ["network", "inspect", opts.network, "--format", "{{.Driver}}"], { encoding: "utf-8" });
14886
+ if (inspect.status !== 0) {
14887
+ throw new Error(`failed to inspect docker network ${opts.network} after create: ${(inspect.stderr || "").trim()}`);
14888
+ }
14889
+ const driver = (inspect.stdout || "").trim();
14890
+ if (driver !== "bridge") {
14891
+ throw new Error(`docker network ${opts.network} exists with driver=${driver}, expected bridge. Run \`docker network rm ${opts.network} && olam init\` to recreate.`);
14892
+ }
14893
+ }
14894
+ function pullPostgresImage(options = {}) {
14895
+ const opts = resolve11(options);
14896
+ const pull = opts.spawn("docker", ["pull", "--platform", "linux/amd64", opts.image], { encoding: "utf-8" });
14897
+ if (pull.status !== 0) {
14898
+ throw new Error(`failed to pull postgres image ${opts.image}: ${(pull.stderr || "").trim()}`);
14899
+ }
14900
+ }
14901
+ function startPostgresSingleton(options = {}) {
14902
+ const opts = resolve11(options);
14903
+ const state = statusPostgresSingleton(options);
14904
+ if (state === "running")
14905
+ return;
14906
+ if (state === "stopped") {
14907
+ const start = opts.spawn("docker", ["start", opts.containerName], {
14908
+ encoding: "utf-8"
14909
+ });
14910
+ if (start.status !== 0) {
14911
+ throw new Error(`failed to start existing postgres container ${opts.containerName}: ${(start.stderr || "").trim()}`);
14912
+ }
14913
+ return;
14914
+ }
14915
+ const run = opts.spawn("docker", [
14916
+ "run",
14917
+ "--detach",
14918
+ "--name",
14919
+ opts.containerName,
14920
+ "--network",
14921
+ opts.network,
14922
+ "--network-alias",
14923
+ opts.containerName,
14924
+ "--restart",
14925
+ "unless-stopped",
14926
+ "--publish",
14927
+ `127.0.0.1:${opts.hostPort}:5432`,
14928
+ "--env",
14929
+ `POSTGRES_USER=${opts.user}`,
14930
+ "--env",
14931
+ `POSTGRES_PASSWORD=${opts.password}`,
14932
+ "--env",
14933
+ `POSTGRES_DB=${opts.db}`,
14934
+ "--env",
14935
+ `TZ=Asia/Singapore`,
14936
+ opts.image
14937
+ ], { encoding: "utf-8" });
14938
+ if (run.status !== 0) {
14939
+ throw new Error(`failed to start postgres singleton ${opts.containerName}: ${(run.stderr || "").trim()}`);
14940
+ }
14941
+ }
14942
+ async function waitPostgresReady(options = {}) {
14943
+ const opts = resolve11(options);
14944
+ const deadline = Date.now() + opts.readyTimeoutMs;
14945
+ while (Date.now() < deadline) {
14946
+ const ready = opts.spawn("docker", ["exec", opts.containerName, "pg_isready", "-U", opts.user], { encoding: "utf-8" });
14947
+ if (ready.status === 0)
14948
+ return true;
14949
+ await sleep3(1e3);
14950
+ }
14951
+ return false;
14952
+ }
14953
+ async function ensureOlamPostgresSingleton(options = {}) {
14954
+ const opts = resolve11(options);
14955
+ ensureOlamSharedNetwork(options);
14956
+ pullPostgresImage(options);
14957
+ startPostgresSingleton(options);
14958
+ const ready = await waitPostgresReady(options);
14959
+ if (!ready) {
14960
+ throw new Error(`postgres singleton ${opts.containerName} failed pg_isready within ${opts.readyTimeoutMs}ms. Inspect with \`docker logs ${opts.containerName}\`.`);
14961
+ }
14962
+ const finalState = statusPostgresSingleton(options);
14963
+ return {
14964
+ state: finalState,
14965
+ network: opts.network,
14966
+ container: opts.containerName,
14967
+ hostPort: opts.hostPort,
14968
+ ready
14969
+ };
14970
+ }
14971
+ function sleep3(ms) {
14972
+ return new Promise((resolve14) => setTimeout(resolve14, ms));
14973
+ }
14974
+ var DEFAULT_POSTGRES_NETWORK, DEFAULT_POSTGRES_CONTAINER, DEFAULT_POSTGRES_HOST_PORT, DEFAULT_POSTGRES_IMAGE, DEFAULT_POSTGRES_USER, DEFAULT_POSTGRES_PASSWORD, DEFAULT_POSTGRES_DB, DEFAULT_POSTGRES_READY_TIMEOUT_MS;
14975
+ var init_postgres_init_helpers = __esm({
14976
+ "../core/dist/auth/postgres-init-helpers.js"() {
14977
+ "use strict";
14978
+ DEFAULT_POSTGRES_NETWORK = "olam-shared";
14979
+ DEFAULT_POSTGRES_CONTAINER = "olam-postgres";
14980
+ DEFAULT_POSTGRES_HOST_PORT = 5433;
14981
+ DEFAULT_POSTGRES_IMAGE = "odidev/postgis:13-3.1-alpine";
14982
+ DEFAULT_POSTGRES_USER = "development";
14983
+ DEFAULT_POSTGRES_PASSWORD = "development";
14984
+ DEFAULT_POSTGRES_DB = "postgres";
14985
+ DEFAULT_POSTGRES_READY_TIMEOUT_MS = 6e4;
14986
+ }
14987
+ });
14988
+
14417
14989
  // src/registry-allowlist.ts
14418
14990
  var registry_allowlist_exports = {};
14419
14991
  __export(registry_allowlist_exports, {
@@ -15256,7 +15828,7 @@ init_host_cp();
15256
15828
  init_auth();
15257
15829
  import * as fs27 from "node:fs";
15258
15830
  import * as path29 from "node:path";
15259
- import { spawnSync as spawnSync7 } from "node:child_process";
15831
+ import { spawnSync as spawnSync9 } from "node:child_process";
15260
15832
  import ora2 from "ora";
15261
15833
  import pc7 from "picocolors";
15262
15834
 
@@ -15266,7 +15838,7 @@ init_install_root();
15266
15838
  init_exit_codes();
15267
15839
  init_protocol_version();
15268
15840
  init_output();
15269
- import { spawnSync as spawnSync6 } from "node:child_process";
15841
+ import { spawnSync as spawnSync8 } from "node:child_process";
15270
15842
  import { existsSync as existsSync25, readFileSync as readFileSync19 } from "node:fs";
15271
15843
  import { join as join31 } from "node:path";
15272
15844
  import ora from "ora";
@@ -15298,7 +15870,7 @@ function loadImageDigests(installRootDir = installRoot()) {
15298
15870
  return parsed;
15299
15871
  }
15300
15872
  var REAL_OLAM_SUBCOMMAND = (args) => {
15301
- const result = spawnSync6(process.execPath, [
15873
+ const result = spawnSync8(process.execPath, [
15302
15874
  process.argv[1] ?? "olam",
15303
15875
  ...args
15304
15876
  ], {
@@ -15460,6 +16032,48 @@ async function runBootstrap2(opts, deps = {}) {
15460
16032
  return { exitCode: EXIT_GENERIC_ERROR, summary: "auth up failed" };
15461
16033
  }
15462
16034
  authUpSpinner.succeed("auth-service running");
16035
+ if (opts.skipPostgresSingleton || process.env.OLAM_BOOTSTRAP_SKIP_POSTGRES === "1") {
16036
+ printInfo("postgres singleton", "skipped");
16037
+ } else {
16038
+ const pgSpinner = ora("Starting olam-postgres singleton").start();
16039
+ try {
16040
+ const { ensureOlamPostgresSingleton: ensureOlamPostgresSingleton2 } = await Promise.resolve().then(() => (init_postgres_init_helpers(), postgres_init_helpers_exports));
16041
+ const result = await ensureOlamPostgresSingleton2();
16042
+ pgSpinner.succeed(
16043
+ `olam-postgres ${result.state} on network ${result.network} \u2192 127.0.0.1:${result.hostPort}`
16044
+ );
16045
+ } catch (err) {
16046
+ pgSpinner.fail("olam-postgres singleton bring-up failed");
16047
+ process.stderr.write(` ${err instanceof Error ? err.message : String(err)}
16048
+ `);
16049
+ process.stderr.write(` Re-run \`olam bootstrap\` after resolving, or skip via OLAM_BOOTSTRAP_SKIP_POSTGRES=1.
16050
+ `);
16051
+ return { exitCode: EXIT_GENERIC_ERROR, summary: "postgres singleton bring-up failed" };
16052
+ }
16053
+ }
16054
+ if (opts.skipMemory || process.env.OLAM_BOOTSTRAP_SKIP_MEMORY === "1") {
16055
+ printInfo("agent-memory", "skipped");
16056
+ } else {
16057
+ const memSpinner = ora("Starting agent-memory host-process").start();
16058
+ const mem = runOlam(["memory", "start"]);
16059
+ if (mem.exitCode !== 0) {
16060
+ memSpinner.fail("agent-memory start failed");
16061
+ if (mem.stdout.trim().length > 0) {
16062
+ process.stderr.write(` stdout: ${mem.stdout.split("\n").slice(0, 20).join("\n ")}
16063
+ `);
16064
+ }
16065
+ if (mem.stderr.trim().length > 0) {
16066
+ process.stderr.write(` stderr: ${mem.stderr.split("\n").slice(0, 20).join("\n ")}
16067
+ `);
16068
+ }
16069
+ process.stderr.write(
16070
+ ` Re-run \`olam bootstrap\` after resolving, or skip via OLAM_BOOTSTRAP_SKIP_MEMORY=1.
16071
+ `
16072
+ );
16073
+ return { exitCode: EXIT_GENERIC_ERROR, summary: "agent-memory start failed" };
16074
+ }
16075
+ memSpinner.succeed("agent-memory running");
16076
+ }
15463
16077
  if (opts.skipAuthLogin || process.env.OLAM_BOOTSTRAP_SKIP_AUTH_LOGIN === "1") {
15464
16078
  printInfo("auth login", "skipped");
15465
16079
  } else {
@@ -15496,6 +16110,8 @@ async function runBootstrap2(opts, deps = {}) {
15496
16110
  printHeader("Stack ready");
15497
16111
  printInfo("olam-host-cp", `running (${digests["host-cp"].slice(0, 19)}\u2026)`);
15498
16112
  printInfo("olam-auth", `running (${digests.auth.slice(0, 19)}\u2026)`);
16113
+ const memorySkipped = opts.skipMemory || process.env.OLAM_BOOTSTRAP_SKIP_MEMORY === "1";
16114
+ printInfo("agent-memory", memorySkipped ? "skipped" : "running");
15499
16115
  printInfo("olam-devbox", `pulled (${digests.devbox.slice(0, 19)}\u2026)`);
15500
16116
  printInfo("next", '`olam create --task "your task"` to spawn a world');
15501
16117
  return { exitCode: 0, summary: "stack ready" };
@@ -15503,7 +16119,7 @@ async function runBootstrap2(opts, deps = {}) {
15503
16119
  function registerBootstrap(program2) {
15504
16120
  program2.command("bootstrap").description(
15505
16121
  "Bootstrap the olam stack: pull 3 images by digest, verify protocol handshake, start host-cp + auth, run auth login."
15506
- ).option("--with-smoke", "After bootstrap, create a smoke-test world to verify end-to-end").option("--skip-auth-login", "Skip the interactive PKCE step (CI / scripted use only)").option(
16122
+ ).option("--with-smoke", "After bootstrap, create a smoke-test world to verify end-to-end").option("--skip-auth-login", "Skip the interactive PKCE step (CI / scripted use only)").option("--skip-postgres-singleton", "Skip the olam-postgres singleton bring-up (operators with host postgres opt-out, or CI)").option("--skip-memory", "Skip the agent-memory host-process bring-up (CI / scripted use, or unsupported arch)").option(
15507
16123
  "--registry <ref>",
15508
16124
  "Override the registry prefix (default: read from image-digests.json or fall back to ghcr.io/pleri)"
15509
16125
  ).action(async (opts) => {
@@ -15511,6 +16127,8 @@ function registerBootstrap(program2) {
15511
16127
  const result = await runBootstrap2({
15512
16128
  withSmoke: opts.withSmoke === true,
15513
16129
  skipAuthLogin: opts.skipAuthLogin === true,
16130
+ skipPostgresSingleton: opts.skipPostgresSingleton === true,
16131
+ skipMemory: opts.skipMemory === true,
15514
16132
  registry: opts.registry
15515
16133
  });
15516
16134
  process.exitCode = result.exitCode;
@@ -15594,7 +16212,7 @@ async function smokeTestCodexProvider(authSecret) {
15594
16212
  function runStep(label, cmd, args, opts = {}) {
15595
16213
  const start = Date.now();
15596
16214
  process.stdout.write(` ${pc7.dim(label.padEnd(34))}`);
15597
- const result = spawnSync7(cmd, [...args], {
16215
+ const result = spawnSync9(cmd, [...args], {
15598
16216
  encoding: "utf-8",
15599
16217
  stdio: ["ignore", "pipe", "pipe"],
15600
16218
  cwd: opts.cwd ?? process.cwd(),
@@ -15616,10 +16234,10 @@ async function confirm(message) {
15616
16234
  if (!process.stdin.isTTY) return true;
15617
16235
  const { createInterface: createInterface6 } = await import("node:readline");
15618
16236
  const rl = createInterface6({ input: process.stdin, output: process.stdout });
15619
- return new Promise((resolve12) => {
16237
+ return new Promise((resolve14) => {
15620
16238
  rl.question(`${message} [y/N] `, (answer) => {
15621
16239
  rl.close();
15622
- resolve12(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
16240
+ resolve14(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
15623
16241
  });
15624
16242
  });
15625
16243
  }
@@ -15714,7 +16332,7 @@ async function runAuthUpgradePullByDigest(deps = {}) {
15714
16332
  }
15715
16333
  function defaultTagAuth(from, to) {
15716
16334
  try {
15717
- const r = spawnSync7("docker", ["tag", from, to], {
16335
+ const r = spawnSync9("docker", ["tag", from, to], {
15718
16336
  encoding: "utf-8",
15719
16337
  stdio: ["ignore", "ignore", "pipe"]
15720
16338
  });
@@ -15732,11 +16350,11 @@ function defaultTagAuth(from, to) {
15732
16350
  }
15733
16351
  async function defaultRecreateAuth() {
15734
16352
  try {
15735
- spawnSync7("docker", ["stop", "olam-auth"], {
16353
+ spawnSync9("docker", ["stop", "olam-auth"], {
15736
16354
  encoding: "utf-8",
15737
16355
  stdio: ["ignore", "ignore", "ignore"]
15738
16356
  });
15739
- spawnSync7("docker", ["rm", "olam-auth"], {
16357
+ spawnSync9("docker", ["rm", "olam-auth"], {
15740
16358
  encoding: "utf-8",
15741
16359
  stdio: ["ignore", "ignore", "ignore"]
15742
16360
  });
@@ -15805,7 +16423,7 @@ ${imageResult.stderr}`);
15805
16423
  }
15806
16424
  const stopStart = Date.now();
15807
16425
  process.stdout.write(` ${pc7.dim("docker stop olam-auth".padEnd(34))}`);
15808
- spawnSync7("docker", ["stop", "olam-auth"], {
16426
+ spawnSync9("docker", ["stop", "olam-auth"], {
15809
16427
  encoding: "utf-8",
15810
16428
  stdio: ["ignore", "pipe", "pipe"]
15811
16429
  });
@@ -15815,7 +16433,7 @@ ${imageResult.stderr}`);
15815
16433
  timings.push({ label: "container stop", durationMs: stopDurationMs });
15816
16434
  const rmStart = Date.now();
15817
16435
  process.stdout.write(` ${pc7.dim("docker rm olam-auth".padEnd(34))}`);
15818
- spawnSync7("docker", ["rm", "olam-auth"], {
16436
+ spawnSync9("docker", ["rm", "olam-auth"], {
15819
16437
  encoding: "utf-8",
15820
16438
  stdio: ["ignore", "pipe", "pipe"]
15821
16439
  });
@@ -15907,7 +16525,7 @@ function registerAuthUpgrade(auth) {
15907
16525
  // src/commands/services.ts
15908
16526
  init_auth();
15909
16527
  init_output();
15910
- import { execFileSync as execFileSync7, spawnSync as spawnSync8 } from "node:child_process";
16528
+ import { execFileSync as execFileSync7, spawnSync as spawnSync10 } from "node:child_process";
15911
16529
  import pc8 from "picocolors";
15912
16530
  var MCP_AUTH_PORT = 9998;
15913
16531
  var MCP_AUTH_VOLUME = "olam-mcp-auth-data";
@@ -15919,7 +16537,7 @@ var MCP_AUTH_HEALTH_TIMEOUT_MS = 6e4;
15919
16537
  var McpAuthContainerController = class {
15920
16538
  imageTag = MCP_AUTH_LOCAL_TAG;
15921
16539
  status() {
15922
- const r = spawnSync8(
16540
+ const r = spawnSync10(
15923
16541
  "docker",
15924
16542
  ["inspect", "--format", "{{.State.Status}}|{{.Id}}", MCP_AUTH_CONTAINER],
15925
16543
  { encoding: "utf-8" }
@@ -15935,7 +16553,7 @@ var McpAuthContainerController = class {
15935
16553
  return { state: "missing", port: MCP_AUTH_PORT };
15936
16554
  }
15937
16555
  imageExists(tag = this.imageTag) {
15938
- return spawnSync8("docker", ["image", "inspect", tag], { encoding: "utf-8" }).status === 0;
16556
+ return spawnSync10("docker", ["image", "inspect", tag], { encoding: "utf-8" }).status === 0;
15939
16557
  }
15940
16558
  start() {
15941
16559
  const current = this.status();
@@ -15977,7 +16595,7 @@ var McpAuthContainerController = class {
15977
16595
  execFileSync7("docker", ["stop", MCP_AUTH_CONTAINER], { stdio: "pipe" });
15978
16596
  }
15979
16597
  remove() {
15980
- spawnSync8("docker", ["rm", "-f", MCP_AUTH_CONTAINER], { stdio: "pipe" });
16598
+ spawnSync10("docker", ["rm", "-f", MCP_AUTH_CONTAINER], { stdio: "pipe" });
15981
16599
  }
15982
16600
  async waitForReady(timeoutMs = MCP_AUTH_HEALTH_TIMEOUT_MS) {
15983
16601
  const deadline = Date.now() + timeoutMs;
@@ -15987,17 +16605,17 @@ var McpAuthContainerController = class {
15987
16605
  if (res.ok) return true;
15988
16606
  } catch {
15989
16607
  }
15990
- await sleep3(500);
16608
+ await sleep4(500);
15991
16609
  }
15992
16610
  return false;
15993
16611
  }
15994
16612
  };
15995
- function sleep3(ms) {
15996
- return new Promise((resolve12) => setTimeout(resolve12, ms));
16613
+ function sleep4(ms) {
16614
+ return new Promise((resolve14) => setTimeout(resolve14, ms));
15997
16615
  }
15998
16616
  function dumpContainerLogs(container, tail = 40) {
15999
16617
  try {
16000
- const result = spawnSync8("docker", ["logs", "--tail", String(tail), container], {
16618
+ const result = spawnSync10("docker", ["logs", "--tail", String(tail), container], {
16001
16619
  encoding: "utf-8",
16002
16620
  stdio: ["ignore", "pipe", "pipe"]
16003
16621
  });
@@ -16270,9 +16888,9 @@ ${pc9.dim("Next: olam create --name my-world")}`);
16270
16888
 
16271
16889
  // src/commands/create.ts
16272
16890
  init_manager();
16273
- import { spawnSync as spawnSync9 } from "node:child_process";
16891
+ import { spawnSync as spawnSync11 } from "node:child_process";
16274
16892
  import { existsSync as existsSync28 } from "node:fs";
16275
- import { dirname as dirname18, resolve as resolve10 } from "node:path";
16893
+ import { dirname as dirname18, resolve as resolve12 } from "node:path";
16276
16894
  import ora3 from "ora";
16277
16895
  import pc10 from "picocolors";
16278
16896
 
@@ -16401,17 +17019,17 @@ function inferRepos(input) {
16401
17019
  proceed: true
16402
17020
  };
16403
17021
  }
16404
- const candidates = extractCandidates(input.prompt);
16405
- if (candidates.length === 0) {
17022
+ const candidates2 = extractCandidates(input.prompt);
17023
+ if (candidates2.length === 0) {
16406
17024
  return { repos: [], confidence: 0, proceed: false };
16407
17025
  }
16408
17026
  if (input.catalog && input.catalog.length > 0) {
16409
17027
  const catalogSet = new Set(input.catalog.map((c) => c.toLowerCase()));
16410
- const matched = candidates.filter((c) => catalogSet.has(c));
17028
+ const matched = candidates2.filter((c) => catalogSet.has(c));
16411
17029
  if (matched.length === 0) {
16412
17030
  return { repos: [], confidence: 0.2, proceed: false };
16413
17031
  }
16414
- const ratio = matched.length / candidates.length;
17032
+ const ratio = matched.length / candidates2.length;
16415
17033
  const confidence = Math.min(0.95, 0.5 + 0.5 * ratio);
16416
17034
  return {
16417
17035
  repos: matched,
@@ -16420,7 +17038,7 @@ function inferRepos(input) {
16420
17038
  };
16421
17039
  }
16422
17040
  return {
16423
- repos: candidates,
17041
+ repos: candidates2,
16424
17042
  confidence: 0.6,
16425
17043
  proceed: false
16426
17044
  };
@@ -16542,7 +17160,7 @@ function registerCreate(program2) {
16542
17160
  if (decision.stderrLine) {
16543
17161
  process.stderr.write(decision.stderrLine + "\n");
16544
17162
  }
16545
- spawnSync9("docker", ["pull", overrideRef], { stdio: "pipe" });
17163
+ spawnSync11("docker", ["pull", overrideRef], { stdio: "pipe" });
16546
17164
  const { inspectImageProtocolVersions: inspectImageProtocolVersions2, checkProtocolOverlap: checkProtocolOverlap2 } = await Promise.resolve().then(() => (init_protocol_version(), protocol_version_exports));
16547
17165
  const inspect = inspectImageProtocolVersions2(overrideRef);
16548
17166
  if (inspect.inspectFailed) {
@@ -16659,7 +17277,7 @@ function registerCreate(program2) {
16659
17277
  throw err;
16660
17278
  }
16661
17279
  const spinner2 = ora3("Rebuilding olam-devbox:latest\u2026").start();
16662
- const rebuild = spawnSync9(
17280
+ const rebuild = spawnSync11(
16663
17281
  "bash",
16664
17282
  [buildScript],
16665
17283
  { cwd: repoRoot, stdio: "inherit" }
@@ -16866,7 +17484,7 @@ ${pc10.cyan("Host CP UI:")} ${worldUrl}`);
16866
17484
  function resolveRepoRoot(start) {
16867
17485
  let cur = start;
16868
17486
  while (true) {
16869
- if (existsSync28(resolve10(cur, "packages")) && existsSync28(resolve10(cur, "package.json"))) {
17487
+ if (existsSync28(resolve12(cur, "packages")) && existsSync28(resolve12(cur, "package.json"))) {
16870
17488
  return cur;
16871
17489
  }
16872
17490
  const parent = dirname18(cur);
@@ -17103,7 +17721,7 @@ import * as path31 from "node:path";
17103
17721
  var CLI_VERSION2 = process.env["OLAM_CLI_VERSION"] ?? "0.0.0";
17104
17722
  var HOST_CP_PORT2 = 19e3;
17105
17723
  async function getMachineStatus(_probe, _loadCtx, _readToken) {
17106
- const probe = _probe ?? (async () => {
17724
+ const probe2 = _probe ?? (async () => {
17107
17725
  const { probeHostCp: probeHostCp2 } = await Promise.resolve().then(() => (init_host_cp(), host_cp_exports));
17108
17726
  return probeHostCp2();
17109
17727
  });
@@ -17118,7 +17736,7 @@ async function getMachineStatus(_probe, _loadCtx, _readToken) {
17118
17736
  const { loadContext: loadContext2 } = await Promise.resolve().then(() => (init_context(), context_exports));
17119
17737
  return loadContext2();
17120
17738
  });
17121
- const probeResult = await probe();
17739
+ const probeResult = await probe2();
17122
17740
  const tokenPresent = readToken2() !== null;
17123
17741
  const hostCpRunning = probeResult !== null;
17124
17742
  let worldCount = 0;
@@ -17553,14 +18171,14 @@ function printTable(entries) {
17553
18171
  async function confirmInteractive() {
17554
18172
  process.stdout.write(" Type `yes` to proceed: ");
17555
18173
  const buf = [];
17556
- return new Promise((resolve12) => {
18174
+ return new Promise((resolve14) => {
17557
18175
  const onData = (chunk) => {
17558
18176
  buf.push(chunk);
17559
18177
  if (Buffer.concat(buf).toString("utf-8").includes("\n")) {
17560
18178
  process.stdin.removeListener("data", onData);
17561
18179
  process.stdin.pause();
17562
18180
  const answer = Buffer.concat(buf).toString("utf-8").trim();
17563
- resolve12(answer.toLowerCase() === "yes");
18181
+ resolve14(answer.toLowerCase() === "yes");
17564
18182
  }
17565
18183
  };
17566
18184
  process.stdin.resume();
@@ -19507,8 +20125,8 @@ var path35 = {
19507
20125
  win32: { sep: "\\" },
19508
20126
  posix: { sep: "/" }
19509
20127
  };
19510
- var sep = defaultPlatform === "win32" ? path35.win32.sep : path35.posix.sep;
19511
- minimatch.sep = sep;
20128
+ var sep2 = defaultPlatform === "win32" ? path35.win32.sep : path35.posix.sep;
20129
+ minimatch.sep = sep2;
19512
20130
  var GLOBSTAR = /* @__PURE__ */ Symbol("globstar **");
19513
20131
  minimatch.GLOBSTAR = GLOBSTAR;
19514
20132
  var qmark2 = "[^/]";
@@ -20660,10 +21278,10 @@ function deriveSuggestion(issue) {
20660
21278
  if (firstKey === void 0)
20661
21279
  return void 0;
20662
21280
  const isTopLevel = issue.path.length === 0;
20663
- const candidates = isTopLevel ? KNOWN_TOP_LEVEL_KEYS2 : [];
20664
- if (candidates.length === 0)
21281
+ const candidates2 = isTopLevel ? KNOWN_TOP_LEVEL_KEYS2 : [];
21282
+ if (candidates2.length === 0)
20665
21283
  return void 0;
20666
- const closest = nearestMatch(firstKey, [...candidates]);
21284
+ const closest = nearestMatch(firstKey, [...candidates2]);
20667
21285
  return closest ? `did you mean "${closest}"?` : void 0;
20668
21286
  }
20669
21287
  if (issue.code === "invalid_union_discriminator") {
@@ -20671,10 +21289,10 @@ function deriveSuggestion(issue) {
20671
21289
  }
20672
21290
  return void 0;
20673
21291
  }
20674
- function nearestMatch(input, candidates) {
21292
+ function nearestMatch(input, candidates2) {
20675
21293
  let best;
20676
21294
  let bestDistance = 3;
20677
- for (const c of candidates) {
21295
+ for (const c of candidates2) {
20678
21296
  const d = levenshtein(input.toLowerCase(), c.toLowerCase());
20679
21297
  if (d < bestDistance) {
20680
21298
  bestDistance = d;
@@ -22760,7 +23378,7 @@ init_output();
22760
23378
  init_host_cp();
22761
23379
  import * as fs35 from "node:fs";
22762
23380
  import * as path38 from "node:path";
22763
- import { spawnSync as spawnSync11 } from "node:child_process";
23381
+ import { spawnSync as spawnSync13 } from "node:child_process";
22764
23382
  import ora7 from "ora";
22765
23383
  import pc18 from "picocolors";
22766
23384
 
@@ -22768,7 +23386,7 @@ import pc18 from "picocolors";
22768
23386
  import * as fs33 from "node:fs";
22769
23387
  import * as os19 from "node:os";
22770
23388
  import * as path36 from "node:path";
22771
- import { spawnSync as spawnSync10 } from "node:child_process";
23389
+ import { spawnSync as spawnSync12 } from "node:child_process";
22772
23390
  var LOCK_FILE_PATH = path36.join(os19.homedir(), ".olam", ".upgrade.lock");
22773
23391
  var STALE_LOCK_TIMEOUT_MS = 5 * 60 * 1e3;
22774
23392
  function readLockFile(lockPath) {
@@ -22793,7 +23411,7 @@ function isPidAlive2(pid) {
22793
23411
  }
22794
23412
  var PS_UNAVAILABLE = "__ps_unavailable__";
22795
23413
  function getPidCommand(pid) {
22796
- const result = spawnSync10("ps", ["-p", String(pid), "-o", "comm="], {
23414
+ const result = spawnSync12("ps", ["-p", String(pid), "-o", "comm="], {
22797
23415
  encoding: "utf-8",
22798
23416
  stdio: ["ignore", "pipe", "ignore"]
22799
23417
  });
@@ -23052,7 +23670,7 @@ function extractBundleHash(indexHtml) {
23052
23670
  function runStep2(label, cmd, args, opts = {}) {
23053
23671
  const start = Date.now();
23054
23672
  process.stdout.write(` ${pc18.dim(label.padEnd(34))}`);
23055
- const result = spawnSync11(cmd, [...args], {
23673
+ const result = spawnSync13(cmd, [...args], {
23056
23674
  encoding: "utf-8",
23057
23675
  stdio: ["ignore", "pipe", "pipe"],
23058
23676
  cwd: opts.cwd ?? process.cwd(),
@@ -23071,7 +23689,7 @@ function runStep2(label, cmd, args, opts = {}) {
23071
23689
  };
23072
23690
  }
23073
23691
  function isGitDirty(cwd) {
23074
- const result = spawnSync11("git", ["status", "--porcelain"], {
23692
+ const result = spawnSync13("git", ["status", "--porcelain"], {
23075
23693
  encoding: "utf-8",
23076
23694
  stdio: ["ignore", "pipe", "pipe"],
23077
23695
  cwd
@@ -23079,7 +23697,7 @@ function isGitDirty(cwd) {
23079
23697
  return (result.stdout ?? "").trim().length > 0;
23080
23698
  }
23081
23699
  function hasGitUpstream(cwd) {
23082
- const result = spawnSync11("git", ["rev-parse", "--abbrev-ref", "@{u}"], {
23700
+ const result = spawnSync13("git", ["rev-parse", "--abbrev-ref", "@{u}"], {
23083
23701
  encoding: "utf-8",
23084
23702
  stdio: ["ignore", "pipe", "pipe"],
23085
23703
  cwd
@@ -23087,7 +23705,7 @@ function hasGitUpstream(cwd) {
23087
23705
  return result.status === 0;
23088
23706
  }
23089
23707
  function captureHeadSha(cwd) {
23090
- const result = spawnSync11("git", ["rev-parse", "HEAD"], {
23708
+ const result = spawnSync13("git", ["rev-parse", "HEAD"], {
23091
23709
  encoding: "utf-8",
23092
23710
  stdio: ["ignore", "pipe", "pipe"],
23093
23711
  cwd
@@ -23102,7 +23720,7 @@ function abbreviateSha(sha) {
23102
23720
  }
23103
23721
  function imageExists(tag) {
23104
23722
  try {
23105
- const result = spawnSync11("docker", ["image", "inspect", "--format", "{{.Id}}", tag], {
23723
+ const result = spawnSync13("docker", ["image", "inspect", "--format", "{{.Id}}", tag], {
23106
23724
  encoding: "utf-8",
23107
23725
  stdio: ["ignore", "pipe", "ignore"]
23108
23726
  });
@@ -23118,7 +23736,7 @@ function checkRollbackSetExists(plan) {
23118
23736
  }
23119
23737
  var SMOKE_DOCKER_TIMEOUT_MS = 3e4;
23120
23738
  function smokeImage(image, targetSha) {
23121
- const createResult = spawnSync11("docker", ["create", "--name", `olam-smoke-${Date.now()}`, image], {
23739
+ const createResult = spawnSync13("docker", ["create", "--name", `olam-smoke-${Date.now()}`, image], {
23122
23740
  encoding: "utf-8",
23123
23741
  stdio: ["ignore", "pipe", "pipe"],
23124
23742
  timeout: SMOKE_DOCKER_TIMEOUT_MS
@@ -23132,7 +23750,7 @@ function smokeImage(image, targetSha) {
23132
23750
  };
23133
23751
  }
23134
23752
  const containerId = (createResult.stdout ?? "").trim();
23135
- const inspectResult = spawnSync11(
23753
+ const inspectResult = spawnSync13(
23136
23754
  "docker",
23137
23755
  ["inspect", "--format", '{{index .Config.Labels "olam.build.sha"}}', image],
23138
23756
  {
@@ -23142,7 +23760,7 @@ function smokeImage(image, targetSha) {
23142
23760
  }
23143
23761
  );
23144
23762
  if (containerId.length > 0) {
23145
- spawnSync11("docker", ["rm", "-f", containerId], {
23763
+ spawnSync13("docker", ["rm", "-f", containerId], {
23146
23764
  encoding: "utf-8",
23147
23765
  stdio: ["ignore", "ignore", "ignore"],
23148
23766
  timeout: SMOKE_DOCKER_TIMEOUT_MS
@@ -23182,7 +23800,7 @@ var PRODUCTION_SWAP_PLAN = [
23182
23800
  ];
23183
23801
  function dockerTag(source, dest) {
23184
23802
  try {
23185
- const result = spawnSync11("docker", ["tag", source, dest], {
23803
+ const result = spawnSync13("docker", ["tag", source, dest], {
23186
23804
  encoding: "utf-8",
23187
23805
  stdio: ["ignore", "ignore", "pipe"]
23188
23806
  });
@@ -23275,10 +23893,10 @@ async function confirm2(message) {
23275
23893
  if (!process.stdin.isTTY) return true;
23276
23894
  const { createInterface: createInterface6 } = await import("node:readline");
23277
23895
  const rl = createInterface6({ input: process.stdin, output: process.stdout });
23278
- return new Promise((resolve12) => {
23896
+ return new Promise((resolve14) => {
23279
23897
  rl.question(`${message} [y/N] `, (answer) => {
23280
23898
  rl.close();
23281
- resolve12(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
23899
+ resolve14(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
23282
23900
  });
23283
23901
  });
23284
23902
  }
@@ -23346,11 +23964,11 @@ async function waitForAuthHealthLocal(timeoutMs = AUTH_HEALTH_TIMEOUT_MS) {
23346
23964
  async function recreateAuthService() {
23347
23965
  const start = Date.now();
23348
23966
  try {
23349
- spawnSync11("docker", ["stop", "olam-auth"], {
23967
+ spawnSync13("docker", ["stop", "olam-auth"], {
23350
23968
  encoding: "utf-8",
23351
23969
  stdio: ["ignore", "ignore", "ignore"]
23352
23970
  });
23353
- spawnSync11("docker", ["rm", "olam-auth"], {
23971
+ spawnSync13("docker", ["rm", "olam-auth"], {
23354
23972
  encoding: "utf-8",
23355
23973
  stdio: ["ignore", "ignore", "ignore"]
23356
23974
  });
@@ -23609,11 +24227,11 @@ async function defaultRecreateMcpAuthForUpgrade() {
23609
24227
  }
23610
24228
  async function defaultRecreateAuthForUpgrade() {
23611
24229
  try {
23612
- spawnSync11("docker", ["stop", "olam-auth"], {
24230
+ spawnSync13("docker", ["stop", "olam-auth"], {
23613
24231
  encoding: "utf-8",
23614
24232
  stdio: ["ignore", "ignore", "ignore"]
23615
24233
  });
23616
- spawnSync11("docker", ["rm", "olam-auth"], {
24234
+ spawnSync13("docker", ["rm", "olam-auth"], {
23617
24235
  encoding: "utf-8",
23618
24236
  stdio: ["ignore", "ignore", "ignore"]
23619
24237
  });
@@ -23959,7 +24577,7 @@ ${spaResult.stderr}`);
23959
24577
  process.stdout.write(` ${pc18.dim(step.label.padEnd(34))}
23960
24578
  `);
23961
24579
  const start = Date.now();
23962
- const result = spawnSync11("bash", [scriptPath], {
24580
+ const result = spawnSync13("bash", [scriptPath], {
23963
24581
  stdio: "inherit",
23964
24582
  cwd,
23965
24583
  env: { ...process.env, ...olamTagEnv }
@@ -24308,7 +24926,7 @@ function registerLogs(program2) {
24308
24926
  init_context();
24309
24927
  init_output();
24310
24928
  import pc20 from "picocolors";
24311
- import { spawnSync as spawnSync12 } from "node:child_process";
24929
+ import { spawnSync as spawnSync14 } from "node:child_process";
24312
24930
  var SAFE_IDENT4 = /^[a-z0-9][a-z0-9-]{0,63}$/;
24313
24931
  function parseDockerTop(stdout) {
24314
24932
  const trimmed = stdout.trim();
@@ -24329,8 +24947,8 @@ function parseDockerTop(stdout) {
24329
24947
  titles = (lines[0] ?? "").trim().toLowerCase().split(/\s+/);
24330
24948
  processes = lines.slice(1).map((l) => l.trim().split(/\s+/));
24331
24949
  }
24332
- const idx = (candidates) => {
24333
- for (const c of candidates) {
24950
+ const idx = (candidates2) => {
24951
+ for (const c of candidates2) {
24334
24952
  const i = titles.indexOf(c);
24335
24953
  if (i !== -1) return i;
24336
24954
  }
@@ -24408,7 +25026,7 @@ function registerPs(program2) {
24408
25026
  const containerName = `olam-${worldId}-devbox`;
24409
25027
  let watchInterval;
24410
25028
  function fetchAndPrint() {
24411
- const result = spawnSync12(
25029
+ const result = spawnSync14(
24412
25030
  "docker",
24413
25031
  ["top", containerName, "pid", "user", "pcpu", "pmem", "stime", "stat", "cmd"],
24414
25032
  { encoding: "utf-8", timeout: 3e3 }
@@ -24901,7 +25519,7 @@ init_output();
24901
25519
  import * as fs39 from "node:fs";
24902
25520
  import * as os22 from "node:os";
24903
25521
  import * as path42 from "node:path";
24904
- import { spawnSync as spawnSync13 } from "node:child_process";
25522
+ import { spawnSync as spawnSync15 } from "node:child_process";
24905
25523
  import ora8 from "ora";
24906
25524
 
24907
25525
  // src/commands/refresh-helpers.ts
@@ -24945,7 +25563,7 @@ var RESTART_TIMEOUT_S = 30;
24945
25563
  var HEALTH_POLL_MS = 500;
24946
25564
  var HEALTH_TIMEOUT_MS = 3e4;
24947
25565
  function docker(args) {
24948
- const result = spawnSync13("docker", args, {
25566
+ const result = spawnSync15("docker", args, {
24949
25567
  encoding: "utf-8",
24950
25568
  stdio: ["ignore", "pipe", "pipe"]
24951
25569
  });
@@ -25293,7 +25911,7 @@ function registerDiagnose(program2) {
25293
25911
  }
25294
25912
 
25295
25913
  // src/lib/health-probes.ts
25296
- import { spawnSync as spawnSync14 } from "node:child_process";
25914
+ import { spawnSync as spawnSync16 } from "node:child_process";
25297
25915
  import { existsSync as existsSync44, readdirSync as readdirSync11, readFileSync as readFileSync31, statSync as statSync13 } from "node:fs";
25298
25916
  import { homedir as homedir23 } from "node:os";
25299
25917
  import path44 from "node:path";
@@ -25305,7 +25923,7 @@ var HARD_CAP_BYTES = 5e9;
25305
25923
  // src/lib/health-probes.ts
25306
25924
  var HEALTH_TIMEOUT_MS2 = 5e3;
25307
25925
  var defaultDockerExec2 = (cmd, args) => {
25308
- const r = spawnSync14(cmd, [...args], {
25926
+ const r = spawnSync16(cmd, [...args], {
25309
25927
  encoding: "utf-8",
25310
25928
  stdio: ["ignore", "pipe", "pipe"]
25311
25929
  });
@@ -25795,10 +26413,10 @@ var NEXT_STEPS_DOCS = [
25795
26413
  "docs/architecture/manifest-spec.md \u2014 per-repo .adb.yaml schema",
25796
26414
  "docs/architecture/config-spec.md \u2014 workspace .olam/config.yaml schema"
25797
26415
  ];
25798
- var defaultSpawn = (cmd, args) => new Promise((resolve12) => {
26416
+ var defaultSpawn = (cmd, args) => new Promise((resolve14) => {
25799
26417
  const child = spawn5(cmd, [...args], { stdio: "inherit" });
25800
- child.on("exit", (code) => resolve12({ status: code }));
25801
- child.on("error", () => resolve12({ status: 1 }));
26418
+ child.on("exit", (code) => resolve14({ status: code }));
26419
+ child.on("error", () => resolve14({ status: 1 }));
25802
26420
  });
25803
26421
  var defaultPrompt = (question, defaultYes) => {
25804
26422
  if (!process.stdin.isTTY) {
@@ -25808,18 +26426,18 @@ var defaultPrompt = (question, defaultYes) => {
25808
26426
  );
25809
26427
  return Promise.resolve(defaultYes);
25810
26428
  }
25811
- return new Promise((resolve12) => {
26429
+ return new Promise((resolve14) => {
25812
26430
  const rl = createInterface3({ input: process.stdin, output: process.stdout });
25813
26431
  const suffix = defaultYes ? " [Y/n]: " : " [y/N]: ";
25814
26432
  rl.question(`${question}${suffix}`, (answer) => {
25815
26433
  rl.close();
25816
26434
  const t = answer.trim().toLowerCase();
25817
- if (t === "") resolve12(defaultYes);
25818
- else if (t === "y" || t === "yes") resolve12(true);
25819
- else if (t === "n" || t === "no") resolve12(false);
25820
- else resolve12(defaultYes);
26435
+ if (t === "") resolve14(defaultYes);
26436
+ else if (t === "y" || t === "yes") resolve14(true);
26437
+ else if (t === "n" || t === "no") resolve14(false);
26438
+ else resolve14(defaultYes);
25821
26439
  });
25822
- rl.on("close", () => resolve12(defaultYes));
26440
+ rl.on("close", () => resolve14(defaultYes));
25823
26441
  });
25824
26442
  };
25825
26443
  async function phase1SystemCheck(deps) {
@@ -25847,9 +26465,9 @@ function parseNodeMajor(version) {
25847
26465
  const n = Number.parseInt(m[1], 10);
25848
26466
  return Number.isFinite(n) ? n : null;
25849
26467
  }
25850
- function phaseFromProbe(probe) {
25851
- if (probe.ok) return { ok: true, message: probe.message };
25852
- return { ok: false, message: probe.message, remedy: probe.remedy };
26468
+ function phaseFromProbe(probe2) {
26469
+ if (probe2.ok) return { ok: true, message: probe2.message };
26470
+ return { ok: false, message: probe2.message, remedy: probe2.remedy };
25853
26471
  }
25854
26472
  async function phase2CliSanity(deps) {
25855
26473
  const version = deps.olamCliVersion ?? safeReadCliVersion();
@@ -26789,17 +27407,17 @@ function registerWorldUpgrade(program2) {
26789
27407
  // src/commands/mcp/serve.ts
26790
27408
  init_output();
26791
27409
  import { existsSync as existsSync51 } from "node:fs";
26792
- import { dirname as dirname25, resolve as resolve11 } from "node:path";
27410
+ import { dirname as dirname25, resolve as resolve13 } from "node:path";
26793
27411
  import { fileURLToPath as fileURLToPath6 } from "node:url";
26794
27412
  var here = dirname25(fileURLToPath6(import.meta.url));
26795
27413
  var BUNDLE_PATH_CANDIDATES = [
26796
27414
  // bundled (`dist/index.js` after bundle-cli.mjs) — sibling
26797
- resolve11(here, "mcp-server.js"),
27415
+ resolve13(here, "mcp-server.js"),
26798
27416
  // dev / tsc-only (`dist/commands/mcp/serve.js`) — up two levels
26799
- resolve11(here, "..", "..", "mcp-server.js")
27417
+ resolve13(here, "..", "..", "mcp-server.js")
26800
27418
  ];
26801
- function resolveBundlePath(candidates = BUNDLE_PATH_CANDIDATES, exists = existsSync51) {
26802
- return candidates.find(exists) ?? null;
27419
+ function resolveBundlePath(candidates2 = BUNDLE_PATH_CANDIDATES, exists = existsSync51) {
27420
+ return candidates2.find(exists) ?? null;
26803
27421
  }
26804
27422
  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.";
26805
27423
  function registerMcpServe(cmd) {
@@ -26820,14 +27438,14 @@ function registerMcpServe(cmd) {
26820
27438
  init_output();
26821
27439
 
26822
27440
  // src/commands/mcp/install-shared.ts
26823
- import { spawnSync as spawnSync15 } from "node:child_process";
27441
+ import { spawnSync as spawnSync17 } from "node:child_process";
26824
27442
  var DEFAULT_CLAUDE_SHELL_DEPS = {
26825
- spawn: spawnSync15,
27443
+ spawn: spawnSync17,
26826
27444
  log: (msg) => console.log(msg)
26827
27445
  };
26828
27446
  function isOnPath(deps, bin) {
26829
- const probe = process.platform === "win32" ? "where" : "which";
26830
- const result = deps.spawn(probe, [bin], {
27447
+ const probe2 = process.platform === "win32" ? "where" : "which";
27448
+ const result = deps.spawn(probe2, [bin], {
26831
27449
  encoding: "utf8",
26832
27450
  stdio: ["ignore", "pipe", "ignore"]
26833
27451
  });
@@ -26838,6 +27456,10 @@ function normaliseScope(raw) {
26838
27456
  return value === "user" || value === "project" || value === "local" ? value : null;
26839
27457
  }
26840
27458
  var REMEDY_CLAUDE_MISSING = "`claude` not found on PATH. Install Claude Code (https://docs.claude.com/claude-code) or paste the JSON snippet from docs/architecture/mcp-as-npx-served.md.";
27459
+ var NOT_INSTALLED_PATTERN = /no\s+(?:\S+\s+)*mcp\s+server\s+found/i;
27460
+ function looksLikeNotInstalled(text) {
27461
+ return NOT_INSTALLED_PATTERN.test(text);
27462
+ }
26841
27463
 
26842
27464
  // src/commands/mcp/install.ts
26843
27465
  var NPM_PACKAGE_NAME = "@pleri/olam-cli";
@@ -26889,10 +27511,6 @@ function registerMcpInstall(cmd) {
26889
27511
 
26890
27512
  // src/commands/mcp/uninstall.ts
26891
27513
  init_output();
26892
- var NOT_INSTALLED_PATTERN = /no\s+(?:\S+\s+)*mcp\s+server\s+found/i;
26893
- function looksLikeNotInstalled(text) {
26894
- return NOT_INSTALLED_PATTERN.test(text);
26895
- }
26896
27514
  function buildClaudeMcpRemoveArgs(scope) {
26897
27515
  return {
26898
27516
  command: "claude",
@@ -27043,7 +27661,7 @@ function registerMcpLogin(cmd) {
27043
27661
  init_output();
27044
27662
  import * as readline2 from "node:readline";
27045
27663
  async function readTokenSilent(prompt) {
27046
- return new Promise((resolve12, reject) => {
27664
+ return new Promise((resolve14, reject) => {
27047
27665
  const rl = readline2.createInterface({
27048
27666
  input: process.stdin,
27049
27667
  output: process.stdout,
@@ -27061,7 +27679,7 @@ async function readTokenSilent(prompt) {
27061
27679
  process.stdin.removeListener("data", onData);
27062
27680
  process.stdout.write("\n");
27063
27681
  rl.close();
27064
- resolve12(token);
27682
+ resolve14(token);
27065
27683
  } else if (char === "") {
27066
27684
  if (process.stdin.isTTY) process.stdin.setRawMode(false);
27067
27685
  process.stdin.removeListener("data", onData);
@@ -27336,7 +27954,7 @@ async function discoverMcpSources(repoPaths) {
27336
27954
  import { spawn as spawn7 } from "node:child_process";
27337
27955
  var VALIDATION_TIMEOUT_MS = 5e3;
27338
27956
  async function validateMcpEntry(entry) {
27339
- return new Promise((resolve12) => {
27957
+ return new Promise((resolve14) => {
27340
27958
  let stdout = "";
27341
27959
  let timedOut = false;
27342
27960
  let child;
@@ -27346,7 +27964,7 @@ async function validateMcpEntry(entry) {
27346
27964
  env: { ...process.env, ...entry.env ?? {} }
27347
27965
  });
27348
27966
  } catch (err) {
27349
- resolve12({
27967
+ resolve14({
27350
27968
  name: entry.name,
27351
27969
  validated: false,
27352
27970
  reason: err instanceof Error ? err.message : "spawn failed"
@@ -27363,11 +27981,11 @@ async function validateMcpEntry(entry) {
27363
27981
  child.on("close", (code) => {
27364
27982
  clearTimeout(timer);
27365
27983
  if (timedOut) {
27366
- resolve12({ name: entry.name, validated: false, reason: "timeout (5s)" });
27984
+ resolve14({ name: entry.name, validated: false, reason: "timeout (5s)" });
27367
27985
  return;
27368
27986
  }
27369
27987
  const validated = code === 0 && stdout.trim().length > 0;
27370
- resolve12({
27988
+ resolve14({
27371
27989
  name: entry.name,
27372
27990
  validated,
27373
27991
  reason: validated ? "ok" : `exit code ${code ?? "null"}`
@@ -27375,7 +27993,7 @@ async function validateMcpEntry(entry) {
27375
27993
  });
27376
27994
  child.on("error", (err) => {
27377
27995
  clearTimeout(timer);
27378
- resolve12({ name: entry.name, validated: false, reason: err.message });
27996
+ resolve14({ name: entry.name, validated: false, reason: err.message });
27379
27997
  });
27380
27998
  });
27381
27999
  }
@@ -27390,11 +28008,11 @@ async function multiSelectPicker(entries) {
27390
28008
  );
27391
28009
  });
27392
28010
  console.log("\n" + pc29.dim('Enter numbers to import (e.g. 1,2,3 or "all" or Enter to skip):'));
27393
- const answer = await new Promise((resolve12) => {
28011
+ const answer = await new Promise((resolve14) => {
27394
28012
  const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
27395
28013
  rl.question("> ", (ans) => {
27396
28014
  rl.close();
27397
- resolve12(ans.trim());
28015
+ resolve14(ans.trim());
27398
28016
  });
27399
28017
  });
27400
28018
  if (!answer || answer === "") return [];
@@ -27421,21 +28039,21 @@ function registerMcpImport(cmd) {
27421
28039
  }
27422
28040
  printInfo("Sources", sources.length > 0 ? sources.join(", ") : "none");
27423
28041
  printInfo("Found", `${entries.length} MCP server(s) in ${Math.round(durationMs)}ms`);
27424
- let candidates = entries;
28042
+ let candidates2 = entries;
27425
28043
  let skippedCount = 0;
27426
28044
  if (!opts.reimport) {
27427
28045
  const filtered = entries.filter((e) => !existingIds.has(e.name));
27428
28046
  skippedCount = entries.length - filtered.length;
27429
- candidates = filtered;
28047
+ candidates2 = filtered;
27430
28048
  }
27431
28049
  if (skippedCount > 0) {
27432
28050
  console.log(pc29.dim(`skipped: ${skippedCount} already registered`));
27433
28051
  }
27434
- if (candidates.length === 0) {
28052
+ if (candidates2.length === 0) {
27435
28053
  console.log(pc29.dim("Nothing new to import. Use --reimport to force."));
27436
28054
  return;
27437
28055
  }
27438
- const selected = await multiSelectPicker(candidates);
28056
+ const selected = await multiSelectPicker(candidates2);
27439
28057
  if (selected.length === 0) {
27440
28058
  console.log(pc29.dim("No servers selected."));
27441
28059
  return;
@@ -27535,11 +28153,607 @@ function registerMcp(program2) {
27535
28153
  registerMcpRevoke(mcp);
27536
28154
  }
27537
28155
 
28156
+ // src/commands/memory/start.ts
28157
+ init_output();
28158
+ import { spawn as spawn8 } from "node:child_process";
28159
+ import { existsSync as existsSync54, mkdirSync as mkdirSync30, openSync as openSync3, readFileSync as readFileSync38, writeFileSync as writeFileSync24 } from "node:fs";
28160
+ import { join as join52 } from "node:path";
28161
+
28162
+ // src/lib/memory-secret.ts
28163
+ import { existsSync as existsSync53, mkdirSync as mkdirSync29, readFileSync as readFileSync37, statSync as statSync14, writeFileSync as writeFileSync23, renameSync as renameSync5, chmodSync as chmodSync4 } from "node:fs";
28164
+ import { homedir as homedir29 } from "node:os";
28165
+ import { join as join50, dirname as dirname26 } from "node:path";
28166
+ import { randomBytes as randomBytes6 } from "node:crypto";
28167
+ var MEMORY_SECRET_PATH = join50(homedir29(), ".olam", "memory-secret");
28168
+ var SECRET_LEN_BYTES = 32;
28169
+ function generateSecret() {
28170
+ return randomBytes6(SECRET_LEN_BYTES).toString("hex");
28171
+ }
28172
+ function writeSecretAtPath(path56, value) {
28173
+ mkdirSync29(dirname26(path56), { recursive: true });
28174
+ const tmp = `${path56}.tmp.${process.pid}`;
28175
+ writeFileSync23(tmp, value, { mode: 384 });
28176
+ chmodSync4(tmp, 384);
28177
+ renameSync5(tmp, path56);
28178
+ }
28179
+ function readSecretAtPathOrNull(path56) {
28180
+ if (!existsSync53(path56)) return null;
28181
+ const mode = statSync14(path56).mode & 511;
28182
+ if (mode !== 384) {
28183
+ process.stderr.write(
28184
+ `warn: ${path56} has mode 0${mode.toString(8)}; expected 0600. Run 'olam memory secret rotate' to regenerate.
28185
+ `
28186
+ );
28187
+ }
28188
+ return readFileSync37(path56, "utf8").trim();
28189
+ }
28190
+ function readSecretAtPath(path56) {
28191
+ const v = readSecretAtPathOrNull(path56);
28192
+ if (v === null) {
28193
+ throw new Error(
28194
+ `Secret not found at ${path56}. Run 'olam memory start' to generate it.`
28195
+ );
28196
+ }
28197
+ return v;
28198
+ }
28199
+ function ensureMemorySecret(path56 = MEMORY_SECRET_PATH) {
28200
+ const existing = readSecretAtPathOrNull(path56);
28201
+ if (existing) return existing;
28202
+ const fresh = generateSecret();
28203
+ writeSecretAtPath(path56, fresh);
28204
+ return fresh;
28205
+ }
28206
+ function readMemorySecretOrNull(path56 = MEMORY_SECRET_PATH) {
28207
+ return readSecretAtPathOrNull(path56);
28208
+ }
28209
+ function readMemorySecret(path56 = MEMORY_SECRET_PATH) {
28210
+ return readSecretAtPath(path56);
28211
+ }
28212
+ function rotateMemorySecret(path56 = MEMORY_SECRET_PATH) {
28213
+ const fresh = generateSecret();
28214
+ writeSecretAtPath(path56, fresh);
28215
+ return fresh;
28216
+ }
28217
+ function hasMemorySecret(path56 = MEMORY_SECRET_PATH) {
28218
+ return existsSync53(path56);
28219
+ }
28220
+
28221
+ // src/commands/memory/_paths.ts
28222
+ import { homedir as homedir30 } from "node:os";
28223
+ import { join as join51, dirname as dirname27 } from "node:path";
28224
+ import { fileURLToPath as fileURLToPath7 } from "node:url";
28225
+ var OLAM_HOME = join51(homedir30(), ".olam");
28226
+ var OLAM_BIN_DIR = join51(OLAM_HOME, "bin");
28227
+ var III_BINARY_PATH = join51(OLAM_BIN_DIR, "iii");
28228
+ var MEMORY_PID_PATH = join51(OLAM_HOME, "memory.pid");
28229
+ var MEMORY_LOG_PATH = join51(OLAM_HOME, "memory-service.log");
28230
+ var MEMORY_DATA_DIR = join51(OLAM_HOME, "memory-data");
28231
+ var MEMORY_REST_PORT = 3111;
28232
+ var MEMORY_LIVEZ_URL = `http://localhost:${MEMORY_REST_PORT}/agentmemory/livez`;
28233
+ var here2 = dirname27(fileURLToPath7(import.meta.url));
28234
+ var candidates = [
28235
+ // Workspace dev: packages/cli/src/commands/memory/_paths.ts (run via tsx) — unlikely
28236
+ // Workspace built: packages/cli/dist/commands/memory/_paths.js → packages/cli → packages/memory-service
28237
+ join51(here2, "..", "..", "..", "..", "memory-service"),
28238
+ // Bundled: dist/index.js (esbuild) → packages/cli → packages/memory-service
28239
+ join51(here2, "..", "..", "memory-service"),
28240
+ // Fallback: cwd-relative
28241
+ join51(process.cwd(), "packages", "memory-service")
28242
+ ];
28243
+ var MEMORY_SERVICE_CANDIDATES = candidates;
28244
+
28245
+ // src/commands/memory/start.ts
28246
+ var READINESS_TIMEOUT_MS = 3e4;
28247
+ var READINESS_POLL_MS = 500;
28248
+ function resolveMemoryServiceDir() {
28249
+ for (const c of MEMORY_SERVICE_CANDIDATES) {
28250
+ if (existsSync54(c)) return c;
28251
+ }
28252
+ throw new Error(
28253
+ `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.`
28254
+ );
28255
+ }
28256
+ function resolveAgentMemoryBin(serviceDir) {
28257
+ const candidates2 = [
28258
+ join52(serviceDir, "node_modules", ".bin", "agentmemory"),
28259
+ join52(serviceDir, "..", "..", "node_modules", ".bin", "agentmemory"),
28260
+ join52(serviceDir, "..", "..", "..", "node_modules", ".bin", "agentmemory")
28261
+ ];
28262
+ for (const c of candidates2) {
28263
+ if (existsSync54(c)) return c;
28264
+ }
28265
+ throw new Error(
28266
+ `Could not find agentmemory bin. Searched: ${candidates2.join(", ")}. Run 'npm install' from the repo root.`
28267
+ );
28268
+ }
28269
+ function isProcessAlive(pid) {
28270
+ try {
28271
+ process.kill(pid, 0);
28272
+ return true;
28273
+ } catch {
28274
+ return false;
28275
+ }
28276
+ }
28277
+ function readPidFromFile() {
28278
+ if (!existsSync54(MEMORY_PID_PATH)) return null;
28279
+ const raw = readFileSync38(MEMORY_PID_PATH, "utf8").trim();
28280
+ const pid = parseInt(raw, 10);
28281
+ if (!Number.isFinite(pid) || pid <= 0) return null;
28282
+ return pid;
28283
+ }
28284
+ async function probeLivez(secret, signal) {
28285
+ try {
28286
+ const resp = await fetch(MEMORY_LIVEZ_URL, {
28287
+ headers: { authorization: `Bearer ${secret}` },
28288
+ signal
28289
+ });
28290
+ if (!resp.ok) return false;
28291
+ const body = await resp.json();
28292
+ return body.status === "ok";
28293
+ } catch {
28294
+ return false;
28295
+ }
28296
+ }
28297
+ async function waitForReady(secret) {
28298
+ const deadline = Date.now() + READINESS_TIMEOUT_MS;
28299
+ while (Date.now() < deadline) {
28300
+ if (await probeLivez(secret)) return true;
28301
+ await new Promise((r) => setTimeout(r, READINESS_POLL_MS));
28302
+ }
28303
+ return false;
28304
+ }
28305
+ async function runMemoryStart() {
28306
+ printHeader("olam memory start");
28307
+ if (!existsSync54(III_BINARY_PATH)) {
28308
+ printError(
28309
+ `iii binary missing at ${III_BINARY_PATH}. Run: node packages/memory-service/scripts/ensure-iii-engine.mjs`
28310
+ );
28311
+ return 1;
28312
+ }
28313
+ printInfo("iii binary", III_BINARY_PATH);
28314
+ const secret = ensureMemorySecret();
28315
+ printInfo("secret", "~/.olam/memory-secret (0600)");
28316
+ const serviceDir = resolveMemoryServiceDir();
28317
+ const agentmemoryBin = resolveAgentMemoryBin(serviceDir);
28318
+ printInfo("agentmemory", agentmemoryBin);
28319
+ const existingPid = readPidFromFile();
28320
+ if (existingPid !== null && isProcessAlive(existingPid)) {
28321
+ const ready2 = await probeLivez(secret);
28322
+ if (ready2) {
28323
+ printSuccess(`already running (pid ${existingPid}); /agentmemory/livez ok`);
28324
+ return 0;
28325
+ }
28326
+ printError(
28327
+ `pid ${existingPid} is alive but /agentmemory/livez isn't responding. Run 'olam memory stop' to clear, then retry.`
28328
+ );
28329
+ return 1;
28330
+ }
28331
+ mkdirSync30(OLAM_HOME, { recursive: true });
28332
+ mkdirSync30(MEMORY_DATA_DIR, { recursive: true });
28333
+ const logFd = openSync3(MEMORY_LOG_PATH, "a");
28334
+ const child = spawn8(agentmemoryBin, [], {
28335
+ cwd: OLAM_HOME,
28336
+ env: {
28337
+ ...process.env,
28338
+ AGENTMEMORY_SECRET: secret,
28339
+ PATH: `${OLAM_BIN_DIR}:${process.env.PATH ?? ""}`
28340
+ },
28341
+ stdio: ["ignore", logFd, logFd],
28342
+ detached: true
28343
+ });
28344
+ child.unref();
28345
+ if (child.pid === void 0) {
28346
+ printError("spawn returned no pid (process failed to start)");
28347
+ return 1;
28348
+ }
28349
+ writeFileSync24(MEMORY_PID_PATH, `${child.pid}
28350
+ `, { mode: 420 });
28351
+ printInfo("pid", `${child.pid}`);
28352
+ printInfo("readiness", `polling ${MEMORY_LIVEZ_URL}`);
28353
+ const ready = await waitForReady(secret);
28354
+ if (!ready) {
28355
+ printError(
28356
+ `agentmemory failed to come up within ${READINESS_TIMEOUT_MS / 1e3}s. Inspect ${MEMORY_LOG_PATH} for engine errors.`
28357
+ );
28358
+ return 1;
28359
+ }
28360
+ printSuccess(`memory service ready (pid ${child.pid}; logs at ${MEMORY_LOG_PATH})`);
28361
+ return 0;
28362
+ }
28363
+ function registerMemoryStart(cmd) {
28364
+ cmd.command("start").description("Start the host agent-memory process (idempotent; polls livez until ready)").action(async () => {
28365
+ const rc = await runMemoryStart();
28366
+ if (rc !== 0) process.exitCode = rc;
28367
+ });
28368
+ }
28369
+
28370
+ // src/commands/memory/stop.ts
28371
+ init_output();
28372
+ import { existsSync as existsSync55, readFileSync as readFileSync39, unlinkSync as unlinkSync9 } from "node:fs";
28373
+ var SIGTERM_GRACE_MS = 1e4;
28374
+ var POLL_MS = 250;
28375
+ function isAlive(pid) {
28376
+ try {
28377
+ process.kill(pid, 0);
28378
+ return true;
28379
+ } catch {
28380
+ return false;
28381
+ }
28382
+ }
28383
+ async function runMemoryStop() {
28384
+ printHeader("olam memory stop");
28385
+ if (!existsSync55(MEMORY_PID_PATH)) {
28386
+ printSuccess("no pidfile present (nothing to stop)");
28387
+ return 0;
28388
+ }
28389
+ const pid = parseInt(readFileSync39(MEMORY_PID_PATH, "utf8").trim(), 10);
28390
+ if (!Number.isFinite(pid) || pid <= 0) {
28391
+ printWarning(`pidfile contained invalid value; removing`);
28392
+ unlinkSync9(MEMORY_PID_PATH);
28393
+ return 0;
28394
+ }
28395
+ if (!isAlive(pid)) {
28396
+ printSuccess(`pid ${pid} is not running (stale pidfile); cleaned up`);
28397
+ unlinkSync9(MEMORY_PID_PATH);
28398
+ return 0;
28399
+ }
28400
+ printInfo("pid", `${pid}`);
28401
+ try {
28402
+ process.kill(pid, "SIGTERM");
28403
+ } catch (err) {
28404
+ printWarning(`SIGTERM to pid ${pid} failed: ${err.message}`);
28405
+ }
28406
+ const deadline = Date.now() + SIGTERM_GRACE_MS;
28407
+ while (Date.now() < deadline) {
28408
+ if (!isAlive(pid)) break;
28409
+ await new Promise((r) => setTimeout(r, POLL_MS));
28410
+ }
28411
+ if (isAlive(pid)) {
28412
+ printWarning(`pid ${pid} did not exit within ${SIGTERM_GRACE_MS / 1e3}s; sending SIGKILL`);
28413
+ try {
28414
+ process.kill(pid, "SIGKILL");
28415
+ } catch (err) {
28416
+ printWarning(`SIGKILL to pid ${pid} failed: ${err.message}`);
28417
+ }
28418
+ await new Promise((r) => setTimeout(r, 500));
28419
+ }
28420
+ if (existsSync55(MEMORY_PID_PATH)) {
28421
+ unlinkSync9(MEMORY_PID_PATH);
28422
+ }
28423
+ printSuccess(`stopped (pid ${pid})`);
28424
+ return 0;
28425
+ }
28426
+ function registerMemoryStop(cmd) {
28427
+ cmd.command("stop").description("Stop the host agent-memory process (SIGTERM \u2192 SIGKILL after 10s; idempotent)").action(async () => {
28428
+ const rc = await runMemoryStop();
28429
+ if (rc !== 0) process.exitCode = rc;
28430
+ });
28431
+ }
28432
+
28433
+ // src/commands/memory/status.ts
28434
+ init_output();
28435
+ import { existsSync as existsSync56, readFileSync as readFileSync40 } from "node:fs";
28436
+ function isAlive2(pid) {
28437
+ try {
28438
+ process.kill(pid, 0);
28439
+ return true;
28440
+ } catch {
28441
+ return false;
28442
+ }
28443
+ }
28444
+ async function probe(secret) {
28445
+ try {
28446
+ const headers = {};
28447
+ if (secret) headers.authorization = `Bearer ${secret}`;
28448
+ const resp = await fetch(MEMORY_LIVEZ_URL, { headers });
28449
+ if (resp.status === 401) return "unauthorized";
28450
+ if (!resp.ok) return "unknown";
28451
+ const body = await resp.json();
28452
+ return body.status === "ok" ? "ok" : "unknown";
28453
+ } catch {
28454
+ return "unreachable";
28455
+ }
28456
+ }
28457
+ async function collectMemoryStatus() {
28458
+ let pid = null;
28459
+ if (existsSync56(MEMORY_PID_PATH)) {
28460
+ const raw = readFileSync40(MEMORY_PID_PATH, "utf8").trim();
28461
+ const parsed = parseInt(raw, 10);
28462
+ pid = Number.isFinite(parsed) && parsed > 0 ? parsed : null;
28463
+ }
28464
+ const alive = pid === null ? null : isAlive2(pid);
28465
+ const secret = readMemorySecretOrNull();
28466
+ const livez = await probe(secret);
28467
+ return {
28468
+ pid,
28469
+ alive,
28470
+ livez,
28471
+ secretSet: hasMemorySecret(),
28472
+ iiiBinary: existsSync56(III_BINARY_PATH) ? III_BINARY_PATH : null,
28473
+ port: MEMORY_REST_PORT
28474
+ };
28475
+ }
28476
+ async function runMemoryStatus(opts = {}) {
28477
+ const s = await collectMemoryStatus();
28478
+ if (opts.json) {
28479
+ process.stdout.write(JSON.stringify(s, null, 2) + "\n");
28480
+ return s.livez === "ok" ? 0 : 1;
28481
+ }
28482
+ printHeader("olam memory status");
28483
+ printInfo("pid", s.pid === null ? "(no pidfile)" : `${s.pid}`);
28484
+ printInfo("alive", s.alive === null ? "n/a" : s.alive ? "yes" : "no (stale pidfile)");
28485
+ printInfo("livez", s.livez);
28486
+ printInfo("secret", s.secretSet ? "~/.olam/memory-secret (set)" : "(missing)");
28487
+ printInfo("iii", s.iiiBinary ?? "(not installed)");
28488
+ printInfo("port", `${s.port}`);
28489
+ if (s.livez === "ok" && s.alive) return 0;
28490
+ if (s.livez === "unauthorized") {
28491
+ printWarning("livez returned 401 \u2014 the memory service is up but the local secret does not match. Try `olam memory secret rotate`.");
28492
+ return 1;
28493
+ }
28494
+ if (s.alive === false && s.pid !== null) {
28495
+ printWarning("pidfile points at a dead process; run `olam memory stop` to clean up");
28496
+ return 1;
28497
+ }
28498
+ if (!s.iiiBinary) {
28499
+ printWarning("iii binary missing; run `node packages/memory-service/scripts/ensure-iii-engine.mjs`");
28500
+ }
28501
+ if (!s.secretSet) {
28502
+ printWarning("secret missing; `olam memory start` will generate it");
28503
+ }
28504
+ return s.livez === "ok" ? 0 : 1;
28505
+ }
28506
+ function registerMemoryStatus(cmd) {
28507
+ cmd.command("status").description("Diagnostic dump for the host memory service (pid + livez + secret-set check)").option("--json", "Machine-readable JSON output", false).action(async (opts) => {
28508
+ const rc = await runMemoryStatus(opts);
28509
+ if (rc !== 0) process.exitCode = rc;
28510
+ });
28511
+ }
28512
+
28513
+ // src/commands/memory/logs.ts
28514
+ init_output();
28515
+ import { existsSync as existsSync57 } from "node:fs";
28516
+ import { spawn as spawn9 } from "node:child_process";
28517
+ async function runMemoryLogs(opts) {
28518
+ if (!existsSync57(MEMORY_LOG_PATH)) {
28519
+ printWarning(`no log at ${MEMORY_LOG_PATH} (start the service first via 'olam memory start')`);
28520
+ return 1;
28521
+ }
28522
+ const tailN = opts.tail ? parseInt(opts.tail, 10) : 50;
28523
+ if (!Number.isFinite(tailN) || tailN < 0) {
28524
+ printWarning(`invalid --tail value: ${opts.tail}`);
28525
+ return 1;
28526
+ }
28527
+ const args = ["-n", `${tailN}`];
28528
+ if (opts.follow) args.push("-f");
28529
+ args.push(MEMORY_LOG_PATH);
28530
+ printHeader(`olam memory logs (${opts.follow ? "follow" : `tail -n ${tailN}`})`);
28531
+ const child = spawn9("tail", args, { stdio: "inherit" });
28532
+ return new Promise((resolve14) => {
28533
+ child.on("exit", (code) => resolve14(code ?? 0));
28534
+ });
28535
+ }
28536
+ function registerMemoryLogs(cmd) {
28537
+ cmd.command("logs").description("Tail the memory service log file at ~/.olam/memory-service.log").option("-f, --follow", "Stream new log lines (Ctrl-C to stop)", false).option("--tail <n>", "Number of trailing lines (default 50)", "50").action(async (opts) => {
28538
+ const rc = await runMemoryLogs(opts);
28539
+ if (rc !== 0) process.exitCode = rc;
28540
+ });
28541
+ }
28542
+
28543
+ // src/commands/memory/secret.ts
28544
+ import { existsSync as existsSync58 } from "node:fs";
28545
+ init_output();
28546
+ async function runMemorySecretShow() {
28547
+ if (!hasMemorySecret()) {
28548
+ printError(
28549
+ `secret missing at ${MEMORY_SECRET_PATH}. Run 'olam memory start' to generate it.`
28550
+ );
28551
+ return 1;
28552
+ }
28553
+ process.stdout.write(`${readMemorySecret()}
28554
+ `);
28555
+ return 0;
28556
+ }
28557
+ async function runMemorySecretRotate() {
28558
+ printHeader("olam memory secret rotate");
28559
+ const wasRunning = existsSync58(MEMORY_PID_PATH);
28560
+ if (wasRunning) {
28561
+ printInfo("current state", "service running; will restart with new secret");
28562
+ const stopRc = await runMemoryStop();
28563
+ if (stopRc !== 0) {
28564
+ printError("failed to stop the running service; rotation aborted to avoid split-brain");
28565
+ return stopRc;
28566
+ }
28567
+ } else {
28568
+ printInfo("current state", "service stopped");
28569
+ }
28570
+ const fresh = rotateMemorySecret();
28571
+ printSuccess(`secret rotated (${fresh.length / 2}-byte hex written to ${MEMORY_SECRET_PATH})`);
28572
+ if (wasRunning) {
28573
+ printInfo("restarting", "memory service with new secret");
28574
+ const startRc = await runMemoryStart();
28575
+ if (startRc !== 0) {
28576
+ printError(
28577
+ "service failed to restart after rotation. The new secret is on disk; run `olam memory start` once you fix the underlying issue."
28578
+ );
28579
+ return startRc;
28580
+ }
28581
+ } else {
28582
+ printWarning("service was not running; new secret will be picked up on next `olam memory start`");
28583
+ }
28584
+ printWarning(
28585
+ "worlds created BEFORE this rotation still have the old secret in their env. Re-create them (olam destroy + olam create) to pick up the new value."
28586
+ );
28587
+ return 0;
28588
+ }
28589
+ function registerMemorySecret(cmd) {
28590
+ const secret = cmd.command("secret").description("Show or rotate the bearer secret at ~/.olam/memory-secret");
28591
+ secret.command("show").description("Print the current secret value (use with care; do not pipe to logs)").action(async () => {
28592
+ const rc = await runMemorySecretShow();
28593
+ if (rc !== 0) process.exitCode = rc;
28594
+ });
28595
+ secret.command("rotate").description(
28596
+ "Regenerate the secret and restart the running memory service (worlds created before rotation must be re-created)"
28597
+ ).action(async () => {
28598
+ const rc = await runMemorySecretRotate();
28599
+ if (rc !== 0) process.exitCode = rc;
28600
+ });
28601
+ }
28602
+
28603
+ // src/commands/memory/install.ts
28604
+ init_output();
28605
+ var MCP_NAME = "agentmemory";
28606
+ var NPM_PACKAGE_NAME2 = "@agentmemory/mcp";
28607
+ var DEFAULT_SECRET_READER = () => readMemorySecretOrNull();
28608
+ function buildClaudeMcpAddArgs2(scope, agentmemoryUrl, secret) {
28609
+ return {
28610
+ command: "claude",
28611
+ args: [
28612
+ "mcp",
28613
+ "add",
28614
+ MCP_NAME,
28615
+ "--scope",
28616
+ scope,
28617
+ "--env",
28618
+ `AGENTMEMORY_URL=${agentmemoryUrl}`,
28619
+ "--env",
28620
+ `AGENTMEMORY_SECRET=${secret}`,
28621
+ "--",
28622
+ "npx",
28623
+ "-y",
28624
+ NPM_PACKAGE_NAME2
28625
+ ]
28626
+ };
28627
+ }
28628
+ var REMEDY_SECRET_MISSING = `No memory-secret found at ${MEMORY_SECRET_PATH}. Run 'olam memory start' first \u2014 it generates the secret on first launch.`;
28629
+ function redactSecretInText(text) {
28630
+ return text.replace(/AGENTMEMORY_SECRET=\S+/g, "AGENTMEMORY_SECRET=<redacted>");
28631
+ }
28632
+ async function runInstall2(opts, deps = DEFAULT_CLAUDE_SHELL_DEPS) {
28633
+ if (!isOnPath(deps, "claude")) {
28634
+ printError(REMEDY_CLAUDE_MISSING);
28635
+ return 1;
28636
+ }
28637
+ const readSecret = opts.readSecret ?? DEFAULT_SECRET_READER;
28638
+ const secret = opts.secret ?? readSecret();
28639
+ if (!secret) {
28640
+ printError(REMEDY_SECRET_MISSING);
28641
+ return 1;
28642
+ }
28643
+ const agentmemoryUrl = `http://localhost:${MEMORY_REST_PORT}`;
28644
+ const { command, args } = buildClaudeMcpAddArgs2(opts.scope, agentmemoryUrl, secret);
28645
+ const redacted = args.map(
28646
+ (a) => a.startsWith("AGENTMEMORY_SECRET=") ? "AGENTMEMORY_SECRET=<redacted>" : a
28647
+ );
28648
+ deps.log(`Wiring: ${command} ${redacted.join(" ")}`);
28649
+ const result = deps.spawn(command, args, {
28650
+ encoding: "utf8",
28651
+ stdio: ["ignore", "pipe", "pipe"]
28652
+ });
28653
+ if (result.status !== 0) {
28654
+ const detail = redactSecretInText(
28655
+ result.stderr?.trim() || result.stdout?.trim() || "(no output)"
28656
+ );
28657
+ printError(`claude mcp add failed (rc=${result.status ?? "unknown"}): ${detail}`);
28658
+ return result.status ?? 1;
28659
+ }
28660
+ printSuccess(
28661
+ `agentmemory MCP registered with claude (--scope ${opts.scope}; url ${agentmemoryUrl}).`
28662
+ );
28663
+ return 0;
28664
+ }
28665
+ function registerMemoryInstall(cmd) {
28666
+ cmd.command("install").description(
28667
+ "Register agentmemory as an MCP server with the operator's host claude (shells to `claude mcp add`)"
28668
+ ).option("--scope <scope>", "claude mcp scope: user | project | local", "user").action(async (rawOpts) => {
28669
+ const scope = normaliseScope(rawOpts.scope);
28670
+ if (scope === null) {
28671
+ printError(`--scope must be one of: user, project, local (got: ${rawOpts.scope})`);
28672
+ process.exitCode = 1;
28673
+ return;
28674
+ }
28675
+ const rc = await runInstall2({ scope });
28676
+ if (rc !== 0) {
28677
+ process.exitCode = rc;
28678
+ }
28679
+ });
28680
+ }
28681
+
28682
+ // src/commands/memory/uninstall.ts
28683
+ init_output();
28684
+ var MCP_NAME2 = "agentmemory";
28685
+ function buildClaudeMcpRemoveArgs2(scope) {
28686
+ return {
28687
+ command: "claude",
28688
+ args: ["mcp", "remove", MCP_NAME2, "--scope", scope]
28689
+ };
28690
+ }
28691
+ async function runUninstall2(opts, deps = DEFAULT_CLAUDE_SHELL_DEPS) {
28692
+ if (!isOnPath(deps, "claude")) {
28693
+ printError(REMEDY_CLAUDE_MISSING);
28694
+ return 1;
28695
+ }
28696
+ const { command, args } = buildClaudeMcpRemoveArgs2(opts.scope);
28697
+ deps.log(`Unwiring: ${command} ${args.join(" ")}`);
28698
+ const result = deps.spawn(command, args, {
28699
+ encoding: "utf8",
28700
+ stdio: ["ignore", "pipe", "pipe"]
28701
+ });
28702
+ if (result.status === 0) {
28703
+ printSuccess(`agentmemory MCP removed from claude (--scope ${opts.scope}).`);
28704
+ return 0;
28705
+ }
28706
+ const stderr = result.stderr?.trim() ?? "";
28707
+ const stdout = result.stdout?.trim() ?? "";
28708
+ const combined = `${stderr}
28709
+ ${stdout}`.trim();
28710
+ if (looksLikeNotInstalled(combined)) {
28711
+ printWarning(
28712
+ `agentmemory MCP not registered with claude (--scope ${opts.scope}); nothing to remove.`
28713
+ );
28714
+ return 0;
28715
+ }
28716
+ printError(
28717
+ `claude mcp remove failed (rc=${result.status ?? "unknown"}): ${combined || "(no output)"}`
28718
+ );
28719
+ return result.status ?? 1;
28720
+ }
28721
+ function registerMemoryUninstall(cmd) {
28722
+ cmd.command("uninstall").description(
28723
+ "Remove agentmemory from the operator's host claude (symmetric with `install`; idempotent)"
28724
+ ).option("--scope <scope>", "claude mcp scope: user | project | local", "user").action(async (rawOpts) => {
28725
+ const scope = normaliseScope(rawOpts.scope);
28726
+ if (scope === null) {
28727
+ printError(`--scope must be one of: user, project, local (got: ${rawOpts.scope})`);
28728
+ process.exitCode = 1;
28729
+ return;
28730
+ }
28731
+ const rc = await runUninstall2({ scope });
28732
+ if (rc !== 0) {
28733
+ process.exitCode = rc;
28734
+ }
28735
+ });
28736
+ }
28737
+
28738
+ // src/commands/memory/index.ts
28739
+ function registerMemory(program2) {
28740
+ const memory = program2.command("memory").description(
28741
+ "Host-process agent-memory service for the olam fleet (start, stop, status, logs, secret, install, uninstall)"
28742
+ );
28743
+ registerMemoryStart(memory);
28744
+ registerMemoryStop(memory);
28745
+ registerMemoryStatus(memory);
28746
+ registerMemoryLogs(memory);
28747
+ registerMemorySecret(memory);
28748
+ registerMemoryInstall(memory);
28749
+ registerMemoryUninstall(memory);
28750
+ }
28751
+
27538
28752
  // src/commands/kg-build.ts
27539
28753
  init_storage_paths();
27540
28754
  init_workspace_name();
27541
28755
  init_output();
27542
- import { spawnSync as spawnSync16 } from "node:child_process";
28756
+ import { spawnSync as spawnSync18 } from "node:child_process";
27543
28757
  import fs49 from "node:fs";
27544
28758
  import path54 from "node:path";
27545
28759
 
@@ -27547,11 +28761,11 @@ import path54 from "node:path";
27547
28761
  init_storage_paths();
27548
28762
  init_workspace_name();
27549
28763
  import fs47 from "node:fs";
27550
- import { homedir as homedir29 } from "node:os";
28764
+ import { homedir as homedir31 } from "node:os";
27551
28765
  import path52 from "node:path";
27552
28766
  init_output();
27553
28767
  function olamHome4() {
27554
- return process.env.OLAM_HOME ?? path52.join(homedir29(), ".olam");
28768
+ return process.env.OLAM_HOME ?? path52.join(homedir31(), ".olam");
27555
28769
  }
27556
28770
  function kgRoot2() {
27557
28771
  return path52.join(olamHome4(), "kg");
@@ -27806,7 +29020,7 @@ function registerKgStatusCommand(kg) {
27806
29020
  init_storage_paths();
27807
29021
  init_workspace_name();
27808
29022
  init_output();
27809
- import { spawn as spawn8 } from "node:child_process";
29023
+ import { spawn as spawn10 } from "node:child_process";
27810
29024
  import fs48 from "node:fs";
27811
29025
  import path53 from "node:path";
27812
29026
  function pidFilePath(workspace) {
@@ -27876,7 +29090,7 @@ async function runKgWatch(workspaceArg, opts, deps = {}) {
27876
29090
  if (pidState.status === "stale-reclaimed") {
27877
29091
  printInfo("stale-pid", `reclaimed dead PID file at ${pidFilePath(name)}`);
27878
29092
  }
27879
- const spawnFn = deps.spawnImpl ?? spawn8;
29093
+ const spawnFn = deps.spawnImpl ?? spawn10;
27880
29094
  const child = spawnFn(
27881
29095
  "graphify",
27882
29096
  [cwd, "--watch", "--update", "--graph", graphPath],
@@ -27905,16 +29119,16 @@ async function runKgWatch(workspaceArg, opts, deps = {}) {
27905
29119
  process.on("SIGINT", () => forward("SIGINT"));
27906
29120
  process.on("SIGTERM", () => forward("SIGTERM"));
27907
29121
  }
27908
- return new Promise((resolve12) => {
29122
+ return new Promise((resolve14) => {
27909
29123
  child.on("exit", (code, signal) => {
27910
29124
  removePidFile(name);
27911
29125
  const exitCode = typeof code === "number" ? code : signal === "SIGINT" || signal === "SIGTERM" ? 0 : 1;
27912
- resolve12({ exitCode, pidWritten: true });
29126
+ resolve14({ exitCode, pidWritten: true });
27913
29127
  });
27914
29128
  child.on("error", (err) => {
27915
29129
  removePidFile(name);
27916
29130
  printError(`graphify subprocess error: ${err.message}`);
27917
- resolve12({ exitCode: 1, pidWritten: true });
29131
+ resolve14({ exitCode: 1, pidWritten: true });
27918
29132
  });
27919
29133
  });
27920
29134
  }
@@ -27937,13 +29151,13 @@ function resolveWorkspace(arg) {
27937
29151
  }
27938
29152
  function copyWorkspaceToScratch(source, scratch) {
27939
29153
  if (process.platform === "darwin") {
27940
- const r2 = spawnSync16("cp", ["-c", "-r", source + "/.", scratch], {
29154
+ const r2 = spawnSync18("cp", ["-c", "-r", source + "/.", scratch], {
27941
29155
  stdio: ["ignore", "ignore", "pipe"],
27942
29156
  encoding: "utf-8"
27943
29157
  });
27944
29158
  if (r2.status === 0) return "cp-c-r-reflink";
27945
29159
  }
27946
- const r = spawnSync16("cp", ["-r", source + "/.", scratch], {
29160
+ const r = spawnSync18("cp", ["-r", source + "/.", scratch], {
27947
29161
  stdio: ["ignore", "ignore", "pipe"],
27948
29162
  encoding: "utf-8"
27949
29163
  });
@@ -27964,7 +29178,7 @@ function parseNodeCount(graphifyOutDir) {
27964
29178
  }
27965
29179
  }
27966
29180
  function readGraphifyVersion(image) {
27967
- const r = spawnSync16(
29181
+ const r = spawnSync18(
27968
29182
  "docker",
27969
29183
  [
27970
29184
  "run",
@@ -28016,7 +29230,7 @@ async function runKgBuild(workspaceArg, options = {}) {
28016
29230
  "update",
28017
29231
  "."
28018
29232
  ];
28019
- const r = human ? spawnSync16("docker", dockerArgs, { stdio: "inherit" }) : spawnSync16("docker", dockerArgs, { stdio: ["ignore", "ignore", "pipe"] });
29233
+ const r = human ? spawnSync18("docker", dockerArgs, { stdio: "inherit" }) : spawnSync18("docker", dockerArgs, { stdio: ["ignore", "ignore", "pipe"] });
28020
29234
  if (r.status !== 0) {
28021
29235
  printError(`graphify update failed (exit ${r.status})`);
28022
29236
  return { exitCode: r.status ?? 1 };
@@ -28072,6 +29286,250 @@ function registerKg(program2) {
28072
29286
  registerKgWatchCommand(kg);
28073
29287
  }
28074
29288
 
29289
+ // src/commands/seed.ts
29290
+ init_output();
29291
+ import { spawnSync as spawnSync19, spawn as spawnAsync2 } from "node:child_process";
29292
+ var DEFAULT_SINGLETON_CONTAINER = "olam-postgres";
29293
+ var DEFAULT_SINGLETON_USER = "development";
29294
+ function assertValidSeedName(name) {
29295
+ if (!/^[a-z][a-z0-9_]*$/.test(name)) {
29296
+ throw new Error(
29297
+ `invalid seed name "${name}" \u2014 must match [a-z][a-z0-9_]* (no hyphens, no uppercase)`
29298
+ );
29299
+ }
29300
+ }
29301
+ function singletonDocker(container, user, args, stdin) {
29302
+ return spawnSync19(
29303
+ "docker",
29304
+ ["exec", "-i", container, "psql", "-U", user, ...args],
29305
+ { encoding: "utf-8", input: stdin }
29306
+ );
29307
+ }
29308
+ function singletonHasDb(container, user, db) {
29309
+ const r = singletonDocker(container, user, [
29310
+ "-d",
29311
+ "postgres",
29312
+ "-tAc",
29313
+ `SELECT 1 FROM pg_database WHERE datname='${db.replace(/'/g, "''")}'`
29314
+ ]);
29315
+ return r.status === 0 && (r.stdout || "").trim() === "1";
29316
+ }
29317
+ function singletonListSeeds(container, user) {
29318
+ const r = singletonDocker(container, user, [
29319
+ "-d",
29320
+ "postgres",
29321
+ "-tAc",
29322
+ "SELECT datname || '|' || pg_size_pretty(pg_database_size(datname)) FROM pg_database WHERE datname LIKE '%\\_seed' ORDER BY datname"
29323
+ ]);
29324
+ if (r.status !== 0) {
29325
+ throw new Error(`failed to list seeds: ${(r.stderr || "").trim()}`);
29326
+ }
29327
+ const lines = (r.stdout || "").trim().split("\n").filter((l) => l.length > 0);
29328
+ return lines.map((line) => {
29329
+ const [name = "", size = ""] = line.split("|");
29330
+ return { name, size };
29331
+ });
29332
+ }
29333
+ function singletonListClonesOf(container, user, seed) {
29334
+ const prefix = seed.endsWith("_seed") ? seed.slice(0, -"_seed".length) : seed;
29335
+ const r = singletonDocker(container, user, [
29336
+ "-d",
29337
+ "postgres",
29338
+ "-tAc",
29339
+ `SELECT datname FROM pg_database WHERE datname LIKE '${prefix.replace(/'/g, "''")}_world_%' ORDER BY datname`
29340
+ ]);
29341
+ if (r.status !== 0) return [];
29342
+ return (r.stdout || "").trim().split("\n").filter((l) => l.length > 0);
29343
+ }
29344
+ async function handleBake(opts) {
29345
+ assertValidSeedName(opts.as);
29346
+ const singleton = opts.singletonContainer ?? DEFAULT_SINGLETON_CONTAINER;
29347
+ const singletonUser = opts.singletonUser ?? DEFAULT_SINGLETON_USER;
29348
+ const sources = [opts.sourceContainer, opts.sourceUrl, opts.sourceLocal].filter(Boolean);
29349
+ if (sources.length === 0) {
29350
+ throw new Error(
29351
+ "no source specified \u2014 pass exactly one of --source-container, --source-url, --source-local"
29352
+ );
29353
+ }
29354
+ if (sources.length > 1) {
29355
+ throw new Error("multiple sources specified \u2014 pass exactly one of --source-container, --source-url, --source-local");
29356
+ }
29357
+ const ping = spawnSync19("docker", ["inspect", "--format", "{{.State.Status}}", singleton], { encoding: "utf-8" });
29358
+ if (ping.status !== 0 || (ping.stdout || "").trim() !== "running") {
29359
+ throw new Error(`singleton container "${singleton}" not running \u2014 run \`olam bootstrap\` first`);
29360
+ }
29361
+ if (singletonHasDb(singleton, singletonUser, opts.as)) {
29362
+ if (!opts.force) {
29363
+ throw new Error(`seed "${opts.as}" already exists \u2014 pass --force to overwrite (DROP + recreate)`);
29364
+ }
29365
+ const clones = singletonListClonesOf(singleton, singletonUser, opts.as);
29366
+ if (clones.length > 0) {
29367
+ throw new Error(
29368
+ `cannot --force overwrite "${opts.as}" \u2014 ${clones.length} per-world clone(s) still depend on it: ${clones.join(", ")}
29369
+ Destroy those worlds first, then re-bake.`
29370
+ );
29371
+ }
29372
+ printWarning(`Dropping existing seed "${opts.as}" (--force)`);
29373
+ const drop = singletonDocker(singleton, singletonUser, ["-d", "postgres", "-c", `DROP DATABASE "${opts.as}"`]);
29374
+ if (drop.status !== 0) {
29375
+ throw new Error(`DROP DATABASE failed: ${(drop.stderr || "").trim()}`);
29376
+ }
29377
+ }
29378
+ process.stdout.write(` Creating empty target DB "${opts.as}" on singleton
29379
+ `);
29380
+ const create = singletonDocker(singleton, singletonUser, ["-d", "postgres", "-c", `CREATE DATABASE "${opts.as}"`]);
29381
+ if (create.status !== 0) {
29382
+ throw new Error(`CREATE DATABASE "${opts.as}" failed: ${(create.stderr || "").trim()}`);
29383
+ }
29384
+ let dumpArgs;
29385
+ let dumpEnv = { PATH: process.env["PATH"] || "" };
29386
+ if (opts.sourceContainer) {
29387
+ const srcDb = opts.sourceDb ?? "postgres";
29388
+ const srcUser = opts.sourceUser ?? DEFAULT_SINGLETON_USER;
29389
+ process.stdout.write(` Source: container ${opts.sourceContainer} DB=${srcDb} user=${srcUser}
29390
+ `);
29391
+ dumpArgs = ["exec", opts.sourceContainer, "pg_dump", "-U", srcUser, "-d", srcDb, "--no-owner", "--no-privileges"];
29392
+ dumpEnv = { PATH: process.env["PATH"] || "" };
29393
+ } else if (opts.sourceLocal) {
29394
+ if (!singletonHasDb(singleton, singletonUser, opts.sourceLocal)) {
29395
+ throw new Error(`--source-local "${opts.sourceLocal}" does not exist on singleton`);
29396
+ }
29397
+ process.stdout.write(` Source: singleton local DB ${opts.sourceLocal}
29398
+ `);
29399
+ dumpArgs = ["exec", singleton, "pg_dump", "-U", singletonUser, "-d", opts.sourceLocal, "--no-owner", "--no-privileges"];
29400
+ dumpEnv = { PATH: process.env["PATH"] || "" };
29401
+ } else {
29402
+ process.stdout.write(` Source: ${opts.sourceUrl.replace(/\/\/[^:]+:[^@]+@/, "//<creds>@")}
29403
+ `);
29404
+ dumpArgs = ["exec", "-e", `PGURL=${opts.sourceUrl}`, singleton, "sh", "-c", 'pg_dump --no-owner --no-privileges "$PGURL"'];
29405
+ dumpEnv = { PATH: process.env["PATH"] || "" };
29406
+ }
29407
+ const dumper = spawnAsync2("docker", dumpArgs, { stdio: ["ignore", "pipe", "inherit"], env: dumpEnv });
29408
+ const loader = spawnAsync2(
29409
+ "docker",
29410
+ ["exec", "-i", singleton, "psql", "-U", singletonUser, "-d", opts.as, "-v", "ON_ERROR_STOP=1", "-q"],
29411
+ { stdio: ["pipe", "inherit", "inherit"], env: { PATH: process.env["PATH"] || "" } }
29412
+ );
29413
+ dumper.stdout.pipe(loader.stdin);
29414
+ const [dumpExit, loadExit] = await Promise.all([
29415
+ new Promise((res) => dumper.on("close", (code) => res(code ?? 1))),
29416
+ new Promise((res) => loader.on("close", (code) => res(code ?? 1)))
29417
+ ]);
29418
+ if (dumpExit !== 0) {
29419
+ throw new Error(`pg_dump exited with code ${dumpExit}`);
29420
+ }
29421
+ if (loadExit !== 0) {
29422
+ throw new Error(`psql load exited with code ${loadExit}`);
29423
+ }
29424
+ const size = singletonDocker(singleton, singletonUser, [
29425
+ "-d",
29426
+ "postgres",
29427
+ "-tAc",
29428
+ `SELECT pg_size_pretty(pg_database_size('${opts.as}'))`
29429
+ ]);
29430
+ const sizeStr = (size.stdout || "").trim() || "unknown";
29431
+ printSuccess(`Seed "${opts.as}" baked (${sizeStr})`);
29432
+ }
29433
+ function handleList3(opts) {
29434
+ const singleton = opts.singletonContainer ?? DEFAULT_SINGLETON_CONTAINER;
29435
+ const singletonUser = opts.singletonUser ?? DEFAULT_SINGLETON_USER;
29436
+ const seeds = singletonListSeeds(singleton, singletonUser);
29437
+ if (opts.json) {
29438
+ process.stdout.write(JSON.stringify(seeds, null, 2) + "\n");
29439
+ return;
29440
+ }
29441
+ if (seeds.length === 0) {
29442
+ process.stdout.write("No seeds found on singleton.\n");
29443
+ return;
29444
+ }
29445
+ printHeader(`${seeds.length} seed(s) on ${singleton}`);
29446
+ const nameWidth = Math.max(...seeds.map((s) => s.name.length), 4);
29447
+ for (const s of seeds) {
29448
+ process.stdout.write(` ${s.name.padEnd(nameWidth)} ${s.size}
29449
+ `);
29450
+ }
29451
+ }
29452
+ function handleRemove(name, opts) {
29453
+ assertValidSeedName(name);
29454
+ const singleton = opts.singletonContainer ?? DEFAULT_SINGLETON_CONTAINER;
29455
+ const singletonUser = opts.singletonUser ?? DEFAULT_SINGLETON_USER;
29456
+ if (!singletonHasDb(singleton, singletonUser, name)) {
29457
+ printWarning(`Seed "${name}" does not exist on singleton \u2014 nothing to do`);
29458
+ return;
29459
+ }
29460
+ const clones = singletonListClonesOf(singleton, singletonUser, name);
29461
+ if (clones.length > 0 && !opts.force) {
29462
+ throw new Error(
29463
+ `seed "${name}" has ${clones.length} active world clone(s): ${clones.join(", ")}
29464
+ Destroy those worlds first, or re-run with --force to drop anyway (CAUTION: leaves world DBs orphaned).`
29465
+ );
29466
+ }
29467
+ const drop = singletonDocker(singleton, singletonUser, ["-d", "postgres", "-c", `DROP DATABASE "${name}"`]);
29468
+ if (drop.status !== 0) {
29469
+ throw new Error(`DROP DATABASE "${name}" failed: ${(drop.stderr || "").trim()}`);
29470
+ }
29471
+ printSuccess(`Removed seed "${name}"`);
29472
+ }
29473
+ function handleVerify(name, opts) {
29474
+ assertValidSeedName(name);
29475
+ const singleton = opts.singletonContainer ?? DEFAULT_SINGLETON_CONTAINER;
29476
+ const singletonUser = opts.singletonUser ?? DEFAULT_SINGLETON_USER;
29477
+ if (!singletonHasDb(singleton, singletonUser, name)) {
29478
+ throw new Error(`seed "${name}" does not exist on singleton`);
29479
+ }
29480
+ const heartbeat = singletonDocker(singleton, singletonUser, ["-d", name, "-tAc", "SELECT 1"]);
29481
+ if (heartbeat.status !== 0 || (heartbeat.stdout || "").trim() !== "1") {
29482
+ throw new Error(`heartbeat failed: ${(heartbeat.stderr || "").trim()}`);
29483
+ }
29484
+ const meta = singletonDocker(singleton, singletonUser, [
29485
+ "-d",
29486
+ name,
29487
+ "-tAc",
29488
+ "SELECT pg_size_pretty(pg_database_size(current_database())) || '|' || (SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public')"
29489
+ ]);
29490
+ const [size = "?", tableCount = "?"] = (meta.stdout || "").trim().split("|");
29491
+ const clones = singletonListClonesOf(singleton, singletonUser, name);
29492
+ printSuccess(`Seed "${name}" OK`);
29493
+ printInfo("size", size);
29494
+ printInfo("tables", `${tableCount} (public schema)`);
29495
+ printInfo("clones", `${clones.length}${clones.length > 0 ? " (" + clones.join(", ") + ")" : ""}`);
29496
+ }
29497
+ function registerSeed(program2) {
29498
+ const seed = program2.command("seed").description("Manage postgres seed templates on the olam-postgres singleton");
29499
+ seed.command("bake").description("Bake a source DB into the singleton as a named seed template").requiredOption("--as <name>", "Target seed name (e.g. atlas_common_seed); [a-z][a-z0-9_]*").option("--source-container <name>", "Source: pg_dump inside this docker container").option("--source-url <url>", "Source: pg_dump via postgres:// URL (libpq)").option("--source-local <db>", "Source: pg_dump a DB already on the singleton").option("--source-db <db>", "DB name to dump (when --source-container; default: postgres)").option("--source-user <user>", "Source-side user (when --source-container; default: development)").option("--singleton-container <name>", "Override singleton container name (default: olam-postgres)").option("--singleton-user <user>", "Override singleton user (default: development)").option("--force", "Drop + recreate if the seed already exists").action(async (opts) => {
29500
+ try {
29501
+ await handleBake(opts);
29502
+ } catch (err) {
29503
+ printError(err instanceof Error ? err.message : String(err));
29504
+ process.exit(1);
29505
+ }
29506
+ });
29507
+ seed.command("list").description("List seed templates on the singleton").option("--json", "Emit machine-parseable JSON").option("--singleton-container <name>", "Override singleton container name (default: olam-postgres)").option("--singleton-user <user>", "Override singleton user (default: development)").action((opts) => {
29508
+ try {
29509
+ handleList3(opts);
29510
+ } catch (err) {
29511
+ printError(err instanceof Error ? err.message : String(err));
29512
+ process.exit(1);
29513
+ }
29514
+ });
29515
+ seed.command("remove <name>").description("Remove a seed template (refuses by default if any per-world clones exist)").option("--force", "DROP even if active clones exist (clones will be orphaned)").option("--singleton-container <name>", "Override singleton container name (default: olam-postgres)").option("--singleton-user <user>", "Override singleton user (default: development)").action((name, opts) => {
29516
+ try {
29517
+ handleRemove(name, opts);
29518
+ } catch (err) {
29519
+ printError(err instanceof Error ? err.message : String(err));
29520
+ process.exit(1);
29521
+ }
29522
+ });
29523
+ seed.command("verify <name>").description("Sanity-check a seed (existence, connectivity, size, clone count)").option("--singleton-container <name>", "Override singleton container name (default: olam-postgres)").option("--singleton-user <user>", "Override singleton user (default: development)").action((name, opts) => {
29524
+ try {
29525
+ handleVerify(name, opts);
29526
+ } catch (err) {
29527
+ printError(err instanceof Error ? err.message : String(err));
29528
+ process.exit(1);
29529
+ }
29530
+ });
29531
+ }
29532
+
28075
29533
  // src/pleri-config.ts
28076
29534
  import * as fs50 from "node:fs";
28077
29535
  import * as path55 from "node:path";
@@ -28122,6 +29580,7 @@ registerCrystallize(program, { hidden: !isPleriConfigured() });
28122
29580
  registerPr(program);
28123
29581
  registerWorkspace(program);
28124
29582
  registerHostCp(program);
29583
+ registerSeed(program);
28125
29584
  registerLanes(program);
28126
29585
  registerPolicyCheck(program);
28127
29586
  registerWorldspec(program);
@@ -28141,6 +29600,7 @@ registerBegin(program);
28141
29600
  registerStop(program);
28142
29601
  registerWorldUpgrade(program);
28143
29602
  registerMcp(program);
29603
+ registerMemory(program);
28144
29604
  registerKg(program);
28145
29605
  registerConfig(program);
28146
29606
  registerRepos(program);