@odla-ai/cli 0.29.0 → 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 +42 -23
- package/dist/bin.cjs +254 -43
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-YTVLTADT.js → chunk-PPSXHZJI.js} +234 -44
- package/dist/chunk-PPSXHZJI.js.map +1 -0
- package/dist/{cli-DNOAYDBR.js → cli-CKBUVTJM.js} +2 -2
- package/dist/index.cjs +233 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1 -1
- package/dist/runtime/pi-agent.js +18728 -15
- package/package.json +1 -1
- package/skills/odla/SKILL.md +4 -1
- 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-CKBUVTJM.js.map} +0 -0
package/README.md
CHANGED
|
@@ -482,8 +482,8 @@ commands continue to default to the sandbox.
|
|
|
482
482
|
`provision` performs the standard safe setup flow:
|
|
483
483
|
|
|
484
484
|
When using `--push-secrets`, create the Worker and its Wrangler config first.
|
|
485
|
-
The CLI checks that config and `wrangler whoami` before it issues
|
|
486
|
-
|
|
485
|
+
The CLI checks that config and `wrangler whoami` before it issues a shown-once
|
|
486
|
+
credential set.
|
|
487
487
|
|
|
488
488
|
1. Gets an `odla_dev_...` token by email-bound device handshake, or reuses
|
|
489
489
|
`ODLA_DEV_TOKEN` / `.odla/dev-token.json`. A fresh handshake requires
|
|
@@ -521,8 +521,9 @@ shown-once credential.
|
|
|
521
521
|
reservation approved in step 1; it is not ambient project-creation authority.
|
|
522
522
|
3. Enables configured services in every configured environment. Calendar
|
|
523
523
|
config is normalized per env and fixed to Google read-only access.
|
|
524
|
-
4.
|
|
525
|
-
|
|
524
|
+
4. For local development, mints or reuses that developer's configured service
|
|
525
|
+
credentials. Multiple developers may hold independent DB/o11y credentials
|
|
526
|
+
for the same shared sandbox tenant.
|
|
526
527
|
5. Collision-checks and merges declared integration schema/rule fragments,
|
|
527
528
|
pushes the composed database contract, and creates integration seeds only
|
|
528
529
|
when their natural-key rows are absent.
|
|
@@ -533,23 +534,40 @@ shown-once credential.
|
|
|
533
534
|
exact Google OAuth endpoint (or a same-platform interstitial). The human completes consent there;
|
|
534
535
|
provision follows the exact attempt through initial sync. OAuth codes and
|
|
535
536
|
refresh tokens never enter the CLI, repo, chat, or app.
|
|
536
|
-
8.
|
|
537
|
+
8. Local-only provisioning writes `.odla/credentials.local.json` with mode
|
|
538
|
+
`0600`. A deployment run with `--push-secrets` does not write runtime
|
|
539
|
+
credential plaintext there.
|
|
537
540
|
9. With `--write-dev-vars`, writes the local Worker values to `.dev.vars`; with
|
|
538
|
-
`--push-secrets`,
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
541
|
+
`--push-secrets`, binds a fresh additive credential set to the exact
|
|
542
|
+
Cloudflare account/script/environment, transfers both values in one
|
|
543
|
+
`wrangler secret bulk` stdin payload, and commits a receipt. A failed push
|
|
544
|
+
aborts the session and revokes partial issuance.
|
|
545
|
+
|
|
546
|
+
Plain reruns are idempotent at the tenant/configuration layer. Each deployment
|
|
547
|
+
receives new, independently revocable runtime credentials; it never rotates a
|
|
548
|
+
sibling developer or Worker. `--rotate-o11y-token` remains a destructive legacy
|
|
549
|
+
recovery operation for locally cached credentials, and `--rotate-keys` remains
|
|
550
|
+
the broader local rotation. Neither rotation flag may be combined with
|
|
551
|
+
`--push-secrets`. Production mutation or secret transfer requires
|
|
552
|
+
`--yes`. `--no-write-credentials` is valid with `--push-secrets` because the
|
|
553
|
+
deployment path is memory-only by design.
|
|
554
|
+
|
|
555
|
+
Registry staging and Wrangler installation use a short-lived receipt protocol.
|
|
556
|
+
The CLI preflights the exact target, issues additive credentials, sends one
|
|
557
|
+
bulk payload, and commits only after Wrangler succeeds. Failure revokes staged
|
|
558
|
+
credentials; retrying creates a new bounded session and never invalidates a
|
|
559
|
+
working sibling runtime.
|
|
560
|
+
|
|
561
|
+
Inventory and retire one successful deployment without disturbing any sibling:
|
|
562
|
+
|
|
563
|
+
```sh
|
|
564
|
+
npx @odla-ai/cli credentials list --env dev
|
|
565
|
+
npx @odla-ai/cli credentials revoke <receipt-id>
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
The inventory contains only receipt, target, state, and timestamp metadata.
|
|
569
|
+
Revocation uses the receipt's exact DB and o11y credential ids; no value is
|
|
570
|
+
retrieved or rotated.
|
|
553
571
|
|
|
554
572
|
### Application capability integrations
|
|
555
573
|
|
|
@@ -797,9 +815,10 @@ in an agent's transcript. It preflights `wrangler whoami` and the presence of a
|
|
|
797
815
|
wrangler config file. The env `prod`/`production` targets the top-level
|
|
798
816
|
wrangler environment (no `--env` flag is passed to Wrangler) and requires
|
|
799
817
|
`--yes`; every other env maps to `wrangler --env <name>`. `--dry-run` prints a
|
|
800
|
-
redacted plan without spawning anything.
|
|
801
|
-
|
|
802
|
-
|
|
818
|
+
redacted plan without spawning anything. It is only for an already-saved local
|
|
819
|
+
credential. For a new Worker or a lost local plaintext, use
|
|
820
|
+
`provision --push-secrets`: the handshake authorizes new target-bound
|
|
821
|
+
credentials and no local production cache is required.
|
|
803
822
|
|
|
804
823
|
`secrets set <name> --env <env>` stores one named secret in that env's tenant
|
|
805
824
|
vault — the same write-only slot Studio's Secrets panel fills (for example
|
package/dist/bin.cjs
CHANGED
|
@@ -4376,6 +4376,41 @@ async function wranglerLoggedIn(run, cwd) {
|
|
|
4376
4376
|
return false;
|
|
4377
4377
|
}
|
|
4378
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
|
+
}
|
|
4379
4414
|
function wranglerPutSecret(run, opts) {
|
|
4380
4415
|
const args = [
|
|
4381
4416
|
"wrangler",
|
|
@@ -4387,6 +4422,16 @@ function wranglerPutSecret(run, opts) {
|
|
|
4387
4422
|
];
|
|
4388
4423
|
return run("npx", args, { input: opts.value, cwd: opts.cwd });
|
|
4389
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
|
+
}
|
|
4390
4435
|
var import_node_child_process2, import_node_fs12, import_node_path11, defaultRunner, WRANGLER_CONFIG_FILES;
|
|
4391
4436
|
var init_wrangler = __esm({
|
|
4392
4437
|
"src/wrangler.ts"() {
|
|
@@ -4947,9 +4992,6 @@ var init_init = __esm({
|
|
|
4947
4992
|
async function secretsPush(options) {
|
|
4948
4993
|
await secretsPushImpl(options, true);
|
|
4949
4994
|
}
|
|
4950
|
-
async function secretsPushAfterPreflight(options) {
|
|
4951
|
-
await secretsPushImpl(options, false);
|
|
4952
|
-
}
|
|
4953
4995
|
async function secretsPushImpl(options, preflight) {
|
|
4954
4996
|
const out = options.stdout ?? console;
|
|
4955
4997
|
const cfg = await loadProjectConfig(options.configPath);
|
|
@@ -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({
|
|
@@ -9624,6 +9725,8 @@ Usage:
|
|
|
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
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]
|
|
@@ -9748,9 +9851,13 @@ Safety:
|
|
|
9748
9851
|
pinned versions of every external @odla-ai runtime module before importing
|
|
9749
9852
|
the command graph. A stale workspace module blocks with its resolved path and
|
|
9750
9853
|
tells the agent to update/rebase, npm ci, and rebuild.
|
|
9751
|
-
Provision caches the approved developer token
|
|
9752
|
-
|
|
9753
|
-
|
|
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.
|
|
9754
9861
|
Projectless PM, Discussions, o11y, runbook, and identity commands use
|
|
9755
9862
|
--platform/--app/--env, ODLA_PLATFORM_URL/ODLA_APP_ID/ODLA_ENV, and
|
|
9756
9863
|
ODLA_DEV_TOKEN. Save non-secret scope metadata with "context save", then
|
|
@@ -11829,7 +11936,7 @@ async function issueO11yToken(opts) {
|
|
|
11829
11936
|
);
|
|
11830
11937
|
if (res.status === 409 && !opts.rotateO11y) {
|
|
11831
11938
|
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 --
|
|
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`
|
|
11833
11940
|
);
|
|
11834
11941
|
}
|
|
11835
11942
|
if (!res.ok) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) failed: ${res.status} ${await safeText7(res)}`);
|
|
@@ -11855,6 +11962,97 @@ var init_provision_credentials = __esm({
|
|
|
11855
11962
|
}
|
|
11856
11963
|
});
|
|
11857
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
|
+
|
|
11858
12056
|
// src/provision-live.ts
|
|
11859
12057
|
function liveProvisionConfig(cfg) {
|
|
11860
12058
|
if (!cfg.envs.includes("dev")) {
|
|
@@ -11894,8 +12092,10 @@ async function provision(options) {
|
|
|
11894
12092
|
if (options.rotateO11yToken && !hasO11y) {
|
|
11895
12093
|
throw new Error("--rotate-o11y-token requires the o11y service in odla.config.mjs");
|
|
11896
12094
|
}
|
|
11897
|
-
if (options.pushSecrets && options.
|
|
11898
|
-
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
|
+
);
|
|
11899
12099
|
}
|
|
11900
12100
|
out.log(`odla-ai: ${plan.appName} (${plan.appId})`);
|
|
11901
12101
|
out.log(` platform: ${plan.platformUrl}`);
|
|
@@ -11932,7 +12132,7 @@ async function provision(options) {
|
|
|
11932
12132
|
out.log(` secrets: ${options.pushSecrets ? "push configured Worker secrets" : "local only"}`);
|
|
11933
12133
|
return;
|
|
11934
12134
|
}
|
|
11935
|
-
let credentials = readCredentials(cfg.local.credentialsFile);
|
|
12135
|
+
let credentials = options.pushSecrets ? null : readCredentials(cfg.local.credentialsFile);
|
|
11936
12136
|
if (credentials && credentials.appId !== cfg.app.id) {
|
|
11937
12137
|
throw new Error(
|
|
11938
12138
|
`credentials at ${displayPath(cfg.local.credentialsFile, cfg.rootDir)} are for "${credentials.appId}", not "${cfg.app.id}"`
|
|
@@ -11941,7 +12141,7 @@ async function provision(options) {
|
|
|
11941
12141
|
const rotatesO11y = !!(options.rotateKeys || options.rotateO11yToken);
|
|
11942
12142
|
const missingO11y = cfg.envs.some((env) => !credentials?.envs[env]?.o11yToken);
|
|
11943
12143
|
const missingDb = cfg.envs.some((env) => !credentials?.envs[env]?.dbKey);
|
|
11944
|
-
const losesShownOnceCredential = hasDb && (!!options.rotateKeys || missingDb) || hasO11y && (rotatesO11y || missingO11y);
|
|
12144
|
+
const losesShownOnceCredential = !options.pushSecrets && (hasDb && (!!options.rotateKeys || missingDb) || hasO11y && (rotatesO11y || missingO11y));
|
|
11945
12145
|
if (options.writeCredentials === false && losesShownOnceCredential) {
|
|
11946
12146
|
throw new Error("credential issuance/rotation requires the private credentials file; remove --no-write-credentials");
|
|
11947
12147
|
}
|
|
@@ -12015,38 +12215,42 @@ async function provision(options) {
|
|
|
12015
12215
|
await applyCalendarBookingPage(calendarCtx, calendarBookingPageUrl(cfg, env), out);
|
|
12016
12216
|
}
|
|
12017
12217
|
}
|
|
12218
|
+
let devVarsCredentials = credentials;
|
|
12018
12219
|
for (const env of cfg.envs) {
|
|
12019
12220
|
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;
|
|
12221
|
+
let dbKey;
|
|
12032
12222
|
if (options.pushSecrets) {
|
|
12033
|
-
|
|
12034
|
-
|
|
12035
|
-
|
|
12036
|
-
|
|
12037
|
-
|
|
12038
|
-
|
|
12039
|
-
|
|
12040
|
-
|
|
12041
|
-
|
|
12042
|
-
|
|
12043
|
-
|
|
12044
|
-
|
|
12045
|
-
|
|
12046
|
-
|
|
12047
|
-
|
|
12048
|
-
|
|
12049
|
-
}
|
|
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;
|
|
12050
12254
|
}
|
|
12051
12255
|
if (schema && dbKey) {
|
|
12052
12256
|
await postJson(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(tenantId)}/schema`, dbKey, { schema });
|
|
@@ -12070,13 +12274,13 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
|
|
|
12070
12274
|
}
|
|
12071
12275
|
}
|
|
12072
12276
|
}
|
|
12073
|
-
if (options.writeCredentials !== false && credentials) {
|
|
12277
|
+
if (!options.pushSecrets && options.writeCredentials !== false && credentials) {
|
|
12074
12278
|
const ignored = cfg.local.gitignore && gitignoreEntry(cfg.rootDir, cfg.local.credentialsFile);
|
|
12075
12279
|
out.log(`credentials: wrote ${displayPath(cfg.local.credentialsFile, cfg.rootDir)} (0600${ignored ? ", gitignored" : ""})`);
|
|
12076
12280
|
}
|
|
12077
|
-
if (devVarsTarget &&
|
|
12281
|
+
if (devVarsTarget && devVarsCredentials) {
|
|
12078
12282
|
const env = cfg.envs.includes("dev") ? "dev" : cfg.envs[0] ?? "prod";
|
|
12079
|
-
writeDevVars(devVarsTarget,
|
|
12283
|
+
writeDevVars(devVarsTarget, devVarsCredentials, env, o11yDevVars(cfg));
|
|
12080
12284
|
out.log(`dev vars: wrote ${displayPath(devVarsTarget, cfg.rootDir)} for ${env}`);
|
|
12081
12285
|
}
|
|
12082
12286
|
if (cfg.services.includes("calendar")) {
|
|
@@ -12116,6 +12320,7 @@ var init_provision = __esm({
|
|
|
12116
12320
|
init_token();
|
|
12117
12321
|
init_local();
|
|
12118
12322
|
init_provision_credentials();
|
|
12323
|
+
init_runtime_credentials();
|
|
12119
12324
|
init_provision_live();
|
|
12120
12325
|
init_provision_helpers();
|
|
12121
12326
|
init_secrets();
|
|
@@ -12226,6 +12431,7 @@ var init_surface = __esm({
|
|
|
12226
12431
|
code: { connect: {} },
|
|
12227
12432
|
config: { diff: {}, plan: {}, apply: {} },
|
|
12228
12433
|
context: { show: {}, list: {}, save: {}, remove: {} },
|
|
12434
|
+
credentials: { list: {}, revoke: {} },
|
|
12229
12435
|
// `watch`, `read`, `reply`, and `resolve` take a topic id from there on.
|
|
12230
12436
|
discuss: {
|
|
12231
12437
|
groups: {},
|
|
@@ -14022,6 +14228,10 @@ async function runCli(argv2 = process.argv.slice(2), dependencies = {}) {
|
|
|
14022
14228
|
await contextCommand(parsed, runtime);
|
|
14023
14229
|
return;
|
|
14024
14230
|
}
|
|
14231
|
+
if (command === "credentials") {
|
|
14232
|
+
await credentialCommand(parsed, runtime);
|
|
14233
|
+
return;
|
|
14234
|
+
}
|
|
14025
14235
|
if (command === "runbook") {
|
|
14026
14236
|
await runbookCommand(parsed, runtime);
|
|
14027
14237
|
return;
|
|
@@ -14164,6 +14374,7 @@ var init_cli = __esm({
|
|
|
14164
14374
|
init_cli_project();
|
|
14165
14375
|
init_code_command();
|
|
14166
14376
|
init_context_command();
|
|
14377
|
+
init_credential_command();
|
|
14167
14378
|
init_help();
|
|
14168
14379
|
init_version();
|
|
14169
14380
|
init_discuss_command();
|