@letterstory/cli 0.121.4 → 0.121.6

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 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
@@ -19,6 +19,7 @@ import {
19
19
  cmdPosts,
20
20
  cmdPublished,
21
21
  cmdReviewers,
22
+ cmdAutomations,
22
23
  cmdCollections,
23
24
  cmdAuthors,
24
25
  cmdFlows,
@@ -60,7 +61,7 @@ import {
60
61
  } from "./commands.mjs";
61
62
 
62
63
  // Keep in sync with cli/package.json.
63
- export const VERSION = "0.121.4";
64
+ export const VERSION = "0.121.6";
64
65
 
65
66
  // Flags that never take a value. Listing them explicitly means `deploy get --json <id>`
66
67
  // can't accidentally swallow the id as --json's value. Every `type: "bool"` flag (and its
@@ -136,6 +137,9 @@ export const BOOLEAN_FLAGS = new Set([
136
137
  "stock-only",
137
138
  "gate",
138
139
  "no-verify",
140
+ // `posts comments update <article-id> <comment-id> --resolve | --reopen`.
141
+ "resolve",
142
+ "reopen",
139
143
  "skip",
140
144
  // `phantom-onboard domain` / `admin create-org --onboard` switches.
141
145
  "watch",
@@ -185,6 +189,11 @@ export const BOOLEAN_FLAGS = new Set([
185
189
  "retired",
186
190
  "retire",
187
191
  "restore",
192
+ // `posts list --mine`: only your posts.
193
+ "mine",
194
+ // `automations set --pause | --resume`.
195
+ "pause",
196
+ "resume",
188
197
  // `collection add "<headline>" --collection <id> --queue`.
189
198
  "queue",
190
199
  // Bool flags from command ARGS maps that were missing here.
@@ -376,9 +385,29 @@ Phantomstory (a collection Letterstory hosts as an independent publication):
376
385
  phantom delete <phantom-id> --yes
377
386
 
378
387
  Content:
379
- posts list [--limit <n>] [--collection <uuid>] List your articles
388
+ posts list [--limit <n>] [--offset <n>] [--collection <uuid>] [--search <words>] [--mine]
389
+ List your articles
380
390
  posts show <article-id> Show one article
381
391
  posts new --title <t> --collection <uuid> (--body <text> | --file <path|->) [--article-id <uuid>] [--context <text>]
392
+ posts update <article-id> [--title <t>] [--body <html> | --file <path|->] [--status draft|published|archived]
393
+ [--summary <dek>] [--tag <t> ...] [--owner <user-id> ...] [--due <YYYY-MM-DD>] [--author <name>]
394
+ [--author-id <id>] [--slug <s>] [--meta-title <t>] [--meta-description <d>] [--published-url <url>]
395
+ [--template <key|none|inherit>] [--mention off|subtle|woven|inherit] [--direction-gate on|off|inherit]
396
+ [--paper-trail '<json>'] [--metadata-json '<json>']
397
+ Edit a post; only what you pass changes
398
+ posts edit <article-id> --instruction "<what to change>"
399
+ Rewrite the post with AI from an instruction
400
+ posts mentions <article-id> Re-apply the brand-mention & competitor policy
401
+ posts history <article-id> The post's versions, newest first
402
+ posts restore <article-id> <version> Bring a version back as the latest
403
+ posts export <article-id> --format md|pdf [--out <path>]
404
+ The draft as Markdown or PDF
405
+ posts comments <article-id> The post's comments and replies
406
+ posts comments add <article-id> --body <text> [--quote <text> [--prefix <t>] [--suffix <t>]]
407
+ [--kind note|ai_esque] [--scope sentence|document] [--reply-to <comment-id>] [--mention <user-id> ...]
408
+ posts comments update <article-id> <comment-id> [--body <text>] [--resolve | --reopen] [--mention <user-id> ...]
409
+ posts comments delete <article-id> <comment-id>
410
+ posts comments resolve-ai <article-id> <comment-id> Make the change the comment asks for, with AI
382
411
  posts publish <article-id> Publish an article
383
412
  posts unpublish <article-id> Unpublish an article
384
413
  posts delete --id <uuid> [--id <uuid> ...] Permanently delete article(s)
@@ -391,6 +420,16 @@ Reviewers:
391
420
  guest reviewer on every future
392
421
  finished draft (no email sent now)
393
422
  reviewers standing remove <reviewer-id> Stop future auto-invites
423
+ reviewers assigned Posts waiting on your review, across letterbases
424
+ automations list --collection <id> What a letterbase writes, how often, and why
425
+ automations show <automation-id> --collection <id> What it covers, its queued posts, its next ideas
426
+ automations add topic|series|signal|best --collection <id> [--topic <id>] [--goal <target-id>]
427
+ [--seer <id>] [--pace <n>] Add one (refused if it says the same as another)
428
+ automations set <automation-id> --collection <id> [--pace <n>] [--pause | --resume] [--goal <id>]
429
+ automations remove <automation-id> --collection <id>
430
+ reviewers invite <article-id> --email <e> [--name <n>]
431
+ Invite a guest to review one post
432
+ (emails them a link and a PIN)
394
433
 
395
434
  Collections:
396
435
  collections list List your collections
@@ -1343,6 +1382,7 @@ const CLIENT_COMMANDS = {
1343
1382
  posts: cmdPosts,
1344
1383
  published: cmdPublished,
1345
1384
  reviewers: cmdReviewers,
1385
+ automations: cmdAutomations,
1346
1386
  collections: cmdCollections,
1347
1387
  authors: cmdAuthors,
1348
1388
  flows: cmdFlows,
@@ -0,0 +1,68 @@
1
+ // `automations` — a letterbase's automations (docs/architecture/automations.md): what it writes,
2
+ // how often, and why. list / show / add / set / remove, through the same tools the app uses.
3
+ // Same flag/positional/io conventions as posts.mjs — no commander, no color.
4
+
5
+ import { CliError } from "../client.mjs";
6
+ import { printResult, ok, argsFromFlags, refuseUnknownFlags } from "./shared.mjs";
7
+
8
+ /** See `argsFromFlags` in shared.mjs; `scripts/parity-check.ts` diffs these keys against the tool. */
9
+ export const ARGS = {
10
+ list_automations: {
11
+ collection_id: { flag: "collection", required: true },
12
+ },
13
+ get_automation: {
14
+ collection_id: { flag: "collection", required: true },
15
+ automation_id: { positional: 0, label: "automation-id", required: true },
16
+ },
17
+ create_automation: {
18
+ collection_id: { flag: "collection", required: true },
19
+ kind: { positional: 0, label: "kind", required: true, enum: ["topic", "series", "signal", "best"] },
20
+ cluster_id: { flag: "topic" },
21
+ target_id: { flag: "goal" },
22
+ seer_id: { flag: "seer" },
23
+ pace_per_week: { flag: "pace", type: "num" },
24
+ },
25
+ update_automation: {
26
+ collection_id: { flag: "collection", required: true },
27
+ automation_id: { positional: 0, label: "automation-id", required: true },
28
+ pace_per_week: { flag: "pace", type: "num" },
29
+ paused: {
30
+ value: (flags) => (flags.pause === true ? true : flags.resume === true ? false : undefined),
31
+ },
32
+ target_id: { flag: "goal" },
33
+ },
34
+ delete_automation: {
35
+ collection_id: { flag: "collection", required: true },
36
+ automation_id: { positional: 0, label: "automation-id", required: true },
37
+ },
38
+ };
39
+
40
+ export async function cmdAutomations(ctx) {
41
+ const sub = ctx.positionals[0];
42
+ const rest = { ...ctx, positionals: ctx.positionals.slice(1) };
43
+ switch (sub) {
44
+ case "list":
45
+ case "ls":
46
+ return run(rest, "list_automations");
47
+ case "show":
48
+ return run(rest, "get_automation");
49
+ case "add":
50
+ return run(rest, "create_automation", "Added.");
51
+ case "set":
52
+ refuseUnknownFlags(rest, ARGS.update_automation, ["pause", "resume"]);
53
+ return run(rest, "update_automation", "Changed.");
54
+ case "remove":
55
+ case "rm":
56
+ return run(rest, "delete_automation", "Removed.");
57
+ default:
58
+ throw new CliError(`Unknown automations subcommand: ${sub ?? "(none)"}. Try: list, show, add, set, remove`);
59
+ }
60
+ }
61
+
62
+ async function run(ctx, tool, said) {
63
+ const { client, flags, io } = ctx;
64
+ const result = await client.callTool(tool, argsFromFlags(ARGS[tool], flags, ctx.positionals));
65
+ if (said) ok(ctx, said);
66
+ printResult(io, flags, result);
67
+ return 0;
68
+ }
@@ -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
 
@@ -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: { positional: 0, label: "article-id", required: true },
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({ limit: flagNum(flags.limit), collection_id: flagStr(flags.collection) });
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 { flagStr, requireFlag, requirePositional, printResult, compact, ok } from "./shared.mjs";
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
+ }
package/lib/commands.mjs CHANGED
@@ -11,6 +11,7 @@ export * from "./commands/phantom.mjs";
11
11
  export * from "./commands/mcp.mjs";
12
12
  export * from "./commands/posts.mjs";
13
13
  export * from "./commands/reviewers.mjs";
14
+ export * from "./commands/automations.mjs";
14
15
  export * from "./commands/collections.mjs";
15
16
  export * from "./commands/authors.mjs";
16
17
  export * from "./commands/flows.mjs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letterstory/cli",
3
- "version": "0.121.4",
3
+ "version": "0.121.6",
4
4
  "description": "Spin up and manage Letterstory phantom blogs from your terminal.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",