@odla-ai/cli 0.27.1 → 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-4JWDJN3P.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 {
@@ -315,8 +363,8 @@ async function freshHandshake(ctx, waitMs) {
315
363
  projectIds: [ctx.cfg.app.id],
316
364
  fetch: ctx.doFetch,
317
365
  waitMs,
318
- onCode: async ({ userCode, deviceCode, expiresIn, interval, verificationUriComplete }) => {
319
- const approvalUrl = verificationUriComplete ?? handshakeUrl(ctx.cfg.platformUrl, userCode);
366
+ onCode: async ({ userCode, deviceCode, expiresIn, interval }) => {
367
+ const approvalUrl = handshakeUrl(ctx.audience, userCode);
320
368
  started = {
321
369
  platform: ctx.audience,
322
370
  email: ctx.email,
@@ -327,15 +375,12 @@ async function freshHandshake(ctx, waitMs) {
327
375
  expiresAt: Date.now() + expiresIn * 1e3
328
376
  };
329
377
  writePendingHandshake(ctx.pendingFile, started);
330
- printApproval(ctx.out, {
378
+ await presentHandshakeApproval(ctx.out, {
331
379
  userCode,
332
380
  approvalUrl,
333
381
  minutesLeft: Math.floor(expiresIn / 60),
334
- purpose: `sign this terminal in as ${ctx.email}`,
335
- browserAttempted: ctx.browser.open,
336
- browserSkipped: ctx.browser.reason
337
- });
338
- await launchApproval(ctx, approvalUrl);
382
+ purpose: `sign this terminal in as ${ctx.email}`
383
+ }, ctx.options);
339
384
  stopReminder = approvalReminder(ctx.out, started);
340
385
  }
341
386
  });
@@ -350,18 +395,6 @@ async function freshHandshake(ctx, waitMs) {
350
395
  stopReminder?.();
351
396
  }
352
397
  }
353
- async function launchApproval(ctx, approvalUrl) {
354
- if (ctx.browser.open) {
355
- try {
356
- await (ctx.options.openApprovalUrl ?? openUrl)(approvalUrl);
357
- 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`);
358
- } catch (err) {
359
- ctx.out.error(`auth: could not open browser (${err instanceof Error ? err.message : String(err)})`);
360
- }
361
- } else if (ctx.browser.reason) {
362
- ctx.out.error(`auth: browser launch skipped (${ctx.browser.reason}) \u2014 show the human the URL above`);
363
- }
364
- }
365
398
  function stillPending(pending, email) {
366
399
  return new OdlaError(
367
400
  "handshake_pending",
@@ -370,30 +403,12 @@ function stillPending(pending, email) {
370
403
  );
371
404
  }
372
405
  function handshakeEmail(value2, cached) {
373
- const email = (value2 ?? process4.env.ODLA_USER_EMAIL ?? cached ?? "").trim().toLowerCase();
406
+ const email = (value2 ?? process5.env.ODLA_USER_EMAIL ?? cached ?? "").trim().toLowerCase();
374
407
  if (email.length > 254 || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
375
408
  throw new Error("a fresh odla handshake requires --email <account> or ODLA_USER_EMAIL");
376
409
  }
377
410
  return email;
378
411
  }
379
- function approvalBrowser(options, host = {}) {
380
- if (options.open === true) return { open: true, mode: "forced" };
381
- if (options.open === false) return { open: false, reason: "disabled by --no-open" };
382
- const env = host.env ?? process4.env;
383
- if (env.VITEST || env.NODE_ENV === "test") return { open: false, reason: "test environment" };
384
- if (env.CI) return { open: false, reason: "CI environment" };
385
- if (env.SSH_CONNECTION || env.SSH_TTY) return { open: false, reason: "SSH session; pass --open to force" };
386
- const platform = host.platform ?? process4.platform;
387
- if (platform !== "darwin" && platform !== "win32" && !env.DISPLAY && !env.WAYLAND_DISPLAY) {
388
- return { open: false, reason: "no graphical display; pass --open to force" };
389
- }
390
- return { open: true, mode: "auto" };
391
- }
392
- function handshakeUrl(platformUrl, userCode) {
393
- const url = new URL("/studio", platformUrl);
394
- url.searchParams.set("code", userCode);
395
- return url.toString();
396
- }
397
412
  function platformAudience(value2) {
398
413
  let url;
399
414
  try {
@@ -418,7 +433,7 @@ async function getScopedPlatformToken(options) {
418
433
  async function resolveAdminPlatformToken(options) {
419
434
  const audience = platformAudience(options.platform);
420
435
  if (options.token) return options.token;
421
- const fromEnv = process5.env.ODLA_ADMIN_TOKEN;
436
+ const fromEnv = process6.env.ODLA_ADMIN_TOKEN;
422
437
  if (fromEnv) return audienceBoundEnvToken(fromEnv, audience);
423
438
  return scopedToken(
424
439
  audience,
@@ -430,7 +445,7 @@ async function resolveAdminPlatformToken(options) {
430
445
  }
431
446
  function audienceBoundEnvToken(token, platform) {
432
447
  const audience = platformAudience(platform);
433
- const declared = process5.env.ODLA_ADMIN_TOKEN_AUDIENCE;
448
+ const declared = process6.env.ODLA_ADMIN_TOKEN_AUDIENCE;
434
449
  if (declared) {
435
450
  if (platformAudience(declared) !== audience) throw new Error("ODLA_ADMIN_TOKEN_AUDIENCE does not match the configured platform");
436
451
  } else if (audience !== "https://odla.ai") {
@@ -455,7 +470,7 @@ var SCOPE_PURPOSE = {
455
470
  };
456
471
  async function scopedToken(platform, scope, options, doFetch, out) {
457
472
  const audience = platformAudience(platform);
458
- const rootDir = options.rootDir ?? process5.cwd();
473
+ const rootDir = options.rootDir ?? process6.cwd();
459
474
  const tokenFile = options.tokenFile ?? join2(rootDir, ".odla/admin-token.local.json");
460
475
  const cache = options.cache === false ? null : readJsonFile(tokenFile);
461
476
  const cached = cache?.platform === audience ? cache.tokens?.[scope] : void 0;
@@ -470,21 +485,14 @@ async function scopedToken(platform, scope, options, doFetch, out) {
470
485
  label: options.label ?? `odla CLI (${scope})`,
471
486
  scopes: [scope],
472
487
  fetch: doFetch,
473
- onCode: async ({ userCode, expiresIn, verificationUriComplete }) => {
474
- const approvalUrl = verificationUriComplete ?? handshakeUrl(audience, userCode);
475
- const browser = approvalBrowser({ open: options.open });
476
- printApproval(out, {
488
+ onCode: async ({ userCode, expiresIn }) => {
489
+ await presentHandshakeApproval(out, {
477
490
  userCode,
478
- approvalUrl,
491
+ approvalUrl: handshakeUrl(audience, userCode),
479
492
  minutesLeft: Math.floor(expiresIn / 60),
480
493
  purpose: SCOPE_PURPOSE[scope] ?? `use ${scope}`,
481
- approver: scope.startsWith("app:") ? "A signed-in app owner" : "A signed-in odla platform admin",
482
- browserAttempted: browser.open,
483
- browserSkipped: browser.reason
484
- });
485
- if (browser.open) {
486
- await (options.openApprovalUrl ?? openUrl)(approvalUrl).catch(() => void 0);
487
- }
494
+ approver: scope.startsWith("app:") ? "A signed-in app owner" : "A signed-in odla platform admin"
495
+ }, options);
488
496
  }
489
497
  });
490
498
  if (options.cache !== false) {
@@ -514,15 +522,15 @@ function requireSystemAiPurpose(value2) {
514
522
  }
515
523
 
516
524
  // src/admin-ai.ts
517
- import process7 from "process";
525
+ import process8 from "process";
518
526
 
519
527
  // src/secret-input.ts
520
- import process6 from "process";
528
+ import process7 from "process";
521
529
  var MAX_BYTES = 64 * 1024;
522
530
  async function secretInputValue(options, kind = "credential") {
523
531
  if (options.fromEnv && options.stdin) throw new Error("choose exactly one of --from-env or --stdin");
524
532
  let value2;
525
- if (options.fromEnv) value2 = process6.env[options.fromEnv];
533
+ if (options.fromEnv) value2 = process7.env[options.fromEnv];
526
534
  else if (options.stdin) value2 = await (options.readStdin ?? (() => readSecretStream(kind)))();
527
535
  else throw new Error(`${kind} input required: use --from-env <NAME> or --stdin; values are never accepted as arguments`);
528
536
  value2 = value2?.replace(/[\r\n]+$/, "");
@@ -530,7 +538,7 @@ async function secretInputValue(options, kind = "credential") {
530
538
  if (new TextEncoder().encode(value2).byteLength > MAX_BYTES) throw new Error(`${kind} exceeds 64 KiB`);
531
539
  return value2;
532
540
  }
533
- async function readSecretStream(kind, stream = process6.stdin) {
541
+ async function readSecretStream(kind, stream = process7.stdin) {
534
542
  let value2 = "";
535
543
  for await (const chunk of stream) {
536
544
  value2 += String(chunk);
@@ -693,7 +701,7 @@ function isRecord2(value2) {
693
701
 
694
702
  // src/admin-ai.ts
695
703
  async function adminAi(options) {
696
- 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");
697
705
  const doFetch = options.fetch ?? fetch;
698
706
  const out = options.stdout ?? console;
699
707
  const usageQuery = options.action === "usage" ? adminAiUsageQuery(options) : void 0;
@@ -7124,7 +7132,7 @@ function record6(value2) {
7124
7132
  // src/provision.ts
7125
7133
  import { createAppsClient as createAppsClient3, orderAppServices as orderAppServices3, tenantIdFor as tenantIdFor4 } from "@odla-ai/apps";
7126
7134
  import { putSecret as putSecret2 } from "@odla-ai/ai";
7127
- import process8 from "process";
7135
+ import process9 from "process";
7128
7136
 
7129
7137
  // src/integration-provision.ts
7130
7138
  import { uuidv7 } from "@odla-ai/db";
@@ -7431,7 +7439,7 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
7431
7439
  await provisionIntegrationSeeds(doFetch, cfg.dbEndpoint, tenantId, dbKey, database.integrations, env, out);
7432
7440
  }
7433
7441
  if (cfg.services.includes("ai") && cfg.ai?.provider && cfg.ai.keyEnv) {
7434
- const key = process8.env[cfg.ai.keyEnv];
7442
+ const key = process9.env[cfg.ai.keyEnv];
7435
7443
  if (key) {
7436
7444
  const secretName = cfg.ai.secretName ?? defaultSecretName(cfg.ai.provider);
7437
7445
  await putSecret2({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, secretName, key);
@@ -7977,21 +7985,21 @@ function addOption(options, name, value2) {
7977
7985
  // src/operator-context.ts
7978
7986
  import { existsSync as existsSync10 } from "fs";
7979
7987
  import { join as join12, resolve as resolve11 } from "path";
7980
- import process10 from "process";
7988
+ import process11 from "process";
7981
7989
 
7982
7990
  // src/operator-profiles.ts
7983
7991
  import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
7984
7992
  import { homedir as homedir2 } from "os";
7985
7993
  import { dirname as dirname7, join as join11, resolve as resolve10 } from "path";
7986
- import process9 from "process";
7994
+ import process10 from "process";
7987
7995
  function operatorProfileFile() {
7988
7996
  return resolve10(
7989
- clean(process9.env.ODLA_CONTEXT_FILE) ?? join11(homedir2(), ".odla", "contexts.json")
7997
+ clean(process10.env.ODLA_CONTEXT_FILE) ?? join11(homedir2(), ".odla", "contexts.json")
7990
7998
  );
7991
7999
  }
7992
8000
  function resolveOperatorProfile(parsed) {
7993
8001
  const fromFlag = clean(stringOpt(parsed.options.context));
7994
- const fromEnvironment = clean(process9.env.ODLA_CONTEXT);
8002
+ const fromEnvironment = clean(process10.env.ODLA_CONTEXT);
7995
8003
  const name = fromFlag ?? fromEnvironment ?? null;
7996
8004
  const file = operatorProfileFile();
7997
8005
  if (!name) {
@@ -8120,13 +8128,13 @@ async function resolveOperatorContext(parsed, options = {}) {
8120
8128
  }
8121
8129
  const loaded = hasConfig ? await loadProjectConfig(configArgument) : void 0;
8122
8130
  const platformFlag = clean2(stringOpt(parsed.options.platform));
8123
- const platformEnvironment = clean2(process10.env.ODLA_PLATFORM_URL);
8131
+ const platformEnvironment = clean2(process11.env.ODLA_PLATFORM_URL);
8124
8132
  const platformValue = platformAudience(
8125
8133
  platformFlag ?? platformEnvironment ?? profile.value?.platform ?? loaded?.platformUrl ?? DEFAULT_PLATFORM2
8126
8134
  );
8127
8135
  const platformSource = platformFlag ? "flag" : platformEnvironment ? "environment" : profile.value ? "profile" : loaded ? "config" : "default";
8128
8136
  const appFlag = clean2(stringOpt(parsed.options.app));
8129
- const appEnvironment = clean2(process10.env.ODLA_APP_ID);
8137
+ const appEnvironment = clean2(process11.env.ODLA_APP_ID);
8130
8138
  const appValue = appFlag ?? appEnvironment ?? profile.value?.app ?? loaded?.app.id ?? null;
8131
8139
  const appSource = appFlag ? "flag" : appEnvironment ? "environment" : profile.value?.app ? "profile" : loaded ? "config" : "unresolved";
8132
8140
  if (appValue) assertOperatorName(appValue, "app");
@@ -8136,16 +8144,16 @@ async function resolveOperatorContext(parsed, options = {}) {
8136
8144
  );
8137
8145
  }
8138
8146
  const envFlag = clean2(stringOpt(parsed.options.env));
8139
- const envEnvironment = clean2(process10.env.ODLA_ENV);
8147
+ const envEnvironment = clean2(process11.env.ODLA_ENV);
8140
8148
  const environmentValue = envFlag ?? envEnvironment ?? profile.value?.environment ?? options.defaultEnvironment ?? null;
8141
8149
  const environmentSource = envFlag ? "flag" : envEnvironment ? "environment" : profile.value?.environment ? "profile" : options.defaultEnvironment ? "default" : "unresolved";
8142
8150
  if (environmentValue) {
8143
8151
  assertOperatorName(environmentValue, "environment");
8144
8152
  }
8145
- const rootDir = loaded?.rootDir ?? process10.cwd();
8153
+ const rootDir = loaded?.rootDir ?? process11.cwd();
8146
8154
  const profileCredentials = operatorCredentialFiles(profile);
8147
- 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;
8148
- 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;
8149
8157
  const cfg = loaded ? {
8150
8158
  ...loaded,
8151
8159
  platformUrl: platformValue,
@@ -9259,13 +9267,13 @@ async function codeCommand(parsed, dependencies) {
9259
9267
  }
9260
9268
 
9261
9269
  // src/operator-credentials.ts
9262
- import process11 from "process";
9270
+ import process12 from "process";
9263
9271
  function developerTokenStatus(context, parsed, now = Date.now()) {
9264
9272
  const cached = readJsonFile(context.cfg.local.tokenFile);
9265
9273
  const cacheStatus = !cached?.token ? "missing" : cached.platform !== context.platform.value ? "other-platform" : (cached.expiresAt ?? 0) <= now + 6e4 ? "expired" : "valid";
9266
9274
  const source = clean3(
9267
9275
  stringOpt(parsed.options.token)
9268
- ) ? "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";
9269
9277
  return {
9270
9278
  source,
9271
9279
  cacheFile: context.cfg.local.tokenFile,
@@ -9639,15 +9647,16 @@ Safety:
9639
9647
  the metadata file. Flags and specific ODLA_* scope variables beat a selected
9640
9648
  context, which beats project config. There is no ambient current context.
9641
9649
  "context show" reports only provenance and cache state and never authenticates.
9642
- Provision opens the approval page in your browser automatically whenever the
9643
- machine can show one, including agent-driven runs; only CI, SSH, and
9644
- display-less hosts skip it. Use --open to force or --no-open to suppress.
9645
- Browser launch is best-effort: the printed approval URL is authoritative and
9646
- 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
9647
9655
  under .odla/, so a command killed mid-wait loses nothing \u2014 rerunning resumes
9648
9656
  the same code. Outside an interactive terminal the wait is capped (90s by
9649
9657
  default, --wait <seconds> to change); a still-pending handshake then exits
9650
- 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.
9651
9660
  A fresh device handshake requires --email <odla-account> or ODLA_USER_EMAIL.
9652
9661
  The email is a non-secret identity hint: never provide a password or session
9653
9662
  token. The matching account must already exist, be signed in, explicitly
@@ -11055,9 +11064,9 @@ async function read2(url, headers, doFetch) {
11055
11064
 
11056
11065
  // src/record.ts
11057
11066
  import { appendFileSync } from "fs";
11058
- import process12 from "process";
11067
+ import process13 from "process";
11059
11068
  function recordInvocation(parsed) {
11060
- const file = process12.env.ODLA_CLI_RECORD;
11069
+ const file = process13.env.ODLA_CLI_RECORD;
11061
11070
  if (!file) return;
11062
11071
  try {
11063
11072
  const entry = {
@@ -11729,9 +11738,9 @@ import { spawnSync } from "child_process";
11729
11738
  import { mkdtempSync, readFileSync as readFileSync13, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
11730
11739
  import { tmpdir as tmpdir4 } from "os";
11731
11740
  import { join as join15 } from "path";
11732
- import process13 from "process";
11741
+ import process14 from "process";
11733
11742
  var EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
11734
- function resolveEditor(env = process13.env) {
11743
+ function resolveEditor(env = process14.env) {
11735
11744
  for (const name of EDITOR_ENV) {
11736
11745
  const value2 = env[name];
11737
11746
  if (value2 && value2.trim()) return value2.trim();
@@ -11745,8 +11754,8 @@ function defaultRun(command, path) {
11745
11754
  return result.status ?? 0;
11746
11755
  }
11747
11756
  function editText(initial, slug, deps = {}) {
11748
- const env = deps.env ?? process13.env;
11749
- const interactive = deps.interactive ?? (() => Boolean(process13.stdin.isTTY));
11757
+ const env = deps.env ?? process14.env;
11758
+ const interactive = deps.interactive ?? (() => Boolean(process14.stdin.isTTY));
11750
11759
  const editor = resolveEditor(env);
11751
11760
  if (!editor)
11752
11761
  throw new Error(
@@ -12737,4 +12746,4 @@ export {
12737
12746
  exitCodeFor,
12738
12747
  runCli
12739
12748
  };
12740
- //# sourceMappingURL=chunk-4JWDJN3P.js.map
12749
+ //# sourceMappingURL=chunk-JWEBGIBR.js.map