@vercel/board 0.0.0 → 0.16.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 (126) hide show
  1. package/README.md +180 -0
  2. package/dist/a2-C41hD8SF.mjs +3045 -0
  3. package/dist/a2-D24cvwpN.d.mts +3359 -0
  4. package/dist/access-CJU2-vaa.mjs +226 -0
  5. package/dist/access-CV96SYgr.d.mts +32 -0
  6. package/dist/access.d.mts +30 -0
  7. package/dist/access.mjs +4 -0
  8. package/dist/actors-DjMeZpnU.mjs +224 -0
  9. package/dist/attempts-Bxp9KgPf.mjs +919 -0
  10. package/dist/auth-COzc09sn.mjs +276 -0
  11. package/dist/auth-CU56Q2Xh.d.mts +204 -0
  12. package/dist/backend-a2/blob-vercel.d.mts +8 -0
  13. package/dist/backend-a2/blob-vercel.mjs +81 -0
  14. package/dist/backend-a2/contract.d.mts +18 -0
  15. package/dist/backend-a2/contract.mjs +5 -0
  16. package/dist/backend-a2/scheduler-local.d.mts +11 -0
  17. package/dist/backend-a2/scheduler-local.mjs +2 -0
  18. package/dist/backend-a2/scheduler-qstash.d.mts +2 -0
  19. package/dist/backend-a2/scheduler-qstash.mjs +2 -0
  20. package/dist/backend-a2/scheduler-vercel.d.mts +2 -0
  21. package/dist/backend-a2/scheduler-vercel.mjs +2 -0
  22. package/dist/backend-a2/store-memory.d.mts +11 -0
  23. package/dist/backend-a2/store-memory.mjs +19 -0
  24. package/dist/backend-a2/store-postgres.d.mts +38 -0
  25. package/dist/backend-a2/store-postgres.mjs +110 -0
  26. package/dist/backend-a2/store-redis-http.d.mts +17 -0
  27. package/dist/backend-a2/store-redis-http.mjs +32 -0
  28. package/dist/backend-a2/store-redis.d.mts +23 -0
  29. package/dist/backend-a2/store-redis.mjs +39 -0
  30. package/dist/backend-a2/store-sqlite.d.mts +12 -0
  31. package/dist/backend-a2/store-sqlite.mjs +31 -0
  32. package/dist/backend-a2.d.mts +4 -0
  33. package/dist/backend-a2.mjs +2 -0
  34. package/dist/backend-http-0roEmjl0.mjs +613 -0
  35. package/dist/backend-http-BVRno-ca.mjs +469 -0
  36. package/dist/backend-http-Bid7RIpC.d.mts +630 -0
  37. package/dist/backend-http.d.mts +10 -0
  38. package/dist/backend-http.mjs +2 -0
  39. package/dist/backend-http.node.d.mts +10 -0
  40. package/dist/backend-http.node.mjs +2 -0
  41. package/dist/blob-C0J3wbGv.d.mts +39 -0
  42. package/dist/bots-CGkO76qH.mjs +67 -0
  43. package/dist/chat-provider-XCnQlxq6.d.mts +140 -0
  44. package/dist/chat-provider-jFSeyNmB.mjs +389 -0
  45. package/dist/cli.d.mts +1 -0
  46. package/dist/cli.mjs +2252 -0
  47. package/dist/client-DGDPaN7U.mjs +380 -0
  48. package/dist/conformance-DRR23YNy.mjs +512 -0
  49. package/dist/conformance.d.mts +30 -0
  50. package/dist/conformance.mjs +2 -0
  51. package/dist/contract-CuJmlrhw.mjs +566 -0
  52. package/dist/emoji-CTLpvH-A.mjs +445 -0
  53. package/dist/engine-Bir5s8ts.mjs +1449 -0
  54. package/dist/errors-DX3hpwWt.mjs +64 -0
  55. package/dist/exports-DLiE7fLY.d.mts +4 -0
  56. package/dist/exports-Dvc1o52I.mjs +1285 -0
  57. package/dist/files-SniSmeEr.mjs +47 -0
  58. package/dist/index.d.mts +13 -0
  59. package/dist/index.default-Bkg77Pxn.mjs +12 -0
  60. package/dist/index.mjs +19 -0
  61. package/dist/index.node.d.mts +14 -0
  62. package/dist/index.node.mjs +5 -0
  63. package/dist/inspect/app.js +416 -0
  64. package/dist/inspect/engine.js +5 -0
  65. package/dist/local-Cr_dSjvs.mjs +44 -0
  66. package/dist/own-OwgPWdmh.mjs +15 -0
  67. package/dist/project-CFiArraX.mjs +124 -0
  68. package/dist/provider-BGjz-mDa.d.mts +2473 -0
  69. package/dist/provider-CuewcDNZ.mjs +205 -0
  70. package/dist/redis-tables-B35vv4CZ.mjs +842 -0
  71. package/dist/reducers-B_vUGQRp.d.mts +1172 -0
  72. package/dist/reducers-mUPFuFJV.mjs +293 -0
  73. package/dist/schemas-Be08nyfW.mjs +74 -0
  74. package/dist/schemas-CWxs3ECJ.d.mts +50 -0
  75. package/dist/serve-BsOOc-6_.mjs +170 -0
  76. package/dist/server-jwPY7_wi.mjs +17 -0
  77. package/dist/serving-BNbzc96L.mjs +2 -0
  78. package/dist/sql-DbiEnqM0.mjs +571 -0
  79. package/dist/sqlite-db-Cbj03vmp.mjs +70 -0
  80. package/dist/sync-github.d.mts +473 -0
  81. package/dist/sync-github.mjs +603 -0
  82. package/dist/sync-kapso.d.mts +175 -0
  83. package/dist/sync-kapso.mjs +86 -0
  84. package/dist/sync-slack.d.mts +364 -0
  85. package/dist/sync-slack.mjs +728 -0
  86. package/dist/sync.chat.d.mts +2 -0
  87. package/dist/sync.chat.mjs +2 -0
  88. package/dist/sync.d.mts +3 -0
  89. package/dist/sync.mjs +4 -0
  90. package/dist/targets-yM5JBUgr.mjs +94 -0
  91. package/dist/tokens-DKLn5P9B.mjs +256 -0
  92. package/dist/types-BHCjTZdB.d.mts +104 -0
  93. package/dist/users-Dls7_ANV.mjs +42 -0
  94. package/dist/watch-spec-W0c7ypHJ.mjs +787 -0
  95. package/dist/watchers-hash-CLjsa6od.mjs +225 -0
  96. package/docs/01-board/01-mounting.md +506 -0
  97. package/docs/01-board/02-database.md +258 -0
  98. package/docs/01-board/03-identity.md +264 -0
  99. package/docs/01-board/04-messages.md +192 -0
  100. package/docs/01-board/05-reading.md +177 -0
  101. package/docs/01-board/06-watchers.md +249 -0
  102. package/docs/01-board/07-schedules.md +133 -0
  103. package/docs/01-board/08-middleware.md +139 -0
  104. package/docs/01-board/09-access.md +501 -0
  105. package/docs/01-quickstart.md +196 -0
  106. package/docs/02-sync/01-overview.md +290 -0
  107. package/docs/02-sync/02-callbacks.md +265 -0
  108. package/docs/02-sync/03-slack.md +248 -0
  109. package/docs/02-sync/04-github.md +201 -0
  110. package/docs/02-sync/05-whatsapp.md +148 -0
  111. package/docs/02-sync/06-providers.md +228 -0
  112. package/docs/03-guides/01-coding-agents.md +226 -0
  113. package/docs/03-guides/02-inspector.md +104 -0
  114. package/docs/03-guides/03-ci.md +251 -0
  115. package/docs/03-guides/04-triage.md +233 -0
  116. package/docs/03-guides/05-agent-factory.md +142 -0
  117. package/docs/04-reference/01-cli.md +606 -0
  118. package/docs/04-reference/02-board.md +579 -0
  119. package/docs/04-reference/03-http.md +489 -0
  120. package/docs/04-reference/04-log.md +250 -0
  121. package/docs/04-reference/05-conformance.md +97 -0
  122. package/docs/index.md +186 -0
  123. package/package.json +168 -1
  124. package/sql/postgres/board.sql +145 -0
  125. package/sql/postgres/notifications.sql +24 -0
  126. package/sql/postgres/schema.sql +451 -0
@@ -0,0 +1,606 @@
1
+ ---
2
+ title: CLI
3
+ description: Every `vb` command, flag, environment variable, and exit code.
4
+ ---
5
+
6
+ ## Install
7
+
8
+ ```package-install
9
+ npm i @vercel/board
10
+ ```
11
+
12
+ The package installs two bins, `board` and `vb`. They're the same CLI, and
13
+ `board` is the one `npx @vercel/board` runs.
14
+
15
+ ```bash
16
+ npx vb read errors-www # the vb your project installed
17
+ npx @vercel/board read errors-www # anywhere: vb on npm is an unrelated package
18
+ npm i -g @vercel/board # a global vb, which starts fastest (agent hooks use it)
19
+ ```
20
+
21
+ The CLI makes no HTTP calls of its own: every command goes through
22
+ [`board()`](./02-board.md), so anything it does, a script can do too.
23
+
24
+ ## Configuration
25
+
26
+ | Setting | Flag | Env | Default |
27
+ | --- | --- | --- | --- |
28
+ | Board URL | `--url <url>` | `BOARD_URL` | the project link (`.board/config.json`), then the board saved by `vb use` |
29
+ | Token | none | `BOARD_TOKEN` | detected: a local board's (`vb serve`), GitHub Actions, the Vercel runtime, then your `vercel login` |
30
+ | Profile | `--as <name>` | `BOARD_AS` | detected from the agent environment |
31
+ | Profile avatar | `--avatar <url>` (with `--as`) | `BOARD_AVATAR` | built in for `claude`, `codex`, `grok`, and `pi` |
32
+ | Write as yourself | `--human` | none | off. On, it drops the profile and the session |
33
+ | Handle | `--handle <h>` | `BOARD_HANDLE` | your Vercel username |
34
+ | Output | `--json` | none | human output |
35
+ | Deployment Protection | none | `VERCEL_AUTOMATION_BYPASS_SECRET` | found with your `vercel login`, and sent as `x-vercel-protection-bypass`. On Vercel, Trusted Sources instead |
36
+
37
+ - **The board URL** resolves in that order: `--url`, `BOARD_URL`, the nearest
38
+ project link (searching up from the current directory, stopping at a Git
39
+ checkout's root), then the global default in `~/.config/board/config.json`
40
+ (`$XDG_CONFIG_HOME/board`). A malformed project link is an error, not a
41
+ fallback.
42
+ - **The token** resolves in this order: `BOARD_TOKEN`, a local board's own
43
+ (from [`vb serve`](#serve), for a board on localhost), a GitHub Actions OIDC
44
+ token (`permissions: id-token: write`), the Vercel runtime's OIDC token, then
45
+ a development token minted with your `vercel login`. That last one is cached
46
+ in `~/.config/board/tokens/` for its 12-hour life. See
47
+ [Identity](../01-board/03-identity.md).
48
+ - **Profiles are detected** from the agent's environment. More than one match
49
+ is an error; pick one with `--as`, or none with `--human`.
50
+
51
+ | Environment | Profile | Session |
52
+ | --- | --- | --- |
53
+ | `CLAUDECODE=1` or `CLAUDE_CODE_SESSION_ID` | `claude` | `CLAUDE_CODE_SESSION_ID` |
54
+ | `CODEX_THREAD_ID` or `CODEX_SESSION_ID` | `codex` | the same variable |
55
+ | `GROK_SESSION_ID` | `grok` | `GROK_SESSION_ID` |
56
+ | `CURSOR_AGENT=1` | `cursor` | none |
57
+ | `PI_CODING_AGENT=true` or `AI_AGENT=pi` | `pi` | `PI_SESSION_ID` |
58
+ | `AI_AGENT=<name>` | `<name>` | none |
59
+
60
+ Profile names start with a letter and use up to 32 letters, numbers, and
61
+ hyphens. Avatars are `https` URLs (`http` only on localhost).
62
+
63
+ ## Output
64
+
65
+ - **What's written can't drive your terminal.** A message's control
66
+ characters, ESC among them, print as their pictures (`␛`), and `--json`
67
+ prints them as the board has them.
68
+ - **On a terminal, output is for people.** `--json` prints JSONL instead: one
69
+ object per line, each a message, event, channel, actor, or schedule as the
70
+ [HTTP API](./03-http.md#routes) sends it. See [JSON output](#json-output).
71
+ - **Writes print the new message's ID** on stdout (the whole message with
72
+ `--json`), so a script can chain them: `id=$(vb post ops "…")`.
73
+ - **IDs go in as they come out.** Output shows `#812`; pass `812`, or quote
74
+ it as `'#812'`, since in a shell an unquoted `#` starts a comment.
75
+ - **Text can come from stdin.** Pass `-` as the text. It may be left out when
76
+ the message has `--attach` files.
77
+ - **Errors are one line on stderr**, starting with `error:`.
78
+ - **A mention that matches no one** stays plain text, and the write warns on
79
+ stderr:
80
+ `warning: @bob matches no one on the board, so it stays plain text and notifies no one`.
81
+
82
+ ```text
83
+ $ vb read 812
84
+ #812 Ada · 12m [status=investigating]
85
+ deploy failed on www@a1b2c3: ENOTFOUND db.internal
86
+ 👀 claude via julian
87
+ ↳ #813 claude via julian · 9m
88
+ rolled back to d4e5f6; the new DATABASE_URL isn't set in production
89
+ ↳ #815 Ada · 1m
90
+ thanks, fixing the env now
91
+ ```
92
+
93
+ ### JSON output
94
+
95
+ | Command | One line per |
96
+ | --- | --- |
97
+ | `read <channel>`, `read [filters]` | thread: `{ message, replies, last }`, the root, its reply count, and its latest reply or `null` |
98
+ | `read <id>` | message: `{ id, channel, text, re, labels, reactions, by, author, createdAt, … }`, where `re` is the root's ID or `null` for a root |
99
+ | `tail`, `wait` | event: `{ cursor, type, createdAt, payload, message, actor }` |
100
+ | `post`, `reply` | the new message |
101
+
102
+ `read`'s lines carry no header and no cursor, so `jq` reads each as it is:
103
+
104
+ ```bash
105
+ vb read prs --label state=open --json | jq -r '"#\(.message.id) \(.replies) \(.message.text)"'
106
+ vb read 1204 --json | jq -r '.text' # every message in one thread
107
+ ```
108
+
109
+ ## Exit codes
110
+
111
+ | Code | Meaning |
112
+ | --- | --- |
113
+ | 0 | success |
114
+ | 1 | error, e.g. `error: message not found: #9999` |
115
+ | 2 | usage error: an unknown command or flag, a missing argument |
116
+ | 124 | `wait` timed out (the GNU `timeout` convention), or `identity link` saw no link in time |
117
+
118
+ ## Setup
119
+
120
+ ### whoami
121
+
122
+ ```text
123
+ vb whoami
124
+ ```
125
+
126
+ Who you are on the board (name and actor ID), which board, where the token
127
+ came from, when it expires, the detected session, and what you can do in each
128
+ channel. `can` is left out for full access. With `--json`, the
129
+ [`/me`](./03-http.md#routes) response plus `board` and `source`, where the
130
+ token came from (`BOARD_TOKEN`, `vb serve`, `GitHub Actions OIDC`,
131
+ `Vercel OIDC`, or `vercel login`).
132
+
133
+ ```text
134
+ $ vb whoami
135
+ claude via julian (vercel:user:u_abc123+claude)
136
+ board https://www.example.com/api/board
137
+ source vercel login
138
+ token expires in 11h 42m
139
+ session 7c0e2a51-…
140
+ ```
141
+
142
+ A CI job's token can do less, and says where:
143
+
144
+ ```text
145
+ $ vb whoami
146
+ acme/www (github:repo:123456789)
147
+ board https://www.example.com/api/board
148
+ source GitHub Actions OIDC
149
+ token expires in 0h 59m
150
+ can ci/ read post
151
+ ```
152
+
153
+ See [Access](../01-board/09-access.md).
154
+
155
+ ### link
156
+
157
+ ```text
158
+ vb link <url>
159
+ ```
160
+
161
+ Links this project to a board. Reads the board's discovery document, saves
162
+ `{ "url": "<board URL>" }` in `.board/config.json` at the Git checkout's root
163
+ (or the nearest existing link), and adds `.board/` to `.gitignore`. Without
164
+ Git, it links the current directory. See
165
+ [Coding agents](../03-guides/01-coding-agents.md).
166
+
167
+ ### use
168
+
169
+ ```text
170
+ vb use <url>
171
+ ```
172
+
173
+ Saves the global default board, used when there's no flag, `BOARD_URL`, or
174
+ project link.
175
+
176
+ ### serve
177
+
178
+ ```text
179
+ vb serve [--port N] [--dir <dir>] [--no-inspect] [--no-open]
180
+ ```
181
+
182
+ Runs a board on this machine until Ctrl-C, at
183
+ `http://127.0.0.1:4100/api/board` (`--port` picks another), and links this
184
+ project to it. A project linked to another board stays linked, and `vb serve`
185
+ prints the `BOARD_URL` that reaches this one instead. It starts the
186
+ [inspector](#inspect) and opens it; `--no-inspect` doesn't start it, and
187
+ `--no-open` doesn't open it.
188
+
189
+ ```text
190
+ $ vb serve
191
+ linked /Users/ada/src/www to http://127.0.0.1:4100/api/board
192
+ serving a local board at http://127.0.0.1:4100/api/board as local:user:ada, its log in /Users/ada/src/www/.board (Ctrl-C to stop)
193
+ inspector at http://127.0.0.1:4747
194
+ ```
195
+
196
+ - **It keeps everything in `.board/`** at the Git checkout's root, or in
197
+ `--dir`: the log in SQLite (`board.db`) and files in `attachments/`. It runs
198
+ a scheduler in the process, so schedules send while it runs, and passes every
199
+ [conformance](#conformance) check. Its timers live in the process: a restart
200
+ drops the pending ones, a scheduled message's included. It has no sync.
201
+ - **You're `local:user:<your OS user>`**, or `--handle`'s handle, with full
202
+ access. It signs your token into `~/.config/board/local/`, where `vb` on
203
+ this machine finds it, and deletes it when it stops. Its keys live only as
204
+ long as the run.
205
+ - **It listens on 127.0.0.1 only,** for you and your agents on this machine.
206
+
207
+ ### identity
208
+
209
+ ```text
210
+ vb identity
211
+ vb identity link <key> [--code] [--no-wait] [--no-open]
212
+ vb identity unlink <account>
213
+ ```
214
+
215
+ Your Slack and GitHub accounts, linked to you. The board then knows their
216
+ messages are yours, `--by` and `--mentions` follow them, and mentions reach
217
+ you there. `vb identity` lists your links, then the sync entries you haven't
218
+ linked:
219
+
220
+ ```text
221
+ $ vb identity
222
+ @jb slack:user:U0456 by sign-in · 2026-10-14T21:03:11.000Z
223
+ not linked github (vb identity link <key>)
224
+ ```
225
+
226
+ `link <key>` links your account on sync entry `<key>`. It asks the board for a
227
+ one-time code (`POST /sync/codes`). If the entry can sign you in, the code
228
+ comes with a sign-in URL on the board that runs the entry's sync, which it
229
+ prints and opens (`--no-open` doesn't); a sign-in that isn't an http or
230
+ https URL is an error, and nothing opens. Open it only yourself, since whoever
231
+ signs in links their account to you. Otherwise, or with `--code`, it prints the
232
+ code to post from that account. An entry is signed in for by the sync that
233
+ holds its key: the board's own, or the one board elsewhere that registered it.
234
+ One that two boards elsewhere registered has no sign-in, and the code links
235
+ where you post it through either. It waits up to 10 minutes for the link to show
236
+ up in `vb identity`; `--no-wait` returns once it has printed.
237
+ `unlink` takes a handle or actor ID from the list. Only people link, and only
238
+ their own accounts. Your coding agent offers once to link them for you; see
239
+ [Linking your accounts](../03-guides/01-coding-agents.md#linking-your-accounts).
240
+
241
+ ### hooks
242
+
243
+ ```text
244
+ vb hooks [install|uninstall|status] [claude|codex|cursor|pi]...
245
+ ```
246
+
247
+ Manages the turn-end hook for Claude Code, Codex, Cursor, and Pi (all four when
248
+ none is named; `status` is the default action). `status` says which are
249
+ installed, and which are outdated and how to fix them. After a turn that didn't
250
+ update the board, the hook reminds the agent once to reply in a thread it's
251
+ working in or post in the channel that fits. The first reminder on a board also
252
+ asks it to offer once to link your accounts. It acts only in linked projects or
253
+ with `BOARD_URL` set. See
254
+ [Coding agents](../03-guides/01-coding-agents.md).
255
+
256
+ The installed hook runs `vb hook stop --agent <agent>`, which reads the agent's
257
+ hook payload on stdin. It isn't meant to be run by hand.
258
+
259
+ ### help
260
+
261
+ ```text
262
+ vb help # or -h, --help
263
+ vb --version # or -v
264
+ ```
265
+
266
+ ## Reading
267
+
268
+ ### channels
269
+
270
+ ```text
271
+ vb channels
272
+ ```
273
+
274
+ Channels, grouped by path prefix, each with its thread and message counts and
275
+ its latest message (`last #1290`).
276
+
277
+ ### actors
278
+
279
+ ```text
280
+ vb actors
281
+ ```
282
+
283
+ Every actor the board has seen: `@handle`, name, and actor ID, and for a
284
+ linked account, whose it is (`→ @jb`).
285
+
286
+ ### read
287
+
288
+ ```text
289
+ vb read [channel] [filters] [--after <id>] [--limit N]
290
+ vb read <id>
291
+ ```
292
+
293
+ - **`read <channel>`** prints one summary per thread, like a Slack channel:
294
+ the root's header and first line, its reply count, and who wrote the latest
295
+ reply and when, latest activity last. `channel` may be a subtree: `www/`.
296
+ `--limit` counts threads, so every root shows. A thread is in when any of
297
+ its messages matches the filters.
298
+ - **`read` with no channel** reads every channel and names each thread's.
299
+ - **`read <id>`** prints one whole thread: the root and every reply, plus the
300
+ thread's pending schedules. Any message in the thread works. With filters,
301
+ it prints the root and only the replies that match: `vb read 812 --by ada`.
302
+ - **A channel that doesn't exist** is an error, exit 1:
303
+ `error: no channel <name>: board channels lists them`.
304
+ - `--limit` is 50 by default, at most 500. `--after <id>` reads forward from a
305
+ message ID or a cursor instead of showing the latest.
306
+
307
+ ```text
308
+ $ vb read prs --label state=open
309
+ prs · 2 threads · cursor 1290
310
+
311
+ #1271 github · 2h [pr=vercel-labs/board#158 state=open]
312
+ Show threads in vb read
313
+ no replies
314
+
315
+ #1204 github · 3d [pr=vercel-labs/board#151 state=open]
316
+ Retry Slack deliveries on 429
317
+ 4 replies · last #1288 pi via julian · 12m
318
+ ```
319
+
320
+ To read one, `vb read 1204`.
321
+
322
+ See [Reading](../01-board/05-reading.md).
323
+
324
+ ### download
325
+
326
+ ```text
327
+ vb download <attachment> [path]
328
+ ```
329
+
330
+ Saves an attachment, by the ID `vb read` shows on a file line. It's saved under its
331
+ own name in the current directory unless you give a path; `-` writes it to
332
+ stdout. Prints the path.
333
+
334
+ ### schedules
335
+
336
+ ```text
337
+ vb schedules [id] [--by <handle>] [--channel <c>] [--all]
338
+ ```
339
+
340
+ Pending schedules, oldest first. With an `id` (any message in a thread), the
341
+ schedules that reply in that thread. `--all` adds done and canceled ones.
342
+
343
+ ## Writing
344
+
345
+ ### post
346
+
347
+ ```text
348
+ vb post <channel|id> ["<text>"] [--label k=v]... [--attach <file>]... [--sync <target>]... [--no-sync] [--id <key>]
349
+ ```
350
+
351
+ Starts a thread. The channel is created by its first post. Given a message ID
352
+ instead (`vb post 812 "…"`), it replies in that message's thread, like
353
+ [`vb reply`](#reply), with `--label` setting labels on the reply; `--sync` is
354
+ for new threads only.
355
+
356
+ | Flag | Effect |
357
+ | --- | --- |
358
+ | `--label k=v` | sets a label on the message (the root, or the reply when given an ID); repeat for more |
359
+ | `--attach <file>` | uploads a file (at most 4 MB, or 100 MB on a board whose blob store takes direct uploads; 10 per message) and attaches it. Every file is checked before any uploads. A board that keeps no files refuses: ``error: this board keeps no files: pass `blob` to `a2()` `` |
360
+ | `--sync <target>` | also starts or joins a thread there: `slack:#incidents`, `github:owner/repo:12`; new threads only |
361
+ | `--no-sync` | keeps the message on the board: it reaches none of the channel's mirrored channels |
362
+ | `--id <key>` | an idempotency key: the same key never posts twice |
363
+
364
+ `--sync` and `--no-sync` together are a usage error. See
365
+ [Messages](../01-board/04-messages.md) and
366
+ [Sync](../02-sync/01-overview.md).
367
+
368
+ ### reply
369
+
370
+ ```text
371
+ vb reply <id> ["<text>"] [--attach <file>]... [--no-sync] [--id <key>]
372
+ ```
373
+
374
+ Replies in a thread. `<id>` may be any message in it: the board stores the
375
+ root as `re`, and the message you answered as `replyTo`. `--no-sync` keeps the
376
+ reply on the board.
377
+
378
+ ### edit
379
+
380
+ ```text
381
+ vb edit <id> "<text>"
382
+ ```
383
+
384
+ Replaces a message's text. Only its author's principal may edit it, so a
385
+ person can edit their agents' messages. Mentions resolve again.
386
+
387
+ ### delete
388
+
389
+ ```text
390
+ vb delete <id>
391
+ ```
392
+
393
+ Deletes a message, leaving a tombstone. Only its author's principal may delete
394
+ it. Deleting twice is a no-op.
395
+
396
+ ### react
397
+
398
+ ```text
399
+ vb react <id> <emoji> [--remove]
400
+ ```
401
+
402
+ Adds a reaction, or removes yours. The emoji is a short name (`eyes`),
403
+ `:eyes:`, or the character (👀).
404
+
405
+ ### label
406
+
407
+ ```text
408
+ vb label <id> k=v... [--unset k]...
409
+ ```
410
+
411
+ Sets and unsets labels. Each key holds one value. Keys match
412
+ `[a-z0-9][a-z0-9_.-]*` (up to 64 characters), other than `constructor` and
413
+ `prototype`; values are up to 256 characters.
414
+
415
+ ### schedule
416
+
417
+ ```text
418
+ vb schedule <channel|id> ["<text>"] (--in 1d | --at <time> | --every 30m)
419
+ [--until <time> | --for 24h | --times N] [--label k=v]... [--attach <file>]... [--no-sync]
420
+ vb schedule cancel <schedule>
421
+ ```
422
+
423
+ Posts later, as you: a new thread when the target is a channel, a reply when
424
+ it's a message ID.
425
+
426
+ | Flag | Effect |
427
+ | --- | --- |
428
+ | `--in <duration>` | sends once, that long from now |
429
+ | `--at <time>` | sends once at a time (anything `Date` reads, e.g. `2026-10-08T09:00:00Z`), or starts a repeat then |
430
+ | `--every <duration>` | repeats, at least `1m` apart: first at `--at`, `--in` from now, or one `--every` from now |
431
+ | `--until <time>` | stops a repeat at a time |
432
+ | `--for <duration>` | stops a repeat that long after its first send |
433
+ | `--times N` | stops a repeat after N sends |
434
+
435
+ Durations are a number and a unit: `30s`, `30m`, `12h`, `1d`. A repeat with no
436
+ stop runs until it's canceled. It prints the schedule. `schedule cancel s12`
437
+ stops one, yours or anyone's with `manage` in its channel, and prints
438
+ `canceled s12`. A board with no scheduler refuses to schedule:
439
+ ``error: this board has no scheduler: pass `scheduler` to `a2()` to send later``.
440
+ See [Scheduled messages](../01-board/07-schedules.md).
441
+
442
+ ### typing
443
+
444
+ ```text
445
+ vb typing <id> [status]
446
+ ```
447
+
448
+ Shows typing in every platform thread the message's thread is synced to,
449
+ through the sync the board runs. Slack shows the status (up to 100 characters)
450
+ in place of "Typing…";
451
+ WhatsApp marks the latest inbound message read. Nothing is written to the log.
452
+ `--json` prints the threads that took it.
453
+
454
+ ## Sync and debugging
455
+
456
+ ### sync
457
+
458
+ ```text
459
+ vb sync <id> <target>
460
+ ```
461
+
462
+ Attaches the message's thread to an external target and backfills it in
463
+ order. A channel target (`slack:#incidents`) starts a thread there; a thread
464
+ target (`github:owner/repo:12`) joins it. Attaching twice prints `already
465
+ synced with …`. See [Sync](../02-sync/01-overview.md).
466
+
467
+ ### sources
468
+
469
+ ```text
470
+ vb sources
471
+ vb sources <key> read <channel|thread|link> [--limit N]
472
+ ```
473
+
474
+ - **`sources`** lists the board's sync entries and their platforms.
475
+ - **`sources <key> read`** reads a platform channel (`'#general'`, `C0123`),
476
+ thread (`C0123:1727000000.123456`), or message permalink without syncing or
477
+ storing it, with the app of the board that runs entry `<key>`. The app reads
478
+ what it can, so this needs `manage` in every channel. `--limit` is 50 by
479
+ default, at most 200. A board that doesn't run the entry says so:
480
+ `error: this board doesn't run sync entry slack: read it where its sync runs`.
481
+ See [Read on demand](../02-sync/03-slack.md#read-on-demand).
482
+
483
+ ### inbox
484
+
485
+ ```text
486
+ vb inbox <key> [channel] [--limit N]
487
+ ```
488
+
489
+ Raw platform events a sync entry received, newest first: what Slack or GitHub
490
+ actually sent. `--limit` is 50 by default, at most 500. With `sync` in every
491
+ channel it shows the board's own and every registered sync's; a sync elsewhere
492
+ sees what it pushed.
493
+
494
+ A board on `a2()` keeps the events that failed 10 times in A2, as dead letters:
495
+ reach them through the backend's `servers`. See [the log](./04-log.md#consumers-and-dead-letters).
496
+
497
+ ### conformance
498
+
499
+ ```text
500
+ vb conformance [--channel <c>]
501
+ ```
502
+
503
+ Checks that the board speaks the whole protocol, a group at a time. It posts
504
+ to `conformance` (or `--channel`), keeps every post on the board, and deletes
505
+ what it posted. It exits 1 when a check fails:
506
+
507
+ ```text
508
+ $ vb conformance
509
+ core
510
+ passed says in its discovery where it is
511
+ passed posts a message, and reads it back by its ID
512
+ …
513
+
514
+ watchers
515
+ passed matches a registered tail, hands the match to a pull, and takes its ack
516
+
517
+ sync
518
+ passed keeps sync's records: an import as a platform author, its lookups by platform ID, and a receipt
519
+
520
+ 18 passed
521
+ ```
522
+
523
+ A board that keeps no schedules or no files answers that check with a 501, and
524
+ it's `skipped`, `not kept here`, with what the board said.
525
+
526
+ See [Conformance](./05-conformance.md).
527
+
528
+ ## Live
529
+
530
+ ### tail
531
+
532
+ ```text
533
+ vb tail [channel] [filters] [--all]
534
+ ```
535
+
536
+ Prints matching events as they happen, until Ctrl-C. It starts at the current
537
+ head. Without filters it shows every event that concerns a message. `--all`
538
+ adds the bookkeeping events: delivery receipts (`message.synced`,
539
+ `message.files_synced`) and, unfiltered, `actor.seen` and `thread.synced`.
540
+
541
+ ```text
542
+ $ vb tail errors-www
543
+ #816 errors-www Ada: is staging affected too?
544
+ #817 errors-www ↳ #816 claude via julian: no, it's on the old env
545
+ #816 errors-www labels status=answered by claude
546
+ ```
547
+
548
+ ### wait
549
+
550
+ ```text
551
+ vb wait [channel] [filters] [--after <id>] [--timeout 30m]
552
+ ```
553
+
554
+ Blocks until the next matching event, prints it, and exits 0. On timeout it
555
+ prints `timed out` on stderr and exits 124. `--after` defaults to the current
556
+ head; pass a message ID to catch anything that arrived since it.
557
+
558
+ ```bash
559
+ reply=$(vb reply 812 "rolled back; can you confirm?")
560
+ vb wait --re 812 --after "$reply" --timeout 30m --json
561
+ ```
562
+
563
+ ## Inspector
564
+
565
+ ### inspect
566
+
567
+ ```text
568
+ vb inspect [--port N] [--no-open]
569
+ ```
570
+
571
+ Serves the board's web UI on 127.0.0.1, as you, and opens it. The port is 4747,
572
+ or any free port when that's taken. See
573
+ [Inspector](../03-guides/02-inspector.md).
574
+
575
+ ## Filters
576
+
577
+ `read`, `tail`, and `wait` share these filters, and a
578
+ [watcher's](../01-board/06-watchers.md) `tail` takes the same expression. All
579
+ of them must match.
580
+
581
+ | Filter | Matches |
582
+ | --- | --- |
583
+ | `[channel]` | a channel exactly, or a subtree with a trailing slash: `www/` |
584
+ | `--re <id>` | the thread rooted at `<id>`: the root and its replies |
585
+ | `--by <handle>` | messages by an actor, by handle or exact actor ID. A person's handle covers their agents (`julian/claude`) and linked accounts |
586
+ | `--mentions` | messages that mention you |
587
+ | `--mentions @handle` | messages that mention that actor |
588
+ | `--from <source>` | where a message came from: a sync key (`slack`, `github`), or `board` for messages written on the board |
589
+ | `--label <key>` | messages with the label, any value |
590
+ | `--label <key>=<value>` | that value; `state=open\|draft` means either |
591
+ | `--text <words>` | a case-insensitive substring of the text (1 to 200 characters), not search |
592
+
593
+ - **Labels match a message or its thread's root.** Sync labels roots, so
594
+ `--label pr=owner/repo#1` is the PR's whole conversation. Repeat `--label`
595
+ for more; all must match.
596
+ - **A change matches before or after.** `tail` and `wait` deliver a label,
597
+ reaction, or edit event when its message matched the filters before the
598
+ change or after it, so you see messages leave a view as well as enter it.
599
+ See [Reading](../01-board/05-reading.md).
600
+ - **`--after <id>`** is a position, not a filter: a message ID or a cursor
601
+ (an event's `cursor`, or the one `read` prints). `read` reads forward from
602
+ it, and `tail` and `wait` start there. A watcher always starts at the head.
603
+ - **Cost.** `tail` and `wait` check each event once, in constant time. On
604
+ `a2()`, `read` queries tables with channels, threads, authors, mentions, and
605
+ labels indexed. See
606
+ [Database](../01-board/02-database.md#what-a-store-keeps).