@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/ads.ts +2207 -0
- package/dench.ts +49 -0
- package/email.ts +305 -4
- package/lib/command-registry.ts +238 -1
- package/package.json +2 -1
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
|
|
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
|
}
|
package/lib/command-registry.ts
CHANGED
|
@@ -1096,7 +1096,7 @@ const rawApiOperations = [
|
|
|
1096
1096
|
id: "crm.templates.removeSampleData",
|
|
1097
1097
|
group: "crm",
|
|
1098
1098
|
summary:
|
|
1099
|
-
|
|
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.
|
|
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",
|