@odla-ai/cli 0.35.1 → 0.36.0
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.cjs +489 -231
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-SRL2TN24.js → chunk-DBZQIMES.js} +357 -137
- package/dist/chunk-DBZQIMES.js.map +1 -0
- package/dist/{cli-HS35QLXR.js → cli-BN6WLH5O.js} +2 -2
- package/dist/index.cjs +424 -204
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-SRL2TN24.js.map +0 -1
- /package/dist/{cli-HS35QLXR.js.map → cli-BN6WLH5O.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -97,12 +97,12 @@ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${_
|
|
|
97
97
|
var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
98
98
|
|
|
99
99
|
// src/admin-ai.ts
|
|
100
|
-
var
|
|
100
|
+
var import_node_process8 = __toESM(require("process"), 1);
|
|
101
101
|
|
|
102
102
|
// src/token.ts
|
|
103
103
|
var import_db = require("@odla-ai/db");
|
|
104
104
|
var import_node_crypto = require("crypto");
|
|
105
|
-
var
|
|
105
|
+
var import_node_process5 = __toESM(require("process"), 1);
|
|
106
106
|
|
|
107
107
|
// src/handshake-approval.ts
|
|
108
108
|
var import_node_process2 = __toESM(require("process"), 1);
|
|
@@ -236,13 +236,72 @@ function handshakeWaitMs(waitSeconds, interactive = import_node_process3.default
|
|
|
236
236
|
return interactive ? void 0 : 9e4;
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
-
// src/
|
|
239
|
+
// src/cached-credential.ts
|
|
240
240
|
var import_node_fs2 = require("fs");
|
|
241
|
+
var noted = null;
|
|
242
|
+
function noteCachedCredential(tokenFile) {
|
|
243
|
+
noted = tokenFile;
|
|
244
|
+
}
|
|
245
|
+
function isCredentialRejection(error) {
|
|
246
|
+
const message2 = error instanceof Error ? error.message : String(error ?? "");
|
|
247
|
+
return /\((401|403)\)\s*$/.test(message2.trim());
|
|
248
|
+
}
|
|
249
|
+
function explainRejectedCredential(error) {
|
|
250
|
+
const tokenFile = noted;
|
|
251
|
+
if (!tokenFile || !isCredentialRejection(error)) return null;
|
|
252
|
+
noted = null;
|
|
253
|
+
(0, import_node_fs2.rmSync)(tokenFile, { force: true });
|
|
254
|
+
return [
|
|
255
|
+
"auth: the cached credential was rejected by odla, so it was revoked before its cached expiry.",
|
|
256
|
+
" The usual cause is a newer sign-in for this project: collecting a handshake retires the",
|
|
257
|
+
" principal's other credentials, so a second terminal or worktree supersedes this one.",
|
|
258
|
+
` Discarded ${tokenFile}; re-run this command to request a fresh approval.`
|
|
259
|
+
].join("\n");
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// src/device-session.ts
|
|
263
|
+
var import_node_fs3 = require("fs");
|
|
264
|
+
var import_node_os = require("os");
|
|
241
265
|
var import_node_path2 = require("path");
|
|
266
|
+
var import_node_process4 = __toESM(require("process"), 1);
|
|
267
|
+
function deviceCredentialPath(env = import_node_process4.default.env) {
|
|
268
|
+
return env.ODLA_DEVICE_CREDENTIAL ?? (0, import_node_path2.join)(env.HOME ?? (0, import_node_os.homedir)(), ".odla", "device.json");
|
|
269
|
+
}
|
|
270
|
+
function readDeviceCredential(platform, env = import_node_process4.default.env) {
|
|
271
|
+
const path = deviceCredentialPath(env);
|
|
272
|
+
if (!(0, import_node_fs3.existsSync)(path)) return null;
|
|
273
|
+
try {
|
|
274
|
+
const parsed = JSON.parse((0, import_node_fs3.readFileSync)(path, "utf8"));
|
|
275
|
+
if (typeof parsed.token !== "string" || !parsed.token.startsWith("odla_device_")) return null;
|
|
276
|
+
if (parsed.platform !== platform) return null;
|
|
277
|
+
return { ...parsed, token: parsed.token, platform: parsed.platform };
|
|
278
|
+
} catch {
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
async function mintDeviceSession(platformUrl, credential2, doFetch) {
|
|
283
|
+
const response2 = await doFetch(`${platformUrl.replace(/\/$/, "")}/registry/devices/session`, {
|
|
284
|
+
method: "POST",
|
|
285
|
+
headers: { authorization: `Bearer ${credential2.token}`, "content-type": "application/json" },
|
|
286
|
+
body: "{}"
|
|
287
|
+
});
|
|
288
|
+
const body = await response2.json().catch(() => ({}));
|
|
289
|
+
if (!response2.ok || typeof body.token !== "string") {
|
|
290
|
+
const detail = body.error?.message ?? `registry returned ${response2.status}`;
|
|
291
|
+
throw new Error(
|
|
292
|
+
`device session failed: ${detail} (${response2.status}) \u2014 if this machine's enrollment was revoked or has expired, enroll it again in Studio`
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
return { token: body.token, expiresAt: body.expiresAt ?? Date.now() };
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// src/local.ts
|
|
299
|
+
var import_node_fs4 = require("fs");
|
|
300
|
+
var import_node_path3 = require("path");
|
|
242
301
|
var GITIGNORE_LINES = [".odla/*.local.json", ".odla/dev-token.json", ".dev.vars"];
|
|
243
302
|
function readJsonFile(path) {
|
|
244
303
|
try {
|
|
245
|
-
return JSON.parse((0,
|
|
304
|
+
return JSON.parse((0, import_node_fs4.readFileSync)(path, "utf8"));
|
|
246
305
|
} catch {
|
|
247
306
|
return null;
|
|
248
307
|
}
|
|
@@ -252,10 +311,10 @@ function writePrivateJson(path, value2) {
|
|
|
252
311
|
`);
|
|
253
312
|
}
|
|
254
313
|
function readCredentials(path) {
|
|
255
|
-
if (!(0,
|
|
314
|
+
if (!(0, import_node_fs4.existsSync)(path)) return null;
|
|
256
315
|
let value2;
|
|
257
316
|
try {
|
|
258
|
-
value2 = JSON.parse((0,
|
|
317
|
+
value2 = JSON.parse((0, import_node_fs4.readFileSync)(path, "utf8"));
|
|
259
318
|
} catch {
|
|
260
319
|
throw new Error(`credentials file ${path} is not valid JSON; fix or remove it before provisioning`);
|
|
261
320
|
}
|
|
@@ -285,14 +344,14 @@ function mergeCredential(current, update) {
|
|
|
285
344
|
return next;
|
|
286
345
|
}
|
|
287
346
|
function ensureGitignore(rootDir, localPaths = []) {
|
|
288
|
-
const path = (0,
|
|
289
|
-
const existing = (0,
|
|
347
|
+
const path = (0, import_node_path3.resolve)(rootDir, ".gitignore");
|
|
348
|
+
const existing = (0, import_node_fs4.existsSync)(path) ? (0, import_node_fs4.readFileSync)(path, "utf8") : "";
|
|
290
349
|
const configured = localPaths.map((localPath) => gitignoreEntry(rootDir, localPath)).filter((line2) => !!line2);
|
|
291
350
|
const wanted = [.../* @__PURE__ */ new Set([...GITIGNORE_LINES, ...configured])];
|
|
292
351
|
const missing = wanted.filter((line2) => !existing.split(/\r?\n/).includes(line2));
|
|
293
352
|
if (missing.length === 0) return;
|
|
294
353
|
const prefix = existing && !existing.endsWith("\n") ? "\n" : "";
|
|
295
|
-
(0,
|
|
354
|
+
(0, import_node_fs4.writeFileSync)(path, `${existing}${prefix}${missing.join("\n")}
|
|
296
355
|
`);
|
|
297
356
|
}
|
|
298
357
|
function o11yDevVars(cfg) {
|
|
@@ -306,7 +365,7 @@ function o11yDevVars(cfg) {
|
|
|
306
365
|
function resolveWriteDevVarsTarget(cfg, requested) {
|
|
307
366
|
if (!requested) return null;
|
|
308
367
|
if (requested === true) return cfg.local.devVarsFile;
|
|
309
|
-
return (0,
|
|
368
|
+
return (0, import_node_path3.resolve)((0, import_node_path3.dirname)(cfg.configPath), requested);
|
|
310
369
|
}
|
|
311
370
|
function writeDevVars(path, credentials, env, o11y) {
|
|
312
371
|
const entry = credentials.envs[env];
|
|
@@ -320,7 +379,7 @@ function writeDevVars(path, credentials, env, o11y) {
|
|
|
320
379
|
if (o11y.version) lines.push(`ODLA_O11Y_VERSION="${o11y.version}"`);
|
|
321
380
|
if (entry.o11yToken) lines.push(`ODLA_O11Y_TOKEN="${entry.o11yToken}"`);
|
|
322
381
|
}
|
|
323
|
-
const existing = (0,
|
|
382
|
+
const existing = (0, import_node_fs4.existsSync)(path) ? (0, import_node_fs4.readFileSync)(path, "utf8") : "";
|
|
324
383
|
const retained = existing.split(/\r?\n/).filter((line2) => !isManagedDevVar(line2));
|
|
325
384
|
while (retained.at(-1) === "") retained.pop();
|
|
326
385
|
const prefix = retained.length ? `${retained.join("\n")}
|
|
@@ -346,19 +405,19 @@ function isManagedDevVar(line2) {
|
|
|
346
405
|
return !!match?.[1] && MANAGED_DEV_VARS.has(match[1]);
|
|
347
406
|
}
|
|
348
407
|
function writePrivateText(path, text3) {
|
|
349
|
-
(0,
|
|
408
|
+
(0, import_node_fs4.mkdirSync)((0, import_node_path3.dirname)(path), { recursive: true });
|
|
350
409
|
const temporary = `${path}.tmp-${process.pid}-${Date.now()}`;
|
|
351
|
-
(0,
|
|
352
|
-
(0,
|
|
353
|
-
(0,
|
|
410
|
+
(0, import_node_fs4.writeFileSync)(temporary, text3, { mode: 384 });
|
|
411
|
+
(0, import_node_fs4.chmodSync)(temporary, 384);
|
|
412
|
+
(0, import_node_fs4.renameSync)(temporary, path);
|
|
354
413
|
}
|
|
355
414
|
function gitignoreEntry(rootDir, path) {
|
|
356
|
-
const rel = (0,
|
|
357
|
-
if (!rel || rel === ".." || rel.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || (0,
|
|
415
|
+
const rel = (0, import_node_path3.relative)((0, import_node_path3.resolve)(rootDir), (0, import_node_path3.resolve)(path));
|
|
416
|
+
if (!rel || rel === ".." || rel.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || (0, import_node_path3.isAbsolute)(rel)) return null;
|
|
358
417
|
return rel.replaceAll("\\", "/");
|
|
359
418
|
}
|
|
360
419
|
function displayPath(path, rootDir = process.cwd()) {
|
|
361
|
-
const rel = (0,
|
|
420
|
+
const rel = (0, import_node_path3.relative)(rootDir, path);
|
|
362
421
|
return rel && !rel.startsWith("..") ? rel : path;
|
|
363
422
|
}
|
|
364
423
|
|
|
@@ -370,17 +429,24 @@ async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {})
|
|
|
370
429
|
const cached = readJsonFile(cfg.local.tokenFile);
|
|
371
430
|
if (!grantRequest.forceReview && !grantRequest.freshLogin) {
|
|
372
431
|
if (options.token) return options.token;
|
|
373
|
-
if (
|
|
374
|
-
const declared =
|
|
432
|
+
if (import_node_process5.default.env.ODLA_DEV_TOKEN) {
|
|
433
|
+
const declared = import_node_process5.default.env.ODLA_DEV_TOKEN_AUDIENCE;
|
|
375
434
|
if (declared) {
|
|
376
435
|
if (platformAudience(declared) !== audience) throw new Error("ODLA_DEV_TOKEN_AUDIENCE does not match the configured platform");
|
|
377
436
|
} else if (audience !== "https://odla.ai") {
|
|
378
437
|
throw new Error("ODLA_DEV_TOKEN_AUDIENCE is required for a non-default platform");
|
|
379
438
|
}
|
|
380
|
-
return
|
|
439
|
+
return import_node_process5.default.env.ODLA_DEV_TOKEN;
|
|
440
|
+
}
|
|
441
|
+
const device = readDeviceCredential(audience);
|
|
442
|
+
if (device) {
|
|
443
|
+
const session = await mintDeviceSession(cfg.platformUrl, device, doFetch);
|
|
444
|
+
out.error(`auth: session minted by this enrolled device (${displayPath(deviceCredentialPath(), cfg.rootDir)})`);
|
|
445
|
+
return session.token;
|
|
381
446
|
}
|
|
382
447
|
if (cached?.token && cached.platform === audience && (cached.expiresAt ?? 0) > Date.now() + 6e4 && cachedGrantCovers(cached, grantIntent)) {
|
|
383
448
|
out.error(`auth: using cached developer token (${displayPath(cfg.local.tokenFile, cfg.rootDir)})`);
|
|
449
|
+
noteCachedCredential(cfg.local.tokenFile);
|
|
384
450
|
return cached.token;
|
|
385
451
|
}
|
|
386
452
|
} else {
|
|
@@ -481,7 +547,7 @@ function stillPending(pending, email) {
|
|
|
481
547
|
);
|
|
482
548
|
}
|
|
483
549
|
function handshakeEmail(value2, cached) {
|
|
484
|
-
const email = (value2 ??
|
|
550
|
+
const email = (value2 ?? import_node_process5.default.env.ODLA_USER_EMAIL ?? cached ?? "").trim().toLowerCase();
|
|
485
551
|
if (/@users\.noreply\.github\.com$/i.test(email)) {
|
|
486
552
|
throw new Error(
|
|
487
553
|
`"${email}" is a GitHub commit identity, not an odla account email; use --email <signed-in-odla-account> or ODLA_USER_EMAIL`
|
|
@@ -512,12 +578,12 @@ function platformAudience(value2) {
|
|
|
512
578
|
}
|
|
513
579
|
|
|
514
580
|
// src/secret-input.ts
|
|
515
|
-
var
|
|
581
|
+
var import_node_process6 = __toESM(require("process"), 1);
|
|
516
582
|
var MAX_BYTES = 64 * 1024;
|
|
517
583
|
async function secretInputValue(options, kind = "credential") {
|
|
518
584
|
if (options.fromEnv && options.stdin) throw new Error("choose exactly one of --from-env or --stdin");
|
|
519
585
|
let value2;
|
|
520
|
-
if (options.fromEnv) value2 =
|
|
586
|
+
if (options.fromEnv) value2 = import_node_process6.default.env[options.fromEnv];
|
|
521
587
|
else if (options.stdin) value2 = await (options.readStdin ?? (() => readSecretStream(kind)))();
|
|
522
588
|
else throw new Error(`${kind} input required: use --from-env <NAME> or --stdin; values are never accepted as arguments`);
|
|
523
589
|
value2 = value2?.replace(/[\r\n]+$/, "");
|
|
@@ -525,7 +591,7 @@ async function secretInputValue(options, kind = "credential") {
|
|
|
525
591
|
if (new TextEncoder().encode(value2).byteLength > MAX_BYTES) throw new Error(`${kind} exceeds 64 KiB`);
|
|
526
592
|
return value2;
|
|
527
593
|
}
|
|
528
|
-
async function readSecretStream(kind, stream =
|
|
594
|
+
async function readSecretStream(kind, stream = import_node_process6.default.stdin) {
|
|
529
595
|
let value2 = "";
|
|
530
596
|
for await (const chunk of stream) {
|
|
531
597
|
value2 += String(chunk);
|
|
@@ -535,9 +601,9 @@ async function readSecretStream(kind, stream = import_node_process5.default.stdi
|
|
|
535
601
|
}
|
|
536
602
|
|
|
537
603
|
// src/admin-ai-auth.ts
|
|
538
|
-
var
|
|
539
|
-
var
|
|
540
|
-
var
|
|
604
|
+
var import_node_fs5 = require("fs");
|
|
605
|
+
var import_node_path4 = require("path");
|
|
606
|
+
var import_node_process7 = __toESM(require("process"), 1);
|
|
541
607
|
var import_db2 = require("@odla-ai/db");
|
|
542
608
|
async function getScopedPlatformToken(options) {
|
|
543
609
|
return resolveAdminPlatformToken(options);
|
|
@@ -545,7 +611,7 @@ async function getScopedPlatformToken(options) {
|
|
|
545
611
|
async function resolveAdminPlatformToken(options) {
|
|
546
612
|
const audience = platformAudience(options.platform);
|
|
547
613
|
if (options.token) return options.token;
|
|
548
|
-
const fromEnv =
|
|
614
|
+
const fromEnv = import_node_process7.default.env.ODLA_ADMIN_TOKEN;
|
|
549
615
|
if (fromEnv) return audienceBoundEnvToken(fromEnv, audience);
|
|
550
616
|
return scopedToken(
|
|
551
617
|
audience,
|
|
@@ -557,7 +623,7 @@ async function resolveAdminPlatformToken(options) {
|
|
|
557
623
|
}
|
|
558
624
|
function audienceBoundEnvToken(token, platform) {
|
|
559
625
|
const audience = platformAudience(platform);
|
|
560
|
-
const declared =
|
|
626
|
+
const declared = import_node_process7.default.env.ODLA_ADMIN_TOKEN_AUDIENCE;
|
|
561
627
|
if (declared) {
|
|
562
628
|
if (platformAudience(declared) !== audience) throw new Error("ODLA_ADMIN_TOKEN_AUDIENCE does not match the configured platform");
|
|
563
629
|
} else if (audience !== "https://odla.ai") {
|
|
@@ -570,6 +636,7 @@ var SCOPE_PURPOSE = {
|
|
|
570
636
|
"app:config:read": "compare checked-in intent with an exact-id app Registry configuration",
|
|
571
637
|
"app:config:write": "apply or inspect one revision-bound configuration operation for an app you own",
|
|
572
638
|
"platform:runbook:write": "read and edit all of odla's operational runbooks, including admin-visible content",
|
|
639
|
+
"app:device:enroll": "enrol this machine so it can mint its own short-lived credentials without asking you again",
|
|
573
640
|
"platform:ai:policy:write": "change System AI model routing",
|
|
574
641
|
"platform:ai:policy:read": "read System AI model routing",
|
|
575
642
|
"platform:ai:credential:write": "replace a stored AI provider key",
|
|
@@ -582,8 +649,8 @@ var SCOPE_PURPOSE = {
|
|
|
582
649
|
};
|
|
583
650
|
async function scopedToken(platform, scope, options, doFetch, out) {
|
|
584
651
|
const audience = platformAudience(platform);
|
|
585
|
-
const rootDir = options.rootDir ??
|
|
586
|
-
const tokenFile = options.tokenFile ?? (0,
|
|
652
|
+
const rootDir = options.rootDir ?? import_node_process7.default.cwd();
|
|
653
|
+
const tokenFile = options.tokenFile ?? (0, import_node_path4.join)(rootDir, ".odla/admin-token.local.json");
|
|
587
654
|
const cache2 = options.cache === false ? null : readJsonFile(tokenFile);
|
|
588
655
|
const cached = cache2?.platform === audience ? cache2.tokens?.[scope] : void 0;
|
|
589
656
|
if (cached?.token && (cached.expiresAt ?? 0) > Date.now() + 6e4) {
|
|
@@ -610,7 +677,7 @@ async function scopedToken(platform, scope, options, doFetch, out) {
|
|
|
610
677
|
if (options.cache !== false) {
|
|
611
678
|
const tokens = cache2?.platform === audience ? { ...cache2.tokens ?? {} } : {};
|
|
612
679
|
tokens[scope] = { token, expiresAt };
|
|
613
|
-
if ((0,
|
|
680
|
+
if ((0, import_node_fs5.existsSync)((0, import_node_path4.join)(rootDir, ".git"))) ensureGitignore(rootDir, [tokenFile]);
|
|
614
681
|
writePrivateJson(tokenFile, { platform: audience, email, tokens });
|
|
615
682
|
out.error(`auth: cached ${scope} grant (${tokenFile}; mode 0600)`);
|
|
616
683
|
} else {
|
|
@@ -787,7 +854,7 @@ function isRecord2(value2) {
|
|
|
787
854
|
|
|
788
855
|
// src/admin-ai.ts
|
|
789
856
|
async function adminAi(options) {
|
|
790
|
-
const platform = platformAudience(options.platform ??
|
|
857
|
+
const platform = platformAudience(options.platform ?? import_node_process8.default.env.ODLA_PLATFORM ?? "https://odla.ai");
|
|
791
858
|
const doFetch = options.fetch ?? fetch;
|
|
792
859
|
const out = options.stdout ?? console;
|
|
793
860
|
const usageQuery = options.action === "usage" ? adminAiUsageQuery(options) : void 0;
|
|
@@ -1042,13 +1109,13 @@ function addOption(options, name, value2) {
|
|
|
1042
1109
|
}
|
|
1043
1110
|
|
|
1044
1111
|
// src/operator-context.ts
|
|
1045
|
-
var
|
|
1046
|
-
var
|
|
1047
|
-
var
|
|
1112
|
+
var import_node_fs8 = require("fs");
|
|
1113
|
+
var import_node_path7 = require("path");
|
|
1114
|
+
var import_node_process10 = __toESM(require("process"), 1);
|
|
1048
1115
|
|
|
1049
1116
|
// src/config.ts
|
|
1050
|
-
var
|
|
1051
|
-
var
|
|
1117
|
+
var import_node_fs6 = require("fs");
|
|
1118
|
+
var import_node_path5 = require("path");
|
|
1052
1119
|
var import_node_url = require("url");
|
|
1053
1120
|
var import_apps = require("@odla-ai/apps");
|
|
1054
1121
|
|
|
@@ -1453,12 +1520,12 @@ var DEFAULT_SERVICES = ["db", "ai"];
|
|
|
1453
1520
|
var configImportSerial = 0;
|
|
1454
1521
|
var GOOGLE_CALENDAR_EVENTS_SCOPE = "https://www.googleapis.com/auth/calendar.events";
|
|
1455
1522
|
async function loadProjectConfig(configPath = "odla.config.mjs", options = {}) {
|
|
1456
|
-
const resolved = (0,
|
|
1457
|
-
if (!(0,
|
|
1523
|
+
const resolved = (0, import_node_path5.resolve)(configPath);
|
|
1524
|
+
if (!(0, import_node_fs6.existsSync)(resolved)) {
|
|
1458
1525
|
throw new Error(`config not found: ${configPath}. Run "odla-ai init" first or pass --config.`);
|
|
1459
1526
|
}
|
|
1460
1527
|
const raw = await loadConfigModule(resolved);
|
|
1461
|
-
const rootDir = (0,
|
|
1528
|
+
const rootDir = (0, import_node_path5.dirname)(resolved);
|
|
1462
1529
|
validateRawConfig(raw, resolved);
|
|
1463
1530
|
const platformUrl = trimSlash(process.env.ODLA_PLATFORM_URL || raw.platformUrl || DEFAULT_PLATFORM);
|
|
1464
1531
|
const dbEndpoint = trimSlash(process.env.ODLA_DB_ENDPOINT || raw.dbEndpoint || platformUrl);
|
|
@@ -1468,9 +1535,9 @@ async function loadProjectConfig(configPath = "odla.config.mjs", options = {}) {
|
|
|
1468
1535
|
validateCalendarConfig(raw, unique3([...envs, ...options.additionalEnvs ?? []]), services, resolved);
|
|
1469
1536
|
validateMonitoringConfig(raw, unique3([...envs, ...options.additionalEnvs ?? []]), services, resolved);
|
|
1470
1537
|
const local = {
|
|
1471
|
-
tokenFile: (0,
|
|
1472
|
-
credentialsFile: (0,
|
|
1473
|
-
devVarsFile: (0,
|
|
1538
|
+
tokenFile: (0, import_node_path5.resolve)(rootDir, raw.local?.tokenFile ?? ".odla/dev-token.json"),
|
|
1539
|
+
credentialsFile: (0, import_node_path5.resolve)(rootDir, raw.local?.credentialsFile ?? ".odla/credentials.local.json"),
|
|
1540
|
+
devVarsFile: (0, import_node_path5.resolve)(rootDir, raw.local?.devVarsFile ?? ".dev.vars"),
|
|
1474
1541
|
gitignore: raw.local?.gitignore ?? true
|
|
1475
1542
|
};
|
|
1476
1543
|
return {
|
|
@@ -1487,9 +1554,9 @@ async function loadProjectConfig(configPath = "odla.config.mjs", options = {}) {
|
|
|
1487
1554
|
async function resolveDataExport(cfg, value2, names) {
|
|
1488
1555
|
if (value2 === void 0 || value2 === null || value2 === false) return void 0;
|
|
1489
1556
|
if (typeof value2 !== "string") return value2;
|
|
1490
|
-
const target = (0,
|
|
1557
|
+
const target = (0, import_node_path5.isAbsolute)(value2) ? value2 : (0, import_node_path5.resolve)(cfg.rootDir, value2);
|
|
1491
1558
|
if (target.endsWith(".json")) {
|
|
1492
|
-
return JSON.parse((0,
|
|
1559
|
+
return JSON.parse((0, import_node_fs6.readFileSync)(target, "utf8"));
|
|
1493
1560
|
}
|
|
1494
1561
|
const mod = await import((0, import_node_url.pathToFileURL)(target).href);
|
|
1495
1562
|
for (const name of names) {
|
|
@@ -1565,7 +1632,7 @@ function validId2(value2) {
|
|
|
1565
1632
|
return typeof value2 === "string" && /^[a-z0-9][a-z0-9-]*$/.test(value2);
|
|
1566
1633
|
}
|
|
1567
1634
|
async function loadConfigModule(path) {
|
|
1568
|
-
if (path.endsWith(".json")) return JSON.parse((0,
|
|
1635
|
+
if (path.endsWith(".json")) return JSON.parse((0, import_node_fs6.readFileSync)(path, "utf8"));
|
|
1569
1636
|
const nonce = `${Date.now()}-${configImportSerial++}`;
|
|
1570
1637
|
const mod = await import(`${(0, import_node_url.pathToFileURL)(path).href}?reload=${nonce}`);
|
|
1571
1638
|
const value2 = mod.default ?? mod.config;
|
|
@@ -1580,18 +1647,18 @@ function unique3(values) {
|
|
|
1580
1647
|
}
|
|
1581
1648
|
|
|
1582
1649
|
// src/operator-profiles.ts
|
|
1583
|
-
var
|
|
1584
|
-
var
|
|
1585
|
-
var
|
|
1586
|
-
var
|
|
1650
|
+
var import_node_fs7 = require("fs");
|
|
1651
|
+
var import_node_os2 = require("os");
|
|
1652
|
+
var import_node_path6 = require("path");
|
|
1653
|
+
var import_node_process9 = __toESM(require("process"), 1);
|
|
1587
1654
|
function operatorProfileFile() {
|
|
1588
|
-
return (0,
|
|
1589
|
-
clean(
|
|
1655
|
+
return (0, import_node_path6.resolve)(
|
|
1656
|
+
clean(import_node_process9.default.env.ODLA_CONTEXT_FILE) ?? (0, import_node_path6.join)((0, import_node_os2.homedir)(), ".odla", "contexts.json")
|
|
1590
1657
|
);
|
|
1591
1658
|
}
|
|
1592
1659
|
function resolveOperatorProfile(parsed) {
|
|
1593
1660
|
const fromFlag = clean(stringOpt(parsed.options.context));
|
|
1594
|
-
const fromEnvironment = clean(
|
|
1661
|
+
const fromEnvironment = clean(import_node_process9.default.env.ODLA_CONTEXT);
|
|
1595
1662
|
const name = fromFlag ?? fromEnvironment ?? null;
|
|
1596
1663
|
const file = operatorProfileFile();
|
|
1597
1664
|
if (!name) {
|
|
@@ -1631,10 +1698,10 @@ function removeOperatorProfile(name, file = operatorProfileFile()) {
|
|
|
1631
1698
|
return true;
|
|
1632
1699
|
}
|
|
1633
1700
|
function operatorCredentialFiles(selection) {
|
|
1634
|
-
const base = selection.name ? (0,
|
|
1701
|
+
const base = selection.name ? (0, import_node_path6.join)((0, import_node_path6.dirname)(selection.file), "profiles", selection.name) : (0, import_node_path6.join)((0, import_node_os2.homedir)(), ".odla");
|
|
1635
1702
|
return {
|
|
1636
|
-
developer: (0,
|
|
1637
|
-
scoped: (0,
|
|
1703
|
+
developer: (0, import_node_path6.join)(base, "dev-token.json"),
|
|
1704
|
+
scoped: (0, import_node_path6.join)(base, "admin-token.local.json")
|
|
1638
1705
|
};
|
|
1639
1706
|
}
|
|
1640
1707
|
function assertOperatorName(value2, label) {
|
|
@@ -1645,10 +1712,10 @@ function assertOperatorName(value2, label) {
|
|
|
1645
1712
|
}
|
|
1646
1713
|
}
|
|
1647
1714
|
function readOperatorProfiles(file) {
|
|
1648
|
-
if (!(0,
|
|
1715
|
+
if (!(0, import_node_fs7.existsSync)(file)) return emptyProfiles();
|
|
1649
1716
|
let raw;
|
|
1650
1717
|
try {
|
|
1651
|
-
raw = JSON.parse((0,
|
|
1718
|
+
raw = JSON.parse((0, import_node_fs7.readFileSync)(file, "utf8"));
|
|
1652
1719
|
} catch {
|
|
1653
1720
|
throw new Error(`operator context file ${file} is not valid JSON`);
|
|
1654
1721
|
}
|
|
@@ -1712,21 +1779,21 @@ var DEFAULT_PLATFORM2 = "https://odla.ai";
|
|
|
1712
1779
|
async function resolveOperatorContext(parsed, options = {}) {
|
|
1713
1780
|
const profile = resolveOperatorProfile(parsed);
|
|
1714
1781
|
const configArgument = stringOpt(parsed.options.config) ?? "odla.config.mjs";
|
|
1715
|
-
const configPath = (0,
|
|
1782
|
+
const configPath = (0, import_node_path7.resolve)(configArgument);
|
|
1716
1783
|
const explicitConfig = parsed.options.config !== void 0;
|
|
1717
|
-
const hasConfig = (0,
|
|
1784
|
+
const hasConfig = (0, import_node_fs8.existsSync)(configPath);
|
|
1718
1785
|
if (!hasConfig && (!options.allowMissingConfig || explicitConfig)) {
|
|
1719
1786
|
await loadProjectConfig(configArgument);
|
|
1720
1787
|
}
|
|
1721
1788
|
const loaded = hasConfig ? await loadProjectConfig(configArgument) : void 0;
|
|
1722
1789
|
const platformFlag = clean2(stringOpt(parsed.options.platform));
|
|
1723
|
-
const platformEnvironment = clean2(
|
|
1790
|
+
const platformEnvironment = clean2(import_node_process10.default.env.ODLA_PLATFORM_URL);
|
|
1724
1791
|
const platformValue = platformAudience(
|
|
1725
1792
|
platformFlag ?? platformEnvironment ?? profile.value?.platform ?? loaded?.platformUrl ?? DEFAULT_PLATFORM2
|
|
1726
1793
|
);
|
|
1727
1794
|
const platformSource = platformFlag ? "flag" : platformEnvironment ? "environment" : profile.value ? "profile" : loaded ? "config" : "default";
|
|
1728
1795
|
const appFlag = clean2(stringOpt(parsed.options.app));
|
|
1729
|
-
const appEnvironment = clean2(
|
|
1796
|
+
const appEnvironment = clean2(import_node_process10.default.env.ODLA_APP_ID);
|
|
1730
1797
|
const appValue = appFlag ?? appEnvironment ?? profile.value?.app ?? loaded?.app.id ?? null;
|
|
1731
1798
|
const appSource = appFlag ? "flag" : appEnvironment ? "environment" : profile.value?.app ? "profile" : loaded ? "config" : "unresolved";
|
|
1732
1799
|
if (appValue) assertOperatorName(appValue, "app");
|
|
@@ -1736,16 +1803,16 @@ async function resolveOperatorContext(parsed, options = {}) {
|
|
|
1736
1803
|
);
|
|
1737
1804
|
}
|
|
1738
1805
|
const envFlag = clean2(stringOpt(parsed.options.env));
|
|
1739
|
-
const envEnvironment = clean2(
|
|
1806
|
+
const envEnvironment = clean2(import_node_process10.default.env.ODLA_ENV);
|
|
1740
1807
|
const environmentValue = envFlag ?? envEnvironment ?? profile.value?.environment ?? options.defaultEnvironment ?? null;
|
|
1741
1808
|
const environmentSource = envFlag ? "flag" : envEnvironment ? "environment" : profile.value?.environment ? "profile" : options.defaultEnvironment ? "default" : "unresolved";
|
|
1742
1809
|
if (environmentValue) {
|
|
1743
1810
|
assertOperatorName(environmentValue, "environment");
|
|
1744
1811
|
}
|
|
1745
|
-
const rootDir = loaded?.rootDir ??
|
|
1812
|
+
const rootDir = loaded?.rootDir ?? import_node_process10.default.cwd();
|
|
1746
1813
|
const profileCredentials = operatorCredentialFiles(profile);
|
|
1747
|
-
const tokenFile = clean2(
|
|
1748
|
-
const scopedTokenFile = clean2(
|
|
1814
|
+
const tokenFile = clean2(import_node_process10.default.env.ODLA_DEV_TOKEN_FILE) ? (0, import_node_path7.resolve)(import_node_process10.default.env.ODLA_DEV_TOKEN_FILE) : profile.name ? profileCredentials.developer : loaded?.local.tokenFile ?? profileCredentials.developer;
|
|
1815
|
+
const scopedTokenFile = clean2(import_node_process10.default.env.ODLA_ADMIN_TOKEN_FILE) ? (0, import_node_path7.resolve)(import_node_process10.default.env.ODLA_ADMIN_TOKEN_FILE) : profile.name ? profileCredentials.scoped : loaded ? (0, import_node_path7.join)(loaded.rootDir, ".odla", "admin-token.local.json") : profileCredentials.scoped;
|
|
1749
1816
|
const cfg = loaded ? {
|
|
1750
1817
|
...loaded,
|
|
1751
1818
|
platformUrl: platformValue,
|
|
@@ -1767,8 +1834,8 @@ async function resolveOperatorContext(parsed, options = {}) {
|
|
|
1767
1834
|
services: [],
|
|
1768
1835
|
local: {
|
|
1769
1836
|
tokenFile,
|
|
1770
|
-
credentialsFile: (0,
|
|
1771
|
-
devVarsFile: (0,
|
|
1837
|
+
credentialsFile: (0, import_node_path7.join)(rootDir, ".odla", "credentials.local.json"),
|
|
1838
|
+
devVarsFile: (0, import_node_path7.join)(rootDir, ".dev.vars"),
|
|
1772
1839
|
gitignore: true
|
|
1773
1840
|
}
|
|
1774
1841
|
};
|
|
@@ -1865,7 +1932,7 @@ async function adminCommand(parsed, deps = {}) {
|
|
|
1865
1932
|
}
|
|
1866
1933
|
|
|
1867
1934
|
// src/auth-command.ts
|
|
1868
|
-
var
|
|
1935
|
+
var import_node_process11 = __toESM(require("process"), 1);
|
|
1869
1936
|
|
|
1870
1937
|
// src/whoami-command.ts
|
|
1871
1938
|
var text2 = (value2) => typeof value2 === "string" && value2.trim() ? value2.trim() : null;
|
|
@@ -2027,7 +2094,7 @@ async function authCommand(parsed, deps = {}) {
|
|
|
2027
2094
|
const { cfg } = context;
|
|
2028
2095
|
const out = deps.stdout ?? console;
|
|
2029
2096
|
const doFetch = deps.fetch ?? fetch;
|
|
2030
|
-
const email = stringOpt(parsed.options.email) ??
|
|
2097
|
+
const email = stringOpt(parsed.options.email) ?? import_node_process11.default.env.ODLA_USER_EMAIL?.trim();
|
|
2031
2098
|
if (!email) {
|
|
2032
2099
|
throw new Error(
|
|
2033
2100
|
"auth login requires --email <odla-account> or ODLA_USER_EMAIL; confirm the signed-in odla email instead of using git or GitHub identity"
|
|
@@ -2180,7 +2247,7 @@ async function appExport(options) {
|
|
|
2180
2247
|
}
|
|
2181
2248
|
|
|
2182
2249
|
// src/app-import.ts
|
|
2183
|
-
var
|
|
2250
|
+
var import_node_fs9 = require("fs");
|
|
2184
2251
|
var import_import = require("@odla-ai/db/import");
|
|
2185
2252
|
function chooseIdMode(options, rows) {
|
|
2186
2253
|
const chosen = [options.idField && "field", options.key && "key", options.generateIds && "generate"].filter(Boolean);
|
|
@@ -2198,7 +2265,7 @@ async function appImport(options) {
|
|
|
2198
2265
|
const out = options.stdout ?? console;
|
|
2199
2266
|
const say = options.json ? (line2) => out.error(line2) : (line2) => out.log(line2);
|
|
2200
2267
|
const { tenant } = resolveTenant(cfg, options.env);
|
|
2201
|
-
const text3 = options.file === "-" ? (options.readStdin ?? (() => (0,
|
|
2268
|
+
const text3 = options.file === "-" ? (options.readStdin ?? (() => (0, import_node_fs9.readFileSync)(0, "utf8")))() : (0, import_node_fs9.readFileSync)(options.file, "utf8");
|
|
2202
2269
|
const { format, sources } = (0, import_import.parseImport)(text3, options.ns);
|
|
2203
2270
|
if (format === "namespace-map" && options.ns) {
|
|
2204
2271
|
throw new Error("--ns cannot be combined with a {namespace: rows} file \u2014 the file already names each namespace");
|
|
@@ -2388,7 +2455,7 @@ async function appCommand(parsed, dependencies = {}) {
|
|
|
2388
2455
|
|
|
2389
2456
|
// src/brand-command.ts
|
|
2390
2457
|
var import_promises = require("fs/promises");
|
|
2391
|
-
var
|
|
2458
|
+
var import_node_path8 = require("path");
|
|
2392
2459
|
|
|
2393
2460
|
// src/brand-design-unpack.ts
|
|
2394
2461
|
var import_node_zlib = require("zlib");
|
|
@@ -2490,15 +2557,15 @@ function describeUnpack(result, outDir) {
|
|
|
2490
2557
|
// src/brand-command.ts
|
|
2491
2558
|
var USAGE = "usage: odla-ai brand design unpack <bundle.html|-> [--out <dir>] [--json]";
|
|
2492
2559
|
async function readBundle(source, deps) {
|
|
2493
|
-
if (source !== "-") return (0, import_promises.readFile)((0,
|
|
2560
|
+
if (source !== "-") return (0, import_promises.readFile)((0, import_node_path8.resolve)(source), "utf8");
|
|
2494
2561
|
const readStdin = deps.readStdin;
|
|
2495
2562
|
if (!readStdin) throw new Error("reading a bundle from stdin is not supported here");
|
|
2496
2563
|
return readStdin();
|
|
2497
2564
|
}
|
|
2498
2565
|
async function writeAll(result, outDir) {
|
|
2499
2566
|
for (const file of result.files) {
|
|
2500
|
-
const target = (0,
|
|
2501
|
-
await (0, import_promises.mkdir)((0,
|
|
2567
|
+
const target = (0, import_node_path8.resolve)(outDir, file.path);
|
|
2568
|
+
await (0, import_promises.mkdir)((0, import_node_path8.dirname)(target), { recursive: true });
|
|
2502
2569
|
await (0, import_promises.writeFile)(target, file.bytes);
|
|
2503
2570
|
}
|
|
2504
2571
|
}
|
|
@@ -2506,7 +2573,7 @@ async function designUnpack(parsed, deps) {
|
|
|
2506
2573
|
assertArgs(parsed, ["out", "json"], 4);
|
|
2507
2574
|
const source = parsed.positionals[3];
|
|
2508
2575
|
if (!source) throw new Error(USAGE);
|
|
2509
|
-
const outDir = (0,
|
|
2576
|
+
const outDir = (0, import_node_path8.resolve)(stringOpt(parsed.options.out) ?? "design");
|
|
2510
2577
|
const result = unpackDesign(await readBundle(source, deps));
|
|
2511
2578
|
await writeAll(result, outDir);
|
|
2512
2579
|
const out = deps.stdout ?? console;
|
|
@@ -3111,12 +3178,12 @@ async function safeText4(response2) {
|
|
|
3111
3178
|
|
|
3112
3179
|
// src/config-operation-command.ts
|
|
3113
3180
|
var import_apps6 = require("@odla-ai/apps");
|
|
3114
|
-
var
|
|
3181
|
+
var import_node_path9 = require("path");
|
|
3115
3182
|
|
|
3116
3183
|
// src/version.ts
|
|
3117
|
-
var
|
|
3184
|
+
var import_node_fs10 = require("fs");
|
|
3118
3185
|
function cliVersion() {
|
|
3119
|
-
const pkg = JSON.parse((0,
|
|
3186
|
+
const pkg = JSON.parse((0, import_node_fs10.readFileSync)(new URL("../package.json", importMetaUrl), "utf8"));
|
|
3120
3187
|
return pkg.version ?? "unknown";
|
|
3121
3188
|
}
|
|
3122
3189
|
|
|
@@ -3132,7 +3199,7 @@ var ConfigOperationCommandError = class extends Error {
|
|
|
3132
3199
|
|
|
3133
3200
|
// src/config-operation-validate.ts
|
|
3134
3201
|
var import_apps3 = require("@odla-ai/apps");
|
|
3135
|
-
var
|
|
3202
|
+
var import_node_fs11 = require("fs");
|
|
3136
3203
|
|
|
3137
3204
|
// src/config-reconcile-digest.ts
|
|
3138
3205
|
var import_node_crypto2 = require("crypto");
|
|
@@ -3168,7 +3235,7 @@ var SERVICE = /^[a-z][a-z0-9-]{0,39}$/;
|
|
|
3168
3235
|
function readPlan(path) {
|
|
3169
3236
|
let value2;
|
|
3170
3237
|
try {
|
|
3171
|
-
const raw = (0,
|
|
3238
|
+
const raw = (0, import_node_fs11.readFileSync)(path, "utf8");
|
|
3172
3239
|
if (Buffer.byteLength(raw) > 128 * 1024) throw new Error("plan exceeds 128 KiB");
|
|
3173
3240
|
value2 = JSON.parse(raw);
|
|
3174
3241
|
} catch (error) {
|
|
@@ -3541,7 +3608,7 @@ async function operationClient(cfg, options, purpose) {
|
|
|
3541
3608
|
platform: cfg.platformUrl,
|
|
3542
3609
|
scope: "app:config:write",
|
|
3543
3610
|
token: options.token,
|
|
3544
|
-
tokenFile: (0,
|
|
3611
|
+
tokenFile: (0, import_node_path9.join)(cfg.rootDir, ".odla", "admin-token.local.json"),
|
|
3545
3612
|
rootDir: cfg.rootDir,
|
|
3546
3613
|
email: options.email,
|
|
3547
3614
|
open: options.open,
|
|
@@ -3596,7 +3663,7 @@ function record4(value2) {
|
|
|
3596
3663
|
|
|
3597
3664
|
// src/config-reconcile-command.ts
|
|
3598
3665
|
var import_apps8 = require("@odla-ai/apps");
|
|
3599
|
-
var
|
|
3666
|
+
var import_node_path10 = require("path");
|
|
3600
3667
|
|
|
3601
3668
|
// src/config-reconcile.ts
|
|
3602
3669
|
var import_apps7 = require("@odla-ai/apps");
|
|
@@ -3892,7 +3959,7 @@ async function inspectConfig(options) {
|
|
|
3892
3959
|
platform: cfg.platformUrl,
|
|
3893
3960
|
scope: "app:config:read",
|
|
3894
3961
|
token: options.token,
|
|
3895
|
-
tokenFile: (0,
|
|
3962
|
+
tokenFile: (0, import_node_path10.join)(cfg.rootDir, ".odla", "admin-token.local.json"),
|
|
3896
3963
|
rootDir: cfg.rootDir,
|
|
3897
3964
|
email: options.email,
|
|
3898
3965
|
open: options.open,
|
|
@@ -4024,13 +4091,13 @@ function quoteArg2(value2) {
|
|
|
4024
4091
|
|
|
4025
4092
|
// src/doctor-checks.ts
|
|
4026
4093
|
var import_node_child_process3 = require("child_process");
|
|
4027
|
-
var
|
|
4028
|
-
var
|
|
4094
|
+
var import_node_fs13 = require("fs");
|
|
4095
|
+
var import_node_path12 = require("path");
|
|
4029
4096
|
|
|
4030
4097
|
// src/wrangler.ts
|
|
4031
4098
|
var import_node_child_process2 = require("child_process");
|
|
4032
|
-
var
|
|
4033
|
-
var
|
|
4099
|
+
var import_node_fs12 = require("fs");
|
|
4100
|
+
var import_node_path11 = require("path");
|
|
4034
4101
|
var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
|
|
4035
4102
|
const child = (0, import_node_child_process2.spawn)(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
|
|
4036
4103
|
let stdout = "";
|
|
@@ -4044,15 +4111,15 @@ var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) =>
|
|
|
4044
4111
|
var WRANGLER_CONFIG_FILES = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"];
|
|
4045
4112
|
function findWranglerConfig(rootDir) {
|
|
4046
4113
|
for (const name of WRANGLER_CONFIG_FILES) {
|
|
4047
|
-
const path = (0,
|
|
4048
|
-
if ((0,
|
|
4114
|
+
const path = (0, import_node_path11.join)(rootDir, name);
|
|
4115
|
+
if ((0, import_node_fs12.existsSync)(path)) return path;
|
|
4049
4116
|
}
|
|
4050
4117
|
return null;
|
|
4051
4118
|
}
|
|
4052
4119
|
function readWranglerConfig(path) {
|
|
4053
4120
|
if (path.endsWith(".toml")) return null;
|
|
4054
4121
|
try {
|
|
4055
|
-
return JSON.parse(stripJsonComments((0,
|
|
4122
|
+
return JSON.parse(stripJsonComments((0, import_node_fs12.readFileSync)(path, "utf8")));
|
|
4056
4123
|
} catch {
|
|
4057
4124
|
return null;
|
|
4058
4125
|
}
|
|
@@ -4202,10 +4269,10 @@ function wranglerWarnings(rootDir) {
|
|
|
4202
4269
|
for (const { label, block } of blocks) {
|
|
4203
4270
|
const assets = block.assets;
|
|
4204
4271
|
if (assets?.directory) {
|
|
4205
|
-
const dir = (0,
|
|
4206
|
-
if (dir === (0,
|
|
4272
|
+
const dir = (0, import_node_path12.resolve)(rootDir, assets.directory);
|
|
4273
|
+
if (dir === (0, import_node_path12.resolve)(rootDir)) {
|
|
4207
4274
|
warnings.push(`${label}assets.directory is the project root \u2014 point it at a dedicated build dir (wrangler dev fails with "spawn EBADF")`);
|
|
4208
|
-
} else if ((0,
|
|
4275
|
+
} else if ((0, import_node_fs13.existsSync)((0, import_node_path12.join)(dir, "node_modules"))) {
|
|
4209
4276
|
warnings.push(`${label}assets.directory contains node_modules \u2014 wrangler dev's watcher will exhaust file descriptors`);
|
|
4210
4277
|
}
|
|
4211
4278
|
}
|
|
@@ -4240,13 +4307,13 @@ function o11yProjectWarnings(rootDir) {
|
|
|
4240
4307
|
warnings.push("cannot verify o11y Worker instrumentation \u2014 add a parseable wrangler.jsonc/json config");
|
|
4241
4308
|
return warnings;
|
|
4242
4309
|
}
|
|
4243
|
-
const main = typeof config.main === "string" ? (0,
|
|
4244
|
-
if (!main || !(0,
|
|
4310
|
+
const main = typeof config.main === "string" ? (0, import_node_path12.resolve)(rootDir, config.main) : null;
|
|
4311
|
+
if (!main || !(0, import_node_fs13.existsSync)(main)) {
|
|
4245
4312
|
warnings.push("cannot verify o11y Worker instrumentation \u2014 wrangler main is missing or unreadable");
|
|
4246
4313
|
} else {
|
|
4247
4314
|
let source = "";
|
|
4248
4315
|
try {
|
|
4249
|
-
source = (0,
|
|
4316
|
+
source = (0, import_node_fs13.readFileSync)(main, "utf8");
|
|
4250
4317
|
} catch {
|
|
4251
4318
|
}
|
|
4252
4319
|
if (!/\bwithObservability\b/.test(source)) {
|
|
@@ -4270,7 +4337,7 @@ function calendarProjectWarnings(rootDir) {
|
|
|
4270
4337
|
}
|
|
4271
4338
|
function readPackageJson(rootDir) {
|
|
4272
4339
|
try {
|
|
4273
|
-
return JSON.parse((0,
|
|
4340
|
+
return JSON.parse((0, import_node_fs13.readFileSync)((0, import_node_path12.join)(rootDir, "package.json"), "utf8"));
|
|
4274
4341
|
} catch {
|
|
4275
4342
|
return null;
|
|
4276
4343
|
}
|
|
@@ -4564,14 +4631,14 @@ function harnessOption(value2, flag) {
|
|
|
4564
4631
|
}
|
|
4565
4632
|
|
|
4566
4633
|
// src/init.ts
|
|
4567
|
-
var
|
|
4568
|
-
var
|
|
4634
|
+
var import_node_fs14 = require("fs");
|
|
4635
|
+
var import_node_path13 = require("path");
|
|
4569
4636
|
var import_apps9 = require("@odla-ai/apps");
|
|
4570
4637
|
function initProject(options) {
|
|
4571
4638
|
const out = options.stdout ?? console;
|
|
4572
|
-
const rootDir = (0,
|
|
4573
|
-
const configPath = (0,
|
|
4574
|
-
if ((0,
|
|
4639
|
+
const rootDir = (0, import_node_path13.resolve)(options.rootDir ?? process.cwd());
|
|
4640
|
+
const configPath = (0, import_node_path13.resolve)(rootDir, options.configPath ?? "odla.config.mjs");
|
|
4641
|
+
if ((0, import_node_fs14.existsSync)(configPath) && !options.force) {
|
|
4575
4642
|
throw new Error(`${configPath} already exists. Pass --force to overwrite.`);
|
|
4576
4643
|
}
|
|
4577
4644
|
if (!/^[a-z0-9][a-z0-9-]*$/.test(options.appId)) {
|
|
@@ -4587,20 +4654,20 @@ function initProject(options) {
|
|
|
4587
4654
|
}
|
|
4588
4655
|
}
|
|
4589
4656
|
const aiProvider = options.aiProvider;
|
|
4590
|
-
(0,
|
|
4591
|
-
(0,
|
|
4592
|
-
(0,
|
|
4593
|
-
(0,
|
|
4594
|
-
writeIfMissing((0,
|
|
4595
|
-
writeIfMissing((0,
|
|
4657
|
+
(0, import_node_fs14.mkdirSync)((0, import_node_path13.dirname)(configPath), { recursive: true });
|
|
4658
|
+
(0, import_node_fs14.mkdirSync)((0, import_node_path13.resolve)(rootDir, "src/odla"), { recursive: true });
|
|
4659
|
+
(0, import_node_fs14.mkdirSync)((0, import_node_path13.resolve)(rootDir, ".odla"), { recursive: true });
|
|
4660
|
+
(0, import_node_fs14.writeFileSync)(configPath, configTemplate({ appId: options.appId, name: options.name, envs, services, aiProvider }));
|
|
4661
|
+
writeIfMissing((0, import_node_path13.resolve)(rootDir, "src/odla/schema.mjs"), schemaTemplate());
|
|
4662
|
+
writeIfMissing((0, import_node_path13.resolve)(rootDir, "src/odla/rules.mjs"), rulesTemplate());
|
|
4596
4663
|
ensureGitignore(rootDir);
|
|
4597
4664
|
out.log(`created ${relativeDisplay(configPath, rootDir)}`);
|
|
4598
4665
|
out.log("created src/odla/schema.mjs and src/odla/rules.mjs");
|
|
4599
4666
|
out.log("updated .gitignore for local odla credentials");
|
|
4600
4667
|
}
|
|
4601
4668
|
function writeIfMissing(path, text3) {
|
|
4602
|
-
if ((0,
|
|
4603
|
-
(0,
|
|
4669
|
+
if ((0, import_node_fs14.existsSync)(path)) return;
|
|
4670
|
+
(0, import_node_fs14.writeFileSync)(path, text3);
|
|
4604
4671
|
}
|
|
4605
4672
|
function configTemplate(input) {
|
|
4606
4673
|
const calendar = input.services.includes("calendar") ? ` calendar: {
|
|
@@ -4896,9 +4963,9 @@ function printReport(report5, out) {
|
|
|
4896
4963
|
}
|
|
4897
4964
|
|
|
4898
4965
|
// src/skill.ts
|
|
4899
|
-
var
|
|
4900
|
-
var
|
|
4901
|
-
var
|
|
4966
|
+
var import_node_fs15 = require("fs");
|
|
4967
|
+
var import_node_os3 = require("os");
|
|
4968
|
+
var import_node_path14 = require("path");
|
|
4902
4969
|
var import_node_url2 = require("url");
|
|
4903
4970
|
|
|
4904
4971
|
// src/skill-adapters.ts
|
|
@@ -4997,8 +5064,8 @@ function installSkill(options = {}) {
|
|
|
4997
5064
|
const files = listFiles(sourceDir);
|
|
4998
5065
|
if (files.length === 0) throw new Error(`no bundled skills found at ${sourceDir}`);
|
|
4999
5066
|
const harnesses = normalizeHarnesses(options.harnesses, options.global === true);
|
|
5000
|
-
const root = (0,
|
|
5001
|
-
const home = (0,
|
|
5067
|
+
const root = (0, import_node_path14.resolve)(options.dir ?? process.cwd());
|
|
5068
|
+
const home = (0, import_node_path14.resolve)(options.homeDir ?? (0, import_node_os3.homedir)());
|
|
5002
5069
|
const plans = /* @__PURE__ */ new Map();
|
|
5003
5070
|
const targets = /* @__PURE__ */ new Map();
|
|
5004
5071
|
const rememberTarget = (harness, target) => {
|
|
@@ -5012,48 +5079,48 @@ function installSkill(options = {}) {
|
|
|
5012
5079
|
plans.set(target, { target, content: content2, boundary, managedMerge });
|
|
5013
5080
|
};
|
|
5014
5081
|
const planSkillTree = (targetDir2, boundary = root) => {
|
|
5015
|
-
for (const rel of files) plan((0,
|
|
5082
|
+
for (const rel of files) plan((0, import_node_path14.join)(targetDir2, rel), (0, import_node_fs15.readFileSync)((0, import_node_path14.join)(sourceDir, rel), "utf8"), false, boundary);
|
|
5016
5083
|
};
|
|
5017
5084
|
let targetDir;
|
|
5018
5085
|
if (options.global) {
|
|
5019
|
-
const claudeRoot = (0,
|
|
5020
|
-
const codexRoot = (0,
|
|
5086
|
+
const claudeRoot = (0, import_node_path14.join)(home, ".claude", "skills");
|
|
5087
|
+
const codexRoot = (0, import_node_path14.resolve)(options.codexHomeDir ?? process.env.CODEX_HOME ?? (0, import_node_path14.join)(home, ".codex"), "skills");
|
|
5021
5088
|
targetDir = harnesses[0] === "codex" ? codexRoot : claudeRoot;
|
|
5022
5089
|
for (const harness of harnesses) {
|
|
5023
5090
|
const skillRoot = harness === "claude" ? claudeRoot : codexRoot;
|
|
5024
|
-
planSkillTree(skillRoot, harness === "claude" ? home : (0,
|
|
5091
|
+
planSkillTree(skillRoot, harness === "claude" ? home : (0, import_node_path14.dirname)((0, import_node_path14.dirname)(codexRoot)));
|
|
5025
5092
|
rememberTarget(harness, skillRoot);
|
|
5026
5093
|
}
|
|
5027
5094
|
} else {
|
|
5028
|
-
const sharedRoot = (0,
|
|
5095
|
+
const sharedRoot = (0, import_node_path14.join)(root, ".agents", "skills");
|
|
5029
5096
|
planSkillTree(sharedRoot);
|
|
5030
|
-
const claudeRoot = (0,
|
|
5097
|
+
const claudeRoot = (0, import_node_path14.join)(root, ".claude", "skills");
|
|
5031
5098
|
targetDir = harnesses.includes("claude") ? claudeRoot : sharedRoot;
|
|
5032
5099
|
for (const harness of harnesses) rememberTarget(harness, sharedRoot);
|
|
5033
5100
|
if (harnesses.includes("claude")) {
|
|
5034
5101
|
for (const skill of skillNames(files)) {
|
|
5035
|
-
const canonical2 = (0,
|
|
5036
|
-
plan((0,
|
|
5102
|
+
const canonical2 = (0, import_node_fs15.readFileSync)((0, import_node_path14.join)(sourceDir, skill, "SKILL.md"), "utf8");
|
|
5103
|
+
plan((0, import_node_path14.join)(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical2));
|
|
5037
5104
|
}
|
|
5038
5105
|
rememberTarget("claude", claudeRoot);
|
|
5039
5106
|
}
|
|
5040
5107
|
if (harnesses.includes("cursor")) {
|
|
5041
|
-
const cursorRule = (0,
|
|
5108
|
+
const cursorRule = (0, import_node_path14.join)(root, ".cursor", "rules", "odla.mdc");
|
|
5042
5109
|
plan(cursorRule, CURSOR_RULE);
|
|
5043
5110
|
rememberTarget("cursor", cursorRule);
|
|
5044
5111
|
}
|
|
5045
5112
|
if (harnesses.includes("agents")) {
|
|
5046
|
-
const agentsFile = (0,
|
|
5113
|
+
const agentsFile = (0, import_node_path14.join)(root, "AGENTS.md");
|
|
5047
5114
|
plan(agentsFile, managedFileContent(agentsFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
5048
5115
|
rememberTarget("agents", agentsFile);
|
|
5049
5116
|
}
|
|
5050
5117
|
if (harnesses.includes("copilot")) {
|
|
5051
|
-
const copilotFile = (0,
|
|
5118
|
+
const copilotFile = (0, import_node_path14.join)(root, ".github", "copilot-instructions.md");
|
|
5052
5119
|
plan(copilotFile, managedFileContent(copilotFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
5053
5120
|
rememberTarget("copilot", copilotFile);
|
|
5054
5121
|
}
|
|
5055
5122
|
if (harnesses.includes("gemini")) {
|
|
5056
|
-
const geminiFile = (0,
|
|
5123
|
+
const geminiFile = (0, import_node_path14.join)(root, "GEMINI.md");
|
|
5057
5124
|
plan(geminiFile, managedFileContent(geminiFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
5058
5125
|
rememberTarget("gemini", geminiFile);
|
|
5059
5126
|
}
|
|
@@ -5067,11 +5134,11 @@ function installSkill(options = {}) {
|
|
|
5067
5134
|
conflicts.push(`${file.target} (redirected by symbolic link ${symlink})`);
|
|
5068
5135
|
continue;
|
|
5069
5136
|
}
|
|
5070
|
-
if (!(0,
|
|
5137
|
+
if (!(0, import_node_fs15.existsSync)(file.target)) {
|
|
5071
5138
|
writtenPaths.add(file.target);
|
|
5072
5139
|
continue;
|
|
5073
5140
|
}
|
|
5074
|
-
const current = (0,
|
|
5141
|
+
const current = (0, import_node_fs15.readFileSync)(file.target, "utf8");
|
|
5075
5142
|
if (current === file.content) {
|
|
5076
5143
|
unchangedPaths.add(file.target);
|
|
5077
5144
|
} else if (file.managedMerge || options.force) {
|
|
@@ -5088,9 +5155,9 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
|
|
|
5088
5155
|
);
|
|
5089
5156
|
}
|
|
5090
5157
|
for (const file of plans.values()) {
|
|
5091
|
-
if (!(0,
|
|
5092
|
-
(0,
|
|
5093
|
-
(0,
|
|
5158
|
+
if (!(0, import_node_fs15.existsSync)(file.target) || (0, import_node_fs15.readFileSync)(file.target, "utf8") !== file.content) {
|
|
5159
|
+
(0, import_node_fs15.mkdirSync)((0, import_node_path14.dirname)(file.target), { recursive: true });
|
|
5160
|
+
(0, import_node_fs15.writeFileSync)(file.target, file.content);
|
|
5094
5161
|
}
|
|
5095
5162
|
}
|
|
5096
5163
|
const skills = skillNames(files);
|
|
@@ -5109,7 +5176,7 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
|
|
|
5109
5176
|
};
|
|
5110
5177
|
}
|
|
5111
5178
|
function pathsUnder(root, paths) {
|
|
5112
|
-
return [...paths].map((path) => (0,
|
|
5179
|
+
return [...paths].map((path) => (0, import_node_path14.relative)(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${import_node_path14.sep}`) && !(0, import_node_path14.isAbsolute)(path)).sort();
|
|
5113
5180
|
}
|
|
5114
5181
|
function normalizeHarnesses(values, global) {
|
|
5115
5182
|
const requested = values?.length ? values : ["claude"];
|
|
@@ -5131,9 +5198,9 @@ function normalizeHarnesses(values, global) {
|
|
|
5131
5198
|
function managedFileContent(path, block, force, boundary) {
|
|
5132
5199
|
const symlink = symlinkedComponent(boundary, path);
|
|
5133
5200
|
if (symlink) throw new Error(`refusing to manage ${path}: symbolic link component ${symlink}`);
|
|
5134
|
-
if (!(0,
|
|
5201
|
+
if (!(0, import_node_fs15.existsSync)(path)) return `${block}
|
|
5135
5202
|
`;
|
|
5136
|
-
const current = (0,
|
|
5203
|
+
const current = (0, import_node_fs15.readFileSync)(path, "utf8");
|
|
5137
5204
|
const start = "<!-- odla-ai agent setup:start -->";
|
|
5138
5205
|
const end = "<!-- odla-ai agent setup:end -->";
|
|
5139
5206
|
const startAt = current.indexOf(start);
|
|
@@ -5154,15 +5221,15 @@ function managedFileContent(path, block, force, boundary) {
|
|
|
5154
5221
|
return `${current.slice(0, startAt)}${block}${current.slice(afterEnd)}`;
|
|
5155
5222
|
}
|
|
5156
5223
|
function symlinkedComponent(boundary, target) {
|
|
5157
|
-
const rel = (0,
|
|
5158
|
-
if (rel === ".." || rel.startsWith(`..${
|
|
5224
|
+
const rel = (0, import_node_path14.relative)(boundary, target);
|
|
5225
|
+
if (rel === ".." || rel.startsWith(`..${import_node_path14.sep}`) || (0, import_node_path14.isAbsolute)(rel)) {
|
|
5159
5226
|
throw new Error(`agent setup target escapes its install root: ${target}`);
|
|
5160
5227
|
}
|
|
5161
5228
|
let current = boundary;
|
|
5162
|
-
for (const part of rel.split(
|
|
5163
|
-
current = (0,
|
|
5229
|
+
for (const part of rel.split(import_node_path14.sep).filter(Boolean)) {
|
|
5230
|
+
current = (0, import_node_path14.join)(current, part);
|
|
5164
5231
|
try {
|
|
5165
|
-
if ((0,
|
|
5232
|
+
if ((0, import_node_fs15.lstatSync)(current).isSymbolicLink()) return current;
|
|
5166
5233
|
} catch (error) {
|
|
5167
5234
|
if (error.code !== "ENOENT") throw error;
|
|
5168
5235
|
}
|
|
@@ -5173,13 +5240,13 @@ function skillNames(files) {
|
|
|
5173
5240
|
return [...new Set(files.filter((file) => /(^|[\\/])SKILL\.md$/.test(file)).map((file) => file.split(/[\\/]/)[0]))].sort();
|
|
5174
5241
|
}
|
|
5175
5242
|
function listFiles(dir) {
|
|
5176
|
-
if (!(0,
|
|
5243
|
+
if (!(0, import_node_fs15.existsSync)(dir)) return [];
|
|
5177
5244
|
const results = [];
|
|
5178
5245
|
const walk = (current) => {
|
|
5179
|
-
for (const entry of (0,
|
|
5180
|
-
const path = (0,
|
|
5246
|
+
for (const entry of (0, import_node_fs15.readdirSync)(current, { withFileTypes: true })) {
|
|
5247
|
+
const path = (0, import_node_path14.join)(current, entry.name);
|
|
5181
5248
|
if (entry.isDirectory()) walk(path);
|
|
5182
|
-
else results.push((0,
|
|
5249
|
+
else results.push((0, import_node_path14.relative)(dir, path));
|
|
5183
5250
|
}
|
|
5184
5251
|
};
|
|
5185
5252
|
walk(dir);
|
|
@@ -5534,9 +5601,9 @@ async function projectCommand(command, parsed, deps) {
|
|
|
5534
5601
|
}
|
|
5535
5602
|
|
|
5536
5603
|
// src/code-connect.ts
|
|
5537
|
-
var
|
|
5538
|
-
var
|
|
5539
|
-
var
|
|
5604
|
+
var import_node_fs16 = require("fs");
|
|
5605
|
+
var import_node_os4 = require("os");
|
|
5606
|
+
var import_node_path15 = require("path");
|
|
5540
5607
|
|
|
5541
5608
|
// ../harness/dist/chunk-3QP4VDQS.js
|
|
5542
5609
|
var HARNESS_PROTOCOL_VERSION = 1;
|
|
@@ -6645,8 +6712,8 @@ function rollup(graph, kind, options = {}) {
|
|
|
6645
6712
|
for (const node of nodesOfKind(graph, kind)) {
|
|
6646
6713
|
if (options.prefix && !node.name.startsWith(options.prefix)) continue;
|
|
6647
6714
|
const key = node.name.split(separator).slice(0, depth).join(separator);
|
|
6648
|
-
const
|
|
6649
|
-
if (
|
|
6715
|
+
const list3 = groups.get(key);
|
|
6716
|
+
if (list3) list3.push(node);
|
|
6650
6717
|
else groups.set(key, [node]);
|
|
6651
6718
|
}
|
|
6652
6719
|
return [...groups].map(([prefix, nodes]) => ({
|
|
@@ -6661,7 +6728,7 @@ function dirname8(path) {
|
|
|
6661
6728
|
const at = path.lastIndexOf("/");
|
|
6662
6729
|
return at <= 0 ? "." : path.slice(0, at);
|
|
6663
6730
|
}
|
|
6664
|
-
function
|
|
6731
|
+
function join12(base, specifier) {
|
|
6665
6732
|
const parts = [];
|
|
6666
6733
|
const segments = `${base === "." ? "" : `${base}/`}${specifier}`.split("/");
|
|
6667
6734
|
for (const segment of segments) {
|
|
@@ -6685,7 +6752,7 @@ var BARE_IMPORT = /^\s*import\s*["']([^"']+)["']/gm;
|
|
|
6685
6752
|
var isSourcePath = (path) => SOURCE.test(path);
|
|
6686
6753
|
function resolveImport(fromPath, specifier, known) {
|
|
6687
6754
|
if (!specifier.startsWith(".")) return null;
|
|
6688
|
-
const base =
|
|
6755
|
+
const base = join12(dirname8(fromPath), specifier);
|
|
6689
6756
|
const candidates = [
|
|
6690
6757
|
base,
|
|
6691
6758
|
base.replace(/\.js$/, ".ts"),
|
|
@@ -9583,8 +9650,8 @@ var CODE_BUILD_RECIPES = Object.freeze([{
|
|
|
9583
9650
|
// src/code-connect.ts
|
|
9584
9651
|
async function codeConnect(options) {
|
|
9585
9652
|
const cwd = options.cwd ?? process.cwd();
|
|
9586
|
-
const configPath = (0,
|
|
9587
|
-
const cfg = (0,
|
|
9653
|
+
const configPath = (0, import_node_path15.resolve)(cwd, options.configPath);
|
|
9654
|
+
const cfg = (0, import_node_fs16.existsSync)(configPath) ? await loadProjectConfig(configPath) : null;
|
|
9588
9655
|
const requestedAppId = options.appId?.trim();
|
|
9589
9656
|
if (requestedAppId && !/^[a-z0-9][a-z0-9-]{1,62}$/.test(requestedAppId)) {
|
|
9590
9657
|
throw new Error("--app-id must be a valid odla app id");
|
|
@@ -9613,7 +9680,7 @@ async function codeConnect(options) {
|
|
|
9613
9680
|
const doFetch = options.fetch ?? fetch;
|
|
9614
9681
|
const engine = await (options.selectEngine ?? selectContainerEngine)(options.engine ?? "auto");
|
|
9615
9682
|
const hostPlatform = process.platform === "darwin" ? "macos" : "linux";
|
|
9616
|
-
const hostName = (options.name ?? (0,
|
|
9683
|
+
const hostName = (options.name ?? (0, import_node_os4.hostname)()).trim();
|
|
9617
9684
|
if (!hostName || hostName.length > 120) throw new Error("--name must contain 1 to 120 characters");
|
|
9618
9685
|
const repository = await inferGitHubRepository(cwd, options.readGitOrigin);
|
|
9619
9686
|
const localSource = await (options.prepareLocalSource ?? prepareCodeLocalSource)(
|
|
@@ -9650,8 +9717,8 @@ async function codeConnect(options) {
|
|
|
9650
9717
|
platform: hostPlatform,
|
|
9651
9718
|
arch: process.arch,
|
|
9652
9719
|
engines: [engine],
|
|
9653
|
-
cpuCount: (0,
|
|
9654
|
-
memoryBytes: (0,
|
|
9720
|
+
cpuCount: (0, import_node_os4.cpus)().length,
|
|
9721
|
+
memoryBytes: (0, import_node_os4.totalmem)(),
|
|
9655
9722
|
source: descriptor2,
|
|
9656
9723
|
images: {
|
|
9657
9724
|
ready: true,
|
|
@@ -10018,13 +10085,13 @@ async function codeCommand(parsed, dependencies) {
|
|
|
10018
10085
|
}
|
|
10019
10086
|
|
|
10020
10087
|
// src/operator-credentials.ts
|
|
10021
|
-
var
|
|
10088
|
+
var import_node_process12 = __toESM(require("process"), 1);
|
|
10022
10089
|
function developerTokenStatus(context, parsed, now = Date.now()) {
|
|
10023
10090
|
const cached = readJsonFile(context.cfg.local.tokenFile);
|
|
10024
10091
|
const cacheStatus = !cached?.token ? "missing" : cached.platform !== context.platform.value ? "other-platform" : (cached.expiresAt ?? 0) <= now + 6e4 ? "expired" : "valid";
|
|
10025
10092
|
const source = clean3(
|
|
10026
10093
|
stringOpt(parsed.options.token)
|
|
10027
|
-
) ? "flag" : clean3(
|
|
10094
|
+
) ? "flag" : clean3(import_node_process12.default.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
|
|
10028
10095
|
return {
|
|
10029
10096
|
source,
|
|
10030
10097
|
cacheFile: context.cfg.local.tokenFile,
|
|
@@ -10346,6 +10413,9 @@ Usage:
|
|
|
10346
10413
|
odla-ai security run [target] --ack-redacted-source [--env dev] [--profile odla] [--fail-on high]
|
|
10347
10414
|
odla-ai security run [target] --self --ack-redacted-source
|
|
10348
10415
|
odla-ai provision [--live] [--config odla.config.mjs] [--email <odla-account>] [--request-grant] [--no-open] [--wait <seconds>] [--dry-run] [--push-secrets] [--rotate-o11y-token] [--write-dev-vars[=path]] [--yes]
|
|
10416
|
+
odla-ai device enroll [--app <id>[,<id>...]] [--name <label>] [--capability <c>[,<c>...]] [--email <odla-account>] [--no-open] [--json]
|
|
10417
|
+
odla-ai device list [--email <odla-account>] [--json]
|
|
10418
|
+
odla-ai device revoke <device-id> [--email <odla-account>] [--json]
|
|
10349
10419
|
odla-ai credentials list [--config odla.config.mjs] [--env dev] [--all] [--json]
|
|
10350
10420
|
odla-ai credentials revoke <receipt-id> [--config odla.config.mjs] [--json]
|
|
10351
10421
|
odla-ai smoke [--config odla.config.mjs] [--env dev] [--runtime] [--email <odla-account>] [--no-open]
|
|
@@ -10451,6 +10521,10 @@ Commands:
|
|
|
10451
10521
|
stable status, incident, and report JSON to agents and CI.
|
|
10452
10522
|
platform Read canonical fleet health, releases, provider load/freshness,
|
|
10453
10523
|
explicit unknowns, and next actions through a read-only grant.
|
|
10524
|
+
device Enrol THIS machine once, then stop asking. A human approves the
|
|
10525
|
+
enrollment in the browser; from then on this terminal mints its
|
|
10526
|
+
own short-lived credentials for the named projects with nobody's
|
|
10527
|
+
attention, until the device expires or is revoked.
|
|
10454
10528
|
provision Register services, compose integrations, persist credentials, optionally push secrets.
|
|
10455
10529
|
"provision --live --yes" initializes only the live instance of
|
|
10456
10530
|
an existing sandbox app and enables every configured service;
|
|
@@ -11701,8 +11775,8 @@ async function pmWatch(ctx, parsed) {
|
|
|
11701
11775
|
}
|
|
11702
11776
|
|
|
11703
11777
|
// src/pm-project-context.ts
|
|
11704
|
-
var
|
|
11705
|
-
var pmProjectContextFile = (rootDir) => (0,
|
|
11778
|
+
var import_node_path16 = require("path");
|
|
11779
|
+
var pmProjectContextFile = (rootDir) => (0, import_node_path16.resolve)(rootDir, ".odla", "pm-project.local.json");
|
|
11706
11780
|
function readPmProjectContext(rootDir) {
|
|
11707
11781
|
const value2 = readJsonFile(pmProjectContextFile(rootDir));
|
|
11708
11782
|
return value2 && typeof value2.appId === "string" && typeof value2.projectId === "string" ? value2 : null;
|
|
@@ -12733,7 +12807,7 @@ function percent(value2) {
|
|
|
12733
12807
|
// src/provision.ts
|
|
12734
12808
|
var import_apps13 = require("@odla-ai/apps");
|
|
12735
12809
|
var import_ai5 = require("@odla-ai/ai");
|
|
12736
|
-
var
|
|
12810
|
+
var import_node_process13 = __toESM(require("process"), 1);
|
|
12737
12811
|
|
|
12738
12812
|
// src/integration-provision.ts
|
|
12739
12813
|
var import_db3 = require("@odla-ai/db");
|
|
@@ -13169,7 +13243,7 @@ async function provision(options) {
|
|
|
13169
13243
|
await provisionIntegrationSeeds(doFetch, cfg.dbEndpoint, tenantId, dbKey, database.integrations, env, out);
|
|
13170
13244
|
}
|
|
13171
13245
|
if (cfg.services.includes("ai") && cfg.ai?.provider && cfg.ai.keyEnv) {
|
|
13172
|
-
const key =
|
|
13246
|
+
const key = import_node_process13.default.env[cfg.ai.keyEnv];
|
|
13173
13247
|
if (key) {
|
|
13174
13248
|
const secretName = cfg.ai.secretName ?? defaultSecretName(cfg.ai.provider);
|
|
13175
13249
|
await (0, import_ai5.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, secretName, key);
|
|
@@ -13210,8 +13284,8 @@ async function provision(options) {
|
|
|
13210
13284
|
}
|
|
13211
13285
|
|
|
13212
13286
|
// src/record.ts
|
|
13213
|
-
var
|
|
13214
|
-
var
|
|
13287
|
+
var import_node_fs17 = require("fs");
|
|
13288
|
+
var import_node_process14 = __toESM(require("process"), 1);
|
|
13215
13289
|
|
|
13216
13290
|
// src/surface.ts
|
|
13217
13291
|
var PM_ACTIONS = {
|
|
@@ -13281,6 +13355,7 @@ var COMMAND_SURFACE = {
|
|
|
13281
13355
|
config: { diff: {}, plan: {}, apply: {} },
|
|
13282
13356
|
context: { show: {}, list: {}, save: {}, remove: {} },
|
|
13283
13357
|
credentials: { list: {}, revoke: {} },
|
|
13358
|
+
device: { enroll: {}, list: {}, revoke: {} },
|
|
13284
13359
|
// `watch`, `read`, `reply`, and `resolve` take a topic id from there on.
|
|
13285
13360
|
discuss: {
|
|
13286
13361
|
groups: {},
|
|
@@ -13392,7 +13467,7 @@ function surfacePaths(node = COMMAND_SURFACE, prefix = []) {
|
|
|
13392
13467
|
|
|
13393
13468
|
// src/record.ts
|
|
13394
13469
|
function recordInvocation(parsed) {
|
|
13395
|
-
const file =
|
|
13470
|
+
const file = import_node_process14.default.env.ODLA_CLI_RECORD;
|
|
13396
13471
|
if (!file) return;
|
|
13397
13472
|
try {
|
|
13398
13473
|
const entry = {
|
|
@@ -13400,14 +13475,138 @@ function recordInvocation(parsed) {
|
|
|
13400
13475
|
options: Object.entries(parsed.options).map(([name, value2]) => value2 === false ? `no-${name}` : name).sort()
|
|
13401
13476
|
};
|
|
13402
13477
|
if (!entry.path.length) return;
|
|
13403
|
-
(0,
|
|
13478
|
+
(0, import_node_fs17.appendFileSync)(file, `${JSON.stringify(entry)}
|
|
13404
13479
|
`);
|
|
13405
13480
|
} catch {
|
|
13406
13481
|
}
|
|
13407
13482
|
}
|
|
13408
13483
|
|
|
13484
|
+
// src/advisory-output.ts
|
|
13485
|
+
var import_apps14 = require("@odla-ai/apps");
|
|
13486
|
+
function advisoryCollectingFetch(inner, sink) {
|
|
13487
|
+
return (async (input, init) => {
|
|
13488
|
+
const response2 = await inner(input, init);
|
|
13489
|
+
try {
|
|
13490
|
+
sink.push(...(0, import_apps14.parseAdvisories)(response2));
|
|
13491
|
+
} catch {
|
|
13492
|
+
}
|
|
13493
|
+
return response2;
|
|
13494
|
+
});
|
|
13495
|
+
}
|
|
13496
|
+
function renderAdvisories(out, advisories, env = process.env) {
|
|
13497
|
+
if (env.ODLA_NO_ADVISORIES) return;
|
|
13498
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13499
|
+
for (const advisory of advisories) {
|
|
13500
|
+
const key = `${advisory.code}:${advisory.message}`;
|
|
13501
|
+
if (seen.has(key)) continue;
|
|
13502
|
+
seen.add(key);
|
|
13503
|
+
out.error((0, import_apps14.formatAdvisory)(advisory));
|
|
13504
|
+
}
|
|
13505
|
+
}
|
|
13506
|
+
|
|
13507
|
+
// src/device-command.ts
|
|
13508
|
+
var import_node_fs18 = require("fs");
|
|
13509
|
+
var import_node_path17 = require("path");
|
|
13510
|
+
var import_node_process15 = __toESM(require("process"), 1);
|
|
13511
|
+
async function deviceCommand(parsed, deps) {
|
|
13512
|
+
const action2 = parsed.positionals[1] ?? "";
|
|
13513
|
+
const out = deps.stdout ?? console;
|
|
13514
|
+
const doFetch = deps.fetch ?? fetch;
|
|
13515
|
+
const cfg = await loadProjectConfig(stringOpt(parsed.options.config));
|
|
13516
|
+
const json = parsed.options.json === true;
|
|
13517
|
+
if (action2 === "enroll") return enroll(parsed, deps, cfg, doFetch, out, json);
|
|
13518
|
+
if (action2 === "list") return list2(parsed, deps, cfg, doFetch, out, json);
|
|
13519
|
+
if (action2 === "revoke") return revoke(parsed, deps, cfg, doFetch, out, json);
|
|
13520
|
+
throw new Error('odla-ai device expects "enroll", "list", or "revoke"');
|
|
13521
|
+
}
|
|
13522
|
+
async function enroll(parsed, deps, cfg, doFetch, out, json) {
|
|
13523
|
+
const name = stringOpt(parsed.options.name) ?? defaultDeviceName();
|
|
13524
|
+
const apps = (stringOpt(parsed.options.app) ?? cfg.app.id).split(",").map((id2) => id2.trim()).filter(Boolean);
|
|
13525
|
+
if (apps.length === 0) throw new Error("device enroll needs --app <id>[,<id>\u2026]");
|
|
13526
|
+
const token = await scopedToken2(parsed, deps, cfg, doFetch, out, `odla CLI (enroll ${name})`);
|
|
13527
|
+
const response2 = await doFetch(`${cfg.platformUrl}/registry/devices`, {
|
|
13528
|
+
method: "POST",
|
|
13529
|
+
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
13530
|
+
body: JSON.stringify({
|
|
13531
|
+
name,
|
|
13532
|
+
platform: import_node_process15.default.platform,
|
|
13533
|
+
appIds: apps,
|
|
13534
|
+
...parsed.options.capability ? { capabilities: String(parsed.options.capability).split(",").map((c) => c.trim()).filter(Boolean) } : {}
|
|
13535
|
+
})
|
|
13536
|
+
});
|
|
13537
|
+
const body = await response2.json().catch(() => ({}));
|
|
13538
|
+
if (!response2.ok || !body.token || !body.device) {
|
|
13539
|
+
throw new Error(`device enroll failed: ${body.error?.message ?? `registry returned ${response2.status}`} (${response2.status})`);
|
|
13540
|
+
}
|
|
13541
|
+
const path = deviceCredentialPath();
|
|
13542
|
+
(0, import_node_fs18.mkdirSync)((0, import_node_path17.dirname)(path), { recursive: true });
|
|
13543
|
+
(0, import_node_fs18.writeFileSync)(path, JSON.stringify({
|
|
13544
|
+
token: body.token,
|
|
13545
|
+
platform: cfg.platformUrl.replace(/\/$/, ""),
|
|
13546
|
+
deviceId: body.device.deviceId,
|
|
13547
|
+
name
|
|
13548
|
+
}, null, 2));
|
|
13549
|
+
(0, import_node_fs18.chmodSync)(path, 384);
|
|
13550
|
+
out.error(`device: enrolled "${name}" for ${body.device.appIds.join(", ")}; credential written to ${path}`);
|
|
13551
|
+
out.error("device: this terminal will mint its own credentials from now on \u2014 no further approvals.");
|
|
13552
|
+
if (json) {
|
|
13553
|
+
out.log(JSON.stringify({ deviceId: body.device.deviceId, name, appIds: body.device.appIds, expiresAt: body.device.expiresAt }, null, 2));
|
|
13554
|
+
}
|
|
13555
|
+
}
|
|
13556
|
+
async function list2(parsed, deps, cfg, doFetch, out, json) {
|
|
13557
|
+
const token = await scopedToken2(parsed, deps, cfg, doFetch, out, "odla CLI (device list)");
|
|
13558
|
+
const response2 = await doFetch(`${cfg.platformUrl}/registry/devices`, {
|
|
13559
|
+
headers: { authorization: `Bearer ${token}` }
|
|
13560
|
+
});
|
|
13561
|
+
const body = await response2.json().catch(() => ({}));
|
|
13562
|
+
if (!response2.ok || !body.devices) {
|
|
13563
|
+
throw new Error(`device list failed: ${body.error?.message ?? `registry returned ${response2.status}`} (${response2.status})`);
|
|
13564
|
+
}
|
|
13565
|
+
if (json) return out.log(JSON.stringify(body.devices, null, 2));
|
|
13566
|
+
if (body.devices.length === 0) return out.log("no enrolled devices");
|
|
13567
|
+
for (const device of body.devices) {
|
|
13568
|
+
const state2 = device.revokedAt ? "revoked" : device.expiresAt <= Date.now() ? "expired" : "active";
|
|
13569
|
+
out.log(`${device.deviceId} ${state2.padEnd(7)} ${device.name} [${device.appIds.join(", ")}]`);
|
|
13570
|
+
}
|
|
13571
|
+
}
|
|
13572
|
+
async function revoke(parsed, deps, cfg, doFetch, out, json) {
|
|
13573
|
+
const deviceId = parsed.positionals[2];
|
|
13574
|
+
if (!deviceId) throw new Error("device revoke needs the device id from `odla-ai device list`");
|
|
13575
|
+
const token = await scopedToken2(parsed, deps, cfg, doFetch, out, "odla CLI (device revoke)");
|
|
13576
|
+
const response2 = await doFetch(`${cfg.platformUrl}/registry/devices/${encodeURIComponent(deviceId)}/revoke`, {
|
|
13577
|
+
method: "POST",
|
|
13578
|
+
headers: { authorization: `Bearer ${token}` }
|
|
13579
|
+
});
|
|
13580
|
+
if (!response2.ok) {
|
|
13581
|
+
const body = await response2.json().catch(() => ({}));
|
|
13582
|
+
throw new Error(`device revoke failed: ${body.error?.message ?? `registry returned ${response2.status}`} (${response2.status})`);
|
|
13583
|
+
}
|
|
13584
|
+
out.error(`device: revoked ${deviceId}; every credential it minted is revoked with it`);
|
|
13585
|
+
if (json) out.log(JSON.stringify({ deviceId, revoked: true }, null, 2));
|
|
13586
|
+
}
|
|
13587
|
+
async function scopedToken2(parsed, deps, cfg, doFetch, out, label) {
|
|
13588
|
+
const { credentials } = await resolveOperatorContext(parsed, { allowMissingConfig: true });
|
|
13589
|
+
const scopedTokenFile = credentials.scopedTokenFile;
|
|
13590
|
+
return getScopedPlatformToken({
|
|
13591
|
+
platform: cfg.platformUrl,
|
|
13592
|
+
scope: "app:device:enroll",
|
|
13593
|
+
email: stringOpt(parsed.options.email),
|
|
13594
|
+
label,
|
|
13595
|
+
fetch: doFetch,
|
|
13596
|
+
stdout: out,
|
|
13597
|
+
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
13598
|
+
openApprovalUrl: deps.openUrl,
|
|
13599
|
+
rootDir: cfg.rootDir,
|
|
13600
|
+
tokenFile: scopedTokenFile,
|
|
13601
|
+
...stringOpt(parsed.options.token) ? { token: stringOpt(parsed.options.token) } : {}
|
|
13602
|
+
});
|
|
13603
|
+
}
|
|
13604
|
+
function defaultDeviceName() {
|
|
13605
|
+
return `${import_node_process15.default.env.HOSTNAME ?? import_node_process15.default.env.HOST ?? "machine"}-${import_node_process15.default.platform}`;
|
|
13606
|
+
}
|
|
13607
|
+
|
|
13409
13608
|
// src/runbook-actions.ts
|
|
13410
|
-
var
|
|
13609
|
+
var import_node_fs19 = require("fs");
|
|
13411
13610
|
|
|
13412
13611
|
// src/runbook-requires.ts
|
|
13413
13612
|
var SPEC = /^(@?[\w./-]+?)@(\d+\.\d+\.\d+(?:[\w.-]*)?)$/;
|
|
@@ -13503,7 +13702,7 @@ async function bySlug(ctx, slug) {
|
|
|
13503
13702
|
function readBody(file, inline) {
|
|
13504
13703
|
if (inline !== void 0) return inline;
|
|
13505
13704
|
if (file === void 0) throw new Error("supply the new text with --file <path>, --file - (stdin), or --body");
|
|
13506
|
-
return (0,
|
|
13705
|
+
return (0, import_node_fs19.readFileSync)(file === "-" ? 0 : file, "utf8");
|
|
13507
13706
|
}
|
|
13508
13707
|
var stamp = (ms) => ms ? new Date(ms).toISOString().slice(0, 16).replace("T", " ") : "";
|
|
13509
13708
|
async function runbookList(ctx, all, query) {
|
|
@@ -13595,8 +13794,8 @@ async function runbookRemove(ctx, slug) {
|
|
|
13595
13794
|
}
|
|
13596
13795
|
|
|
13597
13796
|
// src/runbook-import.ts
|
|
13598
|
-
var
|
|
13599
|
-
var
|
|
13797
|
+
var import_node_fs20 = require("fs");
|
|
13798
|
+
var import_node_path18 = require("path");
|
|
13600
13799
|
function parseRunbook(text3, slug) {
|
|
13601
13800
|
let rest = text3;
|
|
13602
13801
|
const meta = {};
|
|
@@ -13621,12 +13820,12 @@ function parseRunbook(text3, slug) {
|
|
|
13621
13820
|
};
|
|
13622
13821
|
}
|
|
13623
13822
|
function readRunbookDir(dir) {
|
|
13624
|
-
if (!(0,
|
|
13625
|
-
const files = (0,
|
|
13823
|
+
if (!(0, import_node_fs20.statSync)(dir, { throwIfNoEntry: false })?.isDirectory()) throw new Error(`not a directory: ${dir}`);
|
|
13824
|
+
const files = (0, import_node_fs20.readdirSync)(dir).filter((f) => f.endsWith(".md")).sort();
|
|
13626
13825
|
if (!files.length) throw new Error(`no .md files in ${dir}`);
|
|
13627
13826
|
return files.map((file) => {
|
|
13628
|
-
const slug = (0,
|
|
13629
|
-
const parsed = parseRunbook((0,
|
|
13827
|
+
const slug = (0, import_node_path18.basename)(file, ".md");
|
|
13828
|
+
const parsed = parseRunbook((0, import_node_fs20.readFileSync)((0, import_node_path18.join)(dir, file), "utf8"), slug);
|
|
13630
13829
|
return { file, slug, ...parsed, words: parsed.body.split(/\s+/).filter(Boolean).length };
|
|
13631
13830
|
});
|
|
13632
13831
|
}
|
|
@@ -13699,8 +13898,8 @@ async function upsert(ctx, r, visibility) {
|
|
|
13699
13898
|
|
|
13700
13899
|
// src/runbook-impact.ts
|
|
13701
13900
|
var import_node_child_process6 = require("child_process");
|
|
13702
|
-
var
|
|
13703
|
-
var
|
|
13901
|
+
var import_node_fs21 = require("fs");
|
|
13902
|
+
var import_node_path19 = require("path");
|
|
13704
13903
|
|
|
13705
13904
|
// src/runbook-impact-scan.ts
|
|
13706
13905
|
var DECL = /^[+-]\s*export\s+(?:declare\s+)?(?:default\s+)?(?:abstract\s+)?(?:async\s+)?(?:const|let|var|function|class|interface|type|enum)\s+([A-Za-z_$][\w$]*)/;
|
|
@@ -13869,10 +14068,10 @@ ${body.split("\n").map((line2) => `+${line2}`).join("\n")}
|
|
|
13869
14068
|
}
|
|
13870
14069
|
function manifestLabeller(root) {
|
|
13871
14070
|
return (workspace) => {
|
|
13872
|
-
const manifest = (0,
|
|
13873
|
-
if (!(0,
|
|
14071
|
+
const manifest = (0, import_node_path19.join)(root, workspace, "package.json");
|
|
14072
|
+
if (!(0, import_node_fs21.existsSync)(manifest)) return void 0;
|
|
13874
14073
|
try {
|
|
13875
|
-
const name = JSON.parse((0,
|
|
14074
|
+
const name = JSON.parse((0, import_node_fs21.readFileSync)(manifest, "utf8")).name;
|
|
13876
14075
|
return typeof name === "string" ? name : void 0;
|
|
13877
14076
|
} catch {
|
|
13878
14077
|
return void 0;
|
|
@@ -13939,7 +14138,7 @@ function report4(ctx, impacts) {
|
|
|
13939
14138
|
async function runbookImpact(ctx, options, deps = {}) {
|
|
13940
14139
|
const cwd = deps.cwd ?? process.cwd();
|
|
13941
14140
|
const runGit = deps.runGit ?? gitRunner(cwd);
|
|
13942
|
-
const read3 = deps.readRepoFile ?? ((path) => (0,
|
|
14141
|
+
const read3 = deps.readRepoFile ?? ((path) => (0, import_node_fs21.readFileSync)((0, import_node_path19.join)(cwd, path), "utf8"));
|
|
13943
14142
|
const surfaces = changedSurfaces(collectDiff(runGit, options.base, read3), manifestLabeller(cwd));
|
|
13944
14143
|
if (!surfaces.length) {
|
|
13945
14144
|
return ctx.out.log(
|
|
@@ -14072,12 +14271,12 @@ async function runbookComment(ctx, slug, body) {
|
|
|
14072
14271
|
|
|
14073
14272
|
// src/runbook-editor.ts
|
|
14074
14273
|
var import_node_child_process7 = require("child_process");
|
|
14075
|
-
var
|
|
14076
|
-
var
|
|
14077
|
-
var
|
|
14078
|
-
var
|
|
14274
|
+
var import_node_fs22 = require("fs");
|
|
14275
|
+
var import_node_os5 = require("os");
|
|
14276
|
+
var import_node_path20 = require("path");
|
|
14277
|
+
var import_node_process16 = __toESM(require("process"), 1);
|
|
14079
14278
|
var EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
|
|
14080
|
-
function resolveEditor(env =
|
|
14279
|
+
function resolveEditor(env = import_node_process16.default.env) {
|
|
14081
14280
|
for (const name of EDITOR_ENV) {
|
|
14082
14281
|
const value2 = env[name];
|
|
14083
14282
|
if (value2 && value2.trim()) return value2.trim();
|
|
@@ -14091,8 +14290,8 @@ function defaultRun(command, path) {
|
|
|
14091
14290
|
return result.status ?? 0;
|
|
14092
14291
|
}
|
|
14093
14292
|
function editText(initial, slug, deps = {}) {
|
|
14094
|
-
const env = deps.env ??
|
|
14095
|
-
const interactive = deps.interactive ?? (() => Boolean(
|
|
14293
|
+
const env = deps.env ?? import_node_process16.default.env;
|
|
14294
|
+
const interactive = deps.interactive ?? (() => Boolean(import_node_process16.default.stdin.isTTY));
|
|
14096
14295
|
const editor = resolveEditor(env);
|
|
14097
14296
|
if (!editor)
|
|
14098
14297
|
throw new Error(
|
|
@@ -14100,16 +14299,16 @@ function editText(initial, slug, deps = {}) {
|
|
|
14100
14299
|
);
|
|
14101
14300
|
if (!interactive())
|
|
14102
14301
|
throw new Error(`cannot open an editor without a terminal \u2014 pass --file <path> or --body "\u2026" instead`);
|
|
14103
|
-
const dir = (0,
|
|
14104
|
-
const file = (0,
|
|
14302
|
+
const dir = (0, import_node_fs22.mkdtempSync)((0, import_node_path20.join)((0, import_node_os5.tmpdir)(), "odla-runbook-"));
|
|
14303
|
+
const file = (0, import_node_path20.join)(dir, `${slug}.md`);
|
|
14105
14304
|
try {
|
|
14106
|
-
(0,
|
|
14305
|
+
(0, import_node_fs22.writeFileSync)(file, initial, { mode: 384 });
|
|
14107
14306
|
const code = defaultRunOrInjected(deps)(editor, file);
|
|
14108
14307
|
if (code !== 0) throw new Error(`editor "${editor}" exited with ${code}; nothing was written`);
|
|
14109
|
-
const edited = (0,
|
|
14308
|
+
const edited = (0, import_node_fs22.readFileSync)(file, "utf8");
|
|
14110
14309
|
return edited === initial ? null : edited;
|
|
14111
14310
|
} finally {
|
|
14112
|
-
(0,
|
|
14311
|
+
(0, import_node_fs22.rmSync)(dir, { recursive: true, force: true });
|
|
14113
14312
|
}
|
|
14114
14313
|
}
|
|
14115
14314
|
var defaultRunOrInjected = (deps) => deps.run ?? defaultRun;
|
|
@@ -14453,7 +14652,7 @@ function hostedSeverity(value2, flag) {
|
|
|
14453
14652
|
var import_security2 = require("@odla-ai/security");
|
|
14454
14653
|
|
|
14455
14654
|
// src/security.ts
|
|
14456
|
-
var
|
|
14655
|
+
var import_node_path21 = require("path");
|
|
14457
14656
|
var import_security = require("@odla-ai/security");
|
|
14458
14657
|
var import_node3 = require("@odla-ai/security/node");
|
|
14459
14658
|
async function runHostedSecurity(options) {
|
|
@@ -14465,9 +14664,9 @@ async function runHostedSecurity(options) {
|
|
|
14465
14664
|
const appId = selfAudit ? "odla-ai" : cfg.app.id;
|
|
14466
14665
|
const env = selfAudit ? "prod" : selectEnv(options.env, cfg.envs, cfg.configPath, cfg.rootDir);
|
|
14467
14666
|
const platform = options.platform ?? cfg?.platformUrl ?? "https://odla.ai";
|
|
14468
|
-
const target = (0,
|
|
14469
|
-
const output = (0,
|
|
14470
|
-
const outputRelative = (0,
|
|
14667
|
+
const target = (0, import_node_path21.resolve)(options.target ?? cfg?.rootDir ?? ".");
|
|
14668
|
+
const output = (0, import_node_path21.resolve)(options.out ?? (0, import_node_path21.resolve)(target, ".odla/security/hosted"));
|
|
14669
|
+
const outputRelative = (0, import_node_path21.relative)(target, output).split(import_node_path21.sep).join("/");
|
|
14471
14670
|
if (!outputRelative) throw new Error("Hosted security output cannot be the repository root");
|
|
14472
14671
|
const profile = profileFor(options.profile ?? "odla", options.maxHuntTasks ?? 12);
|
|
14473
14672
|
const tokenRequest = {
|
|
@@ -14479,7 +14678,7 @@ async function runHostedSecurity(options) {
|
|
|
14479
14678
|
};
|
|
14480
14679
|
const token = await injectedToken(options, tokenRequest);
|
|
14481
14680
|
const snapshot = await (0, import_node3.snapshotDirectory)(target, {
|
|
14482
|
-
exclude: !outputRelative.startsWith("../") && !(0,
|
|
14681
|
+
exclude: !outputRelative.startsWith("../") && !(0, import_node_path21.isAbsolute)(outputRelative) ? [outputRelative] : []
|
|
14483
14682
|
});
|
|
14484
14683
|
const hosted = await (0, import_security.createPlatformSecurityReasoners)({
|
|
14485
14684
|
platform,
|
|
@@ -14497,7 +14696,7 @@ async function runHostedSecurity(options) {
|
|
|
14497
14696
|
});
|
|
14498
14697
|
const harness = (0, import_security.createSecurityHarness)({
|
|
14499
14698
|
profile,
|
|
14500
|
-
store: new import_node3.FileRunStore((0,
|
|
14699
|
+
store: new import_node3.FileRunStore((0, import_node_path21.resolve)(output, "state")),
|
|
14501
14700
|
discoveryReasoner: hosted.discoveryReasoner,
|
|
14502
14701
|
validationReasoner: hosted.validationReasoner,
|
|
14503
14702
|
policy: {
|
|
@@ -14521,7 +14720,7 @@ async function runHostedSecurity(options) {
|
|
|
14521
14720
|
function selectEnv(requested, declared, configPath, rootDir) {
|
|
14522
14721
|
const env = requested ?? (declared.includes("dev") ? "dev" : declared[0]);
|
|
14523
14722
|
if (!env || !declared.includes(env)) {
|
|
14524
|
-
const shown = (0,
|
|
14723
|
+
const shown = (0, import_node_path21.relative)(rootDir, configPath) || configPath;
|
|
14525
14724
|
throw new Error(`env "${env ?? ""}" is not declared in ${shown}`);
|
|
14526
14725
|
}
|
|
14527
14726
|
return env;
|
|
@@ -14550,7 +14749,7 @@ function printSummary(out, appId, env, run, report5, output) {
|
|
|
14550
14749
|
out.log(` coverage: ${report5.coverageStatus} ${complete}/${report5.coverage.length} blocked=${report5.metrics.blockedCells} shallow=${report5.metrics.shallowCells} unscheduled=${report5.metrics.unscheduledCells} budget_exhausted=${report5.metrics.budgetExhaustedCells}`);
|
|
14551
14750
|
if (report5.callBudget) out.log(` calls: discovery=${formatBudget(report5.callBudget.discovery)} validation=${formatBudget(report5.callBudget.validation)}`);
|
|
14552
14751
|
out.log(` findings: confirmed=${report5.metrics.confirmed} needs_reproduction=${report5.metrics.needsReproduction} candidates=${report5.metrics.candidates}`);
|
|
14553
|
-
out.log(` report: ${(0,
|
|
14752
|
+
out.log(` report: ${(0, import_node_path21.resolve)(output, "REPORT.md")}`);
|
|
14554
14753
|
}
|
|
14555
14754
|
function formatBudget(usage) {
|
|
14556
14755
|
return usage ? `${usage.usedCalls}/${usage.maxCalls} skipped=${usage.skippedCalls}` : "caller-managed";
|
|
@@ -15001,6 +15200,23 @@ function exitCodeFor(err) {
|
|
|
15001
15200
|
|
|
15002
15201
|
// src/cli.ts
|
|
15003
15202
|
async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
15203
|
+
const out = redactingOutput(dependencies.stdout ?? console);
|
|
15204
|
+
const advisories = [];
|
|
15205
|
+
const withAdvisoryReader = {
|
|
15206
|
+
...dependencies,
|
|
15207
|
+
fetch: advisoryCollectingFetch(dependencies.fetch ?? fetch, advisories)
|
|
15208
|
+
};
|
|
15209
|
+
try {
|
|
15210
|
+
return await dispatchCli(argv, withAdvisoryReader);
|
|
15211
|
+
} catch (error) {
|
|
15212
|
+
const explanation = explainRejectedCredential(error);
|
|
15213
|
+
if (explanation) out.error(explanation);
|
|
15214
|
+
throw error;
|
|
15215
|
+
} finally {
|
|
15216
|
+
renderAdvisories(out, advisories);
|
|
15217
|
+
}
|
|
15218
|
+
}
|
|
15219
|
+
async function dispatchCli(argv, dependencies) {
|
|
15004
15220
|
const runtime = {
|
|
15005
15221
|
...dependencies,
|
|
15006
15222
|
stdout: redactingOutput(dependencies.stdout ?? console)
|
|
@@ -15030,6 +15246,10 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
|
15030
15246
|
await contextCommand(parsed, runtime);
|
|
15031
15247
|
return;
|
|
15032
15248
|
}
|
|
15249
|
+
if (command === "device") {
|
|
15250
|
+
await deviceCommand(parsed, runtime);
|
|
15251
|
+
return;
|
|
15252
|
+
}
|
|
15033
15253
|
if (command === "credentials") {
|
|
15034
15254
|
await credentialCommand(parsed, runtime);
|
|
15035
15255
|
return;
|