@odla-ai/cli 0.25.18 → 0.25.20

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.
@@ -861,6 +861,7 @@ function isRecord3(value) {
861
861
  import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
862
862
  import { dirname as dirname3, isAbsolute as isAbsolute2, resolve as resolve2 } from "path";
863
863
  import { pathToFileURL } from "url";
864
+ import { appServiceDefinition, appServiceIds } from "@odla-ai/apps";
864
865
 
865
866
  // src/integration-validation.ts
866
867
  function validateIntegrations(cfg, path, defaultServices) {
@@ -948,6 +949,7 @@ async function loadProjectConfig(configPath = "odla.config.mjs") {
948
949
  const dbEndpoint = trimSlash(process.env.ODLA_DB_ENDPOINT || raw.dbEndpoint || platformUrl);
949
950
  const envs = unique2(raw.envs?.length ? raw.envs : DEFAULT_ENVS);
950
951
  const services = unique2(raw.services?.length ? raw.services : DEFAULT_SERVICES);
952
+ validateServices(services, resolved);
951
953
  validateCalendarConfig(raw, envs, services, resolved);
952
954
  const local = {
953
955
  tokenFile: resolve2(rootDir, raw.local?.tokenFile ?? ".odla/dev-token.json"),
@@ -1103,7 +1105,19 @@ function validateCalendarConfig(cfg, envs, services, path) {
1103
1105
  }
1104
1106
  }
1105
1107
  }
1106
- if (enabled && !services.includes("db")) throw new Error(`${path}: calendar service requires the db service`);
1108
+ }
1109
+ function validateServices(services, path) {
1110
+ for (const service of services) {
1111
+ const definition = appServiceDefinition(service);
1112
+ if (!definition) {
1113
+ throw new Error(`${path}: unknown service "${service}" (known: ${appServiceIds().join(", ")})`);
1114
+ }
1115
+ for (const dependency of definition.requires) {
1116
+ if (!services.includes(dependency)) {
1117
+ throw new Error(`${path}: ${service} service requires the ${dependency} service`);
1118
+ }
1119
+ }
1120
+ }
1107
1121
  }
1108
1122
  function assertOnly(value, allowed, label) {
1109
1123
  const extra = Object.keys(value).find((key) => !allowed.includes(key));
@@ -2070,6 +2084,7 @@ async function doctor(options) {
2070
2084
  // src/init.ts
2071
2085
  import { existsSync as existsSync6, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
2072
2086
  import { dirname as dirname4, resolve as resolve4 } from "path";
2087
+ import { appServiceDefinition as appServiceDefinition2, appServiceIds as appServiceIds2 } from "@odla-ai/apps";
2073
2088
  function initProject(options) {
2074
2089
  const out = options.stdout ?? console;
2075
2090
  const rootDir = resolve4(options.rootDir ?? process.cwd());
@@ -2082,7 +2097,13 @@ function initProject(options) {
2082
2097
  }
2083
2098
  const envs = options.envs?.length ? options.envs : ["dev"];
2084
2099
  const services = options.services?.length ? options.services : ["db", "ai"];
2085
- if (services.includes("calendar") && !services.includes("db")) throw new Error("--services calendar requires db");
2100
+ for (const service of services) {
2101
+ const definition = appServiceDefinition2(service);
2102
+ if (!definition) throw new Error(`--services contains unknown service "${service}" (known: ${appServiceIds2().join(", ")})`);
2103
+ for (const dependency of definition.requires) {
2104
+ if (!services.includes(dependency)) throw new Error(`--services ${service} requires ${dependency}`);
2105
+ }
2106
+ }
2086
2107
  const aiProvider = options.aiProvider ?? "anthropic";
2087
2108
  mkdirSync2(dirname4(configPath), { recursive: true });
2088
2109
  mkdirSync2(resolve4(rootDir, "src/odla"), { recursive: true });
@@ -6033,15 +6054,16 @@ Usage:
6033
6054
  odla-ai runbook rm <slug> [--app <id>]
6034
6055
  odla-ai capabilities [--json]
6035
6056
  odla-ai code connect [--env dev|prod] [--email <odla-account>] [--engine auto|container|podman|docker] [--slots <1-64>] [--once]
6036
- odla-ai admin ai show [--platform https://odla.ai] [--email <odla-account>] [--json]
6037
- odla-ai admin ai models [--provider <id>] [--json]
6038
- odla-ai admin ai set <purpose> [--provider <id>] [--model <id>] [--enabled|--no-enabled]
6039
- odla-ai admin ai set security --discovery-provider <id> --discovery-model <id>
6040
- --validation-provider <id> --validation-model <id> [--enabled|--no-enabled]
6041
- odla-ai admin ai credentials [--json]
6042
- odla-ai admin ai credential set <provider> (--from-env <NAME>|--stdin)
6043
- odla-ai admin ai usage [--app-id <id>] [--env <env>] [--run-id <id>] [--limit <1-500>] [--json]
6044
- odla-ai admin ai audit [--limit <1-200>] [--json]
6057
+ odla-ai admin ai show [--context <name>] [--platform https://odla.ai] [--email <odla-account>] [--json]
6058
+ odla-ai admin ai models [--context <name>] [--provider <id>] [--json]
6059
+ odla-ai admin ai set <purpose> [--context <name>] [--provider <id>] [--model <id>] [--enabled|--no-enabled]
6060
+ [--max-input-bytes <n>] [--max-output-tokens <n>] [--max-calls-per-run <n>] [--json]
6061
+ odla-ai admin ai set security [--context <name>] --discovery-provider <id> --discovery-model <id>
6062
+ --validation-provider <id> --validation-model <id> [--enabled|--no-enabled] [--json]
6063
+ odla-ai admin ai credentials [--context <name>] [--json]
6064
+ odla-ai admin ai credential set <provider> [--context <name>] (--from-env <NAME>|--stdin)
6065
+ odla-ai admin ai usage [--context <name>] [--app-id <id>] [--env <env>] [--run-id <id>] [--limit <1-500>] [--json]
6066
+ odla-ai admin ai audit [--context <name>] [--limit <1-200>] [--json]
6045
6067
  odla-ai security github connect [--repo owner/name] [--env dev] [--email <odla-account>] [--no-open]
6046
6068
  odla-ai security github disconnect --source <id> [--env dev] [--yes]
6047
6069
  odla-ai security plan [--env dev] [--json]
@@ -6430,7 +6452,7 @@ function record4(value) {
6430
6452
  }
6431
6453
 
6432
6454
  // src/provision.ts
6433
- import { createAppsClient, tenantIdFor as tenantIdFor4 } from "@odla-ai/apps";
6455
+ import { createAppsClient, orderAppServices, tenantIdFor as tenantIdFor4 } from "@odla-ai/apps";
6434
6456
  import { putSecret as putSecret2 } from "@odla-ai/ai";
6435
6457
  import process8 from "process";
6436
6458
 
@@ -6584,11 +6606,6 @@ async function safeText4(res) {
6584
6606
  // src/provision-helpers.ts
6585
6607
  import { DEFAULT_SECRET_NAMES } from "@odla-ai/ai";
6586
6608
  import { tenantIdFor as tenantIdFor3 } from "@odla-ai/apps";
6587
- function serviceRank(service) {
6588
- if (service === "db") return 0;
6589
- if (service === "calendar") return 2;
6590
- return 1;
6591
- }
6592
6609
  function defaultSecretName(provider) {
6593
6610
  const names = DEFAULT_SECRET_NAMES;
6594
6611
  return names[provider] ?? `${provider}_api_key`;
@@ -6714,7 +6731,7 @@ async function provision(options) {
6714
6731
  for (const env of cfg.envs) {
6715
6732
  await assertTenantAdminAccess(doFetch, cfg, env, token);
6716
6733
  }
6717
- const serviceOrder = [...cfg.services].sort((a, b) => serviceRank(a) - serviceRank(b));
6734
+ const serviceOrder = orderAppServices(cfg.services);
6718
6735
  for (const env of cfg.envs) {
6719
6736
  for (const service of serviceOrder) {
6720
6737
  if (service === "ai") {
@@ -7328,8 +7345,250 @@ function addOption(options, name, value) {
7328
7345
  else options[name] = [String(current), String(value)];
7329
7346
  }
7330
7347
 
7348
+ // src/operator-context.ts
7349
+ import { existsSync as existsSync10 } from "fs";
7350
+ import { join as join10, resolve as resolve11 } from "path";
7351
+ import process10 from "process";
7352
+
7353
+ // src/operator-profiles.ts
7354
+ import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
7355
+ import { homedir as homedir2 } from "os";
7356
+ import { dirname as dirname7, join as join9, resolve as resolve10 } from "path";
7357
+ import process9 from "process";
7358
+ function operatorProfileFile() {
7359
+ return resolve10(
7360
+ clean(process9.env.ODLA_CONTEXT_FILE) ?? join9(homedir2(), ".odla", "contexts.json")
7361
+ );
7362
+ }
7363
+ function resolveOperatorProfile(parsed) {
7364
+ const fromFlag = clean(stringOpt(parsed.options.context));
7365
+ const fromEnvironment = clean(process9.env.ODLA_CONTEXT);
7366
+ const name = fromFlag ?? fromEnvironment ?? null;
7367
+ const file = operatorProfileFile();
7368
+ if (!name) {
7369
+ return { name: null, source: "unresolved", file, value: null };
7370
+ }
7371
+ assertOperatorName(name, "context");
7372
+ const profiles = readOperatorProfiles(file);
7373
+ const value = Object.hasOwn(profiles, name) ? profiles[name] : void 0;
7374
+ if (!value) {
7375
+ throw new Error(
7376
+ `operator context "${name}" not found in ${file}; run "odla-ai context list" or save it first`
7377
+ );
7378
+ }
7379
+ return {
7380
+ name,
7381
+ source: fromFlag ? "flag" : "environment",
7382
+ file,
7383
+ value
7384
+ };
7385
+ }
7386
+ function listOperatorProfiles(file = operatorProfileFile()) {
7387
+ return Object.entries(readOperatorProfiles(file)).sort(([a], [b]) => a.localeCompare(b)).map(([name, profile]) => ({ name, ...profile }));
7388
+ }
7389
+ function saveOperatorProfile(name, profile, file = operatorProfileFile()) {
7390
+ assertOperatorName(name, "context");
7391
+ const normalized = validateProfile(profile, `operator context "${name}"`);
7392
+ const profiles = readOperatorProfiles(file);
7393
+ profiles[name] = normalized;
7394
+ writePrivateJson(file, { schemaVersion: 1, profiles });
7395
+ }
7396
+ function removeOperatorProfile(name, file = operatorProfileFile()) {
7397
+ assertOperatorName(name, "context");
7398
+ const profiles = readOperatorProfiles(file);
7399
+ if (!Object.hasOwn(profiles, name)) return false;
7400
+ delete profiles[name];
7401
+ writePrivateJson(file, { schemaVersion: 1, profiles });
7402
+ return true;
7403
+ }
7404
+ function operatorCredentialFiles(selection) {
7405
+ const base = selection.name ? join9(dirname7(selection.file), "profiles", selection.name) : join9(homedir2(), ".odla");
7406
+ return {
7407
+ developer: join9(base, "dev-token.json"),
7408
+ scoped: join9(base, "admin-token.local.json")
7409
+ };
7410
+ }
7411
+ function assertOperatorName(value, label) {
7412
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(value)) {
7413
+ throw new Error(
7414
+ `${label} must contain lowercase letters, numbers, and hyphens`
7415
+ );
7416
+ }
7417
+ }
7418
+ function readOperatorProfiles(file) {
7419
+ if (!existsSync9(file)) return emptyProfiles();
7420
+ let raw;
7421
+ try {
7422
+ raw = JSON.parse(readFileSync7(file, "utf8"));
7423
+ } catch {
7424
+ throw new Error(`operator context file ${file} is not valid JSON`);
7425
+ }
7426
+ if (!raw || typeof raw !== "object" || raw.schemaVersion !== 1 || !raw.profiles || typeof raw.profiles !== "object" || Array.isArray(raw.profiles)) {
7427
+ throw new Error(`operator context file ${file} has an invalid shape`);
7428
+ }
7429
+ const unknown = Object.keys(raw).filter(
7430
+ (key) => !["schemaVersion", "profiles"].includes(key)
7431
+ );
7432
+ if (unknown.length > 0) {
7433
+ throw new Error(
7434
+ `operator context file ${file} contains unsupported field(s): ${unknown.sort().join(", ")}`
7435
+ );
7436
+ }
7437
+ const profiles = emptyProfiles();
7438
+ for (const [name, value] of Object.entries(
7439
+ raw.profiles
7440
+ )) {
7441
+ assertOperatorName(name, "context");
7442
+ profiles[name] = validateProfile(value, `operator context "${name}"`);
7443
+ }
7444
+ return profiles;
7445
+ }
7446
+ function emptyProfiles() {
7447
+ return /* @__PURE__ */ Object.create(null);
7448
+ }
7449
+ function validateProfile(value, label) {
7450
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
7451
+ throw new Error(`${label} has an invalid shape`);
7452
+ }
7453
+ const unknown = Object.keys(value).filter(
7454
+ (key) => !["platform", "app", "environment"].includes(key)
7455
+ );
7456
+ if (unknown.length > 0) {
7457
+ throw new Error(
7458
+ `${label} contains unsupported field(s): ${unknown.sort().join(", ")}; contexts store scope metadata only, never credentials`
7459
+ );
7460
+ }
7461
+ const candidate = value;
7462
+ if (typeof candidate.platform !== "string") {
7463
+ throw new Error(`${label} needs an absolute platform URL`);
7464
+ }
7465
+ const platform = platformAudience(candidate.platform);
7466
+ const app = clean(candidate.app);
7467
+ const environment2 = clean(candidate.environment);
7468
+ if (app) assertOperatorName(app, "app");
7469
+ if (environment2) assertOperatorName(environment2, "environment");
7470
+ return {
7471
+ platform,
7472
+ ...app ? { app } : {},
7473
+ ...environment2 ? { environment: environment2 } : {}
7474
+ };
7475
+ }
7476
+ function clean(value) {
7477
+ const normalized = value?.trim();
7478
+ return normalized || void 0;
7479
+ }
7480
+
7481
+ // src/operator-context.ts
7482
+ var DEFAULT_PLATFORM2 = "https://odla.ai";
7483
+ async function resolveOperatorContext(parsed, options = {}) {
7484
+ const profile = resolveOperatorProfile(parsed);
7485
+ const configArgument = stringOpt(parsed.options.config) ?? "odla.config.mjs";
7486
+ const configPath = resolve11(configArgument);
7487
+ const explicitConfig = parsed.options.config !== void 0;
7488
+ const hasConfig = existsSync10(configPath);
7489
+ if (!hasConfig && (!options.allowMissingConfig || explicitConfig)) {
7490
+ await loadProjectConfig(configArgument);
7491
+ }
7492
+ const loaded = hasConfig ? await loadProjectConfig(configArgument) : void 0;
7493
+ const platformFlag = clean2(stringOpt(parsed.options.platform));
7494
+ const platformEnvironment = clean2(process10.env.ODLA_PLATFORM_URL);
7495
+ const platformValue = platformAudience(
7496
+ platformFlag ?? platformEnvironment ?? profile.value?.platform ?? loaded?.platformUrl ?? DEFAULT_PLATFORM2
7497
+ );
7498
+ const platformSource = platformFlag ? "flag" : platformEnvironment ? "environment" : profile.value ? "profile" : loaded ? "config" : "default";
7499
+ const appFlag = clean2(stringOpt(parsed.options.app));
7500
+ const appEnvironment = clean2(process10.env.ODLA_APP_ID);
7501
+ const appValue = appFlag ?? appEnvironment ?? profile.value?.app ?? loaded?.app.id ?? null;
7502
+ const appSource = appFlag ? "flag" : appEnvironment ? "environment" : profile.value?.app ? "profile" : loaded ? "config" : "unresolved";
7503
+ if (appValue) assertOperatorName(appValue, "app");
7504
+ if (options.requireApp && !appValue) {
7505
+ throw new Error(
7506
+ "app context is unresolved; pass --app <id>, set ODLA_APP_ID, select --context <name>, or run inside a project with odla.config.mjs"
7507
+ );
7508
+ }
7509
+ const envFlag = clean2(stringOpt(parsed.options.env));
7510
+ const envEnvironment = clean2(process10.env.ODLA_ENV);
7511
+ const environmentValue = envFlag ?? envEnvironment ?? profile.value?.environment ?? options.defaultEnvironment ?? null;
7512
+ const environmentSource = envFlag ? "flag" : envEnvironment ? "environment" : profile.value?.environment ? "profile" : options.defaultEnvironment ? "default" : "unresolved";
7513
+ if (environmentValue) {
7514
+ assertOperatorName(environmentValue, "environment");
7515
+ }
7516
+ const rootDir = loaded?.rootDir ?? process10.cwd();
7517
+ const profileCredentials = operatorCredentialFiles(profile);
7518
+ const tokenFile = clean2(process10.env.ODLA_DEV_TOKEN_FILE) ? resolve11(process10.env.ODLA_DEV_TOKEN_FILE) : profile.name ? profileCredentials.developer : loaded?.local.tokenFile ?? profileCredentials.developer;
7519
+ const scopedTokenFile = clean2(process10.env.ODLA_ADMIN_TOKEN_FILE) ? resolve11(process10.env.ODLA_ADMIN_TOKEN_FILE) : profile.name ? profileCredentials.scoped : loaded ? join10(loaded.rootDir, ".odla", "admin-token.local.json") : profileCredentials.scoped;
7520
+ const cfg = loaded ? {
7521
+ ...loaded,
7522
+ platformUrl: platformValue,
7523
+ app: appValue ? {
7524
+ id: appValue,
7525
+ name: appValue === loaded.app.id ? loaded.app.name : appValue
7526
+ } : loaded.app,
7527
+ local: { ...loaded.local, tokenFile }
7528
+ } : {
7529
+ configPath,
7530
+ rootDir,
7531
+ platformUrl: platformValue,
7532
+ dbEndpoint: platformValue,
7533
+ app: {
7534
+ id: appValue ?? "operator",
7535
+ name: appValue ?? "Operator"
7536
+ },
7537
+ envs: environmentValue ? [environmentValue] : [],
7538
+ services: [],
7539
+ local: {
7540
+ tokenFile,
7541
+ credentialsFile: join10(rootDir, ".odla", "credentials.local.json"),
7542
+ devVarsFile: join10(rootDir, ".dev.vars"),
7543
+ gitignore: true
7544
+ }
7545
+ };
7546
+ return {
7547
+ cfg,
7548
+ profile: {
7549
+ name: profile.name,
7550
+ source: profile.source,
7551
+ file: profile.file
7552
+ },
7553
+ config: {
7554
+ path: configPath,
7555
+ status: loaded ? "loaded" : "absent",
7556
+ explicit: explicitConfig
7557
+ },
7558
+ platform: { value: platformValue, source: platformSource },
7559
+ app: { value: appValue, source: appSource },
7560
+ environment: {
7561
+ value: environmentValue,
7562
+ source: environmentSource
7563
+ },
7564
+ credentials: {
7565
+ developerTokenFile: tokenFile,
7566
+ scopedTokenFile
7567
+ }
7568
+ };
7569
+ }
7570
+ function clean2(value) {
7571
+ const normalized = value?.trim();
7572
+ return normalized || void 0;
7573
+ }
7574
+
7331
7575
  // src/admin-command.ts
7332
- async function adminCommand(parsed) {
7576
+ var CONTEXT_OPTIONS = ["platform", "config", "context", "token", "open", "email"];
7577
+ var JSON_OPTIONS = [...CONTEXT_OPTIONS, "json"];
7578
+ var SET_OPTIONS = [
7579
+ ...JSON_OPTIONS,
7580
+ "provider",
7581
+ "model",
7582
+ "enabled",
7583
+ "max-input-bytes",
7584
+ "max-output-tokens",
7585
+ "max-calls-per-run",
7586
+ "discovery-provider",
7587
+ "discovery-model",
7588
+ "validation-provider",
7589
+ "validation-model"
7590
+ ];
7591
+ async function adminCommand(parsed, deps = {}) {
7333
7592
  const area = parsed.positionals[1];
7334
7593
  const action = parsed.positionals[2];
7335
7594
  const credentialSet = action === "credential" && parsed.positionals[3] === "set";
@@ -7340,28 +7599,9 @@ async function adminCommand(parsed) {
7340
7599
  if (area !== "ai" || action !== "show" && action !== "set" && !credentialSet && !credentials && !models && !usage && !audit) {
7341
7600
  throw new Error('unknown admin command. Try "odla-ai admin ai show".');
7342
7601
  }
7343
- assertArgs(parsed, [
7344
- "platform",
7345
- "json",
7346
- "open",
7347
- "email",
7348
- "provider",
7349
- "model",
7350
- "enabled",
7351
- "max-input-bytes",
7352
- "max-output-tokens",
7353
- "max-calls-per-run",
7354
- "from-env",
7355
- "stdin",
7356
- "discovery-provider",
7357
- "discovery-model",
7358
- "validation-provider",
7359
- "validation-model",
7360
- "app-id",
7361
- "env",
7362
- "run-id",
7363
- "limit"
7364
- ], credentialSet ? 5 : action === "set" ? 4 : 3);
7602
+ const allowed = credentialSet ? [...CONTEXT_OPTIONS, "from-env", "stdin"] : action === "set" ? SET_OPTIONS : models ? [...JSON_OPTIONS, "provider"] : usage ? [...JSON_OPTIONS, "app-id", "env", "run-id", "limit"] : audit ? [...JSON_OPTIONS, "limit"] : JSON_OPTIONS;
7603
+ assertArgs(parsed, allowed, credentialSet ? 5 : action === "set" ? 4 : 3);
7604
+ const context = await resolveOperatorContext(parsed, { allowMissingConfig: true });
7365
7605
  await adminAi({
7366
7606
  action: credentialSet ? "credential-set" : credentials ? "credentials" : models ? "models" : usage ? "usage" : audit ? "audit" : action,
7367
7607
  purpose: action === "set" ? parsed.positionals[3] : void 0,
@@ -7371,13 +7611,16 @@ async function adminCommand(parsed) {
7371
7611
  maxInputBytes: numberOpt(parsed.options["max-input-bytes"], "--max-input-bytes"),
7372
7612
  maxOutputTokens: numberOpt(parsed.options["max-output-tokens"], "--max-output-tokens"),
7373
7613
  maxCallsPerRun: numberOpt(parsed.options["max-calls-per-run"], "--max-calls-per-run"),
7374
- platform: stringOpt(parsed.options.platform),
7614
+ platform: context.platform.value,
7615
+ token: stringOpt(parsed.options.token),
7616
+ tokenFile: context.credentials.scopedTokenFile,
7375
7617
  email: stringOpt(parsed.options.email),
7376
7618
  json: parsed.options.json === true,
7377
7619
  open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
7378
7620
  credentialProvider: credentialSet ? parsed.positionals[4] : void 0,
7379
7621
  fromEnv: stringOpt(parsed.options["from-env"]),
7380
7622
  stdin: parsed.options.stdin === true,
7623
+ readStdin: deps.readStdin,
7381
7624
  discoveryProvider: stringOpt(parsed.options["discovery-provider"]),
7382
7625
  discoveryModel: stringOpt(parsed.options["discovery-model"]),
7383
7626
  validationProvider: stringOpt(parsed.options["validation-provider"]),
@@ -7385,7 +7628,10 @@ async function adminCommand(parsed) {
7385
7628
  appId: stringOpt(parsed.options["app-id"]),
7386
7629
  env: stringOpt(parsed.options.env),
7387
7630
  runId: stringOpt(parsed.options["run-id"]),
7388
- limit: numberOpt(parsed.options.limit, "--limit")
7631
+ limit: numberOpt(parsed.options.limit, "--limit"),
7632
+ fetch: deps.fetch,
7633
+ stdout: deps.stdout,
7634
+ openApprovalUrl: deps.openUrl
7389
7635
  });
7390
7636
  }
7391
7637
 
@@ -7537,7 +7783,7 @@ async function appExport(options) {
7537
7783
  }
7538
7784
 
7539
7785
  // src/app-import.ts
7540
- import { readFileSync as readFileSync7 } from "fs";
7786
+ import { readFileSync as readFileSync8 } from "fs";
7541
7787
  import {
7542
7788
  buildImportOps,
7543
7789
  importMutationId,
@@ -7561,7 +7807,7 @@ async function appImport(options) {
7561
7807
  const say = options.json ? (line) => out.error(line) : (line) => out.log(line);
7562
7808
  const doFetch = options.fetch ?? fetch;
7563
7809
  const { tenant } = resolveTenant(cfg, options.env);
7564
- const text = options.file === "-" ? (options.readStdin ?? (() => readFileSync7(0, "utf8")))() : readFileSync7(options.file, "utf8");
7810
+ const text = options.file === "-" ? (options.readStdin ?? (() => readFileSync8(0, "utf8")))() : readFileSync8(options.file, "utf8");
7565
7811
  const { format, sources } = parseImport(text, options.ns);
7566
7812
  if (format === "namespace-map" && options.ns) {
7567
7813
  throw new Error("--ns cannot be combined with a {namespace: rows} file \u2014 the file already names each namespace");
@@ -8024,13 +8270,16 @@ function harnessOption(value, flag) {
8024
8270
 
8025
8271
  // src/cli-project.ts
8026
8272
  var SKILL_OPTS = ["dir", "global", "force", "agent", "harness"];
8027
- function install(parsed, positionals) {
8273
+ function install(parsed, positionals, deps) {
8028
8274
  assertArgs(parsed, SKILL_OPTS, positionals);
8029
8275
  installSkill({
8030
8276
  dir: stringOpt(parsed.options.dir),
8031
8277
  global: parsed.options.global === true,
8032
8278
  harnesses: harnessList(parsed),
8033
- force: parsed.options.force === true
8279
+ force: parsed.options.force === true,
8280
+ homeDir: deps.homeDir,
8281
+ codexHomeDir: deps.codexHomeDir,
8282
+ stdout: deps.stdout
8034
8283
  });
8035
8284
  }
8036
8285
  async function secretsCommand(parsed, deps) {
@@ -8046,6 +8295,7 @@ async function secretsCommand(parsed, deps) {
8046
8295
  token: stringOpt(parsed.options.token),
8047
8296
  email: stringOpt(parsed.options.email),
8048
8297
  yes: parsed.options.yes === true,
8298
+ readStdin: deps.readStdin,
8049
8299
  fetch: deps.fetch,
8050
8300
  stdout: deps.stdout
8051
8301
  };
@@ -8104,8 +8354,8 @@ async function projectCommand(command, parsed, deps) {
8104
8354
  return true;
8105
8355
  }
8106
8356
  if (command === "setup") {
8107
- install(parsed, 1);
8108
- console.log(
8357
+ install(parsed, 1, deps);
8358
+ (deps.stdout ?? console).log(
8109
8359
  "\nOffline runbooks installed. Open this repo in your coding agent and ask it to set up odla.\nIts native adapter finds the local `odla` skill, chooses build or migration, and drives the CLI."
8110
8360
  );
8111
8361
  return true;
@@ -8113,7 +8363,7 @@ async function projectCommand(command, parsed, deps) {
8113
8363
  if (command === "skill") {
8114
8364
  const sub = parsed.positionals[1];
8115
8365
  if (sub !== "install") throw new Error(`unknown skill subcommand "${sub ?? ""}". Try "odla-ai skill install".`);
8116
- install(parsed, 2);
8366
+ install(parsed, 2, deps);
8117
8367
  return true;
8118
8368
  }
8119
8369
  if (command === "secrets") {
@@ -8166,246 +8416,19 @@ async function codeCommand(parsed, dependencies) {
8166
8416
  }
8167
8417
 
8168
8418
  // src/operator-credentials.ts
8169
- import process9 from "process";
8419
+ import process11 from "process";
8170
8420
  function developerTokenStatus(context, parsed, now = Date.now()) {
8171
8421
  const cached = readJsonFile(context.cfg.local.tokenFile);
8172
8422
  const cacheStatus = !cached?.token ? "missing" : cached.platform !== context.platform.value ? "other-platform" : (cached.expiresAt ?? 0) <= now + 6e4 ? "expired" : "valid";
8173
- const source = clean(
8423
+ const source = clean3(
8174
8424
  stringOpt(parsed.options.token)
8175
- ) ? "flag" : clean(process9.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
8425
+ ) ? "flag" : clean3(process11.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
8176
8426
  return {
8177
8427
  source,
8178
8428
  cacheFile: context.cfg.local.tokenFile,
8179
8429
  cacheStatus
8180
8430
  };
8181
8431
  }
8182
- function clean(value) {
8183
- const normalized = value?.trim();
8184
- return normalized || void 0;
8185
- }
8186
-
8187
- // src/operator-context.ts
8188
- import { existsSync as existsSync10 } from "fs";
8189
- import { join as join10, resolve as resolve11 } from "path";
8190
- import process11 from "process";
8191
-
8192
- // src/operator-profiles.ts
8193
- import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
8194
- import { homedir as homedir2 } from "os";
8195
- import { dirname as dirname7, join as join9, resolve as resolve10 } from "path";
8196
- import process10 from "process";
8197
- function operatorProfileFile() {
8198
- return resolve10(
8199
- clean2(process10.env.ODLA_CONTEXT_FILE) ?? join9(homedir2(), ".odla", "contexts.json")
8200
- );
8201
- }
8202
- function resolveOperatorProfile(parsed) {
8203
- const fromFlag = clean2(stringOpt(parsed.options.context));
8204
- const fromEnvironment = clean2(process10.env.ODLA_CONTEXT);
8205
- const name = fromFlag ?? fromEnvironment ?? null;
8206
- const file = operatorProfileFile();
8207
- if (!name) {
8208
- return { name: null, source: "unresolved", file, value: null };
8209
- }
8210
- assertOperatorName(name, "context");
8211
- const profiles = readOperatorProfiles(file);
8212
- const value = Object.hasOwn(profiles, name) ? profiles[name] : void 0;
8213
- if (!value) {
8214
- throw new Error(
8215
- `operator context "${name}" not found in ${file}; run "odla-ai context list" or save it first`
8216
- );
8217
- }
8218
- return {
8219
- name,
8220
- source: fromFlag ? "flag" : "environment",
8221
- file,
8222
- value
8223
- };
8224
- }
8225
- function listOperatorProfiles(file = operatorProfileFile()) {
8226
- return Object.entries(readOperatorProfiles(file)).sort(([a], [b]) => a.localeCompare(b)).map(([name, profile]) => ({ name, ...profile }));
8227
- }
8228
- function saveOperatorProfile(name, profile, file = operatorProfileFile()) {
8229
- assertOperatorName(name, "context");
8230
- const normalized = validateProfile(profile, `operator context "${name}"`);
8231
- const profiles = readOperatorProfiles(file);
8232
- profiles[name] = normalized;
8233
- writePrivateJson(file, { schemaVersion: 1, profiles });
8234
- }
8235
- function removeOperatorProfile(name, file = operatorProfileFile()) {
8236
- assertOperatorName(name, "context");
8237
- const profiles = readOperatorProfiles(file);
8238
- if (!Object.hasOwn(profiles, name)) return false;
8239
- delete profiles[name];
8240
- writePrivateJson(file, { schemaVersion: 1, profiles });
8241
- return true;
8242
- }
8243
- function operatorCredentialFiles(selection) {
8244
- const base = selection.name ? join9(dirname7(selection.file), "profiles", selection.name) : join9(homedir2(), ".odla");
8245
- return {
8246
- developer: join9(base, "dev-token.json"),
8247
- scoped: join9(base, "admin-token.local.json")
8248
- };
8249
- }
8250
- function assertOperatorName(value, label) {
8251
- if (!/^[a-z0-9][a-z0-9-]*$/.test(value)) {
8252
- throw new Error(
8253
- `${label} must contain lowercase letters, numbers, and hyphens`
8254
- );
8255
- }
8256
- }
8257
- function readOperatorProfiles(file) {
8258
- if (!existsSync9(file)) return emptyProfiles();
8259
- let raw;
8260
- try {
8261
- raw = JSON.parse(readFileSync8(file, "utf8"));
8262
- } catch {
8263
- throw new Error(`operator context file ${file} is not valid JSON`);
8264
- }
8265
- if (!raw || typeof raw !== "object" || raw.schemaVersion !== 1 || !raw.profiles || typeof raw.profiles !== "object" || Array.isArray(raw.profiles)) {
8266
- throw new Error(`operator context file ${file} has an invalid shape`);
8267
- }
8268
- const unknown = Object.keys(raw).filter(
8269
- (key) => !["schemaVersion", "profiles"].includes(key)
8270
- );
8271
- if (unknown.length > 0) {
8272
- throw new Error(
8273
- `operator context file ${file} contains unsupported field(s): ${unknown.sort().join(", ")}`
8274
- );
8275
- }
8276
- const profiles = emptyProfiles();
8277
- for (const [name, value] of Object.entries(
8278
- raw.profiles
8279
- )) {
8280
- assertOperatorName(name, "context");
8281
- profiles[name] = validateProfile(value, `operator context "${name}"`);
8282
- }
8283
- return profiles;
8284
- }
8285
- function emptyProfiles() {
8286
- return /* @__PURE__ */ Object.create(null);
8287
- }
8288
- function validateProfile(value, label) {
8289
- if (!value || typeof value !== "object" || Array.isArray(value)) {
8290
- throw new Error(`${label} has an invalid shape`);
8291
- }
8292
- const unknown = Object.keys(value).filter(
8293
- (key) => !["platform", "app", "environment"].includes(key)
8294
- );
8295
- if (unknown.length > 0) {
8296
- throw new Error(
8297
- `${label} contains unsupported field(s): ${unknown.sort().join(", ")}; contexts store scope metadata only, never credentials`
8298
- );
8299
- }
8300
- const candidate = value;
8301
- if (typeof candidate.platform !== "string") {
8302
- throw new Error(`${label} needs an absolute platform URL`);
8303
- }
8304
- const platform = platformAudience(candidate.platform);
8305
- const app = clean2(candidate.app);
8306
- const environment2 = clean2(candidate.environment);
8307
- if (app) assertOperatorName(app, "app");
8308
- if (environment2) assertOperatorName(environment2, "environment");
8309
- return {
8310
- platform,
8311
- ...app ? { app } : {},
8312
- ...environment2 ? { environment: environment2 } : {}
8313
- };
8314
- }
8315
- function clean2(value) {
8316
- const normalized = value?.trim();
8317
- return normalized || void 0;
8318
- }
8319
-
8320
- // src/operator-context.ts
8321
- var DEFAULT_PLATFORM2 = "https://odla.ai";
8322
- async function resolveOperatorContext(parsed, options = {}) {
8323
- const profile = resolveOperatorProfile(parsed);
8324
- const configArgument = stringOpt(parsed.options.config) ?? "odla.config.mjs";
8325
- const configPath = resolve11(configArgument);
8326
- const explicitConfig = parsed.options.config !== void 0;
8327
- const hasConfig = existsSync10(configPath);
8328
- if (!hasConfig && (!options.allowMissingConfig || explicitConfig)) {
8329
- await loadProjectConfig(configArgument);
8330
- }
8331
- const loaded = hasConfig ? await loadProjectConfig(configArgument) : void 0;
8332
- const platformFlag = clean3(stringOpt(parsed.options.platform));
8333
- const platformEnvironment = clean3(process11.env.ODLA_PLATFORM_URL);
8334
- const platformValue = platformAudience(
8335
- platformFlag ?? platformEnvironment ?? profile.value?.platform ?? loaded?.platformUrl ?? DEFAULT_PLATFORM2
8336
- );
8337
- const platformSource = platformFlag ? "flag" : platformEnvironment ? "environment" : profile.value ? "profile" : loaded ? "config" : "default";
8338
- const appFlag = clean3(stringOpt(parsed.options.app));
8339
- const appEnvironment = clean3(process11.env.ODLA_APP_ID);
8340
- const appValue = appFlag ?? appEnvironment ?? profile.value?.app ?? loaded?.app.id ?? null;
8341
- const appSource = appFlag ? "flag" : appEnvironment ? "environment" : profile.value?.app ? "profile" : loaded ? "config" : "unresolved";
8342
- if (appValue) assertOperatorName(appValue, "app");
8343
- if (options.requireApp && !appValue) {
8344
- throw new Error(
8345
- "app context is unresolved; pass --app <id>, set ODLA_APP_ID, select --context <name>, or run inside a project with odla.config.mjs"
8346
- );
8347
- }
8348
- const envFlag = clean3(stringOpt(parsed.options.env));
8349
- const envEnvironment = clean3(process11.env.ODLA_ENV);
8350
- const environmentValue = envFlag ?? envEnvironment ?? profile.value?.environment ?? options.defaultEnvironment ?? null;
8351
- const environmentSource = envFlag ? "flag" : envEnvironment ? "environment" : profile.value?.environment ? "profile" : options.defaultEnvironment ? "default" : "unresolved";
8352
- if (environmentValue) {
8353
- assertOperatorName(environmentValue, "environment");
8354
- }
8355
- const rootDir = loaded?.rootDir ?? process11.cwd();
8356
- const profileCredentials = operatorCredentialFiles(profile);
8357
- const tokenFile = clean3(process11.env.ODLA_DEV_TOKEN_FILE) ? resolve11(process11.env.ODLA_DEV_TOKEN_FILE) : profile.name ? profileCredentials.developer : loaded?.local.tokenFile ?? profileCredentials.developer;
8358
- const scopedTokenFile = clean3(process11.env.ODLA_ADMIN_TOKEN_FILE) ? resolve11(process11.env.ODLA_ADMIN_TOKEN_FILE) : profile.name ? profileCredentials.scoped : loaded ? join10(loaded.rootDir, ".odla", "admin-token.local.json") : profileCredentials.scoped;
8359
- const cfg = loaded ? {
8360
- ...loaded,
8361
- platformUrl: platformValue,
8362
- app: appValue ? {
8363
- id: appValue,
8364
- name: appValue === loaded.app.id ? loaded.app.name : appValue
8365
- } : loaded.app,
8366
- local: { ...loaded.local, tokenFile }
8367
- } : {
8368
- configPath,
8369
- rootDir,
8370
- platformUrl: platformValue,
8371
- dbEndpoint: platformValue,
8372
- app: {
8373
- id: appValue ?? "operator",
8374
- name: appValue ?? "Operator"
8375
- },
8376
- envs: environmentValue ? [environmentValue] : [],
8377
- services: [],
8378
- local: {
8379
- tokenFile,
8380
- credentialsFile: join10(rootDir, ".odla", "credentials.local.json"),
8381
- devVarsFile: join10(rootDir, ".dev.vars"),
8382
- gitignore: true
8383
- }
8384
- };
8385
- return {
8386
- cfg,
8387
- profile: {
8388
- name: profile.name,
8389
- source: profile.source,
8390
- file: profile.file
8391
- },
8392
- config: {
8393
- path: configPath,
8394
- status: loaded ? "loaded" : "absent",
8395
- explicit: explicitConfig
8396
- },
8397
- platform: { value: platformValue, source: platformSource },
8398
- app: { value: appValue, source: appSource },
8399
- environment: {
8400
- value: environmentValue,
8401
- source: environmentSource
8402
- },
8403
- credentials: {
8404
- developerTokenFile: tokenFile,
8405
- scopedTokenFile
8406
- }
8407
- };
8408
- }
8409
8432
  function clean3(value) {
8410
8433
  const normalized = value?.trim();
8411
8434
  return normalized || void 0;
@@ -11205,7 +11228,7 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
11205
11228
  return;
11206
11229
  }
11207
11230
  if (command === "admin") {
11208
- await adminCommand(parsed);
11231
+ await adminCommand(parsed, runtime);
11209
11232
  return;
11210
11233
  }
11211
11234
  if (command === "agent") {
@@ -11351,4 +11374,4 @@ export {
11351
11374
  exitCodeFor,
11352
11375
  runCli
11353
11376
  };
11354
- //# sourceMappingURL=chunk-B6DISJPC.js.map
11377
+ //# sourceMappingURL=chunk-ONETPJFW.js.map