@letterstory/cli 0.176.91 → 0.176.93

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
@@ -86,7 +86,7 @@ import {
86
86
  import { fetchParkedSections, withoutParked } from "./parked.mjs";
87
87
 
88
88
  // Matches cli/package.json; publish-cli.yml rewrites both to the published version.
89
- export const VERSION = "0.176.91";
89
+ export const VERSION = "0.176.93";
90
90
 
91
91
  /** The exit code for a gate refusal (CARD_REQUIRED, PAYMENT_FAILED, OUT_OF_TOKENS): 1 is a plain error. */
92
92
  export const EXIT_PAYMENT_REQUIRED = 3;
@@ -354,6 +354,8 @@ export const BOOLEAN_FLAGS = new Set([
354
354
  "consent",
355
355
  "no-redesign",
356
356
  "no-test-mode",
357
+ "first-touch-only",
358
+ "no-first-touch-only",
357
359
  "no-unsubscribe-links",
358
360
  "now",
359
361
  "opt-out-footer",
@@ -1640,6 +1642,7 @@ Sequences (multi-step cold email sent from your mailers; approving or enrolling
1640
1642
  [--[no-]opt-out-footer] [--[no-]postal-address-footer] [--[no-]unsubscribe-links] [--[no-]detect-reply-opt-outs]
1641
1643
  [--[no-]test-mode] [--default-lede <fallback opener line>]
1642
1644
  [--send-window-start <HH:MM>] [--send-window-end <HH:MM>] [--send-window-timezone <IANA tz>]
1645
+ [--daily-send-cap <n>|none] [--[no-]first-touch-only]
1643
1646
  New draft sequence (nothing sends yet; rails default on, opt-out line off;
1644
1647
  sends 9:00-17:00 in each recipient's inferred timezone, else the sender's)
1645
1648
  sequences update <sequence-id> [--name <n>] [--flow '<json>'|--flow-file <path>]
@@ -1648,6 +1651,7 @@ Sequences (multi-step cold email sent from your mailers; approving or enrolling
1648
1651
  [--[no-]test-mode] [--default-lede <fallback opener line>]
1649
1652
  [--send-window-start <HH:MM>] [--send-window-end <HH:MM>] [--send-window-timezone <IANA tz>]
1650
1653
  [--enroll-on-cake paid|scheduled|out_for_delivery|delivered|none] [--enroll-on-cake-list <list-id>|none]
1654
+ [--daily-send-cap <n>|none] [--[no-]first-touch-only]
1651
1655
  --test-mode (internal accounts only): walks as live, sends nothing
1652
1656
  --enroll-on-cake: start it when a recipient's Daymaker cake reaches that stage
1653
1657
  --enroll-on-cake-list: only for prospects in that list (none = any)
@@ -1686,8 +1690,10 @@ Prospects (who you write to):
1686
1690
  --find-emails: look up the business email of rows with a
1687
1691
  LinkedIn URL and no email (paid; default 25 lookups)
1688
1692
  prospects source --description <who you want> [--list <list-id>|--list-name <n>] [--limit <1..100>]
1689
- [--one-per-company] [--enrich] Fill a list from paid data providers (background);
1690
- --enrich starts enrichment as soon as the pull lands
1693
+ [--one-per-company] [--enrich] [--exclude-platform webflow]...
1694
+ Fill a list from paid data providers (background);
1695
+ --enrich starts enrichment as soon as the pull lands;
1696
+ --exclude-platform leaves out sites built on it
1691
1697
  prospects enrich [--list <list-id>] [--status <s>] [--q <search>]
1692
1698
  Funding + competitors for un-enriched rows
1693
1699
  prospects openers <list-id> [--group <ledes-group-id>|--new-group <name>]
@@ -5172,6 +5172,8 @@ function recipeDetail(r) {
5172
5172
  `${pad}recipe: ${v.recipe || "—"}`,
5173
5173
  `${pad}when to use: ${v.when_to_use || "—"}`,
5174
5174
  `${pad}when not to use: ${v.when_not_to_use || "—"}`,
5175
+ // The facts it cannot run without (the fit holds it back in code); unset is the legacy rule until reviewed.
5176
+ `${pad}required inputs: ${v.requires == null ? "not reviewed (the legacy rule)" : v.requires.join(", ") || "none"}`,
5175
5177
  ];
5176
5178
  return [
5177
5179
  `${r.name} weight ${r.weight}/10 ${r.id}`,
@@ -5186,8 +5188,13 @@ function recipeDetail(r) {
5186
5188
  ` its prompt: ${r.automation.prompt}`,
5187
5189
  ]
5188
5190
  : []),
5189
- ...(r.primitives_access ? [" letter's tools: its build may use them (primitives_access)"] : []),
5190
- ...(r.immediate ? [" immediate: a quick win: the Plan puts one in every top three"] : []),
5191
+ ...(r.tool_access
5192
+ ? [
5193
+ ` letter's tools: its build needs ${r.tool_access.required?.join(", ") || "none named"}; may write ${r.tool_access.writes?.join(", ") || "nothing"} (tool_access)`,
5194
+ ]
5195
+ : r.primitives_access
5196
+ ? [" letter's tools: its build may read the account through them (primitives_access)"]
5197
+ : []),
5191
5198
  ...(variations.length > 1
5192
5199
  ? variations.flatMap((v, i) => [` variation ${i + 1}: ${v.name}`, ...fields(v, " ")])
5193
5200
  : fields(r, " ")),
@@ -5240,7 +5247,7 @@ function recipeFromFlags(flags) {
5240
5247
  // --primitives-access true hands a start's build Letter's own tools; false takes them away.
5241
5248
  primitives_access:
5242
5249
  flags["primitives-access"] === undefined ? undefined : String(flags["primitives-access"]) === "true",
5243
- // --immediate true marks a quick win (live within a day): the Plan puts one in every top three.
5250
+ // --immediate is accepted for old scripts and read by nothing: a quick row is one whose runs is empty.
5244
5251
  immediate: flags.immediate === undefined ? undefined : String(flags.immediate) === "true",
5245
5252
  // --reads llms_txt,github declares the facts its conditions rest on; --reads "" clears them.
5246
5253
  reads: flagStr(flags.reads)
@@ -89,6 +89,22 @@ const SEQUENCE_WINDOW_ARGS = {
89
89
  send_window_end_minute: { value: windowMinute("send-window-end") },
90
90
  send_window_timezone: "send-window-timezone",
91
91
  };
92
+ // --daily-send-cap <n|none>: the most emails the sequence sends per UTC day across its mailers;
93
+ // --first-touch-only: only people no other sequence has written to.
94
+ const SEQUENCE_LIMIT_ARGS = {
95
+ daily_send_cap: {
96
+ value: (flags) => {
97
+ const raw = flagStr(flags["daily-send-cap"]);
98
+ if (raw === undefined) return undefined;
99
+ if (raw === "none") return null;
100
+ const n = Number(raw);
101
+ if (!Number.isInteger(n) || n < 1 || n > 500)
102
+ throw new CliError(`--daily-send-cap takes 1–500 or none (got ${raw})`);
103
+ return n;
104
+ },
105
+ },
106
+ first_touch_only: { flag: "first-touch-only", type: "bool", negate: "no-first-touch-only" },
107
+ };
92
108
  // --enroll-on-cake <stage>: start the sequence when a recipient's Daymaker cake
93
109
  // reaches it; `none` clears it.
94
110
  const CAKE_TRIGGER_STAGES = ["paid", "scheduled", "out_for_delivery", "delivered"];
@@ -169,6 +185,7 @@ export const ARGS = {
169
185
  mailer_ids: mailerIds,
170
186
  ...SEQUENCE_RAIL_ARGS,
171
187
  ...SEQUENCE_WINDOW_ARGS,
188
+ ...SEQUENCE_LIMIT_ARGS,
172
189
  enroll_on_cake: enrollOnCake,
173
190
  enroll_on_cake_list_id: enrollOnCakeList,
174
191
  },
@@ -180,6 +197,7 @@ export const ARGS = {
180
197
  mailer_ids: mailerIds,
181
198
  ...SEQUENCE_RAIL_ARGS,
182
199
  ...SEQUENCE_WINDOW_ARGS,
200
+ ...SEQUENCE_LIMIT_ARGS,
183
201
  enroll_on_cake: enrollOnCake,
184
202
  enroll_on_cake_list_id: enrollOnCakeList,
185
203
  },
@@ -291,6 +309,7 @@ export const ARGS = {
291
309
  list_name: "list-name",
292
310
  limit: { flag: "limit", type: "num" },
293
311
  one_per_company: { flag: "one-per-company", type: "bool" },
312
+ exclude_platforms: { flag: "exclude-platform", type: "list" },
294
313
  enrich: { flag: "enrich", type: "bool" },
295
314
  },
296
315
  enrich_prospects: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letterstory/cli",
3
- "version": "0.176.91",
3
+ "version": "0.176.93",
4
4
  "description": "Spin up and manage Letter phantom blogs from your terminal.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",