@odla-ai/cli 0.35.1 → 0.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.cjs CHANGED
@@ -347,10 +347,83 @@ var init_handshake_state = __esm({
347
347
  }
348
348
  });
349
349
 
350
+ // src/cached-credential.ts
351
+ function noteCachedCredential(tokenFile) {
352
+ noted = tokenFile;
353
+ }
354
+ function isCredentialRejection(error) {
355
+ const message2 = error instanceof Error ? error.message : String(error ?? "");
356
+ return /\((401|403)\)\s*$/.test(message2.trim());
357
+ }
358
+ function explainRejectedCredential(error) {
359
+ const tokenFile = noted;
360
+ if (!tokenFile || !isCredentialRejection(error)) return null;
361
+ noted = null;
362
+ (0, import_node_fs5.rmSync)(tokenFile, { force: true });
363
+ return [
364
+ "auth: the cached credential was rejected by odla, so it was revoked before its cached expiry.",
365
+ " The usual cause is a newer sign-in for this project: collecting a handshake retires the",
366
+ " principal's other credentials, so a second terminal or worktree supersedes this one.",
367
+ ` Discarded ${tokenFile}; re-run this command to request a fresh approval.`
368
+ ].join("\n");
369
+ }
370
+ var import_node_fs5, noted;
371
+ var init_cached_credential = __esm({
372
+ "src/cached-credential.ts"() {
373
+ "use strict";
374
+ init_cjs_shims();
375
+ import_node_fs5 = require("fs");
376
+ noted = null;
377
+ }
378
+ });
379
+
380
+ // src/device-session.ts
381
+ function deviceCredentialPath(env = import_node_process4.default.env) {
382
+ return env.ODLA_DEVICE_CREDENTIAL ?? (0, import_node_path3.join)(env.HOME ?? (0, import_node_os.homedir)(), ".odla", "device.json");
383
+ }
384
+ function readDeviceCredential(platform, env = import_node_process4.default.env) {
385
+ const path = deviceCredentialPath(env);
386
+ if (!(0, import_node_fs6.existsSync)(path)) return null;
387
+ try {
388
+ const parsed = JSON.parse((0, import_node_fs6.readFileSync)(path, "utf8"));
389
+ if (typeof parsed.token !== "string" || !parsed.token.startsWith("odla_device_")) return null;
390
+ if (parsed.platform !== platform) return null;
391
+ return { ...parsed, token: parsed.token, platform: parsed.platform };
392
+ } catch {
393
+ return null;
394
+ }
395
+ }
396
+ async function mintDeviceSession(platformUrl, credential2, doFetch) {
397
+ const response2 = await doFetch(`${platformUrl.replace(/\/$/, "")}/registry/devices/session`, {
398
+ method: "POST",
399
+ headers: { authorization: `Bearer ${credential2.token}`, "content-type": "application/json" },
400
+ body: "{}"
401
+ });
402
+ const body = await response2.json().catch(() => ({}));
403
+ if (!response2.ok || typeof body.token !== "string") {
404
+ const detail = body.error?.message ?? `registry returned ${response2.status}`;
405
+ throw new Error(
406
+ `device session failed: ${detail} (${response2.status}) \u2014 if this machine's enrollment was revoked or has expired, enroll it again in Studio`
407
+ );
408
+ }
409
+ return { token: body.token, expiresAt: body.expiresAt ?? Date.now() };
410
+ }
411
+ var import_node_fs6, import_node_os, import_node_path3, import_node_process4;
412
+ var init_device_session = __esm({
413
+ "src/device-session.ts"() {
414
+ "use strict";
415
+ init_cjs_shims();
416
+ import_node_fs6 = require("fs");
417
+ import_node_os = require("os");
418
+ import_node_path3 = require("path");
419
+ import_node_process4 = __toESM(require("process"), 1);
420
+ }
421
+ });
422
+
350
423
  // src/local.ts
351
424
  function readJsonFile(path) {
352
425
  try {
353
- return JSON.parse((0, import_node_fs5.readFileSync)(path, "utf8"));
426
+ return JSON.parse((0, import_node_fs7.readFileSync)(path, "utf8"));
354
427
  } catch {
355
428
  return null;
356
429
  }
@@ -360,10 +433,10 @@ function writePrivateJson(path, value2) {
360
433
  `);
361
434
  }
362
435
  function readCredentials(path) {
363
- if (!(0, import_node_fs5.existsSync)(path)) return null;
436
+ if (!(0, import_node_fs7.existsSync)(path)) return null;
364
437
  let value2;
365
438
  try {
366
- value2 = JSON.parse((0, import_node_fs5.readFileSync)(path, "utf8"));
439
+ value2 = JSON.parse((0, import_node_fs7.readFileSync)(path, "utf8"));
367
440
  } catch {
368
441
  throw new Error(`credentials file ${path} is not valid JSON; fix or remove it before provisioning`);
369
442
  }
@@ -393,14 +466,14 @@ function mergeCredential(current, update) {
393
466
  return next;
394
467
  }
395
468
  function ensureGitignore(rootDir, localPaths = []) {
396
- const path = (0, import_node_path3.resolve)(rootDir, ".gitignore");
397
- const existing = (0, import_node_fs5.existsSync)(path) ? (0, import_node_fs5.readFileSync)(path, "utf8") : "";
469
+ const path = (0, import_node_path4.resolve)(rootDir, ".gitignore");
470
+ const existing = (0, import_node_fs7.existsSync)(path) ? (0, import_node_fs7.readFileSync)(path, "utf8") : "";
398
471
  const configured = localPaths.map((localPath) => gitignoreEntry(rootDir, localPath)).filter((line2) => !!line2);
399
472
  const wanted = [.../* @__PURE__ */ new Set([...GITIGNORE_LINES, ...configured])];
400
473
  const missing = wanted.filter((line2) => !existing.split(/\r?\n/).includes(line2));
401
474
  if (missing.length === 0) return;
402
475
  const prefix = existing && !existing.endsWith("\n") ? "\n" : "";
403
- (0, import_node_fs5.writeFileSync)(path, `${existing}${prefix}${missing.join("\n")}
476
+ (0, import_node_fs7.writeFileSync)(path, `${existing}${prefix}${missing.join("\n")}
404
477
  `);
405
478
  }
406
479
  function o11yDevVars(cfg) {
@@ -414,7 +487,7 @@ function o11yDevVars(cfg) {
414
487
  function resolveWriteDevVarsTarget(cfg, requested) {
415
488
  if (!requested) return null;
416
489
  if (requested === true) return cfg.local.devVarsFile;
417
- return (0, import_node_path3.resolve)((0, import_node_path3.dirname)(cfg.configPath), requested);
490
+ return (0, import_node_path4.resolve)((0, import_node_path4.dirname)(cfg.configPath), requested);
418
491
  }
419
492
  function writeDevVars(path, credentials, env, o11y) {
420
493
  const entry = credentials.envs[env];
@@ -428,7 +501,7 @@ function writeDevVars(path, credentials, env, o11y) {
428
501
  if (o11y.version) lines.push(`ODLA_O11Y_VERSION="${o11y.version}"`);
429
502
  if (entry.o11yToken) lines.push(`ODLA_O11Y_TOKEN="${entry.o11yToken}"`);
430
503
  }
431
- const existing = (0, import_node_fs5.existsSync)(path) ? (0, import_node_fs5.readFileSync)(path, "utf8") : "";
504
+ const existing = (0, import_node_fs7.existsSync)(path) ? (0, import_node_fs7.readFileSync)(path, "utf8") : "";
432
505
  const retained = existing.split(/\r?\n/).filter((line2) => !isManagedDevVar(line2));
433
506
  while (retained.at(-1) === "") retained.pop();
434
507
  const prefix = retained.length ? `${retained.join("\n")}
@@ -442,28 +515,28 @@ function isManagedDevVar(line2) {
442
515
  return !!match?.[1] && MANAGED_DEV_VARS.has(match[1]);
443
516
  }
444
517
  function writePrivateText(path, text3) {
445
- (0, import_node_fs5.mkdirSync)((0, import_node_path3.dirname)(path), { recursive: true });
518
+ (0, import_node_fs7.mkdirSync)((0, import_node_path4.dirname)(path), { recursive: true });
446
519
  const temporary = `${path}.tmp-${process.pid}-${Date.now()}`;
447
- (0, import_node_fs5.writeFileSync)(temporary, text3, { mode: 384 });
448
- (0, import_node_fs5.chmodSync)(temporary, 384);
449
- (0, import_node_fs5.renameSync)(temporary, path);
520
+ (0, import_node_fs7.writeFileSync)(temporary, text3, { mode: 384 });
521
+ (0, import_node_fs7.chmodSync)(temporary, 384);
522
+ (0, import_node_fs7.renameSync)(temporary, path);
450
523
  }
451
524
  function gitignoreEntry(rootDir, path) {
452
- const rel = (0, import_node_path3.relative)((0, import_node_path3.resolve)(rootDir), (0, import_node_path3.resolve)(path));
453
- if (!rel || rel === ".." || rel.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || (0, import_node_path3.isAbsolute)(rel)) return null;
525
+ const rel = (0, import_node_path4.relative)((0, import_node_path4.resolve)(rootDir), (0, import_node_path4.resolve)(path));
526
+ if (!rel || rel === ".." || rel.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || (0, import_node_path4.isAbsolute)(rel)) return null;
454
527
  return rel.replaceAll("\\", "/");
455
528
  }
456
529
  function displayPath(path, rootDir = process.cwd()) {
457
- const rel = (0, import_node_path3.relative)(rootDir, path);
530
+ const rel = (0, import_node_path4.relative)(rootDir, path);
458
531
  return rel && !rel.startsWith("..") ? rel : path;
459
532
  }
460
- var import_node_fs5, import_node_path3, GITIGNORE_LINES, MANAGED_DEV_VARS;
533
+ var import_node_fs7, import_node_path4, GITIGNORE_LINES, MANAGED_DEV_VARS;
461
534
  var init_local = __esm({
462
535
  "src/local.ts"() {
463
536
  "use strict";
464
537
  init_cjs_shims();
465
- import_node_fs5 = require("fs");
466
- import_node_path3 = require("path");
538
+ import_node_fs7 = require("fs");
539
+ import_node_path4 = require("path");
467
540
  GITIGNORE_LINES = [".odla/*.local.json", ".odla/dev-token.json", ".dev.vars"];
468
541
  MANAGED_DEV_VARS = /* @__PURE__ */ new Set([
469
542
  "ODLA_PLATFORM",
@@ -488,17 +561,24 @@ async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {})
488
561
  const cached = readJsonFile(cfg.local.tokenFile);
489
562
  if (!grantRequest.forceReview && !grantRequest.freshLogin) {
490
563
  if (options.token) return options.token;
491
- if (import_node_process4.default.env.ODLA_DEV_TOKEN) {
492
- const declared = import_node_process4.default.env.ODLA_DEV_TOKEN_AUDIENCE;
564
+ if (import_node_process5.default.env.ODLA_DEV_TOKEN) {
565
+ const declared = import_node_process5.default.env.ODLA_DEV_TOKEN_AUDIENCE;
493
566
  if (declared) {
494
567
  if (platformAudience(declared) !== audience) throw new Error("ODLA_DEV_TOKEN_AUDIENCE does not match the configured platform");
495
568
  } else if (audience !== "https://odla.ai") {
496
569
  throw new Error("ODLA_DEV_TOKEN_AUDIENCE is required for a non-default platform");
497
570
  }
498
- return import_node_process4.default.env.ODLA_DEV_TOKEN;
571
+ return import_node_process5.default.env.ODLA_DEV_TOKEN;
572
+ }
573
+ const device = readDeviceCredential(audience);
574
+ if (device) {
575
+ const session = await mintDeviceSession(cfg.platformUrl, device, doFetch);
576
+ out.error(`auth: session minted by this enrolled device (${displayPath(deviceCredentialPath(), cfg.rootDir)})`);
577
+ return session.token;
499
578
  }
500
579
  if (cached?.token && cached.platform === audience && (cached.expiresAt ?? 0) > Date.now() + 6e4 && cachedGrantCovers(cached, grantIntent)) {
501
580
  out.error(`auth: using cached developer token (${displayPath(cfg.local.tokenFile, cfg.rootDir)})`);
581
+ noteCachedCredential(cfg.local.tokenFile);
502
582
  return cached.token;
503
583
  }
504
584
  } else {
@@ -599,7 +679,7 @@ function stillPending(pending, email) {
599
679
  );
600
680
  }
601
681
  function handshakeEmail(value2, cached) {
602
- const email = (value2 ?? import_node_process4.default.env.ODLA_USER_EMAIL ?? cached ?? "").trim().toLowerCase();
682
+ const email = (value2 ?? import_node_process5.default.env.ODLA_USER_EMAIL ?? cached ?? "").trim().toLowerCase();
603
683
  if (/@users\.noreply\.github\.com$/i.test(email)) {
604
684
  throw new Error(
605
685
  `"${email}" is a GitHub commit identity, not an odla account email; use --email <signed-in-odla-account> or ODLA_USER_EMAIL`
@@ -628,16 +708,18 @@ function platformAudience(value2) {
628
708
  }
629
709
  return url.origin;
630
710
  }
631
- var import_db, import_node_crypto, import_node_process4;
711
+ var import_db, import_node_crypto, import_node_process5;
632
712
  var init_token = __esm({
633
713
  "src/token.ts"() {
634
714
  "use strict";
635
715
  init_cjs_shims();
636
716
  import_db = require("@odla-ai/db");
637
717
  import_node_crypto = require("crypto");
638
- import_node_process4 = __toESM(require("process"), 1);
718
+ import_node_process5 = __toESM(require("process"), 1);
639
719
  init_handshake_approval();
640
720
  init_handshake_state();
721
+ init_cached_credential();
722
+ init_device_session();
641
723
  init_local();
642
724
  }
643
725
  });
@@ -646,7 +728,7 @@ var init_token = __esm({
646
728
  async function secretInputValue(options, kind = "credential") {
647
729
  if (options.fromEnv && options.stdin) throw new Error("choose exactly one of --from-env or --stdin");
648
730
  let value2;
649
- if (options.fromEnv) value2 = import_node_process5.default.env[options.fromEnv];
731
+ if (options.fromEnv) value2 = import_node_process6.default.env[options.fromEnv];
650
732
  else if (options.stdin) value2 = await (options.readStdin ?? (() => readSecretStream(kind)))();
651
733
  else throw new Error(`${kind} input required: use --from-env <NAME> or --stdin; values are never accepted as arguments`);
652
734
  value2 = value2?.replace(/[\r\n]+$/, "");
@@ -654,7 +736,7 @@ async function secretInputValue(options, kind = "credential") {
654
736
  if (new TextEncoder().encode(value2).byteLength > MAX_BYTES) throw new Error(`${kind} exceeds 64 KiB`);
655
737
  return value2;
656
738
  }
657
- async function readSecretStream(kind, stream = import_node_process5.default.stdin) {
739
+ async function readSecretStream(kind, stream = import_node_process6.default.stdin) {
658
740
  let value2 = "";
659
741
  for await (const chunk of stream) {
660
742
  value2 += String(chunk);
@@ -662,12 +744,12 @@ async function readSecretStream(kind, stream = import_node_process5.default.stdi
662
744
  }
663
745
  return value2;
664
746
  }
665
- var import_node_process5, MAX_BYTES;
747
+ var import_node_process6, MAX_BYTES;
666
748
  var init_secret_input = __esm({
667
749
  "src/secret-input.ts"() {
668
750
  "use strict";
669
751
  init_cjs_shims();
670
- import_node_process5 = __toESM(require("process"), 1);
752
+ import_node_process6 = __toESM(require("process"), 1);
671
753
  MAX_BYTES = 64 * 1024;
672
754
  }
673
755
  });
@@ -679,7 +761,7 @@ async function getScopedPlatformToken(options) {
679
761
  async function resolveAdminPlatformToken(options) {
680
762
  const audience = platformAudience(options.platform);
681
763
  if (options.token) return options.token;
682
- const fromEnv = import_node_process6.default.env.ODLA_ADMIN_TOKEN;
764
+ const fromEnv = import_node_process7.default.env.ODLA_ADMIN_TOKEN;
683
765
  if (fromEnv) return audienceBoundEnvToken(fromEnv, audience);
684
766
  return scopedToken(
685
767
  audience,
@@ -691,7 +773,7 @@ async function resolveAdminPlatformToken(options) {
691
773
  }
692
774
  function audienceBoundEnvToken(token, platform) {
693
775
  const audience = platformAudience(platform);
694
- const declared = import_node_process6.default.env.ODLA_ADMIN_TOKEN_AUDIENCE;
776
+ const declared = import_node_process7.default.env.ODLA_ADMIN_TOKEN_AUDIENCE;
695
777
  if (declared) {
696
778
  if (platformAudience(declared) !== audience) throw new Error("ODLA_ADMIN_TOKEN_AUDIENCE does not match the configured platform");
697
779
  } else if (audience !== "https://odla.ai") {
@@ -701,8 +783,8 @@ function audienceBoundEnvToken(token, platform) {
701
783
  }
702
784
  async function scopedToken(platform, scope, options, doFetch, out) {
703
785
  const audience = platformAudience(platform);
704
- const rootDir = options.rootDir ?? import_node_process6.default.cwd();
705
- const tokenFile = options.tokenFile ?? (0, import_node_path4.join)(rootDir, ".odla/admin-token.local.json");
786
+ const rootDir = options.rootDir ?? import_node_process7.default.cwd();
787
+ const tokenFile = options.tokenFile ?? (0, import_node_path5.join)(rootDir, ".odla/admin-token.local.json");
706
788
  const cache2 = options.cache === false ? null : readJsonFile(tokenFile);
707
789
  const cached = cache2?.platform === audience ? cache2.tokens?.[scope] : void 0;
708
790
  if (cached?.token && (cached.expiresAt ?? 0) > Date.now() + 6e4) {
@@ -729,7 +811,7 @@ async function scopedToken(platform, scope, options, doFetch, out) {
729
811
  if (options.cache !== false) {
730
812
  const tokens = cache2?.platform === audience ? { ...cache2.tokens ?? {} } : {};
731
813
  tokens[scope] = { token, expiresAt };
732
- if ((0, import_node_fs6.existsSync)((0, import_node_path4.join)(rootDir, ".git"))) ensureGitignore(rootDir, [tokenFile]);
814
+ if ((0, import_node_fs8.existsSync)((0, import_node_path5.join)(rootDir, ".git"))) ensureGitignore(rootDir, [tokenFile]);
733
815
  writePrivateJson(tokenFile, { platform: audience, email, tokens });
734
816
  out.error(`auth: cached ${scope} grant (${tokenFile}; mode 0600)`);
735
817
  } else {
@@ -737,14 +819,14 @@ async function scopedToken(platform, scope, options, doFetch, out) {
737
819
  }
738
820
  return token;
739
821
  }
740
- var import_node_fs6, import_node_path4, import_node_process6, import_db2, SCOPE_PURPOSE;
822
+ var import_node_fs8, import_node_path5, import_node_process7, import_db2, SCOPE_PURPOSE;
741
823
  var init_admin_ai_auth = __esm({
742
824
  "src/admin-ai-auth.ts"() {
743
825
  "use strict";
744
826
  init_cjs_shims();
745
- import_node_fs6 = require("fs");
746
- import_node_path4 = require("path");
747
- import_node_process6 = __toESM(require("process"), 1);
827
+ import_node_fs8 = require("fs");
828
+ import_node_path5 = require("path");
829
+ import_node_process7 = __toESM(require("process"), 1);
748
830
  import_db2 = require("@odla-ai/db");
749
831
  init_local();
750
832
  init_handshake_approval();
@@ -754,6 +836,7 @@ var init_admin_ai_auth = __esm({
754
836
  "app:config:read": "compare checked-in intent with an exact-id app Registry configuration",
755
837
  "app:config:write": "apply or inspect one revision-bound configuration operation for an app you own",
756
838
  "platform:runbook:write": "read and edit all of odla's operational runbooks, including admin-visible content",
839
+ "app:device:enroll": "enrol this machine so it can mint its own short-lived credentials without asking you again",
757
840
  "platform:ai:policy:write": "change System AI model routing",
758
841
  "platform:ai:policy:read": "read System AI model routing",
759
842
  "platform:ai:credential:write": "replace a stored AI provider key",
@@ -962,7 +1045,7 @@ var init_admin_ai_usage = __esm({
962
1045
 
963
1046
  // src/admin-ai.ts
964
1047
  async function adminAi(options) {
965
- const platform = platformAudience(options.platform ?? import_node_process7.default.env.ODLA_PLATFORM ?? "https://odla.ai");
1048
+ const platform = platformAudience(options.platform ?? import_node_process8.default.env.ODLA_PLATFORM ?? "https://odla.ai");
966
1049
  const doFetch = options.fetch ?? fetch;
967
1050
  const out = options.stdout ?? console;
968
1051
  const usageQuery = options.action === "usage" ? adminAiUsageQuery(options) : void 0;
@@ -1140,12 +1223,12 @@ function apiError3(action2, status, body) {
1140
1223
  function isRecord3(value2) {
1141
1224
  return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
1142
1225
  }
1143
- var import_node_process7;
1226
+ var import_node_process8;
1144
1227
  var init_admin_ai = __esm({
1145
1228
  "src/admin-ai.ts"() {
1146
1229
  "use strict";
1147
1230
  init_cjs_shims();
1148
- import_node_process7 = __toESM(require("process"), 1);
1231
+ import_node_process8 = __toESM(require("process"), 1);
1149
1232
  init_token();
1150
1233
  init_secret_input();
1151
1234
  init_admin_ai_auth();
@@ -1659,12 +1742,12 @@ var init_monitoring_validation = __esm({
1659
1742
 
1660
1743
  // src/config.ts
1661
1744
  async function loadProjectConfig(configPath = "odla.config.mjs", options = {}) {
1662
- const resolved = (0, import_node_path5.resolve)(configPath);
1663
- if (!(0, import_node_fs7.existsSync)(resolved)) {
1745
+ const resolved = (0, import_node_path6.resolve)(configPath);
1746
+ if (!(0, import_node_fs9.existsSync)(resolved)) {
1664
1747
  throw new Error(`config not found: ${configPath}. Run "odla-ai init" first or pass --config.`);
1665
1748
  }
1666
1749
  const raw = await loadConfigModule(resolved);
1667
- const rootDir = (0, import_node_path5.dirname)(resolved);
1750
+ const rootDir = (0, import_node_path6.dirname)(resolved);
1668
1751
  validateRawConfig(raw, resolved);
1669
1752
  const platformUrl = trimSlash(process.env.ODLA_PLATFORM_URL || raw.platformUrl || DEFAULT_PLATFORM);
1670
1753
  const dbEndpoint = trimSlash(process.env.ODLA_DB_ENDPOINT || raw.dbEndpoint || platformUrl);
@@ -1674,9 +1757,9 @@ async function loadProjectConfig(configPath = "odla.config.mjs", options = {}) {
1674
1757
  validateCalendarConfig(raw, unique3([...envs, ...options.additionalEnvs ?? []]), services, resolved);
1675
1758
  validateMonitoringConfig(raw, unique3([...envs, ...options.additionalEnvs ?? []]), services, resolved);
1676
1759
  const local = {
1677
- tokenFile: (0, import_node_path5.resolve)(rootDir, raw.local?.tokenFile ?? ".odla/dev-token.json"),
1678
- credentialsFile: (0, import_node_path5.resolve)(rootDir, raw.local?.credentialsFile ?? ".odla/credentials.local.json"),
1679
- devVarsFile: (0, import_node_path5.resolve)(rootDir, raw.local?.devVarsFile ?? ".dev.vars"),
1760
+ tokenFile: (0, import_node_path6.resolve)(rootDir, raw.local?.tokenFile ?? ".odla/dev-token.json"),
1761
+ credentialsFile: (0, import_node_path6.resolve)(rootDir, raw.local?.credentialsFile ?? ".odla/credentials.local.json"),
1762
+ devVarsFile: (0, import_node_path6.resolve)(rootDir, raw.local?.devVarsFile ?? ".dev.vars"),
1680
1763
  gitignore: raw.local?.gitignore ?? true
1681
1764
  };
1682
1765
  return {
@@ -1693,9 +1776,9 @@ async function loadProjectConfig(configPath = "odla.config.mjs", options = {}) {
1693
1776
  async function resolveDataExport(cfg, value2, names) {
1694
1777
  if (value2 === void 0 || value2 === null || value2 === false) return void 0;
1695
1778
  if (typeof value2 !== "string") return value2;
1696
- const target = (0, import_node_path5.isAbsolute)(value2) ? value2 : (0, import_node_path5.resolve)(cfg.rootDir, value2);
1779
+ const target = (0, import_node_path6.isAbsolute)(value2) ? value2 : (0, import_node_path6.resolve)(cfg.rootDir, value2);
1697
1780
  if (target.endsWith(".json")) {
1698
- return JSON.parse((0, import_node_fs7.readFileSync)(target, "utf8"));
1781
+ return JSON.parse((0, import_node_fs9.readFileSync)(target, "utf8"));
1699
1782
  }
1700
1783
  const mod = await import((0, import_node_url.pathToFileURL)(target).href);
1701
1784
  for (const name of names) {
@@ -1771,7 +1854,7 @@ function validId2(value2) {
1771
1854
  return typeof value2 === "string" && /^[a-z0-9][a-z0-9-]*$/.test(value2);
1772
1855
  }
1773
1856
  async function loadConfigModule(path) {
1774
- if (path.endsWith(".json")) return JSON.parse((0, import_node_fs7.readFileSync)(path, "utf8"));
1857
+ if (path.endsWith(".json")) return JSON.parse((0, import_node_fs9.readFileSync)(path, "utf8"));
1775
1858
  const nonce = `${Date.now()}-${configImportSerial++}`;
1776
1859
  const mod = await import(`${(0, import_node_url.pathToFileURL)(path).href}?reload=${nonce}`);
1777
1860
  const value2 = mod.default ?? mod.config;
@@ -1784,13 +1867,13 @@ function trimSlash(value2) {
1784
1867
  function unique3(values) {
1785
1868
  return [...new Set(values.filter(Boolean))];
1786
1869
  }
1787
- var import_node_fs7, import_node_path5, import_node_url, import_apps, DEFAULT_PLATFORM, DEFAULT_ENVS, DEFAULT_SERVICES, configImportSerial, GOOGLE_CALENDAR_EVENTS_SCOPE;
1870
+ var import_node_fs9, import_node_path6, import_node_url, import_apps, DEFAULT_PLATFORM, DEFAULT_ENVS, DEFAULT_SERVICES, configImportSerial, GOOGLE_CALENDAR_EVENTS_SCOPE;
1788
1871
  var init_config = __esm({
1789
1872
  "src/config.ts"() {
1790
1873
  "use strict";
1791
1874
  init_cjs_shims();
1792
- import_node_fs7 = require("fs");
1793
- import_node_path5 = require("path");
1875
+ import_node_fs9 = require("fs");
1876
+ import_node_path6 = require("path");
1794
1877
  import_node_url = require("url");
1795
1878
  import_apps = require("@odla-ai/apps");
1796
1879
  init_ai_config_validation();
@@ -1808,13 +1891,13 @@ var init_config = __esm({
1808
1891
 
1809
1892
  // src/operator-profiles.ts
1810
1893
  function operatorProfileFile() {
1811
- return (0, import_node_path6.resolve)(
1812
- clean(import_node_process8.default.env.ODLA_CONTEXT_FILE) ?? (0, import_node_path6.join)((0, import_node_os.homedir)(), ".odla", "contexts.json")
1894
+ return (0, import_node_path7.resolve)(
1895
+ clean(import_node_process9.default.env.ODLA_CONTEXT_FILE) ?? (0, import_node_path7.join)((0, import_node_os2.homedir)(), ".odla", "contexts.json")
1813
1896
  );
1814
1897
  }
1815
1898
  function resolveOperatorProfile(parsed) {
1816
1899
  const fromFlag = clean(stringOpt(parsed.options.context));
1817
- const fromEnvironment = clean(import_node_process8.default.env.ODLA_CONTEXT);
1900
+ const fromEnvironment = clean(import_node_process9.default.env.ODLA_CONTEXT);
1818
1901
  const name = fromFlag ?? fromEnvironment ?? null;
1819
1902
  const file = operatorProfileFile();
1820
1903
  if (!name) {
@@ -1854,10 +1937,10 @@ function removeOperatorProfile(name, file = operatorProfileFile()) {
1854
1937
  return true;
1855
1938
  }
1856
1939
  function operatorCredentialFiles(selection) {
1857
- const base = selection.name ? (0, import_node_path6.join)((0, import_node_path6.dirname)(selection.file), "profiles", selection.name) : (0, import_node_path6.join)((0, import_node_os.homedir)(), ".odla");
1940
+ const base = selection.name ? (0, import_node_path7.join)((0, import_node_path7.dirname)(selection.file), "profiles", selection.name) : (0, import_node_path7.join)((0, import_node_os2.homedir)(), ".odla");
1858
1941
  return {
1859
- developer: (0, import_node_path6.join)(base, "dev-token.json"),
1860
- scoped: (0, import_node_path6.join)(base, "admin-token.local.json")
1942
+ developer: (0, import_node_path7.join)(base, "dev-token.json"),
1943
+ scoped: (0, import_node_path7.join)(base, "admin-token.local.json")
1861
1944
  };
1862
1945
  }
1863
1946
  function assertOperatorName(value2, label) {
@@ -1868,10 +1951,10 @@ function assertOperatorName(value2, label) {
1868
1951
  }
1869
1952
  }
1870
1953
  function readOperatorProfiles(file) {
1871
- if (!(0, import_node_fs8.existsSync)(file)) return emptyProfiles();
1954
+ if (!(0, import_node_fs10.existsSync)(file)) return emptyProfiles();
1872
1955
  let raw;
1873
1956
  try {
1874
- raw = JSON.parse((0, import_node_fs8.readFileSync)(file, "utf8"));
1957
+ raw = JSON.parse((0, import_node_fs10.readFileSync)(file, "utf8"));
1875
1958
  } catch {
1876
1959
  throw new Error(`operator context file ${file} is not valid JSON`);
1877
1960
  }
@@ -1929,15 +2012,15 @@ function clean(value2) {
1929
2012
  const normalized = value2?.trim();
1930
2013
  return normalized || void 0;
1931
2014
  }
1932
- var import_node_fs8, import_node_os, import_node_path6, import_node_process8;
2015
+ var import_node_fs10, import_node_os2, import_node_path7, import_node_process9;
1933
2016
  var init_operator_profiles = __esm({
1934
2017
  "src/operator-profiles.ts"() {
1935
2018
  "use strict";
1936
2019
  init_cjs_shims();
1937
- import_node_fs8 = require("fs");
1938
- import_node_os = require("os");
1939
- import_node_path6 = require("path");
1940
- import_node_process8 = __toESM(require("process"), 1);
2020
+ import_node_fs10 = require("fs");
2021
+ import_node_os2 = require("os");
2022
+ import_node_path7 = require("path");
2023
+ import_node_process9 = __toESM(require("process"), 1);
1941
2024
  init_argv();
1942
2025
  init_local();
1943
2026
  init_token();
@@ -1948,21 +2031,21 @@ var init_operator_profiles = __esm({
1948
2031
  async function resolveOperatorContext(parsed, options = {}) {
1949
2032
  const profile = resolveOperatorProfile(parsed);
1950
2033
  const configArgument = stringOpt(parsed.options.config) ?? "odla.config.mjs";
1951
- const configPath = (0, import_node_path7.resolve)(configArgument);
2034
+ const configPath = (0, import_node_path8.resolve)(configArgument);
1952
2035
  const explicitConfig = parsed.options.config !== void 0;
1953
- const hasConfig = (0, import_node_fs9.existsSync)(configPath);
2036
+ const hasConfig = (0, import_node_fs11.existsSync)(configPath);
1954
2037
  if (!hasConfig && (!options.allowMissingConfig || explicitConfig)) {
1955
2038
  await loadProjectConfig(configArgument);
1956
2039
  }
1957
2040
  const loaded = hasConfig ? await loadProjectConfig(configArgument) : void 0;
1958
2041
  const platformFlag = clean2(stringOpt(parsed.options.platform));
1959
- const platformEnvironment = clean2(import_node_process9.default.env.ODLA_PLATFORM_URL);
2042
+ const platformEnvironment = clean2(import_node_process10.default.env.ODLA_PLATFORM_URL);
1960
2043
  const platformValue = platformAudience(
1961
2044
  platformFlag ?? platformEnvironment ?? profile.value?.platform ?? loaded?.platformUrl ?? DEFAULT_PLATFORM2
1962
2045
  );
1963
2046
  const platformSource = platformFlag ? "flag" : platformEnvironment ? "environment" : profile.value ? "profile" : loaded ? "config" : "default";
1964
2047
  const appFlag = clean2(stringOpt(parsed.options.app));
1965
- const appEnvironment = clean2(import_node_process9.default.env.ODLA_APP_ID);
2048
+ const appEnvironment = clean2(import_node_process10.default.env.ODLA_APP_ID);
1966
2049
  const appValue = appFlag ?? appEnvironment ?? profile.value?.app ?? loaded?.app.id ?? null;
1967
2050
  const appSource = appFlag ? "flag" : appEnvironment ? "environment" : profile.value?.app ? "profile" : loaded ? "config" : "unresolved";
1968
2051
  if (appValue) assertOperatorName(appValue, "app");
@@ -1972,16 +2055,16 @@ async function resolveOperatorContext(parsed, options = {}) {
1972
2055
  );
1973
2056
  }
1974
2057
  const envFlag = clean2(stringOpt(parsed.options.env));
1975
- const envEnvironment = clean2(import_node_process9.default.env.ODLA_ENV);
2058
+ const envEnvironment = clean2(import_node_process10.default.env.ODLA_ENV);
1976
2059
  const environmentValue = envFlag ?? envEnvironment ?? profile.value?.environment ?? options.defaultEnvironment ?? null;
1977
2060
  const environmentSource = envFlag ? "flag" : envEnvironment ? "environment" : profile.value?.environment ? "profile" : options.defaultEnvironment ? "default" : "unresolved";
1978
2061
  if (environmentValue) {
1979
2062
  assertOperatorName(environmentValue, "environment");
1980
2063
  }
1981
- const rootDir = loaded?.rootDir ?? import_node_process9.default.cwd();
2064
+ const rootDir = loaded?.rootDir ?? import_node_process10.default.cwd();
1982
2065
  const profileCredentials = operatorCredentialFiles(profile);
1983
- 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;
1984
- const scopedTokenFile = clean2(import_node_process9.default.env.ODLA_ADMIN_TOKEN_FILE) ? (0, import_node_path7.resolve)(import_node_process9.default.env.ODLA_ADMIN_TOKEN_FILE) : profile.name ? profileCredentials.scoped : loaded ? (0, import_node_path7.join)(loaded.rootDir, ".odla", "admin-token.local.json") : profileCredentials.scoped;
2066
+ const tokenFile = clean2(import_node_process10.default.env.ODLA_DEV_TOKEN_FILE) ? (0, import_node_path8.resolve)(import_node_process10.default.env.ODLA_DEV_TOKEN_FILE) : profile.name ? profileCredentials.developer : loaded?.local.tokenFile ?? profileCredentials.developer;
2067
+ const scopedTokenFile = clean2(import_node_process10.default.env.ODLA_ADMIN_TOKEN_FILE) ? (0, import_node_path8.resolve)(import_node_process10.default.env.ODLA_ADMIN_TOKEN_FILE) : profile.name ? profileCredentials.scoped : loaded ? (0, import_node_path8.join)(loaded.rootDir, ".odla", "admin-token.local.json") : profileCredentials.scoped;
1985
2068
  const cfg = loaded ? {
1986
2069
  ...loaded,
1987
2070
  platformUrl: platformValue,
@@ -2003,8 +2086,8 @@ async function resolveOperatorContext(parsed, options = {}) {
2003
2086
  services: [],
2004
2087
  local: {
2005
2088
  tokenFile,
2006
- credentialsFile: (0, import_node_path7.join)(rootDir, ".odla", "credentials.local.json"),
2007
- devVarsFile: (0, import_node_path7.join)(rootDir, ".dev.vars"),
2089
+ credentialsFile: (0, import_node_path8.join)(rootDir, ".odla", "credentials.local.json"),
2090
+ devVarsFile: (0, import_node_path8.join)(rootDir, ".dev.vars"),
2008
2091
  gitignore: true
2009
2092
  }
2010
2093
  };
@@ -2036,14 +2119,14 @@ function clean2(value2) {
2036
2119
  const normalized = value2?.trim();
2037
2120
  return normalized || void 0;
2038
2121
  }
2039
- var import_node_fs9, import_node_path7, import_node_process9, DEFAULT_PLATFORM2;
2122
+ var import_node_fs11, import_node_path8, import_node_process10, DEFAULT_PLATFORM2;
2040
2123
  var init_operator_context = __esm({
2041
2124
  "src/operator-context.ts"() {
2042
2125
  "use strict";
2043
2126
  init_cjs_shims();
2044
- import_node_fs9 = require("fs");
2045
- import_node_path7 = require("path");
2046
- import_node_process9 = __toESM(require("process"), 1);
2127
+ import_node_fs11 = require("fs");
2128
+ import_node_path8 = require("path");
2129
+ import_node_process10 = __toESM(require("process"), 1);
2047
2130
  init_argv();
2048
2131
  init_config();
2049
2132
  init_operator_profiles();
@@ -2295,7 +2378,7 @@ async function authCommand(parsed, deps = {}) {
2295
2378
  const { cfg } = context;
2296
2379
  const out = deps.stdout ?? console;
2297
2380
  const doFetch = deps.fetch ?? fetch;
2298
- const email = stringOpt(parsed.options.email) ?? import_node_process10.default.env.ODLA_USER_EMAIL?.trim();
2381
+ const email = stringOpt(parsed.options.email) ?? import_node_process11.default.env.ODLA_USER_EMAIL?.trim();
2299
2382
  if (!email) {
2300
2383
  throw new Error(
2301
2384
  "auth login requires --email <odla-account> or ODLA_USER_EMAIL; confirm the signed-in odla email instead of using git or GitHub identity"
@@ -2329,12 +2412,12 @@ async function authCommand(parsed, deps = {}) {
2329
2412
  out.log(`Authorized ${identity.displayName}${handle} for ${cfg.app.id}.`);
2330
2413
  out.log(`odla account: ${identity.email ?? "not returned"}`);
2331
2414
  }
2332
- var import_node_process10;
2415
+ var import_node_process11;
2333
2416
  var init_auth_command = __esm({
2334
2417
  "src/auth-command.ts"() {
2335
2418
  "use strict";
2336
2419
  init_cjs_shims();
2337
- import_node_process10 = __toESM(require("process"), 1);
2420
+ import_node_process11 = __toESM(require("process"), 1);
2338
2421
  init_argv();
2339
2422
  init_operator_context();
2340
2423
  init_token();
@@ -2507,7 +2590,7 @@ async function appImport(options) {
2507
2590
  const out = options.stdout ?? console;
2508
2591
  const say = options.json ? (line2) => out.error(line2) : (line2) => out.log(line2);
2509
2592
  const { tenant } = resolveTenant(cfg, options.env);
2510
- const text3 = options.file === "-" ? (options.readStdin ?? (() => (0, import_node_fs10.readFileSync)(0, "utf8")))() : (0, import_node_fs10.readFileSync)(options.file, "utf8");
2593
+ const text3 = options.file === "-" ? (options.readStdin ?? (() => (0, import_node_fs12.readFileSync)(0, "utf8")))() : (0, import_node_fs12.readFileSync)(options.file, "utf8");
2511
2594
  const { format, sources } = (0, import_import.parseImport)(text3, options.ns);
2512
2595
  if (format === "namespace-map" && options.ns) {
2513
2596
  throw new Error("--ns cannot be combined with a {namespace: rows} file \u2014 the file already names each namespace");
@@ -2535,12 +2618,12 @@ ${detail}${more}`);
2535
2618
  }
2536
2619
  return requireStudioHuman(options.configPath, "database import", "database", options.env);
2537
2620
  }
2538
- var import_node_fs10, import_import;
2621
+ var import_node_fs12, import_import;
2539
2622
  var init_app_import = __esm({
2540
2623
  "src/app-import.ts"() {
2541
2624
  "use strict";
2542
2625
  init_cjs_shims();
2543
- import_node_fs10 = require("fs");
2626
+ import_node_fs12 = require("fs");
2544
2627
  import_import = require("@odla-ai/db/import");
2545
2628
  init_config();
2546
2629
  init_human_session();
@@ -2851,15 +2934,15 @@ var init_brand_design_unpack = __esm({
2851
2934
 
2852
2935
  // src/brand-command.ts
2853
2936
  async function readBundle(source, deps) {
2854
- if (source !== "-") return (0, import_promises.readFile)((0, import_node_path8.resolve)(source), "utf8");
2937
+ if (source !== "-") return (0, import_promises.readFile)((0, import_node_path9.resolve)(source), "utf8");
2855
2938
  const readStdin = deps.readStdin;
2856
2939
  if (!readStdin) throw new Error("reading a bundle from stdin is not supported here");
2857
2940
  return readStdin();
2858
2941
  }
2859
2942
  async function writeAll(result, outDir) {
2860
2943
  for (const file of result.files) {
2861
- const target = (0, import_node_path8.resolve)(outDir, file.path);
2862
- await (0, import_promises.mkdir)((0, import_node_path8.dirname)(target), { recursive: true });
2944
+ const target = (0, import_node_path9.resolve)(outDir, file.path);
2945
+ await (0, import_promises.mkdir)((0, import_node_path9.dirname)(target), { recursive: true });
2863
2946
  await (0, import_promises.writeFile)(target, file.bytes);
2864
2947
  }
2865
2948
  }
@@ -2867,7 +2950,7 @@ async function designUnpack(parsed, deps) {
2867
2950
  assertArgs(parsed, ["out", "json"], 4);
2868
2951
  const source = parsed.positionals[3];
2869
2952
  if (!source) throw new Error(USAGE);
2870
- const outDir = (0, import_node_path8.resolve)(stringOpt(parsed.options.out) ?? "design");
2953
+ const outDir = (0, import_node_path9.resolve)(stringOpt(parsed.options.out) ?? "design");
2871
2954
  const result = unpackDesign(await readBundle(source, deps));
2872
2955
  await writeAll(result, outDir);
2873
2956
  const out = deps.stdout ?? console;
@@ -2893,13 +2976,13 @@ async function brandCommand(parsed, deps) {
2893
2976
  }
2894
2977
  throw new Error(USAGE);
2895
2978
  }
2896
- var import_promises, import_node_path8, USAGE;
2979
+ var import_promises, import_node_path9, USAGE;
2897
2980
  var init_brand_command = __esm({
2898
2981
  "src/brand-command.ts"() {
2899
2982
  "use strict";
2900
2983
  init_cjs_shims();
2901
2984
  import_promises = require("fs/promises");
2902
- import_node_path8 = require("path");
2985
+ import_node_path9 = require("path");
2903
2986
  init_argv();
2904
2987
  init_brand_design_unpack();
2905
2988
  USAGE = "usage: odla-ai brand design unpack <bundle.html|-> [--out <dir>] [--json]";
@@ -3537,7 +3620,7 @@ var init_config_reconcile_digest = __esm({
3537
3620
  function readPlan(path) {
3538
3621
  let value2;
3539
3622
  try {
3540
- const raw = (0, import_node_fs11.readFileSync)(path, "utf8");
3623
+ const raw = (0, import_node_fs13.readFileSync)(path, "utf8");
3541
3624
  if (Buffer.byteLength(raw) > 128 * 1024) throw new Error("plan exceeds 128 KiB");
3542
3625
  value2 = JSON.parse(raw);
3543
3626
  } catch (error) {
@@ -3653,13 +3736,13 @@ function invalidPlan(message2) {
3653
3736
  function record3(value2) {
3654
3737
  return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
3655
3738
  }
3656
- var import_apps3, import_node_fs11, DIGEST, REVISION, OPERATION_ID, ACTION_ID, ENV, SERVICE;
3739
+ var import_apps3, import_node_fs13, DIGEST, REVISION, OPERATION_ID, ACTION_ID, ENV, SERVICE;
3657
3740
  var init_config_operation_validate = __esm({
3658
3741
  "src/config-operation-validate.ts"() {
3659
3742
  "use strict";
3660
3743
  init_cjs_shims();
3661
3744
  import_apps3 = require("@odla-ai/apps");
3662
- import_node_fs11 = require("fs");
3745
+ import_node_fs13 = require("fs");
3663
3746
  init_config_operation_error();
3664
3747
  init_config_reconcile_digest();
3665
3748
  DIGEST = /^sha256:[0-9a-f]{64}$/;
@@ -3947,7 +4030,7 @@ async function operationClient(cfg, options, purpose) {
3947
4030
  platform: cfg.platformUrl,
3948
4031
  scope: "app:config:write",
3949
4032
  token: options.token,
3950
- tokenFile: (0, import_node_path9.join)(cfg.rootDir, ".odla", "admin-token.local.json"),
4033
+ tokenFile: (0, import_node_path10.join)(cfg.rootDir, ".odla", "admin-token.local.json"),
3951
4034
  rootDir: cfg.rootDir,
3952
4035
  email: options.email,
3953
4036
  open: options.open,
@@ -3999,13 +4082,13 @@ function normalizeRequestError(error) {
3999
4082
  function record4(value2) {
4000
4083
  return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
4001
4084
  }
4002
- var import_apps6, import_node_path9, IDEMPOTENCY_KEY, DEFAULT_WAIT_SECONDS, DEFAULT_INTERVAL_SECONDS;
4085
+ var import_apps6, import_node_path10, IDEMPOTENCY_KEY, DEFAULT_WAIT_SECONDS, DEFAULT_INTERVAL_SECONDS;
4003
4086
  var init_config_operation_command = __esm({
4004
4087
  "src/config-operation-command.ts"() {
4005
4088
  "use strict";
4006
4089
  init_cjs_shims();
4007
4090
  import_apps6 = require("@odla-ai/apps");
4008
- import_node_path9 = require("path");
4091
+ import_node_path10 = require("path");
4009
4092
  init_admin_ai_auth();
4010
4093
  init_version();
4011
4094
  init_config();
@@ -4328,7 +4411,7 @@ async function inspectConfig(options) {
4328
4411
  platform: cfg.platformUrl,
4329
4412
  scope: "app:config:read",
4330
4413
  token: options.token,
4331
- tokenFile: (0, import_node_path10.join)(cfg.rootDir, ".odla", "admin-token.local.json"),
4414
+ tokenFile: (0, import_node_path11.join)(cfg.rootDir, ".odla", "admin-token.local.json"),
4332
4415
  rootDir: cfg.rootDir,
4333
4416
  email: options.email,
4334
4417
  open: options.open,
@@ -4457,13 +4540,13 @@ function studioSettingsUrl(reconciliation) {
4457
4540
  function quoteArg2(value2) {
4458
4541
  return `'${value2.replace(/'/g, `'\\''`)}'`;
4459
4542
  }
4460
- var import_apps8, import_node_path10;
4543
+ var import_apps8, import_node_path11;
4461
4544
  var init_config_reconcile_command = __esm({
4462
4545
  "src/config-reconcile-command.ts"() {
4463
4546
  "use strict";
4464
4547
  init_cjs_shims();
4465
4548
  import_apps8 = require("@odla-ai/apps");
4466
- import_node_path10 = require("path");
4549
+ import_node_path11 = require("path");
4467
4550
  init_admin_ai_auth();
4468
4551
  init_config();
4469
4552
  init_config_reconcile_digest();
@@ -4476,15 +4559,15 @@ var init_config_reconcile_command = __esm({
4476
4559
  // src/wrangler.ts
4477
4560
  function findWranglerConfig(rootDir) {
4478
4561
  for (const name of WRANGLER_CONFIG_FILES) {
4479
- const path = (0, import_node_path11.join)(rootDir, name);
4480
- if ((0, import_node_fs12.existsSync)(path)) return path;
4562
+ const path = (0, import_node_path12.join)(rootDir, name);
4563
+ if ((0, import_node_fs14.existsSync)(path)) return path;
4481
4564
  }
4482
4565
  return null;
4483
4566
  }
4484
4567
  function readWranglerConfig(path) {
4485
4568
  if (path.endsWith(".toml")) return null;
4486
4569
  try {
4487
- return JSON.parse(stripJsonComments((0, import_node_fs12.readFileSync)(path, "utf8")));
4570
+ return JSON.parse(stripJsonComments((0, import_node_fs14.readFileSync)(path, "utf8")));
4488
4571
  } catch {
4489
4572
  return null;
4490
4573
  }
@@ -4588,14 +4671,14 @@ function wranglerBulkSecrets(run, opts) {
4588
4671
  ];
4589
4672
  return run("npx", args, { input: JSON.stringify(opts.secrets), cwd: opts.cwd });
4590
4673
  }
4591
- var import_node_child_process2, import_node_fs12, import_node_path11, defaultRunner, WRANGLER_CONFIG_FILES;
4674
+ var import_node_child_process2, import_node_fs14, import_node_path12, defaultRunner, WRANGLER_CONFIG_FILES;
4592
4675
  var init_wrangler = __esm({
4593
4676
  "src/wrangler.ts"() {
4594
4677
  "use strict";
4595
4678
  init_cjs_shims();
4596
4679
  import_node_child_process2 = require("child_process");
4597
- import_node_fs12 = require("fs");
4598
- import_node_path11 = require("path");
4680
+ import_node_fs14 = require("fs");
4681
+ import_node_path12 = require("path");
4599
4682
  defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
4600
4683
  const child = (0, import_node_child_process2.spawn)(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
4601
4684
  let stdout = "";
@@ -4654,10 +4737,10 @@ function wranglerWarnings(rootDir) {
4654
4737
  for (const { label, block } of blocks) {
4655
4738
  const assets = block.assets;
4656
4739
  if (assets?.directory) {
4657
- const dir = (0, import_node_path12.resolve)(rootDir, assets.directory);
4658
- if (dir === (0, import_node_path12.resolve)(rootDir)) {
4740
+ const dir = (0, import_node_path13.resolve)(rootDir, assets.directory);
4741
+ if (dir === (0, import_node_path13.resolve)(rootDir)) {
4659
4742
  warnings.push(`${label}assets.directory is the project root \u2014 point it at a dedicated build dir (wrangler dev fails with "spawn EBADF")`);
4660
- } else if ((0, import_node_fs13.existsSync)((0, import_node_path12.join)(dir, "node_modules"))) {
4743
+ } else if ((0, import_node_fs15.existsSync)((0, import_node_path13.join)(dir, "node_modules"))) {
4661
4744
  warnings.push(`${label}assets.directory contains node_modules \u2014 wrangler dev's watcher will exhaust file descriptors`);
4662
4745
  }
4663
4746
  }
@@ -4692,13 +4775,13 @@ function o11yProjectWarnings(rootDir) {
4692
4775
  warnings.push("cannot verify o11y Worker instrumentation \u2014 add a parseable wrangler.jsonc/json config");
4693
4776
  return warnings;
4694
4777
  }
4695
- const main = typeof config.main === "string" ? (0, import_node_path12.resolve)(rootDir, config.main) : null;
4696
- if (!main || !(0, import_node_fs13.existsSync)(main)) {
4778
+ const main = typeof config.main === "string" ? (0, import_node_path13.resolve)(rootDir, config.main) : null;
4779
+ if (!main || !(0, import_node_fs15.existsSync)(main)) {
4697
4780
  warnings.push("cannot verify o11y Worker instrumentation \u2014 wrangler main is missing or unreadable");
4698
4781
  } else {
4699
4782
  let source = "";
4700
4783
  try {
4701
- source = (0, import_node_fs13.readFileSync)(main, "utf8");
4784
+ source = (0, import_node_fs15.readFileSync)(main, "utf8");
4702
4785
  } catch {
4703
4786
  }
4704
4787
  if (!/\bwithObservability\b/.test(source)) {
@@ -4722,19 +4805,19 @@ function calendarProjectWarnings(rootDir) {
4722
4805
  }
4723
4806
  function readPackageJson(rootDir) {
4724
4807
  try {
4725
- return JSON.parse((0, import_node_fs13.readFileSync)((0, import_node_path12.join)(rootDir, "package.json"), "utf8"));
4808
+ return JSON.parse((0, import_node_fs15.readFileSync)((0, import_node_path13.join)(rootDir, "package.json"), "utf8"));
4726
4809
  } catch {
4727
4810
  return null;
4728
4811
  }
4729
4812
  }
4730
- var import_node_child_process3, import_node_fs13, import_node_path12, defaultExec;
4813
+ var import_node_child_process3, import_node_fs15, import_node_path13, defaultExec;
4731
4814
  var init_doctor_checks = __esm({
4732
4815
  "src/doctor-checks.ts"() {
4733
4816
  "use strict";
4734
4817
  init_cjs_shims();
4735
4818
  import_node_child_process3 = require("child_process");
4736
- import_node_fs13 = require("fs");
4737
- import_node_path12 = require("path");
4819
+ import_node_fs15 = require("fs");
4820
+ import_node_path13 = require("path");
4738
4821
  init_redact();
4739
4822
  init_local();
4740
4823
  init_wrangler();
@@ -5071,9 +5154,9 @@ var init_harness_options = __esm({
5071
5154
  // src/init.ts
5072
5155
  function initProject(options) {
5073
5156
  const out = options.stdout ?? console;
5074
- const rootDir = (0, import_node_path13.resolve)(options.rootDir ?? process.cwd());
5075
- const configPath = (0, import_node_path13.resolve)(rootDir, options.configPath ?? "odla.config.mjs");
5076
- if ((0, import_node_fs14.existsSync)(configPath) && !options.force) {
5157
+ const rootDir = (0, import_node_path14.resolve)(options.rootDir ?? process.cwd());
5158
+ const configPath = (0, import_node_path14.resolve)(rootDir, options.configPath ?? "odla.config.mjs");
5159
+ if ((0, import_node_fs16.existsSync)(configPath) && !options.force) {
5077
5160
  throw new Error(`${configPath} already exists. Pass --force to overwrite.`);
5078
5161
  }
5079
5162
  if (!/^[a-z0-9][a-z0-9-]*$/.test(options.appId)) {
@@ -5089,20 +5172,20 @@ function initProject(options) {
5089
5172
  }
5090
5173
  }
5091
5174
  const aiProvider = options.aiProvider;
5092
- (0, import_node_fs14.mkdirSync)((0, import_node_path13.dirname)(configPath), { recursive: true });
5093
- (0, import_node_fs14.mkdirSync)((0, import_node_path13.resolve)(rootDir, "src/odla"), { recursive: true });
5094
- (0, import_node_fs14.mkdirSync)((0, import_node_path13.resolve)(rootDir, ".odla"), { recursive: true });
5095
- (0, import_node_fs14.writeFileSync)(configPath, configTemplate({ appId: options.appId, name: options.name, envs, services, aiProvider }));
5096
- writeIfMissing((0, import_node_path13.resolve)(rootDir, "src/odla/schema.mjs"), schemaTemplate());
5097
- writeIfMissing((0, import_node_path13.resolve)(rootDir, "src/odla/rules.mjs"), rulesTemplate());
5175
+ (0, import_node_fs16.mkdirSync)((0, import_node_path14.dirname)(configPath), { recursive: true });
5176
+ (0, import_node_fs16.mkdirSync)((0, import_node_path14.resolve)(rootDir, "src/odla"), { recursive: true });
5177
+ (0, import_node_fs16.mkdirSync)((0, import_node_path14.resolve)(rootDir, ".odla"), { recursive: true });
5178
+ (0, import_node_fs16.writeFileSync)(configPath, configTemplate({ appId: options.appId, name: options.name, envs, services, aiProvider }));
5179
+ writeIfMissing((0, import_node_path14.resolve)(rootDir, "src/odla/schema.mjs"), schemaTemplate());
5180
+ writeIfMissing((0, import_node_path14.resolve)(rootDir, "src/odla/rules.mjs"), rulesTemplate());
5098
5181
  ensureGitignore(rootDir);
5099
5182
  out.log(`created ${relativeDisplay(configPath, rootDir)}`);
5100
5183
  out.log("created src/odla/schema.mjs and src/odla/rules.mjs");
5101
5184
  out.log("updated .gitignore for local odla credentials");
5102
5185
  }
5103
5186
  function writeIfMissing(path, text3) {
5104
- if ((0, import_node_fs14.existsSync)(path)) return;
5105
- (0, import_node_fs14.writeFileSync)(path, text3);
5187
+ if ((0, import_node_fs16.existsSync)(path)) return;
5188
+ (0, import_node_fs16.writeFileSync)(path, text3);
5106
5189
  }
5107
5190
  function configTemplate(input) {
5108
5191
  const calendar = input.services.includes("calendar") ? ` calendar: {
@@ -5205,13 +5288,13 @@ function defaultKeyEnv(provider) {
5205
5288
  function relativeDisplay(path, rootDir) {
5206
5289
  return path.startsWith(rootDir) ? path.slice(rootDir.length + 1) : path;
5207
5290
  }
5208
- var import_node_fs14, import_node_path13, import_apps9;
5291
+ var import_node_fs16, import_node_path14, import_apps9;
5209
5292
  var init_init = __esm({
5210
5293
  "src/init.ts"() {
5211
5294
  "use strict";
5212
5295
  init_cjs_shims();
5213
- import_node_fs14 = require("fs");
5214
- import_node_path13 = require("path");
5296
+ import_node_fs16 = require("fs");
5297
+ import_node_path14 = require("path");
5215
5298
  import_apps9 = require("@odla-ai/apps");
5216
5299
  init_local();
5217
5300
  }
@@ -5544,8 +5627,8 @@ function installSkill(options = {}) {
5544
5627
  const files = listFiles(sourceDir);
5545
5628
  if (files.length === 0) throw new Error(`no bundled skills found at ${sourceDir}`);
5546
5629
  const harnesses = normalizeHarnesses(options.harnesses, options.global === true);
5547
- const root = (0, import_node_path14.resolve)(options.dir ?? process.cwd());
5548
- const home = (0, import_node_path14.resolve)(options.homeDir ?? (0, import_node_os2.homedir)());
5630
+ const root = (0, import_node_path15.resolve)(options.dir ?? process.cwd());
5631
+ const home = (0, import_node_path15.resolve)(options.homeDir ?? (0, import_node_os3.homedir)());
5549
5632
  const plans = /* @__PURE__ */ new Map();
5550
5633
  const targets = /* @__PURE__ */ new Map();
5551
5634
  const rememberTarget = (harness, target) => {
@@ -5559,48 +5642,48 @@ function installSkill(options = {}) {
5559
5642
  plans.set(target, { target, content: content2, boundary, managedMerge });
5560
5643
  };
5561
5644
  const planSkillTree = (targetDir2, boundary = root) => {
5562
- for (const rel of files) plan((0, import_node_path14.join)(targetDir2, rel), (0, import_node_fs15.readFileSync)((0, import_node_path14.join)(sourceDir, rel), "utf8"), false, boundary);
5645
+ for (const rel of files) plan((0, import_node_path15.join)(targetDir2, rel), (0, import_node_fs17.readFileSync)((0, import_node_path15.join)(sourceDir, rel), "utf8"), false, boundary);
5563
5646
  };
5564
5647
  let targetDir;
5565
5648
  if (options.global) {
5566
- const claudeRoot = (0, import_node_path14.join)(home, ".claude", "skills");
5567
- const codexRoot = (0, import_node_path14.resolve)(options.codexHomeDir ?? process.env.CODEX_HOME ?? (0, import_node_path14.join)(home, ".codex"), "skills");
5649
+ const claudeRoot = (0, import_node_path15.join)(home, ".claude", "skills");
5650
+ const codexRoot = (0, import_node_path15.resolve)(options.codexHomeDir ?? process.env.CODEX_HOME ?? (0, import_node_path15.join)(home, ".codex"), "skills");
5568
5651
  targetDir = harnesses[0] === "codex" ? codexRoot : claudeRoot;
5569
5652
  for (const harness of harnesses) {
5570
5653
  const skillRoot = harness === "claude" ? claudeRoot : codexRoot;
5571
- planSkillTree(skillRoot, harness === "claude" ? home : (0, import_node_path14.dirname)((0, import_node_path14.dirname)(codexRoot)));
5654
+ planSkillTree(skillRoot, harness === "claude" ? home : (0, import_node_path15.dirname)((0, import_node_path15.dirname)(codexRoot)));
5572
5655
  rememberTarget(harness, skillRoot);
5573
5656
  }
5574
5657
  } else {
5575
- const sharedRoot = (0, import_node_path14.join)(root, ".agents", "skills");
5658
+ const sharedRoot = (0, import_node_path15.join)(root, ".agents", "skills");
5576
5659
  planSkillTree(sharedRoot);
5577
- const claudeRoot = (0, import_node_path14.join)(root, ".claude", "skills");
5660
+ const claudeRoot = (0, import_node_path15.join)(root, ".claude", "skills");
5578
5661
  targetDir = harnesses.includes("claude") ? claudeRoot : sharedRoot;
5579
5662
  for (const harness of harnesses) rememberTarget(harness, sharedRoot);
5580
5663
  if (harnesses.includes("claude")) {
5581
5664
  for (const skill of skillNames(files)) {
5582
- const canonical2 = (0, import_node_fs15.readFileSync)((0, import_node_path14.join)(sourceDir, skill, "SKILL.md"), "utf8");
5583
- plan((0, import_node_path14.join)(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical2));
5665
+ const canonical2 = (0, import_node_fs17.readFileSync)((0, import_node_path15.join)(sourceDir, skill, "SKILL.md"), "utf8");
5666
+ plan((0, import_node_path15.join)(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical2));
5584
5667
  }
5585
5668
  rememberTarget("claude", claudeRoot);
5586
5669
  }
5587
5670
  if (harnesses.includes("cursor")) {
5588
- const cursorRule = (0, import_node_path14.join)(root, ".cursor", "rules", "odla.mdc");
5671
+ const cursorRule = (0, import_node_path15.join)(root, ".cursor", "rules", "odla.mdc");
5589
5672
  plan(cursorRule, CURSOR_RULE);
5590
5673
  rememberTarget("cursor", cursorRule);
5591
5674
  }
5592
5675
  if (harnesses.includes("agents")) {
5593
- const agentsFile = (0, import_node_path14.join)(root, "AGENTS.md");
5676
+ const agentsFile = (0, import_node_path15.join)(root, "AGENTS.md");
5594
5677
  plan(agentsFile, managedFileContent(agentsFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
5595
5678
  rememberTarget("agents", agentsFile);
5596
5679
  }
5597
5680
  if (harnesses.includes("copilot")) {
5598
- const copilotFile = (0, import_node_path14.join)(root, ".github", "copilot-instructions.md");
5681
+ const copilotFile = (0, import_node_path15.join)(root, ".github", "copilot-instructions.md");
5599
5682
  plan(copilotFile, managedFileContent(copilotFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
5600
5683
  rememberTarget("copilot", copilotFile);
5601
5684
  }
5602
5685
  if (harnesses.includes("gemini")) {
5603
- const geminiFile = (0, import_node_path14.join)(root, "GEMINI.md");
5686
+ const geminiFile = (0, import_node_path15.join)(root, "GEMINI.md");
5604
5687
  plan(geminiFile, managedFileContent(geminiFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
5605
5688
  rememberTarget("gemini", geminiFile);
5606
5689
  }
@@ -5614,11 +5697,11 @@ function installSkill(options = {}) {
5614
5697
  conflicts.push(`${file.target} (redirected by symbolic link ${symlink})`);
5615
5698
  continue;
5616
5699
  }
5617
- if (!(0, import_node_fs15.existsSync)(file.target)) {
5700
+ if (!(0, import_node_fs17.existsSync)(file.target)) {
5618
5701
  writtenPaths.add(file.target);
5619
5702
  continue;
5620
5703
  }
5621
- const current = (0, import_node_fs15.readFileSync)(file.target, "utf8");
5704
+ const current = (0, import_node_fs17.readFileSync)(file.target, "utf8");
5622
5705
  if (current === file.content) {
5623
5706
  unchangedPaths.add(file.target);
5624
5707
  } else if (file.managedMerge || options.force) {
@@ -5635,9 +5718,9 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
5635
5718
  );
5636
5719
  }
5637
5720
  for (const file of plans.values()) {
5638
- if (!(0, import_node_fs15.existsSync)(file.target) || (0, import_node_fs15.readFileSync)(file.target, "utf8") !== file.content) {
5639
- (0, import_node_fs15.mkdirSync)((0, import_node_path14.dirname)(file.target), { recursive: true });
5640
- (0, import_node_fs15.writeFileSync)(file.target, file.content);
5721
+ if (!(0, import_node_fs17.existsSync)(file.target) || (0, import_node_fs17.readFileSync)(file.target, "utf8") !== file.content) {
5722
+ (0, import_node_fs17.mkdirSync)((0, import_node_path15.dirname)(file.target), { recursive: true });
5723
+ (0, import_node_fs17.writeFileSync)(file.target, file.content);
5641
5724
  }
5642
5725
  }
5643
5726
  const skills = skillNames(files);
@@ -5656,7 +5739,7 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
5656
5739
  };
5657
5740
  }
5658
5741
  function pathsUnder(root, paths) {
5659
- return [...paths].map((path) => (0, import_node_path14.relative)(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${import_node_path14.sep}`) && !(0, import_node_path14.isAbsolute)(path)).sort();
5742
+ return [...paths].map((path) => (0, import_node_path15.relative)(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${import_node_path15.sep}`) && !(0, import_node_path15.isAbsolute)(path)).sort();
5660
5743
  }
5661
5744
  function normalizeHarnesses(values, global) {
5662
5745
  const requested = values?.length ? values : ["claude"];
@@ -5678,9 +5761,9 @@ function normalizeHarnesses(values, global) {
5678
5761
  function managedFileContent(path, block, force, boundary) {
5679
5762
  const symlink = symlinkedComponent(boundary, path);
5680
5763
  if (symlink) throw new Error(`refusing to manage ${path}: symbolic link component ${symlink}`);
5681
- if (!(0, import_node_fs15.existsSync)(path)) return `${block}
5764
+ if (!(0, import_node_fs17.existsSync)(path)) return `${block}
5682
5765
  `;
5683
- const current = (0, import_node_fs15.readFileSync)(path, "utf8");
5766
+ const current = (0, import_node_fs17.readFileSync)(path, "utf8");
5684
5767
  const start = "<!-- odla-ai agent setup:start -->";
5685
5768
  const end = "<!-- odla-ai agent setup:end -->";
5686
5769
  const startAt = current.indexOf(start);
@@ -5701,15 +5784,15 @@ function managedFileContent(path, block, force, boundary) {
5701
5784
  return `${current.slice(0, startAt)}${block}${current.slice(afterEnd)}`;
5702
5785
  }
5703
5786
  function symlinkedComponent(boundary, target) {
5704
- const rel = (0, import_node_path14.relative)(boundary, target);
5705
- if (rel === ".." || rel.startsWith(`..${import_node_path14.sep}`) || (0, import_node_path14.isAbsolute)(rel)) {
5787
+ const rel = (0, import_node_path15.relative)(boundary, target);
5788
+ if (rel === ".." || rel.startsWith(`..${import_node_path15.sep}`) || (0, import_node_path15.isAbsolute)(rel)) {
5706
5789
  throw new Error(`agent setup target escapes its install root: ${target}`);
5707
5790
  }
5708
5791
  let current = boundary;
5709
- for (const part of rel.split(import_node_path14.sep).filter(Boolean)) {
5710
- current = (0, import_node_path14.join)(current, part);
5792
+ for (const part of rel.split(import_node_path15.sep).filter(Boolean)) {
5793
+ current = (0, import_node_path15.join)(current, part);
5711
5794
  try {
5712
- if ((0, import_node_fs15.lstatSync)(current).isSymbolicLink()) return current;
5795
+ if ((0, import_node_fs17.lstatSync)(current).isSymbolicLink()) return current;
5713
5796
  } catch (error) {
5714
5797
  if (error.code !== "ENOENT") throw error;
5715
5798
  }
@@ -5720,26 +5803,26 @@ function skillNames(files) {
5720
5803
  return [...new Set(files.filter((file) => /(^|[\\/])SKILL\.md$/.test(file)).map((file) => file.split(/[\\/]/)[0]))].sort();
5721
5804
  }
5722
5805
  function listFiles(dir) {
5723
- if (!(0, import_node_fs15.existsSync)(dir)) return [];
5806
+ if (!(0, import_node_fs17.existsSync)(dir)) return [];
5724
5807
  const results = [];
5725
5808
  const walk = (current) => {
5726
- for (const entry of (0, import_node_fs15.readdirSync)(current, { withFileTypes: true })) {
5727
- const path = (0, import_node_path14.join)(current, entry.name);
5809
+ for (const entry of (0, import_node_fs17.readdirSync)(current, { withFileTypes: true })) {
5810
+ const path = (0, import_node_path15.join)(current, entry.name);
5728
5811
  if (entry.isDirectory()) walk(path);
5729
- else results.push((0, import_node_path14.relative)(dir, path));
5812
+ else results.push((0, import_node_path15.relative)(dir, path));
5730
5813
  }
5731
5814
  };
5732
5815
  walk(dir);
5733
5816
  return results.sort();
5734
5817
  }
5735
- var import_node_fs15, import_node_os2, import_node_path14, import_node_url2, AGENT_HARNESSES;
5818
+ var import_node_fs17, import_node_os3, import_node_path15, import_node_url2, AGENT_HARNESSES;
5736
5819
  var init_skill = __esm({
5737
5820
  "src/skill.ts"() {
5738
5821
  "use strict";
5739
5822
  init_cjs_shims();
5740
- import_node_fs15 = require("fs");
5741
- import_node_os2 = require("os");
5742
- import_node_path14 = require("path");
5823
+ import_node_fs17 = require("fs");
5824
+ import_node_os3 = require("os");
5825
+ import_node_path15 = require("path");
5743
5826
  import_node_url2 = require("url");
5744
5827
  init_skill_adapters();
5745
5828
  AGENT_HARNESSES = ["claude", "codex", "cursor", "copilot", "gemini", "agents"];
@@ -7290,8 +7373,8 @@ function rollup(graph, kind, options = {}) {
7290
7373
  for (const node of nodesOfKind(graph, kind)) {
7291
7374
  if (options.prefix && !node.name.startsWith(options.prefix)) continue;
7292
7375
  const key = node.name.split(separator).slice(0, depth).join(separator);
7293
- const list2 = groups.get(key);
7294
- if (list2) list2.push(node);
7376
+ const list3 = groups.get(key);
7377
+ if (list3) list3.push(node);
7295
7378
  else groups.set(key, [node]);
7296
7379
  }
7297
7380
  return [...groups].map(([prefix, nodes]) => ({
@@ -7316,7 +7399,7 @@ function dirname9(path) {
7316
7399
  const at = path.lastIndexOf("/");
7317
7400
  return at <= 0 ? "." : path.slice(0, at);
7318
7401
  }
7319
- function join12(base, specifier) {
7402
+ function join13(base, specifier) {
7320
7403
  const parts = [];
7321
7404
  const segments = `${base === "." ? "" : `${base}/`}${specifier}`.split("/");
7322
7405
  for (const segment of segments) {
@@ -7328,7 +7411,7 @@ function join12(base, specifier) {
7328
7411
  }
7329
7412
  function resolveImport(fromPath, specifier, known) {
7330
7413
  if (!specifier.startsWith(".")) return null;
7331
- const base = join12(dirname9(fromPath), specifier);
7414
+ const base = join13(dirname9(fromPath), specifier);
7332
7415
  const candidates = [
7333
7416
  base,
7334
7417
  base.replace(/\.js$/, ".ts"),
@@ -10254,8 +10337,8 @@ var init_code_runtime_config = __esm({
10254
10337
  // src/code-connect.ts
10255
10338
  async function codeConnect(options) {
10256
10339
  const cwd = options.cwd ?? process.cwd();
10257
- const configPath = (0, import_node_path15.resolve)(cwd, options.configPath);
10258
- const cfg = (0, import_node_fs16.existsSync)(configPath) ? await loadProjectConfig(configPath) : null;
10340
+ const configPath = (0, import_node_path16.resolve)(cwd, options.configPath);
10341
+ const cfg = (0, import_node_fs18.existsSync)(configPath) ? await loadProjectConfig(configPath) : null;
10259
10342
  const requestedAppId = options.appId?.trim();
10260
10343
  if (requestedAppId && !/^[a-z0-9][a-z0-9-]{1,62}$/.test(requestedAppId)) {
10261
10344
  throw new Error("--app-id must be a valid odla app id");
@@ -10284,7 +10367,7 @@ async function codeConnect(options) {
10284
10367
  const doFetch = options.fetch ?? fetch;
10285
10368
  const engine = await (options.selectEngine ?? selectContainerEngine)(options.engine ?? "auto");
10286
10369
  const hostPlatform = process.platform === "darwin" ? "macos" : "linux";
10287
- const hostName = (options.name ?? (0, import_node_os3.hostname)()).trim();
10370
+ const hostName = (options.name ?? (0, import_node_os4.hostname)()).trim();
10288
10371
  if (!hostName || hostName.length > 120) throw new Error("--name must contain 1 to 120 characters");
10289
10372
  const repository = await inferGitHubRepository(cwd, options.readGitOrigin);
10290
10373
  const localSource = await (options.prepareLocalSource ?? prepareCodeLocalSource)(
@@ -10321,8 +10404,8 @@ async function codeConnect(options) {
10321
10404
  platform: hostPlatform,
10322
10405
  arch: process.arch,
10323
10406
  engines: [engine],
10324
- cpuCount: (0, import_node_os3.cpus)().length,
10325
- memoryBytes: (0, import_node_os3.totalmem)(),
10407
+ cpuCount: (0, import_node_os4.cpus)().length,
10408
+ memoryBytes: (0, import_node_os4.totalmem)(),
10326
10409
  source: descriptor2,
10327
10410
  images: {
10328
10411
  ready: true,
@@ -10419,14 +10502,14 @@ function apiFailure(action2, status, value2) {
10419
10502
  function record6(value2) {
10420
10503
  return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
10421
10504
  }
10422
- var import_node_fs16, import_node_os3, import_node_path15;
10505
+ var import_node_fs18, import_node_os4, import_node_path16;
10423
10506
  var init_code_connect = __esm({
10424
10507
  "src/code-connect.ts"() {
10425
10508
  "use strict";
10426
10509
  init_cjs_shims();
10427
- import_node_fs16 = require("fs");
10428
- import_node_os3 = require("os");
10429
- import_node_path15 = require("path");
10510
+ import_node_fs18 = require("fs");
10511
+ import_node_os4 = require("os");
10512
+ import_node_path16 = require("path");
10430
10513
  init_node();
10431
10514
  init_admin_ai_auth();
10432
10515
  init_config();
@@ -10743,7 +10826,7 @@ function developerTokenStatus(context, parsed, now = Date.now()) {
10743
10826
  const cacheStatus = !cached?.token ? "missing" : cached.platform !== context.platform.value ? "other-platform" : (cached.expiresAt ?? 0) <= now + 6e4 ? "expired" : "valid";
10744
10827
  const source = clean3(
10745
10828
  stringOpt(parsed.options.token)
10746
- ) ? "flag" : clean3(import_node_process11.default.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
10829
+ ) ? "flag" : clean3(import_node_process12.default.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
10747
10830
  return {
10748
10831
  source,
10749
10832
  cacheFile: context.cfg.local.tokenFile,
@@ -10754,12 +10837,12 @@ function clean3(value2) {
10754
10837
  const normalized = value2?.trim();
10755
10838
  return normalized || void 0;
10756
10839
  }
10757
- var import_node_process11;
10840
+ var import_node_process12;
10758
10841
  var init_operator_credentials = __esm({
10759
10842
  "src/operator-credentials.ts"() {
10760
10843
  "use strict";
10761
10844
  init_cjs_shims();
10762
- import_node_process11 = __toESM(require("process"), 1);
10845
+ import_node_process12 = __toESM(require("process"), 1);
10763
10846
  init_argv();
10764
10847
  init_local();
10765
10848
  }
@@ -11100,6 +11183,9 @@ Usage:
11100
11183
  odla-ai security run [target] --ack-redacted-source [--env dev] [--profile odla] [--fail-on high]
11101
11184
  odla-ai security run [target] --self --ack-redacted-source
11102
11185
  odla-ai provision [--live] [--config odla.config.mjs] [--email <odla-account>] [--request-grant] [--no-open] [--wait <seconds>] [--dry-run] [--push-secrets] [--rotate-o11y-token] [--write-dev-vars[=path]] [--yes]
11186
+ odla-ai device enroll [--app <id>[,<id>...]] [--name <label>] [--capability <c>[,<c>...]] [--email <odla-account>] [--no-open] [--json]
11187
+ odla-ai device list [--email <odla-account>] [--json]
11188
+ odla-ai device revoke <device-id> [--email <odla-account>] [--json]
11103
11189
  odla-ai credentials list [--config odla.config.mjs] [--env dev] [--all] [--json]
11104
11190
  odla-ai credentials revoke <receipt-id> [--config odla.config.mjs] [--json]
11105
11191
  odla-ai smoke [--config odla.config.mjs] [--env dev] [--runtime] [--email <odla-account>] [--no-open]
@@ -11207,6 +11293,10 @@ Commands:
11207
11293
  stable status, incident, and report JSON to agents and CI.
11208
11294
  platform Read canonical fleet health, releases, provider load/freshness,
11209
11295
  explicit unknowns, and next actions through a read-only grant.
11296
+ device Enrol THIS machine once, then stop asking. A human approves the
11297
+ enrollment in the browser; from then on this terminal mints its
11298
+ own short-lived credentials for the named projects with nobody's
11299
+ attention, until the device expires or is revoked.
11210
11300
  provision Register services, compose integrations, persist credentials, optionally push secrets.
11211
11301
  "provision --live --yes" initializes only the live instance of
11212
11302
  an existing sandbox app and enables every configured service;
@@ -12581,14 +12671,14 @@ function readPmProjectContext(rootDir) {
12581
12671
  function writePmProjectContext(rootDir, value2) {
12582
12672
  writePrivateJson(pmProjectContextFile(rootDir), { ...value2, selectedAt: (/* @__PURE__ */ new Date()).toISOString() });
12583
12673
  }
12584
- var import_node_path16, pmProjectContextFile;
12674
+ var import_node_path17, pmProjectContextFile;
12585
12675
  var init_pm_project_context = __esm({
12586
12676
  "src/pm-project-context.ts"() {
12587
12677
  "use strict";
12588
12678
  init_cjs_shims();
12589
- import_node_path16 = require("path");
12679
+ import_node_path17 = require("path");
12590
12680
  init_local();
12591
- pmProjectContextFile = (rootDir) => (0, import_node_path16.resolve)(rootDir, ".odla", "pm-project.local.json");
12681
+ pmProjectContextFile = (rootDir) => (0, import_node_path17.resolve)(rootDir, ".odla", "pm-project.local.json");
12592
12682
  }
12593
12683
  });
12594
12684
 
@@ -14160,7 +14250,7 @@ async function provision(options) {
14160
14250
  await provisionIntegrationSeeds(doFetch, cfg.dbEndpoint, tenantId, dbKey, database.integrations, env, out);
14161
14251
  }
14162
14252
  if (cfg.services.includes("ai") && cfg.ai?.provider && cfg.ai.keyEnv) {
14163
- const key = import_node_process12.default.env[cfg.ai.keyEnv];
14253
+ const key = import_node_process13.default.env[cfg.ai.keyEnv];
14164
14254
  if (key) {
14165
14255
  const secretName = cfg.ai.secretName ?? defaultSecretName(cfg.ai.provider);
14166
14256
  await (0, import_ai5.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, secretName, key);
@@ -14199,14 +14289,14 @@ async function provision(options) {
14199
14289
  }
14200
14290
  }
14201
14291
  }
14202
- var import_apps13, import_ai5, import_node_process12;
14292
+ var import_apps13, import_ai5, import_node_process13;
14203
14293
  var init_provision = __esm({
14204
14294
  "src/provision.ts"() {
14205
14295
  "use strict";
14206
14296
  init_cjs_shims();
14207
14297
  import_apps13 = require("@odla-ai/apps");
14208
14298
  import_ai5 = require("@odla-ai/ai");
14209
- import_node_process12 = __toESM(require("process"), 1);
14299
+ import_node_process13 = __toESM(require("process"), 1);
14210
14300
  init_config();
14211
14301
  init_calendar();
14212
14302
  init_calendar_errors();
@@ -14332,6 +14422,7 @@ var init_surface = __esm({
14332
14422
  config: { diff: {}, plan: {}, apply: {} },
14333
14423
  context: { show: {}, list: {}, save: {}, remove: {} },
14334
14424
  credentials: { list: {}, revoke: {} },
14425
+ device: { enroll: {}, list: {}, revoke: {} },
14335
14426
  // `watch`, `read`, `reply`, and `resolve` take a topic id from there on.
14336
14427
  discuss: {
14337
14428
  groups: {},
@@ -14400,7 +14491,7 @@ var init_surface = __esm({
14400
14491
 
14401
14492
  // src/record.ts
14402
14493
  function recordInvocation(parsed) {
14403
- const file = import_node_process13.default.env.ODLA_CLI_RECORD;
14494
+ const file = import_node_process14.default.env.ODLA_CLI_RECORD;
14404
14495
  if (!file) return;
14405
14496
  try {
14406
14497
  const entry = {
@@ -14408,22 +14499,165 @@ function recordInvocation(parsed) {
14408
14499
  options: Object.entries(parsed.options).map(([name, value2]) => value2 === false ? `no-${name}` : name).sort()
14409
14500
  };
14410
14501
  if (!entry.path.length) return;
14411
- (0, import_node_fs17.appendFileSync)(file, `${JSON.stringify(entry)}
14502
+ (0, import_node_fs19.appendFileSync)(file, `${JSON.stringify(entry)}
14412
14503
  `);
14413
14504
  } catch {
14414
14505
  }
14415
14506
  }
14416
- var import_node_fs17, import_node_process13;
14507
+ var import_node_fs19, import_node_process14;
14417
14508
  var init_record = __esm({
14418
14509
  "src/record.ts"() {
14419
14510
  "use strict";
14420
14511
  init_cjs_shims();
14421
- import_node_fs17 = require("fs");
14422
- import_node_process13 = __toESM(require("process"), 1);
14512
+ import_node_fs19 = require("fs");
14513
+ import_node_process14 = __toESM(require("process"), 1);
14423
14514
  init_surface();
14424
14515
  }
14425
14516
  });
14426
14517
 
14518
+ // src/advisory-output.ts
14519
+ function advisoryCollectingFetch(inner, sink) {
14520
+ return (async (input, init) => {
14521
+ const response2 = await inner(input, init);
14522
+ try {
14523
+ sink.push(...(0, import_apps14.parseAdvisories)(response2));
14524
+ } catch {
14525
+ }
14526
+ return response2;
14527
+ });
14528
+ }
14529
+ function renderAdvisories(out, advisories, env = process.env) {
14530
+ if (env.ODLA_NO_ADVISORIES) return;
14531
+ const seen = /* @__PURE__ */ new Set();
14532
+ for (const advisory of advisories) {
14533
+ const key = `${advisory.code}:${advisory.message}`;
14534
+ if (seen.has(key)) continue;
14535
+ seen.add(key);
14536
+ out.error((0, import_apps14.formatAdvisory)(advisory));
14537
+ }
14538
+ }
14539
+ var import_apps14;
14540
+ var init_advisory_output = __esm({
14541
+ "src/advisory-output.ts"() {
14542
+ "use strict";
14543
+ init_cjs_shims();
14544
+ import_apps14 = require("@odla-ai/apps");
14545
+ }
14546
+ });
14547
+
14548
+ // src/device-command.ts
14549
+ async function deviceCommand(parsed, deps) {
14550
+ const action2 = parsed.positionals[1] ?? "";
14551
+ const out = deps.stdout ?? console;
14552
+ const doFetch = deps.fetch ?? fetch;
14553
+ const cfg = await loadProjectConfig(stringOpt(parsed.options.config));
14554
+ const json = parsed.options.json === true;
14555
+ if (action2 === "enroll") return enroll(parsed, deps, cfg, doFetch, out, json);
14556
+ if (action2 === "list") return list2(parsed, deps, cfg, doFetch, out, json);
14557
+ if (action2 === "revoke") return revoke(parsed, deps, cfg, doFetch, out, json);
14558
+ throw new Error('odla-ai device expects "enroll", "list", or "revoke"');
14559
+ }
14560
+ async function enroll(parsed, deps, cfg, doFetch, out, json) {
14561
+ const name = stringOpt(parsed.options.name) ?? defaultDeviceName();
14562
+ const apps = (stringOpt(parsed.options.app) ?? cfg.app.id).split(",").map((id2) => id2.trim()).filter(Boolean);
14563
+ if (apps.length === 0) throw new Error("device enroll needs --app <id>[,<id>\u2026]");
14564
+ const token = await scopedToken2(parsed, deps, cfg, doFetch, out, `odla CLI (enroll ${name})`);
14565
+ const response2 = await doFetch(`${cfg.platformUrl}/registry/devices`, {
14566
+ method: "POST",
14567
+ headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
14568
+ body: JSON.stringify({
14569
+ name,
14570
+ platform: import_node_process15.default.platform,
14571
+ appIds: apps,
14572
+ ...parsed.options.capability ? { capabilities: String(parsed.options.capability).split(",").map((c) => c.trim()).filter(Boolean) } : {}
14573
+ })
14574
+ });
14575
+ const body = await response2.json().catch(() => ({}));
14576
+ if (!response2.ok || !body.token || !body.device) {
14577
+ throw new Error(`device enroll failed: ${body.error?.message ?? `registry returned ${response2.status}`} (${response2.status})`);
14578
+ }
14579
+ const path = deviceCredentialPath();
14580
+ (0, import_node_fs20.mkdirSync)((0, import_node_path18.dirname)(path), { recursive: true });
14581
+ (0, import_node_fs20.writeFileSync)(path, JSON.stringify({
14582
+ token: body.token,
14583
+ platform: cfg.platformUrl.replace(/\/$/, ""),
14584
+ deviceId: body.device.deviceId,
14585
+ name
14586
+ }, null, 2));
14587
+ (0, import_node_fs20.chmodSync)(path, 384);
14588
+ out.error(`device: enrolled "${name}" for ${body.device.appIds.join(", ")}; credential written to ${path}`);
14589
+ out.error("device: this terminal will mint its own credentials from now on \u2014 no further approvals.");
14590
+ if (json) {
14591
+ out.log(JSON.stringify({ deviceId: body.device.deviceId, name, appIds: body.device.appIds, expiresAt: body.device.expiresAt }, null, 2));
14592
+ }
14593
+ }
14594
+ async function list2(parsed, deps, cfg, doFetch, out, json) {
14595
+ const token = await scopedToken2(parsed, deps, cfg, doFetch, out, "odla CLI (device list)");
14596
+ const response2 = await doFetch(`${cfg.platformUrl}/registry/devices`, {
14597
+ headers: { authorization: `Bearer ${token}` }
14598
+ });
14599
+ const body = await response2.json().catch(() => ({}));
14600
+ if (!response2.ok || !body.devices) {
14601
+ throw new Error(`device list failed: ${body.error?.message ?? `registry returned ${response2.status}`} (${response2.status})`);
14602
+ }
14603
+ if (json) return out.log(JSON.stringify(body.devices, null, 2));
14604
+ if (body.devices.length === 0) return out.log("no enrolled devices");
14605
+ for (const device of body.devices) {
14606
+ const state2 = device.revokedAt ? "revoked" : device.expiresAt <= Date.now() ? "expired" : "active";
14607
+ out.log(`${device.deviceId} ${state2.padEnd(7)} ${device.name} [${device.appIds.join(", ")}]`);
14608
+ }
14609
+ }
14610
+ async function revoke(parsed, deps, cfg, doFetch, out, json) {
14611
+ const deviceId = parsed.positionals[2];
14612
+ if (!deviceId) throw new Error("device revoke needs the device id from `odla-ai device list`");
14613
+ const token = await scopedToken2(parsed, deps, cfg, doFetch, out, "odla CLI (device revoke)");
14614
+ const response2 = await doFetch(`${cfg.platformUrl}/registry/devices/${encodeURIComponent(deviceId)}/revoke`, {
14615
+ method: "POST",
14616
+ headers: { authorization: `Bearer ${token}` }
14617
+ });
14618
+ if (!response2.ok) {
14619
+ const body = await response2.json().catch(() => ({}));
14620
+ throw new Error(`device revoke failed: ${body.error?.message ?? `registry returned ${response2.status}`} (${response2.status})`);
14621
+ }
14622
+ out.error(`device: revoked ${deviceId}; every credential it minted is revoked with it`);
14623
+ if (json) out.log(JSON.stringify({ deviceId, revoked: true }, null, 2));
14624
+ }
14625
+ async function scopedToken2(parsed, deps, cfg, doFetch, out, label) {
14626
+ const { credentials } = await resolveOperatorContext(parsed, { allowMissingConfig: true });
14627
+ const scopedTokenFile = credentials.scopedTokenFile;
14628
+ return getScopedPlatformToken({
14629
+ platform: cfg.platformUrl,
14630
+ scope: "app:device:enroll",
14631
+ email: stringOpt(parsed.options.email),
14632
+ label,
14633
+ fetch: doFetch,
14634
+ stdout: out,
14635
+ open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
14636
+ openApprovalUrl: deps.openUrl,
14637
+ rootDir: cfg.rootDir,
14638
+ tokenFile: scopedTokenFile,
14639
+ ...stringOpt(parsed.options.token) ? { token: stringOpt(parsed.options.token) } : {}
14640
+ });
14641
+ }
14642
+ function defaultDeviceName() {
14643
+ return `${import_node_process15.default.env.HOSTNAME ?? import_node_process15.default.env.HOST ?? "machine"}-${import_node_process15.default.platform}`;
14644
+ }
14645
+ var import_node_fs20, import_node_path18, import_node_process15;
14646
+ var init_device_command = __esm({
14647
+ "src/device-command.ts"() {
14648
+ "use strict";
14649
+ init_cjs_shims();
14650
+ import_node_fs20 = require("fs");
14651
+ import_node_path18 = require("path");
14652
+ import_node_process15 = __toESM(require("process"), 1);
14653
+ init_argv();
14654
+ init_admin_ai_auth();
14655
+ init_device_session();
14656
+ init_config();
14657
+ init_operator_context();
14658
+ }
14659
+ });
14660
+
14427
14661
  // src/runbook-actions.ts
14428
14662
  async function call(ctx, method, path, body) {
14429
14663
  const res = await ctx.doFetch(`${ctx.platformUrl.replace(/\/$/, "")}/registry/pm${path}`, {
@@ -14470,7 +14704,7 @@ async function bySlug(ctx, slug) {
14470
14704
  function readBody(file, inline) {
14471
14705
  if (inline !== void 0) return inline;
14472
14706
  if (file === void 0) throw new Error("supply the new text with --file <path>, --file - (stdin), or --body");
14473
- return (0, import_node_fs18.readFileSync)(file === "-" ? 0 : file, "utf8");
14707
+ return (0, import_node_fs21.readFileSync)(file === "-" ? 0 : file, "utf8");
14474
14708
  }
14475
14709
  async function runbookList(ctx, all, query) {
14476
14710
  const params = new URLSearchParams();
@@ -14559,12 +14793,12 @@ async function runbookRemove(ctx, slug) {
14559
14793
  await call(ctx, "DELETE", `/runbook/${encodeURIComponent(runbook.id)}`);
14560
14794
  ctx.out.log(`removed ${slug}`);
14561
14795
  }
14562
- var import_node_fs18, PLATFORM_SCOPE, stamp;
14796
+ var import_node_fs21, PLATFORM_SCOPE, stamp;
14563
14797
  var init_runbook_actions = __esm({
14564
14798
  "src/runbook-actions.ts"() {
14565
14799
  "use strict";
14566
14800
  init_cjs_shims();
14567
- import_node_fs18 = require("fs");
14801
+ import_node_fs21 = require("fs");
14568
14802
  init_version();
14569
14803
  init_runbook_requires();
14570
14804
  PLATFORM_SCOPE = "$platform";
@@ -14597,12 +14831,12 @@ function parseRunbook(text3, slug) {
14597
14831
  };
14598
14832
  }
14599
14833
  function readRunbookDir(dir) {
14600
- if (!(0, import_node_fs19.statSync)(dir, { throwIfNoEntry: false })?.isDirectory()) throw new Error(`not a directory: ${dir}`);
14601
- const files = (0, import_node_fs19.readdirSync)(dir).filter((f) => f.endsWith(".md")).sort();
14834
+ if (!(0, import_node_fs22.statSync)(dir, { throwIfNoEntry: false })?.isDirectory()) throw new Error(`not a directory: ${dir}`);
14835
+ const files = (0, import_node_fs22.readdirSync)(dir).filter((f) => f.endsWith(".md")).sort();
14602
14836
  if (!files.length) throw new Error(`no .md files in ${dir}`);
14603
14837
  return files.map((file) => {
14604
- const slug = (0, import_node_path17.basename)(file, ".md");
14605
- const parsed = parseRunbook((0, import_node_fs19.readFileSync)((0, import_node_path17.join)(dir, file), "utf8"), slug);
14838
+ const slug = (0, import_node_path19.basename)(file, ".md");
14839
+ const parsed = parseRunbook((0, import_node_fs22.readFileSync)((0, import_node_path19.join)(dir, file), "utf8"), slug);
14606
14840
  return { file, slug, ...parsed, words: parsed.body.split(/\s+/).filter(Boolean).length };
14607
14841
  });
14608
14842
  }
@@ -14672,13 +14906,13 @@ async function upsert(ctx, r, visibility) {
14672
14906
  );
14673
14907
  return "updated";
14674
14908
  }
14675
- var import_node_fs19, import_node_path17;
14909
+ var import_node_fs22, import_node_path19;
14676
14910
  var init_runbook_import = __esm({
14677
14911
  "src/runbook-import.ts"() {
14678
14912
  "use strict";
14679
14913
  init_cjs_shims();
14680
- import_node_fs19 = require("fs");
14681
- import_node_path17 = require("path");
14914
+ import_node_fs22 = require("fs");
14915
+ import_node_path19 = require("path");
14682
14916
  init_runbook_actions();
14683
14917
  }
14684
14918
  });
@@ -14856,10 +15090,10 @@ ${body.split("\n").map((line2) => `+${line2}`).join("\n")}
14856
15090
  }
14857
15091
  function manifestLabeller(root) {
14858
15092
  return (workspace) => {
14859
- const manifest = (0, import_node_path18.join)(root, workspace, "package.json");
14860
- if (!(0, import_node_fs20.existsSync)(manifest)) return void 0;
15093
+ const manifest = (0, import_node_path20.join)(root, workspace, "package.json");
15094
+ if (!(0, import_node_fs23.existsSync)(manifest)) return void 0;
14861
15095
  try {
14862
- const name = JSON.parse((0, import_node_fs20.readFileSync)(manifest, "utf8")).name;
15096
+ const name = JSON.parse((0, import_node_fs23.readFileSync)(manifest, "utf8")).name;
14863
15097
  return typeof name === "string" ? name : void 0;
14864
15098
  } catch {
14865
15099
  return void 0;
@@ -14925,7 +15159,7 @@ function report4(ctx, impacts) {
14925
15159
  async function runbookImpact(ctx, options, deps = {}) {
14926
15160
  const cwd = deps.cwd ?? process.cwd();
14927
15161
  const runGit = deps.runGit ?? gitRunner(cwd);
14928
- const read3 = deps.readRepoFile ?? ((path) => (0, import_node_fs20.readFileSync)((0, import_node_path18.join)(cwd, path), "utf8"));
15162
+ const read3 = deps.readRepoFile ?? ((path) => (0, import_node_fs23.readFileSync)((0, import_node_path20.join)(cwd, path), "utf8"));
14929
15163
  const surfaces = changedSurfaces(collectDiff(runGit, options.base, read3), manifestLabeller(cwd));
14930
15164
  if (!surfaces.length) {
14931
15165
  return ctx.out.log(
@@ -14936,14 +15170,14 @@ async function runbookImpact(ctx, options, deps = {}) {
14936
15170
  if (ctx.json) return ctx.out.log(JSON.stringify({ base: options.base, impacts }, null, 2));
14937
15171
  report4(ctx, impacts);
14938
15172
  }
14939
- var import_node_child_process6, import_node_fs20, import_node_path18, SOURCE3, editHint;
15173
+ var import_node_child_process6, import_node_fs23, import_node_path20, SOURCE3, editHint;
14940
15174
  var init_runbook_impact = __esm({
14941
15175
  "src/runbook-impact.ts"() {
14942
15176
  "use strict";
14943
15177
  init_cjs_shims();
14944
15178
  import_node_child_process6 = require("child_process");
14945
- import_node_fs20 = require("fs");
14946
- import_node_path18 = require("path");
15179
+ import_node_fs23 = require("fs");
15180
+ import_node_path20 = require("path");
14947
15181
  init_runbook_impact_scan();
14948
15182
  init_runbook_actions();
14949
15183
  SOURCE3 = /\.(ts|tsx|js|jsx|mts|cts)$/;
@@ -15089,7 +15323,7 @@ var init_runbook_search_command = __esm({
15089
15323
  });
15090
15324
 
15091
15325
  // src/runbook-editor.ts
15092
- function resolveEditor(env = import_node_process14.default.env) {
15326
+ function resolveEditor(env = import_node_process16.default.env) {
15093
15327
  for (const name of EDITOR_ENV) {
15094
15328
  const value2 = env[name];
15095
15329
  if (value2 && value2.trim()) return value2.trim();
@@ -15103,8 +15337,8 @@ function defaultRun(command, path) {
15103
15337
  return result.status ?? 0;
15104
15338
  }
15105
15339
  function editText(initial, slug, deps = {}) {
15106
- const env = deps.env ?? import_node_process14.default.env;
15107
- const interactive = deps.interactive ?? (() => Boolean(import_node_process14.default.stdin.isTTY));
15340
+ const env = deps.env ?? import_node_process16.default.env;
15341
+ const interactive = deps.interactive ?? (() => Boolean(import_node_process16.default.stdin.isTTY));
15108
15342
  const editor = resolveEditor(env);
15109
15343
  if (!editor)
15110
15344
  throw new Error(
@@ -15112,28 +15346,28 @@ function editText(initial, slug, deps = {}) {
15112
15346
  );
15113
15347
  if (!interactive())
15114
15348
  throw new Error(`cannot open an editor without a terminal \u2014 pass --file <path> or --body "\u2026" instead`);
15115
- const dir = (0, import_node_fs21.mkdtempSync)((0, import_node_path19.join)((0, import_node_os4.tmpdir)(), "odla-runbook-"));
15116
- const file = (0, import_node_path19.join)(dir, `${slug}.md`);
15349
+ const dir = (0, import_node_fs24.mkdtempSync)((0, import_node_path21.join)((0, import_node_os5.tmpdir)(), "odla-runbook-"));
15350
+ const file = (0, import_node_path21.join)(dir, `${slug}.md`);
15117
15351
  try {
15118
- (0, import_node_fs21.writeFileSync)(file, initial, { mode: 384 });
15352
+ (0, import_node_fs24.writeFileSync)(file, initial, { mode: 384 });
15119
15353
  const code = defaultRunOrInjected(deps)(editor, file);
15120
15354
  if (code !== 0) throw new Error(`editor "${editor}" exited with ${code}; nothing was written`);
15121
- const edited = (0, import_node_fs21.readFileSync)(file, "utf8");
15355
+ const edited = (0, import_node_fs24.readFileSync)(file, "utf8");
15122
15356
  return edited === initial ? null : edited;
15123
15357
  } finally {
15124
- (0, import_node_fs21.rmSync)(dir, { recursive: true, force: true });
15358
+ (0, import_node_fs24.rmSync)(dir, { recursive: true, force: true });
15125
15359
  }
15126
15360
  }
15127
- var import_node_child_process7, import_node_fs21, import_node_os4, import_node_path19, import_node_process14, EDITOR_ENV, defaultRunOrInjected;
15361
+ var import_node_child_process7, import_node_fs24, import_node_os5, import_node_path21, import_node_process16, EDITOR_ENV, defaultRunOrInjected;
15128
15362
  var init_runbook_editor = __esm({
15129
15363
  "src/runbook-editor.ts"() {
15130
15364
  "use strict";
15131
15365
  init_cjs_shims();
15132
15366
  import_node_child_process7 = require("child_process");
15133
- import_node_fs21 = require("fs");
15134
- import_node_os4 = require("os");
15135
- import_node_path19 = require("path");
15136
- import_node_process14 = __toESM(require("process"), 1);
15367
+ import_node_fs24 = require("fs");
15368
+ import_node_os5 = require("os");
15369
+ import_node_path21 = require("path");
15370
+ import_node_process16 = __toESM(require("process"), 1);
15137
15371
  EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
15138
15372
  defaultRunOrInjected = (deps) => deps.run ?? defaultRun;
15139
15373
  }
@@ -15528,9 +15762,9 @@ async function runHostedSecurity(options) {
15528
15762
  const appId = selfAudit ? "odla-ai" : cfg.app.id;
15529
15763
  const env = selfAudit ? "prod" : selectEnv(options.env, cfg.envs, cfg.configPath, cfg.rootDir);
15530
15764
  const platform = options.platform ?? cfg?.platformUrl ?? "https://odla.ai";
15531
- const target = (0, import_node_path20.resolve)(options.target ?? cfg?.rootDir ?? ".");
15532
- const output = (0, import_node_path20.resolve)(options.out ?? (0, import_node_path20.resolve)(target, ".odla/security/hosted"));
15533
- const outputRelative = (0, import_node_path20.relative)(target, output).split(import_node_path20.sep).join("/");
15765
+ const target = (0, import_node_path22.resolve)(options.target ?? cfg?.rootDir ?? ".");
15766
+ const output = (0, import_node_path22.resolve)(options.out ?? (0, import_node_path22.resolve)(target, ".odla/security/hosted"));
15767
+ const outputRelative = (0, import_node_path22.relative)(target, output).split(import_node_path22.sep).join("/");
15534
15768
  if (!outputRelative) throw new Error("Hosted security output cannot be the repository root");
15535
15769
  const profile = profileFor(options.profile ?? "odla", options.maxHuntTasks ?? 12);
15536
15770
  const tokenRequest = {
@@ -15542,7 +15776,7 @@ async function runHostedSecurity(options) {
15542
15776
  };
15543
15777
  const token = await injectedToken(options, tokenRequest);
15544
15778
  const snapshot = await (0, import_node3.snapshotDirectory)(target, {
15545
- exclude: !outputRelative.startsWith("../") && !(0, import_node_path20.isAbsolute)(outputRelative) ? [outputRelative] : []
15779
+ exclude: !outputRelative.startsWith("../") && !(0, import_node_path22.isAbsolute)(outputRelative) ? [outputRelative] : []
15546
15780
  });
15547
15781
  const hosted = await (0, import_security.createPlatformSecurityReasoners)({
15548
15782
  platform,
@@ -15560,7 +15794,7 @@ async function runHostedSecurity(options) {
15560
15794
  });
15561
15795
  const harness = (0, import_security.createSecurityHarness)({
15562
15796
  profile,
15563
- store: new import_node3.FileRunStore((0, import_node_path20.resolve)(output, "state")),
15797
+ store: new import_node3.FileRunStore((0, import_node_path22.resolve)(output, "state")),
15564
15798
  discoveryReasoner: hosted.discoveryReasoner,
15565
15799
  validationReasoner: hosted.validationReasoner,
15566
15800
  policy: {
@@ -15584,7 +15818,7 @@ async function runHostedSecurity(options) {
15584
15818
  function selectEnv(requested, declared, configPath, rootDir) {
15585
15819
  const env = requested ?? (declared.includes("dev") ? "dev" : declared[0]);
15586
15820
  if (!env || !declared.includes(env)) {
15587
- const shown = (0, import_node_path20.relative)(rootDir, configPath) || configPath;
15821
+ const shown = (0, import_node_path22.relative)(rootDir, configPath) || configPath;
15588
15822
  throw new Error(`env "${env ?? ""}" is not declared in ${shown}`);
15589
15823
  }
15590
15824
  return env;
@@ -15613,17 +15847,17 @@ function printSummary(out, appId, env, run, report5, output) {
15613
15847
  out.log(` coverage: ${report5.coverageStatus} ${complete}/${report5.coverage.length} blocked=${report5.metrics.blockedCells} shallow=${report5.metrics.shallowCells} unscheduled=${report5.metrics.unscheduledCells} budget_exhausted=${report5.metrics.budgetExhaustedCells}`);
15614
15848
  if (report5.callBudget) out.log(` calls: discovery=${formatBudget(report5.callBudget.discovery)} validation=${formatBudget(report5.callBudget.validation)}`);
15615
15849
  out.log(` findings: confirmed=${report5.metrics.confirmed} needs_reproduction=${report5.metrics.needsReproduction} candidates=${report5.metrics.candidates}`);
15616
- out.log(` report: ${(0, import_node_path20.resolve)(output, "REPORT.md")}`);
15850
+ out.log(` report: ${(0, import_node_path22.resolve)(output, "REPORT.md")}`);
15617
15851
  }
15618
15852
  function formatBudget(usage) {
15619
15853
  return usage ? `${usage.usedCalls}/${usage.maxCalls} skipped=${usage.skippedCalls}` : "caller-managed";
15620
15854
  }
15621
- var import_node_path20, import_security, import_node3;
15855
+ var import_node_path22, import_security, import_node3;
15622
15856
  var init_security = __esm({
15623
15857
  "src/security.ts"() {
15624
15858
  "use strict";
15625
15859
  init_cjs_shims();
15626
- import_node_path20 = require("path");
15860
+ import_node_path22 = require("path");
15627
15861
  import_security = require("@odla-ai/security");
15628
15862
  import_node3 = require("@odla-ai/security/node");
15629
15863
  init_config();
@@ -16111,6 +16345,23 @@ __export(cli_exports, {
16111
16345
  runCli: () => runCli
16112
16346
  });
16113
16347
  async function runCli(argv2 = process.argv.slice(2), dependencies = {}) {
16348
+ const out = redactingOutput(dependencies.stdout ?? console);
16349
+ const advisories = [];
16350
+ const withAdvisoryReader = {
16351
+ ...dependencies,
16352
+ fetch: advisoryCollectingFetch(dependencies.fetch ?? fetch, advisories)
16353
+ };
16354
+ try {
16355
+ return await dispatchCli(argv2, withAdvisoryReader);
16356
+ } catch (error) {
16357
+ const explanation = explainRejectedCredential(error);
16358
+ if (explanation) out.error(explanation);
16359
+ throw error;
16360
+ } finally {
16361
+ renderAdvisories(out, advisories);
16362
+ }
16363
+ }
16364
+ async function dispatchCli(argv2, dependencies) {
16114
16365
  const runtime = {
16115
16366
  ...dependencies,
16116
16367
  stdout: redactingOutput(dependencies.stdout ?? console)
@@ -16140,6 +16391,10 @@ async function runCli(argv2 = process.argv.slice(2), dependencies = {}) {
16140
16391
  await contextCommand(parsed, runtime);
16141
16392
  return;
16142
16393
  }
16394
+ if (command === "device") {
16395
+ await deviceCommand(parsed, runtime);
16396
+ return;
16397
+ }
16143
16398
  if (command === "credentials") {
16144
16399
  await credentialCommand(parsed, runtime);
16145
16400
  return;
@@ -16300,6 +16555,9 @@ var init_cli = __esm({
16300
16555
  init_monitor_command();
16301
16556
  init_provision();
16302
16557
  init_record();
16558
+ init_advisory_output();
16559
+ init_cached_credential();
16560
+ init_device_command();
16303
16561
  init_redact();
16304
16562
  init_runbook_command();
16305
16563
  init_security_command();