@odla-ai/cli 0.35.3 → 0.36.1

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