@odla-ai/cli 0.29.0 → 0.30.1
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 +61 -29
- package/dist/bin.cjs +350 -191
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-YTVLTADT.js → chunk-7V7WVTDT.js} +304 -166
- package/dist/chunk-7V7WVTDT.js.map +1 -0
- package/dist/{cli-DNOAYDBR.js → cli-SZUWA6EF.js} +2 -2
- package/dist/index.cjs +317 -179
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -9
- package/dist/index.d.ts +11 -9
- package/dist/index.js +1 -1
- package/dist/runtime/pi-agent.js +18728 -15
- package/package.json +1 -1
- package/skills/odla/SKILL.md +10 -5
- 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-YTVLTADT.js.map +0 -1
- /package/dist/{cli-DNOAYDBR.js.map → cli-SZUWA6EF.js.map} +0 -0
package/dist/bin.cjs
CHANGED
|
@@ -305,10 +305,51 @@ var init_handshake_approval = __esm({
|
|
|
305
305
|
}
|
|
306
306
|
});
|
|
307
307
|
|
|
308
|
+
// src/handshake-state.ts
|
|
309
|
+
function handshakeFile(cfg) {
|
|
310
|
+
return (0, import_node_path2.join)((0, import_node_path2.dirname)(cfg.local.tokenFile), "handshake.local.json");
|
|
311
|
+
}
|
|
312
|
+
function clearPendingHandshake(path) {
|
|
313
|
+
(0, import_node_fs4.rmSync)(path, { force: true });
|
|
314
|
+
}
|
|
315
|
+
function minutesLeft(expiresAt) {
|
|
316
|
+
return Math.max(1, Math.round((expiresAt - Date.now()) / 6e4));
|
|
317
|
+
}
|
|
318
|
+
function approvalHint(pending) {
|
|
319
|
+
return `approve code ${pending.userCode} at ${pending.approvalUrl} (${minutesLeft(pending.expiresAt)}m left)`;
|
|
320
|
+
}
|
|
321
|
+
function approvalReminder(out, pending, periodMs = 3e4) {
|
|
322
|
+
const timer = setInterval(() => {
|
|
323
|
+
for (const line of reminderLines({
|
|
324
|
+
userCode: pending.userCode,
|
|
325
|
+
approvalUrl: pending.approvalUrl,
|
|
326
|
+
minutesLeft: minutesLeft(pending.expiresAt)
|
|
327
|
+
}))
|
|
328
|
+
out.log(line);
|
|
329
|
+
}, periodMs);
|
|
330
|
+
timer.unref?.();
|
|
331
|
+
return () => clearInterval(timer);
|
|
332
|
+
}
|
|
333
|
+
function handshakeWaitMs(waitSeconds, interactive = import_node_process3.default.stdout.isTTY === true) {
|
|
334
|
+
if (waitSeconds !== void 0) return waitSeconds * 1e3;
|
|
335
|
+
return interactive ? void 0 : 9e4;
|
|
336
|
+
}
|
|
337
|
+
var import_node_fs4, import_node_path2, import_node_process3;
|
|
338
|
+
var init_handshake_state = __esm({
|
|
339
|
+
"src/handshake-state.ts"() {
|
|
340
|
+
"use strict";
|
|
341
|
+
init_cjs_shims();
|
|
342
|
+
import_node_fs4 = require("fs");
|
|
343
|
+
import_node_path2 = require("path");
|
|
344
|
+
import_node_process3 = __toESM(require("process"), 1);
|
|
345
|
+
init_approval_prompt();
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
|
|
308
349
|
// src/local.ts
|
|
309
350
|
function readJsonFile(path) {
|
|
310
351
|
try {
|
|
311
|
-
return JSON.parse((0,
|
|
352
|
+
return JSON.parse((0, import_node_fs5.readFileSync)(path, "utf8"));
|
|
312
353
|
} catch {
|
|
313
354
|
return null;
|
|
314
355
|
}
|
|
@@ -318,10 +359,10 @@ function writePrivateJson(path, value2) {
|
|
|
318
359
|
`);
|
|
319
360
|
}
|
|
320
361
|
function readCredentials(path) {
|
|
321
|
-
if (!(0,
|
|
362
|
+
if (!(0, import_node_fs5.existsSync)(path)) return null;
|
|
322
363
|
let value2;
|
|
323
364
|
try {
|
|
324
|
-
value2 = JSON.parse((0,
|
|
365
|
+
value2 = JSON.parse((0, import_node_fs5.readFileSync)(path, "utf8"));
|
|
325
366
|
} catch {
|
|
326
367
|
throw new Error(`credentials file ${path} is not valid JSON; fix or remove it before provisioning`);
|
|
327
368
|
}
|
|
@@ -351,14 +392,14 @@ function mergeCredential(current, update) {
|
|
|
351
392
|
return next;
|
|
352
393
|
}
|
|
353
394
|
function ensureGitignore(rootDir, localPaths = []) {
|
|
354
|
-
const path = (0,
|
|
355
|
-
const existing = (0,
|
|
395
|
+
const path = (0, import_node_path3.resolve)(rootDir, ".gitignore");
|
|
396
|
+
const existing = (0, import_node_fs5.existsSync)(path) ? (0, import_node_fs5.readFileSync)(path, "utf8") : "";
|
|
356
397
|
const configured = localPaths.map((localPath) => gitignoreEntry(rootDir, localPath)).filter((line) => !!line);
|
|
357
398
|
const wanted = [.../* @__PURE__ */ new Set([...GITIGNORE_LINES, ...configured])];
|
|
358
399
|
const missing = wanted.filter((line) => !existing.split(/\r?\n/).includes(line));
|
|
359
400
|
if (missing.length === 0) return;
|
|
360
401
|
const prefix = existing && !existing.endsWith("\n") ? "\n" : "";
|
|
361
|
-
(0,
|
|
402
|
+
(0, import_node_fs5.writeFileSync)(path, `${existing}${prefix}${missing.join("\n")}
|
|
362
403
|
`);
|
|
363
404
|
}
|
|
364
405
|
function o11yDevVars(cfg) {
|
|
@@ -372,7 +413,7 @@ function o11yDevVars(cfg) {
|
|
|
372
413
|
function resolveWriteDevVarsTarget(cfg, requested) {
|
|
373
414
|
if (!requested) return null;
|
|
374
415
|
if (requested === true) return cfg.local.devVarsFile;
|
|
375
|
-
return (0,
|
|
416
|
+
return (0, import_node_path3.resolve)((0, import_node_path3.dirname)(cfg.configPath), requested);
|
|
376
417
|
}
|
|
377
418
|
function writeDevVars(path, credentials, env, o11y) {
|
|
378
419
|
const entry = credentials.envs[env];
|
|
@@ -386,7 +427,7 @@ function writeDevVars(path, credentials, env, o11y) {
|
|
|
386
427
|
if (o11y.version) lines.push(`ODLA_O11Y_VERSION="${o11y.version}"`);
|
|
387
428
|
if (entry.o11yToken) lines.push(`ODLA_O11Y_TOKEN="${entry.o11yToken}"`);
|
|
388
429
|
}
|
|
389
|
-
const existing = (0,
|
|
430
|
+
const existing = (0, import_node_fs5.existsSync)(path) ? (0, import_node_fs5.readFileSync)(path, "utf8") : "";
|
|
390
431
|
const retained = existing.split(/\r?\n/).filter((line) => !isManagedDevVar(line));
|
|
391
432
|
while (retained.at(-1) === "") retained.pop();
|
|
392
433
|
const prefix = retained.length ? `${retained.join("\n")}
|
|
@@ -400,28 +441,28 @@ function isManagedDevVar(line) {
|
|
|
400
441
|
return !!match?.[1] && MANAGED_DEV_VARS.has(match[1]);
|
|
401
442
|
}
|
|
402
443
|
function writePrivateText(path, text2) {
|
|
403
|
-
(0,
|
|
444
|
+
(0, import_node_fs5.mkdirSync)((0, import_node_path3.dirname)(path), { recursive: true });
|
|
404
445
|
const temporary = `${path}.tmp-${process.pid}-${Date.now()}`;
|
|
405
|
-
(0,
|
|
406
|
-
(0,
|
|
407
|
-
(0,
|
|
446
|
+
(0, import_node_fs5.writeFileSync)(temporary, text2, { mode: 384 });
|
|
447
|
+
(0, import_node_fs5.chmodSync)(temporary, 384);
|
|
448
|
+
(0, import_node_fs5.renameSync)(temporary, path);
|
|
408
449
|
}
|
|
409
450
|
function gitignoreEntry(rootDir, path) {
|
|
410
|
-
const rel = (0,
|
|
411
|
-
if (!rel || rel === ".." || rel.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || (0,
|
|
451
|
+
const rel = (0, import_node_path3.relative)((0, import_node_path3.resolve)(rootDir), (0, import_node_path3.resolve)(path));
|
|
452
|
+
if (!rel || rel === ".." || rel.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || (0, import_node_path3.isAbsolute)(rel)) return null;
|
|
412
453
|
return rel.replaceAll("\\", "/");
|
|
413
454
|
}
|
|
414
455
|
function displayPath(path, rootDir = process.cwd()) {
|
|
415
|
-
const rel = (0,
|
|
456
|
+
const rel = (0, import_node_path3.relative)(rootDir, path);
|
|
416
457
|
return rel && !rel.startsWith("..") ? rel : path;
|
|
417
458
|
}
|
|
418
|
-
var
|
|
459
|
+
var import_node_fs5, import_node_path3, GITIGNORE_LINES, MANAGED_DEV_VARS;
|
|
419
460
|
var init_local = __esm({
|
|
420
461
|
"src/local.ts"() {
|
|
421
462
|
"use strict";
|
|
422
463
|
init_cjs_shims();
|
|
423
|
-
|
|
424
|
-
|
|
464
|
+
import_node_fs5 = require("fs");
|
|
465
|
+
import_node_path3 = require("path");
|
|
425
466
|
GITIGNORE_LINES = [".odla/*.local.json", ".odla/dev-token.json", ".dev.vars"];
|
|
426
467
|
MANAGED_DEV_VARS = /* @__PURE__ */ new Set([
|
|
427
468
|
"ODLA_PLATFORM",
|
|
@@ -438,71 +479,6 @@ var init_local = __esm({
|
|
|
438
479
|
}
|
|
439
480
|
});
|
|
440
481
|
|
|
441
|
-
// src/handshake-state.ts
|
|
442
|
-
function handshakeFile(cfg) {
|
|
443
|
-
return (0, import_node_path3.join)((0, import_node_path3.dirname)(cfg.local.tokenFile), "handshake.local.json");
|
|
444
|
-
}
|
|
445
|
-
function readPendingHandshake(path, platform, email, requiredGrant) {
|
|
446
|
-
const pending = readJsonFile(path);
|
|
447
|
-
if (!pending || pending.platform !== platform || pending.email !== email) return null;
|
|
448
|
-
if (typeof pending.userCode !== "string" || typeof pending.deviceCode !== "string") return null;
|
|
449
|
-
if (typeof pending.expiresAt !== "number" || pending.expiresAt <= Date.now() + RESUME_MARGIN_MS) return null;
|
|
450
|
-
if (requiredGrant && !grantCovers(pending, requiredGrant)) return null;
|
|
451
|
-
return {
|
|
452
|
-
interval: typeof pending.interval === "number" ? pending.interval : 3,
|
|
453
|
-
...pending,
|
|
454
|
-
approvalUrl: handshakeUrl(platform, pending.userCode)
|
|
455
|
-
};
|
|
456
|
-
}
|
|
457
|
-
function grantCovers(stored, required) {
|
|
458
|
-
if (required.optionalProjectCapabilities.length === 0) return true;
|
|
459
|
-
return required.projectIds.every((id) => stored.projectIds?.includes(id)) && required.optionalProjectCapabilities.every(
|
|
460
|
-
(capability) => stored.optionalProjectCapabilities?.includes(capability)
|
|
461
|
-
);
|
|
462
|
-
}
|
|
463
|
-
function writePendingHandshake(path, pending) {
|
|
464
|
-
writePrivateJson(path, pending);
|
|
465
|
-
}
|
|
466
|
-
function clearPendingHandshake(path) {
|
|
467
|
-
(0, import_node_fs5.rmSync)(path, { force: true });
|
|
468
|
-
}
|
|
469
|
-
function minutesLeft(expiresAt) {
|
|
470
|
-
return Math.max(1, Math.round((expiresAt - Date.now()) / 6e4));
|
|
471
|
-
}
|
|
472
|
-
function approvalHint(pending) {
|
|
473
|
-
return `approve code ${pending.userCode} at ${pending.approvalUrl} (${minutesLeft(pending.expiresAt)}m left)`;
|
|
474
|
-
}
|
|
475
|
-
function approvalReminder(out, pending, periodMs = 3e4) {
|
|
476
|
-
const timer = setInterval(() => {
|
|
477
|
-
for (const line of reminderLines({
|
|
478
|
-
userCode: pending.userCode,
|
|
479
|
-
approvalUrl: pending.approvalUrl,
|
|
480
|
-
minutesLeft: minutesLeft(pending.expiresAt)
|
|
481
|
-
}))
|
|
482
|
-
out.log(line);
|
|
483
|
-
}, periodMs);
|
|
484
|
-
timer.unref?.();
|
|
485
|
-
return () => clearInterval(timer);
|
|
486
|
-
}
|
|
487
|
-
function handshakeWaitMs(waitSeconds, interactive = import_node_process3.default.stdout.isTTY === true) {
|
|
488
|
-
if (waitSeconds !== void 0) return waitSeconds * 1e3;
|
|
489
|
-
return interactive ? void 0 : 9e4;
|
|
490
|
-
}
|
|
491
|
-
var import_node_fs5, import_node_path3, import_node_process3, RESUME_MARGIN_MS;
|
|
492
|
-
var init_handshake_state = __esm({
|
|
493
|
-
"src/handshake-state.ts"() {
|
|
494
|
-
"use strict";
|
|
495
|
-
init_cjs_shims();
|
|
496
|
-
import_node_fs5 = require("fs");
|
|
497
|
-
import_node_path3 = require("path");
|
|
498
|
-
import_node_process3 = __toESM(require("process"), 1);
|
|
499
|
-
init_local();
|
|
500
|
-
init_approval_prompt();
|
|
501
|
-
init_handshake_approval();
|
|
502
|
-
RESUME_MARGIN_MS = 5e3;
|
|
503
|
-
}
|
|
504
|
-
});
|
|
505
|
-
|
|
506
482
|
// src/token.ts
|
|
507
483
|
async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {}) {
|
|
508
484
|
const audience = platformAudience(cfg.platformUrl);
|
|
@@ -541,8 +517,8 @@ async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {})
|
|
|
541
517
|
grantIntent
|
|
542
518
|
};
|
|
543
519
|
const waitMs = handshakeWaitMs(options.wait);
|
|
544
|
-
|
|
545
|
-
const { token, expiresAt } = await
|
|
520
|
+
clearPendingHandshake(ctx.pendingFile);
|
|
521
|
+
const { token, expiresAt } = await freshHandshake(ctx, waitMs);
|
|
546
522
|
clearPendingHandshake(ctx.pendingFile);
|
|
547
523
|
writePrivateJson(cfg.local.tokenFile, {
|
|
548
524
|
platform: audience,
|
|
@@ -555,47 +531,6 @@ async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {})
|
|
|
555
531
|
out.error(`auth: developer token cached (${displayPath(cfg.local.tokenFile, cfg.rootDir)})`);
|
|
556
532
|
return token;
|
|
557
533
|
}
|
|
558
|
-
async function resumePendingHandshake(ctx, waitMs) {
|
|
559
|
-
const pending = readPendingHandshake(
|
|
560
|
-
ctx.pendingFile,
|
|
561
|
-
ctx.audience,
|
|
562
|
-
ctx.email,
|
|
563
|
-
ctx.grantIntent
|
|
564
|
-
);
|
|
565
|
-
if (!pending) return null;
|
|
566
|
-
ctx.out.error("");
|
|
567
|
-
ctx.out.error(`auth: resuming pending handshake \u2014 ${approvalHint(pending)}`);
|
|
568
|
-
await presentHandshakeApproval(ctx.out, {
|
|
569
|
-
userCode: pending.userCode,
|
|
570
|
-
approvalUrl: pending.approvalUrl,
|
|
571
|
-
minutesLeft: Math.max(1, Math.floor((pending.expiresAt - Date.now()) / 6e4)),
|
|
572
|
-
purpose: `sign this terminal in as ${ctx.email}`
|
|
573
|
-
}, ctx.options);
|
|
574
|
-
ctx.out.error("");
|
|
575
|
-
const stopReminder = approvalReminder(ctx.out, pending);
|
|
576
|
-
try {
|
|
577
|
-
return await (0, import_db.collectToken)({
|
|
578
|
-
endpoint: ctx.cfg.platformUrl,
|
|
579
|
-
deviceCode: pending.deviceCode,
|
|
580
|
-
expiresAt: pending.expiresAt,
|
|
581
|
-
interval: pending.interval,
|
|
582
|
-
waitMs,
|
|
583
|
-
fetch: ctx.doFetch
|
|
584
|
-
});
|
|
585
|
-
} catch (err) {
|
|
586
|
-
const code = err instanceof import_db.OdlaError ? err.code : void 0;
|
|
587
|
-
if (code === "handshake_pending") throw stillPending(pending, ctx.email);
|
|
588
|
-
if (code === "handshake_expired" || code === "handshake_timeout") {
|
|
589
|
-
clearPendingHandshake(ctx.pendingFile);
|
|
590
|
-
ctx.out.error("auth: pending handshake lapsed unapproved; starting a fresh one");
|
|
591
|
-
return null;
|
|
592
|
-
}
|
|
593
|
-
if (code === "handshake_denied") clearPendingHandshake(ctx.pendingFile);
|
|
594
|
-
throw err;
|
|
595
|
-
} finally {
|
|
596
|
-
stopReminder();
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
534
|
async function freshHandshake(ctx, waitMs) {
|
|
600
535
|
let started;
|
|
601
536
|
let stopReminder;
|
|
@@ -623,7 +558,6 @@ async function freshHandshake(ctx, waitMs) {
|
|
|
623
558
|
projectIds: ctx.grantIntent.projectIds,
|
|
624
559
|
optionalProjectCapabilities: ctx.grantIntent.optionalProjectCapabilities
|
|
625
560
|
};
|
|
626
|
-
writePendingHandshake(ctx.pendingFile, started);
|
|
627
561
|
await presentHandshakeApproval(ctx.out, {
|
|
628
562
|
userCode,
|
|
629
563
|
approvalUrl,
|
|
@@ -659,7 +593,7 @@ function projectAgentHandle(appId) {
|
|
|
659
593
|
function stillPending(pending, email) {
|
|
660
594
|
return new import_db.OdlaError(
|
|
661
595
|
"handshake_pending",
|
|
662
|
-
`handshake still pending \u2014 ask ${email} to ${approvalHint(pending)}
|
|
596
|
+
`handshake still pending \u2014 ask ${email} to ${approvalHint(pending)}; if this command exits, re-run it to request a new code`,
|
|
663
597
|
{ retryable: true }
|
|
664
598
|
);
|
|
665
599
|
}
|
|
@@ -4376,6 +4310,41 @@ async function wranglerLoggedIn(run, cwd) {
|
|
|
4376
4310
|
return false;
|
|
4377
4311
|
}
|
|
4378
4312
|
}
|
|
4313
|
+
async function wranglerRuntimeTarget(run, opts) {
|
|
4314
|
+
const configPath = findWranglerConfig(opts.cwd);
|
|
4315
|
+
if (!configPath) throw new Error(`no wrangler config found in ${opts.cwd}`);
|
|
4316
|
+
const config = readWranglerConfig(configPath);
|
|
4317
|
+
if (!config) throw new Error("runtime credential delivery requires wrangler.json or wrangler.jsonc");
|
|
4318
|
+
const baseName = typeof config.name === "string" ? config.name : "";
|
|
4319
|
+
const envs = config.env && typeof config.env === "object" && !Array.isArray(config.env) ? config.env : {};
|
|
4320
|
+
const envConfig = opts.env && envs[opts.env] && typeof envs[opts.env] === "object" ? envs[opts.env] : {};
|
|
4321
|
+
const explicitName = typeof envConfig.name === "string" ? envConfig.name : "";
|
|
4322
|
+
const serviceEnvironments = config.legacy_env === false;
|
|
4323
|
+
if (serviceEnvironments && explicitName) {
|
|
4324
|
+
throw new Error("env.<name>.name is not allowed when wrangler legacy_env is false");
|
|
4325
|
+
}
|
|
4326
|
+
const scriptName = serviceEnvironments ? baseName : explicitName || (opts.env ? `${baseName}-${opts.env}` : baseName);
|
|
4327
|
+
if (!/^[a-z0-9][a-z0-9_-]{0,62}$/.test(scriptName)) {
|
|
4328
|
+
throw new Error("wrangler config must resolve an exact Worker name before credentials are issued");
|
|
4329
|
+
}
|
|
4330
|
+
const configuredAccount = typeof envConfig.account_id === "string" ? envConfig.account_id : typeof config.account_id === "string" ? config.account_id : "";
|
|
4331
|
+
const whoami = await run("npx", ["wrangler", "whoami"], { cwd: opts.cwd });
|
|
4332
|
+
if (whoami.code !== 0 || /not authenticated/i.test(`${whoami.stdout}${whoami.stderr}`)) {
|
|
4333
|
+
throw new Error(`wrangler is not logged in \u2014 run "wrangler login" (a browser step for the human)`);
|
|
4334
|
+
}
|
|
4335
|
+
const discovered = [...new Set(`${whoami.stdout}
|
|
4336
|
+
${whoami.stderr}`.match(/\b[a-f0-9]{32}\b/gi)?.map((id) => id.toLowerCase()) ?? [])];
|
|
4337
|
+
const accountId = configuredAccount.toLowerCase() || (discovered.length === 1 ? discovered[0] : "");
|
|
4338
|
+
if (!/^[a-f0-9]{32}$/.test(accountId) || configuredAccount && discovered.length > 0 && !discovered.includes(accountId)) {
|
|
4339
|
+
throw new Error("Wrangler account is ambiguous or does not match account_id in the config");
|
|
4340
|
+
}
|
|
4341
|
+
return {
|
|
4342
|
+
provider: "cloudflare",
|
|
4343
|
+
accountId,
|
|
4344
|
+
scriptName,
|
|
4345
|
+
...opts.env ? { environment: opts.env } : {}
|
|
4346
|
+
};
|
|
4347
|
+
}
|
|
4379
4348
|
function wranglerPutSecret(run, opts) {
|
|
4380
4349
|
const args = [
|
|
4381
4350
|
"wrangler",
|
|
@@ -4387,6 +4356,16 @@ function wranglerPutSecret(run, opts) {
|
|
|
4387
4356
|
];
|
|
4388
4357
|
return run("npx", args, { input: opts.value, cwd: opts.cwd });
|
|
4389
4358
|
}
|
|
4359
|
+
function wranglerBulkSecrets(run, opts) {
|
|
4360
|
+
const args = [
|
|
4361
|
+
"wrangler",
|
|
4362
|
+
"secret",
|
|
4363
|
+
"bulk",
|
|
4364
|
+
...opts.env ? ["--env", opts.env] : [],
|
|
4365
|
+
...opts.configPath ? ["--config", opts.configPath] : []
|
|
4366
|
+
];
|
|
4367
|
+
return run("npx", args, { input: JSON.stringify(opts.secrets), cwd: opts.cwd });
|
|
4368
|
+
}
|
|
4390
4369
|
var import_node_child_process2, import_node_fs12, import_node_path11, defaultRunner, WRANGLER_CONFIG_FILES;
|
|
4391
4370
|
var init_wrangler = __esm({
|
|
4392
4371
|
"src/wrangler.ts"() {
|
|
@@ -4947,9 +4926,6 @@ var init_init = __esm({
|
|
|
4947
4926
|
async function secretsPush(options) {
|
|
4948
4927
|
await secretsPushImpl(options, true);
|
|
4949
4928
|
}
|
|
4950
|
-
async function secretsPushAfterPreflight(options) {
|
|
4951
|
-
await secretsPushImpl(options, false);
|
|
4952
|
-
}
|
|
4953
4929
|
async function secretsPushImpl(options, preflight) {
|
|
4954
4930
|
const out = options.stdout ?? console;
|
|
4955
4931
|
const cfg = await loadProjectConfig(options.configPath);
|
|
@@ -5394,28 +5370,31 @@ async function smoke(options) {
|
|
|
5394
5370
|
const out = options.stdout ?? console;
|
|
5395
5371
|
const cfg = await loadProjectConfig(options.configPath);
|
|
5396
5372
|
const env = resolveEnv(cfg, options.env);
|
|
5397
|
-
const
|
|
5398
|
-
|
|
5373
|
+
const runtime = options.runtime === true;
|
|
5374
|
+
const credentials = runtime ? null : readCredentials(cfg.local.credentialsFile);
|
|
5375
|
+
if (!runtime && !credentials) {
|
|
5399
5376
|
throw new Error(`local credentials missing: ${displayPath(cfg.local.credentialsFile, cfg.rootDir)}. Run "odla-ai provision --write-dev-vars".`);
|
|
5400
5377
|
}
|
|
5401
|
-
if (credentials.appId !== cfg.app.id) {
|
|
5378
|
+
if (credentials && credentials.appId !== cfg.app.id) {
|
|
5402
5379
|
throw new Error(`local credentials are for app "${credentials.appId}", but config app is "${cfg.app.id}"`);
|
|
5403
5380
|
}
|
|
5404
|
-
const entry = credentials
|
|
5405
|
-
if (!entry?.tenantId) {
|
|
5381
|
+
const entry = credentials?.envs[env];
|
|
5382
|
+
if (!runtime && !entry?.tenantId) {
|
|
5406
5383
|
throw new Error(`local credentials have no tenant for env "${env}". Run "odla-ai provision --write-dev-vars".`);
|
|
5407
5384
|
}
|
|
5408
5385
|
const hasDb = cfg.services.includes("db");
|
|
5409
5386
|
const hasO11y = cfg.services.includes("o11y");
|
|
5410
|
-
if (hasDb && !entry
|
|
5387
|
+
if (!runtime && hasDb && !entry?.dbKey) {
|
|
5411
5388
|
throw new Error(`local credentials have no db key for env "${env}". Run "odla-ai provision --write-dev-vars".`);
|
|
5412
5389
|
}
|
|
5413
|
-
if (hasO11y && !entry
|
|
5390
|
+
if (!runtime && hasO11y && !entry?.o11yToken) {
|
|
5414
5391
|
throw new Error(`local credentials have no o11y token for env "${env}". Run "odla-ai provision --write-dev-vars".`);
|
|
5415
5392
|
}
|
|
5416
5393
|
const doFetch = options.fetch ?? fetch;
|
|
5394
|
+
const tenantId = entry?.tenantId ?? resolveTenant(cfg, env).tenant;
|
|
5417
5395
|
out.log(`smoke: ${cfg.app.id}/${env}`);
|
|
5418
|
-
out.log(`
|
|
5396
|
+
out.log(` mode: ${runtime ? "runtime (Worker-held credentials)" : "local credentials"}`);
|
|
5397
|
+
out.log(` tenant: ${tenantId}`);
|
|
5419
5398
|
const publicConfig = await getJson(doFetch, publicConfigUrl(cfg.platformUrl, cfg.app.id, env), void 0);
|
|
5420
5399
|
out.log(` public-config: ok`);
|
|
5421
5400
|
if (cfg.services.includes("ai") && cfg.ai?.provider) {
|
|
@@ -5429,7 +5408,7 @@ async function smoke(options) {
|
|
|
5429
5408
|
if (mode !== "hosted") throw new Error(`ai mode mismatch: expected "hosted", public-config has "${String(mode ?? "none")}"`);
|
|
5430
5409
|
out.log(" ai: hosted");
|
|
5431
5410
|
}
|
|
5432
|
-
if (hasO11y) out.log(` o11y: credentials present`);
|
|
5411
|
+
if (hasO11y) out.log(runtime ? ` o11y: Worker-held credential` : ` o11y: credentials present`);
|
|
5433
5412
|
if (cfg.services.includes("calendar")) {
|
|
5434
5413
|
const token = await getDeveloperToken(
|
|
5435
5414
|
cfg,
|
|
@@ -5448,10 +5427,10 @@ async function smoke(options) {
|
|
|
5448
5427
|
out.log(` calendar: ${status.status}, bookable (booking \u2192 ${status.bookingCalendarId ?? "primary"})`);
|
|
5449
5428
|
}
|
|
5450
5429
|
const database = await resolveDatabaseConfig(cfg);
|
|
5451
|
-
if (hasDb) {
|
|
5430
|
+
if (hasDb && !runtime) {
|
|
5452
5431
|
const expectedSchema = database.schema;
|
|
5453
5432
|
const expectedEntities = serializedEntities(expectedSchema);
|
|
5454
|
-
const liveSchemaPayload = await getJson(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(
|
|
5433
|
+
const liveSchemaPayload = await getJson(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(tenantId)}/schema`, entry.dbKey);
|
|
5455
5434
|
const liveSchema = liveSchemaPayload.schema ?? liveSchemaPayload;
|
|
5456
5435
|
const liveEntities = serializedEntities(liveSchema);
|
|
5457
5436
|
if (expectedEntities.length) {
|
|
@@ -5461,7 +5440,7 @@ async function smoke(options) {
|
|
|
5461
5440
|
out.log(` schema: ${liveEntities.length} entities`);
|
|
5462
5441
|
const aggregateEntity = expectedEntities[0] ?? liveEntities[0];
|
|
5463
5442
|
if (aggregateEntity) {
|
|
5464
|
-
const aggregate = await postJson2(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(
|
|
5443
|
+
const aggregate = await postJson2(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(tenantId)}/aggregate`, entry.dbKey, {
|
|
5465
5444
|
ns: aggregateEntity,
|
|
5466
5445
|
aggregate: { count: true }
|
|
5467
5446
|
});
|
|
@@ -5470,9 +5449,20 @@ async function smoke(options) {
|
|
|
5470
5449
|
} else {
|
|
5471
5450
|
out.log(` aggregate: skipped (schema has no entities)`);
|
|
5472
5451
|
}
|
|
5452
|
+
} else if (hasDb) {
|
|
5453
|
+
out.log(` db: Worker-held credential (direct schema/aggregate skipped)`);
|
|
5473
5454
|
} else {
|
|
5474
5455
|
out.log(` db: skipped (not enabled)`);
|
|
5475
5456
|
}
|
|
5457
|
+
if (runtime) {
|
|
5458
|
+
const link = cfg.links?.[env];
|
|
5459
|
+
if (!link) throw new Error(`runtime smoke requires links.${env} in odla.config.mjs`);
|
|
5460
|
+
const res = await doFetch(link, { redirect: "manual" });
|
|
5461
|
+
if (res.status < 200 || res.status >= 500) {
|
|
5462
|
+
throw new Error(`runtime target ${new URL(link).origin} returned ${res.status}`);
|
|
5463
|
+
}
|
|
5464
|
+
out.log(` runtime target: ${res.status}`);
|
|
5465
|
+
}
|
|
5476
5466
|
const probes = database.integrations.flatMap(
|
|
5477
5467
|
(integration) => (integration.probes ?? []).map((probe) => ({ integration: integration.id, probe }))
|
|
5478
5468
|
);
|
|
@@ -5687,10 +5677,11 @@ async function projectCommand(command, parsed, deps) {
|
|
|
5687
5677
|
return true;
|
|
5688
5678
|
}
|
|
5689
5679
|
if (command === "smoke") {
|
|
5690
|
-
assertArgs(parsed, ["config", "env", "token", "email", "open"], 1);
|
|
5680
|
+
assertArgs(parsed, ["config", "env", "runtime", "token", "email", "open"], 1);
|
|
5691
5681
|
await smoke({
|
|
5692
5682
|
configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
|
|
5693
5683
|
env: stringOpt(parsed.options.env),
|
|
5684
|
+
runtime: parsed.options.runtime === true,
|
|
5694
5685
|
token: stringOpt(parsed.options.token),
|
|
5695
5686
|
email: stringOpt(parsed.options.email),
|
|
5696
5687
|
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
@@ -9501,6 +9492,65 @@ var init_context_command = __esm({
|
|
|
9501
9492
|
}
|
|
9502
9493
|
});
|
|
9503
9494
|
|
|
9495
|
+
// src/credential-command.ts
|
|
9496
|
+
async function responseError(response2) {
|
|
9497
|
+
return redactSecrets((await response2.text()).slice(0, 1e3));
|
|
9498
|
+
}
|
|
9499
|
+
async function credentialCommand(parsed, deps = {}) {
|
|
9500
|
+
const action2 = parsed.positionals[1] ?? "list";
|
|
9501
|
+
if (action2 !== "list" && action2 !== "revoke") {
|
|
9502
|
+
throw new Error(`unknown credentials action "${action2}". Try "odla-ai credentials list".`);
|
|
9503
|
+
}
|
|
9504
|
+
assertArgs(parsed, ["config", "env", "all", "json", "token", "email", "open"], action2 === "revoke" ? 3 : 2);
|
|
9505
|
+
const cfg = await loadProjectConfig(stringOpt(parsed.options.config) ?? "odla.config.mjs");
|
|
9506
|
+
const doFetch = deps.fetch ?? fetch;
|
|
9507
|
+
const out = deps.stdout ?? console;
|
|
9508
|
+
const token = await getDeveloperToken(cfg, {
|
|
9509
|
+
configPath: cfg.configPath,
|
|
9510
|
+
token: stringOpt(parsed.options.token),
|
|
9511
|
+
email: stringOpt(parsed.options.email),
|
|
9512
|
+
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
9513
|
+
openApprovalUrl: deps.openUrl
|
|
9514
|
+
}, doFetch, out, { optionalProjectCapabilities: ["app.manage"] });
|
|
9515
|
+
const base = `${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/runtime-credentials`;
|
|
9516
|
+
if (action2 === "revoke") {
|
|
9517
|
+
const id = parsed.positionals[2];
|
|
9518
|
+
if (!id) throw new Error("credentials revoke requires the exact receipt id from credentials list");
|
|
9519
|
+
const response3 = await doFetch(`${base}/${encodeURIComponent(id)}`, {
|
|
9520
|
+
method: "DELETE",
|
|
9521
|
+
headers: { authorization: `Bearer ${token}` }
|
|
9522
|
+
});
|
|
9523
|
+
if (!response3.ok) throw new Error(`runtime credential revoke failed (${response3.status}): ${await responseError(response3)}`);
|
|
9524
|
+
const body2 = await response3.json();
|
|
9525
|
+
return out.log(parsed.options.json === true ? JSON.stringify(body2, null, 2) : `revoked ${body2.receipt.id} (${body2.receipt.env} ${body2.receipt.target.scriptName})`);
|
|
9526
|
+
}
|
|
9527
|
+
const query = new URLSearchParams();
|
|
9528
|
+
const requestedEnv = stringOpt(parsed.options.env);
|
|
9529
|
+
if (requestedEnv) query.set("env", requestedEnv);
|
|
9530
|
+
const response2 = await doFetch(`${base}${query.size ? `?${query}` : ""}`, {
|
|
9531
|
+
headers: { authorization: `Bearer ${token}` }
|
|
9532
|
+
});
|
|
9533
|
+
if (!response2.ok) throw new Error(`runtime credential inventory failed (${response2.status}): ${await responseError(response2)}`);
|
|
9534
|
+
const body = await response2.json();
|
|
9535
|
+
const credentials = parsed.options.all === true ? body.credentials : body.credentials.filter((item) => item.state === "committed");
|
|
9536
|
+
if (parsed.options.json === true) return out.log(JSON.stringify({ credentials }, null, 2));
|
|
9537
|
+
if (!credentials.length) return out.log("no active runtime credential receipts");
|
|
9538
|
+
out.log("RECEIPT ENV TARGET CREATED");
|
|
9539
|
+
for (const item of credentials) {
|
|
9540
|
+
out.log(`${item.id} ${item.env} ${item.target.accountId ?? "local"}/${item.target.scriptName} ${new Date(item.createdAt).toISOString()}`);
|
|
9541
|
+
}
|
|
9542
|
+
}
|
|
9543
|
+
var init_credential_command = __esm({
|
|
9544
|
+
"src/credential-command.ts"() {
|
|
9545
|
+
"use strict";
|
|
9546
|
+
init_cjs_shims();
|
|
9547
|
+
init_argv();
|
|
9548
|
+
init_config();
|
|
9549
|
+
init_redact();
|
|
9550
|
+
init_token();
|
|
9551
|
+
}
|
|
9552
|
+
});
|
|
9553
|
+
|
|
9504
9554
|
// src/help-usage.ts
|
|
9505
9555
|
var USAGE_SECTION;
|
|
9506
9556
|
var init_help_usage = __esm({
|
|
@@ -9624,7 +9674,9 @@ Usage:
|
|
|
9624
9674
|
odla-ai security run [target] --ack-redacted-source [--env dev] [--profile odla] [--fail-on high]
|
|
9625
9675
|
odla-ai security run [target] --self --ack-redacted-source
|
|
9626
9676
|
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]
|
|
9627
|
-
odla-ai
|
|
9677
|
+
odla-ai credentials list [--config odla.config.mjs] [--env dev] [--all] [--json]
|
|
9678
|
+
odla-ai credentials revoke <receipt-id> [--config odla.config.mjs] [--json]
|
|
9679
|
+
odla-ai smoke [--config odla.config.mjs] [--env dev] [--runtime] [--email <odla-account>] [--no-open]
|
|
9628
9680
|
odla-ai skill install [--dir <project>] [--agent <name>] [--global] [--force]
|
|
9629
9681
|
odla-ai secrets push --env <env> [--config odla.config.mjs] [--dry-run] [--yes]
|
|
9630
9682
|
odla-ai secrets set <name> --env <env> (--from-env <NAME>|--stdin) [--email <odla-account>] [--config odla.config.mjs] [--yes]
|
|
@@ -9728,7 +9780,7 @@ Commands:
|
|
|
9728
9780
|
"provision --live --yes" initializes only the live instance of
|
|
9729
9781
|
an existing sandbox app and enables every configured service;
|
|
9730
9782
|
no edit to the dev-first envs list is required.
|
|
9731
|
-
smoke Verify
|
|
9783
|
+
smoke Verify local service access, or use --runtime for a Worker-held credential deployment.
|
|
9732
9784
|
skill Same installer; --agent accepts all, claude, codex, cursor,
|
|
9733
9785
|
copilot, gemini, or agents (repeatable or comma-separated).
|
|
9734
9786
|
secrets Push configured db/o11y secrets into the Worker via wrangler
|
|
@@ -9748,9 +9800,13 @@ Safety:
|
|
|
9748
9800
|
pinned versions of every external @odla-ai runtime module before importing
|
|
9749
9801
|
the command graph. A stale workspace module blocks with its resolved path and
|
|
9750
9802
|
tells the agent to update/rebase, npm ci, and rebuild.
|
|
9751
|
-
Provision caches the approved developer token
|
|
9752
|
-
|
|
9753
|
-
|
|
9803
|
+
Provision caches the approved developer token under .odla/ with mode 0600.
|
|
9804
|
+
Local-only development provisioning may also cache that developer's service
|
|
9805
|
+
credentials there. \`provision --push-secrets\` instead stages a fresh,
|
|
9806
|
+
independently revocable credential set through the approved handshake,
|
|
9807
|
+
transfers the complete set to the exact Worker with \`wrangler secret bulk\`
|
|
9808
|
+
over stdin, and never writes its plaintext under .odla. Secret push preflights
|
|
9809
|
+
Wrangler before shown-once issuance; it never rotates sibling credentials.
|
|
9754
9810
|
Projectless PM, Discussions, o11y, runbook, and identity commands use
|
|
9755
9811
|
--platform/--app/--env, ODLA_PLATFORM_URL/ODLA_APP_ID/ODLA_ENV, and
|
|
9756
9812
|
ODLA_DEV_TOKEN. Save non-secret scope metadata with "context save", then
|
|
@@ -9764,12 +9820,11 @@ Safety:
|
|
|
9764
9820
|
attempts to open it \u2014 including in CI, SSH, display-less, and agent-driven
|
|
9765
9821
|
shells. Only --no-open suppresses the attempt. Browser launch is best-effort:
|
|
9766
9822
|
agents with browser control must open that exact URL immediately; otherwise
|
|
9767
|
-
they must give it to the human verbatim. A
|
|
9768
|
-
|
|
9769
|
-
the same code. Outside an interactive terminal the wait is capped (90s by
|
|
9823
|
+
they must give it to the human verbatim. A device code remains only in the
|
|
9824
|
+
running process. Outside an interactive terminal the wait is capped (90s by
|
|
9770
9825
|
default, --wait <seconds> to change); a still-pending handshake then exits
|
|
9771
|
-
with code 75
|
|
9772
|
-
|
|
9826
|
+
with code 75. Rerunning always requests and opens a new code; older clients'
|
|
9827
|
+
persisted pending state is discarded.
|
|
9773
9828
|
A fresh device handshake requires --email <odla-account> or ODLA_USER_EMAIL.
|
|
9774
9829
|
The email is a non-secret identity hint: never provide a password or session
|
|
9775
9830
|
token. It is the email shown by the signed-in odla account \u2014 never infer it
|
|
@@ -11829,7 +11884,7 @@ async function issueO11yToken(opts) {
|
|
|
11829
11884
|
);
|
|
11830
11885
|
if (res.status === 409 && !opts.rotateO11y) {
|
|
11831
11886
|
throw new Error(
|
|
11832
|
-
`o11y token already exists for env "${opts.env}", but its shown-once value is not in the local credentials file; run "odla-ai provision --
|
|
11887
|
+
`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`
|
|
11833
11888
|
);
|
|
11834
11889
|
}
|
|
11835
11890
|
if (!res.ok) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) failed: ${res.status} ${await safeText7(res)}`);
|
|
@@ -11855,6 +11910,97 @@ var init_provision_credentials = __esm({
|
|
|
11855
11910
|
}
|
|
11856
11911
|
});
|
|
11857
11912
|
|
|
11913
|
+
// src/runtime-credentials.ts
|
|
11914
|
+
function runtimeUrl(cfg, suffix = "") {
|
|
11915
|
+
return `${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/runtime-credentials${suffix}`;
|
|
11916
|
+
}
|
|
11917
|
+
async function safeError(response2) {
|
|
11918
|
+
const text2 = await response2.text();
|
|
11919
|
+
return redactSecrets(text2.slice(0, 1e3));
|
|
11920
|
+
}
|
|
11921
|
+
async function finish(doFetch, cfg, token, sessionId, method) {
|
|
11922
|
+
return doFetch(runtimeUrl(cfg, `/${encodeURIComponent(sessionId)}`), {
|
|
11923
|
+
method,
|
|
11924
|
+
headers: { authorization: `Bearer ${token}` }
|
|
11925
|
+
});
|
|
11926
|
+
}
|
|
11927
|
+
async function deliverRuntimeCredentials(cfg, options) {
|
|
11928
|
+
const doFetch = options.fetch ?? fetch;
|
|
11929
|
+
const run = options.runner ?? defaultRunner;
|
|
11930
|
+
const wranglerEnv = options.env === "prod" || options.env === "production" ? void 0 : options.env;
|
|
11931
|
+
const target = await wranglerRuntimeTarget(run, {
|
|
11932
|
+
cwd: cfg.rootDir,
|
|
11933
|
+
env: wranglerEnv
|
|
11934
|
+
});
|
|
11935
|
+
const issue = await doFetch(runtimeUrl(cfg), {
|
|
11936
|
+
method: "POST",
|
|
11937
|
+
headers: {
|
|
11938
|
+
authorization: `Bearer ${options.developerToken}`,
|
|
11939
|
+
"content-type": "application/json"
|
|
11940
|
+
},
|
|
11941
|
+
body: JSON.stringify({
|
|
11942
|
+
env: options.env,
|
|
11943
|
+
idempotencyKey: `wrangler:${(0, import_node_crypto5.randomUUID)()}`,
|
|
11944
|
+
target
|
|
11945
|
+
})
|
|
11946
|
+
});
|
|
11947
|
+
const body = await issue.json().catch(() => null);
|
|
11948
|
+
if (!issue.ok || typeof body?.sessionId !== "string" || !body.secrets || typeof body.secrets !== "object") {
|
|
11949
|
+
const detail = body?.error?.code ?? body?.error?.message ?? issue.status;
|
|
11950
|
+
throw new Error(`runtime credential staging failed: ${String(detail)}`);
|
|
11951
|
+
}
|
|
11952
|
+
const secrets = body.secrets;
|
|
11953
|
+
const values = {};
|
|
11954
|
+
for (const name of ["ODLA_API_KEY", "ODLA_O11Y_TOKEN"]) {
|
|
11955
|
+
const value2 = secrets[name];
|
|
11956
|
+
if (typeof value2 === "string" && value2) values[name] = value2;
|
|
11957
|
+
}
|
|
11958
|
+
if (Object.keys(values).length === 0) {
|
|
11959
|
+
await finish(doFetch, cfg, options.developerToken, body.sessionId, "DELETE");
|
|
11960
|
+
throw new Error("runtime credential staging returned no configured service credentials");
|
|
11961
|
+
}
|
|
11962
|
+
const pushed = await wranglerBulkSecrets(run, {
|
|
11963
|
+
secrets: values,
|
|
11964
|
+
env: wranglerEnv,
|
|
11965
|
+
cwd: cfg.rootDir
|
|
11966
|
+
});
|
|
11967
|
+
if (pushed.code !== 0) {
|
|
11968
|
+
await finish(doFetch, cfg, options.developerToken, body.sessionId, "DELETE");
|
|
11969
|
+
const detail = redactSecrets(`${pushed.stderr || pushed.stdout}`.trim());
|
|
11970
|
+
throw new Error(`wrangler secret bulk failed (exit ${pushed.code}): ${detail}`);
|
|
11971
|
+
}
|
|
11972
|
+
const committed = await finish(
|
|
11973
|
+
doFetch,
|
|
11974
|
+
cfg,
|
|
11975
|
+
options.developerToken,
|
|
11976
|
+
body.sessionId,
|
|
11977
|
+
"POST"
|
|
11978
|
+
);
|
|
11979
|
+
if (!committed.ok) {
|
|
11980
|
+
await finish(doFetch, cfg, options.developerToken, body.sessionId, "DELETE");
|
|
11981
|
+
throw new Error(`runtime credential receipt commit failed: ${await safeError(committed)}`);
|
|
11982
|
+
}
|
|
11983
|
+
options.stdout?.log(
|
|
11984
|
+
`${options.env}: installed ${Object.keys(values).join(" + ")} on ${target.accountId}/${target.scriptName}${target.environment ? ` (${target.environment})` : ""}; plaintext stayed in memory`
|
|
11985
|
+
);
|
|
11986
|
+
return {
|
|
11987
|
+
sessionId: body.sessionId,
|
|
11988
|
+
target,
|
|
11989
|
+
...values.ODLA_API_KEY ? { dbKey: values.ODLA_API_KEY } : {},
|
|
11990
|
+
...values.ODLA_O11Y_TOKEN ? { o11yToken: values.ODLA_O11Y_TOKEN } : {}
|
|
11991
|
+
};
|
|
11992
|
+
}
|
|
11993
|
+
var import_node_crypto5;
|
|
11994
|
+
var init_runtime_credentials = __esm({
|
|
11995
|
+
"src/runtime-credentials.ts"() {
|
|
11996
|
+
"use strict";
|
|
11997
|
+
init_cjs_shims();
|
|
11998
|
+
import_node_crypto5 = require("crypto");
|
|
11999
|
+
init_redact();
|
|
12000
|
+
init_wrangler();
|
|
12001
|
+
}
|
|
12002
|
+
});
|
|
12003
|
+
|
|
11858
12004
|
// src/provision-live.ts
|
|
11859
12005
|
function liveProvisionConfig(cfg) {
|
|
11860
12006
|
if (!cfg.envs.includes("dev")) {
|
|
@@ -11894,8 +12040,10 @@ async function provision(options) {
|
|
|
11894
12040
|
if (options.rotateO11yToken && !hasO11y) {
|
|
11895
12041
|
throw new Error("--rotate-o11y-token requires the o11y service in odla.config.mjs");
|
|
11896
12042
|
}
|
|
11897
|
-
if (options.pushSecrets && options.
|
|
11898
|
-
throw new Error(
|
|
12043
|
+
if (options.pushSecrets && (options.rotateKeys || options.rotateO11yToken)) {
|
|
12044
|
+
throw new Error(
|
|
12045
|
+
"--push-secrets always issues an independent runtime credential set; do not combine it with destructive rotation flags"
|
|
12046
|
+
);
|
|
11899
12047
|
}
|
|
11900
12048
|
out.log(`odla-ai: ${plan.appName} (${plan.appId})`);
|
|
11901
12049
|
out.log(` platform: ${plan.platformUrl}`);
|
|
@@ -11932,7 +12080,7 @@ async function provision(options) {
|
|
|
11932
12080
|
out.log(` secrets: ${options.pushSecrets ? "push configured Worker secrets" : "local only"}`);
|
|
11933
12081
|
return;
|
|
11934
12082
|
}
|
|
11935
|
-
let credentials = readCredentials(cfg.local.credentialsFile);
|
|
12083
|
+
let credentials = options.pushSecrets ? null : readCredentials(cfg.local.credentialsFile);
|
|
11936
12084
|
if (credentials && credentials.appId !== cfg.app.id) {
|
|
11937
12085
|
throw new Error(
|
|
11938
12086
|
`credentials at ${displayPath(cfg.local.credentialsFile, cfg.rootDir)} are for "${credentials.appId}", not "${cfg.app.id}"`
|
|
@@ -11941,7 +12089,7 @@ async function provision(options) {
|
|
|
11941
12089
|
const rotatesO11y = !!(options.rotateKeys || options.rotateO11yToken);
|
|
11942
12090
|
const missingO11y = cfg.envs.some((env) => !credentials?.envs[env]?.o11yToken);
|
|
11943
12091
|
const missingDb = cfg.envs.some((env) => !credentials?.envs[env]?.dbKey);
|
|
11944
|
-
const losesShownOnceCredential = hasDb && (!!options.rotateKeys || missingDb) || hasO11y && (rotatesO11y || missingO11y);
|
|
12092
|
+
const losesShownOnceCredential = !options.pushSecrets && (hasDb && (!!options.rotateKeys || missingDb) || hasO11y && (rotatesO11y || missingO11y));
|
|
11945
12093
|
if (options.writeCredentials === false && losesShownOnceCredential) {
|
|
11946
12094
|
throw new Error("credential issuance/rotation requires the private credentials file; remove --no-write-credentials");
|
|
11947
12095
|
}
|
|
@@ -12015,38 +12163,42 @@ async function provision(options) {
|
|
|
12015
12163
|
await applyCalendarBookingPage(calendarCtx, calendarBookingPageUrl(cfg, env), out);
|
|
12016
12164
|
}
|
|
12017
12165
|
}
|
|
12166
|
+
let devVarsCredentials = credentials;
|
|
12018
12167
|
for (const env of cfg.envs) {
|
|
12019
12168
|
const tenantId = (0, import_apps12.tenantIdFor)(cfg.app.id, env);
|
|
12020
|
-
|
|
12021
|
-
cfg,
|
|
12022
|
-
env,
|
|
12023
|
-
developerToken: token,
|
|
12024
|
-
credentials,
|
|
12025
|
-
rotateDb: !!options.rotateKeys,
|
|
12026
|
-
rotateO11y: rotatesO11y,
|
|
12027
|
-
write: options.writeCredentials !== false,
|
|
12028
|
-
fetch: doFetch,
|
|
12029
|
-
stdout: out
|
|
12030
|
-
});
|
|
12031
|
-
const dbKey = credentials.envs[env]?.dbKey;
|
|
12169
|
+
let dbKey;
|
|
12032
12170
|
if (options.pushSecrets) {
|
|
12033
|
-
|
|
12034
|
-
|
|
12035
|
-
|
|
12036
|
-
|
|
12037
|
-
|
|
12038
|
-
|
|
12039
|
-
|
|
12040
|
-
|
|
12041
|
-
|
|
12042
|
-
|
|
12043
|
-
|
|
12044
|
-
|
|
12045
|
-
|
|
12046
|
-
|
|
12047
|
-
|
|
12048
|
-
|
|
12049
|
-
}
|
|
12171
|
+
const delivered = await deliverRuntimeCredentials(cfg, {
|
|
12172
|
+
env,
|
|
12173
|
+
developerToken: token,
|
|
12174
|
+
fetch: doFetch,
|
|
12175
|
+
runner: options.secretRunner,
|
|
12176
|
+
stdout: out
|
|
12177
|
+
});
|
|
12178
|
+
dbKey = delivered.dbKey;
|
|
12179
|
+
devVarsCredentials = mergeCredential(devVarsCredentials, {
|
|
12180
|
+
appId: cfg.app.id,
|
|
12181
|
+
platformUrl: cfg.platformUrl,
|
|
12182
|
+
dbEndpoint: cfg.dbEndpoint,
|
|
12183
|
+
env,
|
|
12184
|
+
tenantId,
|
|
12185
|
+
...delivered.dbKey ? { dbKey: delivered.dbKey } : {},
|
|
12186
|
+
...delivered.o11yToken ? { o11yToken: delivered.o11yToken } : {}
|
|
12187
|
+
});
|
|
12188
|
+
} else {
|
|
12189
|
+
credentials = await provisionEnvCredentials({
|
|
12190
|
+
cfg,
|
|
12191
|
+
env,
|
|
12192
|
+
developerToken: token,
|
|
12193
|
+
credentials,
|
|
12194
|
+
rotateDb: !!options.rotateKeys,
|
|
12195
|
+
rotateO11y: rotatesO11y,
|
|
12196
|
+
write: options.writeCredentials !== false,
|
|
12197
|
+
fetch: doFetch,
|
|
12198
|
+
stdout: out
|
|
12199
|
+
});
|
|
12200
|
+
devVarsCredentials = credentials;
|
|
12201
|
+
dbKey = credentials.envs[env]?.dbKey;
|
|
12050
12202
|
}
|
|
12051
12203
|
if (schema && dbKey) {
|
|
12052
12204
|
await postJson(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(tenantId)}/schema`, dbKey, { schema });
|
|
@@ -12070,13 +12222,13 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
|
|
|
12070
12222
|
}
|
|
12071
12223
|
}
|
|
12072
12224
|
}
|
|
12073
|
-
if (options.writeCredentials !== false && credentials) {
|
|
12225
|
+
if (!options.pushSecrets && options.writeCredentials !== false && credentials) {
|
|
12074
12226
|
const ignored = cfg.local.gitignore && gitignoreEntry(cfg.rootDir, cfg.local.credentialsFile);
|
|
12075
12227
|
out.log(`credentials: wrote ${displayPath(cfg.local.credentialsFile, cfg.rootDir)} (0600${ignored ? ", gitignored" : ""})`);
|
|
12076
12228
|
}
|
|
12077
|
-
if (devVarsTarget &&
|
|
12229
|
+
if (devVarsTarget && devVarsCredentials) {
|
|
12078
12230
|
const env = cfg.envs.includes("dev") ? "dev" : cfg.envs[0] ?? "prod";
|
|
12079
|
-
writeDevVars(devVarsTarget,
|
|
12231
|
+
writeDevVars(devVarsTarget, devVarsCredentials, env, o11yDevVars(cfg));
|
|
12080
12232
|
out.log(`dev vars: wrote ${displayPath(devVarsTarget, cfg.rootDir)} for ${env}`);
|
|
12081
12233
|
}
|
|
12082
12234
|
if (cfg.services.includes("calendar")) {
|
|
@@ -12116,6 +12268,7 @@ var init_provision = __esm({
|
|
|
12116
12268
|
init_token();
|
|
12117
12269
|
init_local();
|
|
12118
12270
|
init_provision_credentials();
|
|
12271
|
+
init_runtime_credentials();
|
|
12119
12272
|
init_provision_live();
|
|
12120
12273
|
init_provision_helpers();
|
|
12121
12274
|
init_secrets();
|
|
@@ -12226,6 +12379,7 @@ var init_surface = __esm({
|
|
|
12226
12379
|
code: { connect: {} },
|
|
12227
12380
|
config: { diff: {}, plan: {}, apply: {} },
|
|
12228
12381
|
context: { show: {}, list: {}, save: {}, remove: {} },
|
|
12382
|
+
credentials: { list: {}, revoke: {} },
|
|
12229
12383
|
// `watch`, `read`, `reply`, and `resolve` take a topic id from there on.
|
|
12230
12384
|
discuss: {
|
|
12231
12385
|
groups: {},
|
|
@@ -14022,6 +14176,10 @@ async function runCli(argv2 = process.argv.slice(2), dependencies = {}) {
|
|
|
14022
14176
|
await contextCommand(parsed, runtime);
|
|
14023
14177
|
return;
|
|
14024
14178
|
}
|
|
14179
|
+
if (command === "credentials") {
|
|
14180
|
+
await credentialCommand(parsed, runtime);
|
|
14181
|
+
return;
|
|
14182
|
+
}
|
|
14025
14183
|
if (command === "runbook") {
|
|
14026
14184
|
await runbookCommand(parsed, runtime);
|
|
14027
14185
|
return;
|
|
@@ -14164,6 +14322,7 @@ var init_cli = __esm({
|
|
|
14164
14322
|
init_cli_project();
|
|
14165
14323
|
init_code_command();
|
|
14166
14324
|
init_context_command();
|
|
14325
|
+
init_credential_command();
|
|
14167
14326
|
init_help();
|
|
14168
14327
|
init_version();
|
|
14169
14328
|
init_discuss_command();
|