@odla-ai/cli 0.10.2 → 0.11.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/dist/index.cjs CHANGED
@@ -286,12 +286,16 @@ function handshakeEmail(value, cached) {
286
286
  }
287
287
  return email;
288
288
  }
289
- function approvalBrowser(options) {
289
+ function approvalBrowser(options, host = {}) {
290
290
  if (options.open === true) return { open: true, mode: "forced" };
291
291
  if (options.open === false) return { open: false, reason: "disabled by --no-open" };
292
- if (import_node_process2.default.env.CI) return { open: false, reason: "CI environment" };
293
- const interactive = options.interactive ?? Boolean(import_node_process2.default.stdin.isTTY && import_node_process2.default.stdout.isTTY);
294
- if (!interactive) return { open: false, reason: "non-interactive shell; pass --open to force" };
292
+ const env = host.env ?? import_node_process2.default.env;
293
+ if (env.CI) return { open: false, reason: "CI environment" };
294
+ if (env.SSH_CONNECTION || env.SSH_TTY) return { open: false, reason: "SSH session; pass --open to force" };
295
+ const platform = host.platform ?? import_node_process2.default.platform;
296
+ if (platform !== "darwin" && platform !== "win32" && !env.DISPLAY && !env.WAYLAND_DISPLAY) {
297
+ return { open: false, reason: "no graphical display; pass --open to force" };
298
+ }
295
299
  return { open: true, mode: "auto" };
296
300
  }
297
301
  function handshakeUrl(platformUrl, userCode) {
@@ -388,8 +392,9 @@ async function scopedToken(platform, scope, options, doFetch, out) {
388
392
  onCode: async ({ userCode, expiresIn, verificationUriComplete }) => {
389
393
  const approvalUrl = verificationUriComplete ?? handshakeUrl(audience, userCode);
390
394
  out.log(`Review, then approve scoped request ${userCode} at ${approvalUrl} within ${Math.floor(expiresIn / 60)}m.`);
391
- 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);
392
- if (shouldOpen) await (options.openApprovalUrl ?? openUrl)(approvalUrl).catch(() => void 0);
395
+ if (approvalBrowser({ open: options.open }).open) {
396
+ await (options.openApprovalUrl ?? openUrl)(approvalUrl).catch(() => void 0);
397
+ }
393
398
  }
394
399
  });
395
400
  const tokens = cache?.platform === audience ? { ...cache.tokens ?? {} } : {};
@@ -1133,6 +1138,28 @@ function unique(values) {
1133
1138
  return [...new Set(values.filter(Boolean))];
1134
1139
  }
1135
1140
 
1141
+ // src/calendar-errors.ts
1142
+ var PLATFORM_NOT_READY_CODES = /* @__PURE__ */ new Set([
1143
+ "calendar_google_oauth_not_configured",
1144
+ "calendar_token_vault_not_configured",
1145
+ "calendar_db_ingress_not_configured"
1146
+ ]);
1147
+ var CalendarRequestError = class extends Error {
1148
+ /** HTTP status returned by the registry. */
1149
+ status;
1150
+ /** Machine-readable `error.code` from the response body, when present. */
1151
+ code;
1152
+ constructor(message, status, code) {
1153
+ super(message);
1154
+ this.name = "CalendarRequestError";
1155
+ this.status = status;
1156
+ if (code !== void 0) this.code = code;
1157
+ }
1158
+ };
1159
+ function isCalendarPlatformNotReady(error) {
1160
+ return error instanceof CalendarRequestError && error.code !== void 0 && PLATFORM_NOT_READY_CODES.has(error.code);
1161
+ }
1162
+
1136
1163
  // src/redact.ts
1137
1164
  var REPLACEMENTS = [
1138
1165
  [/odla_(sk|dev)_[A-Za-z0-9._-]+/g, "odla_$1_[redacted]"],
@@ -1298,7 +1325,8 @@ async function calendarJson(ctx, suffix, init) {
1298
1325
  if (!response.ok) {
1299
1326
  const code = textField(body.error?.code, 128);
1300
1327
  const message = textField(body.error?.message, 500);
1301
- throw new Error(redactSecrets(`calendar request failed (${response.status})${code ? ` ${code}` : ""}${message ? `: ${message}` : ""}`));
1328
+ const detail = `${code ? ` ${code}` : ""}${message ? `: ${message}` : ""}`;
1329
+ throw new CalendarRequestError(redactSecrets(`calendar request failed (${response.status})${detail}`), response.status, code);
1302
1330
  }
1303
1331
  return body;
1304
1332
  }
@@ -1438,7 +1466,6 @@ async function lifecycleContext(options) {
1438
1466
  token: options.token,
1439
1467
  email: options.email,
1440
1468
  open: options.open,
1441
- interactive: options.interactive,
1442
1469
  openApprovalUrl: options.openConsentUrl
1443
1470
  },
1444
1471
  doFetch,
@@ -1447,7 +1474,7 @@ async function lifecycleContext(options) {
1447
1474
  return { cfg, ctx: { platform: cfg.platformUrl, appId: cfg.app.id, env, token, fetch: doFetch }, out };
1448
1475
  }
1449
1476
  function connectionOptions(options, out) {
1450
- const browser = approvalBrowser({ configPath: "odla.config.mjs", open: options.open, interactive: options.interactive });
1477
+ const browser = approvalBrowser({ open: options.open });
1451
1478
  return {
1452
1479
  out,
1453
1480
  open: browser.open,
@@ -1908,8 +1935,9 @@ Safety:
1908
1935
  Provision caches the approved developer token and service credentials under
1909
1936
  .odla/ with mode 0600, and init adds those paths to .gitignore. Secret push
1910
1937
  preflights Wrangler before any shown-once issuance or destructive rotation.
1911
- Provision opens the approval page automatically in interactive terminals;
1912
- use --open to force browser launch or --no-open to suppress it.
1938
+ Provision opens the approval page in your browser automatically whenever the
1939
+ machine can show one, including agent-driven runs; only CI, SSH, and
1940
+ display-less hosts skip it. Use --open to force or --no-open to suppress.
1913
1941
  A fresh device handshake requires --email <odla-account> or ODLA_USER_EMAIL.
1914
1942
  The email is a non-secret identity hint: never provide a password or session
1915
1943
  token. The matching account must already exist, be signed in, explicitly
@@ -2349,17 +2377,6 @@ async function provision(options) {
2349
2377
  if (cfg.services.includes("calendar")) {
2350
2378
  const calendarCtx = { platform: cfg.platformUrl, appId: cfg.app.id, env, token, fetch: doFetch };
2351
2379
  await applyCalendarBookingPage(calendarCtx, calendarBookingPageUrl(cfg, env), out);
2352
- await ensureCalendarConnected(
2353
- calendarCtx,
2354
- {
2355
- open: options.open,
2356
- interactive: options.interactive,
2357
- openConsentUrl: options.openApprovalUrl,
2358
- wait: options.calendarPollWait,
2359
- pollTimeoutMs: options.calendarPollTimeoutMs,
2360
- stdout: out
2361
- }
2362
- );
2363
2380
  }
2364
2381
  }
2365
2382
  for (const env of cfg.envs) {
@@ -2423,6 +2440,25 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
2423
2440
  writeDevVars(devVarsTarget, credentials, env, o11yDevVars(cfg));
2424
2441
  out.log(`dev vars: wrote ${displayPath(devVarsTarget, cfg.rootDir)} for ${env}`);
2425
2442
  }
2443
+ if (cfg.services.includes("calendar")) {
2444
+ for (const env of cfg.envs) {
2445
+ try {
2446
+ await ensureCalendarConnected(
2447
+ { platform: cfg.platformUrl, appId: cfg.app.id, env, token, fetch: doFetch },
2448
+ {
2449
+ open: options.open,
2450
+ openConsentUrl: options.openApprovalUrl,
2451
+ wait: options.calendarPollWait,
2452
+ pollTimeoutMs: options.calendarPollTimeoutMs,
2453
+ stdout: out
2454
+ }
2455
+ );
2456
+ } catch (error) {
2457
+ if (!isCalendarPlatformNotReady(error)) throw error;
2458
+ out.log(`${env}: calendar consent skipped (${error.code}); resume with "odla-ai calendar connect --env ${env}" once the platform is configured`);
2459
+ }
2460
+ }
2461
+ }
2426
2462
  }
2427
2463
  function serviceRank(service) {
2428
2464
  if (service === "db") return 0;
@@ -3732,7 +3768,6 @@ async function smoke(options) {
3732
3768
  token: options.token,
3733
3769
  email: options.email,
3734
3770
  open: options.open,
3735
- interactive: options.interactive,
3736
3771
  openApprovalUrl: options.openApprovalUrl
3737
3772
  },
3738
3773
  doFetch,