chrome-cdp-cli 2.0.5 → 2.1.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.
@@ -2,140 +2,160 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ListNetworkRequestsHandler = void 0;
4
4
  const NetworkMonitor_1 = require("../monitors/NetworkMonitor");
5
- const ProxyClient_1 = require("../client/ProxyClient");
6
5
  class ListNetworkRequestsHandler {
7
6
  constructor() {
8
- this.name = 'network';
7
+ this.name = "network";
9
8
  this.networkMonitor = null;
10
- this.proxyClient = null;
11
9
  }
12
10
  async execute(client, args) {
13
11
  try {
14
12
  const params = args;
15
- const proxyResult = await this.tryProxyExecution(params);
16
- if (proxyResult) {
17
- return proxyResult;
18
- }
19
- return await this.executeDirectCDP(client, params);
13
+ return await this.executeFollowMode(client, params);
20
14
  }
21
15
  catch (error) {
22
16
  return {
23
17
  success: false,
24
- error: error instanceof Error ? error.message : 'Unknown error occurred'
18
+ error: error instanceof Error ? error.message : "Unknown error occurred",
25
19
  };
26
20
  }
27
21
  }
28
- async tryProxyExecution(params) {
29
- try {
30
- if (!this.proxyClient) {
31
- this.proxyClient = new ProxyClient_1.ProxyClient();
32
- }
33
- const isProxyAvailable = await this.proxyClient.isProxyAvailable();
34
- if (!isProxyAvailable) {
35
- const proxyStarted = await this.proxyClient.ensureProxyRunning();
36
- if (!proxyStarted) {
37
- console.warn('⚠️ Proxy server unavailable. Falling back to direct CDP connection.');
38
- console.warn('⚠️ Note: Direct connection only captures NEW network requests, not historical data.');
39
- return null;
40
- }
41
- }
42
- if (!this.proxyClient.getConnectionId()) {
43
- const host = params.host || 'localhost';
44
- const port = params.port || 9222;
45
- await this.proxyClient.connect(host, port, params.targetId);
46
- }
47
- const filter = params.filter ? {
48
- methods: params.filter.methods,
49
- urlPattern: params.filter.urlPattern,
50
- statusCodes: params.filter.statusCodes,
51
- maxRequests: params.latest ? 1 : params.filter.maxRequests,
52
- startTime: params.filter.startTime,
53
- endTime: params.filter.endTime,
54
- } : params.latest ? { maxRequests: 1 } : undefined;
55
- const requests = await this.proxyClient.getNetworkRequests(filter);
56
- if (params.latest) {
57
- const latestRequest = requests.length > 0 ? requests[requests.length - 1] : null;
58
- if (!latestRequest) {
59
- return {
60
- success: true,
61
- data: null,
62
- dataSource: 'proxy',
63
- hasHistoricalData: true
64
- };
65
- }
66
- return {
67
- success: true,
68
- data: latestRequest,
69
- dataSource: 'proxy',
70
- hasHistoricalData: true
71
- };
72
- }
73
- return {
74
- success: true,
75
- data: {
76
- requests,
77
- count: requests.length,
78
- isMonitoring: true
79
- },
80
- dataSource: 'proxy',
81
- hasHistoricalData: true
82
- };
83
- }
84
- catch (error) {
85
- console.warn('⚠️ Proxy execution failed, falling back to direct CDP:', error instanceof Error ? error.message : error);
86
- console.warn('⚠️ Note: Direct connection only captures NEW network requests, not historical data.');
87
- return null;
88
- }
89
- }
90
- async executeDirectCDP(client, params) {
22
+ async executeFollowMode(client, params) {
91
23
  if (!this.networkMonitor) {
92
24
  this.networkMonitor = new NetworkMonitor_1.NetworkMonitor(client);
93
25
  }
94
- if (!this.networkMonitor.isActive()) {
95
- await this.networkMonitor.startMonitoring();
96
- }
97
- const filter = params.filter ? {
98
- methods: params.filter.methods,
99
- urlPattern: params.filter.urlPattern,
100
- statusCodes: params.filter.statusCodes,
101
- maxRequests: params.filter.maxRequests,
102
- startTime: params.filter.startTime,
103
- endTime: params.filter.endTime,
104
- } : undefined;
105
- if (params.latest) {
106
- const latestRequest = this.networkMonitor.getLatestRequest(filter);
107
- if (!latestRequest) {
108
- return {
109
- success: true,
110
- data: null,
111
- dataSource: 'direct',
112
- hasHistoricalData: false
113
- };
26
+ await this.networkMonitor.startMonitoring();
27
+ const methods = this.parseList(params.methods);
28
+ const statusCodes = this.parseList(params.statusCodes)
29
+ .map(Number)
30
+ .filter((n) => !isNaN(n));
31
+ const urlPattern = params.urlPattern;
32
+ const outputFormat = params.format || "text";
33
+ const requestCallback = (request) => {
34
+ if (!this.shouldOutputRequest(request, methods, urlPattern, statusCodes)) {
35
+ return;
114
36
  }
115
- return {
116
- success: true,
117
- data: latestRequest,
118
- dataSource: 'direct',
119
- hasHistoricalData: false
120
- };
37
+ this.outputRequest(request, outputFormat);
38
+ };
39
+ this.networkMonitor.onRequest(requestCallback);
40
+ let isShuttingDown = false;
41
+ const cleanup = async () => {
42
+ if (isShuttingDown)
43
+ return;
44
+ isShuttingDown = true;
45
+ this.networkMonitor?.offRequest(requestCallback);
46
+ await this.networkMonitor?.stopMonitoring();
47
+ };
48
+ const signalHandler = async () => {
49
+ process.stderr.write("\n");
50
+ await cleanup();
51
+ process.exit(0);
52
+ };
53
+ process.removeAllListeners("SIGINT");
54
+ process.removeAllListeners("SIGTERM");
55
+ process.on("SIGINT", signalHandler);
56
+ process.on("SIGTERM", signalHandler);
57
+ if (outputFormat === "text") {
58
+ console.log("Following network requests (press Ctrl+C to stop)...\n");
121
59
  }
122
- const requests = this.networkMonitor.getRequests(filter);
123
60
  return {
124
61
  success: true,
125
62
  data: {
126
- requests,
127
- count: requests.length,
128
- isMonitoring: this.networkMonitor.isActive()
63
+ message: "Following network requests in real-time. Press Ctrl+C to stop.",
64
+ isLongRunning: true,
129
65
  },
130
- dataSource: 'direct',
131
- hasHistoricalData: false
66
+ isLongRunning: true,
132
67
  };
133
68
  }
134
- getNetworkMonitor() {
135
- return this.networkMonitor;
69
+ parseList(value) {
70
+ if (!value)
71
+ return [];
72
+ if (Array.isArray(value))
73
+ return value;
74
+ return value
75
+ .split(",")
76
+ .map((s) => s.trim())
77
+ .filter(Boolean);
78
+ }
79
+ shouldOutputRequest(request, methods, urlPattern, statusCodes) {
80
+ if (methods.length > 0 &&
81
+ !methods
82
+ .map((m) => m.toUpperCase())
83
+ .includes(request.method.toUpperCase())) {
84
+ return false;
85
+ }
86
+ if (urlPattern) {
87
+ const pattern = new RegExp(urlPattern, "i");
88
+ if (!pattern.test(request.url))
89
+ return false;
90
+ }
91
+ if (statusCodes.length > 0 &&
92
+ request.status !== undefined &&
93
+ !statusCodes.includes(request.status)) {
94
+ return false;
95
+ }
96
+ return true;
97
+ }
98
+ outputRequest(request, format) {
99
+ const status = request.status ?? "???";
100
+ const method = request.method.toUpperCase().padEnd(7);
101
+ const time = new Date(request.timestamp).toISOString();
102
+ switch (format) {
103
+ case "json":
104
+ console.log(JSON.stringify({
105
+ method: request.method,
106
+ url: request.url,
107
+ status: request.status,
108
+ timestamp: request.timestamp,
109
+ }));
110
+ break;
111
+ case "pretty": {
112
+ const statusColor = this.getStatusColor(request.status);
113
+ console.log(`[${time}] ${method} ${statusColor}${status}\x1b[0m ${request.url}`);
114
+ break;
115
+ }
116
+ case "text":
117
+ default:
118
+ console.log(`[${time}] ${method} ${status} ${request.url}`);
119
+ break;
120
+ }
121
+ }
122
+ getStatusColor(status) {
123
+ if (status === undefined || status === 0)
124
+ return "\x1b[31m";
125
+ if (status >= 500)
126
+ return "\x1b[31m";
127
+ if (status >= 400)
128
+ return "\x1b[33m";
129
+ if (status >= 300)
130
+ return "\x1b[36m";
131
+ return "\x1b[32m";
136
132
  }
137
- setNetworkMonitor(monitor) {
138
- this.networkMonitor = monitor;
133
+ getHelp() {
134
+ return `network - Follow network requests in real-time
135
+
136
+ Usage:
137
+ cdp network [options]
138
+
139
+ Options:
140
+ --methods <methods> Filter by HTTP methods (comma-separated: GET,POST,PUT,DELETE,...)
141
+ --urlPattern <pattern> Filter by URL pattern (regex, case-insensitive)
142
+ --statusCodes <codes> Filter by HTTP status codes (comma-separated: 200,404,500)
143
+ --format <format> Output format: text, json, or pretty (default: text)
144
+ -f, --follow Alias flag (follow mode is always active)
145
+
146
+ Examples:
147
+ cdp network # Follow all network requests
148
+ cdp network --methods POST,PUT # Follow only POST and PUT requests
149
+ cdp network --urlPattern "/api/" # Follow requests matching /\\/api\\//i
150
+ cdp network --statusCodes 404,500 # Follow requests with error status
151
+ cdp network --format json # Output as JSON (one object per line)
152
+ cdp network --format pretty # Colorized output
153
+ cdp network --methods GET --urlPattern "/api" # Combined filters
154
+
155
+ Note:
156
+ This command runs continuously and streams network requests in real-time.
157
+ Each entry is printed when the request fully completes (or fails).
158
+ Press Ctrl+C to stop. No background process is required.`;
139
159
  }
140
160
  }
141
161
  exports.ListNetworkRequestsHandler = ListNetworkRequestsHandler;
@@ -61,8 +61,8 @@ class RestartProxyHandler {
61
61
  restart - Restart the proxy server process
62
62
 
63
63
  Usage:
64
- chrome-cdp-cli restart
65
- chrome-cdp-cli restart --force
64
+ cdp restart
65
+ cdp restart --force
66
66
 
67
67
  Description:
68
68
  Restarts the proxy server process. This will:
@@ -82,10 +82,10 @@ Options:
82
82
 
83
83
  Examples:
84
84
  # Restart the proxy server
85
- chrome-cdp-cli restart
85
+ cdp restart
86
86
 
87
87
  # Force restart even if proxy is healthy
88
- chrome-cdp-cli restart --force
88
+ cdp restart --force
89
89
 
90
90
  Note:
91
91
  - Restarting the proxy will clear all stored console messages and network requests
@@ -5,18 +5,27 @@ const fs_1 = require("fs");
5
5
  const path_1 = require("path");
6
6
  class TakeScreenshotHandler {
7
7
  constructor() {
8
- this.name = 'screenshot';
8
+ this.name = "screenshot";
9
9
  }
10
10
  async execute(client, args) {
11
11
  const screenshotArgs = args;
12
12
  try {
13
- await client.send('Page.enable');
13
+ if (!screenshotArgs.filename && process.stdout.isTTY) {
14
+ process.stderr.write("Error: Binary output to terminal detected.\n" +
15
+ "Use --filename <path> to save to a file, or redirect: cdp screenshot > page.png\n");
16
+ return {
17
+ success: false,
18
+ error: "Binary output to terminal detected. Use --filename or redirect output.",
19
+ exitCode: 1,
20
+ };
21
+ }
22
+ await client.send("Page.enable");
14
23
  const params = this.buildScreenshotParams(screenshotArgs);
15
- const response = await client.send('Page.captureScreenshot', params);
24
+ const response = (await client.send("Page.captureScreenshot", params));
16
25
  if (!response || !response.data) {
17
26
  return {
18
27
  success: false,
19
- error: 'Failed to capture screenshot: empty response'
28
+ error: "Failed to capture screenshot: empty response",
20
29
  };
21
30
  }
22
31
  if (screenshotArgs.filename) {
@@ -26,31 +35,30 @@ class TakeScreenshotHandler {
26
35
  data: {
27
36
  message: `Screenshot saved to ${screenshotArgs.filename}`,
28
37
  filename: screenshotArgs.filename,
29
- format: screenshotArgs.format || 'png'
30
- }
38
+ imageFormat: screenshotArgs.imageFormat || "png",
39
+ },
31
40
  };
32
41
  }
42
+ process.stdout.write(Buffer.from(response.data, "base64"));
33
43
  return {
34
44
  success: true,
35
- data: {
36
- base64: response.data,
37
- format: screenshotArgs.format || 'png'
38
- }
45
+ data: null,
46
+ _rawOutput: true,
39
47
  };
40
48
  }
41
49
  catch (error) {
42
50
  return {
43
51
  success: false,
44
- error: error instanceof Error ? error.message : String(error)
52
+ error: error instanceof Error ? error.message : String(error),
45
53
  };
46
54
  }
47
55
  }
48
56
  buildScreenshotParams(args) {
49
57
  const params = {};
50
- params.format = args.format || 'png';
51
- if (args.format === 'jpeg' && args.quality !== undefined) {
58
+ params.format = args.imageFormat || "png";
59
+ if (args.imageFormat === "jpeg" && args.quality !== undefined) {
52
60
  if (args.quality < 0 || args.quality > 100) {
53
- throw new Error('JPEG quality must be between 0 and 100');
61
+ throw new Error("JPEG quality must be between 0 and 100");
54
62
  }
55
63
  params.quality = args.quality;
56
64
  }
@@ -60,7 +68,7 @@ class TakeScreenshotHandler {
60
68
  y: args.clip.y,
61
69
  width: args.clip.width,
62
70
  height: args.clip.height,
63
- scale: args.clip.scale || 1
71
+ scale: args.clip.scale || 1,
64
72
  };
65
73
  }
66
74
  if (args.width || args.height) {
@@ -74,7 +82,7 @@ class TakeScreenshotHandler {
74
82
  try {
75
83
  const dir = (0, path_1.dirname)(filename);
76
84
  await fs_1.promises.mkdir(dir, { recursive: true });
77
- const buffer = Buffer.from(base64Data, 'base64');
85
+ const buffer = Buffer.from(base64Data, "base64");
78
86
  await fs_1.promises.writeFile(filename, buffer);
79
87
  }
80
88
  catch (error) {
@@ -82,88 +90,91 @@ class TakeScreenshotHandler {
82
90
  }
83
91
  }
84
92
  validateArgs(args) {
85
- if (typeof args !== 'object' || args === null) {
93
+ if (typeof args !== "object" || args === null) {
86
94
  return false;
87
95
  }
88
96
  const screenshotArgs = args;
89
- if (screenshotArgs.filename !== undefined && typeof screenshotArgs.filename !== 'string') {
97
+ if (screenshotArgs.filename !== undefined &&
98
+ typeof screenshotArgs.filename !== "string") {
90
99
  return false;
91
100
  }
92
- if (screenshotArgs.width !== undefined && (typeof screenshotArgs.width !== 'number' || screenshotArgs.width <= 0)) {
101
+ if (screenshotArgs.width !== undefined &&
102
+ (typeof screenshotArgs.width !== "number" || screenshotArgs.width <= 0)) {
93
103
  return false;
94
104
  }
95
- if (screenshotArgs.height !== undefined && (typeof screenshotArgs.height !== 'number' || screenshotArgs.height <= 0)) {
105
+ if (screenshotArgs.height !== undefined &&
106
+ (typeof screenshotArgs.height !== "number" || screenshotArgs.height <= 0)) {
96
107
  return false;
97
108
  }
98
- if (screenshotArgs.format !== undefined && !['png', 'jpeg'].includes(screenshotArgs.format)) {
109
+ if (screenshotArgs.imageFormat !== undefined &&
110
+ !["png", "jpeg"].includes(screenshotArgs.imageFormat)) {
99
111
  return false;
100
112
  }
101
113
  if (screenshotArgs.quality !== undefined) {
102
- if (typeof screenshotArgs.quality !== 'number' || screenshotArgs.quality < 0 || screenshotArgs.quality > 100) {
114
+ if (typeof screenshotArgs.quality !== "number" ||
115
+ screenshotArgs.quality < 0 ||
116
+ screenshotArgs.quality > 100) {
103
117
  return false;
104
118
  }
105
119
  }
106
- if (screenshotArgs.fullPage !== undefined && typeof screenshotArgs.fullPage !== 'boolean') {
120
+ if (screenshotArgs.fullPage !== undefined &&
121
+ typeof screenshotArgs.fullPage !== "boolean") {
107
122
  return false;
108
123
  }
109
124
  if (screenshotArgs.clip !== undefined) {
110
125
  const clip = screenshotArgs.clip;
111
- if (typeof clip !== 'object' || clip === null) {
126
+ if (typeof clip !== "object" || clip === null) {
112
127
  return false;
113
128
  }
114
- if (typeof clip.x !== 'number' || typeof clip.y !== 'number' ||
115
- typeof clip.width !== 'number' || typeof clip.height !== 'number') {
129
+ if (typeof clip.x !== "number" ||
130
+ typeof clip.y !== "number" ||
131
+ typeof clip.width !== "number" ||
132
+ typeof clip.height !== "number") {
116
133
  return false;
117
134
  }
118
135
  if (clip.width <= 0 || clip.height <= 0) {
119
136
  return false;
120
137
  }
121
- if (clip.scale !== undefined && (typeof clip.scale !== 'number' || clip.scale <= 0)) {
138
+ if (clip.scale !== undefined &&
139
+ (typeof clip.scale !== "number" || clip.scale <= 0)) {
122
140
  return false;
123
141
  }
124
142
  }
125
143
  return true;
126
144
  }
127
145
  getHelp() {
128
- return `
129
- screenshot - Capture a screenshot of the current page
146
+ return `screenshot - Capture a screenshot of the current page
130
147
 
131
148
  Usage:
132
- screenshot
133
- screenshot --filename screenshot.png
134
- screenshot --width 1920 --height 1080 --filename full-hd.png
135
- screenshot --format jpeg --quality 80 --filename compressed.jpg
136
- screenshot --full-page --filename full-page.png
149
+ cdp screenshot # Output PNG binary to stdout
150
+ cdp screenshot --filename page.png # Save to file
151
+ cdp screenshot > page.png # Pipe binary to file
137
152
 
138
153
  Arguments:
139
- --filename <path> Output filename (if not provided, returns base64 data)
140
- --width <pixels> Screenshot width (requires viewport adjustment)
141
- --height <pixels> Screenshot height (requires viewport adjustment)
142
- --format <png|jpeg> Image format (default: png)
143
- --quality <1-100> JPEG quality (only for jpeg format)
144
- --full-page Capture full page beyond viewport (default: false)
145
- --clip-x <pixels> Clip rectangle X coordinate
146
- --clip-y <pixels> Clip rectangle Y coordinate
147
- --clip-width <pixels> Clip rectangle width
148
- --clip-height <pixels> Clip rectangle height
149
- --clip-scale <number> Clip rectangle scale factor
154
+ --filename <path> Output filename (if omitted, binary is written to stdout)
155
+ --width <pixels> Screenshot width
156
+ --height <pixels> Screenshot height
157
+ --image-format <png|jpeg> Image encoding format (default: png)
158
+ --quality <1-100> JPEG quality (only for jpeg format)
159
+ --full-page Capture full page beyond viewport (default: false)
160
+ --clip-x <pixels> Clip rectangle X coordinate
161
+ --clip-y <pixels> Clip rectangle Y coordinate
162
+ --clip-width <pixels> Clip rectangle width
163
+ --clip-height <pixels> Clip rectangle height
164
+ --clip-scale <number> Clip rectangle scale factor
150
165
 
151
166
  Examples:
152
- # Basic screenshot
153
- screenshot --filename page.png
154
-
155
- # High quality JPEG
156
- screenshot --format jpeg --quality 95 --filename page.jpg
157
-
158
- # Full page screenshot
159
- screenshot --full-page --filename full-page.png
160
-
161
- # Clipped screenshot
162
- screenshot --clip-x 100 --clip-y 100 --clip-width 800 --clip-height 600 --filename clipped.png
167
+ cdp screenshot --filename page.png
168
+ cdp screenshot > page.png
169
+ cdp screenshot --image-format jpeg --quality 90 --filename page.jpg
170
+ cdp screenshot --full-page --filename full-page.png
171
+ cdp screenshot --clip-x 100 --clip-y 100 --clip-width 800 --clip-height 600 --filename clipped.png
163
172
 
164
- # Return base64 data (no file)
165
- screenshot --format png
166
- `;
173
+ Note:
174
+ When no --filename is given, raw binary image data is written to stdout.
175
+ Redirecting to a file (> page.png) or piping is the intended usage.
176
+ Running without redirection in a terminal will show a warning and exit.
177
+ The global --format flag has no effect on screenshot output.`;
167
178
  }
168
179
  }
169
180
  exports.TakeScreenshotHandler = TakeScreenshotHandler;