@usenaive-sdk/cli 0.15.0 → 1.0.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.
Files changed (278) hide show
  1. package/README.md +43 -354
  2. package/dist/index.js +51775 -502
  3. package/package.json +22 -18
  4. package/dist/client.d.ts +0 -108
  5. package/dist/client.js +0 -458
  6. package/dist/client.js.map +0 -1
  7. package/dist/commands/account-kits.d.ts +0 -2
  8. package/dist/commands/account-kits.js +0 -71
  9. package/dist/commands/account-kits.js.map +0 -1
  10. package/dist/commands/aeo.d.ts +0 -2
  11. package/dist/commands/aeo.js +0 -451
  12. package/dist/commands/aeo.js.map +0 -1
  13. package/dist/commands/agent-profiles.d.ts +0 -11
  14. package/dist/commands/agent-profiles.js +0 -120
  15. package/dist/commands/agent-profiles.js.map +0 -1
  16. package/dist/commands/agents.d.ts +0 -2
  17. package/dist/commands/agents.js +0 -916
  18. package/dist/commands/agents.js.map +0 -1
  19. package/dist/commands/app-data.d.ts +0 -2
  20. package/dist/commands/app-data.js +0 -445
  21. package/dist/commands/app-data.js.map +0 -1
  22. package/dist/commands/approvals.d.ts +0 -2
  23. package/dist/commands/approvals.js +0 -242
  24. package/dist/commands/approvals.js.map +0 -1
  25. package/dist/commands/apps.d.ts +0 -9
  26. package/dist/commands/apps.js +0 -844
  27. package/dist/commands/apps.js.map +0 -1
  28. package/dist/commands/audio.d.ts +0 -9
  29. package/dist/commands/audio.js +0 -391
  30. package/dist/commands/audio.js.map +0 -1
  31. package/dist/commands/auth.d.ts +0 -31
  32. package/dist/commands/auth.js +0 -428
  33. package/dist/commands/auth.js.map +0 -1
  34. package/dist/commands/billing.d.ts +0 -2
  35. package/dist/commands/billing.js +0 -245
  36. package/dist/commands/billing.js.map +0 -1
  37. package/dist/commands/brain.d.ts +0 -2
  38. package/dist/commands/brain.js +0 -1300
  39. package/dist/commands/brain.js.map +0 -1
  40. package/dist/commands/browser.d.ts +0 -2
  41. package/dist/commands/browser.js +0 -378
  42. package/dist/commands/browser.js.map +0 -1
  43. package/dist/commands/business.d.ts +0 -2
  44. package/dist/commands/business.js +0 -566
  45. package/dist/commands/business.js.map +0 -1
  46. package/dist/commands/cards.d.ts +0 -2
  47. package/dist/commands/cards.js +0 -419
  48. package/dist/commands/cards.js.map +0 -1
  49. package/dist/commands/ceo.d.ts +0 -2
  50. package/dist/commands/ceo.js +0 -369
  51. package/dist/commands/ceo.js.map +0 -1
  52. package/dist/commands/clone.d.ts +0 -2
  53. package/dist/commands/clone.js +0 -203
  54. package/dist/commands/clone.js.map +0 -1
  55. package/dist/commands/companies.d.ts +0 -10
  56. package/dist/commands/companies.js +0 -283
  57. package/dist/commands/companies.js.map +0 -1
  58. package/dist/commands/company-data.d.ts +0 -2
  59. package/dist/commands/company-data.js +0 -168
  60. package/dist/commands/company-data.js.map +0 -1
  61. package/dist/commands/compute.d.ts +0 -2
  62. package/dist/commands/compute.js +0 -498
  63. package/dist/commands/compute.js.map +0 -1
  64. package/dist/commands/config.d.ts +0 -2
  65. package/dist/commands/config.js +0 -36
  66. package/dist/commands/config.js.map +0 -1
  67. package/dist/commands/connections.d.ts +0 -8
  68. package/dist/commands/connections.js +0 -137
  69. package/dist/commands/connections.js.map +0 -1
  70. package/dist/commands/cron-jobs.d.ts +0 -2
  71. package/dist/commands/cron-jobs.js +0 -245
  72. package/dist/commands/cron-jobs.js.map +0 -1
  73. package/dist/commands/domains.d.ts +0 -2
  74. package/dist/commands/domains.js +0 -474
  75. package/dist/commands/domains.js.map +0 -1
  76. package/dist/commands/ecommerce.d.ts +0 -2
  77. package/dist/commands/ecommerce.js +0 -277
  78. package/dist/commands/ecommerce.js.map +0 -1
  79. package/dist/commands/email.d.ts +0 -2
  80. package/dist/commands/email.js +0 -336
  81. package/dist/commands/email.js.map +0 -1
  82. package/dist/commands/employees.d.ts +0 -2
  83. package/dist/commands/employees.js +0 -190
  84. package/dist/commands/employees.js.map +0 -1
  85. package/dist/commands/env.d.ts +0 -7
  86. package/dist/commands/env.js +0 -112
  87. package/dist/commands/env.js.map +0 -1
  88. package/dist/commands/formation.d.ts +0 -2
  89. package/dist/commands/formation.js +0 -273
  90. package/dist/commands/formation.js.map +0 -1
  91. package/dist/commands/identity.d.ts +0 -2
  92. package/dist/commands/identity.js +0 -110
  93. package/dist/commands/identity.js.map +0 -1
  94. package/dist/commands/images.d.ts +0 -2
  95. package/dist/commands/images.js +0 -371
  96. package/dist/commands/images.js.map +0 -1
  97. package/dist/commands/init.d.ts +0 -5
  98. package/dist/commands/init.js +0 -43
  99. package/dist/commands/init.js.map +0 -1
  100. package/dist/commands/jobs.d.ts +0 -2
  101. package/dist/commands/jobs.js +0 -144
  102. package/dist/commands/jobs.js.map +0 -1
  103. package/dist/commands/keys.d.ts +0 -2
  104. package/dist/commands/keys.js +0 -156
  105. package/dist/commands/keys.js.map +0 -1
  106. package/dist/commands/legacy-auth.d.ts +0 -27
  107. package/dist/commands/legacy-auth.js +0 -64
  108. package/dist/commands/legacy-auth.js.map +0 -1
  109. package/dist/commands/llm.d.ts +0 -2
  110. package/dist/commands/llm.js +0 -106
  111. package/dist/commands/llm.js.map +0 -1
  112. package/dist/commands/login.d.ts +0 -2
  113. package/dist/commands/login.js +0 -65
  114. package/dist/commands/login.js.map +0 -1
  115. package/dist/commands/logout.d.ts +0 -7
  116. package/dist/commands/logout.js +0 -55
  117. package/dist/commands/logout.js.map +0 -1
  118. package/dist/commands/logs.d.ts +0 -2
  119. package/dist/commands/logs.js +0 -42
  120. package/dist/commands/logs.js.map +0 -1
  121. package/dist/commands/loops.d.ts +0 -2
  122. package/dist/commands/loops.js +0 -260
  123. package/dist/commands/loops.js.map +0 -1
  124. package/dist/commands/media.d.ts +0 -2
  125. package/dist/commands/media.js +0 -183
  126. package/dist/commands/media.js.map +0 -1
  127. package/dist/commands/memory.d.ts +0 -2
  128. package/dist/commands/memory.js +0 -151
  129. package/dist/commands/memory.js.map +0 -1
  130. package/dist/commands/mobile.d.ts +0 -4
  131. package/dist/commands/mobile.js +0 -382
  132. package/dist/commands/mobile.js.map +0 -1
  133. package/dist/commands/module.d.ts +0 -8
  134. package/dist/commands/module.js +0 -48
  135. package/dist/commands/module.js.map +0 -1
  136. package/dist/commands/objectives.d.ts +0 -2
  137. package/dist/commands/objectives.js +0 -243
  138. package/dist/commands/objectives.js.map +0 -1
  139. package/dist/commands/payments.d.ts +0 -2
  140. package/dist/commands/payments.js +0 -131
  141. package/dist/commands/payments.js.map +0 -1
  142. package/dist/commands/people.d.ts +0 -2
  143. package/dist/commands/people.js +0 -197
  144. package/dist/commands/people.js.map +0 -1
  145. package/dist/commands/phone.d.ts +0 -2
  146. package/dist/commands/phone.js +0 -289
  147. package/dist/commands/phone.js.map +0 -1
  148. package/dist/commands/projects.d.ts +0 -13
  149. package/dist/commands/projects.js +0 -220
  150. package/dist/commands/projects.js.map +0 -1
  151. package/dist/commands/queue.d.ts +0 -2
  152. package/dist/commands/queue.js +0 -121
  153. package/dist/commands/queue.js.map +0 -1
  154. package/dist/commands/register.d.ts +0 -2
  155. package/dist/commands/register.js +0 -80
  156. package/dist/commands/register.js.map +0 -1
  157. package/dist/commands/report.d.ts +0 -17
  158. package/dist/commands/report.js +0 -109
  159. package/dist/commands/report.js.map +0 -1
  160. package/dist/commands/sandbox.d.ts +0 -2
  161. package/dist/commands/sandbox.js +0 -558
  162. package/dist/commands/sandbox.js.map +0 -1
  163. package/dist/commands/search.d.ts +0 -2
  164. package/dist/commands/search.js +0 -223
  165. package/dist/commands/search.js.map +0 -1
  166. package/dist/commands/seo.d.ts +0 -2
  167. package/dist/commands/seo.js +0 -528
  168. package/dist/commands/seo.js.map +0 -1
  169. package/dist/commands/sessions.d.ts +0 -6
  170. package/dist/commands/sessions.js +0 -109
  171. package/dist/commands/sessions.js.map +0 -1
  172. package/dist/commands/social-data.d.ts +0 -2
  173. package/dist/commands/social-data.js +0 -245
  174. package/dist/commands/social-data.js.map +0 -1
  175. package/dist/commands/social.d.ts +0 -2
  176. package/dist/commands/social.js +0 -407
  177. package/dist/commands/social.js.map +0 -1
  178. package/dist/commands/status.d.ts +0 -2
  179. package/dist/commands/status.js +0 -73
  180. package/dist/commands/status.js.map +0 -1
  181. package/dist/commands/support.d.ts +0 -2
  182. package/dist/commands/support.js +0 -134
  183. package/dist/commands/support.js.map +0 -1
  184. package/dist/commands/tasks.d.ts +0 -2
  185. package/dist/commands/tasks.js +0 -409
  186. package/dist/commands/tasks.js.map +0 -1
  187. package/dist/commands/teams.d.ts +0 -69
  188. package/dist/commands/teams.js +0 -503
  189. package/dist/commands/teams.js.map +0 -1
  190. package/dist/commands/trading.d.ts +0 -2
  191. package/dist/commands/trading.js +0 -333
  192. package/dist/commands/trading.js.map +0 -1
  193. package/dist/commands/up.d.ts +0 -551
  194. package/dist/commands/up.js +0 -1149
  195. package/dist/commands/up.js.map +0 -1
  196. package/dist/commands/usage.d.ts +0 -2
  197. package/dist/commands/usage.js +0 -73
  198. package/dist/commands/usage.js.map +0 -1
  199. package/dist/commands/use.d.ts +0 -12
  200. package/dist/commands/use.js +0 -96
  201. package/dist/commands/use.js.map +0 -1
  202. package/dist/commands/users.d.ts +0 -10
  203. package/dist/commands/users.js +0 -82
  204. package/dist/commands/users.js.map +0 -1
  205. package/dist/commands/vault.d.ts +0 -2
  206. package/dist/commands/vault.js +0 -101
  207. package/dist/commands/vault.js.map +0 -1
  208. package/dist/commands/verification.d.ts +0 -2
  209. package/dist/commands/verification.js +0 -152
  210. package/dist/commands/verification.js.map +0 -1
  211. package/dist/commands/verify-email.d.ts +0 -19
  212. package/dist/commands/verify-email.js +0 -112
  213. package/dist/commands/verify-email.js.map +0 -1
  214. package/dist/commands/video.d.ts +0 -2
  215. package/dist/commands/video.js +0 -417
  216. package/dist/commands/video.js.map +0 -1
  217. package/dist/commands/voice.d.ts +0 -2
  218. package/dist/commands/voice.js +0 -516
  219. package/dist/commands/voice.js.map +0 -1
  220. package/dist/commands/wallet.d.ts +0 -2
  221. package/dist/commands/wallet.js +0 -244
  222. package/dist/commands/wallet.js.map +0 -1
  223. package/dist/commands/webhooks.d.ts +0 -13
  224. package/dist/commands/webhooks.js +0 -119
  225. package/dist/commands/webhooks.js.map +0 -1
  226. package/dist/commands/whoami.d.ts +0 -2
  227. package/dist/commands/whoami.js +0 -53
  228. package/dist/commands/whoami.js.map +0 -1
  229. package/dist/config.d.ts +0 -42
  230. package/dist/config.js +0 -83
  231. package/dist/config.js.map +0 -1
  232. package/dist/index.d.ts +0 -3
  233. package/dist/index.js.map +0 -1
  234. package/dist/lib/brain-select.d.ts +0 -193
  235. package/dist/lib/brain-select.js +0 -403
  236. package/dist/lib/brain-select.js.map +0 -1
  237. package/dist/lib/cli-validation.d.ts +0 -12
  238. package/dist/lib/cli-validation.js +0 -46
  239. package/dist/lib/cli-validation.js.map +0 -1
  240. package/dist/lib/config-errors.d.ts +0 -4
  241. package/dist/lib/config-errors.js +0 -13
  242. package/dist/lib/config-errors.js.map +0 -1
  243. package/dist/lib/deprecation.d.ts +0 -158
  244. package/dist/lib/deprecation.js +0 -140
  245. package/dist/lib/deprecation.js.map +0 -1
  246. package/dist/lib/duration.d.ts +0 -8
  247. package/dist/lib/duration.js +0 -11
  248. package/dist/lib/duration.js.map +0 -1
  249. package/dist/lib/http-response.d.ts +0 -11
  250. package/dist/lib/http-response.js +0 -96
  251. package/dist/lib/http-response.js.map +0 -1
  252. package/dist/lib/pricing.d.ts +0 -132
  253. package/dist/lib/pricing.js +0 -148
  254. package/dist/lib/pricing.js.map +0 -1
  255. package/dist/lib/starter-config.d.ts +0 -13
  256. package/dist/lib/starter-config.js +0 -71
  257. package/dist/lib/starter-config.js.map +0 -1
  258. package/dist/lib/stream-frames.d.ts +0 -114
  259. package/dist/lib/stream-frames.js +0 -169
  260. package/dist/lib/stream-frames.js.map +0 -1
  261. package/dist/lib/teams-catalog.d.ts +0 -141
  262. package/dist/lib/teams-catalog.js +0 -841
  263. package/dist/lib/teams-catalog.js.map +0 -1
  264. package/dist/lib/unknown-command.d.ts +0 -71
  265. package/dist/lib/unknown-command.js +0 -132
  266. package/dist/lib/unknown-command.js.map +0 -1
  267. package/dist/lib/version-check.d.ts +0 -26
  268. package/dist/lib/version-check.js +0 -21
  269. package/dist/lib/version-check.js.map +0 -1
  270. package/dist/output.d.ts +0 -174
  271. package/dist/output.js +0 -435
  272. package/dist/output.js.map +0 -1
  273. package/dist/prompt.d.ts +0 -17
  274. package/dist/prompt.js +0 -70
  275. package/dist/prompt.js.map +0 -1
  276. package/dist/version.d.ts +0 -2
  277. package/dist/version.js +0 -14
  278. package/dist/version.js.map +0 -1
@@ -1,841 +0,0 @@
1
- /**
2
- * THE ONE DECLARATION SITE for the `naive teams` surface (06 §1.4).
3
- *
4
- * Two design notes shipped two `teams` command sets and neither was a superset of
5
- * the other. Two hand-written lists is exactly the drift a one-declaration rule
6
- * exists to catch, so both are deleted and the union is declared once, here, as
7
- * DATA. `commands/teams.ts` builds the commander tree by walking this array; it
8
- * writes no subcommand of its own. A row added here is a working command, and a
9
- * command that is not a row here does not exist.
10
- *
11
- * ── WHY THIS IS A .ts FILE IN THIS PACKAGE AND NOT A GENERATED ARTIFACT ─────
12
- *
13
- * 06 §1.4 puts the declaration in `@usenaive/agent-contract/capabilities` and has
14
- * a generator emit `packages/cli/src/generated/teams-commands.ts` from it, so the
15
- * CLI subcommand, the MCP tool, the REST path and the openapi operation are all
16
- * one statement. MEASURED: there is no `packages/agent-contract` directory in
17
- * this tree and no generator to run, and this task owns `packages/cli` only. A
18
- * hand-written file that IMITATES a generated one — committed under `generated/`,
19
- * with nothing that regenerates it — is worse than an honest source file: the
20
- * next person edits it and their edit is silently the truth.
21
- *
22
- * So this is the honest intermediate: ONE declaration, inside the package that
23
- * consumes it, shaped so a future generator emits exactly this array. The fields
24
- * a generator would need and the CLI does not (`action`, `rest`, `mcp`,
25
- * `because`, `fenced`, `write`) are carried anyway, so the day the contract
26
- * package exists this file is deleted rather than reconciled.
27
- *
28
- * ── WHY IT MUST BE ONE LIST AND NOT 28 `.command()` CALLS ───────────────────
29
- *
30
- * `src/index.ts` calls `findUnknownSubcommand` BEFORE `parseAsync` and hard-fails
31
- * on an unrecognised subcommand even with `--help` appended. A subcommand list
32
- * that can drift from the thing that answers it therefore makes VALID commands
33
- * fail with `cli_outdated`, rather than degrading. One array, walked once, cannot
34
- * drift from itself.
35
- */
36
- /* -------------------------------------------------------------------------- */
37
- /* shared flag groups — declared once so two ops cannot spell the same filter */
38
- /* two ways */
39
- /* -------------------------------------------------------------------------- */
40
- const CURSOR = {
41
- flag: "--cursor <seq>",
42
- key: "cursor",
43
- describe: "Resume from this position (a cursor is a POSITION, not a time)",
44
- in: "query",
45
- };
46
- const LIMIT = { flag: "--limit <n>", key: "limit", describe: "Maximum rows", in: "query", int: true };
47
- const STATUS = { flag: "--status <status>", key: "status", describe: "Filter by status", in: "query" };
48
- const ROLE = { flag: "--role <role>", key: "role", describe: "Filter by the declared role of the agent", in: "query" };
49
- const TASK = { flag: "--task <id>", key: "task", describe: "Filter to one task", in: "query" };
50
- /**
51
- * `--because` is a `requiredOption`, never a positional, on every op that widens
52
- * authority or destroys state. A positional reason is unquotable in practice and
53
- * invisible in `--help`; and because commander validates required options before
54
- * any action hook runs, a missing one already produces the CLI's structured
55
- * `invalid_input` envelope for free (index.ts's CommanderError catch).
56
- */
57
- const BECAUSE = {
58
- flag: "--because <why>",
59
- key: "because",
60
- describe: "Why you are doing this. Recorded on the decision. REQUIRED",
61
- in: "body",
62
- required: true,
63
- };
64
- /**
65
- * 🔴 THIS TEXT SAID "HERMES ONLY" AFTER THAT STOPPED BEING TRUE, AND THE HELP IS
66
- * THE ONLY ACCOUNT MOST OPERATORS EVER READ.
67
- *
68
- * `unverified` is written by naive's own completion adjudicator onto
69
- * `tasks_mirror`. A durable tenant's board used to come straight from the runtime,
70
- * whose six states (`todo ready doing review done blocked`) carry no such value,
71
- * so exit 3 really was unreachable there — and `naive teams tasks` announced that
72
- * on stderr rather than let a CI job read exit 0 as "attested".
73
- *
74
- * The verification join removed the cause: `durableCard` joins `tasks_mirror` and
75
- * the terminal column is naive's verdict. `commands/teams.ts` deleted the banner
76
- * on exactly that ground and `legacy-commands-still-work.test.ts` asserts exit 3
77
- * on a durable board. This string was left behind, so one binary shipped two
78
- * contradictory accounts of one flag — and the one in `--help` said the gate
79
- * cannot fire, which is the reading that makes an operator stop trusting it.
80
- *
81
- * WHAT IT SAYS NOW IS THE FLAG'S OWN CONTRACT AND NOTHING ABOUT THE LANE. The
82
- * gate asks one question of one field, deliberately (see `teams.ts`: a CLI that
83
- * branched on which runtime answered would be a second place the two lanes could
84
- * diverge). So the caveat that belongs here is the one that is true on BOTH
85
- * lanes: exit 0 means no card on this board reads `unverified`, which is not the
86
- * same claim as "every card was adjudicated" — a board naive never scored reports
87
- * no `unverified` for want of a verdict, not for want of a problem.
88
- */
89
- const FAIL_ON_UNVERIFIED = {
90
- flag: "--fail-on-unverified",
91
- key: "failOnUnverified",
92
- describe: "Exit 3 when a task's terminal state is `unverified` (a THIRD terminal state, not a success). " +
93
- "Both lanes. Exit 0 means no card on this board reads `unverified` — NOT that every card was adjudicated",
94
- in: "client",
95
- };
96
- const BOARD_FILTERS = [STATUS, ROLE, LIMIT, CURSOR];
97
- const TEAM_ARG = { arg: "<team>", name: "team", describe: "The declared team", in: "path" };
98
- /** The base every tenant-scoped path is built on. R-4: the addressing tuple is
99
- * `(company, tenant, team)`, and the tuple is IN THE PATH rather than inferred. */
100
- const BASE = "/v1/teams/{team}/tenants/{tenant}";
101
- export const TEAM_OPS = [
102
- {
103
- cli: "list",
104
- describe: "List the declared teams (the only subcommand that does not take --tenant)",
105
- action: "teams.list",
106
- method: "GET",
107
- path: "/v1/teams",
108
- tenant: false,
109
- fenced: false,
110
- write: false,
111
- mcp: false,
112
- // This said "GET /v1/teams answers 501, so a tool over it could only ever
113
- // refuse". It serves now (migration 065 renamed the JSONB key holding a
114
- // declared team's name), so that reason has expired — but the ANSWER is
115
- // still no, on the ground that outlives the 501: this is the ONE
116
- // company-scoped operation on the surface, and every MCP tool here is
117
- // addressed at a (team, tenant) pair a model is already scoped to. A tool
118
- // that enumerates the company's teams hands a model the names of teams it
119
- // was not pointed at, which is a widening of what the model can see rather
120
- // than a convenience. The operator has the CLI and the REST route.
121
- because: "company-scoped enumeration; every teams tool is scoped to one (team, tenant) pair, and listing the company's other teams would widen what a model can see",
122
- args: [],
123
- flags: [
124
- { flag: "--runtime <kind>", key: "runtime", describe: "durable | hermes", in: "query" },
125
- { flag: "--project <name>", key: "project", describe: "Only teams declared under this project", in: "query" },
126
- ],
127
- },
128
- {
129
- cli: "show",
130
- describe: "Show one team's status for one tenant",
131
- action: "teams.status",
132
- method: "GET",
133
- path: BASE,
134
- tenant: true,
135
- fenced: false,
136
- write: false,
137
- mcp: false,
138
- because: "the MCP surface declares no such tool; a model reads the board, not the team header",
139
- args: [TEAM_ARG],
140
- flags: [],
141
- },
142
- {
143
- cli: "plan",
144
- describe: "The honesty report for one (team, tenant): digests, fence, caps, attestation parity",
145
- action: "teams.plan",
146
- method: "GET",
147
- path: `${BASE}/plan`,
148
- tenant: true,
149
- fenced: false,
150
- write: false,
151
- mcp: false,
152
- because: "the MCP surface declares no such tool; the honesty report is an operator artifact, and its central field (the manifest digest) has no source",
153
- args: [TEAM_ARG],
154
- flags: [
155
- {
156
- flag: "--detailed-exitcode",
157
- key: "detailedExitcode",
158
- describe: "Exit 2 when a diff exists (terraform's convention; exit 1 stays a real error)",
159
- in: "client",
160
- },
161
- ],
162
- },
163
- {
164
- cli: "roster",
165
- describe: "The declared agents of a team, and optionally the edges between them",
166
- action: "teams.roster",
167
- method: "GET",
168
- path: `${BASE}/roster`,
169
- tenant: true,
170
- fenced: false,
171
- write: false,
172
- mcp: false,
173
- because: "the MCP surface declares no such tool; can/approve/view are all null on this build, so it would report an authority it cannot see",
174
- args: [TEAM_ARG],
175
- flags: [{ flag: "--edges", key: "edges", describe: "Include the declared edges", in: "query" }],
176
- },
177
- {
178
- cli: "board",
179
- describe: "The team's board for one tenant",
180
- action: "teams.board",
181
- method: "GET",
182
- path: `${BASE}/board`,
183
- tenant: true,
184
- fenced: false,
185
- write: false,
186
- mcp: "naive_teams_board",
187
- args: [TEAM_ARG],
188
- flags: BOARD_FILTERS,
189
- },
190
- {
191
- cli: "submit",
192
- describe: "Send work to a team. THE verb — one ActionId, one fence, every surface",
193
- action: "teams.submit",
194
- method: "POST",
195
- path: `${BASE}/submit`,
196
- tenant: true,
197
- fenced: true,
198
- write: true,
199
- mcp: false,
200
- because: "the MCP surface declares no such tool; POST …/submit answers 501, and a submit tool is the one verb that must not exist before the route does",
201
- args: [TEAM_ARG, { arg: "<goal>", name: "goal", describe: "What you want done", in: "body" }],
202
- flags: [
203
- { flag: "--task <id>", key: "task", describe: "Attach this goal to an existing task", in: "body" },
204
- {
205
- flag: "--brief <file>",
206
- key: "brief",
207
- describe: "Read the prose brief from a file, or `-` for stdin (prose on argv is mangled by every shell)",
208
- in: "body",
209
- },
210
- { flag: "--attest <claim>", key: "attest", describe: "The claim the result must attest to", in: "body" },
211
- { flag: "--parent <id>", key: "parent", describe: "Parent task id", in: "body" },
212
- {
213
- flag: "--idempotency-key <k>",
214
- key: "idempotencyKey",
215
- describe: "Forwarded as Idempotency-Key. Best-effort today — see `naive teams plan`",
216
- in: "client",
217
- },
218
- ],
219
- },
220
- {
221
- cli: "say",
222
- describe: "Say something into a team session",
223
- action: "teams.say",
224
- method: "POST",
225
- path: `${BASE}/sessions/{channel}/messages`,
226
- tenant: true,
227
- fenced: true,
228
- write: true,
229
- mcp: false,
230
- because: "the CLI `say` posts a SESSION message (…/sessions/{channel}/messages, 501 today); the MCP tool of the nearest name, naive_teams_comment, posts a TASK comment — a different operation, not this one",
231
- args: [TEAM_ARG, { arg: "<text>", name: "text", describe: "What to say", in: "body" }],
232
- flags: [
233
- { flag: "--channel <channel>", key: "channel", describe: "The session channel (default: main)", in: "path" },
234
- { flag: "--as <role>", key: "as", describe: "Speak as a declared role", in: "body" },
235
- ],
236
- },
237
- {
238
- cli: "tasks",
239
- describe: "The board as a task list",
240
- action: "teams.board",
241
- method: "GET",
242
- path: `${BASE}/board`,
243
- tenant: true,
244
- fenced: false,
245
- write: false,
246
- mcp: false,
247
- because: "an alias of `board`; naive_teams_board is the tool",
248
- args: [TEAM_ARG],
249
- flags: [...BOARD_FILTERS, FAIL_ON_UNVERIFIED],
250
- },
251
- {
252
- cli: "task",
253
- describe: "One card on the board",
254
- action: "teams.board",
255
- method: "GET",
256
- path: `${BASE}/board/{card}`,
257
- tenant: true,
258
- fenced: false,
259
- write: false,
260
- mcp: false,
261
- because: "the MCP surface declares no such tool; a model reads one card through naive_teams_board",
262
- args: [TEAM_ARG, { arg: "<id>", name: "card", describe: "The card id", in: "path" }],
263
- flags: [FAIL_ON_UNVERIFIED],
264
- },
265
- {
266
- cli: "unblock",
267
- describe: "Unblock a card",
268
- action: "teams.unblock",
269
- method: "POST",
270
- path: `${BASE}/board/{card}/unblock`,
271
- tenant: true,
272
- fenced: true,
273
- write: true,
274
- mcp: "naive_teams_unblock",
275
- args: [TEAM_ARG, { arg: "<id>", name: "card", describe: "The card id", in: "path" }],
276
- flags: [BECAUSE],
277
- },
278
- {
279
- cli: "runs",
280
- describe: "The team's runs",
281
- action: "teams.runs",
282
- method: "GET",
283
- path: `${BASE}/runs`,
284
- tenant: true,
285
- fenced: false,
286
- write: false,
287
- mcp: false,
288
- because: "the MCP surface declares no such tool; naive_teams_run reads ONE run by id, it does not list them",
289
- args: [TEAM_ARG],
290
- // NOT [TASK, STATUS, …]. The runs handler reads exactly `limit` and
291
- // `cursor`; `?task=` and `?status=` were declared as query flags, sent, and
292
- // silently ignored, so filtering a busy tenant's runs returned the
293
- // unfiltered list with no signal. openapi declares no such parameters
294
- // either. They come back when the route supports them.
295
- flags: [LIMIT, CURSOR],
296
- },
297
- {
298
- cli: "watch",
299
- describe: "Watch a run's transcript as NDJSON — one object per line, resumable",
300
- action: "teams.runs",
301
- method: "GET",
302
- path: `${BASE}/runs/{run}/stream`,
303
- tenant: true,
304
- fenced: false,
305
- write: false,
306
- mcp: false,
307
- because: "the MCP surface declares no such tool; …/runs/{id}/stream answers 501 and MCP has no streaming response",
308
- streams: true,
309
- args: [TEAM_ARG],
310
- flags: [
311
- { flag: "--run <id>", key: "run", describe: "The run to watch (required)", in: "path", required: true },
312
- {
313
- flag: "--follow",
314
- // What it decides is what happens at a TERMINAL frame, not how much
315
- // of the stream is read: without it the command stops at the first
316
- // `stream.end`, with it the connection stays open past it. Both
317
- // modes drain the body — the old behaviour stopped after one
318
- // network chunk, which truncated the transcript at an arbitrary
319
- // point and still exited 0.
320
- key: "follow",
321
- describe: "Keep watching past a terminal frame (default: stop at the first stream.end)",
322
- in: "client",
323
- },
324
- { ...CURSOR, in: "client", describe: "Resume from this seq — sent as Last-Event-ID" },
325
- {
326
- flag: "--fail-on-terminal",
327
- key: "failOnTerminal",
328
- // Of the three, only `failed` is reachable on the durable runtime: a
329
- // durable run's status vocabulary is `running | completed | failed`
330
- // (`runState` in the runtime's naive.ts), so `unverified` and `blocked`
331
- // are hermes states. The flag is not hermes-only the way
332
- // `--fail-on-unverified` is — it keeps a real reach on both — so it is
333
- // narrowed in the text rather than announced at runtime.
334
- describe: "Exit 3 when the final status is unverified, failed or blocked (on the durable runtime only `failed` occurs)",
335
- in: "client",
336
- },
337
- {
338
- flag: "--show-args",
339
- key: "showArgs",
340
- describe: "Print tool arguments instead of args_digest. Requires an interactive terminal",
341
- in: "client",
342
- },
343
- ],
344
- },
345
- {
346
- cli: "events",
347
- describe: "The team's event log",
348
- action: "teams.events",
349
- method: "GET",
350
- path: `${BASE}/events`,
351
- tenant: true,
352
- fenced: false,
353
- write: false,
354
- mcp: false,
355
- because: "the MCP surface declares no such tool",
356
- args: [TEAM_ARG],
357
- flags: [TASK, CURSOR, LIMIT],
358
- },
359
- {
360
- cli: "cost",
361
- describe: "What the team spent",
362
- action: "teams.cost",
363
- method: "GET",
364
- path: `${BASE}/cost`,
365
- tenant: true,
366
- fenced: false,
367
- write: false,
368
- mcp: false,
369
- because: "the MCP surface declares no such tool; spend is an operator surface",
370
- args: [TEAM_ARG],
371
- flags: [
372
- // `action` IS THE ONLY ACCEPTED VALUE. The route throws
373
- // `invalid_input: by must be "action" (the only bucketing this build can
374
- // compute)` for anything else (routes/teams.ts), so the three the describe
375
- // string used to advertise — task | day | role — all 400.
376
- {
377
- flag: "--by <dimension>",
378
- key: "by",
379
- describe: 'action (the only value this build accepts)',
380
- in: "query",
381
- },
382
- // `--period` IS NOT SENT. The route never reads `req.query.period`; the
383
- // window comes from `spendWindow(accountKit.budget)`. A query flag the
384
- // server ignores is the worst shape available — the response looks right
385
- // and answers a different question — so it is `client` and the CLI prints
386
- // the window the server actually used.
387
- {
388
- flag: "--period <period>",
389
- key: "period",
390
- describe: "IGNORED — the window is fixed by the account kit's budget period",
391
- in: "client",
392
- },
393
- ],
394
- },
395
- {
396
- cli: "diagnose",
397
- describe: "Diagnostics for a team that is not behaving",
398
- action: "teams.diagnostics",
399
- method: "GET",
400
- path: `${BASE}/diagnostics`,
401
- tenant: true,
402
- fenced: false,
403
- write: false,
404
- mcp: false,
405
- because: "the MCP surface declares no such tool",
406
- args: [TEAM_ARG],
407
- flags: [{ flag: "--kind <kind>", key: "kind", describe: "Restrict to one diagnostic kind", in: "query" }],
408
- },
409
- {
410
- cli: "sessions",
411
- describe: "The team's sessions",
412
- action: "teams.sessions",
413
- method: "GET",
414
- path: `${BASE}/sessions`,
415
- tenant: true,
416
- fenced: false,
417
- write: false,
418
- mcp: false,
419
- because: "the MCP surface declares no such tool; both session reads answer 501",
420
- args: [TEAM_ARG],
421
- flags: [CURSOR, LIMIT],
422
- },
423
- {
424
- cli: "read",
425
- describe: "Read one session channel",
426
- action: "teams.sessions",
427
- method: "GET",
428
- path: `${BASE}/sessions/{channel}`,
429
- tenant: true,
430
- fenced: false,
431
- write: false,
432
- mcp: false,
433
- because: "the MCP surface declares no such tool; both session reads answer 501",
434
- args: [TEAM_ARG, { arg: "<channel>", name: "channel", describe: "The channel to read", in: "path" }],
435
- flags: [CURSOR, LIMIT],
436
- },
437
- {
438
- cli: "approvals",
439
- describe: "Approvals this team is waiting on",
440
- action: "teams.approvals",
441
- method: "GET",
442
- path: `${BASE}/approvals`,
443
- tenant: true,
444
- fenced: false,
445
- write: false,
446
- mcp: false,
447
- because: "naive_approvals_list is the tool, and it is not a teams tool",
448
- args: [TEAM_ARG],
449
- flags: [STATUS],
450
- },
451
- {
452
- cli: "decide",
453
- describe: "Allow or deny one approval",
454
- action: "teams.approval_decide",
455
- method: "POST",
456
- path: `${BASE}/approvals/{approval}/decide`,
457
- tenant: true,
458
- fenced: true,
459
- write: true,
460
- mcp: false,
461
- because: "an agent may not resolve the gate meant to constrain it — assertHumanResolver refuses an agent key",
462
- args: [TEAM_ARG, { arg: "<id>", name: "approval", describe: "The approval id", in: "path" }],
463
- flags: [
464
- { flag: "--allow", key: "allow", describe: "Allow it", in: "body", field: "decision", value: "approve" },
465
- { flag: "--deny", key: "deny", describe: "Deny it", in: "body", field: "decision", value: "deny" },
466
- BECAUSE,
467
- ],
468
- },
469
- {
470
- cli: "effects",
471
- describe: "Outstanding effects",
472
- action: "teams.effects",
473
- method: "GET",
474
- path: `${BASE}/effects`,
475
- tenant: true,
476
- fenced: false,
477
- write: false,
478
- mcp: false,
479
- because: "the MCP surface declares no such tool; the effects routes answer 501",
480
- args: [TEAM_ARG],
481
- flags: [{ flag: "--state <state>", key: "state", describe: "Filter by effect state", in: "query" }],
482
- },
483
- {
484
- cli: "settle",
485
- describe: "Settle one effect — done, compensated or rejected",
486
- action: "teams.effect_settle",
487
- method: "POST",
488
- path: `${BASE}/effects/{effect}/settle`,
489
- tenant: true,
490
- fenced: true,
491
- write: true,
492
- mcp: false,
493
- because: "the MCP surface declares no such tool; the effects routes answer 501",
494
- args: [TEAM_ARG, { arg: "<id>", name: "effect", describe: "The effect id", in: "path" }],
495
- flags: [
496
- // Same selector shape as `decide`. openapi declares `{decision, because,
497
- // receipt}` here too, with `decision` an unconstrained string — the route
498
- // is a 501 refusal, so nothing narrows the value set yet. The values are
499
- // therefore spelled exactly like their flags, so `--help` and the wire
500
- // cannot drift into two names for one choice.
501
- { flag: "--done", key: "done", describe: "It completed", in: "body", field: "decision", value: "done" },
502
- { flag: "--compensate", key: "compensate", describe: "It was compensated", in: "body", field: "decision", value: "compensate" },
503
- { flag: "--reject", key: "reject", describe: "It was rejected", in: "body", field: "decision", value: "reject" },
504
- { flag: "--receipt <file>", key: "receipt", describe: "Read the receipt from a file, or `-` for stdin", in: "body" },
505
- BECAUSE,
506
- ],
507
- },
508
- {
509
- cli: "schedule",
510
- describe: "Schedule recurring work for a team",
511
- action: "teams.schedule",
512
- method: "POST",
513
- path: `${BASE}/schedule`,
514
- tenant: true,
515
- fenced: true,
516
- write: true,
517
- mcp: false,
518
- // CORRECTED: the second clause was measured false. `POST …/schedule`,
519
- // `GET …/schedule` and `DELETE …/schedule/{id}` all SERVE a durable
520
- // tenant — production answered `success: true` and the runtime held the
521
- // row. They 501 only for a hermes tenant, which is a fact about the
522
- // tenant, not about the route. The reason this is not an MCP tool is
523
- // unchanged and is the real one: a standing permission to spend on a
524
- // timer with no human present is not something an agent should be able
525
- // to grant itself.
526
- because: "the MCP surface declares no such tool, and should not: a schedule is standing permission to spend with nobody watching",
527
- args: [
528
- TEAM_ARG,
529
- { arg: "<cron>", name: "cron", describe: "Cron expression", in: "body" },
530
- { arg: "<goal>", name: "goal", describe: "What to submit on each tick", in: "body" },
531
- ],
532
- flags: [
533
- { flag: "--brief <file>", key: "brief", describe: "Read the prose brief from a file, or `-` for stdin", in: "body" },
534
- // 🔴 `--timezone <tz>` WAS DECLARED HERE AND IS DELETED, NOT MOVED.
535
- //
536
- // It was a body flag the whole chain dropped: this catalog sent it,
537
- // naive's `durableScheduleBody` read `cron`/`goal`/`name` only, and
538
- // vetta's `/cron` head reads `schedule`/`prompt`/`name` only. MEASURED
539
- // on production — `0 4 1 1 *` with `--timezone America/New_York`
540
- // answered `success: true` and the runtime's row came back
541
- // `nextRunAt: 1798776000000`, which is 2027-01-01T04:00:00Z: identical
542
- // to the millisecond to the same expression sent with no zone. The
543
- // operator asked for New York, got UTC, and was told it worked.
544
- //
545
- // The durable runtime cannot honour it — `RecurringSpec` is
546
- // `{cron} | {everySeconds}` with no zone field, and the alarm evaluates
547
- // cron in UTC. A flag `--help` offers is a promise; this one could not
548
- // be kept, so it is withdrawn rather than documented as a no-op. The
549
- // REST route still REFUSES `timezone` with the conversion spelled out,
550
- // for callers that do not come through this CLI.
551
- ],
552
- },
553
- {
554
- // 🔴 THE READ THAT MAKES THE WRITE RECOVERABLE, AND IT WAS NOT A ROW.
555
- //
556
- // `schedule` returns the job id once and `unschedule` is addressed by that
557
- // id. With no listing, an operator who lost the create response owned a
558
- // recurring job they could not name to cancel — and no other subcommand
559
- // (`show`, `plan`, `diagnose`) reports one. The route it calls is the GET
560
- // side of `…/schedule`; the runtime has answered it the whole time
561
- // (`GET /control/{company}/{tenant}/cron` → 200 with the full row,
562
- // measured), and naive's own seam table has declared the translation
563
- // (`DURABLE_ROUTE.schedules`) with nothing calling it.
564
- cli: "schedules",
565
- describe: "What recurs for this team — cadence, next firing, and the model each job is pinned to",
566
- action: "teams.schedule",
567
- method: "GET",
568
- path: `${BASE}/schedule`,
569
- tenant: true,
570
- fenced: false,
571
- write: false,
572
- mcp: false,
573
- // CORRECTED: the second clause was measured false. `POST …/schedule`,
574
- // `GET …/schedule` and `DELETE …/schedule/{id}` all SERVE a durable
575
- // tenant — production answered `success: true` and the runtime held the
576
- // row. They 501 only for a hermes tenant, which is a fact about the
577
- // tenant, not about the route. The reason this is not an MCP tool is
578
- // unchanged and is the real one: a standing permission to spend on a
579
- // timer with no human present is not something an agent should be able
580
- // to grant itself.
581
- because: "the MCP surface declares no such tool, and should not: a schedule is standing permission to spend with nobody watching",
582
- args: [TEAM_ARG],
583
- flags: [],
584
- },
585
- {
586
- cli: "unschedule",
587
- // 06 §1.4 gives `schedule / unschedule` ONE row, one arg list and the path
588
- // glob `…/schedule*`. A DELETE carrying a body to identify its own target is
589
- // the shape that makes a proxy drop the request, so the id is in the path.
590
- describe: "Remove a schedule",
591
- action: "teams.schedule",
592
- method: "DELETE",
593
- path: `${BASE}/schedule/{schedule}`,
594
- tenant: true,
595
- fenced: true,
596
- write: true,
597
- mcp: false,
598
- // CORRECTED: the second clause was measured false. `POST …/schedule`,
599
- // `GET …/schedule` and `DELETE …/schedule/{id}` all SERVE a durable
600
- // tenant — production answered `success: true` and the runtime held the
601
- // row. They 501 only for a hermes tenant, which is a fact about the
602
- // tenant, not about the route. The reason this is not an MCP tool is
603
- // unchanged and is the real one: a standing permission to spend on a
604
- // timer with no human present is not something an agent should be able
605
- // to grant itself.
606
- because: "the MCP surface declares no such tool, and should not: a schedule is standing permission to spend with nobody watching",
607
- args: [TEAM_ARG, { arg: "<id>", name: "schedule", describe: "The schedule id", in: "path" }],
608
- flags: [],
609
- },
610
- {
611
- // 🔴 THIS ROW WAS MISSING, AND IT IS A STEP OF THE CORE LOOP.
612
- //
613
- // `POST /v1/teams/{team}/tenants/{tenant}/apply` has been mounted the whole
614
- // time and no CLI verb reached it — so "declare a team, apply it, submit
615
- // work" had a hole in the middle that only a hand-written curl could cross.
616
- // The other 26 rows were derived from the route table; this one was not,
617
- // because a route with no CLI verb is invisible to a reader checking the CLI
618
- // against itself.
619
- //
620
- // `--expected-digest` is REQUIRED and has no default. Apply exists to raise
621
- // `409 stale_expected_digest` — to refuse when the compiled company has
622
- // moved since you last looked — and an apply that sends whatever digest is
623
- // current cannot ever raise it. A flag defaulted to "whatever you have"
624
- // turns a concurrency check into a rubber stamp. `naive teams plan` prints
625
- // the digest to pass.
626
- cli: "apply",
627
- describe: "Record that an operator applied this exact manifest digest. Refuses a stale one",
628
- action: "teams.apply",
629
- method: "POST",
630
- path: `${BASE}/apply`,
631
- tenant: true,
632
- fenced: true,
633
- write: true,
634
- mcp: false,
635
- because: "applying a manifest is the act that makes a capability change something a human reviewed; an agent applying its own manifest reviews nothing",
636
- args: [TEAM_ARG],
637
- flags: [
638
- {
639
- flag: "--expected-digest <digest>",
640
- key: "expectedDigest",
641
- describe: "REQUIRED. The digest you are applying, from `naive teams plan`. A stale one is refused",
642
- in: "body",
643
- field: "expected_digest",
644
- required: true,
645
- },
646
- // NO `--because`, deliberately, and it is the one fenced op without one.
647
- // The published contract for this operation declares exactly one body
648
- // property — `expected_digest` — and the CLI may not send a key the
649
- // contract does not declare (the gate below enforces that, and it caught
650
- // this row). More importantly the reason would be redundant: the artefact
651
- // that records what an operator approved is the DIGEST, which names the
652
- // exact compiled manifest, and a free-text sentence beside it adds a
653
- // second, weaker account of the same decision.
654
- ],
655
- },
656
- {
657
- cli: "model",
658
- describe: "Change the model a team runs on",
659
- action: "teams.model",
660
- method: "POST",
661
- path: `${BASE}/model`,
662
- tenant: true,
663
- fenced: true,
664
- write: true,
665
- mcp: false,
666
- because: "the MCP surface declares no such tool; a model must not choose its own model",
667
- args: [TEAM_ARG, { arg: "<modelId>", name: "modelId", describe: "The model id", in: "body", field: "model_id" }],
668
- flags: [BECAUSE],
669
- },
670
- {
671
- cli: "stop",
672
- describe: "Stop a team",
673
- action: "teams.stop",
674
- method: "POST",
675
- path: `${BASE}/stop`,
676
- tenant: true,
677
- fenced: true,
678
- write: true,
679
- mcp: false,
680
- because: "the MCP surface declares no such tool; naive_teams_run reads a run, it does not stop one",
681
- args: [TEAM_ARG],
682
- flags: [BECAUSE],
683
- },
684
- {
685
- cli: "migrate",
686
- describe: "Move this tenant onto the durable runtime",
687
- action: "teams.migrate",
688
- method: "POST",
689
- path: `${BASE}/migrate`,
690
- tenant: true,
691
- fenced: true,
692
- write: true,
693
- // An agent must not move its own tenant between runtimes: the cutover
694
- // destroys the legacy coordinates irreversibly. Declared, not omitted, so a
695
- // four-way parity check sees an exemption rather than a hole.
696
- mcp: false,
697
- because: "an agent must not move its own tenant between runtimes; the cutover is irreversible",
698
- args: [TEAM_ARG],
699
- flags: [{ flag: "--dry-run", key: "dryRun", describe: "Report what would move; change nothing", in: "body" }, BECAUSE],
700
- },
701
- {
702
- cli: "rollback",
703
- describe: "Move this tenant back off the durable runtime. DESTROYS the workspace",
704
- action: "teams.rollback",
705
- method: "POST",
706
- path: `${BASE}/rollback`,
707
- tenant: true,
708
- fenced: true,
709
- write: true,
710
- mcp: false,
711
- because: "an agent must not move its own tenant between runtimes; the rollback destroys /data",
712
- args: [TEAM_ARG],
713
- flags: [
714
- {
715
- // A flag a script cannot accidentally satisfy is the honest interlock:
716
- // the rollback re-claims the slot, so /data is gone with it.
717
- flag: "--i-understand-data-loss",
718
- key: "iUnderstandDataLoss",
719
- describe: "Required. The workspace is destroyed by this operation and is not recoverable",
720
- in: "body",
721
- },
722
- BECAUSE,
723
- ],
724
- },
725
- ];
726
- /* -------------------------------------------------------------------------- */
727
- /* pure builders — everything the runner needs, testable without a network */
728
- /* -------------------------------------------------------------------------- */
729
- export class TeamOpError extends Error {
730
- }
731
- /** Every `{placeholder}` this op's path expects. */
732
- export function pathPlaceholders(op) {
733
- return [...op.path.matchAll(/\{([a-zA-Z]+)\}/g)].map((m) => m[1]);
734
- }
735
- /**
736
- * Fill the path. Every value is `encodeURIComponent`'d — a team name or a card id
737
- * that contains a slash must not be able to reach a different route, and an
738
- * absent value is an error rather than the literal string "undefined" in a URL.
739
- */
740
- export function buildPath(op, values) {
741
- return op.path.replace(/\{([a-zA-Z]+)\}/g, (_m, name) => {
742
- const v = values[name];
743
- if (v === undefined || v === "") {
744
- throw new TeamOpError(`naive teams ${op.cli}: missing value for path segment {${name}}`);
745
- }
746
- return encodeURIComponent(v);
747
- });
748
- }
749
- /** The query string, including its leading `?`, or `""`. */
750
- export function buildQuery(op, opts) {
751
- const params = new URLSearchParams();
752
- for (const f of op.flags) {
753
- if (f.in !== "query")
754
- continue;
755
- const raw = opts[f.key];
756
- if (raw === undefined || raw === false || raw === null)
757
- continue;
758
- params.set(f.field ?? f.key, raw === true ? "true" : String(raw));
759
- }
760
- const s = params.toString();
761
- return s ? `?${s}` : "";
762
- }
763
- /**
764
- * The request body: positional args declared `in: "body"`, then flags declared
765
- * `in: "body"`. A GET has no body at all — a GET that carries one is a request
766
- * some proxy will strip and some server will ignore, which is a silent wrong
767
- * answer rather than an error.
768
- */
769
- export function buildBody(op, args, opts) {
770
- if (op.method === "GET")
771
- return undefined;
772
- const body = {};
773
- for (const a of op.args) {
774
- if (a.in !== "body")
775
- continue;
776
- const v = args[a.name];
777
- if (v !== undefined)
778
- body[a.field ?? a.name] = v;
779
- }
780
- for (const f of op.flags) {
781
- if (f.in !== "body")
782
- continue;
783
- const v = opts[f.key];
784
- if (v === undefined || v === false || v === null)
785
- continue;
786
- // A selector flag sends its declared VALUE under a shared field, not `true`
787
- // under its own name. See `FlagSpec.value`.
788
- body[f.field ?? snake(f.key)] = f.value !== undefined ? f.value : v;
789
- }
790
- return body;
791
- }
792
- /**
793
- * The mutually exclusive selector groups on this op, keyed by the wire field
794
- * they share. DERIVED from the flags rather than listed, so a third member added
795
- * to a group joins the exclusion without anyone remembering to widen a list.
796
- */
797
- export function selectorGroups(op) {
798
- const groups = new Map();
799
- for (const f of op.flags) {
800
- if (f.value === undefined)
801
- continue;
802
- if (f.field === undefined) {
803
- throw new TeamOpError(`teams-catalog: ${op.cli} --${f.key} declares a value with no field`);
804
- }
805
- const g = groups.get(f.field) ?? [];
806
- g.push(f);
807
- groups.set(f.field, g);
808
- }
809
- return groups;
810
- }
811
- /**
812
- * Refuse an ambiguous or absent decision BEFORE the request leaves the process.
813
- *
814
- * A selector group is a CHOICE: exactly one member must be given. Neither branch
815
- * can be left to the server. `--allow --deny` would otherwise resolve by flag
816
- * order in this file — an approval decided by declaration order — and giving
817
- * neither would send a body with no `decision`, which `settle` (whose openapi
818
- * marks nothing required) would accept as a well-formed request that settles an
819
- * effect no particular way.
820
- */
821
- export function assertSelectors(op, opts) {
822
- for (const [field, flags] of selectorGroups(op)) {
823
- const chosen = flags.filter((f) => opts[f.key] === true);
824
- const spellings = flags.map((f) => `--${f.key}`).join(" or ");
825
- if (chosen.length === 0) {
826
- throw new TeamOpError(`naive teams ${op.cli}: ${spellings} is required (it is the \`${field}\` this sends).`);
827
- }
828
- if (chosen.length > 1) {
829
- throw new TeamOpError(`naive teams ${op.cli}: ${chosen.map((f) => `--${f.key}`).join(" and ")} are mutually exclusive — pass exactly one.`);
830
- }
831
- }
832
- }
833
- /** `failOnTerminal` -> `fail_on_terminal`. Flags are camelCase in commander and
834
- * snake_case on this API's wire; converting in one place keeps the two apart. */
835
- export function snake(key) {
836
- return key.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);
837
- }
838
- export function findOp(cli) {
839
- return TEAM_OPS.find((o) => o.cli === cli);
840
- }
841
- //# sourceMappingURL=teams-catalog.js.map