chrome-cdp-cli 1.7.7 → 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.
@@ -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
- metadata: {
146
- captureTime: new Date().toISOString(),
147
- documentsCount: response.documents.length,
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 DOM snapshot including DOM tree structure, computed styles, and layout information
504
+ snapshot - Capture a text-based DOM representation optimized for LLM understanding
335
505
 
336
506
  Usage:
337
507
  snapshot
338
- snapshot --filename dom-snapshot.json
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 text node indices (default: false)
517
+ --include-text-index Include additional DOM tree data (default: false)
349
518
 
350
- Output Formats:
351
- json: Structured JSON with DOM tree, layout info, and computed styles
352
- html: Reconstructed HTML representation of the DOM
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
- # Basic DOM snapshot as JSON
356
- snapshot --filename dom-snapshot.json
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
- DOM snapshots capture the complete DOM tree structure including:
369
- - Element hierarchy and attributes
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.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",