@odla-ai/cli 0.35.3 → 0.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.cjs CHANGED
@@ -377,10 +377,53 @@ var init_cached_credential = __esm({
377
377
  }
378
378
  });
379
379
 
380
+ // src/device-session.ts
381
+ function deviceCredentialPath(env = import_node_process4.default.env) {
382
+ return env.ODLA_DEVICE_CREDENTIAL ?? (0, import_node_path3.join)(env.HOME ?? (0, import_node_os.homedir)(), ".odla", "device.json");
383
+ }
384
+ function readDeviceCredential(platform, env = import_node_process4.default.env) {
385
+ const path = deviceCredentialPath(env);
386
+ if (!(0, import_node_fs6.existsSync)(path)) return null;
387
+ try {
388
+ const parsed = JSON.parse((0, import_node_fs6.readFileSync)(path, "utf8"));
389
+ if (typeof parsed.token !== "string" || !parsed.token.startsWith("odla_device_")) return null;
390
+ if (parsed.platform !== platform) return null;
391
+ return { ...parsed, token: parsed.token, platform: parsed.platform };
392
+ } catch {
393
+ return null;
394
+ }
395
+ }
396
+ async function mintDeviceSession(platformUrl, credential2, doFetch) {
397
+ const response2 = await doFetch(`${platformUrl.replace(/\/$/, "")}/registry/devices/session`, {
398
+ method: "POST",
399
+ headers: { authorization: `Bearer ${credential2.token}`, "content-type": "application/json" },
400
+ body: "{}"
401
+ });
402
+ const body = await response2.json().catch(() => ({}));
403
+ if (!response2.ok || typeof body.token !== "string") {
404
+ const detail = body.error?.message ?? `registry returned ${response2.status}`;
405
+ throw new Error(
406
+ `device session failed: ${detail} (${response2.status}) \u2014 if this machine's enrollment was revoked or has expired, enroll it again in Studio`
407
+ );
408
+ }
409
+ return { token: body.token, expiresAt: body.expiresAt ?? Date.now() };
410
+ }
411
+ var import_node_fs6, import_node_os, import_node_path3, import_node_process4;
412
+ var init_device_session = __esm({
413
+ "src/device-session.ts"() {
414
+ "use strict";
415
+ init_cjs_shims();
416
+ import_node_fs6 = require("fs");
417
+ import_node_os = require("os");
418
+ import_node_path3 = require("path");
419
+ import_node_process4 = __toESM(require("process"), 1);
420
+ }
421
+ });
422
+
380
423
  // src/local.ts
381
424
  function readJsonFile(path) {
382
425
  try {
383
- return JSON.parse((0, import_node_fs6.readFileSync)(path, "utf8"));
426
+ return JSON.parse((0, import_node_fs7.readFileSync)(path, "utf8"));
384
427
  } catch {
385
428
  return null;
386
429
  }
@@ -390,10 +433,10 @@ function writePrivateJson(path, value2) {
390
433
  `);
391
434
  }
392
435
  function readCredentials(path) {
393
- if (!(0, import_node_fs6.existsSync)(path)) return null;
436
+ if (!(0, import_node_fs7.existsSync)(path)) return null;
394
437
  let value2;
395
438
  try {
396
- value2 = JSON.parse((0, import_node_fs6.readFileSync)(path, "utf8"));
439
+ value2 = JSON.parse((0, import_node_fs7.readFileSync)(path, "utf8"));
397
440
  } catch {
398
441
  throw new Error(`credentials file ${path} is not valid JSON; fix or remove it before provisioning`);
399
442
  }
@@ -423,14 +466,14 @@ function mergeCredential(current, update) {
423
466
  return next;
424
467
  }
425
468
  function ensureGitignore(rootDir, localPaths = []) {
426
- const path = (0, import_node_path3.resolve)(rootDir, ".gitignore");
427
- const existing = (0, import_node_fs6.existsSync)(path) ? (0, import_node_fs6.readFileSync)(path, "utf8") : "";
469
+ const path = (0, import_node_path4.resolve)(rootDir, ".gitignore");
470
+ const existing = (0, import_node_fs7.existsSync)(path) ? (0, import_node_fs7.readFileSync)(path, "utf8") : "";
428
471
  const configured = localPaths.map((localPath) => gitignoreEntry(rootDir, localPath)).filter((line2) => !!line2);
429
472
  const wanted = [.../* @__PURE__ */ new Set([...GITIGNORE_LINES, ...configured])];
430
473
  const missing = wanted.filter((line2) => !existing.split(/\r?\n/).includes(line2));
431
474
  if (missing.length === 0) return;
432
475
  const prefix = existing && !existing.endsWith("\n") ? "\n" : "";
433
- (0, import_node_fs6.writeFileSync)(path, `${existing}${prefix}${missing.join("\n")}
476
+ (0, import_node_fs7.writeFileSync)(path, `${existing}${prefix}${missing.join("\n")}
434
477
  `);
435
478
  }
436
479
  function o11yDevVars(cfg) {
@@ -444,7 +487,7 @@ function o11yDevVars(cfg) {
444
487
  function resolveWriteDevVarsTarget(cfg, requested) {
445
488
  if (!requested) return null;
446
489
  if (requested === true) return cfg.local.devVarsFile;
447
- return (0, import_node_path3.resolve)((0, import_node_path3.dirname)(cfg.configPath), requested);
490
+ return (0, import_node_path4.resolve)((0, import_node_path4.dirname)(cfg.configPath), requested);
448
491
  }
449
492
  function writeDevVars(path, credentials, env, o11y) {
450
493
  const entry = credentials.envs[env];
@@ -458,7 +501,7 @@ function writeDevVars(path, credentials, env, o11y) {
458
501
  if (o11y.version) lines.push(`ODLA_O11Y_VERSION="${o11y.version}"`);
459
502
  if (entry.o11yToken) lines.push(`ODLA_O11Y_TOKEN="${entry.o11yToken}"`);
460
503
  }
461
- const existing = (0, import_node_fs6.existsSync)(path) ? (0, import_node_fs6.readFileSync)(path, "utf8") : "";
504
+ const existing = (0, import_node_fs7.existsSync)(path) ? (0, import_node_fs7.readFileSync)(path, "utf8") : "";
462
505
  const retained = existing.split(/\r?\n/).filter((line2) => !isManagedDevVar(line2));
463
506
  while (retained.at(-1) === "") retained.pop();
464
507
  const prefix = retained.length ? `${retained.join("\n")}
@@ -472,28 +515,28 @@ function isManagedDevVar(line2) {
472
515
  return !!match?.[1] && MANAGED_DEV_VARS.has(match[1]);
473
516
  }
474
517
  function writePrivateText(path, text3) {
475
- (0, import_node_fs6.mkdirSync)((0, import_node_path3.dirname)(path), { recursive: true });
518
+ (0, import_node_fs7.mkdirSync)((0, import_node_path4.dirname)(path), { recursive: true });
476
519
  const temporary = `${path}.tmp-${process.pid}-${Date.now()}`;
477
- (0, import_node_fs6.writeFileSync)(temporary, text3, { mode: 384 });
478
- (0, import_node_fs6.chmodSync)(temporary, 384);
479
- (0, import_node_fs6.renameSync)(temporary, path);
520
+ (0, import_node_fs7.writeFileSync)(temporary, text3, { mode: 384 });
521
+ (0, import_node_fs7.chmodSync)(temporary, 384);
522
+ (0, import_node_fs7.renameSync)(temporary, path);
480
523
  }
481
524
  function gitignoreEntry(rootDir, path) {
482
- const rel = (0, import_node_path3.relative)((0, import_node_path3.resolve)(rootDir), (0, import_node_path3.resolve)(path));
483
- if (!rel || rel === ".." || rel.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || (0, import_node_path3.isAbsolute)(rel)) return null;
525
+ const rel = (0, import_node_path4.relative)((0, import_node_path4.resolve)(rootDir), (0, import_node_path4.resolve)(path));
526
+ if (!rel || rel === ".." || rel.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || (0, import_node_path4.isAbsolute)(rel)) return null;
484
527
  return rel.replaceAll("\\", "/");
485
528
  }
486
529
  function displayPath(path, rootDir = process.cwd()) {
487
- const rel = (0, import_node_path3.relative)(rootDir, path);
530
+ const rel = (0, import_node_path4.relative)(rootDir, path);
488
531
  return rel && !rel.startsWith("..") ? rel : path;
489
532
  }
490
- var import_node_fs6, import_node_path3, GITIGNORE_LINES, MANAGED_DEV_VARS;
533
+ var import_node_fs7, import_node_path4, GITIGNORE_LINES, MANAGED_DEV_VARS;
491
534
  var init_local = __esm({
492
535
  "src/local.ts"() {
493
536
  "use strict";
494
537
  init_cjs_shims();
495
- import_node_fs6 = require("fs");
496
- import_node_path3 = require("path");
538
+ import_node_fs7 = require("fs");
539
+ import_node_path4 = require("path");
497
540
  GITIGNORE_LINES = [".odla/*.local.json", ".odla/dev-token.json", ".dev.vars"];
498
541
  MANAGED_DEV_VARS = /* @__PURE__ */ new Set([
499
542
  "ODLA_PLATFORM",
@@ -518,14 +561,20 @@ async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {})
518
561
  const cached = readJsonFile(cfg.local.tokenFile);
519
562
  if (!grantRequest.forceReview && !grantRequest.freshLogin) {
520
563
  if (options.token) return options.token;
521
- if (import_node_process4.default.env.ODLA_DEV_TOKEN) {
522
- const declared = import_node_process4.default.env.ODLA_DEV_TOKEN_AUDIENCE;
564
+ if (import_node_process5.default.env.ODLA_DEV_TOKEN) {
565
+ const declared = import_node_process5.default.env.ODLA_DEV_TOKEN_AUDIENCE;
523
566
  if (declared) {
524
567
  if (platformAudience(declared) !== audience) throw new Error("ODLA_DEV_TOKEN_AUDIENCE does not match the configured platform");
525
568
  } else if (audience !== "https://odla.ai") {
526
569
  throw new Error("ODLA_DEV_TOKEN_AUDIENCE is required for a non-default platform");
527
570
  }
528
- return import_node_process4.default.env.ODLA_DEV_TOKEN;
571
+ return import_node_process5.default.env.ODLA_DEV_TOKEN;
572
+ }
573
+ const device = readDeviceCredential(audience);
574
+ if (device) {
575
+ const session = await mintDeviceSession(cfg.platformUrl, device, doFetch);
576
+ out.error(`auth: session minted by this enrolled device (${displayPath(deviceCredentialPath(), cfg.rootDir)})`);
577
+ return session.token;
529
578
  }
530
579
  if (cached?.token && cached.platform === audience && (cached.expiresAt ?? 0) > Date.now() + 6e4 && cachedGrantCovers(cached, grantIntent)) {
531
580
  out.error(`auth: using cached developer token (${displayPath(cfg.local.tokenFile, cfg.rootDir)})`);
@@ -630,7 +679,7 @@ function stillPending(pending, email) {
630
679
  );
631
680
  }
632
681
  function handshakeEmail(value2, cached) {
633
- const email = (value2 ?? import_node_process4.default.env.ODLA_USER_EMAIL ?? cached ?? "").trim().toLowerCase();
682
+ const email = (value2 ?? import_node_process5.default.env.ODLA_USER_EMAIL ?? cached ?? "").trim().toLowerCase();
634
683
  if (/@users\.noreply\.github\.com$/i.test(email)) {
635
684
  throw new Error(
636
685
  `"${email}" is a GitHub commit identity, not an odla account email; use --email <signed-in-odla-account> or ODLA_USER_EMAIL`
@@ -659,17 +708,18 @@ function platformAudience(value2) {
659
708
  }
660
709
  return url.origin;
661
710
  }
662
- var import_db, import_node_crypto, import_node_process4;
711
+ var import_db, import_node_crypto, import_node_process5;
663
712
  var init_token = __esm({
664
713
  "src/token.ts"() {
665
714
  "use strict";
666
715
  init_cjs_shims();
667
716
  import_db = require("@odla-ai/db");
668
717
  import_node_crypto = require("crypto");
669
- import_node_process4 = __toESM(require("process"), 1);
718
+ import_node_process5 = __toESM(require("process"), 1);
670
719
  init_handshake_approval();
671
720
  init_handshake_state();
672
721
  init_cached_credential();
722
+ init_device_session();
673
723
  init_local();
674
724
  }
675
725
  });
@@ -678,7 +728,7 @@ var init_token = __esm({
678
728
  async function secretInputValue(options, kind = "credential") {
679
729
  if (options.fromEnv && options.stdin) throw new Error("choose exactly one of --from-env or --stdin");
680
730
  let value2;
681
- if (options.fromEnv) value2 = import_node_process5.default.env[options.fromEnv];
731
+ if (options.fromEnv) value2 = import_node_process6.default.env[options.fromEnv];
682
732
  else if (options.stdin) value2 = await (options.readStdin ?? (() => readSecretStream(kind)))();
683
733
  else throw new Error(`${kind} input required: use --from-env <NAME> or --stdin; values are never accepted as arguments`);
684
734
  value2 = value2?.replace(/[\r\n]+$/, "");
@@ -686,7 +736,7 @@ async function secretInputValue(options, kind = "credential") {
686
736
  if (new TextEncoder().encode(value2).byteLength > MAX_BYTES) throw new Error(`${kind} exceeds 64 KiB`);
687
737
  return value2;
688
738
  }
689
- async function readSecretStream(kind, stream = import_node_process5.default.stdin) {
739
+ async function readSecretStream(kind, stream = import_node_process6.default.stdin) {
690
740
  let value2 = "";
691
741
  for await (const chunk of stream) {
692
742
  value2 += String(chunk);
@@ -694,12 +744,12 @@ async function readSecretStream(kind, stream = import_node_process5.default.stdi
694
744
  }
695
745
  return value2;
696
746
  }
697
- var import_node_process5, MAX_BYTES;
747
+ var import_node_process6, MAX_BYTES;
698
748
  var init_secret_input = __esm({
699
749
  "src/secret-input.ts"() {
700
750
  "use strict";
701
751
  init_cjs_shims();
702
- import_node_process5 = __toESM(require("process"), 1);
752
+ import_node_process6 = __toESM(require("process"), 1);
703
753
  MAX_BYTES = 64 * 1024;
704
754
  }
705
755
  });
@@ -711,7 +761,7 @@ async function getScopedPlatformToken(options) {
711
761
  async function resolveAdminPlatformToken(options) {
712
762
  const audience = platformAudience(options.platform);
713
763
  if (options.token) return options.token;
714
- const fromEnv = import_node_process6.default.env.ODLA_ADMIN_TOKEN;
764
+ const fromEnv = import_node_process7.default.env.ODLA_ADMIN_TOKEN;
715
765
  if (fromEnv) return audienceBoundEnvToken(fromEnv, audience);
716
766
  return scopedToken(
717
767
  audience,
@@ -723,7 +773,7 @@ async function resolveAdminPlatformToken(options) {
723
773
  }
724
774
  function audienceBoundEnvToken(token, platform) {
725
775
  const audience = platformAudience(platform);
726
- const declared = import_node_process6.default.env.ODLA_ADMIN_TOKEN_AUDIENCE;
776
+ const declared = import_node_process7.default.env.ODLA_ADMIN_TOKEN_AUDIENCE;
727
777
  if (declared) {
728
778
  if (platformAudience(declared) !== audience) throw new Error("ODLA_ADMIN_TOKEN_AUDIENCE does not match the configured platform");
729
779
  } else if (audience !== "https://odla.ai") {
@@ -733,8 +783,8 @@ function audienceBoundEnvToken(token, platform) {
733
783
  }
734
784
  async function scopedToken(platform, scope, options, doFetch, out) {
735
785
  const audience = platformAudience(platform);
736
- const rootDir = options.rootDir ?? import_node_process6.default.cwd();
737
- const tokenFile = options.tokenFile ?? (0, import_node_path4.join)(rootDir, ".odla/admin-token.local.json");
786
+ const rootDir = options.rootDir ?? import_node_process7.default.cwd();
787
+ const tokenFile = options.tokenFile ?? (0, import_node_path5.join)(rootDir, ".odla/admin-token.local.json");
738
788
  const cache2 = options.cache === false ? null : readJsonFile(tokenFile);
739
789
  const cached = cache2?.platform === audience ? cache2.tokens?.[scope] : void 0;
740
790
  if (cached?.token && (cached.expiresAt ?? 0) > Date.now() + 6e4) {
@@ -761,7 +811,7 @@ async function scopedToken(platform, scope, options, doFetch, out) {
761
811
  if (options.cache !== false) {
762
812
  const tokens = cache2?.platform === audience ? { ...cache2.tokens ?? {} } : {};
763
813
  tokens[scope] = { token, expiresAt };
764
- if ((0, import_node_fs7.existsSync)((0, import_node_path4.join)(rootDir, ".git"))) ensureGitignore(rootDir, [tokenFile]);
814
+ if ((0, import_node_fs8.existsSync)((0, import_node_path5.join)(rootDir, ".git"))) ensureGitignore(rootDir, [tokenFile]);
765
815
  writePrivateJson(tokenFile, { platform: audience, email, tokens });
766
816
  out.error(`auth: cached ${scope} grant (${tokenFile}; mode 0600)`);
767
817
  } else {
@@ -769,14 +819,14 @@ async function scopedToken(platform, scope, options, doFetch, out) {
769
819
  }
770
820
  return token;
771
821
  }
772
- var import_node_fs7, import_node_path4, import_node_process6, import_db2, SCOPE_PURPOSE;
822
+ var import_node_fs8, import_node_path5, import_node_process7, import_db2, SCOPE_PURPOSE;
773
823
  var init_admin_ai_auth = __esm({
774
824
  "src/admin-ai-auth.ts"() {
775
825
  "use strict";
776
826
  init_cjs_shims();
777
- import_node_fs7 = require("fs");
778
- import_node_path4 = require("path");
779
- import_node_process6 = __toESM(require("process"), 1);
827
+ import_node_fs8 = require("fs");
828
+ import_node_path5 = require("path");
829
+ import_node_process7 = __toESM(require("process"), 1);
780
830
  import_db2 = require("@odla-ai/db");
781
831
  init_local();
782
832
  init_handshake_approval();
@@ -786,6 +836,7 @@ var init_admin_ai_auth = __esm({
786
836
  "app:config:read": "compare checked-in intent with an exact-id app Registry configuration",
787
837
  "app:config:write": "apply or inspect one revision-bound configuration operation for an app you own",
788
838
  "platform:runbook:write": "read and edit all of odla's operational runbooks, including admin-visible content",
839
+ "app:device:enroll": "enrol this machine so it can mint its own short-lived credentials without asking you again",
789
840
  "platform:ai:policy:write": "change System AI model routing",
790
841
  "platform:ai:policy:read": "read System AI model routing",
791
842
  "platform:ai:credential:write": "replace a stored AI provider key",
@@ -994,7 +1045,7 @@ var init_admin_ai_usage = __esm({
994
1045
 
995
1046
  // src/admin-ai.ts
996
1047
  async function adminAi(options) {
997
- const platform = platformAudience(options.platform ?? import_node_process7.default.env.ODLA_PLATFORM ?? "https://odla.ai");
1048
+ const platform = platformAudience(options.platform ?? import_node_process8.default.env.ODLA_PLATFORM ?? "https://odla.ai");
998
1049
  const doFetch = options.fetch ?? fetch;
999
1050
  const out = options.stdout ?? console;
1000
1051
  const usageQuery = options.action === "usage" ? adminAiUsageQuery(options) : void 0;
@@ -1172,12 +1223,12 @@ function apiError3(action2, status, body) {
1172
1223
  function isRecord3(value2) {
1173
1224
  return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
1174
1225
  }
1175
- var import_node_process7;
1226
+ var import_node_process8;
1176
1227
  var init_admin_ai = __esm({
1177
1228
  "src/admin-ai.ts"() {
1178
1229
  "use strict";
1179
1230
  init_cjs_shims();
1180
- import_node_process7 = __toESM(require("process"), 1);
1231
+ import_node_process8 = __toESM(require("process"), 1);
1181
1232
  init_token();
1182
1233
  init_secret_input();
1183
1234
  init_admin_ai_auth();
@@ -1691,12 +1742,12 @@ var init_monitoring_validation = __esm({
1691
1742
 
1692
1743
  // src/config.ts
1693
1744
  async function loadProjectConfig(configPath = "odla.config.mjs", options = {}) {
1694
- const resolved = (0, import_node_path5.resolve)(configPath);
1695
- if (!(0, import_node_fs8.existsSync)(resolved)) {
1745
+ const resolved = (0, import_node_path6.resolve)(configPath);
1746
+ if (!(0, import_node_fs9.existsSync)(resolved)) {
1696
1747
  throw new Error(`config not found: ${configPath}. Run "odla-ai init" first or pass --config.`);
1697
1748
  }
1698
1749
  const raw = await loadConfigModule(resolved);
1699
- const rootDir = (0, import_node_path5.dirname)(resolved);
1750
+ const rootDir = (0, import_node_path6.dirname)(resolved);
1700
1751
  validateRawConfig(raw, resolved);
1701
1752
  const platformUrl = trimSlash(process.env.ODLA_PLATFORM_URL || raw.platformUrl || DEFAULT_PLATFORM);
1702
1753
  const dbEndpoint = trimSlash(process.env.ODLA_DB_ENDPOINT || raw.dbEndpoint || platformUrl);
@@ -1706,9 +1757,9 @@ async function loadProjectConfig(configPath = "odla.config.mjs", options = {}) {
1706
1757
  validateCalendarConfig(raw, unique3([...envs, ...options.additionalEnvs ?? []]), services, resolved);
1707
1758
  validateMonitoringConfig(raw, unique3([...envs, ...options.additionalEnvs ?? []]), services, resolved);
1708
1759
  const local = {
1709
- tokenFile: (0, import_node_path5.resolve)(rootDir, raw.local?.tokenFile ?? ".odla/dev-token.json"),
1710
- credentialsFile: (0, import_node_path5.resolve)(rootDir, raw.local?.credentialsFile ?? ".odla/credentials.local.json"),
1711
- devVarsFile: (0, import_node_path5.resolve)(rootDir, raw.local?.devVarsFile ?? ".dev.vars"),
1760
+ tokenFile: (0, import_node_path6.resolve)(rootDir, raw.local?.tokenFile ?? ".odla/dev-token.json"),
1761
+ credentialsFile: (0, import_node_path6.resolve)(rootDir, raw.local?.credentialsFile ?? ".odla/credentials.local.json"),
1762
+ devVarsFile: (0, import_node_path6.resolve)(rootDir, raw.local?.devVarsFile ?? ".dev.vars"),
1712
1763
  gitignore: raw.local?.gitignore ?? true
1713
1764
  };
1714
1765
  return {
@@ -1725,9 +1776,9 @@ async function loadProjectConfig(configPath = "odla.config.mjs", options = {}) {
1725
1776
  async function resolveDataExport(cfg, value2, names) {
1726
1777
  if (value2 === void 0 || value2 === null || value2 === false) return void 0;
1727
1778
  if (typeof value2 !== "string") return value2;
1728
- const target = (0, import_node_path5.isAbsolute)(value2) ? value2 : (0, import_node_path5.resolve)(cfg.rootDir, value2);
1779
+ const target = (0, import_node_path6.isAbsolute)(value2) ? value2 : (0, import_node_path6.resolve)(cfg.rootDir, value2);
1729
1780
  if (target.endsWith(".json")) {
1730
- return JSON.parse((0, import_node_fs8.readFileSync)(target, "utf8"));
1781
+ return JSON.parse((0, import_node_fs9.readFileSync)(target, "utf8"));
1731
1782
  }
1732
1783
  const mod = await import((0, import_node_url.pathToFileURL)(target).href);
1733
1784
  for (const name of names) {
@@ -1803,7 +1854,7 @@ function validId2(value2) {
1803
1854
  return typeof value2 === "string" && /^[a-z0-9][a-z0-9-]*$/.test(value2);
1804
1855
  }
1805
1856
  async function loadConfigModule(path) {
1806
- if (path.endsWith(".json")) return JSON.parse((0, import_node_fs8.readFileSync)(path, "utf8"));
1857
+ if (path.endsWith(".json")) return JSON.parse((0, import_node_fs9.readFileSync)(path, "utf8"));
1807
1858
  const nonce = `${Date.now()}-${configImportSerial++}`;
1808
1859
  const mod = await import(`${(0, import_node_url.pathToFileURL)(path).href}?reload=${nonce}`);
1809
1860
  const value2 = mod.default ?? mod.config;
@@ -1816,13 +1867,13 @@ function trimSlash(value2) {
1816
1867
  function unique3(values) {
1817
1868
  return [...new Set(values.filter(Boolean))];
1818
1869
  }
1819
- var import_node_fs8, import_node_path5, import_node_url, import_apps, DEFAULT_PLATFORM, DEFAULT_ENVS, DEFAULT_SERVICES, configImportSerial, GOOGLE_CALENDAR_EVENTS_SCOPE;
1870
+ var import_node_fs9, import_node_path6, import_node_url, import_apps, DEFAULT_PLATFORM, DEFAULT_ENVS, DEFAULT_SERVICES, configImportSerial, GOOGLE_CALENDAR_EVENTS_SCOPE;
1820
1871
  var init_config = __esm({
1821
1872
  "src/config.ts"() {
1822
1873
  "use strict";
1823
1874
  init_cjs_shims();
1824
- import_node_fs8 = require("fs");
1825
- import_node_path5 = require("path");
1875
+ import_node_fs9 = require("fs");
1876
+ import_node_path6 = require("path");
1826
1877
  import_node_url = require("url");
1827
1878
  import_apps = require("@odla-ai/apps");
1828
1879
  init_ai_config_validation();
@@ -1840,13 +1891,13 @@ var init_config = __esm({
1840
1891
 
1841
1892
  // src/operator-profiles.ts
1842
1893
  function operatorProfileFile() {
1843
- return (0, import_node_path6.resolve)(
1844
- clean(import_node_process8.default.env.ODLA_CONTEXT_FILE) ?? (0, import_node_path6.join)((0, import_node_os.homedir)(), ".odla", "contexts.json")
1894
+ return (0, import_node_path7.resolve)(
1895
+ clean(import_node_process9.default.env.ODLA_CONTEXT_FILE) ?? (0, import_node_path7.join)((0, import_node_os2.homedir)(), ".odla", "contexts.json")
1845
1896
  );
1846
1897
  }
1847
1898
  function resolveOperatorProfile(parsed) {
1848
1899
  const fromFlag = clean(stringOpt(parsed.options.context));
1849
- const fromEnvironment = clean(import_node_process8.default.env.ODLA_CONTEXT);
1900
+ const fromEnvironment = clean(import_node_process9.default.env.ODLA_CONTEXT);
1850
1901
  const name = fromFlag ?? fromEnvironment ?? null;
1851
1902
  const file = operatorProfileFile();
1852
1903
  if (!name) {
@@ -1886,10 +1937,10 @@ function removeOperatorProfile(name, file = operatorProfileFile()) {
1886
1937
  return true;
1887
1938
  }
1888
1939
  function operatorCredentialFiles(selection) {
1889
- const base = selection.name ? (0, import_node_path6.join)((0, import_node_path6.dirname)(selection.file), "profiles", selection.name) : (0, import_node_path6.join)((0, import_node_os.homedir)(), ".odla");
1940
+ const base = selection.name ? (0, import_node_path7.join)((0, import_node_path7.dirname)(selection.file), "profiles", selection.name) : (0, import_node_path7.join)((0, import_node_os2.homedir)(), ".odla");
1890
1941
  return {
1891
- developer: (0, import_node_path6.join)(base, "dev-token.json"),
1892
- scoped: (0, import_node_path6.join)(base, "admin-token.local.json")
1942
+ developer: (0, import_node_path7.join)(base, "dev-token.json"),
1943
+ scoped: (0, import_node_path7.join)(base, "admin-token.local.json")
1893
1944
  };
1894
1945
  }
1895
1946
  function assertOperatorName(value2, label) {
@@ -1900,10 +1951,10 @@ function assertOperatorName(value2, label) {
1900
1951
  }
1901
1952
  }
1902
1953
  function readOperatorProfiles(file) {
1903
- if (!(0, import_node_fs9.existsSync)(file)) return emptyProfiles();
1954
+ if (!(0, import_node_fs10.existsSync)(file)) return emptyProfiles();
1904
1955
  let raw;
1905
1956
  try {
1906
- raw = JSON.parse((0, import_node_fs9.readFileSync)(file, "utf8"));
1957
+ raw = JSON.parse((0, import_node_fs10.readFileSync)(file, "utf8"));
1907
1958
  } catch {
1908
1959
  throw new Error(`operator context file ${file} is not valid JSON`);
1909
1960
  }
@@ -1961,15 +2012,15 @@ function clean(value2) {
1961
2012
  const normalized = value2?.trim();
1962
2013
  return normalized || void 0;
1963
2014
  }
1964
- var import_node_fs9, import_node_os, import_node_path6, import_node_process8;
2015
+ var import_node_fs10, import_node_os2, import_node_path7, import_node_process9;
1965
2016
  var init_operator_profiles = __esm({
1966
2017
  "src/operator-profiles.ts"() {
1967
2018
  "use strict";
1968
2019
  init_cjs_shims();
1969
- import_node_fs9 = require("fs");
1970
- import_node_os = require("os");
1971
- import_node_path6 = require("path");
1972
- import_node_process8 = __toESM(require("process"), 1);
2020
+ import_node_fs10 = require("fs");
2021
+ import_node_os2 = require("os");
2022
+ import_node_path7 = require("path");
2023
+ import_node_process9 = __toESM(require("process"), 1);
1973
2024
  init_argv();
1974
2025
  init_local();
1975
2026
  init_token();
@@ -1980,21 +2031,21 @@ var init_operator_profiles = __esm({
1980
2031
  async function resolveOperatorContext(parsed, options = {}) {
1981
2032
  const profile = resolveOperatorProfile(parsed);
1982
2033
  const configArgument = stringOpt(parsed.options.config) ?? "odla.config.mjs";
1983
- const configPath = (0, import_node_path7.resolve)(configArgument);
2034
+ const configPath = (0, import_node_path8.resolve)(configArgument);
1984
2035
  const explicitConfig = parsed.options.config !== void 0;
1985
- const hasConfig = (0, import_node_fs10.existsSync)(configPath);
2036
+ const hasConfig = (0, import_node_fs11.existsSync)(configPath);
1986
2037
  if (!hasConfig && (!options.allowMissingConfig || explicitConfig)) {
1987
2038
  await loadProjectConfig(configArgument);
1988
2039
  }
1989
2040
  const loaded = hasConfig ? await loadProjectConfig(configArgument) : void 0;
1990
2041
  const platformFlag = clean2(stringOpt(parsed.options.platform));
1991
- const platformEnvironment = clean2(import_node_process9.default.env.ODLA_PLATFORM_URL);
2042
+ const platformEnvironment = clean2(import_node_process10.default.env.ODLA_PLATFORM_URL);
1992
2043
  const platformValue = platformAudience(
1993
2044
  platformFlag ?? platformEnvironment ?? profile.value?.platform ?? loaded?.platformUrl ?? DEFAULT_PLATFORM2
1994
2045
  );
1995
2046
  const platformSource = platformFlag ? "flag" : platformEnvironment ? "environment" : profile.value ? "profile" : loaded ? "config" : "default";
1996
2047
  const appFlag = clean2(stringOpt(parsed.options.app));
1997
- const appEnvironment = clean2(import_node_process9.default.env.ODLA_APP_ID);
2048
+ const appEnvironment = clean2(import_node_process10.default.env.ODLA_APP_ID);
1998
2049
  const appValue = appFlag ?? appEnvironment ?? profile.value?.app ?? loaded?.app.id ?? null;
1999
2050
  const appSource = appFlag ? "flag" : appEnvironment ? "environment" : profile.value?.app ? "profile" : loaded ? "config" : "unresolved";
2000
2051
  if (appValue) assertOperatorName(appValue, "app");
@@ -2004,16 +2055,16 @@ async function resolveOperatorContext(parsed, options = {}) {
2004
2055
  );
2005
2056
  }
2006
2057
  const envFlag = clean2(stringOpt(parsed.options.env));
2007
- const envEnvironment = clean2(import_node_process9.default.env.ODLA_ENV);
2058
+ const envEnvironment = clean2(import_node_process10.default.env.ODLA_ENV);
2008
2059
  const environmentValue = envFlag ?? envEnvironment ?? profile.value?.environment ?? options.defaultEnvironment ?? null;
2009
2060
  const environmentSource = envFlag ? "flag" : envEnvironment ? "environment" : profile.value?.environment ? "profile" : options.defaultEnvironment ? "default" : "unresolved";
2010
2061
  if (environmentValue) {
2011
2062
  assertOperatorName(environmentValue, "environment");
2012
2063
  }
2013
- const rootDir = loaded?.rootDir ?? import_node_process9.default.cwd();
2064
+ const rootDir = loaded?.rootDir ?? import_node_process10.default.cwd();
2014
2065
  const profileCredentials = operatorCredentialFiles(profile);
2015
- const tokenFile = clean2(import_node_process9.default.env.ODLA_DEV_TOKEN_FILE) ? (0, import_node_path7.resolve)(import_node_process9.default.env.ODLA_DEV_TOKEN_FILE) : profile.name ? profileCredentials.developer : loaded?.local.tokenFile ?? profileCredentials.developer;
2016
- const scopedTokenFile = clean2(import_node_process9.default.env.ODLA_ADMIN_TOKEN_FILE) ? (0, import_node_path7.resolve)(import_node_process9.default.env.ODLA_ADMIN_TOKEN_FILE) : profile.name ? profileCredentials.scoped : loaded ? (0, import_node_path7.join)(loaded.rootDir, ".odla", "admin-token.local.json") : profileCredentials.scoped;
2066
+ const tokenFile = clean2(import_node_process10.default.env.ODLA_DEV_TOKEN_FILE) ? (0, import_node_path8.resolve)(import_node_process10.default.env.ODLA_DEV_TOKEN_FILE) : profile.name ? profileCredentials.developer : loaded?.local.tokenFile ?? profileCredentials.developer;
2067
+ const scopedTokenFile = clean2(import_node_process10.default.env.ODLA_ADMIN_TOKEN_FILE) ? (0, import_node_path8.resolve)(import_node_process10.default.env.ODLA_ADMIN_TOKEN_FILE) : profile.name ? profileCredentials.scoped : loaded ? (0, import_node_path8.join)(loaded.rootDir, ".odla", "admin-token.local.json") : profileCredentials.scoped;
2017
2068
  const cfg = loaded ? {
2018
2069
  ...loaded,
2019
2070
  platformUrl: platformValue,
@@ -2035,8 +2086,8 @@ async function resolveOperatorContext(parsed, options = {}) {
2035
2086
  services: [],
2036
2087
  local: {
2037
2088
  tokenFile,
2038
- credentialsFile: (0, import_node_path7.join)(rootDir, ".odla", "credentials.local.json"),
2039
- devVarsFile: (0, import_node_path7.join)(rootDir, ".dev.vars"),
2089
+ credentialsFile: (0, import_node_path8.join)(rootDir, ".odla", "credentials.local.json"),
2090
+ devVarsFile: (0, import_node_path8.join)(rootDir, ".dev.vars"),
2040
2091
  gitignore: true
2041
2092
  }
2042
2093
  };
@@ -2068,14 +2119,14 @@ function clean2(value2) {
2068
2119
  const normalized = value2?.trim();
2069
2120
  return normalized || void 0;
2070
2121
  }
2071
- var import_node_fs10, import_node_path7, import_node_process9, DEFAULT_PLATFORM2;
2122
+ var import_node_fs11, import_node_path8, import_node_process10, DEFAULT_PLATFORM2;
2072
2123
  var init_operator_context = __esm({
2073
2124
  "src/operator-context.ts"() {
2074
2125
  "use strict";
2075
2126
  init_cjs_shims();
2076
- import_node_fs10 = require("fs");
2077
- import_node_path7 = require("path");
2078
- import_node_process9 = __toESM(require("process"), 1);
2127
+ import_node_fs11 = require("fs");
2128
+ import_node_path8 = require("path");
2129
+ import_node_process10 = __toESM(require("process"), 1);
2079
2130
  init_argv();
2080
2131
  init_config();
2081
2132
  init_operator_profiles();
@@ -2327,7 +2378,7 @@ async function authCommand(parsed, deps = {}) {
2327
2378
  const { cfg } = context;
2328
2379
  const out = deps.stdout ?? console;
2329
2380
  const doFetch = deps.fetch ?? fetch;
2330
- const email = stringOpt(parsed.options.email) ?? import_node_process10.default.env.ODLA_USER_EMAIL?.trim();
2381
+ const email = stringOpt(parsed.options.email) ?? import_node_process11.default.env.ODLA_USER_EMAIL?.trim();
2331
2382
  if (!email) {
2332
2383
  throw new Error(
2333
2384
  "auth login requires --email <odla-account> or ODLA_USER_EMAIL; confirm the signed-in odla email instead of using git or GitHub identity"
@@ -2361,12 +2412,12 @@ async function authCommand(parsed, deps = {}) {
2361
2412
  out.log(`Authorized ${identity.displayName}${handle} for ${cfg.app.id}.`);
2362
2413
  out.log(`odla account: ${identity.email ?? "not returned"}`);
2363
2414
  }
2364
- var import_node_process10;
2415
+ var import_node_process11;
2365
2416
  var init_auth_command = __esm({
2366
2417
  "src/auth-command.ts"() {
2367
2418
  "use strict";
2368
2419
  init_cjs_shims();
2369
- import_node_process10 = __toESM(require("process"), 1);
2420
+ import_node_process11 = __toESM(require("process"), 1);
2370
2421
  init_argv();
2371
2422
  init_operator_context();
2372
2423
  init_token();
@@ -2539,7 +2590,7 @@ async function appImport(options) {
2539
2590
  const out = options.stdout ?? console;
2540
2591
  const say = options.json ? (line2) => out.error(line2) : (line2) => out.log(line2);
2541
2592
  const { tenant } = resolveTenant(cfg, options.env);
2542
- const text3 = options.file === "-" ? (options.readStdin ?? (() => (0, import_node_fs11.readFileSync)(0, "utf8")))() : (0, import_node_fs11.readFileSync)(options.file, "utf8");
2593
+ const text3 = options.file === "-" ? (options.readStdin ?? (() => (0, import_node_fs12.readFileSync)(0, "utf8")))() : (0, import_node_fs12.readFileSync)(options.file, "utf8");
2543
2594
  const { format, sources } = (0, import_import.parseImport)(text3, options.ns);
2544
2595
  if (format === "namespace-map" && options.ns) {
2545
2596
  throw new Error("--ns cannot be combined with a {namespace: rows} file \u2014 the file already names each namespace");
@@ -2567,12 +2618,12 @@ ${detail}${more}`);
2567
2618
  }
2568
2619
  return requireStudioHuman(options.configPath, "database import", "database", options.env);
2569
2620
  }
2570
- var import_node_fs11, import_import;
2621
+ var import_node_fs12, import_import;
2571
2622
  var init_app_import = __esm({
2572
2623
  "src/app-import.ts"() {
2573
2624
  "use strict";
2574
2625
  init_cjs_shims();
2575
- import_node_fs11 = require("fs");
2626
+ import_node_fs12 = require("fs");
2576
2627
  import_import = require("@odla-ai/db/import");
2577
2628
  init_config();
2578
2629
  init_human_session();
@@ -2883,15 +2934,15 @@ var init_brand_design_unpack = __esm({
2883
2934
 
2884
2935
  // src/brand-command.ts
2885
2936
  async function readBundle(source, deps) {
2886
- if (source !== "-") return (0, import_promises.readFile)((0, import_node_path8.resolve)(source), "utf8");
2937
+ if (source !== "-") return (0, import_promises.readFile)((0, import_node_path9.resolve)(source), "utf8");
2887
2938
  const readStdin = deps.readStdin;
2888
2939
  if (!readStdin) throw new Error("reading a bundle from stdin is not supported here");
2889
2940
  return readStdin();
2890
2941
  }
2891
2942
  async function writeAll(result, outDir) {
2892
2943
  for (const file of result.files) {
2893
- const target = (0, import_node_path8.resolve)(outDir, file.path);
2894
- await (0, import_promises.mkdir)((0, import_node_path8.dirname)(target), { recursive: true });
2944
+ const target = (0, import_node_path9.resolve)(outDir, file.path);
2945
+ await (0, import_promises.mkdir)((0, import_node_path9.dirname)(target), { recursive: true });
2895
2946
  await (0, import_promises.writeFile)(target, file.bytes);
2896
2947
  }
2897
2948
  }
@@ -2899,7 +2950,7 @@ async function designUnpack(parsed, deps) {
2899
2950
  assertArgs(parsed, ["out", "json"], 4);
2900
2951
  const source = parsed.positionals[3];
2901
2952
  if (!source) throw new Error(USAGE);
2902
- const outDir = (0, import_node_path8.resolve)(stringOpt(parsed.options.out) ?? "design");
2953
+ const outDir = (0, import_node_path9.resolve)(stringOpt(parsed.options.out) ?? "design");
2903
2954
  const result = unpackDesign(await readBundle(source, deps));
2904
2955
  await writeAll(result, outDir);
2905
2956
  const out = deps.stdout ?? console;
@@ -2925,13 +2976,13 @@ async function brandCommand(parsed, deps) {
2925
2976
  }
2926
2977
  throw new Error(USAGE);
2927
2978
  }
2928
- var import_promises, import_node_path8, USAGE;
2979
+ var import_promises, import_node_path9, USAGE;
2929
2980
  var init_brand_command = __esm({
2930
2981
  "src/brand-command.ts"() {
2931
2982
  "use strict";
2932
2983
  init_cjs_shims();
2933
2984
  import_promises = require("fs/promises");
2934
- import_node_path8 = require("path");
2985
+ import_node_path9 = require("path");
2935
2986
  init_argv();
2936
2987
  init_brand_design_unpack();
2937
2988
  USAGE = "usage: odla-ai brand design unpack <bundle.html|-> [--out <dir>] [--json]";
@@ -3569,7 +3620,7 @@ var init_config_reconcile_digest = __esm({
3569
3620
  function readPlan(path) {
3570
3621
  let value2;
3571
3622
  try {
3572
- const raw = (0, import_node_fs12.readFileSync)(path, "utf8");
3623
+ const raw = (0, import_node_fs13.readFileSync)(path, "utf8");
3573
3624
  if (Buffer.byteLength(raw) > 128 * 1024) throw new Error("plan exceeds 128 KiB");
3574
3625
  value2 = JSON.parse(raw);
3575
3626
  } catch (error) {
@@ -3685,13 +3736,13 @@ function invalidPlan(message2) {
3685
3736
  function record3(value2) {
3686
3737
  return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
3687
3738
  }
3688
- var import_apps3, import_node_fs12, DIGEST, REVISION, OPERATION_ID, ACTION_ID, ENV, SERVICE;
3739
+ var import_apps3, import_node_fs13, DIGEST, REVISION, OPERATION_ID, ACTION_ID, ENV, SERVICE;
3689
3740
  var init_config_operation_validate = __esm({
3690
3741
  "src/config-operation-validate.ts"() {
3691
3742
  "use strict";
3692
3743
  init_cjs_shims();
3693
3744
  import_apps3 = require("@odla-ai/apps");
3694
- import_node_fs12 = require("fs");
3745
+ import_node_fs13 = require("fs");
3695
3746
  init_config_operation_error();
3696
3747
  init_config_reconcile_digest();
3697
3748
  DIGEST = /^sha256:[0-9a-f]{64}$/;
@@ -3979,7 +4030,7 @@ async function operationClient(cfg, options, purpose) {
3979
4030
  platform: cfg.platformUrl,
3980
4031
  scope: "app:config:write",
3981
4032
  token: options.token,
3982
- tokenFile: (0, import_node_path9.join)(cfg.rootDir, ".odla", "admin-token.local.json"),
4033
+ tokenFile: (0, import_node_path10.join)(cfg.rootDir, ".odla", "admin-token.local.json"),
3983
4034
  rootDir: cfg.rootDir,
3984
4035
  email: options.email,
3985
4036
  open: options.open,
@@ -4031,13 +4082,13 @@ function normalizeRequestError(error) {
4031
4082
  function record4(value2) {
4032
4083
  return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
4033
4084
  }
4034
- var import_apps6, import_node_path9, IDEMPOTENCY_KEY, DEFAULT_WAIT_SECONDS, DEFAULT_INTERVAL_SECONDS;
4085
+ var import_apps6, import_node_path10, IDEMPOTENCY_KEY, DEFAULT_WAIT_SECONDS, DEFAULT_INTERVAL_SECONDS;
4035
4086
  var init_config_operation_command = __esm({
4036
4087
  "src/config-operation-command.ts"() {
4037
4088
  "use strict";
4038
4089
  init_cjs_shims();
4039
4090
  import_apps6 = require("@odla-ai/apps");
4040
- import_node_path9 = require("path");
4091
+ import_node_path10 = require("path");
4041
4092
  init_admin_ai_auth();
4042
4093
  init_version();
4043
4094
  init_config();
@@ -4360,7 +4411,7 @@ async function inspectConfig(options) {
4360
4411
  platform: cfg.platformUrl,
4361
4412
  scope: "app:config:read",
4362
4413
  token: options.token,
4363
- tokenFile: (0, import_node_path10.join)(cfg.rootDir, ".odla", "admin-token.local.json"),
4414
+ tokenFile: (0, import_node_path11.join)(cfg.rootDir, ".odla", "admin-token.local.json"),
4364
4415
  rootDir: cfg.rootDir,
4365
4416
  email: options.email,
4366
4417
  open: options.open,
@@ -4489,13 +4540,13 @@ function studioSettingsUrl(reconciliation) {
4489
4540
  function quoteArg2(value2) {
4490
4541
  return `'${value2.replace(/'/g, `'\\''`)}'`;
4491
4542
  }
4492
- var import_apps8, import_node_path10;
4543
+ var import_apps8, import_node_path11;
4493
4544
  var init_config_reconcile_command = __esm({
4494
4545
  "src/config-reconcile-command.ts"() {
4495
4546
  "use strict";
4496
4547
  init_cjs_shims();
4497
4548
  import_apps8 = require("@odla-ai/apps");
4498
- import_node_path10 = require("path");
4549
+ import_node_path11 = require("path");
4499
4550
  init_admin_ai_auth();
4500
4551
  init_config();
4501
4552
  init_config_reconcile_digest();
@@ -4508,15 +4559,15 @@ var init_config_reconcile_command = __esm({
4508
4559
  // src/wrangler.ts
4509
4560
  function findWranglerConfig(rootDir) {
4510
4561
  for (const name of WRANGLER_CONFIG_FILES) {
4511
- const path = (0, import_node_path11.join)(rootDir, name);
4512
- if ((0, import_node_fs13.existsSync)(path)) return path;
4562
+ const path = (0, import_node_path12.join)(rootDir, name);
4563
+ if ((0, import_node_fs14.existsSync)(path)) return path;
4513
4564
  }
4514
4565
  return null;
4515
4566
  }
4516
4567
  function readWranglerConfig(path) {
4517
4568
  if (path.endsWith(".toml")) return null;
4518
4569
  try {
4519
- return JSON.parse(stripJsonComments((0, import_node_fs13.readFileSync)(path, "utf8")));
4570
+ return JSON.parse(stripJsonComments((0, import_node_fs14.readFileSync)(path, "utf8")));
4520
4571
  } catch {
4521
4572
  return null;
4522
4573
  }
@@ -4620,14 +4671,14 @@ function wranglerBulkSecrets(run, opts) {
4620
4671
  ];
4621
4672
  return run("npx", args, { input: JSON.stringify(opts.secrets), cwd: opts.cwd });
4622
4673
  }
4623
- var import_node_child_process2, import_node_fs13, import_node_path11, defaultRunner, WRANGLER_CONFIG_FILES;
4674
+ var import_node_child_process2, import_node_fs14, import_node_path12, defaultRunner, WRANGLER_CONFIG_FILES;
4624
4675
  var init_wrangler = __esm({
4625
4676
  "src/wrangler.ts"() {
4626
4677
  "use strict";
4627
4678
  init_cjs_shims();
4628
4679
  import_node_child_process2 = require("child_process");
4629
- import_node_fs13 = require("fs");
4630
- import_node_path11 = require("path");
4680
+ import_node_fs14 = require("fs");
4681
+ import_node_path12 = require("path");
4631
4682
  defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
4632
4683
  const child = (0, import_node_child_process2.spawn)(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
4633
4684
  let stdout = "";
@@ -4686,10 +4737,10 @@ function wranglerWarnings(rootDir) {
4686
4737
  for (const { label, block } of blocks) {
4687
4738
  const assets = block.assets;
4688
4739
  if (assets?.directory) {
4689
- const dir = (0, import_node_path12.resolve)(rootDir, assets.directory);
4690
- if (dir === (0, import_node_path12.resolve)(rootDir)) {
4740
+ const dir = (0, import_node_path13.resolve)(rootDir, assets.directory);
4741
+ if (dir === (0, import_node_path13.resolve)(rootDir)) {
4691
4742
  warnings.push(`${label}assets.directory is the project root \u2014 point it at a dedicated build dir (wrangler dev fails with "spawn EBADF")`);
4692
- } else if ((0, import_node_fs14.existsSync)((0, import_node_path12.join)(dir, "node_modules"))) {
4743
+ } else if ((0, import_node_fs15.existsSync)((0, import_node_path13.join)(dir, "node_modules"))) {
4693
4744
  warnings.push(`${label}assets.directory contains node_modules \u2014 wrangler dev's watcher will exhaust file descriptors`);
4694
4745
  }
4695
4746
  }
@@ -4724,13 +4775,13 @@ function o11yProjectWarnings(rootDir) {
4724
4775
  warnings.push("cannot verify o11y Worker instrumentation \u2014 add a parseable wrangler.jsonc/json config");
4725
4776
  return warnings;
4726
4777
  }
4727
- const main = typeof config.main === "string" ? (0, import_node_path12.resolve)(rootDir, config.main) : null;
4728
- if (!main || !(0, import_node_fs14.existsSync)(main)) {
4778
+ const main = typeof config.main === "string" ? (0, import_node_path13.resolve)(rootDir, config.main) : null;
4779
+ if (!main || !(0, import_node_fs15.existsSync)(main)) {
4729
4780
  warnings.push("cannot verify o11y Worker instrumentation \u2014 wrangler main is missing or unreadable");
4730
4781
  } else {
4731
4782
  let source = "";
4732
4783
  try {
4733
- source = (0, import_node_fs14.readFileSync)(main, "utf8");
4784
+ source = (0, import_node_fs15.readFileSync)(main, "utf8");
4734
4785
  } catch {
4735
4786
  }
4736
4787
  if (!/\bwithObservability\b/.test(source)) {
@@ -4754,19 +4805,19 @@ function calendarProjectWarnings(rootDir) {
4754
4805
  }
4755
4806
  function readPackageJson(rootDir) {
4756
4807
  try {
4757
- return JSON.parse((0, import_node_fs14.readFileSync)((0, import_node_path12.join)(rootDir, "package.json"), "utf8"));
4808
+ return JSON.parse((0, import_node_fs15.readFileSync)((0, import_node_path13.join)(rootDir, "package.json"), "utf8"));
4758
4809
  } catch {
4759
4810
  return null;
4760
4811
  }
4761
4812
  }
4762
- var import_node_child_process3, import_node_fs14, import_node_path12, defaultExec;
4813
+ var import_node_child_process3, import_node_fs15, import_node_path13, defaultExec;
4763
4814
  var init_doctor_checks = __esm({
4764
4815
  "src/doctor-checks.ts"() {
4765
4816
  "use strict";
4766
4817
  init_cjs_shims();
4767
4818
  import_node_child_process3 = require("child_process");
4768
- import_node_fs14 = require("fs");
4769
- import_node_path12 = require("path");
4819
+ import_node_fs15 = require("fs");
4820
+ import_node_path13 = require("path");
4770
4821
  init_redact();
4771
4822
  init_local();
4772
4823
  init_wrangler();
@@ -5103,9 +5154,9 @@ var init_harness_options = __esm({
5103
5154
  // src/init.ts
5104
5155
  function initProject(options) {
5105
5156
  const out = options.stdout ?? console;
5106
- const rootDir = (0, import_node_path13.resolve)(options.rootDir ?? process.cwd());
5107
- const configPath = (0, import_node_path13.resolve)(rootDir, options.configPath ?? "odla.config.mjs");
5108
- if ((0, import_node_fs15.existsSync)(configPath) && !options.force) {
5157
+ const rootDir = (0, import_node_path14.resolve)(options.rootDir ?? process.cwd());
5158
+ const configPath = (0, import_node_path14.resolve)(rootDir, options.configPath ?? "odla.config.mjs");
5159
+ if ((0, import_node_fs16.existsSync)(configPath) && !options.force) {
5109
5160
  throw new Error(`${configPath} already exists. Pass --force to overwrite.`);
5110
5161
  }
5111
5162
  if (!/^[a-z0-9][a-z0-9-]*$/.test(options.appId)) {
@@ -5121,20 +5172,20 @@ function initProject(options) {
5121
5172
  }
5122
5173
  }
5123
5174
  const aiProvider = options.aiProvider;
5124
- (0, import_node_fs15.mkdirSync)((0, import_node_path13.dirname)(configPath), { recursive: true });
5125
- (0, import_node_fs15.mkdirSync)((0, import_node_path13.resolve)(rootDir, "src/odla"), { recursive: true });
5126
- (0, import_node_fs15.mkdirSync)((0, import_node_path13.resolve)(rootDir, ".odla"), { recursive: true });
5127
- (0, import_node_fs15.writeFileSync)(configPath, configTemplate({ appId: options.appId, name: options.name, envs, services, aiProvider }));
5128
- writeIfMissing((0, import_node_path13.resolve)(rootDir, "src/odla/schema.mjs"), schemaTemplate());
5129
- writeIfMissing((0, import_node_path13.resolve)(rootDir, "src/odla/rules.mjs"), rulesTemplate());
5175
+ (0, import_node_fs16.mkdirSync)((0, import_node_path14.dirname)(configPath), { recursive: true });
5176
+ (0, import_node_fs16.mkdirSync)((0, import_node_path14.resolve)(rootDir, "src/odla"), { recursive: true });
5177
+ (0, import_node_fs16.mkdirSync)((0, import_node_path14.resolve)(rootDir, ".odla"), { recursive: true });
5178
+ (0, import_node_fs16.writeFileSync)(configPath, configTemplate({ appId: options.appId, name: options.name, envs, services, aiProvider }));
5179
+ writeIfMissing((0, import_node_path14.resolve)(rootDir, "src/odla/schema.mjs"), schemaTemplate());
5180
+ writeIfMissing((0, import_node_path14.resolve)(rootDir, "src/odla/rules.mjs"), rulesTemplate());
5130
5181
  ensureGitignore(rootDir);
5131
5182
  out.log(`created ${relativeDisplay(configPath, rootDir)}`);
5132
5183
  out.log("created src/odla/schema.mjs and src/odla/rules.mjs");
5133
5184
  out.log("updated .gitignore for local odla credentials");
5134
5185
  }
5135
5186
  function writeIfMissing(path, text3) {
5136
- if ((0, import_node_fs15.existsSync)(path)) return;
5137
- (0, import_node_fs15.writeFileSync)(path, text3);
5187
+ if ((0, import_node_fs16.existsSync)(path)) return;
5188
+ (0, import_node_fs16.writeFileSync)(path, text3);
5138
5189
  }
5139
5190
  function configTemplate(input) {
5140
5191
  const calendar = input.services.includes("calendar") ? ` calendar: {
@@ -5237,13 +5288,13 @@ function defaultKeyEnv(provider) {
5237
5288
  function relativeDisplay(path, rootDir) {
5238
5289
  return path.startsWith(rootDir) ? path.slice(rootDir.length + 1) : path;
5239
5290
  }
5240
- var import_node_fs15, import_node_path13, import_apps9;
5291
+ var import_node_fs16, import_node_path14, import_apps9;
5241
5292
  var init_init = __esm({
5242
5293
  "src/init.ts"() {
5243
5294
  "use strict";
5244
5295
  init_cjs_shims();
5245
- import_node_fs15 = require("fs");
5246
- import_node_path13 = require("path");
5296
+ import_node_fs16 = require("fs");
5297
+ import_node_path14 = require("path");
5247
5298
  import_apps9 = require("@odla-ai/apps");
5248
5299
  init_local();
5249
5300
  }
@@ -5576,8 +5627,8 @@ function installSkill(options = {}) {
5576
5627
  const files = listFiles(sourceDir);
5577
5628
  if (files.length === 0) throw new Error(`no bundled skills found at ${sourceDir}`);
5578
5629
  const harnesses = normalizeHarnesses(options.harnesses, options.global === true);
5579
- const root = (0, import_node_path14.resolve)(options.dir ?? process.cwd());
5580
- const home = (0, import_node_path14.resolve)(options.homeDir ?? (0, import_node_os2.homedir)());
5630
+ const root = (0, import_node_path15.resolve)(options.dir ?? process.cwd());
5631
+ const home = (0, import_node_path15.resolve)(options.homeDir ?? (0, import_node_os3.homedir)());
5581
5632
  const plans = /* @__PURE__ */ new Map();
5582
5633
  const targets = /* @__PURE__ */ new Map();
5583
5634
  const rememberTarget = (harness, target) => {
@@ -5591,48 +5642,48 @@ function installSkill(options = {}) {
5591
5642
  plans.set(target, { target, content: content2, boundary, managedMerge });
5592
5643
  };
5593
5644
  const planSkillTree = (targetDir2, boundary = root) => {
5594
- for (const rel of files) plan((0, import_node_path14.join)(targetDir2, rel), (0, import_node_fs16.readFileSync)((0, import_node_path14.join)(sourceDir, rel), "utf8"), false, boundary);
5645
+ for (const rel of files) plan((0, import_node_path15.join)(targetDir2, rel), (0, import_node_fs17.readFileSync)((0, import_node_path15.join)(sourceDir, rel), "utf8"), false, boundary);
5595
5646
  };
5596
5647
  let targetDir;
5597
5648
  if (options.global) {
5598
- const claudeRoot = (0, import_node_path14.join)(home, ".claude", "skills");
5599
- const codexRoot = (0, import_node_path14.resolve)(options.codexHomeDir ?? process.env.CODEX_HOME ?? (0, import_node_path14.join)(home, ".codex"), "skills");
5649
+ const claudeRoot = (0, import_node_path15.join)(home, ".claude", "skills");
5650
+ const codexRoot = (0, import_node_path15.resolve)(options.codexHomeDir ?? process.env.CODEX_HOME ?? (0, import_node_path15.join)(home, ".codex"), "skills");
5600
5651
  targetDir = harnesses[0] === "codex" ? codexRoot : claudeRoot;
5601
5652
  for (const harness of harnesses) {
5602
5653
  const skillRoot = harness === "claude" ? claudeRoot : codexRoot;
5603
- planSkillTree(skillRoot, harness === "claude" ? home : (0, import_node_path14.dirname)((0, import_node_path14.dirname)(codexRoot)));
5654
+ planSkillTree(skillRoot, harness === "claude" ? home : (0, import_node_path15.dirname)((0, import_node_path15.dirname)(codexRoot)));
5604
5655
  rememberTarget(harness, skillRoot);
5605
5656
  }
5606
5657
  } else {
5607
- const sharedRoot = (0, import_node_path14.join)(root, ".agents", "skills");
5658
+ const sharedRoot = (0, import_node_path15.join)(root, ".agents", "skills");
5608
5659
  planSkillTree(sharedRoot);
5609
- const claudeRoot = (0, import_node_path14.join)(root, ".claude", "skills");
5660
+ const claudeRoot = (0, import_node_path15.join)(root, ".claude", "skills");
5610
5661
  targetDir = harnesses.includes("claude") ? claudeRoot : sharedRoot;
5611
5662
  for (const harness of harnesses) rememberTarget(harness, sharedRoot);
5612
5663
  if (harnesses.includes("claude")) {
5613
5664
  for (const skill of skillNames(files)) {
5614
- const canonical2 = (0, import_node_fs16.readFileSync)((0, import_node_path14.join)(sourceDir, skill, "SKILL.md"), "utf8");
5615
- plan((0, import_node_path14.join)(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical2));
5665
+ const canonical2 = (0, import_node_fs17.readFileSync)((0, import_node_path15.join)(sourceDir, skill, "SKILL.md"), "utf8");
5666
+ plan((0, import_node_path15.join)(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical2));
5616
5667
  }
5617
5668
  rememberTarget("claude", claudeRoot);
5618
5669
  }
5619
5670
  if (harnesses.includes("cursor")) {
5620
- const cursorRule = (0, import_node_path14.join)(root, ".cursor", "rules", "odla.mdc");
5671
+ const cursorRule = (0, import_node_path15.join)(root, ".cursor", "rules", "odla.mdc");
5621
5672
  plan(cursorRule, CURSOR_RULE);
5622
5673
  rememberTarget("cursor", cursorRule);
5623
5674
  }
5624
5675
  if (harnesses.includes("agents")) {
5625
- const agentsFile = (0, import_node_path14.join)(root, "AGENTS.md");
5676
+ const agentsFile = (0, import_node_path15.join)(root, "AGENTS.md");
5626
5677
  plan(agentsFile, managedFileContent(agentsFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
5627
5678
  rememberTarget("agents", agentsFile);
5628
5679
  }
5629
5680
  if (harnesses.includes("copilot")) {
5630
- const copilotFile = (0, import_node_path14.join)(root, ".github", "copilot-instructions.md");
5681
+ const copilotFile = (0, import_node_path15.join)(root, ".github", "copilot-instructions.md");
5631
5682
  plan(copilotFile, managedFileContent(copilotFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
5632
5683
  rememberTarget("copilot", copilotFile);
5633
5684
  }
5634
5685
  if (harnesses.includes("gemini")) {
5635
- const geminiFile = (0, import_node_path14.join)(root, "GEMINI.md");
5686
+ const geminiFile = (0, import_node_path15.join)(root, "GEMINI.md");
5636
5687
  plan(geminiFile, managedFileContent(geminiFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
5637
5688
  rememberTarget("gemini", geminiFile);
5638
5689
  }
@@ -5646,11 +5697,11 @@ function installSkill(options = {}) {
5646
5697
  conflicts.push(`${file.target} (redirected by symbolic link ${symlink})`);
5647
5698
  continue;
5648
5699
  }
5649
- if (!(0, import_node_fs16.existsSync)(file.target)) {
5700
+ if (!(0, import_node_fs17.existsSync)(file.target)) {
5650
5701
  writtenPaths.add(file.target);
5651
5702
  continue;
5652
5703
  }
5653
- const current = (0, import_node_fs16.readFileSync)(file.target, "utf8");
5704
+ const current = (0, import_node_fs17.readFileSync)(file.target, "utf8");
5654
5705
  if (current === file.content) {
5655
5706
  unchangedPaths.add(file.target);
5656
5707
  } else if (file.managedMerge || options.force) {
@@ -5667,9 +5718,9 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
5667
5718
  );
5668
5719
  }
5669
5720
  for (const file of plans.values()) {
5670
- if (!(0, import_node_fs16.existsSync)(file.target) || (0, import_node_fs16.readFileSync)(file.target, "utf8") !== file.content) {
5671
- (0, import_node_fs16.mkdirSync)((0, import_node_path14.dirname)(file.target), { recursive: true });
5672
- (0, import_node_fs16.writeFileSync)(file.target, file.content);
5721
+ if (!(0, import_node_fs17.existsSync)(file.target) || (0, import_node_fs17.readFileSync)(file.target, "utf8") !== file.content) {
5722
+ (0, import_node_fs17.mkdirSync)((0, import_node_path15.dirname)(file.target), { recursive: true });
5723
+ (0, import_node_fs17.writeFileSync)(file.target, file.content);
5673
5724
  }
5674
5725
  }
5675
5726
  const skills = skillNames(files);
@@ -5688,7 +5739,7 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
5688
5739
  };
5689
5740
  }
5690
5741
  function pathsUnder(root, paths) {
5691
- return [...paths].map((path) => (0, import_node_path14.relative)(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${import_node_path14.sep}`) && !(0, import_node_path14.isAbsolute)(path)).sort();
5742
+ return [...paths].map((path) => (0, import_node_path15.relative)(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${import_node_path15.sep}`) && !(0, import_node_path15.isAbsolute)(path)).sort();
5692
5743
  }
5693
5744
  function normalizeHarnesses(values, global) {
5694
5745
  const requested = values?.length ? values : ["claude"];
@@ -5710,9 +5761,9 @@ function normalizeHarnesses(values, global) {
5710
5761
  function managedFileContent(path, block, force, boundary) {
5711
5762
  const symlink = symlinkedComponent(boundary, path);
5712
5763
  if (symlink) throw new Error(`refusing to manage ${path}: symbolic link component ${symlink}`);
5713
- if (!(0, import_node_fs16.existsSync)(path)) return `${block}
5764
+ if (!(0, import_node_fs17.existsSync)(path)) return `${block}
5714
5765
  `;
5715
- const current = (0, import_node_fs16.readFileSync)(path, "utf8");
5766
+ const current = (0, import_node_fs17.readFileSync)(path, "utf8");
5716
5767
  const start = "<!-- odla-ai agent setup:start -->";
5717
5768
  const end = "<!-- odla-ai agent setup:end -->";
5718
5769
  const startAt = current.indexOf(start);
@@ -5733,15 +5784,15 @@ function managedFileContent(path, block, force, boundary) {
5733
5784
  return `${current.slice(0, startAt)}${block}${current.slice(afterEnd)}`;
5734
5785
  }
5735
5786
  function symlinkedComponent(boundary, target) {
5736
- const rel = (0, import_node_path14.relative)(boundary, target);
5737
- if (rel === ".." || rel.startsWith(`..${import_node_path14.sep}`) || (0, import_node_path14.isAbsolute)(rel)) {
5787
+ const rel = (0, import_node_path15.relative)(boundary, target);
5788
+ if (rel === ".." || rel.startsWith(`..${import_node_path15.sep}`) || (0, import_node_path15.isAbsolute)(rel)) {
5738
5789
  throw new Error(`agent setup target escapes its install root: ${target}`);
5739
5790
  }
5740
5791
  let current = boundary;
5741
- for (const part of rel.split(import_node_path14.sep).filter(Boolean)) {
5742
- current = (0, import_node_path14.join)(current, part);
5792
+ for (const part of rel.split(import_node_path15.sep).filter(Boolean)) {
5793
+ current = (0, import_node_path15.join)(current, part);
5743
5794
  try {
5744
- if ((0, import_node_fs16.lstatSync)(current).isSymbolicLink()) return current;
5795
+ if ((0, import_node_fs17.lstatSync)(current).isSymbolicLink()) return current;
5745
5796
  } catch (error) {
5746
5797
  if (error.code !== "ENOENT") throw error;
5747
5798
  }
@@ -5752,26 +5803,26 @@ function skillNames(files) {
5752
5803
  return [...new Set(files.filter((file) => /(^|[\\/])SKILL\.md$/.test(file)).map((file) => file.split(/[\\/]/)[0]))].sort();
5753
5804
  }
5754
5805
  function listFiles(dir) {
5755
- if (!(0, import_node_fs16.existsSync)(dir)) return [];
5806
+ if (!(0, import_node_fs17.existsSync)(dir)) return [];
5756
5807
  const results = [];
5757
5808
  const walk = (current) => {
5758
- for (const entry of (0, import_node_fs16.readdirSync)(current, { withFileTypes: true })) {
5759
- const path = (0, import_node_path14.join)(current, entry.name);
5809
+ for (const entry of (0, import_node_fs17.readdirSync)(current, { withFileTypes: true })) {
5810
+ const path = (0, import_node_path15.join)(current, entry.name);
5760
5811
  if (entry.isDirectory()) walk(path);
5761
- else results.push((0, import_node_path14.relative)(dir, path));
5812
+ else results.push((0, import_node_path15.relative)(dir, path));
5762
5813
  }
5763
5814
  };
5764
5815
  walk(dir);
5765
5816
  return results.sort();
5766
5817
  }
5767
- var import_node_fs16, import_node_os2, import_node_path14, import_node_url2, AGENT_HARNESSES;
5818
+ var import_node_fs17, import_node_os3, import_node_path15, import_node_url2, AGENT_HARNESSES;
5768
5819
  var init_skill = __esm({
5769
5820
  "src/skill.ts"() {
5770
5821
  "use strict";
5771
5822
  init_cjs_shims();
5772
- import_node_fs16 = require("fs");
5773
- import_node_os2 = require("os");
5774
- import_node_path14 = require("path");
5823
+ import_node_fs17 = require("fs");
5824
+ import_node_os3 = require("os");
5825
+ import_node_path15 = require("path");
5775
5826
  import_node_url2 = require("url");
5776
5827
  init_skill_adapters();
5777
5828
  AGENT_HARNESSES = ["claude", "codex", "cursor", "copilot", "gemini", "agents"];
@@ -7322,8 +7373,8 @@ function rollup(graph, kind, options = {}) {
7322
7373
  for (const node of nodesOfKind(graph, kind)) {
7323
7374
  if (options.prefix && !node.name.startsWith(options.prefix)) continue;
7324
7375
  const key = node.name.split(separator).slice(0, depth).join(separator);
7325
- const list2 = groups.get(key);
7326
- if (list2) list2.push(node);
7376
+ const list3 = groups.get(key);
7377
+ if (list3) list3.push(node);
7327
7378
  else groups.set(key, [node]);
7328
7379
  }
7329
7380
  return [...groups].map(([prefix, nodes]) => ({
@@ -7348,7 +7399,7 @@ function dirname9(path) {
7348
7399
  const at = path.lastIndexOf("/");
7349
7400
  return at <= 0 ? "." : path.slice(0, at);
7350
7401
  }
7351
- function join12(base, specifier) {
7402
+ function join13(base, specifier) {
7352
7403
  const parts = [];
7353
7404
  const segments = `${base === "." ? "" : `${base}/`}${specifier}`.split("/");
7354
7405
  for (const segment of segments) {
@@ -7360,7 +7411,7 @@ function join12(base, specifier) {
7360
7411
  }
7361
7412
  function resolveImport(fromPath, specifier, known) {
7362
7413
  if (!specifier.startsWith(".")) return null;
7363
- const base = join12(dirname9(fromPath), specifier);
7414
+ const base = join13(dirname9(fromPath), specifier);
7364
7415
  const candidates = [
7365
7416
  base,
7366
7417
  base.replace(/\.js$/, ".ts"),
@@ -10286,8 +10337,8 @@ var init_code_runtime_config = __esm({
10286
10337
  // src/code-connect.ts
10287
10338
  async function codeConnect(options) {
10288
10339
  const cwd = options.cwd ?? process.cwd();
10289
- const configPath = (0, import_node_path15.resolve)(cwd, options.configPath);
10290
- const cfg = (0, import_node_fs17.existsSync)(configPath) ? await loadProjectConfig(configPath) : null;
10340
+ const configPath = (0, import_node_path16.resolve)(cwd, options.configPath);
10341
+ const cfg = (0, import_node_fs18.existsSync)(configPath) ? await loadProjectConfig(configPath) : null;
10291
10342
  const requestedAppId = options.appId?.trim();
10292
10343
  if (requestedAppId && !/^[a-z0-9][a-z0-9-]{1,62}$/.test(requestedAppId)) {
10293
10344
  throw new Error("--app-id must be a valid odla app id");
@@ -10316,7 +10367,7 @@ async function codeConnect(options) {
10316
10367
  const doFetch = options.fetch ?? fetch;
10317
10368
  const engine = await (options.selectEngine ?? selectContainerEngine)(options.engine ?? "auto");
10318
10369
  const hostPlatform = process.platform === "darwin" ? "macos" : "linux";
10319
- const hostName = (options.name ?? (0, import_node_os3.hostname)()).trim();
10370
+ const hostName = (options.name ?? (0, import_node_os4.hostname)()).trim();
10320
10371
  if (!hostName || hostName.length > 120) throw new Error("--name must contain 1 to 120 characters");
10321
10372
  const repository = await inferGitHubRepository(cwd, options.readGitOrigin);
10322
10373
  const localSource = await (options.prepareLocalSource ?? prepareCodeLocalSource)(
@@ -10353,8 +10404,8 @@ async function codeConnect(options) {
10353
10404
  platform: hostPlatform,
10354
10405
  arch: process.arch,
10355
10406
  engines: [engine],
10356
- cpuCount: (0, import_node_os3.cpus)().length,
10357
- memoryBytes: (0, import_node_os3.totalmem)(),
10407
+ cpuCount: (0, import_node_os4.cpus)().length,
10408
+ memoryBytes: (0, import_node_os4.totalmem)(),
10358
10409
  source: descriptor2,
10359
10410
  images: {
10360
10411
  ready: true,
@@ -10451,14 +10502,14 @@ function apiFailure(action2, status, value2) {
10451
10502
  function record6(value2) {
10452
10503
  return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
10453
10504
  }
10454
- var import_node_fs17, import_node_os3, import_node_path15;
10505
+ var import_node_fs18, import_node_os4, import_node_path16;
10455
10506
  var init_code_connect = __esm({
10456
10507
  "src/code-connect.ts"() {
10457
10508
  "use strict";
10458
10509
  init_cjs_shims();
10459
- import_node_fs17 = require("fs");
10460
- import_node_os3 = require("os");
10461
- import_node_path15 = require("path");
10510
+ import_node_fs18 = require("fs");
10511
+ import_node_os4 = require("os");
10512
+ import_node_path16 = require("path");
10462
10513
  init_node();
10463
10514
  init_admin_ai_auth();
10464
10515
  init_config();
@@ -10775,7 +10826,7 @@ function developerTokenStatus(context, parsed, now = Date.now()) {
10775
10826
  const cacheStatus = !cached?.token ? "missing" : cached.platform !== context.platform.value ? "other-platform" : (cached.expiresAt ?? 0) <= now + 6e4 ? "expired" : "valid";
10776
10827
  const source = clean3(
10777
10828
  stringOpt(parsed.options.token)
10778
- ) ? "flag" : clean3(import_node_process11.default.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
10829
+ ) ? "flag" : clean3(import_node_process12.default.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
10779
10830
  return {
10780
10831
  source,
10781
10832
  cacheFile: context.cfg.local.tokenFile,
@@ -10786,12 +10837,12 @@ function clean3(value2) {
10786
10837
  const normalized = value2?.trim();
10787
10838
  return normalized || void 0;
10788
10839
  }
10789
- var import_node_process11;
10840
+ var import_node_process12;
10790
10841
  var init_operator_credentials = __esm({
10791
10842
  "src/operator-credentials.ts"() {
10792
10843
  "use strict";
10793
10844
  init_cjs_shims();
10794
- import_node_process11 = __toESM(require("process"), 1);
10845
+ import_node_process12 = __toESM(require("process"), 1);
10795
10846
  init_argv();
10796
10847
  init_local();
10797
10848
  }
@@ -11132,6 +11183,9 @@ Usage:
11132
11183
  odla-ai security run [target] --ack-redacted-source [--env dev] [--profile odla] [--fail-on high]
11133
11184
  odla-ai security run [target] --self --ack-redacted-source
11134
11185
  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]
11186
+ odla-ai device enroll [--app <id>[,<id>...]] [--name <label>] [--capability <c>[,<c>...]] [--email <odla-account>] [--no-open] [--json]
11187
+ odla-ai device list [--email <odla-account>] [--json]
11188
+ odla-ai device revoke <device-id> [--email <odla-account>] [--json]
11135
11189
  odla-ai credentials list [--config odla.config.mjs] [--env dev] [--all] [--json]
11136
11190
  odla-ai credentials revoke <receipt-id> [--config odla.config.mjs] [--json]
11137
11191
  odla-ai smoke [--config odla.config.mjs] [--env dev] [--runtime] [--email <odla-account>] [--no-open]
@@ -11239,6 +11293,10 @@ Commands:
11239
11293
  stable status, incident, and report JSON to agents and CI.
11240
11294
  platform Read canonical fleet health, releases, provider load/freshness,
11241
11295
  explicit unknowns, and next actions through a read-only grant.
11296
+ device Enrol THIS machine once, then stop asking. A human approves the
11297
+ enrollment in the browser; from then on this terminal mints its
11298
+ own short-lived credentials for the named projects with nobody's
11299
+ attention, until the device expires or is revoked.
11242
11300
  provision Register services, compose integrations, persist credentials, optionally push secrets.
11243
11301
  "provision --live --yes" initializes only the live instance of
11244
11302
  an existing sandbox app and enables every configured service;
@@ -12613,14 +12671,14 @@ function readPmProjectContext(rootDir) {
12613
12671
  function writePmProjectContext(rootDir, value2) {
12614
12672
  writePrivateJson(pmProjectContextFile(rootDir), { ...value2, selectedAt: (/* @__PURE__ */ new Date()).toISOString() });
12615
12673
  }
12616
- var import_node_path16, pmProjectContextFile;
12674
+ var import_node_path17, pmProjectContextFile;
12617
12675
  var init_pm_project_context = __esm({
12618
12676
  "src/pm-project-context.ts"() {
12619
12677
  "use strict";
12620
12678
  init_cjs_shims();
12621
- import_node_path16 = require("path");
12679
+ import_node_path17 = require("path");
12622
12680
  init_local();
12623
- pmProjectContextFile = (rootDir) => (0, import_node_path16.resolve)(rootDir, ".odla", "pm-project.local.json");
12681
+ pmProjectContextFile = (rootDir) => (0, import_node_path17.resolve)(rootDir, ".odla", "pm-project.local.json");
12624
12682
  }
12625
12683
  });
12626
12684
 
@@ -14192,7 +14250,7 @@ async function provision(options) {
14192
14250
  await provisionIntegrationSeeds(doFetch, cfg.dbEndpoint, tenantId, dbKey, database.integrations, env, out);
14193
14251
  }
14194
14252
  if (cfg.services.includes("ai") && cfg.ai?.provider && cfg.ai.keyEnv) {
14195
- const key = import_node_process12.default.env[cfg.ai.keyEnv];
14253
+ const key = import_node_process13.default.env[cfg.ai.keyEnv];
14196
14254
  if (key) {
14197
14255
  const secretName = cfg.ai.secretName ?? defaultSecretName(cfg.ai.provider);
14198
14256
  await (0, import_ai5.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, secretName, key);
@@ -14231,14 +14289,14 @@ async function provision(options) {
14231
14289
  }
14232
14290
  }
14233
14291
  }
14234
- var import_apps13, import_ai5, import_node_process12;
14292
+ var import_apps13, import_ai5, import_node_process13;
14235
14293
  var init_provision = __esm({
14236
14294
  "src/provision.ts"() {
14237
14295
  "use strict";
14238
14296
  init_cjs_shims();
14239
14297
  import_apps13 = require("@odla-ai/apps");
14240
14298
  import_ai5 = require("@odla-ai/ai");
14241
- import_node_process12 = __toESM(require("process"), 1);
14299
+ import_node_process13 = __toESM(require("process"), 1);
14242
14300
  init_config();
14243
14301
  init_calendar();
14244
14302
  init_calendar_errors();
@@ -14364,6 +14422,7 @@ var init_surface = __esm({
14364
14422
  config: { diff: {}, plan: {}, apply: {} },
14365
14423
  context: { show: {}, list: {}, save: {}, remove: {} },
14366
14424
  credentials: { list: {}, revoke: {} },
14425
+ device: { enroll: {}, list: {}, revoke: {} },
14367
14426
  // `watch`, `read`, `reply`, and `resolve` take a topic id from there on.
14368
14427
  discuss: {
14369
14428
  groups: {},
@@ -14432,7 +14491,7 @@ var init_surface = __esm({
14432
14491
 
14433
14492
  // src/record.ts
14434
14493
  function recordInvocation(parsed) {
14435
- const file = import_node_process13.default.env.ODLA_CLI_RECORD;
14494
+ const file = import_node_process14.default.env.ODLA_CLI_RECORD;
14436
14495
  if (!file) return;
14437
14496
  try {
14438
14497
  const entry = {
@@ -14440,18 +14499,18 @@ function recordInvocation(parsed) {
14440
14499
  options: Object.entries(parsed.options).map(([name, value2]) => value2 === false ? `no-${name}` : name).sort()
14441
14500
  };
14442
14501
  if (!entry.path.length) return;
14443
- (0, import_node_fs18.appendFileSync)(file, `${JSON.stringify(entry)}
14502
+ (0, import_node_fs19.appendFileSync)(file, `${JSON.stringify(entry)}
14444
14503
  `);
14445
14504
  } catch {
14446
14505
  }
14447
14506
  }
14448
- var import_node_fs18, import_node_process13;
14507
+ var import_node_fs19, import_node_process14;
14449
14508
  var init_record = __esm({
14450
14509
  "src/record.ts"() {
14451
14510
  "use strict";
14452
14511
  init_cjs_shims();
14453
- import_node_fs18 = require("fs");
14454
- import_node_process13 = __toESM(require("process"), 1);
14512
+ import_node_fs19 = require("fs");
14513
+ import_node_process14 = __toESM(require("process"), 1);
14455
14514
  init_surface();
14456
14515
  }
14457
14516
  });
@@ -14486,6 +14545,119 @@ var init_advisory_output = __esm({
14486
14545
  }
14487
14546
  });
14488
14547
 
14548
+ // src/device-command.ts
14549
+ async function deviceCommand(parsed, deps) {
14550
+ const action2 = parsed.positionals[1] ?? "";
14551
+ const out = deps.stdout ?? console;
14552
+ const doFetch = deps.fetch ?? fetch;
14553
+ const cfg = await loadProjectConfig(stringOpt(parsed.options.config));
14554
+ const json = parsed.options.json === true;
14555
+ if (action2 === "enroll") return enroll(parsed, deps, cfg, doFetch, out, json);
14556
+ if (action2 === "list") return list2(parsed, deps, cfg, doFetch, out, json);
14557
+ if (action2 === "revoke") return revoke(parsed, deps, cfg, doFetch, out, json);
14558
+ throw new Error('odla-ai device expects "enroll", "list", or "revoke"');
14559
+ }
14560
+ async function enroll(parsed, deps, cfg, doFetch, out, json) {
14561
+ const name = stringOpt(parsed.options.name) ?? defaultDeviceName();
14562
+ const apps = (stringOpt(parsed.options.app) ?? cfg.app.id).split(",").map((id2) => id2.trim()).filter(Boolean);
14563
+ if (apps.length === 0) throw new Error("device enroll needs --app <id>[,<id>\u2026]");
14564
+ const token = await scopedToken2(parsed, deps, cfg, doFetch, out, `odla CLI (enroll ${name})`);
14565
+ const response2 = await doFetch(`${cfg.platformUrl}/registry/devices`, {
14566
+ method: "POST",
14567
+ headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
14568
+ body: JSON.stringify({
14569
+ name,
14570
+ platform: import_node_process15.default.platform,
14571
+ appIds: apps,
14572
+ ...parsed.options.capability ? { capabilities: String(parsed.options.capability).split(",").map((c) => c.trim()).filter(Boolean) } : {}
14573
+ })
14574
+ });
14575
+ const body = await response2.json().catch(() => ({}));
14576
+ if (!response2.ok || !body.token || !body.device) {
14577
+ throw new Error(`device enroll failed: ${body.error?.message ?? `registry returned ${response2.status}`} (${response2.status})`);
14578
+ }
14579
+ const path = deviceCredentialPath();
14580
+ (0, import_node_fs20.mkdirSync)((0, import_node_path18.dirname)(path), { recursive: true });
14581
+ (0, import_node_fs20.writeFileSync)(path, JSON.stringify({
14582
+ token: body.token,
14583
+ platform: cfg.platformUrl.replace(/\/$/, ""),
14584
+ deviceId: body.device.deviceId,
14585
+ name
14586
+ }, null, 2));
14587
+ (0, import_node_fs20.chmodSync)(path, 384);
14588
+ out.error(`device: enrolled "${name}" for ${body.device.appIds.join(", ")}; credential written to ${path}`);
14589
+ out.error("device: this terminal will mint its own credentials from now on \u2014 no further approvals.");
14590
+ if (json) {
14591
+ out.log(JSON.stringify({ deviceId: body.device.deviceId, name, appIds: body.device.appIds, expiresAt: body.device.expiresAt }, null, 2));
14592
+ }
14593
+ }
14594
+ async function list2(parsed, deps, cfg, doFetch, out, json) {
14595
+ const token = await scopedToken2(parsed, deps, cfg, doFetch, out, "odla CLI (device list)");
14596
+ const response2 = await doFetch(`${cfg.platformUrl}/registry/devices`, {
14597
+ headers: { authorization: `Bearer ${token}` }
14598
+ });
14599
+ const body = await response2.json().catch(() => ({}));
14600
+ if (!response2.ok || !body.devices) {
14601
+ throw new Error(`device list failed: ${body.error?.message ?? `registry returned ${response2.status}`} (${response2.status})`);
14602
+ }
14603
+ if (json) return out.log(JSON.stringify(body.devices, null, 2));
14604
+ if (body.devices.length === 0) return out.log("no enrolled devices");
14605
+ for (const device of body.devices) {
14606
+ const state2 = device.revokedAt ? "revoked" : device.expiresAt <= Date.now() ? "expired" : "active";
14607
+ out.log(`${device.deviceId} ${state2.padEnd(7)} ${device.name} [${device.appIds.join(", ")}]`);
14608
+ }
14609
+ }
14610
+ async function revoke(parsed, deps, cfg, doFetch, out, json) {
14611
+ const deviceId = parsed.positionals[2];
14612
+ if (!deviceId) throw new Error("device revoke needs the device id from `odla-ai device list`");
14613
+ const token = await scopedToken2(parsed, deps, cfg, doFetch, out, "odla CLI (device revoke)");
14614
+ const response2 = await doFetch(`${cfg.platformUrl}/registry/devices/${encodeURIComponent(deviceId)}/revoke`, {
14615
+ method: "POST",
14616
+ headers: { authorization: `Bearer ${token}` }
14617
+ });
14618
+ if (!response2.ok) {
14619
+ const body = await response2.json().catch(() => ({}));
14620
+ throw new Error(`device revoke failed: ${body.error?.message ?? `registry returned ${response2.status}`} (${response2.status})`);
14621
+ }
14622
+ out.error(`device: revoked ${deviceId}; every credential it minted is revoked with it`);
14623
+ if (json) out.log(JSON.stringify({ deviceId, revoked: true }, null, 2));
14624
+ }
14625
+ async function scopedToken2(parsed, deps, cfg, doFetch, out, label) {
14626
+ const { credentials } = await resolveOperatorContext(parsed, { allowMissingConfig: true });
14627
+ const scopedTokenFile = credentials.scopedTokenFile;
14628
+ return getScopedPlatformToken({
14629
+ platform: cfg.platformUrl,
14630
+ scope: "app:device:enroll",
14631
+ email: stringOpt(parsed.options.email),
14632
+ label,
14633
+ fetch: doFetch,
14634
+ stdout: out,
14635
+ open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
14636
+ openApprovalUrl: deps.openUrl,
14637
+ rootDir: cfg.rootDir,
14638
+ tokenFile: scopedTokenFile,
14639
+ ...stringOpt(parsed.options.token) ? { token: stringOpt(parsed.options.token) } : {}
14640
+ });
14641
+ }
14642
+ function defaultDeviceName() {
14643
+ return `${import_node_process15.default.env.HOSTNAME ?? import_node_process15.default.env.HOST ?? "machine"}-${import_node_process15.default.platform}`;
14644
+ }
14645
+ var import_node_fs20, import_node_path18, import_node_process15;
14646
+ var init_device_command = __esm({
14647
+ "src/device-command.ts"() {
14648
+ "use strict";
14649
+ init_cjs_shims();
14650
+ import_node_fs20 = require("fs");
14651
+ import_node_path18 = require("path");
14652
+ import_node_process15 = __toESM(require("process"), 1);
14653
+ init_argv();
14654
+ init_admin_ai_auth();
14655
+ init_device_session();
14656
+ init_config();
14657
+ init_operator_context();
14658
+ }
14659
+ });
14660
+
14489
14661
  // src/runbook-actions.ts
14490
14662
  async function call(ctx, method, path, body) {
14491
14663
  const res = await ctx.doFetch(`${ctx.platformUrl.replace(/\/$/, "")}/registry/pm${path}`, {
@@ -14532,7 +14704,7 @@ async function bySlug(ctx, slug) {
14532
14704
  function readBody(file, inline) {
14533
14705
  if (inline !== void 0) return inline;
14534
14706
  if (file === void 0) throw new Error("supply the new text with --file <path>, --file - (stdin), or --body");
14535
- return (0, import_node_fs19.readFileSync)(file === "-" ? 0 : file, "utf8");
14707
+ return (0, import_node_fs21.readFileSync)(file === "-" ? 0 : file, "utf8");
14536
14708
  }
14537
14709
  async function runbookList(ctx, all, query) {
14538
14710
  const params = new URLSearchParams();
@@ -14621,12 +14793,12 @@ async function runbookRemove(ctx, slug) {
14621
14793
  await call(ctx, "DELETE", `/runbook/${encodeURIComponent(runbook.id)}`);
14622
14794
  ctx.out.log(`removed ${slug}`);
14623
14795
  }
14624
- var import_node_fs19, PLATFORM_SCOPE, stamp;
14796
+ var import_node_fs21, PLATFORM_SCOPE, stamp;
14625
14797
  var init_runbook_actions = __esm({
14626
14798
  "src/runbook-actions.ts"() {
14627
14799
  "use strict";
14628
14800
  init_cjs_shims();
14629
- import_node_fs19 = require("fs");
14801
+ import_node_fs21 = require("fs");
14630
14802
  init_version();
14631
14803
  init_runbook_requires();
14632
14804
  PLATFORM_SCOPE = "$platform";
@@ -14659,12 +14831,12 @@ function parseRunbook(text3, slug) {
14659
14831
  };
14660
14832
  }
14661
14833
  function readRunbookDir(dir) {
14662
- if (!(0, import_node_fs20.statSync)(dir, { throwIfNoEntry: false })?.isDirectory()) throw new Error(`not a directory: ${dir}`);
14663
- const files = (0, import_node_fs20.readdirSync)(dir).filter((f) => f.endsWith(".md")).sort();
14834
+ if (!(0, import_node_fs22.statSync)(dir, { throwIfNoEntry: false })?.isDirectory()) throw new Error(`not a directory: ${dir}`);
14835
+ const files = (0, import_node_fs22.readdirSync)(dir).filter((f) => f.endsWith(".md")).sort();
14664
14836
  if (!files.length) throw new Error(`no .md files in ${dir}`);
14665
14837
  return files.map((file) => {
14666
- const slug = (0, import_node_path17.basename)(file, ".md");
14667
- const parsed = parseRunbook((0, import_node_fs20.readFileSync)((0, import_node_path17.join)(dir, file), "utf8"), slug);
14838
+ const slug = (0, import_node_path19.basename)(file, ".md");
14839
+ const parsed = parseRunbook((0, import_node_fs22.readFileSync)((0, import_node_path19.join)(dir, file), "utf8"), slug);
14668
14840
  return { file, slug, ...parsed, words: parsed.body.split(/\s+/).filter(Boolean).length };
14669
14841
  });
14670
14842
  }
@@ -14734,13 +14906,13 @@ async function upsert(ctx, r, visibility) {
14734
14906
  );
14735
14907
  return "updated";
14736
14908
  }
14737
- var import_node_fs20, import_node_path17;
14909
+ var import_node_fs22, import_node_path19;
14738
14910
  var init_runbook_import = __esm({
14739
14911
  "src/runbook-import.ts"() {
14740
14912
  "use strict";
14741
14913
  init_cjs_shims();
14742
- import_node_fs20 = require("fs");
14743
- import_node_path17 = require("path");
14914
+ import_node_fs22 = require("fs");
14915
+ import_node_path19 = require("path");
14744
14916
  init_runbook_actions();
14745
14917
  }
14746
14918
  });
@@ -14918,10 +15090,10 @@ ${body.split("\n").map((line2) => `+${line2}`).join("\n")}
14918
15090
  }
14919
15091
  function manifestLabeller(root) {
14920
15092
  return (workspace) => {
14921
- const manifest = (0, import_node_path18.join)(root, workspace, "package.json");
14922
- if (!(0, import_node_fs21.existsSync)(manifest)) return void 0;
15093
+ const manifest = (0, import_node_path20.join)(root, workspace, "package.json");
15094
+ if (!(0, import_node_fs23.existsSync)(manifest)) return void 0;
14923
15095
  try {
14924
- const name = JSON.parse((0, import_node_fs21.readFileSync)(manifest, "utf8")).name;
15096
+ const name = JSON.parse((0, import_node_fs23.readFileSync)(manifest, "utf8")).name;
14925
15097
  return typeof name === "string" ? name : void 0;
14926
15098
  } catch {
14927
15099
  return void 0;
@@ -14987,7 +15159,7 @@ function report4(ctx, impacts) {
14987
15159
  async function runbookImpact(ctx, options, deps = {}) {
14988
15160
  const cwd = deps.cwd ?? process.cwd();
14989
15161
  const runGit = deps.runGit ?? gitRunner(cwd);
14990
- const read3 = deps.readRepoFile ?? ((path) => (0, import_node_fs21.readFileSync)((0, import_node_path18.join)(cwd, path), "utf8"));
15162
+ const read3 = deps.readRepoFile ?? ((path) => (0, import_node_fs23.readFileSync)((0, import_node_path20.join)(cwd, path), "utf8"));
14991
15163
  const surfaces = changedSurfaces(collectDiff(runGit, options.base, read3), manifestLabeller(cwd));
14992
15164
  if (!surfaces.length) {
14993
15165
  return ctx.out.log(
@@ -14998,14 +15170,14 @@ async function runbookImpact(ctx, options, deps = {}) {
14998
15170
  if (ctx.json) return ctx.out.log(JSON.stringify({ base: options.base, impacts }, null, 2));
14999
15171
  report4(ctx, impacts);
15000
15172
  }
15001
- var import_node_child_process6, import_node_fs21, import_node_path18, SOURCE3, editHint;
15173
+ var import_node_child_process6, import_node_fs23, import_node_path20, SOURCE3, editHint;
15002
15174
  var init_runbook_impact = __esm({
15003
15175
  "src/runbook-impact.ts"() {
15004
15176
  "use strict";
15005
15177
  init_cjs_shims();
15006
15178
  import_node_child_process6 = require("child_process");
15007
- import_node_fs21 = require("fs");
15008
- import_node_path18 = require("path");
15179
+ import_node_fs23 = require("fs");
15180
+ import_node_path20 = require("path");
15009
15181
  init_runbook_impact_scan();
15010
15182
  init_runbook_actions();
15011
15183
  SOURCE3 = /\.(ts|tsx|js|jsx|mts|cts)$/;
@@ -15151,7 +15323,7 @@ var init_runbook_search_command = __esm({
15151
15323
  });
15152
15324
 
15153
15325
  // src/runbook-editor.ts
15154
- function resolveEditor(env = import_node_process14.default.env) {
15326
+ function resolveEditor(env = import_node_process16.default.env) {
15155
15327
  for (const name of EDITOR_ENV) {
15156
15328
  const value2 = env[name];
15157
15329
  if (value2 && value2.trim()) return value2.trim();
@@ -15165,8 +15337,8 @@ function defaultRun(command, path) {
15165
15337
  return result.status ?? 0;
15166
15338
  }
15167
15339
  function editText(initial, slug, deps = {}) {
15168
- const env = deps.env ?? import_node_process14.default.env;
15169
- const interactive = deps.interactive ?? (() => Boolean(import_node_process14.default.stdin.isTTY));
15340
+ const env = deps.env ?? import_node_process16.default.env;
15341
+ const interactive = deps.interactive ?? (() => Boolean(import_node_process16.default.stdin.isTTY));
15170
15342
  const editor = resolveEditor(env);
15171
15343
  if (!editor)
15172
15344
  throw new Error(
@@ -15174,28 +15346,28 @@ function editText(initial, slug, deps = {}) {
15174
15346
  );
15175
15347
  if (!interactive())
15176
15348
  throw new Error(`cannot open an editor without a terminal \u2014 pass --file <path> or --body "\u2026" instead`);
15177
- const dir = (0, import_node_fs22.mkdtempSync)((0, import_node_path19.join)((0, import_node_os4.tmpdir)(), "odla-runbook-"));
15178
- const file = (0, import_node_path19.join)(dir, `${slug}.md`);
15349
+ const dir = (0, import_node_fs24.mkdtempSync)((0, import_node_path21.join)((0, import_node_os5.tmpdir)(), "odla-runbook-"));
15350
+ const file = (0, import_node_path21.join)(dir, `${slug}.md`);
15179
15351
  try {
15180
- (0, import_node_fs22.writeFileSync)(file, initial, { mode: 384 });
15352
+ (0, import_node_fs24.writeFileSync)(file, initial, { mode: 384 });
15181
15353
  const code = defaultRunOrInjected(deps)(editor, file);
15182
15354
  if (code !== 0) throw new Error(`editor "${editor}" exited with ${code}; nothing was written`);
15183
- const edited = (0, import_node_fs22.readFileSync)(file, "utf8");
15355
+ const edited = (0, import_node_fs24.readFileSync)(file, "utf8");
15184
15356
  return edited === initial ? null : edited;
15185
15357
  } finally {
15186
- (0, import_node_fs22.rmSync)(dir, { recursive: true, force: true });
15358
+ (0, import_node_fs24.rmSync)(dir, { recursive: true, force: true });
15187
15359
  }
15188
15360
  }
15189
- var import_node_child_process7, import_node_fs22, import_node_os4, import_node_path19, import_node_process14, EDITOR_ENV, defaultRunOrInjected;
15361
+ var import_node_child_process7, import_node_fs24, import_node_os5, import_node_path21, import_node_process16, EDITOR_ENV, defaultRunOrInjected;
15190
15362
  var init_runbook_editor = __esm({
15191
15363
  "src/runbook-editor.ts"() {
15192
15364
  "use strict";
15193
15365
  init_cjs_shims();
15194
15366
  import_node_child_process7 = require("child_process");
15195
- import_node_fs22 = require("fs");
15196
- import_node_os4 = require("os");
15197
- import_node_path19 = require("path");
15198
- import_node_process14 = __toESM(require("process"), 1);
15367
+ import_node_fs24 = require("fs");
15368
+ import_node_os5 = require("os");
15369
+ import_node_path21 = require("path");
15370
+ import_node_process16 = __toESM(require("process"), 1);
15199
15371
  EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
15200
15372
  defaultRunOrInjected = (deps) => deps.run ?? defaultRun;
15201
15373
  }
@@ -15590,9 +15762,9 @@ async function runHostedSecurity(options) {
15590
15762
  const appId = selfAudit ? "odla-ai" : cfg.app.id;
15591
15763
  const env = selfAudit ? "prod" : selectEnv(options.env, cfg.envs, cfg.configPath, cfg.rootDir);
15592
15764
  const platform = options.platform ?? cfg?.platformUrl ?? "https://odla.ai";
15593
- const target = (0, import_node_path20.resolve)(options.target ?? cfg?.rootDir ?? ".");
15594
- const output = (0, import_node_path20.resolve)(options.out ?? (0, import_node_path20.resolve)(target, ".odla/security/hosted"));
15595
- const outputRelative = (0, import_node_path20.relative)(target, output).split(import_node_path20.sep).join("/");
15765
+ const target = (0, import_node_path22.resolve)(options.target ?? cfg?.rootDir ?? ".");
15766
+ const output = (0, import_node_path22.resolve)(options.out ?? (0, import_node_path22.resolve)(target, ".odla/security/hosted"));
15767
+ const outputRelative = (0, import_node_path22.relative)(target, output).split(import_node_path22.sep).join("/");
15596
15768
  if (!outputRelative) throw new Error("Hosted security output cannot be the repository root");
15597
15769
  const profile = profileFor(options.profile ?? "odla", options.maxHuntTasks ?? 12);
15598
15770
  const tokenRequest = {
@@ -15604,7 +15776,7 @@ async function runHostedSecurity(options) {
15604
15776
  };
15605
15777
  const token = await injectedToken(options, tokenRequest);
15606
15778
  const snapshot = await (0, import_node3.snapshotDirectory)(target, {
15607
- exclude: !outputRelative.startsWith("../") && !(0, import_node_path20.isAbsolute)(outputRelative) ? [outputRelative] : []
15779
+ exclude: !outputRelative.startsWith("../") && !(0, import_node_path22.isAbsolute)(outputRelative) ? [outputRelative] : []
15608
15780
  });
15609
15781
  const hosted = await (0, import_security.createPlatformSecurityReasoners)({
15610
15782
  platform,
@@ -15622,7 +15794,7 @@ async function runHostedSecurity(options) {
15622
15794
  });
15623
15795
  const harness = (0, import_security.createSecurityHarness)({
15624
15796
  profile,
15625
- store: new import_node3.FileRunStore((0, import_node_path20.resolve)(output, "state")),
15797
+ store: new import_node3.FileRunStore((0, import_node_path22.resolve)(output, "state")),
15626
15798
  discoveryReasoner: hosted.discoveryReasoner,
15627
15799
  validationReasoner: hosted.validationReasoner,
15628
15800
  policy: {
@@ -15646,7 +15818,7 @@ async function runHostedSecurity(options) {
15646
15818
  function selectEnv(requested, declared, configPath, rootDir) {
15647
15819
  const env = requested ?? (declared.includes("dev") ? "dev" : declared[0]);
15648
15820
  if (!env || !declared.includes(env)) {
15649
- const shown = (0, import_node_path20.relative)(rootDir, configPath) || configPath;
15821
+ const shown = (0, import_node_path22.relative)(rootDir, configPath) || configPath;
15650
15822
  throw new Error(`env "${env ?? ""}" is not declared in ${shown}`);
15651
15823
  }
15652
15824
  return env;
@@ -15675,17 +15847,17 @@ function printSummary(out, appId, env, run, report5, output) {
15675
15847
  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}`);
15676
15848
  if (report5.callBudget) out.log(` calls: discovery=${formatBudget(report5.callBudget.discovery)} validation=${formatBudget(report5.callBudget.validation)}`);
15677
15849
  out.log(` findings: confirmed=${report5.metrics.confirmed} needs_reproduction=${report5.metrics.needsReproduction} candidates=${report5.metrics.candidates}`);
15678
- out.log(` report: ${(0, import_node_path20.resolve)(output, "REPORT.md")}`);
15850
+ out.log(` report: ${(0, import_node_path22.resolve)(output, "REPORT.md")}`);
15679
15851
  }
15680
15852
  function formatBudget(usage) {
15681
15853
  return usage ? `${usage.usedCalls}/${usage.maxCalls} skipped=${usage.skippedCalls}` : "caller-managed";
15682
15854
  }
15683
- var import_node_path20, import_security, import_node3;
15855
+ var import_node_path22, import_security, import_node3;
15684
15856
  var init_security = __esm({
15685
15857
  "src/security.ts"() {
15686
15858
  "use strict";
15687
15859
  init_cjs_shims();
15688
- import_node_path20 = require("path");
15860
+ import_node_path22 = require("path");
15689
15861
  import_security = require("@odla-ai/security");
15690
15862
  import_node3 = require("@odla-ai/security/node");
15691
15863
  init_config();
@@ -16219,6 +16391,10 @@ async function dispatchCli(argv2, dependencies) {
16219
16391
  await contextCommand(parsed, runtime);
16220
16392
  return;
16221
16393
  }
16394
+ if (command === "device") {
16395
+ await deviceCommand(parsed, runtime);
16396
+ return;
16397
+ }
16222
16398
  if (command === "credentials") {
16223
16399
  await credentialCommand(parsed, runtime);
16224
16400
  return;
@@ -16381,6 +16557,7 @@ var init_cli = __esm({
16381
16557
  init_record();
16382
16558
  init_advisory_output();
16383
16559
  init_cached_credential();
16560
+ init_device_command();
16384
16561
  init_redact();
16385
16562
  init_runbook_command();
16386
16563
  init_security_command();