browser-devtools-mcp 0.1.4 → 0.1.6

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 (56) hide show
  1. package/README.md +554 -7
  2. package/SECURITY.md +136 -0
  3. package/dist/cli.js +1847 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/config.js +5 -1
  6. package/dist/config.js.map +1 -1
  7. package/dist/context.js +7 -7
  8. package/dist/context.js.map +1 -1
  9. package/dist/daemon-server.js +338 -0
  10. package/dist/daemon-server.js.map +1 -0
  11. package/dist/index.js +3 -260
  12. package/dist/index.js.map +1 -1
  13. package/dist/mcp-server.js +401 -0
  14. package/dist/mcp-server.js.map +1 -0
  15. package/dist/tools/a11y/take-aria-snapshot.js.map +1 -1
  16. package/dist/tools/a11y/take-ax-tree-snapshot.js.map +1 -1
  17. package/dist/tools/content/get-as-html.js.map +1 -1
  18. package/dist/tools/content/get-as-text.js.map +1 -1
  19. package/dist/tools/content/save-as-pdf.js.map +1 -1
  20. package/dist/tools/content/take-screenshot.js.map +1 -1
  21. package/dist/tools/figma/compare-page-with-design.js.map +1 -1
  22. package/dist/tools/interaction/click.js.map +1 -1
  23. package/dist/tools/interaction/drag.js.map +1 -1
  24. package/dist/tools/interaction/fill.js.map +1 -1
  25. package/dist/tools/interaction/hover.js.map +1 -1
  26. package/dist/tools/interaction/press-key.js.map +1 -1
  27. package/dist/tools/interaction/resize-viewport.js.map +1 -1
  28. package/dist/tools/interaction/resize-window.js.map +1 -1
  29. package/dist/tools/interaction/scroll.js.map +1 -1
  30. package/dist/tools/interaction/select.js.map +1 -1
  31. package/dist/tools/navigation/go-back.js.map +1 -1
  32. package/dist/tools/navigation/go-forward.js.map +1 -1
  33. package/dist/tools/navigation/go-to.js.map +1 -1
  34. package/dist/tools/navigation/reload.js.map +1 -1
  35. package/dist/tools/o11y/get-console-messages.js.map +1 -1
  36. package/dist/tools/o11y/get-http-requests.js.map +1 -1
  37. package/dist/tools/o11y/get-trace-id.js.map +1 -1
  38. package/dist/tools/o11y/get-web-vitals.js.map +1 -1
  39. package/dist/tools/o11y/new-trace-id.js.map +1 -1
  40. package/dist/tools/o11y/set-trace-id.js.map +1 -1
  41. package/dist/tools/react/get-component-for-element.js.map +1 -1
  42. package/dist/tools/react/get-element-for-component.js.map +1 -1
  43. package/dist/tools/run/js-in-browser.js.map +1 -1
  44. package/dist/tools/run/js-in-sandbox.js.map +1 -1
  45. package/dist/tools/stub/clear.js.map +1 -1
  46. package/dist/tools/stub/intercept-http-request.js.map +1 -1
  47. package/dist/tools/stub/list.js.map +1 -1
  48. package/dist/tools/stub/mock-http-response.js.map +1 -1
  49. package/dist/tools/sync/wait-for-network-idle.js.map +1 -1
  50. package/dist/tools/tool-executor.js +1 -1
  51. package/dist/tools/tool-executor.js.map +1 -1
  52. package/dist/utils/cli-utils.js +253 -0
  53. package/dist/utils/cli-utils.js.map +1 -0
  54. package/package.json +6 -3
  55. package/dist/server.js +0 -154
  56. package/dist/server.js.map +0 -1
package/dist/cli.js ADDED
@@ -0,0 +1,1847 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ const child_process_1 = require("child_process");
38
+ const path = __importStar(require("path"));
39
+ const readline = __importStar(require("readline"));
40
+ const commander_1 = require("commander");
41
+ const config = __importStar(require("./config"));
42
+ const logger = __importStar(require("./logger"));
43
+ const tools_1 = require("./tools");
44
+ const cli_utils_1 = require("./utils/cli-utils");
45
+ const DEFAULT_TIMEOUT = 30000;
46
+ let verboseEnabled = false;
47
+ function _verbose(message, data) {
48
+ if (verboseEnabled) {
49
+ const timestamp = new Date().toISOString();
50
+ if (data !== undefined) {
51
+ console.error(`[${timestamp}] [DEBUG] ${message}`, data);
52
+ }
53
+ else {
54
+ console.error(`[${timestamp}] [DEBUG] ${message}`);
55
+ }
56
+ }
57
+ }
58
+ async function _isDaemonRunning(port) {
59
+ _verbose(`Checking if daemon is running on port ${port}`);
60
+ try {
61
+ const response = await fetch(`http://localhost:${port}/health`, {
62
+ method: 'GET',
63
+ signal: AbortSignal.timeout(3000),
64
+ });
65
+ if (response.ok) {
66
+ const data = await response.json();
67
+ const isRunning = data.status === 'ok';
68
+ _verbose(`Daemon health check result: ${isRunning ? 'running' : 'not running'}`);
69
+ return isRunning;
70
+ }
71
+ _verbose(`Daemon health check failed: HTTP ${response.status}`);
72
+ return false;
73
+ }
74
+ catch (err) {
75
+ _verbose(`Daemon health check error: ${err.message}`);
76
+ return false;
77
+ }
78
+ }
79
+ function _buildDaemonEnv(opts) {
80
+ const env = { ...process.env };
81
+ // Set browser options as environment variables
82
+ if (opts.headless !== undefined) {
83
+ env['BROWSER_HEADLESS_ENABLE'] = String(opts.headless);
84
+ }
85
+ if (opts.persistent !== undefined) {
86
+ env['BROWSER_PERSISTENT_ENABLE'] = String(opts.persistent);
87
+ }
88
+ if (opts.userDataDir !== undefined) {
89
+ env['BROWSER_PERSISTENT_USER_DATA_DIR'] = opts.userDataDir;
90
+ }
91
+ if (opts.useSystemBrowser !== undefined) {
92
+ env['BROWSER_USE_INSTALLED_ON_SYSTEM'] = String(opts.useSystemBrowser);
93
+ }
94
+ if (opts.browserPath !== undefined) {
95
+ env['BROWSER_EXECUTABLE_PATH'] = opts.browserPath;
96
+ }
97
+ return env;
98
+ }
99
+ function _startDaemonDetached(opts) {
100
+ const daemonServerPath = path.join(__dirname, 'daemon-server.js');
101
+ const env = _buildDaemonEnv(opts);
102
+ _verbose(`Starting daemon server from: ${daemonServerPath}`);
103
+ _verbose(`Daemon port: ${opts.port}`);
104
+ _verbose(`Environment variables:`, {
105
+ BROWSER_HEADLESS_ENABLE: env['BROWSER_HEADLESS_ENABLE'],
106
+ BROWSER_PERSISTENT_ENABLE: env['BROWSER_PERSISTENT_ENABLE'],
107
+ BROWSER_USE_INSTALLED_ON_SYSTEM: env['BROWSER_USE_INSTALLED_ON_SYSTEM'],
108
+ });
109
+ const child = (0, child_process_1.spawn)(process.execPath, [daemonServerPath, '--port', String(opts.port)], {
110
+ detached: true,
111
+ stdio: 'ignore',
112
+ env,
113
+ });
114
+ child.unref();
115
+ _verbose(`Daemon process spawned with PID: ${child.pid}`);
116
+ if (!opts.quiet) {
117
+ logger.info(`Started daemon server as detached process (PID: ${child.pid})`);
118
+ }
119
+ }
120
+ async function _ensureDaemonRunning(opts) {
121
+ const isRunning = await _isDaemonRunning(opts.port);
122
+ if (!isRunning) {
123
+ if (!opts.quiet) {
124
+ logger.info(`Daemon server is not running on port ${opts.port}, starting...`);
125
+ }
126
+ _startDaemonDetached(opts);
127
+ // Wait for daemon to be ready
128
+ const maxRetries = 10;
129
+ const retryDelay = 500;
130
+ _verbose(`Waiting for daemon to be ready (max ${maxRetries} retries, ${retryDelay}ms delay)`);
131
+ for (let i = 0; i < maxRetries; i++) {
132
+ await new Promise((resolve) => setTimeout(resolve, retryDelay));
133
+ _verbose(`Retry ${i + 1}/${maxRetries}: checking daemon status...`);
134
+ if (await _isDaemonRunning(opts.port)) {
135
+ _verbose('Daemon is now ready');
136
+ if (!opts.quiet) {
137
+ logger.info('Daemon server is ready');
138
+ }
139
+ return;
140
+ }
141
+ }
142
+ throw new Error(`Daemon server failed to start within ${(maxRetries * retryDelay) / 1000} seconds`);
143
+ }
144
+ else {
145
+ _verbose('Daemon is already running');
146
+ }
147
+ }
148
+ async function _stopDaemon(port, timeout) {
149
+ try {
150
+ const response = await fetch(`http://localhost:${port}/shutdown`, {
151
+ method: 'POST',
152
+ signal: AbortSignal.timeout(timeout),
153
+ });
154
+ return response.ok;
155
+ }
156
+ catch {
157
+ return false;
158
+ }
159
+ }
160
+ async function _callTool(port, toolName, toolInput, sessionId, timeout) {
161
+ const headers = {
162
+ 'Content-Type': 'application/json',
163
+ };
164
+ if (sessionId) {
165
+ headers['session-id'] = sessionId;
166
+ }
167
+ const request = {
168
+ toolName,
169
+ toolInput,
170
+ };
171
+ _verbose(`Calling tool: ${toolName}`);
172
+ _verbose(`Tool input:`, toolInput);
173
+ _verbose(`Session ID: ${sessionId || '(default)'}`);
174
+ _verbose(`Timeout: ${timeout || 'none'}`);
175
+ const startTime = Date.now();
176
+ const response = await fetch(`http://localhost:${port}/call`, {
177
+ method: 'POST',
178
+ headers,
179
+ body: JSON.stringify(request),
180
+ signal: timeout ? AbortSignal.timeout(timeout) : undefined,
181
+ });
182
+ const duration = Date.now() - startTime;
183
+ _verbose(`Tool call completed in ${duration}ms, status: ${response.status}`);
184
+ if (!response.ok) {
185
+ const errorBody = await response.json().catch(() => ({}));
186
+ _verbose(`Tool call error:`, errorBody);
187
+ throw new Error(errorBody?.error?.message ||
188
+ `HTTP ${response.status}: ${response.statusText}`);
189
+ }
190
+ const result = (await response.json());
191
+ _verbose(`Tool call result:`, result.toolError ? { error: result.toolError } : { success: true });
192
+ return result;
193
+ }
194
+ async function _deleteSession(port, sessionId, timeout) {
195
+ try {
196
+ const response = await fetch(`http://localhost:${port}/session`, {
197
+ method: 'DELETE',
198
+ headers: {
199
+ 'session-id': sessionId,
200
+ },
201
+ signal: AbortSignal.timeout(timeout),
202
+ });
203
+ return response.ok;
204
+ }
205
+ catch {
206
+ return false;
207
+ }
208
+ }
209
+ async function _getDaemonInfo(port, timeout) {
210
+ try {
211
+ const response = await fetch(`http://localhost:${port}/info`, {
212
+ method: 'GET',
213
+ signal: AbortSignal.timeout(timeout),
214
+ });
215
+ if (response.ok) {
216
+ return (await response.json());
217
+ }
218
+ return null;
219
+ }
220
+ catch {
221
+ return null;
222
+ }
223
+ }
224
+ async function _listSessions(port, timeout) {
225
+ try {
226
+ const response = await fetch(`http://localhost:${port}/sessions`, {
227
+ method: 'GET',
228
+ signal: AbortSignal.timeout(timeout),
229
+ });
230
+ if (response.ok) {
231
+ return (await response.json());
232
+ }
233
+ return null;
234
+ }
235
+ catch {
236
+ return null;
237
+ }
238
+ }
239
+ async function _getSessionInfo(port, sessionId, timeout) {
240
+ try {
241
+ const response = await fetch(`http://localhost:${port}/session`, {
242
+ method: 'GET',
243
+ headers: {
244
+ 'session-id': sessionId,
245
+ },
246
+ signal: AbortSignal.timeout(timeout),
247
+ });
248
+ if (response.ok) {
249
+ return (await response.json());
250
+ }
251
+ return null;
252
+ }
253
+ catch {
254
+ return null;
255
+ }
256
+ }
257
+ function _formatUptime(seconds) {
258
+ const days = Math.floor(seconds / 86400);
259
+ const hours = Math.floor((seconds % 86400) / 3600);
260
+ const minutes = Math.floor((seconds % 3600) / 60);
261
+ const secs = seconds % 60;
262
+ const parts = [];
263
+ if (days > 0) {
264
+ parts.push(`${days}d`);
265
+ }
266
+ if (hours > 0) {
267
+ parts.push(`${hours}h`);
268
+ }
269
+ if (minutes > 0) {
270
+ parts.push(`${minutes}m`);
271
+ }
272
+ parts.push(`${secs}s`);
273
+ return parts.join(' ');
274
+ }
275
+ function _formatTimestamp(timestamp) {
276
+ return new Date(timestamp).toISOString();
277
+ }
278
+ function _getZodTypeName(schema) {
279
+ const typeName = schema._def.typeName;
280
+ if (typeName === 'ZodOptional' || typeName === 'ZodNullable') {
281
+ return _getZodTypeName(schema._def.innerType);
282
+ }
283
+ if (typeName === 'ZodDefault') {
284
+ return _getZodTypeName(schema._def.innerType);
285
+ }
286
+ if (typeName === 'ZodArray') {
287
+ return `${_getZodTypeName(schema._def.type)}[]`;
288
+ }
289
+ if (typeName === 'ZodEnum') {
290
+ return schema._def.values.join(' | ');
291
+ }
292
+ if (typeName === 'ZodLiteral') {
293
+ return JSON.stringify(schema._def.value);
294
+ }
295
+ if (typeName === 'ZodUnion') {
296
+ return schema._def.options
297
+ .map((opt) => _getZodTypeName(opt))
298
+ .join(' | ');
299
+ }
300
+ const typeMap = {
301
+ ZodString: 'string',
302
+ ZodNumber: 'number',
303
+ ZodBoolean: 'boolean',
304
+ ZodObject: 'object',
305
+ ZodRecord: 'Record<string, any>',
306
+ ZodAny: 'any',
307
+ };
308
+ return typeMap[typeName] || typeName.replace('Zod', '').toLowerCase();
309
+ }
310
+ function _getZodDescription(schema) {
311
+ if (schema._def.description) {
312
+ return schema._def.description;
313
+ }
314
+ if (schema._def.typeName === 'ZodOptional' ||
315
+ schema._def.typeName === 'ZodNullable' ||
316
+ schema._def.typeName === 'ZodDefault') {
317
+ return _getZodDescription(schema._def.innerType);
318
+ }
319
+ return undefined;
320
+ }
321
+ function _isZodOptional(schema) {
322
+ const typeName = schema._def.typeName;
323
+ return typeName === 'ZodOptional' || typeName === 'ZodNullable';
324
+ }
325
+ function _hasZodDefault(schema) {
326
+ if (schema._def.typeName === 'ZodDefault') {
327
+ return true;
328
+ }
329
+ if (schema._def.typeName === 'ZodOptional' ||
330
+ schema._def.typeName === 'ZodNullable') {
331
+ return _hasZodDefault(schema._def.innerType);
332
+ }
333
+ return false;
334
+ }
335
+ function _getZodDefault(schema) {
336
+ if (schema._def.typeName === 'ZodDefault') {
337
+ return schema._def.defaultValue();
338
+ }
339
+ if (schema._def.typeName === 'ZodOptional' ||
340
+ schema._def.typeName === 'ZodNullable') {
341
+ return _getZodDefault(schema._def.innerType);
342
+ }
343
+ return undefined;
344
+ }
345
+ function _formatOutput(output, indent = 0) {
346
+ const prefix = ' '.repeat(indent);
347
+ if (output === null || output === undefined) {
348
+ return `${prefix}(empty)`;
349
+ }
350
+ if (typeof output === 'string') {
351
+ return output
352
+ .split('\n')
353
+ .map((line) => `${prefix}${line}`)
354
+ .join('\n');
355
+ }
356
+ if (typeof output === 'number' || typeof output === 'boolean') {
357
+ return `${prefix}${output}`;
358
+ }
359
+ if (Array.isArray(output)) {
360
+ if (output.length === 0) {
361
+ return `${prefix}[]`;
362
+ }
363
+ return output
364
+ .map((item) => _formatOutput(item, indent))
365
+ .join('\n');
366
+ }
367
+ if (typeof output === 'object') {
368
+ const lines = [];
369
+ for (const [key, value] of Object.entries(output)) {
370
+ if (value === undefined) {
371
+ continue;
372
+ }
373
+ if (typeof value === 'object' &&
374
+ value !== null &&
375
+ !Array.isArray(value)) {
376
+ lines.push(`${prefix}${key}:`);
377
+ lines.push(_formatOutput(value, indent + 1));
378
+ }
379
+ else if (Array.isArray(value)) {
380
+ lines.push(`${prefix}${key}:`);
381
+ lines.push(_formatOutput(value, indent + 1));
382
+ }
383
+ else {
384
+ lines.push(`${prefix}${key}: ${value}`);
385
+ }
386
+ }
387
+ return lines.join('\n');
388
+ }
389
+ return `${prefix}${String(output)}`;
390
+ }
391
+ function _printOutput(data, json, isError = false) {
392
+ const output = json ? JSON.stringify(data, null, 2) : String(data);
393
+ if (isError) {
394
+ console.error(output);
395
+ }
396
+ else {
397
+ console.log(output);
398
+ }
399
+ }
400
+ // Helper function to add global options to a Command
401
+ function _addGlobalOptions(cmd) {
402
+ return (cmd
403
+ .addOption(new commander_1.Option('--port <number>', 'Daemon server port')
404
+ .argParser((value) => {
405
+ const n = Number(value);
406
+ if (!Number.isInteger(n) || n < 1 || n > 65535) {
407
+ throw new Error('Port must be an integer between 1 and 65535');
408
+ }
409
+ return n;
410
+ })
411
+ .default(config.DAEMON_PORT))
412
+ .addOption(new commander_1.Option('--session-id <string>', 'Session ID for maintaining browser state across commands'))
413
+ .addOption(new commander_1.Option('--json', 'Output results as JSON'))
414
+ .addOption(new commander_1.Option('--quiet', 'Suppress log messages, only show output'))
415
+ .addOption(new commander_1.Option('--verbose', 'Enable verbose/debug output'))
416
+ .addOption(new commander_1.Option('--timeout <ms>', 'Timeout for operations in milliseconds')
417
+ .argParser((value) => {
418
+ const n = Number(value);
419
+ if (!Number.isFinite(n) || n < 0) {
420
+ throw new Error('Timeout must be a positive number');
421
+ }
422
+ return n;
423
+ })
424
+ .default(DEFAULT_TIMEOUT))
425
+ // Browser options
426
+ .addOption(new commander_1.Option('--headless', 'Run browser in headless mode (no visible window)').default(config.BROWSER_HEADLESS_ENABLE))
427
+ .addOption(new commander_1.Option('--no-headless', 'Run browser in headful mode (visible window)'))
428
+ .addOption(new commander_1.Option('--persistent', 'Use persistent browser context (preserves cookies, localStorage)').default(config.BROWSER_PERSISTENT_ENABLE))
429
+ .addOption(new commander_1.Option('--no-persistent', 'Use ephemeral browser context (cleared on session end)'))
430
+ .addOption(new commander_1.Option('--user-data-dir <path>', 'Directory for persistent browser context user data').default(config.BROWSER_PERSISTENT_USER_DATA_DIR))
431
+ .addOption(new commander_1.Option('--use-system-browser', 'Use system-installed Chrome instead of bundled browser').default(config.BROWSER_USE_INSTALLED_ON_SYSTEM))
432
+ .addOption(new commander_1.Option('--browser-path <path>', 'Custom browser executable path')));
433
+ }
434
+ async function main() {
435
+ const program = _addGlobalOptions(new commander_1.Command('browser-devtools-cli')
436
+ .description('Browser DevTools MCP CLI')
437
+ .version(require('../package.json').version));
438
+ // Enable verbose mode if flag is set
439
+ program.hook('preAction', (thisCommand) => {
440
+ const opts = thisCommand.opts();
441
+ if (opts.verbose) {
442
+ verboseEnabled = true;
443
+ _verbose('Verbose mode enabled');
444
+ _verbose('CLI version:', require('../package.json').version);
445
+ _verbose('Node version:', process.version);
446
+ _verbose('Platform:', process.platform);
447
+ }
448
+ });
449
+ // ==================== daemon subcommand ====================
450
+ const daemonCmd = new commander_1.Command('daemon').description('Manage the daemon server');
451
+ daemonCmd
452
+ .command('start')
453
+ .description('Start the daemon server')
454
+ .action(async () => {
455
+ const opts = program.opts();
456
+ const isRunning = await _isDaemonRunning(opts.port);
457
+ if (isRunning) {
458
+ if (opts.json) {
459
+ _printOutput({
460
+ status: 'already_running',
461
+ port: opts.port,
462
+ }, true);
463
+ }
464
+ else if (!opts.quiet) {
465
+ console.log(`Daemon server is already running on port ${opts.port}`);
466
+ }
467
+ return;
468
+ }
469
+ _startDaemonDetached(opts);
470
+ // Wait for daemon to be ready
471
+ const maxRetries = 10;
472
+ const retryDelay = 500;
473
+ for (let i = 0; i < maxRetries; i++) {
474
+ await new Promise((resolve) => setTimeout(resolve, retryDelay));
475
+ if (await _isDaemonRunning(opts.port)) {
476
+ if (opts.json) {
477
+ _printOutput({
478
+ status: 'started',
479
+ port: opts.port,
480
+ }, true);
481
+ }
482
+ else if (!opts.quiet) {
483
+ console.log(`Daemon server started on port ${opts.port}`);
484
+ }
485
+ return;
486
+ }
487
+ }
488
+ if (opts.json) {
489
+ _printOutput({
490
+ status: 'failed',
491
+ error: 'Daemon server failed to start',
492
+ }, true, true);
493
+ }
494
+ else {
495
+ console.error('Failed to start daemon server');
496
+ }
497
+ process.exit(1);
498
+ });
499
+ daemonCmd
500
+ .command('stop')
501
+ .description('Stop the daemon server')
502
+ .action(async () => {
503
+ const opts = program.opts();
504
+ const isRunning = await _isDaemonRunning(opts.port);
505
+ if (!isRunning) {
506
+ if (opts.json) {
507
+ _printOutput({
508
+ status: 'not_running',
509
+ port: opts.port,
510
+ }, true);
511
+ }
512
+ else if (!opts.quiet) {
513
+ console.log(`Daemon server is not running on port ${opts.port}`);
514
+ }
515
+ return;
516
+ }
517
+ const stopped = await _stopDaemon(opts.port, opts.timeout ?? DEFAULT_TIMEOUT);
518
+ if (stopped) {
519
+ if (opts.json) {
520
+ _printOutput({
521
+ status: 'stopped',
522
+ port: opts.port,
523
+ }, true);
524
+ }
525
+ else if (!opts.quiet) {
526
+ console.log(`Daemon server stopped on port ${opts.port}`);
527
+ }
528
+ }
529
+ else {
530
+ if (opts.json) {
531
+ _printOutput({
532
+ status: 'failed',
533
+ error: 'Failed to stop daemon server',
534
+ }, true, true);
535
+ }
536
+ else {
537
+ console.error('Failed to stop daemon server');
538
+ }
539
+ process.exit(1);
540
+ }
541
+ });
542
+ daemonCmd
543
+ .command('restart')
544
+ .description('Restart the daemon server (stop + start)')
545
+ .action(async () => {
546
+ const opts = program.opts();
547
+ const wasRunning = await _isDaemonRunning(opts.port);
548
+ // Stop if running
549
+ if (wasRunning) {
550
+ _verbose('Stopping daemon server...');
551
+ const stopped = await _stopDaemon(opts.port, opts.timeout ?? DEFAULT_TIMEOUT);
552
+ if (!stopped) {
553
+ if (opts.json) {
554
+ _printOutput({
555
+ status: 'failed',
556
+ error: 'Failed to stop daemon server',
557
+ }, true, true);
558
+ }
559
+ else {
560
+ console.error('Failed to stop daemon server');
561
+ }
562
+ process.exit(1);
563
+ }
564
+ // Wait a moment for the port to be released
565
+ _verbose('Waiting for port to be released...');
566
+ await new Promise((resolve) => setTimeout(resolve, 1000));
567
+ }
568
+ // Start daemon
569
+ _verbose('Starting daemon server...');
570
+ _startDaemonDetached(opts);
571
+ // Wait for daemon to be ready
572
+ const maxRetries = 10;
573
+ const retryDelay = 500;
574
+ for (let i = 0; i < maxRetries; i++) {
575
+ await new Promise((resolve) => setTimeout(resolve, retryDelay));
576
+ if (await _isDaemonRunning(opts.port)) {
577
+ if (opts.json) {
578
+ _printOutput({
579
+ status: 'restarted',
580
+ port: opts.port,
581
+ }, true);
582
+ }
583
+ else if (!opts.quiet) {
584
+ console.log(`Daemon server ${wasRunning ? 'restarted' : 'started'} on port ${opts.port}`);
585
+ }
586
+ return;
587
+ }
588
+ }
589
+ if (opts.json) {
590
+ _printOutput({
591
+ status: 'failed',
592
+ error: 'Daemon server failed to start',
593
+ }, true, true);
594
+ }
595
+ else {
596
+ console.error('Failed to start daemon server');
597
+ }
598
+ process.exit(1);
599
+ });
600
+ daemonCmd
601
+ .command('status')
602
+ .description('Check daemon server status')
603
+ .action(async () => {
604
+ const opts = program.opts();
605
+ const isRunning = await _isDaemonRunning(opts.port);
606
+ if (opts.json) {
607
+ _printOutput({
608
+ status: isRunning ? 'running' : 'stopped',
609
+ port: opts.port,
610
+ }, true);
611
+ }
612
+ else {
613
+ if (isRunning) {
614
+ console.log(`Daemon server is running on port ${opts.port}`);
615
+ }
616
+ else {
617
+ console.log(`Daemon server is not running on port ${opts.port}`);
618
+ }
619
+ }
620
+ });
621
+ daemonCmd
622
+ .command('info')
623
+ .description('Get detailed daemon server information')
624
+ .action(async () => {
625
+ const opts = program.opts();
626
+ const isRunning = await _isDaemonRunning(opts.port);
627
+ if (!isRunning) {
628
+ if (opts.json) {
629
+ _printOutput({
630
+ status: 'not_running',
631
+ port: opts.port,
632
+ }, true, true);
633
+ }
634
+ else {
635
+ console.error(`Daemon server is not running on port ${opts.port}`);
636
+ }
637
+ process.exit(1);
638
+ }
639
+ const info = await _getDaemonInfo(opts.port, opts.timeout ?? DEFAULT_TIMEOUT);
640
+ if (info) {
641
+ if (opts.json) {
642
+ _printOutput(info, true);
643
+ }
644
+ else {
645
+ console.log(`Daemon Server Information:`);
646
+ console.log(` Version: ${info.version}`);
647
+ console.log(` Port: ${info.port}`);
648
+ console.log(` Uptime: ${_formatUptime(info.uptime)}`);
649
+ console.log(` Sessions: ${info.sessionCount}`);
650
+ }
651
+ }
652
+ else {
653
+ if (opts.json) {
654
+ _printOutput({
655
+ status: 'error',
656
+ error: 'Failed to get daemon info',
657
+ }, true, true);
658
+ }
659
+ else {
660
+ console.error('Failed to get daemon info');
661
+ }
662
+ process.exit(1);
663
+ }
664
+ });
665
+ program.addCommand(daemonCmd);
666
+ // ==================== session subcommand ====================
667
+ const sessionCmd = new commander_1.Command('session').description('Manage browser sessions');
668
+ sessionCmd
669
+ .command('list')
670
+ .description('List all active sessions')
671
+ .action(async () => {
672
+ const opts = program.opts();
673
+ try {
674
+ await _ensureDaemonRunning(opts);
675
+ const result = await _listSessions(opts.port, opts.timeout ?? DEFAULT_TIMEOUT);
676
+ if (result) {
677
+ if (opts.json) {
678
+ _printOutput(result, true);
679
+ }
680
+ else {
681
+ if (result.sessions.length === 0) {
682
+ console.log('No active sessions');
683
+ }
684
+ else {
685
+ console.log(`Active Sessions (${result.sessions.length}):`);
686
+ for (const session of result.sessions) {
687
+ console.log(` ${session.id}`);
688
+ console.log(` Created: ${_formatTimestamp(session.createdAt)}`);
689
+ console.log(` Last Active: ${_formatTimestamp(session.lastActiveAt)}`);
690
+ console.log(` Idle: ${_formatUptime(session.idleSeconds)}`);
691
+ }
692
+ }
693
+ }
694
+ }
695
+ else {
696
+ if (opts.json) {
697
+ _printOutput({
698
+ status: 'error',
699
+ error: 'Failed to list sessions',
700
+ }, true, true);
701
+ }
702
+ else {
703
+ console.error('Failed to list sessions');
704
+ }
705
+ process.exit(1);
706
+ }
707
+ }
708
+ catch (err) {
709
+ if (opts.json) {
710
+ _printOutput({
711
+ status: 'error',
712
+ error: err.message,
713
+ }, true, true);
714
+ }
715
+ else {
716
+ console.error(`Error: ${err.message}`);
717
+ }
718
+ process.exit(1);
719
+ }
720
+ });
721
+ sessionCmd
722
+ .command('info <session-id>')
723
+ .description('Get information about a specific session')
724
+ .action(async (sessionId) => {
725
+ const opts = program.opts();
726
+ try {
727
+ await _ensureDaemonRunning(opts);
728
+ const info = await _getSessionInfo(opts.port, sessionId, opts.timeout ?? DEFAULT_TIMEOUT);
729
+ if (info) {
730
+ if (opts.json) {
731
+ _printOutput(info, true);
732
+ }
733
+ else {
734
+ console.log(`Session: ${info.id}`);
735
+ console.log(` Created: ${_formatTimestamp(info.createdAt)}`);
736
+ console.log(` Last Active: ${_formatTimestamp(info.lastActiveAt)}`);
737
+ console.log(` Idle: ${_formatUptime(info.idleSeconds)}`);
738
+ }
739
+ }
740
+ else {
741
+ if (opts.json) {
742
+ _printOutput({
743
+ status: 'not_found',
744
+ sessionId: sessionId,
745
+ }, true, true);
746
+ }
747
+ else {
748
+ console.error(`Session '${sessionId}' not found`);
749
+ }
750
+ process.exit(1);
751
+ }
752
+ }
753
+ catch (err) {
754
+ if (opts.json) {
755
+ _printOutput({
756
+ status: 'error',
757
+ error: err.message,
758
+ }, true, true);
759
+ }
760
+ else {
761
+ console.error(`Error: ${err.message}`);
762
+ }
763
+ process.exit(1);
764
+ }
765
+ });
766
+ sessionCmd
767
+ .command('delete <session-id>')
768
+ .description('Delete a specific session')
769
+ .action(async (sessionId) => {
770
+ const opts = program.opts();
771
+ try {
772
+ await _ensureDaemonRunning(opts);
773
+ const deleted = await _deleteSession(opts.port, sessionId, opts.timeout ?? DEFAULT_TIMEOUT);
774
+ if (deleted) {
775
+ if (opts.json) {
776
+ _printOutput({
777
+ status: 'deleted',
778
+ sessionId: sessionId,
779
+ }, true);
780
+ }
781
+ else if (!opts.quiet) {
782
+ console.log(`Session '${sessionId}' deleted`);
783
+ }
784
+ }
785
+ else {
786
+ if (opts.json) {
787
+ _printOutput({
788
+ status: 'not_found',
789
+ sessionId: sessionId,
790
+ }, true, true);
791
+ }
792
+ else {
793
+ console.error(`Session '${sessionId}' not found or already deleted`);
794
+ }
795
+ process.exit(1);
796
+ }
797
+ }
798
+ catch (err) {
799
+ if (opts.json) {
800
+ _printOutput({
801
+ status: 'error',
802
+ error: err.message,
803
+ }, true, true);
804
+ }
805
+ else {
806
+ console.error(`Error: ${err.message}`);
807
+ }
808
+ process.exit(1);
809
+ }
810
+ });
811
+ program.addCommand(sessionCmd);
812
+ // ==================== tools subcommand ====================
813
+ const toolsCmd = new commander_1.Command('tools').description('List and inspect available tools');
814
+ toolsCmd
815
+ .command('list')
816
+ .description('List all available tools')
817
+ .option('--domain <domain>', 'Filter by domain (e.g., navigation, content)')
818
+ .action((cmdOpts) => {
819
+ const opts = program.opts();
820
+ // Group tools by domain
821
+ const toolsByDomain = new Map();
822
+ for (const tool of tools_1.tools) {
823
+ const parts = tool.name().split('_');
824
+ const domain = parts[0];
825
+ if (cmdOpts.domain && domain !== cmdOpts.domain) {
826
+ continue;
827
+ }
828
+ if (!toolsByDomain.has(domain)) {
829
+ toolsByDomain.set(domain, []);
830
+ }
831
+ toolsByDomain.get(domain).push(tool);
832
+ }
833
+ if (opts.json) {
834
+ const result = [];
835
+ for (const [domain, domainTools] of toolsByDomain) {
836
+ result.push({
837
+ domain,
838
+ tools: domainTools.map((t) => ({
839
+ name: t.name(),
840
+ description: t.description().trim().split('\n')[0],
841
+ })),
842
+ });
843
+ }
844
+ _printOutput(result, true);
845
+ }
846
+ else {
847
+ if (toolsByDomain.size === 0) {
848
+ console.log('No tools found');
849
+ return;
850
+ }
851
+ console.log(`Available Tools (${tools_1.tools.length} total):\n`);
852
+ for (const [domain, domainTools] of toolsByDomain) {
853
+ console.log(` ${domain}:`);
854
+ for (const tool of domainTools) {
855
+ const name = tool.name().split('_')[1] || tool.name();
856
+ const desc = tool
857
+ .description()
858
+ .trim()
859
+ .split('\n')[0];
860
+ console.log(` ${name.padEnd(30)} ${desc}`);
861
+ }
862
+ console.log();
863
+ }
864
+ }
865
+ });
866
+ toolsCmd
867
+ .command('info <tool-name>')
868
+ .description('Get detailed information about a specific tool')
869
+ .action((toolName) => {
870
+ const opts = program.opts();
871
+ // Find tool by name (support both full name and short name)
872
+ let tool = tools_1.tools.find((t) => t.name() === toolName);
873
+ // Try to find by partial name (e.g., "go-to" -> "navigation_go-to")
874
+ if (!tool) {
875
+ tool = tools_1.tools.find((t) => {
876
+ const parts = t.name().split('_');
877
+ return parts[1] === toolName;
878
+ });
879
+ }
880
+ if (!tool) {
881
+ if (opts.json) {
882
+ _printOutput({
883
+ status: 'not_found',
884
+ toolName: toolName,
885
+ }, true, true);
886
+ }
887
+ else {
888
+ console.error(`Tool '${toolName}' not found`);
889
+ }
890
+ process.exit(1);
891
+ }
892
+ const inputSchema = tool.inputSchema();
893
+ const params = [];
894
+ for (const [key, schema] of Object.entries(inputSchema)) {
895
+ params.push({
896
+ name: key,
897
+ type: _getZodTypeName(schema),
898
+ required: !_isZodOptional(schema),
899
+ description: _getZodDescription(schema),
900
+ default: _hasZodDefault(schema)
901
+ ? _getZodDefault(schema)
902
+ : undefined,
903
+ });
904
+ }
905
+ if (opts.json) {
906
+ _printOutput({
907
+ name: tool.name(),
908
+ description: tool.description().trim(),
909
+ parameters: params,
910
+ }, true);
911
+ }
912
+ else {
913
+ const nameParts = tool.name().split('_');
914
+ console.log(`Tool: ${tool.name()}`);
915
+ console.log(`Domain: ${nameParts[0]}`);
916
+ console.log(`\nDescription:`);
917
+ console.log(tool
918
+ .description()
919
+ .trim()
920
+ .split('\n')
921
+ .map((line) => ` ${line}`)
922
+ .join('\n'));
923
+ console.log(`\nParameters:`);
924
+ if (params.length === 0) {
925
+ console.log(' (none)');
926
+ }
927
+ else {
928
+ for (const param of params) {
929
+ const reqStr = param.required
930
+ ? '(required)'
931
+ : '(optional)';
932
+ console.log(` --${param.name} <${param.type}> ${reqStr}`);
933
+ if (param.description) {
934
+ console.log(` ${param.description}`);
935
+ }
936
+ if (param.default !== undefined) {
937
+ console.log(` Default: ${JSON.stringify(param.default)}`);
938
+ }
939
+ }
940
+ }
941
+ console.log(`\nUsage:`);
942
+ console.log(` browser-devtools-cli ${nameParts[0]} ${nameParts[1] || tool.name()} [options]`);
943
+ }
944
+ });
945
+ toolsCmd
946
+ .command('search <query>')
947
+ .description('Search tools by name or description')
948
+ .action((query) => {
949
+ const opts = program.opts();
950
+ const lowerQuery = query.toLowerCase();
951
+ // Search tools by name and description
952
+ const matchingTools = tools_1.tools.filter((tool) => {
953
+ const name = tool.name().toLowerCase();
954
+ const description = tool.description().toLowerCase();
955
+ return (name.includes(lowerQuery) ||
956
+ description.includes(lowerQuery));
957
+ });
958
+ if (opts.json) {
959
+ const result = matchingTools.map((t) => {
960
+ const parts = t.name().split('_');
961
+ return {
962
+ name: t.name(),
963
+ domain: parts[0],
964
+ description: t.description().trim().split('\n')[0],
965
+ };
966
+ });
967
+ _printOutput(result, true);
968
+ }
969
+ else {
970
+ if (matchingTools.length === 0) {
971
+ console.log(`No tools found matching "${query}"`);
972
+ return;
973
+ }
974
+ console.log(`Tools matching "${query}" (${matchingTools.length} found):\n`);
975
+ for (const tool of matchingTools) {
976
+ const parts = tool.name().split('_');
977
+ const domain = parts[0];
978
+ const name = parts[1] || tool.name();
979
+ const desc = tool
980
+ .description()
981
+ .trim()
982
+ .split('\n')[0];
983
+ console.log(` ${domain}/${name}`);
984
+ console.log(` ${desc}\n`);
985
+ }
986
+ }
987
+ });
988
+ program.addCommand(toolsCmd);
989
+ // ==================== config subcommand ====================
990
+ const configCmd = new commander_1.Command('config')
991
+ .description('Show current configuration')
992
+ .action(() => {
993
+ const opts = program.opts();
994
+ const configValues = {
995
+ // Daemon
996
+ daemon: {
997
+ port: config.DAEMON_PORT,
998
+ sessionIdleSeconds: config.DAEMON_SESSION_IDLE_SECONDS,
999
+ sessionIdleCheckSeconds: config.DAEMON_SESSION_IDLE_CHECK_SECONDS,
1000
+ },
1001
+ // Browser
1002
+ browser: {
1003
+ headless: config.BROWSER_HEADLESS_ENABLE,
1004
+ persistent: config.BROWSER_PERSISTENT_ENABLE,
1005
+ userDataDir: config.BROWSER_PERSISTENT_USER_DATA_DIR,
1006
+ useSystemBrowser: config.BROWSER_USE_INSTALLED_ON_SYSTEM,
1007
+ executablePath: config.BROWSER_EXECUTABLE_PATH,
1008
+ },
1009
+ // OpenTelemetry
1010
+ otel: {
1011
+ enabled: config.OTEL_ENABLE,
1012
+ serviceName: config.OTEL_SERVICE_NAME,
1013
+ serviceVersion: config.OTEL_SERVICE_VERSION,
1014
+ exporterType: config.OTEL_EXPORTER_TYPE,
1015
+ exporterHttpUrl: config.OTEL_EXPORTER_HTTP_URL,
1016
+ },
1017
+ // AWS
1018
+ aws: {
1019
+ region: config.AWS_REGION,
1020
+ profile: config.AWS_PROFILE,
1021
+ },
1022
+ // Bedrock
1023
+ bedrock: {
1024
+ enabled: config.AMAZON_BEDROCK_ENABLE,
1025
+ imageEmbedModelId: config.AMAZON_BEDROCK_IMAGE_EMBED_MODEL_ID,
1026
+ textEmbedModelId: config.AMAZON_BEDROCK_TEXT_EMBED_MODEL_ID,
1027
+ visionModelId: config.AMAZON_BEDROCK_VISION_MODEL_ID,
1028
+ },
1029
+ // Figma
1030
+ figma: {
1031
+ accessToken: config.FIGMA_ACCESS_TOKEN ? '***' : undefined,
1032
+ apiBaseUrl: config.FIGMA_API_BASE_URL,
1033
+ },
1034
+ };
1035
+ if (opts.json) {
1036
+ _printOutput(configValues, true);
1037
+ }
1038
+ else {
1039
+ console.log('Current Configuration:\n');
1040
+ console.log(' Daemon:');
1041
+ console.log(` Port: ${configValues.daemon.port}`);
1042
+ console.log(` Session Idle (sec): ${configValues.daemon.sessionIdleSeconds}`);
1043
+ console.log(` Idle Check Interval: ${configValues.daemon.sessionIdleCheckSeconds}`);
1044
+ console.log('\n Browser:');
1045
+ console.log(` Headless: ${configValues.browser.headless}`);
1046
+ console.log(` Persistent: ${configValues.browser.persistent}`);
1047
+ console.log(` User Data Dir: ${configValues.browser.userDataDir || '(default)'}`);
1048
+ console.log(` Use System Browser: ${configValues.browser.useSystemBrowser}`);
1049
+ console.log(` Executable Path: ${configValues.browser.executablePath || '(bundled)'}`);
1050
+ console.log('\n OpenTelemetry:');
1051
+ console.log(` Enabled: ${configValues.otel.enabled}`);
1052
+ console.log(` Service Name: ${configValues.otel.serviceName}`);
1053
+ console.log(` Service Version: ${configValues.otel.serviceVersion || '(not set)'}`);
1054
+ console.log(` Exporter Type: ${configValues.otel.exporterType}`);
1055
+ console.log(` Exporter HTTP URL: ${configValues.otel.exporterHttpUrl || '(not set)'}`);
1056
+ console.log('\n AWS:');
1057
+ console.log(` Region: ${configValues.aws.region || '(not set)'}`);
1058
+ console.log(` Profile: ${configValues.aws.profile || '(not set)'}`);
1059
+ console.log('\n Bedrock:');
1060
+ console.log(` Enabled: ${configValues.bedrock.enabled}`);
1061
+ console.log(` Image Embed Model ID: ${configValues.bedrock.imageEmbedModelId || '(not set)'}`);
1062
+ console.log(` Text Embed Model ID: ${configValues.bedrock.textEmbedModelId || '(not set)'}`);
1063
+ console.log(` Vision Model ID: ${configValues.bedrock.visionModelId || '(not set)'}`);
1064
+ console.log('\n Figma:');
1065
+ console.log(` Access Token: ${configValues.figma.accessToken || '(not set)'}`);
1066
+ console.log(` API Base URL: ${configValues.figma.apiBaseUrl}`);
1067
+ }
1068
+ });
1069
+ program.addCommand(configCmd);
1070
+ // ==================== completion subcommand ====================
1071
+ const completionCmd = new commander_1.Command('completion').description('Generate shell completion scripts');
1072
+ completionCmd
1073
+ .command('bash')
1074
+ .description('Generate bash completion script')
1075
+ .action(() => {
1076
+ const script = `
1077
+ # Browser DevTools CLI bash completion
1078
+ _browser_devtools_cli_completions() {
1079
+ local cur="\${COMP_WORDS[COMP_CWORD]}"
1080
+ local prev="\${COMP_WORDS[COMP_CWORD-1]}"
1081
+
1082
+ # Main commands
1083
+ local commands="daemon session tools config completion interactive navigation content interaction a11y accessibility o11y react run stub sync figma"
1084
+
1085
+ # Daemon subcommands
1086
+ local daemon_cmds="start stop restart status info"
1087
+
1088
+ # Session subcommands
1089
+ local session_cmds="list info delete"
1090
+
1091
+ # Tools subcommands
1092
+ local tools_cmds="list info search"
1093
+
1094
+ case "\${prev}" in
1095
+ browser-devtools-cli)
1096
+ COMPREPLY=( \$(compgen -W "\${commands}" -- "\${cur}") )
1097
+ return 0
1098
+ ;;
1099
+ daemon)
1100
+ COMPREPLY=( \$(compgen -W "\${daemon_cmds}" -- "\${cur}") )
1101
+ return 0
1102
+ ;;
1103
+ session)
1104
+ COMPREPLY=( \$(compgen -W "\${session_cmds}" -- "\${cur}") )
1105
+ return 0
1106
+ ;;
1107
+ tools)
1108
+ COMPREPLY=( \$(compgen -W "\${tools_cmds}" -- "\${cur}") )
1109
+ return 0
1110
+ ;;
1111
+ esac
1112
+
1113
+ # Global options
1114
+ if [[ "\${cur}" == -* ]]; then
1115
+ local opts="--port --session-id --json --quiet --verbose --timeout --headless --no-headless --persistent --no-persistent --user-data-dir --use-system-browser --browser-path --help --version"
1116
+ COMPREPLY=( \$(compgen -W "\${opts}" -- "\${cur}") )
1117
+ return 0
1118
+ fi
1119
+ }
1120
+
1121
+ complete -F _browser_devtools_cli_completions browser-devtools-cli
1122
+ `;
1123
+ console.log(script);
1124
+ console.error('\n# To enable, add to your ~/.bashrc:');
1125
+ console.error('# eval "$(browser-devtools-cli completion bash)"');
1126
+ });
1127
+ completionCmd
1128
+ .command('zsh')
1129
+ .description('Generate zsh completion script')
1130
+ .action(() => {
1131
+ const script = `
1132
+ #compdef browser-devtools-cli
1133
+
1134
+ _browser_devtools_cli() {
1135
+ local -a commands
1136
+ commands=(
1137
+ 'daemon:Manage the daemon server'
1138
+ 'session:Manage browser sessions'
1139
+ 'tools:List and inspect available tools'
1140
+ 'config:Show current configuration'
1141
+ 'completion:Generate shell completion scripts'
1142
+ 'interactive:Start interactive REPL mode'
1143
+ 'navigation:Navigation commands'
1144
+ 'content:Content extraction commands'
1145
+ 'interaction:Interaction commands'
1146
+ 'a11y:Accessibility commands'
1147
+ 'accessibility:Extended accessibility commands'
1148
+ 'o11y:Observability commands'
1149
+ 'react:React debugging commands'
1150
+ 'run:Script execution commands'
1151
+ 'stub:HTTP stubbing commands'
1152
+ 'sync:Synchronization commands'
1153
+ 'figma:Figma integration commands'
1154
+ )
1155
+
1156
+ local -a daemon_cmds
1157
+ daemon_cmds=(
1158
+ 'start:Start the daemon server'
1159
+ 'stop:Stop the daemon server'
1160
+ 'restart:Restart the daemon server'
1161
+ 'status:Check daemon server status'
1162
+ 'info:Get detailed daemon info'
1163
+ )
1164
+
1165
+ local -a session_cmds
1166
+ session_cmds=(
1167
+ 'list:List all active sessions'
1168
+ 'info:Get information about a session'
1169
+ 'delete:Delete a specific session'
1170
+ )
1171
+
1172
+ local -a tools_cmds
1173
+ tools_cmds=(
1174
+ 'list:List all available tools'
1175
+ 'info:Get detailed tool information'
1176
+ 'search:Search tools by keyword'
1177
+ )
1178
+
1179
+ _arguments -C \\
1180
+ '--port[Daemon server port]:port' \\
1181
+ '--session-id[Session ID]:session_id' \\
1182
+ '--json[Output as JSON]' \\
1183
+ '--quiet[Suppress log messages]' \\
1184
+ '--verbose[Enable verbose output]' \\
1185
+ '--timeout[Operation timeout]:ms' \\
1186
+ '--headless[Run in headless mode]' \\
1187
+ '--no-headless[Run in headful mode]' \\
1188
+ '--persistent[Use persistent context]' \\
1189
+ '--no-persistent[Use ephemeral context]' \\
1190
+ '--user-data-dir[User data directory]:path:_files -/' \\
1191
+ '--use-system-browser[Use system browser]' \\
1192
+ '--browser-path[Browser executable path]:path:_files' \\
1193
+ '--help[Show help]' \\
1194
+ '--version[Show version]' \\
1195
+ '1: :->cmd' \\
1196
+ '*:: :->args'
1197
+
1198
+ case "\$state" in
1199
+ cmd)
1200
+ _describe 'command' commands
1201
+ ;;
1202
+ args)
1203
+ case "\$words[1]" in
1204
+ daemon)
1205
+ _describe 'subcommand' daemon_cmds
1206
+ ;;
1207
+ session)
1208
+ _describe 'subcommand' session_cmds
1209
+ ;;
1210
+ tools)
1211
+ _describe 'subcommand' tools_cmds
1212
+ ;;
1213
+ esac
1214
+ ;;
1215
+ esac
1216
+ }
1217
+
1218
+ _browser_devtools_cli
1219
+ `;
1220
+ console.log(script);
1221
+ console.error('\n# To enable, add to your ~/.zshrc:');
1222
+ console.error('# eval "$(browser-devtools-cli completion zsh)"');
1223
+ });
1224
+ program.addCommand(completionCmd);
1225
+ // ==================== interactive subcommand ====================
1226
+ // Create a reusable REPL program that shares the same subcommands
1227
+ function _createReplProgram(parentOpts) {
1228
+ const replProgram = new commander_1.Command('repl')
1229
+ .exitOverride() // Prevent process.exit()
1230
+ .configureOutput({
1231
+ writeOut: (str) => console.log(str.trimEnd()),
1232
+ writeErr: (str) => console.error(str.trimEnd()),
1233
+ });
1234
+ // Add daemon subcommand
1235
+ const replDaemonCmd = new commander_1.Command('daemon')
1236
+ .description('Manage daemon server')
1237
+ .exitOverride();
1238
+ replDaemonCmd
1239
+ .command('start')
1240
+ .description('Start the daemon server')
1241
+ .action(async () => {
1242
+ const opts = parentOpts;
1243
+ const running = await _isDaemonRunning(opts.port);
1244
+ if (running) {
1245
+ console.log(`Daemon server is already running on port ${opts.port}`);
1246
+ }
1247
+ else {
1248
+ _startDaemonDetached(opts);
1249
+ await _ensureDaemonRunning(opts);
1250
+ console.log(`Daemon server started on port ${opts.port}`);
1251
+ }
1252
+ });
1253
+ replDaemonCmd
1254
+ .command('stop')
1255
+ .description('Stop the daemon server')
1256
+ .action(async () => {
1257
+ const opts = parentOpts;
1258
+ const running = await _isDaemonRunning(opts.port);
1259
+ if (!running) {
1260
+ console.log('Daemon server is not running');
1261
+ }
1262
+ else {
1263
+ const stopped = await _stopDaemon(opts.port, opts.timeout ?? DEFAULT_TIMEOUT);
1264
+ if (stopped) {
1265
+ console.log('Daemon server stopped');
1266
+ }
1267
+ else {
1268
+ console.error('Failed to stop daemon server');
1269
+ }
1270
+ }
1271
+ });
1272
+ replDaemonCmd
1273
+ .command('restart')
1274
+ .description('Restart the daemon server')
1275
+ .action(async () => {
1276
+ const opts = parentOpts;
1277
+ const wasRunning = await _isDaemonRunning(opts.port);
1278
+ if (wasRunning) {
1279
+ await _stopDaemon(opts.port, opts.timeout ?? DEFAULT_TIMEOUT);
1280
+ await new Promise((r) => setTimeout(r, 1000));
1281
+ }
1282
+ _startDaemonDetached(opts);
1283
+ await _ensureDaemonRunning(opts);
1284
+ console.log(`Daemon server restarted on port ${opts.port}`);
1285
+ });
1286
+ replDaemonCmd
1287
+ .command('status')
1288
+ .description('Check daemon server status')
1289
+ .action(async () => {
1290
+ const opts = parentOpts;
1291
+ const running = await _isDaemonRunning(opts.port);
1292
+ if (running) {
1293
+ console.log(`Daemon server is running on port ${opts.port}`);
1294
+ }
1295
+ else {
1296
+ console.log('Daemon server is not running');
1297
+ }
1298
+ });
1299
+ replDaemonCmd
1300
+ .command('info')
1301
+ .description('Show daemon server information')
1302
+ .action(async () => {
1303
+ const opts = parentOpts;
1304
+ const info = await _getDaemonInfo(opts.port, opts.timeout ?? DEFAULT_TIMEOUT);
1305
+ if (info) {
1306
+ console.log(`Version: ${info.version}`);
1307
+ console.log(`Uptime: ${_formatUptime(info.uptime)}`);
1308
+ console.log(`Sessions: ${info.sessionCount}`);
1309
+ console.log(`Port: ${info.port}`);
1310
+ }
1311
+ else {
1312
+ console.log('Daemon server is not running');
1313
+ }
1314
+ });
1315
+ replProgram.addCommand(replDaemonCmd);
1316
+ // Add session subcommand
1317
+ const replSessionCmd = new commander_1.Command('session')
1318
+ .description('Manage browser sessions')
1319
+ .exitOverride();
1320
+ replSessionCmd
1321
+ .command('list')
1322
+ .description('List active sessions')
1323
+ .action(async () => {
1324
+ const opts = parentOpts;
1325
+ const result = await _listSessions(opts.port, opts.timeout ?? DEFAULT_TIMEOUT);
1326
+ if (result && result.sessions.length > 0) {
1327
+ console.log(`Active sessions: ${result.sessions.length}`);
1328
+ for (const session of result.sessions) {
1329
+ console.log(` ${session.id} (idle: ${_formatUptime(session.idleSeconds)})`);
1330
+ }
1331
+ }
1332
+ else {
1333
+ console.log('No active sessions');
1334
+ }
1335
+ });
1336
+ replSessionCmd
1337
+ .command('info <session-id>')
1338
+ .description('Show session information')
1339
+ .action(async (sessionId) => {
1340
+ const opts = parentOpts;
1341
+ try {
1342
+ const response = await fetch(`http://localhost:${opts.port}/session`, {
1343
+ method: 'GET',
1344
+ headers: { 'session-id': sessionId },
1345
+ signal: AbortSignal.timeout(opts.timeout ?? DEFAULT_TIMEOUT),
1346
+ });
1347
+ if (response.ok) {
1348
+ const info = await response.json();
1349
+ console.log(`Session: ${info.id}`);
1350
+ console.log(`Created: ${new Date(info.createdAt).toISOString()}`);
1351
+ console.log(`Last Active: ${new Date(info.lastActiveAt).toISOString()}`);
1352
+ console.log(`Idle: ${_formatUptime(info.idleSeconds)}`);
1353
+ }
1354
+ else {
1355
+ console.log(`Session not found: ${sessionId}`);
1356
+ }
1357
+ }
1358
+ catch (err) {
1359
+ console.error(`Error: ${err.message}`);
1360
+ }
1361
+ });
1362
+ replSessionCmd
1363
+ .command('delete <session-id>')
1364
+ .description('Delete a session')
1365
+ .action(async (sessionId) => {
1366
+ const opts = parentOpts;
1367
+ try {
1368
+ const response = await fetch(`http://localhost:${opts.port}/session`, {
1369
+ method: 'DELETE',
1370
+ headers: { 'session-id': sessionId },
1371
+ signal: AbortSignal.timeout(opts.timeout ?? DEFAULT_TIMEOUT),
1372
+ });
1373
+ if (response.ok) {
1374
+ console.log(`Session deleted: ${sessionId}`);
1375
+ }
1376
+ else {
1377
+ console.log(`Session not found: ${sessionId}`);
1378
+ }
1379
+ }
1380
+ catch (err) {
1381
+ console.error(`Error: ${err.message}`);
1382
+ }
1383
+ });
1384
+ replProgram.addCommand(replSessionCmd);
1385
+ // Add tools subcommand
1386
+ const replToolsCmd = new commander_1.Command('tools')
1387
+ .description('Discover and inspect available tools')
1388
+ .exitOverride();
1389
+ replToolsCmd
1390
+ .command('list')
1391
+ .description('List all available tools')
1392
+ .action(() => {
1393
+ const domains = new Set();
1394
+ for (const tool of tools_1.tools) {
1395
+ domains.add(tool.name().split('_')[0]);
1396
+ }
1397
+ console.log(`Available domains: ${Array.from(domains).join(', ')}`);
1398
+ console.log(`Total tools: ${tools_1.tools.length}`);
1399
+ });
1400
+ replToolsCmd
1401
+ .command('search <query>')
1402
+ .description('Search tools by name or description')
1403
+ .action((query) => {
1404
+ const lowerQuery = query.toLowerCase();
1405
+ const matchingTools = tools_1.tools.filter((t) => t.name().toLowerCase().includes(lowerQuery) ||
1406
+ t.description().toLowerCase().includes(lowerQuery));
1407
+ if (matchingTools.length > 0) {
1408
+ console.log(`Found ${matchingTools.length} tools:`);
1409
+ for (const t of matchingTools) {
1410
+ console.log(` ${t.name()} - ${t.description()}`);
1411
+ }
1412
+ }
1413
+ else {
1414
+ console.log(`No tools found matching "${query}"`);
1415
+ }
1416
+ });
1417
+ replToolsCmd
1418
+ .command('info <tool-name>')
1419
+ .description('Show detailed information about a tool')
1420
+ .action((toolName) => {
1421
+ const tool = tools_1.tools.find((t) => t.name() === toolName);
1422
+ if (tool) {
1423
+ console.log(`\nTool: ${tool.name()}`);
1424
+ console.log(`Description: ${tool.description()}`);
1425
+ console.log('Input Schema:');
1426
+ const schema = tool.inputSchema();
1427
+ for (const [key, value] of Object.entries(schema)) {
1428
+ const typeName = _getZodTypeName(value);
1429
+ const desc = _getZodDescription(value) || '';
1430
+ const optional = value.isOptional();
1431
+ console.log(` --${key} <${typeName}>${optional ? ' (optional)' : ''} ${desc}`);
1432
+ }
1433
+ }
1434
+ else {
1435
+ console.log(`Tool not found: ${toolName}`);
1436
+ }
1437
+ });
1438
+ replProgram.addCommand(replToolsCmd);
1439
+ // Add config command
1440
+ replProgram
1441
+ .command('config')
1442
+ .description('Show current configuration')
1443
+ .action(() => {
1444
+ const opts = parentOpts;
1445
+ console.log('\nCurrent Configuration:');
1446
+ console.log(` port = ${opts.port}`);
1447
+ console.log(` session-id = ${opts.sessionId || '(auto)'}`);
1448
+ console.log(` headless = ${opts.headless ?? config.BROWSER_HEADLESS_ENABLE}`);
1449
+ console.log(` persistent = ${opts.persistent ?? config.BROWSER_PERSISTENT_ENABLE}`);
1450
+ console.log(` user-data-dir = ${opts.userDataDir || config.BROWSER_PERSISTENT_USER_DATA_DIR || '(default)'}`);
1451
+ console.log(` use-system-browser = ${opts.useSystemBrowser ?? config.BROWSER_USE_INSTALLED_ON_SYSTEM}`);
1452
+ console.log(` browser-path = ${opts.browserPath || '(auto)'}`);
1453
+ console.log(` timeout = ${opts.timeout ?? DEFAULT_TIMEOUT}`);
1454
+ console.log(` json = ${opts.json ?? false}`);
1455
+ console.log(` quiet = ${opts.quiet ?? false}`);
1456
+ console.log(` verbose = ${opts.verbose ?? false}`);
1457
+ console.log('\nTip: Start interactive mode with options:');
1458
+ console.log(' browser-devtools-cli --no-headless interactive');
1459
+ });
1460
+ // Add update command
1461
+ replProgram
1462
+ .command('update')
1463
+ .description('Check for updates')
1464
+ .option('--check', 'Only check for updates without installing')
1465
+ .action(async (cmdOpts) => {
1466
+ const currentVersion = require('../package.json').version;
1467
+ const packageName = 'browser-devtools-mcp';
1468
+ console.log('Checking for updates...\n');
1469
+ try {
1470
+ const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`, { signal: AbortSignal.timeout(10000) });
1471
+ if (!response.ok) {
1472
+ console.error('Failed to check for updates');
1473
+ return;
1474
+ }
1475
+ const data = await response.json();
1476
+ const latestVersion = data.version;
1477
+ console.log(`Current version: ${currentVersion}`);
1478
+ console.log(`Latest version: ${latestVersion}`);
1479
+ if (currentVersion === latestVersion) {
1480
+ console.log('\nYou are using the latest version!');
1481
+ }
1482
+ else {
1483
+ console.log('\nA new version is available!');
1484
+ if (!cmdOpts.check) {
1485
+ console.log(`Run: npm install -g ${packageName}@latest`);
1486
+ }
1487
+ }
1488
+ }
1489
+ catch (err) {
1490
+ console.error(`Error checking for updates: ${err.message}`);
1491
+ }
1492
+ });
1493
+ // Add status command (quick daemon status)
1494
+ replProgram
1495
+ .command('status')
1496
+ .description('Show daemon status summary')
1497
+ .action(async () => {
1498
+ const opts = parentOpts;
1499
+ const info = await _getDaemonInfo(opts.port, opts.timeout ?? DEFAULT_TIMEOUT);
1500
+ if (info) {
1501
+ console.log(`Daemon: running (v${info.version}, uptime: ${_formatUptime(info.uptime)}, sessions: ${info.sessionCount})`);
1502
+ }
1503
+ else {
1504
+ console.log('Daemon: not running');
1505
+ }
1506
+ });
1507
+ // Register tool commands for REPL
1508
+ (0, cli_utils_1.registerToolCommands)(replProgram, tools_1.tools, async (toolName, toolInput, _globalOptions) => {
1509
+ const opts = parentOpts;
1510
+ try {
1511
+ const response = await _callTool(opts.port, toolName, toolInput, opts.sessionId, opts.timeout);
1512
+ if (response.toolError) {
1513
+ console.error(`Error: ${response.toolError.message}`);
1514
+ }
1515
+ else if (response.toolOutput) {
1516
+ if (opts.json) {
1517
+ console.log(JSON.stringify(response.toolOutput, null, 2));
1518
+ }
1519
+ else {
1520
+ console.log(_formatOutput(response.toolOutput));
1521
+ }
1522
+ }
1523
+ }
1524
+ catch (err) {
1525
+ console.error(`Error: ${err.message}`);
1526
+ }
1527
+ });
1528
+ return replProgram;
1529
+ }
1530
+ // Parse input for REPL, handling quoted strings
1531
+ function _parseReplInput(input) {
1532
+ const args = [];
1533
+ let current = '';
1534
+ let inQuote = false;
1535
+ let quoteChar = '';
1536
+ for (let i = 0; i < input.length; i++) {
1537
+ const char = input[i];
1538
+ if (inQuote) {
1539
+ if (char === quoteChar) {
1540
+ inQuote = false;
1541
+ args.push(current);
1542
+ current = '';
1543
+ }
1544
+ else {
1545
+ current += char;
1546
+ }
1547
+ }
1548
+ else if (char === '"' || char === "'") {
1549
+ inQuote = true;
1550
+ quoteChar = char;
1551
+ }
1552
+ else if (char === ' ' || char === '\t') {
1553
+ if (current) {
1554
+ args.push(current);
1555
+ current = '';
1556
+ }
1557
+ }
1558
+ else {
1559
+ current += char;
1560
+ }
1561
+ }
1562
+ if (current) {
1563
+ args.push(current);
1564
+ }
1565
+ return args;
1566
+ }
1567
+ const interactiveCmd = new commander_1.Command('interactive')
1568
+ .alias('repl')
1569
+ .description('Start interactive REPL mode')
1570
+ .action(async () => {
1571
+ const opts = program.opts();
1572
+ console.log('Browser DevTools CLI - Interactive Mode');
1573
+ console.log('Type "help" for available commands, "exit" to quit\n');
1574
+ // Ensure daemon is running
1575
+ try {
1576
+ await _ensureDaemonRunning(opts);
1577
+ }
1578
+ catch (err) {
1579
+ console.error(`Error: ${err.message}`);
1580
+ process.exit(1);
1581
+ }
1582
+ // Create REPL program with shared options
1583
+ const replProgram = _createReplProgram(opts);
1584
+ const rl = readline.createInterface({
1585
+ input: process.stdin,
1586
+ output: process.stdout,
1587
+ prompt: 'browser> ',
1588
+ });
1589
+ rl.prompt();
1590
+ rl.on('line', async (line) => {
1591
+ const input = line.trim();
1592
+ if (!input) {
1593
+ rl.prompt();
1594
+ return;
1595
+ }
1596
+ // Handle special REPL commands
1597
+ if (input === 'exit' || input === 'quit') {
1598
+ console.log('Goodbye!');
1599
+ rl.close();
1600
+ process.exit(0);
1601
+ }
1602
+ if (input === 'help') {
1603
+ // Show REPL help
1604
+ console.log('\nREPL Commands:');
1605
+ console.log(' help Show this help');
1606
+ console.log(' exit, quit Exit interactive mode');
1607
+ console.log('\nAvailable Commands:');
1608
+ console.log(' status Show daemon status summary');
1609
+ console.log(' config Show current configuration');
1610
+ console.log(' update Check for CLI updates');
1611
+ console.log(' daemon <cmd> Daemon management (start, stop, restart, status, info)');
1612
+ console.log(' session <cmd> Session management (list, info, delete)');
1613
+ console.log(' tools <cmd> Tool discovery (list, search, info)');
1614
+ console.log(' <domain> <tool> Execute a tool (e.g., navigation go-to --url ...)');
1615
+ console.log('\nExamples:');
1616
+ console.log(' # Daemon & Session');
1617
+ console.log(' daemon status');
1618
+ console.log(' daemon info');
1619
+ console.log(' session list');
1620
+ console.log(' session delete my-session');
1621
+ console.log('');
1622
+ console.log(' # Tool Discovery');
1623
+ console.log(' tools list');
1624
+ console.log(' tools search screenshot');
1625
+ console.log(' tools info navigation_go-to');
1626
+ console.log('');
1627
+ console.log(' # Navigation');
1628
+ console.log(' navigation go-to --url "https://example.com"');
1629
+ console.log(' navigation go-back');
1630
+ console.log(' navigation reload');
1631
+ console.log('');
1632
+ console.log(' # Content');
1633
+ console.log(' content take-screenshot --name "test"');
1634
+ console.log(' content get-as-text');
1635
+ console.log(' content get-as-html --selector "#main"');
1636
+ console.log('');
1637
+ console.log(' # Interaction');
1638
+ console.log(' interaction click --ref "Submit"');
1639
+ console.log(' interaction fill --ref "Email" --value "test@example.com"');
1640
+ console.log(' interaction hover --ref "Menu"');
1641
+ console.log('');
1642
+ console.log(' # Accessibility');
1643
+ console.log(' a11y get-snapshot');
1644
+ console.log(' a11y get-ax-tree-snapshot');
1645
+ console.log('\nTip: Use global options when starting interactive mode:');
1646
+ console.log(' browser-devtools-cli --no-headless interactive');
1647
+ console.log(' browser-devtools-cli --persistent --no-headless interactive');
1648
+ console.log();
1649
+ rl.prompt();
1650
+ return;
1651
+ }
1652
+ // Parse input and pass to Commander
1653
+ try {
1654
+ const args = _parseReplInput(input);
1655
+ await replProgram.parseAsync(['node', 'repl', ...args]);
1656
+ }
1657
+ catch (err) {
1658
+ // Commander throws on exitOverride, handle gracefully
1659
+ if (err.code === 'commander.help') {
1660
+ // Help was shown, do nothing
1661
+ }
1662
+ else if (err.code === 'commander.unknownCommand') {
1663
+ console.log(`Unknown command: ${input}`);
1664
+ console.log('Type "help" for available commands');
1665
+ }
1666
+ else if (err.code === 'commander.missingArgument') {
1667
+ console.error(`Missing argument: ${err.message}`);
1668
+ }
1669
+ else if (err.code === 'commander.invalidArgument') {
1670
+ console.error(`Invalid argument: ${err.message}`);
1671
+ }
1672
+ else if (err.code && err.code.startsWith('commander.')) {
1673
+ // Other Commander errors, message already shown
1674
+ }
1675
+ else {
1676
+ console.error(`Error: ${err.message}`);
1677
+ }
1678
+ }
1679
+ rl.prompt();
1680
+ });
1681
+ rl.on('close', () => {
1682
+ process.exit(0);
1683
+ });
1684
+ });
1685
+ program.addCommand(interactiveCmd);
1686
+ // ==================== update subcommand ====================
1687
+ const updateCmd = new commander_1.Command('update')
1688
+ .description('Check for updates and optionally install them')
1689
+ .option('--check', 'Only check for updates without installing')
1690
+ .action(async (cmdOpts) => {
1691
+ const opts = program.opts();
1692
+ const currentVersion = require('../package.json').version;
1693
+ const packageName = 'browser-devtools-mcp';
1694
+ console.log('Checking for updates...\n');
1695
+ try {
1696
+ // Fetch latest version from npm registry
1697
+ const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`, {
1698
+ method: 'GET',
1699
+ signal: AbortSignal.timeout(10000),
1700
+ });
1701
+ if (!response.ok) {
1702
+ throw new Error(`Failed to check npm registry: HTTP ${response.status}`);
1703
+ }
1704
+ const data = await response.json();
1705
+ const latestVersion = data.version;
1706
+ if (opts.json) {
1707
+ _printOutput({
1708
+ currentVersion,
1709
+ latestVersion,
1710
+ updateAvailable: latestVersion !== currentVersion,
1711
+ }, true);
1712
+ return;
1713
+ }
1714
+ console.log(` Current version: ${currentVersion}`);
1715
+ console.log(` Latest version: ${latestVersion}`);
1716
+ console.log();
1717
+ if (latestVersion === currentVersion) {
1718
+ console.log('\x1b[32m✓ You are using the latest version!\x1b[0m');
1719
+ return;
1720
+ }
1721
+ // Compare versions
1722
+ const currentParts = currentVersion
1723
+ .split('.')
1724
+ .map(Number);
1725
+ const latestParts = latestVersion
1726
+ .split('.')
1727
+ .map(Number);
1728
+ let isNewer = false;
1729
+ for (let i = 0; i < 3; i++) {
1730
+ if (latestParts[i] > currentParts[i]) {
1731
+ isNewer = true;
1732
+ break;
1733
+ }
1734
+ else if (latestParts[i] < currentParts[i]) {
1735
+ break;
1736
+ }
1737
+ }
1738
+ if (!isNewer) {
1739
+ console.log('\x1b[32m✓ You are using a newer version than published!\x1b[0m');
1740
+ return;
1741
+ }
1742
+ console.log(`\x1b[33m⚠ Update available: ${currentVersion} → ${latestVersion}\x1b[0m\n`);
1743
+ if (cmdOpts.check) {
1744
+ console.log('To update, run:');
1745
+ console.log(` npm install -g ${packageName}@latest`);
1746
+ console.log('or');
1747
+ console.log(` npx ${packageName}@latest`);
1748
+ return;
1749
+ }
1750
+ // Ask for confirmation
1751
+ const readline = require('readline');
1752
+ const rl = readline.createInterface({
1753
+ input: process.stdin,
1754
+ output: process.stdout,
1755
+ });
1756
+ const answer = await new Promise((resolve) => {
1757
+ rl.question('Do you want to update now? (y/N) ', (ans) => {
1758
+ rl.close();
1759
+ resolve(ans.toLowerCase());
1760
+ });
1761
+ });
1762
+ if (answer !== 'y' && answer !== 'yes') {
1763
+ console.log('\nUpdate cancelled.');
1764
+ return;
1765
+ }
1766
+ console.log('\nUpdating...\n');
1767
+ // Run npm install
1768
+ const { execSync } = require('child_process');
1769
+ try {
1770
+ execSync(`npm install -g ${packageName}@latest`, {
1771
+ stdio: 'inherit',
1772
+ });
1773
+ console.log('\n\x1b[32m✓ Update complete!\x1b[0m');
1774
+ console.log('Please restart your terminal or run a new command to use the updated version.');
1775
+ }
1776
+ catch (installErr) {
1777
+ console.error('\n\x1b[31m✗ Update failed.\x1b[0m');
1778
+ console.error('Try running manually with sudo:');
1779
+ console.error(` sudo npm install -g ${packageName}@latest`);
1780
+ process.exit(1);
1781
+ }
1782
+ }
1783
+ catch (err) {
1784
+ if (opts.json) {
1785
+ _printOutput({
1786
+ error: err.message,
1787
+ currentVersion,
1788
+ }, true, true);
1789
+ }
1790
+ else {
1791
+ console.error(`\x1b[31m✗ Failed to check for updates: ${err.message}\x1b[0m`);
1792
+ console.error('\nYou can manually check at:');
1793
+ console.error(` https://www.npmjs.com/package/${packageName}`);
1794
+ }
1795
+ process.exit(1);
1796
+ }
1797
+ });
1798
+ program.addCommand(updateCmd);
1799
+ // ==================== tool subcommands ====================
1800
+ (0, cli_utils_1.registerToolCommands)(program, tools_1.tools, async (toolName, toolInput, globalOptions) => {
1801
+ const opts = globalOptions;
1802
+ try {
1803
+ // Ensure daemon is running
1804
+ await _ensureDaemonRunning(opts);
1805
+ // Call the tool via daemon
1806
+ const response = await _callTool(opts.port, toolName, toolInput, opts.sessionId, opts.timeout);
1807
+ // Handle response
1808
+ if (response.toolError) {
1809
+ if (opts.json) {
1810
+ _printOutput({
1811
+ error: response.toolError,
1812
+ }, true, true);
1813
+ }
1814
+ else {
1815
+ console.error(`Error: ${response.toolError.message || 'Unknown error'}`);
1816
+ }
1817
+ process.exit(1);
1818
+ }
1819
+ if (response.toolOutput) {
1820
+ if (opts.json) {
1821
+ _printOutput(response.toolOutput, true);
1822
+ }
1823
+ else {
1824
+ console.log(_formatOutput(response.toolOutput));
1825
+ }
1826
+ }
1827
+ }
1828
+ catch (err) {
1829
+ if (opts.json) {
1830
+ _printOutput({
1831
+ error: err.message,
1832
+ }, true, true);
1833
+ }
1834
+ else {
1835
+ console.error(`Error: ${err.message}`);
1836
+ }
1837
+ process.exit(1);
1838
+ }
1839
+ });
1840
+ // Parse and execute
1841
+ await program.parseAsync(process.argv);
1842
+ }
1843
+ main().catch((err) => {
1844
+ console.error(`Fatal error: ${err.message}`);
1845
+ process.exit(1);
1846
+ });
1847
+ //# sourceMappingURL=cli.js.map