@odla-ai/cli 0.28.2 → 0.30.0
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 +59 -26
- package/REQUIREMENTS.md +4 -0
- package/dist/bin.cjs +333 -80
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-STJCWZJU.js → chunk-PPSXHZJI.js} +301 -81
- package/dist/chunk-PPSXHZJI.js.map +1 -0
- package/dist/{cli-2WZIFXA6.js → cli-CKBUVTJM.js} +2 -2
- package/dist/index.cjs +300 -80
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -5
- package/dist/index.d.ts +11 -5
- package/dist/index.js +1 -1
- package/dist/runtime/pi-agent.js +18728 -15
- package/package.json +1 -1
- package/skills/odla/SKILL.md +12 -7
- package/skills/odla/references/build.md +8 -2
- package/skills/odla/references/co-owners.md +3 -2
- package/skills/odla/references/pm-work-intake.md +5 -3
- package/skills/odla/references/pm.md +4 -0
- package/skills/odla-migrate/references/secrets-map.md +22 -22
- package/skills/odla-migrate/references/troubleshooting.md +14 -16
- package/dist/chunk-STJCWZJU.js.map +0 -1
- /package/dist/{cli-2WZIFXA6.js.map → cli-CKBUVTJM.js.map} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
runCli
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-PPSXHZJI.js";
|
|
5
5
|
import {
|
|
6
6
|
exitCodeFor
|
|
7
7
|
} from "./chunk-UKLSRQ5J.js";
|
|
@@ -9,4 +9,4 @@ export {
|
|
|
9
9
|
exitCodeFor,
|
|
10
10
|
runCli
|
|
11
11
|
};
|
|
12
|
-
//# sourceMappingURL=cli-
|
|
12
|
+
//# sourceMappingURL=cli-CKBUVTJM.js.map
|
package/dist/index.cjs
CHANGED
|
@@ -1218,7 +1218,7 @@ var DEFAULT_ENVS = ["dev"];
|
|
|
1218
1218
|
var DEFAULT_SERVICES = ["db", "ai"];
|
|
1219
1219
|
var configImportSerial = 0;
|
|
1220
1220
|
var GOOGLE_CALENDAR_EVENTS_SCOPE = "https://www.googleapis.com/auth/calendar.events";
|
|
1221
|
-
async function loadProjectConfig(configPath = "odla.config.mjs") {
|
|
1221
|
+
async function loadProjectConfig(configPath = "odla.config.mjs", options = {}) {
|
|
1222
1222
|
const resolved = (0, import_node_path4.resolve)(configPath);
|
|
1223
1223
|
if (!(0, import_node_fs4.existsSync)(resolved)) {
|
|
1224
1224
|
throw new Error(`config not found: ${configPath}. Run "odla-ai init" first or pass --config.`);
|
|
@@ -1231,7 +1231,7 @@ async function loadProjectConfig(configPath = "odla.config.mjs") {
|
|
|
1231
1231
|
const envs = unique2(raw.envs?.length ? raw.envs : DEFAULT_ENVS);
|
|
1232
1232
|
const services = unique2(raw.services?.length ? raw.services : DEFAULT_SERVICES);
|
|
1233
1233
|
validateServices(services, resolved);
|
|
1234
|
-
validateCalendarConfig(raw, envs, services, resolved);
|
|
1234
|
+
validateCalendarConfig(raw, unique2([...envs, ...options.additionalEnvs ?? []]), services, resolved);
|
|
1235
1235
|
const local = {
|
|
1236
1236
|
tokenFile: (0, import_node_path4.resolve)(rootDir, raw.local?.tokenFile ?? ".odla/dev-token.json"),
|
|
1237
1237
|
credentialsFile: (0, import_node_path4.resolve)(rootDir, raw.local?.credentialsFile ?? ".odla/credentials.local.json"),
|
|
@@ -1281,12 +1281,12 @@ function buildPlan(cfg) {
|
|
|
1281
1281
|
}
|
|
1282
1282
|
function calendarServiceConfig(cfg, env) {
|
|
1283
1283
|
if (!cfg.services.includes("calendar")) throw new Error("calendar service is not enabled in config services");
|
|
1284
|
-
if (!cfg.envs.includes(env)) throw new Error(`calendar env "${env}" is not declared in config envs`);
|
|
1284
|
+
if (!cfg.envs.includes(env) && env !== "prod") throw new Error(`calendar env "${env}" is not declared in config envs`);
|
|
1285
1285
|
const google = cfg.calendar?.google;
|
|
1286
1286
|
if (!google) throw new Error("calendar.google is required when the calendar service is enabled");
|
|
1287
|
-
const
|
|
1288
|
-
|
|
1289
|
-
);
|
|
1287
|
+
const configured = google.availabilityCalendars?.[env] ?? google.calendars?.[env];
|
|
1288
|
+
if (!configured?.length) throw new Error(`calendar.google.availabilityCalendars.${env} is required`);
|
|
1289
|
+
const availability = unique2(configured.map((id) => id.trim()));
|
|
1290
1290
|
return {
|
|
1291
1291
|
provider: "google",
|
|
1292
1292
|
access: "book",
|
|
@@ -1353,13 +1353,18 @@ function validateCalendarConfig(cfg, envs, services, path) {
|
|
|
1353
1353
|
}
|
|
1354
1354
|
const availability = google[availabilityKey];
|
|
1355
1355
|
if (!isRecord6(availability)) throw new Error(`${path}: calendar.google.${availabilityKey} must map env names to calendar ids`);
|
|
1356
|
-
const unknownEnv = Object.keys(availability).find((env) => !envs.includes(env));
|
|
1356
|
+
const unknownEnv = Object.keys(availability).find((env) => !envs.includes(env) && env !== "prod");
|
|
1357
1357
|
if (unknownEnv) throw new Error(`${path}: calendar.google.${availabilityKey}.${unknownEnv} is not in config envs`);
|
|
1358
1358
|
for (const env of envs) {
|
|
1359
1359
|
const ids = availability[env];
|
|
1360
1360
|
if (!Array.isArray(ids) || ids.length === 0) {
|
|
1361
1361
|
throw new Error(`${path}: calendar.google.${availabilityKey}.${env} must be a non-empty array`);
|
|
1362
1362
|
}
|
|
1363
|
+
}
|
|
1364
|
+
for (const [env, ids] of Object.entries(availability)) {
|
|
1365
|
+
if (!Array.isArray(ids) || ids.length === 0) {
|
|
1366
|
+
throw new Error(`${path}: calendar.google.${availabilityKey}.${env} must be a non-empty array`);
|
|
1367
|
+
}
|
|
1363
1368
|
if (ids.length > 10) {
|
|
1364
1369
|
throw new Error(`${path}: calendar.google.${availabilityKey}.${env} must contain at most 10 calendar ids`);
|
|
1365
1370
|
}
|
|
@@ -1369,7 +1374,7 @@ function validateCalendarConfig(cfg, envs, services, path) {
|
|
|
1369
1374
|
}
|
|
1370
1375
|
if (google.bookingCalendar !== void 0) {
|
|
1371
1376
|
if (!isRecord6(google.bookingCalendar)) throw new Error(`${path}: calendar.google.bookingCalendar must map env names to one calendar id`);
|
|
1372
|
-
const unknownBookingEnv = Object.keys(google.bookingCalendar).find((env) => !envs.includes(env));
|
|
1377
|
+
const unknownBookingEnv = Object.keys(google.bookingCalendar).find((env) => !envs.includes(env) && env !== "prod");
|
|
1373
1378
|
if (unknownBookingEnv) throw new Error(`${path}: calendar.google.bookingCalendar.${unknownBookingEnv} is not in config envs`);
|
|
1374
1379
|
for (const [env, value2] of Object.entries(google.bookingCalendar)) {
|
|
1375
1380
|
if (!safeText3(value2, 1024)) {
|
|
@@ -1379,7 +1384,7 @@ function validateCalendarConfig(cfg, envs, services, path) {
|
|
|
1379
1384
|
}
|
|
1380
1385
|
if (google.bookingPageUrl !== void 0) {
|
|
1381
1386
|
if (!isRecord6(google.bookingPageUrl)) throw new Error(`${path}: calendar.google.bookingPageUrl must map env names to HTTPS URLs or null`);
|
|
1382
|
-
const unknownBookingEnv = Object.keys(google.bookingPageUrl).find((env) => !envs.includes(env));
|
|
1387
|
+
const unknownBookingEnv = Object.keys(google.bookingPageUrl).find((env) => !envs.includes(env) && env !== "prod");
|
|
1383
1388
|
if (unknownBookingEnv) throw new Error(`${path}: calendar.google.bookingPageUrl.${unknownBookingEnv} is not in config envs`);
|
|
1384
1389
|
for (const [env, value2] of Object.entries(google.bookingPageUrl)) {
|
|
1385
1390
|
if (value2 !== null && !safeHttpsUrl(value2)) {
|
|
@@ -1925,7 +1930,7 @@ async function authCommand(parsed, deps = {}) {
|
|
|
1925
1930
|
var import_apps2 = require("@odla-ai/apps");
|
|
1926
1931
|
function resolveEnv(cfg, requested) {
|
|
1927
1932
|
const env = requested ?? (cfg.envs.includes("dev") ? "dev" : cfg.envs[0]);
|
|
1928
|
-
if (!env || !cfg.envs.includes(env)) {
|
|
1933
|
+
if (!env || !cfg.envs.includes(env) && env !== "prod") {
|
|
1929
1934
|
throw new Error(`env "${env ?? ""}" is not declared in ${cfg.configPath}`);
|
|
1930
1935
|
}
|
|
1931
1936
|
return env;
|
|
@@ -1935,12 +1940,10 @@ function resolveTenant(cfg, requested) {
|
|
|
1935
1940
|
return { env, tenant: (0, import_apps2.tenantIdFor)(cfg.app.id, env) };
|
|
1936
1941
|
}
|
|
1937
1942
|
function bothTenants(cfg) {
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
);
|
|
1943
|
-
}
|
|
1943
|
+
if (!cfg.envs.includes("dev")) {
|
|
1944
|
+
throw new Error(
|
|
1945
|
+
`env "dev" is not declared in ${cfg.configPath} \u2014 sandbox/live transfers require an existing sandbox`
|
|
1946
|
+
);
|
|
1944
1947
|
}
|
|
1945
1948
|
return { sandbox: (0, import_apps2.tenantIdFor)(cfg.app.id, "dev"), live: (0, import_apps2.tenantIdFor)(cfg.app.id, "prod") };
|
|
1946
1949
|
}
|
|
@@ -2028,7 +2031,7 @@ function errorMessage(body) {
|
|
|
2028
2031
|
// src/human-session.ts
|
|
2029
2032
|
async function requireStudioHuman(configPath, action2, destination = "app", env) {
|
|
2030
2033
|
const cfg = await loadProjectConfig(configPath);
|
|
2031
|
-
const appEnv = env && cfg.envs.includes(env) ? env : cfg.envs.includes("dev") ? "dev" : cfg.envs[0] ?? "prod";
|
|
2034
|
+
const appEnv = env && (cfg.envs.includes(env) || env === "prod") ? env : cfg.envs.includes("dev") ? "dev" : cfg.envs[0] ?? "prod";
|
|
2032
2035
|
const path = destination === "app" ? `/studio/apps/${encodeURIComponent(cfg.app.id)}/${encodeURIComponent(appEnv)}/settings/app` : `/studio/apps/${encodeURIComponent(cfg.app.id)}/${encodeURIComponent(appEnv)}/${destination}`;
|
|
2033
2036
|
throw new Error(
|
|
2034
2037
|
`human_session_required: ${action2} requires the signed-in owner in Studio. A CLI device token is an agent credential, so another approval or retry cannot work. ${new URL(path, cfg.platformUrl).href}`
|
|
@@ -2714,8 +2717,7 @@ async function ensureCalendarConnected(ctx, options) {
|
|
|
2714
2717
|
async function lifecycleContext(options, optionalProjectCapabilities = []) {
|
|
2715
2718
|
const cfg = await loadProjectConfig(options.configPath);
|
|
2716
2719
|
if (!cfg.services.includes("calendar")) throw new Error("calendar service is not enabled in config services");
|
|
2717
|
-
const env =
|
|
2718
|
-
if (!env || !cfg.envs.includes(env)) throw new Error(`env "${env ?? ""}" is not declared in ${options.configPath}`);
|
|
2720
|
+
const env = resolveEnv(cfg, options.env);
|
|
2719
2721
|
calendarServiceConfig(cfg, env);
|
|
2720
2722
|
const out = options.stdout ?? console;
|
|
2721
2723
|
const doFetch = options.fetch ?? fetch;
|
|
@@ -2916,8 +2918,7 @@ function printGroup(out, heading, items) {
|
|
|
2916
2918
|
var import_ai = require("@odla-ai/ai");
|
|
2917
2919
|
async function aiModels(options = {}) {
|
|
2918
2920
|
const cfg = await loadProjectConfig(options.configPath ?? "odla.config.mjs");
|
|
2919
|
-
const env = options.env
|
|
2920
|
-
if (!cfg.envs.includes(env)) throw new Error(`ai models env "${env}" is not declared in config envs`);
|
|
2921
|
+
const env = resolveEnv(cfg, options.env);
|
|
2921
2922
|
const url = new URL(`/registry/apps/${encodeURIComponent(cfg.app.id)}/public-config`, cfg.platformUrl);
|
|
2922
2923
|
url.searchParams.set("env", env);
|
|
2923
2924
|
const response2 = await (options.fetch ?? fetch)(url);
|
|
@@ -3960,6 +3961,41 @@ async function wranglerLoggedIn(run, cwd) {
|
|
|
3960
3961
|
return false;
|
|
3961
3962
|
}
|
|
3962
3963
|
}
|
|
3964
|
+
async function wranglerRuntimeTarget(run, opts) {
|
|
3965
|
+
const configPath = findWranglerConfig(opts.cwd);
|
|
3966
|
+
if (!configPath) throw new Error(`no wrangler config found in ${opts.cwd}`);
|
|
3967
|
+
const config = readWranglerConfig(configPath);
|
|
3968
|
+
if (!config) throw new Error("runtime credential delivery requires wrangler.json or wrangler.jsonc");
|
|
3969
|
+
const baseName = typeof config.name === "string" ? config.name : "";
|
|
3970
|
+
const envs = config.env && typeof config.env === "object" && !Array.isArray(config.env) ? config.env : {};
|
|
3971
|
+
const envConfig = opts.env && envs[opts.env] && typeof envs[opts.env] === "object" ? envs[opts.env] : {};
|
|
3972
|
+
const explicitName = typeof envConfig.name === "string" ? envConfig.name : "";
|
|
3973
|
+
const serviceEnvironments = config.legacy_env === false;
|
|
3974
|
+
if (serviceEnvironments && explicitName) {
|
|
3975
|
+
throw new Error("env.<name>.name is not allowed when wrangler legacy_env is false");
|
|
3976
|
+
}
|
|
3977
|
+
const scriptName = serviceEnvironments ? baseName : explicitName || (opts.env ? `${baseName}-${opts.env}` : baseName);
|
|
3978
|
+
if (!/^[a-z0-9][a-z0-9_-]{0,62}$/.test(scriptName)) {
|
|
3979
|
+
throw new Error("wrangler config must resolve an exact Worker name before credentials are issued");
|
|
3980
|
+
}
|
|
3981
|
+
const configuredAccount = typeof envConfig.account_id === "string" ? envConfig.account_id : typeof config.account_id === "string" ? config.account_id : "";
|
|
3982
|
+
const whoami = await run("npx", ["wrangler", "whoami"], { cwd: opts.cwd });
|
|
3983
|
+
if (whoami.code !== 0 || /not authenticated/i.test(`${whoami.stdout}${whoami.stderr}`)) {
|
|
3984
|
+
throw new Error(`wrangler is not logged in \u2014 run "wrangler login" (a browser step for the human)`);
|
|
3985
|
+
}
|
|
3986
|
+
const discovered = [...new Set(`${whoami.stdout}
|
|
3987
|
+
${whoami.stderr}`.match(/\b[a-f0-9]{32}\b/gi)?.map((id) => id.toLowerCase()) ?? [])];
|
|
3988
|
+
const accountId = configuredAccount.toLowerCase() || (discovered.length === 1 ? discovered[0] : "");
|
|
3989
|
+
if (!/^[a-f0-9]{32}$/.test(accountId) || configuredAccount && discovered.length > 0 && !discovered.includes(accountId)) {
|
|
3990
|
+
throw new Error("Wrangler account is ambiguous or does not match account_id in the config");
|
|
3991
|
+
}
|
|
3992
|
+
return {
|
|
3993
|
+
provider: "cloudflare",
|
|
3994
|
+
accountId,
|
|
3995
|
+
scriptName,
|
|
3996
|
+
...opts.env ? { environment: opts.env } : {}
|
|
3997
|
+
};
|
|
3998
|
+
}
|
|
3963
3999
|
function wranglerPutSecret(run, opts) {
|
|
3964
4000
|
const args = [
|
|
3965
4001
|
"wrangler",
|
|
@@ -3971,6 +4007,16 @@ function wranglerPutSecret(run, opts) {
|
|
|
3971
4007
|
];
|
|
3972
4008
|
return run("npx", args, { input: opts.value, cwd: opts.cwd });
|
|
3973
4009
|
}
|
|
4010
|
+
function wranglerBulkSecrets(run, opts) {
|
|
4011
|
+
const args = [
|
|
4012
|
+
"wrangler",
|
|
4013
|
+
"secret",
|
|
4014
|
+
"bulk",
|
|
4015
|
+
...opts.env ? ["--env", opts.env] : [],
|
|
4016
|
+
...opts.configPath ? ["--config", opts.configPath] : []
|
|
4017
|
+
];
|
|
4018
|
+
return run("npx", args, { input: JSON.stringify(opts.secrets), cwd: opts.cwd });
|
|
4019
|
+
}
|
|
3974
4020
|
|
|
3975
4021
|
// src/doctor-checks.ts
|
|
3976
4022
|
function lintRules(rules, entities, publicRead) {
|
|
@@ -4459,16 +4505,10 @@ var PROD_ENV_NAMES = /* @__PURE__ */ new Set(["prod", "production"]);
|
|
|
4459
4505
|
async function secretsPush(options) {
|
|
4460
4506
|
await secretsPushImpl(options, true);
|
|
4461
4507
|
}
|
|
4462
|
-
async function secretsPushAfterPreflight(options) {
|
|
4463
|
-
await secretsPushImpl(options, false);
|
|
4464
|
-
}
|
|
4465
4508
|
async function secretsPushImpl(options, preflight) {
|
|
4466
4509
|
const out = options.stdout ?? console;
|
|
4467
4510
|
const cfg = await loadProjectConfig(options.configPath);
|
|
4468
|
-
const env = options.env;
|
|
4469
|
-
if (!cfg.envs.includes(env)) {
|
|
4470
|
-
throw new Error(`env "${env}" is not in config envs (${cfg.envs.join(", ")})`);
|
|
4471
|
-
}
|
|
4511
|
+
const env = resolveEnv(cfg, options.env);
|
|
4472
4512
|
if (PROD_ENV_NAMES.has(env) && !options.yes) {
|
|
4473
4513
|
throw new Error(`refusing to push a secret to "${env}" without --yes`);
|
|
4474
4514
|
}
|
|
@@ -4578,14 +4618,12 @@ async function resolveVaultWrite(options) {
|
|
|
4578
4618
|
const out = options.stdout ?? console;
|
|
4579
4619
|
const doFetch = options.fetch ?? fetch;
|
|
4580
4620
|
const cfg = await loadProjectConfig(options.configPath);
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
if (PROD_ENV_NAMES2.has(options.env) && !options.yes) {
|
|
4585
|
-
throw new Error(`refusing to store a secret for "${options.env}" without --yes`);
|
|
4621
|
+
const env = resolveEnv(cfg, options.env);
|
|
4622
|
+
if (PROD_ENV_NAMES2.has(env) && !options.yes) {
|
|
4623
|
+
throw new Error(`refusing to store a secret for "${env}" without --yes`);
|
|
4586
4624
|
}
|
|
4587
4625
|
const value2 = await secretInputValue(options, "secret");
|
|
4588
|
-
return { cfg, tenantId: (0, import_apps10.tenantIdFor)(cfg.app.id,
|
|
4626
|
+
return { cfg, tenantId: (0, import_apps10.tenantIdFor)(cfg.app.id, env), value: value2, doFetch, out };
|
|
4589
4627
|
}
|
|
4590
4628
|
|
|
4591
4629
|
// src/skill.ts
|
|
@@ -4872,8 +4910,7 @@ function listFiles(dir) {
|
|
|
4872
4910
|
async function smoke(options) {
|
|
4873
4911
|
const out = options.stdout ?? console;
|
|
4874
4912
|
const cfg = await loadProjectConfig(options.configPath);
|
|
4875
|
-
const env =
|
|
4876
|
-
if (!cfg.envs.includes(env)) throw new Error(`env "${env}" is not declared in ${displayPath(cfg.configPath, cfg.rootDir)}`);
|
|
4913
|
+
const env = resolveEnv(cfg, options.env);
|
|
4877
4914
|
const credentials = readCredentials(cfg.local.credentialsFile);
|
|
4878
4915
|
if (!credentials) {
|
|
4879
4916
|
throw new Error(`local credentials missing: ${displayPath(cfg.local.credentialsFile, cfg.rootDir)}. Run "odla-ai provision --write-dev-vars".`);
|
|
@@ -8832,6 +8869,55 @@ function requireName(parsed) {
|
|
|
8832
8869
|
return name;
|
|
8833
8870
|
}
|
|
8834
8871
|
|
|
8872
|
+
// src/credential-command.ts
|
|
8873
|
+
async function responseError(response2) {
|
|
8874
|
+
return redactSecrets((await response2.text()).slice(0, 1e3));
|
|
8875
|
+
}
|
|
8876
|
+
async function credentialCommand(parsed, deps = {}) {
|
|
8877
|
+
const action2 = parsed.positionals[1] ?? "list";
|
|
8878
|
+
if (action2 !== "list" && action2 !== "revoke") {
|
|
8879
|
+
throw new Error(`unknown credentials action "${action2}". Try "odla-ai credentials list".`);
|
|
8880
|
+
}
|
|
8881
|
+
assertArgs(parsed, ["config", "env", "all", "json", "token", "email", "open"], action2 === "revoke" ? 3 : 2);
|
|
8882
|
+
const cfg = await loadProjectConfig(stringOpt(parsed.options.config) ?? "odla.config.mjs");
|
|
8883
|
+
const doFetch = deps.fetch ?? fetch;
|
|
8884
|
+
const out = deps.stdout ?? console;
|
|
8885
|
+
const token = await getDeveloperToken(cfg, {
|
|
8886
|
+
configPath: cfg.configPath,
|
|
8887
|
+
token: stringOpt(parsed.options.token),
|
|
8888
|
+
email: stringOpt(parsed.options.email),
|
|
8889
|
+
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
8890
|
+
openApprovalUrl: deps.openUrl
|
|
8891
|
+
}, doFetch, out, { optionalProjectCapabilities: ["app.manage"] });
|
|
8892
|
+
const base = `${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/runtime-credentials`;
|
|
8893
|
+
if (action2 === "revoke") {
|
|
8894
|
+
const id = parsed.positionals[2];
|
|
8895
|
+
if (!id) throw new Error("credentials revoke requires the exact receipt id from credentials list");
|
|
8896
|
+
const response3 = await doFetch(`${base}/${encodeURIComponent(id)}`, {
|
|
8897
|
+
method: "DELETE",
|
|
8898
|
+
headers: { authorization: `Bearer ${token}` }
|
|
8899
|
+
});
|
|
8900
|
+
if (!response3.ok) throw new Error(`runtime credential revoke failed (${response3.status}): ${await responseError(response3)}`);
|
|
8901
|
+
const body2 = await response3.json();
|
|
8902
|
+
return out.log(parsed.options.json === true ? JSON.stringify(body2, null, 2) : `revoked ${body2.receipt.id} (${body2.receipt.env} ${body2.receipt.target.scriptName})`);
|
|
8903
|
+
}
|
|
8904
|
+
const query = new URLSearchParams();
|
|
8905
|
+
const requestedEnv = stringOpt(parsed.options.env);
|
|
8906
|
+
if (requestedEnv) query.set("env", requestedEnv);
|
|
8907
|
+
const response2 = await doFetch(`${base}${query.size ? `?${query}` : ""}`, {
|
|
8908
|
+
headers: { authorization: `Bearer ${token}` }
|
|
8909
|
+
});
|
|
8910
|
+
if (!response2.ok) throw new Error(`runtime credential inventory failed (${response2.status}): ${await responseError(response2)}`);
|
|
8911
|
+
const body = await response2.json();
|
|
8912
|
+
const credentials = parsed.options.all === true ? body.credentials : body.credentials.filter((item) => item.state === "committed");
|
|
8913
|
+
if (parsed.options.json === true) return out.log(JSON.stringify({ credentials }, null, 2));
|
|
8914
|
+
if (!credentials.length) return out.log("no active runtime credential receipts");
|
|
8915
|
+
out.log("RECEIPT ENV TARGET CREATED");
|
|
8916
|
+
for (const item of credentials) {
|
|
8917
|
+
out.log(`${item.id} ${item.env} ${item.target.accountId ?? "local"}/${item.target.scriptName} ${new Date(item.createdAt).toISOString()}`);
|
|
8918
|
+
}
|
|
8919
|
+
}
|
|
8920
|
+
|
|
8835
8921
|
// src/help-usage.ts
|
|
8836
8922
|
var USAGE_SECTION = `
|
|
8837
8923
|
Start here:
|
|
@@ -8949,7 +9035,9 @@ Usage:
|
|
|
8949
9035
|
odla-ai security report <job-id> [--json]
|
|
8950
9036
|
odla-ai security run [target] --ack-redacted-source [--env dev] [--profile odla] [--fail-on high]
|
|
8951
9037
|
odla-ai security run [target] --self --ack-redacted-source
|
|
8952
|
-
odla-ai provision [--config odla.config.mjs] [--email <odla-account>] [--request-grant] [--wait <seconds>] [--dry-run] [--push-secrets] [--rotate-o11y-token] [--write-dev-vars[=path]] [--yes]
|
|
9038
|
+
odla-ai provision [--live] [--config odla.config.mjs] [--email <odla-account>] [--request-grant] [--wait <seconds>] [--dry-run] [--push-secrets] [--rotate-o11y-token] [--write-dev-vars[=path]] [--yes]
|
|
9039
|
+
odla-ai credentials list [--config odla.config.mjs] [--env dev] [--all] [--json]
|
|
9040
|
+
odla-ai credentials revoke <receipt-id> [--config odla.config.mjs] [--json]
|
|
8953
9041
|
odla-ai smoke [--config odla.config.mjs] [--env dev] [--email <odla-account>] [--no-open]
|
|
8954
9042
|
odla-ai skill install [--dir <project>] [--agent <name>] [--global] [--force]
|
|
8955
9043
|
odla-ai secrets push --env <env> [--config odla.config.mjs] [--dry-run] [--yes]
|
|
@@ -9049,6 +9137,9 @@ Commands:
|
|
|
9049
9137
|
platform Read canonical fleet health, releases, provider load/freshness,
|
|
9050
9138
|
explicit unknowns, and next actions through a read-only grant.
|
|
9051
9139
|
provision Register services, compose integrations, persist credentials, optionally push secrets.
|
|
9140
|
+
"provision --live --yes" initializes only the live instance of
|
|
9141
|
+
an existing sandbox app and enables every configured service;
|
|
9142
|
+
no edit to the dev-first envs list is required.
|
|
9052
9143
|
smoke Verify credentials, public-config, composed schema, db aggregate, and integration probes.
|
|
9053
9144
|
skill Same installer; --agent accepts all, claude, codex, cursor,
|
|
9054
9145
|
copilot, gemini, or agents (repeatable or comma-separated).
|
|
@@ -9061,15 +9152,21 @@ Safety:
|
|
|
9061
9152
|
Every app has two databases on odla.ai: a sandbox (env "dev", tenant
|
|
9062
9153
|
<appId>--dev) and a live one (env "prod", tenant <appId>). Both are served by
|
|
9063
9154
|
production odla.ai \u2014 there is no separate odla to point at. New projects get
|
|
9064
|
-
the sandbox only.
|
|
9065
|
-
|
|
9155
|
+
the sandbox only. Run "provision --live --dry-run", then "provision --live
|
|
9156
|
+
--yes" to initialize live from that existing sandbox project's config. Add
|
|
9157
|
+
"prod" to envs only when ordinary provision runs should manage both; use
|
|
9158
|
+
--dry-run first to inspect either resolved plan.
|
|
9066
9159
|
A real provision first verifies both the released CLI version and the exact
|
|
9067
9160
|
pinned versions of every external @odla-ai runtime module before importing
|
|
9068
9161
|
the command graph. A stale workspace module blocks with its resolved path and
|
|
9069
9162
|
tells the agent to update/rebase, npm ci, and rebuild.
|
|
9070
|
-
Provision caches the approved developer token
|
|
9071
|
-
|
|
9072
|
-
|
|
9163
|
+
Provision caches the approved developer token under .odla/ with mode 0600.
|
|
9164
|
+
Local-only development provisioning may also cache that developer's service
|
|
9165
|
+
credentials there. \`provision --push-secrets\` instead stages a fresh,
|
|
9166
|
+
independently revocable credential set through the approved handshake,
|
|
9167
|
+
transfers the complete set to the exact Worker with \`wrangler secret bulk\`
|
|
9168
|
+
over stdin, and never writes its plaintext under .odla. Secret push preflights
|
|
9169
|
+
Wrangler before shown-once issuance; it never rotates sibling credentials.
|
|
9073
9170
|
Projectless PM, Discussions, o11y, runbook, and identity commands use
|
|
9074
9171
|
--platform/--app/--env, ODLA_PLATFORM_URL/ODLA_APP_ID/ODLA_ENV, and
|
|
9075
9172
|
ODLA_DEV_TOKEN. Save non-secret scope metadata with "context save", then
|
|
@@ -10993,7 +11090,7 @@ async function issueO11yToken(opts) {
|
|
|
10993
11090
|
);
|
|
10994
11091
|
if (res.status === 409 && !opts.rotateO11y) {
|
|
10995
11092
|
throw new Error(
|
|
10996
|
-
`o11y token already exists for env "${opts.env}", but its shown-once value is not in the local credentials file; run "odla-ai provision --
|
|
11093
|
+
`o11y token already exists for env "${opts.env}", but its shown-once value is not in the local credentials file; run "odla-ai provision --push-secrets" to install a separate runtime credential without rotating siblings`
|
|
10997
11094
|
);
|
|
10998
11095
|
}
|
|
10999
11096
|
if (!res.ok) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) failed: ${res.status} ${await safeText7(res)}`);
|
|
@@ -11009,23 +11106,131 @@ async function safeText7(res) {
|
|
|
11009
11106
|
}
|
|
11010
11107
|
}
|
|
11011
11108
|
|
|
11109
|
+
// src/runtime-credentials.ts
|
|
11110
|
+
var import_node_crypto5 = require("crypto");
|
|
11111
|
+
function runtimeUrl(cfg, suffix = "") {
|
|
11112
|
+
return `${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/runtime-credentials${suffix}`;
|
|
11113
|
+
}
|
|
11114
|
+
async function safeError(response2) {
|
|
11115
|
+
const text2 = await response2.text();
|
|
11116
|
+
return redactSecrets(text2.slice(0, 1e3));
|
|
11117
|
+
}
|
|
11118
|
+
async function finish(doFetch, cfg, token, sessionId, method) {
|
|
11119
|
+
return doFetch(runtimeUrl(cfg, `/${encodeURIComponent(sessionId)}`), {
|
|
11120
|
+
method,
|
|
11121
|
+
headers: { authorization: `Bearer ${token}` }
|
|
11122
|
+
});
|
|
11123
|
+
}
|
|
11124
|
+
async function deliverRuntimeCredentials(cfg, options) {
|
|
11125
|
+
const doFetch = options.fetch ?? fetch;
|
|
11126
|
+
const run = options.runner ?? defaultRunner;
|
|
11127
|
+
const wranglerEnv = options.env === "prod" || options.env === "production" ? void 0 : options.env;
|
|
11128
|
+
const target = await wranglerRuntimeTarget(run, {
|
|
11129
|
+
cwd: cfg.rootDir,
|
|
11130
|
+
env: wranglerEnv
|
|
11131
|
+
});
|
|
11132
|
+
const issue = await doFetch(runtimeUrl(cfg), {
|
|
11133
|
+
method: "POST",
|
|
11134
|
+
headers: {
|
|
11135
|
+
authorization: `Bearer ${options.developerToken}`,
|
|
11136
|
+
"content-type": "application/json"
|
|
11137
|
+
},
|
|
11138
|
+
body: JSON.stringify({
|
|
11139
|
+
env: options.env,
|
|
11140
|
+
idempotencyKey: `wrangler:${(0, import_node_crypto5.randomUUID)()}`,
|
|
11141
|
+
target
|
|
11142
|
+
})
|
|
11143
|
+
});
|
|
11144
|
+
const body = await issue.json().catch(() => null);
|
|
11145
|
+
if (!issue.ok || typeof body?.sessionId !== "string" || !body.secrets || typeof body.secrets !== "object") {
|
|
11146
|
+
const detail = body?.error?.code ?? body?.error?.message ?? issue.status;
|
|
11147
|
+
throw new Error(`runtime credential staging failed: ${String(detail)}`);
|
|
11148
|
+
}
|
|
11149
|
+
const secrets = body.secrets;
|
|
11150
|
+
const values = {};
|
|
11151
|
+
for (const name of ["ODLA_API_KEY", "ODLA_O11Y_TOKEN"]) {
|
|
11152
|
+
const value2 = secrets[name];
|
|
11153
|
+
if (typeof value2 === "string" && value2) values[name] = value2;
|
|
11154
|
+
}
|
|
11155
|
+
if (Object.keys(values).length === 0) {
|
|
11156
|
+
await finish(doFetch, cfg, options.developerToken, body.sessionId, "DELETE");
|
|
11157
|
+
throw new Error("runtime credential staging returned no configured service credentials");
|
|
11158
|
+
}
|
|
11159
|
+
const pushed = await wranglerBulkSecrets(run, {
|
|
11160
|
+
secrets: values,
|
|
11161
|
+
env: wranglerEnv,
|
|
11162
|
+
cwd: cfg.rootDir
|
|
11163
|
+
});
|
|
11164
|
+
if (pushed.code !== 0) {
|
|
11165
|
+
await finish(doFetch, cfg, options.developerToken, body.sessionId, "DELETE");
|
|
11166
|
+
const detail = redactSecrets(`${pushed.stderr || pushed.stdout}`.trim());
|
|
11167
|
+
throw new Error(`wrangler secret bulk failed (exit ${pushed.code}): ${detail}`);
|
|
11168
|
+
}
|
|
11169
|
+
const committed = await finish(
|
|
11170
|
+
doFetch,
|
|
11171
|
+
cfg,
|
|
11172
|
+
options.developerToken,
|
|
11173
|
+
body.sessionId,
|
|
11174
|
+
"POST"
|
|
11175
|
+
);
|
|
11176
|
+
if (!committed.ok) {
|
|
11177
|
+
await finish(doFetch, cfg, options.developerToken, body.sessionId, "DELETE");
|
|
11178
|
+
throw new Error(`runtime credential receipt commit failed: ${await safeError(committed)}`);
|
|
11179
|
+
}
|
|
11180
|
+
options.stdout?.log(
|
|
11181
|
+
`${options.env}: installed ${Object.keys(values).join(" + ")} on ${target.accountId}/${target.scriptName}${target.environment ? ` (${target.environment})` : ""}; plaintext stayed in memory`
|
|
11182
|
+
);
|
|
11183
|
+
return {
|
|
11184
|
+
sessionId: body.sessionId,
|
|
11185
|
+
target,
|
|
11186
|
+
...values.ODLA_API_KEY ? { dbKey: values.ODLA_API_KEY } : {},
|
|
11187
|
+
...values.ODLA_O11Y_TOKEN ? { o11yToken: values.ODLA_O11Y_TOKEN } : {}
|
|
11188
|
+
};
|
|
11189
|
+
}
|
|
11190
|
+
|
|
11191
|
+
// src/provision-live.ts
|
|
11192
|
+
function liveProvisionConfig(cfg) {
|
|
11193
|
+
if (!cfg.envs.includes("dev")) {
|
|
11194
|
+
throw new Error(
|
|
11195
|
+
`--live initializes an existing sandbox, but env "dev" is not declared in ${cfg.configPath}; use normal "odla-ai provision --yes" for a prod-only project`
|
|
11196
|
+
);
|
|
11197
|
+
}
|
|
11198
|
+
if (cfg.services.includes("calendar")) {
|
|
11199
|
+
const google = cfg.calendar?.google;
|
|
11200
|
+
const availability = google?.availabilityCalendars ?? google?.calendars;
|
|
11201
|
+
if (!availability?.prod?.length) {
|
|
11202
|
+
throw new Error(
|
|
11203
|
+
`--live requires calendar.google.availabilityCalendars.prod in ${cfg.configPath}; live uses a separate Google Calendar connection, so sandbox calendar ids are not copied`
|
|
11204
|
+
);
|
|
11205
|
+
}
|
|
11206
|
+
}
|
|
11207
|
+
return { ...cfg, envs: ["prod"] };
|
|
11208
|
+
}
|
|
11209
|
+
|
|
11012
11210
|
// src/provision.ts
|
|
11013
11211
|
async function provision(options) {
|
|
11014
11212
|
const out = options.stdout ?? console;
|
|
11015
|
-
const
|
|
11213
|
+
const loaded = await loadProjectConfig(
|
|
11214
|
+
options.configPath,
|
|
11215
|
+
options.live ? { additionalEnvs: ["prod"] } : void 0
|
|
11216
|
+
);
|
|
11217
|
+
const cfg = options.live ? liveProvisionConfig(loaded) : loaded;
|
|
11016
11218
|
const plan = buildPlan(cfg);
|
|
11017
11219
|
const hasDb = cfg.services.includes("db");
|
|
11018
11220
|
const hasO11y = cfg.services.includes("o11y");
|
|
11019
11221
|
if (options.rotateO11yToken && !hasO11y) {
|
|
11020
11222
|
throw new Error("--rotate-o11y-token requires the o11y service in odla.config.mjs");
|
|
11021
11223
|
}
|
|
11022
|
-
if (options.pushSecrets && options.
|
|
11023
|
-
throw new Error(
|
|
11224
|
+
if (options.pushSecrets && (options.rotateKeys || options.rotateO11yToken)) {
|
|
11225
|
+
throw new Error(
|
|
11226
|
+
"--push-secrets always issues an independent runtime credential set; do not combine it with destructive rotation flags"
|
|
11227
|
+
);
|
|
11024
11228
|
}
|
|
11025
11229
|
out.log(`odla-ai: ${plan.appName} (${plan.appId})`);
|
|
11026
11230
|
out.log(` platform: ${plan.platformUrl}`);
|
|
11027
11231
|
out.log(` db: ${plan.dbEndpoint}`);
|
|
11028
11232
|
out.log(` envs: ${plan.envs.join(", ")}`);
|
|
11233
|
+
if (options.live) out.log(" target: live setup (existing sandbox app)");
|
|
11029
11234
|
out.log(` services: ${plan.services.join(", ")}`);
|
|
11030
11235
|
out.log(` integrations: ${plan.integrations.length ? plan.integrations.join(", ") : "none"}`);
|
|
11031
11236
|
const productionEnvs = plan.envs.filter((env) => env === "prod" || env === "production");
|
|
@@ -11056,7 +11261,7 @@ async function provision(options) {
|
|
|
11056
11261
|
out.log(` secrets: ${options.pushSecrets ? "push configured Worker secrets" : "local only"}`);
|
|
11057
11262
|
return;
|
|
11058
11263
|
}
|
|
11059
|
-
let credentials = readCredentials(cfg.local.credentialsFile);
|
|
11264
|
+
let credentials = options.pushSecrets ? null : readCredentials(cfg.local.credentialsFile);
|
|
11060
11265
|
if (credentials && credentials.appId !== cfg.app.id) {
|
|
11061
11266
|
throw new Error(
|
|
11062
11267
|
`credentials at ${displayPath(cfg.local.credentialsFile, cfg.rootDir)} are for "${credentials.appId}", not "${cfg.app.id}"`
|
|
@@ -11065,7 +11270,7 @@ async function provision(options) {
|
|
|
11065
11270
|
const rotatesO11y = !!(options.rotateKeys || options.rotateO11yToken);
|
|
11066
11271
|
const missingO11y = cfg.envs.some((env) => !credentials?.envs[env]?.o11yToken);
|
|
11067
11272
|
const missingDb = cfg.envs.some((env) => !credentials?.envs[env]?.dbKey);
|
|
11068
|
-
const losesShownOnceCredential = hasDb && (!!options.rotateKeys || missingDb) || hasO11y && (rotatesO11y || missingO11y);
|
|
11273
|
+
const losesShownOnceCredential = !options.pushSecrets && (hasDb && (!!options.rotateKeys || missingDb) || hasO11y && (rotatesO11y || missingO11y));
|
|
11069
11274
|
if (options.writeCredentials === false && losesShownOnceCredential) {
|
|
11070
11275
|
throw new Error("credential issuance/rotation requires the private credentials file; remove --no-write-credentials");
|
|
11071
11276
|
}
|
|
@@ -11086,6 +11291,10 @@ async function provision(options) {
|
|
|
11086
11291
|
const existing = await apps.resolveApp(cfg.app.id);
|
|
11087
11292
|
if (existing) {
|
|
11088
11293
|
out.log(`app: ${cfg.app.id} already exists`);
|
|
11294
|
+
} else if (options.live) {
|
|
11295
|
+
throw new Error(
|
|
11296
|
+
`cannot set up live for "${cfg.app.id}": the sandbox app does not exist yet; run "odla-ai provision" first`
|
|
11297
|
+
);
|
|
11089
11298
|
} else {
|
|
11090
11299
|
try {
|
|
11091
11300
|
await apps.createApp({ name: cfg.app.name, appId: cfg.app.id });
|
|
@@ -11135,38 +11344,42 @@ async function provision(options) {
|
|
|
11135
11344
|
await applyCalendarBookingPage(calendarCtx, calendarBookingPageUrl(cfg, env), out);
|
|
11136
11345
|
}
|
|
11137
11346
|
}
|
|
11347
|
+
let devVarsCredentials = credentials;
|
|
11138
11348
|
for (const env of cfg.envs) {
|
|
11139
11349
|
const tenantId = (0, import_apps12.tenantIdFor)(cfg.app.id, env);
|
|
11140
|
-
|
|
11141
|
-
cfg,
|
|
11142
|
-
env,
|
|
11143
|
-
developerToken: token,
|
|
11144
|
-
credentials,
|
|
11145
|
-
rotateDb: !!options.rotateKeys,
|
|
11146
|
-
rotateO11y: rotatesO11y,
|
|
11147
|
-
write: options.writeCredentials !== false,
|
|
11148
|
-
fetch: doFetch,
|
|
11149
|
-
stdout: out
|
|
11150
|
-
});
|
|
11151
|
-
const dbKey = credentials.envs[env]?.dbKey;
|
|
11350
|
+
let dbKey;
|
|
11152
11351
|
if (options.pushSecrets) {
|
|
11153
|
-
|
|
11154
|
-
|
|
11155
|
-
|
|
11156
|
-
|
|
11157
|
-
|
|
11158
|
-
|
|
11159
|
-
|
|
11160
|
-
|
|
11161
|
-
|
|
11162
|
-
|
|
11163
|
-
|
|
11164
|
-
|
|
11165
|
-
|
|
11166
|
-
|
|
11167
|
-
|
|
11168
|
-
|
|
11169
|
-
}
|
|
11352
|
+
const delivered = await deliverRuntimeCredentials(cfg, {
|
|
11353
|
+
env,
|
|
11354
|
+
developerToken: token,
|
|
11355
|
+
fetch: doFetch,
|
|
11356
|
+
runner: options.secretRunner,
|
|
11357
|
+
stdout: out
|
|
11358
|
+
});
|
|
11359
|
+
dbKey = delivered.dbKey;
|
|
11360
|
+
devVarsCredentials = mergeCredential(devVarsCredentials, {
|
|
11361
|
+
appId: cfg.app.id,
|
|
11362
|
+
platformUrl: cfg.platformUrl,
|
|
11363
|
+
dbEndpoint: cfg.dbEndpoint,
|
|
11364
|
+
env,
|
|
11365
|
+
tenantId,
|
|
11366
|
+
...delivered.dbKey ? { dbKey: delivered.dbKey } : {},
|
|
11367
|
+
...delivered.o11yToken ? { o11yToken: delivered.o11yToken } : {}
|
|
11368
|
+
});
|
|
11369
|
+
} else {
|
|
11370
|
+
credentials = await provisionEnvCredentials({
|
|
11371
|
+
cfg,
|
|
11372
|
+
env,
|
|
11373
|
+
developerToken: token,
|
|
11374
|
+
credentials,
|
|
11375
|
+
rotateDb: !!options.rotateKeys,
|
|
11376
|
+
rotateO11y: rotatesO11y,
|
|
11377
|
+
write: options.writeCredentials !== false,
|
|
11378
|
+
fetch: doFetch,
|
|
11379
|
+
stdout: out
|
|
11380
|
+
});
|
|
11381
|
+
devVarsCredentials = credentials;
|
|
11382
|
+
dbKey = credentials.envs[env]?.dbKey;
|
|
11170
11383
|
}
|
|
11171
11384
|
if (schema && dbKey) {
|
|
11172
11385
|
await postJson(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(tenantId)}/schema`, dbKey, { schema });
|
|
@@ -11190,13 +11403,13 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
|
|
|
11190
11403
|
}
|
|
11191
11404
|
}
|
|
11192
11405
|
}
|
|
11193
|
-
if (options.writeCredentials !== false && credentials) {
|
|
11406
|
+
if (!options.pushSecrets && options.writeCredentials !== false && credentials) {
|
|
11194
11407
|
const ignored = cfg.local.gitignore && gitignoreEntry(cfg.rootDir, cfg.local.credentialsFile);
|
|
11195
11408
|
out.log(`credentials: wrote ${displayPath(cfg.local.credentialsFile, cfg.rootDir)} (0600${ignored ? ", gitignored" : ""})`);
|
|
11196
11409
|
}
|
|
11197
|
-
if (devVarsTarget &&
|
|
11410
|
+
if (devVarsTarget && devVarsCredentials) {
|
|
11198
11411
|
const env = cfg.envs.includes("dev") ? "dev" : cfg.envs[0] ?? "prod";
|
|
11199
|
-
writeDevVars(devVarsTarget,
|
|
11412
|
+
writeDevVars(devVarsTarget, devVarsCredentials, env, o11yDevVars(cfg));
|
|
11200
11413
|
out.log(`dev vars: wrote ${displayPath(devVarsTarget, cfg.rootDir)} for ${env}`);
|
|
11201
11414
|
}
|
|
11202
11415
|
if (cfg.services.includes("calendar")) {
|
|
@@ -11287,6 +11500,7 @@ var COMMAND_SURFACE = {
|
|
|
11287
11500
|
code: { connect: {} },
|
|
11288
11501
|
config: { diff: {}, plan: {}, apply: {} },
|
|
11289
11502
|
context: { show: {}, list: {}, save: {}, remove: {} },
|
|
11503
|
+
credentials: { list: {}, revoke: {} },
|
|
11290
11504
|
// `watch`, `read`, `reply`, and `resolve` take a topic id from there on.
|
|
11291
11505
|
discuss: {
|
|
11292
11506
|
groups: {},
|
|
@@ -13024,6 +13238,10 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
|
13024
13238
|
await contextCommand(parsed, runtime);
|
|
13025
13239
|
return;
|
|
13026
13240
|
}
|
|
13241
|
+
if (command === "credentials") {
|
|
13242
|
+
await credentialCommand(parsed, runtime);
|
|
13243
|
+
return;
|
|
13244
|
+
}
|
|
13027
13245
|
if (command === "runbook") {
|
|
13028
13246
|
await runbookCommand(parsed, runtime);
|
|
13029
13247
|
return;
|
|
@@ -13091,6 +13309,7 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
|
13091
13309
|
async function provisionCommand(parsed, dependencies) {
|
|
13092
13310
|
assertArgs(parsed, [
|
|
13093
13311
|
"config",
|
|
13312
|
+
"live",
|
|
13094
13313
|
"dry-run",
|
|
13095
13314
|
"rotate-keys",
|
|
13096
13315
|
"rotate-o11y-token",
|
|
@@ -13107,6 +13326,7 @@ async function provisionCommand(parsed, dependencies) {
|
|
|
13107
13326
|
const writeDevVars2 = parsed.options["write-dev-vars"];
|
|
13108
13327
|
const options = {
|
|
13109
13328
|
configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
|
|
13329
|
+
live: parsed.options.live === true,
|
|
13110
13330
|
dryRun: parsed.options["dry-run"] === true,
|
|
13111
13331
|
rotateKeys: parsed.options["rotate-keys"] === true,
|
|
13112
13332
|
rotateO11yToken: parsed.options["rotate-o11y-token"] === true,
|