@pleri/olam-cli 0.1.120 → 0.1.126
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.
- package/dist/commands/bootstrap.d.ts +4 -0
- package/dist/commands/bootstrap.d.ts.map +1 -1
- package/dist/commands/bootstrap.js +64 -0
- package/dist/commands/bootstrap.js.map +1 -1
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +43 -0
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/mcp/install-shared.d.ts +2 -0
- package/dist/commands/mcp/install-shared.d.ts.map +1 -1
- package/dist/commands/mcp/install-shared.js +13 -0
- package/dist/commands/mcp/install-shared.js.map +1 -1
- package/dist/commands/mcp/uninstall.d.ts.map +1 -1
- package/dist/commands/mcp/uninstall.js +1 -10
- package/dist/commands/mcp/uninstall.js.map +1 -1
- package/dist/commands/memory/_paths.d.ts +15 -0
- package/dist/commands/memory/_paths.d.ts.map +1 -0
- package/dist/commands/memory/_paths.js +34 -0
- package/dist/commands/memory/_paths.js.map +1 -0
- package/dist/commands/memory/index.d.ts +17 -0
- package/dist/commands/memory/index.d.ts.map +1 -0
- package/dist/commands/memory/index.js +34 -0
- package/dist/commands/memory/index.js.map +1 -0
- package/dist/commands/memory/install.d.ts +57 -0
- package/dist/commands/memory/install.d.ts.map +1 -0
- package/dist/commands/memory/install.js +114 -0
- package/dist/commands/memory/install.js.map +1 -0
- package/dist/commands/memory/logs.d.ts +15 -0
- package/dist/commands/memory/logs.d.ts.map +1 -0
- package/dist/commands/memory/logs.js +45 -0
- package/dist/commands/memory/logs.js.map +1 -0
- package/dist/commands/memory/secret.d.ts +16 -0
- package/dist/commands/memory/secret.d.ts.map +1 -0
- package/dist/commands/memory/secret.js +79 -0
- package/dist/commands/memory/secret.js.map +1 -0
- package/dist/commands/memory/start.d.ts +23 -0
- package/dist/commands/memory/start.d.ts.map +1 -0
- package/dist/commands/memory/start.js +166 -0
- package/dist/commands/memory/start.js.map +1 -0
- package/dist/commands/memory/status.d.ts +25 -0
- package/dist/commands/memory/status.d.ts.map +1 -0
- package/dist/commands/memory/status.js +101 -0
- package/dist/commands/memory/status.js.map +1 -0
- package/dist/commands/memory/stop.d.ts +12 -0
- package/dist/commands/memory/stop.d.ts.map +1 -0
- package/dist/commands/memory/stop.js +81 -0
- package/dist/commands/memory/stop.js.map +1 -0
- package/dist/commands/memory/uninstall.d.ts +19 -0
- package/dist/commands/memory/uninstall.d.ts.map +1 -0
- package/dist/commands/memory/uninstall.js +60 -0
- package/dist/commands/memory/uninstall.js.map +1 -0
- package/dist/commands/seed.d.ts +27 -0
- package/dist/commands/seed.d.ts.map +1 -0
- package/dist/commands/seed.js +303 -0
- package/dist/commands/seed.js.map +1 -0
- package/dist/image-digests.json +3 -3
- package/dist/index.js +1835 -259
- package/dist/index.js.map +1 -1
- package/dist/lib/memory-secret.d.ts +48 -0
- package/dist/lib/memory-secret.d.ts.map +1 -0
- package/dist/lib/memory-secret.js +92 -0
- package/dist/lib/memory-secret.js.map +1 -0
- package/dist/lib/world-mcp-register.d.ts +98 -0
- package/dist/lib/world-mcp-register.d.ts.map +1 -0
- package/dist/lib/world-mcp-register.js +117 -0
- package/dist/lib/world-mcp-register.js.map +1 -0
- package/dist/mcp-server.js +502 -65
- package/package.json +1 -1
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
|
|
24
|
+
const here3 = path.dirname(fileURLToPath(import.meta.url));
|
|
25
25
|
for (const candidate of [
|
|
26
|
-
path.join(
|
|
27
|
-
path.join(
|
|
28
|
-
path.join(
|
|
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
|
-
|
|
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((
|
|
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
|
|
5349
|
-
const pkgsDir = path9.resolve(path9.dirname(
|
|
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((
|
|
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((
|
|
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
|
-
|
|
5754
|
+
resolve14({ exitCode: -1, stdout, stderr: stderr + err.message });
|
|
5666
5755
|
});
|
|
5667
5756
|
child.on("close", (code) => {
|
|
5668
|
-
|
|
5757
|
+
resolve14({ exitCode: code ?? -1, stdout, stderr });
|
|
5669
5758
|
});
|
|
5670
5759
|
});
|
|
5671
5760
|
}
|
|
@@ -5781,6 +5870,17 @@ function readHostCpToken() {
|
|
|
5781
5870
|
return "";
|
|
5782
5871
|
}
|
|
5783
5872
|
}
|
|
5873
|
+
function readMemorySecret() {
|
|
5874
|
+
const fromEnv = process.env["OLAM_MEMORY_SECRET"];
|
|
5875
|
+
if (fromEnv && fromEnv.length > 0)
|
|
5876
|
+
return fromEnv;
|
|
5877
|
+
const file = path11.join(os6.homedir(), ".olam", "memory-secret");
|
|
5878
|
+
try {
|
|
5879
|
+
return fs10.readFileSync(file, "utf-8").trim();
|
|
5880
|
+
} catch {
|
|
5881
|
+
return "";
|
|
5882
|
+
}
|
|
5883
|
+
}
|
|
5784
5884
|
function sanitizeContainerName(name) {
|
|
5785
5885
|
return name.replace(/[^a-zA-Z0-9_.-]/g, "-");
|
|
5786
5886
|
}
|
|
@@ -5828,7 +5928,7 @@ async function auditPortsForZombies(docker2, hostPorts) {
|
|
|
5828
5928
|
}
|
|
5829
5929
|
}
|
|
5830
5930
|
}
|
|
5831
|
-
var realPullImage, serviceContainerName, worldContainerName, miseCacheVolumeName, PACKAGE_MANAGER_CACHE_DIRS, createServiceContainer, DEFAULT_IMAGE2, CONTROL_PLANE_PORT, HOST_CONTROL_PLANE_BASE, HOST_APP_PORT_BASE_DELTA, DEFAULT_MEMORY_BYTES, PortHeldByZombieError, createWorldContainer, stopAndRemove;
|
|
5931
|
+
var realPullImage, AGENTMEMORY_HOST_INTERNAL_URL, serviceContainerName, worldContainerName, miseCacheVolumeName, PACKAGE_MANAGER_CACHE_DIRS, createServiceContainer, DEFAULT_IMAGE2, CONTROL_PLANE_PORT, HOST_CONTROL_PLANE_BASE, HOST_APP_PORT_BASE_DELTA, DEFAULT_MEMORY_BYTES, PortHeldByZombieError, createWorldContainer, stopAndRemove;
|
|
5832
5932
|
var init_container2 = __esm({
|
|
5833
5933
|
"../adapters/dist/docker/container.js"() {
|
|
5834
5934
|
"use strict";
|
|
@@ -5836,6 +5936,7 @@ var init_container2 = __esm({
|
|
|
5836
5936
|
init_pull();
|
|
5837
5937
|
init_volume();
|
|
5838
5938
|
realPullImage = (imageRef, platform) => pullImageWithRetry(imageRef, void 0, platform ? { perAttemptTimeoutMs: 18e4, retryOnce: true, platform } : { perAttemptTimeoutMs: 18e4, retryOnce: true });
|
|
5939
|
+
AGENTMEMORY_HOST_INTERNAL_URL = "http://host.docker.internal:3111";
|
|
5839
5940
|
serviceContainerName = (worldId, serviceName) => `olam-${sanitizeContainerName(worldId)}-${sanitizeContainerName(serviceName)}`;
|
|
5840
5941
|
worldContainerName = (worldId) => `olam-${sanitizeContainerName(worldId)}-devbox`;
|
|
5841
5942
|
miseCacheVolumeName = (arch2 = process.arch) => `olam-mise-cache-${arch2}`;
|
|
@@ -5918,6 +6019,7 @@ var init_container2 = __esm({
|
|
|
5918
6019
|
const labels = olamLabels(worldId, worldName);
|
|
5919
6020
|
const authSecret = readAuthSecret();
|
|
5920
6021
|
const hostCpToken = readHostCpToken();
|
|
6022
|
+
const memorySecret = readMemorySecret();
|
|
5921
6023
|
const worldEnv = {
|
|
5922
6024
|
OLAM_WORLD_ID: worldId,
|
|
5923
6025
|
OLAM_WORLD_NAME: worldName,
|
|
@@ -5934,6 +6036,14 @@ var init_container2 = __esm({
|
|
|
5934
6036
|
OLAM_HOST_CP_URL: "http://host.docker.internal:19000",
|
|
5935
6037
|
...authSecret ? { OLAM_AUTH_SECRET: authSecret } : {},
|
|
5936
6038
|
...hostCpToken ? { OLAM_HOST_CP_TOKEN: hostCpToken } : {},
|
|
6039
|
+
// Phase B1 — agent-memory wiring. Both vars are injected together
|
|
6040
|
+
// (or neither): without a secret the URL is useless. The in-world
|
|
6041
|
+
// @agentmemory/mcp shim (Phase B2/B3) reads these env vars to find
|
|
6042
|
+
// the host's REST endpoint.
|
|
6043
|
+
...memorySecret ? {
|
|
6044
|
+
AGENTMEMORY_URL: AGENTMEMORY_HOST_INTERNAL_URL,
|
|
6045
|
+
AGENTMEMORY_SECRET: memorySecret
|
|
6046
|
+
} : {},
|
|
5937
6047
|
...env
|
|
5938
6048
|
};
|
|
5939
6049
|
const envList = Object.entries(worldEnv).map(([k, v]) => `${k}=${v}`);
|
|
@@ -6050,7 +6160,7 @@ var demuxStream, execInContainer;
|
|
|
6050
6160
|
var init_exec = __esm({
|
|
6051
6161
|
"../adapters/dist/docker/exec.js"() {
|
|
6052
6162
|
"use strict";
|
|
6053
|
-
demuxStream = (stream) => new Promise((
|
|
6163
|
+
demuxStream = (stream) => new Promise((resolve14, reject) => {
|
|
6054
6164
|
const stdoutChunks = [];
|
|
6055
6165
|
const stderrChunks = [];
|
|
6056
6166
|
const stdout = new PassThrough();
|
|
@@ -6064,7 +6174,7 @@ var init_exec = __esm({
|
|
|
6064
6174
|
stream.pipe(stdout);
|
|
6065
6175
|
}
|
|
6066
6176
|
stream.on("end", () => {
|
|
6067
|
-
|
|
6177
|
+
resolve14({
|
|
6068
6178
|
stdout: Buffer.concat(stdoutChunks).toString("utf-8"),
|
|
6069
6179
|
stderr: Buffer.concat(stderrChunks).toString("utf-8")
|
|
6070
6180
|
});
|
|
@@ -6260,6 +6370,19 @@ var init_provider = __esm({
|
|
|
6260
6370
|
}
|
|
6261
6371
|
const mergedEnv = { ...serviceEnv, ...config.env };
|
|
6262
6372
|
const container = await createWorldContainer(this.docker, id, name, config.image, mergedEnv, config.resources, config.workspacePath, config.portOffset, config.appPorts, config.cacheArch);
|
|
6373
|
+
for (const networkName3 of config.extraNetworks ?? []) {
|
|
6374
|
+
try {
|
|
6375
|
+
const network = this.docker.getNetwork(networkName3);
|
|
6376
|
+
await network.connect({ Container: container.id });
|
|
6377
|
+
} catch (err) {
|
|
6378
|
+
const statusCode = err.statusCode;
|
|
6379
|
+
if (statusCode === 404) {
|
|
6380
|
+
throw new Error(`extra network "${networkName3}" not found \u2014 run \`olam bootstrap\` to recreate it`);
|
|
6381
|
+
}
|
|
6382
|
+
if (statusCode !== 403)
|
|
6383
|
+
throw err;
|
|
6384
|
+
}
|
|
6385
|
+
}
|
|
6263
6386
|
return new DockerWorld(id, name, container, "running");
|
|
6264
6387
|
}
|
|
6265
6388
|
// -----------------------------------------------------------------------
|
|
@@ -6496,7 +6619,7 @@ var init_connection = __esm({
|
|
|
6496
6619
|
// -----------------------------------------------------------------------
|
|
6497
6620
|
async exec(host, command) {
|
|
6498
6621
|
const client = await this.getConnection(host);
|
|
6499
|
-
return new Promise((
|
|
6622
|
+
return new Promise((resolve14, reject) => {
|
|
6500
6623
|
client.exec(command, (err, stream) => {
|
|
6501
6624
|
if (err) {
|
|
6502
6625
|
reject(new Error(`SSH exec failed on ${host}: ${err.message}`));
|
|
@@ -6511,7 +6634,7 @@ var init_connection = __esm({
|
|
|
6511
6634
|
stderr += data.toString();
|
|
6512
6635
|
});
|
|
6513
6636
|
stream.on("close", (code) => {
|
|
6514
|
-
|
|
6637
|
+
resolve14({
|
|
6515
6638
|
exitCode: code ?? 0,
|
|
6516
6639
|
stdout: stdout.trimEnd(),
|
|
6517
6640
|
stderr: stderr.trimEnd()
|
|
@@ -6542,10 +6665,10 @@ var init_connection = __esm({
|
|
|
6542
6665
|
throw new Error(`No SSH configuration found for host: ${host}`);
|
|
6543
6666
|
}
|
|
6544
6667
|
const client = new SSHClient();
|
|
6545
|
-
return new Promise((
|
|
6668
|
+
return new Promise((resolve14, reject) => {
|
|
6546
6669
|
client.on("ready", () => {
|
|
6547
6670
|
this.connections.set(host, client);
|
|
6548
|
-
|
|
6671
|
+
resolve14(client);
|
|
6549
6672
|
}).on("error", (err) => {
|
|
6550
6673
|
this.connections.delete(host);
|
|
6551
6674
|
reject(new Error(`SSH connection to ${host} failed: ${err.message}`));
|
|
@@ -7270,6 +7393,7 @@ function rowToMetadata(row) {
|
|
|
7270
7393
|
let readinessChain;
|
|
7271
7394
|
let expectedServices;
|
|
7272
7395
|
let appPortUrls;
|
|
7396
|
+
let worldDbNames;
|
|
7273
7397
|
try {
|
|
7274
7398
|
if (row.readiness_chain)
|
|
7275
7399
|
readinessChain = JSON.parse(row.readiness_chain);
|
|
@@ -7285,6 +7409,11 @@ function rowToMetadata(row) {
|
|
|
7285
7409
|
appPortUrls = JSON.parse(row.app_port_urls);
|
|
7286
7410
|
} catch {
|
|
7287
7411
|
}
|
|
7412
|
+
try {
|
|
7413
|
+
if (row.world_db_names)
|
|
7414
|
+
worldDbNames = JSON.parse(row.world_db_names);
|
|
7415
|
+
} catch {
|
|
7416
|
+
}
|
|
7288
7417
|
return {
|
|
7289
7418
|
id: row.id,
|
|
7290
7419
|
name: row.name,
|
|
@@ -7307,7 +7436,9 @@ function rowToMetadata(row) {
|
|
|
7307
7436
|
autoDestroyOnMerge: (row.auto_destroy_on_merge ?? 1) !== 0,
|
|
7308
7437
|
...readinessChain !== void 0 ? { readinessChain } : {},
|
|
7309
7438
|
...expectedServices !== void 0 ? { expectedServices } : {},
|
|
7310
|
-
...appPortUrls !== void 0 ? { appPortUrls } : {}
|
|
7439
|
+
...appPortUrls !== void 0 ? { appPortUrls } : {},
|
|
7440
|
+
...worldDbNames !== void 0 ? { worldDbNames } : {},
|
|
7441
|
+
...row.world_role_name ? { worldRoleName: row.world_role_name } : {}
|
|
7311
7442
|
};
|
|
7312
7443
|
}
|
|
7313
7444
|
var _require, _Database, SCHEMA_VERSION, CREATE_TABLE, CREATE_META, WorldRegistry;
|
|
@@ -7359,7 +7490,17 @@ CREATE TABLE IF NOT EXISTS meta (
|
|
|
7359
7490
|
this.db.pragma("foreign_keys = ON");
|
|
7360
7491
|
this.db.exec(CREATE_TABLE);
|
|
7361
7492
|
this.db.exec(CREATE_META);
|
|
7362
|
-
for (const col of [
|
|
7493
|
+
for (const col of [
|
|
7494
|
+
"readiness_chain TEXT",
|
|
7495
|
+
"expected_services TEXT",
|
|
7496
|
+
"app_port_urls TEXT",
|
|
7497
|
+
"tailscale_paths TEXT",
|
|
7498
|
+
// olam-hybrid-shared-postgres Phase A task A7. JSON-serialised string for
|
|
7499
|
+
// world_db_names (SQLite has no native array; matches repos column shape).
|
|
7500
|
+
// TEXT[] would fail at DDL time (closes OQ13/OQ22 / FS-03).
|
|
7501
|
+
"world_db_names TEXT",
|
|
7502
|
+
"world_role_name TEXT"
|
|
7503
|
+
]) {
|
|
7363
7504
|
try {
|
|
7364
7505
|
this.db.exec(`ALTER TABLE worlds ADD COLUMN ${col}`);
|
|
7365
7506
|
} catch {
|
|
@@ -7385,8 +7526,9 @@ CREATE TABLE IF NOT EXISTS meta (
|
|
|
7385
7526
|
(id, name, status, repos, branch, port_offset, workspace_path,
|
|
7386
7527
|
compute_provider, total_cost_usd, thought_count, created_at, updated_at,
|
|
7387
7528
|
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
|
-
|
|
7529
|
+
readiness_chain, expected_services, app_port_urls,
|
|
7530
|
+
world_db_names, world_role_name)
|
|
7531
|
+
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
7532
|
}
|
|
7391
7533
|
update(worldId, updates) {
|
|
7392
7534
|
const setClauses = [];
|
|
@@ -7411,9 +7553,12 @@ CREATE TABLE IF NOT EXISTS meta (
|
|
|
7411
7553
|
autoDestroyOnMerge: "auto_destroy_on_merge",
|
|
7412
7554
|
readinessChain: "readiness_chain",
|
|
7413
7555
|
expectedServices: "expected_services",
|
|
7414
|
-
appPortUrls: "app_port_urls"
|
|
7556
|
+
appPortUrls: "app_port_urls",
|
|
7557
|
+
// olam-hybrid-shared-postgres Phase A task A7.
|
|
7558
|
+
worldDbNames: "world_db_names",
|
|
7559
|
+
worldRoleName: "world_role_name"
|
|
7415
7560
|
};
|
|
7416
|
-
const jsonColumns = /* @__PURE__ */ new Set(["repos", "readinessChain", "expectedServices", "appPortUrls"]);
|
|
7561
|
+
const jsonColumns = /* @__PURE__ */ new Set(["repos", "readinessChain", "expectedServices", "appPortUrls", "worldDbNames"]);
|
|
7417
7562
|
for (const [key, col] of Object.entries(columnMap)) {
|
|
7418
7563
|
const val = updates[key];
|
|
7419
7564
|
if (val !== void 0) {
|
|
@@ -8300,7 +8445,7 @@ var init_workspace_name = __esm({
|
|
|
8300
8445
|
|
|
8301
8446
|
// ../core/dist/kg/storage-paths.js
|
|
8302
8447
|
import { homedir as homedir9 } from "node:os";
|
|
8303
|
-
import { join as join16, resolve as
|
|
8448
|
+
import { join as join16, resolve as resolve5 } from "node:path";
|
|
8304
8449
|
function olamHome() {
|
|
8305
8450
|
return process.env.OLAM_HOME ?? join16(homedir9(), ".olam");
|
|
8306
8451
|
}
|
|
@@ -8318,7 +8463,7 @@ function assertWithinPrefix(path56, prefix, label) {
|
|
|
8318
8463
|
function kgPristinePath(workspace) {
|
|
8319
8464
|
validateWorkspaceName(workspace);
|
|
8320
8465
|
const root = kgRoot();
|
|
8321
|
-
const path56 =
|
|
8466
|
+
const path56 = resolve5(join16(root, workspace));
|
|
8322
8467
|
assertWithinPrefix(path56, root, "kgPristinePath");
|
|
8323
8468
|
return path56;
|
|
8324
8469
|
}
|
|
@@ -8471,8 +8616,8 @@ import { execFileSync as execFileSync5 } from "node:child_process";
|
|
|
8471
8616
|
import * as fs15 from "node:fs";
|
|
8472
8617
|
import * as os9 from "node:os";
|
|
8473
8618
|
import * as path16 from "node:path";
|
|
8474
|
-
function expandHome(p,
|
|
8475
|
-
return p.replace(/^~(?=$|\/|\\)/,
|
|
8619
|
+
function expandHome(p, homedir32) {
|
|
8620
|
+
return p.replace(/^~(?=$|\/|\\)/, homedir32());
|
|
8476
8621
|
}
|
|
8477
8622
|
function sanitizeRepoFilename(name) {
|
|
8478
8623
|
const sanitized = name.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
@@ -8495,7 +8640,7 @@ ${stderr}`;
|
|
|
8495
8640
|
}
|
|
8496
8641
|
function snapshotBaselineDiff(repos, workspacePath, deps = {}) {
|
|
8497
8642
|
const exec = deps.exec ?? ((cmd, args, opts) => execFileSync5(cmd, args, opts));
|
|
8498
|
-
const
|
|
8643
|
+
const homedir32 = deps.homedir ?? (() => os9.homedir());
|
|
8499
8644
|
const baselineDir = path16.join(workspacePath, ".olam", "baseline");
|
|
8500
8645
|
try {
|
|
8501
8646
|
fs15.mkdirSync(baselineDir, { recursive: true });
|
|
@@ -8511,7 +8656,7 @@ function snapshotBaselineDiff(repos, workspacePath, deps = {}) {
|
|
|
8511
8656
|
continue;
|
|
8512
8657
|
const filename = `${sanitizeRepoFilename(repo.name)}.diff`;
|
|
8513
8658
|
const outPath = path16.join(baselineDir, filename);
|
|
8514
|
-
const repoPath = expandHome(repo.path,
|
|
8659
|
+
const repoPath = expandHome(repo.path, homedir32);
|
|
8515
8660
|
if (!fs15.existsSync(repoPath)) {
|
|
8516
8661
|
writeBaselineFile(outPath, `# repo: ${repo.name}
|
|
8517
8662
|
# (skipped: path ${repoPath} does not exist)
|
|
@@ -9411,8 +9556,8 @@ function computeGemsFingerprint(repoDir, imageDigest) {
|
|
|
9411
9556
|
return hashBuffers(entries);
|
|
9412
9557
|
}
|
|
9413
9558
|
function computeNodeFingerprint(repoDir, imageDigest) {
|
|
9414
|
-
const
|
|
9415
|
-
for (const name of
|
|
9559
|
+
const candidates2 = ["yarn.lock", "pnpm-lock.yaml", "package-lock.json"];
|
|
9560
|
+
for (const name of candidates2) {
|
|
9416
9561
|
const lockfile = path18.join(repoDir, name);
|
|
9417
9562
|
if (fs17.existsSync(lockfile)) {
|
|
9418
9563
|
const entries = [
|
|
@@ -10566,7 +10711,7 @@ async function startSupervisedApps(containerName, worldId, repos, exec, options
|
|
|
10566
10711
|
const probeTimeoutMs = options.probeTimeoutMs ?? 3e4;
|
|
10567
10712
|
const probeIntervalMs = options.probeIntervalMs ?? 1e3;
|
|
10568
10713
|
const clock = options.clock ?? Date.now;
|
|
10569
|
-
const
|
|
10714
|
+
const sleep5 = options.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
10570
10715
|
for (const repo of repos) {
|
|
10571
10716
|
if (isPortBound(exec, containerName, repo.hostPort)) {
|
|
10572
10717
|
throw new PortInUseError(repo.hostPort, repo.name, repo.manifestPath);
|
|
@@ -10591,7 +10736,7 @@ async function startSupervisedApps(containerName, worldId, repos, exec, options
|
|
|
10591
10736
|
probeTimeoutMs,
|
|
10592
10737
|
probeIntervalMs,
|
|
10593
10738
|
clock,
|
|
10594
|
-
sleep:
|
|
10739
|
+
sleep: sleep5
|
|
10595
10740
|
})));
|
|
10596
10741
|
return {
|
|
10597
10742
|
sessionName,
|
|
@@ -10658,16 +10803,20 @@ __export(manager_exports, {
|
|
|
10658
10803
|
WorkspaceNotFoundError: () => WorkspaceNotFoundError,
|
|
10659
10804
|
WorldManager: () => WorldManager,
|
|
10660
10805
|
applyPostgresNetworkOverrides: () => applyPostgresNetworkOverrides,
|
|
10806
|
+
applyPostgresTemplateClone: () => applyPostgresTemplateClone,
|
|
10807
|
+
applyPostgresWorldRole: () => applyPostgresWorldRole,
|
|
10661
10808
|
buildManifestRuntimeForTest: () => buildManifestRuntime,
|
|
10662
10809
|
cleanupWorldTailscale: () => cleanupWorldTailscale,
|
|
10663
10810
|
deriveReadinessChain: () => deriveReadinessChain,
|
|
10811
|
+
deriveWorldRoleName: () => deriveWorldRoleName,
|
|
10664
10812
|
exposeWorldOverTailscale: () => exposeWorldOverTailscale,
|
|
10665
10813
|
getTokenScopes: () => getTokenScopes,
|
|
10666
10814
|
planManifestPipeline: () => planManifestPipeline,
|
|
10815
|
+
redactCreateRolePassword: () => redactCreateRolePassword,
|
|
10667
10816
|
runManifestRuntime: () => runManifestRuntime
|
|
10668
10817
|
});
|
|
10669
10818
|
import * as crypto5 from "node:crypto";
|
|
10670
|
-
import { execSync as execSync5 } from "node:child_process";
|
|
10819
|
+
import { execSync as execSync5, spawnSync as spawnSync4 } from "node:child_process";
|
|
10671
10820
|
import * as fs23 from "node:fs";
|
|
10672
10821
|
import * as os13 from "node:os";
|
|
10673
10822
|
import * as path24 from "node:path";
|
|
@@ -11059,13 +11208,13 @@ function cleanupWorldTailscale(worldId, registry, _exec = execSync5) {
|
|
|
11059
11208
|
}
|
|
11060
11209
|
}
|
|
11061
11210
|
function resolveTailscaleBin(_exec = execSync5) {
|
|
11062
|
-
const
|
|
11211
|
+
const candidates2 = [
|
|
11063
11212
|
process.env["TAILSCALE_BIN"],
|
|
11064
11213
|
"/Applications/Tailscale.app/Contents/MacOS/Tailscale",
|
|
11065
11214
|
"/usr/local/bin/tailscale",
|
|
11066
11215
|
"/opt/homebrew/bin/tailscale"
|
|
11067
11216
|
];
|
|
11068
|
-
for (const c of
|
|
11217
|
+
for (const c of candidates2) {
|
|
11069
11218
|
if (!c)
|
|
11070
11219
|
continue;
|
|
11071
11220
|
try {
|
|
@@ -11076,16 +11225,268 @@ function resolveTailscaleBin(_exec = execSync5) {
|
|
|
11076
11225
|
}
|
|
11077
11226
|
return void 0;
|
|
11078
11227
|
}
|
|
11079
|
-
function applyPostgresNetworkOverrides(worldEnv, enrichedRepos) {
|
|
11228
|
+
function applyPostgresNetworkOverrides(worldEnv, enrichedRepos, worldId) {
|
|
11080
11229
|
const hasPostgres = enrichedRepos.some((r) => r.manifest?.services?.["postgres"] !== void 0);
|
|
11081
11230
|
if (!hasPostgres)
|
|
11082
11231
|
return;
|
|
11083
|
-
|
|
11232
|
+
const hasSeedTemplate = enrichedRepos.some((r) => {
|
|
11233
|
+
const pg = r.manifest?.services?.["postgres"];
|
|
11234
|
+
return pg?.seed_template !== void 0;
|
|
11235
|
+
});
|
|
11236
|
+
const host = hasSeedTemplate ? "olam-postgres" : "postgres";
|
|
11237
|
+
worldEnv["POSTGRESQL_HOST"] = host;
|
|
11084
11238
|
worldEnv["POSTGRESQL_PORT"] = "5432";
|
|
11085
|
-
if (
|
|
11086
|
-
worldEnv["POSTGRESQL_COMMON_HOST"] =
|
|
11087
|
-
if ("POSTGRESQL_COMMON_PORT" in worldEnv)
|
|
11239
|
+
if (hasSeedTemplate) {
|
|
11240
|
+
worldEnv["POSTGRESQL_COMMON_HOST"] = host;
|
|
11088
11241
|
worldEnv["POSTGRESQL_COMMON_PORT"] = "5432";
|
|
11242
|
+
if (worldId !== void 0) {
|
|
11243
|
+
assertSafeWorldId(worldId);
|
|
11244
|
+
const seedTemplates = /* @__PURE__ */ new Set();
|
|
11245
|
+
for (const repo of enrichedRepos) {
|
|
11246
|
+
const pg = repo.manifest?.services?.["postgres"];
|
|
11247
|
+
const raw = pg?.seed_template;
|
|
11248
|
+
if (typeof raw === "string")
|
|
11249
|
+
seedTemplates.add(raw);
|
|
11250
|
+
else if (Array.isArray(raw)) {
|
|
11251
|
+
for (const s of raw)
|
|
11252
|
+
if (typeof s === "string")
|
|
11253
|
+
seedTemplates.add(s);
|
|
11254
|
+
}
|
|
11255
|
+
}
|
|
11256
|
+
for (const seed of seedTemplates) {
|
|
11257
|
+
const match2 = seed.match(/^atlas_([a-z][a-z0-9_]*?)_seed$/i);
|
|
11258
|
+
if (match2 && match2[1] !== void 0) {
|
|
11259
|
+
const purpose = match2[1].toUpperCase();
|
|
11260
|
+
const envKey = `POSTGRESQL_${purpose}_DATABASE`;
|
|
11261
|
+
worldEnv[envKey] = deriveWorldDbName(seed, worldId);
|
|
11262
|
+
}
|
|
11263
|
+
}
|
|
11264
|
+
}
|
|
11265
|
+
} else {
|
|
11266
|
+
if ("POSTGRESQL_COMMON_HOST" in worldEnv)
|
|
11267
|
+
worldEnv["POSTGRESQL_COMMON_HOST"] = "postgres";
|
|
11268
|
+
if ("POSTGRESQL_COMMON_PORT" in worldEnv)
|
|
11269
|
+
worldEnv["POSTGRESQL_COMMON_PORT"] = "5432";
|
|
11270
|
+
}
|
|
11271
|
+
}
|
|
11272
|
+
function applyPostgresTemplateClone(worldId, enrichedRepos, options = {}) {
|
|
11273
|
+
assertSafeWorldId(worldId);
|
|
11274
|
+
const container = options.singletonContainer ?? "olam-postgres";
|
|
11275
|
+
const user = options.postgresUser ?? "development";
|
|
11276
|
+
const seedTemplates = [];
|
|
11277
|
+
const postCloneSqlBySeed = /* @__PURE__ */ new Map();
|
|
11278
|
+
for (const repo of enrichedRepos) {
|
|
11279
|
+
const pg = repo.manifest?.services?.["postgres"];
|
|
11280
|
+
if (!pg?.seed_template)
|
|
11281
|
+
continue;
|
|
11282
|
+
const list = Array.isArray(pg.seed_template) ? pg.seed_template : [pg.seed_template];
|
|
11283
|
+
for (const t of list) {
|
|
11284
|
+
if (typeof t === "string" && t.length > 0 && !seedTemplates.includes(t)) {
|
|
11285
|
+
seedTemplates.push(t);
|
|
11286
|
+
}
|
|
11287
|
+
}
|
|
11288
|
+
if (pg.post_clone_sql && typeof pg.post_clone_sql === "object") {
|
|
11289
|
+
for (const [seedKey, sqlList] of Object.entries(pg.post_clone_sql)) {
|
|
11290
|
+
if (!Array.isArray(sqlList))
|
|
11291
|
+
continue;
|
|
11292
|
+
const existing = postCloneSqlBySeed.get(seedKey) ?? [];
|
|
11293
|
+
for (const stmt of sqlList) {
|
|
11294
|
+
if (typeof stmt === "string" && stmt.length > 0)
|
|
11295
|
+
existing.push(stmt);
|
|
11296
|
+
}
|
|
11297
|
+
postCloneSqlBySeed.set(seedKey, existing);
|
|
11298
|
+
}
|
|
11299
|
+
}
|
|
11300
|
+
}
|
|
11301
|
+
if (seedTemplates.length === 0) {
|
|
11302
|
+
return { worldDbNames: [] };
|
|
11303
|
+
}
|
|
11304
|
+
const spawn11 = options.spawn ?? spawnSync4;
|
|
11305
|
+
const seedToWorldDb = /* @__PURE__ */ new Map();
|
|
11306
|
+
for (const seed of seedTemplates) {
|
|
11307
|
+
seedToWorldDb.set(seed, deriveWorldDbName(seed, worldId));
|
|
11308
|
+
}
|
|
11309
|
+
const created = [];
|
|
11310
|
+
for (const seed of seedTemplates) {
|
|
11311
|
+
const worldDb = seedToWorldDb.get(seed);
|
|
11312
|
+
const exists = spawn11("docker", [
|
|
11313
|
+
"exec",
|
|
11314
|
+
container,
|
|
11315
|
+
"psql",
|
|
11316
|
+
"-U",
|
|
11317
|
+
user,
|
|
11318
|
+
"-tAc",
|
|
11319
|
+
`SELECT 1 FROM pg_database WHERE datname='${worldDb}'`
|
|
11320
|
+
], { encoding: "utf-8" });
|
|
11321
|
+
const dbAlreadyExists = exists.status === 0 && (exists.stdout || "").trim() === "1";
|
|
11322
|
+
if (!dbAlreadyExists) {
|
|
11323
|
+
const sql = `CREATE DATABASE "${worldDb}" TEMPLATE "${seed}"`;
|
|
11324
|
+
const create = spawn11("docker", ["exec", container, "psql", "-U", user, "-d", "postgres", "-v", "ON_ERROR_STOP=1", "-c", sql], { encoding: "utf-8" });
|
|
11325
|
+
if (create.status !== 0) {
|
|
11326
|
+
throw new Error(`failed to CREATE DATABASE "${worldDb}" TEMPLATE "${seed}": ${(create.stderr || "").trim()}`);
|
|
11327
|
+
}
|
|
11328
|
+
}
|
|
11329
|
+
created.push(worldDb);
|
|
11330
|
+
const stmts = postCloneSqlBySeed.get(seed);
|
|
11331
|
+
if (stmts !== void 0 && stmts.length > 0) {
|
|
11332
|
+
for (const rawStmt of stmts) {
|
|
11333
|
+
const stmt = interpolatePostCloneSql(rawStmt, worldId, seedToWorldDb);
|
|
11334
|
+
const fixup = spawn11("docker", ["exec", container, "psql", "-U", user, "-d", worldDb, "-v", "ON_ERROR_STOP=1", "-c", stmt], { encoding: "utf-8" });
|
|
11335
|
+
if (fixup.status !== 0) {
|
|
11336
|
+
throw new Error(`post_clone_sql against "${worldDb}" failed (statement: ${truncate(stmt, 120)}): ${(fixup.stderr || "").trim()}`);
|
|
11337
|
+
}
|
|
11338
|
+
}
|
|
11339
|
+
}
|
|
11340
|
+
}
|
|
11341
|
+
return { worldDbNames: created };
|
|
11342
|
+
}
|
|
11343
|
+
function interpolatePostCloneSql(stmt, worldId, seedToWorldDb) {
|
|
11344
|
+
return stmt.replace(/\$\{([^}]+)\}/g, (_, expr) => {
|
|
11345
|
+
if (expr === "WORLD_ID")
|
|
11346
|
+
return worldId;
|
|
11347
|
+
const dbMatch = expr.match(/^WORLD_DB:(.+)$/);
|
|
11348
|
+
if (dbMatch && dbMatch[1] !== void 0) {
|
|
11349
|
+
const target = seedToWorldDb.get(dbMatch[1]);
|
|
11350
|
+
if (target === void 0) {
|
|
11351
|
+
throw new Error(`post_clone_sql references seed "${dbMatch[1]}" not declared in seed_template`);
|
|
11352
|
+
}
|
|
11353
|
+
return target;
|
|
11354
|
+
}
|
|
11355
|
+
throw new Error(`post_clone_sql contains unknown placeholder "\${${expr}}" \u2014 only \${WORLD_ID} and \${WORLD_DB:<seed>} are supported`);
|
|
11356
|
+
});
|
|
11357
|
+
}
|
|
11358
|
+
function truncate(s, max) {
|
|
11359
|
+
return s.length <= max ? s : s.slice(0, max - 1) + "\u2026";
|
|
11360
|
+
}
|
|
11361
|
+
function deriveWorldRoleName(worldId) {
|
|
11362
|
+
return `olam_world_${worldId}`;
|
|
11363
|
+
}
|
|
11364
|
+
function applyPostgresWorldRole(worldId, worldDbNames, options = {}) {
|
|
11365
|
+
assertSafeWorldId(worldId);
|
|
11366
|
+
if (worldDbNames.length === 0) {
|
|
11367
|
+
throw new Error(`applyPostgresWorldRole called with empty worldDbNames for "${worldId}" \u2014 should only run when applyPostgresTemplateClone produced clones`);
|
|
11368
|
+
}
|
|
11369
|
+
const spawn11 = options.spawn ?? spawnSync4;
|
|
11370
|
+
const container = options.singletonContainer ?? "olam-postgres";
|
|
11371
|
+
const user = options.postgresUser ?? "development";
|
|
11372
|
+
const genPassword = options.generatePassword ?? defaultPasswordGenerator;
|
|
11373
|
+
const worldRoleName = deriveWorldRoleName(worldId);
|
|
11374
|
+
const password = genPassword();
|
|
11375
|
+
const exists = spawn11("docker", [
|
|
11376
|
+
"exec",
|
|
11377
|
+
container,
|
|
11378
|
+
"psql",
|
|
11379
|
+
"-U",
|
|
11380
|
+
user,
|
|
11381
|
+
"-tAc",
|
|
11382
|
+
`SELECT 1 FROM pg_roles WHERE rolname='${escapeSqlLiteral(worldRoleName)}'`
|
|
11383
|
+
], { encoding: "utf-8" });
|
|
11384
|
+
const roleExists = exists.status === 0 && (exists.stdout || "").trim() === "1";
|
|
11385
|
+
const escapedPassword = escapeSqlLiteral(password);
|
|
11386
|
+
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`;
|
|
11387
|
+
const dml = spawn11("docker", ["exec", container, "psql", "-U", user, "-d", "postgres", "-v", "ON_ERROR_STOP=1", "-c", roleDdl], { encoding: "utf-8" });
|
|
11388
|
+
if (dml.status !== 0) {
|
|
11389
|
+
throw new Error(`failed to ${roleExists ? "ALTER" : "CREATE"} ROLE "${worldRoleName}": ` + redactCreateRolePassword((dml.stderr || "").trim()));
|
|
11390
|
+
}
|
|
11391
|
+
for (const worldDb of worldDbNames) {
|
|
11392
|
+
const grantConnect = spawn11("docker", [
|
|
11393
|
+
"exec",
|
|
11394
|
+
container,
|
|
11395
|
+
"psql",
|
|
11396
|
+
"-U",
|
|
11397
|
+
user,
|
|
11398
|
+
"-d",
|
|
11399
|
+
"postgres",
|
|
11400
|
+
"-v",
|
|
11401
|
+
"ON_ERROR_STOP=1",
|
|
11402
|
+
"-c",
|
|
11403
|
+
`GRANT CONNECT ON DATABASE "${worldDb}" TO "${worldRoleName}"`
|
|
11404
|
+
], { encoding: "utf-8" });
|
|
11405
|
+
if (grantConnect.status !== 0) {
|
|
11406
|
+
throw new Error(`failed to GRANT CONNECT on "${worldDb}" to "${worldRoleName}": ${(grantConnect.stderr || "").trim()}`);
|
|
11407
|
+
}
|
|
11408
|
+
const perDbGrants = [
|
|
11409
|
+
`GRANT USAGE ON SCHEMA public TO "${worldRoleName}"`,
|
|
11410
|
+
`GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO "${worldRoleName}"`,
|
|
11411
|
+
`GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO "${worldRoleName}"`,
|
|
11412
|
+
// DEFAULT PRIVILEGES for future objects — Rails migrations create new
|
|
11413
|
+
// tables/sequences post-spawn and they need to be grantable.
|
|
11414
|
+
`ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO "${worldRoleName}"`,
|
|
11415
|
+
`ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO "${worldRoleName}"`
|
|
11416
|
+
].join("; ");
|
|
11417
|
+
const grantResult = spawn11("docker", ["exec", container, "psql", "-U", user, "-d", worldDb, "-v", "ON_ERROR_STOP=1", "-c", perDbGrants], { encoding: "utf-8" });
|
|
11418
|
+
if (grantResult.status !== 0) {
|
|
11419
|
+
throw new Error(`failed to GRANT privileges on "${worldDb}" to "${worldRoleName}": ${(grantResult.stderr || "").trim()}`);
|
|
11420
|
+
}
|
|
11421
|
+
}
|
|
11422
|
+
return { worldRoleName, password };
|
|
11423
|
+
}
|
|
11424
|
+
function defaultPasswordGenerator() {
|
|
11425
|
+
return crypto5.randomBytes(24).toString("base64url");
|
|
11426
|
+
}
|
|
11427
|
+
function escapeSqlLiteral(s) {
|
|
11428
|
+
return s.replace(/'/g, "''");
|
|
11429
|
+
}
|
|
11430
|
+
function redactCreateRolePassword(s) {
|
|
11431
|
+
return s.replace(/PASSWORD\s+'[^']*'/g, "PASSWORD '<scrubbed>'");
|
|
11432
|
+
}
|
|
11433
|
+
function dropPostgresWorldDbs(worldId, worldDbNames, options = {}) {
|
|
11434
|
+
if (worldDbNames.length === 0)
|
|
11435
|
+
return;
|
|
11436
|
+
assertSafeWorldId(worldId);
|
|
11437
|
+
const spawn11 = options.spawn ?? spawnSync4;
|
|
11438
|
+
const container = options.container ?? "olam-postgres";
|
|
11439
|
+
const user = options.user ?? "development";
|
|
11440
|
+
for (const db of worldDbNames) {
|
|
11441
|
+
const drop = spawn11("docker", [
|
|
11442
|
+
"exec",
|
|
11443
|
+
container,
|
|
11444
|
+
"psql",
|
|
11445
|
+
"-U",
|
|
11446
|
+
user,
|
|
11447
|
+
"-d",
|
|
11448
|
+
"postgres",
|
|
11449
|
+
"-c",
|
|
11450
|
+
`DROP DATABASE IF EXISTS "${db}" WITH (FORCE)`
|
|
11451
|
+
], { encoding: "utf-8" });
|
|
11452
|
+
if (drop.status !== 0) {
|
|
11453
|
+
console.warn(`[manager] destroyWorld(${worldId}): failed to DROP "${db}" from singleton: ${(drop.stderr || "").trim()}`);
|
|
11454
|
+
}
|
|
11455
|
+
}
|
|
11456
|
+
}
|
|
11457
|
+
function dropPostgresWorldRole(worldId, worldRoleName, options = {}) {
|
|
11458
|
+
if (!worldRoleName)
|
|
11459
|
+
return;
|
|
11460
|
+
assertSafeWorldId(worldId);
|
|
11461
|
+
const spawn11 = options.spawn ?? spawnSync4;
|
|
11462
|
+
const container = options.container ?? "olam-postgres";
|
|
11463
|
+
const user = options.user ?? "development";
|
|
11464
|
+
const cleanup = spawn11("docker", [
|
|
11465
|
+
"exec",
|
|
11466
|
+
container,
|
|
11467
|
+
"psql",
|
|
11468
|
+
"-U",
|
|
11469
|
+
user,
|
|
11470
|
+
"-d",
|
|
11471
|
+
"postgres",
|
|
11472
|
+
"-c",
|
|
11473
|
+
`DROP OWNED BY "${worldRoleName}" CASCADE; DROP ROLE IF EXISTS "${worldRoleName}"`
|
|
11474
|
+
], { encoding: "utf-8" });
|
|
11475
|
+
if (cleanup.status !== 0) {
|
|
11476
|
+
console.warn(`[manager] destroyWorld(${worldId}): failed to DROP role "${worldRoleName}" from singleton: ${(cleanup.stderr || "").trim()}`);
|
|
11477
|
+
}
|
|
11478
|
+
}
|
|
11479
|
+
function assertSafeWorldId(worldId) {
|
|
11480
|
+
if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(worldId)) {
|
|
11481
|
+
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`);
|
|
11482
|
+
}
|
|
11483
|
+
}
|
|
11484
|
+
function deriveWorldDbName(seed, worldId) {
|
|
11485
|
+
if (seed.endsWith("_seed")) {
|
|
11486
|
+
const prefix = seed.slice(0, -"_seed".length);
|
|
11487
|
+
return `${prefix}_world_${worldId}`;
|
|
11488
|
+
}
|
|
11489
|
+
return `${seed}_world_${worldId}`;
|
|
11089
11490
|
}
|
|
11090
11491
|
var BotIdentityError, ADJECTIVES, NOUNS, AuthPreflightError, WorkspaceNotFoundError, WorldManager;
|
|
11091
11492
|
var init_manager = __esm({
|
|
@@ -11472,6 +11873,8 @@ ${detail}`);
|
|
|
11472
11873
|
const hostPort = override !== void 0 ? override : ap.port + 1e4 + portOffset;
|
|
11473
11874
|
return { repoName: ap.name, internalPort: ap.port, hostPort, url: `http://localhost:${hostPort}` };
|
|
11474
11875
|
});
|
|
11876
|
+
let worldDbNames = void 0;
|
|
11877
|
+
let worldRoleName = void 0;
|
|
11475
11878
|
try {
|
|
11476
11879
|
const worldEnv = {};
|
|
11477
11880
|
if (opts.task)
|
|
@@ -11572,7 +11975,27 @@ ${detail}`);
|
|
|
11572
11975
|
}
|
|
11573
11976
|
}
|
|
11574
11977
|
}
|
|
11575
|
-
applyPostgresNetworkOverrides(worldEnv, enrichedRepos);
|
|
11978
|
+
applyPostgresNetworkOverrides(worldEnv, enrichedRepos, worldId);
|
|
11979
|
+
const cloneResult = applyPostgresTemplateClone(worldId, enrichedRepos);
|
|
11980
|
+
worldDbNames = cloneResult.worldDbNames.length > 0 ? cloneResult.worldDbNames : void 0;
|
|
11981
|
+
if (worldDbNames !== void 0) {
|
|
11982
|
+
const roleResult = applyPostgresWorldRole(worldId, worldDbNames);
|
|
11983
|
+
worldRoleName = roleResult.worldRoleName;
|
|
11984
|
+
worldEnv["POSTGRESQL_USERNAME"] = roleResult.worldRoleName;
|
|
11985
|
+
worldEnv["POSTGRESQL_PASSWORD"] = roleResult.password;
|
|
11986
|
+
worldEnv["POSTGRESQL_COMMON_USERNAME"] = roleResult.worldRoleName;
|
|
11987
|
+
worldEnv["POSTGRESQL_COMMON_PASSWORD"] = roleResult.password;
|
|
11988
|
+
worldEnv["POSTGRESQL_MERCHANT_USERNAME"] = roleResult.worldRoleName;
|
|
11989
|
+
worldEnv["POSTGRESQL_MERCHANT_PASSWORD"] = roleResult.password;
|
|
11990
|
+
}
|
|
11991
|
+
if (worldDbNames !== void 0 || worldRoleName !== void 0) {
|
|
11992
|
+
this.registry.update(worldId, {
|
|
11993
|
+
...worldDbNames !== void 0 ? { worldDbNames: [...worldDbNames] } : {},
|
|
11994
|
+
...worldRoleName !== void 0 ? { worldRoleName } : {}
|
|
11995
|
+
});
|
|
11996
|
+
}
|
|
11997
|
+
const hybridActive = worldDbNames !== void 0;
|
|
11998
|
+
const extraNetworks = hybridActive ? ["olam-shared"] : void 0;
|
|
11576
11999
|
await this.provider.createWorld({
|
|
11577
12000
|
id: worldId,
|
|
11578
12001
|
name: opts.name,
|
|
@@ -11582,6 +12005,7 @@ ${detail}`);
|
|
|
11582
12005
|
portOffset,
|
|
11583
12006
|
workspacePath,
|
|
11584
12007
|
appPorts: appPorts.length > 0 ? appPorts : void 0,
|
|
12008
|
+
...extraNetworks ? { extraNetworks } : {},
|
|
11585
12009
|
// Phase 7 A1: per-world cost ceiling sourced from config.cost.
|
|
11586
12010
|
// Cloudflare provider forwards this to /session/start so the DO
|
|
11587
12011
|
// can enforce kill-switch on `cost_update` events. Other providers
|
|
@@ -11605,7 +12029,16 @@ ${detail}`);
|
|
|
11605
12029
|
sm.transition("running");
|
|
11606
12030
|
this.registry.update(worldId, {
|
|
11607
12031
|
status: "running",
|
|
11608
|
-
...appPortUrls.length > 0 ? { appPortUrls } : {}
|
|
12032
|
+
...appPortUrls.length > 0 ? { appPortUrls } : {},
|
|
12033
|
+
// olam-hybrid-shared-postgres A3: persist the cloned per-world DB
|
|
12034
|
+
// names so olam destroy + olam gc can find them later (A7's
|
|
12035
|
+
// world_db_names TEXT JSON column).
|
|
12036
|
+
...worldDbNames !== void 0 ? { worldDbNames: [...worldDbNames] } : {},
|
|
12037
|
+
// olam-hybrid-shared-postgres A4: persist the role name so olam destroy
|
|
12038
|
+
// can DROP it after the world's DBs are gone (A7's world_role_name
|
|
12039
|
+
// column). Password is NOT persisted — it lives only in the world
|
|
12040
|
+
// container's env and the in-memory return value.
|
|
12041
|
+
...worldRoleName !== void 0 ? { worldRoleName } : {}
|
|
11609
12042
|
});
|
|
11610
12043
|
const containerName = `olam-${worldId}-devbox`;
|
|
11611
12044
|
try {
|
|
@@ -11905,6 +12338,14 @@ ${opts.task}`;
|
|
|
11905
12338
|
cleanupWorldTailscale(worldId, this.registry);
|
|
11906
12339
|
} catch {
|
|
11907
12340
|
}
|
|
12341
|
+
const worldDbNames = world.worldDbNames;
|
|
12342
|
+
if (worldDbNames !== void 0 && worldDbNames.length > 0) {
|
|
12343
|
+
dropPostgresWorldDbs(worldId, worldDbNames);
|
|
12344
|
+
}
|
|
12345
|
+
const worldRoleName = world.worldRoleName;
|
|
12346
|
+
if (typeof worldRoleName === "string" && worldRoleName.length > 0) {
|
|
12347
|
+
dropPostgresWorldRole(worldId, worldRoleName);
|
|
12348
|
+
}
|
|
11908
12349
|
try {
|
|
11909
12350
|
await this.provider.destroyWorld(worldId);
|
|
11910
12351
|
} catch {
|
|
@@ -13306,7 +13747,7 @@ function isCloudflaredAvailable() {
|
|
|
13306
13747
|
}
|
|
13307
13748
|
}
|
|
13308
13749
|
function startTunnel(port) {
|
|
13309
|
-
return new Promise((
|
|
13750
|
+
return new Promise((resolve14, reject) => {
|
|
13310
13751
|
const child = spawn3("cloudflared", ["tunnel", "--url", `http://localhost:${port}`], {
|
|
13311
13752
|
stdio: ["ignore", "pipe", "pipe"],
|
|
13312
13753
|
detached: false
|
|
@@ -13328,7 +13769,7 @@ function startTunnel(port) {
|
|
|
13328
13769
|
if (match2) {
|
|
13329
13770
|
resolved = true;
|
|
13330
13771
|
clearTimeout(timeout);
|
|
13331
|
-
|
|
13772
|
+
resolve14(match2[0]);
|
|
13332
13773
|
}
|
|
13333
13774
|
}
|
|
13334
13775
|
child.stdout?.on("data", scan);
|
|
@@ -13415,8 +13856,8 @@ var init_dashboard = __esm({
|
|
|
13415
13856
|
}
|
|
13416
13857
|
throw err;
|
|
13417
13858
|
}
|
|
13418
|
-
await new Promise((
|
|
13419
|
-
this.server.on("listening",
|
|
13859
|
+
await new Promise((resolve14, reject) => {
|
|
13860
|
+
this.server.on("listening", resolve14);
|
|
13420
13861
|
this.server.on("error", reject);
|
|
13421
13862
|
});
|
|
13422
13863
|
this.info = { localUrl: `http://localhost:${port}` };
|
|
@@ -13462,8 +13903,8 @@ var init_dashboard = __esm({
|
|
|
13462
13903
|
async stop() {
|
|
13463
13904
|
stopTunnel();
|
|
13464
13905
|
if (this.server) {
|
|
13465
|
-
await new Promise((
|
|
13466
|
-
this.server.close(() =>
|
|
13906
|
+
await new Promise((resolve14) => {
|
|
13907
|
+
this.server.close(() => resolve14());
|
|
13467
13908
|
});
|
|
13468
13909
|
this.server = null;
|
|
13469
13910
|
}
|
|
@@ -13689,10 +14130,10 @@ import * as crypto6 from "node:crypto";
|
|
|
13689
14130
|
import * as fs26 from "node:fs";
|
|
13690
14131
|
import * as os15 from "node:os";
|
|
13691
14132
|
import * as path28 from "node:path";
|
|
13692
|
-
import { spawnSync as
|
|
14133
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
13693
14134
|
import Dockerode2 from "dockerode";
|
|
13694
14135
|
function findComposeFile() {
|
|
13695
|
-
const
|
|
14136
|
+
const candidates2 = [
|
|
13696
14137
|
// Bundled path: dist/index.js lives at <pkg>/dist/; host-cp/ is a sibling of dist/
|
|
13697
14138
|
path28.resolve(path28.dirname(new URL(import.meta.url).pathname), "../host-cp/compose.yaml"),
|
|
13698
14139
|
// Source-mode: cwd is monorepo root
|
|
@@ -13700,7 +14141,7 @@ function findComposeFile() {
|
|
|
13700
14141
|
// Source-mode: cwd is one level inside the monorepo
|
|
13701
14142
|
path28.resolve(process.cwd(), "../packages/host-cp/compose.yaml")
|
|
13702
14143
|
];
|
|
13703
|
-
for (const c of
|
|
14144
|
+
for (const c of candidates2) {
|
|
13704
14145
|
if (fs26.existsSync(c)) return c;
|
|
13705
14146
|
}
|
|
13706
14147
|
return path28.resolve(process.cwd(), "packages/host-cp/compose.yaml");
|
|
@@ -13875,7 +14316,7 @@ async function probeHealth() {
|
|
|
13875
14316
|
}
|
|
13876
14317
|
}
|
|
13877
14318
|
function runCompose(args, composeFile, extraEnv = {}) {
|
|
13878
|
-
const result =
|
|
14319
|
+
const result = spawnSync5("docker", ["compose", "-f", composeFile, ...args], {
|
|
13879
14320
|
encoding: "utf-8",
|
|
13880
14321
|
stdio: ["ignore", "pipe", "pipe"],
|
|
13881
14322
|
env: { ...process.env, ...extraEnv }
|
|
@@ -13902,7 +14343,7 @@ function buildComposeEnv(authSecret, ghToken) {
|
|
|
13902
14343
|
}
|
|
13903
14344
|
function captureGhToken() {
|
|
13904
14345
|
try {
|
|
13905
|
-
const result =
|
|
14346
|
+
const result = spawnSync5("gh", ["auth", "token"], {
|
|
13906
14347
|
encoding: "utf-8",
|
|
13907
14348
|
stdio: ["ignore", "pipe", "pipe"]
|
|
13908
14349
|
});
|
|
@@ -14282,15 +14723,15 @@ __export(install_root_exports, {
|
|
|
14282
14723
|
resolveBuildScript: () => resolveBuildScript
|
|
14283
14724
|
});
|
|
14284
14725
|
import { existsSync as existsSync24 } from "node:fs";
|
|
14285
|
-
import { dirname as dirname17, join as join30, resolve as
|
|
14726
|
+
import { dirname as dirname17, join as join30, resolve as resolve10 } from "node:path";
|
|
14286
14727
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
14287
14728
|
function installRoot(metaUrl = import.meta.url) {
|
|
14288
|
-
const
|
|
14289
|
-
return
|
|
14729
|
+
const here3 = fileURLToPath5(metaUrl);
|
|
14730
|
+
return resolve10(dirname17(here3), "..");
|
|
14290
14731
|
}
|
|
14291
14732
|
function isDevMode(env = process.env, installRootDir = installRoot()) {
|
|
14292
14733
|
if (env.OLAM_DEV !== "1") return false;
|
|
14293
|
-
const repoRoot =
|
|
14734
|
+
const repoRoot = resolve10(installRootDir, "..", "..");
|
|
14294
14735
|
return existsSync24(join30(repoRoot, "packages")) && existsSync24(join30(repoRoot, "package.json"));
|
|
14295
14736
|
}
|
|
14296
14737
|
function resolveBuildScript(input) {
|
|
@@ -14298,7 +14739,7 @@ function resolveBuildScript(input) {
|
|
|
14298
14739
|
if (!isDevMode(env, installRootDir)) {
|
|
14299
14740
|
throw new MissingBuildScriptError(scriptRelPath);
|
|
14300
14741
|
}
|
|
14301
|
-
const repoRoot =
|
|
14742
|
+
const repoRoot = resolve10(installRootDir, "..", "..");
|
|
14302
14743
|
return join30(repoRoot, scriptRelPath);
|
|
14303
14744
|
}
|
|
14304
14745
|
var MissingBuildScriptError;
|
|
@@ -14329,7 +14770,7 @@ __export(protocol_version_exports, {
|
|
|
14329
14770
|
inspectImageProtocolVersions: () => inspectImageProtocolVersions,
|
|
14330
14771
|
parseProtocolVersionsLabel: () => parseProtocolVersionsLabel
|
|
14331
14772
|
});
|
|
14332
|
-
import { spawnSync as
|
|
14773
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
14333
14774
|
function parseProtocolVersionsLabel(labelValue) {
|
|
14334
14775
|
if (!labelValue) return [];
|
|
14335
14776
|
const parts = labelValue.split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -14400,7 +14841,7 @@ var init_protocol_version = __esm({
|
|
|
14400
14841
|
"use strict";
|
|
14401
14842
|
OLAM_PROTOCOL_VERSIONS_SUPPORTED = [1];
|
|
14402
14843
|
realDockerInspect = (imageRef) => {
|
|
14403
|
-
const result =
|
|
14844
|
+
const result = spawnSync6(
|
|
14404
14845
|
"docker",
|
|
14405
14846
|
["inspect", imageRef, "--format", '{{ index .Config.Labels "olam.protocol.versions" }}'],
|
|
14406
14847
|
{ encoding: "utf8", timeout: 1e4 }
|
|
@@ -14414,6 +14855,158 @@ var init_protocol_version = __esm({
|
|
|
14414
14855
|
}
|
|
14415
14856
|
});
|
|
14416
14857
|
|
|
14858
|
+
// ../core/dist/auth/postgres-init-helpers.js
|
|
14859
|
+
var postgres_init_helpers_exports = {};
|
|
14860
|
+
__export(postgres_init_helpers_exports, {
|
|
14861
|
+
DEFAULT_POSTGRES_CONTAINER: () => DEFAULT_POSTGRES_CONTAINER,
|
|
14862
|
+
DEFAULT_POSTGRES_DB: () => DEFAULT_POSTGRES_DB,
|
|
14863
|
+
DEFAULT_POSTGRES_HOST_PORT: () => DEFAULT_POSTGRES_HOST_PORT,
|
|
14864
|
+
DEFAULT_POSTGRES_IMAGE: () => DEFAULT_POSTGRES_IMAGE,
|
|
14865
|
+
DEFAULT_POSTGRES_NETWORK: () => DEFAULT_POSTGRES_NETWORK,
|
|
14866
|
+
DEFAULT_POSTGRES_PASSWORD: () => DEFAULT_POSTGRES_PASSWORD,
|
|
14867
|
+
DEFAULT_POSTGRES_READY_TIMEOUT_MS: () => DEFAULT_POSTGRES_READY_TIMEOUT_MS,
|
|
14868
|
+
DEFAULT_POSTGRES_USER: () => DEFAULT_POSTGRES_USER,
|
|
14869
|
+
ensureOlamPostgresSingleton: () => ensureOlamPostgresSingleton,
|
|
14870
|
+
ensureOlamSharedNetwork: () => ensureOlamSharedNetwork,
|
|
14871
|
+
pullPostgresImage: () => pullPostgresImage,
|
|
14872
|
+
startPostgresSingleton: () => startPostgresSingleton,
|
|
14873
|
+
statusPostgresSingleton: () => statusPostgresSingleton,
|
|
14874
|
+
waitPostgresReady: () => waitPostgresReady
|
|
14875
|
+
});
|
|
14876
|
+
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
14877
|
+
function resolve11(options) {
|
|
14878
|
+
return {
|
|
14879
|
+
network: options.network ?? DEFAULT_POSTGRES_NETWORK,
|
|
14880
|
+
containerName: options.containerName ?? DEFAULT_POSTGRES_CONTAINER,
|
|
14881
|
+
image: options.image ?? DEFAULT_POSTGRES_IMAGE,
|
|
14882
|
+
hostPort: options.hostPort ?? DEFAULT_POSTGRES_HOST_PORT,
|
|
14883
|
+
user: options.user ?? DEFAULT_POSTGRES_USER,
|
|
14884
|
+
password: options.password ?? DEFAULT_POSTGRES_PASSWORD,
|
|
14885
|
+
db: options.db ?? DEFAULT_POSTGRES_DB,
|
|
14886
|
+
readyTimeoutMs: options.readyTimeoutMs ?? DEFAULT_POSTGRES_READY_TIMEOUT_MS,
|
|
14887
|
+
spawn: options.spawn ?? spawnSync7
|
|
14888
|
+
};
|
|
14889
|
+
}
|
|
14890
|
+
function statusPostgresSingleton(options = {}) {
|
|
14891
|
+
const opts = resolve11(options);
|
|
14892
|
+
const inspect = opts.spawn("docker", ["inspect", "--format", "{{.State.Status}}", opts.containerName], { encoding: "utf-8" });
|
|
14893
|
+
if (inspect.status !== 0)
|
|
14894
|
+
return "missing";
|
|
14895
|
+
const raw = (inspect.stdout || "").trim();
|
|
14896
|
+
return raw === "running" ? "running" : "stopped";
|
|
14897
|
+
}
|
|
14898
|
+
function ensureOlamSharedNetwork(options = {}) {
|
|
14899
|
+
const opts = resolve11(options);
|
|
14900
|
+
const create = opts.spawn("docker", ["network", "create", opts.network], {
|
|
14901
|
+
encoding: "utf-8"
|
|
14902
|
+
});
|
|
14903
|
+
if (create.status !== 0 && !(create.stderr || "").includes("already exists")) {
|
|
14904
|
+
throw new Error(`failed to create docker network ${opts.network}: ${(create.stderr || "").trim()}`);
|
|
14905
|
+
}
|
|
14906
|
+
const inspect = opts.spawn("docker", ["network", "inspect", opts.network, "--format", "{{.Driver}}"], { encoding: "utf-8" });
|
|
14907
|
+
if (inspect.status !== 0) {
|
|
14908
|
+
throw new Error(`failed to inspect docker network ${opts.network} after create: ${(inspect.stderr || "").trim()}`);
|
|
14909
|
+
}
|
|
14910
|
+
const driver = (inspect.stdout || "").trim();
|
|
14911
|
+
if (driver !== "bridge") {
|
|
14912
|
+
throw new Error(`docker network ${opts.network} exists with driver=${driver}, expected bridge. Run \`docker network rm ${opts.network} && olam init\` to recreate.`);
|
|
14913
|
+
}
|
|
14914
|
+
}
|
|
14915
|
+
function pullPostgresImage(options = {}) {
|
|
14916
|
+
const opts = resolve11(options);
|
|
14917
|
+
const pull = opts.spawn("docker", ["pull", "--platform", "linux/amd64", opts.image], { encoding: "utf-8" });
|
|
14918
|
+
if (pull.status !== 0) {
|
|
14919
|
+
throw new Error(`failed to pull postgres image ${opts.image}: ${(pull.stderr || "").trim()}`);
|
|
14920
|
+
}
|
|
14921
|
+
}
|
|
14922
|
+
function startPostgresSingleton(options = {}) {
|
|
14923
|
+
const opts = resolve11(options);
|
|
14924
|
+
const state = statusPostgresSingleton(options);
|
|
14925
|
+
if (state === "running")
|
|
14926
|
+
return;
|
|
14927
|
+
if (state === "stopped") {
|
|
14928
|
+
const start = opts.spawn("docker", ["start", opts.containerName], {
|
|
14929
|
+
encoding: "utf-8"
|
|
14930
|
+
});
|
|
14931
|
+
if (start.status !== 0) {
|
|
14932
|
+
throw new Error(`failed to start existing postgres container ${opts.containerName}: ${(start.stderr || "").trim()}`);
|
|
14933
|
+
}
|
|
14934
|
+
return;
|
|
14935
|
+
}
|
|
14936
|
+
const run = opts.spawn("docker", [
|
|
14937
|
+
"run",
|
|
14938
|
+
"--detach",
|
|
14939
|
+
"--name",
|
|
14940
|
+
opts.containerName,
|
|
14941
|
+
"--network",
|
|
14942
|
+
opts.network,
|
|
14943
|
+
"--network-alias",
|
|
14944
|
+
opts.containerName,
|
|
14945
|
+
"--restart",
|
|
14946
|
+
"unless-stopped",
|
|
14947
|
+
"--publish",
|
|
14948
|
+
`127.0.0.1:${opts.hostPort}:5432`,
|
|
14949
|
+
"--env",
|
|
14950
|
+
`POSTGRES_USER=${opts.user}`,
|
|
14951
|
+
"--env",
|
|
14952
|
+
`POSTGRES_PASSWORD=${opts.password}`,
|
|
14953
|
+
"--env",
|
|
14954
|
+
`POSTGRES_DB=${opts.db}`,
|
|
14955
|
+
"--env",
|
|
14956
|
+
`TZ=Asia/Singapore`,
|
|
14957
|
+
opts.image
|
|
14958
|
+
], { encoding: "utf-8" });
|
|
14959
|
+
if (run.status !== 0) {
|
|
14960
|
+
throw new Error(`failed to start postgres singleton ${opts.containerName}: ${(run.stderr || "").trim()}`);
|
|
14961
|
+
}
|
|
14962
|
+
}
|
|
14963
|
+
async function waitPostgresReady(options = {}) {
|
|
14964
|
+
const opts = resolve11(options);
|
|
14965
|
+
const deadline = Date.now() + opts.readyTimeoutMs;
|
|
14966
|
+
while (Date.now() < deadline) {
|
|
14967
|
+
const ready = opts.spawn("docker", ["exec", opts.containerName, "pg_isready", "-U", opts.user], { encoding: "utf-8" });
|
|
14968
|
+
if (ready.status === 0)
|
|
14969
|
+
return true;
|
|
14970
|
+
await sleep3(1e3);
|
|
14971
|
+
}
|
|
14972
|
+
return false;
|
|
14973
|
+
}
|
|
14974
|
+
async function ensureOlamPostgresSingleton(options = {}) {
|
|
14975
|
+
const opts = resolve11(options);
|
|
14976
|
+
ensureOlamSharedNetwork(options);
|
|
14977
|
+
pullPostgresImage(options);
|
|
14978
|
+
startPostgresSingleton(options);
|
|
14979
|
+
const ready = await waitPostgresReady(options);
|
|
14980
|
+
if (!ready) {
|
|
14981
|
+
throw new Error(`postgres singleton ${opts.containerName} failed pg_isready within ${opts.readyTimeoutMs}ms. Inspect with \`docker logs ${opts.containerName}\`.`);
|
|
14982
|
+
}
|
|
14983
|
+
const finalState = statusPostgresSingleton(options);
|
|
14984
|
+
return {
|
|
14985
|
+
state: finalState,
|
|
14986
|
+
network: opts.network,
|
|
14987
|
+
container: opts.containerName,
|
|
14988
|
+
hostPort: opts.hostPort,
|
|
14989
|
+
ready
|
|
14990
|
+
};
|
|
14991
|
+
}
|
|
14992
|
+
function sleep3(ms) {
|
|
14993
|
+
return new Promise((resolve14) => setTimeout(resolve14, ms));
|
|
14994
|
+
}
|
|
14995
|
+
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;
|
|
14996
|
+
var init_postgres_init_helpers = __esm({
|
|
14997
|
+
"../core/dist/auth/postgres-init-helpers.js"() {
|
|
14998
|
+
"use strict";
|
|
14999
|
+
DEFAULT_POSTGRES_NETWORK = "olam-shared";
|
|
15000
|
+
DEFAULT_POSTGRES_CONTAINER = "olam-postgres";
|
|
15001
|
+
DEFAULT_POSTGRES_HOST_PORT = 5433;
|
|
15002
|
+
DEFAULT_POSTGRES_IMAGE = "odidev/postgis:13-3.1-alpine";
|
|
15003
|
+
DEFAULT_POSTGRES_USER = "development";
|
|
15004
|
+
DEFAULT_POSTGRES_PASSWORD = "development";
|
|
15005
|
+
DEFAULT_POSTGRES_DB = "postgres";
|
|
15006
|
+
DEFAULT_POSTGRES_READY_TIMEOUT_MS = 6e4;
|
|
15007
|
+
}
|
|
15008
|
+
});
|
|
15009
|
+
|
|
14417
15010
|
// src/registry-allowlist.ts
|
|
14418
15011
|
var registry_allowlist_exports = {};
|
|
14419
15012
|
__export(registry_allowlist_exports, {
|
|
@@ -15256,7 +15849,7 @@ init_host_cp();
|
|
|
15256
15849
|
init_auth();
|
|
15257
15850
|
import * as fs27 from "node:fs";
|
|
15258
15851
|
import * as path29 from "node:path";
|
|
15259
|
-
import { spawnSync as
|
|
15852
|
+
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
15260
15853
|
import ora2 from "ora";
|
|
15261
15854
|
import pc7 from "picocolors";
|
|
15262
15855
|
|
|
@@ -15266,7 +15859,7 @@ init_install_root();
|
|
|
15266
15859
|
init_exit_codes();
|
|
15267
15860
|
init_protocol_version();
|
|
15268
15861
|
init_output();
|
|
15269
|
-
import { spawnSync as
|
|
15862
|
+
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
15270
15863
|
import { existsSync as existsSync25, readFileSync as readFileSync19 } from "node:fs";
|
|
15271
15864
|
import { join as join31 } from "node:path";
|
|
15272
15865
|
import ora from "ora";
|
|
@@ -15298,7 +15891,7 @@ function loadImageDigests(installRootDir = installRoot()) {
|
|
|
15298
15891
|
return parsed;
|
|
15299
15892
|
}
|
|
15300
15893
|
var REAL_OLAM_SUBCOMMAND = (args) => {
|
|
15301
|
-
const result =
|
|
15894
|
+
const result = spawnSync8(process.execPath, [
|
|
15302
15895
|
process.argv[1] ?? "olam",
|
|
15303
15896
|
...args
|
|
15304
15897
|
], {
|
|
@@ -15460,6 +16053,48 @@ async function runBootstrap2(opts, deps = {}) {
|
|
|
15460
16053
|
return { exitCode: EXIT_GENERIC_ERROR, summary: "auth up failed" };
|
|
15461
16054
|
}
|
|
15462
16055
|
authUpSpinner.succeed("auth-service running");
|
|
16056
|
+
if (opts.skipPostgresSingleton || process.env.OLAM_BOOTSTRAP_SKIP_POSTGRES === "1") {
|
|
16057
|
+
printInfo("postgres singleton", "skipped");
|
|
16058
|
+
} else {
|
|
16059
|
+
const pgSpinner = ora("Starting olam-postgres singleton").start();
|
|
16060
|
+
try {
|
|
16061
|
+
const { ensureOlamPostgresSingleton: ensureOlamPostgresSingleton2 } = await Promise.resolve().then(() => (init_postgres_init_helpers(), postgres_init_helpers_exports));
|
|
16062
|
+
const result = await ensureOlamPostgresSingleton2();
|
|
16063
|
+
pgSpinner.succeed(
|
|
16064
|
+
`olam-postgres ${result.state} on network ${result.network} \u2192 127.0.0.1:${result.hostPort}`
|
|
16065
|
+
);
|
|
16066
|
+
} catch (err) {
|
|
16067
|
+
pgSpinner.fail("olam-postgres singleton bring-up failed");
|
|
16068
|
+
process.stderr.write(` ${err instanceof Error ? err.message : String(err)}
|
|
16069
|
+
`);
|
|
16070
|
+
process.stderr.write(` Re-run \`olam bootstrap\` after resolving, or skip via OLAM_BOOTSTRAP_SKIP_POSTGRES=1.
|
|
16071
|
+
`);
|
|
16072
|
+
return { exitCode: EXIT_GENERIC_ERROR, summary: "postgres singleton bring-up failed" };
|
|
16073
|
+
}
|
|
16074
|
+
}
|
|
16075
|
+
if (opts.skipMemory || process.env.OLAM_BOOTSTRAP_SKIP_MEMORY === "1") {
|
|
16076
|
+
printInfo("agent-memory", "skipped");
|
|
16077
|
+
} else {
|
|
16078
|
+
const memSpinner = ora("Starting agent-memory host-process").start();
|
|
16079
|
+
const mem = runOlam(["memory", "start"]);
|
|
16080
|
+
if (mem.exitCode !== 0) {
|
|
16081
|
+
memSpinner.fail("agent-memory start failed");
|
|
16082
|
+
if (mem.stdout.trim().length > 0) {
|
|
16083
|
+
process.stderr.write(` stdout: ${mem.stdout.split("\n").slice(0, 20).join("\n ")}
|
|
16084
|
+
`);
|
|
16085
|
+
}
|
|
16086
|
+
if (mem.stderr.trim().length > 0) {
|
|
16087
|
+
process.stderr.write(` stderr: ${mem.stderr.split("\n").slice(0, 20).join("\n ")}
|
|
16088
|
+
`);
|
|
16089
|
+
}
|
|
16090
|
+
process.stderr.write(
|
|
16091
|
+
` Re-run \`olam bootstrap\` after resolving, or skip via OLAM_BOOTSTRAP_SKIP_MEMORY=1.
|
|
16092
|
+
`
|
|
16093
|
+
);
|
|
16094
|
+
return { exitCode: EXIT_GENERIC_ERROR, summary: "agent-memory start failed" };
|
|
16095
|
+
}
|
|
16096
|
+
memSpinner.succeed("agent-memory running");
|
|
16097
|
+
}
|
|
15463
16098
|
if (opts.skipAuthLogin || process.env.OLAM_BOOTSTRAP_SKIP_AUTH_LOGIN === "1") {
|
|
15464
16099
|
printInfo("auth login", "skipped");
|
|
15465
16100
|
} else {
|
|
@@ -15496,6 +16131,8 @@ async function runBootstrap2(opts, deps = {}) {
|
|
|
15496
16131
|
printHeader("Stack ready");
|
|
15497
16132
|
printInfo("olam-host-cp", `running (${digests["host-cp"].slice(0, 19)}\u2026)`);
|
|
15498
16133
|
printInfo("olam-auth", `running (${digests.auth.slice(0, 19)}\u2026)`);
|
|
16134
|
+
const memorySkipped = opts.skipMemory || process.env.OLAM_BOOTSTRAP_SKIP_MEMORY === "1";
|
|
16135
|
+
printInfo("agent-memory", memorySkipped ? "skipped" : "running");
|
|
15499
16136
|
printInfo("olam-devbox", `pulled (${digests.devbox.slice(0, 19)}\u2026)`);
|
|
15500
16137
|
printInfo("next", '`olam create --task "your task"` to spawn a world');
|
|
15501
16138
|
return { exitCode: 0, summary: "stack ready" };
|
|
@@ -15503,7 +16140,7 @@ async function runBootstrap2(opts, deps = {}) {
|
|
|
15503
16140
|
function registerBootstrap(program2) {
|
|
15504
16141
|
program2.command("bootstrap").description(
|
|
15505
16142
|
"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(
|
|
16143
|
+
).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
16144
|
"--registry <ref>",
|
|
15508
16145
|
"Override the registry prefix (default: read from image-digests.json or fall back to ghcr.io/pleri)"
|
|
15509
16146
|
).action(async (opts) => {
|
|
@@ -15511,6 +16148,8 @@ function registerBootstrap(program2) {
|
|
|
15511
16148
|
const result = await runBootstrap2({
|
|
15512
16149
|
withSmoke: opts.withSmoke === true,
|
|
15513
16150
|
skipAuthLogin: opts.skipAuthLogin === true,
|
|
16151
|
+
skipPostgresSingleton: opts.skipPostgresSingleton === true,
|
|
16152
|
+
skipMemory: opts.skipMemory === true,
|
|
15514
16153
|
registry: opts.registry
|
|
15515
16154
|
});
|
|
15516
16155
|
process.exitCode = result.exitCode;
|
|
@@ -15594,7 +16233,7 @@ async function smokeTestCodexProvider(authSecret) {
|
|
|
15594
16233
|
function runStep(label, cmd, args, opts = {}) {
|
|
15595
16234
|
const start = Date.now();
|
|
15596
16235
|
process.stdout.write(` ${pc7.dim(label.padEnd(34))}`);
|
|
15597
|
-
const result =
|
|
16236
|
+
const result = spawnSync9(cmd, [...args], {
|
|
15598
16237
|
encoding: "utf-8",
|
|
15599
16238
|
stdio: ["ignore", "pipe", "pipe"],
|
|
15600
16239
|
cwd: opts.cwd ?? process.cwd(),
|
|
@@ -15616,10 +16255,10 @@ async function confirm(message) {
|
|
|
15616
16255
|
if (!process.stdin.isTTY) return true;
|
|
15617
16256
|
const { createInterface: createInterface6 } = await import("node:readline");
|
|
15618
16257
|
const rl = createInterface6({ input: process.stdin, output: process.stdout });
|
|
15619
|
-
return new Promise((
|
|
16258
|
+
return new Promise((resolve14) => {
|
|
15620
16259
|
rl.question(`${message} [y/N] `, (answer) => {
|
|
15621
16260
|
rl.close();
|
|
15622
|
-
|
|
16261
|
+
resolve14(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
|
|
15623
16262
|
});
|
|
15624
16263
|
});
|
|
15625
16264
|
}
|
|
@@ -15714,7 +16353,7 @@ async function runAuthUpgradePullByDigest(deps = {}) {
|
|
|
15714
16353
|
}
|
|
15715
16354
|
function defaultTagAuth(from, to) {
|
|
15716
16355
|
try {
|
|
15717
|
-
const r =
|
|
16356
|
+
const r = spawnSync9("docker", ["tag", from, to], {
|
|
15718
16357
|
encoding: "utf-8",
|
|
15719
16358
|
stdio: ["ignore", "ignore", "pipe"]
|
|
15720
16359
|
});
|
|
@@ -15732,11 +16371,11 @@ function defaultTagAuth(from, to) {
|
|
|
15732
16371
|
}
|
|
15733
16372
|
async function defaultRecreateAuth() {
|
|
15734
16373
|
try {
|
|
15735
|
-
|
|
16374
|
+
spawnSync9("docker", ["stop", "olam-auth"], {
|
|
15736
16375
|
encoding: "utf-8",
|
|
15737
16376
|
stdio: ["ignore", "ignore", "ignore"]
|
|
15738
16377
|
});
|
|
15739
|
-
|
|
16378
|
+
spawnSync9("docker", ["rm", "olam-auth"], {
|
|
15740
16379
|
encoding: "utf-8",
|
|
15741
16380
|
stdio: ["ignore", "ignore", "ignore"]
|
|
15742
16381
|
});
|
|
@@ -15805,7 +16444,7 @@ ${imageResult.stderr}`);
|
|
|
15805
16444
|
}
|
|
15806
16445
|
const stopStart = Date.now();
|
|
15807
16446
|
process.stdout.write(` ${pc7.dim("docker stop olam-auth".padEnd(34))}`);
|
|
15808
|
-
|
|
16447
|
+
spawnSync9("docker", ["stop", "olam-auth"], {
|
|
15809
16448
|
encoding: "utf-8",
|
|
15810
16449
|
stdio: ["ignore", "pipe", "pipe"]
|
|
15811
16450
|
});
|
|
@@ -15815,7 +16454,7 @@ ${imageResult.stderr}`);
|
|
|
15815
16454
|
timings.push({ label: "container stop", durationMs: stopDurationMs });
|
|
15816
16455
|
const rmStart = Date.now();
|
|
15817
16456
|
process.stdout.write(` ${pc7.dim("docker rm olam-auth".padEnd(34))}`);
|
|
15818
|
-
|
|
16457
|
+
spawnSync9("docker", ["rm", "olam-auth"], {
|
|
15819
16458
|
encoding: "utf-8",
|
|
15820
16459
|
stdio: ["ignore", "pipe", "pipe"]
|
|
15821
16460
|
});
|
|
@@ -15907,7 +16546,7 @@ function registerAuthUpgrade(auth) {
|
|
|
15907
16546
|
// src/commands/services.ts
|
|
15908
16547
|
init_auth();
|
|
15909
16548
|
init_output();
|
|
15910
|
-
import { execFileSync as execFileSync7, spawnSync as
|
|
16549
|
+
import { execFileSync as execFileSync7, spawnSync as spawnSync10 } from "node:child_process";
|
|
15911
16550
|
import pc8 from "picocolors";
|
|
15912
16551
|
var MCP_AUTH_PORT = 9998;
|
|
15913
16552
|
var MCP_AUTH_VOLUME = "olam-mcp-auth-data";
|
|
@@ -15919,7 +16558,7 @@ var MCP_AUTH_HEALTH_TIMEOUT_MS = 6e4;
|
|
|
15919
16558
|
var McpAuthContainerController = class {
|
|
15920
16559
|
imageTag = MCP_AUTH_LOCAL_TAG;
|
|
15921
16560
|
status() {
|
|
15922
|
-
const r =
|
|
16561
|
+
const r = spawnSync10(
|
|
15923
16562
|
"docker",
|
|
15924
16563
|
["inspect", "--format", "{{.State.Status}}|{{.Id}}", MCP_AUTH_CONTAINER],
|
|
15925
16564
|
{ encoding: "utf-8" }
|
|
@@ -15935,7 +16574,7 @@ var McpAuthContainerController = class {
|
|
|
15935
16574
|
return { state: "missing", port: MCP_AUTH_PORT };
|
|
15936
16575
|
}
|
|
15937
16576
|
imageExists(tag = this.imageTag) {
|
|
15938
|
-
return
|
|
16577
|
+
return spawnSync10("docker", ["image", "inspect", tag], { encoding: "utf-8" }).status === 0;
|
|
15939
16578
|
}
|
|
15940
16579
|
start() {
|
|
15941
16580
|
const current = this.status();
|
|
@@ -15977,7 +16616,7 @@ var McpAuthContainerController = class {
|
|
|
15977
16616
|
execFileSync7("docker", ["stop", MCP_AUTH_CONTAINER], { stdio: "pipe" });
|
|
15978
16617
|
}
|
|
15979
16618
|
remove() {
|
|
15980
|
-
|
|
16619
|
+
spawnSync10("docker", ["rm", "-f", MCP_AUTH_CONTAINER], { stdio: "pipe" });
|
|
15981
16620
|
}
|
|
15982
16621
|
async waitForReady(timeoutMs = MCP_AUTH_HEALTH_TIMEOUT_MS) {
|
|
15983
16622
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -15987,17 +16626,17 @@ var McpAuthContainerController = class {
|
|
|
15987
16626
|
if (res.ok) return true;
|
|
15988
16627
|
} catch {
|
|
15989
16628
|
}
|
|
15990
|
-
await
|
|
16629
|
+
await sleep4(500);
|
|
15991
16630
|
}
|
|
15992
16631
|
return false;
|
|
15993
16632
|
}
|
|
15994
16633
|
};
|
|
15995
|
-
function
|
|
15996
|
-
return new Promise((
|
|
16634
|
+
function sleep4(ms) {
|
|
16635
|
+
return new Promise((resolve14) => setTimeout(resolve14, ms));
|
|
15997
16636
|
}
|
|
15998
16637
|
function dumpContainerLogs(container, tail = 40) {
|
|
15999
16638
|
try {
|
|
16000
|
-
const result =
|
|
16639
|
+
const result = spawnSync10("docker", ["logs", "--tail", String(tail), container], {
|
|
16001
16640
|
encoding: "utf-8",
|
|
16002
16641
|
stdio: ["ignore", "pipe", "pipe"]
|
|
16003
16642
|
});
|
|
@@ -16270,9 +16909,9 @@ ${pc9.dim("Next: olam create --name my-world")}`);
|
|
|
16270
16909
|
|
|
16271
16910
|
// src/commands/create.ts
|
|
16272
16911
|
init_manager();
|
|
16273
|
-
import { spawnSync as
|
|
16274
|
-
import { existsSync as
|
|
16275
|
-
import { dirname as
|
|
16912
|
+
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
16913
|
+
import { existsSync as existsSync29 } from "node:fs";
|
|
16914
|
+
import { dirname as dirname19, resolve as resolve12 } from "node:path";
|
|
16276
16915
|
import ora3 from "ora";
|
|
16277
16916
|
import pc10 from "picocolors";
|
|
16278
16917
|
|
|
@@ -16401,17 +17040,17 @@ function inferRepos(input) {
|
|
|
16401
17040
|
proceed: true
|
|
16402
17041
|
};
|
|
16403
17042
|
}
|
|
16404
|
-
const
|
|
16405
|
-
if (
|
|
17043
|
+
const candidates2 = extractCandidates(input.prompt);
|
|
17044
|
+
if (candidates2.length === 0) {
|
|
16406
17045
|
return { repos: [], confidence: 0, proceed: false };
|
|
16407
17046
|
}
|
|
16408
17047
|
if (input.catalog && input.catalog.length > 0) {
|
|
16409
17048
|
const catalogSet = new Set(input.catalog.map((c) => c.toLowerCase()));
|
|
16410
|
-
const matched =
|
|
17049
|
+
const matched = candidates2.filter((c) => catalogSet.has(c));
|
|
16411
17050
|
if (matched.length === 0) {
|
|
16412
17051
|
return { repos: [], confidence: 0.2, proceed: false };
|
|
16413
17052
|
}
|
|
16414
|
-
const ratio = matched.length /
|
|
17053
|
+
const ratio = matched.length / candidates2.length;
|
|
16415
17054
|
const confidence = Math.min(0.95, 0.5 + 0.5 * ratio);
|
|
16416
17055
|
return {
|
|
16417
17056
|
repos: matched,
|
|
@@ -16420,7 +17059,7 @@ function inferRepos(input) {
|
|
|
16420
17059
|
};
|
|
16421
17060
|
}
|
|
16422
17061
|
return {
|
|
16423
|
-
repos:
|
|
17062
|
+
repos: candidates2,
|
|
16424
17063
|
confidence: 0.6,
|
|
16425
17064
|
proceed: false
|
|
16426
17065
|
};
|
|
@@ -16506,6 +17145,132 @@ function decideWorkspaceMatch(input) {
|
|
|
16506
17145
|
init_context();
|
|
16507
17146
|
init_output();
|
|
16508
17147
|
init_host_cp();
|
|
17148
|
+
|
|
17149
|
+
// src/lib/memory-secret.ts
|
|
17150
|
+
import { existsSync as existsSync28, mkdirSync as mkdirSync17, readFileSync as readFileSync20, statSync as statSync7, writeFileSync as writeFileSync13, renameSync as renameSync4, chmodSync as chmodSync3 } from "node:fs";
|
|
17151
|
+
import { homedir as homedir17 } from "node:os";
|
|
17152
|
+
import { join as join34, dirname as dirname18 } from "node:path";
|
|
17153
|
+
import { randomBytes as randomBytes6 } from "node:crypto";
|
|
17154
|
+
var MEMORY_SECRET_PATH = join34(homedir17(), ".olam", "memory-secret");
|
|
17155
|
+
var SECRET_LEN_BYTES = 32;
|
|
17156
|
+
function generateSecret() {
|
|
17157
|
+
return randomBytes6(SECRET_LEN_BYTES).toString("hex");
|
|
17158
|
+
}
|
|
17159
|
+
function writeSecretAtPath(path56, value) {
|
|
17160
|
+
mkdirSync17(dirname18(path56), { recursive: true });
|
|
17161
|
+
const tmp = `${path56}.tmp.${process.pid}`;
|
|
17162
|
+
writeFileSync13(tmp, value, { mode: 384 });
|
|
17163
|
+
chmodSync3(tmp, 384);
|
|
17164
|
+
renameSync4(tmp, path56);
|
|
17165
|
+
}
|
|
17166
|
+
function readSecretAtPathOrNull(path56) {
|
|
17167
|
+
if (!existsSync28(path56)) return null;
|
|
17168
|
+
const mode = statSync7(path56).mode & 511;
|
|
17169
|
+
if (mode !== 384) {
|
|
17170
|
+
process.stderr.write(
|
|
17171
|
+
`warn: ${path56} has mode 0${mode.toString(8)}; expected 0600. Run 'olam memory secret rotate' to regenerate.
|
|
17172
|
+
`
|
|
17173
|
+
);
|
|
17174
|
+
}
|
|
17175
|
+
return readFileSync20(path56, "utf8").trim();
|
|
17176
|
+
}
|
|
17177
|
+
function readSecretAtPath(path56) {
|
|
17178
|
+
const v = readSecretAtPathOrNull(path56);
|
|
17179
|
+
if (v === null) {
|
|
17180
|
+
throw new Error(
|
|
17181
|
+
`Secret not found at ${path56}. Run 'olam memory start' to generate it.`
|
|
17182
|
+
);
|
|
17183
|
+
}
|
|
17184
|
+
return v;
|
|
17185
|
+
}
|
|
17186
|
+
function ensureMemorySecret(path56 = MEMORY_SECRET_PATH) {
|
|
17187
|
+
const existing = readSecretAtPathOrNull(path56);
|
|
17188
|
+
if (existing) return existing;
|
|
17189
|
+
const fresh = generateSecret();
|
|
17190
|
+
writeSecretAtPath(path56, fresh);
|
|
17191
|
+
return fresh;
|
|
17192
|
+
}
|
|
17193
|
+
function readMemorySecretOrNull(path56 = MEMORY_SECRET_PATH) {
|
|
17194
|
+
return readSecretAtPathOrNull(path56);
|
|
17195
|
+
}
|
|
17196
|
+
function readMemorySecret2(path56 = MEMORY_SECRET_PATH) {
|
|
17197
|
+
return readSecretAtPath(path56);
|
|
17198
|
+
}
|
|
17199
|
+
function rotateMemorySecret(path56 = MEMORY_SECRET_PATH) {
|
|
17200
|
+
const fresh = generateSecret();
|
|
17201
|
+
writeSecretAtPath(path56, fresh);
|
|
17202
|
+
return fresh;
|
|
17203
|
+
}
|
|
17204
|
+
function hasMemorySecret(path56 = MEMORY_SECRET_PATH) {
|
|
17205
|
+
return existsSync28(path56);
|
|
17206
|
+
}
|
|
17207
|
+
|
|
17208
|
+
// src/lib/world-mcp-register.ts
|
|
17209
|
+
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
17210
|
+
var DEFAULT_DOCKER_EXEC_DEPS = {
|
|
17211
|
+
spawn: spawnSync11,
|
|
17212
|
+
log: (msg) => console.log(msg)
|
|
17213
|
+
};
|
|
17214
|
+
var MCP_NAME = "agentmemory";
|
|
17215
|
+
var MCP_BIN = "agentmemory-mcp";
|
|
17216
|
+
function probeMcpListed(containerName, deps) {
|
|
17217
|
+
const result = deps.spawn(
|
|
17218
|
+
"docker",
|
|
17219
|
+
["exec", containerName, "claude", "mcp", "list"],
|
|
17220
|
+
{ encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }
|
|
17221
|
+
);
|
|
17222
|
+
if (result.status !== 0) return "unknown";
|
|
17223
|
+
const combined = `${result.stdout ?? ""}
|
|
17224
|
+
${result.stderr ?? ""}`;
|
|
17225
|
+
return new RegExp(`^\\s*${MCP_NAME}\\b`, "m").test(combined) ? "present" : "absent";
|
|
17226
|
+
}
|
|
17227
|
+
function registerAgentMemoryMcp(opts, deps = DEFAULT_DOCKER_EXEC_DEPS) {
|
|
17228
|
+
if (!opts.agentmemorySecret || opts.agentmemorySecret.length === 0) {
|
|
17229
|
+
return {
|
|
17230
|
+
outcome: "skipped-no-env",
|
|
17231
|
+
reason: "world has no AGENTMEMORY_SECRET (memory service was absent or --skip-memory active at spawn time)"
|
|
17232
|
+
};
|
|
17233
|
+
}
|
|
17234
|
+
const probe2 = probeMcpListed(opts.containerName, deps);
|
|
17235
|
+
if (probe2 === "present") {
|
|
17236
|
+
return { outcome: "already-registered", scope: "user" };
|
|
17237
|
+
}
|
|
17238
|
+
const args = [
|
|
17239
|
+
"exec",
|
|
17240
|
+
opts.containerName,
|
|
17241
|
+
"claude",
|
|
17242
|
+
"mcp",
|
|
17243
|
+
"add",
|
|
17244
|
+
MCP_NAME,
|
|
17245
|
+
"--scope",
|
|
17246
|
+
"user",
|
|
17247
|
+
"--env",
|
|
17248
|
+
`AGENTMEMORY_URL=${opts.agentmemoryUrl}`,
|
|
17249
|
+
"--env",
|
|
17250
|
+
`AGENTMEMORY_SECRET=${opts.agentmemorySecret}`,
|
|
17251
|
+
"--",
|
|
17252
|
+
MCP_BIN
|
|
17253
|
+
];
|
|
17254
|
+
const redacted = args.map(
|
|
17255
|
+
(a) => a.startsWith("AGENTMEMORY_SECRET=") ? "AGENTMEMORY_SECRET=<redacted>" : a
|
|
17256
|
+
);
|
|
17257
|
+
deps.log?.(`Wiring agent-memory MCP in ${opts.containerName}: docker ${redacted.join(" ")}`);
|
|
17258
|
+
const result = deps.spawn("docker", args, {
|
|
17259
|
+
encoding: "utf8",
|
|
17260
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
17261
|
+
});
|
|
17262
|
+
if (result.status === 0) {
|
|
17263
|
+
return { outcome: "registered", scope: "user" };
|
|
17264
|
+
}
|
|
17265
|
+
const detail = (result.stderr?.trim() || result.stdout?.trim() || "(no output)").replace(
|
|
17266
|
+
/AGENTMEMORY_SECRET=\S+/g,
|
|
17267
|
+
"AGENTMEMORY_SECRET=<redacted>"
|
|
17268
|
+
);
|
|
17269
|
+
return { outcome: "failed", rc: result.status ?? 1, detail };
|
|
17270
|
+
}
|
|
17271
|
+
|
|
17272
|
+
// src/commands/create.ts
|
|
17273
|
+
var AGENTMEMORY_LOCAL_URL = "http://host.docker.internal:3111";
|
|
16509
17274
|
var HOST_CP_URL = "http://127.0.0.1:19000";
|
|
16510
17275
|
async function readHostCpTokenForCreate() {
|
|
16511
17276
|
try {
|
|
@@ -16542,7 +17307,7 @@ function registerCreate(program2) {
|
|
|
16542
17307
|
if (decision.stderrLine) {
|
|
16543
17308
|
process.stderr.write(decision.stderrLine + "\n");
|
|
16544
17309
|
}
|
|
16545
|
-
|
|
17310
|
+
spawnSync12("docker", ["pull", overrideRef], { stdio: "pipe" });
|
|
16546
17311
|
const { inspectImageProtocolVersions: inspectImageProtocolVersions2, checkProtocolOverlap: checkProtocolOverlap2 } = await Promise.resolve().then(() => (init_protocol_version(), protocol_version_exports));
|
|
16547
17312
|
const inspect = inspectImageProtocolVersions2(overrideRef);
|
|
16548
17313
|
if (inspect.inspectFailed) {
|
|
@@ -16659,7 +17424,7 @@ function registerCreate(program2) {
|
|
|
16659
17424
|
throw err;
|
|
16660
17425
|
}
|
|
16661
17426
|
const spinner2 = ora3("Rebuilding olam-devbox:latest\u2026").start();
|
|
16662
|
-
const rebuild =
|
|
17427
|
+
const rebuild = spawnSync12(
|
|
16663
17428
|
"bash",
|
|
16664
17429
|
[buildScript],
|
|
16665
17430
|
{ cwd: repoRoot, stdio: "inherit" }
|
|
@@ -16696,6 +17461,34 @@ function registerCreate(program2) {
|
|
|
16696
17461
|
...opts.runbook ? { runbookName: opts.runbook } : {}
|
|
16697
17462
|
});
|
|
16698
17463
|
spinner.succeed("World created");
|
|
17464
|
+
try {
|
|
17465
|
+
const memorySecret = readMemorySecretOrNull() ?? "";
|
|
17466
|
+
const containerName = `olam-${world.id}-devbox`;
|
|
17467
|
+
const result = registerAgentMemoryMcp({
|
|
17468
|
+
containerName,
|
|
17469
|
+
agentmemoryUrl: AGENTMEMORY_LOCAL_URL,
|
|
17470
|
+
agentmemorySecret: memorySecret
|
|
17471
|
+
});
|
|
17472
|
+
switch (result.outcome) {
|
|
17473
|
+
case "registered":
|
|
17474
|
+
printInfo("Agent memory", `MCP registered in-world (--scope ${result.scope})`);
|
|
17475
|
+
break;
|
|
17476
|
+
case "already-registered":
|
|
17477
|
+
printInfo("Agent memory", `MCP already registered (idempotent skip)`);
|
|
17478
|
+
break;
|
|
17479
|
+
case "skipped-no-env":
|
|
17480
|
+
break;
|
|
17481
|
+
case "failed":
|
|
17482
|
+
printWarning(
|
|
17483
|
+
`Agent memory MCP registration failed (rc=${result.rc}): ${result.detail}. World is still usable; the in-world @agentmemory/mcp shim falls back to local InMemoryKV.`
|
|
17484
|
+
);
|
|
17485
|
+
break;
|
|
17486
|
+
}
|
|
17487
|
+
} catch (err) {
|
|
17488
|
+
printWarning(
|
|
17489
|
+
`Agent memory MCP registration threw: ${err instanceof Error ? err.message : String(err)}. World is still usable.`
|
|
17490
|
+
);
|
|
17491
|
+
}
|
|
16699
17492
|
if (opts.keepAfterMerge) {
|
|
16700
17493
|
const { WorldRegistry: WorldRegistry2 } = await Promise.resolve().then(() => (init_registry(), registry_exports));
|
|
16701
17494
|
const reg2 = new WorldRegistry2();
|
|
@@ -16866,10 +17659,10 @@ ${pc10.cyan("Host CP UI:")} ${worldUrl}`);
|
|
|
16866
17659
|
function resolveRepoRoot(start) {
|
|
16867
17660
|
let cur = start;
|
|
16868
17661
|
while (true) {
|
|
16869
|
-
if (
|
|
17662
|
+
if (existsSync29(resolve12(cur, "packages")) && existsSync29(resolve12(cur, "package.json"))) {
|
|
16870
17663
|
return cur;
|
|
16871
17664
|
}
|
|
16872
|
-
const parent =
|
|
17665
|
+
const parent = dirname19(cur);
|
|
16873
17666
|
if (parent === cur) return start;
|
|
16874
17667
|
cur = parent;
|
|
16875
17668
|
}
|
|
@@ -17103,7 +17896,7 @@ import * as path31 from "node:path";
|
|
|
17103
17896
|
var CLI_VERSION2 = process.env["OLAM_CLI_VERSION"] ?? "0.0.0";
|
|
17104
17897
|
var HOST_CP_PORT2 = 19e3;
|
|
17105
17898
|
async function getMachineStatus(_probe, _loadCtx, _readToken) {
|
|
17106
|
-
const
|
|
17899
|
+
const probe2 = _probe ?? (async () => {
|
|
17107
17900
|
const { probeHostCp: probeHostCp2 } = await Promise.resolve().then(() => (init_host_cp(), host_cp_exports));
|
|
17108
17901
|
return probeHostCp2();
|
|
17109
17902
|
});
|
|
@@ -17118,7 +17911,7 @@ async function getMachineStatus(_probe, _loadCtx, _readToken) {
|
|
|
17118
17911
|
const { loadContext: loadContext2 } = await Promise.resolve().then(() => (init_context(), context_exports));
|
|
17119
17912
|
return loadContext2();
|
|
17120
17913
|
});
|
|
17121
|
-
const probeResult = await
|
|
17914
|
+
const probeResult = await probe2();
|
|
17122
17915
|
const tokenPresent = readToken2() !== null;
|
|
17123
17916
|
const hostCpRunning = probeResult !== null;
|
|
17124
17917
|
let worldCount = 0;
|
|
@@ -17553,14 +18346,14 @@ function printTable(entries) {
|
|
|
17553
18346
|
async function confirmInteractive() {
|
|
17554
18347
|
process.stdout.write(" Type `yes` to proceed: ");
|
|
17555
18348
|
const buf = [];
|
|
17556
|
-
return new Promise((
|
|
18349
|
+
return new Promise((resolve14) => {
|
|
17557
18350
|
const onData = (chunk) => {
|
|
17558
18351
|
buf.push(chunk);
|
|
17559
18352
|
if (Buffer.concat(buf).toString("utf-8").includes("\n")) {
|
|
17560
18353
|
process.stdin.removeListener("data", onData);
|
|
17561
18354
|
process.stdin.pause();
|
|
17562
18355
|
const answer = Buffer.concat(buf).toString("utf-8").trim();
|
|
17563
|
-
|
|
18356
|
+
resolve14(answer.toLowerCase() === "yes");
|
|
17564
18357
|
}
|
|
17565
18358
|
};
|
|
17566
18359
|
process.stdin.resume();
|
|
@@ -19507,8 +20300,8 @@ var path35 = {
|
|
|
19507
20300
|
win32: { sep: "\\" },
|
|
19508
20301
|
posix: { sep: "/" }
|
|
19509
20302
|
};
|
|
19510
|
-
var
|
|
19511
|
-
minimatch.sep =
|
|
20303
|
+
var sep2 = defaultPlatform === "win32" ? path35.win32.sep : path35.posix.sep;
|
|
20304
|
+
minimatch.sep = sep2;
|
|
19512
20305
|
var GLOBSTAR = /* @__PURE__ */ Symbol("globstar **");
|
|
19513
20306
|
minimatch.GLOBSTAR = GLOBSTAR;
|
|
19514
20307
|
var qmark2 = "[^/]";
|
|
@@ -20333,7 +21126,7 @@ function registerPolicyCheck(program2) {
|
|
|
20333
21126
|
}
|
|
20334
21127
|
|
|
20335
21128
|
// src/commands/worldspec/compile.ts
|
|
20336
|
-
import { existsSync as
|
|
21129
|
+
import { existsSync as existsSync34, mkdirSync as mkdirSync19, readFileSync as readFileSync23, writeFileSync as writeFileSync15 } from "node:fs";
|
|
20337
21130
|
import { resolve as resolvePath } from "node:path";
|
|
20338
21131
|
import YAML5 from "yaml";
|
|
20339
21132
|
|
|
@@ -20660,10 +21453,10 @@ function deriveSuggestion(issue) {
|
|
|
20660
21453
|
if (firstKey === void 0)
|
|
20661
21454
|
return void 0;
|
|
20662
21455
|
const isTopLevel = issue.path.length === 0;
|
|
20663
|
-
const
|
|
20664
|
-
if (
|
|
21456
|
+
const candidates2 = isTopLevel ? KNOWN_TOP_LEVEL_KEYS2 : [];
|
|
21457
|
+
if (candidates2.length === 0)
|
|
20665
21458
|
return void 0;
|
|
20666
|
-
const closest = nearestMatch(firstKey, [...
|
|
21459
|
+
const closest = nearestMatch(firstKey, [...candidates2]);
|
|
20667
21460
|
return closest ? `did you mean "${closest}"?` : void 0;
|
|
20668
21461
|
}
|
|
20669
21462
|
if (issue.code === "invalid_union_discriminator") {
|
|
@@ -20671,10 +21464,10 @@ function deriveSuggestion(issue) {
|
|
|
20671
21464
|
}
|
|
20672
21465
|
return void 0;
|
|
20673
21466
|
}
|
|
20674
|
-
function nearestMatch(input,
|
|
21467
|
+
function nearestMatch(input, candidates2) {
|
|
20675
21468
|
let best;
|
|
20676
21469
|
let bestDistance = 3;
|
|
20677
|
-
for (const c of
|
|
21470
|
+
for (const c of candidates2) {
|
|
20678
21471
|
const d = levenshtein(input.toLowerCase(), c.toLowerCase());
|
|
20679
21472
|
if (d < bestDistance) {
|
|
20680
21473
|
bestDistance = d;
|
|
@@ -21136,13 +21929,13 @@ function registerWorldspecCompile(parent) {
|
|
|
21136
21929
|
const sourcePolicies = [];
|
|
21137
21930
|
for (const p of paths) {
|
|
21138
21931
|
const abs = resolvePath(process.cwd(), p);
|
|
21139
|
-
if (!
|
|
21932
|
+
if (!existsSync34(abs)) {
|
|
21140
21933
|
printError(`worldspec not found at ${abs}`);
|
|
21141
21934
|
process.exit(EXIT_WORLDSPEC_FILE_NOT_FOUND);
|
|
21142
21935
|
}
|
|
21143
21936
|
let yaml;
|
|
21144
21937
|
try {
|
|
21145
|
-
yaml = YAML5.parse(
|
|
21938
|
+
yaml = YAML5.parse(readFileSync23(abs, "utf8"));
|
|
21146
21939
|
} catch (err) {
|
|
21147
21940
|
printError(
|
|
21148
21941
|
`${p}: YAML parse error: ${err.message}`
|
|
@@ -21198,13 +21991,13 @@ Resolutions:
|
|
|
21198
21991
|
if (opts.out) {
|
|
21199
21992
|
const outDir = resolvePath(process.cwd(), opts.out);
|
|
21200
21993
|
try {
|
|
21201
|
-
|
|
21202
|
-
|
|
21994
|
+
mkdirSync19(outDir, { recursive: true });
|
|
21995
|
+
writeFileSync15(
|
|
21203
21996
|
resolvePath(outDir, "execution-graph.json"),
|
|
21204
21997
|
graphJson,
|
|
21205
21998
|
"utf8"
|
|
21206
21999
|
);
|
|
21207
|
-
|
|
22000
|
+
writeFileSync15(
|
|
21208
22001
|
resolvePath(outDir, "lockfile.json"),
|
|
21209
22002
|
lockfileJson,
|
|
21210
22003
|
"utf8"
|
|
@@ -21227,7 +22020,7 @@ function getPkgVersion() {
|
|
|
21227
22020
|
// src/commands/worldspec/init.ts
|
|
21228
22021
|
init_exit_codes();
|
|
21229
22022
|
init_output();
|
|
21230
|
-
import { existsSync as
|
|
22023
|
+
import { existsSync as existsSync35, mkdirSync as mkdirSync20, readFileSync as readFileSync24, writeFileSync as writeFileSync16 } from "node:fs";
|
|
21231
22024
|
import { execSync as execSync10 } from "node:child_process";
|
|
21232
22025
|
import { basename as basename3, resolve as resolvePath2 } from "node:path";
|
|
21233
22026
|
function registerWorldspecInit(parent) {
|
|
@@ -21245,7 +22038,7 @@ function registerWorldspecInit(parent) {
|
|
|
21245
22038
|
const cwd = process.cwd();
|
|
21246
22039
|
const targetDir = resolvePath2(cwd, ".olam/worldspecs");
|
|
21247
22040
|
const targetFile = resolvePath2(targetDir, "default.yaml");
|
|
21248
|
-
if (
|
|
22041
|
+
if (existsSync35(targetFile) && !opts.force) {
|
|
21249
22042
|
printError(
|
|
21250
22043
|
`${targetFile} already exists; pass --force to overwrite`
|
|
21251
22044
|
);
|
|
@@ -21260,8 +22053,8 @@ function registerWorldspecInit(parent) {
|
|
|
21260
22053
|
const shape = detectProjectShape(cwd, opts.fromAdbYaml);
|
|
21261
22054
|
const yaml = renderWorldspecYaml(shape);
|
|
21262
22055
|
try {
|
|
21263
|
-
|
|
21264
|
-
|
|
22056
|
+
mkdirSync20(targetDir, { recursive: true });
|
|
22057
|
+
writeFileSync16(targetFile, yaml, {
|
|
21265
22058
|
encoding: "utf8",
|
|
21266
22059
|
flag: opts.force ? "w" : "wx"
|
|
21267
22060
|
});
|
|
@@ -21279,14 +22072,14 @@ function registerWorldspecInit(parent) {
|
|
|
21279
22072
|
});
|
|
21280
22073
|
}
|
|
21281
22074
|
function detectProjectShape(root, useAdbYaml) {
|
|
21282
|
-
const hasGemfile =
|
|
21283
|
-
const hasNodePackageJson =
|
|
21284
|
-
const hasAdbYaml =
|
|
22075
|
+
const hasGemfile = existsSync35(resolvePath2(root, "Gemfile"));
|
|
22076
|
+
const hasNodePackageJson = existsSync35(resolvePath2(root, "package.json"));
|
|
22077
|
+
const hasAdbYaml = existsSync35(resolvePath2(root, ".adb.yaml"));
|
|
21285
22078
|
let rubyVersion = null;
|
|
21286
22079
|
const rubyVersionPath = resolvePath2(root, ".ruby-version");
|
|
21287
|
-
if (
|
|
22080
|
+
if (existsSync35(rubyVersionPath)) {
|
|
21288
22081
|
try {
|
|
21289
|
-
const raw =
|
|
22082
|
+
const raw = readFileSync24(rubyVersionPath, "utf8").trim();
|
|
21290
22083
|
const match2 = raw.match(/(\d+\.\d+\.\d+)/);
|
|
21291
22084
|
if (match2) rubyVersion = match2[1] ?? null;
|
|
21292
22085
|
} catch {
|
|
@@ -21358,7 +22151,7 @@ function relativeFromCwd(absPath, cwd) {
|
|
|
21358
22151
|
}
|
|
21359
22152
|
|
|
21360
22153
|
// src/commands/worldspec/schema.ts
|
|
21361
|
-
import { writeFileSync as
|
|
22154
|
+
import { writeFileSync as writeFileSync17 } from "node:fs";
|
|
21362
22155
|
import { resolve as resolvePath3 } from "node:path";
|
|
21363
22156
|
|
|
21364
22157
|
// ../../node_modules/zod-to-json-schema/dist/esm/Options.js
|
|
@@ -22673,7 +23466,7 @@ function registerWorldspecSchema(parent) {
|
|
|
22673
23466
|
if (opts.out) {
|
|
22674
23467
|
const absPath = resolvePath3(process.cwd(), opts.out);
|
|
22675
23468
|
try {
|
|
22676
|
-
|
|
23469
|
+
writeFileSync17(absPath, json + "\n", "utf8");
|
|
22677
23470
|
} catch (err) {
|
|
22678
23471
|
printError(
|
|
22679
23472
|
`failed to write ${absPath}: ${err.message}`
|
|
@@ -22689,7 +23482,7 @@ function registerWorldspecSchema(parent) {
|
|
|
22689
23482
|
}
|
|
22690
23483
|
|
|
22691
23484
|
// src/commands/worldspec/validate.ts
|
|
22692
|
-
import { existsSync as
|
|
23485
|
+
import { existsSync as existsSync36, readFileSync as readFileSync25 } from "node:fs";
|
|
22693
23486
|
import { resolve as resolvePath4 } from "node:path";
|
|
22694
23487
|
init_exit_codes();
|
|
22695
23488
|
init_output();
|
|
@@ -22706,13 +23499,13 @@ function registerWorldspecValidate(parent) {
|
|
|
22706
23499
|
"human"
|
|
22707
23500
|
).action(async (pathArg, opts) => {
|
|
22708
23501
|
const absPath = resolvePath4(process.cwd(), pathArg);
|
|
22709
|
-
if (!
|
|
23502
|
+
if (!existsSync36(absPath)) {
|
|
22710
23503
|
printError(`worldspec not found at ${absPath}`);
|
|
22711
23504
|
process.exit(EXIT_WORLDSPEC_FILE_NOT_FOUND);
|
|
22712
23505
|
}
|
|
22713
23506
|
let yamlSource;
|
|
22714
23507
|
try {
|
|
22715
|
-
yamlSource =
|
|
23508
|
+
yamlSource = readFileSync25(absPath, "utf8");
|
|
22716
23509
|
} catch (err) {
|
|
22717
23510
|
printError(
|
|
22718
23511
|
`failed to read ${absPath}: ${err.message}`
|
|
@@ -22760,7 +23553,7 @@ init_output();
|
|
|
22760
23553
|
init_host_cp();
|
|
22761
23554
|
import * as fs35 from "node:fs";
|
|
22762
23555
|
import * as path38 from "node:path";
|
|
22763
|
-
import { spawnSync as
|
|
23556
|
+
import { spawnSync as spawnSync14 } from "node:child_process";
|
|
22764
23557
|
import ora7 from "ora";
|
|
22765
23558
|
import pc18 from "picocolors";
|
|
22766
23559
|
|
|
@@ -22768,7 +23561,7 @@ import pc18 from "picocolors";
|
|
|
22768
23561
|
import * as fs33 from "node:fs";
|
|
22769
23562
|
import * as os19 from "node:os";
|
|
22770
23563
|
import * as path36 from "node:path";
|
|
22771
|
-
import { spawnSync as
|
|
23564
|
+
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
22772
23565
|
var LOCK_FILE_PATH = path36.join(os19.homedir(), ".olam", ".upgrade.lock");
|
|
22773
23566
|
var STALE_LOCK_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
22774
23567
|
function readLockFile(lockPath) {
|
|
@@ -22793,7 +23586,7 @@ function isPidAlive2(pid) {
|
|
|
22793
23586
|
}
|
|
22794
23587
|
var PS_UNAVAILABLE = "__ps_unavailable__";
|
|
22795
23588
|
function getPidCommand(pid) {
|
|
22796
|
-
const result =
|
|
23589
|
+
const result = spawnSync13("ps", ["-p", String(pid), "-o", "comm="], {
|
|
22797
23590
|
encoding: "utf-8",
|
|
22798
23591
|
stdio: ["ignore", "pipe", "ignore"]
|
|
22799
23592
|
});
|
|
@@ -23052,7 +23845,7 @@ function extractBundleHash(indexHtml) {
|
|
|
23052
23845
|
function runStep2(label, cmd, args, opts = {}) {
|
|
23053
23846
|
const start = Date.now();
|
|
23054
23847
|
process.stdout.write(` ${pc18.dim(label.padEnd(34))}`);
|
|
23055
|
-
const result =
|
|
23848
|
+
const result = spawnSync14(cmd, [...args], {
|
|
23056
23849
|
encoding: "utf-8",
|
|
23057
23850
|
stdio: ["ignore", "pipe", "pipe"],
|
|
23058
23851
|
cwd: opts.cwd ?? process.cwd(),
|
|
@@ -23071,7 +23864,7 @@ function runStep2(label, cmd, args, opts = {}) {
|
|
|
23071
23864
|
};
|
|
23072
23865
|
}
|
|
23073
23866
|
function isGitDirty(cwd) {
|
|
23074
|
-
const result =
|
|
23867
|
+
const result = spawnSync14("git", ["status", "--porcelain"], {
|
|
23075
23868
|
encoding: "utf-8",
|
|
23076
23869
|
stdio: ["ignore", "pipe", "pipe"],
|
|
23077
23870
|
cwd
|
|
@@ -23079,7 +23872,7 @@ function isGitDirty(cwd) {
|
|
|
23079
23872
|
return (result.stdout ?? "").trim().length > 0;
|
|
23080
23873
|
}
|
|
23081
23874
|
function hasGitUpstream(cwd) {
|
|
23082
|
-
const result =
|
|
23875
|
+
const result = spawnSync14("git", ["rev-parse", "--abbrev-ref", "@{u}"], {
|
|
23083
23876
|
encoding: "utf-8",
|
|
23084
23877
|
stdio: ["ignore", "pipe", "pipe"],
|
|
23085
23878
|
cwd
|
|
@@ -23087,7 +23880,7 @@ function hasGitUpstream(cwd) {
|
|
|
23087
23880
|
return result.status === 0;
|
|
23088
23881
|
}
|
|
23089
23882
|
function captureHeadSha(cwd) {
|
|
23090
|
-
const result =
|
|
23883
|
+
const result = spawnSync14("git", ["rev-parse", "HEAD"], {
|
|
23091
23884
|
encoding: "utf-8",
|
|
23092
23885
|
stdio: ["ignore", "pipe", "pipe"],
|
|
23093
23886
|
cwd
|
|
@@ -23102,7 +23895,7 @@ function abbreviateSha(sha) {
|
|
|
23102
23895
|
}
|
|
23103
23896
|
function imageExists(tag) {
|
|
23104
23897
|
try {
|
|
23105
|
-
const result =
|
|
23898
|
+
const result = spawnSync14("docker", ["image", "inspect", "--format", "{{.Id}}", tag], {
|
|
23106
23899
|
encoding: "utf-8",
|
|
23107
23900
|
stdio: ["ignore", "pipe", "ignore"]
|
|
23108
23901
|
});
|
|
@@ -23118,7 +23911,7 @@ function checkRollbackSetExists(plan) {
|
|
|
23118
23911
|
}
|
|
23119
23912
|
var SMOKE_DOCKER_TIMEOUT_MS = 3e4;
|
|
23120
23913
|
function smokeImage(image, targetSha) {
|
|
23121
|
-
const createResult =
|
|
23914
|
+
const createResult = spawnSync14("docker", ["create", "--name", `olam-smoke-${Date.now()}`, image], {
|
|
23122
23915
|
encoding: "utf-8",
|
|
23123
23916
|
stdio: ["ignore", "pipe", "pipe"],
|
|
23124
23917
|
timeout: SMOKE_DOCKER_TIMEOUT_MS
|
|
@@ -23132,7 +23925,7 @@ function smokeImage(image, targetSha) {
|
|
|
23132
23925
|
};
|
|
23133
23926
|
}
|
|
23134
23927
|
const containerId = (createResult.stdout ?? "").trim();
|
|
23135
|
-
const inspectResult =
|
|
23928
|
+
const inspectResult = spawnSync14(
|
|
23136
23929
|
"docker",
|
|
23137
23930
|
["inspect", "--format", '{{index .Config.Labels "olam.build.sha"}}', image],
|
|
23138
23931
|
{
|
|
@@ -23142,7 +23935,7 @@ function smokeImage(image, targetSha) {
|
|
|
23142
23935
|
}
|
|
23143
23936
|
);
|
|
23144
23937
|
if (containerId.length > 0) {
|
|
23145
|
-
|
|
23938
|
+
spawnSync14("docker", ["rm", "-f", containerId], {
|
|
23146
23939
|
encoding: "utf-8",
|
|
23147
23940
|
stdio: ["ignore", "ignore", "ignore"],
|
|
23148
23941
|
timeout: SMOKE_DOCKER_TIMEOUT_MS
|
|
@@ -23182,7 +23975,7 @@ var PRODUCTION_SWAP_PLAN = [
|
|
|
23182
23975
|
];
|
|
23183
23976
|
function dockerTag(source, dest) {
|
|
23184
23977
|
try {
|
|
23185
|
-
const result =
|
|
23978
|
+
const result = spawnSync14("docker", ["tag", source, dest], {
|
|
23186
23979
|
encoding: "utf-8",
|
|
23187
23980
|
stdio: ["ignore", "ignore", "pipe"]
|
|
23188
23981
|
});
|
|
@@ -23275,10 +24068,10 @@ async function confirm2(message) {
|
|
|
23275
24068
|
if (!process.stdin.isTTY) return true;
|
|
23276
24069
|
const { createInterface: createInterface6 } = await import("node:readline");
|
|
23277
24070
|
const rl = createInterface6({ input: process.stdin, output: process.stdout });
|
|
23278
|
-
return new Promise((
|
|
24071
|
+
return new Promise((resolve14) => {
|
|
23279
24072
|
rl.question(`${message} [y/N] `, (answer) => {
|
|
23280
24073
|
rl.close();
|
|
23281
|
-
|
|
24074
|
+
resolve14(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
|
|
23282
24075
|
});
|
|
23283
24076
|
});
|
|
23284
24077
|
}
|
|
@@ -23346,11 +24139,11 @@ async function waitForAuthHealthLocal(timeoutMs = AUTH_HEALTH_TIMEOUT_MS) {
|
|
|
23346
24139
|
async function recreateAuthService() {
|
|
23347
24140
|
const start = Date.now();
|
|
23348
24141
|
try {
|
|
23349
|
-
|
|
24142
|
+
spawnSync14("docker", ["stop", "olam-auth"], {
|
|
23350
24143
|
encoding: "utf-8",
|
|
23351
24144
|
stdio: ["ignore", "ignore", "ignore"]
|
|
23352
24145
|
});
|
|
23353
|
-
|
|
24146
|
+
spawnSync14("docker", ["rm", "olam-auth"], {
|
|
23354
24147
|
encoding: "utf-8",
|
|
23355
24148
|
stdio: ["ignore", "ignore", "ignore"]
|
|
23356
24149
|
});
|
|
@@ -23609,11 +24402,11 @@ async function defaultRecreateMcpAuthForUpgrade() {
|
|
|
23609
24402
|
}
|
|
23610
24403
|
async function defaultRecreateAuthForUpgrade() {
|
|
23611
24404
|
try {
|
|
23612
|
-
|
|
24405
|
+
spawnSync14("docker", ["stop", "olam-auth"], {
|
|
23613
24406
|
encoding: "utf-8",
|
|
23614
24407
|
stdio: ["ignore", "ignore", "ignore"]
|
|
23615
24408
|
});
|
|
23616
|
-
|
|
24409
|
+
spawnSync14("docker", ["rm", "olam-auth"], {
|
|
23617
24410
|
encoding: "utf-8",
|
|
23618
24411
|
stdio: ["ignore", "ignore", "ignore"]
|
|
23619
24412
|
});
|
|
@@ -23959,7 +24752,7 @@ ${spaResult.stderr}`);
|
|
|
23959
24752
|
process.stdout.write(` ${pc18.dim(step.label.padEnd(34))}
|
|
23960
24753
|
`);
|
|
23961
24754
|
const start = Date.now();
|
|
23962
|
-
const result =
|
|
24755
|
+
const result = spawnSync14("bash", [scriptPath], {
|
|
23963
24756
|
stdio: "inherit",
|
|
23964
24757
|
cwd,
|
|
23965
24758
|
env: { ...process.env, ...olamTagEnv }
|
|
@@ -24308,7 +25101,7 @@ function registerLogs(program2) {
|
|
|
24308
25101
|
init_context();
|
|
24309
25102
|
init_output();
|
|
24310
25103
|
import pc20 from "picocolors";
|
|
24311
|
-
import { spawnSync as
|
|
25104
|
+
import { spawnSync as spawnSync15 } from "node:child_process";
|
|
24312
25105
|
var SAFE_IDENT4 = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
24313
25106
|
function parseDockerTop(stdout) {
|
|
24314
25107
|
const trimmed = stdout.trim();
|
|
@@ -24329,8 +25122,8 @@ function parseDockerTop(stdout) {
|
|
|
24329
25122
|
titles = (lines[0] ?? "").trim().toLowerCase().split(/\s+/);
|
|
24330
25123
|
processes = lines.slice(1).map((l) => l.trim().split(/\s+/));
|
|
24331
25124
|
}
|
|
24332
|
-
const idx = (
|
|
24333
|
-
for (const c of
|
|
25125
|
+
const idx = (candidates2) => {
|
|
25126
|
+
for (const c of candidates2) {
|
|
24334
25127
|
const i = titles.indexOf(c);
|
|
24335
25128
|
if (i !== -1) return i;
|
|
24336
25129
|
}
|
|
@@ -24408,7 +25201,7 @@ function registerPs(program2) {
|
|
|
24408
25201
|
const containerName = `olam-${worldId}-devbox`;
|
|
24409
25202
|
let watchInterval;
|
|
24410
25203
|
function fetchAndPrint() {
|
|
24411
|
-
const result =
|
|
25204
|
+
const result = spawnSync15(
|
|
24412
25205
|
"docker",
|
|
24413
25206
|
["top", containerName, "pid", "user", "pcpu", "pmem", "stime", "stat", "cmd"],
|
|
24414
25207
|
{ encoding: "utf-8", timeout: 3e3 }
|
|
@@ -24901,7 +25694,7 @@ init_output();
|
|
|
24901
25694
|
import * as fs39 from "node:fs";
|
|
24902
25695
|
import * as os22 from "node:os";
|
|
24903
25696
|
import * as path42 from "node:path";
|
|
24904
|
-
import { spawnSync as
|
|
25697
|
+
import { spawnSync as spawnSync16 } from "node:child_process";
|
|
24905
25698
|
import ora8 from "ora";
|
|
24906
25699
|
|
|
24907
25700
|
// src/commands/refresh-helpers.ts
|
|
@@ -24945,7 +25738,7 @@ var RESTART_TIMEOUT_S = 30;
|
|
|
24945
25738
|
var HEALTH_POLL_MS = 500;
|
|
24946
25739
|
var HEALTH_TIMEOUT_MS = 3e4;
|
|
24947
25740
|
function docker(args) {
|
|
24948
|
-
const result =
|
|
25741
|
+
const result = spawnSync16("docker", args, {
|
|
24949
25742
|
encoding: "utf-8",
|
|
24950
25743
|
stdio: ["ignore", "pipe", "pipe"]
|
|
24951
25744
|
});
|
|
@@ -25293,9 +26086,9 @@ function registerDiagnose(program2) {
|
|
|
25293
26086
|
}
|
|
25294
26087
|
|
|
25295
26088
|
// src/lib/health-probes.ts
|
|
25296
|
-
import { spawnSync as
|
|
25297
|
-
import { existsSync as
|
|
25298
|
-
import { homedir as
|
|
26089
|
+
import { spawnSync as spawnSync17 } from "node:child_process";
|
|
26090
|
+
import { existsSync as existsSync45, readdirSync as readdirSync11, readFileSync as readFileSync32, statSync as statSync14 } from "node:fs";
|
|
26091
|
+
import { homedir as homedir24 } from "node:os";
|
|
25299
26092
|
import path44 from "node:path";
|
|
25300
26093
|
|
|
25301
26094
|
// src/lib/kg-caps.ts
|
|
@@ -25305,7 +26098,7 @@ var HARD_CAP_BYTES = 5e9;
|
|
|
25305
26098
|
// src/lib/health-probes.ts
|
|
25306
26099
|
var HEALTH_TIMEOUT_MS2 = 5e3;
|
|
25307
26100
|
var defaultDockerExec2 = (cmd, args) => {
|
|
25308
|
-
const r =
|
|
26101
|
+
const r = spawnSync17(cmd, [...args], {
|
|
25309
26102
|
encoding: "utf-8",
|
|
25310
26103
|
stdio: ["ignore", "pipe", "pipe"]
|
|
25311
26104
|
});
|
|
@@ -25391,7 +26184,7 @@ function isHostCpHealthEnvelope(body) {
|
|
|
25391
26184
|
}
|
|
25392
26185
|
async function probeAuthVault(olamHomeOverride) {
|
|
25393
26186
|
const vaultPath = resolveAccountsPath(olamHomeOverride);
|
|
25394
|
-
if (!
|
|
26187
|
+
if (!existsSync45(vaultPath)) {
|
|
25395
26188
|
return {
|
|
25396
26189
|
ok: false,
|
|
25397
26190
|
message: `auth vault missing at ${vaultPath}`,
|
|
@@ -25400,7 +26193,7 @@ async function probeAuthVault(olamHomeOverride) {
|
|
|
25400
26193
|
}
|
|
25401
26194
|
let parsed;
|
|
25402
26195
|
try {
|
|
25403
|
-
parsed = JSON.parse(
|
|
26196
|
+
parsed = JSON.parse(readFileSync32(vaultPath, "utf-8"));
|
|
25404
26197
|
} catch (err) {
|
|
25405
26198
|
return {
|
|
25406
26199
|
ok: false,
|
|
@@ -25430,7 +26223,7 @@ async function probeAuthVault(olamHomeOverride) {
|
|
|
25430
26223
|
function resolveAccountsPath(olamHomeOverride) {
|
|
25431
26224
|
const explicit = process.env.OLAM_AUTH_DATA_PATH;
|
|
25432
26225
|
if (explicit) return explicit;
|
|
25433
|
-
const olamHome5 = olamHomeOverride ?? process.env.OLAM_HOME ?? path44.join(
|
|
26226
|
+
const olamHome5 = olamHomeOverride ?? process.env.OLAM_HOME ?? path44.join(homedir24(), ".olam");
|
|
25434
26227
|
return path44.join(olamHome5, "auth-data", "accounts.json");
|
|
25435
26228
|
}
|
|
25436
26229
|
function effectiveState2(account, now) {
|
|
@@ -25444,7 +26237,7 @@ function effectiveState2(account, now) {
|
|
|
25444
26237
|
return persisted;
|
|
25445
26238
|
}
|
|
25446
26239
|
async function probeKgStorage(olamHomeOverride) {
|
|
25447
|
-
const olamHome5 = olamHomeOverride ?? process.env.OLAM_HOME ?? path44.join(
|
|
26240
|
+
const olamHome5 = olamHomeOverride ?? process.env.OLAM_HOME ?? path44.join(homedir24(), ".olam");
|
|
25448
26241
|
const kgRoot3 = path44.join(olamHome5, "kg");
|
|
25449
26242
|
const worldsRoot3 = path44.join(olamHome5, "worlds");
|
|
25450
26243
|
const kgBytes = enumerateGraphifyOut(kgRoot3, "pristine");
|
|
@@ -25466,7 +26259,7 @@ async function probeKgStorage(olamHomeOverride) {
|
|
|
25466
26259
|
return { ok: true, message: `KG storage ${formatBytes4(totalBytes)} (under cap)` };
|
|
25467
26260
|
}
|
|
25468
26261
|
function enumerateGraphifyOut(root, layout) {
|
|
25469
|
-
if (!
|
|
26262
|
+
if (!existsSync45(root)) return 0;
|
|
25470
26263
|
let total = 0;
|
|
25471
26264
|
let entries;
|
|
25472
26265
|
try {
|
|
@@ -25495,7 +26288,7 @@ function enumerateGraphifyOut(root, layout) {
|
|
|
25495
26288
|
return total;
|
|
25496
26289
|
}
|
|
25497
26290
|
function dirSizeBytes(dir) {
|
|
25498
|
-
if (!
|
|
26291
|
+
if (!existsSync45(dir)) return 0;
|
|
25499
26292
|
let total = 0;
|
|
25500
26293
|
const stack = [dir];
|
|
25501
26294
|
while (stack.length > 0) {
|
|
@@ -25514,7 +26307,7 @@ function dirSizeBytes(dir) {
|
|
|
25514
26307
|
continue;
|
|
25515
26308
|
}
|
|
25516
26309
|
try {
|
|
25517
|
-
total +=
|
|
26310
|
+
total += statSync14(full).size;
|
|
25518
26311
|
} catch {
|
|
25519
26312
|
}
|
|
25520
26313
|
}
|
|
@@ -25751,20 +26544,20 @@ function registerCompletion(program2) {
|
|
|
25751
26544
|
// src/commands/setup.ts
|
|
25752
26545
|
init_cli_version();
|
|
25753
26546
|
import { spawn as spawn5 } from "node:child_process";
|
|
25754
|
-
import { existsSync as
|
|
25755
|
-
import { homedir as
|
|
26547
|
+
import { existsSync as existsSync47 } from "node:fs";
|
|
26548
|
+
import { homedir as homedir25 } from "node:os";
|
|
25756
26549
|
import path46 from "node:path";
|
|
25757
26550
|
import { createInterface as createInterface3 } from "node:readline";
|
|
25758
26551
|
|
|
25759
26552
|
// src/lib/shell-rc.ts
|
|
25760
|
-
import { copyFileSync as copyFileSync5, existsSync as
|
|
26553
|
+
import { copyFileSync as copyFileSync5, existsSync as existsSync46, readFileSync as readFileSync33, renameSync as renameSync5, writeFileSync as writeFileSync21 } from "node:fs";
|
|
25761
26554
|
import path45 from "node:path";
|
|
25762
26555
|
function appendIdempotent(opts) {
|
|
25763
26556
|
const { rcPath, marker, contentLine, clock = () => /* @__PURE__ */ new Date() } = opts;
|
|
25764
|
-
if (!
|
|
26557
|
+
if (!existsSync46(rcPath)) {
|
|
25765
26558
|
return { status: "no-rc-file", backupPath: null };
|
|
25766
26559
|
}
|
|
25767
|
-
const content =
|
|
26560
|
+
const content = readFileSync33(rcPath, "utf-8");
|
|
25768
26561
|
if (content.includes(marker)) {
|
|
25769
26562
|
return { status: "already-present", backupPath: null };
|
|
25770
26563
|
}
|
|
@@ -25775,8 +26568,8 @@ function appendIdempotent(opts) {
|
|
|
25775
26568
|
const trailing = contentLine.endsWith("\n") ? "" : "\n";
|
|
25776
26569
|
const nextContent = `${content}${separator}${contentLine}${trailing}`;
|
|
25777
26570
|
const tmpPath = `${rcPath}.olam-tmp.${process.pid}`;
|
|
25778
|
-
|
|
25779
|
-
|
|
26571
|
+
writeFileSync21(tmpPath, nextContent, { encoding: "utf-8" });
|
|
26572
|
+
renameSync5(tmpPath, rcPath);
|
|
25780
26573
|
return { status: "appended", backupPath };
|
|
25781
26574
|
}
|
|
25782
26575
|
function resolveShellRc(home, shellEnv) {
|
|
@@ -25795,10 +26588,10 @@ var NEXT_STEPS_DOCS = [
|
|
|
25795
26588
|
"docs/architecture/manifest-spec.md \u2014 per-repo .adb.yaml schema",
|
|
25796
26589
|
"docs/architecture/config-spec.md \u2014 workspace .olam/config.yaml schema"
|
|
25797
26590
|
];
|
|
25798
|
-
var defaultSpawn = (cmd, args) => new Promise((
|
|
26591
|
+
var defaultSpawn = (cmd, args) => new Promise((resolve14) => {
|
|
25799
26592
|
const child = spawn5(cmd, [...args], { stdio: "inherit" });
|
|
25800
|
-
child.on("exit", (code) =>
|
|
25801
|
-
child.on("error", () =>
|
|
26593
|
+
child.on("exit", (code) => resolve14({ status: code }));
|
|
26594
|
+
child.on("error", () => resolve14({ status: 1 }));
|
|
25802
26595
|
});
|
|
25803
26596
|
var defaultPrompt = (question, defaultYes) => {
|
|
25804
26597
|
if (!process.stdin.isTTY) {
|
|
@@ -25808,18 +26601,18 @@ var defaultPrompt = (question, defaultYes) => {
|
|
|
25808
26601
|
);
|
|
25809
26602
|
return Promise.resolve(defaultYes);
|
|
25810
26603
|
}
|
|
25811
|
-
return new Promise((
|
|
26604
|
+
return new Promise((resolve14) => {
|
|
25812
26605
|
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
25813
26606
|
const suffix = defaultYes ? " [Y/n]: " : " [y/N]: ";
|
|
25814
26607
|
rl.question(`${question}${suffix}`, (answer) => {
|
|
25815
26608
|
rl.close();
|
|
25816
26609
|
const t = answer.trim().toLowerCase();
|
|
25817
|
-
if (t === "")
|
|
25818
|
-
else if (t === "y" || t === "yes")
|
|
25819
|
-
else if (t === "n" || t === "no")
|
|
25820
|
-
else
|
|
26610
|
+
if (t === "") resolve14(defaultYes);
|
|
26611
|
+
else if (t === "y" || t === "yes") resolve14(true);
|
|
26612
|
+
else if (t === "n" || t === "no") resolve14(false);
|
|
26613
|
+
else resolve14(defaultYes);
|
|
25821
26614
|
});
|
|
25822
|
-
rl.on("close", () =>
|
|
26615
|
+
rl.on("close", () => resolve14(defaultYes));
|
|
25823
26616
|
});
|
|
25824
26617
|
};
|
|
25825
26618
|
async function phase1SystemCheck(deps) {
|
|
@@ -25847,9 +26640,9 @@ function parseNodeMajor(version) {
|
|
|
25847
26640
|
const n = Number.parseInt(m[1], 10);
|
|
25848
26641
|
return Number.isFinite(n) ? n : null;
|
|
25849
26642
|
}
|
|
25850
|
-
function phaseFromProbe(
|
|
25851
|
-
if (
|
|
25852
|
-
return { ok: false, message:
|
|
26643
|
+
function phaseFromProbe(probe2) {
|
|
26644
|
+
if (probe2.ok) return { ok: true, message: probe2.message };
|
|
26645
|
+
return { ok: false, message: probe2.message, remedy: probe2.remedy };
|
|
25853
26646
|
}
|
|
25854
26647
|
async function phase2CliSanity(deps) {
|
|
25855
26648
|
const version = deps.olamCliVersion ?? safeReadCliVersion();
|
|
@@ -25885,7 +26678,7 @@ async function phase4ShellInit(opts, deps) {
|
|
|
25885
26678
|
if (opts.skipShellInit) {
|
|
25886
26679
|
return { ok: true, skipped: true, message: "skipped via --skip-shell-init" };
|
|
25887
26680
|
}
|
|
25888
|
-
const home = deps.home ??
|
|
26681
|
+
const home = deps.home ?? homedir25();
|
|
25889
26682
|
const shellEnv = deps.shellEnv ?? process.env.SHELL;
|
|
25890
26683
|
const rcPath = resolveShellRc(home, shellEnv);
|
|
25891
26684
|
if (rcPath === null) {
|
|
@@ -25922,7 +26715,7 @@ async function phase5InitProject(opts, deps) {
|
|
|
25922
26715
|
const cwd = deps.cwd ?? process.cwd();
|
|
25923
26716
|
const projectRoot = findProjectRoot(cwd);
|
|
25924
26717
|
const configPath = path46.join(projectRoot, ".olam", "config.yaml");
|
|
25925
|
-
if (
|
|
26718
|
+
if (existsSync47(configPath)) {
|
|
25926
26719
|
return { ok: true, message: `${configPath} present (no change)` };
|
|
25927
26720
|
}
|
|
25928
26721
|
const promptFn = deps.prompt ?? defaultPrompt;
|
|
@@ -26788,18 +27581,18 @@ function registerWorldUpgrade(program2) {
|
|
|
26788
27581
|
|
|
26789
27582
|
// src/commands/mcp/serve.ts
|
|
26790
27583
|
init_output();
|
|
26791
|
-
import { existsSync as
|
|
26792
|
-
import { dirname as
|
|
27584
|
+
import { existsSync as existsSync52 } from "node:fs";
|
|
27585
|
+
import { dirname as dirname26, resolve as resolve13 } from "node:path";
|
|
26793
27586
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
26794
|
-
var here =
|
|
27587
|
+
var here = dirname26(fileURLToPath6(import.meta.url));
|
|
26795
27588
|
var BUNDLE_PATH_CANDIDATES = [
|
|
26796
27589
|
// bundled (`dist/index.js` after bundle-cli.mjs) — sibling
|
|
26797
|
-
|
|
27590
|
+
resolve13(here, "mcp-server.js"),
|
|
26798
27591
|
// dev / tsc-only (`dist/commands/mcp/serve.js`) — up two levels
|
|
26799
|
-
|
|
27592
|
+
resolve13(here, "..", "..", "mcp-server.js")
|
|
26800
27593
|
];
|
|
26801
|
-
function resolveBundlePath(
|
|
26802
|
-
return
|
|
27594
|
+
function resolveBundlePath(candidates2 = BUNDLE_PATH_CANDIDATES, exists = existsSync52) {
|
|
27595
|
+
return candidates2.find(exists) ?? null;
|
|
26803
27596
|
}
|
|
26804
27597
|
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
27598
|
function registerMcpServe(cmd) {
|
|
@@ -26820,14 +27613,14 @@ function registerMcpServe(cmd) {
|
|
|
26820
27613
|
init_output();
|
|
26821
27614
|
|
|
26822
27615
|
// src/commands/mcp/install-shared.ts
|
|
26823
|
-
import { spawnSync as
|
|
27616
|
+
import { spawnSync as spawnSync18 } from "node:child_process";
|
|
26824
27617
|
var DEFAULT_CLAUDE_SHELL_DEPS = {
|
|
26825
|
-
spawn:
|
|
27618
|
+
spawn: spawnSync18,
|
|
26826
27619
|
log: (msg) => console.log(msg)
|
|
26827
27620
|
};
|
|
26828
27621
|
function isOnPath(deps, bin) {
|
|
26829
|
-
const
|
|
26830
|
-
const result = deps.spawn(
|
|
27622
|
+
const probe2 = process.platform === "win32" ? "where" : "which";
|
|
27623
|
+
const result = deps.spawn(probe2, [bin], {
|
|
26831
27624
|
encoding: "utf8",
|
|
26832
27625
|
stdio: ["ignore", "pipe", "ignore"]
|
|
26833
27626
|
});
|
|
@@ -26838,6 +27631,10 @@ function normaliseScope(raw) {
|
|
|
26838
27631
|
return value === "user" || value === "project" || value === "local" ? value : null;
|
|
26839
27632
|
}
|
|
26840
27633
|
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.";
|
|
27634
|
+
var NOT_INSTALLED_PATTERN = /no\s+(?:\S+\s+)*mcp\s+server\s+found/i;
|
|
27635
|
+
function looksLikeNotInstalled(text) {
|
|
27636
|
+
return NOT_INSTALLED_PATTERN.test(text);
|
|
27637
|
+
}
|
|
26841
27638
|
|
|
26842
27639
|
// src/commands/mcp/install.ts
|
|
26843
27640
|
var NPM_PACKAGE_NAME = "@pleri/olam-cli";
|
|
@@ -26889,10 +27686,6 @@ function registerMcpInstall(cmd) {
|
|
|
26889
27686
|
|
|
26890
27687
|
// src/commands/mcp/uninstall.ts
|
|
26891
27688
|
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
27689
|
function buildClaudeMcpRemoveArgs(scope) {
|
|
26897
27690
|
return {
|
|
26898
27691
|
command: "claude",
|
|
@@ -27043,7 +27836,7 @@ function registerMcpLogin(cmd) {
|
|
|
27043
27836
|
init_output();
|
|
27044
27837
|
import * as readline2 from "node:readline";
|
|
27045
27838
|
async function readTokenSilent(prompt) {
|
|
27046
|
-
return new Promise((
|
|
27839
|
+
return new Promise((resolve14, reject) => {
|
|
27047
27840
|
const rl = readline2.createInterface({
|
|
27048
27841
|
input: process.stdin,
|
|
27049
27842
|
output: process.stdout,
|
|
@@ -27061,7 +27854,7 @@ async function readTokenSilent(prompt) {
|
|
|
27061
27854
|
process.stdin.removeListener("data", onData);
|
|
27062
27855
|
process.stdout.write("\n");
|
|
27063
27856
|
rl.close();
|
|
27064
|
-
|
|
27857
|
+
resolve14(token);
|
|
27065
27858
|
} else if (char === "") {
|
|
27066
27859
|
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
27067
27860
|
process.stdin.removeListener("data", onData);
|
|
@@ -27336,7 +28129,7 @@ async function discoverMcpSources(repoPaths) {
|
|
|
27336
28129
|
import { spawn as spawn7 } from "node:child_process";
|
|
27337
28130
|
var VALIDATION_TIMEOUT_MS = 5e3;
|
|
27338
28131
|
async function validateMcpEntry(entry) {
|
|
27339
|
-
return new Promise((
|
|
28132
|
+
return new Promise((resolve14) => {
|
|
27340
28133
|
let stdout = "";
|
|
27341
28134
|
let timedOut = false;
|
|
27342
28135
|
let child;
|
|
@@ -27346,7 +28139,7 @@ async function validateMcpEntry(entry) {
|
|
|
27346
28139
|
env: { ...process.env, ...entry.env ?? {} }
|
|
27347
28140
|
});
|
|
27348
28141
|
} catch (err) {
|
|
27349
|
-
|
|
28142
|
+
resolve14({
|
|
27350
28143
|
name: entry.name,
|
|
27351
28144
|
validated: false,
|
|
27352
28145
|
reason: err instanceof Error ? err.message : "spawn failed"
|
|
@@ -27363,11 +28156,11 @@ async function validateMcpEntry(entry) {
|
|
|
27363
28156
|
child.on("close", (code) => {
|
|
27364
28157
|
clearTimeout(timer);
|
|
27365
28158
|
if (timedOut) {
|
|
27366
|
-
|
|
28159
|
+
resolve14({ name: entry.name, validated: false, reason: "timeout (5s)" });
|
|
27367
28160
|
return;
|
|
27368
28161
|
}
|
|
27369
28162
|
const validated = code === 0 && stdout.trim().length > 0;
|
|
27370
|
-
|
|
28163
|
+
resolve14({
|
|
27371
28164
|
name: entry.name,
|
|
27372
28165
|
validated,
|
|
27373
28166
|
reason: validated ? "ok" : `exit code ${code ?? "null"}`
|
|
@@ -27375,7 +28168,7 @@ async function validateMcpEntry(entry) {
|
|
|
27375
28168
|
});
|
|
27376
28169
|
child.on("error", (err) => {
|
|
27377
28170
|
clearTimeout(timer);
|
|
27378
|
-
|
|
28171
|
+
resolve14({ name: entry.name, validated: false, reason: err.message });
|
|
27379
28172
|
});
|
|
27380
28173
|
});
|
|
27381
28174
|
}
|
|
@@ -27390,11 +28183,11 @@ async function multiSelectPicker(entries) {
|
|
|
27390
28183
|
);
|
|
27391
28184
|
});
|
|
27392
28185
|
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((
|
|
28186
|
+
const answer = await new Promise((resolve14) => {
|
|
27394
28187
|
const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
|
|
27395
28188
|
rl.question("> ", (ans) => {
|
|
27396
28189
|
rl.close();
|
|
27397
|
-
|
|
28190
|
+
resolve14(ans.trim());
|
|
27398
28191
|
});
|
|
27399
28192
|
});
|
|
27400
28193
|
if (!answer || answer === "") return [];
|
|
@@ -27421,21 +28214,21 @@ function registerMcpImport(cmd) {
|
|
|
27421
28214
|
}
|
|
27422
28215
|
printInfo("Sources", sources.length > 0 ? sources.join(", ") : "none");
|
|
27423
28216
|
printInfo("Found", `${entries.length} MCP server(s) in ${Math.round(durationMs)}ms`);
|
|
27424
|
-
let
|
|
28217
|
+
let candidates2 = entries;
|
|
27425
28218
|
let skippedCount = 0;
|
|
27426
28219
|
if (!opts.reimport) {
|
|
27427
28220
|
const filtered = entries.filter((e) => !existingIds.has(e.name));
|
|
27428
28221
|
skippedCount = entries.length - filtered.length;
|
|
27429
|
-
|
|
28222
|
+
candidates2 = filtered;
|
|
27430
28223
|
}
|
|
27431
28224
|
if (skippedCount > 0) {
|
|
27432
28225
|
console.log(pc29.dim(`skipped: ${skippedCount} already registered`));
|
|
27433
28226
|
}
|
|
27434
|
-
if (
|
|
28227
|
+
if (candidates2.length === 0) {
|
|
27435
28228
|
console.log(pc29.dim("Nothing new to import. Use --reimport to force."));
|
|
27436
28229
|
return;
|
|
27437
28230
|
}
|
|
27438
|
-
const selected = await multiSelectPicker(
|
|
28231
|
+
const selected = await multiSelectPicker(candidates2);
|
|
27439
28232
|
if (selected.length === 0) {
|
|
27440
28233
|
console.log(pc29.dim("No servers selected."));
|
|
27441
28234
|
return;
|
|
@@ -27535,47 +28328,584 @@ function registerMcp(program2) {
|
|
|
27535
28328
|
registerMcpRevoke(mcp);
|
|
27536
28329
|
}
|
|
27537
28330
|
|
|
27538
|
-
// src/commands/
|
|
27539
|
-
init_storage_paths();
|
|
27540
|
-
init_workspace_name();
|
|
28331
|
+
// src/commands/memory/start.ts
|
|
27541
28332
|
init_output();
|
|
27542
|
-
import {
|
|
27543
|
-
import
|
|
27544
|
-
import
|
|
28333
|
+
import { spawn as spawn8 } from "node:child_process";
|
|
28334
|
+
import { existsSync as existsSync54, mkdirSync as mkdirSync30, openSync as openSync3, readFileSync as readFileSync38, writeFileSync as writeFileSync24 } from "node:fs";
|
|
28335
|
+
import { join as join52 } from "node:path";
|
|
28336
|
+
|
|
28337
|
+
// src/commands/memory/_paths.ts
|
|
28338
|
+
import { homedir as homedir30 } from "node:os";
|
|
28339
|
+
import { join as join51, dirname as dirname27 } from "node:path";
|
|
28340
|
+
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
28341
|
+
var OLAM_HOME = join51(homedir30(), ".olam");
|
|
28342
|
+
var OLAM_BIN_DIR = join51(OLAM_HOME, "bin");
|
|
28343
|
+
var III_BINARY_PATH = join51(OLAM_BIN_DIR, "iii");
|
|
28344
|
+
var MEMORY_PID_PATH = join51(OLAM_HOME, "memory.pid");
|
|
28345
|
+
var MEMORY_LOG_PATH = join51(OLAM_HOME, "memory-service.log");
|
|
28346
|
+
var MEMORY_DATA_DIR = join51(OLAM_HOME, "memory-data");
|
|
28347
|
+
var MEMORY_REST_PORT = 3111;
|
|
28348
|
+
var MEMORY_LIVEZ_URL = `http://localhost:${MEMORY_REST_PORT}/agentmemory/livez`;
|
|
28349
|
+
var here2 = dirname27(fileURLToPath7(import.meta.url));
|
|
28350
|
+
var candidates = [
|
|
28351
|
+
// Workspace dev: packages/cli/src/commands/memory/_paths.ts (run via tsx) — unlikely
|
|
28352
|
+
// Workspace built: packages/cli/dist/commands/memory/_paths.js → packages/cli → packages/memory-service
|
|
28353
|
+
join51(here2, "..", "..", "..", "..", "memory-service"),
|
|
28354
|
+
// Bundled: dist/index.js (esbuild) → packages/cli → packages/memory-service
|
|
28355
|
+
join51(here2, "..", "..", "memory-service"),
|
|
28356
|
+
// Fallback: cwd-relative
|
|
28357
|
+
join51(process.cwd(), "packages", "memory-service")
|
|
28358
|
+
];
|
|
28359
|
+
var MEMORY_SERVICE_CANDIDATES = candidates;
|
|
27545
28360
|
|
|
27546
|
-
// src/commands/
|
|
27547
|
-
|
|
27548
|
-
|
|
27549
|
-
|
|
27550
|
-
|
|
27551
|
-
|
|
27552
|
-
|
|
27553
|
-
|
|
27554
|
-
|
|
28361
|
+
// src/commands/memory/start.ts
|
|
28362
|
+
var READINESS_TIMEOUT_MS = 3e4;
|
|
28363
|
+
var READINESS_POLL_MS = 500;
|
|
28364
|
+
function resolveMemoryServiceDir() {
|
|
28365
|
+
for (const c of MEMORY_SERVICE_CANDIDATES) {
|
|
28366
|
+
if (existsSync54(c)) return c;
|
|
28367
|
+
}
|
|
28368
|
+
throw new Error(
|
|
28369
|
+
`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.`
|
|
28370
|
+
);
|
|
27555
28371
|
}
|
|
27556
|
-
function
|
|
27557
|
-
|
|
28372
|
+
function resolveAgentMemoryBin(serviceDir) {
|
|
28373
|
+
const candidates2 = [
|
|
28374
|
+
join52(serviceDir, "node_modules", ".bin", "agentmemory"),
|
|
28375
|
+
join52(serviceDir, "..", "..", "node_modules", ".bin", "agentmemory"),
|
|
28376
|
+
join52(serviceDir, "..", "..", "..", "node_modules", ".bin", "agentmemory")
|
|
28377
|
+
];
|
|
28378
|
+
for (const c of candidates2) {
|
|
28379
|
+
if (existsSync54(c)) return c;
|
|
28380
|
+
}
|
|
28381
|
+
throw new Error(
|
|
28382
|
+
`Could not find agentmemory bin. Searched: ${candidates2.join(", ")}. Run 'npm install' from the repo root.`
|
|
28383
|
+
);
|
|
27558
28384
|
}
|
|
27559
|
-
function
|
|
27560
|
-
|
|
28385
|
+
function isProcessAlive(pid) {
|
|
28386
|
+
try {
|
|
28387
|
+
process.kill(pid, 0);
|
|
28388
|
+
return true;
|
|
28389
|
+
} catch {
|
|
28390
|
+
return false;
|
|
28391
|
+
}
|
|
27561
28392
|
}
|
|
27562
|
-
function
|
|
27563
|
-
if (!
|
|
27564
|
-
|
|
27565
|
-
const
|
|
27566
|
-
|
|
27567
|
-
|
|
27568
|
-
|
|
27569
|
-
|
|
27570
|
-
|
|
27571
|
-
|
|
27572
|
-
|
|
27573
|
-
|
|
27574
|
-
|
|
27575
|
-
|
|
27576
|
-
|
|
27577
|
-
|
|
27578
|
-
|
|
28393
|
+
function readPidFromFile() {
|
|
28394
|
+
if (!existsSync54(MEMORY_PID_PATH)) return null;
|
|
28395
|
+
const raw = readFileSync38(MEMORY_PID_PATH, "utf8").trim();
|
|
28396
|
+
const pid = parseInt(raw, 10);
|
|
28397
|
+
if (!Number.isFinite(pid) || pid <= 0) return null;
|
|
28398
|
+
return pid;
|
|
28399
|
+
}
|
|
28400
|
+
async function probeLivez(secret, signal) {
|
|
28401
|
+
try {
|
|
28402
|
+
const resp = await fetch(MEMORY_LIVEZ_URL, {
|
|
28403
|
+
headers: { authorization: `Bearer ${secret}` },
|
|
28404
|
+
signal
|
|
28405
|
+
});
|
|
28406
|
+
if (!resp.ok) return false;
|
|
28407
|
+
const body = await resp.json();
|
|
28408
|
+
return body.status === "ok";
|
|
28409
|
+
} catch {
|
|
28410
|
+
return false;
|
|
28411
|
+
}
|
|
28412
|
+
}
|
|
28413
|
+
async function waitForReady(secret) {
|
|
28414
|
+
const deadline = Date.now() + READINESS_TIMEOUT_MS;
|
|
28415
|
+
while (Date.now() < deadline) {
|
|
28416
|
+
if (await probeLivez(secret)) return true;
|
|
28417
|
+
await new Promise((r) => setTimeout(r, READINESS_POLL_MS));
|
|
28418
|
+
}
|
|
28419
|
+
return false;
|
|
28420
|
+
}
|
|
28421
|
+
async function runMemoryStart() {
|
|
28422
|
+
printHeader("olam memory start");
|
|
28423
|
+
if (!existsSync54(III_BINARY_PATH)) {
|
|
28424
|
+
printError(
|
|
28425
|
+
`iii binary missing at ${III_BINARY_PATH}. Run: node packages/memory-service/scripts/ensure-iii-engine.mjs`
|
|
28426
|
+
);
|
|
28427
|
+
return 1;
|
|
28428
|
+
}
|
|
28429
|
+
printInfo("iii binary", III_BINARY_PATH);
|
|
28430
|
+
const secret = ensureMemorySecret();
|
|
28431
|
+
printInfo("secret", "~/.olam/memory-secret (0600)");
|
|
28432
|
+
const serviceDir = resolveMemoryServiceDir();
|
|
28433
|
+
const agentmemoryBin = resolveAgentMemoryBin(serviceDir);
|
|
28434
|
+
printInfo("agentmemory", agentmemoryBin);
|
|
28435
|
+
const existingPid = readPidFromFile();
|
|
28436
|
+
if (existingPid !== null && isProcessAlive(existingPid)) {
|
|
28437
|
+
const ready2 = await probeLivez(secret);
|
|
28438
|
+
if (ready2) {
|
|
28439
|
+
printSuccess(`already running (pid ${existingPid}); /agentmemory/livez ok`);
|
|
28440
|
+
return 0;
|
|
28441
|
+
}
|
|
28442
|
+
printError(
|
|
28443
|
+
`pid ${existingPid} is alive but /agentmemory/livez isn't responding. Run 'olam memory stop' to clear, then retry.`
|
|
28444
|
+
);
|
|
28445
|
+
return 1;
|
|
28446
|
+
}
|
|
28447
|
+
mkdirSync30(OLAM_HOME, { recursive: true });
|
|
28448
|
+
mkdirSync30(MEMORY_DATA_DIR, { recursive: true });
|
|
28449
|
+
const logFd = openSync3(MEMORY_LOG_PATH, "a");
|
|
28450
|
+
const child = spawn8(agentmemoryBin, [], {
|
|
28451
|
+
cwd: OLAM_HOME,
|
|
28452
|
+
env: {
|
|
28453
|
+
...process.env,
|
|
28454
|
+
AGENTMEMORY_SECRET: secret,
|
|
28455
|
+
PATH: `${OLAM_BIN_DIR}:${process.env.PATH ?? ""}`
|
|
28456
|
+
},
|
|
28457
|
+
stdio: ["ignore", logFd, logFd],
|
|
28458
|
+
detached: true
|
|
28459
|
+
});
|
|
28460
|
+
child.unref();
|
|
28461
|
+
if (child.pid === void 0) {
|
|
28462
|
+
printError("spawn returned no pid (process failed to start)");
|
|
28463
|
+
return 1;
|
|
28464
|
+
}
|
|
28465
|
+
writeFileSync24(MEMORY_PID_PATH, `${child.pid}
|
|
28466
|
+
`, { mode: 420 });
|
|
28467
|
+
printInfo("pid", `${child.pid}`);
|
|
28468
|
+
printInfo("readiness", `polling ${MEMORY_LIVEZ_URL}`);
|
|
28469
|
+
const ready = await waitForReady(secret);
|
|
28470
|
+
if (!ready) {
|
|
28471
|
+
printError(
|
|
28472
|
+
`agentmemory failed to come up within ${READINESS_TIMEOUT_MS / 1e3}s. Inspect ${MEMORY_LOG_PATH} for engine errors.`
|
|
28473
|
+
);
|
|
28474
|
+
return 1;
|
|
28475
|
+
}
|
|
28476
|
+
printSuccess(`memory service ready (pid ${child.pid}; logs at ${MEMORY_LOG_PATH})`);
|
|
28477
|
+
return 0;
|
|
28478
|
+
}
|
|
28479
|
+
function registerMemoryStart(cmd) {
|
|
28480
|
+
cmd.command("start").description("Start the host agent-memory process (idempotent; polls livez until ready)").action(async () => {
|
|
28481
|
+
const rc = await runMemoryStart();
|
|
28482
|
+
if (rc !== 0) process.exitCode = rc;
|
|
28483
|
+
});
|
|
28484
|
+
}
|
|
28485
|
+
|
|
28486
|
+
// src/commands/memory/stop.ts
|
|
28487
|
+
init_output();
|
|
28488
|
+
import { existsSync as existsSync55, readFileSync as readFileSync39, unlinkSync as unlinkSync9 } from "node:fs";
|
|
28489
|
+
var SIGTERM_GRACE_MS = 1e4;
|
|
28490
|
+
var POLL_MS = 250;
|
|
28491
|
+
function isAlive(pid) {
|
|
28492
|
+
try {
|
|
28493
|
+
process.kill(pid, 0);
|
|
28494
|
+
return true;
|
|
28495
|
+
} catch {
|
|
28496
|
+
return false;
|
|
28497
|
+
}
|
|
28498
|
+
}
|
|
28499
|
+
async function runMemoryStop() {
|
|
28500
|
+
printHeader("olam memory stop");
|
|
28501
|
+
if (!existsSync55(MEMORY_PID_PATH)) {
|
|
28502
|
+
printSuccess("no pidfile present (nothing to stop)");
|
|
28503
|
+
return 0;
|
|
28504
|
+
}
|
|
28505
|
+
const pid = parseInt(readFileSync39(MEMORY_PID_PATH, "utf8").trim(), 10);
|
|
28506
|
+
if (!Number.isFinite(pid) || pid <= 0) {
|
|
28507
|
+
printWarning(`pidfile contained invalid value; removing`);
|
|
28508
|
+
unlinkSync9(MEMORY_PID_PATH);
|
|
28509
|
+
return 0;
|
|
28510
|
+
}
|
|
28511
|
+
if (!isAlive(pid)) {
|
|
28512
|
+
printSuccess(`pid ${pid} is not running (stale pidfile); cleaned up`);
|
|
28513
|
+
unlinkSync9(MEMORY_PID_PATH);
|
|
28514
|
+
return 0;
|
|
28515
|
+
}
|
|
28516
|
+
printInfo("pid", `${pid}`);
|
|
28517
|
+
try {
|
|
28518
|
+
process.kill(pid, "SIGTERM");
|
|
28519
|
+
} catch (err) {
|
|
28520
|
+
printWarning(`SIGTERM to pid ${pid} failed: ${err.message}`);
|
|
28521
|
+
}
|
|
28522
|
+
const deadline = Date.now() + SIGTERM_GRACE_MS;
|
|
28523
|
+
while (Date.now() < deadline) {
|
|
28524
|
+
if (!isAlive(pid)) break;
|
|
28525
|
+
await new Promise((r) => setTimeout(r, POLL_MS));
|
|
28526
|
+
}
|
|
28527
|
+
if (isAlive(pid)) {
|
|
28528
|
+
printWarning(`pid ${pid} did not exit within ${SIGTERM_GRACE_MS / 1e3}s; sending SIGKILL`);
|
|
28529
|
+
try {
|
|
28530
|
+
process.kill(pid, "SIGKILL");
|
|
28531
|
+
} catch (err) {
|
|
28532
|
+
printWarning(`SIGKILL to pid ${pid} failed: ${err.message}`);
|
|
28533
|
+
}
|
|
28534
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
28535
|
+
}
|
|
28536
|
+
if (existsSync55(MEMORY_PID_PATH)) {
|
|
28537
|
+
unlinkSync9(MEMORY_PID_PATH);
|
|
28538
|
+
}
|
|
28539
|
+
printSuccess(`stopped (pid ${pid})`);
|
|
28540
|
+
return 0;
|
|
28541
|
+
}
|
|
28542
|
+
function registerMemoryStop(cmd) {
|
|
28543
|
+
cmd.command("stop").description("Stop the host agent-memory process (SIGTERM \u2192 SIGKILL after 10s; idempotent)").action(async () => {
|
|
28544
|
+
const rc = await runMemoryStop();
|
|
28545
|
+
if (rc !== 0) process.exitCode = rc;
|
|
28546
|
+
});
|
|
28547
|
+
}
|
|
28548
|
+
|
|
28549
|
+
// src/commands/memory/status.ts
|
|
28550
|
+
init_output();
|
|
28551
|
+
import { existsSync as existsSync56, readFileSync as readFileSync40 } from "node:fs";
|
|
28552
|
+
function isAlive2(pid) {
|
|
28553
|
+
try {
|
|
28554
|
+
process.kill(pid, 0);
|
|
28555
|
+
return true;
|
|
28556
|
+
} catch {
|
|
28557
|
+
return false;
|
|
28558
|
+
}
|
|
28559
|
+
}
|
|
28560
|
+
async function probe(secret) {
|
|
28561
|
+
try {
|
|
28562
|
+
const headers = {};
|
|
28563
|
+
if (secret) headers.authorization = `Bearer ${secret}`;
|
|
28564
|
+
const resp = await fetch(MEMORY_LIVEZ_URL, { headers });
|
|
28565
|
+
if (resp.status === 401) return "unauthorized";
|
|
28566
|
+
if (!resp.ok) return "unknown";
|
|
28567
|
+
const body = await resp.json();
|
|
28568
|
+
return body.status === "ok" ? "ok" : "unknown";
|
|
28569
|
+
} catch {
|
|
28570
|
+
return "unreachable";
|
|
28571
|
+
}
|
|
28572
|
+
}
|
|
28573
|
+
async function collectMemoryStatus() {
|
|
28574
|
+
let pid = null;
|
|
28575
|
+
if (existsSync56(MEMORY_PID_PATH)) {
|
|
28576
|
+
const raw = readFileSync40(MEMORY_PID_PATH, "utf8").trim();
|
|
28577
|
+
const parsed = parseInt(raw, 10);
|
|
28578
|
+
pid = Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
28579
|
+
}
|
|
28580
|
+
const alive = pid === null ? null : isAlive2(pid);
|
|
28581
|
+
const secret = readMemorySecretOrNull();
|
|
28582
|
+
const livez = await probe(secret);
|
|
28583
|
+
return {
|
|
28584
|
+
pid,
|
|
28585
|
+
alive,
|
|
28586
|
+
livez,
|
|
28587
|
+
secretSet: hasMemorySecret(),
|
|
28588
|
+
iiiBinary: existsSync56(III_BINARY_PATH) ? III_BINARY_PATH : null,
|
|
28589
|
+
port: MEMORY_REST_PORT
|
|
28590
|
+
};
|
|
28591
|
+
}
|
|
28592
|
+
async function runMemoryStatus(opts = {}) {
|
|
28593
|
+
const s = await collectMemoryStatus();
|
|
28594
|
+
if (opts.json) {
|
|
28595
|
+
process.stdout.write(JSON.stringify(s, null, 2) + "\n");
|
|
28596
|
+
return s.livez === "ok" ? 0 : 1;
|
|
28597
|
+
}
|
|
28598
|
+
printHeader("olam memory status");
|
|
28599
|
+
printInfo("pid", s.pid === null ? "(no pidfile)" : `${s.pid}`);
|
|
28600
|
+
printInfo("alive", s.alive === null ? "n/a" : s.alive ? "yes" : "no (stale pidfile)");
|
|
28601
|
+
printInfo("livez", s.livez);
|
|
28602
|
+
printInfo("secret", s.secretSet ? "~/.olam/memory-secret (set)" : "(missing)");
|
|
28603
|
+
printInfo("iii", s.iiiBinary ?? "(not installed)");
|
|
28604
|
+
printInfo("port", `${s.port}`);
|
|
28605
|
+
if (s.livez === "ok" && s.alive) return 0;
|
|
28606
|
+
if (s.livez === "unauthorized") {
|
|
28607
|
+
printWarning("livez returned 401 \u2014 the memory service is up but the local secret does not match. Try `olam memory secret rotate`.");
|
|
28608
|
+
return 1;
|
|
28609
|
+
}
|
|
28610
|
+
if (s.alive === false && s.pid !== null) {
|
|
28611
|
+
printWarning("pidfile points at a dead process; run `olam memory stop` to clean up");
|
|
28612
|
+
return 1;
|
|
28613
|
+
}
|
|
28614
|
+
if (!s.iiiBinary) {
|
|
28615
|
+
printWarning("iii binary missing; run `node packages/memory-service/scripts/ensure-iii-engine.mjs`");
|
|
28616
|
+
}
|
|
28617
|
+
if (!s.secretSet) {
|
|
28618
|
+
printWarning("secret missing; `olam memory start` will generate it");
|
|
28619
|
+
}
|
|
28620
|
+
return s.livez === "ok" ? 0 : 1;
|
|
28621
|
+
}
|
|
28622
|
+
function registerMemoryStatus(cmd) {
|
|
28623
|
+
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) => {
|
|
28624
|
+
const rc = await runMemoryStatus(opts);
|
|
28625
|
+
if (rc !== 0) process.exitCode = rc;
|
|
28626
|
+
});
|
|
28627
|
+
}
|
|
28628
|
+
|
|
28629
|
+
// src/commands/memory/logs.ts
|
|
28630
|
+
init_output();
|
|
28631
|
+
import { existsSync as existsSync57 } from "node:fs";
|
|
28632
|
+
import { spawn as spawn9 } from "node:child_process";
|
|
28633
|
+
async function runMemoryLogs(opts) {
|
|
28634
|
+
if (!existsSync57(MEMORY_LOG_PATH)) {
|
|
28635
|
+
printWarning(`no log at ${MEMORY_LOG_PATH} (start the service first via 'olam memory start')`);
|
|
28636
|
+
return 1;
|
|
28637
|
+
}
|
|
28638
|
+
const tailN = opts.tail ? parseInt(opts.tail, 10) : 50;
|
|
28639
|
+
if (!Number.isFinite(tailN) || tailN < 0) {
|
|
28640
|
+
printWarning(`invalid --tail value: ${opts.tail}`);
|
|
28641
|
+
return 1;
|
|
28642
|
+
}
|
|
28643
|
+
const args = ["-n", `${tailN}`];
|
|
28644
|
+
if (opts.follow) args.push("-f");
|
|
28645
|
+
args.push(MEMORY_LOG_PATH);
|
|
28646
|
+
printHeader(`olam memory logs (${opts.follow ? "follow" : `tail -n ${tailN}`})`);
|
|
28647
|
+
const child = spawn9("tail", args, { stdio: "inherit" });
|
|
28648
|
+
return new Promise((resolve14) => {
|
|
28649
|
+
child.on("exit", (code) => resolve14(code ?? 0));
|
|
28650
|
+
});
|
|
28651
|
+
}
|
|
28652
|
+
function registerMemoryLogs(cmd) {
|
|
28653
|
+
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) => {
|
|
28654
|
+
const rc = await runMemoryLogs(opts);
|
|
28655
|
+
if (rc !== 0) process.exitCode = rc;
|
|
28656
|
+
});
|
|
28657
|
+
}
|
|
28658
|
+
|
|
28659
|
+
// src/commands/memory/secret.ts
|
|
28660
|
+
import { existsSync as existsSync58 } from "node:fs";
|
|
28661
|
+
init_output();
|
|
28662
|
+
async function runMemorySecretShow() {
|
|
28663
|
+
if (!hasMemorySecret()) {
|
|
28664
|
+
printError(
|
|
28665
|
+
`secret missing at ${MEMORY_SECRET_PATH}. Run 'olam memory start' to generate it.`
|
|
28666
|
+
);
|
|
28667
|
+
return 1;
|
|
28668
|
+
}
|
|
28669
|
+
process.stdout.write(`${readMemorySecret2()}
|
|
28670
|
+
`);
|
|
28671
|
+
return 0;
|
|
28672
|
+
}
|
|
28673
|
+
async function runMemorySecretRotate() {
|
|
28674
|
+
printHeader("olam memory secret rotate");
|
|
28675
|
+
const wasRunning = existsSync58(MEMORY_PID_PATH);
|
|
28676
|
+
if (wasRunning) {
|
|
28677
|
+
printInfo("current state", "service running; will restart with new secret");
|
|
28678
|
+
const stopRc = await runMemoryStop();
|
|
28679
|
+
if (stopRc !== 0) {
|
|
28680
|
+
printError("failed to stop the running service; rotation aborted to avoid split-brain");
|
|
28681
|
+
return stopRc;
|
|
28682
|
+
}
|
|
28683
|
+
} else {
|
|
28684
|
+
printInfo("current state", "service stopped");
|
|
28685
|
+
}
|
|
28686
|
+
const fresh = rotateMemorySecret();
|
|
28687
|
+
printSuccess(`secret rotated (${fresh.length / 2}-byte hex written to ${MEMORY_SECRET_PATH})`);
|
|
28688
|
+
if (wasRunning) {
|
|
28689
|
+
printInfo("restarting", "memory service with new secret");
|
|
28690
|
+
const startRc = await runMemoryStart();
|
|
28691
|
+
if (startRc !== 0) {
|
|
28692
|
+
printError(
|
|
28693
|
+
"service failed to restart after rotation. The new secret is on disk; run `olam memory start` once you fix the underlying issue."
|
|
28694
|
+
);
|
|
28695
|
+
return startRc;
|
|
28696
|
+
}
|
|
28697
|
+
} else {
|
|
28698
|
+
printWarning("service was not running; new secret will be picked up on next `olam memory start`");
|
|
28699
|
+
}
|
|
28700
|
+
printWarning(
|
|
28701
|
+
"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."
|
|
28702
|
+
);
|
|
28703
|
+
return 0;
|
|
28704
|
+
}
|
|
28705
|
+
function registerMemorySecret(cmd) {
|
|
28706
|
+
const secret = cmd.command("secret").description("Show or rotate the bearer secret at ~/.olam/memory-secret");
|
|
28707
|
+
secret.command("show").description("Print the current secret value (use with care; do not pipe to logs)").action(async () => {
|
|
28708
|
+
const rc = await runMemorySecretShow();
|
|
28709
|
+
if (rc !== 0) process.exitCode = rc;
|
|
28710
|
+
});
|
|
28711
|
+
secret.command("rotate").description(
|
|
28712
|
+
"Regenerate the secret and restart the running memory service (worlds created before rotation must be re-created)"
|
|
28713
|
+
).action(async () => {
|
|
28714
|
+
const rc = await runMemorySecretRotate();
|
|
28715
|
+
if (rc !== 0) process.exitCode = rc;
|
|
28716
|
+
});
|
|
28717
|
+
}
|
|
28718
|
+
|
|
28719
|
+
// src/commands/memory/install.ts
|
|
28720
|
+
init_output();
|
|
28721
|
+
var MCP_NAME2 = "agentmemory";
|
|
28722
|
+
var NPM_PACKAGE_NAME2 = "@agentmemory/mcp";
|
|
28723
|
+
var DEFAULT_SECRET_READER = () => readMemorySecretOrNull();
|
|
28724
|
+
function buildClaudeMcpAddArgs2(scope, agentmemoryUrl, secret) {
|
|
28725
|
+
return {
|
|
28726
|
+
command: "claude",
|
|
28727
|
+
args: [
|
|
28728
|
+
"mcp",
|
|
28729
|
+
"add",
|
|
28730
|
+
MCP_NAME2,
|
|
28731
|
+
"--scope",
|
|
28732
|
+
scope,
|
|
28733
|
+
"--env",
|
|
28734
|
+
`AGENTMEMORY_URL=${agentmemoryUrl}`,
|
|
28735
|
+
"--env",
|
|
28736
|
+
`AGENTMEMORY_SECRET=${secret}`,
|
|
28737
|
+
"--",
|
|
28738
|
+
"npx",
|
|
28739
|
+
"-y",
|
|
28740
|
+
NPM_PACKAGE_NAME2
|
|
28741
|
+
]
|
|
28742
|
+
};
|
|
28743
|
+
}
|
|
28744
|
+
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.`;
|
|
28745
|
+
function redactSecretInText(text) {
|
|
28746
|
+
return text.replace(/AGENTMEMORY_SECRET=\S+/g, "AGENTMEMORY_SECRET=<redacted>");
|
|
28747
|
+
}
|
|
28748
|
+
async function runInstall2(opts, deps = DEFAULT_CLAUDE_SHELL_DEPS) {
|
|
28749
|
+
if (!isOnPath(deps, "claude")) {
|
|
28750
|
+
printError(REMEDY_CLAUDE_MISSING);
|
|
28751
|
+
return 1;
|
|
28752
|
+
}
|
|
28753
|
+
const readSecret = opts.readSecret ?? DEFAULT_SECRET_READER;
|
|
28754
|
+
const secret = opts.secret ?? readSecret();
|
|
28755
|
+
if (!secret) {
|
|
28756
|
+
printError(REMEDY_SECRET_MISSING);
|
|
28757
|
+
return 1;
|
|
28758
|
+
}
|
|
28759
|
+
const agentmemoryUrl = `http://localhost:${MEMORY_REST_PORT}`;
|
|
28760
|
+
const { command, args } = buildClaudeMcpAddArgs2(opts.scope, agentmemoryUrl, secret);
|
|
28761
|
+
const redacted = args.map(
|
|
28762
|
+
(a) => a.startsWith("AGENTMEMORY_SECRET=") ? "AGENTMEMORY_SECRET=<redacted>" : a
|
|
28763
|
+
);
|
|
28764
|
+
deps.log(`Wiring: ${command} ${redacted.join(" ")}`);
|
|
28765
|
+
const result = deps.spawn(command, args, {
|
|
28766
|
+
encoding: "utf8",
|
|
28767
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
28768
|
+
});
|
|
28769
|
+
if (result.status !== 0) {
|
|
28770
|
+
const detail = redactSecretInText(
|
|
28771
|
+
result.stderr?.trim() || result.stdout?.trim() || "(no output)"
|
|
28772
|
+
);
|
|
28773
|
+
printError(`claude mcp add failed (rc=${result.status ?? "unknown"}): ${detail}`);
|
|
28774
|
+
return result.status ?? 1;
|
|
28775
|
+
}
|
|
28776
|
+
printSuccess(
|
|
28777
|
+
`agentmemory MCP registered with claude (--scope ${opts.scope}; url ${agentmemoryUrl}).`
|
|
28778
|
+
);
|
|
28779
|
+
return 0;
|
|
28780
|
+
}
|
|
28781
|
+
function registerMemoryInstall(cmd) {
|
|
28782
|
+
cmd.command("install").description(
|
|
28783
|
+
"Register agentmemory as an MCP server with the operator's host claude (shells to `claude mcp add`)"
|
|
28784
|
+
).option("--scope <scope>", "claude mcp scope: user | project | local", "user").action(async (rawOpts) => {
|
|
28785
|
+
const scope = normaliseScope(rawOpts.scope);
|
|
28786
|
+
if (scope === null) {
|
|
28787
|
+
printError(`--scope must be one of: user, project, local (got: ${rawOpts.scope})`);
|
|
28788
|
+
process.exitCode = 1;
|
|
28789
|
+
return;
|
|
28790
|
+
}
|
|
28791
|
+
const rc = await runInstall2({ scope });
|
|
28792
|
+
if (rc !== 0) {
|
|
28793
|
+
process.exitCode = rc;
|
|
28794
|
+
}
|
|
28795
|
+
});
|
|
28796
|
+
}
|
|
28797
|
+
|
|
28798
|
+
// src/commands/memory/uninstall.ts
|
|
28799
|
+
init_output();
|
|
28800
|
+
var MCP_NAME3 = "agentmemory";
|
|
28801
|
+
function buildClaudeMcpRemoveArgs2(scope) {
|
|
28802
|
+
return {
|
|
28803
|
+
command: "claude",
|
|
28804
|
+
args: ["mcp", "remove", MCP_NAME3, "--scope", scope]
|
|
28805
|
+
};
|
|
28806
|
+
}
|
|
28807
|
+
async function runUninstall2(opts, deps = DEFAULT_CLAUDE_SHELL_DEPS) {
|
|
28808
|
+
if (!isOnPath(deps, "claude")) {
|
|
28809
|
+
printError(REMEDY_CLAUDE_MISSING);
|
|
28810
|
+
return 1;
|
|
28811
|
+
}
|
|
28812
|
+
const { command, args } = buildClaudeMcpRemoveArgs2(opts.scope);
|
|
28813
|
+
deps.log(`Unwiring: ${command} ${args.join(" ")}`);
|
|
28814
|
+
const result = deps.spawn(command, args, {
|
|
28815
|
+
encoding: "utf8",
|
|
28816
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
28817
|
+
});
|
|
28818
|
+
if (result.status === 0) {
|
|
28819
|
+
printSuccess(`agentmemory MCP removed from claude (--scope ${opts.scope}).`);
|
|
28820
|
+
return 0;
|
|
28821
|
+
}
|
|
28822
|
+
const stderr = result.stderr?.trim() ?? "";
|
|
28823
|
+
const stdout = result.stdout?.trim() ?? "";
|
|
28824
|
+
const combined = `${stderr}
|
|
28825
|
+
${stdout}`.trim();
|
|
28826
|
+
if (looksLikeNotInstalled(combined)) {
|
|
28827
|
+
printWarning(
|
|
28828
|
+
`agentmemory MCP not registered with claude (--scope ${opts.scope}); nothing to remove.`
|
|
28829
|
+
);
|
|
28830
|
+
return 0;
|
|
28831
|
+
}
|
|
28832
|
+
printError(
|
|
28833
|
+
`claude mcp remove failed (rc=${result.status ?? "unknown"}): ${combined || "(no output)"}`
|
|
28834
|
+
);
|
|
28835
|
+
return result.status ?? 1;
|
|
28836
|
+
}
|
|
28837
|
+
function registerMemoryUninstall(cmd) {
|
|
28838
|
+
cmd.command("uninstall").description(
|
|
28839
|
+
"Remove agentmemory from the operator's host claude (symmetric with `install`; idempotent)"
|
|
28840
|
+
).option("--scope <scope>", "claude mcp scope: user | project | local", "user").action(async (rawOpts) => {
|
|
28841
|
+
const scope = normaliseScope(rawOpts.scope);
|
|
28842
|
+
if (scope === null) {
|
|
28843
|
+
printError(`--scope must be one of: user, project, local (got: ${rawOpts.scope})`);
|
|
28844
|
+
process.exitCode = 1;
|
|
28845
|
+
return;
|
|
28846
|
+
}
|
|
28847
|
+
const rc = await runUninstall2({ scope });
|
|
28848
|
+
if (rc !== 0) {
|
|
28849
|
+
process.exitCode = rc;
|
|
28850
|
+
}
|
|
28851
|
+
});
|
|
28852
|
+
}
|
|
28853
|
+
|
|
28854
|
+
// src/commands/memory/index.ts
|
|
28855
|
+
function registerMemory(program2) {
|
|
28856
|
+
const memory = program2.command("memory").description(
|
|
28857
|
+
"Host-process agent-memory service for the olam fleet (start, stop, status, logs, secret, install, uninstall)"
|
|
28858
|
+
);
|
|
28859
|
+
registerMemoryStart(memory);
|
|
28860
|
+
registerMemoryStop(memory);
|
|
28861
|
+
registerMemoryStatus(memory);
|
|
28862
|
+
registerMemoryLogs(memory);
|
|
28863
|
+
registerMemorySecret(memory);
|
|
28864
|
+
registerMemoryInstall(memory);
|
|
28865
|
+
registerMemoryUninstall(memory);
|
|
28866
|
+
}
|
|
28867
|
+
|
|
28868
|
+
// src/commands/kg-build.ts
|
|
28869
|
+
init_storage_paths();
|
|
28870
|
+
init_workspace_name();
|
|
28871
|
+
init_output();
|
|
28872
|
+
import { spawnSync as spawnSync19 } from "node:child_process";
|
|
28873
|
+
import fs49 from "node:fs";
|
|
28874
|
+
import path54 from "node:path";
|
|
28875
|
+
|
|
28876
|
+
// src/commands/kg-status.ts
|
|
28877
|
+
init_storage_paths();
|
|
28878
|
+
init_workspace_name();
|
|
28879
|
+
import fs47 from "node:fs";
|
|
28880
|
+
import { homedir as homedir31 } from "node:os";
|
|
28881
|
+
import path52 from "node:path";
|
|
28882
|
+
init_output();
|
|
28883
|
+
function olamHome4() {
|
|
28884
|
+
return process.env.OLAM_HOME ?? path52.join(homedir31(), ".olam");
|
|
28885
|
+
}
|
|
28886
|
+
function kgRoot2() {
|
|
28887
|
+
return path52.join(olamHome4(), "kg");
|
|
28888
|
+
}
|
|
28889
|
+
function worldsRoot2() {
|
|
28890
|
+
return path52.join(olamHome4(), "worlds");
|
|
28891
|
+
}
|
|
28892
|
+
function dirSizeBytes2(dir) {
|
|
28893
|
+
if (!fs47.existsSync(dir)) return 0;
|
|
28894
|
+
let total = 0;
|
|
28895
|
+
const stack = [dir];
|
|
28896
|
+
while (stack.length > 0) {
|
|
28897
|
+
const cur = stack.pop();
|
|
28898
|
+
let entries;
|
|
28899
|
+
try {
|
|
28900
|
+
entries = fs47.readdirSync(cur, { withFileTypes: true });
|
|
28901
|
+
} catch {
|
|
28902
|
+
continue;
|
|
28903
|
+
}
|
|
28904
|
+
for (const entry of entries) {
|
|
28905
|
+
const full = path52.join(cur, entry.name);
|
|
28906
|
+
if (entry.isSymbolicLink()) continue;
|
|
28907
|
+
if (entry.isDirectory()) {
|
|
28908
|
+
stack.push(full);
|
|
27579
28909
|
continue;
|
|
27580
28910
|
}
|
|
27581
28911
|
try {
|
|
@@ -27806,7 +29136,7 @@ function registerKgStatusCommand(kg) {
|
|
|
27806
29136
|
init_storage_paths();
|
|
27807
29137
|
init_workspace_name();
|
|
27808
29138
|
init_output();
|
|
27809
|
-
import { spawn as
|
|
29139
|
+
import { spawn as spawn10 } from "node:child_process";
|
|
27810
29140
|
import fs48 from "node:fs";
|
|
27811
29141
|
import path53 from "node:path";
|
|
27812
29142
|
function pidFilePath(workspace) {
|
|
@@ -27876,7 +29206,7 @@ async function runKgWatch(workspaceArg, opts, deps = {}) {
|
|
|
27876
29206
|
if (pidState.status === "stale-reclaimed") {
|
|
27877
29207
|
printInfo("stale-pid", `reclaimed dead PID file at ${pidFilePath(name)}`);
|
|
27878
29208
|
}
|
|
27879
|
-
const spawnFn = deps.spawnImpl ??
|
|
29209
|
+
const spawnFn = deps.spawnImpl ?? spawn10;
|
|
27880
29210
|
const child = spawnFn(
|
|
27881
29211
|
"graphify",
|
|
27882
29212
|
[cwd, "--watch", "--update", "--graph", graphPath],
|
|
@@ -27905,16 +29235,16 @@ async function runKgWatch(workspaceArg, opts, deps = {}) {
|
|
|
27905
29235
|
process.on("SIGINT", () => forward("SIGINT"));
|
|
27906
29236
|
process.on("SIGTERM", () => forward("SIGTERM"));
|
|
27907
29237
|
}
|
|
27908
|
-
return new Promise((
|
|
29238
|
+
return new Promise((resolve14) => {
|
|
27909
29239
|
child.on("exit", (code, signal) => {
|
|
27910
29240
|
removePidFile(name);
|
|
27911
29241
|
const exitCode = typeof code === "number" ? code : signal === "SIGINT" || signal === "SIGTERM" ? 0 : 1;
|
|
27912
|
-
|
|
29242
|
+
resolve14({ exitCode, pidWritten: true });
|
|
27913
29243
|
});
|
|
27914
29244
|
child.on("error", (err) => {
|
|
27915
29245
|
removePidFile(name);
|
|
27916
29246
|
printError(`graphify subprocess error: ${err.message}`);
|
|
27917
|
-
|
|
29247
|
+
resolve14({ exitCode: 1, pidWritten: true });
|
|
27918
29248
|
});
|
|
27919
29249
|
});
|
|
27920
29250
|
}
|
|
@@ -27937,13 +29267,13 @@ function resolveWorkspace(arg) {
|
|
|
27937
29267
|
}
|
|
27938
29268
|
function copyWorkspaceToScratch(source, scratch) {
|
|
27939
29269
|
if (process.platform === "darwin") {
|
|
27940
|
-
const r2 =
|
|
29270
|
+
const r2 = spawnSync19("cp", ["-c", "-r", source + "/.", scratch], {
|
|
27941
29271
|
stdio: ["ignore", "ignore", "pipe"],
|
|
27942
29272
|
encoding: "utf-8"
|
|
27943
29273
|
});
|
|
27944
29274
|
if (r2.status === 0) return "cp-c-r-reflink";
|
|
27945
29275
|
}
|
|
27946
|
-
const r =
|
|
29276
|
+
const r = spawnSync19("cp", ["-r", source + "/.", scratch], {
|
|
27947
29277
|
stdio: ["ignore", "ignore", "pipe"],
|
|
27948
29278
|
encoding: "utf-8"
|
|
27949
29279
|
});
|
|
@@ -27964,7 +29294,7 @@ function parseNodeCount(graphifyOutDir) {
|
|
|
27964
29294
|
}
|
|
27965
29295
|
}
|
|
27966
29296
|
function readGraphifyVersion(image) {
|
|
27967
|
-
const r =
|
|
29297
|
+
const r = spawnSync19(
|
|
27968
29298
|
"docker",
|
|
27969
29299
|
[
|
|
27970
29300
|
"run",
|
|
@@ -28016,7 +29346,7 @@ async function runKgBuild(workspaceArg, options = {}) {
|
|
|
28016
29346
|
"update",
|
|
28017
29347
|
"."
|
|
28018
29348
|
];
|
|
28019
|
-
const r = human ?
|
|
29349
|
+
const r = human ? spawnSync19("docker", dockerArgs, { stdio: "inherit" }) : spawnSync19("docker", dockerArgs, { stdio: ["ignore", "ignore", "pipe"] });
|
|
28020
29350
|
if (r.status !== 0) {
|
|
28021
29351
|
printError(`graphify update failed (exit ${r.status})`);
|
|
28022
29352
|
return { exitCode: r.status ?? 1 };
|
|
@@ -28072,6 +29402,250 @@ function registerKg(program2) {
|
|
|
28072
29402
|
registerKgWatchCommand(kg);
|
|
28073
29403
|
}
|
|
28074
29404
|
|
|
29405
|
+
// src/commands/seed.ts
|
|
29406
|
+
init_output();
|
|
29407
|
+
import { spawnSync as spawnSync20, spawn as spawnAsync2 } from "node:child_process";
|
|
29408
|
+
var DEFAULT_SINGLETON_CONTAINER = "olam-postgres";
|
|
29409
|
+
var DEFAULT_SINGLETON_USER = "development";
|
|
29410
|
+
function assertValidSeedName(name) {
|
|
29411
|
+
if (!/^[a-z][a-z0-9_]*$/.test(name)) {
|
|
29412
|
+
throw new Error(
|
|
29413
|
+
`invalid seed name "${name}" \u2014 must match [a-z][a-z0-9_]* (no hyphens, no uppercase)`
|
|
29414
|
+
);
|
|
29415
|
+
}
|
|
29416
|
+
}
|
|
29417
|
+
function singletonDocker(container, user, args, stdin) {
|
|
29418
|
+
return spawnSync20(
|
|
29419
|
+
"docker",
|
|
29420
|
+
["exec", "-i", container, "psql", "-U", user, ...args],
|
|
29421
|
+
{ encoding: "utf-8", input: stdin }
|
|
29422
|
+
);
|
|
29423
|
+
}
|
|
29424
|
+
function singletonHasDb(container, user, db) {
|
|
29425
|
+
const r = singletonDocker(container, user, [
|
|
29426
|
+
"-d",
|
|
29427
|
+
"postgres",
|
|
29428
|
+
"-tAc",
|
|
29429
|
+
`SELECT 1 FROM pg_database WHERE datname='${db.replace(/'/g, "''")}'`
|
|
29430
|
+
]);
|
|
29431
|
+
return r.status === 0 && (r.stdout || "").trim() === "1";
|
|
29432
|
+
}
|
|
29433
|
+
function singletonListSeeds(container, user) {
|
|
29434
|
+
const r = singletonDocker(container, user, [
|
|
29435
|
+
"-d",
|
|
29436
|
+
"postgres",
|
|
29437
|
+
"-tAc",
|
|
29438
|
+
"SELECT datname || '|' || pg_size_pretty(pg_database_size(datname)) FROM pg_database WHERE datname LIKE '%\\_seed' ORDER BY datname"
|
|
29439
|
+
]);
|
|
29440
|
+
if (r.status !== 0) {
|
|
29441
|
+
throw new Error(`failed to list seeds: ${(r.stderr || "").trim()}`);
|
|
29442
|
+
}
|
|
29443
|
+
const lines = (r.stdout || "").trim().split("\n").filter((l) => l.length > 0);
|
|
29444
|
+
return lines.map((line) => {
|
|
29445
|
+
const [name = "", size = ""] = line.split("|");
|
|
29446
|
+
return { name, size };
|
|
29447
|
+
});
|
|
29448
|
+
}
|
|
29449
|
+
function singletonListClonesOf(container, user, seed) {
|
|
29450
|
+
const prefix = seed.endsWith("_seed") ? seed.slice(0, -"_seed".length) : seed;
|
|
29451
|
+
const r = singletonDocker(container, user, [
|
|
29452
|
+
"-d",
|
|
29453
|
+
"postgres",
|
|
29454
|
+
"-tAc",
|
|
29455
|
+
`SELECT datname FROM pg_database WHERE datname LIKE '${prefix.replace(/'/g, "''")}_world_%' ORDER BY datname`
|
|
29456
|
+
]);
|
|
29457
|
+
if (r.status !== 0) return [];
|
|
29458
|
+
return (r.stdout || "").trim().split("\n").filter((l) => l.length > 0);
|
|
29459
|
+
}
|
|
29460
|
+
async function handleBake(opts) {
|
|
29461
|
+
assertValidSeedName(opts.as);
|
|
29462
|
+
const singleton = opts.singletonContainer ?? DEFAULT_SINGLETON_CONTAINER;
|
|
29463
|
+
const singletonUser = opts.singletonUser ?? DEFAULT_SINGLETON_USER;
|
|
29464
|
+
const sources = [opts.sourceContainer, opts.sourceUrl, opts.sourceLocal].filter(Boolean);
|
|
29465
|
+
if (sources.length === 0) {
|
|
29466
|
+
throw new Error(
|
|
29467
|
+
"no source specified \u2014 pass exactly one of --source-container, --source-url, --source-local"
|
|
29468
|
+
);
|
|
29469
|
+
}
|
|
29470
|
+
if (sources.length > 1) {
|
|
29471
|
+
throw new Error("multiple sources specified \u2014 pass exactly one of --source-container, --source-url, --source-local");
|
|
29472
|
+
}
|
|
29473
|
+
const ping = spawnSync20("docker", ["inspect", "--format", "{{.State.Status}}", singleton], { encoding: "utf-8" });
|
|
29474
|
+
if (ping.status !== 0 || (ping.stdout || "").trim() !== "running") {
|
|
29475
|
+
throw new Error(`singleton container "${singleton}" not running \u2014 run \`olam bootstrap\` first`);
|
|
29476
|
+
}
|
|
29477
|
+
if (singletonHasDb(singleton, singletonUser, opts.as)) {
|
|
29478
|
+
if (!opts.force) {
|
|
29479
|
+
throw new Error(`seed "${opts.as}" already exists \u2014 pass --force to overwrite (DROP + recreate)`);
|
|
29480
|
+
}
|
|
29481
|
+
const clones = singletonListClonesOf(singleton, singletonUser, opts.as);
|
|
29482
|
+
if (clones.length > 0) {
|
|
29483
|
+
throw new Error(
|
|
29484
|
+
`cannot --force overwrite "${opts.as}" \u2014 ${clones.length} per-world clone(s) still depend on it: ${clones.join(", ")}
|
|
29485
|
+
Destroy those worlds first, then re-bake.`
|
|
29486
|
+
);
|
|
29487
|
+
}
|
|
29488
|
+
printWarning(`Dropping existing seed "${opts.as}" (--force)`);
|
|
29489
|
+
const drop = singletonDocker(singleton, singletonUser, ["-d", "postgres", "-c", `DROP DATABASE "${opts.as}"`]);
|
|
29490
|
+
if (drop.status !== 0) {
|
|
29491
|
+
throw new Error(`DROP DATABASE failed: ${(drop.stderr || "").trim()}`);
|
|
29492
|
+
}
|
|
29493
|
+
}
|
|
29494
|
+
process.stdout.write(` Creating empty target DB "${opts.as}" on singleton
|
|
29495
|
+
`);
|
|
29496
|
+
const create = singletonDocker(singleton, singletonUser, ["-d", "postgres", "-c", `CREATE DATABASE "${opts.as}"`]);
|
|
29497
|
+
if (create.status !== 0) {
|
|
29498
|
+
throw new Error(`CREATE DATABASE "${opts.as}" failed: ${(create.stderr || "").trim()}`);
|
|
29499
|
+
}
|
|
29500
|
+
let dumpArgs;
|
|
29501
|
+
let dumpEnv = { PATH: process.env["PATH"] || "" };
|
|
29502
|
+
if (opts.sourceContainer) {
|
|
29503
|
+
const srcDb = opts.sourceDb ?? "postgres";
|
|
29504
|
+
const srcUser = opts.sourceUser ?? DEFAULT_SINGLETON_USER;
|
|
29505
|
+
process.stdout.write(` Source: container ${opts.sourceContainer} DB=${srcDb} user=${srcUser}
|
|
29506
|
+
`);
|
|
29507
|
+
dumpArgs = ["exec", opts.sourceContainer, "pg_dump", "-U", srcUser, "-d", srcDb, "--no-owner", "--no-privileges"];
|
|
29508
|
+
dumpEnv = { PATH: process.env["PATH"] || "" };
|
|
29509
|
+
} else if (opts.sourceLocal) {
|
|
29510
|
+
if (!singletonHasDb(singleton, singletonUser, opts.sourceLocal)) {
|
|
29511
|
+
throw new Error(`--source-local "${opts.sourceLocal}" does not exist on singleton`);
|
|
29512
|
+
}
|
|
29513
|
+
process.stdout.write(` Source: singleton local DB ${opts.sourceLocal}
|
|
29514
|
+
`);
|
|
29515
|
+
dumpArgs = ["exec", singleton, "pg_dump", "-U", singletonUser, "-d", opts.sourceLocal, "--no-owner", "--no-privileges"];
|
|
29516
|
+
dumpEnv = { PATH: process.env["PATH"] || "" };
|
|
29517
|
+
} else {
|
|
29518
|
+
process.stdout.write(` Source: ${opts.sourceUrl.replace(/\/\/[^:]+:[^@]+@/, "//<creds>@")}
|
|
29519
|
+
`);
|
|
29520
|
+
dumpArgs = ["exec", "-e", `PGURL=${opts.sourceUrl}`, singleton, "sh", "-c", 'pg_dump --no-owner --no-privileges "$PGURL"'];
|
|
29521
|
+
dumpEnv = { PATH: process.env["PATH"] || "" };
|
|
29522
|
+
}
|
|
29523
|
+
const dumper = spawnAsync2("docker", dumpArgs, { stdio: ["ignore", "pipe", "inherit"], env: dumpEnv });
|
|
29524
|
+
const loader = spawnAsync2(
|
|
29525
|
+
"docker",
|
|
29526
|
+
["exec", "-i", singleton, "psql", "-U", singletonUser, "-d", opts.as, "-v", "ON_ERROR_STOP=1", "-q"],
|
|
29527
|
+
{ stdio: ["pipe", "inherit", "inherit"], env: { PATH: process.env["PATH"] || "" } }
|
|
29528
|
+
);
|
|
29529
|
+
dumper.stdout.pipe(loader.stdin);
|
|
29530
|
+
const [dumpExit, loadExit] = await Promise.all([
|
|
29531
|
+
new Promise((res) => dumper.on("close", (code) => res(code ?? 1))),
|
|
29532
|
+
new Promise((res) => loader.on("close", (code) => res(code ?? 1)))
|
|
29533
|
+
]);
|
|
29534
|
+
if (dumpExit !== 0) {
|
|
29535
|
+
throw new Error(`pg_dump exited with code ${dumpExit}`);
|
|
29536
|
+
}
|
|
29537
|
+
if (loadExit !== 0) {
|
|
29538
|
+
throw new Error(`psql load exited with code ${loadExit}`);
|
|
29539
|
+
}
|
|
29540
|
+
const size = singletonDocker(singleton, singletonUser, [
|
|
29541
|
+
"-d",
|
|
29542
|
+
"postgres",
|
|
29543
|
+
"-tAc",
|
|
29544
|
+
`SELECT pg_size_pretty(pg_database_size('${opts.as}'))`
|
|
29545
|
+
]);
|
|
29546
|
+
const sizeStr = (size.stdout || "").trim() || "unknown";
|
|
29547
|
+
printSuccess(`Seed "${opts.as}" baked (${sizeStr})`);
|
|
29548
|
+
}
|
|
29549
|
+
function handleList3(opts) {
|
|
29550
|
+
const singleton = opts.singletonContainer ?? DEFAULT_SINGLETON_CONTAINER;
|
|
29551
|
+
const singletonUser = opts.singletonUser ?? DEFAULT_SINGLETON_USER;
|
|
29552
|
+
const seeds = singletonListSeeds(singleton, singletonUser);
|
|
29553
|
+
if (opts.json) {
|
|
29554
|
+
process.stdout.write(JSON.stringify(seeds, null, 2) + "\n");
|
|
29555
|
+
return;
|
|
29556
|
+
}
|
|
29557
|
+
if (seeds.length === 0) {
|
|
29558
|
+
process.stdout.write("No seeds found on singleton.\n");
|
|
29559
|
+
return;
|
|
29560
|
+
}
|
|
29561
|
+
printHeader(`${seeds.length} seed(s) on ${singleton}`);
|
|
29562
|
+
const nameWidth = Math.max(...seeds.map((s) => s.name.length), 4);
|
|
29563
|
+
for (const s of seeds) {
|
|
29564
|
+
process.stdout.write(` ${s.name.padEnd(nameWidth)} ${s.size}
|
|
29565
|
+
`);
|
|
29566
|
+
}
|
|
29567
|
+
}
|
|
29568
|
+
function handleRemove(name, opts) {
|
|
29569
|
+
assertValidSeedName(name);
|
|
29570
|
+
const singleton = opts.singletonContainer ?? DEFAULT_SINGLETON_CONTAINER;
|
|
29571
|
+
const singletonUser = opts.singletonUser ?? DEFAULT_SINGLETON_USER;
|
|
29572
|
+
if (!singletonHasDb(singleton, singletonUser, name)) {
|
|
29573
|
+
printWarning(`Seed "${name}" does not exist on singleton \u2014 nothing to do`);
|
|
29574
|
+
return;
|
|
29575
|
+
}
|
|
29576
|
+
const clones = singletonListClonesOf(singleton, singletonUser, name);
|
|
29577
|
+
if (clones.length > 0 && !opts.force) {
|
|
29578
|
+
throw new Error(
|
|
29579
|
+
`seed "${name}" has ${clones.length} active world clone(s): ${clones.join(", ")}
|
|
29580
|
+
Destroy those worlds first, or re-run with --force to drop anyway (CAUTION: leaves world DBs orphaned).`
|
|
29581
|
+
);
|
|
29582
|
+
}
|
|
29583
|
+
const drop = singletonDocker(singleton, singletonUser, ["-d", "postgres", "-c", `DROP DATABASE "${name}"`]);
|
|
29584
|
+
if (drop.status !== 0) {
|
|
29585
|
+
throw new Error(`DROP DATABASE "${name}" failed: ${(drop.stderr || "").trim()}`);
|
|
29586
|
+
}
|
|
29587
|
+
printSuccess(`Removed seed "${name}"`);
|
|
29588
|
+
}
|
|
29589
|
+
function handleVerify(name, opts) {
|
|
29590
|
+
assertValidSeedName(name);
|
|
29591
|
+
const singleton = opts.singletonContainer ?? DEFAULT_SINGLETON_CONTAINER;
|
|
29592
|
+
const singletonUser = opts.singletonUser ?? DEFAULT_SINGLETON_USER;
|
|
29593
|
+
if (!singletonHasDb(singleton, singletonUser, name)) {
|
|
29594
|
+
throw new Error(`seed "${name}" does not exist on singleton`);
|
|
29595
|
+
}
|
|
29596
|
+
const heartbeat = singletonDocker(singleton, singletonUser, ["-d", name, "-tAc", "SELECT 1"]);
|
|
29597
|
+
if (heartbeat.status !== 0 || (heartbeat.stdout || "").trim() !== "1") {
|
|
29598
|
+
throw new Error(`heartbeat failed: ${(heartbeat.stderr || "").trim()}`);
|
|
29599
|
+
}
|
|
29600
|
+
const meta = singletonDocker(singleton, singletonUser, [
|
|
29601
|
+
"-d",
|
|
29602
|
+
name,
|
|
29603
|
+
"-tAc",
|
|
29604
|
+
"SELECT pg_size_pretty(pg_database_size(current_database())) || '|' || (SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public')"
|
|
29605
|
+
]);
|
|
29606
|
+
const [size = "?", tableCount = "?"] = (meta.stdout || "").trim().split("|");
|
|
29607
|
+
const clones = singletonListClonesOf(singleton, singletonUser, name);
|
|
29608
|
+
printSuccess(`Seed "${name}" OK`);
|
|
29609
|
+
printInfo("size", size);
|
|
29610
|
+
printInfo("tables", `${tableCount} (public schema)`);
|
|
29611
|
+
printInfo("clones", `${clones.length}${clones.length > 0 ? " (" + clones.join(", ") + ")" : ""}`);
|
|
29612
|
+
}
|
|
29613
|
+
function registerSeed(program2) {
|
|
29614
|
+
const seed = program2.command("seed").description("Manage postgres seed templates on the olam-postgres singleton");
|
|
29615
|
+
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) => {
|
|
29616
|
+
try {
|
|
29617
|
+
await handleBake(opts);
|
|
29618
|
+
} catch (err) {
|
|
29619
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
29620
|
+
process.exit(1);
|
|
29621
|
+
}
|
|
29622
|
+
});
|
|
29623
|
+
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) => {
|
|
29624
|
+
try {
|
|
29625
|
+
handleList3(opts);
|
|
29626
|
+
} catch (err) {
|
|
29627
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
29628
|
+
process.exit(1);
|
|
29629
|
+
}
|
|
29630
|
+
});
|
|
29631
|
+
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) => {
|
|
29632
|
+
try {
|
|
29633
|
+
handleRemove(name, opts);
|
|
29634
|
+
} catch (err) {
|
|
29635
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
29636
|
+
process.exit(1);
|
|
29637
|
+
}
|
|
29638
|
+
});
|
|
29639
|
+
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) => {
|
|
29640
|
+
try {
|
|
29641
|
+
handleVerify(name, opts);
|
|
29642
|
+
} catch (err) {
|
|
29643
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
29644
|
+
process.exit(1);
|
|
29645
|
+
}
|
|
29646
|
+
});
|
|
29647
|
+
}
|
|
29648
|
+
|
|
28075
29649
|
// src/pleri-config.ts
|
|
28076
29650
|
import * as fs50 from "node:fs";
|
|
28077
29651
|
import * as path55 from "node:path";
|
|
@@ -28122,6 +29696,7 @@ registerCrystallize(program, { hidden: !isPleriConfigured() });
|
|
|
28122
29696
|
registerPr(program);
|
|
28123
29697
|
registerWorkspace(program);
|
|
28124
29698
|
registerHostCp(program);
|
|
29699
|
+
registerSeed(program);
|
|
28125
29700
|
registerLanes(program);
|
|
28126
29701
|
registerPolicyCheck(program);
|
|
28127
29702
|
registerWorldspec(program);
|
|
@@ -28141,6 +29716,7 @@ registerBegin(program);
|
|
|
28141
29716
|
registerStop(program);
|
|
28142
29717
|
registerWorldUpgrade(program);
|
|
28143
29718
|
registerMcp(program);
|
|
29719
|
+
registerMemory(program);
|
|
28144
29720
|
registerKg(program);
|
|
28145
29721
|
registerConfig(program);
|
|
28146
29722
|
registerRepos(program);
|