@vercel/board 0.29.0 → 0.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -17
- package/dist/{a2-HUbQJ0RD.mjs → a2-DngWY2Eb.mjs} +741 -415
- package/dist/access-CY5xVMIK.mjs +242 -0
- package/dist/{access-CV96SYgr.d.mts → access-DPcrKIH_.d.mts} +6 -1
- package/dist/access.d.mts +4 -4
- package/dist/access.mjs +5 -4
- package/dist/{actors-DcJhFbwJ.mjs → actors-Bm-ojWWT.mjs} +2 -2
- package/dist/{auth-JO22iIwr.mjs → auth-CyO2aw5n.mjs} +126 -236
- package/dist/{auth-CU56Q2Xh.d.mts → auth-DLYSt_-A.d.mts} +32 -4
- package/dist/{backend-CPC_vyOo.d.mts → backend-Bob-5CM7.d.mts} +93 -42
- package/dist/backend-a2/blob-vercel.d.mts +1 -1
- package/dist/backend-a2/contract.d.mts +38 -98
- package/dist/backend-a2/contract.mjs +1 -1
- package/dist/backend-a2/scheduler-local.d.mts +1 -1
- package/dist/backend-a2/scheduler-local.mjs +1 -1
- package/dist/backend-a2/scheduler-qstash.d.mts +3 -3
- package/dist/backend-a2/scheduler-qstash.mjs +8 -8
- package/dist/backend-a2/scheduler-vercel.d.mts +3 -3
- package/dist/backend-a2/scheduler-vercel.mjs +6 -6
- package/dist/backend-a2/store-memory.d.mts +2 -2
- package/dist/backend-a2/store-memory.mjs +3 -3
- package/dist/backend-a2/store-postgres.d.mts +1 -1
- package/dist/backend-a2/store-postgres.mjs +34 -15
- package/dist/backend-a2/store-redis-http.d.mts +1 -1
- package/dist/backend-a2/store-redis-http.mjs +6 -6
- package/dist/backend-a2/store-redis.d.mts +2 -2
- package/dist/backend-a2/store-redis.mjs +5 -5
- package/dist/backend-a2/store-sqlite.d.mts +1 -1
- package/dist/backend-a2/store-sqlite.mjs +3 -3
- package/dist/backend-a2.d.mts +143 -242
- package/dist/backend-a2.mjs +2 -2
- package/dist/{backend-http-CqfKhrnn.d.mts → backend-http-BRrRbjRx.d.mts} +59 -45
- package/dist/{backend-http-CzIJMm58.mjs → backend-http-CvUKOn-m.mjs} +11 -10
- package/dist/{backend-http-Bmp0IgD3.mjs → backend-http-D8mUzuKQ.mjs} +2 -2
- package/dist/backend-http.d.mts +1 -1
- package/dist/backend-http.mjs +1 -1
- package/dist/backend-http.node.d.mts +1 -1
- package/dist/backend-http.node.mjs +1 -1
- package/dist/{backend-vercel-DQc_TaHp.mjs → backend-vercel-Ba8JDtqW.mjs} +3 -3
- package/dist/{backend-vercel-4tvcPKqX.d.mts → backend-vercel-BaSEUdwg.d.mts} +62 -47
- package/dist/{backend-vercel-BNauAs7w.mjs → backend-vercel-CmY9yZ9m.mjs} +279 -42
- package/dist/backend-vercel.d.mts +1 -1
- package/dist/backend-vercel.mjs +1 -1
- package/dist/backend-vercel.node.d.mts +1 -1
- package/dist/backend-vercel.node.mjs +1 -1
- package/dist/{blob-DmiQjrYy.d.mts → blob-Bx8QPWEd.d.mts} +2 -2
- package/dist/{blob-DdbpJqN1.mjs → blob-WbB2DJKn.mjs} +30 -11
- package/dist/{chat-provider-BWHbChcQ.d.mts → chat-provider-BChYrrA7.d.mts} +32 -28
- package/dist/{chat-provider-COtsU5jB.mjs → chat-provider-DkFp2hrh.mjs} +105 -60
- package/dist/cli-commands.d.mts +1 -1
- package/dist/cli-commands.mjs +1 -1
- package/dist/cli-embedded.d.mts +2 -2
- package/dist/cli-embedded.mjs +1 -1
- package/dist/cli.mjs +4 -4
- package/dist/{client-BqAjk0Q4.d.mts → client-COfYrgfV.d.mts} +240 -227
- package/dist/{commands-D6gvrOh3.d.mts → commands-DK4W3HCJ.d.mts} +47 -10
- package/dist/{commands-C3R1AWgm.mjs → commands-L1JSGz2Z.mjs} +212 -54
- package/dist/conformance-DK43HiHf.mjs +1000 -0
- package/dist/conformance.d.mts +8 -2
- package/dist/conformance.mjs +1 -1
- package/dist/{engine-DQxXk7dX.mjs → engine-DurYIRt8.mjs} +29 -210
- package/dist/{errors-DDktVeUa.d.mts → errors-2vGs49Db.d.mts} +1 -1
- package/dist/{client-D0Px7KzF.mjs → errors-CJXhhzvT.mjs} +132 -27
- package/dist/{provider-BaVmi4Hi.mjs → events-lujPA3gQ.mjs} +80 -17
- package/dist/{exports-BG-0Wyn-.mjs → exports-CWIdN0Ub.mjs} +195 -103
- package/dist/exports-FBOQWT7O.d.mts +5 -0
- package/dist/{folds-BDMSxBUf.mjs → folds-Cu9Q9zyQ.mjs} +3 -22
- package/dist/{folds-ZkHM4M-v.d.mts → folds-DLUzfibJ.d.mts} +15 -40
- package/dist/{index-D3VvNfh_.d.mts → index-Ho-ayyQL.d.mts} +2 -2
- package/dist/index.d.mts +7 -7
- package/dist/{index.default-rB8sJ9RX.mjs → index.default-BUcwDaAe.mjs} +1 -1
- package/dist/index.mjs +5 -6
- package/dist/index.node.d.mts +7 -7
- package/dist/index.node.mjs +3 -4
- package/dist/inspect/app.js +62 -61
- package/dist/inspect/engine.js +3 -3
- package/dist/{internal-CvsrprnU-CRY89I-b.mjs → internal-CvsrprnU-zZMpDiRY.mjs} +1 -1
- package/dist/{local-DhkrAqPP.mjs → local-C76Fy5ad.mjs} +1 -1
- package/dist/{main-CVBqe3VO.mjs → main-Dz-8komG.mjs} +251 -127
- package/dist/{middleware-CXjEeef4.mjs → middleware-BavYSDUa.mjs} +1 -1
- package/dist/{node-Zd_C-gxP.mjs → node-BgeGcwX0.mjs} +7 -3
- package/dist/{platform-B4TnJtWu-CwcTyKg2.mjs → platform-B4TnJtWu-Do3agKr5.mjs} +1 -1
- package/dist/{project-CBXlLftU.mjs → project-CQ4VUibA.mjs} +1 -1
- package/dist/{provider-DmSTp9lI.d.mts → provider-gynDUoyW.d.mts} +13 -43
- package/dist/{redis-tables-BJQVUspu.mjs → redis-tables-DHf85ixI.mjs} +40 -42
- package/dist/{reducer-CjmvZSAb-D_OSISmj.mjs → reducer-Bswr11ar-P324o9vn.mjs} +2 -2
- package/dist/{reducer-CYPHsSmd-BQIfozGi.d.mts → reducer-CYPHsSmd-BXjGiptm.d.mts} +1 -1
- package/dist/{reducers-DJzVpXwM.mjs → reducers-DcENwtwZ.mjs} +74 -77
- package/dist/{retryable-lazy-DZWmHpii-Bye-W8q7.mjs → retryable-lazy-DZWmHpii-DpTc5PIP.mjs} +1 -1
- package/dist/{scheduler-task-X1SToDjP-BVoWuInI.mjs → scheduler-task-X1SToDjP-D2Il2rln.mjs} +1 -1
- package/dist/{serve-G9RuaDSr.mjs → serve-CTmow-YH.mjs} +7 -7
- package/dist/{server-qgez0Xny-D5mLfqFQ.d.mts → server-B9gu0qi1-DI1WvcVK.d.mts} +10 -4
- package/dist/{server-BixV_byP.mjs → server-DG8D6VV0.mjs} +4 -4
- package/dist/{server-DtFrWxi3-Boa6gdWO.mjs → server-DX7mM6nm-D7N_7bug.mjs} +32 -23
- package/dist/{serving-B75Y4BF-.mjs → serving-DBFRo6U_.mjs} +1 -1
- package/dist/{session-clear-wHd5GCpZ-BnKSxReK.mjs → session-clear-XJYpmUpg-2q8l_FJR.mjs} +14 -2
- package/dist/{sql-D4Z_efJX.mjs → sql-CT5xVuGr.mjs} +2 -8
- package/dist/{sqlite-db-Bz2JkfCU.mjs → sqlite-db-0WeSOEZN.mjs} +1 -1
- package/dist/{store-D0MhBvg6-4Z2L0Kqd.d.mts → store-DEzcHZOA-B-5vhxef.d.mts} +12 -2
- package/dist/store-N8PXxDAS-DWWgv-Vz.mjs +5 -0
- package/dist/{store-codec-DwrWlgQx-CYxGW0aD.mjs → store-codec-pAG2oPgQ-SqDXlPBR.mjs} +4 -4
- package/dist/store-memory-4IBl0Ztu.mjs +2 -0
- package/dist/{store-memory-Bas9Uq5C.mjs → store-memory-B0PkVSuQ.mjs} +14 -7
- package/dist/{store-polling-BZt2Rf3D-BiEM6_HB.mjs → store-polling-BZt2Rf3D-Div-bJ_D.mjs} +2 -2
- package/dist/{store-presence-polling-JuAy2Ck2-CYpuK75X.mjs → store-presence-polling-JuAy2Ck2-Dh-g_KYM.mjs} +3 -3
- package/dist/{store-sqlite-CxxPCcCT.mjs → store-sqlite-D85t3tE7.mjs} +14 -7
- package/dist/store-sqlite-iBTYYKBj.mjs +2 -0
- package/dist/{stream-buffer-DrTyiIuo-BKy-vI14.mjs → stream-buffer-DrTyiIuo-Be10yOBR.mjs} +2 -2
- package/dist/{sync-kapso.d.mts → sync-chat-sdk.d.mts} +33 -37
- package/dist/sync-chat-sdk.mjs +44 -0
- package/dist/sync-github.d.mts +5 -18
- package/dist/sync-github.mjs +4 -70
- package/dist/sync-linear.d.mts +3 -3
- package/dist/sync-linear.mjs +6 -5
- package/dist/sync-slack.d.mts +7 -7
- package/dist/sync-slack.mjs +33 -54
- package/dist/sync.d.mts +3 -3
- package/dist/sync.mjs +2 -4
- package/dist/{tokens-CC3izSNr.mjs → tokens-Dnyd_y1n.mjs} +15 -6
- package/dist/{types-No0WdhE5.d.mts → types-CmrFjpml.d.mts} +2 -4
- package/dist/{watchers-hash-BDhLVkV5.mjs → watchers-hash-BjwlnGD9.mjs} +124 -115
- package/docs/01-board/01-mounting.md +2 -2
- package/docs/01-board/03-identity.md +5 -6
- package/docs/01-board/04-messages.md +96 -2
- package/docs/01-board/05-reading.md +2 -1
- package/docs/01-board/06-watchers.md +8 -6
- package/docs/01-board/08-middleware.md +6 -3
- package/docs/01-board/09-access.md +145 -14
- package/docs/02-sync/01-overview.md +19 -26
- package/docs/02-sync/02-functions.md +8 -9
- package/docs/02-sync/03-slack.md +5 -5
- package/docs/02-sync/04-github.md +2 -2
- package/docs/02-sync/06-providers.md +91 -62
- package/docs/03-guides/01-coding-agents.md +1 -1
- package/docs/03-guides/03-ci.md +7 -4
- package/docs/04-reference/01-cli.md +109 -51
- package/docs/04-reference/02-board.md +76 -28
- package/docs/04-reference/03-http.md +76 -38
- package/docs/04-reference/04-log.md +8 -11
- package/docs/04-reference/05-conformance.md +47 -7
- package/docs/index.md +2 -2
- package/package.json +7 -10
- package/sql/postgres/board.sql +1 -1
- package/sql/postgres/schema.sql +1 -1
- package/dist/conformance-DlnfuzEB.mjs +0 -556
- package/dist/errors-CusN4x2l.mjs +0 -88
- package/dist/events-qf5XG5VQ.mjs +0 -75
- package/dist/exports-mhAYiU4i.d.mts +0 -5
- package/dist/store-N8PXxDAS-BWBgzBHp.mjs +0 -5
- package/dist/store-memory-ZLiZbH3R.mjs +0 -2
- package/dist/store-sqlite-BmwL3pUR.mjs +0 -2
- package/dist/sync-kapso.mjs +0 -89
- package/dist/sync.chat.d.mts +0 -2
- package/dist/sync.chat.mjs +0 -2
- package/dist/users-yryHKFMo.mjs +0 -42
- package/docs/02-sync/05-whatsapp.md +0 -152
|
@@ -81,7 +81,7 @@ handle, attach proof with `--attach`, and wait for an answer with
|
|
|
81
81
|
to install it in the project.
|
|
82
82
|
|
|
83
83
|
Posting writes to the shared board, and from there to any Slack, GitHub, or
|
|
84
|
-
|
|
84
|
+
Linear thread it syncs with. The skill tells agents to stay within what you
|
|
85
85
|
asked for: having a board doesn't authorize them to message the team.
|
|
86
86
|
|
|
87
87
|
## Agents post as themselves
|
package/docs/03-guides/03-ci.md
CHANGED
|
@@ -209,8 +209,10 @@ handle.
|
|
|
209
209
|
## From anywhere else
|
|
210
210
|
|
|
211
211
|
`BOARD_TOKEN` takes a token you minted yourself, and the CLI and `board()`
|
|
212
|
-
send it as is. It must be one the board verifies:
|
|
213
|
-
|
|
212
|
+
send it as is. It must be one the board verifies: a
|
|
213
|
+
[board token](../01-board/09-access.md#tokens) from `vb tokens create`, or an OIDC
|
|
214
|
+
token whose audience is the board's URL, from the board's own project or a
|
|
215
|
+
caller a rule in `access` or a [grant](../01-board/09-access.md#grants)
|
|
214
216
|
matches. Discovery tells you the audience:
|
|
215
217
|
|
|
216
218
|
```bash
|
|
@@ -219,8 +221,9 @@ curl -s https://team.example/api/board | jq -r .audience
|
|
|
219
221
|
```
|
|
220
222
|
|
|
221
223
|
Another CI's tokens work through `oidc()`, which pins the issuer and the
|
|
222
|
-
claims to match
|
|
223
|
-
|
|
224
|
+
claims to match, or through a grant with the same issuer and claims,
|
|
225
|
+
`vb grants create https://gitlab.com --claim project_path=acme/api --claim ref=main --can deploys=post`.
|
|
226
|
+
In GitLab, a job's `id_tokens` with the board's audience is the token:
|
|
224
227
|
|
|
225
228
|
```ts app/api/board/[[...path]]/route.ts
|
|
226
229
|
import { board } from '@vercel/board'
|
|
@@ -105,7 +105,7 @@ await vb(team, ['reply', '42', 'on it', '--attach', 'shot.png'], {
|
|
|
105
105
|
|
|
106
106
|
Its arguments, output, and exit codes are the CLI's. The handle says which
|
|
107
107
|
board and who, so `--url` and `--handle` don't apply, and the commands about
|
|
108
|
-
your machine (`whoami`, `
|
|
108
|
+
your machine (`whoami`, `hooks`, `serve`, `inspect`) answer 2. Files come from its options: `readFile` for `--attach`,
|
|
109
109
|
`writeFile` for `download`, `stdin` for `-`, and `signal` stops `tail` and
|
|
110
110
|
`wait`. Calls run one at a time, in order, so a running `tail` or `wait` holds
|
|
111
111
|
every later call until it ends: they answer 2 without a `signal`. `refuse`
|
|
@@ -257,7 +257,7 @@ error document:
|
|
|
257
257
|
| Code | Meaning |
|
|
258
258
|
| --- | --- |
|
|
259
259
|
| 0 | success |
|
|
260
|
-
| 1 | it failed: `error: Couldn't find message #9999.`, or `wait`
|
|
260
|
+
| 1 | it failed: `error: Couldn't find message #9999.`, or `wait` timed out (`reason: "timeout"`) |
|
|
261
261
|
| 2 | misuse: an unknown command or option, a missing or malformed argument, or no board to use |
|
|
262
262
|
| 130 | Ctrl-C stopped `tail` or `wait` |
|
|
263
263
|
|
|
@@ -328,39 +328,6 @@ inspector at http://127.0.0.1:4747
|
|
|
328
328
|
long as the run.
|
|
329
329
|
- **It listens on 127.0.0.1 only,** for you and your agents on this machine.
|
|
330
330
|
|
|
331
|
-
### identity
|
|
332
|
-
|
|
333
|
-
```text
|
|
334
|
-
vb identity
|
|
335
|
-
vb identity link <key> [--code] [--no-wait] [--no-open]
|
|
336
|
-
vb identity unlink <account>
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
Your Slack and GitHub accounts, linked to you. The board then knows their
|
|
340
|
-
messages are yours, `--by` and `--mentions` follow them, and mentions reach
|
|
341
|
-
you there. `vb identity` lists your links, then the sync entries you haven't
|
|
342
|
-
linked:
|
|
343
|
-
|
|
344
|
-
```text
|
|
345
|
-
$ vb identity
|
|
346
|
-
@jb slack:user:U0456 by sign-in · 2026-10-14T21:03:11.000Z
|
|
347
|
-
not linked github (vb identity link <key>)
|
|
348
|
-
```
|
|
349
|
-
|
|
350
|
-
`link <key>` links your account on sync entry `<key>`. It asks the board for a
|
|
351
|
-
one-time code (`POST /sync/codes`). If the entry can sign you in, the code
|
|
352
|
-
comes with a sign-in URL on the board that runs the entry's sync, which it
|
|
353
|
-
prints and opens (`--no-open` doesn't); a sign-in that isn't an http or
|
|
354
|
-
https URL is an error, and nothing opens. Open it only yourself, since whoever
|
|
355
|
-
signs in links their account to you. Otherwise, or with `--code`, it prints the
|
|
356
|
-
code to post from that account. An entry is signed in for by the sync that
|
|
357
|
-
holds its key: the board's own, or the one board elsewhere that registered it.
|
|
358
|
-
One that two boards elsewhere registered has no sign-in, and the code links
|
|
359
|
-
where you post it through either. It waits up to 10 minutes for the link to show
|
|
360
|
-
up in `vb identity`; `--no-wait` returns once it has printed.
|
|
361
|
-
`unlink` takes a handle or actor ID from the list. Only people link, and only
|
|
362
|
-
their own accounts.
|
|
363
|
-
|
|
364
331
|
### hooks
|
|
365
332
|
|
|
366
333
|
```text
|
|
@@ -405,16 +372,95 @@ examples:
|
|
|
405
372
|
options every command takes: board help
|
|
406
373
|
```
|
|
407
374
|
|
|
375
|
+
## Access
|
|
376
|
+
|
|
377
|
+
### tokens
|
|
378
|
+
|
|
379
|
+
```text
|
|
380
|
+
vb tokens [list]
|
|
381
|
+
vb tokens create <name> [--can <scope>] [--profile <name>] [--session <id>] [--expires 30d]
|
|
382
|
+
vb tokens revoke <token>
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
`tokens create` mints a token that acts as you, narrowed to `--can`, and
|
|
386
|
+
prints its secret, which shows only here, so it pipes:
|
|
387
|
+
`BOARD_TOKEN=$(vb tokens create widget --can feedback/=post)`. `--can` is
|
|
388
|
+
`x-board-scope`'s syntax (`dev/=read,post;ci/=read`). `--profile` and
|
|
389
|
+
`--session` pin what every request with it writes as. It lives 30 days, or
|
|
390
|
+
`--expires`, 90 days at most. See [Tokens](../01-board/09-access.md#tokens).
|
|
391
|
+
|
|
392
|
+
| Flag | Effect |
|
|
393
|
+
| --- | --- |
|
|
394
|
+
| `--can <scope>` | what it may do at most: `feedback/=post`, `dev/=read,post;ci/=read`, or `*=all`. Without it, read, post, react, and label everywhere |
|
|
395
|
+
| `--profile <name>` | the profile every request with it writes as |
|
|
396
|
+
| `--session <id>` | the session every request with it writes from |
|
|
397
|
+
| `--expires <duration>` | how long it lives: `1h`, `7d`, up to `90d` |
|
|
398
|
+
|
|
399
|
+
`vb tokens` lists your live tokens, or with `manage` in every channel,
|
|
400
|
+
everyone's: each one's ID, name, what it may do now, and when it expires, never a
|
|
401
|
+
secret. With `BOARD_TOKEN` set, yours are that token and those minted with it. `tokens revoke t1` stops one and every token minted with it, and prints
|
|
402
|
+
`revoked t1`.
|
|
403
|
+
|
|
404
|
+
```text
|
|
405
|
+
$ vb tokens
|
|
406
|
+
t1 feedback-widget feedback/=post expires in 30d
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### grants
|
|
410
|
+
|
|
411
|
+
```text
|
|
412
|
+
vb grants [list]
|
|
413
|
+
vb grants create <issuer> --claim <name=value>... [--can <scope>]
|
|
414
|
+
vb grants revoke <grant>
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
`grants create` lets in tokens from an OIDC issuer (`vercel`, `github`, or an
|
|
418
|
+
https URL) that carry every `--claim`. A name given twice is any of its values.
|
|
419
|
+
Creating one with the same issuer and claims again changes its `--can` and
|
|
420
|
+
prints `updated g3`. `--can` left out is read, post, react, and label
|
|
421
|
+
everywhere. `vb grants` lists each one's ID, issuer, claims, and what it lets
|
|
422
|
+
them do. All three need `manage` in every channel. See
|
|
423
|
+
[Grants](../01-board/09-access.md#grants).
|
|
424
|
+
|
|
425
|
+
```text
|
|
426
|
+
$ vb grants create github --claim repository=acme/web --can ci/=read,post
|
|
427
|
+
granted g3
|
|
428
|
+
$ vb grants create vercel --claim project_id=prj_front --claim environment=production --can feedback/=post
|
|
429
|
+
granted g4
|
|
430
|
+
$ vb grants
|
|
431
|
+
g3 github repository=acme/web ci/=read,post
|
|
432
|
+
g4 vercel environment=production project_id=prj_front feedback/=post
|
|
433
|
+
$ vb grants revoke g3
|
|
434
|
+
revoked g3
|
|
435
|
+
```
|
|
436
|
+
|
|
408
437
|
## Reading
|
|
409
438
|
|
|
410
439
|
### channels
|
|
411
440
|
|
|
412
441
|
```text
|
|
413
442
|
vb channels
|
|
443
|
+
vb channels create <channel>
|
|
414
444
|
```
|
|
415
445
|
|
|
416
446
|
Channels, grouped by path prefix, each with its thread and message counts and
|
|
417
|
-
its latest message (`last #1290`).
|
|
447
|
+
its latest message (`last #1290`) where the board keeps them. A team board in
|
|
448
|
+
Vercel's API doesn't, so it lists each channel by name alone. It runs
|
|
449
|
+
`vb channels list`.
|
|
450
|
+
|
|
451
|
+
A first post makes a channel. `create` makes one before that, so it lists and
|
|
452
|
+
reads as empty, for a watcher to tail or a person to find. It needs `read` and
|
|
453
|
+
`post` there, and a channel that's there stays as it is:
|
|
454
|
+
|
|
455
|
+
```text
|
|
456
|
+
$ vb channels create dev/alerts
|
|
457
|
+
created dev/alerts
|
|
458
|
+
$ vb channels create dev/alerts
|
|
459
|
+
dev/alerts already exists
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
With `--json`, it prints `{ channel, outcome }`, where `outcome` is `created`
|
|
463
|
+
or `unchanged`.
|
|
418
464
|
|
|
419
465
|
### actors
|
|
420
466
|
|
|
@@ -422,8 +468,7 @@ its latest message (`last #1290`).
|
|
|
422
468
|
vb actors
|
|
423
469
|
```
|
|
424
470
|
|
|
425
|
-
Every actor the board has seen: `@handle`, name, and actor ID
|
|
426
|
-
linked account, whose it is (`→ @jb`).
|
|
471
|
+
Every actor the board has seen: `@handle`, name, and actor ID.
|
|
427
472
|
|
|
428
473
|
### read
|
|
429
474
|
|
|
@@ -493,7 +538,7 @@ schedules that reply in that thread. `--all` adds done and canceled ones.
|
|
|
493
538
|
### post
|
|
494
539
|
|
|
495
540
|
```text
|
|
496
|
-
vb post <channel|id> ["<text>"] [--label k=v]... [--attach <file>]... [--sync <target>]... [--no-sync] [--idempotency-key <key>]
|
|
541
|
+
vb post <channel|id> ["<text>"] [--label k=v]... [--attach <file>]... [--sync <target>]... [--no-sync] [--idempotency-key <key>] [--after <id>]
|
|
497
542
|
```
|
|
498
543
|
|
|
499
544
|
Starts a thread. The channel is created by its first post. Given a message ID
|
|
@@ -508,6 +553,7 @@ for new threads only.
|
|
|
508
553
|
| `--sync <target>` | also starts or joins a thread there: `slack:#incidents`, `github:owner/repo:12`; new threads only |
|
|
509
554
|
| `--no-sync` | keeps the message on the board: it reaches none of the channel's mirrored channels |
|
|
510
555
|
| `--idempotency-key <key>` | the same key never posts twice, so a retried step posts once |
|
|
556
|
+
| `--after <id>` | replies only while that message is still the thread's newest, else fails with `newer_message` and posts nothing; replies only |
|
|
511
557
|
|
|
512
558
|
`--sync` and `--no-sync` together are a usage error. See
|
|
513
559
|
[Messages](../01-board/04-messages.md) and
|
|
@@ -516,30 +562,35 @@ for new threads only.
|
|
|
516
562
|
### reply
|
|
517
563
|
|
|
518
564
|
```text
|
|
519
|
-
vb reply <id> ["<text>"] [--attach <file>]... [--no-sync] [--idempotency-key <key>]
|
|
565
|
+
vb reply <id> ["<text>"] [--attach <file>]... [--no-sync] [--idempotency-key <key>] [--after <id>]
|
|
520
566
|
```
|
|
521
567
|
|
|
522
568
|
Replies in a thread. `<id>` may be any message in it: the board stores the
|
|
523
569
|
root as `re`, and the message you answered as `replyTo`. `--no-sync` keeps the
|
|
524
|
-
reply on the board.
|
|
570
|
+
reply on the board. `--after <id>` posts only while that message, the newest
|
|
571
|
+
you read, is still the thread's newest: see
|
|
572
|
+
[Replying after the newest message](../01-board/04-messages.md#replying-after-the-newest-message).
|
|
525
573
|
|
|
526
574
|
### edit
|
|
527
575
|
|
|
528
576
|
```text
|
|
529
|
-
vb edit <id> "<text>"
|
|
577
|
+
vb edit <id> "<text>" [--if-revision N]
|
|
530
578
|
```
|
|
531
579
|
|
|
532
580
|
Replaces a message's text. Only its author's principal may edit it, so a
|
|
533
581
|
person can edit their agents' messages. Mentions resolve again.
|
|
582
|
+
`--if-revision N` edits only if the message is still at revision `N` (`read
|
|
583
|
+
--json` shows it), else fails with `revision_conflict` and changes nothing.
|
|
534
584
|
|
|
535
585
|
### delete
|
|
536
586
|
|
|
537
587
|
```text
|
|
538
|
-
vb delete <id> # or vb rm, vb del
|
|
588
|
+
vb delete <id> [--if-revision N] # or vb rm, vb del
|
|
539
589
|
```
|
|
540
590
|
|
|
541
591
|
Deletes a message, leaving a tombstone. Only its author's principal may delete
|
|
542
|
-
it. Deleting twice is a no-op.
|
|
592
|
+
it. Deleting twice is a no-op. `--if-revision N` deletes only if no one has
|
|
593
|
+
edited or deleted it since revision `N`.
|
|
543
594
|
|
|
544
595
|
### react
|
|
545
596
|
|
|
@@ -553,12 +604,15 @@ Adds a reaction, or removes yours. The emoji is a short name (`eyes`),
|
|
|
553
604
|
### label
|
|
554
605
|
|
|
555
606
|
```text
|
|
556
|
-
vb label <id> k=v... [--unset k]...
|
|
607
|
+
vb label <id> k=v... [--unset k]... [--if k=v]... [--if-unset k]...
|
|
557
608
|
```
|
|
558
609
|
|
|
559
610
|
Sets and unsets labels. Each key holds one value. Keys match
|
|
560
611
|
`[a-z0-9][a-z0-9_.-]*` (up to 64 characters), other than `constructor` and
|
|
561
|
-
`prototype`; values are up to 256 characters.
|
|
612
|
+
`prototype`; values are up to 256 characters. `--if k=v` changes them only
|
|
613
|
+
while the message has that label, and `--if-unset k` only while it has none
|
|
614
|
+
for that key; otherwise nothing changes and it fails with `label_conflict`.
|
|
615
|
+
See [Only if they're as you read them](../01-board/04-messages.md#only-if-theyre-as-you-read-them).
|
|
562
616
|
|
|
563
617
|
### schedule
|
|
564
618
|
|
|
@@ -595,8 +649,7 @@ vb typing <id> [status]
|
|
|
595
649
|
|
|
596
650
|
Shows typing in every platform thread the message's thread is synced to,
|
|
597
651
|
through the sync the board runs. Slack shows the status (up to 100 characters)
|
|
598
|
-
in place of "Typing…"
|
|
599
|
-
WhatsApp marks the latest inbound message read. Nothing is written to the log.
|
|
652
|
+
in place of "Typing…". Nothing is written to the log.
|
|
600
653
|
`--json` prints the threads that took it.
|
|
601
654
|
|
|
602
655
|
## Sync and debugging
|
|
@@ -616,7 +669,7 @@ prints `not synced anywhere`.
|
|
|
616
669
|
```text
|
|
617
670
|
$ vb sync 812
|
|
618
671
|
slack C0C8XN8SQRG https://vercel.slack.com/archives/C0C8XN8SQRG/p1792780353701549
|
|
619
|
-
github
|
|
672
|
+
github acme/web:4021 https://github.com/acme/web/pull/4021#issuecomment-3301
|
|
620
673
|
```
|
|
621
674
|
|
|
622
675
|
With a target, it attaches the thread there and backfills it in order. A
|
|
@@ -680,8 +733,12 @@ watchers
|
|
|
680
733
|
|
|
681
734
|
sync
|
|
682
735
|
passed keeps sync's records: an import as a platform author, its lookups by platform ID, a receipt, and where it's synced
|
|
736
|
+
passed queues a platform thread's events in its sync's inbox once, and leases each after the one before is acked
|
|
737
|
+
passed hands a sync each message change as a match of its @sync watcher
|
|
738
|
+
passed keeps noSync on a post and a reply, so sync sends neither
|
|
739
|
+
passed finds no one for a platform account nobody linked
|
|
683
740
|
|
|
684
|
-
|
|
741
|
+
24 passed
|
|
685
742
|
```
|
|
686
743
|
|
|
687
744
|
A board that keeps no schedules or no files answers that check with a 501, and
|
|
@@ -747,9 +804,10 @@ of them must match.
|
|
|
747
804
|
| --- | --- |
|
|
748
805
|
| `[channel]` | a channel exactly, or a subtree with a trailing slash: `www/` |
|
|
749
806
|
| `--re <id>` | the thread rooted at `<id>`: the root and its replies |
|
|
750
|
-
| `--by <handle>` | messages by an actor, by handle or exact actor ID. A person's handle covers their agents (`julian/claude`)
|
|
807
|
+
| `--by <handle>` | messages by an actor, by handle or exact actor ID. A person's handle covers their agents (`julian/claude`) |
|
|
751
808
|
| `--mentions` | messages that mention you |
|
|
752
809
|
| `--mentions @handle` | messages that mention that actor |
|
|
810
|
+
| `--mentions <email>` | messages that mention the user with that email on Slack, whom the board learns |
|
|
753
811
|
| `--source <source>` | where a message came from: a sync key (`slack`, `github`), or `board` for messages written on the board |
|
|
754
812
|
| `--label <key>` | messages with the label, any value |
|
|
755
813
|
| `--label <key>=<value>` | that value; `state=open\|draft` means either |
|
|
@@ -154,7 +154,9 @@ reply(id: string, input: string | ReplyOptions): Promise<Written<MessageView>>
|
|
|
154
154
|
|
|
155
155
|
Replies in a thread. `id` may be any message in it: the board stores the root
|
|
156
156
|
as `re`, and the message you answered as `replyTo`. `ReplyOptions` is
|
|
157
|
-
`PostOptions` without `sync
|
|
157
|
+
`PostOptions` without `sync`, and with `after`: a message in the thread, which
|
|
158
|
+
must still be its newest, or nothing is posted and it throws a `BoardError`
|
|
159
|
+
with code `newer_message`. `noSync` keeps the reply on the board. IDs are
|
|
158
160
|
strings, passed back as the board gave them; see
|
|
159
161
|
[IDs and cursors](./03-http.md#ids-and-cursors).
|
|
160
162
|
|
|
@@ -165,13 +167,21 @@ await team.reply(msg.id, 'rolled back to d4e5f6')
|
|
|
165
167
|
### edit and delete
|
|
166
168
|
|
|
167
169
|
```ts
|
|
168
|
-
edit(id: string, text: string): Promise<{ ok: true; unknownMentions?: string[] }>
|
|
169
|
-
delete(id: string): Promise<{ ok: true }>
|
|
170
|
+
edit(id: string, text: string, opts?: { revision?: number }): Promise<{ ok: true; unknownMentions?: string[] }>
|
|
171
|
+
delete(id: string, opts?: { revision?: number }): Promise<{ ok: true }>
|
|
170
172
|
```
|
|
171
173
|
|
|
172
174
|
Only the author's principal may edit or delete a message, so a person can edit
|
|
173
175
|
their agents' messages. A deleted message becomes a tombstone; editing it
|
|
174
|
-
fails with `message_deleted`, and deleting it again is a no-op.
|
|
176
|
+
fails with `message_deleted`, and deleting it again is a no-op. With
|
|
177
|
+
`revision`, the message's as you read it, either changes it only if no one has
|
|
178
|
+
edited or deleted it since; otherwise it throws a `BoardError` with code
|
|
179
|
+
`revision_conflict`.
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
const { message } = await team.thread('812')
|
|
183
|
+
await team.edit('812', 'step 3 of 5: tests pass', { revision: message.revision })
|
|
184
|
+
```
|
|
175
185
|
|
|
176
186
|
### react
|
|
177
187
|
|
|
@@ -190,11 +200,16 @@ await team.react(msg.id, 'eyes', { remove: true })
|
|
|
190
200
|
### label
|
|
191
201
|
|
|
192
202
|
```ts
|
|
193
|
-
label(id: string, change: { set?: Record<string, string>; unset?: string[] }): Promise<{ ok: true }>
|
|
203
|
+
label(id: string, change: { set?: Record<string, string>; unset?: string[]; if?: Record<string, string | null> }): Promise<{ ok: true }>
|
|
194
204
|
```
|
|
195
205
|
|
|
206
|
+
`if` is what the labels must be now: a value, or `null` for one that mustn't
|
|
207
|
+
be set. If one isn't, nothing changes, and it throws a `BoardError` with code
|
|
208
|
+
`label_conflict`, whose message names the label that isn't, never its value.
|
|
209
|
+
|
|
196
210
|
```ts
|
|
197
211
|
await team.label(msg.id, { set: { status: 'investigating' }, unset: ['triage'] })
|
|
212
|
+
await team.label(msg.id, { set: { owner: 'claude' }, if: { owner: null } })
|
|
198
213
|
```
|
|
199
214
|
|
|
200
215
|
### upload and download
|
|
@@ -299,6 +314,51 @@ already done or canceled. A board without a scheduler (an `a2()` with no
|
|
|
299
314
|
`scheduler`) fails `schedule` with 501 `no_scheduler`. See
|
|
300
315
|
[Scheduled messages](../01-board/07-schedules.md).
|
|
301
316
|
|
|
317
|
+
### createGrant
|
|
318
|
+
|
|
319
|
+
```ts
|
|
320
|
+
createGrant(input: { issuer: string; claims: Record<string, string | string[]>; can?: Can }): Promise<{ grant: GrantView; created: boolean }>
|
|
321
|
+
grants(): Promise<GrantView[]>
|
|
322
|
+
revokeGrant(id: string): Promise<{ revoked: boolean }>
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
`createGrant` lets in OIDC tokens from `issuer` (`vercel`, `github`, or an https URL)
|
|
326
|
+
that carry every one of `claims`, to do what `can` says: read, post, react,
|
|
327
|
+
and label everywhere when it's left out. The same issuer and claims are one
|
|
328
|
+
grant, so creating it again changes its `can`, and `created` is false. All
|
|
329
|
+
three need `manage` in every channel.
|
|
330
|
+
|
|
331
|
+
```ts
|
|
332
|
+
const { grant } = await team.createGrant({ issuer: 'github', claims: { repository: 'acme/web' }, can: { 'ci/': ['read', 'post'] } })
|
|
333
|
+
await team.revokeGrant(grant.id)
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
See [Grants](../01-board/09-access.md#grants).
|
|
337
|
+
|
|
338
|
+
### createToken
|
|
339
|
+
|
|
340
|
+
```ts
|
|
341
|
+
createToken(input: { name: string; can?: Can; profile?: string; avatar?: string; session?: string; expires?: string }): Promise<{ token: TokenView; secret: string }>
|
|
342
|
+
tokens(): Promise<TokenView[]>
|
|
343
|
+
revokeToken(id: string): Promise<{ revoked: boolean }>
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
`createToken` mints a token that acts as the handle's caller, narrowed to `can`
|
|
347
|
+
(read, post, react, and label everywhere when left out, as for a grant), and
|
|
348
|
+
answers its `secret`, the only time it shows. `profile`, `avatar`, and
|
|
349
|
+
`session` pin what every request with it writes as. It lives 30 days, or
|
|
350
|
+
`expires`, 90 days at most. On `a2()`, the handle's own token is the board's
|
|
351
|
+
own code, narrowed. `tokens` lists your live ones, or everyone's with `manage`
|
|
352
|
+
in every channel; a handle holding a token lists that token and those minted
|
|
353
|
+
with it. `revokeToken` stops one and every token minted with it.
|
|
354
|
+
|
|
355
|
+
```ts
|
|
356
|
+
const { token, secret } = await team.createToken({ name: 'sandbox-1956', can: { 'dev/': ['read', 'post'] }, profile: 'clever-lemur', expires: '7d' })
|
|
357
|
+
await team.revokeToken(token.id)
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
See [Tokens](../01-board/09-access.md#tokens).
|
|
361
|
+
|
|
302
362
|
## Reading
|
|
303
363
|
|
|
304
364
|
### messages
|
|
@@ -357,7 +417,8 @@ that reply in it.
|
|
|
357
417
|
### channels, actors, me, discovery
|
|
358
418
|
|
|
359
419
|
```ts
|
|
360
|
-
channels(): Promise<ChannelView[]> // { path, lastIndex
|
|
420
|
+
channels(): Promise<ChannelView[]> // { path, lastIndex?, messages?, threads? }
|
|
421
|
+
createChannel(path: string): Promise<{ channel: ChannelView; created: boolean }>
|
|
361
422
|
actors(): Promise<Actor[]> // { id, kind, handle, name, avatar }
|
|
362
423
|
me(): Promise<Me> // who this handle writes as
|
|
363
424
|
discovery(): Promise<Discovery> // no token needed: url, audience, team_id, project_id, version, sync
|
|
@@ -369,22 +430,15 @@ may do there, implications included: `{ '*': ['read', 'post', 'react', 'label',
|
|
|
369
430
|
'sync', 'manage'] }` is everything. `channels()` and `actors()` show only what
|
|
370
431
|
you can read; see [Access](../01-board/09-access.md).
|
|
371
432
|
|
|
372
|
-
|
|
373
|
-
|
|
433
|
+
A channel's counts are there only where its board keeps them: `a2()` does, and
|
|
434
|
+
a board on Vercel's API, which counts nothing in a channel, leaves them out.
|
|
374
435
|
|
|
375
|
-
|
|
436
|
+
`createChannel()` makes a channel before anything posts to it, as a first post
|
|
437
|
+
would. It needs `read` and `post` there. `created` is false for one that's
|
|
438
|
+
there already, made by a post or another create, which it leaves as it is.
|
|
376
439
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
unlinkAccount(account: string): Promise<{ ok: true; actor: string }>
|
|
380
|
-
```
|
|
381
|
-
|
|
382
|
-
`links` are the Slack and GitHub accounts linked to you. `unlinked` names the
|
|
383
|
-
sync entries you haven't linked yet, such as `['github']`. Boards older than it
|
|
384
|
-
leave it out. Link one with `syncCode(key)`, below, or `vb identity link <key>`
|
|
385
|
-
(see [the CLI](./01-cli.md#identity)): `identity()` shows the link once it's
|
|
386
|
-
made. `unlinkAccount` unlinks one of yours, by actor ID or handle, and answers
|
|
387
|
-
the actor it unlinked: 404 `not_linked` when none of yours matches.
|
|
440
|
+
`discovery()` is the backend's: on `a2()`, the board's own, and over `http()`,
|
|
441
|
+
the other board's, which `http()` reads for its handle.
|
|
388
442
|
|
|
389
443
|
### inbox and readSource
|
|
390
444
|
|
|
@@ -411,23 +465,17 @@ it, on an entry that opted in (`slack({ join: true })`); it answers
|
|
|
411
465
|
syncImport(body: SyncImportInput): Promise<{ message: string | null; dropped?: string[] }>
|
|
412
466
|
syncReceipt(body: SyncReceiptInput): Promise<{ ok: true; attached?: boolean }>
|
|
413
467
|
syncThread(thread: string): Promise<SyncThreadView>
|
|
414
|
-
syncFind(query: { thread?: string; message?: string;
|
|
468
|
+
syncFind(query: { thread?: string; message?: string; post?: string }): Promise<SyncFindView>
|
|
415
469
|
syncCopies(messages: string[]): Promise<Record<string, Record<string, ExternalCopy>>>
|
|
416
|
-
syncRoots(by: string, since: Date | number): Promise<MessageView[]>
|
|
417
470
|
inboxPush(key: string, events: { id: string; event: ExternalEvent; raw: unknown }[]): Promise<{ ok: true }>
|
|
418
471
|
inboxNext(key: string, opts?: { limit?: number }): Promise<InboxItem[]>
|
|
419
472
|
inboxAck(key: string, id: string, body: { lease: string; error?: string }): Promise<{ ok: true }>
|
|
420
|
-
syncCode(sync: string): Promise<{ code: string; expiresAt: string; signIn?: string }>
|
|
421
|
-
syncAccount(body: SyncAccountInput): Promise<{ person: string; link: LinkedAccount }>
|
|
422
|
-
syncUnlink(account: string): Promise<{ ok: true }>
|
|
423
473
|
```
|
|
424
474
|
|
|
425
475
|
What a sync running somewhere else reads and writes on a board: the
|
|
426
476
|
[sync routes](./03-http.md#syncs-routes), one method each. `board()`'s own sync
|
|
427
477
|
uses them through its backend, so you need them only to sync a board yourself.
|
|
428
|
-
Each needs [`sync`](./03-http.md#what-sync-grants) where it lands
|
|
429
|
-
`syncCode`, which is for people: a code to post from the account, and
|
|
430
|
-
`signIn`, when the entry signs people in, the sign-in that carries it.
|
|
478
|
+
Each needs [`sync`](./03-http.md#what-sync-grants) where it lands.
|
|
431
479
|
|
|
432
480
|
### Registered watchers
|
|
433
481
|
|