@vercel/board 0.18.1 → 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.
Files changed (64) hide show
  1. package/dist/{a2-CW8jHGD_.mjs → a2-D__qQpe7.mjs} +37 -6
  2. package/dist/access.mjs +3 -3
  3. package/dist/{actors-BOamkEuz.mjs → actors-rPnEQLZu.mjs} +2 -2
  4. package/dist/{auth-Drg9FiC2.mjs → auth-5nLsx9qB.mjs} +1 -1
  5. package/dist/{backend-B-SJ7p6p.d.mts → backend-CWmd4UH-.d.mts} +8 -1
  6. package/dist/backend-a2.d.mts +22 -2
  7. package/dist/backend-a2.mjs +1 -1
  8. package/dist/{backend-http-7cNb8nJI.d.mts → backend-http-BfWQfvqB.d.mts} +22 -2
  9. package/dist/{backend-http-CDDS7MLI.mjs → backend-http-CG5y03UP.mjs} +25 -6
  10. package/dist/{backend-http-BjH8jMDi.mjs → backend-http-c-VHpUOs.mjs} +19 -6
  11. package/dist/backend-http.d.mts +1 -1
  12. package/dist/backend-http.mjs +1 -1
  13. package/dist/backend-http.node.d.mts +1 -1
  14. package/dist/backend-http.node.mjs +1 -1
  15. package/dist/{chat-provider-DNJQyajS.d.mts → chat-provider-D0k3y8zY.d.mts} +4 -1
  16. package/dist/{chat-provider-YHy-WbrB.mjs → chat-provider-DnHibqnM.mjs} +4 -1
  17. package/dist/cli-embedded.d.mts +47 -0
  18. package/dist/cli-embedded.mjs +64 -0
  19. package/dist/cli.mjs +73 -1661
  20. package/dist/{client-BA-J7usH.mjs → client-ClznIBMg.mjs} +2 -0
  21. package/dist/{conformance-CT5EeHeC.mjs → conformance-DQ9qVzWP.mjs} +47 -6
  22. package/dist/conformance.d.mts +1 -1
  23. package/dist/conformance.mjs +1 -1
  24. package/dist/{engine-D0oBUBEK.mjs → engine-kHfSOXnQ.mjs} +15 -4
  25. package/dist/{errors-DX3hpwWt.mjs → errors-CQ-fwxM3.mjs} +13 -1
  26. package/dist/{exports-CwsfgCul.mjs → exports-DqWJ7nzC.mjs} +15 -6
  27. package/dist/exports-ggfE9jNv.d.mts +4 -0
  28. package/dist/index-DsMPCv_K.d.mts +11 -0
  29. package/dist/index.d.mts +4 -4
  30. package/dist/{index.default-jkA7eFFe.mjs → index.default-C3luVnd-.mjs} +1 -1
  31. package/dist/index.mjs +6 -6
  32. package/dist/index.node.d.mts +4 -12
  33. package/dist/index.node.mjs +4 -4
  34. package/dist/inspect/app.js +93 -92
  35. package/dist/inspect/engine.js +4 -4
  36. package/dist/main-wB-VfTCL.mjs +1744 -0
  37. package/dist/{middleware-CqaCwucP.mjs → middleware-DUjyQP_a.mjs} +1 -1
  38. package/dist/{provider-B0b9Qxw4.d.mts → provider-D11Dfda0.d.mts} +91 -22
  39. package/dist/{provider-B3DpYSDq.mjs → provider-poIkXapd.mjs} +4 -0
  40. package/dist/{serve-Dpb7zMJ0.mjs → serve-AnApUWOR.mjs} +4 -4
  41. package/dist/{server-Bj1_ugXd.mjs → server-BqVickb5.mjs} +4 -4
  42. package/dist/{serving-CNkgUPW1.mjs → serving-B307gppu.mjs} +1 -1
  43. package/dist/sync-github.d.mts +2 -2
  44. package/dist/sync-github.mjs +2 -2
  45. package/dist/sync-kapso.d.mts +2 -2
  46. package/dist/sync-kapso.mjs +2 -2
  47. package/dist/sync-linear.d.mts +2 -2
  48. package/dist/sync-linear.mjs +2 -2
  49. package/dist/sync-slack.d.mts +11 -2
  50. package/dist/sync-slack.mjs +57 -17
  51. package/dist/sync.chat.d.mts +1 -1
  52. package/dist/sync.chat.mjs +1 -1
  53. package/dist/sync.d.mts +1 -1
  54. package/dist/sync.mjs +1 -1
  55. package/dist/{tokens-DhuzKzAl.mjs → tokens-DUXaXwOj.mjs} +2 -2
  56. package/dist/{watchers-hash-qlnbKQQ0.mjs → watchers-hash-DYwvkhdX.mjs} +38 -7
  57. package/docs/01-board/06-watchers.md +7 -0
  58. package/docs/02-sync/03-slack.md +14 -1
  59. package/docs/02-sync/06-providers.md +1 -0
  60. package/docs/04-reference/01-cli.md +55 -3
  61. package/docs/04-reference/02-board.md +8 -3
  62. package/docs/04-reference/03-http.md +7 -4
  63. package/package.json +5 -1
  64. package/dist/exports-ZsgjzEnM.d.mts +0 -4
@@ -1,15 +1,15 @@
1
1
  import { n as DIRECT_UPLOAD_BYTES_MAX, r as IMPORT_BYTES_MAX, t as ATTACHMENT_BYTES_MAX } from "./files-SniSmeEr.mjs";
2
2
  import { _ as RemoteWatcherId, b as actorKind, i as Attachment, x as platformAvatar } from "./schemas-Be08nyfW.mjs";
3
3
  import "./external-BV_cUncs.mjs";
4
- import { f as VERSION, n as compileWatcher, o as METHODS, p as SYNC, r as hasCriteria, t as canonicalWatchers } from "./watchers-hash-qlnbKQQ0.mjs";
5
- import { r as duration, t as BoardError } from "./client-BA-J7usH.mjs";
4
+ import { f as VERSION, n as compileWatcher, o as METHODS, p as SYNC, r as hasCriteria, t as canonicalWatchers } from "./watchers-hash-DYwvkhdX.mjs";
5
+ import { r as duration, t as BoardError } from "./client-ClznIBMg.mjs";
6
6
  import { n as derivedId, r as isKeyIdOf } from "./ids-C6AYo1bu.mjs";
7
- import { a as forbidden, c as toErrorResponse, i as duplicateId, r as badRequest, s as notFound, t as HttpError } from "./errors-DX3hpwWt.mjs";
7
+ import { a as forbidden, c as toErrorResponse, i as duplicateId, r as badRequest, s as notFound, t as HttpError } from "./errors-CQ-fwxM3.mjs";
8
8
  import { t as own } from "./own-OwgPWdmh.mjs";
9
9
  import { n as replaceMentions } from "./mentions-CB6ShY4R.mjs";
10
- import { _ as FULL_ACCESS, i as authenticates, v as authorize } from "./auth-Drg9FiC2.mjs";
11
- import { a as resolveMentions, n as actorSeen } from "./actors-BOamkEuz.mjs";
12
- import { n as MIDDLEWARE_ACTOR, t as DroppedError } from "./middleware-CqaCwucP.mjs";
10
+ import { _ as FULL_ACCESS, i as authenticates, v as authorize } from "./auth-5nLsx9qB.mjs";
11
+ import { a as resolveMentions, n as actorSeen } from "./actors-rPnEQLZu.mjs";
12
+ import { n as MIDDLEWARE_ACTOR, t as DroppedError } from "./middleware-DUjyQP_a.mjs";
13
13
  import { n as syncTarget, r as withEmailMentions } from "./targets-C-wdR5wP.mjs";
14
14
  import { t as A2Error } from "./session-clear-wHd5GCpZ-BnKSxReK.mjs";
15
15
  import { a as reducer } from "./reducer-CjmvZSAb-D_OSISmj.mjs";
@@ -714,8 +714,32 @@ function createReads(tables, session, projection) {
714
714
  });
715
715
  waitUntil(catching);
716
716
  }
717
+ /**
718
+ * The tables caught up with the whole log once on this instance. Until then, a read by index waits
719
+ * for all of it: tables rebuilt from the log can be far behind, and a message's later events (an
720
+ * edit, a reaction, a reply) come after its own index. Once caught up, each write applies as it lands.
721
+ */
722
+ let whole = null;
723
+ function caughtUp() {
724
+ whole ??= session.consume(projection).then(() => void 0, (error) => {
725
+ whole = null;
726
+ throw error;
727
+ });
728
+ return whole;
729
+ }
730
+ /** `promise`, unless `signal` aborts first: the caller stops waiting, and what it waits for keeps going. */
731
+ function untilAborted(promise, signal) {
732
+ if (!signal) return promise;
733
+ if (signal.aborted) return Promise.reject(signal.reason);
734
+ return new Promise((resolve, reject) => {
735
+ const abort = () => reject(signal.reason);
736
+ signal.addEventListener("abort", abort, { once: true });
737
+ promise.then(resolve, reject).finally(() => signal.removeEventListener("abort", abort));
738
+ });
739
+ }
717
740
  /** Waits until the tables have applied `index`, or the whole log when it's omitted. */
718
741
  async function ensure(index, signal) {
742
+ if (index !== void 0) await untilAborted(caughtUp(), signal);
719
743
  if (index !== void 0 && await tables.position() >= index) return;
720
744
  await session.consume(projection, {
721
745
  ...index !== void 0 ? { through: index } : {},
@@ -3081,6 +3105,7 @@ function build(options, binding) {
3081
3105
  return { watchers: principal.access.everywhere("manage") ? all : all.filter((found) => found.owner === principal.id) };
3082
3106
  },
3083
3107
  async register(caller, body) {
3108
+ if (body.path) throw new HttpError(501, "no_wake_by_path", "this board wakes a url, not a path: register `url` instead");
3084
3109
  const { principal } = await identityOf(caller);
3085
3110
  ownWatchers(principal);
3086
3111
  if (!principal.access.anywhere("read")) throw forbidden("this token can't read anywhere, so a watcher it registers would see nothing");
@@ -3179,6 +3204,12 @@ function build(options, binding) {
3179
3204
  if (!principal.access.everywhere("manage")) throw forbidden("this token can't read a source: it needs manage in every channel");
3180
3205
  return binding.readSource(key, target, limit ?? 50);
3181
3206
  },
3207
+ async joinSource(caller, { key, target }) {
3208
+ if (!binding.sync.some((entry) => entry.key === key)) throw notFound(`this board doesn't run sync entry ${key}: join it where its sync runs`, "not_synced_here");
3209
+ const { principal } = await identityOf(caller);
3210
+ if (!principal.access.everywhere("manage")) throw forbidden("this token can't join a source: it needs manage in every channel");
3211
+ return binding.joinSource(key, target);
3212
+ },
3182
3213
  async synced(caller, { id }, signal) {
3183
3214
  const { principal } = await identityOf(caller);
3184
3215
  const message = await reads.message(messageAt(id), signal);
package/dist/access.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { a as compileRules, c as githubActions, h as vercelProject, p as oidc } from "./auth-Drg9FiC2.mjs";
2
- import { i as resolveActor } from "./actors-BOamkEuz.mjs";
3
- import { t as oidcTokens } from "./tokens-DhuzKzAl.mjs";
1
+ import { a as compileRules, c as githubActions, h as vercelProject, p as oidc } from "./auth-5nLsx9qB.mjs";
2
+ import { i as resolveActor } from "./actors-rPnEQLZu.mjs";
3
+ import { t as oidcTokens } from "./tokens-DUXaXwOj.mjs";
4
4
  export { compileRules, githubActions, oidc, oidcTokens, resolveActor, vercelProject };
@@ -1,8 +1,8 @@
1
1
  import { C as toStorable } from "./schemas-Be08nyfW.mjs";
2
- import { r as badRequest } from "./errors-DX3hpwWt.mjs";
2
+ import { r as badRequest } from "./errors-CQ-fwxM3.mjs";
3
3
  import { t as own } from "./own-OwgPWdmh.mjs";
4
4
  import { t as extractMentions } from "./mentions-CB6ShY4R.mjs";
5
- import { y as parseScope } from "./auth-Drg9FiC2.mjs";
5
+ import { y as parseScope } from "./auth-5nLsx9qB.mjs";
6
6
  //#region src/server/actors.ts
7
7
  /** Agents whose avatars the board serves itself. */
8
8
  const KNOWN_AGENTS = [
@@ -1,5 +1,5 @@
1
1
  import { c as ChannelPath, v as SCOPE_MAX } from "./schemas-Be08nyfW.mjs";
2
- import { a as forbidden, l as unauthorized } from "./errors-DX3hpwWt.mjs";
2
+ import { a as forbidden, l as unauthorized } from "./errors-CQ-fwxM3.mjs";
3
3
  //#region src/server/access.ts
4
4
  /**
5
5
  * What a principal may do in a channel. `read` covers messages, threads,
@@ -2,7 +2,7 @@ import { t as Access } from "./access-CV96SYgr.mjs";
2
2
  import { m as RuleFunction, n as AccessRule, r as Authenticate, s as Granted, t as AccessFunction, u as Known } from "./auth-CU56Q2Xh.mjs";
3
3
  import { i as Attachment, o as Author, p as Labels } from "./schemas-CWxs3ECJ.mjs";
4
4
  import { t as Actor, u as ExternalEvent } from "./folds-CRYY14kc.mjs";
5
- import { $ as Discovery, Et as PostBody, F as BoardClient, Ft as ScheduleBody, Gt as SourceReadQuery, Kt as SyncAccountBody, Mt as RegistrationView, Qt as SyncCopiesView, Rt as ScheduleView, St as MessagesQuery, Tt as PersonView, Wt as SourceRead, X as BoardEventView, Xt as SyncCodeView, Yt as SyncCodeBody, Z as ChannelView, Zt as SyncCopiesQuery, _n as UploadStartBody, _t as MatchView, bt as MessageView, cn as SyncUnlinkBody, ct as InboxEntry, dn as ThreadView, dt as InboxPushBody, en as SyncFindQuery, et as EditBody, fn as ThreadsPage, ft as InboxQuery, gt as LinkedQuery, hn as TypingBody, ht as LinkedAccount, in as SyncReceiptBody, jt as RegisterBody, kt as ReactBody, ln as SyncedView, lt as InboxItem, nn as SyncImportBody, nt as EventsQuery, on as SyncRootsQuery, ot as IdentityView, p as SyncProvider, pn as ThreadsQuery, pt as LabelBody, q as AckBody, sn as SyncThreadView, st as InboxAckBody, tn as SyncFindView, tt as EventsPage, ut as InboxNextQuery, vn as UploadTicket, vt as MatchesQuery, xt as MessagesPage, yt as Me, z as ProfileOption, zt as SchedulesQuery } from "./provider-B0b9Qxw4.mjs";
5
+ import { $ as Discovery, $t as SyncCodeView, Bt as SchedulesQuery, Et as PostBody, F as BoardClient, Gt as SourceJoinBody, It as ScheduleBody, Jt as SourceReadQuery, Nt as RegistrationView, Qt as SyncCodeBody, St as MessagesQuery, Tt as PersonView, Wt as SourceJoin, X as BoardEventView, Yt as SyncAccountBody, Z as ChannelView, _t as MatchView, an as SyncImportBody, bn as UploadStartBody, bt as MessageView, ct as InboxEntry, dn as SyncUnlinkBody, dt as InboxPushBody, en as SyncCopiesQuery, et as EditBody, fn as SyncedView, ft as InboxQuery, gn as ThreadsQuery, gt as LinkedQuery, hn as ThreadsPage, ht as LinkedAccount, in as SyncFindView, jt as RegisterBody, kt as ReactBody, ln as SyncRootsQuery, lt as InboxItem, mn as ThreadView, nt as EventsQuery, ot as IdentityView, p as SyncProvider, pt as LabelBody, q as AckBody, qt as SourceRead, rn as SyncFindQuery, sn as SyncReceiptBody, st as InboxAckBody, tn as SyncCopiesView, tt as EventsPage, un as SyncThreadView, ut as InboxNextQuery, vn as TypingBody, vt as MatchesQuery, xn as UploadTicket, xt as MessagesPage, yt as Me, z as ProfileOption, zt as ScheduleView } from "./provider-D11Dfda0.mjs";
6
6
  import { z } from "zod";
7
7
  //#region src/api/protocol.d.ts
8
8
  type Input<S extends z.ZodType> = NonNullable<z.output<S>>;
@@ -186,6 +186,11 @@ type Protocol = {
186
186
  input: Key & Input<typeof SourceReadQuery>;
187
187
  output: SourceRead;
188
188
  };
189
+ /** Adds sync entry `key`'s app to a public channel, where that sync runs and opted in: needs `manage` everywhere. */
190
+ joinSource: {
191
+ input: Key & Input<typeof SourceJoinBody>;
192
+ output: SourceJoin;
193
+ };
189
194
  /** Where a message's thread is synced, and its copy on each target, from sync's records: needs `read` where it is. */
190
195
  synced: {
191
196
  input: Id;
@@ -618,6 +623,8 @@ type BoardBinding = {
618
623
  * `readSource` answers. 404 `not_synced_here` for an entry it doesn't run.
619
624
  */
620
625
  readSource(key: string, target: string, limit: number): Promise<SourceRead>;
626
+ /** Adds sync entry `key`'s app to a channel, where it opted in: what `joinSource` answers. 404 `not_synced_here` likewise. */
627
+ joinSource(key: string, target: string): Promise<SourceJoin>;
621
628
  /**
622
629
  * The message a platform link names, as each platform this board syncs reads it: the platform,
623
630
  * and its own thread and message IDs, the message null for a link to a thread. Any entry of that
@@ -2,8 +2,8 @@ import { i as Attachment } from "./schemas-CWxs3ECJ.mjs";
2
2
  import { t as Actor } from "./folds-CRYY14kc.mjs";
3
3
  import { l as Reducer, r as ContractEvent } from "./reducer-CYPHsSmd-BQIfozGi.mjs";
4
4
  import { a as SessionAppend, c as SessionConsumeOptions, d as SessionSchedule, f as StateOptions, l as SessionConsumeResult, n as A2Server, o as SessionClearOptions, p as SessionCleared, r as Consumer, s as SessionClearResult, t as A2Scheduler, u as SessionDeadLetters } from "./server-qgez0Xny-D5mLfqFQ.mjs";
5
- import { $ as Discovery, Mt as RegistrationView, Qt as SyncCopiesView, Rt as ScheduleView, Tt as PersonView, Wt as SourceRead, Xt as SyncCodeView, Z as ChannelView, _t as MatchView, bt as MessageView, ct as InboxEntry, dn as ThreadView, fn as ThreadsPage, ht as LinkedAccount, ln as SyncedView, lt as InboxItem, ot as IdentityView, sn as SyncThreadView, tn as SyncFindView, tt as EventsPage, vn as UploadTicket, xt as MessagesPage, yt as Me } from "./provider-B0b9Qxw4.mjs";
6
- import { i as Caller, n as BackendRoute, r as BoardBinding } from "./backend-B-SJ7p6p.mjs";
5
+ import { $ as Discovery, $t as SyncCodeView, Nt as RegistrationView, Tt as PersonView, Wt as SourceJoin, Z as ChannelView, _t as MatchView, bt as MessageView, ct as InboxEntry, fn as SyncedView, hn as ThreadsPage, ht as LinkedAccount, in as SyncFindView, lt as InboxItem, mn as ThreadView, ot as IdentityView, qt as SourceRead, tn as SyncCopiesView, tt as EventsPage, un as SyncThreadView, xn as UploadTicket, xt as MessagesPage, yt as Me, zt as ScheduleView } from "./provider-D11Dfda0.mjs";
6
+ import { i as Caller, n as BackendRoute, r as BoardBinding } from "./backend-CWmd4UH-.mjs";
7
7
  import { n as fsBlob, r as memoryBlob, t as BlobStore } from "./blob-C0J3wbGv.mjs";
8
8
  import { t as BoardStore } from "./types-8yq2VnKI.mjs";
9
9
  import { z } from "zod";
@@ -220,7 +220,19 @@ export declare function a2(options?: A2Options): {
220
220
  watchers: RegistrationView[];
221
221
  }>;
222
222
  register: (caller: Caller, input: {
223
+ watch: Record<string, {
224
+ tail: string;
225
+ when: "enters" | "matches";
226
+ }>;
227
+ sync?: {
228
+ key: string;
229
+ platform: string;
230
+ signIn: boolean;
231
+ }[] | undefined;
223
232
  url?: string | null | undefined;
233
+ path?: null | undefined;
234
+ branch?: null | undefined;
235
+ } | {
224
236
  watch: Record<string, {
225
237
  tail: string;
226
238
  when: "enters" | "matches";
@@ -230,6 +242,9 @@ export declare function a2(options?: A2Options): {
230
242
  platform: string;
231
243
  signIn: boolean;
232
244
  }[] | undefined;
245
+ url?: null | undefined;
246
+ path: string;
247
+ branch?: string | null | undefined;
233
248
  }, signal: AbortSignal) => Promise<RegistrationView>;
234
249
  matches: (caller: Caller, input: {
235
250
  limit?: number | undefined;
@@ -271,6 +286,11 @@ export declare function a2(options?: A2Options): {
271
286
  target: string;
272
287
  limit?: number | undefined;
273
288
  }, signal: AbortSignal) => Promise<SourceRead>;
289
+ joinSource: (caller: Caller, input: {
290
+ key: string;
291
+ } & {
292
+ target: string;
293
+ }, signal: AbortSignal) => Promise<SourceJoin>;
274
294
  synced: (caller: Caller, input: {
275
295
  id: string;
276
296
  }, signal: AbortSignal) => Promise<SyncedView>;
@@ -1,2 +1,2 @@
1
- import { n as fsBlob, r as memoryBlob, t as a2 } from "./a2-CW8jHGD_.mjs";
1
+ import { n as fsBlob, r as memoryBlob, t as a2 } from "./a2-D__qQpe7.mjs";
2
2
  export { a2, fsBlob, memoryBlob };
@@ -1,7 +1,7 @@
1
1
  import { i as Attachment } from "./schemas-CWxs3ECJ.mjs";
2
2
  import { t as Actor } from "./folds-CRYY14kc.mjs";
3
- import { $ as Discovery, Mt as RegistrationView, Qt as SyncCopiesView, Rt as ScheduleView, Tt as PersonView, Wt as SourceRead, Xt as SyncCodeView, Z as ChannelView, _t as MatchView, bt as MessageView, ct as InboxEntry, dn as ThreadView, fn as ThreadsPage, ht as LinkedAccount, ln as SyncedView, lt as InboxItem, ot as IdentityView, sn as SyncThreadView, tn as SyncFindView, tt as EventsPage, vn as UploadTicket, xt as MessagesPage, yt as Me, z as ProfileOption } from "./provider-B0b9Qxw4.mjs";
4
- import { i as Caller, n as BackendRoute, r as BoardBinding } from "./backend-B-SJ7p6p.mjs";
3
+ import { $ as Discovery, $t as SyncCodeView, Nt as RegistrationView, Tt as PersonView, Wt as SourceJoin, Z as ChannelView, _t as MatchView, bt as MessageView, ct as InboxEntry, fn as SyncedView, hn as ThreadsPage, ht as LinkedAccount, in as SyncFindView, lt as InboxItem, mn as ThreadView, ot as IdentityView, qt as SourceRead, tn as SyncCopiesView, tt as EventsPage, un as SyncThreadView, xn as UploadTicket, xt as MessagesPage, yt as Me, z as ProfileOption, zt as ScheduleView } from "./provider-D11Dfda0.mjs";
4
+ import { i as Caller, n as BackendRoute, r as BoardBinding } from "./backend-CWmd4UH-.mjs";
5
5
  //#region src/server/backend-http.d.ts
6
6
  type HttpOptions = {
7
7
  /**
@@ -194,7 +194,19 @@ declare function createHttp(url: string | undefined, options: HttpOptions, envir
194
194
  watchers: RegistrationView[];
195
195
  }>;
196
196
  register: (caller: Caller, input: {
197
+ watch: Record<string, {
198
+ tail: string;
199
+ when: "enters" | "matches";
200
+ }>;
201
+ sync?: {
202
+ key: string;
203
+ platform: string;
204
+ signIn: boolean;
205
+ }[] | undefined;
197
206
  url?: string | null | undefined;
207
+ path?: null | undefined;
208
+ branch?: null | undefined;
209
+ } | {
198
210
  watch: Record<string, {
199
211
  tail: string;
200
212
  when: "enters" | "matches";
@@ -204,6 +216,9 @@ declare function createHttp(url: string | undefined, options: HttpOptions, envir
204
216
  platform: string;
205
217
  signIn: boolean;
206
218
  }[] | undefined;
219
+ url?: null | undefined;
220
+ path: string;
221
+ branch?: string | null | undefined;
207
222
  }, signal: AbortSignal) => Promise<RegistrationView>;
208
223
  matches: (caller: Caller, input: {
209
224
  limit?: number | undefined;
@@ -245,6 +260,11 @@ declare function createHttp(url: string | undefined, options: HttpOptions, envir
245
260
  target: string;
246
261
  limit?: number | undefined;
247
262
  }, signal: AbortSignal) => Promise<SourceRead>;
263
+ joinSource: (caller: Caller, input: {
264
+ key: string;
265
+ } & {
266
+ target: string;
267
+ }, signal: AbortSignal) => Promise<SourceJoin>;
248
268
  synced: (caller: Caller, input: {
249
269
  id: string;
250
270
  }, signal: AbortSignal) => Promise<SyncedView>;
@@ -1,9 +1,9 @@
1
1
  import "./files-SniSmeEr.mjs";
2
2
  import "./schemas-Be08nyfW.mjs";
3
- import { K as routes, k as RegisterBody, n as compileWatcher, o as METHODS, t as canonicalWatchers } from "./watchers-hash-qlnbKQQ0.mjs";
4
- import { n as createBoardClient, t as BoardError } from "./client-BA-J7usH.mjs";
3
+ import { k as RegisterBody, n as compileWatcher, o as METHODS, q as routes, t as canonicalWatchers } from "./watchers-hash-DYwvkhdX.mjs";
4
+ import { n as createBoardClient, t as BoardError } from "./client-ClznIBMg.mjs";
5
5
  import { a as sha256, i as keyId, n as derivedId, t as DERIVED_ID } from "./ids-C6AYo1bu.mjs";
6
- import { a as forbidden, o as json, t as HttpError } from "./errors-DX3hpwWt.mjs";
6
+ import { a as forbidden, o as json, t as HttpError } from "./errors-CQ-fwxM3.mjs";
7
7
  //#region src/server/backend-http.ts
8
8
  /** A watcher's or client's name is the profile its writes are by. */
9
9
  const PROFILE = /^[A-Za-z][A-Za-z0-9-]{0,31}$/;
@@ -308,6 +308,10 @@ function createHttp(url, options, environment) {
308
308
  if ("self" in caller && bound().sync.some((entry) => entry.key === input.key)) return bound().readSource(input.key, input.target, input.limit ?? 50);
309
309
  return remoteCall("readSource", caller, input, signal);
310
310
  },
311
+ async joinSource(caller, input, signal) {
312
+ if ("self" in caller && bound().sync.some((entry) => entry.key === input.key)) return bound().joinSource(input.key, input.target);
313
+ return remoteCall("joinSource", caller, input, signal);
314
+ },
311
315
  async synced(caller, input, signal) {
312
316
  const { targets } = await remoteCall("synced", caller, input, signal);
313
317
  return { targets: await Promise.all(targets.map(async (row) => ({
@@ -350,17 +354,32 @@ function createHttp(url, options, environment) {
350
354
  };
351
355
  /** Only an https URL can be woken; anything else pulls on its own. */
352
356
  const wakeable = (here) => here?.startsWith("https://") ? here : null;
357
+ /**
358
+ * Where the remote wakes this board: on a board that wakes by path, the path on this project,
359
+ * which gets through its Deployment Protection, from production or from a preview of a branch,
360
+ * which it names; anywhere else (off Vercel, development, a custom environment), its URL.
361
+ */
362
+ async function wakeAt(here) {
363
+ const env = typeof process !== "undefined" && process.env.VERCEL === "1" ? process.env : null;
364
+ const environment = env?.VERCEL_TARGET_ENV || env?.VERCEL_ENV;
365
+ const branch = environment === "preview" ? env?.VERCEL_GIT_COMMIT_REF || null : null;
366
+ if (!here || environment !== "production" && !branch || !(await discovery()).wake_by_path) return { url: here };
367
+ return {
368
+ path: new URL(here).pathname,
369
+ ...branch ? { branch } : {}
370
+ };
371
+ }
353
372
  let last = null;
354
373
  let registering = null;
355
374
  /** Registers this board's watchers with the remote, as itself, replacing its last registration. */
356
375
  async function register(here) {
357
376
  const current = await watchersHash();
358
377
  if (last && last.url === here && last.result.hash === current && Date.now() - last.at < REGISTER_EVERY_MS) return last.result;
359
- registering ??= own.register({
360
- url: here,
378
+ registering ??= wakeAt(here).then((where) => own.register({
379
+ ...where,
361
380
  watch: specs,
362
381
  ...sync.length > 0 ? { sync } : {}
363
- }).then((result) => {
382
+ })).then((result) => {
364
383
  last = {
365
384
  url: here,
366
385
  at: Date.now(),
@@ -1,5 +1,5 @@
1
- import { t as BoardError } from "./client-BA-J7usH.mjs";
2
- import { t as createHttp } from "./backend-http-CDDS7MLI.mjs";
1
+ import { t as BoardError } from "./client-ClznIBMg.mjs";
2
+ import { t as createHttp } from "./backend-http-CG5y03UP.mjs";
3
3
  import { execFile } from "node:child_process";
4
4
  import { createHash } from "node:crypto";
5
5
  import { mkdir, readFile, writeFile } from "node:fs/promises";
@@ -120,7 +120,15 @@ const expiry = (token) => {
120
120
  return Date.now() + 3e5;
121
121
  }
122
122
  };
123
- const cacheFile = (audience) => join(configDir(), "tokens", `${createHash("sha256").update(audience).digest("hex").slice(0, 32)}.json`);
123
+ /**
124
+ * Where `audience`'s token is cached: one per board, and with a login the Vercel CLI hands over
125
+ * (`VERCEL_AUTH_TOKEN`), one per login too, since it can be someone else's than `vercel login`'s.
126
+ */
127
+ const cacheFile = (audience) => {
128
+ const login = process.env.VERCEL_AUTH_TOKEN;
129
+ const key = login ? `${audience}\n${login}` : audience;
130
+ return join(configDir(), "tokens", `${createHash("sha256").update(key).digest("hex").slice(0, 32)}.json`);
131
+ };
124
132
  async function readCache(audience) {
125
133
  try {
126
134
  const cached = JSON.parse(await readFile(cacheFile(audience), "utf8"));
@@ -137,6 +145,11 @@ async function writeCache(audience, cached) {
137
145
  await writeFile(cacheFile(audience), JSON.stringify(cached), { mode: 384 });
138
146
  }
139
147
  /**
148
+ * Your Vercel login: what the Vercel CLI hands a CLI it runs, as `vercel board` (`VERCEL_AUTH_TOKEN`,
149
+ * from its `--token` or its own login), or else what `vercel login` saved.
150
+ */
151
+ const vercelLogin = () => process.env.VERCEL_AUTH_TOKEN ? Promise.resolve(process.env.VERCEL_AUTH_TOKEN) : getVercelToken();
152
+ /**
140
153
  * A person's token: their Vercel CLI login mints a development token for the
141
154
  * project the board's host is a deployment of, exchanged so its audience is the
142
155
  * board's URL. Cached on disk for its 12-hour life.
@@ -144,7 +157,7 @@ async function writeCache(audience, cached) {
144
157
  async function personToken(audience, discovery) {
145
158
  const cached = await readCache(audience);
146
159
  if (cached && cached.name === void 0) {
147
- const user = await getVercelToken().then(vercelUser).catch(() => null);
160
+ const user = await vercelLogin().then(vercelUser).catch(() => null);
148
161
  const filled = {
149
162
  ...cached,
150
163
  name: user?.name ?? null,
@@ -155,7 +168,7 @@ async function personToken(audience, discovery) {
155
168
  }
156
169
  if (cached) return cached;
157
170
  if (!discovery.project_id) throw new BoardError("no_project", `the board at ${discovery.url} doesn't know its Vercel project`, 0);
158
- const authToken = await getVercelToken().catch(() => null);
171
+ const authToken = await vercelLogin().catch(() => null);
159
172
  const project = await boardProject(audience, discovery, authToken);
160
173
  let development;
161
174
  try {
@@ -517,7 +530,7 @@ async function projectBypass(url, host, doFetch) {
517
530
  const blocked = (why) => new BoardError("protected", `${url} is behind Vercel Deployment Protection, and your Vercel login ${why}, or set VERCEL_AUTOMATION_BYPASS_SECRET (${WHERE})`, 0);
518
531
  let api;
519
532
  try {
520
- const token = await getVercelToken();
533
+ const token = await vercelLogin();
521
534
  api = async (method, path) => {
522
535
  const response = await doFetch(`https://api.vercel.com${path}`, {
523
536
  method,
@@ -1,4 +1,4 @@
1
- import { n as HttpOptions, t as HttpBackend } from "./backend-http-7cNb8nJI.mjs";
1
+ import { n as HttpOptions, t as HttpBackend } from "./backend-http-BfWQfvqB.mjs";
2
2
  //#region src/backend-http/index.default.d.ts
3
3
  /**
4
4
  * The board at `url`, which keeps the log. Outside Node there's no environment to find it or a
@@ -1,2 +1,2 @@
1
- import { t as http } from "./index.default-jkA7eFFe.mjs";
1
+ import { t as http } from "./index.default-C3luVnd-.mjs";
2
2
  export { http };
@@ -1,4 +1,4 @@
1
- import { n as HttpOptions, t as HttpBackend } from "./backend-http-7cNb8nJI.mjs";
1
+ import { n as HttpOptions, t as HttpBackend } from "./backend-http-BfWQfvqB.mjs";
2
2
  //#region src/backend-http/index.d.ts
3
3
  /**
4
4
  * The board at `url`, which keeps the log: this board uses it as this deployment. Without a
@@ -1,2 +1,2 @@
1
- import { t as http } from "./backend-http-BjH8jMDi.mjs";
1
+ import { t as http } from "./backend-http-c-VHpUOs.mjs";
2
2
  export { http };
@@ -1,5 +1,5 @@
1
1
  import { d as ExternalFile, f as ExternalLink, l as ExternalAuthor, t as Actor } from "./folds-CRYY14kc.mjs";
2
- import { M as SyncRoot, O as ProviderEvent, Wt as SourceRead, a as ProviderOptions, c as ReceivedEvent, d as ResolvedTarget, i as ProviderFile, n as FileDelivery, o as ProviderPost, p as SyncProvider, r as ProviderDelivery, s as Receive, t as FetchedLink, y as SyncFn } from "./provider-B0b9Qxw4.mjs";
2
+ import { M as SyncRoot, O as ProviderEvent, Wt as SourceJoin, a as ProviderOptions, c as ReceivedEvent, d as ResolvedTarget, i as ProviderFile, n as FileDelivery, o as ProviderPost, p as SyncProvider, qt as SourceRead, r as ProviderDelivery, s as Receive, t as FetchedLink, y as SyncFn } from "./provider-D11Dfda0.mjs";
3
3
  import { Adapter, Author, FileUpload, Message, ReactionEvent } from "chat";
4
4
  //#region src/sync/types.d.ts
5
5
  declare function externalAuthor(platform: string, author: Author): ExternalAuthor;
@@ -45,6 +45,8 @@ type ChatTransport = Omit<ProviderOptions, 'uploadsFiles'> & {
45
45
  read?(target: string, options: {
46
46
  limit: number;
47
47
  }): Promise<SourceRead>;
48
+ /** Adds the app to a channel so `read` can read it. */
49
+ join?(target: string): Promise<SourceJoin>;
48
50
  avatar?(message: Message): Promise<string | null>;
49
51
  fetchRoot?(thread: string): Promise<Omit<SyncRoot, 'raw'> & {
50
52
  raw?: unknown;
@@ -124,6 +126,7 @@ declare abstract class ChatSyncProvider<E extends ProviderEvent = ProviderEvent>
124
126
  read(target: string, options: {
125
127
  limit: number;
126
128
  }): Promise<SourceRead>;
129
+ join(target: string): Promise<SourceJoin>;
127
130
  fetchFile(file: ExternalFile): Promise<Uint8Array | null>;
128
131
  post(target: string, kind: ResolvedTarget['kind'], { as: _sender, files, ...body }: ProviderPost): Promise<ProviderDelivery>;
129
132
  whoAmI(token: string): Promise<{
@@ -1,5 +1,5 @@
1
1
  import "./external-BV_cUncs.mjs";
2
- import { n as ResolvedTarget, r as SyncProvider } from "./provider-B3DpYSDq.mjs";
2
+ import { n as ResolvedTarget, r as SyncProvider } from "./provider-poIkXapd.mjs";
3
3
  import { n as userTokensOf } from "./users-yryHKFMo.mjs";
4
4
  import { AsyncLocalStorage } from "node:async_hooks";
5
5
  import { Chat, stringifyMarkdown } from "chat";
@@ -312,6 +312,9 @@ var ChatSyncProvider = class extends SyncProvider {
312
312
  async read(target, options) {
313
313
  return this.transport.read ? this.transport.read(target, options) : super.read(target, options);
314
314
  }
315
+ async join(target) {
316
+ return this.transport.join ? this.transport.join(target) : super.join(target);
317
+ }
315
318
  async fetchFile(file) {
316
319
  const fetch = (await this.ready()).rehydrateAttachment?.({ ...this.transport.fileToFetch?.(file) ?? file }).fetchData;
317
320
  if (!fetch) return null;
@@ -0,0 +1,47 @@
1
+ import "./provider-D11Dfda0.mjs";
2
+ import { d as Board } from "./backend-CWmd4UH-.mjs";
3
+ import "./index-DsMPCv_K.mjs";
4
+ //#region src/cli/main.d.ts
5
+ /** The command word: the first argument that isn't a flag or a string flag's value. */
6
+ export declare function commandWord(args: string[]): string | undefined;
7
+ //#endregion
8
+ //#region src/cli/embedded.d.ts
9
+ /**
10
+ * Board's CLI as `appName` names it in help, usage, and errors (`vercel board`). An agent's hook
11
+ * runs it the same way, by that name. `run` takes what follows the name, and answers the exit code:
12
+ * 0 success, 1 error, 2 misuse, 124 when `wait` times out. It never exits the process itself. One `run`
13
+ * at a time: a run's flags are the CLI's while it runs.
14
+ */
15
+ export declare function createApp(options: {
16
+ appName: string;
17
+ }): {
18
+ run(args: string[]): Promise<number>;
19
+ };
20
+ /** Where a `vb()` command's files come from and go, and what stops it. A command that needs one it lacks says so. */
21
+ export type VbOptions = {
22
+ /** A file's bytes, for `--attach`; null when there's no such file. */
23
+ readFile?: (path: string) => Promise<Uint8Array | null> | PromiseLike<Uint8Array | null>;
24
+ /** Saves a file, for `download`. */
25
+ writeFile?: (path: string, data: Uint8Array) => Promise<void> | PromiseLike<void>;
26
+ /** The text `-` reads, as stdin would hold it. */
27
+ stdin?: string;
28
+ /** Stops `tail` and `wait`, as Ctrl-C does. */
29
+ signal?: AbortSignal;
30
+ /** Commands this caller doesn't run, which answer 2: `['tail', 'wait']` for an agent's tool. */
31
+ refuse?: string[];
32
+ };
33
+ /** What a `vb()` command printed, and its exit code: 0, 1 for an error, 2 for misuse, 124 when `wait` timed out. */
34
+ export type VbResult = {
35
+ code: number;
36
+ stdout: string;
37
+ stderr: string;
38
+ };
39
+ /**
40
+ * Runs the vb command `argv` (`['read', '42']`, `['reply', '42', 'on it']`) on `board`, in this
41
+ * process, and returns what the CLI would print. `board` already says which board and who, so
42
+ * `--url` and `--handle` don't apply, and the commands about your machine (`link`, `use`, `whoami`,
43
+ * `identity`, `hooks`, `serve`, `inspect`) answer 2. Calls run one at a time, in order, so
44
+ * `tail` and `wait`, which hold the rest until they end, need `options.signal`.
45
+ */
46
+ export declare function vb(board: Board, argv: string[], options?: VbOptions): Promise<VbResult>;
47
+ //#endregion
@@ -0,0 +1,64 @@
1
+ import { n as main, r as mainWith, t as commandWord } from "./main-wB-VfTCL.mjs";
2
+ //#region src/cli/embedded.ts
3
+ /**
4
+ * Board's CLI as `appName` names it in help, usage, and errors (`vercel board`). An agent's hook
5
+ * runs it the same way, by that name. `run` takes what follows the name, and answers the exit code:
6
+ * 0 success, 1 error, 2 misuse, 124 when `wait` times out. It never exits the process itself. One `run`
7
+ * at a time: a run's flags are the CLI's while it runs.
8
+ */
9
+ function createApp(options) {
10
+ return { run: (args) => main(args, {
11
+ name: options.appName,
12
+ command: options.appName,
13
+ commands: {}
14
+ }) };
15
+ }
16
+ const lacking = (what) => async () => {
17
+ throw new Error(`${what}: pass it in vb()'s options`);
18
+ };
19
+ let queue = Promise.resolve();
20
+ /**
21
+ * Runs the vb command `argv` (`['read', '42']`, `['reply', '42', 'on it']`) on `board`, in this
22
+ * process, and returns what the CLI would print. `board` already says which board and who, so
23
+ * `--url` and `--handle` don't apply, and the commands about your machine (`link`, `use`, `whoami`,
24
+ * `identity`, `hooks`, `serve`, `inspect`) answer 2. Calls run one at a time, in order, so
25
+ * `tail` and `wait`, which hold the rest until they end, need `options.signal`.
26
+ */
27
+ function vb(board, argv, options = {}) {
28
+ const word = commandWord(argv);
29
+ if (word !== void 0 && options.refuse?.includes(word)) return Promise.resolve({
30
+ code: 2,
31
+ stdout: "",
32
+ stderr: `error: ${word} isn't available here\n`
33
+ });
34
+ if ((word === "tail" || word === "wait") && !options.signal) return Promise.resolve({
35
+ code: 2,
36
+ stdout: "",
37
+ stderr: `error: ${word} runs until it's stopped, and vb() runs one call at a time: pass a signal\n`
38
+ });
39
+ const result = queue.then(async () => {
40
+ let stdout = "";
41
+ let stderr = "";
42
+ const decoder = new TextDecoder();
43
+ return {
44
+ code: await mainWith(argv, {
45
+ name: "board",
46
+ commands: {}
47
+ }, {
48
+ board,
49
+ write: (chunk) => void (stdout += typeof chunk === "string" ? chunk : decoder.decode(chunk, { stream: true })),
50
+ error: (chunk) => void (stderr += chunk),
51
+ readFile: options.readFile ? async (path) => options.readFile(path) : lacking("--attach reads files"),
52
+ writeFile: options.writeFile ? async (path, data) => options.writeFile(path, data) : lacking("download saves files"),
53
+ stdin: options.stdin !== void 0 ? async () => options.stdin : lacking("- reads stdin"),
54
+ ...options.signal ? { signal: options.signal } : {}
55
+ }),
56
+ stdout,
57
+ stderr
58
+ };
59
+ });
60
+ queue = result.catch(() => {});
61
+ return result;
62
+ }
63
+ //#endregion
64
+ export { commandWord, createApp, vb };