@odla-ai/cli 0.27.15 → 0.27.17

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.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runCli
4
- } from "./chunk-MR5QXX3B.js";
4
+ } from "./chunk-3WVVHH3Y.js";
5
5
  import {
6
6
  exitCodeFor
7
7
  } from "./chunk-UKLSRQ5J.js";
@@ -9,4 +9,4 @@ export {
9
9
  exitCodeFor,
10
10
  runCli
11
11
  };
12
- //# sourceMappingURL=cli-2RZFZRRT.js.map
12
+ //# sourceMappingURL=cli-DCSVQAZ6.js.map
package/dist/index.cjs CHANGED
@@ -41,6 +41,7 @@ __export(index_exports, {
41
41
  SYSTEM_AI_PURPOSES: () => SYSTEM_AI_PURPOSES,
42
42
  acceptedAfter: () => acceptedAfter,
43
43
  adminAi: () => adminAi,
44
+ aiModels: () => aiModels,
44
45
  calendarBookingPageUrl: () => calendarBookingPageUrl,
45
46
  calendarCalendars: () => calendarCalendars,
46
47
  calendarConnect: () => calendarConnect,
@@ -2704,6 +2705,63 @@ function printGroup(out, heading, items) {
2704
2705
  out.log("");
2705
2706
  }
2706
2707
 
2708
+ // src/ai-models.ts
2709
+ var import_ai = require("@odla-ai/ai");
2710
+ async function aiModels(options = {}) {
2711
+ const cfg = await loadProjectConfig(options.configPath ?? "odla.config.mjs");
2712
+ const env = options.env ?? cfg.envs[0] ?? "dev";
2713
+ if (!cfg.envs.includes(env)) throw new Error(`ai models env "${env}" is not declared in config envs`);
2714
+ const url = new URL(`/registry/apps/${encodeURIComponent(cfg.app.id)}/public-config`, cfg.platformUrl);
2715
+ url.searchParams.set("env", env);
2716
+ const response2 = await (options.fetch ?? fetch)(url);
2717
+ if (!response2.ok) throw new Error(`read app AI models failed (${response2.status}): ${await safeText4(response2)}`);
2718
+ const body = await response2.json();
2719
+ if (!body.ai) throw new Error(`ai is not configured for ${cfg.app.id}/${env}`);
2720
+ const mode = body.ai.mode === "hosted" ? "hosted" : "byok";
2721
+ const defaultModel = typeof body.ai.model === "string" ? body.ai.model : void 0;
2722
+ let models;
2723
+ if (mode === "hosted") {
2724
+ if (body.ai.enabled !== true) throw new Error(`hosted ai is disabled for ${cfg.app.id}/${env}`);
2725
+ if (!Array.isArray(body.ai.models) || !body.ai.models.every(isModelSpec)) {
2726
+ throw new Error("platform returned an invalid hosted AI model catalog");
2727
+ }
2728
+ models = body.ai.models;
2729
+ } else {
2730
+ const provider = typeof body.ai.provider === "string" ? body.ai.provider : cfg.ai?.provider;
2731
+ if (!provider) throw new Error(`BYOK ai has no provider for ${cfg.app.id}/${env}`);
2732
+ models = Object.values(import_ai.DEFAULT_CATALOG).filter((model) => model.provider === provider);
2733
+ }
2734
+ if (options.provider) models = models.filter((model) => model.provider === options.provider);
2735
+ models.sort((a, b) => a.provider.localeCompare(b.provider) || a.id.localeCompare(b.id));
2736
+ const out = options.stdout ?? console;
2737
+ if (options.json) {
2738
+ out.log(JSON.stringify({ appId: cfg.app.id, env, mode, defaultModel: defaultModel ?? null, models }, null, 2));
2739
+ return;
2740
+ }
2741
+ out.log("provider model default capabilities");
2742
+ for (const model of models) {
2743
+ out.log([model.provider, model.id, model.id === defaultModel ? "yes" : "", capabilityList(model)].join(" "));
2744
+ }
2745
+ }
2746
+ function capabilityList(model) {
2747
+ return [
2748
+ model.capabilities.imageIn ? "image" : "",
2749
+ model.capabilities.audioIn ? "audio" : "",
2750
+ model.capabilities.documentIn ? "document" : "",
2751
+ model.capabilities.toolUse ? "tools" : "",
2752
+ model.capabilities.thinking || model.capabilities.effort ? "reasoning" : "",
2753
+ model.capabilities.webSearch || model.superpowers?.webSearch ? "web-search" : ""
2754
+ ].filter(Boolean).join(",");
2755
+ }
2756
+ function isModelSpec(value2) {
2757
+ if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) return false;
2758
+ const model = value2;
2759
+ return typeof model.id === "string" && typeof model.nativeId === "string" && (model.provider === "anthropic" || model.provider === "openai" || model.provider === "google") && Boolean(model.capabilities) && typeof model.capabilities === "object";
2760
+ }
2761
+ async function safeText4(response2) {
2762
+ return (await response2.text().catch(() => "request failed")).slice(0, 300);
2763
+ }
2764
+
2707
2765
  // src/config-operation-command.ts
2708
2766
  var import_apps6 = require("@odla-ai/apps");
2709
2767
  var import_node_path8 = require("path");
@@ -2884,10 +2942,10 @@ function record2(value2) {
2884
2942
  var import_apps5 = require("@odla-ai/apps");
2885
2943
 
2886
2944
  // src/provision-helpers.ts
2887
- var import_ai = require("@odla-ai/ai");
2945
+ var import_ai2 = require("@odla-ai/ai");
2888
2946
  var import_apps4 = require("@odla-ai/apps");
2889
2947
  function defaultSecretName(provider) {
2890
- const names = import_ai.DEFAULT_SECRET_NAMES;
2948
+ const names = import_ai2.DEFAULT_SECRET_NAMES;
2891
2949
  return names[provider] ?? `${provider}_api_key`;
2892
2950
  }
2893
2951
  async function assertTenantAdminAccess(doFetch, cfg, env, token) {
@@ -2897,7 +2955,7 @@ async function assertTenantAdminAccess(doFetch, cfg, env, token) {
2897
2955
  });
2898
2956
  if (res.ok || res.status === 404) return;
2899
2957
  if (res.status === 403) {
2900
- const detail = await safeText4(res);
2958
+ const detail = await safeText5(res);
2901
2959
  const code = errorCode(detail);
2902
2960
  if (code === "human_session_required") {
2903
2961
  throw new Error(
@@ -2913,7 +2971,7 @@ async function assertTenantAdminAccess(doFetch, cfg, env, token) {
2913
2971
  `${env}: this credential lacks live app.manage authority for "${cfg.app.id}" (tenant ${tenantId}) \u2014 nothing was minted or written; run "odla-ai provision --request-grant --email <odla-account>" to open a fresh owner review. If the human account is not an owner, an existing owner must add it in signed-in Studio; an agent token cannot repair ownership`
2914
2972
  );
2915
2973
  }
2916
- throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await safeText4(res)}`);
2974
+ throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await safeText5(res)}`);
2917
2975
  }
2918
2976
  function errorCode(text2) {
2919
2977
  try {
@@ -2929,7 +2987,7 @@ async function postJson(doFetch, url, bearer, body) {
2929
2987
  headers: { authorization: `Bearer ${bearer}`, "content-type": "application/json" },
2930
2988
  body: JSON.stringify(body)
2931
2989
  });
2932
- if (!res.ok) throw new Error(`${new URL(url).pathname} failed: ${res.status} ${await safeText4(res)}`);
2990
+ if (!res.ok) throw new Error(`${new URL(url).pathname} failed: ${res.status} ${await safeText5(res)}`);
2933
2991
  }
2934
2992
  function normalizeClerkConfig(value2) {
2935
2993
  if (!value2) return null;
@@ -2942,7 +3000,7 @@ function normalizeClerkConfig(value2) {
2942
3000
  const publishableKey = envValue(cfg.publishableKey);
2943
3001
  return publishableKey ? { publishableKey, ...cfg.audience ? { audience: cfg.audience } : {}, ...cfg.mode ? { mode: cfg.mode } : {} } : null;
2944
3002
  }
2945
- async function safeText4(res) {
3003
+ async function safeText5(res) {
2946
3004
  try {
2947
3005
  return redactSecrets((await res.text()).slice(0, 500));
2948
3006
  } catch {
@@ -4263,7 +4321,7 @@ function assertWranglerConfig(cfg) {
4263
4321
  }
4264
4322
 
4265
4323
  // src/secrets-set.ts
4266
- var import_ai2 = require("@odla-ai/ai");
4324
+ var import_ai3 = require("@odla-ai/ai");
4267
4325
  var import_apps10 = require("@odla-ai/apps");
4268
4326
  var PROD_ENV_NAMES2 = /* @__PURE__ */ new Set(["prod", "production"]);
4269
4327
  async function secretsSet(options) {
@@ -4277,7 +4335,7 @@ async function secretsSet(options) {
4277
4335
  optionalProjectCapabilities: ["app.manage"]
4278
4336
  });
4279
4337
  try {
4280
- await (0, import_ai2.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, name, value2);
4338
+ await (0, import_ai3.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, name, value2);
4281
4339
  } catch (err) {
4282
4340
  throw new Error(scrubValue(err instanceof Error ? err.message : String(err), value2));
4283
4341
  }
@@ -4712,7 +4770,7 @@ async function getJson(doFetch, url, bearer) {
4712
4770
  const res = await doFetch(url, {
4713
4771
  headers: bearer ? { authorization: `Bearer ${bearer}` } : void 0
4714
4772
  });
4715
- if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await safeText5(res)}`);
4773
+ if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await safeText6(res)}`);
4716
4774
  return res.json();
4717
4775
  }
4718
4776
  async function postJson2(doFetch, url, bearer, body) {
@@ -4721,7 +4779,7 @@ async function postJson2(doFetch, url, bearer, body) {
4721
4779
  headers: { authorization: `Bearer ${bearer}`, "content-type": "application/json" },
4722
4780
  body: JSON.stringify(body)
4723
4781
  });
4724
- if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await safeText5(res)}`);
4782
+ if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await safeText6(res)}`);
4725
4783
  return res.json();
4726
4784
  }
4727
4785
  function publicConfigUrl(platformUrl, appId, env) {
@@ -4729,7 +4787,7 @@ function publicConfigUrl(platformUrl, appId, env) {
4729
4787
  url.searchParams.set("env", env);
4730
4788
  return url.toString();
4731
4789
  }
4732
- async function safeText5(res) {
4790
+ async function safeText6(res) {
4733
4791
  try {
4734
4792
  return redactSecrets((await res.text()).slice(0, 500));
4735
4793
  } catch {
@@ -4785,6 +4843,20 @@ async function secretsCommand(parsed, deps) {
4785
4843
  });
4786
4844
  }
4787
4845
  async function projectCommand(command, parsed, deps) {
4846
+ if (command === "ai") {
4847
+ const sub = parsed.positionals[1];
4848
+ if (sub !== "models") throw new Error(`unknown ai subcommand "${sub ?? ""}". Try "odla-ai ai models --env dev".`);
4849
+ assertArgs(parsed, ["config", "env", "provider", "json"], 2);
4850
+ await aiModels({
4851
+ configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
4852
+ env: stringOpt(parsed.options.env),
4853
+ provider: stringOpt(parsed.options.provider),
4854
+ json: parsed.options.json === true,
4855
+ fetch: deps.fetch,
4856
+ stdout: deps.stdout
4857
+ });
4858
+ return true;
4859
+ }
4788
4860
  if (command === "config") {
4789
4861
  const sub = parsed.positionals[1];
4790
4862
  if (sub !== "diff" && sub !== "plan" && sub !== "apply") {
@@ -8561,6 +8633,7 @@ Usage:
8561
8633
  odla-ai setup [--dir <project>] [--agent <name>] [--global] [--force]
8562
8634
  odla-ai init --app-id <id> --name <name> [--services db,ai,o11y,calendar] [--env dev --env prod] [--ai-provider <byok-provider>]
8563
8635
  odla-ai doctor [--config odla.config.mjs]
8636
+ odla-ai ai models [--config odla.config.mjs] [--env dev] [--provider <id>] [--json]
8564
8637
  odla-ai config <diff|plan> [--config odla.config.mjs] [--email <odla-account>] [--json]
8565
8638
  odla-ai config apply --plan <plan.json> [--idempotency-key <key>] [--email <odla-account>] [--json]
8566
8639
  odla-ai operations get <operation-id> [--json]
@@ -10550,7 +10623,7 @@ async function read2(url, headers, doFetch) {
10550
10623
 
10551
10624
  // src/provision.ts
10552
10625
  var import_apps12 = require("@odla-ai/apps");
10553
- var import_ai3 = require("@odla-ai/ai");
10626
+ var import_ai4 = require("@odla-ai/ai");
10554
10627
  var import_node_process11 = __toESM(require("process"), 1);
10555
10628
 
10556
10629
  // src/integration-provision.ts
@@ -10664,14 +10737,14 @@ async function mintDbKey(opts, tenantId) {
10664
10737
  appId: tenantId
10665
10738
  })
10666
10739
  });
10667
- if (!created.ok) throw new Error(`db app create (${tenantId}) failed: ${created.status} ${await safeText6(created)}`);
10740
+ if (!created.ok) throw new Error(`db app create (${tenantId}) failed: ${created.status} ${await safeText7(created)}`);
10668
10741
  res = await opts.fetch(`${opts.cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenantId)}/keys`, {
10669
10742
  method: "POST",
10670
10743
  headers,
10671
10744
  body: "{}"
10672
10745
  });
10673
10746
  }
10674
- if (!res.ok) throw new Error(`db key mint (${tenantId}) failed: ${res.status} ${await safeText6(res)}`);
10747
+ if (!res.ok) throw new Error(`db key mint (${tenantId}) failed: ${res.status} ${await safeText7(res)}`);
10675
10748
  const body = await res.json();
10676
10749
  if (!body.key) throw new Error(`db key mint (${tenantId}) returned no key`);
10677
10750
  return body.key;
@@ -10687,12 +10760,12 @@ async function issueO11yToken(opts) {
10687
10760
  `o11y token already exists for env "${opts.env}", but its shown-once value is not in the local credentials file; run "odla-ai provision --rotate-o11y-token --push-secrets" to replace it explicitly`
10688
10761
  );
10689
10762
  }
10690
- if (!res.ok) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) failed: ${res.status} ${await safeText6(res)}`);
10763
+ if (!res.ok) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) failed: ${res.status} ${await safeText7(res)}`);
10691
10764
  const body = await res.json();
10692
10765
  if (!body.token) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) returned no token`);
10693
10766
  return body.token;
10694
10767
  }
10695
- async function safeText6(res) {
10768
+ async function safeText7(res) {
10696
10769
  try {
10697
10770
  return redactSecrets((await res.text()).slice(0, 500));
10698
10771
  } catch {
@@ -10874,7 +10947,7 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
10874
10947
  const key = import_node_process11.default.env[cfg.ai.keyEnv];
10875
10948
  if (key) {
10876
10949
  const secretName = cfg.ai.secretName ?? defaultSecretName(cfg.ai.provider);
10877
- await (0, import_ai3.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, secretName, key);
10950
+ await (0, import_ai4.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, secretName, key);
10878
10951
  out.log(`${env}: ${cfg.ai.provider} key stored in vault (${secretName})`);
10879
10952
  } else {
10880
10953
  out.log(`${env}: ${cfg.ai.keyEnv} not set; skipped provider key storage`);
@@ -10947,6 +11020,7 @@ var PM_ENTITIES = {
10947
11020
  };
10948
11021
  var COMMAND_SURFACE = {
10949
11022
  agent: { jobs: {}, retry: {} },
11023
+ ai: { models: {} },
10950
11024
  admin: {
10951
11025
  ai: {
10952
11026
  show: {},
@@ -12973,6 +13047,7 @@ async function calendarCommand(parsed, dependencies) {
12973
13047
  SYSTEM_AI_PURPOSES,
12974
13048
  acceptedAfter,
12975
13049
  adminAi,
13050
+ aiModels,
12976
13051
  calendarBookingPageUrl,
12977
13052
  calendarCalendars,
12978
13053
  calendarConnect,