proton-mail-bridge-client 2.13.0 → 2.13.1
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 +5 -3
- package/dist/cli.d.ts +9 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +56 -7
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +35 -22
- package/dist/index.js.map +1 -1
- package/dist/services/local-index-service.d.ts.map +1 -1
- package/dist/services/local-index-service.js +2 -1
- package/dist/services/local-index-service.js.map +1 -1
- package/dist/services/simple-imap-service.d.ts +8 -1
- package/dist/services/simple-imap-service.d.ts.map +1 -1
- package/dist/services/simple-imap-service.js +52 -14
- package/dist/services/simple-imap-service.js.map +1 -1
- package/dist/services/smtp-service.d.ts +2 -0
- package/dist/services/smtp-service.d.ts.map +1 -1
- package/dist/services/smtp-service.js +6 -3
- package/dist/services/smtp-service.js.map +1 -1
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/health.d.ts.map +1 -1
- package/dist/utils/health.js +7 -4
- package/dist/utils/health.js.map +1 -1
- package/dist/utils/helpers.d.ts.map +1 -1
- package/dist/utils/helpers.js +18 -5
- package/dist/utils/helpers.js.map +1 -1
- package/dist/utils/runtime-policy.d.ts.map +1 -1
- package/dist/utils/runtime-policy.js +2 -0
- package/dist/utils/runtime-policy.js.map +1 -1
- package/dist/utils/tls.d.ts +8 -0
- package/dist/utils/tls.d.ts.map +1 -0
- package/dist/utils/tls.js +28 -0
- package/dist/utils/tls.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -290,17 +290,17 @@ Reload the Cline extension after saving. Proton Mail tools will appear in Cline'
|
|
|
290
290
|
|
|
291
291
|
## Running in Docker (headless machines)
|
|
292
292
|
|
|
293
|
-
For a server with no desktop, next to a headless Bridge. The image installs the published npm package
|
|
293
|
+
For a server with no desktop, next to a headless Bridge. The image installs the published npm package and is published for each release to `ghcr.io/googlarz/proton-mail-bridge-client` (`:<version>` and `:latest`, linux/amd64 and linux/arm64). To build it yourself from this repo:
|
|
294
294
|
|
|
295
295
|
```bash
|
|
296
|
-
docker build -f docker/Dockerfile --build-arg VERSION=2.13.
|
|
296
|
+
docker build -f docker/Dockerfile --build-arg VERSION=2.13.1 -t proton-mail-bridge-client .
|
|
297
297
|
docker run -i --rm --network host \
|
|
298
298
|
-e PROTONMAIL_USERNAME=you@proton.me -e PROTONMAIL_PASSWORD_FILE=/run/secrets/bridge \
|
|
299
299
|
-v /path/to/bridge-password:/run/secrets/bridge:ro -v bridge-client-data:/data \
|
|
300
300
|
proton-mail-bridge-client
|
|
301
301
|
```
|
|
302
302
|
|
|
303
|
-
`docker run -i` speaks MCP over stdio. The container must reach Bridge's IMAP and SMTP ports (`--network host`, or `--network container:<bridge>` to share Bridge's network). The CLI is in the same image: add `--entrypoint proton-mail-bridge-client` and a command such as `doctor`. The server has no network transport of its own; reaching it from a remote client needs your own wrapper. The image was checked to build, report its version, run as a non-root user and answer an MCP `initialize`; it has not been run against a real Bridge.
|
|
303
|
+
`docker run -i` speaks MCP over stdio. The container must reach Bridge's IMAP and SMTP ports (`--network host`, or `--network container:<bridge>` to share Bridge's network). The CLI is in the same image: add `--entrypoint proton-mail-bridge-client` and a command such as `doctor`. If Bridge is reached by a host name (for example `host.docker.internal` or another container), its self-signed certificate fails verification: export it from Bridge and point `PROTONMAIL_TLS_CA_FILE` at it, or list the name in `PROTONMAIL_TLS_INSECURE_HOSTS`. The server has no network transport of its own; reaching it from a remote client needs your own wrapper. The image was checked to build, report its version, run as a non-root user and answer an MCP `initialize`; it has not been run against a real Bridge.
|
|
304
304
|
|
|
305
305
|
## Multi-account support (v2.1.0+)
|
|
306
306
|
|
|
@@ -538,6 +538,8 @@ PROTONMAIL_IDLE_WATCH='true'
|
|
|
538
538
|
PROTONMAIL_IDLE_MAX_SECONDS='30'
|
|
539
539
|
|
|
540
540
|
# Attachments
|
|
541
|
+
PROTONMAIL_TLS_CA_FILE='' # PEM file with Bridge's exported certificate: trusted when Bridge is reached by a name other than localhost (another container, another machine)
|
|
542
|
+
PROTONMAIL_TLS_INSECURE_HOSTS='' # comma-separated host names whose certificate is not verified, like localhost is (only for a private network you trust)
|
|
541
543
|
PROTONMAIL_SHORTEN_LINKS='false' # true = long tracking links in Markdown bodies become host/… (unsubscribe, confirm, reset links are kept)
|
|
542
544
|
PROTONMAIL_MAX_INLINE_KB='40' # in KB (older name PROTONMAIL_MAX_INLINE_BYTES still works, also in KB): largest attachment get_attachment_content returns inline as base64
|
|
543
545
|
PROTONMAIL_PDF_MAX_MB='10' # largest PDF get_attachment_text extracts text from (1-100 MB); bigger ones are refused with a message
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import type { ProtonMailConfig } from "./types/index.js";
|
|
2
3
|
type CliFlags = Record<string, string | boolean>;
|
|
3
4
|
export interface ParsedCliArgs {
|
|
4
5
|
command: string;
|
|
@@ -29,6 +30,14 @@ export declare function diagnoseConnectionFailure(reason: unknown): {
|
|
|
29
30
|
suggestion: string;
|
|
30
31
|
} | undefined;
|
|
31
32
|
export declare function maskAddress(address: string): string;
|
|
33
|
+
export declare function reportRuntime(runtime: ProtonMailConfig["runtime"], home?: string): Record<string, unknown>;
|
|
34
|
+
export declare function watchHealthNotice(previous: string | undefined, error: unknown): {
|
|
35
|
+
cause: string | undefined;
|
|
36
|
+
notice?: {
|
|
37
|
+
title: string;
|
|
38
|
+
body: string;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
32
41
|
interface ToolOnlyCommand {
|
|
33
42
|
command: string;
|
|
34
43
|
tool: string;
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAaA,OAAO,KAAK,EAAgB,gBAAgB,EAAqB,MAAM,kBAAkB,CAAC;AAoB1F,KAAK,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC;AAEjD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,EAAE,QAAQ,CAAC;CACjB;AAiBD,qBAAa,aAAc,SAAQ,KAAK;CAAG;AAM3C,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,aAAa,CAuD1D;AA0ID,UAAU,WAAW;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAOD,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAqErD,CAAC;AAoBF,eAAO,MAAM,iBAAiB,YAAI,KAAK,EAAE,MAAM,EAAE,MAAM,CAAU,CAAC;AAGlE,wBAAgB,kBAAkB,IAAI,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAQlG;AAID,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CA+DtD;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAcnE;AAyKD,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAY5F;AAiND,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,OAAO,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAiB5G;AA+FD,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAGnD;AAGD,wBAAgB,aAAa,CAAC,OAAO,EAAE,gBAAgB,CAAC,SAAS,CAAC,EAAE,IAAI,SAAY,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAS7G;AA01BD,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,KAAK,EAAE,OAAO,GACb;IAAE,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAYzE;AAySD,UAAU,eAAe;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IAIb,SAAS,CAAC,EAAE,MAAM,CAAC;IAMnB,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,eAAO,MAAM,kBAAkB,EAAE,eAAe,EAmE/C,CAAC;AA2EF,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAoP1C;AA4ED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAGzD"}
|
package/dist/cli.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { execFile } from "node:child_process";
|
|
3
3
|
import { accessSync, constants as fsConstants, realpathSync } from "node:fs";
|
|
4
4
|
import { readFile } from "node:fs/promises";
|
|
5
|
+
import { homedir, release as osRelease } from "node:os";
|
|
5
6
|
import { promisify } from "node:util";
|
|
6
7
|
import { fileURLToPath } from "node:url";
|
|
7
8
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
@@ -284,7 +285,7 @@ export const COMMAND_SPECS = {
|
|
|
284
285
|
watch: { usage: "watch [--folder <name>] [--timeout <s>]", description: "Wait for mailbox changes via IMAP IDLE", flags: ["folder", "timeout"] },
|
|
285
286
|
"clear-cache": { usage: "clear-cache", description: "Clear in-memory MCP server caches", flags: [] },
|
|
286
287
|
"get-logs": { usage: "get-logs [--limit <n>] [--offset <n>] [--level <level>]", description: "Return recent in-memory MCP server logs", flags: ["limit", "level", "offset"] },
|
|
287
|
-
notify: { usage: "notify [--folder <name>] [--timeout <s>]", description: "Daemon: watch a folder and send a system notification on new mail", flags: ["folder", "timeout"] },
|
|
288
|
+
notify: { usage: "notify [--folder <name>] [--timeout <s>]", description: "Daemon: watch a folder and send a system notification on new mail, and when it cannot reach or log in to Bridge (once per problem, and when it recovers)", flags: ["folder", "timeout"] },
|
|
288
289
|
drafts: { usage: "drafts [--sent]", description: "List local drafts", flags: ["sent"] },
|
|
289
290
|
"remote-drafts": { usage: "remote-drafts [--limit <n>] [--offset <n>]", description: "List drafts in the Proton Drafts mailbox", flags: ["limit", "offset"] },
|
|
290
291
|
"draft-create": { usage: "draft-create --subject <text> [--to <addr>] (--body <text> | stdin) [--cc <a>] [--bcc <a>]", description: "Create a draft", flags: ["to", "cc", "bcc", "subject", ...BODY_FLAGS] },
|
|
@@ -868,6 +869,17 @@ export function maskAddress(address) {
|
|
|
868
869
|
const at = address.lastIndexOf("@");
|
|
869
870
|
return at <= 0 ? "***" : `${address[0]}***${address.slice(at)}`;
|
|
870
871
|
}
|
|
872
|
+
// The runtime settings for a bug report: folder lists become a count, and the home directory is written as ~.
|
|
873
|
+
export function reportRuntime(runtime, home = homedir()) {
|
|
874
|
+
const sanitized = sanitizeRuntimeConfig(runtime);
|
|
875
|
+
const folders = typeof sanitized.autoSyncFolder === "string" ? sanitized.autoSyncFolder.split(",").filter((entry) => entry.trim()).length : undefined;
|
|
876
|
+
const download = typeof sanitized.allowFileDownloadDir === "string" ? sanitized.allowFileDownloadDir : undefined;
|
|
877
|
+
return {
|
|
878
|
+
...sanitized,
|
|
879
|
+
autoSyncFolder: folders === undefined ? undefined : `${folders} folder${folders === 1 ? "" : "s"}`,
|
|
880
|
+
allowFileDownloadDir: download ? (home && download.startsWith(home) ? `~${download.slice(home.length)}` : download) : sanitized.allowFileDownloadDir,
|
|
881
|
+
};
|
|
882
|
+
}
|
|
871
883
|
async function runReport() {
|
|
872
884
|
await withServices(async ({ config, accountManager }) => {
|
|
873
885
|
const accounts = await Promise.all(accountManager.all().map(async (bundle) => {
|
|
@@ -891,19 +903,28 @@ async function runReport() {
|
|
|
891
903
|
return {
|
|
892
904
|
account: maskAddress(bundle.account.address),
|
|
893
905
|
imap: { host: bundle.config.imap.host, port: bundle.config.imap.port, secure: bundle.config.imap.secure, check: imap },
|
|
894
|
-
smtp: {
|
|
906
|
+
smtp: {
|
|
907
|
+
host: bundle.config.smtp.host,
|
|
908
|
+
port: bundle.config.smtp.port,
|
|
909
|
+
configuredSecure: bundle.config.smtp.secure,
|
|
910
|
+
secureInUse: bundle.smtpService.effectiveSecure,
|
|
911
|
+
userSet: bundle.config.smtp.secureExplicit === true,
|
|
912
|
+
check: smtp,
|
|
913
|
+
},
|
|
914
|
+
bridge: bundle.imapService.getServerInfo(),
|
|
895
915
|
folderCount,
|
|
896
916
|
index: { storedMessageCount: index.storedMessageCount, updatedAt: index.updatedAt, isStale: index.isStale },
|
|
897
|
-
|
|
917
|
+
// Folder names can be as private as an address: only how many are synced.
|
|
918
|
+
backgroundSync: { syncedFolderCount: bundle.imapService.getSyncFolderList().length, full: bundle.backgroundSyncService.getStatus().full },
|
|
898
919
|
};
|
|
899
920
|
}));
|
|
900
921
|
const claudeDesktop = await getClaudeDesktopInstallStatus();
|
|
901
922
|
process.stdout.write(json({
|
|
902
923
|
version: await getPkgVersion(),
|
|
903
924
|
node: process.version,
|
|
904
|
-
platform: `${process.platform} ${process.arch}`,
|
|
925
|
+
platform: `${process.platform} ${process.arch} ${osRelease()}`,
|
|
905
926
|
claudeDesktopInstalled: claudeDesktop.installed,
|
|
906
|
-
runtime:
|
|
927
|
+
runtime: reportRuntime(config.runtime),
|
|
907
928
|
accounts,
|
|
908
929
|
}));
|
|
909
930
|
});
|
|
@@ -1649,6 +1670,21 @@ async function sendSystemNotification(title, body) {
|
|
|
1649
1670
|
// Silent — stdout already carries the event
|
|
1650
1671
|
}
|
|
1651
1672
|
}
|
|
1673
|
+
// What `notify` should tell the user when its own watch starts or stops working. A problem is announced once per
|
|
1674
|
+
// cause (not every retry), and recovery once; `cause` is what the caller passes back in as `previous`.
|
|
1675
|
+
export function watchHealthNotice(previous, error) {
|
|
1676
|
+
if (error === null) {
|
|
1677
|
+
return previous
|
|
1678
|
+
? { cause: undefined, notice: { title: "Proton Mail Bridge Client", body: "The connection to Bridge works again." } }
|
|
1679
|
+
: { cause: undefined };
|
|
1680
|
+
}
|
|
1681
|
+
const cause = diagnoseConnectionFailure(error)?.cause ?? "failed";
|
|
1682
|
+
if (cause === previous)
|
|
1683
|
+
return { cause };
|
|
1684
|
+
const body = diagnoseConnectionFailure(error)?.suggestion ??
|
|
1685
|
+
`The mailbox watch stopped working: ${error instanceof Error ? error.message : String(error)}`;
|
|
1686
|
+
return { cause, notice: { title: "Proton Mail Bridge Client needs attention", body } };
|
|
1687
|
+
}
|
|
1652
1688
|
async function runNotify(parsed) {
|
|
1653
1689
|
const folder = getStringFlag(parsed.flags, "folder") || "INBOX";
|
|
1654
1690
|
const timeoutSeconds = getNumberFlag(parsed.flags, "timeout", 30);
|
|
@@ -1656,6 +1692,7 @@ async function runNotify(parsed) {
|
|
|
1656
1692
|
const imapService = new SimpleIMAPService(config);
|
|
1657
1693
|
let running = true;
|
|
1658
1694
|
let previousCount;
|
|
1695
|
+
let problemCause;
|
|
1659
1696
|
const shutdown = () => { running = false; };
|
|
1660
1697
|
process.on("SIGINT", shutdown);
|
|
1661
1698
|
process.on("SIGTERM", shutdown);
|
|
@@ -1668,6 +1705,12 @@ async function runNotify(parsed) {
|
|
|
1668
1705
|
});
|
|
1669
1706
|
if (!running)
|
|
1670
1707
|
break;
|
|
1708
|
+
const recovered = watchHealthNotice(problemCause, null);
|
|
1709
|
+
if (recovered.notice) {
|
|
1710
|
+
await sendSystemNotification(recovered.notice.title, recovered.notice.body);
|
|
1711
|
+
process.stdout.write(JSON.stringify({ event: "recovered", at: result.checkedAt }) + "\n");
|
|
1712
|
+
}
|
|
1713
|
+
problemCause = recovered.cause;
|
|
1671
1714
|
if (result.changed) {
|
|
1672
1715
|
const existsEvent = result.events.find((e) => e["type"] === "exists");
|
|
1673
1716
|
if (existsEvent) {
|
|
@@ -1694,8 +1737,14 @@ async function runNotify(parsed) {
|
|
|
1694
1737
|
break;
|
|
1695
1738
|
const msg = error instanceof Error ? error.message : String(error);
|
|
1696
1739
|
process.stderr.write(`[notify] Watch error: ${msg}\n`);
|
|
1697
|
-
|
|
1698
|
-
|
|
1740
|
+
const health = watchHealthNotice(problemCause, error);
|
|
1741
|
+
problemCause = health.cause;
|
|
1742
|
+
if (health.notice) {
|
|
1743
|
+
await sendSystemNotification(health.notice.title, health.notice.body);
|
|
1744
|
+
process.stdout.write(JSON.stringify({ event: "problem", cause: health.cause, message: health.notice.body }) + "\n");
|
|
1745
|
+
}
|
|
1746
|
+
// Simple backoff before retrying; a rejected login will not fix itself in seconds.
|
|
1747
|
+
await new Promise((resolve) => { setTimeout(resolve, health.cause === "authentication_failed" ? 120_000 : 15_000).unref(); });
|
|
1699
1748
|
}
|
|
1700
1749
|
}
|
|
1701
1750
|
try {
|