@dench.com/cli 3.6.3-staging.372 → 3.6.5-staging.384
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -0
- package/agent-config.ts +10 -16
- package/app-sdk.d.ts +234 -6
- package/crm.ts +62 -16
- package/dench.ts +70 -36
- package/email.ts +207 -2
- package/lib/api-schemas.ts +127 -9
- package/lib/app-sdk-catalog.generated.ts +17 -0
- package/lib/app-sdk-surface.ts +49 -0
- package/lib/command-registry.ts +115 -3
- package/lib/enrichment-gateway.ts +12 -182
- package/lib/enrichment-shapes.ts +182 -0
- package/lib/native-providers.ts +44 -0
- package/package.json +1 -1
- package/tools.ts +106 -61
package/dench.ts
CHANGED
|
@@ -32,7 +32,6 @@ import { cliVersion } from "./lib/cli-version";
|
|
|
32
32
|
import { denchConfigDirectory } from "./lib/config-path.mjs";
|
|
33
33
|
import { resolveOrganizationBySelector } from "./lib/organizationSelector";
|
|
34
34
|
import { backendErrorText } from "./lib/public-error";
|
|
35
|
-
import { normalizeUnipileToolkitSlug } from "./lib/unipile-toolkits";
|
|
36
35
|
import { formatApprovalOpenMessage, openUrl } from "./openUrl";
|
|
37
36
|
import {
|
|
38
37
|
explicitSessionKeyInput,
|
|
@@ -176,6 +175,9 @@ const api = {
|
|
|
176
175
|
),
|
|
177
176
|
},
|
|
178
177
|
agentConfig: {
|
|
178
|
+
workspaceContext: makeFunctionReference<"query">(
|
|
179
|
+
"functions/agentConfig:workspaceContext",
|
|
180
|
+
),
|
|
179
181
|
workspaceInstructions: makeFunctionReference<"query">(
|
|
180
182
|
"functions/agentConfig:workspaceInstructions",
|
|
181
183
|
),
|
|
@@ -465,6 +467,7 @@ Usage:
|
|
|
465
467
|
dench logout [session-key-or-workspace-slug] [--session <key-or-scope>] [--host <host>] [--all] [--json]
|
|
466
468
|
dench backend [show|set|clear] [<production|staging|local|<url>>] [--json]
|
|
467
469
|
dench context [--instructions] [--json]
|
|
470
|
+
dench context --sections organisation,user,memory [--json]
|
|
468
471
|
dench instructions [--json]
|
|
469
472
|
dench status [--mine|--self] [--json]
|
|
470
473
|
dench memory search "query" [--limit 8] [--json]
|
|
@@ -742,6 +745,7 @@ function contextHelp() {
|
|
|
742
745
|
|
|
743
746
|
Usage:
|
|
744
747
|
dench context [--instructions] [--json]
|
|
748
|
+
dench context --sections organisation,user,memory [--json]
|
|
745
749
|
dench instructions [--json]
|
|
746
750
|
|
|
747
751
|
Shows the current workspace, current agent, assigned tasks, pending approvals
|
|
@@ -3222,19 +3226,22 @@ function formatContext(context: JsonRecord) {
|
|
|
3222
3226
|
|
|
3223
3227
|
async function buildWorkspaceInstructionsContext(
|
|
3224
3228
|
runtime: Awaited<ReturnType<typeof getRuntime>>,
|
|
3229
|
+
sections?: string[],
|
|
3225
3230
|
) {
|
|
3226
3231
|
if (runtime.mode === "dev") {
|
|
3227
3232
|
return (await runtime.client.query(
|
|
3228
3233
|
api.functions.agentConfig.devWorkspaceInstructions,
|
|
3229
|
-
runtime.workspaceArgs,
|
|
3234
|
+
{ ...runtime.workspaceArgs, ...(sections ? { sections } : {}) },
|
|
3230
3235
|
)) as JsonRecord;
|
|
3231
3236
|
}
|
|
3232
3237
|
if (runtime.mode !== "session" && runtime.mode !== "apiKey") {
|
|
3233
3238
|
throw loginRequiredError("dench context --instructions");
|
|
3234
3239
|
}
|
|
3235
3240
|
return (await runtime.client.query(
|
|
3236
|
-
|
|
3237
|
-
|
|
3241
|
+
sections
|
|
3242
|
+
? api.functions.agentConfig.workspaceContext
|
|
3243
|
+
: api.functions.agentConfig.workspaceInstructions,
|
|
3244
|
+
{ sessionToken: runtime.sessionToken, ...(sections ? { sections } : {}) },
|
|
3238
3245
|
)) as JsonRecord;
|
|
3239
3246
|
}
|
|
3240
3247
|
|
|
@@ -3550,7 +3557,8 @@ async function runFilesLs() {
|
|
|
3550
3557
|
const rows = recursive
|
|
3551
3558
|
? await listConvexTreeRecursive(runtime, apiKey, target)
|
|
3552
3559
|
: await listConvexTreeAt(runtime, apiKey, target);
|
|
3553
|
-
|
|
3560
|
+
// Inline handlers share the top-level flag; filteredArgs omits --json.
|
|
3561
|
+
if (json) {
|
|
3554
3562
|
print({
|
|
3555
3563
|
ok: true,
|
|
3556
3564
|
prefix: target,
|
|
@@ -3670,7 +3678,7 @@ async function runFilesRm() {
|
|
|
3670
3678
|
);
|
|
3671
3679
|
const self = parentRows.find((row) => row.path === target);
|
|
3672
3680
|
if (!self) {
|
|
3673
|
-
if (
|
|
3681
|
+
if (json) {
|
|
3674
3682
|
print({ ok: true, path: target, removedFiles: 0, removedDirs: 0 });
|
|
3675
3683
|
} else {
|
|
3676
3684
|
process.stdout.write(
|
|
@@ -3772,7 +3780,7 @@ async function runFilesCat() {
|
|
|
3772
3780
|
});
|
|
3773
3781
|
}
|
|
3774
3782
|
|
|
3775
|
-
if (
|
|
3783
|
+
if (json) {
|
|
3776
3784
|
print({ ok: true, ...file });
|
|
3777
3785
|
return;
|
|
3778
3786
|
}
|
|
@@ -3822,7 +3830,7 @@ async function runFilesWrite() {
|
|
|
3822
3830
|
} as never,
|
|
3823
3831
|
)) as { path: string; contentHash: string };
|
|
3824
3832
|
|
|
3825
|
-
if (
|
|
3833
|
+
if (json) {
|
|
3826
3834
|
print({ ok: true, ...result, bytes: content.length });
|
|
3827
3835
|
return;
|
|
3828
3836
|
}
|
|
@@ -3850,7 +3858,7 @@ async function runFilesDownload() {
|
|
|
3850
3858
|
);
|
|
3851
3859
|
}
|
|
3852
3860
|
const target = normalizeFilesPath(targetRaw);
|
|
3853
|
-
const jsonOut =
|
|
3861
|
+
const jsonOut = json;
|
|
3854
3862
|
|
|
3855
3863
|
if (hasFlag("--url")) {
|
|
3856
3864
|
const url = (await runtime.client.action(
|
|
@@ -4800,7 +4808,14 @@ async function main() {
|
|
|
4800
4808
|
| Awaited<ReturnType<typeof resolveGatewayCommandAuth>>
|
|
4801
4809
|
| undefined;
|
|
4802
4810
|
try {
|
|
4803
|
-
|
|
4811
|
+
if (
|
|
4812
|
+
subArgs[0] !== "draft" &&
|
|
4813
|
+
!subArgs.some(
|
|
4814
|
+
(arg) => arg === "--mailbox" || arg.startsWith("--mailbox="),
|
|
4815
|
+
)
|
|
4816
|
+
) {
|
|
4817
|
+
gatewayAuth = await resolveGatewayCommandAuth(runtime, "dench email");
|
|
4818
|
+
}
|
|
4804
4819
|
} catch {
|
|
4805
4820
|
gatewayAuth = undefined;
|
|
4806
4821
|
}
|
|
@@ -4881,35 +4896,16 @@ async function main() {
|
|
|
4881
4896
|
if (argsIndex !== -1) parseJson(filteredSubArgs[argsIndex + 1]);
|
|
4882
4897
|
}
|
|
4883
4898
|
const runtime = await getRuntime();
|
|
4884
|
-
const isSlackConnect =
|
|
4885
|
-
sub === "connect" &&
|
|
4886
|
-
["slack", "slackbot"].includes(
|
|
4887
|
-
(filteredSubArgs[1] ?? "").trim().toLowerCase(),
|
|
4888
|
-
);
|
|
4889
|
-
const isUnipileCommand =
|
|
4890
|
-
(sub === "connect" || sub === "status") &&
|
|
4891
|
-
normalizeUnipileToolkitSlug(filteredSubArgs[1] ?? "") !== null;
|
|
4892
|
-
let gatewayAuth: GatewayCommandAuth | null = null;
|
|
4893
|
-
let gatewayAuthError: string | undefined;
|
|
4894
|
-
if (!isSlackConnect && !isUnipileCommand) {
|
|
4895
|
-
try {
|
|
4896
|
-
gatewayAuth = await resolveGatewayCommandAuth(runtime, "dench tool");
|
|
4897
|
-
} catch (error) {
|
|
4898
|
-
if (sub !== "status") throw error;
|
|
4899
|
-
gatewayAuthError = gatewayAuthFailureReason(error);
|
|
4900
|
-
}
|
|
4901
|
-
}
|
|
4902
4899
|
await runToolCommand({
|
|
4903
4900
|
args: filteredSubArgs,
|
|
4904
4901
|
jsonOutput: json,
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
: {}),
|
|
4902
|
+
resolveGatewayAuth: async () => {
|
|
4903
|
+
try {
|
|
4904
|
+
return await resolveGatewayCommandAuth(runtime, "dench tool");
|
|
4905
|
+
} catch (error) {
|
|
4906
|
+
return { gatewayAuthError: gatewayAuthFailureReason(error) };
|
|
4907
|
+
}
|
|
4908
|
+
},
|
|
4913
4909
|
...unipileQueryAuth(runtime),
|
|
4914
4910
|
...(runtime.mode !== "dev"
|
|
4915
4911
|
? {
|
|
@@ -5260,6 +5256,44 @@ async function main() {
|
|
|
5260
5256
|
return;
|
|
5261
5257
|
}
|
|
5262
5258
|
|
|
5259
|
+
if (command === "context" && args.includes("--sections")) {
|
|
5260
|
+
const raw = args[args.indexOf("--sections") + 1];
|
|
5261
|
+
const sections = raw
|
|
5262
|
+
?.split(",")
|
|
5263
|
+
.map((section) => section.trim().toLowerCase())
|
|
5264
|
+
.filter(Boolean);
|
|
5265
|
+
const allowed = [
|
|
5266
|
+
"identity",
|
|
5267
|
+
"organisation",
|
|
5268
|
+
"user",
|
|
5269
|
+
"memory",
|
|
5270
|
+
"rules",
|
|
5271
|
+
"tools",
|
|
5272
|
+
];
|
|
5273
|
+
if (
|
|
5274
|
+
!sections?.length ||
|
|
5275
|
+
sections.some((section) => !allowed.includes(section))
|
|
5276
|
+
)
|
|
5277
|
+
throw new Error(
|
|
5278
|
+
`Usage: dench context --sections ${allowed.join(",")} [--json]`,
|
|
5279
|
+
);
|
|
5280
|
+
const context = await buildWorkspaceInstructionsContext(runtime, [
|
|
5281
|
+
...new Set(sections),
|
|
5282
|
+
]);
|
|
5283
|
+
const documents = Array.isArray(context.documents)
|
|
5284
|
+
? (context.documents as Array<{ name: string; content?: string }>)
|
|
5285
|
+
: [];
|
|
5286
|
+
print(
|
|
5287
|
+
json
|
|
5288
|
+
? context
|
|
5289
|
+
: documents
|
|
5290
|
+
.filter((doc) => doc.content !== undefined)
|
|
5291
|
+
.map((doc) => `## ${doc.name}\n${doc.content || "(empty)"}`)
|
|
5292
|
+
.join("\n\n"),
|
|
5293
|
+
);
|
|
5294
|
+
return;
|
|
5295
|
+
}
|
|
5296
|
+
|
|
5263
5297
|
if (command === "context" && args.includes("--instructions")) {
|
|
5264
5298
|
const instructions = await buildWorkspaceInstructionsContext(runtime);
|
|
5265
5299
|
print(
|
package/email.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createHash, createHmac, randomUUID } from "node:crypto";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { basename, extname, join } from "node:path";
|
|
2
4
|
import type { ConvexHttpClient } from "convex/browser";
|
|
3
5
|
import { makeFunctionReference } from "convex/server";
|
|
4
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
assertNoUnknownFlags,
|
|
8
|
+
getFlag,
|
|
9
|
+
hasFlag,
|
|
10
|
+
parseJson,
|
|
11
|
+
} from "./lib/cli-args";
|
|
12
|
+
import { denchConfigDirectory } from "./lib/config-path.mjs";
|
|
5
13
|
|
|
6
14
|
type EmailCliContext = {
|
|
7
15
|
convex: ConvexHttpClient;
|
|
@@ -358,6 +366,13 @@ export function emailHelp() {
|
|
|
358
366
|
console.log(`dench email — outbound email from a connected inbox (or Dench Emailing Service)
|
|
359
367
|
|
|
360
368
|
Usage:
|
|
369
|
+
dench email draft create --to <email> --subject <subject> --text <body> [--mailbox <id/email>] [--request-id <id>] [--json]
|
|
370
|
+
dench email draft show <id> [--json]
|
|
371
|
+
dench email draft update|send <id> --revision <n> [--json]
|
|
372
|
+
dench email draft attach <id> --revision <n> --file <path> [--content-type <mime>] [--json] # up to 5 MB/file
|
|
373
|
+
dench email draft signature <id> [--json] # read signature HTML; insert visibly into the body
|
|
374
|
+
# 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]
|
|
361
376
|
dench email identity verify --from founder@example.com [--private] [--json]
|
|
362
377
|
dench email identity verify --domain example.com [--from founder@example.com] [--private] [--json]
|
|
363
378
|
dench email identity list [--domain example.com] [--json]
|
|
@@ -432,11 +447,20 @@ Email + LinkedIn multichannel sequences (see \`dench campaign\` — the fuller s
|
|
|
432
447
|
}
|
|
433
448
|
|
|
434
449
|
export async function runEmailCommand(ctx: EmailCliContext): Promise<void> {
|
|
450
|
+
ctx.args = ctx.args.flatMap((arg) => {
|
|
451
|
+
const split = arg.startsWith("--") ? arg.indexOf("=") : -1;
|
|
452
|
+
return split > 2 ? [arg.slice(0, split), arg.slice(split + 1)] : [arg];
|
|
453
|
+
});
|
|
454
|
+
ctx.jsonOutput = hasFlag(ctx.args, "--json") || ctx.jsonOutput;
|
|
435
455
|
const group = ctx.args.shift();
|
|
436
456
|
if (!group || group === "help" || group === "--help" || group === "-h") {
|
|
437
457
|
emailHelp();
|
|
438
458
|
return;
|
|
439
459
|
}
|
|
460
|
+
if (group === "draft") {
|
|
461
|
+
await runDraft(ctx);
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
440
464
|
if (group === "identity") {
|
|
441
465
|
await runIdentity(ctx);
|
|
442
466
|
return;
|
|
@@ -500,6 +524,17 @@ function parseAddressList(
|
|
|
500
524
|
}
|
|
501
525
|
|
|
502
526
|
async function runSend(ctx: EmailCliContext) {
|
|
527
|
+
if (ctx.args.includes("--mailbox")) {
|
|
528
|
+
const content = {
|
|
529
|
+
...(await draftContent(ctx)),
|
|
530
|
+
mailbox: requiredFlag(ctx.args, "--mailbox"),
|
|
531
|
+
threadId: getFlag(ctx.args, "--thread") ?? process.env.DENCH_THREAD_ID,
|
|
532
|
+
mode: "send",
|
|
533
|
+
};
|
|
534
|
+
const payload = await createNativeEmail(ctx, content);
|
|
535
|
+
out(ctx, payload, JSON.stringify(payload, null, 2));
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
503
538
|
const from = requiredFlag(ctx.args, "--from");
|
|
504
539
|
const to = parseAddressList(requiredFlag(ctx.args, "--to"));
|
|
505
540
|
if (to.length === 0) {
|
|
@@ -540,6 +575,176 @@ async function runSend(ctx: EmailCliContext) {
|
|
|
540
575
|
);
|
|
541
576
|
}
|
|
542
577
|
|
|
578
|
+
/** Persist before HTTP. Identical invocations recover the same request even
|
|
579
|
+
* after the CLI process dies; an explicit new --request-id starts another email. */
|
|
580
|
+
async function createNativeEmail(ctx: EmailCliContext, content: JsonRecord) {
|
|
581
|
+
let requestId = getFlag(ctx.args, "--request-id");
|
|
582
|
+
assertEmailArgs(ctx);
|
|
583
|
+
if (!requestId) {
|
|
584
|
+
// The credential keys the receipt fingerprint; it is never serialized into
|
|
585
|
+
// receipt data. Different sessions must not reuse one another's request IDs.
|
|
586
|
+
const fingerprint = ctx.sessionToken
|
|
587
|
+
? createHmac("sha256", ctx.sessionToken)
|
|
588
|
+
: createHash("sha256");
|
|
589
|
+
const key = fingerprint
|
|
590
|
+
.update(JSON.stringify([ctx.convex.url, content]))
|
|
591
|
+
.digest("hex");
|
|
592
|
+
const directory = join(denchConfigDirectory(), "email-requests");
|
|
593
|
+
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
594
|
+
const path = join(directory, `${key}.json`);
|
|
595
|
+
const proposed = randomUUID();
|
|
596
|
+
try {
|
|
597
|
+
await writeFile(path, JSON.stringify({ requestId: proposed }), {
|
|
598
|
+
flag: "wx",
|
|
599
|
+
mode: 0o600,
|
|
600
|
+
});
|
|
601
|
+
} catch (error) {
|
|
602
|
+
if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;
|
|
603
|
+
}
|
|
604
|
+
const saved = JSON.parse(await readFile(path, "utf8")) as {
|
|
605
|
+
requestId: string;
|
|
606
|
+
};
|
|
607
|
+
if (typeof saved.requestId !== "string" || !saved.requestId)
|
|
608
|
+
throw new EmailCliError("Invalid local email request receipt");
|
|
609
|
+
requestId = saved.requestId;
|
|
610
|
+
}
|
|
611
|
+
return await callMutation(ctx, "functions/emailDrafts:create", {
|
|
612
|
+
...content,
|
|
613
|
+
requestId,
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function assertEmailArgs(ctx: EmailCliContext) {
|
|
618
|
+
assertNoUnknownFlags(ctx.args, "dench email");
|
|
619
|
+
if (ctx.args.length)
|
|
620
|
+
throw new EmailCliError(`Unexpected argument: ${ctx.args[0]}`);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function draftIdArg(ctx: EmailCliContext) {
|
|
624
|
+
const positional =
|
|
625
|
+
ctx.args[0] && !ctx.args[0].startsWith("--") ? ctx.args.shift() : undefined;
|
|
626
|
+
return positional ?? requiredFlag(ctx.args, "--draft");
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function revisionArg(ctx: EmailCliContext) {
|
|
630
|
+
const revision = Number(requiredFlag(ctx.args, "--revision"));
|
|
631
|
+
if (!Number.isSafeInteger(revision) || revision < 1)
|
|
632
|
+
throw new EmailCliError(
|
|
633
|
+
"--revision must be a positive integer from draft show",
|
|
634
|
+
);
|
|
635
|
+
return revision;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
async function draftContent(ctx: EmailCliContext) {
|
|
639
|
+
const to = getFlag(ctx.args, "--to");
|
|
640
|
+
const cc = getFlag(ctx.args, "--cc");
|
|
641
|
+
const bcc = getFlag(ctx.args, "--bcc");
|
|
642
|
+
const subject = getFlag(ctx.args, "--subject");
|
|
643
|
+
const htmlBody = await readTextFlag(ctx.args, "--html", "--html-file");
|
|
644
|
+
const textBody = await readTextFlag(ctx.args, "--text", "--text-file");
|
|
645
|
+
return {
|
|
646
|
+
...(to !== undefined ? { to: parseAddressList(to) } : {}),
|
|
647
|
+
...(cc !== undefined ? { cc: parseAddressList(cc) } : {}),
|
|
648
|
+
...(bcc !== undefined ? { bcc: parseAddressList(bcc) } : {}),
|
|
649
|
+
...(subject !== undefined ? { subject } : {}),
|
|
650
|
+
...(htmlBody !== undefined ? { htmlBody } : {}),
|
|
651
|
+
...(textBody !== undefined ? { textBody } : {}),
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
async function runDraft(ctx: EmailCliContext) {
|
|
656
|
+
const sub = ctx.args.shift();
|
|
657
|
+
let result: unknown;
|
|
658
|
+
if (sub === "create") {
|
|
659
|
+
result = await createNativeEmail(ctx, {
|
|
660
|
+
...(await draftContent(ctx)),
|
|
661
|
+
mailbox: getFlag(ctx.args, "--mailbox"),
|
|
662
|
+
threadId: getFlag(ctx.args, "--thread") ?? process.env.DENCH_THREAD_ID,
|
|
663
|
+
});
|
|
664
|
+
} else if (sub === "show" || sub === "signature") {
|
|
665
|
+
const draftId = draftIdArg(ctx);
|
|
666
|
+
assertEmailArgs(ctx);
|
|
667
|
+
result =
|
|
668
|
+
sub === "show"
|
|
669
|
+
? await callQuery(ctx, "functions/emailDrafts:get", { draftId })
|
|
670
|
+
: await callAction(ctx, "functions/emailDraftsNode:loadSignature", {
|
|
671
|
+
draftId,
|
|
672
|
+
});
|
|
673
|
+
} else if (sub === "update" || sub === "send") {
|
|
674
|
+
const draftId = draftIdArg(ctx);
|
|
675
|
+
const expectedRevision = revisionArg(ctx);
|
|
676
|
+
const args =
|
|
677
|
+
sub === "update"
|
|
678
|
+
? {
|
|
679
|
+
draftId,
|
|
680
|
+
expectedRevision,
|
|
681
|
+
patch: {
|
|
682
|
+
...(await draftContent(ctx)),
|
|
683
|
+
mailboxAccountId: getFlag(ctx.args, "--mailbox"),
|
|
684
|
+
},
|
|
685
|
+
}
|
|
686
|
+
: {
|
|
687
|
+
draftId,
|
|
688
|
+
expectedRevision,
|
|
689
|
+
requestId: getFlag(ctx.args, "--request-id"),
|
|
690
|
+
};
|
|
691
|
+
assertEmailArgs(ctx);
|
|
692
|
+
result = await callMutation(ctx, `functions/emailDrafts:${sub}`, args);
|
|
693
|
+
} else if (sub === "attach") {
|
|
694
|
+
const draftId = draftIdArg(ctx);
|
|
695
|
+
const expectedRevision = revisionArg(ctx);
|
|
696
|
+
const file = requiredFlag(ctx.args, "--file");
|
|
697
|
+
const contentType =
|
|
698
|
+
getFlag(ctx.args, "--content-type") ??
|
|
699
|
+
(
|
|
700
|
+
{
|
|
701
|
+
".txt": "text/plain",
|
|
702
|
+
".csv": "text/csv",
|
|
703
|
+
".pdf": "application/pdf",
|
|
704
|
+
".png": "image/png",
|
|
705
|
+
".jpg": "image/jpeg",
|
|
706
|
+
".jpeg": "image/jpeg",
|
|
707
|
+
".json": "application/json",
|
|
708
|
+
".html": "text/html",
|
|
709
|
+
} as Record<string, string>
|
|
710
|
+
)[extname(file).toLowerCase()] ??
|
|
711
|
+
"application/octet-stream";
|
|
712
|
+
assertEmailArgs(ctx);
|
|
713
|
+
const bytes = await readFile(file);
|
|
714
|
+
if (bytes.length > 5 * 1024 * 1024)
|
|
715
|
+
throw new EmailCliError("Attachments can be up to 5 MB each");
|
|
716
|
+
const draft = (await callQuery(ctx, "functions/emailDrafts:get", {
|
|
717
|
+
draftId,
|
|
718
|
+
})) as { revision: number; attachments: unknown[]; canEdit: boolean };
|
|
719
|
+
if (draft.revision !== expectedRevision)
|
|
720
|
+
throw new EmailCliError("Draft changed. Refresh before attaching a file");
|
|
721
|
+
if (!draft.canEdit) throw new EmailCliError("This draft is read-only");
|
|
722
|
+
const attachment = await callAction(
|
|
723
|
+
ctx,
|
|
724
|
+
"functions/emailDraftsNode:uploadAttachment",
|
|
725
|
+
{
|
|
726
|
+
draftId,
|
|
727
|
+
name: basename(file),
|
|
728
|
+
contentType,
|
|
729
|
+
contentBase64: bytes.toString("base64"),
|
|
730
|
+
},
|
|
731
|
+
);
|
|
732
|
+
result = await callMutation(ctx, "functions/emailDrafts:update", {
|
|
733
|
+
draftId,
|
|
734
|
+
expectedRevision,
|
|
735
|
+
patch: { attachments: [...draft.attachments, attachment] },
|
|
736
|
+
});
|
|
737
|
+
} else if (sub === "senders") {
|
|
738
|
+
assertEmailArgs(ctx);
|
|
739
|
+
result = await callQuery(ctx, "functions/emailDrafts:listSenders", {});
|
|
740
|
+
} else {
|
|
741
|
+
throw new EmailCliError(
|
|
742
|
+
"Usage: dench email draft create|show|update|send|senders|attach|signature. update/send/attach require --revision from draft show. attach: --file <path> [--content-type <mime>]. create: --to --subject --text/--html [--mailbox <id/email>] [--request-id <stable-id>] [--thread <chat-id>].",
|
|
743
|
+
);
|
|
744
|
+
}
|
|
745
|
+
out(ctx, result, JSON.stringify(result, null, 2));
|
|
746
|
+
}
|
|
747
|
+
|
|
543
748
|
async function runMessage(ctx: EmailCliContext) {
|
|
544
749
|
const sub = ctx.args.shift();
|
|
545
750
|
if (sub === "status") {
|
package/lib/api-schemas.ts
CHANGED
|
@@ -31,6 +31,42 @@ import {
|
|
|
31
31
|
MIN_WITHDRAW_AFTER_HOURS,
|
|
32
32
|
} from "./campaign-steps";
|
|
33
33
|
|
|
34
|
+
const nativeEmailRecipient = z.object({
|
|
35
|
+
email: z.string(),
|
|
36
|
+
name: z.string().optional(),
|
|
37
|
+
});
|
|
38
|
+
const nativeEmailAttachment = z.object({
|
|
39
|
+
storageId: z.string(),
|
|
40
|
+
name: z.string(),
|
|
41
|
+
contentType: z.string(),
|
|
42
|
+
size: z.number(),
|
|
43
|
+
});
|
|
44
|
+
const nativeEmailContent = {
|
|
45
|
+
to: z.array(nativeEmailRecipient).optional(),
|
|
46
|
+
cc: z.array(nativeEmailRecipient).optional(),
|
|
47
|
+
bcc: z.array(nativeEmailRecipient).optional(),
|
|
48
|
+
subject: z.string().optional(),
|
|
49
|
+
htmlBody: z.string().optional(),
|
|
50
|
+
textBody: z.string().optional(),
|
|
51
|
+
attachments: z.array(nativeEmailAttachment).optional(),
|
|
52
|
+
};
|
|
53
|
+
const nativeEmailCreate = z.object({
|
|
54
|
+
organizationId: z.string().optional(),
|
|
55
|
+
threadId: z.string().optional(),
|
|
56
|
+
mailbox: z.string().optional(),
|
|
57
|
+
requestId: z.string(),
|
|
58
|
+
...nativeEmailContent,
|
|
59
|
+
});
|
|
60
|
+
const nativeEmailReceipt = z
|
|
61
|
+
.object({
|
|
62
|
+
ok: z.boolean(),
|
|
63
|
+
kind: z.literal("email"),
|
|
64
|
+
draftId: z.string(),
|
|
65
|
+
syncStatus: z.string(),
|
|
66
|
+
revision: z.number(),
|
|
67
|
+
})
|
|
68
|
+
.passthrough();
|
|
69
|
+
|
|
34
70
|
// ---------------------------------------------------------------------------
|
|
35
71
|
// Shared enums + fragments (mirror convex validators)
|
|
36
72
|
// ---------------------------------------------------------------------------
|
|
@@ -1786,7 +1822,11 @@ const toolSearchResponse = z
|
|
|
1786
1822
|
session: z
|
|
1787
1823
|
.object({ id: z.string() })
|
|
1788
1824
|
.passthrough()
|
|
1825
|
+
.optional()
|
|
1789
1826
|
.describe("Tool-router session; reuse session.id for follow-up calls."),
|
|
1827
|
+
items: z.array(z.record(z.string(), z.unknown())).optional(),
|
|
1828
|
+
nativeAvailable: z.boolean().optional(),
|
|
1829
|
+
available: z.boolean().optional(),
|
|
1790
1830
|
execution_ref_version: z.number().optional(),
|
|
1791
1831
|
})
|
|
1792
1832
|
.passthrough()
|
|
@@ -2022,13 +2062,27 @@ const imageEdit = imageGenerate.extend({
|
|
|
2022
2062
|
mask_base64: z.string().optional().describe("Optional base64 mask image."),
|
|
2023
2063
|
});
|
|
2024
2064
|
|
|
2025
|
-
const composioConnect = z
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2065
|
+
const composioConnect = z
|
|
2066
|
+
.object({
|
|
2067
|
+
toolkit: z
|
|
2068
|
+
.string()
|
|
2069
|
+
.min(1)
|
|
2070
|
+
.optional()
|
|
2071
|
+
.describe("Toolkit slug, e.g. github, gmail, stripe."),
|
|
2072
|
+
toolkit_slug: z.string().min(1).optional(),
|
|
2073
|
+
callback_url: z
|
|
2074
|
+
.string()
|
|
2075
|
+
.optional()
|
|
2076
|
+
.describe("Optional OAuth callback URL."),
|
|
2077
|
+
returnTo: z
|
|
2078
|
+
.string()
|
|
2079
|
+
.optional()
|
|
2080
|
+
.describe("Native OAuth return path within the workspace."),
|
|
2081
|
+
})
|
|
2082
|
+
.refine((value) => Boolean(value.toolkit || value.toolkit_slug), {
|
|
2083
|
+
message: "A toolkit is required.",
|
|
2084
|
+
path: ["toolkit"],
|
|
2085
|
+
});
|
|
2032
2086
|
|
|
2033
2087
|
const composioToolSearch = z.object({
|
|
2034
2088
|
queries: z
|
|
@@ -2039,7 +2093,13 @@ const composioToolSearch = z.object({
|
|
|
2039
2093
|
}),
|
|
2040
2094
|
)
|
|
2041
2095
|
.min(1)
|
|
2042
|
-
.max(7)
|
|
2096
|
+
.max(7)
|
|
2097
|
+
.optional(),
|
|
2098
|
+
query: z.string().min(1).optional(),
|
|
2099
|
+
toolkit: z.string().min(1).optional(),
|
|
2100
|
+
toolkit_slug: z.string().min(1).optional(),
|
|
2101
|
+
limit: z.number().int().min(1).max(100).optional(),
|
|
2102
|
+
important: z.boolean().optional(),
|
|
2043
2103
|
session: z
|
|
2044
2104
|
.object({ id: z.string().optional(), generate_id: z.boolean().optional() })
|
|
2045
2105
|
.optional(),
|
|
@@ -2064,6 +2124,8 @@ const composioToolRun = z.object({
|
|
|
2064
2124
|
.optional()
|
|
2065
2125
|
.describe("Tool arguments object."),
|
|
2066
2126
|
account: z.string().optional(),
|
|
2127
|
+
connected_account_id: z.string().optional(),
|
|
2128
|
+
approvalId: z.string().optional(),
|
|
2067
2129
|
});
|
|
2068
2130
|
|
|
2069
2131
|
/**
|
|
@@ -3315,8 +3377,47 @@ export const requestSchemas: Record<string, ZodTypeAny> = {
|
|
|
3315
3377
|
}),
|
|
3316
3378
|
|
|
3317
3379
|
// Workspace
|
|
3380
|
+
"workspace.context.sections": z.object({
|
|
3381
|
+
organizationId: z.string().optional(),
|
|
3382
|
+
sections: z
|
|
3383
|
+
.array(
|
|
3384
|
+
z.enum([
|
|
3385
|
+
"identity",
|
|
3386
|
+
"organisation",
|
|
3387
|
+
"user",
|
|
3388
|
+
"memory",
|
|
3389
|
+
"rules",
|
|
3390
|
+
"tools",
|
|
3391
|
+
]),
|
|
3392
|
+
)
|
|
3393
|
+
.optional(),
|
|
3394
|
+
}),
|
|
3395
|
+
"email.drafts.create": nativeEmailCreate,
|
|
3396
|
+
"email.native.send": nativeEmailCreate,
|
|
3397
|
+
"email.drafts.get": z.object({ draftId: z.string() }),
|
|
3398
|
+
"email.drafts.senders": z.object({ organizationId: z.string().optional() }),
|
|
3399
|
+
"email.drafts.update": z.object({
|
|
3400
|
+
draftId: z.string(),
|
|
3401
|
+
expectedRevision: z.number().int().nonnegative(),
|
|
3402
|
+
patch: z.object({
|
|
3403
|
+
mailboxAccountId: z.string().optional(),
|
|
3404
|
+
...nativeEmailContent,
|
|
3405
|
+
}),
|
|
3406
|
+
}),
|
|
3407
|
+
"email.drafts.send": z.object({
|
|
3408
|
+
draftId: z.string(),
|
|
3409
|
+
expectedRevision: z.number().int().nonnegative(),
|
|
3410
|
+
requestId: z.string().optional(),
|
|
3411
|
+
}),
|
|
3412
|
+
"email.drafts.upload": z.object({
|
|
3413
|
+
draftId: z.string(),
|
|
3414
|
+
name: z.string(),
|
|
3415
|
+
contentType: z.string(),
|
|
3416
|
+
contentBase64: z.string(),
|
|
3417
|
+
}),
|
|
3418
|
+
"email.drafts.signature": z.object({ draftId: z.string() }),
|
|
3318
3419
|
"workspace.context": noInput,
|
|
3319
|
-
"workspace.instructions":
|
|
3420
|
+
"workspace.instructions": z.object({ organizationId: z.string().optional() }),
|
|
3320
3421
|
"workspace.status": noInput,
|
|
3321
3422
|
"workspace.agents": noInput,
|
|
3322
3423
|
"workspace.approvals.list": noInput,
|
|
@@ -5055,6 +5156,15 @@ export const responseSchemas: Record<string, ZodTypeAny> = {
|
|
|
5055
5156
|
}),
|
|
5056
5157
|
|
|
5057
5158
|
// Workspace orientation
|
|
5159
|
+
"workspace.context.sections": z.object({}).passthrough(),
|
|
5160
|
+
"email.drafts.create": nativeEmailReceipt,
|
|
5161
|
+
"email.drafts.get": nativeEmailReceipt,
|
|
5162
|
+
"email.drafts.update": nativeEmailReceipt,
|
|
5163
|
+
"email.drafts.send": nativeEmailReceipt,
|
|
5164
|
+
"email.native.send": nativeEmailReceipt,
|
|
5165
|
+
"email.drafts.senders": z.array(z.object({}).passthrough()),
|
|
5166
|
+
"email.drafts.upload": nativeEmailAttachment,
|
|
5167
|
+
"email.drafts.signature": z.object({}).passthrough(),
|
|
5058
5168
|
"workspace.context": workspaceStatusPayload,
|
|
5059
5169
|
"workspace.instructions": workspaceInstructionsResponse,
|
|
5060
5170
|
"workspace.status": workspaceStatusPayload,
|
|
@@ -5865,6 +5975,14 @@ export const responseSchemas: Record<string, ZodTypeAny> = {
|
|
|
5865
5975
|
.optional()
|
|
5866
5976
|
.describe("OAuth URL the user opens to finish connecting."),
|
|
5867
5977
|
connection_id: z.string().optional(),
|
|
5978
|
+
redirectUrl: z.string().optional(),
|
|
5979
|
+
link: z.string().optional(),
|
|
5980
|
+
artifact: z
|
|
5981
|
+
.object({
|
|
5982
|
+
kind: z.literal("connect"),
|
|
5983
|
+
data: z.object({ toolkit: z.string() }),
|
|
5984
|
+
})
|
|
5985
|
+
.optional(),
|
|
5868
5986
|
})
|
|
5869
5987
|
.passthrough()
|
|
5870
5988
|
.describe("Connect-link response."),
|