@dench.com/cli 2.7.5 → 2.7.7-staging.21
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 +1 -1
- package/crm.ts +164 -26
- package/dench.ts +2 -3
- package/email.ts +50 -6
- package/host.ts +1 -1
- package/lib/api-schemas.ts +101 -5
- package/lib/command-registry.ts +169 -3
- package/lib/enrichment-gateway.ts +61 -0
- package/lib/exa-companies.ts +389 -0
- package/lib/exa-people.ts +682 -0
- package/lib/exa-search-constraints.ts +70 -0
- package/package.json +1 -1
- package/search.ts +204 -9
package/README.md
CHANGED
package/crm.ts
CHANGED
|
@@ -687,6 +687,21 @@ const api = {
|
|
|
687
687
|
reposition: makeFunctionReference<"mutation">(
|
|
688
688
|
"functions/workspaceNav:repositionMyNavItem",
|
|
689
689
|
),
|
|
690
|
+
createGroup: makeFunctionReference<"mutation">(
|
|
691
|
+
"functions/workspaceNav:createNavGroup",
|
|
692
|
+
),
|
|
693
|
+
renameGroup: makeFunctionReference<"mutation">(
|
|
694
|
+
"functions/workspaceNav:renameNavGroup",
|
|
695
|
+
),
|
|
696
|
+
deleteGroup: makeFunctionReference<"mutation">(
|
|
697
|
+
"functions/workspaceNav:deleteNavGroup",
|
|
698
|
+
),
|
|
699
|
+
moveItemToGroup: makeFunctionReference<"mutation">(
|
|
700
|
+
"functions/workspaceNav:moveNavItemToGroup",
|
|
701
|
+
),
|
|
702
|
+
setRootPin: makeFunctionReference<"mutation">(
|
|
703
|
+
"functions/workspaceNav:setNavRootPin",
|
|
704
|
+
),
|
|
690
705
|
},
|
|
691
706
|
fields: {
|
|
692
707
|
list: makeFunctionReference<"query">("functions/crm/fields:list"),
|
|
@@ -959,13 +974,15 @@ async function runCrmMembersCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
959
974
|
}
|
|
960
975
|
|
|
961
976
|
/**
|
|
962
|
-
* `dench crm sidebar …` — the workspace app rail.
|
|
977
|
+
* `dench crm sidebar …` — the workspace app rail's Library.
|
|
963
978
|
*
|
|
964
|
-
*
|
|
965
|
-
*
|
|
966
|
-
*
|
|
967
|
-
*
|
|
968
|
-
*
|
|
979
|
+
* The whole layout is WORKSPACE-SHARED: one tree of rows and named
|
|
980
|
+
* groups for every member. Permissions split by operation — `move` (and
|
|
981
|
+
* `crm objects archive` / `crm views archive`) are ADMIN-ONLY because
|
|
982
|
+
* they change what everyone sees; `group …` operations and view pinning
|
|
983
|
+
* are open to any member. `list` shows the visible rows in tree order
|
|
984
|
+
* (rows inside a group carry `groupId`/`groupName`), the groups, what's
|
|
985
|
+
* hidden and why, and whether YOU are an admin (`viewerIsAdmin`).
|
|
969
986
|
*/
|
|
970
987
|
async function runSidebarCommand(ctx: CrmCliContext): Promise<void> {
|
|
971
988
|
const verb = ctx.args.shift() ?? "list";
|
|
@@ -997,6 +1014,85 @@ async function runSidebarCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
997
1014
|
);
|
|
998
1015
|
return;
|
|
999
1016
|
}
|
|
1017
|
+
// Pin/unpin a Library row (or a whole group) on the Sidebar's home
|
|
1018
|
+
// level shortcut strip. Member-level, workspace-shared — like view
|
|
1019
|
+
// pinning.
|
|
1020
|
+
case "pin":
|
|
1021
|
+
case "unpin": {
|
|
1022
|
+
const navId = shift(ctx.args, "nav id");
|
|
1023
|
+
assertNoUnknownFlags(ctx.args, `crm sidebar ${verb}`);
|
|
1024
|
+
out(
|
|
1025
|
+
ctx,
|
|
1026
|
+
await callMutation(ctx, api.sidebar.setRootPin, {
|
|
1027
|
+
navId,
|
|
1028
|
+
pinned: verb === "pin",
|
|
1029
|
+
}),
|
|
1030
|
+
);
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
case "group": {
|
|
1034
|
+
const groupVerb = ctx.args.shift();
|
|
1035
|
+
switch (groupVerb) {
|
|
1036
|
+
case "create": {
|
|
1037
|
+
const name = shift(ctx.args, "group name");
|
|
1038
|
+
assertNoUnknownFlags(ctx.args, "crm sidebar group create");
|
|
1039
|
+
out(ctx, await callMutation(ctx, api.sidebar.createGroup, { name }));
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
case "rename": {
|
|
1043
|
+
const groupId = shift(ctx.args, "group id");
|
|
1044
|
+
const name = shift(ctx.args, "new name");
|
|
1045
|
+
assertNoUnknownFlags(ctx.args, "crm sidebar group rename");
|
|
1046
|
+
out(
|
|
1047
|
+
ctx,
|
|
1048
|
+
await callMutation(ctx, api.sidebar.renameGroup, {
|
|
1049
|
+
groupId,
|
|
1050
|
+
name,
|
|
1051
|
+
}),
|
|
1052
|
+
);
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
case "delete": {
|
|
1056
|
+
const groupId = shift(ctx.args, "group id");
|
|
1057
|
+
assertNoUnknownFlags(ctx.args, "crm sidebar group delete");
|
|
1058
|
+
out(
|
|
1059
|
+
ctx,
|
|
1060
|
+
await callMutation(ctx, api.sidebar.deleteGroup, { groupId }),
|
|
1061
|
+
);
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
case "add": {
|
|
1065
|
+
const navId = shift(ctx.args, "nav id");
|
|
1066
|
+
const groupId = shift(ctx.args, "group id");
|
|
1067
|
+
assertNoUnknownFlags(ctx.args, "crm sidebar group add");
|
|
1068
|
+
out(
|
|
1069
|
+
ctx,
|
|
1070
|
+
await callMutation(ctx, api.sidebar.moveItemToGroup, {
|
|
1071
|
+
navId,
|
|
1072
|
+
groupId,
|
|
1073
|
+
}),
|
|
1074
|
+
);
|
|
1075
|
+
return;
|
|
1076
|
+
}
|
|
1077
|
+
case "remove": {
|
|
1078
|
+
const navId = shift(ctx.args, "nav id");
|
|
1079
|
+
assertNoUnknownFlags(ctx.args, "crm sidebar group remove");
|
|
1080
|
+
out(
|
|
1081
|
+
ctx,
|
|
1082
|
+
await callMutation(ctx, api.sidebar.moveItemToGroup, {
|
|
1083
|
+
navId,
|
|
1084
|
+
groupId: null,
|
|
1085
|
+
}),
|
|
1086
|
+
);
|
|
1087
|
+
return;
|
|
1088
|
+
}
|
|
1089
|
+
default:
|
|
1090
|
+
throw new CrmCliError(
|
|
1091
|
+
`Unknown crm sidebar group verb: ${groupVerb ?? "(none)"}. ` +
|
|
1092
|
+
"Use create|rename|delete|add|remove.",
|
|
1093
|
+
);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1000
1096
|
default:
|
|
1001
1097
|
throw new CrmCliError(`Unknown crm sidebar verb: ${verb}`);
|
|
1002
1098
|
}
|
|
@@ -1071,9 +1167,10 @@ async function runObjectsCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
1071
1167
|
out(ctx, await callMutation(ctx, api.objects.remove, { name }));
|
|
1072
1168
|
return;
|
|
1073
1169
|
}
|
|
1074
|
-
// Archive is the sidebar's "remove from
|
|
1170
|
+
// Archive is the sidebar's "remove from the rail", NOT a delete: the
|
|
1075
1171
|
// table, its rows and every link to it keep working, the row just
|
|
1076
|
-
// moves into the rail's Archived flyout. Team-shared
|
|
1172
|
+
// moves into the rail's Archived flyout. Team-shared, and ADMIN-ONLY
|
|
1173
|
+
// (the server rejects non-admin members).
|
|
1077
1174
|
case "archive":
|
|
1078
1175
|
case "unarchive": {
|
|
1079
1176
|
const name = shift(ctx.args, "object name");
|
|
@@ -2785,7 +2882,8 @@ type CrmFieldForEnrichment = FieldCandidate & {
|
|
|
2785
2882
|
category: EnrichmentCategory;
|
|
2786
2883
|
key: string;
|
|
2787
2884
|
apolloPath: string;
|
|
2788
|
-
inputFieldName
|
|
2885
|
+
inputFieldName?: string;
|
|
2886
|
+
strategy?: Record<string, unknown>;
|
|
2789
2887
|
};
|
|
2790
2888
|
};
|
|
2791
2889
|
|
|
@@ -2794,6 +2892,8 @@ type CrmEntryForEnrichment = {
|
|
|
2794
2892
|
fields: Record<string, unknown>;
|
|
2795
2893
|
};
|
|
2796
2894
|
|
|
2895
|
+
type CliEnrichmentProvider = "fullenrich" | "aviato";
|
|
2896
|
+
|
|
2797
2897
|
type EnrichmentTarget = {
|
|
2798
2898
|
category: EnrichmentCategory;
|
|
2799
2899
|
column: EnrichmentColumnDef;
|
|
@@ -2805,7 +2905,7 @@ type CellEnrichmentArgs = {
|
|
|
2805
2905
|
objectName: string;
|
|
2806
2906
|
entryId: string;
|
|
2807
2907
|
fieldName: string;
|
|
2808
|
-
provider?:
|
|
2908
|
+
provider?: CliEnrichmentProvider;
|
|
2809
2909
|
category?: EnrichmentCategory;
|
|
2810
2910
|
inputFieldName?: string;
|
|
2811
2911
|
apolloPath?: string;
|
|
@@ -2825,6 +2925,16 @@ function parseCategoryFlag(
|
|
|
2825
2925
|
throw new CrmCliError("--category must be people or company");
|
|
2826
2926
|
}
|
|
2827
2927
|
|
|
2928
|
+
function parseProviderFlag(
|
|
2929
|
+
value: string | undefined,
|
|
2930
|
+
): CliEnrichmentProvider | undefined {
|
|
2931
|
+
if (value === undefined) return undefined;
|
|
2932
|
+
if (value === "fullenrich" || value === "aviato") return value;
|
|
2933
|
+
throw new CrmCliError(
|
|
2934
|
+
`--provider must be fullenrich or aviato; received "${value}"`,
|
|
2935
|
+
);
|
|
2936
|
+
}
|
|
2937
|
+
|
|
2828
2938
|
function parsePositiveInt(
|
|
2829
2939
|
flag: string,
|
|
2830
2940
|
value: string | undefined,
|
|
@@ -2877,8 +2987,16 @@ function readFieldEnrichment(
|
|
|
2877
2987
|
typeof candidate.inputFieldName === "string"
|
|
2878
2988
|
? candidate.inputFieldName
|
|
2879
2989
|
: undefined;
|
|
2880
|
-
|
|
2881
|
-
|
|
2990
|
+
const strategy =
|
|
2991
|
+
candidate.strategy &&
|
|
2992
|
+
typeof candidate.strategy === "object" &&
|
|
2993
|
+
!Array.isArray(candidate.strategy)
|
|
2994
|
+
? (candidate.strategy as Record<string, unknown>)
|
|
2995
|
+
: undefined;
|
|
2996
|
+
if (!category || !key || !apolloPath || (!inputFieldName && !strategy)) {
|
|
2997
|
+
return undefined;
|
|
2998
|
+
}
|
|
2999
|
+
return { category, key, apolloPath, inputFieldName, strategy };
|
|
2882
3000
|
}
|
|
2883
3001
|
|
|
2884
3002
|
function asEntry(value: unknown): CrmEntryForEnrichment | null {
|
|
@@ -3003,7 +3121,7 @@ async function callEnrichmentGatewayForEntry(args: {
|
|
|
3003
3121
|
target: EnrichmentTarget;
|
|
3004
3122
|
entry: CrmEntryForEnrichment;
|
|
3005
3123
|
/** Explicit provider override. Defaults: people=fullenrich, company=aviato. */
|
|
3006
|
-
provider?:
|
|
3124
|
+
provider?: CliEnrichmentProvider;
|
|
3007
3125
|
gateway?: Pick<EnrichmentGatewayOptions, "apiKey" | "baseUrl">;
|
|
3008
3126
|
}): Promise<Record<string, unknown>> {
|
|
3009
3127
|
const { entry, target } = args;
|
|
@@ -3340,7 +3458,7 @@ async function runPeopleCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
3340
3458
|
}
|
|
3341
3459
|
case "enrich": {
|
|
3342
3460
|
const entryId = shift(ctx.args, "person entry id");
|
|
3343
|
-
const provider = getFlag(ctx.args, "--provider");
|
|
3461
|
+
const provider = parseProviderFlag(getFlag(ctx.args, "--provider"));
|
|
3344
3462
|
const fieldName = getFlag(ctx.args, "--field") ?? "LinkedIn URL";
|
|
3345
3463
|
const inputFieldName = getFlag(ctx.args, "--input-field");
|
|
3346
3464
|
const overwrite = hasFlag(ctx.args, "--overwrite");
|
|
@@ -3405,7 +3523,7 @@ async function runCompaniesCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
3405
3523
|
}
|
|
3406
3524
|
case "enrich": {
|
|
3407
3525
|
const entryId = shift(ctx.args, "company entry id");
|
|
3408
|
-
const provider = getFlag(ctx.args, "--provider");
|
|
3526
|
+
const provider = parseProviderFlag(getFlag(ctx.args, "--provider"));
|
|
3409
3527
|
const fieldName = getFlag(ctx.args, "--field") ?? "Company Name";
|
|
3410
3528
|
const inputFieldName = getFlag(ctx.args, "--input-field") ?? "Domain";
|
|
3411
3529
|
const overwrite = hasFlag(ctx.args, "--overwrite");
|
|
@@ -3438,7 +3556,7 @@ async function runEnrichCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
3438
3556
|
const objectName = shift(ctx.args, "object name");
|
|
3439
3557
|
const entryId = shift(ctx.args, "entry id");
|
|
3440
3558
|
const fieldName = shift(ctx.args, "field name");
|
|
3441
|
-
const provider = getFlag(ctx.args, "--provider");
|
|
3559
|
+
const provider = parseProviderFlag(getFlag(ctx.args, "--provider"));
|
|
3442
3560
|
const inputFieldName = getFlag(ctx.args, "--input-field");
|
|
3443
3561
|
const category = parseCategoryFlag(getFlag(ctx.args, "--category"));
|
|
3444
3562
|
const apolloPath = getFlag(ctx.args, "--apollo-path");
|
|
@@ -3463,7 +3581,7 @@ async function runEnrichCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
3463
3581
|
const objectName = shift(ctx.args, "object name");
|
|
3464
3582
|
const fieldName = getFlag(ctx.args, "--field");
|
|
3465
3583
|
const missingOnly = !hasFlag(ctx.args, "--all");
|
|
3466
|
-
const provider = getFlag(ctx.args, "--provider");
|
|
3584
|
+
const provider = parseProviderFlag(getFlag(ctx.args, "--provider"));
|
|
3467
3585
|
const inputFieldName = getFlag(ctx.args, "--input-field");
|
|
3468
3586
|
const category = parseCategoryFlag(getFlag(ctx.args, "--category"));
|
|
3469
3587
|
const apolloPath = getFlag(ctx.args, "--apollo-path");
|
|
@@ -3550,7 +3668,7 @@ Objects (CRM tables):
|
|
|
3550
3668
|
dench crm objects archive <name> | unarchive <name>
|
|
3551
3669
|
Hides/restores the object's workspace SIDEBAR row. Not a delete —
|
|
3552
3670
|
the table, its rows and every link keep working; the row just moves
|
|
3553
|
-
into the rail's Archived flyout. Team-shared.
|
|
3671
|
+
into the rail's Archived flyout. Team-shared and ADMIN-ONLY.
|
|
3554
3672
|
|
|
3555
3673
|
Views (saved CRM subsets):
|
|
3556
3674
|
dench crm views list <object> [--json]
|
|
@@ -3575,17 +3693,37 @@ Views (saved CRM subsets):
|
|
|
3575
3693
|
clears any archive stamp.
|
|
3576
3694
|
dench crm views archive <object> <view-name> | unarchive <object> <view-name>
|
|
3577
3695
|
Moves the view's sidebar row into the Archived flyout. Unarchiving
|
|
3578
|
-
re-asserts the pin so the row comes back.
|
|
3696
|
+
re-asserts the pin so the row comes back. ADMIN-ONLY: archiving
|
|
3697
|
+
hides the row for the whole workspace.
|
|
3579
3698
|
|
|
3580
|
-
Sidebar (the workspace app rail):
|
|
3699
|
+
Sidebar (the workspace app rail's Library — layout is WORKSPACE-SHARED):
|
|
3581
3700
|
dench crm sidebar list [--json]
|
|
3582
|
-
Visible rows in order
|
|
3583
|
-
|
|
3701
|
+
Visible rows in tree order (rows inside a group carry groupId/
|
|
3702
|
+
groupName), the groups, what's hidden and why (archived vs. an
|
|
3703
|
+
unpinned view), and whether you are an admin (viewerIsAdmin).
|
|
3584
3704
|
dench crm sidebar move <nav-id> <top|bottom|N>
|
|
3585
|
-
Reposition one row
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3705
|
+
Reposition one row (or a whole group by its group id) among the
|
|
3706
|
+
TOP-LEVEL slots — a group counts as one slot, and a row inside a
|
|
3707
|
+
group is pulled out to the top level. Nav ids come from 'sidebar
|
|
3708
|
+
list' — e.g. 'crm-object:task', 'crm-view:task:Due%20today',
|
|
3709
|
+
'crm-people', 'group:abc123'. Automations and Outreach are fixed
|
|
3710
|
+
home-level tabs, not Library rows. ADMIN-ONLY: the order is
|
|
3711
|
+
shared by every member.
|
|
3712
|
+
dench crm sidebar group create <name>
|
|
3713
|
+
New named group (e.g. "Hiring") at the bottom of the Library.
|
|
3714
|
+
Open to any member; prints the group id.
|
|
3715
|
+
dench crm sidebar group rename <group-id> <new-name>
|
|
3716
|
+
dench crm sidebar group delete <group-id>
|
|
3717
|
+
Dissolves the group; its rows return to the top level in its slot.
|
|
3718
|
+
dench crm sidebar group add <nav-id> <group-id>
|
|
3719
|
+
File a row into a group (appended at its end).
|
|
3720
|
+
dench crm sidebar group remove <nav-id>
|
|
3721
|
+
Move a row out of its group, back to the top level.
|
|
3722
|
+
dench crm sidebar pin <nav-id> | unpin <nav-id>
|
|
3723
|
+
Pin/unpin a Library row — or a whole group (group id) — on the
|
|
3724
|
+
Sidebar's home level, under Chats/Inbox/Meetings/Automations/
|
|
3725
|
+
Outreach/Library.
|
|
3726
|
+
A pin is a shared shortcut, not a move; open to any member.
|
|
3589
3727
|
|
|
3590
3728
|
Fields (columns):
|
|
3591
3729
|
dench crm fields list <object> [--json]
|
package/dench.ts
CHANGED
|
@@ -538,9 +538,8 @@ Monetize the AI "thinking…" line (supported hooks only — no file patching):
|
|
|
538
538
|
dench ads uninstall [--target claude|shell|agentmd|all]
|
|
539
539
|
Aliases: dench ad, dench adline. Help: dench ads help
|
|
540
540
|
|
|
541
|
-
Managed email campaigns
|
|
542
|
-
dench email
|
|
543
|
-
dench email template create --name "Intro" --subject "Hi {{firstName}}" --html-file body.html [--json]
|
|
541
|
+
Managed email campaigns:
|
|
542
|
+
dench email campaign create --name "Q2 outreach" --mailbox you@gmail.com --template <id> [--json]
|
|
544
543
|
dench email campaign create --name "Q2 outreach" --identity <id> --template <id> [--json]
|
|
545
544
|
dench email campaign recipients add --campaign <id> --file recipients.jsonl [--json]
|
|
546
545
|
dench email campaign submit --campaign <id> [--json]
|
package/email.ts
CHANGED
|
@@ -118,6 +118,48 @@ function requiredFlag(args: string[], name: string): string {
|
|
|
118
118
|
return value;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
async function resolveMailboxAccountIds(
|
|
122
|
+
ctx: EmailCliContext,
|
|
123
|
+
mailbox: string,
|
|
124
|
+
): Promise<string[]> {
|
|
125
|
+
const payload = await callQuery(
|
|
126
|
+
ctx,
|
|
127
|
+
"functions/emailMailboxAccounts:listAccounts",
|
|
128
|
+
{},
|
|
129
|
+
);
|
|
130
|
+
const accounts = Array.isArray(payload) ? (payload as JsonRecord[]) : [];
|
|
131
|
+
const needle = mailbox.trim().toLowerCase();
|
|
132
|
+
const match = accounts.find((row) => {
|
|
133
|
+
const id = typeof row._id === "string" ? row._id : "";
|
|
134
|
+
const email = typeof row.email === "string" ? row.email.toLowerCase() : "";
|
|
135
|
+
return id === mailbox.trim() || email === needle;
|
|
136
|
+
});
|
|
137
|
+
if (!match || typeof match._id !== "string") {
|
|
138
|
+
throw new EmailCliError(
|
|
139
|
+
`Connected inbox not found: ${mailbox}. Connect Gmail or Outlook in Outreach → Accounts.`,
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
return [match._id];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function campaignSenderArgs(ctx: EmailCliContext): Promise<{
|
|
146
|
+
identityId?: string;
|
|
147
|
+
mailboxAccountIds?: string[];
|
|
148
|
+
}> {
|
|
149
|
+
const identity = getFlag(ctx.args, "--identity")?.trim();
|
|
150
|
+
const mailbox = getFlag(ctx.args, "--mailbox")?.trim();
|
|
151
|
+
if (identity && mailbox) {
|
|
152
|
+
throw new EmailCliError("Pass --mailbox or --identity, not both.");
|
|
153
|
+
}
|
|
154
|
+
if (mailbox) {
|
|
155
|
+
return { mailboxAccountIds: await resolveMailboxAccountIds(ctx, mailbox) };
|
|
156
|
+
}
|
|
157
|
+
if (identity) return { identityId: identity };
|
|
158
|
+
throw new EmailCliError(
|
|
159
|
+
"Pass --mailbox <email> (connected inbox) or --identity <sender>.",
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
121
163
|
function optionalJsonFlag(args: string[], name: string): unknown | undefined {
|
|
122
164
|
const raw = getFlag(args, name);
|
|
123
165
|
return raw === undefined ? undefined : parseJson(raw);
|
|
@@ -185,7 +227,7 @@ function toRecipients(rows: JsonRecord[]) {
|
|
|
185
227
|
}
|
|
186
228
|
|
|
187
229
|
export function emailHelp() {
|
|
188
|
-
console.log(`dench email — Dench Emailing Service
|
|
230
|
+
console.log(`dench email — outbound email from a connected inbox (or Dench Emailing Service)
|
|
189
231
|
|
|
190
232
|
Usage:
|
|
191
233
|
dench email identity verify --from founder@example.com [--json]
|
|
@@ -204,7 +246,7 @@ Usage:
|
|
|
204
246
|
dench email message list [--limit 25] [--json]
|
|
205
247
|
dench email template create --name "Intro" --subject "Hi {{firstName}}" --html-file body.html [--text-file body.txt] [--json]
|
|
206
248
|
dench email template preview --template <id> --data '{"firstName":"Ada"}' [--json]
|
|
207
|
-
dench email campaign create --name "Q2 outreach" --identity <identityId|fromEmail> --template <id> [--json]
|
|
249
|
+
dench email campaign create --name "Q2 outreach" (--mailbox you@gmail.com | --identity <identityId|fromEmail>) --template <id> [--json]
|
|
208
250
|
dench email campaign list [--status sending] [--limit 25] [--json]
|
|
209
251
|
dench email campaign recipients add --campaign <id> --file recipients.jsonl [--json]
|
|
210
252
|
dench email campaign recipients list --campaign <id> [--status queued] [--limit 50] [--json]
|
|
@@ -215,7 +257,7 @@ Usage:
|
|
|
215
257
|
dench email campaign pause|resume|cancel --campaign <id> [--json]
|
|
216
258
|
|
|
217
259
|
Sequences (automatic multi-step follow-ups):
|
|
218
|
-
dench email sequence create --name "New-customer follow-up" --identity <identityId|fromEmail> [--stop-on-click] [--no-stop-on-reply] [--json]
|
|
260
|
+
dench email sequence create --name "New-customer follow-up" (--mailbox you@gmail.com | --identity <identityId|fromEmail>) [--stop-on-click] [--no-stop-on-reply] [--json]
|
|
219
261
|
dench email sequence list [--all] [--json]
|
|
220
262
|
dench email sequence status --sequence <id> [--json]
|
|
221
263
|
dench email sequence steps add --sequence <id> --subject "Hi {{firstName}}" --html-file step1.html [--delay-days 0] [--delay-hours 0] [--json]
|
|
@@ -495,7 +537,7 @@ async function runIdentity(ctx: EmailCliContext) {
|
|
|
495
537
|
const identities = await listIdentitiesQuery(ctx, domain);
|
|
496
538
|
const text =
|
|
497
539
|
identities.length === 0
|
|
498
|
-
? "No sending identities
|
|
540
|
+
? "No sending identities. Connect Gmail or Outlook in Outreach → Accounts."
|
|
499
541
|
: identities
|
|
500
542
|
.map(
|
|
501
543
|
(identity) =>
|
|
@@ -716,12 +758,13 @@ async function runTemplate(ctx: EmailCliContext) {
|
|
|
716
758
|
async function runCampaign(ctx: EmailCliContext) {
|
|
717
759
|
const sub = ctx.args.shift();
|
|
718
760
|
if (sub === "create") {
|
|
761
|
+
const sender = await campaignSenderArgs(ctx);
|
|
719
762
|
const payload = await callMutation(
|
|
720
763
|
ctx,
|
|
721
764
|
"functions/emailCampaigns:createCampaign",
|
|
722
765
|
{
|
|
723
766
|
name: requiredFlag(ctx.args, "--name"),
|
|
724
|
-
|
|
767
|
+
...sender,
|
|
725
768
|
templateId: requiredFlag(ctx.args, "--template"),
|
|
726
769
|
scheduledAt: getFlag(ctx.args, "--scheduled-at")
|
|
727
770
|
? Date.parse(requiredFlag(ctx.args, "--scheduled-at"))
|
|
@@ -867,12 +910,13 @@ async function runSequence(ctx: EmailCliContext) {
|
|
|
867
910
|
const sub = ctx.args.shift();
|
|
868
911
|
if (sub === "create") {
|
|
869
912
|
const stopOnReply = !hasFlag(ctx.args, "--no-stop-on-reply");
|
|
913
|
+
const sender = await campaignSenderArgs(ctx);
|
|
870
914
|
const payload = await callMutation(
|
|
871
915
|
ctx,
|
|
872
916
|
"functions/emailSequences:createSequence",
|
|
873
917
|
{
|
|
874
918
|
name: requiredFlag(ctx.args, "--name"),
|
|
875
|
-
|
|
919
|
+
...sender,
|
|
876
920
|
replyToMode: getFlag(ctx.args, "--reply-to-mode"),
|
|
877
921
|
stopOnReply,
|
|
878
922
|
stopOnClick: getFlag(ctx.args, "--stop-on-click") !== undefined,
|
package/host.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export const PRODUCTION_HOST = "https://dench.com";
|
|
2
|
-
export const STAGING_HOST = "https://
|
|
2
|
+
export const STAGING_HOST = "https://staging.dench.com";
|
|
3
3
|
export const LOCAL_HOST = "http://localhost:3000";
|
|
4
4
|
export const DEFAULT_HOST = PRODUCTION_HOST;
|
|
5
5
|
|
package/lib/api-schemas.ts
CHANGED
|
@@ -791,6 +791,12 @@ const exaResultItem = z
|
|
|
791
791
|
image: z.string().optional(),
|
|
792
792
|
favicon: z.string().optional(),
|
|
793
793
|
id: z.string().optional(),
|
|
794
|
+
entities: z
|
|
795
|
+
.array(z.record(z.string(), z.unknown()))
|
|
796
|
+
.optional()
|
|
797
|
+
.describe(
|
|
798
|
+
"Structured entity metadata returned by category=people or category=company.",
|
|
799
|
+
),
|
|
794
800
|
})
|
|
795
801
|
.passthrough()
|
|
796
802
|
.describe("An Exa search/contents result (provider passthrough).");
|
|
@@ -1058,11 +1064,26 @@ const exaSearch = z.object({
|
|
|
1058
1064
|
"financial report",
|
|
1059
1065
|
"people",
|
|
1060
1066
|
])
|
|
1061
|
-
.optional()
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1067
|
+
.optional()
|
|
1068
|
+
.describe(
|
|
1069
|
+
"Entity categories return structured entities. Domain filters are unsupported for people; published-date filters are unsupported for people and company.",
|
|
1070
|
+
),
|
|
1071
|
+
includeDomains: z
|
|
1072
|
+
.array(z.string())
|
|
1073
|
+
.optional()
|
|
1074
|
+
.describe("Unsupported for category=people."),
|
|
1075
|
+
excludeDomains: z
|
|
1076
|
+
.array(z.string())
|
|
1077
|
+
.optional()
|
|
1078
|
+
.describe("Unsupported for category=people."),
|
|
1079
|
+
startPublishedDate: z
|
|
1080
|
+
.string()
|
|
1081
|
+
.optional()
|
|
1082
|
+
.describe("Unsupported for category=people and category=company."),
|
|
1083
|
+
endPublishedDate: z
|
|
1084
|
+
.string()
|
|
1085
|
+
.optional()
|
|
1086
|
+
.describe("Unsupported for category=people and category=company."),
|
|
1066
1087
|
includeText: z.array(z.string()).optional(),
|
|
1067
1088
|
excludeText: z.array(z.string()).optional(),
|
|
1068
1089
|
userLocation: z.string().optional(),
|
|
@@ -1384,6 +1405,19 @@ export const requestSchemas: Record<string, ZodTypeAny> = {
|
|
|
1384
1405
|
to: z.string().describe("New object name."),
|
|
1385
1406
|
}),
|
|
1386
1407
|
"crm.objects.delete": z.object({ name: z.string() }),
|
|
1408
|
+
"crm.objects.archive": z.object({ name: z.string() }),
|
|
1409
|
+
"crm.objects.unarchive": z.object({ name: z.string() }),
|
|
1410
|
+
|
|
1411
|
+
// The workspace app rail
|
|
1412
|
+
"crm.sidebar.list": z.object({}),
|
|
1413
|
+
"crm.sidebar.move": z.object({
|
|
1414
|
+
navId: z
|
|
1415
|
+
.string()
|
|
1416
|
+
.describe("Nav id from `sidebar list`, e.g. crm-object:task."),
|
|
1417
|
+
position: z
|
|
1418
|
+
.union([z.literal("top"), z.literal("bottom"), z.number().int().min(1)])
|
|
1419
|
+
.describe("Where to land among the visible rows; N is 1-based."),
|
|
1420
|
+
}),
|
|
1387
1421
|
|
|
1388
1422
|
// CRM saved views
|
|
1389
1423
|
"crm.views.list": z.object({ objectName: z.string() }),
|
|
@@ -1424,6 +1458,14 @@ export const requestSchemas: Record<string, ZodTypeAny> = {
|
|
|
1424
1458
|
objectName: z.string(),
|
|
1425
1459
|
viewName: z.string(),
|
|
1426
1460
|
}),
|
|
1461
|
+
"crm.views.archive": z.object({
|
|
1462
|
+
objectName: z.string(),
|
|
1463
|
+
viewName: z.string(),
|
|
1464
|
+
}),
|
|
1465
|
+
"crm.views.unarchive": z.object({
|
|
1466
|
+
objectName: z.string(),
|
|
1467
|
+
viewName: z.string(),
|
|
1468
|
+
}),
|
|
1427
1469
|
|
|
1428
1470
|
// CRM fields
|
|
1429
1471
|
"crm.fields.list": z.object({ objectName: z.string() }),
|
|
@@ -1991,6 +2033,12 @@ export const requestSchemas: Record<string, ZodTypeAny> = {
|
|
|
1991
2033
|
"tool.search": composioToolSearch,
|
|
1992
2034
|
"tool.run": composioToolRun,
|
|
1993
2035
|
"tool.disconnect": z.object({ connectionId: z.string() }),
|
|
2036
|
+
"tool.label.set": z.object({
|
|
2037
|
+
connectionId: z.string(),
|
|
2038
|
+
displayName: z
|
|
2039
|
+
.string()
|
|
2040
|
+
.describe("Human name for the account. Empty clears it."),
|
|
2041
|
+
}),
|
|
1994
2042
|
};
|
|
1995
2043
|
|
|
1996
2044
|
// ---------------------------------------------------------------------------
|
|
@@ -2177,6 +2225,22 @@ export const responseSchemas: Record<string, ZodTypeAny> = {
|
|
|
2177
2225
|
"crm.objects.update": okResponse,
|
|
2178
2226
|
"crm.objects.rename": okResponse,
|
|
2179
2227
|
"crm.objects.delete": okResponse,
|
|
2228
|
+
"crm.objects.archive": okResponse,
|
|
2229
|
+
"crm.objects.unarchive": okResponse,
|
|
2230
|
+
|
|
2231
|
+
// The workspace app rail
|
|
2232
|
+
"crm.sidebar.list": z.object({
|
|
2233
|
+
visible: z.array(z.record(z.string(), z.unknown())),
|
|
2234
|
+
hidden: z.array(z.record(z.string(), z.unknown())),
|
|
2235
|
+
}),
|
|
2236
|
+
"crm.sidebar.move": z.object({
|
|
2237
|
+
ok: z.boolean(),
|
|
2238
|
+
navId: z.string(),
|
|
2239
|
+
/** 1-based slot the row landed in, or null when the move was refused. */
|
|
2240
|
+
position: z.number().nullable(),
|
|
2241
|
+
visibleOrder: z.array(z.string()).optional(),
|
|
2242
|
+
error: z.string().optional(),
|
|
2243
|
+
}),
|
|
2180
2244
|
|
|
2181
2245
|
// CRM saved views
|
|
2182
2246
|
"crm.views.list": z.object({
|
|
@@ -2205,6 +2269,8 @@ export const responseSchemas: Record<string, ZodTypeAny> = {
|
|
|
2205
2269
|
viewName: z.string(),
|
|
2206
2270
|
removed: z.boolean(),
|
|
2207
2271
|
}),
|
|
2272
|
+
"crm.views.archive": okResponse,
|
|
2273
|
+
"crm.views.unarchive": okResponse,
|
|
2208
2274
|
|
|
2209
2275
|
// CRM fields
|
|
2210
2276
|
"crm.fields.list": z.array(crmFieldDoc),
|
|
@@ -2676,6 +2742,15 @@ export const responseSchemas: Record<string, ZodTypeAny> = {
|
|
|
2676
2742
|
})
|
|
2677
2743
|
.passthrough()
|
|
2678
2744
|
.describe("Composio delete response (provider passthrough)."),
|
|
2745
|
+
"tool.labels.list": z.object({
|
|
2746
|
+
labels: z.array(
|
|
2747
|
+
z.object({ composioConnectionId: z.string(), displayName: z.string() }),
|
|
2748
|
+
),
|
|
2749
|
+
}),
|
|
2750
|
+
"tool.label.set": z.object({
|
|
2751
|
+
ok: z.boolean(),
|
|
2752
|
+
displayName: z.string().nullable(),
|
|
2753
|
+
}),
|
|
2679
2754
|
|
|
2680
2755
|
// Agent config
|
|
2681
2756
|
"agentConfig.identity.show": agentConfigPayload,
|
|
@@ -2714,6 +2789,27 @@ export type ConvexExtras = {
|
|
|
2714
2789
|
export const convexExtras: Record<string, ConvexExtras> = {
|
|
2715
2790
|
"cron.enable": { fixedArgs: { enabled: true } },
|
|
2716
2791
|
"cron.disable": { fixedArgs: { enabled: false } },
|
|
2792
|
+
// Archive/unarchive are one mutation behind two paths, so the flag comes
|
|
2793
|
+
// from the route rather than the body — a caller cannot un-archive by
|
|
2794
|
+
// POSTing `{ archived: false }` to `/archive`.
|
|
2795
|
+
"crm.objects.archive": { fixedArgs: { archived: true } },
|
|
2796
|
+
"crm.objects.unarchive": { fixedArgs: { archived: false } },
|
|
2797
|
+
// Same mutation for a saved view, but the path calls the object
|
|
2798
|
+
// `objectName` while `setArchived` calls it `name`.
|
|
2799
|
+
"crm.views.archive": {
|
|
2800
|
+
transformArgs: (input) => ({
|
|
2801
|
+
name: input.objectName,
|
|
2802
|
+
viewName: input.viewName,
|
|
2803
|
+
}),
|
|
2804
|
+
fixedArgs: { archived: true },
|
|
2805
|
+
},
|
|
2806
|
+
"crm.views.unarchive": {
|
|
2807
|
+
transformArgs: (input) => ({
|
|
2808
|
+
name: input.objectName,
|
|
2809
|
+
viewName: input.viewName,
|
|
2810
|
+
}),
|
|
2811
|
+
fixedArgs: { archived: false },
|
|
2812
|
+
},
|
|
2717
2813
|
// PATCH /crm/objects/{objectName}/views/{viewName} — the path's
|
|
2718
2814
|
// viewName is the upsert key: fold it into `view.name` and force
|
|
2719
2815
|
// replaceExisting so the mutation overwrites instead of erroring on
|