proton-mail-bridge-client 2.1.19 → 2.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +44 -110
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +112 -50
- package/dist/index.js.map +1 -1
- package/dist/services/account-manager.d.ts.map +1 -1
- package/dist/services/account-manager.js +5 -0
- package/dist/services/account-manager.js.map +1 -1
- package/dist/services/delivery-queue-service.d.ts +4 -1
- package/dist/services/delivery-queue-service.d.ts.map +1 -1
- package/dist/services/delivery-queue-service.js +53 -16
- package/dist/services/delivery-queue-service.js.map +1 -1
- package/dist/services/draft-store-service.d.ts +2 -0
- package/dist/services/draft-store-service.d.ts.map +1 -1
- package/dist/services/draft-store-service.js +24 -0
- package/dist/services/draft-store-service.js.map +1 -1
- package/dist/services/simple-imap-service.d.ts +3 -0
- package/dist/services/simple-imap-service.d.ts.map +1 -1
- package/dist/services/simple-imap-service.js +62 -14
- package/dist/services/simple-imap-service.js.map +1 -1
- package/dist/services/snooze-service.d.ts.map +1 -1
- package/dist/services/snooze-service.js +23 -5
- package/dist/services/snooze-service.js.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AASA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAYnE,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AAC5E,OAAO,EAAE,iBAAiB,EAAwB,MAAM,mCAAmC,CAAC;AAC5F,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,EAA2H,iBAAiB,EAA+B,MAAM,mCAAmC,CAAC;AAC5N,OAAO,EAAmC,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAC1F,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAE7D,OAAO,KAAK,EAMV,mBAAmB,EACnB,cAAc,EACd,WAAW,EAIX,WAAW,EAIX,gBAAgB,EAChB,cAAc,EAGf,MAAM,kBAAkB,CAAC;AAoB1B,OAAO,EAAE,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -154,6 +154,7 @@ const TOOLS = [
|
|
|
154
154
|
type: "object",
|
|
155
155
|
properties: {
|
|
156
156
|
emailId: { type: "string", description: "Original email id." },
|
|
157
|
+
undoWindowSeconds: { type: "number", description: "Override PROTONMAIL_SEND_DELAY_SECONDS for this one send: queue it for this many seconds (cancelable via cancel_send) instead of the server's configured default. 0 sends immediately even if the server has a default window configured. Same caveat as the server default: only fires while this server process stays running." },
|
|
157
158
|
body: { type: "string", description: "Reply body to prepend (plain text). Required unless markdownBody is provided." },
|
|
158
159
|
markdownBody: { type: "string", description: "Reply body in Markdown. Rendered to HTML with body as plain-text fallback; takes precedence over body+isHtml." },
|
|
159
160
|
replyAll: { type: "boolean", description: "Reply to all original recipients.", default: false },
|
|
@@ -194,6 +195,7 @@ const TOOLS = [
|
|
|
194
195
|
type: "object",
|
|
195
196
|
properties: {
|
|
196
197
|
emailId: { type: "string", description: "Original email id." },
|
|
198
|
+
undoWindowSeconds: { type: "number", description: "Override PROTONMAIL_SEND_DELAY_SECONDS for this one send: queue it for this many seconds (cancelable via cancel_send) instead of the server's configured default. 0 sends immediately even if the server has a default window configured. Same caveat as the server default: only fires while this server process stays running." },
|
|
197
199
|
body: { type: "string", description: "Reply body to prepend (plain text). Required unless markdownBody is provided." },
|
|
198
200
|
markdownBody: { type: "string", description: "Reply body in Markdown. Rendered to HTML with body as plain-text fallback; takes precedence over body+isHtml." },
|
|
199
201
|
isHtml: { type: "boolean", description: "Send body as HTML (ignored when markdownBody is provided).", default: false },
|
|
@@ -233,6 +235,7 @@ const TOOLS = [
|
|
|
233
235
|
type: "object",
|
|
234
236
|
properties: {
|
|
235
237
|
emailId: { type: "string", description: "Original email id." },
|
|
238
|
+
undoWindowSeconds: { type: "number", description: "Override PROTONMAIL_SEND_DELAY_SECONDS for this one send: queue it for this many seconds (cancelable via cancel_send) instead of the server's configured default. 0 sends immediately even if the server has a default window configured. Same caveat as the server default: only fires while this server process stays running." },
|
|
236
239
|
to: { type: "string", description: "Forward recipient list, comma-separated." },
|
|
237
240
|
body: { type: "string", description: "Optional message before the forwarded content (plain text)." },
|
|
238
241
|
markdownBody: { type: "string", description: "Optional introductory note in Markdown. Rendered to HTML with body as plain-text fallback; takes precedence over body+isHtml." },
|
|
@@ -2530,7 +2533,61 @@ function parseResourceUri(uri) {
|
|
|
2530
2533
|
}
|
|
2531
2534
|
throw new Error(`Unsupported resource URI: ${uri}`);
|
|
2532
2535
|
}
|
|
2536
|
+
// Writes attachment bytes to <downloadDir>/<saveTo>, refusing any path that escapes the
|
|
2537
|
+
// download dir (lexically or through a symlink). The containment check canonicalizes
|
|
2538
|
+
// BOTH sides — comparing a canonical target with a merely-resolved allowed dir rejected
|
|
2539
|
+
// every save when the allowed dir is or sits under a symlink (macOS /var/folders is
|
|
2540
|
+
// really /private/var/folders). Attachments are private mail content, so directories
|
|
2541
|
+
// created here are owner-only (0700) and the file is 0600, existing files included.
|
|
2542
|
+
export async function writeAttachmentToDownloadDir(downloadDir, saveTo, data) {
|
|
2543
|
+
const { resolve: pathResolve, join: pathJoin, dirname, basename, sep } = await import("node:path");
|
|
2544
|
+
const { realpathSync } = await import("node:fs");
|
|
2545
|
+
const { writeFile: wf, mkdir: mkd, chmod: chm } = await import("node:fs/promises");
|
|
2546
|
+
const absDir = pathResolve(downloadDir);
|
|
2547
|
+
const absTarget = pathJoin(absDir, saveTo);
|
|
2548
|
+
// A hardcoded "/" never matched on win32 (path.resolve/join produce
|
|
2549
|
+
// backslash-separated paths there), hence `sep`.
|
|
2550
|
+
if (!absTarget.startsWith(absDir + sep) && absTarget !== absDir) {
|
|
2551
|
+
throw new McpError(ErrorCode.InvalidParams, "saveTo path escapes the allowed directory.");
|
|
2552
|
+
}
|
|
2553
|
+
await mkd(pathResolve(absTarget, ".."), { recursive: true, mode: 0o700 });
|
|
2554
|
+
const realDir = realpathSync(absDir);
|
|
2555
|
+
let realTarget;
|
|
2556
|
+
try {
|
|
2557
|
+
realTarget = realpathSync(absTarget);
|
|
2558
|
+
}
|
|
2559
|
+
catch (error) {
|
|
2560
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
2561
|
+
realTarget = realpathSync(dirname(absTarget)) + sep + basename(absTarget);
|
|
2562
|
+
}
|
|
2563
|
+
else {
|
|
2564
|
+
throw error;
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
if (!realTarget.startsWith(realDir + sep) && realTarget !== realDir) {
|
|
2568
|
+
throw new McpError(ErrorCode.InvalidParams, "saveTo path escapes the allowed directory.");
|
|
2569
|
+
}
|
|
2570
|
+
await wf(absTarget, data, { mode: 0o600 });
|
|
2571
|
+
// writeFile's mode only applies to a newly created file; tighten an existing one too.
|
|
2572
|
+
await chm(absTarget, 0o600);
|
|
2573
|
+
return absTarget;
|
|
2574
|
+
}
|
|
2575
|
+
// Syncs run one at a time per draft, and re-read the draft once they hold the lock: a
|
|
2576
|
+
// sync that queued behind another sees the remoteDraft the first one recorded (so it
|
|
2577
|
+
// updates that copy instead of APPENDing a second) and uploads the latest content.
|
|
2533
2578
|
async function syncDraftToRemote(draftStore, smtpService, imapService, draft) {
|
|
2579
|
+
try {
|
|
2580
|
+
return await draftStore.withDraftSyncLock(draft.id, async () => syncDraftToRemoteLocked(draftStore, smtpService, imapService, await draftStore.getDraft(draft.id).catch(() => draft)));
|
|
2581
|
+
}
|
|
2582
|
+
catch (error) {
|
|
2583
|
+
// Only reachable when the sync lock itself could not be taken — the upload's own
|
|
2584
|
+
// failures are handled (and recorded) inside syncDraftToRemoteLocked.
|
|
2585
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2586
|
+
logger.warn("Draft remote sync could not start", "MCPServer", { draftId: draft.id, error });
|
|
2587
|
+
return { draft: await draftStore.markRemoteSyncError(draft.id, message), remoteSync: { ok: false, message } };
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
async function syncDraftToRemoteLocked(draftStore, smtpService, imapService, draft) {
|
|
2534
2591
|
try {
|
|
2535
2592
|
const raw = await smtpService.buildRawMessage({
|
|
2536
2593
|
to: draft.to,
|
|
@@ -3387,6 +3444,33 @@ export function createServer(config, options = {}) {
|
|
|
3387
3444
|
function responseSlug(bundle) {
|
|
3388
3445
|
return bundle.account.slug === accountManager.primary().account.slug ? undefined : bundle.account.slug;
|
|
3389
3446
|
}
|
|
3447
|
+
// Undo-send window for one send: the server default (PROTONMAIL_SEND_DELAY_SECONDS),
|
|
3448
|
+
// overridable per call with undoWindowSeconds (0 = send immediately). Shared by
|
|
3449
|
+
// send_email, reply_to_email, reply_all_email and forward_email.
|
|
3450
|
+
function resolveUndoWindowSeconds(args) {
|
|
3451
|
+
if (args.undoWindowSeconds === undefined) {
|
|
3452
|
+
return config.runtime.sendDelaySeconds ?? 0;
|
|
3453
|
+
}
|
|
3454
|
+
const requested = Number(args.undoWindowSeconds);
|
|
3455
|
+
if (!Number.isInteger(requested) || requested < 0 || requested > 300) {
|
|
3456
|
+
throw new McpError(ErrorCode.InvalidParams, "undoWindowSeconds must be an integer between 0 and 300.");
|
|
3457
|
+
}
|
|
3458
|
+
return requested;
|
|
3459
|
+
}
|
|
3460
|
+
// Queues a send on the SENDING account's own delivery queue (so it fires through that
|
|
3461
|
+
// account's SMTP) and returns the tool result. The id carries that account's slug so
|
|
3462
|
+
// cancel_send/list_scheduled_sends can find it again.
|
|
3463
|
+
async function queueUndoSend(bundle, payload, undoWindowSeconds, toolName, toolArgs, extra = {}) {
|
|
3464
|
+
const sendAt = new Date(Date.now() + undoWindowSeconds * 1000).toISOString();
|
|
3465
|
+
const queued = await withAudit(auditService, toolName, toolArgs, async () => bundle.deliveryQueueService.enqueue(payload, sendAt, "undo_send"));
|
|
3466
|
+
return createTextResult({
|
|
3467
|
+
queued: true,
|
|
3468
|
+
id: withAccountPrefix(responseSlug(bundle), queued.id),
|
|
3469
|
+
sendAt: queued.sendAt,
|
|
3470
|
+
...extra,
|
|
3471
|
+
note: `Not sent yet — will send in ~${undoWindowSeconds}s unless canceled with cancel_send. This server must stay running for the send to fire; if it's restarted before sendAt, the send fires on next startup instead.`,
|
|
3472
|
+
});
|
|
3473
|
+
}
|
|
3390
3474
|
// Alias for responseSlug — tagAccountIds (added alongside the digest/follow-up
|
|
3391
3475
|
// multi-account fan-out) was written against this name; kept as a thin alias rather
|
|
3392
3476
|
// than rewriting every call site to the other name.
|
|
@@ -3652,14 +3736,7 @@ export function createServer(config, options = {}) {
|
|
|
3652
3736
|
// immediately, cancelable via cancel_send until the window elapses.
|
|
3653
3737
|
// undoWindowSeconds overrides the server default for this one send —
|
|
3654
3738
|
// including forcing 0 (send immediately) when a default is configured.
|
|
3655
|
-
|
|
3656
|
-
if (args.undoWindowSeconds !== undefined) {
|
|
3657
|
-
const requested = Number(args.undoWindowSeconds);
|
|
3658
|
-
if (!Number.isInteger(requested) || requested < 0 || requested > 300) {
|
|
3659
|
-
throw new McpError(ErrorCode.InvalidParams, "undoWindowSeconds must be an integer between 0 and 300.");
|
|
3660
|
-
}
|
|
3661
|
-
undoWindowSeconds = requested;
|
|
3662
|
-
}
|
|
3739
|
+
const undoWindowSeconds = resolveUndoWindowSeconds(args);
|
|
3663
3740
|
// A `from` matching a configured additional account routes through that
|
|
3664
3741
|
// account's own SMTP connection — required under Bridge's Split Addresses
|
|
3665
3742
|
// feature, where each address is its own separate IMAP/SMTP login, not just
|
|
@@ -3874,7 +3951,7 @@ export function createServer(config, options = {}) {
|
|
|
3874
3951
|
if (dryRunReply) {
|
|
3875
3952
|
return createTextResult({ dryRun: true, wouldSendTo: { to, cc, bcc: extraBcc }, subject: prefixedSubject(detail.subject, "Re:"), note: "No email was sent." }, false, [emailSource({ ...detail, id: prefixedIdFor(readBundleReply, detail.id) })]);
|
|
3876
3953
|
}
|
|
3877
|
-
const
|
|
3954
|
+
const replyPayload = {
|
|
3878
3955
|
to,
|
|
3879
3956
|
cc,
|
|
3880
3957
|
bcc: extraBcc,
|
|
@@ -3890,7 +3967,12 @@ export function createServer(config, options = {}) {
|
|
|
3890
3967
|
attachments,
|
|
3891
3968
|
// Already applied above, before quote-wrapping.
|
|
3892
3969
|
appendSignature: false,
|
|
3893
|
-
}
|
|
3970
|
+
};
|
|
3971
|
+
const undoWindowreply = resolveUndoWindowSeconds(args);
|
|
3972
|
+
if (undoWindowreply > 0) {
|
|
3973
|
+
return queueUndoSend(sendBundleForReply, replyPayload, undoWindowreply, name, args, { repliedTo: prefixedIdFor(readBundleReply, detail.id), to, cc });
|
|
3974
|
+
}
|
|
3975
|
+
const result = await withAudit(auditService, name, args, async () => sendBundleForReply.smtpService.sendEmail(replyPayload));
|
|
3894
3976
|
let sentCopyTokenReply = "[sent-copy:unverified]";
|
|
3895
3977
|
try {
|
|
3896
3978
|
const verifyMsgId = result.messageId;
|
|
@@ -3963,7 +4045,7 @@ export function createServer(config, options = {}) {
|
|
|
3963
4045
|
const replyHtmlBodyRa = includeQuoteRa && signedReplyRa.htmlBody !== undefined
|
|
3964
4046
|
? buildReplyHtml(detailRa, signedReplyRa.htmlBody)
|
|
3965
4047
|
: signedReplyRa.htmlBody;
|
|
3966
|
-
const
|
|
4048
|
+
const replyAllPayload = {
|
|
3967
4049
|
to: toRa,
|
|
3968
4050
|
cc: ccRa,
|
|
3969
4051
|
bcc: extraBccRa,
|
|
@@ -3979,7 +4061,12 @@ export function createServer(config, options = {}) {
|
|
|
3979
4061
|
attachments: attachmentsRa,
|
|
3980
4062
|
// Already applied above, before quote-wrapping.
|
|
3981
4063
|
appendSignature: false,
|
|
3982
|
-
}
|
|
4064
|
+
};
|
|
4065
|
+
const undoWindowreplyAll = resolveUndoWindowSeconds(args);
|
|
4066
|
+
if (undoWindowreplyAll > 0) {
|
|
4067
|
+
return queueUndoSend(sendBundleForRa, replyAllPayload, undoWindowreplyAll, name, args, { repliedTo: prefixedIdFor(readBundleRa, detailRa.id), to: toRa, cc: ccRa });
|
|
4068
|
+
}
|
|
4069
|
+
const resultRa = await withAudit(auditService, name, args, async () => sendBundleForRa.smtpService.sendEmail(replyAllPayload));
|
|
3983
4070
|
let sentCopyTokenRa = "[sent-copy:unverified]";
|
|
3984
4071
|
try {
|
|
3985
4072
|
const verifyMsgId = resultRa.messageId;
|
|
@@ -4076,7 +4163,7 @@ export function createServer(config, options = {}) {
|
|
|
4076
4163
|
const forwardHtmlBody = signedFwd.htmlBody !== undefined
|
|
4077
4164
|
? buildForwardHtml(detail, signedFwd.htmlBody)
|
|
4078
4165
|
: signedFwd.htmlBody;
|
|
4079
|
-
const
|
|
4166
|
+
const forwardPayload = {
|
|
4080
4167
|
to,
|
|
4081
4168
|
cc,
|
|
4082
4169
|
bcc,
|
|
@@ -4090,7 +4177,12 @@ export function createServer(config, options = {}) {
|
|
|
4090
4177
|
attachments: fwdAttachments,
|
|
4091
4178
|
// Already applied above, before quote-wrapping.
|
|
4092
4179
|
appendSignature: false,
|
|
4093
|
-
}
|
|
4180
|
+
};
|
|
4181
|
+
const undoWindowforward = resolveUndoWindowSeconds(args);
|
|
4182
|
+
if (undoWindowforward > 0) {
|
|
4183
|
+
return queueUndoSend(sendBundleForFwd, forwardPayload, undoWindowforward, name, args, { forwardedMessage: prefixedIdFor(readBundleFwd, detail.id), to, cc });
|
|
4184
|
+
}
|
|
4185
|
+
const result = await withAudit(auditService, name, args, async () => sendBundleForFwd.smtpService.sendEmail(forwardPayload));
|
|
4094
4186
|
let sentCopyTokenFwd = "[sent-copy:unverified]";
|
|
4095
4187
|
try {
|
|
4096
4188
|
const verifyMsgId = result.messageId;
|
|
@@ -4801,7 +4893,10 @@ export function createServer(config, options = {}) {
|
|
|
4801
4893
|
// Draft content is already finalized and reviewed — see the
|
|
4802
4894
|
// identical note on send_draft.
|
|
4803
4895
|
appendSignature: false,
|
|
4804
|
-
}, new Date(sendAtTime).toISOString(), "scheduled_send", draft.id
|
|
4896
|
+
}, new Date(sendAtTime).toISOString(), "scheduled_send", draft.id,
|
|
4897
|
+
// The draft stays in its own account's store even when `from` routes the
|
|
4898
|
+
// send through another account's queue/SMTP.
|
|
4899
|
+
scheduleDraftBundle.account.slug));
|
|
4805
4900
|
return createTextResult({
|
|
4806
4901
|
queued: true,
|
|
4807
4902
|
id: withAccountPrefix(responseSlug(sendBundleForScheduleDraft), queued.id),
|
|
@@ -6887,42 +6982,9 @@ export function createServer(config, options = {}) {
|
|
|
6887
6982
|
if (!downloadDir) {
|
|
6888
6983
|
throw new McpError(ErrorCode.InvalidParams, "PROTONMAIL_ALLOW_FILE_DOWNLOAD_DIR env var is not set.");
|
|
6889
6984
|
}
|
|
6890
|
-
const { resolve: pathResolve, join: pathJoin, dirname, basename, sep } = await import("node:path");
|
|
6891
|
-
const { realpathSync } = await import("node:fs");
|
|
6892
|
-
const { writeFile: wf, mkdir: mkd } = await import("node:fs/promises");
|
|
6893
|
-
const absDir = pathResolve(downloadDir);
|
|
6894
|
-
const absTarget = pathJoin(absDir, saveTo);
|
|
6895
|
-
// Hardcoded "/" here never matched on win32 (path.resolve/join
|
|
6896
|
-
// produce backslash-separated paths there), so every subdirectory
|
|
6897
|
-
// save failed with a false "escapes the allowed directory" —
|
|
6898
|
-
// fails safe/closed, not a security bypass, but breaks a normal
|
|
6899
|
-
// save on the Windows deployment this codebase explicitly
|
|
6900
|
-
// supports (see install-claude-desktop.ts's win32 branches).
|
|
6901
|
-
if (!absTarget.startsWith(absDir + sep) && absTarget !== absDir) {
|
|
6902
|
-
throw new McpError(ErrorCode.InvalidParams, "saveTo path escapes the allowed directory.");
|
|
6903
|
-
}
|
|
6904
|
-
await mkd(pathResolve(absTarget, ".."), { recursive: true });
|
|
6905
|
-
let realTarget;
|
|
6906
|
-
try {
|
|
6907
|
-
realTarget = realpathSync(absTarget);
|
|
6908
|
-
}
|
|
6909
|
-
catch (error) {
|
|
6910
|
-
if (error &&
|
|
6911
|
-
typeof error === "object" &&
|
|
6912
|
-
"code" in error &&
|
|
6913
|
-
error.code === "ENOENT") {
|
|
6914
|
-
realTarget = realpathSync(dirname(absTarget)) + sep + basename(absTarget);
|
|
6915
|
-
}
|
|
6916
|
-
else {
|
|
6917
|
-
throw error;
|
|
6918
|
-
}
|
|
6919
|
-
}
|
|
6920
|
-
if (!realTarget.startsWith(absDir + sep) && realTarget !== absDir) {
|
|
6921
|
-
throw new McpError(ErrorCode.InvalidParams, "saveTo path escapes the allowed directory.");
|
|
6922
|
-
}
|
|
6923
6985
|
const buf = Buffer.from(result.base64, "base64");
|
|
6924
|
-
await
|
|
6925
|
-
return createTextResult({ saved: true, path:
|
|
6986
|
+
const savedPath = await writeAttachmentToDownloadDir(downloadDir, saveTo, buf);
|
|
6987
|
+
return createTextResult({ saved: true, path: savedPath, bytes: buf.length, filename: result.attachment?.filename });
|
|
6926
6988
|
}
|
|
6927
6989
|
return createTextResult(result, false, [attachmentSource(result.emailId, result.attachment)]);
|
|
6928
6990
|
}
|