proton-mail-bridge-client 2.12.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 +17 -0
- package/dist/cli.d.ts +10 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +119 -7
- 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 +64 -30
- 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 +14 -1
- package/dist/services/local-index-service.js.map +1 -1
- package/dist/services/simple-imap-service.d.ts +9 -1
- package/dist/services/simple-imap-service.d.ts.map +1 -1
- package/dist/services/simple-imap-service.js +58 -16
- 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 +12 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/health.d.ts +11 -0
- package/dist/utils/health.d.ts.map +1 -0
- package/dist/utils/health.js +41 -0
- package/dist/utils/health.js.map +1 -0
- package/dist/utils/helpers.d.ts +1 -0
- package/dist/utils/helpers.d.ts.map +1 -1
- package/dist/utils/helpers.js +30 -0
- package/dist/utils/helpers.js.map +1 -1
- package/dist/utils/runtime-policy.d.ts.map +1 -1
- package/dist/utils/runtime-policy.js +3 -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/dist/utils/update-check.d.ts +10 -0
- package/dist/utils/update-check.d.ts.map +1 -0
- package/dist/utils/update-check.js +37 -0
- package/dist/utils/update-check.js.map +1 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -288,6 +288,20 @@ Reload the Cline extension after saving. Proton Mail tools will appear in Cline'
|
|
|
288
288
|
|
|
289
289
|
---
|
|
290
290
|
|
|
291
|
+
## Running in Docker (headless machines)
|
|
292
|
+
|
|
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
|
+
|
|
295
|
+
```bash
|
|
296
|
+
docker build -f docker/Dockerfile --build-arg VERSION=2.13.1 -t proton-mail-bridge-client .
|
|
297
|
+
docker run -i --rm --network host \
|
|
298
|
+
-e PROTONMAIL_USERNAME=you@proton.me -e PROTONMAIL_PASSWORD_FILE=/run/secrets/bridge \
|
|
299
|
+
-v /path/to/bridge-password:/run/secrets/bridge:ro -v bridge-client-data:/data \
|
|
300
|
+
proton-mail-bridge-client
|
|
301
|
+
```
|
|
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`. 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
|
+
|
|
291
305
|
## Multi-account support (v2.1.0+)
|
|
292
306
|
|
|
293
307
|
Use more than one Proton mailbox from the same server: either several addresses of one account (Bridge's **Split Addresses** feature) or separate Proton accounts signed in to the same Bridge. Each one gets its own isolated service stack, local index, and drafts storage.
|
|
@@ -524,6 +538,9 @@ PROTONMAIL_IDLE_WATCH='true'
|
|
|
524
538
|
PROTONMAIL_IDLE_MAX_SECONDS='30'
|
|
525
539
|
|
|
526
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)
|
|
543
|
+
PROTONMAIL_SHORTEN_LINKS='false' # true = long tracking links in Markdown bodies become host/… (unsubscribe, confirm, reset links are kept)
|
|
527
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
|
|
528
545
|
PROTONMAIL_PDF_MAX_MB='10' # largest PDF get_attachment_text extracts text from (1-100 MB); bigger ones are refused with a message
|
|
529
546
|
PROTONMAIL_PDF_MAX_PAGES='50' # pages get_attachment_text reads from a PDF (1-2000); a longer PDF is cut off with a note saying how many pages are missing
|
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;
|
|
@@ -28,6 +29,15 @@ export declare function diagnoseConnectionFailure(reason: unknown): {
|
|
|
28
29
|
cause: string;
|
|
29
30
|
suggestion: string;
|
|
30
31
|
} | undefined;
|
|
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
|
+
};
|
|
31
41
|
interface ToolOnlyCommand {
|
|
32
42
|
command: string;
|
|
33
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";
|
|
@@ -11,6 +12,8 @@ import { isMainModule } from "./is-main.js";
|
|
|
11
12
|
import { isLikelyAuthenticationError, isLikelyConnectionError, isLikelyTlsMismatchError, SimpleIMAPService } from "./services/simple-imap-service.js";
|
|
12
13
|
import { ensureDestructiveConfirmed, ensureEmailActionAllowed, ensureMailboxWriteAllowed, sanitizeRuntimeConfig } from "./utils/runtime-policy.js";
|
|
13
14
|
import { isValidEmail, parseEmails } from "./utils/helpers.js";
|
|
15
|
+
import { checkForUpdate } from "./utils/update-check.js";
|
|
16
|
+
import { healthProblems } from "./utils/health.js";
|
|
14
17
|
import { buildSchedule, defaultSchedulerSystem, findStableAlias, parseTimeOfDay, SCHEDULER_SYSTEMS } from "./utils/schedule-templates.js";
|
|
15
18
|
import { getClaudeDesktopInstallStatus } from "./scripts/check-claude-desktop.js";
|
|
16
19
|
import { installClaudeDesktopConfig } from "./scripts/install-claude-desktop.js";
|
|
@@ -231,12 +234,13 @@ export const COMMAND_SPECS = {
|
|
|
231
234
|
completion: { usage: "completion <zsh|bash|fish>", description: "Print a shell completion script for the commands and their flags", flags: [] },
|
|
232
235
|
schedule: { usage: "schedule [--at HH:MM] [--system launchd|systemd|cron|schtasks] [--output <file>] [--password-file <file>]", description: "Print the scheduler definition that runs `digest` every day, with the commands to switch it on (installs nothing)", flags: ["at", "system", "output", "password-file"] },
|
|
233
236
|
help: { usage: "help [command]", description: "Show the command list, or the help for one command", flags: [] },
|
|
234
|
-
version: { usage: "version", description: "Print the version and exit", flags: [] },
|
|
237
|
+
version: { usage: "version [--check]", description: "Print the version and exit; --check also asks the npm registry whether a newer release exists", flags: ["check"] },
|
|
235
238
|
"setup-claude-desktop": { usage: "setup-claude-desktop", description: "Run the Claude Desktop setup wizard (works from any install)", flags: [] },
|
|
236
239
|
claude: { usage: "claude <setup|install|check|update|doctor>", description: "Claude Desktop integration: setup wizard, install or update the runtime, check status (update is an alias for install, doctor for check)", flags: [] },
|
|
237
240
|
status: { usage: "status", description: "Show local config, index, runtime, and Claude Desktop status", flags: [] },
|
|
238
|
-
doctor: { usage: "doctor [--account <slug|address>]", description: "Verify IMAP, SMTP, and Claude Desktop wiring for every account, or one (exits 1 when the check fails)", flags: ["account"] },
|
|
241
|
+
doctor: { usage: "doctor [--account <slug|address>] [--check-updates]", description: "Verify IMAP, SMTP, and Claude Desktop wiring for every account, or one (exits 1 when the check fails); --check-updates also asks the npm registry for a newer release", flags: ["account", "check-updates"] },
|
|
239
242
|
"connection-status": { usage: "connection-status", description: "Show live IMAP/SMTP connectivity state (exits 1 when either is unreachable)", flags: [] },
|
|
243
|
+
report: { usage: "report", description: "Print a diagnostic summary to paste into a bug report: versions, system, connection modes, flags and per-account state; no passwords, no mail content, addresses masked", flags: [] },
|
|
240
244
|
"runtime-status": { usage: "runtime-status [--account <slug|address>]", description: "Show runtime policy and background sync state, per account", flags: ["account"] },
|
|
241
245
|
sync: { usage: "sync [--folder <name>] [--limit <n>] [--full]", description: "Refresh the local index from Proton Bridge", flags: ["folder", "limit", "full", "no-attachment-text"] },
|
|
242
246
|
"index-status": { usage: "index-status", description: "Show local index health and freshness", flags: [] },
|
|
@@ -281,7 +285,7 @@ export const COMMAND_SPECS = {
|
|
|
281
285
|
watch: { usage: "watch [--folder <name>] [--timeout <s>]", description: "Wait for mailbox changes via IMAP IDLE", flags: ["folder", "timeout"] },
|
|
282
286
|
"clear-cache": { usage: "clear-cache", description: "Clear in-memory MCP server caches", flags: [] },
|
|
283
287
|
"get-logs": { usage: "get-logs [--limit <n>] [--offset <n>] [--level <level>]", description: "Return recent in-memory MCP server logs", flags: ["limit", "level", "offset"] },
|
|
284
|
-
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"] },
|
|
285
289
|
drafts: { usage: "drafts [--sent]", description: "List local drafts", flags: ["sent"] },
|
|
286
290
|
"remote-drafts": { usage: "remote-drafts [--limit <n>] [--offset <n>]", description: "List drafts in the Proton Drafts mailbox", flags: ["limit", "offset"] },
|
|
287
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] },
|
|
@@ -817,6 +821,7 @@ async function runDoctor(parsed) {
|
|
|
817
821
|
// Top-level fields describe the first account shown; ok covers all of them.
|
|
818
822
|
ok: checked.every((entry) => entry.ok),
|
|
819
823
|
claudeDesktopInstalled: claudeDesktop.installed,
|
|
824
|
+
...(isTruthyFlag(parsed.flags["check-updates"]) ? { update: await checkForUpdate(await getPkgVersion()) } : {}),
|
|
820
825
|
...(checked.length > 1 ? { accounts: checked } : {}),
|
|
821
826
|
};
|
|
822
827
|
process.stdout.write(wantJson ? json(result) : `${result.ok ? "Doctor OK" : "Doctor failed"}\n${json(result)}`);
|
|
@@ -859,6 +864,71 @@ async function runConnectionStatus(parsed) {
|
|
|
859
864
|
process.exitCode = 1;
|
|
860
865
|
});
|
|
861
866
|
}
|
|
867
|
+
// a***@example.com: enough to tell accounts apart in a bug report, not enough to identify the person.
|
|
868
|
+
export function maskAddress(address) {
|
|
869
|
+
const at = address.lastIndexOf("@");
|
|
870
|
+
return at <= 0 ? "***" : `${address[0]}***${address.slice(at)}`;
|
|
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
|
+
}
|
|
883
|
+
async function runReport() {
|
|
884
|
+
await withServices(async ({ config, accountManager }) => {
|
|
885
|
+
const accounts = await Promise.all(accountManager.all().map(async (bundle) => {
|
|
886
|
+
let imap = "ok";
|
|
887
|
+
let folderCount;
|
|
888
|
+
try {
|
|
889
|
+
await bundle.imapService.ping();
|
|
890
|
+
folderCount = (await bundle.imapService.getFolders(true)).length;
|
|
891
|
+
}
|
|
892
|
+
catch (error) {
|
|
893
|
+
imap = diagnoseConnectionFailure(error)?.cause ?? "failed";
|
|
894
|
+
}
|
|
895
|
+
let smtp = "ok";
|
|
896
|
+
try {
|
|
897
|
+
await bundle.smtpService.verifyConnection();
|
|
898
|
+
}
|
|
899
|
+
catch (error) {
|
|
900
|
+
smtp = diagnoseConnectionFailure(error)?.cause ?? "failed";
|
|
901
|
+
}
|
|
902
|
+
const index = await bundle.localIndexService.getStatus();
|
|
903
|
+
return {
|
|
904
|
+
account: maskAddress(bundle.account.address),
|
|
905
|
+
imap: { host: bundle.config.imap.host, port: bundle.config.imap.port, secure: bundle.config.imap.secure, check: imap },
|
|
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(),
|
|
915
|
+
folderCount,
|
|
916
|
+
index: { storedMessageCount: index.storedMessageCount, updatedAt: index.updatedAt, isStale: index.isStale },
|
|
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 },
|
|
919
|
+
};
|
|
920
|
+
}));
|
|
921
|
+
const claudeDesktop = await getClaudeDesktopInstallStatus();
|
|
922
|
+
process.stdout.write(json({
|
|
923
|
+
version: await getPkgVersion(),
|
|
924
|
+
node: process.version,
|
|
925
|
+
platform: `${process.platform} ${process.arch} ${osRelease()}`,
|
|
926
|
+
claudeDesktopInstalled: claudeDesktop.installed,
|
|
927
|
+
runtime: reportRuntime(config.runtime),
|
|
928
|
+
accounts,
|
|
929
|
+
}));
|
|
930
|
+
});
|
|
931
|
+
}
|
|
862
932
|
async function runRuntimeStatus(parsed) {
|
|
863
933
|
const wantJson = isTruthyFlag(parsed.flags.json);
|
|
864
934
|
await withServices(async ({ accountManager }) => {
|
|
@@ -868,6 +938,7 @@ async function runRuntimeStatus(parsed) {
|
|
|
868
938
|
account: bundle.config.smtp.username,
|
|
869
939
|
runtime: sanitizeRuntimeConfig(bundle.config.runtime),
|
|
870
940
|
backgroundSync: bundle.backgroundSyncService.getStatus(),
|
|
941
|
+
health: healthProblems(bundle.backgroundSyncService.getStatus(), await bundle.localIndexService.getStatus()),
|
|
871
942
|
localIndex: await bundle.localIndexService.getStatus(),
|
|
872
943
|
drafts: {
|
|
873
944
|
total: (await bundle.draftStore.listDrafts(true)).length,
|
|
@@ -1599,6 +1670,21 @@ async function sendSystemNotification(title, body) {
|
|
|
1599
1670
|
// Silent — stdout already carries the event
|
|
1600
1671
|
}
|
|
1601
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
|
+
}
|
|
1602
1688
|
async function runNotify(parsed) {
|
|
1603
1689
|
const folder = getStringFlag(parsed.flags, "folder") || "INBOX";
|
|
1604
1690
|
const timeoutSeconds = getNumberFlag(parsed.flags, "timeout", 30);
|
|
@@ -1606,6 +1692,7 @@ async function runNotify(parsed) {
|
|
|
1606
1692
|
const imapService = new SimpleIMAPService(config);
|
|
1607
1693
|
let running = true;
|
|
1608
1694
|
let previousCount;
|
|
1695
|
+
let problemCause;
|
|
1609
1696
|
const shutdown = () => { running = false; };
|
|
1610
1697
|
process.on("SIGINT", shutdown);
|
|
1611
1698
|
process.on("SIGTERM", shutdown);
|
|
@@ -1618,6 +1705,12 @@ async function runNotify(parsed) {
|
|
|
1618
1705
|
});
|
|
1619
1706
|
if (!running)
|
|
1620
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;
|
|
1621
1714
|
if (result.changed) {
|
|
1622
1715
|
const existsEvent = result.events.find((e) => e["type"] === "exists");
|
|
1623
1716
|
if (existsEvent) {
|
|
@@ -1644,8 +1737,14 @@ async function runNotify(parsed) {
|
|
|
1644
1737
|
break;
|
|
1645
1738
|
const msg = error instanceof Error ? error.message : String(error);
|
|
1646
1739
|
process.stderr.write(`[notify] Watch error: ${msg}\n`);
|
|
1647
|
-
|
|
1648
|
-
|
|
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(); });
|
|
1649
1748
|
}
|
|
1650
1749
|
}
|
|
1651
1750
|
try {
|
|
@@ -2023,9 +2122,19 @@ export async function main() {
|
|
|
2023
2122
|
process.stdout.write(scheduleText(parsed));
|
|
2024
2123
|
return;
|
|
2025
2124
|
case "-v":
|
|
2026
|
-
case "version":
|
|
2027
|
-
|
|
2125
|
+
case "version": {
|
|
2126
|
+
const current = await getPkgVersion();
|
|
2127
|
+
process.stdout.write(`proton-mail-bridge-client ${current}\n`);
|
|
2128
|
+
if (isTruthyFlag(parsed.flags.check)) {
|
|
2129
|
+
const update = await checkForUpdate(current);
|
|
2130
|
+
process.stdout.write(update.error
|
|
2131
|
+
? `Could not check for updates: ${update.error}\n`
|
|
2132
|
+
: update.newer
|
|
2133
|
+
? `A newer release is available: ${update.latest} (${update.releaseUrl}). Update with: npm install -g proton-mail-bridge-client\n`
|
|
2134
|
+
: "You are on the latest release.\n");
|
|
2135
|
+
}
|
|
2028
2136
|
return;
|
|
2137
|
+
}
|
|
2029
2138
|
case "setup-claude-desktop":
|
|
2030
2139
|
await runClaudeDesktopSetupWizard();
|
|
2031
2140
|
return;
|
|
@@ -2038,6 +2147,9 @@ export async function main() {
|
|
|
2038
2147
|
case "connection-status":
|
|
2039
2148
|
await runConnectionStatus(parsed);
|
|
2040
2149
|
return;
|
|
2150
|
+
case "report":
|
|
2151
|
+
await runReport();
|
|
2152
|
+
return;
|
|
2041
2153
|
case "runtime-status":
|
|
2042
2154
|
await runRuntimeStatus(parsed);
|
|
2043
2155
|
return;
|