chrome-cdp-cli 1.7.8 → 1.7.9
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/dist/handlers/TakeSnapshotHandler.js +242 -61
- package/package.json +1 -1
|
@@ -141,26 +141,217 @@ class TakeSnapshotHandler {
|
|
|
141
141
|
if (args.format === 'html') {
|
|
142
142
|
return this.convertToHTML(response);
|
|
143
143
|
}
|
|
144
|
+
const doc = response.documents[0];
|
|
145
|
+
if (!doc) {
|
|
146
|
+
return {
|
|
147
|
+
error: 'No documents found'
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
const textSnapshot = this.createTextSnapshot(doc, response.strings);
|
|
144
151
|
return {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
stringsCount: response.strings.length
|
|
149
|
-
},
|
|
150
|
-
documents: response.documents.map(doc => ({
|
|
151
|
-
url: doc.documentURL,
|
|
152
|
-
title: doc.title,
|
|
153
|
-
baseURL: doc.baseURL,
|
|
154
|
-
language: doc.contentLanguage,
|
|
155
|
-
encoding: doc.encodingName,
|
|
156
|
-
frameId: doc.frameId,
|
|
157
|
-
domTree: this.buildDOMTree(doc, response.strings),
|
|
158
|
-
layout: this.processLayoutInfo(doc.layout, response.strings),
|
|
159
|
-
textBoxes: doc.textBoxes
|
|
160
|
-
})),
|
|
161
|
-
strings: response.strings
|
|
152
|
+
url: doc.documentURL,
|
|
153
|
+
title: doc.title,
|
|
154
|
+
snapshot: textSnapshot
|
|
162
155
|
};
|
|
163
156
|
}
|
|
157
|
+
createTextSnapshot(doc, strings) {
|
|
158
|
+
const nodes = doc.nodes;
|
|
159
|
+
if (!nodes.nodeName || !nodes.nodeType) {
|
|
160
|
+
return 'Empty document';
|
|
161
|
+
}
|
|
162
|
+
const nodeTree = this.buildNodeTree(doc, strings);
|
|
163
|
+
let output = `PAGE: ${doc.title || 'Untitled'} (${doc.documentURL})\n`;
|
|
164
|
+
const bodyNode = this.findBodyNode(nodeTree);
|
|
165
|
+
if (bodyNode) {
|
|
166
|
+
output += this.renderNodeAsText(bodyNode, 0);
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
for (const node of nodeTree) {
|
|
170
|
+
if (this.shouldIncludeNode(node)) {
|
|
171
|
+
output += this.renderNodeAsText(node, 0);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return output;
|
|
176
|
+
}
|
|
177
|
+
buildNodeTree(doc, strings) {
|
|
178
|
+
const nodes = doc.nodes;
|
|
179
|
+
const nodeCount = nodes.nodeName.length;
|
|
180
|
+
const nodeMap = new Map();
|
|
181
|
+
for (let i = 0; i < nodeCount; i++) {
|
|
182
|
+
const node = {
|
|
183
|
+
index: i,
|
|
184
|
+
nodeType: nodes.nodeType[i],
|
|
185
|
+
nodeName: nodes.nodeName[i].toLowerCase(),
|
|
186
|
+
children: [],
|
|
187
|
+
textContent: '',
|
|
188
|
+
attributes: {}
|
|
189
|
+
};
|
|
190
|
+
if (nodes.nodeValue?.[i]) {
|
|
191
|
+
node.textContent = nodes.nodeValue[i].trim();
|
|
192
|
+
}
|
|
193
|
+
if (nodes.textValue?.index.includes(i)) {
|
|
194
|
+
const textIndex = nodes.textValue.index.indexOf(i);
|
|
195
|
+
const stringIndex = nodes.textValue.value[textIndex];
|
|
196
|
+
if (typeof stringIndex === 'number' && strings[stringIndex]) {
|
|
197
|
+
node.textContent = strings[stringIndex].trim();
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (nodes.attributes?.[i]) {
|
|
201
|
+
const attrs = nodes.attributes[i];
|
|
202
|
+
for (let j = 0; j < attrs.length; j += 2) {
|
|
203
|
+
const name = strings[parseInt(attrs[j])];
|
|
204
|
+
const value = strings[parseInt(attrs[j + 1])];
|
|
205
|
+
if (['id', 'class', 'type', 'name', 'href', 'src', 'alt', 'placeholder', 'value', 'title'].includes(name)) {
|
|
206
|
+
node.attributes[name] = value;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (nodes.inputValue?.index.includes(i)) {
|
|
211
|
+
const inputIndex = nodes.inputValue.index.indexOf(i);
|
|
212
|
+
const stringIndex = nodes.inputValue.value[inputIndex];
|
|
213
|
+
if (typeof stringIndex === 'number') {
|
|
214
|
+
node.inputValue = strings[stringIndex];
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (nodes.inputChecked?.index.includes(i)) {
|
|
218
|
+
node.checked = true;
|
|
219
|
+
}
|
|
220
|
+
if (nodes.optionSelected?.index.includes(i)) {
|
|
221
|
+
node.selected = true;
|
|
222
|
+
}
|
|
223
|
+
if (nodes.isClickable?.index.includes(i)) {
|
|
224
|
+
node.clickable = true;
|
|
225
|
+
}
|
|
226
|
+
nodeMap.set(i, node);
|
|
227
|
+
}
|
|
228
|
+
const tree = [];
|
|
229
|
+
for (let i = 0; i < nodeCount; i++) {
|
|
230
|
+
const parentIndex = nodes.parentIndex?.[i];
|
|
231
|
+
const node = nodeMap.get(i);
|
|
232
|
+
if (parentIndex !== undefined && parentIndex >= 0) {
|
|
233
|
+
const parent = nodeMap.get(parentIndex);
|
|
234
|
+
if (parent) {
|
|
235
|
+
parent.children.push(node);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
tree.push(node);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return tree;
|
|
243
|
+
}
|
|
244
|
+
findBodyNode(nodeTree) {
|
|
245
|
+
const findNode = (nodes, tagName) => {
|
|
246
|
+
for (const node of nodes) {
|
|
247
|
+
if (node.nodeName === tagName) {
|
|
248
|
+
return node;
|
|
249
|
+
}
|
|
250
|
+
const found = findNode(node.children, tagName);
|
|
251
|
+
if (found)
|
|
252
|
+
return found;
|
|
253
|
+
}
|
|
254
|
+
return null;
|
|
255
|
+
};
|
|
256
|
+
return findNode(nodeTree, 'body') || findNode(nodeTree, 'main') || null;
|
|
257
|
+
}
|
|
258
|
+
shouldIncludeNode(node) {
|
|
259
|
+
const skipTags = ['script', 'style', 'meta', 'link', 'head', 'noscript', 'svg', 'path'];
|
|
260
|
+
if (skipTags.includes(node.nodeName)) {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
if (node.nodeType === 3 && !node.textContent) {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
if (node.nodeType === 1 && !node.textContent && node.children.length === 0) {
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
renderNodeAsText(node, depth) {
|
|
272
|
+
if (!this.shouldIncludeNode(node)) {
|
|
273
|
+
return '';
|
|
274
|
+
}
|
|
275
|
+
const indent = '│ '.repeat(depth);
|
|
276
|
+
const prefix = depth > 0 ? '├── ' : '';
|
|
277
|
+
let output = '';
|
|
278
|
+
if (node.nodeType === 3) {
|
|
279
|
+
if (node.textContent) {
|
|
280
|
+
output += `${indent}${prefix}"${node.textContent}"\n`;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
else if (node.nodeType === 1) {
|
|
284
|
+
const tag = node.nodeName.toUpperCase();
|
|
285
|
+
let description = tag;
|
|
286
|
+
const attrs = [];
|
|
287
|
+
if (node.attributes.id)
|
|
288
|
+
attrs.push(`#${node.attributes.id}`);
|
|
289
|
+
if (node.attributes.class)
|
|
290
|
+
attrs.push(`.${node.attributes.class.split(' ')[0]}`);
|
|
291
|
+
if (node.attributes.type)
|
|
292
|
+
attrs.push(`[${node.attributes.type}]`);
|
|
293
|
+
if (node.attributes.name)
|
|
294
|
+
attrs.push(`name="${node.attributes.name}"`);
|
|
295
|
+
if (attrs.length > 0) {
|
|
296
|
+
description += `(${attrs.join(' ')})`;
|
|
297
|
+
}
|
|
298
|
+
if (node.nodeName === 'img' && node.attributes.alt) {
|
|
299
|
+
description += `: "${node.attributes.alt}"`;
|
|
300
|
+
}
|
|
301
|
+
else if (node.nodeName === 'a' && node.attributes.href) {
|
|
302
|
+
description += `: "${node.attributes.href}"`;
|
|
303
|
+
}
|
|
304
|
+
else if (['input', 'textarea'].includes(node.nodeName)) {
|
|
305
|
+
if (node.attributes.placeholder) {
|
|
306
|
+
description += `: "${node.attributes.placeholder}"`;
|
|
307
|
+
}
|
|
308
|
+
else if (node.inputValue) {
|
|
309
|
+
description += `: "${node.inputValue}"`;
|
|
310
|
+
}
|
|
311
|
+
if (node.checked)
|
|
312
|
+
description += ' [checked]';
|
|
313
|
+
}
|
|
314
|
+
else if (node.nodeName === 'button' || ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(node.nodeName)) {
|
|
315
|
+
const textContent = this.extractTextContent(node);
|
|
316
|
+
if (textContent) {
|
|
317
|
+
description += `: "${textContent}"`;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
output += `${indent}${prefix}${description}\n`;
|
|
321
|
+
const meaningfulChildren = node.children.filter((child) => this.shouldIncludeNode(child));
|
|
322
|
+
if (['p', 'div', 'span', 'li', 'td', 'th'].includes(node.nodeName)) {
|
|
323
|
+
const textContent = this.extractTextContent(node);
|
|
324
|
+
if (textContent && meaningfulChildren.length <= 1) {
|
|
325
|
+
output += `${indent}│ └── "${textContent}"\n`;
|
|
326
|
+
return output;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
for (const child of meaningfulChildren) {
|
|
330
|
+
output += this.renderNodeAsText(child, depth + 1);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return output;
|
|
334
|
+
}
|
|
335
|
+
extractTextContent(node) {
|
|
336
|
+
let text = '';
|
|
337
|
+
if (node.nodeType === 3) {
|
|
338
|
+
return node.textContent || '';
|
|
339
|
+
}
|
|
340
|
+
if (node.textContent) {
|
|
341
|
+
text += node.textContent + ' ';
|
|
342
|
+
}
|
|
343
|
+
for (const child of node.children) {
|
|
344
|
+
text += this.extractTextContent(child) + ' ';
|
|
345
|
+
}
|
|
346
|
+
return text.trim().replace(/\s+/g, ' ');
|
|
347
|
+
}
|
|
348
|
+
convertToHTML(response) {
|
|
349
|
+
const doc = response.documents[0];
|
|
350
|
+
if (!doc)
|
|
351
|
+
return '';
|
|
352
|
+
const tree = this.buildDOMTree(doc, response.strings);
|
|
353
|
+
return this.renderNodeAsHTML(tree[0], 0);
|
|
354
|
+
}
|
|
164
355
|
buildDOMTree(doc, strings) {
|
|
165
356
|
const nodes = doc.nodes;
|
|
166
357
|
if (!nodes.nodeName || !nodes.nodeType) {
|
|
@@ -227,27 +418,6 @@ class TakeSnapshotHandler {
|
|
|
227
418
|
}
|
|
228
419
|
return tree;
|
|
229
420
|
}
|
|
230
|
-
processLayoutInfo(layout, strings) {
|
|
231
|
-
return {
|
|
232
|
-
nodeCount: layout.nodeIndex.length,
|
|
233
|
-
styles: layout.styles.map(styleArray => styleArray.map(styleIndex => strings[parseInt(styleIndex)])),
|
|
234
|
-
bounds: layout.bounds,
|
|
235
|
-
text: layout.text,
|
|
236
|
-
paintOrders: layout.paintOrders,
|
|
237
|
-
offsetRects: layout.offsetRects,
|
|
238
|
-
scrollRects: layout.scrollRects,
|
|
239
|
-
clientRects: layout.clientRects,
|
|
240
|
-
blendedBackgroundColors: layout.blendedBackgroundColors,
|
|
241
|
-
textColorOpacities: layout.textColorOpacities
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
convertToHTML(response) {
|
|
245
|
-
const doc = response.documents[0];
|
|
246
|
-
if (!doc)
|
|
247
|
-
return '';
|
|
248
|
-
const tree = this.buildDOMTree(doc, response.strings);
|
|
249
|
-
return this.renderNodeAsHTML(tree[0], 0);
|
|
250
|
-
}
|
|
251
421
|
renderNodeAsHTML(node, depth) {
|
|
252
422
|
if (!node)
|
|
253
423
|
return '';
|
|
@@ -331,47 +501,58 @@ class TakeSnapshotHandler {
|
|
|
331
501
|
}
|
|
332
502
|
getHelp() {
|
|
333
503
|
return `
|
|
334
|
-
snapshot - Capture a
|
|
504
|
+
snapshot - Capture a text-based DOM representation optimized for LLM understanding
|
|
335
505
|
|
|
336
506
|
Usage:
|
|
337
507
|
snapshot
|
|
338
|
-
snapshot --filename
|
|
508
|
+
snapshot --filename page-structure.txt
|
|
339
509
|
snapshot --format html --filename page-structure.html
|
|
340
|
-
snapshot --include-paint-order --filename detailed-snapshot.json
|
|
341
510
|
|
|
342
511
|
Arguments:
|
|
343
512
|
--filename <path> Output filename (if not provided, returns data directly)
|
|
344
|
-
--format <json|html> Output format (default: json)
|
|
513
|
+
--format <json|html> Output format (default: json with text snapshot)
|
|
345
514
|
--include-styles Include computed styles (default: true)
|
|
346
515
|
--include-attributes Include DOM attributes (default: true)
|
|
347
516
|
--include-paint-order Include paint order information (default: false)
|
|
348
|
-
--include-text-index Include
|
|
517
|
+
--include-text-index Include additional DOM tree data (default: false)
|
|
349
518
|
|
|
350
|
-
Output
|
|
351
|
-
|
|
352
|
-
|
|
519
|
+
Output Format:
|
|
520
|
+
The default JSON output contains a text-based representation of the page structure
|
|
521
|
+
that LLMs can easily understand, showing:
|
|
522
|
+
- Page hierarchy with indentation
|
|
523
|
+
- Element types and key attributes
|
|
524
|
+
- Text content and form values
|
|
525
|
+
- Interactive elements (buttons, links, forms)
|
|
526
|
+
- Semantic structure (headings, lists, etc.)
|
|
527
|
+
|
|
528
|
+
Example Output:
|
|
529
|
+
PAGE: Example Site (https://example.com)
|
|
530
|
+
├── HEADER
|
|
531
|
+
│ ├── IMG(#logo): "Company Logo"
|
|
532
|
+
│ └── NAV
|
|
533
|
+
│ ├── A: "https://example.com/home"
|
|
534
|
+
│ └── "Home"
|
|
535
|
+
├── MAIN
|
|
536
|
+
│ ├── H1: "Welcome to Our Site"
|
|
537
|
+
│ ├── P
|
|
538
|
+
│ │ └── "This is the main content..."
|
|
539
|
+
│ └── FORM
|
|
540
|
+
│ ├── INPUT[email](name="email"): "Enter your email"
|
|
541
|
+
│ └── BUTTON: "Submit"
|
|
353
542
|
|
|
354
543
|
Examples:
|
|
355
|
-
#
|
|
356
|
-
snapshot
|
|
544
|
+
# Get text-based page structure
|
|
545
|
+
snapshot
|
|
546
|
+
|
|
547
|
+
# Save to file
|
|
548
|
+
snapshot --filename page-structure.json
|
|
357
549
|
|
|
358
550
|
# HTML representation
|
|
359
551
|
snapshot --format html --filename page-structure.html
|
|
360
552
|
|
|
361
|
-
# Detailed snapshot with paint order
|
|
362
|
-
snapshot --include-paint-order --filename detailed-snapshot.json
|
|
363
|
-
|
|
364
|
-
# Return data directly (no file)
|
|
365
|
-
snapshot --format json
|
|
366
|
-
|
|
367
553
|
Note:
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
- Computed CSS styles for each element
|
|
371
|
-
- Layout information (bounds, positioning)
|
|
372
|
-
- Text content and form values
|
|
373
|
-
- Visibility and clickability information
|
|
374
|
-
- Paint order and rendering details (optional)
|
|
554
|
+
This format provides a "text screenshot" that LLMs can easily parse to understand
|
|
555
|
+
page layout, content, and interactive elements without overwhelming detail.
|
|
375
556
|
`;
|
|
376
557
|
}
|
|
377
558
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chrome-cdp-cli",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.9",
|
|
4
4
|
"description": "Browser automation CLI via Chrome DevTools Protocol. Designed for developers and AI assistants - combines dedicated commands for common tasks with flexible JavaScript execution for complex scenarios. Features: element interaction, screenshots, DOM snapshots, console/network monitoring. Built-in IDE integration for Cursor and Claude.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|