@odla-ai/cli 0.25.18 → 0.25.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1019,66 +1019,10 @@ function addOption(options, name, value) {
1019
1019
  else options[name] = [String(current), String(value)];
1020
1020
  }
1021
1021
 
1022
- // src/admin-command.ts
1023
- async function adminCommand(parsed) {
1024
- const area = parsed.positionals[1];
1025
- const action = parsed.positionals[2];
1026
- const credentialSet = action === "credential" && parsed.positionals[3] === "set";
1027
- const credentials = action === "credentials";
1028
- const models = action === "models";
1029
- const usage = action === "usage";
1030
- const audit = action === "audit";
1031
- if (area !== "ai" || action !== "show" && action !== "set" && !credentialSet && !credentials && !models && !usage && !audit) {
1032
- throw new Error('unknown admin command. Try "odla-ai admin ai show".');
1033
- }
1034
- assertArgs(parsed, [
1035
- "platform",
1036
- "json",
1037
- "open",
1038
- "email",
1039
- "provider",
1040
- "model",
1041
- "enabled",
1042
- "max-input-bytes",
1043
- "max-output-tokens",
1044
- "max-calls-per-run",
1045
- "from-env",
1046
- "stdin",
1047
- "discovery-provider",
1048
- "discovery-model",
1049
- "validation-provider",
1050
- "validation-model",
1051
- "app-id",
1052
- "env",
1053
- "run-id",
1054
- "limit"
1055
- ], credentialSet ? 5 : action === "set" ? 4 : 3);
1056
- await adminAi({
1057
- action: credentialSet ? "credential-set" : credentials ? "credentials" : models ? "models" : usage ? "usage" : audit ? "audit" : action,
1058
- purpose: action === "set" ? parsed.positionals[3] : void 0,
1059
- provider: stringOpt(parsed.options.provider),
1060
- model: stringOpt(parsed.options.model),
1061
- enabled: boolOpt(parsed.options.enabled),
1062
- maxInputBytes: numberOpt(parsed.options["max-input-bytes"], "--max-input-bytes"),
1063
- maxOutputTokens: numberOpt(parsed.options["max-output-tokens"], "--max-output-tokens"),
1064
- maxCallsPerRun: numberOpt(parsed.options["max-calls-per-run"], "--max-calls-per-run"),
1065
- platform: stringOpt(parsed.options.platform),
1066
- email: stringOpt(parsed.options.email),
1067
- json: parsed.options.json === true,
1068
- open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
1069
- credentialProvider: credentialSet ? parsed.positionals[4] : void 0,
1070
- fromEnv: stringOpt(parsed.options["from-env"]),
1071
- stdin: parsed.options.stdin === true,
1072
- discoveryProvider: stringOpt(parsed.options["discovery-provider"]),
1073
- discoveryModel: stringOpt(parsed.options["discovery-model"]),
1074
- validationProvider: stringOpt(parsed.options["validation-provider"]),
1075
- validationModel: stringOpt(parsed.options["validation-model"]),
1076
- appId: stringOpt(parsed.options["app-id"]),
1077
- env: stringOpt(parsed.options.env),
1078
- runId: stringOpt(parsed.options["run-id"]),
1079
- limit: numberOpt(parsed.options.limit, "--limit")
1080
- });
1081
- }
1022
+ // src/operator-context.ts
1023
+ var import_node_fs6 = require("fs");
1024
+ var import_node_path6 = require("path");
1025
+ var import_node_process8 = __toESM(require("process"), 1);
1082
1026
 
1083
1027
  // src/config.ts
1084
1028
  var import_node_fs4 = require("fs");
@@ -1364,6 +1308,291 @@ function unique2(values) {
1364
1308
  return [...new Set(values.filter(Boolean))];
1365
1309
  }
1366
1310
 
1311
+ // src/operator-profiles.ts
1312
+ var import_node_fs5 = require("fs");
1313
+ var import_node_os = require("os");
1314
+ var import_node_path5 = require("path");
1315
+ var import_node_process7 = __toESM(require("process"), 1);
1316
+ function operatorProfileFile() {
1317
+ return (0, import_node_path5.resolve)(
1318
+ clean(import_node_process7.default.env.ODLA_CONTEXT_FILE) ?? (0, import_node_path5.join)((0, import_node_os.homedir)(), ".odla", "contexts.json")
1319
+ );
1320
+ }
1321
+ function resolveOperatorProfile(parsed) {
1322
+ const fromFlag = clean(stringOpt(parsed.options.context));
1323
+ const fromEnvironment = clean(import_node_process7.default.env.ODLA_CONTEXT);
1324
+ const name = fromFlag ?? fromEnvironment ?? null;
1325
+ const file = operatorProfileFile();
1326
+ if (!name) {
1327
+ return { name: null, source: "unresolved", file, value: null };
1328
+ }
1329
+ assertOperatorName(name, "context");
1330
+ const profiles = readOperatorProfiles(file);
1331
+ const value = Object.hasOwn(profiles, name) ? profiles[name] : void 0;
1332
+ if (!value) {
1333
+ throw new Error(
1334
+ `operator context "${name}" not found in ${file}; run "odla-ai context list" or save it first`
1335
+ );
1336
+ }
1337
+ return {
1338
+ name,
1339
+ source: fromFlag ? "flag" : "environment",
1340
+ file,
1341
+ value
1342
+ };
1343
+ }
1344
+ function listOperatorProfiles(file = operatorProfileFile()) {
1345
+ return Object.entries(readOperatorProfiles(file)).sort(([a], [b]) => a.localeCompare(b)).map(([name, profile]) => ({ name, ...profile }));
1346
+ }
1347
+ function saveOperatorProfile(name, profile, file = operatorProfileFile()) {
1348
+ assertOperatorName(name, "context");
1349
+ const normalized = validateProfile(profile, `operator context "${name}"`);
1350
+ const profiles = readOperatorProfiles(file);
1351
+ profiles[name] = normalized;
1352
+ writePrivateJson(file, { schemaVersion: 1, profiles });
1353
+ }
1354
+ function removeOperatorProfile(name, file = operatorProfileFile()) {
1355
+ assertOperatorName(name, "context");
1356
+ const profiles = readOperatorProfiles(file);
1357
+ if (!Object.hasOwn(profiles, name)) return false;
1358
+ delete profiles[name];
1359
+ writePrivateJson(file, { schemaVersion: 1, profiles });
1360
+ return true;
1361
+ }
1362
+ function operatorCredentialFiles(selection) {
1363
+ 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");
1364
+ return {
1365
+ developer: (0, import_node_path5.join)(base, "dev-token.json"),
1366
+ scoped: (0, import_node_path5.join)(base, "admin-token.local.json")
1367
+ };
1368
+ }
1369
+ function assertOperatorName(value, label) {
1370
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(value)) {
1371
+ throw new Error(
1372
+ `${label} must contain lowercase letters, numbers, and hyphens`
1373
+ );
1374
+ }
1375
+ }
1376
+ function readOperatorProfiles(file) {
1377
+ if (!(0, import_node_fs5.existsSync)(file)) return emptyProfiles();
1378
+ let raw;
1379
+ try {
1380
+ raw = JSON.parse((0, import_node_fs5.readFileSync)(file, "utf8"));
1381
+ } catch {
1382
+ throw new Error(`operator context file ${file} is not valid JSON`);
1383
+ }
1384
+ if (!raw || typeof raw !== "object" || raw.schemaVersion !== 1 || !raw.profiles || typeof raw.profiles !== "object" || Array.isArray(raw.profiles)) {
1385
+ throw new Error(`operator context file ${file} has an invalid shape`);
1386
+ }
1387
+ const unknown = Object.keys(raw).filter(
1388
+ (key) => !["schemaVersion", "profiles"].includes(key)
1389
+ );
1390
+ if (unknown.length > 0) {
1391
+ throw new Error(
1392
+ `operator context file ${file} contains unsupported field(s): ${unknown.sort().join(", ")}`
1393
+ );
1394
+ }
1395
+ const profiles = emptyProfiles();
1396
+ for (const [name, value] of Object.entries(
1397
+ raw.profiles
1398
+ )) {
1399
+ assertOperatorName(name, "context");
1400
+ profiles[name] = validateProfile(value, `operator context "${name}"`);
1401
+ }
1402
+ return profiles;
1403
+ }
1404
+ function emptyProfiles() {
1405
+ return /* @__PURE__ */ Object.create(null);
1406
+ }
1407
+ function validateProfile(value, label) {
1408
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1409
+ throw new Error(`${label} has an invalid shape`);
1410
+ }
1411
+ const unknown = Object.keys(value).filter(
1412
+ (key) => !["platform", "app", "environment"].includes(key)
1413
+ );
1414
+ if (unknown.length > 0) {
1415
+ throw new Error(
1416
+ `${label} contains unsupported field(s): ${unknown.sort().join(", ")}; contexts store scope metadata only, never credentials`
1417
+ );
1418
+ }
1419
+ const candidate = value;
1420
+ if (typeof candidate.platform !== "string") {
1421
+ throw new Error(`${label} needs an absolute platform URL`);
1422
+ }
1423
+ const platform = platformAudience(candidate.platform);
1424
+ const app = clean(candidate.app);
1425
+ const environment2 = clean(candidate.environment);
1426
+ if (app) assertOperatorName(app, "app");
1427
+ if (environment2) assertOperatorName(environment2, "environment");
1428
+ return {
1429
+ platform,
1430
+ ...app ? { app } : {},
1431
+ ...environment2 ? { environment: environment2 } : {}
1432
+ };
1433
+ }
1434
+ function clean(value) {
1435
+ const normalized = value?.trim();
1436
+ return normalized || void 0;
1437
+ }
1438
+
1439
+ // src/operator-context.ts
1440
+ var DEFAULT_PLATFORM2 = "https://odla.ai";
1441
+ async function resolveOperatorContext(parsed, options = {}) {
1442
+ const profile = resolveOperatorProfile(parsed);
1443
+ const configArgument = stringOpt(parsed.options.config) ?? "odla.config.mjs";
1444
+ const configPath = (0, import_node_path6.resolve)(configArgument);
1445
+ const explicitConfig = parsed.options.config !== void 0;
1446
+ const hasConfig = (0, import_node_fs6.existsSync)(configPath);
1447
+ if (!hasConfig && (!options.allowMissingConfig || explicitConfig)) {
1448
+ await loadProjectConfig(configArgument);
1449
+ }
1450
+ const loaded = hasConfig ? await loadProjectConfig(configArgument) : void 0;
1451
+ const platformFlag = clean2(stringOpt(parsed.options.platform));
1452
+ const platformEnvironment = clean2(import_node_process8.default.env.ODLA_PLATFORM_URL);
1453
+ const platformValue = platformAudience(
1454
+ platformFlag ?? platformEnvironment ?? profile.value?.platform ?? loaded?.platformUrl ?? DEFAULT_PLATFORM2
1455
+ );
1456
+ const platformSource = platformFlag ? "flag" : platformEnvironment ? "environment" : profile.value ? "profile" : loaded ? "config" : "default";
1457
+ const appFlag = clean2(stringOpt(parsed.options.app));
1458
+ const appEnvironment = clean2(import_node_process8.default.env.ODLA_APP_ID);
1459
+ const appValue = appFlag ?? appEnvironment ?? profile.value?.app ?? loaded?.app.id ?? null;
1460
+ const appSource = appFlag ? "flag" : appEnvironment ? "environment" : profile.value?.app ? "profile" : loaded ? "config" : "unresolved";
1461
+ if (appValue) assertOperatorName(appValue, "app");
1462
+ if (options.requireApp && !appValue) {
1463
+ throw new Error(
1464
+ "app context is unresolved; pass --app <id>, set ODLA_APP_ID, select --context <name>, or run inside a project with odla.config.mjs"
1465
+ );
1466
+ }
1467
+ const envFlag = clean2(stringOpt(parsed.options.env));
1468
+ const envEnvironment = clean2(import_node_process8.default.env.ODLA_ENV);
1469
+ const environmentValue = envFlag ?? envEnvironment ?? profile.value?.environment ?? options.defaultEnvironment ?? null;
1470
+ const environmentSource = envFlag ? "flag" : envEnvironment ? "environment" : profile.value?.environment ? "profile" : options.defaultEnvironment ? "default" : "unresolved";
1471
+ if (environmentValue) {
1472
+ assertOperatorName(environmentValue, "environment");
1473
+ }
1474
+ const rootDir = loaded?.rootDir ?? import_node_process8.default.cwd();
1475
+ const profileCredentials = operatorCredentialFiles(profile);
1476
+ 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;
1477
+ 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;
1478
+ const cfg = loaded ? {
1479
+ ...loaded,
1480
+ platformUrl: platformValue,
1481
+ app: appValue ? {
1482
+ id: appValue,
1483
+ name: appValue === loaded.app.id ? loaded.app.name : appValue
1484
+ } : loaded.app,
1485
+ local: { ...loaded.local, tokenFile }
1486
+ } : {
1487
+ configPath,
1488
+ rootDir,
1489
+ platformUrl: platformValue,
1490
+ dbEndpoint: platformValue,
1491
+ app: {
1492
+ id: appValue ?? "operator",
1493
+ name: appValue ?? "Operator"
1494
+ },
1495
+ envs: environmentValue ? [environmentValue] : [],
1496
+ services: [],
1497
+ local: {
1498
+ tokenFile,
1499
+ credentialsFile: (0, import_node_path6.join)(rootDir, ".odla", "credentials.local.json"),
1500
+ devVarsFile: (0, import_node_path6.join)(rootDir, ".dev.vars"),
1501
+ gitignore: true
1502
+ }
1503
+ };
1504
+ return {
1505
+ cfg,
1506
+ profile: {
1507
+ name: profile.name,
1508
+ source: profile.source,
1509
+ file: profile.file
1510
+ },
1511
+ config: {
1512
+ path: configPath,
1513
+ status: loaded ? "loaded" : "absent",
1514
+ explicit: explicitConfig
1515
+ },
1516
+ platform: { value: platformValue, source: platformSource },
1517
+ app: { value: appValue, source: appSource },
1518
+ environment: {
1519
+ value: environmentValue,
1520
+ source: environmentSource
1521
+ },
1522
+ credentials: {
1523
+ developerTokenFile: tokenFile,
1524
+ scopedTokenFile
1525
+ }
1526
+ };
1527
+ }
1528
+ function clean2(value) {
1529
+ const normalized = value?.trim();
1530
+ return normalized || void 0;
1531
+ }
1532
+
1533
+ // src/admin-command.ts
1534
+ var CONTEXT_OPTIONS = ["platform", "config", "context", "token", "open", "email"];
1535
+ var JSON_OPTIONS = [...CONTEXT_OPTIONS, "json"];
1536
+ var SET_OPTIONS = [
1537
+ ...JSON_OPTIONS,
1538
+ "provider",
1539
+ "model",
1540
+ "enabled",
1541
+ "max-input-bytes",
1542
+ "max-output-tokens",
1543
+ "max-calls-per-run",
1544
+ "discovery-provider",
1545
+ "discovery-model",
1546
+ "validation-provider",
1547
+ "validation-model"
1548
+ ];
1549
+ async function adminCommand(parsed, deps = {}) {
1550
+ const area = parsed.positionals[1];
1551
+ const action = parsed.positionals[2];
1552
+ const credentialSet = action === "credential" && parsed.positionals[3] === "set";
1553
+ const credentials = action === "credentials";
1554
+ const models = action === "models";
1555
+ const usage = action === "usage";
1556
+ const audit = action === "audit";
1557
+ if (area !== "ai" || action !== "show" && action !== "set" && !credentialSet && !credentials && !models && !usage && !audit) {
1558
+ throw new Error('unknown admin command. Try "odla-ai admin ai show".');
1559
+ }
1560
+ 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;
1561
+ assertArgs(parsed, allowed, credentialSet ? 5 : action === "set" ? 4 : 3);
1562
+ const context = await resolveOperatorContext(parsed, { allowMissingConfig: true });
1563
+ await adminAi({
1564
+ action: credentialSet ? "credential-set" : credentials ? "credentials" : models ? "models" : usage ? "usage" : audit ? "audit" : action,
1565
+ purpose: action === "set" ? parsed.positionals[3] : void 0,
1566
+ provider: stringOpt(parsed.options.provider),
1567
+ model: stringOpt(parsed.options.model),
1568
+ enabled: boolOpt(parsed.options.enabled),
1569
+ maxInputBytes: numberOpt(parsed.options["max-input-bytes"], "--max-input-bytes"),
1570
+ maxOutputTokens: numberOpt(parsed.options["max-output-tokens"], "--max-output-tokens"),
1571
+ maxCallsPerRun: numberOpt(parsed.options["max-calls-per-run"], "--max-calls-per-run"),
1572
+ platform: context.platform.value,
1573
+ token: stringOpt(parsed.options.token),
1574
+ tokenFile: context.credentials.scopedTokenFile,
1575
+ email: stringOpt(parsed.options.email),
1576
+ json: parsed.options.json === true,
1577
+ open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
1578
+ credentialProvider: credentialSet ? parsed.positionals[4] : void 0,
1579
+ fromEnv: stringOpt(parsed.options["from-env"]),
1580
+ stdin: parsed.options.stdin === true,
1581
+ readStdin: deps.readStdin,
1582
+ discoveryProvider: stringOpt(parsed.options["discovery-provider"]),
1583
+ discoveryModel: stringOpt(parsed.options["discovery-model"]),
1584
+ validationProvider: stringOpt(parsed.options["validation-provider"]),
1585
+ validationModel: stringOpt(parsed.options["validation-model"]),
1586
+ appId: stringOpt(parsed.options["app-id"]),
1587
+ env: stringOpt(parsed.options.env),
1588
+ runId: stringOpt(parsed.options["run-id"]),
1589
+ limit: numberOpt(parsed.options.limit, "--limit"),
1590
+ fetch: deps.fetch,
1591
+ stdout: deps.stdout,
1592
+ openApprovalUrl: deps.openUrl
1593
+ });
1594
+ }
1595
+
1367
1596
  // src/tenant.ts
1368
1597
  var import_apps = require("@odla-ai/apps");
1369
1598
  function resolveEnv(cfg, requested) {
@@ -1469,7 +1698,7 @@ function errorMessage(body) {
1469
1698
  }
1470
1699
 
1471
1700
  // src/app-export.ts
1472
- var import_node_fs5 = require("fs");
1701
+ var import_node_fs7 = require("fs");
1473
1702
  var import_node_stream = require("stream");
1474
1703
  var import_promises = require("stream/promises");
1475
1704
  async function appExport(options) {
@@ -1502,7 +1731,7 @@ async function appExport(options) {
1502
1731
  const download = await doFetch(`${base}/backups/${newest.id}/download`, { headers: auth });
1503
1732
  if (!download.ok || !download.body) throw new Error(`download failed (${download.status})`);
1504
1733
  const file = options.out ?? `${tenant}-${new Date(newest.created_at).toISOString().slice(0, 10)}-tx${newest.max_tx}.jsonl.gz`;
1505
- await (0, import_promises.pipeline)(import_node_stream.Readable.fromWeb(download.body), (0, import_node_fs5.createWriteStream)(file));
1734
+ await (0, import_promises.pipeline)(import_node_stream.Readable.fromWeb(download.body), (0, import_node_fs7.createWriteStream)(file));
1506
1735
  if (options.json) out.log(JSON.stringify({ file, backup: newest }, null, 2));
1507
1736
  else {
1508
1737
  out.log(`${tenant}: wrote ${file} (${newest.bytes} bytes, ${newest.kind} snapshot at tx ${newest.max_tx})`);
@@ -1512,7 +1741,7 @@ async function appExport(options) {
1512
1741
  }
1513
1742
 
1514
1743
  // src/app-import.ts
1515
- var import_node_fs6 = require("fs");
1744
+ var import_node_fs8 = require("fs");
1516
1745
  var import_import = require("@odla-ai/db/import");
1517
1746
  function chooseIdMode(options, rows) {
1518
1747
  const chosen = [options.idField && "field", options.key && "key", options.generateIds && "generate"].filter(Boolean);
@@ -1531,7 +1760,7 @@ async function appImport(options) {
1531
1760
  const say = options.json ? (line) => out.error(line) : (line) => out.log(line);
1532
1761
  const doFetch = options.fetch ?? fetch;
1533
1762
  const { tenant } = resolveTenant(cfg, options.env);
1534
- const text = options.file === "-" ? (options.readStdin ?? (() => (0, import_node_fs6.readFileSync)(0, "utf8")))() : (0, import_node_fs6.readFileSync)(options.file, "utf8");
1763
+ const text = options.file === "-" ? (options.readStdin ?? (() => (0, import_node_fs8.readFileSync)(0, "utf8")))() : (0, import_node_fs8.readFileSync)(options.file, "utf8");
1535
1764
  const { format, sources } = (0, import_import.parseImport)(text, options.ns);
1536
1765
  if (format === "namespace-map" && options.ns) {
1537
1766
  throw new Error("--ns cannot be combined with a {namespace: rows} file \u2014 the file already names each namespace");
@@ -2498,13 +2727,13 @@ function printGroup(out, heading, items) {
2498
2727
 
2499
2728
  // src/doctor-checks.ts
2500
2729
  var import_node_child_process3 = require("child_process");
2501
- var import_node_fs8 = require("fs");
2502
- var import_node_path6 = require("path");
2730
+ var import_node_fs10 = require("fs");
2731
+ var import_node_path8 = require("path");
2503
2732
 
2504
2733
  // src/wrangler.ts
2505
2734
  var import_node_child_process2 = require("child_process");
2506
- var import_node_fs7 = require("fs");
2507
- var import_node_path5 = require("path");
2735
+ var import_node_fs9 = require("fs");
2736
+ var import_node_path7 = require("path");
2508
2737
  var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
2509
2738
  const child = (0, import_node_child_process2.spawn)(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
2510
2739
  let stdout = "";
@@ -2518,15 +2747,15 @@ var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) =>
2518
2747
  var WRANGLER_CONFIG_FILES = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"];
2519
2748
  function findWranglerConfig(rootDir) {
2520
2749
  for (const name of WRANGLER_CONFIG_FILES) {
2521
- const path = (0, import_node_path5.join)(rootDir, name);
2522
- if ((0, import_node_fs7.existsSync)(path)) return path;
2750
+ const path = (0, import_node_path7.join)(rootDir, name);
2751
+ if ((0, import_node_fs9.existsSync)(path)) return path;
2523
2752
  }
2524
2753
  return null;
2525
2754
  }
2526
2755
  function readWranglerConfig(path) {
2527
2756
  if (path.endsWith(".toml")) return null;
2528
2757
  try {
2529
- return JSON.parse(stripJsonComments((0, import_node_fs7.readFileSync)(path, "utf8")));
2758
+ return JSON.parse(stripJsonComments((0, import_node_fs9.readFileSync)(path, "utf8")));
2530
2759
  } catch {
2531
2760
  return null;
2532
2761
  }
@@ -2624,10 +2853,10 @@ function wranglerWarnings(rootDir) {
2624
2853
  for (const { label, block } of blocks) {
2625
2854
  const assets = block.assets;
2626
2855
  if (assets?.directory) {
2627
- const dir = (0, import_node_path6.resolve)(rootDir, assets.directory);
2628
- if (dir === (0, import_node_path6.resolve)(rootDir)) {
2856
+ const dir = (0, import_node_path8.resolve)(rootDir, assets.directory);
2857
+ if (dir === (0, import_node_path8.resolve)(rootDir)) {
2629
2858
  warnings.push(`${label}assets.directory is the project root \u2014 point it at a dedicated build dir (wrangler dev fails with "spawn EBADF")`);
2630
- } else if ((0, import_node_fs8.existsSync)((0, import_node_path6.join)(dir, "node_modules"))) {
2859
+ } else if ((0, import_node_fs10.existsSync)((0, import_node_path8.join)(dir, "node_modules"))) {
2631
2860
  warnings.push(`${label}assets.directory contains node_modules \u2014 wrangler dev's watcher will exhaust file descriptors`);
2632
2861
  }
2633
2862
  }
@@ -2662,13 +2891,13 @@ function o11yProjectWarnings(rootDir) {
2662
2891
  warnings.push("cannot verify o11y Worker instrumentation \u2014 add a parseable wrangler.jsonc/json config");
2663
2892
  return warnings;
2664
2893
  }
2665
- const main = typeof config.main === "string" ? (0, import_node_path6.resolve)(rootDir, config.main) : null;
2666
- if (!main || !(0, import_node_fs8.existsSync)(main)) {
2894
+ const main = typeof config.main === "string" ? (0, import_node_path8.resolve)(rootDir, config.main) : null;
2895
+ if (!main || !(0, import_node_fs10.existsSync)(main)) {
2667
2896
  warnings.push("cannot verify o11y Worker instrumentation \u2014 wrangler main is missing or unreadable");
2668
2897
  } else {
2669
2898
  let source = "";
2670
2899
  try {
2671
- source = (0, import_node_fs8.readFileSync)(main, "utf8");
2900
+ source = (0, import_node_fs10.readFileSync)(main, "utf8");
2672
2901
  } catch {
2673
2902
  }
2674
2903
  if (!/\bwithObservability\b/.test(source)) {
@@ -2692,7 +2921,7 @@ function calendarProjectWarnings(rootDir) {
2692
2921
  }
2693
2922
  function readPackageJson(rootDir) {
2694
2923
  try {
2695
- return JSON.parse((0, import_node_fs8.readFileSync)((0, import_node_path6.join)(rootDir, "package.json"), "utf8"));
2924
+ return JSON.parse((0, import_node_fs10.readFileSync)((0, import_node_path8.join)(rootDir, "package.json"), "utf8"));
2696
2925
  } catch {
2697
2926
  return null;
2698
2927
  }
@@ -2919,13 +3148,13 @@ function harnessOption(value, flag) {
2919
3148
  }
2920
3149
 
2921
3150
  // src/init.ts
2922
- var import_node_fs9 = require("fs");
2923
- var import_node_path7 = require("path");
3151
+ var import_node_fs11 = require("fs");
3152
+ var import_node_path9 = require("path");
2924
3153
  function initProject(options) {
2925
3154
  const out = options.stdout ?? console;
2926
- const rootDir = (0, import_node_path7.resolve)(options.rootDir ?? process.cwd());
2927
- const configPath = (0, import_node_path7.resolve)(rootDir, options.configPath ?? "odla.config.mjs");
2928
- if ((0, import_node_fs9.existsSync)(configPath) && !options.force) {
3155
+ const rootDir = (0, import_node_path9.resolve)(options.rootDir ?? process.cwd());
3156
+ const configPath = (0, import_node_path9.resolve)(rootDir, options.configPath ?? "odla.config.mjs");
3157
+ if ((0, import_node_fs11.existsSync)(configPath) && !options.force) {
2929
3158
  throw new Error(`${configPath} already exists. Pass --force to overwrite.`);
2930
3159
  }
2931
3160
  if (!/^[a-z0-9][a-z0-9-]*$/.test(options.appId)) {
@@ -2935,20 +3164,20 @@ function initProject(options) {
2935
3164
  const services = options.services?.length ? options.services : ["db", "ai"];
2936
3165
  if (services.includes("calendar") && !services.includes("db")) throw new Error("--services calendar requires db");
2937
3166
  const aiProvider = options.aiProvider ?? "anthropic";
2938
- (0, import_node_fs9.mkdirSync)((0, import_node_path7.dirname)(configPath), { recursive: true });
2939
- (0, import_node_fs9.mkdirSync)((0, import_node_path7.resolve)(rootDir, "src/odla"), { recursive: true });
2940
- (0, import_node_fs9.mkdirSync)((0, import_node_path7.resolve)(rootDir, ".odla"), { recursive: true });
2941
- (0, import_node_fs9.writeFileSync)(configPath, configTemplate({ appId: options.appId, name: options.name, envs, services, aiProvider }));
2942
- writeIfMissing((0, import_node_path7.resolve)(rootDir, "src/odla/schema.mjs"), schemaTemplate());
2943
- writeIfMissing((0, import_node_path7.resolve)(rootDir, "src/odla/rules.mjs"), rulesTemplate());
3167
+ (0, import_node_fs11.mkdirSync)((0, import_node_path9.dirname)(configPath), { recursive: true });
3168
+ (0, import_node_fs11.mkdirSync)((0, import_node_path9.resolve)(rootDir, "src/odla"), { recursive: true });
3169
+ (0, import_node_fs11.mkdirSync)((0, import_node_path9.resolve)(rootDir, ".odla"), { recursive: true });
3170
+ (0, import_node_fs11.writeFileSync)(configPath, configTemplate({ appId: options.appId, name: options.name, envs, services, aiProvider }));
3171
+ writeIfMissing((0, import_node_path9.resolve)(rootDir, "src/odla/schema.mjs"), schemaTemplate());
3172
+ writeIfMissing((0, import_node_path9.resolve)(rootDir, "src/odla/rules.mjs"), rulesTemplate());
2944
3173
  ensureGitignore(rootDir);
2945
3174
  out.log(`created ${relativeDisplay(configPath, rootDir)}`);
2946
3175
  out.log("created src/odla/schema.mjs and src/odla/rules.mjs");
2947
3176
  out.log("updated .gitignore for local odla credentials");
2948
3177
  }
2949
3178
  function writeIfMissing(path, text) {
2950
- if ((0, import_node_fs9.existsSync)(path)) return;
2951
- (0, import_node_fs9.writeFileSync)(path, text);
3179
+ if ((0, import_node_fs11.existsSync)(path)) return;
3180
+ (0, import_node_fs11.writeFileSync)(path, text);
2952
3181
  }
2953
3182
  function configTemplate(input) {
2954
3183
  const calendar = input.services.includes("calendar") ? ` calendar: {
@@ -3179,9 +3408,9 @@ async function resolveVaultWrite(options) {
3179
3408
  }
3180
3409
 
3181
3410
  // src/skill.ts
3182
- var import_node_fs10 = require("fs");
3183
- var import_node_os = require("os");
3184
- var import_node_path8 = require("path");
3411
+ var import_node_fs12 = require("fs");
3412
+ var import_node_os2 = require("os");
3413
+ var import_node_path10 = require("path");
3185
3414
  var import_node_url2 = require("url");
3186
3415
 
3187
3416
  // src/skill-adapters.ts
@@ -3260,8 +3489,8 @@ function installSkill(options = {}) {
3260
3489
  const files = listFiles(sourceDir);
3261
3490
  if (files.length === 0) throw new Error(`no bundled skills found at ${sourceDir}`);
3262
3491
  const harnesses = normalizeHarnesses(options.harnesses, options.global === true);
3263
- const root = (0, import_node_path8.resolve)(options.dir ?? process.cwd());
3264
- const home = (0, import_node_path8.resolve)(options.homeDir ?? (0, import_node_os.homedir)());
3492
+ const root = (0, import_node_path10.resolve)(options.dir ?? process.cwd());
3493
+ const home = (0, import_node_path10.resolve)(options.homeDir ?? (0, import_node_os2.homedir)());
3265
3494
  const plans = /* @__PURE__ */ new Map();
3266
3495
  const targets = /* @__PURE__ */ new Map();
3267
3496
  const rememberTarget = (harness, target) => {
@@ -3275,48 +3504,48 @@ function installSkill(options = {}) {
3275
3504
  plans.set(target, { target, content: content2, boundary, managedMerge });
3276
3505
  };
3277
3506
  const planSkillTree = (targetDir2, boundary = root) => {
3278
- 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);
3507
+ 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);
3279
3508
  };
3280
3509
  let targetDir;
3281
3510
  if (options.global) {
3282
- const claudeRoot = (0, import_node_path8.join)(home, ".claude", "skills");
3283
- const codexRoot = (0, import_node_path8.resolve)(options.codexHomeDir ?? process.env.CODEX_HOME ?? (0, import_node_path8.join)(home, ".codex"), "skills");
3511
+ const claudeRoot = (0, import_node_path10.join)(home, ".claude", "skills");
3512
+ const codexRoot = (0, import_node_path10.resolve)(options.codexHomeDir ?? process.env.CODEX_HOME ?? (0, import_node_path10.join)(home, ".codex"), "skills");
3284
3513
  targetDir = harnesses[0] === "codex" ? codexRoot : claudeRoot;
3285
3514
  for (const harness of harnesses) {
3286
3515
  const skillRoot = harness === "claude" ? claudeRoot : codexRoot;
3287
- planSkillTree(skillRoot, harness === "claude" ? home : (0, import_node_path8.dirname)((0, import_node_path8.dirname)(codexRoot)));
3516
+ planSkillTree(skillRoot, harness === "claude" ? home : (0, import_node_path10.dirname)((0, import_node_path10.dirname)(codexRoot)));
3288
3517
  rememberTarget(harness, skillRoot);
3289
3518
  }
3290
3519
  } else {
3291
- const sharedRoot = (0, import_node_path8.join)(root, ".agents", "skills");
3520
+ const sharedRoot = (0, import_node_path10.join)(root, ".agents", "skills");
3292
3521
  planSkillTree(sharedRoot);
3293
- const claudeRoot = (0, import_node_path8.join)(root, ".claude", "skills");
3522
+ const claudeRoot = (0, import_node_path10.join)(root, ".claude", "skills");
3294
3523
  targetDir = harnesses.includes("claude") ? claudeRoot : sharedRoot;
3295
3524
  for (const harness of harnesses) rememberTarget(harness, sharedRoot);
3296
3525
  if (harnesses.includes("claude")) {
3297
3526
  for (const skill of skillNames(files)) {
3298
- const canonical = (0, import_node_fs10.readFileSync)((0, import_node_path8.join)(sourceDir, skill, "SKILL.md"), "utf8");
3299
- plan((0, import_node_path8.join)(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical));
3527
+ const canonical = (0, import_node_fs12.readFileSync)((0, import_node_path10.join)(sourceDir, skill, "SKILL.md"), "utf8");
3528
+ plan((0, import_node_path10.join)(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical));
3300
3529
  }
3301
3530
  rememberTarget("claude", claudeRoot);
3302
3531
  }
3303
3532
  if (harnesses.includes("cursor")) {
3304
- const cursorRule = (0, import_node_path8.join)(root, ".cursor", "rules", "odla.mdc");
3533
+ const cursorRule = (0, import_node_path10.join)(root, ".cursor", "rules", "odla.mdc");
3305
3534
  plan(cursorRule, CURSOR_RULE);
3306
3535
  rememberTarget("cursor", cursorRule);
3307
3536
  }
3308
3537
  if (harnesses.includes("agents")) {
3309
- const agentsFile = (0, import_node_path8.join)(root, "AGENTS.md");
3538
+ const agentsFile = (0, import_node_path10.join)(root, "AGENTS.md");
3310
3539
  plan(agentsFile, managedFileContent(agentsFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
3311
3540
  rememberTarget("agents", agentsFile);
3312
3541
  }
3313
3542
  if (harnesses.includes("copilot")) {
3314
- const copilotFile = (0, import_node_path8.join)(root, ".github", "copilot-instructions.md");
3543
+ const copilotFile = (0, import_node_path10.join)(root, ".github", "copilot-instructions.md");
3315
3544
  plan(copilotFile, managedFileContent(copilotFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
3316
3545
  rememberTarget("copilot", copilotFile);
3317
3546
  }
3318
3547
  if (harnesses.includes("gemini")) {
3319
- const geminiFile = (0, import_node_path8.join)(root, "GEMINI.md");
3548
+ const geminiFile = (0, import_node_path10.join)(root, "GEMINI.md");
3320
3549
  plan(geminiFile, managedFileContent(geminiFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
3321
3550
  rememberTarget("gemini", geminiFile);
3322
3551
  }
@@ -3330,11 +3559,11 @@ function installSkill(options = {}) {
3330
3559
  conflicts.push(`${file.target} (redirected by symbolic link ${symlink})`);
3331
3560
  continue;
3332
3561
  }
3333
- if (!(0, import_node_fs10.existsSync)(file.target)) {
3562
+ if (!(0, import_node_fs12.existsSync)(file.target)) {
3334
3563
  writtenPaths.add(file.target);
3335
3564
  continue;
3336
3565
  }
3337
- const current = (0, import_node_fs10.readFileSync)(file.target, "utf8");
3566
+ const current = (0, import_node_fs12.readFileSync)(file.target, "utf8");
3338
3567
  if (current === file.content) {
3339
3568
  unchangedPaths.add(file.target);
3340
3569
  } else if (file.managedMerge || options.force) {
@@ -3351,9 +3580,9 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
3351
3580
  );
3352
3581
  }
3353
3582
  for (const file of plans.values()) {
3354
- if (!(0, import_node_fs10.existsSync)(file.target) || (0, import_node_fs10.readFileSync)(file.target, "utf8") !== file.content) {
3355
- (0, import_node_fs10.mkdirSync)((0, import_node_path8.dirname)(file.target), { recursive: true });
3356
- (0, import_node_fs10.writeFileSync)(file.target, file.content);
3583
+ if (!(0, import_node_fs12.existsSync)(file.target) || (0, import_node_fs12.readFileSync)(file.target, "utf8") !== file.content) {
3584
+ (0, import_node_fs12.mkdirSync)((0, import_node_path10.dirname)(file.target), { recursive: true });
3585
+ (0, import_node_fs12.writeFileSync)(file.target, file.content);
3357
3586
  }
3358
3587
  }
3359
3588
  const skills = skillNames(files);
@@ -3372,7 +3601,7 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
3372
3601
  };
3373
3602
  }
3374
3603
  function pathsUnder(root, paths) {
3375
- 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();
3604
+ 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();
3376
3605
  }
3377
3606
  function normalizeHarnesses(values, global) {
3378
3607
  const requested = values?.length ? values : ["claude"];
@@ -3394,9 +3623,9 @@ function normalizeHarnesses(values, global) {
3394
3623
  function managedFileContent(path, block, force, boundary) {
3395
3624
  const symlink = symlinkedComponent(boundary, path);
3396
3625
  if (symlink) throw new Error(`refusing to manage ${path}: symbolic link component ${symlink}`);
3397
- if (!(0, import_node_fs10.existsSync)(path)) return `${block}
3626
+ if (!(0, import_node_fs12.existsSync)(path)) return `${block}
3398
3627
  `;
3399
- const current = (0, import_node_fs10.readFileSync)(path, "utf8");
3628
+ const current = (0, import_node_fs12.readFileSync)(path, "utf8");
3400
3629
  const start = "<!-- odla-ai agent setup:start -->";
3401
3630
  const end = "<!-- odla-ai agent setup:end -->";
3402
3631
  const startAt = current.indexOf(start);
@@ -3417,15 +3646,15 @@ function managedFileContent(path, block, force, boundary) {
3417
3646
  return `${current.slice(0, startAt)}${block}${current.slice(afterEnd)}`;
3418
3647
  }
3419
3648
  function symlinkedComponent(boundary, target) {
3420
- const rel = (0, import_node_path8.relative)(boundary, target);
3421
- if (rel === ".." || rel.startsWith(`..${import_node_path8.sep}`) || (0, import_node_path8.isAbsolute)(rel)) {
3649
+ const rel = (0, import_node_path10.relative)(boundary, target);
3650
+ if (rel === ".." || rel.startsWith(`..${import_node_path10.sep}`) || (0, import_node_path10.isAbsolute)(rel)) {
3422
3651
  throw new Error(`agent setup target escapes its install root: ${target}`);
3423
3652
  }
3424
3653
  let current = boundary;
3425
- for (const part of rel.split(import_node_path8.sep).filter(Boolean)) {
3426
- current = (0, import_node_path8.join)(current, part);
3654
+ for (const part of rel.split(import_node_path10.sep).filter(Boolean)) {
3655
+ current = (0, import_node_path10.join)(current, part);
3427
3656
  try {
3428
- if ((0, import_node_fs10.lstatSync)(current).isSymbolicLink()) return current;
3657
+ if ((0, import_node_fs12.lstatSync)(current).isSymbolicLink()) return current;
3429
3658
  } catch (error) {
3430
3659
  if (error.code !== "ENOENT") throw error;
3431
3660
  }
@@ -3436,13 +3665,13 @@ function skillNames(files) {
3436
3665
  return [...new Set(files.filter((file) => /(^|[\\/])SKILL\.md$/.test(file)).map((file) => file.split(/[\\/]/)[0]))].sort();
3437
3666
  }
3438
3667
  function listFiles(dir) {
3439
- if (!(0, import_node_fs10.existsSync)(dir)) return [];
3668
+ if (!(0, import_node_fs12.existsSync)(dir)) return [];
3440
3669
  const results = [];
3441
3670
  const walk = (current) => {
3442
- for (const entry of (0, import_node_fs10.readdirSync)(current, { withFileTypes: true })) {
3443
- const path = (0, import_node_path8.join)(current, entry.name);
3671
+ for (const entry of (0, import_node_fs12.readdirSync)(current, { withFileTypes: true })) {
3672
+ const path = (0, import_node_path10.join)(current, entry.name);
3444
3673
  if (entry.isDirectory()) walk(path);
3445
- else results.push((0, import_node_path8.relative)(dir, path));
3674
+ else results.push((0, import_node_path10.relative)(dir, path));
3446
3675
  }
3447
3676
  };
3448
3677
  walk(dir);
@@ -3589,13 +3818,16 @@ async function safeText3(res) {
3589
3818
 
3590
3819
  // src/cli-project.ts
3591
3820
  var SKILL_OPTS = ["dir", "global", "force", "agent", "harness"];
3592
- function install(parsed, positionals) {
3821
+ function install(parsed, positionals, deps) {
3593
3822
  assertArgs(parsed, SKILL_OPTS, positionals);
3594
3823
  installSkill({
3595
3824
  dir: stringOpt(parsed.options.dir),
3596
3825
  global: parsed.options.global === true,
3597
3826
  harnesses: harnessList(parsed),
3598
- force: parsed.options.force === true
3827
+ force: parsed.options.force === true,
3828
+ homeDir: deps.homeDir,
3829
+ codexHomeDir: deps.codexHomeDir,
3830
+ stdout: deps.stdout
3599
3831
  });
3600
3832
  }
3601
3833
  async function secretsCommand(parsed, deps) {
@@ -3611,6 +3843,7 @@ async function secretsCommand(parsed, deps) {
3611
3843
  token: stringOpt(parsed.options.token),
3612
3844
  email: stringOpt(parsed.options.email),
3613
3845
  yes: parsed.options.yes === true,
3846
+ readStdin: deps.readStdin,
3614
3847
  fetch: deps.fetch,
3615
3848
  stdout: deps.stdout
3616
3849
  };
@@ -3669,8 +3902,8 @@ async function projectCommand(command, parsed, deps) {
3669
3902
  return true;
3670
3903
  }
3671
3904
  if (command === "setup") {
3672
- install(parsed, 1);
3673
- console.log(
3905
+ install(parsed, 1, deps);
3906
+ (deps.stdout ?? console).log(
3674
3907
  "\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."
3675
3908
  );
3676
3909
  return true;
@@ -3678,7 +3911,7 @@ async function projectCommand(command, parsed, deps) {
3678
3911
  if (command === "skill") {
3679
3912
  const sub = parsed.positionals[1];
3680
3913
  if (sub !== "install") throw new Error(`unknown skill subcommand "${sub ?? ""}". Try "odla-ai skill install".`);
3681
- install(parsed, 2);
3914
+ install(parsed, 2, deps);
3682
3915
  return true;
3683
3916
  }
3684
3917
  if (command === "secrets") {
@@ -3689,9 +3922,9 @@ async function projectCommand(command, parsed, deps) {
3689
3922
  }
3690
3923
 
3691
3924
  // src/code-connect.ts
3692
- var import_node_fs12 = require("fs");
3693
- var import_node_os3 = require("os");
3694
- var import_node_path10 = require("path");
3925
+ var import_node_fs14 = require("fs");
3926
+ var import_node_os4 = require("os");
3927
+ var import_node_path12 = require("path");
3695
3928
 
3696
3929
  // ../harness/dist/chunk-QTUEF2HZ.js
3697
3930
  var HARNESS_PROTOCOL_VERSION = 1;
@@ -6576,9 +6809,9 @@ var CodePiRuntimeEngine = class {
6576
6809
  };
6577
6810
 
6578
6811
  // src/help.ts
6579
- var import_node_fs11 = require("fs");
6812
+ var import_node_fs13 = require("fs");
6580
6813
  function cliVersion() {
6581
- const pkg = JSON.parse((0, import_node_fs11.readFileSync)(new URL("../package.json", importMetaUrl), "utf8"));
6814
+ const pkg = JSON.parse((0, import_node_fs13.readFileSync)(new URL("../package.json", importMetaUrl), "utf8"));
6582
6815
  return pkg.version ?? "unknown";
6583
6816
  }
6584
6817
  function printHelp(output = console) {
@@ -6666,15 +6899,16 @@ Usage:
6666
6899
  odla-ai runbook rm <slug> [--app <id>]
6667
6900
  odla-ai capabilities [--json]
6668
6901
  odla-ai code connect [--env dev|prod] [--email <odla-account>] [--engine auto|container|podman|docker] [--slots <1-64>] [--once]
6669
- odla-ai admin ai show [--platform https://odla.ai] [--email <odla-account>] [--json]
6670
- odla-ai admin ai models [--provider <id>] [--json]
6671
- odla-ai admin ai set <purpose> [--provider <id>] [--model <id>] [--enabled|--no-enabled]
6672
- odla-ai admin ai set security --discovery-provider <id> --discovery-model <id>
6673
- --validation-provider <id> --validation-model <id> [--enabled|--no-enabled]
6674
- odla-ai admin ai credentials [--json]
6675
- odla-ai admin ai credential set <provider> (--from-env <NAME>|--stdin)
6676
- odla-ai admin ai usage [--app-id <id>] [--env <env>] [--run-id <id>] [--limit <1-500>] [--json]
6677
- odla-ai admin ai audit [--limit <1-200>] [--json]
6902
+ odla-ai admin ai show [--context <name>] [--platform https://odla.ai] [--email <odla-account>] [--json]
6903
+ odla-ai admin ai models [--context <name>] [--provider <id>] [--json]
6904
+ odla-ai admin ai set <purpose> [--context <name>] [--provider <id>] [--model <id>] [--enabled|--no-enabled]
6905
+ [--max-input-bytes <n>] [--max-output-tokens <n>] [--max-calls-per-run <n>] [--json]
6906
+ odla-ai admin ai set security [--context <name>] --discovery-provider <id> --discovery-model <id>
6907
+ --validation-provider <id> --validation-model <id> [--enabled|--no-enabled] [--json]
6908
+ odla-ai admin ai credentials [--context <name>] [--json]
6909
+ odla-ai admin ai credential set <provider> [--context <name>] (--from-env <NAME>|--stdin)
6910
+ odla-ai admin ai usage [--context <name>] [--app-id <id>] [--env <env>] [--run-id <id>] [--limit <1-500>] [--json]
6911
+ odla-ai admin ai audit [--context <name>] [--limit <1-200>] [--json]
6678
6912
  odla-ai security github connect [--repo owner/name] [--env dev] [--email <odla-account>] [--no-open]
6679
6913
  odla-ai security github disconnect --source <id> [--env dev] [--yes]
6680
6914
  odla-ai security plan [--env dev] [--json]
@@ -7101,8 +7335,8 @@ function digestText(value) {
7101
7335
  var import_node_child_process6 = require("child_process");
7102
7336
  var import_node_crypto2 = require("crypto");
7103
7337
  var import_promises10 = require("fs/promises");
7104
- var import_node_os2 = require("os");
7105
- var import_node_path9 = require("path");
7338
+ var import_node_os3 = require("os");
7339
+ var import_node_path11 = require("path");
7106
7340
  var import_node_url3 = require("url");
7107
7341
 
7108
7342
  // src/code-runtime-config.ts
@@ -7188,10 +7422,10 @@ async function embeddedPiImageName() {
7188
7422
  return `odla-ai/pi-agent:embedded-sha256-${(0, import_node_crypto2.createHash)("sha256").update(bundle).digest("hex")}`;
7189
7423
  }
7190
7424
  async function buildEmbeddedPiImage(engine, image, run) {
7191
- const context = await (0, import_promises10.mkdtemp)((0, import_node_path9.join)((0, import_node_os2.tmpdir)(), "odla-code-pi-"));
7425
+ const context = await (0, import_promises10.mkdtemp)((0, import_node_path11.join)((0, import_node_os3.tmpdir)(), "odla-code-pi-"));
7192
7426
  try {
7193
- await (0, import_promises10.copyFile)(embeddedPiAssetPath(), (0, import_node_path9.join)(context, "pi-agent.js"));
7194
- await (0, import_promises10.writeFile)((0, import_node_path9.join)(context, "Dockerfile"), [
7427
+ await (0, import_promises10.copyFile)(embeddedPiAssetPath(), (0, import_node_path11.join)(context, "pi-agent.js"));
7428
+ await (0, import_promises10.writeFile)((0, import_node_path11.join)(context, "Dockerfile"), [
7195
7429
  `FROM ${CODE_NODE_IMAGE}`,
7196
7430
  "COPY pi-agent.js /opt/odla/pi-agent.js",
7197
7431
  "WORKDIR /workspace",
@@ -7207,8 +7441,8 @@ async function buildEmbeddedPiImage(engine, image, run) {
7207
7441
  // src/code-connect.ts
7208
7442
  async function codeConnect(options) {
7209
7443
  const cwd = options.cwd ?? process.cwd();
7210
- const configPath = (0, import_node_path10.resolve)(cwd, options.configPath);
7211
- const cfg = (0, import_node_fs12.existsSync)(configPath) ? await loadProjectConfig(configPath) : null;
7444
+ const configPath = (0, import_node_path12.resolve)(cwd, options.configPath);
7445
+ const cfg = (0, import_node_fs14.existsSync)(configPath) ? await loadProjectConfig(configPath) : null;
7212
7446
  const requestedAppId = options.appId?.trim();
7213
7447
  if (requestedAppId && !/^[a-z0-9][a-z0-9-]{1,62}$/.test(requestedAppId)) {
7214
7448
  throw new Error("--app-id must be a valid odla app id");
@@ -7242,7 +7476,7 @@ async function codeConnect(options) {
7242
7476
  );
7243
7477
  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");
7244
7478
  const hostPlatform = process.platform === "darwin" ? "macos" : "linux";
7245
- const hostName = (options.name ?? (0, import_node_os3.hostname)()).trim();
7479
+ const hostName = (options.name ?? (0, import_node_os4.hostname)()).trim();
7246
7480
  if (!hostName || hostName.length > 120) throw new Error("--name must contain 1 to 120 characters");
7247
7481
  const repository = await inferGitHubRepository(cwd, options.readGitOrigin);
7248
7482
  const localSource = await (options.prepareLocalSource ?? prepareCodeLocalSource)(
@@ -7279,8 +7513,8 @@ async function codeConnect(options) {
7279
7513
  platform: hostPlatform,
7280
7514
  arch: process.arch,
7281
7515
  engines: [engine],
7282
- cpuCount: (0, import_node_os3.cpus)().length,
7283
- memoryBytes: (0, import_node_os3.totalmem)(),
7516
+ cpuCount: (0, import_node_os4.cpus)().length,
7517
+ memoryBytes: (0, import_node_os4.totalmem)(),
7284
7518
  source: descriptor2,
7285
7519
  images: {
7286
7520
  ready: true,
@@ -7426,246 +7660,19 @@ async function codeCommand(parsed, dependencies) {
7426
7660
  }
7427
7661
 
7428
7662
  // src/operator-credentials.ts
7429
- var import_node_process7 = __toESM(require("process"), 1);
7663
+ var import_node_process9 = __toESM(require("process"), 1);
7430
7664
  function developerTokenStatus(context, parsed, now = Date.now()) {
7431
7665
  const cached = readJsonFile(context.cfg.local.tokenFile);
7432
7666
  const cacheStatus = !cached?.token ? "missing" : cached.platform !== context.platform.value ? "other-platform" : (cached.expiresAt ?? 0) <= now + 6e4 ? "expired" : "valid";
7433
- const source = clean(
7667
+ const source = clean3(
7434
7668
  stringOpt(parsed.options.token)
7435
- ) ? "flag" : clean(import_node_process7.default.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
7669
+ ) ? "flag" : clean3(import_node_process9.default.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
7436
7670
  return {
7437
7671
  source,
7438
7672
  cacheFile: context.cfg.local.tokenFile,
7439
7673
  cacheStatus
7440
7674
  };
7441
7675
  }
7442
- function clean(value) {
7443
- const normalized = value?.trim();
7444
- return normalized || void 0;
7445
- }
7446
-
7447
- // src/operator-context.ts
7448
- var import_node_fs14 = require("fs");
7449
- var import_node_path12 = require("path");
7450
- var import_node_process9 = __toESM(require("process"), 1);
7451
-
7452
- // src/operator-profiles.ts
7453
- var import_node_fs13 = require("fs");
7454
- var import_node_os4 = require("os");
7455
- var import_node_path11 = require("path");
7456
- var import_node_process8 = __toESM(require("process"), 1);
7457
- function operatorProfileFile() {
7458
- return (0, import_node_path11.resolve)(
7459
- clean2(import_node_process8.default.env.ODLA_CONTEXT_FILE) ?? (0, import_node_path11.join)((0, import_node_os4.homedir)(), ".odla", "contexts.json")
7460
- );
7461
- }
7462
- function resolveOperatorProfile(parsed) {
7463
- const fromFlag = clean2(stringOpt(parsed.options.context));
7464
- const fromEnvironment = clean2(import_node_process8.default.env.ODLA_CONTEXT);
7465
- const name = fromFlag ?? fromEnvironment ?? null;
7466
- const file = operatorProfileFile();
7467
- if (!name) {
7468
- return { name: null, source: "unresolved", file, value: null };
7469
- }
7470
- assertOperatorName(name, "context");
7471
- const profiles = readOperatorProfiles(file);
7472
- const value = Object.hasOwn(profiles, name) ? profiles[name] : void 0;
7473
- if (!value) {
7474
- throw new Error(
7475
- `operator context "${name}" not found in ${file}; run "odla-ai context list" or save it first`
7476
- );
7477
- }
7478
- return {
7479
- name,
7480
- source: fromFlag ? "flag" : "environment",
7481
- file,
7482
- value
7483
- };
7484
- }
7485
- function listOperatorProfiles(file = operatorProfileFile()) {
7486
- return Object.entries(readOperatorProfiles(file)).sort(([a], [b]) => a.localeCompare(b)).map(([name, profile]) => ({ name, ...profile }));
7487
- }
7488
- function saveOperatorProfile(name, profile, file = operatorProfileFile()) {
7489
- assertOperatorName(name, "context");
7490
- const normalized = validateProfile(profile, `operator context "${name}"`);
7491
- const profiles = readOperatorProfiles(file);
7492
- profiles[name] = normalized;
7493
- writePrivateJson(file, { schemaVersion: 1, profiles });
7494
- }
7495
- function removeOperatorProfile(name, file = operatorProfileFile()) {
7496
- assertOperatorName(name, "context");
7497
- const profiles = readOperatorProfiles(file);
7498
- if (!Object.hasOwn(profiles, name)) return false;
7499
- delete profiles[name];
7500
- writePrivateJson(file, { schemaVersion: 1, profiles });
7501
- return true;
7502
- }
7503
- function operatorCredentialFiles(selection) {
7504
- 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");
7505
- return {
7506
- developer: (0, import_node_path11.join)(base, "dev-token.json"),
7507
- scoped: (0, import_node_path11.join)(base, "admin-token.local.json")
7508
- };
7509
- }
7510
- function assertOperatorName(value, label) {
7511
- if (!/^[a-z0-9][a-z0-9-]*$/.test(value)) {
7512
- throw new Error(
7513
- `${label} must contain lowercase letters, numbers, and hyphens`
7514
- );
7515
- }
7516
- }
7517
- function readOperatorProfiles(file) {
7518
- if (!(0, import_node_fs13.existsSync)(file)) return emptyProfiles();
7519
- let raw;
7520
- try {
7521
- raw = JSON.parse((0, import_node_fs13.readFileSync)(file, "utf8"));
7522
- } catch {
7523
- throw new Error(`operator context file ${file} is not valid JSON`);
7524
- }
7525
- if (!raw || typeof raw !== "object" || raw.schemaVersion !== 1 || !raw.profiles || typeof raw.profiles !== "object" || Array.isArray(raw.profiles)) {
7526
- throw new Error(`operator context file ${file} has an invalid shape`);
7527
- }
7528
- const unknown = Object.keys(raw).filter(
7529
- (key) => !["schemaVersion", "profiles"].includes(key)
7530
- );
7531
- if (unknown.length > 0) {
7532
- throw new Error(
7533
- `operator context file ${file} contains unsupported field(s): ${unknown.sort().join(", ")}`
7534
- );
7535
- }
7536
- const profiles = emptyProfiles();
7537
- for (const [name, value] of Object.entries(
7538
- raw.profiles
7539
- )) {
7540
- assertOperatorName(name, "context");
7541
- profiles[name] = validateProfile(value, `operator context "${name}"`);
7542
- }
7543
- return profiles;
7544
- }
7545
- function emptyProfiles() {
7546
- return /* @__PURE__ */ Object.create(null);
7547
- }
7548
- function validateProfile(value, label) {
7549
- if (!value || typeof value !== "object" || Array.isArray(value)) {
7550
- throw new Error(`${label} has an invalid shape`);
7551
- }
7552
- const unknown = Object.keys(value).filter(
7553
- (key) => !["platform", "app", "environment"].includes(key)
7554
- );
7555
- if (unknown.length > 0) {
7556
- throw new Error(
7557
- `${label} contains unsupported field(s): ${unknown.sort().join(", ")}; contexts store scope metadata only, never credentials`
7558
- );
7559
- }
7560
- const candidate = value;
7561
- if (typeof candidate.platform !== "string") {
7562
- throw new Error(`${label} needs an absolute platform URL`);
7563
- }
7564
- const platform = platformAudience(candidate.platform);
7565
- const app = clean2(candidate.app);
7566
- const environment2 = clean2(candidate.environment);
7567
- if (app) assertOperatorName(app, "app");
7568
- if (environment2) assertOperatorName(environment2, "environment");
7569
- return {
7570
- platform,
7571
- ...app ? { app } : {},
7572
- ...environment2 ? { environment: environment2 } : {}
7573
- };
7574
- }
7575
- function clean2(value) {
7576
- const normalized = value?.trim();
7577
- return normalized || void 0;
7578
- }
7579
-
7580
- // src/operator-context.ts
7581
- var DEFAULT_PLATFORM2 = "https://odla.ai";
7582
- async function resolveOperatorContext(parsed, options = {}) {
7583
- const profile = resolveOperatorProfile(parsed);
7584
- const configArgument = stringOpt(parsed.options.config) ?? "odla.config.mjs";
7585
- const configPath = (0, import_node_path12.resolve)(configArgument);
7586
- const explicitConfig = parsed.options.config !== void 0;
7587
- const hasConfig = (0, import_node_fs14.existsSync)(configPath);
7588
- if (!hasConfig && (!options.allowMissingConfig || explicitConfig)) {
7589
- await loadProjectConfig(configArgument);
7590
- }
7591
- const loaded = hasConfig ? await loadProjectConfig(configArgument) : void 0;
7592
- const platformFlag = clean3(stringOpt(parsed.options.platform));
7593
- const platformEnvironment = clean3(import_node_process9.default.env.ODLA_PLATFORM_URL);
7594
- const platformValue = platformAudience(
7595
- platformFlag ?? platformEnvironment ?? profile.value?.platform ?? loaded?.platformUrl ?? DEFAULT_PLATFORM2
7596
- );
7597
- const platformSource = platformFlag ? "flag" : platformEnvironment ? "environment" : profile.value ? "profile" : loaded ? "config" : "default";
7598
- const appFlag = clean3(stringOpt(parsed.options.app));
7599
- const appEnvironment = clean3(import_node_process9.default.env.ODLA_APP_ID);
7600
- const appValue = appFlag ?? appEnvironment ?? profile.value?.app ?? loaded?.app.id ?? null;
7601
- const appSource = appFlag ? "flag" : appEnvironment ? "environment" : profile.value?.app ? "profile" : loaded ? "config" : "unresolved";
7602
- if (appValue) assertOperatorName(appValue, "app");
7603
- if (options.requireApp && !appValue) {
7604
- throw new Error(
7605
- "app context is unresolved; pass --app <id>, set ODLA_APP_ID, select --context <name>, or run inside a project with odla.config.mjs"
7606
- );
7607
- }
7608
- const envFlag = clean3(stringOpt(parsed.options.env));
7609
- const envEnvironment = clean3(import_node_process9.default.env.ODLA_ENV);
7610
- const environmentValue = envFlag ?? envEnvironment ?? profile.value?.environment ?? options.defaultEnvironment ?? null;
7611
- const environmentSource = envFlag ? "flag" : envEnvironment ? "environment" : profile.value?.environment ? "profile" : options.defaultEnvironment ? "default" : "unresolved";
7612
- if (environmentValue) {
7613
- assertOperatorName(environmentValue, "environment");
7614
- }
7615
- const rootDir = loaded?.rootDir ?? import_node_process9.default.cwd();
7616
- const profileCredentials = operatorCredentialFiles(profile);
7617
- 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;
7618
- 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;
7619
- const cfg = loaded ? {
7620
- ...loaded,
7621
- platformUrl: platformValue,
7622
- app: appValue ? {
7623
- id: appValue,
7624
- name: appValue === loaded.app.id ? loaded.app.name : appValue
7625
- } : loaded.app,
7626
- local: { ...loaded.local, tokenFile }
7627
- } : {
7628
- configPath,
7629
- rootDir,
7630
- platformUrl: platformValue,
7631
- dbEndpoint: platformValue,
7632
- app: {
7633
- id: appValue ?? "operator",
7634
- name: appValue ?? "Operator"
7635
- },
7636
- envs: environmentValue ? [environmentValue] : [],
7637
- services: [],
7638
- local: {
7639
- tokenFile,
7640
- credentialsFile: (0, import_node_path12.join)(rootDir, ".odla", "credentials.local.json"),
7641
- devVarsFile: (0, import_node_path12.join)(rootDir, ".dev.vars"),
7642
- gitignore: true
7643
- }
7644
- };
7645
- return {
7646
- cfg,
7647
- profile: {
7648
- name: profile.name,
7649
- source: profile.source,
7650
- file: profile.file
7651
- },
7652
- config: {
7653
- path: configPath,
7654
- status: loaded ? "loaded" : "absent",
7655
- explicit: explicitConfig
7656
- },
7657
- platform: { value: platformValue, source: platformSource },
7658
- app: { value: appValue, source: appSource },
7659
- environment: {
7660
- value: environmentValue,
7661
- source: environmentSource
7662
- },
7663
- credentials: {
7664
- developerTokenFile: tokenFile,
7665
- scopedTokenFile
7666
- }
7667
- };
7668
- }
7669
7676
  function clean3(value) {
7670
7677
  const normalized = value?.trim();
7671
7678
  return normalized || void 0;
@@ -11286,7 +11293,7 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
11286
11293
  return;
11287
11294
  }
11288
11295
  if (command === "admin") {
11289
- await adminCommand(parsed);
11296
+ await adminCommand(parsed, runtime);
11290
11297
  return;
11291
11298
  }
11292
11299
  if (command === "agent") {