chrome-cdp-cli 1.5.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
@@ -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
@@ -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) {