@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/README.md +16 -8
- package/dist/bin.cjs +428 -405
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-B6DISJPC.js → chunk-ONETPJFW.js} +305 -282
- package/dist/chunk-ONETPJFW.js.map +1 -0
- package/dist/index.cjs +428 -405
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-B6DISJPC.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1019,71 +1019,16 @@ function addOption(options, name, value) {
|
|
|
1019
1019
|
else options[name] = [String(current), String(value)];
|
|
1020
1020
|
}
|
|
1021
1021
|
|
|
1022
|
-
// src/
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
const credentialSet = action === "credential" && parsed.positionals[3] === "set";
|
|
1027
|
-
const credentials = action === "credentials";
|
|
1028
|
-
const models = action === "models";
|
|
1029
|
-
const usage = action === "usage";
|
|
1030
|
-
const audit = action === "audit";
|
|
1031
|
-
if (area !== "ai" || action !== "show" && action !== "set" && !credentialSet && !credentials && !models && !usage && !audit) {
|
|
1032
|
-
throw new Error('unknown admin command. Try "odla-ai admin ai show".');
|
|
1033
|
-
}
|
|
1034
|
-
assertArgs(parsed, [
|
|
1035
|
-
"platform",
|
|
1036
|
-
"json",
|
|
1037
|
-
"open",
|
|
1038
|
-
"email",
|
|
1039
|
-
"provider",
|
|
1040
|
-
"model",
|
|
1041
|
-
"enabled",
|
|
1042
|
-
"max-input-bytes",
|
|
1043
|
-
"max-output-tokens",
|
|
1044
|
-
"max-calls-per-run",
|
|
1045
|
-
"from-env",
|
|
1046
|
-
"stdin",
|
|
1047
|
-
"discovery-provider",
|
|
1048
|
-
"discovery-model",
|
|
1049
|
-
"validation-provider",
|
|
1050
|
-
"validation-model",
|
|
1051
|
-
"app-id",
|
|
1052
|
-
"env",
|
|
1053
|
-
"run-id",
|
|
1054
|
-
"limit"
|
|
1055
|
-
], credentialSet ? 5 : action === "set" ? 4 : 3);
|
|
1056
|
-
await adminAi({
|
|
1057
|
-
action: credentialSet ? "credential-set" : credentials ? "credentials" : models ? "models" : usage ? "usage" : audit ? "audit" : action,
|
|
1058
|
-
purpose: action === "set" ? parsed.positionals[3] : void 0,
|
|
1059
|
-
provider: stringOpt(parsed.options.provider),
|
|
1060
|
-
model: stringOpt(parsed.options.model),
|
|
1061
|
-
enabled: boolOpt(parsed.options.enabled),
|
|
1062
|
-
maxInputBytes: numberOpt(parsed.options["max-input-bytes"], "--max-input-bytes"),
|
|
1063
|
-
maxOutputTokens: numberOpt(parsed.options["max-output-tokens"], "--max-output-tokens"),
|
|
1064
|
-
maxCallsPerRun: numberOpt(parsed.options["max-calls-per-run"], "--max-calls-per-run"),
|
|
1065
|
-
platform: stringOpt(parsed.options.platform),
|
|
1066
|
-
email: stringOpt(parsed.options.email),
|
|
1067
|
-
json: parsed.options.json === true,
|
|
1068
|
-
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
1069
|
-
credentialProvider: credentialSet ? parsed.positionals[4] : void 0,
|
|
1070
|
-
fromEnv: stringOpt(parsed.options["from-env"]),
|
|
1071
|
-
stdin: parsed.options.stdin === true,
|
|
1072
|
-
discoveryProvider: stringOpt(parsed.options["discovery-provider"]),
|
|
1073
|
-
discoveryModel: stringOpt(parsed.options["discovery-model"]),
|
|
1074
|
-
validationProvider: stringOpt(parsed.options["validation-provider"]),
|
|
1075
|
-
validationModel: stringOpt(parsed.options["validation-model"]),
|
|
1076
|
-
appId: stringOpt(parsed.options["app-id"]),
|
|
1077
|
-
env: stringOpt(parsed.options.env),
|
|
1078
|
-
runId: stringOpt(parsed.options["run-id"]),
|
|
1079
|
-
limit: numberOpt(parsed.options.limit, "--limit")
|
|
1080
|
-
});
|
|
1081
|
-
}
|
|
1022
|
+
// src/operator-context.ts
|
|
1023
|
+
var import_node_fs6 = require("fs");
|
|
1024
|
+
var import_node_path6 = require("path");
|
|
1025
|
+
var import_node_process8 = __toESM(require("process"), 1);
|
|
1082
1026
|
|
|
1083
1027
|
// src/config.ts
|
|
1084
1028
|
var import_node_fs4 = require("fs");
|
|
1085
1029
|
var import_node_path4 = require("path");
|
|
1086
1030
|
var import_node_url = require("url");
|
|
1031
|
+
var import_apps = require("@odla-ai/apps");
|
|
1087
1032
|
|
|
1088
1033
|
// src/integration-validation.ts
|
|
1089
1034
|
function validateIntegrations(cfg, path, defaultServices) {
|
|
@@ -1171,6 +1116,7 @@ async function loadProjectConfig(configPath = "odla.config.mjs") {
|
|
|
1171
1116
|
const dbEndpoint = trimSlash(process.env.ODLA_DB_ENDPOINT || raw.dbEndpoint || platformUrl);
|
|
1172
1117
|
const envs = unique2(raw.envs?.length ? raw.envs : DEFAULT_ENVS);
|
|
1173
1118
|
const services = unique2(raw.services?.length ? raw.services : DEFAULT_SERVICES);
|
|
1119
|
+
validateServices(services, resolved);
|
|
1174
1120
|
validateCalendarConfig(raw, envs, services, resolved);
|
|
1175
1121
|
const local = {
|
|
1176
1122
|
tokenFile: (0, import_node_path4.resolve)(rootDir, raw.local?.tokenFile ?? ".odla/dev-token.json"),
|
|
@@ -1326,7 +1272,19 @@ function validateCalendarConfig(cfg, envs, services, path) {
|
|
|
1326
1272
|
}
|
|
1327
1273
|
}
|
|
1328
1274
|
}
|
|
1329
|
-
|
|
1275
|
+
}
|
|
1276
|
+
function validateServices(services, path) {
|
|
1277
|
+
for (const service of services) {
|
|
1278
|
+
const definition = (0, import_apps.appServiceDefinition)(service);
|
|
1279
|
+
if (!definition) {
|
|
1280
|
+
throw new Error(`${path}: unknown service "${service}" (known: ${(0, import_apps.appServiceIds)().join(", ")})`);
|
|
1281
|
+
}
|
|
1282
|
+
for (const dependency of definition.requires) {
|
|
1283
|
+
if (!services.includes(dependency)) {
|
|
1284
|
+
throw new Error(`${path}: ${service} service requires the ${dependency} service`);
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1330
1288
|
}
|
|
1331
1289
|
function assertOnly(value, allowed, label) {
|
|
1332
1290
|
const extra = Object.keys(value).find((key) => !allowed.includes(key));
|
|
@@ -1364,8 +1322,293 @@ function unique2(values) {
|
|
|
1364
1322
|
return [...new Set(values.filter(Boolean))];
|
|
1365
1323
|
}
|
|
1366
1324
|
|
|
1325
|
+
// src/operator-profiles.ts
|
|
1326
|
+
var import_node_fs5 = require("fs");
|
|
1327
|
+
var import_node_os = require("os");
|
|
1328
|
+
var import_node_path5 = require("path");
|
|
1329
|
+
var import_node_process7 = __toESM(require("process"), 1);
|
|
1330
|
+
function operatorProfileFile() {
|
|
1331
|
+
return (0, import_node_path5.resolve)(
|
|
1332
|
+
clean(import_node_process7.default.env.ODLA_CONTEXT_FILE) ?? (0, import_node_path5.join)((0, import_node_os.homedir)(), ".odla", "contexts.json")
|
|
1333
|
+
);
|
|
1334
|
+
}
|
|
1335
|
+
function resolveOperatorProfile(parsed) {
|
|
1336
|
+
const fromFlag = clean(stringOpt(parsed.options.context));
|
|
1337
|
+
const fromEnvironment = clean(import_node_process7.default.env.ODLA_CONTEXT);
|
|
1338
|
+
const name = fromFlag ?? fromEnvironment ?? null;
|
|
1339
|
+
const file = operatorProfileFile();
|
|
1340
|
+
if (!name) {
|
|
1341
|
+
return { name: null, source: "unresolved", file, value: null };
|
|
1342
|
+
}
|
|
1343
|
+
assertOperatorName(name, "context");
|
|
1344
|
+
const profiles = readOperatorProfiles(file);
|
|
1345
|
+
const value = Object.hasOwn(profiles, name) ? profiles[name] : void 0;
|
|
1346
|
+
if (!value) {
|
|
1347
|
+
throw new Error(
|
|
1348
|
+
`operator context "${name}" not found in ${file}; run "odla-ai context list" or save it first`
|
|
1349
|
+
);
|
|
1350
|
+
}
|
|
1351
|
+
return {
|
|
1352
|
+
name,
|
|
1353
|
+
source: fromFlag ? "flag" : "environment",
|
|
1354
|
+
file,
|
|
1355
|
+
value
|
|
1356
|
+
};
|
|
1357
|
+
}
|
|
1358
|
+
function listOperatorProfiles(file = operatorProfileFile()) {
|
|
1359
|
+
return Object.entries(readOperatorProfiles(file)).sort(([a], [b]) => a.localeCompare(b)).map(([name, profile]) => ({ name, ...profile }));
|
|
1360
|
+
}
|
|
1361
|
+
function saveOperatorProfile(name, profile, file = operatorProfileFile()) {
|
|
1362
|
+
assertOperatorName(name, "context");
|
|
1363
|
+
const normalized = validateProfile(profile, `operator context "${name}"`);
|
|
1364
|
+
const profiles = readOperatorProfiles(file);
|
|
1365
|
+
profiles[name] = normalized;
|
|
1366
|
+
writePrivateJson(file, { schemaVersion: 1, profiles });
|
|
1367
|
+
}
|
|
1368
|
+
function removeOperatorProfile(name, file = operatorProfileFile()) {
|
|
1369
|
+
assertOperatorName(name, "context");
|
|
1370
|
+
const profiles = readOperatorProfiles(file);
|
|
1371
|
+
if (!Object.hasOwn(profiles, name)) return false;
|
|
1372
|
+
delete profiles[name];
|
|
1373
|
+
writePrivateJson(file, { schemaVersion: 1, profiles });
|
|
1374
|
+
return true;
|
|
1375
|
+
}
|
|
1376
|
+
function operatorCredentialFiles(selection) {
|
|
1377
|
+
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");
|
|
1378
|
+
return {
|
|
1379
|
+
developer: (0, import_node_path5.join)(base, "dev-token.json"),
|
|
1380
|
+
scoped: (0, import_node_path5.join)(base, "admin-token.local.json")
|
|
1381
|
+
};
|
|
1382
|
+
}
|
|
1383
|
+
function assertOperatorName(value, label) {
|
|
1384
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(value)) {
|
|
1385
|
+
throw new Error(
|
|
1386
|
+
`${label} must contain lowercase letters, numbers, and hyphens`
|
|
1387
|
+
);
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
function readOperatorProfiles(file) {
|
|
1391
|
+
if (!(0, import_node_fs5.existsSync)(file)) return emptyProfiles();
|
|
1392
|
+
let raw;
|
|
1393
|
+
try {
|
|
1394
|
+
raw = JSON.parse((0, import_node_fs5.readFileSync)(file, "utf8"));
|
|
1395
|
+
} catch {
|
|
1396
|
+
throw new Error(`operator context file ${file} is not valid JSON`);
|
|
1397
|
+
}
|
|
1398
|
+
if (!raw || typeof raw !== "object" || raw.schemaVersion !== 1 || !raw.profiles || typeof raw.profiles !== "object" || Array.isArray(raw.profiles)) {
|
|
1399
|
+
throw new Error(`operator context file ${file} has an invalid shape`);
|
|
1400
|
+
}
|
|
1401
|
+
const unknown = Object.keys(raw).filter(
|
|
1402
|
+
(key) => !["schemaVersion", "profiles"].includes(key)
|
|
1403
|
+
);
|
|
1404
|
+
if (unknown.length > 0) {
|
|
1405
|
+
throw new Error(
|
|
1406
|
+
`operator context file ${file} contains unsupported field(s): ${unknown.sort().join(", ")}`
|
|
1407
|
+
);
|
|
1408
|
+
}
|
|
1409
|
+
const profiles = emptyProfiles();
|
|
1410
|
+
for (const [name, value] of Object.entries(
|
|
1411
|
+
raw.profiles
|
|
1412
|
+
)) {
|
|
1413
|
+
assertOperatorName(name, "context");
|
|
1414
|
+
profiles[name] = validateProfile(value, `operator context "${name}"`);
|
|
1415
|
+
}
|
|
1416
|
+
return profiles;
|
|
1417
|
+
}
|
|
1418
|
+
function emptyProfiles() {
|
|
1419
|
+
return /* @__PURE__ */ Object.create(null);
|
|
1420
|
+
}
|
|
1421
|
+
function validateProfile(value, label) {
|
|
1422
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1423
|
+
throw new Error(`${label} has an invalid shape`);
|
|
1424
|
+
}
|
|
1425
|
+
const unknown = Object.keys(value).filter(
|
|
1426
|
+
(key) => !["platform", "app", "environment"].includes(key)
|
|
1427
|
+
);
|
|
1428
|
+
if (unknown.length > 0) {
|
|
1429
|
+
throw new Error(
|
|
1430
|
+
`${label} contains unsupported field(s): ${unknown.sort().join(", ")}; contexts store scope metadata only, never credentials`
|
|
1431
|
+
);
|
|
1432
|
+
}
|
|
1433
|
+
const candidate = value;
|
|
1434
|
+
if (typeof candidate.platform !== "string") {
|
|
1435
|
+
throw new Error(`${label} needs an absolute platform URL`);
|
|
1436
|
+
}
|
|
1437
|
+
const platform = platformAudience(candidate.platform);
|
|
1438
|
+
const app = clean(candidate.app);
|
|
1439
|
+
const environment2 = clean(candidate.environment);
|
|
1440
|
+
if (app) assertOperatorName(app, "app");
|
|
1441
|
+
if (environment2) assertOperatorName(environment2, "environment");
|
|
1442
|
+
return {
|
|
1443
|
+
platform,
|
|
1444
|
+
...app ? { app } : {},
|
|
1445
|
+
...environment2 ? { environment: environment2 } : {}
|
|
1446
|
+
};
|
|
1447
|
+
}
|
|
1448
|
+
function clean(value) {
|
|
1449
|
+
const normalized = value?.trim();
|
|
1450
|
+
return normalized || void 0;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
// src/operator-context.ts
|
|
1454
|
+
var DEFAULT_PLATFORM2 = "https://odla.ai";
|
|
1455
|
+
async function resolveOperatorContext(parsed, options = {}) {
|
|
1456
|
+
const profile = resolveOperatorProfile(parsed);
|
|
1457
|
+
const configArgument = stringOpt(parsed.options.config) ?? "odla.config.mjs";
|
|
1458
|
+
const configPath = (0, import_node_path6.resolve)(configArgument);
|
|
1459
|
+
const explicitConfig = parsed.options.config !== void 0;
|
|
1460
|
+
const hasConfig = (0, import_node_fs6.existsSync)(configPath);
|
|
1461
|
+
if (!hasConfig && (!options.allowMissingConfig || explicitConfig)) {
|
|
1462
|
+
await loadProjectConfig(configArgument);
|
|
1463
|
+
}
|
|
1464
|
+
const loaded = hasConfig ? await loadProjectConfig(configArgument) : void 0;
|
|
1465
|
+
const platformFlag = clean2(stringOpt(parsed.options.platform));
|
|
1466
|
+
const platformEnvironment = clean2(import_node_process8.default.env.ODLA_PLATFORM_URL);
|
|
1467
|
+
const platformValue = platformAudience(
|
|
1468
|
+
platformFlag ?? platformEnvironment ?? profile.value?.platform ?? loaded?.platformUrl ?? DEFAULT_PLATFORM2
|
|
1469
|
+
);
|
|
1470
|
+
const platformSource = platformFlag ? "flag" : platformEnvironment ? "environment" : profile.value ? "profile" : loaded ? "config" : "default";
|
|
1471
|
+
const appFlag = clean2(stringOpt(parsed.options.app));
|
|
1472
|
+
const appEnvironment = clean2(import_node_process8.default.env.ODLA_APP_ID);
|
|
1473
|
+
const appValue = appFlag ?? appEnvironment ?? profile.value?.app ?? loaded?.app.id ?? null;
|
|
1474
|
+
const appSource = appFlag ? "flag" : appEnvironment ? "environment" : profile.value?.app ? "profile" : loaded ? "config" : "unresolved";
|
|
1475
|
+
if (appValue) assertOperatorName(appValue, "app");
|
|
1476
|
+
if (options.requireApp && !appValue) {
|
|
1477
|
+
throw new Error(
|
|
1478
|
+
"app context is unresolved; pass --app <id>, set ODLA_APP_ID, select --context <name>, or run inside a project with odla.config.mjs"
|
|
1479
|
+
);
|
|
1480
|
+
}
|
|
1481
|
+
const envFlag = clean2(stringOpt(parsed.options.env));
|
|
1482
|
+
const envEnvironment = clean2(import_node_process8.default.env.ODLA_ENV);
|
|
1483
|
+
const environmentValue = envFlag ?? envEnvironment ?? profile.value?.environment ?? options.defaultEnvironment ?? null;
|
|
1484
|
+
const environmentSource = envFlag ? "flag" : envEnvironment ? "environment" : profile.value?.environment ? "profile" : options.defaultEnvironment ? "default" : "unresolved";
|
|
1485
|
+
if (environmentValue) {
|
|
1486
|
+
assertOperatorName(environmentValue, "environment");
|
|
1487
|
+
}
|
|
1488
|
+
const rootDir = loaded?.rootDir ?? import_node_process8.default.cwd();
|
|
1489
|
+
const profileCredentials = operatorCredentialFiles(profile);
|
|
1490
|
+
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;
|
|
1491
|
+
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;
|
|
1492
|
+
const cfg = loaded ? {
|
|
1493
|
+
...loaded,
|
|
1494
|
+
platformUrl: platformValue,
|
|
1495
|
+
app: appValue ? {
|
|
1496
|
+
id: appValue,
|
|
1497
|
+
name: appValue === loaded.app.id ? loaded.app.name : appValue
|
|
1498
|
+
} : loaded.app,
|
|
1499
|
+
local: { ...loaded.local, tokenFile }
|
|
1500
|
+
} : {
|
|
1501
|
+
configPath,
|
|
1502
|
+
rootDir,
|
|
1503
|
+
platformUrl: platformValue,
|
|
1504
|
+
dbEndpoint: platformValue,
|
|
1505
|
+
app: {
|
|
1506
|
+
id: appValue ?? "operator",
|
|
1507
|
+
name: appValue ?? "Operator"
|
|
1508
|
+
},
|
|
1509
|
+
envs: environmentValue ? [environmentValue] : [],
|
|
1510
|
+
services: [],
|
|
1511
|
+
local: {
|
|
1512
|
+
tokenFile,
|
|
1513
|
+
credentialsFile: (0, import_node_path6.join)(rootDir, ".odla", "credentials.local.json"),
|
|
1514
|
+
devVarsFile: (0, import_node_path6.join)(rootDir, ".dev.vars"),
|
|
1515
|
+
gitignore: true
|
|
1516
|
+
}
|
|
1517
|
+
};
|
|
1518
|
+
return {
|
|
1519
|
+
cfg,
|
|
1520
|
+
profile: {
|
|
1521
|
+
name: profile.name,
|
|
1522
|
+
source: profile.source,
|
|
1523
|
+
file: profile.file
|
|
1524
|
+
},
|
|
1525
|
+
config: {
|
|
1526
|
+
path: configPath,
|
|
1527
|
+
status: loaded ? "loaded" : "absent",
|
|
1528
|
+
explicit: explicitConfig
|
|
1529
|
+
},
|
|
1530
|
+
platform: { value: platformValue, source: platformSource },
|
|
1531
|
+
app: { value: appValue, source: appSource },
|
|
1532
|
+
environment: {
|
|
1533
|
+
value: environmentValue,
|
|
1534
|
+
source: environmentSource
|
|
1535
|
+
},
|
|
1536
|
+
credentials: {
|
|
1537
|
+
developerTokenFile: tokenFile,
|
|
1538
|
+
scopedTokenFile
|
|
1539
|
+
}
|
|
1540
|
+
};
|
|
1541
|
+
}
|
|
1542
|
+
function clean2(value) {
|
|
1543
|
+
const normalized = value?.trim();
|
|
1544
|
+
return normalized || void 0;
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
// src/admin-command.ts
|
|
1548
|
+
var CONTEXT_OPTIONS = ["platform", "config", "context", "token", "open", "email"];
|
|
1549
|
+
var JSON_OPTIONS = [...CONTEXT_OPTIONS, "json"];
|
|
1550
|
+
var SET_OPTIONS = [
|
|
1551
|
+
...JSON_OPTIONS,
|
|
1552
|
+
"provider",
|
|
1553
|
+
"model",
|
|
1554
|
+
"enabled",
|
|
1555
|
+
"max-input-bytes",
|
|
1556
|
+
"max-output-tokens",
|
|
1557
|
+
"max-calls-per-run",
|
|
1558
|
+
"discovery-provider",
|
|
1559
|
+
"discovery-model",
|
|
1560
|
+
"validation-provider",
|
|
1561
|
+
"validation-model"
|
|
1562
|
+
];
|
|
1563
|
+
async function adminCommand(parsed, deps = {}) {
|
|
1564
|
+
const area = parsed.positionals[1];
|
|
1565
|
+
const action = parsed.positionals[2];
|
|
1566
|
+
const credentialSet = action === "credential" && parsed.positionals[3] === "set";
|
|
1567
|
+
const credentials = action === "credentials";
|
|
1568
|
+
const models = action === "models";
|
|
1569
|
+
const usage = action === "usage";
|
|
1570
|
+
const audit = action === "audit";
|
|
1571
|
+
if (area !== "ai" || action !== "show" && action !== "set" && !credentialSet && !credentials && !models && !usage && !audit) {
|
|
1572
|
+
throw new Error('unknown admin command. Try "odla-ai admin ai show".');
|
|
1573
|
+
}
|
|
1574
|
+
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;
|
|
1575
|
+
assertArgs(parsed, allowed, credentialSet ? 5 : action === "set" ? 4 : 3);
|
|
1576
|
+
const context = await resolveOperatorContext(parsed, { allowMissingConfig: true });
|
|
1577
|
+
await adminAi({
|
|
1578
|
+
action: credentialSet ? "credential-set" : credentials ? "credentials" : models ? "models" : usage ? "usage" : audit ? "audit" : action,
|
|
1579
|
+
purpose: action === "set" ? parsed.positionals[3] : void 0,
|
|
1580
|
+
provider: stringOpt(parsed.options.provider),
|
|
1581
|
+
model: stringOpt(parsed.options.model),
|
|
1582
|
+
enabled: boolOpt(parsed.options.enabled),
|
|
1583
|
+
maxInputBytes: numberOpt(parsed.options["max-input-bytes"], "--max-input-bytes"),
|
|
1584
|
+
maxOutputTokens: numberOpt(parsed.options["max-output-tokens"], "--max-output-tokens"),
|
|
1585
|
+
maxCallsPerRun: numberOpt(parsed.options["max-calls-per-run"], "--max-calls-per-run"),
|
|
1586
|
+
platform: context.platform.value,
|
|
1587
|
+
token: stringOpt(parsed.options.token),
|
|
1588
|
+
tokenFile: context.credentials.scopedTokenFile,
|
|
1589
|
+
email: stringOpt(parsed.options.email),
|
|
1590
|
+
json: parsed.options.json === true,
|
|
1591
|
+
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
1592
|
+
credentialProvider: credentialSet ? parsed.positionals[4] : void 0,
|
|
1593
|
+
fromEnv: stringOpt(parsed.options["from-env"]),
|
|
1594
|
+
stdin: parsed.options.stdin === true,
|
|
1595
|
+
readStdin: deps.readStdin,
|
|
1596
|
+
discoveryProvider: stringOpt(parsed.options["discovery-provider"]),
|
|
1597
|
+
discoveryModel: stringOpt(parsed.options["discovery-model"]),
|
|
1598
|
+
validationProvider: stringOpt(parsed.options["validation-provider"]),
|
|
1599
|
+
validationModel: stringOpt(parsed.options["validation-model"]),
|
|
1600
|
+
appId: stringOpt(parsed.options["app-id"]),
|
|
1601
|
+
env: stringOpt(parsed.options.env),
|
|
1602
|
+
runId: stringOpt(parsed.options["run-id"]),
|
|
1603
|
+
limit: numberOpt(parsed.options.limit, "--limit"),
|
|
1604
|
+
fetch: deps.fetch,
|
|
1605
|
+
stdout: deps.stdout,
|
|
1606
|
+
openApprovalUrl: deps.openUrl
|
|
1607
|
+
});
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1367
1610
|
// src/tenant.ts
|
|
1368
|
-
var
|
|
1611
|
+
var import_apps2 = require("@odla-ai/apps");
|
|
1369
1612
|
function resolveEnv(cfg, requested) {
|
|
1370
1613
|
const env = requested ?? (cfg.envs.includes("dev") ? "dev" : cfg.envs[0]);
|
|
1371
1614
|
if (!env || !cfg.envs.includes(env)) {
|
|
@@ -1375,7 +1618,7 @@ function resolveEnv(cfg, requested) {
|
|
|
1375
1618
|
}
|
|
1376
1619
|
function resolveTenant(cfg, requested) {
|
|
1377
1620
|
const env = resolveEnv(cfg, requested);
|
|
1378
|
-
return { env, tenant: (0,
|
|
1621
|
+
return { env, tenant: (0, import_apps2.tenantIdFor)(cfg.app.id, env) };
|
|
1379
1622
|
}
|
|
1380
1623
|
function bothTenants(cfg) {
|
|
1381
1624
|
for (const env of ["dev", "prod"]) {
|
|
@@ -1385,7 +1628,7 @@ function bothTenants(cfg) {
|
|
|
1385
1628
|
);
|
|
1386
1629
|
}
|
|
1387
1630
|
}
|
|
1388
|
-
return { sandbox: (0,
|
|
1631
|
+
return { sandbox: (0, import_apps2.tenantIdFor)(cfg.app.id, "dev"), live: (0, import_apps2.tenantIdFor)(cfg.app.id, "prod") };
|
|
1389
1632
|
}
|
|
1390
1633
|
|
|
1391
1634
|
// src/agent-command.ts
|
|
@@ -1469,7 +1712,7 @@ function errorMessage(body) {
|
|
|
1469
1712
|
}
|
|
1470
1713
|
|
|
1471
1714
|
// src/app-export.ts
|
|
1472
|
-
var
|
|
1715
|
+
var import_node_fs7 = require("fs");
|
|
1473
1716
|
var import_node_stream = require("stream");
|
|
1474
1717
|
var import_promises = require("stream/promises");
|
|
1475
1718
|
async function appExport(options) {
|
|
@@ -1502,7 +1745,7 @@ async function appExport(options) {
|
|
|
1502
1745
|
const download = await doFetch(`${base}/backups/${newest.id}/download`, { headers: auth });
|
|
1503
1746
|
if (!download.ok || !download.body) throw new Error(`download failed (${download.status})`);
|
|
1504
1747
|
const file = options.out ?? `${tenant}-${new Date(newest.created_at).toISOString().slice(0, 10)}-tx${newest.max_tx}.jsonl.gz`;
|
|
1505
|
-
await (0, import_promises.pipeline)(import_node_stream.Readable.fromWeb(download.body), (0,
|
|
1748
|
+
await (0, import_promises.pipeline)(import_node_stream.Readable.fromWeb(download.body), (0, import_node_fs7.createWriteStream)(file));
|
|
1506
1749
|
if (options.json) out.log(JSON.stringify({ file, backup: newest }, null, 2));
|
|
1507
1750
|
else {
|
|
1508
1751
|
out.log(`${tenant}: wrote ${file} (${newest.bytes} bytes, ${newest.kind} snapshot at tx ${newest.max_tx})`);
|
|
@@ -1512,7 +1755,7 @@ async function appExport(options) {
|
|
|
1512
1755
|
}
|
|
1513
1756
|
|
|
1514
1757
|
// src/app-import.ts
|
|
1515
|
-
var
|
|
1758
|
+
var import_node_fs8 = require("fs");
|
|
1516
1759
|
var import_import = require("@odla-ai/db/import");
|
|
1517
1760
|
function chooseIdMode(options, rows) {
|
|
1518
1761
|
const chosen = [options.idField && "field", options.key && "key", options.generateIds && "generate"].filter(Boolean);
|
|
@@ -1531,7 +1774,7 @@ async function appImport(options) {
|
|
|
1531
1774
|
const say = options.json ? (line) => out.error(line) : (line) => out.log(line);
|
|
1532
1775
|
const doFetch = options.fetch ?? fetch;
|
|
1533
1776
|
const { tenant } = resolveTenant(cfg, options.env);
|
|
1534
|
-
const text = options.file === "-" ? (options.readStdin ?? (() => (0,
|
|
1777
|
+
const text = options.file === "-" ? (options.readStdin ?? (() => (0, import_node_fs8.readFileSync)(0, "utf8")))() : (0, import_node_fs8.readFileSync)(options.file, "utf8");
|
|
1535
1778
|
const { format, sources } = (0, import_import.parseImport)(text, options.ns);
|
|
1536
1779
|
if (format === "namespace-map" && options.ns) {
|
|
1537
1780
|
throw new Error("--ns cannot be combined with a {namespace: rows} file \u2014 the file already names each namespace");
|
|
@@ -2498,13 +2741,13 @@ function printGroup(out, heading, items) {
|
|
|
2498
2741
|
|
|
2499
2742
|
// src/doctor-checks.ts
|
|
2500
2743
|
var import_node_child_process3 = require("child_process");
|
|
2501
|
-
var
|
|
2502
|
-
var
|
|
2744
|
+
var import_node_fs10 = require("fs");
|
|
2745
|
+
var import_node_path8 = require("path");
|
|
2503
2746
|
|
|
2504
2747
|
// src/wrangler.ts
|
|
2505
2748
|
var import_node_child_process2 = require("child_process");
|
|
2506
|
-
var
|
|
2507
|
-
var
|
|
2749
|
+
var import_node_fs9 = require("fs");
|
|
2750
|
+
var import_node_path7 = require("path");
|
|
2508
2751
|
var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
|
|
2509
2752
|
const child = (0, import_node_child_process2.spawn)(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
|
|
2510
2753
|
let stdout = "";
|
|
@@ -2518,15 +2761,15 @@ var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) =>
|
|
|
2518
2761
|
var WRANGLER_CONFIG_FILES = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"];
|
|
2519
2762
|
function findWranglerConfig(rootDir) {
|
|
2520
2763
|
for (const name of WRANGLER_CONFIG_FILES) {
|
|
2521
|
-
const path = (0,
|
|
2522
|
-
if ((0,
|
|
2764
|
+
const path = (0, import_node_path7.join)(rootDir, name);
|
|
2765
|
+
if ((0, import_node_fs9.existsSync)(path)) return path;
|
|
2523
2766
|
}
|
|
2524
2767
|
return null;
|
|
2525
2768
|
}
|
|
2526
2769
|
function readWranglerConfig(path) {
|
|
2527
2770
|
if (path.endsWith(".toml")) return null;
|
|
2528
2771
|
try {
|
|
2529
|
-
return JSON.parse(stripJsonComments((0,
|
|
2772
|
+
return JSON.parse(stripJsonComments((0, import_node_fs9.readFileSync)(path, "utf8")));
|
|
2530
2773
|
} catch {
|
|
2531
2774
|
return null;
|
|
2532
2775
|
}
|
|
@@ -2624,10 +2867,10 @@ function wranglerWarnings(rootDir) {
|
|
|
2624
2867
|
for (const { label, block } of blocks) {
|
|
2625
2868
|
const assets = block.assets;
|
|
2626
2869
|
if (assets?.directory) {
|
|
2627
|
-
const dir = (0,
|
|
2628
|
-
if (dir === (0,
|
|
2870
|
+
const dir = (0, import_node_path8.resolve)(rootDir, assets.directory);
|
|
2871
|
+
if (dir === (0, import_node_path8.resolve)(rootDir)) {
|
|
2629
2872
|
warnings.push(`${label}assets.directory is the project root \u2014 point it at a dedicated build dir (wrangler dev fails with "spawn EBADF")`);
|
|
2630
|
-
} else if ((0,
|
|
2873
|
+
} else if ((0, import_node_fs10.existsSync)((0, import_node_path8.join)(dir, "node_modules"))) {
|
|
2631
2874
|
warnings.push(`${label}assets.directory contains node_modules \u2014 wrangler dev's watcher will exhaust file descriptors`);
|
|
2632
2875
|
}
|
|
2633
2876
|
}
|
|
@@ -2662,13 +2905,13 @@ function o11yProjectWarnings(rootDir) {
|
|
|
2662
2905
|
warnings.push("cannot verify o11y Worker instrumentation \u2014 add a parseable wrangler.jsonc/json config");
|
|
2663
2906
|
return warnings;
|
|
2664
2907
|
}
|
|
2665
|
-
const main = typeof config.main === "string" ? (0,
|
|
2666
|
-
if (!main || !(0,
|
|
2908
|
+
const main = typeof config.main === "string" ? (0, import_node_path8.resolve)(rootDir, config.main) : null;
|
|
2909
|
+
if (!main || !(0, import_node_fs10.existsSync)(main)) {
|
|
2667
2910
|
warnings.push("cannot verify o11y Worker instrumentation \u2014 wrangler main is missing or unreadable");
|
|
2668
2911
|
} else {
|
|
2669
2912
|
let source = "";
|
|
2670
2913
|
try {
|
|
2671
|
-
source = (0,
|
|
2914
|
+
source = (0, import_node_fs10.readFileSync)(main, "utf8");
|
|
2672
2915
|
} catch {
|
|
2673
2916
|
}
|
|
2674
2917
|
if (!/\bwithObservability\b/.test(source)) {
|
|
@@ -2692,7 +2935,7 @@ function calendarProjectWarnings(rootDir) {
|
|
|
2692
2935
|
}
|
|
2693
2936
|
function readPackageJson(rootDir) {
|
|
2694
2937
|
try {
|
|
2695
|
-
return JSON.parse((0,
|
|
2938
|
+
return JSON.parse((0, import_node_fs10.readFileSync)((0, import_node_path8.join)(rootDir, "package.json"), "utf8"));
|
|
2696
2939
|
} catch {
|
|
2697
2940
|
return null;
|
|
2698
2941
|
}
|
|
@@ -2919,13 +3162,14 @@ function harnessOption(value, flag) {
|
|
|
2919
3162
|
}
|
|
2920
3163
|
|
|
2921
3164
|
// src/init.ts
|
|
2922
|
-
var
|
|
2923
|
-
var
|
|
3165
|
+
var import_node_fs11 = require("fs");
|
|
3166
|
+
var import_node_path9 = require("path");
|
|
3167
|
+
var import_apps3 = require("@odla-ai/apps");
|
|
2924
3168
|
function initProject(options) {
|
|
2925
3169
|
const out = options.stdout ?? console;
|
|
2926
|
-
const rootDir = (0,
|
|
2927
|
-
const configPath = (0,
|
|
2928
|
-
if ((0,
|
|
3170
|
+
const rootDir = (0, import_node_path9.resolve)(options.rootDir ?? process.cwd());
|
|
3171
|
+
const configPath = (0, import_node_path9.resolve)(rootDir, options.configPath ?? "odla.config.mjs");
|
|
3172
|
+
if ((0, import_node_fs11.existsSync)(configPath) && !options.force) {
|
|
2929
3173
|
throw new Error(`${configPath} already exists. Pass --force to overwrite.`);
|
|
2930
3174
|
}
|
|
2931
3175
|
if (!/^[a-z0-9][a-z0-9-]*$/.test(options.appId)) {
|
|
@@ -2933,22 +3177,28 @@ function initProject(options) {
|
|
|
2933
3177
|
}
|
|
2934
3178
|
const envs = options.envs?.length ? options.envs : ["dev"];
|
|
2935
3179
|
const services = options.services?.length ? options.services : ["db", "ai"];
|
|
2936
|
-
|
|
3180
|
+
for (const service of services) {
|
|
3181
|
+
const definition = (0, import_apps3.appServiceDefinition)(service);
|
|
3182
|
+
if (!definition) throw new Error(`--services contains unknown service "${service}" (known: ${(0, import_apps3.appServiceIds)().join(", ")})`);
|
|
3183
|
+
for (const dependency of definition.requires) {
|
|
3184
|
+
if (!services.includes(dependency)) throw new Error(`--services ${service} requires ${dependency}`);
|
|
3185
|
+
}
|
|
3186
|
+
}
|
|
2937
3187
|
const aiProvider = options.aiProvider ?? "anthropic";
|
|
2938
|
-
(0,
|
|
2939
|
-
(0,
|
|
2940
|
-
(0,
|
|
2941
|
-
(0,
|
|
2942
|
-
writeIfMissing((0,
|
|
2943
|
-
writeIfMissing((0,
|
|
3188
|
+
(0, import_node_fs11.mkdirSync)((0, import_node_path9.dirname)(configPath), { recursive: true });
|
|
3189
|
+
(0, import_node_fs11.mkdirSync)((0, import_node_path9.resolve)(rootDir, "src/odla"), { recursive: true });
|
|
3190
|
+
(0, import_node_fs11.mkdirSync)((0, import_node_path9.resolve)(rootDir, ".odla"), { recursive: true });
|
|
3191
|
+
(0, import_node_fs11.writeFileSync)(configPath, configTemplate({ appId: options.appId, name: options.name, envs, services, aiProvider }));
|
|
3192
|
+
writeIfMissing((0, import_node_path9.resolve)(rootDir, "src/odla/schema.mjs"), schemaTemplate());
|
|
3193
|
+
writeIfMissing((0, import_node_path9.resolve)(rootDir, "src/odla/rules.mjs"), rulesTemplate());
|
|
2944
3194
|
ensureGitignore(rootDir);
|
|
2945
3195
|
out.log(`created ${relativeDisplay(configPath, rootDir)}`);
|
|
2946
3196
|
out.log("created src/odla/schema.mjs and src/odla/rules.mjs");
|
|
2947
3197
|
out.log("updated .gitignore for local odla credentials");
|
|
2948
3198
|
}
|
|
2949
3199
|
function writeIfMissing(path, text) {
|
|
2950
|
-
if ((0,
|
|
2951
|
-
(0,
|
|
3200
|
+
if ((0, import_node_fs11.existsSync)(path)) return;
|
|
3201
|
+
(0, import_node_fs11.writeFileSync)(path, text);
|
|
2952
3202
|
}
|
|
2953
3203
|
function configTemplate(input) {
|
|
2954
3204
|
const calendar = input.services.includes("calendar") ? ` calendar: {
|
|
@@ -3123,7 +3373,7 @@ function assertWranglerConfig(cfg) {
|
|
|
3123
3373
|
|
|
3124
3374
|
// src/secrets-set.ts
|
|
3125
3375
|
var import_ai = require("@odla-ai/ai");
|
|
3126
|
-
var
|
|
3376
|
+
var import_apps4 = require("@odla-ai/apps");
|
|
3127
3377
|
var PROD_ENV_NAMES2 = /* @__PURE__ */ new Set(["prod", "production"]);
|
|
3128
3378
|
async function secretsSet(options) {
|
|
3129
3379
|
const name = (options.name ?? "").trim();
|
|
@@ -3175,13 +3425,13 @@ async function resolveVaultWrite(options) {
|
|
|
3175
3425
|
throw new Error(`refusing to store a secret for "${options.env}" without --yes`);
|
|
3176
3426
|
}
|
|
3177
3427
|
const value = await secretInputValue(options, "secret");
|
|
3178
|
-
return { cfg, tenantId: (0,
|
|
3428
|
+
return { cfg, tenantId: (0, import_apps4.tenantIdFor)(cfg.app.id, options.env), value, doFetch, out };
|
|
3179
3429
|
}
|
|
3180
3430
|
|
|
3181
3431
|
// src/skill.ts
|
|
3182
|
-
var
|
|
3183
|
-
var
|
|
3184
|
-
var
|
|
3432
|
+
var import_node_fs12 = require("fs");
|
|
3433
|
+
var import_node_os2 = require("os");
|
|
3434
|
+
var import_node_path10 = require("path");
|
|
3185
3435
|
var import_node_url2 = require("url");
|
|
3186
3436
|
|
|
3187
3437
|
// src/skill-adapters.ts
|
|
@@ -3260,8 +3510,8 @@ function installSkill(options = {}) {
|
|
|
3260
3510
|
const files = listFiles(sourceDir);
|
|
3261
3511
|
if (files.length === 0) throw new Error(`no bundled skills found at ${sourceDir}`);
|
|
3262
3512
|
const harnesses = normalizeHarnesses(options.harnesses, options.global === true);
|
|
3263
|
-
const root = (0,
|
|
3264
|
-
const home = (0,
|
|
3513
|
+
const root = (0, import_node_path10.resolve)(options.dir ?? process.cwd());
|
|
3514
|
+
const home = (0, import_node_path10.resolve)(options.homeDir ?? (0, import_node_os2.homedir)());
|
|
3265
3515
|
const plans = /* @__PURE__ */ new Map();
|
|
3266
3516
|
const targets = /* @__PURE__ */ new Map();
|
|
3267
3517
|
const rememberTarget = (harness, target) => {
|
|
@@ -3275,48 +3525,48 @@ function installSkill(options = {}) {
|
|
|
3275
3525
|
plans.set(target, { target, content: content2, boundary, managedMerge });
|
|
3276
3526
|
};
|
|
3277
3527
|
const planSkillTree = (targetDir2, boundary = root) => {
|
|
3278
|
-
for (const rel of files) plan((0,
|
|
3528
|
+
for (const rel of files) plan((0, import_node_path10.join)(targetDir2, rel), (0, import_node_fs12.readFileSync)((0, import_node_path10.join)(sourceDir, rel), "utf8"), false, boundary);
|
|
3279
3529
|
};
|
|
3280
3530
|
let targetDir;
|
|
3281
3531
|
if (options.global) {
|
|
3282
|
-
const claudeRoot = (0,
|
|
3283
|
-
const codexRoot = (0,
|
|
3532
|
+
const claudeRoot = (0, import_node_path10.join)(home, ".claude", "skills");
|
|
3533
|
+
const codexRoot = (0, import_node_path10.resolve)(options.codexHomeDir ?? process.env.CODEX_HOME ?? (0, import_node_path10.join)(home, ".codex"), "skills");
|
|
3284
3534
|
targetDir = harnesses[0] === "codex" ? codexRoot : claudeRoot;
|
|
3285
3535
|
for (const harness of harnesses) {
|
|
3286
3536
|
const skillRoot = harness === "claude" ? claudeRoot : codexRoot;
|
|
3287
|
-
planSkillTree(skillRoot, harness === "claude" ? home : (0,
|
|
3537
|
+
planSkillTree(skillRoot, harness === "claude" ? home : (0, import_node_path10.dirname)((0, import_node_path10.dirname)(codexRoot)));
|
|
3288
3538
|
rememberTarget(harness, skillRoot);
|
|
3289
3539
|
}
|
|
3290
3540
|
} else {
|
|
3291
|
-
const sharedRoot = (0,
|
|
3541
|
+
const sharedRoot = (0, import_node_path10.join)(root, ".agents", "skills");
|
|
3292
3542
|
planSkillTree(sharedRoot);
|
|
3293
|
-
const claudeRoot = (0,
|
|
3543
|
+
const claudeRoot = (0, import_node_path10.join)(root, ".claude", "skills");
|
|
3294
3544
|
targetDir = harnesses.includes("claude") ? claudeRoot : sharedRoot;
|
|
3295
3545
|
for (const harness of harnesses) rememberTarget(harness, sharedRoot);
|
|
3296
3546
|
if (harnesses.includes("claude")) {
|
|
3297
3547
|
for (const skill of skillNames(files)) {
|
|
3298
|
-
const canonical = (0,
|
|
3299
|
-
plan((0,
|
|
3548
|
+
const canonical = (0, import_node_fs12.readFileSync)((0, import_node_path10.join)(sourceDir, skill, "SKILL.md"), "utf8");
|
|
3549
|
+
plan((0, import_node_path10.join)(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical));
|
|
3300
3550
|
}
|
|
3301
3551
|
rememberTarget("claude", claudeRoot);
|
|
3302
3552
|
}
|
|
3303
3553
|
if (harnesses.includes("cursor")) {
|
|
3304
|
-
const cursorRule = (0,
|
|
3554
|
+
const cursorRule = (0, import_node_path10.join)(root, ".cursor", "rules", "odla.mdc");
|
|
3305
3555
|
plan(cursorRule, CURSOR_RULE);
|
|
3306
3556
|
rememberTarget("cursor", cursorRule);
|
|
3307
3557
|
}
|
|
3308
3558
|
if (harnesses.includes("agents")) {
|
|
3309
|
-
const agentsFile = (0,
|
|
3559
|
+
const agentsFile = (0, import_node_path10.join)(root, "AGENTS.md");
|
|
3310
3560
|
plan(agentsFile, managedFileContent(agentsFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
3311
3561
|
rememberTarget("agents", agentsFile);
|
|
3312
3562
|
}
|
|
3313
3563
|
if (harnesses.includes("copilot")) {
|
|
3314
|
-
const copilotFile = (0,
|
|
3564
|
+
const copilotFile = (0, import_node_path10.join)(root, ".github", "copilot-instructions.md");
|
|
3315
3565
|
plan(copilotFile, managedFileContent(copilotFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
3316
3566
|
rememberTarget("copilot", copilotFile);
|
|
3317
3567
|
}
|
|
3318
3568
|
if (harnesses.includes("gemini")) {
|
|
3319
|
-
const geminiFile = (0,
|
|
3569
|
+
const geminiFile = (0, import_node_path10.join)(root, "GEMINI.md");
|
|
3320
3570
|
plan(geminiFile, managedFileContent(geminiFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
3321
3571
|
rememberTarget("gemini", geminiFile);
|
|
3322
3572
|
}
|
|
@@ -3330,11 +3580,11 @@ function installSkill(options = {}) {
|
|
|
3330
3580
|
conflicts.push(`${file.target} (redirected by symbolic link ${symlink})`);
|
|
3331
3581
|
continue;
|
|
3332
3582
|
}
|
|
3333
|
-
if (!(0,
|
|
3583
|
+
if (!(0, import_node_fs12.existsSync)(file.target)) {
|
|
3334
3584
|
writtenPaths.add(file.target);
|
|
3335
3585
|
continue;
|
|
3336
3586
|
}
|
|
3337
|
-
const current = (0,
|
|
3587
|
+
const current = (0, import_node_fs12.readFileSync)(file.target, "utf8");
|
|
3338
3588
|
if (current === file.content) {
|
|
3339
3589
|
unchangedPaths.add(file.target);
|
|
3340
3590
|
} else if (file.managedMerge || options.force) {
|
|
@@ -3351,9 +3601,9 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
|
|
|
3351
3601
|
);
|
|
3352
3602
|
}
|
|
3353
3603
|
for (const file of plans.values()) {
|
|
3354
|
-
if (!(0,
|
|
3355
|
-
(0,
|
|
3356
|
-
(0,
|
|
3604
|
+
if (!(0, import_node_fs12.existsSync)(file.target) || (0, import_node_fs12.readFileSync)(file.target, "utf8") !== file.content) {
|
|
3605
|
+
(0, import_node_fs12.mkdirSync)((0, import_node_path10.dirname)(file.target), { recursive: true });
|
|
3606
|
+
(0, import_node_fs12.writeFileSync)(file.target, file.content);
|
|
3357
3607
|
}
|
|
3358
3608
|
}
|
|
3359
3609
|
const skills = skillNames(files);
|
|
@@ -3372,7 +3622,7 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
|
|
|
3372
3622
|
};
|
|
3373
3623
|
}
|
|
3374
3624
|
function pathsUnder(root, paths) {
|
|
3375
|
-
return [...paths].map((path) => (0,
|
|
3625
|
+
return [...paths].map((path) => (0, import_node_path10.relative)(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${import_node_path10.sep}`) && !(0, import_node_path10.isAbsolute)(path)).sort();
|
|
3376
3626
|
}
|
|
3377
3627
|
function normalizeHarnesses(values, global) {
|
|
3378
3628
|
const requested = values?.length ? values : ["claude"];
|
|
@@ -3394,9 +3644,9 @@ function normalizeHarnesses(values, global) {
|
|
|
3394
3644
|
function managedFileContent(path, block, force, boundary) {
|
|
3395
3645
|
const symlink = symlinkedComponent(boundary, path);
|
|
3396
3646
|
if (symlink) throw new Error(`refusing to manage ${path}: symbolic link component ${symlink}`);
|
|
3397
|
-
if (!(0,
|
|
3647
|
+
if (!(0, import_node_fs12.existsSync)(path)) return `${block}
|
|
3398
3648
|
`;
|
|
3399
|
-
const current = (0,
|
|
3649
|
+
const current = (0, import_node_fs12.readFileSync)(path, "utf8");
|
|
3400
3650
|
const start = "<!-- odla-ai agent setup:start -->";
|
|
3401
3651
|
const end = "<!-- odla-ai agent setup:end -->";
|
|
3402
3652
|
const startAt = current.indexOf(start);
|
|
@@ -3417,15 +3667,15 @@ function managedFileContent(path, block, force, boundary) {
|
|
|
3417
3667
|
return `${current.slice(0, startAt)}${block}${current.slice(afterEnd)}`;
|
|
3418
3668
|
}
|
|
3419
3669
|
function symlinkedComponent(boundary, target) {
|
|
3420
|
-
const rel = (0,
|
|
3421
|
-
if (rel === ".." || rel.startsWith(`..${
|
|
3670
|
+
const rel = (0, import_node_path10.relative)(boundary, target);
|
|
3671
|
+
if (rel === ".." || rel.startsWith(`..${import_node_path10.sep}`) || (0, import_node_path10.isAbsolute)(rel)) {
|
|
3422
3672
|
throw new Error(`agent setup target escapes its install root: ${target}`);
|
|
3423
3673
|
}
|
|
3424
3674
|
let current = boundary;
|
|
3425
|
-
for (const part of rel.split(
|
|
3426
|
-
current = (0,
|
|
3675
|
+
for (const part of rel.split(import_node_path10.sep).filter(Boolean)) {
|
|
3676
|
+
current = (0, import_node_path10.join)(current, part);
|
|
3427
3677
|
try {
|
|
3428
|
-
if ((0,
|
|
3678
|
+
if ((0, import_node_fs12.lstatSync)(current).isSymbolicLink()) return current;
|
|
3429
3679
|
} catch (error) {
|
|
3430
3680
|
if (error.code !== "ENOENT") throw error;
|
|
3431
3681
|
}
|
|
@@ -3436,13 +3686,13 @@ function skillNames(files) {
|
|
|
3436
3686
|
return [...new Set(files.filter((file) => /(^|[\\/])SKILL\.md$/.test(file)).map((file) => file.split(/[\\/]/)[0]))].sort();
|
|
3437
3687
|
}
|
|
3438
3688
|
function listFiles(dir) {
|
|
3439
|
-
if (!(0,
|
|
3689
|
+
if (!(0, import_node_fs12.existsSync)(dir)) return [];
|
|
3440
3690
|
const results = [];
|
|
3441
3691
|
const walk = (current) => {
|
|
3442
|
-
for (const entry of (0,
|
|
3443
|
-
const path = (0,
|
|
3692
|
+
for (const entry of (0, import_node_fs12.readdirSync)(current, { withFileTypes: true })) {
|
|
3693
|
+
const path = (0, import_node_path10.join)(current, entry.name);
|
|
3444
3694
|
if (entry.isDirectory()) walk(path);
|
|
3445
|
-
else results.push((0,
|
|
3695
|
+
else results.push((0, import_node_path10.relative)(dir, path));
|
|
3446
3696
|
}
|
|
3447
3697
|
};
|
|
3448
3698
|
walk(dir);
|
|
@@ -3589,13 +3839,16 @@ async function safeText3(res) {
|
|
|
3589
3839
|
|
|
3590
3840
|
// src/cli-project.ts
|
|
3591
3841
|
var SKILL_OPTS = ["dir", "global", "force", "agent", "harness"];
|
|
3592
|
-
function install(parsed, positionals) {
|
|
3842
|
+
function install(parsed, positionals, deps) {
|
|
3593
3843
|
assertArgs(parsed, SKILL_OPTS, positionals);
|
|
3594
3844
|
installSkill({
|
|
3595
3845
|
dir: stringOpt(parsed.options.dir),
|
|
3596
3846
|
global: parsed.options.global === true,
|
|
3597
3847
|
harnesses: harnessList(parsed),
|
|
3598
|
-
force: parsed.options.force === true
|
|
3848
|
+
force: parsed.options.force === true,
|
|
3849
|
+
homeDir: deps.homeDir,
|
|
3850
|
+
codexHomeDir: deps.codexHomeDir,
|
|
3851
|
+
stdout: deps.stdout
|
|
3599
3852
|
});
|
|
3600
3853
|
}
|
|
3601
3854
|
async function secretsCommand(parsed, deps) {
|
|
@@ -3611,6 +3864,7 @@ async function secretsCommand(parsed, deps) {
|
|
|
3611
3864
|
token: stringOpt(parsed.options.token),
|
|
3612
3865
|
email: stringOpt(parsed.options.email),
|
|
3613
3866
|
yes: parsed.options.yes === true,
|
|
3867
|
+
readStdin: deps.readStdin,
|
|
3614
3868
|
fetch: deps.fetch,
|
|
3615
3869
|
stdout: deps.stdout
|
|
3616
3870
|
};
|
|
@@ -3669,8 +3923,8 @@ async function projectCommand(command, parsed, deps) {
|
|
|
3669
3923
|
return true;
|
|
3670
3924
|
}
|
|
3671
3925
|
if (command === "setup") {
|
|
3672
|
-
install(parsed, 1);
|
|
3673
|
-
console.log(
|
|
3926
|
+
install(parsed, 1, deps);
|
|
3927
|
+
(deps.stdout ?? console).log(
|
|
3674
3928
|
"\nOffline runbooks installed. Open this repo in your coding agent and ask it to set up odla.\nIts native adapter finds the local `odla` skill, chooses build or migration, and drives the CLI."
|
|
3675
3929
|
);
|
|
3676
3930
|
return true;
|
|
@@ -3678,7 +3932,7 @@ async function projectCommand(command, parsed, deps) {
|
|
|
3678
3932
|
if (command === "skill") {
|
|
3679
3933
|
const sub = parsed.positionals[1];
|
|
3680
3934
|
if (sub !== "install") throw new Error(`unknown skill subcommand "${sub ?? ""}". Try "odla-ai skill install".`);
|
|
3681
|
-
install(parsed, 2);
|
|
3935
|
+
install(parsed, 2, deps);
|
|
3682
3936
|
return true;
|
|
3683
3937
|
}
|
|
3684
3938
|
if (command === "secrets") {
|
|
@@ -3689,9 +3943,9 @@ async function projectCommand(command, parsed, deps) {
|
|
|
3689
3943
|
}
|
|
3690
3944
|
|
|
3691
3945
|
// src/code-connect.ts
|
|
3692
|
-
var
|
|
3693
|
-
var
|
|
3694
|
-
var
|
|
3946
|
+
var import_node_fs14 = require("fs");
|
|
3947
|
+
var import_node_os4 = require("os");
|
|
3948
|
+
var import_node_path12 = require("path");
|
|
3695
3949
|
|
|
3696
3950
|
// ../harness/dist/chunk-QTUEF2HZ.js
|
|
3697
3951
|
var HARNESS_PROTOCOL_VERSION = 1;
|
|
@@ -6576,9 +6830,9 @@ var CodePiRuntimeEngine = class {
|
|
|
6576
6830
|
};
|
|
6577
6831
|
|
|
6578
6832
|
// src/help.ts
|
|
6579
|
-
var
|
|
6833
|
+
var import_node_fs13 = require("fs");
|
|
6580
6834
|
function cliVersion() {
|
|
6581
|
-
const pkg = JSON.parse((0,
|
|
6835
|
+
const pkg = JSON.parse((0, import_node_fs13.readFileSync)(new URL("../package.json", importMetaUrl), "utf8"));
|
|
6582
6836
|
return pkg.version ?? "unknown";
|
|
6583
6837
|
}
|
|
6584
6838
|
function printHelp(output = console) {
|
|
@@ -6666,15 +6920,16 @@ Usage:
|
|
|
6666
6920
|
odla-ai runbook rm <slug> [--app <id>]
|
|
6667
6921
|
odla-ai capabilities [--json]
|
|
6668
6922
|
odla-ai code connect [--env dev|prod] [--email <odla-account>] [--engine auto|container|podman|docker] [--slots <1-64>] [--once]
|
|
6669
|
-
odla-ai admin ai show [--platform https://odla.ai] [--email <odla-account>] [--json]
|
|
6670
|
-
odla-ai admin ai models [--provider <id>] [--json]
|
|
6671
|
-
odla-ai admin ai set <purpose> [--provider <id>] [--model <id>] [--enabled|--no-enabled]
|
|
6672
|
-
|
|
6673
|
-
|
|
6674
|
-
|
|
6675
|
-
odla-ai admin ai
|
|
6676
|
-
odla-ai admin ai
|
|
6677
|
-
odla-ai admin ai
|
|
6923
|
+
odla-ai admin ai show [--context <name>] [--platform https://odla.ai] [--email <odla-account>] [--json]
|
|
6924
|
+
odla-ai admin ai models [--context <name>] [--provider <id>] [--json]
|
|
6925
|
+
odla-ai admin ai set <purpose> [--context <name>] [--provider <id>] [--model <id>] [--enabled|--no-enabled]
|
|
6926
|
+
[--max-input-bytes <n>] [--max-output-tokens <n>] [--max-calls-per-run <n>] [--json]
|
|
6927
|
+
odla-ai admin ai set security [--context <name>] --discovery-provider <id> --discovery-model <id>
|
|
6928
|
+
--validation-provider <id> --validation-model <id> [--enabled|--no-enabled] [--json]
|
|
6929
|
+
odla-ai admin ai credentials [--context <name>] [--json]
|
|
6930
|
+
odla-ai admin ai credential set <provider> [--context <name>] (--from-env <NAME>|--stdin)
|
|
6931
|
+
odla-ai admin ai usage [--context <name>] [--app-id <id>] [--env <env>] [--run-id <id>] [--limit <1-500>] [--json]
|
|
6932
|
+
odla-ai admin ai audit [--context <name>] [--limit <1-200>] [--json]
|
|
6678
6933
|
odla-ai security github connect [--repo owner/name] [--env dev] [--email <odla-account>] [--no-open]
|
|
6679
6934
|
odla-ai security github disconnect --source <id> [--env dev] [--yes]
|
|
6680
6935
|
odla-ai security plan [--env dev] [--json]
|
|
@@ -7101,8 +7356,8 @@ function digestText(value) {
|
|
|
7101
7356
|
var import_node_child_process6 = require("child_process");
|
|
7102
7357
|
var import_node_crypto2 = require("crypto");
|
|
7103
7358
|
var import_promises10 = require("fs/promises");
|
|
7104
|
-
var
|
|
7105
|
-
var
|
|
7359
|
+
var import_node_os3 = require("os");
|
|
7360
|
+
var import_node_path11 = require("path");
|
|
7106
7361
|
var import_node_url3 = require("url");
|
|
7107
7362
|
|
|
7108
7363
|
// src/code-runtime-config.ts
|
|
@@ -7188,10 +7443,10 @@ async function embeddedPiImageName() {
|
|
|
7188
7443
|
return `odla-ai/pi-agent:embedded-sha256-${(0, import_node_crypto2.createHash)("sha256").update(bundle).digest("hex")}`;
|
|
7189
7444
|
}
|
|
7190
7445
|
async function buildEmbeddedPiImage(engine, image, run) {
|
|
7191
|
-
const context = await (0, import_promises10.mkdtemp)((0,
|
|
7446
|
+
const context = await (0, import_promises10.mkdtemp)((0, import_node_path11.join)((0, import_node_os3.tmpdir)(), "odla-code-pi-"));
|
|
7192
7447
|
try {
|
|
7193
|
-
await (0, import_promises10.copyFile)(embeddedPiAssetPath(), (0,
|
|
7194
|
-
await (0, import_promises10.writeFile)((0,
|
|
7448
|
+
await (0, import_promises10.copyFile)(embeddedPiAssetPath(), (0, import_node_path11.join)(context, "pi-agent.js"));
|
|
7449
|
+
await (0, import_promises10.writeFile)((0, import_node_path11.join)(context, "Dockerfile"), [
|
|
7195
7450
|
`FROM ${CODE_NODE_IMAGE}`,
|
|
7196
7451
|
"COPY pi-agent.js /opt/odla/pi-agent.js",
|
|
7197
7452
|
"WORKDIR /workspace",
|
|
@@ -7207,8 +7462,8 @@ async function buildEmbeddedPiImage(engine, image, run) {
|
|
|
7207
7462
|
// src/code-connect.ts
|
|
7208
7463
|
async function codeConnect(options) {
|
|
7209
7464
|
const cwd = options.cwd ?? process.cwd();
|
|
7210
|
-
const configPath = (0,
|
|
7211
|
-
const cfg = (0,
|
|
7465
|
+
const configPath = (0, import_node_path12.resolve)(cwd, options.configPath);
|
|
7466
|
+
const cfg = (0, import_node_fs14.existsSync)(configPath) ? await loadProjectConfig(configPath) : null;
|
|
7212
7467
|
const requestedAppId = options.appId?.trim();
|
|
7213
7468
|
if (requestedAppId && !/^[a-z0-9][a-z0-9-]{1,62}$/.test(requestedAppId)) {
|
|
7214
7469
|
throw new Error("--app-id must be a valid odla app id");
|
|
@@ -7242,7 +7497,7 @@ async function codeConnect(options) {
|
|
|
7242
7497
|
);
|
|
7243
7498
|
if (!piImage || !/^odla-ai\/pi-agent:embedded-sha256-[0-9a-f]{64}$/.test(piImage)) throw new Error("Code image preflight did not produce the content-addressed embedded Pi runtime");
|
|
7244
7499
|
const hostPlatform = process.platform === "darwin" ? "macos" : "linux";
|
|
7245
|
-
const hostName = (options.name ?? (0,
|
|
7500
|
+
const hostName = (options.name ?? (0, import_node_os4.hostname)()).trim();
|
|
7246
7501
|
if (!hostName || hostName.length > 120) throw new Error("--name must contain 1 to 120 characters");
|
|
7247
7502
|
const repository = await inferGitHubRepository(cwd, options.readGitOrigin);
|
|
7248
7503
|
const localSource = await (options.prepareLocalSource ?? prepareCodeLocalSource)(
|
|
@@ -7279,8 +7534,8 @@ async function codeConnect(options) {
|
|
|
7279
7534
|
platform: hostPlatform,
|
|
7280
7535
|
arch: process.arch,
|
|
7281
7536
|
engines: [engine],
|
|
7282
|
-
cpuCount: (0,
|
|
7283
|
-
memoryBytes: (0,
|
|
7537
|
+
cpuCount: (0, import_node_os4.cpus)().length,
|
|
7538
|
+
memoryBytes: (0, import_node_os4.totalmem)(),
|
|
7284
7539
|
source: descriptor2,
|
|
7285
7540
|
images: {
|
|
7286
7541
|
ready: true,
|
|
@@ -7426,246 +7681,19 @@ async function codeCommand(parsed, dependencies) {
|
|
|
7426
7681
|
}
|
|
7427
7682
|
|
|
7428
7683
|
// src/operator-credentials.ts
|
|
7429
|
-
var
|
|
7684
|
+
var import_node_process9 = __toESM(require("process"), 1);
|
|
7430
7685
|
function developerTokenStatus(context, parsed, now = Date.now()) {
|
|
7431
7686
|
const cached = readJsonFile(context.cfg.local.tokenFile);
|
|
7432
7687
|
const cacheStatus = !cached?.token ? "missing" : cached.platform !== context.platform.value ? "other-platform" : (cached.expiresAt ?? 0) <= now + 6e4 ? "expired" : "valid";
|
|
7433
|
-
const source =
|
|
7688
|
+
const source = clean3(
|
|
7434
7689
|
stringOpt(parsed.options.token)
|
|
7435
|
-
) ? "flag" :
|
|
7690
|
+
) ? "flag" : clean3(import_node_process9.default.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
|
|
7436
7691
|
return {
|
|
7437
7692
|
source,
|
|
7438
7693
|
cacheFile: context.cfg.local.tokenFile,
|
|
7439
7694
|
cacheStatus
|
|
7440
7695
|
};
|
|
7441
7696
|
}
|
|
7442
|
-
function clean(value) {
|
|
7443
|
-
const normalized = value?.trim();
|
|
7444
|
-
return normalized || void 0;
|
|
7445
|
-
}
|
|
7446
|
-
|
|
7447
|
-
// src/operator-context.ts
|
|
7448
|
-
var import_node_fs14 = require("fs");
|
|
7449
|
-
var import_node_path12 = require("path");
|
|
7450
|
-
var import_node_process9 = __toESM(require("process"), 1);
|
|
7451
|
-
|
|
7452
|
-
// src/operator-profiles.ts
|
|
7453
|
-
var import_node_fs13 = require("fs");
|
|
7454
|
-
var import_node_os4 = require("os");
|
|
7455
|
-
var import_node_path11 = require("path");
|
|
7456
|
-
var import_node_process8 = __toESM(require("process"), 1);
|
|
7457
|
-
function operatorProfileFile() {
|
|
7458
|
-
return (0, import_node_path11.resolve)(
|
|
7459
|
-
clean2(import_node_process8.default.env.ODLA_CONTEXT_FILE) ?? (0, import_node_path11.join)((0, import_node_os4.homedir)(), ".odla", "contexts.json")
|
|
7460
|
-
);
|
|
7461
|
-
}
|
|
7462
|
-
function resolveOperatorProfile(parsed) {
|
|
7463
|
-
const fromFlag = clean2(stringOpt(parsed.options.context));
|
|
7464
|
-
const fromEnvironment = clean2(import_node_process8.default.env.ODLA_CONTEXT);
|
|
7465
|
-
const name = fromFlag ?? fromEnvironment ?? null;
|
|
7466
|
-
const file = operatorProfileFile();
|
|
7467
|
-
if (!name) {
|
|
7468
|
-
return { name: null, source: "unresolved", file, value: null };
|
|
7469
|
-
}
|
|
7470
|
-
assertOperatorName(name, "context");
|
|
7471
|
-
const profiles = readOperatorProfiles(file);
|
|
7472
|
-
const value = Object.hasOwn(profiles, name) ? profiles[name] : void 0;
|
|
7473
|
-
if (!value) {
|
|
7474
|
-
throw new Error(
|
|
7475
|
-
`operator context "${name}" not found in ${file}; run "odla-ai context list" or save it first`
|
|
7476
|
-
);
|
|
7477
|
-
}
|
|
7478
|
-
return {
|
|
7479
|
-
name,
|
|
7480
|
-
source: fromFlag ? "flag" : "environment",
|
|
7481
|
-
file,
|
|
7482
|
-
value
|
|
7483
|
-
};
|
|
7484
|
-
}
|
|
7485
|
-
function listOperatorProfiles(file = operatorProfileFile()) {
|
|
7486
|
-
return Object.entries(readOperatorProfiles(file)).sort(([a], [b]) => a.localeCompare(b)).map(([name, profile]) => ({ name, ...profile }));
|
|
7487
|
-
}
|
|
7488
|
-
function saveOperatorProfile(name, profile, file = operatorProfileFile()) {
|
|
7489
|
-
assertOperatorName(name, "context");
|
|
7490
|
-
const normalized = validateProfile(profile, `operator context "${name}"`);
|
|
7491
|
-
const profiles = readOperatorProfiles(file);
|
|
7492
|
-
profiles[name] = normalized;
|
|
7493
|
-
writePrivateJson(file, { schemaVersion: 1, profiles });
|
|
7494
|
-
}
|
|
7495
|
-
function removeOperatorProfile(name, file = operatorProfileFile()) {
|
|
7496
|
-
assertOperatorName(name, "context");
|
|
7497
|
-
const profiles = readOperatorProfiles(file);
|
|
7498
|
-
if (!Object.hasOwn(profiles, name)) return false;
|
|
7499
|
-
delete profiles[name];
|
|
7500
|
-
writePrivateJson(file, { schemaVersion: 1, profiles });
|
|
7501
|
-
return true;
|
|
7502
|
-
}
|
|
7503
|
-
function operatorCredentialFiles(selection) {
|
|
7504
|
-
const base = selection.name ? (0, import_node_path11.join)((0, import_node_path11.dirname)(selection.file), "profiles", selection.name) : (0, import_node_path11.join)((0, import_node_os4.homedir)(), ".odla");
|
|
7505
|
-
return {
|
|
7506
|
-
developer: (0, import_node_path11.join)(base, "dev-token.json"),
|
|
7507
|
-
scoped: (0, import_node_path11.join)(base, "admin-token.local.json")
|
|
7508
|
-
};
|
|
7509
|
-
}
|
|
7510
|
-
function assertOperatorName(value, label) {
|
|
7511
|
-
if (!/^[a-z0-9][a-z0-9-]*$/.test(value)) {
|
|
7512
|
-
throw new Error(
|
|
7513
|
-
`${label} must contain lowercase letters, numbers, and hyphens`
|
|
7514
|
-
);
|
|
7515
|
-
}
|
|
7516
|
-
}
|
|
7517
|
-
function readOperatorProfiles(file) {
|
|
7518
|
-
if (!(0, import_node_fs13.existsSync)(file)) return emptyProfiles();
|
|
7519
|
-
let raw;
|
|
7520
|
-
try {
|
|
7521
|
-
raw = JSON.parse((0, import_node_fs13.readFileSync)(file, "utf8"));
|
|
7522
|
-
} catch {
|
|
7523
|
-
throw new Error(`operator context file ${file} is not valid JSON`);
|
|
7524
|
-
}
|
|
7525
|
-
if (!raw || typeof raw !== "object" || raw.schemaVersion !== 1 || !raw.profiles || typeof raw.profiles !== "object" || Array.isArray(raw.profiles)) {
|
|
7526
|
-
throw new Error(`operator context file ${file} has an invalid shape`);
|
|
7527
|
-
}
|
|
7528
|
-
const unknown = Object.keys(raw).filter(
|
|
7529
|
-
(key) => !["schemaVersion", "profiles"].includes(key)
|
|
7530
|
-
);
|
|
7531
|
-
if (unknown.length > 0) {
|
|
7532
|
-
throw new Error(
|
|
7533
|
-
`operator context file ${file} contains unsupported field(s): ${unknown.sort().join(", ")}`
|
|
7534
|
-
);
|
|
7535
|
-
}
|
|
7536
|
-
const profiles = emptyProfiles();
|
|
7537
|
-
for (const [name, value] of Object.entries(
|
|
7538
|
-
raw.profiles
|
|
7539
|
-
)) {
|
|
7540
|
-
assertOperatorName(name, "context");
|
|
7541
|
-
profiles[name] = validateProfile(value, `operator context "${name}"`);
|
|
7542
|
-
}
|
|
7543
|
-
return profiles;
|
|
7544
|
-
}
|
|
7545
|
-
function emptyProfiles() {
|
|
7546
|
-
return /* @__PURE__ */ Object.create(null);
|
|
7547
|
-
}
|
|
7548
|
-
function validateProfile(value, label) {
|
|
7549
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
7550
|
-
throw new Error(`${label} has an invalid shape`);
|
|
7551
|
-
}
|
|
7552
|
-
const unknown = Object.keys(value).filter(
|
|
7553
|
-
(key) => !["platform", "app", "environment"].includes(key)
|
|
7554
|
-
);
|
|
7555
|
-
if (unknown.length > 0) {
|
|
7556
|
-
throw new Error(
|
|
7557
|
-
`${label} contains unsupported field(s): ${unknown.sort().join(", ")}; contexts store scope metadata only, never credentials`
|
|
7558
|
-
);
|
|
7559
|
-
}
|
|
7560
|
-
const candidate = value;
|
|
7561
|
-
if (typeof candidate.platform !== "string") {
|
|
7562
|
-
throw new Error(`${label} needs an absolute platform URL`);
|
|
7563
|
-
}
|
|
7564
|
-
const platform = platformAudience(candidate.platform);
|
|
7565
|
-
const app = clean2(candidate.app);
|
|
7566
|
-
const environment2 = clean2(candidate.environment);
|
|
7567
|
-
if (app) assertOperatorName(app, "app");
|
|
7568
|
-
if (environment2) assertOperatorName(environment2, "environment");
|
|
7569
|
-
return {
|
|
7570
|
-
platform,
|
|
7571
|
-
...app ? { app } : {},
|
|
7572
|
-
...environment2 ? { environment: environment2 } : {}
|
|
7573
|
-
};
|
|
7574
|
-
}
|
|
7575
|
-
function clean2(value) {
|
|
7576
|
-
const normalized = value?.trim();
|
|
7577
|
-
return normalized || void 0;
|
|
7578
|
-
}
|
|
7579
|
-
|
|
7580
|
-
// src/operator-context.ts
|
|
7581
|
-
var DEFAULT_PLATFORM2 = "https://odla.ai";
|
|
7582
|
-
async function resolveOperatorContext(parsed, options = {}) {
|
|
7583
|
-
const profile = resolveOperatorProfile(parsed);
|
|
7584
|
-
const configArgument = stringOpt(parsed.options.config) ?? "odla.config.mjs";
|
|
7585
|
-
const configPath = (0, import_node_path12.resolve)(configArgument);
|
|
7586
|
-
const explicitConfig = parsed.options.config !== void 0;
|
|
7587
|
-
const hasConfig = (0, import_node_fs14.existsSync)(configPath);
|
|
7588
|
-
if (!hasConfig && (!options.allowMissingConfig || explicitConfig)) {
|
|
7589
|
-
await loadProjectConfig(configArgument);
|
|
7590
|
-
}
|
|
7591
|
-
const loaded = hasConfig ? await loadProjectConfig(configArgument) : void 0;
|
|
7592
|
-
const platformFlag = clean3(stringOpt(parsed.options.platform));
|
|
7593
|
-
const platformEnvironment = clean3(import_node_process9.default.env.ODLA_PLATFORM_URL);
|
|
7594
|
-
const platformValue = platformAudience(
|
|
7595
|
-
platformFlag ?? platformEnvironment ?? profile.value?.platform ?? loaded?.platformUrl ?? DEFAULT_PLATFORM2
|
|
7596
|
-
);
|
|
7597
|
-
const platformSource = platformFlag ? "flag" : platformEnvironment ? "environment" : profile.value ? "profile" : loaded ? "config" : "default";
|
|
7598
|
-
const appFlag = clean3(stringOpt(parsed.options.app));
|
|
7599
|
-
const appEnvironment = clean3(import_node_process9.default.env.ODLA_APP_ID);
|
|
7600
|
-
const appValue = appFlag ?? appEnvironment ?? profile.value?.app ?? loaded?.app.id ?? null;
|
|
7601
|
-
const appSource = appFlag ? "flag" : appEnvironment ? "environment" : profile.value?.app ? "profile" : loaded ? "config" : "unresolved";
|
|
7602
|
-
if (appValue) assertOperatorName(appValue, "app");
|
|
7603
|
-
if (options.requireApp && !appValue) {
|
|
7604
|
-
throw new Error(
|
|
7605
|
-
"app context is unresolved; pass --app <id>, set ODLA_APP_ID, select --context <name>, or run inside a project with odla.config.mjs"
|
|
7606
|
-
);
|
|
7607
|
-
}
|
|
7608
|
-
const envFlag = clean3(stringOpt(parsed.options.env));
|
|
7609
|
-
const envEnvironment = clean3(import_node_process9.default.env.ODLA_ENV);
|
|
7610
|
-
const environmentValue = envFlag ?? envEnvironment ?? profile.value?.environment ?? options.defaultEnvironment ?? null;
|
|
7611
|
-
const environmentSource = envFlag ? "flag" : envEnvironment ? "environment" : profile.value?.environment ? "profile" : options.defaultEnvironment ? "default" : "unresolved";
|
|
7612
|
-
if (environmentValue) {
|
|
7613
|
-
assertOperatorName(environmentValue, "environment");
|
|
7614
|
-
}
|
|
7615
|
-
const rootDir = loaded?.rootDir ?? import_node_process9.default.cwd();
|
|
7616
|
-
const profileCredentials = operatorCredentialFiles(profile);
|
|
7617
|
-
const tokenFile = clean3(import_node_process9.default.env.ODLA_DEV_TOKEN_FILE) ? (0, import_node_path12.resolve)(import_node_process9.default.env.ODLA_DEV_TOKEN_FILE) : profile.name ? profileCredentials.developer : loaded?.local.tokenFile ?? profileCredentials.developer;
|
|
7618
|
-
const scopedTokenFile = clean3(import_node_process9.default.env.ODLA_ADMIN_TOKEN_FILE) ? (0, import_node_path12.resolve)(import_node_process9.default.env.ODLA_ADMIN_TOKEN_FILE) : profile.name ? profileCredentials.scoped : loaded ? (0, import_node_path12.join)(loaded.rootDir, ".odla", "admin-token.local.json") : profileCredentials.scoped;
|
|
7619
|
-
const cfg = loaded ? {
|
|
7620
|
-
...loaded,
|
|
7621
|
-
platformUrl: platformValue,
|
|
7622
|
-
app: appValue ? {
|
|
7623
|
-
id: appValue,
|
|
7624
|
-
name: appValue === loaded.app.id ? loaded.app.name : appValue
|
|
7625
|
-
} : loaded.app,
|
|
7626
|
-
local: { ...loaded.local, tokenFile }
|
|
7627
|
-
} : {
|
|
7628
|
-
configPath,
|
|
7629
|
-
rootDir,
|
|
7630
|
-
platformUrl: platformValue,
|
|
7631
|
-
dbEndpoint: platformValue,
|
|
7632
|
-
app: {
|
|
7633
|
-
id: appValue ?? "operator",
|
|
7634
|
-
name: appValue ?? "Operator"
|
|
7635
|
-
},
|
|
7636
|
-
envs: environmentValue ? [environmentValue] : [],
|
|
7637
|
-
services: [],
|
|
7638
|
-
local: {
|
|
7639
|
-
tokenFile,
|
|
7640
|
-
credentialsFile: (0, import_node_path12.join)(rootDir, ".odla", "credentials.local.json"),
|
|
7641
|
-
devVarsFile: (0, import_node_path12.join)(rootDir, ".dev.vars"),
|
|
7642
|
-
gitignore: true
|
|
7643
|
-
}
|
|
7644
|
-
};
|
|
7645
|
-
return {
|
|
7646
|
-
cfg,
|
|
7647
|
-
profile: {
|
|
7648
|
-
name: profile.name,
|
|
7649
|
-
source: profile.source,
|
|
7650
|
-
file: profile.file
|
|
7651
|
-
},
|
|
7652
|
-
config: {
|
|
7653
|
-
path: configPath,
|
|
7654
|
-
status: loaded ? "loaded" : "absent",
|
|
7655
|
-
explicit: explicitConfig
|
|
7656
|
-
},
|
|
7657
|
-
platform: { value: platformValue, source: platformSource },
|
|
7658
|
-
app: { value: appValue, source: appSource },
|
|
7659
|
-
environment: {
|
|
7660
|
-
value: environmentValue,
|
|
7661
|
-
source: environmentSource
|
|
7662
|
-
},
|
|
7663
|
-
credentials: {
|
|
7664
|
-
developerTokenFile: tokenFile,
|
|
7665
|
-
scopedTokenFile
|
|
7666
|
-
}
|
|
7667
|
-
};
|
|
7668
|
-
}
|
|
7669
7697
|
function clean3(value) {
|
|
7670
7698
|
const normalized = value?.trim();
|
|
7671
7699
|
return normalized || void 0;
|
|
@@ -9025,7 +9053,7 @@ async function read2(url, headers, doFetch) {
|
|
|
9025
9053
|
}
|
|
9026
9054
|
|
|
9027
9055
|
// src/provision.ts
|
|
9028
|
-
var
|
|
9056
|
+
var import_apps7 = require("@odla-ai/apps");
|
|
9029
9057
|
var import_ai3 = require("@odla-ai/ai");
|
|
9030
9058
|
var import_node_process10 = __toESM(require("process"), 1);
|
|
9031
9059
|
|
|
@@ -9082,9 +9110,9 @@ async function responseText(res) {
|
|
|
9082
9110
|
}
|
|
9083
9111
|
|
|
9084
9112
|
// src/provision-credentials.ts
|
|
9085
|
-
var
|
|
9113
|
+
var import_apps5 = require("@odla-ai/apps");
|
|
9086
9114
|
async function provisionEnvCredentials(opts) {
|
|
9087
|
-
const tenantId = (0,
|
|
9115
|
+
const tenantId = (0, import_apps5.tenantIdFor)(opts.cfg.app.id, opts.env);
|
|
9088
9116
|
const prior = opts.credentials?.envs[opts.env];
|
|
9089
9117
|
let credentials = opts.credentials;
|
|
9090
9118
|
let dbKey = opts.cfg.services.includes("db") && !opts.rotateDb ? prior?.dbKey : void 0;
|
|
@@ -9178,18 +9206,13 @@ async function safeText4(res) {
|
|
|
9178
9206
|
|
|
9179
9207
|
// src/provision-helpers.ts
|
|
9180
9208
|
var import_ai2 = require("@odla-ai/ai");
|
|
9181
|
-
var
|
|
9182
|
-
function serviceRank(service) {
|
|
9183
|
-
if (service === "db") return 0;
|
|
9184
|
-
if (service === "calendar") return 2;
|
|
9185
|
-
return 1;
|
|
9186
|
-
}
|
|
9209
|
+
var import_apps6 = require("@odla-ai/apps");
|
|
9187
9210
|
function defaultSecretName(provider) {
|
|
9188
9211
|
const names = import_ai2.DEFAULT_SECRET_NAMES;
|
|
9189
9212
|
return names[provider] ?? `${provider}_api_key`;
|
|
9190
9213
|
}
|
|
9191
9214
|
async function assertTenantAdminAccess(doFetch, cfg, env, token) {
|
|
9192
|
-
const tenantId = (0,
|
|
9215
|
+
const tenantId = (0, import_apps6.tenantIdFor)(cfg.app.id, env);
|
|
9193
9216
|
const res = await doFetch(`${cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenantId)}/entitlements`, {
|
|
9194
9217
|
headers: { authorization: `Bearer ${token}` }
|
|
9195
9218
|
});
|
|
@@ -9298,7 +9321,7 @@ async function provision(options) {
|
|
|
9298
9321
|
}
|
|
9299
9322
|
const doFetch = options.fetch ?? fetch;
|
|
9300
9323
|
const token = await getDeveloperToken(cfg, options, doFetch, out);
|
|
9301
|
-
const apps = (0,
|
|
9324
|
+
const apps = (0, import_apps7.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
|
|
9302
9325
|
const existing = await apps.resolveApp(cfg.app.id);
|
|
9303
9326
|
if (existing) {
|
|
9304
9327
|
out.log(`app: ${cfg.app.id} already exists`);
|
|
@@ -9309,7 +9332,7 @@ async function provision(options) {
|
|
|
9309
9332
|
for (const env of cfg.envs) {
|
|
9310
9333
|
await assertTenantAdminAccess(doFetch, cfg, env, token);
|
|
9311
9334
|
}
|
|
9312
|
-
const serviceOrder =
|
|
9335
|
+
const serviceOrder = (0, import_apps7.orderAppServices)(cfg.services);
|
|
9313
9336
|
for (const env of cfg.envs) {
|
|
9314
9337
|
for (const service of serviceOrder) {
|
|
9315
9338
|
if (service === "ai") {
|
|
@@ -9342,7 +9365,7 @@ async function provision(options) {
|
|
|
9342
9365
|
}
|
|
9343
9366
|
}
|
|
9344
9367
|
for (const env of cfg.envs) {
|
|
9345
|
-
const tenantId = (0,
|
|
9368
|
+
const tenantId = (0, import_apps7.tenantIdFor)(cfg.app.id, env);
|
|
9346
9369
|
credentials = await provisionEnvCredentials({
|
|
9347
9370
|
cfg,
|
|
9348
9371
|
env,
|
|
@@ -11286,7 +11309,7 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
|
11286
11309
|
return;
|
|
11287
11310
|
}
|
|
11288
11311
|
if (command === "admin") {
|
|
11289
|
-
await adminCommand(parsed);
|
|
11312
|
+
await adminCommand(parsed, runtime);
|
|
11290
11313
|
return;
|
|
11291
11314
|
}
|
|
11292
11315
|
if (command === "agent") {
|