@letterstory/cli 0.176.77 → 0.176.79
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 +6 -3
- package/lib/commands/admin.mjs +10 -0
- package/lib/commands/automate.mjs +4 -0
- package/lib/commands/replicate.mjs +24 -0
- 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.79";
|
|
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;
|
|
@@ -648,7 +648,7 @@ Reviewers:
|
|
|
648
648
|
schedule]
|
|
649
649
|
[--prompt <what Letterpress does with each event>] [--slack-channel <C…>] [--instruction <which events>]
|
|
650
650
|
[--event <name>] [--kind post|landing] [--competitor <host>] [--site <host>]
|
|
651
|
-
[--every <n> --unit hours|days|weeks|months] [--sandbox]
|
|
651
|
+
[--every <n> --unit hours|days|weeks|months] [--facts <a,b>] [--sandbox] [--recipe <id>]
|
|
652
652
|
[--hot-leads-only] [--per-day <n>] [--backfill all|<YYYY-MM-DD>] [--off]
|
|
653
653
|
Make one (a template fills what you leave out): it starts
|
|
654
654
|
from now (--backfill also runs it on past events, oldest
|
|
@@ -656,7 +656,10 @@ Reviewers:
|
|
|
656
656
|
to --slack-channel with its buttons. --trigger schedule
|
|
657
657
|
--every 2 --unit weeks is a recurring one (on the calendar:
|
|
658
658
|
Monday, the 1st); --sandbox always runs its prompt in a Build
|
|
659
|
-
sandbox (off, Letterpress decides per event)
|
|
659
|
+
sandbox (off, Letterpress decides per event); --facts
|
|
660
|
+
names what each tick carries (merged_changes, page_reads,
|
|
661
|
+
new_pages_unlinked, striking_queries, losing_pages);
|
|
662
|
+
--recipe makes a Plan recipe's own
|
|
660
663
|
automate set <automation-id> [--name <n>] [--instruction <text>] [--prompt <text>] [--slack-channel <C…>|none]
|
|
661
664
|
[--sandbox|--no-sandbox] [--backfill all|<YYYY-MM-DD>] [--on|--off] ...
|
|
662
665
|
automate try [<automation-id>] [--trigger <t> --prompt <text> --instruction <text> ...] [--limit <n>]
|
package/lib/commands/admin.mjs
CHANGED
|
@@ -5134,6 +5134,10 @@ const RECIPE_FILE_FIELDS = [
|
|
|
5134
5134
|
// The inputs the fit is told a recipe cannot run without (2026-11-08: left out here, an import never carried them).
|
|
5135
5135
|
"requires",
|
|
5136
5136
|
"runs",
|
|
5137
|
+
// The recurring automation a recipe carries (runs automation:recipe); left out here, an import never reached prod.
|
|
5138
|
+
"automation",
|
|
5139
|
+
// The build's declared tools and scoped writes (left out here, an export dropped them).
|
|
5140
|
+
"tool_access",
|
|
5137
5141
|
"primitives_access",
|
|
5138
5142
|
"immediate",
|
|
5139
5143
|
"status",
|
|
@@ -5176,6 +5180,12 @@ function recipeDetail(r) {
|
|
|
5176
5180
|
` reads: ${r.reads?.length ? r.reads.join(", ") : "— (not written: every probe runs)"}`,
|
|
5177
5181
|
` status: ${r.status === "parked" ? `parked${r.needs ? `, needs ${r.needs}` : ""}` : "live"}`,
|
|
5178
5182
|
` runs: ${r.runs || "one change to the site's code, from its own directions"}`,
|
|
5183
|
+
...(r.automation
|
|
5184
|
+
? [
|
|
5185
|
+
` automation: "${r.automation.name}" every ${r.automation.every} ${r.automation.unit}, carrying ${(r.automation.facts ?? []).join(", ") || "no facts"}`,
|
|
5186
|
+
` its prompt: ${r.automation.prompt}`,
|
|
5187
|
+
]
|
|
5188
|
+
: []),
|
|
5179
5189
|
...(r.primitives_access ? [" letter's tools: its build may use them (primitives_access)"] : []),
|
|
5180
5190
|
...(r.immediate ? [" immediate: a quick win: the Plan puts one in every top three"] : []),
|
|
5181
5191
|
...(variations.length > 1
|
|
@@ -46,6 +46,8 @@ const triggerParams = {
|
|
|
46
46
|
// --trigger schedule --every 2 --unit weeks: a recurring automation, on the calendar (Monday, the 1st).
|
|
47
47
|
every: { flag: "every", type: "num" },
|
|
48
48
|
unit: { flag: "unit", enum: UNITS },
|
|
49
|
+
// --facts striking_queries,losing_pages: what each tick of a schedule carries, read by Letter before the turn.
|
|
50
|
+
facts: { flag: "facts", type: "list" },
|
|
49
51
|
},
|
|
50
52
|
};
|
|
51
53
|
// --sandbox / --no-sandbox: always run the prompt in a Build sandbox (the repo and Letter's tools); off, Letterpress decides per event.
|
|
@@ -114,6 +116,8 @@ export const ARGS = {
|
|
|
114
116
|
create_event_automation: {
|
|
115
117
|
destination,
|
|
116
118
|
template: { flag: "template", enum: TEMPLATES },
|
|
119
|
+
// --recipe <id>: make the automation a Plan recipe carries (the Plan row's own call).
|
|
120
|
+
recipe: { flag: "recipe" },
|
|
117
121
|
name: { flag: "name" },
|
|
118
122
|
trigger: { flag: "trigger", enum: TRIGGERS },
|
|
119
123
|
trigger_params: triggerParams,
|
|
@@ -107,6 +107,7 @@ function headline(x) {
|
|
|
107
107
|
` ${x.summary.replicated} of ${x.summary.pages} pages replicated; ${x.summary.passing} match the original at every width (mean ${pct(x.summary.meanPixel)})`,
|
|
108
108
|
]
|
|
109
109
|
: []),
|
|
110
|
+
...motionLines(x.motion),
|
|
110
111
|
...(x.agent_pass
|
|
111
112
|
? [
|
|
112
113
|
` Agent pass: ${x.agent_pass.model}, $${x.agent_pass.cost_usd.toFixed(2)} of $${x.agent_pass.budget_usd.toFixed(2)} spent`,
|
|
@@ -134,6 +135,24 @@ function headline(x) {
|
|
|
134
135
|
];
|
|
135
136
|
}
|
|
136
137
|
|
|
138
|
+
const TRIGGERS = ["load", "scroll", "hover", "loop"];
|
|
139
|
+
|
|
140
|
+
// Motion: what moves on the original against the replica, beside the still scores (never the bar).
|
|
141
|
+
function motionLines(m) {
|
|
142
|
+
if (!m) return [];
|
|
143
|
+
const found = TRIGGERS.reduce((s, t) => s + m.found[t], 0);
|
|
144
|
+
const reproduced = TRIGGERS.reduce((s, t) => s + m.reproduced[t], 0);
|
|
145
|
+
return [
|
|
146
|
+
` Motion ${reproduced} of ${found} animations reproduced (${TRIGGERS.map((t) => `${t} ${m.reproduced[t]}/${m.found[t]}`).join(", ")})${m.mean_score !== null ? `, frames ${pct(m.mean_score)}` : ""}${m.not_measured ? `; ${m.not_measured} page(s) not measured` : ""}`,
|
|
147
|
+
...m.gaps
|
|
148
|
+
.slice(0, 8)
|
|
149
|
+
.map(
|
|
150
|
+
(g) =>
|
|
151
|
+
` ${g.issue}: ${g.trigger} "${g.element}" (${g.what}) on ${g.pages} page${g.pages === 1 ? "" : "s"}, e.g. ${g.example}`
|
|
152
|
+
),
|
|
153
|
+
];
|
|
154
|
+
}
|
|
155
|
+
|
|
137
156
|
// The staging link: the replica live on a Letter domain, to share before (or without) a repo.
|
|
138
157
|
function previewLines(x) {
|
|
139
158
|
if (x.staging_url)
|
|
@@ -175,6 +194,11 @@ function replicationDetail(ctx, result) {
|
|
|
175
194
|
lines.push(` ${s.viewport}: "${worst.label}" ${pct(worst.pixel)}`);
|
|
176
195
|
}
|
|
177
196
|
if (p.refined) lines.push(` changed by the agent pass: ${p.refined}`);
|
|
197
|
+
if (p.motion?.error) lines.push(` motion not measured: ${p.motion.error}`);
|
|
198
|
+
for (const g of p.motion?.gaps ?? [])
|
|
199
|
+
lines.push(
|
|
200
|
+
` motion ${g.viewport} ${g.trigger} ${g.issue}: "${g.element}" ${g.what}${g.score !== null ? ` (${pct(g.score)})` : ""}`
|
|
201
|
+
);
|
|
178
202
|
}
|
|
179
203
|
}
|
|
180
204
|
if (r.dropped?.length)
|