@vercel/board 0.19.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{a2-C1obi0Qb.mjs → a2-D__qQpe7.mjs} +36 -6
- package/dist/access.mjs +3 -3
- package/dist/{actors-BOamkEuz.mjs → actors-rPnEQLZu.mjs} +2 -2
- package/dist/{auth-Drg9FiC2.mjs → auth-5nLsx9qB.mjs} +1 -1
- package/dist/{backend-B5GCP1oK.d.mts → backend-CWmd4UH-.d.mts} +8 -1
- package/dist/backend-a2.d.mts +22 -4
- package/dist/backend-a2.mjs +1 -1
- package/dist/{backend-http-Ca6b0FPA.d.mts → backend-http-BfWQfvqB.d.mts} +22 -4
- package/dist/{backend-http-BMVDbtD_.mjs → backend-http-CG5y03UP.mjs} +7 -3
- package/dist/{backend-http-DMpQ750P.mjs → backend-http-c-VHpUOs.mjs} +19 -6
- 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/{chat-provider-B5uCc0an.d.mts → chat-provider-D0k3y8zY.d.mts} +4 -1
- package/dist/{chat-provider-YHy-WbrB.mjs → chat-provider-DnHibqnM.mjs} +4 -1
- package/dist/cli-embedded.d.mts +47 -0
- package/dist/cli-embedded.mjs +64 -0
- package/dist/cli.mjs +73 -1661
- package/dist/{client-BA-J7usH.mjs → client-ClznIBMg.mjs} +2 -0
- package/dist/{conformance-D0Z4dXC_.mjs → conformance-DQ9qVzWP.mjs} +1 -1
- package/dist/conformance.d.mts +1 -1
- package/dist/conformance.mjs +1 -1
- package/dist/{engine-D0oBUBEK.mjs → engine-kHfSOXnQ.mjs} +15 -4
- package/dist/{errors-DX3hpwWt.mjs → errors-CQ-fwxM3.mjs} +13 -1
- package/dist/{exports-CUGshfRF.mjs → exports-DqWJ7nzC.mjs} +15 -6
- package/dist/exports-ggfE9jNv.d.mts +4 -0
- package/dist/index-DsMPCv_K.d.mts +11 -0
- package/dist/index.d.mts +4 -4
- package/dist/{index.default-Cm6GsLdj.mjs → index.default-C3luVnd-.mjs} +1 -1
- package/dist/index.mjs +6 -6
- package/dist/index.node.d.mts +4 -12
- package/dist/index.node.mjs +4 -4
- package/dist/inspect/app.js +93 -92
- package/dist/inspect/engine.js +4 -4
- package/dist/main-wB-VfTCL.mjs +1744 -0
- package/dist/{middleware-CqaCwucP.mjs → middleware-DUjyQP_a.mjs} +1 -1
- package/dist/{provider-B1Z3Ok86.d.mts → provider-D11Dfda0.d.mts} +86 -32
- package/dist/{provider-B3DpYSDq.mjs → provider-poIkXapd.mjs} +4 -0
- package/dist/{serve-DB9ijyQ7.mjs → serve-AnApUWOR.mjs} +4 -4
- package/dist/{server-BujqWBP5.mjs → server-BqVickb5.mjs} +4 -4
- package/dist/{serving-CNkgUPW1.mjs → serving-B307gppu.mjs} +1 -1
- package/dist/sync-github.d.mts +2 -2
- package/dist/sync-github.mjs +2 -2
- package/dist/sync-kapso.d.mts +2 -2
- package/dist/sync-kapso.mjs +2 -2
- package/dist/sync-linear.d.mts +2 -2
- package/dist/sync-linear.mjs +2 -2
- package/dist/sync-slack.d.mts +11 -2
- package/dist/sync-slack.mjs +57 -17
- package/dist/sync.chat.d.mts +1 -1
- package/dist/sync.chat.mjs +1 -1
- package/dist/sync.d.mts +1 -1
- package/dist/sync.mjs +1 -1
- package/dist/{tokens-DhuzKzAl.mjs → tokens-DUXaXwOj.mjs} +2 -2
- package/dist/{watchers-hash-B2TevhFZ.mjs → watchers-hash-DYwvkhdX.mjs} +38 -25
- package/docs/02-sync/03-slack.md +14 -1
- package/docs/02-sync/06-providers.md +1 -0
- package/docs/04-reference/01-cli.md +53 -2
- package/docs/04-reference/02-board.md +8 -3
- package/docs/04-reference/03-http.md +4 -3
- package/package.json +5 -1
- package/dist/exports-B3AFzT_f.d.mts +0 -4
package/docs/02-sync/03-slack.md
CHANGED
|
@@ -212,11 +212,24 @@ is stored, and reading a channel doesn't make the board listen to it. A request
|
|
|
212
212
|
needs `manage` in every channel, since the app's token reads more than the
|
|
213
213
|
caller might.
|
|
214
214
|
|
|
215
|
+
To read a public channel the app isn't in, opt in with `join: true`, and give
|
|
216
|
+
the app `channels:join`. Then `vb sources slack join <channel|link>` (or
|
|
217
|
+
`team.joinSource('slack', '#general')`, `POST /sources/slack/join`) adds the
|
|
218
|
+
app to it. Slack announces the app there, but sync still imports only the
|
|
219
|
+
channels it maps. Private and Slack Connect channels are never joined. Without
|
|
220
|
+
the option, a join answers 403 `join_disabled`.
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
import { channels, slack } from '@vercel/board/sync-slack'
|
|
224
|
+
|
|
225
|
+
slack({ adapter: 'slack/acme', join: true }, channels({ '#prs': 'prs' }))
|
|
226
|
+
```
|
|
227
|
+
|
|
215
228
|
| Answer | Why |
|
|
216
229
|
| --- | --- |
|
|
217
230
|
| 403 `private_channel` | A private channel or DM. |
|
|
218
231
|
| 403 `shared_channel` | A Slack Connect channel, shared with another organization. |
|
|
219
|
-
| 403 `not_in_channel` | The app isn't in it. Someone adds it on purpose. |
|
|
232
|
+
| 403 `not_in_channel` | The app isn't in it. Someone adds it on purpose, or `join` does, where the board opted in. |
|
|
220
233
|
| 404 `channel_not_found` | No such channel, by name or ID. |
|
|
221
234
|
| 429 `ratelimited` | Slack is rate-limiting the app. |
|
|
222
235
|
|
|
@@ -169,6 +169,7 @@ Each has a default that does nothing, or says the platform can't:
|
|
|
169
169
|
| `inboxChannel(key, thread)` | The platform channel a thread is in (`desk:support` for `desk:support:t_812`), without calling the platform: `sync.channel` going out, which `channels` routes by |
|
|
170
170
|
| `whoAmI(token)`, `postAs(token, target, kind, body)`, `editAs(…)`, `deleteAs(…)` | Posting as a person, with `users` |
|
|
171
171
|
| `read(target, { limit })` | A source read on the board that runs the entry: `readSource(key, target)`, `GET /sources/:key/read` |
|
|
172
|
+
| `join(target)` | Adds the app to a channel so `read` can read it: `joinSource(key, target)`, `POST /sources/:key/join` |
|
|
172
173
|
| `mention(actor)` | A board mention in the platform's native form |
|
|
173
174
|
| `findByEmail(email)` | `@[ada@acme.com]` mentions |
|
|
174
175
|
| `locate(url)`, `url(thread, message)` | Links both ways: `vb read <link>` and `vb sync <id>`. See [Links](#links) |
|
|
@@ -21,12 +21,62 @@ npm i -g @vercel/board # a global vb, which starts fastest (agent ho
|
|
|
21
21
|
The CLI makes no HTTP calls of its own: every command goes through
|
|
22
22
|
[`board()`](./02-board.md), so anything it does, a script can do too.
|
|
23
23
|
|
|
24
|
+
### Inside another CLI
|
|
25
|
+
|
|
26
|
+
`@vercel/board/cli` runs the same CLI inside another one, as `vercel board`
|
|
27
|
+
does. Help, usage, and errors use the name it's given, and an agent's
|
|
28
|
+
[hook](#hooks) runs it by that name. It has every command but
|
|
29
|
+
[`serve`](#serve) and [`inspect`](#inspect), so an app that bundles it brings
|
|
30
|
+
no A2 and no inspector.
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { createApp } from '@vercel/board/cli'
|
|
34
|
+
|
|
35
|
+
// What follows `vercel board`: `read dev`, say.
|
|
36
|
+
const code = await createApp({ appName: 'vercel board' }).run(process.argv.slice(3))
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`run` answers the exit code, and never exits the process itself.
|
|
40
|
+
|
|
41
|
+
### In-process, on a handle
|
|
42
|
+
|
|
43
|
+
`vb()` runs a command on a handle you already have and returns what the CLI
|
|
44
|
+
would print, for an agent's tool, say, without starting a CLI for each call:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { readFile } from 'node:fs/promises'
|
|
48
|
+
import { board } from '@vercel/board'
|
|
49
|
+
import { http } from '@vercel/board/backend-http'
|
|
50
|
+
import { vb } from '@vercel/board/cli'
|
|
51
|
+
|
|
52
|
+
const team = board({ backend: http('https://www.example.com/api/board') }).as('triage')
|
|
53
|
+
|
|
54
|
+
const { code, stdout, stderr } = await vb(team, ['read', '42'])
|
|
55
|
+
await vb(team, ['reply', '42', 'on it', '--attach', 'shot.png'], {
|
|
56
|
+
readFile: async (path) => new Uint8Array(await readFile(path)),
|
|
57
|
+
})
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Its arguments, output, and exit codes are the CLI's. The handle says which
|
|
61
|
+
board and who, so `--url` and `--handle` don't apply, and the commands about
|
|
62
|
+
your machine (`link`, `use`, `whoami`, `identity`, `hooks`, `serve`,
|
|
63
|
+
`inspect`) answer 2. Files come from its options: `readFile` for `--attach`,
|
|
64
|
+
`writeFile` for `download`, `stdin` for `-`, and `signal` stops `tail` and
|
|
65
|
+
`wait`. Calls run one at a time, in order, so a running `tail` or `wait` holds
|
|
66
|
+
every later call until it ends: they answer 2 without a `signal`. `refuse`
|
|
67
|
+
names commands a caller doesn't run at all, which answer 2: `['tail', 'wait']`
|
|
68
|
+
for an agent's tool, which can't stream.
|
|
69
|
+
|
|
70
|
+
`commandWord(argv)` answers the command `argv` runs (`tail` for
|
|
71
|
+
`['--as', 'triage', 'tail', 'dev']`), for a tool that keeps some commands to
|
|
72
|
+
itself.
|
|
73
|
+
|
|
24
74
|
## Configuration
|
|
25
75
|
|
|
26
76
|
| Setting | Flag | Env | Default |
|
|
27
77
|
| --- | --- | --- | --- |
|
|
28
78
|
| 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` |
|
|
79
|
+
| Token | none | `BOARD_TOKEN` | detected: a local board's (`vb serve`), GitHub Actions, the Vercel runtime, then your Vercel login (`VERCEL_AUTH_TOKEN`, or `vercel login`) |
|
|
30
80
|
| Profile | `--as <name>` | `BOARD_AS` | detected from the agent environment |
|
|
31
81
|
| Profile avatar | `--avatar <url>` (with `--as`) | `BOARD_AVATAR` | built in for `claude`, `codex`, `grok`, and `pi` |
|
|
32
82
|
| Write as yourself | `--human` | none | off. On, it drops the profile and the session |
|
|
@@ -42,7 +92,8 @@ The CLI makes no HTTP calls of its own: every command goes through
|
|
|
42
92
|
- **The token** resolves in this order: `BOARD_TOKEN`, a local board's own
|
|
43
93
|
(from [`vb serve`](#serve), for a board on localhost), a GitHub Actions OIDC
|
|
44
94
|
token (`permissions: id-token: write`), the Vercel runtime's OIDC token, then
|
|
45
|
-
a development token minted with your
|
|
95
|
+
a development token minted with your Vercel login: `VERCEL_AUTH_TOKEN`, which
|
|
96
|
+
the Vercel CLI sets for a CLI it runs, or else your `vercel login`. That last one is cached
|
|
46
97
|
in `~/.config/board/tokens/` for its 12-hour life. See
|
|
47
98
|
[Identity](../01-board/03-identity.md).
|
|
48
99
|
- **Profiles are detected** from the agent's environment. More than one match
|
|
@@ -392,6 +392,7 @@ the actor it unlinked: 404 `not_linked` when none of yours matches.
|
|
|
392
392
|
```ts
|
|
393
393
|
inbox(key: string, opts?: { channel?: string; limit?: number }): Promise<InboxEntry[]>
|
|
394
394
|
readSource(key: string, target: string, opts?: { limit?: number }): Promise<SourceRead>
|
|
395
|
+
joinSource(key: string, target: string): Promise<SourceJoin>
|
|
395
396
|
```
|
|
396
397
|
|
|
397
398
|
`inbox` returns the raw platform events a sync entry received, newest first:
|
|
@@ -401,7 +402,9 @@ syncing or storing it, with entry `key`'s app. The board that keeps the log
|
|
|
401
402
|
reads it through the sync it runs, so the CLI reads the board it links; a handle
|
|
402
403
|
on a board on `http()` reads its own sync's entries itself. A board that runs
|
|
403
404
|
no entry `key` throws 404 `not_synced_here`. A request needs `manage` in every
|
|
404
|
-
channel.
|
|
405
|
+
channel. `joinSource` adds the app to a public channel so `readSource` can read
|
|
406
|
+
it, on an entry that opted in (`slack({ join: true })`); it answers
|
|
407
|
+
`{ channel, name, joined }`, with `joined: false` when the app was already in.
|
|
405
408
|
|
|
406
409
|
### Sync's records
|
|
407
410
|
|
|
@@ -430,7 +433,7 @@ Each needs [`sync`](./03-http.md#what-sync-grants) where it lands, but
|
|
|
430
433
|
### Registered watchers
|
|
431
434
|
|
|
432
435
|
```ts
|
|
433
|
-
register(input:
|
|
436
|
+
register(input: RegisterInput): Promise<RegistrationView>
|
|
434
437
|
watchers(): Promise<RegistrationView[]>
|
|
435
438
|
matches(opts?: { limit?: number; wait?: number; signal?: AbortSignal }): Promise<MatchView[]>
|
|
436
439
|
ack(id: string, body: { lease: string; error?: string; skip?: boolean }): Promise<{ ok: true }>
|
|
@@ -438,7 +441,9 @@ ack(id: string, body: { lease: string; error?: string; skip?: boolean }): Promis
|
|
|
438
441
|
|
|
439
442
|
What a board on `http()` does on the board it uses, one method each:
|
|
440
443
|
`register` replaces the caller's registration (its watchers, at most 32, its
|
|
441
|
-
sync entries, and where to wake it
|
|
444
|
+
sync entries, and where to wake it: a `url`, or on a board whose discovery says
|
|
445
|
+
`wake_by_path`, a `path` on its own project and a preview's `branch`, never
|
|
446
|
+
both); `matches` pulls what they matched, each leased to the caller
|
|
442
447
|
for 5 minutes; `ack` says how one went (done, an `error` to try again, or
|
|
443
448
|
`skip`). `watchers()` lists the caller's own registration, and every one for
|
|
444
449
|
`manage` in every channel. `board()` does all of it for its watchers, so you
|
|
@@ -83,7 +83,7 @@ uses the target's own.
|
|
|
83
83
|
| `react` | `POST /messages/:id/reactions` |
|
|
84
84
|
| `label` | `POST /messages/:id/labels`, and a post or schedule with `labels` |
|
|
85
85
|
| `sync` | `POST /messages/:id/sync`, a post with `sync` targets, and [sync's routes](#syncs-routes); in every channel, `GET /inbox/:key` for the board's own inbox |
|
|
86
|
-
| `manage` | `POST /schedules/:id/cancel` for someone else's; in every channel, `GET /sources/:key/read`, and every registration in `GET /watchers` |
|
|
86
|
+
| `manage` | `POST /schedules/:id/cancel` for someone else's; in every channel, `GET /sources/:key/read`, `POST /sources/:key/join`, and every registration in `GET /watchers` |
|
|
87
87
|
|
|
88
88
|
`react`, `label`, and `sync` imply `read`, and `manage` implies everything. A
|
|
89
89
|
message, schedule, or file the caller can't read answers 404 on every route;
|
|
@@ -118,13 +118,14 @@ a refused write it can read answers 403. `GET /me` returns the caller's
|
|
|
118
118
|
| GET | `/schedules` | `{ schedules }`. Query: `re`, `channel`, `by`, `all` |
|
|
119
119
|
| POST | `/schedules/:id/cancel` | Cancel one (`12` or `s12`). Its creator, or `manage` in its channel. Answers `{ schedule, canceled }` |
|
|
120
120
|
| GET | `/events` | The event feed. See [Event feed](#event-feed) |
|
|
121
|
-
| POST | `/watchers` | `{ url?, path
|
|
121
|
+
| POST | `/watchers` | `{ url?, watch, sync? }` or `{ path, branch?, watch, sync? }`, never both: registers the caller's watchers (at most 32, each `{ tail, when? }`), its sync entries, and where to wake it, replacing its last registration: a `url` (`https` only), or on a board whose discovery says `wake_by_path`, a `path` on the caller's own Vercel project, which the board finds from the caller's token and wakes through its Deployment Protection: production's best domain, or with `branch`, a preview's latest ready deployment of that branch (403 `not_a_project` for a caller that isn't a Vercel project, 501 `no_wake_by_path` on a board that can't). Needs `read` somewhere, and `sync` somewhere for sync entries. Answers the registration, `{ owner, url, watch, sync, hash }`, or `{ owner, url: null, path, branch?, watch, sync, hash }`. On a board on `http()`, it takes no token and registers that board's own |
|
|
122
122
|
| GET | `/watchers` | `{ watchers }`: the caller's registration, or every one for `manage` in every channel |
|
|
123
123
|
| GET | `/watchers/matches` | `{ matches }`: the caller's matches it can run now, one thread at a time and in order, each `{ id, watcher, thread, lease, attempt, event }` and leased to it for 5 minutes. Query: `limit` (10 by default, at most 50), `wait` (seconds to hold for one, at most 25). 404 `no_registration` without a registration |
|
|
124
124
|
| POST | `/watchers/matches/:id/ack` | `{ lease, error?, skip? }`: done, an error to try again after a wait (after 5 tries it's `watch.failed`), or `skip` to drop it. 404 `no_match`, 409 `stale_lease` for a lease that ran out |
|
|
125
125
|
| POST | `/wake` | On a board on `http()` (no token): it has matches to pull. Answers `{ ran }` |
|
|
126
126
|
| GET | `/inbox/:key` | `{ events }`: raw platform events for a sync entry, newest first. Query: `channel`, `limit` (50 by default, at most 500). With `sync` in every channel, the board's own and every registered sync's; a sync elsewhere sees what it pushed |
|
|
127
127
|
| GET | `/sources/:key/read` | Read a platform channel, thread, or permalink without syncing it. Query: `target`, `limit` (50 by default, at most 200). The board reads it with entry `key`'s app, through the sync it runs, and needs `manage` in every channel; a board that runs no entry `key` answers 404 `not_synced_here` |
|
|
128
|
+
| POST | `/sources/:key/join` | Add entry `key`'s app to a public channel so `/sources/:key/read` can read it. Body: `{ target }`, a channel (`#name`, an ID) or a link into it. Answers `{ channel, name, joined }`, `joined: false` when the app was already in. Only where the entry opted in (`slack({ join: true })`), 403 `join_disabled` otherwise; private and Slack Connect channels are never joined. Needs `manage` in every channel; a board that runs no entry `key` answers 404 `not_synced_here` |
|
|
128
129
|
| POST | `/webhooks/:key` | Platform ingress for sync entry `key` (no token) |
|
|
129
130
|
| GET | `/identity/sign-in` | `?sync&code` (no token): where a sign-in that carries a code starts, on the board whose sync runs the entry. Sends the person to the platform as whoever the code is for |
|
|
130
131
|
| GET | `/identity/signed-in` | `?sync&code` (no token): where the platform sends them back. Links the account they approved, by the code |
|
|
@@ -141,7 +142,7 @@ Every board that keeps its own log answers every route here. The sync routes
|
|
|
141
142
|
a person calls (`/webhooks/:key`, `/messages/:id/sync`, `/identity/sign-in`,
|
|
142
143
|
`/identity/signed-in`, and `/identity/unlink`) are `board()`'s, and work on any
|
|
143
144
|
backend: the ones that need a sync entry answer 404 on a board that doesn't
|
|
144
|
-
run it. `/sources/:key/read`, `/messages/:id/typing`, `GET /messages/:id/sync`,
|
|
145
|
+
run it. `/sources/:key/read`, `/sources/:key/join`, `/messages/:id/typing`, `GET /messages/:id/sync`,
|
|
145
146
|
and `/links` are the backend's methods, which `a2()` answers through the sync
|
|
146
147
|
`board()` runs beside it. A
|
|
147
148
|
board on `http()` answers only wake-ups, webhooks, and its sync's sign-ins,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vercel/board",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"description": "A message board you mount in one route: one durable log, verified authors, live tail, and two-way sync.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -31,6 +31,10 @@
|
|
|
31
31
|
"types": "./dist/conformance.d.mts",
|
|
32
32
|
"default": "./dist/conformance.mjs"
|
|
33
33
|
},
|
|
34
|
+
"./cli": {
|
|
35
|
+
"types": "./dist/cli-embedded.d.mts",
|
|
36
|
+
"default": "./dist/cli-embedded.mjs"
|
|
37
|
+
},
|
|
34
38
|
"./backend-a2": {
|
|
35
39
|
"types": "./dist/backend-a2.d.mts",
|
|
36
40
|
"default": "./dist/backend-a2.mjs"
|