@odla-ai/cli 0.25.18 → 0.25.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.cjs CHANGED
@@ -959,71 +959,16 @@ 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");
1025
969
  var import_node_path4 = require("path");
1026
970
  var import_node_url = require("url");
971
+ var import_apps = require("@odla-ai/apps");
1027
972
 
1028
973
  // src/integration-validation.ts
1029
974
  function validateIntegrations(cfg, path, defaultServices) {
@@ -1111,6 +1056,7 @@ async function loadProjectConfig(configPath = "odla.config.mjs") {
1111
1056
  const dbEndpoint = trimSlash(process.env.ODLA_DB_ENDPOINT || raw.dbEndpoint || platformUrl);
1112
1057
  const envs = unique2(raw.envs?.length ? raw.envs : DEFAULT_ENVS);
1113
1058
  const services = unique2(raw.services?.length ? raw.services : DEFAULT_SERVICES);
1059
+ validateServices(services, resolved);
1114
1060
  validateCalendarConfig(raw, envs, services, resolved);
1115
1061
  const local = {
1116
1062
  tokenFile: (0, import_node_path4.resolve)(rootDir, raw.local?.tokenFile ?? ".odla/dev-token.json"),
@@ -1266,7 +1212,19 @@ function validateCalendarConfig(cfg, envs, services, path) {
1266
1212
  }
1267
1213
  }
1268
1214
  }
1269
- if (enabled && !services.includes("db")) throw new Error(`${path}: calendar service requires the db service`);
1215
+ }
1216
+ function validateServices(services, path) {
1217
+ for (const service of services) {
1218
+ const definition = (0, import_apps.appServiceDefinition)(service);
1219
+ if (!definition) {
1220
+ throw new Error(`${path}: unknown service "${service}" (known: ${(0, import_apps.appServiceIds)().join(", ")})`);
1221
+ }
1222
+ for (const dependency of definition.requires) {
1223
+ if (!services.includes(dependency)) {
1224
+ throw new Error(`${path}: ${service} service requires the ${dependency} service`);
1225
+ }
1226
+ }
1227
+ }
1270
1228
  }
1271
1229
  function assertOnly(value, allowed, label) {
1272
1230
  const extra = Object.keys(value).find((key) => !allowed.includes(key));
@@ -1304,8 +1262,293 @@ function unique2(values) {
1304
1262
  return [...new Set(values.filter(Boolean))];
1305
1263
  }
1306
1264
 
1265
+ // src/operator-profiles.ts
1266
+ var import_node_fs5 = require("fs");
1267
+ var import_node_os = require("os");
1268
+ var import_node_path5 = require("path");
1269
+ var import_node_process7 = __toESM(require("process"), 1);
1270
+ function operatorProfileFile() {
1271
+ return (0, import_node_path5.resolve)(
1272
+ clean(import_node_process7.default.env.ODLA_CONTEXT_FILE) ?? (0, import_node_path5.join)((0, import_node_os.homedir)(), ".odla", "contexts.json")
1273
+ );
1274
+ }
1275
+ function resolveOperatorProfile(parsed) {
1276
+ const fromFlag = clean(stringOpt(parsed.options.context));
1277
+ const fromEnvironment = clean(import_node_process7.default.env.ODLA_CONTEXT);
1278
+ const name = fromFlag ?? fromEnvironment ?? null;
1279
+ const file = operatorProfileFile();
1280
+ if (!name) {
1281
+ return { name: null, source: "unresolved", file, value: null };
1282
+ }
1283
+ assertOperatorName(name, "context");
1284
+ const profiles = readOperatorProfiles(file);
1285
+ const value = Object.hasOwn(profiles, name) ? profiles[name] : void 0;
1286
+ if (!value) {
1287
+ throw new Error(
1288
+ `operator context "${name}" not found in ${file}; run "odla-ai context list" or save it first`
1289
+ );
1290
+ }
1291
+ return {
1292
+ name,
1293
+ source: fromFlag ? "flag" : "environment",
1294
+ file,
1295
+ value
1296
+ };
1297
+ }
1298
+ function listOperatorProfiles(file = operatorProfileFile()) {
1299
+ return Object.entries(readOperatorProfiles(file)).sort(([a], [b]) => a.localeCompare(b)).map(([name, profile]) => ({ name, ...profile }));
1300
+ }
1301
+ function saveOperatorProfile(name, profile, file = operatorProfileFile()) {
1302
+ assertOperatorName(name, "context");
1303
+ const normalized = validateProfile(profile, `operator context "${name}"`);
1304
+ const profiles = readOperatorProfiles(file);
1305
+ profiles[name] = normalized;
1306
+ writePrivateJson(file, { schemaVersion: 1, profiles });
1307
+ }
1308
+ function removeOperatorProfile(name, file = operatorProfileFile()) {
1309
+ assertOperatorName(name, "context");
1310
+ const profiles = readOperatorProfiles(file);
1311
+ if (!Object.hasOwn(profiles, name)) return false;
1312
+ delete profiles[name];
1313
+ writePrivateJson(file, { schemaVersion: 1, profiles });
1314
+ return true;
1315
+ }
1316
+ function operatorCredentialFiles(selection) {
1317
+ 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");
1318
+ return {
1319
+ developer: (0, import_node_path5.join)(base, "dev-token.json"),
1320
+ scoped: (0, import_node_path5.join)(base, "admin-token.local.json")
1321
+ };
1322
+ }
1323
+ function assertOperatorName(value, label) {
1324
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(value)) {
1325
+ throw new Error(
1326
+ `${label} must contain lowercase letters, numbers, and hyphens`
1327
+ );
1328
+ }
1329
+ }
1330
+ function readOperatorProfiles(file) {
1331
+ if (!(0, import_node_fs5.existsSync)(file)) return emptyProfiles();
1332
+ let raw;
1333
+ try {
1334
+ raw = JSON.parse((0, import_node_fs5.readFileSync)(file, "utf8"));
1335
+ } catch {
1336
+ throw new Error(`operator context file ${file} is not valid JSON`);
1337
+ }
1338
+ if (!raw || typeof raw !== "object" || raw.schemaVersion !== 1 || !raw.profiles || typeof raw.profiles !== "object" || Array.isArray(raw.profiles)) {
1339
+ throw new Error(`operator context file ${file} has an invalid shape`);
1340
+ }
1341
+ const unknown = Object.keys(raw).filter(
1342
+ (key) => !["schemaVersion", "profiles"].includes(key)
1343
+ );
1344
+ if (unknown.length > 0) {
1345
+ throw new Error(
1346
+ `operator context file ${file} contains unsupported field(s): ${unknown.sort().join(", ")}`
1347
+ );
1348
+ }
1349
+ const profiles = emptyProfiles();
1350
+ for (const [name, value] of Object.entries(
1351
+ raw.profiles
1352
+ )) {
1353
+ assertOperatorName(name, "context");
1354
+ profiles[name] = validateProfile(value, `operator context "${name}"`);
1355
+ }
1356
+ return profiles;
1357
+ }
1358
+ function emptyProfiles() {
1359
+ return /* @__PURE__ */ Object.create(null);
1360
+ }
1361
+ function validateProfile(value, label) {
1362
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1363
+ throw new Error(`${label} has an invalid shape`);
1364
+ }
1365
+ const unknown = Object.keys(value).filter(
1366
+ (key) => !["platform", "app", "environment"].includes(key)
1367
+ );
1368
+ if (unknown.length > 0) {
1369
+ throw new Error(
1370
+ `${label} contains unsupported field(s): ${unknown.sort().join(", ")}; contexts store scope metadata only, never credentials`
1371
+ );
1372
+ }
1373
+ const candidate = value;
1374
+ if (typeof candidate.platform !== "string") {
1375
+ throw new Error(`${label} needs an absolute platform URL`);
1376
+ }
1377
+ const platform = platformAudience(candidate.platform);
1378
+ const app = clean(candidate.app);
1379
+ const environment2 = clean(candidate.environment);
1380
+ if (app) assertOperatorName(app, "app");
1381
+ if (environment2) assertOperatorName(environment2, "environment");
1382
+ return {
1383
+ platform,
1384
+ ...app ? { app } : {},
1385
+ ...environment2 ? { environment: environment2 } : {}
1386
+ };
1387
+ }
1388
+ function clean(value) {
1389
+ const normalized = value?.trim();
1390
+ return normalized || void 0;
1391
+ }
1392
+
1393
+ // src/operator-context.ts
1394
+ var DEFAULT_PLATFORM2 = "https://odla.ai";
1395
+ async function resolveOperatorContext(parsed, options = {}) {
1396
+ const profile = resolveOperatorProfile(parsed);
1397
+ const configArgument = stringOpt(parsed.options.config) ?? "odla.config.mjs";
1398
+ const configPath = (0, import_node_path6.resolve)(configArgument);
1399
+ const explicitConfig = parsed.options.config !== void 0;
1400
+ const hasConfig = (0, import_node_fs6.existsSync)(configPath);
1401
+ if (!hasConfig && (!options.allowMissingConfig || explicitConfig)) {
1402
+ await loadProjectConfig(configArgument);
1403
+ }
1404
+ const loaded = hasConfig ? await loadProjectConfig(configArgument) : void 0;
1405
+ const platformFlag = clean2(stringOpt(parsed.options.platform));
1406
+ const platformEnvironment = clean2(import_node_process8.default.env.ODLA_PLATFORM_URL);
1407
+ const platformValue = platformAudience(
1408
+ platformFlag ?? platformEnvironment ?? profile.value?.platform ?? loaded?.platformUrl ?? DEFAULT_PLATFORM2
1409
+ );
1410
+ const platformSource = platformFlag ? "flag" : platformEnvironment ? "environment" : profile.value ? "profile" : loaded ? "config" : "default";
1411
+ const appFlag = clean2(stringOpt(parsed.options.app));
1412
+ const appEnvironment = clean2(import_node_process8.default.env.ODLA_APP_ID);
1413
+ const appValue = appFlag ?? appEnvironment ?? profile.value?.app ?? loaded?.app.id ?? null;
1414
+ const appSource = appFlag ? "flag" : appEnvironment ? "environment" : profile.value?.app ? "profile" : loaded ? "config" : "unresolved";
1415
+ if (appValue) assertOperatorName(appValue, "app");
1416
+ if (options.requireApp && !appValue) {
1417
+ throw new Error(
1418
+ "app context is unresolved; pass --app <id>, set ODLA_APP_ID, select --context <name>, or run inside a project with odla.config.mjs"
1419
+ );
1420
+ }
1421
+ const envFlag = clean2(stringOpt(parsed.options.env));
1422
+ const envEnvironment = clean2(import_node_process8.default.env.ODLA_ENV);
1423
+ const environmentValue = envFlag ?? envEnvironment ?? profile.value?.environment ?? options.defaultEnvironment ?? null;
1424
+ const environmentSource = envFlag ? "flag" : envEnvironment ? "environment" : profile.value?.environment ? "profile" : options.defaultEnvironment ? "default" : "unresolved";
1425
+ if (environmentValue) {
1426
+ assertOperatorName(environmentValue, "environment");
1427
+ }
1428
+ const rootDir = loaded?.rootDir ?? import_node_process8.default.cwd();
1429
+ const profileCredentials = operatorCredentialFiles(profile);
1430
+ 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;
1431
+ 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;
1432
+ const cfg = loaded ? {
1433
+ ...loaded,
1434
+ platformUrl: platformValue,
1435
+ app: appValue ? {
1436
+ id: appValue,
1437
+ name: appValue === loaded.app.id ? loaded.app.name : appValue
1438
+ } : loaded.app,
1439
+ local: { ...loaded.local, tokenFile }
1440
+ } : {
1441
+ configPath,
1442
+ rootDir,
1443
+ platformUrl: platformValue,
1444
+ dbEndpoint: platformValue,
1445
+ app: {
1446
+ id: appValue ?? "operator",
1447
+ name: appValue ?? "Operator"
1448
+ },
1449
+ envs: environmentValue ? [environmentValue] : [],
1450
+ services: [],
1451
+ local: {
1452
+ tokenFile,
1453
+ credentialsFile: (0, import_node_path6.join)(rootDir, ".odla", "credentials.local.json"),
1454
+ devVarsFile: (0, import_node_path6.join)(rootDir, ".dev.vars"),
1455
+ gitignore: true
1456
+ }
1457
+ };
1458
+ return {
1459
+ cfg,
1460
+ profile: {
1461
+ name: profile.name,
1462
+ source: profile.source,
1463
+ file: profile.file
1464
+ },
1465
+ config: {
1466
+ path: configPath,
1467
+ status: loaded ? "loaded" : "absent",
1468
+ explicit: explicitConfig
1469
+ },
1470
+ platform: { value: platformValue, source: platformSource },
1471
+ app: { value: appValue, source: appSource },
1472
+ environment: {
1473
+ value: environmentValue,
1474
+ source: environmentSource
1475
+ },
1476
+ credentials: {
1477
+ developerTokenFile: tokenFile,
1478
+ scopedTokenFile
1479
+ }
1480
+ };
1481
+ }
1482
+ function clean2(value) {
1483
+ const normalized = value?.trim();
1484
+ return normalized || void 0;
1485
+ }
1486
+
1487
+ // src/admin-command.ts
1488
+ var CONTEXT_OPTIONS = ["platform", "config", "context", "token", "open", "email"];
1489
+ var JSON_OPTIONS = [...CONTEXT_OPTIONS, "json"];
1490
+ var SET_OPTIONS = [
1491
+ ...JSON_OPTIONS,
1492
+ "provider",
1493
+ "model",
1494
+ "enabled",
1495
+ "max-input-bytes",
1496
+ "max-output-tokens",
1497
+ "max-calls-per-run",
1498
+ "discovery-provider",
1499
+ "discovery-model",
1500
+ "validation-provider",
1501
+ "validation-model"
1502
+ ];
1503
+ async function adminCommand(parsed, deps = {}) {
1504
+ const area = parsed.positionals[1];
1505
+ const action = parsed.positionals[2];
1506
+ const credentialSet = action === "credential" && parsed.positionals[3] === "set";
1507
+ const credentials = action === "credentials";
1508
+ const models = action === "models";
1509
+ const usage = action === "usage";
1510
+ const audit = action === "audit";
1511
+ if (area !== "ai" || action !== "show" && action !== "set" && !credentialSet && !credentials && !models && !usage && !audit) {
1512
+ throw new Error('unknown admin command. Try "odla-ai admin ai show".');
1513
+ }
1514
+ 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;
1515
+ assertArgs(parsed, allowed, credentialSet ? 5 : action === "set" ? 4 : 3);
1516
+ const context = await resolveOperatorContext(parsed, { allowMissingConfig: true });
1517
+ await adminAi({
1518
+ action: credentialSet ? "credential-set" : credentials ? "credentials" : models ? "models" : usage ? "usage" : audit ? "audit" : action,
1519
+ purpose: action === "set" ? parsed.positionals[3] : void 0,
1520
+ provider: stringOpt(parsed.options.provider),
1521
+ model: stringOpt(parsed.options.model),
1522
+ enabled: boolOpt(parsed.options.enabled),
1523
+ maxInputBytes: numberOpt(parsed.options["max-input-bytes"], "--max-input-bytes"),
1524
+ maxOutputTokens: numberOpt(parsed.options["max-output-tokens"], "--max-output-tokens"),
1525
+ maxCallsPerRun: numberOpt(parsed.options["max-calls-per-run"], "--max-calls-per-run"),
1526
+ platform: context.platform.value,
1527
+ token: stringOpt(parsed.options.token),
1528
+ tokenFile: context.credentials.scopedTokenFile,
1529
+ email: stringOpt(parsed.options.email),
1530
+ json: parsed.options.json === true,
1531
+ open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
1532
+ credentialProvider: credentialSet ? parsed.positionals[4] : void 0,
1533
+ fromEnv: stringOpt(parsed.options["from-env"]),
1534
+ stdin: parsed.options.stdin === true,
1535
+ readStdin: deps.readStdin,
1536
+ discoveryProvider: stringOpt(parsed.options["discovery-provider"]),
1537
+ discoveryModel: stringOpt(parsed.options["discovery-model"]),
1538
+ validationProvider: stringOpt(parsed.options["validation-provider"]),
1539
+ validationModel: stringOpt(parsed.options["validation-model"]),
1540
+ appId: stringOpt(parsed.options["app-id"]),
1541
+ env: stringOpt(parsed.options.env),
1542
+ runId: stringOpt(parsed.options["run-id"]),
1543
+ limit: numberOpt(parsed.options.limit, "--limit"),
1544
+ fetch: deps.fetch,
1545
+ stdout: deps.stdout,
1546
+ openApprovalUrl: deps.openUrl
1547
+ });
1548
+ }
1549
+
1307
1550
  // src/tenant.ts
1308
- var import_apps = require("@odla-ai/apps");
1551
+ var import_apps2 = require("@odla-ai/apps");
1309
1552
  function resolveEnv(cfg, requested) {
1310
1553
  const env = requested ?? (cfg.envs.includes("dev") ? "dev" : cfg.envs[0]);
1311
1554
  if (!env || !cfg.envs.includes(env)) {
@@ -1315,7 +1558,7 @@ function resolveEnv(cfg, requested) {
1315
1558
  }
1316
1559
  function resolveTenant(cfg, requested) {
1317
1560
  const env = resolveEnv(cfg, requested);
1318
- return { env, tenant: (0, import_apps.tenantIdFor)(cfg.app.id, env) };
1561
+ return { env, tenant: (0, import_apps2.tenantIdFor)(cfg.app.id, env) };
1319
1562
  }
1320
1563
  function bothTenants(cfg) {
1321
1564
  for (const env of ["dev", "prod"]) {
@@ -1325,7 +1568,7 @@ function bothTenants(cfg) {
1325
1568
  );
1326
1569
  }
1327
1570
  }
1328
- return { sandbox: (0, import_apps.tenantIdFor)(cfg.app.id, "dev"), live: (0, import_apps.tenantIdFor)(cfg.app.id, "prod") };
1571
+ return { sandbox: (0, import_apps2.tenantIdFor)(cfg.app.id, "dev"), live: (0, import_apps2.tenantIdFor)(cfg.app.id, "prod") };
1329
1572
  }
1330
1573
 
1331
1574
  // src/agent-command.ts
@@ -1409,7 +1652,7 @@ function errorMessage(body) {
1409
1652
  }
1410
1653
 
1411
1654
  // src/app-export.ts
1412
- var import_node_fs5 = require("fs");
1655
+ var import_node_fs7 = require("fs");
1413
1656
  var import_node_stream = require("stream");
1414
1657
  var import_promises = require("stream/promises");
1415
1658
  async function appExport(options) {
@@ -1442,7 +1685,7 @@ async function appExport(options) {
1442
1685
  const download = await doFetch(`${base}/backups/${newest.id}/download`, { headers: auth });
1443
1686
  if (!download.ok || !download.body) throw new Error(`download failed (${download.status})`);
1444
1687
  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));
1688
+ await (0, import_promises.pipeline)(import_node_stream.Readable.fromWeb(download.body), (0, import_node_fs7.createWriteStream)(file));
1446
1689
  if (options.json) out.log(JSON.stringify({ file, backup: newest }, null, 2));
1447
1690
  else {
1448
1691
  out.log(`${tenant}: wrote ${file} (${newest.bytes} bytes, ${newest.kind} snapshot at tx ${newest.max_tx})`);
@@ -1452,7 +1695,7 @@ async function appExport(options) {
1452
1695
  }
1453
1696
 
1454
1697
  // src/app-import.ts
1455
- var import_node_fs6 = require("fs");
1698
+ var import_node_fs8 = require("fs");
1456
1699
  var import_import = require("@odla-ai/db/import");
1457
1700
  function chooseIdMode(options, rows) {
1458
1701
  const chosen = [options.idField && "field", options.key && "key", options.generateIds && "generate"].filter(Boolean);
@@ -1471,7 +1714,7 @@ async function appImport(options) {
1471
1714
  const say = options.json ? (line) => out.error(line) : (line) => out.log(line);
1472
1715
  const doFetch = options.fetch ?? fetch;
1473
1716
  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");
1717
+ const text = options.file === "-" ? (options.readStdin ?? (() => (0, import_node_fs8.readFileSync)(0, "utf8")))() : (0, import_node_fs8.readFileSync)(options.file, "utf8");
1475
1718
  const { format, sources } = (0, import_import.parseImport)(text, options.ns);
1476
1719
  if (format === "namespace-map" && options.ns) {
1477
1720
  throw new Error("--ns cannot be combined with a {namespace: rows} file \u2014 the file already names each namespace");
@@ -2438,13 +2681,13 @@ function printGroup(out, heading, items) {
2438
2681
 
2439
2682
  // src/doctor-checks.ts
2440
2683
  var import_node_child_process3 = require("child_process");
2441
- var import_node_fs8 = require("fs");
2442
- var import_node_path6 = require("path");
2684
+ var import_node_fs10 = require("fs");
2685
+ var import_node_path8 = require("path");
2443
2686
 
2444
2687
  // src/wrangler.ts
2445
2688
  var import_node_child_process2 = require("child_process");
2446
- var import_node_fs7 = require("fs");
2447
- var import_node_path5 = require("path");
2689
+ var import_node_fs9 = require("fs");
2690
+ var import_node_path7 = require("path");
2448
2691
  var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
2449
2692
  const child = (0, import_node_child_process2.spawn)(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
2450
2693
  let stdout = "";
@@ -2458,15 +2701,15 @@ var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) =>
2458
2701
  var WRANGLER_CONFIG_FILES = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"];
2459
2702
  function findWranglerConfig(rootDir) {
2460
2703
  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;
2704
+ const path = (0, import_node_path7.join)(rootDir, name);
2705
+ if ((0, import_node_fs9.existsSync)(path)) return path;
2463
2706
  }
2464
2707
  return null;
2465
2708
  }
2466
2709
  function readWranglerConfig(path) {
2467
2710
  if (path.endsWith(".toml")) return null;
2468
2711
  try {
2469
- return JSON.parse(stripJsonComments((0, import_node_fs7.readFileSync)(path, "utf8")));
2712
+ return JSON.parse(stripJsonComments((0, import_node_fs9.readFileSync)(path, "utf8")));
2470
2713
  } catch {
2471
2714
  return null;
2472
2715
  }
@@ -2564,10 +2807,10 @@ function wranglerWarnings(rootDir) {
2564
2807
  for (const { label, block } of blocks) {
2565
2808
  const assets = block.assets;
2566
2809
  if (assets?.directory) {
2567
- const dir = (0, import_node_path6.resolve)(rootDir, assets.directory);
2568
- if (dir === (0, import_node_path6.resolve)(rootDir)) {
2810
+ const dir = (0, import_node_path8.resolve)(rootDir, assets.directory);
2811
+ if (dir === (0, import_node_path8.resolve)(rootDir)) {
2569
2812
  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"))) {
2813
+ } else if ((0, import_node_fs10.existsSync)((0, import_node_path8.join)(dir, "node_modules"))) {
2571
2814
  warnings.push(`${label}assets.directory contains node_modules \u2014 wrangler dev's watcher will exhaust file descriptors`);
2572
2815
  }
2573
2816
  }
@@ -2602,13 +2845,13 @@ function o11yProjectWarnings(rootDir) {
2602
2845
  warnings.push("cannot verify o11y Worker instrumentation \u2014 add a parseable wrangler.jsonc/json config");
2603
2846
  return warnings;
2604
2847
  }
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)) {
2848
+ const main = typeof config.main === "string" ? (0, import_node_path8.resolve)(rootDir, config.main) : null;
2849
+ if (!main || !(0, import_node_fs10.existsSync)(main)) {
2607
2850
  warnings.push("cannot verify o11y Worker instrumentation \u2014 wrangler main is missing or unreadable");
2608
2851
  } else {
2609
2852
  let source = "";
2610
2853
  try {
2611
- source = (0, import_node_fs8.readFileSync)(main, "utf8");
2854
+ source = (0, import_node_fs10.readFileSync)(main, "utf8");
2612
2855
  } catch {
2613
2856
  }
2614
2857
  if (!/\bwithObservability\b/.test(source)) {
@@ -2632,7 +2875,7 @@ function calendarProjectWarnings(rootDir) {
2632
2875
  }
2633
2876
  function readPackageJson(rootDir) {
2634
2877
  try {
2635
- return JSON.parse((0, import_node_fs8.readFileSync)((0, import_node_path6.join)(rootDir, "package.json"), "utf8"));
2878
+ return JSON.parse((0, import_node_fs10.readFileSync)((0, import_node_path8.join)(rootDir, "package.json"), "utf8"));
2636
2879
  } catch {
2637
2880
  return null;
2638
2881
  }
@@ -2859,13 +3102,14 @@ function harnessOption(value, flag) {
2859
3102
  }
2860
3103
 
2861
3104
  // src/init.ts
2862
- var import_node_fs9 = require("fs");
2863
- var import_node_path7 = require("path");
3105
+ var import_node_fs11 = require("fs");
3106
+ var import_node_path9 = require("path");
3107
+ var import_apps3 = require("@odla-ai/apps");
2864
3108
  function initProject(options) {
2865
3109
  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) {
3110
+ const rootDir = (0, import_node_path9.resolve)(options.rootDir ?? process.cwd());
3111
+ const configPath = (0, import_node_path9.resolve)(rootDir, options.configPath ?? "odla.config.mjs");
3112
+ if ((0, import_node_fs11.existsSync)(configPath) && !options.force) {
2869
3113
  throw new Error(`${configPath} already exists. Pass --force to overwrite.`);
2870
3114
  }
2871
3115
  if (!/^[a-z0-9][a-z0-9-]*$/.test(options.appId)) {
@@ -2873,22 +3117,28 @@ function initProject(options) {
2873
3117
  }
2874
3118
  const envs = options.envs?.length ? options.envs : ["dev"];
2875
3119
  const services = options.services?.length ? options.services : ["db", "ai"];
2876
- if (services.includes("calendar") && !services.includes("db")) throw new Error("--services calendar requires db");
3120
+ for (const service of services) {
3121
+ const definition = (0, import_apps3.appServiceDefinition)(service);
3122
+ if (!definition) throw new Error(`--services contains unknown service "${service}" (known: ${(0, import_apps3.appServiceIds)().join(", ")})`);
3123
+ for (const dependency of definition.requires) {
3124
+ if (!services.includes(dependency)) throw new Error(`--services ${service} requires ${dependency}`);
3125
+ }
3126
+ }
2877
3127
  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());
3128
+ (0, import_node_fs11.mkdirSync)((0, import_node_path9.dirname)(configPath), { recursive: true });
3129
+ (0, import_node_fs11.mkdirSync)((0, import_node_path9.resolve)(rootDir, "src/odla"), { recursive: true });
3130
+ (0, import_node_fs11.mkdirSync)((0, import_node_path9.resolve)(rootDir, ".odla"), { recursive: true });
3131
+ (0, import_node_fs11.writeFileSync)(configPath, configTemplate({ appId: options.appId, name: options.name, envs, services, aiProvider }));
3132
+ writeIfMissing((0, import_node_path9.resolve)(rootDir, "src/odla/schema.mjs"), schemaTemplate());
3133
+ writeIfMissing((0, import_node_path9.resolve)(rootDir, "src/odla/rules.mjs"), rulesTemplate());
2884
3134
  ensureGitignore(rootDir);
2885
3135
  out.log(`created ${relativeDisplay(configPath, rootDir)}`);
2886
3136
  out.log("created src/odla/schema.mjs and src/odla/rules.mjs");
2887
3137
  out.log("updated .gitignore for local odla credentials");
2888
3138
  }
2889
3139
  function writeIfMissing(path, text) {
2890
- if ((0, import_node_fs9.existsSync)(path)) return;
2891
- (0, import_node_fs9.writeFileSync)(path, text);
3140
+ if ((0, import_node_fs11.existsSync)(path)) return;
3141
+ (0, import_node_fs11.writeFileSync)(path, text);
2892
3142
  }
2893
3143
  function configTemplate(input) {
2894
3144
  const calendar = input.services.includes("calendar") ? ` calendar: {
@@ -3063,7 +3313,7 @@ function assertWranglerConfig(cfg) {
3063
3313
 
3064
3314
  // src/secrets-set.ts
3065
3315
  var import_ai = require("@odla-ai/ai");
3066
- var import_apps2 = require("@odla-ai/apps");
3316
+ var import_apps4 = require("@odla-ai/apps");
3067
3317
  var PROD_ENV_NAMES2 = /* @__PURE__ */ new Set(["prod", "production"]);
3068
3318
  async function secretsSet(options) {
3069
3319
  const name = (options.name ?? "").trim();
@@ -3115,13 +3365,13 @@ async function resolveVaultWrite(options) {
3115
3365
  throw new Error(`refusing to store a secret for "${options.env}" without --yes`);
3116
3366
  }
3117
3367
  const value = await secretInputValue(options, "secret");
3118
- return { cfg, tenantId: (0, import_apps2.tenantIdFor)(cfg.app.id, options.env), value, doFetch, out };
3368
+ return { cfg, tenantId: (0, import_apps4.tenantIdFor)(cfg.app.id, options.env), value, doFetch, out };
3119
3369
  }
3120
3370
 
3121
3371
  // src/skill.ts
3122
- var import_node_fs10 = require("fs");
3123
- var import_node_os = require("os");
3124
- var import_node_path8 = require("path");
3372
+ var import_node_fs12 = require("fs");
3373
+ var import_node_os2 = require("os");
3374
+ var import_node_path10 = require("path");
3125
3375
  var import_node_url2 = require("url");
3126
3376
 
3127
3377
  // src/skill-adapters.ts
@@ -3200,8 +3450,8 @@ function installSkill(options = {}) {
3200
3450
  const files = listFiles(sourceDir);
3201
3451
  if (files.length === 0) throw new Error(`no bundled skills found at ${sourceDir}`);
3202
3452
  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)());
3453
+ const root = (0, import_node_path10.resolve)(options.dir ?? process.cwd());
3454
+ const home = (0, import_node_path10.resolve)(options.homeDir ?? (0, import_node_os2.homedir)());
3205
3455
  const plans = /* @__PURE__ */ new Map();
3206
3456
  const targets = /* @__PURE__ */ new Map();
3207
3457
  const rememberTarget = (harness, target) => {
@@ -3215,48 +3465,48 @@ function installSkill(options = {}) {
3215
3465
  plans.set(target, { target, content: content2, boundary, managedMerge });
3216
3466
  };
3217
3467
  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);
3468
+ 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
3469
  };
3220
3470
  let targetDir;
3221
3471
  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");
3472
+ const claudeRoot = (0, import_node_path10.join)(home, ".claude", "skills");
3473
+ const codexRoot = (0, import_node_path10.resolve)(options.codexHomeDir ?? process.env.CODEX_HOME ?? (0, import_node_path10.join)(home, ".codex"), "skills");
3224
3474
  targetDir = harnesses[0] === "codex" ? codexRoot : claudeRoot;
3225
3475
  for (const harness of harnesses) {
3226
3476
  const skillRoot = harness === "claude" ? claudeRoot : codexRoot;
3227
- planSkillTree(skillRoot, harness === "claude" ? home : (0, import_node_path8.dirname)((0, import_node_path8.dirname)(codexRoot)));
3477
+ planSkillTree(skillRoot, harness === "claude" ? home : (0, import_node_path10.dirname)((0, import_node_path10.dirname)(codexRoot)));
3228
3478
  rememberTarget(harness, skillRoot);
3229
3479
  }
3230
3480
  } else {
3231
- const sharedRoot = (0, import_node_path8.join)(root, ".agents", "skills");
3481
+ const sharedRoot = (0, import_node_path10.join)(root, ".agents", "skills");
3232
3482
  planSkillTree(sharedRoot);
3233
- const claudeRoot = (0, import_node_path8.join)(root, ".claude", "skills");
3483
+ const claudeRoot = (0, import_node_path10.join)(root, ".claude", "skills");
3234
3484
  targetDir = harnesses.includes("claude") ? claudeRoot : sharedRoot;
3235
3485
  for (const harness of harnesses) rememberTarget(harness, sharedRoot);
3236
3486
  if (harnesses.includes("claude")) {
3237
3487
  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));
3488
+ const canonical = (0, import_node_fs12.readFileSync)((0, import_node_path10.join)(sourceDir, skill, "SKILL.md"), "utf8");
3489
+ plan((0, import_node_path10.join)(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical));
3240
3490
  }
3241
3491
  rememberTarget("claude", claudeRoot);
3242
3492
  }
3243
3493
  if (harnesses.includes("cursor")) {
3244
- const cursorRule = (0, import_node_path8.join)(root, ".cursor", "rules", "odla.mdc");
3494
+ const cursorRule = (0, import_node_path10.join)(root, ".cursor", "rules", "odla.mdc");
3245
3495
  plan(cursorRule, CURSOR_RULE);
3246
3496
  rememberTarget("cursor", cursorRule);
3247
3497
  }
3248
3498
  if (harnesses.includes("agents")) {
3249
- const agentsFile = (0, import_node_path8.join)(root, "AGENTS.md");
3499
+ const agentsFile = (0, import_node_path10.join)(root, "AGENTS.md");
3250
3500
  plan(agentsFile, managedFileContent(agentsFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
3251
3501
  rememberTarget("agents", agentsFile);
3252
3502
  }
3253
3503
  if (harnesses.includes("copilot")) {
3254
- const copilotFile = (0, import_node_path8.join)(root, ".github", "copilot-instructions.md");
3504
+ const copilotFile = (0, import_node_path10.join)(root, ".github", "copilot-instructions.md");
3255
3505
  plan(copilotFile, managedFileContent(copilotFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
3256
3506
  rememberTarget("copilot", copilotFile);
3257
3507
  }
3258
3508
  if (harnesses.includes("gemini")) {
3259
- const geminiFile = (0, import_node_path8.join)(root, "GEMINI.md");
3509
+ const geminiFile = (0, import_node_path10.join)(root, "GEMINI.md");
3260
3510
  plan(geminiFile, managedFileContent(geminiFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
3261
3511
  rememberTarget("gemini", geminiFile);
3262
3512
  }
@@ -3270,11 +3520,11 @@ function installSkill(options = {}) {
3270
3520
  conflicts.push(`${file.target} (redirected by symbolic link ${symlink})`);
3271
3521
  continue;
3272
3522
  }
3273
- if (!(0, import_node_fs10.existsSync)(file.target)) {
3523
+ if (!(0, import_node_fs12.existsSync)(file.target)) {
3274
3524
  writtenPaths.add(file.target);
3275
3525
  continue;
3276
3526
  }
3277
- const current = (0, import_node_fs10.readFileSync)(file.target, "utf8");
3527
+ const current = (0, import_node_fs12.readFileSync)(file.target, "utf8");
3278
3528
  if (current === file.content) {
3279
3529
  unchangedPaths.add(file.target);
3280
3530
  } else if (file.managedMerge || options.force) {
@@ -3291,9 +3541,9 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
3291
3541
  );
3292
3542
  }
3293
3543
  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);
3544
+ if (!(0, import_node_fs12.existsSync)(file.target) || (0, import_node_fs12.readFileSync)(file.target, "utf8") !== file.content) {
3545
+ (0, import_node_fs12.mkdirSync)((0, import_node_path10.dirname)(file.target), { recursive: true });
3546
+ (0, import_node_fs12.writeFileSync)(file.target, file.content);
3297
3547
  }
3298
3548
  }
3299
3549
  const skills = skillNames(files);
@@ -3312,7 +3562,7 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
3312
3562
  };
3313
3563
  }
3314
3564
  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();
3565
+ 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
3566
  }
3317
3567
  function normalizeHarnesses(values, global) {
3318
3568
  const requested = values?.length ? values : ["claude"];
@@ -3334,9 +3584,9 @@ function normalizeHarnesses(values, global) {
3334
3584
  function managedFileContent(path, block, force, boundary) {
3335
3585
  const symlink = symlinkedComponent(boundary, path);
3336
3586
  if (symlink) throw new Error(`refusing to manage ${path}: symbolic link component ${symlink}`);
3337
- if (!(0, import_node_fs10.existsSync)(path)) return `${block}
3587
+ if (!(0, import_node_fs12.existsSync)(path)) return `${block}
3338
3588
  `;
3339
- const current = (0, import_node_fs10.readFileSync)(path, "utf8");
3589
+ const current = (0, import_node_fs12.readFileSync)(path, "utf8");
3340
3590
  const start = "<!-- odla-ai agent setup:start -->";
3341
3591
  const end = "<!-- odla-ai agent setup:end -->";
3342
3592
  const startAt = current.indexOf(start);
@@ -3357,15 +3607,15 @@ function managedFileContent(path, block, force, boundary) {
3357
3607
  return `${current.slice(0, startAt)}${block}${current.slice(afterEnd)}`;
3358
3608
  }
3359
3609
  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)) {
3610
+ const rel = (0, import_node_path10.relative)(boundary, target);
3611
+ if (rel === ".." || rel.startsWith(`..${import_node_path10.sep}`) || (0, import_node_path10.isAbsolute)(rel)) {
3362
3612
  throw new Error(`agent setup target escapes its install root: ${target}`);
3363
3613
  }
3364
3614
  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);
3615
+ for (const part of rel.split(import_node_path10.sep).filter(Boolean)) {
3616
+ current = (0, import_node_path10.join)(current, part);
3367
3617
  try {
3368
- if ((0, import_node_fs10.lstatSync)(current).isSymbolicLink()) return current;
3618
+ if ((0, import_node_fs12.lstatSync)(current).isSymbolicLink()) return current;
3369
3619
  } catch (error) {
3370
3620
  if (error.code !== "ENOENT") throw error;
3371
3621
  }
@@ -3376,13 +3626,13 @@ function skillNames(files) {
3376
3626
  return [...new Set(files.filter((file) => /(^|[\\/])SKILL\.md$/.test(file)).map((file) => file.split(/[\\/]/)[0]))].sort();
3377
3627
  }
3378
3628
  function listFiles(dir) {
3379
- if (!(0, import_node_fs10.existsSync)(dir)) return [];
3629
+ if (!(0, import_node_fs12.existsSync)(dir)) return [];
3380
3630
  const results = [];
3381
3631
  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);
3632
+ for (const entry of (0, import_node_fs12.readdirSync)(current, { withFileTypes: true })) {
3633
+ const path = (0, import_node_path10.join)(current, entry.name);
3384
3634
  if (entry.isDirectory()) walk(path);
3385
- else results.push((0, import_node_path8.relative)(dir, path));
3635
+ else results.push((0, import_node_path10.relative)(dir, path));
3386
3636
  }
3387
3637
  };
3388
3638
  walk(dir);
@@ -3529,13 +3779,16 @@ async function safeText3(res) {
3529
3779
 
3530
3780
  // src/cli-project.ts
3531
3781
  var SKILL_OPTS = ["dir", "global", "force", "agent", "harness"];
3532
- function install(parsed, positionals) {
3782
+ function install(parsed, positionals, deps) {
3533
3783
  assertArgs(parsed, SKILL_OPTS, positionals);
3534
3784
  installSkill({
3535
3785
  dir: stringOpt(parsed.options.dir),
3536
3786
  global: parsed.options.global === true,
3537
3787
  harnesses: harnessList(parsed),
3538
- force: parsed.options.force === true
3788
+ force: parsed.options.force === true,
3789
+ homeDir: deps.homeDir,
3790
+ codexHomeDir: deps.codexHomeDir,
3791
+ stdout: deps.stdout
3539
3792
  });
3540
3793
  }
3541
3794
  async function secretsCommand(parsed, deps) {
@@ -3551,6 +3804,7 @@ async function secretsCommand(parsed, deps) {
3551
3804
  token: stringOpt(parsed.options.token),
3552
3805
  email: stringOpt(parsed.options.email),
3553
3806
  yes: parsed.options.yes === true,
3807
+ readStdin: deps.readStdin,
3554
3808
  fetch: deps.fetch,
3555
3809
  stdout: deps.stdout
3556
3810
  };
@@ -3609,8 +3863,8 @@ async function projectCommand(command, parsed, deps) {
3609
3863
  return true;
3610
3864
  }
3611
3865
  if (command === "setup") {
3612
- install(parsed, 1);
3613
- console.log(
3866
+ install(parsed, 1, deps);
3867
+ (deps.stdout ?? console).log(
3614
3868
  "\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
3869
  );
3616
3870
  return true;
@@ -3618,7 +3872,7 @@ async function projectCommand(command, parsed, deps) {
3618
3872
  if (command === "skill") {
3619
3873
  const sub = parsed.positionals[1];
3620
3874
  if (sub !== "install") throw new Error(`unknown skill subcommand "${sub ?? ""}". Try "odla-ai skill install".`);
3621
- install(parsed, 2);
3875
+ install(parsed, 2, deps);
3622
3876
  return true;
3623
3877
  }
3624
3878
  if (command === "secrets") {
@@ -3629,9 +3883,9 @@ async function projectCommand(command, parsed, deps) {
3629
3883
  }
3630
3884
 
3631
3885
  // 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");
3886
+ var import_node_fs14 = require("fs");
3887
+ var import_node_os4 = require("os");
3888
+ var import_node_path12 = require("path");
3635
3889
 
3636
3890
  // ../harness/dist/chunk-QTUEF2HZ.js
3637
3891
  var HARNESS_PROTOCOL_VERSION = 1;
@@ -6516,9 +6770,9 @@ var CodePiRuntimeEngine = class {
6516
6770
  };
6517
6771
 
6518
6772
  // src/help.ts
6519
- var import_node_fs11 = require("fs");
6773
+ var import_node_fs13 = require("fs");
6520
6774
  function cliVersion() {
6521
- const pkg = JSON.parse((0, import_node_fs11.readFileSync)(new URL("../package.json", importMetaUrl), "utf8"));
6775
+ const pkg = JSON.parse((0, import_node_fs13.readFileSync)(new URL("../package.json", importMetaUrl), "utf8"));
6522
6776
  return pkg.version ?? "unknown";
6523
6777
  }
6524
6778
  function printHelp(output = console) {
@@ -6606,15 +6860,16 @@ Usage:
6606
6860
  odla-ai runbook rm <slug> [--app <id>]
6607
6861
  odla-ai capabilities [--json]
6608
6862
  odla-ai code connect [--env dev|prod] [--email <odla-account>] [--engine auto|container|podman|docker] [--slots <1-64>] [--once]
6609
- odla-ai admin ai show [--platform https://odla.ai] [--email <odla-account>] [--json]
6610
- odla-ai admin ai models [--provider <id>] [--json]
6611
- odla-ai admin ai set <purpose> [--provider <id>] [--model <id>] [--enabled|--no-enabled]
6612
- odla-ai admin ai set security --discovery-provider <id> --discovery-model <id>
6613
- --validation-provider <id> --validation-model <id> [--enabled|--no-enabled]
6614
- odla-ai admin ai credentials [--json]
6615
- odla-ai admin ai credential set <provider> (--from-env <NAME>|--stdin)
6616
- odla-ai admin ai usage [--app-id <id>] [--env <env>] [--run-id <id>] [--limit <1-500>] [--json]
6617
- odla-ai admin ai audit [--limit <1-200>] [--json]
6863
+ odla-ai admin ai show [--context <name>] [--platform https://odla.ai] [--email <odla-account>] [--json]
6864
+ odla-ai admin ai models [--context <name>] [--provider <id>] [--json]
6865
+ odla-ai admin ai set <purpose> [--context <name>] [--provider <id>] [--model <id>] [--enabled|--no-enabled]
6866
+ [--max-input-bytes <n>] [--max-output-tokens <n>] [--max-calls-per-run <n>] [--json]
6867
+ odla-ai admin ai set security [--context <name>] --discovery-provider <id> --discovery-model <id>
6868
+ --validation-provider <id> --validation-model <id> [--enabled|--no-enabled] [--json]
6869
+ odla-ai admin ai credentials [--context <name>] [--json]
6870
+ odla-ai admin ai credential set <provider> [--context <name>] (--from-env <NAME>|--stdin)
6871
+ odla-ai admin ai usage [--context <name>] [--app-id <id>] [--env <env>] [--run-id <id>] [--limit <1-500>] [--json]
6872
+ odla-ai admin ai audit [--context <name>] [--limit <1-200>] [--json]
6618
6873
  odla-ai security github connect [--repo owner/name] [--env dev] [--email <odla-account>] [--no-open]
6619
6874
  odla-ai security github disconnect --source <id> [--env dev] [--yes]
6620
6875
  odla-ai security plan [--env dev] [--json]
@@ -7041,8 +7296,8 @@ function digestText(value) {
7041
7296
  var import_node_child_process6 = require("child_process");
7042
7297
  var import_node_crypto2 = require("crypto");
7043
7298
  var import_promises10 = require("fs/promises");
7044
- var import_node_os2 = require("os");
7045
- var import_node_path9 = require("path");
7299
+ var import_node_os3 = require("os");
7300
+ var import_node_path11 = require("path");
7046
7301
  var import_node_url3 = require("url");
7047
7302
 
7048
7303
  // src/code-runtime-config.ts
@@ -7128,10 +7383,10 @@ async function embeddedPiImageName() {
7128
7383
  return `odla-ai/pi-agent:embedded-sha256-${(0, import_node_crypto2.createHash)("sha256").update(bundle).digest("hex")}`;
7129
7384
  }
7130
7385
  async function buildEmbeddedPiImage(engine, image, run) {
7131
- const context = await (0, import_promises10.mkdtemp)((0, import_node_path9.join)((0, import_node_os2.tmpdir)(), "odla-code-pi-"));
7386
+ const context = await (0, import_promises10.mkdtemp)((0, import_node_path11.join)((0, import_node_os3.tmpdir)(), "odla-code-pi-"));
7132
7387
  try {
7133
- await (0, import_promises10.copyFile)(embeddedPiAssetPath(), (0, import_node_path9.join)(context, "pi-agent.js"));
7134
- await (0, import_promises10.writeFile)((0, import_node_path9.join)(context, "Dockerfile"), [
7388
+ await (0, import_promises10.copyFile)(embeddedPiAssetPath(), (0, import_node_path11.join)(context, "pi-agent.js"));
7389
+ await (0, import_promises10.writeFile)((0, import_node_path11.join)(context, "Dockerfile"), [
7135
7390
  `FROM ${CODE_NODE_IMAGE}`,
7136
7391
  "COPY pi-agent.js /opt/odla/pi-agent.js",
7137
7392
  "WORKDIR /workspace",
@@ -7147,8 +7402,8 @@ async function buildEmbeddedPiImage(engine, image, run) {
7147
7402
  // src/code-connect.ts
7148
7403
  async function codeConnect(options) {
7149
7404
  const cwd = options.cwd ?? process.cwd();
7150
- const configPath = (0, import_node_path10.resolve)(cwd, options.configPath);
7151
- const cfg = (0, import_node_fs12.existsSync)(configPath) ? await loadProjectConfig(configPath) : null;
7405
+ const configPath = (0, import_node_path12.resolve)(cwd, options.configPath);
7406
+ const cfg = (0, import_node_fs14.existsSync)(configPath) ? await loadProjectConfig(configPath) : null;
7152
7407
  const requestedAppId = options.appId?.trim();
7153
7408
  if (requestedAppId && !/^[a-z0-9][a-z0-9-]{1,62}$/.test(requestedAppId)) {
7154
7409
  throw new Error("--app-id must be a valid odla app id");
@@ -7182,7 +7437,7 @@ async function codeConnect(options) {
7182
7437
  );
7183
7438
  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");
7184
7439
  const hostPlatform = process.platform === "darwin" ? "macos" : "linux";
7185
- const hostName = (options.name ?? (0, import_node_os3.hostname)()).trim();
7440
+ const hostName = (options.name ?? (0, import_node_os4.hostname)()).trim();
7186
7441
  if (!hostName || hostName.length > 120) throw new Error("--name must contain 1 to 120 characters");
7187
7442
  const repository = await inferGitHubRepository(cwd, options.readGitOrigin);
7188
7443
  const localSource = await (options.prepareLocalSource ?? prepareCodeLocalSource)(
@@ -7219,8 +7474,8 @@ async function codeConnect(options) {
7219
7474
  platform: hostPlatform,
7220
7475
  arch: process.arch,
7221
7476
  engines: [engine],
7222
- cpuCount: (0, import_node_os3.cpus)().length,
7223
- memoryBytes: (0, import_node_os3.totalmem)(),
7477
+ cpuCount: (0, import_node_os4.cpus)().length,
7478
+ memoryBytes: (0, import_node_os4.totalmem)(),
7224
7479
  source: descriptor2,
7225
7480
  images: {
7226
7481
  ready: true,
@@ -7366,246 +7621,19 @@ async function codeCommand(parsed, dependencies) {
7366
7621
  }
7367
7622
 
7368
7623
  // src/operator-credentials.ts
7369
- var import_node_process7 = __toESM(require("process"), 1);
7624
+ var import_node_process9 = __toESM(require("process"), 1);
7370
7625
  function developerTokenStatus(context, parsed, now = Date.now()) {
7371
7626
  const cached = readJsonFile(context.cfg.local.tokenFile);
7372
7627
  const cacheStatus = !cached?.token ? "missing" : cached.platform !== context.platform.value ? "other-platform" : (cached.expiresAt ?? 0) <= now + 6e4 ? "expired" : "valid";
7373
- const source = clean(
7628
+ const source = clean3(
7374
7629
  stringOpt(parsed.options.token)
7375
- ) ? "flag" : clean(import_node_process7.default.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
7630
+ ) ? "flag" : clean3(import_node_process9.default.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
7376
7631
  return {
7377
7632
  source,
7378
7633
  cacheFile: context.cfg.local.tokenFile,
7379
7634
  cacheStatus
7380
7635
  };
7381
7636
  }
7382
- function clean(value) {
7383
- const normalized = value?.trim();
7384
- return normalized || void 0;
7385
- }
7386
-
7387
- // src/operator-context.ts
7388
- var import_node_fs14 = require("fs");
7389
- var import_node_path12 = require("path");
7390
- var import_node_process9 = __toESM(require("process"), 1);
7391
-
7392
- // src/operator-profiles.ts
7393
- var import_node_fs13 = require("fs");
7394
- var import_node_os4 = require("os");
7395
- var import_node_path11 = require("path");
7396
- var import_node_process8 = __toESM(require("process"), 1);
7397
- function operatorProfileFile() {
7398
- return (0, import_node_path11.resolve)(
7399
- clean2(import_node_process8.default.env.ODLA_CONTEXT_FILE) ?? (0, import_node_path11.join)((0, import_node_os4.homedir)(), ".odla", "contexts.json")
7400
- );
7401
- }
7402
- function resolveOperatorProfile(parsed) {
7403
- const fromFlag = clean2(stringOpt(parsed.options.context));
7404
- const fromEnvironment = clean2(import_node_process8.default.env.ODLA_CONTEXT);
7405
- const name = fromFlag ?? fromEnvironment ?? null;
7406
- const file = operatorProfileFile();
7407
- if (!name) {
7408
- return { name: null, source: "unresolved", file, value: null };
7409
- }
7410
- assertOperatorName(name, "context");
7411
- const profiles = readOperatorProfiles(file);
7412
- const value = Object.hasOwn(profiles, name) ? profiles[name] : void 0;
7413
- if (!value) {
7414
- throw new Error(
7415
- `operator context "${name}" not found in ${file}; run "odla-ai context list" or save it first`
7416
- );
7417
- }
7418
- return {
7419
- name,
7420
- source: fromFlag ? "flag" : "environment",
7421
- file,
7422
- value
7423
- };
7424
- }
7425
- function listOperatorProfiles(file = operatorProfileFile()) {
7426
- return Object.entries(readOperatorProfiles(file)).sort(([a], [b]) => a.localeCompare(b)).map(([name, profile]) => ({ name, ...profile }));
7427
- }
7428
- function saveOperatorProfile(name, profile, file = operatorProfileFile()) {
7429
- assertOperatorName(name, "context");
7430
- const normalized = validateProfile(profile, `operator context "${name}"`);
7431
- const profiles = readOperatorProfiles(file);
7432
- profiles[name] = normalized;
7433
- writePrivateJson(file, { schemaVersion: 1, profiles });
7434
- }
7435
- function removeOperatorProfile(name, file = operatorProfileFile()) {
7436
- assertOperatorName(name, "context");
7437
- const profiles = readOperatorProfiles(file);
7438
- if (!Object.hasOwn(profiles, name)) return false;
7439
- delete profiles[name];
7440
- writePrivateJson(file, { schemaVersion: 1, profiles });
7441
- return true;
7442
- }
7443
- function operatorCredentialFiles(selection) {
7444
- 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");
7445
- return {
7446
- developer: (0, import_node_path11.join)(base, "dev-token.json"),
7447
- scoped: (0, import_node_path11.join)(base, "admin-token.local.json")
7448
- };
7449
- }
7450
- function assertOperatorName(value, label) {
7451
- if (!/^[a-z0-9][a-z0-9-]*$/.test(value)) {
7452
- throw new Error(
7453
- `${label} must contain lowercase letters, numbers, and hyphens`
7454
- );
7455
- }
7456
- }
7457
- function readOperatorProfiles(file) {
7458
- if (!(0, import_node_fs13.existsSync)(file)) return emptyProfiles();
7459
- let raw;
7460
- try {
7461
- raw = JSON.parse((0, import_node_fs13.readFileSync)(file, "utf8"));
7462
- } catch {
7463
- throw new Error(`operator context file ${file} is not valid JSON`);
7464
- }
7465
- if (!raw || typeof raw !== "object" || raw.schemaVersion !== 1 || !raw.profiles || typeof raw.profiles !== "object" || Array.isArray(raw.profiles)) {
7466
- throw new Error(`operator context file ${file} has an invalid shape`);
7467
- }
7468
- const unknown = Object.keys(raw).filter(
7469
- (key) => !["schemaVersion", "profiles"].includes(key)
7470
- );
7471
- if (unknown.length > 0) {
7472
- throw new Error(
7473
- `operator context file ${file} contains unsupported field(s): ${unknown.sort().join(", ")}`
7474
- );
7475
- }
7476
- const profiles = emptyProfiles();
7477
- for (const [name, value] of Object.entries(
7478
- raw.profiles
7479
- )) {
7480
- assertOperatorName(name, "context");
7481
- profiles[name] = validateProfile(value, `operator context "${name}"`);
7482
- }
7483
- return profiles;
7484
- }
7485
- function emptyProfiles() {
7486
- return /* @__PURE__ */ Object.create(null);
7487
- }
7488
- function validateProfile(value, label) {
7489
- if (!value || typeof value !== "object" || Array.isArray(value)) {
7490
- throw new Error(`${label} has an invalid shape`);
7491
- }
7492
- const unknown = Object.keys(value).filter(
7493
- (key) => !["platform", "app", "environment"].includes(key)
7494
- );
7495
- if (unknown.length > 0) {
7496
- throw new Error(
7497
- `${label} contains unsupported field(s): ${unknown.sort().join(", ")}; contexts store scope metadata only, never credentials`
7498
- );
7499
- }
7500
- const candidate = value;
7501
- if (typeof candidate.platform !== "string") {
7502
- throw new Error(`${label} needs an absolute platform URL`);
7503
- }
7504
- const platform = platformAudience(candidate.platform);
7505
- const app = clean2(candidate.app);
7506
- const environment2 = clean2(candidate.environment);
7507
- if (app) assertOperatorName(app, "app");
7508
- if (environment2) assertOperatorName(environment2, "environment");
7509
- return {
7510
- platform,
7511
- ...app ? { app } : {},
7512
- ...environment2 ? { environment: environment2 } : {}
7513
- };
7514
- }
7515
- function clean2(value) {
7516
- const normalized = value?.trim();
7517
- return normalized || void 0;
7518
- }
7519
-
7520
- // src/operator-context.ts
7521
- var DEFAULT_PLATFORM2 = "https://odla.ai";
7522
- async function resolveOperatorContext(parsed, options = {}) {
7523
- const profile = resolveOperatorProfile(parsed);
7524
- const configArgument = stringOpt(parsed.options.config) ?? "odla.config.mjs";
7525
- const configPath = (0, import_node_path12.resolve)(configArgument);
7526
- const explicitConfig = parsed.options.config !== void 0;
7527
- const hasConfig = (0, import_node_fs14.existsSync)(configPath);
7528
- if (!hasConfig && (!options.allowMissingConfig || explicitConfig)) {
7529
- await loadProjectConfig(configArgument);
7530
- }
7531
- const loaded = hasConfig ? await loadProjectConfig(configArgument) : void 0;
7532
- const platformFlag = clean3(stringOpt(parsed.options.platform));
7533
- const platformEnvironment = clean3(import_node_process9.default.env.ODLA_PLATFORM_URL);
7534
- const platformValue = platformAudience(
7535
- platformFlag ?? platformEnvironment ?? profile.value?.platform ?? loaded?.platformUrl ?? DEFAULT_PLATFORM2
7536
- );
7537
- const platformSource = platformFlag ? "flag" : platformEnvironment ? "environment" : profile.value ? "profile" : loaded ? "config" : "default";
7538
- const appFlag = clean3(stringOpt(parsed.options.app));
7539
- const appEnvironment = clean3(import_node_process9.default.env.ODLA_APP_ID);
7540
- const appValue = appFlag ?? appEnvironment ?? profile.value?.app ?? loaded?.app.id ?? null;
7541
- const appSource = appFlag ? "flag" : appEnvironment ? "environment" : profile.value?.app ? "profile" : loaded ? "config" : "unresolved";
7542
- if (appValue) assertOperatorName(appValue, "app");
7543
- if (options.requireApp && !appValue) {
7544
- throw new Error(
7545
- "app context is unresolved; pass --app <id>, set ODLA_APP_ID, select --context <name>, or run inside a project with odla.config.mjs"
7546
- );
7547
- }
7548
- const envFlag = clean3(stringOpt(parsed.options.env));
7549
- const envEnvironment = clean3(import_node_process9.default.env.ODLA_ENV);
7550
- const environmentValue = envFlag ?? envEnvironment ?? profile.value?.environment ?? options.defaultEnvironment ?? null;
7551
- const environmentSource = envFlag ? "flag" : envEnvironment ? "environment" : profile.value?.environment ? "profile" : options.defaultEnvironment ? "default" : "unresolved";
7552
- if (environmentValue) {
7553
- assertOperatorName(environmentValue, "environment");
7554
- }
7555
- const rootDir = loaded?.rootDir ?? import_node_process9.default.cwd();
7556
- const profileCredentials = operatorCredentialFiles(profile);
7557
- 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;
7558
- 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;
7559
- const cfg = loaded ? {
7560
- ...loaded,
7561
- platformUrl: platformValue,
7562
- app: appValue ? {
7563
- id: appValue,
7564
- name: appValue === loaded.app.id ? loaded.app.name : appValue
7565
- } : loaded.app,
7566
- local: { ...loaded.local, tokenFile }
7567
- } : {
7568
- configPath,
7569
- rootDir,
7570
- platformUrl: platformValue,
7571
- dbEndpoint: platformValue,
7572
- app: {
7573
- id: appValue ?? "operator",
7574
- name: appValue ?? "Operator"
7575
- },
7576
- envs: environmentValue ? [environmentValue] : [],
7577
- services: [],
7578
- local: {
7579
- tokenFile,
7580
- credentialsFile: (0, import_node_path12.join)(rootDir, ".odla", "credentials.local.json"),
7581
- devVarsFile: (0, import_node_path12.join)(rootDir, ".dev.vars"),
7582
- gitignore: true
7583
- }
7584
- };
7585
- return {
7586
- cfg,
7587
- profile: {
7588
- name: profile.name,
7589
- source: profile.source,
7590
- file: profile.file
7591
- },
7592
- config: {
7593
- path: configPath,
7594
- status: loaded ? "loaded" : "absent",
7595
- explicit: explicitConfig
7596
- },
7597
- platform: { value: platformValue, source: platformSource },
7598
- app: { value: appValue, source: appSource },
7599
- environment: {
7600
- value: environmentValue,
7601
- source: environmentSource
7602
- },
7603
- credentials: {
7604
- developerTokenFile: tokenFile,
7605
- scopedTokenFile
7606
- }
7607
- };
7608
- }
7609
7637
  function clean3(value) {
7610
7638
  const normalized = value?.trim();
7611
7639
  return normalized || void 0;
@@ -8965,7 +8993,7 @@ async function read2(url, headers, doFetch) {
8965
8993
  }
8966
8994
 
8967
8995
  // src/provision.ts
8968
- var import_apps5 = require("@odla-ai/apps");
8996
+ var import_apps7 = require("@odla-ai/apps");
8969
8997
  var import_ai3 = require("@odla-ai/ai");
8970
8998
  var import_node_process10 = __toESM(require("process"), 1);
8971
8999
 
@@ -9022,9 +9050,9 @@ async function responseText(res) {
9022
9050
  }
9023
9051
 
9024
9052
  // src/provision-credentials.ts
9025
- var import_apps3 = require("@odla-ai/apps");
9053
+ var import_apps5 = require("@odla-ai/apps");
9026
9054
  async function provisionEnvCredentials(opts) {
9027
- const tenantId = (0, import_apps3.tenantIdFor)(opts.cfg.app.id, opts.env);
9055
+ const tenantId = (0, import_apps5.tenantIdFor)(opts.cfg.app.id, opts.env);
9028
9056
  const prior = opts.credentials?.envs[opts.env];
9029
9057
  let credentials = opts.credentials;
9030
9058
  let dbKey = opts.cfg.services.includes("db") && !opts.rotateDb ? prior?.dbKey : void 0;
@@ -9118,18 +9146,13 @@ async function safeText4(res) {
9118
9146
 
9119
9147
  // src/provision-helpers.ts
9120
9148
  var import_ai2 = require("@odla-ai/ai");
9121
- var import_apps4 = require("@odla-ai/apps");
9122
- function serviceRank(service) {
9123
- if (service === "db") return 0;
9124
- if (service === "calendar") return 2;
9125
- return 1;
9126
- }
9149
+ var import_apps6 = require("@odla-ai/apps");
9127
9150
  function defaultSecretName(provider) {
9128
9151
  const names = import_ai2.DEFAULT_SECRET_NAMES;
9129
9152
  return names[provider] ?? `${provider}_api_key`;
9130
9153
  }
9131
9154
  async function assertTenantAdminAccess(doFetch, cfg, env, token) {
9132
- const tenantId = (0, import_apps4.tenantIdFor)(cfg.app.id, env);
9155
+ const tenantId = (0, import_apps6.tenantIdFor)(cfg.app.id, env);
9133
9156
  const res = await doFetch(`${cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenantId)}/entitlements`, {
9134
9157
  headers: { authorization: `Bearer ${token}` }
9135
9158
  });
@@ -9238,7 +9261,7 @@ async function provision(options) {
9238
9261
  }
9239
9262
  const doFetch = options.fetch ?? fetch;
9240
9263
  const token = await getDeveloperToken(cfg, options, doFetch, out);
9241
- const apps = (0, import_apps5.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
9264
+ const apps = (0, import_apps7.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
9242
9265
  const existing = await apps.resolveApp(cfg.app.id);
9243
9266
  if (existing) {
9244
9267
  out.log(`app: ${cfg.app.id} already exists`);
@@ -9249,7 +9272,7 @@ async function provision(options) {
9249
9272
  for (const env of cfg.envs) {
9250
9273
  await assertTenantAdminAccess(doFetch, cfg, env, token);
9251
9274
  }
9252
- const serviceOrder = [...cfg.services].sort((a, b) => serviceRank(a) - serviceRank(b));
9275
+ const serviceOrder = (0, import_apps7.orderAppServices)(cfg.services);
9253
9276
  for (const env of cfg.envs) {
9254
9277
  for (const service of serviceOrder) {
9255
9278
  if (service === "ai") {
@@ -9282,7 +9305,7 @@ async function provision(options) {
9282
9305
  }
9283
9306
  }
9284
9307
  for (const env of cfg.envs) {
9285
- const tenantId = (0, import_apps5.tenantIdFor)(cfg.app.id, env);
9308
+ const tenantId = (0, import_apps7.tenantIdFor)(cfg.app.id, env);
9286
9309
  credentials = await provisionEnvCredentials({
9287
9310
  cfg,
9288
9311
  env,
@@ -11206,7 +11229,7 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
11206
11229
  return;
11207
11230
  }
11208
11231
  if (command === "admin") {
11209
- await adminCommand(parsed);
11232
+ await adminCommand(parsed, runtime);
11210
11233
  return;
11211
11234
  }
11212
11235
  if (command === "agent") {