proton-mail-bridge-client 2.3.2 → 2.4.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.
Files changed (68) hide show
  1. package/README.md +8 -2
  2. package/dist/cli.d.ts +11 -0
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +348 -202
  5. package/dist/cli.js.map +1 -1
  6. package/dist/index.d.ts +10 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +251 -133
  9. package/dist/index.js.map +1 -1
  10. package/dist/scripts/check-claude-desktop.d.ts +18 -0
  11. package/dist/scripts/check-claude-desktop.d.ts.map +1 -1
  12. package/dist/scripts/check-claude-desktop.js +91 -30
  13. package/dist/scripts/check-claude-desktop.js.map +1 -1
  14. package/dist/scripts/install-claude-desktop.d.ts +12 -0
  15. package/dist/scripts/install-claude-desktop.d.ts.map +1 -1
  16. package/dist/scripts/install-claude-desktop.js +211 -43
  17. package/dist/scripts/install-claude-desktop.js.map +1 -1
  18. package/dist/services/analytics-service.d.ts +1 -1
  19. package/dist/services/analytics-service.d.ts.map +1 -1
  20. package/dist/services/analytics-service.js +2 -2
  21. package/dist/services/analytics-service.js.map +1 -1
  22. package/dist/services/audit-service.d.ts.map +1 -1
  23. package/dist/services/audit-service.js +7 -2
  24. package/dist/services/audit-service.js.map +1 -1
  25. package/dist/services/background-sync-service.d.ts.map +1 -1
  26. package/dist/services/background-sync-service.js +10 -2
  27. package/dist/services/background-sync-service.js.map +1 -1
  28. package/dist/services/delivery-queue-service.d.ts.map +1 -1
  29. package/dist/services/delivery-queue-service.js +3 -8
  30. package/dist/services/delivery-queue-service.js.map +1 -1
  31. package/dist/services/draft-store-service.d.ts.map +1 -1
  32. package/dist/services/draft-store-service.js +3 -4
  33. package/dist/services/draft-store-service.js.map +1 -1
  34. package/dist/services/local-index-service.d.ts +2 -1
  35. package/dist/services/local-index-service.d.ts.map +1 -1
  36. package/dist/services/local-index-service.js +45 -9
  37. package/dist/services/local-index-service.js.map +1 -1
  38. package/dist/services/simple-imap-service.d.ts +21 -2
  39. package/dist/services/simple-imap-service.d.ts.map +1 -1
  40. package/dist/services/simple-imap-service.js +271 -103
  41. package/dist/services/simple-imap-service.js.map +1 -1
  42. package/dist/services/smtp-service.d.ts.map +1 -1
  43. package/dist/services/smtp-service.js +15 -0
  44. package/dist/services/smtp-service.js.map +1 -1
  45. package/dist/services/snooze-service.d.ts.map +1 -1
  46. package/dist/services/snooze-service.js +40 -20
  47. package/dist/services/snooze-service.js.map +1 -1
  48. package/dist/services/template-service.d.ts +4 -2
  49. package/dist/services/template-service.d.ts.map +1 -1
  50. package/dist/services/template-service.js +43 -10
  51. package/dist/services/template-service.js.map +1 -1
  52. package/dist/types/index.d.ts +1 -0
  53. package/dist/types/index.d.ts.map +1 -1
  54. package/dist/utils/atomic-write.d.ts +2 -0
  55. package/dist/utils/atomic-write.d.ts.map +1 -0
  56. package/dist/utils/atomic-write.js +36 -0
  57. package/dist/utils/atomic-write.js.map +1 -0
  58. package/dist/utils/file-lock.d.ts.map +1 -1
  59. package/dist/utils/file-lock.js +39 -4
  60. package/dist/utils/file-lock.js.map +1 -1
  61. package/dist/utils/helpers.d.ts +14 -0
  62. package/dist/utils/helpers.d.ts.map +1 -1
  63. package/dist/utils/helpers.js +222 -21
  64. package/dist/utils/helpers.js.map +1 -1
  65. package/dist/utils/logger.d.ts.map +1 -1
  66. package/dist/utils/logger.js +29 -12
  67. package/dist/utils/logger.js.map +1 -1
  68. package/package.json +1 -1
@@ -14,4 +14,22 @@ export declare function getClaudeDesktopInstallStatus(options?: {
14
14
  configPath?: string;
15
15
  serverName?: string;
16
16
  }): Promise<ClaudeDesktopInstallStatus>;
17
+ export interface ClaudeDesktopHealth {
18
+ status: ClaudeDesktopInstallStatus;
19
+ ok: boolean;
20
+ problems: string[];
21
+ warnings: string[];
22
+ runtimeVersion?: string;
23
+ sourceVersion?: string;
24
+ }
25
+ /**
26
+ * Presence checks are not enough: a bumped Node, a half-installed runtime or a broken dist all
27
+ * leave the files in place while Claude Desktop cannot start the server. Run what Claude Desktop
28
+ * will run, with the configured command.
29
+ */
30
+ export declare function checkClaudeDesktopInstall(options?: {
31
+ configPath?: string;
32
+ serverName?: string;
33
+ sourceVersion?: string;
34
+ }): Promise<ClaudeDesktopHealth>;
17
35
  //# sourceMappingURL=check-claude-desktop.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"check-claude-desktop.d.ts","sourceRoot":"","sources":["../../src/scripts/check-claude-desktop.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,0BAA0B;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,OAAO,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,wBAAwB,EAAE,OAAO,CAAC;IAClC,YAAY,EAAE,OAAO,CAAC;CACvB;AA6DD,wBAAsB,6BAA6B,CAAC,OAAO,GAAE;IAC3D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,0BAA0B,CAAC,CA0C3C"}
1
+ {"version":3,"file":"check-claude-desktop.d.ts","sourceRoot":"","sources":["../../src/scripts/check-claude-desktop.ts"],"names":[],"mappings":"AAiBA,MAAM,WAAW,0BAA0B;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,OAAO,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,wBAAwB,EAAE,OAAO,CAAC;IAClC,YAAY,EAAE,OAAO,CAAC;CACvB;AA6DD,wBAAsB,6BAA6B,CAAC,OAAO,GAAE;IAC3D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAyC3C;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,0BAA0B,CAAC;IACnC,EAAE,EAAE,OAAO,CAAC;IACZ,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAWD;;;;GAIG;AACH,wBAAsB,yBAAyB,CAC7C,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAO,GACjF,OAAO,CAAC,mBAAmB,CAAC,CAwD9B"}
@@ -1,8 +1,11 @@
1
1
  import { constants as fsConstants } from "node:fs";
2
+ import { execFile } from "node:child_process";
2
3
  import { access, readFile } from "node:fs/promises";
3
- import { resolve } from "node:path";
4
+ import { dirname, join, resolve } from "node:path";
4
5
  import { fileURLToPath } from "node:url";
5
- import { resolveClaudeDesktopConfigPath, } from "./install-claude-desktop.js";
6
+ import { promisify } from "node:util";
7
+ import { nodeCommandWarnings, parseClaudeDesktopConfig, resolveClaudeDesktopConfigPath, verifyRuntime, } from "./install-claude-desktop.js";
8
+ const execFileAsync = promisify(execFile);
6
9
  const DEFAULT_SERVER_NAME = "proton-mail-bridge";
7
10
  function parseCliArgs(argv) {
8
11
  const options = {};
@@ -62,8 +65,7 @@ export async function getClaudeDesktopInstallStatus(options = {}) {
62
65
  let configExists = false;
63
66
  let parsed = {};
64
67
  try {
65
- const raw = await readFile(configPath, "utf8");
66
- parsed = JSON.parse(raw);
68
+ parsed = parseClaudeDesktopConfig(await readFile(configPath, "utf8"), configPath);
67
69
  configExists = true;
68
70
  }
69
71
  catch (error) {
@@ -93,40 +95,99 @@ export async function getClaudeDesktopInstallStatus(options = {}) {
93
95
  hasEnvConfig: Boolean(entry?.env && typeof entry.env === "object" && !Array.isArray(entry.env)),
94
96
  };
95
97
  }
96
- function renderStatus(status) {
98
+ async function readVersion(packageJsonPath) {
99
+ try {
100
+ const version = JSON.parse(await readFile(packageJsonPath, "utf8")).version;
101
+ return typeof version === "string" ? version : undefined;
102
+ }
103
+ catch {
104
+ return undefined;
105
+ }
106
+ }
107
+ /**
108
+ * Presence checks are not enough: a bumped Node, a half-installed runtime or a broken dist all
109
+ * leave the files in place while Claude Desktop cannot start the server. Run what Claude Desktop
110
+ * will run, with the configured command.
111
+ */
112
+ export async function checkClaudeDesktopInstall(options = {}) {
113
+ const status = await getClaudeDesktopInstallStatus(options);
114
+ const problems = [];
115
+ const warnings = [];
116
+ const health = { status, ok: false, problems, warnings };
97
117
  if (!status.configExists) {
98
- return [
99
- "Claude Desktop config not found.",
100
- `Expected config path: ${status.configPath}`,
101
- "Run npm run setup:claude-desktop to install Proton Mail Bridge Client for Claude Desktop.",
102
- ].join("\n");
118
+ problems.push(`Claude Desktop config not found at ${status.configPath}. Run npm run setup:claude-desktop.`);
119
+ return health;
103
120
  }
104
121
  if (!status.installed) {
105
- return [
106
- "Proton Mail Bridge Client is not currently registered in Claude Desktop.",
107
- `Config path: ${status.configPath}`,
108
- `Expected server key: ${status.serverName}`,
109
- "Run npm run setup:claude-desktop to add it.",
110
- ].join("\n");
111
- }
112
- return [
113
- "Claude Desktop install looks present.",
114
- `Config path: ${status.configPath}`,
115
- `Server key: ${status.serverName}`,
116
- `Runtime dir: ${status.runtimeDir || "unknown"}`,
117
- `Runtime entry exists: ${status.runtimeEntryExists ? "yes" : "no"}`,
118
- `Runtime dependencies exist: ${status.runtimeNodeModulesExists ? "yes" : "no"}`,
119
- `Env config present: ${status.hasEnvConfig ? "yes" : "no"}`,
120
- ].join("\n");
122
+ problems.push(`"${status.serverName}" is not registered in ${status.configPath}. Run npm run setup:claude-desktop.`);
123
+ return health;
124
+ }
125
+ const { runtimeDir, entryCommand } = status;
126
+ if (!runtimeDir)
127
+ problems.push("The config entry has no cwd (runtime directory).");
128
+ if (!status.runtimeEntryExists)
129
+ problems.push("Runtime entry (args[0], dist/index.js) does not exist.");
130
+ if (!status.runtimeNodeModulesExists)
131
+ problems.push("Runtime dependencies (node_modules) are missing.");
132
+ if (!entryCommand)
133
+ problems.push("The config entry has no command.");
134
+ let commandRuns = false;
135
+ if (entryCommand) {
136
+ try {
137
+ await execFileAsync(entryCommand, ["--version"], { timeout: 10_000 });
138
+ commandRuns = true;
139
+ }
140
+ catch (error) {
141
+ problems.push(`Configured command "${entryCommand}" does not run (${error instanceof Error ? error.message.split("\n")[0] : String(error)}). Re-run npm run setup:claude-desktop.`);
142
+ }
143
+ warnings.push(...nodeCommandWarnings(entryCommand));
144
+ }
145
+ if (runtimeDir) {
146
+ health.runtimeVersion = await readVersion(join(runtimeDir, "package.json"));
147
+ health.sourceVersion =
148
+ options.sourceVersion ?? (await readVersion(resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "package.json")));
149
+ if (!health.runtimeVersion) {
150
+ problems.push(`Runtime package.json is missing or unreadable in ${runtimeDir}.`);
151
+ }
152
+ else if (health.sourceVersion && health.runtimeVersion !== health.sourceVersion) {
153
+ problems.push(`Runtime is version ${health.runtimeVersion} but this package is ${health.sourceVersion}. Re-run npm run setup:claude-desktop to update it.`);
154
+ }
155
+ if (commandRuns && health.runtimeVersion && status.runtimeEntryExists && status.runtimeNodeModulesExists) {
156
+ try {
157
+ await verifyRuntime(runtimeDir, entryCommand, health.runtimeVersion);
158
+ }
159
+ catch (error) {
160
+ problems.push(error instanceof Error ? error.message : String(error));
161
+ }
162
+ }
163
+ }
164
+ health.ok = problems.length === 0;
165
+ return health;
166
+ }
167
+ function renderHealth(health) {
168
+ const { status } = health;
169
+ const lines = health.ok
170
+ ? ["Claude Desktop install is healthy (command runs, better-sqlite3 loads, dist imports)."]
171
+ : ["Claude Desktop install has problems:", ...health.problems.map((problem) => ` - ${problem}`)];
172
+ if (health.warnings.length > 0)
173
+ lines.push(...health.warnings.map((warning) => `Warning: ${warning}`));
174
+ if (status.installed) {
175
+ lines.push(`Config path: ${status.configPath}`, `Server key: ${status.serverName}`, `Runtime dir: ${status.runtimeDir || "unknown"}`, `Runtime version: ${health.runtimeVersion ?? "unknown"}${health.sourceVersion ? ` (package ${health.sourceVersion})` : ""}`, `Env config present: ${status.hasEnvConfig ? "yes" : "no"}`);
176
+ }
177
+ return lines.join("\n");
121
178
  }
122
179
  async function main() {
123
180
  const options = parseCliArgs(process.argv.slice(2));
124
- const status = await getClaudeDesktopInstallStatus(options);
181
+ const health = await checkClaudeDesktopInstall(options);
125
182
  if (options.json) {
126
- process.stdout.write(`${JSON.stringify(status, null, 2)}\n`);
127
- return;
183
+ process.stdout.write(`${JSON.stringify(health, null, 2)}\n`);
184
+ }
185
+ else {
186
+ process.stdout.write(`${renderHealth(health)}\n`);
187
+ }
188
+ if (!health.ok) {
189
+ process.exitCode = 1;
128
190
  }
129
- process.stdout.write(`${renderStatus(status)}\n`);
130
191
  }
131
192
  const isDirectExecution = process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url);
132
193
  if (isDirectExecution) {
@@ -1 +1 @@
1
- {"version":3,"file":"check-claude-desktop.js","sourceRoot":"","sources":["../../src/scripts/check-claude-desktop.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"check-claude-desktop.js","sourceRoot":"","sources":["../../src/scripts/check-claude-desktop.ts"],"names":[],"mappings":"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"}
@@ -13,22 +13,33 @@ export interface InstallOptions {
13
13
  includeEnv?: boolean;
14
14
  env?: Record<string, string>;
15
15
  useRepoRuntime?: boolean;
16
+ /** Test seams for the staged install; the defaults are the real implementations. */
17
+ installDependencies?: (stagingDir: string, hasLockfile: boolean) => Promise<void>;
18
+ afterInstall?: (stagingDir: string) => Promise<void>;
19
+ rename?: (from: string, to: string) => Promise<void>;
20
+ verifyRuntime?: (dir: string, nodeCommand: string, expectedVersion: string) => Promise<void>;
16
21
  }
17
22
  export declare function resolveClaudeDesktopConfigPath(explicitPath?: string): string;
18
23
  export declare function resolveClaudeDesktopRuntimeDir(explicitPath?: string): string;
19
24
  export declare function collectInstallEnv(sourceEnv?: NodeJS.ProcessEnv): Record<string, string>;
20
25
  export declare function resolveStableNodeCommand(execPath: string, realpath?: (path: string) => string): string;
26
+ /** A version-pinned nvm path stops existing after `nvm uninstall`; the config would silently break. */
27
+ export declare function nodeCommandWarnings(command: string): string[];
21
28
  export declare function buildClaudeDesktopServerConfig(options?: InstallOptions): {
22
29
  serverName: string;
23
30
  serverConfig: ClaudeDesktopServerConfig;
24
31
  };
25
32
  export declare function buildRuntimeInstallArgs(hasLockfile: boolean): string[];
33
+ export declare function verifyRuntime(dir: string, nodeCommand: string, expectedVersion: string): Promise<void>;
26
34
  export declare function prepareClaudeDesktopRuntime(options?: InstallOptions): Promise<{
27
35
  runtimeDir: string;
28
36
  sourceCwd: string;
29
37
  usedRepoRuntime: boolean;
30
38
  }>;
31
39
  export declare function mergeClaudeDesktopConfig(existing: Record<string, unknown>, serverName: string, serverConfig: ClaudeDesktopServerConfig): Record<string, unknown>;
40
+ export declare function parseClaudeDesktopConfig(raw: string, configPath: string): Record<string, unknown>;
41
+ /** Copy of an env map that is safe to print: secret values hidden, key names kept. */
42
+ export declare function redactEnv(env: Record<string, string>): Record<string, string>;
32
43
  export declare function installClaudeDesktopConfig(options?: InstallOptions): Promise<{
33
44
  configPath: string;
34
45
  backupPath?: string;
@@ -37,5 +48,6 @@ export declare function installClaudeDesktopConfig(options?: InstallOptions): Pr
37
48
  runtimeDir: string;
38
49
  sourceCwd: string;
39
50
  usedRepoRuntime: boolean;
51
+ warnings: string[];
40
52
  }>;
41
53
  //# sourceMappingURL=install-claude-desktop.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"install-claude-desktop.d.ts","sourceRoot":"","sources":["../../src/scripts/install-claude-desktop.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAoFD,wBAAgB,8BAA8B,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAkB5E;AAED,wBAAgB,8BAA8B,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAkB5E;AAED,wBAAgB,iBAAiB,CAAC,SAAS,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAMpG;AAsBD,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,EAChB,QAAQ,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAqC,GAChE,MAAM,CAgBR;AAED,wBAAgB,8BAA8B,CAC5C,OAAO,GAAE,cAAmB,GAC3B;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,yBAAyB,CAAA;CAAE,CAiBjE;AAeD,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,OAAO,GAAG,MAAM,EAAE,CAMtE;AAqBD,wBAAsB,2BAA2B,CAAC,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC;IACvF,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,OAAO,CAAC;CAC1B,CAAC,CAiCD;AAED,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,yBAAyB,GACtC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAsCzB;AAsCD,wBAAsB,0BAA0B,CAAC,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC;IACtF,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,yBAAyB,CAAC;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,OAAO,CAAC;CAC1B,CAAC,CA0BD"}
1
+ {"version":3,"file":"install-claude-desktop.d.ts","sourceRoot":"","sources":["../../src/scripts/install-claude-desktop.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  import { execFile } from "node:child_process";
2
2
  import { constants as fsConstants, realpathSync } from "node:fs";
3
- import { access, chmod, copyFile, cp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
3
+ import { access, chmod, copyFile, cp, mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
4
4
  import { homedir } from "node:os";
5
- import { dirname, join, resolve } from "node:path";
5
+ import { basename, dirname, join, resolve } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { promisify } from "node:util";
8
8
  const DEFAULT_SERVER_NAME = "proton-mail-bridge";
@@ -116,7 +116,10 @@ export function resolveClaudeDesktopRuntimeDir(explicitPath) {
116
116
  }
117
117
  }
118
118
  export function collectInstallEnv(sourceEnv = process.env) {
119
- return Object.fromEntries(Object.entries(sourceEnv).filter(([key, value]) => Boolean(value) && (key.startsWith("PROTONMAIL_") || key === "DEBUG")));
119
+ return Object.fromEntries(Object.entries(sourceEnv).filter(([key, value]) => Boolean(value) &&
120
+ (key.startsWith("PROTONMAIL_") || key === "DEBUG") &&
121
+ // The installer's own override; the server never reads it.
122
+ key !== "PROTONMAIL_CLAUDE_RUNTIME_DIR"));
120
123
  }
121
124
  // Homebrew's `node` formula installs into a version-pinned Cellar directory
122
125
  // (e.g. /opt/homebrew/Cellar/node/25.8.0/bin/node) and symlinks a stable
@@ -139,11 +142,15 @@ export function collectInstallEnv(sourceEnv = process.env) {
139
142
  // either already version-managed a different way or we can't safely assume
140
143
  // a stable alternative exists.
141
144
  export function resolveStableNodeCommand(execPath, realpath = (path) => realpathSync(path)) {
142
- const cellarMatch = execPath.match(/^(.*)\/Cellar\/node\/[^/]+\/bin\/node$/);
145
+ // `node` -> <prefix>/bin/node; versioned formulae (`node@22`) keep their stable
146
+ // link under <prefix>/opt/node@22/bin/node (there is no <prefix>/bin/node for them).
147
+ const cellarMatch = execPath.match(/^(.*)\/Cellar\/(node(?:@\d+)?)\/[^/]+\/bin\/node$/);
143
148
  if (!cellarMatch) {
144
149
  return execPath;
145
150
  }
146
- const stableCandidate = join(cellarMatch[1], "bin", "node");
151
+ const stableCandidate = cellarMatch[2] === "node"
152
+ ? join(cellarMatch[1], "bin", "node")
153
+ : join(cellarMatch[1], "opt", cellarMatch[2], "bin", "node");
147
154
  try {
148
155
  if (realpath(stableCandidate) === execPath) {
149
156
  return stableCandidate;
@@ -154,6 +161,15 @@ export function resolveStableNodeCommand(execPath, realpath = (path) => realpath
154
161
  }
155
162
  return execPath;
156
163
  }
164
+ /** A version-pinned nvm path stops existing after `nvm uninstall`; the config would silently break. */
165
+ export function nodeCommandWarnings(command) {
166
+ if (/[\\/]\.nvm[\\/]versions[\\/]node[\\/]v[^\\/]+[\\/]bin[\\/]node$/.test(command)) {
167
+ return [
168
+ `The configured Node (${command}) is a version-pinned nvm path. It disappears after "nvm uninstall"/"nvm install" of another version and Claude Desktop would then fail to start the server. Install Node via Homebrew or pass --command with a stable path.`,
169
+ ];
170
+ }
171
+ return [];
172
+ }
157
173
  export function buildClaudeDesktopServerConfig(options = {}) {
158
174
  const cwd = resolve(options.runtimeDir || options.cwd || resolveSourceRepoRoot());
159
175
  const command = options.command || resolveStableNodeCommand(process.execPath);
@@ -206,6 +222,31 @@ async function installRuntimeDependencies(runtimeDir, hasLockfile) {
206
222
  ...shellOpts,
207
223
  });
208
224
  }
225
+ // Runs in the runtime dir with the node Claude Desktop will use. Exits non-zero (stderr says why)
226
+ // unless the native module works, the version matches and both entry points import.
227
+ const VERIFY_SCRIPT = [
228
+ 'const { createRequire } = require("node:module");',
229
+ 'const path = require("node:path");',
230
+ 'const req = createRequire(path.join(process.cwd(), "package.json"));',
231
+ 'const Database = req("better-sqlite3");',
232
+ 'const db = new Database(":memory:");',
233
+ 'db.prepare("select 1").get();',
234
+ "db.close();",
235
+ 'const version = req("./package.json").version;',
236
+ 'if (version !== process.argv[1]) throw new Error("runtime package.json is " + version + ", expected " + process.argv[1]);',
237
+ 'Promise.all([import("./dist/index.js"), import("./dist/lib.js")]).then(() => process.exit(0), (error) => { console.error(error && error.stack || String(error)); process.exit(1); });',
238
+ ].join("\n");
239
+ export async function verifyRuntime(dir, nodeCommand, expectedVersion) {
240
+ try {
241
+ await execFileAsync(nodeCommand, ["-e", VERIFY_SCRIPT, expectedVersion], { cwd: dir, timeout: 60_000 });
242
+ }
243
+ catch (error) {
244
+ const detail = error.stderr?.trim() || (error instanceof Error ? error.message : String(error));
245
+ throw new Error(`Runtime verification failed in ${dir}: ${detail}`);
246
+ }
247
+ }
248
+ // Entries the installer owns inside the runtime dir; anything else is the user's and is carried over.
249
+ const MANAGED_RUNTIME_ENTRIES = new Set(["dist", "node_modules", "package.json", "package-lock.json"]);
209
250
  export async function prepareClaudeDesktopRuntime(options = {}) {
210
251
  const sourceCwd = resolveSourceRepoRoot(options.cwd);
211
252
  const runtimeDir = options.useRepoRuntime ? sourceCwd : resolveClaudeDesktopRuntimeDir(options.runtimeDir);
@@ -216,20 +257,56 @@ export async function prepareClaudeDesktopRuntime(options = {}) {
216
257
  usedRepoRuntime: true,
217
258
  };
218
259
  }
219
- await mkdir(runtimeDir, { recursive: true });
220
- await rm(join(runtimeDir, "dist"), { recursive: true, force: true });
221
- await cp(join(sourceCwd, "dist"), join(runtimeDir, "dist"), { recursive: true, force: true });
222
- await copyFile(join(sourceCwd, "package.json"), join(runtimeDir, "package.json"));
223
- // Only present when installing from a git checkout; published tarballs never contain it.
224
- const lockfilePath = join(sourceCwd, "package-lock.json");
225
- const hasLockfile = await pathExists(lockfilePath);
226
- if (hasLockfile) {
227
- await copyFile(lockfilePath, join(runtimeDir, "package-lock.json"));
260
+ // Build and verify a complete runtime next to the live one, then swap by rename. The live
261
+ // runtime is never touched until the replacement is known to work, and the old one is kept
262
+ // as <runtimeDir>.previous (release 2.2.0 broke a machine by deleting dist and running
263
+ // `npm ci` in place).
264
+ const stagingDir = `${runtimeDir}.staging-${process.pid}`;
265
+ const previousDir = `${runtimeDir}.previous`;
266
+ const nodeCommand = options.command || resolveStableNodeCommand(process.execPath);
267
+ const verify = options.verifyRuntime ?? verifyRuntime;
268
+ const move = options.rename ?? rename;
269
+ const install = options.installDependencies ?? installRuntimeDependencies;
270
+ const { version } = JSON.parse(await readFile(join(sourceCwd, "package.json"), "utf8"));
271
+ await rm(stagingDir, { recursive: true, force: true });
272
+ await mkdir(dirname(runtimeDir), { recursive: true });
273
+ await mkdir(stagingDir);
274
+ try {
275
+ await cp(join(sourceCwd, "dist"), join(stagingDir, "dist"), { recursive: true });
276
+ await copyFile(join(sourceCwd, "package.json"), join(stagingDir, "package.json"));
277
+ // Only present when installing from a git checkout; published tarballs never contain it.
278
+ const hasLockfile = await pathExists(join(sourceCwd, "package-lock.json"));
279
+ if (hasLockfile) {
280
+ await copyFile(join(sourceCwd, "package-lock.json"), join(stagingDir, "package-lock.json"));
281
+ }
282
+ await install(stagingDir, hasLockfile);
283
+ await options.afterInstall?.(stagingDir);
284
+ await verify(stagingDir, nodeCommand, version);
285
+ const hadPrevious = await pathExists(runtimeDir);
286
+ if (hadPrevious) {
287
+ for (const entry of await readdir(runtimeDir)) {
288
+ if (!MANAGED_RUNTIME_ENTRIES.has(entry)) {
289
+ await cp(join(runtimeDir, entry), join(stagingDir, entry), { recursive: true });
290
+ }
291
+ }
292
+ await rm(previousDir, { recursive: true, force: true });
293
+ await move(runtimeDir, previousDir);
294
+ }
295
+ try {
296
+ await move(stagingDir, runtimeDir);
297
+ await verify(runtimeDir, nodeCommand, version);
298
+ }
299
+ catch (error) {
300
+ await rm(runtimeDir, { recursive: true, force: true });
301
+ if (hadPrevious) {
302
+ await move(previousDir, runtimeDir);
303
+ }
304
+ throw new Error(`${error instanceof Error ? error.message : String(error)}\n${hadPrevious ? "The previous runtime was restored." : "Nothing was installed."}`);
305
+ }
228
306
  }
229
- else {
230
- await rm(join(runtimeDir, "package-lock.json"), { force: true });
307
+ finally {
308
+ await rm(stagingDir, { recursive: true, force: true });
231
309
  }
232
- await installRuntimeDependencies(runtimeDir, hasLockfile);
233
310
  return {
234
311
  runtimeDir,
235
312
  sourceCwd,
@@ -240,45 +317,57 @@ export function mergeClaudeDesktopConfig(existing, serverName, serverConfig) {
240
317
  const existingServers = existing.mcpServers && typeof existing.mcpServers === "object" && !Array.isArray(existing.mcpServers)
241
318
  ? existing.mcpServers
242
319
  : {};
243
- // collectInstallEnv() only sees PROTONMAIL_*/DEBUG vars exported in the
244
- // shell that's running this installer right now — it has no way to see
245
- // credentials that were hand-added straight into the config (e.g. because
246
- // Claude Desktop itself, not this shell, is what had them set). A plain
247
- // re-run of `npm run install:claude-desktop` from a shell without those
248
- // vars exported used to silently wipe a working env block, since this
249
- // function replaced the whole server entry outright. Reproduced live:
250
- // install ran with none of PROTONMAIL_* exported, and the previously-
251
- // working server lost its login and failed with "Missing required
252
- // environment variables" until the config was hand-repaired from the
253
- // installer's own pre-write backup. If this run has nothing new to
254
- // contribute, keep whatever was already configured instead of dropping it.
320
+ // collectInstallEnv() only sees PROTONMAIL_*/DEBUG vars exported in the shell running this
321
+ // installer; it cannot see keys that were hand-added to the config (accounts, signature, ...).
322
+ // Replacing the env wholesale dropped them: with only PROTONMAIL_PASSWORD exported, the
323
+ // existing PROTONMAIL_ACCOUNTS_JSON vanished. Merge instead, new values winning per key.
255
324
  const existingServerConfig = existingServers[serverName];
256
325
  const existingEnv = existingServerConfig &&
257
326
  typeof existingServerConfig === "object" &&
258
327
  !Array.isArray(existingServerConfig) &&
328
+ existingServerConfig.env &&
259
329
  typeof existingServerConfig.env === "object"
260
- ? (existingServerConfig.env ?? undefined)
330
+ ? (existingServerConfig.env)
261
331
  : undefined;
262
- const env = serverConfig.env && Object.keys(serverConfig.env).length > 0 ? serverConfig.env : existingEnv;
332
+ const env = { ...existingEnv, ...serverConfig.env };
263
333
  return {
264
334
  ...existing,
265
335
  mcpServers: {
266
336
  ...existingServers,
267
337
  [serverName]: {
268
338
  ...serverConfig,
269
- ...(env && Object.keys(env).length > 0 ? { env } : {}),
339
+ ...(Object.keys(env).length > 0 ? { env } : {}),
270
340
  },
271
341
  },
272
342
  };
273
343
  }
344
+ function lineOf(raw, message) {
345
+ const line = message.match(/\(line (\d+)/)?.[1];
346
+ if (line)
347
+ return line;
348
+ const position = message.match(/position (\d+)/)?.[1];
349
+ if (position)
350
+ return String(raw.slice(0, Number(position)).split("\n").length);
351
+ return "unknown";
352
+ }
353
+ export function parseClaudeDesktopConfig(raw, configPath) {
354
+ const text = raw.charCodeAt(0) === 0xfeff ? raw.slice(1) : raw;
355
+ let parsed;
356
+ try {
357
+ parsed = JSON.parse(text);
358
+ }
359
+ catch (error) {
360
+ const message = error instanceof Error ? error.message : String(error);
361
+ throw new Error(`${basename(configPath)} is not valid JSON at line ${lineOf(text, message)}: ${message} — Claude Desktop does not accept comments or trailing commas. The file was not changed.`);
362
+ }
363
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
364
+ throw new Error(`Claude Desktop config at ${configPath} must contain a JSON object.`);
365
+ }
366
+ return parsed;
367
+ }
274
368
  async function readExistingConfig(configPath) {
275
369
  try {
276
- const raw = await readFile(configPath, "utf8");
277
- const parsed = JSON.parse(raw);
278
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
279
- throw new Error(`Claude Desktop config at ${configPath} must contain a JSON object.`);
280
- }
281
- return parsed;
370
+ return parseClaudeDesktopConfig(await readFile(configPath, "utf8"), configPath);
282
371
  }
283
372
  catch (error) {
284
373
  if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
@@ -287,6 +376,38 @@ async function readExistingConfig(configPath) {
287
376
  throw error;
288
377
  }
289
378
  }
379
+ // Fail before the runtime is replaced, not after: a read-only config used to surface only at
380
+ // the final write, with the new runtime already in place and a stray backup left behind.
381
+ async function assertConfigWritable(configPath) {
382
+ try {
383
+ await access(configPath, fsConstants.W_OK);
384
+ return;
385
+ }
386
+ catch (error) {
387
+ if (!(error && typeof error === "object" && "code" in error && error.code === "ENOENT")) {
388
+ throw new Error(`Claude Desktop config ${configPath} is not writable: ${error.message}`);
389
+ }
390
+ }
391
+ // Not there yet: the first existing ancestor must be writable.
392
+ let dir = dirname(configPath);
393
+ while (!(await pathExists(dir)) && dirname(dir) !== dir)
394
+ dir = dirname(dir);
395
+ try {
396
+ await access(dir, fsConstants.W_OK);
397
+ }
398
+ catch (error) {
399
+ throw new Error(`Cannot create ${configPath}: ${error.message}`);
400
+ }
401
+ }
402
+ const MAX_CONFIG_BACKUPS = 5;
403
+ async function pruneConfigBackups(configPath) {
404
+ const prefix = `${basename(configPath)}.bak-`;
405
+ // The names embed an ISO timestamp, so lexical order is chronological.
406
+ const backups = (await readdir(dirname(configPath))).filter((name) => name.startsWith(prefix)).sort();
407
+ for (const name of backups.slice(0, Math.max(0, backups.length - MAX_CONFIG_BACKUPS))) {
408
+ await rm(join(dirname(configPath), name), { force: true });
409
+ }
410
+ }
290
411
  async function backupConfigIfPresent(configPath) {
291
412
  try {
292
413
  await access(configPath, fsConstants.F_OK);
@@ -298,6 +419,7 @@ async function backupConfigIfPresent(configPath) {
298
419
  // codebase that persists secrets (audit log, draft store, delivery queue).
299
420
  await copyFile(configPath, backupPath);
300
421
  await chmod(backupPath, 0o600);
422
+ await pruneConfigBackups(configPath);
301
423
  return backupPath;
302
424
  }
303
425
  catch (error) {
@@ -307,21 +429,56 @@ async function backupConfigIfPresent(configPath) {
307
429
  throw error;
308
430
  }
309
431
  }
432
+ const SECRET_KEY = /password|secret|token/i;
433
+ const REDACTED = "[redacted]";
434
+ function redactAccountsJson(value) {
435
+ try {
436
+ const parsed = JSON.parse(value);
437
+ const scrub = (node) => Array.isArray(node)
438
+ ? node.map(scrub)
439
+ : node && typeof node === "object"
440
+ ? Object.fromEntries(Object.entries(node).map(([key, inner]) => [key, SECRET_KEY.test(key) ? REDACTED : scrub(inner)]))
441
+ : node;
442
+ return JSON.stringify(scrub(parsed));
443
+ }
444
+ catch {
445
+ return REDACTED;
446
+ }
447
+ }
448
+ /** Copy of an env map that is safe to print: secret values hidden, key names kept. */
449
+ export function redactEnv(env) {
450
+ return Object.fromEntries(Object.entries(env).map(([key, value]) => [
451
+ key,
452
+ key === "PROTONMAIL_ACCOUNTS_JSON" ? redactAccountsJson(value) : SECRET_KEY.test(key) ? REDACTED : value,
453
+ ]));
454
+ }
310
455
  export async function installClaudeDesktopConfig(options = {}) {
311
456
  const configPath = resolveClaudeDesktopConfigPath(options.configPath);
312
- const existing = await readExistingConfig(configPath);
457
+ // Validate first (bad JSON, read-only) so nothing has been replaced when we refuse.
458
+ await readExistingConfig(configPath);
459
+ await assertConfigWritable(configPath);
313
460
  const runtime = await prepareClaudeDesktopRuntime(options);
314
461
  const { serverName, serverConfig } = buildClaudeDesktopServerConfig({
315
462
  ...options,
316
463
  runtimeDir: runtime.runtimeDir,
317
464
  });
465
+ // Read again now: Claude Desktop or the user may have edited the file while the runtime installed.
466
+ const existing = await readExistingConfig(configPath);
318
467
  const merged = mergeClaudeDesktopConfig(existing, serverName, serverConfig);
319
468
  await mkdir(dirname(configPath), { recursive: true, mode: 0o700 });
320
469
  const backupPath = await backupConfigIfPresent(configPath);
470
+ // Temp file in the same directory + rename, so a crash never leaves a half-written config.
321
471
  // See backupConfigIfPresent's comment: this file holds live Bridge credentials.
322
- await writeFile(configPath, `${JSON.stringify(merged, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
323
- // writeFile's mode only applies to a newly created file; tighten an existing one too.
324
- await chmod(configPath, 0o600);
472
+ const tempPath = `${configPath}.tmp-${process.pid}`;
473
+ try {
474
+ await writeFile(tempPath, `${JSON.stringify(merged, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
475
+ await chmod(tempPath, 0o600);
476
+ await rename(tempPath, configPath);
477
+ }
478
+ catch (error) {
479
+ await rm(tempPath, { force: true });
480
+ throw error;
481
+ }
325
482
  return {
326
483
  configPath,
327
484
  backupPath,
@@ -330,12 +487,23 @@ export async function installClaudeDesktopConfig(options = {}) {
330
487
  runtimeDir: runtime.runtimeDir,
331
488
  sourceCwd: runtime.sourceCwd,
332
489
  usedRepoRuntime: runtime.usedRepoRuntime,
490
+ warnings: nodeCommandWarnings(serverConfig.command),
333
491
  };
334
492
  }
335
493
  async function main() {
336
494
  const options = parseCliArgs(process.argv.slice(2));
337
495
  const result = await installClaudeDesktopConfig(options);
338
- process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
496
+ const printable = {
497
+ ...result,
498
+ serverConfig: {
499
+ ...result.serverConfig,
500
+ ...(result.serverConfig.env ? { env: redactEnv(result.serverConfig.env) } : {}),
501
+ },
502
+ };
503
+ process.stdout.write(`${JSON.stringify(printable, null, 2)}\n`);
504
+ for (const warning of result.warnings) {
505
+ process.stderr.write(`Warning: ${warning}\n`);
506
+ }
339
507
  }
340
508
  const isDirectExecution = process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url);
341
509
  if (isDirectExecution) {