@letterstory/cli 0.90.0 → 0.91.0

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
@@ -57,7 +57,7 @@ import {
57
57
  } from "./commands.mjs";
58
58
 
59
59
  // Keep in sync with cli/package.json.
60
- export const VERSION = "0.90.0";
60
+ export const VERSION = "0.91.0";
61
61
 
62
62
  // Flags that never take a value. Listing them explicitly means `deploy get --json <id>`
63
63
  // can't accidentally swallow the id as --json's value. Every `type: "bool"` flag (and its
@@ -135,9 +135,10 @@ export const BOOLEAN_FLAGS = new Set([
135
135
  "no-enrich",
136
136
  // `offerings set-collection <id> --none` (the org itself is the subject again).
137
137
  "none",
138
- // `prospects import|source --one-per-company`, `prospects source --enrich`.
138
+ // `prospects import|source --one-per-company`, `prospects source --enrich`, `prospects import --find-emails`.
139
139
  "one-per-company",
140
140
  "enrich",
141
+ "find-emails",
141
142
  // `targets update <id> --clear-framing`; `targets subqueries --retired`,
142
143
  // `targets subquery update <id> --retire` / `--restore`.
143
144
  "clear-framing",
@@ -992,9 +993,10 @@ Sequences (multi-step cold email sent from your mailers; approving or enrolling
992
993
  [--[no-]opt-out-footer] [--[no-]postal-address-footer] [--[no-]unsubscribe-links] [--[no-]detect-reply-opt-outs]
993
994
  [--[no-]test-mode] [--default-lede <fallback opener line>]
994
995
  [--send-window-start <HH:MM>] [--send-window-end <HH:MM>] [--send-window-timezone <IANA tz>]
995
- [--enroll-on-cake paid|scheduled|out_for_delivery|delivered|none]
996
+ [--enroll-on-cake paid|scheduled|out_for_delivery|delivered|none] [--enroll-on-cake-list <list-id>|none]
996
997
  --test-mode (internal accounts only): walks as live, sends nothing
997
998
  --enroll-on-cake: start it when a recipient's Daymaker cake reaches that stage
999
+ --enroll-on-cake-list: only for prospects in that list (none = any)
998
1000
  sequences pause|resume <sequence-id> Stop / restart sending
999
1001
  sequences enroll <sequence-id> (--list <list-id> | --email <addr>... | --recipients '<json>'|--recipients-file <path>)
1000
1002
  sequences stop <sequence-id> <enrollment-id> Stop one recipient
@@ -1023,6 +1025,9 @@ Prospects (who you write to):
1023
1025
  prospects get <prospect-id>
1024
1026
  prospects create-list --name <n>
1025
1027
  prospects import --csv-file <path|-> [--list <list-id>|--list-name <n>] [--one-per-company]
1028
+ [--find-emails [--max-email-lookups <1..100>]]
1029
+ --find-emails: look up the business email of rows with a
1030
+ LinkedIn URL and no email (paid; default 25 lookups)
1026
1031
  prospects source --description <who you want> [--list <list-id>|--list-name <n>] [--limit <1..100>]
1027
1032
  [--one-per-company] [--enrich] Fill a list from paid data providers (background);
1028
1033
  --enrich starts enrichment as soon as the pull lands
@@ -93,6 +93,13 @@ const SEQUENCE_WINDOW_ARGS = {
93
93
  // reaches it; `none` clears it.
94
94
  const CAKE_TRIGGER_STAGES = ["paid", "scheduled", "out_for_delivery", "delivered"];
95
95
  const CAKE_STAGES = ["draft", ...CAKE_TRIGGER_STAGES, "delivery_failed", "cancelled"];
96
+ const enrollOnCakeList = {
97
+ value: (flags) => {
98
+ const raw = flagStr(flags["enroll-on-cake-list"]);
99
+ if (raw === undefined) return undefined;
100
+ return raw === "none" ? null : raw;
101
+ },
102
+ };
96
103
  const enrollOnCake = {
97
104
  value: (flags) => {
98
105
  const raw = flagStr(flags["enroll-on-cake"]);
@@ -163,6 +170,7 @@ export const ARGS = {
163
170
  ...SEQUENCE_RAIL_ARGS,
164
171
  ...SEQUENCE_WINDOW_ARGS,
165
172
  enroll_on_cake: enrollOnCake,
173
+ enroll_on_cake_list_id: enrollOnCakeList,
166
174
  },
167
175
  update_sequence: {
168
176
  sequence_id: { positional: 0, label: "sequence-id", required: true },
@@ -173,6 +181,7 @@ export const ARGS = {
173
181
  ...SEQUENCE_RAIL_ARGS,
174
182
  ...SEQUENCE_WINDOW_ARGS,
175
183
  enroll_on_cake: enrollOnCake,
184
+ enroll_on_cake_list_id: enrollOnCakeList,
176
185
  },
177
186
  list_cakes: {
178
187
  stage: { flag: "stage", enum: CAKE_STAGES },
@@ -265,6 +274,8 @@ export const ARGS = {
265
274
  return csv;
266
275
  },
267
276
  },
277
+ find_emails: { flag: "find-emails", type: "bool" },
278
+ max_email_lookups: { flag: "max-email-lookups", type: "num" },
268
279
  one_per_company: { flag: "one-per-company", type: "bool" },
269
280
  list_id: "list",
270
281
  list_name: "list-name",
@@ -378,20 +389,8 @@ export const ARGS = {
378
389
  },
379
390
  };
380
391
 
381
- // Flags the computed `value` sources read (refuseUnknownFlags can't see them).
382
- const VALUE_FLAGS = {
383
- create_sequence: ["flow", "flow-file", "mailer", "send-window-start", "send-window-end"],
384
- update_sequence: ["flow", "flow-file", "mailer", "send-window-start", "send-window-end"],
385
- enroll_in_sequence: ["recipients", "recipients-file", "email"],
386
- update_review_item: ["body", "file", "personalize", "personalize-mode"],
387
- delete_prospects: ["id"],
388
- import_prospects_csv: ["csv", "csv-file"],
389
- update_prospect: ["custom"],
390
- set_mailer_forwarding: ["to", "clear"],
391
- };
392
-
393
392
  async function run(ctx, tool, message) {
394
- refuseUnknownFlags(ctx, ARGS[tool], VALUE_FLAGS[tool] ?? []);
393
+ refuseUnknownFlags(ctx, ARGS[tool]);
395
394
  const result = await ctx.client.callTool(tool, argsFromFlags(ARGS[tool], ctx.flags, ctx.positionals));
396
395
  if (message) ok(ctx, message);
397
396
  printResult(ctx.io, ctx.flags, result);
@@ -83,6 +83,35 @@ export const GLOBAL_FLAGS = new Set([
83
83
  "profile",
84
84
  ]);
85
85
 
86
+ /**
87
+ * The flags a computed `{ value }` source reads, found by running it against
88
+ * no flags and recording each key it looks up. A value source names its flags
89
+ * inside code the map can't see, so `sequences update --enroll-on-cake` was
90
+ * refused by a command whose help and parser both took it.
91
+ */
92
+ export function valueSourceFlags(value) {
93
+ const read = new Set();
94
+ const flags = new Proxy(
95
+ {},
96
+ {
97
+ get: (_target, key) => {
98
+ if (typeof key === "string") read.add(key);
99
+ return undefined;
100
+ },
101
+ has: (_target, key) => {
102
+ if (typeof key === "string") read.add(key);
103
+ return false;
104
+ },
105
+ }
106
+ );
107
+ try {
108
+ value(flags, []);
109
+ } catch {
110
+ // A required source throws when its flag is absent; the read was recorded first.
111
+ }
112
+ return read;
113
+ }
114
+
86
115
  /**
87
116
  * Refuse a flag this command does not understand.
88
117
  *
@@ -105,6 +134,8 @@ export function refuseUnknownFlags(ctx, argsMap, own = []) {
105
134
  else if (source && typeof source === "object" && source.flag) {
106
135
  allowed.add(source.flag);
107
136
  if (source.negate) allowed.add(source.negate);
137
+ } else if (source && typeof source === "object" && typeof source.value === "function") {
138
+ valueSourceFlags(source.value).forEach((f) => allowed.add(f));
108
139
  }
109
140
  }
110
141
  const unknown = Object.keys(ctx.flags).filter((f) => !allowed.has(f));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letterstory/cli",
3
- "version": "0.90.0",
3
+ "version": "0.91.0",
4
4
  "description": "Spin up and manage Letterstory phantom blogs from your terminal.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",