@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.
- package/README.md +5 -1
- package/dist/bin.cjs +102 -20
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-MR5QXX3B.js → chunk-3WVVHH3Y.js} +86 -12
- package/dist/chunk-3WVVHH3Y.js.map +1 -0
- package/dist/{cli-2RZFZRRT.js → cli-DCSVQAZ6.js} +2 -2
- package/dist/index.cjs +92 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -1
- package/dist/index.d.ts +14 -1
- package/dist/index.js +3 -1
- package/dist/runtime/pi-agent.js +15 -314577
- package/package.json +2 -2
- package/dist/chunk-MR5QXX3B.js.map +0 -1
- /package/dist/{cli-2RZFZRRT.js.map → cli-DCSVQAZ6.js.map} +0 -0
package/README.md
CHANGED
|
@@ -344,6 +344,7 @@ npx odla-ai whoami
|
|
|
344
344
|
npx odla-ai setup
|
|
345
345
|
npx odla-ai init --app-id my-app --name "My App"
|
|
346
346
|
npx odla-ai doctor
|
|
347
|
+
npx odla-ai ai models --env dev # exact models this app may select; no admin grant
|
|
347
348
|
npx odla-ai config diff --json
|
|
348
349
|
npx odla-ai config plan --json > .odla/config-plan.json
|
|
349
350
|
npx odla-ai config apply --plan .odla/config-plan.json --json
|
|
@@ -753,7 +754,10 @@ otherwise. It never follows an arbitrary shell working directory. Studio display
|
|
|
753
754
|
exact scope, only an admin can approve it, and it expires after ~15 minutes;
|
|
754
755
|
policy, credential, and usage capabilities are separate and cannot act as the
|
|
755
756
|
approver on ordinary app/db/o11y routes. `admin ai models` reads the server
|
|
756
|
-
catalog.
|
|
757
|
+
catalog. For app code and provisioning, `ai models [--env dev] [--json]` reads
|
|
758
|
+
the app-safe public configuration and lists only the hosted models that exact
|
|
759
|
+
app may select (or the configured provider's local catalog in BYOK mode), with
|
|
760
|
+
the default and generic capabilities. `admin ai set security` updates discovery and independent validation
|
|
757
761
|
together with expected revisions; a concurrent edit returns 409 and reloads
|
|
758
762
|
instead of being overwritten. `admin ai usage` requests its own read-only
|
|
759
763
|
capability and lists metadata-only events and status aggregates; narrow it with
|
package/dist/bin.cjs
CHANGED
|
@@ -3035,6 +3035,71 @@ var init_capabilities = __esm({
|
|
|
3035
3035
|
}
|
|
3036
3036
|
});
|
|
3037
3037
|
|
|
3038
|
+
// src/ai-models.ts
|
|
3039
|
+
async function aiModels(options = {}) {
|
|
3040
|
+
const cfg = await loadProjectConfig(options.configPath ?? "odla.config.mjs");
|
|
3041
|
+
const env = options.env ?? cfg.envs[0] ?? "dev";
|
|
3042
|
+
if (!cfg.envs.includes(env)) throw new Error(`ai models env "${env}" is not declared in config envs`);
|
|
3043
|
+
const url = new URL(`/registry/apps/${encodeURIComponent(cfg.app.id)}/public-config`, cfg.platformUrl);
|
|
3044
|
+
url.searchParams.set("env", env);
|
|
3045
|
+
const response2 = await (options.fetch ?? fetch)(url);
|
|
3046
|
+
if (!response2.ok) throw new Error(`read app AI models failed (${response2.status}): ${await safeText4(response2)}`);
|
|
3047
|
+
const body = await response2.json();
|
|
3048
|
+
if (!body.ai) throw new Error(`ai is not configured for ${cfg.app.id}/${env}`);
|
|
3049
|
+
const mode = body.ai.mode === "hosted" ? "hosted" : "byok";
|
|
3050
|
+
const defaultModel = typeof body.ai.model === "string" ? body.ai.model : void 0;
|
|
3051
|
+
let models;
|
|
3052
|
+
if (mode === "hosted") {
|
|
3053
|
+
if (body.ai.enabled !== true) throw new Error(`hosted ai is disabled for ${cfg.app.id}/${env}`);
|
|
3054
|
+
if (!Array.isArray(body.ai.models) || !body.ai.models.every(isModelSpec)) {
|
|
3055
|
+
throw new Error("platform returned an invalid hosted AI model catalog");
|
|
3056
|
+
}
|
|
3057
|
+
models = body.ai.models;
|
|
3058
|
+
} else {
|
|
3059
|
+
const provider = typeof body.ai.provider === "string" ? body.ai.provider : cfg.ai?.provider;
|
|
3060
|
+
if (!provider) throw new Error(`BYOK ai has no provider for ${cfg.app.id}/${env}`);
|
|
3061
|
+
models = Object.values(import_ai.DEFAULT_CATALOG).filter((model) => model.provider === provider);
|
|
3062
|
+
}
|
|
3063
|
+
if (options.provider) models = models.filter((model) => model.provider === options.provider);
|
|
3064
|
+
models.sort((a, b) => a.provider.localeCompare(b.provider) || a.id.localeCompare(b.id));
|
|
3065
|
+
const out = options.stdout ?? console;
|
|
3066
|
+
if (options.json) {
|
|
3067
|
+
out.log(JSON.stringify({ appId: cfg.app.id, env, mode, defaultModel: defaultModel ?? null, models }, null, 2));
|
|
3068
|
+
return;
|
|
3069
|
+
}
|
|
3070
|
+
out.log("provider model default capabilities");
|
|
3071
|
+
for (const model of models) {
|
|
3072
|
+
out.log([model.provider, model.id, model.id === defaultModel ? "yes" : "", capabilityList(model)].join(" "));
|
|
3073
|
+
}
|
|
3074
|
+
}
|
|
3075
|
+
function capabilityList(model) {
|
|
3076
|
+
return [
|
|
3077
|
+
model.capabilities.imageIn ? "image" : "",
|
|
3078
|
+
model.capabilities.audioIn ? "audio" : "",
|
|
3079
|
+
model.capabilities.documentIn ? "document" : "",
|
|
3080
|
+
model.capabilities.toolUse ? "tools" : "",
|
|
3081
|
+
model.capabilities.thinking || model.capabilities.effort ? "reasoning" : "",
|
|
3082
|
+
model.capabilities.webSearch || model.superpowers?.webSearch ? "web-search" : ""
|
|
3083
|
+
].filter(Boolean).join(",");
|
|
3084
|
+
}
|
|
3085
|
+
function isModelSpec(value2) {
|
|
3086
|
+
if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) return false;
|
|
3087
|
+
const model = value2;
|
|
3088
|
+
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";
|
|
3089
|
+
}
|
|
3090
|
+
async function safeText4(response2) {
|
|
3091
|
+
return (await response2.text().catch(() => "request failed")).slice(0, 300);
|
|
3092
|
+
}
|
|
3093
|
+
var import_ai;
|
|
3094
|
+
var init_ai_models = __esm({
|
|
3095
|
+
"src/ai-models.ts"() {
|
|
3096
|
+
"use strict";
|
|
3097
|
+
init_cjs_shims();
|
|
3098
|
+
import_ai = require("@odla-ai/ai");
|
|
3099
|
+
init_config();
|
|
3100
|
+
}
|
|
3101
|
+
});
|
|
3102
|
+
|
|
3038
3103
|
// src/config-operation-error.ts
|
|
3039
3104
|
var ConfigOperationCommandError;
|
|
3040
3105
|
var init_config_operation_error = __esm({
|
|
@@ -3223,7 +3288,7 @@ var init_config_operation_validate = __esm({
|
|
|
3223
3288
|
|
|
3224
3289
|
// src/provision-helpers.ts
|
|
3225
3290
|
function defaultSecretName(provider) {
|
|
3226
|
-
const names =
|
|
3291
|
+
const names = import_ai2.DEFAULT_SECRET_NAMES;
|
|
3227
3292
|
return names[provider] ?? `${provider}_api_key`;
|
|
3228
3293
|
}
|
|
3229
3294
|
async function assertTenantAdminAccess(doFetch, cfg, env, token) {
|
|
@@ -3233,7 +3298,7 @@ async function assertTenantAdminAccess(doFetch, cfg, env, token) {
|
|
|
3233
3298
|
});
|
|
3234
3299
|
if (res.ok || res.status === 404) return;
|
|
3235
3300
|
if (res.status === 403) {
|
|
3236
|
-
const detail = await
|
|
3301
|
+
const detail = await safeText5(res);
|
|
3237
3302
|
const code = errorCode(detail);
|
|
3238
3303
|
if (code === "human_session_required") {
|
|
3239
3304
|
throw new Error(
|
|
@@ -3249,7 +3314,7 @@ async function assertTenantAdminAccess(doFetch, cfg, env, token) {
|
|
|
3249
3314
|
`${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`
|
|
3250
3315
|
);
|
|
3251
3316
|
}
|
|
3252
|
-
throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await
|
|
3317
|
+
throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await safeText5(res)}`);
|
|
3253
3318
|
}
|
|
3254
3319
|
function errorCode(text2) {
|
|
3255
3320
|
try {
|
|
@@ -3265,7 +3330,7 @@ async function postJson(doFetch, url, bearer, body) {
|
|
|
3265
3330
|
headers: { authorization: `Bearer ${bearer}`, "content-type": "application/json" },
|
|
3266
3331
|
body: JSON.stringify(body)
|
|
3267
3332
|
});
|
|
3268
|
-
if (!res.ok) throw new Error(`${new URL(url).pathname} failed: ${res.status} ${await
|
|
3333
|
+
if (!res.ok) throw new Error(`${new URL(url).pathname} failed: ${res.status} ${await safeText5(res)}`);
|
|
3269
3334
|
}
|
|
3270
3335
|
function normalizeClerkConfig(value2) {
|
|
3271
3336
|
if (!value2) return null;
|
|
@@ -3278,19 +3343,19 @@ function normalizeClerkConfig(value2) {
|
|
|
3278
3343
|
const publishableKey = envValue(cfg.publishableKey);
|
|
3279
3344
|
return publishableKey ? { publishableKey, ...cfg.audience ? { audience: cfg.audience } : {}, ...cfg.mode ? { mode: cfg.mode } : {} } : null;
|
|
3280
3345
|
}
|
|
3281
|
-
async function
|
|
3346
|
+
async function safeText5(res) {
|
|
3282
3347
|
try {
|
|
3283
3348
|
return redactSecrets((await res.text()).slice(0, 500));
|
|
3284
3349
|
} catch {
|
|
3285
3350
|
return "";
|
|
3286
3351
|
}
|
|
3287
3352
|
}
|
|
3288
|
-
var
|
|
3353
|
+
var import_ai2, import_apps4;
|
|
3289
3354
|
var init_provision_helpers = __esm({
|
|
3290
3355
|
"src/provision-helpers.ts"() {
|
|
3291
3356
|
"use strict";
|
|
3292
3357
|
init_cjs_shims();
|
|
3293
|
-
|
|
3358
|
+
import_ai2 = require("@odla-ai/ai");
|
|
3294
3359
|
import_apps4 = require("@odla-ai/apps");
|
|
3295
3360
|
init_config();
|
|
3296
3361
|
init_redact();
|
|
@@ -4745,7 +4810,7 @@ async function secretsSet(options) {
|
|
|
4745
4810
|
optionalProjectCapabilities: ["app.manage"]
|
|
4746
4811
|
});
|
|
4747
4812
|
try {
|
|
4748
|
-
await (0,
|
|
4813
|
+
await (0, import_ai3.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, name, value2);
|
|
4749
4814
|
} catch (err) {
|
|
4750
4815
|
throw new Error(scrubValue(err instanceof Error ? err.message : String(err), value2));
|
|
4751
4816
|
}
|
|
@@ -4790,12 +4855,12 @@ async function resolveVaultWrite(options) {
|
|
|
4790
4855
|
const value2 = await secretInputValue(options, "secret");
|
|
4791
4856
|
return { cfg, tenantId: (0, import_apps10.tenantIdFor)(cfg.app.id, options.env), value: value2, doFetch, out };
|
|
4792
4857
|
}
|
|
4793
|
-
var
|
|
4858
|
+
var import_ai3, import_apps10, PROD_ENV_NAMES2;
|
|
4794
4859
|
var init_secrets_set = __esm({
|
|
4795
4860
|
"src/secrets-set.ts"() {
|
|
4796
4861
|
"use strict";
|
|
4797
4862
|
init_cjs_shims();
|
|
4798
|
-
|
|
4863
|
+
import_ai3 = require("@odla-ai/ai");
|
|
4799
4864
|
import_apps10 = require("@odla-ai/apps");
|
|
4800
4865
|
init_config();
|
|
4801
4866
|
init_redact();
|
|
@@ -5207,7 +5272,7 @@ async function getJson(doFetch, url, bearer) {
|
|
|
5207
5272
|
const res = await doFetch(url, {
|
|
5208
5273
|
headers: bearer ? { authorization: `Bearer ${bearer}` } : void 0
|
|
5209
5274
|
});
|
|
5210
|
-
if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await
|
|
5275
|
+
if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await safeText6(res)}`);
|
|
5211
5276
|
return res.json();
|
|
5212
5277
|
}
|
|
5213
5278
|
async function postJson2(doFetch, url, bearer, body) {
|
|
@@ -5216,7 +5281,7 @@ async function postJson2(doFetch, url, bearer, body) {
|
|
|
5216
5281
|
headers: { authorization: `Bearer ${bearer}`, "content-type": "application/json" },
|
|
5217
5282
|
body: JSON.stringify(body)
|
|
5218
5283
|
});
|
|
5219
|
-
if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await
|
|
5284
|
+
if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await safeText6(res)}`);
|
|
5220
5285
|
return res.json();
|
|
5221
5286
|
}
|
|
5222
5287
|
function publicConfigUrl(platformUrl, appId, env) {
|
|
@@ -5224,7 +5289,7 @@ function publicConfigUrl(platformUrl, appId, env) {
|
|
|
5224
5289
|
url.searchParams.set("env", env);
|
|
5225
5290
|
return url.toString();
|
|
5226
5291
|
}
|
|
5227
|
-
async function
|
|
5292
|
+
async function safeText6(res) {
|
|
5228
5293
|
try {
|
|
5229
5294
|
return redactSecrets((await res.text()).slice(0, 500));
|
|
5230
5295
|
} catch {
|
|
@@ -5291,6 +5356,20 @@ async function secretsCommand(parsed, deps) {
|
|
|
5291
5356
|
});
|
|
5292
5357
|
}
|
|
5293
5358
|
async function projectCommand(command, parsed, deps) {
|
|
5359
|
+
if (command === "ai") {
|
|
5360
|
+
const sub = parsed.positionals[1];
|
|
5361
|
+
if (sub !== "models") throw new Error(`unknown ai subcommand "${sub ?? ""}". Try "odla-ai ai models --env dev".`);
|
|
5362
|
+
assertArgs(parsed, ["config", "env", "provider", "json"], 2);
|
|
5363
|
+
await aiModels({
|
|
5364
|
+
configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
|
|
5365
|
+
env: stringOpt(parsed.options.env),
|
|
5366
|
+
provider: stringOpt(parsed.options.provider),
|
|
5367
|
+
json: parsed.options.json === true,
|
|
5368
|
+
fetch: deps.fetch,
|
|
5369
|
+
stdout: deps.stdout
|
|
5370
|
+
});
|
|
5371
|
+
return true;
|
|
5372
|
+
}
|
|
5294
5373
|
if (command === "config") {
|
|
5295
5374
|
const sub = parsed.positionals[1];
|
|
5296
5375
|
if (sub !== "diff" && sub !== "plan" && sub !== "apply") {
|
|
@@ -5413,6 +5492,7 @@ var init_cli_project = __esm({
|
|
|
5413
5492
|
init_cjs_shims();
|
|
5414
5493
|
init_argv();
|
|
5415
5494
|
init_capabilities();
|
|
5495
|
+
init_ai_models();
|
|
5416
5496
|
init_config_operation_command();
|
|
5417
5497
|
init_config_reconcile_command();
|
|
5418
5498
|
init_doctor();
|
|
@@ -9208,6 +9288,7 @@ Usage:
|
|
|
9208
9288
|
odla-ai setup [--dir <project>] [--agent <name>] [--global] [--force]
|
|
9209
9289
|
odla-ai init --app-id <id> --name <name> [--services db,ai,o11y,calendar] [--env dev --env prod] [--ai-provider <byok-provider>]
|
|
9210
9290
|
odla-ai doctor [--config odla.config.mjs]
|
|
9291
|
+
odla-ai ai models [--config odla.config.mjs] [--env dev] [--provider <id>] [--json]
|
|
9211
9292
|
odla-ai config <diff|plan> [--config odla.config.mjs] [--email <odla-account>] [--json]
|
|
9212
9293
|
odla-ai config apply --plan <plan.json> [--idempotency-key <key>] [--email <odla-account>] [--json]
|
|
9213
9294
|
odla-ai operations get <operation-id> [--json]
|
|
@@ -11468,14 +11549,14 @@ async function mintDbKey(opts, tenantId) {
|
|
|
11468
11549
|
appId: tenantId
|
|
11469
11550
|
})
|
|
11470
11551
|
});
|
|
11471
|
-
if (!created.ok) throw new Error(`db app create (${tenantId}) failed: ${created.status} ${await
|
|
11552
|
+
if (!created.ok) throw new Error(`db app create (${tenantId}) failed: ${created.status} ${await safeText7(created)}`);
|
|
11472
11553
|
res = await opts.fetch(`${opts.cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenantId)}/keys`, {
|
|
11473
11554
|
method: "POST",
|
|
11474
11555
|
headers,
|
|
11475
11556
|
body: "{}"
|
|
11476
11557
|
});
|
|
11477
11558
|
}
|
|
11478
|
-
if (!res.ok) throw new Error(`db key mint (${tenantId}) failed: ${res.status} ${await
|
|
11559
|
+
if (!res.ok) throw new Error(`db key mint (${tenantId}) failed: ${res.status} ${await safeText7(res)}`);
|
|
11479
11560
|
const body = await res.json();
|
|
11480
11561
|
if (!body.key) throw new Error(`db key mint (${tenantId}) returned no key`);
|
|
11481
11562
|
return body.key;
|
|
@@ -11491,12 +11572,12 @@ async function issueO11yToken(opts) {
|
|
|
11491
11572
|
`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`
|
|
11492
11573
|
);
|
|
11493
11574
|
}
|
|
11494
|
-
if (!res.ok) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) failed: ${res.status} ${await
|
|
11575
|
+
if (!res.ok) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) failed: ${res.status} ${await safeText7(res)}`);
|
|
11495
11576
|
const body = await res.json();
|
|
11496
11577
|
if (!body.token) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) returned no token`);
|
|
11497
11578
|
return body.token;
|
|
11498
11579
|
}
|
|
11499
|
-
async function
|
|
11580
|
+
async function safeText7(res) {
|
|
11500
11581
|
try {
|
|
11501
11582
|
return redactSecrets((await res.text()).slice(0, 500));
|
|
11502
11583
|
} catch {
|
|
@@ -11688,7 +11769,7 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
|
|
|
11688
11769
|
const key = import_node_process11.default.env[cfg.ai.keyEnv];
|
|
11689
11770
|
if (key) {
|
|
11690
11771
|
const secretName = cfg.ai.secretName ?? defaultSecretName(cfg.ai.provider);
|
|
11691
|
-
await (0,
|
|
11772
|
+
await (0, import_ai4.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, secretName, key);
|
|
11692
11773
|
out.log(`${env}: ${cfg.ai.provider} key stored in vault (${secretName})`);
|
|
11693
11774
|
} else {
|
|
11694
11775
|
out.log(`${env}: ${cfg.ai.keyEnv} not set; skipped provider key storage`);
|
|
@@ -11724,13 +11805,13 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
|
|
|
11724
11805
|
}
|
|
11725
11806
|
}
|
|
11726
11807
|
}
|
|
11727
|
-
var import_apps12,
|
|
11808
|
+
var import_apps12, import_ai4, import_node_process11;
|
|
11728
11809
|
var init_provision = __esm({
|
|
11729
11810
|
"src/provision.ts"() {
|
|
11730
11811
|
"use strict";
|
|
11731
11812
|
init_cjs_shims();
|
|
11732
11813
|
import_apps12 = require("@odla-ai/apps");
|
|
11733
|
-
|
|
11814
|
+
import_ai4 = require("@odla-ai/ai");
|
|
11734
11815
|
import_node_process11 = __toESM(require("process"), 1);
|
|
11735
11816
|
init_config();
|
|
11736
11817
|
init_calendar();
|
|
@@ -11819,6 +11900,7 @@ var init_surface = __esm({
|
|
|
11819
11900
|
};
|
|
11820
11901
|
COMMAND_SURFACE = {
|
|
11821
11902
|
agent: { jobs: {}, retry: {} },
|
|
11903
|
+
ai: { models: {} },
|
|
11822
11904
|
admin: {
|
|
11823
11905
|
ai: {
|
|
11824
11906
|
show: {},
|