@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.
package/dist/bin.cjs CHANGED
@@ -915,8 +915,8 @@ function parseArgv(argv) {
915
915
  }
916
916
  return { positionals, options };
917
917
  }
918
- function assertArgs(parsed, allowedOptions, maxPositionals) {
919
- const allowed = new Set(allowedOptions);
918
+ function assertArgs(parsed, allowedOptions2, maxPositionals) {
919
+ const allowed = new Set(allowedOptions2);
920
920
  for (const name of Object.keys(parsed.options)) {
921
921
  if (!allowed.has(name)) throw new Error(`unknown option "--${name}"; run "odla-ai help" for supported options`);
922
922
  }
@@ -959,66 +959,10 @@ function addOption(options, name, value) {
959
959
  else options[name] = [String(current), String(value)];
960
960
  }
961
961
 
962
- // src/admin-command.ts
963
- async function adminCommand(parsed) {
964
- const area = parsed.positionals[1];
965
- const action = parsed.positionals[2];
966
- const credentialSet = action === "credential" && parsed.positionals[3] === "set";
967
- const credentials = action === "credentials";
968
- const models = action === "models";
969
- const usage = action === "usage";
970
- const audit = action === "audit";
971
- if (area !== "ai" || action !== "show" && action !== "set" && !credentialSet && !credentials && !models && !usage && !audit) {
972
- throw new Error('unknown admin command. Try "odla-ai admin ai show".');
973
- }
974
- assertArgs(parsed, [
975
- "platform",
976
- "json",
977
- "open",
978
- "email",
979
- "provider",
980
- "model",
981
- "enabled",
982
- "max-input-bytes",
983
- "max-output-tokens",
984
- "max-calls-per-run",
985
- "from-env",
986
- "stdin",
987
- "discovery-provider",
988
- "discovery-model",
989
- "validation-provider",
990
- "validation-model",
991
- "app-id",
992
- "env",
993
- "run-id",
994
- "limit"
995
- ], credentialSet ? 5 : action === "set" ? 4 : 3);
996
- await adminAi({
997
- action: credentialSet ? "credential-set" : credentials ? "credentials" : models ? "models" : usage ? "usage" : audit ? "audit" : action,
998
- purpose: action === "set" ? parsed.positionals[3] : void 0,
999
- provider: stringOpt(parsed.options.provider),
1000
- model: stringOpt(parsed.options.model),
1001
- enabled: boolOpt(parsed.options.enabled),
1002
- maxInputBytes: numberOpt(parsed.options["max-input-bytes"], "--max-input-bytes"),
1003
- maxOutputTokens: numberOpt(parsed.options["max-output-tokens"], "--max-output-tokens"),
1004
- maxCallsPerRun: numberOpt(parsed.options["max-calls-per-run"], "--max-calls-per-run"),
1005
- platform: stringOpt(parsed.options.platform),
1006
- email: stringOpt(parsed.options.email),
1007
- json: parsed.options.json === true,
1008
- open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
1009
- credentialProvider: credentialSet ? parsed.positionals[4] : void 0,
1010
- fromEnv: stringOpt(parsed.options["from-env"]),
1011
- stdin: parsed.options.stdin === true,
1012
- discoveryProvider: stringOpt(parsed.options["discovery-provider"]),
1013
- discoveryModel: stringOpt(parsed.options["discovery-model"]),
1014
- validationProvider: stringOpt(parsed.options["validation-provider"]),
1015
- validationModel: stringOpt(parsed.options["validation-model"]),
1016
- appId: stringOpt(parsed.options["app-id"]),
1017
- env: stringOpt(parsed.options.env),
1018
- runId: stringOpt(parsed.options["run-id"]),
1019
- limit: numberOpt(parsed.options.limit, "--limit")
1020
- });
1021
- }
962
+ // src/operator-context.ts
963
+ var import_node_fs6 = require("fs");
964
+ var import_node_path6 = require("path");
965
+ var import_node_process8 = __toESM(require("process"), 1);
1022
966
 
1023
967
  // src/config.ts
1024
968
  var import_node_fs4 = require("fs");
@@ -1304,6 +1248,291 @@ function unique2(values) {
1304
1248
  return [...new Set(values.filter(Boolean))];
1305
1249
  }
1306
1250
 
1251
+ // src/operator-profiles.ts
1252
+ var import_node_fs5 = require("fs");
1253
+ var import_node_os = require("os");
1254
+ var import_node_path5 = require("path");
1255
+ var import_node_process7 = __toESM(require("process"), 1);
1256
+ function operatorProfileFile() {
1257
+ return (0, import_node_path5.resolve)(
1258
+ clean(import_node_process7.default.env.ODLA_CONTEXT_FILE) ?? (0, import_node_path5.join)((0, import_node_os.homedir)(), ".odla", "contexts.json")
1259
+ );
1260
+ }
1261
+ function resolveOperatorProfile(parsed) {
1262
+ const fromFlag = clean(stringOpt(parsed.options.context));
1263
+ const fromEnvironment = clean(import_node_process7.default.env.ODLA_CONTEXT);
1264
+ const name = fromFlag ?? fromEnvironment ?? null;
1265
+ const file = operatorProfileFile();
1266
+ if (!name) {
1267
+ return { name: null, source: "unresolved", file, value: null };
1268
+ }
1269
+ assertOperatorName(name, "context");
1270
+ const profiles = readOperatorProfiles(file);
1271
+ const value = Object.hasOwn(profiles, name) ? profiles[name] : void 0;
1272
+ if (!value) {
1273
+ throw new Error(
1274
+ `operator context "${name}" not found in ${file}; run "odla-ai context list" or save it first`
1275
+ );
1276
+ }
1277
+ return {
1278
+ name,
1279
+ source: fromFlag ? "flag" : "environment",
1280
+ file,
1281
+ value
1282
+ };
1283
+ }
1284
+ function listOperatorProfiles(file = operatorProfileFile()) {
1285
+ return Object.entries(readOperatorProfiles(file)).sort(([a], [b]) => a.localeCompare(b)).map(([name, profile]) => ({ name, ...profile }));
1286
+ }
1287
+ function saveOperatorProfile(name, profile, file = operatorProfileFile()) {
1288
+ assertOperatorName(name, "context");
1289
+ const normalized = validateProfile(profile, `operator context "${name}"`);
1290
+ const profiles = readOperatorProfiles(file);
1291
+ profiles[name] = normalized;
1292
+ writePrivateJson(file, { schemaVersion: 1, profiles });
1293
+ }
1294
+ function removeOperatorProfile(name, file = operatorProfileFile()) {
1295
+ assertOperatorName(name, "context");
1296
+ const profiles = readOperatorProfiles(file);
1297
+ if (!Object.hasOwn(profiles, name)) return false;
1298
+ delete profiles[name];
1299
+ writePrivateJson(file, { schemaVersion: 1, profiles });
1300
+ return true;
1301
+ }
1302
+ function operatorCredentialFiles(selection) {
1303
+ const base = selection.name ? (0, import_node_path5.join)((0, import_node_path5.dirname)(selection.file), "profiles", selection.name) : (0, import_node_path5.join)((0, import_node_os.homedir)(), ".odla");
1304
+ return {
1305
+ developer: (0, import_node_path5.join)(base, "dev-token.json"),
1306
+ scoped: (0, import_node_path5.join)(base, "admin-token.local.json")
1307
+ };
1308
+ }
1309
+ function assertOperatorName(value, label) {
1310
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(value)) {
1311
+ throw new Error(
1312
+ `${label} must contain lowercase letters, numbers, and hyphens`
1313
+ );
1314
+ }
1315
+ }
1316
+ function readOperatorProfiles(file) {
1317
+ if (!(0, import_node_fs5.existsSync)(file)) return emptyProfiles();
1318
+ let raw;
1319
+ try {
1320
+ raw = JSON.parse((0, import_node_fs5.readFileSync)(file, "utf8"));
1321
+ } catch {
1322
+ throw new Error(`operator context file ${file} is not valid JSON`);
1323
+ }
1324
+ if (!raw || typeof raw !== "object" || raw.schemaVersion !== 1 || !raw.profiles || typeof raw.profiles !== "object" || Array.isArray(raw.profiles)) {
1325
+ throw new Error(`operator context file ${file} has an invalid shape`);
1326
+ }
1327
+ const unknown = Object.keys(raw).filter(
1328
+ (key) => !["schemaVersion", "profiles"].includes(key)
1329
+ );
1330
+ if (unknown.length > 0) {
1331
+ throw new Error(
1332
+ `operator context file ${file} contains unsupported field(s): ${unknown.sort().join(", ")}`
1333
+ );
1334
+ }
1335
+ const profiles = emptyProfiles();
1336
+ for (const [name, value] of Object.entries(
1337
+ raw.profiles
1338
+ )) {
1339
+ assertOperatorName(name, "context");
1340
+ profiles[name] = validateProfile(value, `operator context "${name}"`);
1341
+ }
1342
+ return profiles;
1343
+ }
1344
+ function emptyProfiles() {
1345
+ return /* @__PURE__ */ Object.create(null);
1346
+ }
1347
+ function validateProfile(value, label) {
1348
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1349
+ throw new Error(`${label} has an invalid shape`);
1350
+ }
1351
+ const unknown = Object.keys(value).filter(
1352
+ (key) => !["platform", "app", "environment"].includes(key)
1353
+ );
1354
+ if (unknown.length > 0) {
1355
+ throw new Error(
1356
+ `${label} contains unsupported field(s): ${unknown.sort().join(", ")}; contexts store scope metadata only, never credentials`
1357
+ );
1358
+ }
1359
+ const candidate = value;
1360
+ if (typeof candidate.platform !== "string") {
1361
+ throw new Error(`${label} needs an absolute platform URL`);
1362
+ }
1363
+ const platform = platformAudience(candidate.platform);
1364
+ const app = clean(candidate.app);
1365
+ const environment2 = clean(candidate.environment);
1366
+ if (app) assertOperatorName(app, "app");
1367
+ if (environment2) assertOperatorName(environment2, "environment");
1368
+ return {
1369
+ platform,
1370
+ ...app ? { app } : {},
1371
+ ...environment2 ? { environment: environment2 } : {}
1372
+ };
1373
+ }
1374
+ function clean(value) {
1375
+ const normalized = value?.trim();
1376
+ return normalized || void 0;
1377
+ }
1378
+
1379
+ // src/operator-context.ts
1380
+ var DEFAULT_PLATFORM2 = "https://odla.ai";
1381
+ async function resolveOperatorContext(parsed, options = {}) {
1382
+ const profile = resolveOperatorProfile(parsed);
1383
+ const configArgument = stringOpt(parsed.options.config) ?? "odla.config.mjs";
1384
+ const configPath = (0, import_node_path6.resolve)(configArgument);
1385
+ const explicitConfig = parsed.options.config !== void 0;
1386
+ const hasConfig = (0, import_node_fs6.existsSync)(configPath);
1387
+ if (!hasConfig && (!options.allowMissingConfig || explicitConfig)) {
1388
+ await loadProjectConfig(configArgument);
1389
+ }
1390
+ const loaded = hasConfig ? await loadProjectConfig(configArgument) : void 0;
1391
+ const platformFlag = clean2(stringOpt(parsed.options.platform));
1392
+ const platformEnvironment = clean2(import_node_process8.default.env.ODLA_PLATFORM_URL);
1393
+ const platformValue = platformAudience(
1394
+ platformFlag ?? platformEnvironment ?? profile.value?.platform ?? loaded?.platformUrl ?? DEFAULT_PLATFORM2
1395
+ );
1396
+ const platformSource = platformFlag ? "flag" : platformEnvironment ? "environment" : profile.value ? "profile" : loaded ? "config" : "default";
1397
+ const appFlag = clean2(stringOpt(parsed.options.app));
1398
+ const appEnvironment = clean2(import_node_process8.default.env.ODLA_APP_ID);
1399
+ const appValue = appFlag ?? appEnvironment ?? profile.value?.app ?? loaded?.app.id ?? null;
1400
+ const appSource = appFlag ? "flag" : appEnvironment ? "environment" : profile.value?.app ? "profile" : loaded ? "config" : "unresolved";
1401
+ if (appValue) assertOperatorName(appValue, "app");
1402
+ if (options.requireApp && !appValue) {
1403
+ throw new Error(
1404
+ "app context is unresolved; pass --app <id>, set ODLA_APP_ID, select --context <name>, or run inside a project with odla.config.mjs"
1405
+ );
1406
+ }
1407
+ const envFlag = clean2(stringOpt(parsed.options.env));
1408
+ const envEnvironment = clean2(import_node_process8.default.env.ODLA_ENV);
1409
+ const environmentValue = envFlag ?? envEnvironment ?? profile.value?.environment ?? options.defaultEnvironment ?? null;
1410
+ const environmentSource = envFlag ? "flag" : envEnvironment ? "environment" : profile.value?.environment ? "profile" : options.defaultEnvironment ? "default" : "unresolved";
1411
+ if (environmentValue) {
1412
+ assertOperatorName(environmentValue, "environment");
1413
+ }
1414
+ const rootDir = loaded?.rootDir ?? import_node_process8.default.cwd();
1415
+ const profileCredentials = operatorCredentialFiles(profile);
1416
+ const tokenFile = clean2(import_node_process8.default.env.ODLA_DEV_TOKEN_FILE) ? (0, import_node_path6.resolve)(import_node_process8.default.env.ODLA_DEV_TOKEN_FILE) : profile.name ? profileCredentials.developer : loaded?.local.tokenFile ?? profileCredentials.developer;
1417
+ const scopedTokenFile = clean2(import_node_process8.default.env.ODLA_ADMIN_TOKEN_FILE) ? (0, import_node_path6.resolve)(import_node_process8.default.env.ODLA_ADMIN_TOKEN_FILE) : profile.name ? profileCredentials.scoped : loaded ? (0, import_node_path6.join)(loaded.rootDir, ".odla", "admin-token.local.json") : profileCredentials.scoped;
1418
+ const cfg = loaded ? {
1419
+ ...loaded,
1420
+ platformUrl: platformValue,
1421
+ app: appValue ? {
1422
+ id: appValue,
1423
+ name: appValue === loaded.app.id ? loaded.app.name : appValue
1424
+ } : loaded.app,
1425
+ local: { ...loaded.local, tokenFile }
1426
+ } : {
1427
+ configPath,
1428
+ rootDir,
1429
+ platformUrl: platformValue,
1430
+ dbEndpoint: platformValue,
1431
+ app: {
1432
+ id: appValue ?? "operator",
1433
+ name: appValue ?? "Operator"
1434
+ },
1435
+ envs: environmentValue ? [environmentValue] : [],
1436
+ services: [],
1437
+ local: {
1438
+ tokenFile,
1439
+ credentialsFile: (0, import_node_path6.join)(rootDir, ".odla", "credentials.local.json"),
1440
+ devVarsFile: (0, import_node_path6.join)(rootDir, ".dev.vars"),
1441
+ gitignore: true
1442
+ }
1443
+ };
1444
+ return {
1445
+ cfg,
1446
+ profile: {
1447
+ name: profile.name,
1448
+ source: profile.source,
1449
+ file: profile.file
1450
+ },
1451
+ config: {
1452
+ path: configPath,
1453
+ status: loaded ? "loaded" : "absent",
1454
+ explicit: explicitConfig
1455
+ },
1456
+ platform: { value: platformValue, source: platformSource },
1457
+ app: { value: appValue, source: appSource },
1458
+ environment: {
1459
+ value: environmentValue,
1460
+ source: environmentSource
1461
+ },
1462
+ credentials: {
1463
+ developerTokenFile: tokenFile,
1464
+ scopedTokenFile
1465
+ }
1466
+ };
1467
+ }
1468
+ function clean2(value) {
1469
+ const normalized = value?.trim();
1470
+ return normalized || void 0;
1471
+ }
1472
+
1473
+ // src/admin-command.ts
1474
+ var CONTEXT_OPTIONS = ["platform", "config", "context", "token", "open", "email"];
1475
+ var JSON_OPTIONS = [...CONTEXT_OPTIONS, "json"];
1476
+ var SET_OPTIONS = [
1477
+ ...JSON_OPTIONS,
1478
+ "provider",
1479
+ "model",
1480
+ "enabled",
1481
+ "max-input-bytes",
1482
+ "max-output-tokens",
1483
+ "max-calls-per-run",
1484
+ "discovery-provider",
1485
+ "discovery-model",
1486
+ "validation-provider",
1487
+ "validation-model"
1488
+ ];
1489
+ async function adminCommand(parsed, deps = {}) {
1490
+ const area = parsed.positionals[1];
1491
+ const action = parsed.positionals[2];
1492
+ const credentialSet = action === "credential" && parsed.positionals[3] === "set";
1493
+ const credentials = action === "credentials";
1494
+ const models = action === "models";
1495
+ const usage = action === "usage";
1496
+ const audit = action === "audit";
1497
+ if (area !== "ai" || action !== "show" && action !== "set" && !credentialSet && !credentials && !models && !usage && !audit) {
1498
+ throw new Error('unknown admin command. Try "odla-ai admin ai show".');
1499
+ }
1500
+ 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;
1501
+ assertArgs(parsed, allowed, credentialSet ? 5 : action === "set" ? 4 : 3);
1502
+ const context = await resolveOperatorContext(parsed, { allowMissingConfig: true });
1503
+ await adminAi({
1504
+ action: credentialSet ? "credential-set" : credentials ? "credentials" : models ? "models" : usage ? "usage" : audit ? "audit" : action,
1505
+ purpose: action === "set" ? parsed.positionals[3] : void 0,
1506
+ provider: stringOpt(parsed.options.provider),
1507
+ model: stringOpt(parsed.options.model),
1508
+ enabled: boolOpt(parsed.options.enabled),
1509
+ maxInputBytes: numberOpt(parsed.options["max-input-bytes"], "--max-input-bytes"),
1510
+ maxOutputTokens: numberOpt(parsed.options["max-output-tokens"], "--max-output-tokens"),
1511
+ maxCallsPerRun: numberOpt(parsed.options["max-calls-per-run"], "--max-calls-per-run"),
1512
+ platform: context.platform.value,
1513
+ token: stringOpt(parsed.options.token),
1514
+ tokenFile: context.credentials.scopedTokenFile,
1515
+ email: stringOpt(parsed.options.email),
1516
+ json: parsed.options.json === true,
1517
+ open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
1518
+ credentialProvider: credentialSet ? parsed.positionals[4] : void 0,
1519
+ fromEnv: stringOpt(parsed.options["from-env"]),
1520
+ stdin: parsed.options.stdin === true,
1521
+ readStdin: deps.readStdin,
1522
+ discoveryProvider: stringOpt(parsed.options["discovery-provider"]),
1523
+ discoveryModel: stringOpt(parsed.options["discovery-model"]),
1524
+ validationProvider: stringOpt(parsed.options["validation-provider"]),
1525
+ validationModel: stringOpt(parsed.options["validation-model"]),
1526
+ appId: stringOpt(parsed.options["app-id"]),
1527
+ env: stringOpt(parsed.options.env),
1528
+ runId: stringOpt(parsed.options["run-id"]),
1529
+ limit: numberOpt(parsed.options.limit, "--limit"),
1530
+ fetch: deps.fetch,
1531
+ stdout: deps.stdout,
1532
+ openApprovalUrl: deps.openUrl
1533
+ });
1534
+ }
1535
+
1307
1536
  // src/tenant.ts
1308
1537
  var import_apps = require("@odla-ai/apps");
1309
1538
  function resolveEnv(cfg, requested) {
@@ -1409,7 +1638,7 @@ function errorMessage(body) {
1409
1638
  }
1410
1639
 
1411
1640
  // src/app-export.ts
1412
- var import_node_fs5 = require("fs");
1641
+ var import_node_fs7 = require("fs");
1413
1642
  var import_node_stream = require("stream");
1414
1643
  var import_promises = require("stream/promises");
1415
1644
  async function appExport(options) {
@@ -1442,7 +1671,7 @@ async function appExport(options) {
1442
1671
  const download = await doFetch(`${base}/backups/${newest.id}/download`, { headers: auth });
1443
1672
  if (!download.ok || !download.body) throw new Error(`download failed (${download.status})`);
1444
1673
  const file = options.out ?? `${tenant}-${new Date(newest.created_at).toISOString().slice(0, 10)}-tx${newest.max_tx}.jsonl.gz`;
1445
- await (0, import_promises.pipeline)(import_node_stream.Readable.fromWeb(download.body), (0, import_node_fs5.createWriteStream)(file));
1674
+ await (0, import_promises.pipeline)(import_node_stream.Readable.fromWeb(download.body), (0, import_node_fs7.createWriteStream)(file));
1446
1675
  if (options.json) out.log(JSON.stringify({ file, backup: newest }, null, 2));
1447
1676
  else {
1448
1677
  out.log(`${tenant}: wrote ${file} (${newest.bytes} bytes, ${newest.kind} snapshot at tx ${newest.max_tx})`);
@@ -1452,7 +1681,7 @@ async function appExport(options) {
1452
1681
  }
1453
1682
 
1454
1683
  // src/app-import.ts
1455
- var import_node_fs6 = require("fs");
1684
+ var import_node_fs8 = require("fs");
1456
1685
  var import_import = require("@odla-ai/db/import");
1457
1686
  function chooseIdMode(options, rows) {
1458
1687
  const chosen = [options.idField && "field", options.key && "key", options.generateIds && "generate"].filter(Boolean);
@@ -1471,7 +1700,7 @@ async function appImport(options) {
1471
1700
  const say = options.json ? (line) => out.error(line) : (line) => out.log(line);
1472
1701
  const doFetch = options.fetch ?? fetch;
1473
1702
  const { tenant } = resolveTenant(cfg, options.env);
1474
- const text = options.file === "-" ? (options.readStdin ?? (() => (0, import_node_fs6.readFileSync)(0, "utf8")))() : (0, import_node_fs6.readFileSync)(options.file, "utf8");
1703
+ const text = options.file === "-" ? (options.readStdin ?? (() => (0, import_node_fs8.readFileSync)(0, "utf8")))() : (0, import_node_fs8.readFileSync)(options.file, "utf8");
1475
1704
  const { format, sources } = (0, import_import.parseImport)(text, options.ns);
1476
1705
  if (format === "namespace-map" && options.ns) {
1477
1706
  throw new Error("--ns cannot be combined with a {namespace: rows} file \u2014 the file already names each namespace");
@@ -2438,13 +2667,13 @@ function printGroup(out, heading, items) {
2438
2667
 
2439
2668
  // src/doctor-checks.ts
2440
2669
  var import_node_child_process3 = require("child_process");
2441
- var import_node_fs8 = require("fs");
2442
- var import_node_path6 = require("path");
2670
+ var import_node_fs10 = require("fs");
2671
+ var import_node_path8 = require("path");
2443
2672
 
2444
2673
  // src/wrangler.ts
2445
2674
  var import_node_child_process2 = require("child_process");
2446
- var import_node_fs7 = require("fs");
2447
- var import_node_path5 = require("path");
2675
+ var import_node_fs9 = require("fs");
2676
+ var import_node_path7 = require("path");
2448
2677
  var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
2449
2678
  const child = (0, import_node_child_process2.spawn)(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
2450
2679
  let stdout = "";
@@ -2458,15 +2687,15 @@ var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) =>
2458
2687
  var WRANGLER_CONFIG_FILES = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"];
2459
2688
  function findWranglerConfig(rootDir) {
2460
2689
  for (const name of WRANGLER_CONFIG_FILES) {
2461
- const path = (0, import_node_path5.join)(rootDir, name);
2462
- if ((0, import_node_fs7.existsSync)(path)) return path;
2690
+ const path = (0, import_node_path7.join)(rootDir, name);
2691
+ if ((0, import_node_fs9.existsSync)(path)) return path;
2463
2692
  }
2464
2693
  return null;
2465
2694
  }
2466
2695
  function readWranglerConfig(path) {
2467
2696
  if (path.endsWith(".toml")) return null;
2468
2697
  try {
2469
- return JSON.parse(stripJsonComments((0, import_node_fs7.readFileSync)(path, "utf8")));
2698
+ return JSON.parse(stripJsonComments((0, import_node_fs9.readFileSync)(path, "utf8")));
2470
2699
  } catch {
2471
2700
  return null;
2472
2701
  }
@@ -2564,10 +2793,10 @@ function wranglerWarnings(rootDir) {
2564
2793
  for (const { label, block } of blocks) {
2565
2794
  const assets = block.assets;
2566
2795
  if (assets?.directory) {
2567
- const dir = (0, import_node_path6.resolve)(rootDir, assets.directory);
2568
- if (dir === (0, import_node_path6.resolve)(rootDir)) {
2796
+ const dir = (0, import_node_path8.resolve)(rootDir, assets.directory);
2797
+ if (dir === (0, import_node_path8.resolve)(rootDir)) {
2569
2798
  warnings.push(`${label}assets.directory is the project root \u2014 point it at a dedicated build dir (wrangler dev fails with "spawn EBADF")`);
2570
- } else if ((0, import_node_fs8.existsSync)((0, import_node_path6.join)(dir, "node_modules"))) {
2799
+ } else if ((0, import_node_fs10.existsSync)((0, import_node_path8.join)(dir, "node_modules"))) {
2571
2800
  warnings.push(`${label}assets.directory contains node_modules \u2014 wrangler dev's watcher will exhaust file descriptors`);
2572
2801
  }
2573
2802
  }
@@ -2602,13 +2831,13 @@ function o11yProjectWarnings(rootDir) {
2602
2831
  warnings.push("cannot verify o11y Worker instrumentation \u2014 add a parseable wrangler.jsonc/json config");
2603
2832
  return warnings;
2604
2833
  }
2605
- const main = typeof config.main === "string" ? (0, import_node_path6.resolve)(rootDir, config.main) : null;
2606
- if (!main || !(0, import_node_fs8.existsSync)(main)) {
2834
+ const main = typeof config.main === "string" ? (0, import_node_path8.resolve)(rootDir, config.main) : null;
2835
+ if (!main || !(0, import_node_fs10.existsSync)(main)) {
2607
2836
  warnings.push("cannot verify o11y Worker instrumentation \u2014 wrangler main is missing or unreadable");
2608
2837
  } else {
2609
2838
  let source = "";
2610
2839
  try {
2611
- source = (0, import_node_fs8.readFileSync)(main, "utf8");
2840
+ source = (0, import_node_fs10.readFileSync)(main, "utf8");
2612
2841
  } catch {
2613
2842
  }
2614
2843
  if (!/\bwithObservability\b/.test(source)) {
@@ -2632,7 +2861,7 @@ function calendarProjectWarnings(rootDir) {
2632
2861
  }
2633
2862
  function readPackageJson(rootDir) {
2634
2863
  try {
2635
- return JSON.parse((0, import_node_fs8.readFileSync)((0, import_node_path6.join)(rootDir, "package.json"), "utf8"));
2864
+ return JSON.parse((0, import_node_fs10.readFileSync)((0, import_node_path8.join)(rootDir, "package.json"), "utf8"));
2636
2865
  } catch {
2637
2866
  return null;
2638
2867
  }
@@ -2859,13 +3088,13 @@ function harnessOption(value, flag) {
2859
3088
  }
2860
3089
 
2861
3090
  // src/init.ts
2862
- var import_node_fs9 = require("fs");
2863
- var import_node_path7 = require("path");
3091
+ var import_node_fs11 = require("fs");
3092
+ var import_node_path9 = require("path");
2864
3093
  function initProject(options) {
2865
3094
  const out = options.stdout ?? console;
2866
- const rootDir = (0, import_node_path7.resolve)(options.rootDir ?? process.cwd());
2867
- const configPath = (0, import_node_path7.resolve)(rootDir, options.configPath ?? "odla.config.mjs");
2868
- if ((0, import_node_fs9.existsSync)(configPath) && !options.force) {
3095
+ const rootDir = (0, import_node_path9.resolve)(options.rootDir ?? process.cwd());
3096
+ const configPath = (0, import_node_path9.resolve)(rootDir, options.configPath ?? "odla.config.mjs");
3097
+ if ((0, import_node_fs11.existsSync)(configPath) && !options.force) {
2869
3098
  throw new Error(`${configPath} already exists. Pass --force to overwrite.`);
2870
3099
  }
2871
3100
  if (!/^[a-z0-9][a-z0-9-]*$/.test(options.appId)) {
@@ -2875,20 +3104,20 @@ function initProject(options) {
2875
3104
  const services = options.services?.length ? options.services : ["db", "ai"];
2876
3105
  if (services.includes("calendar") && !services.includes("db")) throw new Error("--services calendar requires db");
2877
3106
  const aiProvider = options.aiProvider ?? "anthropic";
2878
- (0, import_node_fs9.mkdirSync)((0, import_node_path7.dirname)(configPath), { recursive: true });
2879
- (0, import_node_fs9.mkdirSync)((0, import_node_path7.resolve)(rootDir, "src/odla"), { recursive: true });
2880
- (0, import_node_fs9.mkdirSync)((0, import_node_path7.resolve)(rootDir, ".odla"), { recursive: true });
2881
- (0, import_node_fs9.writeFileSync)(configPath, configTemplate({ appId: options.appId, name: options.name, envs, services, aiProvider }));
2882
- writeIfMissing((0, import_node_path7.resolve)(rootDir, "src/odla/schema.mjs"), schemaTemplate());
2883
- writeIfMissing((0, import_node_path7.resolve)(rootDir, "src/odla/rules.mjs"), rulesTemplate());
3107
+ (0, import_node_fs11.mkdirSync)((0, import_node_path9.dirname)(configPath), { recursive: true });
3108
+ (0, import_node_fs11.mkdirSync)((0, import_node_path9.resolve)(rootDir, "src/odla"), { recursive: true });
3109
+ (0, import_node_fs11.mkdirSync)((0, import_node_path9.resolve)(rootDir, ".odla"), { recursive: true });
3110
+ (0, import_node_fs11.writeFileSync)(configPath, configTemplate({ appId: options.appId, name: options.name, envs, services, aiProvider }));
3111
+ writeIfMissing((0, import_node_path9.resolve)(rootDir, "src/odla/schema.mjs"), schemaTemplate());
3112
+ writeIfMissing((0, import_node_path9.resolve)(rootDir, "src/odla/rules.mjs"), rulesTemplate());
2884
3113
  ensureGitignore(rootDir);
2885
3114
  out.log(`created ${relativeDisplay(configPath, rootDir)}`);
2886
3115
  out.log("created src/odla/schema.mjs and src/odla/rules.mjs");
2887
3116
  out.log("updated .gitignore for local odla credentials");
2888
3117
  }
2889
3118
  function writeIfMissing(path, text) {
2890
- if ((0, import_node_fs9.existsSync)(path)) return;
2891
- (0, import_node_fs9.writeFileSync)(path, text);
3119
+ if ((0, import_node_fs11.existsSync)(path)) return;
3120
+ (0, import_node_fs11.writeFileSync)(path, text);
2892
3121
  }
2893
3122
  function configTemplate(input) {
2894
3123
  const calendar = input.services.includes("calendar") ? ` calendar: {
@@ -3119,9 +3348,9 @@ async function resolveVaultWrite(options) {
3119
3348
  }
3120
3349
 
3121
3350
  // src/skill.ts
3122
- var import_node_fs10 = require("fs");
3123
- var import_node_os = require("os");
3124
- var import_node_path8 = require("path");
3351
+ var import_node_fs12 = require("fs");
3352
+ var import_node_os2 = require("os");
3353
+ var import_node_path10 = require("path");
3125
3354
  var import_node_url2 = require("url");
3126
3355
 
3127
3356
  // src/skill-adapters.ts
@@ -3200,8 +3429,8 @@ function installSkill(options = {}) {
3200
3429
  const files = listFiles(sourceDir);
3201
3430
  if (files.length === 0) throw new Error(`no bundled skills found at ${sourceDir}`);
3202
3431
  const harnesses = normalizeHarnesses(options.harnesses, options.global === true);
3203
- const root = (0, import_node_path8.resolve)(options.dir ?? process.cwd());
3204
- const home = (0, import_node_path8.resolve)(options.homeDir ?? (0, import_node_os.homedir)());
3432
+ const root = (0, import_node_path10.resolve)(options.dir ?? process.cwd());
3433
+ const home = (0, import_node_path10.resolve)(options.homeDir ?? (0, import_node_os2.homedir)());
3205
3434
  const plans = /* @__PURE__ */ new Map();
3206
3435
  const targets = /* @__PURE__ */ new Map();
3207
3436
  const rememberTarget = (harness, target) => {
@@ -3215,48 +3444,48 @@ function installSkill(options = {}) {
3215
3444
  plans.set(target, { target, content: content2, boundary, managedMerge });
3216
3445
  };
3217
3446
  const planSkillTree = (targetDir2, boundary = root) => {
3218
- for (const rel of files) plan((0, import_node_path8.join)(targetDir2, rel), (0, import_node_fs10.readFileSync)((0, import_node_path8.join)(sourceDir, rel), "utf8"), false, boundary);
3447
+ for (const rel of files) plan((0, import_node_path10.join)(targetDir2, rel), (0, import_node_fs12.readFileSync)((0, import_node_path10.join)(sourceDir, rel), "utf8"), false, boundary);
3219
3448
  };
3220
3449
  let targetDir;
3221
3450
  if (options.global) {
3222
- const claudeRoot = (0, import_node_path8.join)(home, ".claude", "skills");
3223
- const codexRoot = (0, import_node_path8.resolve)(options.codexHomeDir ?? process.env.CODEX_HOME ?? (0, import_node_path8.join)(home, ".codex"), "skills");
3451
+ const claudeRoot = (0, import_node_path10.join)(home, ".claude", "skills");
3452
+ const codexRoot = (0, import_node_path10.resolve)(options.codexHomeDir ?? process.env.CODEX_HOME ?? (0, import_node_path10.join)(home, ".codex"), "skills");
3224
3453
  targetDir = harnesses[0] === "codex" ? codexRoot : claudeRoot;
3225
3454
  for (const harness of harnesses) {
3226
3455
  const skillRoot = harness === "claude" ? claudeRoot : codexRoot;
3227
- planSkillTree(skillRoot, harness === "claude" ? home : (0, import_node_path8.dirname)((0, import_node_path8.dirname)(codexRoot)));
3456
+ planSkillTree(skillRoot, harness === "claude" ? home : (0, import_node_path10.dirname)((0, import_node_path10.dirname)(codexRoot)));
3228
3457
  rememberTarget(harness, skillRoot);
3229
3458
  }
3230
3459
  } else {
3231
- const sharedRoot = (0, import_node_path8.join)(root, ".agents", "skills");
3460
+ const sharedRoot = (0, import_node_path10.join)(root, ".agents", "skills");
3232
3461
  planSkillTree(sharedRoot);
3233
- const claudeRoot = (0, import_node_path8.join)(root, ".claude", "skills");
3462
+ const claudeRoot = (0, import_node_path10.join)(root, ".claude", "skills");
3234
3463
  targetDir = harnesses.includes("claude") ? claudeRoot : sharedRoot;
3235
3464
  for (const harness of harnesses) rememberTarget(harness, sharedRoot);
3236
3465
  if (harnesses.includes("claude")) {
3237
3466
  for (const skill of skillNames(files)) {
3238
- const canonical = (0, import_node_fs10.readFileSync)((0, import_node_path8.join)(sourceDir, skill, "SKILL.md"), "utf8");
3239
- plan((0, import_node_path8.join)(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical));
3467
+ const canonical = (0, import_node_fs12.readFileSync)((0, import_node_path10.join)(sourceDir, skill, "SKILL.md"), "utf8");
3468
+ plan((0, import_node_path10.join)(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical));
3240
3469
  }
3241
3470
  rememberTarget("claude", claudeRoot);
3242
3471
  }
3243
3472
  if (harnesses.includes("cursor")) {
3244
- const cursorRule = (0, import_node_path8.join)(root, ".cursor", "rules", "odla.mdc");
3473
+ const cursorRule = (0, import_node_path10.join)(root, ".cursor", "rules", "odla.mdc");
3245
3474
  plan(cursorRule, CURSOR_RULE);
3246
3475
  rememberTarget("cursor", cursorRule);
3247
3476
  }
3248
3477
  if (harnesses.includes("agents")) {
3249
- const agentsFile = (0, import_node_path8.join)(root, "AGENTS.md");
3478
+ const agentsFile = (0, import_node_path10.join)(root, "AGENTS.md");
3250
3479
  plan(agentsFile, managedFileContent(agentsFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
3251
3480
  rememberTarget("agents", agentsFile);
3252
3481
  }
3253
3482
  if (harnesses.includes("copilot")) {
3254
- const copilotFile = (0, import_node_path8.join)(root, ".github", "copilot-instructions.md");
3483
+ const copilotFile = (0, import_node_path10.join)(root, ".github", "copilot-instructions.md");
3255
3484
  plan(copilotFile, managedFileContent(copilotFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
3256
3485
  rememberTarget("copilot", copilotFile);
3257
3486
  }
3258
3487
  if (harnesses.includes("gemini")) {
3259
- const geminiFile = (0, import_node_path8.join)(root, "GEMINI.md");
3488
+ const geminiFile = (0, import_node_path10.join)(root, "GEMINI.md");
3260
3489
  plan(geminiFile, managedFileContent(geminiFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
3261
3490
  rememberTarget("gemini", geminiFile);
3262
3491
  }
@@ -3270,11 +3499,11 @@ function installSkill(options = {}) {
3270
3499
  conflicts.push(`${file.target} (redirected by symbolic link ${symlink})`);
3271
3500
  continue;
3272
3501
  }
3273
- if (!(0, import_node_fs10.existsSync)(file.target)) {
3502
+ if (!(0, import_node_fs12.existsSync)(file.target)) {
3274
3503
  writtenPaths.add(file.target);
3275
3504
  continue;
3276
3505
  }
3277
- const current = (0, import_node_fs10.readFileSync)(file.target, "utf8");
3506
+ const current = (0, import_node_fs12.readFileSync)(file.target, "utf8");
3278
3507
  if (current === file.content) {
3279
3508
  unchangedPaths.add(file.target);
3280
3509
  } else if (file.managedMerge || options.force) {
@@ -3291,9 +3520,9 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
3291
3520
  );
3292
3521
  }
3293
3522
  for (const file of plans.values()) {
3294
- if (!(0, import_node_fs10.existsSync)(file.target) || (0, import_node_fs10.readFileSync)(file.target, "utf8") !== file.content) {
3295
- (0, import_node_fs10.mkdirSync)((0, import_node_path8.dirname)(file.target), { recursive: true });
3296
- (0, import_node_fs10.writeFileSync)(file.target, file.content);
3523
+ if (!(0, import_node_fs12.existsSync)(file.target) || (0, import_node_fs12.readFileSync)(file.target, "utf8") !== file.content) {
3524
+ (0, import_node_fs12.mkdirSync)((0, import_node_path10.dirname)(file.target), { recursive: true });
3525
+ (0, import_node_fs12.writeFileSync)(file.target, file.content);
3297
3526
  }
3298
3527
  }
3299
3528
  const skills = skillNames(files);
@@ -3312,7 +3541,7 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
3312
3541
  };
3313
3542
  }
3314
3543
  function pathsUnder(root, paths) {
3315
- return [...paths].map((path) => (0, import_node_path8.relative)(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${import_node_path8.sep}`) && !(0, import_node_path8.isAbsolute)(path)).sort();
3544
+ return [...paths].map((path) => (0, import_node_path10.relative)(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${import_node_path10.sep}`) && !(0, import_node_path10.isAbsolute)(path)).sort();
3316
3545
  }
3317
3546
  function normalizeHarnesses(values, global) {
3318
3547
  const requested = values?.length ? values : ["claude"];
@@ -3334,9 +3563,9 @@ function normalizeHarnesses(values, global) {
3334
3563
  function managedFileContent(path, block, force, boundary) {
3335
3564
  const symlink = symlinkedComponent(boundary, path);
3336
3565
  if (symlink) throw new Error(`refusing to manage ${path}: symbolic link component ${symlink}`);
3337
- if (!(0, import_node_fs10.existsSync)(path)) return `${block}
3566
+ if (!(0, import_node_fs12.existsSync)(path)) return `${block}
3338
3567
  `;
3339
- const current = (0, import_node_fs10.readFileSync)(path, "utf8");
3568
+ const current = (0, import_node_fs12.readFileSync)(path, "utf8");
3340
3569
  const start = "<!-- odla-ai agent setup:start -->";
3341
3570
  const end = "<!-- odla-ai agent setup:end -->";
3342
3571
  const startAt = current.indexOf(start);
@@ -3357,15 +3586,15 @@ function managedFileContent(path, block, force, boundary) {
3357
3586
  return `${current.slice(0, startAt)}${block}${current.slice(afterEnd)}`;
3358
3587
  }
3359
3588
  function symlinkedComponent(boundary, target) {
3360
- const rel = (0, import_node_path8.relative)(boundary, target);
3361
- if (rel === ".." || rel.startsWith(`..${import_node_path8.sep}`) || (0, import_node_path8.isAbsolute)(rel)) {
3589
+ const rel = (0, import_node_path10.relative)(boundary, target);
3590
+ if (rel === ".." || rel.startsWith(`..${import_node_path10.sep}`) || (0, import_node_path10.isAbsolute)(rel)) {
3362
3591
  throw new Error(`agent setup target escapes its install root: ${target}`);
3363
3592
  }
3364
3593
  let current = boundary;
3365
- for (const part of rel.split(import_node_path8.sep).filter(Boolean)) {
3366
- current = (0, import_node_path8.join)(current, part);
3594
+ for (const part of rel.split(import_node_path10.sep).filter(Boolean)) {
3595
+ current = (0, import_node_path10.join)(current, part);
3367
3596
  try {
3368
- if ((0, import_node_fs10.lstatSync)(current).isSymbolicLink()) return current;
3597
+ if ((0, import_node_fs12.lstatSync)(current).isSymbolicLink()) return current;
3369
3598
  } catch (error) {
3370
3599
  if (error.code !== "ENOENT") throw error;
3371
3600
  }
@@ -3376,13 +3605,13 @@ function skillNames(files) {
3376
3605
  return [...new Set(files.filter((file) => /(^|[\\/])SKILL\.md$/.test(file)).map((file) => file.split(/[\\/]/)[0]))].sort();
3377
3606
  }
3378
3607
  function listFiles(dir) {
3379
- if (!(0, import_node_fs10.existsSync)(dir)) return [];
3608
+ if (!(0, import_node_fs12.existsSync)(dir)) return [];
3380
3609
  const results = [];
3381
3610
  const walk = (current) => {
3382
- for (const entry of (0, import_node_fs10.readdirSync)(current, { withFileTypes: true })) {
3383
- const path = (0, import_node_path8.join)(current, entry.name);
3611
+ for (const entry of (0, import_node_fs12.readdirSync)(current, { withFileTypes: true })) {
3612
+ const path = (0, import_node_path10.join)(current, entry.name);
3384
3613
  if (entry.isDirectory()) walk(path);
3385
- else results.push((0, import_node_path8.relative)(dir, path));
3614
+ else results.push((0, import_node_path10.relative)(dir, path));
3386
3615
  }
3387
3616
  };
3388
3617
  walk(dir);
@@ -3529,13 +3758,16 @@ async function safeText3(res) {
3529
3758
 
3530
3759
  // src/cli-project.ts
3531
3760
  var SKILL_OPTS = ["dir", "global", "force", "agent", "harness"];
3532
- function install(parsed, positionals) {
3761
+ function install(parsed, positionals, deps) {
3533
3762
  assertArgs(parsed, SKILL_OPTS, positionals);
3534
3763
  installSkill({
3535
3764
  dir: stringOpt(parsed.options.dir),
3536
3765
  global: parsed.options.global === true,
3537
3766
  harnesses: harnessList(parsed),
3538
- force: parsed.options.force === true
3767
+ force: parsed.options.force === true,
3768
+ homeDir: deps.homeDir,
3769
+ codexHomeDir: deps.codexHomeDir,
3770
+ stdout: deps.stdout
3539
3771
  });
3540
3772
  }
3541
3773
  async function secretsCommand(parsed, deps) {
@@ -3551,6 +3783,7 @@ async function secretsCommand(parsed, deps) {
3551
3783
  token: stringOpt(parsed.options.token),
3552
3784
  email: stringOpt(parsed.options.email),
3553
3785
  yes: parsed.options.yes === true,
3786
+ readStdin: deps.readStdin,
3554
3787
  fetch: deps.fetch,
3555
3788
  stdout: deps.stdout
3556
3789
  };
@@ -3609,8 +3842,8 @@ async function projectCommand(command, parsed, deps) {
3609
3842
  return true;
3610
3843
  }
3611
3844
  if (command === "setup") {
3612
- install(parsed, 1);
3613
- console.log(
3845
+ install(parsed, 1, deps);
3846
+ (deps.stdout ?? console).log(
3614
3847
  "\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."
3615
3848
  );
3616
3849
  return true;
@@ -3618,7 +3851,7 @@ async function projectCommand(command, parsed, deps) {
3618
3851
  if (command === "skill") {
3619
3852
  const sub = parsed.positionals[1];
3620
3853
  if (sub !== "install") throw new Error(`unknown skill subcommand "${sub ?? ""}". Try "odla-ai skill install".`);
3621
- install(parsed, 2);
3854
+ install(parsed, 2, deps);
3622
3855
  return true;
3623
3856
  }
3624
3857
  if (command === "secrets") {
@@ -3629,9 +3862,9 @@ async function projectCommand(command, parsed, deps) {
3629
3862
  }
3630
3863
 
3631
3864
  // src/code-connect.ts
3632
- var import_node_fs12 = require("fs");
3633
- var import_node_os3 = require("os");
3634
- var import_node_path10 = require("path");
3865
+ var import_node_fs14 = require("fs");
3866
+ var import_node_os4 = require("os");
3867
+ var import_node_path12 = require("path");
3635
3868
 
3636
3869
  // ../harness/dist/chunk-QTUEF2HZ.js
3637
3870
  var HARNESS_PROTOCOL_VERSION = 1;
@@ -6516,9 +6749,9 @@ var CodePiRuntimeEngine = class {
6516
6749
  };
6517
6750
 
6518
6751
  // src/help.ts
6519
- var import_node_fs11 = require("fs");
6752
+ var import_node_fs13 = require("fs");
6520
6753
  function cliVersion() {
6521
- const pkg = JSON.parse((0, import_node_fs11.readFileSync)(new URL("../package.json", importMetaUrl), "utf8"));
6754
+ const pkg = JSON.parse((0, import_node_fs13.readFileSync)(new URL("../package.json", importMetaUrl), "utf8"));
6522
6755
  return pkg.version ?? "unknown";
6523
6756
  }
6524
6757
  function printHelp(output = console) {
@@ -6553,11 +6786,21 @@ Usage:
6553
6786
  odla-ai app owners list [--config odla.config.mjs] [--email <odla-account>] [--json]
6554
6787
  odla-ai app owners add <email> [--email <odla-account>] [--json]
6555
6788
  odla-ai app owners remove <email> [--email <odla-account>] [--json]
6556
- odla-ai pm <goal|task|decision|bug> list [--app <id>] [--status <s>] [--severity <s>] [--column <c>] [--q <text>] [--json]
6557
- 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>]
6789
+ odla-ai pm goal list [--app <id>] [--status <s>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
6790
+ odla-ai pm task list [--app <id>] [--column <c>] [--goal <id>] [--assignee <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
6791
+ odla-ai pm decision list [--app <id>] [--status <s>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
6792
+ odla-ai pm bug list [--app <id>] [--status <s>] [--severity <s>] [--goal <id>] [--assignee <id>] [--decision <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
6793
+ odla-ai pm goal add --app <id> --title <t> [--status <s>] [--proof <text>] [--target <pct>] [--mutation-id <id>] [--json]
6794
+ 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]
6795
+ odla-ai pm decision add --app <id> --title <t> --body <text> [--status <s>] [--mutation-id <id>] [--json]
6796
+ 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]
6558
6797
  odla-ai pm <goal|task|decision|bug> get <id> [--json]
6559
- odla-ai pm <goal|task|decision|bug> set <id> [--status <s>|--column <c>|--assignee <id>|--no-assignee|--goal <id>|--decision <id>] [--mutation-id <id>]
6560
- odla-ai pm <goal|task|decision|bug> done <id> [--decision <accepted-decision-id>] [--mutation-id <id>]
6798
+ odla-ai pm goal set <id> [--title <t>|--status <s>|--proof <text>|--no-proof|--target <pct>|--no-target] [--mutation-id <id>] [--json]
6799
+ 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]
6800
+ odla-ai pm decision set <id> [--title <t>|--status <s>|--body <text>] [--mutation-id <id>] [--json]
6801
+ 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]
6802
+ odla-ai pm <goal|task|decision> done <id> [--mutation-id <id>]
6803
+ odla-ai pm bug done <id> [--decision <accepted-decision-id>] [--mutation-id <id>]
6561
6804
  odla-ai pm <goal|task|decision|bug> comment <id> --body "..." [--mutation-id <id>]
6562
6805
  odla-ai pm <goal|task|decision|bug> comments <id> [--json]
6563
6806
  odla-ai pm <goal|task|decision|bug> rm <id>
@@ -6596,15 +6839,16 @@ Usage:
6596
6839
  odla-ai runbook rm <slug> [--app <id>]
6597
6840
  odla-ai capabilities [--json]
6598
6841
  odla-ai code connect [--env dev|prod] [--email <odla-account>] [--engine auto|container|podman|docker] [--slots <1-64>] [--once]
6599
- odla-ai admin ai show [--platform https://odla.ai] [--email <odla-account>] [--json]
6600
- odla-ai admin ai models [--provider <id>] [--json]
6601
- odla-ai admin ai set <purpose> [--provider <id>] [--model <id>] [--enabled|--no-enabled]
6602
- odla-ai admin ai set security --discovery-provider <id> --discovery-model <id>
6603
- --validation-provider <id> --validation-model <id> [--enabled|--no-enabled]
6604
- odla-ai admin ai credentials [--json]
6605
- odla-ai admin ai credential set <provider> (--from-env <NAME>|--stdin)
6606
- odla-ai admin ai usage [--app-id <id>] [--env <env>] [--run-id <id>] [--limit <1-500>] [--json]
6607
- odla-ai admin ai audit [--limit <1-200>] [--json]
6842
+ odla-ai admin ai show [--context <name>] [--platform https://odla.ai] [--email <odla-account>] [--json]
6843
+ odla-ai admin ai models [--context <name>] [--provider <id>] [--json]
6844
+ odla-ai admin ai set <purpose> [--context <name>] [--provider <id>] [--model <id>] [--enabled|--no-enabled]
6845
+ [--max-input-bytes <n>] [--max-output-tokens <n>] [--max-calls-per-run <n>] [--json]
6846
+ odla-ai admin ai set security [--context <name>] --discovery-provider <id> --discovery-model <id>
6847
+ --validation-provider <id> --validation-model <id> [--enabled|--no-enabled] [--json]
6848
+ odla-ai admin ai credentials [--context <name>] [--json]
6849
+ odla-ai admin ai credential set <provider> [--context <name>] (--from-env <NAME>|--stdin)
6850
+ odla-ai admin ai usage [--context <name>] [--app-id <id>] [--env <env>] [--run-id <id>] [--limit <1-500>] [--json]
6851
+ odla-ai admin ai audit [--context <name>] [--limit <1-200>] [--json]
6608
6852
  odla-ai security github connect [--repo owner/name] [--env dev] [--email <odla-account>] [--no-open]
6609
6853
  odla-ai security github disconnect --source <id> [--env dev] [--yes]
6610
6854
  odla-ai security plan [--env dev] [--json]
@@ -7031,8 +7275,8 @@ function digestText(value) {
7031
7275
  var import_node_child_process6 = require("child_process");
7032
7276
  var import_node_crypto2 = require("crypto");
7033
7277
  var import_promises10 = require("fs/promises");
7034
- var import_node_os2 = require("os");
7035
- var import_node_path9 = require("path");
7278
+ var import_node_os3 = require("os");
7279
+ var import_node_path11 = require("path");
7036
7280
  var import_node_url3 = require("url");
7037
7281
 
7038
7282
  // src/code-runtime-config.ts
@@ -7118,10 +7362,10 @@ async function embeddedPiImageName() {
7118
7362
  return `odla-ai/pi-agent:embedded-sha256-${(0, import_node_crypto2.createHash)("sha256").update(bundle).digest("hex")}`;
7119
7363
  }
7120
7364
  async function buildEmbeddedPiImage(engine, image, run) {
7121
- const context = await (0, import_promises10.mkdtemp)((0, import_node_path9.join)((0, import_node_os2.tmpdir)(), "odla-code-pi-"));
7365
+ const context = await (0, import_promises10.mkdtemp)((0, import_node_path11.join)((0, import_node_os3.tmpdir)(), "odla-code-pi-"));
7122
7366
  try {
7123
- await (0, import_promises10.copyFile)(embeddedPiAssetPath(), (0, import_node_path9.join)(context, "pi-agent.js"));
7124
- await (0, import_promises10.writeFile)((0, import_node_path9.join)(context, "Dockerfile"), [
7367
+ await (0, import_promises10.copyFile)(embeddedPiAssetPath(), (0, import_node_path11.join)(context, "pi-agent.js"));
7368
+ await (0, import_promises10.writeFile)((0, import_node_path11.join)(context, "Dockerfile"), [
7125
7369
  `FROM ${CODE_NODE_IMAGE}`,
7126
7370
  "COPY pi-agent.js /opt/odla/pi-agent.js",
7127
7371
  "WORKDIR /workspace",
@@ -7137,8 +7381,8 @@ async function buildEmbeddedPiImage(engine, image, run) {
7137
7381
  // src/code-connect.ts
7138
7382
  async function codeConnect(options) {
7139
7383
  const cwd = options.cwd ?? process.cwd();
7140
- const configPath = (0, import_node_path10.resolve)(cwd, options.configPath);
7141
- const cfg = (0, import_node_fs12.existsSync)(configPath) ? await loadProjectConfig(configPath) : null;
7384
+ const configPath = (0, import_node_path12.resolve)(cwd, options.configPath);
7385
+ const cfg = (0, import_node_fs14.existsSync)(configPath) ? await loadProjectConfig(configPath) : null;
7142
7386
  const requestedAppId = options.appId?.trim();
7143
7387
  if (requestedAppId && !/^[a-z0-9][a-z0-9-]{1,62}$/.test(requestedAppId)) {
7144
7388
  throw new Error("--app-id must be a valid odla app id");
@@ -7172,7 +7416,7 @@ async function codeConnect(options) {
7172
7416
  );
7173
7417
  if (!piImage || !/^odla-ai\/pi-agent:embedded-sha256-[0-9a-f]{64}$/.test(piImage)) throw new Error("Code image preflight did not produce the content-addressed embedded Pi runtime");
7174
7418
  const hostPlatform = process.platform === "darwin" ? "macos" : "linux";
7175
- const hostName = (options.name ?? (0, import_node_os3.hostname)()).trim();
7419
+ const hostName = (options.name ?? (0, import_node_os4.hostname)()).trim();
7176
7420
  if (!hostName || hostName.length > 120) throw new Error("--name must contain 1 to 120 characters");
7177
7421
  const repository = await inferGitHubRepository(cwd, options.readGitOrigin);
7178
7422
  const localSource = await (options.prepareLocalSource ?? prepareCodeLocalSource)(
@@ -7209,8 +7453,8 @@ async function codeConnect(options) {
7209
7453
  platform: hostPlatform,
7210
7454
  arch: process.arch,
7211
7455
  engines: [engine],
7212
- cpuCount: (0, import_node_os3.cpus)().length,
7213
- memoryBytes: (0, import_node_os3.totalmem)(),
7456
+ cpuCount: (0, import_node_os4.cpus)().length,
7457
+ memoryBytes: (0, import_node_os4.totalmem)(),
7214
7458
  source: descriptor2,
7215
7459
  images: {
7216
7460
  ready: true,
@@ -7356,246 +7600,19 @@ async function codeCommand(parsed, dependencies) {
7356
7600
  }
7357
7601
 
7358
7602
  // src/operator-credentials.ts
7359
- var import_node_process7 = __toESM(require("process"), 1);
7603
+ var import_node_process9 = __toESM(require("process"), 1);
7360
7604
  function developerTokenStatus(context, parsed, now = Date.now()) {
7361
7605
  const cached = readJsonFile(context.cfg.local.tokenFile);
7362
7606
  const cacheStatus = !cached?.token ? "missing" : cached.platform !== context.platform.value ? "other-platform" : (cached.expiresAt ?? 0) <= now + 6e4 ? "expired" : "valid";
7363
- const source = clean(
7607
+ const source = clean3(
7364
7608
  stringOpt(parsed.options.token)
7365
- ) ? "flag" : clean(import_node_process7.default.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
7609
+ ) ? "flag" : clean3(import_node_process9.default.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
7366
7610
  return {
7367
7611
  source,
7368
7612
  cacheFile: context.cfg.local.tokenFile,
7369
7613
  cacheStatus
7370
7614
  };
7371
7615
  }
7372
- function clean(value) {
7373
- const normalized = value?.trim();
7374
- return normalized || void 0;
7375
- }
7376
-
7377
- // src/operator-context.ts
7378
- var import_node_fs14 = require("fs");
7379
- var import_node_path12 = require("path");
7380
- var import_node_process9 = __toESM(require("process"), 1);
7381
-
7382
- // src/operator-profiles.ts
7383
- var import_node_fs13 = require("fs");
7384
- var import_node_os4 = require("os");
7385
- var import_node_path11 = require("path");
7386
- var import_node_process8 = __toESM(require("process"), 1);
7387
- function operatorProfileFile() {
7388
- return (0, import_node_path11.resolve)(
7389
- clean2(import_node_process8.default.env.ODLA_CONTEXT_FILE) ?? (0, import_node_path11.join)((0, import_node_os4.homedir)(), ".odla", "contexts.json")
7390
- );
7391
- }
7392
- function resolveOperatorProfile(parsed) {
7393
- const fromFlag = clean2(stringOpt(parsed.options.context));
7394
- const fromEnvironment = clean2(import_node_process8.default.env.ODLA_CONTEXT);
7395
- const name = fromFlag ?? fromEnvironment ?? null;
7396
- const file = operatorProfileFile();
7397
- if (!name) {
7398
- return { name: null, source: "unresolved", file, value: null };
7399
- }
7400
- assertOperatorName(name, "context");
7401
- const profiles = readOperatorProfiles(file);
7402
- const value = Object.hasOwn(profiles, name) ? profiles[name] : void 0;
7403
- if (!value) {
7404
- throw new Error(
7405
- `operator context "${name}" not found in ${file}; run "odla-ai context list" or save it first`
7406
- );
7407
- }
7408
- return {
7409
- name,
7410
- source: fromFlag ? "flag" : "environment",
7411
- file,
7412
- value
7413
- };
7414
- }
7415
- function listOperatorProfiles(file = operatorProfileFile()) {
7416
- return Object.entries(readOperatorProfiles(file)).sort(([a], [b]) => a.localeCompare(b)).map(([name, profile]) => ({ name, ...profile }));
7417
- }
7418
- function saveOperatorProfile(name, profile, file = operatorProfileFile()) {
7419
- assertOperatorName(name, "context");
7420
- const normalized = validateProfile(profile, `operator context "${name}"`);
7421
- const profiles = readOperatorProfiles(file);
7422
- profiles[name] = normalized;
7423
- writePrivateJson(file, { schemaVersion: 1, profiles });
7424
- }
7425
- function removeOperatorProfile(name, file = operatorProfileFile()) {
7426
- assertOperatorName(name, "context");
7427
- const profiles = readOperatorProfiles(file);
7428
- if (!Object.hasOwn(profiles, name)) return false;
7429
- delete profiles[name];
7430
- writePrivateJson(file, { schemaVersion: 1, profiles });
7431
- return true;
7432
- }
7433
- function operatorCredentialFiles(selection) {
7434
- const base = selection.name ? (0, import_node_path11.join)((0, import_node_path11.dirname)(selection.file), "profiles", selection.name) : (0, import_node_path11.join)((0, import_node_os4.homedir)(), ".odla");
7435
- return {
7436
- developer: (0, import_node_path11.join)(base, "dev-token.json"),
7437
- scoped: (0, import_node_path11.join)(base, "admin-token.local.json")
7438
- };
7439
- }
7440
- function assertOperatorName(value, label) {
7441
- if (!/^[a-z0-9][a-z0-9-]*$/.test(value)) {
7442
- throw new Error(
7443
- `${label} must contain lowercase letters, numbers, and hyphens`
7444
- );
7445
- }
7446
- }
7447
- function readOperatorProfiles(file) {
7448
- if (!(0, import_node_fs13.existsSync)(file)) return emptyProfiles();
7449
- let raw;
7450
- try {
7451
- raw = JSON.parse((0, import_node_fs13.readFileSync)(file, "utf8"));
7452
- } catch {
7453
- throw new Error(`operator context file ${file} is not valid JSON`);
7454
- }
7455
- if (!raw || typeof raw !== "object" || raw.schemaVersion !== 1 || !raw.profiles || typeof raw.profiles !== "object" || Array.isArray(raw.profiles)) {
7456
- throw new Error(`operator context file ${file} has an invalid shape`);
7457
- }
7458
- const unknown = Object.keys(raw).filter(
7459
- (key) => !["schemaVersion", "profiles"].includes(key)
7460
- );
7461
- if (unknown.length > 0) {
7462
- throw new Error(
7463
- `operator context file ${file} contains unsupported field(s): ${unknown.sort().join(", ")}`
7464
- );
7465
- }
7466
- const profiles = emptyProfiles();
7467
- for (const [name, value] of Object.entries(
7468
- raw.profiles
7469
- )) {
7470
- assertOperatorName(name, "context");
7471
- profiles[name] = validateProfile(value, `operator context "${name}"`);
7472
- }
7473
- return profiles;
7474
- }
7475
- function emptyProfiles() {
7476
- return /* @__PURE__ */ Object.create(null);
7477
- }
7478
- function validateProfile(value, label) {
7479
- if (!value || typeof value !== "object" || Array.isArray(value)) {
7480
- throw new Error(`${label} has an invalid shape`);
7481
- }
7482
- const unknown = Object.keys(value).filter(
7483
- (key) => !["platform", "app", "environment"].includes(key)
7484
- );
7485
- if (unknown.length > 0) {
7486
- throw new Error(
7487
- `${label} contains unsupported field(s): ${unknown.sort().join(", ")}; contexts store scope metadata only, never credentials`
7488
- );
7489
- }
7490
- const candidate = value;
7491
- if (typeof candidate.platform !== "string") {
7492
- throw new Error(`${label} needs an absolute platform URL`);
7493
- }
7494
- const platform = platformAudience(candidate.platform);
7495
- const app = clean2(candidate.app);
7496
- const environment2 = clean2(candidate.environment);
7497
- if (app) assertOperatorName(app, "app");
7498
- if (environment2) assertOperatorName(environment2, "environment");
7499
- return {
7500
- platform,
7501
- ...app ? { app } : {},
7502
- ...environment2 ? { environment: environment2 } : {}
7503
- };
7504
- }
7505
- function clean2(value) {
7506
- const normalized = value?.trim();
7507
- return normalized || void 0;
7508
- }
7509
-
7510
- // src/operator-context.ts
7511
- var DEFAULT_PLATFORM2 = "https://odla.ai";
7512
- async function resolveOperatorContext(parsed, options = {}) {
7513
- const profile = resolveOperatorProfile(parsed);
7514
- const configArgument = stringOpt(parsed.options.config) ?? "odla.config.mjs";
7515
- const configPath = (0, import_node_path12.resolve)(configArgument);
7516
- const explicitConfig = parsed.options.config !== void 0;
7517
- const hasConfig = (0, import_node_fs14.existsSync)(configPath);
7518
- if (!hasConfig && (!options.allowMissingConfig || explicitConfig)) {
7519
- await loadProjectConfig(configArgument);
7520
- }
7521
- const loaded = hasConfig ? await loadProjectConfig(configArgument) : void 0;
7522
- const platformFlag = clean3(stringOpt(parsed.options.platform));
7523
- const platformEnvironment = clean3(import_node_process9.default.env.ODLA_PLATFORM_URL);
7524
- const platformValue = platformAudience(
7525
- platformFlag ?? platformEnvironment ?? profile.value?.platform ?? loaded?.platformUrl ?? DEFAULT_PLATFORM2
7526
- );
7527
- const platformSource = platformFlag ? "flag" : platformEnvironment ? "environment" : profile.value ? "profile" : loaded ? "config" : "default";
7528
- const appFlag = clean3(stringOpt(parsed.options.app));
7529
- const appEnvironment = clean3(import_node_process9.default.env.ODLA_APP_ID);
7530
- const appValue = appFlag ?? appEnvironment ?? profile.value?.app ?? loaded?.app.id ?? null;
7531
- const appSource = appFlag ? "flag" : appEnvironment ? "environment" : profile.value?.app ? "profile" : loaded ? "config" : "unresolved";
7532
- if (appValue) assertOperatorName(appValue, "app");
7533
- if (options.requireApp && !appValue) {
7534
- throw new Error(
7535
- "app context is unresolved; pass --app <id>, set ODLA_APP_ID, select --context <name>, or run inside a project with odla.config.mjs"
7536
- );
7537
- }
7538
- const envFlag = clean3(stringOpt(parsed.options.env));
7539
- const envEnvironment = clean3(import_node_process9.default.env.ODLA_ENV);
7540
- const environmentValue = envFlag ?? envEnvironment ?? profile.value?.environment ?? options.defaultEnvironment ?? null;
7541
- const environmentSource = envFlag ? "flag" : envEnvironment ? "environment" : profile.value?.environment ? "profile" : options.defaultEnvironment ? "default" : "unresolved";
7542
- if (environmentValue) {
7543
- assertOperatorName(environmentValue, "environment");
7544
- }
7545
- const rootDir = loaded?.rootDir ?? import_node_process9.default.cwd();
7546
- const profileCredentials = operatorCredentialFiles(profile);
7547
- const tokenFile = clean3(import_node_process9.default.env.ODLA_DEV_TOKEN_FILE) ? (0, import_node_path12.resolve)(import_node_process9.default.env.ODLA_DEV_TOKEN_FILE) : profile.name ? profileCredentials.developer : loaded?.local.tokenFile ?? profileCredentials.developer;
7548
- const scopedTokenFile = clean3(import_node_process9.default.env.ODLA_ADMIN_TOKEN_FILE) ? (0, import_node_path12.resolve)(import_node_process9.default.env.ODLA_ADMIN_TOKEN_FILE) : profile.name ? profileCredentials.scoped : loaded ? (0, import_node_path12.join)(loaded.rootDir, ".odla", "admin-token.local.json") : profileCredentials.scoped;
7549
- const cfg = loaded ? {
7550
- ...loaded,
7551
- platformUrl: platformValue,
7552
- app: appValue ? {
7553
- id: appValue,
7554
- name: appValue === loaded.app.id ? loaded.app.name : appValue
7555
- } : loaded.app,
7556
- local: { ...loaded.local, tokenFile }
7557
- } : {
7558
- configPath,
7559
- rootDir,
7560
- platformUrl: platformValue,
7561
- dbEndpoint: platformValue,
7562
- app: {
7563
- id: appValue ?? "operator",
7564
- name: appValue ?? "Operator"
7565
- },
7566
- envs: environmentValue ? [environmentValue] : [],
7567
- services: [],
7568
- local: {
7569
- tokenFile,
7570
- credentialsFile: (0, import_node_path12.join)(rootDir, ".odla", "credentials.local.json"),
7571
- devVarsFile: (0, import_node_path12.join)(rootDir, ".dev.vars"),
7572
- gitignore: true
7573
- }
7574
- };
7575
- return {
7576
- cfg,
7577
- profile: {
7578
- name: profile.name,
7579
- source: profile.source,
7580
- file: profile.file
7581
- },
7582
- config: {
7583
- path: configPath,
7584
- status: loaded ? "loaded" : "absent",
7585
- explicit: explicitConfig
7586
- },
7587
- platform: { value: platformValue, source: platformSource },
7588
- app: { value: appValue, source: appSource },
7589
- environment: {
7590
- value: environmentValue,
7591
- source: environmentSource
7592
- },
7593
- credentials: {
7594
- developerTokenFile: tokenFile,
7595
- scopedTokenFile
7596
- }
7597
- };
7598
- }
7599
7616
  function clean3(value) {
7600
7617
  const normalized = value?.trim();
7601
7618
  return normalized || void 0;
@@ -8439,33 +8456,53 @@ var ALIASES = {
8439
8456
  decision: "decision",
8440
8457
  bug: "bug"
8441
8458
  };
8442
- var ALLOWED2 = [
8443
- "config",
8444
- "token",
8445
- "email",
8446
- "json",
8447
- "app",
8448
- "title",
8449
- "status",
8450
- "severity",
8451
- "column",
8452
- "rank",
8453
- "goal",
8454
- "assignee",
8455
- "due",
8456
- "proof",
8457
- "body",
8458
- "target",
8459
- "description",
8460
- "desc",
8461
- "decision",
8462
- "limit",
8463
- "offset",
8464
- "q",
8465
- "mutation-id",
8466
- "platform",
8467
- "context"
8468
- ];
8459
+ var COMMON_OPTIONS = ["config", "token", "email", "json", "platform", "context"];
8460
+ var ACTION_OPTIONS = {
8461
+ list: ["app", "q", "limit", "offset"],
8462
+ add: ["app", "title", "mutation-id"],
8463
+ get: [],
8464
+ set: ["mutation-id"],
8465
+ done: ["mutation-id"],
8466
+ comment: ["body", "mutation-id"],
8467
+ comments: [],
8468
+ rm: []
8469
+ };
8470
+ var ENTITY_OPTIONS = {
8471
+ goal: {
8472
+ list: ["status"],
8473
+ add: ["status", "proof", "target"],
8474
+ set: ["title", "status", "proof", "target"],
8475
+ done: []
8476
+ },
8477
+ task: {
8478
+ list: ["column", "goal", "assignee"],
8479
+ add: ["column", "goal", "assignee", "due", "description", "desc", "body"],
8480
+ set: ["title", "column", "rank", "goal", "assignee", "due", "description", "desc", "body"],
8481
+ done: []
8482
+ },
8483
+ decision: {
8484
+ list: ["status"],
8485
+ add: ["status", "body"],
8486
+ set: ["title", "status", "body"],
8487
+ done: []
8488
+ },
8489
+ bug: {
8490
+ list: ["status", "severity", "goal", "assignee", "decision"],
8491
+ add: ["status", "severity", "goal", "assignee", "decision", "description", "desc", "body"],
8492
+ set: ["title", "status", "severity", "goal", "assignee", "decision", "description", "desc", "body"],
8493
+ done: ["decision"]
8494
+ }
8495
+ };
8496
+ function canonicalAction(action) {
8497
+ if (action === "create") return "add";
8498
+ if (action === "update" || action === "status" || action === "move") return "set";
8499
+ if (action === "delete") return "rm";
8500
+ return action in ACTION_OPTIONS ? action : null;
8501
+ }
8502
+ function allowedOptions(entity, action) {
8503
+ const entityOptions = action === "list" || action === "add" || action === "set" || action === "done" ? ENTITY_OPTIONS[entity][action] : [];
8504
+ return [...COMMON_OPTIONS, ...ACTION_OPTIONS[action], ...entityOptions];
8505
+ }
8469
8506
  function requireId2(id, action) {
8470
8507
  if (!id) throw new Error(`"pm ... ${action}" needs an item id`);
8471
8508
  return id;
@@ -8497,27 +8534,25 @@ async function buildContext2(parsed, deps) {
8497
8534
  async function pmCommand(parsed, deps = {}) {
8498
8535
  const word = parsed.positionals[1] ?? "";
8499
8536
  if (word === "handoff") {
8500
- assertArgs(parsed, ALLOWED2, 2);
8537
+ assertArgs(parsed, [...COMMON_OPTIONS, "app"], 2);
8501
8538
  return pmHandoff(await buildContext2(parsed, deps), parsed);
8502
8539
  }
8503
8540
  const entity = ALIASES[word];
8504
8541
  if (!entity) throw new Error(`unknown pm entity "${word}". Try "odla-ai pm bug list" (goal|task|decision|bug).`);
8505
- const action = parsed.positionals[2] ?? "list";
8506
- assertArgs(parsed, ALLOWED2, 4);
8542
+ const requestedAction = parsed.positionals[2] ?? "list";
8543
+ const action = canonicalAction(requestedAction);
8544
+ if (!action) throw new Error(`unknown pm action "${requestedAction}". Try list|add|get|set|done|comment|comments|rm.`);
8545
+ assertArgs(parsed, allowedOptions(entity, action), 4);
8507
8546
  const ctx = await buildContext2(parsed, deps);
8508
8547
  const id = parsed.positionals[3];
8509
8548
  switch (action) {
8510
8549
  case "list":
8511
8550
  return pmList(ctx, entity, parsed);
8512
8551
  case "add":
8513
- case "create":
8514
8552
  return pmAdd(ctx, entity, parsed);
8515
8553
  case "get":
8516
8554
  return pmGet(ctx, entity, requireId2(id, action));
8517
8555
  case "set":
8518
- case "update":
8519
- case "status":
8520
- case "move":
8521
8556
  return pmSet(ctx, entity, requireId2(id, action), parsed);
8522
8557
  case "done":
8523
8558
  return pmDone(ctx, entity, requireId2(id, action), parsed);
@@ -8526,10 +8561,7 @@ async function pmCommand(parsed, deps = {}) {
8526
8561
  case "comments":
8527
8562
  return pmComments(ctx, entity, requireId2(id, action));
8528
8563
  case "rm":
8529
- case "delete":
8530
8564
  return pmRemove(ctx, entity, requireId2(id, action));
8531
- default:
8532
- throw new Error(`unknown pm action "${action}". Try list|add|get|set|done|comment|comments|rm.`);
8533
8565
  }
8534
8566
  }
8535
8567
 
@@ -9491,7 +9523,7 @@ function recordInvocation(parsed) {
9491
9523
  try {
9492
9524
  const entry = {
9493
9525
  path: invocationPath(parsed.positionals),
9494
- options: Object.keys(parsed.options).sort()
9526
+ options: Object.entries(parsed.options).map(([name, value]) => value === false ? `no-${name}` : name).sort()
9495
9527
  };
9496
9528
  if (!entry.path.length) return;
9497
9529
  (0, import_node_fs15.appendFileSync)(file, `${JSON.stringify(entry)}
@@ -10264,7 +10296,7 @@ async function whoamiCommand(parsed, deps = {}) {
10264
10296
  }
10265
10297
 
10266
10298
  // src/runbook-command.ts
10267
- var ALLOWED3 = [
10299
+ var ALLOWED2 = [
10268
10300
  "config",
10269
10301
  "token",
10270
10302
  "email",
@@ -10352,7 +10384,7 @@ async function buildContext3(parsed, deps, action) {
10352
10384
  }
10353
10385
  async function runbookCommand(parsed, deps = {}) {
10354
10386
  const action = parsed.positionals[1] ?? "list";
10355
- assertArgs(parsed, ALLOWED3, action === "ask" || action === "search" ? 64 : 4);
10387
+ assertArgs(parsed, ALLOWED2, action === "ask" || action === "search" ? 64 : 4);
10356
10388
  const ctx = await buildContext3(parsed, deps, action);
10357
10389
  const slug = parsed.positionals[2];
10358
10390
  switch (action) {
@@ -11181,7 +11213,7 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
11181
11213
  return;
11182
11214
  }
11183
11215
  if (command === "admin") {
11184
- await adminCommand(parsed);
11216
+ await adminCommand(parsed, runtime);
11185
11217
  return;
11186
11218
  }
11187
11219
  if (command === "agent") {