chrome-cdp-cli 1.7.9 → 1.8.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/dist/cli/CLIInterface.js
CHANGED
|
@@ -373,6 +373,9 @@ class CLIInterface {
|
|
|
373
373
|
}
|
|
374
374
|
if (result.data && typeof result.data === 'object') {
|
|
375
375
|
const data = result.data;
|
|
376
|
+
if (data.snapshot && typeof data.snapshot === 'string') {
|
|
377
|
+
return data.snapshot;
|
|
378
|
+
}
|
|
376
379
|
if (data.messages && Array.isArray(data.messages)) {
|
|
377
380
|
output += dataSourceInfo;
|
|
378
381
|
if (data.messages.length === 0) {
|
|
@@ -47,6 +47,20 @@ class TakeSnapshotHandler {
|
|
|
47
47
|
}
|
|
48
48
|
};
|
|
49
49
|
}
|
|
50
|
+
if (snapshotArgs.format !== 'html' && typeof processedSnapshot === 'object' && processedSnapshot !== null) {
|
|
51
|
+
const snapshotObj = processedSnapshot;
|
|
52
|
+
if (snapshotObj.snapshot && typeof snapshotObj.snapshot === 'string') {
|
|
53
|
+
return {
|
|
54
|
+
success: true,
|
|
55
|
+
data: {
|
|
56
|
+
snapshot: snapshotObj.snapshot,
|
|
57
|
+
format: 'text',
|
|
58
|
+
documentsCount: response.documents.length,
|
|
59
|
+
nodesCount: response.documents[0]?.nodes?.nodeName?.length || 0
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
50
64
|
return {
|
|
51
65
|
success: true,
|
|
52
66
|
data: {
|
|
@@ -62,29 +76,24 @@ class TakeSnapshotHandler {
|
|
|
62
76
|
if (!docResponse || !docResponse.root) {
|
|
63
77
|
throw new Error('Failed to get document root');
|
|
64
78
|
}
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
});
|
|
68
|
-
if (!htmlResponse || !htmlResponse.outerHTML) {
|
|
69
|
-
throw new Error('Failed to get document HTML');
|
|
70
|
-
}
|
|
79
|
+
const url = await this.getCurrentURL(client);
|
|
80
|
+
const title = await this.getCurrentTitle(client);
|
|
71
81
|
let processedSnapshot;
|
|
72
82
|
if (snapshotArgs.format === 'html') {
|
|
83
|
+
const htmlResponse = await client.send('DOM.getOuterHTML', {
|
|
84
|
+
nodeId: docResponse.root.nodeId
|
|
85
|
+
});
|
|
86
|
+
if (!htmlResponse || !htmlResponse.outerHTML) {
|
|
87
|
+
throw new Error('Failed to get document HTML');
|
|
88
|
+
}
|
|
73
89
|
processedSnapshot = htmlResponse.outerHTML;
|
|
74
90
|
}
|
|
75
91
|
else {
|
|
92
|
+
const textSnapshot = this.buildTextFromDOMNode(docResponse.root, url, title);
|
|
76
93
|
processedSnapshot = {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
documentsCount: 1
|
|
81
|
-
},
|
|
82
|
-
documents: [{
|
|
83
|
-
url: await this.getCurrentURL(client),
|
|
84
|
-
title: await this.getCurrentTitle(client),
|
|
85
|
-
html: htmlResponse.outerHTML,
|
|
86
|
-
domTree: docResponse.root
|
|
87
|
-
}]
|
|
94
|
+
url,
|
|
95
|
+
title,
|
|
96
|
+
snapshot: textSnapshot
|
|
88
97
|
};
|
|
89
98
|
}
|
|
90
99
|
if (snapshotArgs.filename) {
|
|
@@ -98,6 +107,18 @@ class TakeSnapshotHandler {
|
|
|
98
107
|
}
|
|
99
108
|
};
|
|
100
109
|
}
|
|
110
|
+
if (snapshotArgs.format !== 'html' && typeof processedSnapshot === 'object' && processedSnapshot !== null) {
|
|
111
|
+
const snapshotObj = processedSnapshot;
|
|
112
|
+
if (snapshotObj.snapshot && typeof snapshotObj.snapshot === 'string') {
|
|
113
|
+
return {
|
|
114
|
+
success: true,
|
|
115
|
+
data: {
|
|
116
|
+
snapshot: snapshotObj.snapshot,
|
|
117
|
+
format: 'text'
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
101
122
|
return {
|
|
102
123
|
success: true,
|
|
103
124
|
data: {
|
|
@@ -148,11 +169,12 @@ class TakeSnapshotHandler {
|
|
|
148
169
|
};
|
|
149
170
|
}
|
|
150
171
|
const textSnapshot = this.createTextSnapshot(doc, response.strings);
|
|
151
|
-
|
|
172
|
+
const result = {
|
|
152
173
|
url: doc.documentURL,
|
|
153
174
|
title: doc.title,
|
|
154
175
|
snapshot: textSnapshot
|
|
155
176
|
};
|
|
177
|
+
return result;
|
|
156
178
|
}
|
|
157
179
|
createTextSnapshot(doc, strings) {
|
|
158
180
|
const nodes = doc.nodes;
|
|
@@ -160,7 +182,7 @@ class TakeSnapshotHandler {
|
|
|
160
182
|
return 'Empty document';
|
|
161
183
|
}
|
|
162
184
|
const nodeTree = this.buildNodeTree(doc, strings);
|
|
163
|
-
let output = `PAGE: ${doc.title || 'Untitled'}
|
|
185
|
+
let output = `PAGE: ${doc.title || 'Untitled'}\n`;
|
|
164
186
|
const bodyNode = this.findBodyNode(nodeTree);
|
|
165
187
|
if (bodyNode) {
|
|
166
188
|
output += this.renderNodeAsText(bodyNode, 0);
|
|
@@ -256,28 +278,30 @@ class TakeSnapshotHandler {
|
|
|
256
278
|
return findNode(nodeTree, 'body') || findNode(nodeTree, 'main') || null;
|
|
257
279
|
}
|
|
258
280
|
shouldIncludeNode(node) {
|
|
259
|
-
const skipTags = ['script', 'style', 'meta', 'link', 'head', 'noscript'
|
|
281
|
+
const skipTags = ['script', 'style', 'meta', 'link', 'head', 'noscript'];
|
|
260
282
|
if (skipTags.includes(node.nodeName)) {
|
|
261
283
|
return false;
|
|
262
284
|
}
|
|
263
|
-
if (node.nodeType === 3
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
if (node.nodeType === 1 && !node.textContent && node.children.length === 0) {
|
|
267
|
-
return false;
|
|
285
|
+
if (node.nodeType === 3) {
|
|
286
|
+
const text = (node.textContent || '').trim();
|
|
287
|
+
return text.length > 0;
|
|
268
288
|
}
|
|
269
289
|
return true;
|
|
270
290
|
}
|
|
271
|
-
renderNodeAsText(node, depth) {
|
|
291
|
+
renderNodeAsText(node, depth, isLast = false, parentIsLast = []) {
|
|
272
292
|
if (!this.shouldIncludeNode(node)) {
|
|
273
293
|
return '';
|
|
274
294
|
}
|
|
275
|
-
|
|
276
|
-
|
|
295
|
+
let indent = '';
|
|
296
|
+
for (let i = 0; i < parentIsLast.length; i++) {
|
|
297
|
+
indent += parentIsLast[i] ? ' ' : '│ ';
|
|
298
|
+
}
|
|
299
|
+
const prefix = depth > 0 ? (isLast ? '└── ' : '├── ') : '';
|
|
277
300
|
let output = '';
|
|
278
301
|
if (node.nodeType === 3) {
|
|
279
302
|
if (node.textContent) {
|
|
280
|
-
|
|
303
|
+
const truncatedText = this.truncateText(node.textContent.trim(), 40);
|
|
304
|
+
output += `${indent}${prefix}"${truncatedText}"\n`;
|
|
281
305
|
}
|
|
282
306
|
}
|
|
283
307
|
else if (node.nodeType === 1) {
|
|
@@ -286,8 +310,12 @@ class TakeSnapshotHandler {
|
|
|
286
310
|
const attrs = [];
|
|
287
311
|
if (node.attributes.id)
|
|
288
312
|
attrs.push(`#${node.attributes.id}`);
|
|
289
|
-
if (node.attributes.class)
|
|
290
|
-
|
|
313
|
+
if (node.attributes.class) {
|
|
314
|
+
const classes = node.attributes.class.split(/\s+/).filter((c) => c.trim().length > 0);
|
|
315
|
+
classes.forEach((cls) => {
|
|
316
|
+
attrs.push(`.${cls.trim()}`);
|
|
317
|
+
});
|
|
318
|
+
}
|
|
291
319
|
if (node.attributes.type)
|
|
292
320
|
attrs.push(`[${node.attributes.type}]`);
|
|
293
321
|
if (node.attributes.name)
|
|
@@ -296,17 +324,27 @@ class TakeSnapshotHandler {
|
|
|
296
324
|
description += `(${attrs.join(' ')})`;
|
|
297
325
|
}
|
|
298
326
|
if (node.nodeName === 'img' && node.attributes.alt) {
|
|
299
|
-
|
|
327
|
+
const altText = this.truncateText(node.attributes.alt, 40);
|
|
328
|
+
description += `: "${altText}"`;
|
|
300
329
|
}
|
|
301
330
|
else if (node.nodeName === 'a' && node.attributes.href) {
|
|
302
331
|
description += `: "${node.attributes.href}"`;
|
|
303
332
|
}
|
|
304
333
|
else if (['input', 'textarea'].includes(node.nodeName)) {
|
|
305
334
|
if (node.attributes.placeholder) {
|
|
306
|
-
|
|
335
|
+
const placeholderText = this.truncateText(node.attributes.placeholder, 40);
|
|
336
|
+
description += `: "${placeholderText}"`;
|
|
307
337
|
}
|
|
308
338
|
else if (node.inputValue) {
|
|
309
|
-
|
|
339
|
+
const inputText = this.truncateText(node.inputValue, 40);
|
|
340
|
+
description += `: "${inputText}"`;
|
|
341
|
+
}
|
|
342
|
+
else if (node.nodeName === 'textarea') {
|
|
343
|
+
const textContent = this.extractTextContent(node);
|
|
344
|
+
if (textContent) {
|
|
345
|
+
const truncatedText = this.truncateText(textContent, 40);
|
|
346
|
+
description += `: "${truncatedText}"`;
|
|
347
|
+
}
|
|
310
348
|
}
|
|
311
349
|
if (node.checked)
|
|
312
350
|
description += ' [checked]';
|
|
@@ -314,20 +352,31 @@ class TakeSnapshotHandler {
|
|
|
314
352
|
else if (node.nodeName === 'button' || ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(node.nodeName)) {
|
|
315
353
|
const textContent = this.extractTextContent(node);
|
|
316
354
|
if (textContent) {
|
|
317
|
-
|
|
355
|
+
const truncatedText = this.truncateText(textContent, 40);
|
|
356
|
+
description += `: "${truncatedText}"`;
|
|
318
357
|
}
|
|
319
358
|
}
|
|
320
359
|
output += `${indent}${prefix}${description}\n`;
|
|
360
|
+
if (node.nodeName === 'textarea') {
|
|
361
|
+
return output;
|
|
362
|
+
}
|
|
321
363
|
const meaningfulChildren = node.children.filter((child) => this.shouldIncludeNode(child));
|
|
322
|
-
if (
|
|
364
|
+
if (meaningfulChildren.length === 0) {
|
|
323
365
|
const textContent = this.extractTextContent(node);
|
|
324
|
-
if (textContent &&
|
|
325
|
-
|
|
366
|
+
if (textContent && textContent.trim().length > 0) {
|
|
367
|
+
const truncatedText = this.truncateText(textContent.trim(), 40);
|
|
368
|
+
output += `${indent}│ └── "${truncatedText}"\n`;
|
|
326
369
|
return output;
|
|
327
370
|
}
|
|
371
|
+
return output;
|
|
328
372
|
}
|
|
329
|
-
|
|
330
|
-
|
|
373
|
+
else {
|
|
374
|
+
const newParentIsLast = [...parentIsLast, isLast];
|
|
375
|
+
for (let i = 0; i < meaningfulChildren.length; i++) {
|
|
376
|
+
const child = meaningfulChildren[i];
|
|
377
|
+
const childIsLast = i === meaningfulChildren.length - 1;
|
|
378
|
+
output += this.renderNodeAsText(child, depth + 1, childIsLast, newParentIsLast);
|
|
379
|
+
}
|
|
331
380
|
}
|
|
332
381
|
}
|
|
333
382
|
return output;
|
|
@@ -345,6 +394,180 @@ class TakeSnapshotHandler {
|
|
|
345
394
|
}
|
|
346
395
|
return text.trim().replace(/\s+/g, ' ');
|
|
347
396
|
}
|
|
397
|
+
truncateText(text, maxLength) {
|
|
398
|
+
if (!text)
|
|
399
|
+
return '';
|
|
400
|
+
if (text.length <= maxLength)
|
|
401
|
+
return text;
|
|
402
|
+
return text.substring(0, maxLength) + '...';
|
|
403
|
+
}
|
|
404
|
+
buildTextFromDOMNode(root, _url, title) {
|
|
405
|
+
let output = `PAGE: ${title || 'Untitled'}\n`;
|
|
406
|
+
const bodyNode = this.findBodyInDOMTree(root);
|
|
407
|
+
if (bodyNode) {
|
|
408
|
+
output += this.renderDOMNodeAsText(bodyNode, 0);
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
if (root.children) {
|
|
412
|
+
for (const child of root.children) {
|
|
413
|
+
if (this.shouldIncludeDOMNode(child)) {
|
|
414
|
+
output += this.renderDOMNodeAsText(child, 0);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return output;
|
|
420
|
+
}
|
|
421
|
+
findBodyInDOMTree(node) {
|
|
422
|
+
if (node.nodeName && node.nodeName.toLowerCase() === 'body') {
|
|
423
|
+
return node;
|
|
424
|
+
}
|
|
425
|
+
if (node.nodeName && node.nodeName.toLowerCase() === 'main') {
|
|
426
|
+
return node;
|
|
427
|
+
}
|
|
428
|
+
if (node.children) {
|
|
429
|
+
for (const child of node.children) {
|
|
430
|
+
const found = this.findBodyInDOMTree(child);
|
|
431
|
+
if (found)
|
|
432
|
+
return found;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
shouldIncludeDOMNode(node) {
|
|
438
|
+
if (!node)
|
|
439
|
+
return false;
|
|
440
|
+
const nodeName = node.nodeName ? node.nodeName.toLowerCase() : '';
|
|
441
|
+
const skipTags = ['script', 'style', 'meta', 'link', 'head', 'noscript'];
|
|
442
|
+
if (skipTags.includes(nodeName)) {
|
|
443
|
+
return false;
|
|
444
|
+
}
|
|
445
|
+
if (node.nodeType === 3) {
|
|
446
|
+
const text = node.nodeValue || '';
|
|
447
|
+
return text.trim().length > 0;
|
|
448
|
+
}
|
|
449
|
+
return true;
|
|
450
|
+
}
|
|
451
|
+
renderDOMNodeAsText(node, depth, isLast = false, parentIsLast = []) {
|
|
452
|
+
if (!this.shouldIncludeDOMNode(node)) {
|
|
453
|
+
return '';
|
|
454
|
+
}
|
|
455
|
+
let indent = '';
|
|
456
|
+
for (let i = 0; i < parentIsLast.length; i++) {
|
|
457
|
+
indent += parentIsLast[i] ? ' ' : '│ ';
|
|
458
|
+
}
|
|
459
|
+
const prefix = depth > 0 ? (isLast ? '└── ' : '├── ') : '';
|
|
460
|
+
let output = '';
|
|
461
|
+
if (node.nodeType === 3) {
|
|
462
|
+
const text = (node.nodeValue || '').trim();
|
|
463
|
+
if (text) {
|
|
464
|
+
const truncatedText = this.truncateText(text, 40);
|
|
465
|
+
output += `${indent}${prefix}"${truncatedText}"\n`;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
else if (node.nodeType === 1) {
|
|
469
|
+
const tag = (node.nodeName || '').toUpperCase();
|
|
470
|
+
let description = tag;
|
|
471
|
+
const attrs = [];
|
|
472
|
+
if (node.attributes) {
|
|
473
|
+
for (let i = 0; i < node.attributes.length; i += 2) {
|
|
474
|
+
const name = node.attributes[i];
|
|
475
|
+
const value = node.attributes[i + 1];
|
|
476
|
+
if (['id', 'class', 'type', 'name', 'href', 'src', 'alt', 'placeholder', 'value', 'title'].includes(name)) {
|
|
477
|
+
if (name === 'id')
|
|
478
|
+
attrs.push(`#${value}`);
|
|
479
|
+
else if (name === 'class') {
|
|
480
|
+
const classes = value.split(/\s+/).filter((c) => c.trim().length > 0);
|
|
481
|
+
classes.forEach((cls) => {
|
|
482
|
+
attrs.push(`.${cls.trim()}`);
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
else if (name === 'type')
|
|
486
|
+
attrs.push(`[${value}]`);
|
|
487
|
+
else
|
|
488
|
+
attrs.push(`${name}="${value}"`);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
if (attrs.length > 0) {
|
|
493
|
+
description += `(${attrs.join(' ')})`;
|
|
494
|
+
}
|
|
495
|
+
const nodeName = (node.nodeName || '').toLowerCase();
|
|
496
|
+
if (nodeName === 'img' && node.attributes) {
|
|
497
|
+
const altIndex = node.attributes.indexOf('alt');
|
|
498
|
+
if (altIndex >= 0 && altIndex + 1 < node.attributes.length) {
|
|
499
|
+
const altText = this.truncateText(node.attributes[altIndex + 1], 40);
|
|
500
|
+
description += `: "${altText}"`;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
else if (nodeName === 'a' && node.attributes) {
|
|
504
|
+
const hrefIndex = node.attributes.indexOf('href');
|
|
505
|
+
if (hrefIndex >= 0 && hrefIndex + 1 < node.attributes.length) {
|
|
506
|
+
description += `: "${node.attributes[hrefIndex + 1]}"`;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
else if (['input', 'textarea'].includes(nodeName)) {
|
|
510
|
+
if (node.attributes) {
|
|
511
|
+
const placeholderIndex = node.attributes.indexOf('placeholder');
|
|
512
|
+
if (placeholderIndex >= 0 && placeholderIndex + 1 < node.attributes.length) {
|
|
513
|
+
const placeholderText = this.truncateText(node.attributes[placeholderIndex + 1], 40);
|
|
514
|
+
description += `: "${placeholderText}"`;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
if (nodeName === 'textarea') {
|
|
518
|
+
const textContent = this.extractTextFromDOMNode(node);
|
|
519
|
+
if (textContent) {
|
|
520
|
+
const truncatedText = this.truncateText(textContent, 40);
|
|
521
|
+
description += `: "${truncatedText}"`;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
else if (nodeName === 'button' || ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(nodeName)) {
|
|
526
|
+
const textContent = this.extractTextFromDOMNode(node);
|
|
527
|
+
if (textContent) {
|
|
528
|
+
const truncatedText = this.truncateText(textContent, 40);
|
|
529
|
+
description += `: "${truncatedText}"`;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
output += `${indent}${prefix}${description}\n`;
|
|
533
|
+
if (nodeName === 'textarea') {
|
|
534
|
+
return output;
|
|
535
|
+
}
|
|
536
|
+
if (node.children) {
|
|
537
|
+
const meaningfulChildren = node.children.filter((child) => this.shouldIncludeDOMNode(child));
|
|
538
|
+
if (meaningfulChildren.length === 0) {
|
|
539
|
+
const textContent = this.extractTextFromDOMNode(node);
|
|
540
|
+
if (textContent && textContent.trim().length > 0) {
|
|
541
|
+
const truncatedText = this.truncateText(textContent.trim(), 40);
|
|
542
|
+
output += `${indent}│ └── "${truncatedText}"\n`;
|
|
543
|
+
return output;
|
|
544
|
+
}
|
|
545
|
+
return output;
|
|
546
|
+
}
|
|
547
|
+
else {
|
|
548
|
+
const newParentIsLast = [...parentIsLast, isLast];
|
|
549
|
+
for (let i = 0; i < meaningfulChildren.length; i++) {
|
|
550
|
+
const child = meaningfulChildren[i];
|
|
551
|
+
const childIsLast = i === meaningfulChildren.length - 1;
|
|
552
|
+
output += this.renderDOMNodeAsText(child, depth + 1, childIsLast, newParentIsLast);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return output;
|
|
558
|
+
}
|
|
559
|
+
extractTextFromDOMNode(node) {
|
|
560
|
+
if (node.nodeType === 3) {
|
|
561
|
+
return (node.nodeValue || '').trim();
|
|
562
|
+
}
|
|
563
|
+
let text = '';
|
|
564
|
+
if (node.children) {
|
|
565
|
+
for (const child of node.children) {
|
|
566
|
+
text += this.extractTextFromDOMNode(child) + ' ';
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
return text.trim().replace(/\s+/g, ' ');
|
|
570
|
+
}
|
|
348
571
|
convertToHTML(response) {
|
|
349
572
|
const doc = response.documents[0];
|
|
350
573
|
if (!doc)
|
|
@@ -482,7 +705,7 @@ class TakeSnapshotHandler {
|
|
|
482
705
|
if (snapshotArgs.filename !== undefined && typeof snapshotArgs.filename !== 'string') {
|
|
483
706
|
return false;
|
|
484
707
|
}
|
|
485
|
-
if (snapshotArgs.format !== undefined && !['json', 'html'].includes(snapshotArgs.format)) {
|
|
708
|
+
if (snapshotArgs.format !== undefined && !['json', 'html', 'text'].includes(snapshotArgs.format)) {
|
|
486
709
|
return false;
|
|
487
710
|
}
|
|
488
711
|
if (snapshotArgs.includeStyles !== undefined && typeof snapshotArgs.includeStyles !== 'boolean') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chrome-cdp-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
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",
|