@odla-ai/cli 0.25.20 → 0.25.22

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
@@ -43,7 +43,9 @@ function approvalLines(prompt) {
43
43
  lines.push("");
44
44
  lines.push(` ${prompt.approvalUrl}`);
45
45
  lines.push("");
46
- lines.push(" A signed-in odla admin opens that URL, checks the code matches, and approves.");
46
+ lines.push(
47
+ ` ${prompt.approver ?? "The matching signed-in odla account"} opens that URL, checks the code matches, and approves.`
48
+ );
47
49
  lines.push(" Opening the link without approving does nothing; this command waits until they do.");
48
50
  if (prompt.browserAttempted) {
49
51
  lines.push(" A browser launch was attempted, but it is best-effort and may have shown no tab.");
@@ -82,22 +84,22 @@ function readJsonFile(path) {
82
84
  return null;
83
85
  }
84
86
  }
85
- function writePrivateJson(path, value) {
86
- writePrivateText(path, `${JSON.stringify(value, null, 2)}
87
+ function writePrivateJson(path, value2) {
88
+ writePrivateText(path, `${JSON.stringify(value2, null, 2)}
87
89
  `);
88
90
  }
89
91
  function readCredentials(path) {
90
92
  if (!(0, import_node_fs.existsSync)(path)) return null;
91
- let value;
93
+ let value2;
92
94
  try {
93
- value = JSON.parse((0, import_node_fs.readFileSync)(path, "utf8"));
95
+ value2 = JSON.parse((0, import_node_fs.readFileSync)(path, "utf8"));
94
96
  } catch {
95
97
  throw new Error(`credentials file ${path} is not valid JSON; fix or remove it before provisioning`);
96
98
  }
97
- if (!value || typeof value !== "object" || typeof value.appId !== "string" || !value.envs || typeof value.envs !== "object") {
99
+ if (!value2 || typeof value2 !== "object" || typeof value2.appId !== "string" || !value2.envs || typeof value2.envs !== "object") {
98
100
  throw new Error(`credentials file ${path} has an invalid shape; fix or remove it before provisioning`);
99
101
  }
100
- return value;
102
+ return value2;
101
103
  }
102
104
  function mergeCredential(current, update) {
103
105
  const next = current ?? {
@@ -392,8 +394,8 @@ function stillPending(pending, email) {
392
394
  { retryable: true }
393
395
  );
394
396
  }
395
- function handshakeEmail(value, cached) {
396
- const email = (value ?? import_node_process3.default.env.ODLA_USER_EMAIL ?? cached ?? "").trim().toLowerCase();
397
+ function handshakeEmail(value2, cached) {
398
+ const email = (value2 ?? import_node_process3.default.env.ODLA_USER_EMAIL ?? cached ?? "").trim().toLowerCase();
397
399
  if (email.length > 254 || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
398
400
  throw new Error("a fresh odla handshake requires --email <account> or ODLA_USER_EMAIL");
399
401
  }
@@ -417,10 +419,10 @@ function handshakeUrl(platformUrl, userCode) {
417
419
  url.searchParams.set("code", userCode);
418
420
  return url.toString();
419
421
  }
420
- function platformAudience(value) {
422
+ function platformAudience(value2) {
421
423
  let url;
422
424
  try {
423
- url = new URL(value);
425
+ url = new URL(value2);
424
426
  } catch {
425
427
  throw new Error("platform must be an absolute URL");
426
428
  }
@@ -439,22 +441,22 @@ var import_node_process4 = __toESM(require("process"), 1);
439
441
  var MAX_BYTES = 64 * 1024;
440
442
  async function secretInputValue(options, kind = "credential") {
441
443
  if (options.fromEnv && options.stdin) throw new Error("choose exactly one of --from-env or --stdin");
442
- let value;
443
- if (options.fromEnv) value = import_node_process4.default.env[options.fromEnv];
444
- else if (options.stdin) value = await (options.readStdin ?? (() => readSecretStream(kind)))();
444
+ let value2;
445
+ if (options.fromEnv) value2 = import_node_process4.default.env[options.fromEnv];
446
+ else if (options.stdin) value2 = await (options.readStdin ?? (() => readSecretStream(kind)))();
445
447
  else throw new Error(`${kind} input required: use --from-env <NAME> or --stdin; values are never accepted as arguments`);
446
- value = value?.replace(/[\r\n]+$/, "");
447
- if (!value) throw new Error(options.fromEnv ? `environment variable ${options.fromEnv} is empty or unset` : `stdin ${kind} is empty`);
448
- if (new TextEncoder().encode(value).byteLength > MAX_BYTES) throw new Error(`${kind} exceeds 64 KiB`);
449
- return value;
448
+ value2 = value2?.replace(/[\r\n]+$/, "");
449
+ if (!value2) throw new Error(options.fromEnv ? `environment variable ${options.fromEnv} is empty or unset` : `stdin ${kind} is empty`);
450
+ if (new TextEncoder().encode(value2).byteLength > MAX_BYTES) throw new Error(`${kind} exceeds 64 KiB`);
451
+ return value2;
450
452
  }
451
453
  async function readSecretStream(kind, stream = import_node_process4.default.stdin) {
452
- let value = "";
454
+ let value2 = "";
453
455
  for await (const chunk of stream) {
454
- value += String(chunk);
455
- if (value.length > MAX_BYTES) throw new Error(`${kind} exceeds 64 KiB`);
456
+ value2 += String(chunk);
457
+ if (value2.length > MAX_BYTES) throw new Error(`${kind} exceeds 64 KiB`);
456
458
  }
457
- return value;
459
+ return value2;
458
460
  }
459
461
 
460
462
  // src/admin-ai-auth.ts
@@ -489,6 +491,8 @@ function audienceBoundEnvToken(token, platform) {
489
491
  return token;
490
492
  }
491
493
  var SCOPE_PURPOSE = {
494
+ "platform:status:read": "read the platform fleet health and deployment snapshot",
495
+ "app:config:read": "compare checked-in intent with an exact-id app Registry configuration",
492
496
  "platform:runbook:write": "add or edit odla's operational runbooks",
493
497
  "platform:ai:policy:write": "change System AI model routing",
494
498
  "platform:ai:policy:read": "read System AI model routing",
@@ -525,6 +529,7 @@ async function scopedToken(platform, scope, options, doFetch, out) {
525
529
  approvalUrl,
526
530
  minutesLeft: Math.floor(expiresIn / 60),
527
531
  purpose: SCOPE_PURPOSE[scope] ?? `use ${scope}`,
532
+ approver: scope.startsWith("app:") ? "A signed-in app owner" : "A signed-in odla platform admin",
528
533
  browserAttempted: browser.open,
529
534
  browserSkipped: browser.reason
530
535
  });
@@ -592,13 +597,13 @@ function apiError(status, body) {
592
597
  const message2 = error && typeof error.message === "string" ? error.message : isRecord(body) && typeof body.message === "string" ? body.message : "request failed";
593
598
  return `read System AI admin changes failed (${status}): ${message2}`;
594
599
  }
595
- function timestamp(value) {
596
- if (typeof value !== "number" || !Number.isFinite(value)) return String(value ?? "");
597
- const date = new Date(value);
600
+ function timestamp(value2) {
601
+ if (typeof value2 !== "number" || !Number.isFinite(value2)) return String(value2 ?? "");
602
+ const date = new Date(value2);
598
603
  return Number.isFinite(date.valueOf()) ? date.toISOString() : "";
599
604
  }
600
- function isRecord(value) {
601
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
605
+ function isRecord(value2) {
606
+ return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
602
607
  }
603
608
 
604
609
  // src/admin-ai-policy.ts
@@ -608,11 +613,11 @@ var SYSTEM_AI_PURPOSES = [
608
613
  "security.validation",
609
614
  "runbook.answer"
610
615
  ];
611
- function requireSystemAiPurpose(value) {
612
- if (!SYSTEM_AI_PURPOSES.includes(value)) {
616
+ function requireSystemAiPurpose(value2) {
617
+ if (!SYSTEM_AI_PURPOSES.includes(value2)) {
613
618
  throw new Error(`purpose must be one of: ${SYSTEM_AI_PURPOSES.join(", ")}`);
614
619
  }
615
- return value;
620
+ return value2;
616
621
  }
617
622
 
618
623
  // src/admin-ai-usage.ts
@@ -634,11 +639,11 @@ async function readAdminAiUsage(request2) {
634
639
  if (request2.json) request2.stdout.log(JSON.stringify(body, null, 2));
635
640
  else printUsage(body, request2.stdout);
636
641
  }
637
- function usageLimit(value) {
638
- if (!Number.isSafeInteger(value) || value < 1 || value > 500) {
642
+ function usageLimit(value2) {
643
+ if (!Number.isSafeInteger(value2) || value2 < 1 || value2 > 500) {
639
644
  throw new Error("usage limit must be an integer from 1 to 500");
640
645
  }
641
- return value;
646
+ return value2;
642
647
  }
643
648
  function printUsage(body, out) {
644
649
  const aggregates = isRecord2(body) && isRecord2(body.aggregates) && Array.isArray(body.aggregates.byStatus) ? body.aggregates.byStatus.filter(isRecord2) : [];
@@ -674,15 +679,15 @@ when app/env run actor purpose/role route / policy tokens cost status`);
674
679
  ].join(" "));
675
680
  }
676
681
  }
677
- function numeric(value) {
678
- return typeof value === "number" && Number.isFinite(value) ? value : 0;
682
+ function numeric(value2) {
683
+ return typeof value2 === "number" && Number.isFinite(value2) ? value2 : 0;
679
684
  }
680
- function formatMicrousd(value) {
681
- return typeof value === "number" && Number.isFinite(value) ? `$${(value / 1e6).toFixed(6)}` : "unpriced";
685
+ function formatMicrousd(value2) {
686
+ return typeof value2 === "number" && Number.isFinite(value2) ? `$${(value2 / 1e6).toFixed(6)}` : "unpriced";
682
687
  }
683
- function timestamp2(value) {
684
- if (typeof value !== "number" || !Number.isFinite(value)) return String(value ?? "");
685
- const date = new Date(value);
688
+ function timestamp2(value2) {
689
+ if (typeof value2 !== "number" || !Number.isFinite(value2)) return String(value2 ?? "");
690
+ const date = new Date(value2);
686
691
  return Number.isFinite(date.valueOf()) ? date.toISOString() : "";
687
692
  }
688
693
  async function responseBody2(res) {
@@ -694,13 +699,13 @@ async function responseBody2(res) {
694
699
  return { message: text.slice(0, 300) };
695
700
  }
696
701
  }
697
- function apiError2(action, status, body) {
702
+ function apiError2(action2, status, body) {
698
703
  const error = isRecord2(body) && isRecord2(body.error) ? body.error : void 0;
699
704
  const message2 = error && typeof error.message === "string" ? error.message : isRecord2(body) && typeof body.message === "string" ? body.message : "request failed";
700
- return `${action} failed (${status}): ${message2}`;
705
+ return `${action2} failed (${status}): ${message2}`;
701
706
  }
702
- function isRecord2(value) {
703
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
707
+ function isRecord2(value2) {
708
+ return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
704
709
  }
705
710
 
706
711
  // src/admin-ai.ts
@@ -745,11 +750,11 @@ async function adminAi(options) {
745
750
  }
746
751
  if (options.action === "credential-set") {
747
752
  const provider = requireProvider(options.credentialProvider);
748
- const value = await secretInputValue(options);
753
+ const value2 = await secretInputValue(options);
749
754
  const res2 = await doFetch(`${platform}/registry/platform/ai-credentials/${encodeURIComponent(provider)}`, {
750
755
  method: "PUT",
751
756
  headers,
752
- body: JSON.stringify({ value })
757
+ body: JSON.stringify({ value: value2 })
753
758
  });
754
759
  const body2 = await responseBody3(res2);
755
760
  if (!res2.ok) throw new Error(apiError3(`store ${provider} platform credential`, res2.status, body2));
@@ -849,11 +854,11 @@ async function adminAi(options) {
849
854
  const policy = isRecord3(response2) && isRecord3(response2.policy) ? response2.policy : isRecord3(response2) ? response2 : {};
850
855
  out.log(options.json ? JSON.stringify({ policy }, null, 2) : `updated ${purpose}: ${String(policy.provider ?? "unchanged")}/${String(policy.model ?? "unchanged")}`);
851
856
  }
852
- function requireProvider(value) {
853
- if (value !== "anthropic" && value !== "openai" && value !== "google") {
857
+ function requireProvider(value2) {
858
+ if (value2 !== "anthropic" && value2 !== "openai" && value2 !== "google") {
854
859
  throw new Error("provider must be one of: anthropic, openai, google");
855
860
  }
856
- return value;
861
+ return value2;
857
862
  }
858
863
  function policyArray(body) {
859
864
  if (isRecord3(body) && Array.isArray(body.policies)) return body.policies.filter(isRecord3);
@@ -864,7 +869,7 @@ function catalogModels(body) {
864
869
  if (!isRecord3(body) || !isRecord3(body.catalog) || !Array.isArray(body.catalog.models)) {
865
870
  throw new Error("platform returned an invalid System AI model catalog");
866
871
  }
867
- return body.catalog.models.filter((value) => isRecord3(value) && typeof value.id === "string" && typeof value.provider === "string");
872
+ return body.catalog.models.filter((value2) => isRecord3(value2) && typeof value2.id === "string" && typeof value2.provider === "string");
868
873
  }
869
874
  async function responseBody3(res) {
870
875
  const text = await res.text();
@@ -875,13 +880,13 @@ async function responseBody3(res) {
875
880
  return { message: text.slice(0, 300) };
876
881
  }
877
882
  }
878
- function apiError3(action, status, body) {
883
+ function apiError3(action2, status, body) {
879
884
  const error = isRecord3(body) && isRecord3(body.error) ? body.error : void 0;
880
885
  const message2 = error && typeof error.message === "string" ? error.message : isRecord3(body) && typeof body.message === "string" ? body.message : "request failed";
881
- return `${action} failed (${status}): ${message2}`;
886
+ return `${action2} failed (${status}): ${message2}`;
882
887
  }
883
- function isRecord3(value) {
884
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
888
+ function isRecord3(value2) {
889
+ return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
885
890
  }
886
891
 
887
892
  // src/argv.ts
@@ -905,10 +910,10 @@ function parseArgv(argv) {
905
910
  addOption(options, rawName, arg.slice(eq + 1));
906
911
  continue;
907
912
  }
908
- const value = argv[i + 1];
909
- if (value !== void 0 && !value.startsWith("--")) {
913
+ const value2 = argv[i + 1];
914
+ if (value2 !== void 0 && !value2.startsWith("--")) {
910
915
  i++;
911
- addOption(options, rawName, value);
916
+ addOption(options, rawName, value2);
912
917
  } else {
913
918
  addOption(options, rawName, true);
914
919
  }
@@ -924,39 +929,39 @@ function assertArgs(parsed, allowedOptions2, maxPositionals) {
924
929
  throw new Error(`unexpected argument "${parsed.positionals[maxPositionals]}"; run "odla-ai help"`);
925
930
  }
926
931
  }
927
- function requiredString(value, name) {
928
- const result = stringOpt(value);
932
+ function requiredString(value2, name) {
933
+ const result = stringOpt(value2);
929
934
  if (!result) throw new Error(`${name} is required`);
930
935
  return result;
931
936
  }
932
- function stringOpt(value) {
933
- if (typeof value === "string") return value;
934
- if (Array.isArray(value)) return value[value.length - 1];
937
+ function stringOpt(value2) {
938
+ if (typeof value2 === "string") return value2;
939
+ if (Array.isArray(value2)) return value2[value2.length - 1];
935
940
  return void 0;
936
941
  }
937
- function listOpt(value) {
938
- if (value === void 0 || typeof value === "boolean") return void 0;
939
- const values = Array.isArray(value) ? value : [value];
942
+ function listOpt(value2) {
943
+ if (value2 === void 0 || typeof value2 === "boolean") return void 0;
944
+ const values = Array.isArray(value2) ? value2 : [value2];
940
945
  return values.flatMap((item) => item.split(",")).map((item) => item.trim()).filter(Boolean);
941
946
  }
942
- function boolOpt(value) {
943
- if (typeof value === "boolean") return value;
944
- if (typeof value === "string" && (value === "true" || value === "false")) return value === "true";
945
- if (value === void 0) return void 0;
947
+ function boolOpt(value2) {
948
+ if (typeof value2 === "boolean") return value2;
949
+ if (typeof value2 === "string" && (value2 === "true" || value2 === "false")) return value2 === "true";
950
+ if (value2 === void 0) return void 0;
946
951
  throw new Error("boolean option must be true or false");
947
952
  }
948
- function numberOpt(value, flag) {
949
- if (value === void 0) return void 0;
950
- const raw = stringOpt(value);
953
+ function numberOpt(value2, flag) {
954
+ if (value2 === void 0) return void 0;
955
+ const raw = stringOpt(value2);
951
956
  const parsed = raw === void 0 ? Number.NaN : Number(raw);
952
957
  if (!Number.isSafeInteger(parsed) || parsed < 1) throw new Error(`${flag} requires a positive integer`);
953
958
  return parsed;
954
959
  }
955
- function addOption(options, name, value) {
960
+ function addOption(options, name, value2) {
956
961
  const current = options[name];
957
- if (current === void 0) options[name] = value;
958
- else if (Array.isArray(current)) current.push(String(value));
959
- else options[name] = [String(current), String(value)];
962
+ if (current === void 0) options[name] = value2;
963
+ else if (Array.isArray(current)) current.push(String(value2));
964
+ else options[name] = [String(current), String(value2)];
960
965
  }
961
966
 
962
967
  // src/operator-context.ts
@@ -1023,17 +1028,17 @@ function validateProbes(integration, at) {
1023
1028
  }
1024
1029
  }
1025
1030
  }
1026
- function isRecord4(value) {
1027
- return value !== null && typeof value === "object" && !Array.isArray(value);
1031
+ function isRecord4(value2) {
1032
+ return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
1028
1033
  }
1029
- function safeText(value, max) {
1030
- return typeof value === "string" && value.trim().length > 0 && value.length <= max && !/[\u0000-\u001f\u007f]/.test(value);
1034
+ function safeText(value2, max) {
1035
+ return typeof value2 === "string" && value2.trim().length > 0 && value2.length <= max && !/[\u0000-\u001f\u007f]/.test(value2);
1031
1036
  }
1032
- function safeProbePath(value) {
1033
- return typeof value === "string" && /^\/[A-Za-z0-9._~!$&'()*+,;=:@%/-]*$/.test(value);
1037
+ function safeProbePath(value2) {
1038
+ return typeof value2 === "string" && /^\/[A-Za-z0-9._~!$&'()*+,;=:@%/-]*$/.test(value2);
1034
1039
  }
1035
- function validId(value) {
1036
- return typeof value === "string" && /^[a-z0-9][a-z0-9-]*$/.test(value);
1040
+ function validId(value2) {
1041
+ return typeof value2 === "string" && /^[a-z0-9][a-z0-9-]*$/.test(value2);
1037
1042
  }
1038
1043
  function unique(values) {
1039
1044
  return [...new Set(values.filter(Boolean))];
@@ -1075,10 +1080,10 @@ async function loadProjectConfig(configPath = "odla.config.mjs") {
1075
1080
  local
1076
1081
  };
1077
1082
  }
1078
- async function resolveDataExport(cfg, value, names) {
1079
- if (value === void 0 || value === null || value === false) return void 0;
1080
- if (typeof value !== "string") return value;
1081
- const target = (0, import_node_path4.isAbsolute)(value) ? value : (0, import_node_path4.resolve)(cfg.rootDir, value);
1083
+ async function resolveDataExport(cfg, value2, names) {
1084
+ if (value2 === void 0 || value2 === null || value2 === false) return void 0;
1085
+ if (typeof value2 !== "string") return value2;
1086
+ const target = (0, import_node_path4.isAbsolute)(value2) ? value2 : (0, import_node_path4.resolve)(cfg.rootDir, value2);
1082
1087
  if (target.endsWith(".json")) {
1083
1088
  return JSON.parse((0, import_node_fs4.readFileSync)(target, "utf8"));
1084
1089
  }
@@ -1087,7 +1092,7 @@ async function resolveDataExport(cfg, value, names) {
1087
1092
  if (mod[name] !== void 0) return mod[name];
1088
1093
  }
1089
1094
  if (mod.default !== void 0) return mod.default;
1090
- throw new Error(`${value} did not export ${names.join(", ")} or default`);
1095
+ throw new Error(`${value2} did not export ${names.join(", ")} or default`);
1091
1096
  }
1092
1097
  function buildPlan(cfg) {
1093
1098
  const integrationSchema = cfg.integrations?.some((integration) => integration.schema) ?? false;
@@ -1121,9 +1126,9 @@ function calendarServiceConfig(cfg, env) {
1121
1126
  };
1122
1127
  }
1123
1128
  function calendarBookingPageUrl(cfg, env) {
1124
- const value = cfg.calendar?.google.bookingPageUrl?.[env];
1125
- if (value === void 0 || value === null) return value;
1126
- return new URL(value).toString();
1129
+ const value2 = cfg.calendar?.google.bookingPageUrl?.[env];
1130
+ if (value2 === void 0 || value2 === null) return value2;
1131
+ return new URL(value2).toString();
1127
1132
  }
1128
1133
  function rulesFromSchema(schema) {
1129
1134
  const entities = serializedEntities(schema);
@@ -1137,10 +1142,10 @@ function serializedEntities(schema) {
1137
1142
  if (!entities || typeof entities !== "object" || Array.isArray(entities)) return [];
1138
1143
  return Object.keys(entities);
1139
1144
  }
1140
- function envValue(value) {
1141
- if (!value) return void 0;
1142
- if (value.startsWith("$")) return process.env[value.slice(1)];
1143
- return value;
1145
+ function envValue(value2) {
1146
+ if (!value2) return void 0;
1147
+ if (value2.startsWith("$")) return process.env[value2.slice(1)];
1148
+ return value2;
1144
1149
  }
1145
1150
  function validateRawConfig(raw, path) {
1146
1151
  if (!raw || typeof raw !== "object") throw new Error(`${path} must export an object`);
@@ -1196,8 +1201,8 @@ function validateCalendarConfig(cfg, envs, services, path) {
1196
1201
  if (!isRecord5(google.bookingCalendar)) throw new Error(`${path}: calendar.google.bookingCalendar must map env names to one calendar id`);
1197
1202
  const unknownBookingEnv = Object.keys(google.bookingCalendar).find((env) => !envs.includes(env));
1198
1203
  if (unknownBookingEnv) throw new Error(`${path}: calendar.google.bookingCalendar.${unknownBookingEnv} is not in config envs`);
1199
- for (const [env, value] of Object.entries(google.bookingCalendar)) {
1200
- if (!safeText2(value, 1024)) {
1204
+ for (const [env, value2] of Object.entries(google.bookingCalendar)) {
1205
+ if (!safeText2(value2, 1024)) {
1201
1206
  throw new Error(`${path}: calendar.google.bookingCalendar.${env} must be a calendar id`);
1202
1207
  }
1203
1208
  }
@@ -1206,8 +1211,8 @@ function validateCalendarConfig(cfg, envs, services, path) {
1206
1211
  if (!isRecord5(google.bookingPageUrl)) throw new Error(`${path}: calendar.google.bookingPageUrl must map env names to HTTPS URLs or null`);
1207
1212
  const unknownBookingEnv = Object.keys(google.bookingPageUrl).find((env) => !envs.includes(env));
1208
1213
  if (unknownBookingEnv) throw new Error(`${path}: calendar.google.bookingPageUrl.${unknownBookingEnv} is not in config envs`);
1209
- for (const [env, value] of Object.entries(google.bookingPageUrl)) {
1210
- if (value !== null && !safeHttpsUrl(value)) {
1214
+ for (const [env, value2] of Object.entries(google.bookingPageUrl)) {
1215
+ if (value2 !== null && !safeHttpsUrl(value2)) {
1211
1216
  throw new Error(`${path}: calendar.google.bookingPageUrl.${env} must be an HTTPS URL without credentials or fragment`);
1212
1217
  }
1213
1218
  }
@@ -1226,37 +1231,37 @@ function validateServices(services, path) {
1226
1231
  }
1227
1232
  }
1228
1233
  }
1229
- function assertOnly(value, allowed, label) {
1230
- const extra = Object.keys(value).find((key) => !allowed.includes(key));
1234
+ function assertOnly(value2, allowed, label) {
1235
+ const extra = Object.keys(value2).find((key) => !allowed.includes(key));
1231
1236
  if (extra) throw new Error(`${label}.${extra} is not supported`);
1232
1237
  }
1233
- function isRecord5(value) {
1234
- return value !== null && typeof value === "object" && !Array.isArray(value);
1238
+ function isRecord5(value2) {
1239
+ return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
1235
1240
  }
1236
- function safeText2(value, max) {
1237
- return typeof value === "string" && value.trim().length > 0 && value.length <= max && !/[\u0000-\u001f\u007f]/.test(value);
1241
+ function safeText2(value2, max) {
1242
+ return typeof value2 === "string" && value2.trim().length > 0 && value2.length <= max && !/[\u0000-\u001f\u007f]/.test(value2);
1238
1243
  }
1239
- function safeHttpsUrl(value) {
1240
- if (typeof value !== "string" || value.length > 2048) return false;
1244
+ function safeHttpsUrl(value2) {
1245
+ if (typeof value2 !== "string" || value2.length > 2048) return false;
1241
1246
  try {
1242
- const url = new URL(value);
1247
+ const url = new URL(value2);
1243
1248
  return url.protocol === "https:" && !url.username && !url.password && !url.hash;
1244
1249
  } catch {
1245
1250
  return false;
1246
1251
  }
1247
1252
  }
1248
- function validId2(value) {
1249
- return typeof value === "string" && /^[a-z0-9][a-z0-9-]*$/.test(value);
1253
+ function validId2(value2) {
1254
+ return typeof value2 === "string" && /^[a-z0-9][a-z0-9-]*$/.test(value2);
1250
1255
  }
1251
1256
  async function loadConfigModule(path) {
1252
1257
  if (path.endsWith(".json")) return JSON.parse((0, import_node_fs4.readFileSync)(path, "utf8"));
1253
1258
  const mod = await import(`${(0, import_node_url.pathToFileURL)(path).href}?t=${Date.now()}`);
1254
- const value = mod.default ?? mod.config;
1255
- if (typeof value === "function") return await value();
1256
- return value;
1259
+ const value2 = mod.default ?? mod.config;
1260
+ if (typeof value2 === "function") return await value2();
1261
+ return value2;
1257
1262
  }
1258
- function trimSlash(value) {
1259
- return value.replace(/\/+$/, "");
1263
+ function trimSlash(value2) {
1264
+ return value2.replace(/\/+$/, "");
1260
1265
  }
1261
1266
  function unique2(values) {
1262
1267
  return [...new Set(values.filter(Boolean))];
@@ -1282,8 +1287,8 @@ function resolveOperatorProfile(parsed) {
1282
1287
  }
1283
1288
  assertOperatorName(name, "context");
1284
1289
  const profiles = readOperatorProfiles(file);
1285
- const value = Object.hasOwn(profiles, name) ? profiles[name] : void 0;
1286
- if (!value) {
1290
+ const value2 = Object.hasOwn(profiles, name) ? profiles[name] : void 0;
1291
+ if (!value2) {
1287
1292
  throw new Error(
1288
1293
  `operator context "${name}" not found in ${file}; run "odla-ai context list" or save it first`
1289
1294
  );
@@ -1292,7 +1297,7 @@ function resolveOperatorProfile(parsed) {
1292
1297
  name,
1293
1298
  source: fromFlag ? "flag" : "environment",
1294
1299
  file,
1295
- value
1300
+ value: value2
1296
1301
  };
1297
1302
  }
1298
1303
  function listOperatorProfiles(file = operatorProfileFile()) {
@@ -1320,8 +1325,8 @@ function operatorCredentialFiles(selection) {
1320
1325
  scoped: (0, import_node_path5.join)(base, "admin-token.local.json")
1321
1326
  };
1322
1327
  }
1323
- function assertOperatorName(value, label) {
1324
- if (!/^[a-z0-9][a-z0-9-]*$/.test(value)) {
1328
+ function assertOperatorName(value2, label) {
1329
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(value2)) {
1325
1330
  throw new Error(
1326
1331
  `${label} must contain lowercase letters, numbers, and hyphens`
1327
1332
  );
@@ -1347,22 +1352,22 @@ function readOperatorProfiles(file) {
1347
1352
  );
1348
1353
  }
1349
1354
  const profiles = emptyProfiles();
1350
- for (const [name, value] of Object.entries(
1355
+ for (const [name, value2] of Object.entries(
1351
1356
  raw.profiles
1352
1357
  )) {
1353
1358
  assertOperatorName(name, "context");
1354
- profiles[name] = validateProfile(value, `operator context "${name}"`);
1359
+ profiles[name] = validateProfile(value2, `operator context "${name}"`);
1355
1360
  }
1356
1361
  return profiles;
1357
1362
  }
1358
1363
  function emptyProfiles() {
1359
1364
  return /* @__PURE__ */ Object.create(null);
1360
1365
  }
1361
- function validateProfile(value, label) {
1362
- if (!value || typeof value !== "object" || Array.isArray(value)) {
1366
+ function validateProfile(value2, label) {
1367
+ if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) {
1363
1368
  throw new Error(`${label} has an invalid shape`);
1364
1369
  }
1365
- const unknown = Object.keys(value).filter(
1370
+ const unknown = Object.keys(value2).filter(
1366
1371
  (key) => !["platform", "app", "environment"].includes(key)
1367
1372
  );
1368
1373
  if (unknown.length > 0) {
@@ -1370,7 +1375,7 @@ function validateProfile(value, label) {
1370
1375
  `${label} contains unsupported field(s): ${unknown.sort().join(", ")}; contexts store scope metadata only, never credentials`
1371
1376
  );
1372
1377
  }
1373
- const candidate = value;
1378
+ const candidate = value2;
1374
1379
  if (typeof candidate.platform !== "string") {
1375
1380
  throw new Error(`${label} needs an absolute platform URL`);
1376
1381
  }
@@ -1385,8 +1390,8 @@ function validateProfile(value, label) {
1385
1390
  ...environment2 ? { environment: environment2 } : {}
1386
1391
  };
1387
1392
  }
1388
- function clean(value) {
1389
- const normalized = value?.trim();
1393
+ function clean(value2) {
1394
+ const normalized = value2?.trim();
1390
1395
  return normalized || void 0;
1391
1396
  }
1392
1397
 
@@ -1479,8 +1484,8 @@ async function resolveOperatorContext(parsed, options = {}) {
1479
1484
  }
1480
1485
  };
1481
1486
  }
1482
- function clean2(value) {
1483
- const normalized = value?.trim();
1487
+ function clean2(value2) {
1488
+ const normalized = value2?.trim();
1484
1489
  return normalized || void 0;
1485
1490
  }
1486
1491
 
@@ -1502,21 +1507,21 @@ var SET_OPTIONS = [
1502
1507
  ];
1503
1508
  async function adminCommand(parsed, deps = {}) {
1504
1509
  const area = parsed.positionals[1];
1505
- const action = parsed.positionals[2];
1506
- const credentialSet = action === "credential" && parsed.positionals[3] === "set";
1507
- const credentials = action === "credentials";
1508
- const models = action === "models";
1509
- const usage = action === "usage";
1510
- const audit = action === "audit";
1511
- if (area !== "ai" || action !== "show" && action !== "set" && !credentialSet && !credentials && !models && !usage && !audit) {
1510
+ const action2 = parsed.positionals[2];
1511
+ const credentialSet = action2 === "credential" && parsed.positionals[3] === "set";
1512
+ const credentials = action2 === "credentials";
1513
+ const models = action2 === "models";
1514
+ const usage = action2 === "usage";
1515
+ const audit = action2 === "audit";
1516
+ if (area !== "ai" || action2 !== "show" && action2 !== "set" && !credentialSet && !credentials && !models && !usage && !audit) {
1512
1517
  throw new Error('unknown admin command. Try "odla-ai admin ai show".');
1513
1518
  }
1514
- const allowed = credentialSet ? [...CONTEXT_OPTIONS, "from-env", "stdin"] : action === "set" ? SET_OPTIONS : models ? [...JSON_OPTIONS, "provider"] : usage ? [...JSON_OPTIONS, "app-id", "env", "run-id", "limit"] : audit ? [...JSON_OPTIONS, "limit"] : JSON_OPTIONS;
1515
- assertArgs(parsed, allowed, credentialSet ? 5 : action === "set" ? 4 : 3);
1519
+ const allowed = credentialSet ? [...CONTEXT_OPTIONS, "from-env", "stdin"] : action2 === "set" ? SET_OPTIONS : models ? [...JSON_OPTIONS, "provider"] : usage ? [...JSON_OPTIONS, "app-id", "env", "run-id", "limit"] : audit ? [...JSON_OPTIONS, "limit"] : JSON_OPTIONS;
1520
+ assertArgs(parsed, allowed, credentialSet ? 5 : action2 === "set" ? 4 : 3);
1516
1521
  const context = await resolveOperatorContext(parsed, { allowMissingConfig: true });
1517
1522
  await adminAi({
1518
- action: credentialSet ? "credential-set" : credentials ? "credentials" : models ? "models" : usage ? "usage" : audit ? "audit" : action,
1519
- purpose: action === "set" ? parsed.positionals[3] : void 0,
1523
+ action: credentialSet ? "credential-set" : credentials ? "credentials" : models ? "models" : usage ? "usage" : audit ? "audit" : action2,
1524
+ purpose: action2 === "set" ? parsed.positionals[3] : void 0,
1520
1525
  provider: stringOpt(parsed.options.provider),
1521
1526
  model: stringOpt(parsed.options.model),
1522
1527
  enabled: boolOpt(parsed.options.enabled),
@@ -1573,12 +1578,12 @@ function bothTenants(cfg) {
1573
1578
 
1574
1579
  // src/agent-command.ts
1575
1580
  async function agentCommand(parsed, deps = {}) {
1576
- const action = parsed.positionals[1];
1577
- if (action !== "jobs" && action !== "retry") {
1578
- throw new Error(`unknown agent action "${action ?? ""}". Try "odla-ai agent jobs --json".`);
1581
+ const action2 = parsed.positionals[1];
1582
+ if (action2 !== "jobs" && action2 !== "retry") {
1583
+ throw new Error(`unknown agent action "${action2 ?? ""}". Try "odla-ai agent jobs --json".`);
1579
1584
  }
1580
- assertArgs(parsed, ["config", "env", "state", "limit", "json", "token", "email"], action === "jobs" ? 2 : 3);
1581
- if (action === "retry" && (parsed.options.state !== void 0 || parsed.options.limit !== void 0)) {
1585
+ assertArgs(parsed, ["config", "env", "state", "limit", "json", "token", "email"], action2 === "jobs" ? 2 : 3);
1586
+ if (action2 === "retry" && (parsed.options.state !== void 0 || parsed.options.limit !== void 0)) {
1582
1587
  throw new Error('--state and --limit are supported only by "agent jobs"');
1583
1588
  }
1584
1589
  const cfg = await loadProjectConfig(stringOpt(parsed.options.config) ?? "odla.config.mjs");
@@ -1598,7 +1603,7 @@ async function agentCommand(parsed, deps = {}) {
1598
1603
  );
1599
1604
  const base = `${cfg.dbEndpoint}/app/${encodeURIComponent(tenant)}/admin/agent-jobs`;
1600
1605
  const headers = { authorization: `Bearer ${credential2}` };
1601
- if (action === "retry") {
1606
+ if (action2 === "retry") {
1602
1607
  const id = parsed.positionals[2];
1603
1608
  const res2 = await doFetch(`${base}/${encodeURIComponent(id)}/retry`, { method: "POST", headers });
1604
1609
  const body2 = await readJson(res2);
@@ -2034,7 +2039,7 @@ async function copyFiles(cfg, token, route2, doFetch, out) {
2034
2039
  }
2035
2040
 
2036
2041
  // src/app-lifecycle.ts
2037
- async function lifecycleCall(action, options) {
2042
+ async function lifecycleCall(action2, options) {
2038
2043
  const cfg = await loadProjectConfig(options.configPath);
2039
2044
  const out = options.stdout ?? console;
2040
2045
  const doFetch = options.fetch ?? fetch;
@@ -2044,13 +2049,13 @@ async function lifecycleCall(action, options) {
2044
2049
  doFetch,
2045
2050
  out
2046
2051
  );
2047
- const res = await doFetch(`${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/${action}`, {
2052
+ const res = await doFetch(`${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/${action2}`, {
2048
2053
  method: "POST",
2049
2054
  headers: { authorization: `Bearer ${token}`, "content-type": "application/json" }
2050
2055
  });
2051
2056
  const body = await res.json().catch(() => ({}));
2052
2057
  if (!res.ok || !body.ok) {
2053
- throw new Error(`${action} failed${body.error?.code ? ` (${body.error.code})` : ""}: ${body.error?.message ?? `registry returned ${res.status}`}`);
2058
+ throw new Error(`${action2} failed${body.error?.code ? ` (${body.error.code})` : ""}: ${body.error?.message ?? `registry returned ${res.status}`}`);
2054
2059
  }
2055
2060
  return body;
2056
2061
  }
@@ -2191,44 +2196,44 @@ var REPLACEMENTS = [
2191
2196
  [/\b(ghp|gho|github_pat)_[A-Za-z0-9_]+/g, "$1_[redacted]"],
2192
2197
  [/\bAKIA[A-Z0-9]{12,}/g, "AKIA[redacted]"]
2193
2198
  ];
2194
- function redactSecrets(value) {
2195
- let result = value;
2199
+ function redactSecrets(value2) {
2200
+ let result = value2;
2196
2201
  for (const [pattern, replacement] of REPLACEMENTS) result = result.replace(pattern, replacement);
2197
2202
  return result;
2198
2203
  }
2199
2204
  function redactingOutput(output) {
2200
- const redactArgs = (args) => args.map((value) => redactOutputValue(value, /* @__PURE__ */ new WeakMap()));
2205
+ const redactArgs = (args) => args.map((value2) => redactOutputValue(value2, /* @__PURE__ */ new WeakMap()));
2201
2206
  return {
2202
2207
  log: (...args) => output.log(...redactArgs(args)),
2203
2208
  error: (...args) => output.error(...redactArgs(args))
2204
2209
  };
2205
2210
  }
2206
- function redactOutputValue(value, seen) {
2207
- if (typeof value === "string") return redactSecrets(value);
2208
- if (value instanceof Error) {
2209
- const redacted = new Error(redactSecrets(value.message));
2210
- redacted.name = value.name;
2211
- if (value.stack) redacted.stack = redactSecrets(value.stack);
2211
+ function redactOutputValue(value2, seen) {
2212
+ if (typeof value2 === "string") return redactSecrets(value2);
2213
+ if (value2 instanceof Error) {
2214
+ const redacted = new Error(redactSecrets(value2.message));
2215
+ redacted.name = value2.name;
2216
+ if (value2.stack) redacted.stack = redactSecrets(value2.stack);
2212
2217
  return redacted;
2213
2218
  }
2214
- if (!value || typeof value !== "object") return value;
2215
- const prior = seen.get(value);
2219
+ if (!value2 || typeof value2 !== "object") return value2;
2220
+ const prior = seen.get(value2);
2216
2221
  if (prior) return prior;
2217
- if (Array.isArray(value)) {
2222
+ if (Array.isArray(value2)) {
2218
2223
  const next2 = [];
2219
- seen.set(value, next2);
2220
- for (const item of value) next2.push(redactOutputValue(item, seen));
2224
+ seen.set(value2, next2);
2225
+ for (const item of value2) next2.push(redactOutputValue(item, seen));
2221
2226
  return next2;
2222
2227
  }
2223
- const prototype = Object.getPrototypeOf(value);
2224
- if (prototype !== Object.prototype && prototype !== null) return value;
2228
+ const prototype = Object.getPrototypeOf(value2);
2229
+ if (prototype !== Object.prototype && prototype !== null) return value2;
2225
2230
  const next = {};
2226
- seen.set(value, next);
2227
- for (const [key, item] of Object.entries(value)) next[key] = redactOutputValue(item, seen);
2231
+ seen.set(value2, next);
2232
+ for (const [key, item] of Object.entries(value2)) next[key] = redactOutputValue(item, seen);
2228
2233
  return next;
2229
2234
  }
2230
- function looksSecret(value) {
2231
- return redactSecrets(value) !== value || value.includes("-----BEGIN");
2235
+ function looksSecret(value2) {
2236
+ return redactSecrets(value2) !== value2 || value2.includes("-----BEGIN");
2232
2237
  }
2233
2238
 
2234
2239
  // src/calendar-http.ts
@@ -2245,9 +2250,9 @@ async function readCalendarStatus(ctx) {
2245
2250
  }
2246
2251
  async function discoverGoogleCalendars(ctx) {
2247
2252
  const raw = await calendarJson(ctx, "/calendars", {});
2248
- const value = record(raw);
2249
- if (!value || !Array.isArray(value.calendars)) throw new Error("calendar discovery returned an invalid response");
2250
- return value.calendars.map((item, index) => {
2253
+ const value2 = record(raw);
2254
+ if (!value2 || !Array.isArray(value2.calendars)) throw new Error("calendar discovery returned an invalid response");
2255
+ return value2.calendars.map((item, index) => {
2251
2256
  const calendar = record(item);
2252
2257
  const id = textField(calendar?.id, 1024);
2253
2258
  if (!calendar || !id) throw new Error(`calendar discovery returned an invalid calendar at index ${index}`);
@@ -2269,10 +2274,10 @@ async function applyCalendarSettings(ctx, bookingPageUrl) {
2269
2274
  }
2270
2275
  async function startCalendarConnection(ctx) {
2271
2276
  const raw = await calendarJson(ctx, "/google/connect", { method: "POST", body: "{}" });
2272
- const value = wrapped(raw, "attempt");
2273
- const attemptId = textField(value.attemptId, 180);
2274
- const consentUrl = textField(value.consentUrl, 4096);
2275
- const expiresAt = timestamp3(value.expiresAt);
2277
+ const value2 = wrapped(raw, "attempt");
2278
+ const attemptId = textField(value2.attemptId, 180);
2279
+ const consentUrl = textField(value2.consentUrl, 4096);
2280
+ const expiresAt = timestamp3(value2.expiresAt);
2276
2281
  if (!attemptId || !consentUrl || expiresAt === void 0) throw new Error("calendar connect returned an invalid attempt");
2277
2282
  return { attemptId, consentUrl, expiresAt };
2278
2283
  }
@@ -2290,42 +2295,42 @@ async function requestCalendarDisconnect(ctx) {
2290
2295
  }
2291
2296
  function parseCalendarStatus(raw, env) {
2292
2297
  const outer = wrapped(raw, "calendar");
2293
- const value = record(outer.attempt) ?? record(outer.status) ?? outer;
2294
- const connection = record(value.connection) ?? {};
2295
- const config = record(value.config) ?? record(outer.config) ?? {};
2298
+ const value2 = record(outer.attempt) ?? record(outer.status) ?? outer;
2299
+ const connection = record(value2.connection) ?? {};
2300
+ const config = record(value2.config) ?? record(outer.config) ?? {};
2296
2301
  const googleConfig = record(config.google) ?? config;
2297
- const stateValue = calendarState(value.status ?? value.state ?? connection.status ?? connection.state);
2302
+ const stateValue = calendarState(value2.status ?? value2.state ?? connection.status ?? connection.state);
2298
2303
  if (!stateValue) {
2299
2304
  throw new Error("calendar status returned an invalid connection state");
2300
2305
  }
2301
- const providerValue = value.provider ?? connection.provider ?? config.provider ?? (config.google ? "google" : void 0);
2306
+ const providerValue = value2.provider ?? connection.provider ?? config.provider ?? (config.google ? "google" : void 0);
2302
2307
  if (providerValue !== void 0 && providerValue !== null && providerValue !== "google") {
2303
2308
  throw new Error("calendar status returned an unsupported provider");
2304
2309
  }
2305
- const accessValue = value.access ?? connection.access ?? config.access ?? googleConfig.access;
2310
+ const accessValue = value2.access ?? connection.access ?? config.access ?? googleConfig.access;
2306
2311
  if (accessValue !== void 0 && accessValue !== "book" && accessValue !== "read") {
2307
2312
  throw new Error("calendar status returned unsupported access");
2308
2313
  }
2309
- const errorValue = record(value.error) ?? record(connection.error);
2310
- const errorCode = textField(value.lastErrorCode, 128);
2311
- const bookingPageValue = Object.hasOwn(value, "bookingPageUrl") ? value.bookingPageUrl : Object.hasOwn(config, "bookingPageUrl") ? config.bookingPageUrl : googleConfig.bookingPageUrl;
2312
- const connected = typeof (value.connected ?? connection.connected) === "boolean" ? Boolean(value.connected ?? connection.connected) : ["healthy", "degraded"].includes(stateValue);
2313
- const bookingCalendarId = textField(value.bookingCalendarId ?? config.bookingCalendarId, 1024);
2314
+ const errorValue = record(value2.error) ?? record(connection.error);
2315
+ const errorCode = textField(value2.lastErrorCode, 128);
2316
+ const bookingPageValue = Object.hasOwn(value2, "bookingPageUrl") ? value2.bookingPageUrl : Object.hasOwn(config, "bookingPageUrl") ? config.bookingPageUrl : googleConfig.bookingPageUrl;
2317
+ const connected = typeof (value2.connected ?? connection.connected) === "boolean" ? Boolean(value2.connected ?? connection.connected) : ["healthy", "degraded"].includes(stateValue);
2318
+ const bookingCalendarId = textField(value2.bookingCalendarId ?? config.bookingCalendarId, 1024);
2314
2319
  return {
2315
2320
  env,
2316
- enabled: typeof value.enabled === "boolean" ? value.enabled : true,
2321
+ enabled: typeof value2.enabled === "boolean" ? value2.enabled : true,
2317
2322
  connected,
2318
- writable: typeof value.writable === "boolean" ? value.writable : stateValue === "healthy",
2323
+ writable: typeof value2.writable === "boolean" ? value2.writable : stateValue === "healthy",
2319
2324
  provider: providerValue === "google" ? "google" : null,
2320
2325
  status: stateValue,
2321
2326
  ...accessValue !== void 0 ? { access: "book" } : {},
2322
2327
  ...bookingCalendarId ? { bookingCalendarId } : {},
2323
2328
  calendars: calendarIds(
2324
- value.availabilityCalendars ?? config.availabilityCalendars ?? value.configuredCalendars ?? value.calendars ?? config.calendars ?? googleConfig.calendars
2329
+ value2.availabilityCalendars ?? config.availabilityCalendars ?? value2.configuredCalendars ?? value2.calendars ?? config.calendars ?? googleConfig.calendars
2325
2330
  ),
2326
2331
  ...optionalNullableUrl("bookingPageUrl", bookingPageValue),
2327
- grantedScopes: stringList(value.grantedScopes ?? connection.grantedScopes ?? connection.scopes),
2328
- ...optionalText("attemptId", value.attemptId ?? connection.attemptId, 180),
2332
+ grantedScopes: stringList(value2.grantedScopes ?? connection.grantedScopes ?? connection.scopes),
2333
+ ...optionalText("attemptId", value2.attemptId ?? connection.attemptId, 180),
2329
2334
  ...errorValue || errorCode ? { error: {
2330
2335
  ...textField(errorValue?.code, 128) ? { code: textField(errorValue?.code, 128) } : {},
2331
2336
  ...textField(errorValue?.message, 500) ? { message: textField(errorValue?.message, 500) } : {},
@@ -2333,9 +2338,9 @@ function parseCalendarStatus(raw, env) {
2333
2338
  } } : {}
2334
2339
  };
2335
2340
  }
2336
- function calendarState(value) {
2337
- if (typeof value !== "string") return null;
2338
- if (CALENDAR_STATES.includes(value)) return value;
2341
+ function calendarState(value2) {
2342
+ if (typeof value2 !== "string") return null;
2343
+ if (CALENDAR_STATES.includes(value2)) return value2;
2339
2344
  const legacy = {
2340
2345
  disabled: "not_connected",
2341
2346
  disconnected: "disconnected",
@@ -2346,7 +2351,7 @@ function calendarState(value) {
2346
2351
  ready: "healthy",
2347
2352
  error: "failed"
2348
2353
  };
2349
- return legacy[value] ?? null;
2354
+ return legacy[value2] ?? null;
2350
2355
  }
2351
2356
  async function calendarJson(ctx, suffix, init) {
2352
2357
  const platform = platformAudience(ctx.platform);
@@ -2380,50 +2385,50 @@ function wrapped(raw, key) {
2380
2385
  if (!outer) throw new Error("calendar returned an invalid response");
2381
2386
  return record(outer[key]) ?? outer;
2382
2387
  }
2383
- function record(value) {
2384
- return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
2388
+ function record(value2) {
2389
+ return value2 !== null && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
2385
2390
  }
2386
- function textField(value, max) {
2387
- return typeof value === "string" && value.length > 0 && value.length <= max && !/[\u0000-\u001f\u007f]/.test(value) ? value : void 0;
2391
+ function textField(value2, max) {
2392
+ return typeof value2 === "string" && value2.length > 0 && value2.length <= max && !/[\u0000-\u001f\u007f]/.test(value2) ? value2 : void 0;
2388
2393
  }
2389
- function stringList(value) {
2390
- return Array.isArray(value) ? [...new Set(value.filter((item) => !!textField(item, 4096)))] : [];
2394
+ function stringList(value2) {
2395
+ return Array.isArray(value2) ? [...new Set(value2.filter((item) => !!textField(item, 4096)))] : [];
2391
2396
  }
2392
- function calendarIds(value) {
2393
- if (!Array.isArray(value)) return [];
2394
- return [...new Set(value.flatMap((item) => {
2397
+ function calendarIds(value2) {
2398
+ if (!Array.isArray(value2)) return [];
2399
+ return [...new Set(value2.flatMap((item) => {
2395
2400
  if (typeof item === "string") return textField(item, 4096) ? [item] : [];
2396
2401
  const calendar = record(item);
2397
2402
  const id = textField(calendar?.id, 4096);
2398
2403
  return id && calendar?.selected !== false ? [id] : [];
2399
2404
  }))];
2400
2405
  }
2401
- function timestamp3(value) {
2402
- if (typeof value === "number" && Number.isFinite(value) && value >= 0) return value;
2403
- if (typeof value === "string") {
2404
- const parsed = Date.parse(value);
2406
+ function timestamp3(value2) {
2407
+ if (typeof value2 === "number" && Number.isFinite(value2) && value2 >= 0) return value2;
2408
+ if (typeof value2 === "string") {
2409
+ const parsed = Date.parse(value2);
2405
2410
  if (Number.isFinite(parsed)) return parsed;
2406
2411
  }
2407
2412
  return void 0;
2408
2413
  }
2409
- function optionalText(key, value, max) {
2410
- const parsed = textField(value, max);
2414
+ function optionalText(key, value2, max) {
2415
+ const parsed = textField(value2, max);
2411
2416
  return parsed ? { [key]: parsed } : {};
2412
2417
  }
2413
- function optionalNullableUrl(key, value) {
2414
- if (value === null) return { [key]: null };
2415
- const parsed = textField(value, 4096);
2418
+ function optionalNullableUrl(key, value2) {
2419
+ if (value2 === null) return { [key]: null };
2420
+ const parsed = textField(value2, 4096);
2416
2421
  return parsed ? { [key]: parsed } : {};
2417
2422
  }
2418
- function identifier(value, name) {
2419
- if (typeof value !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,179}$/.test(value)) throw new Error(`${name} is invalid`);
2420
- return value;
2423
+ function identifier(value2, name) {
2424
+ if (typeof value2 !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,179}$/.test(value2)) throw new Error(`${name} is invalid`);
2425
+ return value2;
2421
2426
  }
2422
- function credential(value) {
2423
- if (typeof value !== "string" || value.length < 8 || value.length > 8192 || /\s|[\u0000-\u001f\u007f]/.test(value)) {
2427
+ function credential(value2) {
2428
+ if (typeof value2 !== "string" || value2.length < 8 || value2.length > 8192 || /\s|[\u0000-\u001f\u007f]/.test(value2)) {
2424
2429
  throw new Error("calendar requires an odla developer token");
2425
2430
  }
2426
- return value;
2431
+ return value2;
2427
2432
  }
2428
2433
 
2429
2434
  // src/calendar-poll.ts
@@ -2584,11 +2589,11 @@ async function connectWithContext(ctx, options) {
2584
2589
  }
2585
2590
  throw new Error('Google Calendar consent timed out; run "odla-ai calendar status" and retry connect');
2586
2591
  }
2587
- function trustedCalendarConsentUrl(platform, value) {
2592
+ function trustedCalendarConsentUrl(platform, value2) {
2588
2593
  const origin = platformAudience(platform);
2589
2594
  let url;
2590
2595
  try {
2591
- url = value.startsWith("/") ? new URL(value, origin) : new URL(value);
2596
+ url = value2.startsWith("/") ? new URL(value2, origin) : new URL(value2);
2592
2597
  } catch {
2593
2598
  throw new Error("odla.ai returned an invalid calendar consent URL");
2594
2599
  }
@@ -2599,12 +2604,12 @@ function trustedCalendarConsentUrl(platform, value) {
2599
2604
  }
2600
2605
  return url.toString();
2601
2606
  }
2602
- function pollTimeout(value = 10 * 6e4) {
2603
- if (!Number.isSafeInteger(value) || value < 1e3 || value > 60 * 6e4) throw new Error("calendar poll timeout must be 1000-3600000ms");
2604
- return value;
2607
+ function pollTimeout(value2 = 10 * 6e4) {
2608
+ if (!Number.isSafeInteger(value2) || value2 < 1e3 || value2 > 60 * 6e4) throw new Error("calendar poll timeout must be 1000-3600000ms");
2609
+ return value2;
2605
2610
  }
2606
- function productionConsent(env, yes, action) {
2607
- if ((env === "prod" || env === "production") && !yes) throw new Error(`refusing to ${action} for "${env}" without --yes`);
2611
+ function productionConsent(env, yes, action2) {
2612
+ if ((env === "prod" || env === "production") && !yes) throw new Error(`refusing to ${action2} for "${env}" without --yes`);
2608
2613
  }
2609
2614
  function printStatus(status, json, out) {
2610
2615
  if (json) {
@@ -2633,6 +2638,8 @@ var CAPABILITIES = {
2633
2638
  "validate integration contracts offline and smoke-test a provisioned db environment plus anonymous capability routes",
2634
2639
  "save and explicitly select non-secret named operator contexts with isolated credential caches; resolve and explain platform, app, environment, and credential provenance without authenticating; then run PM, Discussions, o11y, runbook, and identity operations outside a project checkout",
2635
2640
  "read one versioned o11y status envelope spanning application RED, exact Worker versions and Cloudflare colos observed in traffic, current live-sync freshness/load, the protected commit-to-visible canary, collector ingest/scheduler trust, provider-owned runtime metrics, account-scoped Durable Object, D1, and R2 evidence under odla-db, and a bounded machine verdict",
2641
+ "read one canonical platform fleet snapshot over private service bindings, including release identities, probe latency, Cloudflare load/runtime freshness, explicit unknowns, and stable next actions through a read-only capability",
2642
+ "compare one project's checked-in Registry intent with live owner-visible state and freeze a secret-free plan digest through an exact app:config:read capability",
2636
2643
  "inspect durable agent wakeups as a versioned JSON envelope and explicitly requeue one dead-lettered job with a scoped environment credential",
2637
2644
  "run app-attributed hosted security discovery and independent validation without provider keys",
2638
2645
  "connect/revoke source-read-only GitHub sources and drive commit-pinned hosted security jobs without PATs or provider keys",
@@ -2679,15 +2686,542 @@ function printGroup(out, heading, items) {
2679
2686
  out.log("");
2680
2687
  }
2681
2688
 
2689
+ // src/config-reconcile-command.ts
2690
+ var import_apps6 = require("@odla-ai/apps");
2691
+ var import_node_path7 = require("path");
2692
+
2693
+ // src/config-reconcile-digest.ts
2694
+ var import_node_crypto = require("crypto");
2695
+ function canonicalJson(value2) {
2696
+ return JSON.stringify(canonicalValue(value2));
2697
+ }
2698
+ function configDigest(value2) {
2699
+ return `sha256:${(0, import_node_crypto.createHash)("sha256").update(canonicalJson(value2)).digest("hex")}`;
2700
+ }
2701
+ function canonicalValue(value2) {
2702
+ if (Array.isArray(value2)) return value2.map(canonicalValue);
2703
+ if (value2 && typeof value2 === "object") {
2704
+ return Object.fromEntries(
2705
+ Object.entries(value2).filter(([, entry]) => entry !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, canonicalValue(entry)])
2706
+ );
2707
+ }
2708
+ return value2;
2709
+ }
2710
+
2711
+ // src/config-reconcile-desired.ts
2712
+ var import_apps4 = require("@odla-ai/apps");
2713
+
2714
+ // src/provision-helpers.ts
2715
+ var import_ai = require("@odla-ai/ai");
2716
+ var import_apps3 = require("@odla-ai/apps");
2717
+ function defaultSecretName(provider) {
2718
+ const names = import_ai.DEFAULT_SECRET_NAMES;
2719
+ return names[provider] ?? `${provider}_api_key`;
2720
+ }
2721
+ async function assertTenantAdminAccess(doFetch, cfg, env, token) {
2722
+ const tenantId = (0, import_apps3.tenantIdFor)(cfg.app.id, env);
2723
+ const res = await doFetch(`${cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenantId)}/entitlements`, {
2724
+ headers: { authorization: `Bearer ${token}` }
2725
+ });
2726
+ if (res.ok || res.status === 404) return;
2727
+ if (res.status === 403) {
2728
+ throw new Error(
2729
+ `${env}: you are not an owner of "${cfg.app.id}" (tenant ${tenantId}) \u2014 nothing was minted or written; ask an existing owner to run "odla-ai app owners add <your-email>", then re-run provision`
2730
+ );
2731
+ }
2732
+ throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await safeText3(res)}`);
2733
+ }
2734
+ async function postJson(doFetch, url, bearer, body) {
2735
+ const res = await doFetch(url, {
2736
+ method: "POST",
2737
+ headers: { authorization: `Bearer ${bearer}`, "content-type": "application/json" },
2738
+ body: JSON.stringify(body)
2739
+ });
2740
+ if (!res.ok) throw new Error(`${new URL(url).pathname} failed: ${res.status} ${await safeText3(res)}`);
2741
+ }
2742
+ function normalizeClerkConfig(value2) {
2743
+ if (!value2) return null;
2744
+ if (typeof value2 === "string") {
2745
+ const publishableKey2 = envValue(value2);
2746
+ return publishableKey2 ? { publishableKey: publishableKey2 } : null;
2747
+ }
2748
+ if (typeof value2 !== "object") return null;
2749
+ const cfg = value2;
2750
+ const publishableKey = envValue(cfg.publishableKey);
2751
+ return publishableKey ? { publishableKey, ...cfg.audience ? { audience: cfg.audience } : {}, ...cfg.mode ? { mode: cfg.mode } : {} } : null;
2752
+ }
2753
+ async function safeText3(res) {
2754
+ try {
2755
+ return redactSecrets((await res.text()).slice(0, 500));
2756
+ } catch {
2757
+ return "";
2758
+ }
2759
+ }
2760
+
2761
+ // src/config-reconcile-desired.ts
2762
+ function desiredRegistryState(cfg) {
2763
+ const environments = {};
2764
+ const services = (0, import_apps4.orderAppServices)(cfg.services);
2765
+ for (const env of cfg.envs) {
2766
+ const desiredServices = {};
2767
+ for (const service of services) {
2768
+ desiredServices[service] = {
2769
+ enabled: true,
2770
+ ...managedServiceConfig(cfg, env, service)
2771
+ };
2772
+ }
2773
+ const authoredAuth = cfg.auth?.clerk && Object.hasOwn(cfg.auth.clerk, env) ? cfg.auth.clerk[env] : void 0;
2774
+ const auth = normalizeClerkConfig(authoredAuth);
2775
+ if (authoredAuth && !auth) {
2776
+ throw new Error(`auth.clerk.${env} could not resolve its publishable key`);
2777
+ }
2778
+ const linkManaged = !!cfg.links && Object.hasOwn(cfg.links, env);
2779
+ environments[env] = {
2780
+ services: desiredServices,
2781
+ ...auth ? { auth } : {},
2782
+ ...linkManaged ? { link: cfg.links?.[env] ?? null } : {}
2783
+ };
2784
+ }
2785
+ return {
2786
+ appId: cfg.app.id,
2787
+ name: cfg.app.name,
2788
+ environments
2789
+ };
2790
+ }
2791
+ function managedServiceConfig(cfg, env, service) {
2792
+ if (service === "ai" && cfg.ai?.provider) {
2793
+ return {
2794
+ config: {
2795
+ provider: cfg.ai.provider,
2796
+ ...cfg.ai.model ? { model: cfg.ai.model } : {}
2797
+ }
2798
+ };
2799
+ }
2800
+ if (service === "calendar") return { config: calendarServiceConfig(cfg, env) };
2801
+ return {};
2802
+ }
2803
+
2804
+ // src/config-reconcile.ts
2805
+ var import_apps5 = require("@odla-ai/apps");
2806
+
2807
+ // src/config-reconcile-values.ts
2808
+ function difference(path, desired, observed, status, reason, desiredSource, observedSource, env, service) {
2809
+ return { path, env, service, status, desired, observed, desiredSource, observedSource, reason };
2810
+ }
2811
+ function action(kind, path, before, after, risk, applySupport, reason) {
2812
+ return { kind, path, before, after, risk, requiresApproval: false, applySupport, reason };
2813
+ }
2814
+ function scopedAction(kind, path, env, service, before, after, reason, destructive = false) {
2815
+ const production = env === "prod" || env === "production";
2816
+ return {
2817
+ ...action(
2818
+ kind,
2819
+ path,
2820
+ before,
2821
+ after,
2822
+ destructive ? "high" : production ? "medium" : "low",
2823
+ destructive ? "studio" : "provision",
2824
+ reason
2825
+ ),
2826
+ env,
2827
+ ...service ? { service } : {},
2828
+ requiresApproval: production || destructive
2829
+ };
2830
+ }
2831
+ function observedProjection(app) {
2832
+ return {
2833
+ appId: app.appId,
2834
+ name: app.name,
2835
+ archivedAt: app.archivedAt,
2836
+ environments: Object.fromEntries(
2837
+ Object.entries(app.environments).sort(([left], [right]) => left.localeCompare(right)).map(([env, services]) => [
2838
+ env,
2839
+ Object.fromEntries(Object.entries(services).sort(([left], [right]) => left.localeCompare(right)))
2840
+ ])
2841
+ ),
2842
+ auth: app.auth,
2843
+ links: app.links
2844
+ };
2845
+ }
2846
+ function projectConfig(value2, keys) {
2847
+ return Object.fromEntries(keys.filter((key) => value2[key] !== void 0).map((key) => [key, value2[key]]));
2848
+ }
2849
+ function same(left, right) {
2850
+ return canonicalJson(left) === canonicalJson(right);
2851
+ }
2852
+ function actionId(actionValue) {
2853
+ return `action-${configDigest(actionValue).slice("sha256:".length, "sha256:".length + 16)}`;
2854
+ }
2855
+ function quoteArg(value2) {
2856
+ return `'${value2.replace(/'/g, `'\\''`)}'`;
2857
+ }
2858
+
2859
+ // src/config-reconcile.ts
2860
+ var COVERAGE = {
2861
+ included: [
2862
+ "app display name",
2863
+ "environment service enablement",
2864
+ "AI provider/model and calendar booking service configuration",
2865
+ "auth configuration when explicitly declared",
2866
+ "deployment link when explicitly declared"
2867
+ ],
2868
+ excluded: [
2869
+ { path: "db.schema", reason: "owned by the database data plane" },
2870
+ { path: "db.rules", reason: "owned by the database data plane" },
2871
+ { path: "integrations.seeds", reason: "guarded data-plane writes are not Registry state" },
2872
+ { path: "credentials", reason: "shown-once credentials are intentionally local" },
2873
+ { path: "secrets", reason: "vault and Worker secrets are write-only" },
2874
+ { path: "calendar.connection", reason: "OAuth connection state is provider-owned" },
2875
+ { path: "service.config.opaque", reason: "controller-assigned fields remain service-owned" }
2876
+ ]
2877
+ };
2878
+ function reconcileConfig(input) {
2879
+ const desiredSource = { kind: "project_config", location: input.configPath };
2880
+ const observedSource = {
2881
+ kind: "registry",
2882
+ location: `${input.platformUrl}/registry/apps/${encodeURIComponent(input.desired.appId)}`
2883
+ };
2884
+ const differences = [];
2885
+ const actions = [];
2886
+ const add = (difference2, action2) => {
2887
+ differences.push(difference2);
2888
+ if (action2) actions.push({ ...action2, id: actionId(action2) });
2889
+ };
2890
+ compareApp(input.desired, input.observed, desiredSource, observedSource, add);
2891
+ compareEnvironments(input.desired, input.observed, desiredSource, observedSource, add);
2892
+ const desiredRevision = configDigest(input.desired);
2893
+ const observedRevision = input.observed ? configDigest(observedProjection(input.observed)) : null;
2894
+ const different = differences.filter((entry) => entry.status === "different").length;
2895
+ const unmanaged = differences.length - different;
2896
+ return {
2897
+ generatedAt: input.generatedAt,
2898
+ scope: {
2899
+ appId: input.desired.appId,
2900
+ platformUrl: input.platformUrl,
2901
+ environments: Object.keys(input.desired.environments).sort()
2902
+ },
2903
+ sources: { desired: desiredSource, observed: observedSource },
2904
+ desiredRevision,
2905
+ observedRevision,
2906
+ status: different ? "different" : unmanaged ? "unmanaged" : "in_sync",
2907
+ summary: {
2908
+ differences: different,
2909
+ unmanaged,
2910
+ actions: actions.length,
2911
+ productionActions: actions.filter((action2) => action2.env === "prod" || action2.env === "production").length,
2912
+ highRiskActions: actions.filter((action2) => action2.risk === "high").length
2913
+ },
2914
+ coverage: COVERAGE,
2915
+ differences,
2916
+ actions
2917
+ };
2918
+ }
2919
+ function compareApp(desired, observed, desiredSource, observedSource, add) {
2920
+ if (!observed) {
2921
+ const path = "app";
2922
+ add(
2923
+ difference(path, desired, null, "different", "the app is absent from Registry", desiredSource, observedSource),
2924
+ action(
2925
+ "create_app",
2926
+ path,
2927
+ null,
2928
+ { appId: desired.appId, name: desired.name },
2929
+ "low",
2930
+ "provision",
2931
+ "create the repository-declared app"
2932
+ )
2933
+ );
2934
+ return;
2935
+ }
2936
+ if (observed.name !== desired.name) {
2937
+ const path = "app.name";
2938
+ add(
2939
+ difference(path, desired.name, observed.name, "different", "the checked-in display name differs", desiredSource, observedSource),
2940
+ {
2941
+ ...action("rename_app", path, observed.name, desired.name, "low", "command", "make Registry match the checked-in display name"),
2942
+ command: `odla-ai app rename ${quoteArg(desired.name)} --config ${quoteArg(desiredSource.location)}`
2943
+ }
2944
+ );
2945
+ }
2946
+ }
2947
+ function compareEnvironments(desired, observed, desiredSource, observedSource, add) {
2948
+ const envs = /* @__PURE__ */ new Set([
2949
+ ...Object.keys(desired.environments),
2950
+ ...Object.keys(observed?.environments ?? {}).filter((env) => Object.values(observed?.environments[env] ?? {}).some((service) => service.enabled))
2951
+ ]);
2952
+ for (const env of [...envs].sort()) {
2953
+ compareServices(env, desired, observed, desiredSource, observedSource, add);
2954
+ compareOptionalEnvironmentState(env, desired, observed, desiredSource, observedSource, add);
2955
+ }
2956
+ }
2957
+ function compareServices(env, desired, observed, desiredSource, observedSource, add) {
2958
+ const wanted = desired.environments[env]?.services ?? {};
2959
+ const live = observed?.environments[env] ?? {};
2960
+ const knownOrder = (0, import_apps5.orderAppServices)((0, import_apps5.appServiceIds)());
2961
+ const services = [.../* @__PURE__ */ new Set([...knownOrder, ...Object.keys(wanted), ...Object.keys(live)])];
2962
+ for (const service of services) {
2963
+ const next = wanted[service];
2964
+ const current = live[service];
2965
+ const path = `environments.${env}.services.${service}`;
2966
+ if (next?.enabled && !current?.enabled) {
2967
+ add(
2968
+ difference(path, next, current ?? null, "different", "the repository enables a service Registry does not", desiredSource, observedSource, env, service),
2969
+ scopedAction("enable_service", path, env, service, current ?? null, next, "enable the repository-declared service")
2970
+ );
2971
+ continue;
2972
+ }
2973
+ if (next?.enabled && current?.enabled && next.config) {
2974
+ const projected = projectConfig(current.config, Object.keys(next.config));
2975
+ if (!same(next.config, projected)) {
2976
+ add(
2977
+ difference(`${path}.config`, next.config, projected, "different", "repository-managed service settings differ", desiredSource, observedSource, env, service),
2978
+ scopedAction(
2979
+ "configure_service",
2980
+ `${path}.config`,
2981
+ env,
2982
+ service,
2983
+ projected,
2984
+ next.config,
2985
+ "apply the repository-managed service settings"
2986
+ )
2987
+ );
2988
+ }
2989
+ }
2990
+ }
2991
+ for (const service of [...services].reverse()) {
2992
+ const next = wanted[service];
2993
+ const current = live[service];
2994
+ if (next?.enabled || !current?.enabled) continue;
2995
+ const path = `environments.${env}.services.${service}`;
2996
+ add(
2997
+ difference(path, null, { enabled: true }, "different", "Registry enables a service absent from repository intent", desiredSource, observedSource, env, service),
2998
+ scopedAction(
2999
+ "disable_service",
3000
+ path,
3001
+ env,
3002
+ service,
3003
+ { enabled: true },
3004
+ { enabled: false },
3005
+ "disablement is destructive to availability and requires an explicit Studio review",
3006
+ true
3007
+ )
3008
+ );
3009
+ }
3010
+ }
3011
+ function compareOptionalEnvironmentState(env, desired, observed, desiredSource, observedSource, add) {
3012
+ const wanted = desired.environments[env];
3013
+ const liveAuth = observed?.auth[env] ?? null;
3014
+ if (wanted?.auth) {
3015
+ const projected = liveAuth ? projectConfig(liveAuth, Object.keys(wanted.auth)) : null;
3016
+ if (!same(wanted.auth, projected)) {
3017
+ const path = `environments.${env}.auth`;
3018
+ add(
3019
+ difference(path, wanted.auth, projected, "different", "explicit repository auth settings differ", desiredSource, observedSource, env),
3020
+ scopedAction("set_auth", path, env, void 0, projected, wanted.auth, "apply explicit repository auth settings")
3021
+ );
3022
+ }
3023
+ } else if (liveAuth) {
3024
+ add(difference(
3025
+ `environments.${env}.auth`,
3026
+ null,
3027
+ projectConfig(liveAuth, ["publishableKey", "audience", "mode"]),
3028
+ "unmanaged",
3029
+ "live auth exists but this project config does not manage it",
3030
+ desiredSource,
3031
+ observedSource,
3032
+ env
3033
+ ));
3034
+ }
3035
+ const managesLink = wanted && Object.hasOwn(wanted, "link");
3036
+ const liveLink = observed?.links[env] ?? null;
3037
+ if (managesLink && wanted.link !== liveLink) {
3038
+ const path = `environments.${env}.link`;
3039
+ add(
3040
+ difference(path, wanted.link ?? null, liveLink, "different", "explicit repository deployment link differs", desiredSource, observedSource, env),
3041
+ scopedAction("set_link", path, env, void 0, liveLink, wanted.link ?? null, "apply the repository deployment link")
3042
+ );
3043
+ } else if (!managesLink && liveLink) {
3044
+ add(difference(
3045
+ `environments.${env}.link`,
3046
+ null,
3047
+ liveLink,
3048
+ "unmanaged",
3049
+ "a live deployment link exists but this project config does not manage it",
3050
+ desiredSource,
3051
+ observedSource,
3052
+ env
3053
+ ));
3054
+ }
3055
+ }
3056
+
3057
+ // src/config-reconcile-command.ts
3058
+ async function configDiff(options) {
3059
+ const reconciliation = await inspectConfig(options);
3060
+ const { actions: _actions, ...read3 } = reconciliation;
3061
+ const document2 = {
3062
+ schemaVersion: "odla.config-diff/v1",
3063
+ ...read3,
3064
+ nextActions: diffNextActions(reconciliation, options.configPath)
3065
+ };
3066
+ printDiff(document2, options);
3067
+ return document2;
3068
+ }
3069
+ async function configPlan(options) {
3070
+ const reconciliation = await inspectConfig(options);
3071
+ const planDigest = configDigest({
3072
+ schemaVersion: "odla.config-plan/v1",
3073
+ desiredRevision: reconciliation.desiredRevision,
3074
+ observedRevision: reconciliation.observedRevision,
3075
+ actions: reconciliation.actions
3076
+ });
3077
+ const document2 = {
3078
+ schemaVersion: "odla.config-plan/v1",
3079
+ ...reconciliation,
3080
+ planDigest,
3081
+ apply: {
3082
+ supported: false,
3083
+ reason: "conditional, resumable config apply is not available yet; use the exact reviewed commands below"
3084
+ },
3085
+ nextActions: planNextActions(reconciliation, options.configPath)
3086
+ };
3087
+ printPlan2(document2, options);
3088
+ return document2;
3089
+ }
3090
+ async function inspectConfig(options) {
3091
+ const out = options.stdout ?? console;
3092
+ const cfg = await loadProjectConfig(options.configPath);
3093
+ const doFetch = options.fetch ?? fetch;
3094
+ const token = await resolveAdminPlatformToken({
3095
+ platform: cfg.platformUrl,
3096
+ scope: "app:config:read",
3097
+ token: options.token,
3098
+ tokenFile: (0, import_node_path7.join)(cfg.rootDir, ".odla", "admin-token.local.json"),
3099
+ rootDir: cfg.rootDir,
3100
+ email: options.email,
3101
+ open: options.open,
3102
+ fetch: doFetch,
3103
+ stdout: out,
3104
+ openApprovalUrl: options.openApprovalUrl,
3105
+ label: `odla CLI (${cfg.app.id} config read)`
3106
+ });
3107
+ const client = (0, import_apps6.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
3108
+ const observed = await client.resolveApp(cfg.app.id);
3109
+ return reconcileConfig({
3110
+ desired: desiredRegistryState(cfg),
3111
+ observed,
3112
+ configPath: cfg.configPath,
3113
+ platformUrl: cfg.platformUrl,
3114
+ generatedAt: (options.now?.() ?? /* @__PURE__ */ new Date()).toISOString()
3115
+ });
3116
+ }
3117
+ function printDiff(document2, options) {
3118
+ const out = options.stdout ?? console;
3119
+ if (options.json) {
3120
+ out.log(JSON.stringify(document2, null, 2));
3121
+ return;
3122
+ }
3123
+ printHeader(out, "diff", document2);
3124
+ printDifferences(out, document2);
3125
+ printNext(out, document2.nextActions);
3126
+ }
3127
+ function printPlan2(document2, options) {
3128
+ const out = options.stdout ?? console;
3129
+ if (options.json) {
3130
+ out.log(JSON.stringify(document2, null, 2));
3131
+ return;
3132
+ }
3133
+ printHeader(out, "plan", document2);
3134
+ for (const entry of document2.actions) {
3135
+ const scope = [entry.env, entry.service].filter(Boolean).join("/");
3136
+ out.log(
3137
+ ` ${entry.id} ${entry.kind}${scope ? ` (${scope})` : ""} ${entry.risk}${entry.requiresApproval ? ", approval required" : ""}`
3138
+ );
3139
+ out.log(` ${entry.reason}`);
3140
+ }
3141
+ if (!document2.actions.length) out.log(" no managed changes");
3142
+ out.log(`plan digest: ${document2.planDigest}`);
3143
+ out.log(`apply: unsupported \u2014 ${document2.apply.reason}`);
3144
+ printNext(out, document2.nextActions);
3145
+ }
3146
+ function printHeader(out, kind, document2) {
3147
+ out.log(`config ${kind}: ${document2.scope.appId} \u2014 ${document2.status}`);
3148
+ out.log(`platform: ${document2.scope.platformUrl}`);
3149
+ out.log(`desired: ${document2.desiredRevision}`);
3150
+ out.log(`observed: ${document2.observedRevision ?? "absent"}`);
3151
+ out.log(
3152
+ `summary: ${document2.summary.differences} different, ${document2.summary.unmanaged} unmanaged, ${document2.summary.actions} planned actions`
3153
+ );
3154
+ }
3155
+ function printDifferences(out, document2) {
3156
+ for (const entry of document2.differences) {
3157
+ out.log(` ${entry.status} ${entry.path} ${entry.reason}`);
3158
+ }
3159
+ if (!document2.differences.length) out.log(" managed Registry configuration is in sync");
3160
+ }
3161
+ function printNext(out, next) {
3162
+ if (!next.length) return;
3163
+ out.log("next:");
3164
+ for (const item of next) out.log(` ${item.command}
3165
+ ${item.description}`);
3166
+ }
3167
+ function diffNextActions(reconciliation, configPath) {
3168
+ if (reconciliation.status === "in_sync") {
3169
+ return [{
3170
+ code: "verify_runtime",
3171
+ command: `odla-ai smoke --config ${quoteArg2(configPath)}`,
3172
+ description: "Registry intent matches; verify the service-owned data planes separately."
3173
+ }];
3174
+ }
3175
+ return [{
3176
+ code: "freeze_plan",
3177
+ command: `odla-ai config plan --config ${quoteArg2(configPath)} --json`,
3178
+ description: "Freeze the current desired and observed revisions into a reviewable action plan."
3179
+ }];
3180
+ }
3181
+ function planNextActions(reconciliation, configPath) {
3182
+ const next = [];
3183
+ if (reconciliation.actions.some((action2) => action2.applySupport === "provision")) {
3184
+ next.push({
3185
+ code: "review_provision",
3186
+ command: `odla-ai provision --config ${quoteArg2(configPath)} --dry-run`,
3187
+ description: "Review the existing provisioner's service/auth/link work before applying it."
3188
+ });
3189
+ }
3190
+ for (const action2 of reconciliation.actions.filter((entry) => entry.command)) {
3191
+ if (!next.some((entry) => entry.command === action2.command)) {
3192
+ next.push({ code: action2.kind, command: action2.command, description: action2.reason });
3193
+ }
3194
+ }
3195
+ if (reconciliation.actions.some((action2) => action2.applySupport === "studio")) {
3196
+ const { appId, platformUrl } = reconciliation.scope;
3197
+ next.push({
3198
+ code: "review_destructive",
3199
+ command: `${platformUrl}/studio/apps/${encodeURIComponent(appId)}/settings`,
3200
+ description: "Review service disablement in the owning Studio scope; this plan will not apply it."
3201
+ });
3202
+ }
3203
+ if (!reconciliation.actions.length && reconciliation.summary.unmanaged) {
3204
+ next.push({
3205
+ code: "declare_or_accept_runtime_state",
3206
+ command: `${reconciliation.scope.platformUrl}/studio/apps/${encodeURIComponent(reconciliation.scope.appId)}/settings`,
3207
+ description: "Declare the live value in project config or keep it as an explicit runtime-managed setting."
3208
+ });
3209
+ }
3210
+ return next;
3211
+ }
3212
+ function quoteArg2(value2) {
3213
+ return `'${value2.replace(/'/g, `'\\''`)}'`;
3214
+ }
3215
+
2682
3216
  // src/doctor-checks.ts
2683
3217
  var import_node_child_process3 = require("child_process");
2684
3218
  var import_node_fs10 = require("fs");
2685
- var import_node_path8 = require("path");
3219
+ var import_node_path9 = require("path");
2686
3220
 
2687
3221
  // src/wrangler.ts
2688
3222
  var import_node_child_process2 = require("child_process");
2689
3223
  var import_node_fs9 = require("fs");
2690
- var import_node_path7 = require("path");
3224
+ var import_node_path8 = require("path");
2691
3225
  var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
2692
3226
  const child = (0, import_node_child_process2.spawn)(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
2693
3227
  let stdout = "";
@@ -2701,7 +3235,7 @@ var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) =>
2701
3235
  var WRANGLER_CONFIG_FILES = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"];
2702
3236
  function findWranglerConfig(rootDir) {
2703
3237
  for (const name of WRANGLER_CONFIG_FILES) {
2704
- const path = (0, import_node_path7.join)(rootDir, name);
3238
+ const path = (0, import_node_path8.join)(rootDir, name);
2705
3239
  if ((0, import_node_fs9.existsSync)(path)) return path;
2706
3240
  }
2707
3241
  return null;
@@ -2767,11 +3301,11 @@ function lintRules(rules, entities, publicRead) {
2767
3301
  const warnings = [];
2768
3302
  if (!rules) return warnings;
2769
3303
  for (const [ns, actions] of Object.entries(rules)) {
2770
- for (const [action, expr] of Object.entries(actions)) {
3304
+ for (const [action2, expr] of Object.entries(actions)) {
2771
3305
  if (typeof expr !== "string" || expr.trim() !== "true") continue;
2772
- if (action === "view" && publicRead.includes(ns)) continue;
3306
+ if (action2 === "view" && publicRead.includes(ns)) continue;
2773
3307
  warnings.push(
2774
- action === "view" ? `rules.${ns}.view is "true" \u2014 public read; add "${ns}" to db.publicRead if intended` : `rules.${ns}.${action} is "true" \u2014 any client can ${action} ${ns} rows`
3308
+ action2 === "view" ? `rules.${ns}.view is "true" \u2014 public read; add "${ns}" to db.publicRead if intended` : `rules.${ns}.${action2} is "true" \u2014 any client can ${action2} ${ns} rows`
2775
3309
  );
2776
3310
  }
2777
3311
  }
@@ -2807,17 +3341,17 @@ function wranglerWarnings(rootDir) {
2807
3341
  for (const { label, block } of blocks) {
2808
3342
  const assets = block.assets;
2809
3343
  if (assets?.directory) {
2810
- const dir = (0, import_node_path8.resolve)(rootDir, assets.directory);
2811
- if (dir === (0, import_node_path8.resolve)(rootDir)) {
3344
+ const dir = (0, import_node_path9.resolve)(rootDir, assets.directory);
3345
+ if (dir === (0, import_node_path9.resolve)(rootDir)) {
2812
3346
  warnings.push(`${label}assets.directory is the project root \u2014 point it at a dedicated build dir (wrangler dev fails with "spawn EBADF")`);
2813
- } else if ((0, import_node_fs10.existsSync)((0, import_node_path8.join)(dir, "node_modules"))) {
3347
+ } else if ((0, import_node_fs10.existsSync)((0, import_node_path9.join)(dir, "node_modules"))) {
2814
3348
  warnings.push(`${label}assets.directory contains node_modules \u2014 wrangler dev's watcher will exhaust file descriptors`);
2815
3349
  }
2816
3350
  }
2817
3351
  const vars = block.vars;
2818
3352
  if (vars && typeof vars === "object") {
2819
- for (const [name, value] of Object.entries(vars)) {
2820
- if (name === "ODLA_API_KEY" || name === "ODLA_O11Y_TOKEN" || typeof value === "string" && looksSecret(value)) {
3353
+ for (const [name, value2] of Object.entries(vars)) {
3354
+ if (name === "ODLA_API_KEY" || name === "ODLA_O11Y_TOKEN" || typeof value2 === "string" && looksSecret(value2)) {
2821
3355
  warnings.push(`wrangler ${label}vars.${name} looks like a secret \u2014 use "odla-ai secrets push" / wrangler secret put, never vars`);
2822
3356
  }
2823
3357
  }
@@ -2845,7 +3379,7 @@ function o11yProjectWarnings(rootDir) {
2845
3379
  warnings.push("cannot verify o11y Worker instrumentation \u2014 add a parseable wrangler.jsonc/json config");
2846
3380
  return warnings;
2847
3381
  }
2848
- const main = typeof config.main === "string" ? (0, import_node_path8.resolve)(rootDir, config.main) : null;
3382
+ const main = typeof config.main === "string" ? (0, import_node_path9.resolve)(rootDir, config.main) : null;
2849
3383
  if (!main || !(0, import_node_fs10.existsSync)(main)) {
2850
3384
  warnings.push("cannot verify o11y Worker instrumentation \u2014 wrangler main is missing or unreadable");
2851
3385
  } else {
@@ -2875,7 +3409,7 @@ function calendarProjectWarnings(rootDir) {
2875
3409
  }
2876
3410
  function readPackageJson(rootDir) {
2877
3411
  try {
2878
- return JSON.parse((0, import_node_fs10.readFileSync)((0, import_node_path8.join)(rootDir, "package.json"), "utf8"));
3412
+ return JSON.parse((0, import_node_fs10.readFileSync)((0, import_node_path9.join)(rootDir, "package.json"), "utf8"));
2879
3413
  } catch {
2880
3414
  return null;
2881
3415
  }
@@ -2931,8 +3465,8 @@ function integrationWarnings(integrations, schema, rules) {
2931
3465
  warnings.push(`integration "${integration.id}" has no rules for "${ns}"`);
2932
3466
  continue;
2933
3467
  }
2934
- for (const action of ["view", "create", "update", "delete"]) {
2935
- if (rule[action] === void 0) warnings.push(`integration "${integration.id}" rule "${ns}.${action}" is missing`);
3468
+ for (const action2 of ["view", "create", "update", "delete"]) {
3469
+ if (rule[action2] === void 0) warnings.push(`integration "${integration.id}" rule "${ns}.${action2}" is missing`);
2936
3470
  }
2937
3471
  }
2938
3472
  for (const seed of integration.seeds ?? []) {
@@ -2985,27 +3519,27 @@ function isUniqueAttr(schema, ns, attr) {
2985
3519
  const definition = entity.attrs[attr];
2986
3520
  return isRecord6(definition) && definition.unique === true;
2987
3521
  }
2988
- function normalizeSchema(value) {
2989
- if (value === void 0 || value === null) return { entities: {}, links: {} };
2990
- if (!isRecord6(value) || !isRecord6(value.entities)) {
3522
+ function normalizeSchema(value2) {
3523
+ if (value2 === void 0 || value2 === null) return { entities: {}, links: {} };
3524
+ if (!isRecord6(value2) || !isRecord6(value2.entities)) {
2991
3525
  throw new Error("db schema must be a serialized schema object with an entities map");
2992
3526
  }
2993
- if (value.links !== void 0 && !isRecord6(value.links)) throw new Error("db schema links must be an object");
3527
+ if (value2.links !== void 0 && !isRecord6(value2.links)) throw new Error("db schema links must be an object");
2994
3528
  return {
2995
- entities: { ...value.entities },
2996
- links: { ...value.links ?? {} }
3529
+ entities: { ...value2.entities },
3530
+ links: { ...value2.links ?? {} }
2997
3531
  };
2998
3532
  }
2999
3533
  function mergeMap(target, fragment, label) {
3000
- for (const [name, value] of Object.entries(fragment)) {
3001
- if (Object.hasOwn(target, name) && !(0, import_node_util.isDeepStrictEqual)(target[name], value)) {
3534
+ for (const [name, value2] of Object.entries(fragment)) {
3535
+ if (Object.hasOwn(target, name) && !(0, import_node_util.isDeepStrictEqual)(target[name], value2)) {
3002
3536
  throw new Error(`${label} "${name}" conflicts with an existing definition`);
3003
3537
  }
3004
- target[name] = value;
3538
+ target[name] = value2;
3005
3539
  }
3006
3540
  }
3007
- function isRecord6(value) {
3008
- return value !== null && typeof value === "object" && !Array.isArray(value);
3541
+ function isRecord6(value2) {
3542
+ return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
3009
3543
  }
3010
3544
 
3011
3545
  // src/doctor.ts
@@ -3046,9 +3580,9 @@ async function doctor(options) {
3046
3580
  warnings.push(...integrationWarnings(database.integrations, schema, rules));
3047
3581
  if (cfg.services.includes("ai") && !cfg.ai?.provider) warnings.push("ai service is enabled but ai.provider is not set");
3048
3582
  if (cfg.auth?.clerk) {
3049
- for (const [env, value] of Object.entries(cfg.auth.clerk)) {
3050
- if (typeof value === "string" && value.startsWith("$") && !process.env[value.slice(1)]) {
3051
- warnings.push(`auth.clerk.${env} references unset env var ${value}`);
3583
+ for (const [env, value2] of Object.entries(cfg.auth.clerk)) {
3584
+ if (typeof value2 === "string" && value2.startsWith("$") && !process.env[value2.slice(1)]) {
3585
+ warnings.push(`auth.clerk.${env} references unset env var ${value2}`);
3052
3586
  }
3053
3587
  }
3054
3588
  }
@@ -3090,10 +3624,10 @@ function harnessList(parsed) {
3090
3624
  ];
3091
3625
  return selected.length ? selected : ["all"];
3092
3626
  }
3093
- function harnessOption(value, flag) {
3094
- if (value === void 0) return [];
3095
- if (typeof value === "boolean") throw new Error(`${flag} requires a harness name`);
3096
- const raw = Array.isArray(value) ? value : [value];
3627
+ function harnessOption(value2, flag) {
3628
+ if (value2 === void 0) return [];
3629
+ if (typeof value2 === "boolean") throw new Error(`${flag} requires a harness name`);
3630
+ const raw = Array.isArray(value2) ? value2 : [value2];
3097
3631
  const parts = raw.flatMap((item) => item.split(","));
3098
3632
  if (parts.some((item) => !item.trim())) {
3099
3633
  throw new Error(`${flag} requires a harness name`);
@@ -3103,12 +3637,12 @@ function harnessOption(value, flag) {
3103
3637
 
3104
3638
  // src/init.ts
3105
3639
  var import_node_fs11 = require("fs");
3106
- var import_node_path9 = require("path");
3107
- var import_apps3 = require("@odla-ai/apps");
3640
+ var import_node_path10 = require("path");
3641
+ var import_apps7 = require("@odla-ai/apps");
3108
3642
  function initProject(options) {
3109
3643
  const out = options.stdout ?? console;
3110
- const rootDir = (0, import_node_path9.resolve)(options.rootDir ?? process.cwd());
3111
- const configPath = (0, import_node_path9.resolve)(rootDir, options.configPath ?? "odla.config.mjs");
3644
+ const rootDir = (0, import_node_path10.resolve)(options.rootDir ?? process.cwd());
3645
+ const configPath = (0, import_node_path10.resolve)(rootDir, options.configPath ?? "odla.config.mjs");
3112
3646
  if ((0, import_node_fs11.existsSync)(configPath) && !options.force) {
3113
3647
  throw new Error(`${configPath} already exists. Pass --force to overwrite.`);
3114
3648
  }
@@ -3118,19 +3652,19 @@ function initProject(options) {
3118
3652
  const envs = options.envs?.length ? options.envs : ["dev"];
3119
3653
  const services = options.services?.length ? options.services : ["db", "ai"];
3120
3654
  for (const service of services) {
3121
- const definition = (0, import_apps3.appServiceDefinition)(service);
3122
- if (!definition) throw new Error(`--services contains unknown service "${service}" (known: ${(0, import_apps3.appServiceIds)().join(", ")})`);
3655
+ const definition = (0, import_apps7.appServiceDefinition)(service);
3656
+ if (!definition) throw new Error(`--services contains unknown service "${service}" (known: ${(0, import_apps7.appServiceIds)().join(", ")})`);
3123
3657
  for (const dependency of definition.requires) {
3124
3658
  if (!services.includes(dependency)) throw new Error(`--services ${service} requires ${dependency}`);
3125
3659
  }
3126
3660
  }
3127
3661
  const aiProvider = options.aiProvider ?? "anthropic";
3128
- (0, import_node_fs11.mkdirSync)((0, import_node_path9.dirname)(configPath), { recursive: true });
3129
- (0, import_node_fs11.mkdirSync)((0, import_node_path9.resolve)(rootDir, "src/odla"), { recursive: true });
3130
- (0, import_node_fs11.mkdirSync)((0, import_node_path9.resolve)(rootDir, ".odla"), { recursive: true });
3662
+ (0, import_node_fs11.mkdirSync)((0, import_node_path10.dirname)(configPath), { recursive: true });
3663
+ (0, import_node_fs11.mkdirSync)((0, import_node_path10.resolve)(rootDir, "src/odla"), { recursive: true });
3664
+ (0, import_node_fs11.mkdirSync)((0, import_node_path10.resolve)(rootDir, ".odla"), { recursive: true });
3131
3665
  (0, import_node_fs11.writeFileSync)(configPath, configTemplate({ appId: options.appId, name: options.name, envs, services, aiProvider }));
3132
- writeIfMissing((0, import_node_path9.resolve)(rootDir, "src/odla/schema.mjs"), schemaTemplate());
3133
- writeIfMissing((0, import_node_path9.resolve)(rootDir, "src/odla/rules.mjs"), rulesTemplate());
3666
+ writeIfMissing((0, import_node_path10.resolve)(rootDir, "src/odla/schema.mjs"), schemaTemplate());
3667
+ writeIfMissing((0, import_node_path10.resolve)(rootDir, "src/odla/rules.mjs"), rulesTemplate());
3134
3668
  ensureGitignore(rootDir);
3135
3669
  out.log(`created ${relativeDisplay(configPath, rootDir)}`);
3136
3670
  out.log("created src/odla/schema.mjs and src/odla/rules.mjs");
@@ -3312,8 +3846,8 @@ function assertWranglerConfig(cfg) {
3312
3846
  }
3313
3847
 
3314
3848
  // src/secrets-set.ts
3315
- var import_ai = require("@odla-ai/ai");
3316
- var import_apps4 = require("@odla-ai/apps");
3849
+ var import_ai2 = require("@odla-ai/ai");
3850
+ var import_apps8 = require("@odla-ai/apps");
3317
3851
  var PROD_ENV_NAMES2 = /* @__PURE__ */ new Set(["prod", "production"]);
3318
3852
  async function secretsSet(options) {
3319
3853
  const name = (options.name ?? "").trim();
@@ -3321,38 +3855,38 @@ async function secretsSet(options) {
3321
3855
  if (name.startsWith("$")) {
3322
3856
  throw new Error('"$"-prefixed vault names are platform-reserved; for the Clerk secret key use "odla-ai secrets set-clerk-key"');
3323
3857
  }
3324
- const { cfg, tenantId, value, doFetch, out } = await resolveVaultWrite(options);
3858
+ const { cfg, tenantId, value: value2, doFetch, out } = await resolveVaultWrite(options);
3325
3859
  const token = await getDeveloperToken(cfg, options, doFetch, out);
3326
3860
  try {
3327
- await (0, import_ai.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, name, value);
3861
+ await (0, import_ai2.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, name, value2);
3328
3862
  } catch (err) {
3329
- throw new Error(scrubValue(err instanceof Error ? err.message : String(err), value));
3863
+ throw new Error(scrubValue(err instanceof Error ? err.message : String(err), value2));
3330
3864
  }
3331
3865
  out.log(`${name} stored in the ${tenantId} vault (write-only; the value was never echoed and cannot be read back here)`);
3332
3866
  }
3333
3867
  async function secretsSetClerkKey(options) {
3334
- const { cfg, tenantId, value, doFetch, out } = await resolveVaultWrite(options);
3335
- if (!value.startsWith("sk_")) throw new Error("the Clerk secret key must start with sk_ (sk_test_\u2026 or sk_live_\u2026)");
3336
- if (value.startsWith("sk_test_") && PROD_ENV_NAMES2.has(options.env)) {
3868
+ const { cfg, tenantId, value: value2, doFetch, out } = await resolveVaultWrite(options);
3869
+ if (!value2.startsWith("sk_")) throw new Error("the Clerk secret key must start with sk_ (sk_test_\u2026 or sk_live_\u2026)");
3870
+ if (value2.startsWith("sk_test_") && PROD_ENV_NAMES2.has(options.env)) {
3337
3871
  throw new Error(`refusing to store an sk_test_ Clerk key for "${options.env}" \u2014 use the production instance's sk_live_ key`);
3338
3872
  }
3339
- if (value.startsWith("sk_live_") && !PROD_ENV_NAMES2.has(options.env) && !options.yes) {
3873
+ if (value2.startsWith("sk_live_") && !PROD_ENV_NAMES2.has(options.env) && !options.yes) {
3340
3874
  throw new Error(`refusing to store an sk_live_ Clerk key for "${options.env}" without --yes (live users would sync into a non-prod tenant)`);
3341
3875
  }
3342
3876
  const token = await getDeveloperToken(cfg, options, doFetch, out);
3343
3877
  const res = await doFetch(`${cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenantId)}/clerk-secret`, {
3344
3878
  method: "POST",
3345
3879
  headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
3346
- body: JSON.stringify({ value })
3880
+ body: JSON.stringify({ value: value2 })
3347
3881
  });
3348
3882
  if (!res.ok) {
3349
- const text = scrubValue((await res.text().catch(() => "")).slice(0, 300), value);
3883
+ const text = scrubValue((await res.text().catch(() => "")).slice(0, 300), value2);
3350
3884
  throw new Error(`store Clerk secret key failed (${res.status}): ${text || "request failed"}`);
3351
3885
  }
3352
3886
  out.log(`Clerk secret key stored for ${tenantId} ($clerk_secret, reserved + write-only; the value was never echoed)`);
3353
3887
  }
3354
- function scrubValue(text, value) {
3355
- return redactSecrets(text).split(value).join("[value redacted]");
3888
+ function scrubValue(text, value2) {
3889
+ return redactSecrets(text).split(value2).join("[value redacted]");
3356
3890
  }
3357
3891
  async function resolveVaultWrite(options) {
3358
3892
  const out = options.stdout ?? console;
@@ -3364,14 +3898,14 @@ async function resolveVaultWrite(options) {
3364
3898
  if (PROD_ENV_NAMES2.has(options.env) && !options.yes) {
3365
3899
  throw new Error(`refusing to store a secret for "${options.env}" without --yes`);
3366
3900
  }
3367
- const value = await secretInputValue(options, "secret");
3368
- return { cfg, tenantId: (0, import_apps4.tenantIdFor)(cfg.app.id, options.env), value, doFetch, out };
3901
+ const value2 = await secretInputValue(options, "secret");
3902
+ return { cfg, tenantId: (0, import_apps8.tenantIdFor)(cfg.app.id, options.env), value: value2, doFetch, out };
3369
3903
  }
3370
3904
 
3371
3905
  // src/skill.ts
3372
3906
  var import_node_fs12 = require("fs");
3373
3907
  var import_node_os2 = require("os");
3374
- var import_node_path10 = require("path");
3908
+ var import_node_path11 = require("path");
3375
3909
  var import_node_url2 = require("url");
3376
3910
 
3377
3911
  // src/skill-adapters.ts
@@ -3450,8 +3984,8 @@ function installSkill(options = {}) {
3450
3984
  const files = listFiles(sourceDir);
3451
3985
  if (files.length === 0) throw new Error(`no bundled skills found at ${sourceDir}`);
3452
3986
  const harnesses = normalizeHarnesses(options.harnesses, options.global === true);
3453
- const root = (0, import_node_path10.resolve)(options.dir ?? process.cwd());
3454
- const home = (0, import_node_path10.resolve)(options.homeDir ?? (0, import_node_os2.homedir)());
3987
+ const root = (0, import_node_path11.resolve)(options.dir ?? process.cwd());
3988
+ const home = (0, import_node_path11.resolve)(options.homeDir ?? (0, import_node_os2.homedir)());
3455
3989
  const plans = /* @__PURE__ */ new Map();
3456
3990
  const targets = /* @__PURE__ */ new Map();
3457
3991
  const rememberTarget = (harness, target) => {
@@ -3465,48 +3999,48 @@ function installSkill(options = {}) {
3465
3999
  plans.set(target, { target, content: content2, boundary, managedMerge });
3466
4000
  };
3467
4001
  const planSkillTree = (targetDir2, boundary = root) => {
3468
- for (const rel of files) plan((0, import_node_path10.join)(targetDir2, rel), (0, import_node_fs12.readFileSync)((0, import_node_path10.join)(sourceDir, rel), "utf8"), false, boundary);
4002
+ for (const rel of files) plan((0, import_node_path11.join)(targetDir2, rel), (0, import_node_fs12.readFileSync)((0, import_node_path11.join)(sourceDir, rel), "utf8"), false, boundary);
3469
4003
  };
3470
4004
  let targetDir;
3471
4005
  if (options.global) {
3472
- const claudeRoot = (0, import_node_path10.join)(home, ".claude", "skills");
3473
- const codexRoot = (0, import_node_path10.resolve)(options.codexHomeDir ?? process.env.CODEX_HOME ?? (0, import_node_path10.join)(home, ".codex"), "skills");
4006
+ const claudeRoot = (0, import_node_path11.join)(home, ".claude", "skills");
4007
+ const codexRoot = (0, import_node_path11.resolve)(options.codexHomeDir ?? process.env.CODEX_HOME ?? (0, import_node_path11.join)(home, ".codex"), "skills");
3474
4008
  targetDir = harnesses[0] === "codex" ? codexRoot : claudeRoot;
3475
4009
  for (const harness of harnesses) {
3476
4010
  const skillRoot = harness === "claude" ? claudeRoot : codexRoot;
3477
- planSkillTree(skillRoot, harness === "claude" ? home : (0, import_node_path10.dirname)((0, import_node_path10.dirname)(codexRoot)));
4011
+ planSkillTree(skillRoot, harness === "claude" ? home : (0, import_node_path11.dirname)((0, import_node_path11.dirname)(codexRoot)));
3478
4012
  rememberTarget(harness, skillRoot);
3479
4013
  }
3480
4014
  } else {
3481
- const sharedRoot = (0, import_node_path10.join)(root, ".agents", "skills");
4015
+ const sharedRoot = (0, import_node_path11.join)(root, ".agents", "skills");
3482
4016
  planSkillTree(sharedRoot);
3483
- const claudeRoot = (0, import_node_path10.join)(root, ".claude", "skills");
4017
+ const claudeRoot = (0, import_node_path11.join)(root, ".claude", "skills");
3484
4018
  targetDir = harnesses.includes("claude") ? claudeRoot : sharedRoot;
3485
4019
  for (const harness of harnesses) rememberTarget(harness, sharedRoot);
3486
4020
  if (harnesses.includes("claude")) {
3487
4021
  for (const skill of skillNames(files)) {
3488
- const canonical = (0, import_node_fs12.readFileSync)((0, import_node_path10.join)(sourceDir, skill, "SKILL.md"), "utf8");
3489
- plan((0, import_node_path10.join)(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical));
4022
+ const canonical = (0, import_node_fs12.readFileSync)((0, import_node_path11.join)(sourceDir, skill, "SKILL.md"), "utf8");
4023
+ plan((0, import_node_path11.join)(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical));
3490
4024
  }
3491
4025
  rememberTarget("claude", claudeRoot);
3492
4026
  }
3493
4027
  if (harnesses.includes("cursor")) {
3494
- const cursorRule = (0, import_node_path10.join)(root, ".cursor", "rules", "odla.mdc");
4028
+ const cursorRule = (0, import_node_path11.join)(root, ".cursor", "rules", "odla.mdc");
3495
4029
  plan(cursorRule, CURSOR_RULE);
3496
4030
  rememberTarget("cursor", cursorRule);
3497
4031
  }
3498
4032
  if (harnesses.includes("agents")) {
3499
- const agentsFile = (0, import_node_path10.join)(root, "AGENTS.md");
4033
+ const agentsFile = (0, import_node_path11.join)(root, "AGENTS.md");
3500
4034
  plan(agentsFile, managedFileContent(agentsFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
3501
4035
  rememberTarget("agents", agentsFile);
3502
4036
  }
3503
4037
  if (harnesses.includes("copilot")) {
3504
- const copilotFile = (0, import_node_path10.join)(root, ".github", "copilot-instructions.md");
4038
+ const copilotFile = (0, import_node_path11.join)(root, ".github", "copilot-instructions.md");
3505
4039
  plan(copilotFile, managedFileContent(copilotFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
3506
4040
  rememberTarget("copilot", copilotFile);
3507
4041
  }
3508
4042
  if (harnesses.includes("gemini")) {
3509
- const geminiFile = (0, import_node_path10.join)(root, "GEMINI.md");
4043
+ const geminiFile = (0, import_node_path11.join)(root, "GEMINI.md");
3510
4044
  plan(geminiFile, managedFileContent(geminiFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
3511
4045
  rememberTarget("gemini", geminiFile);
3512
4046
  }
@@ -3542,7 +4076,7 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
3542
4076
  }
3543
4077
  for (const file of plans.values()) {
3544
4078
  if (!(0, import_node_fs12.existsSync)(file.target) || (0, import_node_fs12.readFileSync)(file.target, "utf8") !== file.content) {
3545
- (0, import_node_fs12.mkdirSync)((0, import_node_path10.dirname)(file.target), { recursive: true });
4079
+ (0, import_node_fs12.mkdirSync)((0, import_node_path11.dirname)(file.target), { recursive: true });
3546
4080
  (0, import_node_fs12.writeFileSync)(file.target, file.content);
3547
4081
  }
3548
4082
  }
@@ -3562,7 +4096,7 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
3562
4096
  };
3563
4097
  }
3564
4098
  function pathsUnder(root, paths) {
3565
- return [...paths].map((path) => (0, import_node_path10.relative)(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${import_node_path10.sep}`) && !(0, import_node_path10.isAbsolute)(path)).sort();
4099
+ return [...paths].map((path) => (0, import_node_path11.relative)(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${import_node_path11.sep}`) && !(0, import_node_path11.isAbsolute)(path)).sort();
3566
4100
  }
3567
4101
  function normalizeHarnesses(values, global) {
3568
4102
  const requested = values?.length ? values : ["claude"];
@@ -3607,13 +4141,13 @@ function managedFileContent(path, block, force, boundary) {
3607
4141
  return `${current.slice(0, startAt)}${block}${current.slice(afterEnd)}`;
3608
4142
  }
3609
4143
  function symlinkedComponent(boundary, target) {
3610
- const rel = (0, import_node_path10.relative)(boundary, target);
3611
- if (rel === ".." || rel.startsWith(`..${import_node_path10.sep}`) || (0, import_node_path10.isAbsolute)(rel)) {
4144
+ const rel = (0, import_node_path11.relative)(boundary, target);
4145
+ if (rel === ".." || rel.startsWith(`..${import_node_path11.sep}`) || (0, import_node_path11.isAbsolute)(rel)) {
3612
4146
  throw new Error(`agent setup target escapes its install root: ${target}`);
3613
4147
  }
3614
4148
  let current = boundary;
3615
- for (const part of rel.split(import_node_path10.sep).filter(Boolean)) {
3616
- current = (0, import_node_path10.join)(current, part);
4149
+ for (const part of rel.split(import_node_path11.sep).filter(Boolean)) {
4150
+ current = (0, import_node_path11.join)(current, part);
3617
4151
  try {
3618
4152
  if ((0, import_node_fs12.lstatSync)(current).isSymbolicLink()) return current;
3619
4153
  } catch (error) {
@@ -3630,9 +4164,9 @@ function listFiles(dir) {
3630
4164
  const results = [];
3631
4165
  const walk = (current) => {
3632
4166
  for (const entry of (0, import_node_fs12.readdirSync)(current, { withFileTypes: true })) {
3633
- const path = (0, import_node_path10.join)(current, entry.name);
4167
+ const path = (0, import_node_path11.join)(current, entry.name);
3634
4168
  if (entry.isDirectory()) walk(path);
3635
- else results.push((0, import_node_path10.relative)(dir, path));
4169
+ else results.push((0, import_node_path11.relative)(dir, path));
3636
4170
  }
3637
4171
  };
3638
4172
  walk(dir);
@@ -3708,7 +4242,7 @@ async function smoke(options) {
3708
4242
  out.log(` schema: ${liveEntities.length} entities`);
3709
4243
  const aggregateEntity = expectedEntities[0] ?? liveEntities[0];
3710
4244
  if (aggregateEntity) {
3711
- const aggregate = await postJson(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(entry.tenantId)}/aggregate`, entry.dbKey, {
4245
+ const aggregate = await postJson2(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(entry.tenantId)}/aggregate`, entry.dbKey, {
3712
4246
  ns: aggregateEntity,
3713
4247
  aggregate: { count: true }
3714
4248
  });
@@ -3752,16 +4286,16 @@ async function getJson(doFetch, url, bearer) {
3752
4286
  const res = await doFetch(url, {
3753
4287
  headers: bearer ? { authorization: `Bearer ${bearer}` } : void 0
3754
4288
  });
3755
- if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await safeText3(res)}`);
4289
+ if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await safeText4(res)}`);
3756
4290
  return res.json();
3757
4291
  }
3758
- async function postJson(doFetch, url, bearer, body) {
4292
+ async function postJson2(doFetch, url, bearer, body) {
3759
4293
  const res = await doFetch(url, {
3760
4294
  method: "POST",
3761
4295
  headers: { authorization: `Bearer ${bearer}`, "content-type": "application/json" },
3762
4296
  body: JSON.stringify(body)
3763
4297
  });
3764
- if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await safeText3(res)}`);
4298
+ if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await safeText4(res)}`);
3765
4299
  return res.json();
3766
4300
  }
3767
4301
  function publicConfigUrl(platformUrl, appId, env) {
@@ -3769,7 +4303,7 @@ function publicConfigUrl(platformUrl, appId, env) {
3769
4303
  url.searchParams.set("env", env);
3770
4304
  return url.toString();
3771
4305
  }
3772
- async function safeText3(res) {
4306
+ async function safeText4(res) {
3773
4307
  try {
3774
4308
  return redactSecrets((await res.text()).slice(0, 500));
3775
4309
  } catch {
@@ -3825,6 +4359,26 @@ async function secretsCommand(parsed, deps) {
3825
4359
  });
3826
4360
  }
3827
4361
  async function projectCommand(command, parsed, deps) {
4362
+ if (command === "config") {
4363
+ const sub = parsed.positionals[1];
4364
+ if (sub !== "diff" && sub !== "plan") {
4365
+ throw new Error(`unknown config subcommand "${sub ?? ""}". Try "odla-ai config diff --json".`);
4366
+ }
4367
+ assertArgs(parsed, ["config", "token", "email", "open", "json"], 2);
4368
+ const options = {
4369
+ configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
4370
+ token: stringOpt(parsed.options.token),
4371
+ email: stringOpt(parsed.options.email),
4372
+ open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
4373
+ json: parsed.options.json === true,
4374
+ fetch: deps.fetch,
4375
+ openApprovalUrl: deps.openUrl,
4376
+ stdout: deps.stdout
4377
+ };
4378
+ if (sub === "diff") await configDiff(options);
4379
+ else await configPlan(options);
4380
+ return true;
4381
+ }
3828
4382
  if (command === "init") {
3829
4383
  assertArgs(parsed, ["app-id", "name", "config", "env", "services", "ai-provider", "force"], 1);
3830
4384
  initProject({
@@ -3885,7 +4439,7 @@ async function projectCommand(command, parsed, deps) {
3885
4439
  // src/code-connect.ts
3886
4440
  var import_node_fs14 = require("fs");
3887
4441
  var import_node_os4 = require("os");
3888
- var import_node_path12 = require("path");
4442
+ var import_node_path13 = require("path");
3889
4443
 
3890
4444
  // ../harness/dist/chunk-QTUEF2HZ.js
3891
4445
  var HARNESS_PROTOCOL_VERSION = 1;
@@ -3895,24 +4449,24 @@ var CONTROL = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/;
3895
4449
  var HarnessProtocolError = class extends Error {
3896
4450
  name = "HarnessProtocolError";
3897
4451
  };
3898
- function record2(value) {
3899
- return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
4452
+ function record2(value2) {
4453
+ return value2 !== null && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
3900
4454
  }
3901
- function boundedText(value, label, max) {
3902
- if (typeof value !== "string" || !value || value.length > max || CONTROL.test(value)) {
4455
+ function boundedText(value2, label, max) {
4456
+ if (typeof value2 !== "string" || !value2 || value2.length > max || CONTROL.test(value2)) {
3903
4457
  throw new HarnessProtocolError(`${label} must be a non-empty string of at most ${max} characters`);
3904
4458
  }
3905
- return value;
4459
+ return value2;
3906
4460
  }
3907
4461
  function parseAgentOutput(line) {
3908
4462
  if (Buffer.byteLength(line, "utf8") > 1e6) throw new HarnessProtocolError("agent message exceeds 1 MB");
3909
- let value;
4463
+ let value2;
3910
4464
  try {
3911
- value = JSON.parse(line);
4465
+ value2 = JSON.parse(line);
3912
4466
  } catch {
3913
4467
  throw new HarnessProtocolError("agent emitted invalid JSON");
3914
4468
  }
3915
- const message2 = record2(value);
4469
+ const message2 = record2(value2);
3916
4470
  if (!message2 || message2.protocolVersion !== HARNESS_PROTOCOL_VERSION) {
3917
4471
  throw new HarnessProtocolError(`agent protocolVersion must be ${HARNESS_PROTOCOL_VERSION}`);
3918
4472
  }
@@ -4221,7 +4775,7 @@ async function gitOutput(cwd, args, maxBytes) {
4221
4775
  else stdout.push(chunk);
4222
4776
  });
4223
4777
  child.stderr.on("data", (chunk) => {
4224
- if (stderr.reduce((sum, value) => sum + value.byteLength, 0) < 16384) stderr.push(chunk);
4778
+ if (stderr.reduce((sum, value2) => sum + value2.byteLength, 0) < 16384) stderr.push(chunk);
4225
4779
  });
4226
4780
  const code = await new Promise((accept, reject) => {
4227
4781
  child.once("error", reject);
@@ -4242,7 +4796,7 @@ async function gitBlobs(cwd, entries, maxBytes) {
4242
4796
  else stdout.push(chunk);
4243
4797
  });
4244
4798
  child.stderr.on("data", (chunk) => {
4245
- if (stderr.reduce((sum, value) => sum + value.byteLength, 0) < 16384) stderr.push(chunk);
4799
+ if (stderr.reduce((sum, value2) => sum + value2.byteLength, 0) < 16384) stderr.push(chunk);
4246
4800
  });
4247
4801
  child.stdin.end(`${entries.map((entry) => entry.hash).join("\n")}
4248
4802
  `);
@@ -4280,8 +4834,8 @@ async function materializeGitTree(source, commitSha, options = {}) {
4280
4834
  const maxFiles = options.maxFiles ?? 2e4;
4281
4835
  const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
4282
4836
  const inventory = (await gitOutput(sourceDir, ["ls-tree", "-rz", commitSha], 16 * 1024 * 1024)).toString("utf8").split("\0").filter(Boolean);
4283
- const entries = inventory.flatMap((record7) => {
4284
- const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record7);
4837
+ const entries = inventory.flatMap((record8) => {
4838
+ const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record8);
4285
4839
  return match && allowedWorkspacePath(match[3]) ? [{ mode: match[1], hash: match[2], path: match[3] }] : [];
4286
4840
  });
4287
4841
  if (entries.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
@@ -4356,7 +4910,7 @@ async function gitSourceFiles(sourceDir, maxFiles, maxBytes) {
4356
4910
  else stdout.push(chunk);
4357
4911
  });
4358
4912
  child.stderr.on("data", (chunk) => {
4359
- if (stderr.reduce((sum, value) => sum + value.byteLength, 0) < 16384) stderr.push(chunk);
4913
+ if (stderr.reduce((sum, value2) => sum + value2.byteLength, 0) < 16384) stderr.push(chunk);
4360
4914
  });
4361
4915
  const code = await new Promise((accept, reject) => {
4362
4916
  child.once("error", reject);
@@ -4416,7 +4970,7 @@ async function captureGitDiff(root, maxBytes) {
4416
4970
  else stdout.push(chunk);
4417
4971
  });
4418
4972
  child.stderr.on("data", (chunk) => {
4419
- if (stderr.reduce((sum, value) => sum + value.byteLength, 0) < 16384) stderr.push(chunk);
4973
+ if (stderr.reduce((sum, value2) => sum + value2.byteLength, 0) < 16384) stderr.push(chunk);
4420
4974
  });
4421
4975
  const code = await new Promise((accept, reject) => {
4422
4976
  child.once("error", reject);
@@ -4503,34 +5057,34 @@ var CamelError = class extends Error {
4503
5057
  };
4504
5058
 
4505
5059
  // ../camel/dist/chunk-L5DYU2E2.js
4506
- function canonicalJson(value) {
4507
- return JSON.stringify(normalize(value));
5060
+ function canonicalJson2(value2) {
5061
+ return JSON.stringify(normalize(value2));
4508
5062
  }
4509
- async function sha256Hex(value) {
4510
- const bytes = typeof value === "string" ? new TextEncoder().encode(value) : value;
5063
+ async function sha256Hex(value2) {
5064
+ const bytes = typeof value2 === "string" ? new TextEncoder().encode(value2) : value2;
4511
5065
  const digest = await crypto.subtle.digest("SHA-256", Uint8Array.from(bytes).buffer);
4512
5066
  return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
4513
5067
  }
4514
- function utf8Length(value) {
4515
- if (typeof value === "string") return new TextEncoder().encode(value).byteLength;
4516
- if (value instanceof Uint8Array) return value.byteLength;
5068
+ function utf8Length(value2) {
5069
+ if (typeof value2 === "string") return new TextEncoder().encode(value2).byteLength;
5070
+ if (value2 instanceof Uint8Array) return value2.byteLength;
4517
5071
  try {
4518
- return new TextEncoder().encode(canonicalJson(value)).byteLength;
5072
+ return new TextEncoder().encode(canonicalJson2(value2)).byteLength;
4519
5073
  } catch {
4520
5074
  throw new CamelError("conversion_rejected", "Unsafe input could not be canonically measured.");
4521
5075
  }
4522
5076
  }
4523
- function normalize(value) {
4524
- if (value === null || typeof value === "string" || typeof value === "boolean") return value;
4525
- if (typeof value === "number") {
4526
- if (!Number.isFinite(value)) throw new CamelError("state_conflict", "Canonical JSON rejects non-finite numbers.");
4527
- return value;
5077
+ function normalize(value2) {
5078
+ if (value2 === null || typeof value2 === "string" || typeof value2 === "boolean") return value2;
5079
+ if (typeof value2 === "number") {
5080
+ if (!Number.isFinite(value2)) throw new CamelError("state_conflict", "Canonical JSON rejects non-finite numbers.");
5081
+ return value2;
4528
5082
  }
4529
- if (Array.isArray(value)) return value.map(normalize);
4530
- if (value instanceof Uint8Array) return { $bytes: [...value] };
4531
- if (typeof value === "object") {
4532
- const record7 = value;
4533
- return Object.fromEntries(Object.keys(record7).filter((key) => record7[key] !== void 0).sort().map((key) => [key, normalize(record7[key])]));
5083
+ if (Array.isArray(value2)) return value2.map(normalize);
5084
+ if (value2 instanceof Uint8Array) return { $bytes: [...value2] };
5085
+ if (typeof value2 === "object") {
5086
+ const record8 = value2;
5087
+ return Object.fromEntries(Object.keys(record8).filter((key) => record8[key] !== void 0).sort().map((key) => [key, normalize(record8[key])]));
4534
5088
  }
4535
5089
  throw new CamelError("state_conflict", "Canonical JSON rejects unsupported values.");
4536
5090
  }
@@ -4539,10 +5093,10 @@ function canRead(readers, readerId) {
4539
5093
  }
4540
5094
  function dependenciesOf(values, influence = "data") {
4541
5095
  const result = [];
4542
- for (const value of values) {
4543
- result.push(...value.label.dependencies);
4544
- for (const ref of value.label.provenance) {
4545
- result.push({ ref, influence, promptSafetyAtUse: value.label.promptSafety });
5096
+ for (const value2 of values) {
5097
+ result.push(...value2.label.dependencies);
5098
+ for (const ref of value2.label.provenance) {
5099
+ result.push({ ref, influence, promptSafetyAtUse: value2.label.promptSafety });
4546
5100
  }
4547
5101
  }
4548
5102
  const unique3 = /* @__PURE__ */ new Map();
@@ -4553,32 +5107,32 @@ function dependenciesOf(values, influence = "data") {
4553
5107
  // ../camel/dist/chunk-S7EVNA2U.js
4554
5108
  var camelValueBrand = /* @__PURE__ */ Symbol("@odla-ai/camel/value");
4555
5109
  var authenticCamelValues = /* @__PURE__ */ new WeakSet();
4556
- function isCamelValue(value) {
4557
- return typeof value === "object" && value !== null && authenticCamelValues.has(value);
5110
+ function isCamelValue(value2) {
5111
+ return typeof value2 === "object" && value2 !== null && authenticCamelValues.has(value2);
4558
5112
  }
4559
- function isSafe(value) {
4560
- return isCamelValue(value) && value.label.promptSafety === "safe";
5113
+ function isSafe(value2) {
5114
+ return isCamelValue(value2) && value2.label.promptSafety === "safe";
4561
5115
  }
4562
- function isUnsafe(value) {
4563
- return isCamelValue(value) && value.label.promptSafety === "unsafe";
5116
+ function isUnsafe(value2) {
5117
+ return isCamelValue(value2) && value2.label.promptSafety === "unsafe";
4564
5118
  }
4565
- function createSafeInternal(value, safeBasis, metadata2) {
4566
- return createValue(value, {
5119
+ function createSafeInternal(value2, safeBasis, metadata2) {
5120
+ return createValue(value2, {
4567
5121
  schemaVersion: 1,
4568
5122
  promptSafety: "safe",
4569
5123
  safeBasis,
4570
5124
  ...copyMetadata(metadata2)
4571
5125
  });
4572
5126
  }
4573
- function createUnsafeInternal(value, metadata2) {
4574
- return createValue(value, {
5127
+ function createUnsafeInternal(value2, metadata2) {
5128
+ return createValue(value2, {
4575
5129
  schemaVersion: 1,
4576
5130
  promptSafety: "unsafe",
4577
5131
  ...copyMetadata(metadata2)
4578
5132
  });
4579
5133
  }
4580
- function createValue(value, label) {
4581
- const result = { value, label: Object.freeze(label) };
5134
+ function createValue(value2, label) {
5135
+ const result = { value: value2, label: Object.freeze(label) };
4582
5136
  Object.defineProperty(result, camelValueBrand, { value: label.promptSafety, enumerable: false });
4583
5137
  authenticCamelValues.add(result);
4584
5138
  return Object.freeze(result);
@@ -4641,7 +5195,7 @@ async function digestCodeVerificationReceipt(fields) {
4641
5195
  changedTestsRequireReview: fields.changedTestsRequireReview,
4642
5196
  outcome: fields.outcome
4643
5197
  };
4644
- return `sha256:${await sha256Hex(canonicalJson(canonical))}`;
5198
+ return `sha256:${await sha256Hex(canonicalJson2(canonical))}`;
4645
5199
  }
4646
5200
  function validate(fields) {
4647
5201
  if (fields.schemaVersion !== 1 || typeof fields.verificationId !== "string" || !ID.test(fields.verificationId) || typeof fields.trustedBaseCommitSha !== "string" || !SHA.test(fields.trustedBaseCommitSha) || !DIGEST.test(fields.trustedBaseDigest) || !DIGEST.test(fields.patchDigest) || !DIGEST.test(fields.candidateDigest) || !DIGEST.test(fields.sourceDigest) || !DIGEST.test(fields.policyDigest) || !DIGEST.test(fields.changedTestSetDigest) || !Number.isSafeInteger(fields.changedTestCount) || fields.changedTestCount < 0 || fields.changedTestCount > 1e4 || fields.changedTestsRequireReview !== fields.changedTestCount > 0 || fields.recipes.length < 1 || fields.recipes.length > 64) {
@@ -4679,13 +5233,13 @@ async function createCodePortableCheckpoint(input) {
4679
5233
  const state2 = normalizeState(input.state);
4680
5234
  validateBaseAndPatch(input.baseCommitSha, input.patch);
4681
5235
  const patchDigest = `sha256:${await sha256Hex(input.patch)}`;
4682
- const stateDigest = `sha256:${await sha256Hex(canonicalJson(state2))}`;
5236
+ const stateDigest = `sha256:${await sha256Hex(canonicalJson2(state2))}`;
4683
5237
  const fields = { schemaVersion: 1, baseCommitSha: input.baseCommitSha, patchDigest, state: state2, stateDigest };
4684
- const checkpointDigest = `sha256:${await sha256Hex(canonicalJson(fields))}`;
5238
+ const checkpointDigest = `sha256:${await sha256Hex(canonicalJson2(fields))}`;
4685
5239
  return freeze({ ...fields, patch: input.patch, checkpointDigest });
4686
5240
  }
4687
- async function verifyCodePortableCheckpoint(value) {
4688
- const input = object(value, "checkpoint");
5241
+ async function verifyCodePortableCheckpoint(value2) {
5242
+ const input = object(value2, "checkpoint");
4689
5243
  exact2(input, ["schemaVersion", "baseCommitSha", "patch", "patchDigest", "state", "stateDigest", "checkpointDigest"]);
4690
5244
  if (input.schemaVersion !== 1 || typeof input.baseCommitSha !== "string" || typeof input.patch !== "string" || typeof input.patchDigest !== "string" || typeof input.stateDigest !== "string" || typeof input.checkpointDigest !== "string") {
4691
5245
  throw invalid2("Portable checkpoint fields are malformed.");
@@ -4700,8 +5254,8 @@ async function verifyCodePortableCheckpoint(value) {
4700
5254
  }
4701
5255
  return created;
4702
5256
  }
4703
- function normalizeState(value) {
4704
- const state2 = object(value, "state");
5257
+ function normalizeState(value2) {
5258
+ const state2 = object(value2, "state");
4705
5259
  exact2(state2, [
4706
5260
  "planCursor",
4707
5261
  "conversationRefs",
@@ -4759,30 +5313,30 @@ function validateBaseAndPatch(baseCommitSha, patch2) {
4759
5313
  throw invalid2("Portable checkpoint base or patch is malformed or outside its bounds.");
4760
5314
  }
4761
5315
  }
4762
- function strings(value, name, pattern, maximum, sort) {
4763
- if (!Array.isArray(value) || value.length > maximum || value.some((item) => typeof item !== "string" || !pattern.test(item)) || new Set(value).size !== value.length) {
5316
+ function strings(value2, name, pattern, maximum, sort) {
5317
+ if (!Array.isArray(value2) || value2.length > maximum || value2.some((item) => typeof item !== "string" || !pattern.test(item)) || new Set(value2).size !== value2.length) {
4764
5318
  throw invalid2(`Portable checkpoint ${name} is malformed or outside its bounds.`);
4765
5319
  }
4766
- const result = [...value];
5320
+ const result = [...value2];
4767
5321
  return sort ? result.sort() : result;
4768
5322
  }
4769
- function object(value, name) {
4770
- if (!value || typeof value !== "object" || Array.isArray(value)) throw invalid2(`Portable ${name} must be an object.`);
4771
- return value;
5323
+ function object(value2, name) {
5324
+ if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) throw invalid2(`Portable ${name} must be an object.`);
5325
+ return value2;
4772
5326
  }
4773
- function exact2(value, keys) {
4774
- if (Object.keys(value).some((key) => !keys.includes(key)) || keys.some((key) => !(key in value))) {
5327
+ function exact2(value2, keys) {
5328
+ if (Object.keys(value2).some((key) => !keys.includes(key)) || keys.some((key) => !(key in value2))) {
4775
5329
  throw invalid2("Portable checkpoint contains missing or unsupported fields.");
4776
5330
  }
4777
5331
  }
4778
- function freeze(value) {
5332
+ function freeze(value2) {
4779
5333
  return Object.freeze({
4780
- ...value,
5334
+ ...value2,
4781
5335
  state: Object.freeze({
4782
- ...value.state,
4783
- conversationRefs: Object.freeze([...value.state.conversationRefs]),
4784
- completedEffects: Object.freeze(value.state.completedEffects.map((item) => Object.freeze({ ...item }))),
4785
- unresolvedApprovals: Object.freeze([...value.state.unresolvedApprovals])
5336
+ ...value2.state,
5337
+ conversationRefs: Object.freeze([...value2.state.conversationRefs]),
5338
+ completedEffects: Object.freeze(value2.state.completedEffects.map((item) => Object.freeze({ ...item }))),
5339
+ unresolvedApprovals: Object.freeze([...value2.state.unresolvedApprovals])
4786
5340
  })
4787
5341
  });
4788
5342
  }
@@ -4799,7 +5353,7 @@ async function digestCodeRepositorySnapshot(snapshot, limits = DEFAULT_LIMITS) {
4799
5353
  commitSha: snapshot.commitSha,
4800
5354
  files: [...snapshot.files].map((file) => ({ path: file.path, content: file.content })).sort((left, right) => left.path.localeCompare(right.path))
4801
5355
  };
4802
- return `sha256:${await sha256Hex(canonicalJson(normalized))}`;
5356
+ return `sha256:${await sha256Hex(canonicalJson2(normalized))}`;
4803
5357
  }
4804
5358
  function validateSnapshot(snapshot, limits) {
4805
5359
  if (!REPOSITORY.test(snapshot.repository) || !SHA4.test(snapshot.commitSha)) {
@@ -4841,10 +5395,10 @@ var import_path9 = require("path");
4841
5395
 
4842
5396
  // ../camel/dist/chunk-LAXU2AVK.js
4843
5397
  function conversionPolicyDigest(policy) {
4844
- return sha256Hex(canonicalJson(policy));
5398
+ return sha256Hex(canonicalJson2(policy));
4845
5399
  }
4846
5400
  function registeredIdRegistryDigest(values) {
4847
- return sha256Hex(canonicalJson(values));
5401
+ return sha256Hex(canonicalJson2(values));
4848
5402
  }
4849
5403
  async function createConversionRegistry(config) {
4850
5404
  const policies = /* @__PURE__ */ new Map();
@@ -4873,61 +5427,61 @@ async function createConversionRegistry(config) {
4873
5427
  if (utf8Length(source.value) > policy.maximumSourceBytes) throw new CamelError("limit_exceeded", "Unsafe conversion input exceeds its byte bound.");
4874
5428
  return policy;
4875
5429
  };
4876
- const emit3 = (source, policy, value) => convert(source, policy, value, outputCounts);
5430
+ const emit3 = (source, policy, value2) => convert(source, policy, value2, outputCounts);
4877
5431
  const operations = Object.freeze({
4878
- boolean: async (value, id) => {
4879
- const policy = checked(value, id, "boolean");
4880
- return emit3(value, policy, requireBoolean(value.value));
5432
+ boolean: async (value2, id) => {
5433
+ const policy = checked(value2, id, "boolean");
5434
+ return emit3(value2, policy, requireBoolean(value2.value));
4881
5435
  },
4882
- integer: async (value, id) => {
4883
- const policy = checked(value, id, "integer");
4884
- return emit3(value, policy, boundedInteger(value.value, policy.output));
5436
+ integer: async (value2, id) => {
5437
+ const policy = checked(value2, id, "integer");
5438
+ return emit3(value2, policy, boundedInteger(value2.value, policy.output));
4885
5439
  },
4886
- finiteNumber: async (value, id) => {
4887
- const policy = checked(value, id, "finite_number");
4888
- return emit3(value, policy, boundedNumber(value.value, policy.output));
5440
+ finiteNumber: async (value2, id) => {
5441
+ const policy = checked(value2, id, "finite_number");
5442
+ return emit3(value2, policy, boundedNumber(value2.value, policy.output));
4889
5443
  },
4890
- enum: async (value, id) => {
4891
- const policy = checked(value, id, "enum");
4892
- return emit3(value, policy, enumMember(value.value, policy.output));
5444
+ enum: async (value2, id) => {
5445
+ const policy = checked(value2, id, "enum");
5446
+ return emit3(value2, policy, enumMember(value2.value, policy.output));
4893
5447
  },
4894
- date: async (value, id) => {
4895
- const policy = checked(value, id, "date");
4896
- return emit3(value, policy, canonicalDate(value.value, policy.output));
5448
+ date: async (value2, id) => {
5449
+ const policy = checked(value2, id, "date");
5450
+ return emit3(value2, policy, canonicalDate(value2.value, policy.output));
4897
5451
  },
4898
- registeredId: async (value, id) => {
4899
- const policy = checked(value, id, "registered_id");
5452
+ registeredId: async (value2, id) => {
5453
+ const policy = checked(value2, id, "registered_id");
4900
5454
  const registry = config.registeredIds?.[policy.output.registryId];
4901
- const output = typeof value.value === "string" ? registry?.values[value.value] : void 0;
5455
+ const output = typeof value2.value === "string" ? registry?.values[value2.value] : void 0;
4902
5456
  if (!output) throw new CamelError("conversion_rejected", "Registered-ID conversion rejected the candidate.");
4903
- return emit3(value, policy, output);
5457
+ return emit3(value2, policy, output);
4904
5458
  },
4905
- digest: async (value, id) => {
4906
- const policy = checked(value, id, "digest");
4907
- if (!(value.value instanceof Uint8Array)) throw new CamelError("conversion_rejected", "Digest conversion requires bytes.");
4908
- return emit3(value, policy, await sha256Hex(value.value));
5459
+ digest: async (value2, id) => {
5460
+ const policy = checked(value2, id, "digest");
5461
+ if (!(value2.value instanceof Uint8Array)) throw new CamelError("conversion_rejected", "Digest conversion requires bytes.");
5462
+ return emit3(value2, policy, await sha256Hex(value2.value));
4909
5463
  },
4910
- measure: async (value, metric, id) => {
4911
- const policy = checked(value, id, "integer");
4912
- const measured = measure(value.value, metric);
4913
- return emit3(value, policy, boundedInteger(measured, policy.output));
5464
+ measure: async (value2, metric, id) => {
5465
+ const policy = checked(value2, id, "integer");
5466
+ const measured = measure(value2.value, metric);
5467
+ return emit3(value2, policy, boundedInteger(measured, policy.output));
4914
5468
  },
4915
- test: async (value, predicateId, id) => {
4916
- const policy = checked(value, id, "boolean");
5469
+ test: async (value2, predicateId, id) => {
5470
+ const policy = checked(value2, id, "boolean");
4917
5471
  const predicate = config.predicates?.[predicateId];
4918
5472
  if (!predicate) throw new CamelError("conversion_rejected", "Predicate is not registered.");
4919
- return emit3(value, policy, evaluatePredicate(value.value, predicate, config.registeredIds));
5473
+ return emit3(value2, policy, evaluatePredicate(value2.value, predicate, config.registeredIds));
4920
5474
  }
4921
5475
  });
4922
5476
  return Object.freeze({ operations, policy: (id) => policies.get(id) ?? missingPolicy() });
4923
5477
  }
4924
- async function convert(source, policy, value, counts) {
5478
+ async function convert(source, policy, value2, counts) {
4925
5479
  const sourceKey = sourceIdentity(source);
4926
5480
  const countKey = `${policy.conversionId}\0${sourceKey}`;
4927
5481
  const count = counts.get(countKey) ?? 0;
4928
5482
  if (count >= policy.maximumOutputsPerArtifact) throw new CamelError("limit_exceeded", "Conversion output count exceeds its per-source bound.");
4929
5483
  counts.set(countKey, count + 1);
4930
- return createSafeInternal(value, "atomic_conversion", {
5484
+ return createSafeInternal(value2, "atomic_conversion", {
4931
5485
  readers: source.label.readers,
4932
5486
  provenance: [...source.label.provenance, { kind: "converter", id: policy.conversionId, digest: policy.digest }],
4933
5487
  dependencies: dependenciesOf([source])
@@ -4939,49 +5493,49 @@ function validatePolicyShape(policy) {
4939
5493
  const output = policy.output;
4940
5494
  if ((output.kind === "integer" || output.kind === "finite_number") && (!Number.isFinite(output.minimum) || !Number.isFinite(output.maximum) || output.minimum > output.maximum)) throw new CamelError("state_conflict", "Numeric conversion bounds are invalid.");
4941
5495
  if (output.kind === "finite_number" && (!Number.isSafeInteger(output.maximumDecimalPlaces) || output.maximumDecimalPlaces < 0 || output.maximumDecimalPlaces > 15)) throw new CamelError("state_conflict", "Decimal-place bound is invalid.");
4942
- if (output.kind === "enum" && (output.values.length === 0 || output.values.some((value) => typeof value !== "string" || !value) || new Set(output.values).size !== output.values.length)) throw new CamelError("state_conflict", "Enum conversion members must be unique and non-empty.");
5496
+ if (output.kind === "enum" && (output.values.length === 0 || output.values.some((value2) => typeof value2 !== "string" || !value2) || new Set(output.values).size !== output.values.length)) throw new CamelError("state_conflict", "Enum conversion members must be unique and non-empty.");
4943
5497
  if (output.kind === "registered_id" && (!output.registryId || !output.registryDigest)) throw new CamelError("state_conflict", "Registered-ID conversion identity is invalid.");
4944
5498
  if (output.kind === "date" && output.format !== "date" && output.format !== "rfc3339") throw new CamelError("state_conflict", "Date conversion format is invalid.");
4945
5499
  if (output.kind === "digest" && output.algorithm !== "sha256") throw new CamelError("state_conflict", "Digest conversion algorithm is invalid.");
4946
5500
  }
4947
- function requireBoolean(value) {
4948
- if (typeof value !== "boolean") throw new CamelError("conversion_rejected", "Boolean conversion requires a structured boolean.");
4949
- return value;
5501
+ function requireBoolean(value2) {
5502
+ if (typeof value2 !== "boolean") throw new CamelError("conversion_rejected", "Boolean conversion requires a structured boolean.");
5503
+ return value2;
4950
5504
  }
4951
- function boundedInteger(value, spec) {
4952
- if (spec.kind !== "integer" || typeof value !== "number" || !Number.isSafeInteger(value) || value < spec.minimum || value > spec.maximum) throw new CamelError("conversion_rejected", "Integer conversion rejected the structured value.");
4953
- return value;
5505
+ function boundedInteger(value2, spec) {
5506
+ if (spec.kind !== "integer" || typeof value2 !== "number" || !Number.isSafeInteger(value2) || value2 < spec.minimum || value2 > spec.maximum) throw new CamelError("conversion_rejected", "Integer conversion rejected the structured value.");
5507
+ return value2;
4954
5508
  }
4955
- function boundedNumber(value, spec) {
4956
- if (spec.kind !== "finite_number" || typeof value !== "number" || !Number.isFinite(value) || value < spec.minimum || value > spec.maximum) throw new CamelError("conversion_rejected", "Finite-number conversion rejected the structured value.");
4957
- const text = String(value);
5509
+ function boundedNumber(value2, spec) {
5510
+ if (spec.kind !== "finite_number" || typeof value2 !== "number" || !Number.isFinite(value2) || value2 < spec.minimum || value2 > spec.maximum) throw new CamelError("conversion_rejected", "Finite-number conversion rejected the structured value.");
5511
+ const text = String(value2);
4958
5512
  if (/e/i.test(text) || (text.split(".")[1]?.length ?? 0) > spec.maximumDecimalPlaces) throw new CamelError("conversion_rejected", "Finite-number conversion rejected a non-canonical decimal.");
4959
- return value;
5513
+ return value2;
4960
5514
  }
4961
- function enumMember(value, spec) {
4962
- if (spec.kind !== "enum" || typeof value !== "string") throw new CamelError("conversion_rejected", "Enum conversion requires a structured string member.");
4963
- const member = spec.caseSensitive ? spec.values.find((item) => item === value) : spec.values.find((item) => item.toLocaleLowerCase("en-US") === value.toLocaleLowerCase("en-US"));
5515
+ function enumMember(value2, spec) {
5516
+ if (spec.kind !== "enum" || typeof value2 !== "string") throw new CamelError("conversion_rejected", "Enum conversion requires a structured string member.");
5517
+ const member = spec.caseSensitive ? spec.values.find((item) => item === value2) : spec.values.find((item) => item.toLocaleLowerCase("en-US") === value2.toLocaleLowerCase("en-US"));
4964
5518
  if (member === void 0) throw new CamelError("conversion_rejected", "Enum conversion rejected a non-member.");
4965
5519
  return member;
4966
5520
  }
4967
- function canonicalDate(value, spec) {
4968
- if (spec.kind !== "date" || typeof value !== "string") throw new CamelError("conversion_rejected", "Date conversion requires a canonical structured string.");
5521
+ function canonicalDate(value2, spec) {
5522
+ if (spec.kind !== "date" || typeof value2 !== "string") throw new CamelError("conversion_rejected", "Date conversion requires a canonical structured string.");
4969
5523
  const pattern = spec.format === "date" ? /^\d{4}-\d{2}-\d{2}$/ : /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?(?:Z|[+-]\d{2}:\d{2})$/;
4970
- const instant = Date.parse(spec.format === "date" ? `${value}T00:00:00Z` : value);
4971
- if (!pattern.test(value) || !Number.isFinite(instant) || spec.format === "date" && new Date(instant).toISOString().slice(0, 10) !== value) throw new CamelError("conversion_rejected", "Date conversion rejected a non-canonical value.");
4972
- if (spec.earliest && value < spec.earliest || spec.latest && value > spec.latest) throw new CamelError("conversion_rejected", "Date conversion rejected an out-of-range value.");
4973
- return value;
4974
- }
4975
- function measure(value, metric) {
4976
- if (metric === "byte_length" && (typeof value === "string" || value instanceof Uint8Array)) return utf8Length(value);
4977
- if (metric === "codepoint_length" && typeof value === "string") return [...value].length;
4978
- if (metric === "item_count" && Array.isArray(value)) return value.length;
5524
+ const instant = Date.parse(spec.format === "date" ? `${value2}T00:00:00Z` : value2);
5525
+ if (!pattern.test(value2) || !Number.isFinite(instant) || spec.format === "date" && new Date(instant).toISOString().slice(0, 10) !== value2) throw new CamelError("conversion_rejected", "Date conversion rejected a non-canonical value.");
5526
+ if (spec.earliest && value2 < spec.earliest || spec.latest && value2 > spec.latest) throw new CamelError("conversion_rejected", "Date conversion rejected an out-of-range value.");
5527
+ return value2;
5528
+ }
5529
+ function measure(value2, metric) {
5530
+ if (metric === "byte_length" && (typeof value2 === "string" || value2 instanceof Uint8Array)) return utf8Length(value2);
5531
+ if (metric === "codepoint_length" && typeof value2 === "string") return [...value2].length;
5532
+ if (metric === "item_count" && Array.isArray(value2)) return value2.length;
4979
5533
  throw new CamelError("conversion_rejected", "Measurement input does not match the registered metric.");
4980
5534
  }
4981
- function evaluatePredicate(value, predicate, registries) {
4982
- if (predicate.kind === "has_fields") return typeof value === "object" && value !== null && !Array.isArray(value) && predicate.fields.every((field) => Object.hasOwn(value, field));
4983
- if (predicate.kind === "registered_id") return typeof value === "string" && registries?.[predicate.registryId]?.values[value] !== void 0;
4984
- const actual = value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
5535
+ function evaluatePredicate(value2, predicate, registries) {
5536
+ if (predicate.kind === "has_fields") return typeof value2 === "object" && value2 !== null && !Array.isArray(value2) && predicate.fields.every((field) => Object.hasOwn(value2, field));
5537
+ if (predicate.kind === "registered_id") return typeof value2 === "string" && registries?.[predicate.registryId]?.values[value2] !== void 0;
5538
+ const actual = value2 === null ? "null" : Array.isArray(value2) ? "array" : typeof value2;
4985
5539
  return actual === predicate.valueType;
4986
5540
  }
4987
5541
  function sourceIdentity(source) {
@@ -5004,17 +5558,17 @@ function createCamelIngress(constants2 = []) {
5004
5558
  byId.set(item.id, item);
5005
5559
  }
5006
5560
  const ingress = {
5007
- userInstruction: (value, input) => createSafeInternal(value, "user_instruction", metadata("user_instruction", input.id, input.readers)),
5008
- systemPolicy: (value, input) => createSafeInternal(value, "system_policy", metadata("system_policy", input.id, input.readers)),
5561
+ userInstruction: (value2, input) => createSafeInternal(value2, "user_instruction", metadata("user_instruction", input.id, input.readers)),
5562
+ systemPolicy: (value2, input) => createSafeInternal(value2, "system_policy", metadata("system_policy", input.id, input.readers)),
5009
5563
  control: (id) => {
5010
5564
  const item = byId.get(id);
5011
5565
  if (!item) throw new CamelError("permission_denied", "Unknown control constant.");
5012
5566
  return createSafeInternal(item.value, "harness_constant", metadata("harness", id, item.readers));
5013
5567
  },
5014
- external: (value, label) => createUnsafeInternal(value, label),
5015
- quarantinedOutput: (value, input) => {
5568
+ external: (value2, label) => createUnsafeInternal(value2, label),
5569
+ quarantinedOutput: (value2, input) => {
5016
5570
  if (!input.runId) throw new CamelError("state_conflict", "Quarantined run IDs must be non-empty.");
5017
- return createUnsafeInternal(value, {
5571
+ return createUnsafeInternal(value2, {
5018
5572
  readers: input.readers,
5019
5573
  dependencies: input.dependencies,
5020
5574
  provenance: [{ kind: "quarantined_llm", id: input.runId, digest: input.digest }]
@@ -5023,15 +5577,15 @@ function createCamelIngress(constants2 = []) {
5023
5577
  };
5024
5578
  return Object.freeze(ingress);
5025
5579
  }
5026
- function assertNoUnsafeConstant(value, seen = /* @__PURE__ */ new WeakSet()) {
5027
- if (typeof value !== "object" || value === null || seen.has(value)) return;
5028
- seen.add(value);
5029
- if (isCamelValue(value)) {
5030
- if (isUnsafe(value)) throw new CamelError("unsafe_privileged_flow", "Control constants cannot contain Prompt-Unsafe values.");
5031
- assertNoUnsafeConstant(value.value, seen);
5580
+ function assertNoUnsafeConstant(value2, seen = /* @__PURE__ */ new WeakSet()) {
5581
+ if (typeof value2 !== "object" || value2 === null || seen.has(value2)) return;
5582
+ seen.add(value2);
5583
+ if (isCamelValue(value2)) {
5584
+ if (isUnsafe(value2)) throw new CamelError("unsafe_privileged_flow", "Control constants cannot contain Prompt-Unsafe values.");
5585
+ assertNoUnsafeConstant(value2.value, seen);
5032
5586
  return;
5033
5587
  }
5034
- for (const child of Object.values(value)) assertNoUnsafeConstant(child, seen);
5588
+ for (const child of Object.values(value2)) assertNoUnsafeConstant(child, seen);
5035
5589
  }
5036
5590
  function metadata(kind, id, readers) {
5037
5591
  if (!id) throw new CamelError("state_conflict", "Provenance IDs must be non-empty.");
@@ -5045,10 +5599,10 @@ function createEffectPolicy(config = {}) {
5045
5599
  return Object.freeze({ evaluate: (input) => evaluate(input, registries, approvalEffects) });
5046
5600
  }
5047
5601
  function destinationRegistryDigest(values) {
5048
- return sha256Hex(canonicalJson([...values].sort()));
5602
+ return sha256Hex(canonicalJson2([...values].sort()));
5049
5603
  }
5050
5604
  async function evaluate(input, registries, approvals) {
5051
- const actionDigest = await sha256Hex(canonicalJson(input));
5605
+ const actionDigest = await sha256Hex(canonicalJson2(input));
5052
5606
  const decisionId = `decision_${actionDigest.slice(0, 24)}`;
5053
5607
  const deny = (reasonCode) => ({ outcome: "deny", decisionId, reasonCode });
5054
5608
  if (!input.planId || !input.tool.name || !input.tool.policyId || !Number.isSafeInteger(input.tool.version)) return deny("invalid_descriptor");
@@ -5062,7 +5616,7 @@ async function evaluate(input, registries, approvals) {
5062
5616
  if (invalid3) return deny(invalid3);
5063
5617
  if (arg.role === "selector" && !isSafe(arg.value)) confined.add(arg.value);
5064
5618
  }
5065
- if (input.controlDependencies.some((value) => !isSafe(value) && !confined.has(value))) return deny("unsafe_control_dependency");
5619
+ if (input.controlDependencies.some((value2) => !isSafe(value2) && !confined.has(value2))) return deny("unsafe_control_dependency");
5066
5620
  if (confined.size > 0) {
5067
5621
  const fixed = input.tool.unsafeSelectorPolicy?.fixedProviderIds ?? [];
5068
5622
  const destinations = Object.values(input.args).filter((arg) => arg.role === "destination");
@@ -5089,29 +5643,29 @@ async function validateArgument(path, arg, tool, registries) {
5089
5643
  if (!isSafe(arg.value)) return "unsafe_destination_argument";
5090
5644
  if (!isControlOwned(arg.value)) return "destination_not_control_owned";
5091
5645
  const registry = registries[arg.registryId];
5092
- const validValues = registry && registry.values.length > 0 && registry.values.every((value) => typeof value === "string" && value.length > 0) && new Set(registry.values).size === registry.values.length;
5646
+ const validValues = registry && registry.values.length > 0 && registry.values.every((value2) => typeof value2 === "string" && value2.length > 0) && new Set(registry.values).size === registry.values.length;
5093
5647
  if (!registry || !validValues || registry.digest !== arg.registryDigest || await destinationRegistryDigest(registry.values) !== registry.digest || !registry.values.includes(arg.value.value)) return "destination_not_registered";
5094
5648
  }
5095
5649
  if (arg.role === "selector" && !isSafe(arg.value)) return validateUnsafeSelector(path, arg.value, tool);
5096
5650
  return void 0;
5097
5651
  }
5098
- function isControlOwned(value) {
5099
- return value.label.safeBasis === "system_policy" || value.label.safeBasis === "harness_constant";
5652
+ function isControlOwned(value2) {
5653
+ return value2.label.safeBasis === "system_policy" || value2.label.safeBasis === "harness_constant";
5100
5654
  }
5101
5655
  function copyRegistries(registries) {
5102
5656
  return Object.freeze(Object.fromEntries(Object.entries(registries).map(([id, registry]) => [id, Object.freeze({ digest: registry.digest, values: Object.freeze([...registry.values]) })])));
5103
5657
  }
5104
- function validateUnsafeSelector(path, value, tool) {
5658
+ function validateUnsafeSelector(path, value2, tool) {
5105
5659
  const policy = tool.unsafeSelectorPolicy;
5106
5660
  if (!policy || policy.effect !== tool.effect || !policy.selectorPaths.includes(path)) return "unsafe_selector_not_confined";
5107
5661
  if (!policy.fixedDestination || !policy.unsafeOutput || policy.fixedProviderIds.length === 0) return "unsafe_selector_not_confined";
5108
5662
  if (![policy.maximumCalls, policy.maximumResultsPerCall, policy.maximumOutputBytes, policy.maximumSelectorBytes].every((bound) => Number.isSafeInteger(bound) && bound > 0)) return "unsafe_selector_not_confined";
5109
- if (utf8Length(value.value) > policy.maximumSelectorBytes) return "unsafe_selector_too_large";
5110
- if (typeof value.value === "string" && looksLikeDestination(value.value)) return "unsafe_selector_is_destination";
5663
+ if (utf8Length(value2.value) > policy.maximumSelectorBytes) return "unsafe_selector_too_large";
5664
+ if (typeof value2.value === "string" && looksLikeDestination(value2.value)) return "unsafe_selector_is_destination";
5111
5665
  return void 0;
5112
5666
  }
5113
- function looksLikeDestination(value) {
5114
- const text = value.trim();
5667
+ function looksLikeDestination(value2) {
5668
+ const text = value2.trim();
5115
5669
  return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text);
5116
5670
  }
5117
5671
 
@@ -5197,9 +5751,9 @@ var CodeRuntimeReconciler = class {
5197
5751
  }
5198
5752
  }
5199
5753
  };
5200
- function retryableControlFailure(value) {
5201
- if (!value || typeof value !== "object") return false;
5202
- const failure = value;
5754
+ function retryableControlFailure(value2) {
5755
+ if (!value2 || typeof value2 !== "object") return false;
5756
+ const failure = value2;
5203
5757
  if (failure.code === "invalid_response" || typeof failure.status !== "number") return false;
5204
5758
  return failure.status === 408 || failure.status === 425 || failure.status === 429 || failure.status >= 500;
5205
5759
  }
@@ -5251,16 +5805,16 @@ function createCodeRuntimeControlClient(options) {
5251
5805
  if (options.signal?.aborted) throw cause;
5252
5806
  throw new CodeRuntimeControlError("Code runtime control plane is unavailable", 503, "transport_unavailable");
5253
5807
  }
5254
- const value = await response2.json().catch(() => null);
5808
+ const value2 = await response2.json().catch(() => null);
5255
5809
  if (!response2.ok) {
5256
- const problem = record3(record3(value)?.error);
5810
+ const problem = record3(record3(value2)?.error);
5257
5811
  throw new CodeRuntimeControlError(
5258
5812
  typeof problem?.message === "string" ? problem.message : `Code runtime request failed (${response2.status})`,
5259
5813
  response2.status,
5260
5814
  typeof problem?.code === "string" ? problem.code : void 0
5261
5815
  );
5262
5816
  }
5263
- return value;
5817
+ return value2;
5264
5818
  };
5265
5819
  return {
5266
5820
  heartbeat: async (version, capabilities) => {
@@ -5275,15 +5829,15 @@ function createCodeRuntimeControlClient(options) {
5275
5829
  await call2(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source`, {})
5276
5830
  ),
5277
5831
  infer: async (sessionId, inference) => {
5278
- const value = record3(await call2(
5832
+ const value2 = record3(await call2(
5279
5833
  `/registry/code/runtime/sessions/${validSessionId(sessionId)}/inference`,
5280
5834
  inference,
5281
5835
  modelRequestTimeoutMs
5282
5836
  ));
5283
- if (!value || value.requestId !== inference.requestId || !record3(value.response) || !record3(value.receipt)) {
5837
+ if (!value2 || value2.requestId !== inference.requestId || !record3(value2.response) || !record3(value2.receipt)) {
5284
5838
  throw new CodeRuntimeControlError("invalid Code inference response", 502, "invalid_response");
5285
5839
  }
5286
- return value;
5840
+ return value2;
5287
5841
  },
5288
5842
  review: async (sessionId, review) => parseReview(
5289
5843
  await call2(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/review`, review, modelRequestTimeoutMs)
@@ -5308,8 +5862,8 @@ function createCodeRuntimeControlClient(options) {
5308
5862
  }
5309
5863
  };
5310
5864
  }
5311
- function validatedEndpoint(value) {
5312
- const endpoint = value.replace(/\/+$/, "");
5865
+ function validatedEndpoint(value2) {
5866
+ const endpoint = value2.replace(/\/+$/, "");
5313
5867
  let url;
5314
5868
  try {
5315
5869
  url = new URL(endpoint);
@@ -5322,9 +5876,9 @@ function validatedEndpoint(value) {
5322
5876
  }
5323
5877
  return endpoint;
5324
5878
  }
5325
- function validSessionId(value) {
5326
- if (!/^csess_[0-9a-f]{32}$/.test(value)) throw new TypeError("invalid Code session id");
5327
- return value;
5879
+ function validSessionId(value2) {
5880
+ if (!/^csess_[0-9a-f]{32}$/.test(value2)) throw new TypeError("invalid Code session id");
5881
+ return value2;
5328
5882
  }
5329
5883
  function validateHeartbeat(version, capabilities) {
5330
5884
  if (!version.trim() || version.length > 80) throw new TypeError("runtimeVersion is required and at most 80 characters");
@@ -5337,8 +5891,8 @@ function validateHeartbeat(version, capabilities) {
5337
5891
  throw new TypeError("runtime resources must be positive integers");
5338
5892
  }
5339
5893
  }
5340
- function parseSnapshot(value) {
5341
- const root = record3(value);
5894
+ function parseSnapshot(value2) {
5895
+ const root = record3(value2);
5342
5896
  const host = record3(root?.host);
5343
5897
  if (!host || typeof host.hostId !== "string" || typeof host.runtimeVersion !== "string" || !Number.isSafeInteger(host.lastSeenAt) || host.revokedAt !== null || !Array.isArray(root?.bindings) || root.bindings.length > 1024 || !Array.isArray(root?.commands) || root.commands.length > 64) throw invalid("heartbeat");
5344
5898
  const bindingIds = /* @__PURE__ */ new Set();
@@ -5363,11 +5917,11 @@ function parseSnapshot(value) {
5363
5917
  });
5364
5918
  return { host, bindings, commands };
5365
5919
  }
5366
- async function parseSource(value) {
5367
- const snapshot = record3(record3(value)?.snapshot);
5920
+ async function parseSource(value2) {
5921
+ const snapshot = record3(record3(value2)?.snapshot);
5368
5922
  if (!snapshot || typeof snapshot.repository !== "string" || typeof snapshot.commitSha !== "string" || typeof snapshot.treeDigest !== "string" || !Array.isArray(snapshot.files)) throw invalid("source");
5369
- const files = snapshot.files.map((value2) => {
5370
- const file = record3(value2);
5923
+ const files = snapshot.files.map((value22) => {
5924
+ const file = record3(value22);
5371
5925
  if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("source file");
5372
5926
  return { path: file.path, content: file.content };
5373
5927
  });
@@ -5394,19 +5948,19 @@ async function parseSource(value) {
5394
5948
  if (digest !== snapshot.treeDigest) throw invalid("source digest");
5395
5949
  return { ...source, treeDigest: digest, ...references.length ? { references } : {} };
5396
5950
  }
5397
- function parseReview(value) {
5398
- const review = record3(record3(value)?.review);
5951
+ function parseReview(value2) {
5952
+ const review = record3(record3(value2)?.review);
5399
5953
  if (!review || !["approved", "rejected"].includes(String(review.verdict)) || typeof review.reviewDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(review.reviewDigest) || typeof review.provider !== "string" || !review.provider || typeof review.model !== "string" || !review.model || !Number.isSafeInteger(review.policyVersion) || Number(review.policyVersion) < 1) throw invalid("review");
5400
5954
  return review;
5401
5955
  }
5402
- function parseCandidate(value) {
5403
- const candidate = record3(record3(value)?.candidate);
5956
+ function parseCandidate(value2) {
5957
+ const candidate = record3(record3(value2)?.candidate);
5404
5958
  if (!candidate || typeof candidate.candidateId !== "string" || !/^ccand_[0-9a-f]{32}$/.test(candidate.candidateId) || !["submitted", "approved", "published", "failed"].includes(String(candidate.status))) {
5405
5959
  throw invalid("candidate");
5406
5960
  }
5407
5961
  return { candidateId: candidate.candidateId, status: candidate.status };
5408
5962
  }
5409
- var record3 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
5963
+ var record3 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
5410
5964
  var invalid = (part) => new CodeRuntimeControlError(`invalid Code runtime ${part} response`, 502, "invalid_response");
5411
5965
  var RESERVED = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
5412
5966
  var SECRET = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
@@ -5440,8 +5994,8 @@ function validateCodePatch(patch2, maxBytes) {
5440
5994
  if (!paths.length || new Set(paths).size !== paths.length) throw new TypeError("patch has no diffs or repeats a path");
5441
5995
  return paths;
5442
5996
  }
5443
- function validHeaderPath(value, path, prefix) {
5444
- return value === "/dev/null" || value === `${prefix}/${path}`;
5997
+ function validHeaderPath(value2, path, prefix) {
5998
+ return value2 === "/dev/null" || value2 === `${prefix}/${path}`;
5445
5999
  }
5446
6000
  function validateRelativePath(path) {
5447
6001
  const parts = path.split("/");
@@ -5587,8 +6141,8 @@ function assertCodeBuildRecipe(recipe2) {
5587
6141
  throw new TypeError("build recipe is malformed or exceeds its control bounds");
5588
6142
  }
5589
6143
  }
5590
- function parseMemory(value) {
5591
- const match = /^([1-9][0-9]{0,4})([kmg])$/.exec(value.toLowerCase());
6144
+ function parseMemory(value2) {
6145
+ const match = /^([1-9][0-9]{0,4})([kmg])$/.exec(value2.toLowerCase());
5592
6146
  if (!match?.[1] || !match[2]) return 0;
5593
6147
  const scale = match[2] === "k" ? 1024 : match[2] === "m" ? 1024 ** 2 : 1024 ** 3;
5594
6148
  return Number(match[1]) * scale;
@@ -5823,14 +6377,14 @@ function normalizedRecipe(recipe2) {
5823
6377
  expectedArtifacts: [...recipe2.expectedArtifacts ?? []].sort((left, right) => left.id.localeCompare(right.id)).map((artifact) => ({ id: artifact.id, path: artifact.path, maximumBytes: artifact.maximumBytes }))
5824
6378
  };
5825
6379
  }
5826
- function digestJson(value) {
5827
- return digestBytes(JSON.stringify(value));
6380
+ function digestJson(value2) {
6381
+ return digestBytes(JSON.stringify(value2));
5828
6382
  }
5829
- function digestBytes(value) {
5830
- return `sha256:${(0, import_crypto3.createHash)("sha256").update(value).digest("hex")}`;
6383
+ function digestBytes(value2) {
6384
+ return `sha256:${(0, import_crypto3.createHash)("sha256").update(value2).digest("hex")}`;
5831
6385
  }
5832
- function integer(value, minimum, maximum) {
5833
- return Number.isSafeInteger(value) && value >= minimum && value <= maximum;
6386
+ function integer(value2, minimum, maximum) {
6387
+ return Number.isSafeInteger(value2) && value2 >= minimum && value2 <= maximum;
5834
6388
  }
5835
6389
  async function prepareRuntimeCheckpoint(input) {
5836
6390
  const patch2 = await input.workspace.patch(256 * 1024);
@@ -5883,7 +6437,7 @@ async function prepareRuntimeCheckpoint(input) {
5883
6437
  });
5884
6438
  return { checkpoint, verification, review, note };
5885
6439
  }
5886
- var message = (value) => (value instanceof Error ? value.message : String(value)).slice(0, 500);
6440
+ var message = (value2) => (value2 instanceof Error ? value2.message : String(value2)).slice(0, 500);
5887
6441
  var CodeRuntimeCheckpointManager = class {
5888
6442
  constructor(options) {
5889
6443
  this.options = options;
@@ -6077,7 +6631,7 @@ async function conversionPolicy(id, output) {
6077
6631
  return { ...definition, digest: await conversionPolicyDigest(definition) };
6078
6632
  }
6079
6633
  async function registeredPolicy(id, registryId, values) {
6080
- const mapping = Object.fromEntries(values.map((value) => [value, value]));
6634
+ const mapping = Object.fromEntries(values.map((value2) => [value2, value2]));
6081
6635
  return conversionPolicy(id, {
6082
6636
  kind: "registered_id",
6083
6637
  registryId,
@@ -6086,7 +6640,7 @@ async function registeredPolicy(id, registryId, values) {
6086
6640
  }
6087
6641
  async function conversionRegistry(policies, values) {
6088
6642
  const registeredIds = Object.fromEntries(await Promise.all(Object.entries(values).map(async ([id, entries]) => {
6089
- const mapping = Object.fromEntries(entries.map((value) => [value, value]));
6643
+ const mapping = Object.fromEntries(entries.map((value2) => [value2, value2]));
6090
6644
  return [id, { values: mapping, digest: await registeredIdRegistryDigest(mapping) }];
6091
6645
  })));
6092
6646
  return createConversionRegistry({ policies, registeredIds });
@@ -6110,8 +6664,8 @@ async function environment(input, options, tool) {
6110
6664
  };
6111
6665
  return { ingress, policy, fixedArgs, reader: ingress.control("reader"), runId: `${input.request.requestId}:${tool}` };
6112
6666
  }
6113
- function unsafe(base, value, field) {
6114
- return base.ingress.quarantinedOutput(value, {
6667
+ function unsafe(base, value2, field) {
6668
+ return base.ingress.quarantinedOutput(value2, {
6115
6669
  readers: base.reader.label.readers,
6116
6670
  runId: `${base.runId}:${field}`
6117
6671
  });
@@ -6191,14 +6745,14 @@ async function read(context, request2, options, policy) {
6191
6745
  }
6192
6746
  async function patch(context, request2, options, policy) {
6193
6747
  exactKeys(request2.input, ["patch"]);
6194
- const value = stringField(request2.input, "patch");
6195
- const paths = validateCodePatch(value, options.maxPatchBytes ?? 256 * 1024);
6748
+ const value2 = stringField(request2.input, "patch");
6749
+ const paths = validateCodePatch(value2, options.maxPatchBytes ?? 256 * 1024);
6196
6750
  if (paths.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
6197
6751
  throw new TypeError("patch targets a read-only reference source");
6198
6752
  }
6199
- const allowed = await policy.patch(policyContext(context, request2, options, { patch: value }));
6753
+ const allowed = await policy.patch(policyContext(context, request2, options, { patch: value2 }));
6200
6754
  if (!allowed) return response(request2, false, "tool denied by CaMeL policy");
6201
- await applyCodePatch(context.workspaceDir, value, paths);
6755
+ await applyCodePatch(context.workspaceDir, value2, paths);
6202
6756
  return response(request2, true, `Applied patch to ${paths.length} file(s).`, { paths });
6203
6757
  }
6204
6758
  async function recipe(context, request2, options, recipes, policy) {
@@ -6289,14 +6843,14 @@ function exactKeys(input, allowed) {
6289
6843
  if (Object.keys(input).some((key) => !allowed.includes(key))) throw new TypeError("tool input contains an unsupported field");
6290
6844
  }
6291
6845
  function stringField(input, name) {
6292
- const value = input[name];
6293
- if (typeof value !== "string" || !value) throw new TypeError(`${name} must be a non-empty string`);
6294
- return value;
6846
+ const value2 = input[name];
6847
+ if (typeof value2 !== "string" || !value2) throw new TypeError(`${name} must be a non-empty string`);
6848
+ return value2;
6295
6849
  }
6296
- function optionalInteger(value) {
6297
- if (value === void 0) return void 0;
6298
- if (!Number.isSafeInteger(value) || value < 1) throw new TypeError("line bounds must be positive integers");
6299
- return value;
6850
+ function optionalInteger(value2) {
6851
+ if (value2 === void 0) return void 0;
6852
+ if (!Number.isSafeInteger(value2) || value2 < 1) throw new TypeError("line bounds must be positive integers");
6853
+ return value2;
6300
6854
  }
6301
6855
  function response(request2, ok, content2, details) {
6302
6856
  return { requestId: request2.requestId, ok, content: content2, ...details ? { details } : {} };
@@ -6342,9 +6896,9 @@ function codeLocalSource(payload) {
6342
6896
  return source;
6343
6897
  }
6344
6898
  function codeCheckpointPayload(payload) {
6345
- const value = payload.checkpoint;
6346
- if (!value || typeof value !== "object" || Array.isArray(value)) throw new TypeError("resume checkpoint is missing");
6347
- return value;
6899
+ const value2 = payload.checkpoint;
6900
+ if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) throw new TypeError("resume checkpoint is missing");
6901
+ return value2;
6348
6902
  }
6349
6903
  function fakeCodeLease(command, metadata2) {
6350
6904
  return {
@@ -6368,7 +6922,7 @@ function fakeCodeLease(command, metadata2) {
6368
6922
  }
6369
6923
  };
6370
6924
  }
6371
- var record22 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
6925
+ var record22 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
6372
6926
  var SOURCE_LIMITS = { maxFiles: 2e4, maxBytes: 512 * 1024 * 1024 };
6373
6927
  async function prepareRuntimeLocalSource(input) {
6374
6928
  const { command, descriptor: descriptor2, available, repository, baseCommitSha, resume } = input;
@@ -6458,24 +7012,24 @@ async function appendCodeRuntimeEvent(control, command, event, refs) {
6458
7012
  const bounded = event.type === "message" ? { ...event, body: event.body.trim().slice(0, 2e4) || `${event.actor} event` } : event;
6459
7013
  await control.appendSessionEvent(command.sessionId, eventId, bounded);
6460
7014
  }
6461
- var digestRuntimeValue = (value) => `sha256:${(0, import_crypto4.createHash)("sha256").update(value).digest("hex")}`;
6462
- var runtimeErrorMessage = (value) => value instanceof Error ? value.message : String(value);
6463
- var runtimeRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
6464
- var safeRuntimeJson = (value) => {
7015
+ var digestRuntimeValue = (value2) => `sha256:${(0, import_crypto4.createHash)("sha256").update(value2).digest("hex")}`;
7016
+ var runtimeErrorMessage = (value2) => value2 instanceof Error ? value2.message : String(value2);
7017
+ var runtimeRecord = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
7018
+ var safeRuntimeJson = (value2) => {
6465
7019
  try {
6466
- return JSON.stringify(value).slice(0, 1e4);
7020
+ return JSON.stringify(value2).slice(0, 1e4);
6467
7021
  } catch {
6468
7022
  return "[event]";
6469
7023
  }
6470
7024
  };
6471
- function runtimeResultText(value) {
6472
- const record32 = runtimeRecord(value);
7025
+ function runtimeResultText(value2) {
7026
+ const record32 = runtimeRecord(value2);
6473
7027
  if (record32 && typeof record32.text === "string") return record32.text.slice(0, 2e4);
6474
7028
  if (record32 && typeof record32.error === "string") return `Pi failed: ${record32.error.slice(0, 19989)}`;
6475
7029
  return null;
6476
7030
  }
6477
- function runtimeResultError(value) {
6478
- const record32 = runtimeRecord(value);
7031
+ function runtimeResultError(value2) {
7032
+ const record32 = runtimeRecord(value2);
6479
7033
  return record32 && typeof record32.error === "string" && record32.error.trim() ? record32.error.trim().slice(0, 2e3) : null;
6480
7034
  }
6481
7035
  var CodePiRuntimeEngine = class {
@@ -6749,14 +7303,14 @@ var CodePiRuntimeEngine = class {
6749
7303
  this.#active.delete(command.sessionId);
6750
7304
  return result;
6751
7305
  }
6752
- async #failure(command, active, value) {
6753
- active.failure = value.slice(0, 2e3);
7306
+ async #failure(command, active, value2) {
7307
+ active.failure = value2.slice(0, 2e3);
6754
7308
  if (active.acknowledged) {
6755
7309
  await this.options.control.reportSessionFailure(command.sessionId, active.failure).catch(() => void 0);
6756
7310
  }
6757
7311
  }
6758
- async #diagnostic(command, active, value) {
6759
- const detail = value.trim().slice(0, 2e3) || "Pi runtime failed";
7312
+ async #diagnostic(command, active, value2) {
7313
+ const detail = value2.trim().slice(0, 2e3) || "Pi runtime failed";
6760
7314
  this.options.onDiagnostic?.(detail);
6761
7315
  await this.#event(
6762
7316
  command,
@@ -6769,265 +7323,19 @@ var CodePiRuntimeEngine = class {
6769
7323
  }
6770
7324
  };
6771
7325
 
6772
- // src/help.ts
7326
+ // src/version.ts
6773
7327
  var import_node_fs13 = require("fs");
6774
7328
  function cliVersion() {
6775
7329
  const pkg = JSON.parse((0, import_node_fs13.readFileSync)(new URL("../package.json", importMetaUrl), "utf8"));
6776
7330
  return pkg.version ?? "unknown";
6777
7331
  }
6778
- function printHelp(output = console) {
6779
- output.log(`odla-ai
6780
7332
 
6781
- Start here:
6782
- odla-ai runbook ask "<question>" The current procedure, from odla's own
6783
- runbooks. Ask BEFORE searching the web or
6784
- working from memory: runbooks are edited
6785
- live, so your training data is out of date
6786
- and this is not.
6787
- odla-ai runbook impact After a change: which runbooks describe the
6788
- code you just touched, so you can fix any
6789
- step it made wrong.
6790
-
6791
- Usage:
6792
- odla-ai setup [--dir <project>] [--agent <name>] [--global] [--force]
6793
- odla-ai init --app-id <id> --name <name> [--services db,ai,o11y,calendar] [--env dev --env prod]
6794
- odla-ai doctor [--config odla.config.mjs]
6795
- odla-ai calendar status [--env dev] [--email <odla-account>] [--json]
6796
- odla-ai calendar calendars [--env dev] [--email <odla-account>] [--json]
6797
- odla-ai calendar connect [--env dev] [--email <odla-account>] [--no-open] [--yes]
6798
- odla-ai calendar disconnect [--env dev] [--email <odla-account>] --yes
6799
- odla-ai app archive [--config odla.config.mjs] [--email <odla-account>] [--json] --yes
6800
- odla-ai app restore [--config odla.config.mjs] [--email <odla-account>] [--json]
6801
- odla-ai app export [--env dev] [--fresh] [--out <file>] [--email <odla-account>] [--json]
6802
- odla-ai app import <file|-> [--env dev] [--ns <namespace>] [--id-field <f>|--key <attr>|--generate-ids] [--dry-run] [--json] --yes
6803
- odla-ai app refresh-sandbox [--include-identity] [--include-files] [--dry-run] [--json] --yes
6804
- odla-ai app go-live [--include-identity] [--include-files] [--dry-run] [--json] --yes
6805
- odla-ai app promote [--dry-run] [--json] --yes
6806
- odla-ai app rename <name> [--config odla.config.mjs] [--email <odla-account>] [--json]
6807
- odla-ai app owners list [--config odla.config.mjs] [--email <odla-account>] [--json]
6808
- odla-ai app owners add <email> [--email <odla-account>] [--json]
6809
- odla-ai app owners remove <email> [--email <odla-account>] [--json]
6810
- odla-ai pm goal list [--app <id>] [--status <s>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
6811
- odla-ai pm task list [--app <id>] [--column <c>] [--goal <id>] [--assignee <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
6812
- odla-ai pm decision list [--app <id>] [--status <s>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
6813
- odla-ai pm bug list [--app <id>] [--status <s>] [--severity <s>] [--goal <id>] [--assignee <id>] [--decision <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
6814
- odla-ai pm goal add --app <id> --title <t> [--status <s>] [--proof <text>] [--target <pct>] [--mutation-id <id>] [--json]
6815
- odla-ai pm task add --app <id> --title <t> [--column <c>] [--goal <id>] [--assignee <id>] [--description <text>|--body <text>] [--due <epoch-ms>] [--mutation-id <id>] [--json]
6816
- odla-ai pm decision add --app <id> --title <t> --body <text> [--status <s>] [--mutation-id <id>] [--json]
6817
- odla-ai pm bug add --app <id> --title <t> (--description <text>|--body <text>) [--status <s>] [--severity <s>] [--goal <id>] [--assignee <id>] [--decision <id>] [--mutation-id <id>] [--json]
6818
- odla-ai pm <goal|task|decision|bug> get <id> [--json]
6819
- odla-ai pm goal set <id> [--title <t>|--status <s>|--proof <text>|--no-proof|--target <pct>|--no-target] [--mutation-id <id>] [--json]
6820
- odla-ai pm task set <id> [--title <t>|--column <c>|--rank <n>|--goal <id>|--no-goal|--assignee <id>|--no-assignee|--description <text>|--body <text>|--due <epoch-ms>|--no-due] [--mutation-id <id>] [--json]
6821
- odla-ai pm decision set <id> [--title <t>|--status <s>|--body <text>] [--mutation-id <id>] [--json]
6822
- odla-ai pm bug set <id> [--title <t>|--status <s>|--severity <s>|--goal <id>|--no-goal|--assignee <id>|--no-assignee|--decision <id>|--no-decision|--description <text>|--body <text>] [--mutation-id <id>] [--json]
6823
- odla-ai pm <goal|task|decision> done <id> [--mutation-id <id>]
6824
- odla-ai pm bug done <id> [--decision <accepted-decision-id>] [--mutation-id <id>]
6825
- odla-ai pm <goal|task|decision|bug> comment <id> --body "..." [--mutation-id <id>]
6826
- odla-ai pm <goal|task|decision|bug> comments <id> [--json]
6827
- odla-ai pm <goal|task|decision|bug> rm <id>
6828
- odla-ai pm handoff --app <id> [--json]
6829
- odla-ai discuss groups [--json]
6830
- odla-ai discuss list [--app <id>] [--q <text>] [--state open|resolved|all] [--json]
6831
- odla-ai discuss read <topic> [--limit <n> --offset <n>] [--json]
6832
- odla-ai discuss post --app <id> --subject "..." --body "..." [--markup "... @[Label](kind/id)"] [--mutation-id <id>]
6833
- odla-ai discuss reply <topic> --body "..." [--markup "..."] [--mutation-id <id>]
6834
- odla-ai discuss resolve <topic> [--reopen] [--mutation-id <id>]
6835
- odla-ai discuss who --q <text> [--app <id>] [--kinds user,pm:task] [--json]
6836
- odla-ai discuss watch [<topic>] [--cursor <cursor>] [--by <authorId>] [--self <authorId>] [--interval <s>] [--timeout <s>] [--json|--jsonl]
6837
- odla-ai agent jobs [--env dev] [--state pending|running|succeeded|dead_letter] [--limit 50] [--email <email>] [--json]
6838
- odla-ai agent retry <job-id> [--env dev] [--email <email>] [--json]
6839
- odla-ai context show [--context <name>] [--platform https://odla.ai] [--app <id>] [--env prod] [--json]
6840
- odla-ai context list [--json]
6841
- odla-ai context save <name> [--platform <url>] [--app <id>] [--env <name>] [--json]
6842
- odla-ai context remove <name> --yes [--json]
6843
- odla-ai o11y status [--app <id>] [--context <name>] [--platform https://odla.ai] [--env prod] [--minutes 60] [--json]
6844
- odla-ai whoami [--context <name>] [--platform https://odla.ai] [--json]
6845
- odla-ai runbook ask "<question>" [--app <id>] [--all] [--json]
6846
- odla-ai runbook search "<question>" [--app <id>] [--all] [--limit <n>] [--json]
6847
- odla-ai runbook impact [--base origin/main] [--app <id>] [--all] [--limit <n>] [--json]
6848
- odla-ai runbook lint [--app <id>] [--all] [--json]
6849
- odla-ai runbook list [--app <id>] [--all] [--q <text>] [--json]
6850
- odla-ai runbook comment <slug> --body "..." [--app <id>]
6851
- odla-ai runbook get <slug> [--app <id>]
6852
- odla-ai runbook new <slug> --title <t> --file <path|-> [--summary <s>] [--requires <specs>] [--app <id>]
6853
- odla-ai runbook edit <slug> [--file <path|->|--body "..."] [--note <why>] [--requires <specs>] [--app <id>]
6854
- odla-ai runbook import <dir> [--visibility operator|admin] [--dry-run] [--app <id>]
6855
- odla-ai runbook publish <slug> [--app <id>]
6856
- odla-ai runbook visibility <slug> <operator|admin> [--app <id>]
6857
- odla-ai runbook archive <slug> [--app <id>]
6858
- odla-ai runbook history <slug> [--app <id>] [--json]
6859
- odla-ai runbook revert <slug> --version <n> [--app <id>]
6860
- odla-ai runbook rm <slug> [--app <id>]
6861
- odla-ai capabilities [--json]
6862
- odla-ai code connect [--env dev|prod] [--email <odla-account>] [--engine auto|container|podman|docker] [--slots <1-64>] [--once]
6863
- odla-ai admin ai show [--context <name>] [--platform https://odla.ai] [--email <odla-account>] [--json]
6864
- odla-ai admin ai models [--context <name>] [--provider <id>] [--json]
6865
- odla-ai admin ai set <purpose> [--context <name>] [--provider <id>] [--model <id>] [--enabled|--no-enabled]
6866
- [--max-input-bytes <n>] [--max-output-tokens <n>] [--max-calls-per-run <n>] [--json]
6867
- odla-ai admin ai set security [--context <name>] --discovery-provider <id> --discovery-model <id>
6868
- --validation-provider <id> --validation-model <id> [--enabled|--no-enabled] [--json]
6869
- odla-ai admin ai credentials [--context <name>] [--json]
6870
- odla-ai admin ai credential set <provider> [--context <name>] (--from-env <NAME>|--stdin)
6871
- odla-ai admin ai usage [--context <name>] [--app-id <id>] [--env <env>] [--run-id <id>] [--limit <1-500>] [--json]
6872
- odla-ai admin ai audit [--context <name>] [--limit <1-200>] [--json]
6873
- odla-ai security github connect [--repo owner/name] [--env dev] [--email <odla-account>] [--no-open]
6874
- odla-ai security github disconnect --source <id> [--env dev] [--yes]
6875
- odla-ai security plan [--env dev] [--json]
6876
- odla-ai security sources [--env dev] [--json]
6877
- odla-ai security run --source <id> --plan-digest <sha256:...> --ack-redacted-source [--ref <branch|tag|sha>] [--env dev] [--no-follow]
6878
- odla-ai security status <job-id> [--follow] [--json]
6879
- odla-ai security report <job-id> [--json]
6880
- odla-ai security run [target] --ack-redacted-source [--env dev] [--profile odla] [--fail-on high]
6881
- odla-ai security run [target] --self --ack-redacted-source
6882
- odla-ai provision [--config odla.config.mjs] [--email <odla-account>] [--wait <seconds>] [--dry-run] [--push-secrets] [--rotate-o11y-token] [--write-dev-vars[=path]] [--yes]
6883
- odla-ai smoke [--config odla.config.mjs] [--env dev] [--email <odla-account>] [--no-open]
6884
- odla-ai skill install [--dir <project>] [--agent <name>] [--global] [--force]
6885
- odla-ai secrets push --env <env> [--config odla.config.mjs] [--dry-run] [--yes]
6886
- odla-ai secrets set <name> --env <env> (--from-env <NAME>|--stdin) [--email <odla-account>] [--config odla.config.mjs] [--yes]
6887
- odla-ai secrets set-clerk-key --env <env> (--from-env <NAME>|--stdin) [--email <odla-account>] [--config odla.config.mjs] [--yes]
6888
- odla-ai version
6889
-
6890
- Commands:
6891
- agent Inspect durable agent wakeups and explicitly requeue a
6892
- dead-lettered job; JSON output is stable for remote operators.
6893
- runbook odla's operational procedures, stored in the database and read at
6894
- the moment they are followed. "ask" gives a written, cited answer;
6895
- "search" the passages behind it; "get" the whole document.
6896
- "impact" diffs your working tree against a base ref and names the
6897
- runbooks that describe what you changed \u2014 run it after touching a
6898
- package's exported API or JSDoc, or a Studio surface. "lint"
6899
- checks the corpus the other way: every odla-ai command the
6900
- runbooks name is held against this CLI's real command surface,
6901
- and against the minimum versions each runbook declares. A wrong step
6902
- is fixed with "edit", which takes effect immediately: a runbook is
6903
- a row, not a release. Runbooks describe PROCEDURE; what an export
6904
- does and what it guarantees is JSDoc, rendered per package at
6905
- https://odla.ai/docs and shipped in the installed .d.ts. Answering
6906
- a question usually needs both.
6907
- whoami Report who this terminal is authenticated as, and whether it holds
6908
- platform admin. A handshake-minted device token is never admin,
6909
- however the human who approved it is configured.
6910
- context Explain selected config, platform, app, environment, and
6911
- developer-token provenance without printing credentials or
6912
- starting a device handshake. Operator work can run outside a
6913
- project checkout with explicit flags or ODLA_* environment
6914
- variables.
6915
- setup Install offline odla runbooks for common coding-agent harnesses.
6916
- init Create a generic odla.config.mjs plus starter schema/rules files.
6917
- doctor Validate and summarize the project config without network calls.
6918
- calendar Inspect, connect, or disconnect the live Google booking connection.
6919
- app Archive (suspend, data retained), restore, export, import, or
6920
- manage the co-owners of the app. Archiving takes every
6921
- environment's data plane down until restored; permanent deletion
6922
- has NO CLI \u2014 it requires a signed-in owner in Studio, and no
6923
- machine or agent credential can purge. "app export" downloads a
6924
- portable gzipped-JSONL snapshot of one database (--fresh takes a
6925
- new snapshot first) \u2014 your data is always yours to take.
6926
- "app import" upserts rows back in from JSON, JSONL, or a
6927
- {namespace: rows} map (the shape a query returns); it writes only
6928
- with --yes, so the bare command is a dry run, and it refuses to
6929
- guess an id mode that would duplicate rows on a re-run.
6930
- "app refresh-sandbox" replaces the sandbox with a copy of live
6931
- (the routine dev loop); "app go-live" copies the sandbox into an
6932
- EMPTY live database, once, at launch; "app promote" pushes only
6933
- schema, rules and gates up afterwards, leaving live's rows alone.
6934
- Each prints its plan and writes nothing without --yes, so the
6935
- bare command is the dry run. Clerk config, triggers, allowlists,
6936
- secrets and API keys never travel; identity rows stay behind
6937
- unless --include-identity.
6938
- "app rename <name>" changes only the display name humans read;
6939
- the app id is permanent (tenants, URLs and keys embed it), so a
6940
- rename never re-provisions anything or invalidates a credential.
6941
- "app owners add <email>" grants a signed-up odla member the SAME
6942
- full access as you; they then run their own "provision" to mint
6943
- their own credentials for the shared db (the live one included)
6944
- \u2014 no secret is ever copied between people.
6945
- capabilities Show what the CLI automates vs agent edits and human checkpoints.
6946
- code Enroll this Mac/Linux host for the current Studio-connected repository and run Pi.
6947
- admin Manage platform-funded AI routing/credentials/usage with narrow device grants.
6948
- security Connect GitHub sources and run commit-pinned hosted reviews, or scan a local snapshot.
6949
- pm Project management (via @odla-ai/pm) shared across the apps you
6950
- co-own: conformance goals, kanban tasks, decisions, and bugs. With
6951
- no --app, "list" spans every co-owned project (the cross-project
6952
- view); with --app it scopes to one. Same device-grant auth as
6953
- "app". Entities: goal (alias conformance), task (alias kanban),
6954
- decision, bug. Status changes and comments post to each item's
6955
- @odla-ai/chat discussion thread.
6956
- discuss Group discussions (via @odla-ai/chat) for the apps you co-own:
6957
- one group per project, topics with replies, @-mentions of people,
6958
- agents, PM items, and projects. Built for unattended use \u2014 post a
6959
- question, then "watch" it until someone answers. "watch" exits 75
6960
- (not 1) when it times out with nothing new, so a script can tell
6961
- "no answer yet" from a failure and just rerun. Use "who" to look
6962
- up the exact @[Label](kind/id) markup for a mention.
6963
- o11y Read one stable status envelope for application RED, current
6964
- live-sync load/freshness, the protected commit-to-visible
6965
- canary, collector ingest/scheduler trust, Cloudflare-owned
6966
- runtime metrics, and a machine verdict.
6967
- --json keeps auth progress on stderr for unattended agents.
6968
- provision Register services, compose integrations, persist credentials, optionally push secrets.
6969
- smoke Verify credentials, public-config, composed schema, db aggregate, and integration probes.
6970
- skill Same installer; --agent accepts all, claude, codex, cursor,
6971
- copilot, gemini, or agents (repeatable or comma-separated).
6972
- secrets Push configured db/o11y secrets into the Worker via wrangler
6973
- stdin; set stores a tenant-vault secret and set-clerk-key the
6974
- reserved Clerk secret key, write-only from stdin or an env var.
6975
- version Print the CLI version.
6976
-
6977
- Safety:
6978
- Every app has two databases on odla.ai: a sandbox (env "dev", tenant
6979
- <appId>--dev) and a live one (env "prod", tenant <appId>). Both are served by
6980
- production odla.ai \u2014 there is no separate odla to point at. New projects get
6981
- the sandbox only. Add "prod" explicitly to odla.config.mjs and pass --yes to
6982
- provision the live database; use --dry-run first to inspect the resolved plan.
6983
- Provision caches the approved developer token and service credentials under
6984
- .odla/ with mode 0600, and init adds those paths to .gitignore. Secret push
6985
- preflights Wrangler before any shown-once issuance or destructive rotation.
6986
- Projectless PM, Discussions, o11y, runbook, and identity commands use
6987
- --platform/--app/--env, ODLA_PLATFORM_URL/ODLA_APP_ID/ODLA_ENV, and
6988
- ODLA_DEV_TOKEN. Save non-secret scope metadata with "context save", then
6989
- select it explicitly with --context or ODLA_CONTEXT. Each named context gets
6990
- isolated developer and scoped-token caches. Override their locations with
6991
- ODLA_DEV_TOKEN_FILE and ODLA_ADMIN_TOKEN_FILE; ODLA_CONTEXT_FILE relocates
6992
- the metadata file. Flags and specific ODLA_* scope variables beat a selected
6993
- context, which beats project config. There is no ambient current context.
6994
- "context show" reports only provenance and cache state and never authenticates.
6995
- Provision opens the approval page in your browser automatically whenever the
6996
- machine can show one, including agent-driven runs; only CI, SSH, and
6997
- display-less hosts skip it. Use --open to force or --no-open to suppress.
6998
- Browser launch is best-effort: the printed approval URL is authoritative and
6999
- agents must relay it to the human verbatim. A started handshake is persisted
7000
- under .odla/, so a command killed mid-wait loses nothing \u2014 rerunning resumes
7001
- the same code. Outside an interactive terminal the wait is capped (90s by
7002
- default, --wait <seconds> to change); a still-pending handshake then exits
7003
- with code 75: relay the URL, wait for approval, and re-run to collect.
7004
- A fresh device handshake requires --email <odla-account> or ODLA_USER_EMAIL.
7005
- The email is a non-secret identity hint: never provide a password or session
7006
- token. The matching account must already exist, be signed in, explicitly
7007
- review the exact code, and finish any current request before claiming another.
7008
- Run Code from a GitHub checkout already connected to an app in Studio; an
7009
- odla.config.mjs may select the app explicitly but is not required. Code host
7010
- approval and credential hashes live in odla-ai/db. The host
7011
- credential is never written under .odla/; it exists only in the foreground
7012
- "code connect" process and is rotated by the next approved connection.
7013
- Calendar setup has a second human checkpoint: odla issues a state-bound Google consent URL;
7014
- OAuth codes and refresh tokens never enter the CLI, repo, chat, or app.
7015
- GitHub security uses source-read-only access plus optional metadata-only Checks write: the CLI never asks
7016
- for a PAT or provider key. GitHub read access is separate from the explicit
7017
- --ack-redacted-source consent and the exact --plan-digest printed by security
7018
- plan are required before bounded snippets reach System AI.
7019
- Run security plan first to inspect the admin-selected providers, models,
7020
- per-route bounds, credential readiness, retention, no-execution boundary,
7021
- and digest that binds consent to that exact plan.
7022
- `);
7023
- }
7024
-
7025
- // src/security-hosted-github.ts
7026
- var import_node_child_process4 = require("child_process");
7027
- var import_node_util2 = require("util");
7333
+ // src/security-hosted-github.ts
7334
+ var import_node_child_process4 = require("child_process");
7335
+ var import_node_util2 = require("util");
7028
7336
 
7029
7337
  // src/security-hosted-request.ts
7030
- async function requestHostedSecurityJson(options, path, init, action) {
7338
+ async function requestHostedSecurityJson(options, path, init, action2) {
7031
7339
  const platform = platformAudience(options.platform);
7032
7340
  const token = hostedSecurityCredential(options.token);
7033
7341
  const requestInit = {
@@ -7047,35 +7355,35 @@ async function requestHostedSecurityJson(options, path, init, action) {
7047
7355
  if (!response2.ok) {
7048
7356
  const code = optionalHostedText(body.error?.code, "error code", 128);
7049
7357
  const message2 = optionalHostedText(body.error?.message, "error message", 300);
7050
- throw new Error(`${action} failed (${response2.status})${code ? ` ${code}` : ""}${message2 ? `: ${message2}` : ""}`);
7358
+ throw new Error(`${action2} failed (${response2.status})${code ? ` ${code}` : ""}${message2 ? `: ${message2}` : ""}`);
7051
7359
  }
7052
7360
  return body;
7053
7361
  }
7054
- function hostedIdentifier(value, name) {
7055
- const result = optionalHostedText(value, name, 180);
7362
+ function hostedIdentifier(value2, name) {
7363
+ const result = optionalHostedText(value2, name, 180);
7056
7364
  if (!result || !/^[A-Za-z0-9][A-Za-z0-9._:-]*$/.test(result)) {
7057
7365
  throw new Error(`${name} is invalid`);
7058
7366
  }
7059
7367
  return result;
7060
7368
  }
7061
- function positivePolicyVersion(value, name) {
7062
- if (!Number.isSafeInteger(value) || value < 1) {
7369
+ function positivePolicyVersion(value2, name) {
7370
+ if (!Number.isSafeInteger(value2) || value2 < 1) {
7063
7371
  throw new Error(`${name} is invalid`);
7064
7372
  }
7065
- return value;
7373
+ return value2;
7066
7374
  }
7067
- function optionalHostedText(value, name, maxLength) {
7068
- if (value === void 0 || value === null || value === "") return void 0;
7069
- if (typeof value !== "string" || value.length > maxLength || /[\u0000-\u001f\u007f]/.test(value)) {
7375
+ function optionalHostedText(value2, name, maxLength) {
7376
+ if (value2 === void 0 || value2 === null || value2 === "") return void 0;
7377
+ if (typeof value2 !== "string" || value2.length > maxLength || /[\u0000-\u001f\u007f]/.test(value2)) {
7070
7378
  throw new Error(`${name} is invalid`);
7071
7379
  }
7072
- return value;
7380
+ return value2;
7073
7381
  }
7074
- function githubRepositoryName(value) {
7075
- if (typeof value !== "string" || value.length > 201) {
7382
+ function githubRepositoryName(value2) {
7383
+ if (typeof value2 !== "string" || value2.length > 201) {
7076
7384
  throw new Error("repository must be owner/name");
7077
7385
  }
7078
- const parts = value.split("/");
7386
+ const parts = value2.split("/");
7079
7387
  const owner = parts[0] ?? "";
7080
7388
  const name = (parts[1] ?? "").replace(/\.git$/i, "");
7081
7389
  if (parts.length !== 2 || !/^[A-Za-z0-9](?:[A-Za-z0-9-]{0,98}[A-Za-z0-9])?$/.test(owner) || !/^[A-Za-z0-9_.-]{1,100}$/.test(name) || name === "." || name === "..") {
@@ -7083,10 +7391,10 @@ function githubRepositoryName(value) {
7083
7391
  }
7084
7392
  return `${owner}/${name}`;
7085
7393
  }
7086
- function trustedGitHubInstallUrl(value) {
7394
+ function trustedGitHubInstallUrl(value2) {
7087
7395
  let url;
7088
7396
  try {
7089
- url = new URL(value);
7397
+ url = new URL(value2);
7090
7398
  } catch {
7091
7399
  throw new Error("odla.ai returned an invalid GitHub installation URL");
7092
7400
  }
@@ -7095,17 +7403,17 @@ function trustedGitHubInstallUrl(value) {
7095
7403
  }
7096
7404
  return url.toString();
7097
7405
  }
7098
- function hostedPollInterval(value = 2e3) {
7099
- if (!Number.isSafeInteger(value) || value < 100 || value > 3e4) {
7406
+ function hostedPollInterval(value2 = 2e3) {
7407
+ if (!Number.isSafeInteger(value2) || value2 < 100 || value2 > 3e4) {
7100
7408
  throw new Error("poll interval must be 100-30000ms");
7101
7409
  }
7102
- return value;
7410
+ return value2;
7103
7411
  }
7104
- function hostedPollTimeout(value = 10 * 6e4) {
7105
- if (!Number.isSafeInteger(value) || value < 1e3 || value > 60 * 6e4) {
7412
+ function hostedPollTimeout(value2 = 10 * 6e4) {
7413
+ if (!Number.isSafeInteger(value2) || value2 < 1e3 || value2 > 60 * 6e4) {
7106
7414
  throw new Error("poll timeout must be 1000-3600000ms");
7107
7415
  }
7108
- return value;
7416
+ return value2;
7109
7417
  }
7110
7418
  async function waitForHostedPoll(milliseconds, signal) {
7111
7419
  if (signal?.aborted) throw signal.reason ?? new DOMException("aborted", "AbortError");
@@ -7117,16 +7425,16 @@ async function waitForHostedPoll(milliseconds, signal) {
7117
7425
  }, { once: true });
7118
7426
  });
7119
7427
  }
7120
- function isValidHostedSecurityPlan(value, env) {
7121
- if (!value || value.env !== env || !/^sha256:[a-f0-9]{64}$/.test(value.planDigest) || value.consentContract !== "odla.hosted-security-consent.v1" || value.reportProjection !== "odla.hosted-security-report.v1" || value.redactionContract !== "odla.best-effort-credential-pattern-redaction.v1" || typeof value.promptBundle !== "string" || value.promptBundle.length < 1 || value.promptBundle.length > 100 || typeof value.ready !== "boolean" || typeof value.independent !== "boolean" || value.sourceDisclosure !== "redacted" || value.targetExecution !== false || !Number.isSafeInteger(value.reportRetentionDays) || value.reportRetentionDays < 1) return false;
7428
+ function isValidHostedSecurityPlan(value2, env) {
7429
+ if (!value2 || value2.env !== env || !/^sha256:[a-f0-9]{64}$/.test(value2.planDigest) || value2.consentContract !== "odla.hosted-security-consent.v1" || value2.reportProjection !== "odla.hosted-security-report.v1" || value2.redactionContract !== "odla.best-effort-credential-pattern-redaction.v1" || typeof value2.promptBundle !== "string" || value2.promptBundle.length < 1 || value2.promptBundle.length > 100 || typeof value2.ready !== "boolean" || typeof value2.independent !== "boolean" || value2.sourceDisclosure !== "redacted" || value2.targetExecution !== false || !Number.isSafeInteger(value2.reportRetentionDays) || value2.reportRetentionDays < 1) return false;
7122
7430
  const validRoute = (route2, purpose) => !!route2 && route2.purpose === purpose && typeof route2.enabled === "boolean" && typeof route2.credentialReady === "boolean" && typeof route2.provider === "string" && route2.provider.length > 0 && route2.provider.length <= 100 && typeof route2.model === "string" && route2.model.length > 0 && route2.model.length <= 200 && Number.isSafeInteger(route2.policyVersion) && route2.policyVersion >= 1 && Number.isSafeInteger(route2.maxCallsPerRun) && route2.maxCallsPerRun >= 1 && Number.isSafeInteger(route2.maxInputBytes) && route2.maxInputBytes >= 1 && Number.isSafeInteger(route2.maxOutputTokens) && route2.maxOutputTokens >= 1;
7123
- return validRoute(value.routes?.discovery, "security.discovery") && validRoute(value.routes?.validation, "security.validation");
7431
+ return validRoute(value2.routes?.discovery, "security.discovery") && validRoute(value2.routes?.validation, "security.validation");
7124
7432
  }
7125
- function hostedSecurityCredential(value) {
7126
- if (typeof value !== "string" || value.length < 8 || value.length > 8192 || /\s|[\u0000-\u001f\u007f]/.test(value)) {
7433
+ function hostedSecurityCredential(value2) {
7434
+ if (typeof value2 !== "string" || value2.length < 8 || value2.length > 8192 || /\s|[\u0000-\u001f\u007f]/.test(value2)) {
7127
7435
  throw new Error("Hosted security requires an injected odla developer token");
7128
7436
  }
7129
- return value;
7437
+ return value2;
7130
7438
  }
7131
7439
 
7132
7440
  // src/security-hosted-github.ts
@@ -7238,7 +7546,7 @@ async function defaultReadOrigin(cwd) {
7238
7546
 
7239
7547
  // src/code-local-source.ts
7240
7548
  var import_node_child_process5 = require("child_process");
7241
- var import_node_crypto = require("crypto");
7549
+ var import_node_crypto2 = require("crypto");
7242
7550
  var SOURCE_LIMITS2 = { maxFiles: 2e4, maxBytes: 512 * 1024 * 1024 };
7243
7551
  async function prepareCodeLocalSource(cwd, repository, readHead = readGitHead) {
7244
7552
  const headCommitSha = await readHead(cwd);
@@ -7279,25 +7587,25 @@ async function prepareCodeLocalSource(cwd, repository, readHead = readGitHead) {
7279
7587
  }
7280
7588
  }
7281
7589
  async function readGitHead(cwd) {
7282
- const value = await new Promise((accept, reject) => {
7590
+ const value2 = await new Promise((accept, reject) => {
7283
7591
  (0, import_node_child_process5.execFile)("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8", maxBuffer: 16384 }, (error, stdout) => {
7284
7592
  if (error) reject(new Error("code connect requires a Git checkout with an initial commit"));
7285
7593
  else accept(stdout.trim());
7286
7594
  });
7287
7595
  });
7288
- if (!/^[0-9a-f]{40}$/.test(value)) throw new Error("code connect could not resolve the checkout HEAD commit");
7289
- return value;
7596
+ if (!/^[0-9a-f]{40}$/.test(value2)) throw new Error("code connect could not resolve the checkout HEAD commit");
7597
+ return value2;
7290
7598
  }
7291
- function digestText(value) {
7292
- return `sha256:${(0, import_node_crypto.createHash)("sha256").update(value).digest("hex")}`;
7599
+ function digestText(value2) {
7600
+ return `sha256:${(0, import_node_crypto2.createHash)("sha256").update(value2).digest("hex")}`;
7293
7601
  }
7294
7602
 
7295
7603
  // src/code-images.ts
7296
7604
  var import_node_child_process6 = require("child_process");
7297
- var import_node_crypto2 = require("crypto");
7605
+ var import_node_crypto3 = require("crypto");
7298
7606
  var import_promises10 = require("fs/promises");
7299
7607
  var import_node_os3 = require("os");
7300
- var import_node_path11 = require("path");
7608
+ var import_node_path12 = require("path");
7301
7609
  var import_node_url3 = require("url");
7302
7610
 
7303
7611
  // src/code-runtime-config.ts
@@ -7380,13 +7688,13 @@ async function embeddedPiImageName() {
7380
7688
  const bundle = await (0, import_promises10.readFile)(embeddedPiAssetPath()).catch(() => {
7381
7689
  throw new Error("CLI-embedded Pi runtime is missing; reinstall this exact @odla-ai/cli version");
7382
7690
  });
7383
- return `odla-ai/pi-agent:embedded-sha256-${(0, import_node_crypto2.createHash)("sha256").update(bundle).digest("hex")}`;
7691
+ return `odla-ai/pi-agent:embedded-sha256-${(0, import_node_crypto3.createHash)("sha256").update(bundle).digest("hex")}`;
7384
7692
  }
7385
7693
  async function buildEmbeddedPiImage(engine, image, run) {
7386
- const context = await (0, import_promises10.mkdtemp)((0, import_node_path11.join)((0, import_node_os3.tmpdir)(), "odla-code-pi-"));
7694
+ const context = await (0, import_promises10.mkdtemp)((0, import_node_path12.join)((0, import_node_os3.tmpdir)(), "odla-code-pi-"));
7387
7695
  try {
7388
- await (0, import_promises10.copyFile)(embeddedPiAssetPath(), (0, import_node_path11.join)(context, "pi-agent.js"));
7389
- await (0, import_promises10.writeFile)((0, import_node_path11.join)(context, "Dockerfile"), [
7696
+ await (0, import_promises10.copyFile)(embeddedPiAssetPath(), (0, import_node_path12.join)(context, "pi-agent.js"));
7697
+ await (0, import_promises10.writeFile)((0, import_node_path12.join)(context, "Dockerfile"), [
7390
7698
  `FROM ${CODE_NODE_IMAGE}`,
7391
7699
  "COPY pi-agent.js /opt/odla/pi-agent.js",
7392
7700
  "WORKDIR /workspace",
@@ -7402,7 +7710,7 @@ async function buildEmbeddedPiImage(engine, image, run) {
7402
7710
  // src/code-connect.ts
7403
7711
  async function codeConnect(options) {
7404
7712
  const cwd = options.cwd ?? process.cwd();
7405
- const configPath = (0, import_node_path12.resolve)(cwd, options.configPath);
7713
+ const configPath = (0, import_node_path13.resolve)(cwd, options.configPath);
7406
7714
  const cfg = (0, import_node_fs14.existsSync)(configPath) ? await loadProjectConfig(configPath) : null;
7407
7715
  const requestedAppId = options.appId?.trim();
7408
7716
  if (requestedAppId && !/^[a-z0-9][a-z0-9-]{1,62}$/.test(requestedAppId)) {
@@ -7560,8 +7868,8 @@ async function runCodeRuntime(input) {
7560
7868
  for (const [name, handler] of handlers) process.removeListener(name, handler);
7561
7869
  }
7562
7870
  }
7563
- function parseConnection(value, appId, appEnv) {
7564
- const root = record4(value);
7871
+ function parseConnection(value2, appId, appEnv) {
7872
+ const root = record4(value2);
7565
7873
  const host = record4(root?.host);
7566
7874
  const offer = record4(root?.offer);
7567
7875
  const binding = record4(root?.binding);
@@ -7570,12 +7878,12 @@ function parseConnection(value, appId, appEnv) {
7570
7878
  }
7571
7879
  return root;
7572
7880
  }
7573
- function apiFailure(action, status, value) {
7574
- const message2 = record4(record4(value)?.error)?.message;
7575
- return `${action} failed (${status})${typeof message2 === "string" ? `: ${message2}` : ""}`;
7881
+ function apiFailure(action2, status, value2) {
7882
+ const message2 = record4(record4(value2)?.error)?.message;
7883
+ return `${action2} failed (${status})${typeof message2 === "string" ? `: ${message2}` : ""}`;
7576
7884
  }
7577
- function record4(value) {
7578
- return value && typeof value === "object" && !Array.isArray(value) ? value : null;
7885
+ function record4(value2) {
7886
+ return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
7579
7887
  }
7580
7888
 
7581
7889
  // src/code-command.ts
@@ -7634,8 +7942,8 @@ function developerTokenStatus(context, parsed, now = Date.now()) {
7634
7942
  cacheStatus
7635
7943
  };
7636
7944
  }
7637
- function clean3(value) {
7638
- const normalized = value?.trim();
7945
+ function clean3(value2) {
7946
+ const normalized = value2?.trim();
7639
7947
  return normalized || void 0;
7640
7948
  }
7641
7949
 
@@ -7655,9 +7963,9 @@ async function contextCommand(parsed, deps = {}) {
7655
7963
  ],
7656
7964
  3
7657
7965
  );
7658
- const action = parsed.positionals[1];
7966
+ const action2 = parsed.positionals[1];
7659
7967
  const out = deps.stdout ?? console;
7660
- if (action === "list") {
7968
+ if (action2 === "list") {
7661
7969
  const profiles = listOperatorProfiles();
7662
7970
  if (parsed.options.json === true) {
7663
7971
  out.log(JSON.stringify({ schemaVersion: 1, profiles }, null, 2));
@@ -7675,7 +7983,7 @@ async function contextCommand(parsed, deps = {}) {
7675
7983
  }
7676
7984
  return;
7677
7985
  }
7678
- if (action === "save") {
7986
+ if (action2 === "save") {
7679
7987
  if (parsed.options.token !== void 0) {
7680
7988
  throw new Error(
7681
7989
  "context save does not accept --token; contexts store scope metadata only"
@@ -7698,7 +8006,7 @@ async function contextCommand(parsed, deps = {}) {
7698
8006
  }
7699
8007
  return;
7700
8008
  }
7701
- if (action === "remove") {
8009
+ if (action2 === "remove") {
7702
8010
  const name = requireName(parsed);
7703
8011
  if (parsed.options.yes !== true) {
7704
8012
  throw new Error(`context remove "${name}" requires --yes`);
@@ -7718,9 +8026,9 @@ async function contextCommand(parsed, deps = {}) {
7718
8026
  }
7719
8027
  return;
7720
8028
  }
7721
- if (action !== "show") {
8029
+ if (action2 !== "show") {
7722
8030
  throw new Error(
7723
- `unknown context action "${action ?? ""}". Try show|list|save|remove.`
8031
+ `unknown context action "${action2 ?? ""}". Try show|list|save|remove.`
7724
8032
  );
7725
8033
  }
7726
8034
  const context = await resolveOperatorContext(parsed, {
@@ -7771,6 +8079,261 @@ function requireName(parsed) {
7771
8079
  return name;
7772
8080
  }
7773
8081
 
8082
+ // src/help.ts
8083
+ function printHelp(output = console) {
8084
+ output.log(`odla-ai
8085
+
8086
+ Start here:
8087
+ odla-ai runbook ask "<question>" The current procedure, from odla's own
8088
+ runbooks. Ask BEFORE searching the web or
8089
+ working from memory: runbooks are edited
8090
+ live, so your training data is out of date
8091
+ and this is not.
8092
+ odla-ai runbook impact After a change: which runbooks describe the
8093
+ code you just touched, so you can fix any
8094
+ step it made wrong.
8095
+
8096
+ Usage:
8097
+ odla-ai setup [--dir <project>] [--agent <name>] [--global] [--force]
8098
+ odla-ai init --app-id <id> --name <name> [--services db,ai,o11y,calendar] [--env dev --env prod]
8099
+ odla-ai doctor [--config odla.config.mjs]
8100
+ odla-ai config diff [--config odla.config.mjs] [--email <odla-account>] [--json]
8101
+ odla-ai config plan [--config odla.config.mjs] [--email <odla-account>] [--json]
8102
+ odla-ai calendar status [--env dev] [--email <odla-account>] [--json]
8103
+ odla-ai calendar calendars [--env dev] [--email <odla-account>] [--json]
8104
+ odla-ai calendar connect [--env dev] [--email <odla-account>] [--no-open] [--yes]
8105
+ odla-ai calendar disconnect [--env dev] [--email <odla-account>] --yes
8106
+ odla-ai app archive [--config odla.config.mjs] [--email <odla-account>] [--json] --yes
8107
+ odla-ai app restore [--config odla.config.mjs] [--email <odla-account>] [--json]
8108
+ odla-ai app export [--env dev] [--fresh] [--out <file>] [--email <odla-account>] [--json]
8109
+ odla-ai app import <file|-> [--env dev] [--ns <namespace>] [--id-field <f>|--key <attr>|--generate-ids] [--dry-run] [--json] --yes
8110
+ odla-ai app refresh-sandbox [--include-identity] [--include-files] [--dry-run] [--json] --yes
8111
+ odla-ai app go-live [--include-identity] [--include-files] [--dry-run] [--json] --yes
8112
+ odla-ai app promote [--dry-run] [--json] --yes
8113
+ odla-ai app rename <name> [--config odla.config.mjs] [--email <odla-account>] [--json]
8114
+ odla-ai app owners list [--config odla.config.mjs] [--email <odla-account>] [--json]
8115
+ odla-ai app owners add <email> [--email <odla-account>] [--json]
8116
+ odla-ai app owners remove <email> [--email <odla-account>] [--json]
8117
+ odla-ai pm goal list [--app <id>] [--status <s>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
8118
+ odla-ai pm task list [--app <id>] [--column <c>] [--goal <id>] [--assignee <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
8119
+ odla-ai pm decision list [--app <id>] [--status <s>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
8120
+ odla-ai pm bug list [--app <id>] [--status <s>] [--severity <s>] [--goal <id>] [--assignee <id>] [--decision <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
8121
+ odla-ai pm goal add --app <id> --title <t> [--status <s>] [--proof <text>] [--target <pct>] [--mutation-id <id>] [--json]
8122
+ odla-ai pm task add --app <id> --title <t> [--column <c>] [--goal <id>] [--assignee <id>] [--description <text>|--body <text>] [--due <epoch-ms>] [--mutation-id <id>] [--json]
8123
+ odla-ai pm decision add --app <id> --title <t> --body <text> [--status <s>] [--mutation-id <id>] [--json]
8124
+ odla-ai pm bug add --app <id> --title <t> (--description <text>|--body <text>) [--status <s>] [--severity <s>] [--goal <id>] [--assignee <id>] [--decision <id>] [--mutation-id <id>] [--json]
8125
+ odla-ai pm <goal|task|decision|bug> get <id> [--json]
8126
+ odla-ai pm goal set <id> [--title <t>|--status <s>|--proof <text>|--no-proof|--target <pct>|--no-target] [--mutation-id <id>] [--json]
8127
+ odla-ai pm task set <id> [--title <t>|--column <c>|--rank <n>|--goal <id>|--no-goal|--assignee <id>|--no-assignee|--description <text>|--body <text>|--due <epoch-ms>|--no-due] [--mutation-id <id>] [--json]
8128
+ odla-ai pm decision set <id> [--title <t>|--status <s>|--body <text>] [--mutation-id <id>] [--json]
8129
+ odla-ai pm bug set <id> [--title <t>|--status <s>|--severity <s>|--goal <id>|--no-goal|--assignee <id>|--no-assignee|--decision <id>|--no-decision|--description <text>|--body <text>] [--mutation-id <id>] [--json]
8130
+ odla-ai pm <goal|task|decision> done <id> [--mutation-id <id>]
8131
+ odla-ai pm bug done <id> [--decision <accepted-decision-id>] [--mutation-id <id>]
8132
+ odla-ai pm <goal|task|decision|bug> comment <id> --body "..." [--mutation-id <id>]
8133
+ odla-ai pm <goal|task|decision|bug> comments <id> [--json]
8134
+ odla-ai pm <goal|task|decision|bug> rm <id>
8135
+ odla-ai pm handoff --app <id> [--json]
8136
+ odla-ai discuss groups [--json]
8137
+ odla-ai discuss list [--app <id>] [--q <text>] [--state open|resolved|all] [--json]
8138
+ odla-ai discuss read <topic> [--limit <n> --offset <n>] [--json]
8139
+ odla-ai discuss post --app <id> --subject "..." --body "..." [--markup "... @[Label](kind/id)"] [--mutation-id <id>]
8140
+ odla-ai discuss reply <topic> --body "..." [--markup "..."] [--mutation-id <id>]
8141
+ odla-ai discuss resolve <topic> [--reopen] [--mutation-id <id>]
8142
+ odla-ai discuss who --q <text> [--app <id>] [--kinds user,pm:task] [--json]
8143
+ odla-ai discuss watch [<topic>] [--cursor <cursor>] [--by <authorId>] [--self <authorId>] [--interval <s>] [--timeout <s>] [--json|--jsonl]
8144
+ odla-ai agent jobs [--env dev] [--state pending|running|succeeded|dead_letter] [--limit 50] [--email <email>] [--json]
8145
+ odla-ai agent retry <job-id> [--env dev] [--email <email>] [--json]
8146
+ odla-ai context show [--context <name>] [--platform https://odla.ai] [--app <id>] [--env prod] [--json]
8147
+ odla-ai context list [--json]
8148
+ odla-ai context save <name> [--platform <url>] [--app <id>] [--env <name>] [--json]
8149
+ odla-ai context remove <name> --yes [--json]
8150
+ odla-ai o11y status [--app <id>] [--context <name>] [--platform https://odla.ai] [--env prod] [--minutes 60] [--json]
8151
+ odla-ai platform status [--context <name>] [--platform https://odla.ai] [--email <odla-account>] [--json]
8152
+ odla-ai whoami [--context <name>] [--platform https://odla.ai] [--json]
8153
+ odla-ai runbook ask "<question>" [--app <id>] [--all] [--json]
8154
+ odla-ai runbook search "<question>" [--app <id>] [--all] [--limit <n>] [--json]
8155
+ odla-ai runbook impact [--base origin/main] [--app <id>] [--all] [--limit <n>] [--json]
8156
+ odla-ai runbook lint [--app <id>] [--all] [--json]
8157
+ odla-ai runbook list [--app <id>] [--all] [--q <text>] [--json]
8158
+ odla-ai runbook comment <slug> --body "..." [--app <id>]
8159
+ odla-ai runbook get <slug> [--app <id>]
8160
+ odla-ai runbook new <slug> --title <t> --file <path|-> [--summary <s>] [--requires <specs>] [--app <id>]
8161
+ odla-ai runbook edit <slug> [--file <path|->|--body "..."] [--note <why>] [--requires <specs>] [--app <id>]
8162
+ odla-ai runbook import <dir> [--visibility operator|admin] [--dry-run] [--app <id>]
8163
+ odla-ai runbook publish <slug> [--app <id>]
8164
+ odla-ai runbook visibility <slug> <operator|admin> [--app <id>]
8165
+ odla-ai runbook archive <slug> [--app <id>]
8166
+ odla-ai runbook history <slug> [--app <id>] [--json]
8167
+ odla-ai runbook revert <slug> --version <n> [--app <id>]
8168
+ odla-ai runbook rm <slug> [--app <id>]
8169
+ odla-ai capabilities [--json]
8170
+ odla-ai code connect [--env dev|prod] [--email <odla-account>] [--engine auto|container|podman|docker] [--slots <1-64>] [--once]
8171
+ odla-ai admin ai show [--context <name>] [--platform https://odla.ai] [--email <odla-account>] [--json]
8172
+ odla-ai admin ai models [--context <name>] [--provider <id>] [--json]
8173
+ odla-ai admin ai set <purpose> [--context <name>] [--provider <id>] [--model <id>] [--enabled|--no-enabled]
8174
+ [--max-input-bytes <n>] [--max-output-tokens <n>] [--max-calls-per-run <n>] [--json]
8175
+ odla-ai admin ai set security [--context <name>] --discovery-provider <id> --discovery-model <id>
8176
+ --validation-provider <id> --validation-model <id> [--enabled|--no-enabled] [--json]
8177
+ odla-ai admin ai credentials [--context <name>] [--json]
8178
+ odla-ai admin ai credential set <provider> [--context <name>] (--from-env <NAME>|--stdin)
8179
+ odla-ai admin ai usage [--context <name>] [--app-id <id>] [--env <env>] [--run-id <id>] [--limit <1-500>] [--json]
8180
+ odla-ai admin ai audit [--context <name>] [--limit <1-200>] [--json]
8181
+ odla-ai security github connect [--repo owner/name] [--env dev] [--email <odla-account>] [--no-open]
8182
+ odla-ai security github disconnect --source <id> [--env dev] [--yes]
8183
+ odla-ai security plan [--env dev] [--json]
8184
+ odla-ai security sources [--env dev] [--json]
8185
+ odla-ai security run --source <id> --plan-digest <sha256:...> --ack-redacted-source [--ref <branch|tag|sha>] [--env dev] [--no-follow]
8186
+ odla-ai security status <job-id> [--follow] [--json]
8187
+ odla-ai security report <job-id> [--json]
8188
+ odla-ai security run [target] --ack-redacted-source [--env dev] [--profile odla] [--fail-on high]
8189
+ odla-ai security run [target] --self --ack-redacted-source
8190
+ odla-ai provision [--config odla.config.mjs] [--email <odla-account>] [--wait <seconds>] [--dry-run] [--push-secrets] [--rotate-o11y-token] [--write-dev-vars[=path]] [--yes]
8191
+ odla-ai smoke [--config odla.config.mjs] [--env dev] [--email <odla-account>] [--no-open]
8192
+ odla-ai skill install [--dir <project>] [--agent <name>] [--global] [--force]
8193
+ odla-ai secrets push --env <env> [--config odla.config.mjs] [--dry-run] [--yes]
8194
+ odla-ai secrets set <name> --env <env> (--from-env <NAME>|--stdin) [--email <odla-account>] [--config odla.config.mjs] [--yes]
8195
+ odla-ai secrets set-clerk-key --env <env> (--from-env <NAME>|--stdin) [--email <odla-account>] [--config odla.config.mjs] [--yes]
8196
+ odla-ai version
8197
+
8198
+ Commands:
8199
+ agent Inspect durable agent wakeups and explicitly requeue a
8200
+ dead-lettered job; JSON output is stable for remote operators.
8201
+ runbook odla's operational procedures, stored in the database and read at
8202
+ the moment they are followed. "ask" gives a written, cited answer;
8203
+ "search" the passages behind it; "get" the whole document.
8204
+ "impact" diffs your working tree against a base ref and names the
8205
+ runbooks that describe what you changed \u2014 run it after touching a
8206
+ package's exported API or JSDoc, or a Studio surface. "lint"
8207
+ checks the corpus the other way: every odla-ai command the
8208
+ runbooks name is held against this CLI's real command surface,
8209
+ and against the minimum versions each runbook declares. A wrong step
8210
+ is fixed with "edit", which takes effect immediately: a runbook is
8211
+ a row, not a release. Runbooks describe PROCEDURE; what an export
8212
+ does and what it guarantees is JSDoc, rendered per package at
8213
+ https://odla.ai/docs and shipped in the installed .d.ts. Answering
8214
+ a question usually needs both.
8215
+ whoami Report who this terminal is authenticated as, and whether it holds
8216
+ platform admin. A handshake-minted device token is never admin,
8217
+ however the human who approved it is configured.
8218
+ context Explain selected config, platform, app, environment, and
8219
+ developer-token provenance without printing credentials or
8220
+ starting a device handshake. Operator work can run outside a
8221
+ project checkout with explicit flags or ODLA_* environment
8222
+ variables.
8223
+ setup Install offline odla runbooks for common coding-agent harnesses.
8224
+ init Create a generic odla.config.mjs plus starter schema/rules files.
8225
+ doctor Validate and summarize the project config without network calls.
8226
+ config Read-only diff and revision-bound plan for checked-in Registry
8227
+ intent; runtime-owned fields and excluded coverage stay explicit.
8228
+ calendar Inspect, connect, or disconnect the live Google booking connection.
8229
+ app Archive (suspend, data retained), restore, export, import, or
8230
+ manage the co-owners of the app. Archiving takes every
8231
+ environment's data plane down until restored; permanent deletion
8232
+ has NO CLI \u2014 it requires a signed-in owner in Studio, and no
8233
+ machine or agent credential can purge. "app export" downloads a
8234
+ portable gzipped-JSONL snapshot of one database (--fresh takes a
8235
+ new snapshot first) \u2014 your data is always yours to take.
8236
+ "app import" upserts rows back in from JSON, JSONL, or a
8237
+ {namespace: rows} map (the shape a query returns); it writes only
8238
+ with --yes, so the bare command is a dry run, and it refuses to
8239
+ guess an id mode that would duplicate rows on a re-run.
8240
+ "app refresh-sandbox" replaces the sandbox with a copy of live
8241
+ (the routine dev loop); "app go-live" copies the sandbox into an
8242
+ EMPTY live database, once, at launch; "app promote" pushes only
8243
+ schema, rules and gates up afterwards, leaving live's rows alone.
8244
+ Each prints its plan and writes nothing without --yes, so the
8245
+ bare command is the dry run. Clerk config, triggers, allowlists,
8246
+ secrets and API keys never travel; identity rows stay behind
8247
+ unless --include-identity.
8248
+ "app rename <name>" changes only the display name humans read;
8249
+ the app id is permanent (tenants, URLs and keys embed it), so a
8250
+ rename never re-provisions anything or invalidates a credential.
8251
+ "app owners add <email>" grants a signed-up odla member the SAME
8252
+ full access as you; they then run their own "provision" to mint
8253
+ their own credentials for the shared db (the live one included)
8254
+ \u2014 no secret is ever copied between people.
8255
+ capabilities Show what the CLI automates vs agent edits and human checkpoints.
8256
+ code Enroll this Mac/Linux host for the current Studio-connected repository and run Pi.
8257
+ admin Manage platform-funded AI routing/credentials/usage with narrow device grants.
8258
+ security Connect GitHub sources and run commit-pinned hosted reviews, or scan a local snapshot.
8259
+ pm Project management (via @odla-ai/pm) shared across the apps you
8260
+ co-own: conformance goals, kanban tasks, decisions, and bugs. With
8261
+ no --app, "list" spans every co-owned project (the cross-project
8262
+ view); with --app it scopes to one. Same device-grant auth as
8263
+ "app". Entities: goal (alias conformance), task (alias kanban),
8264
+ decision, bug. Status changes and comments post to each item's
8265
+ @odla-ai/chat discussion thread.
8266
+ discuss Group discussions (via @odla-ai/chat) for the apps you co-own:
8267
+ one group per project, topics with replies, @-mentions of people,
8268
+ agents, PM items, and projects. Built for unattended use \u2014 post a
8269
+ question, then "watch" it until someone answers. "watch" exits 75
8270
+ (not 1) when it times out with nothing new, so a script can tell
8271
+ "no answer yet" from a failure and just rerun. Use "who" to look
8272
+ up the exact @[Label](kind/id) markup for a mention.
8273
+ o11y Read one stable status envelope for application RED, current
8274
+ live-sync load/freshness, the protected commit-to-visible
8275
+ canary, collector ingest/scheduler trust, Cloudflare-owned
8276
+ runtime metrics, and a machine verdict.
8277
+ --json keeps auth progress on stderr for unattended agents.
8278
+ platform Read canonical fleet health, releases, provider load/freshness,
8279
+ explicit unknowns, and next actions through a read-only grant.
8280
+ provision Register services, compose integrations, persist credentials, optionally push secrets.
8281
+ smoke Verify credentials, public-config, composed schema, db aggregate, and integration probes.
8282
+ skill Same installer; --agent accepts all, claude, codex, cursor,
8283
+ copilot, gemini, or agents (repeatable or comma-separated).
8284
+ secrets Push configured db/o11y secrets into the Worker via wrangler
8285
+ stdin; set stores a tenant-vault secret and set-clerk-key the
8286
+ reserved Clerk secret key, write-only from stdin or an env var.
8287
+ version Print the CLI version.
8288
+
8289
+ Safety:
8290
+ Every app has two databases on odla.ai: a sandbox (env "dev", tenant
8291
+ <appId>--dev) and a live one (env "prod", tenant <appId>). Both are served by
8292
+ production odla.ai \u2014 there is no separate odla to point at. New projects get
8293
+ the sandbox only. Add "prod" explicitly to odla.config.mjs and pass --yes to
8294
+ provision the live database; use --dry-run first to inspect the resolved plan.
8295
+ Provision caches the approved developer token and service credentials under
8296
+ .odla/ with mode 0600, and init adds those paths to .gitignore. Secret push
8297
+ preflights Wrangler before any shown-once issuance or destructive rotation.
8298
+ Projectless PM, Discussions, o11y, runbook, and identity commands use
8299
+ --platform/--app/--env, ODLA_PLATFORM_URL/ODLA_APP_ID/ODLA_ENV, and
8300
+ ODLA_DEV_TOKEN. Save non-secret scope metadata with "context save", then
8301
+ select it explicitly with --context or ODLA_CONTEXT. Each named context gets
8302
+ isolated developer and scoped-token caches. Override their locations with
8303
+ ODLA_DEV_TOKEN_FILE and ODLA_ADMIN_TOKEN_FILE; ODLA_CONTEXT_FILE relocates
8304
+ the metadata file. Flags and specific ODLA_* scope variables beat a selected
8305
+ context, which beats project config. There is no ambient current context.
8306
+ "context show" reports only provenance and cache state and never authenticates.
8307
+ Provision opens the approval page in your browser automatically whenever the
8308
+ machine can show one, including agent-driven runs; only CI, SSH, and
8309
+ display-less hosts skip it. Use --open to force or --no-open to suppress.
8310
+ Browser launch is best-effort: the printed approval URL is authoritative and
8311
+ agents must relay it to the human verbatim. A started handshake is persisted
8312
+ under .odla/, so a command killed mid-wait loses nothing \u2014 rerunning resumes
8313
+ the same code. Outside an interactive terminal the wait is capped (90s by
8314
+ default, --wait <seconds> to change); a still-pending handshake then exits
8315
+ with code 75: relay the URL, wait for approval, and re-run to collect.
8316
+ A fresh device handshake requires --email <odla-account> or ODLA_USER_EMAIL.
8317
+ The email is a non-secret identity hint: never provide a password or session
8318
+ token. The matching account must already exist, be signed in, explicitly
8319
+ review the exact code, and finish any current request before claiming another.
8320
+ Run Code from a GitHub checkout already connected to an app in Studio; an
8321
+ odla.config.mjs may select the app explicitly but is not required. Code host
8322
+ approval and credential hashes live in odla-ai/db. The host
8323
+ credential is never written under .odla/; it exists only in the foreground
8324
+ "code connect" process and is rotated by the next approved connection.
8325
+ Calendar setup has a second human checkpoint: odla issues a state-bound Google consent URL;
8326
+ OAuth codes and refresh tokens never enter the CLI, repo, chat, or app.
8327
+ GitHub security uses source-read-only access plus optional metadata-only Checks write: the CLI never asks
8328
+ for a PAT or provider key. GitHub read access is separate from the explicit
8329
+ --ack-redacted-source consent and the exact --plan-digest printed by security
8330
+ plan are required before bounded snippets reach System AI.
8331
+ Run security plan first to inspect the admin-selected providers, models,
8332
+ per-route bounds, credential readiness, retention, no-execution boundary,
8333
+ and digest that binds consent to that exact plan.
8334
+ `);
8335
+ }
8336
+
7774
8337
  // src/discuss-actions.ts
7775
8338
  var writeMutationId = (parsed) => stringOpt(parsed.options["mutation-id"]) ?? crypto.randomUUID();
7776
8339
  async function request(ctx, method, path, body) {
@@ -7784,8 +8347,8 @@ async function request(ctx, method, path, body) {
7784
8347
  throw new Error(`discuss ${method} ${path} failed: ${data.error ?? `registry returned ${res.status}`}`);
7785
8348
  return data;
7786
8349
  }
7787
- function emit(ctx, value, human) {
7788
- if (ctx.json) ctx.out.log(JSON.stringify(value, null, 2));
8350
+ function emit(ctx, value2, human) {
8351
+ if (ctx.json) ctx.out.log(JSON.stringify(value2, null, 2));
7789
8352
  else human();
7790
8353
  }
7791
8354
  var state = (topic) => topic.resolved ? "resolved" : "open";
@@ -7830,8 +8393,8 @@ async function discussList(ctx, parsed) {
7830
8393
  ["limit", "limit"],
7831
8394
  ["offset", "offset"]
7832
8395
  ]) {
7833
- const value = stringOpt(parsed.options[flag]);
7834
- if (value) query.set(param, value);
8396
+ const value2 = stringOpt(parsed.options[flag]);
8397
+ if (value2) query.set(param, value2);
7835
8398
  }
7836
8399
  const qs = query.toString();
7837
8400
  const page = await request(ctx, "GET", `/topics${qs ? `?${qs}` : ""}`);
@@ -8028,11 +8591,11 @@ var MAX_CONSECUTIVE_FAILURES = 5;
8028
8591
  var MAX_BACKOFF_MS = 3e4;
8029
8592
  function numberOpt2(parsed, flag, fallback) {
8030
8593
  const raw = stringOpt(parsed.options[flag]);
8031
- const value = raw == null ? NaN : Number(raw);
8032
- return Number.isFinite(value) && value > 0 ? value : fallback;
8594
+ const value2 = raw == null ? NaN : Number(raw);
8595
+ return Number.isFinite(value2) && value2 > 0 ? value2 : fallback;
8033
8596
  }
8034
- function jsonl(ctx, parsed, value) {
8035
- if (parsed.options.jsonl === true) ctx.out.log(JSON.stringify({ v: 1, ...value }));
8597
+ function jsonl(ctx, parsed, value2) {
8598
+ if (parsed.options.jsonl === true) ctx.out.log(JSON.stringify({ v: 1, ...value2 }));
8036
8599
  }
8037
8600
  async function discussWatch(ctx, topicId, parsed) {
8038
8601
  if (ctx.json && parsed.options.jsonl === true) throw new Error("--json and --jsonl cannot be combined");
@@ -8192,8 +8755,8 @@ var ALLOWED = [
8192
8755
  "platform",
8193
8756
  "context"
8194
8757
  ];
8195
- function requireId(id, action) {
8196
- if (!id) throw new Error(`"discuss ${action}" needs a topic id`);
8758
+ function requireId(id, action2) {
8759
+ if (!id) throw new Error(`"discuss ${action2}" needs a topic id`);
8197
8760
  return id;
8198
8761
  }
8199
8762
  async function buildContext(parsed, deps) {
@@ -8227,11 +8790,11 @@ async function buildContext(parsed, deps) {
8227
8790
  }
8228
8791
  async function discussCommand(parsed, deps = {}) {
8229
8792
  assertArgs(parsed, ALLOWED, 3);
8230
- const action = parsed.positionals[1];
8793
+ const action2 = parsed.positionals[1];
8231
8794
  const id = parsed.positionals[2];
8232
- if (!action) throw new Error('"discuss" needs an action. Run "odla-ai help".');
8795
+ if (!action2) throw new Error('"discuss" needs an action. Run "odla-ai help".');
8233
8796
  const ctx = await buildContext(parsed, deps);
8234
- switch (action) {
8797
+ switch (action2) {
8235
8798
  case "groups":
8236
8799
  return discussGroups(ctx);
8237
8800
  case "list":
@@ -8253,7 +8816,7 @@ async function discussCommand(parsed, deps = {}) {
8253
8816
  return;
8254
8817
  }
8255
8818
  default:
8256
- throw new Error(`unknown discuss action "${action}". Run "odla-ai help".`);
8819
+ throw new Error(`unknown discuss action "${action2}". Run "odla-ai help".`);
8257
8820
  }
8258
8821
  }
8259
8822
 
@@ -8294,13 +8857,13 @@ async function pmRequest(ctx, method, path, body) {
8294
8857
  function collectFields(parsed, allowClear) {
8295
8858
  const out = {};
8296
8859
  for (const [flag, spec] of Object.entries(FIELD_MAP)) {
8297
- const value = parsed.options[flag];
8298
- if (value === void 0 || value === true) continue;
8299
- if (value === false) {
8860
+ const value2 = parsed.options[flag];
8861
+ if (value2 === void 0 || value2 === true) continue;
8862
+ if (value2 === false) {
8300
8863
  if (allowClear) out[spec.key] = null;
8301
8864
  continue;
8302
8865
  }
8303
- const text = stringOpt(value);
8866
+ const text = stringOpt(value2);
8304
8867
  out[spec.key] = spec.num ? Number(text) : text;
8305
8868
  }
8306
8869
  return out;
@@ -8321,8 +8884,8 @@ function statusCol(entity, r) {
8321
8884
  function printRecord(ctx, entity, r) {
8322
8885
  ctx.out.log(`${r.id} [${statusCol(entity, r)}] ${r.appId} ${r.title ?? ""}`);
8323
8886
  }
8324
- function emit2(ctx, value, human) {
8325
- if (ctx.json) ctx.out.log(JSON.stringify(value, null, 2));
8887
+ function emit2(ctx, value2, human) {
8888
+ if (ctx.json) ctx.out.log(JSON.stringify(value2, null, 2));
8326
8889
  else human();
8327
8890
  }
8328
8891
  async function pmList(ctx, entity, parsed) {
@@ -8368,8 +8931,8 @@ async function pmAdd(ctx, entity, parsed) {
8368
8931
  emit2(ctx, res, () => ctx.out.log(`created ${entity} ${res.id}`));
8369
8932
  }
8370
8933
  async function pmGet(ctx, entity, id) {
8371
- const { record: record7 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id)}`);
8372
- emit2(ctx, record7, () => printRecord(ctx, entity, record7));
8934
+ const { record: record8 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id)}`);
8935
+ emit2(ctx, record8, () => printRecord(ctx, entity, record8));
8373
8936
  }
8374
8937
  async function pmSet(ctx, entity, id, parsed) {
8375
8938
  const patch2 = collectEntityFields(entity, parsed, true);
@@ -8414,9 +8977,9 @@ async function pmHandoff(ctx, parsed) {
8414
8977
  ]);
8415
8978
  const handoff = {
8416
8979
  appId,
8417
- unmetGoals: goals.filter((record7) => record7.status !== "met"),
8418
- activeTasks: tasks.filter((record7) => record7.column !== "done"),
8419
- openBugs: bugs.filter((record7) => record7.status !== "fixed" && record7.status !== "wontfix")
8980
+ unmetGoals: goals.filter((record8) => record8.status !== "met"),
8981
+ activeTasks: tasks.filter((record8) => record8.column !== "done"),
8982
+ openBugs: bugs.filter((record8) => record8.status !== "fixed" && record8.status !== "wontfix")
8420
8983
  };
8421
8984
  const result = {
8422
8985
  ...handoff,
@@ -8435,10 +8998,10 @@ async function pmHandoff(ctx, parsed) {
8435
8998
  ]) {
8436
8999
  ctx.out.log(`${label}:`);
8437
9000
  if (!records.length) ctx.out.log("- (none)");
8438
- else for (const record7 of records) printRecord(
9001
+ else for (const record8 of records) printRecord(
8439
9002
  ctx,
8440
9003
  label === "unmet goals" ? "goal" : label === "active tasks" ? "task" : "bug",
8441
- record7
9004
+ record8
8442
9005
  );
8443
9006
  }
8444
9007
  });
@@ -8514,18 +9077,18 @@ var ENTITY_OPTIONS = {
8514
9077
  done: ["decision"]
8515
9078
  }
8516
9079
  };
8517
- function canonicalAction(action) {
8518
- if (action === "create") return "add";
8519
- if (action === "update" || action === "status" || action === "move") return "set";
8520
- if (action === "delete") return "rm";
8521
- return action in ACTION_OPTIONS ? action : null;
8522
- }
8523
- function allowedOptions(entity, action) {
8524
- const entityOptions = action === "list" || action === "add" || action === "set" || action === "done" ? ENTITY_OPTIONS[entity][action] : [];
8525
- return [...COMMON_OPTIONS, ...ACTION_OPTIONS[action], ...entityOptions];
8526
- }
8527
- function requireId2(id, action) {
8528
- if (!id) throw new Error(`"pm ... ${action}" needs an item id`);
9080
+ function canonicalAction(action2) {
9081
+ if (action2 === "create") return "add";
9082
+ if (action2 === "update" || action2 === "status" || action2 === "move") return "set";
9083
+ if (action2 === "delete") return "rm";
9084
+ return action2 in ACTION_OPTIONS ? action2 : null;
9085
+ }
9086
+ function allowedOptions(entity, action2) {
9087
+ const entityOptions = action2 === "list" || action2 === "add" || action2 === "set" || action2 === "done" ? ENTITY_OPTIONS[entity][action2] : [];
9088
+ return [...COMMON_OPTIONS, ...ACTION_OPTIONS[action2], ...entityOptions];
9089
+ }
9090
+ function requireId2(id, action2) {
9091
+ if (!id) throw new Error(`"pm ... ${action2}" needs an item id`);
8529
9092
  return id;
8530
9093
  }
8531
9094
  async function buildContext2(parsed, deps) {
@@ -8561,29 +9124,136 @@ async function pmCommand(parsed, deps = {}) {
8561
9124
  const entity = ALIASES[word];
8562
9125
  if (!entity) throw new Error(`unknown pm entity "${word}". Try "odla-ai pm bug list" (goal|task|decision|bug).`);
8563
9126
  const requestedAction = parsed.positionals[2] ?? "list";
8564
- const action = canonicalAction(requestedAction);
8565
- if (!action) throw new Error(`unknown pm action "${requestedAction}". Try list|add|get|set|done|comment|comments|rm.`);
8566
- assertArgs(parsed, allowedOptions(entity, action), 4);
9127
+ const action2 = canonicalAction(requestedAction);
9128
+ if (!action2) throw new Error(`unknown pm action "${requestedAction}". Try list|add|get|set|done|comment|comments|rm.`);
9129
+ assertArgs(parsed, allowedOptions(entity, action2), 4);
8567
9130
  const ctx = await buildContext2(parsed, deps);
8568
9131
  const id = parsed.positionals[3];
8569
- switch (action) {
9132
+ switch (action2) {
8570
9133
  case "list":
8571
9134
  return pmList(ctx, entity, parsed);
8572
9135
  case "add":
8573
9136
  return pmAdd(ctx, entity, parsed);
8574
9137
  case "get":
8575
- return pmGet(ctx, entity, requireId2(id, action));
9138
+ return pmGet(ctx, entity, requireId2(id, action2));
8576
9139
  case "set":
8577
- return pmSet(ctx, entity, requireId2(id, action), parsed);
9140
+ return pmSet(ctx, entity, requireId2(id, action2), parsed);
8578
9141
  case "done":
8579
- return pmDone(ctx, entity, requireId2(id, action), parsed);
9142
+ return pmDone(ctx, entity, requireId2(id, action2), parsed);
8580
9143
  case "comment":
8581
- return pmComment(ctx, entity, requireId2(id, action), parsed);
9144
+ return pmComment(ctx, entity, requireId2(id, action2), parsed);
8582
9145
  case "comments":
8583
- return pmComments(ctx, entity, requireId2(id, action));
9146
+ return pmComments(ctx, entity, requireId2(id, action2));
8584
9147
  case "rm":
8585
- return pmRemove(ctx, entity, requireId2(id, action));
9148
+ return pmRemove(ctx, entity, requireId2(id, action2));
9149
+ }
9150
+ }
9151
+
9152
+ // src/platform-output.ts
9153
+ function printPlatformStatus(status, out) {
9154
+ out.log(`platform ${status.verdict.status} ${status.observedAt}`);
9155
+ out.log(
9156
+ `fleet ${status.summary.healthy}/${status.summary.services} healthy ${status.summary.required} required ${status.summary.unreachable} unreachable ${status.summary.notConfigured} not configured`
9157
+ );
9158
+ out.log(`catalog ${status.catalog.revision}`);
9159
+ out.log(
9160
+ "service required health probe ms version provider age requests errors cpu p99 us wall p99 us"
9161
+ );
9162
+ for (const service of status.services) {
9163
+ const metrics = service.provider.metrics;
9164
+ out.log([
9165
+ service.id,
9166
+ service.required ? "yes" : "no",
9167
+ service.health.status,
9168
+ value(service.health.latencyMs),
9169
+ service.release.observedVersionId ?? "unknown",
9170
+ service.provider.status,
9171
+ age(service.provider.ageMs),
9172
+ value(metrics?.requests),
9173
+ value(metrics?.errors),
9174
+ value(metrics?.cpuTimeP99),
9175
+ value(metrics?.wallTimeP99)
9176
+ ].join(" "));
9177
+ }
9178
+ if (status.verdict.reasons.length) {
9179
+ out.log(`reasons ${status.verdict.reasons.join(", ")}`);
9180
+ }
9181
+ for (const action2 of status.nextActions) {
9182
+ out.log(`next ${action2.service} ${action2.code} ${action2.message}`);
9183
+ }
9184
+ }
9185
+ function value(input) {
9186
+ return input === null || input === void 0 ? "unknown" : String(input);
9187
+ }
9188
+ function age(input) {
9189
+ if (input === null) return "unknown";
9190
+ if (input < 1e3) return `${input}ms`;
9191
+ if (input < 6e4) return `${Math.round(input / 1e3)}s`;
9192
+ return `${Math.round(input / 6e4)}m`;
9193
+ }
9194
+
9195
+ // src/platform-command.ts
9196
+ async function platformCommand(parsed, deps = {}) {
9197
+ assertArgs(
9198
+ parsed,
9199
+ ["config", "context", "platform", "token", "email", "open", "json"],
9200
+ 2
9201
+ );
9202
+ const action2 = parsed.positionals[1];
9203
+ if (action2 !== "status") {
9204
+ throw new Error(
9205
+ `unknown platform action "${action2 ?? ""}". Try "odla-ai platform status --json".`
9206
+ );
9207
+ }
9208
+ const context = await resolveOperatorContext(parsed, {
9209
+ allowMissingConfig: true
9210
+ });
9211
+ const platform = context.platform.value;
9212
+ const doFetch = deps.fetch ?? fetch;
9213
+ const out = deps.stdout ?? console;
9214
+ const token = await resolveAdminPlatformToken({
9215
+ platform,
9216
+ scope: "platform:status:read",
9217
+ token: stringOpt(parsed.options.token),
9218
+ tokenFile: context.credentials.scopedTokenFile,
9219
+ email: stringOpt(parsed.options.email),
9220
+ open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
9221
+ fetch: doFetch,
9222
+ stdout: out,
9223
+ openApprovalUrl: deps.openUrl,
9224
+ label: "odla CLI (platform fleet status)"
9225
+ });
9226
+ const response2 = await doFetch(`${platform}/registry/platform/status`, {
9227
+ headers: { authorization: `Bearer ${token}` }
9228
+ });
9229
+ const body = await response2.json().catch(() => null);
9230
+ if (!response2.ok) {
9231
+ throw new Error(
9232
+ `read platform status failed (HTTP ${response2.status}): ${apiMessage(body)}`
9233
+ );
9234
+ }
9235
+ if (!isPlatformStatus(body)) {
9236
+ throw new Error("platform returned an invalid odla.platform-status/v1 envelope");
8586
9237
  }
9238
+ if (parsed.options.json === true) {
9239
+ out.log(JSON.stringify(body, null, 2));
9240
+ } else {
9241
+ printPlatformStatus(body, out);
9242
+ }
9243
+ }
9244
+ function isPlatformStatus(value2) {
9245
+ if (!record5(value2) || value2.schemaVersion !== "odla.platform-status/v1") return false;
9246
+ if (!record5(value2.verdict) || !Array.isArray(value2.verdict.reasons)) return false;
9247
+ if (!record5(value2.catalog) || !record5(value2.summary)) return false;
9248
+ return Array.isArray(value2.services) && Array.isArray(value2.nextActions);
9249
+ }
9250
+ function apiMessage(value2) {
9251
+ if (!record5(value2)) return "request failed";
9252
+ const error = record5(value2.error) ? value2.error : value2;
9253
+ return typeof error.message === "string" ? error.message : typeof error.code === "string" ? error.code : "request failed";
9254
+ }
9255
+ function record5(value2) {
9256
+ return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
8587
9257
  }
8588
9258
 
8589
9259
  // src/o11y-verdict.ts
@@ -8623,7 +9293,7 @@ function statusVerdict(reads) {
8623
9293
  severity: "degraded"
8624
9294
  });
8625
9295
  }
8626
- const performance = record5(reads.liveSync.body.performance) ? reads.liveSync.body.performance : null;
9296
+ const performance = record6(reads.liveSync.body.performance) ? reads.liveSync.body.performance : null;
8627
9297
  if (performance?.status === "unavailable") {
8628
9298
  reasons.push({
8629
9299
  source: "liveSync",
@@ -8704,11 +9374,11 @@ function statusVerdict(reads) {
8704
9374
  reasons
8705
9375
  };
8706
9376
  }
8707
- function record5(value) {
8708
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
9377
+ function record6(value2) {
9378
+ return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
8709
9379
  }
8710
- function numeric2(value) {
8711
- return typeof value === "number" && Number.isFinite(value) ? value : 0;
9380
+ function numeric2(value2) {
9381
+ return typeof value2 === "number" && Number.isFinite(value2) ? value2 : 0;
8712
9382
  }
8713
9383
  function collectorReason(read3, fallback) {
8714
9384
  const reasons = read3.body.reasons;
@@ -8732,7 +9402,7 @@ function printO11yStatus(status, out) {
8732
9402
  out.log(
8733
9403
  `o11y status ${status.scope.appId}/${status.scope.env} (${status.scope.minutes}m)`
8734
9404
  );
8735
- const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record6) : [];
9405
+ const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record7) : [];
8736
9406
  const requests = routes.reduce(
8737
9407
  (total, row) => total + numeric3(row.requests),
8738
9408
  0
@@ -8744,39 +9414,39 @@ function printO11yStatus(status, out) {
8744
9414
  out.log(
8745
9415
  `application ${status.application.httpStatus} ${requests} requests ${errors} errors`
8746
9416
  );
8747
- const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(record6) : [];
9417
+ const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(record7) : [];
8748
9418
  out.log(
8749
9419
  `application-versions ${status.applicationVersions.httpStatus} ${versions.length ? versions.slice(0, 5).map(
8750
9420
  (row) => `${String(row.value || "(unattributed)")}:${numeric3(row.requests)}`
8751
9421
  ).join(", ") : "none observed"}`
8752
9422
  );
8753
9423
  out.log(liveSyncLine(status.liveSync));
8754
- const canaryDurations = record6(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
9424
+ const canaryDurations = record7(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
8755
9425
  out.log(
8756
9426
  `canary ${status.canary.httpStatus} ${String(status.canary.body.status ?? status.canary.body.error ?? "unavailable")} ${optionalNumeric(canaryDurations.publishToVisibleMs)} publish-to-visible`
8757
9427
  );
8758
- const collectorIngest = record6(status.collector.body.ingest) ? status.collector.body.ingest : {};
8759
- const collectorStorage = record6(collectorIngest.storage) ? collectorIngest.storage : {};
9428
+ const collectorIngest = record7(status.collector.body.ingest) ? status.collector.body.ingest : {};
9429
+ const collectorStorage = record7(collectorIngest.storage) ? collectorIngest.storage : {};
8760
9430
  out.log(
8761
9431
  `collector ${status.collector.httpStatus} ${String(status.collector.body.status ?? status.collector.body.error ?? "unavailable")} ${numeric3(collectorStorage.affectedPoints)} affected points`
8762
9432
  );
8763
- const providerMetrics = record6(status.provider.body.metrics) ? status.provider.body.metrics : {};
8764
- const providerCapacity = record6(status.provider.body.capacity) ? status.provider.body.capacity : {};
8765
- const workerMemory = record6(providerCapacity.memory) ? providerCapacity.memory : {};
9433
+ const providerMetrics = record7(status.provider.body.metrics) ? status.provider.body.metrics : {};
9434
+ const providerCapacity = record7(status.provider.body.capacity) ? status.provider.body.capacity : {};
9435
+ const workerMemory = record7(providerCapacity.memory) ? providerCapacity.memory : {};
8766
9436
  out.log(
8767
9437
  `cloudflare ${status.provider.httpStatus} ${String(status.provider.body.status ?? status.provider.body.error ?? "unavailable")} ${numeric3(providerMetrics.requests)} invocations ${numeric3(providerMetrics.errors)} runtime errors ${optionalBytes(workerMemory.headroomBytes)} isolate memory headroom`
8768
9438
  );
8769
9439
  for (const line of providerCapacityLines(status.providerCapacity)) {
8770
9440
  out.log(line);
8771
9441
  }
8772
- const coverage = record6(status.providerReconciliation.body.comparison) ? status.providerReconciliation.body.comparison : {};
8773
- const coverageCounts = record6(status.providerReconciliation.body.counts) ? status.providerReconciliation.body.counts : {};
8774
- const coverageBudget = record6(status.providerReconciliation.body.budget) ? status.providerReconciliation.body.budget : {};
9442
+ const coverage = record7(status.providerReconciliation.body.comparison) ? status.providerReconciliation.body.comparison : {};
9443
+ const coverageCounts = record7(status.providerReconciliation.body.counts) ? status.providerReconciliation.body.counts : {};
9444
+ const coverageBudget = record7(status.providerReconciliation.body.budget) ? status.providerReconciliation.body.budget : {};
8775
9445
  out.log(
8776
9446
  `request-coverage ${status.providerReconciliation.httpStatus} ${String(status.providerReconciliation.body.status ?? status.providerReconciliation.body.error ?? "unavailable")} ${optionalPercent(coverage.applicationCoverage)} application/provider ${numeric3(coverageCounts.applicationRequests)}/${numeric3(coverageCounts.providerRequests)} requests \xB1${optionalPercent(coverageBudget.maxRelativeError)} budget`
8777
9447
  );
8778
9448
  const providerPoints = Array.isArray(status.providerHistory.body.points) ? status.providerHistory.body.points.length : 0;
8779
- const providerFreshness = record6(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
9449
+ const providerFreshness = record7(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
8780
9450
  out.log(
8781
9451
  `cloudflare-history ${status.providerHistory.httpStatus} ${String(status.providerHistory.body.status ?? status.providerHistory.body.error ?? "unavailable")} ${providerPoints} snapshots ${optionalAge(providerFreshness.ageMs)} old`
8782
9452
  );
@@ -8785,17 +9455,17 @@ function printO11yStatus(status, out) {
8785
9455
  );
8786
9456
  }
8787
9457
  function providerCapacityLines(read3) {
8788
- const resources = record6(read3.body.resources) ? read3.body.resources : {};
8789
- const durableObjects = record6(resources.durableObjects) ? resources.durableObjects : {};
8790
- const periodic = record6(durableObjects.periodic) ? durableObjects.periodic : {};
8791
- const storage = record6(durableObjects.sqliteStorage) ? durableObjects.sqliteStorage : {};
8792
- const d1 = record6(resources.d1) ? resources.d1 : {};
8793
- const d1Activity = record6(d1.activity) ? d1.activity : {};
8794
- const d1Storage = record6(d1.storage) ? d1.storage : {};
8795
- const d1Latency = record6(d1Activity.latency) ? d1Activity.latency : {};
8796
- const r2 = record6(resources.r2) ? resources.r2 : {};
8797
- const r2Operations = record6(r2.operations) ? r2.operations : {};
8798
- const r2Storage = record6(r2.storage) ? r2.storage : {};
9458
+ const resources = record7(read3.body.resources) ? read3.body.resources : {};
9459
+ const durableObjects = record7(resources.durableObjects) ? resources.durableObjects : {};
9460
+ const periodic = record7(durableObjects.periodic) ? durableObjects.periodic : {};
9461
+ const storage = record7(durableObjects.sqliteStorage) ? durableObjects.sqliteStorage : {};
9462
+ const d1 = record7(resources.d1) ? resources.d1 : {};
9463
+ const d1Activity = record7(d1.activity) ? d1.activity : {};
9464
+ const d1Storage = record7(d1.storage) ? d1.storage : {};
9465
+ const d1Latency = record7(d1Activity.latency) ? d1Activity.latency : {};
9466
+ const r2 = record7(resources.r2) ? resources.r2 : {};
9467
+ const r2Operations = record7(r2.operations) ? r2.operations : {};
9468
+ const r2Storage = record7(r2.storage) ? r2.storage : {};
8799
9469
  const status = String(
8800
9470
  read3.body.status ?? read3.body.error ?? "unavailable"
8801
9471
  );
@@ -8806,42 +9476,42 @@ function providerCapacityLines(read3) {
8806
9476
  ];
8807
9477
  }
8808
9478
  function liveSyncLine(read3) {
8809
- const performance = record6(read3.body.performance) ? read3.body.performance : {};
8810
- const commitToSend = record6(performance.commitToSend) ? performance.commitToSend : {};
9479
+ const performance = record7(read3.body.performance) ? read3.body.performance : {};
9480
+ const commitToSend = record7(performance.commitToSend) ? performance.commitToSend : {};
8811
9481
  return `live-sync ${read3.httpStatus} ${String(read3.body.status ?? read3.body.error ?? "unavailable")} ${numeric3(read3.body.activeConnections)} active ${optionalNumeric(commitToSend.p95)} commit-to-send p95 ${numeric3(performance.sendFailures)} send failures`;
8812
9482
  }
8813
- function record6(value) {
8814
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
9483
+ function record7(value2) {
9484
+ return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
8815
9485
  }
8816
- function numeric3(value) {
8817
- return typeof value === "number" && Number.isFinite(value) ? value : 0;
9486
+ function numeric3(value2) {
9487
+ return typeof value2 === "number" && Number.isFinite(value2) ? value2 : 0;
8818
9488
  }
8819
- function optionalNumeric(value) {
8820
- return typeof value === "number" && Number.isFinite(value) ? `${value} ms` : "\u2014";
9489
+ function optionalNumeric(value2) {
9490
+ return typeof value2 === "number" && Number.isFinite(value2) ? `${value2} ms` : "\u2014";
8821
9491
  }
8822
- function optionalAge(value) {
8823
- if (typeof value !== "number" || !Number.isFinite(value)) return "unknown";
8824
- return `${Math.max(0, Math.round(value / 1e3))}s`;
9492
+ function optionalAge(value2) {
9493
+ if (typeof value2 !== "number" || !Number.isFinite(value2)) return "unknown";
9494
+ return `${Math.max(0, Math.round(value2 / 1e3))}s`;
8825
9495
  }
8826
- function optionalPercent(value) {
8827
- return typeof value === "number" && Number.isFinite(value) ? `${Math.round(value * 100)}%` : "\u2014";
9496
+ function optionalPercent(value2) {
9497
+ return typeof value2 === "number" && Number.isFinite(value2) ? `${Math.round(value2 * 100)}%` : "\u2014";
8828
9498
  }
8829
- function optionalBytes(value) {
8830
- if (typeof value !== "number" || !Number.isFinite(value)) return "\u2014";
8831
- if (value >= 1024 * 1024 * 1024) {
8832
- return `${(value / (1024 * 1024 * 1024)).toFixed(2)} GiB`;
9499
+ function optionalBytes(value2) {
9500
+ if (typeof value2 !== "number" || !Number.isFinite(value2)) return "\u2014";
9501
+ if (value2 >= 1024 * 1024 * 1024) {
9502
+ return `${(value2 / (1024 * 1024 * 1024)).toFixed(2)} GiB`;
8833
9503
  }
8834
- if (value >= 1024 * 1024) {
8835
- return `${(value / (1024 * 1024)).toFixed(1)} MiB`;
9504
+ if (value2 >= 1024 * 1024) {
9505
+ return `${(value2 / (1024 * 1024)).toFixed(1)} MiB`;
8836
9506
  }
8837
- if (value >= 1024) return `${(value / 1024).toFixed(1)} KiB`;
8838
- return `${Math.round(value)} B`;
9507
+ if (value2 >= 1024) return `${(value2 / 1024).toFixed(1)} KiB`;
9508
+ return `${Math.round(value2)} B`;
8839
9509
  }
8840
- function optionalCount(value, unit) {
8841
- return typeof value === "number" && Number.isFinite(value) ? `${value} ${unit}` : `\u2014 ${unit}`;
9510
+ function optionalCount(value2, unit) {
9511
+ return typeof value2 === "number" && Number.isFinite(value2) ? `${value2} ${unit}` : `\u2014 ${unit}`;
8842
9512
  }
8843
- function optionalAgeSeconds(value) {
8844
- return typeof value === "number" && Number.isFinite(value) ? `${Math.max(0, Math.round(value))}s` : "unknown";
9513
+ function optionalAgeSeconds(value2) {
9514
+ return typeof value2 === "number" && Number.isFinite(value2) ? `${Math.max(0, Math.round(value2))}s` : "unknown";
8845
9515
  }
8846
9516
 
8847
9517
  // src/o11y-command.ts
@@ -8861,10 +9531,10 @@ async function o11yCommand(parsed, deps = {}) {
8861
9531
  ],
8862
9532
  2
8863
9533
  );
8864
- const action = parsed.positionals[1];
8865
- if (action !== "status") {
9534
+ const action2 = parsed.positionals[1];
9535
+ if (action2 !== "status") {
8866
9536
  throw new Error(
8867
- `unknown o11y action "${action ?? ""}". Try "odla-ai o11y status --json".`
9537
+ `unknown o11y action "${action2 ?? ""}". Try "odla-ai o11y status --json".`
8868
9538
  );
8869
9539
  }
8870
9540
  const minutes = statusMinutes(
@@ -8971,11 +9641,11 @@ async function o11yCommand(parsed, deps = {}) {
8971
9641
  }
8972
9642
  printO11yStatus(status, out);
8973
9643
  }
8974
- function statusMinutes(value) {
8975
- if (!Number.isSafeInteger(value) || value < 1 || value > 7 * 24 * 60) {
9644
+ function statusMinutes(value2) {
9645
+ if (!Number.isSafeInteger(value2) || value2 < 1 || value2 > 7 * 24 * 60) {
8976
9646
  throw new Error("--minutes must be an integer from 1 to 10080");
8977
9647
  }
8978
- return value;
9648
+ return value2;
8979
9649
  }
8980
9650
  async function read2(url, headers, doFetch) {
8981
9651
  const response2 = await doFetch(url, { headers });
@@ -8983,8 +9653,8 @@ async function read2(url, headers, doFetch) {
8983
9653
  let body = {};
8984
9654
  if (text) {
8985
9655
  try {
8986
- const value = JSON.parse(text);
8987
- body = value && typeof value === "object" && !Array.isArray(value) ? value : { value };
9656
+ const value2 = JSON.parse(text);
9657
+ body = value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : { value: value2 };
8988
9658
  } catch {
8989
9659
  body = { message: text.slice(0, 300) };
8990
9660
  }
@@ -8993,7 +9663,7 @@ async function read2(url, headers, doFetch) {
8993
9663
  }
8994
9664
 
8995
9665
  // src/provision.ts
8996
- var import_apps7 = require("@odla-ai/apps");
9666
+ var import_apps10 = require("@odla-ai/apps");
8997
9667
  var import_ai3 = require("@odla-ai/ai");
8998
9668
  var import_node_process10 = __toESM(require("process"), 1);
8999
9669
 
@@ -9003,7 +9673,7 @@ async function provisionIntegrationSeeds(doFetch, endpoint, tenantId, dbKey, int
9003
9673
  const base = `${endpoint}/app/${encodeURIComponent(tenantId)}`;
9004
9674
  for (const integration of integrations) {
9005
9675
  for (const seed of integration.seeds ?? []) {
9006
- const payload = await postJson2(doFetch, `${base}/query`, dbKey, {
9676
+ const payload = await postJson3(doFetch, `${base}/query`, dbKey, {
9007
9677
  query: { [seed.ns]: { $: { where: { [seed.key.attr]: seed.key.value }, limit: 1 } } }
9008
9678
  });
9009
9679
  const rows = isRecord7(payload) && isRecord7(payload.result) ? payload.result[seed.ns] : void 0;
@@ -9014,7 +9684,7 @@ async function provisionIntegrationSeeds(doFetch, endpoint, tenantId, dbKey, int
9014
9684
  out.log(`${env}: integration ${integration.id} seed ${seed.id} already exists`);
9015
9685
  continue;
9016
9686
  }
9017
- await postJson2(doFetch, `${base}/transact`, dbKey, {
9687
+ await postJson3(doFetch, `${base}/transact`, dbKey, {
9018
9688
  mutationId: `integration:${integration.id}:seed:${seed.id}`,
9019
9689
  ops: [{
9020
9690
  t: "update",
@@ -9029,7 +9699,7 @@ async function provisionIntegrationSeeds(doFetch, endpoint, tenantId, dbKey, int
9029
9699
  }
9030
9700
  }
9031
9701
  }
9032
- async function postJson2(doFetch, url, bearer, body) {
9702
+ async function postJson3(doFetch, url, bearer, body) {
9033
9703
  const res = await doFetch(url, {
9034
9704
  method: "POST",
9035
9705
  headers: { authorization: `Bearer ${bearer}`, "content-type": "application/json" },
@@ -9038,8 +9708,8 @@ async function postJson2(doFetch, url, bearer, body) {
9038
9708
  if (!res.ok) throw new Error(`${new URL(url).pathname} failed: ${res.status} ${await responseText(res)}`);
9039
9709
  return res.json().catch(() => ({}));
9040
9710
  }
9041
- function isRecord7(value) {
9042
- return value !== null && typeof value === "object" && !Array.isArray(value);
9711
+ function isRecord7(value2) {
9712
+ return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
9043
9713
  }
9044
9714
  async function responseText(res) {
9045
9715
  try {
@@ -9050,9 +9720,9 @@ async function responseText(res) {
9050
9720
  }
9051
9721
 
9052
9722
  // src/provision-credentials.ts
9053
- var import_apps5 = require("@odla-ai/apps");
9723
+ var import_apps9 = require("@odla-ai/apps");
9054
9724
  async function provisionEnvCredentials(opts) {
9055
- const tenantId = (0, import_apps5.tenantIdFor)(opts.cfg.app.id, opts.env);
9725
+ const tenantId = (0, import_apps9.tenantIdFor)(opts.cfg.app.id, opts.env);
9056
9726
  const prior = opts.credentials?.envs[opts.env];
9057
9727
  let credentials = opts.credentials;
9058
9728
  let dbKey = opts.cfg.services.includes("db") && !opts.rotateDb ? prior?.dbKey : void 0;
@@ -9108,14 +9778,14 @@ async function mintDbKey(opts, tenantId) {
9108
9778
  appId: tenantId
9109
9779
  })
9110
9780
  });
9111
- if (!created.ok) throw new Error(`db app create (${tenantId}) failed: ${created.status} ${await safeText4(created)}`);
9781
+ if (!created.ok) throw new Error(`db app create (${tenantId}) failed: ${created.status} ${await safeText5(created)}`);
9112
9782
  res = await opts.fetch(`${opts.cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenantId)}/keys`, {
9113
9783
  method: "POST",
9114
9784
  headers,
9115
9785
  body: "{}"
9116
9786
  });
9117
9787
  }
9118
- if (!res.ok) throw new Error(`db key mint (${tenantId}) failed: ${res.status} ${await safeText4(res)}`);
9788
+ if (!res.ok) throw new Error(`db key mint (${tenantId}) failed: ${res.status} ${await safeText5(res)}`);
9119
9789
  const body = await res.json();
9120
9790
  if (!body.key) throw new Error(`db key mint (${tenantId}) returned no key`);
9121
9791
  return body.key;
@@ -9131,58 +9801,11 @@ async function issueO11yToken(opts) {
9131
9801
  `o11y token already exists for env "${opts.env}", but its shown-once value is not in the local credentials file; run "odla-ai provision --rotate-o11y-token --push-secrets" to replace it explicitly`
9132
9802
  );
9133
9803
  }
9134
- if (!res.ok) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) failed: ${res.status} ${await safeText4(res)}`);
9804
+ if (!res.ok) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) failed: ${res.status} ${await safeText5(res)}`);
9135
9805
  const body = await res.json();
9136
9806
  if (!body.token) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) returned no token`);
9137
9807
  return body.token;
9138
9808
  }
9139
- async function safeText4(res) {
9140
- try {
9141
- return redactSecrets((await res.text()).slice(0, 500));
9142
- } catch {
9143
- return "";
9144
- }
9145
- }
9146
-
9147
- // src/provision-helpers.ts
9148
- var import_ai2 = require("@odla-ai/ai");
9149
- var import_apps6 = require("@odla-ai/apps");
9150
- function defaultSecretName(provider) {
9151
- const names = import_ai2.DEFAULT_SECRET_NAMES;
9152
- return names[provider] ?? `${provider}_api_key`;
9153
- }
9154
- async function assertTenantAdminAccess(doFetch, cfg, env, token) {
9155
- const tenantId = (0, import_apps6.tenantIdFor)(cfg.app.id, env);
9156
- const res = await doFetch(`${cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenantId)}/entitlements`, {
9157
- headers: { authorization: `Bearer ${token}` }
9158
- });
9159
- if (res.ok || res.status === 404) return;
9160
- if (res.status === 403) {
9161
- throw new Error(
9162
- `${env}: you are not an owner of "${cfg.app.id}" (tenant ${tenantId}) \u2014 nothing was minted or written; ask an existing owner to run "odla-ai app owners add <your-email>", then re-run provision`
9163
- );
9164
- }
9165
- throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await safeText5(res)}`);
9166
- }
9167
- async function postJson3(doFetch, url, bearer, body) {
9168
- const res = await doFetch(url, {
9169
- method: "POST",
9170
- headers: { authorization: `Bearer ${bearer}`, "content-type": "application/json" },
9171
- body: JSON.stringify(body)
9172
- });
9173
- if (!res.ok) throw new Error(`${new URL(url).pathname} failed: ${res.status} ${await safeText5(res)}`);
9174
- }
9175
- function normalizeClerkConfig(value) {
9176
- if (!value) return null;
9177
- if (typeof value === "string") {
9178
- const publishableKey2 = envValue(value);
9179
- return publishableKey2 ? { publishableKey: publishableKey2 } : null;
9180
- }
9181
- if (typeof value !== "object") return null;
9182
- const cfg = value;
9183
- const publishableKey = envValue(cfg.publishableKey);
9184
- return publishableKey ? { publishableKey, ...cfg.audience ? { audience: cfg.audience } : {}, ...cfg.mode ? { mode: cfg.mode } : {} } : null;
9185
- }
9186
9809
  async function safeText5(res) {
9187
9810
  try {
9188
9811
  return redactSecrets((await res.text()).slice(0, 500));
@@ -9261,7 +9884,7 @@ async function provision(options) {
9261
9884
  }
9262
9885
  const doFetch = options.fetch ?? fetch;
9263
9886
  const token = await getDeveloperToken(cfg, options, doFetch, out);
9264
- const apps = (0, import_apps7.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
9887
+ const apps = (0, import_apps10.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
9265
9888
  const existing = await apps.resolveApp(cfg.app.id);
9266
9889
  if (existing) {
9267
9890
  out.log(`app: ${cfg.app.id} already exists`);
@@ -9272,7 +9895,7 @@ async function provision(options) {
9272
9895
  for (const env of cfg.envs) {
9273
9896
  await assertTenantAdminAccess(doFetch, cfg, env, token);
9274
9897
  }
9275
- const serviceOrder = (0, import_apps7.orderAppServices)(cfg.services);
9898
+ const serviceOrder = (0, import_apps10.orderAppServices)(cfg.services);
9276
9899
  for (const env of cfg.envs) {
9277
9900
  for (const service of serviceOrder) {
9278
9901
  if (service === "ai") {
@@ -9305,7 +9928,7 @@ async function provision(options) {
9305
9928
  }
9306
9929
  }
9307
9930
  for (const env of cfg.envs) {
9308
- const tenantId = (0, import_apps7.tenantIdFor)(cfg.app.id, env);
9931
+ const tenantId = (0, import_apps10.tenantIdFor)(cfg.app.id, env);
9309
9932
  credentials = await provisionEnvCredentials({
9310
9933
  cfg,
9311
9934
  env,
@@ -9338,11 +9961,11 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
9338
9961
  }
9339
9962
  }
9340
9963
  if (schema && dbKey) {
9341
- await postJson3(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(tenantId)}/schema`, dbKey, { schema });
9964
+ await postJson(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(tenantId)}/schema`, dbKey, { schema });
9342
9965
  out.log(`${env}: schema pushed`);
9343
9966
  }
9344
9967
  if (rules) {
9345
- await postJson3(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(tenantId)}/admin/rules`, token, rules);
9968
+ await postJson(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(tenantId)}/admin/rules`, token, rules);
9346
9969
  out.log(`${env}: rules pushed (${Object.keys(rules).length} namespaces)`);
9347
9970
  }
9348
9971
  if (dbKey) {
@@ -9439,6 +10062,7 @@ var COMMAND_SURFACE = {
9439
10062
  calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
9440
10063
  capabilities: {},
9441
10064
  code: { connect: {} },
10065
+ config: { diff: {}, plan: {} },
9442
10066
  context: { show: {}, list: {}, save: {}, remove: {} },
9443
10067
  // `watch`, `read`, `reply`, and `resolve` take a topic id from there on.
9444
10068
  discuss: {
@@ -9456,6 +10080,7 @@ var COMMAND_SURFACE = {
9456
10080
  help: {},
9457
10081
  init: {},
9458
10082
  o11y: { status: {} },
10083
+ platform: { status: {} },
9459
10084
  pm: {
9460
10085
  ...PM_ENTITIES,
9461
10086
  handoff: {}
@@ -9539,7 +10164,7 @@ function recordInvocation(parsed) {
9539
10164
  try {
9540
10165
  const entry = {
9541
10166
  path: invocationPath(parsed.positionals),
9542
- options: Object.entries(parsed.options).map(([name, value]) => value === false ? `no-${name}` : name).sort()
10167
+ options: Object.entries(parsed.options).map(([name, value2]) => value2 === false ? `no-${name}` : name).sort()
9543
10168
  };
9544
10169
  if (!entry.path.length) return;
9545
10170
  (0, import_node_fs15.appendFileSync)(file, `${JSON.stringify(entry)}
@@ -9553,10 +10178,10 @@ var import_node_fs16 = require("fs");
9553
10178
 
9554
10179
  // src/runbook-requires.ts
9555
10180
  var SPEC = /^(@?[\w./-]+?)@(\d+\.\d+\.\d+(?:[\w.-]*)?)$/;
9556
- function parseRequires(value) {
9557
- if (!value) return [];
10181
+ function parseRequires(value2) {
10182
+ if (!value2) return [];
9558
10183
  const out = [];
9559
- for (const token of value.split(/[\s,]+/).filter(Boolean)) {
10184
+ for (const token of value2.split(/[\s,]+/).filter(Boolean)) {
9560
10185
  const match = SPEC.exec(token);
9561
10186
  if (match?.[1] && match[2]) out.push({ name: match[1], min: match[2] });
9562
10187
  }
@@ -9722,7 +10347,7 @@ async function runbookRemove(ctx, slug) {
9722
10347
 
9723
10348
  // src/runbook-import.ts
9724
10349
  var import_node_fs17 = require("fs");
9725
- var import_node_path13 = require("path");
10350
+ var import_node_path14 = require("path");
9726
10351
  function parseRunbook(text, slug) {
9727
10352
  let rest = text;
9728
10353
  const meta = {};
@@ -9732,9 +10357,9 @@ function parseRunbook(text, slug) {
9732
10357
  for (const line of fm[1].split(/\r?\n/)) {
9733
10358
  const pair = /^(\w+)\s*:\s*(.+)$/.exec(line.trim());
9734
10359
  if (!pair) continue;
9735
- const value = pair[2].trim().replace(/^["']|["']$/g, "");
9736
- if (pair[1] === "summary") meta.summary = value;
9737
- if (pair[1] === "tags") meta.tags = value.replace(/^\[|\]$/g, "").trim();
10360
+ const value2 = pair[2].trim().replace(/^["']|["']$/g, "");
10361
+ if (pair[1] === "summary") meta.summary = value2;
10362
+ if (pair[1] === "tags") meta.tags = value2.replace(/^\[|\]$/g, "").trim();
9738
10363
  }
9739
10364
  }
9740
10365
  const lines = rest.split("\n");
@@ -9751,8 +10376,8 @@ function readRunbookDir(dir) {
9751
10376
  const files = (0, import_node_fs17.readdirSync)(dir).filter((f) => f.endsWith(".md")).sort();
9752
10377
  if (!files.length) throw new Error(`no .md files in ${dir}`);
9753
10378
  return files.map((file) => {
9754
- const slug = (0, import_node_path13.basename)(file, ".md");
9755
- const parsed = parseRunbook((0, import_node_fs17.readFileSync)((0, import_node_path13.join)(dir, file), "utf8"), slug);
10379
+ const slug = (0, import_node_path14.basename)(file, ".md");
10380
+ const parsed = parseRunbook((0, import_node_fs17.readFileSync)((0, import_node_path14.join)(dir, file), "utf8"), slug);
9756
10381
  return { file, slug, ...parsed, words: parsed.body.split(/\s+/).filter(Boolean).length };
9757
10382
  });
9758
10383
  }
@@ -9825,7 +10450,7 @@ async function upsert(ctx, r, visibility) {
9825
10450
  // src/runbook-impact.ts
9826
10451
  var import_node_child_process7 = require("child_process");
9827
10452
  var import_node_fs18 = require("fs");
9828
- var import_node_path14 = require("path");
10453
+ var import_node_path15 = require("path");
9829
10454
 
9830
10455
  // src/runbook-impact-scan.ts
9831
10456
  var DECL = /^[+-]\s*export\s+(?:declare\s+)?(?:default\s+)?(?:abstract\s+)?(?:async\s+)?(?:const|let|var|function|class|interface|type|enum)\s+([A-Za-z_$][\w$]*)/;
@@ -9851,7 +10476,7 @@ var NOISE = /* @__PURE__ */ new Set([
9851
10476
  "and",
9852
10477
  "for"
9853
10478
  ]);
9854
- var words = (value) => value.replace(/^@[\w-]+\//, "").split(/[^A-Za-z0-9]+/).filter((w) => w.length > 1 && !NOISE.has(w.toLowerCase()));
10479
+ var words = (value2) => value2.replace(/^@[\w-]+\//, "").split(/[^A-Za-z0-9]+/).filter((w) => w.length > 1 && !NOISE.has(w.toLowerCase()));
9855
10480
  function namedExports(clause) {
9856
10481
  return clause.split(",").map((part) => part.trim().split(/\s+as\s+/)[0]?.trim() ?? "").filter((name) => /^[A-Za-z_$][\w$]*$/.test(name) && name !== "type");
9857
10482
  }
@@ -9994,7 +10619,7 @@ ${body.split("\n").map((line) => `+${line}`).join("\n")}
9994
10619
  }
9995
10620
  function manifestLabeller(root) {
9996
10621
  return (workspace) => {
9997
- const manifest = (0, import_node_path14.join)(root, workspace, "package.json");
10622
+ const manifest = (0, import_node_path15.join)(root, workspace, "package.json");
9998
10623
  if (!(0, import_node_fs18.existsSync)(manifest)) return void 0;
9999
10624
  try {
10000
10625
  const name = JSON.parse((0, import_node_fs18.readFileSync)(manifest, "utf8")).name;
@@ -10064,7 +10689,7 @@ function report3(ctx, impacts) {
10064
10689
  async function runbookImpact(ctx, options, deps = {}) {
10065
10690
  const cwd = deps.cwd ?? process.cwd();
10066
10691
  const runGit = deps.runGit ?? gitRunner(cwd);
10067
- const read3 = deps.readRepoFile ?? ((path) => (0, import_node_fs18.readFileSync)((0, import_node_path14.join)(cwd, path), "utf8"));
10692
+ const read3 = deps.readRepoFile ?? ((path) => (0, import_node_fs18.readFileSync)((0, import_node_path15.join)(cwd, path), "utf8"));
10068
10693
  const surfaces = changedSurfaces(collectDiff(runGit, options.base, read3), manifestLabeller(cwd));
10069
10694
  if (!surfaces.length) {
10070
10695
  return ctx.out.log(
@@ -10199,13 +10824,13 @@ async function runbookComment(ctx, slug, body) {
10199
10824
  var import_node_child_process8 = require("child_process");
10200
10825
  var import_node_fs19 = require("fs");
10201
10826
  var import_node_os5 = require("os");
10202
- var import_node_path15 = require("path");
10827
+ var import_node_path16 = require("path");
10203
10828
  var import_node_process12 = __toESM(require("process"), 1);
10204
10829
  var EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
10205
10830
  function resolveEditor(env = import_node_process12.default.env) {
10206
10831
  for (const name of EDITOR_ENV) {
10207
- const value = env[name];
10208
- if (value && value.trim()) return value.trim();
10832
+ const value2 = env[name];
10833
+ if (value2 && value2.trim()) return value2.trim();
10209
10834
  }
10210
10835
  return null;
10211
10836
  }
@@ -10225,8 +10850,8 @@ function editText(initial, slug, deps = {}) {
10225
10850
  );
10226
10851
  if (!interactive())
10227
10852
  throw new Error(`cannot open an editor without a terminal \u2014 pass --file <path> or --body "\u2026" instead`);
10228
- const dir = (0, import_node_fs19.mkdtempSync)((0, import_node_path15.join)((0, import_node_os5.tmpdir)(), "odla-runbook-"));
10229
- const file = (0, import_node_path15.join)(dir, `${slug}.md`);
10853
+ const dir = (0, import_node_fs19.mkdtempSync)((0, import_node_path16.join)((0, import_node_os5.tmpdir)(), "odla-runbook-"));
10854
+ const file = (0, import_node_path16.join)(dir, `${slug}.md`);
10230
10855
  try {
10231
10856
  (0, import_node_fs19.writeFileSync)(file, initial, { mode: 384 });
10232
10857
  const code = defaultRunOrInjected(deps)(editor, file);
@@ -10334,12 +10959,12 @@ var ALLOWED2 = [
10334
10959
  "platform",
10335
10960
  "context"
10336
10961
  ];
10337
- function requireSlug(slug, action) {
10338
- if (!slug) throw new Error(`"runbook ${action}" needs a slug, e.g. "odla-ai runbook ${action} release"`);
10962
+ function requireSlug(slug, action2) {
10963
+ if (!slug) throw new Error(`"runbook ${action2}" needs a slug, e.g. "odla-ai runbook ${action2} release"`);
10339
10964
  return slug;
10340
10965
  }
10341
10966
  var WRITES = /* @__PURE__ */ new Set(["new", "edit", "publish", "archive", "visibility", "revert", "rm", "import"]);
10342
- async function buildContext3(parsed, deps, action) {
10967
+ async function buildContext3(parsed, deps, action2) {
10343
10968
  const appIdOption = stringOpt(parsed.options.app);
10344
10969
  const context = await resolveOperatorContext(parsed, {
10345
10970
  allowMissingConfig: true
@@ -10349,7 +10974,7 @@ async function buildContext3(parsed, deps, action) {
10349
10974
  const out = deps.stdout ?? console;
10350
10975
  const appId = appIdOption ?? (context.app.source === "environment" || context.app.source === "profile" ? context.app.value : null) ?? PLATFORM_SCOPE;
10351
10976
  const dryRun = parsed.options["dry-run"] === true;
10352
- if (action === "import" && dryRun) {
10977
+ if (action2 === "import" && dryRun) {
10353
10978
  return {
10354
10979
  platformUrl: cfg.platformUrl,
10355
10980
  token: "",
@@ -10359,12 +10984,12 @@ async function buildContext3(parsed, deps, action) {
10359
10984
  appId
10360
10985
  };
10361
10986
  }
10362
- const needsCapability = WRITES.has(action) && !dryRun && appId === PLATFORM_SCOPE && !stringOpt(parsed.options.token);
10987
+ const needsCapability = WRITES.has(action2) && !dryRun && appId === PLATFORM_SCOPE && !stringOpt(parsed.options.token);
10363
10988
  const token = needsCapability ? await getScopedPlatformToken({
10364
10989
  platform: cfg.platformUrl,
10365
10990
  scope: "platform:runbook:write",
10366
10991
  email: stringOpt(parsed.options.email),
10367
- label: `odla CLI (runbook ${action})`,
10992
+ label: `odla CLI (runbook ${action2})`,
10368
10993
  fetch: doFetch,
10369
10994
  stdout: out,
10370
10995
  openApprovalUrl: deps.openUrl,
@@ -10399,11 +11024,11 @@ async function buildContext3(parsed, deps, action) {
10399
11024
  };
10400
11025
  }
10401
11026
  async function runbookCommand(parsed, deps = {}) {
10402
- const action = parsed.positionals[1] ?? "list";
10403
- assertArgs(parsed, ALLOWED2, action === "ask" || action === "search" ? 64 : 4);
10404
- const ctx = await buildContext3(parsed, deps, action);
11027
+ const action2 = parsed.positionals[1] ?? "list";
11028
+ assertArgs(parsed, ALLOWED2, action2 === "ask" || action2 === "search" ? 64 : 4);
11029
+ const ctx = await buildContext3(parsed, deps, action2);
10405
11030
  const slug = parsed.positionals[2];
10406
- switch (action) {
11031
+ switch (action2) {
10407
11032
  case "list":
10408
11033
  return runbookList(ctx, parsed.options.all === true, stringOpt(parsed.options.q));
10409
11034
  case "ask": {
@@ -10470,9 +11095,9 @@ async function runbookCommand(parsed, deps = {}) {
10470
11095
  });
10471
11096
  }
10472
11097
  case "visibility": {
10473
- const value = parsed.positionals[3] ?? stringOpt(parsed.options.visibility);
10474
- if (!value) throw new Error('"runbook visibility" needs operator|admin, e.g. "runbook visibility release operator"');
10475
- return runbookVisibility(ctx, requireSlug(slug, "visibility"), value);
11098
+ const value2 = parsed.positionals[3] ?? stringOpt(parsed.options.visibility);
11099
+ if (!value2) throw new Error('"runbook visibility" needs operator|admin, e.g. "runbook visibility release operator"');
11100
+ return runbookVisibility(ctx, requireSlug(slug, "visibility"), value2);
10476
11101
  }
10477
11102
  case "publish":
10478
11103
  return runbookStatus(ctx, requireSlug(slug, "publish"), "published");
@@ -10488,7 +11113,7 @@ async function runbookCommand(parsed, deps = {}) {
10488
11113
  case "rm":
10489
11114
  return runbookRemove(ctx, requireSlug(slug, "rm"));
10490
11115
  default:
10491
- throw new Error(`unknown runbook action "${action}". Try ${acceptedAfter(["runbook"]).join(", ")}.`);
11116
+ throw new Error(`unknown runbook action "${action2}". Try ${acceptedAfter(["runbook"]).join(", ")}.`);
10492
11117
  }
10493
11118
  }
10494
11119
 
@@ -10528,13 +11153,13 @@ async function interactiveConfirmation(message2, dependencies) {
10528
11153
  }
10529
11154
  }
10530
11155
  function requiredSecurityPositional(parsed, index, label) {
10531
- const value = parsed.positionals[index];
10532
- if (!value) throw new Error(`${label} is required`);
10533
- return value;
11156
+ const value2 = parsed.positionals[index];
11157
+ if (!value2) throw new Error(`${label} is required`);
11158
+ return value2;
10534
11159
  }
10535
- function securityProfile(value) {
10536
- if (value === void 0) return void 0;
10537
- if (value === "odla" || value === "cloudflare-app" || value === "generic") return value;
11160
+ function securityProfile(value2) {
11161
+ if (value2 === void 0) return void 0;
11162
+ if (value2 === "odla" || value2 === "cloudflare-app" || value2 === "generic") return value2;
10538
11163
  throw new Error("--profile must be odla, cloudflare-app, or generic");
10539
11164
  }
10540
11165
 
@@ -10635,9 +11260,9 @@ function routeLabel(route2) {
10635
11260
  return `${route2.provider}/${route2.model}${route2.policyVersion ? ` policy v${route2.policyVersion}` : ""}`;
10636
11261
  }
10637
11262
  var HOSTED_SEVERITIES = ["informational", "low", "medium", "high", "critical"];
10638
- function hostedSeverity(value, flag) {
10639
- if (HOSTED_SEVERITIES.includes(value)) {
10640
- return value;
11263
+ function hostedSeverity(value2, flag) {
11264
+ if (HOSTED_SEVERITIES.includes(value2)) {
11265
+ return value2;
10641
11266
  }
10642
11267
  throw new Error(`${flag} must be informational, low, medium, high, or critical`);
10643
11268
  }
@@ -10646,7 +11271,7 @@ function hostedSeverity(value, flag) {
10646
11271
  var import_security2 = require("@odla-ai/security");
10647
11272
 
10648
11273
  // src/security.ts
10649
- var import_node_path16 = require("path");
11274
+ var import_node_path17 = require("path");
10650
11275
  var import_security = require("@odla-ai/security");
10651
11276
  var import_node3 = require("@odla-ai/security/node");
10652
11277
  async function runHostedSecurity(options) {
@@ -10658,9 +11283,9 @@ async function runHostedSecurity(options) {
10658
11283
  const appId = selfAudit ? "odla-ai" : cfg.app.id;
10659
11284
  const env = selfAudit ? "prod" : selectEnv(options.env, cfg.envs, cfg.configPath, cfg.rootDir);
10660
11285
  const platform = options.platform ?? cfg?.platformUrl ?? "https://odla.ai";
10661
- const target = (0, import_node_path16.resolve)(options.target ?? cfg?.rootDir ?? ".");
10662
- const output = (0, import_node_path16.resolve)(options.out ?? (0, import_node_path16.resolve)(target, ".odla/security/hosted"));
10663
- const outputRelative = (0, import_node_path16.relative)(target, output).split(import_node_path16.sep).join("/");
11286
+ const target = (0, import_node_path17.resolve)(options.target ?? cfg?.rootDir ?? ".");
11287
+ const output = (0, import_node_path17.resolve)(options.out ?? (0, import_node_path17.resolve)(target, ".odla/security/hosted"));
11288
+ const outputRelative = (0, import_node_path17.relative)(target, output).split(import_node_path17.sep).join("/");
10664
11289
  if (!outputRelative) throw new Error("Hosted security output cannot be the repository root");
10665
11290
  const profile = profileFor(options.profile ?? "odla", options.maxHuntTasks ?? 12);
10666
11291
  const tokenRequest = {
@@ -10672,7 +11297,7 @@ async function runHostedSecurity(options) {
10672
11297
  };
10673
11298
  const token = await injectedToken(options, tokenRequest);
10674
11299
  const snapshot = await (0, import_node3.snapshotDirectory)(target, {
10675
- exclude: !outputRelative.startsWith("../") && !(0, import_node_path16.isAbsolute)(outputRelative) ? [outputRelative] : []
11300
+ exclude: !outputRelative.startsWith("../") && !(0, import_node_path17.isAbsolute)(outputRelative) ? [outputRelative] : []
10676
11301
  });
10677
11302
  const hosted = await (0, import_security.createPlatformSecurityReasoners)({
10678
11303
  platform,
@@ -10690,7 +11315,7 @@ async function runHostedSecurity(options) {
10690
11315
  });
10691
11316
  const harness = (0, import_security.createSecurityHarness)({
10692
11317
  profile,
10693
- store: new import_node3.FileRunStore((0, import_node_path16.resolve)(output, "state")),
11318
+ store: new import_node3.FileRunStore((0, import_node_path17.resolve)(output, "state")),
10694
11319
  discoveryReasoner: hosted.discoveryReasoner,
10695
11320
  validationReasoner: hosted.validationReasoner,
10696
11321
  policy: {
@@ -10714,19 +11339,19 @@ async function runHostedSecurity(options) {
10714
11339
  function selectEnv(requested, declared, configPath, rootDir) {
10715
11340
  const env = requested ?? (declared.includes("dev") ? "dev" : declared[0]);
10716
11341
  if (!env || !declared.includes(env)) {
10717
- const shown = (0, import_node_path16.relative)(rootDir, configPath) || configPath;
11342
+ const shown = (0, import_node_path17.relative)(rootDir, configPath) || configPath;
10718
11343
  throw new Error(`env "${env ?? ""}" is not declared in ${shown}`);
10719
11344
  }
10720
11345
  return env;
10721
11346
  }
10722
11347
  async function injectedToken(options, request2) {
10723
- const value = options.token ?? await options.getToken?.(Object.freeze({ ...request2 }));
10724
- if (typeof value !== "string" || value.length < 8 || value.length > 8192 || /\s|[\u0000-\u001f\u007f]/.test(value)) {
11348
+ const value2 = options.token ?? await options.getToken?.(Object.freeze({ ...request2 }));
11349
+ if (typeof value2 !== "string" || value2.length < 8 || value2.length > 8192 || /\s|[\u0000-\u001f\u007f]/.test(value2)) {
10725
11350
  throw new Error(
10726
11351
  request2.selfAudit ? "Self-audit requires an injected, scoped platform security token" : "Hosted security requires an injected app developer token or getToken callback"
10727
11352
  );
10728
11353
  }
10729
- return value;
11354
+ return value2;
10730
11355
  }
10731
11356
  function profileFor(name, maxHuntTasks) {
10732
11357
  const profile = name === "odla" ? (0, import_security.odlaProfile)() : name === "cloudflare-app" ? (0, import_security.cloudflareAppProfile)() : name === "generic" ? (0, import_security.genericProfile)() : void 0;
@@ -10743,7 +11368,7 @@ function printSummary(out, appId, env, run, report4, output) {
10743
11368
  out.log(` coverage: ${report4.coverageStatus} ${complete}/${report4.coverage.length} blocked=${report4.metrics.blockedCells} shallow=${report4.metrics.shallowCells} unscheduled=${report4.metrics.unscheduledCells} budget_exhausted=${report4.metrics.budgetExhaustedCells}`);
10744
11369
  if (report4.callBudget) out.log(` calls: discovery=${formatBudget(report4.callBudget.discovery)} validation=${formatBudget(report4.callBudget.validation)}`);
10745
11370
  out.log(` findings: confirmed=${report4.metrics.confirmed} needs_reproduction=${report4.metrics.needsReproduction} candidates=${report4.metrics.candidates}`);
10746
- out.log(` report: ${(0, import_node_path16.resolve)(output, "REPORT.md")}`);
11371
+ out.log(` report: ${(0, import_node_path17.resolve)(output, "REPORT.md")}`);
10747
11372
  }
10748
11373
  function formatBudget(usage) {
10749
11374
  return usage ? `${usage.usedCalls}/${usage.maxCalls} skipped=${usage.skippedCalls}` : "caller-managed";
@@ -10772,8 +11397,8 @@ async function getHostedSecurityIntent(options) {
10772
11397
  }
10773
11398
  return response2;
10774
11399
  }
10775
- function isValidIntent(value, expected) {
10776
- return !!value && value.executionContract === "odla.hosted-security-execution-consent.v1" && /^sha256:[a-f0-9]{64}$/.test(value.executionDigest) && /^sha256:[a-f0-9]{64}$/.test(value.planDigest) && value.appId === expected.appId && value.env === expected.env && value.sourceId === expected.sourceId && typeof value.repository === "string" && value.repository.length > 2 && value.repository.length <= 201 && typeof value.defaultBranch === "string" && value.defaultBranch.length > 0 && value.defaultBranch.length <= 255 && typeof value.requestedRef === "string" && value.requestedRef.length > 0 && value.requestedRef.length <= 255 && !/[\u0000-\u001f\u007f]/.test(value.requestedRef) && ["odla", "cloudflare-app", "generic"].includes(value.profile) && value.sourceDisclosure === "redacted";
11400
+ function isValidIntent(value2, expected) {
11401
+ return !!value2 && value2.executionContract === "odla.hosted-security-execution-consent.v1" && /^sha256:[a-f0-9]{64}$/.test(value2.executionDigest) && /^sha256:[a-f0-9]{64}$/.test(value2.planDigest) && value2.appId === expected.appId && value2.env === expected.env && value2.sourceId === expected.sourceId && typeof value2.repository === "string" && value2.repository.length > 2 && value2.repository.length <= 201 && typeof value2.defaultBranch === "string" && value2.defaultBranch.length > 0 && value2.defaultBranch.length <= 255 && typeof value2.requestedRef === "string" && value2.requestedRef.length > 0 && value2.requestedRef.length <= 255 && !/[\u0000-\u001f\u007f]/.test(value2.requestedRef) && ["odla", "cloudflare-app", "generic"].includes(value2.profile) && value2.sourceDisclosure === "redacted";
10777
11402
  }
10778
11403
 
10779
11404
  // src/security-hosted-jobs.ts
@@ -10888,17 +11513,17 @@ function hostedJobPath(appIdInput, jobIdInput) {
10888
11513
  const jobId = hostedIdentifier(jobIdInput, "jobId");
10889
11514
  return `/registry/apps/${encodeURIComponent(appId)}/security/jobs/${encodeURIComponent(jobId)}`;
10890
11515
  }
10891
- function securityPlanDigest(value) {
10892
- if (!/^sha256:[a-f0-9]{64}$/.test(value)) {
11516
+ function securityPlanDigest(value2) {
11517
+ if (!/^sha256:[a-f0-9]{64}$/.test(value2)) {
10893
11518
  throw new Error("expected plan digest must be a sha256 digest from security plan");
10894
11519
  }
10895
- return value;
11520
+ return value2;
10896
11521
  }
10897
- function securityExecutionDigest(value) {
10898
- if (!/^sha256:[a-f0-9]{64}$/.test(value)) {
11522
+ function securityExecutionDigest(value2) {
11523
+ if (!/^sha256:[a-f0-9]{64}$/.test(value2)) {
10899
11524
  throw new Error("expected execution digest must be a sha256 digest from security intent");
10900
11525
  }
10901
- return value;
11526
+ return value2;
10902
11527
  }
10903
11528
 
10904
11529
  // src/security-run-command.ts
@@ -10962,7 +11587,7 @@ async function runSourceSecurityCommand(parsed, dependencies, sourceId) {
10962
11587
  ...context,
10963
11588
  jobId: job.jobId,
10964
11589
  wait: dependencies.pollWait,
10965
- onUpdate: parsed.options.json === true ? void 0 : (value) => printHostedJob(context.stdout, value, context.platform, context.appId)
11590
+ onUpdate: parsed.options.json === true ? void 0 : (value2) => printHostedJob(context.stdout, value2, context.platform, context.appId)
10966
11591
  }) : job;
10967
11592
  if (!follow) {
10968
11593
  if (parsed.options.json === true) {
@@ -11062,9 +11687,9 @@ function enforceLocalGate(report4, parsed) {
11062
11687
  throw new Error(`hosted security gate failed: ${confirmed.length} confirmed >= ${failOn}; ${leads.length} leads >= ${failOnCandidates ?? "disabled"}${incomplete ? "; coverage incomplete" : ""}`);
11063
11688
  }
11064
11689
  }
11065
- function severityOpt(value, flag) {
11066
- if (["informational", "low", "medium", "high", "critical"].includes(value)) {
11067
- return value;
11690
+ function severityOpt(value2, flag) {
11691
+ if (["informational", "low", "medium", "high", "critical"].includes(value2)) {
11692
+ return value2;
11068
11693
  }
11069
11694
  throw new Error(`${flag} must be informational, low, medium, high, or critical`);
11070
11695
  }
@@ -11109,8 +11734,8 @@ async function securityCommand(parsed, dependencies) {
11109
11734
  else await runLocalSecurityCommand(parsed, dependencies);
11110
11735
  }
11111
11736
  async function githubSecurityCommand(parsed, dependencies) {
11112
- const action = parsed.positionals[2];
11113
- if (action === "disconnect") {
11737
+ const action2 = parsed.positionals[2];
11738
+ if (action2 === "disconnect") {
11114
11739
  assertArgs(parsed, ["config", "env", "platform", "source", "email", "open", "yes"], 3);
11115
11740
  const context2 = await hostedSecurityContext(parsed, dependencies);
11116
11741
  const sourceId = requiredString(parsed.options.source, "--source");
@@ -11125,7 +11750,7 @@ async function githubSecurityCommand(parsed, dependencies) {
11125
11750
  context2.stdout.log(`github: disconnected ${sourceId} from ${context2.appId}/${context2.env}`);
11126
11751
  return;
11127
11752
  }
11128
- if (action !== "connect") {
11753
+ if (action2 !== "connect") {
11129
11754
  throw new Error('unknown security github command. Try "odla-ai security github connect".');
11130
11755
  }
11131
11756
  assertArgs(parsed, ["config", "env", "platform", "repo", "email", "open"], 3);
@@ -11172,7 +11797,7 @@ async function securityStatus(parsed, dependencies) {
11172
11797
  ...context,
11173
11798
  jobId,
11174
11799
  wait: dependencies.pollWait,
11175
- onUpdate: parsed.options.json === true ? void 0 : (value) => printHostedJob(context.stdout, value, context.platform, context.appId)
11800
+ onUpdate: parsed.options.json === true ? void 0 : (value2) => printHostedJob(context.stdout, value2, context.platform, context.appId)
11176
11801
  }) : await getHostedSecurityJob({ ...context, jobId });
11177
11802
  if (parsed.options.json === true) context.stdout.log(JSON.stringify(job, null, 2));
11178
11803
  else if (parsed.options.follow !== true) {
@@ -11256,6 +11881,10 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
11256
11881
  await o11yCommand(parsed, runtime);
11257
11882
  return;
11258
11883
  }
11884
+ if (command === "platform") {
11885
+ await platformCommand(parsed, runtime);
11886
+ return;
11887
+ }
11259
11888
  if (command === "provision") {
11260
11889
  await provisionCommand(parsed, runtime);
11261
11890
  return;