@odla-ai/cli 0.27.0 → 0.27.2

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-DS64H4FY.js";
6
+ } from "./chunk-JWEBGIBR.js";
7
7
 
8
8
  // src/bin.ts
9
9
  runCli().catch((err) => {
@@ -3,7 +3,7 @@
3
3
  // src/admin-ai-auth.ts
4
4
  import { existsSync as existsSync2 } from "fs";
5
5
  import { join as join2 } from "path";
6
- import process5 from "process";
6
+ import process6 from "process";
7
7
  import { requestToken as requestToken2 } from "@odla-ai/db";
8
8
 
9
9
  // src/local.ts
@@ -132,36 +132,16 @@ function displayPath(path, rootDir = process.cwd()) {
132
132
  return rel && !rel.startsWith("..") ? rel : path;
133
133
  }
134
134
 
135
- // src/open.ts
136
- import { spawn } from "child_process";
137
- import process2 from "process";
138
- async function openUrl(url, options = {}) {
139
- const command = openerFor(options.platform ?? process2.platform);
140
- const doSpawn = options.spawnImpl ?? spawn;
141
- await new Promise((resolve13, reject) => {
142
- const child = doSpawn(command.cmd, [...command.args, url], {
143
- stdio: "ignore",
144
- detached: true
145
- });
146
- child.once("error", reject);
147
- child.once("spawn", () => {
148
- child.unref();
149
- resolve13();
150
- });
151
- });
152
- }
153
- function openerFor(platform) {
154
- if (platform === "darwin") return { cmd: "open", args: [] };
155
- if (platform === "win32") return { cmd: "cmd", args: ["/c", "start", ""] };
156
- return { cmd: "xdg-open", args: [] };
157
- }
135
+ // src/handshake-approval.ts
136
+ import process3 from "process";
158
137
 
159
138
  // src/approval-prompt.ts
160
139
  function approvalLines(prompt) {
161
140
  const lines = [""];
162
141
  lines.push(
163
- `\u23F8 Human approval required${prompt.purpose ? ` to ${prompt.purpose}` : ""} \u2014 code ${prompt.userCode}, ${prompt.minutesLeft}m left.`
142
+ `\u23F8 AUTHORIZATION REQUIRED${prompt.purpose ? ` to ${prompt.purpose}` : ""} \u2014 code ${prompt.userCode}, ${prompt.minutesLeft}m left.`
164
143
  );
144
+ lines.push(" OPEN THIS URL IN THE APPROVER'S SIGNED-IN BROWSER NOW:");
165
145
  lines.push("");
166
146
  lines.push(` ${prompt.approvalUrl}`);
167
147
  lines.push("");
@@ -175,8 +155,9 @@ function approvalLines(prompt) {
175
155
  lines.push(` No browser was opened (${prompt.browserSkipped}).`);
176
156
  }
177
157
  lines.push("");
178
- lines.push(" AGENTS: relay the URL above to the human verbatim, now, before waiting.");
179
- lines.push(" You cannot approve it yourself \u2014 the grant is issued to you, not by you.");
158
+ lines.push(" AGENTS: use browser control to open the URL above now; do not wait silently.");
159
+ lines.push(" If browser control is unavailable, give the exact URL to the human verbatim.");
160
+ lines.push(" You cannot approve it yourself, retry it away, or start a substitute handshake.");
180
161
  lines.push("");
181
162
  return lines;
182
163
  }
@@ -190,14 +171,73 @@ function reminderLines(prompt) {
190
171
  ];
191
172
  }
192
173
 
174
+ // src/open.ts
175
+ import { spawn } from "child_process";
176
+ import process2 from "process";
177
+ async function openUrl(url, options = {}) {
178
+ const command = openerFor(options.platform ?? process2.platform);
179
+ const doSpawn = options.spawnImpl ?? spawn;
180
+ await new Promise((resolve13, reject) => {
181
+ const child = doSpawn(command.cmd, [...command.args, url], {
182
+ stdio: "ignore",
183
+ detached: true
184
+ });
185
+ child.once("error", reject);
186
+ child.once("spawn", () => {
187
+ child.unref();
188
+ resolve13();
189
+ });
190
+ });
191
+ }
192
+ function openerFor(platform) {
193
+ if (platform === "darwin") return { cmd: "open", args: [] };
194
+ if (platform === "win32") return { cmd: "cmd", args: ["/c", "start", ""] };
195
+ return { cmd: "xdg-open", args: [] };
196
+ }
197
+
198
+ // src/handshake-approval.ts
199
+ function approvalBrowser(options, host = {}) {
200
+ if (options.open === false) return { open: false, reason: "disabled by --no-open" };
201
+ if (options.open === true) return { open: true, mode: "forced" };
202
+ const env = host.env ?? process3.env;
203
+ if (env.VITEST || env.NODE_ENV === "test") {
204
+ return { open: false, reason: "test environment" };
205
+ }
206
+ return { open: true, mode: "auto" };
207
+ }
208
+ async function presentHandshakeApproval(out, prompt, options) {
209
+ const browser = approvalBrowser(options);
210
+ printApproval(out, {
211
+ ...prompt,
212
+ browserAttempted: browser.open,
213
+ browserSkipped: browser.reason
214
+ });
215
+ if (!browser.open) {
216
+ out.error(`auth: browser launch skipped (${browser.reason}) \u2014 open the URL above manually`);
217
+ return;
218
+ }
219
+ try {
220
+ await (options.openApprovalUrl ?? openUrl)(prompt.approvalUrl);
221
+ out.error("auth: browser open requested for the approval URL above");
222
+ } catch (error) {
223
+ const reason = error instanceof Error ? error.message : String(error);
224
+ out.error(`auth: browser did not open (${reason}) \u2014 OPEN THE URL ABOVE NOW`);
225
+ }
226
+ }
227
+ function handshakeUrl(platformUrl, userCode) {
228
+ const url = new URL("/studio", platformUrl);
229
+ url.searchParams.set("code", userCode);
230
+ return url.toString();
231
+ }
232
+
193
233
  // src/token.ts
194
234
  import { collectToken, OdlaError, requestToken } from "@odla-ai/db";
195
- import process4 from "process";
235
+ import process5 from "process";
196
236
 
197
237
  // src/handshake-state.ts
198
238
  import { rmSync } from "fs";
199
239
  import { dirname as dirname2, join } from "path";
200
- import process3 from "process";
240
+ import process4 from "process";
201
241
  function handshakeFile(cfg) {
202
242
  return join(dirname2(cfg.local.tokenFile), "handshake.local.json");
203
243
  }
@@ -205,9 +245,13 @@ var RESUME_MARGIN_MS = 5e3;
205
245
  function readPendingHandshake(path, platform, email) {
206
246
  const pending = readJsonFile(path);
207
247
  if (!pending || pending.platform !== platform || pending.email !== email) return null;
208
- if (typeof pending.userCode !== "string" || typeof pending.deviceCode !== "string" || typeof pending.approvalUrl !== "string") return null;
248
+ if (typeof pending.userCode !== "string" || typeof pending.deviceCode !== "string") return null;
209
249
  if (typeof pending.expiresAt !== "number" || pending.expiresAt <= Date.now() + RESUME_MARGIN_MS) return null;
210
- return { interval: 3, ...pending };
250
+ return {
251
+ interval: typeof pending.interval === "number" ? pending.interval : 3,
252
+ ...pending,
253
+ approvalUrl: handshakeUrl(platform, pending.userCode)
254
+ };
211
255
  }
212
256
  function writePendingHandshake(path, pending) {
213
257
  writePrivateJson(path, pending);
@@ -233,7 +277,7 @@ function approvalReminder(out, pending, periodMs = 3e4) {
233
277
  timer.unref?.();
234
278
  return () => clearInterval(timer);
235
279
  }
236
- function handshakeWaitMs(waitSeconds, interactive = process3.stdout.isTTY === true) {
280
+ function handshakeWaitMs(waitSeconds, interactive = process4.stdout.isTTY === true) {
237
281
  if (waitSeconds !== void 0) return waitSeconds * 1e3;
238
282
  return interactive ? void 0 : 9e4;
239
283
  }
@@ -242,14 +286,14 @@ function handshakeWaitMs(waitSeconds, interactive = process3.stdout.isTTY === tr
242
286
  async function getDeveloperToken(cfg, options, doFetch, out) {
243
287
  if (options.token) return options.token;
244
288
  const audience = platformAudience(cfg.platformUrl);
245
- if (process4.env.ODLA_DEV_TOKEN) {
246
- const declared = process4.env.ODLA_DEV_TOKEN_AUDIENCE;
289
+ if (process5.env.ODLA_DEV_TOKEN) {
290
+ const declared = process5.env.ODLA_DEV_TOKEN_AUDIENCE;
247
291
  if (declared) {
248
292
  if (platformAudience(declared) !== audience) throw new Error("ODLA_DEV_TOKEN_AUDIENCE does not match the configured platform");
249
293
  } else if (audience !== "https://odla.ai") {
250
294
  throw new Error("ODLA_DEV_TOKEN_AUDIENCE is required for a non-default platform");
251
295
  }
252
- return process4.env.ODLA_DEV_TOKEN;
296
+ return process5.env.ODLA_DEV_TOKEN;
253
297
  }
254
298
  const cached = readJsonFile(cfg.local.tokenFile);
255
299
  if (cached?.token && cached.platform === audience && (cached.expiresAt ?? 0) > Date.now() + 6e4) {
@@ -262,7 +306,6 @@ async function getDeveloperToken(cfg, options, doFetch, out) {
262
306
  doFetch,
263
307
  out,
264
308
  audience,
265
- browser: approvalBrowser(options),
266
309
  email: handshakeEmail(options.email, cached?.platform === audience ? cached.email : void 0),
267
310
  pendingFile: handshakeFile(cfg)
268
311
  };
@@ -278,7 +321,12 @@ async function resumePendingHandshake(ctx, waitMs) {
278
321
  if (!pending) return null;
279
322
  ctx.out.error("");
280
323
  ctx.out.error(`auth: resuming pending handshake \u2014 ${approvalHint(pending)}`);
281
- await launchApproval(ctx, pending.approvalUrl);
324
+ await presentHandshakeApproval(ctx.out, {
325
+ userCode: pending.userCode,
326
+ approvalUrl: pending.approvalUrl,
327
+ minutesLeft: Math.max(1, Math.floor((pending.expiresAt - Date.now()) / 6e4)),
328
+ purpose: `sign this terminal in as ${ctx.email}`
329
+ }, ctx.options);
282
330
  ctx.out.error("");
283
331
  const stopReminder = approvalReminder(ctx.out, pending);
284
332
  try {
@@ -312,10 +360,11 @@ async function freshHandshake(ctx, waitMs) {
312
360
  endpoint: ctx.cfg.platformUrl,
313
361
  email: ctx.email,
314
362
  label: `${ctx.cfg.app.id} provisioner`,
363
+ projectIds: [ctx.cfg.app.id],
315
364
  fetch: ctx.doFetch,
316
365
  waitMs,
317
- onCode: async ({ userCode, deviceCode, expiresIn, interval, verificationUriComplete }) => {
318
- const approvalUrl = verificationUriComplete ?? handshakeUrl(ctx.cfg.platformUrl, userCode);
366
+ onCode: async ({ userCode, deviceCode, expiresIn, interval }) => {
367
+ const approvalUrl = handshakeUrl(ctx.audience, userCode);
319
368
  started = {
320
369
  platform: ctx.audience,
321
370
  email: ctx.email,
@@ -326,15 +375,12 @@ async function freshHandshake(ctx, waitMs) {
326
375
  expiresAt: Date.now() + expiresIn * 1e3
327
376
  };
328
377
  writePendingHandshake(ctx.pendingFile, started);
329
- printApproval(ctx.out, {
378
+ await presentHandshakeApproval(ctx.out, {
330
379
  userCode,
331
380
  approvalUrl,
332
381
  minutesLeft: Math.floor(expiresIn / 60),
333
- purpose: `sign this terminal in as ${ctx.email}`,
334
- browserAttempted: ctx.browser.open,
335
- browserSkipped: ctx.browser.reason
336
- });
337
- await launchApproval(ctx, approvalUrl);
382
+ purpose: `sign this terminal in as ${ctx.email}`
383
+ }, ctx.options);
338
384
  stopReminder = approvalReminder(ctx.out, started);
339
385
  }
340
386
  });
@@ -349,18 +395,6 @@ async function freshHandshake(ctx, waitMs) {
349
395
  stopReminder?.();
350
396
  }
351
397
  }
352
- async function launchApproval(ctx, approvalUrl) {
353
- if (ctx.browser.open) {
354
- try {
355
- await (ctx.options.openApprovalUrl ?? openUrl)(approvalUrl);
356
- ctx.out.error(`auth: asked the OS to open a browser${ctx.browser.mode === "auto" ? " (auto)" : ""} \u2014 if no tab appeared, use the URL above`);
357
- } catch (err) {
358
- ctx.out.error(`auth: could not open browser (${err instanceof Error ? err.message : String(err)})`);
359
- }
360
- } else if (ctx.browser.reason) {
361
- ctx.out.error(`auth: browser launch skipped (${ctx.browser.reason}) \u2014 show the human the URL above`);
362
- }
363
- }
364
398
  function stillPending(pending, email) {
365
399
  return new OdlaError(
366
400
  "handshake_pending",
@@ -369,30 +403,12 @@ function stillPending(pending, email) {
369
403
  );
370
404
  }
371
405
  function handshakeEmail(value2, cached) {
372
- const email = (value2 ?? process4.env.ODLA_USER_EMAIL ?? cached ?? "").trim().toLowerCase();
406
+ const email = (value2 ?? process5.env.ODLA_USER_EMAIL ?? cached ?? "").trim().toLowerCase();
373
407
  if (email.length > 254 || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
374
408
  throw new Error("a fresh odla handshake requires --email <account> or ODLA_USER_EMAIL");
375
409
  }
376
410
  return email;
377
411
  }
378
- function approvalBrowser(options, host = {}) {
379
- if (options.open === true) return { open: true, mode: "forced" };
380
- if (options.open === false) return { open: false, reason: "disabled by --no-open" };
381
- const env = host.env ?? process4.env;
382
- if (env.VITEST || env.NODE_ENV === "test") return { open: false, reason: "test environment" };
383
- if (env.CI) return { open: false, reason: "CI environment" };
384
- if (env.SSH_CONNECTION || env.SSH_TTY) return { open: false, reason: "SSH session; pass --open to force" };
385
- const platform = host.platform ?? process4.platform;
386
- if (platform !== "darwin" && platform !== "win32" && !env.DISPLAY && !env.WAYLAND_DISPLAY) {
387
- return { open: false, reason: "no graphical display; pass --open to force" };
388
- }
389
- return { open: true, mode: "auto" };
390
- }
391
- function handshakeUrl(platformUrl, userCode) {
392
- const url = new URL("/studio", platformUrl);
393
- url.searchParams.set("code", userCode);
394
- return url.toString();
395
- }
396
412
  function platformAudience(value2) {
397
413
  let url;
398
414
  try {
@@ -417,7 +433,7 @@ async function getScopedPlatformToken(options) {
417
433
  async function resolveAdminPlatformToken(options) {
418
434
  const audience = platformAudience(options.platform);
419
435
  if (options.token) return options.token;
420
- const fromEnv = process5.env.ODLA_ADMIN_TOKEN;
436
+ const fromEnv = process6.env.ODLA_ADMIN_TOKEN;
421
437
  if (fromEnv) return audienceBoundEnvToken(fromEnv, audience);
422
438
  return scopedToken(
423
439
  audience,
@@ -429,7 +445,7 @@ async function resolveAdminPlatformToken(options) {
429
445
  }
430
446
  function audienceBoundEnvToken(token, platform) {
431
447
  const audience = platformAudience(platform);
432
- const declared = process5.env.ODLA_ADMIN_TOKEN_AUDIENCE;
448
+ const declared = process6.env.ODLA_ADMIN_TOKEN_AUDIENCE;
433
449
  if (declared) {
434
450
  if (platformAudience(declared) !== audience) throw new Error("ODLA_ADMIN_TOKEN_AUDIENCE does not match the configured platform");
435
451
  } else if (audience !== "https://odla.ai") {
@@ -454,7 +470,7 @@ var SCOPE_PURPOSE = {
454
470
  };
455
471
  async function scopedToken(platform, scope, options, doFetch, out) {
456
472
  const audience = platformAudience(platform);
457
- const rootDir = options.rootDir ?? process5.cwd();
473
+ const rootDir = options.rootDir ?? process6.cwd();
458
474
  const tokenFile = options.tokenFile ?? join2(rootDir, ".odla/admin-token.local.json");
459
475
  const cache = options.cache === false ? null : readJsonFile(tokenFile);
460
476
  const cached = cache?.platform === audience ? cache.tokens?.[scope] : void 0;
@@ -469,21 +485,14 @@ async function scopedToken(platform, scope, options, doFetch, out) {
469
485
  label: options.label ?? `odla CLI (${scope})`,
470
486
  scopes: [scope],
471
487
  fetch: doFetch,
472
- onCode: async ({ userCode, expiresIn, verificationUriComplete }) => {
473
- const approvalUrl = verificationUriComplete ?? handshakeUrl(audience, userCode);
474
- const browser = approvalBrowser({ open: options.open });
475
- printApproval(out, {
488
+ onCode: async ({ userCode, expiresIn }) => {
489
+ await presentHandshakeApproval(out, {
476
490
  userCode,
477
- approvalUrl,
491
+ approvalUrl: handshakeUrl(audience, userCode),
478
492
  minutesLeft: Math.floor(expiresIn / 60),
479
493
  purpose: SCOPE_PURPOSE[scope] ?? `use ${scope}`,
480
- approver: scope.startsWith("app:") ? "A signed-in app owner" : "A signed-in odla platform admin",
481
- browserAttempted: browser.open,
482
- browserSkipped: browser.reason
483
- });
484
- if (browser.open) {
485
- await (options.openApprovalUrl ?? openUrl)(approvalUrl).catch(() => void 0);
486
- }
494
+ approver: scope.startsWith("app:") ? "A signed-in app owner" : "A signed-in odla platform admin"
495
+ }, options);
487
496
  }
488
497
  });
489
498
  if (options.cache !== false) {
@@ -513,15 +522,15 @@ function requireSystemAiPurpose(value2) {
513
522
  }
514
523
 
515
524
  // src/admin-ai.ts
516
- import process7 from "process";
525
+ import process8 from "process";
517
526
 
518
527
  // src/secret-input.ts
519
- import process6 from "process";
528
+ import process7 from "process";
520
529
  var MAX_BYTES = 64 * 1024;
521
530
  async function secretInputValue(options, kind = "credential") {
522
531
  if (options.fromEnv && options.stdin) throw new Error("choose exactly one of --from-env or --stdin");
523
532
  let value2;
524
- if (options.fromEnv) value2 = process6.env[options.fromEnv];
533
+ if (options.fromEnv) value2 = process7.env[options.fromEnv];
525
534
  else if (options.stdin) value2 = await (options.readStdin ?? (() => readSecretStream(kind)))();
526
535
  else throw new Error(`${kind} input required: use --from-env <NAME> or --stdin; values are never accepted as arguments`);
527
536
  value2 = value2?.replace(/[\r\n]+$/, "");
@@ -529,7 +538,7 @@ async function secretInputValue(options, kind = "credential") {
529
538
  if (new TextEncoder().encode(value2).byteLength > MAX_BYTES) throw new Error(`${kind} exceeds 64 KiB`);
530
539
  return value2;
531
540
  }
532
- async function readSecretStream(kind, stream = process6.stdin) {
541
+ async function readSecretStream(kind, stream = process7.stdin) {
533
542
  let value2 = "";
534
543
  for await (const chunk of stream) {
535
544
  value2 += String(chunk);
@@ -692,7 +701,7 @@ function isRecord2(value2) {
692
701
 
693
702
  // src/admin-ai.ts
694
703
  async function adminAi(options) {
695
- const platform = platformAudience(options.platform ?? process7.env.ODLA_PLATFORM ?? "https://odla.ai");
704
+ const platform = platformAudience(options.platform ?? process8.env.ODLA_PLATFORM ?? "https://odla.ai");
696
705
  const doFetch = options.fetch ?? fetch;
697
706
  const out = options.stdout ?? console;
698
707
  const usageQuery = options.action === "usage" ? adminAiUsageQuery(options) : void 0;
@@ -7123,7 +7132,7 @@ function record6(value2) {
7123
7132
  // src/provision.ts
7124
7133
  import { createAppsClient as createAppsClient3, orderAppServices as orderAppServices3, tenantIdFor as tenantIdFor4 } from "@odla-ai/apps";
7125
7134
  import { putSecret as putSecret2 } from "@odla-ai/ai";
7126
- import process8 from "process";
7135
+ import process9 from "process";
7127
7136
 
7128
7137
  // src/integration-provision.ts
7129
7138
  import { uuidv7 } from "@odla-ai/db";
@@ -7430,7 +7439,7 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
7430
7439
  await provisionIntegrationSeeds(doFetch, cfg.dbEndpoint, tenantId, dbKey, database.integrations, env, out);
7431
7440
  }
7432
7441
  if (cfg.services.includes("ai") && cfg.ai?.provider && cfg.ai.keyEnv) {
7433
- const key = process8.env[cfg.ai.keyEnv];
7442
+ const key = process9.env[cfg.ai.keyEnv];
7434
7443
  if (key) {
7435
7444
  const secretName = cfg.ai.secretName ?? defaultSecretName(cfg.ai.provider);
7436
7445
  await putSecret2({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, secretName, key);
@@ -7976,21 +7985,21 @@ function addOption(options, name, value2) {
7976
7985
  // src/operator-context.ts
7977
7986
  import { existsSync as existsSync10 } from "fs";
7978
7987
  import { join as join12, resolve as resolve11 } from "path";
7979
- import process10 from "process";
7988
+ import process11 from "process";
7980
7989
 
7981
7990
  // src/operator-profiles.ts
7982
7991
  import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
7983
7992
  import { homedir as homedir2 } from "os";
7984
7993
  import { dirname as dirname7, join as join11, resolve as resolve10 } from "path";
7985
- import process9 from "process";
7994
+ import process10 from "process";
7986
7995
  function operatorProfileFile() {
7987
7996
  return resolve10(
7988
- clean(process9.env.ODLA_CONTEXT_FILE) ?? join11(homedir2(), ".odla", "contexts.json")
7997
+ clean(process10.env.ODLA_CONTEXT_FILE) ?? join11(homedir2(), ".odla", "contexts.json")
7989
7998
  );
7990
7999
  }
7991
8000
  function resolveOperatorProfile(parsed) {
7992
8001
  const fromFlag = clean(stringOpt(parsed.options.context));
7993
- const fromEnvironment = clean(process9.env.ODLA_CONTEXT);
8002
+ const fromEnvironment = clean(process10.env.ODLA_CONTEXT);
7994
8003
  const name = fromFlag ?? fromEnvironment ?? null;
7995
8004
  const file = operatorProfileFile();
7996
8005
  if (!name) {
@@ -8119,13 +8128,13 @@ async function resolveOperatorContext(parsed, options = {}) {
8119
8128
  }
8120
8129
  const loaded = hasConfig ? await loadProjectConfig(configArgument) : void 0;
8121
8130
  const platformFlag = clean2(stringOpt(parsed.options.platform));
8122
- const platformEnvironment = clean2(process10.env.ODLA_PLATFORM_URL);
8131
+ const platformEnvironment = clean2(process11.env.ODLA_PLATFORM_URL);
8123
8132
  const platformValue = platformAudience(
8124
8133
  platformFlag ?? platformEnvironment ?? profile.value?.platform ?? loaded?.platformUrl ?? DEFAULT_PLATFORM2
8125
8134
  );
8126
8135
  const platformSource = platformFlag ? "flag" : platformEnvironment ? "environment" : profile.value ? "profile" : loaded ? "config" : "default";
8127
8136
  const appFlag = clean2(stringOpt(parsed.options.app));
8128
- const appEnvironment = clean2(process10.env.ODLA_APP_ID);
8137
+ const appEnvironment = clean2(process11.env.ODLA_APP_ID);
8129
8138
  const appValue = appFlag ?? appEnvironment ?? profile.value?.app ?? loaded?.app.id ?? null;
8130
8139
  const appSource = appFlag ? "flag" : appEnvironment ? "environment" : profile.value?.app ? "profile" : loaded ? "config" : "unresolved";
8131
8140
  if (appValue) assertOperatorName(appValue, "app");
@@ -8135,16 +8144,16 @@ async function resolveOperatorContext(parsed, options = {}) {
8135
8144
  );
8136
8145
  }
8137
8146
  const envFlag = clean2(stringOpt(parsed.options.env));
8138
- const envEnvironment = clean2(process10.env.ODLA_ENV);
8147
+ const envEnvironment = clean2(process11.env.ODLA_ENV);
8139
8148
  const environmentValue = envFlag ?? envEnvironment ?? profile.value?.environment ?? options.defaultEnvironment ?? null;
8140
8149
  const environmentSource = envFlag ? "flag" : envEnvironment ? "environment" : profile.value?.environment ? "profile" : options.defaultEnvironment ? "default" : "unresolved";
8141
8150
  if (environmentValue) {
8142
8151
  assertOperatorName(environmentValue, "environment");
8143
8152
  }
8144
- const rootDir = loaded?.rootDir ?? process10.cwd();
8153
+ const rootDir = loaded?.rootDir ?? process11.cwd();
8145
8154
  const profileCredentials = operatorCredentialFiles(profile);
8146
- const tokenFile = clean2(process10.env.ODLA_DEV_TOKEN_FILE) ? resolve11(process10.env.ODLA_DEV_TOKEN_FILE) : profile.name ? profileCredentials.developer : loaded?.local.tokenFile ?? profileCredentials.developer;
8147
- const scopedTokenFile = clean2(process10.env.ODLA_ADMIN_TOKEN_FILE) ? resolve11(process10.env.ODLA_ADMIN_TOKEN_FILE) : profile.name ? profileCredentials.scoped : loaded ? join12(loaded.rootDir, ".odla", "admin-token.local.json") : profileCredentials.scoped;
8155
+ const tokenFile = clean2(process11.env.ODLA_DEV_TOKEN_FILE) ? resolve11(process11.env.ODLA_DEV_TOKEN_FILE) : profile.name ? profileCredentials.developer : loaded?.local.tokenFile ?? profileCredentials.developer;
8156
+ const scopedTokenFile = clean2(process11.env.ODLA_ADMIN_TOKEN_FILE) ? resolve11(process11.env.ODLA_ADMIN_TOKEN_FILE) : profile.name ? profileCredentials.scoped : loaded ? join12(loaded.rootDir, ".odla", "admin-token.local.json") : profileCredentials.scoped;
8148
8157
  const cfg = loaded ? {
8149
8158
  ...loaded,
8150
8159
  platformUrl: platformValue,
@@ -9258,13 +9267,13 @@ async function codeCommand(parsed, dependencies) {
9258
9267
  }
9259
9268
 
9260
9269
  // src/operator-credentials.ts
9261
- import process11 from "process";
9270
+ import process12 from "process";
9262
9271
  function developerTokenStatus(context, parsed, now = Date.now()) {
9263
9272
  const cached = readJsonFile(context.cfg.local.tokenFile);
9264
9273
  const cacheStatus = !cached?.token ? "missing" : cached.platform !== context.platform.value ? "other-platform" : (cached.expiresAt ?? 0) <= now + 6e4 ? "expired" : "valid";
9265
9274
  const source = clean3(
9266
9275
  stringOpt(parsed.options.token)
9267
- ) ? "flag" : clean3(process11.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
9276
+ ) ? "flag" : clean3(process12.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
9268
9277
  return {
9269
9278
  source,
9270
9279
  cacheFile: context.cfg.local.tokenFile,
@@ -9638,15 +9647,16 @@ Safety:
9638
9647
  the metadata file. Flags and specific ODLA_* scope variables beat a selected
9639
9648
  context, which beats project config. There is no ambient current context.
9640
9649
  "context show" reports only provenance and cache state and never authenticates.
9641
- Provision opens the approval page in your browser automatically whenever the
9642
- machine can show one, including agent-driven runs; only CI, SSH, and
9643
- display-less hosts skip it. Use --open to force or --no-open to suppress.
9644
- Browser launch is best-effort: the printed approval URL is authoritative and
9645
- agents must relay it to the human verbatim. A started handshake is persisted
9650
+ Every real CLI handshake prints one canonical /studio?code= approval URL and
9651
+ attempts to open it \u2014 including in CI, SSH, display-less, and agent-driven
9652
+ shells. Only --no-open suppresses the attempt. Browser launch is best-effort:
9653
+ agents with browser control must open that exact URL immediately; otherwise
9654
+ they must give it to the human verbatim. A started handshake is persisted
9646
9655
  under .odla/, so a command killed mid-wait loses nothing \u2014 rerunning resumes
9647
9656
  the same code. Outside an interactive terminal the wait is capped (90s by
9648
9657
  default, --wait <seconds> to change); a still-pending handshake then exits
9649
- with code 75: relay the URL, wait for approval, and re-run to collect.
9658
+ with code 75: open the same URL (or relay it if browser control is unavailable),
9659
+ wait for approval, and re-run to collect.
9650
9660
  A fresh device handshake requires --email <odla-account> or ODLA_USER_EMAIL.
9651
9661
  The email is a non-secret identity hint: never provide a password or session
9652
9662
  token. The matching account must already exist, be signed in, explicitly
@@ -11054,9 +11064,9 @@ async function read2(url, headers, doFetch) {
11054
11064
 
11055
11065
  // src/record.ts
11056
11066
  import { appendFileSync } from "fs";
11057
- import process12 from "process";
11067
+ import process13 from "process";
11058
11068
  function recordInvocation(parsed) {
11059
- const file = process12.env.ODLA_CLI_RECORD;
11069
+ const file = process13.env.ODLA_CLI_RECORD;
11060
11070
  if (!file) return;
11061
11071
  try {
11062
11072
  const entry = {
@@ -11728,9 +11738,9 @@ import { spawnSync } from "child_process";
11728
11738
  import { mkdtempSync, readFileSync as readFileSync13, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
11729
11739
  import { tmpdir as tmpdir4 } from "os";
11730
11740
  import { join as join15 } from "path";
11731
- import process13 from "process";
11741
+ import process14 from "process";
11732
11742
  var EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
11733
- function resolveEditor(env = process13.env) {
11743
+ function resolveEditor(env = process14.env) {
11734
11744
  for (const name of EDITOR_ENV) {
11735
11745
  const value2 = env[name];
11736
11746
  if (value2 && value2.trim()) return value2.trim();
@@ -11744,8 +11754,8 @@ function defaultRun(command, path) {
11744
11754
  return result.status ?? 0;
11745
11755
  }
11746
11756
  function editText(initial, slug, deps = {}) {
11747
- const env = deps.env ?? process13.env;
11748
- const interactive = deps.interactive ?? (() => Boolean(process13.stdin.isTTY));
11757
+ const env = deps.env ?? process14.env;
11758
+ const interactive = deps.interactive ?? (() => Boolean(process14.stdin.isTTY));
11749
11759
  const editor = resolveEditor(env);
11750
11760
  if (!editor)
11751
11761
  throw new Error(
@@ -12736,4 +12746,4 @@ export {
12736
12746
  exitCodeFor,
12737
12747
  runCli
12738
12748
  };
12739
- //# sourceMappingURL=chunk-DS64H4FY.js.map
12749
+ //# sourceMappingURL=chunk-JWEBGIBR.js.map