@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
package/dist/bin.cjs
CHANGED
|
@@ -1410,7 +1410,7 @@ var init_integration_validation = __esm({
|
|
|
1410
1410
|
});
|
|
1411
1411
|
|
|
1412
1412
|
// src/config.ts
|
|
1413
|
-
async function loadProjectConfig(configPath = "odla.config.mjs") {
|
|
1413
|
+
async function loadProjectConfig(configPath = "odla.config.mjs", options = {}) {
|
|
1414
1414
|
const resolved = (0, import_node_path5.resolve)(configPath);
|
|
1415
1415
|
if (!(0, import_node_fs7.existsSync)(resolved)) {
|
|
1416
1416
|
throw new Error(`config not found: ${configPath}. Run "odla-ai init" first or pass --config.`);
|
|
@@ -1423,7 +1423,7 @@ async function loadProjectConfig(configPath = "odla.config.mjs") {
|
|
|
1423
1423
|
const envs = unique2(raw.envs?.length ? raw.envs : DEFAULT_ENVS);
|
|
1424
1424
|
const services = unique2(raw.services?.length ? raw.services : DEFAULT_SERVICES);
|
|
1425
1425
|
validateServices(services, resolved);
|
|
1426
|
-
validateCalendarConfig(raw, envs, services, resolved);
|
|
1426
|
+
validateCalendarConfig(raw, unique2([...envs, ...options.additionalEnvs ?? []]), services, resolved);
|
|
1427
1427
|
const local = {
|
|
1428
1428
|
tokenFile: (0, import_node_path5.resolve)(rootDir, raw.local?.tokenFile ?? ".odla/dev-token.json"),
|
|
1429
1429
|
credentialsFile: (0, import_node_path5.resolve)(rootDir, raw.local?.credentialsFile ?? ".odla/credentials.local.json"),
|
|
@@ -1473,12 +1473,12 @@ function buildPlan(cfg) {
|
|
|
1473
1473
|
}
|
|
1474
1474
|
function calendarServiceConfig(cfg, env) {
|
|
1475
1475
|
if (!cfg.services.includes("calendar")) throw new Error("calendar service is not enabled in config services");
|
|
1476
|
-
if (!cfg.envs.includes(env)) throw new Error(`calendar env "${env}" is not declared in config envs`);
|
|
1476
|
+
if (!cfg.envs.includes(env) && env !== "prod") throw new Error(`calendar env "${env}" is not declared in config envs`);
|
|
1477
1477
|
const google = cfg.calendar?.google;
|
|
1478
1478
|
if (!google) throw new Error("calendar.google is required when the calendar service is enabled");
|
|
1479
|
-
const
|
|
1480
|
-
|
|
1481
|
-
);
|
|
1479
|
+
const configured = google.availabilityCalendars?.[env] ?? google.calendars?.[env];
|
|
1480
|
+
if (!configured?.length) throw new Error(`calendar.google.availabilityCalendars.${env} is required`);
|
|
1481
|
+
const availability = unique2(configured.map((id) => id.trim()));
|
|
1482
1482
|
return {
|
|
1483
1483
|
provider: "google",
|
|
1484
1484
|
access: "book",
|
|
@@ -1545,13 +1545,18 @@ function validateCalendarConfig(cfg, envs, services, path) {
|
|
|
1545
1545
|
}
|
|
1546
1546
|
const availability = google[availabilityKey];
|
|
1547
1547
|
if (!isRecord6(availability)) throw new Error(`${path}: calendar.google.${availabilityKey} must map env names to calendar ids`);
|
|
1548
|
-
const unknownEnv = Object.keys(availability).find((env) => !envs.includes(env));
|
|
1548
|
+
const unknownEnv = Object.keys(availability).find((env) => !envs.includes(env) && env !== "prod");
|
|
1549
1549
|
if (unknownEnv) throw new Error(`${path}: calendar.google.${availabilityKey}.${unknownEnv} is not in config envs`);
|
|
1550
1550
|
for (const env of envs) {
|
|
1551
1551
|
const ids = availability[env];
|
|
1552
1552
|
if (!Array.isArray(ids) || ids.length === 0) {
|
|
1553
1553
|
throw new Error(`${path}: calendar.google.${availabilityKey}.${env} must be a non-empty array`);
|
|
1554
1554
|
}
|
|
1555
|
+
}
|
|
1556
|
+
for (const [env, ids] of Object.entries(availability)) {
|
|
1557
|
+
if (!Array.isArray(ids) || ids.length === 0) {
|
|
1558
|
+
throw new Error(`${path}: calendar.google.${availabilityKey}.${env} must be a non-empty array`);
|
|
1559
|
+
}
|
|
1555
1560
|
if (ids.length > 10) {
|
|
1556
1561
|
throw new Error(`${path}: calendar.google.${availabilityKey}.${env} must contain at most 10 calendar ids`);
|
|
1557
1562
|
}
|
|
@@ -1561,7 +1566,7 @@ function validateCalendarConfig(cfg, envs, services, path) {
|
|
|
1561
1566
|
}
|
|
1562
1567
|
if (google.bookingCalendar !== void 0) {
|
|
1563
1568
|
if (!isRecord6(google.bookingCalendar)) throw new Error(`${path}: calendar.google.bookingCalendar must map env names to one calendar id`);
|
|
1564
|
-
const unknownBookingEnv = Object.keys(google.bookingCalendar).find((env) => !envs.includes(env));
|
|
1569
|
+
const unknownBookingEnv = Object.keys(google.bookingCalendar).find((env) => !envs.includes(env) && env !== "prod");
|
|
1565
1570
|
if (unknownBookingEnv) throw new Error(`${path}: calendar.google.bookingCalendar.${unknownBookingEnv} is not in config envs`);
|
|
1566
1571
|
for (const [env, value2] of Object.entries(google.bookingCalendar)) {
|
|
1567
1572
|
if (!safeText3(value2, 1024)) {
|
|
@@ -1571,7 +1576,7 @@ function validateCalendarConfig(cfg, envs, services, path) {
|
|
|
1571
1576
|
}
|
|
1572
1577
|
if (google.bookingPageUrl !== void 0) {
|
|
1573
1578
|
if (!isRecord6(google.bookingPageUrl)) throw new Error(`${path}: calendar.google.bookingPageUrl must map env names to HTTPS URLs or null`);
|
|
1574
|
-
const unknownBookingEnv = Object.keys(google.bookingPageUrl).find((env) => !envs.includes(env));
|
|
1579
|
+
const unknownBookingEnv = Object.keys(google.bookingPageUrl).find((env) => !envs.includes(env) && env !== "prod");
|
|
1575
1580
|
if (unknownBookingEnv) throw new Error(`${path}: calendar.google.bookingPageUrl.${unknownBookingEnv} is not in config envs`);
|
|
1576
1581
|
for (const [env, value2] of Object.entries(google.bookingPageUrl)) {
|
|
1577
1582
|
if (value2 !== null && !safeHttpsUrl(value2)) {
|
|
@@ -2187,7 +2192,7 @@ var init_auth_command = __esm({
|
|
|
2187
2192
|
// src/tenant.ts
|
|
2188
2193
|
function resolveEnv(cfg, requested) {
|
|
2189
2194
|
const env = requested ?? (cfg.envs.includes("dev") ? "dev" : cfg.envs[0]);
|
|
2190
|
-
if (!env || !cfg.envs.includes(env)) {
|
|
2195
|
+
if (!env || !cfg.envs.includes(env) && env !== "prod") {
|
|
2191
2196
|
throw new Error(`env "${env ?? ""}" is not declared in ${cfg.configPath}`);
|
|
2192
2197
|
}
|
|
2193
2198
|
return env;
|
|
@@ -2197,12 +2202,10 @@ function resolveTenant(cfg, requested) {
|
|
|
2197
2202
|
return { env, tenant: (0, import_apps2.tenantIdFor)(cfg.app.id, env) };
|
|
2198
2203
|
}
|
|
2199
2204
|
function bothTenants(cfg) {
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
);
|
|
2205
|
-
}
|
|
2205
|
+
if (!cfg.envs.includes("dev")) {
|
|
2206
|
+
throw new Error(
|
|
2207
|
+
`env "dev" is not declared in ${cfg.configPath} \u2014 sandbox/live transfers require an existing sandbox`
|
|
2208
|
+
);
|
|
2206
2209
|
}
|
|
2207
2210
|
return { sandbox: (0, import_apps2.tenantIdFor)(cfg.app.id, "dev"), live: (0, import_apps2.tenantIdFor)(cfg.app.id, "prod") };
|
|
2208
2211
|
}
|
|
@@ -2308,7 +2311,7 @@ var init_agent_command = __esm({
|
|
|
2308
2311
|
// src/human-session.ts
|
|
2309
2312
|
async function requireStudioHuman(configPath, action2, destination = "app", env) {
|
|
2310
2313
|
const cfg = await loadProjectConfig(configPath);
|
|
2311
|
-
const appEnv = env && cfg.envs.includes(env) ? env : cfg.envs.includes("dev") ? "dev" : cfg.envs[0] ?? "prod";
|
|
2314
|
+
const appEnv = env && (cfg.envs.includes(env) || env === "prod") ? env : cfg.envs.includes("dev") ? "dev" : cfg.envs[0] ?? "prod";
|
|
2312
2315
|
const path = destination === "app" ? `/studio/apps/${encodeURIComponent(cfg.app.id)}/${encodeURIComponent(appEnv)}/settings/app` : `/studio/apps/${encodeURIComponent(cfg.app.id)}/${encodeURIComponent(appEnv)}/${destination}`;
|
|
2313
2316
|
throw new Error(
|
|
2314
2317
|
`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}`
|
|
@@ -3043,8 +3046,7 @@ async function ensureCalendarConnected(ctx, options) {
|
|
|
3043
3046
|
async function lifecycleContext(options, optionalProjectCapabilities = []) {
|
|
3044
3047
|
const cfg = await loadProjectConfig(options.configPath);
|
|
3045
3048
|
if (!cfg.services.includes("calendar")) throw new Error("calendar service is not enabled in config services");
|
|
3046
|
-
const env =
|
|
3047
|
-
if (!env || !cfg.envs.includes(env)) throw new Error(`env "${env ?? ""}" is not declared in ${options.configPath}`);
|
|
3049
|
+
const env = resolveEnv(cfg, options.env);
|
|
3048
3050
|
calendarServiceConfig(cfg, env);
|
|
3049
3051
|
const out = options.stdout ?? console;
|
|
3050
3052
|
const doFetch = options.fetch ?? fetch;
|
|
@@ -3188,6 +3190,7 @@ var init_calendar = __esm({
|
|
|
3188
3190
|
init_handshake_approval();
|
|
3189
3191
|
init_token();
|
|
3190
3192
|
init_human_session();
|
|
3193
|
+
init_tenant();
|
|
3191
3194
|
}
|
|
3192
3195
|
});
|
|
3193
3196
|
|
|
@@ -3264,8 +3267,7 @@ var init_capabilities = __esm({
|
|
|
3264
3267
|
// src/ai-models.ts
|
|
3265
3268
|
async function aiModels(options = {}) {
|
|
3266
3269
|
const cfg = await loadProjectConfig(options.configPath ?? "odla.config.mjs");
|
|
3267
|
-
const env = options.env
|
|
3268
|
-
if (!cfg.envs.includes(env)) throw new Error(`ai models env "${env}" is not declared in config envs`);
|
|
3270
|
+
const env = resolveEnv(cfg, options.env);
|
|
3269
3271
|
const url = new URL(`/registry/apps/${encodeURIComponent(cfg.app.id)}/public-config`, cfg.platformUrl);
|
|
3270
3272
|
url.searchParams.set("env", env);
|
|
3271
3273
|
const response2 = await (options.fetch ?? fetch)(url);
|
|
@@ -3323,6 +3325,7 @@ var init_ai_models = __esm({
|
|
|
3323
3325
|
init_cjs_shims();
|
|
3324
3326
|
import_ai = require("@odla-ai/ai");
|
|
3325
3327
|
init_config();
|
|
3328
|
+
init_tenant();
|
|
3326
3329
|
}
|
|
3327
3330
|
});
|
|
3328
3331
|
|
|
@@ -4373,6 +4376,41 @@ async function wranglerLoggedIn(run, cwd) {
|
|
|
4373
4376
|
return false;
|
|
4374
4377
|
}
|
|
4375
4378
|
}
|
|
4379
|
+
async function wranglerRuntimeTarget(run, opts) {
|
|
4380
|
+
const configPath = findWranglerConfig(opts.cwd);
|
|
4381
|
+
if (!configPath) throw new Error(`no wrangler config found in ${opts.cwd}`);
|
|
4382
|
+
const config = readWranglerConfig(configPath);
|
|
4383
|
+
if (!config) throw new Error("runtime credential delivery requires wrangler.json or wrangler.jsonc");
|
|
4384
|
+
const baseName = typeof config.name === "string" ? config.name : "";
|
|
4385
|
+
const envs = config.env && typeof config.env === "object" && !Array.isArray(config.env) ? config.env : {};
|
|
4386
|
+
const envConfig = opts.env && envs[opts.env] && typeof envs[opts.env] === "object" ? envs[opts.env] : {};
|
|
4387
|
+
const explicitName = typeof envConfig.name === "string" ? envConfig.name : "";
|
|
4388
|
+
const serviceEnvironments = config.legacy_env === false;
|
|
4389
|
+
if (serviceEnvironments && explicitName) {
|
|
4390
|
+
throw new Error("env.<name>.name is not allowed when wrangler legacy_env is false");
|
|
4391
|
+
}
|
|
4392
|
+
const scriptName = serviceEnvironments ? baseName : explicitName || (opts.env ? `${baseName}-${opts.env}` : baseName);
|
|
4393
|
+
if (!/^[a-z0-9][a-z0-9_-]{0,62}$/.test(scriptName)) {
|
|
4394
|
+
throw new Error("wrangler config must resolve an exact Worker name before credentials are issued");
|
|
4395
|
+
}
|
|
4396
|
+
const configuredAccount = typeof envConfig.account_id === "string" ? envConfig.account_id : typeof config.account_id === "string" ? config.account_id : "";
|
|
4397
|
+
const whoami = await run("npx", ["wrangler", "whoami"], { cwd: opts.cwd });
|
|
4398
|
+
if (whoami.code !== 0 || /not authenticated/i.test(`${whoami.stdout}${whoami.stderr}`)) {
|
|
4399
|
+
throw new Error(`wrangler is not logged in \u2014 run "wrangler login" (a browser step for the human)`);
|
|
4400
|
+
}
|
|
4401
|
+
const discovered = [...new Set(`${whoami.stdout}
|
|
4402
|
+
${whoami.stderr}`.match(/\b[a-f0-9]{32}\b/gi)?.map((id) => id.toLowerCase()) ?? [])];
|
|
4403
|
+
const accountId = configuredAccount.toLowerCase() || (discovered.length === 1 ? discovered[0] : "");
|
|
4404
|
+
if (!/^[a-f0-9]{32}$/.test(accountId) || configuredAccount && discovered.length > 0 && !discovered.includes(accountId)) {
|
|
4405
|
+
throw new Error("Wrangler account is ambiguous or does not match account_id in the config");
|
|
4406
|
+
}
|
|
4407
|
+
return {
|
|
4408
|
+
provider: "cloudflare",
|
|
4409
|
+
accountId,
|
|
4410
|
+
scriptName,
|
|
4411
|
+
...opts.env ? { environment: opts.env } : {}
|
|
4412
|
+
};
|
|
4413
|
+
}
|
|
4376
4414
|
function wranglerPutSecret(run, opts) {
|
|
4377
4415
|
const args = [
|
|
4378
4416
|
"wrangler",
|
|
@@ -4384,6 +4422,16 @@ function wranglerPutSecret(run, opts) {
|
|
|
4384
4422
|
];
|
|
4385
4423
|
return run("npx", args, { input: opts.value, cwd: opts.cwd });
|
|
4386
4424
|
}
|
|
4425
|
+
function wranglerBulkSecrets(run, opts) {
|
|
4426
|
+
const args = [
|
|
4427
|
+
"wrangler",
|
|
4428
|
+
"secret",
|
|
4429
|
+
"bulk",
|
|
4430
|
+
...opts.env ? ["--env", opts.env] : [],
|
|
4431
|
+
...opts.configPath ? ["--config", opts.configPath] : []
|
|
4432
|
+
];
|
|
4433
|
+
return run("npx", args, { input: JSON.stringify(opts.secrets), cwd: opts.cwd });
|
|
4434
|
+
}
|
|
4387
4435
|
var import_node_child_process2, import_node_fs12, import_node_path11, defaultRunner, WRANGLER_CONFIG_FILES;
|
|
4388
4436
|
var init_wrangler = __esm({
|
|
4389
4437
|
"src/wrangler.ts"() {
|
|
@@ -4944,16 +4992,10 @@ var init_init = __esm({
|
|
|
4944
4992
|
async function secretsPush(options) {
|
|
4945
4993
|
await secretsPushImpl(options, true);
|
|
4946
4994
|
}
|
|
4947
|
-
async function secretsPushAfterPreflight(options) {
|
|
4948
|
-
await secretsPushImpl(options, false);
|
|
4949
|
-
}
|
|
4950
4995
|
async function secretsPushImpl(options, preflight) {
|
|
4951
4996
|
const out = options.stdout ?? console;
|
|
4952
4997
|
const cfg = await loadProjectConfig(options.configPath);
|
|
4953
|
-
const env = options.env;
|
|
4954
|
-
if (!cfg.envs.includes(env)) {
|
|
4955
|
-
throw new Error(`env "${env}" is not in config envs (${cfg.envs.join(", ")})`);
|
|
4956
|
-
}
|
|
4998
|
+
const env = resolveEnv(cfg, options.env);
|
|
4957
4999
|
if (PROD_ENV_NAMES.has(env) && !options.yes) {
|
|
4958
5000
|
throw new Error(`refusing to push a secret to "${env}" without --yes`);
|
|
4959
5001
|
}
|
|
@@ -5019,6 +5061,7 @@ var init_secrets = __esm({
|
|
|
5019
5061
|
init_config();
|
|
5020
5062
|
init_local();
|
|
5021
5063
|
init_redact();
|
|
5064
|
+
init_tenant();
|
|
5022
5065
|
init_wrangler();
|
|
5023
5066
|
PROD_ENV_NAMES = /* @__PURE__ */ new Set(["prod", "production"]);
|
|
5024
5067
|
}
|
|
@@ -5072,14 +5115,12 @@ async function resolveVaultWrite(options) {
|
|
|
5072
5115
|
const out = options.stdout ?? console;
|
|
5073
5116
|
const doFetch = options.fetch ?? fetch;
|
|
5074
5117
|
const cfg = await loadProjectConfig(options.configPath);
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
if (PROD_ENV_NAMES2.has(options.env) && !options.yes) {
|
|
5079
|
-
throw new Error(`refusing to store a secret for "${options.env}" without --yes`);
|
|
5118
|
+
const env = resolveEnv(cfg, options.env);
|
|
5119
|
+
if (PROD_ENV_NAMES2.has(env) && !options.yes) {
|
|
5120
|
+
throw new Error(`refusing to store a secret for "${env}" without --yes`);
|
|
5080
5121
|
}
|
|
5081
5122
|
const value2 = await secretInputValue(options, "secret");
|
|
5082
|
-
return { cfg, tenantId: (0, import_apps10.tenantIdFor)(cfg.app.id,
|
|
5123
|
+
return { cfg, tenantId: (0, import_apps10.tenantIdFor)(cfg.app.id, env), value: value2, doFetch, out };
|
|
5083
5124
|
}
|
|
5084
5125
|
var import_ai3, import_apps10, PROD_ENV_NAMES2;
|
|
5085
5126
|
var init_secrets_set = __esm({
|
|
@@ -5092,6 +5133,7 @@ var init_secrets_set = __esm({
|
|
|
5092
5133
|
init_redact();
|
|
5093
5134
|
init_secret_input();
|
|
5094
5135
|
init_token();
|
|
5136
|
+
init_tenant();
|
|
5095
5137
|
PROD_ENV_NAMES2 = /* @__PURE__ */ new Set(["prod", "production"]);
|
|
5096
5138
|
}
|
|
5097
5139
|
});
|
|
@@ -5393,8 +5435,7 @@ var init_skill = __esm({
|
|
|
5393
5435
|
async function smoke(options) {
|
|
5394
5436
|
const out = options.stdout ?? console;
|
|
5395
5437
|
const cfg = await loadProjectConfig(options.configPath);
|
|
5396
|
-
const env =
|
|
5397
|
-
if (!cfg.envs.includes(env)) throw new Error(`env "${env}" is not declared in ${displayPath(cfg.configPath, cfg.rootDir)}`);
|
|
5438
|
+
const env = resolveEnv(cfg, options.env);
|
|
5398
5439
|
const credentials = readCredentials(cfg.local.credentialsFile);
|
|
5399
5440
|
if (!credentials) {
|
|
5400
5441
|
throw new Error(`local credentials missing: ${displayPath(cfg.local.credentialsFile, cfg.rootDir)}. Run "odla-ai provision --write-dev-vars".`);
|
|
@@ -5540,6 +5581,7 @@ var init_smoke = __esm({
|
|
|
5540
5581
|
init_integrations();
|
|
5541
5582
|
init_redact();
|
|
5542
5583
|
init_token();
|
|
5584
|
+
init_tenant();
|
|
5543
5585
|
}
|
|
5544
5586
|
});
|
|
5545
5587
|
|
|
@@ -9501,6 +9543,65 @@ var init_context_command = __esm({
|
|
|
9501
9543
|
}
|
|
9502
9544
|
});
|
|
9503
9545
|
|
|
9546
|
+
// src/credential-command.ts
|
|
9547
|
+
async function responseError(response2) {
|
|
9548
|
+
return redactSecrets((await response2.text()).slice(0, 1e3));
|
|
9549
|
+
}
|
|
9550
|
+
async function credentialCommand(parsed, deps = {}) {
|
|
9551
|
+
const action2 = parsed.positionals[1] ?? "list";
|
|
9552
|
+
if (action2 !== "list" && action2 !== "revoke") {
|
|
9553
|
+
throw new Error(`unknown credentials action "${action2}". Try "odla-ai credentials list".`);
|
|
9554
|
+
}
|
|
9555
|
+
assertArgs(parsed, ["config", "env", "all", "json", "token", "email", "open"], action2 === "revoke" ? 3 : 2);
|
|
9556
|
+
const cfg = await loadProjectConfig(stringOpt(parsed.options.config) ?? "odla.config.mjs");
|
|
9557
|
+
const doFetch = deps.fetch ?? fetch;
|
|
9558
|
+
const out = deps.stdout ?? console;
|
|
9559
|
+
const token = await getDeveloperToken(cfg, {
|
|
9560
|
+
configPath: cfg.configPath,
|
|
9561
|
+
token: stringOpt(parsed.options.token),
|
|
9562
|
+
email: stringOpt(parsed.options.email),
|
|
9563
|
+
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
9564
|
+
openApprovalUrl: deps.openUrl
|
|
9565
|
+
}, doFetch, out, { optionalProjectCapabilities: ["app.manage"] });
|
|
9566
|
+
const base = `${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/runtime-credentials`;
|
|
9567
|
+
if (action2 === "revoke") {
|
|
9568
|
+
const id = parsed.positionals[2];
|
|
9569
|
+
if (!id) throw new Error("credentials revoke requires the exact receipt id from credentials list");
|
|
9570
|
+
const response3 = await doFetch(`${base}/${encodeURIComponent(id)}`, {
|
|
9571
|
+
method: "DELETE",
|
|
9572
|
+
headers: { authorization: `Bearer ${token}` }
|
|
9573
|
+
});
|
|
9574
|
+
if (!response3.ok) throw new Error(`runtime credential revoke failed (${response3.status}): ${await responseError(response3)}`);
|
|
9575
|
+
const body2 = await response3.json();
|
|
9576
|
+
return out.log(parsed.options.json === true ? JSON.stringify(body2, null, 2) : `revoked ${body2.receipt.id} (${body2.receipt.env} ${body2.receipt.target.scriptName})`);
|
|
9577
|
+
}
|
|
9578
|
+
const query = new URLSearchParams();
|
|
9579
|
+
const requestedEnv = stringOpt(parsed.options.env);
|
|
9580
|
+
if (requestedEnv) query.set("env", requestedEnv);
|
|
9581
|
+
const response2 = await doFetch(`${base}${query.size ? `?${query}` : ""}`, {
|
|
9582
|
+
headers: { authorization: `Bearer ${token}` }
|
|
9583
|
+
});
|
|
9584
|
+
if (!response2.ok) throw new Error(`runtime credential inventory failed (${response2.status}): ${await responseError(response2)}`);
|
|
9585
|
+
const body = await response2.json();
|
|
9586
|
+
const credentials = parsed.options.all === true ? body.credentials : body.credentials.filter((item) => item.state === "committed");
|
|
9587
|
+
if (parsed.options.json === true) return out.log(JSON.stringify({ credentials }, null, 2));
|
|
9588
|
+
if (!credentials.length) return out.log("no active runtime credential receipts");
|
|
9589
|
+
out.log("RECEIPT ENV TARGET CREATED");
|
|
9590
|
+
for (const item of credentials) {
|
|
9591
|
+
out.log(`${item.id} ${item.env} ${item.target.accountId ?? "local"}/${item.target.scriptName} ${new Date(item.createdAt).toISOString()}`);
|
|
9592
|
+
}
|
|
9593
|
+
}
|
|
9594
|
+
var init_credential_command = __esm({
|
|
9595
|
+
"src/credential-command.ts"() {
|
|
9596
|
+
"use strict";
|
|
9597
|
+
init_cjs_shims();
|
|
9598
|
+
init_argv();
|
|
9599
|
+
init_config();
|
|
9600
|
+
init_redact();
|
|
9601
|
+
init_token();
|
|
9602
|
+
}
|
|
9603
|
+
});
|
|
9604
|
+
|
|
9504
9605
|
// src/help-usage.ts
|
|
9505
9606
|
var USAGE_SECTION;
|
|
9506
9607
|
var init_help_usage = __esm({
|
|
@@ -9623,7 +9724,9 @@ Usage:
|
|
|
9623
9724
|
odla-ai security report <job-id> [--json]
|
|
9624
9725
|
odla-ai security run [target] --ack-redacted-source [--env dev] [--profile odla] [--fail-on high]
|
|
9625
9726
|
odla-ai security run [target] --self --ack-redacted-source
|
|
9626
|
-
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]
|
|
9727
|
+
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]
|
|
9728
|
+
odla-ai credentials list [--config odla.config.mjs] [--env dev] [--all] [--json]
|
|
9729
|
+
odla-ai credentials revoke <receipt-id> [--config odla.config.mjs] [--json]
|
|
9627
9730
|
odla-ai smoke [--config odla.config.mjs] [--env dev] [--email <odla-account>] [--no-open]
|
|
9628
9731
|
odla-ai skill install [--dir <project>] [--agent <name>] [--global] [--force]
|
|
9629
9732
|
odla-ai secrets push --env <env> [--config odla.config.mjs] [--dry-run] [--yes]
|
|
@@ -9725,6 +9828,9 @@ Commands:
|
|
|
9725
9828
|
platform Read canonical fleet health, releases, provider load/freshness,
|
|
9726
9829
|
explicit unknowns, and next actions through a read-only grant.
|
|
9727
9830
|
provision Register services, compose integrations, persist credentials, optionally push secrets.
|
|
9831
|
+
"provision --live --yes" initializes only the live instance of
|
|
9832
|
+
an existing sandbox app and enables every configured service;
|
|
9833
|
+
no edit to the dev-first envs list is required.
|
|
9728
9834
|
smoke Verify credentials, public-config, composed schema, db aggregate, and integration probes.
|
|
9729
9835
|
skill Same installer; --agent accepts all, claude, codex, cursor,
|
|
9730
9836
|
copilot, gemini, or agents (repeatable or comma-separated).
|
|
@@ -9737,15 +9843,21 @@ Safety:
|
|
|
9737
9843
|
Every app has two databases on odla.ai: a sandbox (env "dev", tenant
|
|
9738
9844
|
<appId>--dev) and a live one (env "prod", tenant <appId>). Both are served by
|
|
9739
9845
|
production odla.ai \u2014 there is no separate odla to point at. New projects get
|
|
9740
|
-
the sandbox only.
|
|
9741
|
-
|
|
9846
|
+
the sandbox only. Run "provision --live --dry-run", then "provision --live
|
|
9847
|
+
--yes" to initialize live from that existing sandbox project's config. Add
|
|
9848
|
+
"prod" to envs only when ordinary provision runs should manage both; use
|
|
9849
|
+
--dry-run first to inspect either resolved plan.
|
|
9742
9850
|
A real provision first verifies both the released CLI version and the exact
|
|
9743
9851
|
pinned versions of every external @odla-ai runtime module before importing
|
|
9744
9852
|
the command graph. A stale workspace module blocks with its resolved path and
|
|
9745
9853
|
tells the agent to update/rebase, npm ci, and rebuild.
|
|
9746
|
-
Provision caches the approved developer token
|
|
9747
|
-
|
|
9748
|
-
|
|
9854
|
+
Provision caches the approved developer token under .odla/ with mode 0600.
|
|
9855
|
+
Local-only development provisioning may also cache that developer's service
|
|
9856
|
+
credentials there. \`provision --push-secrets\` instead stages a fresh,
|
|
9857
|
+
independently revocable credential set through the approved handshake,
|
|
9858
|
+
transfers the complete set to the exact Worker with \`wrangler secret bulk\`
|
|
9859
|
+
over stdin, and never writes its plaintext under .odla. Secret push preflights
|
|
9860
|
+
Wrangler before shown-once issuance; it never rotates sibling credentials.
|
|
9749
9861
|
Projectless PM, Discussions, o11y, runbook, and identity commands use
|
|
9750
9862
|
--platform/--app/--env, ODLA_PLATFORM_URL/ODLA_APP_ID/ODLA_ENV, and
|
|
9751
9863
|
ODLA_DEV_TOKEN. Save non-secret scope metadata with "context save", then
|
|
@@ -11824,7 +11936,7 @@ async function issueO11yToken(opts) {
|
|
|
11824
11936
|
);
|
|
11825
11937
|
if (res.status === 409 && !opts.rotateO11y) {
|
|
11826
11938
|
throw new Error(
|
|
11827
|
-
`o11y token already exists for env "${opts.env}", but its shown-once value is not in the local credentials file; run "odla-ai provision --
|
|
11939
|
+
`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`
|
|
11828
11940
|
);
|
|
11829
11941
|
}
|
|
11830
11942
|
if (!res.ok) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) failed: ${res.status} ${await safeText7(res)}`);
|
|
@@ -11850,23 +11962,146 @@ var init_provision_credentials = __esm({
|
|
|
11850
11962
|
}
|
|
11851
11963
|
});
|
|
11852
11964
|
|
|
11965
|
+
// src/runtime-credentials.ts
|
|
11966
|
+
function runtimeUrl(cfg, suffix = "") {
|
|
11967
|
+
return `${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/runtime-credentials${suffix}`;
|
|
11968
|
+
}
|
|
11969
|
+
async function safeError(response2) {
|
|
11970
|
+
const text2 = await response2.text();
|
|
11971
|
+
return redactSecrets(text2.slice(0, 1e3));
|
|
11972
|
+
}
|
|
11973
|
+
async function finish(doFetch, cfg, token, sessionId, method) {
|
|
11974
|
+
return doFetch(runtimeUrl(cfg, `/${encodeURIComponent(sessionId)}`), {
|
|
11975
|
+
method,
|
|
11976
|
+
headers: { authorization: `Bearer ${token}` }
|
|
11977
|
+
});
|
|
11978
|
+
}
|
|
11979
|
+
async function deliverRuntimeCredentials(cfg, options) {
|
|
11980
|
+
const doFetch = options.fetch ?? fetch;
|
|
11981
|
+
const run = options.runner ?? defaultRunner;
|
|
11982
|
+
const wranglerEnv = options.env === "prod" || options.env === "production" ? void 0 : options.env;
|
|
11983
|
+
const target = await wranglerRuntimeTarget(run, {
|
|
11984
|
+
cwd: cfg.rootDir,
|
|
11985
|
+
env: wranglerEnv
|
|
11986
|
+
});
|
|
11987
|
+
const issue = await doFetch(runtimeUrl(cfg), {
|
|
11988
|
+
method: "POST",
|
|
11989
|
+
headers: {
|
|
11990
|
+
authorization: `Bearer ${options.developerToken}`,
|
|
11991
|
+
"content-type": "application/json"
|
|
11992
|
+
},
|
|
11993
|
+
body: JSON.stringify({
|
|
11994
|
+
env: options.env,
|
|
11995
|
+
idempotencyKey: `wrangler:${(0, import_node_crypto5.randomUUID)()}`,
|
|
11996
|
+
target
|
|
11997
|
+
})
|
|
11998
|
+
});
|
|
11999
|
+
const body = await issue.json().catch(() => null);
|
|
12000
|
+
if (!issue.ok || typeof body?.sessionId !== "string" || !body.secrets || typeof body.secrets !== "object") {
|
|
12001
|
+
const detail = body?.error?.code ?? body?.error?.message ?? issue.status;
|
|
12002
|
+
throw new Error(`runtime credential staging failed: ${String(detail)}`);
|
|
12003
|
+
}
|
|
12004
|
+
const secrets = body.secrets;
|
|
12005
|
+
const values = {};
|
|
12006
|
+
for (const name of ["ODLA_API_KEY", "ODLA_O11Y_TOKEN"]) {
|
|
12007
|
+
const value2 = secrets[name];
|
|
12008
|
+
if (typeof value2 === "string" && value2) values[name] = value2;
|
|
12009
|
+
}
|
|
12010
|
+
if (Object.keys(values).length === 0) {
|
|
12011
|
+
await finish(doFetch, cfg, options.developerToken, body.sessionId, "DELETE");
|
|
12012
|
+
throw new Error("runtime credential staging returned no configured service credentials");
|
|
12013
|
+
}
|
|
12014
|
+
const pushed = await wranglerBulkSecrets(run, {
|
|
12015
|
+
secrets: values,
|
|
12016
|
+
env: wranglerEnv,
|
|
12017
|
+
cwd: cfg.rootDir
|
|
12018
|
+
});
|
|
12019
|
+
if (pushed.code !== 0) {
|
|
12020
|
+
await finish(doFetch, cfg, options.developerToken, body.sessionId, "DELETE");
|
|
12021
|
+
const detail = redactSecrets(`${pushed.stderr || pushed.stdout}`.trim());
|
|
12022
|
+
throw new Error(`wrangler secret bulk failed (exit ${pushed.code}): ${detail}`);
|
|
12023
|
+
}
|
|
12024
|
+
const committed = await finish(
|
|
12025
|
+
doFetch,
|
|
12026
|
+
cfg,
|
|
12027
|
+
options.developerToken,
|
|
12028
|
+
body.sessionId,
|
|
12029
|
+
"POST"
|
|
12030
|
+
);
|
|
12031
|
+
if (!committed.ok) {
|
|
12032
|
+
await finish(doFetch, cfg, options.developerToken, body.sessionId, "DELETE");
|
|
12033
|
+
throw new Error(`runtime credential receipt commit failed: ${await safeError(committed)}`);
|
|
12034
|
+
}
|
|
12035
|
+
options.stdout?.log(
|
|
12036
|
+
`${options.env}: installed ${Object.keys(values).join(" + ")} on ${target.accountId}/${target.scriptName}${target.environment ? ` (${target.environment})` : ""}; plaintext stayed in memory`
|
|
12037
|
+
);
|
|
12038
|
+
return {
|
|
12039
|
+
sessionId: body.sessionId,
|
|
12040
|
+
target,
|
|
12041
|
+
...values.ODLA_API_KEY ? { dbKey: values.ODLA_API_KEY } : {},
|
|
12042
|
+
...values.ODLA_O11Y_TOKEN ? { o11yToken: values.ODLA_O11Y_TOKEN } : {}
|
|
12043
|
+
};
|
|
12044
|
+
}
|
|
12045
|
+
var import_node_crypto5;
|
|
12046
|
+
var init_runtime_credentials = __esm({
|
|
12047
|
+
"src/runtime-credentials.ts"() {
|
|
12048
|
+
"use strict";
|
|
12049
|
+
init_cjs_shims();
|
|
12050
|
+
import_node_crypto5 = require("crypto");
|
|
12051
|
+
init_redact();
|
|
12052
|
+
init_wrangler();
|
|
12053
|
+
}
|
|
12054
|
+
});
|
|
12055
|
+
|
|
12056
|
+
// src/provision-live.ts
|
|
12057
|
+
function liveProvisionConfig(cfg) {
|
|
12058
|
+
if (!cfg.envs.includes("dev")) {
|
|
12059
|
+
throw new Error(
|
|
12060
|
+
`--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`
|
|
12061
|
+
);
|
|
12062
|
+
}
|
|
12063
|
+
if (cfg.services.includes("calendar")) {
|
|
12064
|
+
const google = cfg.calendar?.google;
|
|
12065
|
+
const availability = google?.availabilityCalendars ?? google?.calendars;
|
|
12066
|
+
if (!availability?.prod?.length) {
|
|
12067
|
+
throw new Error(
|
|
12068
|
+
`--live requires calendar.google.availabilityCalendars.prod in ${cfg.configPath}; live uses a separate Google Calendar connection, so sandbox calendar ids are not copied`
|
|
12069
|
+
);
|
|
12070
|
+
}
|
|
12071
|
+
}
|
|
12072
|
+
return { ...cfg, envs: ["prod"] };
|
|
12073
|
+
}
|
|
12074
|
+
var init_provision_live = __esm({
|
|
12075
|
+
"src/provision-live.ts"() {
|
|
12076
|
+
"use strict";
|
|
12077
|
+
init_cjs_shims();
|
|
12078
|
+
}
|
|
12079
|
+
});
|
|
12080
|
+
|
|
11853
12081
|
// src/provision.ts
|
|
11854
12082
|
async function provision(options) {
|
|
11855
12083
|
const out = options.stdout ?? console;
|
|
11856
|
-
const
|
|
12084
|
+
const loaded = await loadProjectConfig(
|
|
12085
|
+
options.configPath,
|
|
12086
|
+
options.live ? { additionalEnvs: ["prod"] } : void 0
|
|
12087
|
+
);
|
|
12088
|
+
const cfg = options.live ? liveProvisionConfig(loaded) : loaded;
|
|
11857
12089
|
const plan = buildPlan(cfg);
|
|
11858
12090
|
const hasDb = cfg.services.includes("db");
|
|
11859
12091
|
const hasO11y = cfg.services.includes("o11y");
|
|
11860
12092
|
if (options.rotateO11yToken && !hasO11y) {
|
|
11861
12093
|
throw new Error("--rotate-o11y-token requires the o11y service in odla.config.mjs");
|
|
11862
12094
|
}
|
|
11863
|
-
if (options.pushSecrets && options.
|
|
11864
|
-
throw new Error(
|
|
12095
|
+
if (options.pushSecrets && (options.rotateKeys || options.rotateO11yToken)) {
|
|
12096
|
+
throw new Error(
|
|
12097
|
+
"--push-secrets always issues an independent runtime credential set; do not combine it with destructive rotation flags"
|
|
12098
|
+
);
|
|
11865
12099
|
}
|
|
11866
12100
|
out.log(`odla-ai: ${plan.appName} (${plan.appId})`);
|
|
11867
12101
|
out.log(` platform: ${plan.platformUrl}`);
|
|
11868
12102
|
out.log(` db: ${plan.dbEndpoint}`);
|
|
11869
12103
|
out.log(` envs: ${plan.envs.join(", ")}`);
|
|
12104
|
+
if (options.live) out.log(" target: live setup (existing sandbox app)");
|
|
11870
12105
|
out.log(` services: ${plan.services.join(", ")}`);
|
|
11871
12106
|
out.log(` integrations: ${plan.integrations.length ? plan.integrations.join(", ") : "none"}`);
|
|
11872
12107
|
const productionEnvs = plan.envs.filter((env) => env === "prod" || env === "production");
|
|
@@ -11897,7 +12132,7 @@ async function provision(options) {
|
|
|
11897
12132
|
out.log(` secrets: ${options.pushSecrets ? "push configured Worker secrets" : "local only"}`);
|
|
11898
12133
|
return;
|
|
11899
12134
|
}
|
|
11900
|
-
let credentials = readCredentials(cfg.local.credentialsFile);
|
|
12135
|
+
let credentials = options.pushSecrets ? null : readCredentials(cfg.local.credentialsFile);
|
|
11901
12136
|
if (credentials && credentials.appId !== cfg.app.id) {
|
|
11902
12137
|
throw new Error(
|
|
11903
12138
|
`credentials at ${displayPath(cfg.local.credentialsFile, cfg.rootDir)} are for "${credentials.appId}", not "${cfg.app.id}"`
|
|
@@ -11906,7 +12141,7 @@ async function provision(options) {
|
|
|
11906
12141
|
const rotatesO11y = !!(options.rotateKeys || options.rotateO11yToken);
|
|
11907
12142
|
const missingO11y = cfg.envs.some((env) => !credentials?.envs[env]?.o11yToken);
|
|
11908
12143
|
const missingDb = cfg.envs.some((env) => !credentials?.envs[env]?.dbKey);
|
|
11909
|
-
const losesShownOnceCredential = hasDb && (!!options.rotateKeys || missingDb) || hasO11y && (rotatesO11y || missingO11y);
|
|
12144
|
+
const losesShownOnceCredential = !options.pushSecrets && (hasDb && (!!options.rotateKeys || missingDb) || hasO11y && (rotatesO11y || missingO11y));
|
|
11910
12145
|
if (options.writeCredentials === false && losesShownOnceCredential) {
|
|
11911
12146
|
throw new Error("credential issuance/rotation requires the private credentials file; remove --no-write-credentials");
|
|
11912
12147
|
}
|
|
@@ -11927,6 +12162,10 @@ async function provision(options) {
|
|
|
11927
12162
|
const existing = await apps.resolveApp(cfg.app.id);
|
|
11928
12163
|
if (existing) {
|
|
11929
12164
|
out.log(`app: ${cfg.app.id} already exists`);
|
|
12165
|
+
} else if (options.live) {
|
|
12166
|
+
throw new Error(
|
|
12167
|
+
`cannot set up live for "${cfg.app.id}": the sandbox app does not exist yet; run "odla-ai provision" first`
|
|
12168
|
+
);
|
|
11930
12169
|
} else {
|
|
11931
12170
|
try {
|
|
11932
12171
|
await apps.createApp({ name: cfg.app.name, appId: cfg.app.id });
|
|
@@ -11976,38 +12215,42 @@ async function provision(options) {
|
|
|
11976
12215
|
await applyCalendarBookingPage(calendarCtx, calendarBookingPageUrl(cfg, env), out);
|
|
11977
12216
|
}
|
|
11978
12217
|
}
|
|
12218
|
+
let devVarsCredentials = credentials;
|
|
11979
12219
|
for (const env of cfg.envs) {
|
|
11980
12220
|
const tenantId = (0, import_apps12.tenantIdFor)(cfg.app.id, env);
|
|
11981
|
-
|
|
11982
|
-
cfg,
|
|
11983
|
-
env,
|
|
11984
|
-
developerToken: token,
|
|
11985
|
-
credentials,
|
|
11986
|
-
rotateDb: !!options.rotateKeys,
|
|
11987
|
-
rotateO11y: rotatesO11y,
|
|
11988
|
-
write: options.writeCredentials !== false,
|
|
11989
|
-
fetch: doFetch,
|
|
11990
|
-
stdout: out
|
|
11991
|
-
});
|
|
11992
|
-
const dbKey = credentials.envs[env]?.dbKey;
|
|
12221
|
+
let dbKey;
|
|
11993
12222
|
if (options.pushSecrets) {
|
|
11994
|
-
|
|
11995
|
-
|
|
11996
|
-
|
|
11997
|
-
|
|
11998
|
-
|
|
11999
|
-
|
|
12000
|
-
|
|
12001
|
-
|
|
12002
|
-
|
|
12003
|
-
|
|
12004
|
-
|
|
12005
|
-
|
|
12006
|
-
|
|
12007
|
-
|
|
12008
|
-
|
|
12009
|
-
|
|
12010
|
-
}
|
|
12223
|
+
const delivered = await deliverRuntimeCredentials(cfg, {
|
|
12224
|
+
env,
|
|
12225
|
+
developerToken: token,
|
|
12226
|
+
fetch: doFetch,
|
|
12227
|
+
runner: options.secretRunner,
|
|
12228
|
+
stdout: out
|
|
12229
|
+
});
|
|
12230
|
+
dbKey = delivered.dbKey;
|
|
12231
|
+
devVarsCredentials = mergeCredential(devVarsCredentials, {
|
|
12232
|
+
appId: cfg.app.id,
|
|
12233
|
+
platformUrl: cfg.platformUrl,
|
|
12234
|
+
dbEndpoint: cfg.dbEndpoint,
|
|
12235
|
+
env,
|
|
12236
|
+
tenantId,
|
|
12237
|
+
...delivered.dbKey ? { dbKey: delivered.dbKey } : {},
|
|
12238
|
+
...delivered.o11yToken ? { o11yToken: delivered.o11yToken } : {}
|
|
12239
|
+
});
|
|
12240
|
+
} else {
|
|
12241
|
+
credentials = await provisionEnvCredentials({
|
|
12242
|
+
cfg,
|
|
12243
|
+
env,
|
|
12244
|
+
developerToken: token,
|
|
12245
|
+
credentials,
|
|
12246
|
+
rotateDb: !!options.rotateKeys,
|
|
12247
|
+
rotateO11y: rotatesO11y,
|
|
12248
|
+
write: options.writeCredentials !== false,
|
|
12249
|
+
fetch: doFetch,
|
|
12250
|
+
stdout: out
|
|
12251
|
+
});
|
|
12252
|
+
devVarsCredentials = credentials;
|
|
12253
|
+
dbKey = credentials.envs[env]?.dbKey;
|
|
12011
12254
|
}
|
|
12012
12255
|
if (schema && dbKey) {
|
|
12013
12256
|
await postJson(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(tenantId)}/schema`, dbKey, { schema });
|
|
@@ -12031,13 +12274,13 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
|
|
|
12031
12274
|
}
|
|
12032
12275
|
}
|
|
12033
12276
|
}
|
|
12034
|
-
if (options.writeCredentials !== false && credentials) {
|
|
12277
|
+
if (!options.pushSecrets && options.writeCredentials !== false && credentials) {
|
|
12035
12278
|
const ignored = cfg.local.gitignore && gitignoreEntry(cfg.rootDir, cfg.local.credentialsFile);
|
|
12036
12279
|
out.log(`credentials: wrote ${displayPath(cfg.local.credentialsFile, cfg.rootDir)} (0600${ignored ? ", gitignored" : ""})`);
|
|
12037
12280
|
}
|
|
12038
|
-
if (devVarsTarget &&
|
|
12281
|
+
if (devVarsTarget && devVarsCredentials) {
|
|
12039
12282
|
const env = cfg.envs.includes("dev") ? "dev" : cfg.envs[0] ?? "prod";
|
|
12040
|
-
writeDevVars(devVarsTarget,
|
|
12283
|
+
writeDevVars(devVarsTarget, devVarsCredentials, env, o11yDevVars(cfg));
|
|
12041
12284
|
out.log(`dev vars: wrote ${displayPath(devVarsTarget, cfg.rootDir)} for ${env}`);
|
|
12042
12285
|
}
|
|
12043
12286
|
if (cfg.services.includes("calendar")) {
|
|
@@ -12077,6 +12320,8 @@ var init_provision = __esm({
|
|
|
12077
12320
|
init_token();
|
|
12078
12321
|
init_local();
|
|
12079
12322
|
init_provision_credentials();
|
|
12323
|
+
init_runtime_credentials();
|
|
12324
|
+
init_provision_live();
|
|
12080
12325
|
init_provision_helpers();
|
|
12081
12326
|
init_secrets();
|
|
12082
12327
|
}
|
|
@@ -12186,6 +12431,7 @@ var init_surface = __esm({
|
|
|
12186
12431
|
code: { connect: {} },
|
|
12187
12432
|
config: { diff: {}, plan: {}, apply: {} },
|
|
12188
12433
|
context: { show: {}, list: {}, save: {}, remove: {} },
|
|
12434
|
+
credentials: { list: {}, revoke: {} },
|
|
12189
12435
|
// `watch`, `read`, `reply`, and `resolve` take a topic id from there on.
|
|
12190
12436
|
discuss: {
|
|
12191
12437
|
groups: {},
|
|
@@ -13982,6 +14228,10 @@ async function runCli(argv2 = process.argv.slice(2), dependencies = {}) {
|
|
|
13982
14228
|
await contextCommand(parsed, runtime);
|
|
13983
14229
|
return;
|
|
13984
14230
|
}
|
|
14231
|
+
if (command === "credentials") {
|
|
14232
|
+
await credentialCommand(parsed, runtime);
|
|
14233
|
+
return;
|
|
14234
|
+
}
|
|
13985
14235
|
if (command === "runbook") {
|
|
13986
14236
|
await runbookCommand(parsed, runtime);
|
|
13987
14237
|
return;
|
|
@@ -14049,6 +14299,7 @@ async function runCli(argv2 = process.argv.slice(2), dependencies = {}) {
|
|
|
14049
14299
|
async function provisionCommand(parsed, dependencies) {
|
|
14050
14300
|
assertArgs(parsed, [
|
|
14051
14301
|
"config",
|
|
14302
|
+
"live",
|
|
14052
14303
|
"dry-run",
|
|
14053
14304
|
"rotate-keys",
|
|
14054
14305
|
"rotate-o11y-token",
|
|
@@ -14065,6 +14316,7 @@ async function provisionCommand(parsed, dependencies) {
|
|
|
14065
14316
|
const writeDevVars2 = parsed.options["write-dev-vars"];
|
|
14066
14317
|
const options = {
|
|
14067
14318
|
configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
|
|
14319
|
+
live: parsed.options.live === true,
|
|
14068
14320
|
dryRun: parsed.options["dry-run"] === true,
|
|
14069
14321
|
rotateKeys: parsed.options["rotate-keys"] === true,
|
|
14070
14322
|
rotateO11yToken: parsed.options["rotate-o11y-token"] === true,
|
|
@@ -14122,6 +14374,7 @@ var init_cli = __esm({
|
|
|
14122
14374
|
init_cli_project();
|
|
14123
14375
|
init_code_command();
|
|
14124
14376
|
init_context_command();
|
|
14377
|
+
init_credential_command();
|
|
14125
14378
|
init_help();
|
|
14126
14379
|
init_version();
|
|
14127
14380
|
init_discuss_command();
|