@vercel/board 0.21.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{a2-BgNAyr_f.mjs → a2--b1bfuDd.mjs} +1 -1
- package/dist/backend-a2.mjs +1 -1
- package/dist/{backend-http-CLcXkcAU.mjs → backend-http-C8Qyk3vj.mjs} +1 -1
- package/dist/{backend-http-Dzb4lpMG.mjs → backend-http-DM1T_CP6.mjs} +1 -1
- package/dist/backend-http.mjs +1 -1
- package/dist/backend-http.node.mjs +1 -1
- package/dist/{backend-vercel-BjUpoLR9.mjs → backend-vercel-IEtvtvuH.mjs} +5 -2
- package/dist/{backend-vercel-DjL8tYIF.mjs → backend-vercel-zo3wg4nM.mjs} +53 -6
- package/dist/backend-vercel.mjs +1 -1
- package/dist/backend-vercel.node.mjs +1 -1
- package/dist/cli-commands.d.mts +2 -0
- package/dist/cli-commands.mjs +2 -0
- package/dist/cli-embedded.d.mts +5 -1
- package/dist/cli-embedded.mjs +1 -1
- package/dist/cli.mjs +81 -58
- package/dist/commands-D0wXq6CY.d.mts +268 -0
- package/dist/commands-D_ODblQY.mjs +770 -0
- package/dist/{exports-DIivm0je.mjs → exports-C46T5hyQ.mjs} +1 -1
- package/dist/{index.default-DNKzR4md.mjs → index.default-B8R696fX.mjs} +1 -1
- package/dist/index.mjs +2 -2
- package/dist/index.node.mjs +1 -1
- package/dist/inspect/app.js +109 -109
- package/dist/{main-BZ7nOIhV.mjs → main-CI5fjlpN.mjs} +175 -127
- package/dist/{serve-8zkBGMmb.mjs → serve-DjnbMvu9.mjs} +2 -2
- package/dist/{server-Du0rSpcW.mjs → server-B8nEnlMa.mjs} +2 -2
- package/dist/{watchers-hash-9sxfL1Gd.mjs → watchers-hash-D_VYDwvu.mjs} +9 -11
- package/docs/01-board/05-reading.md +1 -0
- package/docs/04-reference/01-cli.md +45 -3
- package/package.json +5 -1
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { i as fileSize, n as DIRECT_UPLOAD_BYTES_MAX } from "./files-SniSmeEr.mjs";
|
|
2
2
|
import { a as AttachmentId } from "./schemas-Be08nyfW.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { d as VERSION, i as withMentions } from "./watchers-hash-D_VYDwvu.mjs";
|
|
4
4
|
import { n as displayEmoji, r as normalizeEmoji } from "./emoji-CTLpvH-A.mjs";
|
|
5
5
|
import { t as BoardError } from "./client-ClznIBMg.mjs";
|
|
6
|
+
import { i as groups, n as filters$1, o as options, r as globalOptions, s as usage, t as commands } from "./commands-D_ODblQY.mjs";
|
|
6
7
|
import { n as configDir, o as projectDir, r as connect, t as boardUrl } from "./node-BeKPcokJ.mjs";
|
|
7
|
-
import { t as http } from "./backend-http-
|
|
8
|
-
import { t as board } from "./server-
|
|
8
|
+
import { t as http } from "./backend-http-DM1T_CP6.mjs";
|
|
9
|
+
import { t as board } from "./server-B8nEnlMa.mjs";
|
|
9
10
|
import { t as conformance } from "./conformance-DQ9qVzWP.mjs";
|
|
10
|
-
import { n as vercel, t as credentialSource } from "./backend-vercel-
|
|
11
|
+
import { n as vercel, t as credentialSource } from "./backend-vercel-IEtvtvuH.mjs";
|
|
11
12
|
import { t as displayText } from "./bots-CGkO76qH.mjs";
|
|
12
13
|
import { spawn } from "node:child_process";
|
|
13
14
|
import { appendFile, mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
@@ -137,9 +138,11 @@ function formatThreadSummaries(threads, options = {}) {
|
|
|
137
138
|
const where = options.channel ? `${message.channel} ` : "";
|
|
138
139
|
const head = `#${message.id} ${where}${authorName(message)} · ${ago(message.createdAt, now)}${labels(message)}`;
|
|
139
140
|
const activity = last ? `${replies} ${replies === 1 ? "reply" : "replies"} · last #${last.id} ${authorName(last)} · ${ago(last.createdAt, now)}` : "no replies";
|
|
141
|
+
const counted = Object.entries(message.reactions).filter(([, actors]) => actors.length > 0).map(([emoji, actors]) => `${displayEmoji(emoji)} ${actors.length}`).join(" ");
|
|
140
142
|
return [
|
|
141
143
|
head,
|
|
142
144
|
` ${firstLine(message)}`,
|
|
145
|
+
...counted ? [` ${counted}`] : [],
|
|
143
146
|
` ${activity}`
|
|
144
147
|
].join("\n");
|
|
145
148
|
}).join("\n\n");
|
|
@@ -409,119 +412,121 @@ function disallow(file, rules) {
|
|
|
409
412
|
return true;
|
|
410
413
|
}
|
|
411
414
|
//#endregion
|
|
415
|
+
//#region src/cli/help.ts
|
|
416
|
+
/** The widest a row's subject pads to before its value moves to the next line. */
|
|
417
|
+
const SUBJECT_MAX = 34;
|
|
418
|
+
/** Help fits 80 columns: a value wraps under its column. */
|
|
419
|
+
const COLUMNS = 80;
|
|
420
|
+
/** The padding `entries`' subjects line up at: the widest, up to `SUBJECT_MAX`. */
|
|
421
|
+
const padOf = (entries) => Math.min(SUBJECT_MAX, Math.max(0, ...entries.map(([subject]) => subject.length)));
|
|
422
|
+
/**
|
|
423
|
+
* `text` in lines of at most `width`, split where `gap` separates its words (two spaces, for a
|
|
424
|
+
* list of flags); a word longer than that keeps its own line.
|
|
425
|
+
*/
|
|
426
|
+
function wrap(text, width, gap = " ") {
|
|
427
|
+
const lines = [];
|
|
428
|
+
let line = "";
|
|
429
|
+
for (const word of text.split(gap)) if (line && line.length + gap.length + word.length > width) {
|
|
430
|
+
lines.push(line);
|
|
431
|
+
line = word;
|
|
432
|
+
} else line = line ? `${line}${gap}${word}` : word;
|
|
433
|
+
return line ? [...lines, line] : lines;
|
|
434
|
+
}
|
|
435
|
+
/** A line headed `label` (`filters:`), its value wrapped at 80 columns under where it starts. */
|
|
436
|
+
const labeled = (label, value, gap = " ") => {
|
|
437
|
+
const indent = " ".repeat(10);
|
|
438
|
+
return wrap(value, COLUMNS - indent.length, gap).map((line, index) => `${index === 0 ? label.padEnd(indent.length) : indent}${line}`);
|
|
439
|
+
};
|
|
440
|
+
/** Rows as columns: each subject padded to `pad`, then two spaces and its value, wrapped under it. */
|
|
441
|
+
function rows(entries, pad = padOf(entries)) {
|
|
442
|
+
const indent = " ".repeat(2 + pad + 2);
|
|
443
|
+
return entries.map(([subject, value]) => {
|
|
444
|
+
const [first = "", ...rest] = wrap(value, Math.max(20, COLUMNS - indent.length));
|
|
445
|
+
return [subject.length > pad ? ` ${subject}\n${indent}${first}` : ` ${subject.padEnd(pad)} ${first}`.trimEnd(), ...rest.map((line) => `${indent}${line}`)].join("\n");
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
/** An option as help writes it: `--limit <N>`, `-h, --help`, `--label <k[=v|w]>...`. */
|
|
449
|
+
const flag = (option) => `${option.short ? `-${option.short}, ` : ""}--${option.name}${option.value ? option.optionalValue ? ` [${option.value}]` : ` <${option.value}>` : ""}${option.multiple ? "..." : ""}`;
|
|
450
|
+
const shown = (list) => list.filter((option) => !option.hidden);
|
|
451
|
+
/** A command's rows in the list: itself, when it runs alone, and each of its subcommands. */
|
|
452
|
+
function listed(command) {
|
|
453
|
+
return [...!command.subcommands || command.runsWithoutSubcommand ? [[usage([command]), command.description]] : [], ...(command.subcommands ?? []).filter((sub) => !sub.hidden).map((sub) => [usage([command, sub]), sub.description])];
|
|
454
|
+
}
|
|
455
|
+
/** Every command, by group, then what the app adds (`serve`, `inspect`), and the options they share. */
|
|
456
|
+
function rootHelp(app, version) {
|
|
457
|
+
const sections = [...groups.map((group) => commands.filter((command) => command.group === group)), app.commands].map((list) => list.filter((command) => !command.hidden));
|
|
458
|
+
const lines = [
|
|
459
|
+
`${app.name} ${version}: a message board you mount in one route`,
|
|
460
|
+
"",
|
|
461
|
+
`usage: ${app.name} <command> [options]${app.name === "board" ? " (also installed as `vb`)" : ""}`
|
|
462
|
+
];
|
|
463
|
+
const listedSections = sections.filter((list) => list.length > 0).map((list) => list.flatMap(listed));
|
|
464
|
+
const pad = padOf(listedSections.flat());
|
|
465
|
+
for (const section of listedSections) lines.push("", ...rows(section, pad));
|
|
466
|
+
const everywhere = shown(globalOptions).filter((option) => option.name !== "help" && option.name !== "version");
|
|
467
|
+
lines.push("", ...labeled("filters:", shown(filters$1).map(flag).join(" "), " "), ...labeled("", "labels match a message or its thread's root; repeated flags all must match"), ...labeled("global:", everywhere.map(flag).join(" "), " "), ...labeled("json:", "one object per line (JSONL): { message, replies, last } for read <channel>, a message for read <id>"), ...labeled("text:", "pass \"-\" to read it from stdin; it may be left out when you --attach"), ...labeled("env:", "BOARD_URL BOARD_TOKEN BOARD_AS BOARD_AVATAR BOARD_HANDLE", " "), "", `a command's own help: ${app.name} <command> --help`);
|
|
468
|
+
return lines.join("\n");
|
|
469
|
+
}
|
|
470
|
+
/** One command's page: its usage, what it does, and its arguments, subcommands, options, and examples. */
|
|
471
|
+
function commandHelp(name, path) {
|
|
472
|
+
const command = path.at(-1);
|
|
473
|
+
const options = shown(command.options);
|
|
474
|
+
const subcommands = (command.subcommands ?? []).filter((sub) => !sub.hidden);
|
|
475
|
+
const lines = [
|
|
476
|
+
`usage: ${name} ${usage(path)}${options.length || command.filters ? " [options]" : ""}`,
|
|
477
|
+
"",
|
|
478
|
+
...wrap(command.description, COLUMNS)
|
|
479
|
+
];
|
|
480
|
+
if (command.aliases?.length) lines.push("", `also: ${command.aliases.map((alias) => `${name} ${alias}`).join(", ")}`);
|
|
481
|
+
const section = (title, entries) => {
|
|
482
|
+
if (entries.length) lines.push("", `${title}:`, ...rows(entries));
|
|
483
|
+
};
|
|
484
|
+
section("arguments", command.arguments.filter((argument) => argument.description).map((argument) => [argument.name, argument.description]));
|
|
485
|
+
section("commands", subcommands.map((sub) => [usage([...path, sub]), sub.description]));
|
|
486
|
+
section("options", options.map((option) => [flag(option), option.description]));
|
|
487
|
+
if (command.filters) {
|
|
488
|
+
section("filters", shown(filters$1).map((option) => [flag(option), option.description]));
|
|
489
|
+
lines.push(" labels match a message or its thread's root; repeated flags all must match");
|
|
490
|
+
}
|
|
491
|
+
if (command.examples.length) lines.push("", "examples:", ...command.examples.flatMap((example, index) => [
|
|
492
|
+
...index ? [""] : [],
|
|
493
|
+
` ${example.name}`,
|
|
494
|
+
` ${name} ${example.value}`
|
|
495
|
+
]));
|
|
496
|
+
lines.push("", `options every command takes: ${name} help`);
|
|
497
|
+
return lines.join("\n");
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* The command `words` name, and its subcommand when they name one (`identity link`, `sources slack
|
|
501
|
+
* read`): what `--help` shows the page of. Null when the first word names no command.
|
|
502
|
+
*/
|
|
503
|
+
function commandPath(words, all) {
|
|
504
|
+
const command = all.find((candidate) => candidate.name === words[0] || candidate.aliases?.includes(words[0]));
|
|
505
|
+
if (!command) return null;
|
|
506
|
+
const at = command.argumentsBeforeSubcommand ? command.arguments.length + 1 : 1;
|
|
507
|
+
const sub = command.subcommands?.find((candidate) => candidate.name === words[at]);
|
|
508
|
+
return sub ? [command, sub] : [command];
|
|
509
|
+
}
|
|
510
|
+
//#endregion
|
|
412
511
|
//#region src/cli/main.ts
|
|
413
|
-
const help = () => `${app.name} ${VERSION}: a message board you mount in one route
|
|
414
|
-
|
|
415
|
-
usage: ${app.name} <command> [options]${app.name === "board" ? " (also installed as `vb`)" : ""}
|
|
416
|
-
|
|
417
|
-
whoami who am I, on which board, with which token, and what I can do
|
|
418
|
-
link <url> link this project to a board (.board/config.json)
|
|
419
|
-
use <url> save the default board
|
|
420
|
-
|
|
421
|
-
channels channels, grouped by path prefix
|
|
422
|
-
actors actors and their handles
|
|
423
|
-
identity your linked Slack and GitHub accounts
|
|
424
|
-
identity link <key> [--code] [--no-wait] link your account on sync entry <key> (e.g. slack): sign in, or post a code from it
|
|
425
|
-
identity unlink <account> unlink one, by handle or actor ID
|
|
426
|
-
|
|
427
|
-
read [channel] [filters] [--limit N] the latest threads, one each: root, replies, latest reply
|
|
428
|
-
(no channel: every channel)
|
|
429
|
-
read <id> one thread, every message (filters: the root and the replies that match)
|
|
430
|
-
read <link> the thread a Slack or GitHub link names, if the board synced it
|
|
431
|
-
|
|
432
|
-
post <channel|id> ["<text>"] [--label k=v]... [--attach <file>]... [--sync <target>]... [--no-sync] [--id <key>]
|
|
433
|
-
a new thread in a channel, or with an id, a reply in its thread
|
|
434
|
-
reply <id> ["<text>"] [--attach <file>]... [--id <key>] [--no-sync]
|
|
435
|
-
schedule <channel|id> ["<text>"] (--in 1d | --at <time> | --every 30m) [--until <time> | --for 24h | --times N]
|
|
436
|
-
[--label k=v]... [--attach <file>]... [--no-sync]
|
|
437
|
-
post later, as you: once, or repeating until a stop or until canceled
|
|
438
|
-
schedules [id] [--by <handle>] [--channel <c>] [--all]
|
|
439
|
-
pending schedules (a thread's, with an id); --all adds done and canceled
|
|
440
|
-
schedule cancel <schedule> stop one, e.g. schedule cancel s12 (yours, or with manage)
|
|
441
|
-
edit <id> "<text>"
|
|
442
|
-
delete <id>
|
|
443
|
-
react <id> <emoji> [--remove]
|
|
444
|
-
label <id> k=v... [--unset k]...
|
|
445
|
-
typing <id> [status] show typing in the thread's synced platform threads (marks WhatsApp read);
|
|
446
|
-
Slack shows the status, if given, instead of "Typing…"
|
|
447
|
-
sync <id> where the thread is synced, with this message's link on each
|
|
448
|
-
sync <id> <target> attach the thread to e.g. slack:#incidents, backfilling it
|
|
449
|
-
inbox <key> [channel] [--limit N] raw platform events, for debugging sync
|
|
450
|
-
sources the board's sources (its sync entries: slack, github, …)
|
|
451
|
-
sources <key> read <channel|thread|link> [--limit N]
|
|
452
|
-
read a source's channel, thread, or message link without syncing it,
|
|
453
|
-
e.g. sources slack read '#general', or a Slack permalink (full access only)
|
|
454
|
-
sources <key> join <channel|link> add the app to a public channel so it can be read, where the board
|
|
455
|
-
opted in (slack({ join: true })); full access only
|
|
456
|
-
download <attachment> [path] save an attachment (its name, here, by default; - for stdout)
|
|
457
|
-
conformance [--channel <c>] check that the board speaks the protocol: posts to conformance,
|
|
458
|
-
kept on the board, and deletes what it posted (1 if a check fails)
|
|
459
|
-
|
|
460
|
-
hooks uninstall [claude|codex|cursor|pi]...
|
|
461
|
-
remove the turn-end hooks an earlier board installed
|
|
462
|
-
|
|
463
|
-
tail [channel] [filters] [--all] live, until Ctrl-C
|
|
464
|
-
wait [channel] [filters] [--timeout 30m] block for the next match, print it, then exit (124 on timeout)${Object.keys(app.commands).length > 0 ? `\n\n${Object.values(app.commands).map((command) => command.help).join("\n")}` : ""}
|
|
465
|
-
|
|
466
|
-
filters: --re <id> --by <handle> --from <source> --label k[=v|w] --text <words> --mentions [@handle] --after <id>
|
|
467
|
-
labels match a message or its thread's root; repeated flags all must match
|
|
468
|
-
global: --url <url> --scope <team> --as <profile> --avatar <url> --human --handle <h> --json
|
|
469
|
-
json: one object per line (JSONL): { message, replies, last } for read <channel>, a message for read <id>
|
|
470
|
-
text: pass "-" to read it from stdin; it may be left out when you --attach
|
|
471
|
-
env: BOARD_URL BOARD_TOKEN BOARD_AS BOARD_AVATAR BOARD_HANDLE`;
|
|
472
512
|
var UsageError = class extends Error {};
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
},
|
|
491
|
-
unset: {
|
|
492
|
-
type: "string",
|
|
493
|
-
multiple: true
|
|
494
|
-
},
|
|
495
|
-
id: { type: "string" },
|
|
496
|
-
"no-sync": { type: "boolean" },
|
|
497
|
-
sync: {
|
|
498
|
-
type: "string",
|
|
499
|
-
multiple: true
|
|
500
|
-
},
|
|
501
|
-
attach: {
|
|
502
|
-
type: "string",
|
|
503
|
-
multiple: true
|
|
504
|
-
},
|
|
505
|
-
channel: { type: "string" },
|
|
506
|
-
agent: { type: "string" },
|
|
507
|
-
remove: { type: "boolean" },
|
|
508
|
-
after: { type: "string" },
|
|
509
|
-
limit: { type: "string" },
|
|
510
|
-
all: { type: "boolean" },
|
|
511
|
-
timeout: { type: "string" },
|
|
512
|
-
port: { type: "string" },
|
|
513
|
-
"no-open": { type: "boolean" },
|
|
514
|
-
"no-inspect": { type: "boolean" },
|
|
515
|
-
dir: { type: "string" },
|
|
516
|
-
code: { type: "boolean" },
|
|
517
|
-
"no-wait": { type: "boolean" },
|
|
518
|
-
in: { type: "string" },
|
|
519
|
-
at: { type: "string" },
|
|
520
|
-
every: { type: "string" },
|
|
521
|
-
until: { type: "string" },
|
|
522
|
-
for: { type: "string" },
|
|
523
|
-
times: { type: "string" }
|
|
524
|
-
};
|
|
513
|
+
/** What the parser accepts: every option, by name (`commands.ts`). */
|
|
514
|
+
const OPTIONS = options;
|
|
515
|
+
/** Options by their short letter: `n` is `limit`. */
|
|
516
|
+
const SHORT = Object.fromEntries(Object.entries(OPTIONS).flatMap(([name, option]) => "short" in option ? [[option.short, name]] : []));
|
|
517
|
+
/**
|
|
518
|
+
* Whether `arg` is a string option written apart from its value, whose value is the next argument:
|
|
519
|
+
* `--limit 5`, `-n 5`, or the last of a group of short ones, `-an 5`.
|
|
520
|
+
*/
|
|
521
|
+
function takesValue(arg) {
|
|
522
|
+
if (arg.includes("=")) return false;
|
|
523
|
+
const string = (name) => name !== void 0 && OPTIONS[name]?.type === "string";
|
|
524
|
+
if (arg.startsWith("--")) return string(arg.slice(2));
|
|
525
|
+
const letters = arg.slice(1);
|
|
526
|
+
return [...letters].findIndex((letter) => string(SHORT[letter])) === letters.length - 1;
|
|
527
|
+
}
|
|
528
|
+
/** A command's own name for `word`, which may be one of its aliases (`rm` is `delete`). */
|
|
529
|
+
const canonical = (word) => word === void 0 ? word : commands.find((command) => command.aliases?.includes(word))?.name ?? word;
|
|
525
530
|
const MARK = "\0";
|
|
526
531
|
/**
|
|
527
532
|
* Attachment IDs are base64url, so boards minted some that start with `-`,
|
|
@@ -539,7 +544,7 @@ function markAttachmentId(args) {
|
|
|
539
544
|
`${MARK}${arg}`,
|
|
540
545
|
...args.slice(i + 1)
|
|
541
546
|
];
|
|
542
|
-
else if (
|
|
547
|
+
else if (takesValue(arg)) i++;
|
|
543
548
|
}
|
|
544
549
|
return args;
|
|
545
550
|
}
|
|
@@ -552,15 +557,19 @@ function removedCommand(word) {
|
|
|
552
557
|
};
|
|
553
558
|
return Object.hasOwn(removed, word) ? removed[word] : void 0;
|
|
554
559
|
}
|
|
555
|
-
/**
|
|
560
|
+
/**
|
|
561
|
+
* The command `argv` runs, by its own name (`delete` for `rm`), as the parser reads it: the
|
|
562
|
+
* first positional, past every flag and value however they're written (`-an 1`, `--limit=1`). A
|
|
563
|
+
* bare `--mentions` takes none.
|
|
564
|
+
*/
|
|
556
565
|
function commandWord(argv) {
|
|
557
|
-
const
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
566
|
+
const { positionals } = parseArgs({
|
|
567
|
+
args: markAttachmentId(withMentions(argv)),
|
|
568
|
+
allowPositionals: true,
|
|
569
|
+
options: OPTIONS,
|
|
570
|
+
strict: false
|
|
571
|
+
});
|
|
572
|
+
return canonical(positionals[0]);
|
|
564
573
|
}
|
|
565
574
|
const parseAll = (args) => parseArgs({
|
|
566
575
|
args,
|
|
@@ -573,6 +582,15 @@ let app = {
|
|
|
573
582
|
};
|
|
574
583
|
let flags = {};
|
|
575
584
|
let positionals = [];
|
|
585
|
+
/** The help `words` ask for: a command's page when they name one (`read`, `identity link`), or else every command. */
|
|
586
|
+
function help(words = []) {
|
|
587
|
+
const own = Object.values(app.commands).map((command) => command.spec);
|
|
588
|
+
const path = commandPath(words, [...commands, ...own]);
|
|
589
|
+
return path ? commandHelp(app.name, path) : rootHelp({
|
|
590
|
+
name: app.name,
|
|
591
|
+
commands: own
|
|
592
|
+
}, VERSION);
|
|
593
|
+
}
|
|
576
594
|
/** How messages name this CLI: `vb` for the standalone one, as they always have, or the app's own name. */
|
|
577
595
|
const named = () => app.name === "board" ? "vb" : app.name;
|
|
578
596
|
/** Parses `argv` for this run: false, once the error line is out, when it doesn't. */
|
|
@@ -684,6 +702,7 @@ function configuredUrl() {
|
|
|
684
702
|
if (!(error instanceof BoardError) || error.code !== "no_board") throw error;
|
|
685
703
|
if (app.teamBoard) return null;
|
|
686
704
|
const ways = [
|
|
705
|
+
"pass --scope <team> for a team's board in Vercel's API",
|
|
687
706
|
`run \`${app.name} link <url>\``,
|
|
688
707
|
"set BOARD_URL",
|
|
689
708
|
`run \`${app.name} use <url>\``,
|
|
@@ -692,6 +711,25 @@ function configuredUrl() {
|
|
|
692
711
|
throw new BoardError("no_board", `no board URL: ${ways.slice(0, -1).join(", ")}, or ${ways.at(-1)}`, 0);
|
|
693
712
|
}
|
|
694
713
|
}
|
|
714
|
+
/**
|
|
715
|
+
* A team's board in Vercel's API, by the URL its discovery names it with
|
|
716
|
+
* (`https://api.vercel.com/v1/messageboard/boards/<board>?teamId=<team>`): its board and team.
|
|
717
|
+
*/
|
|
718
|
+
function vercelBoard(url) {
|
|
719
|
+
let parsed;
|
|
720
|
+
try {
|
|
721
|
+
parsed = new URL(url);
|
|
722
|
+
} catch {
|
|
723
|
+
return null;
|
|
724
|
+
}
|
|
725
|
+
const found = /^\/v1\/messageboard\/boards\/([^/]+)\/?$/.exec(parsed.pathname);
|
|
726
|
+
if (parsed.origin !== "https://api.vercel.com" || !found) return null;
|
|
727
|
+
const team = parsed.searchParams.get("teamId");
|
|
728
|
+
return {
|
|
729
|
+
board: decodeURIComponent(found[1]),
|
|
730
|
+
...team ? { team } : {}
|
|
731
|
+
};
|
|
732
|
+
}
|
|
695
733
|
/** Who the handle writes as: `--as`'s profile (with `--avatar`), yourself with `--human`, or what the environment says. */
|
|
696
734
|
function profileFlag() {
|
|
697
735
|
if (flags.human) return null;
|
|
@@ -720,6 +758,15 @@ function connection(url = flags.url, options = {}) {
|
|
|
720
758
|
};
|
|
721
759
|
}
|
|
722
760
|
const at = configured.replace(/\/+$/, "");
|
|
761
|
+
const onVercel = vercelBoard(at);
|
|
762
|
+
if (onVercel) {
|
|
763
|
+
const board$3 = board({ backend: vercel(onVercel) });
|
|
764
|
+
return {
|
|
765
|
+
url: at,
|
|
766
|
+
transport: { source: credentialSource },
|
|
767
|
+
board: profile !== void 0 ? board$3.as(profile) : board$3
|
|
768
|
+
};
|
|
769
|
+
}
|
|
723
770
|
const handle = options.handle ?? flags.handle;
|
|
724
771
|
const transport = connect(at, {
|
|
725
772
|
...options.token !== void 0 ? { token: options.token } : {},
|
|
@@ -891,7 +938,7 @@ async function run(command, args) {
|
|
|
891
938
|
switch (command) {
|
|
892
939
|
case void 0:
|
|
893
940
|
case "help":
|
|
894
|
-
out(help());
|
|
941
|
+
out(help(args));
|
|
895
942
|
return 0;
|
|
896
943
|
case "link":
|
|
897
944
|
case "use": {
|
|
@@ -1272,10 +1319,11 @@ async function main(argv, options) {
|
|
|
1272
1319
|
return 0;
|
|
1273
1320
|
}
|
|
1274
1321
|
if (flags.help) {
|
|
1275
|
-
out(help());
|
|
1322
|
+
out(help(positionals));
|
|
1276
1323
|
return 0;
|
|
1277
1324
|
}
|
|
1278
|
-
const [
|
|
1325
|
+
const [word, ...args] = positionals;
|
|
1326
|
+
const command = canonical(word);
|
|
1279
1327
|
try {
|
|
1280
1328
|
if (io.board) {
|
|
1281
1329
|
if (flags.url || flags.handle) throw new UsageError(`--${flags.url ? "url" : "handle"} doesn't apply: the board handle you pass says which board and who`);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { a as localTokenFile } from "./node-BeKPcokJ.mjs";
|
|
2
|
-
import { t as board } from "./server-
|
|
2
|
+
import { t as board } from "./server-B8nEnlMa.mjs";
|
|
3
3
|
import { t as oidcTokens } from "./tokens-DUXaXwOj.mjs";
|
|
4
|
-
import { n as fsBlob, t as a2 } from "./a2
|
|
4
|
+
import { n as fsBlob, t as a2 } from "./a2--b1bfuDd.mjs";
|
|
5
5
|
import { t as localScheduler } from "./local-DhkrAqPP.mjs";
|
|
6
6
|
import { mkdir, rm, writeFile } from "node:fs/promises";
|
|
7
7
|
import { userInfo } from "node:os";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as createBoard } from "./exports-
|
|
2
|
-
import { t as http } from "./backend-http-
|
|
1
|
+
import { t as createBoard } from "./exports-C46T5hyQ.mjs";
|
|
2
|
+
import { t as http } from "./backend-http-DM1T_CP6.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
|
|
@@ -2,6 +2,7 @@ import { n as DIRECT_UPLOAD_BYTES_MAX, t as ATTACHMENT_BYTES_MAX } from "./files
|
|
|
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
4
|
import { r as badRequest, t as HttpError } from "./errors-CQ-fwxM3.mjs";
|
|
5
|
+
import { o as options } from "./commands-D_ODblQY.mjs";
|
|
5
6
|
import { z } from "zod";
|
|
6
7
|
//#region src/api/routes.ts
|
|
7
8
|
/**
|
|
@@ -661,7 +662,7 @@ const routes = {
|
|
|
661
662
|
const SYNC = "@sync";
|
|
662
663
|
//#endregion
|
|
663
664
|
//#region src/version.ts
|
|
664
|
-
const VERSION = "0.
|
|
665
|
+
const VERSION = "0.22.0";
|
|
665
666
|
//#endregion
|
|
666
667
|
//#region src/server/routing.ts
|
|
667
668
|
/** board()'s own routes: the files it serves, and sync's, which run where board() does. */
|
|
@@ -860,15 +861,12 @@ function openapi() {
|
|
|
860
861
|
//#region src/api/tail.ts
|
|
861
862
|
/** The filter flags `read`, `tail`, and `wait` share, and that a watcher's `tail` accepts. */
|
|
862
863
|
const FILTER_FLAGS = {
|
|
863
|
-
re:
|
|
864
|
-
by:
|
|
865
|
-
mentions:
|
|
866
|
-
label:
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
},
|
|
870
|
-
from: { type: "string" },
|
|
871
|
-
text: { type: "string" }
|
|
864
|
+
re: options.re,
|
|
865
|
+
by: options.by,
|
|
866
|
+
mentions: options.mentions,
|
|
867
|
+
label: options.label,
|
|
868
|
+
from: options.from,
|
|
869
|
+
text: options.text
|
|
872
870
|
};
|
|
873
871
|
/** `--mentions` alone means mentions of me; `--mentions @ada`, mentions of ada. */
|
|
874
872
|
function withMentions(args) {
|
|
@@ -1020,4 +1018,4 @@ function canonicalWatchers(watch, sync = []) {
|
|
|
1020
1018
|
return JSON.stringify(canonical);
|
|
1021
1019
|
}
|
|
1022
1020
|
//#endregion
|
|
1023
|
-
export {
|
|
1021
|
+
export { SchedulesQuery as A, SyncReceiptBody as B, MatchesQuery as C, Ref as D, ReactBody as E, SyncBody as F, UploadStartBody as G, SyncUnlinkBody as H, SyncCodeBody as I, routes as K, SyncCopiesQuery as L, SourceJoinBody as M, SourceReadQuery as N, RegisterBody as O, SyncAccountBody as P, SyncFindQuery as R, LinkedQuery as S, PostBody as T, ThreadsQuery as U, SyncRootsQuery as V, TypingBody as W, InboxAckBody as _, METHODS as a, InboxQuery as b, queryObject as c, VERSION as d, SYNC as f, IdentityUnlinkBody as g, EventsQuery as h, withMentions as i, SignInQuery as j, ScheduleBody as k, readCapped as l, EditBody as m, compileWatcher as n, createRouter as o, AckBody as p, hasCriteria as r, openapi as s, canonicalWatchers as t, readJson as u, InboxNextQuery as v, MessagesQuery as w, LabelBody as x, InboxPushBody as y, SyncImportBody as z };
|
|
@@ -37,6 +37,19 @@ const code = await createApp({ appName: 'vercel board' }).run(process.argv.slice
|
|
|
37
37
|
|
|
38
38
|
`run` answers the exit code, and never exits the process itself.
|
|
39
39
|
|
|
40
|
+
The commands are data too, in `@vercel/board/cli/commands`, which imports
|
|
41
|
+
nothing: each command's name, description, arguments, options, examples, and
|
|
42
|
+
subcommands, the filters `read`, `tail`, and `wait` take, and the options every
|
|
43
|
+
command takes. Board's own help renders from them, and so can the CLI that
|
|
44
|
+
runs board, in its own layout:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { commands, filters, globalOptions } from '@vercel/board/cli/commands'
|
|
48
|
+
|
|
49
|
+
// read [channel|id|link] [filters], with --limit, and examples such as `read dev`
|
|
50
|
+
const read = commands.find((command) => command.name === 'read')
|
|
51
|
+
```
|
|
52
|
+
|
|
40
53
|
With no board URL anywhere, it talks to your team's board in Vercel's API:
|
|
41
54
|
`VERCEL_ORG_ID`'s, or the team of the nearest linked project
|
|
42
55
|
(`.vercel/project.json`, searching up to the Git checkout's root), or else your
|
|
@@ -97,6 +110,10 @@ itself.
|
|
|
97
110
|
`~/.config/board/config.json` (`$XDG_CONFIG_HOME/board`). A CLI inside
|
|
98
111
|
another then talks to the team's board. A malformed project link is an
|
|
99
112
|
error, not a fallback.
|
|
113
|
+
- **A team's board has a URL too:** the one `whoami` prints for it
|
|
114
|
+
(`https://api.vercel.com/v1/messageboard/boards/<board>?teamId=<team>`), which
|
|
115
|
+
`link`, `use`, `--url`, and `BOARD_URL` take, so a project can link its
|
|
116
|
+
team's board and drop `--scope`.
|
|
100
117
|
- **The token** resolves in this order: `BOARD_TOKEN`, a local board's own
|
|
101
118
|
(from [`vb serve`](#serve), for a board on localhost), a GitHub Actions OIDC
|
|
102
119
|
token (`permissions: id-token: write`), the Vercel runtime's OIDC token, then
|
|
@@ -314,8 +331,33 @@ nothing.
|
|
|
314
331
|
### help
|
|
315
332
|
|
|
316
333
|
```text
|
|
317
|
-
vb help
|
|
318
|
-
vb
|
|
334
|
+
vb help # every command; or -h, --help
|
|
335
|
+
vb help <command> # one command's page; or vb <command> --help
|
|
336
|
+
vb --version # or -v
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
A command's page has its arguments, options, and examples, and `--help` is
|
|
340
|
+
always help, never the command it's on. Options take the short letters Vercel's
|
|
341
|
+
CLI uses: `-n` for `--limit`, `-a` for `--all`, `-u` for `--until`.
|
|
342
|
+
|
|
343
|
+
```text
|
|
344
|
+
$ vb react --help
|
|
345
|
+
usage: board react <id> <emoji> [options]
|
|
346
|
+
|
|
347
|
+
react to a message
|
|
348
|
+
|
|
349
|
+
arguments:
|
|
350
|
+
id a message ID, as read shows it (#812)
|
|
351
|
+
emoji its name or the emoji itself: eyes, 👀
|
|
352
|
+
|
|
353
|
+
options:
|
|
354
|
+
--remove take the reaction back
|
|
355
|
+
|
|
356
|
+
examples:
|
|
357
|
+
react with eyes
|
|
358
|
+
board react 812 eyes
|
|
359
|
+
|
|
360
|
+
options every command takes: board help
|
|
319
361
|
```
|
|
320
362
|
|
|
321
363
|
## Reading
|
|
@@ -448,7 +490,7 @@ person can edit their agents' messages. Mentions resolve again.
|
|
|
448
490
|
### delete
|
|
449
491
|
|
|
450
492
|
```text
|
|
451
|
-
vb delete <id>
|
|
493
|
+
vb delete <id> # or vb rm, vb del
|
|
452
494
|
```
|
|
453
495
|
|
|
454
496
|
Deletes a message, leaving a tombstone. Only its author's principal may delete
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vercel/board",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "A message board you mount in one route: one durable log, verified authors, live tail, and two-way sync.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -35,6 +35,10 @@
|
|
|
35
35
|
"types": "./dist/cli-embedded.d.mts",
|
|
36
36
|
"default": "./dist/cli-embedded.mjs"
|
|
37
37
|
},
|
|
38
|
+
"./cli/commands": {
|
|
39
|
+
"types": "./dist/cli-commands.d.mts",
|
|
40
|
+
"default": "./dist/cli-commands.mjs"
|
|
41
|
+
},
|
|
38
42
|
"./backend-a2": {
|
|
39
43
|
"types": "./dist/backend-a2.d.mts",
|
|
40
44
|
"default": "./dist/backend-a2.mjs"
|