@odla-ai/cli 0.10.0 → 0.10.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 +33 -6
- package/REQUIREMENTS.md +17 -1
- package/dist/bin.cjs +61 -31
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-WDKBW4BE.js → chunk-MWVKOIGR.js} +62 -32
- package/dist/chunk-MWVKOIGR.js.map +1 -0
- package/dist/index.cjs +61 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +30 -8
- package/dist/index.d.ts +30 -8
- package/dist/index.js +1 -1
- package/llms.txt +64 -15
- package/package.json +2 -2
- package/skills/odla/SKILL.md +7 -2
- package/skills/odla/references/build.md +5 -3
- package/skills/odla-migrate/SKILL.md +3 -0
- package/skills/odla-migrate/references/phase-2-db.md +8 -6
- package/skills/odla-migrate/references/secrets-map.md +2 -1
- package/skills/odla-migrate/references/troubleshooting.md +3 -1
- package/skills/odla-o11y-debug/SKILL.md +7 -3
- package/dist/chunk-WDKBW4BE.js.map +0 -1
package/README.md
CHANGED
|
@@ -29,13 +29,17 @@ For a project you keep working in, install it as a dev dependency so the
|
|
|
29
29
|
shorter `npx odla-ai` form works and the version is pinned by your lockfile:
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
|
-
npm view @odla-ai/cli@0.10.
|
|
33
|
-
npm i -D --save-exact @odla-ai/cli@0.10.
|
|
32
|
+
npm view @odla-ai/cli@0.10.1 version
|
|
33
|
+
npm i -D --save-exact @odla-ai/cli@0.10.1
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
## Prerequisites
|
|
37
37
|
|
|
38
38
|
- Node.js 20 or newer (`node --version`).
|
|
39
|
+
- An existing odla account that has signed in at least once. For any command
|
|
40
|
+
that may need a fresh device grant, pass `--email <account>` or set
|
|
41
|
+
`ODLA_USER_EMAIL`. This value identifies who may review the request; it is not
|
|
42
|
+
a password or session credential, and an agent must never ask for either.
|
|
39
43
|
- odla apps usually deploy as Cloudflare Workers. If you have never used
|
|
40
44
|
Cloudflare:
|
|
41
45
|
1. Create a free account at <https://dash.cloudflare.com/sign-up>.
|
|
@@ -61,7 +65,7 @@ npx odla-ai calendar disconnect --env dev --yes
|
|
|
61
65
|
npx odla-ai capabilities --json
|
|
62
66
|
# install SDKs, write the Worker, and create wrangler.jsonc before secret push
|
|
63
67
|
npx odla-ai provision --dry-run
|
|
64
|
-
npx odla-ai provision --write-dev-vars --push-secrets
|
|
68
|
+
npx odla-ai provision --email owner@example.com --write-dev-vars --push-secrets
|
|
65
69
|
npx odla-ai smoke --env dev
|
|
66
70
|
npx odla-ai secrets push --env dev
|
|
67
71
|
npx odla-ai secrets set clerk_webhook_secret --env dev --stdin
|
|
@@ -73,7 +77,7 @@ npx odla-ai security sources --env dev
|
|
|
73
77
|
npx odla-ai security run --source <source-id> --ref main --env dev --plan-digest <digest-from-security-plan> --ack-redacted-source
|
|
74
78
|
npx odla-ai security status <job-id>
|
|
75
79
|
npx odla-ai security report <job-id>
|
|
76
|
-
npx odla-ai admin ai show
|
|
80
|
+
npx odla-ai admin ai show --email owner@example.com
|
|
77
81
|
npx odla-ai admin ai models
|
|
78
82
|
npx odla-ai admin ai set security --discovery-provider anthropic --discovery-model claude-opus-4-8 --validation-provider openai --validation-model gpt-5.5 --enabled
|
|
79
83
|
npx odla-ai admin ai credentials
|
|
@@ -101,8 +105,11 @@ When using `--push-secrets`, create the Worker and its Wrangler config first.
|
|
|
101
105
|
The CLI checks that config and `wrangler whoami` before it issues or rotates a
|
|
102
106
|
shown-once credential.
|
|
103
107
|
|
|
104
|
-
1. Gets an `odla_dev_...` token by device handshake, or reuses
|
|
105
|
-
`ODLA_DEV_TOKEN` / `.odla/dev-token.json`.
|
|
108
|
+
1. Gets an `odla_dev_...` token by email-bound device handshake, or reuses
|
|
109
|
+
`ODLA_DEV_TOKEN` / `.odla/dev-token.json`. A fresh handshake requires
|
|
110
|
+
`--email <account>` or `ODLA_USER_EMAIL`; the matching existing account must
|
|
111
|
+
sign in, review the exact code, and approve it. Opening the URL alone does
|
|
112
|
+
not claim the request.
|
|
106
113
|
In an interactive terminal it opens the approval page in your browser when
|
|
107
114
|
the code is displayed. Pass `--open` to force browser launch from a
|
|
108
115
|
non-interactive shell, or `--no-open` to suppress it. Browser launch is
|
|
@@ -138,6 +145,26 @@ The CLI preflights Wrangler first and persists the replacement before pushing;
|
|
|
138
145
|
if that final push still fails, follow the printed `secrets push --env ...`
|
|
139
146
|
retry. Do not run the rotation flag again.
|
|
140
147
|
|
|
148
|
+
### Device-grant security and recovery
|
|
149
|
+
|
|
150
|
+
The account email sent during a fresh handshake is a non-secret identity hint,
|
|
151
|
+
not proof of identity. Unknown or never-signed-in accounts never produce a
|
|
152
|
+
claimable Studio request, and the public start/poll shape intentionally does not
|
|
153
|
+
reveal whether an account exists. The signed-in matching user must explicitly
|
|
154
|
+
review the exact code before it becomes pending, and only one claimed pending
|
|
155
|
+
or approved-but-uncollected request can be active for that user.
|
|
156
|
+
|
|
157
|
+
Developer grants are tracked by id, owner, label, scopes, creation/expiry,
|
|
158
|
+
last use, and revocation state. The plaintext token is delivered once to the
|
|
159
|
+
polling client and is never available from the inventory. Every signed-in user
|
|
160
|
+
can inspect and revoke their own grants under **Your agent credentials** in
|
|
161
|
+
Studio; platform admins additionally see the global token inventory and
|
|
162
|
+
metadata-only attempt evidence. Future requests with a revoked token fail.
|
|
163
|
+
Deleting
|
|
164
|
+
`.odla/dev-token.json` or `.odla/admin-token.local.json` removes the local copy
|
|
165
|
+
but does not revoke a token already copied elsewhere, so use Studio revocation
|
|
166
|
+
when compromise or accidental approval is possible.
|
|
167
|
+
|
|
141
168
|
`smoke` verifies a provisioned environment from local credentials. It fetches
|
|
142
169
|
the platform public config, checks the configured AI provider, fetches the live
|
|
143
170
|
odla-db schema with the tenant key, compares expected schema entities, and runs
|
package/REQUIREMENTS.md
CHANGED
|
@@ -25,11 +25,27 @@ Agnacl, but none should mention or special-case Agnacl.
|
|
|
25
25
|
|
|
26
26
|
- Provisioning must default to device-handshake auth, never a platform admin
|
|
27
27
|
secret in chat or config.
|
|
28
|
-
-
|
|
28
|
+
- A fresh handshake must require the existing account email via `--email` or
|
|
29
|
+
`ODLA_USER_EMAIL`. Email is an identity hint, never a password or bearer
|
|
30
|
+
credential; agents must not request either from the human.
|
|
31
|
+
- Unknown and never-signed-in accounts must not yield a claimable request, but
|
|
32
|
+
the public response must not disclose account existence.
|
|
33
|
+
- The approval code must be printed in a copyable, server-supplied verification
|
|
34
|
+
URL. Opening it must not claim the request; the matching signed-in user must
|
|
35
|
+
explicitly review that exact code before approval controls appear.
|
|
36
|
+
- Only one claimed pending or approved-but-uncollected request may be active per
|
|
37
|
+
user. Unclaimed issued codes must not occupy that slot.
|
|
29
38
|
- Interactive provisioning should launch the browser to the approval page when
|
|
30
39
|
possible. `--open` should force launch; `--no-open` should suppress it.
|
|
31
40
|
- Cached developer tokens must be mode `0600`, gitignored, and reused until
|
|
32
41
|
expiry.
|
|
42
|
+
- Minted developer tokens must have a metadata-only inventory (owner, label,
|
|
43
|
+
scopes, created/expires/last-used/revoked state), immediate owner revocation,
|
|
44
|
+
and global admin incident response. Plaintext is shown only once and never
|
|
45
|
+
returned by inventory.
|
|
46
|
+
- Handshake attempts must retain bounded metadata-only attribution using a
|
|
47
|
+
keyed requester fingerprint rather than a raw IP, with independent issue and
|
|
48
|
+
poll rate limits. Agents and developer tokens must not read this admin trail.
|
|
33
49
|
|
|
34
50
|
## Safe Provisioning
|
|
35
51
|
|
package/dist/bin.cjs
CHANGED
|
@@ -203,14 +203,16 @@ async function getDeveloperToken(cfg, options, doFetch, out) {
|
|
|
203
203
|
return cached.token;
|
|
204
204
|
}
|
|
205
205
|
const browser = approvalBrowser(options);
|
|
206
|
+
const email = handshakeEmail(options.email, cached?.platform === audience ? cached.email : void 0);
|
|
206
207
|
const { token, expiresAt } = await (0, import_db.requestToken)({
|
|
207
208
|
endpoint: cfg.platformUrl,
|
|
209
|
+
email,
|
|
208
210
|
label: `${cfg.app.id} provisioner`,
|
|
209
211
|
fetch: doFetch,
|
|
210
|
-
onCode: async ({ userCode, expiresIn }) => {
|
|
211
|
-
const approvalUrl = handshakeUrl(cfg.platformUrl, userCode);
|
|
212
|
+
onCode: async ({ userCode, expiresIn, verificationUriComplete }) => {
|
|
213
|
+
const approvalUrl = verificationUriComplete ?? handshakeUrl(cfg.platformUrl, userCode);
|
|
212
214
|
out.log("");
|
|
213
|
-
out.log(`
|
|
215
|
+
out.log(`Review, then approve code ${userCode} at ${approvalUrl} within ${Math.floor(expiresIn / 60)}m.`);
|
|
214
216
|
if (browser.open) {
|
|
215
217
|
try {
|
|
216
218
|
await (options.openApprovalUrl ?? openUrl)(approvalUrl);
|
|
@@ -224,10 +226,17 @@ async function getDeveloperToken(cfg, options, doFetch, out) {
|
|
|
224
226
|
out.log("");
|
|
225
227
|
}
|
|
226
228
|
});
|
|
227
|
-
writePrivateJson(cfg.local.tokenFile, { platform: audience, token, expiresAt });
|
|
229
|
+
writePrivateJson(cfg.local.tokenFile, { platform: audience, email, token, expiresAt });
|
|
228
230
|
out.log(`auth: developer token cached (${displayPath(cfg.local.tokenFile, cfg.rootDir)})`);
|
|
229
231
|
return token;
|
|
230
232
|
}
|
|
233
|
+
function handshakeEmail(value, cached) {
|
|
234
|
+
const email = (value ?? import_node_process2.default.env.ODLA_USER_EMAIL ?? cached ?? "").trim().toLowerCase();
|
|
235
|
+
if (email.length > 254 || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
236
|
+
throw new Error("a fresh odla handshake requires --email <account> or ODLA_USER_EMAIL");
|
|
237
|
+
}
|
|
238
|
+
return email;
|
|
239
|
+
}
|
|
231
240
|
function approvalBrowser(options) {
|
|
232
241
|
if (options.open === true) return { open: true, mode: "forced" };
|
|
233
242
|
if (options.open === false) return { open: false, reason: "disabled by --no-open" };
|
|
@@ -320,14 +329,16 @@ async function scopedToken(platform, scope, options, doFetch, out) {
|
|
|
320
329
|
out.log(`auth: using cached ${scope} grant (${tokenFile})`);
|
|
321
330
|
return cached.token;
|
|
322
331
|
}
|
|
332
|
+
const email = handshakeEmail(options.email, cache?.platform === audience ? cache.email : void 0);
|
|
323
333
|
const { token, expiresAt } = await (0, import_db2.requestToken)({
|
|
324
334
|
endpoint: audience,
|
|
335
|
+
email,
|
|
325
336
|
label: `odla CLI admin AI (${scope})`,
|
|
326
337
|
scopes: [scope],
|
|
327
338
|
fetch: doFetch,
|
|
328
|
-
onCode: async ({ userCode, expiresIn }) => {
|
|
329
|
-
const approvalUrl = handshakeUrl(audience, userCode);
|
|
330
|
-
out.log(`
|
|
339
|
+
onCode: async ({ userCode, expiresIn, verificationUriComplete }) => {
|
|
340
|
+
const approvalUrl = verificationUriComplete ?? handshakeUrl(audience, userCode);
|
|
341
|
+
out.log(`Review, then approve scoped request ${userCode} at ${approvalUrl} within ${Math.floor(expiresIn / 60)}m.`);
|
|
331
342
|
const shouldOpen = options.open === true || options.open !== false && !import_node_process4.default.env.CI && Boolean(import_node_process4.default.stdin.isTTY && import_node_process4.default.stdout.isTTY);
|
|
332
343
|
if (shouldOpen) await (options.openApprovalUrl ?? openUrl)(approvalUrl).catch(() => void 0);
|
|
333
344
|
}
|
|
@@ -335,7 +346,7 @@ async function scopedToken(platform, scope, options, doFetch, out) {
|
|
|
335
346
|
const tokens = cache?.platform === audience ? { ...cache.tokens ?? {} } : {};
|
|
336
347
|
tokens[scope] = { token, expiresAt };
|
|
337
348
|
if ((0, import_node_fs2.existsSync)(`${import_node_process4.default.cwd()}/.git`)) ensureGitignore(import_node_process4.default.cwd(), [tokenFile]);
|
|
338
|
-
writePrivateJson(tokenFile, { platform: audience, tokens });
|
|
349
|
+
writePrivateJson(tokenFile, { platform: audience, email, tokens });
|
|
339
350
|
out.log(`auth: cached ${scope} grant (${tokenFile}; mode 0600)`);
|
|
340
351
|
return token;
|
|
341
352
|
}
|
|
@@ -505,6 +516,7 @@ async function adminAi(options) {
|
|
|
505
516
|
platform,
|
|
506
517
|
scope,
|
|
507
518
|
token: options.token,
|
|
519
|
+
email: options.email,
|
|
508
520
|
open: options.open,
|
|
509
521
|
fetch: doFetch,
|
|
510
522
|
stdout: out,
|
|
@@ -764,6 +776,7 @@ async function adminCommand(parsed) {
|
|
|
764
776
|
"platform",
|
|
765
777
|
"json",
|
|
766
778
|
"open",
|
|
779
|
+
"email",
|
|
767
780
|
"provider",
|
|
768
781
|
"model",
|
|
769
782
|
"enabled",
|
|
@@ -791,6 +804,7 @@ async function adminCommand(parsed) {
|
|
|
791
804
|
maxOutputTokens: numberOpt(parsed.options["max-output-tokens"], "--max-output-tokens"),
|
|
792
805
|
maxCallsPerRun: numberOpt(parsed.options["max-calls-per-run"], "--max-calls-per-run"),
|
|
793
806
|
platform: stringOpt(parsed.options.platform),
|
|
807
|
+
email: stringOpt(parsed.options.email),
|
|
794
808
|
json: parsed.options.json === true,
|
|
795
809
|
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
796
810
|
credentialProvider: credentialSet ? parsed.positionals[4] : void 0,
|
|
@@ -810,6 +824,7 @@ async function adminCommand(parsed) {
|
|
|
810
824
|
// src/capabilities.ts
|
|
811
825
|
var CAPABILITIES = {
|
|
812
826
|
cli: [
|
|
827
|
+
"start an email-bound device request without accepting a password or user session token",
|
|
813
828
|
"register the app and enable configured services per environment",
|
|
814
829
|
"issue, persist, and explicitly rotate configured service credentials",
|
|
815
830
|
"write local Worker values and push deployed Worker secrets through Wrangler stdin",
|
|
@@ -828,7 +843,7 @@ var CAPABILITIES = {
|
|
|
828
843
|
"wire @odla-ai/calendar into trusted Worker code and keep the app admin key out of browsers"
|
|
829
844
|
],
|
|
830
845
|
human: [
|
|
831
|
-
"
|
|
846
|
+
"provide the existing odla account email, then sign in and explicitly review/approve the exact device code",
|
|
832
847
|
"review mirrored calendar/attendee disclosure and complete the server-issued Google consent flow",
|
|
833
848
|
"consent to production changes with --yes",
|
|
834
849
|
"request destructive credential rotation explicitly",
|
|
@@ -838,6 +853,7 @@ var CAPABILITIES = {
|
|
|
838
853
|
],
|
|
839
854
|
studio: [
|
|
840
855
|
"view telemetry and environment state",
|
|
856
|
+
"let signed-in users inventory/revoke their own agent grants and admins audit/global-revoke them",
|
|
841
857
|
"review calendar connection, granted read scope, selected calendars, and sync health without exposing provider tokens",
|
|
842
858
|
"perform manual credential recovery when the CLI's local shown-once copy is unavailable",
|
|
843
859
|
"configure system AI purposes and view app/environment/run-attributed usage",
|
|
@@ -1371,6 +1387,7 @@ async function lifecycleContext(options) {
|
|
|
1371
1387
|
{
|
|
1372
1388
|
configPath: cfg.configPath,
|
|
1373
1389
|
token: options.token,
|
|
1390
|
+
email: options.email,
|
|
1374
1391
|
open: options.open,
|
|
1375
1392
|
interactive: options.interactive,
|
|
1376
1393
|
openApprovalUrl: options.openConsentUrl
|
|
@@ -1787,13 +1804,13 @@ Usage:
|
|
|
1787
1804
|
odla-ai setup [--dir <project>] [--agent <name>] [--global] [--force]
|
|
1788
1805
|
odla-ai init --app-id <id> --name <name> [--services db,ai,o11y,calendar] [--env dev --env prod]
|
|
1789
1806
|
odla-ai doctor [--config odla.config.mjs]
|
|
1790
|
-
odla-ai calendar status [--env dev] [--json]
|
|
1791
|
-
odla-ai calendar calendars [--env dev] [--json]
|
|
1792
|
-
odla-ai calendar connect [--env dev] [--no-open] [--yes]
|
|
1793
|
-
odla-ai calendar resync [--env dev] [--yes]
|
|
1794
|
-
odla-ai calendar disconnect [--env dev] --yes
|
|
1807
|
+
odla-ai calendar status [--env dev] [--email <odla-account>] [--json]
|
|
1808
|
+
odla-ai calendar calendars [--env dev] [--email <odla-account>] [--json]
|
|
1809
|
+
odla-ai calendar connect [--env dev] [--email <odla-account>] [--no-open] [--yes]
|
|
1810
|
+
odla-ai calendar resync [--env dev] [--email <odla-account>] [--yes]
|
|
1811
|
+
odla-ai calendar disconnect [--env dev] [--email <odla-account>] --yes
|
|
1795
1812
|
odla-ai capabilities [--json]
|
|
1796
|
-
odla-ai admin ai show [--platform https://odla.ai] [--json]
|
|
1813
|
+
odla-ai admin ai show [--platform https://odla.ai] [--email <odla-account>] [--json]
|
|
1797
1814
|
odla-ai admin ai models [--provider <id>] [--json]
|
|
1798
1815
|
odla-ai admin ai set <purpose> [--provider <id>] [--model <id>] [--enabled|--no-enabled]
|
|
1799
1816
|
odla-ai admin ai set security --discovery-provider <id> --discovery-model <id>
|
|
@@ -1802,7 +1819,7 @@ Usage:
|
|
|
1802
1819
|
odla-ai admin ai credential set <provider> (--from-env <NAME>|--stdin)
|
|
1803
1820
|
odla-ai admin ai usage [--app-id <id>] [--env <env>] [--run-id <id>] [--limit <1-500>] [--json]
|
|
1804
1821
|
odla-ai admin ai audit [--limit <1-200>] [--json]
|
|
1805
|
-
odla-ai security github connect [--repo owner/name] [--env dev] [--no-open]
|
|
1822
|
+
odla-ai security github connect [--repo owner/name] [--env dev] [--email <odla-account>] [--no-open]
|
|
1806
1823
|
odla-ai security github disconnect --source <id> [--env dev] [--yes]
|
|
1807
1824
|
odla-ai security plan [--env dev] [--json]
|
|
1808
1825
|
odla-ai security sources [--env dev] [--json]
|
|
@@ -1811,12 +1828,12 @@ Usage:
|
|
|
1811
1828
|
odla-ai security report <job-id> [--json]
|
|
1812
1829
|
odla-ai security run [target] --ack-redacted-source [--env dev] [--profile odla] [--fail-on high]
|
|
1813
1830
|
odla-ai security run [target] --self --ack-redacted-source
|
|
1814
|
-
odla-ai provision [--config odla.config.mjs] [--dry-run] [--push-secrets] [--rotate-o11y-token] [--write-dev-vars[=path]] [--yes]
|
|
1815
|
-
odla-ai smoke [--config odla.config.mjs] [--env dev] [--no-open]
|
|
1831
|
+
odla-ai provision [--config odla.config.mjs] [--email <odla-account>] [--dry-run] [--push-secrets] [--rotate-o11y-token] [--write-dev-vars[=path]] [--yes]
|
|
1832
|
+
odla-ai smoke [--config odla.config.mjs] [--env dev] [--email <odla-account>] [--no-open]
|
|
1816
1833
|
odla-ai skill install [--dir <project>] [--agent <name>] [--global] [--force]
|
|
1817
1834
|
odla-ai secrets push --env <env> [--config odla.config.mjs] [--dry-run] [--yes]
|
|
1818
|
-
odla-ai secrets set <name> --env <env> (--from-env <NAME>|--stdin) [--config odla.config.mjs] [--yes]
|
|
1819
|
-
odla-ai secrets set-clerk-key --env <env> (--from-env <NAME>|--stdin) [--config odla.config.mjs] [--yes]
|
|
1835
|
+
odla-ai secrets set <name> --env <env> (--from-env <NAME>|--stdin) [--email <odla-account>] [--config odla.config.mjs] [--yes]
|
|
1836
|
+
odla-ai secrets set-clerk-key --env <env> (--from-env <NAME>|--stdin) [--email <odla-account>] [--config odla.config.mjs] [--yes]
|
|
1820
1837
|
odla-ai version
|
|
1821
1838
|
|
|
1822
1839
|
Commands:
|
|
@@ -1844,6 +1861,10 @@ Safety:
|
|
|
1844
1861
|
preflights Wrangler before any shown-once issuance or destructive rotation.
|
|
1845
1862
|
Provision opens the approval page automatically in interactive terminals;
|
|
1846
1863
|
use --open to force browser launch or --no-open to suppress it.
|
|
1864
|
+
A fresh device handshake requires --email <odla-account> or ODLA_USER_EMAIL.
|
|
1865
|
+
The email is a non-secret identity hint: never provide a password or session
|
|
1866
|
+
token. The matching account must already exist, be signed in, explicitly
|
|
1867
|
+
review the exact code, and finish any current request before claiming another.
|
|
1847
1868
|
Calendar setup has a second human checkpoint: odla issues a state-bound Google consent URL;
|
|
1848
1869
|
OAuth codes and refresh tokens never enter the CLI, repo, chat, or app.
|
|
1849
1870
|
GitHub security uses source-read-only access plus optional metadata-only Checks write: the CLI never asks
|
|
@@ -2474,7 +2495,7 @@ async function hostedSecurityContext(parsed, dependencies) {
|
|
|
2474
2495
|
const open = parsed.options.open === false ? false : parsed.options.open === true ? true : void 0;
|
|
2475
2496
|
const token = await getDeveloperToken(
|
|
2476
2497
|
cfg,
|
|
2477
|
-
{ configPath, open, openApprovalUrl: dependencies.openUrl },
|
|
2498
|
+
{ configPath, email: stringOpt(parsed.options.email), open, openApprovalUrl: dependencies.openUrl },
|
|
2478
2499
|
doFetch,
|
|
2479
2500
|
stdout
|
|
2480
2501
|
);
|
|
@@ -3092,6 +3113,7 @@ async function runSourceSecurityCommand(parsed, dependencies, sourceId) {
|
|
|
3092
3113
|
"source",
|
|
3093
3114
|
"ref",
|
|
3094
3115
|
"follow",
|
|
3116
|
+
"email",
|
|
3095
3117
|
"open",
|
|
3096
3118
|
"json",
|
|
3097
3119
|
"ack-redacted-source",
|
|
@@ -3180,6 +3202,7 @@ async function runLocalSecurityCommand(parsed, dependencies) {
|
|
|
3180
3202
|
"platform",
|
|
3181
3203
|
"self",
|
|
3182
3204
|
"ack-redacted-source",
|
|
3205
|
+
"email",
|
|
3183
3206
|
"open",
|
|
3184
3207
|
"fail-on",
|
|
3185
3208
|
"fail-on-candidates",
|
|
@@ -3209,6 +3232,7 @@ async function runLocalSecurityCommand(parsed, dependencies) {
|
|
|
3209
3232
|
return getScopedPlatformToken({
|
|
3210
3233
|
platform: request.platform,
|
|
3211
3234
|
scope: request.scope,
|
|
3235
|
+
email: stringOpt(parsed.options.email),
|
|
3212
3236
|
open,
|
|
3213
3237
|
fetch: doFetch,
|
|
3214
3238
|
stdout: out,
|
|
@@ -3221,7 +3245,7 @@ async function runLocalSecurityCommand(parsed, dependencies) {
|
|
|
3221
3245
|
}
|
|
3222
3246
|
return getDeveloperToken(
|
|
3223
3247
|
cfg,
|
|
3224
|
-
{ configPath, open, openApprovalUrl: dependencies.openUrl },
|
|
3248
|
+
{ configPath, email: stringOpt(parsed.options.email), open, openApprovalUrl: dependencies.openUrl },
|
|
3225
3249
|
doFetch,
|
|
3226
3250
|
out
|
|
3227
3251
|
);
|
|
@@ -3255,7 +3279,7 @@ async function securityCommand(parsed, dependencies) {
|
|
|
3255
3279
|
return;
|
|
3256
3280
|
}
|
|
3257
3281
|
if (sub === "plan") {
|
|
3258
|
-
assertArgs(parsed, ["config", "env", "platform", "open", "json"], 2);
|
|
3282
|
+
assertArgs(parsed, ["config", "env", "platform", "email", "open", "json"], 2);
|
|
3259
3283
|
const context = await hostedSecurityContext(parsed, dependencies);
|
|
3260
3284
|
const plan = await getHostedSecurityPlan(context);
|
|
3261
3285
|
if (parsed.options.json === true) context.stdout.log(JSON.stringify(plan, null, 2));
|
|
@@ -3271,7 +3295,7 @@ async function securityCommand(parsed, dependencies) {
|
|
|
3271
3295
|
return;
|
|
3272
3296
|
}
|
|
3273
3297
|
if (sub === "report") {
|
|
3274
|
-
assertArgs(parsed, ["config", "env", "platform", "open", "json"], 3);
|
|
3298
|
+
assertArgs(parsed, ["config", "env", "platform", "email", "open", "json"], 3);
|
|
3275
3299
|
const jobId = requiredSecurityPositional(parsed, 2, "job id");
|
|
3276
3300
|
const context = await hostedSecurityContext(parsed, dependencies);
|
|
3277
3301
|
const report = await getHostedSecurityReport({ ...context, jobId });
|
|
@@ -3289,7 +3313,7 @@ async function securityCommand(parsed, dependencies) {
|
|
|
3289
3313
|
async function githubSecurityCommand(parsed, dependencies) {
|
|
3290
3314
|
const action = parsed.positionals[2];
|
|
3291
3315
|
if (action === "disconnect") {
|
|
3292
|
-
assertArgs(parsed, ["config", "env", "platform", "source", "open", "yes"], 3);
|
|
3316
|
+
assertArgs(parsed, ["config", "env", "platform", "source", "email", "open", "yes"], 3);
|
|
3293
3317
|
const context2 = await hostedSecurityContext(parsed, dependencies);
|
|
3294
3318
|
const sourceId = requiredString(parsed.options.source, "--source");
|
|
3295
3319
|
const confirmed = parsed.options.yes === true || await interactiveConfirmation(
|
|
@@ -3306,7 +3330,7 @@ async function githubSecurityCommand(parsed, dependencies) {
|
|
|
3306
3330
|
if (action !== "connect") {
|
|
3307
3331
|
throw new Error('unknown security github command. Try "odla-ai security github connect".');
|
|
3308
3332
|
}
|
|
3309
|
-
assertArgs(parsed, ["config", "env", "platform", "repo", "open"], 3);
|
|
3333
|
+
assertArgs(parsed, ["config", "env", "platform", "repo", "email", "open"], 3);
|
|
3310
3334
|
const context = await hostedSecurityContext(parsed, dependencies);
|
|
3311
3335
|
const repository = stringOpt(parsed.options.repo) ?? await inferGitHubRepository(process.cwd(), dependencies.readGitOrigin);
|
|
3312
3336
|
const connection = await connectGitHubSecuritySource({
|
|
@@ -3321,7 +3345,7 @@ async function githubSecurityCommand(parsed, dependencies) {
|
|
|
3321
3345
|
context.stdout.log("github: odla.ai stores the installation; no PAT or GitHub token is written locally");
|
|
3322
3346
|
}
|
|
3323
3347
|
async function listSecuritySources(parsed, dependencies) {
|
|
3324
|
-
assertArgs(parsed, ["config", "env", "platform", "open", "json"], 2);
|
|
3348
|
+
assertArgs(parsed, ["config", "env", "platform", "email", "open", "json"], 2);
|
|
3325
3349
|
const context = await hostedSecurityContext(parsed, dependencies);
|
|
3326
3350
|
const [plan, sources] = await Promise.all([
|
|
3327
3351
|
getHostedSecurityPlan(context),
|
|
@@ -3343,7 +3367,7 @@ source repository default ref status`);
|
|
|
3343
3367
|
}
|
|
3344
3368
|
}
|
|
3345
3369
|
async function securityStatus(parsed, dependencies) {
|
|
3346
|
-
assertArgs(parsed, ["config", "env", "platform", "open", "json", "follow"], 3);
|
|
3370
|
+
assertArgs(parsed, ["config", "env", "platform", "email", "open", "json", "follow"], 3);
|
|
3347
3371
|
const jobId = requiredSecurityPositional(parsed, 2, "job id");
|
|
3348
3372
|
const context = await hostedSecurityContext(parsed, dependencies);
|
|
3349
3373
|
const job = parsed.options.follow === true ? await followHostedSecurityJob({
|
|
@@ -3646,6 +3670,7 @@ async function smoke(options) {
|
|
|
3646
3670
|
{
|
|
3647
3671
|
configPath: cfg.configPath,
|
|
3648
3672
|
token: options.token,
|
|
3673
|
+
email: options.email,
|
|
3649
3674
|
open: options.open,
|
|
3650
3675
|
interactive: options.interactive,
|
|
3651
3676
|
openApprovalUrl: options.openApprovalUrl
|
|
@@ -3793,11 +3818,12 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
|
3793
3818
|
return;
|
|
3794
3819
|
}
|
|
3795
3820
|
if (command === "smoke") {
|
|
3796
|
-
assertArgs(parsed, ["config", "env", "token", "open"], 1);
|
|
3821
|
+
assertArgs(parsed, ["config", "env", "token", "email", "open"], 1);
|
|
3797
3822
|
await smoke({
|
|
3798
3823
|
configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
|
|
3799
3824
|
env: stringOpt(parsed.options.env),
|
|
3800
3825
|
token: stringOpt(parsed.options.token),
|
|
3826
|
+
email: stringOpt(parsed.options.email),
|
|
3801
3827
|
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
3802
3828
|
openApprovalUrl: dependencies.openUrl,
|
|
3803
3829
|
fetch: dependencies.fetch,
|
|
@@ -3835,7 +3861,7 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
|
3835
3861
|
if (command === "secrets") {
|
|
3836
3862
|
const sub = parsed.positionals[1];
|
|
3837
3863
|
if (sub === "set" || sub === "set-clerk-key") {
|
|
3838
|
-
assertArgs(parsed, ["config", "env", "from-env", "stdin", "token", "yes"], sub === "set" ? 3 : 2);
|
|
3864
|
+
assertArgs(parsed, ["config", "env", "from-env", "stdin", "token", "email", "yes"], sub === "set" ? 3 : 2);
|
|
3839
3865
|
const options = {
|
|
3840
3866
|
configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
|
|
3841
3867
|
name: parsed.positionals[2],
|
|
@@ -3843,6 +3869,7 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
|
3843
3869
|
fromEnv: stringOpt(parsed.options["from-env"]),
|
|
3844
3870
|
stdin: parsed.options.stdin === true,
|
|
3845
3871
|
token: stringOpt(parsed.options.token),
|
|
3872
|
+
email: stringOpt(parsed.options.email),
|
|
3846
3873
|
yes: parsed.options.yes === true,
|
|
3847
3874
|
fetch: dependencies.fetch,
|
|
3848
3875
|
stdout: dependencies.stdout
|
|
@@ -3876,6 +3903,7 @@ async function provisionCommand(parsed, dependencies) {
|
|
|
3876
3903
|
"write-credentials",
|
|
3877
3904
|
"write-dev-vars",
|
|
3878
3905
|
"token",
|
|
3906
|
+
"email",
|
|
3879
3907
|
"open",
|
|
3880
3908
|
"yes"
|
|
3881
3909
|
], 1);
|
|
@@ -3889,6 +3917,7 @@ async function provisionCommand(parsed, dependencies) {
|
|
|
3889
3917
|
writeCredentials: parsed.options["write-credentials"] !== false,
|
|
3890
3918
|
writeDevVars: typeof writeDevVars2 === "string" ? writeDevVars2 : writeDevVars2 === true,
|
|
3891
3919
|
token: stringOpt(parsed.options.token),
|
|
3920
|
+
email: stringOpt(parsed.options.email),
|
|
3892
3921
|
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
3893
3922
|
yes: parsed.options.yes === true,
|
|
3894
3923
|
fetch: dependencies.fetch,
|
|
@@ -3902,13 +3931,14 @@ async function calendarCommand(parsed, dependencies) {
|
|
|
3902
3931
|
if (sub !== "status" && sub !== "calendars" && sub !== "connect" && sub !== "resync" && sub !== "disconnect") {
|
|
3903
3932
|
throw new Error(`unknown calendar subcommand "${sub ?? ""}". Try "odla-ai calendar status --env dev".`);
|
|
3904
3933
|
}
|
|
3905
|
-
assertArgs(parsed, ["config", "env", "json", "token", "open", "yes"], 2);
|
|
3934
|
+
assertArgs(parsed, ["config", "env", "json", "token", "email", "open", "yes"], 2);
|
|
3906
3935
|
if (sub !== "status" && sub !== "calendars" && parsed.options.json !== void 0) throw new Error(`--json is supported only by calendar status/calendars`);
|
|
3907
3936
|
if ((sub === "status" || sub === "calendars") && parsed.options.yes !== void 0) throw new Error(`--yes is not used by "calendar ${sub}"`);
|
|
3908
3937
|
const options = {
|
|
3909
3938
|
configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
|
|
3910
3939
|
env: stringOpt(parsed.options.env),
|
|
3911
3940
|
token: stringOpt(parsed.options.token),
|
|
3941
|
+
email: stringOpt(parsed.options.email),
|
|
3912
3942
|
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
3913
3943
|
yes: parsed.options.yes === true,
|
|
3914
3944
|
json: parsed.options.json === true,
|