@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 +19 -10
- package/lib/commands/automate.mjs +51 -11
- package/lib/commands/collection.mjs +28 -1
- package/package.json +1 -1
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.
|
|
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
|
|
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>] [--
|
|
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);
|
|
672
|
-
|
|
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
|
-
[--
|
|
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":
|
|
6
|
-
// Fix autopilot,
|
|
7
|
-
//
|
|
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
|
+
}
|