@usenaive-sdk/cli 0.12.5 → 0.13.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 (153) hide show
  1. package/README.md +55 -6
  2. package/dist/client.d.ts +48 -0
  3. package/dist/client.js +199 -27
  4. package/dist/client.js.map +1 -1
  5. package/dist/commands/aeo.js +56 -21
  6. package/dist/commands/aeo.js.map +1 -1
  7. package/dist/commands/app-data.js +29 -11
  8. package/dist/commands/app-data.js.map +1 -1
  9. package/dist/commands/apps.d.ts +7 -0
  10. package/dist/commands/apps.js +255 -18
  11. package/dist/commands/apps.js.map +1 -1
  12. package/dist/commands/audio.d.ts +9 -0
  13. package/dist/commands/audio.js +391 -0
  14. package/dist/commands/audio.js.map +1 -0
  15. package/dist/commands/auth.d.ts +1 -0
  16. package/dist/commands/auth.js +20 -17
  17. package/dist/commands/auth.js.map +1 -1
  18. package/dist/commands/billing.js +9 -9
  19. package/dist/commands/billing.js.map +1 -1
  20. package/dist/commands/brain.js +798 -52
  21. package/dist/commands/brain.js.map +1 -1
  22. package/dist/commands/browser.js +8 -8
  23. package/dist/commands/browser.js.map +1 -1
  24. package/dist/commands/business.js +117 -16
  25. package/dist/commands/business.js.map +1 -1
  26. package/dist/commands/clone.js +6 -2
  27. package/dist/commands/clone.js.map +1 -1
  28. package/dist/commands/companies.d.ts +8 -0
  29. package/dist/commands/companies.js +137 -47
  30. package/dist/commands/companies.js.map +1 -1
  31. package/dist/commands/company-data.d.ts +2 -0
  32. package/dist/commands/company-data.js +168 -0
  33. package/dist/commands/company-data.js.map +1 -0
  34. package/dist/commands/compute.js +10 -3
  35. package/dist/commands/compute.js.map +1 -1
  36. package/dist/commands/connections.d.ts +6 -0
  37. package/dist/commands/connections.js +29 -1
  38. package/dist/commands/connections.js.map +1 -1
  39. package/dist/commands/domains.js +1 -1
  40. package/dist/commands/ecommerce.js +19 -9
  41. package/dist/commands/ecommerce.js.map +1 -1
  42. package/dist/commands/email.js +56 -9
  43. package/dist/commands/email.js.map +1 -1
  44. package/dist/commands/formation.js +3 -3
  45. package/dist/commands/identity.js +4 -3
  46. package/dist/commands/identity.js.map +1 -1
  47. package/dist/commands/images.js +85 -11
  48. package/dist/commands/images.js.map +1 -1
  49. package/dist/commands/init.d.ts +3 -0
  50. package/dist/commands/init.js +39 -36
  51. package/dist/commands/init.js.map +1 -1
  52. package/dist/commands/jobs.js +1 -0
  53. package/dist/commands/jobs.js.map +1 -1
  54. package/dist/commands/legacy-auth.d.ts +27 -0
  55. package/dist/commands/legacy-auth.js +64 -0
  56. package/dist/commands/legacy-auth.js.map +1 -0
  57. package/dist/commands/llm.js +13 -0
  58. package/dist/commands/llm.js.map +1 -1
  59. package/dist/commands/login.js +2 -0
  60. package/dist/commands/login.js.map +1 -1
  61. package/dist/commands/media.js +3 -3
  62. package/dist/commands/media.js.map +1 -1
  63. package/dist/commands/mobile.d.ts +2 -0
  64. package/dist/commands/mobile.js +42 -1
  65. package/dist/commands/mobile.js.map +1 -1
  66. package/dist/commands/payments.js +1 -1
  67. package/dist/commands/people.d.ts +2 -0
  68. package/dist/commands/people.js +193 -0
  69. package/dist/commands/people.js.map +1 -0
  70. package/dist/commands/phone.js +3 -78
  71. package/dist/commands/phone.js.map +1 -1
  72. package/dist/commands/projects.d.ts +13 -0
  73. package/dist/commands/projects.js +220 -0
  74. package/dist/commands/projects.js.map +1 -0
  75. package/dist/commands/register.js +13 -5
  76. package/dist/commands/register.js.map +1 -1
  77. package/dist/commands/sandbox.d.ts +2 -0
  78. package/dist/commands/sandbox.js +245 -0
  79. package/dist/commands/sandbox.js.map +1 -0
  80. package/dist/commands/search.js +26 -12
  81. package/dist/commands/search.js.map +1 -1
  82. package/dist/commands/seo.js +16 -8
  83. package/dist/commands/seo.js.map +1 -1
  84. package/dist/commands/sessions.d.ts +6 -0
  85. package/dist/commands/sessions.js +109 -0
  86. package/dist/commands/sessions.js.map +1 -0
  87. package/dist/commands/social-data.d.ts +2 -0
  88. package/dist/commands/social-data.js +244 -0
  89. package/dist/commands/social-data.js.map +1 -0
  90. package/dist/commands/social.js +8 -6
  91. package/dist/commands/social.js.map +1 -1
  92. package/dist/commands/teams.d.ts +69 -0
  93. package/dist/commands/teams.js +503 -0
  94. package/dist/commands/teams.js.map +1 -0
  95. package/dist/commands/up.d.ts +150 -0
  96. package/dist/commands/up.js +415 -15
  97. package/dist/commands/up.js.map +1 -1
  98. package/dist/commands/usage.js +15 -4
  99. package/dist/commands/usage.js.map +1 -1
  100. package/dist/commands/use.d.ts +9 -0
  101. package/dist/commands/use.js +76 -11
  102. package/dist/commands/use.js.map +1 -1
  103. package/dist/commands/users.d.ts +8 -0
  104. package/dist/commands/users.js +28 -9
  105. package/dist/commands/users.js.map +1 -1
  106. package/dist/commands/vault.js +28 -1
  107. package/dist/commands/vault.js.map +1 -1
  108. package/dist/commands/video.js +117 -18
  109. package/dist/commands/video.js.map +1 -1
  110. package/dist/commands/wallet.js +3 -3
  111. package/dist/commands/wallet.js.map +1 -1
  112. package/dist/commands/whoami.js +17 -1
  113. package/dist/commands/whoami.js.map +1 -1
  114. package/dist/config.d.ts +9 -1
  115. package/dist/config.js +1 -1
  116. package/dist/config.js.map +1 -1
  117. package/dist/index.d.ts +2 -1
  118. package/dist/index.js +228 -24
  119. package/dist/index.js.map +1 -1
  120. package/dist/lib/brain-select.d.ts +193 -0
  121. package/dist/lib/brain-select.js +403 -0
  122. package/dist/lib/brain-select.js.map +1 -0
  123. package/dist/lib/cli-validation.d.ts +12 -0
  124. package/dist/lib/cli-validation.js +46 -0
  125. package/dist/lib/cli-validation.js.map +1 -0
  126. package/dist/lib/deprecation.d.ts +158 -0
  127. package/dist/lib/deprecation.js +140 -0
  128. package/dist/lib/deprecation.js.map +1 -0
  129. package/dist/lib/duration.d.ts +8 -0
  130. package/dist/lib/duration.js +11 -0
  131. package/dist/lib/duration.js.map +1 -0
  132. package/dist/lib/http-response.d.ts +11 -0
  133. package/dist/lib/http-response.js +96 -0
  134. package/dist/lib/http-response.js.map +1 -0
  135. package/dist/lib/pricing.d.ts +132 -0
  136. package/dist/lib/pricing.js +148 -0
  137. package/dist/lib/pricing.js.map +1 -0
  138. package/dist/lib/stream-frames.d.ts +114 -0
  139. package/dist/lib/stream-frames.js +169 -0
  140. package/dist/lib/stream-frames.js.map +1 -0
  141. package/dist/lib/teams-catalog.d.ts +141 -0
  142. package/dist/lib/teams-catalog.js +765 -0
  143. package/dist/lib/teams-catalog.js.map +1 -0
  144. package/dist/lib/unknown-command.d.ts +41 -1
  145. package/dist/lib/unknown-command.js +74 -0
  146. package/dist/lib/unknown-command.js.map +1 -1
  147. package/dist/output.d.ts +99 -5
  148. package/dist/output.js +160 -11
  149. package/dist/output.js.map +1 -1
  150. package/package.json +3 -3
  151. package/dist/commands/link.d.ts +0 -3
  152. package/dist/commands/link.js +0 -91
  153. package/dist/commands/link.js.map +0 -1
@@ -0,0 +1,765 @@
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
+ because: "GET /v1/teams answers 501, so a tool over it could only ever refuse",
113
+ args: [],
114
+ flags: [{ flag: "--runtime <kind>", key: "runtime", describe: "durable | hermes", in: "query" }],
115
+ },
116
+ {
117
+ cli: "show",
118
+ describe: "Show one team's status for one tenant",
119
+ action: "teams.status",
120
+ method: "GET",
121
+ path: BASE,
122
+ tenant: true,
123
+ fenced: false,
124
+ write: false,
125
+ mcp: false,
126
+ because: "the MCP surface declares no such tool; a model reads the board, not the team header",
127
+ args: [TEAM_ARG],
128
+ flags: [],
129
+ },
130
+ {
131
+ cli: "plan",
132
+ describe: "The honesty report for one (team, tenant): digests, fence, caps, attestation parity",
133
+ action: "teams.plan",
134
+ method: "GET",
135
+ path: `${BASE}/plan`,
136
+ tenant: true,
137
+ fenced: false,
138
+ write: false,
139
+ mcp: false,
140
+ 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",
141
+ args: [TEAM_ARG],
142
+ flags: [
143
+ {
144
+ flag: "--detailed-exitcode",
145
+ key: "detailedExitcode",
146
+ describe: "Exit 2 when a diff exists (terraform's convention; exit 1 stays a real error)",
147
+ in: "client",
148
+ },
149
+ ],
150
+ },
151
+ {
152
+ cli: "roster",
153
+ describe: "The declared agents of a team, and optionally the edges between them",
154
+ action: "teams.roster",
155
+ method: "GET",
156
+ path: `${BASE}/roster`,
157
+ tenant: true,
158
+ fenced: false,
159
+ write: false,
160
+ mcp: false,
161
+ 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",
162
+ args: [TEAM_ARG],
163
+ flags: [{ flag: "--edges", key: "edges", describe: "Include the declared edges", in: "query" }],
164
+ },
165
+ {
166
+ cli: "board",
167
+ describe: "The team's board for one tenant",
168
+ action: "teams.board",
169
+ method: "GET",
170
+ path: `${BASE}/board`,
171
+ tenant: true,
172
+ fenced: false,
173
+ write: false,
174
+ mcp: "naive_teams_board",
175
+ args: [TEAM_ARG],
176
+ flags: BOARD_FILTERS,
177
+ },
178
+ {
179
+ cli: "submit",
180
+ describe: "Send work to a team. THE verb — one ActionId, one fence, every surface",
181
+ action: "teams.submit",
182
+ method: "POST",
183
+ path: `${BASE}/submit`,
184
+ tenant: true,
185
+ fenced: true,
186
+ write: true,
187
+ mcp: false,
188
+ 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",
189
+ args: [TEAM_ARG, { arg: "<goal>", name: "goal", describe: "What you want done", in: "body" }],
190
+ flags: [
191
+ { flag: "--task <id>", key: "task", describe: "Attach this goal to an existing task", in: "body" },
192
+ {
193
+ flag: "--brief <file>",
194
+ key: "brief",
195
+ describe: "Read the prose brief from a file, or `-` for stdin (prose on argv is mangled by every shell)",
196
+ in: "body",
197
+ },
198
+ { flag: "--attest <claim>", key: "attest", describe: "The claim the result must attest to", in: "body" },
199
+ { flag: "--parent <id>", key: "parent", describe: "Parent task id", in: "body" },
200
+ {
201
+ flag: "--idempotency-key <k>",
202
+ key: "idempotencyKey",
203
+ describe: "Forwarded as Idempotency-Key. Best-effort today — see `naive teams plan`",
204
+ in: "client",
205
+ },
206
+ ],
207
+ },
208
+ {
209
+ cli: "say",
210
+ describe: "Say something into a team session",
211
+ action: "teams.say",
212
+ method: "POST",
213
+ path: `${BASE}/sessions/{channel}/messages`,
214
+ tenant: true,
215
+ fenced: true,
216
+ write: true,
217
+ mcp: false,
218
+ 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",
219
+ args: [TEAM_ARG, { arg: "<text>", name: "text", describe: "What to say", in: "body" }],
220
+ flags: [
221
+ { flag: "--channel <channel>", key: "channel", describe: "The session channel (default: main)", in: "path" },
222
+ { flag: "--as <role>", key: "as", describe: "Speak as a declared role", in: "body" },
223
+ ],
224
+ },
225
+ {
226
+ cli: "tasks",
227
+ describe: "The board as a task list",
228
+ action: "teams.board",
229
+ method: "GET",
230
+ path: `${BASE}/board`,
231
+ tenant: true,
232
+ fenced: false,
233
+ write: false,
234
+ mcp: false,
235
+ because: "an alias of `board`; naive_teams_board is the tool",
236
+ args: [TEAM_ARG],
237
+ flags: [...BOARD_FILTERS, FAIL_ON_UNVERIFIED],
238
+ },
239
+ {
240
+ cli: "task",
241
+ describe: "One card on the board",
242
+ action: "teams.board",
243
+ method: "GET",
244
+ path: `${BASE}/board/{card}`,
245
+ tenant: true,
246
+ fenced: false,
247
+ write: false,
248
+ mcp: false,
249
+ because: "the MCP surface declares no such tool; a model reads one card through naive_teams_board",
250
+ args: [TEAM_ARG, { arg: "<id>", name: "card", describe: "The card id", in: "path" }],
251
+ flags: [FAIL_ON_UNVERIFIED],
252
+ },
253
+ {
254
+ cli: "unblock",
255
+ describe: "Unblock a card",
256
+ action: "teams.unblock",
257
+ method: "POST",
258
+ path: `${BASE}/board/{card}/unblock`,
259
+ tenant: true,
260
+ fenced: true,
261
+ write: true,
262
+ mcp: "naive_teams_unblock",
263
+ args: [TEAM_ARG, { arg: "<id>", name: "card", describe: "The card id", in: "path" }],
264
+ flags: [BECAUSE],
265
+ },
266
+ {
267
+ cli: "runs",
268
+ describe: "The team's runs",
269
+ action: "teams.runs",
270
+ method: "GET",
271
+ path: `${BASE}/runs`,
272
+ tenant: true,
273
+ fenced: false,
274
+ write: false,
275
+ mcp: false,
276
+ because: "the MCP surface declares no such tool; naive_teams_run reads ONE run by id, it does not list them",
277
+ args: [TEAM_ARG],
278
+ // NOT [TASK, STATUS, …]. The runs handler reads exactly `limit` and
279
+ // `cursor`; `?task=` and `?status=` were declared as query flags, sent, and
280
+ // silently ignored, so filtering a busy tenant's runs returned the
281
+ // unfiltered list with no signal. openapi declares no such parameters
282
+ // either. They come back when the route supports them.
283
+ flags: [LIMIT, CURSOR],
284
+ },
285
+ {
286
+ cli: "watch",
287
+ describe: "Watch a run's transcript as NDJSON — one object per line, resumable",
288
+ action: "teams.runs",
289
+ method: "GET",
290
+ path: `${BASE}/runs/{run}/stream`,
291
+ tenant: true,
292
+ fenced: false,
293
+ write: false,
294
+ mcp: false,
295
+ because: "the MCP surface declares no such tool; …/runs/{id}/stream answers 501 and MCP has no streaming response",
296
+ streams: true,
297
+ args: [TEAM_ARG],
298
+ flags: [
299
+ { flag: "--run <id>", key: "run", describe: "The run to watch (required)", in: "path", required: true },
300
+ {
301
+ flag: "--follow",
302
+ // What it decides is what happens at a TERMINAL frame, not how much
303
+ // of the stream is read: without it the command stops at the first
304
+ // `stream.end`, with it the connection stays open past it. Both
305
+ // modes drain the body — the old behaviour stopped after one
306
+ // network chunk, which truncated the transcript at an arbitrary
307
+ // point and still exited 0.
308
+ key: "follow",
309
+ describe: "Keep watching past a terminal frame (default: stop at the first stream.end)",
310
+ in: "client",
311
+ },
312
+ { ...CURSOR, in: "client", describe: "Resume from this seq — sent as Last-Event-ID" },
313
+ {
314
+ flag: "--fail-on-terminal",
315
+ key: "failOnTerminal",
316
+ // Of the three, only `failed` is reachable on the durable runtime: a
317
+ // durable run's status vocabulary is `running | completed | failed`
318
+ // (`runState` in the runtime's naive.ts), so `unverified` and `blocked`
319
+ // are hermes states. The flag is not hermes-only the way
320
+ // `--fail-on-unverified` is — it keeps a real reach on both — so it is
321
+ // narrowed in the text rather than announced at runtime.
322
+ describe: "Exit 3 when the final status is unverified, failed or blocked (on the durable runtime only `failed` occurs)",
323
+ in: "client",
324
+ },
325
+ {
326
+ flag: "--show-args",
327
+ key: "showArgs",
328
+ describe: "Print tool arguments instead of args_digest. Requires an interactive terminal",
329
+ in: "client",
330
+ },
331
+ ],
332
+ },
333
+ {
334
+ cli: "events",
335
+ describe: "The team's event log",
336
+ action: "teams.events",
337
+ method: "GET",
338
+ path: `${BASE}/events`,
339
+ tenant: true,
340
+ fenced: false,
341
+ write: false,
342
+ mcp: false,
343
+ because: "the MCP surface declares no such tool",
344
+ args: [TEAM_ARG],
345
+ flags: [TASK, CURSOR, LIMIT],
346
+ },
347
+ {
348
+ cli: "cost",
349
+ describe: "What the team spent",
350
+ action: "teams.cost",
351
+ method: "GET",
352
+ path: `${BASE}/cost`,
353
+ tenant: true,
354
+ fenced: false,
355
+ write: false,
356
+ mcp: false,
357
+ because: "the MCP surface declares no such tool; spend is an operator surface",
358
+ args: [TEAM_ARG],
359
+ flags: [
360
+ // `action` IS THE ONLY ACCEPTED VALUE. The route throws
361
+ // `invalid_input: by must be "action" (the only bucketing this build can
362
+ // compute)` for anything else (routes/teams.ts), so the three the describe
363
+ // string used to advertise — task | day | role — all 400.
364
+ {
365
+ flag: "--by <dimension>",
366
+ key: "by",
367
+ describe: 'action (the only value this build accepts)',
368
+ in: "query",
369
+ },
370
+ // `--period` IS NOT SENT. The route never reads `req.query.period`; the
371
+ // window comes from `spendWindow(accountKit.budget)`. A query flag the
372
+ // server ignores is the worst shape available — the response looks right
373
+ // and answers a different question — so it is `client` and the CLI prints
374
+ // the window the server actually used.
375
+ {
376
+ flag: "--period <period>",
377
+ key: "period",
378
+ describe: "IGNORED — the window is fixed by the account kit's budget period",
379
+ in: "client",
380
+ },
381
+ ],
382
+ },
383
+ {
384
+ cli: "diagnose",
385
+ describe: "Diagnostics for a team that is not behaving",
386
+ action: "teams.diagnostics",
387
+ method: "GET",
388
+ path: `${BASE}/diagnostics`,
389
+ tenant: true,
390
+ fenced: false,
391
+ write: false,
392
+ mcp: false,
393
+ because: "the MCP surface declares no such tool",
394
+ args: [TEAM_ARG],
395
+ flags: [{ flag: "--kind <kind>", key: "kind", describe: "Restrict to one diagnostic kind", in: "query" }],
396
+ },
397
+ {
398
+ cli: "sessions",
399
+ describe: "The team's sessions",
400
+ action: "teams.sessions",
401
+ method: "GET",
402
+ path: `${BASE}/sessions`,
403
+ tenant: true,
404
+ fenced: false,
405
+ write: false,
406
+ mcp: false,
407
+ because: "the MCP surface declares no such tool; both session reads answer 501",
408
+ args: [TEAM_ARG],
409
+ flags: [CURSOR, LIMIT],
410
+ },
411
+ {
412
+ cli: "read",
413
+ describe: "Read one session channel",
414
+ action: "teams.sessions",
415
+ method: "GET",
416
+ path: `${BASE}/sessions/{channel}`,
417
+ tenant: true,
418
+ fenced: false,
419
+ write: false,
420
+ mcp: false,
421
+ because: "the MCP surface declares no such tool; both session reads answer 501",
422
+ args: [TEAM_ARG, { arg: "<channel>", name: "channel", describe: "The channel to read", in: "path" }],
423
+ flags: [CURSOR, LIMIT],
424
+ },
425
+ {
426
+ cli: "approvals",
427
+ describe: "Approvals this team is waiting on",
428
+ action: "teams.approvals",
429
+ method: "GET",
430
+ path: `${BASE}/approvals`,
431
+ tenant: true,
432
+ fenced: false,
433
+ write: false,
434
+ mcp: false,
435
+ because: "naive_approvals_list is the tool, and it is not a teams tool",
436
+ args: [TEAM_ARG],
437
+ flags: [STATUS],
438
+ },
439
+ {
440
+ cli: "decide",
441
+ describe: "Allow or deny one approval",
442
+ action: "teams.approval_decide",
443
+ method: "POST",
444
+ path: `${BASE}/approvals/{approval}/decide`,
445
+ tenant: true,
446
+ fenced: true,
447
+ write: true,
448
+ mcp: false,
449
+ because: "an agent may not resolve the gate meant to constrain it — assertHumanResolver refuses an agent key",
450
+ args: [TEAM_ARG, { arg: "<id>", name: "approval", describe: "The approval id", in: "path" }],
451
+ flags: [
452
+ { flag: "--allow", key: "allow", describe: "Allow it", in: "body", field: "decision", value: "approve" },
453
+ { flag: "--deny", key: "deny", describe: "Deny it", in: "body", field: "decision", value: "deny" },
454
+ BECAUSE,
455
+ ],
456
+ },
457
+ {
458
+ cli: "effects",
459
+ describe: "Outstanding effects",
460
+ action: "teams.effects",
461
+ method: "GET",
462
+ path: `${BASE}/effects`,
463
+ tenant: true,
464
+ fenced: false,
465
+ write: false,
466
+ mcp: false,
467
+ because: "the MCP surface declares no such tool; the effects routes answer 501",
468
+ args: [TEAM_ARG],
469
+ flags: [{ flag: "--state <state>", key: "state", describe: "Filter by effect state", in: "query" }],
470
+ },
471
+ {
472
+ cli: "settle",
473
+ describe: "Settle one effect — done, compensated or rejected",
474
+ action: "teams.effect_settle",
475
+ method: "POST",
476
+ path: `${BASE}/effects/{effect}/settle`,
477
+ tenant: true,
478
+ fenced: true,
479
+ write: true,
480
+ mcp: false,
481
+ because: "the MCP surface declares no such tool; the effects routes answer 501",
482
+ args: [TEAM_ARG, { arg: "<id>", name: "effect", describe: "The effect id", in: "path" }],
483
+ flags: [
484
+ // Same selector shape as `decide`. openapi declares `{decision, because,
485
+ // receipt}` here too, with `decision` an unconstrained string — the route
486
+ // is a 501 refusal, so nothing narrows the value set yet. The values are
487
+ // therefore spelled exactly like their flags, so `--help` and the wire
488
+ // cannot drift into two names for one choice.
489
+ { flag: "--done", key: "done", describe: "It completed", in: "body", field: "decision", value: "done" },
490
+ { flag: "--compensate", key: "compensate", describe: "It was compensated", in: "body", field: "decision", value: "compensate" },
491
+ { flag: "--reject", key: "reject", describe: "It was rejected", in: "body", field: "decision", value: "reject" },
492
+ { flag: "--receipt <file>", key: "receipt", describe: "Read the receipt from a file, or `-` for stdin", in: "body" },
493
+ BECAUSE,
494
+ ],
495
+ },
496
+ {
497
+ cli: "schedule",
498
+ describe: "Schedule recurring work for a team",
499
+ action: "teams.schedule",
500
+ method: "POST",
501
+ path: `${BASE}/schedule`,
502
+ tenant: true,
503
+ fenced: true,
504
+ write: true,
505
+ mcp: false,
506
+ because: "the MCP surface declares no such tool; the schedule routes answer 501",
507
+ args: [
508
+ TEAM_ARG,
509
+ { arg: "<cron>", name: "cron", describe: "Cron expression", in: "body" },
510
+ { arg: "<goal>", name: "goal", describe: "What to submit on each tick", in: "body" },
511
+ ],
512
+ flags: [
513
+ { flag: "--brief <file>", key: "brief", describe: "Read the prose brief from a file, or `-` for stdin", in: "body" },
514
+ { flag: "--timezone <tz>", key: "timezone", describe: "IANA timezone for the cron expression", in: "body" },
515
+ ],
516
+ },
517
+ {
518
+ cli: "unschedule",
519
+ // 06 §1.4 gives `schedule / unschedule` ONE row, one arg list and the path
520
+ // glob `…/schedule*`. A DELETE carrying a body to identify its own target is
521
+ // the shape that makes a proxy drop the request, so the id is in the path.
522
+ describe: "Remove a schedule",
523
+ action: "teams.schedule",
524
+ method: "DELETE",
525
+ path: `${BASE}/schedule/{schedule}`,
526
+ tenant: true,
527
+ fenced: true,
528
+ write: true,
529
+ mcp: false,
530
+ because: "the MCP surface declares no such tool; the schedule routes answer 501",
531
+ args: [TEAM_ARG, { arg: "<id>", name: "schedule", describe: "The schedule id", in: "path" }],
532
+ flags: [],
533
+ },
534
+ {
535
+ // 🔴 THIS ROW WAS MISSING, AND IT IS A STEP OF THE CORE LOOP.
536
+ //
537
+ // `POST /v1/teams/{team}/tenants/{tenant}/apply` has been mounted the whole
538
+ // time and no CLI verb reached it — so "declare a team, apply it, submit
539
+ // work" had a hole in the middle that only a hand-written curl could cross.
540
+ // The other 26 rows were derived from the route table; this one was not,
541
+ // because a route with no CLI verb is invisible to a reader checking the CLI
542
+ // against itself.
543
+ //
544
+ // `--expected-digest` is REQUIRED and has no default. Apply exists to raise
545
+ // `409 stale_expected_digest` — to refuse when the compiled company has
546
+ // moved since you last looked — and an apply that sends whatever digest is
547
+ // current cannot ever raise it. A flag defaulted to "whatever you have"
548
+ // turns a concurrency check into a rubber stamp. `naive teams plan` prints
549
+ // the digest to pass.
550
+ cli: "apply",
551
+ describe: "Record that an operator applied this exact manifest digest. Refuses a stale one",
552
+ action: "teams.apply",
553
+ method: "POST",
554
+ path: `${BASE}/apply`,
555
+ tenant: true,
556
+ fenced: true,
557
+ write: true,
558
+ mcp: false,
559
+ because: "applying a manifest is the act that makes a capability change something a human reviewed; an agent applying its own manifest reviews nothing",
560
+ args: [TEAM_ARG],
561
+ flags: [
562
+ {
563
+ flag: "--expected-digest <digest>",
564
+ key: "expectedDigest",
565
+ describe: "REQUIRED. The digest you are applying, from `naive teams plan`. A stale one is refused",
566
+ in: "body",
567
+ field: "expected_digest",
568
+ required: true,
569
+ },
570
+ // NO `--because`, deliberately, and it is the one fenced op without one.
571
+ // The published contract for this operation declares exactly one body
572
+ // property — `expected_digest` — and the CLI may not send a key the
573
+ // contract does not declare (the gate below enforces that, and it caught
574
+ // this row). More importantly the reason would be redundant: the artefact
575
+ // that records what an operator approved is the DIGEST, which names the
576
+ // exact compiled manifest, and a free-text sentence beside it adds a
577
+ // second, weaker account of the same decision.
578
+ ],
579
+ },
580
+ {
581
+ cli: "model",
582
+ describe: "Change the model a team runs on",
583
+ action: "teams.model",
584
+ method: "POST",
585
+ path: `${BASE}/model`,
586
+ tenant: true,
587
+ fenced: true,
588
+ write: true,
589
+ mcp: false,
590
+ because: "the MCP surface declares no such tool; a model must not choose its own model",
591
+ args: [TEAM_ARG, { arg: "<modelId>", name: "modelId", describe: "The model id", in: "body", field: "model_id" }],
592
+ flags: [BECAUSE],
593
+ },
594
+ {
595
+ cli: "stop",
596
+ describe: "Stop a team",
597
+ action: "teams.stop",
598
+ method: "POST",
599
+ path: `${BASE}/stop`,
600
+ tenant: true,
601
+ fenced: true,
602
+ write: true,
603
+ mcp: false,
604
+ because: "the MCP surface declares no such tool; naive_teams_run reads a run, it does not stop one",
605
+ args: [TEAM_ARG],
606
+ flags: [BECAUSE],
607
+ },
608
+ {
609
+ cli: "migrate",
610
+ describe: "Move this tenant onto the durable runtime",
611
+ action: "teams.migrate",
612
+ method: "POST",
613
+ path: `${BASE}/migrate`,
614
+ tenant: true,
615
+ fenced: true,
616
+ write: true,
617
+ // An agent must not move its own tenant between runtimes: the cutover
618
+ // destroys the legacy coordinates irreversibly. Declared, not omitted, so a
619
+ // four-way parity check sees an exemption rather than a hole.
620
+ mcp: false,
621
+ because: "an agent must not move its own tenant between runtimes; the cutover is irreversible",
622
+ args: [TEAM_ARG],
623
+ flags: [{ flag: "--dry-run", key: "dryRun", describe: "Report what would move; change nothing", in: "body" }, BECAUSE],
624
+ },
625
+ {
626
+ cli: "rollback",
627
+ describe: "Move this tenant back off the durable runtime. DESTROYS the workspace",
628
+ action: "teams.rollback",
629
+ method: "POST",
630
+ path: `${BASE}/rollback`,
631
+ tenant: true,
632
+ fenced: true,
633
+ write: true,
634
+ mcp: false,
635
+ because: "an agent must not move its own tenant between runtimes; the rollback destroys /data",
636
+ args: [TEAM_ARG],
637
+ flags: [
638
+ {
639
+ // A flag a script cannot accidentally satisfy is the honest interlock:
640
+ // the rollback re-claims the slot, so /data is gone with it.
641
+ flag: "--i-understand-data-loss",
642
+ key: "iUnderstandDataLoss",
643
+ describe: "Required. The workspace is destroyed by this operation and is not recoverable",
644
+ in: "body",
645
+ },
646
+ BECAUSE,
647
+ ],
648
+ },
649
+ ];
650
+ /* -------------------------------------------------------------------------- */
651
+ /* pure builders — everything the runner needs, testable without a network */
652
+ /* -------------------------------------------------------------------------- */
653
+ export class TeamOpError extends Error {
654
+ }
655
+ /** Every `{placeholder}` this op's path expects. */
656
+ export function pathPlaceholders(op) {
657
+ return [...op.path.matchAll(/\{([a-zA-Z]+)\}/g)].map((m) => m[1]);
658
+ }
659
+ /**
660
+ * Fill the path. Every value is `encodeURIComponent`'d — a team name or a card id
661
+ * that contains a slash must not be able to reach a different route, and an
662
+ * absent value is an error rather than the literal string "undefined" in a URL.
663
+ */
664
+ export function buildPath(op, values) {
665
+ return op.path.replace(/\{([a-zA-Z]+)\}/g, (_m, name) => {
666
+ const v = values[name];
667
+ if (v === undefined || v === "") {
668
+ throw new TeamOpError(`naive teams ${op.cli}: missing value for path segment {${name}}`);
669
+ }
670
+ return encodeURIComponent(v);
671
+ });
672
+ }
673
+ /** The query string, including its leading `?`, or `""`. */
674
+ export function buildQuery(op, opts) {
675
+ const params = new URLSearchParams();
676
+ for (const f of op.flags) {
677
+ if (f.in !== "query")
678
+ continue;
679
+ const raw = opts[f.key];
680
+ if (raw === undefined || raw === false || raw === null)
681
+ continue;
682
+ params.set(f.field ?? f.key, raw === true ? "true" : String(raw));
683
+ }
684
+ const s = params.toString();
685
+ return s ? `?${s}` : "";
686
+ }
687
+ /**
688
+ * The request body: positional args declared `in: "body"`, then flags declared
689
+ * `in: "body"`. A GET has no body at all — a GET that carries one is a request
690
+ * some proxy will strip and some server will ignore, which is a silent wrong
691
+ * answer rather than an error.
692
+ */
693
+ export function buildBody(op, args, opts) {
694
+ if (op.method === "GET")
695
+ return undefined;
696
+ const body = {};
697
+ for (const a of op.args) {
698
+ if (a.in !== "body")
699
+ continue;
700
+ const v = args[a.name];
701
+ if (v !== undefined)
702
+ body[a.field ?? a.name] = v;
703
+ }
704
+ for (const f of op.flags) {
705
+ if (f.in !== "body")
706
+ continue;
707
+ const v = opts[f.key];
708
+ if (v === undefined || v === false || v === null)
709
+ continue;
710
+ // A selector flag sends its declared VALUE under a shared field, not `true`
711
+ // under its own name. See `FlagSpec.value`.
712
+ body[f.field ?? snake(f.key)] = f.value !== undefined ? f.value : v;
713
+ }
714
+ return body;
715
+ }
716
+ /**
717
+ * The mutually exclusive selector groups on this op, keyed by the wire field
718
+ * they share. DERIVED from the flags rather than listed, so a third member added
719
+ * to a group joins the exclusion without anyone remembering to widen a list.
720
+ */
721
+ export function selectorGroups(op) {
722
+ const groups = new Map();
723
+ for (const f of op.flags) {
724
+ if (f.value === undefined)
725
+ continue;
726
+ if (f.field === undefined) {
727
+ throw new TeamOpError(`teams-catalog: ${op.cli} --${f.key} declares a value with no field`);
728
+ }
729
+ const g = groups.get(f.field) ?? [];
730
+ g.push(f);
731
+ groups.set(f.field, g);
732
+ }
733
+ return groups;
734
+ }
735
+ /**
736
+ * Refuse an ambiguous or absent decision BEFORE the request leaves the process.
737
+ *
738
+ * A selector group is a CHOICE: exactly one member must be given. Neither branch
739
+ * can be left to the server. `--allow --deny` would otherwise resolve by flag
740
+ * order in this file — an approval decided by declaration order — and giving
741
+ * neither would send a body with no `decision`, which `settle` (whose openapi
742
+ * marks nothing required) would accept as a well-formed request that settles an
743
+ * effect no particular way.
744
+ */
745
+ export function assertSelectors(op, opts) {
746
+ for (const [field, flags] of selectorGroups(op)) {
747
+ const chosen = flags.filter((f) => opts[f.key] === true);
748
+ const spellings = flags.map((f) => `--${f.key}`).join(" or ");
749
+ if (chosen.length === 0) {
750
+ throw new TeamOpError(`naive teams ${op.cli}: ${spellings} is required (it is the \`${field}\` this sends).`);
751
+ }
752
+ if (chosen.length > 1) {
753
+ throw new TeamOpError(`naive teams ${op.cli}: ${chosen.map((f) => `--${f.key}`).join(" and ")} are mutually exclusive — pass exactly one.`);
754
+ }
755
+ }
756
+ }
757
+ /** `failOnTerminal` -> `fail_on_terminal`. Flags are camelCase in commander and
758
+ * snake_case on this API's wire; converting in one place keeps the two apart. */
759
+ export function snake(key) {
760
+ return key.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);
761
+ }
762
+ export function findOp(cli) {
763
+ return TEAM_OPS.find((o) => o.cli === cli);
764
+ }
765
+ //# sourceMappingURL=teams-catalog.js.map