@letterstory/cli 0.65.0 → 0.66.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/README.md +7 -0
- package/lib/cli.mjs +24 -3
- package/lib/commands/admin.mjs +75 -1
- package/lib/commands/outbound.mjs +99 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -777,10 +777,17 @@ letterstory sequences list | get <id> | create | update <id> | pause <id> | resu
|
|
|
777
777
|
letterstory sequences enroll <id> --list <list-id> # or --email a@x.com ...
|
|
778
778
|
letterstory sequences review list | approve <item-id> | skip <item-id> | edit <item-id> --subject "…"
|
|
779
779
|
letterstory sequences links create --days 2 # no-login phone review link, shown once
|
|
780
|
+
letterstory sequences update <id> --test-mode # internal accounts only: walks as live, sends nothing
|
|
781
|
+
|
|
782
|
+
# Dispatches (the review desks a sequence queues to)
|
|
783
|
+
letterstory dispatches list | create --name "Founders" | rename <id> --name "VIPs" | delete <id> --yes
|
|
780
784
|
|
|
781
785
|
# Prospects
|
|
782
786
|
letterstory prospects lists | ls --list <list-id> | get <id> | import --csv-file leads.csv --list-name "Q4"
|
|
783
787
|
letterstory prospects source --description "heads of growth at seed-stage fintech" --list-name "Fintech"
|
|
788
|
+
letterstory prospects openers <list-id> --new-group "Fintech openers" # print an opening line per row
|
|
789
|
+
letterstory prospects columns add "Cake image" | columns delete <column-id> --yes
|
|
790
|
+
letterstory prospects delete <prospect-id>... --yes
|
|
784
791
|
```
|
|
785
792
|
|
|
786
793
|
Connecting a bring-your-own Gmail mailbox needs a browser (Google consent), so it is done in the
|
package/lib/cli.mjs
CHANGED
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
cmdLede,
|
|
45
45
|
cmdLandings,
|
|
46
46
|
cmdSequences,
|
|
47
|
+
cmdDispatches,
|
|
47
48
|
cmdProspects,
|
|
48
49
|
cmdMail,
|
|
49
50
|
cmdMailers,
|
|
@@ -55,7 +56,7 @@ import {
|
|
|
55
56
|
} from "./commands.mjs";
|
|
56
57
|
|
|
57
58
|
// Keep in sync with cli/package.json.
|
|
58
|
-
export const VERSION = "0.
|
|
59
|
+
export const VERSION = "0.66.0";
|
|
59
60
|
|
|
60
61
|
// Flags that never take a value. Listing them explicitly means `deploy get --json <id>`
|
|
61
62
|
// can't accidentally swallow the id as --json's value.
|
|
@@ -549,6 +550,13 @@ Admin (STAFF key — --staff-key / LETTERSTORY_STAFF_API_KEY; acts before an org
|
|
|
549
550
|
read-only SQL across every org; --sql prints
|
|
550
551
|
the queries behind the answer. Needs the
|
|
551
552
|
fleet:query capability on the staff key.
|
|
553
|
+
admin replays [--org <uuid>] [--target <uuid>] [--status captured|not_reproduced|queued|running|unsupported|failed] [--limit <n>]
|
|
554
|
+
Live answer replays: breakthroughs re-asked in ChatGPT /
|
|
555
|
+
Gemini / Google / Perplexity / Claude, with screenshots.
|
|
556
|
+
Omit --org for the whole fleet. Needs fleet:read.
|
|
557
|
+
admin replay --org <uuid> (<response-id> | --target <uuid>) [--surface openai|anthropic|google-aio|google-gemini|perplexity]
|
|
558
|
+
Replay a breakthrough live now (background; poll
|
|
559
|
+
admin replays). Needs fleet:replay.
|
|
552
560
|
admin experiments list [--q colons] [--status proposed] | show <id>
|
|
553
561
|
| new "<question>" [--hypothesis …] [--plan-file p.json] [--tags a,b] [--as "<agent>"]
|
|
554
562
|
| update <id> [--plan-file …] [--result-file …] [--evidence kind:ref,…] [--budget N]
|
|
@@ -808,13 +816,14 @@ Sequences (multi-step cold email sent from your mailers; approving or enrolling
|
|
|
808
816
|
sequences get <sequence-id> Flow, enrollments, who replied at each step
|
|
809
817
|
sequences create --name <n> (--flow '<json>'|--flow-file <path>) [--mailer <mailer-id>]...
|
|
810
818
|
[--[no-]opt-out-footer] [--[no-]postal-address-footer] [--[no-]unsubscribe-links] [--[no-]detect-reply-opt-outs]
|
|
811
|
-
[--send-window-start <HH:MM>] [--send-window-end <HH:MM>] [--send-window-timezone <IANA tz>]
|
|
819
|
+
[--[no-]test-mode] [--send-window-start <HH:MM>] [--send-window-end <HH:MM>] [--send-window-timezone <IANA tz>]
|
|
812
820
|
New draft sequence (nothing sends yet; rails default on, opt-out line off;
|
|
813
821
|
sends 9:00-17:00 in each recipient's inferred timezone, else the sender's)
|
|
814
822
|
sequences update <sequence-id> [--name <n>] [--flow '<json>'|--flow-file <path>]
|
|
815
823
|
[--status draft|active|paused|archived] [--mailer <mailer-id>]...
|
|
816
824
|
[--[no-]opt-out-footer] [--[no-]postal-address-footer] [--[no-]unsubscribe-links] [--[no-]detect-reply-opt-outs]
|
|
817
|
-
[--send-window-start <HH:MM>] [--send-window-end <HH:MM>] [--send-window-timezone <IANA tz>]
|
|
825
|
+
[--[no-]test-mode] [--send-window-start <HH:MM>] [--send-window-end <HH:MM>] [--send-window-timezone <IANA tz>]
|
|
826
|
+
--test-mode (internal accounts only): walks as live, sends nothing
|
|
818
827
|
sequences pause|resume <sequence-id> Stop / restart sending
|
|
819
828
|
sequences enroll <sequence-id> (--list <list-id> | --email <addr>... | --recipients '<json>'|--recipients-file <path>)
|
|
820
829
|
sequences stop <sequence-id> <enrollment-id> Stop one recipient
|
|
@@ -825,6 +834,12 @@ Sequences (multi-step cold email sent from your mailers; approving or enrolling
|
|
|
825
834
|
sequences links create --days <1..3> Mint a no-login review link (shown once)
|
|
826
835
|
sequences links revoke <link-id> --yes
|
|
827
836
|
|
|
837
|
+
Dispatches (the named review desks a sequence's review stage queues to):
|
|
838
|
+
dispatches list Desks + how many emails each has queued
|
|
839
|
+
dispatches create --name <n>
|
|
840
|
+
dispatches rename <dispatch-id> --name <n>
|
|
841
|
+
dispatches delete <dispatch-id> --yes Queued emails move to the default desk
|
|
842
|
+
|
|
828
843
|
Prospects (who you write to):
|
|
829
844
|
prospects lists Your lists + counts
|
|
830
845
|
prospects list <list-id> One list
|
|
@@ -837,6 +852,11 @@ Prospects (who you write to):
|
|
|
837
852
|
--enrich starts enrichment as soon as the pull lands
|
|
838
853
|
prospects enrich [--list <list-id>] [--status <s>] [--q <search>]
|
|
839
854
|
Funding + competitors for un-enriched rows
|
|
855
|
+
prospects openers <list-id> [--group <ledes-group-id>|--new-group <name>]
|
|
856
|
+
Print an opening line for every row without one (background)
|
|
857
|
+
prospects delete <prospect-id>... --yes Permanently delete prospects
|
|
858
|
+
prospects columns add <label> Add a custom column (key returned)
|
|
859
|
+
prospects columns delete <column-id> --yes
|
|
840
860
|
prospects update <prospect-id> [--status new|contacted|replied|unsubscribed|archived] [--name <n>]
|
|
841
861
|
[--first-name] [--last-name] [--title] [--company] [--website] [--linkedin] [--school]
|
|
842
862
|
[--city] [--country] [--years] [--notes] [--custom key=value]...
|
|
@@ -960,6 +980,7 @@ const CLIENT_COMMANDS = {
|
|
|
960
980
|
ledes: cmdLede,
|
|
961
981
|
landings: cmdLandings,
|
|
962
982
|
sequences: cmdSequences,
|
|
983
|
+
dispatches: cmdDispatches,
|
|
963
984
|
prospects: cmdProspects,
|
|
964
985
|
mail: cmdMail,
|
|
965
986
|
mailers: cmdMailers,
|
package/lib/commands/admin.mjs
CHANGED
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
// admin tuning kernels|kernel|candidate|compare|comparisons|comparison|picks|pick|promote … (tuning:experiment)
|
|
11
11
|
// admin evals scoreboard|cases|runs|run|triage|guard|retire … (fleet:read / tuning:experiment)
|
|
12
12
|
// admin crm import|capture|signals|record|signal|accounts|account|score|slack-channels|link-slack|white-glove|set-white-glove|churn|record-churn|undo-churn|onboard|onboarding|retry-onboarding|onboard-plan|payment-link … (crm:read / crm:write)
|
|
13
|
+
// admin replays [--org <id>] [--target <id>] [--status …] [--limit n] (fleet:read)
|
|
14
|
+
// admin replay --org <id> (<response-id> | --target <id>) [--surface …] (fleet:replay)
|
|
13
15
|
// admin create-org --domain acme.com [--name "Acme"] [--test] [--no-key] [--key-name N]
|
|
14
16
|
// [--capabilities a,b] [--onboard] [--about "…"] [--sitemap <url>]
|
|
15
17
|
// [--cms <provider>] [--posts N] [--seers suggest|skip] [--no-visuals]
|
|
@@ -1926,8 +1928,80 @@ async function cmdAdminEvals(ctx) {
|
|
|
1926
1928
|
}
|
|
1927
1929
|
}
|
|
1928
1930
|
|
|
1931
|
+
const REPLAY_SURFACES = ["openai", "anthropic", "google-aio", "google-gemini", "perplexity"];
|
|
1932
|
+
const REPLAY_STATUSES = ["queued", "running", "captured", "not_reproduced", "unsupported", "failed"];
|
|
1933
|
+
const REPLAYS = "/api/superadmin/answer-replays";
|
|
1934
|
+
|
|
1935
|
+
function oneOf(flags, name, values) {
|
|
1936
|
+
const v = flagStr(flags[name]);
|
|
1937
|
+
if (v !== undefined && !values.includes(v)) throw new CliError(`--${name} must be one of: ${values.join(", ")}`);
|
|
1938
|
+
return v;
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
function formatReplays(r) {
|
|
1942
|
+
const rows = r.replays ?? [];
|
|
1943
|
+
if (!rows.length) return "No answer replays.";
|
|
1944
|
+
return rows
|
|
1945
|
+
.map((x) => {
|
|
1946
|
+
const head = `${x.id} ${x.surfaceLabel} ${x.status} ${x.attemptsMade}/${x.plannedAttempts} asks ${x.createdAt}`;
|
|
1947
|
+
const lines = [head];
|
|
1948
|
+
if (x.capture) {
|
|
1949
|
+
lines.push(
|
|
1950
|
+
` captured on ask ${x.capture.step + 1}: "${x.capture.query}" (web search ${x.capture.webSearch})`
|
|
1951
|
+
);
|
|
1952
|
+
for (const img of x.capture.images ?? []) lines.push(` ${img.kind}: ${img.url}`);
|
|
1953
|
+
}
|
|
1954
|
+
if (x.error) lines.push(` ${x.error}`);
|
|
1955
|
+
return lines.join("\n");
|
|
1956
|
+
})
|
|
1957
|
+
.join("\n");
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
/** `admin replays` / `admin replay` — live answer replays (staff only). */
|
|
1961
|
+
async function cmdAdminReplays(ctx, sub) {
|
|
1962
|
+
const { flags, positionals, io } = ctx;
|
|
1963
|
+
if (sub === "replays") {
|
|
1964
|
+
const params = new URLSearchParams(
|
|
1965
|
+
compact({
|
|
1966
|
+
org_id: flagStr(flags.org),
|
|
1967
|
+
target_id: flagStr(flags.target),
|
|
1968
|
+
status: oneOf(flags, "status", REPLAY_STATUSES),
|
|
1969
|
+
limit: flagNum(flags.limit)?.toString(),
|
|
1970
|
+
})
|
|
1971
|
+
);
|
|
1972
|
+
const result = await staffRequest(ctx, "GET", `${REPLAYS}${params.size ? `?${params}` : ""}`);
|
|
1973
|
+
printResult(io, flags, result, formatReplays);
|
|
1974
|
+
return 0;
|
|
1975
|
+
}
|
|
1976
|
+
const org = flagStr(flags.org);
|
|
1977
|
+
const responseId = positionals[0];
|
|
1978
|
+
const target = flagStr(flags.target);
|
|
1979
|
+
if (!org) throw new CliError("admin replay needs --org <org-id>");
|
|
1980
|
+
if (!responseId && !target) throw new CliError("Pass a breakthrough <response-id>, or --target <target-id>");
|
|
1981
|
+
const result = await staffPost(
|
|
1982
|
+
ctx,
|
|
1983
|
+
REPLAYS,
|
|
1984
|
+
compact({
|
|
1985
|
+
org_id: org,
|
|
1986
|
+
response_id: responseId,
|
|
1987
|
+
target_id: target,
|
|
1988
|
+
surface: oneOf(flags, "surface", REPLAY_SURFACES),
|
|
1989
|
+
})
|
|
1990
|
+
);
|
|
1991
|
+
ok(
|
|
1992
|
+
ctx,
|
|
1993
|
+
result.already_queued
|
|
1994
|
+
? `Already replaying: ${result.replay_id}`
|
|
1995
|
+
: `Replay ${result.replay_id} is ${result.status}. Poll: ${ctx.bin} admin replays --org ${org}`
|
|
1996
|
+
);
|
|
1997
|
+
printResult(io, flags, result);
|
|
1998
|
+
return 0;
|
|
1999
|
+
}
|
|
2000
|
+
|
|
1929
2001
|
export async function cmdAdmin(ctx) {
|
|
1930
2002
|
const sub = ctx.positionals[0];
|
|
2003
|
+
if (sub === "replays" || sub === "replay")
|
|
2004
|
+
return cmdAdminReplays({ ...ctx, positionals: ctx.positionals.slice(1) }, sub);
|
|
1931
2005
|
if (sub === "crm") return cmdAdminCrm({ ...ctx, positionals: ctx.positionals.slice(1) });
|
|
1932
2006
|
if (sub === "create-org") return cmdAdminCreateOrg({ ...ctx, positionals: ctx.positionals.slice(1) });
|
|
1933
2007
|
if (sub === "ask") return cmdAdminAsk({ ...ctx, positionals: ctx.positionals.slice(1) });
|
|
@@ -1937,6 +2011,6 @@ export async function cmdAdmin(ctx) {
|
|
|
1937
2011
|
if (sub === "tuning") return cmdAdminTuning({ ...ctx, positionals: ctx.positionals.slice(1) });
|
|
1938
2012
|
if (sub === "evals") return cmdAdminEvals({ ...ctx, positionals: ctx.positionals.slice(1) });
|
|
1939
2013
|
throw new CliError(
|
|
1940
|
-
'Usage: admin create-org --domain <bare-domain> [--onboard] … | admin ask "<question>" | admin experiments … | admin crm … | admin evals … | admin tuning … | admin tuning-context (see --help)'
|
|
2014
|
+
'Usage: admin create-org --domain <bare-domain> [--onboard] … | admin ask "<question>" | admin experiments … | admin crm … | admin evals … | admin tuning … | admin tuning-context | admin replays … (see --help)'
|
|
1941
2015
|
);
|
|
1942
2016
|
}
|
|
@@ -59,6 +59,8 @@ const SEQUENCE_RAIL_ARGS = {
|
|
|
59
59
|
postal_address_footer: { flag: "postal-address-footer", type: "bool", negate: "no-postal-address-footer" },
|
|
60
60
|
unsubscribe_links: { flag: "unsubscribe-links", type: "bool", negate: "no-unsubscribe-links" },
|
|
61
61
|
detect_reply_opt_outs: { flag: "detect-reply-opt-outs", type: "bool", negate: "no-detect-reply-opt-outs" },
|
|
62
|
+
// Staff-only to turn on (the server refuses it for anyone else): walks as live, sends nothing.
|
|
63
|
+
test_mode: { flag: "test-mode", type: "bool", negate: "no-test-mode" },
|
|
62
64
|
};
|
|
63
65
|
|
|
64
66
|
// A send-window edge from --<flag> HH:MM (or a raw minute of day) → the
|
|
@@ -158,6 +160,17 @@ export const ARGS = {
|
|
|
158
160
|
item_id: { positional: 0, label: "item-id", required: true },
|
|
159
161
|
action: { flag: "action", required: true, enum: ["approve", "skip", "delete"] },
|
|
160
162
|
},
|
|
163
|
+
list_dispatches: {},
|
|
164
|
+
create_dispatch: {
|
|
165
|
+
name: { flag: "name", required: true },
|
|
166
|
+
},
|
|
167
|
+
update_dispatch: {
|
|
168
|
+
dispatch_id: { positional: 0, label: "dispatch-id", required: true },
|
|
169
|
+
name: { flag: "name", required: true },
|
|
170
|
+
},
|
|
171
|
+
delete_dispatch: {
|
|
172
|
+
dispatch_id: { positional: 0, label: "dispatch-id", required: true },
|
|
173
|
+
},
|
|
161
174
|
list_dispatch_links: {},
|
|
162
175
|
create_dispatch_link: {
|
|
163
176
|
days: { flag: "days", type: "num", required: true },
|
|
@@ -210,6 +223,26 @@ export const ARGS = {
|
|
|
210
223
|
status: { flag: "status", enum: PROSPECT_STATUSES },
|
|
211
224
|
q: "q",
|
|
212
225
|
},
|
|
226
|
+
delete_prospects: {
|
|
227
|
+
prospect_ids: {
|
|
228
|
+
value: (flags, positionals) => {
|
|
229
|
+
const ids = [...positionals, ...flagList(flags.id)];
|
|
230
|
+
if (!ids.length) throw new CliError("Missing <prospect-id>... (or --id <prospect-id>, repeatable)");
|
|
231
|
+
return ids;
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
create_prospect_column: {
|
|
236
|
+
label: { positional: 0, label: "label", required: true },
|
|
237
|
+
},
|
|
238
|
+
delete_prospect_column: {
|
|
239
|
+
column_id: { positional: 0, label: "column-id", required: true },
|
|
240
|
+
},
|
|
241
|
+
generate_prospect_openers: {
|
|
242
|
+
list_id: { positional: 0, label: "list-id", required: true },
|
|
243
|
+
group_id: "group",
|
|
244
|
+
new_group_name: "new-group",
|
|
245
|
+
},
|
|
213
246
|
update_prospect: {
|
|
214
247
|
prospect_id: { positional: 0, label: "prospect-id", required: true },
|
|
215
248
|
status: { flag: "status", enum: PROSPECT_STATUSES },
|
|
@@ -292,6 +325,7 @@ const VALUE_FLAGS = {
|
|
|
292
325
|
update_sequence: ["flow", "flow-file", "mailer", "send-window-start", "send-window-end"],
|
|
293
326
|
enroll_in_sequence: ["recipients", "recipients-file", "email"],
|
|
294
327
|
update_review_item: ["body", "file"],
|
|
328
|
+
delete_prospects: ["id"],
|
|
295
329
|
import_prospects_csv: ["csv", "csv-file"],
|
|
296
330
|
update_prospect: ["custom"],
|
|
297
331
|
set_mailer_forwarding: ["to", "clear"],
|
|
@@ -306,6 +340,37 @@ async function run(ctx, tool, message) {
|
|
|
306
340
|
}
|
|
307
341
|
|
|
308
342
|
const SEQUENCES_USAGE = `Usage: sequences <list|get|create|update|pause|resume|enroll|stop|review|links>`;
|
|
343
|
+
const DISPATCHES_USAGE = `Usage: dispatches <list|create|rename|delete>`;
|
|
344
|
+
|
|
345
|
+
// Dispatches: the named review desks a sequence's review stage queues to.
|
|
346
|
+
export async function cmdDispatches(ctx) {
|
|
347
|
+
const sub = ctx.positionals[0];
|
|
348
|
+
const rest = { ...ctx, positionals: ctx.positionals.slice(1) };
|
|
349
|
+
switch (sub) {
|
|
350
|
+
case "list":
|
|
351
|
+
case "ls":
|
|
352
|
+
case undefined:
|
|
353
|
+
return run(rest, "list_dispatches");
|
|
354
|
+
case "create":
|
|
355
|
+
case "new":
|
|
356
|
+
return run(rest, "create_dispatch", "Dispatch created.");
|
|
357
|
+
case "rename":
|
|
358
|
+
case "update":
|
|
359
|
+
return run(rest, "update_dispatch", "Dispatch renamed.");
|
|
360
|
+
case "delete": {
|
|
361
|
+
if (!ctx.flags.yes) {
|
|
362
|
+
const id = flagStr(rest.positionals[0]) ?? "<dispatch-id>";
|
|
363
|
+
ctx.io.error(
|
|
364
|
+
`Its queued emails move to the default desk. Re-run with --yes to confirm: ${ctx.bin} dispatches delete ${id} --yes`
|
|
365
|
+
);
|
|
366
|
+
return 1;
|
|
367
|
+
}
|
|
368
|
+
return run(rest, "delete_dispatch", "Dispatch deleted; its queued emails moved to the default desk.");
|
|
369
|
+
}
|
|
370
|
+
default:
|
|
371
|
+
throw new CliError(`Unknown dispatches subcommand: ${sub}. ${DISPATCHES_USAGE}`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
309
374
|
|
|
310
375
|
export async function cmdSequences(ctx) {
|
|
311
376
|
const sub = ctx.positionals[0];
|
|
@@ -395,7 +460,7 @@ async function sequencesLinks(ctx, sub) {
|
|
|
395
460
|
}
|
|
396
461
|
}
|
|
397
462
|
|
|
398
|
-
const PROSPECTS_USAGE = `Usage: prospects <lists|list|ls|get|create-list|delete-list|import|source|enrich|update>`;
|
|
463
|
+
const PROSPECTS_USAGE = `Usage: prospects <lists|list|ls|get|create-list|delete-list|import|source|enrich|openers|update|delete|columns>`;
|
|
399
464
|
|
|
400
465
|
export async function cmdProspects(ctx) {
|
|
401
466
|
const sub = ctx.positionals[0];
|
|
@@ -424,13 +489,46 @@ export async function cmdProspects(ctx) {
|
|
|
424
489
|
);
|
|
425
490
|
case "enrich":
|
|
426
491
|
return run(rest, "enrich_prospects", "Enrichment queued.");
|
|
492
|
+
case "openers":
|
|
493
|
+
return run(rest, "generate_prospect_openers", "Printing openers; they fill in as each one lands.");
|
|
427
494
|
case "update":
|
|
428
495
|
return run(rest, "update_prospect", "Updated prospect.");
|
|
496
|
+
case "delete":
|
|
497
|
+
case "rm": {
|
|
498
|
+
if (!ctx.flags.yes) {
|
|
499
|
+
ctx.io.error(`Deleting prospects cannot be undone. Re-run with --yes to confirm.`);
|
|
500
|
+
return 1;
|
|
501
|
+
}
|
|
502
|
+
return run(rest, "delete_prospects", "Deleted.");
|
|
503
|
+
}
|
|
504
|
+
case "columns":
|
|
505
|
+
return prospectsColumns(rest, rest.positionals[0]);
|
|
429
506
|
default:
|
|
430
507
|
throw new CliError(`Unknown prospects subcommand: ${sub ?? "(none)"}. ${PROSPECTS_USAGE}`);
|
|
431
508
|
}
|
|
432
509
|
}
|
|
433
510
|
|
|
511
|
+
async function prospectsColumns(ctx, sub) {
|
|
512
|
+
const inner = { ...ctx, positionals: ctx.positionals.slice(1) };
|
|
513
|
+
switch (sub) {
|
|
514
|
+
case "add":
|
|
515
|
+
return run(inner, "create_prospect_column", "Column added.");
|
|
516
|
+
case "delete":
|
|
517
|
+
case "rm": {
|
|
518
|
+
if (!ctx.flags.yes) {
|
|
519
|
+
const id = flagStr(inner.positionals[0]) ?? "<column-id>";
|
|
520
|
+
ctx.io.error(`Re-run with --yes to confirm: ${ctx.bin} prospects columns delete ${id} --yes`);
|
|
521
|
+
return 1;
|
|
522
|
+
}
|
|
523
|
+
return run(inner, "delete_prospect_column", "Column removed.");
|
|
524
|
+
}
|
|
525
|
+
default:
|
|
526
|
+
throw new CliError(
|
|
527
|
+
`Unknown columns subcommand: ${sub ?? "(none)"}. Try: add <label>, delete <column-id> --yes`
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
434
532
|
const MAILERS_USAGE = `Usage: mailers <list|domain|messages|message|forward|warmup|limit|retire|suppressions>`;
|
|
435
533
|
|
|
436
534
|
export async function cmdMailers(ctx) {
|