@pleri/olam-cli 0.1.65 → 0.1.68

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/index.js CHANGED
@@ -460,8 +460,8 @@ var init_parseUtil = __esm({
460
460
  init_errors();
461
461
  init_en();
462
462
  makeIssue = (params) => {
463
- const { data, path: path44, errorMaps, issueData } = params;
464
- const fullPath = [...path44, ...issueData.path || []];
463
+ const { data, path: path46, errorMaps, issueData } = params;
464
+ const fullPath = [...path46, ...issueData.path || []];
465
465
  const fullIssue = {
466
466
  ...issueData,
467
467
  path: fullPath
@@ -769,11 +769,11 @@ var init_types = __esm({
769
769
  init_parseUtil();
770
770
  init_util();
771
771
  ParseInputLazyPath = class {
772
- constructor(parent, value, path44, key) {
772
+ constructor(parent, value, path46, key) {
773
773
  this._cachedPath = [];
774
774
  this.parent = parent;
775
775
  this.data = value;
776
- this._path = path44;
776
+ this._path = path46;
777
777
  this._key = key;
778
778
  }
779
779
  get path() {
@@ -4254,7 +4254,7 @@ import YAML from "yaml";
4254
4254
  function bootstrapStepCmd(entry) {
4255
4255
  return typeof entry === "string" ? entry : entry.cmd;
4256
4256
  }
4257
- function refineForbiddenKeys(value, path44, ctx, rejectSource) {
4257
+ function refineForbiddenKeys(value, path46, ctx, rejectSource) {
4258
4258
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
4259
4259
  return;
4260
4260
  }
@@ -4262,12 +4262,12 @@ function refineForbiddenKeys(value, path44, ctx, rejectSource) {
4262
4262
  if (FORBIDDEN_KEYS.has(key)) {
4263
4263
  ctx.addIssue({
4264
4264
  code: external_exports.ZodIssueCode.custom,
4265
- path: [...path44, key],
4265
+ path: [...path46, key],
4266
4266
  message: `forbidden key "${key}" (prototype-pollution surface)`
4267
4267
  });
4268
4268
  continue;
4269
4269
  }
4270
- if (rejectSource && path44.length === 0 && key === "source") {
4270
+ if (rejectSource && path46.length === 0 && key === "source") {
4271
4271
  ctx.addIssue({
4272
4272
  code: external_exports.ZodIssueCode.custom,
4273
4273
  path: ["source"],
@@ -4275,21 +4275,21 @@ function refineForbiddenKeys(value, path44, ctx, rejectSource) {
4275
4275
  });
4276
4276
  continue;
4277
4277
  }
4278
- refineForbiddenKeys(value[key], [...path44, key], ctx, false);
4278
+ refineForbiddenKeys(value[key], [...path46, key], ctx, false);
4279
4279
  }
4280
4280
  }
4281
- function rejectForbiddenKeys(value, path44, rejectSource) {
4281
+ function rejectForbiddenKeys(value, path46, rejectSource) {
4282
4282
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
4283
4283
  return;
4284
4284
  }
4285
4285
  for (const key of Object.keys(value)) {
4286
4286
  if (FORBIDDEN_KEYS.has(key)) {
4287
- throw new Error(`[manifest] ${path44}: forbidden key "${key}" (prototype-pollution surface)`);
4287
+ throw new Error(`[manifest] ${path46}: forbidden key "${key}" (prototype-pollution surface)`);
4288
4288
  }
4289
4289
  if (rejectSource && key === "source") {
4290
- throw new Error(`[manifest] ${path44}: top-level "source" is loader-stamped \u2014 manifests must not author it`);
4290
+ throw new Error(`[manifest] ${path46}: top-level "source" is loader-stamped \u2014 manifests must not author it`);
4291
4291
  }
4292
- rejectForbiddenKeys(value[key], `${path44}.${key}`, false);
4292
+ rejectForbiddenKeys(value[key], `${path46}.${key}`, false);
4293
4293
  }
4294
4294
  }
4295
4295
  function unknownTopLevelKeys(parsed) {
@@ -5280,8 +5280,8 @@ var init_client = __esm({
5280
5280
  throw new Error(`failed to report rate-limit for ${accountId} (HTTP ${res.status})`);
5281
5281
  }
5282
5282
  }
5283
- async request(method, path44, body, attempt = 0) {
5284
- const url = `${this.baseUrl}${path44}`;
5283
+ async request(method, path46, body, attempt = 0) {
5284
+ const url = `${this.baseUrl}${path46}`;
5285
5285
  const controller = new AbortController();
5286
5286
  const timer = setTimeout(() => controller.abort(), this.timeoutMs);
5287
5287
  const headers = {};
@@ -5299,7 +5299,7 @@ var init_client = __esm({
5299
5299
  } catch (err) {
5300
5300
  if (attempt < RETRY_COUNT && isTransient(err)) {
5301
5301
  await sleep(RETRY_BACKOFF_MS * (attempt + 1));
5302
- return this.request(method, path44, body, attempt + 1);
5302
+ return this.request(method, path46, body, attempt + 1);
5303
5303
  }
5304
5304
  throw err;
5305
5305
  } finally {
@@ -6778,8 +6778,8 @@ var init_provider3 = __esm({
6778
6778
  // -----------------------------------------------------------------------
6779
6779
  // Internal fetch helper
6780
6780
  // -----------------------------------------------------------------------
6781
- async request(path44, method, body) {
6782
- const url = `${this.config.workerUrl}${path44}`;
6781
+ async request(path46, method, body) {
6782
+ const url = `${this.config.workerUrl}${path46}`;
6783
6783
  const bearer = await this.config.mintToken();
6784
6784
  const headers = {
6785
6785
  Authorization: `Bearer ${bearer}`
@@ -8056,8 +8056,8 @@ import { execFileSync as execFileSync3 } from "node:child_process";
8056
8056
  import * as fs13 from "node:fs";
8057
8057
  import * as os9 from "node:os";
8058
8058
  import * as path14 from "node:path";
8059
- function expandHome(p, homedir22) {
8060
- return p.replace(/^~(?=$|\/|\\)/, homedir22());
8059
+ function expandHome(p, homedir24) {
8060
+ return p.replace(/^~(?=$|\/|\\)/, homedir24());
8061
8061
  }
8062
8062
  function sanitizeRepoFilename(name) {
8063
8063
  const sanitized = name.replace(/[^A-Za-z0-9._-]/g, "_");
@@ -8080,7 +8080,7 @@ ${stderr}`;
8080
8080
  }
8081
8081
  function snapshotBaselineDiff(repos, workspacePath, deps = {}) {
8082
8082
  const exec = deps.exec ?? ((cmd, args, opts) => execFileSync3(cmd, args, opts));
8083
- const homedir22 = deps.homedir ?? (() => os9.homedir());
8083
+ const homedir24 = deps.homedir ?? (() => os9.homedir());
8084
8084
  const baselineDir = path14.join(workspacePath, ".olam", "baseline");
8085
8085
  try {
8086
8086
  fs13.mkdirSync(baselineDir, { recursive: true });
@@ -8096,7 +8096,7 @@ function snapshotBaselineDiff(repos, workspacePath, deps = {}) {
8096
8096
  continue;
8097
8097
  const filename = `${sanitizeRepoFilename(repo.name)}.diff`;
8098
8098
  const outPath = path14.join(baselineDir, filename);
8099
- const repoPath = expandHome(repo.path, homedir22);
8099
+ const repoPath = expandHome(repo.path, homedir24);
8100
8100
  if (!fs13.existsSync(repoPath)) {
8101
8101
  writeBaselineFile(outPath, `# repo: ${repo.name}
8102
8102
  # (skipped: path ${repoPath} does not exist)
@@ -12523,10 +12523,10 @@ async function readHostCpToken2() {
12523
12523
  if (!fs19.existsSync(tp)) return null;
12524
12524
  return fs19.readFileSync(tp, "utf-8").trim();
12525
12525
  }
12526
- async function callHostCpProxy(method, worldId, path44, body) {
12526
+ async function callHostCpProxy(method, worldId, path46, body) {
12527
12527
  const token = await readHostCpToken2();
12528
12528
  if (!token) return { ok: false, status: 0, error: "no token (host CP not started)" };
12529
- const url = `http://127.0.0.1:${HOST_CP_PORT}/api/world/${encodeURIComponent(worldId)}${path44}`;
12529
+ const url = `http://127.0.0.1:${HOST_CP_PORT}/api/world/${encodeURIComponent(worldId)}${path46}`;
12530
12530
  try {
12531
12531
  const headers = {
12532
12532
  Authorization: `Bearer ${token}`
@@ -13034,8 +13034,8 @@ var init_machine_schema = __esm({
13034
13034
 
13035
13035
  // src/index.ts
13036
13036
  import { Command } from "commander";
13037
- import * as fs39 from "node:fs";
13038
- import * as path43 from "node:path";
13037
+ import * as fs42 from "node:fs";
13038
+ import * as path45 from "node:path";
13039
13039
  import { fileURLToPath as fileURLToPath4 } from "node:url";
13040
13040
 
13041
13041
  // src/commands/init.ts
@@ -13412,9 +13412,9 @@ var UnknownArchetypeError = class extends Error {
13412
13412
  };
13413
13413
  var ArchetypeCycleError = class extends Error {
13414
13414
  path;
13415
- constructor(path44) {
13416
- super(`Archetype inheritance cycle detected: ${path44.join(" \u2192 ")} \u2192 ${path44[0] ?? "?"}`);
13417
- this.path = path44;
13415
+ constructor(path46) {
13416
+ super(`Archetype inheritance cycle detected: ${path46.join(" \u2192 ")} \u2192 ${path46[0] ?? "?"}`);
13417
+ this.path = path46;
13418
13418
  this.name = "ArchetypeCycleError";
13419
13419
  }
13420
13420
  };
@@ -14521,20 +14521,6 @@ function servicesStatus() {
14521
14521
  const mcpStateStr = mcpState.state === "running" ? pc8.green("running") : mcpState.state === "stopped" ? pc8.yellow("stopped") : pc8.red("missing");
14522
14522
  printInfo("olam-mcp-auth", `${mcpStateStr} :${mcpState.port}`);
14523
14523
  }
14524
- function registerServices(program2) {
14525
- const services = program2.command("services").description("Manage Olam service containers (olam-auth, olam-mcp-auth)");
14526
- services.command("up").description("Start all service containers (idempotent)").action(async () => {
14527
- const result = await servicesUp();
14528
- if (result.exitCode !== 0) process.exitCode = result.exitCode;
14529
- });
14530
- services.command("down").description("Stop all service containers").action(() => {
14531
- const result = servicesDown();
14532
- if (result.exitCode !== 0) process.exitCode = result.exitCode;
14533
- });
14534
- services.command("status").description("Show state of all service containers").action(() => {
14535
- servicesStatus();
14536
- });
14537
- }
14538
14524
 
14539
14525
  // src/commands/auth.ts
14540
14526
  function openBrowser(url) {
@@ -14793,9 +14779,9 @@ function formatFreshnessWarning(result, image = DEFAULT_DEVBOX_IMAGE) {
14793
14779
  "These source files have changed since the image was built; the",
14794
14780
  "changes will NOT take effect in fresh worlds until you rebuild:"
14795
14781
  ];
14796
- for (const { path: path44, mtimeMs } of result.newerSources) {
14782
+ for (const { path: path46, mtimeMs } of result.newerSources) {
14797
14783
  const when = new Date(mtimeMs).toISOString();
14798
- lines.push(` \u2022 ${path44} (modified ${when})`);
14784
+ lines.push(` \u2022 ${path46} (modified ${when})`);
14799
14785
  }
14800
14786
  lines.push("");
14801
14787
  lines.push("Rebuild with:");
@@ -14954,15 +14940,15 @@ init_host_cp();
14954
14940
  var HOST_CP_URL = "http://127.0.0.1:19000";
14955
14941
  async function readHostCpTokenForCreate() {
14956
14942
  try {
14957
- const { default: fs40 } = await import("node:fs");
14958
- const { default: os24 } = await import("node:os");
14959
- const { default: path44 } = await import("node:path");
14960
- const tp = path44.join(
14961
- process.env.OLAM_HOME ?? path44.join(os24.homedir(), ".olam"),
14943
+ const { default: fs43 } = await import("node:fs");
14944
+ const { default: os26 } = await import("node:os");
14945
+ const { default: path46 } = await import("node:path");
14946
+ const tp = path46.join(
14947
+ process.env.OLAM_HOME ?? path46.join(os26.homedir(), ".olam"),
14962
14948
  "host-cp.token"
14963
14949
  );
14964
- if (!fs40.existsSync(tp)) return null;
14965
- return fs40.readFileSync(tp, "utf-8").trim();
14950
+ if (!fs43.existsSync(tp)) return null;
14951
+ return fs43.readFileSync(tp, "utf-8").trim();
14966
14952
  } catch {
14967
14953
  return null;
14968
14954
  }
@@ -15324,12 +15310,12 @@ function defaultNameFromPrompt(prompt) {
15324
15310
  }
15325
15311
  async function readHostCpToken3() {
15326
15312
  try {
15327
- const { default: fs40 } = await import("node:fs");
15328
- const { default: os24 } = await import("node:os");
15329
- const { default: path44 } = await import("node:path");
15330
- const tp = path44.join(os24.homedir(), ".olam", "host-cp.token");
15331
- if (!fs40.existsSync(tp)) return null;
15332
- const raw = fs40.readFileSync(tp, "utf-8").trim();
15313
+ const { default: fs43 } = await import("node:fs");
15314
+ const { default: os26 } = await import("node:os");
15315
+ const { default: path46 } = await import("node:path");
15316
+ const tp = path46.join(os26.homedir(), ".olam", "host-cp.token");
15317
+ if (!fs43.existsSync(tp)) return null;
15318
+ const raw = fs43.readFileSync(tp, "utf-8").trim();
15333
15319
  return raw.length > 0 ? raw : null;
15334
15320
  } catch {
15335
15321
  return null;
@@ -21269,6 +21255,308 @@ function registerBegin(program2) {
21269
21255
  });
21270
21256
  }
21271
21257
 
21258
+ // src/commands/config.ts
21259
+ import * as fs39 from "node:fs";
21260
+ import { createRequire as createRequire3 } from "node:module";
21261
+
21262
+ // ../core/dist/global-config/schema.js
21263
+ init_zod();
21264
+ init_schema();
21265
+ function isAbsoluteOrTilde(p) {
21266
+ return p.startsWith("/") || p === "~" || p.startsWith("~/");
21267
+ }
21268
+ function hasNoTraversalComponents(p) {
21269
+ return !p.split("/").some((seg) => seg === "..");
21270
+ }
21271
+ var RepoEntrySchema = external_exports.object({
21272
+ name: external_exports.string().regex(REPO_NAME_PATTERN, "repo name must be ASCII lowercase + digits + dash (1-64 chars, no leading/trailing dash)"),
21273
+ path: external_exports.string().min(1, "path must not be empty").refine(isAbsoluteOrTilde, {
21274
+ message: `path must be absolute (e.g. "/home/user/atlas-core") or "~/..." for the operator's home directory`
21275
+ }).refine(hasNoTraversalComponents, { message: 'path must not contain ".." components' }),
21276
+ description: external_exports.string().optional(),
21277
+ defaultBranch: external_exports.string().optional(),
21278
+ addedAt: external_exports.number().int().nonnegative(),
21279
+ updatedAt: external_exports.number().int().nonnegative()
21280
+ });
21281
+ var PortMapSchema = external_exports.record(external_exports.string().min(1), external_exports.number().int().min(1024).max(65535));
21282
+ var SeedSqlFileSchema = external_exports.object({
21283
+ type: external_exports.literal("sql-file"),
21284
+ repo: external_exports.string().min(1),
21285
+ service: external_exports.string().min(1),
21286
+ path: external_exports.string().min(1)
21287
+ });
21288
+ var SeedCommandSchema = external_exports.object({
21289
+ type: external_exports.literal("command"),
21290
+ repo: external_exports.string().min(1),
21291
+ run: external_exports.string().min(1)
21292
+ });
21293
+ var SeedFixtureCopySchema = external_exports.object({
21294
+ type: external_exports.literal("fixture-copy"),
21295
+ repo: external_exports.string().min(1),
21296
+ src: external_exports.string().min(1),
21297
+ dest: external_exports.string().min(1)
21298
+ });
21299
+ var SeedSchema = external_exports.discriminatedUnion("type", [
21300
+ SeedSqlFileSchema,
21301
+ SeedCommandSchema,
21302
+ SeedFixtureCopySchema
21303
+ ]);
21304
+ var RunbookSchema = external_exports.object({
21305
+ name: external_exports.string().regex(WORKSPACE_NAME_PATTERN, "runbook name must be ASCII lowercase + digits + dash (1-64 chars, no leading/trailing dash)"),
21306
+ description: external_exports.string().optional(),
21307
+ repos: external_exports.array(external_exports.string().min(1)).min(1, "runbook must reference at least one repo"),
21308
+ portMap: external_exports.record(external_exports.string().min(1), PortMapSchema).optional(),
21309
+ seeds: external_exports.array(SeedSchema).optional(),
21310
+ env: external_exports.record(external_exports.string().min(1), external_exports.record(external_exports.string().min(1), external_exports.string())).optional(),
21311
+ updatedAt: external_exports.number().int().nonnegative()
21312
+ });
21313
+ var GlobalConfigSchema = external_exports.object({
21314
+ schemaVersion: external_exports.literal(1),
21315
+ repos: external_exports.array(RepoEntrySchema).optional().default([]),
21316
+ runbooks: external_exports.array(RunbookSchema).optional().default([])
21317
+ }).strip().superRefine((val, ctx) => {
21318
+ const repoNames = val.repos.map((r) => r.name);
21319
+ const repoDupes = repoNames.filter((n, i) => repoNames.indexOf(n) !== i);
21320
+ for (const d of repoDupes) {
21321
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, message: `duplicate repo name: "${d}"`, path: ["repos"] });
21322
+ }
21323
+ const rbNames = val.runbooks.map((r) => r.name);
21324
+ const rbDupes = rbNames.filter((n, i) => rbNames.indexOf(n) !== i);
21325
+ for (const d of rbDupes) {
21326
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, message: `duplicate runbook name: "${d}"`, path: ["runbooks"] });
21327
+ }
21328
+ });
21329
+ var DEFAULT_GLOBAL_CONFIG = {
21330
+ schemaVersion: 1,
21331
+ repos: [],
21332
+ runbooks: []
21333
+ };
21334
+
21335
+ // ../core/dist/global-config/store.js
21336
+ import * as fs37 from "node:fs";
21337
+ import * as os23 from "node:os";
21338
+ import * as path41 from "node:path";
21339
+ var GlobalConfigReadError = class extends Error {
21340
+ constructor(configPath, cause) {
21341
+ const msg = cause instanceof Error ? cause.message : String(cause);
21342
+ super(`Failed to read global config at ${configPath}: ${msg}. Run "olam config validate" to diagnose the issue.`);
21343
+ this.name = "GlobalConfigReadError";
21344
+ this.cause = cause;
21345
+ }
21346
+ };
21347
+ function globalConfigPath() {
21348
+ const override = process.env["OLAM_GLOBAL_CONFIG_PATH"];
21349
+ if (override && override.length > 0)
21350
+ return override;
21351
+ return path41.join(os23.homedir(), ".olam", "config.json");
21352
+ }
21353
+ function readGlobalConfig() {
21354
+ const configPath = globalConfigPath();
21355
+ if (!fs37.existsSync(configPath)) {
21356
+ return { ...DEFAULT_GLOBAL_CONFIG };
21357
+ }
21358
+ let raw;
21359
+ try {
21360
+ raw = fs37.readFileSync(configPath, "utf-8");
21361
+ } catch (err) {
21362
+ throw new GlobalConfigReadError(configPath, err);
21363
+ }
21364
+ let parsed;
21365
+ try {
21366
+ parsed = JSON.parse(raw);
21367
+ } catch (err) {
21368
+ throw new GlobalConfigReadError(configPath, err);
21369
+ }
21370
+ try {
21371
+ return GlobalConfigSchema.parse(parsed);
21372
+ } catch (err) {
21373
+ throw new GlobalConfigReadError(configPath, err);
21374
+ }
21375
+ }
21376
+ function writeGlobalConfig(config) {
21377
+ const configPath = globalConfigPath();
21378
+ const validated = GlobalConfigSchema.parse(config);
21379
+ const dir = path41.dirname(configPath);
21380
+ fs37.mkdirSync(dir, { recursive: true });
21381
+ const tmp = `${configPath}.tmp-${process.pid}`;
21382
+ fs37.writeFileSync(tmp, JSON.stringify(validated, null, 2) + "\n", { mode: 420 });
21383
+ fs37.renameSync(tmp, configPath);
21384
+ }
21385
+
21386
+ // ../core/dist/global-config/repos.js
21387
+ import * as fs38 from "node:fs";
21388
+ import * as os24 from "node:os";
21389
+ import * as path42 from "node:path";
21390
+ function expandPath(p) {
21391
+ if (p === "~" || p.startsWith("~/")) {
21392
+ return path42.join(os24.homedir(), p.slice(1));
21393
+ }
21394
+ return p;
21395
+ }
21396
+ function listRepos() {
21397
+ return readGlobalConfig().repos;
21398
+ }
21399
+ function addRepo(entry) {
21400
+ const config = readGlobalConfig();
21401
+ if (config.repos.some((r) => r.name === entry.name)) {
21402
+ throw new Error(`repo "${entry.name}" already registered. Use "olam repos update" to change its path.`);
21403
+ }
21404
+ const resolvedPath = expandPath(entry.path);
21405
+ if (!fs38.existsSync(resolvedPath)) {
21406
+ throw new Error(`path "${entry.path}" does not exist. Verify the path is correct.`);
21407
+ }
21408
+ const now = Date.now();
21409
+ const newEntry = {
21410
+ name: entry.name,
21411
+ path: resolvedPath,
21412
+ addedAt: now,
21413
+ updatedAt: now,
21414
+ ...entry.description !== void 0 ? { description: entry.description } : {},
21415
+ ...entry.defaultBranch !== void 0 ? { defaultBranch: entry.defaultBranch } : {}
21416
+ };
21417
+ writeGlobalConfig({ ...config, repos: [...config.repos, newEntry] });
21418
+ return newEntry;
21419
+ }
21420
+ function removeRepo(name) {
21421
+ const config = readGlobalConfig();
21422
+ if (!config.repos.some((r) => r.name === name)) {
21423
+ throw new Error(`repo "${name}" is not registered. Run "olam repos list" to see registered repos.`);
21424
+ }
21425
+ writeGlobalConfig({ ...config, repos: config.repos.filter((r) => r.name !== name) });
21426
+ }
21427
+ function updateRepo(name, updates) {
21428
+ const config = readGlobalConfig();
21429
+ const idx = config.repos.findIndex((r) => r.name === name);
21430
+ if (idx === -1) {
21431
+ throw new Error(`repo "${name}" is not registered. Run "olam repos list" to see registered repos.`);
21432
+ }
21433
+ const resolvedUpdatePath = updates.path !== void 0 ? expandPath(updates.path) : void 0;
21434
+ if (resolvedUpdatePath !== void 0 && !fs38.existsSync(resolvedUpdatePath)) {
21435
+ throw new Error(`path "${updates.path}" does not exist. Verify the path is correct.`);
21436
+ }
21437
+ const existing = config.repos[idx];
21438
+ const updated = {
21439
+ ...existing,
21440
+ ...resolvedUpdatePath !== void 0 ? { path: resolvedUpdatePath } : {},
21441
+ ...updates.description !== void 0 ? { description: updates.description } : {},
21442
+ ...updates.defaultBranch !== void 0 ? { defaultBranch: updates.defaultBranch } : {},
21443
+ updatedAt: Date.now()
21444
+ };
21445
+ const newRepos = [...config.repos];
21446
+ newRepos[idx] = updated;
21447
+ writeGlobalConfig({ ...config, repos: newRepos });
21448
+ return updated;
21449
+ }
21450
+
21451
+ // src/commands/config.ts
21452
+ var _require3 = createRequire3(import.meta.url);
21453
+ var { parse: parseWithMap } = _require3("json-source-map");
21454
+ function registerConfig(program2) {
21455
+ const config = program2.command("config").description("Manage global olam configuration");
21456
+ config.command("validate [path]").description("Validate ~/.olam/config.json (or a custom path) against the schema").action((filePath) => {
21457
+ const resolvedPath = filePath ?? globalConfigPath();
21458
+ if (!fs39.existsSync(resolvedPath)) {
21459
+ process.stderr.write(`config file not found: ${resolvedPath}
21460
+ `);
21461
+ process.exit(1);
21462
+ }
21463
+ let raw;
21464
+ try {
21465
+ raw = fs39.readFileSync(resolvedPath, "utf-8");
21466
+ } catch (err) {
21467
+ const msg = err instanceof Error ? err.message : String(err);
21468
+ process.stderr.write(`cannot read ${resolvedPath}: ${msg}
21469
+ `);
21470
+ process.exit(1);
21471
+ }
21472
+ let parsed;
21473
+ let pointers;
21474
+ try {
21475
+ const result2 = parseWithMap(raw);
21476
+ parsed = result2.data;
21477
+ pointers = result2.pointers;
21478
+ } catch (err) {
21479
+ const msg = err instanceof Error ? err.message : String(err);
21480
+ process.stderr.write(`${resolvedPath}: invalid JSON \u2014 ${msg}
21481
+ `);
21482
+ process.exit(1);
21483
+ }
21484
+ const result = GlobalConfigSchema.safeParse(parsed);
21485
+ if (result.success) {
21486
+ process.exit(0);
21487
+ }
21488
+ for (const issue of result.error.issues) {
21489
+ const pointer = "/" + issue.path.join("/");
21490
+ const entry = pointers[pointer];
21491
+ const rawLine = entry?.value?.line ?? entry?.key?.line ?? -1;
21492
+ const lineStr = rawLine >= 0 ? `Line ${rawLine + 1}: ` : "";
21493
+ process.stderr.write(`${lineStr}${pointer}: ${issue.message}
21494
+ `);
21495
+ }
21496
+ process.exit(1);
21497
+ });
21498
+ }
21499
+
21500
+ // src/commands/repos.ts
21501
+ import pc24 from "picocolors";
21502
+ init_output();
21503
+ function asMessage(err) {
21504
+ return err instanceof Error ? err.message : String(err);
21505
+ }
21506
+ function registerRepos(program2) {
21507
+ const repos = program2.command("repos").description("Manage the global repo registry");
21508
+ repos.command("list").description("List all registered repos").action(() => {
21509
+ const all = listRepos();
21510
+ if (all.length === 0) {
21511
+ console.log(pc24.dim("0 repo(s) registered. Add one with: olam repos add --name <n> --path <p>"));
21512
+ return;
21513
+ }
21514
+ printHeader(`${all.length} repo(s)`);
21515
+ for (const r of all) {
21516
+ const when = new Date(r.addedAt).toISOString().slice(0, 10);
21517
+ console.log(
21518
+ ` ${pc24.bold(r.name.padEnd(24))} ${r.path.padEnd(48)} ${pc24.dim(when)}`
21519
+ );
21520
+ }
21521
+ });
21522
+ repos.command("add").description("Register a local repo path").requiredOption("--name <name>", "Repo name (lowercase, digits, dash)").requiredOption("--path <path>", "Absolute or ~/... path to the local repo").option("--description <desc>", "Optional human-readable description").option("--default-branch <branch>", "Default branch name (e.g. main)").action((opts) => {
21523
+ try {
21524
+ const entry = addRepo({
21525
+ name: opts.name,
21526
+ path: opts.path,
21527
+ description: opts.description,
21528
+ defaultBranch: opts.defaultBranch
21529
+ });
21530
+ printSuccess(`registered repo "${entry.name}" at ${entry.path}`);
21531
+ } catch (err) {
21532
+ printError(asMessage(err));
21533
+ process.exitCode = 1;
21534
+ }
21535
+ });
21536
+ repos.command("remove").description("Remove a repo from the registry").argument("<name>", "Repo name to remove").action((name) => {
21537
+ try {
21538
+ removeRepo(name);
21539
+ printSuccess(`removed repo "${name}"`);
21540
+ } catch (err) {
21541
+ printError(asMessage(err));
21542
+ process.exitCode = 1;
21543
+ }
21544
+ });
21545
+ repos.command("update").description("Update a registered repo").argument("<name>", "Repo name to update").option("--path <path>", "New path").option("--description <desc>", "New description").option("--default-branch <branch>", "New default branch").action((name, opts) => {
21546
+ try {
21547
+ const entry = updateRepo(name, {
21548
+ path: opts.path,
21549
+ description: opts.description,
21550
+ defaultBranch: opts.defaultBranch
21551
+ });
21552
+ printSuccess(`updated repo "${entry.name}"`);
21553
+ } catch (err) {
21554
+ printError(asMessage(err));
21555
+ process.exitCode = 1;
21556
+ }
21557
+ });
21558
+ }
21559
+
21272
21560
  // src/commands/stop.ts
21273
21561
  init_host_cp();
21274
21562
  async function doStop(_stopFn = stopHostCp) {
@@ -21350,8 +21638,8 @@ var SECRET = process.env["OLAM_MCP_AUTH_SECRET"] ?? "";
21350
21638
  function authHeaders() {
21351
21639
  return SECRET ? { "X-Olam-Mcp-Secret": SECRET } : {};
21352
21640
  }
21353
- async function apiFetch(path44, init = {}) {
21354
- const res = await fetch(`${BASE_URL}${path44}`, {
21641
+ async function apiFetch(path46, init = {}) {
21642
+ const res = await fetch(`${BASE_URL}${path46}`, {
21355
21643
  ...init,
21356
21644
  headers: {
21357
21645
  "Content-Type": "application/json",
@@ -21503,7 +21791,7 @@ function registerMcpAdd(cmd) {
21503
21791
 
21504
21792
  // src/commands/mcp/list.ts
21505
21793
  init_output();
21506
- import pc24 from "picocolors";
21794
+ import pc25 from "picocolors";
21507
21795
  function registerMcpList(cmd) {
21508
21796
  cmd.command("list").description("List all registered MCP credentials").action(async () => {
21509
21797
  const client = getMcpAuthClient();
@@ -21518,8 +21806,8 @@ function registerMcpList(cmd) {
21518
21806
  }
21519
21807
  const mcps = data.mcps ?? [];
21520
21808
  if (mcps.length === 0) {
21521
- console.log(pc24.dim("No MCP credentials registered."));
21522
- console.log(pc24.dim("Add one with: olam mcp add <service>"));
21809
+ console.log(pc25.dim("No MCP credentials registered."));
21810
+ console.log(pc25.dim("Add one with: olam mcp add <service>"));
21523
21811
  return;
21524
21812
  }
21525
21813
  const [c0, c1, c2, c3] = [16, 20, 10, 24];
@@ -21530,12 +21818,12 @@ function registerMcpList(cmd) {
21530
21818
  "ENV VAR".padEnd(c3),
21531
21819
  "STATUS"
21532
21820
  ].join(" ");
21533
- console.log(pc24.dim(header));
21534
- console.log(pc24.dim("\u2500".repeat(header.length)));
21821
+ console.log(pc25.dim(header));
21822
+ console.log(pc25.dim("\u2500".repeat(header.length)));
21535
21823
  for (const m of mcps) {
21536
- const status = !m.validated ? pc24.yellow("unvalidated") : m.expired ? pc24.red("expired") : pc24.green("active");
21824
+ const status = !m.validated ? pc25.yellow("unvalidated") : m.expired ? pc25.red("expired") : pc25.green("active");
21537
21825
  const row = [
21538
- pc24.cyan(m.service.padEnd(c0)),
21826
+ pc25.cyan(m.service.padEnd(c0)),
21539
21827
  m.label.padEnd(c1).slice(0, c1),
21540
21828
  m.type.padEnd(c2),
21541
21829
  (m.envName ?? "\u2014").padEnd(c3).slice(0, c3),
@@ -21563,13 +21851,13 @@ function registerMcpRemove(cmd) {
21563
21851
 
21564
21852
  // src/commands/mcp/status.ts
21565
21853
  init_output();
21566
- import pc25 from "picocolors";
21854
+ import pc26 from "picocolors";
21567
21855
  function formatExpiry(expiresAt) {
21568
21856
  if (!expiresAt) return "\u2014";
21569
21857
  const ms = expiresAt - Date.now();
21570
- if (ms <= 0) return pc25.red("expired");
21858
+ if (ms <= 0) return pc26.red("expired");
21571
21859
  const hours = ms / (1e3 * 60 * 60);
21572
- if (hours < 1) return pc25.yellow(`${Math.ceil(ms / 6e4)}m`);
21860
+ if (hours < 1) return pc26.yellow(`${Math.ceil(ms / 6e4)}m`);
21573
21861
  return `${hours.toFixed(1)}h`;
21574
21862
  }
21575
21863
  function registerMcpStatus(cmd) {
@@ -21586,14 +21874,14 @@ function registerMcpStatus(cmd) {
21586
21874
  const mcps = data.mcps ?? [];
21587
21875
  printHeader(`MCP Credentials (${mcps.length})`);
21588
21876
  if (mcps.length === 0) {
21589
- console.log(pc25.dim("No credentials registered."));
21877
+ console.log(pc26.dim("No credentials registered."));
21590
21878
  return;
21591
21879
  }
21592
21880
  for (const m of mcps) {
21593
- const stateColor = !m.validated ? pc25.yellow : m.expired ? pc25.red : pc25.green;
21881
+ const stateColor = !m.validated ? pc26.yellow : m.expired ? pc26.red : pc26.green;
21594
21882
  const stateLabel = !m.validated ? "unvalidated" : m.expired ? "expired" : "active";
21595
21883
  console.log(`
21596
- ${pc25.cyan(m.service)} ${stateColor(`[${stateLabel}]`)}`);
21884
+ ${pc26.cyan(m.service)} ${stateColor(`[${stateLabel}]`)}`);
21597
21885
  console.log(` label: ${m.label}`);
21598
21886
  console.log(` type: ${m.type}`);
21599
21887
  if (m.envName) console.log(` env var: ${m.envName}`);
@@ -21608,15 +21896,15 @@ function registerMcpStatus(cmd) {
21608
21896
  // src/commands/mcp/import.ts
21609
21897
  init_output();
21610
21898
  import * as readline3 from "node:readline";
21611
- import pc26 from "picocolors";
21899
+ import pc27 from "picocolors";
21612
21900
 
21613
21901
  // src/commands/mcp/import-discovery.ts
21614
- import * as fs37 from "node:fs";
21615
- import * as os23 from "node:os";
21616
- import * as path41 from "node:path";
21902
+ import * as fs40 from "node:fs";
21903
+ import * as os25 from "node:os";
21904
+ import * as path43 from "node:path";
21617
21905
  function readJsonFile(filePath) {
21618
21906
  try {
21619
- const raw = fs37.readFileSync(filePath, "utf-8");
21907
+ const raw = fs40.readFileSync(filePath, "utf-8");
21620
21908
  return JSON.parse(raw);
21621
21909
  } catch {
21622
21910
  return null;
@@ -21645,24 +21933,24 @@ function extractMcpServers(obj, source, sourceLabel) {
21645
21933
  }
21646
21934
  function getClaudeDesktopPath() {
21647
21935
  if (process.platform === "darwin") {
21648
- return path41.join(os23.homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
21936
+ return path43.join(os25.homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
21649
21937
  }
21650
21938
  if (process.platform === "win32") {
21651
- const appData = process.env["APPDATA"] ?? path41.join(os23.homedir(), "AppData", "Roaming");
21652
- return path41.join(appData, "Claude", "claude_desktop_config.json");
21939
+ const appData = process.env["APPDATA"] ?? path43.join(os25.homedir(), "AppData", "Roaming");
21940
+ return path43.join(appData, "Claude", "claude_desktop_config.json");
21653
21941
  }
21654
- return path41.join(os23.homedir(), ".config", "Claude", "claude_desktop_config.json");
21942
+ return path43.join(os25.homedir(), ".config", "Claude", "claude_desktop_config.json");
21655
21943
  }
21656
21944
  function getOlamRepoPaths() {
21657
21945
  const configPaths = [
21658
- path41.join(os23.homedir(), ".olam", "config.yaml"),
21659
- path41.join(process.cwd(), ".olam", "config.yaml")
21946
+ path43.join(os25.homedir(), ".olam", "config.yaml"),
21947
+ path43.join(process.cwd(), ".olam", "config.yaml")
21660
21948
  ];
21661
21949
  const paths = [];
21662
21950
  for (const configPath of configPaths) {
21663
- if (!fs37.existsSync(configPath)) continue;
21951
+ if (!fs40.existsSync(configPath)) continue;
21664
21952
  try {
21665
- const raw = fs37.readFileSync(configPath, "utf-8");
21953
+ const raw = fs40.readFileSync(configPath, "utf-8");
21666
21954
  const repoMatches = [...raw.matchAll(/path:\s*["']?([^\s"'\n]+)/g)];
21667
21955
  for (const m of repoMatches) {
21668
21956
  if (m[1]) paths.push(m[1]);
@@ -21678,7 +21966,7 @@ async function discoverMcpSources(repoPaths) {
21678
21966
  const sources = [];
21679
21967
  const sourceDefs = [
21680
21968
  {
21681
- path: path41.join(os23.homedir(), ".claude.json"),
21969
+ path: path43.join(os25.homedir(), ".claude.json"),
21682
21970
  label: "Claude Code (~/.claude.json)"
21683
21971
  },
21684
21972
  {
@@ -21686,19 +21974,19 @@ async function discoverMcpSources(repoPaths) {
21686
21974
  label: "Claude Desktop"
21687
21975
  },
21688
21976
  {
21689
- path: path41.join(os23.homedir(), ".cursor", "mcp.json"),
21977
+ path: path43.join(os25.homedir(), ".cursor", "mcp.json"),
21690
21978
  label: "Cursor (~/.cursor/mcp.json)"
21691
21979
  },
21692
21980
  {
21693
- path: path41.join(os23.homedir(), ".codeium", "windsurf", "mcp_config.json"),
21981
+ path: path43.join(os25.homedir(), ".codeium", "windsurf", "mcp_config.json"),
21694
21982
  label: "Windsurf (~/.codeium/windsurf/mcp_config.json)"
21695
21983
  }
21696
21984
  ];
21697
21985
  const resolvedRepoPaths = repoPaths ?? getOlamRepoPaths();
21698
21986
  for (const repoPath of resolvedRepoPaths) {
21699
21987
  sourceDefs.push({
21700
- path: path41.join(repoPath, ".mcp.json"),
21701
- label: `.mcp.json (${path41.basename(repoPath)})`
21988
+ path: path43.join(repoPath, ".mcp.json"),
21989
+ label: `.mcp.json (${path43.basename(repoPath)})`
21702
21990
  });
21703
21991
  }
21704
21992
  const reads = await Promise.all(
@@ -21778,13 +22066,13 @@ async function validateMcpEntry(entry) {
21778
22066
  // src/commands/mcp/import.ts
21779
22067
  async function multiSelectPicker(entries) {
21780
22068
  if (entries.length === 0) return [];
21781
- console.log("\n" + pc26.bold("Discovered MCP servers:"));
22069
+ console.log("\n" + pc27.bold("Discovered MCP servers:"));
21782
22070
  entries.forEach((e, i) => {
21783
22071
  console.log(
21784
- ` ${pc26.dim(`[${i + 1}]`)} ${pc26.cyan(e.name.padEnd(20))} ${pc26.dim(e.sourceLabel)}`
22072
+ ` ${pc27.dim(`[${i + 1}]`)} ${pc27.cyan(e.name.padEnd(20))} ${pc27.dim(e.sourceLabel)}`
21785
22073
  );
21786
22074
  });
21787
- console.log("\n" + pc26.dim('Enter numbers to import (e.g. 1,2,3 or "all" or Enter to skip):'));
22075
+ console.log("\n" + pc27.dim('Enter numbers to import (e.g. 1,2,3 or "all" or Enter to skip):'));
21788
22076
  const answer = await new Promise((resolve8) => {
21789
22077
  const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
21790
22078
  rl.question("> ", (ans) => {
@@ -21811,7 +22099,7 @@ function registerMcpImport(cmd) {
21811
22099
  const repoPaths = opts.repoPaths ? opts.repoPaths.split(",").map((s) => s.trim()) : void 0;
21812
22100
  const { entries, sources, durationMs } = await discoverMcpSources(repoPaths);
21813
22101
  if (entries.length === 0) {
21814
- console.log(pc26.dim("No MCP servers found in any source path."));
22102
+ console.log(pc27.dim("No MCP servers found in any source path."));
21815
22103
  return;
21816
22104
  }
21817
22105
  printInfo("Sources", sources.length > 0 ? sources.join(", ") : "none");
@@ -21824,15 +22112,15 @@ function registerMcpImport(cmd) {
21824
22112
  candidates = filtered;
21825
22113
  }
21826
22114
  if (skippedCount > 0) {
21827
- console.log(pc26.dim(`skipped: ${skippedCount} already registered`));
22115
+ console.log(pc27.dim(`skipped: ${skippedCount} already registered`));
21828
22116
  }
21829
22117
  if (candidates.length === 0) {
21830
- console.log(pc26.dim("Nothing new to import. Use --reimport to force."));
22118
+ console.log(pc27.dim("Nothing new to import. Use --reimport to force."));
21831
22119
  return;
21832
22120
  }
21833
22121
  const selected = await multiSelectPicker(candidates);
21834
22122
  if (selected.length === 0) {
21835
- console.log(pc26.dim("No servers selected."));
22123
+ console.log(pc27.dim("No servers selected."));
21836
22124
  return;
21837
22125
  }
21838
22126
  console.log(`
@@ -21843,16 +22131,16 @@ Importing ${selected.length} server(s)\u2026`);
21843
22131
  let validated = false;
21844
22132
  let validationReason = "skipped";
21845
22133
  if (opts.validate !== false) {
21846
- process.stdout.write(` ${pc26.dim("\u2192")} ${entry.name} validating\u2026 `);
22134
+ process.stdout.write(` ${pc27.dim("\u2192")} ${entry.name} validating\u2026 `);
21847
22135
  const vr = await validateMcpEntry(entry);
21848
22136
  validated = vr.validated;
21849
22137
  validationReason = vr.reason;
21850
22138
  process.stdout.write(
21851
- validated ? pc26.green("ok\n") : pc26.yellow(`unvalidated (${vr.reason})
22139
+ validated ? pc27.green("ok\n") : pc27.yellow(`unvalidated (${vr.reason})
21852
22140
  `)
21853
22141
  );
21854
22142
  } else {
21855
- console.log(` ${pc26.dim("\u2192")} ${entry.name} ${pc26.dim("(validation skipped)")}`);
22143
+ console.log(` ${pc27.dim("\u2192")} ${entry.name} ${pc27.dim("(validation skipped)")}`);
21856
22144
  }
21857
22145
  if (validated) validatedCount++;
21858
22146
  const result = await client.staticAdd({
@@ -21867,21 +22155,21 @@ Importing ${selected.length} server(s)\u2026`);
21867
22155
  }
21868
22156
  }
21869
22157
  console.log("");
21870
- console.log(pc26.green(`\u2713 Imported ${importedCount}/${selected.length}`));
22158
+ console.log(pc27.green(`\u2713 Imported ${importedCount}/${selected.length}`));
21871
22159
  if (validatedCount > 0) {
21872
- console.log(pc26.dim(` ${validatedCount} validated, ${importedCount - validatedCount} unvalidated`));
22160
+ console.log(pc27.dim(` ${validatedCount} validated, ${importedCount - validatedCount} unvalidated`));
21873
22161
  }
21874
22162
  });
21875
22163
  }
21876
22164
 
21877
22165
  // src/commands/mcp/revoke.ts
21878
22166
  init_output();
21879
- import pc27 from "picocolors";
22167
+ import pc28 from "picocolors";
21880
22168
  function registerMcpRevoke(cmd) {
21881
22169
  cmd.command("revoke <user> <world> <service>").description("Revoke a user's MCP service entitlement (multi-tenant mode only)").action(async (user, world, service) => {
21882
22170
  const multiTenant = (process.env["OLAM_MCP_MULTI_TENANT"] ?? "").toLowerCase() === "true";
21883
22171
  if (!multiTenant) {
21884
- console.warn(pc27.yellow("\u26A0 revocation only meaningful in multi_tenant mode \u2014 no-op"));
22172
+ console.warn(pc28.yellow("\u26A0 revocation only meaningful in multi_tenant mode \u2014 no-op"));
21885
22173
  return;
21886
22174
  }
21887
22175
  const baseUrl = process.env["OLAM_MCP_AUTH_SERVICE_URL"] ?? "http://127.0.0.1:9998";
@@ -21910,8 +22198,8 @@ function registerMcpRevoke(cmd) {
21910
22198
  process.exitCode = 1;
21911
22199
  return;
21912
22200
  }
21913
- console.log(pc27.green(`\u2713 Revoked: ${user} / ${world} / ${service}`));
21914
- console.log(pc27.dim(" Next fetch-creds call returns 403; token cleared on next merge."));
22201
+ console.log(pc28.green(`\u2713 Revoked: ${user} / ${world} / ${service}`));
22202
+ console.log(pc28.dim(" Next fetch-creds call returns 403; token cleared on next merge."));
21915
22203
  });
21916
22204
  }
21917
22205
 
@@ -21928,18 +22216,18 @@ function registerMcp(program2) {
21928
22216
  }
21929
22217
 
21930
22218
  // src/pleri-config.ts
21931
- import * as fs38 from "node:fs";
21932
- import * as path42 from "node:path";
22219
+ import * as fs41 from "node:fs";
22220
+ import * as path44 from "node:path";
21933
22221
  function isPleriConfigured(configDir = process.env.OLAM_CONFIG_DIR ?? ".olam") {
21934
22222
  if (process.env.PLERI_BASE_URL) {
21935
22223
  return true;
21936
22224
  }
21937
- const configPath = path42.join(configDir, "config.yaml");
21938
- if (!fs38.existsSync(configPath)) {
22225
+ const configPath = path44.join(configDir, "config.yaml");
22226
+ if (!fs41.existsSync(configPath)) {
21939
22227
  return false;
21940
22228
  }
21941
22229
  try {
21942
- const contents = fs38.readFileSync(configPath, "utf8");
22230
+ const contents = fs41.readFileSync(configPath, "utf8");
21943
22231
  return /^[^#\n]*\bpleri:/m.test(contents);
21944
22232
  } catch {
21945
22233
  return false;
@@ -21950,14 +22238,14 @@ function isPleriConfigured(configDir = process.env.OLAM_CONFIG_DIR ?? ".olam") {
21950
22238
  var program = new Command();
21951
22239
  function readCliVersion() {
21952
22240
  try {
21953
- const here = path43.dirname(fileURLToPath4(import.meta.url));
22241
+ const here = path45.dirname(fileURLToPath4(import.meta.url));
21954
22242
  for (const candidate of [
21955
- path43.join(here, "package.json"),
21956
- path43.join(here, "..", "package.json"),
21957
- path43.join(here, "..", "..", "package.json")
22243
+ path45.join(here, "package.json"),
22244
+ path45.join(here, "..", "package.json"),
22245
+ path45.join(here, "..", "..", "package.json")
21958
22246
  ]) {
21959
- if (fs39.existsSync(candidate)) {
21960
- const pkg = JSON.parse(fs39.readFileSync(candidate, "utf-8"));
22247
+ if (fs42.existsSync(candidate)) {
22248
+ const pkg = JSON.parse(fs42.readFileSync(candidate, "utf-8"));
21961
22249
  if (typeof pkg.version === "string" && pkg.version.length > 0) return pkg.version;
21962
22250
  }
21963
22251
  }
@@ -21996,5 +22284,6 @@ registerBegin(program);
21996
22284
  registerStop(program);
21997
22285
  registerWorldUpgrade(program);
21998
22286
  registerMcp(program);
21999
- registerServices(program);
22287
+ registerConfig(program);
22288
+ registerRepos(program);
22000
22289
  program.parse();