@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 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.77";
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>]
@@ -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)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letterstory/cli",
3
- "version": "0.176.77",
3
+ "version": "0.176.79",
4
4
  "description": "Spin up and manage Letter phantom blogs from your terminal.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",