@odla-ai/cli 0.25.17 → 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.
@@ -5980,11 +5980,21 @@ Usage:
5980
5980
  odla-ai app owners list [--config odla.config.mjs] [--email <odla-account>] [--json]
5981
5981
  odla-ai app owners add <email> [--email <odla-account>] [--json]
5982
5982
  odla-ai app owners remove <email> [--email <odla-account>] [--json]
5983
- odla-ai pm <goal|task|decision|bug> list [--app <id>] [--status <s>] [--severity <s>] [--column <c>] [--q <text>] [--json]
5984
- odla-ai pm <goal|task|decision|bug> add --app <id> --title <t> [--severity high|--column doing|--goal <id>|--description <text>|--body <text>|--proof <text>] [--mutation-id <id>]
5983
+ odla-ai pm goal list [--app <id>] [--status <s>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
5984
+ odla-ai pm task list [--app <id>] [--column <c>] [--goal <id>] [--assignee <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
5985
+ odla-ai pm decision list [--app <id>] [--status <s>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
5986
+ odla-ai pm bug list [--app <id>] [--status <s>] [--severity <s>] [--goal <id>] [--assignee <id>] [--decision <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
5987
+ odla-ai pm goal add --app <id> --title <t> [--status <s>] [--proof <text>] [--target <pct>] [--mutation-id <id>] [--json]
5988
+ odla-ai pm task add --app <id> --title <t> [--column <c>] [--goal <id>] [--assignee <id>] [--description <text>|--body <text>] [--due <epoch-ms>] [--mutation-id <id>] [--json]
5989
+ odla-ai pm decision add --app <id> --title <t> --body <text> [--status <s>] [--mutation-id <id>] [--json]
5990
+ odla-ai pm bug add --app <id> --title <t> (--description <text>|--body <text>) [--status <s>] [--severity <s>] [--goal <id>] [--assignee <id>] [--decision <id>] [--mutation-id <id>] [--json]
5985
5991
  odla-ai pm <goal|task|decision|bug> get <id> [--json]
5986
- odla-ai pm <goal|task|decision|bug> set <id> [--status <s>|--column <c>|--assignee <id>|--no-assignee|--goal <id>|--decision <id>] [--mutation-id <id>]
5987
- odla-ai pm <goal|task|decision|bug> done <id> [--decision <accepted-decision-id>] [--mutation-id <id>]
5992
+ odla-ai pm goal set <id> [--title <t>|--status <s>|--proof <text>|--no-proof|--target <pct>|--no-target] [--mutation-id <id>] [--json]
5993
+ odla-ai pm task set <id> [--title <t>|--column <c>|--rank <n>|--goal <id>|--no-goal|--assignee <id>|--no-assignee|--description <text>|--body <text>|--due <epoch-ms>|--no-due] [--mutation-id <id>] [--json]
5994
+ odla-ai pm decision set <id> [--title <t>|--status <s>|--body <text>] [--mutation-id <id>] [--json]
5995
+ odla-ai pm bug set <id> [--title <t>|--status <s>|--severity <s>|--goal <id>|--no-goal|--assignee <id>|--no-assignee|--decision <id>|--no-decision|--description <text>|--body <text>] [--mutation-id <id>] [--json]
5996
+ odla-ai pm <goal|task|decision> done <id> [--mutation-id <id>]
5997
+ odla-ai pm bug done <id> [--decision <accepted-decision-id>] [--mutation-id <id>]
5988
5998
  odla-ai pm <goal|task|decision|bug> comment <id> --body "..." [--mutation-id <id>]
5989
5999
  odla-ai pm <goal|task|decision|bug> comments <id> [--json]
5990
6000
  odla-ai pm <goal|task|decision|bug> rm <id>
@@ -6023,15 +6033,16 @@ Usage:
6023
6033
  odla-ai runbook rm <slug> [--app <id>]
6024
6034
  odla-ai capabilities [--json]
6025
6035
  odla-ai code connect [--env dev|prod] [--email <odla-account>] [--engine auto|container|podman|docker] [--slots <1-64>] [--once]
6026
- odla-ai admin ai show [--platform https://odla.ai] [--email <odla-account>] [--json]
6027
- odla-ai admin ai models [--provider <id>] [--json]
6028
- odla-ai admin ai set <purpose> [--provider <id>] [--model <id>] [--enabled|--no-enabled]
6029
- odla-ai admin ai set security --discovery-provider <id> --discovery-model <id>
6030
- --validation-provider <id> --validation-model <id> [--enabled|--no-enabled]
6031
- odla-ai admin ai credentials [--json]
6032
- odla-ai admin ai credential set <provider> (--from-env <NAME>|--stdin)
6033
- odla-ai admin ai usage [--app-id <id>] [--env <env>] [--run-id <id>] [--limit <1-500>] [--json]
6034
- 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]
6035
6046
  odla-ai security github connect [--repo owner/name] [--env dev] [--email <odla-account>] [--no-open]
6036
6047
  odla-ai security github disconnect --source <id> [--env dev] [--yes]
6037
6048
  odla-ai security plan [--env dev] [--json]
@@ -7274,8 +7285,8 @@ function parseArgv(argv) {
7274
7285
  }
7275
7286
  return { positionals, options };
7276
7287
  }
7277
- function assertArgs(parsed, allowedOptions, maxPositionals) {
7278
- const allowed = new Set(allowedOptions);
7288
+ function assertArgs(parsed, allowedOptions2, maxPositionals) {
7289
+ const allowed = new Set(allowedOptions2);
7279
7290
  for (const name of Object.keys(parsed.options)) {
7280
7291
  if (!allowed.has(name)) throw new Error(`unknown option "--${name}"; run "odla-ai help" for supported options`);
7281
7292
  }
@@ -7318,8 +7329,250 @@ function addOption(options, name, value) {
7318
7329
  else options[name] = [String(current), String(value)];
7319
7330
  }
7320
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
+
7321
7559
  // src/admin-command.ts
7322
- 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 = {}) {
7323
7576
  const area = parsed.positionals[1];
7324
7577
  const action = parsed.positionals[2];
7325
7578
  const credentialSet = action === "credential" && parsed.positionals[3] === "set";
@@ -7330,28 +7583,9 @@ async function adminCommand(parsed) {
7330
7583
  if (area !== "ai" || action !== "show" && action !== "set" && !credentialSet && !credentials && !models && !usage && !audit) {
7331
7584
  throw new Error('unknown admin command. Try "odla-ai admin ai show".');
7332
7585
  }
7333
- assertArgs(parsed, [
7334
- "platform",
7335
- "json",
7336
- "open",
7337
- "email",
7338
- "provider",
7339
- "model",
7340
- "enabled",
7341
- "max-input-bytes",
7342
- "max-output-tokens",
7343
- "max-calls-per-run",
7344
- "from-env",
7345
- "stdin",
7346
- "discovery-provider",
7347
- "discovery-model",
7348
- "validation-provider",
7349
- "validation-model",
7350
- "app-id",
7351
- "env",
7352
- "run-id",
7353
- "limit"
7354
- ], 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 });
7355
7589
  await adminAi({
7356
7590
  action: credentialSet ? "credential-set" : credentials ? "credentials" : models ? "models" : usage ? "usage" : audit ? "audit" : action,
7357
7591
  purpose: action === "set" ? parsed.positionals[3] : void 0,
@@ -7361,13 +7595,16 @@ async function adminCommand(parsed) {
7361
7595
  maxInputBytes: numberOpt(parsed.options["max-input-bytes"], "--max-input-bytes"),
7362
7596
  maxOutputTokens: numberOpt(parsed.options["max-output-tokens"], "--max-output-tokens"),
7363
7597
  maxCallsPerRun: numberOpt(parsed.options["max-calls-per-run"], "--max-calls-per-run"),
7364
- platform: stringOpt(parsed.options.platform),
7598
+ platform: context.platform.value,
7599
+ token: stringOpt(parsed.options.token),
7600
+ tokenFile: context.credentials.scopedTokenFile,
7365
7601
  email: stringOpt(parsed.options.email),
7366
7602
  json: parsed.options.json === true,
7367
7603
  open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
7368
7604
  credentialProvider: credentialSet ? parsed.positionals[4] : void 0,
7369
7605
  fromEnv: stringOpt(parsed.options["from-env"]),
7370
7606
  stdin: parsed.options.stdin === true,
7607
+ readStdin: deps.readStdin,
7371
7608
  discoveryProvider: stringOpt(parsed.options["discovery-provider"]),
7372
7609
  discoveryModel: stringOpt(parsed.options["discovery-model"]),
7373
7610
  validationProvider: stringOpt(parsed.options["validation-provider"]),
@@ -7375,7 +7612,10 @@ async function adminCommand(parsed) {
7375
7612
  appId: stringOpt(parsed.options["app-id"]),
7376
7613
  env: stringOpt(parsed.options.env),
7377
7614
  runId: stringOpt(parsed.options["run-id"]),
7378
- 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
7379
7619
  });
7380
7620
  }
7381
7621
 
@@ -7527,7 +7767,7 @@ async function appExport(options) {
7527
7767
  }
7528
7768
 
7529
7769
  // src/app-import.ts
7530
- import { readFileSync as readFileSync7 } from "fs";
7770
+ import { readFileSync as readFileSync8 } from "fs";
7531
7771
  import {
7532
7772
  buildImportOps,
7533
7773
  importMutationId,
@@ -7551,7 +7791,7 @@ async function appImport(options) {
7551
7791
  const say = options.json ? (line) => out.error(line) : (line) => out.log(line);
7552
7792
  const doFetch = options.fetch ?? fetch;
7553
7793
  const { tenant } = resolveTenant(cfg, options.env);
7554
- 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");
7555
7795
  const { format, sources } = parseImport(text, options.ns);
7556
7796
  if (format === "namespace-map" && options.ns) {
7557
7797
  throw new Error("--ns cannot be combined with a {namespace: rows} file \u2014 the file already names each namespace");
@@ -8014,13 +8254,16 @@ function harnessOption(value, flag) {
8014
8254
 
8015
8255
  // src/cli-project.ts
8016
8256
  var SKILL_OPTS = ["dir", "global", "force", "agent", "harness"];
8017
- function install(parsed, positionals) {
8257
+ function install(parsed, positionals, deps) {
8018
8258
  assertArgs(parsed, SKILL_OPTS, positionals);
8019
8259
  installSkill({
8020
8260
  dir: stringOpt(parsed.options.dir),
8021
8261
  global: parsed.options.global === true,
8022
8262
  harnesses: harnessList(parsed),
8023
- force: parsed.options.force === true
8263
+ force: parsed.options.force === true,
8264
+ homeDir: deps.homeDir,
8265
+ codexHomeDir: deps.codexHomeDir,
8266
+ stdout: deps.stdout
8024
8267
  });
8025
8268
  }
8026
8269
  async function secretsCommand(parsed, deps) {
@@ -8036,6 +8279,7 @@ async function secretsCommand(parsed, deps) {
8036
8279
  token: stringOpt(parsed.options.token),
8037
8280
  email: stringOpt(parsed.options.email),
8038
8281
  yes: parsed.options.yes === true,
8282
+ readStdin: deps.readStdin,
8039
8283
  fetch: deps.fetch,
8040
8284
  stdout: deps.stdout
8041
8285
  };
@@ -8094,8 +8338,8 @@ async function projectCommand(command, parsed, deps) {
8094
8338
  return true;
8095
8339
  }
8096
8340
  if (command === "setup") {
8097
- install(parsed, 1);
8098
- console.log(
8341
+ install(parsed, 1, deps);
8342
+ (deps.stdout ?? console).log(
8099
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."
8100
8344
  );
8101
8345
  return true;
@@ -8103,7 +8347,7 @@ async function projectCommand(command, parsed, deps) {
8103
8347
  if (command === "skill") {
8104
8348
  const sub = parsed.positionals[1];
8105
8349
  if (sub !== "install") throw new Error(`unknown skill subcommand "${sub ?? ""}". Try "odla-ai skill install".`);
8106
- install(parsed, 2);
8350
+ install(parsed, 2, deps);
8107
8351
  return true;
8108
8352
  }
8109
8353
  if (command === "secrets") {
@@ -8156,246 +8400,19 @@ async function codeCommand(parsed, dependencies) {
8156
8400
  }
8157
8401
 
8158
8402
  // src/operator-credentials.ts
8159
- import process9 from "process";
8403
+ import process11 from "process";
8160
8404
  function developerTokenStatus(context, parsed, now = Date.now()) {
8161
8405
  const cached = readJsonFile(context.cfg.local.tokenFile);
8162
8406
  const cacheStatus = !cached?.token ? "missing" : cached.platform !== context.platform.value ? "other-platform" : (cached.expiresAt ?? 0) <= now + 6e4 ? "expired" : "valid";
8163
- const source = clean(
8407
+ const source = clean3(
8164
8408
  stringOpt(parsed.options.token)
8165
- ) ? "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";
8166
8410
  return {
8167
8411
  source,
8168
8412
  cacheFile: context.cfg.local.tokenFile,
8169
8413
  cacheStatus
8170
8414
  };
8171
8415
  }
8172
- function clean(value) {
8173
- const normalized = value?.trim();
8174
- return normalized || void 0;
8175
- }
8176
-
8177
- // src/operator-context.ts
8178
- import { existsSync as existsSync10 } from "fs";
8179
- import { join as join10, resolve as resolve11 } from "path";
8180
- import process11 from "process";
8181
-
8182
- // src/operator-profiles.ts
8183
- import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
8184
- import { homedir as homedir2 } from "os";
8185
- import { dirname as dirname7, join as join9, resolve as resolve10 } from "path";
8186
- import process10 from "process";
8187
- function operatorProfileFile() {
8188
- return resolve10(
8189
- clean2(process10.env.ODLA_CONTEXT_FILE) ?? join9(homedir2(), ".odla", "contexts.json")
8190
- );
8191
- }
8192
- function resolveOperatorProfile(parsed) {
8193
- const fromFlag = clean2(stringOpt(parsed.options.context));
8194
- const fromEnvironment = clean2(process10.env.ODLA_CONTEXT);
8195
- const name = fromFlag ?? fromEnvironment ?? null;
8196
- const file = operatorProfileFile();
8197
- if (!name) {
8198
- return { name: null, source: "unresolved", file, value: null };
8199
- }
8200
- assertOperatorName(name, "context");
8201
- const profiles = readOperatorProfiles(file);
8202
- const value = Object.hasOwn(profiles, name) ? profiles[name] : void 0;
8203
- if (!value) {
8204
- throw new Error(
8205
- `operator context "${name}" not found in ${file}; run "odla-ai context list" or save it first`
8206
- );
8207
- }
8208
- return {
8209
- name,
8210
- source: fromFlag ? "flag" : "environment",
8211
- file,
8212
- value
8213
- };
8214
- }
8215
- function listOperatorProfiles(file = operatorProfileFile()) {
8216
- return Object.entries(readOperatorProfiles(file)).sort(([a], [b]) => a.localeCompare(b)).map(([name, profile]) => ({ name, ...profile }));
8217
- }
8218
- function saveOperatorProfile(name, profile, file = operatorProfileFile()) {
8219
- assertOperatorName(name, "context");
8220
- const normalized = validateProfile(profile, `operator context "${name}"`);
8221
- const profiles = readOperatorProfiles(file);
8222
- profiles[name] = normalized;
8223
- writePrivateJson(file, { schemaVersion: 1, profiles });
8224
- }
8225
- function removeOperatorProfile(name, file = operatorProfileFile()) {
8226
- assertOperatorName(name, "context");
8227
- const profiles = readOperatorProfiles(file);
8228
- if (!Object.hasOwn(profiles, name)) return false;
8229
- delete profiles[name];
8230
- writePrivateJson(file, { schemaVersion: 1, profiles });
8231
- return true;
8232
- }
8233
- function operatorCredentialFiles(selection) {
8234
- const base = selection.name ? join9(dirname7(selection.file), "profiles", selection.name) : join9(homedir2(), ".odla");
8235
- return {
8236
- developer: join9(base, "dev-token.json"),
8237
- scoped: join9(base, "admin-token.local.json")
8238
- };
8239
- }
8240
- function assertOperatorName(value, label) {
8241
- if (!/^[a-z0-9][a-z0-9-]*$/.test(value)) {
8242
- throw new Error(
8243
- `${label} must contain lowercase letters, numbers, and hyphens`
8244
- );
8245
- }
8246
- }
8247
- function readOperatorProfiles(file) {
8248
- if (!existsSync9(file)) return emptyProfiles();
8249
- let raw;
8250
- try {
8251
- raw = JSON.parse(readFileSync8(file, "utf8"));
8252
- } catch {
8253
- throw new Error(`operator context file ${file} is not valid JSON`);
8254
- }
8255
- if (!raw || typeof raw !== "object" || raw.schemaVersion !== 1 || !raw.profiles || typeof raw.profiles !== "object" || Array.isArray(raw.profiles)) {
8256
- throw new Error(`operator context file ${file} has an invalid shape`);
8257
- }
8258
- const unknown = Object.keys(raw).filter(
8259
- (key) => !["schemaVersion", "profiles"].includes(key)
8260
- );
8261
- if (unknown.length > 0) {
8262
- throw new Error(
8263
- `operator context file ${file} contains unsupported field(s): ${unknown.sort().join(", ")}`
8264
- );
8265
- }
8266
- const profiles = emptyProfiles();
8267
- for (const [name, value] of Object.entries(
8268
- raw.profiles
8269
- )) {
8270
- assertOperatorName(name, "context");
8271
- profiles[name] = validateProfile(value, `operator context "${name}"`);
8272
- }
8273
- return profiles;
8274
- }
8275
- function emptyProfiles() {
8276
- return /* @__PURE__ */ Object.create(null);
8277
- }
8278
- function validateProfile(value, label) {
8279
- if (!value || typeof value !== "object" || Array.isArray(value)) {
8280
- throw new Error(`${label} has an invalid shape`);
8281
- }
8282
- const unknown = Object.keys(value).filter(
8283
- (key) => !["platform", "app", "environment"].includes(key)
8284
- );
8285
- if (unknown.length > 0) {
8286
- throw new Error(
8287
- `${label} contains unsupported field(s): ${unknown.sort().join(", ")}; contexts store scope metadata only, never credentials`
8288
- );
8289
- }
8290
- const candidate = value;
8291
- if (typeof candidate.platform !== "string") {
8292
- throw new Error(`${label} needs an absolute platform URL`);
8293
- }
8294
- const platform = platformAudience(candidate.platform);
8295
- const app = clean2(candidate.app);
8296
- const environment2 = clean2(candidate.environment);
8297
- if (app) assertOperatorName(app, "app");
8298
- if (environment2) assertOperatorName(environment2, "environment");
8299
- return {
8300
- platform,
8301
- ...app ? { app } : {},
8302
- ...environment2 ? { environment: environment2 } : {}
8303
- };
8304
- }
8305
- function clean2(value) {
8306
- const normalized = value?.trim();
8307
- return normalized || void 0;
8308
- }
8309
-
8310
- // src/operator-context.ts
8311
- var DEFAULT_PLATFORM2 = "https://odla.ai";
8312
- async function resolveOperatorContext(parsed, options = {}) {
8313
- const profile = resolveOperatorProfile(parsed);
8314
- const configArgument = stringOpt(parsed.options.config) ?? "odla.config.mjs";
8315
- const configPath = resolve11(configArgument);
8316
- const explicitConfig = parsed.options.config !== void 0;
8317
- const hasConfig = existsSync10(configPath);
8318
- if (!hasConfig && (!options.allowMissingConfig || explicitConfig)) {
8319
- await loadProjectConfig(configArgument);
8320
- }
8321
- const loaded = hasConfig ? await loadProjectConfig(configArgument) : void 0;
8322
- const platformFlag = clean3(stringOpt(parsed.options.platform));
8323
- const platformEnvironment = clean3(process11.env.ODLA_PLATFORM_URL);
8324
- const platformValue = platformAudience(
8325
- platformFlag ?? platformEnvironment ?? profile.value?.platform ?? loaded?.platformUrl ?? DEFAULT_PLATFORM2
8326
- );
8327
- const platformSource = platformFlag ? "flag" : platformEnvironment ? "environment" : profile.value ? "profile" : loaded ? "config" : "default";
8328
- const appFlag = clean3(stringOpt(parsed.options.app));
8329
- const appEnvironment = clean3(process11.env.ODLA_APP_ID);
8330
- const appValue = appFlag ?? appEnvironment ?? profile.value?.app ?? loaded?.app.id ?? null;
8331
- const appSource = appFlag ? "flag" : appEnvironment ? "environment" : profile.value?.app ? "profile" : loaded ? "config" : "unresolved";
8332
- if (appValue) assertOperatorName(appValue, "app");
8333
- if (options.requireApp && !appValue) {
8334
- throw new Error(
8335
- "app context is unresolved; pass --app <id>, set ODLA_APP_ID, select --context <name>, or run inside a project with odla.config.mjs"
8336
- );
8337
- }
8338
- const envFlag = clean3(stringOpt(parsed.options.env));
8339
- const envEnvironment = clean3(process11.env.ODLA_ENV);
8340
- const environmentValue = envFlag ?? envEnvironment ?? profile.value?.environment ?? options.defaultEnvironment ?? null;
8341
- const environmentSource = envFlag ? "flag" : envEnvironment ? "environment" : profile.value?.environment ? "profile" : options.defaultEnvironment ? "default" : "unresolved";
8342
- if (environmentValue) {
8343
- assertOperatorName(environmentValue, "environment");
8344
- }
8345
- const rootDir = loaded?.rootDir ?? process11.cwd();
8346
- const profileCredentials = operatorCredentialFiles(profile);
8347
- 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;
8348
- 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;
8349
- const cfg = loaded ? {
8350
- ...loaded,
8351
- platformUrl: platformValue,
8352
- app: appValue ? {
8353
- id: appValue,
8354
- name: appValue === loaded.app.id ? loaded.app.name : appValue
8355
- } : loaded.app,
8356
- local: { ...loaded.local, tokenFile }
8357
- } : {
8358
- configPath,
8359
- rootDir,
8360
- platformUrl: platformValue,
8361
- dbEndpoint: platformValue,
8362
- app: {
8363
- id: appValue ?? "operator",
8364
- name: appValue ?? "Operator"
8365
- },
8366
- envs: environmentValue ? [environmentValue] : [],
8367
- services: [],
8368
- local: {
8369
- tokenFile,
8370
- credentialsFile: join10(rootDir, ".odla", "credentials.local.json"),
8371
- devVarsFile: join10(rootDir, ".dev.vars"),
8372
- gitignore: true
8373
- }
8374
- };
8375
- return {
8376
- cfg,
8377
- profile: {
8378
- name: profile.name,
8379
- source: profile.source,
8380
- file: profile.file
8381
- },
8382
- config: {
8383
- path: configPath,
8384
- status: loaded ? "loaded" : "absent",
8385
- explicit: explicitConfig
8386
- },
8387
- platform: { value: platformValue, source: platformSource },
8388
- app: { value: appValue, source: appSource },
8389
- environment: {
8390
- value: environmentValue,
8391
- source: environmentSource
8392
- },
8393
- credentials: {
8394
- developerTokenFile: tokenFile,
8395
- scopedTokenFile
8396
- }
8397
- };
8398
- }
8399
8416
  function clean3(value) {
8400
8417
  const normalized = value?.trim();
8401
8418
  return normalized || void 0;
@@ -9239,33 +9256,53 @@ var ALIASES = {
9239
9256
  decision: "decision",
9240
9257
  bug: "bug"
9241
9258
  };
9242
- var ALLOWED2 = [
9243
- "config",
9244
- "token",
9245
- "email",
9246
- "json",
9247
- "app",
9248
- "title",
9249
- "status",
9250
- "severity",
9251
- "column",
9252
- "rank",
9253
- "goal",
9254
- "assignee",
9255
- "due",
9256
- "proof",
9257
- "body",
9258
- "target",
9259
- "description",
9260
- "desc",
9261
- "decision",
9262
- "limit",
9263
- "offset",
9264
- "q",
9265
- "mutation-id",
9266
- "platform",
9267
- "context"
9268
- ];
9259
+ var COMMON_OPTIONS = ["config", "token", "email", "json", "platform", "context"];
9260
+ var ACTION_OPTIONS = {
9261
+ list: ["app", "q", "limit", "offset"],
9262
+ add: ["app", "title", "mutation-id"],
9263
+ get: [],
9264
+ set: ["mutation-id"],
9265
+ done: ["mutation-id"],
9266
+ comment: ["body", "mutation-id"],
9267
+ comments: [],
9268
+ rm: []
9269
+ };
9270
+ var ENTITY_OPTIONS = {
9271
+ goal: {
9272
+ list: ["status"],
9273
+ add: ["status", "proof", "target"],
9274
+ set: ["title", "status", "proof", "target"],
9275
+ done: []
9276
+ },
9277
+ task: {
9278
+ list: ["column", "goal", "assignee"],
9279
+ add: ["column", "goal", "assignee", "due", "description", "desc", "body"],
9280
+ set: ["title", "column", "rank", "goal", "assignee", "due", "description", "desc", "body"],
9281
+ done: []
9282
+ },
9283
+ decision: {
9284
+ list: ["status"],
9285
+ add: ["status", "body"],
9286
+ set: ["title", "status", "body"],
9287
+ done: []
9288
+ },
9289
+ bug: {
9290
+ list: ["status", "severity", "goal", "assignee", "decision"],
9291
+ add: ["status", "severity", "goal", "assignee", "decision", "description", "desc", "body"],
9292
+ set: ["title", "status", "severity", "goal", "assignee", "decision", "description", "desc", "body"],
9293
+ done: ["decision"]
9294
+ }
9295
+ };
9296
+ function canonicalAction(action) {
9297
+ if (action === "create") return "add";
9298
+ if (action === "update" || action === "status" || action === "move") return "set";
9299
+ if (action === "delete") return "rm";
9300
+ return action in ACTION_OPTIONS ? action : null;
9301
+ }
9302
+ function allowedOptions(entity, action) {
9303
+ const entityOptions = action === "list" || action === "add" || action === "set" || action === "done" ? ENTITY_OPTIONS[entity][action] : [];
9304
+ return [...COMMON_OPTIONS, ...ACTION_OPTIONS[action], ...entityOptions];
9305
+ }
9269
9306
  function requireId2(id, action) {
9270
9307
  if (!id) throw new Error(`"pm ... ${action}" needs an item id`);
9271
9308
  return id;
@@ -9297,27 +9334,25 @@ async function buildContext2(parsed, deps) {
9297
9334
  async function pmCommand(parsed, deps = {}) {
9298
9335
  const word = parsed.positionals[1] ?? "";
9299
9336
  if (word === "handoff") {
9300
- assertArgs(parsed, ALLOWED2, 2);
9337
+ assertArgs(parsed, [...COMMON_OPTIONS, "app"], 2);
9301
9338
  return pmHandoff(await buildContext2(parsed, deps), parsed);
9302
9339
  }
9303
9340
  const entity = ALIASES[word];
9304
9341
  if (!entity) throw new Error(`unknown pm entity "${word}". Try "odla-ai pm bug list" (goal|task|decision|bug).`);
9305
- const action = parsed.positionals[2] ?? "list";
9306
- assertArgs(parsed, ALLOWED2, 4);
9342
+ const requestedAction = parsed.positionals[2] ?? "list";
9343
+ const action = canonicalAction(requestedAction);
9344
+ if (!action) throw new Error(`unknown pm action "${requestedAction}". Try list|add|get|set|done|comment|comments|rm.`);
9345
+ assertArgs(parsed, allowedOptions(entity, action), 4);
9307
9346
  const ctx = await buildContext2(parsed, deps);
9308
9347
  const id = parsed.positionals[3];
9309
9348
  switch (action) {
9310
9349
  case "list":
9311
9350
  return pmList(ctx, entity, parsed);
9312
9351
  case "add":
9313
- case "create":
9314
9352
  return pmAdd(ctx, entity, parsed);
9315
9353
  case "get":
9316
9354
  return pmGet(ctx, entity, requireId2(id, action));
9317
9355
  case "set":
9318
- case "update":
9319
- case "status":
9320
- case "move":
9321
9356
  return pmSet(ctx, entity, requireId2(id, action), parsed);
9322
9357
  case "done":
9323
9358
  return pmDone(ctx, entity, requireId2(id, action), parsed);
@@ -9326,10 +9361,7 @@ async function pmCommand(parsed, deps = {}) {
9326
9361
  case "comments":
9327
9362
  return pmComments(ctx, entity, requireId2(id, action));
9328
9363
  case "rm":
9329
- case "delete":
9330
9364
  return pmRemove(ctx, entity, requireId2(id, action));
9331
- default:
9332
- throw new Error(`unknown pm action "${action}". Try list|add|get|set|done|comment|comments|rm.`);
9333
9365
  }
9334
9366
  }
9335
9367
 
@@ -9748,7 +9780,7 @@ function recordInvocation(parsed) {
9748
9780
  try {
9749
9781
  const entry = {
9750
9782
  path: invocationPath(parsed.positionals),
9751
- options: Object.keys(parsed.options).sort()
9783
+ options: Object.entries(parsed.options).map(([name, value]) => value === false ? `no-${name}` : name).sort()
9752
9784
  };
9753
9785
  if (!entry.path.length) return;
9754
9786
  appendFileSync(file, `${JSON.stringify(entry)}
@@ -10521,7 +10553,7 @@ async function whoamiCommand(parsed, deps = {}) {
10521
10553
  }
10522
10554
 
10523
10555
  // src/runbook-command.ts
10524
- var ALLOWED3 = [
10556
+ var ALLOWED2 = [
10525
10557
  "config",
10526
10558
  "token",
10527
10559
  "email",
@@ -10609,7 +10641,7 @@ async function buildContext3(parsed, deps, action) {
10609
10641
  }
10610
10642
  async function runbookCommand(parsed, deps = {}) {
10611
10643
  const action = parsed.positionals[1] ?? "list";
10612
- assertArgs(parsed, ALLOWED3, action === "ask" || action === "search" ? 64 : 4);
10644
+ assertArgs(parsed, ALLOWED2, action === "ask" || action === "search" ? 64 : 4);
10613
10645
  const ctx = await buildContext3(parsed, deps, action);
10614
10646
  const slug = parsed.positionals[2];
10615
10647
  switch (action) {
@@ -11180,7 +11212,7 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
11180
11212
  return;
11181
11213
  }
11182
11214
  if (command === "admin") {
11183
- await adminCommand(parsed);
11215
+ await adminCommand(parsed, runtime);
11184
11216
  return;
11185
11217
  }
11186
11218
  if (command === "agent") {
@@ -11326,4 +11358,4 @@ export {
11326
11358
  exitCodeFor,
11327
11359
  runCli
11328
11360
  };
11329
- //# sourceMappingURL=chunk-5MRY3V2K.js.map
11361
+ //# sourceMappingURL=chunk-S3EJ66VG.js.map