@odla-ai/cli 0.27.8 → 0.27.10

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-OGZELVS5.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;
@@ -1366,7 +1397,7 @@ function parseCalendarStatus(raw, env) {
1366
1397
  throw new Error("calendar status returned unsupported access");
1367
1398
  }
1368
1399
  const errorValue = record(value2.error) ?? record(connection.error);
1369
- const errorCode = textField(value2.lastErrorCode, 128);
1400
+ const errorCode2 = textField(value2.lastErrorCode, 128);
1370
1401
  const bookingPageValue = Object.hasOwn(value2, "bookingPageUrl") ? value2.bookingPageUrl : Object.hasOwn(config, "bookingPageUrl") ? config.bookingPageUrl : googleConfig.bookingPageUrl;
1371
1402
  const connected = typeof (value2.connected ?? connection.connected) === "boolean" ? Boolean(value2.connected ?? connection.connected) : ["healthy", "degraded"].includes(stateValue);
1372
1403
  const bookingCalendarId = textField(value2.bookingCalendarId ?? config.bookingCalendarId, 1024);
@@ -1385,10 +1416,10 @@ function parseCalendarStatus(raw, env) {
1385
1416
  ...optionalNullableUrl("bookingPageUrl", bookingPageValue),
1386
1417
  grantedScopes: stringList(value2.grantedScopes ?? connection.grantedScopes ?? connection.scopes),
1387
1418
  ...optionalText("attemptId", value2.attemptId ?? connection.attemptId, 180),
1388
- ...errorValue || errorCode ? { error: {
1419
+ ...errorValue || errorCode2 ? { error: {
1389
1420
  ...textField(errorValue?.code, 128) ? { code: textField(errorValue?.code, 128) } : {},
1390
1421
  ...textField(errorValue?.message, 500) ? { message: textField(errorValue?.message, 500) } : {},
1391
- ...!errorValue && errorCode ? { code: errorCode } : {}
1422
+ ...!errorValue && errorCode2 ? { code: errorCode2 } : {}
1392
1423
  } } : {}
1393
1424
  };
1394
1425
  }
@@ -1768,12 +1799,26 @@ async function assertTenantAdminAccess(doFetch, cfg, env, token) {
1768
1799
  });
1769
1800
  if (res.ok || res.status === 404) return;
1770
1801
  if (res.status === 403) {
1802
+ const detail = await safeText4(res);
1803
+ if (errorCode(detail) === "human_session_required") {
1804
+ throw new Error(
1805
+ `${env}: odla-db rejected the provision credential before checking ownership for "${cfg.app.id}" (tenant ${tenantId}): human_session_required. Retrying or changing app owners will not help; the deployed odla-db must accept owner-approved app.manage credentials on provisioning routes`
1806
+ );
1807
+ }
1771
1808
  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`
1809
+ `${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
1810
  );
1774
1811
  }
1775
1812
  throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await safeText4(res)}`);
1776
1813
  }
1814
+ function errorCode(text2) {
1815
+ try {
1816
+ const body = JSON.parse(text2);
1817
+ return typeof body.error?.code === "string" ? body.error.code : null;
1818
+ } catch {
1819
+ return null;
1820
+ }
1821
+ }
1777
1822
  async function postJson(doFetch, url, bearer, body) {
1778
1823
  const res = await doFetch(url, {
1779
1824
  method: "POST",
@@ -7404,7 +7449,9 @@ async function provision(options) {
7404
7449
  out.log("secrets: Wrangler config and login preflight passed");
7405
7450
  }
7406
7451
  const doFetch = options.fetch ?? fetch;
7407
- const token = await getDeveloperToken(cfg, options, doFetch, out);
7452
+ const token = await getDeveloperToken(cfg, options, doFetch, out, {
7453
+ optionalProjectCapabilities: ["app.manage"]
7454
+ });
7408
7455
  const apps = createAppsClient3({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
7409
7456
  const existing = await apps.resolveApp(cfg.app.id);
7410
7457
  if (existing) {
@@ -13166,4 +13213,4 @@ export {
13166
13213
  exitCodeFor,
13167
13214
  runCli
13168
13215
  };
13169
- //# sourceMappingURL=chunk-MZSU4YQL.js.map
13216
+ //# sourceMappingURL=chunk-OGZELVS5.js.map