@dench.com/cli 2.7.8 → 2.7.10-staging.100
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 +103 -21
- package/email.ts +186 -4
- package/lib/api-schemas.ts +23 -0
- package/lib/command-registry.ts +139 -4
- package/package.json +2 -1
- package/tools.ts +382 -59
- package/web.ts +356 -0
package/dench.ts
CHANGED
|
@@ -517,6 +517,15 @@ Live web search (Exa via the Dench Gateway, auths with the active \`dench signin
|
|
|
517
517
|
dench search answer "<query>" [--json]
|
|
518
518
|
Help: dench search help
|
|
519
519
|
|
|
520
|
+
Dench web (page read, brand, sitemap, crawl, screenshot, extract — prefer these over writing your own scraper):
|
|
521
|
+
dench web scrape <url> [--json]
|
|
522
|
+
dench web brand <domain|email> [--json]
|
|
523
|
+
dench web sitemap <domain> [--limit N] [--json]
|
|
524
|
+
dench web crawl <url> [--max-pages N] [--json]
|
|
525
|
+
dench web screenshot <url> [--full-page] [--json]
|
|
526
|
+
dench web extract <url> --schema '<json>' [--json]
|
|
527
|
+
Help: dench web help
|
|
528
|
+
|
|
520
529
|
Cloud browser (runs entirely in Dench Cloud):
|
|
521
530
|
dench browser open [url] [--json]
|
|
522
531
|
dench browser inspect [--json]
|
|
@@ -809,9 +818,20 @@ function integrationsHelp() {
|
|
|
809
818
|
Usage:
|
|
810
819
|
dench integrations [--json]
|
|
811
820
|
|
|
812
|
-
Lists
|
|
813
|
-
|
|
814
|
-
|
|
821
|
+
Lists every app connected to this organization, from both brokers:
|
|
822
|
+
|
|
823
|
+
- Composio apps (Slack, GitHub, Notion, …), each with a
|
|
824
|
+
\`connectedAccountId\` you can pass to \`dench tool run --account\`.
|
|
825
|
+
- Gmail, Outlook, Calendar and LinkedIn, which Dench connects itself.
|
|
826
|
+
These show \`source: "unipile"\` and have no connected-account id;
|
|
827
|
+
reach their data through \`dench crm\` (calendar events sync into the
|
|
828
|
+
\`meeting\` object) and \`dench email\`.
|
|
829
|
+
|
|
830
|
+
Composio does not broker those four, so they appear ONLY here — a
|
|
831
|
+
missing calendar in this list means it is genuinely not connected.
|
|
832
|
+
|
|
833
|
+
Authenticates with the active \`dench signin\` agent session or
|
|
834
|
+
DENCH_API_KEY (Bearer), whichever is set. Same output as
|
|
815
835
|
\`dench tool status\`.
|
|
816
836
|
`);
|
|
817
837
|
}
|
|
@@ -2657,6 +2677,27 @@ function encodeDevCrmSessionToken(args: {
|
|
|
2657
2677
|
return `dch_dev_${encodeURIComponent(args.workspaceSlug)}:${encodeURIComponent(args.devKey)}`;
|
|
2658
2678
|
}
|
|
2659
2679
|
|
|
2680
|
+
/**
|
|
2681
|
+
* Convex credentials for the Unipile half of a connection listing.
|
|
2682
|
+
*
|
|
2683
|
+
* Gmail, Outlook, Calendar and LinkedIn are Dench's own connections and live in
|
|
2684
|
+
* Convex, so listing them needs a CRM bearer — not the gateway key that carries
|
|
2685
|
+
* the Composio half. Every runtime already holds both; this just picks the
|
|
2686
|
+
* shape `requireCrmReadAccess` accepts.
|
|
2687
|
+
*/
|
|
2688
|
+
function unipileQueryAuth(runtime: Runtime): {
|
|
2689
|
+
convex: ConvexHttpClient;
|
|
2690
|
+
sessionToken: string;
|
|
2691
|
+
} {
|
|
2692
|
+
return {
|
|
2693
|
+
convex: runtime.client,
|
|
2694
|
+
sessionToken:
|
|
2695
|
+
runtime.mode === "dev"
|
|
2696
|
+
? encodeDevCrmSessionToken(runtime.workspaceArgs)
|
|
2697
|
+
: runtime.sessionToken,
|
|
2698
|
+
};
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2660
2701
|
function asRecord(value: unknown): JsonRecord | undefined {
|
|
2661
2702
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2662
2703
|
return undefined;
|
|
@@ -2750,6 +2791,7 @@ async function connectedAppsContext(runtime: Runtime) {
|
|
|
2750
2791
|
bearerToken: gatewayAuth.bearerToken,
|
|
2751
2792
|
gatewayBaseUrl: gatewayAuth.gatewayBaseUrl,
|
|
2752
2793
|
gatewayHeaders: gatewayAuth.gatewayHeaders,
|
|
2794
|
+
...unipileQueryAuth(runtime),
|
|
2753
2795
|
});
|
|
2754
2796
|
} catch (error) {
|
|
2755
2797
|
return {
|
|
@@ -2859,26 +2901,42 @@ function formatContext(context: JsonRecord) {
|
|
|
2859
2901
|
}
|
|
2860
2902
|
|
|
2861
2903
|
lines.push("", "Connected apps:");
|
|
2862
|
-
|
|
2904
|
+
// The two brokers fail independently. Listing whichever half we DID get, and
|
|
2905
|
+
// naming the half we did not, beats collapsing both into "unavailable" —
|
|
2906
|
+
// that is how a connected calendar came to read as absent.
|
|
2907
|
+
if (connections.length === 0) {
|
|
2863
2908
|
lines.push(
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2909
|
+
connectedApps?.available === false
|
|
2910
|
+
? ` unavailable: ${
|
|
2911
|
+
stringField(connectedApps, "error", "reason") ??
|
|
2912
|
+
"could not check apps"
|
|
2913
|
+
}`
|
|
2914
|
+
: " none",
|
|
2867
2915
|
);
|
|
2868
|
-
} else if (connections.length === 0) {
|
|
2869
|
-
lines.push(" none");
|
|
2870
2916
|
} else {
|
|
2871
2917
|
lines.push(
|
|
2872
|
-
...connections
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
(connection)
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2918
|
+
...connections.slice(0, 8).map((connection) => {
|
|
2919
|
+
const label = stringField(connection, "accountLabel");
|
|
2920
|
+
const via =
|
|
2921
|
+
stringField(connection, "source") === "unipile" ? " via Dench" : "";
|
|
2922
|
+
return ` - ${stringField(connection, "toolkit") ?? "unknown"} [${
|
|
2923
|
+
stringField(connection, "status") ?? "unknown"
|
|
2924
|
+
}]${label ? ` ${label}` : ""}${via}`;
|
|
2925
|
+
}),
|
|
2926
|
+
);
|
|
2927
|
+
if (connectedApps?.available === false) {
|
|
2928
|
+
lines.push(
|
|
2929
|
+
` (could not check Composio apps: ${
|
|
2930
|
+
stringField(connectedApps, "error", "reason") ?? "unknown error"
|
|
2931
|
+
})`,
|
|
2932
|
+
);
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2935
|
+
if (connectedApps?.unipileAvailable === false) {
|
|
2936
|
+
lines.push(
|
|
2937
|
+
` (could not check Gmail/Outlook/Calendar/LinkedIn: ${
|
|
2938
|
+
stringField(connectedApps, "unipileError") ?? "unknown error"
|
|
2939
|
+
})`,
|
|
2882
2940
|
);
|
|
2883
2941
|
}
|
|
2884
2942
|
|
|
@@ -4240,6 +4298,26 @@ async function main() {
|
|
|
4240
4298
|
return;
|
|
4241
4299
|
}
|
|
4242
4300
|
|
|
4301
|
+
if (command === "web") {
|
|
4302
|
+
const subArgs = args.slice(args.indexOf("web") + 1);
|
|
4303
|
+
const filteredSubArgs = subArgs.filter((a) => a !== "--json");
|
|
4304
|
+
const { runWebCommand, webHelp } = await import("./web");
|
|
4305
|
+
const sub = filteredSubArgs[0];
|
|
4306
|
+
if (!sub || sub === "help" || sub === "--help" || sub === "-h") {
|
|
4307
|
+
webHelp();
|
|
4308
|
+
return;
|
|
4309
|
+
}
|
|
4310
|
+
const runtime = await getRuntime();
|
|
4311
|
+
const gatewayAuth = await resolveGatewayCommandAuth(runtime, "dench web");
|
|
4312
|
+
await runWebCommand({
|
|
4313
|
+
args: filteredSubArgs,
|
|
4314
|
+
jsonOutput: json,
|
|
4315
|
+
bearerToken: gatewayAuth.bearerToken,
|
|
4316
|
+
gatewayBaseUrl: gatewayAuth.gatewayBaseUrl,
|
|
4317
|
+
});
|
|
4318
|
+
return;
|
|
4319
|
+
}
|
|
4320
|
+
|
|
4243
4321
|
if (command === "search") {
|
|
4244
4322
|
const subArgs = args.slice(args.indexOf("search") + 1);
|
|
4245
4323
|
const filteredSubArgs = subArgs.filter((a) => a !== "--json");
|
|
@@ -4308,8 +4386,10 @@ async function main() {
|
|
|
4308
4386
|
return;
|
|
4309
4387
|
}
|
|
4310
4388
|
|
|
4311
|
-
// `dench integrations` —
|
|
4312
|
-
//
|
|
4389
|
+
// `dench integrations` — every app connected to this workspace, from BOTH
|
|
4390
|
+
// brokers: Composio (via the gateway) and Dench's own Unipile connections
|
|
4391
|
+
// for Gmail, Outlook, Calendar and LinkedIn (via Convex). Composio tool
|
|
4392
|
+
// search/run/connect stay under `dench tool`.
|
|
4313
4393
|
if (command === "integrations") {
|
|
4314
4394
|
const runtime = await getRuntime();
|
|
4315
4395
|
const gatewayAuth = await resolveGatewayCommandAuth(
|
|
@@ -4322,6 +4402,7 @@ async function main() {
|
|
|
4322
4402
|
bearerToken: gatewayAuth.bearerToken,
|
|
4323
4403
|
gatewayBaseUrl: gatewayAuth.gatewayBaseUrl,
|
|
4324
4404
|
gatewayHeaders: gatewayAuth.gatewayHeaders,
|
|
4405
|
+
...unipileQueryAuth(runtime),
|
|
4325
4406
|
});
|
|
4326
4407
|
return;
|
|
4327
4408
|
}
|
|
@@ -4369,6 +4450,7 @@ async function main() {
|
|
|
4369
4450
|
bearerToken: gatewayAuth.bearerToken,
|
|
4370
4451
|
gatewayBaseUrl: gatewayAuth.gatewayBaseUrl,
|
|
4371
4452
|
gatewayHeaders: gatewayAuth.gatewayHeaders,
|
|
4453
|
+
...unipileQueryAuth(runtime),
|
|
4372
4454
|
});
|
|
4373
4455
|
return;
|
|
4374
4456
|
}
|
package/email.ts
CHANGED
|
@@ -230,14 +230,16 @@ export function emailHelp() {
|
|
|
230
230
|
console.log(`dench email — outbound email from a connected inbox (or Dench Emailing Service)
|
|
231
231
|
|
|
232
232
|
Usage:
|
|
233
|
-
dench email identity verify --from founder@example.com [--json]
|
|
234
|
-
dench email identity verify --domain example.com [--from founder@example.com] [--json]
|
|
233
|
+
dench email identity verify --from founder@example.com [--private] [--json]
|
|
234
|
+
dench email identity verify --domain example.com [--from founder@example.com] [--private] [--json]
|
|
235
235
|
dench email identity list [--domain example.com] [--json]
|
|
236
236
|
dench email identity dns --identity <identityId|domain|email> [--json]
|
|
237
237
|
dench email identity status --identity founder@example.com [--json]
|
|
238
238
|
dench email identity status --identity-id <identityId> [--json]
|
|
239
239
|
dench email identity disable --identity <identityId|email|domain> [--json]
|
|
240
240
|
dench email identity restore --identity <identityId|email|domain> [--json]
|
|
241
|
+
dench email identity share --identity <identityId|email> [--json]
|
|
242
|
+
dench email identity restrict --identity <identityId|email> [--json]
|
|
241
243
|
dench email identity delete --identity <identityId|email|domain> [--json]
|
|
242
244
|
dench email identity forward --identity <identityId|email> --to inbox@you.com [--json] # forward this sender's sequence replies
|
|
243
245
|
dench email identity forward --identity <identityId|email> --off [--json] # turn forwarding off
|
|
@@ -269,7 +271,17 @@ Sequences (automatic multi-step follow-ups):
|
|
|
269
271
|
dench email sequence enroll-from-object --sequence <id> --object prospect --email-field "Email" --entries id1,id2 [--json]
|
|
270
272
|
dench email sequence enrollments list --sequence <id> [--status active] [--limit 50] [--json]
|
|
271
273
|
dench email sequence enrollment pause|resume|stop --enrollment <id> [--json]
|
|
272
|
-
dench email sequence archive --sequence <id> [--json]
|
|
274
|
+
dench email sequence archive --sequence <id> [--json]
|
|
275
|
+
|
|
276
|
+
Email + LinkedIn multichannel sequences:
|
|
277
|
+
dench email multichannel create --name "Founder outreach" [--timezone America/Los_Angeles] [--json]
|
|
278
|
+
dench email multichannel list [--all] [--json]
|
|
279
|
+
dench email multichannel status --sequence <id> [--json]
|
|
280
|
+
dench email multichannel flow --sequence <id> --file steps.json [--json]
|
|
281
|
+
dench email multichannel enroll --sequence <id> [--name Ada] [--email ada@x.com] [--linkedin-url https://linkedin.com/in/ada] [--json]
|
|
282
|
+
dench email multichannel enroll-from-object --sequence <id> --object lead --entries id1,id2 [--email-field Email] [--linkedin-field "POC LinkedIn URL"] [--relation-field Contact] [--reverse] [--allow-duplicates] [--json]
|
|
283
|
+
dench email multichannel launch --sequence <id> [--ack-linkedin-risk] [--json]
|
|
284
|
+
dench email multichannel pause|resume|archive --sequence <id> [--json]`);
|
|
273
285
|
}
|
|
274
286
|
|
|
275
287
|
export async function runEmailCommand(ctx: EmailCliContext): Promise<void> {
|
|
@@ -302,6 +314,10 @@ export async function runEmailCommand(ctx: EmailCliContext): Promise<void> {
|
|
|
302
314
|
await runSequence(ctx);
|
|
303
315
|
return;
|
|
304
316
|
}
|
|
317
|
+
if (group === "multichannel") {
|
|
318
|
+
await runMultichannel(ctx);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
305
321
|
throw new EmailCliError(`Unknown dench email subcommand: ${group}`);
|
|
306
322
|
}
|
|
307
323
|
|
|
@@ -514,6 +530,7 @@ async function runIdentity(ctx: EmailCliContext) {
|
|
|
514
530
|
fromName: getFlag(ctx.args, "--from-name"),
|
|
515
531
|
replyToEmail: getFlag(ctx.args, "--reply-to"),
|
|
516
532
|
configurationSetName,
|
|
533
|
+
...(hasFlag(ctx.args, "--private") ? { sharing: "restricted" } : {}),
|
|
517
534
|
},
|
|
518
535
|
);
|
|
519
536
|
const verification = (await callAction(
|
|
@@ -544,6 +561,7 @@ async function runIdentity(ctx: EmailCliContext) {
|
|
|
544
561
|
`${identity.identityId} ${identity.fromEmail} type=${identity.type}` +
|
|
545
562
|
`${identity.domain ? ` domain=${identity.domain}` : ""}` +
|
|
546
563
|
` delivery=${identity.deliveryVerificationStatus} workspace=${identity.denchVerificationStatus}` +
|
|
564
|
+
` sharing=${identity.sharing ?? "org"}` +
|
|
547
565
|
`${identity.forwardRepliesTo ? ` forward=${identity.forwardRepliesTo}` : ""}` +
|
|
548
566
|
`${identity.disabled ? " DISABLED" : ""}`,
|
|
549
567
|
)
|
|
@@ -551,6 +569,23 @@ async function runIdentity(ctx: EmailCliContext) {
|
|
|
551
569
|
out(ctx, { ok: true, identities }, text);
|
|
552
570
|
return;
|
|
553
571
|
}
|
|
572
|
+
if (sub === "share" || sub === "restrict") {
|
|
573
|
+
const identity = requiredFlag(ctx.args, "--identity");
|
|
574
|
+
const sharing = sub === "restrict" ? "restricted" : "org";
|
|
575
|
+
const payload = await callMutation(
|
|
576
|
+
ctx,
|
|
577
|
+
"functions/emailCampaigns:setIdentitySharing",
|
|
578
|
+
{ identity, sharing },
|
|
579
|
+
);
|
|
580
|
+
out(
|
|
581
|
+
ctx,
|
|
582
|
+
payload,
|
|
583
|
+
sharing === "restricted"
|
|
584
|
+
? `Restricted ${identity} to its owner and workspace admins.`
|
|
585
|
+
: `Shared ${identity} with the workspace.`,
|
|
586
|
+
);
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
554
589
|
if (sub === "disable" || sub === "restore" || sub === "delete") {
|
|
555
590
|
const identity = requiredFlag(ctx.args, "--identity");
|
|
556
591
|
const payload = (await callMutation(
|
|
@@ -714,7 +749,7 @@ async function runIdentity(ctx: EmailCliContext) {
|
|
|
714
749
|
return;
|
|
715
750
|
}
|
|
716
751
|
throw new EmailCliError(
|
|
717
|
-
"Usage: dench email identity verify|list|dns|status|disable|restore|delete",
|
|
752
|
+
"Usage: dench email identity verify|list|dns|status|disable|restore|share|restrict|delete",
|
|
718
753
|
);
|
|
719
754
|
}
|
|
720
755
|
|
|
@@ -1127,3 +1162,150 @@ async function runSequence(ctx: EmailCliContext) {
|
|
|
1127
1162
|
"Usage: dench email sequence create|update|activate|deactivate|archive|list|status|steps|enroll|enroll-from-object|enrollments|enrollment",
|
|
1128
1163
|
);
|
|
1129
1164
|
}
|
|
1165
|
+
|
|
1166
|
+
async function runMultichannel(ctx: EmailCliContext) {
|
|
1167
|
+
const sub = ctx.args.shift();
|
|
1168
|
+
if (sub === "create") {
|
|
1169
|
+
const payload = await callMutation(
|
|
1170
|
+
ctx,
|
|
1171
|
+
"functions/outreachSequences:createSequence",
|
|
1172
|
+
{
|
|
1173
|
+
name: requiredFlag(ctx.args, "--name"),
|
|
1174
|
+
timezone:
|
|
1175
|
+
getFlag(ctx.args, "--timezone") ??
|
|
1176
|
+
Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
1177
|
+
},
|
|
1178
|
+
);
|
|
1179
|
+
out(ctx, payload, JSON.stringify(payload, null, 2));
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
if (sub === "list") {
|
|
1183
|
+
const payload = await callQuery(
|
|
1184
|
+
ctx,
|
|
1185
|
+
"functions/outreachSequences:listSequences",
|
|
1186
|
+
{ includeArchived: hasFlag(ctx.args, "--all") },
|
|
1187
|
+
);
|
|
1188
|
+
out(ctx, payload, JSON.stringify(payload, null, 2));
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1191
|
+
if (sub === "status") {
|
|
1192
|
+
const sequenceId = requiredFlag(ctx.args, "--sequence");
|
|
1193
|
+
const [detail, analytics] = await Promise.all([
|
|
1194
|
+
callQuery(ctx, "functions/outreachSequences:getSequence", { sequenceId }),
|
|
1195
|
+
callQuery(ctx, "functions/outreachSequences:getSequenceAnalytics", {
|
|
1196
|
+
sequenceId,
|
|
1197
|
+
}),
|
|
1198
|
+
]);
|
|
1199
|
+
const payload = { detail, analytics };
|
|
1200
|
+
out(ctx, payload, JSON.stringify(payload, null, 2));
|
|
1201
|
+
return;
|
|
1202
|
+
}
|
|
1203
|
+
if (sub === "flow") {
|
|
1204
|
+
const raw = await readFile(requiredFlag(ctx.args, "--file"), "utf8");
|
|
1205
|
+
const steps = JSON.parse(raw);
|
|
1206
|
+
if (!Array.isArray(steps)) {
|
|
1207
|
+
throw new EmailCliError("Flow file must contain a JSON array of steps");
|
|
1208
|
+
}
|
|
1209
|
+
const payload = await callMutation(
|
|
1210
|
+
ctx,
|
|
1211
|
+
"functions/outreachSequences:replaceSteps",
|
|
1212
|
+
{
|
|
1213
|
+
sequenceId: requiredFlag(ctx.args, "--sequence"),
|
|
1214
|
+
steps,
|
|
1215
|
+
},
|
|
1216
|
+
);
|
|
1217
|
+
out(ctx, payload, JSON.stringify(payload, null, 2));
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
if (sub === "enroll") {
|
|
1221
|
+
const email = getFlag(ctx.args, "--email");
|
|
1222
|
+
const linkedinProfileUrl = getFlag(ctx.args, "--linkedin-url");
|
|
1223
|
+
if (!email && !linkedinProfileUrl) {
|
|
1224
|
+
throw new EmailCliError("Pass --email and/or --linkedin-url");
|
|
1225
|
+
}
|
|
1226
|
+
const payload = await callMutation(
|
|
1227
|
+
ctx,
|
|
1228
|
+
"functions/outreachSequences:addEnrollment",
|
|
1229
|
+
{
|
|
1230
|
+
sequenceId: requiredFlag(ctx.args, "--sequence"),
|
|
1231
|
+
displayName: getFlag(ctx.args, "--name"),
|
|
1232
|
+
email,
|
|
1233
|
+
linkedinProfileUrl,
|
|
1234
|
+
checkDuplicatesAcrossWorkspace: !hasFlag(
|
|
1235
|
+
ctx.args,
|
|
1236
|
+
"--allow-duplicates",
|
|
1237
|
+
),
|
|
1238
|
+
},
|
|
1239
|
+
);
|
|
1240
|
+
out(ctx, payload, JSON.stringify(payload, null, 2));
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
if (sub === "enroll-from-object") {
|
|
1244
|
+
const entryIds = parseEntryIds(requiredFlag(ctx.args, "--entries"));
|
|
1245
|
+
if (entryIds.length === 0) {
|
|
1246
|
+
throw new EmailCliError("Pass at least one CRM entry id in --entries");
|
|
1247
|
+
}
|
|
1248
|
+
const payload = await callMutation(
|
|
1249
|
+
ctx,
|
|
1250
|
+
"functions/outreachSequences:enrollFromCrm",
|
|
1251
|
+
{
|
|
1252
|
+
sequenceId: requiredFlag(ctx.args, "--sequence"),
|
|
1253
|
+
objectName: requiredFlag(ctx.args, "--object"),
|
|
1254
|
+
emailFieldName: getFlag(ctx.args, "--email-field"),
|
|
1255
|
+
linkedinUrlFieldName: getFlag(ctx.args, "--linkedin-field"),
|
|
1256
|
+
relationFieldName: getFlag(ctx.args, "--relation-field"),
|
|
1257
|
+
relationDirection: hasFlag(ctx.args, "--reverse")
|
|
1258
|
+
? "reverse"
|
|
1259
|
+
: undefined,
|
|
1260
|
+
entryIds,
|
|
1261
|
+
checkDuplicatesAcrossWorkspace: !hasFlag(
|
|
1262
|
+
ctx.args,
|
|
1263
|
+
"--allow-duplicates",
|
|
1264
|
+
),
|
|
1265
|
+
},
|
|
1266
|
+
);
|
|
1267
|
+
out(ctx, payload, JSON.stringify(payload, null, 2));
|
|
1268
|
+
return;
|
|
1269
|
+
}
|
|
1270
|
+
if (sub === "launch") {
|
|
1271
|
+
const sequenceId = requiredFlag(ctx.args, "--sequence");
|
|
1272
|
+
const preview = (await callQuery(
|
|
1273
|
+
ctx,
|
|
1274
|
+
"functions/outreachSequences:previewLaunch",
|
|
1275
|
+
{ sequenceId },
|
|
1276
|
+
)) as JsonRecord;
|
|
1277
|
+
if (preview.ready !== true || typeof preview.fingerprint !== "string") {
|
|
1278
|
+
out(ctx, preview, JSON.stringify(preview, null, 2));
|
|
1279
|
+
throw new EmailCliError("Sequence is not ready to launch");
|
|
1280
|
+
}
|
|
1281
|
+
const payload = await callMutation(
|
|
1282
|
+
ctx,
|
|
1283
|
+
"functions/outreachSequences:launch",
|
|
1284
|
+
{
|
|
1285
|
+
sequenceId,
|
|
1286
|
+
fingerprint: preview.fingerprint,
|
|
1287
|
+
linkedinRiskAcknowledged: hasFlag(ctx.args, "--ack-linkedin-risk"),
|
|
1288
|
+
},
|
|
1289
|
+
);
|
|
1290
|
+
out(ctx, payload, JSON.stringify(payload, null, 2));
|
|
1291
|
+
return;
|
|
1292
|
+
}
|
|
1293
|
+
if (sub === "pause" || sub === "resume" || sub === "archive") {
|
|
1294
|
+
const fn =
|
|
1295
|
+
sub === "pause"
|
|
1296
|
+
? "pauseSequence"
|
|
1297
|
+
: sub === "resume"
|
|
1298
|
+
? "resumeSequence"
|
|
1299
|
+
: "archiveSequence";
|
|
1300
|
+
const payload = await callMutation(
|
|
1301
|
+
ctx,
|
|
1302
|
+
`functions/outreachSequences:${fn}`,
|
|
1303
|
+
{ sequenceId: requiredFlag(ctx.args, "--sequence") },
|
|
1304
|
+
);
|
|
1305
|
+
out(ctx, payload, JSON.stringify(payload, null, 2));
|
|
1306
|
+
return;
|
|
1307
|
+
}
|
|
1308
|
+
throw new EmailCliError(
|
|
1309
|
+
"Usage: dench email multichannel create|list|status|flow|enroll|enroll-from-object|launch|pause|resume|archive",
|
|
1310
|
+
);
|
|
1311
|
+
}
|
package/lib/api-schemas.ts
CHANGED
|
@@ -2963,6 +2963,29 @@ export const requestExamples: Record<string, unknown> = {
|
|
|
2963
2963
|
schedule: { kind: "every", everyMs: 86_400_000 },
|
|
2964
2964
|
},
|
|
2965
2965
|
"search.web": { query: "latest AI CRM workflows", numResults: 5 },
|
|
2966
|
+
"web.scrape": {
|
|
2967
|
+
input: { url: "https://example.com", useMainContentOnly: true },
|
|
2968
|
+
},
|
|
2969
|
+
"web.brand": { input: { domain: "stripe.com" } },
|
|
2970
|
+
"web.sitemap": { input: { domain: "example.com", maxLinks: 1000 } },
|
|
2971
|
+
"web.crawl": {
|
|
2972
|
+
input: { url: "https://example.com", maxPages: 15, useMainContentOnly: true },
|
|
2973
|
+
},
|
|
2974
|
+
"web.screenshot": {
|
|
2975
|
+
input: { directUrl: "https://example.com", fullScreenshot: false },
|
|
2976
|
+
},
|
|
2977
|
+
"web.extract": {
|
|
2978
|
+
input: {
|
|
2979
|
+
url: "https://example.com",
|
|
2980
|
+
schema: { type: "object", properties: { title: { type: "string" } } },
|
|
2981
|
+
},
|
|
2982
|
+
},
|
|
2983
|
+
"web.product": { input: { url: "https://example.com/product" } },
|
|
2984
|
+
"web.competitors": { input: { domain: "stripe.com", numCompetitors: 5 } },
|
|
2985
|
+
"web.styleguide": { input: { domain: "stripe.com" } },
|
|
2986
|
+
"web.fonts": { input: { domain: "stripe.com" } },
|
|
2987
|
+
"web.classify": { input: { input: "stripe.com" } },
|
|
2988
|
+
"web.brandSearch": { input: { query: "stripe" } },
|
|
2966
2989
|
"image.generate": {
|
|
2967
2990
|
prompt: "A minimal flat-vector app icon",
|
|
2968
2991
|
size: "1024x1024",
|
package/lib/command-registry.ts
CHANGED
|
@@ -31,6 +31,7 @@ export type CustomOperation = {
|
|
|
31
31
|
| "chatFollow"
|
|
32
32
|
| "cronDelete"
|
|
33
33
|
| "cronRunNow"
|
|
34
|
+
| "connectionsList"
|
|
34
35
|
| "toolDisconnect"
|
|
35
36
|
| "billingTopup"
|
|
36
37
|
| "upgrade"
|
|
@@ -780,7 +781,7 @@ const rawApiOperations = [
|
|
|
780
781
|
id: "crm.sidebar.pins.set",
|
|
781
782
|
group: "crm",
|
|
782
783
|
summary:
|
|
783
|
-
|
|
784
|
+
'Pin (pinned true) or unpin a Library row — or a whole group — on the home-level Pinned shortcut strip (the "tab on the left"). Does not move the row inside Library. Workspace-shared; open to any member.',
|
|
784
785
|
cli: "dench crm sidebar pin",
|
|
785
786
|
aliases: ["dench crm sidebar unpin"],
|
|
786
787
|
method: "POST",
|
|
@@ -2220,6 +2221,138 @@ const rawApiOperations = [
|
|
|
2220
2221
|
responseSchema: anyResponse,
|
|
2221
2222
|
backend: gateway("POST", "/search/answer"),
|
|
2222
2223
|
}),
|
|
2224
|
+
op({
|
|
2225
|
+
id: "web.scrape",
|
|
2226
|
+
group: "gateway",
|
|
2227
|
+
summary: "Read a page as clean Markdown through Dench web.",
|
|
2228
|
+
cli: "dench web scrape",
|
|
2229
|
+
method: "POST",
|
|
2230
|
+
path: "/context/web-scrape-markdown",
|
|
2231
|
+
requestSchema: anyObject,
|
|
2232
|
+
responseSchema: anyResponse,
|
|
2233
|
+
backend: gateway("POST", "/context/web-scrape-markdown"),
|
|
2234
|
+
}),
|
|
2235
|
+
op({
|
|
2236
|
+
id: "web.brand",
|
|
2237
|
+
group: "gateway",
|
|
2238
|
+
summary: "Look up a brand kit from a domain or email.",
|
|
2239
|
+
cli: "dench web brand",
|
|
2240
|
+
method: "POST",
|
|
2241
|
+
path: "/context/brand-retrieve",
|
|
2242
|
+
requestSchema: anyObject,
|
|
2243
|
+
responseSchema: anyResponse,
|
|
2244
|
+
backend: gateway("POST", "/context/brand-retrieve"),
|
|
2245
|
+
}),
|
|
2246
|
+
op({
|
|
2247
|
+
id: "web.sitemap",
|
|
2248
|
+
group: "gateway",
|
|
2249
|
+
summary: "List sitemap URLs for a domain.",
|
|
2250
|
+
cli: "dench web sitemap",
|
|
2251
|
+
method: "POST",
|
|
2252
|
+
path: "/context/web-sitemap",
|
|
2253
|
+
requestSchema: anyObject,
|
|
2254
|
+
responseSchema: anyResponse,
|
|
2255
|
+
backend: gateway("POST", "/context/web-sitemap"),
|
|
2256
|
+
}),
|
|
2257
|
+
op({
|
|
2258
|
+
id: "web.crawl",
|
|
2259
|
+
group: "gateway",
|
|
2260
|
+
summary: "Crawl a site to Markdown (capped).",
|
|
2261
|
+
cli: "dench web crawl",
|
|
2262
|
+
method: "POST",
|
|
2263
|
+
path: "/context/web-crawl",
|
|
2264
|
+
requestSchema: anyObject,
|
|
2265
|
+
responseSchema: anyResponse,
|
|
2266
|
+
backend: gateway("POST", "/context/web-crawl"),
|
|
2267
|
+
}),
|
|
2268
|
+
op({
|
|
2269
|
+
id: "web.screenshot",
|
|
2270
|
+
group: "gateway",
|
|
2271
|
+
summary: "Capture a rendered page screenshot.",
|
|
2272
|
+
cli: "dench web screenshot",
|
|
2273
|
+
method: "POST",
|
|
2274
|
+
path: "/context/web-screenshot",
|
|
2275
|
+
requestSchema: anyObject,
|
|
2276
|
+
responseSchema: anyResponse,
|
|
2277
|
+
backend: gateway("POST", "/context/web-screenshot"),
|
|
2278
|
+
}),
|
|
2279
|
+
op({
|
|
2280
|
+
id: "web.extract",
|
|
2281
|
+
group: "gateway",
|
|
2282
|
+
summary: "Extract typed fields from a page using a JSON Schema.",
|
|
2283
|
+
cli: "dench web extract",
|
|
2284
|
+
method: "POST",
|
|
2285
|
+
path: "/context/web-extract",
|
|
2286
|
+
requestSchema: anyObject,
|
|
2287
|
+
responseSchema: anyResponse,
|
|
2288
|
+
backend: gateway("POST", "/context/web-extract"),
|
|
2289
|
+
}),
|
|
2290
|
+
op({
|
|
2291
|
+
id: "web.product",
|
|
2292
|
+
group: "gateway",
|
|
2293
|
+
summary: "Extract a product listing from a URL.",
|
|
2294
|
+
cli: "dench web product",
|
|
2295
|
+
method: "POST",
|
|
2296
|
+
path: "/context/web-product",
|
|
2297
|
+
requestSchema: anyObject,
|
|
2298
|
+
responseSchema: anyResponse,
|
|
2299
|
+
backend: gateway("POST", "/context/web-product"),
|
|
2300
|
+
}),
|
|
2301
|
+
op({
|
|
2302
|
+
id: "web.competitors",
|
|
2303
|
+
group: "gateway",
|
|
2304
|
+
summary: "Find competitors of a domain.",
|
|
2305
|
+
cli: "dench web competitors",
|
|
2306
|
+
method: "POST",
|
|
2307
|
+
path: "/context/web-competitors",
|
|
2308
|
+
requestSchema: anyObject,
|
|
2309
|
+
responseSchema: anyResponse,
|
|
2310
|
+
backend: gateway("POST", "/context/web-competitors"),
|
|
2311
|
+
}),
|
|
2312
|
+
op({
|
|
2313
|
+
id: "web.styleguide",
|
|
2314
|
+
group: "gateway",
|
|
2315
|
+
summary: "Read a brand styleguide from a domain.",
|
|
2316
|
+
cli: "dench web styleguide",
|
|
2317
|
+
method: "POST",
|
|
2318
|
+
path: "/context/brand-styleguide",
|
|
2319
|
+
requestSchema: anyObject,
|
|
2320
|
+
responseSchema: anyResponse,
|
|
2321
|
+
backend: gateway("POST", "/context/brand-styleguide"),
|
|
2322
|
+
}),
|
|
2323
|
+
op({
|
|
2324
|
+
id: "web.fonts",
|
|
2325
|
+
group: "gateway",
|
|
2326
|
+
summary: "Read site fonts from a domain.",
|
|
2327
|
+
cli: "dench web fonts",
|
|
2328
|
+
method: "POST",
|
|
2329
|
+
path: "/context/brand-fonts",
|
|
2330
|
+
requestSchema: anyObject,
|
|
2331
|
+
responseSchema: anyResponse,
|
|
2332
|
+
backend: gateway("POST", "/context/brand-fonts"),
|
|
2333
|
+
}),
|
|
2334
|
+
op({
|
|
2335
|
+
id: "web.classify",
|
|
2336
|
+
group: "gateway",
|
|
2337
|
+
summary: "Classify a domain with NAICS or SIC.",
|
|
2338
|
+
cli: "dench web classify",
|
|
2339
|
+
method: "POST",
|
|
2340
|
+
path: "/context/classify-naics",
|
|
2341
|
+
requestSchema: anyObject,
|
|
2342
|
+
responseSchema: anyResponse,
|
|
2343
|
+
backend: gateway("POST", "/context/classify-naics"),
|
|
2344
|
+
}),
|
|
2345
|
+
op({
|
|
2346
|
+
id: "web.brandSearch",
|
|
2347
|
+
group: "gateway",
|
|
2348
|
+
summary: "Autocomplete brand names.",
|
|
2349
|
+
cli: "dench web brand-search",
|
|
2350
|
+
method: "POST",
|
|
2351
|
+
path: "/context/brand-search",
|
|
2352
|
+
requestSchema: anyObject,
|
|
2353
|
+
responseSchema: anyResponse,
|
|
2354
|
+
backend: gateway("POST", "/context/brand-search"),
|
|
2355
|
+
}),
|
|
2223
2356
|
op({
|
|
2224
2357
|
id: "image.generate",
|
|
2225
2358
|
group: "gateway",
|
|
@@ -2245,13 +2378,15 @@ const rawApiOperations = [
|
|
|
2245
2378
|
op({
|
|
2246
2379
|
id: "integrations.list",
|
|
2247
2380
|
group: "gateway",
|
|
2248
|
-
summary:
|
|
2381
|
+
summary:
|
|
2382
|
+
"List every connected integration — Composio apps plus the Gmail, " +
|
|
2383
|
+
"Outlook, Calendar and LinkedIn accounts Dench connects itself.",
|
|
2249
2384
|
cli: "dench integrations",
|
|
2250
2385
|
method: "GET",
|
|
2251
2386
|
path: "/integrations",
|
|
2252
2387
|
requestSchema: noBody,
|
|
2253
2388
|
responseSchema: anyResponse,
|
|
2254
|
-
backend:
|
|
2389
|
+
backend: custom("connectionsList", "bearer"),
|
|
2255
2390
|
}),
|
|
2256
2391
|
op({
|
|
2257
2392
|
id: "apps.list",
|
|
@@ -2350,7 +2485,7 @@ const rawApiOperations = [
|
|
|
2350
2485
|
path: "/tools/status",
|
|
2351
2486
|
requestSchema: noBody,
|
|
2352
2487
|
responseSchema: anyResponse,
|
|
2353
|
-
backend:
|
|
2488
|
+
backend: custom("connectionsList", "bearer"),
|
|
2354
2489
|
}),
|
|
2355
2490
|
op({
|
|
2356
2491
|
id: "tool.connect",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dench.com/cli",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.10-staging.100",
|
|
4
4
|
"description": "Dench agent workspace CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"browser.ts",
|
|
31
31
|
"ads.ts",
|
|
32
32
|
"search.ts",
|
|
33
|
+
"web.ts",
|
|
33
34
|
"image.ts",
|
|
34
35
|
"tools.ts",
|
|
35
36
|
"apps.ts",
|