chrome-cdp-cli 1.0.0 → 1.0.2

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
@@ -72,28 +72,28 @@ chrome --remote-debugging-port=9222 --no-first-run --no-default-browser-check
72
72
 
73
73
  ```bash
74
74
  # Connect and execute JavaScript
75
- chrome-cli eval "document.title"
75
+ chrome-cdp-cli eval "document.title"
76
76
 
77
77
  # Or use with npx (no installation needed)
78
78
  npx chrome-cdp-cli eval "document.title"
79
79
 
80
80
  # Navigate to a website
81
- chrome-cli navigate_page "https://example.com"
81
+ chrome-cdp-cli navigate_page "https://example.com"
82
82
 
83
83
  # Take a screenshot
84
- chrome-cli take_screenshot --output screenshot.png
84
+ chrome-cdp-cli screenshot --filename screenshot.png
85
85
 
86
86
  # Click an element
87
- chrome-cli click "#submit-button"
87
+ chrome-cdp-cli click "#submit-button"
88
88
 
89
89
  # Fill a form field
90
- chrome-cli fill "#email" "user@example.com"
90
+ chrome-cdp-cli fill "#email" "user@example.com"
91
91
 
92
92
  # Get help for all commands
93
- chrome-cli --help
93
+ chrome-cdp-cli --help
94
94
 
95
95
  # Get help for a specific command
96
- chrome-cli eval --help
96
+ chrome-cdp-cli eval --help
97
97
  ```
98
98
 
99
99
  ## Command Reference
@@ -117,13 +117,13 @@ All commands support these connection options:
117
117
  #### JavaScript Execution
118
118
  ```bash
119
119
  # Execute JavaScript expression
120
- chrome-cli eval "console.log('Hello World')"
120
+ chrome-cdp-cli eval "console.log('Hello World')"
121
121
 
122
122
  # Execute from file
123
- chrome-cli eval --file script.js
123
+ chrome-cdp-cli eval --file script.js
124
124
 
125
125
  # Execute with timeout
126
- chrome-cli eval "await new Promise(r => setTimeout(r, 5000))" --timeout 10000
126
+ chrome-cdp-cli eval "await new Promise(r => setTimeout(r, 5000))" --timeout 10000
127
127
 
128
128
  # Using npx (no installation required)
129
129
  npx chrome-cdp-cli eval "document.title"
@@ -133,56 +133,59 @@ npx chrome-cdp-cli eval --file script.js
133
133
  #### Page Management
134
134
  ```bash
135
135
  # Navigate to URL
136
- chrome-cli navigate_page "https://example.com"
136
+ chrome-cdp-cli navigate_page "https://example.com"
137
137
 
138
138
  # Create new tab
139
- chrome-cli new_page
139
+ chrome-cdp-cli new_page
140
140
 
141
141
  # List all pages
142
- chrome-cli list_pages
142
+ chrome-cdp-cli list_pages
143
143
 
144
144
  # Close current page
145
- chrome-cli close_page
145
+ chrome-cdp-cli close_page
146
146
 
147
147
  # Switch to specific page
148
- chrome-cli select_page --id "page-id"
148
+ chrome-cdp-cli select_page --id "page-id"
149
149
  ```
150
150
 
151
151
  #### Element Interaction
152
152
  ```bash
153
153
  # Click element
154
- chrome-cli click "#button"
154
+ chrome-cdp-cli click "#button"
155
155
 
156
156
  # Fill input field
157
- chrome-cli fill "#email" "user@example.com"
157
+ chrome-cdp-cli fill "#email" "user@example.com"
158
158
 
159
159
  # Hover over element
160
- chrome-cli hover ".menu-item"
160
+ chrome-cdp-cli hover ".menu-item"
161
161
 
162
162
  # Wait for element
163
- chrome-cli wait_for "#loading" --timeout 5000
163
+ chrome-cdp-cli wait_for "#loading" --timeout 5000
164
164
  ```
165
165
 
166
166
  #### Visual Capture
167
167
  ```bash
168
168
  # Take screenshot
169
- chrome-cli take_screenshot --output screenshot.png
169
+ chrome-cdp-cli screenshot --filename screenshot.png
170
170
 
171
- # Full page screenshot
172
- chrome-cli take_snapshot --output fullpage.png
171
+ # Full page screenshot
172
+ chrome-cdp-cli screenshot --full-page --filename fullpage.png
173
+
174
+ # DOM snapshot
175
+ chrome-cdp-cli snapshot --filename dom-snapshot.json
173
176
 
174
177
  # Custom dimensions
175
- chrome-cli take_screenshot --width 1920 --height 1080 --output custom.png
178
+ chrome-cdp-cli screenshot --width 1920 --height 1080 --filename custom.png
176
179
 
177
180
  # Get HTML content
178
- chrome-cli get_html --output page.html
181
+ chrome-cdp-cli get_html --output page.html
179
182
  ```
180
183
 
181
184
  ## Configuration
182
185
 
183
186
  ### Configuration File
184
187
 
185
- Create a `.chrome-cli.json` file in your project root or home directory:
188
+ Create a `.chrome-cdp-cli.json` file in your project root or home directory:
186
189
 
187
190
  ```json
188
191
  {
@@ -347,7 +350,7 @@ console.log(result);
347
350
  Enable verbose logging for troubleshooting:
348
351
 
349
352
  ```bash
350
- chrome-cli --verbose eval "console.log('debug')"
353
+ chrome-cdp-cli --verbose eval "console.log('debug')"
351
354
  ```
352
355
 
353
356
  ### Packaging
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.CLIApplication = void 0;
4
4
  const CLIInterface_1 = require("./CLIInterface");
5
5
  const ConnectionManager_1 = require("../connection/ConnectionManager");
6
- const EvaluateScriptHandler_1 = require("../handlers/EvaluateScriptHandler");
6
+ const handlers_1 = require("../handlers");
7
7
  const logger_1 = require("../utils/logger");
8
8
  const CommandRouter_1 = require("./CommandRouter");
9
9
  class CLIApplication {
@@ -14,7 +14,9 @@ class CLIApplication {
14
14
  this.setupHandlers();
15
15
  }
16
16
  setupHandlers() {
17
- this.cli.registerHandler(new EvaluateScriptHandler_1.EvaluateScriptHandler());
17
+ this.cli.registerHandler(new handlers_1.EvaluateScriptHandler());
18
+ this.cli.registerHandler(new handlers_1.TakeScreenshotHandler());
19
+ this.cli.registerHandler(new handlers_1.TakeSnapshotHandler());
18
20
  }
19
21
  async run(argv) {
20
22
  try {
@@ -48,7 +48,7 @@ class CLIInterface {
48
48
  }
49
49
  setupProgram() {
50
50
  this.program
51
- .name('chrome-cli')
51
+ .name('chrome-cdp-cli')
52
52
  .description('Command-line tool for controlling Chrome browser via DevTools Protocol')
53
53
  .version('1.0.0')
54
54
  .allowUnknownOption(true)
@@ -139,9 +139,9 @@ class CLIInterface {
139
139
  }
140
140
  else {
141
141
  const defaultPaths = [
142
- '.chrome-cli.json',
143
- path.join(process.env.HOME || '', '.chrome-cli.json'),
144
- '/etc/chrome-cli.json'
142
+ '.chrome-cdp-cli.json',
143
+ path.join(process.env.HOME || '', '.chrome-cdp-cli.json'),
144
+ '/etc/chrome-cdp-cli.json'
145
145
  ];
146
146
  for (const defaultPath of defaultPaths) {
147
147
  try {
@@ -239,12 +239,45 @@ class CLIInterface {
239
239
  commandArgs.text = args[1];
240
240
  break;
241
241
  case 'screenshot':
242
+ if (options.filename)
243
+ commandArgs.filename = options.filename;
242
244
  if (options.output || options.o)
243
- commandArgs.output = options.output || options.o;
245
+ commandArgs.filename = options.output || options.o;
244
246
  if (options.width || options.w)
245
247
  commandArgs.width = parseInt(options.width || options.w, 10);
246
248
  if (options.height || options.h)
247
249
  commandArgs.height = parseInt(options.height || options.h, 10);
250
+ if (options.format)
251
+ commandArgs.format = options.format;
252
+ if (options.quality)
253
+ commandArgs.quality = parseInt(options.quality, 10);
254
+ if (options['full-page'])
255
+ commandArgs.fullPage = true;
256
+ if (options['clip-x'] || options['clip-y'] || options['clip-width'] || options['clip-height'] || options['clip-scale']) {
257
+ commandArgs.clip = {
258
+ x: options['clip-x'] ? parseInt(options['clip-x'], 10) : 0,
259
+ y: options['clip-y'] ? parseInt(options['clip-y'], 10) : 0,
260
+ width: options['clip-width'] ? parseInt(options['clip-width'], 10) : 0,
261
+ height: options['clip-height'] ? parseInt(options['clip-height'], 10) : 0,
262
+ scale: options['clip-scale'] ? parseFloat(options['clip-scale']) : 1
263
+ };
264
+ }
265
+ break;
266
+ case 'snapshot':
267
+ if (options.filename)
268
+ commandArgs.filename = options.filename;
269
+ if (options.output || options.o)
270
+ commandArgs.filename = options.output || options.o;
271
+ if (options.format)
272
+ commandArgs.format = options.format;
273
+ if (options['include-styles'] !== undefined)
274
+ commandArgs.includeStyles = options['include-styles'] !== 'false';
275
+ if (options['include-attributes'] !== undefined)
276
+ commandArgs.includeAttributes = options['include-attributes'] !== 'false';
277
+ if (options['include-paint-order'])
278
+ commandArgs.includePaintOrder = true;
279
+ if (options['include-text-index'])
280
+ commandArgs.includeTextIndex = true;
248
281
  break;
249
282
  case 'console_messages':
250
283
  if (options.filter)
@@ -186,7 +186,7 @@ class CommandRouter {
186
186
  return `
187
187
  Chrome DevTools CLI - Command-line tool for controlling Chrome browser
188
188
 
189
- Usage: chrome-cli [options] <command> [command-options]
189
+ Usage: chrome-cdp-cli [options] <command> [command-options]
190
190
 
191
191
  Global Options:
192
192
  -h, --host <host> Chrome host address (default: localhost)
@@ -201,12 +201,14 @@ Available Commands:
201
201
  ${commands.map(cmd => ` ${cmd.padEnd(20)} - ${this.getCommandDescription(cmd)}`).join('\n')}
202
202
 
203
203
  Examples:
204
- chrome-cli eval "document.title"
205
- chrome-cli eval --file script.js
206
- chrome-cli help <command>
204
+ chrome-cdp-cli eval "document.title"
205
+ chrome-cdp-cli eval --file script.js
206
+ chrome-cdp-cli screenshot --filename page.png
207
+ chrome-cdp-cli snapshot --format html --filename dom.html
208
+ chrome-cdp-cli help <command>
207
209
 
208
210
  For more information about a specific command, use:
209
- chrome-cli help <command>
211
+ chrome-cdp-cli help <command>
210
212
  `;
211
213
  }
212
214
  getCommandDescription(commandName) {
@@ -223,8 +225,8 @@ For more information about a specific command, use:
223
225
  'click': 'Click element',
224
226
  'fill': 'Fill form field',
225
227
  'hover': 'Hover over element',
226
- 'screenshot': 'Take screenshot',
227
- 'get-html': 'Get page HTML content',
228
+ 'screenshot': 'Capture page screenshot',
229
+ 'snapshot': 'Capture DOM snapshot with structure and styles',
228
230
  'console-messages': 'Get console messages',
229
231
  'network-requests': 'Get network requests',
230
232
  'help': 'Show help information'
@@ -0,0 +1,169 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TakeScreenshotHandler = void 0;
4
+ const fs_1 = require("fs");
5
+ const path_1 = require("path");
6
+ class TakeScreenshotHandler {
7
+ constructor() {
8
+ this.name = 'screenshot';
9
+ }
10
+ async execute(client, args) {
11
+ const screenshotArgs = args;
12
+ try {
13
+ await client.send('Page.enable');
14
+ const params = this.buildScreenshotParams(screenshotArgs);
15
+ const response = await client.send('Page.captureScreenshot', params);
16
+ if (!response || !response.data) {
17
+ return {
18
+ success: false,
19
+ error: 'Failed to capture screenshot: empty response'
20
+ };
21
+ }
22
+ if (screenshotArgs.filename) {
23
+ await this.saveScreenshot(response.data, screenshotArgs.filename);
24
+ return {
25
+ success: true,
26
+ data: {
27
+ message: `Screenshot saved to ${screenshotArgs.filename}`,
28
+ filename: screenshotArgs.filename,
29
+ format: screenshotArgs.format || 'png'
30
+ }
31
+ };
32
+ }
33
+ return {
34
+ success: true,
35
+ data: {
36
+ base64: response.data,
37
+ format: screenshotArgs.format || 'png'
38
+ }
39
+ };
40
+ }
41
+ catch (error) {
42
+ return {
43
+ success: false,
44
+ error: error instanceof Error ? error.message : String(error)
45
+ };
46
+ }
47
+ }
48
+ buildScreenshotParams(args) {
49
+ const params = {};
50
+ params.format = args.format || 'png';
51
+ if (args.format === 'jpeg' && args.quality !== undefined) {
52
+ if (args.quality < 0 || args.quality > 100) {
53
+ throw new Error('JPEG quality must be between 0 and 100');
54
+ }
55
+ params.quality = args.quality;
56
+ }
57
+ if (args.clip) {
58
+ params.clip = {
59
+ x: args.clip.x,
60
+ y: args.clip.y,
61
+ width: args.clip.width,
62
+ height: args.clip.height,
63
+ scale: args.clip.scale || 1
64
+ };
65
+ }
66
+ if (args.width || args.height) {
67
+ }
68
+ if (args.fullPage) {
69
+ params.captureBeyondViewport = true;
70
+ }
71
+ return params;
72
+ }
73
+ async saveScreenshot(base64Data, filename) {
74
+ try {
75
+ const dir = (0, path_1.dirname)(filename);
76
+ await fs_1.promises.mkdir(dir, { recursive: true });
77
+ const buffer = Buffer.from(base64Data, 'base64');
78
+ await fs_1.promises.writeFile(filename, buffer);
79
+ }
80
+ catch (error) {
81
+ throw new Error(`Failed to save screenshot to "${filename}": ${error instanceof Error ? error.message : String(error)}`);
82
+ }
83
+ }
84
+ validateArgs(args) {
85
+ if (typeof args !== 'object' || args === null) {
86
+ return false;
87
+ }
88
+ const screenshotArgs = args;
89
+ if (screenshotArgs.filename !== undefined && typeof screenshotArgs.filename !== 'string') {
90
+ return false;
91
+ }
92
+ if (screenshotArgs.width !== undefined && (typeof screenshotArgs.width !== 'number' || screenshotArgs.width <= 0)) {
93
+ return false;
94
+ }
95
+ if (screenshotArgs.height !== undefined && (typeof screenshotArgs.height !== 'number' || screenshotArgs.height <= 0)) {
96
+ return false;
97
+ }
98
+ if (screenshotArgs.format !== undefined && !['png', 'jpeg'].includes(screenshotArgs.format)) {
99
+ return false;
100
+ }
101
+ if (screenshotArgs.quality !== undefined) {
102
+ if (typeof screenshotArgs.quality !== 'number' || screenshotArgs.quality < 0 || screenshotArgs.quality > 100) {
103
+ return false;
104
+ }
105
+ }
106
+ if (screenshotArgs.fullPage !== undefined && typeof screenshotArgs.fullPage !== 'boolean') {
107
+ return false;
108
+ }
109
+ if (screenshotArgs.clip !== undefined) {
110
+ const clip = screenshotArgs.clip;
111
+ if (typeof clip !== 'object' || clip === null) {
112
+ return false;
113
+ }
114
+ if (typeof clip.x !== 'number' || typeof clip.y !== 'number' ||
115
+ typeof clip.width !== 'number' || typeof clip.height !== 'number') {
116
+ return false;
117
+ }
118
+ if (clip.width <= 0 || clip.height <= 0) {
119
+ return false;
120
+ }
121
+ if (clip.scale !== undefined && (typeof clip.scale !== 'number' || clip.scale <= 0)) {
122
+ return false;
123
+ }
124
+ }
125
+ return true;
126
+ }
127
+ getHelp() {
128
+ return `
129
+ screenshot - Capture a screenshot of the current page
130
+
131
+ 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
137
+
138
+ 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
150
+
151
+ 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
163
+
164
+ # Return base64 data (no file)
165
+ screenshot --format png
166
+ `;
167
+ }
168
+ }
169
+ exports.TakeScreenshotHandler = TakeScreenshotHandler;
@@ -0,0 +1,306 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TakeSnapshotHandler = void 0;
4
+ const fs_1 = require("fs");
5
+ const path_1 = require("path");
6
+ class TakeSnapshotHandler {
7
+ constructor() {
8
+ this.name = 'snapshot';
9
+ }
10
+ async execute(client, args) {
11
+ const snapshotArgs = args;
12
+ try {
13
+ await client.send('DOMSnapshot.enable');
14
+ await client.send('DOM.enable');
15
+ await client.send('CSS.enable');
16
+ const params = this.buildSnapshotParams(snapshotArgs);
17
+ const response = await client.send('DOMSnapshot.captureSnapshot', params);
18
+ if (!response || !response.documents || response.documents.length === 0) {
19
+ return {
20
+ success: false,
21
+ error: 'Failed to capture DOM snapshot: empty response'
22
+ };
23
+ }
24
+ const processedSnapshot = this.processSnapshot(response, snapshotArgs);
25
+ if (snapshotArgs.filename) {
26
+ await this.saveSnapshot(processedSnapshot, snapshotArgs.filename, snapshotArgs.format);
27
+ return {
28
+ success: true,
29
+ data: {
30
+ message: `DOM snapshot saved to ${snapshotArgs.filename}`,
31
+ filename: snapshotArgs.filename,
32
+ format: snapshotArgs.format || 'json',
33
+ documentsCount: response.documents.length,
34
+ nodesCount: response.documents[0]?.nodes?.nodeName?.length || 0
35
+ }
36
+ };
37
+ }
38
+ return {
39
+ success: true,
40
+ data: {
41
+ snapshot: processedSnapshot,
42
+ format: snapshotArgs.format || 'json',
43
+ documentsCount: response.documents.length,
44
+ nodesCount: response.documents[0]?.nodes?.nodeName?.length || 0
45
+ }
46
+ };
47
+ }
48
+ catch (error) {
49
+ return {
50
+ success: false,
51
+ error: error instanceof Error ? error.message : String(error)
52
+ };
53
+ }
54
+ }
55
+ buildSnapshotParams(args) {
56
+ const params = {};
57
+ params.computedStyles = args.includeStyles !== false;
58
+ params.includeDOMRects = args.includeAttributes !== false;
59
+ if (args.includePaintOrder) {
60
+ params.includePaintOrder = true;
61
+ }
62
+ if (args.includeTextIndex) {
63
+ params.includeTextIndex = true;
64
+ }
65
+ params.computedStyleWhitelist = [];
66
+ return params;
67
+ }
68
+ processSnapshot(response, args) {
69
+ if (args.format === 'html') {
70
+ return this.convertToHTML(response);
71
+ }
72
+ return {
73
+ metadata: {
74
+ captureTime: new Date().toISOString(),
75
+ documentsCount: response.documents.length,
76
+ stringsCount: response.strings.length
77
+ },
78
+ documents: response.documents.map(doc => ({
79
+ url: doc.documentURL,
80
+ title: doc.title,
81
+ baseURL: doc.baseURL,
82
+ language: doc.contentLanguage,
83
+ encoding: doc.encodingName,
84
+ frameId: doc.frameId,
85
+ domTree: this.buildDOMTree(doc, response.strings),
86
+ layout: this.processLayoutInfo(doc.layout, response.strings),
87
+ textBoxes: doc.textBoxes
88
+ })),
89
+ strings: response.strings
90
+ };
91
+ }
92
+ buildDOMTree(doc, strings) {
93
+ const nodes = doc.nodes;
94
+ if (!nodes.nodeName || !nodes.nodeType) {
95
+ return [];
96
+ }
97
+ const nodeCount = nodes.nodeName.length;
98
+ const tree = [];
99
+ const nodeMap = new Map();
100
+ for (let i = 0; i < nodeCount; i++) {
101
+ const node = {
102
+ index: i,
103
+ nodeType: nodes.nodeType[i],
104
+ nodeName: nodes.nodeName[i],
105
+ nodeValue: nodes.nodeValue?.[i],
106
+ backendNodeId: nodes.backendNodeId?.[i],
107
+ children: []
108
+ };
109
+ if (nodes.attributes?.[i]) {
110
+ const attrs = nodes.attributes[i];
111
+ node.attributes = {};
112
+ for (let j = 0; j < attrs.length; j += 2) {
113
+ const name = strings[parseInt(attrs[j])];
114
+ const value = strings[parseInt(attrs[j + 1])];
115
+ node.attributes[name] = value;
116
+ }
117
+ }
118
+ if (nodes.textValue?.index.includes(i)) {
119
+ const textIndex = nodes.textValue.index.indexOf(i);
120
+ const stringIndex = nodes.textValue.value[textIndex];
121
+ if (typeof stringIndex === 'number') {
122
+ node.textValue = strings[stringIndex];
123
+ }
124
+ }
125
+ if (nodes.inputValue?.index.includes(i)) {
126
+ const inputIndex = nodes.inputValue.index.indexOf(i);
127
+ const stringIndex = nodes.inputValue.value[inputIndex];
128
+ if (typeof stringIndex === 'number') {
129
+ node.inputValue = strings[stringIndex];
130
+ }
131
+ }
132
+ if (nodes.inputChecked?.index.includes(i)) {
133
+ node.inputChecked = true;
134
+ }
135
+ if (nodes.optionSelected?.index.includes(i)) {
136
+ node.optionSelected = true;
137
+ }
138
+ if (nodes.isClickable?.index.includes(i)) {
139
+ node.isClickable = true;
140
+ }
141
+ nodeMap.set(i, node);
142
+ }
143
+ for (let i = 0; i < nodeCount; i++) {
144
+ const parentIndex = nodes.parentIndex?.[i];
145
+ const node = nodeMap.get(i);
146
+ if (parentIndex !== undefined && parentIndex >= 0) {
147
+ const parent = nodeMap.get(parentIndex);
148
+ if (parent) {
149
+ parent.children.push(node);
150
+ }
151
+ }
152
+ else {
153
+ tree.push(node);
154
+ }
155
+ }
156
+ return tree;
157
+ }
158
+ processLayoutInfo(layout, strings) {
159
+ return {
160
+ nodeCount: layout.nodeIndex.length,
161
+ styles: layout.styles.map(styleArray => styleArray.map(styleIndex => strings[parseInt(styleIndex)])),
162
+ bounds: layout.bounds,
163
+ text: layout.text,
164
+ paintOrders: layout.paintOrders,
165
+ offsetRects: layout.offsetRects,
166
+ scrollRects: layout.scrollRects,
167
+ clientRects: layout.clientRects,
168
+ blendedBackgroundColors: layout.blendedBackgroundColors,
169
+ textColorOpacities: layout.textColorOpacities
170
+ };
171
+ }
172
+ convertToHTML(response) {
173
+ const doc = response.documents[0];
174
+ if (!doc)
175
+ return '';
176
+ const tree = this.buildDOMTree(doc, response.strings);
177
+ return this.renderNodeAsHTML(tree[0], 0);
178
+ }
179
+ renderNodeAsHTML(node, depth) {
180
+ if (!node)
181
+ return '';
182
+ const indent = ' '.repeat(depth);
183
+ if (node.nodeType === 3) {
184
+ const text = node.nodeValue || node.textValue || '';
185
+ return text.trim() ? `${indent}${text.trim()}\n` : '';
186
+ }
187
+ if (node.nodeType === 8) {
188
+ return `${indent}<!-- ${node.nodeValue || ''} -->\n`;
189
+ }
190
+ if (node.nodeType === 1) {
191
+ const tagName = node.nodeName.toLowerCase();
192
+ let html = `${indent}<${tagName}`;
193
+ if (node.attributes) {
194
+ for (const [name, value] of Object.entries(node.attributes)) {
195
+ html += ` ${name}="${value}"`;
196
+ }
197
+ }
198
+ const selfClosing = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr'];
199
+ if (selfClosing.includes(tagName)) {
200
+ html += ' />\n';
201
+ return html;
202
+ }
203
+ html += '>';
204
+ if (node.children && node.children.length > 0) {
205
+ html += '\n';
206
+ for (const child of node.children) {
207
+ html += this.renderNodeAsHTML(child, depth + 1);
208
+ }
209
+ html += `${indent}</${tagName}>\n`;
210
+ }
211
+ else {
212
+ html += `</${tagName}>\n`;
213
+ }
214
+ return html;
215
+ }
216
+ return '';
217
+ }
218
+ async saveSnapshot(snapshotData, filename, format) {
219
+ try {
220
+ const dir = (0, path_1.dirname)(filename);
221
+ await fs_1.promises.mkdir(dir, { recursive: true });
222
+ let content;
223
+ if (format === 'html') {
224
+ content = snapshotData;
225
+ }
226
+ else {
227
+ content = JSON.stringify(snapshotData, null, 2);
228
+ }
229
+ await fs_1.promises.writeFile(filename, content, 'utf-8');
230
+ }
231
+ catch (error) {
232
+ throw new Error(`Failed to save DOM snapshot to "${filename}": ${error instanceof Error ? error.message : String(error)}`);
233
+ }
234
+ }
235
+ validateArgs(args) {
236
+ if (typeof args !== 'object' || args === null) {
237
+ return false;
238
+ }
239
+ const snapshotArgs = args;
240
+ if (snapshotArgs.filename !== undefined && typeof snapshotArgs.filename !== 'string') {
241
+ return false;
242
+ }
243
+ if (snapshotArgs.format !== undefined && !['json', 'html'].includes(snapshotArgs.format)) {
244
+ return false;
245
+ }
246
+ if (snapshotArgs.includeStyles !== undefined && typeof snapshotArgs.includeStyles !== 'boolean') {
247
+ return false;
248
+ }
249
+ if (snapshotArgs.includeAttributes !== undefined && typeof snapshotArgs.includeAttributes !== 'boolean') {
250
+ return false;
251
+ }
252
+ if (snapshotArgs.includePaintOrder !== undefined && typeof snapshotArgs.includePaintOrder !== 'boolean') {
253
+ return false;
254
+ }
255
+ if (snapshotArgs.includeTextIndex !== undefined && typeof snapshotArgs.includeTextIndex !== 'boolean') {
256
+ return false;
257
+ }
258
+ return true;
259
+ }
260
+ getHelp() {
261
+ return `
262
+ snapshot - Capture a DOM snapshot including DOM tree structure, computed styles, and layout information
263
+
264
+ Usage:
265
+ snapshot
266
+ snapshot --filename dom-snapshot.json
267
+ snapshot --format html --filename page-structure.html
268
+ snapshot --include-paint-order --filename detailed-snapshot.json
269
+
270
+ Arguments:
271
+ --filename <path> Output filename (if not provided, returns data directly)
272
+ --format <json|html> Output format (default: json)
273
+ --include-styles Include computed styles (default: true)
274
+ --include-attributes Include DOM attributes (default: true)
275
+ --include-paint-order Include paint order information (default: false)
276
+ --include-text-index Include text node indices (default: false)
277
+
278
+ Output Formats:
279
+ json: Structured JSON with DOM tree, layout info, and computed styles
280
+ html: Reconstructed HTML representation of the DOM
281
+
282
+ Examples:
283
+ # Basic DOM snapshot as JSON
284
+ snapshot --filename dom-snapshot.json
285
+
286
+ # HTML representation
287
+ snapshot --format html --filename page-structure.html
288
+
289
+ # Detailed snapshot with paint order
290
+ snapshot --include-paint-order --filename detailed-snapshot.json
291
+
292
+ # Return data directly (no file)
293
+ snapshot --format json
294
+
295
+ Note:
296
+ DOM snapshots capture the complete DOM tree structure including:
297
+ - Element hierarchy and attributes
298
+ - Computed CSS styles for each element
299
+ - Layout information (bounds, positioning)
300
+ - Text content and form values
301
+ - Visibility and clickability information
302
+ - Paint order and rendering details (optional)
303
+ `;
304
+ }
305
+ }
306
+ exports.TakeSnapshotHandler = TakeSnapshotHandler;
@@ -15,3 +15,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./EvaluateScriptHandler"), exports);
18
+ __exportStar(require("./TakeScreenshotHandler"), exports);
19
+ __exportStar(require("./TakeSnapshotHandler"), exports);
package/package.json CHANGED
@@ -1,12 +1,11 @@
1
1
  {
2
2
  "name": "chrome-cdp-cli",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Command-line tool for controlling Chrome browser instances via the Chrome DevTools Protocol",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "bin": {
8
- "chrome-cli": "dist/index.js",
9
- "chrome-devtools-cli": "dist/index.js"
8
+ "chrome-cdp-cli": "dist/index.js"
10
9
  },
11
10
  "files": [
12
11
  "dist/**/*",
@@ -52,12 +51,12 @@
52
51
  "license": "MIT",
53
52
  "repository": {
54
53
  "type": "git",
55
- "url": "https://github.com/nickxiao42/chrome-devtools-cli.git"
54
+ "url": "https://github.com/nicoster/chrome-devtools-cli.git"
56
55
  },
57
56
  "bugs": {
58
- "url": "https://github.com/nickxiao42/chrome-devtools-cli/issues"
57
+ "url": "https://github.com/nicoster/chrome-devtools-cli/issues"
59
58
  },
60
- "homepage": "https://github.com/nickxiao42/chrome-devtools-cli#readme",
59
+ "homepage": "https://github.com/nicoster/chrome-devtools-cli#readme",
61
60
  "dependencies": {
62
61
  "commander": "^11.1.0",
63
62
  "node-fetch": "^2.7.0",