@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/index.cjs CHANGED
@@ -47,8 +47,11 @@ __export(index_exports, {
47
47
  calendarServiceConfig: () => calendarServiceConfig,
48
48
  calendarStatus: () => calendarStatus,
49
49
  codeConnect: () => codeConnect,
50
+ configDiff: () => configDiff,
51
+ configPlan: () => configPlan,
50
52
  connectGitHubSecuritySource: () => connectGitHubSecuritySource,
51
53
  describeProblem: () => describeProblem,
54
+ desiredRegistryState: () => desiredRegistryState,
52
55
  disconnectGitHubSecuritySource: () => disconnectGitHubSecuritySource,
53
56
  doctor: () => doctor,
54
57
  exitCodeFor: () => exitCodeFor,
@@ -68,6 +71,7 @@ __export(index_exports, {
68
71
  prepareCodeImages: () => prepareCodeImages,
69
72
  printCapabilities: () => printCapabilities,
70
73
  provision: () => provision,
74
+ reconcileConfig: () => reconcileConfig,
71
75
  redactSecrets: () => redactSecrets,
72
76
  repositoryFromGitRemote: () => repositoryFromGitRemote,
73
77
  runCli: () => runCli,
@@ -103,7 +107,9 @@ function approvalLines(prompt) {
103
107
  lines.push("");
104
108
  lines.push(` ${prompt.approvalUrl}`);
105
109
  lines.push("");
106
- lines.push(" A signed-in odla admin opens that URL, checks the code matches, and approves.");
110
+ lines.push(
111
+ ` ${prompt.approver ?? "The matching signed-in odla account"} opens that URL, checks the code matches, and approves.`
112
+ );
107
113
  lines.push(" Opening the link without approving does nothing; this command waits until they do.");
108
114
  if (prompt.browserAttempted) {
109
115
  lines.push(" A browser launch was attempted, but it is best-effort and may have shown no tab.");
@@ -142,22 +148,22 @@ function readJsonFile(path) {
142
148
  return null;
143
149
  }
144
150
  }
145
- function writePrivateJson(path, value) {
146
- writePrivateText(path, `${JSON.stringify(value, null, 2)}
151
+ function writePrivateJson(path, value2) {
152
+ writePrivateText(path, `${JSON.stringify(value2, null, 2)}
147
153
  `);
148
154
  }
149
155
  function readCredentials(path) {
150
156
  if (!(0, import_node_fs.existsSync)(path)) return null;
151
- let value;
157
+ let value2;
152
158
  try {
153
- value = JSON.parse((0, import_node_fs.readFileSync)(path, "utf8"));
159
+ value2 = JSON.parse((0, import_node_fs.readFileSync)(path, "utf8"));
154
160
  } catch {
155
161
  throw new Error(`credentials file ${path} is not valid JSON; fix or remove it before provisioning`);
156
162
  }
157
- if (!value || typeof value !== "object" || typeof value.appId !== "string" || !value.envs || typeof value.envs !== "object") {
163
+ if (!value2 || typeof value2 !== "object" || typeof value2.appId !== "string" || !value2.envs || typeof value2.envs !== "object") {
158
164
  throw new Error(`credentials file ${path} has an invalid shape; fix or remove it before provisioning`);
159
165
  }
160
- return value;
166
+ return value2;
161
167
  }
162
168
  function mergeCredential(current, update) {
163
169
  const next = current ?? {
@@ -452,8 +458,8 @@ function stillPending(pending, email) {
452
458
  { retryable: true }
453
459
  );
454
460
  }
455
- function handshakeEmail(value, cached) {
456
- const email = (value ?? import_node_process3.default.env.ODLA_USER_EMAIL ?? cached ?? "").trim().toLowerCase();
461
+ function handshakeEmail(value2, cached) {
462
+ const email = (value2 ?? import_node_process3.default.env.ODLA_USER_EMAIL ?? cached ?? "").trim().toLowerCase();
457
463
  if (email.length > 254 || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
458
464
  throw new Error("a fresh odla handshake requires --email <account> or ODLA_USER_EMAIL");
459
465
  }
@@ -477,10 +483,10 @@ function handshakeUrl(platformUrl, userCode) {
477
483
  url.searchParams.set("code", userCode);
478
484
  return url.toString();
479
485
  }
480
- function platformAudience(value) {
486
+ function platformAudience(value2) {
481
487
  let url;
482
488
  try {
483
- url = new URL(value);
489
+ url = new URL(value2);
484
490
  } catch {
485
491
  throw new Error("platform must be an absolute URL");
486
492
  }
@@ -499,22 +505,22 @@ var import_node_process4 = __toESM(require("process"), 1);
499
505
  var MAX_BYTES = 64 * 1024;
500
506
  async function secretInputValue(options, kind = "credential") {
501
507
  if (options.fromEnv && options.stdin) throw new Error("choose exactly one of --from-env or --stdin");
502
- let value;
503
- if (options.fromEnv) value = import_node_process4.default.env[options.fromEnv];
504
- else if (options.stdin) value = await (options.readStdin ?? (() => readSecretStream(kind)))();
508
+ let value2;
509
+ if (options.fromEnv) value2 = import_node_process4.default.env[options.fromEnv];
510
+ else if (options.stdin) value2 = await (options.readStdin ?? (() => readSecretStream(kind)))();
505
511
  else throw new Error(`${kind} input required: use --from-env <NAME> or --stdin; values are never accepted as arguments`);
506
- value = value?.replace(/[\r\n]+$/, "");
507
- if (!value) throw new Error(options.fromEnv ? `environment variable ${options.fromEnv} is empty or unset` : `stdin ${kind} is empty`);
508
- if (new TextEncoder().encode(value).byteLength > MAX_BYTES) throw new Error(`${kind} exceeds 64 KiB`);
509
- return value;
512
+ value2 = value2?.replace(/[\r\n]+$/, "");
513
+ if (!value2) throw new Error(options.fromEnv ? `environment variable ${options.fromEnv} is empty or unset` : `stdin ${kind} is empty`);
514
+ if (new TextEncoder().encode(value2).byteLength > MAX_BYTES) throw new Error(`${kind} exceeds 64 KiB`);
515
+ return value2;
510
516
  }
511
517
  async function readSecretStream(kind, stream = import_node_process4.default.stdin) {
512
- let value = "";
518
+ let value2 = "";
513
519
  for await (const chunk of stream) {
514
- value += String(chunk);
515
- if (value.length > MAX_BYTES) throw new Error(`${kind} exceeds 64 KiB`);
520
+ value2 += String(chunk);
521
+ if (value2.length > MAX_BYTES) throw new Error(`${kind} exceeds 64 KiB`);
516
522
  }
517
- return value;
523
+ return value2;
518
524
  }
519
525
 
520
526
  // src/admin-ai-auth.ts
@@ -549,6 +555,8 @@ function audienceBoundEnvToken(token, platform) {
549
555
  return token;
550
556
  }
551
557
  var SCOPE_PURPOSE = {
558
+ "platform:status:read": "read the platform fleet health and deployment snapshot",
559
+ "app:config:read": "compare checked-in intent with an exact-id app Registry configuration",
552
560
  "platform:runbook:write": "add or edit odla's operational runbooks",
553
561
  "platform:ai:policy:write": "change System AI model routing",
554
562
  "platform:ai:policy:read": "read System AI model routing",
@@ -585,6 +593,7 @@ async function scopedToken(platform, scope, options, doFetch, out) {
585
593
  approvalUrl,
586
594
  minutesLeft: Math.floor(expiresIn / 60),
587
595
  purpose: SCOPE_PURPOSE[scope] ?? `use ${scope}`,
596
+ approver: scope.startsWith("app:") ? "A signed-in app owner" : "A signed-in odla platform admin",
588
597
  browserAttempted: browser.open,
589
598
  browserSkipped: browser.reason
590
599
  });
@@ -652,13 +661,13 @@ function apiError(status, body) {
652
661
  const message2 = error && typeof error.message === "string" ? error.message : isRecord(body) && typeof body.message === "string" ? body.message : "request failed";
653
662
  return `read System AI admin changes failed (${status}): ${message2}`;
654
663
  }
655
- function timestamp(value) {
656
- if (typeof value !== "number" || !Number.isFinite(value)) return String(value ?? "");
657
- const date = new Date(value);
664
+ function timestamp(value2) {
665
+ if (typeof value2 !== "number" || !Number.isFinite(value2)) return String(value2 ?? "");
666
+ const date = new Date(value2);
658
667
  return Number.isFinite(date.valueOf()) ? date.toISOString() : "";
659
668
  }
660
- function isRecord(value) {
661
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
669
+ function isRecord(value2) {
670
+ return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
662
671
  }
663
672
 
664
673
  // src/admin-ai-policy.ts
@@ -668,11 +677,11 @@ var SYSTEM_AI_PURPOSES = [
668
677
  "security.validation",
669
678
  "runbook.answer"
670
679
  ];
671
- function requireSystemAiPurpose(value) {
672
- if (!SYSTEM_AI_PURPOSES.includes(value)) {
680
+ function requireSystemAiPurpose(value2) {
681
+ if (!SYSTEM_AI_PURPOSES.includes(value2)) {
673
682
  throw new Error(`purpose must be one of: ${SYSTEM_AI_PURPOSES.join(", ")}`);
674
683
  }
675
- return value;
684
+ return value2;
676
685
  }
677
686
 
678
687
  // src/admin-ai-usage.ts
@@ -694,11 +703,11 @@ async function readAdminAiUsage(request2) {
694
703
  if (request2.json) request2.stdout.log(JSON.stringify(body, null, 2));
695
704
  else printUsage(body, request2.stdout);
696
705
  }
697
- function usageLimit(value) {
698
- if (!Number.isSafeInteger(value) || value < 1 || value > 500) {
706
+ function usageLimit(value2) {
707
+ if (!Number.isSafeInteger(value2) || value2 < 1 || value2 > 500) {
699
708
  throw new Error("usage limit must be an integer from 1 to 500");
700
709
  }
701
- return value;
710
+ return value2;
702
711
  }
703
712
  function printUsage(body, out) {
704
713
  const aggregates = isRecord2(body) && isRecord2(body.aggregates) && Array.isArray(body.aggregates.byStatus) ? body.aggregates.byStatus.filter(isRecord2) : [];
@@ -734,15 +743,15 @@ when app/env run actor purpose/role route / policy tokens cost status`);
734
743
  ].join(" "));
735
744
  }
736
745
  }
737
- function numeric(value) {
738
- return typeof value === "number" && Number.isFinite(value) ? value : 0;
746
+ function numeric(value2) {
747
+ return typeof value2 === "number" && Number.isFinite(value2) ? value2 : 0;
739
748
  }
740
- function formatMicrousd(value) {
741
- return typeof value === "number" && Number.isFinite(value) ? `$${(value / 1e6).toFixed(6)}` : "unpriced";
749
+ function formatMicrousd(value2) {
750
+ return typeof value2 === "number" && Number.isFinite(value2) ? `$${(value2 / 1e6).toFixed(6)}` : "unpriced";
742
751
  }
743
- function timestamp2(value) {
744
- if (typeof value !== "number" || !Number.isFinite(value)) return String(value ?? "");
745
- const date = new Date(value);
752
+ function timestamp2(value2) {
753
+ if (typeof value2 !== "number" || !Number.isFinite(value2)) return String(value2 ?? "");
754
+ const date = new Date(value2);
746
755
  return Number.isFinite(date.valueOf()) ? date.toISOString() : "";
747
756
  }
748
757
  async function responseBody2(res) {
@@ -754,13 +763,13 @@ async function responseBody2(res) {
754
763
  return { message: text.slice(0, 300) };
755
764
  }
756
765
  }
757
- function apiError2(action, status, body) {
766
+ function apiError2(action2, status, body) {
758
767
  const error = isRecord2(body) && isRecord2(body.error) ? body.error : void 0;
759
768
  const message2 = error && typeof error.message === "string" ? error.message : isRecord2(body) && typeof body.message === "string" ? body.message : "request failed";
760
- return `${action} failed (${status}): ${message2}`;
769
+ return `${action2} failed (${status}): ${message2}`;
761
770
  }
762
- function isRecord2(value) {
763
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
771
+ function isRecord2(value2) {
772
+ return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
764
773
  }
765
774
 
766
775
  // src/admin-ai.ts
@@ -805,11 +814,11 @@ async function adminAi(options) {
805
814
  }
806
815
  if (options.action === "credential-set") {
807
816
  const provider = requireProvider(options.credentialProvider);
808
- const value = await secretInputValue(options);
817
+ const value2 = await secretInputValue(options);
809
818
  const res2 = await doFetch(`${platform}/registry/platform/ai-credentials/${encodeURIComponent(provider)}`, {
810
819
  method: "PUT",
811
820
  headers,
812
- body: JSON.stringify({ value })
821
+ body: JSON.stringify({ value: value2 })
813
822
  });
814
823
  const body2 = await responseBody3(res2);
815
824
  if (!res2.ok) throw new Error(apiError3(`store ${provider} platform credential`, res2.status, body2));
@@ -909,11 +918,11 @@ async function adminAi(options) {
909
918
  const policy = isRecord3(response2) && isRecord3(response2.policy) ? response2.policy : isRecord3(response2) ? response2 : {};
910
919
  out.log(options.json ? JSON.stringify({ policy }, null, 2) : `updated ${purpose}: ${String(policy.provider ?? "unchanged")}/${String(policy.model ?? "unchanged")}`);
911
920
  }
912
- function requireProvider(value) {
913
- if (value !== "anthropic" && value !== "openai" && value !== "google") {
921
+ function requireProvider(value2) {
922
+ if (value2 !== "anthropic" && value2 !== "openai" && value2 !== "google") {
914
923
  throw new Error("provider must be one of: anthropic, openai, google");
915
924
  }
916
- return value;
925
+ return value2;
917
926
  }
918
927
  function policyArray(body) {
919
928
  if (isRecord3(body) && Array.isArray(body.policies)) return body.policies.filter(isRecord3);
@@ -924,7 +933,7 @@ function catalogModels(body) {
924
933
  if (!isRecord3(body) || !isRecord3(body.catalog) || !Array.isArray(body.catalog.models)) {
925
934
  throw new Error("platform returned an invalid System AI model catalog");
926
935
  }
927
- return body.catalog.models.filter((value) => isRecord3(value) && typeof value.id === "string" && typeof value.provider === "string");
936
+ return body.catalog.models.filter((value2) => isRecord3(value2) && typeof value2.id === "string" && typeof value2.provider === "string");
928
937
  }
929
938
  async function responseBody3(res) {
930
939
  const text = await res.text();
@@ -935,13 +944,13 @@ async function responseBody3(res) {
935
944
  return { message: text.slice(0, 300) };
936
945
  }
937
946
  }
938
- function apiError3(action, status, body) {
947
+ function apiError3(action2, status, body) {
939
948
  const error = isRecord3(body) && isRecord3(body.error) ? body.error : void 0;
940
949
  const message2 = error && typeof error.message === "string" ? error.message : isRecord3(body) && typeof body.message === "string" ? body.message : "request failed";
941
- return `${action} failed (${status}): ${message2}`;
950
+ return `${action2} failed (${status}): ${message2}`;
942
951
  }
943
- function isRecord3(value) {
944
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
952
+ function isRecord3(value2) {
953
+ return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
945
954
  }
946
955
 
947
956
  // src/argv.ts
@@ -965,10 +974,10 @@ function parseArgv(argv) {
965
974
  addOption(options, rawName, arg.slice(eq + 1));
966
975
  continue;
967
976
  }
968
- const value = argv[i + 1];
969
- if (value !== void 0 && !value.startsWith("--")) {
977
+ const value2 = argv[i + 1];
978
+ if (value2 !== void 0 && !value2.startsWith("--")) {
970
979
  i++;
971
- addOption(options, rawName, value);
980
+ addOption(options, rawName, value2);
972
981
  } else {
973
982
  addOption(options, rawName, true);
974
983
  }
@@ -984,39 +993,39 @@ function assertArgs(parsed, allowedOptions2, maxPositionals) {
984
993
  throw new Error(`unexpected argument "${parsed.positionals[maxPositionals]}"; run "odla-ai help"`);
985
994
  }
986
995
  }
987
- function requiredString(value, name) {
988
- const result = stringOpt(value);
996
+ function requiredString(value2, name) {
997
+ const result = stringOpt(value2);
989
998
  if (!result) throw new Error(`${name} is required`);
990
999
  return result;
991
1000
  }
992
- function stringOpt(value) {
993
- if (typeof value === "string") return value;
994
- if (Array.isArray(value)) return value[value.length - 1];
1001
+ function stringOpt(value2) {
1002
+ if (typeof value2 === "string") return value2;
1003
+ if (Array.isArray(value2)) return value2[value2.length - 1];
995
1004
  return void 0;
996
1005
  }
997
- function listOpt(value) {
998
- if (value === void 0 || typeof value === "boolean") return void 0;
999
- const values = Array.isArray(value) ? value : [value];
1006
+ function listOpt(value2) {
1007
+ if (value2 === void 0 || typeof value2 === "boolean") return void 0;
1008
+ const values = Array.isArray(value2) ? value2 : [value2];
1000
1009
  return values.flatMap((item) => item.split(",")).map((item) => item.trim()).filter(Boolean);
1001
1010
  }
1002
- function boolOpt(value) {
1003
- if (typeof value === "boolean") return value;
1004
- if (typeof value === "string" && (value === "true" || value === "false")) return value === "true";
1005
- if (value === void 0) return void 0;
1011
+ function boolOpt(value2) {
1012
+ if (typeof value2 === "boolean") return value2;
1013
+ if (typeof value2 === "string" && (value2 === "true" || value2 === "false")) return value2 === "true";
1014
+ if (value2 === void 0) return void 0;
1006
1015
  throw new Error("boolean option must be true or false");
1007
1016
  }
1008
- function numberOpt(value, flag) {
1009
- if (value === void 0) return void 0;
1010
- const raw = stringOpt(value);
1017
+ function numberOpt(value2, flag) {
1018
+ if (value2 === void 0) return void 0;
1019
+ const raw = stringOpt(value2);
1011
1020
  const parsed = raw === void 0 ? Number.NaN : Number(raw);
1012
1021
  if (!Number.isSafeInteger(parsed) || parsed < 1) throw new Error(`${flag} requires a positive integer`);
1013
1022
  return parsed;
1014
1023
  }
1015
- function addOption(options, name, value) {
1024
+ function addOption(options, name, value2) {
1016
1025
  const current = options[name];
1017
- if (current === void 0) options[name] = value;
1018
- else if (Array.isArray(current)) current.push(String(value));
1019
- else options[name] = [String(current), String(value)];
1026
+ if (current === void 0) options[name] = value2;
1027
+ else if (Array.isArray(current)) current.push(String(value2));
1028
+ else options[name] = [String(current), String(value2)];
1020
1029
  }
1021
1030
 
1022
1031
  // src/operator-context.ts
@@ -1083,17 +1092,17 @@ function validateProbes(integration, at) {
1083
1092
  }
1084
1093
  }
1085
1094
  }
1086
- function isRecord4(value) {
1087
- return value !== null && typeof value === "object" && !Array.isArray(value);
1095
+ function isRecord4(value2) {
1096
+ return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
1088
1097
  }
1089
- function safeText(value, max) {
1090
- return typeof value === "string" && value.trim().length > 0 && value.length <= max && !/[\u0000-\u001f\u007f]/.test(value);
1098
+ function safeText(value2, max) {
1099
+ return typeof value2 === "string" && value2.trim().length > 0 && value2.length <= max && !/[\u0000-\u001f\u007f]/.test(value2);
1091
1100
  }
1092
- function safeProbePath(value) {
1093
- return typeof value === "string" && /^\/[A-Za-z0-9._~!$&'()*+,;=:@%/-]*$/.test(value);
1101
+ function safeProbePath(value2) {
1102
+ return typeof value2 === "string" && /^\/[A-Za-z0-9._~!$&'()*+,;=:@%/-]*$/.test(value2);
1094
1103
  }
1095
- function validId(value) {
1096
- return typeof value === "string" && /^[a-z0-9][a-z0-9-]*$/.test(value);
1104
+ function validId(value2) {
1105
+ return typeof value2 === "string" && /^[a-z0-9][a-z0-9-]*$/.test(value2);
1097
1106
  }
1098
1107
  function unique(values) {
1099
1108
  return [...new Set(values.filter(Boolean))];
@@ -1135,10 +1144,10 @@ async function loadProjectConfig(configPath = "odla.config.mjs") {
1135
1144
  local
1136
1145
  };
1137
1146
  }
1138
- async function resolveDataExport(cfg, value, names) {
1139
- if (value === void 0 || value === null || value === false) return void 0;
1140
- if (typeof value !== "string") return value;
1141
- const target = (0, import_node_path4.isAbsolute)(value) ? value : (0, import_node_path4.resolve)(cfg.rootDir, value);
1147
+ async function resolveDataExport(cfg, value2, names) {
1148
+ if (value2 === void 0 || value2 === null || value2 === false) return void 0;
1149
+ if (typeof value2 !== "string") return value2;
1150
+ const target = (0, import_node_path4.isAbsolute)(value2) ? value2 : (0, import_node_path4.resolve)(cfg.rootDir, value2);
1142
1151
  if (target.endsWith(".json")) {
1143
1152
  return JSON.parse((0, import_node_fs4.readFileSync)(target, "utf8"));
1144
1153
  }
@@ -1147,7 +1156,7 @@ async function resolveDataExport(cfg, value, names) {
1147
1156
  if (mod[name] !== void 0) return mod[name];
1148
1157
  }
1149
1158
  if (mod.default !== void 0) return mod.default;
1150
- throw new Error(`${value} did not export ${names.join(", ")} or default`);
1159
+ throw new Error(`${value2} did not export ${names.join(", ")} or default`);
1151
1160
  }
1152
1161
  function buildPlan(cfg) {
1153
1162
  const integrationSchema = cfg.integrations?.some((integration) => integration.schema) ?? false;
@@ -1181,9 +1190,9 @@ function calendarServiceConfig(cfg, env) {
1181
1190
  };
1182
1191
  }
1183
1192
  function calendarBookingPageUrl(cfg, env) {
1184
- const value = cfg.calendar?.google.bookingPageUrl?.[env];
1185
- if (value === void 0 || value === null) return value;
1186
- return new URL(value).toString();
1193
+ const value2 = cfg.calendar?.google.bookingPageUrl?.[env];
1194
+ if (value2 === void 0 || value2 === null) return value2;
1195
+ return new URL(value2).toString();
1187
1196
  }
1188
1197
  function rulesFromSchema(schema) {
1189
1198
  const entities = serializedEntities(schema);
@@ -1197,10 +1206,10 @@ function serializedEntities(schema) {
1197
1206
  if (!entities || typeof entities !== "object" || Array.isArray(entities)) return [];
1198
1207
  return Object.keys(entities);
1199
1208
  }
1200
- function envValue(value) {
1201
- if (!value) return void 0;
1202
- if (value.startsWith("$")) return process.env[value.slice(1)];
1203
- return value;
1209
+ function envValue(value2) {
1210
+ if (!value2) return void 0;
1211
+ if (value2.startsWith("$")) return process.env[value2.slice(1)];
1212
+ return value2;
1204
1213
  }
1205
1214
  function validateRawConfig(raw, path) {
1206
1215
  if (!raw || typeof raw !== "object") throw new Error(`${path} must export an object`);
@@ -1256,8 +1265,8 @@ function validateCalendarConfig(cfg, envs, services, path) {
1256
1265
  if (!isRecord5(google.bookingCalendar)) throw new Error(`${path}: calendar.google.bookingCalendar must map env names to one calendar id`);
1257
1266
  const unknownBookingEnv = Object.keys(google.bookingCalendar).find((env) => !envs.includes(env));
1258
1267
  if (unknownBookingEnv) throw new Error(`${path}: calendar.google.bookingCalendar.${unknownBookingEnv} is not in config envs`);
1259
- for (const [env, value] of Object.entries(google.bookingCalendar)) {
1260
- if (!safeText2(value, 1024)) {
1268
+ for (const [env, value2] of Object.entries(google.bookingCalendar)) {
1269
+ if (!safeText2(value2, 1024)) {
1261
1270
  throw new Error(`${path}: calendar.google.bookingCalendar.${env} must be a calendar id`);
1262
1271
  }
1263
1272
  }
@@ -1266,8 +1275,8 @@ function validateCalendarConfig(cfg, envs, services, path) {
1266
1275
  if (!isRecord5(google.bookingPageUrl)) throw new Error(`${path}: calendar.google.bookingPageUrl must map env names to HTTPS URLs or null`);
1267
1276
  const unknownBookingEnv = Object.keys(google.bookingPageUrl).find((env) => !envs.includes(env));
1268
1277
  if (unknownBookingEnv) throw new Error(`${path}: calendar.google.bookingPageUrl.${unknownBookingEnv} is not in config envs`);
1269
- for (const [env, value] of Object.entries(google.bookingPageUrl)) {
1270
- if (value !== null && !safeHttpsUrl(value)) {
1278
+ for (const [env, value2] of Object.entries(google.bookingPageUrl)) {
1279
+ if (value2 !== null && !safeHttpsUrl(value2)) {
1271
1280
  throw new Error(`${path}: calendar.google.bookingPageUrl.${env} must be an HTTPS URL without credentials or fragment`);
1272
1281
  }
1273
1282
  }
@@ -1286,37 +1295,37 @@ function validateServices(services, path) {
1286
1295
  }
1287
1296
  }
1288
1297
  }
1289
- function assertOnly(value, allowed, label) {
1290
- const extra = Object.keys(value).find((key) => !allowed.includes(key));
1298
+ function assertOnly(value2, allowed, label) {
1299
+ const extra = Object.keys(value2).find((key) => !allowed.includes(key));
1291
1300
  if (extra) throw new Error(`${label}.${extra} is not supported`);
1292
1301
  }
1293
- function isRecord5(value) {
1294
- return value !== null && typeof value === "object" && !Array.isArray(value);
1302
+ function isRecord5(value2) {
1303
+ return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
1295
1304
  }
1296
- function safeText2(value, max) {
1297
- return typeof value === "string" && value.trim().length > 0 && value.length <= max && !/[\u0000-\u001f\u007f]/.test(value);
1305
+ function safeText2(value2, max) {
1306
+ return typeof value2 === "string" && value2.trim().length > 0 && value2.length <= max && !/[\u0000-\u001f\u007f]/.test(value2);
1298
1307
  }
1299
- function safeHttpsUrl(value) {
1300
- if (typeof value !== "string" || value.length > 2048) return false;
1308
+ function safeHttpsUrl(value2) {
1309
+ if (typeof value2 !== "string" || value2.length > 2048) return false;
1301
1310
  try {
1302
- const url = new URL(value);
1311
+ const url = new URL(value2);
1303
1312
  return url.protocol === "https:" && !url.username && !url.password && !url.hash;
1304
1313
  } catch {
1305
1314
  return false;
1306
1315
  }
1307
1316
  }
1308
- function validId2(value) {
1309
- return typeof value === "string" && /^[a-z0-9][a-z0-9-]*$/.test(value);
1317
+ function validId2(value2) {
1318
+ return typeof value2 === "string" && /^[a-z0-9][a-z0-9-]*$/.test(value2);
1310
1319
  }
1311
1320
  async function loadConfigModule(path) {
1312
1321
  if (path.endsWith(".json")) return JSON.parse((0, import_node_fs4.readFileSync)(path, "utf8"));
1313
1322
  const mod = await import(`${(0, import_node_url.pathToFileURL)(path).href}?t=${Date.now()}`);
1314
- const value = mod.default ?? mod.config;
1315
- if (typeof value === "function") return await value();
1316
- return value;
1323
+ const value2 = mod.default ?? mod.config;
1324
+ if (typeof value2 === "function") return await value2();
1325
+ return value2;
1317
1326
  }
1318
- function trimSlash(value) {
1319
- return value.replace(/\/+$/, "");
1327
+ function trimSlash(value2) {
1328
+ return value2.replace(/\/+$/, "");
1320
1329
  }
1321
1330
  function unique2(values) {
1322
1331
  return [...new Set(values.filter(Boolean))];
@@ -1342,8 +1351,8 @@ function resolveOperatorProfile(parsed) {
1342
1351
  }
1343
1352
  assertOperatorName(name, "context");
1344
1353
  const profiles = readOperatorProfiles(file);
1345
- const value = Object.hasOwn(profiles, name) ? profiles[name] : void 0;
1346
- if (!value) {
1354
+ const value2 = Object.hasOwn(profiles, name) ? profiles[name] : void 0;
1355
+ if (!value2) {
1347
1356
  throw new Error(
1348
1357
  `operator context "${name}" not found in ${file}; run "odla-ai context list" or save it first`
1349
1358
  );
@@ -1352,7 +1361,7 @@ function resolveOperatorProfile(parsed) {
1352
1361
  name,
1353
1362
  source: fromFlag ? "flag" : "environment",
1354
1363
  file,
1355
- value
1364
+ value: value2
1356
1365
  };
1357
1366
  }
1358
1367
  function listOperatorProfiles(file = operatorProfileFile()) {
@@ -1380,8 +1389,8 @@ function operatorCredentialFiles(selection) {
1380
1389
  scoped: (0, import_node_path5.join)(base, "admin-token.local.json")
1381
1390
  };
1382
1391
  }
1383
- function assertOperatorName(value, label) {
1384
- if (!/^[a-z0-9][a-z0-9-]*$/.test(value)) {
1392
+ function assertOperatorName(value2, label) {
1393
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(value2)) {
1385
1394
  throw new Error(
1386
1395
  `${label} must contain lowercase letters, numbers, and hyphens`
1387
1396
  );
@@ -1407,22 +1416,22 @@ function readOperatorProfiles(file) {
1407
1416
  );
1408
1417
  }
1409
1418
  const profiles = emptyProfiles();
1410
- for (const [name, value] of Object.entries(
1419
+ for (const [name, value2] of Object.entries(
1411
1420
  raw.profiles
1412
1421
  )) {
1413
1422
  assertOperatorName(name, "context");
1414
- profiles[name] = validateProfile(value, `operator context "${name}"`);
1423
+ profiles[name] = validateProfile(value2, `operator context "${name}"`);
1415
1424
  }
1416
1425
  return profiles;
1417
1426
  }
1418
1427
  function emptyProfiles() {
1419
1428
  return /* @__PURE__ */ Object.create(null);
1420
1429
  }
1421
- function validateProfile(value, label) {
1422
- if (!value || typeof value !== "object" || Array.isArray(value)) {
1430
+ function validateProfile(value2, label) {
1431
+ if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) {
1423
1432
  throw new Error(`${label} has an invalid shape`);
1424
1433
  }
1425
- const unknown = Object.keys(value).filter(
1434
+ const unknown = Object.keys(value2).filter(
1426
1435
  (key) => !["platform", "app", "environment"].includes(key)
1427
1436
  );
1428
1437
  if (unknown.length > 0) {
@@ -1430,7 +1439,7 @@ function validateProfile(value, label) {
1430
1439
  `${label} contains unsupported field(s): ${unknown.sort().join(", ")}; contexts store scope metadata only, never credentials`
1431
1440
  );
1432
1441
  }
1433
- const candidate = value;
1442
+ const candidate = value2;
1434
1443
  if (typeof candidate.platform !== "string") {
1435
1444
  throw new Error(`${label} needs an absolute platform URL`);
1436
1445
  }
@@ -1445,8 +1454,8 @@ function validateProfile(value, label) {
1445
1454
  ...environment2 ? { environment: environment2 } : {}
1446
1455
  };
1447
1456
  }
1448
- function clean(value) {
1449
- const normalized = value?.trim();
1457
+ function clean(value2) {
1458
+ const normalized = value2?.trim();
1450
1459
  return normalized || void 0;
1451
1460
  }
1452
1461
 
@@ -1539,8 +1548,8 @@ async function resolveOperatorContext(parsed, options = {}) {
1539
1548
  }
1540
1549
  };
1541
1550
  }
1542
- function clean2(value) {
1543
- const normalized = value?.trim();
1551
+ function clean2(value2) {
1552
+ const normalized = value2?.trim();
1544
1553
  return normalized || void 0;
1545
1554
  }
1546
1555
 
@@ -1562,21 +1571,21 @@ var SET_OPTIONS = [
1562
1571
  ];
1563
1572
  async function adminCommand(parsed, deps = {}) {
1564
1573
  const area = parsed.positionals[1];
1565
- const action = parsed.positionals[2];
1566
- const credentialSet = action === "credential" && parsed.positionals[3] === "set";
1567
- const credentials = action === "credentials";
1568
- const models = action === "models";
1569
- const usage = action === "usage";
1570
- const audit = action === "audit";
1571
- if (area !== "ai" || action !== "show" && action !== "set" && !credentialSet && !credentials && !models && !usage && !audit) {
1574
+ const action2 = parsed.positionals[2];
1575
+ const credentialSet = action2 === "credential" && parsed.positionals[3] === "set";
1576
+ const credentials = action2 === "credentials";
1577
+ const models = action2 === "models";
1578
+ const usage = action2 === "usage";
1579
+ const audit = action2 === "audit";
1580
+ if (area !== "ai" || action2 !== "show" && action2 !== "set" && !credentialSet && !credentials && !models && !usage && !audit) {
1572
1581
  throw new Error('unknown admin command. Try "odla-ai admin ai show".');
1573
1582
  }
1574
- 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;
1575
- assertArgs(parsed, allowed, credentialSet ? 5 : action === "set" ? 4 : 3);
1583
+ 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;
1584
+ assertArgs(parsed, allowed, credentialSet ? 5 : action2 === "set" ? 4 : 3);
1576
1585
  const context = await resolveOperatorContext(parsed, { allowMissingConfig: true });
1577
1586
  await adminAi({
1578
- action: credentialSet ? "credential-set" : credentials ? "credentials" : models ? "models" : usage ? "usage" : audit ? "audit" : action,
1579
- purpose: action === "set" ? parsed.positionals[3] : void 0,
1587
+ action: credentialSet ? "credential-set" : credentials ? "credentials" : models ? "models" : usage ? "usage" : audit ? "audit" : action2,
1588
+ purpose: action2 === "set" ? parsed.positionals[3] : void 0,
1580
1589
  provider: stringOpt(parsed.options.provider),
1581
1590
  model: stringOpt(parsed.options.model),
1582
1591
  enabled: boolOpt(parsed.options.enabled),
@@ -1633,12 +1642,12 @@ function bothTenants(cfg) {
1633
1642
 
1634
1643
  // src/agent-command.ts
1635
1644
  async function agentCommand(parsed, deps = {}) {
1636
- const action = parsed.positionals[1];
1637
- if (action !== "jobs" && action !== "retry") {
1638
- throw new Error(`unknown agent action "${action ?? ""}". Try "odla-ai agent jobs --json".`);
1645
+ const action2 = parsed.positionals[1];
1646
+ if (action2 !== "jobs" && action2 !== "retry") {
1647
+ throw new Error(`unknown agent action "${action2 ?? ""}". Try "odla-ai agent jobs --json".`);
1639
1648
  }
1640
- assertArgs(parsed, ["config", "env", "state", "limit", "json", "token", "email"], action === "jobs" ? 2 : 3);
1641
- if (action === "retry" && (parsed.options.state !== void 0 || parsed.options.limit !== void 0)) {
1649
+ assertArgs(parsed, ["config", "env", "state", "limit", "json", "token", "email"], action2 === "jobs" ? 2 : 3);
1650
+ if (action2 === "retry" && (parsed.options.state !== void 0 || parsed.options.limit !== void 0)) {
1642
1651
  throw new Error('--state and --limit are supported only by "agent jobs"');
1643
1652
  }
1644
1653
  const cfg = await loadProjectConfig(stringOpt(parsed.options.config) ?? "odla.config.mjs");
@@ -1658,7 +1667,7 @@ async function agentCommand(parsed, deps = {}) {
1658
1667
  );
1659
1668
  const base = `${cfg.dbEndpoint}/app/${encodeURIComponent(tenant)}/admin/agent-jobs`;
1660
1669
  const headers = { authorization: `Bearer ${credential2}` };
1661
- if (action === "retry") {
1670
+ if (action2 === "retry") {
1662
1671
  const id = parsed.positionals[2];
1663
1672
  const res2 = await doFetch(`${base}/${encodeURIComponent(id)}/retry`, { method: "POST", headers });
1664
1673
  const body2 = await readJson(res2);
@@ -2094,7 +2103,7 @@ async function copyFiles(cfg, token, route2, doFetch, out) {
2094
2103
  }
2095
2104
 
2096
2105
  // src/app-lifecycle.ts
2097
- async function lifecycleCall(action, options) {
2106
+ async function lifecycleCall(action2, options) {
2098
2107
  const cfg = await loadProjectConfig(options.configPath);
2099
2108
  const out = options.stdout ?? console;
2100
2109
  const doFetch = options.fetch ?? fetch;
@@ -2104,13 +2113,13 @@ async function lifecycleCall(action, options) {
2104
2113
  doFetch,
2105
2114
  out
2106
2115
  );
2107
- const res = await doFetch(`${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/${action}`, {
2116
+ const res = await doFetch(`${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/${action2}`, {
2108
2117
  method: "POST",
2109
2118
  headers: { authorization: `Bearer ${token}`, "content-type": "application/json" }
2110
2119
  });
2111
2120
  const body = await res.json().catch(() => ({}));
2112
2121
  if (!res.ok || !body.ok) {
2113
- throw new Error(`${action} failed${body.error?.code ? ` (${body.error.code})` : ""}: ${body.error?.message ?? `registry returned ${res.status}`}`);
2122
+ throw new Error(`${action2} failed${body.error?.code ? ` (${body.error.code})` : ""}: ${body.error?.message ?? `registry returned ${res.status}`}`);
2114
2123
  }
2115
2124
  return body;
2116
2125
  }
@@ -2251,44 +2260,44 @@ var REPLACEMENTS = [
2251
2260
  [/\b(ghp|gho|github_pat)_[A-Za-z0-9_]+/g, "$1_[redacted]"],
2252
2261
  [/\bAKIA[A-Z0-9]{12,}/g, "AKIA[redacted]"]
2253
2262
  ];
2254
- function redactSecrets(value) {
2255
- let result = value;
2263
+ function redactSecrets(value2) {
2264
+ let result = value2;
2256
2265
  for (const [pattern, replacement] of REPLACEMENTS) result = result.replace(pattern, replacement);
2257
2266
  return result;
2258
2267
  }
2259
2268
  function redactingOutput(output) {
2260
- const redactArgs = (args) => args.map((value) => redactOutputValue(value, /* @__PURE__ */ new WeakMap()));
2269
+ const redactArgs = (args) => args.map((value2) => redactOutputValue(value2, /* @__PURE__ */ new WeakMap()));
2261
2270
  return {
2262
2271
  log: (...args) => output.log(...redactArgs(args)),
2263
2272
  error: (...args) => output.error(...redactArgs(args))
2264
2273
  };
2265
2274
  }
2266
- function redactOutputValue(value, seen) {
2267
- if (typeof value === "string") return redactSecrets(value);
2268
- if (value instanceof Error) {
2269
- const redacted = new Error(redactSecrets(value.message));
2270
- redacted.name = value.name;
2271
- if (value.stack) redacted.stack = redactSecrets(value.stack);
2275
+ function redactOutputValue(value2, seen) {
2276
+ if (typeof value2 === "string") return redactSecrets(value2);
2277
+ if (value2 instanceof Error) {
2278
+ const redacted = new Error(redactSecrets(value2.message));
2279
+ redacted.name = value2.name;
2280
+ if (value2.stack) redacted.stack = redactSecrets(value2.stack);
2272
2281
  return redacted;
2273
2282
  }
2274
- if (!value || typeof value !== "object") return value;
2275
- const prior = seen.get(value);
2283
+ if (!value2 || typeof value2 !== "object") return value2;
2284
+ const prior = seen.get(value2);
2276
2285
  if (prior) return prior;
2277
- if (Array.isArray(value)) {
2286
+ if (Array.isArray(value2)) {
2278
2287
  const next2 = [];
2279
- seen.set(value, next2);
2280
- for (const item of value) next2.push(redactOutputValue(item, seen));
2288
+ seen.set(value2, next2);
2289
+ for (const item of value2) next2.push(redactOutputValue(item, seen));
2281
2290
  return next2;
2282
2291
  }
2283
- const prototype = Object.getPrototypeOf(value);
2284
- if (prototype !== Object.prototype && prototype !== null) return value;
2292
+ const prototype = Object.getPrototypeOf(value2);
2293
+ if (prototype !== Object.prototype && prototype !== null) return value2;
2285
2294
  const next = {};
2286
- seen.set(value, next);
2287
- for (const [key, item] of Object.entries(value)) next[key] = redactOutputValue(item, seen);
2295
+ seen.set(value2, next);
2296
+ for (const [key, item] of Object.entries(value2)) next[key] = redactOutputValue(item, seen);
2288
2297
  return next;
2289
2298
  }
2290
- function looksSecret(value) {
2291
- return redactSecrets(value) !== value || value.includes("-----BEGIN");
2299
+ function looksSecret(value2) {
2300
+ return redactSecrets(value2) !== value2 || value2.includes("-----BEGIN");
2292
2301
  }
2293
2302
 
2294
2303
  // src/calendar-http.ts
@@ -2305,9 +2314,9 @@ async function readCalendarStatus(ctx) {
2305
2314
  }
2306
2315
  async function discoverGoogleCalendars(ctx) {
2307
2316
  const raw = await calendarJson(ctx, "/calendars", {});
2308
- const value = record(raw);
2309
- if (!value || !Array.isArray(value.calendars)) throw new Error("calendar discovery returned an invalid response");
2310
- return value.calendars.map((item, index) => {
2317
+ const value2 = record(raw);
2318
+ if (!value2 || !Array.isArray(value2.calendars)) throw new Error("calendar discovery returned an invalid response");
2319
+ return value2.calendars.map((item, index) => {
2311
2320
  const calendar = record(item);
2312
2321
  const id = textField(calendar?.id, 1024);
2313
2322
  if (!calendar || !id) throw new Error(`calendar discovery returned an invalid calendar at index ${index}`);
@@ -2329,10 +2338,10 @@ async function applyCalendarSettings(ctx, bookingPageUrl) {
2329
2338
  }
2330
2339
  async function startCalendarConnection(ctx) {
2331
2340
  const raw = await calendarJson(ctx, "/google/connect", { method: "POST", body: "{}" });
2332
- const value = wrapped(raw, "attempt");
2333
- const attemptId = textField(value.attemptId, 180);
2334
- const consentUrl = textField(value.consentUrl, 4096);
2335
- const expiresAt = timestamp3(value.expiresAt);
2341
+ const value2 = wrapped(raw, "attempt");
2342
+ const attemptId = textField(value2.attemptId, 180);
2343
+ const consentUrl = textField(value2.consentUrl, 4096);
2344
+ const expiresAt = timestamp3(value2.expiresAt);
2336
2345
  if (!attemptId || !consentUrl || expiresAt === void 0) throw new Error("calendar connect returned an invalid attempt");
2337
2346
  return { attemptId, consentUrl, expiresAt };
2338
2347
  }
@@ -2350,42 +2359,42 @@ async function requestCalendarDisconnect(ctx) {
2350
2359
  }
2351
2360
  function parseCalendarStatus(raw, env) {
2352
2361
  const outer = wrapped(raw, "calendar");
2353
- const value = record(outer.attempt) ?? record(outer.status) ?? outer;
2354
- const connection = record(value.connection) ?? {};
2355
- const config = record(value.config) ?? record(outer.config) ?? {};
2362
+ const value2 = record(outer.attempt) ?? record(outer.status) ?? outer;
2363
+ const connection = record(value2.connection) ?? {};
2364
+ const config = record(value2.config) ?? record(outer.config) ?? {};
2356
2365
  const googleConfig = record(config.google) ?? config;
2357
- const stateValue = calendarState(value.status ?? value.state ?? connection.status ?? connection.state);
2366
+ const stateValue = calendarState(value2.status ?? value2.state ?? connection.status ?? connection.state);
2358
2367
  if (!stateValue) {
2359
2368
  throw new Error("calendar status returned an invalid connection state");
2360
2369
  }
2361
- const providerValue = value.provider ?? connection.provider ?? config.provider ?? (config.google ? "google" : void 0);
2370
+ const providerValue = value2.provider ?? connection.provider ?? config.provider ?? (config.google ? "google" : void 0);
2362
2371
  if (providerValue !== void 0 && providerValue !== null && providerValue !== "google") {
2363
2372
  throw new Error("calendar status returned an unsupported provider");
2364
2373
  }
2365
- const accessValue = value.access ?? connection.access ?? config.access ?? googleConfig.access;
2374
+ const accessValue = value2.access ?? connection.access ?? config.access ?? googleConfig.access;
2366
2375
  if (accessValue !== void 0 && accessValue !== "book" && accessValue !== "read") {
2367
2376
  throw new Error("calendar status returned unsupported access");
2368
2377
  }
2369
- const errorValue = record(value.error) ?? record(connection.error);
2370
- const errorCode = textField(value.lastErrorCode, 128);
2371
- const bookingPageValue = Object.hasOwn(value, "bookingPageUrl") ? value.bookingPageUrl : Object.hasOwn(config, "bookingPageUrl") ? config.bookingPageUrl : googleConfig.bookingPageUrl;
2372
- const connected = typeof (value.connected ?? connection.connected) === "boolean" ? Boolean(value.connected ?? connection.connected) : ["healthy", "degraded"].includes(stateValue);
2373
- const bookingCalendarId = textField(value.bookingCalendarId ?? config.bookingCalendarId, 1024);
2378
+ const errorValue = record(value2.error) ?? record(connection.error);
2379
+ const errorCode = textField(value2.lastErrorCode, 128);
2380
+ const bookingPageValue = Object.hasOwn(value2, "bookingPageUrl") ? value2.bookingPageUrl : Object.hasOwn(config, "bookingPageUrl") ? config.bookingPageUrl : googleConfig.bookingPageUrl;
2381
+ const connected = typeof (value2.connected ?? connection.connected) === "boolean" ? Boolean(value2.connected ?? connection.connected) : ["healthy", "degraded"].includes(stateValue);
2382
+ const bookingCalendarId = textField(value2.bookingCalendarId ?? config.bookingCalendarId, 1024);
2374
2383
  return {
2375
2384
  env,
2376
- enabled: typeof value.enabled === "boolean" ? value.enabled : true,
2385
+ enabled: typeof value2.enabled === "boolean" ? value2.enabled : true,
2377
2386
  connected,
2378
- writable: typeof value.writable === "boolean" ? value.writable : stateValue === "healthy",
2387
+ writable: typeof value2.writable === "boolean" ? value2.writable : stateValue === "healthy",
2379
2388
  provider: providerValue === "google" ? "google" : null,
2380
2389
  status: stateValue,
2381
2390
  ...accessValue !== void 0 ? { access: "book" } : {},
2382
2391
  ...bookingCalendarId ? { bookingCalendarId } : {},
2383
2392
  calendars: calendarIds(
2384
- value.availabilityCalendars ?? config.availabilityCalendars ?? value.configuredCalendars ?? value.calendars ?? config.calendars ?? googleConfig.calendars
2393
+ value2.availabilityCalendars ?? config.availabilityCalendars ?? value2.configuredCalendars ?? value2.calendars ?? config.calendars ?? googleConfig.calendars
2385
2394
  ),
2386
2395
  ...optionalNullableUrl("bookingPageUrl", bookingPageValue),
2387
- grantedScopes: stringList(value.grantedScopes ?? connection.grantedScopes ?? connection.scopes),
2388
- ...optionalText("attemptId", value.attemptId ?? connection.attemptId, 180),
2396
+ grantedScopes: stringList(value2.grantedScopes ?? connection.grantedScopes ?? connection.scopes),
2397
+ ...optionalText("attemptId", value2.attemptId ?? connection.attemptId, 180),
2389
2398
  ...errorValue || errorCode ? { error: {
2390
2399
  ...textField(errorValue?.code, 128) ? { code: textField(errorValue?.code, 128) } : {},
2391
2400
  ...textField(errorValue?.message, 500) ? { message: textField(errorValue?.message, 500) } : {},
@@ -2393,9 +2402,9 @@ function parseCalendarStatus(raw, env) {
2393
2402
  } } : {}
2394
2403
  };
2395
2404
  }
2396
- function calendarState(value) {
2397
- if (typeof value !== "string") return null;
2398
- if (CALENDAR_STATES.includes(value)) return value;
2405
+ function calendarState(value2) {
2406
+ if (typeof value2 !== "string") return null;
2407
+ if (CALENDAR_STATES.includes(value2)) return value2;
2399
2408
  const legacy = {
2400
2409
  disabled: "not_connected",
2401
2410
  disconnected: "disconnected",
@@ -2406,7 +2415,7 @@ function calendarState(value) {
2406
2415
  ready: "healthy",
2407
2416
  error: "failed"
2408
2417
  };
2409
- return legacy[value] ?? null;
2418
+ return legacy[value2] ?? null;
2410
2419
  }
2411
2420
  async function calendarJson(ctx, suffix, init) {
2412
2421
  const platform = platformAudience(ctx.platform);
@@ -2440,50 +2449,50 @@ function wrapped(raw, key) {
2440
2449
  if (!outer) throw new Error("calendar returned an invalid response");
2441
2450
  return record(outer[key]) ?? outer;
2442
2451
  }
2443
- function record(value) {
2444
- return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
2452
+ function record(value2) {
2453
+ return value2 !== null && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
2445
2454
  }
2446
- function textField(value, max) {
2447
- return typeof value === "string" && value.length > 0 && value.length <= max && !/[\u0000-\u001f\u007f]/.test(value) ? value : void 0;
2455
+ function textField(value2, max) {
2456
+ return typeof value2 === "string" && value2.length > 0 && value2.length <= max && !/[\u0000-\u001f\u007f]/.test(value2) ? value2 : void 0;
2448
2457
  }
2449
- function stringList(value) {
2450
- return Array.isArray(value) ? [...new Set(value.filter((item) => !!textField(item, 4096)))] : [];
2458
+ function stringList(value2) {
2459
+ return Array.isArray(value2) ? [...new Set(value2.filter((item) => !!textField(item, 4096)))] : [];
2451
2460
  }
2452
- function calendarIds(value) {
2453
- if (!Array.isArray(value)) return [];
2454
- return [...new Set(value.flatMap((item) => {
2461
+ function calendarIds(value2) {
2462
+ if (!Array.isArray(value2)) return [];
2463
+ return [...new Set(value2.flatMap((item) => {
2455
2464
  if (typeof item === "string") return textField(item, 4096) ? [item] : [];
2456
2465
  const calendar = record(item);
2457
2466
  const id = textField(calendar?.id, 4096);
2458
2467
  return id && calendar?.selected !== false ? [id] : [];
2459
2468
  }))];
2460
2469
  }
2461
- function timestamp3(value) {
2462
- if (typeof value === "number" && Number.isFinite(value) && value >= 0) return value;
2463
- if (typeof value === "string") {
2464
- const parsed = Date.parse(value);
2470
+ function timestamp3(value2) {
2471
+ if (typeof value2 === "number" && Number.isFinite(value2) && value2 >= 0) return value2;
2472
+ if (typeof value2 === "string") {
2473
+ const parsed = Date.parse(value2);
2465
2474
  if (Number.isFinite(parsed)) return parsed;
2466
2475
  }
2467
2476
  return void 0;
2468
2477
  }
2469
- function optionalText(key, value, max) {
2470
- const parsed = textField(value, max);
2478
+ function optionalText(key, value2, max) {
2479
+ const parsed = textField(value2, max);
2471
2480
  return parsed ? { [key]: parsed } : {};
2472
2481
  }
2473
- function optionalNullableUrl(key, value) {
2474
- if (value === null) return { [key]: null };
2475
- const parsed = textField(value, 4096);
2482
+ function optionalNullableUrl(key, value2) {
2483
+ if (value2 === null) return { [key]: null };
2484
+ const parsed = textField(value2, 4096);
2476
2485
  return parsed ? { [key]: parsed } : {};
2477
2486
  }
2478
- function identifier(value, name) {
2479
- if (typeof value !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,179}$/.test(value)) throw new Error(`${name} is invalid`);
2480
- return value;
2487
+ function identifier(value2, name) {
2488
+ if (typeof value2 !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,179}$/.test(value2)) throw new Error(`${name} is invalid`);
2489
+ return value2;
2481
2490
  }
2482
- function credential(value) {
2483
- if (typeof value !== "string" || value.length < 8 || value.length > 8192 || /\s|[\u0000-\u001f\u007f]/.test(value)) {
2491
+ function credential(value2) {
2492
+ if (typeof value2 !== "string" || value2.length < 8 || value2.length > 8192 || /\s|[\u0000-\u001f\u007f]/.test(value2)) {
2484
2493
  throw new Error("calendar requires an odla developer token");
2485
2494
  }
2486
- return value;
2495
+ return value2;
2487
2496
  }
2488
2497
 
2489
2498
  // src/calendar-poll.ts
@@ -2644,11 +2653,11 @@ async function connectWithContext(ctx, options) {
2644
2653
  }
2645
2654
  throw new Error('Google Calendar consent timed out; run "odla-ai calendar status" and retry connect');
2646
2655
  }
2647
- function trustedCalendarConsentUrl(platform, value) {
2656
+ function trustedCalendarConsentUrl(platform, value2) {
2648
2657
  const origin = platformAudience(platform);
2649
2658
  let url;
2650
2659
  try {
2651
- url = value.startsWith("/") ? new URL(value, origin) : new URL(value);
2660
+ url = value2.startsWith("/") ? new URL(value2, origin) : new URL(value2);
2652
2661
  } catch {
2653
2662
  throw new Error("odla.ai returned an invalid calendar consent URL");
2654
2663
  }
@@ -2659,12 +2668,12 @@ function trustedCalendarConsentUrl(platform, value) {
2659
2668
  }
2660
2669
  return url.toString();
2661
2670
  }
2662
- function pollTimeout(value = 10 * 6e4) {
2663
- if (!Number.isSafeInteger(value) || value < 1e3 || value > 60 * 6e4) throw new Error("calendar poll timeout must be 1000-3600000ms");
2664
- return value;
2671
+ function pollTimeout(value2 = 10 * 6e4) {
2672
+ if (!Number.isSafeInteger(value2) || value2 < 1e3 || value2 > 60 * 6e4) throw new Error("calendar poll timeout must be 1000-3600000ms");
2673
+ return value2;
2665
2674
  }
2666
- function productionConsent(env, yes, action) {
2667
- if ((env === "prod" || env === "production") && !yes) throw new Error(`refusing to ${action} for "${env}" without --yes`);
2675
+ function productionConsent(env, yes, action2) {
2676
+ if ((env === "prod" || env === "production") && !yes) throw new Error(`refusing to ${action2} for "${env}" without --yes`);
2668
2677
  }
2669
2678
  function printStatus(status, json, out) {
2670
2679
  if (json) {
@@ -2693,6 +2702,8 @@ var CAPABILITIES = {
2693
2702
  "validate integration contracts offline and smoke-test a provisioned db environment plus anonymous capability routes",
2694
2703
  "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",
2695
2704
  "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",
2705
+ "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",
2706
+ "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",
2696
2707
  "inspect durable agent wakeups as a versioned JSON envelope and explicitly requeue one dead-lettered job with a scoped environment credential",
2697
2708
  "run app-attributed hosted security discovery and independent validation without provider keys",
2698
2709
  "connect/revoke source-read-only GitHub sources and drive commit-pinned hosted security jobs without PATs or provider keys",
@@ -2739,15 +2750,542 @@ function printGroup(out, heading, items) {
2739
2750
  out.log("");
2740
2751
  }
2741
2752
 
2753
+ // src/config-reconcile-command.ts
2754
+ var import_apps6 = require("@odla-ai/apps");
2755
+ var import_node_path7 = require("path");
2756
+
2757
+ // src/config-reconcile-digest.ts
2758
+ var import_node_crypto = require("crypto");
2759
+ function canonicalJson(value2) {
2760
+ return JSON.stringify(canonicalValue(value2));
2761
+ }
2762
+ function configDigest(value2) {
2763
+ return `sha256:${(0, import_node_crypto.createHash)("sha256").update(canonicalJson(value2)).digest("hex")}`;
2764
+ }
2765
+ function canonicalValue(value2) {
2766
+ if (Array.isArray(value2)) return value2.map(canonicalValue);
2767
+ if (value2 && typeof value2 === "object") {
2768
+ return Object.fromEntries(
2769
+ Object.entries(value2).filter(([, entry]) => entry !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, canonicalValue(entry)])
2770
+ );
2771
+ }
2772
+ return value2;
2773
+ }
2774
+
2775
+ // src/config-reconcile-desired.ts
2776
+ var import_apps4 = require("@odla-ai/apps");
2777
+
2778
+ // src/provision-helpers.ts
2779
+ var import_ai = require("@odla-ai/ai");
2780
+ var import_apps3 = require("@odla-ai/apps");
2781
+ function defaultSecretName(provider) {
2782
+ const names = import_ai.DEFAULT_SECRET_NAMES;
2783
+ return names[provider] ?? `${provider}_api_key`;
2784
+ }
2785
+ async function assertTenantAdminAccess(doFetch, cfg, env, token) {
2786
+ const tenantId = (0, import_apps3.tenantIdFor)(cfg.app.id, env);
2787
+ const res = await doFetch(`${cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenantId)}/entitlements`, {
2788
+ headers: { authorization: `Bearer ${token}` }
2789
+ });
2790
+ if (res.ok || res.status === 404) return;
2791
+ if (res.status === 403) {
2792
+ throw new Error(
2793
+ `${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`
2794
+ );
2795
+ }
2796
+ throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await safeText3(res)}`);
2797
+ }
2798
+ async function postJson(doFetch, url, bearer, body) {
2799
+ const res = await doFetch(url, {
2800
+ method: "POST",
2801
+ headers: { authorization: `Bearer ${bearer}`, "content-type": "application/json" },
2802
+ body: JSON.stringify(body)
2803
+ });
2804
+ if (!res.ok) throw new Error(`${new URL(url).pathname} failed: ${res.status} ${await safeText3(res)}`);
2805
+ }
2806
+ function normalizeClerkConfig(value2) {
2807
+ if (!value2) return null;
2808
+ if (typeof value2 === "string") {
2809
+ const publishableKey2 = envValue(value2);
2810
+ return publishableKey2 ? { publishableKey: publishableKey2 } : null;
2811
+ }
2812
+ if (typeof value2 !== "object") return null;
2813
+ const cfg = value2;
2814
+ const publishableKey = envValue(cfg.publishableKey);
2815
+ return publishableKey ? { publishableKey, ...cfg.audience ? { audience: cfg.audience } : {}, ...cfg.mode ? { mode: cfg.mode } : {} } : null;
2816
+ }
2817
+ async function safeText3(res) {
2818
+ try {
2819
+ return redactSecrets((await res.text()).slice(0, 500));
2820
+ } catch {
2821
+ return "";
2822
+ }
2823
+ }
2824
+
2825
+ // src/config-reconcile-desired.ts
2826
+ function desiredRegistryState(cfg) {
2827
+ const environments = {};
2828
+ const services = (0, import_apps4.orderAppServices)(cfg.services);
2829
+ for (const env of cfg.envs) {
2830
+ const desiredServices = {};
2831
+ for (const service of services) {
2832
+ desiredServices[service] = {
2833
+ enabled: true,
2834
+ ...managedServiceConfig(cfg, env, service)
2835
+ };
2836
+ }
2837
+ const authoredAuth = cfg.auth?.clerk && Object.hasOwn(cfg.auth.clerk, env) ? cfg.auth.clerk[env] : void 0;
2838
+ const auth = normalizeClerkConfig(authoredAuth);
2839
+ if (authoredAuth && !auth) {
2840
+ throw new Error(`auth.clerk.${env} could not resolve its publishable key`);
2841
+ }
2842
+ const linkManaged = !!cfg.links && Object.hasOwn(cfg.links, env);
2843
+ environments[env] = {
2844
+ services: desiredServices,
2845
+ ...auth ? { auth } : {},
2846
+ ...linkManaged ? { link: cfg.links?.[env] ?? null } : {}
2847
+ };
2848
+ }
2849
+ return {
2850
+ appId: cfg.app.id,
2851
+ name: cfg.app.name,
2852
+ environments
2853
+ };
2854
+ }
2855
+ function managedServiceConfig(cfg, env, service) {
2856
+ if (service === "ai" && cfg.ai?.provider) {
2857
+ return {
2858
+ config: {
2859
+ provider: cfg.ai.provider,
2860
+ ...cfg.ai.model ? { model: cfg.ai.model } : {}
2861
+ }
2862
+ };
2863
+ }
2864
+ if (service === "calendar") return { config: calendarServiceConfig(cfg, env) };
2865
+ return {};
2866
+ }
2867
+
2868
+ // src/config-reconcile.ts
2869
+ var import_apps5 = require("@odla-ai/apps");
2870
+
2871
+ // src/config-reconcile-values.ts
2872
+ function difference(path, desired, observed, status, reason, desiredSource, observedSource, env, service) {
2873
+ return { path, env, service, status, desired, observed, desiredSource, observedSource, reason };
2874
+ }
2875
+ function action(kind, path, before, after, risk, applySupport, reason) {
2876
+ return { kind, path, before, after, risk, requiresApproval: false, applySupport, reason };
2877
+ }
2878
+ function scopedAction(kind, path, env, service, before, after, reason, destructive = false) {
2879
+ const production = env === "prod" || env === "production";
2880
+ return {
2881
+ ...action(
2882
+ kind,
2883
+ path,
2884
+ before,
2885
+ after,
2886
+ destructive ? "high" : production ? "medium" : "low",
2887
+ destructive ? "studio" : "provision",
2888
+ reason
2889
+ ),
2890
+ env,
2891
+ ...service ? { service } : {},
2892
+ requiresApproval: production || destructive
2893
+ };
2894
+ }
2895
+ function observedProjection(app) {
2896
+ return {
2897
+ appId: app.appId,
2898
+ name: app.name,
2899
+ archivedAt: app.archivedAt,
2900
+ environments: Object.fromEntries(
2901
+ Object.entries(app.environments).sort(([left], [right]) => left.localeCompare(right)).map(([env, services]) => [
2902
+ env,
2903
+ Object.fromEntries(Object.entries(services).sort(([left], [right]) => left.localeCompare(right)))
2904
+ ])
2905
+ ),
2906
+ auth: app.auth,
2907
+ links: app.links
2908
+ };
2909
+ }
2910
+ function projectConfig(value2, keys) {
2911
+ return Object.fromEntries(keys.filter((key) => value2[key] !== void 0).map((key) => [key, value2[key]]));
2912
+ }
2913
+ function same(left, right) {
2914
+ return canonicalJson(left) === canonicalJson(right);
2915
+ }
2916
+ function actionId(actionValue) {
2917
+ return `action-${configDigest(actionValue).slice("sha256:".length, "sha256:".length + 16)}`;
2918
+ }
2919
+ function quoteArg(value2) {
2920
+ return `'${value2.replace(/'/g, `'\\''`)}'`;
2921
+ }
2922
+
2923
+ // src/config-reconcile.ts
2924
+ var COVERAGE = {
2925
+ included: [
2926
+ "app display name",
2927
+ "environment service enablement",
2928
+ "AI provider/model and calendar booking service configuration",
2929
+ "auth configuration when explicitly declared",
2930
+ "deployment link when explicitly declared"
2931
+ ],
2932
+ excluded: [
2933
+ { path: "db.schema", reason: "owned by the database data plane" },
2934
+ { path: "db.rules", reason: "owned by the database data plane" },
2935
+ { path: "integrations.seeds", reason: "guarded data-plane writes are not Registry state" },
2936
+ { path: "credentials", reason: "shown-once credentials are intentionally local" },
2937
+ { path: "secrets", reason: "vault and Worker secrets are write-only" },
2938
+ { path: "calendar.connection", reason: "OAuth connection state is provider-owned" },
2939
+ { path: "service.config.opaque", reason: "controller-assigned fields remain service-owned" }
2940
+ ]
2941
+ };
2942
+ function reconcileConfig(input) {
2943
+ const desiredSource = { kind: "project_config", location: input.configPath };
2944
+ const observedSource = {
2945
+ kind: "registry",
2946
+ location: `${input.platformUrl}/registry/apps/${encodeURIComponent(input.desired.appId)}`
2947
+ };
2948
+ const differences = [];
2949
+ const actions = [];
2950
+ const add = (difference2, action2) => {
2951
+ differences.push(difference2);
2952
+ if (action2) actions.push({ ...action2, id: actionId(action2) });
2953
+ };
2954
+ compareApp(input.desired, input.observed, desiredSource, observedSource, add);
2955
+ compareEnvironments(input.desired, input.observed, desiredSource, observedSource, add);
2956
+ const desiredRevision = configDigest(input.desired);
2957
+ const observedRevision = input.observed ? configDigest(observedProjection(input.observed)) : null;
2958
+ const different = differences.filter((entry) => entry.status === "different").length;
2959
+ const unmanaged = differences.length - different;
2960
+ return {
2961
+ generatedAt: input.generatedAt,
2962
+ scope: {
2963
+ appId: input.desired.appId,
2964
+ platformUrl: input.platformUrl,
2965
+ environments: Object.keys(input.desired.environments).sort()
2966
+ },
2967
+ sources: { desired: desiredSource, observed: observedSource },
2968
+ desiredRevision,
2969
+ observedRevision,
2970
+ status: different ? "different" : unmanaged ? "unmanaged" : "in_sync",
2971
+ summary: {
2972
+ differences: different,
2973
+ unmanaged,
2974
+ actions: actions.length,
2975
+ productionActions: actions.filter((action2) => action2.env === "prod" || action2.env === "production").length,
2976
+ highRiskActions: actions.filter((action2) => action2.risk === "high").length
2977
+ },
2978
+ coverage: COVERAGE,
2979
+ differences,
2980
+ actions
2981
+ };
2982
+ }
2983
+ function compareApp(desired, observed, desiredSource, observedSource, add) {
2984
+ if (!observed) {
2985
+ const path = "app";
2986
+ add(
2987
+ difference(path, desired, null, "different", "the app is absent from Registry", desiredSource, observedSource),
2988
+ action(
2989
+ "create_app",
2990
+ path,
2991
+ null,
2992
+ { appId: desired.appId, name: desired.name },
2993
+ "low",
2994
+ "provision",
2995
+ "create the repository-declared app"
2996
+ )
2997
+ );
2998
+ return;
2999
+ }
3000
+ if (observed.name !== desired.name) {
3001
+ const path = "app.name";
3002
+ add(
3003
+ difference(path, desired.name, observed.name, "different", "the checked-in display name differs", desiredSource, observedSource),
3004
+ {
3005
+ ...action("rename_app", path, observed.name, desired.name, "low", "command", "make Registry match the checked-in display name"),
3006
+ command: `odla-ai app rename ${quoteArg(desired.name)} --config ${quoteArg(desiredSource.location)}`
3007
+ }
3008
+ );
3009
+ }
3010
+ }
3011
+ function compareEnvironments(desired, observed, desiredSource, observedSource, add) {
3012
+ const envs = /* @__PURE__ */ new Set([
3013
+ ...Object.keys(desired.environments),
3014
+ ...Object.keys(observed?.environments ?? {}).filter((env) => Object.values(observed?.environments[env] ?? {}).some((service) => service.enabled))
3015
+ ]);
3016
+ for (const env of [...envs].sort()) {
3017
+ compareServices(env, desired, observed, desiredSource, observedSource, add);
3018
+ compareOptionalEnvironmentState(env, desired, observed, desiredSource, observedSource, add);
3019
+ }
3020
+ }
3021
+ function compareServices(env, desired, observed, desiredSource, observedSource, add) {
3022
+ const wanted = desired.environments[env]?.services ?? {};
3023
+ const live = observed?.environments[env] ?? {};
3024
+ const knownOrder = (0, import_apps5.orderAppServices)((0, import_apps5.appServiceIds)());
3025
+ const services = [.../* @__PURE__ */ new Set([...knownOrder, ...Object.keys(wanted), ...Object.keys(live)])];
3026
+ for (const service of services) {
3027
+ const next = wanted[service];
3028
+ const current = live[service];
3029
+ const path = `environments.${env}.services.${service}`;
3030
+ if (next?.enabled && !current?.enabled) {
3031
+ add(
3032
+ difference(path, next, current ?? null, "different", "the repository enables a service Registry does not", desiredSource, observedSource, env, service),
3033
+ scopedAction("enable_service", path, env, service, current ?? null, next, "enable the repository-declared service")
3034
+ );
3035
+ continue;
3036
+ }
3037
+ if (next?.enabled && current?.enabled && next.config) {
3038
+ const projected = projectConfig(current.config, Object.keys(next.config));
3039
+ if (!same(next.config, projected)) {
3040
+ add(
3041
+ difference(`${path}.config`, next.config, projected, "different", "repository-managed service settings differ", desiredSource, observedSource, env, service),
3042
+ scopedAction(
3043
+ "configure_service",
3044
+ `${path}.config`,
3045
+ env,
3046
+ service,
3047
+ projected,
3048
+ next.config,
3049
+ "apply the repository-managed service settings"
3050
+ )
3051
+ );
3052
+ }
3053
+ }
3054
+ }
3055
+ for (const service of [...services].reverse()) {
3056
+ const next = wanted[service];
3057
+ const current = live[service];
3058
+ if (next?.enabled || !current?.enabled) continue;
3059
+ const path = `environments.${env}.services.${service}`;
3060
+ add(
3061
+ difference(path, null, { enabled: true }, "different", "Registry enables a service absent from repository intent", desiredSource, observedSource, env, service),
3062
+ scopedAction(
3063
+ "disable_service",
3064
+ path,
3065
+ env,
3066
+ service,
3067
+ { enabled: true },
3068
+ { enabled: false },
3069
+ "disablement is destructive to availability and requires an explicit Studio review",
3070
+ true
3071
+ )
3072
+ );
3073
+ }
3074
+ }
3075
+ function compareOptionalEnvironmentState(env, desired, observed, desiredSource, observedSource, add) {
3076
+ const wanted = desired.environments[env];
3077
+ const liveAuth = observed?.auth[env] ?? null;
3078
+ if (wanted?.auth) {
3079
+ const projected = liveAuth ? projectConfig(liveAuth, Object.keys(wanted.auth)) : null;
3080
+ if (!same(wanted.auth, projected)) {
3081
+ const path = `environments.${env}.auth`;
3082
+ add(
3083
+ difference(path, wanted.auth, projected, "different", "explicit repository auth settings differ", desiredSource, observedSource, env),
3084
+ scopedAction("set_auth", path, env, void 0, projected, wanted.auth, "apply explicit repository auth settings")
3085
+ );
3086
+ }
3087
+ } else if (liveAuth) {
3088
+ add(difference(
3089
+ `environments.${env}.auth`,
3090
+ null,
3091
+ projectConfig(liveAuth, ["publishableKey", "audience", "mode"]),
3092
+ "unmanaged",
3093
+ "live auth exists but this project config does not manage it",
3094
+ desiredSource,
3095
+ observedSource,
3096
+ env
3097
+ ));
3098
+ }
3099
+ const managesLink = wanted && Object.hasOwn(wanted, "link");
3100
+ const liveLink = observed?.links[env] ?? null;
3101
+ if (managesLink && wanted.link !== liveLink) {
3102
+ const path = `environments.${env}.link`;
3103
+ add(
3104
+ difference(path, wanted.link ?? null, liveLink, "different", "explicit repository deployment link differs", desiredSource, observedSource, env),
3105
+ scopedAction("set_link", path, env, void 0, liveLink, wanted.link ?? null, "apply the repository deployment link")
3106
+ );
3107
+ } else if (!managesLink && liveLink) {
3108
+ add(difference(
3109
+ `environments.${env}.link`,
3110
+ null,
3111
+ liveLink,
3112
+ "unmanaged",
3113
+ "a live deployment link exists but this project config does not manage it",
3114
+ desiredSource,
3115
+ observedSource,
3116
+ env
3117
+ ));
3118
+ }
3119
+ }
3120
+
3121
+ // src/config-reconcile-command.ts
3122
+ async function configDiff(options) {
3123
+ const reconciliation = await inspectConfig(options);
3124
+ const { actions: _actions, ...read3 } = reconciliation;
3125
+ const document2 = {
3126
+ schemaVersion: "odla.config-diff/v1",
3127
+ ...read3,
3128
+ nextActions: diffNextActions(reconciliation, options.configPath)
3129
+ };
3130
+ printDiff(document2, options);
3131
+ return document2;
3132
+ }
3133
+ async function configPlan(options) {
3134
+ const reconciliation = await inspectConfig(options);
3135
+ const planDigest = configDigest({
3136
+ schemaVersion: "odla.config-plan/v1",
3137
+ desiredRevision: reconciliation.desiredRevision,
3138
+ observedRevision: reconciliation.observedRevision,
3139
+ actions: reconciliation.actions
3140
+ });
3141
+ const document2 = {
3142
+ schemaVersion: "odla.config-plan/v1",
3143
+ ...reconciliation,
3144
+ planDigest,
3145
+ apply: {
3146
+ supported: false,
3147
+ reason: "conditional, resumable config apply is not available yet; use the exact reviewed commands below"
3148
+ },
3149
+ nextActions: planNextActions(reconciliation, options.configPath)
3150
+ };
3151
+ printPlan2(document2, options);
3152
+ return document2;
3153
+ }
3154
+ async function inspectConfig(options) {
3155
+ const out = options.stdout ?? console;
3156
+ const cfg = await loadProjectConfig(options.configPath);
3157
+ const doFetch = options.fetch ?? fetch;
3158
+ const token = await resolveAdminPlatformToken({
3159
+ platform: cfg.platformUrl,
3160
+ scope: "app:config:read",
3161
+ token: options.token,
3162
+ tokenFile: (0, import_node_path7.join)(cfg.rootDir, ".odla", "admin-token.local.json"),
3163
+ rootDir: cfg.rootDir,
3164
+ email: options.email,
3165
+ open: options.open,
3166
+ fetch: doFetch,
3167
+ stdout: out,
3168
+ openApprovalUrl: options.openApprovalUrl,
3169
+ label: `odla CLI (${cfg.app.id} config read)`
3170
+ });
3171
+ const client = (0, import_apps6.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
3172
+ const observed = await client.resolveApp(cfg.app.id);
3173
+ return reconcileConfig({
3174
+ desired: desiredRegistryState(cfg),
3175
+ observed,
3176
+ configPath: cfg.configPath,
3177
+ platformUrl: cfg.platformUrl,
3178
+ generatedAt: (options.now?.() ?? /* @__PURE__ */ new Date()).toISOString()
3179
+ });
3180
+ }
3181
+ function printDiff(document2, options) {
3182
+ const out = options.stdout ?? console;
3183
+ if (options.json) {
3184
+ out.log(JSON.stringify(document2, null, 2));
3185
+ return;
3186
+ }
3187
+ printHeader(out, "diff", document2);
3188
+ printDifferences(out, document2);
3189
+ printNext(out, document2.nextActions);
3190
+ }
3191
+ function printPlan2(document2, options) {
3192
+ const out = options.stdout ?? console;
3193
+ if (options.json) {
3194
+ out.log(JSON.stringify(document2, null, 2));
3195
+ return;
3196
+ }
3197
+ printHeader(out, "plan", document2);
3198
+ for (const entry of document2.actions) {
3199
+ const scope = [entry.env, entry.service].filter(Boolean).join("/");
3200
+ out.log(
3201
+ ` ${entry.id} ${entry.kind}${scope ? ` (${scope})` : ""} ${entry.risk}${entry.requiresApproval ? ", approval required" : ""}`
3202
+ );
3203
+ out.log(` ${entry.reason}`);
3204
+ }
3205
+ if (!document2.actions.length) out.log(" no managed changes");
3206
+ out.log(`plan digest: ${document2.planDigest}`);
3207
+ out.log(`apply: unsupported \u2014 ${document2.apply.reason}`);
3208
+ printNext(out, document2.nextActions);
3209
+ }
3210
+ function printHeader(out, kind, document2) {
3211
+ out.log(`config ${kind}: ${document2.scope.appId} \u2014 ${document2.status}`);
3212
+ out.log(`platform: ${document2.scope.platformUrl}`);
3213
+ out.log(`desired: ${document2.desiredRevision}`);
3214
+ out.log(`observed: ${document2.observedRevision ?? "absent"}`);
3215
+ out.log(
3216
+ `summary: ${document2.summary.differences} different, ${document2.summary.unmanaged} unmanaged, ${document2.summary.actions} planned actions`
3217
+ );
3218
+ }
3219
+ function printDifferences(out, document2) {
3220
+ for (const entry of document2.differences) {
3221
+ out.log(` ${entry.status} ${entry.path} ${entry.reason}`);
3222
+ }
3223
+ if (!document2.differences.length) out.log(" managed Registry configuration is in sync");
3224
+ }
3225
+ function printNext(out, next) {
3226
+ if (!next.length) return;
3227
+ out.log("next:");
3228
+ for (const item of next) out.log(` ${item.command}
3229
+ ${item.description}`);
3230
+ }
3231
+ function diffNextActions(reconciliation, configPath) {
3232
+ if (reconciliation.status === "in_sync") {
3233
+ return [{
3234
+ code: "verify_runtime",
3235
+ command: `odla-ai smoke --config ${quoteArg2(configPath)}`,
3236
+ description: "Registry intent matches; verify the service-owned data planes separately."
3237
+ }];
3238
+ }
3239
+ return [{
3240
+ code: "freeze_plan",
3241
+ command: `odla-ai config plan --config ${quoteArg2(configPath)} --json`,
3242
+ description: "Freeze the current desired and observed revisions into a reviewable action plan."
3243
+ }];
3244
+ }
3245
+ function planNextActions(reconciliation, configPath) {
3246
+ const next = [];
3247
+ if (reconciliation.actions.some((action2) => action2.applySupport === "provision")) {
3248
+ next.push({
3249
+ code: "review_provision",
3250
+ command: `odla-ai provision --config ${quoteArg2(configPath)} --dry-run`,
3251
+ description: "Review the existing provisioner's service/auth/link work before applying it."
3252
+ });
3253
+ }
3254
+ for (const action2 of reconciliation.actions.filter((entry) => entry.command)) {
3255
+ if (!next.some((entry) => entry.command === action2.command)) {
3256
+ next.push({ code: action2.kind, command: action2.command, description: action2.reason });
3257
+ }
3258
+ }
3259
+ if (reconciliation.actions.some((action2) => action2.applySupport === "studio")) {
3260
+ const { appId, platformUrl } = reconciliation.scope;
3261
+ next.push({
3262
+ code: "review_destructive",
3263
+ command: `${platformUrl}/studio/apps/${encodeURIComponent(appId)}/settings`,
3264
+ description: "Review service disablement in the owning Studio scope; this plan will not apply it."
3265
+ });
3266
+ }
3267
+ if (!reconciliation.actions.length && reconciliation.summary.unmanaged) {
3268
+ next.push({
3269
+ code: "declare_or_accept_runtime_state",
3270
+ command: `${reconciliation.scope.platformUrl}/studio/apps/${encodeURIComponent(reconciliation.scope.appId)}/settings`,
3271
+ description: "Declare the live value in project config or keep it as an explicit runtime-managed setting."
3272
+ });
3273
+ }
3274
+ return next;
3275
+ }
3276
+ function quoteArg2(value2) {
3277
+ return `'${value2.replace(/'/g, `'\\''`)}'`;
3278
+ }
3279
+
2742
3280
  // src/doctor-checks.ts
2743
3281
  var import_node_child_process3 = require("child_process");
2744
3282
  var import_node_fs10 = require("fs");
2745
- var import_node_path8 = require("path");
3283
+ var import_node_path9 = require("path");
2746
3284
 
2747
3285
  // src/wrangler.ts
2748
3286
  var import_node_child_process2 = require("child_process");
2749
3287
  var import_node_fs9 = require("fs");
2750
- var import_node_path7 = require("path");
3288
+ var import_node_path8 = require("path");
2751
3289
  var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
2752
3290
  const child = (0, import_node_child_process2.spawn)(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
2753
3291
  let stdout = "";
@@ -2761,7 +3299,7 @@ var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) =>
2761
3299
  var WRANGLER_CONFIG_FILES = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"];
2762
3300
  function findWranglerConfig(rootDir) {
2763
3301
  for (const name of WRANGLER_CONFIG_FILES) {
2764
- const path = (0, import_node_path7.join)(rootDir, name);
3302
+ const path = (0, import_node_path8.join)(rootDir, name);
2765
3303
  if ((0, import_node_fs9.existsSync)(path)) return path;
2766
3304
  }
2767
3305
  return null;
@@ -2827,11 +3365,11 @@ function lintRules(rules, entities, publicRead) {
2827
3365
  const warnings = [];
2828
3366
  if (!rules) return warnings;
2829
3367
  for (const [ns, actions] of Object.entries(rules)) {
2830
- for (const [action, expr] of Object.entries(actions)) {
3368
+ for (const [action2, expr] of Object.entries(actions)) {
2831
3369
  if (typeof expr !== "string" || expr.trim() !== "true") continue;
2832
- if (action === "view" && publicRead.includes(ns)) continue;
3370
+ if (action2 === "view" && publicRead.includes(ns)) continue;
2833
3371
  warnings.push(
2834
- 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`
3372
+ 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`
2835
3373
  );
2836
3374
  }
2837
3375
  }
@@ -2867,17 +3405,17 @@ function wranglerWarnings(rootDir) {
2867
3405
  for (const { label, block } of blocks) {
2868
3406
  const assets = block.assets;
2869
3407
  if (assets?.directory) {
2870
- const dir = (0, import_node_path8.resolve)(rootDir, assets.directory);
2871
- if (dir === (0, import_node_path8.resolve)(rootDir)) {
3408
+ const dir = (0, import_node_path9.resolve)(rootDir, assets.directory);
3409
+ if (dir === (0, import_node_path9.resolve)(rootDir)) {
2872
3410
  warnings.push(`${label}assets.directory is the project root \u2014 point it at a dedicated build dir (wrangler dev fails with "spawn EBADF")`);
2873
- } else if ((0, import_node_fs10.existsSync)((0, import_node_path8.join)(dir, "node_modules"))) {
3411
+ } else if ((0, import_node_fs10.existsSync)((0, import_node_path9.join)(dir, "node_modules"))) {
2874
3412
  warnings.push(`${label}assets.directory contains node_modules \u2014 wrangler dev's watcher will exhaust file descriptors`);
2875
3413
  }
2876
3414
  }
2877
3415
  const vars = block.vars;
2878
3416
  if (vars && typeof vars === "object") {
2879
- for (const [name, value] of Object.entries(vars)) {
2880
- if (name === "ODLA_API_KEY" || name === "ODLA_O11Y_TOKEN" || typeof value === "string" && looksSecret(value)) {
3417
+ for (const [name, value2] of Object.entries(vars)) {
3418
+ if (name === "ODLA_API_KEY" || name === "ODLA_O11Y_TOKEN" || typeof value2 === "string" && looksSecret(value2)) {
2881
3419
  warnings.push(`wrangler ${label}vars.${name} looks like a secret \u2014 use "odla-ai secrets push" / wrangler secret put, never vars`);
2882
3420
  }
2883
3421
  }
@@ -2905,7 +3443,7 @@ function o11yProjectWarnings(rootDir) {
2905
3443
  warnings.push("cannot verify o11y Worker instrumentation \u2014 add a parseable wrangler.jsonc/json config");
2906
3444
  return warnings;
2907
3445
  }
2908
- const main = typeof config.main === "string" ? (0, import_node_path8.resolve)(rootDir, config.main) : null;
3446
+ const main = typeof config.main === "string" ? (0, import_node_path9.resolve)(rootDir, config.main) : null;
2909
3447
  if (!main || !(0, import_node_fs10.existsSync)(main)) {
2910
3448
  warnings.push("cannot verify o11y Worker instrumentation \u2014 wrangler main is missing or unreadable");
2911
3449
  } else {
@@ -2935,7 +3473,7 @@ function calendarProjectWarnings(rootDir) {
2935
3473
  }
2936
3474
  function readPackageJson(rootDir) {
2937
3475
  try {
2938
- return JSON.parse((0, import_node_fs10.readFileSync)((0, import_node_path8.join)(rootDir, "package.json"), "utf8"));
3476
+ return JSON.parse((0, import_node_fs10.readFileSync)((0, import_node_path9.join)(rootDir, "package.json"), "utf8"));
2939
3477
  } catch {
2940
3478
  return null;
2941
3479
  }
@@ -2991,8 +3529,8 @@ function integrationWarnings(integrations, schema, rules) {
2991
3529
  warnings.push(`integration "${integration.id}" has no rules for "${ns}"`);
2992
3530
  continue;
2993
3531
  }
2994
- for (const action of ["view", "create", "update", "delete"]) {
2995
- if (rule[action] === void 0) warnings.push(`integration "${integration.id}" rule "${ns}.${action}" is missing`);
3532
+ for (const action2 of ["view", "create", "update", "delete"]) {
3533
+ if (rule[action2] === void 0) warnings.push(`integration "${integration.id}" rule "${ns}.${action2}" is missing`);
2996
3534
  }
2997
3535
  }
2998
3536
  for (const seed of integration.seeds ?? []) {
@@ -3045,27 +3583,27 @@ function isUniqueAttr(schema, ns, attr) {
3045
3583
  const definition = entity.attrs[attr];
3046
3584
  return isRecord6(definition) && definition.unique === true;
3047
3585
  }
3048
- function normalizeSchema(value) {
3049
- if (value === void 0 || value === null) return { entities: {}, links: {} };
3050
- if (!isRecord6(value) || !isRecord6(value.entities)) {
3586
+ function normalizeSchema(value2) {
3587
+ if (value2 === void 0 || value2 === null) return { entities: {}, links: {} };
3588
+ if (!isRecord6(value2) || !isRecord6(value2.entities)) {
3051
3589
  throw new Error("db schema must be a serialized schema object with an entities map");
3052
3590
  }
3053
- if (value.links !== void 0 && !isRecord6(value.links)) throw new Error("db schema links must be an object");
3591
+ if (value2.links !== void 0 && !isRecord6(value2.links)) throw new Error("db schema links must be an object");
3054
3592
  return {
3055
- entities: { ...value.entities },
3056
- links: { ...value.links ?? {} }
3593
+ entities: { ...value2.entities },
3594
+ links: { ...value2.links ?? {} }
3057
3595
  };
3058
3596
  }
3059
3597
  function mergeMap(target, fragment, label) {
3060
- for (const [name, value] of Object.entries(fragment)) {
3061
- if (Object.hasOwn(target, name) && !(0, import_node_util.isDeepStrictEqual)(target[name], value)) {
3598
+ for (const [name, value2] of Object.entries(fragment)) {
3599
+ if (Object.hasOwn(target, name) && !(0, import_node_util.isDeepStrictEqual)(target[name], value2)) {
3062
3600
  throw new Error(`${label} "${name}" conflicts with an existing definition`);
3063
3601
  }
3064
- target[name] = value;
3602
+ target[name] = value2;
3065
3603
  }
3066
3604
  }
3067
- function isRecord6(value) {
3068
- return value !== null && typeof value === "object" && !Array.isArray(value);
3605
+ function isRecord6(value2) {
3606
+ return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
3069
3607
  }
3070
3608
 
3071
3609
  // src/doctor.ts
@@ -3106,9 +3644,9 @@ async function doctor(options) {
3106
3644
  warnings.push(...integrationWarnings(database.integrations, schema, rules));
3107
3645
  if (cfg.services.includes("ai") && !cfg.ai?.provider) warnings.push("ai service is enabled but ai.provider is not set");
3108
3646
  if (cfg.auth?.clerk) {
3109
- for (const [env, value] of Object.entries(cfg.auth.clerk)) {
3110
- if (typeof value === "string" && value.startsWith("$") && !process.env[value.slice(1)]) {
3111
- warnings.push(`auth.clerk.${env} references unset env var ${value}`);
3647
+ for (const [env, value2] of Object.entries(cfg.auth.clerk)) {
3648
+ if (typeof value2 === "string" && value2.startsWith("$") && !process.env[value2.slice(1)]) {
3649
+ warnings.push(`auth.clerk.${env} references unset env var ${value2}`);
3112
3650
  }
3113
3651
  }
3114
3652
  }
@@ -3150,10 +3688,10 @@ function harnessList(parsed) {
3150
3688
  ];
3151
3689
  return selected.length ? selected : ["all"];
3152
3690
  }
3153
- function harnessOption(value, flag) {
3154
- if (value === void 0) return [];
3155
- if (typeof value === "boolean") throw new Error(`${flag} requires a harness name`);
3156
- const raw = Array.isArray(value) ? value : [value];
3691
+ function harnessOption(value2, flag) {
3692
+ if (value2 === void 0) return [];
3693
+ if (typeof value2 === "boolean") throw new Error(`${flag} requires a harness name`);
3694
+ const raw = Array.isArray(value2) ? value2 : [value2];
3157
3695
  const parts = raw.flatMap((item) => item.split(","));
3158
3696
  if (parts.some((item) => !item.trim())) {
3159
3697
  throw new Error(`${flag} requires a harness name`);
@@ -3163,12 +3701,12 @@ function harnessOption(value, flag) {
3163
3701
 
3164
3702
  // src/init.ts
3165
3703
  var import_node_fs11 = require("fs");
3166
- var import_node_path9 = require("path");
3167
- var import_apps3 = require("@odla-ai/apps");
3704
+ var import_node_path10 = require("path");
3705
+ var import_apps7 = require("@odla-ai/apps");
3168
3706
  function initProject(options) {
3169
3707
  const out = options.stdout ?? console;
3170
- const rootDir = (0, import_node_path9.resolve)(options.rootDir ?? process.cwd());
3171
- const configPath = (0, import_node_path9.resolve)(rootDir, options.configPath ?? "odla.config.mjs");
3708
+ const rootDir = (0, import_node_path10.resolve)(options.rootDir ?? process.cwd());
3709
+ const configPath = (0, import_node_path10.resolve)(rootDir, options.configPath ?? "odla.config.mjs");
3172
3710
  if ((0, import_node_fs11.existsSync)(configPath) && !options.force) {
3173
3711
  throw new Error(`${configPath} already exists. Pass --force to overwrite.`);
3174
3712
  }
@@ -3178,19 +3716,19 @@ function initProject(options) {
3178
3716
  const envs = options.envs?.length ? options.envs : ["dev"];
3179
3717
  const services = options.services?.length ? options.services : ["db", "ai"];
3180
3718
  for (const service of services) {
3181
- const definition = (0, import_apps3.appServiceDefinition)(service);
3182
- if (!definition) throw new Error(`--services contains unknown service "${service}" (known: ${(0, import_apps3.appServiceIds)().join(", ")})`);
3719
+ const definition = (0, import_apps7.appServiceDefinition)(service);
3720
+ if (!definition) throw new Error(`--services contains unknown service "${service}" (known: ${(0, import_apps7.appServiceIds)().join(", ")})`);
3183
3721
  for (const dependency of definition.requires) {
3184
3722
  if (!services.includes(dependency)) throw new Error(`--services ${service} requires ${dependency}`);
3185
3723
  }
3186
3724
  }
3187
3725
  const aiProvider = options.aiProvider ?? "anthropic";
3188
- (0, import_node_fs11.mkdirSync)((0, import_node_path9.dirname)(configPath), { recursive: true });
3189
- (0, import_node_fs11.mkdirSync)((0, import_node_path9.resolve)(rootDir, "src/odla"), { recursive: true });
3190
- (0, import_node_fs11.mkdirSync)((0, import_node_path9.resolve)(rootDir, ".odla"), { recursive: true });
3726
+ (0, import_node_fs11.mkdirSync)((0, import_node_path10.dirname)(configPath), { recursive: true });
3727
+ (0, import_node_fs11.mkdirSync)((0, import_node_path10.resolve)(rootDir, "src/odla"), { recursive: true });
3728
+ (0, import_node_fs11.mkdirSync)((0, import_node_path10.resolve)(rootDir, ".odla"), { recursive: true });
3191
3729
  (0, import_node_fs11.writeFileSync)(configPath, configTemplate({ appId: options.appId, name: options.name, envs, services, aiProvider }));
3192
- writeIfMissing((0, import_node_path9.resolve)(rootDir, "src/odla/schema.mjs"), schemaTemplate());
3193
- writeIfMissing((0, import_node_path9.resolve)(rootDir, "src/odla/rules.mjs"), rulesTemplate());
3730
+ writeIfMissing((0, import_node_path10.resolve)(rootDir, "src/odla/schema.mjs"), schemaTemplate());
3731
+ writeIfMissing((0, import_node_path10.resolve)(rootDir, "src/odla/rules.mjs"), rulesTemplate());
3194
3732
  ensureGitignore(rootDir);
3195
3733
  out.log(`created ${relativeDisplay(configPath, rootDir)}`);
3196
3734
  out.log("created src/odla/schema.mjs and src/odla/rules.mjs");
@@ -3372,8 +3910,8 @@ function assertWranglerConfig(cfg) {
3372
3910
  }
3373
3911
 
3374
3912
  // src/secrets-set.ts
3375
- var import_ai = require("@odla-ai/ai");
3376
- var import_apps4 = require("@odla-ai/apps");
3913
+ var import_ai2 = require("@odla-ai/ai");
3914
+ var import_apps8 = require("@odla-ai/apps");
3377
3915
  var PROD_ENV_NAMES2 = /* @__PURE__ */ new Set(["prod", "production"]);
3378
3916
  async function secretsSet(options) {
3379
3917
  const name = (options.name ?? "").trim();
@@ -3381,38 +3919,38 @@ async function secretsSet(options) {
3381
3919
  if (name.startsWith("$")) {
3382
3920
  throw new Error('"$"-prefixed vault names are platform-reserved; for the Clerk secret key use "odla-ai secrets set-clerk-key"');
3383
3921
  }
3384
- const { cfg, tenantId, value, doFetch, out } = await resolveVaultWrite(options);
3922
+ const { cfg, tenantId, value: value2, doFetch, out } = await resolveVaultWrite(options);
3385
3923
  const token = await getDeveloperToken(cfg, options, doFetch, out);
3386
3924
  try {
3387
- await (0, import_ai.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, name, value);
3925
+ await (0, import_ai2.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, name, value2);
3388
3926
  } catch (err) {
3389
- throw new Error(scrubValue(err instanceof Error ? err.message : String(err), value));
3927
+ throw new Error(scrubValue(err instanceof Error ? err.message : String(err), value2));
3390
3928
  }
3391
3929
  out.log(`${name} stored in the ${tenantId} vault (write-only; the value was never echoed and cannot be read back here)`);
3392
3930
  }
3393
3931
  async function secretsSetClerkKey(options) {
3394
- const { cfg, tenantId, value, doFetch, out } = await resolveVaultWrite(options);
3395
- if (!value.startsWith("sk_")) throw new Error("the Clerk secret key must start with sk_ (sk_test_\u2026 or sk_live_\u2026)");
3396
- if (value.startsWith("sk_test_") && PROD_ENV_NAMES2.has(options.env)) {
3932
+ const { cfg, tenantId, value: value2, doFetch, out } = await resolveVaultWrite(options);
3933
+ if (!value2.startsWith("sk_")) throw new Error("the Clerk secret key must start with sk_ (sk_test_\u2026 or sk_live_\u2026)");
3934
+ if (value2.startsWith("sk_test_") && PROD_ENV_NAMES2.has(options.env)) {
3397
3935
  throw new Error(`refusing to store an sk_test_ Clerk key for "${options.env}" \u2014 use the production instance's sk_live_ key`);
3398
3936
  }
3399
- if (value.startsWith("sk_live_") && !PROD_ENV_NAMES2.has(options.env) && !options.yes) {
3937
+ if (value2.startsWith("sk_live_") && !PROD_ENV_NAMES2.has(options.env) && !options.yes) {
3400
3938
  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)`);
3401
3939
  }
3402
3940
  const token = await getDeveloperToken(cfg, options, doFetch, out);
3403
3941
  const res = await doFetch(`${cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenantId)}/clerk-secret`, {
3404
3942
  method: "POST",
3405
3943
  headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
3406
- body: JSON.stringify({ value })
3944
+ body: JSON.stringify({ value: value2 })
3407
3945
  });
3408
3946
  if (!res.ok) {
3409
- const text = scrubValue((await res.text().catch(() => "")).slice(0, 300), value);
3947
+ const text = scrubValue((await res.text().catch(() => "")).slice(0, 300), value2);
3410
3948
  throw new Error(`store Clerk secret key failed (${res.status}): ${text || "request failed"}`);
3411
3949
  }
3412
3950
  out.log(`Clerk secret key stored for ${tenantId} ($clerk_secret, reserved + write-only; the value was never echoed)`);
3413
3951
  }
3414
- function scrubValue(text, value) {
3415
- return redactSecrets(text).split(value).join("[value redacted]");
3952
+ function scrubValue(text, value2) {
3953
+ return redactSecrets(text).split(value2).join("[value redacted]");
3416
3954
  }
3417
3955
  async function resolveVaultWrite(options) {
3418
3956
  const out = options.stdout ?? console;
@@ -3424,14 +3962,14 @@ async function resolveVaultWrite(options) {
3424
3962
  if (PROD_ENV_NAMES2.has(options.env) && !options.yes) {
3425
3963
  throw new Error(`refusing to store a secret for "${options.env}" without --yes`);
3426
3964
  }
3427
- const value = await secretInputValue(options, "secret");
3428
- return { cfg, tenantId: (0, import_apps4.tenantIdFor)(cfg.app.id, options.env), value, doFetch, out };
3965
+ const value2 = await secretInputValue(options, "secret");
3966
+ return { cfg, tenantId: (0, import_apps8.tenantIdFor)(cfg.app.id, options.env), value: value2, doFetch, out };
3429
3967
  }
3430
3968
 
3431
3969
  // src/skill.ts
3432
3970
  var import_node_fs12 = require("fs");
3433
3971
  var import_node_os2 = require("os");
3434
- var import_node_path10 = require("path");
3972
+ var import_node_path11 = require("path");
3435
3973
  var import_node_url2 = require("url");
3436
3974
 
3437
3975
  // src/skill-adapters.ts
@@ -3510,8 +4048,8 @@ function installSkill(options = {}) {
3510
4048
  const files = listFiles(sourceDir);
3511
4049
  if (files.length === 0) throw new Error(`no bundled skills found at ${sourceDir}`);
3512
4050
  const harnesses = normalizeHarnesses(options.harnesses, options.global === true);
3513
- const root = (0, import_node_path10.resolve)(options.dir ?? process.cwd());
3514
- const home = (0, import_node_path10.resolve)(options.homeDir ?? (0, import_node_os2.homedir)());
4051
+ const root = (0, import_node_path11.resolve)(options.dir ?? process.cwd());
4052
+ const home = (0, import_node_path11.resolve)(options.homeDir ?? (0, import_node_os2.homedir)());
3515
4053
  const plans = /* @__PURE__ */ new Map();
3516
4054
  const targets = /* @__PURE__ */ new Map();
3517
4055
  const rememberTarget = (harness, target) => {
@@ -3525,48 +4063,48 @@ function installSkill(options = {}) {
3525
4063
  plans.set(target, { target, content: content2, boundary, managedMerge });
3526
4064
  };
3527
4065
  const planSkillTree = (targetDir2, boundary = root) => {
3528
- 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);
4066
+ 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);
3529
4067
  };
3530
4068
  let targetDir;
3531
4069
  if (options.global) {
3532
- const claudeRoot = (0, import_node_path10.join)(home, ".claude", "skills");
3533
- const codexRoot = (0, import_node_path10.resolve)(options.codexHomeDir ?? process.env.CODEX_HOME ?? (0, import_node_path10.join)(home, ".codex"), "skills");
4070
+ const claudeRoot = (0, import_node_path11.join)(home, ".claude", "skills");
4071
+ const codexRoot = (0, import_node_path11.resolve)(options.codexHomeDir ?? process.env.CODEX_HOME ?? (0, import_node_path11.join)(home, ".codex"), "skills");
3534
4072
  targetDir = harnesses[0] === "codex" ? codexRoot : claudeRoot;
3535
4073
  for (const harness of harnesses) {
3536
4074
  const skillRoot = harness === "claude" ? claudeRoot : codexRoot;
3537
- planSkillTree(skillRoot, harness === "claude" ? home : (0, import_node_path10.dirname)((0, import_node_path10.dirname)(codexRoot)));
4075
+ planSkillTree(skillRoot, harness === "claude" ? home : (0, import_node_path11.dirname)((0, import_node_path11.dirname)(codexRoot)));
3538
4076
  rememberTarget(harness, skillRoot);
3539
4077
  }
3540
4078
  } else {
3541
- const sharedRoot = (0, import_node_path10.join)(root, ".agents", "skills");
4079
+ const sharedRoot = (0, import_node_path11.join)(root, ".agents", "skills");
3542
4080
  planSkillTree(sharedRoot);
3543
- const claudeRoot = (0, import_node_path10.join)(root, ".claude", "skills");
4081
+ const claudeRoot = (0, import_node_path11.join)(root, ".claude", "skills");
3544
4082
  targetDir = harnesses.includes("claude") ? claudeRoot : sharedRoot;
3545
4083
  for (const harness of harnesses) rememberTarget(harness, sharedRoot);
3546
4084
  if (harnesses.includes("claude")) {
3547
4085
  for (const skill of skillNames(files)) {
3548
- const canonical = (0, import_node_fs12.readFileSync)((0, import_node_path10.join)(sourceDir, skill, "SKILL.md"), "utf8");
3549
- plan((0, import_node_path10.join)(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical));
4086
+ const canonical = (0, import_node_fs12.readFileSync)((0, import_node_path11.join)(sourceDir, skill, "SKILL.md"), "utf8");
4087
+ plan((0, import_node_path11.join)(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical));
3550
4088
  }
3551
4089
  rememberTarget("claude", claudeRoot);
3552
4090
  }
3553
4091
  if (harnesses.includes("cursor")) {
3554
- const cursorRule = (0, import_node_path10.join)(root, ".cursor", "rules", "odla.mdc");
4092
+ const cursorRule = (0, import_node_path11.join)(root, ".cursor", "rules", "odla.mdc");
3555
4093
  plan(cursorRule, CURSOR_RULE);
3556
4094
  rememberTarget("cursor", cursorRule);
3557
4095
  }
3558
4096
  if (harnesses.includes("agents")) {
3559
- const agentsFile = (0, import_node_path10.join)(root, "AGENTS.md");
4097
+ const agentsFile = (0, import_node_path11.join)(root, "AGENTS.md");
3560
4098
  plan(agentsFile, managedFileContent(agentsFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
3561
4099
  rememberTarget("agents", agentsFile);
3562
4100
  }
3563
4101
  if (harnesses.includes("copilot")) {
3564
- const copilotFile = (0, import_node_path10.join)(root, ".github", "copilot-instructions.md");
4102
+ const copilotFile = (0, import_node_path11.join)(root, ".github", "copilot-instructions.md");
3565
4103
  plan(copilotFile, managedFileContent(copilotFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
3566
4104
  rememberTarget("copilot", copilotFile);
3567
4105
  }
3568
4106
  if (harnesses.includes("gemini")) {
3569
- const geminiFile = (0, import_node_path10.join)(root, "GEMINI.md");
4107
+ const geminiFile = (0, import_node_path11.join)(root, "GEMINI.md");
3570
4108
  plan(geminiFile, managedFileContent(geminiFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
3571
4109
  rememberTarget("gemini", geminiFile);
3572
4110
  }
@@ -3602,7 +4140,7 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
3602
4140
  }
3603
4141
  for (const file of plans.values()) {
3604
4142
  if (!(0, import_node_fs12.existsSync)(file.target) || (0, import_node_fs12.readFileSync)(file.target, "utf8") !== file.content) {
3605
- (0, import_node_fs12.mkdirSync)((0, import_node_path10.dirname)(file.target), { recursive: true });
4143
+ (0, import_node_fs12.mkdirSync)((0, import_node_path11.dirname)(file.target), { recursive: true });
3606
4144
  (0, import_node_fs12.writeFileSync)(file.target, file.content);
3607
4145
  }
3608
4146
  }
@@ -3622,7 +4160,7 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
3622
4160
  };
3623
4161
  }
3624
4162
  function pathsUnder(root, paths) {
3625
- 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();
4163
+ 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();
3626
4164
  }
3627
4165
  function normalizeHarnesses(values, global) {
3628
4166
  const requested = values?.length ? values : ["claude"];
@@ -3667,13 +4205,13 @@ function managedFileContent(path, block, force, boundary) {
3667
4205
  return `${current.slice(0, startAt)}${block}${current.slice(afterEnd)}`;
3668
4206
  }
3669
4207
  function symlinkedComponent(boundary, target) {
3670
- const rel = (0, import_node_path10.relative)(boundary, target);
3671
- if (rel === ".." || rel.startsWith(`..${import_node_path10.sep}`) || (0, import_node_path10.isAbsolute)(rel)) {
4208
+ const rel = (0, import_node_path11.relative)(boundary, target);
4209
+ if (rel === ".." || rel.startsWith(`..${import_node_path11.sep}`) || (0, import_node_path11.isAbsolute)(rel)) {
3672
4210
  throw new Error(`agent setup target escapes its install root: ${target}`);
3673
4211
  }
3674
4212
  let current = boundary;
3675
- for (const part of rel.split(import_node_path10.sep).filter(Boolean)) {
3676
- current = (0, import_node_path10.join)(current, part);
4213
+ for (const part of rel.split(import_node_path11.sep).filter(Boolean)) {
4214
+ current = (0, import_node_path11.join)(current, part);
3677
4215
  try {
3678
4216
  if ((0, import_node_fs12.lstatSync)(current).isSymbolicLink()) return current;
3679
4217
  } catch (error) {
@@ -3690,9 +4228,9 @@ function listFiles(dir) {
3690
4228
  const results = [];
3691
4229
  const walk = (current) => {
3692
4230
  for (const entry of (0, import_node_fs12.readdirSync)(current, { withFileTypes: true })) {
3693
- const path = (0, import_node_path10.join)(current, entry.name);
4231
+ const path = (0, import_node_path11.join)(current, entry.name);
3694
4232
  if (entry.isDirectory()) walk(path);
3695
- else results.push((0, import_node_path10.relative)(dir, path));
4233
+ else results.push((0, import_node_path11.relative)(dir, path));
3696
4234
  }
3697
4235
  };
3698
4236
  walk(dir);
@@ -3768,7 +4306,7 @@ async function smoke(options) {
3768
4306
  out.log(` schema: ${liveEntities.length} entities`);
3769
4307
  const aggregateEntity = expectedEntities[0] ?? liveEntities[0];
3770
4308
  if (aggregateEntity) {
3771
- const aggregate = await postJson(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(entry.tenantId)}/aggregate`, entry.dbKey, {
4309
+ const aggregate = await postJson2(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(entry.tenantId)}/aggregate`, entry.dbKey, {
3772
4310
  ns: aggregateEntity,
3773
4311
  aggregate: { count: true }
3774
4312
  });
@@ -3812,16 +4350,16 @@ async function getJson(doFetch, url, bearer) {
3812
4350
  const res = await doFetch(url, {
3813
4351
  headers: bearer ? { authorization: `Bearer ${bearer}` } : void 0
3814
4352
  });
3815
- if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await safeText3(res)}`);
4353
+ if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await safeText4(res)}`);
3816
4354
  return res.json();
3817
4355
  }
3818
- async function postJson(doFetch, url, bearer, body) {
4356
+ async function postJson2(doFetch, url, bearer, body) {
3819
4357
  const res = await doFetch(url, {
3820
4358
  method: "POST",
3821
4359
  headers: { authorization: `Bearer ${bearer}`, "content-type": "application/json" },
3822
4360
  body: JSON.stringify(body)
3823
4361
  });
3824
- if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await safeText3(res)}`);
4362
+ if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await safeText4(res)}`);
3825
4363
  return res.json();
3826
4364
  }
3827
4365
  function publicConfigUrl(platformUrl, appId, env) {
@@ -3829,7 +4367,7 @@ function publicConfigUrl(platformUrl, appId, env) {
3829
4367
  url.searchParams.set("env", env);
3830
4368
  return url.toString();
3831
4369
  }
3832
- async function safeText3(res) {
4370
+ async function safeText4(res) {
3833
4371
  try {
3834
4372
  return redactSecrets((await res.text()).slice(0, 500));
3835
4373
  } catch {
@@ -3885,6 +4423,26 @@ async function secretsCommand(parsed, deps) {
3885
4423
  });
3886
4424
  }
3887
4425
  async function projectCommand(command, parsed, deps) {
4426
+ if (command === "config") {
4427
+ const sub = parsed.positionals[1];
4428
+ if (sub !== "diff" && sub !== "plan") {
4429
+ throw new Error(`unknown config subcommand "${sub ?? ""}". Try "odla-ai config diff --json".`);
4430
+ }
4431
+ assertArgs(parsed, ["config", "token", "email", "open", "json"], 2);
4432
+ const options = {
4433
+ configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
4434
+ token: stringOpt(parsed.options.token),
4435
+ email: stringOpt(parsed.options.email),
4436
+ open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
4437
+ json: parsed.options.json === true,
4438
+ fetch: deps.fetch,
4439
+ openApprovalUrl: deps.openUrl,
4440
+ stdout: deps.stdout
4441
+ };
4442
+ if (sub === "diff") await configDiff(options);
4443
+ else await configPlan(options);
4444
+ return true;
4445
+ }
3888
4446
  if (command === "init") {
3889
4447
  assertArgs(parsed, ["app-id", "name", "config", "env", "services", "ai-provider", "force"], 1);
3890
4448
  initProject({
@@ -3945,7 +4503,7 @@ async function projectCommand(command, parsed, deps) {
3945
4503
  // src/code-connect.ts
3946
4504
  var import_node_fs14 = require("fs");
3947
4505
  var import_node_os4 = require("os");
3948
- var import_node_path12 = require("path");
4506
+ var import_node_path13 = require("path");
3949
4507
 
3950
4508
  // ../harness/dist/chunk-QTUEF2HZ.js
3951
4509
  var HARNESS_PROTOCOL_VERSION = 1;
@@ -3955,24 +4513,24 @@ var CONTROL = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/;
3955
4513
  var HarnessProtocolError = class extends Error {
3956
4514
  name = "HarnessProtocolError";
3957
4515
  };
3958
- function record2(value) {
3959
- return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
4516
+ function record2(value2) {
4517
+ return value2 !== null && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
3960
4518
  }
3961
- function boundedText(value, label, max) {
3962
- if (typeof value !== "string" || !value || value.length > max || CONTROL.test(value)) {
4519
+ function boundedText(value2, label, max) {
4520
+ if (typeof value2 !== "string" || !value2 || value2.length > max || CONTROL.test(value2)) {
3963
4521
  throw new HarnessProtocolError(`${label} must be a non-empty string of at most ${max} characters`);
3964
4522
  }
3965
- return value;
4523
+ return value2;
3966
4524
  }
3967
4525
  function parseAgentOutput(line) {
3968
4526
  if (Buffer.byteLength(line, "utf8") > 1e6) throw new HarnessProtocolError("agent message exceeds 1 MB");
3969
- let value;
4527
+ let value2;
3970
4528
  try {
3971
- value = JSON.parse(line);
4529
+ value2 = JSON.parse(line);
3972
4530
  } catch {
3973
4531
  throw new HarnessProtocolError("agent emitted invalid JSON");
3974
4532
  }
3975
- const message2 = record2(value);
4533
+ const message2 = record2(value2);
3976
4534
  if (!message2 || message2.protocolVersion !== HARNESS_PROTOCOL_VERSION) {
3977
4535
  throw new HarnessProtocolError(`agent protocolVersion must be ${HARNESS_PROTOCOL_VERSION}`);
3978
4536
  }
@@ -4281,7 +4839,7 @@ async function gitOutput(cwd, args, maxBytes) {
4281
4839
  else stdout.push(chunk);
4282
4840
  });
4283
4841
  child.stderr.on("data", (chunk) => {
4284
- if (stderr.reduce((sum, value) => sum + value.byteLength, 0) < 16384) stderr.push(chunk);
4842
+ if (stderr.reduce((sum, value2) => sum + value2.byteLength, 0) < 16384) stderr.push(chunk);
4285
4843
  });
4286
4844
  const code = await new Promise((accept, reject) => {
4287
4845
  child.once("error", reject);
@@ -4302,7 +4860,7 @@ async function gitBlobs(cwd, entries, maxBytes) {
4302
4860
  else stdout.push(chunk);
4303
4861
  });
4304
4862
  child.stderr.on("data", (chunk) => {
4305
- if (stderr.reduce((sum, value) => sum + value.byteLength, 0) < 16384) stderr.push(chunk);
4863
+ if (stderr.reduce((sum, value2) => sum + value2.byteLength, 0) < 16384) stderr.push(chunk);
4306
4864
  });
4307
4865
  child.stdin.end(`${entries.map((entry) => entry.hash).join("\n")}
4308
4866
  `);
@@ -4340,8 +4898,8 @@ async function materializeGitTree(source, commitSha, options = {}) {
4340
4898
  const maxFiles = options.maxFiles ?? 2e4;
4341
4899
  const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
4342
4900
  const inventory = (await gitOutput(sourceDir, ["ls-tree", "-rz", commitSha], 16 * 1024 * 1024)).toString("utf8").split("\0").filter(Boolean);
4343
- const entries = inventory.flatMap((record7) => {
4344
- const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record7);
4901
+ const entries = inventory.flatMap((record8) => {
4902
+ const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record8);
4345
4903
  return match && allowedWorkspacePath(match[3]) ? [{ mode: match[1], hash: match[2], path: match[3] }] : [];
4346
4904
  });
4347
4905
  if (entries.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
@@ -4416,7 +4974,7 @@ async function gitSourceFiles(sourceDir, maxFiles, maxBytes) {
4416
4974
  else stdout.push(chunk);
4417
4975
  });
4418
4976
  child.stderr.on("data", (chunk) => {
4419
- if (stderr.reduce((sum, value) => sum + value.byteLength, 0) < 16384) stderr.push(chunk);
4977
+ if (stderr.reduce((sum, value2) => sum + value2.byteLength, 0) < 16384) stderr.push(chunk);
4420
4978
  });
4421
4979
  const code = await new Promise((accept, reject) => {
4422
4980
  child.once("error", reject);
@@ -4476,7 +5034,7 @@ async function captureGitDiff(root, maxBytes) {
4476
5034
  else stdout.push(chunk);
4477
5035
  });
4478
5036
  child.stderr.on("data", (chunk) => {
4479
- if (stderr.reduce((sum, value) => sum + value.byteLength, 0) < 16384) stderr.push(chunk);
5037
+ if (stderr.reduce((sum, value2) => sum + value2.byteLength, 0) < 16384) stderr.push(chunk);
4480
5038
  });
4481
5039
  const code = await new Promise((accept, reject) => {
4482
5040
  child.once("error", reject);
@@ -4563,34 +5121,34 @@ var CamelError = class extends Error {
4563
5121
  };
4564
5122
 
4565
5123
  // ../camel/dist/chunk-L5DYU2E2.js
4566
- function canonicalJson(value) {
4567
- return JSON.stringify(normalize(value));
5124
+ function canonicalJson2(value2) {
5125
+ return JSON.stringify(normalize(value2));
4568
5126
  }
4569
- async function sha256Hex(value) {
4570
- const bytes = typeof value === "string" ? new TextEncoder().encode(value) : value;
5127
+ async function sha256Hex(value2) {
5128
+ const bytes = typeof value2 === "string" ? new TextEncoder().encode(value2) : value2;
4571
5129
  const digest = await crypto.subtle.digest("SHA-256", Uint8Array.from(bytes).buffer);
4572
5130
  return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
4573
5131
  }
4574
- function utf8Length(value) {
4575
- if (typeof value === "string") return new TextEncoder().encode(value).byteLength;
4576
- if (value instanceof Uint8Array) return value.byteLength;
5132
+ function utf8Length(value2) {
5133
+ if (typeof value2 === "string") return new TextEncoder().encode(value2).byteLength;
5134
+ if (value2 instanceof Uint8Array) return value2.byteLength;
4577
5135
  try {
4578
- return new TextEncoder().encode(canonicalJson(value)).byteLength;
5136
+ return new TextEncoder().encode(canonicalJson2(value2)).byteLength;
4579
5137
  } catch {
4580
5138
  throw new CamelError("conversion_rejected", "Unsafe input could not be canonically measured.");
4581
5139
  }
4582
5140
  }
4583
- function normalize(value) {
4584
- if (value === null || typeof value === "string" || typeof value === "boolean") return value;
4585
- if (typeof value === "number") {
4586
- if (!Number.isFinite(value)) throw new CamelError("state_conflict", "Canonical JSON rejects non-finite numbers.");
4587
- return value;
5141
+ function normalize(value2) {
5142
+ if (value2 === null || typeof value2 === "string" || typeof value2 === "boolean") return value2;
5143
+ if (typeof value2 === "number") {
5144
+ if (!Number.isFinite(value2)) throw new CamelError("state_conflict", "Canonical JSON rejects non-finite numbers.");
5145
+ return value2;
4588
5146
  }
4589
- if (Array.isArray(value)) return value.map(normalize);
4590
- if (value instanceof Uint8Array) return { $bytes: [...value] };
4591
- if (typeof value === "object") {
4592
- const record7 = value;
4593
- return Object.fromEntries(Object.keys(record7).filter((key) => record7[key] !== void 0).sort().map((key) => [key, normalize(record7[key])]));
5147
+ if (Array.isArray(value2)) return value2.map(normalize);
5148
+ if (value2 instanceof Uint8Array) return { $bytes: [...value2] };
5149
+ if (typeof value2 === "object") {
5150
+ const record8 = value2;
5151
+ return Object.fromEntries(Object.keys(record8).filter((key) => record8[key] !== void 0).sort().map((key) => [key, normalize(record8[key])]));
4594
5152
  }
4595
5153
  throw new CamelError("state_conflict", "Canonical JSON rejects unsupported values.");
4596
5154
  }
@@ -4599,10 +5157,10 @@ function canRead(readers, readerId) {
4599
5157
  }
4600
5158
  function dependenciesOf(values, influence = "data") {
4601
5159
  const result = [];
4602
- for (const value of values) {
4603
- result.push(...value.label.dependencies);
4604
- for (const ref of value.label.provenance) {
4605
- result.push({ ref, influence, promptSafetyAtUse: value.label.promptSafety });
5160
+ for (const value2 of values) {
5161
+ result.push(...value2.label.dependencies);
5162
+ for (const ref of value2.label.provenance) {
5163
+ result.push({ ref, influence, promptSafetyAtUse: value2.label.promptSafety });
4606
5164
  }
4607
5165
  }
4608
5166
  const unique3 = /* @__PURE__ */ new Map();
@@ -4613,32 +5171,32 @@ function dependenciesOf(values, influence = "data") {
4613
5171
  // ../camel/dist/chunk-S7EVNA2U.js
4614
5172
  var camelValueBrand = /* @__PURE__ */ Symbol("@odla-ai/camel/value");
4615
5173
  var authenticCamelValues = /* @__PURE__ */ new WeakSet();
4616
- function isCamelValue(value) {
4617
- return typeof value === "object" && value !== null && authenticCamelValues.has(value);
5174
+ function isCamelValue(value2) {
5175
+ return typeof value2 === "object" && value2 !== null && authenticCamelValues.has(value2);
4618
5176
  }
4619
- function isSafe(value) {
4620
- return isCamelValue(value) && value.label.promptSafety === "safe";
5177
+ function isSafe(value2) {
5178
+ return isCamelValue(value2) && value2.label.promptSafety === "safe";
4621
5179
  }
4622
- function isUnsafe(value) {
4623
- return isCamelValue(value) && value.label.promptSafety === "unsafe";
5180
+ function isUnsafe(value2) {
5181
+ return isCamelValue(value2) && value2.label.promptSafety === "unsafe";
4624
5182
  }
4625
- function createSafeInternal(value, safeBasis, metadata2) {
4626
- return createValue(value, {
5183
+ function createSafeInternal(value2, safeBasis, metadata2) {
5184
+ return createValue(value2, {
4627
5185
  schemaVersion: 1,
4628
5186
  promptSafety: "safe",
4629
5187
  safeBasis,
4630
5188
  ...copyMetadata(metadata2)
4631
5189
  });
4632
5190
  }
4633
- function createUnsafeInternal(value, metadata2) {
4634
- return createValue(value, {
5191
+ function createUnsafeInternal(value2, metadata2) {
5192
+ return createValue(value2, {
4635
5193
  schemaVersion: 1,
4636
5194
  promptSafety: "unsafe",
4637
5195
  ...copyMetadata(metadata2)
4638
5196
  });
4639
5197
  }
4640
- function createValue(value, label) {
4641
- const result = { value, label: Object.freeze(label) };
5198
+ function createValue(value2, label) {
5199
+ const result = { value: value2, label: Object.freeze(label) };
4642
5200
  Object.defineProperty(result, camelValueBrand, { value: label.promptSafety, enumerable: false });
4643
5201
  authenticCamelValues.add(result);
4644
5202
  return Object.freeze(result);
@@ -4701,7 +5259,7 @@ async function digestCodeVerificationReceipt(fields) {
4701
5259
  changedTestsRequireReview: fields.changedTestsRequireReview,
4702
5260
  outcome: fields.outcome
4703
5261
  };
4704
- return `sha256:${await sha256Hex(canonicalJson(canonical))}`;
5262
+ return `sha256:${await sha256Hex(canonicalJson2(canonical))}`;
4705
5263
  }
4706
5264
  function validate(fields) {
4707
5265
  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) {
@@ -4739,13 +5297,13 @@ async function createCodePortableCheckpoint(input) {
4739
5297
  const state2 = normalizeState(input.state);
4740
5298
  validateBaseAndPatch(input.baseCommitSha, input.patch);
4741
5299
  const patchDigest = `sha256:${await sha256Hex(input.patch)}`;
4742
- const stateDigest = `sha256:${await sha256Hex(canonicalJson(state2))}`;
5300
+ const stateDigest = `sha256:${await sha256Hex(canonicalJson2(state2))}`;
4743
5301
  const fields = { schemaVersion: 1, baseCommitSha: input.baseCommitSha, patchDigest, state: state2, stateDigest };
4744
- const checkpointDigest = `sha256:${await sha256Hex(canonicalJson(fields))}`;
5302
+ const checkpointDigest = `sha256:${await sha256Hex(canonicalJson2(fields))}`;
4745
5303
  return freeze({ ...fields, patch: input.patch, checkpointDigest });
4746
5304
  }
4747
- async function verifyCodePortableCheckpoint(value) {
4748
- const input = object(value, "checkpoint");
5305
+ async function verifyCodePortableCheckpoint(value2) {
5306
+ const input = object(value2, "checkpoint");
4749
5307
  exact2(input, ["schemaVersion", "baseCommitSha", "patch", "patchDigest", "state", "stateDigest", "checkpointDigest"]);
4750
5308
  if (input.schemaVersion !== 1 || typeof input.baseCommitSha !== "string" || typeof input.patch !== "string" || typeof input.patchDigest !== "string" || typeof input.stateDigest !== "string" || typeof input.checkpointDigest !== "string") {
4751
5309
  throw invalid2("Portable checkpoint fields are malformed.");
@@ -4760,8 +5318,8 @@ async function verifyCodePortableCheckpoint(value) {
4760
5318
  }
4761
5319
  return created;
4762
5320
  }
4763
- function normalizeState(value) {
4764
- const state2 = object(value, "state");
5321
+ function normalizeState(value2) {
5322
+ const state2 = object(value2, "state");
4765
5323
  exact2(state2, [
4766
5324
  "planCursor",
4767
5325
  "conversationRefs",
@@ -4819,30 +5377,30 @@ function validateBaseAndPatch(baseCommitSha, patch2) {
4819
5377
  throw invalid2("Portable checkpoint base or patch is malformed or outside its bounds.");
4820
5378
  }
4821
5379
  }
4822
- function strings(value, name, pattern, maximum, sort) {
4823
- if (!Array.isArray(value) || value.length > maximum || value.some((item) => typeof item !== "string" || !pattern.test(item)) || new Set(value).size !== value.length) {
5380
+ function strings(value2, name, pattern, maximum, sort) {
5381
+ if (!Array.isArray(value2) || value2.length > maximum || value2.some((item) => typeof item !== "string" || !pattern.test(item)) || new Set(value2).size !== value2.length) {
4824
5382
  throw invalid2(`Portable checkpoint ${name} is malformed or outside its bounds.`);
4825
5383
  }
4826
- const result = [...value];
5384
+ const result = [...value2];
4827
5385
  return sort ? result.sort() : result;
4828
5386
  }
4829
- function object(value, name) {
4830
- if (!value || typeof value !== "object" || Array.isArray(value)) throw invalid2(`Portable ${name} must be an object.`);
4831
- return value;
5387
+ function object(value2, name) {
5388
+ if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) throw invalid2(`Portable ${name} must be an object.`);
5389
+ return value2;
4832
5390
  }
4833
- function exact2(value, keys) {
4834
- if (Object.keys(value).some((key) => !keys.includes(key)) || keys.some((key) => !(key in value))) {
5391
+ function exact2(value2, keys) {
5392
+ if (Object.keys(value2).some((key) => !keys.includes(key)) || keys.some((key) => !(key in value2))) {
4835
5393
  throw invalid2("Portable checkpoint contains missing or unsupported fields.");
4836
5394
  }
4837
5395
  }
4838
- function freeze(value) {
5396
+ function freeze(value2) {
4839
5397
  return Object.freeze({
4840
- ...value,
5398
+ ...value2,
4841
5399
  state: Object.freeze({
4842
- ...value.state,
4843
- conversationRefs: Object.freeze([...value.state.conversationRefs]),
4844
- completedEffects: Object.freeze(value.state.completedEffects.map((item) => Object.freeze({ ...item }))),
4845
- unresolvedApprovals: Object.freeze([...value.state.unresolvedApprovals])
5400
+ ...value2.state,
5401
+ conversationRefs: Object.freeze([...value2.state.conversationRefs]),
5402
+ completedEffects: Object.freeze(value2.state.completedEffects.map((item) => Object.freeze({ ...item }))),
5403
+ unresolvedApprovals: Object.freeze([...value2.state.unresolvedApprovals])
4846
5404
  })
4847
5405
  });
4848
5406
  }
@@ -4859,7 +5417,7 @@ async function digestCodeRepositorySnapshot(snapshot, limits = DEFAULT_LIMITS) {
4859
5417
  commitSha: snapshot.commitSha,
4860
5418
  files: [...snapshot.files].map((file) => ({ path: file.path, content: file.content })).sort((left, right) => left.path.localeCompare(right.path))
4861
5419
  };
4862
- return `sha256:${await sha256Hex(canonicalJson(normalized))}`;
5420
+ return `sha256:${await sha256Hex(canonicalJson2(normalized))}`;
4863
5421
  }
4864
5422
  function validateSnapshot(snapshot, limits) {
4865
5423
  if (!REPOSITORY.test(snapshot.repository) || !SHA4.test(snapshot.commitSha)) {
@@ -4901,10 +5459,10 @@ var import_path9 = require("path");
4901
5459
 
4902
5460
  // ../camel/dist/chunk-LAXU2AVK.js
4903
5461
  function conversionPolicyDigest(policy) {
4904
- return sha256Hex(canonicalJson(policy));
5462
+ return sha256Hex(canonicalJson2(policy));
4905
5463
  }
4906
5464
  function registeredIdRegistryDigest(values) {
4907
- return sha256Hex(canonicalJson(values));
5465
+ return sha256Hex(canonicalJson2(values));
4908
5466
  }
4909
5467
  async function createConversionRegistry(config) {
4910
5468
  const policies = /* @__PURE__ */ new Map();
@@ -4933,61 +5491,61 @@ async function createConversionRegistry(config) {
4933
5491
  if (utf8Length(source.value) > policy.maximumSourceBytes) throw new CamelError("limit_exceeded", "Unsafe conversion input exceeds its byte bound.");
4934
5492
  return policy;
4935
5493
  };
4936
- const emit3 = (source, policy, value) => convert(source, policy, value, outputCounts);
5494
+ const emit3 = (source, policy, value2) => convert(source, policy, value2, outputCounts);
4937
5495
  const operations = Object.freeze({
4938
- boolean: async (value, id) => {
4939
- const policy = checked(value, id, "boolean");
4940
- return emit3(value, policy, requireBoolean(value.value));
5496
+ boolean: async (value2, id) => {
5497
+ const policy = checked(value2, id, "boolean");
5498
+ return emit3(value2, policy, requireBoolean(value2.value));
4941
5499
  },
4942
- integer: async (value, id) => {
4943
- const policy = checked(value, id, "integer");
4944
- return emit3(value, policy, boundedInteger(value.value, policy.output));
5500
+ integer: async (value2, id) => {
5501
+ const policy = checked(value2, id, "integer");
5502
+ return emit3(value2, policy, boundedInteger(value2.value, policy.output));
4945
5503
  },
4946
- finiteNumber: async (value, id) => {
4947
- const policy = checked(value, id, "finite_number");
4948
- return emit3(value, policy, boundedNumber(value.value, policy.output));
5504
+ finiteNumber: async (value2, id) => {
5505
+ const policy = checked(value2, id, "finite_number");
5506
+ return emit3(value2, policy, boundedNumber(value2.value, policy.output));
4949
5507
  },
4950
- enum: async (value, id) => {
4951
- const policy = checked(value, id, "enum");
4952
- return emit3(value, policy, enumMember(value.value, policy.output));
5508
+ enum: async (value2, id) => {
5509
+ const policy = checked(value2, id, "enum");
5510
+ return emit3(value2, policy, enumMember(value2.value, policy.output));
4953
5511
  },
4954
- date: async (value, id) => {
4955
- const policy = checked(value, id, "date");
4956
- return emit3(value, policy, canonicalDate(value.value, policy.output));
5512
+ date: async (value2, id) => {
5513
+ const policy = checked(value2, id, "date");
5514
+ return emit3(value2, policy, canonicalDate(value2.value, policy.output));
4957
5515
  },
4958
- registeredId: async (value, id) => {
4959
- const policy = checked(value, id, "registered_id");
5516
+ registeredId: async (value2, id) => {
5517
+ const policy = checked(value2, id, "registered_id");
4960
5518
  const registry = config.registeredIds?.[policy.output.registryId];
4961
- const output = typeof value.value === "string" ? registry?.values[value.value] : void 0;
5519
+ const output = typeof value2.value === "string" ? registry?.values[value2.value] : void 0;
4962
5520
  if (!output) throw new CamelError("conversion_rejected", "Registered-ID conversion rejected the candidate.");
4963
- return emit3(value, policy, output);
5521
+ return emit3(value2, policy, output);
4964
5522
  },
4965
- digest: async (value, id) => {
4966
- const policy = checked(value, id, "digest");
4967
- if (!(value.value instanceof Uint8Array)) throw new CamelError("conversion_rejected", "Digest conversion requires bytes.");
4968
- return emit3(value, policy, await sha256Hex(value.value));
5523
+ digest: async (value2, id) => {
5524
+ const policy = checked(value2, id, "digest");
5525
+ if (!(value2.value instanceof Uint8Array)) throw new CamelError("conversion_rejected", "Digest conversion requires bytes.");
5526
+ return emit3(value2, policy, await sha256Hex(value2.value));
4969
5527
  },
4970
- measure: async (value, metric, id) => {
4971
- const policy = checked(value, id, "integer");
4972
- const measured = measure(value.value, metric);
4973
- return emit3(value, policy, boundedInteger(measured, policy.output));
5528
+ measure: async (value2, metric, id) => {
5529
+ const policy = checked(value2, id, "integer");
5530
+ const measured = measure(value2.value, metric);
5531
+ return emit3(value2, policy, boundedInteger(measured, policy.output));
4974
5532
  },
4975
- test: async (value, predicateId, id) => {
4976
- const policy = checked(value, id, "boolean");
5533
+ test: async (value2, predicateId, id) => {
5534
+ const policy = checked(value2, id, "boolean");
4977
5535
  const predicate = config.predicates?.[predicateId];
4978
5536
  if (!predicate) throw new CamelError("conversion_rejected", "Predicate is not registered.");
4979
- return emit3(value, policy, evaluatePredicate(value.value, predicate, config.registeredIds));
5537
+ return emit3(value2, policy, evaluatePredicate(value2.value, predicate, config.registeredIds));
4980
5538
  }
4981
5539
  });
4982
5540
  return Object.freeze({ operations, policy: (id) => policies.get(id) ?? missingPolicy() });
4983
5541
  }
4984
- async function convert(source, policy, value, counts) {
5542
+ async function convert(source, policy, value2, counts) {
4985
5543
  const sourceKey = sourceIdentity(source);
4986
5544
  const countKey = `${policy.conversionId}\0${sourceKey}`;
4987
5545
  const count = counts.get(countKey) ?? 0;
4988
5546
  if (count >= policy.maximumOutputsPerArtifact) throw new CamelError("limit_exceeded", "Conversion output count exceeds its per-source bound.");
4989
5547
  counts.set(countKey, count + 1);
4990
- return createSafeInternal(value, "atomic_conversion", {
5548
+ return createSafeInternal(value2, "atomic_conversion", {
4991
5549
  readers: source.label.readers,
4992
5550
  provenance: [...source.label.provenance, { kind: "converter", id: policy.conversionId, digest: policy.digest }],
4993
5551
  dependencies: dependenciesOf([source])
@@ -4999,49 +5557,49 @@ function validatePolicyShape(policy) {
4999
5557
  const output = policy.output;
5000
5558
  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.");
5001
5559
  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.");
5002
- 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.");
5560
+ 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.");
5003
5561
  if (output.kind === "registered_id" && (!output.registryId || !output.registryDigest)) throw new CamelError("state_conflict", "Registered-ID conversion identity is invalid.");
5004
5562
  if (output.kind === "date" && output.format !== "date" && output.format !== "rfc3339") throw new CamelError("state_conflict", "Date conversion format is invalid.");
5005
5563
  if (output.kind === "digest" && output.algorithm !== "sha256") throw new CamelError("state_conflict", "Digest conversion algorithm is invalid.");
5006
5564
  }
5007
- function requireBoolean(value) {
5008
- if (typeof value !== "boolean") throw new CamelError("conversion_rejected", "Boolean conversion requires a structured boolean.");
5009
- return value;
5565
+ function requireBoolean(value2) {
5566
+ if (typeof value2 !== "boolean") throw new CamelError("conversion_rejected", "Boolean conversion requires a structured boolean.");
5567
+ return value2;
5010
5568
  }
5011
- function boundedInteger(value, spec) {
5012
- 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.");
5013
- return value;
5569
+ function boundedInteger(value2, spec) {
5570
+ 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.");
5571
+ return value2;
5014
5572
  }
5015
- function boundedNumber(value, spec) {
5016
- 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.");
5017
- const text = String(value);
5573
+ function boundedNumber(value2, spec) {
5574
+ 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.");
5575
+ const text = String(value2);
5018
5576
  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.");
5019
- return value;
5577
+ return value2;
5020
5578
  }
5021
- function enumMember(value, spec) {
5022
- if (spec.kind !== "enum" || typeof value !== "string") throw new CamelError("conversion_rejected", "Enum conversion requires a structured string member.");
5023
- const member = spec.caseSensitive ? spec.values.find((item) => item === value) : spec.values.find((item) => item.toLocaleLowerCase("en-US") === value.toLocaleLowerCase("en-US"));
5579
+ function enumMember(value2, spec) {
5580
+ if (spec.kind !== "enum" || typeof value2 !== "string") throw new CamelError("conversion_rejected", "Enum conversion requires a structured string member.");
5581
+ const member = spec.caseSensitive ? spec.values.find((item) => item === value2) : spec.values.find((item) => item.toLocaleLowerCase("en-US") === value2.toLocaleLowerCase("en-US"));
5024
5582
  if (member === void 0) throw new CamelError("conversion_rejected", "Enum conversion rejected a non-member.");
5025
5583
  return member;
5026
5584
  }
5027
- function canonicalDate(value, spec) {
5028
- if (spec.kind !== "date" || typeof value !== "string") throw new CamelError("conversion_rejected", "Date conversion requires a canonical structured string.");
5585
+ function canonicalDate(value2, spec) {
5586
+ if (spec.kind !== "date" || typeof value2 !== "string") throw new CamelError("conversion_rejected", "Date conversion requires a canonical structured string.");
5029
5587
  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})$/;
5030
- const instant = Date.parse(spec.format === "date" ? `${value}T00:00:00Z` : value);
5031
- 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.");
5032
- if (spec.earliest && value < spec.earliest || spec.latest && value > spec.latest) throw new CamelError("conversion_rejected", "Date conversion rejected an out-of-range value.");
5033
- return value;
5034
- }
5035
- function measure(value, metric) {
5036
- if (metric === "byte_length" && (typeof value === "string" || value instanceof Uint8Array)) return utf8Length(value);
5037
- if (metric === "codepoint_length" && typeof value === "string") return [...value].length;
5038
- if (metric === "item_count" && Array.isArray(value)) return value.length;
5588
+ const instant = Date.parse(spec.format === "date" ? `${value2}T00:00:00Z` : value2);
5589
+ 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.");
5590
+ if (spec.earliest && value2 < spec.earliest || spec.latest && value2 > spec.latest) throw new CamelError("conversion_rejected", "Date conversion rejected an out-of-range value.");
5591
+ return value2;
5592
+ }
5593
+ function measure(value2, metric) {
5594
+ if (metric === "byte_length" && (typeof value2 === "string" || value2 instanceof Uint8Array)) return utf8Length(value2);
5595
+ if (metric === "codepoint_length" && typeof value2 === "string") return [...value2].length;
5596
+ if (metric === "item_count" && Array.isArray(value2)) return value2.length;
5039
5597
  throw new CamelError("conversion_rejected", "Measurement input does not match the registered metric.");
5040
5598
  }
5041
- function evaluatePredicate(value, predicate, registries) {
5042
- if (predicate.kind === "has_fields") return typeof value === "object" && value !== null && !Array.isArray(value) && predicate.fields.every((field) => Object.hasOwn(value, field));
5043
- if (predicate.kind === "registered_id") return typeof value === "string" && registries?.[predicate.registryId]?.values[value] !== void 0;
5044
- const actual = value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
5599
+ function evaluatePredicate(value2, predicate, registries) {
5600
+ if (predicate.kind === "has_fields") return typeof value2 === "object" && value2 !== null && !Array.isArray(value2) && predicate.fields.every((field) => Object.hasOwn(value2, field));
5601
+ if (predicate.kind === "registered_id") return typeof value2 === "string" && registries?.[predicate.registryId]?.values[value2] !== void 0;
5602
+ const actual = value2 === null ? "null" : Array.isArray(value2) ? "array" : typeof value2;
5045
5603
  return actual === predicate.valueType;
5046
5604
  }
5047
5605
  function sourceIdentity(source) {
@@ -5064,17 +5622,17 @@ function createCamelIngress(constants2 = []) {
5064
5622
  byId.set(item.id, item);
5065
5623
  }
5066
5624
  const ingress = {
5067
- userInstruction: (value, input) => createSafeInternal(value, "user_instruction", metadata("user_instruction", input.id, input.readers)),
5068
- systemPolicy: (value, input) => createSafeInternal(value, "system_policy", metadata("system_policy", input.id, input.readers)),
5625
+ userInstruction: (value2, input) => createSafeInternal(value2, "user_instruction", metadata("user_instruction", input.id, input.readers)),
5626
+ systemPolicy: (value2, input) => createSafeInternal(value2, "system_policy", metadata("system_policy", input.id, input.readers)),
5069
5627
  control: (id) => {
5070
5628
  const item = byId.get(id);
5071
5629
  if (!item) throw new CamelError("permission_denied", "Unknown control constant.");
5072
5630
  return createSafeInternal(item.value, "harness_constant", metadata("harness", id, item.readers));
5073
5631
  },
5074
- external: (value, label) => createUnsafeInternal(value, label),
5075
- quarantinedOutput: (value, input) => {
5632
+ external: (value2, label) => createUnsafeInternal(value2, label),
5633
+ quarantinedOutput: (value2, input) => {
5076
5634
  if (!input.runId) throw new CamelError("state_conflict", "Quarantined run IDs must be non-empty.");
5077
- return createUnsafeInternal(value, {
5635
+ return createUnsafeInternal(value2, {
5078
5636
  readers: input.readers,
5079
5637
  dependencies: input.dependencies,
5080
5638
  provenance: [{ kind: "quarantined_llm", id: input.runId, digest: input.digest }]
@@ -5083,15 +5641,15 @@ function createCamelIngress(constants2 = []) {
5083
5641
  };
5084
5642
  return Object.freeze(ingress);
5085
5643
  }
5086
- function assertNoUnsafeConstant(value, seen = /* @__PURE__ */ new WeakSet()) {
5087
- if (typeof value !== "object" || value === null || seen.has(value)) return;
5088
- seen.add(value);
5089
- if (isCamelValue(value)) {
5090
- if (isUnsafe(value)) throw new CamelError("unsafe_privileged_flow", "Control constants cannot contain Prompt-Unsafe values.");
5091
- assertNoUnsafeConstant(value.value, seen);
5644
+ function assertNoUnsafeConstant(value2, seen = /* @__PURE__ */ new WeakSet()) {
5645
+ if (typeof value2 !== "object" || value2 === null || seen.has(value2)) return;
5646
+ seen.add(value2);
5647
+ if (isCamelValue(value2)) {
5648
+ if (isUnsafe(value2)) throw new CamelError("unsafe_privileged_flow", "Control constants cannot contain Prompt-Unsafe values.");
5649
+ assertNoUnsafeConstant(value2.value, seen);
5092
5650
  return;
5093
5651
  }
5094
- for (const child of Object.values(value)) assertNoUnsafeConstant(child, seen);
5652
+ for (const child of Object.values(value2)) assertNoUnsafeConstant(child, seen);
5095
5653
  }
5096
5654
  function metadata(kind, id, readers) {
5097
5655
  if (!id) throw new CamelError("state_conflict", "Provenance IDs must be non-empty.");
@@ -5105,10 +5663,10 @@ function createEffectPolicy(config = {}) {
5105
5663
  return Object.freeze({ evaluate: (input) => evaluate(input, registries, approvalEffects) });
5106
5664
  }
5107
5665
  function destinationRegistryDigest(values) {
5108
- return sha256Hex(canonicalJson([...values].sort()));
5666
+ return sha256Hex(canonicalJson2([...values].sort()));
5109
5667
  }
5110
5668
  async function evaluate(input, registries, approvals) {
5111
- const actionDigest = await sha256Hex(canonicalJson(input));
5669
+ const actionDigest = await sha256Hex(canonicalJson2(input));
5112
5670
  const decisionId = `decision_${actionDigest.slice(0, 24)}`;
5113
5671
  const deny = (reasonCode) => ({ outcome: "deny", decisionId, reasonCode });
5114
5672
  if (!input.planId || !input.tool.name || !input.tool.policyId || !Number.isSafeInteger(input.tool.version)) return deny("invalid_descriptor");
@@ -5122,7 +5680,7 @@ async function evaluate(input, registries, approvals) {
5122
5680
  if (invalid3) return deny(invalid3);
5123
5681
  if (arg.role === "selector" && !isSafe(arg.value)) confined.add(arg.value);
5124
5682
  }
5125
- if (input.controlDependencies.some((value) => !isSafe(value) && !confined.has(value))) return deny("unsafe_control_dependency");
5683
+ if (input.controlDependencies.some((value2) => !isSafe(value2) && !confined.has(value2))) return deny("unsafe_control_dependency");
5126
5684
  if (confined.size > 0) {
5127
5685
  const fixed = input.tool.unsafeSelectorPolicy?.fixedProviderIds ?? [];
5128
5686
  const destinations = Object.values(input.args).filter((arg) => arg.role === "destination");
@@ -5149,29 +5707,29 @@ async function validateArgument(path, arg, tool, registries) {
5149
5707
  if (!isSafe(arg.value)) return "unsafe_destination_argument";
5150
5708
  if (!isControlOwned(arg.value)) return "destination_not_control_owned";
5151
5709
  const registry = registries[arg.registryId];
5152
- 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;
5710
+ 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;
5153
5711
  if (!registry || !validValues || registry.digest !== arg.registryDigest || await destinationRegistryDigest(registry.values) !== registry.digest || !registry.values.includes(arg.value.value)) return "destination_not_registered";
5154
5712
  }
5155
5713
  if (arg.role === "selector" && !isSafe(arg.value)) return validateUnsafeSelector(path, arg.value, tool);
5156
5714
  return void 0;
5157
5715
  }
5158
- function isControlOwned(value) {
5159
- return value.label.safeBasis === "system_policy" || value.label.safeBasis === "harness_constant";
5716
+ function isControlOwned(value2) {
5717
+ return value2.label.safeBasis === "system_policy" || value2.label.safeBasis === "harness_constant";
5160
5718
  }
5161
5719
  function copyRegistries(registries) {
5162
5720
  return Object.freeze(Object.fromEntries(Object.entries(registries).map(([id, registry]) => [id, Object.freeze({ digest: registry.digest, values: Object.freeze([...registry.values]) })])));
5163
5721
  }
5164
- function validateUnsafeSelector(path, value, tool) {
5722
+ function validateUnsafeSelector(path, value2, tool) {
5165
5723
  const policy = tool.unsafeSelectorPolicy;
5166
5724
  if (!policy || policy.effect !== tool.effect || !policy.selectorPaths.includes(path)) return "unsafe_selector_not_confined";
5167
5725
  if (!policy.fixedDestination || !policy.unsafeOutput || policy.fixedProviderIds.length === 0) return "unsafe_selector_not_confined";
5168
5726
  if (![policy.maximumCalls, policy.maximumResultsPerCall, policy.maximumOutputBytes, policy.maximumSelectorBytes].every((bound) => Number.isSafeInteger(bound) && bound > 0)) return "unsafe_selector_not_confined";
5169
- if (utf8Length(value.value) > policy.maximumSelectorBytes) return "unsafe_selector_too_large";
5170
- if (typeof value.value === "string" && looksLikeDestination(value.value)) return "unsafe_selector_is_destination";
5727
+ if (utf8Length(value2.value) > policy.maximumSelectorBytes) return "unsafe_selector_too_large";
5728
+ if (typeof value2.value === "string" && looksLikeDestination(value2.value)) return "unsafe_selector_is_destination";
5171
5729
  return void 0;
5172
5730
  }
5173
- function looksLikeDestination(value) {
5174
- const text = value.trim();
5731
+ function looksLikeDestination(value2) {
5732
+ const text = value2.trim();
5175
5733
  return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text);
5176
5734
  }
5177
5735
 
@@ -5257,9 +5815,9 @@ var CodeRuntimeReconciler = class {
5257
5815
  }
5258
5816
  }
5259
5817
  };
5260
- function retryableControlFailure(value) {
5261
- if (!value || typeof value !== "object") return false;
5262
- const failure = value;
5818
+ function retryableControlFailure(value2) {
5819
+ if (!value2 || typeof value2 !== "object") return false;
5820
+ const failure = value2;
5263
5821
  if (failure.code === "invalid_response" || typeof failure.status !== "number") return false;
5264
5822
  return failure.status === 408 || failure.status === 425 || failure.status === 429 || failure.status >= 500;
5265
5823
  }
@@ -5311,16 +5869,16 @@ function createCodeRuntimeControlClient(options) {
5311
5869
  if (options.signal?.aborted) throw cause;
5312
5870
  throw new CodeRuntimeControlError("Code runtime control plane is unavailable", 503, "transport_unavailable");
5313
5871
  }
5314
- const value = await response2.json().catch(() => null);
5872
+ const value2 = await response2.json().catch(() => null);
5315
5873
  if (!response2.ok) {
5316
- const problem = record3(record3(value)?.error);
5874
+ const problem = record3(record3(value2)?.error);
5317
5875
  throw new CodeRuntimeControlError(
5318
5876
  typeof problem?.message === "string" ? problem.message : `Code runtime request failed (${response2.status})`,
5319
5877
  response2.status,
5320
5878
  typeof problem?.code === "string" ? problem.code : void 0
5321
5879
  );
5322
5880
  }
5323
- return value;
5881
+ return value2;
5324
5882
  };
5325
5883
  return {
5326
5884
  heartbeat: async (version, capabilities) => {
@@ -5335,15 +5893,15 @@ function createCodeRuntimeControlClient(options) {
5335
5893
  await call2(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source`, {})
5336
5894
  ),
5337
5895
  infer: async (sessionId, inference) => {
5338
- const value = record3(await call2(
5896
+ const value2 = record3(await call2(
5339
5897
  `/registry/code/runtime/sessions/${validSessionId(sessionId)}/inference`,
5340
5898
  inference,
5341
5899
  modelRequestTimeoutMs
5342
5900
  ));
5343
- if (!value || value.requestId !== inference.requestId || !record3(value.response) || !record3(value.receipt)) {
5901
+ if (!value2 || value2.requestId !== inference.requestId || !record3(value2.response) || !record3(value2.receipt)) {
5344
5902
  throw new CodeRuntimeControlError("invalid Code inference response", 502, "invalid_response");
5345
5903
  }
5346
- return value;
5904
+ return value2;
5347
5905
  },
5348
5906
  review: async (sessionId, review) => parseReview(
5349
5907
  await call2(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/review`, review, modelRequestTimeoutMs)
@@ -5368,8 +5926,8 @@ function createCodeRuntimeControlClient(options) {
5368
5926
  }
5369
5927
  };
5370
5928
  }
5371
- function validatedEndpoint(value) {
5372
- const endpoint = value.replace(/\/+$/, "");
5929
+ function validatedEndpoint(value2) {
5930
+ const endpoint = value2.replace(/\/+$/, "");
5373
5931
  let url;
5374
5932
  try {
5375
5933
  url = new URL(endpoint);
@@ -5382,9 +5940,9 @@ function validatedEndpoint(value) {
5382
5940
  }
5383
5941
  return endpoint;
5384
5942
  }
5385
- function validSessionId(value) {
5386
- if (!/^csess_[0-9a-f]{32}$/.test(value)) throw new TypeError("invalid Code session id");
5387
- return value;
5943
+ function validSessionId(value2) {
5944
+ if (!/^csess_[0-9a-f]{32}$/.test(value2)) throw new TypeError("invalid Code session id");
5945
+ return value2;
5388
5946
  }
5389
5947
  function validateHeartbeat(version, capabilities) {
5390
5948
  if (!version.trim() || version.length > 80) throw new TypeError("runtimeVersion is required and at most 80 characters");
@@ -5397,8 +5955,8 @@ function validateHeartbeat(version, capabilities) {
5397
5955
  throw new TypeError("runtime resources must be positive integers");
5398
5956
  }
5399
5957
  }
5400
- function parseSnapshot(value) {
5401
- const root = record3(value);
5958
+ function parseSnapshot(value2) {
5959
+ const root = record3(value2);
5402
5960
  const host = record3(root?.host);
5403
5961
  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");
5404
5962
  const bindingIds = /* @__PURE__ */ new Set();
@@ -5423,11 +5981,11 @@ function parseSnapshot(value) {
5423
5981
  });
5424
5982
  return { host, bindings, commands };
5425
5983
  }
5426
- async function parseSource(value) {
5427
- const snapshot = record3(record3(value)?.snapshot);
5984
+ async function parseSource(value2) {
5985
+ const snapshot = record3(record3(value2)?.snapshot);
5428
5986
  if (!snapshot || typeof snapshot.repository !== "string" || typeof snapshot.commitSha !== "string" || typeof snapshot.treeDigest !== "string" || !Array.isArray(snapshot.files)) throw invalid("source");
5429
- const files = snapshot.files.map((value2) => {
5430
- const file = record3(value2);
5987
+ const files = snapshot.files.map((value22) => {
5988
+ const file = record3(value22);
5431
5989
  if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("source file");
5432
5990
  return { path: file.path, content: file.content };
5433
5991
  });
@@ -5454,19 +6012,19 @@ async function parseSource(value) {
5454
6012
  if (digest !== snapshot.treeDigest) throw invalid("source digest");
5455
6013
  return { ...source, treeDigest: digest, ...references.length ? { references } : {} };
5456
6014
  }
5457
- function parseReview(value) {
5458
- const review = record3(record3(value)?.review);
6015
+ function parseReview(value2) {
6016
+ const review = record3(record3(value2)?.review);
5459
6017
  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");
5460
6018
  return review;
5461
6019
  }
5462
- function parseCandidate(value) {
5463
- const candidate = record3(record3(value)?.candidate);
6020
+ function parseCandidate(value2) {
6021
+ const candidate = record3(record3(value2)?.candidate);
5464
6022
  if (!candidate || typeof candidate.candidateId !== "string" || !/^ccand_[0-9a-f]{32}$/.test(candidate.candidateId) || !["submitted", "approved", "published", "failed"].includes(String(candidate.status))) {
5465
6023
  throw invalid("candidate");
5466
6024
  }
5467
6025
  return { candidateId: candidate.candidateId, status: candidate.status };
5468
6026
  }
5469
- var record3 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
6027
+ var record3 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
5470
6028
  var invalid = (part) => new CodeRuntimeControlError(`invalid Code runtime ${part} response`, 502, "invalid_response");
5471
6029
  var RESERVED = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
5472
6030
  var SECRET = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
@@ -5500,8 +6058,8 @@ function validateCodePatch(patch2, maxBytes) {
5500
6058
  if (!paths.length || new Set(paths).size !== paths.length) throw new TypeError("patch has no diffs or repeats a path");
5501
6059
  return paths;
5502
6060
  }
5503
- function validHeaderPath(value, path, prefix) {
5504
- return value === "/dev/null" || value === `${prefix}/${path}`;
6061
+ function validHeaderPath(value2, path, prefix) {
6062
+ return value2 === "/dev/null" || value2 === `${prefix}/${path}`;
5505
6063
  }
5506
6064
  function validateRelativePath(path) {
5507
6065
  const parts = path.split("/");
@@ -5647,8 +6205,8 @@ function assertCodeBuildRecipe(recipe2) {
5647
6205
  throw new TypeError("build recipe is malformed or exceeds its control bounds");
5648
6206
  }
5649
6207
  }
5650
- function parseMemory(value) {
5651
- const match = /^([1-9][0-9]{0,4})([kmg])$/.exec(value.toLowerCase());
6208
+ function parseMemory(value2) {
6209
+ const match = /^([1-9][0-9]{0,4})([kmg])$/.exec(value2.toLowerCase());
5652
6210
  if (!match?.[1] || !match[2]) return 0;
5653
6211
  const scale = match[2] === "k" ? 1024 : match[2] === "m" ? 1024 ** 2 : 1024 ** 3;
5654
6212
  return Number(match[1]) * scale;
@@ -5883,14 +6441,14 @@ function normalizedRecipe(recipe2) {
5883
6441
  expectedArtifacts: [...recipe2.expectedArtifacts ?? []].sort((left, right) => left.id.localeCompare(right.id)).map((artifact) => ({ id: artifact.id, path: artifact.path, maximumBytes: artifact.maximumBytes }))
5884
6442
  };
5885
6443
  }
5886
- function digestJson(value) {
5887
- return digestBytes(JSON.stringify(value));
6444
+ function digestJson(value2) {
6445
+ return digestBytes(JSON.stringify(value2));
5888
6446
  }
5889
- function digestBytes(value) {
5890
- return `sha256:${(0, import_crypto3.createHash)("sha256").update(value).digest("hex")}`;
6447
+ function digestBytes(value2) {
6448
+ return `sha256:${(0, import_crypto3.createHash)("sha256").update(value2).digest("hex")}`;
5891
6449
  }
5892
- function integer(value, minimum, maximum) {
5893
- return Number.isSafeInteger(value) && value >= minimum && value <= maximum;
6450
+ function integer(value2, minimum, maximum) {
6451
+ return Number.isSafeInteger(value2) && value2 >= minimum && value2 <= maximum;
5894
6452
  }
5895
6453
  async function prepareRuntimeCheckpoint(input) {
5896
6454
  const patch2 = await input.workspace.patch(256 * 1024);
@@ -5943,7 +6501,7 @@ async function prepareRuntimeCheckpoint(input) {
5943
6501
  });
5944
6502
  return { checkpoint, verification, review, note };
5945
6503
  }
5946
- var message = (value) => (value instanceof Error ? value.message : String(value)).slice(0, 500);
6504
+ var message = (value2) => (value2 instanceof Error ? value2.message : String(value2)).slice(0, 500);
5947
6505
  var CodeRuntimeCheckpointManager = class {
5948
6506
  constructor(options) {
5949
6507
  this.options = options;
@@ -6137,7 +6695,7 @@ async function conversionPolicy(id, output) {
6137
6695
  return { ...definition, digest: await conversionPolicyDigest(definition) };
6138
6696
  }
6139
6697
  async function registeredPolicy(id, registryId, values) {
6140
- const mapping = Object.fromEntries(values.map((value) => [value, value]));
6698
+ const mapping = Object.fromEntries(values.map((value2) => [value2, value2]));
6141
6699
  return conversionPolicy(id, {
6142
6700
  kind: "registered_id",
6143
6701
  registryId,
@@ -6146,7 +6704,7 @@ async function registeredPolicy(id, registryId, values) {
6146
6704
  }
6147
6705
  async function conversionRegistry(policies, values) {
6148
6706
  const registeredIds = Object.fromEntries(await Promise.all(Object.entries(values).map(async ([id, entries]) => {
6149
- const mapping = Object.fromEntries(entries.map((value) => [value, value]));
6707
+ const mapping = Object.fromEntries(entries.map((value2) => [value2, value2]));
6150
6708
  return [id, { values: mapping, digest: await registeredIdRegistryDigest(mapping) }];
6151
6709
  })));
6152
6710
  return createConversionRegistry({ policies, registeredIds });
@@ -6170,8 +6728,8 @@ async function environment(input, options, tool) {
6170
6728
  };
6171
6729
  return { ingress, policy, fixedArgs, reader: ingress.control("reader"), runId: `${input.request.requestId}:${tool}` };
6172
6730
  }
6173
- function unsafe(base, value, field) {
6174
- return base.ingress.quarantinedOutput(value, {
6731
+ function unsafe(base, value2, field) {
6732
+ return base.ingress.quarantinedOutput(value2, {
6175
6733
  readers: base.reader.label.readers,
6176
6734
  runId: `${base.runId}:${field}`
6177
6735
  });
@@ -6251,14 +6809,14 @@ async function read(context, request2, options, policy) {
6251
6809
  }
6252
6810
  async function patch(context, request2, options, policy) {
6253
6811
  exactKeys(request2.input, ["patch"]);
6254
- const value = stringField(request2.input, "patch");
6255
- const paths = validateCodePatch(value, options.maxPatchBytes ?? 256 * 1024);
6812
+ const value2 = stringField(request2.input, "patch");
6813
+ const paths = validateCodePatch(value2, options.maxPatchBytes ?? 256 * 1024);
6256
6814
  if (paths.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
6257
6815
  throw new TypeError("patch targets a read-only reference source");
6258
6816
  }
6259
- const allowed = await policy.patch(policyContext(context, request2, options, { patch: value }));
6817
+ const allowed = await policy.patch(policyContext(context, request2, options, { patch: value2 }));
6260
6818
  if (!allowed) return response(request2, false, "tool denied by CaMeL policy");
6261
- await applyCodePatch(context.workspaceDir, value, paths);
6819
+ await applyCodePatch(context.workspaceDir, value2, paths);
6262
6820
  return response(request2, true, `Applied patch to ${paths.length} file(s).`, { paths });
6263
6821
  }
6264
6822
  async function recipe(context, request2, options, recipes, policy) {
@@ -6349,14 +6907,14 @@ function exactKeys(input, allowed) {
6349
6907
  if (Object.keys(input).some((key) => !allowed.includes(key))) throw new TypeError("tool input contains an unsupported field");
6350
6908
  }
6351
6909
  function stringField(input, name) {
6352
- const value = input[name];
6353
- if (typeof value !== "string" || !value) throw new TypeError(`${name} must be a non-empty string`);
6354
- return value;
6910
+ const value2 = input[name];
6911
+ if (typeof value2 !== "string" || !value2) throw new TypeError(`${name} must be a non-empty string`);
6912
+ return value2;
6355
6913
  }
6356
- function optionalInteger(value) {
6357
- if (value === void 0) return void 0;
6358
- if (!Number.isSafeInteger(value) || value < 1) throw new TypeError("line bounds must be positive integers");
6359
- return value;
6914
+ function optionalInteger(value2) {
6915
+ if (value2 === void 0) return void 0;
6916
+ if (!Number.isSafeInteger(value2) || value2 < 1) throw new TypeError("line bounds must be positive integers");
6917
+ return value2;
6360
6918
  }
6361
6919
  function response(request2, ok, content2, details) {
6362
6920
  return { requestId: request2.requestId, ok, content: content2, ...details ? { details } : {} };
@@ -6402,9 +6960,9 @@ function codeLocalSource(payload) {
6402
6960
  return source;
6403
6961
  }
6404
6962
  function codeCheckpointPayload(payload) {
6405
- const value = payload.checkpoint;
6406
- if (!value || typeof value !== "object" || Array.isArray(value)) throw new TypeError("resume checkpoint is missing");
6407
- return value;
6963
+ const value2 = payload.checkpoint;
6964
+ if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) throw new TypeError("resume checkpoint is missing");
6965
+ return value2;
6408
6966
  }
6409
6967
  function fakeCodeLease(command, metadata2) {
6410
6968
  return {
@@ -6428,7 +6986,7 @@ function fakeCodeLease(command, metadata2) {
6428
6986
  }
6429
6987
  };
6430
6988
  }
6431
- var record22 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
6989
+ var record22 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
6432
6990
  var SOURCE_LIMITS = { maxFiles: 2e4, maxBytes: 512 * 1024 * 1024 };
6433
6991
  async function prepareRuntimeLocalSource(input) {
6434
6992
  const { command, descriptor: descriptor2, available, repository, baseCommitSha, resume } = input;
@@ -6518,24 +7076,24 @@ async function appendCodeRuntimeEvent(control, command, event, refs) {
6518
7076
  const bounded = event.type === "message" ? { ...event, body: event.body.trim().slice(0, 2e4) || `${event.actor} event` } : event;
6519
7077
  await control.appendSessionEvent(command.sessionId, eventId, bounded);
6520
7078
  }
6521
- var digestRuntimeValue = (value) => `sha256:${(0, import_crypto4.createHash)("sha256").update(value).digest("hex")}`;
6522
- var runtimeErrorMessage = (value) => value instanceof Error ? value.message : String(value);
6523
- var runtimeRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
6524
- var safeRuntimeJson = (value) => {
7079
+ var digestRuntimeValue = (value2) => `sha256:${(0, import_crypto4.createHash)("sha256").update(value2).digest("hex")}`;
7080
+ var runtimeErrorMessage = (value2) => value2 instanceof Error ? value2.message : String(value2);
7081
+ var runtimeRecord = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
7082
+ var safeRuntimeJson = (value2) => {
6525
7083
  try {
6526
- return JSON.stringify(value).slice(0, 1e4);
7084
+ return JSON.stringify(value2).slice(0, 1e4);
6527
7085
  } catch {
6528
7086
  return "[event]";
6529
7087
  }
6530
7088
  };
6531
- function runtimeResultText(value) {
6532
- const record32 = runtimeRecord(value);
7089
+ function runtimeResultText(value2) {
7090
+ const record32 = runtimeRecord(value2);
6533
7091
  if (record32 && typeof record32.text === "string") return record32.text.slice(0, 2e4);
6534
7092
  if (record32 && typeof record32.error === "string") return `Pi failed: ${record32.error.slice(0, 19989)}`;
6535
7093
  return null;
6536
7094
  }
6537
- function runtimeResultError(value) {
6538
- const record32 = runtimeRecord(value);
7095
+ function runtimeResultError(value2) {
7096
+ const record32 = runtimeRecord(value2);
6539
7097
  return record32 && typeof record32.error === "string" && record32.error.trim() ? record32.error.trim().slice(0, 2e3) : null;
6540
7098
  }
6541
7099
  var CodePiRuntimeEngine = class {
@@ -6809,14 +7367,14 @@ var CodePiRuntimeEngine = class {
6809
7367
  this.#active.delete(command.sessionId);
6810
7368
  return result;
6811
7369
  }
6812
- async #failure(command, active, value) {
6813
- active.failure = value.slice(0, 2e3);
7370
+ async #failure(command, active, value2) {
7371
+ active.failure = value2.slice(0, 2e3);
6814
7372
  if (active.acknowledged) {
6815
7373
  await this.options.control.reportSessionFailure(command.sessionId, active.failure).catch(() => void 0);
6816
7374
  }
6817
7375
  }
6818
- async #diagnostic(command, active, value) {
6819
- const detail = value.trim().slice(0, 2e3) || "Pi runtime failed";
7376
+ async #diagnostic(command, active, value2) {
7377
+ const detail = value2.trim().slice(0, 2e3) || "Pi runtime failed";
6820
7378
  this.options.onDiagnostic?.(detail);
6821
7379
  await this.#event(
6822
7380
  command,
@@ -6829,265 +7387,19 @@ var CodePiRuntimeEngine = class {
6829
7387
  }
6830
7388
  };
6831
7389
 
6832
- // src/help.ts
7390
+ // src/version.ts
6833
7391
  var import_node_fs13 = require("fs");
6834
7392
  function cliVersion() {
6835
7393
  const pkg = JSON.parse((0, import_node_fs13.readFileSync)(new URL("../package.json", importMetaUrl), "utf8"));
6836
7394
  return pkg.version ?? "unknown";
6837
7395
  }
6838
- function printHelp(output = console) {
6839
- output.log(`odla-ai
6840
7396
 
6841
- Start here:
6842
- odla-ai runbook ask "<question>" The current procedure, from odla's own
6843
- runbooks. Ask BEFORE searching the web or
6844
- working from memory: runbooks are edited
6845
- live, so your training data is out of date
6846
- and this is not.
6847
- odla-ai runbook impact After a change: which runbooks describe the
6848
- code you just touched, so you can fix any
6849
- step it made wrong.
6850
-
6851
- Usage:
6852
- odla-ai setup [--dir <project>] [--agent <name>] [--global] [--force]
6853
- odla-ai init --app-id <id> --name <name> [--services db,ai,o11y,calendar] [--env dev --env prod]
6854
- odla-ai doctor [--config odla.config.mjs]
6855
- odla-ai calendar status [--env dev] [--email <odla-account>] [--json]
6856
- odla-ai calendar calendars [--env dev] [--email <odla-account>] [--json]
6857
- odla-ai calendar connect [--env dev] [--email <odla-account>] [--no-open] [--yes]
6858
- odla-ai calendar disconnect [--env dev] [--email <odla-account>] --yes
6859
- odla-ai app archive [--config odla.config.mjs] [--email <odla-account>] [--json] --yes
6860
- odla-ai app restore [--config odla.config.mjs] [--email <odla-account>] [--json]
6861
- odla-ai app export [--env dev] [--fresh] [--out <file>] [--email <odla-account>] [--json]
6862
- odla-ai app import <file|-> [--env dev] [--ns <namespace>] [--id-field <f>|--key <attr>|--generate-ids] [--dry-run] [--json] --yes
6863
- odla-ai app refresh-sandbox [--include-identity] [--include-files] [--dry-run] [--json] --yes
6864
- odla-ai app go-live [--include-identity] [--include-files] [--dry-run] [--json] --yes
6865
- odla-ai app promote [--dry-run] [--json] --yes
6866
- odla-ai app rename <name> [--config odla.config.mjs] [--email <odla-account>] [--json]
6867
- odla-ai app owners list [--config odla.config.mjs] [--email <odla-account>] [--json]
6868
- odla-ai app owners add <email> [--email <odla-account>] [--json]
6869
- odla-ai app owners remove <email> [--email <odla-account>] [--json]
6870
- odla-ai pm goal list [--app <id>] [--status <s>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
6871
- odla-ai pm task list [--app <id>] [--column <c>] [--goal <id>] [--assignee <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
6872
- odla-ai pm decision list [--app <id>] [--status <s>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
6873
- odla-ai pm bug list [--app <id>] [--status <s>] [--severity <s>] [--goal <id>] [--assignee <id>] [--decision <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
6874
- odla-ai pm goal add --app <id> --title <t> [--status <s>] [--proof <text>] [--target <pct>] [--mutation-id <id>] [--json]
6875
- 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]
6876
- odla-ai pm decision add --app <id> --title <t> --body <text> [--status <s>] [--mutation-id <id>] [--json]
6877
- 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]
6878
- odla-ai pm <goal|task|decision|bug> get <id> [--json]
6879
- odla-ai pm goal set <id> [--title <t>|--status <s>|--proof <text>|--no-proof|--target <pct>|--no-target] [--mutation-id <id>] [--json]
6880
- 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]
6881
- odla-ai pm decision set <id> [--title <t>|--status <s>|--body <text>] [--mutation-id <id>] [--json]
6882
- 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]
6883
- odla-ai pm <goal|task|decision> done <id> [--mutation-id <id>]
6884
- odla-ai pm bug done <id> [--decision <accepted-decision-id>] [--mutation-id <id>]
6885
- odla-ai pm <goal|task|decision|bug> comment <id> --body "..." [--mutation-id <id>]
6886
- odla-ai pm <goal|task|decision|bug> comments <id> [--json]
6887
- odla-ai pm <goal|task|decision|bug> rm <id>
6888
- odla-ai pm handoff --app <id> [--json]
6889
- odla-ai discuss groups [--json]
6890
- odla-ai discuss list [--app <id>] [--q <text>] [--state open|resolved|all] [--json]
6891
- odla-ai discuss read <topic> [--limit <n> --offset <n>] [--json]
6892
- odla-ai discuss post --app <id> --subject "..." --body "..." [--markup "... @[Label](kind/id)"] [--mutation-id <id>]
6893
- odla-ai discuss reply <topic> --body "..." [--markup "..."] [--mutation-id <id>]
6894
- odla-ai discuss resolve <topic> [--reopen] [--mutation-id <id>]
6895
- odla-ai discuss who --q <text> [--app <id>] [--kinds user,pm:task] [--json]
6896
- odla-ai discuss watch [<topic>] [--cursor <cursor>] [--by <authorId>] [--self <authorId>] [--interval <s>] [--timeout <s>] [--json|--jsonl]
6897
- odla-ai agent jobs [--env dev] [--state pending|running|succeeded|dead_letter] [--limit 50] [--email <email>] [--json]
6898
- odla-ai agent retry <job-id> [--env dev] [--email <email>] [--json]
6899
- odla-ai context show [--context <name>] [--platform https://odla.ai] [--app <id>] [--env prod] [--json]
6900
- odla-ai context list [--json]
6901
- odla-ai context save <name> [--platform <url>] [--app <id>] [--env <name>] [--json]
6902
- odla-ai context remove <name> --yes [--json]
6903
- odla-ai o11y status [--app <id>] [--context <name>] [--platform https://odla.ai] [--env prod] [--minutes 60] [--json]
6904
- odla-ai whoami [--context <name>] [--platform https://odla.ai] [--json]
6905
- odla-ai runbook ask "<question>" [--app <id>] [--all] [--json]
6906
- odla-ai runbook search "<question>" [--app <id>] [--all] [--limit <n>] [--json]
6907
- odla-ai runbook impact [--base origin/main] [--app <id>] [--all] [--limit <n>] [--json]
6908
- odla-ai runbook lint [--app <id>] [--all] [--json]
6909
- odla-ai runbook list [--app <id>] [--all] [--q <text>] [--json]
6910
- odla-ai runbook comment <slug> --body "..." [--app <id>]
6911
- odla-ai runbook get <slug> [--app <id>]
6912
- odla-ai runbook new <slug> --title <t> --file <path|-> [--summary <s>] [--requires <specs>] [--app <id>]
6913
- odla-ai runbook edit <slug> [--file <path|->|--body "..."] [--note <why>] [--requires <specs>] [--app <id>]
6914
- odla-ai runbook import <dir> [--visibility operator|admin] [--dry-run] [--app <id>]
6915
- odla-ai runbook publish <slug> [--app <id>]
6916
- odla-ai runbook visibility <slug> <operator|admin> [--app <id>]
6917
- odla-ai runbook archive <slug> [--app <id>]
6918
- odla-ai runbook history <slug> [--app <id>] [--json]
6919
- odla-ai runbook revert <slug> --version <n> [--app <id>]
6920
- odla-ai runbook rm <slug> [--app <id>]
6921
- odla-ai capabilities [--json]
6922
- odla-ai code connect [--env dev|prod] [--email <odla-account>] [--engine auto|container|podman|docker] [--slots <1-64>] [--once]
6923
- odla-ai admin ai show [--context <name>] [--platform https://odla.ai] [--email <odla-account>] [--json]
6924
- odla-ai admin ai models [--context <name>] [--provider <id>] [--json]
6925
- odla-ai admin ai set <purpose> [--context <name>] [--provider <id>] [--model <id>] [--enabled|--no-enabled]
6926
- [--max-input-bytes <n>] [--max-output-tokens <n>] [--max-calls-per-run <n>] [--json]
6927
- odla-ai admin ai set security [--context <name>] --discovery-provider <id> --discovery-model <id>
6928
- --validation-provider <id> --validation-model <id> [--enabled|--no-enabled] [--json]
6929
- odla-ai admin ai credentials [--context <name>] [--json]
6930
- odla-ai admin ai credential set <provider> [--context <name>] (--from-env <NAME>|--stdin)
6931
- odla-ai admin ai usage [--context <name>] [--app-id <id>] [--env <env>] [--run-id <id>] [--limit <1-500>] [--json]
6932
- odla-ai admin ai audit [--context <name>] [--limit <1-200>] [--json]
6933
- odla-ai security github connect [--repo owner/name] [--env dev] [--email <odla-account>] [--no-open]
6934
- odla-ai security github disconnect --source <id> [--env dev] [--yes]
6935
- odla-ai security plan [--env dev] [--json]
6936
- odla-ai security sources [--env dev] [--json]
6937
- odla-ai security run --source <id> --plan-digest <sha256:...> --ack-redacted-source [--ref <branch|tag|sha>] [--env dev] [--no-follow]
6938
- odla-ai security status <job-id> [--follow] [--json]
6939
- odla-ai security report <job-id> [--json]
6940
- odla-ai security run [target] --ack-redacted-source [--env dev] [--profile odla] [--fail-on high]
6941
- odla-ai security run [target] --self --ack-redacted-source
6942
- odla-ai provision [--config odla.config.mjs] [--email <odla-account>] [--wait <seconds>] [--dry-run] [--push-secrets] [--rotate-o11y-token] [--write-dev-vars[=path]] [--yes]
6943
- odla-ai smoke [--config odla.config.mjs] [--env dev] [--email <odla-account>] [--no-open]
6944
- odla-ai skill install [--dir <project>] [--agent <name>] [--global] [--force]
6945
- odla-ai secrets push --env <env> [--config odla.config.mjs] [--dry-run] [--yes]
6946
- odla-ai secrets set <name> --env <env> (--from-env <NAME>|--stdin) [--email <odla-account>] [--config odla.config.mjs] [--yes]
6947
- odla-ai secrets set-clerk-key --env <env> (--from-env <NAME>|--stdin) [--email <odla-account>] [--config odla.config.mjs] [--yes]
6948
- odla-ai version
6949
-
6950
- Commands:
6951
- agent Inspect durable agent wakeups and explicitly requeue a
6952
- dead-lettered job; JSON output is stable for remote operators.
6953
- runbook odla's operational procedures, stored in the database and read at
6954
- the moment they are followed. "ask" gives a written, cited answer;
6955
- "search" the passages behind it; "get" the whole document.
6956
- "impact" diffs your working tree against a base ref and names the
6957
- runbooks that describe what you changed \u2014 run it after touching a
6958
- package's exported API or JSDoc, or a Studio surface. "lint"
6959
- checks the corpus the other way: every odla-ai command the
6960
- runbooks name is held against this CLI's real command surface,
6961
- and against the minimum versions each runbook declares. A wrong step
6962
- is fixed with "edit", which takes effect immediately: a runbook is
6963
- a row, not a release. Runbooks describe PROCEDURE; what an export
6964
- does and what it guarantees is JSDoc, rendered per package at
6965
- https://odla.ai/docs and shipped in the installed .d.ts. Answering
6966
- a question usually needs both.
6967
- whoami Report who this terminal is authenticated as, and whether it holds
6968
- platform admin. A handshake-minted device token is never admin,
6969
- however the human who approved it is configured.
6970
- context Explain selected config, platform, app, environment, and
6971
- developer-token provenance without printing credentials or
6972
- starting a device handshake. Operator work can run outside a
6973
- project checkout with explicit flags or ODLA_* environment
6974
- variables.
6975
- setup Install offline odla runbooks for common coding-agent harnesses.
6976
- init Create a generic odla.config.mjs plus starter schema/rules files.
6977
- doctor Validate and summarize the project config without network calls.
6978
- calendar Inspect, connect, or disconnect the live Google booking connection.
6979
- app Archive (suspend, data retained), restore, export, import, or
6980
- manage the co-owners of the app. Archiving takes every
6981
- environment's data plane down until restored; permanent deletion
6982
- has NO CLI \u2014 it requires a signed-in owner in Studio, and no
6983
- machine or agent credential can purge. "app export" downloads a
6984
- portable gzipped-JSONL snapshot of one database (--fresh takes a
6985
- new snapshot first) \u2014 your data is always yours to take.
6986
- "app import" upserts rows back in from JSON, JSONL, or a
6987
- {namespace: rows} map (the shape a query returns); it writes only
6988
- with --yes, so the bare command is a dry run, and it refuses to
6989
- guess an id mode that would duplicate rows on a re-run.
6990
- "app refresh-sandbox" replaces the sandbox with a copy of live
6991
- (the routine dev loop); "app go-live" copies the sandbox into an
6992
- EMPTY live database, once, at launch; "app promote" pushes only
6993
- schema, rules and gates up afterwards, leaving live's rows alone.
6994
- Each prints its plan and writes nothing without --yes, so the
6995
- bare command is the dry run. Clerk config, triggers, allowlists,
6996
- secrets and API keys never travel; identity rows stay behind
6997
- unless --include-identity.
6998
- "app rename <name>" changes only the display name humans read;
6999
- the app id is permanent (tenants, URLs and keys embed it), so a
7000
- rename never re-provisions anything or invalidates a credential.
7001
- "app owners add <email>" grants a signed-up odla member the SAME
7002
- full access as you; they then run their own "provision" to mint
7003
- their own credentials for the shared db (the live one included)
7004
- \u2014 no secret is ever copied between people.
7005
- capabilities Show what the CLI automates vs agent edits and human checkpoints.
7006
- code Enroll this Mac/Linux host for the current Studio-connected repository and run Pi.
7007
- admin Manage platform-funded AI routing/credentials/usage with narrow device grants.
7008
- security Connect GitHub sources and run commit-pinned hosted reviews, or scan a local snapshot.
7009
- pm Project management (via @odla-ai/pm) shared across the apps you
7010
- co-own: conformance goals, kanban tasks, decisions, and bugs. With
7011
- no --app, "list" spans every co-owned project (the cross-project
7012
- view); with --app it scopes to one. Same device-grant auth as
7013
- "app". Entities: goal (alias conformance), task (alias kanban),
7014
- decision, bug. Status changes and comments post to each item's
7015
- @odla-ai/chat discussion thread.
7016
- discuss Group discussions (via @odla-ai/chat) for the apps you co-own:
7017
- one group per project, topics with replies, @-mentions of people,
7018
- agents, PM items, and projects. Built for unattended use \u2014 post a
7019
- question, then "watch" it until someone answers. "watch" exits 75
7020
- (not 1) when it times out with nothing new, so a script can tell
7021
- "no answer yet" from a failure and just rerun. Use "who" to look
7022
- up the exact @[Label](kind/id) markup for a mention.
7023
- o11y Read one stable status envelope for application RED, current
7024
- live-sync load/freshness, the protected commit-to-visible
7025
- canary, collector ingest/scheduler trust, Cloudflare-owned
7026
- runtime metrics, and a machine verdict.
7027
- --json keeps auth progress on stderr for unattended agents.
7028
- provision Register services, compose integrations, persist credentials, optionally push secrets.
7029
- smoke Verify credentials, public-config, composed schema, db aggregate, and integration probes.
7030
- skill Same installer; --agent accepts all, claude, codex, cursor,
7031
- copilot, gemini, or agents (repeatable or comma-separated).
7032
- secrets Push configured db/o11y secrets into the Worker via wrangler
7033
- stdin; set stores a tenant-vault secret and set-clerk-key the
7034
- reserved Clerk secret key, write-only from stdin or an env var.
7035
- version Print the CLI version.
7036
-
7037
- Safety:
7038
- Every app has two databases on odla.ai: a sandbox (env "dev", tenant
7039
- <appId>--dev) and a live one (env "prod", tenant <appId>). Both are served by
7040
- production odla.ai \u2014 there is no separate odla to point at. New projects get
7041
- the sandbox only. Add "prod" explicitly to odla.config.mjs and pass --yes to
7042
- provision the live database; use --dry-run first to inspect the resolved plan.
7043
- Provision caches the approved developer token and service credentials under
7044
- .odla/ with mode 0600, and init adds those paths to .gitignore. Secret push
7045
- preflights Wrangler before any shown-once issuance or destructive rotation.
7046
- Projectless PM, Discussions, o11y, runbook, and identity commands use
7047
- --platform/--app/--env, ODLA_PLATFORM_URL/ODLA_APP_ID/ODLA_ENV, and
7048
- ODLA_DEV_TOKEN. Save non-secret scope metadata with "context save", then
7049
- select it explicitly with --context or ODLA_CONTEXT. Each named context gets
7050
- isolated developer and scoped-token caches. Override their locations with
7051
- ODLA_DEV_TOKEN_FILE and ODLA_ADMIN_TOKEN_FILE; ODLA_CONTEXT_FILE relocates
7052
- the metadata file. Flags and specific ODLA_* scope variables beat a selected
7053
- context, which beats project config. There is no ambient current context.
7054
- "context show" reports only provenance and cache state and never authenticates.
7055
- Provision opens the approval page in your browser automatically whenever the
7056
- machine can show one, including agent-driven runs; only CI, SSH, and
7057
- display-less hosts skip it. Use --open to force or --no-open to suppress.
7058
- Browser launch is best-effort: the printed approval URL is authoritative and
7059
- agents must relay it to the human verbatim. A started handshake is persisted
7060
- under .odla/, so a command killed mid-wait loses nothing \u2014 rerunning resumes
7061
- the same code. Outside an interactive terminal the wait is capped (90s by
7062
- default, --wait <seconds> to change); a still-pending handshake then exits
7063
- with code 75: relay the URL, wait for approval, and re-run to collect.
7064
- A fresh device handshake requires --email <odla-account> or ODLA_USER_EMAIL.
7065
- The email is a non-secret identity hint: never provide a password or session
7066
- token. The matching account must already exist, be signed in, explicitly
7067
- review the exact code, and finish any current request before claiming another.
7068
- Run Code from a GitHub checkout already connected to an app in Studio; an
7069
- odla.config.mjs may select the app explicitly but is not required. Code host
7070
- approval and credential hashes live in odla-ai/db. The host
7071
- credential is never written under .odla/; it exists only in the foreground
7072
- "code connect" process and is rotated by the next approved connection.
7073
- Calendar setup has a second human checkpoint: odla issues a state-bound Google consent URL;
7074
- OAuth codes and refresh tokens never enter the CLI, repo, chat, or app.
7075
- GitHub security uses source-read-only access plus optional metadata-only Checks write: the CLI never asks
7076
- for a PAT or provider key. GitHub read access is separate from the explicit
7077
- --ack-redacted-source consent and the exact --plan-digest printed by security
7078
- plan are required before bounded snippets reach System AI.
7079
- Run security plan first to inspect the admin-selected providers, models,
7080
- per-route bounds, credential readiness, retention, no-execution boundary,
7081
- and digest that binds consent to that exact plan.
7082
- `);
7083
- }
7084
-
7085
- // src/security-hosted-github.ts
7086
- var import_node_child_process4 = require("child_process");
7087
- var import_node_util2 = require("util");
7397
+ // src/security-hosted-github.ts
7398
+ var import_node_child_process4 = require("child_process");
7399
+ var import_node_util2 = require("util");
7088
7400
 
7089
7401
  // src/security-hosted-request.ts
7090
- async function requestHostedSecurityJson(options, path, init, action) {
7402
+ async function requestHostedSecurityJson(options, path, init, action2) {
7091
7403
  const platform = platformAudience(options.platform);
7092
7404
  const token = hostedSecurityCredential(options.token);
7093
7405
  const requestInit = {
@@ -7107,35 +7419,35 @@ async function requestHostedSecurityJson(options, path, init, action) {
7107
7419
  if (!response2.ok) {
7108
7420
  const code = optionalHostedText(body.error?.code, "error code", 128);
7109
7421
  const message2 = optionalHostedText(body.error?.message, "error message", 300);
7110
- throw new Error(`${action} failed (${response2.status})${code ? ` ${code}` : ""}${message2 ? `: ${message2}` : ""}`);
7422
+ throw new Error(`${action2} failed (${response2.status})${code ? ` ${code}` : ""}${message2 ? `: ${message2}` : ""}`);
7111
7423
  }
7112
7424
  return body;
7113
7425
  }
7114
- function hostedIdentifier(value, name) {
7115
- const result = optionalHostedText(value, name, 180);
7426
+ function hostedIdentifier(value2, name) {
7427
+ const result = optionalHostedText(value2, name, 180);
7116
7428
  if (!result || !/^[A-Za-z0-9][A-Za-z0-9._:-]*$/.test(result)) {
7117
7429
  throw new Error(`${name} is invalid`);
7118
7430
  }
7119
7431
  return result;
7120
7432
  }
7121
- function positivePolicyVersion(value, name) {
7122
- if (!Number.isSafeInteger(value) || value < 1) {
7433
+ function positivePolicyVersion(value2, name) {
7434
+ if (!Number.isSafeInteger(value2) || value2 < 1) {
7123
7435
  throw new Error(`${name} is invalid`);
7124
7436
  }
7125
- return value;
7437
+ return value2;
7126
7438
  }
7127
- function optionalHostedText(value, name, maxLength) {
7128
- if (value === void 0 || value === null || value === "") return void 0;
7129
- if (typeof value !== "string" || value.length > maxLength || /[\u0000-\u001f\u007f]/.test(value)) {
7439
+ function optionalHostedText(value2, name, maxLength) {
7440
+ if (value2 === void 0 || value2 === null || value2 === "") return void 0;
7441
+ if (typeof value2 !== "string" || value2.length > maxLength || /[\u0000-\u001f\u007f]/.test(value2)) {
7130
7442
  throw new Error(`${name} is invalid`);
7131
7443
  }
7132
- return value;
7444
+ return value2;
7133
7445
  }
7134
- function githubRepositoryName(value) {
7135
- if (typeof value !== "string" || value.length > 201) {
7446
+ function githubRepositoryName(value2) {
7447
+ if (typeof value2 !== "string" || value2.length > 201) {
7136
7448
  throw new Error("repository must be owner/name");
7137
7449
  }
7138
- const parts = value.split("/");
7450
+ const parts = value2.split("/");
7139
7451
  const owner = parts[0] ?? "";
7140
7452
  const name = (parts[1] ?? "").replace(/\.git$/i, "");
7141
7453
  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 === "..") {
@@ -7143,10 +7455,10 @@ function githubRepositoryName(value) {
7143
7455
  }
7144
7456
  return `${owner}/${name}`;
7145
7457
  }
7146
- function trustedGitHubInstallUrl(value) {
7458
+ function trustedGitHubInstallUrl(value2) {
7147
7459
  let url;
7148
7460
  try {
7149
- url = new URL(value);
7461
+ url = new URL(value2);
7150
7462
  } catch {
7151
7463
  throw new Error("odla.ai returned an invalid GitHub installation URL");
7152
7464
  }
@@ -7155,17 +7467,17 @@ function trustedGitHubInstallUrl(value) {
7155
7467
  }
7156
7468
  return url.toString();
7157
7469
  }
7158
- function hostedPollInterval(value = 2e3) {
7159
- if (!Number.isSafeInteger(value) || value < 100 || value > 3e4) {
7470
+ function hostedPollInterval(value2 = 2e3) {
7471
+ if (!Number.isSafeInteger(value2) || value2 < 100 || value2 > 3e4) {
7160
7472
  throw new Error("poll interval must be 100-30000ms");
7161
7473
  }
7162
- return value;
7474
+ return value2;
7163
7475
  }
7164
- function hostedPollTimeout(value = 10 * 6e4) {
7165
- if (!Number.isSafeInteger(value) || value < 1e3 || value > 60 * 6e4) {
7476
+ function hostedPollTimeout(value2 = 10 * 6e4) {
7477
+ if (!Number.isSafeInteger(value2) || value2 < 1e3 || value2 > 60 * 6e4) {
7166
7478
  throw new Error("poll timeout must be 1000-3600000ms");
7167
7479
  }
7168
- return value;
7480
+ return value2;
7169
7481
  }
7170
7482
  async function waitForHostedPoll(milliseconds, signal) {
7171
7483
  if (signal?.aborted) throw signal.reason ?? new DOMException("aborted", "AbortError");
@@ -7177,16 +7489,16 @@ async function waitForHostedPoll(milliseconds, signal) {
7177
7489
  }, { once: true });
7178
7490
  });
7179
7491
  }
7180
- function isValidHostedSecurityPlan(value, env) {
7181
- 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;
7492
+ function isValidHostedSecurityPlan(value2, env) {
7493
+ 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;
7182
7494
  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;
7183
- return validRoute(value.routes?.discovery, "security.discovery") && validRoute(value.routes?.validation, "security.validation");
7495
+ return validRoute(value2.routes?.discovery, "security.discovery") && validRoute(value2.routes?.validation, "security.validation");
7184
7496
  }
7185
- function hostedSecurityCredential(value) {
7186
- if (typeof value !== "string" || value.length < 8 || value.length > 8192 || /\s|[\u0000-\u001f\u007f]/.test(value)) {
7497
+ function hostedSecurityCredential(value2) {
7498
+ if (typeof value2 !== "string" || value2.length < 8 || value2.length > 8192 || /\s|[\u0000-\u001f\u007f]/.test(value2)) {
7187
7499
  throw new Error("Hosted security requires an injected odla developer token");
7188
7500
  }
7189
- return value;
7501
+ return value2;
7190
7502
  }
7191
7503
 
7192
7504
  // src/security-hosted-github.ts
@@ -7298,7 +7610,7 @@ async function defaultReadOrigin(cwd) {
7298
7610
 
7299
7611
  // src/code-local-source.ts
7300
7612
  var import_node_child_process5 = require("child_process");
7301
- var import_node_crypto = require("crypto");
7613
+ var import_node_crypto2 = require("crypto");
7302
7614
  var SOURCE_LIMITS2 = { maxFiles: 2e4, maxBytes: 512 * 1024 * 1024 };
7303
7615
  async function prepareCodeLocalSource(cwd, repository, readHead = readGitHead) {
7304
7616
  const headCommitSha = await readHead(cwd);
@@ -7339,25 +7651,25 @@ async function prepareCodeLocalSource(cwd, repository, readHead = readGitHead) {
7339
7651
  }
7340
7652
  }
7341
7653
  async function readGitHead(cwd) {
7342
- const value = await new Promise((accept, reject) => {
7654
+ const value2 = await new Promise((accept, reject) => {
7343
7655
  (0, import_node_child_process5.execFile)("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8", maxBuffer: 16384 }, (error, stdout) => {
7344
7656
  if (error) reject(new Error("code connect requires a Git checkout with an initial commit"));
7345
7657
  else accept(stdout.trim());
7346
7658
  });
7347
7659
  });
7348
- if (!/^[0-9a-f]{40}$/.test(value)) throw new Error("code connect could not resolve the checkout HEAD commit");
7349
- return value;
7660
+ if (!/^[0-9a-f]{40}$/.test(value2)) throw new Error("code connect could not resolve the checkout HEAD commit");
7661
+ return value2;
7350
7662
  }
7351
- function digestText(value) {
7352
- return `sha256:${(0, import_node_crypto.createHash)("sha256").update(value).digest("hex")}`;
7663
+ function digestText(value2) {
7664
+ return `sha256:${(0, import_node_crypto2.createHash)("sha256").update(value2).digest("hex")}`;
7353
7665
  }
7354
7666
 
7355
7667
  // src/code-images.ts
7356
7668
  var import_node_child_process6 = require("child_process");
7357
- var import_node_crypto2 = require("crypto");
7669
+ var import_node_crypto3 = require("crypto");
7358
7670
  var import_promises10 = require("fs/promises");
7359
7671
  var import_node_os3 = require("os");
7360
- var import_node_path11 = require("path");
7672
+ var import_node_path12 = require("path");
7361
7673
  var import_node_url3 = require("url");
7362
7674
 
7363
7675
  // src/code-runtime-config.ts
@@ -7440,13 +7752,13 @@ async function embeddedPiImageName() {
7440
7752
  const bundle = await (0, import_promises10.readFile)(embeddedPiAssetPath()).catch(() => {
7441
7753
  throw new Error("CLI-embedded Pi runtime is missing; reinstall this exact @odla-ai/cli version");
7442
7754
  });
7443
- return `odla-ai/pi-agent:embedded-sha256-${(0, import_node_crypto2.createHash)("sha256").update(bundle).digest("hex")}`;
7755
+ return `odla-ai/pi-agent:embedded-sha256-${(0, import_node_crypto3.createHash)("sha256").update(bundle).digest("hex")}`;
7444
7756
  }
7445
7757
  async function buildEmbeddedPiImage(engine, image, run) {
7446
- const context = await (0, import_promises10.mkdtemp)((0, import_node_path11.join)((0, import_node_os3.tmpdir)(), "odla-code-pi-"));
7758
+ const context = await (0, import_promises10.mkdtemp)((0, import_node_path12.join)((0, import_node_os3.tmpdir)(), "odla-code-pi-"));
7447
7759
  try {
7448
- await (0, import_promises10.copyFile)(embeddedPiAssetPath(), (0, import_node_path11.join)(context, "pi-agent.js"));
7449
- await (0, import_promises10.writeFile)((0, import_node_path11.join)(context, "Dockerfile"), [
7760
+ await (0, import_promises10.copyFile)(embeddedPiAssetPath(), (0, import_node_path12.join)(context, "pi-agent.js"));
7761
+ await (0, import_promises10.writeFile)((0, import_node_path12.join)(context, "Dockerfile"), [
7450
7762
  `FROM ${CODE_NODE_IMAGE}`,
7451
7763
  "COPY pi-agent.js /opt/odla/pi-agent.js",
7452
7764
  "WORKDIR /workspace",
@@ -7462,7 +7774,7 @@ async function buildEmbeddedPiImage(engine, image, run) {
7462
7774
  // src/code-connect.ts
7463
7775
  async function codeConnect(options) {
7464
7776
  const cwd = options.cwd ?? process.cwd();
7465
- const configPath = (0, import_node_path12.resolve)(cwd, options.configPath);
7777
+ const configPath = (0, import_node_path13.resolve)(cwd, options.configPath);
7466
7778
  const cfg = (0, import_node_fs14.existsSync)(configPath) ? await loadProjectConfig(configPath) : null;
7467
7779
  const requestedAppId = options.appId?.trim();
7468
7780
  if (requestedAppId && !/^[a-z0-9][a-z0-9-]{1,62}$/.test(requestedAppId)) {
@@ -7620,8 +7932,8 @@ async function runCodeRuntime(input) {
7620
7932
  for (const [name, handler] of handlers) process.removeListener(name, handler);
7621
7933
  }
7622
7934
  }
7623
- function parseConnection(value, appId, appEnv) {
7624
- const root = record4(value);
7935
+ function parseConnection(value2, appId, appEnv) {
7936
+ const root = record4(value2);
7625
7937
  const host = record4(root?.host);
7626
7938
  const offer = record4(root?.offer);
7627
7939
  const binding = record4(root?.binding);
@@ -7630,12 +7942,12 @@ function parseConnection(value, appId, appEnv) {
7630
7942
  }
7631
7943
  return root;
7632
7944
  }
7633
- function apiFailure(action, status, value) {
7634
- const message2 = record4(record4(value)?.error)?.message;
7635
- return `${action} failed (${status})${typeof message2 === "string" ? `: ${message2}` : ""}`;
7945
+ function apiFailure(action2, status, value2) {
7946
+ const message2 = record4(record4(value2)?.error)?.message;
7947
+ return `${action2} failed (${status})${typeof message2 === "string" ? `: ${message2}` : ""}`;
7636
7948
  }
7637
- function record4(value) {
7638
- return value && typeof value === "object" && !Array.isArray(value) ? value : null;
7949
+ function record4(value2) {
7950
+ return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
7639
7951
  }
7640
7952
 
7641
7953
  // src/code-command.ts
@@ -7694,8 +8006,8 @@ function developerTokenStatus(context, parsed, now = Date.now()) {
7694
8006
  cacheStatus
7695
8007
  };
7696
8008
  }
7697
- function clean3(value) {
7698
- const normalized = value?.trim();
8009
+ function clean3(value2) {
8010
+ const normalized = value2?.trim();
7699
8011
  return normalized || void 0;
7700
8012
  }
7701
8013
 
@@ -7715,9 +8027,9 @@ async function contextCommand(parsed, deps = {}) {
7715
8027
  ],
7716
8028
  3
7717
8029
  );
7718
- const action = parsed.positionals[1];
8030
+ const action2 = parsed.positionals[1];
7719
8031
  const out = deps.stdout ?? console;
7720
- if (action === "list") {
8032
+ if (action2 === "list") {
7721
8033
  const profiles = listOperatorProfiles();
7722
8034
  if (parsed.options.json === true) {
7723
8035
  out.log(JSON.stringify({ schemaVersion: 1, profiles }, null, 2));
@@ -7735,7 +8047,7 @@ async function contextCommand(parsed, deps = {}) {
7735
8047
  }
7736
8048
  return;
7737
8049
  }
7738
- if (action === "save") {
8050
+ if (action2 === "save") {
7739
8051
  if (parsed.options.token !== void 0) {
7740
8052
  throw new Error(
7741
8053
  "context save does not accept --token; contexts store scope metadata only"
@@ -7758,7 +8070,7 @@ async function contextCommand(parsed, deps = {}) {
7758
8070
  }
7759
8071
  return;
7760
8072
  }
7761
- if (action === "remove") {
8073
+ if (action2 === "remove") {
7762
8074
  const name = requireName(parsed);
7763
8075
  if (parsed.options.yes !== true) {
7764
8076
  throw new Error(`context remove "${name}" requires --yes`);
@@ -7778,9 +8090,9 @@ async function contextCommand(parsed, deps = {}) {
7778
8090
  }
7779
8091
  return;
7780
8092
  }
7781
- if (action !== "show") {
8093
+ if (action2 !== "show") {
7782
8094
  throw new Error(
7783
- `unknown context action "${action ?? ""}". Try show|list|save|remove.`
8095
+ `unknown context action "${action2 ?? ""}". Try show|list|save|remove.`
7784
8096
  );
7785
8097
  }
7786
8098
  const context = await resolveOperatorContext(parsed, {
@@ -7831,6 +8143,261 @@ function requireName(parsed) {
7831
8143
  return name;
7832
8144
  }
7833
8145
 
8146
+ // src/help.ts
8147
+ function printHelp(output = console) {
8148
+ output.log(`odla-ai
8149
+
8150
+ Start here:
8151
+ odla-ai runbook ask "<question>" The current procedure, from odla's own
8152
+ runbooks. Ask BEFORE searching the web or
8153
+ working from memory: runbooks are edited
8154
+ live, so your training data is out of date
8155
+ and this is not.
8156
+ odla-ai runbook impact After a change: which runbooks describe the
8157
+ code you just touched, so you can fix any
8158
+ step it made wrong.
8159
+
8160
+ Usage:
8161
+ odla-ai setup [--dir <project>] [--agent <name>] [--global] [--force]
8162
+ odla-ai init --app-id <id> --name <name> [--services db,ai,o11y,calendar] [--env dev --env prod]
8163
+ odla-ai doctor [--config odla.config.mjs]
8164
+ odla-ai config diff [--config odla.config.mjs] [--email <odla-account>] [--json]
8165
+ odla-ai config plan [--config odla.config.mjs] [--email <odla-account>] [--json]
8166
+ odla-ai calendar status [--env dev] [--email <odla-account>] [--json]
8167
+ odla-ai calendar calendars [--env dev] [--email <odla-account>] [--json]
8168
+ odla-ai calendar connect [--env dev] [--email <odla-account>] [--no-open] [--yes]
8169
+ odla-ai calendar disconnect [--env dev] [--email <odla-account>] --yes
8170
+ odla-ai app archive [--config odla.config.mjs] [--email <odla-account>] [--json] --yes
8171
+ odla-ai app restore [--config odla.config.mjs] [--email <odla-account>] [--json]
8172
+ odla-ai app export [--env dev] [--fresh] [--out <file>] [--email <odla-account>] [--json]
8173
+ odla-ai app import <file|-> [--env dev] [--ns <namespace>] [--id-field <f>|--key <attr>|--generate-ids] [--dry-run] [--json] --yes
8174
+ odla-ai app refresh-sandbox [--include-identity] [--include-files] [--dry-run] [--json] --yes
8175
+ odla-ai app go-live [--include-identity] [--include-files] [--dry-run] [--json] --yes
8176
+ odla-ai app promote [--dry-run] [--json] --yes
8177
+ odla-ai app rename <name> [--config odla.config.mjs] [--email <odla-account>] [--json]
8178
+ odla-ai app owners list [--config odla.config.mjs] [--email <odla-account>] [--json]
8179
+ odla-ai app owners add <email> [--email <odla-account>] [--json]
8180
+ odla-ai app owners remove <email> [--email <odla-account>] [--json]
8181
+ odla-ai pm goal list [--app <id>] [--status <s>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
8182
+ odla-ai pm task list [--app <id>] [--column <c>] [--goal <id>] [--assignee <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
8183
+ odla-ai pm decision list [--app <id>] [--status <s>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
8184
+ odla-ai pm bug list [--app <id>] [--status <s>] [--severity <s>] [--goal <id>] [--assignee <id>] [--decision <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
8185
+ odla-ai pm goal add --app <id> --title <t> [--status <s>] [--proof <text>] [--target <pct>] [--mutation-id <id>] [--json]
8186
+ 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]
8187
+ odla-ai pm decision add --app <id> --title <t> --body <text> [--status <s>] [--mutation-id <id>] [--json]
8188
+ 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]
8189
+ odla-ai pm <goal|task|decision|bug> get <id> [--json]
8190
+ odla-ai pm goal set <id> [--title <t>|--status <s>|--proof <text>|--no-proof|--target <pct>|--no-target] [--mutation-id <id>] [--json]
8191
+ 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]
8192
+ odla-ai pm decision set <id> [--title <t>|--status <s>|--body <text>] [--mutation-id <id>] [--json]
8193
+ 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]
8194
+ odla-ai pm <goal|task|decision> done <id> [--mutation-id <id>]
8195
+ odla-ai pm bug done <id> [--decision <accepted-decision-id>] [--mutation-id <id>]
8196
+ odla-ai pm <goal|task|decision|bug> comment <id> --body "..." [--mutation-id <id>]
8197
+ odla-ai pm <goal|task|decision|bug> comments <id> [--json]
8198
+ odla-ai pm <goal|task|decision|bug> rm <id>
8199
+ odla-ai pm handoff --app <id> [--json]
8200
+ odla-ai discuss groups [--json]
8201
+ odla-ai discuss list [--app <id>] [--q <text>] [--state open|resolved|all] [--json]
8202
+ odla-ai discuss read <topic> [--limit <n> --offset <n>] [--json]
8203
+ odla-ai discuss post --app <id> --subject "..." --body "..." [--markup "... @[Label](kind/id)"] [--mutation-id <id>]
8204
+ odla-ai discuss reply <topic> --body "..." [--markup "..."] [--mutation-id <id>]
8205
+ odla-ai discuss resolve <topic> [--reopen] [--mutation-id <id>]
8206
+ odla-ai discuss who --q <text> [--app <id>] [--kinds user,pm:task] [--json]
8207
+ odla-ai discuss watch [<topic>] [--cursor <cursor>] [--by <authorId>] [--self <authorId>] [--interval <s>] [--timeout <s>] [--json|--jsonl]
8208
+ odla-ai agent jobs [--env dev] [--state pending|running|succeeded|dead_letter] [--limit 50] [--email <email>] [--json]
8209
+ odla-ai agent retry <job-id> [--env dev] [--email <email>] [--json]
8210
+ odla-ai context show [--context <name>] [--platform https://odla.ai] [--app <id>] [--env prod] [--json]
8211
+ odla-ai context list [--json]
8212
+ odla-ai context save <name> [--platform <url>] [--app <id>] [--env <name>] [--json]
8213
+ odla-ai context remove <name> --yes [--json]
8214
+ odla-ai o11y status [--app <id>] [--context <name>] [--platform https://odla.ai] [--env prod] [--minutes 60] [--json]
8215
+ odla-ai platform status [--context <name>] [--platform https://odla.ai] [--email <odla-account>] [--json]
8216
+ odla-ai whoami [--context <name>] [--platform https://odla.ai] [--json]
8217
+ odla-ai runbook ask "<question>" [--app <id>] [--all] [--json]
8218
+ odla-ai runbook search "<question>" [--app <id>] [--all] [--limit <n>] [--json]
8219
+ odla-ai runbook impact [--base origin/main] [--app <id>] [--all] [--limit <n>] [--json]
8220
+ odla-ai runbook lint [--app <id>] [--all] [--json]
8221
+ odla-ai runbook list [--app <id>] [--all] [--q <text>] [--json]
8222
+ odla-ai runbook comment <slug> --body "..." [--app <id>]
8223
+ odla-ai runbook get <slug> [--app <id>]
8224
+ odla-ai runbook new <slug> --title <t> --file <path|-> [--summary <s>] [--requires <specs>] [--app <id>]
8225
+ odla-ai runbook edit <slug> [--file <path|->|--body "..."] [--note <why>] [--requires <specs>] [--app <id>]
8226
+ odla-ai runbook import <dir> [--visibility operator|admin] [--dry-run] [--app <id>]
8227
+ odla-ai runbook publish <slug> [--app <id>]
8228
+ odla-ai runbook visibility <slug> <operator|admin> [--app <id>]
8229
+ odla-ai runbook archive <slug> [--app <id>]
8230
+ odla-ai runbook history <slug> [--app <id>] [--json]
8231
+ odla-ai runbook revert <slug> --version <n> [--app <id>]
8232
+ odla-ai runbook rm <slug> [--app <id>]
8233
+ odla-ai capabilities [--json]
8234
+ odla-ai code connect [--env dev|prod] [--email <odla-account>] [--engine auto|container|podman|docker] [--slots <1-64>] [--once]
8235
+ odla-ai admin ai show [--context <name>] [--platform https://odla.ai] [--email <odla-account>] [--json]
8236
+ odla-ai admin ai models [--context <name>] [--provider <id>] [--json]
8237
+ odla-ai admin ai set <purpose> [--context <name>] [--provider <id>] [--model <id>] [--enabled|--no-enabled]
8238
+ [--max-input-bytes <n>] [--max-output-tokens <n>] [--max-calls-per-run <n>] [--json]
8239
+ odla-ai admin ai set security [--context <name>] --discovery-provider <id> --discovery-model <id>
8240
+ --validation-provider <id> --validation-model <id> [--enabled|--no-enabled] [--json]
8241
+ odla-ai admin ai credentials [--context <name>] [--json]
8242
+ odla-ai admin ai credential set <provider> [--context <name>] (--from-env <NAME>|--stdin)
8243
+ odla-ai admin ai usage [--context <name>] [--app-id <id>] [--env <env>] [--run-id <id>] [--limit <1-500>] [--json]
8244
+ odla-ai admin ai audit [--context <name>] [--limit <1-200>] [--json]
8245
+ odla-ai security github connect [--repo owner/name] [--env dev] [--email <odla-account>] [--no-open]
8246
+ odla-ai security github disconnect --source <id> [--env dev] [--yes]
8247
+ odla-ai security plan [--env dev] [--json]
8248
+ odla-ai security sources [--env dev] [--json]
8249
+ odla-ai security run --source <id> --plan-digest <sha256:...> --ack-redacted-source [--ref <branch|tag|sha>] [--env dev] [--no-follow]
8250
+ odla-ai security status <job-id> [--follow] [--json]
8251
+ odla-ai security report <job-id> [--json]
8252
+ odla-ai security run [target] --ack-redacted-source [--env dev] [--profile odla] [--fail-on high]
8253
+ odla-ai security run [target] --self --ack-redacted-source
8254
+ odla-ai provision [--config odla.config.mjs] [--email <odla-account>] [--wait <seconds>] [--dry-run] [--push-secrets] [--rotate-o11y-token] [--write-dev-vars[=path]] [--yes]
8255
+ odla-ai smoke [--config odla.config.mjs] [--env dev] [--email <odla-account>] [--no-open]
8256
+ odla-ai skill install [--dir <project>] [--agent <name>] [--global] [--force]
8257
+ odla-ai secrets push --env <env> [--config odla.config.mjs] [--dry-run] [--yes]
8258
+ odla-ai secrets set <name> --env <env> (--from-env <NAME>|--stdin) [--email <odla-account>] [--config odla.config.mjs] [--yes]
8259
+ odla-ai secrets set-clerk-key --env <env> (--from-env <NAME>|--stdin) [--email <odla-account>] [--config odla.config.mjs] [--yes]
8260
+ odla-ai version
8261
+
8262
+ Commands:
8263
+ agent Inspect durable agent wakeups and explicitly requeue a
8264
+ dead-lettered job; JSON output is stable for remote operators.
8265
+ runbook odla's operational procedures, stored in the database and read at
8266
+ the moment they are followed. "ask" gives a written, cited answer;
8267
+ "search" the passages behind it; "get" the whole document.
8268
+ "impact" diffs your working tree against a base ref and names the
8269
+ runbooks that describe what you changed \u2014 run it after touching a
8270
+ package's exported API or JSDoc, or a Studio surface. "lint"
8271
+ checks the corpus the other way: every odla-ai command the
8272
+ runbooks name is held against this CLI's real command surface,
8273
+ and against the minimum versions each runbook declares. A wrong step
8274
+ is fixed with "edit", which takes effect immediately: a runbook is
8275
+ a row, not a release. Runbooks describe PROCEDURE; what an export
8276
+ does and what it guarantees is JSDoc, rendered per package at
8277
+ https://odla.ai/docs and shipped in the installed .d.ts. Answering
8278
+ a question usually needs both.
8279
+ whoami Report who this terminal is authenticated as, and whether it holds
8280
+ platform admin. A handshake-minted device token is never admin,
8281
+ however the human who approved it is configured.
8282
+ context Explain selected config, platform, app, environment, and
8283
+ developer-token provenance without printing credentials or
8284
+ starting a device handshake. Operator work can run outside a
8285
+ project checkout with explicit flags or ODLA_* environment
8286
+ variables.
8287
+ setup Install offline odla runbooks for common coding-agent harnesses.
8288
+ init Create a generic odla.config.mjs plus starter schema/rules files.
8289
+ doctor Validate and summarize the project config without network calls.
8290
+ config Read-only diff and revision-bound plan for checked-in Registry
8291
+ intent; runtime-owned fields and excluded coverage stay explicit.
8292
+ calendar Inspect, connect, or disconnect the live Google booking connection.
8293
+ app Archive (suspend, data retained), restore, export, import, or
8294
+ manage the co-owners of the app. Archiving takes every
8295
+ environment's data plane down until restored; permanent deletion
8296
+ has NO CLI \u2014 it requires a signed-in owner in Studio, and no
8297
+ machine or agent credential can purge. "app export" downloads a
8298
+ portable gzipped-JSONL snapshot of one database (--fresh takes a
8299
+ new snapshot first) \u2014 your data is always yours to take.
8300
+ "app import" upserts rows back in from JSON, JSONL, or a
8301
+ {namespace: rows} map (the shape a query returns); it writes only
8302
+ with --yes, so the bare command is a dry run, and it refuses to
8303
+ guess an id mode that would duplicate rows on a re-run.
8304
+ "app refresh-sandbox" replaces the sandbox with a copy of live
8305
+ (the routine dev loop); "app go-live" copies the sandbox into an
8306
+ EMPTY live database, once, at launch; "app promote" pushes only
8307
+ schema, rules and gates up afterwards, leaving live's rows alone.
8308
+ Each prints its plan and writes nothing without --yes, so the
8309
+ bare command is the dry run. Clerk config, triggers, allowlists,
8310
+ secrets and API keys never travel; identity rows stay behind
8311
+ unless --include-identity.
8312
+ "app rename <name>" changes only the display name humans read;
8313
+ the app id is permanent (tenants, URLs and keys embed it), so a
8314
+ rename never re-provisions anything or invalidates a credential.
8315
+ "app owners add <email>" grants a signed-up odla member the SAME
8316
+ full access as you; they then run their own "provision" to mint
8317
+ their own credentials for the shared db (the live one included)
8318
+ \u2014 no secret is ever copied between people.
8319
+ capabilities Show what the CLI automates vs agent edits and human checkpoints.
8320
+ code Enroll this Mac/Linux host for the current Studio-connected repository and run Pi.
8321
+ admin Manage platform-funded AI routing/credentials/usage with narrow device grants.
8322
+ security Connect GitHub sources and run commit-pinned hosted reviews, or scan a local snapshot.
8323
+ pm Project management (via @odla-ai/pm) shared across the apps you
8324
+ co-own: conformance goals, kanban tasks, decisions, and bugs. With
8325
+ no --app, "list" spans every co-owned project (the cross-project
8326
+ view); with --app it scopes to one. Same device-grant auth as
8327
+ "app". Entities: goal (alias conformance), task (alias kanban),
8328
+ decision, bug. Status changes and comments post to each item's
8329
+ @odla-ai/chat discussion thread.
8330
+ discuss Group discussions (via @odla-ai/chat) for the apps you co-own:
8331
+ one group per project, topics with replies, @-mentions of people,
8332
+ agents, PM items, and projects. Built for unattended use \u2014 post a
8333
+ question, then "watch" it until someone answers. "watch" exits 75
8334
+ (not 1) when it times out with nothing new, so a script can tell
8335
+ "no answer yet" from a failure and just rerun. Use "who" to look
8336
+ up the exact @[Label](kind/id) markup for a mention.
8337
+ o11y Read one stable status envelope for application RED, current
8338
+ live-sync load/freshness, the protected commit-to-visible
8339
+ canary, collector ingest/scheduler trust, Cloudflare-owned
8340
+ runtime metrics, and a machine verdict.
8341
+ --json keeps auth progress on stderr for unattended agents.
8342
+ platform Read canonical fleet health, releases, provider load/freshness,
8343
+ explicit unknowns, and next actions through a read-only grant.
8344
+ provision Register services, compose integrations, persist credentials, optionally push secrets.
8345
+ smoke Verify credentials, public-config, composed schema, db aggregate, and integration probes.
8346
+ skill Same installer; --agent accepts all, claude, codex, cursor,
8347
+ copilot, gemini, or agents (repeatable or comma-separated).
8348
+ secrets Push configured db/o11y secrets into the Worker via wrangler
8349
+ stdin; set stores a tenant-vault secret and set-clerk-key the
8350
+ reserved Clerk secret key, write-only from stdin or an env var.
8351
+ version Print the CLI version.
8352
+
8353
+ Safety:
8354
+ Every app has two databases on odla.ai: a sandbox (env "dev", tenant
8355
+ <appId>--dev) and a live one (env "prod", tenant <appId>). Both are served by
8356
+ production odla.ai \u2014 there is no separate odla to point at. New projects get
8357
+ the sandbox only. Add "prod" explicitly to odla.config.mjs and pass --yes to
8358
+ provision the live database; use --dry-run first to inspect the resolved plan.
8359
+ Provision caches the approved developer token and service credentials under
8360
+ .odla/ with mode 0600, and init adds those paths to .gitignore. Secret push
8361
+ preflights Wrangler before any shown-once issuance or destructive rotation.
8362
+ Projectless PM, Discussions, o11y, runbook, and identity commands use
8363
+ --platform/--app/--env, ODLA_PLATFORM_URL/ODLA_APP_ID/ODLA_ENV, and
8364
+ ODLA_DEV_TOKEN. Save non-secret scope metadata with "context save", then
8365
+ select it explicitly with --context or ODLA_CONTEXT. Each named context gets
8366
+ isolated developer and scoped-token caches. Override their locations with
8367
+ ODLA_DEV_TOKEN_FILE and ODLA_ADMIN_TOKEN_FILE; ODLA_CONTEXT_FILE relocates
8368
+ the metadata file. Flags and specific ODLA_* scope variables beat a selected
8369
+ context, which beats project config. There is no ambient current context.
8370
+ "context show" reports only provenance and cache state and never authenticates.
8371
+ Provision opens the approval page in your browser automatically whenever the
8372
+ machine can show one, including agent-driven runs; only CI, SSH, and
8373
+ display-less hosts skip it. Use --open to force or --no-open to suppress.
8374
+ Browser launch is best-effort: the printed approval URL is authoritative and
8375
+ agents must relay it to the human verbatim. A started handshake is persisted
8376
+ under .odla/, so a command killed mid-wait loses nothing \u2014 rerunning resumes
8377
+ the same code. Outside an interactive terminal the wait is capped (90s by
8378
+ default, --wait <seconds> to change); a still-pending handshake then exits
8379
+ with code 75: relay the URL, wait for approval, and re-run to collect.
8380
+ A fresh device handshake requires --email <odla-account> or ODLA_USER_EMAIL.
8381
+ The email is a non-secret identity hint: never provide a password or session
8382
+ token. The matching account must already exist, be signed in, explicitly
8383
+ review the exact code, and finish any current request before claiming another.
8384
+ Run Code from a GitHub checkout already connected to an app in Studio; an
8385
+ odla.config.mjs may select the app explicitly but is not required. Code host
8386
+ approval and credential hashes live in odla-ai/db. The host
8387
+ credential is never written under .odla/; it exists only in the foreground
8388
+ "code connect" process and is rotated by the next approved connection.
8389
+ Calendar setup has a second human checkpoint: odla issues a state-bound Google consent URL;
8390
+ OAuth codes and refresh tokens never enter the CLI, repo, chat, or app.
8391
+ GitHub security uses source-read-only access plus optional metadata-only Checks write: the CLI never asks
8392
+ for a PAT or provider key. GitHub read access is separate from the explicit
8393
+ --ack-redacted-source consent and the exact --plan-digest printed by security
8394
+ plan are required before bounded snippets reach System AI.
8395
+ Run security plan first to inspect the admin-selected providers, models,
8396
+ per-route bounds, credential readiness, retention, no-execution boundary,
8397
+ and digest that binds consent to that exact plan.
8398
+ `);
8399
+ }
8400
+
7834
8401
  // src/discuss-actions.ts
7835
8402
  var writeMutationId = (parsed) => stringOpt(parsed.options["mutation-id"]) ?? crypto.randomUUID();
7836
8403
  async function request(ctx, method, path, body) {
@@ -7844,8 +8411,8 @@ async function request(ctx, method, path, body) {
7844
8411
  throw new Error(`discuss ${method} ${path} failed: ${data.error ?? `registry returned ${res.status}`}`);
7845
8412
  return data;
7846
8413
  }
7847
- function emit(ctx, value, human) {
7848
- if (ctx.json) ctx.out.log(JSON.stringify(value, null, 2));
8414
+ function emit(ctx, value2, human) {
8415
+ if (ctx.json) ctx.out.log(JSON.stringify(value2, null, 2));
7849
8416
  else human();
7850
8417
  }
7851
8418
  var state = (topic) => topic.resolved ? "resolved" : "open";
@@ -7890,8 +8457,8 @@ async function discussList(ctx, parsed) {
7890
8457
  ["limit", "limit"],
7891
8458
  ["offset", "offset"]
7892
8459
  ]) {
7893
- const value = stringOpt(parsed.options[flag]);
7894
- if (value) query.set(param, value);
8460
+ const value2 = stringOpt(parsed.options[flag]);
8461
+ if (value2) query.set(param, value2);
7895
8462
  }
7896
8463
  const qs = query.toString();
7897
8464
  const page = await request(ctx, "GET", `/topics${qs ? `?${qs}` : ""}`);
@@ -8088,11 +8655,11 @@ var MAX_CONSECUTIVE_FAILURES = 5;
8088
8655
  var MAX_BACKOFF_MS = 3e4;
8089
8656
  function numberOpt2(parsed, flag, fallback) {
8090
8657
  const raw = stringOpt(parsed.options[flag]);
8091
- const value = raw == null ? NaN : Number(raw);
8092
- return Number.isFinite(value) && value > 0 ? value : fallback;
8658
+ const value2 = raw == null ? NaN : Number(raw);
8659
+ return Number.isFinite(value2) && value2 > 0 ? value2 : fallback;
8093
8660
  }
8094
- function jsonl(ctx, parsed, value) {
8095
- if (parsed.options.jsonl === true) ctx.out.log(JSON.stringify({ v: 1, ...value }));
8661
+ function jsonl(ctx, parsed, value2) {
8662
+ if (parsed.options.jsonl === true) ctx.out.log(JSON.stringify({ v: 1, ...value2 }));
8096
8663
  }
8097
8664
  async function discussWatch(ctx, topicId, parsed) {
8098
8665
  if (ctx.json && parsed.options.jsonl === true) throw new Error("--json and --jsonl cannot be combined");
@@ -8252,8 +8819,8 @@ var ALLOWED = [
8252
8819
  "platform",
8253
8820
  "context"
8254
8821
  ];
8255
- function requireId(id, action) {
8256
- if (!id) throw new Error(`"discuss ${action}" needs a topic id`);
8822
+ function requireId(id, action2) {
8823
+ if (!id) throw new Error(`"discuss ${action2}" needs a topic id`);
8257
8824
  return id;
8258
8825
  }
8259
8826
  async function buildContext(parsed, deps) {
@@ -8287,11 +8854,11 @@ async function buildContext(parsed, deps) {
8287
8854
  }
8288
8855
  async function discussCommand(parsed, deps = {}) {
8289
8856
  assertArgs(parsed, ALLOWED, 3);
8290
- const action = parsed.positionals[1];
8857
+ const action2 = parsed.positionals[1];
8291
8858
  const id = parsed.positionals[2];
8292
- if (!action) throw new Error('"discuss" needs an action. Run "odla-ai help".');
8859
+ if (!action2) throw new Error('"discuss" needs an action. Run "odla-ai help".');
8293
8860
  const ctx = await buildContext(parsed, deps);
8294
- switch (action) {
8861
+ switch (action2) {
8295
8862
  case "groups":
8296
8863
  return discussGroups(ctx);
8297
8864
  case "list":
@@ -8313,7 +8880,7 @@ async function discussCommand(parsed, deps = {}) {
8313
8880
  return;
8314
8881
  }
8315
8882
  default:
8316
- throw new Error(`unknown discuss action "${action}". Run "odla-ai help".`);
8883
+ throw new Error(`unknown discuss action "${action2}". Run "odla-ai help".`);
8317
8884
  }
8318
8885
  }
8319
8886
 
@@ -8354,13 +8921,13 @@ async function pmRequest(ctx, method, path, body) {
8354
8921
  function collectFields(parsed, allowClear) {
8355
8922
  const out = {};
8356
8923
  for (const [flag, spec] of Object.entries(FIELD_MAP)) {
8357
- const value = parsed.options[flag];
8358
- if (value === void 0 || value === true) continue;
8359
- if (value === false) {
8924
+ const value2 = parsed.options[flag];
8925
+ if (value2 === void 0 || value2 === true) continue;
8926
+ if (value2 === false) {
8360
8927
  if (allowClear) out[spec.key] = null;
8361
8928
  continue;
8362
8929
  }
8363
- const text = stringOpt(value);
8930
+ const text = stringOpt(value2);
8364
8931
  out[spec.key] = spec.num ? Number(text) : text;
8365
8932
  }
8366
8933
  return out;
@@ -8381,8 +8948,8 @@ function statusCol(entity, r) {
8381
8948
  function printRecord(ctx, entity, r) {
8382
8949
  ctx.out.log(`${r.id} [${statusCol(entity, r)}] ${r.appId} ${r.title ?? ""}`);
8383
8950
  }
8384
- function emit2(ctx, value, human) {
8385
- if (ctx.json) ctx.out.log(JSON.stringify(value, null, 2));
8951
+ function emit2(ctx, value2, human) {
8952
+ if (ctx.json) ctx.out.log(JSON.stringify(value2, null, 2));
8386
8953
  else human();
8387
8954
  }
8388
8955
  async function pmList(ctx, entity, parsed) {
@@ -8428,8 +8995,8 @@ async function pmAdd(ctx, entity, parsed) {
8428
8995
  emit2(ctx, res, () => ctx.out.log(`created ${entity} ${res.id}`));
8429
8996
  }
8430
8997
  async function pmGet(ctx, entity, id) {
8431
- const { record: record7 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id)}`);
8432
- emit2(ctx, record7, () => printRecord(ctx, entity, record7));
8998
+ const { record: record8 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id)}`);
8999
+ emit2(ctx, record8, () => printRecord(ctx, entity, record8));
8433
9000
  }
8434
9001
  async function pmSet(ctx, entity, id, parsed) {
8435
9002
  const patch2 = collectEntityFields(entity, parsed, true);
@@ -8474,9 +9041,9 @@ async function pmHandoff(ctx, parsed) {
8474
9041
  ]);
8475
9042
  const handoff = {
8476
9043
  appId,
8477
- unmetGoals: goals.filter((record7) => record7.status !== "met"),
8478
- activeTasks: tasks.filter((record7) => record7.column !== "done"),
8479
- openBugs: bugs.filter((record7) => record7.status !== "fixed" && record7.status !== "wontfix")
9044
+ unmetGoals: goals.filter((record8) => record8.status !== "met"),
9045
+ activeTasks: tasks.filter((record8) => record8.column !== "done"),
9046
+ openBugs: bugs.filter((record8) => record8.status !== "fixed" && record8.status !== "wontfix")
8480
9047
  };
8481
9048
  const result = {
8482
9049
  ...handoff,
@@ -8495,10 +9062,10 @@ async function pmHandoff(ctx, parsed) {
8495
9062
  ]) {
8496
9063
  ctx.out.log(`${label}:`);
8497
9064
  if (!records.length) ctx.out.log("- (none)");
8498
- else for (const record7 of records) printRecord(
9065
+ else for (const record8 of records) printRecord(
8499
9066
  ctx,
8500
9067
  label === "unmet goals" ? "goal" : label === "active tasks" ? "task" : "bug",
8501
- record7
9068
+ record8
8502
9069
  );
8503
9070
  }
8504
9071
  });
@@ -8574,18 +9141,18 @@ var ENTITY_OPTIONS = {
8574
9141
  done: ["decision"]
8575
9142
  }
8576
9143
  };
8577
- function canonicalAction(action) {
8578
- if (action === "create") return "add";
8579
- if (action === "update" || action === "status" || action === "move") return "set";
8580
- if (action === "delete") return "rm";
8581
- return action in ACTION_OPTIONS ? action : null;
8582
- }
8583
- function allowedOptions(entity, action) {
8584
- const entityOptions = action === "list" || action === "add" || action === "set" || action === "done" ? ENTITY_OPTIONS[entity][action] : [];
8585
- return [...COMMON_OPTIONS, ...ACTION_OPTIONS[action], ...entityOptions];
8586
- }
8587
- function requireId2(id, action) {
8588
- if (!id) throw new Error(`"pm ... ${action}" needs an item id`);
9144
+ function canonicalAction(action2) {
9145
+ if (action2 === "create") return "add";
9146
+ if (action2 === "update" || action2 === "status" || action2 === "move") return "set";
9147
+ if (action2 === "delete") return "rm";
9148
+ return action2 in ACTION_OPTIONS ? action2 : null;
9149
+ }
9150
+ function allowedOptions(entity, action2) {
9151
+ const entityOptions = action2 === "list" || action2 === "add" || action2 === "set" || action2 === "done" ? ENTITY_OPTIONS[entity][action2] : [];
9152
+ return [...COMMON_OPTIONS, ...ACTION_OPTIONS[action2], ...entityOptions];
9153
+ }
9154
+ function requireId2(id, action2) {
9155
+ if (!id) throw new Error(`"pm ... ${action2}" needs an item id`);
8589
9156
  return id;
8590
9157
  }
8591
9158
  async function buildContext2(parsed, deps) {
@@ -8621,29 +9188,136 @@ async function pmCommand(parsed, deps = {}) {
8621
9188
  const entity = ALIASES[word];
8622
9189
  if (!entity) throw new Error(`unknown pm entity "${word}". Try "odla-ai pm bug list" (goal|task|decision|bug).`);
8623
9190
  const requestedAction = parsed.positionals[2] ?? "list";
8624
- const action = canonicalAction(requestedAction);
8625
- if (!action) throw new Error(`unknown pm action "${requestedAction}". Try list|add|get|set|done|comment|comments|rm.`);
8626
- assertArgs(parsed, allowedOptions(entity, action), 4);
9191
+ const action2 = canonicalAction(requestedAction);
9192
+ if (!action2) throw new Error(`unknown pm action "${requestedAction}". Try list|add|get|set|done|comment|comments|rm.`);
9193
+ assertArgs(parsed, allowedOptions(entity, action2), 4);
8627
9194
  const ctx = await buildContext2(parsed, deps);
8628
9195
  const id = parsed.positionals[3];
8629
- switch (action) {
9196
+ switch (action2) {
8630
9197
  case "list":
8631
9198
  return pmList(ctx, entity, parsed);
8632
9199
  case "add":
8633
9200
  return pmAdd(ctx, entity, parsed);
8634
9201
  case "get":
8635
- return pmGet(ctx, entity, requireId2(id, action));
9202
+ return pmGet(ctx, entity, requireId2(id, action2));
8636
9203
  case "set":
8637
- return pmSet(ctx, entity, requireId2(id, action), parsed);
9204
+ return pmSet(ctx, entity, requireId2(id, action2), parsed);
8638
9205
  case "done":
8639
- return pmDone(ctx, entity, requireId2(id, action), parsed);
9206
+ return pmDone(ctx, entity, requireId2(id, action2), parsed);
8640
9207
  case "comment":
8641
- return pmComment(ctx, entity, requireId2(id, action), parsed);
9208
+ return pmComment(ctx, entity, requireId2(id, action2), parsed);
8642
9209
  case "comments":
8643
- return pmComments(ctx, entity, requireId2(id, action));
9210
+ return pmComments(ctx, entity, requireId2(id, action2));
8644
9211
  case "rm":
8645
- return pmRemove(ctx, entity, requireId2(id, action));
9212
+ return pmRemove(ctx, entity, requireId2(id, action2));
9213
+ }
9214
+ }
9215
+
9216
+ // src/platform-output.ts
9217
+ function printPlatformStatus(status, out) {
9218
+ out.log(`platform ${status.verdict.status} ${status.observedAt}`);
9219
+ out.log(
9220
+ `fleet ${status.summary.healthy}/${status.summary.services} healthy ${status.summary.required} required ${status.summary.unreachable} unreachable ${status.summary.notConfigured} not configured`
9221
+ );
9222
+ out.log(`catalog ${status.catalog.revision}`);
9223
+ out.log(
9224
+ "service required health probe ms version provider age requests errors cpu p99 us wall p99 us"
9225
+ );
9226
+ for (const service of status.services) {
9227
+ const metrics = service.provider.metrics;
9228
+ out.log([
9229
+ service.id,
9230
+ service.required ? "yes" : "no",
9231
+ service.health.status,
9232
+ value(service.health.latencyMs),
9233
+ service.release.observedVersionId ?? "unknown",
9234
+ service.provider.status,
9235
+ age(service.provider.ageMs),
9236
+ value(metrics?.requests),
9237
+ value(metrics?.errors),
9238
+ value(metrics?.cpuTimeP99),
9239
+ value(metrics?.wallTimeP99)
9240
+ ].join(" "));
9241
+ }
9242
+ if (status.verdict.reasons.length) {
9243
+ out.log(`reasons ${status.verdict.reasons.join(", ")}`);
9244
+ }
9245
+ for (const action2 of status.nextActions) {
9246
+ out.log(`next ${action2.service} ${action2.code} ${action2.message}`);
9247
+ }
9248
+ }
9249
+ function value(input) {
9250
+ return input === null || input === void 0 ? "unknown" : String(input);
9251
+ }
9252
+ function age(input) {
9253
+ if (input === null) return "unknown";
9254
+ if (input < 1e3) return `${input}ms`;
9255
+ if (input < 6e4) return `${Math.round(input / 1e3)}s`;
9256
+ return `${Math.round(input / 6e4)}m`;
9257
+ }
9258
+
9259
+ // src/platform-command.ts
9260
+ async function platformCommand(parsed, deps = {}) {
9261
+ assertArgs(
9262
+ parsed,
9263
+ ["config", "context", "platform", "token", "email", "open", "json"],
9264
+ 2
9265
+ );
9266
+ const action2 = parsed.positionals[1];
9267
+ if (action2 !== "status") {
9268
+ throw new Error(
9269
+ `unknown platform action "${action2 ?? ""}". Try "odla-ai platform status --json".`
9270
+ );
9271
+ }
9272
+ const context = await resolveOperatorContext(parsed, {
9273
+ allowMissingConfig: true
9274
+ });
9275
+ const platform = context.platform.value;
9276
+ const doFetch = deps.fetch ?? fetch;
9277
+ const out = deps.stdout ?? console;
9278
+ const token = await resolveAdminPlatformToken({
9279
+ platform,
9280
+ scope: "platform:status:read",
9281
+ token: stringOpt(parsed.options.token),
9282
+ tokenFile: context.credentials.scopedTokenFile,
9283
+ email: stringOpt(parsed.options.email),
9284
+ open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
9285
+ fetch: doFetch,
9286
+ stdout: out,
9287
+ openApprovalUrl: deps.openUrl,
9288
+ label: "odla CLI (platform fleet status)"
9289
+ });
9290
+ const response2 = await doFetch(`${platform}/registry/platform/status`, {
9291
+ headers: { authorization: `Bearer ${token}` }
9292
+ });
9293
+ const body = await response2.json().catch(() => null);
9294
+ if (!response2.ok) {
9295
+ throw new Error(
9296
+ `read platform status failed (HTTP ${response2.status}): ${apiMessage(body)}`
9297
+ );
9298
+ }
9299
+ if (!isPlatformStatus(body)) {
9300
+ throw new Error("platform returned an invalid odla.platform-status/v1 envelope");
8646
9301
  }
9302
+ if (parsed.options.json === true) {
9303
+ out.log(JSON.stringify(body, null, 2));
9304
+ } else {
9305
+ printPlatformStatus(body, out);
9306
+ }
9307
+ }
9308
+ function isPlatformStatus(value2) {
9309
+ if (!record5(value2) || value2.schemaVersion !== "odla.platform-status/v1") return false;
9310
+ if (!record5(value2.verdict) || !Array.isArray(value2.verdict.reasons)) return false;
9311
+ if (!record5(value2.catalog) || !record5(value2.summary)) return false;
9312
+ return Array.isArray(value2.services) && Array.isArray(value2.nextActions);
9313
+ }
9314
+ function apiMessage(value2) {
9315
+ if (!record5(value2)) return "request failed";
9316
+ const error = record5(value2.error) ? value2.error : value2;
9317
+ return typeof error.message === "string" ? error.message : typeof error.code === "string" ? error.code : "request failed";
9318
+ }
9319
+ function record5(value2) {
9320
+ return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
8647
9321
  }
8648
9322
 
8649
9323
  // src/o11y-verdict.ts
@@ -8683,7 +9357,7 @@ function statusVerdict(reads) {
8683
9357
  severity: "degraded"
8684
9358
  });
8685
9359
  }
8686
- const performance = record5(reads.liveSync.body.performance) ? reads.liveSync.body.performance : null;
9360
+ const performance = record6(reads.liveSync.body.performance) ? reads.liveSync.body.performance : null;
8687
9361
  if (performance?.status === "unavailable") {
8688
9362
  reasons.push({
8689
9363
  source: "liveSync",
@@ -8764,11 +9438,11 @@ function statusVerdict(reads) {
8764
9438
  reasons
8765
9439
  };
8766
9440
  }
8767
- function record5(value) {
8768
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
9441
+ function record6(value2) {
9442
+ return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
8769
9443
  }
8770
- function numeric2(value) {
8771
- return typeof value === "number" && Number.isFinite(value) ? value : 0;
9444
+ function numeric2(value2) {
9445
+ return typeof value2 === "number" && Number.isFinite(value2) ? value2 : 0;
8772
9446
  }
8773
9447
  function collectorReason(read3, fallback) {
8774
9448
  const reasons = read3.body.reasons;
@@ -8792,7 +9466,7 @@ function printO11yStatus(status, out) {
8792
9466
  out.log(
8793
9467
  `o11y status ${status.scope.appId}/${status.scope.env} (${status.scope.minutes}m)`
8794
9468
  );
8795
- const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record6) : [];
9469
+ const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record7) : [];
8796
9470
  const requests = routes.reduce(
8797
9471
  (total, row) => total + numeric3(row.requests),
8798
9472
  0
@@ -8804,39 +9478,39 @@ function printO11yStatus(status, out) {
8804
9478
  out.log(
8805
9479
  `application ${status.application.httpStatus} ${requests} requests ${errors} errors`
8806
9480
  );
8807
- const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(record6) : [];
9481
+ const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(record7) : [];
8808
9482
  out.log(
8809
9483
  `application-versions ${status.applicationVersions.httpStatus} ${versions.length ? versions.slice(0, 5).map(
8810
9484
  (row) => `${String(row.value || "(unattributed)")}:${numeric3(row.requests)}`
8811
9485
  ).join(", ") : "none observed"}`
8812
9486
  );
8813
9487
  out.log(liveSyncLine(status.liveSync));
8814
- const canaryDurations = record6(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
9488
+ const canaryDurations = record7(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
8815
9489
  out.log(
8816
9490
  `canary ${status.canary.httpStatus} ${String(status.canary.body.status ?? status.canary.body.error ?? "unavailable")} ${optionalNumeric(canaryDurations.publishToVisibleMs)} publish-to-visible`
8817
9491
  );
8818
- const collectorIngest = record6(status.collector.body.ingest) ? status.collector.body.ingest : {};
8819
- const collectorStorage = record6(collectorIngest.storage) ? collectorIngest.storage : {};
9492
+ const collectorIngest = record7(status.collector.body.ingest) ? status.collector.body.ingest : {};
9493
+ const collectorStorage = record7(collectorIngest.storage) ? collectorIngest.storage : {};
8820
9494
  out.log(
8821
9495
  `collector ${status.collector.httpStatus} ${String(status.collector.body.status ?? status.collector.body.error ?? "unavailable")} ${numeric3(collectorStorage.affectedPoints)} affected points`
8822
9496
  );
8823
- const providerMetrics = record6(status.provider.body.metrics) ? status.provider.body.metrics : {};
8824
- const providerCapacity = record6(status.provider.body.capacity) ? status.provider.body.capacity : {};
8825
- const workerMemory = record6(providerCapacity.memory) ? providerCapacity.memory : {};
9497
+ const providerMetrics = record7(status.provider.body.metrics) ? status.provider.body.metrics : {};
9498
+ const providerCapacity = record7(status.provider.body.capacity) ? status.provider.body.capacity : {};
9499
+ const workerMemory = record7(providerCapacity.memory) ? providerCapacity.memory : {};
8826
9500
  out.log(
8827
9501
  `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`
8828
9502
  );
8829
9503
  for (const line of providerCapacityLines(status.providerCapacity)) {
8830
9504
  out.log(line);
8831
9505
  }
8832
- const coverage = record6(status.providerReconciliation.body.comparison) ? status.providerReconciliation.body.comparison : {};
8833
- const coverageCounts = record6(status.providerReconciliation.body.counts) ? status.providerReconciliation.body.counts : {};
8834
- const coverageBudget = record6(status.providerReconciliation.body.budget) ? status.providerReconciliation.body.budget : {};
9506
+ const coverage = record7(status.providerReconciliation.body.comparison) ? status.providerReconciliation.body.comparison : {};
9507
+ const coverageCounts = record7(status.providerReconciliation.body.counts) ? status.providerReconciliation.body.counts : {};
9508
+ const coverageBudget = record7(status.providerReconciliation.body.budget) ? status.providerReconciliation.body.budget : {};
8835
9509
  out.log(
8836
9510
  `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`
8837
9511
  );
8838
9512
  const providerPoints = Array.isArray(status.providerHistory.body.points) ? status.providerHistory.body.points.length : 0;
8839
- const providerFreshness = record6(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
9513
+ const providerFreshness = record7(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
8840
9514
  out.log(
8841
9515
  `cloudflare-history ${status.providerHistory.httpStatus} ${String(status.providerHistory.body.status ?? status.providerHistory.body.error ?? "unavailable")} ${providerPoints} snapshots ${optionalAge(providerFreshness.ageMs)} old`
8842
9516
  );
@@ -8845,17 +9519,17 @@ function printO11yStatus(status, out) {
8845
9519
  );
8846
9520
  }
8847
9521
  function providerCapacityLines(read3) {
8848
- const resources = record6(read3.body.resources) ? read3.body.resources : {};
8849
- const durableObjects = record6(resources.durableObjects) ? resources.durableObjects : {};
8850
- const periodic = record6(durableObjects.periodic) ? durableObjects.periodic : {};
8851
- const storage = record6(durableObjects.sqliteStorage) ? durableObjects.sqliteStorage : {};
8852
- const d1 = record6(resources.d1) ? resources.d1 : {};
8853
- const d1Activity = record6(d1.activity) ? d1.activity : {};
8854
- const d1Storage = record6(d1.storage) ? d1.storage : {};
8855
- const d1Latency = record6(d1Activity.latency) ? d1Activity.latency : {};
8856
- const r2 = record6(resources.r2) ? resources.r2 : {};
8857
- const r2Operations = record6(r2.operations) ? r2.operations : {};
8858
- const r2Storage = record6(r2.storage) ? r2.storage : {};
9522
+ const resources = record7(read3.body.resources) ? read3.body.resources : {};
9523
+ const durableObjects = record7(resources.durableObjects) ? resources.durableObjects : {};
9524
+ const periodic = record7(durableObjects.periodic) ? durableObjects.periodic : {};
9525
+ const storage = record7(durableObjects.sqliteStorage) ? durableObjects.sqliteStorage : {};
9526
+ const d1 = record7(resources.d1) ? resources.d1 : {};
9527
+ const d1Activity = record7(d1.activity) ? d1.activity : {};
9528
+ const d1Storage = record7(d1.storage) ? d1.storage : {};
9529
+ const d1Latency = record7(d1Activity.latency) ? d1Activity.latency : {};
9530
+ const r2 = record7(resources.r2) ? resources.r2 : {};
9531
+ const r2Operations = record7(r2.operations) ? r2.operations : {};
9532
+ const r2Storage = record7(r2.storage) ? r2.storage : {};
8859
9533
  const status = String(
8860
9534
  read3.body.status ?? read3.body.error ?? "unavailable"
8861
9535
  );
@@ -8866,42 +9540,42 @@ function providerCapacityLines(read3) {
8866
9540
  ];
8867
9541
  }
8868
9542
  function liveSyncLine(read3) {
8869
- const performance = record6(read3.body.performance) ? read3.body.performance : {};
8870
- const commitToSend = record6(performance.commitToSend) ? performance.commitToSend : {};
9543
+ const performance = record7(read3.body.performance) ? read3.body.performance : {};
9544
+ const commitToSend = record7(performance.commitToSend) ? performance.commitToSend : {};
8871
9545
  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`;
8872
9546
  }
8873
- function record6(value) {
8874
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
9547
+ function record7(value2) {
9548
+ return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
8875
9549
  }
8876
- function numeric3(value) {
8877
- return typeof value === "number" && Number.isFinite(value) ? value : 0;
9550
+ function numeric3(value2) {
9551
+ return typeof value2 === "number" && Number.isFinite(value2) ? value2 : 0;
8878
9552
  }
8879
- function optionalNumeric(value) {
8880
- return typeof value === "number" && Number.isFinite(value) ? `${value} ms` : "\u2014";
9553
+ function optionalNumeric(value2) {
9554
+ return typeof value2 === "number" && Number.isFinite(value2) ? `${value2} ms` : "\u2014";
8881
9555
  }
8882
- function optionalAge(value) {
8883
- if (typeof value !== "number" || !Number.isFinite(value)) return "unknown";
8884
- return `${Math.max(0, Math.round(value / 1e3))}s`;
9556
+ function optionalAge(value2) {
9557
+ if (typeof value2 !== "number" || !Number.isFinite(value2)) return "unknown";
9558
+ return `${Math.max(0, Math.round(value2 / 1e3))}s`;
8885
9559
  }
8886
- function optionalPercent(value) {
8887
- return typeof value === "number" && Number.isFinite(value) ? `${Math.round(value * 100)}%` : "\u2014";
9560
+ function optionalPercent(value2) {
9561
+ return typeof value2 === "number" && Number.isFinite(value2) ? `${Math.round(value2 * 100)}%` : "\u2014";
8888
9562
  }
8889
- function optionalBytes(value) {
8890
- if (typeof value !== "number" || !Number.isFinite(value)) return "\u2014";
8891
- if (value >= 1024 * 1024 * 1024) {
8892
- return `${(value / (1024 * 1024 * 1024)).toFixed(2)} GiB`;
9563
+ function optionalBytes(value2) {
9564
+ if (typeof value2 !== "number" || !Number.isFinite(value2)) return "\u2014";
9565
+ if (value2 >= 1024 * 1024 * 1024) {
9566
+ return `${(value2 / (1024 * 1024 * 1024)).toFixed(2)} GiB`;
8893
9567
  }
8894
- if (value >= 1024 * 1024) {
8895
- return `${(value / (1024 * 1024)).toFixed(1)} MiB`;
9568
+ if (value2 >= 1024 * 1024) {
9569
+ return `${(value2 / (1024 * 1024)).toFixed(1)} MiB`;
8896
9570
  }
8897
- if (value >= 1024) return `${(value / 1024).toFixed(1)} KiB`;
8898
- return `${Math.round(value)} B`;
9571
+ if (value2 >= 1024) return `${(value2 / 1024).toFixed(1)} KiB`;
9572
+ return `${Math.round(value2)} B`;
8899
9573
  }
8900
- function optionalCount(value, unit) {
8901
- return typeof value === "number" && Number.isFinite(value) ? `${value} ${unit}` : `\u2014 ${unit}`;
9574
+ function optionalCount(value2, unit) {
9575
+ return typeof value2 === "number" && Number.isFinite(value2) ? `${value2} ${unit}` : `\u2014 ${unit}`;
8902
9576
  }
8903
- function optionalAgeSeconds(value) {
8904
- return typeof value === "number" && Number.isFinite(value) ? `${Math.max(0, Math.round(value))}s` : "unknown";
9577
+ function optionalAgeSeconds(value2) {
9578
+ return typeof value2 === "number" && Number.isFinite(value2) ? `${Math.max(0, Math.round(value2))}s` : "unknown";
8905
9579
  }
8906
9580
 
8907
9581
  // src/o11y-command.ts
@@ -8921,10 +9595,10 @@ async function o11yCommand(parsed, deps = {}) {
8921
9595
  ],
8922
9596
  2
8923
9597
  );
8924
- const action = parsed.positionals[1];
8925
- if (action !== "status") {
9598
+ const action2 = parsed.positionals[1];
9599
+ if (action2 !== "status") {
8926
9600
  throw new Error(
8927
- `unknown o11y action "${action ?? ""}". Try "odla-ai o11y status --json".`
9601
+ `unknown o11y action "${action2 ?? ""}". Try "odla-ai o11y status --json".`
8928
9602
  );
8929
9603
  }
8930
9604
  const minutes = statusMinutes(
@@ -9031,11 +9705,11 @@ async function o11yCommand(parsed, deps = {}) {
9031
9705
  }
9032
9706
  printO11yStatus(status, out);
9033
9707
  }
9034
- function statusMinutes(value) {
9035
- if (!Number.isSafeInteger(value) || value < 1 || value > 7 * 24 * 60) {
9708
+ function statusMinutes(value2) {
9709
+ if (!Number.isSafeInteger(value2) || value2 < 1 || value2 > 7 * 24 * 60) {
9036
9710
  throw new Error("--minutes must be an integer from 1 to 10080");
9037
9711
  }
9038
- return value;
9712
+ return value2;
9039
9713
  }
9040
9714
  async function read2(url, headers, doFetch) {
9041
9715
  const response2 = await doFetch(url, { headers });
@@ -9043,8 +9717,8 @@ async function read2(url, headers, doFetch) {
9043
9717
  let body = {};
9044
9718
  if (text) {
9045
9719
  try {
9046
- const value = JSON.parse(text);
9047
- body = value && typeof value === "object" && !Array.isArray(value) ? value : { value };
9720
+ const value2 = JSON.parse(text);
9721
+ body = value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : { value: value2 };
9048
9722
  } catch {
9049
9723
  body = { message: text.slice(0, 300) };
9050
9724
  }
@@ -9053,7 +9727,7 @@ async function read2(url, headers, doFetch) {
9053
9727
  }
9054
9728
 
9055
9729
  // src/provision.ts
9056
- var import_apps7 = require("@odla-ai/apps");
9730
+ var import_apps10 = require("@odla-ai/apps");
9057
9731
  var import_ai3 = require("@odla-ai/ai");
9058
9732
  var import_node_process10 = __toESM(require("process"), 1);
9059
9733
 
@@ -9063,7 +9737,7 @@ async function provisionIntegrationSeeds(doFetch, endpoint, tenantId, dbKey, int
9063
9737
  const base = `${endpoint}/app/${encodeURIComponent(tenantId)}`;
9064
9738
  for (const integration of integrations) {
9065
9739
  for (const seed of integration.seeds ?? []) {
9066
- const payload = await postJson2(doFetch, `${base}/query`, dbKey, {
9740
+ const payload = await postJson3(doFetch, `${base}/query`, dbKey, {
9067
9741
  query: { [seed.ns]: { $: { where: { [seed.key.attr]: seed.key.value }, limit: 1 } } }
9068
9742
  });
9069
9743
  const rows = isRecord7(payload) && isRecord7(payload.result) ? payload.result[seed.ns] : void 0;
@@ -9074,7 +9748,7 @@ async function provisionIntegrationSeeds(doFetch, endpoint, tenantId, dbKey, int
9074
9748
  out.log(`${env}: integration ${integration.id} seed ${seed.id} already exists`);
9075
9749
  continue;
9076
9750
  }
9077
- await postJson2(doFetch, `${base}/transact`, dbKey, {
9751
+ await postJson3(doFetch, `${base}/transact`, dbKey, {
9078
9752
  mutationId: `integration:${integration.id}:seed:${seed.id}`,
9079
9753
  ops: [{
9080
9754
  t: "update",
@@ -9089,7 +9763,7 @@ async function provisionIntegrationSeeds(doFetch, endpoint, tenantId, dbKey, int
9089
9763
  }
9090
9764
  }
9091
9765
  }
9092
- async function postJson2(doFetch, url, bearer, body) {
9766
+ async function postJson3(doFetch, url, bearer, body) {
9093
9767
  const res = await doFetch(url, {
9094
9768
  method: "POST",
9095
9769
  headers: { authorization: `Bearer ${bearer}`, "content-type": "application/json" },
@@ -9098,8 +9772,8 @@ async function postJson2(doFetch, url, bearer, body) {
9098
9772
  if (!res.ok) throw new Error(`${new URL(url).pathname} failed: ${res.status} ${await responseText(res)}`);
9099
9773
  return res.json().catch(() => ({}));
9100
9774
  }
9101
- function isRecord7(value) {
9102
- return value !== null && typeof value === "object" && !Array.isArray(value);
9775
+ function isRecord7(value2) {
9776
+ return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
9103
9777
  }
9104
9778
  async function responseText(res) {
9105
9779
  try {
@@ -9110,9 +9784,9 @@ async function responseText(res) {
9110
9784
  }
9111
9785
 
9112
9786
  // src/provision-credentials.ts
9113
- var import_apps5 = require("@odla-ai/apps");
9787
+ var import_apps9 = require("@odla-ai/apps");
9114
9788
  async function provisionEnvCredentials(opts) {
9115
- const tenantId = (0, import_apps5.tenantIdFor)(opts.cfg.app.id, opts.env);
9789
+ const tenantId = (0, import_apps9.tenantIdFor)(opts.cfg.app.id, opts.env);
9116
9790
  const prior = opts.credentials?.envs[opts.env];
9117
9791
  let credentials = opts.credentials;
9118
9792
  let dbKey = opts.cfg.services.includes("db") && !opts.rotateDb ? prior?.dbKey : void 0;
@@ -9168,14 +9842,14 @@ async function mintDbKey(opts, tenantId) {
9168
9842
  appId: tenantId
9169
9843
  })
9170
9844
  });
9171
- if (!created.ok) throw new Error(`db app create (${tenantId}) failed: ${created.status} ${await safeText4(created)}`);
9845
+ if (!created.ok) throw new Error(`db app create (${tenantId}) failed: ${created.status} ${await safeText5(created)}`);
9172
9846
  res = await opts.fetch(`${opts.cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenantId)}/keys`, {
9173
9847
  method: "POST",
9174
9848
  headers,
9175
9849
  body: "{}"
9176
9850
  });
9177
9851
  }
9178
- if (!res.ok) throw new Error(`db key mint (${tenantId}) failed: ${res.status} ${await safeText4(res)}`);
9852
+ if (!res.ok) throw new Error(`db key mint (${tenantId}) failed: ${res.status} ${await safeText5(res)}`);
9179
9853
  const body = await res.json();
9180
9854
  if (!body.key) throw new Error(`db key mint (${tenantId}) returned no key`);
9181
9855
  return body.key;
@@ -9191,58 +9865,11 @@ async function issueO11yToken(opts) {
9191
9865
  `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`
9192
9866
  );
9193
9867
  }
9194
- if (!res.ok) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) failed: ${res.status} ${await safeText4(res)}`);
9868
+ if (!res.ok) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) failed: ${res.status} ${await safeText5(res)}`);
9195
9869
  const body = await res.json();
9196
9870
  if (!body.token) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) returned no token`);
9197
9871
  return body.token;
9198
9872
  }
9199
- async function safeText4(res) {
9200
- try {
9201
- return redactSecrets((await res.text()).slice(0, 500));
9202
- } catch {
9203
- return "";
9204
- }
9205
- }
9206
-
9207
- // src/provision-helpers.ts
9208
- var import_ai2 = require("@odla-ai/ai");
9209
- var import_apps6 = require("@odla-ai/apps");
9210
- function defaultSecretName(provider) {
9211
- const names = import_ai2.DEFAULT_SECRET_NAMES;
9212
- return names[provider] ?? `${provider}_api_key`;
9213
- }
9214
- async function assertTenantAdminAccess(doFetch, cfg, env, token) {
9215
- const tenantId = (0, import_apps6.tenantIdFor)(cfg.app.id, env);
9216
- const res = await doFetch(`${cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenantId)}/entitlements`, {
9217
- headers: { authorization: `Bearer ${token}` }
9218
- });
9219
- if (res.ok || res.status === 404) return;
9220
- if (res.status === 403) {
9221
- throw new Error(
9222
- `${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`
9223
- );
9224
- }
9225
- throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await safeText5(res)}`);
9226
- }
9227
- async function postJson3(doFetch, url, bearer, body) {
9228
- const res = await doFetch(url, {
9229
- method: "POST",
9230
- headers: { authorization: `Bearer ${bearer}`, "content-type": "application/json" },
9231
- body: JSON.stringify(body)
9232
- });
9233
- if (!res.ok) throw new Error(`${new URL(url).pathname} failed: ${res.status} ${await safeText5(res)}`);
9234
- }
9235
- function normalizeClerkConfig(value) {
9236
- if (!value) return null;
9237
- if (typeof value === "string") {
9238
- const publishableKey2 = envValue(value);
9239
- return publishableKey2 ? { publishableKey: publishableKey2 } : null;
9240
- }
9241
- if (typeof value !== "object") return null;
9242
- const cfg = value;
9243
- const publishableKey = envValue(cfg.publishableKey);
9244
- return publishableKey ? { publishableKey, ...cfg.audience ? { audience: cfg.audience } : {}, ...cfg.mode ? { mode: cfg.mode } : {} } : null;
9245
- }
9246
9873
  async function safeText5(res) {
9247
9874
  try {
9248
9875
  return redactSecrets((await res.text()).slice(0, 500));
@@ -9321,7 +9948,7 @@ async function provision(options) {
9321
9948
  }
9322
9949
  const doFetch = options.fetch ?? fetch;
9323
9950
  const token = await getDeveloperToken(cfg, options, doFetch, out);
9324
- const apps = (0, import_apps7.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
9951
+ const apps = (0, import_apps10.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
9325
9952
  const existing = await apps.resolveApp(cfg.app.id);
9326
9953
  if (existing) {
9327
9954
  out.log(`app: ${cfg.app.id} already exists`);
@@ -9332,7 +9959,7 @@ async function provision(options) {
9332
9959
  for (const env of cfg.envs) {
9333
9960
  await assertTenantAdminAccess(doFetch, cfg, env, token);
9334
9961
  }
9335
- const serviceOrder = (0, import_apps7.orderAppServices)(cfg.services);
9962
+ const serviceOrder = (0, import_apps10.orderAppServices)(cfg.services);
9336
9963
  for (const env of cfg.envs) {
9337
9964
  for (const service of serviceOrder) {
9338
9965
  if (service === "ai") {
@@ -9365,7 +9992,7 @@ async function provision(options) {
9365
9992
  }
9366
9993
  }
9367
9994
  for (const env of cfg.envs) {
9368
- const tenantId = (0, import_apps7.tenantIdFor)(cfg.app.id, env);
9995
+ const tenantId = (0, import_apps10.tenantIdFor)(cfg.app.id, env);
9369
9996
  credentials = await provisionEnvCredentials({
9370
9997
  cfg,
9371
9998
  env,
@@ -9398,11 +10025,11 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
9398
10025
  }
9399
10026
  }
9400
10027
  if (schema && dbKey) {
9401
- await postJson3(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(tenantId)}/schema`, dbKey, { schema });
10028
+ await postJson(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(tenantId)}/schema`, dbKey, { schema });
9402
10029
  out.log(`${env}: schema pushed`);
9403
10030
  }
9404
10031
  if (rules) {
9405
- await postJson3(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(tenantId)}/admin/rules`, token, rules);
10032
+ await postJson(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(tenantId)}/admin/rules`, token, rules);
9406
10033
  out.log(`${env}: rules pushed (${Object.keys(rules).length} namespaces)`);
9407
10034
  }
9408
10035
  if (dbKey) {
@@ -9499,6 +10126,7 @@ var COMMAND_SURFACE = {
9499
10126
  calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
9500
10127
  capabilities: {},
9501
10128
  code: { connect: {} },
10129
+ config: { diff: {}, plan: {} },
9502
10130
  context: { show: {}, list: {}, save: {}, remove: {} },
9503
10131
  // `watch`, `read`, `reply`, and `resolve` take a topic id from there on.
9504
10132
  discuss: {
@@ -9516,6 +10144,7 @@ var COMMAND_SURFACE = {
9516
10144
  help: {},
9517
10145
  init: {},
9518
10146
  o11y: { status: {} },
10147
+ platform: { status: {} },
9519
10148
  pm: {
9520
10149
  ...PM_ENTITIES,
9521
10150
  handoff: {}
@@ -9608,7 +10237,7 @@ function recordInvocation(parsed) {
9608
10237
  try {
9609
10238
  const entry = {
9610
10239
  path: invocationPath(parsed.positionals),
9611
- options: Object.entries(parsed.options).map(([name, value]) => value === false ? `no-${name}` : name).sort()
10240
+ options: Object.entries(parsed.options).map(([name, value2]) => value2 === false ? `no-${name}` : name).sort()
9612
10241
  };
9613
10242
  if (!entry.path.length) return;
9614
10243
  (0, import_node_fs15.appendFileSync)(file, `${JSON.stringify(entry)}
@@ -9622,10 +10251,10 @@ var import_node_fs16 = require("fs");
9622
10251
 
9623
10252
  // src/runbook-requires.ts
9624
10253
  var SPEC = /^(@?[\w./-]+?)@(\d+\.\d+\.\d+(?:[\w.-]*)?)$/;
9625
- function parseRequires(value) {
9626
- if (!value) return [];
10254
+ function parseRequires(value2) {
10255
+ if (!value2) return [];
9627
10256
  const out = [];
9628
- for (const token of value.split(/[\s,]+/).filter(Boolean)) {
10257
+ for (const token of value2.split(/[\s,]+/).filter(Boolean)) {
9629
10258
  const match = SPEC.exec(token);
9630
10259
  if (match?.[1] && match[2]) out.push({ name: match[1], min: match[2] });
9631
10260
  }
@@ -9791,7 +10420,7 @@ async function runbookRemove(ctx, slug) {
9791
10420
 
9792
10421
  // src/runbook-import.ts
9793
10422
  var import_node_fs17 = require("fs");
9794
- var import_node_path13 = require("path");
10423
+ var import_node_path14 = require("path");
9795
10424
  function parseRunbook(text, slug) {
9796
10425
  let rest = text;
9797
10426
  const meta = {};
@@ -9801,9 +10430,9 @@ function parseRunbook(text, slug) {
9801
10430
  for (const line of fm[1].split(/\r?\n/)) {
9802
10431
  const pair = /^(\w+)\s*:\s*(.+)$/.exec(line.trim());
9803
10432
  if (!pair) continue;
9804
- const value = pair[2].trim().replace(/^["']|["']$/g, "");
9805
- if (pair[1] === "summary") meta.summary = value;
9806
- if (pair[1] === "tags") meta.tags = value.replace(/^\[|\]$/g, "").trim();
10433
+ const value2 = pair[2].trim().replace(/^["']|["']$/g, "");
10434
+ if (pair[1] === "summary") meta.summary = value2;
10435
+ if (pair[1] === "tags") meta.tags = value2.replace(/^\[|\]$/g, "").trim();
9807
10436
  }
9808
10437
  }
9809
10438
  const lines = rest.split("\n");
@@ -9820,8 +10449,8 @@ function readRunbookDir(dir) {
9820
10449
  const files = (0, import_node_fs17.readdirSync)(dir).filter((f) => f.endsWith(".md")).sort();
9821
10450
  if (!files.length) throw new Error(`no .md files in ${dir}`);
9822
10451
  return files.map((file) => {
9823
- const slug = (0, import_node_path13.basename)(file, ".md");
9824
- const parsed = parseRunbook((0, import_node_fs17.readFileSync)((0, import_node_path13.join)(dir, file), "utf8"), slug);
10452
+ const slug = (0, import_node_path14.basename)(file, ".md");
10453
+ const parsed = parseRunbook((0, import_node_fs17.readFileSync)((0, import_node_path14.join)(dir, file), "utf8"), slug);
9825
10454
  return { file, slug, ...parsed, words: parsed.body.split(/\s+/).filter(Boolean).length };
9826
10455
  });
9827
10456
  }
@@ -9894,7 +10523,7 @@ async function upsert(ctx, r, visibility) {
9894
10523
  // src/runbook-impact.ts
9895
10524
  var import_node_child_process7 = require("child_process");
9896
10525
  var import_node_fs18 = require("fs");
9897
- var import_node_path14 = require("path");
10526
+ var import_node_path15 = require("path");
9898
10527
 
9899
10528
  // src/runbook-impact-scan.ts
9900
10529
  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$]*)/;
@@ -9920,7 +10549,7 @@ var NOISE = /* @__PURE__ */ new Set([
9920
10549
  "and",
9921
10550
  "for"
9922
10551
  ]);
9923
- var words = (value) => value.replace(/^@[\w-]+\//, "").split(/[^A-Za-z0-9]+/).filter((w) => w.length > 1 && !NOISE.has(w.toLowerCase()));
10552
+ var words = (value2) => value2.replace(/^@[\w-]+\//, "").split(/[^A-Za-z0-9]+/).filter((w) => w.length > 1 && !NOISE.has(w.toLowerCase()));
9924
10553
  function namedExports(clause) {
9925
10554
  return clause.split(",").map((part) => part.trim().split(/\s+as\s+/)[0]?.trim() ?? "").filter((name) => /^[A-Za-z_$][\w$]*$/.test(name) && name !== "type");
9926
10555
  }
@@ -10063,7 +10692,7 @@ ${body.split("\n").map((line) => `+${line}`).join("\n")}
10063
10692
  }
10064
10693
  function manifestLabeller(root) {
10065
10694
  return (workspace) => {
10066
- const manifest = (0, import_node_path14.join)(root, workspace, "package.json");
10695
+ const manifest = (0, import_node_path15.join)(root, workspace, "package.json");
10067
10696
  if (!(0, import_node_fs18.existsSync)(manifest)) return void 0;
10068
10697
  try {
10069
10698
  const name = JSON.parse((0, import_node_fs18.readFileSync)(manifest, "utf8")).name;
@@ -10133,7 +10762,7 @@ function report3(ctx, impacts) {
10133
10762
  async function runbookImpact(ctx, options, deps = {}) {
10134
10763
  const cwd = deps.cwd ?? process.cwd();
10135
10764
  const runGit = deps.runGit ?? gitRunner(cwd);
10136
- const read3 = deps.readRepoFile ?? ((path) => (0, import_node_fs18.readFileSync)((0, import_node_path14.join)(cwd, path), "utf8"));
10765
+ const read3 = deps.readRepoFile ?? ((path) => (0, import_node_fs18.readFileSync)((0, import_node_path15.join)(cwd, path), "utf8"));
10137
10766
  const surfaces = changedSurfaces(collectDiff(runGit, options.base, read3), manifestLabeller(cwd));
10138
10767
  if (!surfaces.length) {
10139
10768
  return ctx.out.log(
@@ -10268,13 +10897,13 @@ async function runbookComment(ctx, slug, body) {
10268
10897
  var import_node_child_process8 = require("child_process");
10269
10898
  var import_node_fs19 = require("fs");
10270
10899
  var import_node_os5 = require("os");
10271
- var import_node_path15 = require("path");
10900
+ var import_node_path16 = require("path");
10272
10901
  var import_node_process12 = __toESM(require("process"), 1);
10273
10902
  var EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
10274
10903
  function resolveEditor(env = import_node_process12.default.env) {
10275
10904
  for (const name of EDITOR_ENV) {
10276
- const value = env[name];
10277
- if (value && value.trim()) return value.trim();
10905
+ const value2 = env[name];
10906
+ if (value2 && value2.trim()) return value2.trim();
10278
10907
  }
10279
10908
  return null;
10280
10909
  }
@@ -10294,8 +10923,8 @@ function editText(initial, slug, deps = {}) {
10294
10923
  );
10295
10924
  if (!interactive())
10296
10925
  throw new Error(`cannot open an editor without a terminal \u2014 pass --file <path> or --body "\u2026" instead`);
10297
- const dir = (0, import_node_fs19.mkdtempSync)((0, import_node_path15.join)((0, import_node_os5.tmpdir)(), "odla-runbook-"));
10298
- const file = (0, import_node_path15.join)(dir, `${slug}.md`);
10926
+ const dir = (0, import_node_fs19.mkdtempSync)((0, import_node_path16.join)((0, import_node_os5.tmpdir)(), "odla-runbook-"));
10927
+ const file = (0, import_node_path16.join)(dir, `${slug}.md`);
10299
10928
  try {
10300
10929
  (0, import_node_fs19.writeFileSync)(file, initial, { mode: 384 });
10301
10930
  const code = defaultRunOrInjected(deps)(editor, file);
@@ -10403,12 +11032,12 @@ var ALLOWED2 = [
10403
11032
  "platform",
10404
11033
  "context"
10405
11034
  ];
10406
- function requireSlug(slug, action) {
10407
- if (!slug) throw new Error(`"runbook ${action}" needs a slug, e.g. "odla-ai runbook ${action} release"`);
11035
+ function requireSlug(slug, action2) {
11036
+ if (!slug) throw new Error(`"runbook ${action2}" needs a slug, e.g. "odla-ai runbook ${action2} release"`);
10408
11037
  return slug;
10409
11038
  }
10410
11039
  var WRITES = /* @__PURE__ */ new Set(["new", "edit", "publish", "archive", "visibility", "revert", "rm", "import"]);
10411
- async function buildContext3(parsed, deps, action) {
11040
+ async function buildContext3(parsed, deps, action2) {
10412
11041
  const appIdOption = stringOpt(parsed.options.app);
10413
11042
  const context = await resolveOperatorContext(parsed, {
10414
11043
  allowMissingConfig: true
@@ -10418,7 +11047,7 @@ async function buildContext3(parsed, deps, action) {
10418
11047
  const out = deps.stdout ?? console;
10419
11048
  const appId = appIdOption ?? (context.app.source === "environment" || context.app.source === "profile" ? context.app.value : null) ?? PLATFORM_SCOPE;
10420
11049
  const dryRun = parsed.options["dry-run"] === true;
10421
- if (action === "import" && dryRun) {
11050
+ if (action2 === "import" && dryRun) {
10422
11051
  return {
10423
11052
  platformUrl: cfg.platformUrl,
10424
11053
  token: "",
@@ -10428,12 +11057,12 @@ async function buildContext3(parsed, deps, action) {
10428
11057
  appId
10429
11058
  };
10430
11059
  }
10431
- const needsCapability = WRITES.has(action) && !dryRun && appId === PLATFORM_SCOPE && !stringOpt(parsed.options.token);
11060
+ const needsCapability = WRITES.has(action2) && !dryRun && appId === PLATFORM_SCOPE && !stringOpt(parsed.options.token);
10432
11061
  const token = needsCapability ? await getScopedPlatformToken({
10433
11062
  platform: cfg.platformUrl,
10434
11063
  scope: "platform:runbook:write",
10435
11064
  email: stringOpt(parsed.options.email),
10436
- label: `odla CLI (runbook ${action})`,
11065
+ label: `odla CLI (runbook ${action2})`,
10437
11066
  fetch: doFetch,
10438
11067
  stdout: out,
10439
11068
  openApprovalUrl: deps.openUrl,
@@ -10468,11 +11097,11 @@ async function buildContext3(parsed, deps, action) {
10468
11097
  };
10469
11098
  }
10470
11099
  async function runbookCommand(parsed, deps = {}) {
10471
- const action = parsed.positionals[1] ?? "list";
10472
- assertArgs(parsed, ALLOWED2, action === "ask" || action === "search" ? 64 : 4);
10473
- const ctx = await buildContext3(parsed, deps, action);
11100
+ const action2 = parsed.positionals[1] ?? "list";
11101
+ assertArgs(parsed, ALLOWED2, action2 === "ask" || action2 === "search" ? 64 : 4);
11102
+ const ctx = await buildContext3(parsed, deps, action2);
10474
11103
  const slug = parsed.positionals[2];
10475
- switch (action) {
11104
+ switch (action2) {
10476
11105
  case "list":
10477
11106
  return runbookList(ctx, parsed.options.all === true, stringOpt(parsed.options.q));
10478
11107
  case "ask": {
@@ -10539,9 +11168,9 @@ async function runbookCommand(parsed, deps = {}) {
10539
11168
  });
10540
11169
  }
10541
11170
  case "visibility": {
10542
- const value = parsed.positionals[3] ?? stringOpt(parsed.options.visibility);
10543
- if (!value) throw new Error('"runbook visibility" needs operator|admin, e.g. "runbook visibility release operator"');
10544
- return runbookVisibility(ctx, requireSlug(slug, "visibility"), value);
11171
+ const value2 = parsed.positionals[3] ?? stringOpt(parsed.options.visibility);
11172
+ if (!value2) throw new Error('"runbook visibility" needs operator|admin, e.g. "runbook visibility release operator"');
11173
+ return runbookVisibility(ctx, requireSlug(slug, "visibility"), value2);
10545
11174
  }
10546
11175
  case "publish":
10547
11176
  return runbookStatus(ctx, requireSlug(slug, "publish"), "published");
@@ -10557,7 +11186,7 @@ async function runbookCommand(parsed, deps = {}) {
10557
11186
  case "rm":
10558
11187
  return runbookRemove(ctx, requireSlug(slug, "rm"));
10559
11188
  default:
10560
- throw new Error(`unknown runbook action "${action}". Try ${acceptedAfter(["runbook"]).join(", ")}.`);
11189
+ throw new Error(`unknown runbook action "${action2}". Try ${acceptedAfter(["runbook"]).join(", ")}.`);
10561
11190
  }
10562
11191
  }
10563
11192
 
@@ -10597,13 +11226,13 @@ async function interactiveConfirmation(message2, dependencies) {
10597
11226
  }
10598
11227
  }
10599
11228
  function requiredSecurityPositional(parsed, index, label) {
10600
- const value = parsed.positionals[index];
10601
- if (!value) throw new Error(`${label} is required`);
10602
- return value;
11229
+ const value2 = parsed.positionals[index];
11230
+ if (!value2) throw new Error(`${label} is required`);
11231
+ return value2;
10603
11232
  }
10604
- function securityProfile(value) {
10605
- if (value === void 0) return void 0;
10606
- if (value === "odla" || value === "cloudflare-app" || value === "generic") return value;
11233
+ function securityProfile(value2) {
11234
+ if (value2 === void 0) return void 0;
11235
+ if (value2 === "odla" || value2 === "cloudflare-app" || value2 === "generic") return value2;
10607
11236
  throw new Error("--profile must be odla, cloudflare-app, or generic");
10608
11237
  }
10609
11238
 
@@ -10704,9 +11333,9 @@ function routeLabel(route2) {
10704
11333
  return `${route2.provider}/${route2.model}${route2.policyVersion ? ` policy v${route2.policyVersion}` : ""}`;
10705
11334
  }
10706
11335
  var HOSTED_SEVERITIES = ["informational", "low", "medium", "high", "critical"];
10707
- function hostedSeverity(value, flag) {
10708
- if (HOSTED_SEVERITIES.includes(value)) {
10709
- return value;
11336
+ function hostedSeverity(value2, flag) {
11337
+ if (HOSTED_SEVERITIES.includes(value2)) {
11338
+ return value2;
10710
11339
  }
10711
11340
  throw new Error(`${flag} must be informational, low, medium, high, or critical`);
10712
11341
  }
@@ -10715,7 +11344,7 @@ function hostedSeverity(value, flag) {
10715
11344
  var import_security2 = require("@odla-ai/security");
10716
11345
 
10717
11346
  // src/security.ts
10718
- var import_node_path16 = require("path");
11347
+ var import_node_path17 = require("path");
10719
11348
  var import_security = require("@odla-ai/security");
10720
11349
  var import_node3 = require("@odla-ai/security/node");
10721
11350
  async function runHostedSecurity(options) {
@@ -10727,9 +11356,9 @@ async function runHostedSecurity(options) {
10727
11356
  const appId = selfAudit ? "odla-ai" : cfg.app.id;
10728
11357
  const env = selfAudit ? "prod" : selectEnv(options.env, cfg.envs, cfg.configPath, cfg.rootDir);
10729
11358
  const platform = options.platform ?? cfg?.platformUrl ?? "https://odla.ai";
10730
- const target = (0, import_node_path16.resolve)(options.target ?? cfg?.rootDir ?? ".");
10731
- const output = (0, import_node_path16.resolve)(options.out ?? (0, import_node_path16.resolve)(target, ".odla/security/hosted"));
10732
- const outputRelative = (0, import_node_path16.relative)(target, output).split(import_node_path16.sep).join("/");
11359
+ const target = (0, import_node_path17.resolve)(options.target ?? cfg?.rootDir ?? ".");
11360
+ const output = (0, import_node_path17.resolve)(options.out ?? (0, import_node_path17.resolve)(target, ".odla/security/hosted"));
11361
+ const outputRelative = (0, import_node_path17.relative)(target, output).split(import_node_path17.sep).join("/");
10733
11362
  if (!outputRelative) throw new Error("Hosted security output cannot be the repository root");
10734
11363
  const profile = profileFor(options.profile ?? "odla", options.maxHuntTasks ?? 12);
10735
11364
  const tokenRequest = {
@@ -10741,7 +11370,7 @@ async function runHostedSecurity(options) {
10741
11370
  };
10742
11371
  const token = await injectedToken(options, tokenRequest);
10743
11372
  const snapshot = await (0, import_node3.snapshotDirectory)(target, {
10744
- exclude: !outputRelative.startsWith("../") && !(0, import_node_path16.isAbsolute)(outputRelative) ? [outputRelative] : []
11373
+ exclude: !outputRelative.startsWith("../") && !(0, import_node_path17.isAbsolute)(outputRelative) ? [outputRelative] : []
10745
11374
  });
10746
11375
  const hosted = await (0, import_security.createPlatformSecurityReasoners)({
10747
11376
  platform,
@@ -10759,7 +11388,7 @@ async function runHostedSecurity(options) {
10759
11388
  });
10760
11389
  const harness = (0, import_security.createSecurityHarness)({
10761
11390
  profile,
10762
- store: new import_node3.FileRunStore((0, import_node_path16.resolve)(output, "state")),
11391
+ store: new import_node3.FileRunStore((0, import_node_path17.resolve)(output, "state")),
10763
11392
  discoveryReasoner: hosted.discoveryReasoner,
10764
11393
  validationReasoner: hosted.validationReasoner,
10765
11394
  policy: {
@@ -10783,19 +11412,19 @@ async function runHostedSecurity(options) {
10783
11412
  function selectEnv(requested, declared, configPath, rootDir) {
10784
11413
  const env = requested ?? (declared.includes("dev") ? "dev" : declared[0]);
10785
11414
  if (!env || !declared.includes(env)) {
10786
- const shown = (0, import_node_path16.relative)(rootDir, configPath) || configPath;
11415
+ const shown = (0, import_node_path17.relative)(rootDir, configPath) || configPath;
10787
11416
  throw new Error(`env "${env ?? ""}" is not declared in ${shown}`);
10788
11417
  }
10789
11418
  return env;
10790
11419
  }
10791
11420
  async function injectedToken(options, request2) {
10792
- const value = options.token ?? await options.getToken?.(Object.freeze({ ...request2 }));
10793
- if (typeof value !== "string" || value.length < 8 || value.length > 8192 || /\s|[\u0000-\u001f\u007f]/.test(value)) {
11421
+ const value2 = options.token ?? await options.getToken?.(Object.freeze({ ...request2 }));
11422
+ if (typeof value2 !== "string" || value2.length < 8 || value2.length > 8192 || /\s|[\u0000-\u001f\u007f]/.test(value2)) {
10794
11423
  throw new Error(
10795
11424
  request2.selfAudit ? "Self-audit requires an injected, scoped platform security token" : "Hosted security requires an injected app developer token or getToken callback"
10796
11425
  );
10797
11426
  }
10798
- return value;
11427
+ return value2;
10799
11428
  }
10800
11429
  function profileFor(name, maxHuntTasks) {
10801
11430
  const profile = name === "odla" ? (0, import_security.odlaProfile)() : name === "cloudflare-app" ? (0, import_security.cloudflareAppProfile)() : name === "generic" ? (0, import_security.genericProfile)() : void 0;
@@ -10812,7 +11441,7 @@ function printSummary(out, appId, env, run, report4, output) {
10812
11441
  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}`);
10813
11442
  if (report4.callBudget) out.log(` calls: discovery=${formatBudget(report4.callBudget.discovery)} validation=${formatBudget(report4.callBudget.validation)}`);
10814
11443
  out.log(` findings: confirmed=${report4.metrics.confirmed} needs_reproduction=${report4.metrics.needsReproduction} candidates=${report4.metrics.candidates}`);
10815
- out.log(` report: ${(0, import_node_path16.resolve)(output, "REPORT.md")}`);
11444
+ out.log(` report: ${(0, import_node_path17.resolve)(output, "REPORT.md")}`);
10816
11445
  }
10817
11446
  function formatBudget(usage) {
10818
11447
  return usage ? `${usage.usedCalls}/${usage.maxCalls} skipped=${usage.skippedCalls}` : "caller-managed";
@@ -10841,8 +11470,8 @@ async function getHostedSecurityIntent(options) {
10841
11470
  }
10842
11471
  return response2;
10843
11472
  }
10844
- function isValidIntent(value, expected) {
10845
- 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";
11473
+ function isValidIntent(value2, expected) {
11474
+ 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";
10846
11475
  }
10847
11476
 
10848
11477
  // src/security-hosted-jobs.ts
@@ -10968,17 +11597,17 @@ function hostedJobPath(appIdInput, jobIdInput) {
10968
11597
  const jobId = hostedIdentifier(jobIdInput, "jobId");
10969
11598
  return `/registry/apps/${encodeURIComponent(appId)}/security/jobs/${encodeURIComponent(jobId)}`;
10970
11599
  }
10971
- function securityPlanDigest(value) {
10972
- if (!/^sha256:[a-f0-9]{64}$/.test(value)) {
11600
+ function securityPlanDigest(value2) {
11601
+ if (!/^sha256:[a-f0-9]{64}$/.test(value2)) {
10973
11602
  throw new Error("expected plan digest must be a sha256 digest from security plan");
10974
11603
  }
10975
- return value;
11604
+ return value2;
10976
11605
  }
10977
- function securityExecutionDigest(value) {
10978
- if (!/^sha256:[a-f0-9]{64}$/.test(value)) {
11606
+ function securityExecutionDigest(value2) {
11607
+ if (!/^sha256:[a-f0-9]{64}$/.test(value2)) {
10979
11608
  throw new Error("expected execution digest must be a sha256 digest from security intent");
10980
11609
  }
10981
- return value;
11610
+ return value2;
10982
11611
  }
10983
11612
 
10984
11613
  // src/security-run-command.ts
@@ -11042,7 +11671,7 @@ async function runSourceSecurityCommand(parsed, dependencies, sourceId) {
11042
11671
  ...context,
11043
11672
  jobId: job.jobId,
11044
11673
  wait: dependencies.pollWait,
11045
- onUpdate: parsed.options.json === true ? void 0 : (value) => printHostedJob(context.stdout, value, context.platform, context.appId)
11674
+ onUpdate: parsed.options.json === true ? void 0 : (value2) => printHostedJob(context.stdout, value2, context.platform, context.appId)
11046
11675
  }) : job;
11047
11676
  if (!follow) {
11048
11677
  if (parsed.options.json === true) {
@@ -11142,9 +11771,9 @@ function enforceLocalGate(report4, parsed) {
11142
11771
  throw new Error(`hosted security gate failed: ${confirmed.length} confirmed >= ${failOn}; ${leads.length} leads >= ${failOnCandidates ?? "disabled"}${incomplete ? "; coverage incomplete" : ""}`);
11143
11772
  }
11144
11773
  }
11145
- function severityOpt(value, flag) {
11146
- if (["informational", "low", "medium", "high", "critical"].includes(value)) {
11147
- return value;
11774
+ function severityOpt(value2, flag) {
11775
+ if (["informational", "low", "medium", "high", "critical"].includes(value2)) {
11776
+ return value2;
11148
11777
  }
11149
11778
  throw new Error(`${flag} must be informational, low, medium, high, or critical`);
11150
11779
  }
@@ -11189,8 +11818,8 @@ async function securityCommand(parsed, dependencies) {
11189
11818
  else await runLocalSecurityCommand(parsed, dependencies);
11190
11819
  }
11191
11820
  async function githubSecurityCommand(parsed, dependencies) {
11192
- const action = parsed.positionals[2];
11193
- if (action === "disconnect") {
11821
+ const action2 = parsed.positionals[2];
11822
+ if (action2 === "disconnect") {
11194
11823
  assertArgs(parsed, ["config", "env", "platform", "source", "email", "open", "yes"], 3);
11195
11824
  const context2 = await hostedSecurityContext(parsed, dependencies);
11196
11825
  const sourceId = requiredString(parsed.options.source, "--source");
@@ -11205,7 +11834,7 @@ async function githubSecurityCommand(parsed, dependencies) {
11205
11834
  context2.stdout.log(`github: disconnected ${sourceId} from ${context2.appId}/${context2.env}`);
11206
11835
  return;
11207
11836
  }
11208
- if (action !== "connect") {
11837
+ if (action2 !== "connect") {
11209
11838
  throw new Error('unknown security github command. Try "odla-ai security github connect".');
11210
11839
  }
11211
11840
  assertArgs(parsed, ["config", "env", "platform", "repo", "email", "open"], 3);
@@ -11252,7 +11881,7 @@ async function securityStatus(parsed, dependencies) {
11252
11881
  ...context,
11253
11882
  jobId,
11254
11883
  wait: dependencies.pollWait,
11255
- onUpdate: parsed.options.json === true ? void 0 : (value) => printHostedJob(context.stdout, value, context.platform, context.appId)
11884
+ onUpdate: parsed.options.json === true ? void 0 : (value2) => printHostedJob(context.stdout, value2, context.platform, context.appId)
11256
11885
  }) : await getHostedSecurityJob({ ...context, jobId });
11257
11886
  if (parsed.options.json === true) context.stdout.log(JSON.stringify(job, null, 2));
11258
11887
  else if (parsed.options.follow !== true) {
@@ -11336,6 +11965,10 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
11336
11965
  await o11yCommand(parsed, runtime);
11337
11966
  return;
11338
11967
  }
11968
+ if (command === "platform") {
11969
+ await platformCommand(parsed, runtime);
11970
+ return;
11971
+ }
11339
11972
  if (command === "provision") {
11340
11973
  await provisionCommand(parsed, runtime);
11341
11974
  return;
@@ -11421,8 +12054,11 @@ async function calendarCommand(parsed, dependencies) {
11421
12054
  calendarServiceConfig,
11422
12055
  calendarStatus,
11423
12056
  codeConnect,
12057
+ configDiff,
12058
+ configPlan,
11424
12059
  connectGitHubSecuritySource,
11425
12060
  describeProblem,
12061
+ desiredRegistryState,
11426
12062
  disconnectGitHubSecuritySource,
11427
12063
  doctor,
11428
12064
  exitCodeFor,
@@ -11442,6 +12078,7 @@ async function calendarCommand(parsed, dependencies) {
11442
12078
  prepareCodeImages,
11443
12079
  printCapabilities,
11444
12080
  provision,
12081
+ reconcileConfig,
11445
12082
  redactSecrets,
11446
12083
  repositoryFromGitRemote,
11447
12084
  runCli,