@dench.com/cli 2.2.1 → 2.2.2

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/email.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import type { ConvexHttpClient } from "convex/browser";
3
3
  import { makeFunctionReference } from "convex/server";
4
- import { getFlag, parseJson } from "./lib/cli-args";
4
+ import { getFlag, hasFlag, parseJson } from "./lib/cli-args";
5
5
 
6
6
  type EmailCliContext = {
7
7
  convex: ConvexHttpClient;
@@ -202,11 +202,29 @@ Usage:
202
202
  dench email template create --name "Intro" --subject "Hi {{firstName}}" --html-file body.html [--text-file body.txt] [--json]
203
203
  dench email template preview --template <id> --data '{"firstName":"Ada"}' [--json]
204
204
  dench email campaign create --name "Q2 outreach" --identity <identityId|fromEmail> --template <id> [--json]
205
+ dench email campaign list [--status sending] [--limit 25] [--json]
205
206
  dench email campaign recipients add --campaign <id> --file recipients.jsonl [--json]
207
+ dench email campaign recipients list --campaign <id> [--status queued] [--limit 50] [--json]
208
+ dench email campaign recipients add-from-object --campaign <id> --object prospect --email-field "Email" --entries id1,id2 [--json]
206
209
  dench email campaign preview --campaign <id> [--sample 5] [--json]
207
210
  dench email campaign submit --campaign <id> [--json]
208
211
  dench email campaign status --campaign <id> [--json]
209
- dench email campaign pause|resume|cancel --campaign <id> [--json]`);
212
+ dench email campaign pause|resume|cancel --campaign <id> [--json]
213
+
214
+ Sequences (automatic multi-step follow-ups):
215
+ dench email sequence create --name "New-customer follow-up" --identity <identityId|fromEmail> [--stop-on-click] [--no-stop-on-reply] [--json]
216
+ dench email sequence list [--all] [--json]
217
+ dench email sequence status --sequence <id> [--json]
218
+ dench email sequence steps add --sequence <id> --subject "Hi {{firstName}}" --html-file step1.html [--delay-days 0] [--delay-hours 0] [--json]
219
+ dench email sequence steps update --step <id> [--subject ...] [--html-file ...] [--delay-days 3] [--json]
220
+ dench email sequence steps remove --step <id> [--json]
221
+ dench email sequence steps reorder --sequence <id> --steps id1,id2,id3 [--json]
222
+ dench email sequence activate|deactivate --sequence <id> [--json]
223
+ dench email sequence enroll --sequence <id> --file recipients.jsonl [--json]
224
+ dench email sequence enroll-from-object --sequence <id> --object prospect --email-field "Email" --entries id1,id2 [--json]
225
+ dench email sequence enrollments list --sequence <id> [--status active] [--limit 50] [--json]
226
+ dench email sequence enrollment pause|resume|stop --enrollment <id> [--json]
227
+ dench email sequence archive --sequence <id> [--json]`);
210
228
  }
211
229
 
212
230
  export async function runEmailCommand(ctx: EmailCliContext): Promise<void> {
@@ -235,9 +253,22 @@ export async function runEmailCommand(ctx: EmailCliContext): Promise<void> {
235
253
  await runCampaign(ctx);
236
254
  return;
237
255
  }
256
+ if (group === "sequence") {
257
+ await runSequence(ctx);
258
+ return;
259
+ }
238
260
  throw new EmailCliError(`Unknown dench email subcommand: ${group}`);
239
261
  }
240
262
 
263
+ /** Parse a comma-separated list of CRM entry ids. */
264
+ function parseEntryIds(value: string | undefined): string[] {
265
+ if (!value?.trim()) return [];
266
+ return value
267
+ .split(",")
268
+ .map((id) => id.trim())
269
+ .filter(Boolean);
270
+ }
271
+
241
272
  /**
242
273
  * Parse a comma-separated address list where each entry is either a bare
243
274
  * email or `Name <email>`.
@@ -662,11 +693,60 @@ async function runCampaign(ctx: EmailCliContext) {
662
693
  );
663
694
  return;
664
695
  }
696
+ if (sub === "list") {
697
+ const limitRaw = getFlag(ctx.args, "--limit");
698
+ const status = getFlag(ctx.args, "--status");
699
+ const payload = await callQuery(
700
+ ctx,
701
+ "functions/emailCampaigns:listCampaigns",
702
+ {
703
+ paginationOpts: {
704
+ numItems: limitRaw ? Number(limitRaw) : 25,
705
+ cursor: null,
706
+ },
707
+ status,
708
+ },
709
+ );
710
+ out(ctx, payload, JSON.stringify(payload, null, 2));
711
+ return;
712
+ }
665
713
  if (sub === "recipients") {
666
714
  const nested = ctx.args.shift();
715
+ if (nested === "list") {
716
+ const limitRaw = getFlag(ctx.args, "--limit");
717
+ const status = getFlag(ctx.args, "--status");
718
+ const payload = await callQuery(
719
+ ctx,
720
+ "functions/emailCampaigns:listRecipients",
721
+ {
722
+ campaignId: requiredFlag(ctx.args, "--campaign"),
723
+ paginationOpts: {
724
+ numItems: limitRaw ? Number(limitRaw) : 50,
725
+ cursor: null,
726
+ },
727
+ status,
728
+ },
729
+ );
730
+ out(ctx, payload, JSON.stringify(payload, null, 2));
731
+ return;
732
+ }
733
+ if (nested === "add-from-object") {
734
+ const payload = await callMutation(
735
+ ctx,
736
+ "functions/emailCampaigns:addRecipientsFromCrm",
737
+ {
738
+ campaignId: requiredFlag(ctx.args, "--campaign"),
739
+ objectName: requiredFlag(ctx.args, "--object"),
740
+ emailFieldName: getFlag(ctx.args, "--email-field"),
741
+ entryIds: parseEntryIds(requiredFlag(ctx.args, "--entries")),
742
+ },
743
+ );
744
+ out(ctx, payload, JSON.stringify(payload, null, 2));
745
+ return;
746
+ }
667
747
  if (nested !== "add") {
668
748
  throw new EmailCliError(
669
- "Usage: dench email campaign recipients add --campaign <id> --file recipients.jsonl",
749
+ "Usage: dench email campaign recipients add|list|add-from-object",
670
750
  );
671
751
  }
672
752
  const campaignId = requiredFlag(ctx.args, "--campaign");
@@ -734,6 +814,227 @@ async function runCampaign(ctx: EmailCliContext) {
734
814
  return;
735
815
  }
736
816
  throw new EmailCliError(
737
- "Usage: dench email campaign create|recipients|preview|submit|status|pause|resume|cancel",
817
+ "Usage: dench email campaign create|list|recipients|preview|submit|status|pause|resume|cancel",
818
+ );
819
+ }
820
+
821
+ async function runSequence(ctx: EmailCliContext) {
822
+ const sub = ctx.args.shift();
823
+ if (sub === "create") {
824
+ const stopOnReply = !hasFlag(ctx.args, "--no-stop-on-reply");
825
+ const payload = await callMutation(
826
+ ctx,
827
+ "functions/emailSequences:createSequence",
828
+ {
829
+ name: requiredFlag(ctx.args, "--name"),
830
+ identityId: requiredFlag(ctx.args, "--identity"),
831
+ replyToMode: getFlag(ctx.args, "--reply-to-mode"),
832
+ stopOnReply,
833
+ stopOnClick: getFlag(ctx.args, "--stop-on-click") !== undefined,
834
+ },
835
+ );
836
+ out(
837
+ ctx,
838
+ payload,
839
+ `Sequence created: ${(payload as JsonRecord).sequenceId}`,
840
+ );
841
+ return;
842
+ }
843
+ if (sub === "update") {
844
+ const payload = await callMutation(
845
+ ctx,
846
+ "functions/emailSequences:updateSequence",
847
+ {
848
+ sequenceId: requiredFlag(ctx.args, "--sequence"),
849
+ name: getFlag(ctx.args, "--name"),
850
+ identityId: getFlag(ctx.args, "--identity"),
851
+ replyToMode: getFlag(ctx.args, "--reply-to-mode"),
852
+ },
853
+ );
854
+ out(ctx, payload, JSON.stringify(payload, null, 2));
855
+ return;
856
+ }
857
+ if (sub === "activate" || sub === "deactivate") {
858
+ const payload = await callMutation(
859
+ ctx,
860
+ "functions/emailSequences:setSequenceActive",
861
+ {
862
+ sequenceId: requiredFlag(ctx.args, "--sequence"),
863
+ active: sub === "activate",
864
+ },
865
+ );
866
+ out(ctx, payload, JSON.stringify(payload, null, 2));
867
+ return;
868
+ }
869
+ if (sub === "archive") {
870
+ const payload = await callMutation(
871
+ ctx,
872
+ "functions/emailSequences:archiveSequence",
873
+ { sequenceId: requiredFlag(ctx.args, "--sequence") },
874
+ );
875
+ out(ctx, payload, JSON.stringify(payload, null, 2));
876
+ return;
877
+ }
878
+ if (sub === "list") {
879
+ const payload = await callQuery(
880
+ ctx,
881
+ "functions/emailSequences:listSequences",
882
+ { includeArchived: getFlag(ctx.args, "--all") !== undefined },
883
+ );
884
+ out(ctx, payload, JSON.stringify(payload, null, 2));
885
+ return;
886
+ }
887
+ if (sub === "status") {
888
+ const payload = await callQuery(
889
+ ctx,
890
+ "functions/emailSequences:getSequence",
891
+ { sequenceId: requiredFlag(ctx.args, "--sequence") },
892
+ );
893
+ out(ctx, payload, JSON.stringify(payload, null, 2));
894
+ return;
895
+ }
896
+ if (sub === "steps") {
897
+ const nested = ctx.args.shift();
898
+ if (nested === "add") {
899
+ const htmlBody = await readTextFlag(ctx.args, "--html", "--html-file");
900
+ if (!htmlBody) throw new EmailCliError("Missing --html or --html-file");
901
+ const delayDaysRaw = getFlag(ctx.args, "--delay-days");
902
+ const delayHoursRaw = getFlag(ctx.args, "--delay-hours");
903
+ const delayHours =
904
+ (delayDaysRaw ? Number(delayDaysRaw) * 24 : 0) +
905
+ (delayHoursRaw ? Number(delayHoursRaw) : 0);
906
+ const payload = await callMutation(
907
+ ctx,
908
+ "functions/emailSequences:addStep",
909
+ {
910
+ sequenceId: requiredFlag(ctx.args, "--sequence"),
911
+ subject: requiredFlag(ctx.args, "--subject"),
912
+ htmlBody,
913
+ textBody: await readTextFlag(ctx.args, "--text", "--text-file"),
914
+ delayHours,
915
+ },
916
+ );
917
+ out(ctx, payload, `Step added: ${(payload as JsonRecord).stepId}`);
918
+ return;
919
+ }
920
+ if (nested === "update") {
921
+ const delayDaysRaw = getFlag(ctx.args, "--delay-days");
922
+ const delayHoursRaw = getFlag(ctx.args, "--delay-hours");
923
+ const delayHours =
924
+ delayDaysRaw || delayHoursRaw
925
+ ? (delayDaysRaw ? Number(delayDaysRaw) * 24 : 0) +
926
+ (delayHoursRaw ? Number(delayHoursRaw) : 0)
927
+ : undefined;
928
+ const payload = await callMutation(
929
+ ctx,
930
+ "functions/emailSequences:updateStep",
931
+ {
932
+ stepId: requiredFlag(ctx.args, "--step"),
933
+ subject: getFlag(ctx.args, "--subject"),
934
+ htmlBody: await readTextFlag(ctx.args, "--html", "--html-file"),
935
+ textBody: await readTextFlag(ctx.args, "--text", "--text-file"),
936
+ delayHours,
937
+ },
938
+ );
939
+ out(ctx, payload, JSON.stringify(payload, null, 2));
940
+ return;
941
+ }
942
+ if (nested === "remove") {
943
+ const payload = await callMutation(
944
+ ctx,
945
+ "functions/emailSequences:removeStep",
946
+ { stepId: requiredFlag(ctx.args, "--step") },
947
+ );
948
+ out(ctx, payload, JSON.stringify(payload, null, 2));
949
+ return;
950
+ }
951
+ if (nested === "reorder") {
952
+ const payload = await callMutation(
953
+ ctx,
954
+ "functions/emailSequences:reorderSteps",
955
+ {
956
+ sequenceId: requiredFlag(ctx.args, "--sequence"),
957
+ orderedStepIds: parseEntryIds(requiredFlag(ctx.args, "--steps")),
958
+ },
959
+ );
960
+ out(ctx, payload, JSON.stringify(payload, null, 2));
961
+ return;
962
+ }
963
+ throw new EmailCliError(
964
+ "Usage: dench email sequence steps add|update|remove|reorder",
965
+ );
966
+ }
967
+ if (sub === "enroll") {
968
+ const file = getFlag(ctx.args, "--file");
969
+ const rows = file
970
+ ? await readRecipients(file)
971
+ : ((optionalJsonFlag(ctx.args, "--data") as JsonRecord[] | undefined) ??
972
+ []);
973
+ if (rows.length === 0) throw new EmailCliError("No recipients provided");
974
+ const payload = await callMutation(ctx, "functions/emailSequences:enroll", {
975
+ sequenceId: requiredFlag(ctx.args, "--sequence"),
976
+ recipients: toRecipients(rows),
977
+ });
978
+ out(ctx, payload, JSON.stringify(payload, null, 2));
979
+ return;
980
+ }
981
+ if (sub === "enroll-from-object") {
982
+ const payload = await callMutation(
983
+ ctx,
984
+ "functions/emailSequences:enrollFromCrm",
985
+ {
986
+ sequenceId: requiredFlag(ctx.args, "--sequence"),
987
+ objectName: requiredFlag(ctx.args, "--object"),
988
+ emailFieldName: getFlag(ctx.args, "--email-field"),
989
+ entryIds: parseEntryIds(requiredFlag(ctx.args, "--entries")),
990
+ },
991
+ );
992
+ out(ctx, payload, JSON.stringify(payload, null, 2));
993
+ return;
994
+ }
995
+ if (sub === "enrollments") {
996
+ const nested = ctx.args.shift();
997
+ if (nested !== "list") {
998
+ throw new EmailCliError("Usage: dench email sequence enrollments list");
999
+ }
1000
+ const limitRaw = getFlag(ctx.args, "--limit");
1001
+ const payload = await callQuery(
1002
+ ctx,
1003
+ "functions/emailSequences:listEnrollments",
1004
+ {
1005
+ sequenceId: requiredFlag(ctx.args, "--sequence"),
1006
+ paginationOpts: {
1007
+ numItems: limitRaw ? Number(limitRaw) : 50,
1008
+ cursor: null,
1009
+ },
1010
+ status: getFlag(ctx.args, "--status"),
1011
+ },
1012
+ );
1013
+ out(ctx, payload, JSON.stringify(payload, null, 2));
1014
+ return;
1015
+ }
1016
+ if (sub === "enrollment") {
1017
+ const nested = ctx.args.shift();
1018
+ const fn =
1019
+ nested === "pause"
1020
+ ? "pauseEnrollment"
1021
+ : nested === "resume"
1022
+ ? "resumeEnrollment"
1023
+ : nested === "stop"
1024
+ ? "stopEnrollment"
1025
+ : null;
1026
+ if (!fn) {
1027
+ throw new EmailCliError(
1028
+ "Usage: dench email sequence enrollment pause|resume|stop --enrollment <id>",
1029
+ );
1030
+ }
1031
+ const payload = await callMutation(ctx, `functions/emailSequences:${fn}`, {
1032
+ enrollmentId: requiredFlag(ctx.args, "--enrollment"),
1033
+ });
1034
+ out(ctx, payload, JSON.stringify(payload, null, 2));
1035
+ return;
1036
+ }
1037
+ throw new EmailCliError(
1038
+ "Usage: dench email sequence create|update|activate|deactivate|archive|list|status|steps|enroll|enroll-from-object|enrollments|enrollment",
738
1039
  );
739
1040
  }
@@ -1096,7 +1096,7 @@ const rawApiOperations = [
1096
1096
  id: "crm.templates.removeSampleData",
1097
1097
  group: "crm",
1098
1098
  summary:
1099
- "Delete a CRM template's sample rows (tagged \"Sample\"); schema, views, and automations stay.",
1099
+ 'Delete a CRM template\'s sample rows (tagged "Sample"); schema, views, and automations stay.',
1100
1100
  cli: "dench crm templates remove-sample-data",
1101
1101
  method: "POST",
1102
1102
  path: "/crm/templates/{templateId}/remove-sample-data",
@@ -1410,6 +1410,243 @@ const rawApiOperations = [
1410
1410
  responseSchema: anyResponse,
1411
1411
  backend: convex("mutation", "functions/emailCampaigns:cancelCampaign"),
1412
1412
  }),
1413
+ op({
1414
+ id: "email.campaign.list",
1415
+ group: "email",
1416
+ summary:
1417
+ "List managed email campaigns (paginated, optional status filter).",
1418
+ cli: "dench email campaign list",
1419
+ method: "GET",
1420
+ path: "/email/campaigns",
1421
+ requestSchema: anyObject,
1422
+ responseSchema: anyResponse,
1423
+ backend: convex("query", "functions/emailCampaigns:listCampaigns"),
1424
+ }),
1425
+ op({
1426
+ id: "email.campaign.recipients.list",
1427
+ group: "email",
1428
+ summary:
1429
+ "List a campaign's recipients (paginated, optional status filter).",
1430
+ cli: "dench email campaign recipients list",
1431
+ method: "GET",
1432
+ path: "/email/campaigns/{campaignId}/recipients",
1433
+ requestSchema: anyObject,
1434
+ responseSchema: anyResponse,
1435
+ backend: convex("query", "functions/emailCampaigns:listRecipients"),
1436
+ }),
1437
+ op({
1438
+ id: "email.campaign.recipients.addFromObject",
1439
+ group: "email",
1440
+ summary:
1441
+ "Add campaign recipients from a CRM object: pick the object, the email field, and the entry ids.",
1442
+ cli: "dench email campaign recipients add-from-object",
1443
+ method: "POST",
1444
+ path: "/email/campaigns/{campaignId}/recipients/from-object",
1445
+ requestSchema: anyObject,
1446
+ responseSchema: anyResponse,
1447
+ backend: convex(
1448
+ "mutation",
1449
+ "functions/emailCampaigns:addRecipientsFromCrm",
1450
+ ),
1451
+ }),
1452
+
1453
+ // Managed email sequences (multi-step drip cadences).
1454
+ op({
1455
+ id: "email.sequence.create",
1456
+ group: "email",
1457
+ summary:
1458
+ "Create a follow-up sequence; choose a verified sender and reply/stop behavior.",
1459
+ cli: "dench email sequence create",
1460
+ method: "POST",
1461
+ path: "/email/sequences",
1462
+ requestSchema: anyObject,
1463
+ responseSchema: anyResponse,
1464
+ backend: convex("mutation", "functions/emailSequences:createSequence"),
1465
+ }),
1466
+ op({
1467
+ id: "email.sequence.update",
1468
+ group: "email",
1469
+ summary: "Update a sequence's name, sender, or reply/stop settings.",
1470
+ cli: "dench email sequence update",
1471
+ method: "POST",
1472
+ path: "/email/sequences/{sequenceId}",
1473
+ requestSchema: anyObject,
1474
+ responseSchema: anyResponse,
1475
+ backend: convex("mutation", "functions/emailSequences:updateSequence"),
1476
+ }),
1477
+ op({
1478
+ id: "email.sequence.activate",
1479
+ group: "email",
1480
+ summary: "Activate a sequence so its enrollments begin sending.",
1481
+ cli: "dench email sequence activate",
1482
+ method: "POST",
1483
+ path: "/email/sequences/{sequenceId}/activate",
1484
+ requestSchema: anyObject,
1485
+ responseSchema: anyResponse,
1486
+ backend: {
1487
+ ...convex("mutation", "functions/emailSequences:setSequenceActive"),
1488
+ fixedArgs: { active: true },
1489
+ },
1490
+ }),
1491
+ op({
1492
+ id: "email.sequence.deactivate",
1493
+ group: "email",
1494
+ summary: "Deactivate a sequence (pause sending, revert to draft).",
1495
+ cli: "dench email sequence deactivate",
1496
+ method: "POST",
1497
+ path: "/email/sequences/{sequenceId}/deactivate",
1498
+ requestSchema: anyObject,
1499
+ responseSchema: anyResponse,
1500
+ backend: {
1501
+ ...convex("mutation", "functions/emailSequences:setSequenceActive"),
1502
+ fixedArgs: { active: false },
1503
+ },
1504
+ }),
1505
+ op({
1506
+ id: "email.sequence.archive",
1507
+ group: "email",
1508
+ summary: "Archive a sequence (stops all sending).",
1509
+ cli: "dench email sequence archive",
1510
+ method: "POST",
1511
+ path: "/email/sequences/{sequenceId}/archive",
1512
+ requestSchema: anyObject,
1513
+ responseSchema: anyResponse,
1514
+ backend: convex("mutation", "functions/emailSequences:archiveSequence"),
1515
+ }),
1516
+ op({
1517
+ id: "email.sequence.list",
1518
+ group: "email",
1519
+ summary: "List the workspace's follow-up sequences.",
1520
+ cli: "dench email sequence list",
1521
+ method: "GET",
1522
+ path: "/email/sequences",
1523
+ requestSchema: anyObject,
1524
+ responseSchema: anyResponse,
1525
+ backend: convex("query", "functions/emailSequences:listSequences"),
1526
+ }),
1527
+ op({
1528
+ id: "email.sequence.get",
1529
+ group: "email",
1530
+ summary: "Read a sequence with its ordered steps.",
1531
+ cli: "dench email sequence status",
1532
+ method: "GET",
1533
+ path: "/email/sequences/{sequenceId}",
1534
+ requestSchema: anyObject,
1535
+ responseSchema: anyResponse,
1536
+ backend: convex("query", "functions/emailSequences:getSequence"),
1537
+ }),
1538
+ op({
1539
+ id: "email.sequence.step.add",
1540
+ group: "email",
1541
+ summary: "Add a step (subject, body, and wait delay) to a sequence.",
1542
+ cli: "dench email sequence steps add",
1543
+ method: "POST",
1544
+ path: "/email/sequences/{sequenceId}/steps",
1545
+ requestSchema: anyObject,
1546
+ responseSchema: anyResponse,
1547
+ backend: convex("mutation", "functions/emailSequences:addStep"),
1548
+ }),
1549
+ op({
1550
+ id: "email.sequence.step.update",
1551
+ group: "email",
1552
+ summary: "Update a sequence step's subject, body, or delay.",
1553
+ cli: "dench email sequence steps update",
1554
+ method: "POST",
1555
+ path: "/email/sequences/steps/{stepId}",
1556
+ requestSchema: anyObject,
1557
+ responseSchema: anyResponse,
1558
+ backend: convex("mutation", "functions/emailSequences:updateStep"),
1559
+ }),
1560
+ op({
1561
+ id: "email.sequence.step.remove",
1562
+ group: "email",
1563
+ summary: "Remove a step from a sequence.",
1564
+ cli: "dench email sequence steps remove",
1565
+ method: "POST",
1566
+ path: "/email/sequences/steps/{stepId}/remove",
1567
+ requestSchema: anyObject,
1568
+ responseSchema: anyResponse,
1569
+ backend: convex("mutation", "functions/emailSequences:removeStep"),
1570
+ }),
1571
+ op({
1572
+ id: "email.sequence.step.reorder",
1573
+ group: "email",
1574
+ summary: "Reorder a sequence's steps.",
1575
+ cli: "dench email sequence steps reorder",
1576
+ method: "POST",
1577
+ path: "/email/sequences/{sequenceId}/steps/reorder",
1578
+ requestSchema: anyObject,
1579
+ responseSchema: anyResponse,
1580
+ backend: convex("mutation", "functions/emailSequences:reorderSteps"),
1581
+ }),
1582
+ op({
1583
+ id: "email.sequence.enroll",
1584
+ group: "email",
1585
+ summary: "Enroll explicit recipients into a sequence.",
1586
+ cli: "dench email sequence enroll",
1587
+ method: "POST",
1588
+ path: "/email/sequences/{sequenceId}/enroll",
1589
+ requestSchema: anyObject,
1590
+ responseSchema: anyResponse,
1591
+ backend: convex("mutation", "functions/emailSequences:enroll"),
1592
+ }),
1593
+ op({
1594
+ id: "email.sequence.enrollFromObject",
1595
+ group: "email",
1596
+ summary:
1597
+ "Enroll people from a CRM object: pick the object, the email field, and the entry ids.",
1598
+ cli: "dench email sequence enroll-from-object",
1599
+ method: "POST",
1600
+ path: "/email/sequences/{sequenceId}/enroll/from-object",
1601
+ requestSchema: anyObject,
1602
+ responseSchema: anyResponse,
1603
+ backend: convex("mutation", "functions/emailSequences:enrollFromCrm"),
1604
+ }),
1605
+ op({
1606
+ id: "email.sequence.enrollments.list",
1607
+ group: "email",
1608
+ summary:
1609
+ "List a sequence's enrollments (paginated, optional status filter).",
1610
+ cli: "dench email sequence enrollments list",
1611
+ method: "GET",
1612
+ path: "/email/sequences/{sequenceId}/enrollments",
1613
+ requestSchema: anyObject,
1614
+ responseSchema: anyResponse,
1615
+ backend: convex("query", "functions/emailSequences:listEnrollments"),
1616
+ }),
1617
+ op({
1618
+ id: "email.sequence.enrollment.pause",
1619
+ group: "email",
1620
+ summary: "Pause one enrollment.",
1621
+ cli: "dench email sequence enrollment pause",
1622
+ method: "POST",
1623
+ path: "/email/sequences/enrollments/{enrollmentId}/pause",
1624
+ requestSchema: anyObject,
1625
+ responseSchema: anyResponse,
1626
+ backend: convex("mutation", "functions/emailSequences:pauseEnrollment"),
1627
+ }),
1628
+ op({
1629
+ id: "email.sequence.enrollment.resume",
1630
+ group: "email",
1631
+ summary: "Resume one paused enrollment.",
1632
+ cli: "dench email sequence enrollment resume",
1633
+ method: "POST",
1634
+ path: "/email/sequences/enrollments/{enrollmentId}/resume",
1635
+ requestSchema: anyObject,
1636
+ responseSchema: anyResponse,
1637
+ backend: convex("mutation", "functions/emailSequences:resumeEnrollment"),
1638
+ }),
1639
+ op({
1640
+ id: "email.sequence.enrollment.stop",
1641
+ group: "email",
1642
+ summary: "Stop one enrollment (no further steps send).",
1643
+ cli: "dench email sequence enrollment stop",
1644
+ method: "POST",
1645
+ path: "/email/sequences/enrollments/{enrollmentId}/stop",
1646
+ requestSchema: anyObject,
1647
+ responseSchema: anyResponse,
1648
+ backend: convex("mutation", "functions/emailSequences:stopEnrollment"),
1649
+ }),
1413
1650
 
1414
1651
  // Routines.
1415
1652
  op({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dench.com/cli",
3
- "version": "2.2.1",
3
+ "version": "2.2.2",
4
4
  "description": "Dench agent workspace CLI. v2 unifies auth behind `dench signin`; the legacy `dench login` / `dench onboard` / `dench setup` / `dench what-can-i-do` / `dench register` commands now error with a redirect.",
5
5
  "type": "module",
6
6
  "bin": {