proton-mail-bridge-client 2.12.0 → 2.13.0

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 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; it is not published to a registry, so build it from this repo:
294
+
295
+ ```bash
296
+ docker build -f docker/Dockerfile --build-arg VERSION=2.13.0 -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`. 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,7 @@ PROTONMAIL_IDLE_WATCH='true'
524
538
  PROTONMAIL_IDLE_MAX_SECONDS='30'
525
539
 
526
540
  # Attachments
541
+ PROTONMAIL_SHORTEN_LINKS='false' # true = long tracking links in Markdown bodies become host/… (unsubscribe, confirm, reset links are kept)
527
542
  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
543
  PROTONMAIL_PDF_MAX_MB='10' # largest PDF get_attachment_text extracts text from (1-100 MB); bigger ones are refused with a message
529
544
  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
@@ -28,6 +28,7 @@ export declare function diagnoseConnectionFailure(reason: unknown): {
28
28
  cause: string;
29
29
  suggestion: string;
30
30
  } | undefined;
31
+ export declare function maskAddress(address: string): string;
31
32
  interface ToolOnlyCommand {
32
33
  command: string;
33
34
  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":";AA8BA,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,CAoErD,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;AAupCD,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,CAqO1C;AA4ED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAGzD"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAgCA,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;AAwmCD,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
@@ -11,6 +11,8 @@ import { isMainModule } from "./is-main.js";
11
11
  import { isLikelyAuthenticationError, isLikelyConnectionError, isLikelyTlsMismatchError, SimpleIMAPService } from "./services/simple-imap-service.js";
12
12
  import { ensureDestructiveConfirmed, ensureEmailActionAllowed, ensureMailboxWriteAllowed, sanitizeRuntimeConfig } from "./utils/runtime-policy.js";
13
13
  import { isValidEmail, parseEmails } from "./utils/helpers.js";
14
+ import { checkForUpdate } from "./utils/update-check.js";
15
+ import { healthProblems } from "./utils/health.js";
14
16
  import { buildSchedule, defaultSchedulerSystem, findStableAlias, parseTimeOfDay, SCHEDULER_SYSTEMS } from "./utils/schedule-templates.js";
15
17
  import { getClaudeDesktopInstallStatus } from "./scripts/check-claude-desktop.js";
16
18
  import { installClaudeDesktopConfig } from "./scripts/install-claude-desktop.js";
@@ -231,12 +233,13 @@ export const COMMAND_SPECS = {
231
233
  completion: { usage: "completion <zsh|bash|fish>", description: "Print a shell completion script for the commands and their flags", flags: [] },
232
234
  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
235
  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: [] },
236
+ version: { usage: "version [--check]", description: "Print the version and exit; --check also asks the npm registry whether a newer release exists", flags: ["check"] },
235
237
  "setup-claude-desktop": { usage: "setup-claude-desktop", description: "Run the Claude Desktop setup wizard (works from any install)", flags: [] },
236
238
  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
239
  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"] },
240
+ 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
241
  "connection-status": { usage: "connection-status", description: "Show live IMAP/SMTP connectivity state (exits 1 when either is unreachable)", flags: [] },
242
+ 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
243
  "runtime-status": { usage: "runtime-status [--account <slug|address>]", description: "Show runtime policy and background sync state, per account", flags: ["account"] },
241
244
  sync: { usage: "sync [--folder <name>] [--limit <n>] [--full]", description: "Refresh the local index from Proton Bridge", flags: ["folder", "limit", "full", "no-attachment-text"] },
242
245
  "index-status": { usage: "index-status", description: "Show local index health and freshness", flags: [] },
@@ -817,6 +820,7 @@ async function runDoctor(parsed) {
817
820
  // Top-level fields describe the first account shown; ok covers all of them.
818
821
  ok: checked.every((entry) => entry.ok),
819
822
  claudeDesktopInstalled: claudeDesktop.installed,
823
+ ...(isTruthyFlag(parsed.flags["check-updates"]) ? { update: await checkForUpdate(await getPkgVersion()) } : {}),
820
824
  ...(checked.length > 1 ? { accounts: checked } : {}),
821
825
  };
822
826
  process.stdout.write(wantJson ? json(result) : `${result.ok ? "Doctor OK" : "Doctor failed"}\n${json(result)}`);
@@ -859,6 +863,51 @@ async function runConnectionStatus(parsed) {
859
863
  process.exitCode = 1;
860
864
  });
861
865
  }
866
+ // a***@example.com: enough to tell accounts apart in a bug report, not enough to identify the person.
867
+ export function maskAddress(address) {
868
+ const at = address.lastIndexOf("@");
869
+ return at <= 0 ? "***" : `${address[0]}***${address.slice(at)}`;
870
+ }
871
+ async function runReport() {
872
+ await withServices(async ({ config, accountManager }) => {
873
+ const accounts = await Promise.all(accountManager.all().map(async (bundle) => {
874
+ let imap = "ok";
875
+ let folderCount;
876
+ try {
877
+ await bundle.imapService.ping();
878
+ folderCount = (await bundle.imapService.getFolders(true)).length;
879
+ }
880
+ catch (error) {
881
+ imap = diagnoseConnectionFailure(error)?.cause ?? "failed";
882
+ }
883
+ let smtp = "ok";
884
+ try {
885
+ await bundle.smtpService.verifyConnection();
886
+ }
887
+ catch (error) {
888
+ smtp = diagnoseConnectionFailure(error)?.cause ?? "failed";
889
+ }
890
+ const index = await bundle.localIndexService.getStatus();
891
+ return {
892
+ account: maskAddress(bundle.account.address),
893
+ imap: { host: bundle.config.imap.host, port: bundle.config.imap.port, secure: bundle.config.imap.secure, check: imap },
894
+ smtp: { host: bundle.config.smtp.host, port: bundle.config.smtp.port, secure: bundle.config.smtp.secure, userSet: bundle.config.smtp.secureExplicit === true, check: smtp },
895
+ folderCount,
896
+ index: { storedMessageCount: index.storedMessageCount, updatedAt: index.updatedAt, isStale: index.isStale },
897
+ backgroundSync: { folder: bundle.backgroundSyncService.getStatus().folder, full: bundle.backgroundSyncService.getStatus().full },
898
+ };
899
+ }));
900
+ const claudeDesktop = await getClaudeDesktopInstallStatus();
901
+ process.stdout.write(json({
902
+ version: await getPkgVersion(),
903
+ node: process.version,
904
+ platform: `${process.platform} ${process.arch}`,
905
+ claudeDesktopInstalled: claudeDesktop.installed,
906
+ runtime: sanitizeRuntimeConfig(config.runtime),
907
+ accounts,
908
+ }));
909
+ });
910
+ }
862
911
  async function runRuntimeStatus(parsed) {
863
912
  const wantJson = isTruthyFlag(parsed.flags.json);
864
913
  await withServices(async ({ accountManager }) => {
@@ -868,6 +917,7 @@ async function runRuntimeStatus(parsed) {
868
917
  account: bundle.config.smtp.username,
869
918
  runtime: sanitizeRuntimeConfig(bundle.config.runtime),
870
919
  backgroundSync: bundle.backgroundSyncService.getStatus(),
920
+ health: healthProblems(bundle.backgroundSyncService.getStatus(), await bundle.localIndexService.getStatus()),
871
921
  localIndex: await bundle.localIndexService.getStatus(),
872
922
  drafts: {
873
923
  total: (await bundle.draftStore.listDrafts(true)).length,
@@ -2023,9 +2073,19 @@ export async function main() {
2023
2073
  process.stdout.write(scheduleText(parsed));
2024
2074
  return;
2025
2075
  case "-v":
2026
- case "version":
2027
- process.stdout.write(`proton-mail-bridge-client ${await getPkgVersion()}\n`);
2076
+ case "version": {
2077
+ const current = await getPkgVersion();
2078
+ process.stdout.write(`proton-mail-bridge-client ${current}\n`);
2079
+ if (isTruthyFlag(parsed.flags.check)) {
2080
+ const update = await checkForUpdate(current);
2081
+ process.stdout.write(update.error
2082
+ ? `Could not check for updates: ${update.error}\n`
2083
+ : update.newer
2084
+ ? `A newer release is available: ${update.latest} (${update.releaseUrl}). Update with: npm install -g proton-mail-bridge-client\n`
2085
+ : "You are on the latest release.\n");
2086
+ }
2028
2087
  return;
2088
+ }
2029
2089
  case "setup-claude-desktop":
2030
2090
  await runClaudeDesktopSetupWizard();
2031
2091
  return;
@@ -2038,6 +2098,9 @@ export async function main() {
2038
2098
  case "connection-status":
2039
2099
  await runConnectionStatus(parsed);
2040
2100
  return;
2101
+ case "report":
2102
+ await runReport();
2103
+ return;
2041
2104
  case "runtime-status":
2042
2105
  await runRuntimeStatus(parsed);
2043
2106
  return;