@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
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
import process8 from "process";
|
|
14
14
|
|
|
15
15
|
// src/token.ts
|
|
16
|
-
import {
|
|
16
|
+
import { OdlaError, requestToken } from "@odla-ai/db";
|
|
17
17
|
import { createHash } from "crypto";
|
|
18
18
|
import process5 from "process";
|
|
19
19
|
|
|
@@ -117,12 +117,40 @@ function handshakeUrl(platformUrl, userCode) {
|
|
|
117
117
|
|
|
118
118
|
// src/handshake-state.ts
|
|
119
119
|
import { rmSync } from "fs";
|
|
120
|
-
import { dirname
|
|
120
|
+
import { dirname, join } from "path";
|
|
121
121
|
import process4 from "process";
|
|
122
|
+
function handshakeFile(cfg) {
|
|
123
|
+
return join(dirname(cfg.local.tokenFile), "handshake.local.json");
|
|
124
|
+
}
|
|
125
|
+
function clearPendingHandshake(path) {
|
|
126
|
+
rmSync(path, { force: true });
|
|
127
|
+
}
|
|
128
|
+
function minutesLeft(expiresAt) {
|
|
129
|
+
return Math.max(1, Math.round((expiresAt - Date.now()) / 6e4));
|
|
130
|
+
}
|
|
131
|
+
function approvalHint(pending) {
|
|
132
|
+
return `approve code ${pending.userCode} at ${pending.approvalUrl} (${minutesLeft(pending.expiresAt)}m left)`;
|
|
133
|
+
}
|
|
134
|
+
function approvalReminder(out, pending, periodMs = 3e4) {
|
|
135
|
+
const timer = setInterval(() => {
|
|
136
|
+
for (const line of reminderLines({
|
|
137
|
+
userCode: pending.userCode,
|
|
138
|
+
approvalUrl: pending.approvalUrl,
|
|
139
|
+
minutesLeft: minutesLeft(pending.expiresAt)
|
|
140
|
+
}))
|
|
141
|
+
out.log(line);
|
|
142
|
+
}, periodMs);
|
|
143
|
+
timer.unref?.();
|
|
144
|
+
return () => clearInterval(timer);
|
|
145
|
+
}
|
|
146
|
+
function handshakeWaitMs(waitSeconds, interactive = process4.stdout.isTTY === true) {
|
|
147
|
+
if (waitSeconds !== void 0) return waitSeconds * 1e3;
|
|
148
|
+
return interactive ? void 0 : 9e4;
|
|
149
|
+
}
|
|
122
150
|
|
|
123
151
|
// src/local.ts
|
|
124
152
|
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
|
|
125
|
-
import { dirname, isAbsolute, relative, resolve } from "path";
|
|
153
|
+
import { dirname as dirname2, isAbsolute, relative, resolve } from "path";
|
|
126
154
|
var GITIGNORE_LINES = [".odla/*.local.json", ".odla/dev-token.json", ".dev.vars"];
|
|
127
155
|
function readJsonFile(path) {
|
|
128
156
|
try {
|
|
@@ -190,7 +218,7 @@ function o11yDevVars(cfg) {
|
|
|
190
218
|
function resolveWriteDevVarsTarget(cfg, requested) {
|
|
191
219
|
if (!requested) return null;
|
|
192
220
|
if (requested === true) return cfg.local.devVarsFile;
|
|
193
|
-
return resolve(
|
|
221
|
+
return resolve(dirname2(cfg.configPath), requested);
|
|
194
222
|
}
|
|
195
223
|
function writeDevVars(path, credentials, env, o11y) {
|
|
196
224
|
const entry = credentials.envs[env];
|
|
@@ -230,7 +258,7 @@ function isManagedDevVar(line) {
|
|
|
230
258
|
return !!match?.[1] && MANAGED_DEV_VARS.has(match[1]);
|
|
231
259
|
}
|
|
232
260
|
function writePrivateText(path, text2) {
|
|
233
|
-
mkdirSync(
|
|
261
|
+
mkdirSync(dirname2(path), { recursive: true });
|
|
234
262
|
const temporary = `${path}.tmp-${process.pid}-${Date.now()}`;
|
|
235
263
|
writeFileSync(temporary, text2, { mode: 384 });
|
|
236
264
|
chmodSync(temporary, 384);
|
|
@@ -246,58 +274,6 @@ function displayPath(path, rootDir = process.cwd()) {
|
|
|
246
274
|
return rel && !rel.startsWith("..") ? rel : path;
|
|
247
275
|
}
|
|
248
276
|
|
|
249
|
-
// src/handshake-state.ts
|
|
250
|
-
function handshakeFile(cfg) {
|
|
251
|
-
return join(dirname2(cfg.local.tokenFile), "handshake.local.json");
|
|
252
|
-
}
|
|
253
|
-
var RESUME_MARGIN_MS = 5e3;
|
|
254
|
-
function readPendingHandshake(path, platform, email, requiredGrant) {
|
|
255
|
-
const pending = readJsonFile(path);
|
|
256
|
-
if (!pending || pending.platform !== platform || pending.email !== email) return null;
|
|
257
|
-
if (typeof pending.userCode !== "string" || typeof pending.deviceCode !== "string") return null;
|
|
258
|
-
if (typeof pending.expiresAt !== "number" || pending.expiresAt <= Date.now() + RESUME_MARGIN_MS) return null;
|
|
259
|
-
if (requiredGrant && !grantCovers(pending, requiredGrant)) return null;
|
|
260
|
-
return {
|
|
261
|
-
interval: typeof pending.interval === "number" ? pending.interval : 3,
|
|
262
|
-
...pending,
|
|
263
|
-
approvalUrl: handshakeUrl(platform, pending.userCode)
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
function grantCovers(stored, required) {
|
|
267
|
-
if (required.optionalProjectCapabilities.length === 0) return true;
|
|
268
|
-
return required.projectIds.every((id) => stored.projectIds?.includes(id)) && required.optionalProjectCapabilities.every(
|
|
269
|
-
(capability) => stored.optionalProjectCapabilities?.includes(capability)
|
|
270
|
-
);
|
|
271
|
-
}
|
|
272
|
-
function writePendingHandshake(path, pending) {
|
|
273
|
-
writePrivateJson(path, pending);
|
|
274
|
-
}
|
|
275
|
-
function clearPendingHandshake(path) {
|
|
276
|
-
rmSync(path, { force: true });
|
|
277
|
-
}
|
|
278
|
-
function minutesLeft(expiresAt) {
|
|
279
|
-
return Math.max(1, Math.round((expiresAt - Date.now()) / 6e4));
|
|
280
|
-
}
|
|
281
|
-
function approvalHint(pending) {
|
|
282
|
-
return `approve code ${pending.userCode} at ${pending.approvalUrl} (${minutesLeft(pending.expiresAt)}m left)`;
|
|
283
|
-
}
|
|
284
|
-
function approvalReminder(out, pending, periodMs = 3e4) {
|
|
285
|
-
const timer = setInterval(() => {
|
|
286
|
-
for (const line of reminderLines({
|
|
287
|
-
userCode: pending.userCode,
|
|
288
|
-
approvalUrl: pending.approvalUrl,
|
|
289
|
-
minutesLeft: minutesLeft(pending.expiresAt)
|
|
290
|
-
}))
|
|
291
|
-
out.log(line);
|
|
292
|
-
}, periodMs);
|
|
293
|
-
timer.unref?.();
|
|
294
|
-
return () => clearInterval(timer);
|
|
295
|
-
}
|
|
296
|
-
function handshakeWaitMs(waitSeconds, interactive = process4.stdout.isTTY === true) {
|
|
297
|
-
if (waitSeconds !== void 0) return waitSeconds * 1e3;
|
|
298
|
-
return interactive ? void 0 : 9e4;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
277
|
// src/token.ts
|
|
302
278
|
async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {}) {
|
|
303
279
|
const audience = platformAudience(cfg.platformUrl);
|
|
@@ -336,8 +312,8 @@ async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {})
|
|
|
336
312
|
grantIntent
|
|
337
313
|
};
|
|
338
314
|
const waitMs = handshakeWaitMs(options.wait);
|
|
339
|
-
|
|
340
|
-
const { token, expiresAt } = await
|
|
315
|
+
clearPendingHandshake(ctx.pendingFile);
|
|
316
|
+
const { token, expiresAt } = await freshHandshake(ctx, waitMs);
|
|
341
317
|
clearPendingHandshake(ctx.pendingFile);
|
|
342
318
|
writePrivateJson(cfg.local.tokenFile, {
|
|
343
319
|
platform: audience,
|
|
@@ -350,47 +326,6 @@ async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {})
|
|
|
350
326
|
out.error(`auth: developer token cached (${displayPath(cfg.local.tokenFile, cfg.rootDir)})`);
|
|
351
327
|
return token;
|
|
352
328
|
}
|
|
353
|
-
async function resumePendingHandshake(ctx, waitMs) {
|
|
354
|
-
const pending = readPendingHandshake(
|
|
355
|
-
ctx.pendingFile,
|
|
356
|
-
ctx.audience,
|
|
357
|
-
ctx.email,
|
|
358
|
-
ctx.grantIntent
|
|
359
|
-
);
|
|
360
|
-
if (!pending) return null;
|
|
361
|
-
ctx.out.error("");
|
|
362
|
-
ctx.out.error(`auth: resuming pending handshake \u2014 ${approvalHint(pending)}`);
|
|
363
|
-
await presentHandshakeApproval(ctx.out, {
|
|
364
|
-
userCode: pending.userCode,
|
|
365
|
-
approvalUrl: pending.approvalUrl,
|
|
366
|
-
minutesLeft: Math.max(1, Math.floor((pending.expiresAt - Date.now()) / 6e4)),
|
|
367
|
-
purpose: `sign this terminal in as ${ctx.email}`
|
|
368
|
-
}, ctx.options);
|
|
369
|
-
ctx.out.error("");
|
|
370
|
-
const stopReminder = approvalReminder(ctx.out, pending);
|
|
371
|
-
try {
|
|
372
|
-
return await collectToken({
|
|
373
|
-
endpoint: ctx.cfg.platformUrl,
|
|
374
|
-
deviceCode: pending.deviceCode,
|
|
375
|
-
expiresAt: pending.expiresAt,
|
|
376
|
-
interval: pending.interval,
|
|
377
|
-
waitMs,
|
|
378
|
-
fetch: ctx.doFetch
|
|
379
|
-
});
|
|
380
|
-
} catch (err) {
|
|
381
|
-
const code = err instanceof OdlaError ? err.code : void 0;
|
|
382
|
-
if (code === "handshake_pending") throw stillPending(pending, ctx.email);
|
|
383
|
-
if (code === "handshake_expired" || code === "handshake_timeout") {
|
|
384
|
-
clearPendingHandshake(ctx.pendingFile);
|
|
385
|
-
ctx.out.error("auth: pending handshake lapsed unapproved; starting a fresh one");
|
|
386
|
-
return null;
|
|
387
|
-
}
|
|
388
|
-
if (code === "handshake_denied") clearPendingHandshake(ctx.pendingFile);
|
|
389
|
-
throw err;
|
|
390
|
-
} finally {
|
|
391
|
-
stopReminder();
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
329
|
async function freshHandshake(ctx, waitMs) {
|
|
395
330
|
let started;
|
|
396
331
|
let stopReminder;
|
|
@@ -418,7 +353,6 @@ async function freshHandshake(ctx, waitMs) {
|
|
|
418
353
|
projectIds: ctx.grantIntent.projectIds,
|
|
419
354
|
optionalProjectCapabilities: ctx.grantIntent.optionalProjectCapabilities
|
|
420
355
|
};
|
|
421
|
-
writePendingHandshake(ctx.pendingFile, started);
|
|
422
356
|
await presentHandshakeApproval(ctx.out, {
|
|
423
357
|
userCode,
|
|
424
358
|
approvalUrl,
|
|
@@ -454,7 +388,7 @@ function projectAgentHandle(appId) {
|
|
|
454
388
|
function stillPending(pending, email) {
|
|
455
389
|
return new OdlaError(
|
|
456
390
|
"handshake_pending",
|
|
457
|
-
`handshake still pending \u2014 ask ${email} to ${approvalHint(pending)}
|
|
391
|
+
`handshake still pending \u2014 ask ${email} to ${approvalHint(pending)}; if this command exits, re-run it to request a new code`,
|
|
458
392
|
{ retryable: true }
|
|
459
393
|
);
|
|
460
394
|
}
|
|
@@ -3831,6 +3765,41 @@ async function wranglerLoggedIn(run, cwd) {
|
|
|
3831
3765
|
return false;
|
|
3832
3766
|
}
|
|
3833
3767
|
}
|
|
3768
|
+
async function wranglerRuntimeTarget(run, opts) {
|
|
3769
|
+
const configPath = findWranglerConfig(opts.cwd);
|
|
3770
|
+
if (!configPath) throw new Error(`no wrangler config found in ${opts.cwd}`);
|
|
3771
|
+
const config = readWranglerConfig(configPath);
|
|
3772
|
+
if (!config) throw new Error("runtime credential delivery requires wrangler.json or wrangler.jsonc");
|
|
3773
|
+
const baseName = typeof config.name === "string" ? config.name : "";
|
|
3774
|
+
const envs = config.env && typeof config.env === "object" && !Array.isArray(config.env) ? config.env : {};
|
|
3775
|
+
const envConfig = opts.env && envs[opts.env] && typeof envs[opts.env] === "object" ? envs[opts.env] : {};
|
|
3776
|
+
const explicitName = typeof envConfig.name === "string" ? envConfig.name : "";
|
|
3777
|
+
const serviceEnvironments = config.legacy_env === false;
|
|
3778
|
+
if (serviceEnvironments && explicitName) {
|
|
3779
|
+
throw new Error("env.<name>.name is not allowed when wrangler legacy_env is false");
|
|
3780
|
+
}
|
|
3781
|
+
const scriptName = serviceEnvironments ? baseName : explicitName || (opts.env ? `${baseName}-${opts.env}` : baseName);
|
|
3782
|
+
if (!/^[a-z0-9][a-z0-9_-]{0,62}$/.test(scriptName)) {
|
|
3783
|
+
throw new Error("wrangler config must resolve an exact Worker name before credentials are issued");
|
|
3784
|
+
}
|
|
3785
|
+
const configuredAccount = typeof envConfig.account_id === "string" ? envConfig.account_id : typeof config.account_id === "string" ? config.account_id : "";
|
|
3786
|
+
const whoami = await run("npx", ["wrangler", "whoami"], { cwd: opts.cwd });
|
|
3787
|
+
if (whoami.code !== 0 || /not authenticated/i.test(`${whoami.stdout}${whoami.stderr}`)) {
|
|
3788
|
+
throw new Error(`wrangler is not logged in \u2014 run "wrangler login" (a browser step for the human)`);
|
|
3789
|
+
}
|
|
3790
|
+
const discovered = [...new Set(`${whoami.stdout}
|
|
3791
|
+
${whoami.stderr}`.match(/\b[a-f0-9]{32}\b/gi)?.map((id) => id.toLowerCase()) ?? [])];
|
|
3792
|
+
const accountId = configuredAccount.toLowerCase() || (discovered.length === 1 ? discovered[0] : "");
|
|
3793
|
+
if (!/^[a-f0-9]{32}$/.test(accountId) || configuredAccount && discovered.length > 0 && !discovered.includes(accountId)) {
|
|
3794
|
+
throw new Error("Wrangler account is ambiguous or does not match account_id in the config");
|
|
3795
|
+
}
|
|
3796
|
+
return {
|
|
3797
|
+
provider: "cloudflare",
|
|
3798
|
+
accountId,
|
|
3799
|
+
scriptName,
|
|
3800
|
+
...opts.env ? { environment: opts.env } : {}
|
|
3801
|
+
};
|
|
3802
|
+
}
|
|
3834
3803
|
function wranglerPutSecret(run, opts) {
|
|
3835
3804
|
const args = [
|
|
3836
3805
|
"wrangler",
|
|
@@ -3842,6 +3811,16 @@ function wranglerPutSecret(run, opts) {
|
|
|
3842
3811
|
];
|
|
3843
3812
|
return run("npx", args, { input: opts.value, cwd: opts.cwd });
|
|
3844
3813
|
}
|
|
3814
|
+
function wranglerBulkSecrets(run, opts) {
|
|
3815
|
+
const args = [
|
|
3816
|
+
"wrangler",
|
|
3817
|
+
"secret",
|
|
3818
|
+
"bulk",
|
|
3819
|
+
...opts.env ? ["--env", opts.env] : [],
|
|
3820
|
+
...opts.configPath ? ["--config", opts.configPath] : []
|
|
3821
|
+
];
|
|
3822
|
+
return run("npx", args, { input: JSON.stringify(opts.secrets), cwd: opts.cwd });
|
|
3823
|
+
}
|
|
3845
3824
|
|
|
3846
3825
|
// src/doctor-checks.ts
|
|
3847
3826
|
function lintRules(rules, entities, publicRead) {
|
|
@@ -4330,9 +4309,6 @@ var PROD_ENV_NAMES = /* @__PURE__ */ new Set(["prod", "production"]);
|
|
|
4330
4309
|
async function secretsPush(options) {
|
|
4331
4310
|
await secretsPushImpl(options, true);
|
|
4332
4311
|
}
|
|
4333
|
-
async function secretsPushAfterPreflight(options) {
|
|
4334
|
-
await secretsPushImpl(options, false);
|
|
4335
|
-
}
|
|
4336
4312
|
async function secretsPushImpl(options, preflight) {
|
|
4337
4313
|
const out = options.stdout ?? console;
|
|
4338
4314
|
const cfg = await loadProjectConfig(options.configPath);
|
|
@@ -4739,28 +4715,31 @@ async function smoke(options) {
|
|
|
4739
4715
|
const out = options.stdout ?? console;
|
|
4740
4716
|
const cfg = await loadProjectConfig(options.configPath);
|
|
4741
4717
|
const env = resolveEnv(cfg, options.env);
|
|
4742
|
-
const
|
|
4743
|
-
|
|
4718
|
+
const runtime = options.runtime === true;
|
|
4719
|
+
const credentials = runtime ? null : readCredentials(cfg.local.credentialsFile);
|
|
4720
|
+
if (!runtime && !credentials) {
|
|
4744
4721
|
throw new Error(`local credentials missing: ${displayPath(cfg.local.credentialsFile, cfg.rootDir)}. Run "odla-ai provision --write-dev-vars".`);
|
|
4745
4722
|
}
|
|
4746
|
-
if (credentials.appId !== cfg.app.id) {
|
|
4723
|
+
if (credentials && credentials.appId !== cfg.app.id) {
|
|
4747
4724
|
throw new Error(`local credentials are for app "${credentials.appId}", but config app is "${cfg.app.id}"`);
|
|
4748
4725
|
}
|
|
4749
|
-
const entry = credentials
|
|
4750
|
-
if (!entry?.tenantId) {
|
|
4726
|
+
const entry = credentials?.envs[env];
|
|
4727
|
+
if (!runtime && !entry?.tenantId) {
|
|
4751
4728
|
throw new Error(`local credentials have no tenant for env "${env}". Run "odla-ai provision --write-dev-vars".`);
|
|
4752
4729
|
}
|
|
4753
4730
|
const hasDb = cfg.services.includes("db");
|
|
4754
4731
|
const hasO11y = cfg.services.includes("o11y");
|
|
4755
|
-
if (hasDb && !entry
|
|
4732
|
+
if (!runtime && hasDb && !entry?.dbKey) {
|
|
4756
4733
|
throw new Error(`local credentials have no db key for env "${env}". Run "odla-ai provision --write-dev-vars".`);
|
|
4757
4734
|
}
|
|
4758
|
-
if (hasO11y && !entry
|
|
4735
|
+
if (!runtime && hasO11y && !entry?.o11yToken) {
|
|
4759
4736
|
throw new Error(`local credentials have no o11y token for env "${env}". Run "odla-ai provision --write-dev-vars".`);
|
|
4760
4737
|
}
|
|
4761
4738
|
const doFetch = options.fetch ?? fetch;
|
|
4739
|
+
const tenantId = entry?.tenantId ?? resolveTenant(cfg, env).tenant;
|
|
4762
4740
|
out.log(`smoke: ${cfg.app.id}/${env}`);
|
|
4763
|
-
out.log(`
|
|
4741
|
+
out.log(` mode: ${runtime ? "runtime (Worker-held credentials)" : "local credentials"}`);
|
|
4742
|
+
out.log(` tenant: ${tenantId}`);
|
|
4764
4743
|
const publicConfig = await getJson(doFetch, publicConfigUrl(cfg.platformUrl, cfg.app.id, env), void 0);
|
|
4765
4744
|
out.log(` public-config: ok`);
|
|
4766
4745
|
if (cfg.services.includes("ai") && cfg.ai?.provider) {
|
|
@@ -4774,7 +4753,7 @@ async function smoke(options) {
|
|
|
4774
4753
|
if (mode !== "hosted") throw new Error(`ai mode mismatch: expected "hosted", public-config has "${String(mode ?? "none")}"`);
|
|
4775
4754
|
out.log(" ai: hosted");
|
|
4776
4755
|
}
|
|
4777
|
-
if (hasO11y) out.log(` o11y: credentials present`);
|
|
4756
|
+
if (hasO11y) out.log(runtime ? ` o11y: Worker-held credential` : ` o11y: credentials present`);
|
|
4778
4757
|
if (cfg.services.includes("calendar")) {
|
|
4779
4758
|
const token = await getDeveloperToken(
|
|
4780
4759
|
cfg,
|
|
@@ -4793,10 +4772,10 @@ async function smoke(options) {
|
|
|
4793
4772
|
out.log(` calendar: ${status.status}, bookable (booking \u2192 ${status.bookingCalendarId ?? "primary"})`);
|
|
4794
4773
|
}
|
|
4795
4774
|
const database = await resolveDatabaseConfig(cfg);
|
|
4796
|
-
if (hasDb) {
|
|
4775
|
+
if (hasDb && !runtime) {
|
|
4797
4776
|
const expectedSchema = database.schema;
|
|
4798
4777
|
const expectedEntities = serializedEntities(expectedSchema);
|
|
4799
|
-
const liveSchemaPayload = await getJson(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(
|
|
4778
|
+
const liveSchemaPayload = await getJson(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(tenantId)}/schema`, entry.dbKey);
|
|
4800
4779
|
const liveSchema = liveSchemaPayload.schema ?? liveSchemaPayload;
|
|
4801
4780
|
const liveEntities = serializedEntities(liveSchema);
|
|
4802
4781
|
if (expectedEntities.length) {
|
|
@@ -4806,7 +4785,7 @@ async function smoke(options) {
|
|
|
4806
4785
|
out.log(` schema: ${liveEntities.length} entities`);
|
|
4807
4786
|
const aggregateEntity = expectedEntities[0] ?? liveEntities[0];
|
|
4808
4787
|
if (aggregateEntity) {
|
|
4809
|
-
const aggregate = await postJson2(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(
|
|
4788
|
+
const aggregate = await postJson2(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(tenantId)}/aggregate`, entry.dbKey, {
|
|
4810
4789
|
ns: aggregateEntity,
|
|
4811
4790
|
aggregate: { count: true }
|
|
4812
4791
|
});
|
|
@@ -4815,9 +4794,20 @@ async function smoke(options) {
|
|
|
4815
4794
|
} else {
|
|
4816
4795
|
out.log(` aggregate: skipped (schema has no entities)`);
|
|
4817
4796
|
}
|
|
4797
|
+
} else if (hasDb) {
|
|
4798
|
+
out.log(` db: Worker-held credential (direct schema/aggregate skipped)`);
|
|
4818
4799
|
} else {
|
|
4819
4800
|
out.log(` db: skipped (not enabled)`);
|
|
4820
4801
|
}
|
|
4802
|
+
if (runtime) {
|
|
4803
|
+
const link = cfg.links?.[env];
|
|
4804
|
+
if (!link) throw new Error(`runtime smoke requires links.${env} in odla.config.mjs`);
|
|
4805
|
+
const res = await doFetch(link, { redirect: "manual" });
|
|
4806
|
+
if (res.status < 200 || res.status >= 500) {
|
|
4807
|
+
throw new Error(`runtime target ${new URL(link).origin} returned ${res.status}`);
|
|
4808
|
+
}
|
|
4809
|
+
out.log(` runtime target: ${res.status}`);
|
|
4810
|
+
}
|
|
4821
4811
|
const probes = database.integrations.flatMap(
|
|
4822
4812
|
(integration) => (integration.probes ?? []).map((probe) => ({ integration: integration.id, probe }))
|
|
4823
4813
|
);
|
|
@@ -5020,10 +5010,11 @@ async function projectCommand(command, parsed, deps) {
|
|
|
5020
5010
|
return true;
|
|
5021
5011
|
}
|
|
5022
5012
|
if (command === "smoke") {
|
|
5023
|
-
assertArgs(parsed, ["config", "env", "token", "email", "open"], 1);
|
|
5013
|
+
assertArgs(parsed, ["config", "env", "runtime", "token", "email", "open"], 1);
|
|
5024
5014
|
await smoke({
|
|
5025
5015
|
configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
|
|
5026
5016
|
env: stringOpt(parsed.options.env),
|
|
5017
|
+
runtime: parsed.options.runtime === true,
|
|
5027
5018
|
token: stringOpt(parsed.options.token),
|
|
5028
5019
|
email: stringOpt(parsed.options.email),
|
|
5029
5020
|
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
@@ -8697,6 +8688,55 @@ function requireName(parsed) {
|
|
|
8697
8688
|
return name;
|
|
8698
8689
|
}
|
|
8699
8690
|
|
|
8691
|
+
// src/credential-command.ts
|
|
8692
|
+
async function responseError(response2) {
|
|
8693
|
+
return redactSecrets((await response2.text()).slice(0, 1e3));
|
|
8694
|
+
}
|
|
8695
|
+
async function credentialCommand(parsed, deps = {}) {
|
|
8696
|
+
const action2 = parsed.positionals[1] ?? "list";
|
|
8697
|
+
if (action2 !== "list" && action2 !== "revoke") {
|
|
8698
|
+
throw new Error(`unknown credentials action "${action2}". Try "odla-ai credentials list".`);
|
|
8699
|
+
}
|
|
8700
|
+
assertArgs(parsed, ["config", "env", "all", "json", "token", "email", "open"], action2 === "revoke" ? 3 : 2);
|
|
8701
|
+
const cfg = await loadProjectConfig(stringOpt(parsed.options.config) ?? "odla.config.mjs");
|
|
8702
|
+
const doFetch = deps.fetch ?? fetch;
|
|
8703
|
+
const out = deps.stdout ?? console;
|
|
8704
|
+
const token = await getDeveloperToken(cfg, {
|
|
8705
|
+
configPath: cfg.configPath,
|
|
8706
|
+
token: stringOpt(parsed.options.token),
|
|
8707
|
+
email: stringOpt(parsed.options.email),
|
|
8708
|
+
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
8709
|
+
openApprovalUrl: deps.openUrl
|
|
8710
|
+
}, doFetch, out, { optionalProjectCapabilities: ["app.manage"] });
|
|
8711
|
+
const base = `${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/runtime-credentials`;
|
|
8712
|
+
if (action2 === "revoke") {
|
|
8713
|
+
const id = parsed.positionals[2];
|
|
8714
|
+
if (!id) throw new Error("credentials revoke requires the exact receipt id from credentials list");
|
|
8715
|
+
const response3 = await doFetch(`${base}/${encodeURIComponent(id)}`, {
|
|
8716
|
+
method: "DELETE",
|
|
8717
|
+
headers: { authorization: `Bearer ${token}` }
|
|
8718
|
+
});
|
|
8719
|
+
if (!response3.ok) throw new Error(`runtime credential revoke failed (${response3.status}): ${await responseError(response3)}`);
|
|
8720
|
+
const body2 = await response3.json();
|
|
8721
|
+
return out.log(parsed.options.json === true ? JSON.stringify(body2, null, 2) : `revoked ${body2.receipt.id} (${body2.receipt.env} ${body2.receipt.target.scriptName})`);
|
|
8722
|
+
}
|
|
8723
|
+
const query = new URLSearchParams();
|
|
8724
|
+
const requestedEnv = stringOpt(parsed.options.env);
|
|
8725
|
+
if (requestedEnv) query.set("env", requestedEnv);
|
|
8726
|
+
const response2 = await doFetch(`${base}${query.size ? `?${query}` : ""}`, {
|
|
8727
|
+
headers: { authorization: `Bearer ${token}` }
|
|
8728
|
+
});
|
|
8729
|
+
if (!response2.ok) throw new Error(`runtime credential inventory failed (${response2.status}): ${await responseError(response2)}`);
|
|
8730
|
+
const body = await response2.json();
|
|
8731
|
+
const credentials = parsed.options.all === true ? body.credentials : body.credentials.filter((item) => item.state === "committed");
|
|
8732
|
+
if (parsed.options.json === true) return out.log(JSON.stringify({ credentials }, null, 2));
|
|
8733
|
+
if (!credentials.length) return out.log("no active runtime credential receipts");
|
|
8734
|
+
out.log("RECEIPT ENV TARGET CREATED");
|
|
8735
|
+
for (const item of credentials) {
|
|
8736
|
+
out.log(`${item.id} ${item.env} ${item.target.accountId ?? "local"}/${item.target.scriptName} ${new Date(item.createdAt).toISOString()}`);
|
|
8737
|
+
}
|
|
8738
|
+
}
|
|
8739
|
+
|
|
8700
8740
|
// src/help-usage.ts
|
|
8701
8741
|
var USAGE_SECTION = `
|
|
8702
8742
|
Start here:
|
|
@@ -8815,7 +8855,9 @@ Usage:
|
|
|
8815
8855
|
odla-ai security run [target] --ack-redacted-source [--env dev] [--profile odla] [--fail-on high]
|
|
8816
8856
|
odla-ai security run [target] --self --ack-redacted-source
|
|
8817
8857
|
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]
|
|
8818
|
-
odla-ai
|
|
8858
|
+
odla-ai credentials list [--config odla.config.mjs] [--env dev] [--all] [--json]
|
|
8859
|
+
odla-ai credentials revoke <receipt-id> [--config odla.config.mjs] [--json]
|
|
8860
|
+
odla-ai smoke [--config odla.config.mjs] [--env dev] [--runtime] [--email <odla-account>] [--no-open]
|
|
8819
8861
|
odla-ai skill install [--dir <project>] [--agent <name>] [--global] [--force]
|
|
8820
8862
|
odla-ai secrets push --env <env> [--config odla.config.mjs] [--dry-run] [--yes]
|
|
8821
8863
|
odla-ai secrets set <name> --env <env> (--from-env <NAME>|--stdin) [--email <odla-account>] [--config odla.config.mjs] [--yes]
|
|
@@ -8917,7 +8959,7 @@ Commands:
|
|
|
8917
8959
|
"provision --live --yes" initializes only the live instance of
|
|
8918
8960
|
an existing sandbox app and enables every configured service;
|
|
8919
8961
|
no edit to the dev-first envs list is required.
|
|
8920
|
-
smoke Verify
|
|
8962
|
+
smoke Verify local service access, or use --runtime for a Worker-held credential deployment.
|
|
8921
8963
|
skill Same installer; --agent accepts all, claude, codex, cursor,
|
|
8922
8964
|
copilot, gemini, or agents (repeatable or comma-separated).
|
|
8923
8965
|
secrets Push configured db/o11y secrets into the Worker via wrangler
|
|
@@ -8937,9 +8979,13 @@ Safety:
|
|
|
8937
8979
|
pinned versions of every external @odla-ai runtime module before importing
|
|
8938
8980
|
the command graph. A stale workspace module blocks with its resolved path and
|
|
8939
8981
|
tells the agent to update/rebase, npm ci, and rebuild.
|
|
8940
|
-
Provision caches the approved developer token
|
|
8941
|
-
|
|
8942
|
-
|
|
8982
|
+
Provision caches the approved developer token under .odla/ with mode 0600.
|
|
8983
|
+
Local-only development provisioning may also cache that developer's service
|
|
8984
|
+
credentials there. \`provision --push-secrets\` instead stages a fresh,
|
|
8985
|
+
independently revocable credential set through the approved handshake,
|
|
8986
|
+
transfers the complete set to the exact Worker with \`wrangler secret bulk\`
|
|
8987
|
+
over stdin, and never writes its plaintext under .odla. Secret push preflights
|
|
8988
|
+
Wrangler before shown-once issuance; it never rotates sibling credentials.
|
|
8943
8989
|
Projectless PM, Discussions, o11y, runbook, and identity commands use
|
|
8944
8990
|
--platform/--app/--env, ODLA_PLATFORM_URL/ODLA_APP_ID/ODLA_ENV, and
|
|
8945
8991
|
ODLA_DEV_TOKEN. Save non-secret scope metadata with "context save", then
|
|
@@ -8953,12 +8999,11 @@ Safety:
|
|
|
8953
8999
|
attempts to open it \u2014 including in CI, SSH, display-less, and agent-driven
|
|
8954
9000
|
shells. Only --no-open suppresses the attempt. Browser launch is best-effort:
|
|
8955
9001
|
agents with browser control must open that exact URL immediately; otherwise
|
|
8956
|
-
they must give it to the human verbatim. A
|
|
8957
|
-
|
|
8958
|
-
the same code. Outside an interactive terminal the wait is capped (90s by
|
|
9002
|
+
they must give it to the human verbatim. A device code remains only in the
|
|
9003
|
+
running process. Outside an interactive terminal the wait is capped (90s by
|
|
8959
9004
|
default, --wait <seconds> to change); a still-pending handshake then exits
|
|
8960
|
-
with code 75
|
|
8961
|
-
|
|
9005
|
+
with code 75. Rerunning always requests and opens a new code; older clients'
|
|
9006
|
+
persisted pending state is discarded.
|
|
8962
9007
|
A fresh device handshake requires --email <odla-account> or ODLA_USER_EMAIL.
|
|
8963
9008
|
The email is a non-secret identity hint: never provide a password or session
|
|
8964
9009
|
token. It is the email shown by the signed-in odla account \u2014 never infer it
|
|
@@ -10863,7 +10908,7 @@ async function issueO11yToken(opts) {
|
|
|
10863
10908
|
);
|
|
10864
10909
|
if (res.status === 409 && !opts.rotateO11y) {
|
|
10865
10910
|
throw new Error(
|
|
10866
|
-
`o11y token already exists for env "${opts.env}", but its shown-once value is not in the local credentials file; run "odla-ai provision --
|
|
10911
|
+
`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`
|
|
10867
10912
|
);
|
|
10868
10913
|
}
|
|
10869
10914
|
if (!res.ok) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) failed: ${res.status} ${await safeText7(res)}`);
|
|
@@ -10879,6 +10924,88 @@ async function safeText7(res) {
|
|
|
10879
10924
|
}
|
|
10880
10925
|
}
|
|
10881
10926
|
|
|
10927
|
+
// src/runtime-credentials.ts
|
|
10928
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
10929
|
+
function runtimeUrl(cfg, suffix = "") {
|
|
10930
|
+
return `${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/runtime-credentials${suffix}`;
|
|
10931
|
+
}
|
|
10932
|
+
async function safeError(response2) {
|
|
10933
|
+
const text2 = await response2.text();
|
|
10934
|
+
return redactSecrets(text2.slice(0, 1e3));
|
|
10935
|
+
}
|
|
10936
|
+
async function finish(doFetch, cfg, token, sessionId, method) {
|
|
10937
|
+
return doFetch(runtimeUrl(cfg, `/${encodeURIComponent(sessionId)}`), {
|
|
10938
|
+
method,
|
|
10939
|
+
headers: { authorization: `Bearer ${token}` }
|
|
10940
|
+
});
|
|
10941
|
+
}
|
|
10942
|
+
async function deliverRuntimeCredentials(cfg, options) {
|
|
10943
|
+
const doFetch = options.fetch ?? fetch;
|
|
10944
|
+
const run = options.runner ?? defaultRunner;
|
|
10945
|
+
const wranglerEnv = options.env === "prod" || options.env === "production" ? void 0 : options.env;
|
|
10946
|
+
const target = await wranglerRuntimeTarget(run, {
|
|
10947
|
+
cwd: cfg.rootDir,
|
|
10948
|
+
env: wranglerEnv
|
|
10949
|
+
});
|
|
10950
|
+
const issue = await doFetch(runtimeUrl(cfg), {
|
|
10951
|
+
method: "POST",
|
|
10952
|
+
headers: {
|
|
10953
|
+
authorization: `Bearer ${options.developerToken}`,
|
|
10954
|
+
"content-type": "application/json"
|
|
10955
|
+
},
|
|
10956
|
+
body: JSON.stringify({
|
|
10957
|
+
env: options.env,
|
|
10958
|
+
idempotencyKey: `wrangler:${randomUUID3()}`,
|
|
10959
|
+
target
|
|
10960
|
+
})
|
|
10961
|
+
});
|
|
10962
|
+
const body = await issue.json().catch(() => null);
|
|
10963
|
+
if (!issue.ok || typeof body?.sessionId !== "string" || !body.secrets || typeof body.secrets !== "object") {
|
|
10964
|
+
const detail = body?.error?.code ?? body?.error?.message ?? issue.status;
|
|
10965
|
+
throw new Error(`runtime credential staging failed: ${String(detail)}`);
|
|
10966
|
+
}
|
|
10967
|
+
const secrets = body.secrets;
|
|
10968
|
+
const values = {};
|
|
10969
|
+
for (const name of ["ODLA_API_KEY", "ODLA_O11Y_TOKEN"]) {
|
|
10970
|
+
const value2 = secrets[name];
|
|
10971
|
+
if (typeof value2 === "string" && value2) values[name] = value2;
|
|
10972
|
+
}
|
|
10973
|
+
if (Object.keys(values).length === 0) {
|
|
10974
|
+
await finish(doFetch, cfg, options.developerToken, body.sessionId, "DELETE");
|
|
10975
|
+
throw new Error("runtime credential staging returned no configured service credentials");
|
|
10976
|
+
}
|
|
10977
|
+
const pushed = await wranglerBulkSecrets(run, {
|
|
10978
|
+
secrets: values,
|
|
10979
|
+
env: wranglerEnv,
|
|
10980
|
+
cwd: cfg.rootDir
|
|
10981
|
+
});
|
|
10982
|
+
if (pushed.code !== 0) {
|
|
10983
|
+
await finish(doFetch, cfg, options.developerToken, body.sessionId, "DELETE");
|
|
10984
|
+
const detail = redactSecrets(`${pushed.stderr || pushed.stdout}`.trim());
|
|
10985
|
+
throw new Error(`wrangler secret bulk failed (exit ${pushed.code}): ${detail}`);
|
|
10986
|
+
}
|
|
10987
|
+
const committed = await finish(
|
|
10988
|
+
doFetch,
|
|
10989
|
+
cfg,
|
|
10990
|
+
options.developerToken,
|
|
10991
|
+
body.sessionId,
|
|
10992
|
+
"POST"
|
|
10993
|
+
);
|
|
10994
|
+
if (!committed.ok) {
|
|
10995
|
+
await finish(doFetch, cfg, options.developerToken, body.sessionId, "DELETE");
|
|
10996
|
+
throw new Error(`runtime credential receipt commit failed: ${await safeError(committed)}`);
|
|
10997
|
+
}
|
|
10998
|
+
options.stdout?.log(
|
|
10999
|
+
`${options.env}: installed ${Object.keys(values).join(" + ")} on ${target.accountId}/${target.scriptName}${target.environment ? ` (${target.environment})` : ""}; plaintext stayed in memory`
|
|
11000
|
+
);
|
|
11001
|
+
return {
|
|
11002
|
+
sessionId: body.sessionId,
|
|
11003
|
+
target,
|
|
11004
|
+
...values.ODLA_API_KEY ? { dbKey: values.ODLA_API_KEY } : {},
|
|
11005
|
+
...values.ODLA_O11Y_TOKEN ? { o11yToken: values.ODLA_O11Y_TOKEN } : {}
|
|
11006
|
+
};
|
|
11007
|
+
}
|
|
11008
|
+
|
|
10882
11009
|
// src/provision-live.ts
|
|
10883
11010
|
function liveProvisionConfig(cfg) {
|
|
10884
11011
|
if (!cfg.envs.includes("dev")) {
|
|
@@ -10912,8 +11039,10 @@ async function provision(options) {
|
|
|
10912
11039
|
if (options.rotateO11yToken && !hasO11y) {
|
|
10913
11040
|
throw new Error("--rotate-o11y-token requires the o11y service in odla.config.mjs");
|
|
10914
11041
|
}
|
|
10915
|
-
if (options.pushSecrets && options.
|
|
10916
|
-
throw new Error(
|
|
11042
|
+
if (options.pushSecrets && (options.rotateKeys || options.rotateO11yToken)) {
|
|
11043
|
+
throw new Error(
|
|
11044
|
+
"--push-secrets always issues an independent runtime credential set; do not combine it with destructive rotation flags"
|
|
11045
|
+
);
|
|
10917
11046
|
}
|
|
10918
11047
|
out.log(`odla-ai: ${plan.appName} (${plan.appId})`);
|
|
10919
11048
|
out.log(` platform: ${plan.platformUrl}`);
|
|
@@ -10950,7 +11079,7 @@ async function provision(options) {
|
|
|
10950
11079
|
out.log(` secrets: ${options.pushSecrets ? "push configured Worker secrets" : "local only"}`);
|
|
10951
11080
|
return;
|
|
10952
11081
|
}
|
|
10953
|
-
let credentials = readCredentials(cfg.local.credentialsFile);
|
|
11082
|
+
let credentials = options.pushSecrets ? null : readCredentials(cfg.local.credentialsFile);
|
|
10954
11083
|
if (credentials && credentials.appId !== cfg.app.id) {
|
|
10955
11084
|
throw new Error(
|
|
10956
11085
|
`credentials at ${displayPath(cfg.local.credentialsFile, cfg.rootDir)} are for "${credentials.appId}", not "${cfg.app.id}"`
|
|
@@ -10959,7 +11088,7 @@ async function provision(options) {
|
|
|
10959
11088
|
const rotatesO11y = !!(options.rotateKeys || options.rotateO11yToken);
|
|
10960
11089
|
const missingO11y = cfg.envs.some((env) => !credentials?.envs[env]?.o11yToken);
|
|
10961
11090
|
const missingDb = cfg.envs.some((env) => !credentials?.envs[env]?.dbKey);
|
|
10962
|
-
const losesShownOnceCredential = hasDb && (!!options.rotateKeys || missingDb) || hasO11y && (rotatesO11y || missingO11y);
|
|
11091
|
+
const losesShownOnceCredential = !options.pushSecrets && (hasDb && (!!options.rotateKeys || missingDb) || hasO11y && (rotatesO11y || missingO11y));
|
|
10963
11092
|
if (options.writeCredentials === false && losesShownOnceCredential) {
|
|
10964
11093
|
throw new Error("credential issuance/rotation requires the private credentials file; remove --no-write-credentials");
|
|
10965
11094
|
}
|
|
@@ -11033,38 +11162,42 @@ async function provision(options) {
|
|
|
11033
11162
|
await applyCalendarBookingPage(calendarCtx, calendarBookingPageUrl(cfg, env), out);
|
|
11034
11163
|
}
|
|
11035
11164
|
}
|
|
11165
|
+
let devVarsCredentials = credentials;
|
|
11036
11166
|
for (const env of cfg.envs) {
|
|
11037
11167
|
const tenantId = tenantIdFor5(cfg.app.id, env);
|
|
11038
|
-
|
|
11039
|
-
cfg,
|
|
11040
|
-
env,
|
|
11041
|
-
developerToken: token,
|
|
11042
|
-
credentials,
|
|
11043
|
-
rotateDb: !!options.rotateKeys,
|
|
11044
|
-
rotateO11y: rotatesO11y,
|
|
11045
|
-
write: options.writeCredentials !== false,
|
|
11046
|
-
fetch: doFetch,
|
|
11047
|
-
stdout: out
|
|
11048
|
-
});
|
|
11049
|
-
const dbKey = credentials.envs[env]?.dbKey;
|
|
11168
|
+
let dbKey;
|
|
11050
11169
|
if (options.pushSecrets) {
|
|
11051
|
-
|
|
11052
|
-
|
|
11053
|
-
|
|
11054
|
-
|
|
11055
|
-
|
|
11056
|
-
|
|
11057
|
-
|
|
11058
|
-
|
|
11059
|
-
|
|
11060
|
-
|
|
11061
|
-
|
|
11062
|
-
|
|
11063
|
-
|
|
11064
|
-
|
|
11065
|
-
|
|
11066
|
-
|
|
11067
|
-
}
|
|
11170
|
+
const delivered = await deliverRuntimeCredentials(cfg, {
|
|
11171
|
+
env,
|
|
11172
|
+
developerToken: token,
|
|
11173
|
+
fetch: doFetch,
|
|
11174
|
+
runner: options.secretRunner,
|
|
11175
|
+
stdout: out
|
|
11176
|
+
});
|
|
11177
|
+
dbKey = delivered.dbKey;
|
|
11178
|
+
devVarsCredentials = mergeCredential(devVarsCredentials, {
|
|
11179
|
+
appId: cfg.app.id,
|
|
11180
|
+
platformUrl: cfg.platformUrl,
|
|
11181
|
+
dbEndpoint: cfg.dbEndpoint,
|
|
11182
|
+
env,
|
|
11183
|
+
tenantId,
|
|
11184
|
+
...delivered.dbKey ? { dbKey: delivered.dbKey } : {},
|
|
11185
|
+
...delivered.o11yToken ? { o11yToken: delivered.o11yToken } : {}
|
|
11186
|
+
});
|
|
11187
|
+
} else {
|
|
11188
|
+
credentials = await provisionEnvCredentials({
|
|
11189
|
+
cfg,
|
|
11190
|
+
env,
|
|
11191
|
+
developerToken: token,
|
|
11192
|
+
credentials,
|
|
11193
|
+
rotateDb: !!options.rotateKeys,
|
|
11194
|
+
rotateO11y: rotatesO11y,
|
|
11195
|
+
write: options.writeCredentials !== false,
|
|
11196
|
+
fetch: doFetch,
|
|
11197
|
+
stdout: out
|
|
11198
|
+
});
|
|
11199
|
+
devVarsCredentials = credentials;
|
|
11200
|
+
dbKey = credentials.envs[env]?.dbKey;
|
|
11068
11201
|
}
|
|
11069
11202
|
if (schema && dbKey) {
|
|
11070
11203
|
await postJson(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(tenantId)}/schema`, dbKey, { schema });
|
|
@@ -11088,13 +11221,13 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
|
|
|
11088
11221
|
}
|
|
11089
11222
|
}
|
|
11090
11223
|
}
|
|
11091
|
-
if (options.writeCredentials !== false && credentials) {
|
|
11224
|
+
if (!options.pushSecrets && options.writeCredentials !== false && credentials) {
|
|
11092
11225
|
const ignored = cfg.local.gitignore && gitignoreEntry(cfg.rootDir, cfg.local.credentialsFile);
|
|
11093
11226
|
out.log(`credentials: wrote ${displayPath(cfg.local.credentialsFile, cfg.rootDir)} (0600${ignored ? ", gitignored" : ""})`);
|
|
11094
11227
|
}
|
|
11095
|
-
if (devVarsTarget &&
|
|
11228
|
+
if (devVarsTarget && devVarsCredentials) {
|
|
11096
11229
|
const env = cfg.envs.includes("dev") ? "dev" : cfg.envs[0] ?? "prod";
|
|
11097
|
-
writeDevVars(devVarsTarget,
|
|
11230
|
+
writeDevVars(devVarsTarget, devVarsCredentials, env, o11yDevVars(cfg));
|
|
11098
11231
|
out.log(`dev vars: wrote ${displayPath(devVarsTarget, cfg.rootDir)} for ${env}`);
|
|
11099
11232
|
}
|
|
11100
11233
|
if (cfg.services.includes("calendar")) {
|
|
@@ -11185,6 +11318,7 @@ var COMMAND_SURFACE = {
|
|
|
11185
11318
|
code: { connect: {} },
|
|
11186
11319
|
config: { diff: {}, plan: {}, apply: {} },
|
|
11187
11320
|
context: { show: {}, list: {}, save: {}, remove: {} },
|
|
11321
|
+
credentials: { list: {}, revoke: {} },
|
|
11188
11322
|
// `watch`, `read`, `reply`, and `resolve` take a topic id from there on.
|
|
11189
11323
|
discuss: {
|
|
11190
11324
|
groups: {},
|
|
@@ -12869,6 +13003,10 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
|
12869
13003
|
await contextCommand(parsed, runtime);
|
|
12870
13004
|
return;
|
|
12871
13005
|
}
|
|
13006
|
+
if (command === "credentials") {
|
|
13007
|
+
await credentialCommand(parsed, runtime);
|
|
13008
|
+
return;
|
|
13009
|
+
}
|
|
12872
13010
|
if (command === "runbook") {
|
|
12873
13011
|
await runbookCommand(parsed, runtime);
|
|
12874
13012
|
return;
|
|
@@ -13056,4 +13194,4 @@ export {
|
|
|
13056
13194
|
isTerminalHostedSecurityStatus,
|
|
13057
13195
|
runCli
|
|
13058
13196
|
};
|
|
13059
|
-
//# sourceMappingURL=chunk-
|
|
13197
|
+
//# sourceMappingURL=chunk-7V7WVTDT.js.map
|