@letterstory/cli 0.121.4 → 0.121.5
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/README.md +11 -0
- package/lib/cli.mjs +31 -2
- package/lib/commands/autopen.mjs +3 -0
- package/lib/commands/posts.mjs +203 -3
- package/lib/commands/reviewers.mjs +51 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -146,6 +146,17 @@ letterstory posts list [--limit <n>] [--collection <uuid>]
|
|
|
146
146
|
letterstory posts show <article-id>
|
|
147
147
|
letterstory posts new --title "…" --collection <uuid> --body "…" [--article-id <uuid>] # or --file <path|->; --article-id updates that article instead of creating one
|
|
148
148
|
letterstory posts new --title "…" --collection <uuid> --body "…" --context "Internal data from 120 accounts: disputes fell 38%." # your own data: stated in the post with your attribution
|
|
149
|
+
letterstory posts update <article-id> --title "…" --summary "…" --tag seo --tag ai # only what you pass changes; --body/--file for the HTML body
|
|
150
|
+
letterstory posts edit <article-id> --instruction "Tighten the intro" # AI rewrite, saved as your edit
|
|
151
|
+
letterstory posts mentions <article-id> # re-apply the brand-mention & competitor policy
|
|
152
|
+
letterstory posts history <article-id>
|
|
153
|
+
letterstory posts restore <article-id> <version> # comes back as the newest version
|
|
154
|
+
letterstory posts export <article-id> --format pdf --out post.pdf # or --format md (prints the Markdown)
|
|
155
|
+
letterstory posts comments <article-id>
|
|
156
|
+
letterstory posts comments add <article-id> --body "Source?" --quote "grew 40%"
|
|
157
|
+
letterstory posts comments update <article-id> <comment-id> --resolve # or --reopen, --body "…"
|
|
158
|
+
letterstory posts comments resolve-ai <article-id> <comment-id> # AI makes the change the comment asks for
|
|
159
|
+
letterstory reviewers invite <article-id> --email editor@client.com --name "Sam" # a guest reviewer: link + PIN by email
|
|
149
160
|
letterstory posts publish <article-id>
|
|
150
161
|
letterstory posts unpublish <article-id>
|
|
151
162
|
letterstory posts delete --id <uuid> [--id <uuid> ...] # permanent; content, history, and any published copy are all removed
|
package/lib/cli.mjs
CHANGED
|
@@ -60,7 +60,7 @@ import {
|
|
|
60
60
|
} from "./commands.mjs";
|
|
61
61
|
|
|
62
62
|
// Keep in sync with cli/package.json.
|
|
63
|
-
export const VERSION = "0.121.
|
|
63
|
+
export const VERSION = "0.121.5";
|
|
64
64
|
|
|
65
65
|
// Flags that never take a value. Listing them explicitly means `deploy get --json <id>`
|
|
66
66
|
// can't accidentally swallow the id as --json's value. Every `type: "bool"` flag (and its
|
|
@@ -136,6 +136,9 @@ export const BOOLEAN_FLAGS = new Set([
|
|
|
136
136
|
"stock-only",
|
|
137
137
|
"gate",
|
|
138
138
|
"no-verify",
|
|
139
|
+
// `posts comments update <article-id> <comment-id> --resolve | --reopen`.
|
|
140
|
+
"resolve",
|
|
141
|
+
"reopen",
|
|
139
142
|
"skip",
|
|
140
143
|
// `phantom-onboard domain` / `admin create-org --onboard` switches.
|
|
141
144
|
"watch",
|
|
@@ -185,6 +188,8 @@ export const BOOLEAN_FLAGS = new Set([
|
|
|
185
188
|
"retired",
|
|
186
189
|
"retire",
|
|
187
190
|
"restore",
|
|
191
|
+
// `posts list --mine`: only your posts.
|
|
192
|
+
"mine",
|
|
188
193
|
// `collection add "<headline>" --collection <id> --queue`.
|
|
189
194
|
"queue",
|
|
190
195
|
// Bool flags from command ARGS maps that were missing here.
|
|
@@ -376,9 +381,29 @@ Phantomstory (a collection Letterstory hosts as an independent publication):
|
|
|
376
381
|
phantom delete <phantom-id> --yes
|
|
377
382
|
|
|
378
383
|
Content:
|
|
379
|
-
posts list [--limit <n>] [--collection <uuid>]
|
|
384
|
+
posts list [--limit <n>] [--offset <n>] [--collection <uuid>] [--search <words>] [--mine]
|
|
385
|
+
List your articles
|
|
380
386
|
posts show <article-id> Show one article
|
|
381
387
|
posts new --title <t> --collection <uuid> (--body <text> | --file <path|->) [--article-id <uuid>] [--context <text>]
|
|
388
|
+
posts update <article-id> [--title <t>] [--body <html> | --file <path|->] [--status draft|published|archived]
|
|
389
|
+
[--summary <dek>] [--tag <t> ...] [--owner <user-id> ...] [--due <YYYY-MM-DD>] [--author <name>]
|
|
390
|
+
[--author-id <id>] [--slug <s>] [--meta-title <t>] [--meta-description <d>] [--published-url <url>]
|
|
391
|
+
[--template <key|none|inherit>] [--mention off|subtle|woven|inherit] [--direction-gate on|off|inherit]
|
|
392
|
+
[--paper-trail '<json>'] [--metadata-json '<json>']
|
|
393
|
+
Edit a post; only what you pass changes
|
|
394
|
+
posts edit <article-id> --instruction "<what to change>"
|
|
395
|
+
Rewrite the post with AI from an instruction
|
|
396
|
+
posts mentions <article-id> Re-apply the brand-mention & competitor policy
|
|
397
|
+
posts history <article-id> The post's versions, newest first
|
|
398
|
+
posts restore <article-id> <version> Bring a version back as the latest
|
|
399
|
+
posts export <article-id> --format md|pdf [--out <path>]
|
|
400
|
+
The draft as Markdown or PDF
|
|
401
|
+
posts comments <article-id> The post's comments and replies
|
|
402
|
+
posts comments add <article-id> --body <text> [--quote <text> [--prefix <t>] [--suffix <t>]]
|
|
403
|
+
[--kind note|ai_esque] [--scope sentence|document] [--reply-to <comment-id>] [--mention <user-id> ...]
|
|
404
|
+
posts comments update <article-id> <comment-id> [--body <text>] [--resolve | --reopen] [--mention <user-id> ...]
|
|
405
|
+
posts comments delete <article-id> <comment-id>
|
|
406
|
+
posts comments resolve-ai <article-id> <comment-id> Make the change the comment asks for, with AI
|
|
382
407
|
posts publish <article-id> Publish an article
|
|
383
408
|
posts unpublish <article-id> Unpublish an article
|
|
384
409
|
posts delete --id <uuid> [--id <uuid> ...] Permanently delete article(s)
|
|
@@ -391,6 +416,10 @@ Reviewers:
|
|
|
391
416
|
guest reviewer on every future
|
|
392
417
|
finished draft (no email sent now)
|
|
393
418
|
reviewers standing remove <reviewer-id> Stop future auto-invites
|
|
419
|
+
reviewers assigned Posts waiting on your review, across letterbases
|
|
420
|
+
reviewers invite <article-id> --email <e> [--name <n>]
|
|
421
|
+
Invite a guest to review one post
|
|
422
|
+
(emails them a link and a PIN)
|
|
394
423
|
|
|
395
424
|
Collections:
|
|
396
425
|
collections list List your collections
|
package/lib/commands/autopen.mjs
CHANGED
|
@@ -20,6 +20,9 @@ export const ARGS = {
|
|
|
20
20
|
from_scratch: { flag: "from-scratch", type: "bool" },
|
|
21
21
|
// --skip finish,metadata: leave those steps out of this run only.
|
|
22
22
|
skip_steps: { flag: "skip", type: "list" },
|
|
23
|
+
// --source <url> … / --must-include <t> …: what this run's research reads first and covers.
|
|
24
|
+
source_urls: { flag: "source", type: "list" },
|
|
25
|
+
must_include: { flag: "must-include", type: "list" },
|
|
23
26
|
},
|
|
24
27
|
};
|
|
25
28
|
|
package/lib/commands/posts.mjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Ported from phantomstory-cli's posts.ts — same tool calls, same shape, adapted to
|
|
3
3
|
// this CLI's flag/positional/io conventions (no commander, no color).
|
|
4
4
|
|
|
5
|
+
import { writeFileSync } from "node:fs";
|
|
5
6
|
import { CliError } from "../client.mjs";
|
|
6
7
|
import {
|
|
7
8
|
flagStr,
|
|
@@ -13,16 +14,130 @@ import {
|
|
|
13
14
|
ok,
|
|
14
15
|
readBodyInput,
|
|
15
16
|
argsFromFlags,
|
|
17
|
+
refuseUnknownFlags,
|
|
16
18
|
} from "./shared.mjs";
|
|
17
19
|
|
|
20
|
+
const ARTICLE = { positional: 0, label: "article-id", required: true };
|
|
21
|
+
const COMMENT = { positional: 1, label: "comment-id", required: true };
|
|
22
|
+
|
|
23
|
+
/** `--<name> '<json>'` → a JSON object, or undefined when the flag is not set. */
|
|
24
|
+
function jsonObjectFlag(flags, name) {
|
|
25
|
+
const raw = flagStr(flags[name]);
|
|
26
|
+
if (raw === undefined) return undefined;
|
|
27
|
+
let parsed;
|
|
28
|
+
try {
|
|
29
|
+
parsed = JSON.parse(raw);
|
|
30
|
+
} catch (err) {
|
|
31
|
+
throw new CliError(`--${name} is not valid JSON: ${err.message}`);
|
|
32
|
+
}
|
|
33
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
34
|
+
throw new CliError(`--${name} must be a JSON object`);
|
|
35
|
+
return parsed;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** A per-post override: a value, or `inherit` (null: back to the collection's). */
|
|
39
|
+
function overrideFlag(flags, name, allowed) {
|
|
40
|
+
const raw = flagStr(flags[name]);
|
|
41
|
+
if (raw === undefined) return undefined;
|
|
42
|
+
if (raw === "inherit") return null;
|
|
43
|
+
if (allowed && !allowed.includes(raw))
|
|
44
|
+
throw new CliError(`--${name} must be one of: ${[...allowed, "inherit"].join(", ")}`);
|
|
45
|
+
return raw;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** `--direction-gate on|off|inherit`. */
|
|
49
|
+
function directionGateFlag(flags) {
|
|
50
|
+
const raw = overrideFlag(flags, "direction-gate", ["on", "off"]);
|
|
51
|
+
return raw === undefined || raw === null ? raw : raw === "on";
|
|
52
|
+
}
|
|
53
|
+
|
|
18
54
|
/** See `argsFromFlags` in shared.mjs; `scripts/parity-check.ts` diffs these keys against the tool. */
|
|
19
55
|
export const ARGS = {
|
|
20
56
|
publish_article: {
|
|
21
|
-
article_id:
|
|
57
|
+
article_id: ARTICLE,
|
|
22
58
|
},
|
|
23
59
|
delete_article: {
|
|
24
60
|
article_ids: { flag: "id", type: "list", required: true, label: "id" },
|
|
25
61
|
},
|
|
62
|
+
update_article: {
|
|
63
|
+
article_id: ARTICLE,
|
|
64
|
+
title: "title",
|
|
65
|
+
content: { value: (flags) => readBodyInput(flags) },
|
|
66
|
+
status: { flag: "status", enum: ["draft", "published", "archived"] },
|
|
67
|
+
metadata: { value: (flags) => jsonObjectFlag(flags, "metadata-json") },
|
|
68
|
+
metadataPatch: {
|
|
69
|
+
object: {
|
|
70
|
+
summary: "summary",
|
|
71
|
+
tags: { flag: "tag", type: "list" },
|
|
72
|
+
owners: { flag: "owner", type: "list" },
|
|
73
|
+
due_date: "due",
|
|
74
|
+
author: "author",
|
|
75
|
+
author_id: "author-id",
|
|
76
|
+
slug: "slug",
|
|
77
|
+
meta_title: "meta-title",
|
|
78
|
+
meta_description: "meta-description",
|
|
79
|
+
published_url: "published-url",
|
|
80
|
+
template_key: { value: (flags) => overrideFlag(flags, "template") },
|
|
81
|
+
mention_degree: { value: (flags) => overrideFlag(flags, "mention", ["off", "subtle", "woven"]) },
|
|
82
|
+
direction_gate: { value: (flags) => directionGateFlag(flags) },
|
|
83
|
+
paper_trail: { value: (flags) => jsonObjectFlag(flags, "paper-trail") },
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
edit_article_with_prompt: {
|
|
88
|
+
article_id: ARTICLE,
|
|
89
|
+
instruction: { flag: "instruction", required: true },
|
|
90
|
+
},
|
|
91
|
+
run_mention_pass: {
|
|
92
|
+
article_id: ARTICLE,
|
|
93
|
+
},
|
|
94
|
+
get_article_history: {
|
|
95
|
+
article_id: ARTICLE,
|
|
96
|
+
},
|
|
97
|
+
restore_article_version: {
|
|
98
|
+
article_id: ARTICLE,
|
|
99
|
+
// Positional, not `--version`: that is the global "print the CLI version" switch.
|
|
100
|
+
version: {
|
|
101
|
+
value: (_flags, positionals) => {
|
|
102
|
+
const n = flagNum(positionals[1]);
|
|
103
|
+
if (n === undefined) throw new CliError("Missing required <version>");
|
|
104
|
+
return n;
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
export_article: {
|
|
109
|
+
article_id: ARTICLE,
|
|
110
|
+
format: { flag: "format", required: true, enum: ["md", "pdf"] },
|
|
111
|
+
},
|
|
112
|
+
list_article_comments: {
|
|
113
|
+
article_id: ARTICLE,
|
|
114
|
+
},
|
|
115
|
+
add_article_comment: {
|
|
116
|
+
article_id: ARTICLE,
|
|
117
|
+
body: { flag: "body", required: true },
|
|
118
|
+
kind: { flag: "kind", enum: ["note", "ai_esque"] },
|
|
119
|
+
scope: { flag: "scope", enum: ["sentence", "document"] },
|
|
120
|
+
parent_id: "reply-to",
|
|
121
|
+
quote: "quote",
|
|
122
|
+
quote_prefix: "prefix",
|
|
123
|
+
quote_suffix: "suffix",
|
|
124
|
+
mentioned_user_ids: { flag: "mention", type: "list" },
|
|
125
|
+
},
|
|
126
|
+
update_article_comment: {
|
|
127
|
+
article_id: ARTICLE,
|
|
128
|
+
comment_id: COMMENT,
|
|
129
|
+
body: "body",
|
|
130
|
+
resolved: { flag: "resolve", type: "bool", negate: "reopen" },
|
|
131
|
+
mentioned_user_ids: { flag: "mention", type: "list" },
|
|
132
|
+
},
|
|
133
|
+
delete_article_comment: {
|
|
134
|
+
article_id: ARTICLE,
|
|
135
|
+
comment_id: COMMENT,
|
|
136
|
+
},
|
|
137
|
+
resolve_comment_with_ai: {
|
|
138
|
+
article_id: ARTICLE,
|
|
139
|
+
comment_id: COMMENT,
|
|
140
|
+
},
|
|
26
141
|
};
|
|
27
142
|
|
|
28
143
|
export async function cmdPosts(ctx) {
|
|
@@ -36,6 +151,20 @@ export async function cmdPosts(ctx) {
|
|
|
36
151
|
return postsShow(rest);
|
|
37
152
|
case "new":
|
|
38
153
|
return postsNew(rest);
|
|
154
|
+
case "update":
|
|
155
|
+
return postsWrite(rest, "update_article", "Updated.");
|
|
156
|
+
case "edit":
|
|
157
|
+
return postsWrite(rest, "edit_article_with_prompt", "Edited.");
|
|
158
|
+
case "mentions":
|
|
159
|
+
return postsWrite(rest, "run_mention_pass", "Mention pass done.");
|
|
160
|
+
case "history":
|
|
161
|
+
return postsRead(rest, "get_article_history");
|
|
162
|
+
case "restore":
|
|
163
|
+
return postsWrite(rest, "restore_article_version", "Restored as the latest version.");
|
|
164
|
+
case "export":
|
|
165
|
+
return postsExport(rest);
|
|
166
|
+
case "comments":
|
|
167
|
+
return postsComments(rest);
|
|
39
168
|
case "publish":
|
|
40
169
|
return postsPublish(rest);
|
|
41
170
|
case "unpublish":
|
|
@@ -45,14 +174,85 @@ export async function cmdPosts(ctx) {
|
|
|
45
174
|
return postsDelete(rest);
|
|
46
175
|
default:
|
|
47
176
|
throw new CliError(
|
|
48
|
-
`Unknown posts subcommand: ${sub ?? "(none)"}. Try: list, show, new, publish, unpublish, delete`
|
|
177
|
+
`Unknown posts subcommand: ${sub ?? "(none)"}. Try: list, show, new, update, edit, mentions, history, restore, export, comments, publish, unpublish, delete`
|
|
49
178
|
);
|
|
50
179
|
}
|
|
51
180
|
}
|
|
52
181
|
|
|
182
|
+
// Each tool named once, literally, so scripts/parity-check.ts sees which command calls it.
|
|
183
|
+
const CALL = {
|
|
184
|
+
update_article: (client, args) => client.callTool("update_article", args),
|
|
185
|
+
edit_article_with_prompt: (client, args) => client.callTool("edit_article_with_prompt", args),
|
|
186
|
+
run_mention_pass: (client, args) => client.callTool("run_mention_pass", args),
|
|
187
|
+
get_article_history: (client, args) => client.callTool("get_article_history", args),
|
|
188
|
+
restore_article_version: (client, args) => client.callTool("restore_article_version", args),
|
|
189
|
+
list_article_comments: (client, args) => client.callTool("list_article_comments", args),
|
|
190
|
+
add_article_comment: (client, args) => client.callTool("add_article_comment", args),
|
|
191
|
+
update_article_comment: (client, args) => client.callTool("update_article_comment", args),
|
|
192
|
+
delete_article_comment: (client, args) => client.callTool("delete_article_comment", args),
|
|
193
|
+
resolve_comment_with_ai: (client, args) => client.callTool("resolve_comment_with_ai", args),
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
/** A write: refuse a flag its ARGS map does not declare, then call the tool. */
|
|
197
|
+
async function postsWrite(ctx, tool, done) {
|
|
198
|
+
const { client, flags, io, positionals } = ctx;
|
|
199
|
+
refuseUnknownFlags(ctx, ARGS[tool]);
|
|
200
|
+
const result = await CALL[tool](client, argsFromFlags(ARGS[tool], flags, positionals));
|
|
201
|
+
ok(ctx, done);
|
|
202
|
+
printResult(io, flags, result);
|
|
203
|
+
return 0;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function postsRead(ctx, tool) {
|
|
207
|
+
const { client, flags, io, positionals } = ctx;
|
|
208
|
+
const result = await CALL[tool](client, argsFromFlags(ARGS[tool], flags, positionals));
|
|
209
|
+
printResult(io, flags, result);
|
|
210
|
+
return 0;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// `--out <path>` writes the file (a PDF's bytes decoded); without it the result prints.
|
|
214
|
+
async function postsExport(ctx) {
|
|
215
|
+
const { client, flags, io, positionals } = ctx;
|
|
216
|
+
const result = await client.callTool("export_article", argsFromFlags(ARGS.export_article, flags, positionals));
|
|
217
|
+
const out = flagStr(flags.out);
|
|
218
|
+
if (out === undefined) {
|
|
219
|
+
if (!flags.json && result?.encoding === "utf8") io.log(result.content);
|
|
220
|
+
else printResult(io, flags, result);
|
|
221
|
+
return 0;
|
|
222
|
+
}
|
|
223
|
+
const bytes = result.encoding === "base64" ? Buffer.from(result.content, "base64") : result.content;
|
|
224
|
+
try {
|
|
225
|
+
writeFileSync(out, bytes);
|
|
226
|
+
} catch (err) {
|
|
227
|
+
throw new CliError(`Could not write --out ${out}: ${err.message}`);
|
|
228
|
+
}
|
|
229
|
+
ok(ctx, `Wrote ${out}.`);
|
|
230
|
+
return 0;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// `posts comments <article-id>` lists; `posts comments add|update|delete|resolve-ai <article-id> …` write.
|
|
234
|
+
async function postsComments(ctx) {
|
|
235
|
+
const verbs = {
|
|
236
|
+
add: ["add_article_comment", "Commented."],
|
|
237
|
+
update: ["update_article_comment", "Updated."],
|
|
238
|
+
delete: ["delete_article_comment", "Deleted."],
|
|
239
|
+
rm: ["delete_article_comment", "Deleted."],
|
|
240
|
+
"resolve-ai": ["resolve_comment_with_ai", "Resolved with AI."],
|
|
241
|
+
};
|
|
242
|
+
const verb = verbs[ctx.positionals[0]];
|
|
243
|
+
if (!verb) return postsRead(ctx, "list_article_comments");
|
|
244
|
+
return postsWrite({ ...ctx, positionals: ctx.positionals.slice(1) }, verb[0], verb[1]);
|
|
245
|
+
}
|
|
246
|
+
|
|
53
247
|
async function postsList(ctx) {
|
|
54
248
|
const { client, flags, io } = ctx;
|
|
55
|
-
const args = compact({
|
|
249
|
+
const args = compact({
|
|
250
|
+
limit: flagNum(flags.limit),
|
|
251
|
+
offset: flagNum(flags.offset),
|
|
252
|
+
collection_id: flagStr(flags.collection),
|
|
253
|
+
search: flagStr(flags.search),
|
|
254
|
+
mine: flags.mine === true ? true : undefined,
|
|
255
|
+
});
|
|
56
256
|
const result = await client.callTool("list_articles", args);
|
|
57
257
|
printResult(io, flags, result);
|
|
58
258
|
return 0;
|
|
@@ -5,7 +5,26 @@
|
|
|
5
5
|
// Same flag/positional/io conventions as posts.mjs — no commander, no color.
|
|
6
6
|
|
|
7
7
|
import { CliError } from "../client.mjs";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
flagStr,
|
|
10
|
+
requireFlag,
|
|
11
|
+
requirePositional,
|
|
12
|
+
printResult,
|
|
13
|
+
compact,
|
|
14
|
+
ok,
|
|
15
|
+
argsFromFlags,
|
|
16
|
+
refuseUnknownFlags,
|
|
17
|
+
} from "./shared.mjs";
|
|
18
|
+
|
|
19
|
+
/** See `argsFromFlags` in shared.mjs; `scripts/parity-check.ts` diffs these keys against the tool. */
|
|
20
|
+
export const ARGS = {
|
|
21
|
+
list_assigned_reviews: {},
|
|
22
|
+
invite_guest_reviewer: {
|
|
23
|
+
article_id: { positional: 0, label: "article-id", required: true },
|
|
24
|
+
email: { flag: "email", required: true },
|
|
25
|
+
name: "name",
|
|
26
|
+
},
|
|
27
|
+
};
|
|
9
28
|
|
|
10
29
|
export async function cmdReviewers(ctx) {
|
|
11
30
|
const sub = ctx.positionals[0];
|
|
@@ -13,11 +32,33 @@ export async function cmdReviewers(ctx) {
|
|
|
13
32
|
switch (sub) {
|
|
14
33
|
case "standing":
|
|
15
34
|
return cmdStandingReviewers(rest);
|
|
35
|
+
case "invite":
|
|
36
|
+
return inviteGuest(rest);
|
|
37
|
+
case "assigned":
|
|
38
|
+
return assigned(rest);
|
|
16
39
|
default:
|
|
17
|
-
throw new CliError(`Unknown reviewers subcommand: ${sub ?? "(none)"}. Try: standing`);
|
|
40
|
+
throw new CliError(`Unknown reviewers subcommand: ${sub ?? "(none)"}. Try: assigned, standing, invite`);
|
|
18
41
|
}
|
|
19
42
|
}
|
|
20
43
|
|
|
44
|
+
// `reviewers invite <article-id> --email <e> [--name <n>]` — a guest reviewer on one post.
|
|
45
|
+
async function inviteGuest(ctx) {
|
|
46
|
+
const { client, flags, io, positionals } = ctx;
|
|
47
|
+
refuseUnknownFlags(ctx, ARGS.invite_guest_reviewer);
|
|
48
|
+
const result = await client.callTool(
|
|
49
|
+
"invite_guest_reviewer",
|
|
50
|
+
argsFromFlags(ARGS.invite_guest_reviewer, flags, positionals)
|
|
51
|
+
);
|
|
52
|
+
ok(
|
|
53
|
+
ctx,
|
|
54
|
+
result?.email_sent
|
|
55
|
+
? `Invited ${flags.email}: the link and PIN are on their way.`
|
|
56
|
+
: `Invited ${flags.email}, but the email could not be sent: share the link and PIN yourself.`
|
|
57
|
+
);
|
|
58
|
+
printResult(io, flags, result);
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
21
62
|
async function cmdStandingReviewers(ctx) {
|
|
22
63
|
const sub = ctx.positionals[0];
|
|
23
64
|
const rest = { ...ctx, positionals: ctx.positionals.slice(1) };
|
|
@@ -60,3 +101,11 @@ async function standingRemove(ctx) {
|
|
|
60
101
|
printResult(io, flags, result);
|
|
61
102
|
return 0;
|
|
62
103
|
}
|
|
104
|
+
|
|
105
|
+
// `reviewers assigned` — the posts waiting on your review, across every letterbase.
|
|
106
|
+
async function assigned(ctx) {
|
|
107
|
+
const { client, flags, io } = ctx;
|
|
108
|
+
const result = await client.callTool("list_assigned_reviews", argsFromFlags(ARGS.list_assigned_reviews, flags));
|
|
109
|
+
printResult(io, flags, result);
|
|
110
|
+
return 0;
|
|
111
|
+
}
|