@dench.com/cli 3.6.5-staging.384 → 3.7.0-staging.387

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/email.ts CHANGED
@@ -1,17 +1,20 @@
1
1
  import { createHash, createHmac, randomUUID } from "node:crypto";
2
2
  import { mkdir, readFile, writeFile } from "node:fs/promises";
3
3
  import { basename, extname, join } from "node:path";
4
- import type { ConvexHttpClient } from "convex/browser";
4
+ import { ConvexHttpClient } from "convex/browser";
5
5
  import { makeFunctionReference } from "convex/server";
6
+ import { EMAIL_INBOX_HELP, runEmailInboxCommand } from "./email-inbox";
6
7
  import {
7
8
  assertNoUnknownFlags,
8
9
  getFlag,
9
10
  hasFlag,
11
+ isHelpRequest,
10
12
  parseJson,
11
13
  } from "./lib/cli-args";
12
14
  import { denchConfigDirectory } from "./lib/config-path.mjs";
15
+ import { waitForNativeEmail } from "./lib/native-email-wait";
13
16
 
14
- type EmailCliContext = {
17
+ export type EmailCliContext = {
15
18
  convex: ConvexHttpClient;
16
19
  args: string[];
17
20
  jsonOutput: boolean;
@@ -19,9 +22,21 @@ type EmailCliContext = {
19
22
  bearerToken?: string;
20
23
  gatewayBaseUrl?: string;
21
24
  gatewayHeaders?: Record<string, string>;
25
+ /** Injectable read transport; it must honor the supplied observation deadline. */
26
+ readNativeReceipt?: (
27
+ draftId: string,
28
+ signal: AbortSignal,
29
+ ) => Promise<unknown>;
22
30
  };
23
31
 
24
- class EmailCliError extends Error {}
32
+ class EmailCliError extends Error {
33
+ constructor(
34
+ message: string,
35
+ readonly code = "invalid_arguments",
36
+ ) {
37
+ super(message);
38
+ }
39
+ }
25
40
 
26
41
  type JsonRecord = Record<string, unknown>;
27
42
 
@@ -48,6 +63,7 @@ function requireBearerToken(ctx: EmailCliContext): string {
48
63
  if (!token) {
49
64
  throw new EmailCliError(
50
65
  "No Dench credential found. Set DENCH_API_KEY or run `dench signin`.",
66
+ "login_required",
51
67
  );
52
68
  }
53
69
  return token;
@@ -59,6 +75,7 @@ async function callGateway(
59
75
  path: string,
60
76
  body?: JsonRecord,
61
77
  ) {
78
+ if (method === "POST") assertEmailArgs(ctx);
62
79
  const baseUrl = resolveGatewayBaseUrl(ctx);
63
80
  const response = await fetch(`${baseUrl}${path}`, {
64
81
  method,
@@ -74,6 +91,7 @@ async function callGateway(
74
91
  if (!response.ok) {
75
92
  throw new EmailCliError(
76
93
  `Email service returned ${response.status}: ${text.slice(0, 500)}`,
94
+ "upstream_failed",
77
95
  );
78
96
  }
79
97
  const payload = text ? JSON.parse(text) : {};
@@ -85,6 +103,7 @@ async function callMutation(
85
103
  name: string,
86
104
  args: JsonRecord,
87
105
  ) {
106
+ assertEmailArgs(ctx);
88
107
  const ref = makeFunctionReference<"mutation">(name);
89
108
  return await ctx.convex.mutation(ref, {
90
109
  ...args,
@@ -105,6 +124,7 @@ async function callAction(
105
124
  name: string,
106
125
  args: JsonRecord,
107
126
  ) {
127
+ assertEmailArgs(ctx);
108
128
  const ref = makeFunctionReference<"action">(name);
109
129
  return await ctx.convex.action(ref, {
110
130
  ...args,
@@ -362,17 +382,20 @@ function toRecipients(rows: JsonRecord[]) {
362
382
  });
363
383
  }
364
384
 
365
- export function emailHelp() {
366
- console.log(`dench email — outbound email from a connected inbox (or Dench Emailing Service)
385
+ export function emailHelpText() {
386
+ return `dench email — outbound email from a connected inbox (or Dench Emailing Service)
367
387
 
368
388
  Usage:
389
+ ${EMAIL_INBOX_HELP}
369
390
  dench email draft create --to <email> --subject <subject> --text <body> [--mailbox <id/email>] [--request-id <id>] [--json]
370
391
  dench email draft show <id> [--json]
371
- dench email draft update|send <id> --revision <n> [--json]
392
+ dench email draft update <id> --revision <n> [--to <email>] [--subject <subject>] [--text <body>] [--mailbox <id>] [--json]
393
+ dench email draft send <id> --revision <n> [--request-id <id>] [--wait <seconds>|--no-wait] [--json]
394
+ dench email draft senders [--json] # available Gmail/Outlook mailboxes
372
395
  dench email draft attach <id> --revision <n> --file <path> [--content-type <mime>] [--json] # up to 5 MB/file
373
396
  dench email draft signature <id> [--json] # read signature HTML; insert visibly into the body
374
397
  # Identical native create/send invocations reuse a saved request ID. Use --request-id <new-id> for another identical email.
375
- dench email send --mailbox <id/email> --to <email> --subject <subject> --text <body> [--request-id <id>] [--json]
398
+ dench email send --to <email> [--subject <subject>] (--text <body> | --html <body> | --text-file <path> | --html-file <path>) [--mailbox <id/email>] [--cc <emails>] [--bcc <emails>] [--request-id <id>] [--wait <seconds>|--no-wait] [--json] # native default; observe the same receipt for 10s by default (--wait 0..60)
376
399
  dench email identity verify --from founder@example.com [--private] [--json]
377
400
  dench email identity verify --domain example.com [--from founder@example.com] [--private] [--json]
378
401
  dench email identity list [--domain example.com] [--json]
@@ -443,10 +466,15 @@ Email + LinkedIn multichannel sequences (see \`dench campaign\` — the fuller s
443
466
  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]
444
467
  dench email multichannel launch --sequence <id> [--ack-linkedin-risk] [--json]
445
468
  dench email multichannel pause|resume|archive --sequence <id> [--json]
446
- (see \`dench campaign help\` for the same campaigns with schedule, view-based add, launch checks and the Now/Next feed)`);
469
+ (see \`dench campaign help\` for the same campaigns with schedule, view-based add, launch checks and the Now/Next feed)`;
447
470
  }
448
471
 
449
472
  export async function runEmailCommand(ctx: EmailCliContext): Promise<void> {
473
+ if (isHelpRequest(ctx.args)) {
474
+ const { commandHelp } = await import("./lib/command-help");
475
+ console.log(commandHelp(["email", ...ctx.args], emailHelpText()));
476
+ return;
477
+ }
450
478
  ctx.args = ctx.args.flatMap((arg) => {
451
479
  const split = arg.startsWith("--") ? arg.indexOf("=") : -1;
452
480
  return split > 2 ? [arg.slice(0, split), arg.slice(split + 1)] : [arg];
@@ -454,7 +482,11 @@ export async function runEmailCommand(ctx: EmailCliContext): Promise<void> {
454
482
  ctx.jsonOutput = hasFlag(ctx.args, "--json") || ctx.jsonOutput;
455
483
  const group = ctx.args.shift();
456
484
  if (!group || group === "help" || group === "--help" || group === "-h") {
457
- emailHelp();
485
+ console.log(emailHelpText());
486
+ return;
487
+ }
488
+ if (group === "inbox") {
489
+ await runEmailInboxCommand(ctx);
458
490
  return;
459
491
  }
460
492
  if (group === "draft") {
@@ -524,14 +556,20 @@ function parseAddressList(
524
556
  }
525
557
 
526
558
  async function runSend(ctx: EmailCliContext) {
527
- if (ctx.args.includes("--mailbox")) {
559
+ const wait = waitFlag(ctx.args);
560
+ if (!ctx.args.includes("--from")) {
528
561
  const content = {
529
562
  ...(await draftContent(ctx)),
530
- mailbox: requiredFlag(ctx.args, "--mailbox"),
563
+ mailbox: getFlag(ctx.args, "--mailbox"),
531
564
  threadId: getFlag(ctx.args, "--thread") ?? process.env.DENCH_THREAD_ID,
532
565
  mode: "send",
533
566
  };
534
- const payload = await createNativeEmail(ctx, content);
567
+ if (!Array.isArray(content.to) || content.to.length === 0)
568
+ throw new EmailCliError("Sending requires --to <email>");
569
+ const created = await createNativeEmail(ctx, content);
570
+ const payload = wait
571
+ ? await observeNativeEmail(ctx, created, wait)
572
+ : created;
535
573
  out(ctx, payload, JSON.stringify(payload, null, 2));
536
574
  return;
537
575
  }
@@ -575,6 +613,50 @@ async function runSend(ctx: EmailCliContext) {
575
613
  );
576
614
  }
577
615
 
616
+ function waitFlag(args: string[]): number {
617
+ const noWait = hasFlag(args, "--no-wait");
618
+ const raw = getFlag(args, "--wait");
619
+ if (raw !== undefined && noWait)
620
+ throw new EmailCliError("Use --wait or --no-wait, not both");
621
+ const seconds = raw === undefined ? 10 : Number(raw);
622
+ if (
623
+ raw?.trim() === "" ||
624
+ !Number.isFinite(seconds) ||
625
+ seconds < 0 ||
626
+ seconds > 60
627
+ )
628
+ throw new EmailCliError(
629
+ "--wait must be a finite number of seconds from 0 to 60",
630
+ );
631
+ return noWait ? 0 : seconds * 1000;
632
+ }
633
+
634
+ function observeNativeEmail(
635
+ ctx: EmailCliContext,
636
+ receipt: unknown,
637
+ waitMs: number,
638
+ ) {
639
+ return waitForNativeEmail(
640
+ receipt,
641
+ ctx.readNativeReceipt ??
642
+ ((draftId, signal) => {
643
+ const reader = new ConvexHttpClient(ctx.convex.url, {
644
+ logger: false,
645
+ fetch: new Proxy(fetch, {
646
+ apply(target, _receiver, [input, init]) {
647
+ return target(input, { ...init, signal });
648
+ },
649
+ }),
650
+ });
651
+ return reader.query(
652
+ makeFunctionReference<"query">("functions/emailDrafts:get"),
653
+ { draftId, sessionToken: ctx.sessionToken },
654
+ );
655
+ }),
656
+ waitMs,
657
+ );
658
+ }
659
+
578
660
  /** Persist before HTTP. Identical invocations recover the same request even
579
661
  * after the CLI process dies; an explicit new --request-id starts another email. */
580
662
  async function createNativeEmail(ctx: EmailCliContext, content: JsonRecord) {
@@ -615,7 +697,7 @@ async function createNativeEmail(ctx: EmailCliContext, content: JsonRecord) {
615
697
  }
616
698
 
617
699
  function assertEmailArgs(ctx: EmailCliContext) {
618
- assertNoUnknownFlags(ctx.args, "dench email");
700
+ assertNoUnknownFlags(ctx.args, "email");
619
701
  if (ctx.args.length)
620
702
  throw new EmailCliError(`Unexpected argument: ${ctx.args[0]}`);
621
703
  }
@@ -671,6 +753,7 @@ async function runDraft(ctx: EmailCliContext) {
671
753
  draftId,
672
754
  });
673
755
  } else if (sub === "update" || sub === "send") {
756
+ const wait = sub === "send" ? waitFlag(ctx.args) : 0;
674
757
  const draftId = draftIdArg(ctx);
675
758
  const expectedRevision = revisionArg(ctx);
676
759
  const args =
@@ -690,6 +773,7 @@ async function runDraft(ctx: EmailCliContext) {
690
773
  };
691
774
  assertEmailArgs(ctx);
692
775
  result = await callMutation(ctx, `functions/emailDrafts:${sub}`, args);
776
+ if (wait) result = await observeNativeEmail(ctx, result, wait);
693
777
  } else if (sub === "attach") {
694
778
  const draftId = draftIdArg(ctx);
695
779
  const expectedRevision = revisionArg(ctx);
@@ -1146,6 +1230,8 @@ async function runCampaign(ctx: EmailCliContext) {
1146
1230
  const sub = ctx.args.shift();
1147
1231
  if (sub === "create") {
1148
1232
  const sender = await campaignSenderArgs(ctx);
1233
+ const scheduledAt = getFlag(ctx.args, "--scheduled-at");
1234
+ const maxRecipients = getFlag(ctx.args, "--max-recipients");
1149
1235
  const payload = await callMutation(
1150
1236
  ctx,
1151
1237
  "functions/emailCampaigns:createCampaign",
@@ -1153,12 +1239,8 @@ async function runCampaign(ctx: EmailCliContext) {
1153
1239
  name: requiredFlag(ctx.args, "--name"),
1154
1240
  ...sender,
1155
1241
  templateId: requiredFlag(ctx.args, "--template"),
1156
- scheduledAt: getFlag(ctx.args, "--scheduled-at")
1157
- ? Date.parse(requiredFlag(ctx.args, "--scheduled-at"))
1158
- : undefined,
1159
- maxRecipients: getFlag(ctx.args, "--max-recipients")
1160
- ? Number(requiredFlag(ctx.args, "--max-recipients"))
1161
- : undefined,
1242
+ scheduledAt: scheduledAt ? Date.parse(scheduledAt) : undefined,
1243
+ maxRecipients: maxRecipients ? Number(maxRecipients) : undefined,
1162
1244
  },
1163
1245
  );
1164
1246
  out(
@@ -1377,7 +1459,7 @@ async function runSequence(ctx: EmailCliContext) {
1377
1459
  ...sender,
1378
1460
  replyToMode: getFlag(ctx.args, "--reply-to-mode"),
1379
1461
  stopOnReply,
1380
- stopOnClick: getFlag(ctx.args, "--stop-on-click") !== undefined,
1462
+ stopOnClick: hasFlag(ctx.args, "--stop-on-click"),
1381
1463
  },
1382
1464
  );
1383
1465
  out(
@@ -1426,7 +1508,7 @@ async function runSequence(ctx: EmailCliContext) {
1426
1508
  const payload = await callQuery(
1427
1509
  ctx,
1428
1510
  "functions/emailSequences:listSequences",
1429
- { includeArchived: getFlag(ctx.args, "--all") !== undefined },
1511
+ { includeArchived: hasFlag(ctx.args, "--all") },
1430
1512
  );
1431
1513
  out(ctx, payload, JSON.stringify(payload, null, 2));
1432
1514
  return;
@@ -57,6 +57,48 @@ const nativeEmailCreate = z.object({
57
57
  requestId: z.string(),
58
58
  ...nativeEmailContent,
59
59
  });
60
+ const inboxHeader = z.object({
61
+ messageHandle: z.string(),
62
+ threadHandle: z.string(),
63
+ mailboxId: z.string(),
64
+ from: nativeEmailRecipient,
65
+ to: z.array(nativeEmailRecipient),
66
+ subject: z.string(),
67
+ at: z.number(),
68
+ dateSource: z.enum(["sentAt", "createdAt"]),
69
+ direction: z.enum(["inbound", "outbound"]),
70
+ status: z.string(),
71
+ hasAttachments: z.boolean(),
72
+ headersTruncated: z.boolean(),
73
+ });
74
+ const inboxCoverage = z.object({
75
+ source: z.literal("synced"),
76
+ scope: z.literal("mailbox_linked_messages"),
77
+ mailboxId: z.string(),
78
+ mailboxEmail: z.string(),
79
+ mailboxStatus: z.string(),
80
+ ingestionMode: z.string(),
81
+ phase: z.string(),
82
+ syncStatus: z.string().nullable(),
83
+ lastPolledAt: z.number().nullable(),
84
+ lastBackfillCompletedAt: z.number().nullable(),
85
+ oldestSeenAt: z.number().nullable(),
86
+ backfillStatus: z.string().nullable(),
87
+ backfillCapped: z.boolean(),
88
+ providerComplete: z.literal(false),
89
+ privacyLimited: z.boolean(),
90
+ });
91
+ const inboxPaging = {
92
+ limit: z.coerce.number().int().min(1).max(50).optional(),
93
+ cursor: z.string().optional(),
94
+ };
95
+ const inboxPage = z.object({
96
+ page: z.array(inboxHeader),
97
+ continueCursor: z.string().nullable(),
98
+ isDone: z.boolean(),
99
+ coverage: inboxCoverage,
100
+ order: z.literal("sentAt_desc_then_undated"),
101
+ });
60
102
  const nativeEmailReceipt = z
61
103
  .object({
62
104
  ok: z.boolean(),
@@ -2118,7 +2160,7 @@ const composioToolRun = z.object({
2118
2160
  tool_slug: z
2119
2161
  .string()
2120
2162
  .optional()
2121
- .describe("Tool slug, e.g. GMAIL_FETCH_EMAILS."),
2163
+ .describe("Tool slug, e.g. SLACK_LIST_CHANNELS."),
2122
2164
  arguments: z
2123
2165
  .record(z.string(), z.unknown())
2124
2166
  .optional()
@@ -3392,8 +3434,34 @@ export const requestSchemas: Record<string, ZodTypeAny> = {
3392
3434
  )
3393
3435
  .optional(),
3394
3436
  }),
3437
+ "email.inbox.search": z.object({
3438
+ ...inboxPaging,
3439
+ mailbox: z.string().optional(),
3440
+ from: z.string().min(1).max(500).optional(),
3441
+ to: z.string().min(1).max(500).optional(),
3442
+ subject: z.string().min(1).max(500).optional(),
3443
+ after: z.coerce
3444
+ .number()
3445
+ .optional()
3446
+ .describe(
3447
+ "Inclusive Unix milliseconds; missing sentAt falls back to createdAt.",
3448
+ ),
3449
+ before: z.coerce
3450
+ .number()
3451
+ .optional()
3452
+ .describe("Exclusive Unix milliseconds."),
3453
+ direction: z.enum(["inbound", "outbound"]).optional(),
3454
+ }),
3455
+ "email.inbox.thread": z.object({ ...inboxPaging, threadHandle: z.string() }),
3456
+ "email.inbox.message": z.object({
3457
+ messageHandle: z.string(),
3458
+ format: z.enum(["text", "html"]).optional(),
3459
+ maxBytes: z.coerce.number().int().min(1).max(100000).optional(),
3460
+ }),
3395
3461
  "email.drafts.create": nativeEmailCreate,
3396
- "email.native.send": nativeEmailCreate,
3462
+ "email.native.send": nativeEmailCreate.extend({
3463
+ to: z.array(nativeEmailRecipient).min(1),
3464
+ }),
3397
3465
  "email.drafts.get": z.object({ draftId: z.string() }),
3398
3466
  "email.drafts.senders": z.object({ organizationId: z.string().optional() }),
3399
3467
  "email.drafts.update": z.object({
@@ -3536,6 +3604,7 @@ export const requestSchemas: Record<string, ZodTypeAny> = {
3536
3604
 
3537
3605
  // CRM objects
3538
3606
  "crm.objects.list": noInput,
3607
+ "crm.objects.listCompact": noInput,
3539
3608
  "crm.objects.get": z.object({ name: z.string() }),
3540
3609
  "crm.objects.create": z.object({
3541
3610
  name: z.string().describe("Object name (lowercased, singular)."),
@@ -3631,6 +3700,7 @@ export const requestSchemas: Record<string, ZodTypeAny> = {
3631
3700
 
3632
3701
  // CRM fields
3633
3702
  "crm.fields.list": z.object({ objectName: z.string() }),
3703
+ "crm.fields.listCompact": z.object({ objectName: z.string() }),
3634
3704
  "crm.fields.create": z.object({
3635
3705
  objectName: z.string(),
3636
3706
  name: z.string(),
@@ -4163,6 +4233,18 @@ export const requestSchemas: Record<string, ZodTypeAny> = {
4163
4233
  "Number field to total per bucket (e.g. Amount grouped by Stage). Exact at any table size.",
4164
4234
  ),
4165
4235
  }),
4236
+ "crm.search.page": z.object({
4237
+ text: z.string().describe("Full-text query over readable entries."),
4238
+ objectName: z.string().optional(),
4239
+ select: z.array(z.string().min(1)).min(1).max(30).optional(),
4240
+ limit: z.coerce.number().int().min(1).max(100).optional(),
4241
+ cursor: z
4242
+ .string()
4243
+ .optional()
4244
+ .describe(
4245
+ "Use the preceding continueCursor, with the same query and projection.",
4246
+ ),
4247
+ }),
4166
4248
  "crm.search": z.object({
4167
4249
  text: z.string().describe("Full-text query."),
4168
4250
  objectName: z.string().optional().describe("Omit to search org-wide."),
@@ -5157,6 +5239,24 @@ export const responseSchemas: Record<string, ZodTypeAny> = {
5157
5239
 
5158
5240
  // Workspace orientation
5159
5241
  "workspace.context.sections": z.object({}).passthrough(),
5242
+ "email.inbox.search": inboxPage,
5243
+ "email.inbox.thread": inboxPage,
5244
+ "email.inbox.message": z.object({
5245
+ header: inboxHeader,
5246
+ coverage: inboxCoverage,
5247
+ body: z.object({
5248
+ format: z.enum(["text", "html"]),
5249
+ content: z.string(),
5250
+ truncated: z.boolean(),
5251
+ totalBytes: z.number(),
5252
+ }),
5253
+ recipients: z.object({
5254
+ to: z.array(nativeEmailRecipient),
5255
+ cc: z.array(nativeEmailRecipient),
5256
+ bcc: z.array(nativeEmailRecipient),
5257
+ truncated: z.boolean(),
5258
+ }),
5259
+ }),
5160
5260
  "email.drafts.create": nativeEmailReceipt,
5161
5261
  "email.drafts.get": nativeEmailReceipt,
5162
5262
  "email.drafts.update": nativeEmailReceipt,
@@ -5283,6 +5383,18 @@ export const responseSchemas: Record<string, ZodTypeAny> = {
5283
5383
 
5284
5384
  // CRM objects
5285
5385
  "crm.objects.list": z.array(crmObjectDoc),
5386
+ "crm.objects.listCompact": z.array(
5387
+ z.object({
5388
+ _id: z.string(),
5389
+ name: z.string(),
5390
+ displayName: z.string(),
5391
+ description: z.string().optional(),
5392
+ entryCount: z.number().nullable(),
5393
+ countScope: z.enum(["workspace", "private_unknown"]),
5394
+ visibility: z.enum(["workspace", "owner_only", "owner_plus_members"]),
5395
+ archived: z.boolean(),
5396
+ }),
5397
+ ),
5286
5398
  "crm.objects.get": crmObjectDoc.nullable(),
5287
5399
  "crm.objects.create": z.object({ id: z.string() }),
5288
5400
  "crm.objects.update": okResponse,
@@ -5337,6 +5449,20 @@ export const responseSchemas: Record<string, ZodTypeAny> = {
5337
5449
 
5338
5450
  // CRM fields
5339
5451
  "crm.fields.list": z.array(crmFieldDoc),
5452
+ "crm.fields.listCompact": z.array(
5453
+ z.object({
5454
+ _id: z.string(),
5455
+ name: z.string(),
5456
+ type: z.string(),
5457
+ required: z.boolean(),
5458
+ readOnly: z.boolean(),
5459
+ defaultValue: z.string().optional(),
5460
+ enumValues: z.array(z.string()).optional(),
5461
+ enumMultiple: z.boolean(),
5462
+ relatedObjectId: z.string().optional(),
5463
+ relationshipType: z.string().optional(),
5464
+ }),
5465
+ ),
5340
5466
  "crm.fields.create": z.object({ id: z.string() }),
5341
5467
  "crm.fields.update": okResponse,
5342
5468
  "crm.fields.delete": okResponse,
@@ -5493,6 +5619,16 @@ export const responseSchemas: Record<string, ZodTypeAny> = {
5493
5619
  sumNumber: z.number().optional(),
5494
5620
  }),
5495
5621
  ),
5622
+ "crm.search.page": z.object({
5623
+ page: z.array(
5624
+ z.object({ _id: z.string(), objectId: z.string(), data: fieldsMap }),
5625
+ ),
5626
+ isDone: z.boolean(),
5627
+ continueCursor: z.string().nullable(),
5628
+ returnedCount: z.number(),
5629
+ selectedFields: z.array(z.string()).nullable(),
5630
+ completeness: z.literal("page"),
5631
+ }),
5496
5632
  "crm.search": z.array(
5497
5633
  z
5498
5634
  .object({
@@ -6217,6 +6353,12 @@ export const convexExtras: Record<string, ConvexExtras> = {
6217
6353
  // ---------------------------------------------------------------------------
6218
6354
 
6219
6355
  export const requestExamples: Record<string, unknown> = {
6356
+ "email.native.send": {
6357
+ requestId: "send-intro-001",
6358
+ to: [{ email: "ada@example.com", name: "Ada" }],
6359
+ subject: "Introduction",
6360
+ textBody: "Hello Ada",
6361
+ },
6220
6362
  "campaigns.accounts.linkedin": {},
6221
6363
  "campaigns.create": {
6222
6364
  name: "Founder outreach",
@@ -6470,11 +6612,11 @@ export const requestExamples: Record<string, unknown> = {
6470
6612
  input_images_base64: ["iVBORw0KGgoAAAANSUhEUg..."],
6471
6613
  },
6472
6614
  "tool.search": {
6473
- queries: [{ use_case: "fetch the 10 most recent emails" }],
6615
+ queries: [{ use_case: "list Slack channels" }],
6474
6616
  },
6475
6617
  "tool.run": {
6476
- tool_slug: "GMAIL_FETCH_EMAILS",
6477
- arguments: { max_results: 10 },
6618
+ tool_slug: "SLACK_LIST_CHANNELS",
6619
+ arguments: { limit: 10 },
6478
6620
  },
6479
6621
  "crm.people.search": {
6480
6622
  titles: ["Head of Sales"],
@@ -8019,18 +8161,18 @@ export const responseExamples: Record<string, unknown> = {
8019
8161
  error: null,
8020
8162
  results: [
8021
8163
  {
8022
- use_case: "fetch recent emails",
8023
- primary_tool_slugs: ["GMAIL_FETCH_EMAILS"],
8024
- toolkits: ["gmail"],
8164
+ use_case: "list Slack channels",
8165
+ primary_tool_slugs: ["SLACK_LIST_CHANNELS"],
8166
+ toolkits: ["slack"],
8025
8167
  },
8026
8168
  ],
8027
8169
  toolkit_connection_statuses: [
8028
- { toolkit: "gmail", has_active_connection: true },
8170
+ { toolkit: "slack", has_active_connection: true },
8029
8171
  ],
8030
8172
  tool_schemas: {
8031
- GMAIL_FETCH_EMAILS: {
8032
- toolkit: "gmail",
8033
- tool_slug: "GMAIL_FETCH_EMAILS",
8173
+ SLACK_LIST_CHANNELS: {
8174
+ toolkit: "slack",
8175
+ tool_slug: "SLACK_LIST_CHANNELS",
8034
8176
  execution_ref: "eyJzZXNzaW9uIjoi...",
8035
8177
  execution_ref_version: 1,
8036
8178
  },
@@ -8039,11 +8181,11 @@ export const responseExamples: Record<string, unknown> = {
8039
8181
  execution_ref_version: 1,
8040
8182
  },
8041
8183
  "tool.run": {
8042
- data: { messages: [{ id: "msg01", subject: "Quick intro" }] },
8184
+ data: { channels: [{ id: "C_EXAMPLE", name: "general" }] },
8043
8185
  error: null,
8044
8186
  log_id: "log_exec_01",
8045
8187
  execution_mode: "gateway_tool_router",
8046
- tool_slug: "GMAIL_FETCH_EMAILS",
8188
+ tool_slug: "SLACK_LIST_CHANNELS",
8047
8189
  tool_router_session_id: "trs_abc123",
8048
8190
  },
8049
8191
  "tool.disconnect": { id: "ca_github01", deleted: true },
@@ -141,6 +141,7 @@ export const APP_SDK_OPERATIONS: readonly AppSdkOperationInfo[] = [
141
141
  {"id":"chat.send","group":"chat","summary":"Send a message to an existing chat thread.","cli":"dench chat send","method":"POST","path":"/chat/threads/{threadId}/messages","permission":"ai:chat","pathParams":["threadId"]},
142
142
  {"id":"chat.follow","group":"chat","summary":"Follow a live chat response stream.","cli":"dench chat follow","method":"GET","path":"/chat/threads/{threadId}/stream","permission":"ai:chat","pathParams":["threadId"]},
143
143
  {"id":"crm.objects.list","group":"crm","summary":"List CRM objects.","cli":"dench crm objects list","method":"GET","path":"/crm/objects","permission":"crm:read","pathParams":[]},
144
+ {"id":"crm.objects.listCompact","group":"crm","summary":"List names, display labels, visibility and workspace counts without internal state.","cli":"dench crm objects list --compact","method":"GET","path":"/crm/catalog/objects","permission":"crm:read","pathParams":[]},
144
145
  {"id":"crm.objects.get","group":"crm","summary":"Get a CRM object.","cli":"dench crm objects get","method":"GET","path":"/crm/objects/{name}","permission":"crm:read","pathParams":["name"]},
145
146
  {"id":"crm.objects.create","group":"crm","summary":"Create a CRM object.","cli":"dench crm objects create","method":"POST","path":"/crm/objects","permission":"crm:write","pathParams":[]},
146
147
  {"id":"crm.objects.update","group":"crm","summary":"Update a CRM object.","cli":"dench crm objects update","method":"PATCH","path":"/crm/objects/{name}","permission":"crm:write","pathParams":["name"]},
@@ -165,6 +166,7 @@ export const APP_SDK_OPERATIONS: readonly AppSdkOperationInfo[] = [
165
166
  {"id":"crm.sidebar.groups.move-item","group":"crm","summary":"Move a sidebar row into a group (groupId set) or back to the top level (groupId null). Open to any member.","cli":"dench crm sidebar group add","method":"POST","path":"/crm/sidebar/groups/move","permission":"crm:write","pathParams":[]},
166
167
  {"id":"crm.sidebar.pins.set","group":"crm","summary":"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.","cli":"dench crm sidebar pin","method":"POST","path":"/crm/sidebar/pins","permission":"crm:write","pathParams":[]},
167
168
  {"id":"crm.fields.list","group":"crm","summary":"List fields for a CRM object, including read-only calculation metadata.","cli":"dench crm fields list","method":"GET","path":"/crm/objects/{objectName}/fields","permission":"crm:read","pathParams":["objectName"]},
169
+ {"id":"crm.fields.listCompact","group":"crm","summary":"List field names, types and effective constraints without indexing or enrichment internals.","cli":"dench crm fields list --compact","method":"GET","path":"/crm/catalog/objects/{objectName}/fields","permission":"crm:read","pathParams":["objectName"]},
168
170
  {"id":"crm.fields.create","group":"crm","summary":"Create a CRM field or read-only campaign activity boolean column.","cli":"dench crm fields create","method":"POST","path":"/crm/objects/{objectName}/fields","permission":"crm:write","pathParams":["objectName"]},
169
171
  {"id":"crm.fields.update","group":"crm","summary":"Update a CRM field or change/explicitly clear an existing campaign calculation.","cli":"dench crm fields update","method":"PATCH","path":"/crm/objects/{objectName}/fields/{fieldName}","permission":"crm:write","pathParams":["objectName","fieldName"]},
170
172
  {"id":"crm.fields.delete","group":"crm","summary":"Delete a CRM field.","cli":"dench crm fields delete","method":"DELETE","path":"/crm/objects/{objectName}/fields/{fieldName}","permission":"crm:write","pathParams":["objectName","fieldName"]},
@@ -237,6 +239,7 @@ export const APP_SDK_OPERATIONS: readonly AppSdkOperationInfo[] = [
237
239
  {"id":"crm.sql","group":"crm","summary":"Run CRM SQL-compatible query.","cli":"dench crm sql","method":"POST","path":"/crm/sql","permission":"crm:read","pathParams":[]},
238
240
  {"id":"crm.aggregate","group":"crm","summary":"Aggregate CRM entries.","cli":"dench crm aggregate","method":"POST","path":"/crm/aggregate","permission":"crm:read","pathParams":[]},
239
241
  {"id":"crm.search","group":"crm","summary":"Search CRM entries.","cli":"dench crm search","method":"GET","path":"/crm/search","permission":"crm:read","pathParams":[]},
242
+ {"id":"crm.search.page","group":"crm","summary":"Search bounded pages of readable CRM entries, optionally projecting cells. Always follow continueCursor until isDone.","cli":"dench crm search --page","method":"GET","path":"/crm/search/page","permission":"crm:read","pathParams":[]},
240
243
  {"id":"crm.statuses.list","group":"crm","summary":"List CRM statuses.","cli":"dench crm statuses list","method":"GET","path":"/crm/objects/{objectName}/statuses","permission":"crm:read","pathParams":["objectName"]},
241
244
  {"id":"crm.statuses.set","group":"crm","summary":"Set CRM statuses.","cli":"dench crm statuses set","method":"PUT","path":"/crm/objects/{objectName}/statuses","permission":"crm:write","pathParams":["objectName"]},
242
245
  {"id":"crm.batch","group":"crm","summary":"Apply a CRM batch.","cli":"dench crm batch","method":"POST","path":"/crm/batch","permission":"crm:write","pathParams":[]},
@@ -315,6 +318,9 @@ export const APP_SDK_OPERATIONS: readonly AppSdkOperationInfo[] = [
315
318
  {"id":"email.identity.disable","group":"email","summary":"Soft-disable a sender (or every sender on a domain) for the workspace; sends are blocked until restored.","cli":"dench email identity disable","method":"POST","path":"/email/sending-identities/{identity}/disable","permission":"email:write","pathParams":["identity"]},
316
319
  {"id":"email.identity.restore","group":"email","summary":"Restore a previously disabled sender for the workspace.","cli":"dench email identity restore","method":"POST","path":"/email/sending-identities/{identity}/restore","permission":"email:write","pathParams":["identity"]},
317
320
  {"id":"email.identity.delete","group":"email","summary":"Permanently delete a sender (or every sender on a domain) from the workspace; re-adding it requires verification from scratch.","cli":"dench email identity delete","method":"DELETE","path":"/email/sending-identities/{identity}","permission":"email:write","pathParams":["identity"]},
321
+ {"id":"email.inbox.search","group":"email","summary":"Search authorized synced mailbox headers with bounded cursor pagination. isDone covers stored records, not provider completeness.","cli":"dench email inbox search","method":"GET","path":"/email/inbox/search","permission":"email:read","pathParams":[]},
322
+ {"id":"email.inbox.thread","group":"email","summary":"Read a bounded page of synced headers using an exact thread handle from inbox search; bodies remain on demand.","cli":"dench email inbox thread","method":"GET","path":"/email/inbox/threads/{threadHandle}","permission":"email:read","pathParams":["threadHandle"]},
323
+ {"id":"email.inbox.message","group":"email","summary":"Read a bounded stored body using an exact message handle from inbox search; rechecks mailbox and record privacy.","cli":"dench email inbox message","method":"GET","path":"/email/inbox/messages/{messageHandle}","permission":"email:read","pathParams":["messageHandle"]},
318
324
  {"id":"email.drafts.create","group":"email","summary":"Create an editable Dench email draft and synchronize its selected native mailbox asynchronously. Use a stable requestId for retries.","cli":"dench email draft create","method":"POST","path":"/email/drafts","permission":"email:write","pathParams":[]},
319
325
  {"id":"email.drafts.get","group":"email","summary":"Read an email draft, eligible senders and provider sync status.","cli":"dench email draft show","method":"GET","path":"/email/drafts/{draftId}","permission":"email:read","pathParams":["draftId"]},
320
326
  {"id":"email.drafts.senders","group":"email","summary":"List native mailboxes this caller may use.","cli":"dench email draft senders","method":"GET","path":"/email/drafts/senders","permission":"email:read","pathParams":[]},
@@ -322,8 +328,8 @@ export const APP_SDK_OPERATIONS: readonly AppSdkOperationInfo[] = [
322
328
  {"id":"email.drafts.send","group":"email","summary":"Send the current Dench draft revision through its native mailbox. Repeated requests share a durable receipt.","cli":"dench email draft send","method":"POST","path":"/email/drafts/{draftId}/send","permission":"email:write","pathParams":["draftId"]},
323
329
  {"id":"email.drafts.upload","group":"email","summary":"Upload attachment bytes authorized against this draft before adding its storage reference.","cli":"dench email draft attach","method":"POST","path":"/email/drafts/{draftId}/attachments","permission":"email:write","pathParams":["draftId"]},
324
330
  {"id":"email.drafts.signature","group":"email","summary":"Load the selected mailbox signature for explicit visible insertion.","cli":"dench email draft signature","method":"GET","path":"/email/drafts/{draftId}/signature","permission":"email:read","pathParams":["draftId"]},
325
- {"id":"email.native.send","group":"email","summary":"Send through a native Gmail or Outlook mailbox with a durable request receipt. Only use for an explicit send request.","cli":"dench email send --mailbox","method":"POST","path":"/email/native/send","permission":"email:write","pathParams":[]},
326
- {"id":"email.send","group":"email","summary":"Send one raw email (no campaign): to/cc/bcc, custom from-name and reply-to, optional reply threading via replyToMessageId.","cli":"dench email send","method":"POST","path":"/email/send","permission":"email:write","pathParams":[]},
331
+ {"id":"email.native.send","group":"email","summary":"Send through a native Gmail or Outlook mailbox with a durable request receipt. Only use for an explicit send request.","cli":"dench email send [--mailbox <id/email>]","method":"POST","path":"/email/native/send","permission":"email:write","pathParams":[]},
332
+ {"id":"email.send","group":"email","summary":"Send one raw email (no campaign): to/cc/bcc, custom from-name and reply-to, optional reply threading via replyToMessageId.","cli":"dench email send --from <verified-email>","method":"POST","path":"/email/send","permission":"email:write","pathParams":[]},
327
333
  {"id":"email.message.get","group":"email","summary":"Read a raw email message's status and open/click counters.","cli":"dench email message status","method":"GET","path":"/email/messages/{messageId}","permission":"email:read","pathParams":["messageId"]},
328
334
  {"id":"email.message.list","group":"email","summary":"List recent raw email messages for the workspace.","cli":"dench email message list","method":"GET","path":"/email/messages","permission":"email:read","pathParams":[]},
329
335
  {"id":"email.template.create","group":"email","summary":"Create a managed email campaign template.","cli":"dench email template create","method":"POST","path":"/email/templates","permission":"email:write","pathParams":[]},
@@ -459,7 +465,7 @@ export const APP_SDK_OPERATIONS: readonly AppSdkOperationInfo[] = [
459
465
  {"id":"agentConfig.user.show","group":"agent-config","summary":"Read USER.md.","cli":"dench user show","method":"GET","path":"/agent-config/user","permission":"agent-config:read","pathParams":[]},
460
466
  {"id":"agentConfig.user.edit","group":"agent-config","summary":"Update USER.md.","cli":"dench user edit","method":"PUT","path":"/agent-config/user","permission":"agent-config:write","pathParams":[]},
461
467
  {"id":"agentConfig.tools.show","group":"agent-config","summary":"Read tool notes.","cli":"dench tools show","method":"GET","path":"/agent-config/tools","permission":"agent-config:read","pathParams":[]},
462
- {"id":"agentConfig.tools.edit","group":"agent-config","summary":"Update tool notes.","cli":"dench tools notes-edit","method":"PUT","path":"/agent-config/tools","permission":"agent-config:write","pathParams":[]},
468
+ {"id":"agentConfig.tools.edit","group":"agent-config","summary":"Update tool notes.","cli":"dench tools edit","method":"PUT","path":"/agent-config/tools","permission":"agent-config:write","pathParams":[]},
463
469
  {"id":"agentConfig.mem.show","group":"agent-config","summary":"Read aggregate memory.","cli":"dench mem show","method":"GET","path":"/agent-config/mem","permission":"agent-config:read","pathParams":[]},
464
470
  {"id":"agentConfig.mem.edit","group":"agent-config","summary":"Update aggregate memory.","cli":"dench mem set","method":"PUT","path":"/agent-config/mem","permission":"agent-config:write","pathParams":[]},
465
471
  {"id":"agentConfig.bootstrap.show","group":"agent-config","summary":"Read bootstrap state.","cli":"dench bootstrap status","method":"GET","path":"/agent-config/bootstrap","permission":"agent-config:read","pathParams":[]},
@@ -0,0 +1,17 @@
1
+ import bundle from "./references.generated.json";
2
+
3
+ export const referenceRevision = bundle.revision;
4
+ const files: Record<string, { sha256: string; content: string }> = bundle.files;
5
+ export function getBundledReference(path: string): string | undefined {
6
+ return Object.hasOwn(files, path) ? files[path].content : undefined;
7
+ }
8
+ export function bundledReferenceIndex() {
9
+ return {
10
+ revision: referenceRevision,
11
+ references: Object.entries(files).map(([path, file]) => ({
12
+ path,
13
+ sha256: file.sha256,
14
+ bytes: new TextEncoder().encode(file.content).byteLength,
15
+ })),
16
+ };
17
+ }