vellum-cli 0.2.0 → 0.3.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/index.js +506 -22
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -61,7 +61,7 @@ async function clearToken(baseUrl) {
|
|
|
61
61
|
return existing;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
// src/commands/
|
|
64
|
+
// src/commands/comments.ts
|
|
65
65
|
import { parseArgs } from "node:util";
|
|
66
66
|
// ../core/src/client.ts
|
|
67
67
|
class VellumApiError extends Error {
|
|
@@ -113,6 +113,42 @@ class VellumClient {
|
|
|
113
113
|
await this.fail(res);
|
|
114
114
|
return await res.json();
|
|
115
115
|
}
|
|
116
|
+
async getPage(pageId) {
|
|
117
|
+
const res = await this.fetchImpl(`${this.baseUrl}/v1/pages/${encodeURIComponent(pageId)}`, { headers: this.authHeaders() });
|
|
118
|
+
if (!res.ok)
|
|
119
|
+
await this.fail(res);
|
|
120
|
+
return await res.json();
|
|
121
|
+
}
|
|
122
|
+
async createComment(pageId, opts) {
|
|
123
|
+
const res = await this.fetchImpl(`${this.baseUrl}/v1/pages/${encodeURIComponent(pageId)}/comments`, {
|
|
124
|
+
method: "POST",
|
|
125
|
+
headers: { "Content-Type": "application/json", ...this.authHeaders() },
|
|
126
|
+
body: JSON.stringify({
|
|
127
|
+
version_id: opts.versionId,
|
|
128
|
+
body: opts.body,
|
|
129
|
+
anchor: opts.anchor,
|
|
130
|
+
parent_id: opts.parentId
|
|
131
|
+
})
|
|
132
|
+
});
|
|
133
|
+
if (!res.ok)
|
|
134
|
+
await this.fail(res);
|
|
135
|
+
return await res.json();
|
|
136
|
+
}
|
|
137
|
+
async listComments(pageId, opts = {}) {
|
|
138
|
+
const url = new URL(`${this.baseUrl}/v1/pages/${encodeURIComponent(pageId)}/comments`);
|
|
139
|
+
if (opts.versionId)
|
|
140
|
+
url.searchParams.set("version_id", opts.versionId);
|
|
141
|
+
const res = await this.fetchImpl(url, { headers: this.authHeaders() });
|
|
142
|
+
if (!res.ok)
|
|
143
|
+
await this.fail(res);
|
|
144
|
+
return await res.json();
|
|
145
|
+
}
|
|
146
|
+
async resolveComment(commentId) {
|
|
147
|
+
const res = await this.fetchImpl(`${this.baseUrl}/v1/comments/${encodeURIComponent(commentId)}/resolve`, { method: "POST", headers: this.authHeaders() });
|
|
148
|
+
if (!res.ok)
|
|
149
|
+
await this.fail(res);
|
|
150
|
+
return await res.json();
|
|
151
|
+
}
|
|
116
152
|
async startCliAuth() {
|
|
117
153
|
const res = await this.fetchImpl(`${this.baseUrl}/v1/cli/auth/start`, {
|
|
118
154
|
method: "POST",
|
|
@@ -150,6 +186,148 @@ class VellumClient {
|
|
|
150
186
|
await this.fail(res);
|
|
151
187
|
}
|
|
152
188
|
}
|
|
189
|
+
// src/commands/comments.ts
|
|
190
|
+
var HELP = `vellum comments — list the comments on a document
|
|
191
|
+
|
|
192
|
+
Usage:
|
|
193
|
+
vellum comments <page-id> [options]
|
|
194
|
+
|
|
195
|
+
Reads are public, so no login is required. Comments print as threads; replies
|
|
196
|
+
are indented under their parent.
|
|
197
|
+
|
|
198
|
+
Options:
|
|
199
|
+
--open Show only unresolved threads
|
|
200
|
+
--version <id> Only comments anchored to this version id
|
|
201
|
+
--url <url> Server base URL (env: VELLUM_URL)
|
|
202
|
+
--json Print the raw JSON response
|
|
203
|
+
-h, --help Show this help`;
|
|
204
|
+
var truncate = (s, n) => s.length > n ? `${s.slice(0, n - 1)}…` : s;
|
|
205
|
+
var oneLine = (s) => s.replace(/\s+/g, " ").trim();
|
|
206
|
+
function printComment(c, indent) {
|
|
207
|
+
const who = c.author_email ?? c.author_name ?? "(unknown)";
|
|
208
|
+
const state = c.resolved ? "resolved" : "open";
|
|
209
|
+
console.log(`${indent}${c.id} [${state}] ${who}`);
|
|
210
|
+
if (c.anchor?.exact) {
|
|
211
|
+
console.log(`${indent} on “${truncate(oneLine(c.anchor.exact), 70)}”`);
|
|
212
|
+
}
|
|
213
|
+
console.log(`${indent} ${truncate(oneLine(c.body), 100)}`);
|
|
214
|
+
for (const reply of c.replies ?? []) {
|
|
215
|
+
printComment(reply, `${indent} `);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
async function commentsCommand(argv) {
|
|
219
|
+
const { values, positionals } = parseArgs({
|
|
220
|
+
args: argv,
|
|
221
|
+
allowPositionals: true,
|
|
222
|
+
options: {
|
|
223
|
+
open: { type: "boolean", default: false },
|
|
224
|
+
version: { type: "string" },
|
|
225
|
+
url: { type: "string" },
|
|
226
|
+
json: { type: "boolean", default: false },
|
|
227
|
+
help: { type: "boolean", short: "h", default: false }
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
if (values.help) {
|
|
231
|
+
console.log(HELP);
|
|
232
|
+
return 0;
|
|
233
|
+
}
|
|
234
|
+
const pageId = positionals[0];
|
|
235
|
+
if (!pageId) {
|
|
236
|
+
console.error(`Error: missing <page-id>.
|
|
237
|
+
`);
|
|
238
|
+
console.error(HELP);
|
|
239
|
+
return 1;
|
|
240
|
+
}
|
|
241
|
+
const baseUrl = resolveBaseUrl({ url: values.url });
|
|
242
|
+
const client2 = new VellumClient({ baseUrl });
|
|
243
|
+
let comments = await client2.listComments(pageId, {
|
|
244
|
+
versionId: values.version
|
|
245
|
+
});
|
|
246
|
+
if (values.open)
|
|
247
|
+
comments = comments.filter((c) => !c.resolved);
|
|
248
|
+
if (values.json) {
|
|
249
|
+
console.log(JSON.stringify(comments, null, 2));
|
|
250
|
+
return 0;
|
|
251
|
+
}
|
|
252
|
+
if (comments.length === 0) {
|
|
253
|
+
console.log(values.open ? "No open comments." : "No comments.");
|
|
254
|
+
return 0;
|
|
255
|
+
}
|
|
256
|
+
for (const c of comments)
|
|
257
|
+
printComment(c, "");
|
|
258
|
+
return 0;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// src/commands/get.ts
|
|
262
|
+
import { parseArgs as parseArgs2 } from "node:util";
|
|
263
|
+
var HELP2 = `vellum get — print a document's raw HTML
|
|
264
|
+
|
|
265
|
+
Usage:
|
|
266
|
+
vellum get <page-id> [options]
|
|
267
|
+
|
|
268
|
+
Reads are public, so no login is required. Without --version, the page's
|
|
269
|
+
current version is printed.
|
|
270
|
+
|
|
271
|
+
Options:
|
|
272
|
+
--version <n> Print a specific version number (default: current)
|
|
273
|
+
--url <url> Server base URL (env: VELLUM_URL)
|
|
274
|
+
-h, --help Show this help`;
|
|
275
|
+
function resolveVersion(page, requested) {
|
|
276
|
+
if (requested != null) {
|
|
277
|
+
return page.versions.find((v) => v.version_number === requested) ?? null;
|
|
278
|
+
}
|
|
279
|
+
const current = page.current_version_id ? page.versions.find((v) => v.id === page.current_version_id) : undefined;
|
|
280
|
+
return current ?? page.versions[page.versions.length - 1] ?? null;
|
|
281
|
+
}
|
|
282
|
+
async function getCommand(argv) {
|
|
283
|
+
const { values, positionals } = parseArgs2({
|
|
284
|
+
args: argv,
|
|
285
|
+
allowPositionals: true,
|
|
286
|
+
options: {
|
|
287
|
+
version: { type: "string" },
|
|
288
|
+
url: { type: "string" },
|
|
289
|
+
help: { type: "boolean", short: "h", default: false }
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
if (values.help) {
|
|
293
|
+
console.log(HELP2);
|
|
294
|
+
return 0;
|
|
295
|
+
}
|
|
296
|
+
const pageId = positionals[0];
|
|
297
|
+
if (!pageId) {
|
|
298
|
+
console.error(`Error: missing <page-id>.
|
|
299
|
+
`);
|
|
300
|
+
console.error(HELP2);
|
|
301
|
+
return 1;
|
|
302
|
+
}
|
|
303
|
+
let version;
|
|
304
|
+
if (values.version != null) {
|
|
305
|
+
version = Number.parseInt(values.version, 10);
|
|
306
|
+
if (!Number.isInteger(version) || version < 1) {
|
|
307
|
+
console.error("Error: --version must be a positive integer.");
|
|
308
|
+
return 1;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
const baseUrl = resolveBaseUrl({ url: values.url });
|
|
312
|
+
const client2 = new VellumClient({ baseUrl });
|
|
313
|
+
const page = await client2.getPage(pageId);
|
|
314
|
+
const target = resolveVersion(page, version);
|
|
315
|
+
if (!target) {
|
|
316
|
+
console.error(version != null ? `Error: page has no version ${version}.` : "Error: page has no versions.");
|
|
317
|
+
return 1;
|
|
318
|
+
}
|
|
319
|
+
const res = await fetch(target.raw_url);
|
|
320
|
+
if (!res.ok) {
|
|
321
|
+
console.error(`Error: could not fetch raw HTML (${res.status}).`);
|
|
322
|
+
return 1;
|
|
323
|
+
}
|
|
324
|
+
process.stdout.write(await res.text());
|
|
325
|
+
return 0;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// src/commands/login.ts
|
|
329
|
+
import { parseArgs as parseArgs3 } from "node:util";
|
|
330
|
+
|
|
153
331
|
// src/util/open.ts
|
|
154
332
|
import { spawn } from "node:child_process";
|
|
155
333
|
function openBrowser(url) {
|
|
@@ -166,7 +344,7 @@ function openBrowser(url) {
|
|
|
166
344
|
}
|
|
167
345
|
|
|
168
346
|
// src/commands/login.ts
|
|
169
|
-
var
|
|
347
|
+
var HELP3 = `vellum login — authenticate this machine to a Vellum server
|
|
170
348
|
|
|
171
349
|
Opens your browser to approve a CLI login, then stores a token under
|
|
172
350
|
~/.config/vellum/config.json so future commands authenticate as you.
|
|
@@ -180,7 +358,7 @@ Options:
|
|
|
180
358
|
-h, --help Show this help`;
|
|
181
359
|
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
182
360
|
async function loginCommand(argv) {
|
|
183
|
-
const { values } =
|
|
361
|
+
const { values } = parseArgs3({
|
|
184
362
|
args: argv,
|
|
185
363
|
options: {
|
|
186
364
|
url: { type: "string" },
|
|
@@ -189,7 +367,7 @@ async function loginCommand(argv) {
|
|
|
189
367
|
}
|
|
190
368
|
});
|
|
191
369
|
if (values.help) {
|
|
192
|
-
console.log(
|
|
370
|
+
console.log(HELP3);
|
|
193
371
|
return 0;
|
|
194
372
|
}
|
|
195
373
|
const baseUrl = resolveBaseUrl({ url: values.url });
|
|
@@ -229,8 +407,8 @@ To authorize this CLI, visit:
|
|
|
229
407
|
}
|
|
230
408
|
|
|
231
409
|
// src/commands/logout.ts
|
|
232
|
-
import { parseArgs as
|
|
233
|
-
var
|
|
410
|
+
import { parseArgs as parseArgs4 } from "node:util";
|
|
411
|
+
var HELP4 = `vellum logout — remove this machine's stored CLI token
|
|
234
412
|
|
|
235
413
|
Usage:
|
|
236
414
|
vellum logout [options]
|
|
@@ -239,7 +417,7 @@ Options:
|
|
|
239
417
|
--url <url> Server base URL (env: VELLUM_URL)
|
|
240
418
|
-h, --help Show this help`;
|
|
241
419
|
async function logoutCommand(argv) {
|
|
242
|
-
const { values } =
|
|
420
|
+
const { values } = parseArgs4({
|
|
243
421
|
args: argv,
|
|
244
422
|
options: {
|
|
245
423
|
url: { type: "string" },
|
|
@@ -247,7 +425,7 @@ async function logoutCommand(argv) {
|
|
|
247
425
|
}
|
|
248
426
|
});
|
|
249
427
|
if (values.help) {
|
|
250
|
-
console.log(
|
|
428
|
+
console.log(HELP4);
|
|
251
429
|
return 0;
|
|
252
430
|
}
|
|
253
431
|
const baseUrl = resolveBaseUrl({ url: values.url });
|
|
@@ -263,10 +441,170 @@ async function logoutCommand(argv) {
|
|
|
263
441
|
return 0;
|
|
264
442
|
}
|
|
265
443
|
|
|
444
|
+
// src/commands/markup.ts
|
|
445
|
+
import { parseArgs as parseArgs5 } from "node:util";
|
|
446
|
+
var HELP5 = `vellum markup — pin a comment to a passage of a document
|
|
447
|
+
|
|
448
|
+
Usage:
|
|
449
|
+
vellum markup <page-id> --quote "<text>" --body "<note>" [options]
|
|
450
|
+
echo "<note>" | vellum markup <page-id> --quote "<text>"
|
|
451
|
+
|
|
452
|
+
The --quote text must appear verbatim in the document; the viewer highlights it
|
|
453
|
+
and links the highlight to your note. Without --version, the page's current
|
|
454
|
+
version is used.
|
|
455
|
+
|
|
456
|
+
Options:
|
|
457
|
+
--quote <text> Passage to mark up (must match the document text)
|
|
458
|
+
--body <note> Your note (or pipe it via stdin)
|
|
459
|
+
--version <n> Pin to a specific version number (default: current)
|
|
460
|
+
--force Post even if the quote isn't found in the version
|
|
461
|
+
--url <url> Server base URL (env: VELLUM_URL)
|
|
462
|
+
--api-key <key> Shared API key (env: VELLUM_API_KEY)
|
|
463
|
+
--json Print the raw JSON response
|
|
464
|
+
-h, --help Show this help`;
|
|
465
|
+
async function readStdin() {
|
|
466
|
+
const chunks = [];
|
|
467
|
+
for await (const chunk of process.stdin)
|
|
468
|
+
chunks.push(chunk);
|
|
469
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
470
|
+
}
|
|
471
|
+
function resolveVersion2(page, requested) {
|
|
472
|
+
if (requested != null) {
|
|
473
|
+
return page.versions.find((v) => v.version_number === requested) ?? null;
|
|
474
|
+
}
|
|
475
|
+
const current = page.current_version_id ? page.versions.find((v) => v.id === page.current_version_id) : undefined;
|
|
476
|
+
return current ?? page.versions[page.versions.length - 1] ?? null;
|
|
477
|
+
}
|
|
478
|
+
function extractText(html) {
|
|
479
|
+
return html.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, "").replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, "").replace(/<[^>]+>/g, "").replace(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'");
|
|
480
|
+
}
|
|
481
|
+
var collapse = (s) => s.replace(/\s+/g, " ").trim();
|
|
482
|
+
function quoteIsAnchorable(html, quote) {
|
|
483
|
+
const text = extractText(html);
|
|
484
|
+
return text.includes(quote) || collapse(text).includes(collapse(quote));
|
|
485
|
+
}
|
|
486
|
+
async function markupCommand(argv) {
|
|
487
|
+
const { values, positionals } = parseArgs5({
|
|
488
|
+
args: argv,
|
|
489
|
+
allowPositionals: true,
|
|
490
|
+
options: {
|
|
491
|
+
quote: { type: "string" },
|
|
492
|
+
body: { type: "string" },
|
|
493
|
+
version: { type: "string" },
|
|
494
|
+
force: { type: "boolean", default: false },
|
|
495
|
+
url: { type: "string" },
|
|
496
|
+
"api-key": { type: "string" },
|
|
497
|
+
json: { type: "boolean", default: false },
|
|
498
|
+
help: { type: "boolean", short: "h", default: false }
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
if (values.help) {
|
|
502
|
+
console.log(HELP5);
|
|
503
|
+
return 0;
|
|
504
|
+
}
|
|
505
|
+
const pageId = positionals[0];
|
|
506
|
+
if (!pageId) {
|
|
507
|
+
console.error(`Error: missing <page-id>.
|
|
508
|
+
`);
|
|
509
|
+
console.error(HELP5);
|
|
510
|
+
return 1;
|
|
511
|
+
}
|
|
512
|
+
const quote = (values.quote ?? "").trim();
|
|
513
|
+
if (!quote) {
|
|
514
|
+
console.error("Error: --quote is required (the passage to mark up).");
|
|
515
|
+
return 1;
|
|
516
|
+
}
|
|
517
|
+
const body = (values.body ?? await readStdin()).trim();
|
|
518
|
+
if (!body) {
|
|
519
|
+
console.error("Error: no note provided (pass --body or pipe it via stdin).");
|
|
520
|
+
return 1;
|
|
521
|
+
}
|
|
522
|
+
let version;
|
|
523
|
+
if (values.version != null) {
|
|
524
|
+
version = Number.parseInt(values.version, 10);
|
|
525
|
+
if (!Number.isInteger(version) || version < 1) {
|
|
526
|
+
console.error("Error: --version must be a positive integer.");
|
|
527
|
+
return 1;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
const flags = { url: values.url, apiKey: values["api-key"] };
|
|
531
|
+
const baseUrl = resolveBaseUrl(flags);
|
|
532
|
+
const credential = await resolveCredential(flags, baseUrl);
|
|
533
|
+
const client2 = new VellumClient({ baseUrl, ...credential });
|
|
534
|
+
const page = await client2.getPage(pageId);
|
|
535
|
+
const target = resolveVersion2(page, version);
|
|
536
|
+
if (!target) {
|
|
537
|
+
console.error(version != null ? `Error: page has no version ${version}.` : "Error: page has no versions to mark up.");
|
|
538
|
+
return 1;
|
|
539
|
+
}
|
|
540
|
+
if (!values.force) {
|
|
541
|
+
const html = await fetch(target.raw_url).then((r) => r.ok ? r.text() : "");
|
|
542
|
+
if (!quoteIsAnchorable(html, quote)) {
|
|
543
|
+
console.error(`Error: --quote was not found in v${target.version_number}; the highlight
|
|
544
|
+
` + " would not appear. Check the wording, or pass --force to post anyway.");
|
|
545
|
+
return 1;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
const anchor = { type: "text-quote", exact: quote };
|
|
549
|
+
const comment = await client2.createComment(pageId, {
|
|
550
|
+
versionId: target.id,
|
|
551
|
+
body,
|
|
552
|
+
anchor
|
|
553
|
+
});
|
|
554
|
+
if (values.json) {
|
|
555
|
+
console.log(JSON.stringify(comment, null, 2));
|
|
556
|
+
} else {
|
|
557
|
+
console.log("✓ markup added");
|
|
558
|
+
console.log(` on: “${quote.length > 80 ? `${quote.slice(0, 79)}…` : quote}”`);
|
|
559
|
+
console.log(` view: ${page.view_url}`);
|
|
560
|
+
}
|
|
561
|
+
return 0;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// src/commands/open.ts
|
|
565
|
+
import { parseArgs as parseArgs6 } from "node:util";
|
|
566
|
+
var HELP6 = `vellum open — print (and open) a document's view URL
|
|
567
|
+
|
|
568
|
+
Usage:
|
|
569
|
+
vellum open <page-id> [options]
|
|
570
|
+
|
|
571
|
+
Options:
|
|
572
|
+
--no-browser Just print the URL; don't open the browser
|
|
573
|
+
--url <url> Server base URL (env: VELLUM_URL)
|
|
574
|
+
-h, --help Show this help`;
|
|
575
|
+
async function openCommand(argv) {
|
|
576
|
+
const { values, positionals } = parseArgs6({
|
|
577
|
+
args: argv,
|
|
578
|
+
allowPositionals: true,
|
|
579
|
+
options: {
|
|
580
|
+
"no-browser": { type: "boolean", default: false },
|
|
581
|
+
url: { type: "string" },
|
|
582
|
+
help: { type: "boolean", short: "h", default: false }
|
|
583
|
+
}
|
|
584
|
+
});
|
|
585
|
+
if (values.help) {
|
|
586
|
+
console.log(HELP6);
|
|
587
|
+
return 0;
|
|
588
|
+
}
|
|
589
|
+
const pageId = positionals[0];
|
|
590
|
+
if (!pageId) {
|
|
591
|
+
console.error(`Error: missing <page-id>.
|
|
592
|
+
`);
|
|
593
|
+
console.error(HELP6);
|
|
594
|
+
return 1;
|
|
595
|
+
}
|
|
596
|
+
const baseUrl = resolveBaseUrl({ url: values.url });
|
|
597
|
+
const page = await new VellumClient({ baseUrl }).getPage(pageId);
|
|
598
|
+
console.log(page.view_url);
|
|
599
|
+
if (!values["no-browser"])
|
|
600
|
+
openBrowser(page.view_url);
|
|
601
|
+
return 0;
|
|
602
|
+
}
|
|
603
|
+
|
|
266
604
|
// src/commands/push.ts
|
|
267
605
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
268
|
-
import { parseArgs as
|
|
269
|
-
var
|
|
606
|
+
import { parseArgs as parseArgs7 } from "node:util";
|
|
607
|
+
var HELP7 = `vellum push — create a Vellum artifact from HTML
|
|
270
608
|
|
|
271
609
|
Usage:
|
|
272
610
|
vellum push <file.html> [options]
|
|
@@ -278,14 +616,14 @@ Options:
|
|
|
278
616
|
--api-key <key> Shared API key (env: VELLUM_API_KEY)
|
|
279
617
|
--json Print the raw JSON response
|
|
280
618
|
-h, --help Show this help`;
|
|
281
|
-
async function
|
|
619
|
+
async function readStdin2() {
|
|
282
620
|
const chunks = [];
|
|
283
621
|
for await (const chunk of process.stdin)
|
|
284
622
|
chunks.push(chunk);
|
|
285
623
|
return Buffer.concat(chunks).toString("utf8");
|
|
286
624
|
}
|
|
287
625
|
async function pushCommand(argv) {
|
|
288
|
-
const { values, positionals } =
|
|
626
|
+
const { values, positionals } = parseArgs7({
|
|
289
627
|
args: argv,
|
|
290
628
|
allowPositionals: true,
|
|
291
629
|
options: {
|
|
@@ -297,11 +635,11 @@ async function pushCommand(argv) {
|
|
|
297
635
|
}
|
|
298
636
|
});
|
|
299
637
|
if (values.help) {
|
|
300
|
-
console.log(
|
|
638
|
+
console.log(HELP7);
|
|
301
639
|
return 0;
|
|
302
640
|
}
|
|
303
641
|
const file = positionals[0];
|
|
304
|
-
const html = file ? await readFile2(file, "utf8") : await
|
|
642
|
+
const html = file ? await readFile2(file, "utf8") : await readStdin2();
|
|
305
643
|
if (!html.trim()) {
|
|
306
644
|
console.error("Error: no HTML provided (empty file or stdin).");
|
|
307
645
|
return 1;
|
|
@@ -321,9 +659,143 @@ async function pushCommand(argv) {
|
|
|
321
659
|
return 0;
|
|
322
660
|
}
|
|
323
661
|
|
|
662
|
+
// src/commands/reply.ts
|
|
663
|
+
import { parseArgs as parseArgs8 } from "node:util";
|
|
664
|
+
var HELP8 = `vellum reply — reply to a comment thread
|
|
665
|
+
|
|
666
|
+
Usage:
|
|
667
|
+
vellum reply <page-id> <comment-id> --body "<note>"
|
|
668
|
+
echo "<note>" | vellum reply <page-id> <comment-id>
|
|
669
|
+
|
|
670
|
+
The reply is threaded under <comment-id> and inherits the parent's version.
|
|
671
|
+
Run \`vellum comments <page-id>\` to find the page and comment ids.
|
|
672
|
+
|
|
673
|
+
Options:
|
|
674
|
+
--body <note> Your reply (or pipe it via stdin)
|
|
675
|
+
--url <url> Server base URL (env: VELLUM_URL)
|
|
676
|
+
--api-key <key> Shared API key (env: VELLUM_API_KEY)
|
|
677
|
+
--json Print the raw JSON response
|
|
678
|
+
-h, --help Show this help`;
|
|
679
|
+
async function readStdin3() {
|
|
680
|
+
const chunks = [];
|
|
681
|
+
for await (const chunk of process.stdin)
|
|
682
|
+
chunks.push(chunk);
|
|
683
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
684
|
+
}
|
|
685
|
+
function flatten(comments) {
|
|
686
|
+
const out = [];
|
|
687
|
+
const walk = (c) => {
|
|
688
|
+
out.push(c);
|
|
689
|
+
for (const reply of c.replies ?? [])
|
|
690
|
+
walk(reply);
|
|
691
|
+
};
|
|
692
|
+
for (const c of comments)
|
|
693
|
+
walk(c);
|
|
694
|
+
return out;
|
|
695
|
+
}
|
|
696
|
+
async function replyCommand(argv) {
|
|
697
|
+
const { values, positionals } = parseArgs8({
|
|
698
|
+
args: argv,
|
|
699
|
+
allowPositionals: true,
|
|
700
|
+
options: {
|
|
701
|
+
body: { type: "string" },
|
|
702
|
+
url: { type: "string" },
|
|
703
|
+
"api-key": { type: "string" },
|
|
704
|
+
json: { type: "boolean", default: false },
|
|
705
|
+
help: { type: "boolean", short: "h", default: false }
|
|
706
|
+
}
|
|
707
|
+
});
|
|
708
|
+
if (values.help) {
|
|
709
|
+
console.log(HELP8);
|
|
710
|
+
return 0;
|
|
711
|
+
}
|
|
712
|
+
const [pageId, commentId] = positionals;
|
|
713
|
+
if (!pageId || !commentId) {
|
|
714
|
+
console.error("Error: usage is `vellum reply <page-id> <comment-id>`.\n");
|
|
715
|
+
console.error(HELP8);
|
|
716
|
+
return 1;
|
|
717
|
+
}
|
|
718
|
+
const body = (values.body ?? await readStdin3()).trim();
|
|
719
|
+
if (!body) {
|
|
720
|
+
console.error("Error: no note provided (pass --body or pipe it via stdin).");
|
|
721
|
+
return 1;
|
|
722
|
+
}
|
|
723
|
+
const flags = { url: values.url, apiKey: values["api-key"] };
|
|
724
|
+
const baseUrl = resolveBaseUrl(flags);
|
|
725
|
+
const credential = await resolveCredential(flags, baseUrl);
|
|
726
|
+
const client2 = new VellumClient({ baseUrl, ...credential });
|
|
727
|
+
const parent = flatten(await client2.listComments(pageId)).find((c) => c.id === commentId);
|
|
728
|
+
if (!parent) {
|
|
729
|
+
console.error(`Error: no comment ${commentId} on page ${pageId}.`);
|
|
730
|
+
return 1;
|
|
731
|
+
}
|
|
732
|
+
if (!parent.version_id) {
|
|
733
|
+
console.error("Error: parent comment has no version to reply on.");
|
|
734
|
+
return 1;
|
|
735
|
+
}
|
|
736
|
+
const reply = await client2.createComment(pageId, {
|
|
737
|
+
versionId: parent.version_id,
|
|
738
|
+
body,
|
|
739
|
+
parentId: commentId
|
|
740
|
+
});
|
|
741
|
+
if (values.json) {
|
|
742
|
+
console.log(JSON.stringify(reply, null, 2));
|
|
743
|
+
} else {
|
|
744
|
+
console.log(`✓ replied to ${commentId}`);
|
|
745
|
+
}
|
|
746
|
+
return 0;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// src/commands/resolve.ts
|
|
750
|
+
import { parseArgs as parseArgs9 } from "node:util";
|
|
751
|
+
var HELP9 = `vellum resolve — mark a comment as resolved
|
|
752
|
+
|
|
753
|
+
Usage:
|
|
754
|
+
vellum resolve <comment-id> [options]
|
|
755
|
+
|
|
756
|
+
Options:
|
|
757
|
+
--url <url> Server base URL (env: VELLUM_URL)
|
|
758
|
+
--api-key <key> Shared API key (env: VELLUM_API_KEY)
|
|
759
|
+
--json Print the raw JSON response
|
|
760
|
+
-h, --help Show this help`;
|
|
761
|
+
async function resolveCommand(argv) {
|
|
762
|
+
const { values, positionals } = parseArgs9({
|
|
763
|
+
args: argv,
|
|
764
|
+
allowPositionals: true,
|
|
765
|
+
options: {
|
|
766
|
+
url: { type: "string" },
|
|
767
|
+
"api-key": { type: "string" },
|
|
768
|
+
json: { type: "boolean", default: false },
|
|
769
|
+
help: { type: "boolean", short: "h", default: false }
|
|
770
|
+
}
|
|
771
|
+
});
|
|
772
|
+
if (values.help) {
|
|
773
|
+
console.log(HELP9);
|
|
774
|
+
return 0;
|
|
775
|
+
}
|
|
776
|
+
const commentId = positionals[0];
|
|
777
|
+
if (!commentId) {
|
|
778
|
+
console.error(`Error: missing <comment-id>.
|
|
779
|
+
`);
|
|
780
|
+
console.error(HELP9);
|
|
781
|
+
return 1;
|
|
782
|
+
}
|
|
783
|
+
const flags = { url: values.url, apiKey: values["api-key"] };
|
|
784
|
+
const baseUrl = resolveBaseUrl(flags);
|
|
785
|
+
const credential = await resolveCredential(flags, baseUrl);
|
|
786
|
+
const client2 = new VellumClient({ baseUrl, ...credential });
|
|
787
|
+
const comment = await client2.resolveComment(commentId);
|
|
788
|
+
if (values.json) {
|
|
789
|
+
console.log(JSON.stringify(comment, null, 2));
|
|
790
|
+
} else {
|
|
791
|
+
console.log(`✓ resolved ${comment.id}`);
|
|
792
|
+
}
|
|
793
|
+
return 0;
|
|
794
|
+
}
|
|
795
|
+
|
|
324
796
|
// src/commands/whoami.ts
|
|
325
|
-
import { parseArgs as
|
|
326
|
-
var
|
|
797
|
+
import { parseArgs as parseArgs10 } from "node:util";
|
|
798
|
+
var HELP10 = `vellum whoami — show the identity the CLI is authenticated as
|
|
327
799
|
|
|
328
800
|
Usage:
|
|
329
801
|
vellum whoami [options]
|
|
@@ -333,7 +805,7 @@ Options:
|
|
|
333
805
|
--api-key <key> Shared API key (env: VELLUM_API_KEY)
|
|
334
806
|
-h, --help Show this help`;
|
|
335
807
|
async function whoamiCommand(argv) {
|
|
336
|
-
const { values } =
|
|
808
|
+
const { values } = parseArgs10({
|
|
337
809
|
args: argv,
|
|
338
810
|
options: {
|
|
339
811
|
url: { type: "string" },
|
|
@@ -342,7 +814,7 @@ async function whoamiCommand(argv) {
|
|
|
342
814
|
}
|
|
343
815
|
});
|
|
344
816
|
if (values.help) {
|
|
345
|
-
console.log(
|
|
817
|
+
console.log(HELP10);
|
|
346
818
|
return 0;
|
|
347
819
|
}
|
|
348
820
|
const baseUrl = resolveBaseUrl({ url: values.url });
|
|
@@ -358,7 +830,7 @@ async function whoamiCommand(argv) {
|
|
|
358
830
|
}
|
|
359
831
|
|
|
360
832
|
// src/index.ts
|
|
361
|
-
var
|
|
833
|
+
var HELP11 = `vellum — CLI for the Vellum artifact store
|
|
362
834
|
|
|
363
835
|
Usage:
|
|
364
836
|
vellum <command> [options]
|
|
@@ -368,25 +840,37 @@ Commands:
|
|
|
368
840
|
logout Remove this machine's stored CLI token
|
|
369
841
|
whoami Show the identity the CLI is authenticated as
|
|
370
842
|
push Create an artifact (page) from HTML (file or stdin)
|
|
843
|
+
get Print a document's raw HTML
|
|
844
|
+
open Print (and open) a document's view URL
|
|
845
|
+
markup Pin a comment to a passage of a document
|
|
846
|
+
comments List the comments on a document
|
|
847
|
+
reply Reply to a comment thread
|
|
848
|
+
resolve Mark a comment as resolved
|
|
371
849
|
|
|
372
850
|
Run 'vellum <command> --help' for command-specific options.`;
|
|
373
851
|
var commands = {
|
|
374
852
|
login: loginCommand,
|
|
375
853
|
logout: logoutCommand,
|
|
376
854
|
whoami: whoamiCommand,
|
|
377
|
-
push: pushCommand
|
|
855
|
+
push: pushCommand,
|
|
856
|
+
get: getCommand,
|
|
857
|
+
open: openCommand,
|
|
858
|
+
markup: markupCommand,
|
|
859
|
+
comments: commentsCommand,
|
|
860
|
+
reply: replyCommand,
|
|
861
|
+
resolve: resolveCommand
|
|
378
862
|
};
|
|
379
863
|
async function main() {
|
|
380
864
|
const [, , cmd, ...rest] = process.argv;
|
|
381
865
|
if (!cmd || cmd === "-h" || cmd === "--help" || cmd === "help") {
|
|
382
|
-
console.log(
|
|
866
|
+
console.log(HELP11);
|
|
383
867
|
return cmd ? 0 : 1;
|
|
384
868
|
}
|
|
385
869
|
const handler = commands[cmd];
|
|
386
870
|
if (!handler) {
|
|
387
871
|
console.error(`Unknown command: ${cmd}
|
|
388
872
|
`);
|
|
389
|
-
console.error(
|
|
873
|
+
console.error(HELP11);
|
|
390
874
|
return 1;
|
|
391
875
|
}
|
|
392
876
|
return handler(rest);
|