@helmisatria/mcp-chrome-bridge 1.0.30
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 +183 -0
- package/dist/README.md +25 -0
- package/dist/agent/attachment-service.d.ts +83 -0
- package/dist/agent/attachment-service.js +370 -0
- package/dist/agent/attachment-service.js.map +1 -0
- package/dist/agent/ccr-detector.d.ts +59 -0
- package/dist/agent/ccr-detector.js +311 -0
- package/dist/agent/ccr-detector.js.map +1 -0
- package/dist/agent/chat-service.d.ts +50 -0
- package/dist/agent/chat-service.js +439 -0
- package/dist/agent/chat-service.js.map +1 -0
- package/dist/agent/db/client.d.ts +26 -0
- package/dist/agent/db/client.js +244 -0
- package/dist/agent/db/client.js.map +1 -0
- package/dist/agent/db/index.d.ts +5 -0
- package/dist/agent/db/index.js +22 -0
- package/dist/agent/db/index.js.map +1 -0
- package/dist/agent/db/schema.d.ts +711 -0
- package/dist/agent/db/schema.js +121 -0
- package/dist/agent/db/schema.js.map +1 -0
- package/dist/agent/directory-picker.d.ts +11 -0
- package/dist/agent/directory-picker.js +149 -0
- package/dist/agent/directory-picker.js.map +1 -0
- package/dist/agent/engines/claude.d.ts +79 -0
- package/dist/agent/engines/claude.js +1338 -0
- package/dist/agent/engines/claude.js.map +1 -0
- package/dist/agent/engines/codex.d.ts +48 -0
- package/dist/agent/engines/codex.js +822 -0
- package/dist/agent/engines/codex.js.map +1 -0
- package/dist/agent/engines/types.d.ts +133 -0
- package/dist/agent/engines/types.js +3 -0
- package/dist/agent/engines/types.js.map +1 -0
- package/dist/agent/message-service.d.ts +56 -0
- package/dist/agent/message-service.js +198 -0
- package/dist/agent/message-service.js.map +1 -0
- package/dist/agent/open-project.d.ts +25 -0
- package/dist/agent/open-project.js +469 -0
- package/dist/agent/open-project.js.map +1 -0
- package/dist/agent/project-service.d.ts +49 -0
- package/dist/agent/project-service.js +254 -0
- package/dist/agent/project-service.js.map +1 -0
- package/dist/agent/project-types.d.ts +27 -0
- package/dist/agent/project-types.js +3 -0
- package/dist/agent/project-types.js.map +1 -0
- package/dist/agent/session-service.d.ts +198 -0
- package/dist/agent/session-service.js +292 -0
- package/dist/agent/session-service.js.map +1 -0
- package/dist/agent/storage.d.ts +27 -0
- package/dist/agent/storage.js +73 -0
- package/dist/agent/storage.js.map +1 -0
- package/dist/agent/stream-manager.d.ts +42 -0
- package/dist/agent/stream-manager.js +243 -0
- package/dist/agent/stream-manager.js.map +1 -0
- package/dist/agent/tool-bridge.d.ts +44 -0
- package/dist/agent/tool-bridge.js +50 -0
- package/dist/agent/tool-bridge.js.map +1 -0
- package/dist/agent/types.d.ts +6 -0
- package/dist/agent/types.js +3 -0
- package/dist/agent/types.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +224 -0
- package/dist/cli.js.map +1 -0
- package/dist/constant/index.d.ts +60 -0
- package/dist/constant/index.js +80 -0
- package/dist/constant/index.js.map +1 -0
- package/dist/file-handler.d.ts +41 -0
- package/dist/file-handler.js +295 -0
- package/dist/file-handler.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/mcp-server-stdio.d.ts +72 -0
- package/dist/mcp/mcp-server-stdio.js +143 -0
- package/dist/mcp/mcp-server-stdio.js.map +1 -0
- package/dist/mcp/mcp-server.d.ts +36 -0
- package/dist/mcp/mcp-server.js +26 -0
- package/dist/mcp/mcp-server.js.map +1 -0
- package/dist/mcp/register-tools.d.ts +2 -0
- package/dist/mcp/register-tools.js +148 -0
- package/dist/mcp/register-tools.js.map +1 -0
- package/dist/mcp/stdio-config.json +3 -0
- package/dist/native-messaging-host.d.ts +42 -0
- package/dist/native-messaging-host.js +312 -0
- package/dist/native-messaging-host.js.map +1 -0
- package/dist/run_host.bat +194 -0
- package/dist/run_host.sh +264 -0
- package/dist/scripts/browser-config.d.ts +28 -0
- package/dist/scripts/browser-config.js +229 -0
- package/dist/scripts/browser-config.js.map +1 -0
- package/dist/scripts/build.d.ts +1 -0
- package/dist/scripts/build.js +126 -0
- package/dist/scripts/build.js.map +1 -0
- package/dist/scripts/constant.d.ts +4 -0
- package/dist/scripts/constant.js +8 -0
- package/dist/scripts/constant.js.map +1 -0
- package/dist/scripts/doctor.d.ts +70 -0
- package/dist/scripts/doctor.js +930 -0
- package/dist/scripts/doctor.js.map +1 -0
- package/dist/scripts/postinstall.d.ts +2 -0
- package/dist/scripts/postinstall.js +246 -0
- package/dist/scripts/postinstall.js.map +1 -0
- package/dist/scripts/register-dev.d.ts +1 -0
- package/dist/scripts/register-dev.js +5 -0
- package/dist/scripts/register-dev.js.map +1 -0
- package/dist/scripts/register.d.ts +2 -0
- package/dist/scripts/register.js +28 -0
- package/dist/scripts/register.js.map +1 -0
- package/dist/scripts/report.d.ts +96 -0
- package/dist/scripts/report.js +686 -0
- package/dist/scripts/report.js.map +1 -0
- package/dist/scripts/utils.d.ts +64 -0
- package/dist/scripts/utils.js +443 -0
- package/dist/scripts/utils.js.map +1 -0
- package/dist/server/index.d.ts +35 -0
- package/dist/server/index.js +312 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/routes/agent.d.ts +21 -0
- package/dist/server/routes/agent.js +971 -0
- package/dist/server/routes/agent.js.map +1 -0
- package/dist/server/routes/index.d.ts +4 -0
- package/dist/server/routes/index.js +9 -0
- package/dist/server/routes/index.js.map +1 -0
- package/dist/trace-analyzer.d.ts +14 -0
- package/dist/trace-analyzer.js +113 -0
- package/dist/trace-analyzer.js.map +1 -0
- package/dist/util/logger.d.ts +1 -0
- package/dist/util/logger.js +43 -0
- package/dist/util/logger.js.map +1 -0
- package/package.json +91 -0
|
@@ -0,0 +1,930 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* doctor.ts
|
|
5
|
+
*
|
|
6
|
+
* Diagnoses common installation and runtime issues for the Chrome Native Messaging host.
|
|
7
|
+
* Provides checks for manifest files, Node.js path, permissions, and connectivity.
|
|
8
|
+
*/
|
|
9
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
10
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
11
|
+
};
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.collectDoctorReport = collectDoctorReport;
|
|
14
|
+
exports.runDoctor = runDoctor;
|
|
15
|
+
const fs_1 = __importDefault(require("fs"));
|
|
16
|
+
const os_1 = __importDefault(require("os"));
|
|
17
|
+
const path_1 = __importDefault(require("path"));
|
|
18
|
+
const child_process_1 = require("child_process");
|
|
19
|
+
const constant_1 = require("./constant");
|
|
20
|
+
const browser_config_1 = require("./browser-config");
|
|
21
|
+
const utils_1 = require("./utils");
|
|
22
|
+
const constant_2 = require("../constant");
|
|
23
|
+
const EXPECTED_PORT = 12306;
|
|
24
|
+
const SCHEMA_VERSION = 1;
|
|
25
|
+
const MIN_NODE_MAJOR_VERSION = 20;
|
|
26
|
+
// ============================================================================
|
|
27
|
+
// Utility Functions
|
|
28
|
+
// ============================================================================
|
|
29
|
+
function readPackageJson() {
|
|
30
|
+
try {
|
|
31
|
+
return require('../../package.json');
|
|
32
|
+
}
|
|
33
|
+
catch (_a) {
|
|
34
|
+
return {};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function getCommandInfo(pkg) {
|
|
38
|
+
const bin = pkg.bin;
|
|
39
|
+
if (!bin || typeof bin !== 'object') {
|
|
40
|
+
return { canonical: constant_1.COMMAND_NAME, aliases: [] };
|
|
41
|
+
}
|
|
42
|
+
const canonical = constant_1.COMMAND_NAME;
|
|
43
|
+
const canonicalTarget = bin[canonical];
|
|
44
|
+
const aliases = canonicalTarget
|
|
45
|
+
? Object.keys(bin).filter((name) => name !== canonical && bin[name] === canonicalTarget)
|
|
46
|
+
: [];
|
|
47
|
+
return { canonical, aliases };
|
|
48
|
+
}
|
|
49
|
+
function resolveDistDir() {
|
|
50
|
+
// __dirname is dist/scripts when running from compiled code
|
|
51
|
+
const candidateFromDistScripts = path_1.default.resolve(__dirname, '..');
|
|
52
|
+
const candidateFromSrcScripts = path_1.default.resolve(__dirname, '..', '..', 'dist');
|
|
53
|
+
const looksLikeDist = (dir) => {
|
|
54
|
+
return (fs_1.default.existsSync(path_1.default.join(dir, 'mcp', 'stdio-config.json')) ||
|
|
55
|
+
fs_1.default.existsSync(path_1.default.join(dir, 'run_host.sh')) ||
|
|
56
|
+
fs_1.default.existsSync(path_1.default.join(dir, 'run_host.bat')));
|
|
57
|
+
};
|
|
58
|
+
if (looksLikeDist(candidateFromDistScripts))
|
|
59
|
+
return candidateFromDistScripts;
|
|
60
|
+
if (looksLikeDist(candidateFromSrcScripts))
|
|
61
|
+
return candidateFromSrcScripts;
|
|
62
|
+
return candidateFromDistScripts;
|
|
63
|
+
}
|
|
64
|
+
function stringifyError(err) {
|
|
65
|
+
if (err instanceof Error)
|
|
66
|
+
return err.message;
|
|
67
|
+
return String(err);
|
|
68
|
+
}
|
|
69
|
+
function canExecute(filePath) {
|
|
70
|
+
try {
|
|
71
|
+
fs_1.default.accessSync(filePath, fs_1.default.constants.X_OK);
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
catch (_a) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function normalizeComparablePath(filePath) {
|
|
79
|
+
if (process.platform === 'win32') {
|
|
80
|
+
return path_1.default.normalize(filePath).toLowerCase();
|
|
81
|
+
}
|
|
82
|
+
return path_1.default.normalize(filePath);
|
|
83
|
+
}
|
|
84
|
+
function stripOuterQuotes(input) {
|
|
85
|
+
const trimmed = input.trim();
|
|
86
|
+
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
|
|
87
|
+
(trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
|
88
|
+
return trimmed.slice(1, -1);
|
|
89
|
+
}
|
|
90
|
+
return trimmed;
|
|
91
|
+
}
|
|
92
|
+
function expandTilde(inputPath) {
|
|
93
|
+
if (inputPath === '~')
|
|
94
|
+
return os_1.default.homedir();
|
|
95
|
+
if (inputPath.startsWith('~/') || inputPath.startsWith('~\\')) {
|
|
96
|
+
return path_1.default.join(os_1.default.homedir(), inputPath.slice(2));
|
|
97
|
+
}
|
|
98
|
+
return inputPath;
|
|
99
|
+
}
|
|
100
|
+
function expandWindowsEnvVars(input) {
|
|
101
|
+
if (process.platform !== 'win32')
|
|
102
|
+
return input;
|
|
103
|
+
return input.replace(/%([^%]+)%/g, (_match, name) => {
|
|
104
|
+
var _a, _b, _c;
|
|
105
|
+
const key = String(name);
|
|
106
|
+
return ((_c = (_b = (_a = process.env[key]) !== null && _a !== void 0 ? _a : process.env[key.toUpperCase()]) !== null && _b !== void 0 ? _b : process.env[key.toLowerCase()]) !== null && _c !== void 0 ? _c : _match);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
function parseVersionFromDirName(dirName) {
|
|
110
|
+
const cleaned = dirName.trim().replace(/^v/, '');
|
|
111
|
+
if (!/^\d+(\.\d+){0,3}$/.test(cleaned))
|
|
112
|
+
return null;
|
|
113
|
+
return cleaned.split('.').map((part) => Number(part));
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Parse Node.js version string from `node -v` output.
|
|
117
|
+
* Handles versions like: v20.10.0, v22.0.0-nightly.2024..., v21.0.0-rc.1
|
|
118
|
+
* Returns major version number or null if parsing fails.
|
|
119
|
+
*/
|
|
120
|
+
function parseNodeMajorVersion(versionString) {
|
|
121
|
+
if (!versionString)
|
|
122
|
+
return null;
|
|
123
|
+
// Match pattern: v?MAJOR.MINOR.PATCH[-anything]
|
|
124
|
+
const match = versionString.trim().match(/^v?(\d+)(?:\.\d+)*(?:[-+].*)?$/i);
|
|
125
|
+
if (match === null || match === void 0 ? void 0 : match[1]) {
|
|
126
|
+
const major = Number(match[1]);
|
|
127
|
+
return Number.isNaN(major) ? null : major;
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
function compareVersions(a, b) {
|
|
132
|
+
var _a, _b;
|
|
133
|
+
const len = Math.max(a.length, b.length);
|
|
134
|
+
for (let i = 0; i < len; i++) {
|
|
135
|
+
const av = (_a = a[i]) !== null && _a !== void 0 ? _a : 0;
|
|
136
|
+
const bv = (_b = b[i]) !== null && _b !== void 0 ? _b : 0;
|
|
137
|
+
if (av !== bv)
|
|
138
|
+
return av - bv;
|
|
139
|
+
}
|
|
140
|
+
return 0;
|
|
141
|
+
}
|
|
142
|
+
function pickLatestVersionDir(parentDir) {
|
|
143
|
+
if (!fs_1.default.existsSync(parentDir))
|
|
144
|
+
return null;
|
|
145
|
+
const dirents = fs_1.default.readdirSync(parentDir, { withFileTypes: true });
|
|
146
|
+
let best = null;
|
|
147
|
+
for (const dirent of dirents) {
|
|
148
|
+
if (!dirent.isDirectory())
|
|
149
|
+
continue;
|
|
150
|
+
const parsed = parseVersionFromDirName(dirent.name);
|
|
151
|
+
if (!parsed)
|
|
152
|
+
continue;
|
|
153
|
+
if (!best || compareVersions(parsed, best.version) > 0) {
|
|
154
|
+
best = { name: dirent.name, version: parsed };
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return best ? path_1.default.join(parentDir, best.name) : null;
|
|
158
|
+
}
|
|
159
|
+
// ============================================================================
|
|
160
|
+
// Node Resolution (mirrors run_host.sh/bat logic)
|
|
161
|
+
// ============================================================================
|
|
162
|
+
function resolveNodeCandidate(distDir) {
|
|
163
|
+
const nodeFileName = process.platform === 'win32' ? 'node.exe' : 'node';
|
|
164
|
+
const nodePathFilePath = path_1.default.join(distDir, 'node_path.txt');
|
|
165
|
+
const nodePathFile = {
|
|
166
|
+
path: nodePathFilePath,
|
|
167
|
+
exists: fs_1.default.existsSync(nodePathFilePath),
|
|
168
|
+
};
|
|
169
|
+
const consider = (source, rawCandidate) => {
|
|
170
|
+
if (!rawCandidate)
|
|
171
|
+
return null;
|
|
172
|
+
let candidate = expandTilde(stripOuterQuotes(rawCandidate));
|
|
173
|
+
try {
|
|
174
|
+
if (fs_1.default.existsSync(candidate) && fs_1.default.statSync(candidate).isDirectory()) {
|
|
175
|
+
candidate = path_1.default.join(candidate, nodeFileName);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch (_a) {
|
|
179
|
+
// ignore
|
|
180
|
+
}
|
|
181
|
+
if (canExecute(candidate)) {
|
|
182
|
+
return { nodePath: candidate, source };
|
|
183
|
+
}
|
|
184
|
+
return null;
|
|
185
|
+
};
|
|
186
|
+
// Priority 0: CHROME_MCP_NODE_PATH
|
|
187
|
+
const fromEnv = consider('CHROME_MCP_NODE_PATH', process.env.CHROME_MCP_NODE_PATH);
|
|
188
|
+
if (fromEnv) {
|
|
189
|
+
return { ...fromEnv, nodePathFile };
|
|
190
|
+
}
|
|
191
|
+
// Priority 1: node_path.txt
|
|
192
|
+
if (nodePathFile.exists) {
|
|
193
|
+
try {
|
|
194
|
+
const content = fs_1.default.readFileSync(nodePathFilePath, 'utf8').trim();
|
|
195
|
+
nodePathFile.value = content;
|
|
196
|
+
const fromFile = consider('node_path.txt', content);
|
|
197
|
+
nodePathFile.valid = Boolean(fromFile);
|
|
198
|
+
if (fromFile) {
|
|
199
|
+
return { ...fromFile, nodePathFile };
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
catch (e) {
|
|
203
|
+
nodePathFile.error = stringifyError(e);
|
|
204
|
+
nodePathFile.valid = false;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// Priority 1.5: Relative path fallback (mirrors run_host.sh/bat)
|
|
208
|
+
// Unix: ../../../bin/node (from dist/)
|
|
209
|
+
// Windows: ..\..\..\node.exe (from dist/, no bin/ subdirectory)
|
|
210
|
+
const relativeNodePath = process.platform === 'win32'
|
|
211
|
+
? path_1.default.resolve(distDir, '..', '..', '..', nodeFileName)
|
|
212
|
+
: path_1.default.resolve(distDir, '..', '..', '..', 'bin', nodeFileName);
|
|
213
|
+
const fromRelative = consider('relative', relativeNodePath);
|
|
214
|
+
if (fromRelative)
|
|
215
|
+
return { ...fromRelative, nodePathFile };
|
|
216
|
+
// Priority 2: Volta
|
|
217
|
+
const voltaHome = process.env.VOLTA_HOME || path_1.default.join(os_1.default.homedir(), '.volta');
|
|
218
|
+
const fromVolta = consider('volta', path_1.default.join(voltaHome, 'bin', nodeFileName));
|
|
219
|
+
if (fromVolta)
|
|
220
|
+
return { ...fromVolta, nodePathFile };
|
|
221
|
+
// Priority 3: asdf (cross-platform)
|
|
222
|
+
const asdfDir = process.env.ASDF_DATA_DIR || path_1.default.join(os_1.default.homedir(), '.asdf');
|
|
223
|
+
const asdfNodejsDir = path_1.default.join(asdfDir, 'installs', 'nodejs');
|
|
224
|
+
const latestAsdf = pickLatestVersionDir(asdfNodejsDir);
|
|
225
|
+
if (latestAsdf) {
|
|
226
|
+
const fromAsdf = consider('asdf', path_1.default.join(latestAsdf, 'bin', nodeFileName));
|
|
227
|
+
if (fromAsdf)
|
|
228
|
+
return { ...fromAsdf, nodePathFile };
|
|
229
|
+
}
|
|
230
|
+
// Priority 4: fnm (cross-platform, Windows uses different layout)
|
|
231
|
+
const fnmDir = process.env.FNM_DIR || path_1.default.join(os_1.default.homedir(), '.fnm');
|
|
232
|
+
const fnmVersionsDir = path_1.default.join(fnmDir, 'node-versions');
|
|
233
|
+
const latestFnm = pickLatestVersionDir(fnmVersionsDir);
|
|
234
|
+
if (latestFnm) {
|
|
235
|
+
const fnmNodePath = process.platform === 'win32'
|
|
236
|
+
? path_1.default.join(latestFnm, 'installation', nodeFileName)
|
|
237
|
+
: path_1.default.join(latestFnm, 'installation', 'bin', nodeFileName);
|
|
238
|
+
const fromFnm = consider('fnm', fnmNodePath);
|
|
239
|
+
if (fromFnm)
|
|
240
|
+
return { ...fromFnm, nodePathFile };
|
|
241
|
+
}
|
|
242
|
+
// Priority 5: NVM (Unix only)
|
|
243
|
+
if (process.platform !== 'win32') {
|
|
244
|
+
const nvmDir = process.env.NVM_DIR || path_1.default.join(os_1.default.homedir(), '.nvm');
|
|
245
|
+
const nvmDefaultAlias = path_1.default.join(nvmDir, 'alias', 'default');
|
|
246
|
+
try {
|
|
247
|
+
if (fs_1.default.existsSync(nvmDefaultAlias)) {
|
|
248
|
+
const stat = fs_1.default.lstatSync(nvmDefaultAlias);
|
|
249
|
+
const maybeVersion = stat.isSymbolicLink()
|
|
250
|
+
? fs_1.default.readlinkSync(nvmDefaultAlias).trim()
|
|
251
|
+
: fs_1.default.readFileSync(nvmDefaultAlias, 'utf8').trim();
|
|
252
|
+
const fromDefault = consider('nvm-default', path_1.default.join(nvmDir, 'versions', 'node', maybeVersion, 'bin', 'node'));
|
|
253
|
+
if (fromDefault)
|
|
254
|
+
return { ...fromDefault, nodePathFile };
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
catch (_a) {
|
|
258
|
+
// ignore
|
|
259
|
+
}
|
|
260
|
+
const latestNvm = pickLatestVersionDir(path_1.default.join(nvmDir, 'versions', 'node'));
|
|
261
|
+
if (latestNvm) {
|
|
262
|
+
const fromNvm = consider('nvm-latest', path_1.default.join(latestNvm, 'bin', 'node'));
|
|
263
|
+
if (fromNvm)
|
|
264
|
+
return { ...fromNvm, nodePathFile };
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
// Priority 6: Common paths
|
|
268
|
+
const commonPaths = process.platform === 'win32'
|
|
269
|
+
? [
|
|
270
|
+
path_1.default.join(process.env.ProgramFiles || 'C:\\Program Files', 'nodejs', 'node.exe'),
|
|
271
|
+
path_1.default.join(process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', 'nodejs', 'node.exe'),
|
|
272
|
+
path_1.default.join(process.env.LOCALAPPDATA || '', 'Programs', 'nodejs', 'node.exe'),
|
|
273
|
+
].filter((p) => path_1.default.isAbsolute(p))
|
|
274
|
+
: ['/opt/homebrew/bin/node', '/usr/local/bin/node', '/usr/bin/node'];
|
|
275
|
+
for (const common of commonPaths) {
|
|
276
|
+
const resolved = consider('common', common);
|
|
277
|
+
if (resolved)
|
|
278
|
+
return { ...resolved, nodePathFile };
|
|
279
|
+
}
|
|
280
|
+
// Priority 7: PATH
|
|
281
|
+
const pathEnv = process.env.PATH || '';
|
|
282
|
+
for (const rawDir of pathEnv.split(path_1.default.delimiter)) {
|
|
283
|
+
const dir = stripOuterQuotes(rawDir);
|
|
284
|
+
if (!dir)
|
|
285
|
+
continue;
|
|
286
|
+
const candidate = path_1.default.join(dir, nodeFileName);
|
|
287
|
+
if (canExecute(candidate)) {
|
|
288
|
+
return { nodePath: candidate, source: 'PATH', nodePathFile };
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return { nodePathFile };
|
|
292
|
+
}
|
|
293
|
+
// ============================================================================
|
|
294
|
+
// Browser Resolution
|
|
295
|
+
// ============================================================================
|
|
296
|
+
function resolveTargetBrowsers(browserArg) {
|
|
297
|
+
if (!browserArg)
|
|
298
|
+
return undefined;
|
|
299
|
+
const normalized = browserArg.toLowerCase();
|
|
300
|
+
if (normalized === 'all')
|
|
301
|
+
return [browser_config_1.BrowserType.CHROME, browser_config_1.BrowserType.CHROMIUM];
|
|
302
|
+
if (normalized === 'detect' || normalized === 'auto')
|
|
303
|
+
return undefined;
|
|
304
|
+
const parsed = (0, browser_config_1.parseBrowserType)(normalized);
|
|
305
|
+
if (!parsed) {
|
|
306
|
+
throw new Error(`Invalid browser: ${browserArg}. Use 'chrome', 'chromium', or 'all'`);
|
|
307
|
+
}
|
|
308
|
+
return [parsed];
|
|
309
|
+
}
|
|
310
|
+
function resolveBrowsersToCheck(requested) {
|
|
311
|
+
if (requested && requested.length > 0)
|
|
312
|
+
return requested;
|
|
313
|
+
const detected = (0, browser_config_1.detectInstalledBrowsers)();
|
|
314
|
+
if (detected.length > 0)
|
|
315
|
+
return detected;
|
|
316
|
+
return [browser_config_1.BrowserType.CHROME, browser_config_1.BrowserType.CHROMIUM];
|
|
317
|
+
}
|
|
318
|
+
function queryWindowsRegistryDefaultValue(registryKey) {
|
|
319
|
+
try {
|
|
320
|
+
const output = (0, child_process_1.execFileSync)('reg', ['query', registryKey, '/ve'], {
|
|
321
|
+
encoding: 'utf8',
|
|
322
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
323
|
+
timeout: 2500,
|
|
324
|
+
windowsHide: true,
|
|
325
|
+
});
|
|
326
|
+
const lines = output
|
|
327
|
+
.split(/\r?\n/)
|
|
328
|
+
.map((l) => l.trim())
|
|
329
|
+
.filter(Boolean);
|
|
330
|
+
for (const line of lines) {
|
|
331
|
+
const match = line.match(/\b(REG_SZ|REG_EXPAND_SZ)\b\s+(.*)$/i);
|
|
332
|
+
if (match === null || match === void 0 ? void 0 : match[2]) {
|
|
333
|
+
const valueType = match[1].toUpperCase();
|
|
334
|
+
return { value: match[2].trim(), valueType };
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return { error: 'No REG_SZ/REG_EXPAND_SZ default value found' };
|
|
338
|
+
}
|
|
339
|
+
catch (e) {
|
|
340
|
+
return { error: stringifyError(e) };
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
// ============================================================================
|
|
344
|
+
// Fix Attempts
|
|
345
|
+
// ============================================================================
|
|
346
|
+
async function attemptFixes(enabled, silent, distDir, targetBrowsers) {
|
|
347
|
+
if (!enabled)
|
|
348
|
+
return [];
|
|
349
|
+
const fixes = [];
|
|
350
|
+
const logDir = (0, utils_1.getLogDir)();
|
|
351
|
+
const nodePathFile = path_1.default.join(distDir, 'node_path.txt');
|
|
352
|
+
const withMutedConsole = async (fn) => {
|
|
353
|
+
if (!silent)
|
|
354
|
+
return await fn();
|
|
355
|
+
const originalLog = console.log;
|
|
356
|
+
const originalInfo = console.info;
|
|
357
|
+
const originalWarn = console.warn;
|
|
358
|
+
const originalError = console.error;
|
|
359
|
+
console.log = () => { };
|
|
360
|
+
console.info = () => { };
|
|
361
|
+
console.warn = () => { };
|
|
362
|
+
console.error = () => { };
|
|
363
|
+
try {
|
|
364
|
+
return await fn();
|
|
365
|
+
}
|
|
366
|
+
finally {
|
|
367
|
+
console.log = originalLog;
|
|
368
|
+
console.info = originalInfo;
|
|
369
|
+
console.warn = originalWarn;
|
|
370
|
+
console.error = originalError;
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
const attempt = async (id, description, action) => {
|
|
374
|
+
try {
|
|
375
|
+
await withMutedConsole(async () => {
|
|
376
|
+
await action();
|
|
377
|
+
});
|
|
378
|
+
fixes.push({ id, description, success: true });
|
|
379
|
+
}
|
|
380
|
+
catch (e) {
|
|
381
|
+
fixes.push({ id, description, success: false, error: stringifyError(e) });
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
await attempt('logs', 'Ensure logs directory exists', async () => {
|
|
385
|
+
fs_1.default.mkdirSync(logDir, { recursive: true });
|
|
386
|
+
});
|
|
387
|
+
await attempt('node_path', 'Write node_path.txt for run_host scripts', async () => {
|
|
388
|
+
fs_1.default.writeFileSync(nodePathFile, process.execPath, 'utf8');
|
|
389
|
+
});
|
|
390
|
+
await attempt('permissions', 'Fix execution permissions for native host files', async () => {
|
|
391
|
+
await (0, utils_1.ensureExecutionPermissions)();
|
|
392
|
+
});
|
|
393
|
+
await attempt('register', 'Re-register Native Messaging host (user-level)', async () => {
|
|
394
|
+
const ok = await (0, utils_1.tryRegisterUserLevelHost)(targetBrowsers);
|
|
395
|
+
if (!ok) {
|
|
396
|
+
throw new Error('User-level registration failed');
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
return fixes;
|
|
400
|
+
}
|
|
401
|
+
// ============================================================================
|
|
402
|
+
// JSON File Reading
|
|
403
|
+
// ============================================================================
|
|
404
|
+
function readJsonFile(filePath) {
|
|
405
|
+
try {
|
|
406
|
+
const raw = fs_1.default.readFileSync(filePath, 'utf8');
|
|
407
|
+
return { ok: true, value: JSON.parse(raw) };
|
|
408
|
+
}
|
|
409
|
+
catch (e) {
|
|
410
|
+
return { ok: false, error: stringifyError(e) };
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
function resolveFetch() {
|
|
414
|
+
var _a;
|
|
415
|
+
if (typeof globalThis.fetch === 'function') {
|
|
416
|
+
return globalThis.fetch.bind(globalThis);
|
|
417
|
+
}
|
|
418
|
+
try {
|
|
419
|
+
const mod = require('node-fetch');
|
|
420
|
+
return ((_a = mod.default) !== null && _a !== void 0 ? _a : mod);
|
|
421
|
+
}
|
|
422
|
+
catch (_b) {
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
async function checkConnectivity(url, timeoutMs) {
|
|
427
|
+
const fetchFn = resolveFetch();
|
|
428
|
+
if (!fetchFn) {
|
|
429
|
+
return { ok: false, error: 'fetch is not available (requires Node.js >=18 or node-fetch)' };
|
|
430
|
+
}
|
|
431
|
+
const controller = new AbortController();
|
|
432
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
433
|
+
// Prevent timeout from keeping the process alive
|
|
434
|
+
if (typeof timeout.unref === 'function') {
|
|
435
|
+
timeout.unref();
|
|
436
|
+
}
|
|
437
|
+
try {
|
|
438
|
+
const res = await fetchFn(url, { method: 'GET', signal: controller.signal });
|
|
439
|
+
return { ok: res.ok, status: res.status };
|
|
440
|
+
}
|
|
441
|
+
catch (e) {
|
|
442
|
+
const errMessage = e instanceof Error ? e.message : String(e);
|
|
443
|
+
const errName = e instanceof Error ? e.name : '';
|
|
444
|
+
if (errName === 'AbortError' || errMessage.toLowerCase().includes('abort')) {
|
|
445
|
+
return { ok: false, error: `Timeout after ${timeoutMs}ms` };
|
|
446
|
+
}
|
|
447
|
+
return { ok: false, error: errMessage };
|
|
448
|
+
}
|
|
449
|
+
finally {
|
|
450
|
+
clearTimeout(timeout);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
// ============================================================================
|
|
454
|
+
// Summary Computation
|
|
455
|
+
// ============================================================================
|
|
456
|
+
function computeSummary(checks) {
|
|
457
|
+
let ok = 0;
|
|
458
|
+
let warn = 0;
|
|
459
|
+
let error = 0;
|
|
460
|
+
for (const check of checks) {
|
|
461
|
+
if (check.status === 'ok')
|
|
462
|
+
ok++;
|
|
463
|
+
else if (check.status === 'warn')
|
|
464
|
+
warn++;
|
|
465
|
+
else
|
|
466
|
+
error++;
|
|
467
|
+
}
|
|
468
|
+
return { ok, warn, error };
|
|
469
|
+
}
|
|
470
|
+
function statusBadge(status) {
|
|
471
|
+
if (status === 'ok')
|
|
472
|
+
return (0, utils_1.colorText)('[OK]', 'green');
|
|
473
|
+
if (status === 'warn')
|
|
474
|
+
return (0, utils_1.colorText)('[WARN]', 'yellow');
|
|
475
|
+
return (0, utils_1.colorText)('[ERROR]', 'red');
|
|
476
|
+
}
|
|
477
|
+
// ============================================================================
|
|
478
|
+
// Main Doctor Function
|
|
479
|
+
// ============================================================================
|
|
480
|
+
/**
|
|
481
|
+
* Collect doctor report without outputting to console.
|
|
482
|
+
* Used by both runDoctor and report command.
|
|
483
|
+
*/
|
|
484
|
+
async function collectDoctorReport(options) {
|
|
485
|
+
const pkg = readPackageJson();
|
|
486
|
+
const distDir = resolveDistDir();
|
|
487
|
+
const rootDir = path_1.default.resolve(distDir, '..');
|
|
488
|
+
const packageName = typeof pkg.name === 'string' ? pkg.name : 'mcp-chrome-bridge';
|
|
489
|
+
const packageVersion = typeof pkg.version === 'string' ? pkg.version : 'unknown';
|
|
490
|
+
const commandInfo = getCommandInfo(pkg);
|
|
491
|
+
const targetBrowsers = resolveTargetBrowsers(options.browser);
|
|
492
|
+
const browsersToCheck = resolveBrowsersToCheck(targetBrowsers);
|
|
493
|
+
const wrapperScriptName = process.platform === 'win32' ? 'run_host.bat' : 'run_host.sh';
|
|
494
|
+
const wrapperPath = path_1.default.resolve(distDir, wrapperScriptName);
|
|
495
|
+
const nodeScriptPath = path_1.default.resolve(distDir, 'index.js');
|
|
496
|
+
const logDir = (0, utils_1.getLogDir)();
|
|
497
|
+
const stdioConfigPath = path_1.default.resolve(distDir, 'mcp', 'stdio-config.json');
|
|
498
|
+
// Run fixes if requested
|
|
499
|
+
const fixes = await attemptFixes(Boolean(options.fix), Boolean(options.json), distDir, targetBrowsers);
|
|
500
|
+
const checks = [];
|
|
501
|
+
const nextSteps = [];
|
|
502
|
+
// Check 1: Installation info
|
|
503
|
+
checks.push({
|
|
504
|
+
id: 'installation',
|
|
505
|
+
title: 'Installation',
|
|
506
|
+
status: 'ok',
|
|
507
|
+
message: `${packageName}@${packageVersion}, ${process.platform}-${process.arch}, node ${process.version}`,
|
|
508
|
+
details: {
|
|
509
|
+
packageRoot: rootDir,
|
|
510
|
+
distDir,
|
|
511
|
+
execPath: process.execPath,
|
|
512
|
+
aliases: commandInfo.aliases,
|
|
513
|
+
},
|
|
514
|
+
});
|
|
515
|
+
// Check 2: Host files
|
|
516
|
+
const missingHostFiles = [];
|
|
517
|
+
if (!fs_1.default.existsSync(wrapperPath))
|
|
518
|
+
missingHostFiles.push(wrapperPath);
|
|
519
|
+
if (!fs_1.default.existsSync(nodeScriptPath))
|
|
520
|
+
missingHostFiles.push(nodeScriptPath);
|
|
521
|
+
if (!fs_1.default.existsSync(stdioConfigPath))
|
|
522
|
+
missingHostFiles.push(stdioConfigPath);
|
|
523
|
+
if (missingHostFiles.length > 0) {
|
|
524
|
+
checks.push({
|
|
525
|
+
id: 'host.files',
|
|
526
|
+
title: 'Host files',
|
|
527
|
+
status: 'error',
|
|
528
|
+
message: `Missing required files (${missingHostFiles.length})`,
|
|
529
|
+
details: { missing: missingHostFiles },
|
|
530
|
+
});
|
|
531
|
+
nextSteps.push(`Reinstall: npm install -g ${constant_1.COMMAND_NAME}`);
|
|
532
|
+
}
|
|
533
|
+
else {
|
|
534
|
+
checks.push({
|
|
535
|
+
id: 'host.files',
|
|
536
|
+
title: 'Host files',
|
|
537
|
+
status: 'ok',
|
|
538
|
+
message: `Wrapper: ${wrapperPath}`,
|
|
539
|
+
details: { wrapperPath, nodeScriptPath, stdioConfigPath },
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
// Check 3: Permissions (Unix only)
|
|
543
|
+
if (process.platform !== 'win32' && fs_1.default.existsSync(wrapperPath)) {
|
|
544
|
+
const executable = canExecute(wrapperPath);
|
|
545
|
+
checks.push({
|
|
546
|
+
id: 'host.permissions',
|
|
547
|
+
title: 'Host permissions',
|
|
548
|
+
status: executable ? 'ok' : 'error',
|
|
549
|
+
message: executable ? 'run_host.sh is executable' : 'run_host.sh is not executable',
|
|
550
|
+
details: {
|
|
551
|
+
path: wrapperPath,
|
|
552
|
+
fix: executable
|
|
553
|
+
? undefined
|
|
554
|
+
: [`${constant_1.COMMAND_NAME} fix-permissions`, `chmod +x "${wrapperPath}"`],
|
|
555
|
+
},
|
|
556
|
+
});
|
|
557
|
+
if (!executable)
|
|
558
|
+
nextSteps.push(`${constant_1.COMMAND_NAME} fix-permissions`);
|
|
559
|
+
}
|
|
560
|
+
else {
|
|
561
|
+
checks.push({
|
|
562
|
+
id: 'host.permissions',
|
|
563
|
+
title: 'Host permissions',
|
|
564
|
+
status: 'ok',
|
|
565
|
+
message: process.platform === 'win32' ? 'Not applicable on Windows' : 'N/A',
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
// Check 4: Node resolution
|
|
569
|
+
const nodeResolution = resolveNodeCandidate(distDir);
|
|
570
|
+
if (nodeResolution.nodePath) {
|
|
571
|
+
try {
|
|
572
|
+
nodeResolution.version = (0, child_process_1.execFileSync)(nodeResolution.nodePath, ['-v'], {
|
|
573
|
+
encoding: 'utf8',
|
|
574
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
575
|
+
timeout: 2500,
|
|
576
|
+
windowsHide: true,
|
|
577
|
+
}).trim();
|
|
578
|
+
}
|
|
579
|
+
catch (e) {
|
|
580
|
+
nodeResolution.versionError = stringifyError(e);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
// Parse Node version and check if it meets minimum requirement
|
|
584
|
+
const nodeMajorVersion = parseNodeMajorVersion(nodeResolution.version || '');
|
|
585
|
+
const nodeVersionTooOld = nodeMajorVersion !== null && nodeMajorVersion < MIN_NODE_MAJOR_VERSION;
|
|
586
|
+
const nodePathWarn = Boolean(nodeResolution.nodePath) &&
|
|
587
|
+
(!nodeResolution.nodePathFile.exists || nodeResolution.nodePathFile.valid === false) &&
|
|
588
|
+
!process.env.CHROME_MCP_NODE_PATH;
|
|
589
|
+
// Determine node check status: error if not found or version too old, warn if path issue
|
|
590
|
+
let nodeStatus = 'ok';
|
|
591
|
+
let nodeMessage;
|
|
592
|
+
let nodeFix;
|
|
593
|
+
if (!nodeResolution.nodePath) {
|
|
594
|
+
nodeStatus = 'error';
|
|
595
|
+
nodeMessage = 'Node.js executable not found by wrapper search order';
|
|
596
|
+
nodeFix = [
|
|
597
|
+
`${constant_1.COMMAND_NAME} doctor --fix`,
|
|
598
|
+
`Or set CHROME_MCP_NODE_PATH to an absolute node path`,
|
|
599
|
+
];
|
|
600
|
+
nextSteps.push(`${constant_1.COMMAND_NAME} doctor --fix`);
|
|
601
|
+
}
|
|
602
|
+
else if (nodeResolution.versionError) {
|
|
603
|
+
nodeStatus = 'error';
|
|
604
|
+
nodeMessage = `Found ${nodeResolution.source}: ${nodeResolution.nodePath} but failed to run "node -v" (${nodeResolution.versionError})`;
|
|
605
|
+
nodeFix = [
|
|
606
|
+
`Verify the executable: "${nodeResolution.nodePath}" -v`,
|
|
607
|
+
`Reinstall/repair Node.js`,
|
|
608
|
+
];
|
|
609
|
+
nextSteps.push(`Verify Node.js: "${nodeResolution.nodePath}" -v`);
|
|
610
|
+
}
|
|
611
|
+
else if (nodeVersionTooOld) {
|
|
612
|
+
nodeStatus = 'error';
|
|
613
|
+
nodeMessage = `Node.js ${nodeResolution.version} is too old (requires >= ${MIN_NODE_MAJOR_VERSION}.0.0)`;
|
|
614
|
+
nodeFix = [`Upgrade Node.js to version ${MIN_NODE_MAJOR_VERSION} or higher`];
|
|
615
|
+
nextSteps.push(`Upgrade Node.js to version ${MIN_NODE_MAJOR_VERSION}+`);
|
|
616
|
+
}
|
|
617
|
+
else if (nodePathWarn) {
|
|
618
|
+
nodeStatus = 'warn';
|
|
619
|
+
nodeMessage = `Using ${nodeResolution.source}: ${nodeResolution.nodePath}${nodeResolution.version ? ` (${nodeResolution.version})` : ''}`;
|
|
620
|
+
nodeFix = [
|
|
621
|
+
`${constant_1.COMMAND_NAME} doctor --fix`,
|
|
622
|
+
`Or set CHROME_MCP_NODE_PATH to an absolute node path`,
|
|
623
|
+
];
|
|
624
|
+
}
|
|
625
|
+
else {
|
|
626
|
+
nodeStatus = 'ok';
|
|
627
|
+
nodeMessage = `Using ${nodeResolution.source}: ${nodeResolution.nodePath}${nodeResolution.version ? ` (${nodeResolution.version})` : ''}`;
|
|
628
|
+
}
|
|
629
|
+
checks.push({
|
|
630
|
+
id: 'node',
|
|
631
|
+
title: 'Node executable',
|
|
632
|
+
status: nodeStatus,
|
|
633
|
+
message: nodeMessage,
|
|
634
|
+
details: {
|
|
635
|
+
resolved: nodeResolution.nodePath
|
|
636
|
+
? {
|
|
637
|
+
source: nodeResolution.source,
|
|
638
|
+
path: nodeResolution.nodePath,
|
|
639
|
+
version: nodeResolution.version,
|
|
640
|
+
versionError: nodeResolution.versionError,
|
|
641
|
+
majorVersion: nodeMajorVersion,
|
|
642
|
+
}
|
|
643
|
+
: undefined,
|
|
644
|
+
nodePathFile: nodeResolution.nodePathFile,
|
|
645
|
+
minRequired: `>=${MIN_NODE_MAJOR_VERSION}.0.0`,
|
|
646
|
+
fix: nodeFix,
|
|
647
|
+
},
|
|
648
|
+
});
|
|
649
|
+
// Check 5: Manifest checks per browser
|
|
650
|
+
const expectedOrigin = `chrome-extension://${constant_1.EXTENSION_ID}/`;
|
|
651
|
+
for (const browser of browsersToCheck) {
|
|
652
|
+
const config = (0, browser_config_1.getBrowserConfig)(browser);
|
|
653
|
+
const candidates = [config.userManifestPath, config.systemManifestPath];
|
|
654
|
+
const found = candidates.find((p) => fs_1.default.existsSync(p));
|
|
655
|
+
if (!found) {
|
|
656
|
+
checks.push({
|
|
657
|
+
id: `manifest.${browser}`,
|
|
658
|
+
title: `${config.displayName} manifest`,
|
|
659
|
+
status: 'error',
|
|
660
|
+
message: 'Manifest not found',
|
|
661
|
+
details: {
|
|
662
|
+
expected: candidates,
|
|
663
|
+
fix: [
|
|
664
|
+
`${constant_1.COMMAND_NAME} register --browser ${browser}`,
|
|
665
|
+
`${constant_1.COMMAND_NAME} register --detect`,
|
|
666
|
+
],
|
|
667
|
+
},
|
|
668
|
+
});
|
|
669
|
+
nextSteps.push(`${constant_1.COMMAND_NAME} register --detect`);
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
const parsed = readJsonFile(found);
|
|
673
|
+
if (!parsed.ok) {
|
|
674
|
+
checks.push({
|
|
675
|
+
id: `manifest.${browser}`,
|
|
676
|
+
title: `${config.displayName} manifest`,
|
|
677
|
+
status: 'error',
|
|
678
|
+
message: `Failed to parse manifest: ${parsed.error}`,
|
|
679
|
+
details: { path: found, fix: [`${constant_1.COMMAND_NAME} register --browser ${browser}`] },
|
|
680
|
+
});
|
|
681
|
+
nextSteps.push(`${constant_1.COMMAND_NAME} register --browser ${browser}`);
|
|
682
|
+
continue;
|
|
683
|
+
}
|
|
684
|
+
const manifest = parsed.value;
|
|
685
|
+
const issues = [];
|
|
686
|
+
if (manifest.name !== constant_1.HOST_NAME)
|
|
687
|
+
issues.push(`name != ${constant_1.HOST_NAME}`);
|
|
688
|
+
if (manifest.type !== 'stdio')
|
|
689
|
+
issues.push(`type != stdio`);
|
|
690
|
+
if (typeof manifest.path !== 'string')
|
|
691
|
+
issues.push('path is missing');
|
|
692
|
+
if (typeof manifest.path === 'string') {
|
|
693
|
+
const actual = normalizeComparablePath(manifest.path);
|
|
694
|
+
const expected = normalizeComparablePath(wrapperPath);
|
|
695
|
+
if (actual !== expected)
|
|
696
|
+
issues.push('path does not match installed wrapper');
|
|
697
|
+
if (!fs_1.default.existsSync(manifest.path))
|
|
698
|
+
issues.push('path target does not exist');
|
|
699
|
+
}
|
|
700
|
+
const allowedOrigins = manifest.allowed_origins;
|
|
701
|
+
if (!Array.isArray(allowedOrigins) || !allowedOrigins.includes(expectedOrigin)) {
|
|
702
|
+
issues.push(`allowed_origins missing ${expectedOrigin}`);
|
|
703
|
+
}
|
|
704
|
+
checks.push({
|
|
705
|
+
id: `manifest.${browser}`,
|
|
706
|
+
title: `${config.displayName} manifest`,
|
|
707
|
+
status: issues.length === 0 ? 'ok' : 'error',
|
|
708
|
+
message: issues.length === 0 ? found : `Invalid manifest (${issues.join('; ')})`,
|
|
709
|
+
details: {
|
|
710
|
+
path: found,
|
|
711
|
+
expectedWrapperPath: wrapperPath,
|
|
712
|
+
expectedOrigin,
|
|
713
|
+
fix: issues.length === 0 ? undefined : [`${constant_1.COMMAND_NAME} register --browser ${browser}`],
|
|
714
|
+
},
|
|
715
|
+
});
|
|
716
|
+
if (issues.length > 0)
|
|
717
|
+
nextSteps.push(`${constant_1.COMMAND_NAME} register --browser ${browser}`);
|
|
718
|
+
}
|
|
719
|
+
// Check 6: Windows registry (Windows only)
|
|
720
|
+
if (process.platform === 'win32') {
|
|
721
|
+
for (const browser of browsersToCheck) {
|
|
722
|
+
const config = (0, browser_config_1.getBrowserConfig)(browser);
|
|
723
|
+
const keySpecs = [
|
|
724
|
+
config.registryKey ? { key: config.registryKey, expected: config.userManifestPath } : null,
|
|
725
|
+
config.systemRegistryKey
|
|
726
|
+
? { key: config.systemRegistryKey, expected: config.systemManifestPath }
|
|
727
|
+
: null,
|
|
728
|
+
].filter(Boolean);
|
|
729
|
+
if (keySpecs.length === 0)
|
|
730
|
+
continue;
|
|
731
|
+
let anyValue = false;
|
|
732
|
+
let anyExistingTarget = false;
|
|
733
|
+
let anyMissingTarget = false;
|
|
734
|
+
let anyMismatch = false;
|
|
735
|
+
const results = [];
|
|
736
|
+
for (const spec of keySpecs) {
|
|
737
|
+
const res = queryWindowsRegistryDefaultValue(spec.key);
|
|
738
|
+
if (!res.value) {
|
|
739
|
+
results.push({ key: spec.key, expected: spec.expected, error: res.error });
|
|
740
|
+
continue;
|
|
741
|
+
}
|
|
742
|
+
anyValue = true;
|
|
743
|
+
// Expand environment variables for REG_EXPAND_SZ values
|
|
744
|
+
const expandedValue = expandWindowsEnvVars(stripOuterQuotes(res.value));
|
|
745
|
+
const exists = fs_1.default.existsSync(expandedValue);
|
|
746
|
+
const matchesExpected = normalizeComparablePath(expandedValue) === normalizeComparablePath(spec.expected);
|
|
747
|
+
if (exists) {
|
|
748
|
+
anyExistingTarget = true;
|
|
749
|
+
if (!matchesExpected)
|
|
750
|
+
anyMismatch = true;
|
|
751
|
+
}
|
|
752
|
+
else {
|
|
753
|
+
anyMissingTarget = true;
|
|
754
|
+
}
|
|
755
|
+
results.push({
|
|
756
|
+
key: spec.key,
|
|
757
|
+
expected: spec.expected,
|
|
758
|
+
value: res.value,
|
|
759
|
+
valueType: res.valueType,
|
|
760
|
+
expandedValue: expandedValue !== res.value ? expandedValue : undefined,
|
|
761
|
+
exists,
|
|
762
|
+
matchesExpected,
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
let status = 'error';
|
|
766
|
+
let message = 'Registry entry not found';
|
|
767
|
+
if (!anyValue) {
|
|
768
|
+
status = 'error';
|
|
769
|
+
message = 'Registry entry not found';
|
|
770
|
+
}
|
|
771
|
+
else if (!anyExistingTarget) {
|
|
772
|
+
status = 'error';
|
|
773
|
+
message = 'Registry entry points to missing manifest';
|
|
774
|
+
}
|
|
775
|
+
else if (anyMissingTarget || anyMismatch) {
|
|
776
|
+
status = 'warn';
|
|
777
|
+
message = 'Registry entry found but inconsistent';
|
|
778
|
+
}
|
|
779
|
+
else {
|
|
780
|
+
status = 'ok';
|
|
781
|
+
message = 'Registry entry points to manifest';
|
|
782
|
+
}
|
|
783
|
+
checks.push({
|
|
784
|
+
id: `registry.${browser}`,
|
|
785
|
+
title: `${config.displayName} registry`,
|
|
786
|
+
status,
|
|
787
|
+
message,
|
|
788
|
+
details: {
|
|
789
|
+
keys: keySpecs.map((s) => s.key),
|
|
790
|
+
results,
|
|
791
|
+
fix: status === 'ok' ? undefined : [`${constant_1.COMMAND_NAME} register --browser ${browser}`],
|
|
792
|
+
},
|
|
793
|
+
});
|
|
794
|
+
if (status !== 'ok')
|
|
795
|
+
nextSteps.push(`${constant_1.COMMAND_NAME} register --browser ${browser}`);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
// Check 7: Port configuration
|
|
799
|
+
if (fs_1.default.existsSync(stdioConfigPath)) {
|
|
800
|
+
const cfg = readJsonFile(stdioConfigPath);
|
|
801
|
+
if (!cfg.ok) {
|
|
802
|
+
checks.push({
|
|
803
|
+
id: 'port.config',
|
|
804
|
+
title: 'Port config',
|
|
805
|
+
status: 'error',
|
|
806
|
+
message: `Failed to parse stdio-config.json: ${cfg.error}`,
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
else {
|
|
810
|
+
try {
|
|
811
|
+
const configValue = cfg.value;
|
|
812
|
+
const url = new URL(configValue.url);
|
|
813
|
+
const port = Number(url.port);
|
|
814
|
+
const portOk = port === EXPECTED_PORT;
|
|
815
|
+
checks.push({
|
|
816
|
+
id: 'port.config',
|
|
817
|
+
title: 'Port config',
|
|
818
|
+
status: portOk ? 'ok' : 'error',
|
|
819
|
+
message: configValue.url,
|
|
820
|
+
details: {
|
|
821
|
+
expectedPort: EXPECTED_PORT,
|
|
822
|
+
actualPort: port,
|
|
823
|
+
fix: portOk ? undefined : [`${constant_1.COMMAND_NAME} update-port ${EXPECTED_PORT}`],
|
|
824
|
+
},
|
|
825
|
+
});
|
|
826
|
+
if (!portOk)
|
|
827
|
+
nextSteps.push(`${constant_1.COMMAND_NAME} update-port ${EXPECTED_PORT}`);
|
|
828
|
+
// Check constant consistency
|
|
829
|
+
const nativePortOk = constant_2.NATIVE_SERVER_PORT === EXPECTED_PORT;
|
|
830
|
+
checks.push({
|
|
831
|
+
id: 'port.constant',
|
|
832
|
+
title: 'Port constant',
|
|
833
|
+
status: nativePortOk ? 'ok' : 'warn',
|
|
834
|
+
message: `NATIVE_SERVER_PORT=${constant_2.NATIVE_SERVER_PORT}`,
|
|
835
|
+
details: { expectedPort: EXPECTED_PORT },
|
|
836
|
+
});
|
|
837
|
+
// Connectivity check
|
|
838
|
+
const pingUrl = new URL('/ping', url);
|
|
839
|
+
const ping = await checkConnectivity(pingUrl.toString(), 1500);
|
|
840
|
+
checks.push({
|
|
841
|
+
id: 'connectivity',
|
|
842
|
+
title: 'Connectivity',
|
|
843
|
+
status: ping.ok ? 'ok' : 'warn',
|
|
844
|
+
message: ping.ok
|
|
845
|
+
? `GET ${pingUrl} -> ${ping.status}`
|
|
846
|
+
: `GET ${pingUrl} failed (${ping.error || 'unknown error'})`,
|
|
847
|
+
details: {
|
|
848
|
+
hint: 'If the server is not running, click "Connect" in the extension and retry.',
|
|
849
|
+
},
|
|
850
|
+
});
|
|
851
|
+
if (!ping.ok)
|
|
852
|
+
nextSteps.push('Click "Connect" in the extension, then re-run doctor');
|
|
853
|
+
}
|
|
854
|
+
catch (e) {
|
|
855
|
+
checks.push({
|
|
856
|
+
id: 'port.config',
|
|
857
|
+
title: 'Port config',
|
|
858
|
+
status: 'error',
|
|
859
|
+
message: `Invalid URL in stdio-config.json: ${stringifyError(e)}`,
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
// Check 8: Logs directory
|
|
865
|
+
checks.push({
|
|
866
|
+
id: 'logs',
|
|
867
|
+
title: 'Logs',
|
|
868
|
+
status: fs_1.default.existsSync(logDir) ? 'ok' : 'warn',
|
|
869
|
+
message: logDir,
|
|
870
|
+
details: {
|
|
871
|
+
hint: 'Wrapper logs are created when Chrome launches the native host.',
|
|
872
|
+
},
|
|
873
|
+
});
|
|
874
|
+
// Compute summary
|
|
875
|
+
const summary = computeSummary(checks);
|
|
876
|
+
const ok = summary.error === 0;
|
|
877
|
+
const report = {
|
|
878
|
+
schemaVersion: SCHEMA_VERSION,
|
|
879
|
+
timestamp: new Date().toISOString(),
|
|
880
|
+
ok,
|
|
881
|
+
summary,
|
|
882
|
+
environment: {
|
|
883
|
+
platform: process.platform,
|
|
884
|
+
arch: process.arch,
|
|
885
|
+
node: { version: process.version, execPath: process.execPath },
|
|
886
|
+
package: { name: packageName, version: packageVersion, rootDir, distDir },
|
|
887
|
+
command: { canonical: commandInfo.canonical, aliases: commandInfo.aliases },
|
|
888
|
+
nativeHost: { hostName: constant_1.HOST_NAME, expectedPort: EXPECTED_PORT },
|
|
889
|
+
},
|
|
890
|
+
fixes,
|
|
891
|
+
checks,
|
|
892
|
+
nextSteps: Array.from(new Set(nextSteps)).slice(0, 10),
|
|
893
|
+
};
|
|
894
|
+
return report;
|
|
895
|
+
}
|
|
896
|
+
/**
|
|
897
|
+
* Run doctor command with console output.
|
|
898
|
+
*/
|
|
899
|
+
async function runDoctor(options) {
|
|
900
|
+
var _a;
|
|
901
|
+
const report = await collectDoctorReport(options);
|
|
902
|
+
const packageVersion = report.environment.package.version;
|
|
903
|
+
// Output
|
|
904
|
+
if (options.json) {
|
|
905
|
+
process.stdout.write(JSON.stringify(report, null, 2) + '\n');
|
|
906
|
+
}
|
|
907
|
+
else {
|
|
908
|
+
console.log(`${constant_1.COMMAND_NAME} doctor v${packageVersion}\n`);
|
|
909
|
+
for (const check of report.checks) {
|
|
910
|
+
console.log(`${statusBadge(check.status)} ${check.title}: ${check.message}`);
|
|
911
|
+
const fix = (_a = check.details) === null || _a === void 0 ? void 0 : _a.fix;
|
|
912
|
+
if (check.status !== 'ok' && fix && fix.length > 0) {
|
|
913
|
+
console.log(` Fix: ${fix[0]}`);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
if (report.fixes.length > 0) {
|
|
917
|
+
console.log('\nFix attempts:');
|
|
918
|
+
for (const f of report.fixes) {
|
|
919
|
+
const badge = f.success ? (0, utils_1.colorText)('[OK]', 'green') : (0, utils_1.colorText)('[ERROR]', 'red');
|
|
920
|
+
console.log(`${badge} ${f.description}${f.success ? '' : ` (${f.error})`}`);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
if (report.nextSteps.length > 0) {
|
|
924
|
+
console.log('\nNext steps:');
|
|
925
|
+
report.nextSteps.forEach((s, i) => console.log(` ${i + 1}. ${s}`));
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
return report.ok ? 0 : 1;
|
|
929
|
+
}
|
|
930
|
+
//# sourceMappingURL=doctor.js.map
|