chrome-cdp-cli 1.3.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -49,7 +49,7 @@ A command-line tool designed specifically for Large Language Models (LLMs) and A
49
49
  - 📸 **Visual Capture**: Take screenshots and capture complete DOM snapshots with layout information
50
50
  - 📊 **Console Monitoring**: Real-time console message capture with filtering and storage
51
51
  - 🌐 **Network Monitoring**: Real-time network request/response monitoring with comprehensive filtering
52
- - 🖱️ **Element Interaction**: Native click, hover, and form filling commands with CSS selector support
52
+ - 🖱️ **Element Interaction**: Complete native interaction commands (click, hover, fill, drag, press_key, upload_file, wait_for, handle_dialog)
53
53
  - 🔧 **CLI Interface**: Full command-line interface with argument parsing and routing
54
54
  - 🛠️ **IDE Integration**: Install Cursor commands and Claude skills with directory validation and --force option
55
55
  - 📦 **Build System**: Complete TypeScript build pipeline with testing framework
@@ -101,7 +101,7 @@ This tool is designed for LLM-assisted development. The IDE integrations (`insta
101
101
  - ⚡ **JavaScript Execution**: Execute JavaScript code in browser context with full async support
102
102
  - 📸 **Visual Capture**: Take screenshots and capture HTML content
103
103
  - 📊 **Monitoring**: Monitor console messages and network requests in real-time
104
- - 🖱️ **Element Interaction**: Native click, hover, and form filling commands with CSS selector support
104
+ - 🖱️ **Element Interaction**: Complete native interaction commands (click, hover, fill, drag, press_key, upload_file, wait_for, handle_dialog)
105
105
  - 📝 **Form Automation**: Single field and batch form filling with comprehensive options
106
106
  - 🔧 **Flexible Output**: Support for JSON and human-readable text output formats
107
107
  - 🚧 **Eval Workarounds**: Many advanced features available through JavaScript execution
@@ -390,6 +390,18 @@ chrome-cdp-cli list_console_messages
390
390
  chrome-cdp-cli list_console_messages --filter '{"types":["error","warn"]}'
391
391
  ```
392
392
 
393
+ **Note**: Console monitoring only captures messages generated *after* monitoring starts. For historical messages or immediate console operations, use the eval-first approach:
394
+
395
+ ```bash
396
+ # Generate and capture console messages in one command
397
+ chrome-cdp-cli eval "console.log('Test message'); console.warn('Warning'); 'Messages logged'"
398
+
399
+ # Check for existing console history (if page maintains it)
400
+ chrome-cdp-cli eval "window.consoleHistory || window._console_logs || 'No custom console history'"
401
+ ```
402
+
403
+ See [Console Monitoring Documentation](docs/CONSOLE_MONITORING.md) for detailed solutions and workarounds.
404
+
393
405
  #### Network Monitoring
394
406
  ```bash
395
407
  # Get latest network request
@@ -586,7 +598,6 @@ chrome-cdp-cli eval "document.cookie"
586
598
  These features require dedicated handlers and are not yet available:
587
599
 
588
600
  - Native page management commands (new_page, close_page, list_pages, select_page)
589
- - Native element interaction commands (click, hover, fill, drag)
590
601
  - Native performance profiling commands
591
602
  - Native device emulation commands
592
603
  - Advanced output formatting options
@@ -669,20 +680,15 @@ new Promise(resolve => {
669
680
  - `new_page`, `close_page`, `list_pages`, `select_page`
670
681
  - Direct CDP Target domain integration
671
682
 
672
- 2. **Native Element Interaction** ✅ **COMPLETED**
673
- - `click`, `hover`, `fill`, `fill_form` commands
674
- - CSS selector-based element targeting
675
- - Comprehensive form filling with batch operations
676
-
677
- 3. **Performance Analysis**
683
+ 2. **Performance Analysis**
678
684
  - `performance_start_trace`, `performance_stop_trace`
679
685
  - Built-in performance metrics and analysis
680
686
 
681
- 4. **Device Emulation**
687
+ 3. **Device Emulation**
682
688
  - `emulate` command for device simulation
683
689
  - Network condition simulation
684
690
 
685
- 5. **Advanced Output Formatting**
691
+ 4. **Advanced Output Formatting**
686
692
  - Enhanced JSON/text formatting
687
693
  - Quiet and verbose modes
688
694
  - Custom output templates
@@ -6,11 +6,13 @@ const ConnectionManager_1 = require("../connection/ConnectionManager");
6
6
  const handlers_1 = require("../handlers");
7
7
  const logger_1 = require("../utils/logger");
8
8
  const CommandRouter_1 = require("./CommandRouter");
9
+ const ProxyManager_1 = require("../proxy/ProxyManager");
9
10
  class CLIApplication {
10
11
  constructor() {
11
12
  this.cli = new CLIInterface_1.CLIInterface();
12
13
  this.connectionManager = new ConnectionManager_1.ConnectionManager();
13
14
  this.logger = new logger_1.Logger();
15
+ this.proxyManager = ProxyManager_1.ProxyManager.getInstance();
14
16
  this.setupHandlers();
15
17
  }
16
18
  setupHandlers() {
@@ -36,6 +38,10 @@ class CLIApplication {
36
38
  async run(argv) {
37
39
  try {
38
40
  const command = this.cli.parseArgs(argv);
41
+ if (command.config.verbose) {
42
+ this.proxyManager.setLogging(true);
43
+ }
44
+ await this.ensureProxyReady();
39
45
  if (this.needsConnection(command.name)) {
40
46
  await this.ensureConnection(command);
41
47
  }
@@ -49,6 +55,15 @@ class CLIApplication {
49
55
  return CommandRouter_1.ExitCode.GENERAL_ERROR;
50
56
  }
51
57
  }
58
+ async ensureProxyReady() {
59
+ try {
60
+ const isReady = await this.proxyManager.ensureProxyReady();
61
+ if (!isReady) {
62
+ }
63
+ }
64
+ catch (error) {
65
+ }
66
+ }
52
67
  needsConnection(commandName) {
53
68
  const noConnectionCommands = [
54
69
  'help',
@@ -104,6 +119,12 @@ class CLIApplication {
104
119
  this.logger.error('Error during shutdown:', error);
105
120
  }
106
121
  }
122
+ try {
123
+ await this.proxyManager.shutdown();
124
+ }
125
+ catch (error) {
126
+ this.logger.error('Error shutting down proxy manager:', error);
127
+ }
107
128
  }
108
129
  getCLI() {
109
130
  return this.cli;
@@ -39,6 +39,7 @@ const path = __importStar(require("path"));
39
39
  const CLIInterface_1 = require("../interfaces/CLIInterface");
40
40
  const CommandRegistry_1 = require("./CommandRegistry");
41
41
  const CommandRouter_1 = require("./CommandRouter");
42
+ const packageJson = require('../../package.json');
42
43
  class CLIInterface {
43
44
  constructor() {
44
45
  this.program = new commander_1.Command();
@@ -50,7 +51,7 @@ class CLIInterface {
50
51
  this.program
51
52
  .name('chrome-cdp-cli')
52
53
  .description('Command-line tool for controlling Chrome browser via DevTools Protocol')
53
- .version('1.0.0')
54
+ .version(packageJson.version)
54
55
  .allowUnknownOption(true)
55
56
  .allowExcessArguments(true);
56
57
  this.program
@@ -65,6 +66,10 @@ class CLIInterface {
65
66
  parseArgs(argv) {
66
67
  try {
67
68
  const args = argv.slice(2);
69
+ if (args.includes('--version') || args.includes('-V')) {
70
+ console.log(packageJson.version);
71
+ process.exit(0);
72
+ }
68
73
  const options = {};
69
74
  const commandArgs = [];
70
75
  let i = 0;
@@ -341,6 +346,65 @@ class CLIInterface {
341
346
  if (result.data === undefined || result.data === null) {
342
347
  return 'Success';
343
348
  }
349
+ let output = '';
350
+ let dataSourceInfo = '';
351
+ if (result.dataSource === 'proxy' && result.hasHistoricalData) {
352
+ dataSourceInfo = '📊 Data from proxy server (includes historical data)\n';
353
+ }
354
+ else if (result.dataSource === 'direct' && result.hasHistoricalData === false) {
355
+ dataSourceInfo = '⚠️ Data from direct connection (new messages only, no historical data)\n';
356
+ }
357
+ if (result.data && typeof result.data === 'object') {
358
+ const data = result.data;
359
+ if (data.messages && Array.isArray(data.messages)) {
360
+ output += dataSourceInfo;
361
+ if (data.messages.length === 0) {
362
+ output += 'No console messages found.';
363
+ }
364
+ else {
365
+ output += `Found ${data.messages.length} console message(s):\n\n`;
366
+ data.messages.forEach((msg, index) => {
367
+ const timestamp = new Date(msg.timestamp).toISOString();
368
+ output += `[${index + 1}] ${timestamp} [${msg.type.toUpperCase()}] ${msg.text}\n`;
369
+ if (msg.args && msg.args.length > 0) {
370
+ output += ` Args: ${JSON.stringify(msg.args)}\n`;
371
+ }
372
+ });
373
+ }
374
+ return output.trim();
375
+ }
376
+ if (data.requests && Array.isArray(data.requests)) {
377
+ output += dataSourceInfo;
378
+ if (data.requests.length === 0) {
379
+ output += 'No network requests found.';
380
+ }
381
+ else {
382
+ output += `Found ${data.requests.length} network request(s):\n\n`;
383
+ data.requests.forEach((req, index) => {
384
+ const timestamp = new Date(req.timestamp).toISOString();
385
+ const status = req.status ? ` [${req.status}]` : ' [pending]';
386
+ output += `[${index + 1}] ${timestamp} ${req.method} ${req.url}${status}\n`;
387
+ });
388
+ }
389
+ return output.trim();
390
+ }
391
+ if (data.type && data.text !== undefined && data.timestamp) {
392
+ output += dataSourceInfo;
393
+ const timestamp = new Date(data.timestamp).toISOString();
394
+ output += `${timestamp} [${data.type.toUpperCase()}] ${data.text}`;
395
+ if (data.args && data.args.length > 0) {
396
+ output += `\nArgs: ${JSON.stringify(data.args)}`;
397
+ }
398
+ return output;
399
+ }
400
+ if (data.requestId && data.url && data.method) {
401
+ output += dataSourceInfo;
402
+ const timestamp = new Date(data.timestamp).toISOString();
403
+ const status = data.status ? ` [${data.status}]` : ' [pending]';
404
+ output += `${timestamp} ${data.method} ${data.url}${status}`;
405
+ return output;
406
+ }
407
+ }
344
408
  if (typeof result.data === 'string') {
345
409
  return result.data;
346
410
  }
@@ -214,6 +214,7 @@ Global Options:
214
214
  -q, --quiet Enable quiet mode
215
215
  -t, --timeout <ms> Command timeout in milliseconds (default: 30000)
216
216
  -c, --config <path> Configuration file path
217
+ -V, --version Show version number
217
218
 
218
219
  Available Commands:
219
220
  ${commands.map(cmd => ` ${cmd.padEnd(20)} - ${this.getCommandDescription(cmd)}`).join('\n')}
@@ -0,0 +1,254 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ProxyClient = void 0;
7
+ const ws_1 = require("ws");
8
+ const node_fetch_1 = __importDefault(require("node-fetch"));
9
+ const ProxyManager_1 = require("../proxy/ProxyManager");
10
+ class ProxyClient {
11
+ constructor(config) {
12
+ this.config = {
13
+ proxyUrl: 'http://localhost:9223',
14
+ fallbackToDirect: true,
15
+ startProxyIfNeeded: true,
16
+ ...config
17
+ };
18
+ this.proxyManager = ProxyManager_1.ProxyManager.getInstance();
19
+ }
20
+ async ensureProxyRunning() {
21
+ if (this.config.startProxyIfNeeded) {
22
+ return await this.proxyManager.ensureProxyReady();
23
+ }
24
+ return await this.isProxyRunning();
25
+ }
26
+ async isProxyAvailable() {
27
+ return await this.isProxyRunning();
28
+ }
29
+ async isProxyRunning() {
30
+ try {
31
+ const controller = new AbortController();
32
+ const timeout = setTimeout(() => controller.abort(), 2000);
33
+ const response = await (0, node_fetch_1.default)(`${this.config.proxyUrl}/api/health`, {
34
+ method: 'GET',
35
+ signal: controller.signal
36
+ });
37
+ clearTimeout(timeout);
38
+ return response.ok;
39
+ }
40
+ catch (error) {
41
+ return false;
42
+ }
43
+ }
44
+ async connect(host, port, targetId) {
45
+ const request = {
46
+ host,
47
+ port,
48
+ targetId
49
+ };
50
+ try {
51
+ const controller = new AbortController();
52
+ const timeout = setTimeout(() => controller.abort(), 30000);
53
+ const response = await (0, node_fetch_1.default)(`${this.config.proxyUrl}/api/connect`, {
54
+ method: 'POST',
55
+ headers: {
56
+ 'Content-Type': 'application/json'
57
+ },
58
+ body: JSON.stringify(request),
59
+ signal: controller.signal
60
+ });
61
+ clearTimeout(timeout);
62
+ if (!response.ok) {
63
+ throw new Error(`Proxy connect failed: ${response.status} ${response.statusText}`);
64
+ }
65
+ const result = await response.json();
66
+ if (!result.success) {
67
+ throw new Error(`Proxy connect failed: ${result.error}`);
68
+ }
69
+ this.connectionId = result.data.connectionId;
70
+ return this.connectionId;
71
+ }
72
+ catch (error) {
73
+ throw new Error(`Failed to connect through proxy: ${error instanceof Error ? error.message : error}`);
74
+ }
75
+ }
76
+ async getConsoleMessages(filter) {
77
+ if (!this.connectionId) {
78
+ throw new Error('No active connection. Call connect() first.');
79
+ }
80
+ try {
81
+ const url = new URL(`${this.config.proxyUrl}/api/console/${this.connectionId}`);
82
+ if (filter) {
83
+ if (filter.types) {
84
+ url.searchParams.set('types', filter.types.join(','));
85
+ }
86
+ if (filter.textPattern) {
87
+ url.searchParams.set('textPattern', filter.textPattern);
88
+ }
89
+ if (filter.maxMessages) {
90
+ url.searchParams.set('maxMessages', filter.maxMessages.toString());
91
+ }
92
+ if (filter.startTime) {
93
+ url.searchParams.set('startTime', filter.startTime.toString());
94
+ }
95
+ if (filter.endTime) {
96
+ url.searchParams.set('endTime', filter.endTime.toString());
97
+ }
98
+ if (filter.source) {
99
+ url.searchParams.set('source', filter.source);
100
+ }
101
+ }
102
+ const controller = new AbortController();
103
+ const timeout = setTimeout(() => controller.abort(), 5000);
104
+ const response = await (0, node_fetch_1.default)(url.toString(), {
105
+ method: 'GET',
106
+ signal: controller.signal
107
+ });
108
+ clearTimeout(timeout);
109
+ if (!response.ok) {
110
+ throw new Error(`Failed to get console messages: ${response.status} ${response.statusText}`);
111
+ }
112
+ const result = await response.json();
113
+ if (!result.success) {
114
+ throw new Error(`Failed to get console messages: ${result.error}`);
115
+ }
116
+ return result.data?.messages || [];
117
+ }
118
+ catch (error) {
119
+ throw new Error(`Failed to get console messages: ${error instanceof Error ? error.message : error}`);
120
+ }
121
+ }
122
+ async getNetworkRequests(filter) {
123
+ if (!this.connectionId) {
124
+ throw new Error('No active connection. Call connect() first.');
125
+ }
126
+ try {
127
+ const url = new URL(`${this.config.proxyUrl}/api/network/${this.connectionId}`);
128
+ if (filter) {
129
+ if (filter.methods) {
130
+ url.searchParams.set('methods', filter.methods.join(','));
131
+ }
132
+ if (filter.statusCodes) {
133
+ url.searchParams.set('statusCodes', filter.statusCodes.join(','));
134
+ }
135
+ if (filter.urlPattern) {
136
+ url.searchParams.set('urlPattern', filter.urlPattern);
137
+ }
138
+ if (filter.maxRequests) {
139
+ url.searchParams.set('maxRequests', filter.maxRequests.toString());
140
+ }
141
+ if (filter.startTime) {
142
+ url.searchParams.set('startTime', filter.startTime.toString());
143
+ }
144
+ if (filter.endTime) {
145
+ url.searchParams.set('endTime', filter.endTime.toString());
146
+ }
147
+ if (filter.includeResponseBody !== undefined) {
148
+ url.searchParams.set('includeResponseBody', filter.includeResponseBody.toString());
149
+ }
150
+ }
151
+ const controller = new AbortController();
152
+ const timeout = setTimeout(() => controller.abort(), 5000);
153
+ const response = await (0, node_fetch_1.default)(url.toString(), {
154
+ method: 'GET',
155
+ signal: controller.signal
156
+ });
157
+ clearTimeout(timeout);
158
+ if (!response.ok) {
159
+ throw new Error(`Failed to get network requests: ${response.status} ${response.statusText}`);
160
+ }
161
+ const result = await response.json();
162
+ if (!result.success) {
163
+ throw new Error(`Failed to get network requests: ${result.error}`);
164
+ }
165
+ return result.data?.requests || [];
166
+ }
167
+ catch (error) {
168
+ throw new Error(`Failed to get network requests: ${error instanceof Error ? error.message : error}`);
169
+ }
170
+ }
171
+ async createWebSocketProxy() {
172
+ if (!this.connectionId) {
173
+ throw new Error('No active connection. Call connect() first.');
174
+ }
175
+ try {
176
+ const wsUrl = this.config.proxyUrl.replace('http://', 'ws://').replace('https://', 'wss://');
177
+ const ws = new ws_1.WebSocket(`${wsUrl}/ws/${this.connectionId}`);
178
+ return new Promise((resolve, reject) => {
179
+ const timeout = setTimeout(() => {
180
+ reject(new Error('WebSocket connection timeout'));
181
+ }, 10000);
182
+ ws.on('open', () => {
183
+ clearTimeout(timeout);
184
+ this.wsConnection = ws;
185
+ resolve(ws);
186
+ });
187
+ ws.on('error', (error) => {
188
+ clearTimeout(timeout);
189
+ reject(error);
190
+ });
191
+ });
192
+ }
193
+ catch (error) {
194
+ throw new Error(`Failed to create WebSocket proxy: ${error instanceof Error ? error.message : error}`);
195
+ }
196
+ }
197
+ async healthCheck() {
198
+ if (!this.connectionId) {
199
+ return null;
200
+ }
201
+ try {
202
+ const controller = new AbortController();
203
+ const timeout = setTimeout(() => controller.abort(), 5000);
204
+ const response = await (0, node_fetch_1.default)(`${this.config.proxyUrl}/api/health/${this.connectionId}`, {
205
+ method: 'GET',
206
+ signal: controller.signal
207
+ });
208
+ clearTimeout(timeout);
209
+ if (!response.ok) {
210
+ throw new Error(`Health check failed: ${response.status} ${response.statusText}`);
211
+ }
212
+ const result = await response.json();
213
+ if (!result.success) {
214
+ throw new Error(`Health check failed: ${result.error}`);
215
+ }
216
+ return result.data || null;
217
+ }
218
+ catch (error) {
219
+ console.warn('Health check failed:', error instanceof Error ? error.message : error);
220
+ return null;
221
+ }
222
+ }
223
+ async disconnect() {
224
+ try {
225
+ if (this.wsConnection) {
226
+ this.wsConnection.close();
227
+ this.wsConnection = undefined;
228
+ }
229
+ if (this.connectionId) {
230
+ try {
231
+ const controller = new AbortController();
232
+ const timeout = setTimeout(() => controller.abort(), 5000);
233
+ await (0, node_fetch_1.default)(`${this.config.proxyUrl}/api/connection/${this.connectionId}`, {
234
+ method: 'DELETE',
235
+ signal: controller.signal
236
+ });
237
+ clearTimeout(timeout);
238
+ }
239
+ catch (error) {
240
+ }
241
+ this.connectionId = undefined;
242
+ }
243
+ }
244
+ catch (error) {
245
+ }
246
+ }
247
+ getConnectionId() {
248
+ return this.connectionId;
249
+ }
250
+ getConfig() {
251
+ return { ...this.config };
252
+ }
253
+ }
254
+ exports.ProxyClient = ProxyClient;
@@ -15,3 +15,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./CDPClient"), exports);
18
+ __exportStar(require("./ProxyClient"), exports);
@@ -1,10 +1,17 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.EvaluateScriptHandler = void 0;
7
+ const ProxyClient_1 = require("../client/ProxyClient");
4
8
  const fs_1 = require("fs");
9
+ const node_fetch_1 = __importDefault(require("node-fetch"));
5
10
  class EvaluateScriptHandler {
6
- constructor() {
11
+ constructor(useProxy = true) {
7
12
  this.name = 'eval';
13
+ this.proxyClient = new ProxyClient_1.ProxyClient();
14
+ this.useProxy = useProxy;
8
15
  }
9
16
  async execute(client, args) {
10
17
  const scriptArgs = args;
@@ -20,6 +27,124 @@ class EvaluateScriptHandler {
20
27
  error: 'Cannot specify both "expression" and "file" arguments'
21
28
  };
22
29
  }
30
+ try {
31
+ if (this.useProxy) {
32
+ const proxyAvailable = await this.proxyClient.isProxyAvailable();
33
+ if (proxyAvailable) {
34
+ console.log('[INFO] Using proxy connection for script evaluation');
35
+ return await this.executeWithProxy(scriptArgs);
36
+ }
37
+ else {
38
+ console.warn('[WARN] Proxy not available, falling back to direct CDP connection');
39
+ }
40
+ }
41
+ }
42
+ catch (error) {
43
+ console.warn('[WARN] Proxy execution failed, falling back to direct CDP:', error instanceof Error ? error.message : error);
44
+ }
45
+ return await this.executeWithDirectCDP(client, scriptArgs);
46
+ }
47
+ async executeWithProxy(scriptArgs) {
48
+ try {
49
+ let expression;
50
+ if (scriptArgs.file) {
51
+ expression = await this.readScriptFile(scriptArgs.file);
52
+ }
53
+ else {
54
+ expression = scriptArgs.expression;
55
+ }
56
+ const response = await (0, node_fetch_1.default)('http://localhost:9223/api/connections');
57
+ if (!response.ok) {
58
+ throw new Error('Failed to get proxy connections');
59
+ }
60
+ const result = await response.json();
61
+ if (!result.success || !result.data.connections || result.data.connections.length === 0) {
62
+ throw new Error('No active proxy connections found');
63
+ }
64
+ const connection = result.data.connections.find((conn) => conn.isHealthy);
65
+ if (!connection) {
66
+ throw new Error('No healthy proxy connections found');
67
+ }
68
+ console.log(`[INFO] Using existing proxy connection: ${connection.id}`);
69
+ this.proxyClient.connectionId = connection.id;
70
+ const ws = await this.proxyClient.createWebSocketProxy();
71
+ try {
72
+ const result = await this.executeScriptThroughProxy(ws, expression, scriptArgs);
73
+ return {
74
+ success: true,
75
+ data: result
76
+ };
77
+ }
78
+ finally {
79
+ ws.close();
80
+ }
81
+ }
82
+ catch (error) {
83
+ return {
84
+ success: false,
85
+ error: error instanceof Error ? error.message : String(error)
86
+ };
87
+ }
88
+ }
89
+ async executeScriptThroughProxy(ws, expression, args) {
90
+ return new Promise((resolve, reject) => {
91
+ const commandId = Date.now();
92
+ const timeout = args.timeout || 30000;
93
+ const awaitPromise = args.awaitPromise ?? true;
94
+ const returnByValue = args.returnByValue ?? true;
95
+ const timeoutHandle = setTimeout(() => {
96
+ reject(new Error(`Script execution timeout after ${timeout}ms`));
97
+ }, timeout);
98
+ ws.on('message', (data) => {
99
+ try {
100
+ const response = JSON.parse(data.toString());
101
+ if (response.id === commandId) {
102
+ clearTimeout(timeoutHandle);
103
+ if (response.error) {
104
+ reject(new Error(`CDP Error: ${response.error.message}`));
105
+ return;
106
+ }
107
+ const result = response.result;
108
+ if (result.exceptionDetails) {
109
+ const error = new Error(result.result?.description || 'Script execution failed');
110
+ error.exceptionDetails = result.exceptionDetails;
111
+ reject(error);
112
+ return;
113
+ }
114
+ let value = result.result?.value;
115
+ if (result.result?.type === 'undefined') {
116
+ value = undefined;
117
+ }
118
+ else if (result.result?.unserializableValue) {
119
+ value = result.result.unserializableValue;
120
+ }
121
+ resolve(value);
122
+ }
123
+ }
124
+ catch (error) {
125
+ clearTimeout(timeoutHandle);
126
+ reject(new Error(`Failed to parse CDP response: ${error instanceof Error ? error.message : error}`));
127
+ }
128
+ });
129
+ ws.on('error', (error) => {
130
+ clearTimeout(timeoutHandle);
131
+ reject(new Error(`WebSocket error: ${error.message}`));
132
+ });
133
+ const command = {
134
+ id: commandId,
135
+ method: 'Runtime.evaluate',
136
+ params: {
137
+ expression: expression,
138
+ awaitPromise: awaitPromise,
139
+ returnByValue: returnByValue,
140
+ userGesture: true,
141
+ generatePreview: false
142
+ }
143
+ };
144
+ ws.send(JSON.stringify(command));
145
+ });
146
+ }
147
+ async executeWithDirectCDP(client, scriptArgs) {
23
148
  try {
24
149
  let expression;
25
150
  if (scriptArgs.file) {