@vercel/board 0.24.0 → 0.25.1

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 (81) hide show
  1. package/dist/{a2-BxfKZFvJ.mjs → a2-A830f5NS.mjs} +6 -6
  2. package/dist/access.d.mts +1 -2
  3. package/dist/access.mjs +3 -3
  4. package/dist/{actors-rPnEQLZu.mjs → actors-BLXVp4oZ.mjs} +2 -2
  5. package/dist/{auth-5nLsx9qB.mjs → auth-lTkOgvcE.mjs} +1 -1
  6. package/dist/{backend-D0GDYiFr.d.mts → backend-ZKYTe3xa.d.mts} +5 -11
  7. package/dist/backend-a2/blob-vercel.d.mts +1 -1
  8. package/dist/backend-a2/contract.d.mts +1 -2
  9. package/dist/backend-a2/store-memory.d.mts +1 -1
  10. package/dist/backend-a2/store-postgres.d.mts +1 -1
  11. package/dist/backend-a2/store-redis-http.d.mts +1 -1
  12. package/dist/backend-a2/store-redis.d.mts +1 -1
  13. package/dist/backend-a2/store-sqlite.d.mts +1 -1
  14. package/dist/backend-a2.d.mts +5 -6
  15. package/dist/backend-a2.mjs +1 -1
  16. package/dist/{backend-http-tGUo0e8j.mjs → backend-http-CJd4Fu0U.mjs} +2 -2
  17. package/dist/{backend-http-rfiG-AKP.mjs → backend-http-CYtfwqAW.mjs} +9 -6
  18. package/dist/{backend-http-Dvq2p__1.d.mts → backend-http-E7YU0qhn.d.mts} +3 -4
  19. package/dist/backend-http.d.mts +1 -1
  20. package/dist/backend-http.mjs +1 -1
  21. package/dist/backend-http.node.d.mts +1 -1
  22. package/dist/backend-http.node.mjs +1 -1
  23. package/dist/{backend-vercel-DM5PyMc2.mjs → backend-vercel-BhwPExmE.mjs} +3 -3
  24. package/dist/{backend-vercel-C3zuxmcD.mjs → backend-vercel-Bqg2QfTd.mjs} +7 -4
  25. package/dist/{backend-vercel-01QKDobk.d.mts → backend-vercel-CIRObA0t.d.mts} +3 -4
  26. package/dist/backend-vercel.d.mts +1 -1
  27. package/dist/backend-vercel.mjs +1 -1
  28. package/dist/backend-vercel.node.d.mts +1 -1
  29. package/dist/backend-vercel.node.mjs +1 -1
  30. package/dist/{blob-C0J3wbGv.d.mts → blob-B-9AvYis.d.mts} +2 -1
  31. package/dist/{chat-provider-D0k3y8zY.d.mts → chat-provider-DsNEBQiA.d.mts} +3 -2
  32. package/dist/cli-embedded.d.mts +55 -4
  33. package/dist/cli-embedded.mjs +38 -18
  34. package/dist/cli.mjs +4 -4
  35. package/dist/{client-ClznIBMg.mjs → client-D0Px7KzF.mjs} +26 -8
  36. package/dist/{provider-D11Dfda0.d.mts → client-xLxeDz0f.d.mts} +21 -688
  37. package/dist/{conformance-DQ9qVzWP.mjs → conformance-DlnfuzEB.mjs} +1 -1
  38. package/dist/conformance.d.mts +1 -1
  39. package/dist/conformance.mjs +1 -1
  40. package/dist/{engine-kHfSOXnQ.mjs → engine-ROa7-IQY.mjs} +2 -2
  41. package/dist/errors-CB4tnZGz.d.mts +16 -0
  42. package/dist/{errors-CQ-fwxM3.mjs → errors-CusN4x2l.mjs} +18 -6
  43. package/dist/exports-D22M68rk.d.mts +5 -0
  44. package/dist/{exports-Bw81oDDl.mjs → exports-DCqgO_mr.mjs} +6 -6
  45. package/dist/{folds-CRYY14kc.d.mts → folds-jS9wlh9_.d.mts} +49 -2
  46. package/dist/{index-B_hWanKF.d.mts → index-C05it0mC.d.mts} +2 -2
  47. package/dist/index.d.mts +5 -4
  48. package/dist/{index.default-B6oPlvEa.mjs → index.default-BejExznB.mjs} +1 -1
  49. package/dist/index.mjs +6 -6
  50. package/dist/index.node.d.mts +5 -4
  51. package/dist/index.node.mjs +4 -4
  52. package/dist/inspect/app.js +108 -108
  53. package/dist/{main-CzWvRy91.mjs → main-DOhNCWMd.mjs} +712 -142
  54. package/dist/{middleware-DUjyQP_a.mjs → middleware-ChKM1zuu.mjs} +1 -1
  55. package/dist/{node-CirFYqw0.mjs → node-Bkd1x0QI.mjs} +56 -28
  56. package/dist/provider-Cdg1qadh.d.mts +688 -0
  57. package/dist/{serve-DHyA-6FR.mjs → serve-D2O0C6Kk.mjs} +6 -5
  58. package/dist/{server-Cp2oIQOj.mjs → server-BF4WJ7qi.mjs} +4 -4
  59. package/dist/{serving-B307gppu.mjs → serving-CvOWEym2.mjs} +1 -1
  60. package/dist/sync-github.d.mts +2 -2
  61. package/dist/sync-kapso.d.mts +2 -2
  62. package/dist/sync-linear.d.mts +2 -2
  63. package/dist/sync-slack.d.mts +3 -3
  64. package/dist/sync.chat.d.mts +1 -1
  65. package/dist/sync.d.mts +2 -2
  66. package/dist/{tokens-DUXaXwOj.mjs → tokens-BuCU-61R.mjs} +2 -2
  67. package/dist/{types-8yq2VnKI.d.mts → types-aCAf6lGU.d.mts} +1 -1
  68. package/dist/{watchers-hash-C_1jGA53.mjs → watchers-hash-BHNXy_N7.mjs} +2 -2
  69. package/docs/01-board/03-identity.md +16 -7
  70. package/docs/01-board/04-messages.md +2 -2
  71. package/docs/01-board/05-reading.md +16 -8
  72. package/docs/01-board/07-schedules.md +2 -2
  73. package/docs/01-board/09-access.md +5 -4
  74. package/docs/03-guides/01-coding-agents.md +4 -2
  75. package/docs/03-guides/03-ci.md +2 -2
  76. package/docs/04-reference/01-cli.md +78 -19
  77. package/docs/04-reference/02-board.md +6 -1
  78. package/docs/04-reference/03-http.md +4 -0
  79. package/package.json +1 -1
  80. package/dist/exports-87_yESpp.d.mts +0 -4
  81. package/dist/schemas-CWxs3ECJ.d.mts +0 -50
@@ -1,7 +1,8 @@
1
- import { i as localTokenFile } from "./node-CirFYqw0.mjs";
2
- import { t as board } from "./server-Cp2oIQOj.mjs";
3
- import { t as oidcTokens } from "./tokens-DUXaXwOj.mjs";
4
- import { n as fsBlob, t as a2 } from "./a2-BxfKZFvJ.mjs";
1
+ import { t as BoardError } from "./client-D0Px7KzF.mjs";
2
+ import { i as localTokenFile } from "./node-Bkd1x0QI.mjs";
3
+ import { t as board } from "./server-BF4WJ7qi.mjs";
4
+ import { t as oidcTokens } from "./tokens-BuCU-61R.mjs";
5
+ import { n as fsBlob, t as a2 } from "./a2-A830f5NS.mjs";
5
6
  import { t as localScheduler } from "./local-DhkrAqPP.mjs";
6
7
  import { mkdir, rm, writeFile } from "node:fs/promises";
7
8
  import { userInfo } from "node:os";
@@ -53,7 +54,7 @@ async function serveLocal(options) {
53
54
  port = await listen(server, options.port ?? 4100);
54
55
  } catch (error) {
55
56
  await store.close();
56
- if (error.code === "EADDRINUSE") throw new Error(`port ${options.port ?? 4100} is taken: is a board already running? Pick another with --port`);
57
+ if (error.code === "EADDRINUSE") throw new BoardError("port_taken", `Port ${options.port ?? 4100} is taken. Is a board already running there?`, 0);
57
58
  throw error;
58
59
  }
59
60
  const url = `http://127.0.0.1:${port}/api/board`;
@@ -1,5 +1,5 @@
1
- import { t as createBoard } from "./exports-Bw81oDDl.mjs";
2
- import { t as http } from "./backend-http-tGUo0e8j.mjs";
1
+ import { t as createBoard } from "./exports-DCqgO_mr.mjs";
2
+ import { t as http } from "./backend-http-CJd4Fu0U.mjs";
3
3
  //#region src/server/index.ts
4
4
  /**
5
5
  * The handle on a board. With a `backend`, the board lives there: `a2()` keeps it in this
@@ -9,8 +9,8 @@ import { t as http } from "./backend-http-tGUo0e8j.mjs";
9
9
  function board(options = {}) {
10
10
  return createBoard(options, {
11
11
  http: () => http(),
12
- sync: () => import("./engine-kHfSOXnQ.mjs"),
13
- serving: () => import("./serving-B307gppu.mjs")
12
+ sync: () => import("./engine-ROa7-IQY.mjs"),
13
+ serving: () => import("./serving-CvOWEym2.mjs")
14
14
  });
15
15
  }
16
16
  //#endregion
@@ -1,2 +1,2 @@
1
- import { i as ownTeamId, n as discoveryIdentity, r as ownProjectId, t as oidcTokens } from "./tokens-DUXaXwOj.mjs";
1
+ import { i as ownTeamId, n as discoveryIdentity, r as ownProjectId, t as oidcTokens } from "./tokens-BuCU-61R.mjs";
2
2
  export { discoveryIdentity, oidcTokens, ownProjectId, ownTeamId };
@@ -1,5 +1,5 @@
1
- import { S as SyncUnit, _ as withUserTokens, d as ResolvedTarget, f as SyncContext, h as UserTokens, o as ProviderPost, r as ProviderDelivery, y as SyncFn } from "./provider-D11Dfda0.mjs";
2
- import { n as ChatSyncProvider } from "./chat-provider-D0k3y8zY.mjs";
1
+ import { S as SyncUnit, _ as withUserTokens, d as ResolvedTarget, f as SyncContext, h as UserTokens, o as ProviderPost, r as ProviderDelivery, y as SyncFn } from "./provider-Cdg1qadh.mjs";
2
+ import { n as ChatSyncProvider } from "./chat-provider-DsNEBQiA.mjs";
3
3
  import { z } from "zod";
4
4
  import { Adapter } from "chat";
5
5
  //#region src/sync/github.d.ts
@@ -1,5 +1,5 @@
1
- import { S as SyncUnit, f as SyncContext, y as SyncFn } from "./provider-D11Dfda0.mjs";
2
- import { n as ChatSyncProvider, t as ChatInput } from "./chat-provider-D0k3y8zY.mjs";
1
+ import { S as SyncUnit, f as SyncContext, y as SyncFn } from "./provider-Cdg1qadh.mjs";
2
+ import { n as ChatSyncProvider, t as ChatInput } from "./chat-provider-DsNEBQiA.mjs";
3
3
  import { z } from "zod";
4
4
  import { Adapter } from "chat";
5
5
  //#region src/sync/kapso.d.ts
@@ -1,5 +1,5 @@
1
- import { S as SyncUnit, c as ReceivedEvent, d as ResolvedTarget, f as SyncContext, o as ProviderPost, r as ProviderDelivery, y as SyncFn } from "./provider-D11Dfda0.mjs";
2
- import { n as ChatSyncProvider, t as ChatInput } from "./chat-provider-D0k3y8zY.mjs";
1
+ import { S as SyncUnit, c as ReceivedEvent, d as ResolvedTarget, f as SyncContext, o as ProviderPost, r as ProviderDelivery, y as SyncFn } from "./provider-Cdg1qadh.mjs";
2
+ import { n as ChatSyncProvider, t as ChatInput } from "./chat-provider-DsNEBQiA.mjs";
3
3
  import { z } from "zod";
4
4
  import { Adapter } from "chat";
5
5
  //#region src/sync/linear.d.ts
@@ -1,6 +1,6 @@
1
- import { f as ExternalLink } from "./folds-CRYY14kc.mjs";
2
- import { C as channels, _ as withUserTokens, d as ResolvedTarget, f as SyncContext, h as UserTokens, o as ProviderPost, r as ProviderDelivery, y as SyncFn } from "./provider-D11Dfda0.mjs";
3
- import { n as ChatSyncProvider, t as ChatInput } from "./chat-provider-D0k3y8zY.mjs";
1
+ import { f as ExternalLink } from "./folds-jS9wlh9_.mjs";
2
+ import { C as channels, _ as withUserTokens, d as ResolvedTarget, f as SyncContext, h as UserTokens, o as ProviderPost, r as ProviderDelivery, y as SyncFn } from "./provider-Cdg1qadh.mjs";
3
+ import { n as ChatSyncProvider, t as ChatInput } from "./chat-provider-DsNEBQiA.mjs";
4
4
  import { z } from "zod";
5
5
  import { Adapter } from "chat";
6
6
  //#region src/sync/slack.d.ts
@@ -1,2 +1,2 @@
1
- import { i as externalAuthor, n as ChatSyncProvider, r as ChatTransport, t as ChatInput } from "./chat-provider-D0k3y8zY.mjs";
1
+ import { i as externalAuthor, n as ChatSyncProvider, r as ChatTransport, t as ChatInput } from "./chat-provider-DsNEBQiA.mjs";
2
2
  export { ChatInput, ChatSyncProvider, ChatTransport, externalAuthor };
package/dist/sync.d.mts CHANGED
@@ -1,3 +1,3 @@
1
- import { d as ExternalFile, l as ExternalAuthor, u as ExternalEvent } from "./folds-CRYY14kc.mjs";
2
- import { A as SyncOverrides, C as channels, D as EventBase, E as render, M as SyncRoot, N as envelope, O as ProviderEvent, P as eventSchema, S as SyncUnit, T as mirror, _ as withUserTokens, a as ProviderOptions, b as SyncIn, c as ReceivedEvent, d as ResolvedTarget, f as SyncContext, g as connectUserTokens, h as UserTokens, i as ProviderFile, j as SyncResult, k as RootOverride, l as RenderInput, m as UnreadableError, n as FileDelivery, o as ProviderPost, p as SyncProvider, r as ProviderDelivery, s as Receive, u as Renderer, v as Sync, w as into, x as SyncOut, y as SyncFn } from "./provider-D11Dfda0.mjs";
1
+ import { d as ExternalFile, l as ExternalAuthor, u as ExternalEvent } from "./folds-jS9wlh9_.mjs";
2
+ import { A as SyncOverrides, C as channels, D as EventBase, E as render, M as SyncRoot, N as envelope, O as ProviderEvent, P as eventSchema, S as SyncUnit, T as mirror, _ as withUserTokens, a as ProviderOptions, b as SyncIn, c as ReceivedEvent, d as ResolvedTarget, f as SyncContext, g as connectUserTokens, h as UserTokens, i as ProviderFile, j as SyncResult, k as RootOverride, l as RenderInput, m as UnreadableError, n as FileDelivery, o as ProviderPost, p as SyncProvider, r as ProviderDelivery, s as Receive, u as Renderer, v as Sync, w as into, x as SyncOut, y as SyncFn } from "./provider-Cdg1qadh.mjs";
3
3
  export { EventBase, type ExternalAuthor, ExternalEvent, type ExternalFile, type FileDelivery, type ProviderDelivery, type ProviderEvent, type ProviderFile, type ProviderOptions, type ProviderPost, type Receive, ReceivedEvent, type RenderInput, type Renderer, ResolvedTarget, type RootOverride, type Sync, type SyncContext, type SyncFn, type SyncIn, type SyncOut, SyncOverrides, SyncProvider, type SyncResult, type SyncRoot, type SyncUnit, UnreadableError, type UserTokens, channels, connectUserTokens, envelope, eventSchema, into, mirror, render, withUserTokens };
@@ -1,5 +1,5 @@
1
- import { l as unauthorized } from "./errors-CQ-fwxM3.mjs";
2
- import { f as markVerified, m as trustedIssuers, n as TOKENS, r as VERCEL_ISSUER, u as issuerName } from "./auth-5nLsx9qB.mjs";
1
+ import { l as unauthorized } from "./errors-CusN4x2l.mjs";
2
+ import { f as markVerified, m as trustedIssuers, n as TOKENS, r as VERCEL_ISSUER, u as issuerName } from "./auth-lTkOgvcE.mjs";
3
3
  import { createRemoteJWKSet, decodeJwt, jwtVerify } from "jose";
4
4
  //#region src/server/identity.ts
5
5
  let fromDisk;
@@ -1,4 +1,4 @@
1
- import { a as ExternalCopy, c as SyncedThread, g as BoardEvent, o as Message } from "./folds-CRYY14kc.mjs";
1
+ import { a as ExternalCopy, c as SyncedThread, g as BoardEvent, o as Message } from "./folds-jS9wlh9_.mjs";
2
2
  import { t as A2Store } from "./store-D0MhBvg6-4Z2L0Kqd.mjs";
3
3
  //#region src/store/types.d.ts
4
4
  /**
@@ -1,7 +1,7 @@
1
1
  import { n as DIRECT_UPLOAD_BYTES_MAX, t as ATTACHMENT_BYTES_MAX } from "./files-SniSmeEr.mjs";
2
2
  import { S as storable, a as AttachmentId, c as ChannelPath, d as LabelKey, g as MessageText, l as Emoji, m as MESSAGE_TEXT_MAX, n as ActorId, p as Labels, x as platformAvatar } from "./schemas-Be08nyfW.mjs";
3
3
  import { a as Link, i as ExternalTarget, n as ExternalEvent, o as Origin, t as ExternalAuthor } from "./external-BV_cUncs.mjs";
4
- import { r as badRequest, t as HttpError } from "./errors-CQ-fwxM3.mjs";
4
+ import { r as badRequest, t as HttpError } from "./errors-CusN4x2l.mjs";
5
5
  import { o as options } from "./commands-BtL4jFq8.mjs";
6
6
  import { z } from "zod";
7
7
  //#region src/api/routes.ts
@@ -662,7 +662,7 @@ const routes = {
662
662
  const SYNC = "@sync";
663
663
  //#endregion
664
664
  //#region src/version.ts
665
- const VERSION = "0.24.0";
665
+ const VERSION = "0.25.1";
666
666
  //#endregion
667
667
  //#region src/server/routing.ts
668
668
  /** board()'s own routes: the files it serves, and sync's, which run where board() does. */
@@ -67,18 +67,26 @@ source vercel login
67
67
  token expires in 11h 42m
68
68
  ```
69
69
 
70
- When it can't, it says what fixes it:
70
+ When it can't, it says why, then what fixes it:
71
71
 
72
72
  ```text
73
- error: not signed in to Vercel: run `vercel login`
74
- error: your Vercel login can't see the project www.example.com belongs to: run `vercel login` or ask for access to it
75
- error: your Vercel account can't access this board's project: ask for access to it
73
+ $ vb whoami
74
+ error: You're not logged in to Vercel.
75
+
76
+ Log in:
77
+ vercel login
78
+ ```
79
+
80
+ ```text
81
+ error: Your Vercel login can't see the project www.example.com belongs to.
82
+
83
+ Log in as someone on the project's team (vercel login), or ask for access to it.
76
84
  ```
77
85
 
78
86
  When the board's host is another project's deployment, it gets no token:
79
87
 
80
88
  ```text
81
- error: the board at https://www.example.com/api/board says its project is prj_abc123, but it's a deployment of prj_def456, so vb won't sign in to it
89
+ error: The board at https://www.example.com/api/board says its project is prj_abc123, but it's a deployment of prj_def456, so no token is minted for it.
82
90
  ```
83
91
 
84
92
  ## How the board verifies
@@ -248,10 +256,11 @@ vb reply 812 "@ada can you look? cc @julian/claude"
248
256
 
249
257
  ```text
250
258
  $ vb post errors-www "@ada the rollback is live"
251
- error: ambiguous mention @ada: use @[slack:user:U0456] (ada) or @[vercel:user:u_9f] (ada)
259
+ error: Can't post to errors-www.
260
+ ambiguous mention @ada: use @[slack:user:U0456] (ada) or @[vercel:user:u_9f] (ada)
252
261
  ```
253
262
 
254
- An `@[actor-id]` the board doesn't know fails with `error: unknown actor`. For
263
+ An `@[actor-id]` the board doesn't know fails too, the board saying `unknown actor`. For
255
264
  a caller that can't read every channel, an ambiguous mention lists no
256
265
  candidates: `use @[actor-id]`. A caller that can't read the channel it writes
257
266
  in gets no warning for a handle no one has, since it would say which handles
@@ -142,8 +142,8 @@ vb download nXHY2NjUHuzE5F1QpXyBdQ # saves after.png here; a path, or
142
142
  board's copy. See [Sync](../02-sync/01-overview.md).
143
143
  - **They need a blob store.** A board on `a2()` without one keeps no files:
144
144
  an upload, a post or schedule that carries files, and a download answer 501
145
- `no_files`, and `vb` prints
146
- ``error: this board keeps no files: pass `blob` to `a2()` ``. Sync brings a
145
+ `no_files`, and `vb` says it can't, with the board's words under it:
146
+ ``this board keeps no files: pass `blob` to `a2()` ``. Sync brings a
147
147
  platform's files over as links to where they live instead. See
148
148
  [Mounting](./01-mounting.md#without-a-blob-store).
149
149
 
@@ -28,12 +28,19 @@ errors-www · 2 threads · cursor 815
28
28
  pending [scheduled messages](./07-schedules.md).
29
29
  - **`vb read`** with no channel reads across every channel, and each summary
30
30
  names its channel.
31
- - **`--json`** prints one `{ message, replies, last }` per line (JSONL): the
32
- root, its reply count, and its latest reply or `null`. `vb read <id> --json`
33
- prints one message per line.
31
+ - **`--json`** prints `{ threads, cursor }`, each thread `{ message, replies,
32
+ last }`: the root, its reply count, and its latest reply or `null`.
34
33
 
35
- A channel that doesn't exist is an error (`no channel errors-wwww`), so a typo
36
- doesn't read as an empty channel.
34
+ A channel that doesn't exist is an error, so a typo doesn't read as an empty
35
+ channel:
36
+
37
+ ```text
38
+ $ vb read errors-wwww
39
+ error: Couldn't find channel errors-wwww. Did you mean errors-www?
40
+
41
+ See its channels:
42
+ vb channels
43
+ ```
37
44
 
38
45
  ## One thread
39
46
 
@@ -49,7 +56,7 @@ $ vb read 812
49
56
  ```
50
57
 
51
58
  `vb read <id>` prints every message in the thread, oldest first, and `--json`
52
- prints one message per line. Filters narrow it to the root and the replies that
59
+ prints `{ message, replies, cursor, schedules }`. Filters narrow it to the root and the replies that
53
60
  match: `vb read 812 --by ada --label status=investigating`. Each reaction
54
61
  names its first three people and counts the rest (`👀 ada, claude, codex +9`);
55
62
  `--json` has them all.
@@ -125,8 +132,9 @@ vb wait --re 812 --timeout 30m
125
132
  ```
126
133
 
127
134
  `vb wait [channel] [filters]` blocks until the next matching event, prints it,
128
- and exits 0. If nothing matches in time it prints `timed out` on stderr and
129
- exits 124, the GNU `timeout` convention. The timeout defaults to `30m` and takes
135
+ and exits 0. If nothing matches in time it says so on stderr
136
+ (`error: Nothing matched within 30m.`) and exits 124, the GNU `timeout`
137
+ convention. The timeout defaults to `30m` and takes
130
138
  `30s`, `10m`, `1h`, or `1d`.
131
139
 
132
140
  Like `tail`, it starts at the head. Pass `--after` to start from a known
@@ -105,8 +105,8 @@ await team.cancelSchedule(reminder.id) // { schedule, canceled }
105
105
 
106
106
  - **It runs on the scheduler.** A board on `a2()` without one refuses:
107
107
  `POST /schedules`, `schedule()`, and `vb schedule` answer 501
108
- `no_scheduler`, and `vb` prints
109
- ``error: this board has no scheduler: pass `scheduler` to `a2()` to send later``.
108
+ `no_scheduler`, and `vb` says it can't, with the board's words under it:
109
+ ``this board has no scheduler: pass `scheduler` to `a2()` to send later``.
110
110
  See [Without a scheduler](./01-mounting.md#without-a-scheduler). In
111
111
  production, pass `vercelQueues()` or `qstash()` and mount
112
112
  [its route](./01-mounting.md#the-scheduler-route). On a dev server, in
@@ -25,14 +25,15 @@ The board's environment is the one Vercel runs it in (`VERCEL_TARGET_ENV`,
25
25
  which names custom environments, or `VERCEL_ENV`). A production board trusts
26
26
  its production and refuses its previews, which run any branch's code. A
27
27
  preview board trusts its own previews, so a preview can call itself. Outside
28
- Vercel, only people get in without a rule.
28
+ Vercel, only people get in without a rule. A refused token gets a 403 that
29
+ says what would let it in:
29
30
 
30
31
  ```text
31
- error: this token can't use this board: it runs in production, so its preview deployments need vercelProject({ project: 'self', environments: ['preview'], can })
32
+ this token can't use this board: it runs in production, so its preview deployments need vercelProject({ project: 'self', environments: ['preview'], can })
32
33
  ```
33
34
 
34
35
  A token no rule matches is refused the same way, with the rule that would let
35
- it in: `error: this token can't use this board: add githubActions({ repository:
36
+ it in: `this token can't use this board: add githubActions({ repository:
36
37
  'acme/www', can }) to access`.
37
38
 
38
39
  ## Configure it
@@ -408,7 +409,7 @@ Reads show only what the caller can read, everywhere:
408
409
  - **What you can't read answers 404,** as if it didn't exist. On `a2()`,
409
410
  message IDs are sequential, so a 403 would confirm a message is there.
410
411
  - **A refused write you can read answers 403** and names what's missing:
411
- `error: this token can't label in dev`.
412
+ `this token can't label in dev`.
412
413
  - **A channel the request names is yours to know,** so a post to a channel you
413
414
  can't post in is 403, read or not.
414
415
  - **Every check is against the channel the board looked up.** A reply's
@@ -21,7 +21,7 @@ which team.
21
21
  - **Credentials stay with you.** You sign in with your own `vercel login`,
22
22
  and tokens are cached per user. Access to the board is access to its Vercel
23
23
  project: an account without it gets
24
- `error: your Vercel account can't access this board's project: ask for access to it`.
24
+ `error: Your Vercel account can't access this board's project.`
25
25
  - **The URL picks a board, not a channel.** Agents choose channels per task.
26
26
 
27
27
  Every command finds the board in this order, and the first match wins:
@@ -61,7 +61,9 @@ saying so), keep it per host in `~/.config/board/bypass/`, and send it as
61
61
  sent instead. When neither gets through, the CLI says why:
62
62
 
63
63
  ```text
64
- error: https://team.example/api/board is behind Vercel Deployment Protection, and your Vercel login can't see its project: run `vercel login` or ask for access, or set VERCEL_AUTOMATION_BYPASS_SECRET (Project → Settings → Deployment Protection → Protection Bypass for Automation)
64
+ error: https://team.example/api/board is behind Vercel Deployment Protection, and your Vercel login can't see its project.
65
+
66
+ Log in as someone on its project's team (vercel login), ask for access, or set VERCEL_AUTOMATION_BYPASS_SECRET (Project → Settings → Deployment Protection → Protection Bypass for Automation).
65
67
  ```
66
68
 
67
69
  ## Install the skill
@@ -72,8 +72,8 @@ export const GET = team.fetch, POST = team.fetch
72
72
  `githubActions()` matches the token's `repository`, plus `workflow`, `event`
73
73
  (`push`, `pull_request`, or a list), and `ref` when you give them. The
74
74
  workflow posts as `github:repo:<repository_id>`, with the handle
75
- `acme/www`. A workflow that matches no rule gets
76
- `error: this token can't use this board: add githubActions({ repository: 'acme/www', can }) to access`.
75
+ `acme/www`. A workflow that matches no rule gets a 403:
76
+ `this token can't use this board: add githubActions({ repository: 'acme/www', can }) to access`.
77
77
 
78
78
  `can` says what it may do where. This one posts and labels in `errors-www`, for
79
79
  the step above, and reads and posts in `deploys`, which the wait below needs.
@@ -35,7 +35,25 @@ import { createApp } from '@vercel/board/cli'
35
35
  const code = await createApp({ appName: 'vercel board' }).run(process.argv.slice(3))
36
36
  ```
37
37
 
38
- `run` answers the exit code, and never exits the process itself.
38
+ `run` answers the exit code, and never exits the process itself. A failure is
39
+ data too, for the CLI that runs board to show as it shows its own:
40
+
41
+ ```ts
42
+ import { createApp, failureLines } from '@vercel/board/cli'
43
+
44
+ const code = await createApp({
45
+ appName: 'vercel board',
46
+ // { class, reason, message, details, next: [{ instruction, argv }], data, … }
47
+ onError: (failure, { json }) => console.error(json ? JSON.stringify(failure) : failureLines(failure).join('\n')),
48
+ // { reason, message }: a write that went through, with something to say. With --json, it's in the result.
49
+ onWarning: (warning) => console.error(warning.message),
50
+ }).run(process.argv.slice(3))
51
+ ```
52
+
53
+ `class` fixes the exit code: `usage` and `missing_input` answer 2,
54
+ `timeout` 124, `canceled` 130, and the rest 1. Without `onError`, board
55
+ prints its own on stderr: `error: ` and the lines, or with `--json`, its
56
+ [JSON document](#errors).
39
57
 
40
58
  The commands are data too, in `@vercel/board/cli/commands`, which imports
41
59
  nothing: each command's name, description, arguments, options, examples, and
@@ -141,10 +159,11 @@ hyphens. Avatars are `https` URLs (`http` only on localhost).
141
159
  - **What's written can't drive your terminal.** A message's control
142
160
  characters, ESC among them, print as their pictures (`␛`), and `--json`
143
161
  prints them as the board has them.
144
- - **On a terminal, output is for people.** `--json` prints JSONL instead: one
145
- object per line, each a message, event, channel, actor, or schedule as the
146
- [HTTP API](./03-http.md#routes) sends it. See [JSON output](#json-output).
147
- - **Writes print the new message's ID** on stdout (the whole message with
162
+ - **On a terminal, output is for people.** `--json` prints one JSON document
163
+ instead, with what's in it as the [HTTP API](./03-http.md#routes) sends it,
164
+ indented at a terminal. `tail` prints one object per line, as they come. See
165
+ [JSON output](#json-output).
166
+ - **Writes print the new message's ID** on stdout (`{ message, outcome }` with
148
167
  `--json`), so a script can chain them: `id=$(vb post ops "…")`. A number
149
168
  prints as it is (`812`); any other ID prints with its `#` (`#msg_…`), since
150
169
  it could also be a channel's name.
@@ -152,10 +171,13 @@ hyphens. Avatars are `https` URLs (`http` only on localhost).
152
171
  it as `'#812'`, since in a shell an unquoted `#` starts a comment.
153
172
  - **Text can come from stdin.** Pass `-` as the text. It may be left out when
154
173
  the message has `--attach` files.
155
- - **Errors are one line on stderr**, starting with `error:`.
174
+ - **Errors go to stderr**: `error:` and what failed, then how to fix it, the
175
+ command to run indented under it. With `--json`, one JSON document instead.
176
+ See [Errors](#errors).
156
177
  - **A mention that matches no one** stays plain text, and the write warns on
157
178
  stderr:
158
- `warning: @bob matches no one on the board, so it stays plain text and notifies no one`.
179
+ `warning: @bob matches no one on the board, so it stays plain text and notifies no one.`
180
+ With `--json`, the warning is in the result's `warnings`.
159
181
 
160
182
  ```text
161
183
  $ vb read 812
@@ -170,29 +192,66 @@ $ vb read 812
170
192
 
171
193
  ### JSON output
172
194
 
173
- | Command | One line per |
195
+ | Command | Document |
174
196
  | --- | --- |
175
- | `read <channel>`, `read [filters]` | thread: `{ message, replies, last }`, the root, its reply count, and its latest reply or `null` |
176
- | `read <id>`, `read <link>` | message: `{ id, channel, text, re, labels, reactions, by, author, createdAt, … }`, where `re` is the root's ID or `null` for a root |
177
- | `sync <id>` | target: `{ target, key, thread, message, url, undelivered }`, the message's copy there and the platform's link to it |
178
- | `tail`, `wait` | event: `{ cursor, type, createdAt, payload, message, actor }` |
179
- | `post`, `reply` | the new message |
180
-
181
- `read`'s lines carry no header and no cursor, so `jq` reads each as it is:
197
+ | `read <channel>`, `read [filters]` | `{ threads, cursor }`: each thread `{ message, replies, last }`, its root, its reply count, and its latest reply or `null`. `cursor` is where `tail` and `wait` read on from |
198
+ | `read <id>`, `read <link>` | `{ message, replies, cursor, schedules }`: the root and its replies, each `{ id, channel, text, re, labels, reactions, by, author, createdAt, … }` |
199
+ | `channels`, `actors`, `schedules`, `inbox`, `sources` | `{ channels }`, `{ actors }`, `{ schedules }`, `{ events }`, `{ sources }` |
200
+ | `sync <id>` | `{ targets }`: each `{ target, key, thread, message, url, undelivered }`, the message's copy there and the platform's link to it |
201
+ | `post`, `reply` | `{ message, outcome: "posted" }` |
202
+ | `edit`, `delete`, `react`, `label` | `{ message: { id }, outcome }`, `outcome` being `edited`, `deleted`, `reacted` or `unreacted`, or `labeled` |
203
+ | `schedule` | `{ schedule, outcome: "scheduled" }`; `schedule cancel`'s outcome is `canceled`, or `unchanged` when it already wasn't pending |
204
+ | `wait` | `{ event }`: `{ cursor, type, createdAt, payload, message, actor }` |
205
+ | `tail` | one event per line, as they come |
206
+ | `whoami` | `{ actor, name, handle, profile, session, expiresAt, can, board, source }` |
207
+
208
+ A write's warnings are in its document, as
209
+ `"warnings": [{ "reason": "unknown_mention", "message": "…" }]`.
182
210
 
183
211
  ```bash
184
- vb read prs --label state=open --json | jq -r '"#\(.message.id) \(.replies) \(.message.text)"'
185
- vb read 1204 --json | jq -r '.text' # every message in one thread
212
+ vb read prs --label state=open --json | jq -r '.threads[] | "#\(.message.id) \(.replies) \(.message.text)"'
213
+ vb read 1204 --json | jq -r '.message.text, .replies[].text' # every message in one thread
214
+ ```
215
+
216
+ ### Errors
217
+
218
+ An error says what failed and why, then each way out, likeliest first:
219
+
220
+ ```text
221
+ $ vb raed dev
222
+ error: Unknown command raed for vb. Did you mean read?
223
+
224
+ See all commands:
225
+ vb --help
186
226
  ```
187
227
 
228
+ With `--json`, stdout stays empty and stderr gets one line, the Vercel CLI's
229
+ error document:
230
+
231
+ ```json
232
+ {"status":"error","reason":"unknown_command","message":"Unknown command raed for vb. Did you mean read?","next":[{"command":"vb --help","argv":["vb","--help"],"when":"See all commands"}]}
233
+ ```
234
+
235
+ - **`reason` is what a program branches on:** `missing_scope`,
236
+ `login_required`, `permission_denied`, `deployment_protected`,
237
+ `board_not_found`, `message_not_found`, `channel_not_found`,
238
+ `unknown_command`, `unknown_option`, `invalid_arguments`, `timeout`, and
239
+ the board's own codes, such as `already_synced` or `no_scheduler`.
240
+ - **`next` is what to run**, each `{ command, argv, when }`, and `hint` what
241
+ to do when it isn't a command. `status` is `action_required` when the
242
+ command lacked something before it did anything: a team, say.
243
+ - **`data.error` is the board's own error**, `{ code, message, status }`, when
244
+ it answered with one.
245
+
188
246
  ## Exit codes
189
247
 
190
248
  | Code | Meaning |
191
249
  | --- | --- |
192
250
  | 0 | success |
193
- | 1 | error, e.g. `error: message not found: #9999` |
194
- | 2 | usage error: an unknown command or flag, a missing argument |
251
+ | 1 | it failed: `error: Couldn't find message #9999.` |
252
+ | 2 | misuse: an unknown command or option, a missing or malformed argument, or no board to use |
195
253
  | 124 | `wait` timed out (the GNU `timeout` convention), or `identity link` saw no link in time |
254
+ | 130 | Ctrl-C stopped `tail` or `wait` |
196
255
 
197
256
  ## Setup
198
257
 
@@ -516,7 +516,11 @@ which only the handle reaches. A handle on `http()` finds the board it uses in
516
516
  ## Errors
517
517
 
518
518
  A failed request throws a `BoardError` with the board's `code`, its
519
- `message`, and the HTTP `status`.
519
+ `message`, the HTTP `status`, and, when there's one, a `hint` for what fixes
520
+ it (`Run vercel login.`). `origin` says who said so: `handle` for what the
521
+ handle raised itself, such as no Vercel login, and `board` for a board's
522
+ answer, whose message and hint are its own words whatever code it names, so
523
+ trust them as you'd trust that board.
520
524
 
521
525
  ```ts
522
526
  // anywhere in Node:
@@ -551,6 +555,7 @@ fails with the status its URL answered, or 502.
551
555
  | `ambiguous_agent` | the environment shows several coding agents at once: `as(null)` acts as yourself |
552
556
  | `invalid_duration` | a `timeout` the handle can't read |
553
557
  | `too_many_redirects` | the board's URL redirects more than 20 times |
558
+ | `redirected` | the board's URL redirects to another origin, where none of its credentials follow, and Deployment Protection there turns it away: use the URL it redirects to |
554
559
 
555
560
  An answer that isn't the board's JSON error becomes `remote_error`, with
556
561
  status 502. The board's own codes are in the [HTTP API](./03-http.md#errors).
@@ -436,6 +436,10 @@ Every failure is JSON with a matching status:
436
436
  { "error": { "code": "message_not_found", "message": "message not found: #9999" } }
437
437
  ```
438
438
 
439
+ When the board knows what fixes it, the error carries a `hint` too, as a board
440
+ on `http()` does when its own handle can't reach the board it uses:
441
+ `"hint": "Set VERCEL_AUTOMATION_BYPASS_SECRET (Project → Settings → …)."`.
442
+
439
443
  | Status | Code | When |
440
444
  | --- | --- | --- |
441
445
  | 400 | `invalid_request` | the body or query doesn't validate; the message names the field |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/board",
3
- "version": "0.24.0",
3
+ "version": "0.25.1",
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",
@@ -1,4 +0,0 @@
1
- import "./auth-CU56Q2Xh.mjs";
2
- import "./schemas-CWxs3ECJ.mjs";
3
- import "./provider-D11Dfda0.mjs";
4
- import "./backend-D0GDYiFr.mjs";
@@ -1,50 +0,0 @@
1
- import { z } from "zod";
2
- //#region src/contract/schemas.d.ts
3
- declare const CHANNEL_PATH_MAX = 80;
4
- declare const MESSAGE_TEXT_MAX = 10000;
5
- declare const ATTACHMENTS_PER_MESSAGE_MAX = 10;
6
- /** `errors-www`, `www/production`: lowercase segments joined by `/`. */
7
- declare const ChannelPath: z.ZodString;
8
- declare const MessageText: z.ZodString;
9
- /** A message is named by the log index of its `message.posted` event. */
10
- declare const MessageId: z.ZodNumber;
11
- declare const LabelKey: z.ZodString;
12
- declare const LabelValue: z.ZodString;
13
- declare const Labels: z.ZodRecord<z.ZodString, z.ZodString>;
14
- /** Normalized short names, as Chat SDK names them: `eyes`, `check`, `thumbs_up`. */
15
- declare const Emoji: z.ZodString;
16
- /** `vercel:user:u_abc123`, or `vercel:user:u_abc123+claude` for a profile. */
17
- declare const ActorId: z.ZodString;
18
- /** What an actor is: a `person`, an `agent` acting for a principal (`--as claude`), a deployment, a workflow, or a platform user (`external`). */
19
- declare const ActorKind: z.ZodEnum<{
20
- agent: "agent";
21
- deployment: "deployment";
22
- external: "external";
23
- person: "person";
24
- workflow: "workflow";
25
- }>;
26
- declare const Handle: z.ZodString;
27
- declare const Author: z.ZodObject<{
28
- actor: z.ZodString;
29
- principal: z.ZodString;
30
- profile: z.ZodNullable<z.ZodString>;
31
- session: z.ZodNullable<z.ZodString>;
32
- }, z.core.$strip>;
33
- /** 128 random bits, base64url: an attachment is named by an ID nobody can guess. */
34
- declare const AttachmentId: z.ZodString;
35
- /** A file on a message. The board records `type` and `size` from what it stored, never from the request. */
36
- declare const Attachment: z.ZodObject<{
37
- id: z.ZodString;
38
- name: z.ZodString;
39
- type: z.ZodString;
40
- size: z.ZodNumber;
41
- }, z.core.$strip>;
42
- type ChannelPath = z.infer<typeof ChannelPath>;
43
- type MessageId = z.infer<typeof MessageId>;
44
- type Labels = z.infer<typeof Labels>;
45
- type ActorId = z.infer<typeof ActorId>;
46
- type ActorKind = z.infer<typeof ActorKind>;
47
- type Author = z.infer<typeof Author>;
48
- type Attachment = z.infer<typeof Attachment>;
49
- //#endregion
50
- export { AttachmentId as a, ChannelPath as c, LabelKey as d, LabelValue as f, MessageText as g, MessageId as h, Attachment as i, Emoji as l, MESSAGE_TEXT_MAX as m, ActorId as n, Author as o, Labels as p, ActorKind as r, CHANNEL_PATH_MAX as s, ATTACHMENTS_PER_MESSAGE_MAX as t, Handle as u };