@odla-ai/cli 0.43.0 → 0.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -365,10 +365,10 @@ function isManagedDevVar(line2) {
365
365
  const match = line2.match(/^\s*(?:export\s+)?([A-Z][A-Z0-9_]*)\s*=/);
366
366
  return !!match?.[1] && MANAGED_DEV_VARS.has(match[1]);
367
367
  }
368
- function writePrivateText(path, text3) {
368
+ function writePrivateText(path, text4) {
369
369
  mkdirSync2(dirname3(path), { recursive: true });
370
370
  const temporary = `${path}.tmp-${process.pid}-${Date.now()}`;
371
- writeFileSync(temporary, text3, { mode: 384 });
371
+ writeFileSync(temporary, text4, { mode: 384 });
372
372
  chmodSync2(temporary, 384);
373
373
  renameSync(temporary, path);
374
374
  }
@@ -808,12 +808,12 @@ async function readAdminAiAudit(request3) {
808
808
  }
809
809
  }
810
810
  async function responseBody(response2) {
811
- const text3 = await response2.text();
812
- if (!text3) return {};
811
+ const text4 = await response2.text();
812
+ if (!text4) return {};
813
813
  try {
814
- return JSON.parse(text3);
814
+ return JSON.parse(text4);
815
815
  } catch {
816
- return { message: text3.slice(0, 300) };
816
+ return { message: text4.slice(0, 300) };
817
817
  }
818
818
  }
819
819
  function apiError(status, body) {
@@ -915,12 +915,12 @@ function timestamp2(value2) {
915
915
  return Number.isFinite(date.valueOf()) ? date.toISOString() : "";
916
916
  }
917
917
  async function responseBody2(res) {
918
- const text3 = await res.text();
919
- if (!text3) return {};
918
+ const text4 = await res.text();
919
+ if (!text4) return {};
920
920
  try {
921
- return JSON.parse(text3);
921
+ return JSON.parse(text4);
922
922
  } catch {
923
- return { message: text3.slice(0, 300) };
923
+ return { message: text4.slice(0, 300) };
924
924
  }
925
925
  }
926
926
  function apiError2(action2, status, body) {
@@ -1096,12 +1096,12 @@ function catalogModels(body) {
1096
1096
  return body.catalog.models.filter((value2) => isRecord3(value2) && typeof value2.id === "string" && typeof value2.provider === "string");
1097
1097
  }
1098
1098
  async function responseBody3(res) {
1099
- const text3 = await res.text();
1100
- if (!text3) return {};
1099
+ const text4 = await res.text();
1100
+ if (!text4) return {};
1101
1101
  try {
1102
- return JSON.parse(text3);
1102
+ return JSON.parse(text4);
1103
1103
  } catch {
1104
- return { message: text3.slice(0, 300) };
1104
+ return { message: text4.slice(0, 300) };
1105
1105
  }
1106
1106
  }
1107
1107
  function apiError3(action2, status, body) {
@@ -1147,10 +1147,17 @@ function parseArgv(argv) {
1147
1147
  function assertArgs(parsed, allowedOptions2, maxPositionals) {
1148
1148
  const allowed = new Set(allowedOptions2);
1149
1149
  for (const name of Object.keys(parsed.options)) {
1150
- if (!allowed.has(name)) throw new Error(`unknown option "--${name}"; run "odla-ai help" for supported options`);
1150
+ if (allowed.has(name)) continue;
1151
+ const accepts = [...allowed].sort().map((option) => `--${option}`).join(" ");
1152
+ throw new Error(
1153
+ `unknown option "--${name}"` + (accepts ? ` \u2014 this command accepts: ${accepts}` : " \u2014 this command takes no options")
1154
+ );
1151
1155
  }
1152
1156
  if (parsed.positionals.length > maxPositionals) {
1153
- throw new Error(`unexpected argument "${parsed.positionals[maxPositionals]}"; run "odla-ai help"`);
1157
+ const taken = parsed.positionals.slice(0, maxPositionals).join(" ");
1158
+ throw new Error(
1159
+ `unexpected argument "${parsed.positionals[maxPositionals]}" \u2014 ` + (maxPositionals ? `"odla-ai ${taken}" takes no further arguments` : "this command takes no arguments")
1160
+ );
1154
1161
  }
1155
1162
  }
1156
1163
  function requiredString(value2, name) {
@@ -1188,6 +1195,198 @@ function addOption(options, name, value2) {
1188
1195
  else options[name] = [String(current), String(value2)];
1189
1196
  }
1190
1197
 
1198
+ // src/surface.ts
1199
+ var PM_ACTIONS = {
1200
+ list: {},
1201
+ add: {},
1202
+ create: {},
1203
+ get: {},
1204
+ set: {},
1205
+ update: {},
1206
+ status: {},
1207
+ move: {},
1208
+ done: {},
1209
+ comment: {},
1210
+ comments: {},
1211
+ history: {},
1212
+ link: {},
1213
+ ref: {},
1214
+ rm: {},
1215
+ delete: {}
1216
+ };
1217
+ var PM_TASK_ACTIONS = {
1218
+ ...PM_ACTIONS,
1219
+ ready: {},
1220
+ claim: {},
1221
+ release: {}
1222
+ };
1223
+ var PM_ENTITIES = {
1224
+ ...Object.fromEntries(
1225
+ ["goal", "conformance", "decision", "bug"].map((entity) => [entity, PM_ACTIONS])
1226
+ ),
1227
+ task: PM_TASK_ACTIONS,
1228
+ kanban: PM_TASK_ACTIONS
1229
+ };
1230
+ var COMMAND_SURFACE = {
1231
+ agent: { jobs: {}, retry: {} },
1232
+ ai: { models: {} },
1233
+ admin: {
1234
+ ai: {
1235
+ show: {},
1236
+ set: {},
1237
+ credentials: {},
1238
+ models: {},
1239
+ usage: {},
1240
+ audit: {},
1241
+ credential: { set: {} }
1242
+ },
1243
+ spend: { show: {}, reset: {} }
1244
+ },
1245
+ app: {
1246
+ archive: {},
1247
+ restore: {},
1248
+ export: {},
1249
+ import: {},
1250
+ rename: {},
1251
+ "refresh-sandbox": {},
1252
+ "go-live": {},
1253
+ promote: {},
1254
+ owners: { list: {}, add: {}, remove: {} }
1255
+ },
1256
+ auth: { login: {} },
1257
+ brand: { design: { unpack: {} } },
1258
+ bug: { create: {}, list: {}, report: {} },
1259
+ calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
1260
+ capabilities: {},
1261
+ code: {
1262
+ connect: {},
1263
+ grant: { request: {}, list: {}, approve: {}, revoke: {} },
1264
+ repository: { show: {}, list: {}, bind: {} }
1265
+ },
1266
+ config: { diff: {}, plan: {}, apply: {} },
1267
+ context: { show: {}, list: {}, save: {}, remove: {} },
1268
+ credentials: { list: {}, revoke: {} },
1269
+ device: { enroll: {}, list: {}, revoke: {} },
1270
+ // `watch`, `read`, `reply`, and `resolve` take a topic id from there on.
1271
+ discuss: {
1272
+ groups: {},
1273
+ list: {},
1274
+ topics: {},
1275
+ read: {},
1276
+ post: {},
1277
+ reply: {},
1278
+ resolve: {},
1279
+ who: {},
1280
+ watch: {}
1281
+ },
1282
+ doctor: {},
1283
+ help: {},
1284
+ init: {},
1285
+ monitor: { plan: {}, apply: {}, run: {}, status: {}, incidents: {}, report: {} },
1286
+ o11y: { status: {} },
1287
+ operations: { get: {}, wait: {} },
1288
+ platform: {
1289
+ status: {}
1290
+ },
1291
+ pm: {
1292
+ ...PM_ENTITIES,
1293
+ project: { list: {}, add: {}, create: {}, use: {} },
1294
+ handoff: {},
1295
+ next: {},
1296
+ start: {},
1297
+ watch: {}
1298
+ },
1299
+ provision: {},
1300
+ runbook: {
1301
+ ask: {},
1302
+ search: {},
1303
+ impact: {},
1304
+ list: {},
1305
+ get: {},
1306
+ cat: {},
1307
+ new: {},
1308
+ edit: {},
1309
+ comment: {},
1310
+ import: {},
1311
+ visibility: {},
1312
+ publish: {},
1313
+ archive: {},
1314
+ history: {},
1315
+ revert: {},
1316
+ rm: {},
1317
+ lint: {}
1318
+ },
1319
+ secrets: { push: {}, status: {}, set: {}, "set-clerk-key": {} },
1320
+ security: {
1321
+ plan: {},
1322
+ sources: {},
1323
+ run: {},
1324
+ status: {},
1325
+ report: {},
1326
+ github: { connect: {}, disconnect: {} }
1327
+ },
1328
+ setup: {},
1329
+ skill: { install: {} },
1330
+ smoke: {},
1331
+ version: {},
1332
+ whoami: {}
1333
+ };
1334
+ function acceptedAfter(path) {
1335
+ let node = COMMAND_SURFACE;
1336
+ for (const word of path) {
1337
+ node = node?.[word];
1338
+ if (!node) return [];
1339
+ }
1340
+ return Object.keys(node).sort();
1341
+ }
1342
+ function validateInvocation(words2) {
1343
+ let node = COMMAND_SURFACE;
1344
+ const walked = [];
1345
+ for (const word of words2) {
1346
+ if (Object.keys(node).length === 0) return null;
1347
+ const next = node[word];
1348
+ if (!next) return { validPrefix: walked.join(" "), word, accepted: Object.keys(node).sort() };
1349
+ walked.push(word);
1350
+ node = next;
1351
+ }
1352
+ return null;
1353
+ }
1354
+ function describeProblem(problem) {
1355
+ const where = problem.validPrefix ? `after "${problem.validPrefix}"` : "as a command";
1356
+ if (!problem.accepted.length) return `"${problem.word}" is not accepted ${where}. Run "odla-ai help"`;
1357
+ if (!problem.word) return `"odla-ai ${problem.validPrefix}" needs one of: ${problem.accepted.join(", ")}`;
1358
+ return `"${problem.word}" is not accepted ${where} \u2014 try: ${problem.accepted.join(", ")}`;
1359
+ }
1360
+ function rejectWord(path, word, note) {
1361
+ const sentence = describeProblem({
1362
+ validPrefix: path.join(" "),
1363
+ word: word ?? "",
1364
+ accepted: acceptedAfter(path)
1365
+ });
1366
+ throw new Error(note ? `${sentence}. ${note}` : sentence);
1367
+ }
1368
+ function invocationPath(words2) {
1369
+ let node = COMMAND_SURFACE;
1370
+ const path = [];
1371
+ for (const word of words2) {
1372
+ const next = node[word];
1373
+ if (!next) break;
1374
+ path.push(word);
1375
+ node = next;
1376
+ if (Object.keys(node).length === 0) break;
1377
+ }
1378
+ return path;
1379
+ }
1380
+ function surfacePaths(node = COMMAND_SURFACE, prefix = []) {
1381
+ const paths2 = [];
1382
+ for (const [word, child] of Object.entries(node)) {
1383
+ const path = [...prefix, word];
1384
+ paths2.push(path);
1385
+ paths2.push(...surfacePaths(child, path));
1386
+ }
1387
+ return paths2;
1388
+ }
1389
+
1191
1390
  // src/admin-spend.ts
1192
1391
  async function call(ctx, method, scope) {
1193
1392
  const url = `${ctx.platformUrl.replace(/\/$/, "")}/registry/platform/spend?scope=${encodeURIComponent(scope)}`;
@@ -1237,9 +1436,7 @@ async function spendReset(ctx, scope) {
1237
1436
  async function adminSpend(parsed, ctx) {
1238
1437
  const action2 = parsed.positionals[2];
1239
1438
  const scope = parsed.positionals[3] ?? stringOpt(parsed.options.scope);
1240
- if (action2 !== "show" && action2 !== "reset") {
1241
- throw new Error('unknown spend command. Try "odla-ai admin spend show <scope>".');
1242
- }
1439
+ if (action2 !== "show" && action2 !== "reset") rejectWord(["admin", "spend"], action2);
1243
1440
  if (!scope) {
1244
1441
  throw new Error(
1245
1442
  `"admin spend ${action2}" needs a scope, e.g. odla-ai admin spend ${action2} app:my-app:<incarnation>`
@@ -2038,6 +2235,11 @@ var SET_OPTIONS = [
2038
2235
  async function adminCommand(parsed, deps = {}) {
2039
2236
  const area = parsed.positionals[1];
2040
2237
  const action2 = parsed.positionals[2];
2238
+ if (area !== "ai" && area !== "spend") rejectWord(["admin"], area);
2239
+ if (!acceptedAfter(["admin", area]).includes(action2 ?? "")) rejectWord(["admin", area], action2);
2240
+ if (action2 === "credential" && !acceptedAfter(["admin", "ai", "credential"]).includes(parsed.positionals[3] ?? "")) {
2241
+ rejectWord(["admin", "ai", "credential"], parsed.positionals[3]);
2242
+ }
2041
2243
  if (area === "spend") {
2042
2244
  assertArgs(parsed, JSON_OPTIONS, 4);
2043
2245
  const context2 = await resolveOperatorContext(parsed, { allowMissingConfig: true });
@@ -2063,14 +2265,11 @@ async function adminCommand(parsed, deps = {}) {
2063
2265
  out
2064
2266
  });
2065
2267
  }
2066
- const credentialSet = action2 === "credential" && parsed.positionals[3] === "set";
2268
+ const credentialSet = action2 === "credential";
2067
2269
  const credentials = action2 === "credentials";
2068
2270
  const models = action2 === "models";
2069
2271
  const usage = action2 === "usage";
2070
2272
  const audit = action2 === "audit";
2071
- if (area !== "ai" || action2 !== "show" && action2 !== "set" && !credentialSet && !credentials && !models && !usage && !audit) {
2072
- throw new Error('unknown admin command. Try "odla-ai admin ai show".');
2073
- }
2074
2273
  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;
2075
2274
  assertArgs(parsed, allowed, credentialSet ? 5 : action2 === "set" ? 4 : 3);
2076
2275
  const context = await resolveOperatorContext(parsed, { allowMissingConfig: true });
@@ -2284,9 +2483,7 @@ async function authCommand(parsed, deps = {}) {
2284
2483
  "json"
2285
2484
  ], 2);
2286
2485
  const action2 = parsed.positionals[1] ?? "login";
2287
- if (action2 !== "login") {
2288
- throw new Error(`unknown auth action "${action2}". Try "odla-ai auth login --app <id> --email <odla-account>".`);
2289
- }
2486
+ if (action2 !== "login") rejectWord(["auth"], action2);
2290
2487
  const context = await resolveOperatorContext(parsed, {
2291
2488
  allowMissingConfig: true,
2292
2489
  requireApp: true
@@ -2359,9 +2556,7 @@ function bothTenants(cfg) {
2359
2556
  // src/agent-command.ts
2360
2557
  async function agentCommand(parsed, deps = {}) {
2361
2558
  const action2 = parsed.positionals[1];
2362
- if (action2 !== "jobs" && action2 !== "retry") {
2363
- throw new Error(`unknown agent action "${action2 ?? ""}". Try "odla-ai agent jobs --json".`);
2364
- }
2559
+ if (action2 !== "jobs" && action2 !== "retry") rejectWord(["agent"], action2);
2365
2560
  assertArgs(parsed, ["config", "env", "state", "limit", "json", "token"], action2 === "jobs" ? 2 : 3);
2366
2561
  if (action2 === "retry" && (parsed.options.state !== void 0 || parsed.options.limit !== void 0)) {
2367
2562
  throw new Error('--state and --limit are supported only by "agent jobs"');
@@ -2474,8 +2669,8 @@ async function appImport(options) {
2474
2669
  const out = options.stdout ?? console;
2475
2670
  const say = options.json ? (line2) => out.error(line2) : (line2) => out.log(line2);
2476
2671
  const { tenant } = resolveTenant(cfg, options.env);
2477
- const text3 = options.file === "-" ? (options.readStdin ?? (() => readFileSync5(0, "utf8")))() : readFileSync5(options.file, "utf8");
2478
- const { format, sources } = parseImport(text3, options.ns);
2672
+ const text4 = options.file === "-" ? (options.readStdin ?? (() => readFileSync5(0, "utf8")))() : readFileSync5(options.file, "utf8");
2673
+ const { format, sources } = parseImport(text4, options.ns);
2479
2674
  if (format === "namespace-map" && options.ns) {
2480
2675
  throw new Error("--ns cannot be combined with a {namespace: rows} file \u2014 the file already names each namespace");
2481
2676
  }
@@ -2536,9 +2731,7 @@ async function appOwnersCommand(parsed, dependencies = {}) {
2536
2731
  await (sub === "add" ? ownersAdd(email, options) : ownersRemove(email, options));
2537
2732
  return;
2538
2733
  }
2539
- throw new Error(
2540
- `unknown app owners subcommand "${sub}". Try "odla-ai app owners list", "odla-ai app owners add <email>", or "odla-ai app owners remove <email>".`
2541
- );
2734
+ rejectWord(["app", "owners"], sub);
2542
2735
  }
2543
2736
 
2544
2737
  // src/app-rename.ts
@@ -2644,8 +2837,10 @@ async function appCommand(parsed, dependencies = {}) {
2644
2837
  return;
2645
2838
  }
2646
2839
  if (sub !== "archive" && sub !== "restore") {
2647
- throw new Error(
2648
- `unknown app subcommand "${sub ?? ""}". Try "odla-ai app archive --yes", "odla-ai app restore", "odla-ai app export", "odla-ai app import <file>", "odla-ai app refresh-sandbox", "odla-ai app go-live", "odla-ai app promote", "odla-ai app rename <name>", or "odla-ai app owners <list|add|remove>". (Permanent deletion has no CLI: it requires a signed-in owner in Studio.)`
2840
+ rejectWord(
2841
+ ["app"],
2842
+ sub,
2843
+ "Permanent deletion has no CLI: it requires a signed-in owner in Studio."
2649
2844
  );
2650
2845
  }
2651
2846
  assertArgs(parsed, ["config", "token", "email", "yes", "json"], 2);
@@ -2691,7 +2886,7 @@ var EXTENSIONS = {
2691
2886
  "text/html": "html",
2692
2887
  "application/json": "json"
2693
2888
  };
2694
- var encode = (text3) => new TextEncoder().encode(text3);
2889
+ var encode = (text4) => new TextEncoder().encode(text4);
2695
2890
  function assetFileName(uuid, mime) {
2696
2891
  const ext = EXTENSIONS[mime.split(";")[0].trim().toLowerCase()] ?? "bin";
2697
2892
  return `${uuid.replace(/[^a-zA-Z0-9._-]/g, "_")}.${ext}`;
@@ -2811,7 +3006,8 @@ async function brandCommand(parsed, deps) {
2811
3006
  await designUnpack(parsed, deps);
2812
3007
  return;
2813
3008
  }
2814
- throw new Error(USAGE);
3009
+ if (subject !== "design") rejectWord(["brand"], subject);
3010
+ rejectWord(["brand", "design"], action2, USAGE);
2815
3011
  }
2816
3012
 
2817
3013
  // src/calendar-errors.ts
@@ -3549,9 +3745,9 @@ async function assertTenantAdminAccess(doFetch, cfg, env, token) {
3549
3745
  }
3550
3746
  throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await safeText5(res)}`);
3551
3747
  }
3552
- function errorCode(text3) {
3748
+ function errorCode(text4) {
3553
3749
  try {
3554
- const body = JSON.parse(text3);
3750
+ const body = JSON.parse(text4);
3555
3751
  return typeof body.error?.code === "string" ? body.error.code : null;
3556
3752
  } catch {
3557
3753
  return null;
@@ -4286,15 +4482,15 @@ function readWranglerConfig(path) {
4286
4482
  return null;
4287
4483
  }
4288
4484
  }
4289
- function stripJsonComments(text3) {
4485
+ function stripJsonComments(text4) {
4290
4486
  let result = "";
4291
4487
  let inString = false;
4292
- for (let i = 0; i < text3.length; i++) {
4293
- const ch = text3[i];
4488
+ for (let i = 0; i < text4.length; i++) {
4489
+ const ch = text4[i];
4294
4490
  if (inString) {
4295
4491
  result += ch;
4296
4492
  if (ch === "\\") {
4297
- result += text3[i + 1] ?? "";
4493
+ result += text4[i + 1] ?? "";
4298
4494
  i++;
4299
4495
  } else if (ch === '"') {
4300
4496
  inString = false;
@@ -4306,14 +4502,14 @@ function stripJsonComments(text3) {
4306
4502
  result += ch;
4307
4503
  continue;
4308
4504
  }
4309
- if (ch === "/" && text3[i + 1] === "/") {
4310
- while (i < text3.length && text3[i] !== "\n") i++;
4505
+ if (ch === "/" && text4[i + 1] === "/") {
4506
+ while (i < text4.length && text4[i] !== "\n") i++;
4311
4507
  result += "\n";
4312
4508
  continue;
4313
4509
  }
4314
- if (ch === "/" && text3[i + 1] === "*") {
4510
+ if (ch === "/" && text4[i + 1] === "*") {
4315
4511
  i += 2;
4316
- while (i < text3.length && !(text3[i] === "*" && text3[i + 1] === "/")) i++;
4512
+ while (i < text4.length && !(text4[i] === "*" && text4[i + 1] === "/")) i++;
4317
4513
  i++;
4318
4514
  continue;
4319
4515
  }
@@ -4827,9 +5023,9 @@ function initProject(options) {
4827
5023
  out.log("created src/odla/schema.mjs and src/odla/rules.mjs");
4828
5024
  out.log("updated .gitignore for local odla credentials");
4829
5025
  }
4830
- function writeIfMissing(path, text3) {
5026
+ function writeIfMissing(path, text4) {
4831
5027
  if (existsSync9(path)) return;
4832
- writeFileSync2(path, text3);
5028
+ writeFileSync2(path, text4);
4833
5029
  }
4834
5030
  function configTemplate(input) {
4835
5031
  const calendar = input.services.includes("calendar") ? ` calendar: {
@@ -5041,13 +5237,13 @@ async function secretsSetClerkKey(options) {
5041
5237
  body: JSON.stringify({ value: value2 })
5042
5238
  });
5043
5239
  if (!res.ok) {
5044
- const text3 = scrubValue((await res.text().catch(() => "")).slice(0, 300), value2);
5045
- throw new Error(`store Clerk secret key failed (${res.status}): ${text3 || "request failed"}`);
5240
+ const text4 = scrubValue((await res.text().catch(() => "")).slice(0, 300), value2);
5241
+ throw new Error(`store Clerk secret key failed (${res.status}): ${text4 || "request failed"}`);
5046
5242
  }
5047
5243
  out.log(`Clerk secret key stored for ${tenantId} ($clerk_secret, reserved + write-only; the value was never echoed)`);
5048
5244
  }
5049
- function scrubValue(text3, value2) {
5050
- return redactSecrets(text3).split(value2).join("[value redacted]");
5245
+ function scrubValue(text4, value2) {
5246
+ return redactSecrets(text4).split(value2).join("[value redacted]");
5051
5247
  }
5052
5248
  async function resolveVaultWrite(options) {
5053
5249
  const out = options.stdout ?? console;
@@ -5339,8 +5535,8 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
5339
5535
  harnesses: installations
5340
5536
  };
5341
5537
  }
5342
- function pathsUnder(root, paths) {
5343
- return [...paths].map((path) => relative2(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${sep}`) && !isAbsolute3(path)).sort();
5538
+ function pathsUnder(root, paths2) {
5539
+ return [...paths2].map((path) => relative2(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${sep}`) && !isAbsolute3(path)).sort();
5344
5540
  }
5345
5541
  function normalizeHarnesses(values, global) {
5346
5542
  const requested = values?.length ? values : ["claude"];
@@ -5620,9 +5816,7 @@ async function secretsCommand(parsed, deps) {
5620
5816
  return;
5621
5817
  }
5622
5818
  if (sub !== "push") {
5623
- throw new Error(
5624
- `unknown secrets subcommand "${sub ?? ""}". Try "odla-ai secrets push --env dev", "odla-ai secrets status --env dev", "odla-ai secrets set <name> --env dev --stdin", or "odla-ai secrets set-clerk-key --env dev --stdin".`
5625
- );
5819
+ rejectWord(["secrets"], sub);
5626
5820
  }
5627
5821
  assertArgs(parsed, ["config", "env", "dry-run", "yes"], 2);
5628
5822
  await secretsPush({
@@ -5635,7 +5829,7 @@ async function secretsCommand(parsed, deps) {
5635
5829
  async function projectCommand(command, parsed, deps) {
5636
5830
  if (command === "ai") {
5637
5831
  const sub = parsed.positionals[1];
5638
- if (sub !== "models") throw new Error(`unknown ai subcommand "${sub ?? ""}". Try "odla-ai ai models --env dev".`);
5832
+ if (sub !== "models") rejectWord(["ai"], sub);
5639
5833
  assertArgs(parsed, ["config", "env", "provider", "json"], 2);
5640
5834
  await aiModels({
5641
5835
  configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
@@ -5649,9 +5843,7 @@ async function projectCommand(command, parsed, deps) {
5649
5843
  }
5650
5844
  if (command === "config") {
5651
5845
  const sub = parsed.positionals[1];
5652
- if (sub !== "diff" && sub !== "plan" && sub !== "apply") {
5653
- throw new Error(`unknown config subcommand "${sub ?? ""}". Try "odla-ai config diff --json".`);
5654
- }
5846
+ if (sub !== "diff" && sub !== "plan" && sub !== "apply") rejectWord(["config"], sub);
5655
5847
  assertArgs(
5656
5848
  parsed,
5657
5849
  sub === "apply" ? ["config", "plan", "idempotency-key", "token", "email", "open", "json"] : ["config", "token", "email", "open", "json"],
@@ -5679,7 +5871,7 @@ async function projectCommand(command, parsed, deps) {
5679
5871
  if (command === "operations") {
5680
5872
  const sub = parsed.positionals[1];
5681
5873
  if (sub !== "get" && sub !== "wait") {
5682
- throw new Error(`unknown operations subcommand "${sub ?? ""}". Try "odla-ai operations get <operation-id> --json".`);
5874
+ rejectWord(["operations"], sub);
5683
5875
  }
5684
5876
  assertArgs(
5685
5877
  parsed,
@@ -5753,7 +5945,7 @@ async function projectCommand(command, parsed, deps) {
5753
5945
  }
5754
5946
  if (command === "skill") {
5755
5947
  const sub = parsed.positionals[1];
5756
- if (sub !== "install") throw new Error(`unknown skill subcommand "${sub ?? ""}". Try "odla-ai skill install".`);
5948
+ if (sub !== "install") rejectWord(["skill"], sub);
5757
5949
  install(parsed, 2, deps);
5758
5950
  return true;
5759
5951
  }
@@ -5769,10 +5961,10 @@ import { existsSync as existsSync11 } from "fs";
5769
5961
  import { cpus, hostname, totalmem } from "os";
5770
5962
  import { resolve as resolve11 } from "path";
5771
5963
 
5772
- // ../harness/dist/chunk-LNQNFGQC.js
5964
+ // ../harness/dist/chunk-RXNHCGWE.js
5773
5965
  var HARNESS_PROTOCOL_VERSION = 1;
5774
5966
 
5775
- // ../harness/dist/chunk-K76I2TCQ.js
5967
+ // ../harness/dist/chunk-CR6RE3A2.js
5776
5968
  import { execFile, spawn as spawn3 } from "child_process";
5777
5969
  import { constants } from "fs";
5778
5970
  import { access } from "fs/promises";
@@ -6024,12 +6216,12 @@ async function gitSourceFiles(sourceDir, maxFiles, maxBytes) {
6024
6216
  });
6025
6217
  if (outputBytes > 8 * 1024 * 1024) throw new Error("git file inventory exceeds 8 MiB");
6026
6218
  if (code !== 0) throw new Error(`git file inventory failed: ${Buffer.concat(stderr2).toString("utf8").slice(0, 1e3)}`);
6027
- const paths = Buffer.concat(stdout).toString("utf8").split("\0").filter(Boolean).sort();
6028
- if (paths.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
6219
+ const paths2 = Buffer.concat(stdout).toString("utf8").split("\0").filter(Boolean).sort();
6220
+ if (paths2.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
6029
6221
  const root = resolve22(sourceDir);
6030
6222
  const files = [];
6031
6223
  let bytes = 0;
6032
- for (const relativePath of paths) {
6224
+ for (const relativePath of paths2) {
6033
6225
  if (!allowedWorkspacePath(relativePath)) continue;
6034
6226
  const source = resolve22(root, relativePath);
6035
6227
  if (!source.startsWith(`${root}${sep22}`)) throw new TypeError("git file path escapes workspace");
@@ -6145,7 +6337,7 @@ async function stageWorkspacePair(baselineSource, workspaceSource, options = {})
6145
6337
  }
6146
6338
  }
6147
6339
 
6148
- // ../harness/dist/chunk-GYWQM76X.js
6340
+ // ../harness/dist/chunk-ISR434K7.js
6149
6341
  import { createHash as createHash3 } from "crypto";
6150
6342
  import { readFile as readFile2, readdir as readdir2 } from "fs/promises";
6151
6343
  import { relative as relative4, resolve as resolve10 } from "path";
@@ -6468,13 +6660,13 @@ function validateSnapshot(snapshot, limits) {
6468
6660
  if (!Number.isSafeInteger(limits.maximumFiles) || limits.maximumFiles < 1 || !Number.isSafeInteger(limits.maximumBytes) || limits.maximumBytes < 1 || snapshot.files.length > limits.maximumFiles) {
6469
6661
  throw new CamelError("limit_exceeded", "Code snapshot exceeds its registered limits.");
6470
6662
  }
6471
- const paths = /* @__PURE__ */ new Set();
6663
+ const paths2 = /* @__PURE__ */ new Set();
6472
6664
  let bytes = 0;
6473
6665
  for (const file of snapshot.files) {
6474
- if (!file.path || file.path.startsWith("/") || file.path.includes("\\") || file.path.split("/").some((part) => !part || part === "." || part === "..") || paths.has(file.path) || typeof file.content !== "string") {
6666
+ if (!file.path || file.path.startsWith("/") || file.path.includes("\\") || file.path.split("/").some((part) => !part || part === "." || part === "..") || paths2.has(file.path) || typeof file.content !== "string") {
6475
6667
  throw new CamelError("state_conflict", "Code snapshot contains an invalid or duplicate path.");
6476
6668
  }
6477
- paths.add(file.path);
6669
+ paths2.add(file.path);
6478
6670
  bytes += utf8Length(file.path) + utf8Length(file.content);
6479
6671
  }
6480
6672
  if (bytes > limits.maximumBytes) {
@@ -6482,7 +6674,7 @@ function validateSnapshot(snapshot, limits) {
6482
6674
  }
6483
6675
  }
6484
6676
 
6485
- // ../harness/dist/chunk-GYWQM76X.js
6677
+ // ../harness/dist/chunk-ISR434K7.js
6486
6678
  import { spawn as spawn4 } from "child_process";
6487
6679
  import { lstat as lstat2 } from "fs/promises";
6488
6680
  import { resolve as resolve23, sep as sep3 } from "path";
@@ -6628,8 +6820,8 @@ function boundedInteger(value2, spec) {
6628
6820
  }
6629
6821
  function boundedNumber(value2, spec) {
6630
6822
  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.");
6631
- const text3 = String(value2);
6632
- if (/e/i.test(text3) || (text3.split(".")[1]?.length ?? 0) > spec.maximumDecimalPlaces) throw new CamelError("conversion_rejected", "Finite-number conversion rejected a non-canonical decimal.");
6823
+ const text4 = String(value2);
6824
+ if (/e/i.test(text4) || (text4.split(".")[1]?.length ?? 0) > spec.maximumDecimalPlaces) throw new CamelError("conversion_rejected", "Finite-number conversion rejected a non-canonical decimal.");
6633
6825
  return value2;
6634
6826
  }
6635
6827
  function enumMember(value2, spec) {
@@ -6785,11 +6977,11 @@ function validateUnsafeSelector(path, value2, tool) {
6785
6977
  return void 0;
6786
6978
  }
6787
6979
  function looksLikeDestination(value2) {
6788
- const text3 = value2.trim();
6789
- return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text3) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text3);
6980
+ const text4 = value2.trim();
6981
+ return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text4) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text4);
6790
6982
  }
6791
6983
 
6792
- // ../harness/dist/chunk-GYWQM76X.js
6984
+ // ../harness/dist/chunk-ISR434K7.js
6793
6985
  import { readFile as readFile4, stat as stat2 } from "fs/promises";
6794
6986
  import { readFile as readFile3 } from "fs/promises";
6795
6987
  import { join as join33 } from "path";
@@ -6954,9 +7146,9 @@ async function extractImports(builder, input) {
6954
7146
  const sources = input.paths.filter(isSourcePath);
6955
7147
  const known = new Set(sources);
6956
7148
  for (const path of sources) {
6957
- let text3;
7149
+ let text4;
6958
7150
  try {
6959
- text3 = await input.read(path);
7151
+ text4 = await input.read(path);
6960
7152
  } catch {
6961
7153
  continue;
6962
7154
  }
@@ -6964,13 +7156,13 @@ async function extractImports(builder, input) {
6964
7156
  const file = builder.node(FILE, path, pkg ? { pkg } : void 0);
6965
7157
  if (pkg) builder.edge(builder.node(PACKAGE, pkg), CONTAINS, file);
6966
7158
  const specifiers = /* @__PURE__ */ new Set();
6967
- for (const match of text3.matchAll(IMPORT_FROM)) specifiers.add(match[1]);
6968
- for (const match of text3.matchAll(BARE_IMPORT)) specifiers.add(match[1]);
7159
+ for (const match of text4.matchAll(IMPORT_FROM)) specifiers.add(match[1]);
7160
+ for (const match of text4.matchAll(BARE_IMPORT)) specifiers.add(match[1]);
6969
7161
  for (const specifier of specifiers) {
6970
7162
  const resolved = resolveImport(path, specifier, known);
6971
7163
  if (resolved) builder.edge(file, IMPORTS, nodeId(FILE, resolved));
6972
7164
  }
6973
- for (const name of exportedNames(text3)) {
7165
+ for (const name of exportedNames(text4)) {
6974
7166
  builder.edge(file, EXPORTS, builder.node(SYMBOL, name));
6975
7167
  }
6976
7168
  }
@@ -7011,16 +7203,16 @@ async function extractData(builder, input) {
7011
7203
  };
7012
7204
  for (const path of input.paths) {
7013
7205
  if (!SOURCE_FILE.test(path) || input.ignore?.(path)) continue;
7014
- let text3;
7206
+ let text4;
7015
7207
  try {
7016
- text3 = await input.read(path);
7208
+ text4 = await input.read(path);
7017
7209
  } catch {
7018
7210
  continue;
7019
7211
  }
7020
- for (const statement of text3.matchAll(STATEMENT)) {
7212
+ for (const statement of text4.matchAll(STATEMENT)) {
7021
7213
  const verb = statement[1].toUpperCase().replace(/\s+/g, " ");
7022
7214
  const start = statement.index ?? 0;
7023
- const rest = text3.slice(start + statement[0].length, start + STATEMENT_WINDOW);
7215
+ const rest = text4.slice(start + statement[0].length, start + STATEMENT_WINDOW);
7024
7216
  if (verb === "SELECT") {
7025
7217
  for (const read3 of rest.matchAll(READ_TABLES)) touch(path, read3[1].toLowerCase(), TABLE, READS);
7026
7218
  continue;
@@ -7036,16 +7228,16 @@ async function extractData(builder, input) {
7036
7228
  for (const read3 of rest.matchAll(READ_TABLES)) touch(path, read3[1].toLowerCase(), TABLE, READS);
7037
7229
  }
7038
7230
  }
7039
- for (const match of text3.matchAll(NS_CONST)) {
7040
- touch(path, `${match[1]}.${match[2]}`, NAMESPACE, accessFor(text3, match.index ?? 0));
7231
+ for (const match of text4.matchAll(NS_CONST)) {
7232
+ touch(path, `${match[1]}.${match[2]}`, NAMESPACE, accessFor(text4, match.index ?? 0));
7041
7233
  }
7042
- for (const match of text3.matchAll(NS_LITERAL)) {
7043
- touch(path, match[1], NAMESPACE, accessFor(text3, match.index ?? 0));
7234
+ for (const match of text4.matchAll(NS_LITERAL)) {
7235
+ touch(path, match[1], NAMESPACE, accessFor(text4, match.index ?? 0));
7044
7236
  }
7045
7237
  }
7046
7238
  }
7047
- function accessFor(text3, index) {
7048
- const window = text3.slice(Math.max(0, index - 160), index + 40);
7239
+ function accessFor(text4, index) {
7240
+ const window = text4.slice(Math.max(0, index - 160), index + 40);
7049
7241
  return /\b(?:transact|update|delete|create|insert|Ops)\b/.test(window) ? WRITES : READS;
7050
7242
  }
7051
7243
  async function buildCodeGraph(input) {
@@ -7057,7 +7249,7 @@ async function buildCodeGraph(input) {
7057
7249
  return builder.build();
7058
7250
  }
7059
7251
 
7060
- // ../harness/dist/chunk-GYWQM76X.js
7252
+ // ../harness/dist/chunk-ISR434K7.js
7061
7253
  import { createHash as createHash32 } from "crypto";
7062
7254
  async function digestStagedWorkspace(root, limits) {
7063
7255
  const files = [];
@@ -7385,7 +7577,7 @@ function validateCodePatch(rawPatch, maxBytes) {
7385
7577
  if (FORBIDDEN.test(patch2) || /(?:old|new)(?: file)? mode 120000/.test(patch2)) {
7386
7578
  throw new TypeError("patch uses a forbidden binary, link, mode, rename, or copy operation");
7387
7579
  }
7388
- const paths = [];
7580
+ const paths2 = [];
7389
7581
  const lines = patch2.split("\n");
7390
7582
  for (let index = 0; index < lines.length; index += 1) {
7391
7583
  const line2 = lines[index];
@@ -7401,10 +7593,10 @@ function validateCodePatch(rawPatch, maxBytes) {
7401
7593
  if (!validHeaderPath(oldPath, path, "a") || !validHeaderPath(newPath, path, "b")) {
7402
7594
  throw new TypeError("patch file headers do not match the declared path");
7403
7595
  }
7404
- paths.push(path);
7596
+ paths2.push(path);
7405
7597
  }
7406
- if (!paths.length || new Set(paths).size !== paths.length) throw new TypeError("patch has no diffs or repeats a path");
7407
- return paths;
7598
+ if (!paths2.length || new Set(paths2).size !== paths2.length) throw new TypeError("patch has no diffs or repeats a path");
7599
+ return paths2;
7408
7600
  }
7409
7601
  function validHeaderPath(value2, path, prefix) {
7410
7602
  return value2 === "/dev/null" || value2 === `${prefix}/${path}`;
@@ -7429,11 +7621,11 @@ function describePatchFailure(patch2, detail) {
7429
7621
  const hint = hunks.length > 0 && contextless ? " A hunk has no context lines; include at least one unchanged line above or below each change." : "";
7430
7622
  return `patch did not apply: ${detail}${hint}`;
7431
7623
  }
7432
- async function applyCodePatch(workspaceDir, rawPatch, paths) {
7624
+ async function applyCodePatch(workspaceDir, rawPatch, paths2) {
7433
7625
  const patch2 = stripPatchEnvelope(rawPatch);
7434
7626
  await gitApply(workspaceDir, patch2, true);
7435
7627
  await gitApply(workspaceDir, patch2, false);
7436
- for (const path of paths) {
7628
+ for (const path of paths2) {
7437
7629
  try {
7438
7630
  const info = await lstat2(resolveCodePath(workspaceDir, path));
7439
7631
  if (info.isSymbolicLink() || !info.isFile() && !info.isDirectory()) {
@@ -7455,8 +7647,8 @@ function gitApply(cwd, patch2, check) {
7455
7647
  });
7456
7648
  let stderr2 = "";
7457
7649
  child.stderr.setEncoding("utf8");
7458
- child.stderr.on("data", (text3) => {
7459
- if (stderr2.length < 4e3) stderr2 += text3.slice(0, 4e3);
7650
+ child.stderr.on("data", (text22) => {
7651
+ if (stderr2.length < 4e3) stderr2 += text22.slice(0, 4e3);
7460
7652
  });
7461
7653
  child.once("error", reject);
7462
7654
  child.once("exit", (code) => code === 0 ? accept() : reject(new TypeError(describePatchFailure(patch2, stderr2.trim().slice(0, 500)))));
@@ -7480,8 +7672,8 @@ async function restoreCodeWorkspaceCheckpoint(input) {
7480
7672
  const workspace = await stageWorkspace(input.trustedBaseDir, input.stage);
7481
7673
  try {
7482
7674
  if (checkpoint.patch) {
7483
- const paths = validateCodePatch(checkpoint.patch, 256 * 1024);
7484
- await applyCodePatch(workspace.workspaceDir, checkpoint.patch, paths);
7675
+ const paths2 = validateCodePatch(checkpoint.patch, 256 * 1024);
7676
+ await applyCodePatch(workspace.workspaceDir, checkpoint.patch, paths2);
7485
7677
  }
7486
7678
  return { workspace, checkpoint };
7487
7679
  } catch (error) {
@@ -7630,8 +7822,8 @@ async function verifyCodeCandidate(input) {
7630
7822
  try {
7631
7823
  const baseDigest = await digestStagedWorkspace(staged.workspaceDir, limits);
7632
7824
  if (baseDigest !== input.trustedBaseDigest) throw new TypeError("trusted base does not match its registered digest");
7633
- const paths = validateCodePatch(input.candidatePatch, policy.maximumPatchBytes);
7634
- await applyCodePatch(staged.workspaceDir, input.candidatePatch, paths);
7825
+ const paths2 = validateCodePatch(input.candidatePatch, policy.maximumPatchBytes);
7826
+ await applyCodePatch(staged.workspaceDir, input.candidatePatch, paths2);
7635
7827
  const sourceDigest = await digestStagedWorkspace(staged.workspaceDir, limits);
7636
7828
  const policyDigest = digestPolicy(policy);
7637
7829
  const patchDigest = digestBytes(input.candidatePatch);
@@ -7640,7 +7832,7 @@ async function verifyCodeCandidate(input) {
7640
7832
  trustedBaseDigest: input.trustedBaseDigest,
7641
7833
  patchDigest
7642
7834
  });
7643
- const changedTests = changedTestPaths(paths, policy);
7835
+ const changedTests = changedTestPaths(paths2, policy);
7644
7836
  if (changedTests.length > policy.maximumChangedTests) throw new TypeError("candidate changes too many test files");
7645
7837
  const recipes = [];
7646
7838
  const logs = [];
@@ -7707,8 +7899,8 @@ function validate2(input) {
7707
7899
  }
7708
7900
  return result;
7709
7901
  }
7710
- function changedTestPaths(paths, policy) {
7711
- return paths.filter((path) => policy.testPathSuffixes.some((suffix) => path.endsWith(suffix)) || policy.testPathPrefixes.some((prefix) => path.startsWith(prefix) || path.includes(`/${prefix}`))).sort();
7902
+ function changedTestPaths(paths2, policy) {
7903
+ return paths2.filter((path) => policy.testPathSuffixes.some((suffix) => path.endsWith(suffix)) || policy.testPathPrefixes.some((prefix) => path.startsWith(prefix) || path.includes(`/${prefix}`))).sort();
7712
7904
  }
7713
7905
  function recipeReceipt(recipe2, result, artifacts) {
7714
7906
  const status = result.timedOut ? "timed_out" : result.outputLimitExceeded ? "output_limited" : result.exitCode === 0 && artifacts.every((item) => item.status === "verified") ? "passed" : "failed";
@@ -8439,7 +8631,7 @@ function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = regi
8439
8631
  files(root) {
8440
8632
  const existing = cache2.get(root);
8441
8633
  if (existing) return existing;
8442
- const pending = enumerate(root, limit).then((paths) => Object.freeze(paths));
8634
+ const pending = enumerate(root, limit).then((paths2) => Object.freeze(paths2));
8443
8635
  cache2.set(root, pending);
8444
8636
  void pending.catch(() => {
8445
8637
  if (cache2.get(root) === pending) cache2.delete(root);
@@ -8452,7 +8644,7 @@ function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = regi
8452
8644
  };
8453
8645
  }
8454
8646
  async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
8455
- const paths = [];
8647
+ const paths2 = [];
8456
8648
  const walk = async (directory) => {
8457
8649
  for (const entry of await readdir22(directory, { withFileTypes: true })) {
8458
8650
  if (SKIP_WORKSPACE_DIRS.has(entry.name)) continue;
@@ -8466,26 +8658,26 @@ async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
8466
8658
  } catch {
8467
8659
  continue;
8468
8660
  }
8469
- paths.push(path);
8470
- if (paths.length > limit) throw new TypeError("workspace file registry exceeds its bound");
8661
+ paths2.push(path);
8662
+ if (paths2.length > limit) throw new TypeError("workspace file registry exceeds its bound");
8471
8663
  }
8472
8664
  }
8473
8665
  };
8474
8666
  await walk(resolve42(root));
8475
- return paths.sort();
8667
+ return paths2.sort();
8476
8668
  }
8477
- function listWorkspace(paths, options = {}) {
8669
+ function listWorkspace(paths2, options = {}) {
8478
8670
  const max = options.maxEntries ?? 1e3;
8479
8671
  const prefix = options.prefix?.replace(/\/+$/, "");
8480
- const scoped = prefix ? paths.filter((path) => path === prefix || path.startsWith(`${prefix}/`)) : [...paths];
8672
+ const scoped = prefix ? paths2.filter((path) => path === prefix || path.startsWith(`${prefix}/`)) : [...paths2];
8481
8673
  return scoped.slice(0, max);
8482
8674
  }
8483
- async function searchWorkspace(root, paths, options) {
8675
+ async function searchWorkspace(root, paths2, options) {
8484
8676
  options.signal?.throwIfAborted();
8485
8677
  if (!options.query) throw new TypeError("search query must be a non-empty string");
8486
8678
  const maxResults = options.maxResults ?? DEFAULT_MAX_RESULTS;
8487
8679
  const maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES;
8488
- const scoped = listWorkspace(paths, { ...options.prefix ? { prefix: options.prefix } : {}, maxEntries: paths.length });
8680
+ const scoped = listWorkspace(paths2, { ...options.prefix ? { prefix: options.prefix } : {}, maxEntries: paths2.length });
8489
8681
  if (scoped.length === 0) return [];
8490
8682
  try {
8491
8683
  return await nativeSearch(root, scoped, { ...options, maxResults, maxFileBytes });
@@ -8495,11 +8687,11 @@ async function searchWorkspace(root, paths, options) {
8495
8687
  }
8496
8688
  }
8497
8689
  var MAX_NATIVE_ARG_BYTES = 96 * 1024;
8498
- async function nativeSearch(root, paths, options) {
8690
+ async function nativeSearch(root, paths2, options) {
8499
8691
  const batches = [];
8500
8692
  let batch = [];
8501
8693
  let bytes = 0;
8502
- for (const path of paths) {
8694
+ for (const path of paths2) {
8503
8695
  const size = Buffer.byteLength(path) + 1;
8504
8696
  if (batch.length > 0 && bytes + size > MAX_NATIVE_ARG_BYTES) {
8505
8697
  batches.push(batch);
@@ -8518,7 +8710,7 @@ async function nativeSearch(root, paths, options) {
8518
8710
  }
8519
8711
  return matches;
8520
8712
  }
8521
- function nativeSearchBatch(root, paths, options, remaining) {
8713
+ function nativeSearchBatch(root, paths2, options, remaining) {
8522
8714
  return new Promise((resolveMatches, reject) => {
8523
8715
  const args = [
8524
8716
  "--fixed-strings",
@@ -8531,7 +8723,7 @@ function nativeSearchBatch(root, paths, options, remaining) {
8531
8723
  options.caseSensitive === false ? "--ignore-case" : "--case-sensitive",
8532
8724
  "--",
8533
8725
  options.query,
8534
- ...paths
8726
+ ...paths2
8535
8727
  ];
8536
8728
  const child = spawn33("rg", args, {
8537
8729
  cwd: root,
@@ -8717,16 +8909,16 @@ function createCodePolicyGate(options) {
8717
8909
  }
8718
8910
  };
8719
8911
  }
8720
- function directoryPrefixes(paths) {
8912
+ function directoryPrefixes(paths2) {
8721
8913
  const prefixes = /* @__PURE__ */ new Set(["."]);
8722
- for (const path of paths) {
8914
+ for (const path of paths2) {
8723
8915
  const parts = path.split("/");
8724
8916
  for (let index = 1; index < parts.length; index += 1) prefixes.add(parts.slice(0, index).join("/"));
8725
8917
  }
8726
8918
  return [...prefixes].sort();
8727
8919
  }
8728
- async function safePrefix(base, paths, prefix) {
8729
- const prefixes = directoryPrefixes(paths);
8920
+ async function safePrefix(base, paths2, prefix) {
8921
+ const prefixes = directoryPrefixes(paths2);
8730
8922
  const conversions = await conversionRegistry(
8731
8923
  [await registeredPolicy("code.prefix.v1", "code.prefixes.v1", prefixes)],
8732
8924
  { "code.prefixes.v1": prefixes }
@@ -8838,7 +9030,7 @@ function response(request3, ok, content2, details) {
8838
9030
  return { requestId: request3.requestId, ok, content: content2, ...details ? { details } : {} };
8839
9031
  }
8840
9032
  var cache = /* @__PURE__ */ new Map();
8841
- function workspaceGraphs(workspaceDir, paths) {
9033
+ function workspaceGraphs(workspaceDir, paths2) {
8842
9034
  const existing = cache.get(workspaceDir);
8843
9035
  if (existing) return existing;
8844
9036
  const read22 = (path) => readFile3(join33(workspaceDir, path), "utf8");
@@ -8846,7 +9038,7 @@ function workspaceGraphs(workspaceDir, paths) {
8846
9038
  // No knownTables: a staged workspace may not carry migrations, and a filter
8847
9039
  // that silently drops every table is worse than an unfiltered one. Callers
8848
9040
  // with ground truth should build the graph themselves.
8849
- graph: await buildCodeGraph({ paths, read: read22, data: { ignore: (path) => path.includes(".generated.") } })
9041
+ graph: await buildCodeGraph({ paths: paths2, read: read22, data: { ignore: (path) => path.includes(".generated.") } })
8850
9042
  }))();
8851
9043
  cache.set(workspaceDir, built);
8852
9044
  return built;
@@ -8906,11 +9098,11 @@ async function read(context, request3, options, policy, registry) {
8906
9098
  if (endLine < startLine || endLine - startLine + 1 > (options.maxReadLines ?? 2e3)) {
8907
9099
  throw new TypeError("requested line range exceeds its bound");
8908
9100
  }
8909
- const paths = await registry.files(context.workspaceDir);
8910
- if (!paths.includes(path)) {
9101
+ const paths2 = await registry.files(context.workspaceDir);
9102
+ if (!paths2.includes(path)) {
8911
9103
  throw new TypeError(`no such file in the staged workspace: "${path}". Use sandbox.overview, sandbox.where_is or sandbox.search to find the correct path.`);
8912
9104
  }
8913
- const allowed = await policy.read(policyContext(context, request3, options, { paths, path, startLine, endLine }));
9105
+ const allowed = await policy.read(policyContext(context, request3, options, { paths: paths2, path, startLine, endLine }));
8914
9106
  if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
8915
9107
  const target = resolveCodePath(context.workspaceDir, path);
8916
9108
  const info = await stat2(target);
@@ -8932,16 +9124,16 @@ async function list(context, request3, options, policy, registry) {
8932
9124
  const prefix = typeof raw === "string" && raw.length > 0 ? raw : void 0;
8933
9125
  const maxEntries = optionalInteger(request3.input.maxEntries) ?? 1e3;
8934
9126
  if (maxEntries > 5e3) throw new TypeError("maxEntries exceeds its bound");
8935
- const paths = await registry.files(context.workspaceDir);
8936
- const allowed = await policy.list(policyContext(context, request3, options, { paths, ...prefix ? { prefix } : {} }));
9127
+ const paths2 = await registry.files(context.workspaceDir);
9128
+ const allowed = await policy.list(policyContext(context, request3, options, { paths: paths2, ...prefix ? { prefix } : {} }));
8937
9129
  if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
8938
- const entries = listWorkspace(paths, { ...prefix ? { prefix } : {}, maxEntries });
9130
+ const entries = listWorkspace(paths2, { ...prefix ? { prefix } : {}, maxEntries });
8939
9131
  if (!entries.length) {
8940
9132
  return response(request3, true, prefix ? `No files under "${prefix}".` : "Workspace is empty.", { count: 0 });
8941
9133
  }
8942
- const truncated = entries.length < paths.length && entries.length === maxEntries;
8943
- const hint = !prefix && paths.length > 500 ? `
8944
- \u2026 ${paths.length} files total. sandbox.overview is far cheaper for orientation; use a prefix here once you know the area.` : "";
9134
+ const truncated = entries.length < paths2.length && entries.length === maxEntries;
9135
+ const hint = !prefix && paths2.length > 500 ? `
9136
+ \u2026 ${paths2.length} files total. sandbox.overview is far cheaper for orientation; use a prefix here once you know the area.` : "";
8945
9137
  return response(
8946
9138
  request3,
8947
9139
  true,
@@ -8959,10 +9151,10 @@ async function search(context, request3, options, policy, registry) {
8959
9151
  const maxResults = optionalInteger(request3.input.maxResults) ?? 100;
8960
9152
  if (maxResults > 500) throw new TypeError("maxResults exceeds its bound");
8961
9153
  const caseSensitive = request3.input.caseSensitive === void 0 ? true : request3.input.caseSensitive === true;
8962
- const paths = await registry.files(context.workspaceDir);
8963
- const allowed = await policy.search(policyContext(context, request3, options, { paths, query, ...prefix ? { prefix } : {} }));
9154
+ const paths2 = await registry.files(context.workspaceDir);
9155
+ const allowed = await policy.search(policyContext(context, request3, options, { paths: paths2, query, ...prefix ? { prefix } : {} }));
8964
9156
  if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
8965
- const matches = await searchWorkspace(context.workspaceDir, paths, {
9157
+ const matches = await searchWorkspace(context.workspaceDir, paths2, {
8966
9158
  query,
8967
9159
  maxResults,
8968
9160
  caseSensitive,
@@ -8984,8 +9176,8 @@ async function graphQuery(context, request3, options, policy, registry) {
8984
9176
  selector: query
8985
9177
  }));
8986
9178
  if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
8987
- const paths = await registry.files(context.workspaceDir);
8988
- const graphs = await workspaceGraphs(context.workspaceDir, paths);
9179
+ const paths2 = await registry.files(context.workspaceDir);
9180
+ const graphs = await workspaceGraphs(context.workspaceDir, paths2);
8989
9181
  if (request3.tool === "sandbox.overview") {
8990
9182
  return response(request3, true, renderOverview(graphs, query || void 0));
8991
9183
  }
@@ -9047,16 +9239,16 @@ function toolFailureMessage(reason) {
9047
9239
  async function patch(context, request3, options, policy, registry) {
9048
9240
  exactKeys(request3.input, ["patch"]);
9049
9241
  const value2 = stringField(request3.input, "patch");
9050
- const paths = validateCodePatch(value2, options.maxPatchBytes ?? 256 * 1024);
9051
- if (paths.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
9242
+ const paths2 = validateCodePatch(value2, options.maxPatchBytes ?? 256 * 1024);
9243
+ if (paths2.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
9052
9244
  throw new TypeError("patch targets a read-only reference source");
9053
9245
  }
9054
9246
  const allowed = await policy.patch(policyContext(context, request3, options, { patch: value2 }));
9055
9247
  if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
9056
- await applyCodePatch(context.workspaceDir, value2, paths);
9248
+ await applyCodePatch(context.workspaceDir, value2, paths2);
9057
9249
  registry.invalidate(context.workspaceDir);
9058
9250
  forgetWorkspaceGraphs(context.workspaceDir);
9059
- return response(request3, true, `Applied patch to ${paths.length} file(s).`, { paths });
9251
+ return response(request3, true, `Applied patch to ${paths2.length} file(s).`, { paths: paths2 });
9060
9252
  }
9061
9253
  async function recipe(context, request3, options, recipes, policy) {
9062
9254
  exactKeys(request3.input, ["recipeId"]);
@@ -9405,11 +9597,126 @@ async function startGoalPursuit(input) {
9405
9597
  async function appendCodeRuntimeEvent(control, command, event, refs) {
9406
9598
  const eventId = `${command.commandId.slice(0, 45)}:${refs.length + 1}`;
9407
9599
  refs.push(eventId);
9408
- const bounded = event.type === "message" ? { ...event, body: event.body.trim().slice(0, 2e4) || `${event.actor} event` } : event;
9600
+ const attributed = { ...event, interactionId: command.commandId };
9601
+ const bounded = attributed.type === "message" ? { ...attributed, body: attributed.body.trim().slice(0, 2e4) || `${attributed.actor} event` } : attributed;
9409
9602
  await control.appendSessionEvent(command.sessionId, eventId, bounded);
9410
9603
  }
9411
9604
  var digestRuntimeValue = (value2) => `sha256:${createHash32("sha256").update(value2).digest("hex")}`;
9412
9605
  var runtimeErrorMessage = (value2) => value2 instanceof Error ? value2.message : String(value2);
9606
+ var text3 = (value2, maximum) => {
9607
+ if (typeof value2 !== "string") return void 0;
9608
+ const bounded = value2.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ").trim();
9609
+ return bounded ? bounded.slice(0, maximum) : void 0;
9610
+ };
9611
+ var integer2 = (value2) => Number.isSafeInteger(value2) && Number(value2) >= 0 ? Number(value2) : void 0;
9612
+ var record32 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : void 0;
9613
+ var excerpt = (value2, tail = false) => {
9614
+ if (typeof value2 !== "string") return void 0;
9615
+ const safe = value2.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ");
9616
+ const source = (tail ? safe.slice(-1e4) : safe.slice(0, 1e4)).trim();
9617
+ if (!source) return void 0;
9618
+ const lines = source.split("\n").filter((line2) => line2.trim()).map((line2) => line2.slice(0, 240));
9619
+ const selected = tail ? lines.slice(-10) : lines.slice(0, 10);
9620
+ return text3(selected.join("\n"), 2400);
9621
+ };
9622
+ var paths = (value2) => {
9623
+ if (!Array.isArray(value2)) return void 0;
9624
+ const items = value2.flatMap((item) => {
9625
+ const path = text3(item, 1024);
9626
+ return path ? [path] : [];
9627
+ }).slice(0, 12);
9628
+ return items.length ? items : void 0;
9629
+ };
9630
+ function patchStats(value2) {
9631
+ if (typeof value2 !== "string") return {};
9632
+ let additions = 0;
9633
+ let deletions = 0;
9634
+ for (const line2 of value2.slice(0, 262144).split("\n")) {
9635
+ if (line2.startsWith("+++") || line2.startsWith("---")) continue;
9636
+ if (line2.startsWith("+")) additions += 1;
9637
+ else if (line2.startsWith("-")) deletions += 1;
9638
+ }
9639
+ return { ...additions ? { additions } : {}, ...deletions ? { deletions } : {} };
9640
+ }
9641
+ function searchResults(value2) {
9642
+ if (typeof value2 !== "string") return void 0;
9643
+ const results = value2.split("\n").flatMap((line2) => {
9644
+ const match = /^([^:\n]{1,1024}):(\d+):\s?(.*)$/.exec(line2);
9645
+ if (!match) return [];
9646
+ const lineNumber = Number(match[2]);
9647
+ const itemText = text3(match[3], 240);
9648
+ if (!Number.isSafeInteger(lineNumber) || lineNumber < 1) return [];
9649
+ return [{ path: match[1], line: lineNumber, ...itemText ? { text: itemText } : {} }];
9650
+ }).slice(0, 5);
9651
+ return results.length ? results : void 0;
9652
+ }
9653
+ function codeToolRequestPresentation(request3) {
9654
+ const input = request3.input;
9655
+ if (request3.tool === "sandbox.read") {
9656
+ const path = text3(input.path, 1024);
9657
+ if (!path) return void 0;
9658
+ const startLine = integer2(input.startLine);
9659
+ const endLine = integer2(input.endLine);
9660
+ return { kind: "read", path, ...startLine ? { startLine } : {}, ...endLine ? { endLine } : {} };
9661
+ }
9662
+ if (request3.tool === "sandbox.list") {
9663
+ const scope = text3(input.prefix, 1024);
9664
+ return { kind: "list", ...scope ? { scope } : {} };
9665
+ }
9666
+ if (request3.tool === "sandbox.search" || request3.tool === "sandbox.overview" || request3.tool === "sandbox.where_is" || request3.tool === "sandbox.who_imports" || request3.tool === "sandbox.who_touches") {
9667
+ const query = text3(input.query, 512);
9668
+ const scope = request3.tool === "sandbox.search" ? text3(input.prefix, 1024) : void 0;
9669
+ if (request3.tool === "sandbox.search" && !query) return void 0;
9670
+ return { kind: "query", ...query ? { query } : {}, ...scope ? { scope } : {} };
9671
+ }
9672
+ if (request3.tool === "sandbox.apply_patch") {
9673
+ return { kind: "patch", ...patchStats(input.patch) };
9674
+ }
9675
+ const recipeId = text3(input.recipeId, 120);
9676
+ return recipeId ? { kind: "recipe", recipeId } : void 0;
9677
+ }
9678
+ function codeToolResultPresentation(request3, response2) {
9679
+ const started = codeToolRequestPresentation(request3);
9680
+ if (!started || !response2.ok) return started;
9681
+ const details = record32(response2.details);
9682
+ if (started.kind === "read") {
9683
+ return {
9684
+ ...started,
9685
+ ...integer2(details?.startLine) ? { startLine: integer2(details?.startLine) } : {},
9686
+ ...integer2(details?.endLine) ? { endLine: integer2(details?.endLine) } : {},
9687
+ ...excerpt(response2.content) ? { excerpt: excerpt(response2.content) } : {}
9688
+ };
9689
+ }
9690
+ if (started.kind === "list") {
9691
+ const listed = response2.content.split("\n").filter((line2) => line2 && !line2.startsWith("\u2026") && !line2.startsWith("Workspace ")).map((line2) => text3(line2, 1024)).filter((line2) => Boolean(line2)).slice(0, 8);
9692
+ return {
9693
+ ...started,
9694
+ ...integer2(details?.count) !== void 0 ? { count: integer2(details?.count) } : {},
9695
+ ...listed.length ? { paths: listed } : {}
9696
+ };
9697
+ }
9698
+ if (started.kind === "query") {
9699
+ const results = request3.tool === "sandbox.search" ? searchResults(response2.content) : void 0;
9700
+ const resultExcerpt = request3.tool === "sandbox.search" ? void 0 : excerpt(response2.content);
9701
+ return {
9702
+ ...started,
9703
+ ...integer2(details?.count) !== void 0 ? { count: integer2(details?.count) } : {},
9704
+ ...results ? { results } : {},
9705
+ ...resultExcerpt ? { excerpt: resultExcerpt } : {}
9706
+ };
9707
+ }
9708
+ if (started.kind === "patch") {
9709
+ return { ...started, ...paths(details?.paths) ? { paths: paths(details?.paths) } : {} };
9710
+ }
9711
+ const output = response2.content.replace(/^Recipe [^\n]*\.?\s*/u, "");
9712
+ return {
9713
+ ...started,
9714
+ ...integer2(details?.exitCode) !== void 0 ? { exitCode: integer2(details?.exitCode) } : {},
9715
+ ...typeof details?.timedOut === "boolean" ? { timedOut: details.timedOut } : {},
9716
+ ...typeof details?.outputLimitExceeded === "boolean" ? { outputLimitExceeded: details.outputLimitExceeded } : {},
9717
+ ...excerpt(output, true) ? { excerpt: excerpt(output, true) } : {}
9718
+ };
9719
+ }
9413
9720
  var TheseusRuntimeEngine = class {
9414
9721
  constructor(options) {
9415
9722
  this.options = options;
@@ -9643,18 +9950,29 @@ var TheseusRuntimeEngine = class {
9643
9950
  return {
9644
9951
  execute: async (context, request3) => {
9645
9952
  const startedAt = Date.now();
9953
+ const operationId = digestRuntimeValue(`${command.commandId}:${request3.requestId}`);
9954
+ const startedPresentation = codeToolRequestPresentation(request3);
9646
9955
  await this.#event(
9647
9956
  command,
9648
- { type: "tool", phase: "started", tool: request3.tool },
9957
+ {
9958
+ type: "tool",
9959
+ phase: "started",
9960
+ tool: request3.tool,
9961
+ operationId,
9962
+ ...startedPresentation ? { presentation: startedPresentation } : {}
9963
+ },
9649
9964
  active.conversationRefs
9650
9965
  ).catch(() => void 0);
9651
9966
  const response2 = await broker.execute(context, request3);
9967
+ const completedPresentation = codeToolResultPresentation(request3, response2);
9652
9968
  await this.#event(command, {
9653
9969
  type: "tool",
9654
9970
  phase: "completed",
9655
9971
  tool: request3.tool,
9656
9972
  ok: response2.ok,
9657
- durationMs: Date.now() - startedAt
9973
+ durationMs: Date.now() - startedAt,
9974
+ operationId,
9975
+ ...completedPresentation ? { presentation: completedPresentation } : {}
9658
9976
  }, active.conversationRefs).catch(() => void 0);
9659
9977
  return response2;
9660
9978
  }
@@ -10188,9 +10506,7 @@ function grantsUrl(cfg, suffix = "") {
10188
10506
  async function codeGrantCommand(parsed, deps = {}) {
10189
10507
  const action2 = parsed.positionals[2];
10190
10508
  if (!isAction(action2)) {
10191
- throw new Error(
10192
- `unknown code grant action "${action2 ?? ""}". Try "odla-ai code grant list --env dev".`
10193
- );
10509
+ rejectWord(["code", "grant"], action2);
10194
10510
  }
10195
10511
  const decides = action2 === "approve" || action2 === "revoke";
10196
10512
  assertArgs(parsed, ["config", "env", "json", "token", "email", "open"], decides ? 4 : 3);
@@ -10310,9 +10626,7 @@ function repositoryUrl(cfg, suffix = "") {
10310
10626
  async function codeRepositoryCommand(parsed, deps = {}) {
10311
10627
  const action2 = parsed.positionals[2];
10312
10628
  if (!isAction2(action2)) {
10313
- throw new Error(
10314
- `unknown code repository action "${action2 ?? ""}". Try "odla-ai code repository show --env dev".`
10315
- );
10629
+ rejectWord(["code", "repository"], action2);
10316
10630
  }
10317
10631
  assertArgs(parsed, ["config", "env", "repo", "json", "token", "email", "open"], 3);
10318
10632
  const cfg = await loadProjectConfig(stringOpt(parsed.options.config) ?? "odla.config.mjs");
@@ -10387,9 +10701,7 @@ async function codeCommand(parsed, dependencies) {
10387
10701
  });
10388
10702
  }
10389
10703
  if (sub !== "connect") {
10390
- throw new Error(
10391
- `unknown code subcommand "${sub ?? ""}". Try "odla-ai code connect --env dev" or "odla-ai code grant list --env dev".`
10392
- );
10704
+ rejectWord(["code"], sub);
10393
10705
  }
10394
10706
  assertArgs(parsed, [
10395
10707
  "config",
@@ -10526,9 +10838,7 @@ async function contextCommand(parsed, deps = {}) {
10526
10838
  return;
10527
10839
  }
10528
10840
  if (action2 !== "show") {
10529
- throw new Error(
10530
- `unknown context action "${action2 ?? ""}". Try show|list|save|remove.`
10531
- );
10841
+ rejectWord(["context"], action2);
10532
10842
  }
10533
10843
  const context = await resolveOperatorContext(parsed, {
10534
10844
  allowMissingConfig: true,
@@ -10584,9 +10894,7 @@ async function responseError(response2) {
10584
10894
  }
10585
10895
  async function credentialCommand(parsed, deps = {}) {
10586
10896
  const action2 = parsed.positionals[1] ?? "list";
10587
- if (action2 !== "list" && action2 !== "revoke") {
10588
- throw new Error(`unknown credentials action "${action2}". Try "odla-ai credentials list".`);
10589
- }
10897
+ if (action2 !== "list" && action2 !== "revoke") rejectWord(["credentials"], action2);
10590
10898
  assertArgs(parsed, ["config", "env", "all", "json", "token", "email", "open"], action2 === "revoke" ? 3 : 2);
10591
10899
  const cfg = await loadProjectConfig(stringOpt(parsed.options.config) ?? "odla.config.mjs");
10592
10900
  const doFetch = deps.fetch ?? fetch;
@@ -11588,7 +11896,7 @@ async function discussCommand(parsed, deps = {}) {
11588
11896
  assertArgs(parsed, ALLOWED, 3);
11589
11897
  const action2 = parsed.positionals[1];
11590
11898
  const id2 = parsed.positionals[2];
11591
- if (!action2) throw new Error('"discuss" needs an action. Run "odla-ai help".');
11899
+ if (!acceptedAfter(["discuss"]).includes(action2 ?? "")) rejectWord(["discuss"], action2);
11592
11900
  const ctx = await buildContext(parsed, deps);
11593
11901
  switch (action2) {
11594
11902
  case "groups":
@@ -11612,7 +11920,7 @@ async function discussCommand(parsed, deps = {}) {
11612
11920
  return;
11613
11921
  }
11614
11922
  default:
11615
- throw new Error(`unknown discuss action "${action2}". Run "odla-ai help".`);
11923
+ rejectWord(["discuss"], action2);
11616
11924
  }
11617
11925
  }
11618
11926
 
@@ -11688,8 +11996,8 @@ function collectFields(parsed, allowClear) {
11688
11996
  if (allowClear) out[spec.key] = null;
11689
11997
  continue;
11690
11998
  }
11691
- const text3 = stringOpt(value2);
11692
- out[spec.key] = spec.num ? Number(text3) : text3;
11999
+ const text4 = stringOpt(value2);
12000
+ out[spec.key] = spec.num ? Number(text4) : text4;
11693
12001
  }
11694
12002
  return out;
11695
12003
  }
@@ -12522,7 +12830,7 @@ async function pmCommand(parsed, deps = {}) {
12522
12830
  assertArgs(parsed, COMMON_OPTIONS, 4);
12523
12831
  return pmProjectUse(await buildContext2(parsed, deps), requireId2(parsed.positionals[3], action3));
12524
12832
  }
12525
- throw new Error(`unknown pm project action "${action3}". Try list|add|use.`);
12833
+ rejectWord(["pm", "project"], action3);
12526
12834
  }
12527
12835
  if (word === "next") {
12528
12836
  assertArgs(parsed, [...COMMON_OPTIONS, "app", "project", "verbose"], 2);
@@ -12553,10 +12861,10 @@ async function pmCommand(parsed, deps = {}) {
12553
12861
  return pmHandoff(await buildContext2(parsed, deps), parsed);
12554
12862
  }
12555
12863
  const entity = ALIASES[word];
12556
- if (!entity) throw new Error(`unknown pm entity "${word}". Try "odla-ai pm start" to claim Ready work, or "odla-ai pm bug list" (goal|task|decision|bug).`);
12864
+ if (!entity) rejectWord(["pm"], word);
12557
12865
  const requestedAction = parsed.positionals[2] ?? "list";
12558
12866
  const action2 = canonicalAction(requestedAction);
12559
- if (!action2) throw new Error(`unknown pm action "${requestedAction}". Try list|add|get|set|done|link|ref|comment|comments|history|rm.`);
12867
+ if (!action2) rejectWord(["pm", word], requestedAction);
12560
12868
  assertArgs(parsed, allowedOptions(entity, action2), 4);
12561
12869
  if ((action2 === "ready" || action2 === "claim" || action2 === "release") && entity !== "task") {
12562
12870
  throw new Error(`pm ${action2} is only valid for tasks`);
@@ -12642,12 +12950,7 @@ async function platformCommand(parsed, deps = {}) {
12642
12950
  if (action2 === "status") {
12643
12951
  return platformStatus(parsed, deps);
12644
12952
  }
12645
- throw new Error(
12646
- `unknown platform action "${[
12647
- action2,
12648
- parsed.positionals[2]
12649
- ].filter(Boolean).join(" ")}". Try "odla-ai platform status --json".`
12650
- );
12953
+ rejectWord(["platform"], action2);
12651
12954
  }
12652
12955
  async function platformStatus(parsed, deps) {
12653
12956
  assertArgs(
@@ -12984,9 +13287,7 @@ async function o11yCommand(parsed, deps = {}) {
12984
13287
  );
12985
13288
  const action2 = parsed.positionals[1];
12986
13289
  if (action2 !== "status") {
12987
- throw new Error(
12988
- `unknown o11y action "${action2 ?? ""}". Try "odla-ai o11y status --json".`
12989
- );
13290
+ rejectWord(["o11y"], action2);
12990
13291
  }
12991
13292
  const minutes = statusMinutes(
12992
13293
  numberOpt(parsed.options.minutes, "--minutes") ?? 60
@@ -13101,14 +13402,14 @@ function statusMinutes(value2) {
13101
13402
  }
13102
13403
  async function read2(url, headers, doFetch) {
13103
13404
  const response2 = await doFetch(url, { headers });
13104
- const text3 = await response2.text();
13405
+ const text4 = await response2.text();
13105
13406
  let body = {};
13106
- if (text3) {
13407
+ if (text4) {
13107
13408
  try {
13108
- const value2 = JSON.parse(text3);
13409
+ const value2 = JSON.parse(text4);
13109
13410
  body = value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : { value: value2 };
13110
13411
  } catch {
13111
- body = { message: text3.slice(0, 300) };
13412
+ body = { message: text4.slice(0, 300) };
13112
13413
  }
13113
13414
  }
13114
13415
  return { httpStatus: response2.status, body };
@@ -13239,9 +13540,7 @@ var OPTIONS = [
13239
13540
  async function monitorCommand(parsed, deps = {}) {
13240
13541
  assertArgs(parsed, OPTIONS, 3);
13241
13542
  const action2 = parsed.positionals[1] ?? "status";
13242
- if (!["plan", "apply", "run", "status", "incidents", "report"].includes(action2)) {
13243
- throw new Error(`unknown monitor action "${action2}". Try "odla-ai monitor status --json".`);
13244
- }
13543
+ if (!acceptedAfter(["monitor"]).includes(action2)) rejectWord(["monitor"], action2);
13245
13544
  const context = await resolveOperatorContext(parsed, {
13246
13545
  allowMissingConfig: action2 !== "plan" && action2 !== "apply",
13247
13546
  requireApp: true
@@ -13338,13 +13637,13 @@ async function monitorCommand(parsed, deps = {}) {
13338
13637
  }
13339
13638
  async function request2(url, init, doFetch) {
13340
13639
  const response2 = await doFetch(url, init);
13341
- const text3 = await response2.text();
13640
+ const text4 = await response2.text();
13342
13641
  let body = {};
13343
13642
  try {
13344
- const parsed = text3 ? JSON.parse(text3) : {};
13643
+ const parsed = text4 ? JSON.parse(text4) : {};
13345
13644
  body = record10(parsed) ? parsed : { value: parsed };
13346
13645
  } catch {
13347
- body = { message: text3.slice(0, 500) };
13646
+ body = { message: text4.slice(0, 500) };
13348
13647
  }
13349
13648
  if (!response2.ok) {
13350
13649
  const error = record10(body.error) ? body.error : body;
@@ -13547,8 +13846,8 @@ function runtimeUrl(cfg, suffix = "") {
13547
13846
  return `${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/runtime-credentials${suffix}`;
13548
13847
  }
13549
13848
  async function safeError(response2) {
13550
- const text3 = await response2.text();
13551
- return redactSecrets(text3.slice(0, 1e3));
13849
+ const text4 = await response2.text();
13850
+ return redactSecrets(text4.slice(0, 1e3));
13552
13851
  }
13553
13852
  async function finish(doFetch, cfg, token, sessionId, method) {
13554
13853
  return doFetch(runtimeUrl(cfg, `/${encodeURIComponent(sessionId)}`), {
@@ -13871,186 +14170,6 @@ async function provision(options) {
13871
14170
  // src/record.ts
13872
14171
  import { appendFileSync } from "fs";
13873
14172
  import process19 from "process";
13874
-
13875
- // src/surface.ts
13876
- var PM_ACTIONS = {
13877
- list: {},
13878
- add: {},
13879
- create: {},
13880
- get: {},
13881
- set: {},
13882
- update: {},
13883
- status: {},
13884
- move: {},
13885
- done: {},
13886
- comment: {},
13887
- comments: {},
13888
- ref: {},
13889
- rm: {},
13890
- delete: {}
13891
- };
13892
- var PM_TASK_ACTIONS = {
13893
- ...PM_ACTIONS,
13894
- ready: {},
13895
- claim: {},
13896
- release: {}
13897
- };
13898
- var PM_ENTITIES = {
13899
- ...Object.fromEntries(
13900
- ["goal", "conformance", "decision", "bug"].map((entity) => [entity, PM_ACTIONS])
13901
- ),
13902
- task: PM_TASK_ACTIONS,
13903
- kanban: PM_TASK_ACTIONS
13904
- };
13905
- var COMMAND_SURFACE = {
13906
- agent: { jobs: {}, retry: {} },
13907
- ai: { models: {} },
13908
- admin: {
13909
- ai: {
13910
- show: {},
13911
- set: {},
13912
- credentials: {},
13913
- models: {},
13914
- usage: {},
13915
- audit: {},
13916
- credential: { set: {} }
13917
- }
13918
- },
13919
- app: {
13920
- archive: {},
13921
- restore: {},
13922
- export: {},
13923
- import: {},
13924
- rename: {},
13925
- "refresh-sandbox": {},
13926
- "go-live": {},
13927
- promote: {},
13928
- owners: { list: {}, add: {}, remove: {} }
13929
- },
13930
- auth: { login: {} },
13931
- brand: { design: { unpack: {} } },
13932
- bug: { create: {}, list: {}, report: {} },
13933
- calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
13934
- capabilities: {},
13935
- code: {
13936
- connect: {},
13937
- grant: { request: {}, list: {}, approve: {}, revoke: {} },
13938
- repository: { show: {}, list: {}, bind: {} }
13939
- },
13940
- config: { diff: {}, plan: {}, apply: {} },
13941
- context: { show: {}, list: {}, save: {}, remove: {} },
13942
- credentials: { list: {}, revoke: {} },
13943
- device: { enroll: {}, list: {}, revoke: {} },
13944
- // `watch`, `read`, `reply`, and `resolve` take a topic id from there on.
13945
- discuss: {
13946
- groups: {},
13947
- list: {},
13948
- topics: {},
13949
- read: {},
13950
- post: {},
13951
- reply: {},
13952
- resolve: {},
13953
- who: {},
13954
- watch: {}
13955
- },
13956
- doctor: {},
13957
- help: {},
13958
- init: {},
13959
- monitor: { plan: {}, apply: {}, run: {}, status: {}, incidents: {}, report: {} },
13960
- o11y: { status: {} },
13961
- operations: { get: {}, wait: {} },
13962
- platform: {
13963
- status: {}
13964
- },
13965
- pm: {
13966
- ...PM_ENTITIES,
13967
- project: { list: {}, add: {}, create: {}, use: {} },
13968
- handoff: {},
13969
- next: {},
13970
- watch: {}
13971
- },
13972
- provision: {},
13973
- runbook: {
13974
- ask: {},
13975
- search: {},
13976
- impact: {},
13977
- list: {},
13978
- get: {},
13979
- cat: {},
13980
- new: {},
13981
- edit: {},
13982
- comment: {},
13983
- import: {},
13984
- visibility: {},
13985
- publish: {},
13986
- archive: {},
13987
- history: {},
13988
- revert: {},
13989
- rm: {},
13990
- lint: {}
13991
- },
13992
- secrets: { push: {}, status: {}, set: {}, "set-clerk-key": {} },
13993
- security: {
13994
- plan: {},
13995
- sources: {},
13996
- run: {},
13997
- status: {},
13998
- report: {},
13999
- github: { connect: {}, disconnect: {} }
14000
- },
14001
- setup: {},
14002
- skill: { install: {} },
14003
- smoke: {},
14004
- version: {},
14005
- whoami: {}
14006
- };
14007
- function acceptedAfter(path) {
14008
- let node = COMMAND_SURFACE;
14009
- for (const word of path) {
14010
- node = node?.[word];
14011
- if (!node) return [];
14012
- }
14013
- return Object.keys(node).sort();
14014
- }
14015
- function validateInvocation(words2) {
14016
- let node = COMMAND_SURFACE;
14017
- const walked = [];
14018
- for (const word of words2) {
14019
- if (Object.keys(node).length === 0) return null;
14020
- const next = node[word];
14021
- if (!next) return { validPrefix: walked.join(" "), word, accepted: Object.keys(node).sort() };
14022
- walked.push(word);
14023
- node = next;
14024
- }
14025
- return null;
14026
- }
14027
- function describeProblem(problem) {
14028
- const where = problem.validPrefix ? `after "${problem.validPrefix}"` : "as a command";
14029
- return `"${problem.word}" is not accepted ${where} \u2014 try: ${problem.accepted.join(", ")}`;
14030
- }
14031
- function invocationPath(words2) {
14032
- let node = COMMAND_SURFACE;
14033
- const path = [];
14034
- for (const word of words2) {
14035
- const next = node[word];
14036
- if (!next) break;
14037
- path.push(word);
14038
- node = next;
14039
- if (Object.keys(node).length === 0) break;
14040
- }
14041
- return path;
14042
- }
14043
- function surfacePaths(node = COMMAND_SURFACE, prefix = []) {
14044
- const paths = [];
14045
- for (const [word, child] of Object.entries(node)) {
14046
- const path = [...prefix, word];
14047
- paths.push(path);
14048
- paths.push(...surfacePaths(child, path));
14049
- }
14050
- return paths;
14051
- }
14052
-
14053
- // src/record.ts
14054
14173
  function recordInvocation(parsed) {
14055
14174
  const file = process19.env.ODLA_CLI_RECORD;
14056
14175
  if (!file) return;
@@ -14110,10 +14229,10 @@ var DAY_MS = 24 * 60 * 60 * 1e3;
14110
14229
  var UNITS = { d: DAY_MS, w: 7 * DAY_MS, y: 365 * DAY_MS };
14111
14230
  function parseDeviceTtl(raw) {
14112
14231
  if (raw === void 0 || raw === true) return void 0;
14113
- const text3 = String(raw).trim().toLowerCase();
14114
- if (!text3) return void 0;
14115
- if (text3 === "forever" || text3 === "never") return 100 * 365 * DAY_MS;
14116
- const match = /^(\d+)\s*([dwy])$/.exec(text3);
14232
+ const text4 = String(raw).trim().toLowerCase();
14233
+ if (!text4) return void 0;
14234
+ if (text4 === "forever" || text4 === "never") return 100 * 365 * DAY_MS;
14235
+ const match = /^(\d+)\s*([dwy])$/.exec(text4);
14117
14236
  if (!match) {
14118
14237
  throw new Error(
14119
14238
  `--device-ttl expects a duration like 30d, 6w, 2y, or "forever" (got "${raw}")`
@@ -14144,6 +14263,7 @@ async function deviceCommand(parsed, deps) {
14144
14263
  "wait"
14145
14264
  ], 3);
14146
14265
  const action2 = parsed.positionals[1] ?? "";
14266
+ if (!acceptedAfter(["device"]).includes(action2)) rejectWord(["device"], action2);
14147
14267
  const out = deps.stdout ?? console;
14148
14268
  const doFetch = deps.fetch ?? fetch;
14149
14269
  const cfg = await loadProjectConfig(stringOpt(parsed.options.config));
@@ -14151,7 +14271,7 @@ async function deviceCommand(parsed, deps) {
14151
14271
  if (action2 === "enroll") return enroll(parsed, deps, cfg, doFetch, out, json);
14152
14272
  if (action2 === "list") return list2(parsed, deps, cfg, doFetch, out, json);
14153
14273
  if (action2 === "revoke") return revoke(parsed, deps, cfg, doFetch, out, json);
14154
- throw new Error('odla-ai device expects "enroll", "list", or "revoke"');
14274
+ rejectWord(["device"], action2);
14155
14275
  }
14156
14276
  async function enroll(parsed, deps, cfg, doFetch, out, json) {
14157
14277
  const name = stringOpt(parsed.options.name) ?? defaultDeviceName();
@@ -14446,12 +14566,12 @@ async function runbookRemove(ctx, slug) {
14446
14566
  // src/runbook-import.ts
14447
14567
  import { readFileSync as readFileSync11, readdirSync as readdirSync2, statSync } from "fs";
14448
14568
  import { basename as basename2, join as join15 } from "path";
14449
- function parseRunbook(text3, slug) {
14450
- let rest = text3;
14569
+ function parseRunbook(text4, slug) {
14570
+ let rest = text4;
14451
14571
  const meta = {};
14452
- const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text3);
14572
+ const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text4);
14453
14573
  if (fm) {
14454
- rest = text3.slice(fm[0].length);
14574
+ rest = text4.slice(fm[0].length);
14455
14575
  for (const line2 of fm[1].split(/\r?\n/)) {
14456
14576
  const pair = /^(\w+)\s*:\s*(.+)$/.exec(line2.trim());
14457
14577
  if (!pair) continue;
@@ -15049,6 +15169,7 @@ async function buildContext3(parsed, deps, action2) {
15049
15169
  async function runbookCommand(parsed, deps = {}) {
15050
15170
  const action2 = parsed.positionals[1] ?? "list";
15051
15171
  assertArgs(parsed, ALLOWED2, action2 === "ask" || action2 === "search" ? 64 : 4);
15172
+ if (!acceptedAfter(["runbook"]).includes(action2)) rejectWord(["runbook"], action2);
15052
15173
  const ctx = await buildContext3(parsed, deps, action2);
15053
15174
  const slug = parsed.positionals[2];
15054
15175
  switch (action2) {
@@ -15138,7 +15259,7 @@ async function runbookCommand(parsed, deps = {}) {
15138
15259
  case "rm":
15139
15260
  return runbookRemove(ctx, requireSlug(slug, "rm"));
15140
15261
  default:
15141
- throw new Error(`unknown runbook action "${action2}". Try ${acceptedAfter(["runbook"]).join(", ")}.`);
15262
+ rejectWord(["runbook"], action2);
15142
15263
  }
15143
15264
  }
15144
15265
 
@@ -15759,9 +15880,7 @@ async function securityCommand(parsed, dependencies) {
15759
15880
  else printHostedReport(context.stdout, report5);
15760
15881
  return;
15761
15882
  }
15762
- if (sub !== "run") {
15763
- throw new Error('unknown security command. Try "odla-ai security plan", "security sources", or "security run".');
15764
- }
15883
+ if (sub !== "run") rejectWord(["security"], sub);
15765
15884
  const sourceId = stringOpt(parsed.options.source);
15766
15885
  if (sourceId) await runSourceSecurityCommand(parsed, dependencies, sourceId);
15767
15886
  else await runLocalSecurityCommand(parsed, dependencies);
@@ -15778,9 +15897,7 @@ async function githubSecurityCommand(parsed, dependencies) {
15778
15897
  stringOpt(parsed.options.env)
15779
15898
  );
15780
15899
  }
15781
- if (action2 !== "connect") {
15782
- throw new Error('unknown security github command. Try "odla-ai security github connect".');
15783
- }
15900
+ if (action2 !== "connect") rejectWord(["security", "github"], action2);
15784
15901
  assertArgs(parsed, ["config", "env", "platform", "repo", "email", "open"], 3);
15785
15902
  await requireStudioHuman(
15786
15903
  stringOpt(parsed.options.config) ?? "odla.config.mjs",
@@ -15923,6 +16040,7 @@ async function dispatchCli(argv, dependencies) {
15923
16040
  }
15924
16041
  if (command === "bug") {
15925
16042
  const action2 = parsed.positionals[1] ?? "list";
16043
+ if (!acceptedAfter(["bug"]).includes(action2)) rejectWord(["bug"], action2);
15926
16044
  const canonical2 = action2 === "report" || action2 === "create" ? "add" : action2;
15927
16045
  await pmCommand({
15928
16046
  ...parsed,
@@ -15951,7 +16069,7 @@ async function dispatchCli(argv, dependencies) {
15951
16069
  return;
15952
16070
  }
15953
16071
  if (await projectCommand(command, parsed, runtime)) return;
15954
- throw new Error(`unknown command "${command}". Run "odla-ai help".`);
16072
+ rejectWord([], command);
15955
16073
  }
15956
16074
  async function provisionCommand(parsed, dependencies) {
15957
16075
  assertArgs(parsed, [
@@ -15995,7 +16113,7 @@ async function provisionCommand(parsed, dependencies) {
15995
16113
  async function calendarCommand(parsed, dependencies) {
15996
16114
  const sub = parsed.positionals[1];
15997
16115
  if (sub !== "status" && sub !== "calendars" && sub !== "connect" && sub !== "disconnect") {
15998
- throw new Error(`unknown calendar subcommand "${sub ?? ""}". Try "odla-ai calendar status --env dev".`);
16116
+ rejectWord(["calendar"], sub);
15999
16117
  }
16000
16118
  assertArgs(parsed, ["config", "env", "json", "token", "email", "open", "yes"], 2);
16001
16119
  if (sub !== "status" && sub !== "calendars" && parsed.options.json !== void 0) throw new Error(`--json is supported only by calendar status/calendars`);
@@ -16022,6 +16140,12 @@ export {
16022
16140
  getScopedPlatformToken,
16023
16141
  SYSTEM_AI_PURPOSES,
16024
16142
  adminAi,
16143
+ COMMAND_SURFACE,
16144
+ acceptedAfter,
16145
+ validateInvocation,
16146
+ describeProblem,
16147
+ invocationPath,
16148
+ surfacePaths,
16025
16149
  calendarServiceConfig,
16026
16150
  calendarBookingPageUrl,
16027
16151
  GOOGLE_CALENDAR_EVENTS_SCOPE,
@@ -16059,12 +16183,6 @@ export {
16059
16183
  monitoringWireConfig,
16060
16184
  monitorCommand,
16061
16185
  provision,
16062
- COMMAND_SURFACE,
16063
- acceptedAfter,
16064
- validateInvocation,
16065
- describeProblem,
16066
- invocationPath,
16067
- surfacePaths,
16068
16186
  runHostedSecurity,
16069
16187
  getHostedSecurityIntent,
16070
16188
  getHostedSecurityPlan,
@@ -16076,4 +16194,4 @@ export {
16076
16194
  isTerminalHostedSecurityStatus,
16077
16195
  runCli
16078
16196
  };
16079
- //# sourceMappingURL=chunk-WDWJ7HC7.js.map
16197
+ //# sourceMappingURL=chunk-7MUWCSGP.js.map