@dench.com/cli 2.2.1 → 2.2.3

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/dench.ts CHANGED
@@ -511,6 +511,17 @@ Cloud browser (Browserbase, runs entirely in Dench Cloud):
511
511
  dench browser stop
512
512
  Help: dench browser help
513
513
 
514
+ Monetize the AI "thinking…" line (supported hooks only — no file patching):
515
+ dench ads Print the sponsored ad in a rainbow box
516
+ dench ads install [--target claude|shell|agentmd|all] [--apply]
517
+ dench ads refresh (rotate the cached ad; flush impressions)
518
+ dench ads earnings Show real USD ad earnings
519
+ dench ads payout setup Connect Stripe for real USD cash payouts
520
+ dench ads withdraw --amount all
521
+ dench ads status [--json]
522
+ dench ads uninstall [--target claude|shell|agentmd|all]
523
+ Aliases: dench ad, dench adline. Help: dench ads help
524
+
514
525
  Managed email campaigns (Dench Emailing Service):
515
526
  dench email identity verify --from founder@example.com [--json]
516
527
  dench email template create --name "Intro" --subject "Hi {{firstName}}" --html-file body.html [--json]
@@ -4052,6 +4063,44 @@ async function main() {
4052
4063
  return;
4053
4064
  }
4054
4065
 
4066
+ // `dench ads` is canonical; `dench ad` and `dench adline` are aliases for
4067
+ // the same command (one code path in ./ads).
4068
+ if (command === "ads" || command === "ad" || command === "adline") {
4069
+ const subArgs = args.slice(args.indexOf(command) + 1);
4070
+ const { runAdsCommand } = await import("./ads");
4071
+ // Earning is opt-in: resolve the agent session token best-effort so a
4072
+ // signed-in developer gets credited on flush, but never require login —
4073
+ // the ad must still render for everyone. getRuntime throws when there's
4074
+ // no session; swallow that and run anonymously.
4075
+ //
4076
+ // We deliberately attribute earnings ONLY for an interactive, stored
4077
+ // `session` token, NOT for `apiKey` mode or the sandbox agent-session
4078
+ // fallback. Those mean a sandbox / unattended automation context (cron,
4079
+ // CI, the chat-turn harness) — there's no human looking at the terminal,
4080
+ // so counting those impressions would be the exact spam vector we're
4081
+ // trying to close. Headless runs show the ad but earn nothing by design.
4082
+ let authToken: string | undefined;
4083
+ let convexUrl: string | undefined;
4084
+ let host: string | undefined;
4085
+ try {
4086
+ const runtime = await getRuntime();
4087
+ if (runtime.mode === "session") {
4088
+ const sandboxAgentToken = process.env.DENCH_AGENT_SESSION_TOKEN?.trim();
4089
+ const isSandboxAgentSession =
4090
+ sandboxAgentToken && runtime.sessionToken === sandboxAgentToken;
4091
+ if (!isSandboxAgentSession) {
4092
+ authToken = runtime.sessionToken;
4093
+ convexUrl = runtime.convexUrl;
4094
+ host = runtime.host;
4095
+ }
4096
+ }
4097
+ } catch {
4098
+ // not signed in — show the ad without attributing earnings
4099
+ }
4100
+ await runAdsCommand({ args: subArgs, authToken, convexUrl, host });
4101
+ return;
4102
+ }
4103
+
4055
4104
  if (command === "chat") {
4056
4105
  const subArgs = args.slice(args.indexOf("chat") + 1);
4057
4106
  const sub = subArgs[0];
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.3",
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": {
@@ -28,6 +28,7 @@
28
28
  "members.ts",
29
29
  "cron.ts",
30
30
  "browser.ts",
31
+ "ads.ts",
31
32
  "search.ts",
32
33
  "image.ts",
33
34
  "tools.ts",