@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 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 all external apps connected to this organization (Slack, Gmail,
813
- GitHub, etc.). Authenticates with the active \`dench signin\` agent
814
- session or DENCH_API_KEY (Bearer), whichever is set. Alias for
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
- if (connectedApps?.available === false) {
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
- ` unavailable: ${
2865
- stringField(connectedApps, "error", "reason") ?? "could not check apps"
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
- .slice(0, 8)
2874
- .map(
2875
- (connection) =>
2876
- ` - ${stringField(connection, "toolkit") ?? "unknown"} [${stringField(connection, "status") ?? "unknown"}]${
2877
- stringField(connection, "accountLabel")
2878
- ? ` ${stringField(connection, "accountLabel")}`
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` — Composio connected apps (alias for `dench tool
4312
- // status`). Composio tool search/run/connect stay under `dench tool`.
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
+ }
@@ -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",
@@ -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
- "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
+ '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: "List connected Composio integrations.",
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: gateway("GET", "/composio/connections"),
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: gateway("GET", "/composio/connections"),
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.8",
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",