@letterstory/cli 0.176.80 → 0.176.82

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/lib/cli.mjs CHANGED
@@ -85,7 +85,7 @@ import {
85
85
  import { fetchParkedSections, withoutParked } from "./parked.mjs";
86
86
 
87
87
  // Matches cli/package.json; publish-cli.yml rewrites both to the published version.
88
- export const VERSION = "0.176.80";
88
+ export const VERSION = "0.176.82";
89
89
 
90
90
  /** The exit code for a gate refusal (CARD_REQUIRED, PAYMENT_FAILED, OUT_OF_TOKENS): 1 is a plain error. */
91
91
  export const EXIT_PAYMENT_REQUIRED = 3;
@@ -119,12 +119,10 @@ export const BOOLEAN_FLAGS = new Set([
119
119
  "keep-key",
120
120
  "delete-key",
121
121
  "keep",
122
- // automate add/set: on or off; only identified visitors marked hot leads; run the prompt in a Build sandbox.
122
+ // automate add/set: on or off; only identified visitors marked hot leads.
123
123
  "on",
124
124
  "off",
125
125
  "hot-leads-only",
126
- "sandbox",
127
- "no-sandbox",
128
126
  // admin crm prospect-org: start (or retry) the org prepared for the booked call.
129
127
  "start",
130
128
  // site research-builds: run now even when the stored pass is under an hour old.
@@ -566,6 +564,7 @@ Collections (the content engine as a primitive — one door, 'collection run'):
566
564
  collection cancel <run-id>
567
565
  collection inventory <id> Where every piece is; each finished draft's publish-on-its-own time
568
566
  collection links <id> [--refresh] Internal-link refresh: on/off, posts due (--refresh runs the next batch now)
567
+ collection mentions <id> --apply Apply the add-the-mention suggestions past their review window
569
568
  collection delete <id> --yes
570
569
 
571
570
  Phantomstory (a collection Letter hosts as an independent publication):
@@ -656,25 +655,29 @@ Reviewers:
656
655
  and what Letterpress then does
657
656
  automate show <automation-id>
658
657
  automate add [--template post_new_issues|recommended_actions|competitor_pages|competitor_takes|
659
- competitor_changes|icp_visitors|vc_visitors|plan_updates|weekly_release_notes|monthly_page_refresh]
658
+ competitor_changes|icp_visitors|vc_visitors|plan_updates|weekly_release_notes|monthly_page_refresh|
659
+ relink_posts|mention_revisions|fix_autopilot|auto_plan] [--letterbase <id>|--host <site>]
660
660
  [--name <n>] [--trigger competitor_page|competitor_change|visitor_identified|issue_found|build_suggestion|plan_update|custom|
661
661
  schedule]
662
662
  [--prompt <what Letterpress does with each event>] [--slack-channel <C…>] [--instruction <which events>]
663
663
  [--event <name>] [--kind post|landing] [--competitor <host>] [--site <host>]
664
- [--every <n> --unit hours|days|weeks|months] [--facts <a,b>] [--sandbox] [--recipe <id>]
664
+ [--every <n> --unit hours|days|weeks|months] [--facts <a,b>] [--recipe <id>]
665
665
  [--hot-leads-only] [--per-day <n>] [--backfill all|<YYYY-MM-DD>] [--off]
666
666
  Make one (a template fills what you leave out): it starts
667
667
  from now (--backfill also runs it on past events, oldest
668
668
  first), acts on its own, and posts each event it acts on
669
669
  to --slack-channel with its buttons. --trigger schedule
670
670
  --every 2 --unit weeks is a recurring one (on the calendar:
671
- Monday, the 1st); --sandbox always runs its prompt in a Build
672
- sandbox (off, Letterpress decides per event); --facts
671
+ Monday, the 1st); Letterpress decides per run whether
672
+ its prompt needs a Build sandbox; --facts
673
673
  names what each tick carries (merged_changes, page_reads,
674
674
  new_pages_unlinked, striking_queries, losing_pages);
675
- --recipe makes a Plan recipe's own
675
+ --recipe makes a Plan recipe's own; a job Letter runs
676
+ (relink_posts, mention_revisions: --letterbase;
677
+ auto_plan: --host; fix_autopilot) is made, or added
678
+ back where someone removed it, acting as Letter
676
679
  automate set <automation-id> [--name <n>] [--instruction <text>] [--prompt <text>] [--slack-channel <C…>|none]
677
- [--sandbox|--no-sandbox] [--backfill all|<YYYY-MM-DD>] [--on|--off] ...
680
+ [--backfill all|<YYYY-MM-DD>] [--on|--off] ...
678
681
  automate try [<automation-id>] [--trigger <t> --prompt <text> --instruction <text> ...] [--limit <n>]
679
682
  What it would do with the latest events (acts on none)
680
683
  automate runs [--automation <id>] [--status acting|acted|skipped|failed|proposed|rejected] [--limit <n>]
@@ -684,6 +687,12 @@ Reviewers:
684
687
  automate send <event-name> --title <what happened> [--key <id>] [--url <u>] [--fact k=v ...]
685
688
  [--email <e>] [--person <name>] [--company <c>]
686
689
  Send an event from your own systems
690
+ automate findings [--state new|acted|dismissed|expired] [--automation <id>] [--site <host>] [--page <url>]
691
+ [--limit <n>] [--cursor <t>]
692
+ What the automations noticed: evidence, what it's about,
693
+ the button that acts on it, and how it ended
694
+ automate act <finding-id> Make the call a finding stores, as you
695
+ automate dismiss <finding-id> [--reason <why>] Set a finding aside
687
696
  automate remove <automation-id>
688
697
  reviewers invite <article-id> --email <e> [--name <n>]
689
698
  Invite a guest to review one post
@@ -1,10 +1,10 @@
1
1
  // `automate` — Letterpress → Automations (docs/architecture/event-automations.md): an event, read
2
2
  // against your filter (--instruction), then done by Letterpress as your prompt (--prompt) says.
3
- // options / list / show / add / set / try / runs / approve / reject / send / remove, through the
3
+ // options / list / show / add / set / try / runs / approve / reject / send / findings / act / dismiss / remove, through the
4
4
  // same tools the app uses. (A letterbase's writing automations are made and paced with `automations`.) `list` also
5
- // shows the jobs Letter runs for the account (source "letter": internal links, the blog's sync, the site's re-audit,
6
- // Fix autopilot, add-your-name suggestions, Auto-Plan, and each letterbase's writing automations), each with its
7
- // scope; `set <id> --on|--off` turns one on or off.
5
+ // shows the jobs Letter runs for the account (source "letter": a letterbase's internal links, its add-your-name
6
+ // suggestions, Fix autopilot, Auto-Plan, and each letterbase's writing automations), each with its scope: changed with
7
+ // `set`, removed with `remove`, and made or added back with `add --template <job> --letterbase <id>|--host <host>`.
8
8
  // Same flag/positional/io conventions as posts.mjs — no commander, no color.
9
9
 
10
10
  import { CliError } from "../client.mjs";
@@ -31,9 +31,15 @@ const TEMPLATES = [
31
31
  "plan_updates",
32
32
  "weekly_release_notes",
33
33
  "monthly_page_refresh",
34
+ // The jobs Letter runs: one per letterbase (--letterbase), site (--host) or the organization.
35
+ "relink_posts",
36
+ "mention_revisions",
37
+ "fix_autopilot",
38
+ "auto_plan",
34
39
  ];
35
40
  const UNITS = ["hours", "days", "weeks", "months"];
36
41
  const STATUSES = ["pending", "proposed", "acting", "acted", "answered", "skipped", "failed", "rejected"];
42
+ const FINDING_STATES = ["new", "acted", "dismissed", "expired"];
37
43
 
38
44
  const triggerParams = {
39
45
  object: {
@@ -50,10 +56,6 @@ const triggerParams = {
50
56
  facts: { flag: "facts", type: "list" },
51
57
  },
52
58
  };
53
- // --sandbox / --no-sandbox: always run the prompt in a Build sandbox (the repo and Letter's tools); off, Letterpress decides per event.
54
- const sandbox = {
55
- value: (flags) => (flags["no-sandbox"] === true ? false : flags.sandbox === true ? true : undefined),
56
- };
57
59
  // --slack-channel C0123ABCD (repo slack-targets), or none to post nothing.
58
60
  const slackChannel = {
59
61
  value: (flags) =>
@@ -107,6 +109,16 @@ const destination = {
107
109
  },
108
110
  };
109
111
 
112
+ // --letterbase <collection-id> or --host <site>: where a job Letter runs runs (its template's scope).
113
+ const scope = {
114
+ value: (flags) => {
115
+ const letterbase = flagStr(flags.letterbase);
116
+ const host = flagStr(flags.host);
117
+ if (letterbase && host) throw new CliError("Pass --letterbase or --host, not both.");
118
+ return letterbase ? { collection_id: letterbase } : host ? { host } : undefined;
119
+ },
120
+ };
121
+
110
122
  export const ARGS = {
111
123
  describe_automation_options: {},
112
124
  list_event_automations: {},
@@ -116,6 +128,7 @@ export const ARGS = {
116
128
  create_event_automation: {
117
129
  destination,
118
130
  template: { flag: "template", enum: TEMPLATES },
131
+ scope,
119
132
  // --recipe <id>: make the automation a Plan recipe carries (the Plan row's own call).
120
133
  recipe: { flag: "recipe" },
121
134
  name: { flag: "name" },
@@ -124,7 +137,6 @@ export const ARGS = {
124
137
  instruction: { flag: "instruction" },
125
138
  prompt: { flag: "prompt" },
126
139
  slack_channel: slackChannel,
127
- sandbox,
128
140
  enabled,
129
141
  backfill,
130
142
  },
@@ -137,7 +149,6 @@ export const ARGS = {
137
149
  instruction: { flag: "instruction" },
138
150
  prompt: { flag: "prompt" },
139
151
  slack_channel: slackChannel,
140
- sandbox,
141
152
  enabled,
142
153
  backfill,
143
154
  },
@@ -164,6 +175,29 @@ export const ARGS = {
164
175
  decision: { internal: "approve or reject, from the subcommand" },
165
176
  reason: { flag: "reason" },
166
177
  },
178
+ // What the automations noticed (docs/architecture/automation-findings.md): read, act on, dismiss.
179
+ list_findings: {
180
+ automation_id: { flag: "automation" },
181
+ state: { flag: "state", enum: FINDING_STATES },
182
+ about: {
183
+ object: {
184
+ host: { flag: "site" },
185
+ page_url: { flag: "page" },
186
+ collection_id: { flag: "letterbase" },
187
+ question: { flag: "question" },
188
+ target_id: { flag: "target" },
189
+ },
190
+ },
191
+ limit: { flag: "limit", type: "num" },
192
+ cursor: { flag: "cursor" },
193
+ },
194
+ act_on_finding: {
195
+ finding_id: { positional: 0, label: "finding-id", required: true },
196
+ },
197
+ dismiss_finding: {
198
+ finding_id: { positional: 0, label: "finding-id", required: true },
199
+ reason: { flag: "reason" },
200
+ },
167
201
  send_automation_event: {
168
202
  event: { positional: 0, label: "event-name", required: true },
169
203
  key: { flag: "key" },
@@ -207,12 +241,18 @@ export async function cmdAutomate(ctx) {
207
241
  return run(rest, "decide_automation_run", "Rejected.", { decision: "reject" });
208
242
  case "send":
209
243
  return run(rest, "send_automation_event");
244
+ case "findings":
245
+ return run(rest, "list_findings");
246
+ case "act":
247
+ return run(rest, "act_on_finding", "Done.");
248
+ case "dismiss":
249
+ return run(rest, "dismiss_finding", "Dismissed.");
210
250
  case "remove":
211
251
  case "rm":
212
252
  return run(rest, "delete_event_automation", "Removed.");
213
253
  default:
214
254
  throw new CliError(
215
- `Unknown automate subcommand: ${sub ?? "(none)"}. Try: options, list, show, add, set, try, runs, approve, reject, send, remove`
255
+ `Unknown automate subcommand: ${sub ?? "(none)"}. Try: options, list, show, add, set, try, runs, approve, reject, send, findings, act, dismiss, remove`
216
256
  );
217
257
  }
218
258
  }
@@ -147,6 +147,9 @@ export const ARGS = {
147
147
  refresh_internal_links: {
148
148
  collection_id: { positional: 0, label: "collection-id", required: true },
149
149
  },
150
+ apply_mention_suggestions: {
151
+ collection_id: { positional: 0, label: "collection-id", required: true },
152
+ },
150
153
  };
151
154
 
152
155
  export async function cmdCollection(ctx) {
@@ -190,9 +193,11 @@ export async function cmdCollection(ctx) {
190
193
  return collectionOverview(rest);
191
194
  case "links":
192
195
  return collectionLinks(rest);
196
+ case "mentions":
197
+ return collectionMentions(rest);
193
198
  default:
194
199
  throw new CliError(
195
- `Unknown collection subcommand: ${sub ?? "(none)"}. Try: list, overview, new, config, delete, run, add, queue, headline, run-status, runs, approve, move, cancel, inventory, links`
200
+ `Unknown collection subcommand: ${sub ?? "(none)"}. Try: list, overview, new, config, delete, run, add, queue, headline, run-status, runs, approve, move, cancel, inventory, links, mentions`
196
201
  );
197
202
  }
198
203
  }
@@ -470,3 +475,25 @@ async function collectionLinks(ctx) {
470
475
  );
471
476
  return 0;
472
477
  }
478
+
479
+ // `collection mentions <id> --apply` applies the add-the-mention suggestions whose review window has passed: what
480
+ // the collection's "Add your name where AI cites your posts" automation does on each tick.
481
+ async function collectionMentions(ctx) {
482
+ const { client, flags, positionals, io } = ctx;
483
+ refuseUnknownFlags(ctx, {}, ["apply"]);
484
+ if (flags.apply !== true)
485
+ throw new CliError("Pass --apply to apply the suggestions past their review window (see: suggestions list).");
486
+ const r = await client.callTool(
487
+ "apply_mention_suggestions",
488
+ argsFromFlags(ARGS.apply_mention_suggestions, flags, positionals)
489
+ );
490
+ printResult(io, flags, r, (x) =>
491
+ x.cards.length
492
+ ? [
493
+ `Applied ${x.applied}, failed ${x.failed}, skipped ${x.skipped}.`,
494
+ ...x.cards.map((c) => ` ${c.title} (${c.posts} posts): ${c.outcome}`),
495
+ ].join("\n")
496
+ : "Nothing due: no add-the-mention suggestion is past its review window."
497
+ );
498
+ return 0;
499
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letterstory/cli",
3
- "version": "0.176.80",
3
+ "version": "0.176.82",
4
4
  "description": "Spin up and manage Letter phantom blogs from your terminal.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",