@odla-ai/cli 0.27.1 → 0.27.3
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 +10 -6
- package/REQUIREMENTS.md +10 -4
- package/dist/bin.cjs +132 -123
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-4JWDJN3P.js → chunk-JWEBGIBR.js} +132 -123
- package/dist/chunk-JWEBGIBR.js.map +1 -0
- package/dist/index.cjs +132 -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-4JWDJN3P.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 {
|
|
@@ -409,8 +457,8 @@ async function freshHandshake(ctx, waitMs) {
|
|
|
409
457
|
projectIds: [ctx.cfg.app.id],
|
|
410
458
|
fetch: ctx.doFetch,
|
|
411
459
|
waitMs,
|
|
412
|
-
onCode: async ({ userCode, deviceCode, expiresIn, interval
|
|
413
|
-
const approvalUrl =
|
|
460
|
+
onCode: async ({ userCode, deviceCode, expiresIn, interval }) => {
|
|
461
|
+
const approvalUrl = handshakeUrl(ctx.audience, userCode);
|
|
414
462
|
started = {
|
|
415
463
|
platform: ctx.audience,
|
|
416
464
|
email: ctx.email,
|
|
@@ -421,15 +469,12 @@ async function freshHandshake(ctx, waitMs) {
|
|
|
421
469
|
expiresAt: Date.now() + expiresIn * 1e3
|
|
422
470
|
};
|
|
423
471
|
writePendingHandshake(ctx.pendingFile, started);
|
|
424
|
-
|
|
472
|
+
await presentHandshakeApproval(ctx.out, {
|
|
425
473
|
userCode,
|
|
426
474
|
approvalUrl,
|
|
427
475
|
minutesLeft: Math.floor(expiresIn / 60),
|
|
428
|
-
purpose: `sign this terminal in as ${ctx.email}
|
|
429
|
-
|
|
430
|
-
browserSkipped: ctx.browser.reason
|
|
431
|
-
});
|
|
432
|
-
await launchApproval(ctx, approvalUrl);
|
|
476
|
+
purpose: `sign this terminal in as ${ctx.email}`
|
|
477
|
+
}, ctx.options);
|
|
433
478
|
stopReminder = approvalReminder(ctx.out, started);
|
|
434
479
|
}
|
|
435
480
|
});
|
|
@@ -444,18 +489,6 @@ async function freshHandshake(ctx, waitMs) {
|
|
|
444
489
|
stopReminder?.();
|
|
445
490
|
}
|
|
446
491
|
}
|
|
447
|
-
async function launchApproval(ctx, approvalUrl) {
|
|
448
|
-
if (ctx.browser.open) {
|
|
449
|
-
try {
|
|
450
|
-
await (ctx.options.openApprovalUrl ?? openUrl)(approvalUrl);
|
|
451
|
-
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`);
|
|
452
|
-
} catch (err) {
|
|
453
|
-
ctx.out.error(`auth: could not open browser (${err instanceof Error ? err.message : String(err)})`);
|
|
454
|
-
}
|
|
455
|
-
} else if (ctx.browser.reason) {
|
|
456
|
-
ctx.out.error(`auth: browser launch skipped (${ctx.browser.reason}) \u2014 show the human the URL above`);
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
492
|
function stillPending(pending, email) {
|
|
460
493
|
return new import_db.OdlaError(
|
|
461
494
|
"handshake_pending",
|
|
@@ -464,30 +497,12 @@ function stillPending(pending, email) {
|
|
|
464
497
|
);
|
|
465
498
|
}
|
|
466
499
|
function handshakeEmail(value2, cached) {
|
|
467
|
-
const email = (value2 ??
|
|
500
|
+
const email = (value2 ?? import_node_process4.default.env.ODLA_USER_EMAIL ?? cached ?? "").trim().toLowerCase();
|
|
468
501
|
if (email.length > 254 || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
469
502
|
throw new Error("a fresh odla handshake requires --email <account> or ODLA_USER_EMAIL");
|
|
470
503
|
}
|
|
471
504
|
return email;
|
|
472
505
|
}
|
|
473
|
-
function approvalBrowser(options, host = {}) {
|
|
474
|
-
if (options.open === true) return { open: true, mode: "forced" };
|
|
475
|
-
if (options.open === false) return { open: false, reason: "disabled by --no-open" };
|
|
476
|
-
const env = host.env ?? import_node_process3.default.env;
|
|
477
|
-
if (env.VITEST || env.NODE_ENV === "test") return { open: false, reason: "test environment" };
|
|
478
|
-
if (env.CI) return { open: false, reason: "CI environment" };
|
|
479
|
-
if (env.SSH_CONNECTION || env.SSH_TTY) return { open: false, reason: "SSH session; pass --open to force" };
|
|
480
|
-
const platform = host.platform ?? import_node_process3.default.platform;
|
|
481
|
-
if (platform !== "darwin" && platform !== "win32" && !env.DISPLAY && !env.WAYLAND_DISPLAY) {
|
|
482
|
-
return { open: false, reason: "no graphical display; pass --open to force" };
|
|
483
|
-
}
|
|
484
|
-
return { open: true, mode: "auto" };
|
|
485
|
-
}
|
|
486
|
-
function handshakeUrl(platformUrl, userCode) {
|
|
487
|
-
const url = new URL("/studio", platformUrl);
|
|
488
|
-
url.searchParams.set("code", userCode);
|
|
489
|
-
return url.toString();
|
|
490
|
-
}
|
|
491
506
|
function platformAudience(value2) {
|
|
492
507
|
let url;
|
|
493
508
|
try {
|
|
@@ -506,12 +521,12 @@ function platformAudience(value2) {
|
|
|
506
521
|
}
|
|
507
522
|
|
|
508
523
|
// src/secret-input.ts
|
|
509
|
-
var
|
|
524
|
+
var import_node_process5 = __toESM(require("process"), 1);
|
|
510
525
|
var MAX_BYTES = 64 * 1024;
|
|
511
526
|
async function secretInputValue(options, kind = "credential") {
|
|
512
527
|
if (options.fromEnv && options.stdin) throw new Error("choose exactly one of --from-env or --stdin");
|
|
513
528
|
let value2;
|
|
514
|
-
if (options.fromEnv) value2 =
|
|
529
|
+
if (options.fromEnv) value2 = import_node_process5.default.env[options.fromEnv];
|
|
515
530
|
else if (options.stdin) value2 = await (options.readStdin ?? (() => readSecretStream(kind)))();
|
|
516
531
|
else throw new Error(`${kind} input required: use --from-env <NAME> or --stdin; values are never accepted as arguments`);
|
|
517
532
|
value2 = value2?.replace(/[\r\n]+$/, "");
|
|
@@ -519,7 +534,7 @@ async function secretInputValue(options, kind = "credential") {
|
|
|
519
534
|
if (new TextEncoder().encode(value2).byteLength > MAX_BYTES) throw new Error(`${kind} exceeds 64 KiB`);
|
|
520
535
|
return value2;
|
|
521
536
|
}
|
|
522
|
-
async function readSecretStream(kind, stream =
|
|
537
|
+
async function readSecretStream(kind, stream = import_node_process5.default.stdin) {
|
|
523
538
|
let value2 = "";
|
|
524
539
|
for await (const chunk of stream) {
|
|
525
540
|
value2 += String(chunk);
|
|
@@ -531,7 +546,7 @@ async function readSecretStream(kind, stream = import_node_process4.default.stdi
|
|
|
531
546
|
// src/admin-ai-auth.ts
|
|
532
547
|
var import_node_fs3 = require("fs");
|
|
533
548
|
var import_node_path3 = require("path");
|
|
534
|
-
var
|
|
549
|
+
var import_node_process6 = __toESM(require("process"), 1);
|
|
535
550
|
var import_db2 = require("@odla-ai/db");
|
|
536
551
|
async function getScopedPlatformToken(options) {
|
|
537
552
|
return resolveAdminPlatformToken(options);
|
|
@@ -539,7 +554,7 @@ async function getScopedPlatformToken(options) {
|
|
|
539
554
|
async function resolveAdminPlatformToken(options) {
|
|
540
555
|
const audience = platformAudience(options.platform);
|
|
541
556
|
if (options.token) return options.token;
|
|
542
|
-
const fromEnv =
|
|
557
|
+
const fromEnv = import_node_process6.default.env.ODLA_ADMIN_TOKEN;
|
|
543
558
|
if (fromEnv) return audienceBoundEnvToken(fromEnv, audience);
|
|
544
559
|
return scopedToken(
|
|
545
560
|
audience,
|
|
@@ -551,7 +566,7 @@ async function resolveAdminPlatformToken(options) {
|
|
|
551
566
|
}
|
|
552
567
|
function audienceBoundEnvToken(token, platform) {
|
|
553
568
|
const audience = platformAudience(platform);
|
|
554
|
-
const declared =
|
|
569
|
+
const declared = import_node_process6.default.env.ODLA_ADMIN_TOKEN_AUDIENCE;
|
|
555
570
|
if (declared) {
|
|
556
571
|
if (platformAudience(declared) !== audience) throw new Error("ODLA_ADMIN_TOKEN_AUDIENCE does not match the configured platform");
|
|
557
572
|
} else if (audience !== "https://odla.ai") {
|
|
@@ -576,7 +591,7 @@ var SCOPE_PURPOSE = {
|
|
|
576
591
|
};
|
|
577
592
|
async function scopedToken(platform, scope, options, doFetch, out) {
|
|
578
593
|
const audience = platformAudience(platform);
|
|
579
|
-
const rootDir = options.rootDir ??
|
|
594
|
+
const rootDir = options.rootDir ?? import_node_process6.default.cwd();
|
|
580
595
|
const tokenFile = options.tokenFile ?? (0, import_node_path3.join)(rootDir, ".odla/admin-token.local.json");
|
|
581
596
|
const cache = options.cache === false ? null : readJsonFile(tokenFile);
|
|
582
597
|
const cached = cache?.platform === audience ? cache.tokens?.[scope] : void 0;
|
|
@@ -591,21 +606,14 @@ async function scopedToken(platform, scope, options, doFetch, out) {
|
|
|
591
606
|
label: options.label ?? `odla CLI (${scope})`,
|
|
592
607
|
scopes: [scope],
|
|
593
608
|
fetch: doFetch,
|
|
594
|
-
onCode: async ({ userCode, expiresIn
|
|
595
|
-
|
|
596
|
-
const browser = approvalBrowser({ open: options.open });
|
|
597
|
-
printApproval(out, {
|
|
609
|
+
onCode: async ({ userCode, expiresIn }) => {
|
|
610
|
+
await presentHandshakeApproval(out, {
|
|
598
611
|
userCode,
|
|
599
|
-
approvalUrl,
|
|
612
|
+
approvalUrl: handshakeUrl(audience, userCode),
|
|
600
613
|
minutesLeft: Math.floor(expiresIn / 60),
|
|
601
614
|
purpose: SCOPE_PURPOSE[scope] ?? `use ${scope}`,
|
|
602
|
-
approver: scope.startsWith("app:") ? "A signed-in app owner" : "A signed-in odla platform admin"
|
|
603
|
-
|
|
604
|
-
browserSkipped: browser.reason
|
|
605
|
-
});
|
|
606
|
-
if (browser.open) {
|
|
607
|
-
await (options.openApprovalUrl ?? openUrl)(approvalUrl).catch(() => void 0);
|
|
608
|
-
}
|
|
615
|
+
approver: scope.startsWith("app:") ? "A signed-in app owner" : "A signed-in odla platform admin"
|
|
616
|
+
}, options);
|
|
609
617
|
}
|
|
610
618
|
});
|
|
611
619
|
if (options.cache !== false) {
|
|
@@ -788,7 +796,7 @@ function isRecord2(value2) {
|
|
|
788
796
|
|
|
789
797
|
// src/admin-ai.ts
|
|
790
798
|
async function adminAi(options) {
|
|
791
|
-
const platform = platformAudience(options.platform ??
|
|
799
|
+
const platform = platformAudience(options.platform ?? import_node_process7.default.env.ODLA_PLATFORM ?? "https://odla.ai");
|
|
792
800
|
const doFetch = options.fetch ?? fetch;
|
|
793
801
|
const out = options.stdout ?? console;
|
|
794
802
|
const usageQuery = options.action === "usage" ? adminAiUsageQuery(options) : void 0;
|
|
@@ -1045,7 +1053,7 @@ function addOption(options, name, value2) {
|
|
|
1045
1053
|
// src/operator-context.ts
|
|
1046
1054
|
var import_node_fs6 = require("fs");
|
|
1047
1055
|
var import_node_path6 = require("path");
|
|
1048
|
-
var
|
|
1056
|
+
var import_node_process9 = __toESM(require("process"), 1);
|
|
1049
1057
|
|
|
1050
1058
|
// src/config.ts
|
|
1051
1059
|
var import_node_fs4 = require("fs");
|
|
@@ -1351,15 +1359,15 @@ function unique2(values) {
|
|
|
1351
1359
|
var import_node_fs5 = require("fs");
|
|
1352
1360
|
var import_node_os = require("os");
|
|
1353
1361
|
var import_node_path5 = require("path");
|
|
1354
|
-
var
|
|
1362
|
+
var import_node_process8 = __toESM(require("process"), 1);
|
|
1355
1363
|
function operatorProfileFile() {
|
|
1356
1364
|
return (0, import_node_path5.resolve)(
|
|
1357
|
-
clean(
|
|
1365
|
+
clean(import_node_process8.default.env.ODLA_CONTEXT_FILE) ?? (0, import_node_path5.join)((0, import_node_os.homedir)(), ".odla", "contexts.json")
|
|
1358
1366
|
);
|
|
1359
1367
|
}
|
|
1360
1368
|
function resolveOperatorProfile(parsed) {
|
|
1361
1369
|
const fromFlag = clean(stringOpt(parsed.options.context));
|
|
1362
|
-
const fromEnvironment = clean(
|
|
1370
|
+
const fromEnvironment = clean(import_node_process8.default.env.ODLA_CONTEXT);
|
|
1363
1371
|
const name = fromFlag ?? fromEnvironment ?? null;
|
|
1364
1372
|
const file = operatorProfileFile();
|
|
1365
1373
|
if (!name) {
|
|
@@ -1488,13 +1496,13 @@ async function resolveOperatorContext(parsed, options = {}) {
|
|
|
1488
1496
|
}
|
|
1489
1497
|
const loaded = hasConfig ? await loadProjectConfig(configArgument) : void 0;
|
|
1490
1498
|
const platformFlag = clean2(stringOpt(parsed.options.platform));
|
|
1491
|
-
const platformEnvironment = clean2(
|
|
1499
|
+
const platformEnvironment = clean2(import_node_process9.default.env.ODLA_PLATFORM_URL);
|
|
1492
1500
|
const platformValue = platformAudience(
|
|
1493
1501
|
platformFlag ?? platformEnvironment ?? profile.value?.platform ?? loaded?.platformUrl ?? DEFAULT_PLATFORM2
|
|
1494
1502
|
);
|
|
1495
1503
|
const platformSource = platformFlag ? "flag" : platformEnvironment ? "environment" : profile.value ? "profile" : loaded ? "config" : "default";
|
|
1496
1504
|
const appFlag = clean2(stringOpt(parsed.options.app));
|
|
1497
|
-
const appEnvironment = clean2(
|
|
1505
|
+
const appEnvironment = clean2(import_node_process9.default.env.ODLA_APP_ID);
|
|
1498
1506
|
const appValue = appFlag ?? appEnvironment ?? profile.value?.app ?? loaded?.app.id ?? null;
|
|
1499
1507
|
const appSource = appFlag ? "flag" : appEnvironment ? "environment" : profile.value?.app ? "profile" : loaded ? "config" : "unresolved";
|
|
1500
1508
|
if (appValue) assertOperatorName(appValue, "app");
|
|
@@ -1504,16 +1512,16 @@ async function resolveOperatorContext(parsed, options = {}) {
|
|
|
1504
1512
|
);
|
|
1505
1513
|
}
|
|
1506
1514
|
const envFlag = clean2(stringOpt(parsed.options.env));
|
|
1507
|
-
const envEnvironment = clean2(
|
|
1515
|
+
const envEnvironment = clean2(import_node_process9.default.env.ODLA_ENV);
|
|
1508
1516
|
const environmentValue = envFlag ?? envEnvironment ?? profile.value?.environment ?? options.defaultEnvironment ?? null;
|
|
1509
1517
|
const environmentSource = envFlag ? "flag" : envEnvironment ? "environment" : profile.value?.environment ? "profile" : options.defaultEnvironment ? "default" : "unresolved";
|
|
1510
1518
|
if (environmentValue) {
|
|
1511
1519
|
assertOperatorName(environmentValue, "environment");
|
|
1512
1520
|
}
|
|
1513
|
-
const rootDir = loaded?.rootDir ??
|
|
1521
|
+
const rootDir = loaded?.rootDir ?? import_node_process9.default.cwd();
|
|
1514
1522
|
const profileCredentials = operatorCredentialFiles(profile);
|
|
1515
|
-
const tokenFile = clean2(
|
|
1516
|
-
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;
|
|
1517
1525
|
const cfg = loaded ? {
|
|
1518
1526
|
...loaded,
|
|
1519
1527
|
platformUrl: platformValue,
|
|
@@ -8565,13 +8573,13 @@ async function codeCommand(parsed, dependencies) {
|
|
|
8565
8573
|
}
|
|
8566
8574
|
|
|
8567
8575
|
// src/operator-credentials.ts
|
|
8568
|
-
var
|
|
8576
|
+
var import_node_process10 = __toESM(require("process"), 1);
|
|
8569
8577
|
function developerTokenStatus(context, parsed, now = Date.now()) {
|
|
8570
8578
|
const cached = readJsonFile(context.cfg.local.tokenFile);
|
|
8571
8579
|
const cacheStatus = !cached?.token ? "missing" : cached.platform !== context.platform.value ? "other-platform" : (cached.expiresAt ?? 0) <= now + 6e4 ? "expired" : "valid";
|
|
8572
8580
|
const source = clean3(
|
|
8573
8581
|
stringOpt(parsed.options.token)
|
|
8574
|
-
) ? "flag" : clean3(
|
|
8582
|
+
) ? "flag" : clean3(import_node_process10.default.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
|
|
8575
8583
|
return {
|
|
8576
8584
|
source,
|
|
8577
8585
|
cacheFile: context.cfg.local.tokenFile,
|
|
@@ -8945,15 +8953,16 @@ Safety:
|
|
|
8945
8953
|
the metadata file. Flags and specific ODLA_* scope variables beat a selected
|
|
8946
8954
|
context, which beats project config. There is no ambient current context.
|
|
8947
8955
|
"context show" reports only provenance and cache state and never authenticates.
|
|
8948
|
-
|
|
8949
|
-
|
|
8950
|
-
|
|
8951
|
-
|
|
8952
|
-
|
|
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
|
|
8953
8961
|
under .odla/, so a command killed mid-wait loses nothing \u2014 rerunning resumes
|
|
8954
8962
|
the same code. Outside an interactive terminal the wait is capped (90s by
|
|
8955
8963
|
default, --wait <seconds> to change); a still-pending handshake then exits
|
|
8956
|
-
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.
|
|
8957
8966
|
A fresh device handshake requires --email <odla-account> or ODLA_USER_EMAIL.
|
|
8958
8967
|
The email is a non-secret identity hint: never provide a password or session
|
|
8959
8968
|
token. The matching account must already exist, be signed in, explicitly
|
|
@@ -10362,7 +10371,7 @@ async function read2(url, headers, doFetch) {
|
|
|
10362
10371
|
// src/provision.ts
|
|
10363
10372
|
var import_apps12 = require("@odla-ai/apps");
|
|
10364
10373
|
var import_ai3 = require("@odla-ai/ai");
|
|
10365
|
-
var
|
|
10374
|
+
var import_node_process11 = __toESM(require("process"), 1);
|
|
10366
10375
|
|
|
10367
10376
|
// src/integration-provision.ts
|
|
10368
10377
|
var import_db3 = require("@odla-ai/db");
|
|
@@ -10669,7 +10678,7 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
|
|
|
10669
10678
|
await provisionIntegrationSeeds(doFetch, cfg.dbEndpoint, tenantId, dbKey, database.integrations, env, out);
|
|
10670
10679
|
}
|
|
10671
10680
|
if (cfg.services.includes("ai") && cfg.ai?.provider && cfg.ai.keyEnv) {
|
|
10672
|
-
const key =
|
|
10681
|
+
const key = import_node_process11.default.env[cfg.ai.keyEnv];
|
|
10673
10682
|
if (key) {
|
|
10674
10683
|
const secretName = cfg.ai.secretName ?? defaultSecretName(cfg.ai.provider);
|
|
10675
10684
|
await (0, import_ai3.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, secretName, key);
|
|
@@ -10711,7 +10720,7 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
|
|
|
10711
10720
|
|
|
10712
10721
|
// src/record.ts
|
|
10713
10722
|
var import_node_fs16 = require("fs");
|
|
10714
|
-
var
|
|
10723
|
+
var import_node_process12 = __toESM(require("process"), 1);
|
|
10715
10724
|
|
|
10716
10725
|
// src/surface.ts
|
|
10717
10726
|
var PM_ACTIONS = {
|
|
@@ -10869,7 +10878,7 @@ function surfacePaths(node = COMMAND_SURFACE, prefix = []) {
|
|
|
10869
10878
|
|
|
10870
10879
|
// src/record.ts
|
|
10871
10880
|
function recordInvocation(parsed) {
|
|
10872
|
-
const file =
|
|
10881
|
+
const file = import_node_process12.default.env.ODLA_CLI_RECORD;
|
|
10873
10882
|
if (!file) return;
|
|
10874
10883
|
try {
|
|
10875
10884
|
const entry = {
|
|
@@ -11541,9 +11550,9 @@ var import_node_child_process8 = require("child_process");
|
|
|
11541
11550
|
var import_node_fs20 = require("fs");
|
|
11542
11551
|
var import_node_os5 = require("os");
|
|
11543
11552
|
var import_node_path18 = require("path");
|
|
11544
|
-
var
|
|
11553
|
+
var import_node_process13 = __toESM(require("process"), 1);
|
|
11545
11554
|
var EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
|
|
11546
|
-
function resolveEditor(env =
|
|
11555
|
+
function resolveEditor(env = import_node_process13.default.env) {
|
|
11547
11556
|
for (const name of EDITOR_ENV) {
|
|
11548
11557
|
const value2 = env[name];
|
|
11549
11558
|
if (value2 && value2.trim()) return value2.trim();
|
|
@@ -11557,8 +11566,8 @@ function defaultRun(command, path) {
|
|
|
11557
11566
|
return result.status ?? 0;
|
|
11558
11567
|
}
|
|
11559
11568
|
function editText(initial, slug, deps = {}) {
|
|
11560
|
-
const env = deps.env ??
|
|
11561
|
-
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));
|
|
11562
11571
|
const editor = resolveEditor(env);
|
|
11563
11572
|
if (!editor)
|
|
11564
11573
|
throw new Error(
|