@odla-ai/cli 0.27.8 → 0.27.9

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/dist/bin.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  exitCodeFor,
4
4
  redactSecrets,
5
5
  runCli
6
- } from "./chunk-MZSU4YQL.js";
6
+ } from "./chunk-LT56MKDK.js";
7
7
 
8
8
  // src/bin.ts
9
9
  runCli().catch((err) => {
@@ -243,17 +243,24 @@ function handshakeFile(cfg) {
243
243
  return join(dirname2(cfg.local.tokenFile), "handshake.local.json");
244
244
  }
245
245
  var RESUME_MARGIN_MS = 5e3;
246
- function readPendingHandshake(path, platform, email) {
246
+ function readPendingHandshake(path, platform, email, requiredGrant) {
247
247
  const pending = readJsonFile(path);
248
248
  if (!pending || pending.platform !== platform || pending.email !== email) return null;
249
249
  if (typeof pending.userCode !== "string" || typeof pending.deviceCode !== "string") return null;
250
250
  if (typeof pending.expiresAt !== "number" || pending.expiresAt <= Date.now() + RESUME_MARGIN_MS) return null;
251
+ if (requiredGrant && !grantCovers(pending, requiredGrant)) return null;
251
252
  return {
252
253
  interval: typeof pending.interval === "number" ? pending.interval : 3,
253
254
  ...pending,
254
255
  approvalUrl: handshakeUrl(platform, pending.userCode)
255
256
  };
256
257
  }
258
+ function grantCovers(stored, required) {
259
+ if (required.optionalProjectCapabilities.length === 0) return true;
260
+ return required.projectIds.every((id) => stored.projectIds?.includes(id)) && required.optionalProjectCapabilities.every(
261
+ (capability) => stored.optionalProjectCapabilities?.includes(capability)
262
+ );
263
+ }
257
264
  function writePendingHandshake(path, pending) {
258
265
  writePrivateJson(path, pending);
259
266
  }
@@ -284,7 +291,7 @@ function handshakeWaitMs(waitSeconds, interactive = process4.stdout.isTTY === tr
284
291
  }
285
292
 
286
293
  // src/token.ts
287
- async function getDeveloperToken(cfg, options, doFetch, out) {
294
+ async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {}) {
288
295
  if (options.token) return options.token;
289
296
  const audience = platformAudience(cfg.platformUrl);
290
297
  if (process5.env.ODLA_DEV_TOKEN) {
@@ -296,8 +303,10 @@ async function getDeveloperToken(cfg, options, doFetch, out) {
296
303
  }
297
304
  return process5.env.ODLA_DEV_TOKEN;
298
305
  }
306
+ const optionalProjectCapabilities = grantRequest.optionalProjectCapabilities ?? [];
307
+ const grantIntent = { projectIds: [cfg.app.id], optionalProjectCapabilities };
299
308
  const cached = readJsonFile(cfg.local.tokenFile);
300
- if (cached?.token && cached.platform === audience && (cached.expiresAt ?? 0) > Date.now() + 6e4) {
309
+ if (cached?.token && cached.platform === audience && (cached.expiresAt ?? 0) > Date.now() + 6e4 && cachedGrantCovers(cached, grantIntent)) {
301
310
  out.error(`auth: using cached developer token (${displayPath(cfg.local.tokenFile, cfg.rootDir)})`);
302
311
  return cached.token;
303
312
  }
@@ -308,17 +317,30 @@ async function getDeveloperToken(cfg, options, doFetch, out) {
308
317
  out,
309
318
  audience,
310
319
  email: handshakeEmail(options.email, cached?.platform === audience ? cached.email : void 0),
311
- pendingFile: handshakeFile(cfg)
320
+ pendingFile: handshakeFile(cfg),
321
+ grantIntent
312
322
  };
313
323
  const waitMs = handshakeWaitMs(options.wait);
314
324
  const { token, expiresAt } = await resumePendingHandshake(ctx, waitMs) ?? await freshHandshake(ctx, waitMs);
315
325
  clearPendingHandshake(ctx.pendingFile);
316
- writePrivateJson(cfg.local.tokenFile, { platform: audience, email: ctx.email, token, expiresAt });
326
+ writePrivateJson(cfg.local.tokenFile, {
327
+ platform: audience,
328
+ email: ctx.email,
329
+ projectIds: grantIntent.projectIds,
330
+ optionalProjectCapabilities,
331
+ token,
332
+ expiresAt
333
+ });
317
334
  out.error(`auth: developer token cached (${displayPath(cfg.local.tokenFile, cfg.rootDir)})`);
318
335
  return token;
319
336
  }
320
337
  async function resumePendingHandshake(ctx, waitMs) {
321
- const pending = readPendingHandshake(ctx.pendingFile, ctx.audience, ctx.email);
338
+ const pending = readPendingHandshake(
339
+ ctx.pendingFile,
340
+ ctx.audience,
341
+ ctx.email,
342
+ ctx.grantIntent
343
+ );
322
344
  if (!pending) return null;
323
345
  ctx.out.error("");
324
346
  ctx.out.error(`auth: resuming pending handshake \u2014 ${approvalHint(pending)}`);
@@ -363,6 +385,7 @@ async function freshHandshake(ctx, waitMs) {
363
385
  label: `${ctx.cfg.app.id} provisioner`,
364
386
  agentHandle: projectAgentHandle(ctx.cfg.app.id),
365
387
  projectIds: [ctx.cfg.app.id],
388
+ optionalProjectCapabilities: ctx.grantIntent.optionalProjectCapabilities,
366
389
  fetch: ctx.doFetch,
367
390
  waitMs,
368
391
  onCode: async ({ userCode, deviceCode, expiresIn, interval }) => {
@@ -374,7 +397,9 @@ async function freshHandshake(ctx, waitMs) {
374
397
  deviceCode,
375
398
  approvalUrl,
376
399
  interval,
377
- expiresAt: Date.now() + expiresIn * 1e3
400
+ expiresAt: Date.now() + expiresIn * 1e3,
401
+ projectIds: ctx.grantIntent.projectIds,
402
+ optionalProjectCapabilities: ctx.grantIntent.optionalProjectCapabilities
378
403
  };
379
404
  writePendingHandshake(ctx.pendingFile, started);
380
405
  await presentHandshakeApproval(ctx.out, {
@@ -397,6 +422,12 @@ async function freshHandshake(ctx, waitMs) {
397
422
  stopReminder?.();
398
423
  }
399
424
  }
425
+ function cachedGrantCovers(cached, required) {
426
+ if (required.optionalProjectCapabilities.length === 0) return true;
427
+ return required.projectIds.every((id) => cached.projectIds?.includes(id)) && required.optionalProjectCapabilities.every(
428
+ (capability) => cached.optionalProjectCapabilities?.includes(capability)
429
+ );
430
+ }
400
431
  function projectAgentHandle(appId) {
401
432
  const candidate = /^[a-z]/.test(appId) ? appId : `app-${appId}`;
402
433
  if (candidate.length <= 32) return candidate;
@@ -1769,7 +1800,7 @@ async function assertTenantAdminAccess(doFetch, cfg, env, token) {
1769
1800
  if (res.ok || res.status === 404) return;
1770
1801
  if (res.status === 403) {
1771
1802
  throw new Error(
1772
- `${env}: you are not an owner of "${cfg.app.id}" (tenant ${tenantId}) \u2014 nothing was minted or written; ask an existing owner to run "odla-ai app owners add <your-email>", then re-run provision`
1803
+ `${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`
1773
1804
  );
1774
1805
  }
1775
1806
  throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await safeText4(res)}`);
@@ -7404,7 +7435,9 @@ async function provision(options) {
7404
7435
  out.log("secrets: Wrangler config and login preflight passed");
7405
7436
  }
7406
7437
  const doFetch = options.fetch ?? fetch;
7407
- const token = await getDeveloperToken(cfg, options, doFetch, out);
7438
+ const token = await getDeveloperToken(cfg, options, doFetch, out, {
7439
+ optionalProjectCapabilities: ["app.manage"]
7440
+ });
7408
7441
  const apps = createAppsClient3({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
7409
7442
  const existing = await apps.resolveApp(cfg.app.id);
7410
7443
  if (existing) {
@@ -13166,4 +13199,4 @@ export {
13166
13199
  exitCodeFor,
13167
13200
  runCli
13168
13201
  };
13169
- //# sourceMappingURL=chunk-MZSU4YQL.js.map
13202
+ //# sourceMappingURL=chunk-LT56MKDK.js.map