@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.
package/dist/bin.cjs CHANGED
@@ -556,10 +556,10 @@ function isManagedDevVar(line2) {
556
556
  const match = line2.match(/^\s*(?:export\s+)?([A-Z][A-Z0-9_]*)\s*=/);
557
557
  return !!match?.[1] && MANAGED_DEV_VARS.has(match[1]);
558
558
  }
559
- function writePrivateText(path, text3) {
559
+ function writePrivateText(path, text4) {
560
560
  (0, import_node_fs7.mkdirSync)((0, import_node_path5.dirname)(path), { recursive: true });
561
561
  const temporary = `${path}.tmp-${process.pid}-${Date.now()}`;
562
- (0, import_node_fs7.writeFileSync)(temporary, text3, { mode: 384 });
562
+ (0, import_node_fs7.writeFileSync)(temporary, text4, { mode: 384 });
563
563
  (0, import_node_fs7.chmodSync)(temporary, 384);
564
564
  (0, import_node_fs7.renameSync)(temporary, path);
565
565
  }
@@ -1105,12 +1105,12 @@ async function readAdminAiAudit(request3) {
1105
1105
  }
1106
1106
  }
1107
1107
  async function responseBody(response2) {
1108
- const text3 = await response2.text();
1109
- if (!text3) return {};
1108
+ const text4 = await response2.text();
1109
+ if (!text4) return {};
1110
1110
  try {
1111
- return JSON.parse(text3);
1111
+ return JSON.parse(text4);
1112
1112
  } catch {
1113
- return { message: text3.slice(0, 300) };
1113
+ return { message: text4.slice(0, 300) };
1114
1114
  }
1115
1115
  }
1116
1116
  function apiError(status, body) {
@@ -1226,12 +1226,12 @@ function timestamp2(value2) {
1226
1226
  return Number.isFinite(date.valueOf()) ? date.toISOString() : "";
1227
1227
  }
1228
1228
  async function responseBody2(res) {
1229
- const text3 = await res.text();
1230
- if (!text3) return {};
1229
+ const text4 = await res.text();
1230
+ if (!text4) return {};
1231
1231
  try {
1232
- return JSON.parse(text3);
1232
+ return JSON.parse(text4);
1233
1233
  } catch {
1234
- return { message: text3.slice(0, 300) };
1234
+ return { message: text4.slice(0, 300) };
1235
1235
  }
1236
1236
  }
1237
1237
  function apiError2(action2, status, body) {
@@ -1414,12 +1414,12 @@ function catalogModels(body) {
1414
1414
  return body.catalog.models.filter((value2) => isRecord3(value2) && typeof value2.id === "string" && typeof value2.provider === "string");
1415
1415
  }
1416
1416
  async function responseBody3(res) {
1417
- const text3 = await res.text();
1418
- if (!text3) return {};
1417
+ const text4 = await res.text();
1418
+ if (!text4) return {};
1419
1419
  try {
1420
- return JSON.parse(text3);
1420
+ return JSON.parse(text4);
1421
1421
  } catch {
1422
- return { message: text3.slice(0, 300) };
1422
+ return { message: text4.slice(0, 300) };
1423
1423
  }
1424
1424
  }
1425
1425
  function apiError3(action2, status, body) {
@@ -1480,10 +1480,17 @@ function parseArgv(argv2) {
1480
1480
  function assertArgs(parsed, allowedOptions2, maxPositionals) {
1481
1481
  const allowed = new Set(allowedOptions2);
1482
1482
  for (const name of Object.keys(parsed.options)) {
1483
- if (!allowed.has(name)) throw new Error(`unknown option "--${name}"; run "odla-ai help" for supported options`);
1483
+ if (allowed.has(name)) continue;
1484
+ const accepts = [...allowed].sort().map((option) => `--${option}`).join(" ");
1485
+ throw new Error(
1486
+ `unknown option "--${name}"` + (accepts ? ` \u2014 this command accepts: ${accepts}` : " \u2014 this command takes no options")
1487
+ );
1484
1488
  }
1485
1489
  if (parsed.positionals.length > maxPositionals) {
1486
- throw new Error(`unexpected argument "${parsed.positionals[maxPositionals]}"; run "odla-ai help"`);
1490
+ const taken = parsed.positionals.slice(0, maxPositionals).join(" ");
1491
+ throw new Error(
1492
+ `unexpected argument "${parsed.positionals[maxPositionals]}" \u2014 ` + (maxPositionals ? `"odla-ai ${taken}" takes no further arguments` : "this command takes no arguments")
1493
+ );
1487
1494
  }
1488
1495
  }
1489
1496
  function requiredString(value2, name) {
@@ -1527,6 +1534,196 @@ var init_argv = __esm({
1527
1534
  }
1528
1535
  });
1529
1536
 
1537
+ // src/surface.ts
1538
+ function acceptedAfter(path) {
1539
+ let node = COMMAND_SURFACE;
1540
+ for (const word of path) {
1541
+ node = node?.[word];
1542
+ if (!node) return [];
1543
+ }
1544
+ return Object.keys(node).sort();
1545
+ }
1546
+ function validateInvocation(words2) {
1547
+ let node = COMMAND_SURFACE;
1548
+ const walked = [];
1549
+ for (const word of words2) {
1550
+ if (Object.keys(node).length === 0) return null;
1551
+ const next = node[word];
1552
+ if (!next) return { validPrefix: walked.join(" "), word, accepted: Object.keys(node).sort() };
1553
+ walked.push(word);
1554
+ node = next;
1555
+ }
1556
+ return null;
1557
+ }
1558
+ function describeProblem(problem) {
1559
+ const where = problem.validPrefix ? `after "${problem.validPrefix}"` : "as a command";
1560
+ if (!problem.accepted.length) return `"${problem.word}" is not accepted ${where}. Run "odla-ai help"`;
1561
+ if (!problem.word) return `"odla-ai ${problem.validPrefix}" needs one of: ${problem.accepted.join(", ")}`;
1562
+ return `"${problem.word}" is not accepted ${where} \u2014 try: ${problem.accepted.join(", ")}`;
1563
+ }
1564
+ function rejectWord(path, word, note) {
1565
+ const sentence = describeProblem({
1566
+ validPrefix: path.join(" "),
1567
+ word: word ?? "",
1568
+ accepted: acceptedAfter(path)
1569
+ });
1570
+ throw new Error(note ? `${sentence}. ${note}` : sentence);
1571
+ }
1572
+ function invocationPath(words2) {
1573
+ let node = COMMAND_SURFACE;
1574
+ const path = [];
1575
+ for (const word of words2) {
1576
+ const next = node[word];
1577
+ if (!next) break;
1578
+ path.push(word);
1579
+ node = next;
1580
+ if (Object.keys(node).length === 0) break;
1581
+ }
1582
+ return path;
1583
+ }
1584
+ var PM_ACTIONS, PM_TASK_ACTIONS, PM_ENTITIES, COMMAND_SURFACE;
1585
+ var init_surface = __esm({
1586
+ "src/surface.ts"() {
1587
+ "use strict";
1588
+ init_cjs_shims();
1589
+ PM_ACTIONS = {
1590
+ list: {},
1591
+ add: {},
1592
+ create: {},
1593
+ get: {},
1594
+ set: {},
1595
+ update: {},
1596
+ status: {},
1597
+ move: {},
1598
+ done: {},
1599
+ comment: {},
1600
+ comments: {},
1601
+ history: {},
1602
+ link: {},
1603
+ ref: {},
1604
+ rm: {},
1605
+ delete: {}
1606
+ };
1607
+ PM_TASK_ACTIONS = {
1608
+ ...PM_ACTIONS,
1609
+ ready: {},
1610
+ claim: {},
1611
+ release: {}
1612
+ };
1613
+ PM_ENTITIES = {
1614
+ ...Object.fromEntries(
1615
+ ["goal", "conformance", "decision", "bug"].map((entity) => [entity, PM_ACTIONS])
1616
+ ),
1617
+ task: PM_TASK_ACTIONS,
1618
+ kanban: PM_TASK_ACTIONS
1619
+ };
1620
+ COMMAND_SURFACE = {
1621
+ agent: { jobs: {}, retry: {} },
1622
+ ai: { models: {} },
1623
+ admin: {
1624
+ ai: {
1625
+ show: {},
1626
+ set: {},
1627
+ credentials: {},
1628
+ models: {},
1629
+ usage: {},
1630
+ audit: {},
1631
+ credential: { set: {} }
1632
+ },
1633
+ spend: { show: {}, reset: {} }
1634
+ },
1635
+ app: {
1636
+ archive: {},
1637
+ restore: {},
1638
+ export: {},
1639
+ import: {},
1640
+ rename: {},
1641
+ "refresh-sandbox": {},
1642
+ "go-live": {},
1643
+ promote: {},
1644
+ owners: { list: {}, add: {}, remove: {} }
1645
+ },
1646
+ auth: { login: {} },
1647
+ brand: { design: { unpack: {} } },
1648
+ bug: { create: {}, list: {}, report: {} },
1649
+ calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
1650
+ capabilities: {},
1651
+ code: {
1652
+ connect: {},
1653
+ grant: { request: {}, list: {}, approve: {}, revoke: {} },
1654
+ repository: { show: {}, list: {}, bind: {} }
1655
+ },
1656
+ config: { diff: {}, plan: {}, apply: {} },
1657
+ context: { show: {}, list: {}, save: {}, remove: {} },
1658
+ credentials: { list: {}, revoke: {} },
1659
+ device: { enroll: {}, list: {}, revoke: {} },
1660
+ // `watch`, `read`, `reply`, and `resolve` take a topic id from there on.
1661
+ discuss: {
1662
+ groups: {},
1663
+ list: {},
1664
+ topics: {},
1665
+ read: {},
1666
+ post: {},
1667
+ reply: {},
1668
+ resolve: {},
1669
+ who: {},
1670
+ watch: {}
1671
+ },
1672
+ doctor: {},
1673
+ help: {},
1674
+ init: {},
1675
+ monitor: { plan: {}, apply: {}, run: {}, status: {}, incidents: {}, report: {} },
1676
+ o11y: { status: {} },
1677
+ operations: { get: {}, wait: {} },
1678
+ platform: {
1679
+ status: {}
1680
+ },
1681
+ pm: {
1682
+ ...PM_ENTITIES,
1683
+ project: { list: {}, add: {}, create: {}, use: {} },
1684
+ handoff: {},
1685
+ next: {},
1686
+ start: {},
1687
+ watch: {}
1688
+ },
1689
+ provision: {},
1690
+ runbook: {
1691
+ ask: {},
1692
+ search: {},
1693
+ impact: {},
1694
+ list: {},
1695
+ get: {},
1696
+ cat: {},
1697
+ new: {},
1698
+ edit: {},
1699
+ comment: {},
1700
+ import: {},
1701
+ visibility: {},
1702
+ publish: {},
1703
+ archive: {},
1704
+ history: {},
1705
+ revert: {},
1706
+ rm: {},
1707
+ lint: {}
1708
+ },
1709
+ secrets: { push: {}, status: {}, set: {}, "set-clerk-key": {} },
1710
+ security: {
1711
+ plan: {},
1712
+ sources: {},
1713
+ run: {},
1714
+ status: {},
1715
+ report: {},
1716
+ github: { connect: {}, disconnect: {} }
1717
+ },
1718
+ setup: {},
1719
+ skill: { install: {} },
1720
+ smoke: {},
1721
+ version: {},
1722
+ whoami: {}
1723
+ };
1724
+ }
1725
+ });
1726
+
1530
1727
  // src/admin-spend.ts
1531
1728
  async function call(ctx, method, scope) {
1532
1729
  const url = `${ctx.platformUrl.replace(/\/$/, "")}/registry/platform/spend?scope=${encodeURIComponent(scope)}`;
@@ -1575,9 +1772,7 @@ async function spendReset(ctx, scope) {
1575
1772
  async function adminSpend(parsed, ctx) {
1576
1773
  const action2 = parsed.positionals[2];
1577
1774
  const scope = parsed.positionals[3] ?? stringOpt(parsed.options.scope);
1578
- if (action2 !== "show" && action2 !== "reset") {
1579
- throw new Error('unknown spend command. Try "odla-ai admin spend show <scope>".');
1580
- }
1775
+ if (action2 !== "show" && action2 !== "reset") rejectWord(["admin", "spend"], action2);
1581
1776
  if (!scope) {
1582
1777
  throw new Error(
1583
1778
  `"admin spend ${action2}" needs a scope, e.g. odla-ai admin spend ${action2} app:my-app:<incarnation>`
@@ -1591,6 +1786,7 @@ var init_admin_spend = __esm({
1591
1786
  "use strict";
1592
1787
  init_cjs_shims();
1593
1788
  init_argv();
1789
+ init_surface();
1594
1790
  money = (value2) => `$${value2.toFixed(2)}`;
1595
1791
  }
1596
1792
  });
@@ -2426,6 +2622,11 @@ var init_operator_context = __esm({
2426
2622
  async function adminCommand(parsed, deps = {}) {
2427
2623
  const area = parsed.positionals[1];
2428
2624
  const action2 = parsed.positionals[2];
2625
+ if (area !== "ai" && area !== "spend") rejectWord(["admin"], area);
2626
+ if (!acceptedAfter(["admin", area]).includes(action2 ?? "")) rejectWord(["admin", area], action2);
2627
+ if (action2 === "credential" && !acceptedAfter(["admin", "ai", "credential"]).includes(parsed.positionals[3] ?? "")) {
2628
+ rejectWord(["admin", "ai", "credential"], parsed.positionals[3]);
2629
+ }
2429
2630
  if (area === "spend") {
2430
2631
  assertArgs(parsed, JSON_OPTIONS, 4);
2431
2632
  const context2 = await resolveOperatorContext(parsed, { allowMissingConfig: true });
@@ -2451,14 +2652,11 @@ async function adminCommand(parsed, deps = {}) {
2451
2652
  out
2452
2653
  });
2453
2654
  }
2454
- const credentialSet = action2 === "credential" && parsed.positionals[3] === "set";
2655
+ const credentialSet = action2 === "credential";
2455
2656
  const credentials = action2 === "credentials";
2456
2657
  const models = action2 === "models";
2457
2658
  const usage = action2 === "usage";
2458
2659
  const audit = action2 === "audit";
2459
- if (area !== "ai" || action2 !== "show" && action2 !== "set" && !credentialSet && !credentials && !models && !usage && !audit) {
2460
- throw new Error('unknown admin command. Try "odla-ai admin ai show".');
2461
- }
2462
2660
  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;
2463
2661
  assertArgs(parsed, allowed, credentialSet ? 5 : action2 === "set" ? 4 : 3);
2464
2662
  const context = await resolveOperatorContext(parsed, { allowMissingConfig: true });
@@ -2504,6 +2702,7 @@ var init_admin_command = __esm({
2504
2702
  init_token();
2505
2703
  init_argv();
2506
2704
  init_operator_context();
2705
+ init_surface();
2507
2706
  CONTEXT_OPTIONS = ["platform", "config", "context", "token", "open", "email"];
2508
2707
  JSON_OPTIONS = [...CONTEXT_OPTIONS, "json"];
2509
2708
  SET_OPTIONS = [
@@ -2708,9 +2907,7 @@ async function authCommand(parsed, deps = {}) {
2708
2907
  "json"
2709
2908
  ], 2);
2710
2909
  const action2 = parsed.positionals[1] ?? "login";
2711
- if (action2 !== "login") {
2712
- throw new Error(`unknown auth action "${action2}". Try "odla-ai auth login --app <id> --email <odla-account>".`);
2713
- }
2910
+ if (action2 !== "login") rejectWord(["auth"], action2);
2714
2911
  const context = await resolveOperatorContext(parsed, {
2715
2912
  allowMissingConfig: true,
2716
2913
  requireApp: true
@@ -2768,6 +2965,7 @@ var init_auth_command = __esm({
2768
2965
  init_token();
2769
2966
  init_auth_guidance();
2770
2967
  init_whoami_command();
2968
+ init_surface();
2771
2969
  }
2772
2970
  });
2773
2971
 
@@ -2803,9 +3001,7 @@ var init_tenant = __esm({
2803
3001
  // src/agent-command.ts
2804
3002
  async function agentCommand(parsed, deps = {}) {
2805
3003
  const action2 = parsed.positionals[1];
2806
- if (action2 !== "jobs" && action2 !== "retry") {
2807
- throw new Error(`unknown agent action "${action2 ?? ""}". Try "odla-ai agent jobs --json".`);
2808
- }
3004
+ if (action2 !== "jobs" && action2 !== "retry") rejectWord(["agent"], action2);
2809
3005
  assertArgs(parsed, ["config", "env", "state", "limit", "json", "token"], action2 === "jobs" ? 2 : 3);
2810
3006
  if (action2 === "retry" && (parsed.options.state !== void 0 || parsed.options.limit !== void 0)) {
2811
3007
  throw new Error('--state and --limit are supported only by "agent jobs"');
@@ -2887,6 +3083,7 @@ var init_agent_command = __esm({
2887
3083
  init_config();
2888
3084
  init_tenant();
2889
3085
  init_local();
3086
+ init_surface();
2890
3087
  }
2891
3088
  });
2892
3089
 
@@ -2936,8 +3133,8 @@ async function appImport(options) {
2936
3133
  const out = options.stdout ?? console;
2937
3134
  const say = options.json ? (line2) => out.error(line2) : (line2) => out.log(line2);
2938
3135
  const { tenant } = resolveTenant(cfg, options.env);
2939
- const text3 = options.file === "-" ? (options.readStdin ?? (() => (0, import_node_fs12.readFileSync)(0, "utf8")))() : (0, import_node_fs12.readFileSync)(options.file, "utf8");
2940
- const { format, sources } = (0, import_import.parseImport)(text3, options.ns);
3136
+ const text4 = options.file === "-" ? (options.readStdin ?? (() => (0, import_node_fs12.readFileSync)(0, "utf8")))() : (0, import_node_fs12.readFileSync)(options.file, "utf8");
3137
+ const { format, sources } = (0, import_import.parseImport)(text4, options.ns);
2941
3138
  if (format === "namespace-map" && options.ns) {
2942
3139
  throw new Error("--ns cannot be combined with a {namespace: rows} file \u2014 the file already names each namespace");
2943
3140
  }
@@ -3010,9 +3207,7 @@ async function appOwnersCommand(parsed, dependencies = {}) {
3010
3207
  await (sub === "add" ? ownersAdd(email, options) : ownersRemove(email, options));
3011
3208
  return;
3012
3209
  }
3013
- throw new Error(
3014
- `unknown app owners subcommand "${sub}". Try "odla-ai app owners list", "odla-ai app owners add <email>", or "odla-ai app owners remove <email>".`
3015
- );
3210
+ rejectWord(["app", "owners"], sub);
3016
3211
  }
3017
3212
  var init_app_owners = __esm({
3018
3213
  "src/app-owners.ts"() {
@@ -3020,6 +3215,7 @@ var init_app_owners = __esm({
3020
3215
  init_cjs_shims();
3021
3216
  init_argv();
3022
3217
  init_human_session();
3218
+ init_surface();
3023
3219
  }
3024
3220
  });
3025
3221
 
@@ -3143,8 +3339,10 @@ async function appCommand(parsed, dependencies = {}) {
3143
3339
  return;
3144
3340
  }
3145
3341
  if (sub !== "archive" && sub !== "restore") {
3146
- throw new Error(
3147
- `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.)`
3342
+ rejectWord(
3343
+ ["app"],
3344
+ sub,
3345
+ "Permanent deletion has no CLI: it requires a signed-in owner in Studio."
3148
3346
  );
3149
3347
  }
3150
3348
  assertArgs(parsed, ["config", "token", "email", "yes", "json"], 2);
@@ -3171,6 +3369,7 @@ var init_app_lifecycle = __esm({
3171
3369
  init_app_transfer();
3172
3370
  init_argv();
3173
3371
  init_human_session();
3372
+ init_surface();
3174
3373
  }
3175
3374
  });
3176
3375
 
@@ -3274,7 +3473,7 @@ var init_brand_design_unpack = __esm({
3274
3473
  "text/html": "html",
3275
3474
  "application/json": "json"
3276
3475
  };
3277
- encode = (text3) => new TextEncoder().encode(text3);
3476
+ encode = (text4) => new TextEncoder().encode(text4);
3278
3477
  }
3279
3478
  });
3280
3479
 
@@ -3320,7 +3519,8 @@ async function brandCommand(parsed, deps) {
3320
3519
  await designUnpack(parsed, deps);
3321
3520
  return;
3322
3521
  }
3323
- throw new Error(USAGE);
3522
+ if (subject !== "design") rejectWord(["brand"], subject);
3523
+ rejectWord(["brand", "design"], action2, USAGE);
3324
3524
  }
3325
3525
  var import_promises, import_node_path10, USAGE;
3326
3526
  var init_brand_command = __esm({
@@ -3331,6 +3531,7 @@ var init_brand_command = __esm({
3331
3531
  import_node_path10 = require("path");
3332
3532
  init_argv();
3333
3533
  init_brand_design_unpack();
3534
+ init_surface();
3334
3535
  USAGE = "usage: odla-ai brand design unpack <bundle.html|-> [--out <dir>] [--json]";
3335
3536
  }
3336
3537
  });
@@ -4130,9 +4331,9 @@ async function assertTenantAdminAccess(doFetch, cfg, env, token) {
4130
4331
  }
4131
4332
  throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await safeText5(res)}`);
4132
4333
  }
4133
- function errorCode(text3) {
4334
+ function errorCode(text4) {
4134
4335
  try {
4135
- const body = JSON.parse(text3);
4336
+ const body = JSON.parse(text4);
4136
4337
  return typeof body.error?.code === "string" ? body.error.code : null;
4137
4338
  } catch {
4138
4339
  return null;
@@ -4918,15 +5119,15 @@ function readWranglerConfig(path) {
4918
5119
  return null;
4919
5120
  }
4920
5121
  }
4921
- function stripJsonComments(text3) {
5122
+ function stripJsonComments(text4) {
4922
5123
  let result = "";
4923
5124
  let inString = false;
4924
- for (let i = 0; i < text3.length; i++) {
4925
- const ch = text3[i];
5125
+ for (let i = 0; i < text4.length; i++) {
5126
+ const ch = text4[i];
4926
5127
  if (inString) {
4927
5128
  result += ch;
4928
5129
  if (ch === "\\") {
4929
- result += text3[i + 1] ?? "";
5130
+ result += text4[i + 1] ?? "";
4930
5131
  i++;
4931
5132
  } else if (ch === '"') {
4932
5133
  inString = false;
@@ -4938,14 +5139,14 @@ function stripJsonComments(text3) {
4938
5139
  result += ch;
4939
5140
  continue;
4940
5141
  }
4941
- if (ch === "/" && text3[i + 1] === "/") {
4942
- while (i < text3.length && text3[i] !== "\n") i++;
5142
+ if (ch === "/" && text4[i + 1] === "/") {
5143
+ while (i < text4.length && text4[i] !== "\n") i++;
4943
5144
  result += "\n";
4944
5145
  continue;
4945
5146
  }
4946
- if (ch === "/" && text3[i + 1] === "*") {
5147
+ if (ch === "/" && text4[i + 1] === "*") {
4947
5148
  i += 2;
4948
- while (i < text3.length && !(text3[i] === "*" && text3[i + 1] === "/")) i++;
5149
+ while (i < text4.length && !(text4[i] === "*" && text4[i + 1] === "/")) i++;
4949
5150
  i++;
4950
5151
  continue;
4951
5152
  }
@@ -5529,9 +5730,9 @@ function initProject(options) {
5529
5730
  out.log("created src/odla/schema.mjs and src/odla/rules.mjs");
5530
5731
  out.log("updated .gitignore for local odla credentials");
5531
5732
  }
5532
- function writeIfMissing(path, text3) {
5733
+ function writeIfMissing(path, text4) {
5533
5734
  if ((0, import_node_fs16.existsSync)(path)) return;
5534
- (0, import_node_fs16.writeFileSync)(path, text3);
5735
+ (0, import_node_fs16.writeFileSync)(path, text4);
5535
5736
  }
5536
5737
  function configTemplate(input) {
5537
5738
  const calendar = input.services.includes("calendar") ? ` calendar: {
@@ -5763,13 +5964,13 @@ async function secretsSetClerkKey(options) {
5763
5964
  body: JSON.stringify({ value: value2 })
5764
5965
  });
5765
5966
  if (!res.ok) {
5766
- const text3 = scrubValue((await res.text().catch(() => "")).slice(0, 300), value2);
5767
- throw new Error(`store Clerk secret key failed (${res.status}): ${text3 || "request failed"}`);
5967
+ const text4 = scrubValue((await res.text().catch(() => "")).slice(0, 300), value2);
5968
+ throw new Error(`store Clerk secret key failed (${res.status}): ${text4 || "request failed"}`);
5768
5969
  }
5769
5970
  out.log(`Clerk secret key stored for ${tenantId} ($clerk_secret, reserved + write-only; the value was never echoed)`);
5770
5971
  }
5771
- function scrubValue(text3, value2) {
5772
- return redactSecrets(text3).split(value2).join("[value redacted]");
5972
+ function scrubValue(text4, value2) {
5973
+ return redactSecrets(text4).split(value2).join("[value redacted]");
5773
5974
  }
5774
5975
  async function resolveVaultWrite(options) {
5775
5976
  const out = options.stdout ?? console;
@@ -6086,8 +6287,8 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
6086
6287
  harnesses: installations
6087
6288
  };
6088
6289
  }
6089
- function pathsUnder(root, paths) {
6090
- return [...paths].map((path) => (0, import_node_path16.relative)(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${import_node_path16.sep}`) && !(0, import_node_path16.isAbsolute)(path)).sort();
6290
+ function pathsUnder(root, paths2) {
6291
+ return [...paths2].map((path) => (0, import_node_path16.relative)(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${import_node_path16.sep}`) && !(0, import_node_path16.isAbsolute)(path)).sort();
6091
6292
  }
6092
6293
  function normalizeHarnesses(values, global) {
6093
6294
  const requested = values?.length ? values : ["claude"];
@@ -6392,9 +6593,7 @@ async function secretsCommand(parsed, deps) {
6392
6593
  return;
6393
6594
  }
6394
6595
  if (sub !== "push") {
6395
- throw new Error(
6396
- `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".`
6397
- );
6596
+ rejectWord(["secrets"], sub);
6398
6597
  }
6399
6598
  assertArgs(parsed, ["config", "env", "dry-run", "yes"], 2);
6400
6599
  await secretsPush({
@@ -6407,7 +6606,7 @@ async function secretsCommand(parsed, deps) {
6407
6606
  async function projectCommand(command, parsed, deps) {
6408
6607
  if (command === "ai") {
6409
6608
  const sub = parsed.positionals[1];
6410
- if (sub !== "models") throw new Error(`unknown ai subcommand "${sub ?? ""}". Try "odla-ai ai models --env dev".`);
6609
+ if (sub !== "models") rejectWord(["ai"], sub);
6411
6610
  assertArgs(parsed, ["config", "env", "provider", "json"], 2);
6412
6611
  await aiModels({
6413
6612
  configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
@@ -6421,9 +6620,7 @@ async function projectCommand(command, parsed, deps) {
6421
6620
  }
6422
6621
  if (command === "config") {
6423
6622
  const sub = parsed.positionals[1];
6424
- if (sub !== "diff" && sub !== "plan" && sub !== "apply") {
6425
- throw new Error(`unknown config subcommand "${sub ?? ""}". Try "odla-ai config diff --json".`);
6426
- }
6623
+ if (sub !== "diff" && sub !== "plan" && sub !== "apply") rejectWord(["config"], sub);
6427
6624
  assertArgs(
6428
6625
  parsed,
6429
6626
  sub === "apply" ? ["config", "plan", "idempotency-key", "token", "email", "open", "json"] : ["config", "token", "email", "open", "json"],
@@ -6451,7 +6648,7 @@ async function projectCommand(command, parsed, deps) {
6451
6648
  if (command === "operations") {
6452
6649
  const sub = parsed.positionals[1];
6453
6650
  if (sub !== "get" && sub !== "wait") {
6454
- throw new Error(`unknown operations subcommand "${sub ?? ""}". Try "odla-ai operations get <operation-id> --json".`);
6651
+ rejectWord(["operations"], sub);
6455
6652
  }
6456
6653
  assertArgs(
6457
6654
  parsed,
@@ -6525,7 +6722,7 @@ async function projectCommand(command, parsed, deps) {
6525
6722
  }
6526
6723
  if (command === "skill") {
6527
6724
  const sub = parsed.positionals[1];
6528
- if (sub !== "install") throw new Error(`unknown skill subcommand "${sub ?? ""}". Try "odla-ai skill install".`);
6725
+ if (sub !== "install") rejectWord(["skill"], sub);
6529
6726
  install(parsed, 2, deps);
6530
6727
  return true;
6531
6728
  }
@@ -6553,21 +6750,22 @@ var init_cli_project = __esm({
6553
6750
  init_secrets_status();
6554
6751
  init_skill();
6555
6752
  init_smoke();
6753
+ init_surface();
6556
6754
  SKILL_OPTS = ["dir", "global", "force", "agent", "harness"];
6557
6755
  }
6558
6756
  });
6559
6757
 
6560
- // ../harness/dist/chunk-LNQNFGQC.js
6758
+ // ../harness/dist/chunk-RXNHCGWE.js
6561
6759
  var HARNESS_PROTOCOL_VERSION;
6562
- var init_chunk_LNQNFGQC = __esm({
6563
- "../harness/dist/chunk-LNQNFGQC.js"() {
6760
+ var init_chunk_RXNHCGWE = __esm({
6761
+ "../harness/dist/chunk-RXNHCGWE.js"() {
6564
6762
  "use strict";
6565
6763
  init_cjs_shims();
6566
6764
  HARNESS_PROTOCOL_VERSION = 1;
6567
6765
  }
6568
6766
  });
6569
6767
 
6570
- // ../harness/dist/chunk-K76I2TCQ.js
6768
+ // ../harness/dist/chunk-CR6RE3A2.js
6571
6769
  function assertPinnedImage(image) {
6572
6770
  if (!DIGEST_IMAGE.test(image)) throw new TypeError("container image must be pinned by sha256 digest");
6573
6771
  }
@@ -6795,12 +6993,12 @@ async function gitSourceFiles(sourceDir, maxFiles, maxBytes) {
6795
6993
  });
6796
6994
  if (outputBytes > 8 * 1024 * 1024) throw new Error("git file inventory exceeds 8 MiB");
6797
6995
  if (code !== 0) throw new Error(`git file inventory failed: ${Buffer.concat(stderr2).toString("utf8").slice(0, 1e3)}`);
6798
- const paths = Buffer.concat(stdout).toString("utf8").split("\0").filter(Boolean).sort();
6799
- if (paths.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
6996
+ const paths2 = Buffer.concat(stdout).toString("utf8").split("\0").filter(Boolean).sort();
6997
+ if (paths2.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
6800
6998
  const root = (0, import_path4.resolve)(sourceDir);
6801
6999
  const files = [];
6802
7000
  let bytes = 0;
6803
- for (const relativePath of paths) {
7001
+ for (const relativePath of paths2) {
6804
7002
  if (!allowedWorkspacePath(relativePath)) continue;
6805
7003
  const source = (0, import_path4.resolve)(root, relativePath);
6806
7004
  if (!source.startsWith(`${root}${import_path4.sep}`)) throw new TypeError("git file path escapes workspace");
@@ -6916,8 +7114,8 @@ async function stageWorkspacePair(baselineSource, workspaceSource, options = {})
6916
7114
  }
6917
7115
  }
6918
7116
  var import_child_process, import_fs, import_promises2, import_path, import_process, import_promises3, import_os, import_path2, import_child_process2, import_path3, import_promises4, import_os2, import_path4, import_child_process3, DIGEST_IMAGE, SKIP_WORKSPACE_DIRS, SECRET_WORKSPACE_FILE;
6919
- var init_chunk_K76I2TCQ = __esm({
6920
- "../harness/dist/chunk-K76I2TCQ.js"() {
7117
+ var init_chunk_CR6RE3A2 = __esm({
7118
+ "../harness/dist/chunk-CR6RE3A2.js"() {
6921
7119
  "use strict";
6922
7120
  init_cjs_shims();
6923
7121
  import_child_process = require("child_process");
@@ -7276,13 +7474,13 @@ function validateSnapshot(snapshot, limits) {
7276
7474
  if (!Number.isSafeInteger(limits.maximumFiles) || limits.maximumFiles < 1 || !Number.isSafeInteger(limits.maximumBytes) || limits.maximumBytes < 1 || snapshot.files.length > limits.maximumFiles) {
7277
7475
  throw new CamelError("limit_exceeded", "Code snapshot exceeds its registered limits.");
7278
7476
  }
7279
- const paths = /* @__PURE__ */ new Set();
7477
+ const paths2 = /* @__PURE__ */ new Set();
7280
7478
  let bytes = 0;
7281
7479
  for (const file of snapshot.files) {
7282
- 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") {
7480
+ 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") {
7283
7481
  throw new CamelError("state_conflict", "Code snapshot contains an invalid or duplicate path.");
7284
7482
  }
7285
- paths.add(file.path);
7483
+ paths2.add(file.path);
7286
7484
  bytes += utf8Length(file.path) + utf8Length(file.content);
7287
7485
  }
7288
7486
  if (bytes > limits.maximumBytes) {
@@ -7433,8 +7631,8 @@ function boundedInteger(value2, spec) {
7433
7631
  }
7434
7632
  function boundedNumber(value2, spec) {
7435
7633
  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.");
7436
- const text3 = String(value2);
7437
- 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.");
7634
+ const text4 = String(value2);
7635
+ 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.");
7438
7636
  return value2;
7439
7637
  }
7440
7638
  function enumMember(value2, spec) {
@@ -7617,8 +7815,8 @@ function validateUnsafeSelector(path, value2, tool) {
7617
7815
  return void 0;
7618
7816
  }
7619
7817
  function looksLikeDestination(value2) {
7620
- const text3 = value2.trim();
7621
- return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text3) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text3);
7818
+ const text4 = value2.trim();
7819
+ return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text4) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text4);
7622
7820
  }
7623
7821
  var init_policy = __esm({
7624
7822
  "../camel/dist/policy.js"() {
@@ -7792,9 +7990,9 @@ async function extractImports(builder, input) {
7792
7990
  const sources = input.paths.filter(isSourcePath);
7793
7991
  const known = new Set(sources);
7794
7992
  for (const path of sources) {
7795
- let text3;
7993
+ let text4;
7796
7994
  try {
7797
- text3 = await input.read(path);
7995
+ text4 = await input.read(path);
7798
7996
  } catch {
7799
7997
  continue;
7800
7998
  }
@@ -7802,13 +8000,13 @@ async function extractImports(builder, input) {
7802
8000
  const file = builder.node(FILE, path, pkg ? { pkg } : void 0);
7803
8001
  if (pkg) builder.edge(builder.node(PACKAGE, pkg), CONTAINS, file);
7804
8002
  const specifiers = /* @__PURE__ */ new Set();
7805
- for (const match of text3.matchAll(IMPORT_FROM)) specifiers.add(match[1]);
7806
- for (const match of text3.matchAll(BARE_IMPORT)) specifiers.add(match[1]);
8003
+ for (const match of text4.matchAll(IMPORT_FROM)) specifiers.add(match[1]);
8004
+ for (const match of text4.matchAll(BARE_IMPORT)) specifiers.add(match[1]);
7807
8005
  for (const specifier of specifiers) {
7808
8006
  const resolved = resolveImport(path, specifier, known);
7809
8007
  if (resolved) builder.edge(file, IMPORTS, nodeId(FILE, resolved));
7810
8008
  }
7811
- for (const name of exportedNames(text3)) {
8009
+ for (const name of exportedNames(text4)) {
7812
8010
  builder.edge(file, EXPORTS, builder.node(SYMBOL, name));
7813
8011
  }
7814
8012
  }
@@ -7821,16 +8019,16 @@ async function extractData(builder, input) {
7821
8019
  };
7822
8020
  for (const path of input.paths) {
7823
8021
  if (!SOURCE_FILE.test(path) || input.ignore?.(path)) continue;
7824
- let text3;
8022
+ let text4;
7825
8023
  try {
7826
- text3 = await input.read(path);
8024
+ text4 = await input.read(path);
7827
8025
  } catch {
7828
8026
  continue;
7829
8027
  }
7830
- for (const statement of text3.matchAll(STATEMENT)) {
8028
+ for (const statement of text4.matchAll(STATEMENT)) {
7831
8029
  const verb = statement[1].toUpperCase().replace(/\s+/g, " ");
7832
8030
  const start = statement.index ?? 0;
7833
- const rest = text3.slice(start + statement[0].length, start + STATEMENT_WINDOW);
8031
+ const rest = text4.slice(start + statement[0].length, start + STATEMENT_WINDOW);
7834
8032
  if (verb === "SELECT") {
7835
8033
  for (const read3 of rest.matchAll(READ_TABLES)) touch(path, read3[1].toLowerCase(), TABLE, READS);
7836
8034
  continue;
@@ -7846,16 +8044,16 @@ async function extractData(builder, input) {
7846
8044
  for (const read3 of rest.matchAll(READ_TABLES)) touch(path, read3[1].toLowerCase(), TABLE, READS);
7847
8045
  }
7848
8046
  }
7849
- for (const match of text3.matchAll(NS_CONST)) {
7850
- touch(path, `${match[1]}.${match[2]}`, NAMESPACE, accessFor(text3, match.index ?? 0));
8047
+ for (const match of text4.matchAll(NS_CONST)) {
8048
+ touch(path, `${match[1]}.${match[2]}`, NAMESPACE, accessFor(text4, match.index ?? 0));
7851
8049
  }
7852
- for (const match of text3.matchAll(NS_LITERAL)) {
7853
- touch(path, match[1], NAMESPACE, accessFor(text3, match.index ?? 0));
8050
+ for (const match of text4.matchAll(NS_LITERAL)) {
8051
+ touch(path, match[1], NAMESPACE, accessFor(text4, match.index ?? 0));
7854
8052
  }
7855
8053
  }
7856
8054
  }
7857
- function accessFor(text3, index) {
7858
- const window = text3.slice(Math.max(0, index - 160), index + 40);
8055
+ function accessFor(text4, index) {
8056
+ const window = text4.slice(Math.max(0, index - 160), index + 40);
7859
8057
  return /\b(?:transact|update|delete|create|insert|Ops)\b/.test(window) ? WRITES : READS;
7860
8058
  }
7861
8059
  async function buildCodeGraph(input) {
@@ -7915,7 +8113,7 @@ var init_code2 = __esm({
7915
8113
  }
7916
8114
  });
7917
8115
 
7918
- // ../harness/dist/chunk-GYWQM76X.js
8116
+ // ../harness/dist/chunk-ISR434K7.js
7919
8117
  async function digestStagedWorkspace(root, limits) {
7920
8118
  const files = [];
7921
8119
  const walk = async (directory) => {
@@ -8195,7 +8393,7 @@ function validateCodePatch(rawPatch, maxBytes) {
8195
8393
  if (FORBIDDEN.test(patch2) || /(?:old|new)(?: file)? mode 120000/.test(patch2)) {
8196
8394
  throw new TypeError("patch uses a forbidden binary, link, mode, rename, or copy operation");
8197
8395
  }
8198
- const paths = [];
8396
+ const paths2 = [];
8199
8397
  const lines = patch2.split("\n");
8200
8398
  for (let index = 0; index < lines.length; index += 1) {
8201
8399
  const line2 = lines[index];
@@ -8211,10 +8409,10 @@ function validateCodePatch(rawPatch, maxBytes) {
8211
8409
  if (!validHeaderPath(oldPath, path, "a") || !validHeaderPath(newPath, path, "b")) {
8212
8410
  throw new TypeError("patch file headers do not match the declared path");
8213
8411
  }
8214
- paths.push(path);
8412
+ paths2.push(path);
8215
8413
  }
8216
- if (!paths.length || new Set(paths).size !== paths.length) throw new TypeError("patch has no diffs or repeats a path");
8217
- return paths;
8414
+ if (!paths2.length || new Set(paths2).size !== paths2.length) throw new TypeError("patch has no diffs or repeats a path");
8415
+ return paths2;
8218
8416
  }
8219
8417
  function validHeaderPath(value2, path, prefix) {
8220
8418
  return value2 === "/dev/null" || value2 === `${prefix}/${path}`;
@@ -8239,11 +8437,11 @@ function describePatchFailure(patch2, detail) {
8239
8437
  const hint = hunks.length > 0 && contextless ? " A hunk has no context lines; include at least one unchanged line above or below each change." : "";
8240
8438
  return `patch did not apply: ${detail}${hint}`;
8241
8439
  }
8242
- async function applyCodePatch(workspaceDir, rawPatch, paths) {
8440
+ async function applyCodePatch(workspaceDir, rawPatch, paths2) {
8243
8441
  const patch2 = stripPatchEnvelope(rawPatch);
8244
8442
  await gitApply(workspaceDir, patch2, true);
8245
8443
  await gitApply(workspaceDir, patch2, false);
8246
- for (const path of paths) {
8444
+ for (const path of paths2) {
8247
8445
  try {
8248
8446
  const info = await (0, import_promises6.lstat)(resolveCodePath(workspaceDir, path));
8249
8447
  if (info.isSymbolicLink() || !info.isFile() && !info.isDirectory()) {
@@ -8265,8 +8463,8 @@ function gitApply(cwd, patch2, check) {
8265
8463
  });
8266
8464
  let stderr2 = "";
8267
8465
  child.stderr.setEncoding("utf8");
8268
- child.stderr.on("data", (text3) => {
8269
- if (stderr2.length < 4e3) stderr2 += text3.slice(0, 4e3);
8466
+ child.stderr.on("data", (text22) => {
8467
+ if (stderr2.length < 4e3) stderr2 += text22.slice(0, 4e3);
8270
8468
  });
8271
8469
  child.once("error", reject);
8272
8470
  child.once("exit", (code) => code === 0 ? accept() : reject(new TypeError(describePatchFailure(patch2, stderr2.trim().slice(0, 500)))));
@@ -8290,8 +8488,8 @@ async function restoreCodeWorkspaceCheckpoint(input) {
8290
8488
  const workspace = await stageWorkspace(input.trustedBaseDir, input.stage);
8291
8489
  try {
8292
8490
  if (checkpoint.patch) {
8293
- const paths = validateCodePatch(checkpoint.patch, 256 * 1024);
8294
- await applyCodePatch(workspace.workspaceDir, checkpoint.patch, paths);
8491
+ const paths2 = validateCodePatch(checkpoint.patch, 256 * 1024);
8492
+ await applyCodePatch(workspace.workspaceDir, checkpoint.patch, paths2);
8295
8493
  }
8296
8494
  return { workspace, checkpoint };
8297
8495
  } catch (error) {
@@ -8432,8 +8630,8 @@ async function verifyCodeCandidate(input) {
8432
8630
  try {
8433
8631
  const baseDigest = await digestStagedWorkspace(staged.workspaceDir, limits);
8434
8632
  if (baseDigest !== input.trustedBaseDigest) throw new TypeError("trusted base does not match its registered digest");
8435
- const paths = validateCodePatch(input.candidatePatch, policy.maximumPatchBytes);
8436
- await applyCodePatch(staged.workspaceDir, input.candidatePatch, paths);
8633
+ const paths2 = validateCodePatch(input.candidatePatch, policy.maximumPatchBytes);
8634
+ await applyCodePatch(staged.workspaceDir, input.candidatePatch, paths2);
8437
8635
  const sourceDigest = await digestStagedWorkspace(staged.workspaceDir, limits);
8438
8636
  const policyDigest = digestPolicy(policy);
8439
8637
  const patchDigest = digestBytes(input.candidatePatch);
@@ -8442,7 +8640,7 @@ async function verifyCodeCandidate(input) {
8442
8640
  trustedBaseDigest: input.trustedBaseDigest,
8443
8641
  patchDigest
8444
8642
  });
8445
- const changedTests = changedTestPaths(paths, policy);
8643
+ const changedTests = changedTestPaths(paths2, policy);
8446
8644
  if (changedTests.length > policy.maximumChangedTests) throw new TypeError("candidate changes too many test files");
8447
8645
  const recipes = [];
8448
8646
  const logs = [];
@@ -8509,8 +8707,8 @@ function validate2(input) {
8509
8707
  }
8510
8708
  return result;
8511
8709
  }
8512
- function changedTestPaths(paths, policy) {
8513
- return paths.filter((path) => policy.testPathSuffixes.some((suffix) => path.endsWith(suffix)) || policy.testPathPrefixes.some((prefix) => path.startsWith(prefix) || path.includes(`/${prefix}`))).sort();
8710
+ function changedTestPaths(paths2, policy) {
8711
+ return paths2.filter((path) => policy.testPathSuffixes.some((suffix) => path.endsWith(suffix)) || policy.testPathPrefixes.some((prefix) => path.startsWith(prefix) || path.includes(`/${prefix}`))).sort();
8514
8712
  }
8515
8713
  function recipeReceipt(recipe2, result, artifacts) {
8516
8714
  const status = result.timedOut ? "timed_out" : result.outputLimitExceeded ? "output_limited" : result.exitCode === 0 && artifacts.every((item) => item.status === "verified") ? "passed" : "failed";
@@ -9143,7 +9341,7 @@ function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = regi
9143
9341
  files(root) {
9144
9342
  const existing = cache2.get(root);
9145
9343
  if (existing) return existing;
9146
- const pending = enumerate(root, limit).then((paths) => Object.freeze(paths));
9344
+ const pending = enumerate(root, limit).then((paths2) => Object.freeze(paths2));
9147
9345
  cache2.set(root, pending);
9148
9346
  void pending.catch(() => {
9149
9347
  if (cache2.get(root) === pending) cache2.delete(root);
@@ -9156,7 +9354,7 @@ function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = regi
9156
9354
  };
9157
9355
  }
9158
9356
  async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
9159
- const paths = [];
9357
+ const paths2 = [];
9160
9358
  const walk = async (directory) => {
9161
9359
  for (const entry of await (0, import_promises9.readdir)(directory, { withFileTypes: true })) {
9162
9360
  if (SKIP_WORKSPACE_DIRS.has(entry.name)) continue;
@@ -9170,26 +9368,26 @@ async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
9170
9368
  } catch {
9171
9369
  continue;
9172
9370
  }
9173
- paths.push(path);
9174
- if (paths.length > limit) throw new TypeError("workspace file registry exceeds its bound");
9371
+ paths2.push(path);
9372
+ if (paths2.length > limit) throw new TypeError("workspace file registry exceeds its bound");
9175
9373
  }
9176
9374
  }
9177
9375
  };
9178
9376
  await walk((0, import_path9.resolve)(root));
9179
- return paths.sort();
9377
+ return paths2.sort();
9180
9378
  }
9181
- function listWorkspace(paths, options = {}) {
9379
+ function listWorkspace(paths2, options = {}) {
9182
9380
  const max = options.maxEntries ?? 1e3;
9183
9381
  const prefix = options.prefix?.replace(/\/+$/, "");
9184
- const scoped = prefix ? paths.filter((path) => path === prefix || path.startsWith(`${prefix}/`)) : [...paths];
9382
+ const scoped = prefix ? paths2.filter((path) => path === prefix || path.startsWith(`${prefix}/`)) : [...paths2];
9185
9383
  return scoped.slice(0, max);
9186
9384
  }
9187
- async function searchWorkspace(root, paths, options) {
9385
+ async function searchWorkspace(root, paths2, options) {
9188
9386
  options.signal?.throwIfAborted();
9189
9387
  if (!options.query) throw new TypeError("search query must be a non-empty string");
9190
9388
  const maxResults = options.maxResults ?? DEFAULT_MAX_RESULTS;
9191
9389
  const maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES;
9192
- const scoped = listWorkspace(paths, { ...options.prefix ? { prefix: options.prefix } : {}, maxEntries: paths.length });
9390
+ const scoped = listWorkspace(paths2, { ...options.prefix ? { prefix: options.prefix } : {}, maxEntries: paths2.length });
9193
9391
  if (scoped.length === 0) return [];
9194
9392
  try {
9195
9393
  return await nativeSearch(root, scoped, { ...options, maxResults, maxFileBytes });
@@ -9198,11 +9396,11 @@ async function searchWorkspace(root, paths, options) {
9198
9396
  return fallbackSearch(root, scoped, { ...options, maxResults, maxFileBytes });
9199
9397
  }
9200
9398
  }
9201
- async function nativeSearch(root, paths, options) {
9399
+ async function nativeSearch(root, paths2, options) {
9202
9400
  const batches = [];
9203
9401
  let batch = [];
9204
9402
  let bytes = 0;
9205
- for (const path of paths) {
9403
+ for (const path of paths2) {
9206
9404
  const size = Buffer.byteLength(path) + 1;
9207
9405
  if (batch.length > 0 && bytes + size > MAX_NATIVE_ARG_BYTES) {
9208
9406
  batches.push(batch);
@@ -9221,7 +9419,7 @@ async function nativeSearch(root, paths, options) {
9221
9419
  }
9222
9420
  return matches;
9223
9421
  }
9224
- function nativeSearchBatch(root, paths, options, remaining) {
9422
+ function nativeSearchBatch(root, paths2, options, remaining) {
9225
9423
  return new Promise((resolveMatches, reject) => {
9226
9424
  const args = [
9227
9425
  "--fixed-strings",
@@ -9234,7 +9432,7 @@ function nativeSearchBatch(root, paths, options, remaining) {
9234
9432
  options.caseSensitive === false ? "--ignore-case" : "--case-sensitive",
9235
9433
  "--",
9236
9434
  options.query,
9237
- ...paths
9435
+ ...paths2
9238
9436
  ];
9239
9437
  const child = (0, import_child_process6.spawn)("rg", args, {
9240
9438
  cwd: root,
@@ -9380,16 +9578,16 @@ function createCodePolicyGate(options) {
9380
9578
  }
9381
9579
  };
9382
9580
  }
9383
- function directoryPrefixes(paths) {
9581
+ function directoryPrefixes(paths2) {
9384
9582
  const prefixes = /* @__PURE__ */ new Set(["."]);
9385
- for (const path of paths) {
9583
+ for (const path of paths2) {
9386
9584
  const parts = path.split("/");
9387
9585
  for (let index = 1; index < parts.length; index += 1) prefixes.add(parts.slice(0, index).join("/"));
9388
9586
  }
9389
9587
  return [...prefixes].sort();
9390
9588
  }
9391
- async function safePrefix(base, paths, prefix) {
9392
- const prefixes = directoryPrefixes(paths);
9589
+ async function safePrefix(base, paths2, prefix) {
9590
+ const prefixes = directoryPrefixes(paths2);
9393
9591
  const conversions = await conversionRegistry(
9394
9592
  [await registeredPolicy("code.prefix.v1", "code.prefixes.v1", prefixes)],
9395
9593
  { "code.prefixes.v1": prefixes }
@@ -9500,7 +9698,7 @@ function optionalInteger(value2) {
9500
9698
  function response(request3, ok, content2, details) {
9501
9699
  return { requestId: request3.requestId, ok, content: content2, ...details ? { details } : {} };
9502
9700
  }
9503
- function workspaceGraphs(workspaceDir, paths) {
9701
+ function workspaceGraphs(workspaceDir, paths2) {
9504
9702
  const existing = cache.get(workspaceDir);
9505
9703
  if (existing) return existing;
9506
9704
  const read22 = (path) => (0, import_promises11.readFile)((0, import_path10.join)(workspaceDir, path), "utf8");
@@ -9508,7 +9706,7 @@ function workspaceGraphs(workspaceDir, paths) {
9508
9706
  // No knownTables: a staged workspace may not carry migrations, and a filter
9509
9707
  // that silently drops every table is worse than an unfiltered one. Callers
9510
9708
  // with ground truth should build the graph themselves.
9511
- graph: await buildCodeGraph({ paths, read: read22, data: { ignore: (path) => path.includes(".generated.") } })
9709
+ graph: await buildCodeGraph({ paths: paths2, read: read22, data: { ignore: (path) => path.includes(".generated.") } })
9512
9710
  }))();
9513
9711
  cache.set(workspaceDir, built);
9514
9712
  return built;
@@ -9561,11 +9759,11 @@ async function read(context, request3, options, policy, registry) {
9561
9759
  if (endLine < startLine || endLine - startLine + 1 > (options.maxReadLines ?? 2e3)) {
9562
9760
  throw new TypeError("requested line range exceeds its bound");
9563
9761
  }
9564
- const paths = await registry.files(context.workspaceDir);
9565
- if (!paths.includes(path)) {
9762
+ const paths2 = await registry.files(context.workspaceDir);
9763
+ if (!paths2.includes(path)) {
9566
9764
  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.`);
9567
9765
  }
9568
- const allowed = await policy.read(policyContext(context, request3, options, { paths, path, startLine, endLine }));
9766
+ const allowed = await policy.read(policyContext(context, request3, options, { paths: paths2, path, startLine, endLine }));
9569
9767
  if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
9570
9768
  const target = resolveCodePath(context.workspaceDir, path);
9571
9769
  const info = await (0, import_promises10.stat)(target);
@@ -9587,16 +9785,16 @@ async function list(context, request3, options, policy, registry) {
9587
9785
  const prefix = typeof raw === "string" && raw.length > 0 ? raw : void 0;
9588
9786
  const maxEntries = optionalInteger(request3.input.maxEntries) ?? 1e3;
9589
9787
  if (maxEntries > 5e3) throw new TypeError("maxEntries exceeds its bound");
9590
- const paths = await registry.files(context.workspaceDir);
9591
- const allowed = await policy.list(policyContext(context, request3, options, { paths, ...prefix ? { prefix } : {} }));
9788
+ const paths2 = await registry.files(context.workspaceDir);
9789
+ const allowed = await policy.list(policyContext(context, request3, options, { paths: paths2, ...prefix ? { prefix } : {} }));
9592
9790
  if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
9593
- const entries = listWorkspace(paths, { ...prefix ? { prefix } : {}, maxEntries });
9791
+ const entries = listWorkspace(paths2, { ...prefix ? { prefix } : {}, maxEntries });
9594
9792
  if (!entries.length) {
9595
9793
  return response(request3, true, prefix ? `No files under "${prefix}".` : "Workspace is empty.", { count: 0 });
9596
9794
  }
9597
- const truncated = entries.length < paths.length && entries.length === maxEntries;
9598
- const hint = !prefix && paths.length > 500 ? `
9599
- \u2026 ${paths.length} files total. sandbox.overview is far cheaper for orientation; use a prefix here once you know the area.` : "";
9795
+ const truncated = entries.length < paths2.length && entries.length === maxEntries;
9796
+ const hint = !prefix && paths2.length > 500 ? `
9797
+ \u2026 ${paths2.length} files total. sandbox.overview is far cheaper for orientation; use a prefix here once you know the area.` : "";
9600
9798
  return response(
9601
9799
  request3,
9602
9800
  true,
@@ -9614,10 +9812,10 @@ async function search(context, request3, options, policy, registry) {
9614
9812
  const maxResults = optionalInteger(request3.input.maxResults) ?? 100;
9615
9813
  if (maxResults > 500) throw new TypeError("maxResults exceeds its bound");
9616
9814
  const caseSensitive = request3.input.caseSensitive === void 0 ? true : request3.input.caseSensitive === true;
9617
- const paths = await registry.files(context.workspaceDir);
9618
- const allowed = await policy.search(policyContext(context, request3, options, { paths, query, ...prefix ? { prefix } : {} }));
9815
+ const paths2 = await registry.files(context.workspaceDir);
9816
+ const allowed = await policy.search(policyContext(context, request3, options, { paths: paths2, query, ...prefix ? { prefix } : {} }));
9619
9817
  if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
9620
- const matches = await searchWorkspace(context.workspaceDir, paths, {
9818
+ const matches = await searchWorkspace(context.workspaceDir, paths2, {
9621
9819
  query,
9622
9820
  maxResults,
9623
9821
  caseSensitive,
@@ -9639,8 +9837,8 @@ async function graphQuery(context, request3, options, policy, registry) {
9639
9837
  selector: query
9640
9838
  }));
9641
9839
  if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
9642
- const paths = await registry.files(context.workspaceDir);
9643
- const graphs = await workspaceGraphs(context.workspaceDir, paths);
9840
+ const paths2 = await registry.files(context.workspaceDir);
9841
+ const graphs = await workspaceGraphs(context.workspaceDir, paths2);
9644
9842
  if (request3.tool === "sandbox.overview") {
9645
9843
  return response(request3, true, renderOverview(graphs, query || void 0));
9646
9844
  }
@@ -9702,16 +9900,16 @@ function toolFailureMessage(reason) {
9702
9900
  async function patch(context, request3, options, policy, registry) {
9703
9901
  exactKeys(request3.input, ["patch"]);
9704
9902
  const value2 = stringField(request3.input, "patch");
9705
- const paths = validateCodePatch(value2, options.maxPatchBytes ?? 256 * 1024);
9706
- if (paths.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
9903
+ const paths2 = validateCodePatch(value2, options.maxPatchBytes ?? 256 * 1024);
9904
+ if (paths2.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
9707
9905
  throw new TypeError("patch targets a read-only reference source");
9708
9906
  }
9709
9907
  const allowed = await policy.patch(policyContext(context, request3, options, { patch: value2 }));
9710
9908
  if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
9711
- await applyCodePatch(context.workspaceDir, value2, paths);
9909
+ await applyCodePatch(context.workspaceDir, value2, paths2);
9712
9910
  registry.invalidate(context.workspaceDir);
9713
9911
  forgetWorkspaceGraphs(context.workspaceDir);
9714
- return response(request3, true, `Applied patch to ${paths.length} file(s).`, { paths });
9912
+ return response(request3, true, `Applied patch to ${paths2.length} file(s).`, { paths: paths2 });
9715
9913
  }
9716
9914
  async function recipe(context, request3, options, recipes, policy) {
9717
9915
  exactKeys(request3.input, ["recipeId"]);
@@ -10058,16 +10256,107 @@ async function startGoalPursuit(input) {
10058
10256
  async function appendCodeRuntimeEvent(control, command, event, refs) {
10059
10257
  const eventId = `${command.commandId.slice(0, 45)}:${refs.length + 1}`;
10060
10258
  refs.push(eventId);
10061
- const bounded = event.type === "message" ? { ...event, body: event.body.trim().slice(0, 2e4) || `${event.actor} event` } : event;
10259
+ const attributed = { ...event, interactionId: command.commandId };
10260
+ const bounded = attributed.type === "message" ? { ...attributed, body: attributed.body.trim().slice(0, 2e4) || `${attributed.actor} event` } : attributed;
10062
10261
  await control.appendSessionEvent(command.sessionId, eventId, bounded);
10063
10262
  }
10064
- var import_crypto, import_promises5, import_path5, import_child_process4, import_promises6, import_path6, import_child_process5, import_process2, import_crypto2, import_crypto3, import_fs2, import_promises7, import_path7, import_promises8, import_os3, import_path8, import_ai4, import_ai5, import_child_process6, import_promises9, import_path9, import_promises10, import_promises11, import_path10, import_crypto4, CODE_RUNTIME_PROTOCOL_VERSION, CodeRuntimeReconciler, CodeRuntimeControlError, record5, invalid, RESERVED, SECRET, PATH, FORBIDDEN, ARTIFACT_PATH, PRIVATE_ARTIFACT_PART, SHA3, DIGEST3, ID3, RULE, DEFAULT_PREFIXES, DEFAULT_SUFFIXES, message, CodeRuntimeCheckpointManager, record22, SOURCE_LIMITS, RESERVED2, SECRET2, SOURCE_MAX_FILES, SOURCE_MAX_BYTES, SOURCE_SET_MAX_BYTES, V1_SYSTEM_PROMPT, V2_SYSTEM_PROMPT, V3_SYSTEM_PROMPT, SYSTEM_PROMPT_FOR, DEFAULT_MAX_FILES, DEFAULT_MAX_RESULTS, DEFAULT_MAX_FILE_BYTES, MAX_NATIVE_ARG_BYTES, DESTINATIONS, READ, LIST, SEARCH, GRAPH, PATCH, RECIPE, cache, shortId, GRAPH_TOOLS, MAX_MEMORY_BODY, POSITIVE, digestRuntimeValue, runtimeErrorMessage, TheseusRuntimeEngine;
10065
- var init_chunk_GYWQM76X = __esm({
10066
- "../harness/dist/chunk-GYWQM76X.js"() {
10263
+ function patchStats(value2) {
10264
+ if (typeof value2 !== "string") return {};
10265
+ let additions = 0;
10266
+ let deletions = 0;
10267
+ for (const line2 of value2.slice(0, 262144).split("\n")) {
10268
+ if (line2.startsWith("+++") || line2.startsWith("---")) continue;
10269
+ if (line2.startsWith("+")) additions += 1;
10270
+ else if (line2.startsWith("-")) deletions += 1;
10271
+ }
10272
+ return { ...additions ? { additions } : {}, ...deletions ? { deletions } : {} };
10273
+ }
10274
+ function searchResults(value2) {
10275
+ if (typeof value2 !== "string") return void 0;
10276
+ const results = value2.split("\n").flatMap((line2) => {
10277
+ const match = /^([^:\n]{1,1024}):(\d+):\s?(.*)$/.exec(line2);
10278
+ if (!match) return [];
10279
+ const lineNumber = Number(match[2]);
10280
+ const itemText = text3(match[3], 240);
10281
+ if (!Number.isSafeInteger(lineNumber) || lineNumber < 1) return [];
10282
+ return [{ path: match[1], line: lineNumber, ...itemText ? { text: itemText } : {} }];
10283
+ }).slice(0, 5);
10284
+ return results.length ? results : void 0;
10285
+ }
10286
+ function codeToolRequestPresentation(request3) {
10287
+ const input = request3.input;
10288
+ if (request3.tool === "sandbox.read") {
10289
+ const path = text3(input.path, 1024);
10290
+ if (!path) return void 0;
10291
+ const startLine = integer2(input.startLine);
10292
+ const endLine = integer2(input.endLine);
10293
+ return { kind: "read", path, ...startLine ? { startLine } : {}, ...endLine ? { endLine } : {} };
10294
+ }
10295
+ if (request3.tool === "sandbox.list") {
10296
+ const scope = text3(input.prefix, 1024);
10297
+ return { kind: "list", ...scope ? { scope } : {} };
10298
+ }
10299
+ if (request3.tool === "sandbox.search" || request3.tool === "sandbox.overview" || request3.tool === "sandbox.where_is" || request3.tool === "sandbox.who_imports" || request3.tool === "sandbox.who_touches") {
10300
+ const query = text3(input.query, 512);
10301
+ const scope = request3.tool === "sandbox.search" ? text3(input.prefix, 1024) : void 0;
10302
+ if (request3.tool === "sandbox.search" && !query) return void 0;
10303
+ return { kind: "query", ...query ? { query } : {}, ...scope ? { scope } : {} };
10304
+ }
10305
+ if (request3.tool === "sandbox.apply_patch") {
10306
+ return { kind: "patch", ...patchStats(input.patch) };
10307
+ }
10308
+ const recipeId = text3(input.recipeId, 120);
10309
+ return recipeId ? { kind: "recipe", recipeId } : void 0;
10310
+ }
10311
+ function codeToolResultPresentation(request3, response2) {
10312
+ const started = codeToolRequestPresentation(request3);
10313
+ if (!started || !response2.ok) return started;
10314
+ const details = record32(response2.details);
10315
+ if (started.kind === "read") {
10316
+ return {
10317
+ ...started,
10318
+ ...integer2(details?.startLine) ? { startLine: integer2(details?.startLine) } : {},
10319
+ ...integer2(details?.endLine) ? { endLine: integer2(details?.endLine) } : {},
10320
+ ...excerpt(response2.content) ? { excerpt: excerpt(response2.content) } : {}
10321
+ };
10322
+ }
10323
+ if (started.kind === "list") {
10324
+ 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);
10325
+ return {
10326
+ ...started,
10327
+ ...integer2(details?.count) !== void 0 ? { count: integer2(details?.count) } : {},
10328
+ ...listed.length ? { paths: listed } : {}
10329
+ };
10330
+ }
10331
+ if (started.kind === "query") {
10332
+ const results = request3.tool === "sandbox.search" ? searchResults(response2.content) : void 0;
10333
+ const resultExcerpt = request3.tool === "sandbox.search" ? void 0 : excerpt(response2.content);
10334
+ return {
10335
+ ...started,
10336
+ ...integer2(details?.count) !== void 0 ? { count: integer2(details?.count) } : {},
10337
+ ...results ? { results } : {},
10338
+ ...resultExcerpt ? { excerpt: resultExcerpt } : {}
10339
+ };
10340
+ }
10341
+ if (started.kind === "patch") {
10342
+ return { ...started, ...paths(details?.paths) ? { paths: paths(details?.paths) } : {} };
10343
+ }
10344
+ const output = response2.content.replace(/^Recipe [^\n]*\.?\s*/u, "");
10345
+ return {
10346
+ ...started,
10347
+ ...integer2(details?.exitCode) !== void 0 ? { exitCode: integer2(details?.exitCode) } : {},
10348
+ ...typeof details?.timedOut === "boolean" ? { timedOut: details.timedOut } : {},
10349
+ ...typeof details?.outputLimitExceeded === "boolean" ? { outputLimitExceeded: details.outputLimitExceeded } : {},
10350
+ ...excerpt(output, true) ? { excerpt: excerpt(output, true) } : {}
10351
+ };
10352
+ }
10353
+ var import_crypto, import_promises5, import_path5, import_child_process4, import_promises6, import_path6, import_child_process5, import_process2, import_crypto2, import_crypto3, import_fs2, import_promises7, import_path7, import_promises8, import_os3, import_path8, import_ai4, import_ai5, import_child_process6, import_promises9, import_path9, import_promises10, import_promises11, import_path10, import_crypto4, CODE_RUNTIME_PROTOCOL_VERSION, CodeRuntimeReconciler, CodeRuntimeControlError, record5, invalid, RESERVED, SECRET, PATH, FORBIDDEN, ARTIFACT_PATH, PRIVATE_ARTIFACT_PART, SHA3, DIGEST3, ID3, RULE, DEFAULT_PREFIXES, DEFAULT_SUFFIXES, message, CodeRuntimeCheckpointManager, record22, SOURCE_LIMITS, RESERVED2, SECRET2, SOURCE_MAX_FILES, SOURCE_MAX_BYTES, SOURCE_SET_MAX_BYTES, V1_SYSTEM_PROMPT, V2_SYSTEM_PROMPT, V3_SYSTEM_PROMPT, SYSTEM_PROMPT_FOR, DEFAULT_MAX_FILES, DEFAULT_MAX_RESULTS, DEFAULT_MAX_FILE_BYTES, MAX_NATIVE_ARG_BYTES, DESTINATIONS, READ, LIST, SEARCH, GRAPH, PATCH, RECIPE, cache, shortId, GRAPH_TOOLS, MAX_MEMORY_BODY, POSITIVE, digestRuntimeValue, runtimeErrorMessage, text3, integer2, record32, excerpt, paths, TheseusRuntimeEngine;
10354
+ var init_chunk_ISR434K7 = __esm({
10355
+ "../harness/dist/chunk-ISR434K7.js"() {
10067
10356
  "use strict";
10068
10357
  init_cjs_shims();
10069
- init_chunk_K76I2TCQ();
10070
- init_chunk_LNQNFGQC();
10358
+ init_chunk_CR6RE3A2();
10359
+ init_chunk_RXNHCGWE();
10071
10360
  import_crypto = require("crypto");
10072
10361
  import_promises5 = require("fs/promises");
10073
10362
  import_path5 = require("path");
@@ -10306,6 +10595,30 @@ Never claim a build or test passed unless odla_run_recipe returned that result.`
10306
10595
  POSITIVE = (value2) => Number.isFinite(value2) && Number(value2) > 0 ? Number(value2) : void 0;
10307
10596
  digestRuntimeValue = (value2) => `sha256:${(0, import_crypto4.createHash)("sha256").update(value2).digest("hex")}`;
10308
10597
  runtimeErrorMessage = (value2) => value2 instanceof Error ? value2.message : String(value2);
10598
+ text3 = (value2, maximum) => {
10599
+ if (typeof value2 !== "string") return void 0;
10600
+ const bounded = value2.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ").trim();
10601
+ return bounded ? bounded.slice(0, maximum) : void 0;
10602
+ };
10603
+ integer2 = (value2) => Number.isSafeInteger(value2) && Number(value2) >= 0 ? Number(value2) : void 0;
10604
+ record32 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : void 0;
10605
+ excerpt = (value2, tail = false) => {
10606
+ if (typeof value2 !== "string") return void 0;
10607
+ const safe = value2.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ");
10608
+ const source = (tail ? safe.slice(-1e4) : safe.slice(0, 1e4)).trim();
10609
+ if (!source) return void 0;
10610
+ const lines = source.split("\n").filter((line2) => line2.trim()).map((line2) => line2.slice(0, 240));
10611
+ const selected = tail ? lines.slice(-10) : lines.slice(0, 10);
10612
+ return text3(selected.join("\n"), 2400);
10613
+ };
10614
+ paths = (value2) => {
10615
+ if (!Array.isArray(value2)) return void 0;
10616
+ const items = value2.flatMap((item) => {
10617
+ const path = text3(item, 1024);
10618
+ return path ? [path] : [];
10619
+ }).slice(0, 12);
10620
+ return items.length ? items : void 0;
10621
+ };
10309
10622
  TheseusRuntimeEngine = class {
10310
10623
  constructor(options) {
10311
10624
  this.options = options;
@@ -10539,18 +10852,29 @@ Never claim a build or test passed unless odla_run_recipe returned that result.`
10539
10852
  return {
10540
10853
  execute: async (context, request3) => {
10541
10854
  const startedAt = Date.now();
10855
+ const operationId = digestRuntimeValue(`${command.commandId}:${request3.requestId}`);
10856
+ const startedPresentation = codeToolRequestPresentation(request3);
10542
10857
  await this.#event(
10543
10858
  command,
10544
- { type: "tool", phase: "started", tool: request3.tool },
10859
+ {
10860
+ type: "tool",
10861
+ phase: "started",
10862
+ tool: request3.tool,
10863
+ operationId,
10864
+ ...startedPresentation ? { presentation: startedPresentation } : {}
10865
+ },
10545
10866
  active.conversationRefs
10546
10867
  ).catch(() => void 0);
10547
10868
  const response2 = await broker.execute(context, request3);
10869
+ const completedPresentation = codeToolResultPresentation(request3, response2);
10548
10870
  await this.#event(command, {
10549
10871
  type: "tool",
10550
10872
  phase: "completed",
10551
10873
  tool: request3.tool,
10552
10874
  ok: response2.ok,
10553
- durationMs: Date.now() - startedAt
10875
+ durationMs: Date.now() - startedAt,
10876
+ operationId,
10877
+ ...completedPresentation ? { presentation: completedPresentation } : {}
10554
10878
  }, active.conversationRefs).catch(() => void 0);
10555
10879
  return response2;
10556
10880
  }
@@ -10591,8 +10915,8 @@ var init_node = __esm({
10591
10915
  "../harness/dist/node.js"() {
10592
10916
  "use strict";
10593
10917
  init_cjs_shims();
10594
- init_chunk_GYWQM76X();
10595
- init_chunk_K76I2TCQ();
10918
+ init_chunk_ISR434K7();
10919
+ init_chunk_CR6RE3A2();
10596
10920
  MEASURED_PREMIUM = Object.freeze({
10597
10921
  /** 3 racers vs pure depth at equal budget: 21,044 / 7,936. */
10598
10922
  racePerRacer: 0.55,
@@ -11072,9 +11396,7 @@ function grantsUrl(cfg, suffix = "") {
11072
11396
  async function codeGrantCommand(parsed, deps = {}) {
11073
11397
  const action2 = parsed.positionals[2];
11074
11398
  if (!isAction(action2)) {
11075
- throw new Error(
11076
- `unknown code grant action "${action2 ?? ""}". Try "odla-ai code grant list --env dev".`
11077
- );
11399
+ rejectWord(["code", "grant"], action2);
11078
11400
  }
11079
11401
  const decides = action2 === "approve" || action2 === "revoke";
11080
11402
  assertArgs(parsed, ["config", "env", "json", "token", "email", "open"], decides ? 4 : 3);
@@ -11141,6 +11463,7 @@ var init_code_grant_command = __esm({
11141
11463
  init_config();
11142
11464
  init_redact();
11143
11465
  init_token();
11466
+ init_surface();
11144
11467
  ACTIONS = ["request", "list", "approve", "revoke"];
11145
11468
  isAction = (value2) => ACTIONS.includes(value2 ?? "");
11146
11469
  }
@@ -11205,9 +11528,7 @@ function repositoryUrl(cfg, suffix = "") {
11205
11528
  async function codeRepositoryCommand(parsed, deps = {}) {
11206
11529
  const action2 = parsed.positionals[2];
11207
11530
  if (!isAction2(action2)) {
11208
- throw new Error(
11209
- `unknown code repository action "${action2 ?? ""}". Try "odla-ai code repository show --env dev".`
11210
- );
11531
+ rejectWord(["code", "repository"], action2);
11211
11532
  }
11212
11533
  assertArgs(parsed, ["config", "env", "repo", "json", "token", "email", "open"], 3);
11213
11534
  const cfg = await loadProjectConfig(stringOpt(parsed.options.config) ?? "odla.config.mjs");
@@ -11272,6 +11593,7 @@ var init_code_repository_command = __esm({
11272
11593
  init_config();
11273
11594
  init_redact();
11274
11595
  init_token();
11596
+ init_surface();
11275
11597
  ACTIONS2 = ["show", "list", "bind"];
11276
11598
  isAction2 = (value2) => ACTIONS2.includes(value2 ?? "");
11277
11599
  }
@@ -11295,9 +11617,7 @@ async function codeCommand(parsed, dependencies) {
11295
11617
  });
11296
11618
  }
11297
11619
  if (sub !== "connect") {
11298
- throw new Error(
11299
- `unknown code subcommand "${sub ?? ""}". Try "odla-ai code connect --env dev" or "odla-ai code grant list --env dev".`
11300
- );
11620
+ rejectWord(["code"], sub);
11301
11621
  }
11302
11622
  assertArgs(parsed, [
11303
11623
  "config",
@@ -11342,6 +11662,7 @@ var init_code_command = __esm({
11342
11662
  init_code_connect();
11343
11663
  init_code_grant_command();
11344
11664
  init_code_repository_command();
11665
+ init_surface();
11345
11666
  }
11346
11667
  });
11347
11668
 
@@ -11453,9 +11774,7 @@ async function contextCommand(parsed, deps = {}) {
11453
11774
  return;
11454
11775
  }
11455
11776
  if (action2 !== "show") {
11456
- throw new Error(
11457
- `unknown context action "${action2 ?? ""}". Try show|list|save|remove.`
11458
- );
11777
+ rejectWord(["context"], action2);
11459
11778
  }
11460
11779
  const context = await resolveOperatorContext(parsed, {
11461
11780
  allowMissingConfig: true,
@@ -11512,6 +11831,7 @@ var init_context_command = __esm({
11512
11831
  init_operator_credentials();
11513
11832
  init_operator_context();
11514
11833
  init_operator_profiles();
11834
+ init_surface();
11515
11835
  }
11516
11836
  });
11517
11837
 
@@ -11521,9 +11841,7 @@ async function responseError(response2) {
11521
11841
  }
11522
11842
  async function credentialCommand(parsed, deps = {}) {
11523
11843
  const action2 = parsed.positionals[1] ?? "list";
11524
- if (action2 !== "list" && action2 !== "revoke") {
11525
- throw new Error(`unknown credentials action "${action2}". Try "odla-ai credentials list".`);
11526
- }
11844
+ if (action2 !== "list" && action2 !== "revoke") rejectWord(["credentials"], action2);
11527
11845
  assertArgs(parsed, ["config", "env", "all", "json", "token", "email", "open"], action2 === "revoke" ? 3 : 2);
11528
11846
  const cfg = await loadProjectConfig(stringOpt(parsed.options.config) ?? "odla.config.mjs");
11529
11847
  const doFetch = deps.fetch ?? fetch;
@@ -11571,6 +11889,7 @@ var init_credential_command = __esm({
11571
11889
  init_config();
11572
11890
  init_redact();
11573
11891
  init_token();
11892
+ init_surface();
11574
11893
  }
11575
11894
  });
11576
11895
 
@@ -12571,7 +12890,7 @@ async function discussCommand(parsed, deps = {}) {
12571
12890
  assertArgs(parsed, ALLOWED, 3);
12572
12891
  const action2 = parsed.positionals[1];
12573
12892
  const id2 = parsed.positionals[2];
12574
- if (!action2) throw new Error('"discuss" needs an action. Run "odla-ai help".');
12893
+ if (!acceptedAfter(["discuss"]).includes(action2 ?? "")) rejectWord(["discuss"], action2);
12575
12894
  const ctx = await buildContext(parsed, deps);
12576
12895
  switch (action2) {
12577
12896
  case "groups":
@@ -12595,7 +12914,7 @@ async function discussCommand(parsed, deps = {}) {
12595
12914
  return;
12596
12915
  }
12597
12916
  default:
12598
- throw new Error(`unknown discuss action "${action2}". Run "odla-ai help".`);
12917
+ rejectWord(["discuss"], action2);
12599
12918
  }
12600
12919
  }
12601
12920
  var ALLOWED;
@@ -12608,6 +12927,7 @@ var init_discuss_command = __esm({
12608
12927
  init_discuss_actions();
12609
12928
  init_discuss_watch();
12610
12929
  init_token();
12930
+ init_surface();
12611
12931
  ALLOWED = [
12612
12932
  "config",
12613
12933
  "token",
@@ -12675,8 +12995,8 @@ function collectFields(parsed, allowClear) {
12675
12995
  if (allowClear) out[spec.key] = null;
12676
12996
  continue;
12677
12997
  }
12678
- const text3 = stringOpt(value2);
12679
- out[spec.key] = spec.num ? Number(text3) : text3;
12998
+ const text4 = stringOpt(value2);
12999
+ out[spec.key] = spec.num ? Number(text4) : text4;
12680
13000
  }
12681
13001
  return out;
12682
13002
  }
@@ -13596,7 +13916,7 @@ async function pmCommand(parsed, deps = {}) {
13596
13916
  assertArgs(parsed, COMMON_OPTIONS, 4);
13597
13917
  return pmProjectUse(await buildContext2(parsed, deps), requireId2(parsed.positionals[3], action3));
13598
13918
  }
13599
- throw new Error(`unknown pm project action "${action3}". Try list|add|use.`);
13919
+ rejectWord(["pm", "project"], action3);
13600
13920
  }
13601
13921
  if (word === "next") {
13602
13922
  assertArgs(parsed, [...COMMON_OPTIONS, "app", "project", "verbose"], 2);
@@ -13627,10 +13947,10 @@ async function pmCommand(parsed, deps = {}) {
13627
13947
  return pmHandoff(await buildContext2(parsed, deps), parsed);
13628
13948
  }
13629
13949
  const entity = ALIASES[word];
13630
- 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).`);
13950
+ if (!entity) rejectWord(["pm"], word);
13631
13951
  const requestedAction = parsed.positionals[2] ?? "list";
13632
13952
  const action2 = canonicalAction(requestedAction);
13633
- if (!action2) throw new Error(`unknown pm action "${requestedAction}". Try list|add|get|set|done|link|ref|comment|comments|history|rm.`);
13953
+ if (!action2) rejectWord(["pm", word], requestedAction);
13634
13954
  assertArgs(parsed, allowedOptions(entity, action2), 4);
13635
13955
  if ((action2 === "ready" || action2 === "claim" || action2 === "release") && entity !== "task") {
13636
13956
  throw new Error(`pm ${action2} is only valid for tasks`);
@@ -13683,6 +14003,7 @@ var init_pm_command = __esm({
13683
14003
  init_pm_watch();
13684
14004
  init_pm_project_actions();
13685
14005
  init_pm_project_context();
14006
+ init_surface();
13686
14007
  ALIASES = {
13687
14008
  goal: "goal",
13688
14009
  conformance: "goal",
@@ -13792,12 +14113,7 @@ async function platformCommand(parsed, deps = {}) {
13792
14113
  if (action2 === "status") {
13793
14114
  return platformStatus(parsed, deps);
13794
14115
  }
13795
- throw new Error(
13796
- `unknown platform action "${[
13797
- action2,
13798
- parsed.positionals[2]
13799
- ].filter(Boolean).join(" ")}". Try "odla-ai platform status --json".`
13800
- );
14116
+ rejectWord(["platform"], action2);
13801
14117
  }
13802
14118
  async function platformStatus(parsed, deps) {
13803
14119
  assertArgs(
@@ -13863,6 +14179,7 @@ var init_platform_command = __esm({
13863
14179
  init_argv();
13864
14180
  init_operator_context();
13865
14181
  init_platform_output();
14182
+ init_surface();
13866
14183
  }
13867
14184
  });
13868
14185
 
@@ -14156,9 +14473,7 @@ async function o11yCommand(parsed, deps = {}) {
14156
14473
  );
14157
14474
  const action2 = parsed.positionals[1];
14158
14475
  if (action2 !== "status") {
14159
- throw new Error(
14160
- `unknown o11y action "${action2 ?? ""}". Try "odla-ai o11y status --json".`
14161
- );
14476
+ rejectWord(["o11y"], action2);
14162
14477
  }
14163
14478
  const minutes = statusMinutes(
14164
14479
  numberOpt(parsed.options.minutes, "--minutes") ?? 60
@@ -14273,14 +14588,14 @@ function statusMinutes(value2) {
14273
14588
  }
14274
14589
  async function read2(url, headers, doFetch) {
14275
14590
  const response2 = await doFetch(url, { headers });
14276
- const text3 = await response2.text();
14591
+ const text4 = await response2.text();
14277
14592
  let body = {};
14278
- if (text3) {
14593
+ if (text4) {
14279
14594
  try {
14280
- const value2 = JSON.parse(text3);
14595
+ const value2 = JSON.parse(text4);
14281
14596
  body = value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : { value: value2 };
14282
14597
  } catch {
14283
- body = { message: text3.slice(0, 300) };
14598
+ body = { message: text4.slice(0, 300) };
14284
14599
  }
14285
14600
  }
14286
14601
  return { httpStatus: response2.status, body };
@@ -14294,6 +14609,7 @@ var init_o11y_command = __esm({
14294
14609
  init_o11y_verdict();
14295
14610
  init_o11y_output();
14296
14611
  init_token();
14612
+ init_surface();
14297
14613
  }
14298
14614
  });
14299
14615
 
@@ -14414,9 +14730,7 @@ var init_monitoring_config = __esm({
14414
14730
  async function monitorCommand(parsed, deps = {}) {
14415
14731
  assertArgs(parsed, OPTIONS, 3);
14416
14732
  const action2 = parsed.positionals[1] ?? "status";
14417
- if (!["plan", "apply", "run", "status", "incidents", "report"].includes(action2)) {
14418
- throw new Error(`unknown monitor action "${action2}". Try "odla-ai monitor status --json".`);
14419
- }
14733
+ if (!acceptedAfter(["monitor"]).includes(action2)) rejectWord(["monitor"], action2);
14420
14734
  const context = await resolveOperatorContext(parsed, {
14421
14735
  allowMissingConfig: action2 !== "plan" && action2 !== "apply",
14422
14736
  requireApp: true
@@ -14513,13 +14827,13 @@ async function monitorCommand(parsed, deps = {}) {
14513
14827
  }
14514
14828
  async function request2(url, init, doFetch) {
14515
14829
  const response2 = await doFetch(url, init);
14516
- const text3 = await response2.text();
14830
+ const text4 = await response2.text();
14517
14831
  let body = {};
14518
14832
  try {
14519
- const parsed = text3 ? JSON.parse(text3) : {};
14833
+ const parsed = text4 ? JSON.parse(text4) : {};
14520
14834
  body = record10(parsed) ? parsed : { value: parsed };
14521
14835
  } catch {
14522
- body = { message: text3.slice(0, 500) };
14836
+ body = { message: text4.slice(0, 500) };
14523
14837
  }
14524
14838
  if (!response2.ok) {
14525
14839
  const error = record10(body.error) ? body.error : body;
@@ -14570,6 +14884,7 @@ var init_monitor_command = __esm({
14570
14884
  init_monitoring_config();
14571
14885
  init_operator_context();
14572
14886
  init_token();
14887
+ init_surface();
14573
14888
  OPTIONS = [
14574
14889
  "config",
14575
14890
  "context",
@@ -14759,8 +15074,8 @@ function runtimeUrl(cfg, suffix = "") {
14759
15074
  return `${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/runtime-credentials${suffix}`;
14760
15075
  }
14761
15076
  async function safeError(response2) {
14762
- const text3 = await response2.text();
14763
- return redactSecrets(text3.slice(0, 1e3));
15077
+ const text4 = await response2.text();
15078
+ return redactSecrets(text4.slice(0, 1e3));
14764
15079
  }
14765
15080
  async function finish(doFetch, cfg, token, sessionId, method) {
14766
15081
  return doFetch(runtimeUrl(cfg, `/${encodeURIComponent(sessionId)}`), {
@@ -15119,182 +15434,6 @@ var init_provision = __esm({
15119
15434
  }
15120
15435
  });
15121
15436
 
15122
- // src/surface.ts
15123
- function acceptedAfter(path) {
15124
- let node = COMMAND_SURFACE;
15125
- for (const word of path) {
15126
- node = node?.[word];
15127
- if (!node) return [];
15128
- }
15129
- return Object.keys(node).sort();
15130
- }
15131
- function validateInvocation(words2) {
15132
- let node = COMMAND_SURFACE;
15133
- const walked = [];
15134
- for (const word of words2) {
15135
- if (Object.keys(node).length === 0) return null;
15136
- const next = node[word];
15137
- if (!next) return { validPrefix: walked.join(" "), word, accepted: Object.keys(node).sort() };
15138
- walked.push(word);
15139
- node = next;
15140
- }
15141
- return null;
15142
- }
15143
- function describeProblem(problem) {
15144
- const where = problem.validPrefix ? `after "${problem.validPrefix}"` : "as a command";
15145
- return `"${problem.word}" is not accepted ${where} \u2014 try: ${problem.accepted.join(", ")}`;
15146
- }
15147
- function invocationPath(words2) {
15148
- let node = COMMAND_SURFACE;
15149
- const path = [];
15150
- for (const word of words2) {
15151
- const next = node[word];
15152
- if (!next) break;
15153
- path.push(word);
15154
- node = next;
15155
- if (Object.keys(node).length === 0) break;
15156
- }
15157
- return path;
15158
- }
15159
- var PM_ACTIONS, PM_TASK_ACTIONS, PM_ENTITIES, COMMAND_SURFACE;
15160
- var init_surface = __esm({
15161
- "src/surface.ts"() {
15162
- "use strict";
15163
- init_cjs_shims();
15164
- PM_ACTIONS = {
15165
- list: {},
15166
- add: {},
15167
- create: {},
15168
- get: {},
15169
- set: {},
15170
- update: {},
15171
- status: {},
15172
- move: {},
15173
- done: {},
15174
- comment: {},
15175
- comments: {},
15176
- ref: {},
15177
- rm: {},
15178
- delete: {}
15179
- };
15180
- PM_TASK_ACTIONS = {
15181
- ...PM_ACTIONS,
15182
- ready: {},
15183
- claim: {},
15184
- release: {}
15185
- };
15186
- PM_ENTITIES = {
15187
- ...Object.fromEntries(
15188
- ["goal", "conformance", "decision", "bug"].map((entity) => [entity, PM_ACTIONS])
15189
- ),
15190
- task: PM_TASK_ACTIONS,
15191
- kanban: PM_TASK_ACTIONS
15192
- };
15193
- COMMAND_SURFACE = {
15194
- agent: { jobs: {}, retry: {} },
15195
- ai: { models: {} },
15196
- admin: {
15197
- ai: {
15198
- show: {},
15199
- set: {},
15200
- credentials: {},
15201
- models: {},
15202
- usage: {},
15203
- audit: {},
15204
- credential: { set: {} }
15205
- }
15206
- },
15207
- app: {
15208
- archive: {},
15209
- restore: {},
15210
- export: {},
15211
- import: {},
15212
- rename: {},
15213
- "refresh-sandbox": {},
15214
- "go-live": {},
15215
- promote: {},
15216
- owners: { list: {}, add: {}, remove: {} }
15217
- },
15218
- auth: { login: {} },
15219
- brand: { design: { unpack: {} } },
15220
- bug: { create: {}, list: {}, report: {} },
15221
- calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
15222
- capabilities: {},
15223
- code: {
15224
- connect: {},
15225
- grant: { request: {}, list: {}, approve: {}, revoke: {} },
15226
- repository: { show: {}, list: {}, bind: {} }
15227
- },
15228
- config: { diff: {}, plan: {}, apply: {} },
15229
- context: { show: {}, list: {}, save: {}, remove: {} },
15230
- credentials: { list: {}, revoke: {} },
15231
- device: { enroll: {}, list: {}, revoke: {} },
15232
- // `watch`, `read`, `reply`, and `resolve` take a topic id from there on.
15233
- discuss: {
15234
- groups: {},
15235
- list: {},
15236
- topics: {},
15237
- read: {},
15238
- post: {},
15239
- reply: {},
15240
- resolve: {},
15241
- who: {},
15242
- watch: {}
15243
- },
15244
- doctor: {},
15245
- help: {},
15246
- init: {},
15247
- monitor: { plan: {}, apply: {}, run: {}, status: {}, incidents: {}, report: {} },
15248
- o11y: { status: {} },
15249
- operations: { get: {}, wait: {} },
15250
- platform: {
15251
- status: {}
15252
- },
15253
- pm: {
15254
- ...PM_ENTITIES,
15255
- project: { list: {}, add: {}, create: {}, use: {} },
15256
- handoff: {},
15257
- next: {},
15258
- watch: {}
15259
- },
15260
- provision: {},
15261
- runbook: {
15262
- ask: {},
15263
- search: {},
15264
- impact: {},
15265
- list: {},
15266
- get: {},
15267
- cat: {},
15268
- new: {},
15269
- edit: {},
15270
- comment: {},
15271
- import: {},
15272
- visibility: {},
15273
- publish: {},
15274
- archive: {},
15275
- history: {},
15276
- revert: {},
15277
- rm: {},
15278
- lint: {}
15279
- },
15280
- secrets: { push: {}, status: {}, set: {}, "set-clerk-key": {} },
15281
- security: {
15282
- plan: {},
15283
- sources: {},
15284
- run: {},
15285
- status: {},
15286
- report: {},
15287
- github: { connect: {}, disconnect: {} }
15288
- },
15289
- setup: {},
15290
- skill: { install: {} },
15291
- smoke: {},
15292
- version: {},
15293
- whoami: {}
15294
- };
15295
- }
15296
- });
15297
-
15298
15437
  // src/record.ts
15299
15438
  function recordInvocation(parsed) {
15300
15439
  const file = import_node_process18.default.env.ODLA_CLI_RECORD;
@@ -15361,10 +15500,10 @@ var init_advisory_output = __esm({
15361
15500
  // src/device-ttl.ts
15362
15501
  function parseDeviceTtl(raw) {
15363
15502
  if (raw === void 0 || raw === true) return void 0;
15364
- const text3 = String(raw).trim().toLowerCase();
15365
- if (!text3) return void 0;
15366
- if (text3 === "forever" || text3 === "never") return 100 * 365 * DAY_MS;
15367
- const match = /^(\d+)\s*([dwy])$/.exec(text3);
15503
+ const text4 = String(raw).trim().toLowerCase();
15504
+ if (!text4) return void 0;
15505
+ if (text4 === "forever" || text4 === "never") return 100 * 365 * DAY_MS;
15506
+ const match = /^(\d+)\s*([dwy])$/.exec(text4);
15368
15507
  if (!match) {
15369
15508
  throw new Error(
15370
15509
  `--device-ttl expects a duration like 30d, 6w, 2y, or "forever" (got "${raw}")`
@@ -15402,6 +15541,7 @@ async function deviceCommand(parsed, deps) {
15402
15541
  "wait"
15403
15542
  ], 3);
15404
15543
  const action2 = parsed.positionals[1] ?? "";
15544
+ if (!acceptedAfter(["device"]).includes(action2)) rejectWord(["device"], action2);
15405
15545
  const out = deps.stdout ?? console;
15406
15546
  const doFetch = deps.fetch ?? fetch;
15407
15547
  const cfg = await loadProjectConfig(stringOpt(parsed.options.config));
@@ -15409,7 +15549,7 @@ async function deviceCommand(parsed, deps) {
15409
15549
  if (action2 === "enroll") return enroll(parsed, deps, cfg, doFetch, out, json);
15410
15550
  if (action2 === "list") return list2(parsed, deps, cfg, doFetch, out, json);
15411
15551
  if (action2 === "revoke") return revoke(parsed, deps, cfg, doFetch, out, json);
15412
- throw new Error('odla-ai device expects "enroll", "list", or "revoke"');
15552
+ rejectWord(["device"], action2);
15413
15553
  }
15414
15554
  async function enroll(parsed, deps, cfg, doFetch, out, json) {
15415
15555
  const name = stringOpt(parsed.options.name) ?? defaultDeviceName();
@@ -15579,6 +15719,7 @@ var init_device_command = __esm({
15579
15719
  init_device_session();
15580
15720
  init_config();
15581
15721
  init_operator_context();
15722
+ init_surface();
15582
15723
  }
15583
15724
  });
15584
15725
 
@@ -15731,12 +15872,12 @@ var init_runbook_actions = __esm({
15731
15872
  });
15732
15873
 
15733
15874
  // src/runbook-import.ts
15734
- function parseRunbook(text3, slug) {
15735
- let rest = text3;
15875
+ function parseRunbook(text4, slug) {
15876
+ let rest = text4;
15736
15877
  const meta = {};
15737
- const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text3);
15878
+ const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text4);
15738
15879
  if (fm) {
15739
- rest = text3.slice(fm[0].length);
15880
+ rest = text4.slice(fm[0].length);
15740
15881
  for (const line2 of fm[1].split(/\r?\n/)) {
15741
15882
  const pair = /^(\w+)\s*:\s*(.+)$/.exec(line2.trim());
15742
15883
  if (!pair) continue;
@@ -16367,6 +16508,7 @@ async function buildContext3(parsed, deps, action2) {
16367
16508
  async function runbookCommand(parsed, deps = {}) {
16368
16509
  const action2 = parsed.positionals[1] ?? "list";
16369
16510
  assertArgs(parsed, ALLOWED2, action2 === "ask" || action2 === "search" ? 64 : 4);
16511
+ if (!acceptedAfter(["runbook"]).includes(action2)) rejectWord(["runbook"], action2);
16370
16512
  const ctx = await buildContext3(parsed, deps, action2);
16371
16513
  const slug = parsed.positionals[2];
16372
16514
  switch (action2) {
@@ -16456,7 +16598,7 @@ async function runbookCommand(parsed, deps = {}) {
16456
16598
  case "rm":
16457
16599
  return runbookRemove(ctx, requireSlug(slug, "rm"));
16458
16600
  default:
16459
- throw new Error(`unknown runbook action "${action2}". Try ${acceptedAfter(["runbook"]).join(", ")}.`);
16601
+ rejectWord(["runbook"], action2);
16460
16602
  }
16461
16603
  }
16462
16604
  var ALLOWED2, WRITES2;
@@ -17165,9 +17307,7 @@ async function securityCommand(parsed, dependencies) {
17165
17307
  else printHostedReport(context.stdout, report5);
17166
17308
  return;
17167
17309
  }
17168
- if (sub !== "run") {
17169
- throw new Error('unknown security command. Try "odla-ai security plan", "security sources", or "security run".');
17170
- }
17310
+ if (sub !== "run") rejectWord(["security"], sub);
17171
17311
  const sourceId = stringOpt(parsed.options.source);
17172
17312
  if (sourceId) await runSourceSecurityCommand(parsed, dependencies, sourceId);
17173
17313
  else await runLocalSecurityCommand(parsed, dependencies);
@@ -17184,9 +17324,7 @@ async function githubSecurityCommand(parsed, dependencies) {
17184
17324
  stringOpt(parsed.options.env)
17185
17325
  );
17186
17326
  }
17187
- if (action2 !== "connect") {
17188
- throw new Error('unknown security github command. Try "odla-ai security github connect".');
17189
- }
17327
+ if (action2 !== "connect") rejectWord(["security", "github"], action2);
17190
17328
  assertArgs(parsed, ["config", "env", "platform", "repo", "email", "open"], 3);
17191
17329
  await requireStudioHuman(
17192
17330
  stringOpt(parsed.options.config) ?? "odla.config.mjs",
@@ -17242,6 +17380,7 @@ var init_security_command = __esm({
17242
17380
  init_security_run_command();
17243
17381
  init_security_hosted();
17244
17382
  init_human_session();
17383
+ init_surface();
17245
17384
  }
17246
17385
  });
17247
17386
 
@@ -17346,6 +17485,7 @@ async function dispatchCli(argv2, dependencies) {
17346
17485
  }
17347
17486
  if (command === "bug") {
17348
17487
  const action2 = parsed.positionals[1] ?? "list";
17488
+ if (!acceptedAfter(["bug"]).includes(action2)) rejectWord(["bug"], action2);
17349
17489
  const canonical2 = action2 === "report" || action2 === "create" ? "add" : action2;
17350
17490
  await pmCommand({
17351
17491
  ...parsed,
@@ -17374,7 +17514,7 @@ async function dispatchCli(argv2, dependencies) {
17374
17514
  return;
17375
17515
  }
17376
17516
  if (await projectCommand(command, parsed, runtime)) return;
17377
- throw new Error(`unknown command "${command}". Run "odla-ai help".`);
17517
+ rejectWord([], command);
17378
17518
  }
17379
17519
  async function provisionCommand(parsed, dependencies) {
17380
17520
  assertArgs(parsed, [
@@ -17418,7 +17558,7 @@ async function provisionCommand(parsed, dependencies) {
17418
17558
  async function calendarCommand(parsed, dependencies) {
17419
17559
  const sub = parsed.positionals[1];
17420
17560
  if (sub !== "status" && sub !== "calendars" && sub !== "connect" && sub !== "disconnect") {
17421
- throw new Error(`unknown calendar subcommand "${sub ?? ""}". Try "odla-ai calendar status --env dev".`);
17561
+ rejectWord(["calendar"], sub);
17422
17562
  }
17423
17563
  assertArgs(parsed, ["config", "env", "json", "token", "email", "open", "yes"], 2);
17424
17564
  if (sub !== "status" && sub !== "calendars" && parsed.options.json !== void 0) throw new Error(`--json is supported only by calendar status/calendars`);
@@ -17472,6 +17612,7 @@ var init_cli = __esm({
17472
17612
  init_runbook_command();
17473
17613
  init_security_command();
17474
17614
  init_whoami_command();
17615
+ init_surface();
17475
17616
  init_exit_code();
17476
17617
  }
17477
17618
  });