@odla-ai/cli 0.27.4 → 0.27.6
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 +18 -17
- package/dist/bin.cjs +95 -58
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-GENTB3VO.js → chunk-DQOJ4S6H.js} +96 -59
- package/dist/chunk-DQOJ4S6H.js.map +1 -0
- package/dist/index.cjs +95 -58
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -5
- package/dist/index.d.ts +7 -5
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/skills/odla/references/sdks.md +5 -4
- package/dist/chunk-GENTB3VO.js.map +0 -1
package/dist/bin.js
CHANGED
|
@@ -894,6 +894,32 @@ import { dirname as dirname3, isAbsolute as isAbsolute2, resolve as resolve2 } f
|
|
|
894
894
|
import { pathToFileURL } from "url";
|
|
895
895
|
import { appServiceDefinition, appServiceIds } from "@odla-ai/apps";
|
|
896
896
|
|
|
897
|
+
// src/ai-config-validation.ts
|
|
898
|
+
function validateAiConfig(cfg, path) {
|
|
899
|
+
if (cfg.ai === void 0) return;
|
|
900
|
+
if (!isRecord4(cfg.ai)) throw new Error(`${path}: ai must be an object`);
|
|
901
|
+
assertOnly(cfg.ai, ["mode", "provider", "model", "keyEnv", "secretName"], `${path}: ai`);
|
|
902
|
+
if (cfg.ai.mode !== void 0 && cfg.ai.mode !== "hosted" && cfg.ai.mode !== "byok") {
|
|
903
|
+
throw new Error(`${path}: ai.mode must be hosted or byok`);
|
|
904
|
+
}
|
|
905
|
+
if (cfg.ai.mode === "byok" && !safeText(cfg.ai.provider, 100)) {
|
|
906
|
+
throw new Error(`${path}: ai.provider is required when ai.mode is byok`);
|
|
907
|
+
}
|
|
908
|
+
if (cfg.ai.mode === "hosted" && (cfg.ai.provider || cfg.ai.keyEnv || cfg.ai.secretName)) {
|
|
909
|
+
throw new Error(`${path}: hosted ai cannot configure a provider, keyEnv, or secretName`);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
function assertOnly(value2, allowed, label) {
|
|
913
|
+
const extra = Object.keys(value2).find((key) => !allowed.includes(key));
|
|
914
|
+
if (extra) throw new Error(`${label}.${extra} is not supported`);
|
|
915
|
+
}
|
|
916
|
+
function isRecord4(value2) {
|
|
917
|
+
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
|
|
918
|
+
}
|
|
919
|
+
function safeText(value2, max) {
|
|
920
|
+
return typeof value2 === "string" && value2.trim().length > 0 && value2.length <= max && !/[\u0000-\u001f\u007f]/.test(value2);
|
|
921
|
+
}
|
|
922
|
+
|
|
897
923
|
// src/integration-validation.ts
|
|
898
924
|
function validateIntegrations(cfg, path, defaultServices) {
|
|
899
925
|
if (cfg.integrations === void 0) return;
|
|
@@ -901,16 +927,16 @@ function validateIntegrations(cfg, path, defaultServices) {
|
|
|
901
927
|
const ids = /* @__PURE__ */ new Set();
|
|
902
928
|
for (const [index, integration] of cfg.integrations.entries()) {
|
|
903
929
|
const at = `${path}: integrations[${index}]`;
|
|
904
|
-
if (!
|
|
930
|
+
if (!isRecord5(integration)) throw new Error(`${at} must be an object`);
|
|
905
931
|
if (!validId(integration.id)) throw new Error(`${at}.id must be lowercase letters, numbers, and hyphens`);
|
|
906
932
|
if (ids.has(integration.id)) throw new Error(`${path}: duplicate integration id "${integration.id}"`);
|
|
907
933
|
ids.add(integration.id);
|
|
908
|
-
if (!
|
|
909
|
-
if (!
|
|
910
|
-
if (integration.schema !== void 0 && (!
|
|
934
|
+
if (!safeText2(integration.title, 200)) throw new Error(`${at}.title is required`);
|
|
935
|
+
if (!safeText2(integration.npm, 200)) throw new Error(`${at}.npm is required`);
|
|
936
|
+
if (integration.schema !== void 0 && (!isRecord5(integration.schema) || !isRecord5(integration.schema.entities))) {
|
|
911
937
|
throw new Error(`${at}.schema must contain an entities object`);
|
|
912
938
|
}
|
|
913
|
-
if (integration.rules !== void 0 && !
|
|
939
|
+
if (integration.rules !== void 0 && !isRecord5(integration.rules)) throw new Error(`${at}.rules must be an object`);
|
|
914
940
|
validateSeeds(integration, at);
|
|
915
941
|
validateProbes(integration, at);
|
|
916
942
|
}
|
|
@@ -924,13 +950,13 @@ function validateSeeds(integration, at) {
|
|
|
924
950
|
const ids = /* @__PURE__ */ new Set();
|
|
925
951
|
for (const [index, seed] of integration.seeds.entries()) {
|
|
926
952
|
const sat = `${at}.seeds[${index}]`;
|
|
927
|
-
if (!
|
|
953
|
+
if (!isRecord5(seed) || !safeText2(seed.id, 200) || !safeText2(seed.ns, 200)) throw new Error(`${sat} requires id and ns`);
|
|
928
954
|
if (ids.has(seed.id)) throw new Error(`${at} has duplicate seed id "${seed.id}"`);
|
|
929
955
|
ids.add(seed.id);
|
|
930
|
-
if (!
|
|
956
|
+
if (!isRecord5(seed.key) || !safeText2(seed.key.attr, 200) || !safeText2(seed.key.value, 2048)) {
|
|
931
957
|
throw new Error(`${sat}.key requires string attr and value`);
|
|
932
958
|
}
|
|
933
|
-
if (!
|
|
959
|
+
if (!isRecord5(seed.attrs)) throw new Error(`${sat}.attrs must be an object`);
|
|
934
960
|
if (Object.hasOwn(seed.attrs, seed.key.attr) && seed.attrs[seed.key.attr] !== seed.key.value) {
|
|
935
961
|
throw new Error(`${sat}.attrs.${seed.key.attr} conflicts with its natural key`);
|
|
936
962
|
}
|
|
@@ -941,16 +967,16 @@ function validateProbes(integration, at) {
|
|
|
941
967
|
if (!Array.isArray(integration.probes)) throw new Error(`${at}.probes must be an array`);
|
|
942
968
|
for (const [index, probe] of integration.probes.entries()) {
|
|
943
969
|
const pat = `${at}.probes[${index}]`;
|
|
944
|
-
if (!
|
|
970
|
+
if (!isRecord5(probe) || !safeProbePath(probe.path)) throw new Error(`${pat}.path must be an absolute path without query or fragment`);
|
|
945
971
|
if (!Number.isInteger(probe.expectedStatus) || probe.expectedStatus < 100 || probe.expectedStatus > 599) {
|
|
946
972
|
throw new Error(`${pat}.expectedStatus must be an HTTP status`);
|
|
947
973
|
}
|
|
948
974
|
}
|
|
949
975
|
}
|
|
950
|
-
function
|
|
976
|
+
function isRecord5(value2) {
|
|
951
977
|
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
|
|
952
978
|
}
|
|
953
|
-
function
|
|
979
|
+
function safeText2(value2, max) {
|
|
954
980
|
return typeof value2 === "string" && value2.trim().length > 0 && value2.length <= max && !/[\u0000-\u001f\u007f]/.test(value2);
|
|
955
981
|
}
|
|
956
982
|
function safeProbePath(value2) {
|
|
@@ -1080,6 +1106,7 @@ function validateRawConfig(raw, path) {
|
|
|
1080
1106
|
if (cfg.services !== void 0 && (!Array.isArray(cfg.services) || cfg.services.some((service) => typeof service !== "string" || !service.trim()))) {
|
|
1081
1107
|
throw new Error(`${path}: services must be an array of non-empty names`);
|
|
1082
1108
|
}
|
|
1109
|
+
validateAiConfig(cfg, path);
|
|
1083
1110
|
validateIntegrations(cfg, path, DEFAULT_SERVICES);
|
|
1084
1111
|
}
|
|
1085
1112
|
function validateCalendarConfig(cfg, envs, services, path) {
|
|
@@ -1088,11 +1115,11 @@ function validateCalendarConfig(cfg, envs, services, path) {
|
|
|
1088
1115
|
if (enabled) throw new Error(`${path}: calendar.google is required when services includes "calendar"`);
|
|
1089
1116
|
return;
|
|
1090
1117
|
}
|
|
1091
|
-
if (!
|
|
1092
|
-
|
|
1093
|
-
if (!
|
|
1118
|
+
if (!isRecord6(cfg.calendar)) throw new Error(`${path}: calendar must be an object`);
|
|
1119
|
+
assertOnly2(cfg.calendar, ["google"], `${path}: calendar`);
|
|
1120
|
+
if (!isRecord6(cfg.calendar.google)) throw new Error(`${path}: calendar.google must be an object`);
|
|
1094
1121
|
const google = cfg.calendar.google;
|
|
1095
|
-
|
|
1122
|
+
assertOnly2(
|
|
1096
1123
|
google,
|
|
1097
1124
|
["availabilityCalendars", "calendars", "bookingCalendar", "bookingPageUrl"],
|
|
1098
1125
|
`${path}: calendar.google`
|
|
@@ -1102,7 +1129,7 @@ function validateCalendarConfig(cfg, envs, services, path) {
|
|
|
1102
1129
|
throw new Error(`${path}: calendar.google requires exactly one of availabilityCalendars or calendars (legacy)`);
|
|
1103
1130
|
}
|
|
1104
1131
|
const availability = google[availabilityKey];
|
|
1105
|
-
if (!
|
|
1132
|
+
if (!isRecord6(availability)) throw new Error(`${path}: calendar.google.${availabilityKey} must map env names to calendar ids`);
|
|
1106
1133
|
const unknownEnv = Object.keys(availability).find((env) => !envs.includes(env));
|
|
1107
1134
|
if (unknownEnv) throw new Error(`${path}: calendar.google.${availabilityKey}.${unknownEnv} is not in config envs`);
|
|
1108
1135
|
for (const env of envs) {
|
|
@@ -1113,22 +1140,22 @@ function validateCalendarConfig(cfg, envs, services, path) {
|
|
|
1113
1140
|
if (ids.length > 10) {
|
|
1114
1141
|
throw new Error(`${path}: calendar.google.${availabilityKey}.${env} must contain at most 10 calendar ids`);
|
|
1115
1142
|
}
|
|
1116
|
-
if (ids.some((id) => !
|
|
1143
|
+
if (ids.some((id) => !safeText3(id, 1024))) {
|
|
1117
1144
|
throw new Error(`${path}: calendar.google.${availabilityKey}.${env} contains an invalid calendar id`);
|
|
1118
1145
|
}
|
|
1119
1146
|
}
|
|
1120
1147
|
if (google.bookingCalendar !== void 0) {
|
|
1121
|
-
if (!
|
|
1148
|
+
if (!isRecord6(google.bookingCalendar)) throw new Error(`${path}: calendar.google.bookingCalendar must map env names to one calendar id`);
|
|
1122
1149
|
const unknownBookingEnv = Object.keys(google.bookingCalendar).find((env) => !envs.includes(env));
|
|
1123
1150
|
if (unknownBookingEnv) throw new Error(`${path}: calendar.google.bookingCalendar.${unknownBookingEnv} is not in config envs`);
|
|
1124
1151
|
for (const [env, value2] of Object.entries(google.bookingCalendar)) {
|
|
1125
|
-
if (!
|
|
1152
|
+
if (!safeText3(value2, 1024)) {
|
|
1126
1153
|
throw new Error(`${path}: calendar.google.bookingCalendar.${env} must be a calendar id`);
|
|
1127
1154
|
}
|
|
1128
1155
|
}
|
|
1129
1156
|
}
|
|
1130
1157
|
if (google.bookingPageUrl !== void 0) {
|
|
1131
|
-
if (!
|
|
1158
|
+
if (!isRecord6(google.bookingPageUrl)) throw new Error(`${path}: calendar.google.bookingPageUrl must map env names to HTTPS URLs or null`);
|
|
1132
1159
|
const unknownBookingEnv = Object.keys(google.bookingPageUrl).find((env) => !envs.includes(env));
|
|
1133
1160
|
if (unknownBookingEnv) throw new Error(`${path}: calendar.google.bookingPageUrl.${unknownBookingEnv} is not in config envs`);
|
|
1134
1161
|
for (const [env, value2] of Object.entries(google.bookingPageUrl)) {
|
|
@@ -1151,14 +1178,14 @@ function validateServices(services, path) {
|
|
|
1151
1178
|
}
|
|
1152
1179
|
}
|
|
1153
1180
|
}
|
|
1154
|
-
function
|
|
1181
|
+
function assertOnly2(value2, allowed, label) {
|
|
1155
1182
|
const extra = Object.keys(value2).find((key) => !allowed.includes(key));
|
|
1156
1183
|
if (extra) throw new Error(`${label}.${extra} is not supported`);
|
|
1157
1184
|
}
|
|
1158
|
-
function
|
|
1185
|
+
function isRecord6(value2) {
|
|
1159
1186
|
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
|
|
1160
1187
|
}
|
|
1161
|
-
function
|
|
1188
|
+
function safeText3(value2, max) {
|
|
1162
1189
|
return typeof value2 === "string" && value2.trim().length > 0 && value2.length <= max && !/[\u0000-\u001f\u007f]/.test(value2);
|
|
1163
1190
|
}
|
|
1164
1191
|
function safeHttpsUrl(value2) {
|
|
@@ -1745,7 +1772,7 @@ async function assertTenantAdminAccess(doFetch, cfg, env, token) {
|
|
|
1745
1772
|
`${env}: you are not an owner of "${cfg.app.id}" (tenant ${tenantId}) \u2014 nothing was minted or written; ask an existing owner to run "odla-ai app owners add <your-email>", then re-run provision`
|
|
1746
1773
|
);
|
|
1747
1774
|
}
|
|
1748
|
-
throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await
|
|
1775
|
+
throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await safeText4(res)}`);
|
|
1749
1776
|
}
|
|
1750
1777
|
async function postJson(doFetch, url, bearer, body) {
|
|
1751
1778
|
const res = await doFetch(url, {
|
|
@@ -1753,7 +1780,7 @@ async function postJson(doFetch, url, bearer, body) {
|
|
|
1753
1780
|
headers: { authorization: `Bearer ${bearer}`, "content-type": "application/json" },
|
|
1754
1781
|
body: JSON.stringify(body)
|
|
1755
1782
|
});
|
|
1756
|
-
if (!res.ok) throw new Error(`${new URL(url).pathname} failed: ${res.status} ${await
|
|
1783
|
+
if (!res.ok) throw new Error(`${new URL(url).pathname} failed: ${res.status} ${await safeText4(res)}`);
|
|
1757
1784
|
}
|
|
1758
1785
|
function normalizeClerkConfig(value2) {
|
|
1759
1786
|
if (!value2) return null;
|
|
@@ -1766,7 +1793,7 @@ function normalizeClerkConfig(value2) {
|
|
|
1766
1793
|
const publishableKey = envValue(cfg.publishableKey);
|
|
1767
1794
|
return publishableKey ? { publishableKey, ...cfg.audience ? { audience: cfg.audience } : {}, ...cfg.mode ? { mode: cfg.mode } : {} } : null;
|
|
1768
1795
|
}
|
|
1769
|
-
async function
|
|
1796
|
+
async function safeText4(res) {
|
|
1770
1797
|
try {
|
|
1771
1798
|
return redactSecrets((await res.text()).slice(0, 500));
|
|
1772
1799
|
} catch {
|
|
@@ -2917,18 +2944,18 @@ function assertSeedContracts(integrations, schema) {
|
|
|
2917
2944
|
}
|
|
2918
2945
|
}
|
|
2919
2946
|
function isUniqueAttr(schema, ns, attr) {
|
|
2920
|
-
if (!
|
|
2947
|
+
if (!isRecord7(schema) || !isRecord7(schema.entities)) return false;
|
|
2921
2948
|
const entity = schema.entities[ns];
|
|
2922
|
-
if (!
|
|
2949
|
+
if (!isRecord7(entity) || !isRecord7(entity.attrs)) return false;
|
|
2923
2950
|
const definition = entity.attrs[attr];
|
|
2924
|
-
return
|
|
2951
|
+
return isRecord7(definition) && definition.unique === true;
|
|
2925
2952
|
}
|
|
2926
2953
|
function normalizeSchema(value2) {
|
|
2927
2954
|
if (value2 === void 0 || value2 === null) return { entities: {}, links: {} };
|
|
2928
|
-
if (!
|
|
2955
|
+
if (!isRecord7(value2) || !isRecord7(value2.entities)) {
|
|
2929
2956
|
throw new Error("db schema must be a serialized schema object with an entities map");
|
|
2930
2957
|
}
|
|
2931
|
-
if (value2.links !== void 0 && !
|
|
2958
|
+
if (value2.links !== void 0 && !isRecord7(value2.links)) throw new Error("db schema links must be an object");
|
|
2932
2959
|
return {
|
|
2933
2960
|
entities: { ...value2.entities },
|
|
2934
2961
|
links: { ...value2.links ?? {} }
|
|
@@ -2942,7 +2969,7 @@ function mergeMap(target, fragment, label) {
|
|
|
2942
2969
|
target[name] = value2;
|
|
2943
2970
|
}
|
|
2944
2971
|
}
|
|
2945
|
-
function
|
|
2972
|
+
function isRecord7(value2) {
|
|
2946
2973
|
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
|
|
2947
2974
|
}
|
|
2948
2975
|
|
|
@@ -2961,7 +2988,7 @@ async function doctor(options) {
|
|
|
2961
2988
|
out.log(`integrations: ${plan.integrations.length ? plan.integrations.join(", ") : "none"}`);
|
|
2962
2989
|
out.log(`schema: ${schema ? `${entities.length} entities` : "none"}`);
|
|
2963
2990
|
out.log(`rules: ${rules ? `${Object.keys(rules).length} namespaces` : "none"}`);
|
|
2964
|
-
out.log(`ai: ${cfg.services.includes("ai") ? cfg.ai?.provider
|
|
2991
|
+
out.log(`ai: ${cfg.services.includes("ai") ? cfg.ai?.provider ? `byok/${cfg.ai.provider}` : "hosted" : "not enabled"}`);
|
|
2965
2992
|
if (cfg.services.includes("calendar")) {
|
|
2966
2993
|
const calendar = cfg.envs.map((env) => {
|
|
2967
2994
|
const resolved = calendarServiceConfig(cfg, env);
|
|
@@ -2982,7 +3009,9 @@ async function doctor(options) {
|
|
|
2982
3009
|
}
|
|
2983
3010
|
}
|
|
2984
3011
|
warnings.push(...integrationWarnings(database.integrations, schema, rules));
|
|
2985
|
-
if (cfg.services.includes("ai") &&
|
|
3012
|
+
if (cfg.services.includes("ai") && cfg.ai?.mode === "byok" && !cfg.ai.provider) {
|
|
3013
|
+
warnings.push("ai.mode is byok but ai.provider is not set");
|
|
3014
|
+
}
|
|
2986
3015
|
if (cfg.auth?.clerk) {
|
|
2987
3016
|
for (const [env, value2] of Object.entries(cfg.auth.clerk)) {
|
|
2988
3017
|
if (typeof value2 === "string" && value2.startsWith("$") && !process.env[value2.slice(1)]) {
|
|
@@ -3043,7 +3072,7 @@ function initProject(options) {
|
|
|
3043
3072
|
if (!services.includes(dependency)) throw new Error(`--services ${service} requires ${dependency}`);
|
|
3044
3073
|
}
|
|
3045
3074
|
}
|
|
3046
|
-
const aiProvider = options.aiProvider
|
|
3075
|
+
const aiProvider = options.aiProvider;
|
|
3047
3076
|
mkdirSync2(dirname4(configPath), { recursive: true });
|
|
3048
3077
|
mkdirSync2(resolve4(rootDir, "src/odla"), { recursive: true });
|
|
3049
3078
|
mkdirSync2(resolve4(rootDir, ".odla"), { recursive: true });
|
|
@@ -3070,6 +3099,15 @@ function configTemplate(input) {
|
|
|
3070
3099
|
},
|
|
3071
3100
|
},
|
|
3072
3101
|
` : "";
|
|
3102
|
+
const ai = input.aiProvider ? ` ai: {
|
|
3103
|
+
mode: "byok",
|
|
3104
|
+
provider: process.env.ODLA_AI_PROVIDER ?? "${input.aiProvider}",
|
|
3105
|
+
// Optional: set this env var while running provision to store the provider
|
|
3106
|
+
// key in the app vault for each tenant.
|
|
3107
|
+
keyEnv: "${defaultKeyEnv(input.aiProvider)}",
|
|
3108
|
+
},` : ` // Hosted AI uses admin-approved models and central cost tracking.
|
|
3109
|
+
// Pass --ai-provider during init only when this app must use BYOK.
|
|
3110
|
+
ai: { mode: "hosted" },`;
|
|
3073
3111
|
return `export default {
|
|
3074
3112
|
platformUrl: process.env.ODLA_PLATFORM_URL ?? "https://odla.ai",
|
|
3075
3113
|
dbEndpoint: process.env.ODLA_ENDPOINT ?? process.env.ODLA_DB_ENDPOINT ?? "https://db.odla.ai",
|
|
@@ -3088,12 +3126,7 @@ function configTemplate(input) {
|
|
|
3088
3126
|
// When rules is omitted, the CLI generates deny-all rules from schema.
|
|
3089
3127
|
defaultRules: "deny",
|
|
3090
3128
|
},
|
|
3091
|
-
|
|
3092
|
-
provider: process.env.ODLA_AI_PROVIDER ?? "${input.aiProvider}",
|
|
3093
|
-
// Optional: set this env var while running provision to store the provider
|
|
3094
|
-
// key in the platform vault for each tenant.
|
|
3095
|
-
keyEnv: "${defaultKeyEnv(input.aiProvider)}",
|
|
3096
|
-
},
|
|
3129
|
+
${ai}
|
|
3097
3130
|
${calendar}
|
|
3098
3131
|
auth: {
|
|
3099
3132
|
clerk: {
|
|
@@ -3589,12 +3622,16 @@ async function smoke(options) {
|
|
|
3589
3622
|
out.log(` tenant: ${entry.tenantId}`);
|
|
3590
3623
|
const publicConfig = await getJson(doFetch, publicConfigUrl(cfg.platformUrl, cfg.app.id, env), void 0);
|
|
3591
3624
|
out.log(` public-config: ok`);
|
|
3592
|
-
if (cfg.ai?.provider) {
|
|
3625
|
+
if (cfg.services.includes("ai") && cfg.ai?.provider) {
|
|
3593
3626
|
const provider = publicConfig.ai?.provider ?? null;
|
|
3594
3627
|
if (provider !== cfg.ai.provider) {
|
|
3595
3628
|
throw new Error(`ai provider mismatch: expected "${cfg.ai.provider}", public-config has "${provider ?? "none"}"`);
|
|
3596
3629
|
}
|
|
3597
|
-
out.log(` ai:
|
|
3630
|
+
out.log(` ai: byok/${provider}`);
|
|
3631
|
+
} else if (cfg.services.includes("ai")) {
|
|
3632
|
+
const mode = publicConfig.ai?.mode;
|
|
3633
|
+
if (mode !== "hosted") throw new Error(`ai mode mismatch: expected "hosted", public-config has "${String(mode ?? "none")}"`);
|
|
3634
|
+
out.log(" ai: hosted");
|
|
3598
3635
|
}
|
|
3599
3636
|
if (hasO11y) out.log(` o11y: credentials present`);
|
|
3600
3637
|
if (cfg.services.includes("calendar")) {
|
|
@@ -3672,7 +3709,7 @@ async function getJson(doFetch, url, bearer) {
|
|
|
3672
3709
|
const res = await doFetch(url, {
|
|
3673
3710
|
headers: bearer ? { authorization: `Bearer ${bearer}` } : void 0
|
|
3674
3711
|
});
|
|
3675
|
-
if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await
|
|
3712
|
+
if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await safeText5(res)}`);
|
|
3676
3713
|
return res.json();
|
|
3677
3714
|
}
|
|
3678
3715
|
async function postJson2(doFetch, url, bearer, body) {
|
|
@@ -3681,7 +3718,7 @@ async function postJson2(doFetch, url, bearer, body) {
|
|
|
3681
3718
|
headers: { authorization: `Bearer ${bearer}`, "content-type": "application/json" },
|
|
3682
3719
|
body: JSON.stringify(body)
|
|
3683
3720
|
});
|
|
3684
|
-
if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await
|
|
3721
|
+
if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await safeText5(res)}`);
|
|
3685
3722
|
return res.json();
|
|
3686
3723
|
}
|
|
3687
3724
|
function publicConfigUrl(platformUrl, appId, env) {
|
|
@@ -3689,7 +3726,7 @@ function publicConfigUrl(platformUrl, appId, env) {
|
|
|
3689
3726
|
url.searchParams.set("env", env);
|
|
3690
3727
|
return url.toString();
|
|
3691
3728
|
}
|
|
3692
|
-
async function
|
|
3729
|
+
async function safeText5(res) {
|
|
3693
3730
|
try {
|
|
3694
3731
|
return redactSecrets((await res.text()).slice(0, 500));
|
|
3695
3732
|
} catch {
|
|
@@ -7160,7 +7197,7 @@ async function provisionIntegrationSeeds(doFetch, endpoint, tenantId, dbKey, int
|
|
|
7160
7197
|
const payload = await postJson3(doFetch, `${base}/query`, dbKey, {
|
|
7161
7198
|
query: { [seed.ns]: { $: { where: { [seed.key.attr]: seed.key.value }, limit: 1 } } }
|
|
7162
7199
|
});
|
|
7163
|
-
const rows =
|
|
7200
|
+
const rows = isRecord8(payload) && isRecord8(payload.result) ? payload.result[seed.ns] : void 0;
|
|
7164
7201
|
if (!Array.isArray(rows)) {
|
|
7165
7202
|
throw new Error(`${env}: integration ${integration.id} seed ${seed.id} query returned an invalid response`);
|
|
7166
7203
|
}
|
|
@@ -7192,7 +7229,7 @@ async function postJson3(doFetch, url, bearer, body) {
|
|
|
7192
7229
|
if (!res.ok) throw new Error(`${new URL(url).pathname} failed: ${res.status} ${await responseText(res)}`);
|
|
7193
7230
|
return res.json().catch(() => ({}));
|
|
7194
7231
|
}
|
|
7195
|
-
function
|
|
7232
|
+
function isRecord8(value2) {
|
|
7196
7233
|
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
|
|
7197
7234
|
}
|
|
7198
7235
|
async function responseText(res) {
|
|
@@ -7262,14 +7299,14 @@ async function mintDbKey(opts, tenantId) {
|
|
|
7262
7299
|
appId: tenantId
|
|
7263
7300
|
})
|
|
7264
7301
|
});
|
|
7265
|
-
if (!created.ok) throw new Error(`db app create (${tenantId}) failed: ${created.status} ${await
|
|
7302
|
+
if (!created.ok) throw new Error(`db app create (${tenantId}) failed: ${created.status} ${await safeText6(created)}`);
|
|
7266
7303
|
res = await opts.fetch(`${opts.cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenantId)}/keys`, {
|
|
7267
7304
|
method: "POST",
|
|
7268
7305
|
headers,
|
|
7269
7306
|
body: "{}"
|
|
7270
7307
|
});
|
|
7271
7308
|
}
|
|
7272
|
-
if (!res.ok) throw new Error(`db key mint (${tenantId}) failed: ${res.status} ${await
|
|
7309
|
+
if (!res.ok) throw new Error(`db key mint (${tenantId}) failed: ${res.status} ${await safeText6(res)}`);
|
|
7273
7310
|
const body = await res.json();
|
|
7274
7311
|
if (!body.key) throw new Error(`db key mint (${tenantId}) returned no key`);
|
|
7275
7312
|
return body.key;
|
|
@@ -7285,12 +7322,12 @@ async function issueO11yToken(opts) {
|
|
|
7285
7322
|
`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`
|
|
7286
7323
|
);
|
|
7287
7324
|
}
|
|
7288
|
-
if (!res.ok) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) failed: ${res.status} ${await
|
|
7325
|
+
if (!res.ok) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) failed: ${res.status} ${await safeText6(res)}`);
|
|
7289
7326
|
const body = await res.json();
|
|
7290
7327
|
if (!body.token) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) returned no token`);
|
|
7291
7328
|
return body.token;
|
|
7292
7329
|
}
|
|
7293
|
-
async function
|
|
7330
|
+
async function safeText6(res) {
|
|
7294
7331
|
try {
|
|
7295
7332
|
return redactSecrets((await res.text()).slice(0, 500));
|
|
7296
7333
|
} catch {
|
|
@@ -7333,7 +7370,7 @@ async function provision(options) {
|
|
|
7333
7370
|
const namespaces = Object.keys(integration.schema?.entities ?? {}).length;
|
|
7334
7371
|
out.log(` integration.${integration.id}: ${namespaces} namespaces, ${integration.seeds?.length ?? 0} seeds, ${integration.probes?.length ?? 0} smoke probes`);
|
|
7335
7372
|
}
|
|
7336
|
-
out.log(` ai: ${cfg.services.includes("ai") ? cfg.ai?.provider ?? "
|
|
7373
|
+
out.log(` ai: ${cfg.services.includes("ai") ? cfg.ai?.provider ?? "hosted" : "not enabled"}`);
|
|
7337
7374
|
if (cfg.services.includes("calendar")) {
|
|
7338
7375
|
for (const env of cfg.envs) {
|
|
7339
7376
|
const calendar = calendarServiceConfig(cfg, env);
|
|
@@ -7384,11 +7421,11 @@ async function provision(options) {
|
|
|
7384
7421
|
for (const service of serviceOrder) {
|
|
7385
7422
|
if (service === "ai") {
|
|
7386
7423
|
if (cfg.ai?.provider) {
|
|
7387
|
-
await apps.setAi(cfg.app.id, env, { provider: cfg.ai.provider, ...cfg.ai.model ? { model: cfg.ai.model } : {} });
|
|
7388
|
-
out.log(`${env}: ai configured (${cfg.ai.provider}${cfg.ai.model ? `/${cfg.ai.model}` : ""})`);
|
|
7424
|
+
await apps.setAi(cfg.app.id, env, { mode: "byok", provider: cfg.ai.provider, ...cfg.ai.model ? { model: cfg.ai.model } : {} });
|
|
7425
|
+
out.log(`${env}: ai configured (byok ${cfg.ai.provider}${cfg.ai.model ? `/${cfg.ai.model}` : ""})`);
|
|
7389
7426
|
} else {
|
|
7390
|
-
await apps.
|
|
7391
|
-
out.log(`${env}: ai
|
|
7427
|
+
await apps.setAi(cfg.app.id, env, { mode: "hosted", ...cfg.ai?.model ? { model: cfg.ai.model } : {} });
|
|
7428
|
+
out.log(`${env}: ai configured (hosted${cfg.ai?.model ? `/${cfg.ai.model}` : ""})`);
|
|
7392
7429
|
}
|
|
7393
7430
|
} else {
|
|
7394
7431
|
const config = service === "calendar" ? calendarServiceConfig(cfg, env) : void 0;
|
|
@@ -9461,7 +9498,7 @@ Start here:
|
|
|
9461
9498
|
|
|
9462
9499
|
Usage:
|
|
9463
9500
|
odla-ai setup [--dir <project>] [--agent <name>] [--global] [--force]
|
|
9464
|
-
odla-ai init --app-id <id> --name <name> [--services db,ai,o11y,calendar] [--env dev --env prod]
|
|
9501
|
+
odla-ai init --app-id <id> --name <name> [--services db,ai,o11y,calendar] [--env dev --env prod] [--ai-provider <byok-provider>]
|
|
9465
9502
|
odla-ai doctor [--config odla.config.mjs]
|
|
9466
9503
|
odla-ai config <diff|plan> [--config odla.config.mjs] [--email <odla-account>] [--json]
|
|
9467
9504
|
odla-ai config apply --plan <plan.json> [--idempotency-key <key>] [--email <odla-account>] [--json]
|
|
@@ -13124,4 +13161,4 @@ export {
|
|
|
13124
13161
|
exitCodeFor,
|
|
13125
13162
|
runCli
|
|
13126
13163
|
};
|
|
13127
|
-
//# sourceMappingURL=chunk-
|
|
13164
|
+
//# sourceMappingURL=chunk-DQOJ4S6H.js.map
|