@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.
Files changed (129) hide show
  1. package/README.md +183 -0
  2. package/dist/README.md +25 -0
  3. package/dist/agent/attachment-service.d.ts +83 -0
  4. package/dist/agent/attachment-service.js +370 -0
  5. package/dist/agent/attachment-service.js.map +1 -0
  6. package/dist/agent/ccr-detector.d.ts +59 -0
  7. package/dist/agent/ccr-detector.js +311 -0
  8. package/dist/agent/ccr-detector.js.map +1 -0
  9. package/dist/agent/chat-service.d.ts +50 -0
  10. package/dist/agent/chat-service.js +439 -0
  11. package/dist/agent/chat-service.js.map +1 -0
  12. package/dist/agent/db/client.d.ts +26 -0
  13. package/dist/agent/db/client.js +244 -0
  14. package/dist/agent/db/client.js.map +1 -0
  15. package/dist/agent/db/index.d.ts +5 -0
  16. package/dist/agent/db/index.js +22 -0
  17. package/dist/agent/db/index.js.map +1 -0
  18. package/dist/agent/db/schema.d.ts +711 -0
  19. package/dist/agent/db/schema.js +121 -0
  20. package/dist/agent/db/schema.js.map +1 -0
  21. package/dist/agent/directory-picker.d.ts +11 -0
  22. package/dist/agent/directory-picker.js +149 -0
  23. package/dist/agent/directory-picker.js.map +1 -0
  24. package/dist/agent/engines/claude.d.ts +79 -0
  25. package/dist/agent/engines/claude.js +1338 -0
  26. package/dist/agent/engines/claude.js.map +1 -0
  27. package/dist/agent/engines/codex.d.ts +48 -0
  28. package/dist/agent/engines/codex.js +822 -0
  29. package/dist/agent/engines/codex.js.map +1 -0
  30. package/dist/agent/engines/types.d.ts +133 -0
  31. package/dist/agent/engines/types.js +3 -0
  32. package/dist/agent/engines/types.js.map +1 -0
  33. package/dist/agent/message-service.d.ts +56 -0
  34. package/dist/agent/message-service.js +198 -0
  35. package/dist/agent/message-service.js.map +1 -0
  36. package/dist/agent/open-project.d.ts +25 -0
  37. package/dist/agent/open-project.js +469 -0
  38. package/dist/agent/open-project.js.map +1 -0
  39. package/dist/agent/project-service.d.ts +49 -0
  40. package/dist/agent/project-service.js +254 -0
  41. package/dist/agent/project-service.js.map +1 -0
  42. package/dist/agent/project-types.d.ts +27 -0
  43. package/dist/agent/project-types.js +3 -0
  44. package/dist/agent/project-types.js.map +1 -0
  45. package/dist/agent/session-service.d.ts +198 -0
  46. package/dist/agent/session-service.js +292 -0
  47. package/dist/agent/session-service.js.map +1 -0
  48. package/dist/agent/storage.d.ts +27 -0
  49. package/dist/agent/storage.js +73 -0
  50. package/dist/agent/storage.js.map +1 -0
  51. package/dist/agent/stream-manager.d.ts +42 -0
  52. package/dist/agent/stream-manager.js +243 -0
  53. package/dist/agent/stream-manager.js.map +1 -0
  54. package/dist/agent/tool-bridge.d.ts +44 -0
  55. package/dist/agent/tool-bridge.js +50 -0
  56. package/dist/agent/tool-bridge.js.map +1 -0
  57. package/dist/agent/types.d.ts +6 -0
  58. package/dist/agent/types.js +3 -0
  59. package/dist/agent/types.js.map +1 -0
  60. package/dist/cli.d.ts +2 -0
  61. package/dist/cli.js +224 -0
  62. package/dist/cli.js.map +1 -0
  63. package/dist/constant/index.d.ts +60 -0
  64. package/dist/constant/index.js +80 -0
  65. package/dist/constant/index.js.map +1 -0
  66. package/dist/file-handler.d.ts +41 -0
  67. package/dist/file-handler.js +295 -0
  68. package/dist/file-handler.js.map +1 -0
  69. package/dist/index.d.ts +2 -0
  70. package/dist/index.js +35 -0
  71. package/dist/index.js.map +1 -0
  72. package/dist/mcp/mcp-server-stdio.d.ts +72 -0
  73. package/dist/mcp/mcp-server-stdio.js +143 -0
  74. package/dist/mcp/mcp-server-stdio.js.map +1 -0
  75. package/dist/mcp/mcp-server.d.ts +36 -0
  76. package/dist/mcp/mcp-server.js +26 -0
  77. package/dist/mcp/mcp-server.js.map +1 -0
  78. package/dist/mcp/register-tools.d.ts +2 -0
  79. package/dist/mcp/register-tools.js +148 -0
  80. package/dist/mcp/register-tools.js.map +1 -0
  81. package/dist/mcp/stdio-config.json +3 -0
  82. package/dist/native-messaging-host.d.ts +42 -0
  83. package/dist/native-messaging-host.js +312 -0
  84. package/dist/native-messaging-host.js.map +1 -0
  85. package/dist/run_host.bat +194 -0
  86. package/dist/run_host.sh +264 -0
  87. package/dist/scripts/browser-config.d.ts +28 -0
  88. package/dist/scripts/browser-config.js +229 -0
  89. package/dist/scripts/browser-config.js.map +1 -0
  90. package/dist/scripts/build.d.ts +1 -0
  91. package/dist/scripts/build.js +126 -0
  92. package/dist/scripts/build.js.map +1 -0
  93. package/dist/scripts/constant.d.ts +4 -0
  94. package/dist/scripts/constant.js +8 -0
  95. package/dist/scripts/constant.js.map +1 -0
  96. package/dist/scripts/doctor.d.ts +70 -0
  97. package/dist/scripts/doctor.js +930 -0
  98. package/dist/scripts/doctor.js.map +1 -0
  99. package/dist/scripts/postinstall.d.ts +2 -0
  100. package/dist/scripts/postinstall.js +246 -0
  101. package/dist/scripts/postinstall.js.map +1 -0
  102. package/dist/scripts/register-dev.d.ts +1 -0
  103. package/dist/scripts/register-dev.js +5 -0
  104. package/dist/scripts/register-dev.js.map +1 -0
  105. package/dist/scripts/register.d.ts +2 -0
  106. package/dist/scripts/register.js +28 -0
  107. package/dist/scripts/register.js.map +1 -0
  108. package/dist/scripts/report.d.ts +96 -0
  109. package/dist/scripts/report.js +686 -0
  110. package/dist/scripts/report.js.map +1 -0
  111. package/dist/scripts/utils.d.ts +64 -0
  112. package/dist/scripts/utils.js +443 -0
  113. package/dist/scripts/utils.js.map +1 -0
  114. package/dist/server/index.d.ts +35 -0
  115. package/dist/server/index.js +312 -0
  116. package/dist/server/index.js.map +1 -0
  117. package/dist/server/routes/agent.d.ts +21 -0
  118. package/dist/server/routes/agent.js +971 -0
  119. package/dist/server/routes/agent.js.map +1 -0
  120. package/dist/server/routes/index.d.ts +4 -0
  121. package/dist/server/routes/index.js +9 -0
  122. package/dist/server/routes/index.js.map +1 -0
  123. package/dist/trace-analyzer.d.ts +14 -0
  124. package/dist/trace-analyzer.js +113 -0
  125. package/dist/trace-analyzer.js.map +1 -0
  126. package/dist/util/logger.d.ts +1 -0
  127. package/dist/util/logger.js +43 -0
  128. package/dist/util/logger.js.map +1 -0
  129. 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