proton-mail-bridge-client 2.1.42 → 2.1.43
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 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +44 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
|
|
27
27
|
Give Claude Desktop (or Cline, or any MCP client) full access to your Proton Mail inbox: read, search, send, draft, triage threads, manage folders, save attachments, and more — 96 MCP tools in total. Most of the same capabilities are also available as a full CLI for scripting, cron, and piped automation — no Claude required.
|
|
28
28
|
|
|
29
|
+
> **Using Proton Drive too?** See [**proton-drive-mcp**](https://github.com/googlarz/proton-drive-mcp), the companion MCP server and CLI for Proton Drive (upload, download, share and manage your encrypted files from Claude). Run both side by side to handle mail and files in one conversation, for example save an attachment from here, then upload it to Drive. More in [Related projects](#related-projects).
|
|
30
|
+
|
|
29
31
|
> **Battle-tested at scale (v2.0.0):** full mailbox backfill validated end-to-end against a real account with 57,000+ indexed messages across 62 folders/labels — including a 22,800-message Archive folder backfilled from scratch, UID-window by UID-window, with zero data loss across restarts, transient IMAP disconnects, and connection timeouts.
|
|
30
32
|
|
|
31
33
|
## What you get
|
package/dist/index.d.ts
CHANGED
|
@@ -22,6 +22,8 @@ export declare function extractUnsubscribeInfo(detail: EmailDetail): {
|
|
|
22
22
|
mailto?: string;
|
|
23
23
|
url?: string;
|
|
24
24
|
};
|
|
25
|
+
export declare function formatQuoteDate(value: string): string;
|
|
26
|
+
export declare function stripUnshippableImages(html: string): string;
|
|
25
27
|
export declare function buildReplyHtml(detail: EmailDetail, htmlBody: string): string;
|
|
26
28
|
export declare function buildForwardHtml(detail: EmailDetail, htmlBody?: string): string;
|
|
27
29
|
export declare function getReplyRecipients(detail: EmailDetail, ownerEmail: string, replyAll: boolean, otherSelfAddresses?: string[]): {
|
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,iBAAiB,EAAuD,MAAM,mCAAmC,CAAC;AAC3H,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,KAAK,EAMV,mBAAmB,EACnB,cAAc,EACd,WAAW,EAIX,WAAW,EAIX,gBAAgB,EAChB,cAAc,EAGf,MAAM,kBAAkB,CAAC;AAqB1B,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
|
@@ -1434,6 +1434,7 @@ const TOOLS = [
|
|
|
1434
1434
|
type: "object",
|
|
1435
1435
|
properties: {
|
|
1436
1436
|
limit: { type: "number", description: "Maximum threads per digest section.", default: 10 },
|
|
1437
|
+
offset: { type: "number", description: "Skip this many threads in each section (use paging.<section>.nextOffset from the previous response to continue).", default: 0 },
|
|
1437
1438
|
minAgeHours: {
|
|
1438
1439
|
type: "number",
|
|
1439
1440
|
description: "How old a thread must be before it is considered stale waiting on you.",
|
|
@@ -2164,10 +2165,34 @@ function quotePlainText(value) {
|
|
|
2164
2165
|
.map((line) => `> ${line}`)
|
|
2165
2166
|
.join("\n");
|
|
2166
2167
|
}
|
|
2168
|
+
// "2026-09-30T08:11:15.000Z" is not how anyone writes "On <date>, X wrote:". Falls back to the
|
|
2169
|
+
// raw value when it is not a parseable date (an "unknown date" placeholder, a header string).
|
|
2170
|
+
export function formatQuoteDate(value) {
|
|
2171
|
+
const time = Date.parse(value);
|
|
2172
|
+
if (Number.isNaN(time))
|
|
2173
|
+
return value;
|
|
2174
|
+
return new Date(time).toLocaleString("en-GB", {
|
|
2175
|
+
weekday: "short", day: "numeric", month: "short", year: "numeric", hour: "2-digit", minute: "2-digit",
|
|
2176
|
+
}).replace(",", "");
|
|
2177
|
+
}
|
|
2178
|
+
// The quoted original's images cannot travel with a reply: a data: image is dropped by Gmail/Outlook
|
|
2179
|
+
// (a broken-image box) and copies its base64 into every reply, and a cid: image refers to a part
|
|
2180
|
+
// that is not attached here. Replace each with its alt text, or nothing.
|
|
2181
|
+
export function stripUnshippableImages(html) {
|
|
2182
|
+
return html.replace(/<img\b[^>]*>/gi, (tag) => {
|
|
2183
|
+
const src = /\bsrc\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/i.exec(tag);
|
|
2184
|
+
const value = (src?.[1] ?? src?.[2] ?? src?.[3] ?? "").trim().toLowerCase();
|
|
2185
|
+
if (!value.startsWith("data:") && !value.startsWith("cid:"))
|
|
2186
|
+
return tag;
|
|
2187
|
+
const alt = /\balt\s*=\s*(?:"([^"]*)"|'([^']*)')/i.exec(tag);
|
|
2188
|
+
const text = (alt?.[1] ?? alt?.[2] ?? "").trim();
|
|
2189
|
+
return text ? `[${text}]` : "";
|
|
2190
|
+
});
|
|
2191
|
+
}
|
|
2167
2192
|
function buildReplyText(detail, body) {
|
|
2168
2193
|
const originalText = detail.text || detail.preview || "";
|
|
2169
2194
|
const fromText = formatAddressList(detail.from);
|
|
2170
|
-
const dateText = detail.date || detail.internalDate || "an unknown date";
|
|
2195
|
+
const dateText = formatQuoteDate(detail.date || detail.internalDate || "an unknown date");
|
|
2171
2196
|
return [
|
|
2172
2197
|
body.trim(),
|
|
2173
2198
|
"",
|
|
@@ -2205,10 +2230,10 @@ function buildForwardText(detail, body) {
|
|
|
2205
2230
|
// alternative (see smtp-service.ts's plainTextToHtml comment).
|
|
2206
2231
|
export function buildReplyHtml(detail, htmlBody) {
|
|
2207
2232
|
const originalHtml = typeof detail.html === "string" && detail.html
|
|
2208
|
-
? detail.html
|
|
2233
|
+
? stripUnshippableImages(detail.html)
|
|
2209
2234
|
: plainTextToHtml(detail.text || detail.preview || "");
|
|
2210
2235
|
const fromText = formatAddressList(detail.from);
|
|
2211
|
-
const dateText = detail.date || detail.internalDate || "an unknown date";
|
|
2236
|
+
const dateText = formatQuoteDate(detail.date || detail.internalDate || "an unknown date");
|
|
2212
2237
|
return [
|
|
2213
2238
|
htmlBody,
|
|
2214
2239
|
`<p>On ${escapeHtmlForQuote(dateText)}, ${escapeHtmlForQuote(fromText || "the sender")} wrote:</p>`,
|
|
@@ -2217,7 +2242,7 @@ export function buildReplyHtml(detail, htmlBody) {
|
|
|
2217
2242
|
}
|
|
2218
2243
|
export function buildForwardHtml(detail, htmlBody) {
|
|
2219
2244
|
const originalHtml = typeof detail.html === "string" && detail.html
|
|
2220
|
-
? detail.html
|
|
2245
|
+
? stripUnshippableImages(detail.html)
|
|
2221
2246
|
: plainTextToHtml(detail.text || detail.preview || "");
|
|
2222
2247
|
return [
|
|
2223
2248
|
htmlBody?.trim() || "",
|
|
@@ -6854,6 +6879,10 @@ export function createServer(config, options = {}) {
|
|
|
6854
6879
|
case "get_inbox_digest":
|
|
6855
6880
|
{
|
|
6856
6881
|
const limit = typeof args.limit === "number" ? args.limit : 10;
|
|
6882
|
+
const offset = typeof args.offset === "number" ? Math.max(0, Math.floor(args.offset)) : 0;
|
|
6883
|
+
// Each account is asked for offset+limit+1 rows per section: enough to slice the requested
|
|
6884
|
+
// page from the merged list and to know whether another page exists.
|
|
6885
|
+
const perAccountWindow = offset + limit + 1;
|
|
6857
6886
|
// Fan-out merge: get_inbox_digest already internally aggregates two lists
|
|
6858
6887
|
// (topThreads by score/recency, staleAwaitingYou by score/recency) plus a
|
|
6859
6888
|
// `counts` summary — mirror that same internal merge style across accounts.
|
|
@@ -6870,7 +6899,7 @@ export function createServer(config, options = {}) {
|
|
|
6870
6899
|
limitPerFolder: 100,
|
|
6871
6900
|
});
|
|
6872
6901
|
const result = await bundle.localIndexService.getInboxDigest({
|
|
6873
|
-
limit,
|
|
6902
|
+
limit: perAccountWindow,
|
|
6874
6903
|
minAgeHours: typeof args.minAgeHours === "number" ? args.minAgeHours : undefined,
|
|
6875
6904
|
});
|
|
6876
6905
|
const slug = slugForBundle(bundle);
|
|
@@ -6886,8 +6915,10 @@ export function createServer(config, options = {}) {
|
|
|
6886
6915
|
return right.score - left.score;
|
|
6887
6916
|
return new Date(right.latestDate || 0).getTime() - new Date(left.latestDate || 0).getTime();
|
|
6888
6917
|
};
|
|
6889
|
-
const
|
|
6890
|
-
const
|
|
6918
|
+
const mergedTop = perAccount.flatMap((entry) => entry.topThreads).sort(byScoreThenDate);
|
|
6919
|
+
const mergedStale = perAccount.flatMap((entry) => entry.staleAwaitingYou).sort(byScoreThenDate);
|
|
6920
|
+
const topBudget = trimThreadsToBudget(mergedTop.slice(offset, offset + limit), THREAD_LIST_RESPONSE_BUDGET_CHARS / 2);
|
|
6921
|
+
const staleBudget = trimThreadsToBudget(mergedStale.slice(offset, offset + limit), THREAD_LIST_RESPONSE_BUDGET_CHARS / 2);
|
|
6891
6922
|
const topThreads = topBudget.threads;
|
|
6892
6923
|
const staleAwaitingYou = staleBudget.threads;
|
|
6893
6924
|
const countKeys = new Set(perAccount.flatMap((entry) => Object.keys(entry.counts)));
|
|
@@ -6901,6 +6932,12 @@ export function createServer(config, options = {}) {
|
|
|
6901
6932
|
counts,
|
|
6902
6933
|
topThreads,
|
|
6903
6934
|
staleAwaitingYou,
|
|
6935
|
+
// Each section pages independently with the same offset/limit: pass nextOffset back as
|
|
6936
|
+
// `offset` to continue (a section with hasMore:false is exhausted).
|
|
6937
|
+
paging: {
|
|
6938
|
+
topThreads: paginationFields(mergedTop.length, offset, topThreads.length, topBudget.trimmed),
|
|
6939
|
+
staleAwaitingYou: paginationFields(mergedStale.length, offset, staleAwaitingYou.length, staleBudget.trimmed),
|
|
6940
|
+
},
|
|
6904
6941
|
...(topBudget.trimmed > 0 || staleBudget.trimmed > 0 ? { truncatedForSize: true } : {}),
|
|
6905
6942
|
};
|
|
6906
6943
|
return createTextResult(result, false, [...topThreads, ...staleAwaitingYou].map(threadSource));
|