@vercel/board 0.20.0 → 0.21.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 (31) hide show
  1. package/README.md +0 -25
  2. package/dist/{a2-D__qQpe7.mjs → a2-BgNAyr_f.mjs} +1 -1
  3. package/dist/backend-a2.mjs +1 -1
  4. package/dist/{backend-http-CG5y03UP.mjs → backend-http-CLcXkcAU.mjs} +190 -140
  5. package/dist/backend-http-Dzb4lpMG.mjs +18 -0
  6. package/dist/backend-http.mjs +1 -1
  7. package/dist/backend-http.node.mjs +1 -1
  8. package/dist/backend-vercel-BjUpoLR9.mjs +60 -0
  9. package/dist/backend-vercel-CYOqtLPc.d.mts +656 -0
  10. package/dist/backend-vercel-DjL8tYIF.mjs +1314 -0
  11. package/dist/backend-vercel.d.mts +9 -0
  12. package/dist/backend-vercel.mjs +11 -0
  13. package/dist/backend-vercel.node.d.mts +12 -0
  14. package/dist/backend-vercel.node.mjs +2 -0
  15. package/dist/cli-embedded.d.mts +5 -5
  16. package/dist/cli-embedded.mjs +6 -5
  17. package/dist/cli.mjs +3 -3
  18. package/dist/{exports-DqWJ7nzC.mjs → exports-DIivm0je.mjs} +1 -1
  19. package/dist/{index.default-C3luVnd-.mjs → index.default-DNKzR4md.mjs} +1 -1
  20. package/dist/index.mjs +2 -2
  21. package/dist/index.node.mjs +1 -1
  22. package/dist/inspect/app.js +29 -29
  23. package/dist/{main-wB-VfTCL.mjs → main-BZ7nOIhV.mjs} +75 -508
  24. package/dist/{backend-http-c-VHpUOs.mjs → node-BeKPcokJ.mjs} +15 -17
  25. package/dist/{serve-AnApUWOR.mjs → serve-8zkBGMmb.mjs} +3 -3
  26. package/dist/{server-BqVickb5.mjs → server-Du0rSpcW.mjs} +2 -2
  27. package/dist/{watchers-hash-DYwvkhdX.mjs → watchers-hash-9sxfL1Gd.mjs} +1 -1
  28. package/docs/03-guides/01-coding-agents.md +0 -94
  29. package/docs/04-reference/01-cli.md +26 -23
  30. package/docs/index.md +1 -2
  31. package/package.json +6 -1
package/README.md CHANGED
@@ -89,31 +89,6 @@ or WhatsApp. Automatic wakeups and subscriptions aren't part of this skill.
89
89
  The skill installation is global per user with `-g`; omit it to install in
90
90
  the project.
91
91
 
92
- ### Keep the board updated after each turn
93
-
94
- ```bash
95
- npm i -g @vercel/board # the hook runs `vb` on every turn end: a global install starts fastest
96
- vb hooks install # Claude Code, Codex, Cursor, and Pi; or name them: vb hooks install pi
97
- ```
98
-
99
- This adds a turn-end hook to each agent. When a turn ends without the agent
100
- writing to the board, the hook reminds it once to share a 1–3 sentence
101
- summary: `vb reply` in a thread it's working in, or `vb post` in the channel
102
- that fits. With nothing to share, it just finishes. The hook never posts: the
103
- agent uses the CLI, so it can read the board too. Install allows the agent to
104
- run those commands without a prompt (Claude Code, Cursor). Codex's default
105
- sandbox has no network, so install prints a permission profile that allows
106
- just the board.
107
-
108
- Updates go to one thread per person, repo, and branch in the `work` channel
109
- (`--channel` picks another), so a task started in Codex and continued in
110
- Claude Code reads as one thread. They stay on the board: they don't sync to
111
- Slack or GitHub. The hook acts only in linked projects (or with `BOARD_URL`
112
- set) and never fails a turn. Codex asks you to approve the hook once in
113
- `/hooks`; Cursor runs hooks in interactive sessions only, not with
114
- `cursor-agent -p`. Pi gets an extension (`~/.pi/agent/extensions/board.ts`,
115
- or under `PI_CODING_AGENT_DIR`) that runs in every mode, `pi -p` included. `vb hooks status` and `vb hooks uninstall` manage it.
116
-
117
92
  The design lives in [SPEC.md](../../SPEC.md). Status: the board, its handle, the CLI,
118
93
  inspector, Slack, GitHub, and WhatsApp (Kapso) sync, and watchers work;
119
94
  cursors are next.
@@ -1,7 +1,7 @@
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-DYwvkhdX.mjs";
4
+ import { f as VERSION, n as compileWatcher, o as METHODS, p as SYNC, r as hasCriteria, t as canonicalWatchers } from "./watchers-hash-9sxfL1Gd.mjs";
5
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
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";
@@ -1,2 +1,2 @@
1
- import { n as fsBlob, r as memoryBlob, t as a2 } from "./a2-D__qQpe7.mjs";
1
+ import { n as fsBlob, r as memoryBlob, t as a2 } from "./a2-BgNAyr_f.mjs";
2
2
  export { a2, fsBlob, memoryBlob };
@@ -1,9 +1,172 @@
1
1
  import "./files-SniSmeEr.mjs";
2
2
  import "./schemas-Be08nyfW.mjs";
3
- import { k as RegisterBody, n as compileWatcher, o as METHODS, q as routes, t as canonicalWatchers } from "./watchers-hash-DYwvkhdX.mjs";
3
+ import { k as RegisterBody, n as compileWatcher, o as METHODS, q as routes, t as canonicalWatchers } from "./watchers-hash-9sxfL1Gd.mjs";
4
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
6
  import { a as forbidden, o as json, t as HttpError } from "./errors-CQ-fwxM3.mjs";
7
+ //#region src/server/registrant.ts
8
+ /** Registering again this soon after the same registration only repeats it. */
9
+ const REGISTER_EVERY_MS = 1e4;
10
+ /** How long one wake-up runs matches before it leaves the rest to the next. */
11
+ const DRAIN_MS = 24e4;
12
+ /** A pull with nothing to hand out holds this long, when the board pulls on its own. */
13
+ const POLL_WAIT_S = 25;
14
+ const describe$1 = (error) => ((error instanceof Error ? error.message : String(error)) || "unknown error").slice(0, 500);
15
+ /** Only an https URL can be woken; anything else pulls on its own. */
16
+ const wakeable = (here) => here?.startsWith("https://") ? here : null;
17
+ /**
18
+ * A path on this Vercel project for a board that wakes by path, from `here`, this board's URL:
19
+ * from production, or from a preview of a branch, which it names. Null anywhere else (off Vercel,
20
+ * development, a custom environment), where only a URL wakes it.
21
+ */
22
+ function wakePath(here) {
23
+ const env = typeof process !== "undefined" && process.env.VERCEL === "1" ? process.env : null;
24
+ const environment = env?.VERCEL_TARGET_ENV || env?.VERCEL_ENV;
25
+ const branch = environment === "preview" ? env?.VERCEL_GIT_COMMIT_REF || null : null;
26
+ if (!here || environment !== "production" && !branch) return null;
27
+ return {
28
+ path: new URL(here).pathname,
29
+ ...branch ? { branch } : {}
30
+ };
31
+ }
32
+ /**
33
+ * Runs a board's watchers on another board that matches them: it registers them there, is woken
34
+ * when one matches, with nothing in the wake-up but a hash, and pulls what matched with its own
35
+ * credentials, runs it, and acks it. When it can't be woken (no https URL of its own: a dev
36
+ * server, say), it pulls on its own. http() and vercel() run their watchers this way.
37
+ */
38
+ function registrant(options) {
39
+ const { registry, rethrow } = options;
40
+ const bound = options.binding;
41
+ let last = null;
42
+ let registering = null;
43
+ /** Registers this board's watchers with the other board, as itself, replacing its last registration. */
44
+ async function register(here) {
45
+ const current = await options.hash();
46
+ if (last && last.url === here && last.result.hash === current && Date.now() - last.at < REGISTER_EVERY_MS) return last.result;
47
+ registering ??= options.wakeAt(here).then((where) => registry.register(where)).then((result) => {
48
+ last = {
49
+ url: here,
50
+ at: Date.now(),
51
+ result
52
+ };
53
+ return result;
54
+ }).finally(() => {
55
+ registering = null;
56
+ });
57
+ return registering;
58
+ }
59
+ /** Runs one match and acks it: done, its error, or a skip for a watcher this board no longer has. */
60
+ async function runOne(match) {
61
+ let outcome = {};
62
+ if (!bound().subscriptions[match.watcher]) outcome = { skip: true };
63
+ else try {
64
+ await bound().run(match.watcher, match.event, {
65
+ trigger: `watch:${match.watcher}:${match.id}`,
66
+ attempt: match.attempt + 1
67
+ });
68
+ } catch (error) {
69
+ console.error(`[board] watcher ${match.watcher} failed:`, error);
70
+ outcome = { error: describe$1(error) };
71
+ }
72
+ await registry.ack(match.id, {
73
+ lease: match.lease,
74
+ ...outcome
75
+ }).catch((error) => console.error(`[board] couldn't ack a match:`, describe$1(error)));
76
+ }
77
+ /**
78
+ * Pulls and runs what's matched until nothing's left, or this run's time is up, and what sync
79
+ * queued in its inbox. A thread's matches come one at a time.
80
+ */
81
+ async function drain() {
82
+ let ran = 0;
83
+ const until = Date.now() + DRAIN_MS;
84
+ while (Date.now() < until) {
85
+ const matches = await registry.matches({ limit: 10 });
86
+ if (matches.length === 0) break;
87
+ await Promise.all(matches.map(runOne));
88
+ ran += matches.length;
89
+ }
90
+ return ran + await bound().pull();
91
+ }
92
+ let polling = null;
93
+ /** For a board that can't be woken: pulls on its own, each pull held open until something matches. */
94
+ function poll() {
95
+ if (polling) return;
96
+ const controller = polling = new AbortController();
97
+ (async () => {
98
+ for (let pause = 1e3; !controller.signal.aborted;) try {
99
+ await register(null);
100
+ const matches = await registry.matches({
101
+ limit: 10,
102
+ wait: POLL_WAIT_S,
103
+ signal: controller.signal
104
+ });
105
+ await Promise.all(matches.map(runOne));
106
+ await bound().pull();
107
+ pause = 1e3;
108
+ } catch (error) {
109
+ if (controller.signal.aborted) break;
110
+ console.error(`[board] couldn't pull matches from ${options.remote}:`, describe$1(error));
111
+ await new Promise((resolve) => setTimeout(resolve, pause));
112
+ pause = Math.min(pause * 2, 6e4);
113
+ }
114
+ })();
115
+ }
116
+ let started = null;
117
+ let failedAt = 0;
118
+ /**
119
+ * On first use, a board with watchers or sync registers them if it knows its URL. One on Vercel
120
+ * learns its mount path from a request (a `POST /watchers`, a webhook, or a wake-up); one that
121
+ * can't be woken registers without one and pulls instead. Resolves once the first try is done.
122
+ * One that fails tries again on a later use, at most every `REGISTER_EVERY_MS`: nothing would
123
+ * wake a board the other doesn't know.
124
+ */
125
+ function start() {
126
+ if (started) return started;
127
+ if (!options.hasWork() || Date.now() - failedAt < REGISTER_EVERY_MS) return Promise.resolve();
128
+ const here = wakeable(bound().url());
129
+ if (!here && typeof process !== "undefined" && process.env.VERCEL === "1") return Promise.resolve();
130
+ started = register(here).then(() => {}, (error) => {
131
+ started = null;
132
+ failedAt = Date.now();
133
+ console.error(`[board] couldn't register with ${options.remote}:`, describe$1(error));
134
+ });
135
+ if (!here) poll();
136
+ return started;
137
+ }
138
+ return {
139
+ start,
140
+ routes: [{
141
+ method: "POST",
142
+ path: "/wake",
143
+ async handle(request) {
144
+ started ??= Promise.resolve();
145
+ if (request.headers.get("x-board-watchers") !== await options.hash()) await register(wakeable(bound().url())).catch(rethrow);
146
+ return json({ ran: await drain().catch(rethrow) });
147
+ }
148
+ }, {
149
+ method: "POST",
150
+ path: "/watchers",
151
+ async handle(request, { caller }) {
152
+ if (caller) return options.registerFor(caller, request);
153
+ started ??= Promise.resolve();
154
+ const here = wakeable(bound().url());
155
+ const registration = await register(here).catch(rethrow);
156
+ if (!here) poll();
157
+ return json(registration);
158
+ }
159
+ }],
160
+ /** Registers this board's watchers with the other board now, as `POST /watchers` does. */
161
+ registerNow: () => register(wakeable(bound().url())),
162
+ /** Stops pulling, for a board that pulls on its own. */
163
+ close: () => {
164
+ polling?.abort();
165
+ polling = null;
166
+ }
167
+ };
168
+ }
169
+ //#endregion
7
170
  //#region src/server/backend-http.ts
8
171
  /** A watcher's or client's name is the profile its writes are by. */
9
172
  const PROFILE = /^[A-Za-z][A-Za-z0-9-]{0,31}$/;
@@ -73,12 +236,6 @@ function scopeOf(identity) {
73
236
  const entries = Object.entries(access.summary()).map(([pattern, permissions]) => `${pattern}=${permissions.join(",")}`);
74
237
  return entries.length > 0 ? entries.join(";") : "*=";
75
238
  }
76
- /** Registering again this soon after the same registration only repeats it. */
77
- const REGISTER_EVERY_MS = 1e4;
78
- /** How long one wake-up runs matches before it leaves the rest to the next. */
79
- const DRAIN_MS = 24e4;
80
- /** A pull with nothing to hand out holds this long, when the board pulls on its own. */
81
- const POLL_WAIT_S = 25;
82
239
  const describe = (error) => ((error instanceof Error ? error.message : String(error)) || "unknown error").slice(0, 500);
83
240
  /** The SDK's error, as the board answers it: the remote's own status and code, or 502 when it never answered. */
84
241
  const rethrow = (error) => {
@@ -352,140 +509,36 @@ function createHttp(url, options, environment) {
352
509
  return { targets: [...targets, ...here] };
353
510
  }
354
511
  };
355
- /** Only an https URL can be woken; anything else pulls on its own. */
356
- const wakeable = (here) => here?.startsWith("https://") ? here : null;
357
512
  /**
358
513
  * 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.
514
+ * which gets through its Deployment Protection (`wakePath`); anywhere else, its URL.
361
515
  */
362
516
  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
- }
372
- let last = null;
373
- let registering = null;
374
- /** Registers this board's watchers with the remote, as itself, replacing its last registration. */
375
- async function register(here) {
376
- const current = await watchersHash();
377
- if (last && last.url === here && last.result.hash === current && Date.now() - last.at < REGISTER_EVERY_MS) return last.result;
378
- registering ??= wakeAt(here).then((where) => own.register({
379
- ...where,
380
- watch: specs,
381
- ...sync.length > 0 ? { sync } : {}
382
- })).then((result) => {
383
- last = {
384
- url: here,
385
- at: Date.now(),
386
- result
387
- };
388
- return result;
389
- }).finally(() => {
390
- registering = null;
391
- });
392
- return registering;
393
- }
394
- /** Runs one match and acks it: done, its error, or a skip for a watcher this board no longer has. */
395
- async function runOne(match) {
396
- let outcome = {};
397
- if (!bound().subscriptions[match.watcher]) outcome = { skip: true };
398
- else try {
399
- await bound().run(match.watcher, match.event, {
400
- trigger: `watch:${match.watcher}:${match.id}`,
401
- attempt: match.attempt + 1
402
- });
403
- } catch (error) {
404
- console.error(`[board] watcher ${match.watcher} failed:`, error);
405
- outcome = { error: describe(error) };
406
- }
407
- await own.ack(match.id, {
408
- lease: match.lease,
409
- ...outcome
410
- }).catch((error) => console.error(`[board] couldn't ack a match:`, describe(error)));
411
- }
412
- /**
413
- * Pulls and runs what's matched until nothing's left, or this run's time is up, and what sync
414
- * queued in its inbox. A thread's matches come one at a time.
415
- */
416
- async function drain() {
417
- let ran = 0;
418
- const until = Date.now() + DRAIN_MS;
419
- while (Date.now() < until) {
420
- const matches = await own.matches({ limit: 10 });
421
- if (matches.length === 0) break;
422
- await Promise.all(matches.map(runOne));
423
- ran += matches.length;
424
- }
425
- return ran + await bound().pull();
426
- }
427
- let polling = null;
428
- /** For a board that can't be woken: pulls on its own, each pull held open until something matches. */
429
- function poll() {
430
- if (polling) return;
431
- const controller = polling = new AbortController();
432
- (async () => {
433
- for (let pause = 1e3; !controller.signal.aborted;) try {
434
- await register(null);
435
- const matches = await own.matches({
436
- limit: 10,
437
- wait: POLL_WAIT_S,
438
- signal: controller.signal
439
- });
440
- await Promise.all(matches.map(runOne));
441
- await bound().pull();
442
- pause = 1e3;
443
- } catch (error) {
444
- if (controller.signal.aborted) break;
445
- console.error(`[board] couldn't pull matches from ${remote}:`, describe(error));
446
- await new Promise((resolve) => setTimeout(resolve, pause));
447
- pause = Math.min(pause * 2, 6e4);
448
- }
449
- })();
517
+ const path = wakePath(here);
518
+ return path && (await discovery()).wake_by_path ? path : { url: here };
450
519
  }
451
- let started = null;
452
- /**
453
- * On first use, a board with watchers or sync registers them if it knows its URL. One on Vercel
454
- * learns its mount path from a request (a `POST /watchers`, a webhook, or a wake-up); one that
455
- * can't be woken registers without one and pulls instead. Resolves once the first try is done.
456
- */
457
- function start() {
458
- if (started) return started;
459
- if (Object.keys(specs).length === 0 && sync.length === 0) return Promise.resolve();
460
- const here = wakeable(bound().url());
461
- if (!here && typeof process !== "undefined" && process.env.VERCEL === "1") return Promise.resolve();
462
- started = register(here).then(() => {}, (error) => console.error(`[board] couldn't register with ${remote}:`, describe(error)));
463
- if (!here) poll();
464
- return started;
465
- }
466
- const routesHere = [{
467
- method: "POST",
468
- path: "/wake",
469
- async handle(request) {
470
- started ??= Promise.resolve();
471
- if (request.headers.get("x-board-watchers") !== await watchersHash()) await register(wakeable(bound().url())).catch(rethrow);
472
- return json({ ran: await drain().catch(rethrow) });
473
- }
474
- }, {
475
- method: "POST",
476
- path: "/watchers",
477
- async handle(request, { caller }) {
478
- if (caller) return json(await methods.register(caller, RegisterBody.parse(await request.json()), request.signal));
479
- started ??= Promise.resolve();
480
- const here = wakeable(bound().url());
481
- const registration = await register(here).catch(rethrow);
482
- if (!here) poll();
483
- return json(registration);
484
- }
485
- }];
520
+ const runner = registrant({
521
+ remote,
522
+ registry: {
523
+ register: (where) => own.register({
524
+ ...where,
525
+ watch: specs,
526
+ ...sync.length > 0 ? { sync } : {}
527
+ }),
528
+ matches: (query) => own.matches(query),
529
+ ack: (id, body) => own.ack(id, body)
530
+ },
531
+ wakeAt,
532
+ binding: bound,
533
+ hash: watchersHash,
534
+ hasWork: () => Object.keys(specs).length > 0 || sync.length > 0,
535
+ rethrow,
536
+ registerFor: async (caller, request) => json(await methods.register(caller, RegisterBody.parse(await request.json()), request.signal))
537
+ });
538
+ const start = runner.start;
486
539
  return {
487
540
  ...methods,
488
- routes: routesHere,
541
+ routes: runner.routes,
489
542
  keeps: false,
490
543
  bind(next) {
491
544
  if (binding) throw new Error("http: this backend already serves a board: create one http() per board()");
@@ -505,13 +558,10 @@ function createHttp(url, options, environment) {
505
558
  binding = next;
506
559
  },
507
560
  /** Registers this board's watchers with the board it uses now, as `POST /watchers` does. */
508
- registerWatchers: () => register(wakeable(bound().url())),
561
+ registerWatchers: runner.registerNow,
509
562
  /** Stops pulling, for a board that pulls on its own. */
510
- close: () => {
511
- polling?.abort();
512
- polling = null;
513
- }
563
+ close: runner.close
514
564
  };
515
565
  }
516
566
  //#endregion
517
- export { createHttp as t };
567
+ export { wakePath as a, registrant as i, callerProfile as n, createHttp as r, PROJECTS_OWN as t };
@@ -0,0 +1,18 @@
1
+ import { r as createHttp } from "./backend-http-CLcXkcAU.mjs";
2
+ import { i as environmentProfile, r as connect, t as boardUrl } from "./node-BeKPcokJ.mjs";
3
+ //#region src/backend-http/index.ts
4
+ const environment = {
5
+ url: boardUrl,
6
+ fetch: (url) => connect(url).fetch,
7
+ profile: environmentProfile
8
+ };
9
+ /**
10
+ * The board at `url`, which keeps the log: this board uses it as this deployment. Without a
11
+ * `url`, the one the environment points at: `BOARD_URL`, the repo's link, or `vb use`'s.
12
+ * Without a `fetch`, it sends the credentials `vb` would.
13
+ */
14
+ function http(url, options = {}) {
15
+ return createHttp(url, options, environment);
16
+ }
17
+ //#endregion
18
+ export { http as t };
@@ -1,2 +1,2 @@
1
- import { t as http } from "./index.default-C3luVnd-.mjs";
1
+ import { t as http } from "./index.default-DNKzR4md.mjs";
2
2
  export { http };
@@ -1,2 +1,2 @@
1
- import { t as http } from "./backend-http-c-VHpUOs.mjs";
1
+ import { t as http } from "./backend-http-Dzb4lpMG.mjs";
2
2
  export { http };
@@ -0,0 +1,60 @@
1
+ import { c as vercelLogin, i as environmentProfile, l as vercelProjectFile } from "./node-BeKPcokJ.mjs";
2
+ import { t as createVercel } from "./backend-vercel-DjL8tYIF.mjs";
3
+ import { readFile } from "node:fs/promises";
4
+ import { decodeJwt } from "jose";
5
+ import { getVercelOidcToken } from "@vercel/oidc";
6
+ //#region src/backend-vercel/index.ts
7
+ /** On Vercel, or with its OIDC token pulled (`vercel env pull`): a deployment acts as its project. */
8
+ const deployed = () => process.env.VERCEL === "1" || !!process.env.VERCEL_OIDC_TOKEN;
9
+ /**
10
+ * A token Vercel's API takes: `VERCEL_TOKEN`, this deployment's OIDC token, or your Vercel login,
11
+ * the one the Vercel CLI hands a CLI it runs (`VERCEL_AUTH_TOKEN`) or your `vercel login`.
12
+ */
13
+ async function token() {
14
+ if (process.env.VERCEL_TOKEN) return process.env.VERCEL_TOKEN;
15
+ if (deployed()) return getVercelOidcToken();
16
+ return vercelLogin();
17
+ }
18
+ /** Where `token` comes from, in words: what `vb whoami` says. */
19
+ function credentialSource() {
20
+ if (process.env.VERCEL_TOKEN) return "VERCEL_TOKEN";
21
+ if (deployed()) return "Vercel OIDC";
22
+ return process.env.VERCEL_AUTH_TOKEN ? "the Vercel CLI" : "vercel login";
23
+ }
24
+ /** The team: `VERCEL_ORG_ID`, the one the deployment's OIDC token names, or the linked project's (the nearest `.vercel/project.json`). */
25
+ async function team() {
26
+ if (process.env.VERCEL_ORG_ID) return process.env.VERCEL_ORG_ID;
27
+ if (!process.env.VERCEL_TOKEN && deployed()) try {
28
+ const owner = decodeJwt(await getVercelOidcToken()).owner_id;
29
+ if (typeof owner === "string") return owner;
30
+ } catch {}
31
+ const file = vercelProjectFile();
32
+ if (!file) return null;
33
+ try {
34
+ const linked = JSON.parse(await readFile(file, "utf8"));
35
+ if (typeof linked.orgId === "string") return linked.orgId;
36
+ } catch {}
37
+ return null;
38
+ }
39
+ const environment = {
40
+ fetch: () => async (input, init = {}) => {
41
+ const headers = new Headers(init.headers);
42
+ headers.set("authorization", `Bearer ${await token()}`);
43
+ return globalThis.fetch(input, {
44
+ ...init,
45
+ headers
46
+ });
47
+ },
48
+ team,
49
+ profile: environmentProfile
50
+ };
51
+ /**
52
+ * Your team's board, in Vercel's API: this board keeps nothing, and reads and writes that one as
53
+ * this deployment's project, or as you. Without `board`, the team's board; without `fetch`, it
54
+ * sends the credentials the Vercel CLI or the deployment has.
55
+ */
56
+ function vercel(options = {}) {
57
+ return createVercel(options, environment);
58
+ }
59
+ //#endregion
60
+ export { vercel as n, credentialSource as t };