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