@odla-ai/cli 0.27.12 → 0.27.13

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 CHANGED
@@ -359,6 +359,8 @@ npx odla-ai code connect --env prod --once # bounded enrollment + heartbeat proo
359
359
  # install SDKs, write the Worker, and create wrangler.jsonc before secret push
360
360
  npx odla-ai provision --dry-run
361
361
  npx odla-ai provision --email owner@example.com --write-dev-vars --push-secrets
362
+ # recovery when the selected agent credential has no live app.manage grant
363
+ npx odla-ai provision --request-grant --email owner@example.com --write-dev-vars --push-secrets
362
364
  npx odla-ai smoke --env dev
363
365
  npx odla-ai agent jobs --env dev --state dead_letter --json
364
366
  npx odla-ai agent retry <job-id> --env dev --json
@@ -472,7 +474,11 @@ shown-once credential.
472
474
  needed by the run; it does not permit ownership, rename/category, archive,
473
475
  restore, or purge. A cached or pending baseline handshake without
474
476
  `app.manage` is not reused for provision: the CLI starts a fresh request for
475
- owner review. If
477
+ owner review. An explicit `--token` or `ODLA_DEV_TOKEN` has no trustworthy
478
+ local grant metadata, so a server refusal cannot be repaired by retrying it.
479
+ Run `provision --request-grant --email <account>` to ignore the ambient and
480
+ cached credentials, open a new exact-project review, collect the approved
481
+ replacement, and continue the same provisioning run. If
476
482
  the exact project id does not exist yet, approval reserves that id for this
477
483
  credential instead of failing: the credential may create only that app once,
478
484
  and Registry binds its reviewed grant to the new app incarnation.
package/dist/bin.cjs CHANGED
@@ -318,23 +318,30 @@ function handshakeWaitMs(waitSeconds, interactive = import_node_process3.default
318
318
 
319
319
  // src/token.ts
320
320
  async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {}) {
321
- if (options.token) return options.token;
322
321
  const audience = platformAudience(cfg.platformUrl);
323
- if (import_node_process4.default.env.ODLA_DEV_TOKEN) {
324
- const declared = import_node_process4.default.env.ODLA_DEV_TOKEN_AUDIENCE;
325
- if (declared) {
326
- if (platformAudience(declared) !== audience) throw new Error("ODLA_DEV_TOKEN_AUDIENCE does not match the configured platform");
327
- } else if (audience !== "https://odla.ai") {
328
- throw new Error("ODLA_DEV_TOKEN_AUDIENCE is required for a non-default platform");
329
- }
330
- return import_node_process4.default.env.ODLA_DEV_TOKEN;
331
- }
332
322
  const optionalProjectCapabilities = grantRequest.optionalProjectCapabilities ?? [];
333
323
  const grantIntent = { projectIds: [cfg.app.id], optionalProjectCapabilities };
334
324
  const cached = readJsonFile(cfg.local.tokenFile);
335
- if (cached?.token && cached.platform === audience && (cached.expiresAt ?? 0) > Date.now() + 6e4 && cachedGrantCovers(cached, grantIntent)) {
336
- out.error(`auth: using cached developer token (${displayPath(cfg.local.tokenFile, cfg.rootDir)})`);
337
- return cached.token;
325
+ if (!grantRequest.forceReview) {
326
+ if (options.token) return options.token;
327
+ if (import_node_process4.default.env.ODLA_DEV_TOKEN) {
328
+ const declared = import_node_process4.default.env.ODLA_DEV_TOKEN_AUDIENCE;
329
+ if (declared) {
330
+ if (platformAudience(declared) !== audience) throw new Error("ODLA_DEV_TOKEN_AUDIENCE does not match the configured platform");
331
+ } else if (audience !== "https://odla.ai") {
332
+ throw new Error("ODLA_DEV_TOKEN_AUDIENCE is required for a non-default platform");
333
+ }
334
+ return import_node_process4.default.env.ODLA_DEV_TOKEN;
335
+ }
336
+ if (cached?.token && cached.platform === audience && (cached.expiresAt ?? 0) > Date.now() + 6e4 && cachedGrantCovers(cached, grantIntent)) {
337
+ out.error(`auth: using cached developer token (${displayPath(cfg.local.tokenFile, cfg.rootDir)})`);
338
+ return cached.token;
339
+ }
340
+ } else {
341
+ if (options.token) {
342
+ throw new Error("--request-grant cannot be combined with --token; remove --token so the approved replacement credential can be collected and cached");
343
+ }
344
+ out.error(`auth: requesting fresh owner review for app.manage on exact project "${cfg.app.id}"`);
338
345
  }
339
346
  const ctx = {
340
347
  cfg,
@@ -2831,11 +2838,11 @@ async function assertTenantAdminAccess(doFetch, cfg, env, token) {
2831
2838
  }
2832
2839
  if (code === "provision_approval_required") {
2833
2840
  throw new Error(
2834
- `${env}: the agent credential does not carry the owner-reviewed app.manage grant required to provision "${cfg.app.id}" (tenant ${tenantId}). The human owner id on the token is accountability, not agent authority. Discard the cached or supplied token and run this command with the current CLI to approve one fresh exact-project provisioning handshake`
2841
+ `${env}: the agent credential does not carry the owner-reviewed app.manage grant required to provision "${cfg.app.id}" (tenant ${tenantId}). The human owner id on the token is accountability, not agent authority. Run "odla-ai provision --request-grant --email <odla-account>" to open one fresh exact-project owner review; do not change app ownership unless the human account itself is not an owner`
2835
2842
  );
2836
2843
  }
2837
2844
  throw new Error(
2838
- `${env}: this credential lacks live app.manage authority for "${cfg.app.id}" (tenant ${tenantId}) \u2014 nothing was minted or written; re-run provision with a fresh owner-approved provision handshake. If the human account is not an owner, an existing owner must add it in signed-in Studio; an agent token cannot repair ownership`
2845
+ `${env}: this credential lacks live app.manage authority for "${cfg.app.id}" (tenant ${tenantId}) \u2014 nothing was minted or written; run "odla-ai provision --request-grant --email <odla-account>" to open a fresh owner review. If the human account is not an owner, an existing owner must add it in signed-in Studio; an agent token cannot repair ownership`
2839
2846
  );
2840
2847
  }
2841
2848
  throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await safeText4(res)}`);
@@ -8517,7 +8524,7 @@ Usage:
8517
8524
  odla-ai security report <job-id> [--json]
8518
8525
  odla-ai security run [target] --ack-redacted-source [--env dev] [--profile odla] [--fail-on high]
8519
8526
  odla-ai security run [target] --self --ack-redacted-source
8520
- odla-ai provision [--config odla.config.mjs] [--email <odla-account>] [--wait <seconds>] [--dry-run] [--push-secrets] [--rotate-o11y-token] [--write-dev-vars[=path]] [--yes]
8527
+ odla-ai provision [--config odla.config.mjs] [--email <odla-account>] [--request-grant] [--wait <seconds>] [--dry-run] [--push-secrets] [--rotate-o11y-token] [--write-dev-vars[=path]] [--yes]
8521
8528
  odla-ai smoke [--config odla.config.mjs] [--env dev] [--email <odla-account>] [--no-open]
8522
8529
  odla-ai skill install [--dir <project>] [--agent <name>] [--global] [--force]
8523
8530
  odla-ai secrets push --env <env> [--config odla.config.mjs] [--dry-run] [--yes]
@@ -8652,6 +8659,10 @@ Safety:
8652
8659
  The email is a non-secret identity hint: never provide a password or session
8653
8660
  token. The matching account must already exist, be signed in, explicitly
8654
8661
  review the exact code, and finish any current request before claiming another.
8662
+ If provision reports that the current agent principal has no live app.manage
8663
+ grant, run it once with --request-grant. That flag ignores ODLA_DEV_TOKEN and
8664
+ the local cache, prints and opens a fresh exact-project owner-review URL, then
8665
+ continues provisioning with the approved replacement credential.
8655
8666
  Run Code from a GitHub checkout already connected to an app in Studio; an
8656
8667
  odla.config.mjs may select the app explicitly but is not required. Code host
8657
8668
  approval and credential hashes live in odla-ai/db. The host
@@ -10617,14 +10628,25 @@ async function provision(options) {
10617
10628
  }
10618
10629
  const doFetch = options.fetch ?? fetch;
10619
10630
  const token = await getDeveloperToken(cfg, options, doFetch, out, {
10620
- optionalProjectCapabilities: ["app.manage"]
10631
+ optionalProjectCapabilities: ["app.manage"],
10632
+ forceReview: options.requestGrant
10621
10633
  });
10622
10634
  const apps = (0, import_apps12.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
10623
10635
  const existing = await apps.resolveApp(cfg.app.id);
10624
10636
  if (existing) {
10625
10637
  out.log(`app: ${cfg.app.id} already exists`);
10626
10638
  } else {
10627
- await apps.createApp({ name: cfg.app.name, appId: cfg.app.id });
10639
+ try {
10640
+ await apps.createApp({ name: cfg.app.name, appId: cfg.app.id });
10641
+ } catch (error) {
10642
+ if (error instanceof import_apps12.AppsError && error.status === 403) {
10643
+ throw new Error(
10644
+ `app "${cfg.app.id}" does not exist, and this authenticated agent credential has no owner-reviewed app.manage bootstrap grant for that exact id. Run "odla-ai provision --request-grant --email <odla-account>" to open the review URL and continue; developer ownership alone is not agent authority`,
10645
+ { cause: error }
10646
+ );
10647
+ }
10648
+ throw error;
10649
+ }
10628
10650
  out.log(`app: created ${cfg.app.id}`);
10629
10651
  }
10630
10652
  for (const env of cfg.envs) {
@@ -12723,6 +12745,7 @@ async function provisionCommand(parsed, dependencies) {
12723
12745
  "write-credentials",
12724
12746
  "write-dev-vars",
12725
12747
  "token",
12748
+ "request-grant",
12726
12749
  "email",
12727
12750
  "open",
12728
12751
  "wait",
@@ -12738,6 +12761,7 @@ async function provisionCommand(parsed, dependencies) {
12738
12761
  writeCredentials: parsed.options["write-credentials"] !== false,
12739
12762
  writeDevVars: typeof writeDevVars2 === "string" ? writeDevVars2 : writeDevVars2 === true,
12740
12763
  token: stringOpt(parsed.options.token),
12764
+ requestGrant: parsed.options["request-grant"] === true,
12741
12765
  email: stringOpt(parsed.options.email),
12742
12766
  open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
12743
12767
  wait: numberOpt(parsed.options.wait, "--wait"),