proton-mail-bridge-client 2.3.2 → 2.4.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 +8 -2
- package/dist/cli.d.ts +11 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +348 -202
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +251 -133
- package/dist/index.js.map +1 -1
- package/dist/scripts/check-claude-desktop.d.ts +18 -0
- package/dist/scripts/check-claude-desktop.d.ts.map +1 -1
- package/dist/scripts/check-claude-desktop.js +91 -30
- package/dist/scripts/check-claude-desktop.js.map +1 -1
- package/dist/scripts/install-claude-desktop.d.ts +12 -0
- package/dist/scripts/install-claude-desktop.d.ts.map +1 -1
- package/dist/scripts/install-claude-desktop.js +211 -43
- package/dist/scripts/install-claude-desktop.js.map +1 -1
- package/dist/services/analytics-service.d.ts +1 -1
- package/dist/services/analytics-service.d.ts.map +1 -1
- package/dist/services/analytics-service.js +2 -2
- package/dist/services/analytics-service.js.map +1 -1
- package/dist/services/audit-service.d.ts.map +1 -1
- package/dist/services/audit-service.js +7 -2
- package/dist/services/audit-service.js.map +1 -1
- package/dist/services/background-sync-service.d.ts.map +1 -1
- package/dist/services/background-sync-service.js +10 -2
- package/dist/services/background-sync-service.js.map +1 -1
- package/dist/services/delivery-queue-service.d.ts.map +1 -1
- package/dist/services/delivery-queue-service.js +3 -8
- package/dist/services/delivery-queue-service.js.map +1 -1
- package/dist/services/draft-store-service.d.ts.map +1 -1
- package/dist/services/draft-store-service.js +3 -4
- package/dist/services/draft-store-service.js.map +1 -1
- package/dist/services/local-index-service.d.ts +2 -1
- package/dist/services/local-index-service.d.ts.map +1 -1
- package/dist/services/local-index-service.js +45 -9
- package/dist/services/local-index-service.js.map +1 -1
- package/dist/services/simple-imap-service.d.ts +21 -2
- package/dist/services/simple-imap-service.d.ts.map +1 -1
- package/dist/services/simple-imap-service.js +271 -103
- package/dist/services/simple-imap-service.js.map +1 -1
- package/dist/services/smtp-service.d.ts.map +1 -1
- package/dist/services/smtp-service.js +15 -0
- package/dist/services/smtp-service.js.map +1 -1
- package/dist/services/snooze-service.d.ts.map +1 -1
- package/dist/services/snooze-service.js +40 -20
- package/dist/services/snooze-service.js.map +1 -1
- package/dist/services/template-service.d.ts +4 -2
- package/dist/services/template-service.d.ts.map +1 -1
- package/dist/services/template-service.js +43 -10
- package/dist/services/template-service.js.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/atomic-write.d.ts +2 -0
- package/dist/utils/atomic-write.d.ts.map +1 -0
- package/dist/utils/atomic-write.js +36 -0
- package/dist/utils/atomic-write.js.map +1 -0
- package/dist/utils/file-lock.d.ts.map +1 -1
- package/dist/utils/file-lock.js +39 -4
- package/dist/utils/file-lock.js.map +1 -1
- package/dist/utils/helpers.d.ts +14 -0
- package/dist/utils/helpers.d.ts.map +1 -1
- package/dist/utils/helpers.js +222 -21
- package/dist/utils/helpers.js.map +1 -1
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +29 -12
- package/dist/utils/logger.js.map +1 -1
- 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":"
|
|
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 {
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
99
|
-
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
|
181
|
+
const health = await checkClaudeDesktopInstall(options);
|
|
125
182
|
if (options.json) {
|
|
126
|
-
process.stdout.write(`${JSON.stringify(
|
|
127
|
-
|
|
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":"AAAA,OAAO,EAAE,SAAS,IAAI,WAAW,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
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;
|
|
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) &&
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
const
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
|
|
230
|
-
await rm(
|
|
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
|
-
//
|
|
245
|
-
//
|
|
246
|
-
//
|
|
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
|
|
330
|
+
? (existingServerConfig.env)
|
|
261
331
|
: undefined;
|
|
262
|
-
const env =
|
|
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
|
-
...(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
-
|
|
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) {
|