@odla-ai/cli 0.38.3 → 0.40.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 +724 -293
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-WJAN5CZ2.js → chunk-2IHE5U3M.js} +596 -215
- package/dist/chunk-2IHE5U3M.js.map +1 -0
- package/dist/{cli-A5R2DMM4.js → cli-LRJBESE6.js} +2 -2
- package/dist/index.cjs +630 -254
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/skills/odla/SKILL.md +51 -11
- package/skills/odla/references/agent-identity.md +94 -111
- package/dist/chunk-WJAN5CZ2.js.map +0 -1
- /package/dist/{cli-A5R2DMM4.js.map → cli-LRJBESE6.js.map} +0 -0
package/dist/bin.cjs
CHANGED
|
@@ -347,45 +347,15 @@ var init_handshake_state = __esm({
|
|
|
347
347
|
}
|
|
348
348
|
});
|
|
349
349
|
|
|
350
|
-
// src/cached-credential.ts
|
|
351
|
-
function noteCachedCredential(tokenFile) {
|
|
352
|
-
noted = tokenFile;
|
|
353
|
-
}
|
|
354
|
-
function isCredentialRejection(error) {
|
|
355
|
-
const message2 = error instanceof Error ? error.message : String(error ?? "");
|
|
356
|
-
return /\((401|403)\)\s*$/.test(message2.trim());
|
|
357
|
-
}
|
|
358
|
-
function explainRejectedCredential(error) {
|
|
359
|
-
const tokenFile = noted;
|
|
360
|
-
if (!tokenFile || !isCredentialRejection(error)) return null;
|
|
361
|
-
noted = null;
|
|
362
|
-
(0, import_node_fs5.rmSync)(tokenFile, { force: true });
|
|
363
|
-
return [
|
|
364
|
-
"auth: the cached credential was rejected by odla, so it was revoked before its cached expiry.",
|
|
365
|
-
" The usual cause is a newer sign-in for this project: collecting a handshake retires the",
|
|
366
|
-
" principal's other credentials, so a second terminal or worktree supersedes this one.",
|
|
367
|
-
` Discarded ${tokenFile}; re-run this command to request a fresh approval.`
|
|
368
|
-
].join("\n");
|
|
369
|
-
}
|
|
370
|
-
var import_node_fs5, noted;
|
|
371
|
-
var init_cached_credential = __esm({
|
|
372
|
-
"src/cached-credential.ts"() {
|
|
373
|
-
"use strict";
|
|
374
|
-
init_cjs_shims();
|
|
375
|
-
import_node_fs5 = require("fs");
|
|
376
|
-
noted = null;
|
|
377
|
-
}
|
|
378
|
-
});
|
|
379
|
-
|
|
380
350
|
// src/device-session.ts
|
|
381
351
|
function deviceCredentialPath(env = import_node_process4.default.env) {
|
|
382
352
|
return env.ODLA_DEVICE_CREDENTIAL ?? (0, import_node_path3.join)(env.HOME ?? (0, import_node_os.homedir)(), ".odla", "device.json");
|
|
383
353
|
}
|
|
384
354
|
function readDeviceCredential(platform, env = import_node_process4.default.env) {
|
|
385
355
|
const path = deviceCredentialPath(env);
|
|
386
|
-
if (!(0,
|
|
356
|
+
if (!(0, import_node_fs5.existsSync)(path)) return null;
|
|
387
357
|
try {
|
|
388
|
-
const parsed = JSON.parse((0,
|
|
358
|
+
const parsed = JSON.parse((0, import_node_fs5.readFileSync)(path, "utf8"));
|
|
389
359
|
if (typeof parsed.token !== "string" || !parsed.token.startsWith("odla_device_")) return null;
|
|
390
360
|
if (parsed.platform !== platform) return null;
|
|
391
361
|
return { ...parsed, token: parsed.token, platform: parsed.platform };
|
|
@@ -407,20 +377,91 @@ async function mintDeviceSession(platformUrl, credential2, doFetch) {
|
|
|
407
377
|
`device session failed: ${detail} (${response2.status})` + (revocable ? " \u2014 if this machine's enrollment was revoked or has expired, enroll it again in Studio" : "")
|
|
408
378
|
);
|
|
409
379
|
}
|
|
410
|
-
return {
|
|
380
|
+
return {
|
|
381
|
+
token: body.token,
|
|
382
|
+
expiresAt: body.expiresAt ?? Date.now(),
|
|
383
|
+
// Absent from a registry that predates rolling expiry and scoped devices.
|
|
384
|
+
// Left undefined rather than defaulted, so a caller can tell "the platform
|
|
385
|
+
// did not say" from "the platform said none".
|
|
386
|
+
...typeof body.deviceExpiresAt === "number" ? { deviceExpiresAt: body.deviceExpiresAt } : {},
|
|
387
|
+
...Array.isArray(body.appIds) ? { appIds: body.appIds } : {},
|
|
388
|
+
...Array.isArray(body.capabilities) ? { capabilities: body.capabilities } : {},
|
|
389
|
+
...Array.isArray(body.scopes) ? { scopes: body.scopes } : {}
|
|
390
|
+
};
|
|
411
391
|
}
|
|
412
|
-
var
|
|
392
|
+
var import_node_fs5, import_node_os, import_node_path3, import_node_process4;
|
|
413
393
|
var init_device_session = __esm({
|
|
414
394
|
"src/device-session.ts"() {
|
|
415
395
|
"use strict";
|
|
416
396
|
init_cjs_shims();
|
|
417
|
-
|
|
397
|
+
import_node_fs5 = require("fs");
|
|
418
398
|
import_node_os = require("os");
|
|
419
399
|
import_node_path3 = require("path");
|
|
420
400
|
import_node_process4 = __toESM(require("process"), 1);
|
|
421
401
|
}
|
|
422
402
|
});
|
|
423
403
|
|
|
404
|
+
// src/odla-home.ts
|
|
405
|
+
function odlaHome(env = import_node_process5.default.env) {
|
|
406
|
+
if (env.VITEST && !env.ODLA_HOME) {
|
|
407
|
+
throw new Error(
|
|
408
|
+
"ODLA_HOME must be set under test \u2014 resolving the real ~/.odla would write to the developer's machine"
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
return env.ODLA_HOME ?? (0, import_node_path4.join)(env.HOME ?? (0, import_node_os2.homedir)(), ".odla");
|
|
412
|
+
}
|
|
413
|
+
function odlaHomePath(segments, env = import_node_process5.default.env) {
|
|
414
|
+
return (0, import_node_path4.join)(odlaHome(env), ...segments);
|
|
415
|
+
}
|
|
416
|
+
function identityFile(env) {
|
|
417
|
+
return odlaHomePath(["identity.json"], env);
|
|
418
|
+
}
|
|
419
|
+
function deviceSessionFile(env) {
|
|
420
|
+
return odlaHomePath(["session.json"], env);
|
|
421
|
+
}
|
|
422
|
+
function appTokenFile(appId, env) {
|
|
423
|
+
return odlaHomePath(["apps", safeSegment(appId), "dev-token.json"], env);
|
|
424
|
+
}
|
|
425
|
+
function appCredentialsFile(appId, env) {
|
|
426
|
+
return odlaHomePath(["apps", safeSegment(appId), "credentials.json"], env);
|
|
427
|
+
}
|
|
428
|
+
function scopedTokenFile(env) {
|
|
429
|
+
return odlaHomePath(["admin-token.local.json"], env);
|
|
430
|
+
}
|
|
431
|
+
function pmContextFile(env) {
|
|
432
|
+
return odlaHomePath(["pm-context.json"], env);
|
|
433
|
+
}
|
|
434
|
+
function adoptRepoLocalCache(legacyPath, machinePath, out) {
|
|
435
|
+
if (!(0, import_node_fs6.existsSync)(legacyPath) || legacyPath === machinePath) return false;
|
|
436
|
+
const superseded2 = (0, import_node_fs6.existsSync)(machinePath);
|
|
437
|
+
if (!superseded2) {
|
|
438
|
+
(0, import_node_fs6.mkdirSync)((0, import_node_path4.dirname)(machinePath), { recursive: true });
|
|
439
|
+
(0, import_node_fs6.copyFileSync)(legacyPath, machinePath);
|
|
440
|
+
(0, import_node_fs6.chmodSync)(machinePath, 384);
|
|
441
|
+
}
|
|
442
|
+
(0, import_node_fs6.rmSync)(legacyPath, { force: true });
|
|
443
|
+
out?.error(
|
|
444
|
+
superseded2 ? `auth: removed superseded ${legacyPath}; this machine's credentials live in ${odlaHome()}` : `auth: moved ${legacyPath} into ${machinePath}; credentials are per machine now, not per worktree`
|
|
445
|
+
);
|
|
446
|
+
return true;
|
|
447
|
+
}
|
|
448
|
+
function safeSegment(value2) {
|
|
449
|
+
const clean4 = value2.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/^-+|-+$/g, "");
|
|
450
|
+
if (!clean4) throw new Error(`"${value2}" is not a usable app id`);
|
|
451
|
+
return clean4;
|
|
452
|
+
}
|
|
453
|
+
var import_node_fs6, import_node_os2, import_node_path4, import_node_process5;
|
|
454
|
+
var init_odla_home = __esm({
|
|
455
|
+
"src/odla-home.ts"() {
|
|
456
|
+
"use strict";
|
|
457
|
+
init_cjs_shims();
|
|
458
|
+
import_node_fs6 = require("fs");
|
|
459
|
+
import_node_os2 = require("os");
|
|
460
|
+
import_node_path4 = require("path");
|
|
461
|
+
import_node_process5 = __toESM(require("process"), 1);
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
|
|
424
465
|
// src/local.ts
|
|
425
466
|
function readJsonFile(path) {
|
|
426
467
|
try {
|
|
@@ -467,7 +508,7 @@ function mergeCredential(current, update) {
|
|
|
467
508
|
return next;
|
|
468
509
|
}
|
|
469
510
|
function ensureGitignore(rootDir, localPaths = []) {
|
|
470
|
-
const path = (0,
|
|
511
|
+
const path = (0, import_node_path5.resolve)(rootDir, ".gitignore");
|
|
471
512
|
const existing = (0, import_node_fs7.existsSync)(path) ? (0, import_node_fs7.readFileSync)(path, "utf8") : "";
|
|
472
513
|
const configured = localPaths.map((localPath) => gitignoreEntry(rootDir, localPath)).filter((line2) => !!line2);
|
|
473
514
|
const wanted = [.../* @__PURE__ */ new Set([...GITIGNORE_LINES, ...configured])];
|
|
@@ -488,7 +529,7 @@ function o11yDevVars(cfg) {
|
|
|
488
529
|
function resolveWriteDevVarsTarget(cfg, requested) {
|
|
489
530
|
if (!requested) return null;
|
|
490
531
|
if (requested === true) return cfg.local.devVarsFile;
|
|
491
|
-
return (0,
|
|
532
|
+
return (0, import_node_path5.resolve)((0, import_node_path5.dirname)(cfg.configPath), requested);
|
|
492
533
|
}
|
|
493
534
|
function writeDevVars(path, credentials, env, o11y) {
|
|
494
535
|
const entry = credentials.envs[env];
|
|
@@ -516,28 +557,28 @@ function isManagedDevVar(line2) {
|
|
|
516
557
|
return !!match?.[1] && MANAGED_DEV_VARS.has(match[1]);
|
|
517
558
|
}
|
|
518
559
|
function writePrivateText(path, text3) {
|
|
519
|
-
(0, import_node_fs7.mkdirSync)((0,
|
|
560
|
+
(0, import_node_fs7.mkdirSync)((0, import_node_path5.dirname)(path), { recursive: true });
|
|
520
561
|
const temporary = `${path}.tmp-${process.pid}-${Date.now()}`;
|
|
521
562
|
(0, import_node_fs7.writeFileSync)(temporary, text3, { mode: 384 });
|
|
522
563
|
(0, import_node_fs7.chmodSync)(temporary, 384);
|
|
523
564
|
(0, import_node_fs7.renameSync)(temporary, path);
|
|
524
565
|
}
|
|
525
566
|
function gitignoreEntry(rootDir, path) {
|
|
526
|
-
const rel = (0,
|
|
527
|
-
if (!rel || rel === ".." || rel.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || (0,
|
|
567
|
+
const rel = (0, import_node_path5.relative)((0, import_node_path5.resolve)(rootDir), (0, import_node_path5.resolve)(path));
|
|
568
|
+
if (!rel || rel === ".." || rel.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || (0, import_node_path5.isAbsolute)(rel)) return null;
|
|
528
569
|
return rel.replaceAll("\\", "/");
|
|
529
570
|
}
|
|
530
571
|
function displayPath(path, rootDir = process.cwd()) {
|
|
531
|
-
const rel = (0,
|
|
572
|
+
const rel = (0, import_node_path5.relative)(rootDir, path);
|
|
532
573
|
return rel && !rel.startsWith("..") ? rel : path;
|
|
533
574
|
}
|
|
534
|
-
var import_node_fs7,
|
|
575
|
+
var import_node_fs7, import_node_path5, GITIGNORE_LINES, MANAGED_DEV_VARS;
|
|
535
576
|
var init_local = __esm({
|
|
536
577
|
"src/local.ts"() {
|
|
537
578
|
"use strict";
|
|
538
579
|
init_cjs_shims();
|
|
539
580
|
import_node_fs7 = require("fs");
|
|
540
|
-
|
|
581
|
+
import_node_path5 = require("path");
|
|
541
582
|
GITIGNORE_LINES = [".odla/*.local.json", ".odla/dev-token.json", ".dev.vars"];
|
|
542
583
|
MANAGED_DEV_VARS = /* @__PURE__ */ new Set([
|
|
543
584
|
"ODLA_PLATFORM",
|
|
@@ -554,6 +595,147 @@ var init_local = __esm({
|
|
|
554
595
|
}
|
|
555
596
|
});
|
|
556
597
|
|
|
598
|
+
// src/auth-guidance.ts
|
|
599
|
+
function machineAuthState(audience, env = import_node_process6.default.env) {
|
|
600
|
+
const device = readDeviceCredential(audience, env);
|
|
601
|
+
if (!device) return { enrolled: false };
|
|
602
|
+
const session = readJsonFile(deviceSessionFile(env));
|
|
603
|
+
const current = session?.platform === audience && session.deviceId === device.deviceId ? session : void 0;
|
|
604
|
+
return {
|
|
605
|
+
enrolled: true,
|
|
606
|
+
...device.name ? { deviceName: device.name } : {},
|
|
607
|
+
...current?.appIds ? { appIds: current.appIds } : {},
|
|
608
|
+
...current?.capabilities ? { capabilities: current.capabilities } : {},
|
|
609
|
+
...current?.scopes ? { scopes: current.scopes } : {},
|
|
610
|
+
...current?.deviceExpiresAt ? { lapsesAt: current.deviceExpiresAt } : {}
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
function scopeInterruptionNotice(scope, state2) {
|
|
614
|
+
const platformScope = scope.startsWith("platform:");
|
|
615
|
+
const reason = !state2.enrolled ? "this machine is not enrolled, so every privileged command needs its own browser approval" : `this machine is enrolled but its approval did not include "${scope}"`;
|
|
616
|
+
return [
|
|
617
|
+
`odla: ${reason}.`,
|
|
618
|
+
` Approve this one now, then end the interruptions with:`,
|
|
619
|
+
` ${platformScope ? ENROL_PLATFORM_WIDE : ENROL_EVERYTHING}`,
|
|
620
|
+
platformScope ? " A platform scope needs an administrator's approval; an app owner's cannot carry it." : " One approval, every app you own, and it rolls forward while you keep working."
|
|
621
|
+
].join("\n");
|
|
622
|
+
}
|
|
623
|
+
function narrowEnrollmentNotice(envelope) {
|
|
624
|
+
const everyApp = envelope.appIds.includes("*");
|
|
625
|
+
if (everyApp && envelope.capabilities.length > 0 && envelope.scopes.length > 0) return null;
|
|
626
|
+
const missing = [
|
|
627
|
+
everyApp ? null : `only ${envelope.appIds.length} app${envelope.appIds.length === 1 ? "" : "s"} \u2014 a new one will need a new approval`,
|
|
628
|
+
envelope.capabilities.length ? null : "no optional capabilities \u2014 app.manage, crm.read and code.session are not included",
|
|
629
|
+
envelope.scopes.length ? null : "no platform scopes \u2014 runbook edits, config plans and host connect will each ask again"
|
|
630
|
+
].filter(Boolean);
|
|
631
|
+
return [
|
|
632
|
+
`odla: this is a NARROW enrollment: ${missing.join("; ")}.`,
|
|
633
|
+
" That is a fine choice if you meant it. If you did not, drop the flags:",
|
|
634
|
+
` ${ENROL_EVERYTHING}`
|
|
635
|
+
].join("\n");
|
|
636
|
+
}
|
|
637
|
+
function lapseNotice(state2, now = Date.now()) {
|
|
638
|
+
if (!state2.enrolled || !state2.lapsesAt) return null;
|
|
639
|
+
const days = Math.floor((state2.lapsesAt - now) / (24 * 60 * 60 * 1e3));
|
|
640
|
+
if (days < 0) return "this machine's enrollment has lapsed; the next command will ask for approval";
|
|
641
|
+
return `idle for ${days} more day${days === 1 ? "" : "s"} before this machine needs approving again (using it resets the clock)`;
|
|
642
|
+
}
|
|
643
|
+
var import_node_process6, ENROL_EVERYTHING, ENROL_PLATFORM_WIDE;
|
|
644
|
+
var init_auth_guidance = __esm({
|
|
645
|
+
"src/auth-guidance.ts"() {
|
|
646
|
+
"use strict";
|
|
647
|
+
init_cjs_shims();
|
|
648
|
+
import_node_process6 = __toESM(require("process"), 1);
|
|
649
|
+
init_device_session();
|
|
650
|
+
init_odla_home();
|
|
651
|
+
init_local();
|
|
652
|
+
ENROL_EVERYTHING = "npx odla-ai device enroll --no-open --wait 600";
|
|
653
|
+
ENROL_PLATFORM_WIDE = "npx odla-ai device enroll --platform-wide --device-ttl 6w --no-open --wait 600";
|
|
654
|
+
}
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
// src/cached-credential.ts
|
|
658
|
+
function noteCachedCredential(tokenFile) {
|
|
659
|
+
noted = tokenFile;
|
|
660
|
+
}
|
|
661
|
+
function isCredentialRejection(error) {
|
|
662
|
+
const message2 = error instanceof Error ? error.message : String(error ?? "");
|
|
663
|
+
return /\((401|403)\)\s*$/.test(message2.trim());
|
|
664
|
+
}
|
|
665
|
+
function explainRejectedCredential(error) {
|
|
666
|
+
const tokenFile = noted;
|
|
667
|
+
if (!tokenFile || !isCredentialRejection(error)) return null;
|
|
668
|
+
noted = null;
|
|
669
|
+
(0, import_node_fs8.rmSync)(tokenFile, { force: true });
|
|
670
|
+
return [
|
|
671
|
+
"auth: the cached credential was rejected by odla, so it was revoked before its cached expiry.",
|
|
672
|
+
" The usual cause is a newer sign-in for this account: collecting a handshake retires the",
|
|
673
|
+
" principal's other collected credentials, so a second machine supersedes this one.",
|
|
674
|
+
` Discarded ${tokenFile}; re-run this command to request a fresh approval.`,
|
|
675
|
+
` To stop needing one: ${ENROL_EVERYTHING}`
|
|
676
|
+
].join("\n");
|
|
677
|
+
}
|
|
678
|
+
var import_node_fs8, noted;
|
|
679
|
+
var init_cached_credential = __esm({
|
|
680
|
+
"src/cached-credential.ts"() {
|
|
681
|
+
"use strict";
|
|
682
|
+
init_cjs_shims();
|
|
683
|
+
import_node_fs8 = require("fs");
|
|
684
|
+
init_auth_guidance();
|
|
685
|
+
noted = null;
|
|
686
|
+
}
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
// src/device-session-cache.ts
|
|
690
|
+
async function deviceSessionToken(platformUrl, audience, credential2, doFetch, env = import_node_process7.default.env) {
|
|
691
|
+
const path = deviceSessionFile(env);
|
|
692
|
+
const cached = readJsonFile(path);
|
|
693
|
+
if (cached?.token && cached.platform === audience && cached.deviceId === credential2.deviceId && (cached.expiresAt ?? 0) > Date.now() + SKEW_MS) return cached;
|
|
694
|
+
const minted = await mintDeviceSession(platformUrl, credential2, doFetch);
|
|
695
|
+
const session = {
|
|
696
|
+
...minted,
|
|
697
|
+
platform: audience,
|
|
698
|
+
...credential2.deviceId ? { deviceId: credential2.deviceId } : {}
|
|
699
|
+
};
|
|
700
|
+
writePrivateJson(path, session);
|
|
701
|
+
return session;
|
|
702
|
+
}
|
|
703
|
+
var import_node_process7, SKEW_MS;
|
|
704
|
+
var init_device_session_cache = __esm({
|
|
705
|
+
"src/device-session-cache.ts"() {
|
|
706
|
+
"use strict";
|
|
707
|
+
init_cjs_shims();
|
|
708
|
+
import_node_process7 = __toESM(require("process"), 1);
|
|
709
|
+
init_odla_home();
|
|
710
|
+
init_local();
|
|
711
|
+
init_device_session();
|
|
712
|
+
SKEW_MS = 6e4;
|
|
713
|
+
}
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
// src/machine-identity.ts
|
|
717
|
+
function readMachineIdentity(audience, env = import_node_process8.default.env) {
|
|
718
|
+
const stored = readJsonFile(identityFile(env));
|
|
719
|
+
if (!stored || typeof stored.email !== "string" || !stored.email) return null;
|
|
720
|
+
return stored.platform === audience ? { platform: audience, email: stored.email } : null;
|
|
721
|
+
}
|
|
722
|
+
function rememberMachineIdentity(audience, email, env = import_node_process8.default.env) {
|
|
723
|
+
if (!email) return;
|
|
724
|
+
const existing = readMachineIdentity(audience, env);
|
|
725
|
+
if (existing?.email === email) return;
|
|
726
|
+
writePrivateJson(identityFile(env), { platform: audience, email });
|
|
727
|
+
}
|
|
728
|
+
var import_node_process8;
|
|
729
|
+
var init_machine_identity = __esm({
|
|
730
|
+
"src/machine-identity.ts"() {
|
|
731
|
+
"use strict";
|
|
732
|
+
init_cjs_shims();
|
|
733
|
+
import_node_process8 = __toESM(require("process"), 1);
|
|
734
|
+
init_odla_home();
|
|
735
|
+
init_local();
|
|
736
|
+
}
|
|
737
|
+
});
|
|
738
|
+
|
|
557
739
|
// src/token.ts
|
|
558
740
|
async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {}) {
|
|
559
741
|
const audience = platformAudience(cfg.platformUrl);
|
|
@@ -562,19 +744,19 @@ async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {})
|
|
|
562
744
|
const cached = readJsonFile(cfg.local.tokenFile);
|
|
563
745
|
if (!grantRequest.forceReview && !grantRequest.freshLogin) {
|
|
564
746
|
if (options.token) return options.token;
|
|
565
|
-
if (
|
|
566
|
-
const declared =
|
|
747
|
+
if (import_node_process9.default.env.ODLA_DEV_TOKEN) {
|
|
748
|
+
const declared = import_node_process9.default.env.ODLA_DEV_TOKEN_AUDIENCE;
|
|
567
749
|
if (declared) {
|
|
568
750
|
if (platformAudience(declared) !== audience) throw new Error("ODLA_DEV_TOKEN_AUDIENCE does not match the configured platform");
|
|
569
751
|
} else if (audience !== "https://odla.ai") {
|
|
570
752
|
throw new Error("ODLA_DEV_TOKEN_AUDIENCE is required for a non-default platform");
|
|
571
753
|
}
|
|
572
|
-
return
|
|
754
|
+
return import_node_process9.default.env.ODLA_DEV_TOKEN;
|
|
573
755
|
}
|
|
574
756
|
const device = readDeviceCredential(audience);
|
|
575
757
|
if (device) {
|
|
576
|
-
const session = await
|
|
577
|
-
out.error(`auth: session
|
|
758
|
+
const session = await deviceSessionToken(cfg.platformUrl, audience, device, doFetch);
|
|
759
|
+
out.error(`auth: session held by this enrolled device (${displayPath(deviceCredentialPath(), cfg.rootDir)})`);
|
|
578
760
|
return session.token;
|
|
579
761
|
}
|
|
580
762
|
if (cached?.token && cached.platform === audience && (cached.expiresAt ?? 0) > Date.now() + 6e4 && cachedGrantCovers(cached, grantIntent)) {
|
|
@@ -594,7 +776,10 @@ async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {})
|
|
|
594
776
|
doFetch,
|
|
595
777
|
out,
|
|
596
778
|
audience,
|
|
597
|
-
email: handshakeEmail(
|
|
779
|
+
email: handshakeEmail(
|
|
780
|
+
options.email,
|
|
781
|
+
(cached?.platform === audience ? cached.email : void 0) ?? readMachineIdentity(audience)?.email
|
|
782
|
+
),
|
|
598
783
|
pendingFile: handshakeFile(cfg),
|
|
599
784
|
grantIntent
|
|
600
785
|
};
|
|
@@ -611,6 +796,7 @@ async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {})
|
|
|
611
796
|
expiresAt
|
|
612
797
|
});
|
|
613
798
|
out.error(`auth: developer token cached (${displayPath(cfg.local.tokenFile, cfg.rootDir)})`);
|
|
799
|
+
rememberMachineIdentity(audience, ctx.email);
|
|
614
800
|
return token;
|
|
615
801
|
}
|
|
616
802
|
async function freshHandshake(ctx, waitMs) {
|
|
@@ -680,7 +866,7 @@ function stillPending(pending, email) {
|
|
|
680
866
|
);
|
|
681
867
|
}
|
|
682
868
|
function handshakeEmail(value2, cached) {
|
|
683
|
-
const email = (value2 ??
|
|
869
|
+
const email = (value2 ?? import_node_process9.default.env.ODLA_USER_EMAIL ?? cached ?? "").trim().toLowerCase();
|
|
684
870
|
if (/@users\.noreply\.github\.com$/i.test(email)) {
|
|
685
871
|
throw new Error(
|
|
686
872
|
`"${email}" is a GitHub commit identity, not an odla account email; use --email <signed-in-odla-account> or ODLA_USER_EMAIL`
|
|
@@ -709,18 +895,20 @@ function platformAudience(value2) {
|
|
|
709
895
|
}
|
|
710
896
|
return url.origin;
|
|
711
897
|
}
|
|
712
|
-
var import_db, import_node_crypto,
|
|
898
|
+
var import_db, import_node_crypto, import_node_process9;
|
|
713
899
|
var init_token = __esm({
|
|
714
900
|
"src/token.ts"() {
|
|
715
901
|
"use strict";
|
|
716
902
|
init_cjs_shims();
|
|
717
903
|
import_db = require("@odla-ai/db");
|
|
718
904
|
import_node_crypto = require("crypto");
|
|
719
|
-
|
|
905
|
+
import_node_process9 = __toESM(require("process"), 1);
|
|
720
906
|
init_handshake_approval();
|
|
721
907
|
init_handshake_state();
|
|
722
908
|
init_cached_credential();
|
|
723
909
|
init_device_session();
|
|
910
|
+
init_device_session_cache();
|
|
911
|
+
init_machine_identity();
|
|
724
912
|
init_local();
|
|
725
913
|
}
|
|
726
914
|
});
|
|
@@ -729,7 +917,7 @@ var init_token = __esm({
|
|
|
729
917
|
async function secretInputValue(options, kind = "credential") {
|
|
730
918
|
if (options.fromEnv && options.stdin) throw new Error("choose exactly one of --from-env or --stdin");
|
|
731
919
|
let value2;
|
|
732
|
-
if (options.fromEnv) value2 =
|
|
920
|
+
if (options.fromEnv) value2 = import_node_process10.default.env[options.fromEnv];
|
|
733
921
|
else if (options.stdin) value2 = await (options.readStdin ?? (() => readSecretStream(kind)))();
|
|
734
922
|
else throw new Error(`${kind} input required: use --from-env <NAME> or --stdin; values are never accepted as arguments`);
|
|
735
923
|
value2 = value2?.replace(/[\r\n]+$/, "");
|
|
@@ -737,7 +925,7 @@ async function secretInputValue(options, kind = "credential") {
|
|
|
737
925
|
if (new TextEncoder().encode(value2).byteLength > MAX_BYTES) throw new Error(`${kind} exceeds 64 KiB`);
|
|
738
926
|
return value2;
|
|
739
927
|
}
|
|
740
|
-
async function readSecretStream(kind, stream =
|
|
928
|
+
async function readSecretStream(kind, stream = import_node_process10.default.stdin) {
|
|
741
929
|
let value2 = "";
|
|
742
930
|
for await (const chunk of stream) {
|
|
743
931
|
value2 += String(chunk);
|
|
@@ -745,12 +933,12 @@ async function readSecretStream(kind, stream = import_node_process6.default.stdi
|
|
|
745
933
|
}
|
|
746
934
|
return value2;
|
|
747
935
|
}
|
|
748
|
-
var
|
|
936
|
+
var import_node_process10, MAX_BYTES;
|
|
749
937
|
var init_secret_input = __esm({
|
|
750
938
|
"src/secret-input.ts"() {
|
|
751
939
|
"use strict";
|
|
752
940
|
init_cjs_shims();
|
|
753
|
-
|
|
941
|
+
import_node_process10 = __toESM(require("process"), 1);
|
|
754
942
|
MAX_BYTES = 64 * 1024;
|
|
755
943
|
}
|
|
756
944
|
});
|
|
@@ -762,7 +950,7 @@ async function getScopedPlatformToken(options) {
|
|
|
762
950
|
async function resolveAdminPlatformToken(options) {
|
|
763
951
|
const audience = platformAudience(options.platform);
|
|
764
952
|
if (options.token) return options.token;
|
|
765
|
-
const fromEnv =
|
|
953
|
+
const fromEnv = import_node_process11.default.env.ODLA_ADMIN_TOKEN;
|
|
766
954
|
if (fromEnv) return audienceBoundEnvToken(fromEnv, audience);
|
|
767
955
|
return scopedToken(
|
|
768
956
|
audience,
|
|
@@ -774,7 +962,7 @@ async function resolveAdminPlatformToken(options) {
|
|
|
774
962
|
}
|
|
775
963
|
function audienceBoundEnvToken(token, platform) {
|
|
776
964
|
const audience = platformAudience(platform);
|
|
777
|
-
const declared =
|
|
965
|
+
const declared = import_node_process11.default.env.ODLA_ADMIN_TOKEN_AUDIENCE;
|
|
778
966
|
if (declared) {
|
|
779
967
|
if (platformAudience(declared) !== audience) throw new Error("ODLA_ADMIN_TOKEN_AUDIENCE does not match the configured platform");
|
|
780
968
|
} else if (audience !== "https://odla.ai") {
|
|
@@ -784,15 +972,28 @@ function audienceBoundEnvToken(token, platform) {
|
|
|
784
972
|
}
|
|
785
973
|
async function scopedToken(platform, scope, options, doFetch, out) {
|
|
786
974
|
const audience = platformAudience(platform);
|
|
787
|
-
const rootDir = options.rootDir ??
|
|
788
|
-
const tokenFile = options.tokenFile ?? (
|
|
975
|
+
const rootDir = options.rootDir ?? import_node_process11.default.cwd();
|
|
976
|
+
const tokenFile = options.tokenFile ?? scopedTokenFile();
|
|
977
|
+
adoptRepoLocalCache((0, import_node_path6.join)(rootDir, ".odla/admin-token.local.json"), tokenFile, out);
|
|
978
|
+
const device = readDeviceCredential(audience);
|
|
979
|
+
if (device && options.cache !== false) {
|
|
980
|
+
const session = await deviceSessionToken(platform, audience, device, doFetch);
|
|
981
|
+
if (session.scopes?.includes(scope)) {
|
|
982
|
+
out.error(`auth: ${scope} held by this enrolled device`);
|
|
983
|
+
return session.token;
|
|
984
|
+
}
|
|
985
|
+
}
|
|
789
986
|
const cache2 = options.cache === false ? null : readJsonFile(tokenFile);
|
|
790
987
|
const cached = cache2?.platform === audience ? cache2.tokens?.[scope] : void 0;
|
|
791
988
|
if (cached?.token && (cached.expiresAt ?? 0) > Date.now() + 6e4) {
|
|
792
989
|
out.error(`auth: using cached ${scope} grant (${tokenFile})`);
|
|
793
990
|
return cached.token;
|
|
794
991
|
}
|
|
795
|
-
|
|
992
|
+
out.error(scopeInterruptionNotice(scope, machineAuthState(audience)));
|
|
993
|
+
const email = handshakeEmail(
|
|
994
|
+
options.email,
|
|
995
|
+
(cache2?.platform === audience ? cache2.email : void 0) ?? readMachineIdentity(audience)?.email
|
|
996
|
+
);
|
|
796
997
|
const { token, expiresAt } = await (0, import_db2.requestToken)({
|
|
797
998
|
endpoint: audience,
|
|
798
999
|
email,
|
|
@@ -812,26 +1013,30 @@ async function scopedToken(platform, scope, options, doFetch, out) {
|
|
|
812
1013
|
if (options.cache !== false) {
|
|
813
1014
|
const tokens = cache2?.platform === audience ? { ...cache2.tokens ?? {} } : {};
|
|
814
1015
|
tokens[scope] = { token, expiresAt };
|
|
815
|
-
if ((0, import_node_fs8.existsSync)((0, import_node_path5.join)(rootDir, ".git"))) ensureGitignore(rootDir, [tokenFile]);
|
|
816
1016
|
writePrivateJson(tokenFile, { platform: audience, email, tokens });
|
|
1017
|
+
rememberMachineIdentity(audience, email);
|
|
817
1018
|
out.error(`auth: cached ${scope} grant (${tokenFile}; mode 0600)`);
|
|
818
1019
|
} else {
|
|
819
1020
|
out.error(`auth: ${scope} grant is in memory only; its credential record remains in odla-ai/db`);
|
|
820
1021
|
}
|
|
821
1022
|
return token;
|
|
822
1023
|
}
|
|
823
|
-
var
|
|
1024
|
+
var import_node_path6, import_node_process11, import_db2, SCOPE_PURPOSE;
|
|
824
1025
|
var init_admin_ai_auth = __esm({
|
|
825
1026
|
"src/admin-ai-auth.ts"() {
|
|
826
1027
|
"use strict";
|
|
827
1028
|
init_cjs_shims();
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
import_node_process7 = __toESM(require("process"), 1);
|
|
1029
|
+
import_node_path6 = require("path");
|
|
1030
|
+
import_node_process11 = __toESM(require("process"), 1);
|
|
831
1031
|
import_db2 = require("@odla-ai/db");
|
|
832
1032
|
init_local();
|
|
833
1033
|
init_handshake_approval();
|
|
834
1034
|
init_token();
|
|
1035
|
+
init_auth_guidance();
|
|
1036
|
+
init_device_session_cache();
|
|
1037
|
+
init_device_session();
|
|
1038
|
+
init_machine_identity();
|
|
1039
|
+
init_odla_home();
|
|
835
1040
|
SCOPE_PURPOSE = {
|
|
836
1041
|
"platform:status:read": "read the platform fleet health and deployment snapshot",
|
|
837
1042
|
"app:config:read": "compare checked-in intent with an exact-id app Registry configuration",
|
|
@@ -1047,7 +1252,7 @@ var init_admin_ai_usage = __esm({
|
|
|
1047
1252
|
|
|
1048
1253
|
// src/admin-ai.ts
|
|
1049
1254
|
async function adminAi(options) {
|
|
1050
|
-
const platform = platformAudience(options.platform ??
|
|
1255
|
+
const platform = platformAudience(options.platform ?? import_node_process12.default.env.ODLA_PLATFORM ?? "https://odla.ai");
|
|
1051
1256
|
const doFetch = options.fetch ?? fetch;
|
|
1052
1257
|
const out = options.stdout ?? console;
|
|
1053
1258
|
const usageQuery = options.action === "usage" ? adminAiUsageQuery(options) : void 0;
|
|
@@ -1225,12 +1430,12 @@ function apiError3(action2, status, body) {
|
|
|
1225
1430
|
function isRecord3(value2) {
|
|
1226
1431
|
return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
|
|
1227
1432
|
}
|
|
1228
|
-
var
|
|
1433
|
+
var import_node_process12;
|
|
1229
1434
|
var init_admin_ai = __esm({
|
|
1230
1435
|
"src/admin-ai.ts"() {
|
|
1231
1436
|
"use strict";
|
|
1232
1437
|
init_cjs_shims();
|
|
1233
|
-
|
|
1438
|
+
import_node_process12 = __toESM(require("process"), 1);
|
|
1234
1439
|
init_token();
|
|
1235
1440
|
init_secret_input();
|
|
1236
1441
|
init_admin_ai_auth();
|
|
@@ -1812,12 +2017,12 @@ var init_monitoring_validation = __esm({
|
|
|
1812
2017
|
|
|
1813
2018
|
// src/config.ts
|
|
1814
2019
|
async function loadProjectConfig(configPath = "odla.config.mjs", options = {}) {
|
|
1815
|
-
const resolved = (0,
|
|
2020
|
+
const resolved = (0, import_node_path7.resolve)(configPath);
|
|
1816
2021
|
if (!(0, import_node_fs9.existsSync)(resolved)) {
|
|
1817
2022
|
throw new Error(`config not found: ${configPath}. Run "odla-ai init" first or pass --config.`);
|
|
1818
2023
|
}
|
|
1819
2024
|
const raw = await loadConfigModule(resolved);
|
|
1820
|
-
const rootDir = (0,
|
|
2025
|
+
const rootDir = (0, import_node_path7.dirname)(resolved);
|
|
1821
2026
|
validateRawConfig(raw, resolved);
|
|
1822
2027
|
const platformUrl = trimSlash(process.env.ODLA_PLATFORM_URL || raw.platformUrl || DEFAULT_PLATFORM);
|
|
1823
2028
|
const dbEndpoint = trimSlash(process.env.ODLA_DB_ENDPOINT || raw.dbEndpoint || platformUrl);
|
|
@@ -1827,11 +2032,14 @@ async function loadProjectConfig(configPath = "odla.config.mjs", options = {}) {
|
|
|
1827
2032
|
validateCalendarConfig(raw, unique3([...envs, ...options.additionalEnvs ?? []]), services, resolved);
|
|
1828
2033
|
validateMonitoringConfig(raw, unique3([...envs, ...options.additionalEnvs ?? []]), services, resolved);
|
|
1829
2034
|
const local = {
|
|
1830
|
-
tokenFile: (0,
|
|
1831
|
-
credentialsFile: (0,
|
|
1832
|
-
devVarsFile: (0,
|
|
2035
|
+
tokenFile: raw.local?.tokenFile ? (0, import_node_path7.resolve)(rootDir, raw.local.tokenFile) : appTokenFile(raw.app.id),
|
|
2036
|
+
credentialsFile: raw.local?.credentialsFile ? (0, import_node_path7.resolve)(rootDir, raw.local.credentialsFile) : appCredentialsFile(raw.app.id),
|
|
2037
|
+
devVarsFile: (0, import_node_path7.resolve)(rootDir, raw.local?.devVarsFile ?? ".dev.vars"),
|
|
1833
2038
|
gitignore: raw.local?.gitignore ?? true
|
|
1834
2039
|
};
|
|
2040
|
+
adoptRepoLocalCache((0, import_node_path7.resolve)(rootDir, ".odla/dev-token.json"), local.tokenFile, stderr);
|
|
2041
|
+
adoptRepoLocalCache((0, import_node_path7.resolve)(rootDir, ".odla/credentials.local.json"), local.credentialsFile, stderr);
|
|
2042
|
+
(0, import_node_fs9.rmSync)((0, import_node_path7.resolve)(rootDir, ".odla/handshake.local.json"), { force: true });
|
|
1835
2043
|
return {
|
|
1836
2044
|
...raw,
|
|
1837
2045
|
configPath: resolved,
|
|
@@ -1846,7 +2054,7 @@ async function loadProjectConfig(configPath = "odla.config.mjs", options = {}) {
|
|
|
1846
2054
|
async function resolveDataExport(cfg, value2, names) {
|
|
1847
2055
|
if (value2 === void 0 || value2 === null || value2 === false) return void 0;
|
|
1848
2056
|
if (typeof value2 !== "string") return value2;
|
|
1849
|
-
const target = (0,
|
|
2057
|
+
const target = (0, import_node_path7.isAbsolute)(value2) ? value2 : (0, import_node_path7.resolve)(cfg.rootDir, value2);
|
|
1850
2058
|
if (target.endsWith(".json")) {
|
|
1851
2059
|
return JSON.parse((0, import_node_fs9.readFileSync)(target, "utf8"));
|
|
1852
2060
|
}
|
|
@@ -1937,37 +2145,42 @@ function trimSlash(value2) {
|
|
|
1937
2145
|
function unique3(values) {
|
|
1938
2146
|
return [...new Set(values.filter(Boolean))];
|
|
1939
2147
|
}
|
|
1940
|
-
var import_node_fs9,
|
|
2148
|
+
var import_node_fs9, import_node_path7, import_node_url, import_apps, DEFAULT_PLATFORM, DEFAULT_ENVS, DEFAULT_SERVICES, configImportSerial, GOOGLE_CALENDAR_EVENTS_SCOPE, stderr;
|
|
1941
2149
|
var init_config = __esm({
|
|
1942
2150
|
"src/config.ts"() {
|
|
1943
2151
|
"use strict";
|
|
1944
2152
|
init_cjs_shims();
|
|
1945
2153
|
import_node_fs9 = require("fs");
|
|
1946
|
-
|
|
2154
|
+
import_node_path7 = require("path");
|
|
1947
2155
|
import_node_url = require("url");
|
|
1948
2156
|
import_apps = require("@odla-ai/apps");
|
|
1949
2157
|
init_ai_config_validation();
|
|
1950
2158
|
init_calendar_config();
|
|
1951
2159
|
init_integration_validation();
|
|
1952
2160
|
init_monitoring_validation();
|
|
2161
|
+
init_odla_home();
|
|
1953
2162
|
init_calendar_config();
|
|
1954
2163
|
DEFAULT_PLATFORM = "https://odla.ai";
|
|
1955
2164
|
DEFAULT_ENVS = ["dev"];
|
|
1956
2165
|
DEFAULT_SERVICES = ["db", "ai"];
|
|
1957
2166
|
configImportSerial = 0;
|
|
1958
2167
|
GOOGLE_CALENDAR_EVENTS_SCOPE = "https://www.googleapis.com/auth/calendar.events";
|
|
2168
|
+
stderr = { error: (message2) => {
|
|
2169
|
+
process.stderr.write(`${message2}
|
|
2170
|
+
`);
|
|
2171
|
+
} };
|
|
1959
2172
|
}
|
|
1960
2173
|
});
|
|
1961
2174
|
|
|
1962
2175
|
// src/operator-profiles.ts
|
|
1963
2176
|
function operatorProfileFile() {
|
|
1964
|
-
return (0,
|
|
1965
|
-
clean(
|
|
2177
|
+
return (0, import_node_path8.resolve)(
|
|
2178
|
+
clean(import_node_process13.default.env.ODLA_CONTEXT_FILE) ?? (0, import_node_path8.join)((0, import_node_os3.homedir)(), ".odla", "contexts.json")
|
|
1966
2179
|
);
|
|
1967
2180
|
}
|
|
1968
2181
|
function resolveOperatorProfile(parsed) {
|
|
1969
2182
|
const fromFlag = clean(stringOpt(parsed.options.context));
|
|
1970
|
-
const fromEnvironment = clean(
|
|
2183
|
+
const fromEnvironment = clean(import_node_process13.default.env.ODLA_CONTEXT);
|
|
1971
2184
|
const name = fromFlag ?? fromEnvironment ?? null;
|
|
1972
2185
|
const file = operatorProfileFile();
|
|
1973
2186
|
if (!name) {
|
|
@@ -2007,10 +2220,10 @@ function removeOperatorProfile(name, file = operatorProfileFile()) {
|
|
|
2007
2220
|
return true;
|
|
2008
2221
|
}
|
|
2009
2222
|
function operatorCredentialFiles(selection) {
|
|
2010
|
-
const base = selection.name ? (0,
|
|
2223
|
+
const base = selection.name ? (0, import_node_path8.join)((0, import_node_path8.dirname)(selection.file), "profiles", selection.name) : (0, import_node_path8.join)((0, import_node_os3.homedir)(), ".odla");
|
|
2011
2224
|
return {
|
|
2012
|
-
developer: (0,
|
|
2013
|
-
scoped: (0,
|
|
2225
|
+
developer: (0, import_node_path8.join)(base, "dev-token.json"),
|
|
2226
|
+
scoped: (0, import_node_path8.join)(base, "admin-token.local.json")
|
|
2014
2227
|
};
|
|
2015
2228
|
}
|
|
2016
2229
|
function assertOperatorName(value2, label) {
|
|
@@ -2082,15 +2295,15 @@ function clean(value2) {
|
|
|
2082
2295
|
const normalized = value2?.trim();
|
|
2083
2296
|
return normalized || void 0;
|
|
2084
2297
|
}
|
|
2085
|
-
var import_node_fs10,
|
|
2298
|
+
var import_node_fs10, import_node_os3, import_node_path8, import_node_process13;
|
|
2086
2299
|
var init_operator_profiles = __esm({
|
|
2087
2300
|
"src/operator-profiles.ts"() {
|
|
2088
2301
|
"use strict";
|
|
2089
2302
|
init_cjs_shims();
|
|
2090
2303
|
import_node_fs10 = require("fs");
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2304
|
+
import_node_os3 = require("os");
|
|
2305
|
+
import_node_path8 = require("path");
|
|
2306
|
+
import_node_process13 = __toESM(require("process"), 1);
|
|
2094
2307
|
init_argv();
|
|
2095
2308
|
init_local();
|
|
2096
2309
|
init_token();
|
|
@@ -2101,7 +2314,7 @@ var init_operator_profiles = __esm({
|
|
|
2101
2314
|
async function resolveOperatorContext(parsed, options = {}) {
|
|
2102
2315
|
const profile = resolveOperatorProfile(parsed);
|
|
2103
2316
|
const configArgument = stringOpt(parsed.options.config) ?? "odla.config.mjs";
|
|
2104
|
-
const configPath = (0,
|
|
2317
|
+
const configPath = (0, import_node_path9.resolve)(configArgument);
|
|
2105
2318
|
const explicitConfig = parsed.options.config !== void 0;
|
|
2106
2319
|
const hasConfig = (0, import_node_fs11.existsSync)(configPath);
|
|
2107
2320
|
if (!hasConfig && (!options.allowMissingConfig || explicitConfig)) {
|
|
@@ -2109,13 +2322,13 @@ async function resolveOperatorContext(parsed, options = {}) {
|
|
|
2109
2322
|
}
|
|
2110
2323
|
const loaded = hasConfig ? await loadProjectConfig(configArgument) : void 0;
|
|
2111
2324
|
const platformFlag = clean2(stringOpt(parsed.options.platform));
|
|
2112
|
-
const platformEnvironment = clean2(
|
|
2325
|
+
const platformEnvironment = clean2(import_node_process14.default.env.ODLA_PLATFORM_URL);
|
|
2113
2326
|
const platformValue = platformAudience(
|
|
2114
2327
|
platformFlag ?? platformEnvironment ?? profile.value?.platform ?? loaded?.platformUrl ?? DEFAULT_PLATFORM2
|
|
2115
2328
|
);
|
|
2116
2329
|
const platformSource = platformFlag ? "flag" : platformEnvironment ? "environment" : profile.value ? "profile" : loaded ? "config" : "default";
|
|
2117
2330
|
const appFlag = clean2(stringOpt(parsed.options.app));
|
|
2118
|
-
const appEnvironment = clean2(
|
|
2331
|
+
const appEnvironment = clean2(import_node_process14.default.env.ODLA_APP_ID);
|
|
2119
2332
|
const appValue = appFlag ?? appEnvironment ?? profile.value?.app ?? loaded?.app.id ?? null;
|
|
2120
2333
|
const appSource = appFlag ? "flag" : appEnvironment ? "environment" : profile.value?.app ? "profile" : loaded ? "config" : "unresolved";
|
|
2121
2334
|
if (appValue) {
|
|
@@ -2129,16 +2342,16 @@ async function resolveOperatorContext(parsed, options = {}) {
|
|
|
2129
2342
|
);
|
|
2130
2343
|
}
|
|
2131
2344
|
const envFlag = clean2(stringOpt(parsed.options.env));
|
|
2132
|
-
const envEnvironment = clean2(
|
|
2345
|
+
const envEnvironment = clean2(import_node_process14.default.env.ODLA_ENV);
|
|
2133
2346
|
const environmentValue = envFlag ?? envEnvironment ?? profile.value?.environment ?? options.defaultEnvironment ?? null;
|
|
2134
2347
|
const environmentSource = envFlag ? "flag" : envEnvironment ? "environment" : profile.value?.environment ? "profile" : options.defaultEnvironment ? "default" : "unresolved";
|
|
2135
2348
|
if (environmentValue) {
|
|
2136
2349
|
assertOperatorName(environmentValue, "environment");
|
|
2137
2350
|
}
|
|
2138
|
-
const rootDir = loaded?.rootDir ??
|
|
2351
|
+
const rootDir = loaded?.rootDir ?? import_node_process14.default.cwd();
|
|
2139
2352
|
const profileCredentials = operatorCredentialFiles(profile);
|
|
2140
|
-
const tokenFile = clean2(
|
|
2141
|
-
const
|
|
2353
|
+
const tokenFile = clean2(import_node_process14.default.env.ODLA_DEV_TOKEN_FILE) ? (0, import_node_path9.resolve)(import_node_process14.default.env.ODLA_DEV_TOKEN_FILE) : profile.name ? profileCredentials.developer : loaded?.local.tokenFile ?? profileCredentials.developer;
|
|
2354
|
+
const scopedTokenFile2 = clean2(import_node_process14.default.env.ODLA_ADMIN_TOKEN_FILE) ? (0, import_node_path9.resolve)(import_node_process14.default.env.ODLA_ADMIN_TOKEN_FILE) : profile.name ? profileCredentials.scoped : loaded ? (0, import_node_path9.join)(loaded.rootDir, ".odla", "admin-token.local.json") : profileCredentials.scoped;
|
|
2142
2355
|
const cfg = loaded ? {
|
|
2143
2356
|
...loaded,
|
|
2144
2357
|
platformUrl: platformValue,
|
|
@@ -2160,8 +2373,8 @@ async function resolveOperatorContext(parsed, options = {}) {
|
|
|
2160
2373
|
services: [],
|
|
2161
2374
|
local: {
|
|
2162
2375
|
tokenFile,
|
|
2163
|
-
credentialsFile: (0,
|
|
2164
|
-
devVarsFile: (0,
|
|
2376
|
+
credentialsFile: (0, import_node_path9.join)(rootDir, ".odla", "credentials.local.json"),
|
|
2377
|
+
devVarsFile: (0, import_node_path9.join)(rootDir, ".dev.vars"),
|
|
2165
2378
|
gitignore: true
|
|
2166
2379
|
}
|
|
2167
2380
|
};
|
|
@@ -2185,7 +2398,7 @@ async function resolveOperatorContext(parsed, options = {}) {
|
|
|
2185
2398
|
},
|
|
2186
2399
|
credentials: {
|
|
2187
2400
|
developerTokenFile: tokenFile,
|
|
2188
|
-
scopedTokenFile
|
|
2401
|
+
scopedTokenFile: scopedTokenFile2
|
|
2189
2402
|
}
|
|
2190
2403
|
};
|
|
2191
2404
|
}
|
|
@@ -2193,14 +2406,14 @@ function clean2(value2) {
|
|
|
2193
2406
|
const normalized = value2?.trim();
|
|
2194
2407
|
return normalized || void 0;
|
|
2195
2408
|
}
|
|
2196
|
-
var import_node_fs11,
|
|
2409
|
+
var import_node_fs11, import_node_path9, import_node_process14, DEFAULT_PLATFORM2;
|
|
2197
2410
|
var init_operator_context = __esm({
|
|
2198
2411
|
"src/operator-context.ts"() {
|
|
2199
2412
|
"use strict";
|
|
2200
2413
|
init_cjs_shims();
|
|
2201
2414
|
import_node_fs11 = require("fs");
|
|
2202
|
-
|
|
2203
|
-
|
|
2415
|
+
import_node_path9 = require("path");
|
|
2416
|
+
import_node_process14 = __toESM(require("process"), 1);
|
|
2204
2417
|
init_argv();
|
|
2205
2418
|
init_config();
|
|
2206
2419
|
init_operator_profiles();
|
|
@@ -2442,6 +2655,7 @@ async function whoamiCommand(parsed, deps = {}) {
|
|
|
2442
2655
|
} else {
|
|
2443
2656
|
out.log("projects: (none \u2014 every pm and discuss call will be refused)");
|
|
2444
2657
|
}
|
|
2658
|
+
printMachineBlock(cfg.platformUrl, out);
|
|
2445
2659
|
if (!identity.admin) {
|
|
2446
2660
|
if (identity.scopes.includes("platform:runbook:write")) {
|
|
2447
2661
|
out.log("\nThis exact scope can read and edit all platform runbook content.");
|
|
@@ -2452,6 +2666,21 @@ async function whoamiCommand(parsed, deps = {}) {
|
|
|
2452
2666
|
}
|
|
2453
2667
|
}
|
|
2454
2668
|
}
|
|
2669
|
+
function printMachineBlock(platformUrl, out) {
|
|
2670
|
+
const state2 = machineAuthState(platformAudience(platformUrl));
|
|
2671
|
+
if (!state2.enrolled) {
|
|
2672
|
+
out.log("\nmachine: not enrolled \u2014 every privileged command needs its own browser approval.");
|
|
2673
|
+
out.log(` End that with:
|
|
2674
|
+
${ENROL_EVERYTHING}`);
|
|
2675
|
+
return;
|
|
2676
|
+
}
|
|
2677
|
+
const reach = state2.appIds?.includes("*") ? "every app you own" : state2.appIds?.join(", ");
|
|
2678
|
+
out.log(`
|
|
2679
|
+
machine: enrolled${state2.deviceName ? ` as "${state2.deviceName}"` : ""}${reach ? ` for ${reach}` : ""}`);
|
|
2680
|
+
if (state2.scopes?.length) out.log(` carrying ${state2.scopes.join(", ")}`);
|
|
2681
|
+
const lapse = lapseNotice(state2);
|
|
2682
|
+
if (lapse) out.log(` ${lapse}`);
|
|
2683
|
+
}
|
|
2455
2684
|
var text2;
|
|
2456
2685
|
var init_whoami_command = __esm({
|
|
2457
2686
|
"src/whoami-command.ts"() {
|
|
@@ -2460,6 +2689,8 @@ var init_whoami_command = __esm({
|
|
|
2460
2689
|
init_argv();
|
|
2461
2690
|
init_operator_context();
|
|
2462
2691
|
init_token();
|
|
2692
|
+
init_auth_guidance();
|
|
2693
|
+
init_token();
|
|
2463
2694
|
text2 = (value2) => typeof value2 === "string" && value2.trim() ? value2.trim() : null;
|
|
2464
2695
|
}
|
|
2465
2696
|
});
|
|
@@ -2487,12 +2718,17 @@ async function authCommand(parsed, deps = {}) {
|
|
|
2487
2718
|
const { cfg } = context;
|
|
2488
2719
|
const out = deps.stdout ?? console;
|
|
2489
2720
|
const doFetch = deps.fetch ?? fetch;
|
|
2490
|
-
const email = stringOpt(parsed.options.email) ??
|
|
2721
|
+
const email = stringOpt(parsed.options.email) ?? import_node_process15.default.env.ODLA_USER_EMAIL?.trim();
|
|
2491
2722
|
if (!email) {
|
|
2492
2723
|
throw new Error(
|
|
2493
2724
|
"auth login requires --email <odla-account> or ODLA_USER_EMAIL; confirm the signed-in odla email instead of using git or GitHub identity"
|
|
2494
2725
|
);
|
|
2495
2726
|
}
|
|
2727
|
+
if (!machineAuthState(platformAudience(cfg.platformUrl)).enrolled) {
|
|
2728
|
+
out.error("odla: this machine is not enrolled, so this approval buys one project until it lapses.");
|
|
2729
|
+
out.error(` For one approval that covers every app you own, with no repeats:
|
|
2730
|
+
${ENROL_EVERYTHING}`);
|
|
2731
|
+
}
|
|
2496
2732
|
const token = await getDeveloperToken(
|
|
2497
2733
|
cfg,
|
|
2498
2734
|
{
|
|
@@ -2521,15 +2757,16 @@ async function authCommand(parsed, deps = {}) {
|
|
|
2521
2757
|
out.log(`Authorized ${identity.displayName}${handle} for ${cfg.app.id}.`);
|
|
2522
2758
|
out.log(`odla account: ${identity.email ?? "not returned"}`);
|
|
2523
2759
|
}
|
|
2524
|
-
var
|
|
2760
|
+
var import_node_process15;
|
|
2525
2761
|
var init_auth_command = __esm({
|
|
2526
2762
|
"src/auth-command.ts"() {
|
|
2527
2763
|
"use strict";
|
|
2528
2764
|
init_cjs_shims();
|
|
2529
|
-
|
|
2765
|
+
import_node_process15 = __toESM(require("process"), 1);
|
|
2530
2766
|
init_argv();
|
|
2531
2767
|
init_operator_context();
|
|
2532
2768
|
init_token();
|
|
2769
|
+
init_auth_guidance();
|
|
2533
2770
|
init_whoami_command();
|
|
2534
2771
|
}
|
|
2535
2772
|
});
|
|
@@ -3043,15 +3280,15 @@ var init_brand_design_unpack = __esm({
|
|
|
3043
3280
|
|
|
3044
3281
|
// src/brand-command.ts
|
|
3045
3282
|
async function readBundle(source, deps) {
|
|
3046
|
-
if (source !== "-") return (0, import_promises.readFile)((0,
|
|
3283
|
+
if (source !== "-") return (0, import_promises.readFile)((0, import_node_path10.resolve)(source), "utf8");
|
|
3047
3284
|
const readStdin = deps.readStdin;
|
|
3048
3285
|
if (!readStdin) throw new Error("reading a bundle from stdin is not supported here");
|
|
3049
3286
|
return readStdin();
|
|
3050
3287
|
}
|
|
3051
3288
|
async function writeAll(result, outDir) {
|
|
3052
3289
|
for (const file of result.files) {
|
|
3053
|
-
const target = (0,
|
|
3054
|
-
await (0, import_promises.mkdir)((0,
|
|
3290
|
+
const target = (0, import_node_path10.resolve)(outDir, file.path);
|
|
3291
|
+
await (0, import_promises.mkdir)((0, import_node_path10.dirname)(target), { recursive: true });
|
|
3055
3292
|
await (0, import_promises.writeFile)(target, file.bytes);
|
|
3056
3293
|
}
|
|
3057
3294
|
}
|
|
@@ -3059,7 +3296,7 @@ async function designUnpack(parsed, deps) {
|
|
|
3059
3296
|
assertArgs(parsed, ["out", "json"], 4);
|
|
3060
3297
|
const source = parsed.positionals[3];
|
|
3061
3298
|
if (!source) throw new Error(USAGE);
|
|
3062
|
-
const outDir = (0,
|
|
3299
|
+
const outDir = (0, import_node_path10.resolve)(stringOpt(parsed.options.out) ?? "design");
|
|
3063
3300
|
const result = unpackDesign(await readBundle(source, deps));
|
|
3064
3301
|
await writeAll(result, outDir);
|
|
3065
3302
|
const out = deps.stdout ?? console;
|
|
@@ -3085,13 +3322,13 @@ async function brandCommand(parsed, deps) {
|
|
|
3085
3322
|
}
|
|
3086
3323
|
throw new Error(USAGE);
|
|
3087
3324
|
}
|
|
3088
|
-
var import_promises,
|
|
3325
|
+
var import_promises, import_node_path10, USAGE;
|
|
3089
3326
|
var init_brand_command = __esm({
|
|
3090
3327
|
"src/brand-command.ts"() {
|
|
3091
3328
|
"use strict";
|
|
3092
3329
|
init_cjs_shims();
|
|
3093
3330
|
import_promises = require("fs/promises");
|
|
3094
|
-
|
|
3331
|
+
import_node_path10 = require("path");
|
|
3095
3332
|
init_argv();
|
|
3096
3333
|
init_brand_design_unpack();
|
|
3097
3334
|
USAGE = "usage: odla-ai brand design unpack <bundle.html|-> [--out <dir>] [--json]";
|
|
@@ -4139,7 +4376,7 @@ async function operationClient(cfg, options, purpose) {
|
|
|
4139
4376
|
platform: cfg.platformUrl,
|
|
4140
4377
|
scope: "app:config:write",
|
|
4141
4378
|
token: options.token,
|
|
4142
|
-
tokenFile: (0,
|
|
4379
|
+
tokenFile: (0, import_node_path11.join)(cfg.rootDir, ".odla", "admin-token.local.json"),
|
|
4143
4380
|
rootDir: cfg.rootDir,
|
|
4144
4381
|
email: options.email,
|
|
4145
4382
|
open: options.open,
|
|
@@ -4191,13 +4428,13 @@ function normalizeRequestError(error) {
|
|
|
4191
4428
|
function record4(value2) {
|
|
4192
4429
|
return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
|
|
4193
4430
|
}
|
|
4194
|
-
var import_apps6,
|
|
4431
|
+
var import_apps6, import_node_path11, IDEMPOTENCY_KEY, DEFAULT_WAIT_SECONDS, DEFAULT_INTERVAL_SECONDS;
|
|
4195
4432
|
var init_config_operation_command = __esm({
|
|
4196
4433
|
"src/config-operation-command.ts"() {
|
|
4197
4434
|
"use strict";
|
|
4198
4435
|
init_cjs_shims();
|
|
4199
4436
|
import_apps6 = require("@odla-ai/apps");
|
|
4200
|
-
|
|
4437
|
+
import_node_path11 = require("path");
|
|
4201
4438
|
init_admin_ai_auth();
|
|
4202
4439
|
init_version();
|
|
4203
4440
|
init_config();
|
|
@@ -4520,7 +4757,7 @@ async function inspectConfig(options) {
|
|
|
4520
4757
|
platform: cfg.platformUrl,
|
|
4521
4758
|
scope: "app:config:read",
|
|
4522
4759
|
token: options.token,
|
|
4523
|
-
tokenFile: (0,
|
|
4760
|
+
tokenFile: (0, import_node_path12.join)(cfg.rootDir, ".odla", "admin-token.local.json"),
|
|
4524
4761
|
rootDir: cfg.rootDir,
|
|
4525
4762
|
email: options.email,
|
|
4526
4763
|
open: options.open,
|
|
@@ -4649,13 +4886,13 @@ function studioSettingsUrl(reconciliation) {
|
|
|
4649
4886
|
function quoteArg2(value2) {
|
|
4650
4887
|
return `'${value2.replace(/'/g, `'\\''`)}'`;
|
|
4651
4888
|
}
|
|
4652
|
-
var import_apps8,
|
|
4889
|
+
var import_apps8, import_node_path12;
|
|
4653
4890
|
var init_config_reconcile_command = __esm({
|
|
4654
4891
|
"src/config-reconcile-command.ts"() {
|
|
4655
4892
|
"use strict";
|
|
4656
4893
|
init_cjs_shims();
|
|
4657
4894
|
import_apps8 = require("@odla-ai/apps");
|
|
4658
|
-
|
|
4895
|
+
import_node_path12 = require("path");
|
|
4659
4896
|
init_admin_ai_auth();
|
|
4660
4897
|
init_config();
|
|
4661
4898
|
init_config_reconcile_digest();
|
|
@@ -4668,7 +4905,7 @@ var init_config_reconcile_command = __esm({
|
|
|
4668
4905
|
// src/wrangler.ts
|
|
4669
4906
|
function findWranglerConfig(rootDir) {
|
|
4670
4907
|
for (const name of WRANGLER_CONFIG_FILES) {
|
|
4671
|
-
const path = (0,
|
|
4908
|
+
const path = (0, import_node_path13.join)(rootDir, name);
|
|
4672
4909
|
if ((0, import_node_fs14.existsSync)(path)) return path;
|
|
4673
4910
|
}
|
|
4674
4911
|
return null;
|
|
@@ -4780,22 +5017,22 @@ function wranglerBulkSecrets(run, opts) {
|
|
|
4780
5017
|
];
|
|
4781
5018
|
return run("npx", args, { input: JSON.stringify(opts.secrets), cwd: opts.cwd });
|
|
4782
5019
|
}
|
|
4783
|
-
var import_node_child_process2, import_node_fs14,
|
|
5020
|
+
var import_node_child_process2, import_node_fs14, import_node_path13, defaultRunner, WRANGLER_CONFIG_FILES;
|
|
4784
5021
|
var init_wrangler = __esm({
|
|
4785
5022
|
"src/wrangler.ts"() {
|
|
4786
5023
|
"use strict";
|
|
4787
5024
|
init_cjs_shims();
|
|
4788
5025
|
import_node_child_process2 = require("child_process");
|
|
4789
5026
|
import_node_fs14 = require("fs");
|
|
4790
|
-
|
|
5027
|
+
import_node_path13 = require("path");
|
|
4791
5028
|
defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
|
|
4792
5029
|
const child = (0, import_node_child_process2.spawn)(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
|
|
4793
5030
|
let stdout = "";
|
|
4794
|
-
let
|
|
5031
|
+
let stderr2 = "";
|
|
4795
5032
|
child.stdout.on("data", (chunk) => stdout += chunk.toString());
|
|
4796
|
-
child.stderr.on("data", (chunk) =>
|
|
5033
|
+
child.stderr.on("data", (chunk) => stderr2 += chunk.toString());
|
|
4797
5034
|
child.on("error", reject);
|
|
4798
|
-
child.on("close", (code) => resolvePromise({ code: code ?? 1, stdout, stderr }));
|
|
5035
|
+
child.on("close", (code) => resolvePromise({ code: code ?? 1, stdout, stderr: stderr2 }));
|
|
4799
5036
|
child.stdin.end(opts?.input ?? "");
|
|
4800
5037
|
});
|
|
4801
5038
|
WRANGLER_CONFIG_FILES = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"];
|
|
@@ -4839,21 +5076,21 @@ function wranglerWarnings(rootDir) {
|
|
|
4839
5076
|
const blocks = [{ label: "", block: config }];
|
|
4840
5077
|
const envs = config.env;
|
|
4841
5078
|
if (envs && typeof envs === "object") {
|
|
4842
|
-
for (const [name,
|
|
4843
|
-
if (
|
|
5079
|
+
for (const [name, block2] of Object.entries(envs)) {
|
|
5080
|
+
if (block2 && typeof block2 === "object") blocks.push({ label: `env.${name}.`, block: block2 });
|
|
4844
5081
|
}
|
|
4845
5082
|
}
|
|
4846
|
-
for (const { label, block } of blocks) {
|
|
4847
|
-
const assets =
|
|
5083
|
+
for (const { label, block: block2 } of blocks) {
|
|
5084
|
+
const assets = block2.assets;
|
|
4848
5085
|
if (assets?.directory) {
|
|
4849
|
-
const dir = (0,
|
|
4850
|
-
if (dir === (0,
|
|
5086
|
+
const dir = (0, import_node_path14.resolve)(rootDir, assets.directory);
|
|
5087
|
+
if (dir === (0, import_node_path14.resolve)(rootDir)) {
|
|
4851
5088
|
warnings.push(`${label}assets.directory is the project root \u2014 point it at a dedicated build dir (wrangler dev fails with "spawn EBADF")`);
|
|
4852
|
-
} else if ((0, import_node_fs15.existsSync)((0,
|
|
5089
|
+
} else if ((0, import_node_fs15.existsSync)((0, import_node_path14.join)(dir, "node_modules"))) {
|
|
4853
5090
|
warnings.push(`${label}assets.directory contains node_modules \u2014 wrangler dev's watcher will exhaust file descriptors`);
|
|
4854
5091
|
}
|
|
4855
5092
|
}
|
|
4856
|
-
const vars =
|
|
5093
|
+
const vars = block2.vars;
|
|
4857
5094
|
if (vars && typeof vars === "object") {
|
|
4858
5095
|
for (const [name, value2] of Object.entries(vars)) {
|
|
4859
5096
|
if (name === "ODLA_API_KEY" || name === "ODLA_O11Y_TOKEN" || typeof value2 === "string" && looksSecret(value2)) {
|
|
@@ -4884,7 +5121,7 @@ function o11yProjectWarnings(rootDir) {
|
|
|
4884
5121
|
warnings.push("cannot verify o11y Worker instrumentation \u2014 add a parseable wrangler.jsonc/json config");
|
|
4885
5122
|
return warnings;
|
|
4886
5123
|
}
|
|
4887
|
-
const main = typeof config.main === "string" ? (0,
|
|
5124
|
+
const main = typeof config.main === "string" ? (0, import_node_path14.resolve)(rootDir, config.main) : null;
|
|
4888
5125
|
if (!main || !(0, import_node_fs15.existsSync)(main)) {
|
|
4889
5126
|
warnings.push("cannot verify o11y Worker instrumentation \u2014 wrangler main is missing or unreadable");
|
|
4890
5127
|
} else {
|
|
@@ -4914,19 +5151,19 @@ function calendarProjectWarnings(rootDir) {
|
|
|
4914
5151
|
}
|
|
4915
5152
|
function readPackageJson(rootDir) {
|
|
4916
5153
|
try {
|
|
4917
|
-
return JSON.parse((0, import_node_fs15.readFileSync)((0,
|
|
5154
|
+
return JSON.parse((0, import_node_fs15.readFileSync)((0, import_node_path14.join)(rootDir, "package.json"), "utf8"));
|
|
4918
5155
|
} catch {
|
|
4919
5156
|
return null;
|
|
4920
5157
|
}
|
|
4921
5158
|
}
|
|
4922
|
-
var import_node_child_process3, import_node_fs15,
|
|
5159
|
+
var import_node_child_process3, import_node_fs15, import_node_path14, defaultExec;
|
|
4923
5160
|
var init_doctor_checks = __esm({
|
|
4924
5161
|
"src/doctor-checks.ts"() {
|
|
4925
5162
|
"use strict";
|
|
4926
5163
|
init_cjs_shims();
|
|
4927
5164
|
import_node_child_process3 = require("child_process");
|
|
4928
5165
|
import_node_fs15 = require("fs");
|
|
4929
|
-
|
|
5166
|
+
import_node_path14 = require("path");
|
|
4930
5167
|
init_redact();
|
|
4931
5168
|
init_local();
|
|
4932
5169
|
init_wrangler();
|
|
@@ -5263,8 +5500,8 @@ var init_harness_options = __esm({
|
|
|
5263
5500
|
// src/init.ts
|
|
5264
5501
|
function initProject(options) {
|
|
5265
5502
|
const out = options.stdout ?? console;
|
|
5266
|
-
const rootDir = (0,
|
|
5267
|
-
const configPath = (0,
|
|
5503
|
+
const rootDir = (0, import_node_path15.resolve)(options.rootDir ?? process.cwd());
|
|
5504
|
+
const configPath = (0, import_node_path15.resolve)(rootDir, options.configPath ?? "odla.config.mjs");
|
|
5268
5505
|
if ((0, import_node_fs16.existsSync)(configPath) && !options.force) {
|
|
5269
5506
|
throw new Error(`${configPath} already exists. Pass --force to overwrite.`);
|
|
5270
5507
|
}
|
|
@@ -5281,12 +5518,12 @@ function initProject(options) {
|
|
|
5281
5518
|
}
|
|
5282
5519
|
}
|
|
5283
5520
|
const aiProvider = options.aiProvider;
|
|
5284
|
-
(0, import_node_fs16.mkdirSync)((0,
|
|
5285
|
-
(0, import_node_fs16.mkdirSync)((0,
|
|
5286
|
-
(0, import_node_fs16.mkdirSync)((0,
|
|
5521
|
+
(0, import_node_fs16.mkdirSync)((0, import_node_path15.dirname)(configPath), { recursive: true });
|
|
5522
|
+
(0, import_node_fs16.mkdirSync)((0, import_node_path15.resolve)(rootDir, "src/odla"), { recursive: true });
|
|
5523
|
+
(0, import_node_fs16.mkdirSync)((0, import_node_path15.resolve)(rootDir, ".odla"), { recursive: true });
|
|
5287
5524
|
(0, import_node_fs16.writeFileSync)(configPath, configTemplate({ appId: options.appId, name: options.name, envs, services, aiProvider }));
|
|
5288
|
-
writeIfMissing((0,
|
|
5289
|
-
writeIfMissing((0,
|
|
5525
|
+
writeIfMissing((0, import_node_path15.resolve)(rootDir, "src/odla/schema.mjs"), schemaTemplate());
|
|
5526
|
+
writeIfMissing((0, import_node_path15.resolve)(rootDir, "src/odla/rules.mjs"), rulesTemplate());
|
|
5290
5527
|
ensureGitignore(rootDir);
|
|
5291
5528
|
out.log(`created ${relativeDisplay(configPath, rootDir)}`);
|
|
5292
5529
|
out.log("created src/odla/schema.mjs and src/odla/rules.mjs");
|
|
@@ -5353,8 +5590,10 @@ ${calendar}
|
|
|
5353
5590
|
// prod: "https://example.com",
|
|
5354
5591
|
},
|
|
5355
5592
|
local: {
|
|
5356
|
-
|
|
5357
|
-
|
|
5593
|
+
// Credentials live in ~/.odla, per machine, so every worktree of this app
|
|
5594
|
+
// shares one approval instead of asking for its own. Pinning tokenFile or
|
|
5595
|
+
// credentialsFile here still works and still overrides that \u2014 it just puts
|
|
5596
|
+
// this checkout back on its own island.
|
|
5358
5597
|
devVarsFile: ".dev.vars",
|
|
5359
5598
|
},
|
|
5360
5599
|
};
|
|
@@ -5397,13 +5636,13 @@ function defaultKeyEnv(provider) {
|
|
|
5397
5636
|
function relativeDisplay(path, rootDir) {
|
|
5398
5637
|
return path.startsWith(rootDir) ? path.slice(rootDir.length + 1) : path;
|
|
5399
5638
|
}
|
|
5400
|
-
var import_node_fs16,
|
|
5639
|
+
var import_node_fs16, import_node_path15, import_apps9;
|
|
5401
5640
|
var init_init = __esm({
|
|
5402
5641
|
"src/init.ts"() {
|
|
5403
5642
|
"use strict";
|
|
5404
5643
|
init_cjs_shims();
|
|
5405
5644
|
import_node_fs16 = require("fs");
|
|
5406
|
-
|
|
5645
|
+
import_node_path15 = require("path");
|
|
5407
5646
|
import_apps9 = require("@odla-ai/apps");
|
|
5408
5647
|
init_local();
|
|
5409
5648
|
}
|
|
@@ -5736,8 +5975,8 @@ function installSkill(options = {}) {
|
|
|
5736
5975
|
const files = listFiles(sourceDir);
|
|
5737
5976
|
if (files.length === 0) throw new Error(`no bundled skills found at ${sourceDir}`);
|
|
5738
5977
|
const harnesses = normalizeHarnesses(options.harnesses, options.global === true);
|
|
5739
|
-
const root = (0,
|
|
5740
|
-
const home = (0,
|
|
5978
|
+
const root = (0, import_node_path16.resolve)(options.dir ?? process.cwd());
|
|
5979
|
+
const home = (0, import_node_path16.resolve)(options.homeDir ?? (0, import_node_os4.homedir)());
|
|
5741
5980
|
const plans = /* @__PURE__ */ new Map();
|
|
5742
5981
|
const targets = /* @__PURE__ */ new Map();
|
|
5743
5982
|
const rememberTarget = (harness, target) => {
|
|
@@ -5751,48 +5990,48 @@ function installSkill(options = {}) {
|
|
|
5751
5990
|
plans.set(target, { target, content: content2, boundary, managedMerge });
|
|
5752
5991
|
};
|
|
5753
5992
|
const planSkillTree = (targetDir2, boundary = root) => {
|
|
5754
|
-
for (const rel of files) plan((0,
|
|
5993
|
+
for (const rel of files) plan((0, import_node_path16.join)(targetDir2, rel), (0, import_node_fs17.readFileSync)((0, import_node_path16.join)(sourceDir, rel), "utf8"), false, boundary);
|
|
5755
5994
|
};
|
|
5756
5995
|
let targetDir;
|
|
5757
5996
|
if (options.global) {
|
|
5758
|
-
const claudeRoot = (0,
|
|
5759
|
-
const codexRoot = (0,
|
|
5997
|
+
const claudeRoot = (0, import_node_path16.join)(home, ".claude", "skills");
|
|
5998
|
+
const codexRoot = (0, import_node_path16.resolve)(options.codexHomeDir ?? process.env.CODEX_HOME ?? (0, import_node_path16.join)(home, ".codex"), "skills");
|
|
5760
5999
|
targetDir = harnesses[0] === "codex" ? codexRoot : claudeRoot;
|
|
5761
6000
|
for (const harness of harnesses) {
|
|
5762
6001
|
const skillRoot = harness === "claude" ? claudeRoot : codexRoot;
|
|
5763
|
-
planSkillTree(skillRoot, harness === "claude" ? home : (0,
|
|
6002
|
+
planSkillTree(skillRoot, harness === "claude" ? home : (0, import_node_path16.dirname)((0, import_node_path16.dirname)(codexRoot)));
|
|
5764
6003
|
rememberTarget(harness, skillRoot);
|
|
5765
6004
|
}
|
|
5766
6005
|
} else {
|
|
5767
|
-
const sharedRoot = (0,
|
|
6006
|
+
const sharedRoot = (0, import_node_path16.join)(root, ".agents", "skills");
|
|
5768
6007
|
planSkillTree(sharedRoot);
|
|
5769
|
-
const claudeRoot = (0,
|
|
6008
|
+
const claudeRoot = (0, import_node_path16.join)(root, ".claude", "skills");
|
|
5770
6009
|
targetDir = harnesses.includes("claude") ? claudeRoot : sharedRoot;
|
|
5771
6010
|
for (const harness of harnesses) rememberTarget(harness, sharedRoot);
|
|
5772
6011
|
if (harnesses.includes("claude")) {
|
|
5773
6012
|
for (const skill of skillNames(files)) {
|
|
5774
|
-
const canonical2 = (0, import_node_fs17.readFileSync)((0,
|
|
5775
|
-
plan((0,
|
|
6013
|
+
const canonical2 = (0, import_node_fs17.readFileSync)((0, import_node_path16.join)(sourceDir, skill, "SKILL.md"), "utf8");
|
|
6014
|
+
plan((0, import_node_path16.join)(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical2));
|
|
5776
6015
|
}
|
|
5777
6016
|
rememberTarget("claude", claudeRoot);
|
|
5778
6017
|
}
|
|
5779
6018
|
if (harnesses.includes("cursor")) {
|
|
5780
|
-
const cursorRule = (0,
|
|
6019
|
+
const cursorRule = (0, import_node_path16.join)(root, ".cursor", "rules", "odla.mdc");
|
|
5781
6020
|
plan(cursorRule, CURSOR_RULE);
|
|
5782
6021
|
rememberTarget("cursor", cursorRule);
|
|
5783
6022
|
}
|
|
5784
6023
|
if (harnesses.includes("agents")) {
|
|
5785
|
-
const agentsFile = (0,
|
|
6024
|
+
const agentsFile = (0, import_node_path16.join)(root, "AGENTS.md");
|
|
5786
6025
|
plan(agentsFile, managedFileContent(agentsFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
5787
6026
|
rememberTarget("agents", agentsFile);
|
|
5788
6027
|
}
|
|
5789
6028
|
if (harnesses.includes("copilot")) {
|
|
5790
|
-
const copilotFile = (0,
|
|
6029
|
+
const copilotFile = (0, import_node_path16.join)(root, ".github", "copilot-instructions.md");
|
|
5791
6030
|
plan(copilotFile, managedFileContent(copilotFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
5792
6031
|
rememberTarget("copilot", copilotFile);
|
|
5793
6032
|
}
|
|
5794
6033
|
if (harnesses.includes("gemini")) {
|
|
5795
|
-
const geminiFile = (0,
|
|
6034
|
+
const geminiFile = (0, import_node_path16.join)(root, "GEMINI.md");
|
|
5796
6035
|
plan(geminiFile, managedFileContent(geminiFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
5797
6036
|
rememberTarget("gemini", geminiFile);
|
|
5798
6037
|
}
|
|
@@ -5828,7 +6067,7 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
|
|
|
5828
6067
|
}
|
|
5829
6068
|
for (const file of plans.values()) {
|
|
5830
6069
|
if (!(0, import_node_fs17.existsSync)(file.target) || (0, import_node_fs17.readFileSync)(file.target, "utf8") !== file.content) {
|
|
5831
|
-
(0, import_node_fs17.mkdirSync)((0,
|
|
6070
|
+
(0, import_node_fs17.mkdirSync)((0, import_node_path16.dirname)(file.target), { recursive: true });
|
|
5832
6071
|
(0, import_node_fs17.writeFileSync)(file.target, file.content);
|
|
5833
6072
|
}
|
|
5834
6073
|
}
|
|
@@ -5848,7 +6087,7 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
|
|
|
5848
6087
|
};
|
|
5849
6088
|
}
|
|
5850
6089
|
function pathsUnder(root, paths) {
|
|
5851
|
-
return [...paths].map((path) => (0,
|
|
6090
|
+
return [...paths].map((path) => (0, import_node_path16.relative)(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${import_node_path16.sep}`) && !(0, import_node_path16.isAbsolute)(path)).sort();
|
|
5852
6091
|
}
|
|
5853
6092
|
function normalizeHarnesses(values, global) {
|
|
5854
6093
|
const requested = values?.length ? values : ["claude"];
|
|
@@ -5867,10 +6106,10 @@ function normalizeHarnesses(values, global) {
|
|
|
5867
6106
|
}
|
|
5868
6107
|
return expanded;
|
|
5869
6108
|
}
|
|
5870
|
-
function managedFileContent(path,
|
|
6109
|
+
function managedFileContent(path, block2, force, boundary) {
|
|
5871
6110
|
const symlink = symlinkedComponent(boundary, path);
|
|
5872
6111
|
if (symlink) throw new Error(`refusing to manage ${path}: symbolic link component ${symlink}`);
|
|
5873
|
-
if (!(0, import_node_fs17.existsSync)(path)) return `${
|
|
6112
|
+
if (!(0, import_node_fs17.existsSync)(path)) return `${block2}
|
|
5874
6113
|
`;
|
|
5875
6114
|
const current = (0, import_node_fs17.readFileSync)(path, "utf8");
|
|
5876
6115
|
const start = "<!-- odla-ai agent setup:start -->";
|
|
@@ -5882,24 +6121,24 @@ function managedFileContent(path, block, force, boundary) {
|
|
|
5882
6121
|
}
|
|
5883
6122
|
if (startAt === -1) {
|
|
5884
6123
|
const separator = current.length === 0 || current.endsWith("\n\n") ? "" : current.endsWith("\n") ? "\n" : "\n\n";
|
|
5885
|
-
return `${current}${separator}${
|
|
6124
|
+
return `${current}${separator}${block2}
|
|
5886
6125
|
`;
|
|
5887
6126
|
}
|
|
5888
6127
|
const afterEnd = endAt + end.length;
|
|
5889
6128
|
const existing = current.slice(startAt, afterEnd);
|
|
5890
|
-
if (existing !==
|
|
6129
|
+
if (existing !== block2 && !force) {
|
|
5891
6130
|
throw new Error(`odla-managed section modified locally in ${path}; re-run with --force to replace that section`);
|
|
5892
6131
|
}
|
|
5893
|
-
return `${current.slice(0, startAt)}${
|
|
6132
|
+
return `${current.slice(0, startAt)}${block2}${current.slice(afterEnd)}`;
|
|
5894
6133
|
}
|
|
5895
6134
|
function symlinkedComponent(boundary, target) {
|
|
5896
|
-
const rel = (0,
|
|
5897
|
-
if (rel === ".." || rel.startsWith(`..${
|
|
6135
|
+
const rel = (0, import_node_path16.relative)(boundary, target);
|
|
6136
|
+
if (rel === ".." || rel.startsWith(`..${import_node_path16.sep}`) || (0, import_node_path16.isAbsolute)(rel)) {
|
|
5898
6137
|
throw new Error(`agent setup target escapes its install root: ${target}`);
|
|
5899
6138
|
}
|
|
5900
6139
|
let current = boundary;
|
|
5901
|
-
for (const part of rel.split(
|
|
5902
|
-
current = (0,
|
|
6140
|
+
for (const part of rel.split(import_node_path16.sep).filter(Boolean)) {
|
|
6141
|
+
current = (0, import_node_path16.join)(current, part);
|
|
5903
6142
|
try {
|
|
5904
6143
|
if ((0, import_node_fs17.lstatSync)(current).isSymbolicLink()) return current;
|
|
5905
6144
|
} catch (error) {
|
|
@@ -5916,22 +6155,22 @@ function listFiles(dir) {
|
|
|
5916
6155
|
const results = [];
|
|
5917
6156
|
const walk = (current) => {
|
|
5918
6157
|
for (const entry of (0, import_node_fs17.readdirSync)(current, { withFileTypes: true })) {
|
|
5919
|
-
const path = (0,
|
|
6158
|
+
const path = (0, import_node_path16.join)(current, entry.name);
|
|
5920
6159
|
if (entry.isDirectory()) walk(path);
|
|
5921
|
-
else results.push((0,
|
|
6160
|
+
else results.push((0, import_node_path16.relative)(dir, path));
|
|
5922
6161
|
}
|
|
5923
6162
|
};
|
|
5924
6163
|
walk(dir);
|
|
5925
6164
|
return results.sort();
|
|
5926
6165
|
}
|
|
5927
|
-
var import_node_fs17,
|
|
6166
|
+
var import_node_fs17, import_node_os4, import_node_path16, import_node_url2, AGENT_HARNESSES;
|
|
5928
6167
|
var init_skill = __esm({
|
|
5929
6168
|
"src/skill.ts"() {
|
|
5930
6169
|
"use strict";
|
|
5931
6170
|
init_cjs_shims();
|
|
5932
6171
|
import_node_fs17 = require("fs");
|
|
5933
|
-
|
|
5934
|
-
|
|
6172
|
+
import_node_os4 = require("os");
|
|
6173
|
+
import_node_path16 = require("path");
|
|
5935
6174
|
import_node_url2 = require("url");
|
|
5936
6175
|
init_skill_adapters();
|
|
5937
6176
|
AGENT_HARNESSES = ["claude", "codex", "cursor", "copilot", "gemini", "agents"];
|
|
@@ -6405,7 +6644,7 @@ function allowedWorkspacePath(relativePath) {
|
|
|
6405
6644
|
async function gitOutput(cwd, args, maxBytes) {
|
|
6406
6645
|
const child = (0, import_child_process2.spawn)("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"], shell: false });
|
|
6407
6646
|
const stdout = [];
|
|
6408
|
-
const
|
|
6647
|
+
const stderr2 = [];
|
|
6409
6648
|
let bytes = 0;
|
|
6410
6649
|
child.stdout.on("data", (chunk) => {
|
|
6411
6650
|
bytes += chunk.byteLength;
|
|
@@ -6413,20 +6652,20 @@ async function gitOutput(cwd, args, maxBytes) {
|
|
|
6413
6652
|
else stdout.push(chunk);
|
|
6414
6653
|
});
|
|
6415
6654
|
child.stderr.on("data", (chunk) => {
|
|
6416
|
-
if (
|
|
6655
|
+
if (stderr2.reduce((sum, value2) => sum + value2.byteLength, 0) < 16384) stderr2.push(chunk);
|
|
6417
6656
|
});
|
|
6418
6657
|
const code = await new Promise((accept, reject) => {
|
|
6419
6658
|
child.once("error", reject);
|
|
6420
6659
|
child.once("exit", accept);
|
|
6421
6660
|
});
|
|
6422
6661
|
if (bytes > maxBytes) throw new Error(`git output exceeds ${maxBytes} bytes`);
|
|
6423
|
-
if (code !== 0) throw new Error(`git command failed: ${Buffer.concat(
|
|
6662
|
+
if (code !== 0) throw new Error(`git command failed: ${Buffer.concat(stderr2).toString("utf8").slice(0, 1e3)}`);
|
|
6424
6663
|
return Buffer.concat(stdout);
|
|
6425
6664
|
}
|
|
6426
6665
|
async function gitBlobs(cwd, entries, maxBytes) {
|
|
6427
6666
|
const child = (0, import_child_process2.spawn)("git", ["cat-file", "--batch"], { cwd, stdio: ["pipe", "pipe", "pipe"], shell: false });
|
|
6428
6667
|
const stdout = [];
|
|
6429
|
-
const
|
|
6668
|
+
const stderr2 = [];
|
|
6430
6669
|
let bytes = 0;
|
|
6431
6670
|
child.stdout.on("data", (chunk) => {
|
|
6432
6671
|
bytes += chunk.byteLength;
|
|
@@ -6434,7 +6673,7 @@ async function gitBlobs(cwd, entries, maxBytes) {
|
|
|
6434
6673
|
else stdout.push(chunk);
|
|
6435
6674
|
});
|
|
6436
6675
|
child.stderr.on("data", (chunk) => {
|
|
6437
|
-
if (
|
|
6676
|
+
if (stderr2.reduce((sum, value2) => sum + value2.byteLength, 0) < 16384) stderr2.push(chunk);
|
|
6438
6677
|
});
|
|
6439
6678
|
child.stdin.end(`${entries.map((entry) => entry.hash).join("\n")}
|
|
6440
6679
|
`);
|
|
@@ -6443,7 +6682,7 @@ async function gitBlobs(cwd, entries, maxBytes) {
|
|
|
6443
6682
|
child.once("exit", accept);
|
|
6444
6683
|
});
|
|
6445
6684
|
if (bytes > maxBytes + entries.length * 100) throw new Error(`Git tree exceeds ${maxBytes} bytes`);
|
|
6446
|
-
if (code !== 0) throw new Error(`git object read failed: ${Buffer.concat(
|
|
6685
|
+
if (code !== 0) throw new Error(`git object read failed: ${Buffer.concat(stderr2).toString("utf8").slice(0, 1e3)}`);
|
|
6447
6686
|
const output = Buffer.concat(stdout);
|
|
6448
6687
|
const blobs = [];
|
|
6449
6688
|
let offset = 0;
|
|
@@ -6540,7 +6779,7 @@ async function gitSourceFiles(sourceDir, maxFiles, maxBytes) {
|
|
|
6540
6779
|
shell: false
|
|
6541
6780
|
});
|
|
6542
6781
|
const stdout = [];
|
|
6543
|
-
const
|
|
6782
|
+
const stderr2 = [];
|
|
6544
6783
|
let outputBytes = 0;
|
|
6545
6784
|
child.stdout.on("data", (chunk) => {
|
|
6546
6785
|
outputBytes += chunk.byteLength;
|
|
@@ -6548,14 +6787,14 @@ async function gitSourceFiles(sourceDir, maxFiles, maxBytes) {
|
|
|
6548
6787
|
else stdout.push(chunk);
|
|
6549
6788
|
});
|
|
6550
6789
|
child.stderr.on("data", (chunk) => {
|
|
6551
|
-
if (
|
|
6790
|
+
if (stderr2.reduce((sum, value2) => sum + value2.byteLength, 0) < 16384) stderr2.push(chunk);
|
|
6552
6791
|
});
|
|
6553
6792
|
const code = await new Promise((accept, reject) => {
|
|
6554
6793
|
child.once("error", reject);
|
|
6555
6794
|
child.once("exit", accept);
|
|
6556
6795
|
});
|
|
6557
6796
|
if (outputBytes > 8 * 1024 * 1024) throw new Error("git file inventory exceeds 8 MiB");
|
|
6558
|
-
if (code !== 0) throw new Error(`git file inventory failed: ${Buffer.concat(
|
|
6797
|
+
if (code !== 0) throw new Error(`git file inventory failed: ${Buffer.concat(stderr2).toString("utf8").slice(0, 1e3)}`);
|
|
6559
6798
|
const paths = Buffer.concat(stdout).toString("utf8").split("\0").filter(Boolean).sort();
|
|
6560
6799
|
if (paths.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
|
|
6561
6800
|
const root = (0, import_path4.resolve)(sourceDir);
|
|
@@ -6600,7 +6839,7 @@ async function captureGitDiff(root, maxBytes) {
|
|
|
6600
6839
|
"workspace"
|
|
6601
6840
|
], { cwd: root, stdio: ["ignore", "pipe", "pipe"], shell: false });
|
|
6602
6841
|
const stdout = [];
|
|
6603
|
-
const
|
|
6842
|
+
const stderr2 = [];
|
|
6604
6843
|
let bytes = 0;
|
|
6605
6844
|
child.stdout.on("data", (chunk) => {
|
|
6606
6845
|
bytes += chunk.byteLength;
|
|
@@ -6608,7 +6847,7 @@ async function captureGitDiff(root, maxBytes) {
|
|
|
6608
6847
|
else stdout.push(chunk);
|
|
6609
6848
|
});
|
|
6610
6849
|
child.stderr.on("data", (chunk) => {
|
|
6611
|
-
if (
|
|
6850
|
+
if (stderr2.reduce((sum, value2) => sum + value2.byteLength, 0) < 16384) stderr2.push(chunk);
|
|
6612
6851
|
});
|
|
6613
6852
|
const code = await new Promise((accept, reject) => {
|
|
6614
6853
|
child.once("error", reject);
|
|
@@ -6616,7 +6855,7 @@ async function captureGitDiff(root, maxBytes) {
|
|
|
6616
6855
|
});
|
|
6617
6856
|
if (bytes > maxBytes) throw new Error(`patch exceeds ${maxBytes} bytes`);
|
|
6618
6857
|
if (code !== 0 && code !== 1) {
|
|
6619
|
-
throw new Error(`git diff failed: ${Buffer.concat(
|
|
6858
|
+
throw new Error(`git diff failed: ${Buffer.concat(stderr2).toString("utf8").slice(0, 1e3)}`);
|
|
6620
6859
|
}
|
|
6621
6860
|
return Buffer.concat(stdout).toString("utf8").replaceAll("a/baseline/", "a/").replaceAll("a/workspace/", "a/").replaceAll("b/baseline/", "b/").replaceAll("b/workspace/", "b/").replaceAll("--- a/baseline", "--- a").replaceAll("+++ b/workspace", "+++ b");
|
|
6622
6861
|
}
|
|
@@ -7504,11 +7743,11 @@ var init_dist2 = __esm({
|
|
|
7504
7743
|
});
|
|
7505
7744
|
|
|
7506
7745
|
// ../graph/dist/code/index.js
|
|
7507
|
-
function
|
|
7746
|
+
function dirname10(path) {
|
|
7508
7747
|
const at = path.lastIndexOf("/");
|
|
7509
7748
|
return at <= 0 ? "." : path.slice(0, at);
|
|
7510
7749
|
}
|
|
7511
|
-
function
|
|
7750
|
+
function join14(base, specifier) {
|
|
7512
7751
|
const parts = [];
|
|
7513
7752
|
const segments = `${base === "." ? "" : `${base}/`}${specifier}`.split("/");
|
|
7514
7753
|
for (const segment of segments) {
|
|
@@ -7520,7 +7759,7 @@ function join13(base, specifier) {
|
|
|
7520
7759
|
}
|
|
7521
7760
|
function resolveImport(fromPath, specifier, known) {
|
|
7522
7761
|
if (!specifier.startsWith(".")) return null;
|
|
7523
|
-
const base =
|
|
7762
|
+
const base = join14(dirname10(fromPath), specifier);
|
|
7524
7763
|
const candidates = [
|
|
7525
7764
|
base,
|
|
7526
7765
|
base.replace(/\.js$/, ".ts"),
|
|
@@ -8023,13 +8262,13 @@ function gitApply(cwd, patch2, check) {
|
|
|
8023
8262
|
stdio: ["pipe", "ignore", "pipe"],
|
|
8024
8263
|
env: { PATH: process.env.PATH ?? "", GIT_CONFIG_NOSYSTEM: "1", GIT_CONFIG_GLOBAL: "/dev/null" }
|
|
8025
8264
|
});
|
|
8026
|
-
let
|
|
8265
|
+
let stderr2 = "";
|
|
8027
8266
|
child.stderr.setEncoding("utf8");
|
|
8028
8267
|
child.stderr.on("data", (text3) => {
|
|
8029
|
-
if (
|
|
8268
|
+
if (stderr2.length < 4e3) stderr2 += text3.slice(0, 4e3);
|
|
8030
8269
|
});
|
|
8031
8270
|
child.once("error", reject);
|
|
8032
|
-
child.once("exit", (code) => code === 0 ? accept() : reject(new TypeError(describePatchFailure(patch2,
|
|
8271
|
+
child.once("exit", (code) => code === 0 ? accept() : reject(new TypeError(describePatchFailure(patch2, stderr2.trim().slice(0, 500)))));
|
|
8033
8272
|
child.stdin.end(patch2);
|
|
8034
8273
|
});
|
|
8035
8274
|
}
|
|
@@ -8140,7 +8379,7 @@ function execute(engine, args, name, recipe2, signal) {
|
|
|
8140
8379
|
const started = Date.now();
|
|
8141
8380
|
const child = (0, import_child_process5.spawn)(engine, args, { shell: false, stdio: ["ignore", "pipe", "pipe"] });
|
|
8142
8381
|
const stdout = [];
|
|
8143
|
-
const
|
|
8382
|
+
const stderr2 = [];
|
|
8144
8383
|
let bytes = 0;
|
|
8145
8384
|
let outputLimitExceeded = false;
|
|
8146
8385
|
let timedOut = false;
|
|
@@ -8161,7 +8400,7 @@ function execute(engine, args, name, recipe2, signal) {
|
|
|
8161
8400
|
else target.push(chunk);
|
|
8162
8401
|
};
|
|
8163
8402
|
child.stdout.on("data", collect(stdout));
|
|
8164
|
-
child.stderr.on("data", collect(
|
|
8403
|
+
child.stderr.on("data", collect(stderr2));
|
|
8165
8404
|
const abort = () => stop("abort");
|
|
8166
8405
|
signal?.addEventListener("abort", abort, { once: true });
|
|
8167
8406
|
if (signal?.aborted) abort();
|
|
@@ -8177,7 +8416,7 @@ function execute(engine, args, name, recipe2, signal) {
|
|
|
8177
8416
|
accept({
|
|
8178
8417
|
exitCode: code ?? 1,
|
|
8179
8418
|
stdout: Buffer.concat(stdout).toString("utf8"),
|
|
8180
|
-
stderr: Buffer.concat(
|
|
8419
|
+
stderr: Buffer.concat(stderr2).toString("utf8"),
|
|
8181
8420
|
durationMs: Date.now() - started,
|
|
8182
8421
|
outputLimitExceeded,
|
|
8183
8422
|
timedOut
|
|
@@ -8323,11 +8562,11 @@ function checkedResult(result, maximumOutputBytes) {
|
|
|
8323
8562
|
}
|
|
8324
8563
|
function boundedLogs(result, maximum) {
|
|
8325
8564
|
const stdout = Buffer.from(result.stdout);
|
|
8326
|
-
const
|
|
8565
|
+
const stderr2 = Buffer.from(result.stderr);
|
|
8327
8566
|
const first = stdout.subarray(0, maximum);
|
|
8328
8567
|
return {
|
|
8329
8568
|
stdout: first.toString("utf8"),
|
|
8330
|
-
stderr:
|
|
8569
|
+
stderr: stderr2.subarray(0, Math.max(0, maximum - first.byteLength)).toString("utf8")
|
|
8331
8570
|
};
|
|
8332
8571
|
}
|
|
8333
8572
|
function digestPolicy(policy) {
|
|
@@ -10461,7 +10700,7 @@ var init_code_runtime_config = __esm({
|
|
|
10461
10700
|
// src/code-connect.ts
|
|
10462
10701
|
async function codeConnect(options) {
|
|
10463
10702
|
const cwd = options.cwd ?? process.cwd();
|
|
10464
|
-
const configPath = (0,
|
|
10703
|
+
const configPath = (0, import_node_path17.resolve)(cwd, options.configPath);
|
|
10465
10704
|
const cfg = (0, import_node_fs18.existsSync)(configPath) ? await loadProjectConfig(configPath) : null;
|
|
10466
10705
|
const requestedAppId = options.appId?.trim();
|
|
10467
10706
|
if (requestedAppId && !/^[a-z0-9][a-z0-9-]{1,62}$/.test(requestedAppId)) {
|
|
@@ -10491,7 +10730,7 @@ async function codeConnect(options) {
|
|
|
10491
10730
|
const doFetch = options.fetch ?? fetch;
|
|
10492
10731
|
const engine = await (options.selectEngine ?? selectContainerEngine)(options.engine ?? "auto");
|
|
10493
10732
|
const hostPlatform = process.platform === "darwin" ? "macos" : "linux";
|
|
10494
|
-
const hostName = (options.name ?? (0,
|
|
10733
|
+
const hostName = (options.name ?? (0, import_node_os5.hostname)()).trim();
|
|
10495
10734
|
if (!hostName || hostName.length > 120) throw new Error("--name must contain 1 to 120 characters");
|
|
10496
10735
|
const repository = await inferGitHubRepository(cwd, options.readGitOrigin);
|
|
10497
10736
|
const localSource = await (options.prepareLocalSource ?? prepareCodeLocalSource)(
|
|
@@ -10529,8 +10768,8 @@ async function codeConnect(options) {
|
|
|
10529
10768
|
platform: hostPlatform,
|
|
10530
10769
|
arch: process.arch,
|
|
10531
10770
|
engines: [engine],
|
|
10532
|
-
cpuCount: (0,
|
|
10533
|
-
memoryBytes: (0,
|
|
10771
|
+
cpuCount: (0, import_node_os5.cpus)().length,
|
|
10772
|
+
memoryBytes: (0, import_node_os5.totalmem)(),
|
|
10534
10773
|
source: descriptor2,
|
|
10535
10774
|
images: {
|
|
10536
10775
|
ready: true,
|
|
@@ -10627,14 +10866,14 @@ function apiFailure(action2, status, value2) {
|
|
|
10627
10866
|
function record6(value2) {
|
|
10628
10867
|
return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
10629
10868
|
}
|
|
10630
|
-
var import_node_fs18,
|
|
10869
|
+
var import_node_fs18, import_node_os5, import_node_path17;
|
|
10631
10870
|
var init_code_connect = __esm({
|
|
10632
10871
|
"src/code-connect.ts"() {
|
|
10633
10872
|
"use strict";
|
|
10634
10873
|
init_cjs_shims();
|
|
10635
10874
|
import_node_fs18 = require("fs");
|
|
10636
|
-
|
|
10637
|
-
|
|
10875
|
+
import_node_os5 = require("os");
|
|
10876
|
+
import_node_path17 = require("path");
|
|
10638
10877
|
init_node();
|
|
10639
10878
|
init_admin_ai_auth();
|
|
10640
10879
|
init_config();
|
|
@@ -10952,7 +11191,7 @@ function developerTokenStatus(context, parsed, now = Date.now()) {
|
|
|
10952
11191
|
const cacheStatus = !cached?.token ? "missing" : cached.platform !== context.platform.value ? "other-platform" : (cached.expiresAt ?? 0) <= now + 6e4 ? "expired" : "valid";
|
|
10953
11192
|
const source = clean3(
|
|
10954
11193
|
stringOpt(parsed.options.token)
|
|
10955
|
-
) ? "flag" : clean3(
|
|
11194
|
+
) ? "flag" : clean3(import_node_process16.default.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
|
|
10956
11195
|
return {
|
|
10957
11196
|
source,
|
|
10958
11197
|
cacheFile: context.cfg.local.tokenFile,
|
|
@@ -10963,12 +11202,12 @@ function clean3(value2) {
|
|
|
10963
11202
|
const normalized = value2?.trim();
|
|
10964
11203
|
return normalized || void 0;
|
|
10965
11204
|
}
|
|
10966
|
-
var
|
|
11205
|
+
var import_node_process16;
|
|
10967
11206
|
var init_operator_credentials = __esm({
|
|
10968
11207
|
"src/operator-credentials.ts"() {
|
|
10969
11208
|
"use strict";
|
|
10970
11209
|
init_cjs_shims();
|
|
10971
|
-
|
|
11210
|
+
import_node_process16 = __toESM(require("process"), 1);
|
|
10972
11211
|
init_argv();
|
|
10973
11212
|
init_local();
|
|
10974
11213
|
}
|
|
@@ -11176,11 +11415,35 @@ var init_credential_command = __esm({
|
|
|
11176
11415
|
});
|
|
11177
11416
|
|
|
11178
11417
|
// src/help-usage.ts
|
|
11179
|
-
var USAGE_SECTION;
|
|
11418
|
+
var AUTH_SECTION, USAGE_SECTION;
|
|
11180
11419
|
var init_help_usage = __esm({
|
|
11181
11420
|
"src/help-usage.ts"() {
|
|
11182
11421
|
"use strict";
|
|
11183
11422
|
init_cjs_shims();
|
|
11423
|
+
AUTH_SECTION = `
|
|
11424
|
+
Enrol this machine once, then stop asking:
|
|
11425
|
+
npx odla-ai device enroll --no-open --wait 600
|
|
11426
|
+
One browser approval, and no flags to remember: this covers every app you
|
|
11427
|
+
own \u2014 including apps you create later \u2014 with every capability that
|
|
11428
|
+
approval can carry. Afterwards every worktree on this machine mints its
|
|
11429
|
+
own short-lived credentials with nobody's attention. The window rolls
|
|
11430
|
+
forward each time you use it, so continuous work never interrupts anyone;
|
|
11431
|
+
only a real gap does. Give the human the printed /studio?code= URL, keep
|
|
11432
|
+
the process alive, and wait on it.
|
|
11433
|
+
Narrow it deliberately with --app <id> or --capability <c>; the CLI then
|
|
11434
|
+
says what that gave up.
|
|
11435
|
+
|
|
11436
|
+
npx odla-ai device enroll --platform-wide --device-ttl 6w --no-open --wait 600
|
|
11437
|
+
The same thing across all of odla, for weeks. Needs a platform
|
|
11438
|
+
administrator's approval \u2014 an app owner's cannot carry platform scopes.
|
|
11439
|
+
|
|
11440
|
+
npx odla-ai whoami what this machine holds and when it lapses
|
|
11441
|
+
npx odla-ai device list every machine you have enrolled
|
|
11442
|
+
|
|
11443
|
+
Enrollment is the only human decision here. Revoking a machine, purging an app,
|
|
11444
|
+
transferring ownership, and rotating credentials still need a signed-in human in
|
|
11445
|
+
Studio, and no machine credential can do them however wide its approval was.
|
|
11446
|
+
`;
|
|
11184
11447
|
USAGE_SECTION = `
|
|
11185
11448
|
Start here:
|
|
11186
11449
|
odla-ai runbook ask "<question>" The current procedure, from odla's own
|
|
@@ -11219,7 +11482,7 @@ Usage:
|
|
|
11219
11482
|
odla-ai brand design unpack <bundle.html|-> [--out <dir>] [--json]
|
|
11220
11483
|
odla-ai pm project list [--app <product-id>] [--status <s>] [--json]
|
|
11221
11484
|
odla-ai pm project add --app <product-id> --name <name> [--description <text>] [--json]
|
|
11222
|
-
odla-ai pm project use <project-id> [--json] [saved
|
|
11485
|
+
odla-ai pm project use <project-id> [--json] [saved for this app, on this machine]
|
|
11223
11486
|
odla-ai pm goal list [--app <id>] [--project <id>] [--status <s>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
11224
11487
|
odla-ai pm task list [--app <id>] [--column <backlog|ready|doing|review|done>] [--goal <id>] [--assignee <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
11225
11488
|
odla-ai pm decision list [--app <id>] [--status <s>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
@@ -11311,7 +11574,8 @@ Usage:
|
|
|
11311
11574
|
odla-ai security run [target] --ack-redacted-source [--env dev] [--profile odla] [--fail-on high]
|
|
11312
11575
|
odla-ai security run [target] --self --ack-redacted-source
|
|
11313
11576
|
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]
|
|
11314
|
-
odla-ai device enroll [--app <id>[,<id>...]] [--
|
|
11577
|
+
odla-ai device enroll [--app <id>[,<id>...]|--all-apps] [--capability all|<c>[,<c>...]] [--platform-wide]
|
|
11578
|
+
[--name <label>] [--device-ttl <30d|6w|2y|forever>] [--email <odla-account>] [--no-open] [--wait <seconds>] [--json]
|
|
11315
11579
|
odla-ai device list [--email <odla-account>] [--json]
|
|
11316
11580
|
odla-ai device revoke <device-id> [--email <odla-account>] [--json]
|
|
11317
11581
|
odla-ai credentials list [--config odla.config.mjs] [--env dev] [--all] [--json]
|
|
@@ -11327,8 +11591,11 @@ Usage:
|
|
|
11327
11591
|
|
|
11328
11592
|
// src/help.ts
|
|
11329
11593
|
function printHelp(output = console) {
|
|
11330
|
-
output.log(
|
|
11331
|
-
|
|
11594
|
+
output.log(helpText());
|
|
11595
|
+
}
|
|
11596
|
+
function helpText() {
|
|
11597
|
+
return `odla-ai
|
|
11598
|
+
${AUTH_SECTION}${USAGE_SECTION}
|
|
11332
11599
|
Commands:
|
|
11333
11600
|
auth Start a fresh, exact-project agent authorization for human review.
|
|
11334
11601
|
The email is the signed-in odla account, never git or GitHub
|
|
@@ -11401,8 +11668,9 @@ Commands:
|
|
|
11401
11668
|
security Connect GitHub sources and run commit-pinned hosted reviews, or scan a local snapshot.
|
|
11402
11669
|
pm Project management (via @odla-ai/pm): Products contain Projects;
|
|
11403
11670
|
projects contain goals, kanban tasks, decisions, and bugs. Use
|
|
11404
|
-
"pm project list|add|use"
|
|
11405
|
-
|
|
11671
|
+
"pm project list|add|use" selects a project for this app on this
|
|
11672
|
+
machine \u2014 every worktree shares it \u2014 or pass --app/--project
|
|
11673
|
+
explicitly. Same device-grant auth as "app".
|
|
11406
11674
|
Status changes and comments post to each item's @odla-ai/chat
|
|
11407
11675
|
discussion thread.
|
|
11408
11676
|
NOTE: "--column ready" is OWNER-ONLY. Creating a task in Ready
|
|
@@ -11431,9 +11699,22 @@ Commands:
|
|
|
11431
11699
|
platform Read canonical fleet health, releases, provider load/freshness,
|
|
11432
11700
|
explicit unknowns, and next actions through a read-only grant.
|
|
11433
11701
|
device Enrol THIS machine once, then stop asking. A human approves the
|
|
11434
|
-
enrollment in the browser; from then on
|
|
11435
|
-
own short-lived credentials
|
|
11436
|
-
attention, until the device
|
|
11702
|
+
enrollment in the browser; from then on EVERY worktree on this
|
|
11703
|
+
machine mints its own short-lived credentials with nobody's
|
|
11704
|
+
attention, until the device is revoked or goes unused.
|
|
11705
|
+
With no flags it covers every app you own, now and later, with
|
|
11706
|
+
every capability that approval can carry \u2014 so creating an app
|
|
11707
|
+
costs no new approval, and a capability nobody thought to name is
|
|
11708
|
+
not a 403 next week. "--app" or "--capability" narrow it
|
|
11709
|
+
deliberately, and the CLI says what that gave up.
|
|
11710
|
+
"--platform-wide" is the administrator's version, across all of
|
|
11711
|
+
odla.
|
|
11712
|
+
The expiry is a GAP, not a clock: each use rolls it forward, so
|
|
11713
|
+
only going quiet brings a human back into the loop \u2014 which is
|
|
11714
|
+
where anything that changed can be explained.
|
|
11715
|
+
"device list" shows what each machine holds and when it lapses;
|
|
11716
|
+
revoking one takes down every credential it ever minted, and is
|
|
11717
|
+
deliberately a signed-in human's decision in Studio.
|
|
11437
11718
|
provision Register services, compose integrations, persist credentials, optionally push secrets.
|
|
11438
11719
|
"provision --live --yes" initializes only the live instance of
|
|
11439
11720
|
an existing sandbox app and enables every configured service;
|
|
@@ -11529,7 +11810,7 @@ Safety:
|
|
|
11529
11810
|
Run security plan first to inspect the admin-selected providers, models,
|
|
11530
11811
|
per-route bounds, credential readiness, retention, no-execution boundary,
|
|
11531
11812
|
and digest that binds consent to that exact plan.
|
|
11532
|
-
|
|
11813
|
+
`;
|
|
11533
11814
|
}
|
|
11534
11815
|
var init_help = __esm({
|
|
11535
11816
|
"src/help.ts"() {
|
|
@@ -11539,6 +11820,53 @@ var init_help = __esm({
|
|
|
11539
11820
|
}
|
|
11540
11821
|
});
|
|
11541
11822
|
|
|
11823
|
+
// src/help-command.ts
|
|
11824
|
+
function printCommandHelp(command, output = console) {
|
|
11825
|
+
const lines = helpText().split("\n");
|
|
11826
|
+
const usage = allBlocks(lines, new RegExp(`^ odla-ai ${escapeRe(command)}(\\s|$)`));
|
|
11827
|
+
const prose = block(lines, (line2) => new RegExp(`^ ${escapeRe(command)}\\s\\s+\\S`).test(line2));
|
|
11828
|
+
if (usage.length === 0 && prose.length === 0) {
|
|
11829
|
+
output.log(`odla-ai: no command "${command}". Run "odla-ai help" for all of them.`);
|
|
11830
|
+
return;
|
|
11831
|
+
}
|
|
11832
|
+
output.log([
|
|
11833
|
+
...prose.length ? [prose.join("\n"), ""] : [],
|
|
11834
|
+
...usage.length ? ["Usage:", ...usage, ""] : [],
|
|
11835
|
+
AUTH_SECTION.trimEnd()
|
|
11836
|
+
].join("\n"));
|
|
11837
|
+
}
|
|
11838
|
+
function allBlocks(lines, pattern) {
|
|
11839
|
+
const out = [];
|
|
11840
|
+
for (let i = 0; i < lines.length; i++) {
|
|
11841
|
+
if (!pattern.test(lines[i])) continue;
|
|
11842
|
+
out.push(...block(lines.slice(i), (line2) => line2 === lines[i]));
|
|
11843
|
+
}
|
|
11844
|
+
return out;
|
|
11845
|
+
}
|
|
11846
|
+
function block(lines, starts) {
|
|
11847
|
+
const first = lines.findIndex(starts);
|
|
11848
|
+
if (first === -1) return [];
|
|
11849
|
+
const indent = lines[first].length - lines[first].trimStart().length;
|
|
11850
|
+
const out = [lines[first]];
|
|
11851
|
+
for (const line2 of lines.slice(first + 1)) {
|
|
11852
|
+
if (!line2.trim()) break;
|
|
11853
|
+
if (line2.length - line2.trimStart().length <= indent) break;
|
|
11854
|
+
out.push(line2);
|
|
11855
|
+
}
|
|
11856
|
+
return out;
|
|
11857
|
+
}
|
|
11858
|
+
function escapeRe(value2) {
|
|
11859
|
+
return value2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
11860
|
+
}
|
|
11861
|
+
var init_help_command = __esm({
|
|
11862
|
+
"src/help-command.ts"() {
|
|
11863
|
+
"use strict";
|
|
11864
|
+
init_cjs_shims();
|
|
11865
|
+
init_help_usage();
|
|
11866
|
+
init_help();
|
|
11867
|
+
}
|
|
11868
|
+
});
|
|
11869
|
+
|
|
11542
11870
|
// src/discuss-principals.ts
|
|
11543
11871
|
function mergeDiscussPrincipals(target, source) {
|
|
11544
11872
|
Object.assign(target.authors, source.authors ?? {});
|
|
@@ -12804,20 +13132,43 @@ var init_pm_watch = __esm({
|
|
|
12804
13132
|
|
|
12805
13133
|
// src/pm-project-context.ts
|
|
12806
13134
|
function readPmProjectContext(rootDir) {
|
|
12807
|
-
|
|
12808
|
-
|
|
13135
|
+
adoptLegacySelection(rootDir);
|
|
13136
|
+
const entries = Object.values(readSelections()).filter(isSelection);
|
|
13137
|
+
return entries.sort((a, b) => b.selectedAt.localeCompare(a.selectedAt))[0] ?? null;
|
|
12809
13138
|
}
|
|
12810
13139
|
function writePmProjectContext(rootDir, value2) {
|
|
12811
|
-
|
|
13140
|
+
adoptLegacySelection(rootDir);
|
|
13141
|
+
writePrivateJson(pmProjectContextFile(), {
|
|
13142
|
+
...readSelections(),
|
|
13143
|
+
[value2.appId]: { ...value2, selectedAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
13144
|
+
});
|
|
13145
|
+
}
|
|
13146
|
+
function adoptLegacySelection(rootDir) {
|
|
13147
|
+
const legacy = (0, import_node_path18.resolve)(rootDir, ".odla", "pm-project.local.json");
|
|
13148
|
+
if (!(0, import_node_fs19.existsSync)(legacy)) return;
|
|
13149
|
+
const previous = readJsonFile(legacy);
|
|
13150
|
+
(0, import_node_fs19.rmSync)(legacy, { force: true });
|
|
13151
|
+
if (!isSelection(previous)) return;
|
|
13152
|
+
const selections = readSelections();
|
|
13153
|
+
if (selections[previous.appId]) return;
|
|
13154
|
+
writePrivateJson(pmProjectContextFile(), { ...selections, [previous.appId]: previous });
|
|
13155
|
+
}
|
|
13156
|
+
function readSelections() {
|
|
13157
|
+
return readJsonFile(pmProjectContextFile()) ?? {};
|
|
13158
|
+
}
|
|
13159
|
+
function isSelection(value2) {
|
|
13160
|
+
return !!value2 && typeof value2.appId === "string" && typeof value2.projectId === "string" && typeof value2.selectedAt === "string";
|
|
12812
13161
|
}
|
|
12813
|
-
var
|
|
13162
|
+
var import_node_fs19, import_node_path18, pmProjectContextFile;
|
|
12814
13163
|
var init_pm_project_context = __esm({
|
|
12815
13164
|
"src/pm-project-context.ts"() {
|
|
12816
13165
|
"use strict";
|
|
12817
13166
|
init_cjs_shims();
|
|
12818
|
-
|
|
13167
|
+
import_node_fs19 = require("fs");
|
|
13168
|
+
import_node_path18 = require("path");
|
|
12819
13169
|
init_local();
|
|
12820
|
-
|
|
13170
|
+
init_odla_home();
|
|
13171
|
+
pmProjectContextFile = () => pmContextFile();
|
|
12821
13172
|
}
|
|
12822
13173
|
});
|
|
12823
13174
|
|
|
@@ -14391,7 +14742,7 @@ async function provision(options) {
|
|
|
14391
14742
|
await provisionIntegrationSeeds(doFetch, cfg.dbEndpoint, tenantId, dbKey, database.integrations, env, out);
|
|
14392
14743
|
}
|
|
14393
14744
|
if (cfg.services.includes("ai") && cfg.ai?.provider && cfg.ai.keyEnv) {
|
|
14394
|
-
const key =
|
|
14745
|
+
const key = import_node_process17.default.env[cfg.ai.keyEnv];
|
|
14395
14746
|
if (key) {
|
|
14396
14747
|
const secretName = cfg.ai.secretName ?? defaultSecretName(cfg.ai.provider);
|
|
14397
14748
|
await (0, import_ai5.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, secretName, key);
|
|
@@ -14430,14 +14781,14 @@ async function provision(options) {
|
|
|
14430
14781
|
}
|
|
14431
14782
|
}
|
|
14432
14783
|
}
|
|
14433
|
-
var import_apps13, import_ai5,
|
|
14784
|
+
var import_apps13, import_ai5, import_node_process17;
|
|
14434
14785
|
var init_provision = __esm({
|
|
14435
14786
|
"src/provision.ts"() {
|
|
14436
14787
|
"use strict";
|
|
14437
14788
|
init_cjs_shims();
|
|
14438
14789
|
import_apps13 = require("@odla-ai/apps");
|
|
14439
14790
|
import_ai5 = require("@odla-ai/ai");
|
|
14440
|
-
|
|
14791
|
+
import_node_process17 = __toESM(require("process"), 1);
|
|
14441
14792
|
init_config();
|
|
14442
14793
|
init_calendar();
|
|
14443
14794
|
init_calendar_errors();
|
|
@@ -14632,7 +14983,7 @@ var init_surface = __esm({
|
|
|
14632
14983
|
|
|
14633
14984
|
// src/record.ts
|
|
14634
14985
|
function recordInvocation(parsed) {
|
|
14635
|
-
const file =
|
|
14986
|
+
const file = import_node_process18.default.env.ODLA_CLI_RECORD;
|
|
14636
14987
|
if (!file) return;
|
|
14637
14988
|
try {
|
|
14638
14989
|
const entry = {
|
|
@@ -14640,18 +14991,18 @@ function recordInvocation(parsed) {
|
|
|
14640
14991
|
options: Object.entries(parsed.options).map(([name, value2]) => value2 === false ? `no-${name}` : name).sort()
|
|
14641
14992
|
};
|
|
14642
14993
|
if (!entry.path.length) return;
|
|
14643
|
-
(0,
|
|
14994
|
+
(0, import_node_fs20.appendFileSync)(file, `${JSON.stringify(entry)}
|
|
14644
14995
|
`);
|
|
14645
14996
|
} catch {
|
|
14646
14997
|
}
|
|
14647
14998
|
}
|
|
14648
|
-
var
|
|
14999
|
+
var import_node_fs20, import_node_process18;
|
|
14649
15000
|
var init_record = __esm({
|
|
14650
15001
|
"src/record.ts"() {
|
|
14651
15002
|
"use strict";
|
|
14652
15003
|
init_cjs_shims();
|
|
14653
|
-
|
|
14654
|
-
|
|
15004
|
+
import_node_fs20 = require("fs");
|
|
15005
|
+
import_node_process18 = __toESM(require("process"), 1);
|
|
14655
15006
|
init_surface();
|
|
14656
15007
|
}
|
|
14657
15008
|
});
|
|
@@ -14667,22 +15018,29 @@ function advisoryCollectingFetch(inner, sink) {
|
|
|
14667
15018
|
return response2;
|
|
14668
15019
|
});
|
|
14669
15020
|
}
|
|
15021
|
+
function supersedeAdvisory(code) {
|
|
15022
|
+
superseded.add(code);
|
|
15023
|
+
}
|
|
14670
15024
|
function renderAdvisories(out, advisories, env = process.env) {
|
|
15025
|
+
const retracted = new Set(superseded);
|
|
15026
|
+
superseded.clear();
|
|
14671
15027
|
if (env.ODLA_NO_ADVISORIES) return;
|
|
14672
15028
|
const seen = /* @__PURE__ */ new Set();
|
|
14673
15029
|
for (const advisory of advisories) {
|
|
15030
|
+
if (retracted.has(advisory.code)) continue;
|
|
14674
15031
|
const key = `${advisory.code}:${advisory.message}`;
|
|
14675
15032
|
if (seen.has(key)) continue;
|
|
14676
15033
|
seen.add(key);
|
|
14677
15034
|
out.error((0, import_apps14.formatAdvisory)(advisory));
|
|
14678
15035
|
}
|
|
14679
15036
|
}
|
|
14680
|
-
var import_apps14;
|
|
15037
|
+
var import_apps14, superseded;
|
|
14681
15038
|
var init_advisory_output = __esm({
|
|
14682
15039
|
"src/advisory-output.ts"() {
|
|
14683
15040
|
"use strict";
|
|
14684
15041
|
init_cjs_shims();
|
|
14685
15042
|
import_apps14 = require("@odla-ai/apps");
|
|
15043
|
+
superseded = /* @__PURE__ */ new Set();
|
|
14686
15044
|
}
|
|
14687
15045
|
});
|
|
14688
15046
|
|
|
@@ -14713,6 +15071,22 @@ var init_device_ttl = __esm({
|
|
|
14713
15071
|
|
|
14714
15072
|
// src/device-command.ts
|
|
14715
15073
|
async function deviceCommand(parsed, deps) {
|
|
15074
|
+
assertArgs(parsed, [
|
|
15075
|
+
"app",
|
|
15076
|
+
"all-apps",
|
|
15077
|
+
"platform-wide",
|
|
15078
|
+
"name",
|
|
15079
|
+
"capability",
|
|
15080
|
+
"device-ttl",
|
|
15081
|
+
"email",
|
|
15082
|
+
"open",
|
|
15083
|
+
"json",
|
|
15084
|
+
"config",
|
|
15085
|
+
"token",
|
|
15086
|
+
"context",
|
|
15087
|
+
"platform",
|
|
15088
|
+
"wait"
|
|
15089
|
+
], 3);
|
|
14716
15090
|
const action2 = parsed.positionals[1] ?? "";
|
|
14717
15091
|
const out = deps.stdout ?? console;
|
|
14718
15092
|
const doFetch = deps.fetch ?? fetch;
|
|
@@ -14725,10 +15099,19 @@ async function deviceCommand(parsed, deps) {
|
|
|
14725
15099
|
}
|
|
14726
15100
|
async function enroll(parsed, deps, cfg, doFetch, out, json) {
|
|
14727
15101
|
const name = stringOpt(parsed.options.name) ?? defaultDeviceName();
|
|
14728
|
-
const
|
|
14729
|
-
|
|
15102
|
+
const platformWide = parsed.options["platform-wide"] === true;
|
|
15103
|
+
const narrowed = stringOpt(parsed.options.app) !== void 0 || stringOpt(parsed.options.capability) !== void 0;
|
|
15104
|
+
const apps = platformWide || parsed.options["all-apps"] === true || !narrowed ? [import_db4.ALL_OWNED_APPS] : (stringOpt(parsed.options.app) ?? cfg.app.id).split(",").map((id2) => id2.trim()).filter(Boolean);
|
|
15105
|
+
if (apps.length === 0) throw new Error("device enroll needs --app <id>[,<id>\u2026], or --all-apps");
|
|
14730
15106
|
const deviceTtlMs = parseDeviceTtl(parsed.options["device-ttl"]);
|
|
14731
|
-
const extended = deviceTtlMs !== void 0 && deviceTtlMs > OWNER_DEVICE_TTL_MS;
|
|
15107
|
+
const extended = platformWide || deviceTtlMs !== void 0 && deviceTtlMs > OWNER_DEVICE_TTL_MS;
|
|
15108
|
+
const { capabilities, scopes } = requestedEnvelope(parsed, platformWide, narrowed);
|
|
15109
|
+
const narrowNotice = narrowEnrollmentNotice({
|
|
15110
|
+
appIds: apps,
|
|
15111
|
+
capabilities: capabilities ?? [],
|
|
15112
|
+
scopes: scopes ?? []
|
|
15113
|
+
});
|
|
15114
|
+
if (narrowNotice) out.error(narrowNotice);
|
|
14732
15115
|
const token = await scopedToken2(
|
|
14733
15116
|
parsed,
|
|
14734
15117
|
deps,
|
|
@@ -14743,9 +15126,10 @@ async function enroll(parsed, deps, cfg, doFetch, out, json) {
|
|
|
14743
15126
|
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
14744
15127
|
body: JSON.stringify({
|
|
14745
15128
|
name,
|
|
14746
|
-
platform:
|
|
15129
|
+
platform: import_node_process19.default.platform,
|
|
14747
15130
|
appIds: apps,
|
|
14748
|
-
...
|
|
15131
|
+
...capabilities ? { capabilities } : {},
|
|
15132
|
+
...scopes ? { scopes } : {},
|
|
14749
15133
|
...deviceTtlMs === void 0 ? {} : { deviceTtlMs }
|
|
14750
15134
|
})
|
|
14751
15135
|
});
|
|
@@ -14754,19 +15138,54 @@ async function enroll(parsed, deps, cfg, doFetch, out, json) {
|
|
|
14754
15138
|
throw new Error(`device enroll failed: ${body.error?.message ?? `registry returned ${response2.status}`} (${response2.status})`);
|
|
14755
15139
|
}
|
|
14756
15140
|
const path = deviceCredentialPath();
|
|
14757
|
-
(0,
|
|
14758
|
-
(0,
|
|
15141
|
+
(0, import_node_fs21.mkdirSync)((0, import_node_path19.dirname)(path), { recursive: true });
|
|
15142
|
+
(0, import_node_fs21.writeFileSync)(path, JSON.stringify({
|
|
14759
15143
|
token: body.token,
|
|
14760
15144
|
platform: cfg.platformUrl.replace(/\/$/, ""),
|
|
14761
15145
|
deviceId: body.device.deviceId,
|
|
14762
15146
|
name
|
|
14763
15147
|
}, null, 2));
|
|
14764
|
-
(0,
|
|
14765
|
-
|
|
14766
|
-
|
|
15148
|
+
(0, import_node_fs21.chmodSync)(path, 384);
|
|
15149
|
+
rememberMachineIdentity(cfg.platformUrl.replace(/\/$/, ""), stringOpt(parsed.options.email));
|
|
15150
|
+
supersedeAdvisory("credential.expiring");
|
|
15151
|
+
const reach = body.device.appIds.includes(import_db4.ALL_OWNED_APPS) ? "every app you own, now and later" : body.device.appIds.join(", ");
|
|
15152
|
+
out.error(`device: enrolled "${name}" for ${reach}; credential written to ${path}`);
|
|
15153
|
+
if (narrowNotice) out.error(narrowNotice);
|
|
15154
|
+
out.error(
|
|
15155
|
+
"device: every worktree on this machine mints its own credentials from now on \u2014 no further approvals,"
|
|
15156
|
+
);
|
|
15157
|
+
out.error(
|
|
15158
|
+
`device: and the clock resets each time you use it. Going quiet for ${describeWindow(body.device.expiresAt)} is what ends it.`
|
|
15159
|
+
);
|
|
14767
15160
|
if (json) {
|
|
14768
|
-
out.log(JSON.stringify({
|
|
15161
|
+
out.log(JSON.stringify({
|
|
15162
|
+
deviceId: body.device.deviceId,
|
|
15163
|
+
name,
|
|
15164
|
+
appIds: body.device.appIds,
|
|
15165
|
+
capabilities: body.device.capabilities ?? [],
|
|
15166
|
+
scopes: body.device.scopes ?? [],
|
|
15167
|
+
expiresAt: body.device.expiresAt,
|
|
15168
|
+
hardExpiresAt: body.device.hardExpiresAt ?? null
|
|
15169
|
+
}, null, 2));
|
|
15170
|
+
}
|
|
15171
|
+
}
|
|
15172
|
+
function requestedEnvelope(parsed, platformWide, narrowed) {
|
|
15173
|
+
const raw = stringOpt(parsed.options.capability);
|
|
15174
|
+
const everything = platformWide || !narrowed || raw?.trim().toLowerCase() === "all";
|
|
15175
|
+
if (everything) {
|
|
15176
|
+
return {
|
|
15177
|
+
capabilities: [...import_db4.OPTIONAL_AGENT_PROJECT_CAPABILITIES],
|
|
15178
|
+
scopes: platformWide ? [...import_db4.ADMIN_DEVICE_SCOPES] : [...import_db4.OWNER_DEVICE_SCOPES]
|
|
15179
|
+
};
|
|
14769
15180
|
}
|
|
15181
|
+
const named = raw?.split(",").map((c) => c.trim()).filter(Boolean);
|
|
15182
|
+
return named?.length ? { capabilities: named } : {};
|
|
15183
|
+
}
|
|
15184
|
+
function describeWindow(expiresAt, now = Date.now()) {
|
|
15185
|
+
const days = Math.max(1, Math.round((expiresAt - now) / (24 * 60 * 60 * 1e3)));
|
|
15186
|
+
if (days >= 365) return `${Math.round(days / 365)} year${days >= 730 ? "s" : ""}`;
|
|
15187
|
+
if (days % 7 === 0) return `${days / 7} week${days > 7 ? "s" : ""}`;
|
|
15188
|
+
return `${days} day${days === 1 ? "" : "s"}`;
|
|
14770
15189
|
}
|
|
14771
15190
|
async function list2(parsed, deps, cfg, doFetch, out, json) {
|
|
14772
15191
|
const token = await scopedToken2(parsed, deps, cfg, doFetch, out, "odla CLI (device list)");
|
|
@@ -14781,12 +15200,17 @@ async function list2(parsed, deps, cfg, doFetch, out, json) {
|
|
|
14781
15200
|
if (body.devices.length === 0) return out.log("no enrolled devices");
|
|
14782
15201
|
for (const device of body.devices) {
|
|
14783
15202
|
const state2 = device.revokedAt ? "revoked" : device.expiresAt <= Date.now() ? "expired" : "active";
|
|
14784
|
-
|
|
15203
|
+
const reach = device.appIds.includes("*") ? "every app you own" : device.appIds.join(", ");
|
|
15204
|
+
const gap = state2 === "active" ? ` idle ${describeWindow(device.expiresAt)} left` : "";
|
|
15205
|
+
out.log(`${device.deviceId} ${state2.padEnd(7)} ${device.name} [${reach}]${gap}`);
|
|
14785
15206
|
}
|
|
14786
15207
|
}
|
|
14787
15208
|
async function revoke(parsed, deps, cfg, doFetch, out, json) {
|
|
14788
15209
|
const deviceId = parsed.positionals[2];
|
|
14789
15210
|
if (!deviceId) throw new Error("device revoke needs the device id from `odla-ai device list`");
|
|
15211
|
+
out.error(
|
|
15212
|
+
`device: revoking is a signed-in human's decision; if this is refused, open ${cfg.platformUrl}/studio and revoke it there.`
|
|
15213
|
+
);
|
|
14790
15214
|
const token = await scopedToken2(parsed, deps, cfg, doFetch, out, "odla CLI (device revoke)");
|
|
14791
15215
|
const response2 = await doFetch(`${cfg.platformUrl}/registry/devices/${encodeURIComponent(deviceId)}/revoke`, {
|
|
14792
15216
|
method: "POST",
|
|
@@ -14805,7 +15229,7 @@ async function scopedToken2(parsed, deps, cfg, doFetch, out, label, scope = "app
|
|
|
14805
15229
|
// A device is granted the apps named in ONE approval, so --app is a list here.
|
|
14806
15230
|
allowAppList: true
|
|
14807
15231
|
});
|
|
14808
|
-
const
|
|
15232
|
+
const scopedTokenFile2 = credentials.scopedTokenFile;
|
|
14809
15233
|
return getScopedPlatformToken({
|
|
14810
15234
|
platform: cfg.platformUrl,
|
|
14811
15235
|
scope,
|
|
@@ -14816,22 +15240,26 @@ async function scopedToken2(parsed, deps, cfg, doFetch, out, label, scope = "app
|
|
|
14816
15240
|
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
14817
15241
|
openApprovalUrl: deps.openUrl,
|
|
14818
15242
|
rootDir: cfg.rootDir,
|
|
14819
|
-
tokenFile:
|
|
15243
|
+
tokenFile: scopedTokenFile2,
|
|
14820
15244
|
...stringOpt(parsed.options.token) ? { token: stringOpt(parsed.options.token) } : {}
|
|
14821
15245
|
});
|
|
14822
15246
|
}
|
|
14823
15247
|
function defaultDeviceName() {
|
|
14824
|
-
return `${
|
|
15248
|
+
return `${import_node_process19.default.env.HOSTNAME ?? import_node_process19.default.env.HOST ?? "machine"}-${import_node_process19.default.platform}`;
|
|
14825
15249
|
}
|
|
14826
|
-
var
|
|
15250
|
+
var import_db4, import_node_fs21, import_node_path19, import_node_process19;
|
|
14827
15251
|
var init_device_command = __esm({
|
|
14828
15252
|
"src/device-command.ts"() {
|
|
14829
15253
|
"use strict";
|
|
14830
15254
|
init_cjs_shims();
|
|
15255
|
+
import_db4 = require("@odla-ai/db");
|
|
14831
15256
|
init_device_ttl();
|
|
14832
|
-
|
|
14833
|
-
|
|
14834
|
-
|
|
15257
|
+
init_machine_identity();
|
|
15258
|
+
init_auth_guidance();
|
|
15259
|
+
init_advisory_output();
|
|
15260
|
+
import_node_fs21 = require("fs");
|
|
15261
|
+
import_node_path19 = require("path");
|
|
15262
|
+
import_node_process19 = __toESM(require("process"), 1);
|
|
14835
15263
|
init_argv();
|
|
14836
15264
|
init_admin_ai_auth();
|
|
14837
15265
|
init_device_session();
|
|
@@ -14886,7 +15314,7 @@ async function bySlug(ctx, slug) {
|
|
|
14886
15314
|
function readBody(file, inline) {
|
|
14887
15315
|
if (inline !== void 0) return inline;
|
|
14888
15316
|
if (file === void 0) throw new Error("supply the new text with --file <path>, --file - (stdin), or --body");
|
|
14889
|
-
return (0,
|
|
15317
|
+
return (0, import_node_fs22.readFileSync)(file === "-" ? 0 : file, "utf8");
|
|
14890
15318
|
}
|
|
14891
15319
|
async function runbookList(ctx, all, query) {
|
|
14892
15320
|
const params = new URLSearchParams();
|
|
@@ -14975,12 +15403,12 @@ async function runbookRemove(ctx, slug) {
|
|
|
14975
15403
|
await call2(ctx, "DELETE", `/runbook/${encodeURIComponent(runbook.id)}`);
|
|
14976
15404
|
ctx.out.log(`removed ${slug}`);
|
|
14977
15405
|
}
|
|
14978
|
-
var
|
|
15406
|
+
var import_node_fs22, PLATFORM_SCOPE, stamp;
|
|
14979
15407
|
var init_runbook_actions = __esm({
|
|
14980
15408
|
"src/runbook-actions.ts"() {
|
|
14981
15409
|
"use strict";
|
|
14982
15410
|
init_cjs_shims();
|
|
14983
|
-
|
|
15411
|
+
import_node_fs22 = require("fs");
|
|
14984
15412
|
init_version();
|
|
14985
15413
|
init_runbook_requires();
|
|
14986
15414
|
PLATFORM_SCOPE = "$platform";
|
|
@@ -15013,12 +15441,12 @@ function parseRunbook(text3, slug) {
|
|
|
15013
15441
|
};
|
|
15014
15442
|
}
|
|
15015
15443
|
function readRunbookDir(dir) {
|
|
15016
|
-
if (!(0,
|
|
15017
|
-
const files = (0,
|
|
15444
|
+
if (!(0, import_node_fs23.statSync)(dir, { throwIfNoEntry: false })?.isDirectory()) throw new Error(`not a directory: ${dir}`);
|
|
15445
|
+
const files = (0, import_node_fs23.readdirSync)(dir).filter((f) => f.endsWith(".md")).sort();
|
|
15018
15446
|
if (!files.length) throw new Error(`no .md files in ${dir}`);
|
|
15019
15447
|
return files.map((file) => {
|
|
15020
|
-
const slug = (0,
|
|
15021
|
-
const parsed = parseRunbook((0,
|
|
15448
|
+
const slug = (0, import_node_path20.basename)(file, ".md");
|
|
15449
|
+
const parsed = parseRunbook((0, import_node_fs23.readFileSync)((0, import_node_path20.join)(dir, file), "utf8"), slug);
|
|
15022
15450
|
return { file, slug, ...parsed, words: parsed.body.split(/\s+/).filter(Boolean).length };
|
|
15023
15451
|
});
|
|
15024
15452
|
}
|
|
@@ -15083,13 +15511,13 @@ async function upsert(ctx, r, visibility) {
|
|
|
15083
15511
|
);
|
|
15084
15512
|
return "updated";
|
|
15085
15513
|
}
|
|
15086
|
-
var
|
|
15514
|
+
var import_node_fs23, import_node_path20;
|
|
15087
15515
|
var init_runbook_import = __esm({
|
|
15088
15516
|
"src/runbook-import.ts"() {
|
|
15089
15517
|
"use strict";
|
|
15090
15518
|
init_cjs_shims();
|
|
15091
|
-
|
|
15092
|
-
|
|
15519
|
+
import_node_fs23 = require("fs");
|
|
15520
|
+
import_node_path20 = require("path");
|
|
15093
15521
|
init_runbook_actions();
|
|
15094
15522
|
}
|
|
15095
15523
|
});
|
|
@@ -15267,10 +15695,10 @@ ${body.split("\n").map((line2) => `+${line2}`).join("\n")}
|
|
|
15267
15695
|
}
|
|
15268
15696
|
function manifestLabeller(root) {
|
|
15269
15697
|
return (workspace) => {
|
|
15270
|
-
const manifest = (0,
|
|
15271
|
-
if (!(0,
|
|
15698
|
+
const manifest = (0, import_node_path21.join)(root, workspace, "package.json");
|
|
15699
|
+
if (!(0, import_node_fs24.existsSync)(manifest)) return void 0;
|
|
15272
15700
|
try {
|
|
15273
|
-
const name = JSON.parse((0,
|
|
15701
|
+
const name = JSON.parse((0, import_node_fs24.readFileSync)(manifest, "utf8")).name;
|
|
15274
15702
|
return typeof name === "string" ? name : void 0;
|
|
15275
15703
|
} catch {
|
|
15276
15704
|
return void 0;
|
|
@@ -15336,7 +15764,7 @@ function report4(ctx, impacts) {
|
|
|
15336
15764
|
async function runbookImpact(ctx, options, deps = {}) {
|
|
15337
15765
|
const cwd = deps.cwd ?? process.cwd();
|
|
15338
15766
|
const runGit = deps.runGit ?? gitRunner(cwd);
|
|
15339
|
-
const read3 = deps.readRepoFile ?? ((path) => (0,
|
|
15767
|
+
const read3 = deps.readRepoFile ?? ((path) => (0, import_node_fs24.readFileSync)((0, import_node_path21.join)(cwd, path), "utf8"));
|
|
15340
15768
|
const surfaces = changedSurfaces(collectDiff(runGit, options.base, read3), manifestLabeller(cwd));
|
|
15341
15769
|
if (!surfaces.length) {
|
|
15342
15770
|
return ctx.out.log(
|
|
@@ -15347,14 +15775,14 @@ async function runbookImpact(ctx, options, deps = {}) {
|
|
|
15347
15775
|
if (ctx.json) return ctx.out.log(JSON.stringify({ base: options.base, impacts }, null, 2));
|
|
15348
15776
|
report4(ctx, impacts);
|
|
15349
15777
|
}
|
|
15350
|
-
var import_node_child_process6,
|
|
15778
|
+
var import_node_child_process6, import_node_fs24, import_node_path21, SOURCE3, editHint;
|
|
15351
15779
|
var init_runbook_impact = __esm({
|
|
15352
15780
|
"src/runbook-impact.ts"() {
|
|
15353
15781
|
"use strict";
|
|
15354
15782
|
init_cjs_shims();
|
|
15355
15783
|
import_node_child_process6 = require("child_process");
|
|
15356
|
-
|
|
15357
|
-
|
|
15784
|
+
import_node_fs24 = require("fs");
|
|
15785
|
+
import_node_path21 = require("path");
|
|
15358
15786
|
init_runbook_impact_scan();
|
|
15359
15787
|
init_runbook_actions();
|
|
15360
15788
|
SOURCE3 = /\.(ts|tsx|js|jsx|mts|cts)$/;
|
|
@@ -15494,7 +15922,7 @@ var init_runbook_search_command = __esm({
|
|
|
15494
15922
|
});
|
|
15495
15923
|
|
|
15496
15924
|
// src/runbook-editor.ts
|
|
15497
|
-
function resolveEditor(env =
|
|
15925
|
+
function resolveEditor(env = import_node_process20.default.env) {
|
|
15498
15926
|
for (const name of EDITOR_ENV) {
|
|
15499
15927
|
const value2 = env[name];
|
|
15500
15928
|
if (value2 && value2.trim()) return value2.trim();
|
|
@@ -15508,8 +15936,8 @@ function defaultRun(command, path) {
|
|
|
15508
15936
|
return result.status ?? 0;
|
|
15509
15937
|
}
|
|
15510
15938
|
function editText(initial, slug, deps = {}) {
|
|
15511
|
-
const env = deps.env ??
|
|
15512
|
-
const interactive = deps.interactive ?? (() => Boolean(
|
|
15939
|
+
const env = deps.env ?? import_node_process20.default.env;
|
|
15940
|
+
const interactive = deps.interactive ?? (() => Boolean(import_node_process20.default.stdin.isTTY));
|
|
15513
15941
|
const editor = resolveEditor(env);
|
|
15514
15942
|
if (!editor)
|
|
15515
15943
|
throw new Error(
|
|
@@ -15517,28 +15945,28 @@ function editText(initial, slug, deps = {}) {
|
|
|
15517
15945
|
);
|
|
15518
15946
|
if (!interactive())
|
|
15519
15947
|
throw new Error(`cannot open an editor without a terminal \u2014 pass --file <path> or --body "\u2026" instead`);
|
|
15520
|
-
const dir = (0,
|
|
15521
|
-
const file = (0,
|
|
15948
|
+
const dir = (0, import_node_fs25.mkdtempSync)((0, import_node_path22.join)((0, import_node_os6.tmpdir)(), "odla-runbook-"));
|
|
15949
|
+
const file = (0, import_node_path22.join)(dir, `${slug}.md`);
|
|
15522
15950
|
try {
|
|
15523
|
-
(0,
|
|
15951
|
+
(0, import_node_fs25.writeFileSync)(file, initial, { mode: 384 });
|
|
15524
15952
|
const code = defaultRunOrInjected(deps)(editor, file);
|
|
15525
15953
|
if (code !== 0) throw new Error(`editor "${editor}" exited with ${code}; nothing was written`);
|
|
15526
|
-
const edited = (0,
|
|
15954
|
+
const edited = (0, import_node_fs25.readFileSync)(file, "utf8");
|
|
15527
15955
|
return edited === initial ? null : edited;
|
|
15528
15956
|
} finally {
|
|
15529
|
-
(0,
|
|
15957
|
+
(0, import_node_fs25.rmSync)(dir, { recursive: true, force: true });
|
|
15530
15958
|
}
|
|
15531
15959
|
}
|
|
15532
|
-
var import_node_child_process7,
|
|
15960
|
+
var import_node_child_process7, import_node_fs25, import_node_os6, import_node_path22, import_node_process20, EDITOR_ENV, defaultRunOrInjected;
|
|
15533
15961
|
var init_runbook_editor = __esm({
|
|
15534
15962
|
"src/runbook-editor.ts"() {
|
|
15535
15963
|
"use strict";
|
|
15536
15964
|
init_cjs_shims();
|
|
15537
15965
|
import_node_child_process7 = require("child_process");
|
|
15538
|
-
|
|
15539
|
-
|
|
15540
|
-
|
|
15541
|
-
|
|
15966
|
+
import_node_fs25 = require("fs");
|
|
15967
|
+
import_node_os6 = require("os");
|
|
15968
|
+
import_node_path22 = require("path");
|
|
15969
|
+
import_node_process20 = __toESM(require("process"), 1);
|
|
15542
15970
|
EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
|
|
15543
15971
|
defaultRunOrInjected = (deps) => deps.run ?? defaultRun;
|
|
15544
15972
|
}
|
|
@@ -15927,9 +16355,9 @@ async function runHostedSecurity(options) {
|
|
|
15927
16355
|
const appId = selfAudit ? "odla-ai" : cfg.app.id;
|
|
15928
16356
|
const env = selfAudit ? "prod" : selectEnv(options.env, cfg.envs, cfg.configPath, cfg.rootDir);
|
|
15929
16357
|
const platform = options.platform ?? cfg?.platformUrl ?? "https://odla.ai";
|
|
15930
|
-
const target = (0,
|
|
15931
|
-
const output = (0,
|
|
15932
|
-
const outputRelative = (0,
|
|
16358
|
+
const target = (0, import_node_path23.resolve)(options.target ?? cfg?.rootDir ?? ".");
|
|
16359
|
+
const output = (0, import_node_path23.resolve)(options.out ?? (0, import_node_path23.resolve)(target, ".odla/security/hosted"));
|
|
16360
|
+
const outputRelative = (0, import_node_path23.relative)(target, output).split(import_node_path23.sep).join("/");
|
|
15933
16361
|
if (!outputRelative) throw new Error("Hosted security output cannot be the repository root");
|
|
15934
16362
|
const profile = profileFor(options.profile ?? "odla", options.maxHuntTasks ?? 12);
|
|
15935
16363
|
const tokenRequest = {
|
|
@@ -15941,7 +16369,7 @@ async function runHostedSecurity(options) {
|
|
|
15941
16369
|
};
|
|
15942
16370
|
const token = await injectedToken(options, tokenRequest);
|
|
15943
16371
|
const snapshot = await (0, import_node3.snapshotDirectory)(target, {
|
|
15944
|
-
exclude: !outputRelative.startsWith("../") && !(0,
|
|
16372
|
+
exclude: !outputRelative.startsWith("../") && !(0, import_node_path23.isAbsolute)(outputRelative) ? [outputRelative] : []
|
|
15945
16373
|
});
|
|
15946
16374
|
const hosted = await (0, import_security.createPlatformSecurityReasoners)({
|
|
15947
16375
|
platform,
|
|
@@ -15959,7 +16387,7 @@ async function runHostedSecurity(options) {
|
|
|
15959
16387
|
});
|
|
15960
16388
|
const harness = (0, import_security.createSecurityHarness)({
|
|
15961
16389
|
profile,
|
|
15962
|
-
store: new import_node3.FileRunStore((0,
|
|
16390
|
+
store: new import_node3.FileRunStore((0, import_node_path23.resolve)(output, "state")),
|
|
15963
16391
|
discoveryReasoner: hosted.discoveryReasoner,
|
|
15964
16392
|
validationReasoner: hosted.validationReasoner,
|
|
15965
16393
|
policy: {
|
|
@@ -15983,7 +16411,7 @@ async function runHostedSecurity(options) {
|
|
|
15983
16411
|
function selectEnv(requested, declared, configPath, rootDir) {
|
|
15984
16412
|
const env = requested ?? (declared.includes("dev") ? "dev" : declared[0]);
|
|
15985
16413
|
if (!env || !declared.includes(env)) {
|
|
15986
|
-
const shown = (0,
|
|
16414
|
+
const shown = (0, import_node_path23.relative)(rootDir, configPath) || configPath;
|
|
15987
16415
|
throw new Error(`env "${env ?? ""}" is not declared in ${shown}`);
|
|
15988
16416
|
}
|
|
15989
16417
|
return env;
|
|
@@ -16012,17 +16440,17 @@ function printSummary(out, appId, env, run, report5, output) {
|
|
|
16012
16440
|
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}`);
|
|
16013
16441
|
if (report5.callBudget) out.log(` calls: discovery=${formatBudget(report5.callBudget.discovery)} validation=${formatBudget(report5.callBudget.validation)}`);
|
|
16014
16442
|
out.log(` findings: confirmed=${report5.metrics.confirmed} needs_reproduction=${report5.metrics.needsReproduction} candidates=${report5.metrics.candidates}`);
|
|
16015
|
-
out.log(` report: ${(0,
|
|
16443
|
+
out.log(` report: ${(0, import_node_path23.resolve)(output, "REPORT.md")}`);
|
|
16016
16444
|
}
|
|
16017
16445
|
function formatBudget(usage) {
|
|
16018
16446
|
return usage ? `${usage.usedCalls}/${usage.maxCalls} skipped=${usage.skippedCalls}` : "caller-managed";
|
|
16019
16447
|
}
|
|
16020
|
-
var
|
|
16448
|
+
var import_node_path23, import_security, import_node3;
|
|
16021
16449
|
var init_security = __esm({
|
|
16022
16450
|
"src/security.ts"() {
|
|
16023
16451
|
"use strict";
|
|
16024
16452
|
init_cjs_shims();
|
|
16025
|
-
|
|
16453
|
+
import_node_path23 = require("path");
|
|
16026
16454
|
import_security = require("@odla-ai/security");
|
|
16027
16455
|
import_node3 = require("@odla-ai/security/node");
|
|
16028
16456
|
init_config();
|
|
@@ -16540,8 +16968,10 @@ async function dispatchCli(argv2, dependencies) {
|
|
|
16540
16968
|
return;
|
|
16541
16969
|
}
|
|
16542
16970
|
if (command === "help" || command === "--help" || command === "-h") {
|
|
16543
|
-
assertArgs(parsed, ["help"],
|
|
16544
|
-
|
|
16971
|
+
assertArgs(parsed, ["help"], 2);
|
|
16972
|
+
const topic = parsed.positionals[1];
|
|
16973
|
+
if (topic) printCommandHelp(topic, runtime.stdout);
|
|
16974
|
+
else printHelp(runtime.stdout);
|
|
16545
16975
|
return;
|
|
16546
16976
|
}
|
|
16547
16977
|
if (command === "whoami") {
|
|
@@ -16712,6 +17142,7 @@ var init_cli = __esm({
|
|
|
16712
17142
|
init_context_command();
|
|
16713
17143
|
init_credential_command();
|
|
16714
17144
|
init_help();
|
|
17145
|
+
init_help_command();
|
|
16715
17146
|
init_version();
|
|
16716
17147
|
init_discuss_command();
|
|
16717
17148
|
init_pm_command();
|