@odla-ai/cli 0.43.1 → 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
@@ -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
 
@@ -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
 
@@ -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
  });
@@ -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,6 +6750,7 @@ 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
  });
@@ -11198,9 +11396,7 @@ function grantsUrl(cfg, suffix = "") {
11198
11396
  async function codeGrantCommand(parsed, deps = {}) {
11199
11397
  const action2 = parsed.positionals[2];
11200
11398
  if (!isAction(action2)) {
11201
- throw new Error(
11202
- `unknown code grant action "${action2 ?? ""}". Try "odla-ai code grant list --env dev".`
11203
- );
11399
+ rejectWord(["code", "grant"], action2);
11204
11400
  }
11205
11401
  const decides = action2 === "approve" || action2 === "revoke";
11206
11402
  assertArgs(parsed, ["config", "env", "json", "token", "email", "open"], decides ? 4 : 3);
@@ -11267,6 +11463,7 @@ var init_code_grant_command = __esm({
11267
11463
  init_config();
11268
11464
  init_redact();
11269
11465
  init_token();
11466
+ init_surface();
11270
11467
  ACTIONS = ["request", "list", "approve", "revoke"];
11271
11468
  isAction = (value2) => ACTIONS.includes(value2 ?? "");
11272
11469
  }
@@ -11331,9 +11528,7 @@ function repositoryUrl(cfg, suffix = "") {
11331
11528
  async function codeRepositoryCommand(parsed, deps = {}) {
11332
11529
  const action2 = parsed.positionals[2];
11333
11530
  if (!isAction2(action2)) {
11334
- throw new Error(
11335
- `unknown code repository action "${action2 ?? ""}". Try "odla-ai code repository show --env dev".`
11336
- );
11531
+ rejectWord(["code", "repository"], action2);
11337
11532
  }
11338
11533
  assertArgs(parsed, ["config", "env", "repo", "json", "token", "email", "open"], 3);
11339
11534
  const cfg = await loadProjectConfig(stringOpt(parsed.options.config) ?? "odla.config.mjs");
@@ -11398,6 +11593,7 @@ var init_code_repository_command = __esm({
11398
11593
  init_config();
11399
11594
  init_redact();
11400
11595
  init_token();
11596
+ init_surface();
11401
11597
  ACTIONS2 = ["show", "list", "bind"];
11402
11598
  isAction2 = (value2) => ACTIONS2.includes(value2 ?? "");
11403
11599
  }
@@ -11421,9 +11617,7 @@ async function codeCommand(parsed, dependencies) {
11421
11617
  });
11422
11618
  }
11423
11619
  if (sub !== "connect") {
11424
- throw new Error(
11425
- `unknown code subcommand "${sub ?? ""}". Try "odla-ai code connect --env dev" or "odla-ai code grant list --env dev".`
11426
- );
11620
+ rejectWord(["code"], sub);
11427
11621
  }
11428
11622
  assertArgs(parsed, [
11429
11623
  "config",
@@ -11468,6 +11662,7 @@ var init_code_command = __esm({
11468
11662
  init_code_connect();
11469
11663
  init_code_grant_command();
11470
11664
  init_code_repository_command();
11665
+ init_surface();
11471
11666
  }
11472
11667
  });
11473
11668
 
@@ -11579,9 +11774,7 @@ async function contextCommand(parsed, deps = {}) {
11579
11774
  return;
11580
11775
  }
11581
11776
  if (action2 !== "show") {
11582
- throw new Error(
11583
- `unknown context action "${action2 ?? ""}". Try show|list|save|remove.`
11584
- );
11777
+ rejectWord(["context"], action2);
11585
11778
  }
11586
11779
  const context = await resolveOperatorContext(parsed, {
11587
11780
  allowMissingConfig: true,
@@ -11638,6 +11831,7 @@ var init_context_command = __esm({
11638
11831
  init_operator_credentials();
11639
11832
  init_operator_context();
11640
11833
  init_operator_profiles();
11834
+ init_surface();
11641
11835
  }
11642
11836
  });
11643
11837
 
@@ -11647,9 +11841,7 @@ async function responseError(response2) {
11647
11841
  }
11648
11842
  async function credentialCommand(parsed, deps = {}) {
11649
11843
  const action2 = parsed.positionals[1] ?? "list";
11650
- if (action2 !== "list" && action2 !== "revoke") {
11651
- throw new Error(`unknown credentials action "${action2}". Try "odla-ai credentials list".`);
11652
- }
11844
+ if (action2 !== "list" && action2 !== "revoke") rejectWord(["credentials"], action2);
11653
11845
  assertArgs(parsed, ["config", "env", "all", "json", "token", "email", "open"], action2 === "revoke" ? 3 : 2);
11654
11846
  const cfg = await loadProjectConfig(stringOpt(parsed.options.config) ?? "odla.config.mjs");
11655
11847
  const doFetch = deps.fetch ?? fetch;
@@ -11697,6 +11889,7 @@ var init_credential_command = __esm({
11697
11889
  init_config();
11698
11890
  init_redact();
11699
11891
  init_token();
11892
+ init_surface();
11700
11893
  }
11701
11894
  });
11702
11895
 
@@ -12697,7 +12890,7 @@ async function discussCommand(parsed, deps = {}) {
12697
12890
  assertArgs(parsed, ALLOWED, 3);
12698
12891
  const action2 = parsed.positionals[1];
12699
12892
  const id2 = parsed.positionals[2];
12700
- if (!action2) throw new Error('"discuss" needs an action. Run "odla-ai help".');
12893
+ if (!acceptedAfter(["discuss"]).includes(action2 ?? "")) rejectWord(["discuss"], action2);
12701
12894
  const ctx = await buildContext(parsed, deps);
12702
12895
  switch (action2) {
12703
12896
  case "groups":
@@ -12721,7 +12914,7 @@ async function discussCommand(parsed, deps = {}) {
12721
12914
  return;
12722
12915
  }
12723
12916
  default:
12724
- throw new Error(`unknown discuss action "${action2}". Run "odla-ai help".`);
12917
+ rejectWord(["discuss"], action2);
12725
12918
  }
12726
12919
  }
12727
12920
  var ALLOWED;
@@ -12734,6 +12927,7 @@ var init_discuss_command = __esm({
12734
12927
  init_discuss_actions();
12735
12928
  init_discuss_watch();
12736
12929
  init_token();
12930
+ init_surface();
12737
12931
  ALLOWED = [
12738
12932
  "config",
12739
12933
  "token",
@@ -13722,7 +13916,7 @@ async function pmCommand(parsed, deps = {}) {
13722
13916
  assertArgs(parsed, COMMON_OPTIONS, 4);
13723
13917
  return pmProjectUse(await buildContext2(parsed, deps), requireId2(parsed.positionals[3], action3));
13724
13918
  }
13725
- throw new Error(`unknown pm project action "${action3}". Try list|add|use.`);
13919
+ rejectWord(["pm", "project"], action3);
13726
13920
  }
13727
13921
  if (word === "next") {
13728
13922
  assertArgs(parsed, [...COMMON_OPTIONS, "app", "project", "verbose"], 2);
@@ -13753,10 +13947,10 @@ async function pmCommand(parsed, deps = {}) {
13753
13947
  return pmHandoff(await buildContext2(parsed, deps), parsed);
13754
13948
  }
13755
13949
  const entity = ALIASES[word];
13756
- 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);
13757
13951
  const requestedAction = parsed.positionals[2] ?? "list";
13758
13952
  const action2 = canonicalAction(requestedAction);
13759
- 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);
13760
13954
  assertArgs(parsed, allowedOptions(entity, action2), 4);
13761
13955
  if ((action2 === "ready" || action2 === "claim" || action2 === "release") && entity !== "task") {
13762
13956
  throw new Error(`pm ${action2} is only valid for tasks`);
@@ -13809,6 +14003,7 @@ var init_pm_command = __esm({
13809
14003
  init_pm_watch();
13810
14004
  init_pm_project_actions();
13811
14005
  init_pm_project_context();
14006
+ init_surface();
13812
14007
  ALIASES = {
13813
14008
  goal: "goal",
13814
14009
  conformance: "goal",
@@ -13918,12 +14113,7 @@ async function platformCommand(parsed, deps = {}) {
13918
14113
  if (action2 === "status") {
13919
14114
  return platformStatus(parsed, deps);
13920
14115
  }
13921
- throw new Error(
13922
- `unknown platform action "${[
13923
- action2,
13924
- parsed.positionals[2]
13925
- ].filter(Boolean).join(" ")}". Try "odla-ai platform status --json".`
13926
- );
14116
+ rejectWord(["platform"], action2);
13927
14117
  }
13928
14118
  async function platformStatus(parsed, deps) {
13929
14119
  assertArgs(
@@ -13989,6 +14179,7 @@ var init_platform_command = __esm({
13989
14179
  init_argv();
13990
14180
  init_operator_context();
13991
14181
  init_platform_output();
14182
+ init_surface();
13992
14183
  }
13993
14184
  });
13994
14185
 
@@ -14282,9 +14473,7 @@ async function o11yCommand(parsed, deps = {}) {
14282
14473
  );
14283
14474
  const action2 = parsed.positionals[1];
14284
14475
  if (action2 !== "status") {
14285
- throw new Error(
14286
- `unknown o11y action "${action2 ?? ""}". Try "odla-ai o11y status --json".`
14287
- );
14476
+ rejectWord(["o11y"], action2);
14288
14477
  }
14289
14478
  const minutes = statusMinutes(
14290
14479
  numberOpt(parsed.options.minutes, "--minutes") ?? 60
@@ -14420,6 +14609,7 @@ var init_o11y_command = __esm({
14420
14609
  init_o11y_verdict();
14421
14610
  init_o11y_output();
14422
14611
  init_token();
14612
+ init_surface();
14423
14613
  }
14424
14614
  });
14425
14615
 
@@ -14540,9 +14730,7 @@ var init_monitoring_config = __esm({
14540
14730
  async function monitorCommand(parsed, deps = {}) {
14541
14731
  assertArgs(parsed, OPTIONS, 3);
14542
14732
  const action2 = parsed.positionals[1] ?? "status";
14543
- if (!["plan", "apply", "run", "status", "incidents", "report"].includes(action2)) {
14544
- throw new Error(`unknown monitor action "${action2}". Try "odla-ai monitor status --json".`);
14545
- }
14733
+ if (!acceptedAfter(["monitor"]).includes(action2)) rejectWord(["monitor"], action2);
14546
14734
  const context = await resolveOperatorContext(parsed, {
14547
14735
  allowMissingConfig: action2 !== "plan" && action2 !== "apply",
14548
14736
  requireApp: true
@@ -14696,6 +14884,7 @@ var init_monitor_command = __esm({
14696
14884
  init_monitoring_config();
14697
14885
  init_operator_context();
14698
14886
  init_token();
14887
+ init_surface();
14699
14888
  OPTIONS = [
14700
14889
  "config",
14701
14890
  "context",
@@ -15245,183 +15434,6 @@ var init_provision = __esm({
15245
15434
  }
15246
15435
  });
15247
15436
 
15248
- // src/surface.ts
15249
- function acceptedAfter(path) {
15250
- let node = COMMAND_SURFACE;
15251
- for (const word of path) {
15252
- node = node?.[word];
15253
- if (!node) return [];
15254
- }
15255
- return Object.keys(node).sort();
15256
- }
15257
- function validateInvocation(words2) {
15258
- let node = COMMAND_SURFACE;
15259
- const walked = [];
15260
- for (const word of words2) {
15261
- if (Object.keys(node).length === 0) return null;
15262
- const next = node[word];
15263
- if (!next) return { validPrefix: walked.join(" "), word, accepted: Object.keys(node).sort() };
15264
- walked.push(word);
15265
- node = next;
15266
- }
15267
- return null;
15268
- }
15269
- function describeProblem(problem) {
15270
- const where = problem.validPrefix ? `after "${problem.validPrefix}"` : "as a command";
15271
- return `"${problem.word}" is not accepted ${where} \u2014 try: ${problem.accepted.join(", ")}`;
15272
- }
15273
- function invocationPath(words2) {
15274
- let node = COMMAND_SURFACE;
15275
- const path = [];
15276
- for (const word of words2) {
15277
- const next = node[word];
15278
- if (!next) break;
15279
- path.push(word);
15280
- node = next;
15281
- if (Object.keys(node).length === 0) break;
15282
- }
15283
- return path;
15284
- }
15285
- var PM_ACTIONS, PM_TASK_ACTIONS, PM_ENTITIES, COMMAND_SURFACE;
15286
- var init_surface = __esm({
15287
- "src/surface.ts"() {
15288
- "use strict";
15289
- init_cjs_shims();
15290
- PM_ACTIONS = {
15291
- list: {},
15292
- add: {},
15293
- create: {},
15294
- get: {},
15295
- set: {},
15296
- update: {},
15297
- status: {},
15298
- move: {},
15299
- done: {},
15300
- comment: {},
15301
- comments: {},
15302
- ref: {},
15303
- rm: {},
15304
- delete: {}
15305
- };
15306
- PM_TASK_ACTIONS = {
15307
- ...PM_ACTIONS,
15308
- ready: {},
15309
- claim: {},
15310
- release: {}
15311
- };
15312
- PM_ENTITIES = {
15313
- ...Object.fromEntries(
15314
- ["goal", "conformance", "decision", "bug"].map((entity) => [entity, PM_ACTIONS])
15315
- ),
15316
- task: PM_TASK_ACTIONS,
15317
- kanban: PM_TASK_ACTIONS
15318
- };
15319
- COMMAND_SURFACE = {
15320
- agent: { jobs: {}, retry: {} },
15321
- ai: { models: {} },
15322
- admin: {
15323
- ai: {
15324
- show: {},
15325
- set: {},
15326
- credentials: {},
15327
- models: {},
15328
- usage: {},
15329
- audit: {},
15330
- credential: { set: {} }
15331
- }
15332
- },
15333
- app: {
15334
- archive: {},
15335
- restore: {},
15336
- export: {},
15337
- import: {},
15338
- rename: {},
15339
- "refresh-sandbox": {},
15340
- "go-live": {},
15341
- promote: {},
15342
- owners: { list: {}, add: {}, remove: {} }
15343
- },
15344
- auth: { login: {} },
15345
- brand: { design: { unpack: {} } },
15346
- bug: { create: {}, list: {}, report: {} },
15347
- calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
15348
- capabilities: {},
15349
- code: {
15350
- connect: {},
15351
- grant: { request: {}, list: {}, approve: {}, revoke: {} },
15352
- repository: { show: {}, list: {}, bind: {} }
15353
- },
15354
- config: { diff: {}, plan: {}, apply: {} },
15355
- context: { show: {}, list: {}, save: {}, remove: {} },
15356
- credentials: { list: {}, revoke: {} },
15357
- device: { enroll: {}, list: {}, revoke: {} },
15358
- // `watch`, `read`, `reply`, and `resolve` take a topic id from there on.
15359
- discuss: {
15360
- groups: {},
15361
- list: {},
15362
- topics: {},
15363
- read: {},
15364
- post: {},
15365
- reply: {},
15366
- resolve: {},
15367
- who: {},
15368
- watch: {}
15369
- },
15370
- doctor: {},
15371
- help: {},
15372
- init: {},
15373
- monitor: { plan: {}, apply: {}, run: {}, status: {}, incidents: {}, report: {} },
15374
- o11y: { status: {} },
15375
- operations: { get: {}, wait: {} },
15376
- platform: {
15377
- status: {}
15378
- },
15379
- pm: {
15380
- ...PM_ENTITIES,
15381
- project: { list: {}, add: {}, create: {}, use: {} },
15382
- handoff: {},
15383
- next: {},
15384
- start: {},
15385
- watch: {}
15386
- },
15387
- provision: {},
15388
- runbook: {
15389
- ask: {},
15390
- search: {},
15391
- impact: {},
15392
- list: {},
15393
- get: {},
15394
- cat: {},
15395
- new: {},
15396
- edit: {},
15397
- comment: {},
15398
- import: {},
15399
- visibility: {},
15400
- publish: {},
15401
- archive: {},
15402
- history: {},
15403
- revert: {},
15404
- rm: {},
15405
- lint: {}
15406
- },
15407
- secrets: { push: {}, status: {}, set: {}, "set-clerk-key": {} },
15408
- security: {
15409
- plan: {},
15410
- sources: {},
15411
- run: {},
15412
- status: {},
15413
- report: {},
15414
- github: { connect: {}, disconnect: {} }
15415
- },
15416
- setup: {},
15417
- skill: { install: {} },
15418
- smoke: {},
15419
- version: {},
15420
- whoami: {}
15421
- };
15422
- }
15423
- });
15424
-
15425
15437
  // src/record.ts
15426
15438
  function recordInvocation(parsed) {
15427
15439
  const file = import_node_process18.default.env.ODLA_CLI_RECORD;
@@ -15529,6 +15541,7 @@ async function deviceCommand(parsed, deps) {
15529
15541
  "wait"
15530
15542
  ], 3);
15531
15543
  const action2 = parsed.positionals[1] ?? "";
15544
+ if (!acceptedAfter(["device"]).includes(action2)) rejectWord(["device"], action2);
15532
15545
  const out = deps.stdout ?? console;
15533
15546
  const doFetch = deps.fetch ?? fetch;
15534
15547
  const cfg = await loadProjectConfig(stringOpt(parsed.options.config));
@@ -15536,7 +15549,7 @@ async function deviceCommand(parsed, deps) {
15536
15549
  if (action2 === "enroll") return enroll(parsed, deps, cfg, doFetch, out, json);
15537
15550
  if (action2 === "list") return list2(parsed, deps, cfg, doFetch, out, json);
15538
15551
  if (action2 === "revoke") return revoke(parsed, deps, cfg, doFetch, out, json);
15539
- throw new Error('odla-ai device expects "enroll", "list", or "revoke"');
15552
+ rejectWord(["device"], action2);
15540
15553
  }
15541
15554
  async function enroll(parsed, deps, cfg, doFetch, out, json) {
15542
15555
  const name = stringOpt(parsed.options.name) ?? defaultDeviceName();
@@ -15706,6 +15719,7 @@ var init_device_command = __esm({
15706
15719
  init_device_session();
15707
15720
  init_config();
15708
15721
  init_operator_context();
15722
+ init_surface();
15709
15723
  }
15710
15724
  });
15711
15725
 
@@ -16494,6 +16508,7 @@ async function buildContext3(parsed, deps, action2) {
16494
16508
  async function runbookCommand(parsed, deps = {}) {
16495
16509
  const action2 = parsed.positionals[1] ?? "list";
16496
16510
  assertArgs(parsed, ALLOWED2, action2 === "ask" || action2 === "search" ? 64 : 4);
16511
+ if (!acceptedAfter(["runbook"]).includes(action2)) rejectWord(["runbook"], action2);
16497
16512
  const ctx = await buildContext3(parsed, deps, action2);
16498
16513
  const slug = parsed.positionals[2];
16499
16514
  switch (action2) {
@@ -16583,7 +16598,7 @@ async function runbookCommand(parsed, deps = {}) {
16583
16598
  case "rm":
16584
16599
  return runbookRemove(ctx, requireSlug(slug, "rm"));
16585
16600
  default:
16586
- throw new Error(`unknown runbook action "${action2}". Try ${acceptedAfter(["runbook"]).join(", ")}.`);
16601
+ rejectWord(["runbook"], action2);
16587
16602
  }
16588
16603
  }
16589
16604
  var ALLOWED2, WRITES2;
@@ -17292,9 +17307,7 @@ async function securityCommand(parsed, dependencies) {
17292
17307
  else printHostedReport(context.stdout, report5);
17293
17308
  return;
17294
17309
  }
17295
- if (sub !== "run") {
17296
- throw new Error('unknown security command. Try "odla-ai security plan", "security sources", or "security run".');
17297
- }
17310
+ if (sub !== "run") rejectWord(["security"], sub);
17298
17311
  const sourceId = stringOpt(parsed.options.source);
17299
17312
  if (sourceId) await runSourceSecurityCommand(parsed, dependencies, sourceId);
17300
17313
  else await runLocalSecurityCommand(parsed, dependencies);
@@ -17311,9 +17324,7 @@ async function githubSecurityCommand(parsed, dependencies) {
17311
17324
  stringOpt(parsed.options.env)
17312
17325
  );
17313
17326
  }
17314
- if (action2 !== "connect") {
17315
- throw new Error('unknown security github command. Try "odla-ai security github connect".');
17316
- }
17327
+ if (action2 !== "connect") rejectWord(["security", "github"], action2);
17317
17328
  assertArgs(parsed, ["config", "env", "platform", "repo", "email", "open"], 3);
17318
17329
  await requireStudioHuman(
17319
17330
  stringOpt(parsed.options.config) ?? "odla.config.mjs",
@@ -17369,6 +17380,7 @@ var init_security_command = __esm({
17369
17380
  init_security_run_command();
17370
17381
  init_security_hosted();
17371
17382
  init_human_session();
17383
+ init_surface();
17372
17384
  }
17373
17385
  });
17374
17386
 
@@ -17473,6 +17485,7 @@ async function dispatchCli(argv2, dependencies) {
17473
17485
  }
17474
17486
  if (command === "bug") {
17475
17487
  const action2 = parsed.positionals[1] ?? "list";
17488
+ if (!acceptedAfter(["bug"]).includes(action2)) rejectWord(["bug"], action2);
17476
17489
  const canonical2 = action2 === "report" || action2 === "create" ? "add" : action2;
17477
17490
  await pmCommand({
17478
17491
  ...parsed,
@@ -17501,7 +17514,7 @@ async function dispatchCli(argv2, dependencies) {
17501
17514
  return;
17502
17515
  }
17503
17516
  if (await projectCommand(command, parsed, runtime)) return;
17504
- throw new Error(`unknown command "${command}". Run "odla-ai help".`);
17517
+ rejectWord([], command);
17505
17518
  }
17506
17519
  async function provisionCommand(parsed, dependencies) {
17507
17520
  assertArgs(parsed, [
@@ -17545,7 +17558,7 @@ async function provisionCommand(parsed, dependencies) {
17545
17558
  async function calendarCommand(parsed, dependencies) {
17546
17559
  const sub = parsed.positionals[1];
17547
17560
  if (sub !== "status" && sub !== "calendars" && sub !== "connect" && sub !== "disconnect") {
17548
- throw new Error(`unknown calendar subcommand "${sub ?? ""}". Try "odla-ai calendar status --env dev".`);
17561
+ rejectWord(["calendar"], sub);
17549
17562
  }
17550
17563
  assertArgs(parsed, ["config", "env", "json", "token", "email", "open", "yes"], 2);
17551
17564
  if (sub !== "status" && sub !== "calendars" && parsed.options.json !== void 0) throw new Error(`--json is supported only by calendar status/calendars`);
@@ -17599,6 +17612,7 @@ var init_cli = __esm({
17599
17612
  init_runbook_command();
17600
17613
  init_security_command();
17601
17614
  init_whoami_command();
17615
+ init_surface();
17602
17616
  init_exit_code();
17603
17617
  }
17604
17618
  });