@odla-ai/cli 0.25.18 → 0.25.19

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/bin.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  exitCodeFor,
4
4
  redactSecrets,
5
5
  runCli
6
- } from "./chunk-B6DISJPC.js";
6
+ } from "./chunk-S3EJ66VG.js";
7
7
 
8
8
  // src/bin.ts
9
9
  runCli().catch((err) => {
@@ -6033,15 +6033,16 @@ Usage:
6033
6033
  odla-ai runbook rm <slug> [--app <id>]
6034
6034
  odla-ai capabilities [--json]
6035
6035
  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]
6036
+ odla-ai admin ai show [--context <name>] [--platform https://odla.ai] [--email <odla-account>] [--json]
6037
+ odla-ai admin ai models [--context <name>] [--provider <id>] [--json]
6038
+ odla-ai admin ai set <purpose> [--context <name>] [--provider <id>] [--model <id>] [--enabled|--no-enabled]
6039
+ [--max-input-bytes <n>] [--max-output-tokens <n>] [--max-calls-per-run <n>] [--json]
6040
+ odla-ai admin ai set security [--context <name>] --discovery-provider <id> --discovery-model <id>
6041
+ --validation-provider <id> --validation-model <id> [--enabled|--no-enabled] [--json]
6042
+ odla-ai admin ai credentials [--context <name>] [--json]
6043
+ odla-ai admin ai credential set <provider> [--context <name>] (--from-env <NAME>|--stdin)
6044
+ odla-ai admin ai usage [--context <name>] [--app-id <id>] [--env <env>] [--run-id <id>] [--limit <1-500>] [--json]
6045
+ odla-ai admin ai audit [--context <name>] [--limit <1-200>] [--json]
6045
6046
  odla-ai security github connect [--repo owner/name] [--env dev] [--email <odla-account>] [--no-open]
6046
6047
  odla-ai security github disconnect --source <id> [--env dev] [--yes]
6047
6048
  odla-ai security plan [--env dev] [--json]
@@ -7328,8 +7329,250 @@ function addOption(options, name, value) {
7328
7329
  else options[name] = [String(current), String(value)];
7329
7330
  }
7330
7331
 
7332
+ // src/operator-context.ts
7333
+ import { existsSync as existsSync10 } from "fs";
7334
+ import { join as join10, resolve as resolve11 } from "path";
7335
+ import process10 from "process";
7336
+
7337
+ // src/operator-profiles.ts
7338
+ import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
7339
+ import { homedir as homedir2 } from "os";
7340
+ import { dirname as dirname7, join as join9, resolve as resolve10 } from "path";
7341
+ import process9 from "process";
7342
+ function operatorProfileFile() {
7343
+ return resolve10(
7344
+ clean(process9.env.ODLA_CONTEXT_FILE) ?? join9(homedir2(), ".odla", "contexts.json")
7345
+ );
7346
+ }
7347
+ function resolveOperatorProfile(parsed) {
7348
+ const fromFlag = clean(stringOpt(parsed.options.context));
7349
+ const fromEnvironment = clean(process9.env.ODLA_CONTEXT);
7350
+ const name = fromFlag ?? fromEnvironment ?? null;
7351
+ const file = operatorProfileFile();
7352
+ if (!name) {
7353
+ return { name: null, source: "unresolved", file, value: null };
7354
+ }
7355
+ assertOperatorName(name, "context");
7356
+ const profiles = readOperatorProfiles(file);
7357
+ const value = Object.hasOwn(profiles, name) ? profiles[name] : void 0;
7358
+ if (!value) {
7359
+ throw new Error(
7360
+ `operator context "${name}" not found in ${file}; run "odla-ai context list" or save it first`
7361
+ );
7362
+ }
7363
+ return {
7364
+ name,
7365
+ source: fromFlag ? "flag" : "environment",
7366
+ file,
7367
+ value
7368
+ };
7369
+ }
7370
+ function listOperatorProfiles(file = operatorProfileFile()) {
7371
+ return Object.entries(readOperatorProfiles(file)).sort(([a], [b]) => a.localeCompare(b)).map(([name, profile]) => ({ name, ...profile }));
7372
+ }
7373
+ function saveOperatorProfile(name, profile, file = operatorProfileFile()) {
7374
+ assertOperatorName(name, "context");
7375
+ const normalized = validateProfile(profile, `operator context "${name}"`);
7376
+ const profiles = readOperatorProfiles(file);
7377
+ profiles[name] = normalized;
7378
+ writePrivateJson(file, { schemaVersion: 1, profiles });
7379
+ }
7380
+ function removeOperatorProfile(name, file = operatorProfileFile()) {
7381
+ assertOperatorName(name, "context");
7382
+ const profiles = readOperatorProfiles(file);
7383
+ if (!Object.hasOwn(profiles, name)) return false;
7384
+ delete profiles[name];
7385
+ writePrivateJson(file, { schemaVersion: 1, profiles });
7386
+ return true;
7387
+ }
7388
+ function operatorCredentialFiles(selection) {
7389
+ const base = selection.name ? join9(dirname7(selection.file), "profiles", selection.name) : join9(homedir2(), ".odla");
7390
+ return {
7391
+ developer: join9(base, "dev-token.json"),
7392
+ scoped: join9(base, "admin-token.local.json")
7393
+ };
7394
+ }
7395
+ function assertOperatorName(value, label) {
7396
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(value)) {
7397
+ throw new Error(
7398
+ `${label} must contain lowercase letters, numbers, and hyphens`
7399
+ );
7400
+ }
7401
+ }
7402
+ function readOperatorProfiles(file) {
7403
+ if (!existsSync9(file)) return emptyProfiles();
7404
+ let raw;
7405
+ try {
7406
+ raw = JSON.parse(readFileSync7(file, "utf8"));
7407
+ } catch {
7408
+ throw new Error(`operator context file ${file} is not valid JSON`);
7409
+ }
7410
+ if (!raw || typeof raw !== "object" || raw.schemaVersion !== 1 || !raw.profiles || typeof raw.profiles !== "object" || Array.isArray(raw.profiles)) {
7411
+ throw new Error(`operator context file ${file} has an invalid shape`);
7412
+ }
7413
+ const unknown = Object.keys(raw).filter(
7414
+ (key) => !["schemaVersion", "profiles"].includes(key)
7415
+ );
7416
+ if (unknown.length > 0) {
7417
+ throw new Error(
7418
+ `operator context file ${file} contains unsupported field(s): ${unknown.sort().join(", ")}`
7419
+ );
7420
+ }
7421
+ const profiles = emptyProfiles();
7422
+ for (const [name, value] of Object.entries(
7423
+ raw.profiles
7424
+ )) {
7425
+ assertOperatorName(name, "context");
7426
+ profiles[name] = validateProfile(value, `operator context "${name}"`);
7427
+ }
7428
+ return profiles;
7429
+ }
7430
+ function emptyProfiles() {
7431
+ return /* @__PURE__ */ Object.create(null);
7432
+ }
7433
+ function validateProfile(value, label) {
7434
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
7435
+ throw new Error(`${label} has an invalid shape`);
7436
+ }
7437
+ const unknown = Object.keys(value).filter(
7438
+ (key) => !["platform", "app", "environment"].includes(key)
7439
+ );
7440
+ if (unknown.length > 0) {
7441
+ throw new Error(
7442
+ `${label} contains unsupported field(s): ${unknown.sort().join(", ")}; contexts store scope metadata only, never credentials`
7443
+ );
7444
+ }
7445
+ const candidate = value;
7446
+ if (typeof candidate.platform !== "string") {
7447
+ throw new Error(`${label} needs an absolute platform URL`);
7448
+ }
7449
+ const platform = platformAudience(candidate.platform);
7450
+ const app = clean(candidate.app);
7451
+ const environment2 = clean(candidate.environment);
7452
+ if (app) assertOperatorName(app, "app");
7453
+ if (environment2) assertOperatorName(environment2, "environment");
7454
+ return {
7455
+ platform,
7456
+ ...app ? { app } : {},
7457
+ ...environment2 ? { environment: environment2 } : {}
7458
+ };
7459
+ }
7460
+ function clean(value) {
7461
+ const normalized = value?.trim();
7462
+ return normalized || void 0;
7463
+ }
7464
+
7465
+ // src/operator-context.ts
7466
+ var DEFAULT_PLATFORM2 = "https://odla.ai";
7467
+ async function resolveOperatorContext(parsed, options = {}) {
7468
+ const profile = resolveOperatorProfile(parsed);
7469
+ const configArgument = stringOpt(parsed.options.config) ?? "odla.config.mjs";
7470
+ const configPath = resolve11(configArgument);
7471
+ const explicitConfig = parsed.options.config !== void 0;
7472
+ const hasConfig = existsSync10(configPath);
7473
+ if (!hasConfig && (!options.allowMissingConfig || explicitConfig)) {
7474
+ await loadProjectConfig(configArgument);
7475
+ }
7476
+ const loaded = hasConfig ? await loadProjectConfig(configArgument) : void 0;
7477
+ const platformFlag = clean2(stringOpt(parsed.options.platform));
7478
+ const platformEnvironment = clean2(process10.env.ODLA_PLATFORM_URL);
7479
+ const platformValue = platformAudience(
7480
+ platformFlag ?? platformEnvironment ?? profile.value?.platform ?? loaded?.platformUrl ?? DEFAULT_PLATFORM2
7481
+ );
7482
+ const platformSource = platformFlag ? "flag" : platformEnvironment ? "environment" : profile.value ? "profile" : loaded ? "config" : "default";
7483
+ const appFlag = clean2(stringOpt(parsed.options.app));
7484
+ const appEnvironment = clean2(process10.env.ODLA_APP_ID);
7485
+ const appValue = appFlag ?? appEnvironment ?? profile.value?.app ?? loaded?.app.id ?? null;
7486
+ const appSource = appFlag ? "flag" : appEnvironment ? "environment" : profile.value?.app ? "profile" : loaded ? "config" : "unresolved";
7487
+ if (appValue) assertOperatorName(appValue, "app");
7488
+ if (options.requireApp && !appValue) {
7489
+ throw new Error(
7490
+ "app context is unresolved; pass --app <id>, set ODLA_APP_ID, select --context <name>, or run inside a project with odla.config.mjs"
7491
+ );
7492
+ }
7493
+ const envFlag = clean2(stringOpt(parsed.options.env));
7494
+ const envEnvironment = clean2(process10.env.ODLA_ENV);
7495
+ const environmentValue = envFlag ?? envEnvironment ?? profile.value?.environment ?? options.defaultEnvironment ?? null;
7496
+ const environmentSource = envFlag ? "flag" : envEnvironment ? "environment" : profile.value?.environment ? "profile" : options.defaultEnvironment ? "default" : "unresolved";
7497
+ if (environmentValue) {
7498
+ assertOperatorName(environmentValue, "environment");
7499
+ }
7500
+ const rootDir = loaded?.rootDir ?? process10.cwd();
7501
+ const profileCredentials = operatorCredentialFiles(profile);
7502
+ 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;
7503
+ 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;
7504
+ const cfg = loaded ? {
7505
+ ...loaded,
7506
+ platformUrl: platformValue,
7507
+ app: appValue ? {
7508
+ id: appValue,
7509
+ name: appValue === loaded.app.id ? loaded.app.name : appValue
7510
+ } : loaded.app,
7511
+ local: { ...loaded.local, tokenFile }
7512
+ } : {
7513
+ configPath,
7514
+ rootDir,
7515
+ platformUrl: platformValue,
7516
+ dbEndpoint: platformValue,
7517
+ app: {
7518
+ id: appValue ?? "operator",
7519
+ name: appValue ?? "Operator"
7520
+ },
7521
+ envs: environmentValue ? [environmentValue] : [],
7522
+ services: [],
7523
+ local: {
7524
+ tokenFile,
7525
+ credentialsFile: join10(rootDir, ".odla", "credentials.local.json"),
7526
+ devVarsFile: join10(rootDir, ".dev.vars"),
7527
+ gitignore: true
7528
+ }
7529
+ };
7530
+ return {
7531
+ cfg,
7532
+ profile: {
7533
+ name: profile.name,
7534
+ source: profile.source,
7535
+ file: profile.file
7536
+ },
7537
+ config: {
7538
+ path: configPath,
7539
+ status: loaded ? "loaded" : "absent",
7540
+ explicit: explicitConfig
7541
+ },
7542
+ platform: { value: platformValue, source: platformSource },
7543
+ app: { value: appValue, source: appSource },
7544
+ environment: {
7545
+ value: environmentValue,
7546
+ source: environmentSource
7547
+ },
7548
+ credentials: {
7549
+ developerTokenFile: tokenFile,
7550
+ scopedTokenFile
7551
+ }
7552
+ };
7553
+ }
7554
+ function clean2(value) {
7555
+ const normalized = value?.trim();
7556
+ return normalized || void 0;
7557
+ }
7558
+
7331
7559
  // src/admin-command.ts
7332
- async function adminCommand(parsed) {
7560
+ var CONTEXT_OPTIONS = ["platform", "config", "context", "token", "open", "email"];
7561
+ var JSON_OPTIONS = [...CONTEXT_OPTIONS, "json"];
7562
+ var SET_OPTIONS = [
7563
+ ...JSON_OPTIONS,
7564
+ "provider",
7565
+ "model",
7566
+ "enabled",
7567
+ "max-input-bytes",
7568
+ "max-output-tokens",
7569
+ "max-calls-per-run",
7570
+ "discovery-provider",
7571
+ "discovery-model",
7572
+ "validation-provider",
7573
+ "validation-model"
7574
+ ];
7575
+ async function adminCommand(parsed, deps = {}) {
7333
7576
  const area = parsed.positionals[1];
7334
7577
  const action = parsed.positionals[2];
7335
7578
  const credentialSet = action === "credential" && parsed.positionals[3] === "set";
@@ -7340,28 +7583,9 @@ async function adminCommand(parsed) {
7340
7583
  if (area !== "ai" || action !== "show" && action !== "set" && !credentialSet && !credentials && !models && !usage && !audit) {
7341
7584
  throw new Error('unknown admin command. Try "odla-ai admin ai show".');
7342
7585
  }
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);
7586
+ 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;
7587
+ assertArgs(parsed, allowed, credentialSet ? 5 : action === "set" ? 4 : 3);
7588
+ const context = await resolveOperatorContext(parsed, { allowMissingConfig: true });
7365
7589
  await adminAi({
7366
7590
  action: credentialSet ? "credential-set" : credentials ? "credentials" : models ? "models" : usage ? "usage" : audit ? "audit" : action,
7367
7591
  purpose: action === "set" ? parsed.positionals[3] : void 0,
@@ -7371,13 +7595,16 @@ async function adminCommand(parsed) {
7371
7595
  maxInputBytes: numberOpt(parsed.options["max-input-bytes"], "--max-input-bytes"),
7372
7596
  maxOutputTokens: numberOpt(parsed.options["max-output-tokens"], "--max-output-tokens"),
7373
7597
  maxCallsPerRun: numberOpt(parsed.options["max-calls-per-run"], "--max-calls-per-run"),
7374
- platform: stringOpt(parsed.options.platform),
7598
+ platform: context.platform.value,
7599
+ token: stringOpt(parsed.options.token),
7600
+ tokenFile: context.credentials.scopedTokenFile,
7375
7601
  email: stringOpt(parsed.options.email),
7376
7602
  json: parsed.options.json === true,
7377
7603
  open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
7378
7604
  credentialProvider: credentialSet ? parsed.positionals[4] : void 0,
7379
7605
  fromEnv: stringOpt(parsed.options["from-env"]),
7380
7606
  stdin: parsed.options.stdin === true,
7607
+ readStdin: deps.readStdin,
7381
7608
  discoveryProvider: stringOpt(parsed.options["discovery-provider"]),
7382
7609
  discoveryModel: stringOpt(parsed.options["discovery-model"]),
7383
7610
  validationProvider: stringOpt(parsed.options["validation-provider"]),
@@ -7385,7 +7612,10 @@ async function adminCommand(parsed) {
7385
7612
  appId: stringOpt(parsed.options["app-id"]),
7386
7613
  env: stringOpt(parsed.options.env),
7387
7614
  runId: stringOpt(parsed.options["run-id"]),
7388
- limit: numberOpt(parsed.options.limit, "--limit")
7615
+ limit: numberOpt(parsed.options.limit, "--limit"),
7616
+ fetch: deps.fetch,
7617
+ stdout: deps.stdout,
7618
+ openApprovalUrl: deps.openUrl
7389
7619
  });
7390
7620
  }
7391
7621
 
@@ -7537,7 +7767,7 @@ async function appExport(options) {
7537
7767
  }
7538
7768
 
7539
7769
  // src/app-import.ts
7540
- import { readFileSync as readFileSync7 } from "fs";
7770
+ import { readFileSync as readFileSync8 } from "fs";
7541
7771
  import {
7542
7772
  buildImportOps,
7543
7773
  importMutationId,
@@ -7561,7 +7791,7 @@ async function appImport(options) {
7561
7791
  const say = options.json ? (line) => out.error(line) : (line) => out.log(line);
7562
7792
  const doFetch = options.fetch ?? fetch;
7563
7793
  const { tenant } = resolveTenant(cfg, options.env);
7564
- const text = options.file === "-" ? (options.readStdin ?? (() => readFileSync7(0, "utf8")))() : readFileSync7(options.file, "utf8");
7794
+ const text = options.file === "-" ? (options.readStdin ?? (() => readFileSync8(0, "utf8")))() : readFileSync8(options.file, "utf8");
7565
7795
  const { format, sources } = parseImport(text, options.ns);
7566
7796
  if (format === "namespace-map" && options.ns) {
7567
7797
  throw new Error("--ns cannot be combined with a {namespace: rows} file \u2014 the file already names each namespace");
@@ -8024,13 +8254,16 @@ function harnessOption(value, flag) {
8024
8254
 
8025
8255
  // src/cli-project.ts
8026
8256
  var SKILL_OPTS = ["dir", "global", "force", "agent", "harness"];
8027
- function install(parsed, positionals) {
8257
+ function install(parsed, positionals, deps) {
8028
8258
  assertArgs(parsed, SKILL_OPTS, positionals);
8029
8259
  installSkill({
8030
8260
  dir: stringOpt(parsed.options.dir),
8031
8261
  global: parsed.options.global === true,
8032
8262
  harnesses: harnessList(parsed),
8033
- force: parsed.options.force === true
8263
+ force: parsed.options.force === true,
8264
+ homeDir: deps.homeDir,
8265
+ codexHomeDir: deps.codexHomeDir,
8266
+ stdout: deps.stdout
8034
8267
  });
8035
8268
  }
8036
8269
  async function secretsCommand(parsed, deps) {
@@ -8046,6 +8279,7 @@ async function secretsCommand(parsed, deps) {
8046
8279
  token: stringOpt(parsed.options.token),
8047
8280
  email: stringOpt(parsed.options.email),
8048
8281
  yes: parsed.options.yes === true,
8282
+ readStdin: deps.readStdin,
8049
8283
  fetch: deps.fetch,
8050
8284
  stdout: deps.stdout
8051
8285
  };
@@ -8104,8 +8338,8 @@ async function projectCommand(command, parsed, deps) {
8104
8338
  return true;
8105
8339
  }
8106
8340
  if (command === "setup") {
8107
- install(parsed, 1);
8108
- console.log(
8341
+ install(parsed, 1, deps);
8342
+ (deps.stdout ?? console).log(
8109
8343
  "\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
8344
  );
8111
8345
  return true;
@@ -8113,7 +8347,7 @@ async function projectCommand(command, parsed, deps) {
8113
8347
  if (command === "skill") {
8114
8348
  const sub = parsed.positionals[1];
8115
8349
  if (sub !== "install") throw new Error(`unknown skill subcommand "${sub ?? ""}". Try "odla-ai skill install".`);
8116
- install(parsed, 2);
8350
+ install(parsed, 2, deps);
8117
8351
  return true;
8118
8352
  }
8119
8353
  if (command === "secrets") {
@@ -8166,246 +8400,19 @@ async function codeCommand(parsed, dependencies) {
8166
8400
  }
8167
8401
 
8168
8402
  // src/operator-credentials.ts
8169
- import process9 from "process";
8403
+ import process11 from "process";
8170
8404
  function developerTokenStatus(context, parsed, now = Date.now()) {
8171
8405
  const cached = readJsonFile(context.cfg.local.tokenFile);
8172
8406
  const cacheStatus = !cached?.token ? "missing" : cached.platform !== context.platform.value ? "other-platform" : (cached.expiresAt ?? 0) <= now + 6e4 ? "expired" : "valid";
8173
- const source = clean(
8407
+ const source = clean3(
8174
8408
  stringOpt(parsed.options.token)
8175
- ) ? "flag" : clean(process9.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
8409
+ ) ? "flag" : clean3(process11.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
8176
8410
  return {
8177
8411
  source,
8178
8412
  cacheFile: context.cfg.local.tokenFile,
8179
8413
  cacheStatus
8180
8414
  };
8181
8415
  }
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
8416
  function clean3(value) {
8410
8417
  const normalized = value?.trim();
8411
8418
  return normalized || void 0;
@@ -11205,7 +11212,7 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
11205
11212
  return;
11206
11213
  }
11207
11214
  if (command === "admin") {
11208
- await adminCommand(parsed);
11215
+ await adminCommand(parsed, runtime);
11209
11216
  return;
11210
11217
  }
11211
11218
  if (command === "agent") {
@@ -11351,4 +11358,4 @@ export {
11351
11358
  exitCodeFor,
11352
11359
  runCli
11353
11360
  };
11354
- //# sourceMappingURL=chunk-B6DISJPC.js.map
11361
+ //# sourceMappingURL=chunk-S3EJ66VG.js.map