@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/README.md +6 -6
- package/REQUIREMENTS.md +6 -4
- package/dist/bin.cjs +133 -123
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-DS64H4FY.js → chunk-JWEBGIBR.js} +133 -123
- package/dist/chunk-JWEBGIBR.js.map +1 -0
- package/dist/index.cjs +133 -123
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/skills/odla/SKILL.md +10 -8
- package/skills/odla-migrate/references/phase-2-db.md +3 -1
- package/skills/odla-migrate/references/troubleshooting.md +4 -3
- package/dist/chunk-DS64H4FY.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -96,18 +96,22 @@ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${_
|
|
|
96
96
|
var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
97
97
|
|
|
98
98
|
// src/admin-ai.ts
|
|
99
|
-
var
|
|
99
|
+
var import_node_process7 = __toESM(require("process"), 1);
|
|
100
100
|
|
|
101
101
|
// src/token.ts
|
|
102
102
|
var import_db = require("@odla-ai/db");
|
|
103
|
-
var
|
|
103
|
+
var import_node_process4 = __toESM(require("process"), 1);
|
|
104
|
+
|
|
105
|
+
// src/handshake-approval.ts
|
|
106
|
+
var import_node_process2 = __toESM(require("process"), 1);
|
|
104
107
|
|
|
105
108
|
// src/approval-prompt.ts
|
|
106
109
|
function approvalLines(prompt) {
|
|
107
110
|
const lines = [""];
|
|
108
111
|
lines.push(
|
|
109
|
-
`\u23F8
|
|
112
|
+
`\u23F8 AUTHORIZATION REQUIRED${prompt.purpose ? ` to ${prompt.purpose}` : ""} \u2014 code ${prompt.userCode}, ${prompt.minutesLeft}m left.`
|
|
110
113
|
);
|
|
114
|
+
lines.push(" OPEN THIS URL IN THE APPROVER'S SIGNED-IN BROWSER NOW:");
|
|
111
115
|
lines.push("");
|
|
112
116
|
lines.push(` ${prompt.approvalUrl}`);
|
|
113
117
|
lines.push("");
|
|
@@ -121,8 +125,9 @@ function approvalLines(prompt) {
|
|
|
121
125
|
lines.push(` No browser was opened (${prompt.browserSkipped}).`);
|
|
122
126
|
}
|
|
123
127
|
lines.push("");
|
|
124
|
-
lines.push(" AGENTS:
|
|
125
|
-
lines.push("
|
|
128
|
+
lines.push(" AGENTS: use browser control to open the URL above now; do not wait silently.");
|
|
129
|
+
lines.push(" If browser control is unavailable, give the exact URL to the human verbatim.");
|
|
130
|
+
lines.push(" You cannot approve it yourself, retry it away, or start a substitute handshake.");
|
|
126
131
|
lines.push("");
|
|
127
132
|
return lines;
|
|
128
133
|
}
|
|
@@ -136,10 +141,69 @@ function reminderLines(prompt) {
|
|
|
136
141
|
];
|
|
137
142
|
}
|
|
138
143
|
|
|
144
|
+
// src/open.ts
|
|
145
|
+
var import_node_child_process = require("child_process");
|
|
146
|
+
var import_node_process = __toESM(require("process"), 1);
|
|
147
|
+
async function openUrl(url, options = {}) {
|
|
148
|
+
const command = openerFor(options.platform ?? import_node_process.default.platform);
|
|
149
|
+
const doSpawn = options.spawnImpl ?? import_node_child_process.spawn;
|
|
150
|
+
await new Promise((resolve13, reject) => {
|
|
151
|
+
const child = doSpawn(command.cmd, [...command.args, url], {
|
|
152
|
+
stdio: "ignore",
|
|
153
|
+
detached: true
|
|
154
|
+
});
|
|
155
|
+
child.once("error", reject);
|
|
156
|
+
child.once("spawn", () => {
|
|
157
|
+
child.unref();
|
|
158
|
+
resolve13();
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
function openerFor(platform) {
|
|
163
|
+
if (platform === "darwin") return { cmd: "open", args: [] };
|
|
164
|
+
if (platform === "win32") return { cmd: "cmd", args: ["/c", "start", ""] };
|
|
165
|
+
return { cmd: "xdg-open", args: [] };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// src/handshake-approval.ts
|
|
169
|
+
function approvalBrowser(options, host = {}) {
|
|
170
|
+
if (options.open === false) return { open: false, reason: "disabled by --no-open" };
|
|
171
|
+
if (options.open === true) return { open: true, mode: "forced" };
|
|
172
|
+
const env = host.env ?? import_node_process2.default.env;
|
|
173
|
+
if (env.VITEST || env.NODE_ENV === "test") {
|
|
174
|
+
return { open: false, reason: "test environment" };
|
|
175
|
+
}
|
|
176
|
+
return { open: true, mode: "auto" };
|
|
177
|
+
}
|
|
178
|
+
async function presentHandshakeApproval(out, prompt, options) {
|
|
179
|
+
const browser = approvalBrowser(options);
|
|
180
|
+
printApproval(out, {
|
|
181
|
+
...prompt,
|
|
182
|
+
browserAttempted: browser.open,
|
|
183
|
+
browserSkipped: browser.reason
|
|
184
|
+
});
|
|
185
|
+
if (!browser.open) {
|
|
186
|
+
out.error(`auth: browser launch skipped (${browser.reason}) \u2014 open the URL above manually`);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
await (options.openApprovalUrl ?? openUrl)(prompt.approvalUrl);
|
|
191
|
+
out.error("auth: browser open requested for the approval URL above");
|
|
192
|
+
} catch (error) {
|
|
193
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
194
|
+
out.error(`auth: browser did not open (${reason}) \u2014 OPEN THE URL ABOVE NOW`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function handshakeUrl(platformUrl, userCode) {
|
|
198
|
+
const url = new URL("/studio", platformUrl);
|
|
199
|
+
url.searchParams.set("code", userCode);
|
|
200
|
+
return url.toString();
|
|
201
|
+
}
|
|
202
|
+
|
|
139
203
|
// src/handshake-state.ts
|
|
140
204
|
var import_node_fs2 = require("fs");
|
|
141
205
|
var import_node_path2 = require("path");
|
|
142
|
-
var
|
|
206
|
+
var import_node_process3 = __toESM(require("process"), 1);
|
|
143
207
|
|
|
144
208
|
// src/local.ts
|
|
145
209
|
var import_node_fs = require("fs");
|
|
@@ -275,9 +339,13 @@ var RESUME_MARGIN_MS = 5e3;
|
|
|
275
339
|
function readPendingHandshake(path, platform, email) {
|
|
276
340
|
const pending = readJsonFile(path);
|
|
277
341
|
if (!pending || pending.platform !== platform || pending.email !== email) return null;
|
|
278
|
-
if (typeof pending.userCode !== "string" || typeof pending.deviceCode !== "string"
|
|
342
|
+
if (typeof pending.userCode !== "string" || typeof pending.deviceCode !== "string") return null;
|
|
279
343
|
if (typeof pending.expiresAt !== "number" || pending.expiresAt <= Date.now() + RESUME_MARGIN_MS) return null;
|
|
280
|
-
return {
|
|
344
|
+
return {
|
|
345
|
+
interval: typeof pending.interval === "number" ? pending.interval : 3,
|
|
346
|
+
...pending,
|
|
347
|
+
approvalUrl: handshakeUrl(platform, pending.userCode)
|
|
348
|
+
};
|
|
281
349
|
}
|
|
282
350
|
function writePendingHandshake(path, pending) {
|
|
283
351
|
writePrivateJson(path, pending);
|
|
@@ -303,47 +371,23 @@ function approvalReminder(out, pending, periodMs = 3e4) {
|
|
|
303
371
|
timer.unref?.();
|
|
304
372
|
return () => clearInterval(timer);
|
|
305
373
|
}
|
|
306
|
-
function handshakeWaitMs(waitSeconds, interactive =
|
|
374
|
+
function handshakeWaitMs(waitSeconds, interactive = import_node_process3.default.stdout.isTTY === true) {
|
|
307
375
|
if (waitSeconds !== void 0) return waitSeconds * 1e3;
|
|
308
376
|
return interactive ? void 0 : 9e4;
|
|
309
377
|
}
|
|
310
378
|
|
|
311
|
-
// src/open.ts
|
|
312
|
-
var import_node_child_process = require("child_process");
|
|
313
|
-
var import_node_process2 = __toESM(require("process"), 1);
|
|
314
|
-
async function openUrl(url, options = {}) {
|
|
315
|
-
const command = openerFor(options.platform ?? import_node_process2.default.platform);
|
|
316
|
-
const doSpawn = options.spawnImpl ?? import_node_child_process.spawn;
|
|
317
|
-
await new Promise((resolve13, reject) => {
|
|
318
|
-
const child = doSpawn(command.cmd, [...command.args, url], {
|
|
319
|
-
stdio: "ignore",
|
|
320
|
-
detached: true
|
|
321
|
-
});
|
|
322
|
-
child.once("error", reject);
|
|
323
|
-
child.once("spawn", () => {
|
|
324
|
-
child.unref();
|
|
325
|
-
resolve13();
|
|
326
|
-
});
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
function openerFor(platform) {
|
|
330
|
-
if (platform === "darwin") return { cmd: "open", args: [] };
|
|
331
|
-
if (platform === "win32") return { cmd: "cmd", args: ["/c", "start", ""] };
|
|
332
|
-
return { cmd: "xdg-open", args: [] };
|
|
333
|
-
}
|
|
334
|
-
|
|
335
379
|
// src/token.ts
|
|
336
380
|
async function getDeveloperToken(cfg, options, doFetch, out) {
|
|
337
381
|
if (options.token) return options.token;
|
|
338
382
|
const audience = platformAudience(cfg.platformUrl);
|
|
339
|
-
if (
|
|
340
|
-
const declared =
|
|
383
|
+
if (import_node_process4.default.env.ODLA_DEV_TOKEN) {
|
|
384
|
+
const declared = import_node_process4.default.env.ODLA_DEV_TOKEN_AUDIENCE;
|
|
341
385
|
if (declared) {
|
|
342
386
|
if (platformAudience(declared) !== audience) throw new Error("ODLA_DEV_TOKEN_AUDIENCE does not match the configured platform");
|
|
343
387
|
} else if (audience !== "https://odla.ai") {
|
|
344
388
|
throw new Error("ODLA_DEV_TOKEN_AUDIENCE is required for a non-default platform");
|
|
345
389
|
}
|
|
346
|
-
return
|
|
390
|
+
return import_node_process4.default.env.ODLA_DEV_TOKEN;
|
|
347
391
|
}
|
|
348
392
|
const cached = readJsonFile(cfg.local.tokenFile);
|
|
349
393
|
if (cached?.token && cached.platform === audience && (cached.expiresAt ?? 0) > Date.now() + 6e4) {
|
|
@@ -356,7 +400,6 @@ async function getDeveloperToken(cfg, options, doFetch, out) {
|
|
|
356
400
|
doFetch,
|
|
357
401
|
out,
|
|
358
402
|
audience,
|
|
359
|
-
browser: approvalBrowser(options),
|
|
360
403
|
email: handshakeEmail(options.email, cached?.platform === audience ? cached.email : void 0),
|
|
361
404
|
pendingFile: handshakeFile(cfg)
|
|
362
405
|
};
|
|
@@ -372,7 +415,12 @@ async function resumePendingHandshake(ctx, waitMs) {
|
|
|
372
415
|
if (!pending) return null;
|
|
373
416
|
ctx.out.error("");
|
|
374
417
|
ctx.out.error(`auth: resuming pending handshake \u2014 ${approvalHint(pending)}`);
|
|
375
|
-
await
|
|
418
|
+
await presentHandshakeApproval(ctx.out, {
|
|
419
|
+
userCode: pending.userCode,
|
|
420
|
+
approvalUrl: pending.approvalUrl,
|
|
421
|
+
minutesLeft: Math.max(1, Math.floor((pending.expiresAt - Date.now()) / 6e4)),
|
|
422
|
+
purpose: `sign this terminal in as ${ctx.email}`
|
|
423
|
+
}, ctx.options);
|
|
376
424
|
ctx.out.error("");
|
|
377
425
|
const stopReminder = approvalReminder(ctx.out, pending);
|
|
378
426
|
try {
|
|
@@ -406,10 +454,11 @@ async function freshHandshake(ctx, waitMs) {
|
|
|
406
454
|
endpoint: ctx.cfg.platformUrl,
|
|
407
455
|
email: ctx.email,
|
|
408
456
|
label: `${ctx.cfg.app.id} provisioner`,
|
|
457
|
+
projectIds: [ctx.cfg.app.id],
|
|
409
458
|
fetch: ctx.doFetch,
|
|
410
459
|
waitMs,
|
|
411
|
-
onCode: async ({ userCode, deviceCode, expiresIn, interval
|
|
412
|
-
const approvalUrl =
|
|
460
|
+
onCode: async ({ userCode, deviceCode, expiresIn, interval }) => {
|
|
461
|
+
const approvalUrl = handshakeUrl(ctx.audience, userCode);
|
|
413
462
|
started = {
|
|
414
463
|
platform: ctx.audience,
|
|
415
464
|
email: ctx.email,
|
|
@@ -420,15 +469,12 @@ async function freshHandshake(ctx, waitMs) {
|
|
|
420
469
|
expiresAt: Date.now() + expiresIn * 1e3
|
|
421
470
|
};
|
|
422
471
|
writePendingHandshake(ctx.pendingFile, started);
|
|
423
|
-
|
|
472
|
+
await presentHandshakeApproval(ctx.out, {
|
|
424
473
|
userCode,
|
|
425
474
|
approvalUrl,
|
|
426
475
|
minutesLeft: Math.floor(expiresIn / 60),
|
|
427
|
-
purpose: `sign this terminal in as ${ctx.email}
|
|
428
|
-
|
|
429
|
-
browserSkipped: ctx.browser.reason
|
|
430
|
-
});
|
|
431
|
-
await launchApproval(ctx, approvalUrl);
|
|
476
|
+
purpose: `sign this terminal in as ${ctx.email}`
|
|
477
|
+
}, ctx.options);
|
|
432
478
|
stopReminder = approvalReminder(ctx.out, started);
|
|
433
479
|
}
|
|
434
480
|
});
|
|
@@ -443,18 +489,6 @@ async function freshHandshake(ctx, waitMs) {
|
|
|
443
489
|
stopReminder?.();
|
|
444
490
|
}
|
|
445
491
|
}
|
|
446
|
-
async function launchApproval(ctx, approvalUrl) {
|
|
447
|
-
if (ctx.browser.open) {
|
|
448
|
-
try {
|
|
449
|
-
await (ctx.options.openApprovalUrl ?? openUrl)(approvalUrl);
|
|
450
|
-
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`);
|
|
451
|
-
} catch (err) {
|
|
452
|
-
ctx.out.error(`auth: could not open browser (${err instanceof Error ? err.message : String(err)})`);
|
|
453
|
-
}
|
|
454
|
-
} else if (ctx.browser.reason) {
|
|
455
|
-
ctx.out.error(`auth: browser launch skipped (${ctx.browser.reason}) \u2014 show the human the URL above`);
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
492
|
function stillPending(pending, email) {
|
|
459
493
|
return new import_db.OdlaError(
|
|
460
494
|
"handshake_pending",
|
|
@@ -463,30 +497,12 @@ function stillPending(pending, email) {
|
|
|
463
497
|
);
|
|
464
498
|
}
|
|
465
499
|
function handshakeEmail(value2, cached) {
|
|
466
|
-
const email = (value2 ??
|
|
500
|
+
const email = (value2 ?? import_node_process4.default.env.ODLA_USER_EMAIL ?? cached ?? "").trim().toLowerCase();
|
|
467
501
|
if (email.length > 254 || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
468
502
|
throw new Error("a fresh odla handshake requires --email <account> or ODLA_USER_EMAIL");
|
|
469
503
|
}
|
|
470
504
|
return email;
|
|
471
505
|
}
|
|
472
|
-
function approvalBrowser(options, host = {}) {
|
|
473
|
-
if (options.open === true) return { open: true, mode: "forced" };
|
|
474
|
-
if (options.open === false) return { open: false, reason: "disabled by --no-open" };
|
|
475
|
-
const env = host.env ?? import_node_process3.default.env;
|
|
476
|
-
if (env.VITEST || env.NODE_ENV === "test") return { open: false, reason: "test environment" };
|
|
477
|
-
if (env.CI) return { open: false, reason: "CI environment" };
|
|
478
|
-
if (env.SSH_CONNECTION || env.SSH_TTY) return { open: false, reason: "SSH session; pass --open to force" };
|
|
479
|
-
const platform = host.platform ?? import_node_process3.default.platform;
|
|
480
|
-
if (platform !== "darwin" && platform !== "win32" && !env.DISPLAY && !env.WAYLAND_DISPLAY) {
|
|
481
|
-
return { open: false, reason: "no graphical display; pass --open to force" };
|
|
482
|
-
}
|
|
483
|
-
return { open: true, mode: "auto" };
|
|
484
|
-
}
|
|
485
|
-
function handshakeUrl(platformUrl, userCode) {
|
|
486
|
-
const url = new URL("/studio", platformUrl);
|
|
487
|
-
url.searchParams.set("code", userCode);
|
|
488
|
-
return url.toString();
|
|
489
|
-
}
|
|
490
506
|
function platformAudience(value2) {
|
|
491
507
|
let url;
|
|
492
508
|
try {
|
|
@@ -505,12 +521,12 @@ function platformAudience(value2) {
|
|
|
505
521
|
}
|
|
506
522
|
|
|
507
523
|
// src/secret-input.ts
|
|
508
|
-
var
|
|
524
|
+
var import_node_process5 = __toESM(require("process"), 1);
|
|
509
525
|
var MAX_BYTES = 64 * 1024;
|
|
510
526
|
async function secretInputValue(options, kind = "credential") {
|
|
511
527
|
if (options.fromEnv && options.stdin) throw new Error("choose exactly one of --from-env or --stdin");
|
|
512
528
|
let value2;
|
|
513
|
-
if (options.fromEnv) value2 =
|
|
529
|
+
if (options.fromEnv) value2 = import_node_process5.default.env[options.fromEnv];
|
|
514
530
|
else if (options.stdin) value2 = await (options.readStdin ?? (() => readSecretStream(kind)))();
|
|
515
531
|
else throw new Error(`${kind} input required: use --from-env <NAME> or --stdin; values are never accepted as arguments`);
|
|
516
532
|
value2 = value2?.replace(/[\r\n]+$/, "");
|
|
@@ -518,7 +534,7 @@ async function secretInputValue(options, kind = "credential") {
|
|
|
518
534
|
if (new TextEncoder().encode(value2).byteLength > MAX_BYTES) throw new Error(`${kind} exceeds 64 KiB`);
|
|
519
535
|
return value2;
|
|
520
536
|
}
|
|
521
|
-
async function readSecretStream(kind, stream =
|
|
537
|
+
async function readSecretStream(kind, stream = import_node_process5.default.stdin) {
|
|
522
538
|
let value2 = "";
|
|
523
539
|
for await (const chunk of stream) {
|
|
524
540
|
value2 += String(chunk);
|
|
@@ -530,7 +546,7 @@ async function readSecretStream(kind, stream = import_node_process4.default.stdi
|
|
|
530
546
|
// src/admin-ai-auth.ts
|
|
531
547
|
var import_node_fs3 = require("fs");
|
|
532
548
|
var import_node_path3 = require("path");
|
|
533
|
-
var
|
|
549
|
+
var import_node_process6 = __toESM(require("process"), 1);
|
|
534
550
|
var import_db2 = require("@odla-ai/db");
|
|
535
551
|
async function getScopedPlatformToken(options) {
|
|
536
552
|
return resolveAdminPlatformToken(options);
|
|
@@ -538,7 +554,7 @@ async function getScopedPlatformToken(options) {
|
|
|
538
554
|
async function resolveAdminPlatformToken(options) {
|
|
539
555
|
const audience = platformAudience(options.platform);
|
|
540
556
|
if (options.token) return options.token;
|
|
541
|
-
const fromEnv =
|
|
557
|
+
const fromEnv = import_node_process6.default.env.ODLA_ADMIN_TOKEN;
|
|
542
558
|
if (fromEnv) return audienceBoundEnvToken(fromEnv, audience);
|
|
543
559
|
return scopedToken(
|
|
544
560
|
audience,
|
|
@@ -550,7 +566,7 @@ async function resolveAdminPlatformToken(options) {
|
|
|
550
566
|
}
|
|
551
567
|
function audienceBoundEnvToken(token, platform) {
|
|
552
568
|
const audience = platformAudience(platform);
|
|
553
|
-
const declared =
|
|
569
|
+
const declared = import_node_process6.default.env.ODLA_ADMIN_TOKEN_AUDIENCE;
|
|
554
570
|
if (declared) {
|
|
555
571
|
if (platformAudience(declared) !== audience) throw new Error("ODLA_ADMIN_TOKEN_AUDIENCE does not match the configured platform");
|
|
556
572
|
} else if (audience !== "https://odla.ai") {
|
|
@@ -575,7 +591,7 @@ var SCOPE_PURPOSE = {
|
|
|
575
591
|
};
|
|
576
592
|
async function scopedToken(platform, scope, options, doFetch, out) {
|
|
577
593
|
const audience = platformAudience(platform);
|
|
578
|
-
const rootDir = options.rootDir ??
|
|
594
|
+
const rootDir = options.rootDir ?? import_node_process6.default.cwd();
|
|
579
595
|
const tokenFile = options.tokenFile ?? (0, import_node_path3.join)(rootDir, ".odla/admin-token.local.json");
|
|
580
596
|
const cache = options.cache === false ? null : readJsonFile(tokenFile);
|
|
581
597
|
const cached = cache?.platform === audience ? cache.tokens?.[scope] : void 0;
|
|
@@ -590,21 +606,14 @@ async function scopedToken(platform, scope, options, doFetch, out) {
|
|
|
590
606
|
label: options.label ?? `odla CLI (${scope})`,
|
|
591
607
|
scopes: [scope],
|
|
592
608
|
fetch: doFetch,
|
|
593
|
-
onCode: async ({ userCode, expiresIn
|
|
594
|
-
|
|
595
|
-
const browser = approvalBrowser({ open: options.open });
|
|
596
|
-
printApproval(out, {
|
|
609
|
+
onCode: async ({ userCode, expiresIn }) => {
|
|
610
|
+
await presentHandshakeApproval(out, {
|
|
597
611
|
userCode,
|
|
598
|
-
approvalUrl,
|
|
612
|
+
approvalUrl: handshakeUrl(audience, userCode),
|
|
599
613
|
minutesLeft: Math.floor(expiresIn / 60),
|
|
600
614
|
purpose: SCOPE_PURPOSE[scope] ?? `use ${scope}`,
|
|
601
|
-
approver: scope.startsWith("app:") ? "A signed-in app owner" : "A signed-in odla platform admin"
|
|
602
|
-
|
|
603
|
-
browserSkipped: browser.reason
|
|
604
|
-
});
|
|
605
|
-
if (browser.open) {
|
|
606
|
-
await (options.openApprovalUrl ?? openUrl)(approvalUrl).catch(() => void 0);
|
|
607
|
-
}
|
|
615
|
+
approver: scope.startsWith("app:") ? "A signed-in app owner" : "A signed-in odla platform admin"
|
|
616
|
+
}, options);
|
|
608
617
|
}
|
|
609
618
|
});
|
|
610
619
|
if (options.cache !== false) {
|
|
@@ -787,7 +796,7 @@ function isRecord2(value2) {
|
|
|
787
796
|
|
|
788
797
|
// src/admin-ai.ts
|
|
789
798
|
async function adminAi(options) {
|
|
790
|
-
const platform = platformAudience(options.platform ??
|
|
799
|
+
const platform = platformAudience(options.platform ?? import_node_process7.default.env.ODLA_PLATFORM ?? "https://odla.ai");
|
|
791
800
|
const doFetch = options.fetch ?? fetch;
|
|
792
801
|
const out = options.stdout ?? console;
|
|
793
802
|
const usageQuery = options.action === "usage" ? adminAiUsageQuery(options) : void 0;
|
|
@@ -1044,7 +1053,7 @@ function addOption(options, name, value2) {
|
|
|
1044
1053
|
// src/operator-context.ts
|
|
1045
1054
|
var import_node_fs6 = require("fs");
|
|
1046
1055
|
var import_node_path6 = require("path");
|
|
1047
|
-
var
|
|
1056
|
+
var import_node_process9 = __toESM(require("process"), 1);
|
|
1048
1057
|
|
|
1049
1058
|
// src/config.ts
|
|
1050
1059
|
var import_node_fs4 = require("fs");
|
|
@@ -1350,15 +1359,15 @@ function unique2(values) {
|
|
|
1350
1359
|
var import_node_fs5 = require("fs");
|
|
1351
1360
|
var import_node_os = require("os");
|
|
1352
1361
|
var import_node_path5 = require("path");
|
|
1353
|
-
var
|
|
1362
|
+
var import_node_process8 = __toESM(require("process"), 1);
|
|
1354
1363
|
function operatorProfileFile() {
|
|
1355
1364
|
return (0, import_node_path5.resolve)(
|
|
1356
|
-
clean(
|
|
1365
|
+
clean(import_node_process8.default.env.ODLA_CONTEXT_FILE) ?? (0, import_node_path5.join)((0, import_node_os.homedir)(), ".odla", "contexts.json")
|
|
1357
1366
|
);
|
|
1358
1367
|
}
|
|
1359
1368
|
function resolveOperatorProfile(parsed) {
|
|
1360
1369
|
const fromFlag = clean(stringOpt(parsed.options.context));
|
|
1361
|
-
const fromEnvironment = clean(
|
|
1370
|
+
const fromEnvironment = clean(import_node_process8.default.env.ODLA_CONTEXT);
|
|
1362
1371
|
const name = fromFlag ?? fromEnvironment ?? null;
|
|
1363
1372
|
const file = operatorProfileFile();
|
|
1364
1373
|
if (!name) {
|
|
@@ -1487,13 +1496,13 @@ async function resolveOperatorContext(parsed, options = {}) {
|
|
|
1487
1496
|
}
|
|
1488
1497
|
const loaded = hasConfig ? await loadProjectConfig(configArgument) : void 0;
|
|
1489
1498
|
const platformFlag = clean2(stringOpt(parsed.options.platform));
|
|
1490
|
-
const platformEnvironment = clean2(
|
|
1499
|
+
const platformEnvironment = clean2(import_node_process9.default.env.ODLA_PLATFORM_URL);
|
|
1491
1500
|
const platformValue = platformAudience(
|
|
1492
1501
|
platformFlag ?? platformEnvironment ?? profile.value?.platform ?? loaded?.platformUrl ?? DEFAULT_PLATFORM2
|
|
1493
1502
|
);
|
|
1494
1503
|
const platformSource = platformFlag ? "flag" : platformEnvironment ? "environment" : profile.value ? "profile" : loaded ? "config" : "default";
|
|
1495
1504
|
const appFlag = clean2(stringOpt(parsed.options.app));
|
|
1496
|
-
const appEnvironment = clean2(
|
|
1505
|
+
const appEnvironment = clean2(import_node_process9.default.env.ODLA_APP_ID);
|
|
1497
1506
|
const appValue = appFlag ?? appEnvironment ?? profile.value?.app ?? loaded?.app.id ?? null;
|
|
1498
1507
|
const appSource = appFlag ? "flag" : appEnvironment ? "environment" : profile.value?.app ? "profile" : loaded ? "config" : "unresolved";
|
|
1499
1508
|
if (appValue) assertOperatorName(appValue, "app");
|
|
@@ -1503,16 +1512,16 @@ async function resolveOperatorContext(parsed, options = {}) {
|
|
|
1503
1512
|
);
|
|
1504
1513
|
}
|
|
1505
1514
|
const envFlag = clean2(stringOpt(parsed.options.env));
|
|
1506
|
-
const envEnvironment = clean2(
|
|
1515
|
+
const envEnvironment = clean2(import_node_process9.default.env.ODLA_ENV);
|
|
1507
1516
|
const environmentValue = envFlag ?? envEnvironment ?? profile.value?.environment ?? options.defaultEnvironment ?? null;
|
|
1508
1517
|
const environmentSource = envFlag ? "flag" : envEnvironment ? "environment" : profile.value?.environment ? "profile" : options.defaultEnvironment ? "default" : "unresolved";
|
|
1509
1518
|
if (environmentValue) {
|
|
1510
1519
|
assertOperatorName(environmentValue, "environment");
|
|
1511
1520
|
}
|
|
1512
|
-
const rootDir = loaded?.rootDir ??
|
|
1521
|
+
const rootDir = loaded?.rootDir ?? import_node_process9.default.cwd();
|
|
1513
1522
|
const profileCredentials = operatorCredentialFiles(profile);
|
|
1514
|
-
const tokenFile = clean2(
|
|
1515
|
-
const scopedTokenFile = clean2(
|
|
1523
|
+
const tokenFile = clean2(import_node_process9.default.env.ODLA_DEV_TOKEN_FILE) ? (0, import_node_path6.resolve)(import_node_process9.default.env.ODLA_DEV_TOKEN_FILE) : profile.name ? profileCredentials.developer : loaded?.local.tokenFile ?? profileCredentials.developer;
|
|
1524
|
+
const scopedTokenFile = clean2(import_node_process9.default.env.ODLA_ADMIN_TOKEN_FILE) ? (0, import_node_path6.resolve)(import_node_process9.default.env.ODLA_ADMIN_TOKEN_FILE) : profile.name ? profileCredentials.scoped : loaded ? (0, import_node_path6.join)(loaded.rootDir, ".odla", "admin-token.local.json") : profileCredentials.scoped;
|
|
1516
1525
|
const cfg = loaded ? {
|
|
1517
1526
|
...loaded,
|
|
1518
1527
|
platformUrl: platformValue,
|
|
@@ -8564,13 +8573,13 @@ async function codeCommand(parsed, dependencies) {
|
|
|
8564
8573
|
}
|
|
8565
8574
|
|
|
8566
8575
|
// src/operator-credentials.ts
|
|
8567
|
-
var
|
|
8576
|
+
var import_node_process10 = __toESM(require("process"), 1);
|
|
8568
8577
|
function developerTokenStatus(context, parsed, now = Date.now()) {
|
|
8569
8578
|
const cached = readJsonFile(context.cfg.local.tokenFile);
|
|
8570
8579
|
const cacheStatus = !cached?.token ? "missing" : cached.platform !== context.platform.value ? "other-platform" : (cached.expiresAt ?? 0) <= now + 6e4 ? "expired" : "valid";
|
|
8571
8580
|
const source = clean3(
|
|
8572
8581
|
stringOpt(parsed.options.token)
|
|
8573
|
-
) ? "flag" : clean3(
|
|
8582
|
+
) ? "flag" : clean3(import_node_process10.default.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
|
|
8574
8583
|
return {
|
|
8575
8584
|
source,
|
|
8576
8585
|
cacheFile: context.cfg.local.tokenFile,
|
|
@@ -8944,15 +8953,16 @@ Safety:
|
|
|
8944
8953
|
the metadata file. Flags and specific ODLA_* scope variables beat a selected
|
|
8945
8954
|
context, which beats project config. There is no ambient current context.
|
|
8946
8955
|
"context show" reports only provenance and cache state and never authenticates.
|
|
8947
|
-
|
|
8948
|
-
|
|
8949
|
-
|
|
8950
|
-
|
|
8951
|
-
|
|
8956
|
+
Every real CLI handshake prints one canonical /studio?code= approval URL and
|
|
8957
|
+
attempts to open it \u2014 including in CI, SSH, display-less, and agent-driven
|
|
8958
|
+
shells. Only --no-open suppresses the attempt. Browser launch is best-effort:
|
|
8959
|
+
agents with browser control must open that exact URL immediately; otherwise
|
|
8960
|
+
they must give it to the human verbatim. A started handshake is persisted
|
|
8952
8961
|
under .odla/, so a command killed mid-wait loses nothing \u2014 rerunning resumes
|
|
8953
8962
|
the same code. Outside an interactive terminal the wait is capped (90s by
|
|
8954
8963
|
default, --wait <seconds> to change); a still-pending handshake then exits
|
|
8955
|
-
with code 75:
|
|
8964
|
+
with code 75: open the same URL (or relay it if browser control is unavailable),
|
|
8965
|
+
wait for approval, and re-run to collect.
|
|
8956
8966
|
A fresh device handshake requires --email <odla-account> or ODLA_USER_EMAIL.
|
|
8957
8967
|
The email is a non-secret identity hint: never provide a password or session
|
|
8958
8968
|
token. The matching account must already exist, be signed in, explicitly
|
|
@@ -10361,7 +10371,7 @@ async function read2(url, headers, doFetch) {
|
|
|
10361
10371
|
// src/provision.ts
|
|
10362
10372
|
var import_apps12 = require("@odla-ai/apps");
|
|
10363
10373
|
var import_ai3 = require("@odla-ai/ai");
|
|
10364
|
-
var
|
|
10374
|
+
var import_node_process11 = __toESM(require("process"), 1);
|
|
10365
10375
|
|
|
10366
10376
|
// src/integration-provision.ts
|
|
10367
10377
|
var import_db3 = require("@odla-ai/db");
|
|
@@ -10668,7 +10678,7 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
|
|
|
10668
10678
|
await provisionIntegrationSeeds(doFetch, cfg.dbEndpoint, tenantId, dbKey, database.integrations, env, out);
|
|
10669
10679
|
}
|
|
10670
10680
|
if (cfg.services.includes("ai") && cfg.ai?.provider && cfg.ai.keyEnv) {
|
|
10671
|
-
const key =
|
|
10681
|
+
const key = import_node_process11.default.env[cfg.ai.keyEnv];
|
|
10672
10682
|
if (key) {
|
|
10673
10683
|
const secretName = cfg.ai.secretName ?? defaultSecretName(cfg.ai.provider);
|
|
10674
10684
|
await (0, import_ai3.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, secretName, key);
|
|
@@ -10710,7 +10720,7 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
|
|
|
10710
10720
|
|
|
10711
10721
|
// src/record.ts
|
|
10712
10722
|
var import_node_fs16 = require("fs");
|
|
10713
|
-
var
|
|
10723
|
+
var import_node_process12 = __toESM(require("process"), 1);
|
|
10714
10724
|
|
|
10715
10725
|
// src/surface.ts
|
|
10716
10726
|
var PM_ACTIONS = {
|
|
@@ -10868,7 +10878,7 @@ function surfacePaths(node = COMMAND_SURFACE, prefix = []) {
|
|
|
10868
10878
|
|
|
10869
10879
|
// src/record.ts
|
|
10870
10880
|
function recordInvocation(parsed) {
|
|
10871
|
-
const file =
|
|
10881
|
+
const file = import_node_process12.default.env.ODLA_CLI_RECORD;
|
|
10872
10882
|
if (!file) return;
|
|
10873
10883
|
try {
|
|
10874
10884
|
const entry = {
|
|
@@ -11540,9 +11550,9 @@ var import_node_child_process8 = require("child_process");
|
|
|
11540
11550
|
var import_node_fs20 = require("fs");
|
|
11541
11551
|
var import_node_os5 = require("os");
|
|
11542
11552
|
var import_node_path18 = require("path");
|
|
11543
|
-
var
|
|
11553
|
+
var import_node_process13 = __toESM(require("process"), 1);
|
|
11544
11554
|
var EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
|
|
11545
|
-
function resolveEditor(env =
|
|
11555
|
+
function resolveEditor(env = import_node_process13.default.env) {
|
|
11546
11556
|
for (const name of EDITOR_ENV) {
|
|
11547
11557
|
const value2 = env[name];
|
|
11548
11558
|
if (value2 && value2.trim()) return value2.trim();
|
|
@@ -11556,8 +11566,8 @@ function defaultRun(command, path) {
|
|
|
11556
11566
|
return result.status ?? 0;
|
|
11557
11567
|
}
|
|
11558
11568
|
function editText(initial, slug, deps = {}) {
|
|
11559
|
-
const env = deps.env ??
|
|
11560
|
-
const interactive = deps.interactive ?? (() => Boolean(
|
|
11569
|
+
const env = deps.env ?? import_node_process13.default.env;
|
|
11570
|
+
const interactive = deps.interactive ?? (() => Boolean(import_node_process13.default.stdin.isTTY));
|
|
11561
11571
|
const editor = resolveEditor(env);
|
|
11562
11572
|
if (!editor)
|
|
11563
11573
|
throw new Error(
|