cssprobe-cli 0.1.2 → 0.2.5
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 +31 -1
- package/dist/browser/launcher.d.ts +4 -0
- package/dist/browser/launcher.js +27 -0
- package/dist/cli/commands.d.ts +6 -0
- package/dist/cli/commands.js +16 -1
- package/dist/cli/program.js +282 -2
- package/dist/engine/renderer.d.ts +1 -1
- package/dist/engine/renderer.js +17 -195
- package/dist/engine/renderers/ancestors.d.ts +2 -0
- package/dist/engine/renderers/ancestors.js +17 -0
- package/dist/engine/renderers/dom-tree.d.ts +2 -0
- package/dist/engine/renderers/dom-tree.js +43 -0
- package/dist/engine/renderers/findings.d.ts +3 -0
- package/dist/engine/renderers/findings.js +44 -0
- package/dist/engine/renderers/layout.d.ts +2 -0
- package/dist/engine/renderers/layout.js +203 -0
- package/dist/engine/renderers/sketch.d.ts +2 -0
- package/dist/engine/renderers/sketch.js +78 -0
- package/dist/engine/renderers/utils.d.ts +16 -0
- package/dist/engine/renderers/utils.js +54 -0
- package/dist/help.json +18 -3
- package/dist/installer/skill-template.js +36 -0
- package/docs/guide/installation.md +36 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -44,9 +44,34 @@ cssprobe-cli login https://mysite.com
|
|
|
44
44
|
|
|
45
45
|
# Then inspect with state
|
|
46
46
|
cssprobe-cli inspect https://mysite.com/page ".target" --state ~/.cssprobe-cli/states/mysite.json
|
|
47
|
+
|
|
48
|
+
# Wait for user interaction before collecting (e.g. click a button to open a dialog)
|
|
49
|
+
cssprobe-cli inspect https://mysite.com/page ".dialog" --state ~/.cssprobe-cli/states/mysite.json --wait
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 3. Interactive Mode
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# Open browser and inspect interactively (REPL mode)
|
|
56
|
+
cssprobe-cli interactive https://mysite.com --state ~/.cssprobe-cli/states/mysite.json
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Runtime commands:
|
|
60
|
+
```
|
|
61
|
+
inspect> tree .dialog-A # DOM tree
|
|
62
|
+
inspect> layout .sidebar-B # ASCII layout diagram
|
|
63
|
+
inspect> sketch .content # Tree sketch (issues only)
|
|
64
|
+
inspect> findings .modal # Findings only
|
|
65
|
+
inspect> json .dialog-A # JSON output
|
|
66
|
+
inspect> report .dialog-A # Full report
|
|
67
|
+
inspect> .dialog-A # Shorthand for report
|
|
68
|
+
inspect> navigate https://other.com # Navigate to new URL
|
|
69
|
+
inspect> depth 10 # Set depth
|
|
70
|
+
inspect> help # Show help
|
|
71
|
+
inspect> quit # Exit
|
|
47
72
|
```
|
|
48
73
|
|
|
49
|
-
###
|
|
74
|
+
### 4. JSON Output
|
|
50
75
|
|
|
51
76
|
```bash
|
|
52
77
|
cssprobe-cli inspect https://getbootstrap.com/docs/5.3/examples/checkout body --json
|
|
@@ -61,6 +86,8 @@ cssprobe-cli inspect https://getbootstrap.com/docs/5.3/examples/checkout body --
|
|
|
61
86
|
|------|--------|----------|
|
|
62
87
|
| (default) | Markdown report with ancestor chain, DOM tree, findings | Terminal viewing |
|
|
63
88
|
| `--json` | Structured JSON with snapshot, findings, confidence summary | Scripts, `jq` pipes, AI agent consumption |
|
|
89
|
+
| `--brief` | Compact output: tree sketch + warnings/errors only | Quick overview |
|
|
90
|
+
| `--layout` | ASCII layout diagram showing element positions and sizes | Visual layout inspection |
|
|
64
91
|
|
|
65
92
|
---
|
|
66
93
|
|
|
@@ -70,6 +97,7 @@ cssprobe-cli inspect https://getbootstrap.com/docs/5.3/examples/checkout body --
|
|
|
70
97
|
|
|
71
98
|
```bash
|
|
72
99
|
cssprobe-cli inspect <url> [selector] # Inspect CSS layout
|
|
100
|
+
cssprobe-cli interactive <url> # Interactive REPL mode
|
|
73
101
|
cssprobe-cli login <url> # Interactive login, save state
|
|
74
102
|
cssprobe-cli state-import [file] # Import cookies from Netscape format
|
|
75
103
|
```
|
|
@@ -112,6 +140,8 @@ Options:
|
|
|
112
140
|
--up-to <tag> Ancestor stop tag (default html)
|
|
113
141
|
--state <file> Load saved state (cookies + localStorage)
|
|
114
142
|
--brief Compact output: tree sketch + warnings/errors only
|
|
143
|
+
--layout ASCII layout diagram showing element positions and sizes
|
|
144
|
+
--wait Wait for user interaction before collecting (implies --headed)
|
|
115
145
|
```
|
|
116
146
|
|
|
117
147
|
---
|
|
@@ -15,6 +15,10 @@ export declare class BrowserLauncher {
|
|
|
15
15
|
collect(cfg: CollectConfig): Promise<Snapshot>;
|
|
16
16
|
/** Measure the DOM tree depth from a root selector to the deepest leaf. */
|
|
17
17
|
measureDepth(selector: string): Promise<number>;
|
|
18
|
+
/** Wait for user to interact with the page. Press Enter to continue. */
|
|
19
|
+
waitForUser(): Promise<void>;
|
|
20
|
+
/** Navigate to a new URL in the existing page. */
|
|
21
|
+
navigate(url: string): Promise<void>;
|
|
18
22
|
/** Scan the page for likely root elements (modals, dialogs, scroll containers). */
|
|
19
23
|
autoDetectRoot(): Promise<string[]>;
|
|
20
24
|
/**
|
package/dist/browser/launcher.js
CHANGED
|
@@ -125,6 +125,33 @@ class BrowserLauncher {
|
|
|
125
125
|
return maxDepth;
|
|
126
126
|
}, selector);
|
|
127
127
|
}
|
|
128
|
+
/** Wait for user to interact with the page. Press Enter to continue. */
|
|
129
|
+
async waitForUser() {
|
|
130
|
+
console.error('');
|
|
131
|
+
console.error(' Browser is open. Perform your actions (login, click buttons, open dialogs, etc.).');
|
|
132
|
+
console.error(' Press Enter here when ready to inspect...');
|
|
133
|
+
console.error('');
|
|
134
|
+
await new Promise(resolve => {
|
|
135
|
+
process.stdin.once('data', () => resolve());
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
/** Navigate to a new URL in the existing page. */
|
|
139
|
+
async navigate(url) {
|
|
140
|
+
if (!this.page)
|
|
141
|
+
throw new Error('Browser not opened. Call open() first.');
|
|
142
|
+
if (url.startsWith('http://') || url.startsWith('https://')) {
|
|
143
|
+
await this.page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 });
|
|
144
|
+
}
|
|
145
|
+
else if (url.startsWith('file://')) {
|
|
146
|
+
await this.page.goto(url, { waitUntil: 'load', timeout: 30000 });
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
const resolved = path_1.default.resolve(url);
|
|
150
|
+
if (!fs_1.default.existsSync(resolved))
|
|
151
|
+
throw new Error(`File not found: ${resolved}`);
|
|
152
|
+
await this.page.goto(`file://${resolved}`, { waitUntil: 'load', timeout: 30000 });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
128
155
|
/** Scan the page for likely root elements (modals, dialogs, scroll containers). */
|
|
129
156
|
async autoDetectRoot() {
|
|
130
157
|
if (!this.page)
|
package/dist/cli/commands.d.ts
CHANGED
|
@@ -22,6 +22,8 @@ export declare const inspectCommand: import("./command").CommandSchema<z.ZodObje
|
|
|
22
22
|
'up-to': z.ZodOptional<z.ZodString>;
|
|
23
23
|
state: z.ZodOptional<z.ZodString>;
|
|
24
24
|
brief: z.ZodOptional<z.ZodBoolean>;
|
|
25
|
+
layout: z.ZodOptional<z.ZodBoolean>;
|
|
26
|
+
wait: z.ZodOptional<z.ZodBoolean>;
|
|
25
27
|
}, "strip", z.ZodTypeAny, {
|
|
26
28
|
json?: boolean | undefined;
|
|
27
29
|
headed?: boolean | undefined;
|
|
@@ -32,6 +34,8 @@ export declare const inspectCommand: import("./command").CommandSchema<z.ZodObje
|
|
|
32
34
|
'up-to'?: string | undefined;
|
|
33
35
|
state?: string | undefined;
|
|
34
36
|
brief?: boolean | undefined;
|
|
37
|
+
layout?: boolean | undefined;
|
|
38
|
+
wait?: boolean | undefined;
|
|
35
39
|
}, {
|
|
36
40
|
json?: boolean | undefined;
|
|
37
41
|
headed?: boolean | undefined;
|
|
@@ -42,4 +46,6 @@ export declare const inspectCommand: import("./command").CommandSchema<z.ZodObje
|
|
|
42
46
|
'up-to'?: string | undefined;
|
|
43
47
|
state?: string | undefined;
|
|
44
48
|
brief?: boolean | undefined;
|
|
49
|
+
layout?: boolean | undefined;
|
|
50
|
+
wait?: boolean | undefined;
|
|
45
51
|
}>>;
|
package/dist/cli/commands.js
CHANGED
|
@@ -63,6 +63,8 @@ const inspect = (0, command_1.declareCommand)({
|
|
|
63
63
|
'up-to': z.string().optional().describe('ancestor stop tag (default html)'),
|
|
64
64
|
state: z.string().optional().describe('path to saved state file (cookies + localStorage)'),
|
|
65
65
|
brief: z.boolean().optional().describe('compact output: tree sketch + warnings/errors only'),
|
|
66
|
+
layout: z.boolean().optional().describe('ASCII layout diagram showing element positions and sizes'),
|
|
67
|
+
wait: z.boolean().optional().describe('wait for user interaction before collecting (implies --headed)'),
|
|
66
68
|
}),
|
|
67
69
|
});
|
|
68
70
|
const login = (0, command_1.declareCommand)({
|
|
@@ -157,8 +159,21 @@ const stateImport = (0, command_1.declareCommand)({
|
|
|
157
159
|
merge: z.string().optional().describe('existing state file to merge into'),
|
|
158
160
|
}),
|
|
159
161
|
});
|
|
162
|
+
const interactive = (0, command_1.declareCommand)({
|
|
163
|
+
name: 'interactive',
|
|
164
|
+
category: 'core',
|
|
165
|
+
description: 'Open browser and inspect interactively (REPL mode)',
|
|
166
|
+
args: z.object({
|
|
167
|
+
url: urlArg,
|
|
168
|
+
}),
|
|
169
|
+
options: z.object({
|
|
170
|
+
browser: z.enum(['chromium', 'firefox', 'webkit']).optional().describe('browser engine (default chromium)'),
|
|
171
|
+
state: z.string().optional().describe('path to saved state file (cookies + localStorage)'),
|
|
172
|
+
depth: numberArg.optional().describe('DOM tree depth (default: auto, max 20)'),
|
|
173
|
+
}),
|
|
174
|
+
});
|
|
160
175
|
const commandsArray = [
|
|
161
|
-
inspect, login, stateImport,
|
|
176
|
+
inspect, login, stateImport, interactive,
|
|
162
177
|
configShow, configSet, configList, configUse, configNew, configPath,
|
|
163
178
|
skillInstall, skillUninstall,
|
|
164
179
|
];
|
package/dist/cli/program.js
CHANGED
|
@@ -47,6 +47,10 @@ async function program() {
|
|
|
47
47
|
await handleStateImport(command, cmdEntry, rawArgs, output);
|
|
48
48
|
return;
|
|
49
49
|
}
|
|
50
|
+
if (commandName === 'interactive') {
|
|
51
|
+
await handleInteractive(command, cmdEntry, rawArgs, output);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
50
54
|
await handleInspect(command, cmdEntry, rawArgs, output);
|
|
51
55
|
}
|
|
52
56
|
function parseArgs(help) {
|
|
@@ -110,10 +114,12 @@ async function handleInspect(command, cmdEntry, args, output) {
|
|
|
110
114
|
}
|
|
111
115
|
const maxNodes = parsed['max-nodes'] || 60;
|
|
112
116
|
const upTo = parsed['up-to'] || 'html';
|
|
113
|
-
const
|
|
117
|
+
const wait = !!parsed.wait;
|
|
118
|
+
const headed = !!parsed.headed || config.headed || wait;
|
|
114
119
|
const browser = parsed.browser || config.browser;
|
|
115
120
|
const state = parsed.state || undefined;
|
|
116
121
|
const brief = !!parsed.brief;
|
|
122
|
+
const layout = !!parsed.layout;
|
|
117
123
|
if (!url)
|
|
118
124
|
output.error('URL is required. Usage: cssprobe-cli inspect <url> [selector]');
|
|
119
125
|
// Auto-detect root selector if not provided
|
|
@@ -129,6 +135,10 @@ async function handleInspect(command, cmdEntry, args, output) {
|
|
|
129
135
|
}
|
|
130
136
|
const launcher = new launcher_1.BrowserLauncher({ browser, headed, state });
|
|
131
137
|
await launcher.open(url);
|
|
138
|
+
// Wait for user interaction if --wait
|
|
139
|
+
if (wait) {
|
|
140
|
+
await launcher.waitForUser();
|
|
141
|
+
}
|
|
132
142
|
// Resolve auto depth
|
|
133
143
|
if (depth === 0) {
|
|
134
144
|
const measured = await launcher.measureDepth(rootSelector);
|
|
@@ -148,7 +158,7 @@ async function handleInspect(command, cmdEntry, args, output) {
|
|
|
148
158
|
console.log(output.format((0, renderer_1.renderJSON)(snapshot, findings, brief)));
|
|
149
159
|
}
|
|
150
160
|
else {
|
|
151
|
-
console.log((0, renderer_1.renderMarkdown)(snapshot, findings, brief));
|
|
161
|
+
console.log((0, renderer_1.renderMarkdown)(snapshot, findings, brief, layout));
|
|
152
162
|
}
|
|
153
163
|
}
|
|
154
164
|
catch (e) {
|
|
@@ -392,4 +402,274 @@ async function handleStateImport(command, cmdEntry, args, output) {
|
|
|
392
402
|
output.error(e instanceof Error ? e.message : String(e));
|
|
393
403
|
}
|
|
394
404
|
}
|
|
405
|
+
// ── interactive ──
|
|
406
|
+
const REPL_COMMANDS = {
|
|
407
|
+
tree: '<selector> Show DOM tree',
|
|
408
|
+
layout: '<selector> Show ASCII layout diagram',
|
|
409
|
+
sketch: '<selector> Show tree sketch (issues only)',
|
|
410
|
+
findings: '<selector> Show findings only',
|
|
411
|
+
json: '<selector> Show JSON output',
|
|
412
|
+
report: '<selector> Show full report',
|
|
413
|
+
navigate: '<url> Navigate to a new URL',
|
|
414
|
+
depth: '<n> Set depth (default: auto)',
|
|
415
|
+
help: ' Show this help',
|
|
416
|
+
quit: ' Close browser and exit',
|
|
417
|
+
};
|
|
418
|
+
async function handleInteractive(command, cmdEntry, args, _output) {
|
|
419
|
+
const cmdArgs = splitArgs(args);
|
|
420
|
+
const parsed = (0, command_1.parseCommand)(command, cmdArgs);
|
|
421
|
+
const url = parsed.url;
|
|
422
|
+
const browser = parsed.browser || 'chromium';
|
|
423
|
+
const state = parsed.state || undefined;
|
|
424
|
+
const depthRaw = parsed.depth;
|
|
425
|
+
let depth = depthRaw === 'auto' || depthRaw === undefined ? 0 : Number(depthRaw);
|
|
426
|
+
if (!url) {
|
|
427
|
+
console.error('Error: URL is required. Usage: cssprobe-cli interactive <url>');
|
|
428
|
+
process.exit(1);
|
|
429
|
+
}
|
|
430
|
+
const launcher = new launcher_1.BrowserLauncher({ browser, headed: true, state });
|
|
431
|
+
await launcher.open(url);
|
|
432
|
+
let currentUrl = url;
|
|
433
|
+
// Resolve auto depth
|
|
434
|
+
if (depth === 0) {
|
|
435
|
+
try {
|
|
436
|
+
const candidates = await launcher.autoDetectRoot();
|
|
437
|
+
const selector = candidates[0] || 'body';
|
|
438
|
+
const measured = await launcher.measureDepth(selector);
|
|
439
|
+
depth = Math.min(measured, 20) || 6;
|
|
440
|
+
console.error(`Auto-detected depth: ${measured} (using ${depth})`);
|
|
441
|
+
}
|
|
442
|
+
catch {
|
|
443
|
+
depth = 6;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
console.error('');
|
|
447
|
+
console.error(`Browser opened at ${currentUrl}`);
|
|
448
|
+
console.error("Type 'help' for available commands, 'quit' to exit.");
|
|
449
|
+
console.error('');
|
|
450
|
+
const rl = require('readline').createInterface({
|
|
451
|
+
input: process.stdin,
|
|
452
|
+
output: process.stderr,
|
|
453
|
+
prompt: 'inspect> ',
|
|
454
|
+
});
|
|
455
|
+
rl.prompt();
|
|
456
|
+
rl.on('line', async (line) => {
|
|
457
|
+
const trimmed = line.trim();
|
|
458
|
+
if (!trimmed) {
|
|
459
|
+
rl.prompt();
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
// Parse: command [selector] [--options]
|
|
463
|
+
const parts = trimmed.split(/\s+/);
|
|
464
|
+
const cmd = parts[0];
|
|
465
|
+
const rest = parts.slice(1);
|
|
466
|
+
try {
|
|
467
|
+
switch (cmd) {
|
|
468
|
+
case 'help': {
|
|
469
|
+
console.error('');
|
|
470
|
+
console.error('Commands:');
|
|
471
|
+
for (const [name, desc] of Object.entries(REPL_COMMANDS)) {
|
|
472
|
+
console.error(` ${name.padEnd(12)} ${desc}`);
|
|
473
|
+
}
|
|
474
|
+
console.error('');
|
|
475
|
+
break;
|
|
476
|
+
}
|
|
477
|
+
case 'quit':
|
|
478
|
+
case 'exit': {
|
|
479
|
+
await launcher.close();
|
|
480
|
+
console.error('Browser closed.');
|
|
481
|
+
rl.close();
|
|
482
|
+
process.exit(0);
|
|
483
|
+
}
|
|
484
|
+
case 'navigate': {
|
|
485
|
+
const newUrl = rest[0];
|
|
486
|
+
if (!newUrl) {
|
|
487
|
+
console.error('Usage: navigate <url>');
|
|
488
|
+
break;
|
|
489
|
+
}
|
|
490
|
+
await launcher.navigate(newUrl);
|
|
491
|
+
currentUrl = newUrl;
|
|
492
|
+
console.error(`Navigated to ${currentUrl}`);
|
|
493
|
+
break;
|
|
494
|
+
}
|
|
495
|
+
case 'depth': {
|
|
496
|
+
const n = parseInt(rest[0], 10);
|
|
497
|
+
if (isNaN(n) || n < 1) {
|
|
498
|
+
console.error('Usage: depth <n>');
|
|
499
|
+
break;
|
|
500
|
+
}
|
|
501
|
+
depth = Math.min(n, 20);
|
|
502
|
+
console.error(`Depth set to ${depth}`);
|
|
503
|
+
break;
|
|
504
|
+
}
|
|
505
|
+
case 'tree':
|
|
506
|
+
case 'layout':
|
|
507
|
+
case 'sketch':
|
|
508
|
+
case 'findings':
|
|
509
|
+
case 'json':
|
|
510
|
+
case 'report': {
|
|
511
|
+
// Parse selector + options from remaining args
|
|
512
|
+
let selector = '';
|
|
513
|
+
let useBrief = false;
|
|
514
|
+
let useLayout = false;
|
|
515
|
+
let useJson = false;
|
|
516
|
+
for (const arg of rest) {
|
|
517
|
+
if (arg === '--brief')
|
|
518
|
+
useBrief = true;
|
|
519
|
+
else if (arg === '--layout')
|
|
520
|
+
useLayout = true;
|
|
521
|
+
else if (arg === '--json')
|
|
522
|
+
useJson = true;
|
|
523
|
+
else if (!arg.startsWith('-') && !selector)
|
|
524
|
+
selector = arg;
|
|
525
|
+
}
|
|
526
|
+
// Command-specific defaults
|
|
527
|
+
if (cmd === 'layout')
|
|
528
|
+
useLayout = true;
|
|
529
|
+
if (cmd === 'sketch')
|
|
530
|
+
useBrief = true;
|
|
531
|
+
if (cmd === 'json')
|
|
532
|
+
useJson = true;
|
|
533
|
+
if (!selector) {
|
|
534
|
+
console.error('Usage: ' + cmd + ' <selector> [--brief] [--layout] [--json]');
|
|
535
|
+
break;
|
|
536
|
+
}
|
|
537
|
+
// Measure depth if needed
|
|
538
|
+
let useDepth = depth;
|
|
539
|
+
if (useDepth === 0) {
|
|
540
|
+
try {
|
|
541
|
+
const measured = await launcher.measureDepth(selector);
|
|
542
|
+
useDepth = Math.min(measured, 20) || 6;
|
|
543
|
+
}
|
|
544
|
+
catch {
|
|
545
|
+
useDepth = 6;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
const collectCfg = {
|
|
549
|
+
rootSelector: selector,
|
|
550
|
+
upTo: 'html',
|
|
551
|
+
downDepth: useDepth,
|
|
552
|
+
maxNodes: 60,
|
|
553
|
+
};
|
|
554
|
+
const snapshot = await launcher.collect(collectCfg);
|
|
555
|
+
if (snapshot.error) {
|
|
556
|
+
console.error(`Error: ${snapshot.error}`);
|
|
557
|
+
if (snapshot.candidates?.length) {
|
|
558
|
+
console.error(`Similar nodes: ${snapshot.candidates.join(', ')}`);
|
|
559
|
+
}
|
|
560
|
+
break;
|
|
561
|
+
}
|
|
562
|
+
const findings = (0, analyzer_1.analyze)(snapshot);
|
|
563
|
+
if (cmd === 'findings') {
|
|
564
|
+
// Only show findings section
|
|
565
|
+
const important = findings.filter(f => f.level === 'warning' || f.level === 'error');
|
|
566
|
+
if (important.length === 0) {
|
|
567
|
+
console.log('No warnings or errors');
|
|
568
|
+
}
|
|
569
|
+
else {
|
|
570
|
+
for (const f of important) {
|
|
571
|
+
const icon = f.level === 'error' ? '\u26A0' : '\u26A0';
|
|
572
|
+
console.log(`- ${icon} ${f.message} [${f.confidence}]${f.location ? ` @ ${f.location}` : ''}`);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
else if (useJson) {
|
|
577
|
+
console.log(JSON.stringify((0, renderer_1.renderJSON)(snapshot, findings, useBrief), null, 2));
|
|
578
|
+
}
|
|
579
|
+
else if (cmd === 'tree') {
|
|
580
|
+
// Only DOM tree
|
|
581
|
+
const lines = [];
|
|
582
|
+
lines.push(`## DOM tree (${snapshot.downDepth} levels deep)`);
|
|
583
|
+
lines.push('```');
|
|
584
|
+
// Inline renderNode
|
|
585
|
+
const renderNodeLocal = (node, indent) => {
|
|
586
|
+
if (!node)
|
|
587
|
+
return;
|
|
588
|
+
const cls = node.classes?.length ? `.${node.classes.join('.')}` : '';
|
|
589
|
+
const id = node.id ? `#${node.id}` : '';
|
|
590
|
+
const m = node.metrics;
|
|
591
|
+
const s = node.shape || {};
|
|
592
|
+
const shapeStr = s.role ? `[${s.role} h:${s.heightStrategy} w:${s.widthStrategy}]` : '';
|
|
593
|
+
const flag = [];
|
|
594
|
+
if (node.flags?.overflowsParent)
|
|
595
|
+
flag.push('\u26A0parent-overflow');
|
|
596
|
+
if (node.flags?.overflowsViewport)
|
|
597
|
+
flag.push('\u26A0viewport-overflow');
|
|
598
|
+
if (m?.clientHeight === 0 && m?.offsetHeight === 0)
|
|
599
|
+
flag.push('\u26A0collapsed');
|
|
600
|
+
lines.push(`${' '.repeat(indent)}<${node.tag}${id}${cls}> ${shapeStr} [${node.props?.position},${node.props?.display}] rect(${m?.rect?.width}\u00D7${m?.rect?.height})${flag.length ? ' ' + flag.join(' ') : ''}`);
|
|
601
|
+
for (const child of node.children || [])
|
|
602
|
+
renderNodeLocal(child, indent + 1);
|
|
603
|
+
};
|
|
604
|
+
renderNodeLocal(snapshot.tree, 0);
|
|
605
|
+
lines.push('```');
|
|
606
|
+
console.log(lines.join('\n'));
|
|
607
|
+
}
|
|
608
|
+
else {
|
|
609
|
+
console.log((0, renderer_1.renderMarkdown)(snapshot, findings, useBrief, useLayout));
|
|
610
|
+
}
|
|
611
|
+
break;
|
|
612
|
+
}
|
|
613
|
+
default: {
|
|
614
|
+
// Try as a direct selector (shorthand for 'report <selector>')
|
|
615
|
+
if (!cmd.startsWith('-')) {
|
|
616
|
+
const selector = cmd;
|
|
617
|
+
let useBrief = false;
|
|
618
|
+
let useLayout = false;
|
|
619
|
+
for (const arg of rest) {
|
|
620
|
+
if (arg === '--brief')
|
|
621
|
+
useBrief = true;
|
|
622
|
+
if (arg === '--layout')
|
|
623
|
+
useLayout = true;
|
|
624
|
+
}
|
|
625
|
+
let useDepth = depth;
|
|
626
|
+
if (useDepth === 0) {
|
|
627
|
+
try {
|
|
628
|
+
const measured = await launcher.measureDepth(selector);
|
|
629
|
+
useDepth = Math.min(measured, 20) || 6;
|
|
630
|
+
}
|
|
631
|
+
catch {
|
|
632
|
+
useDepth = 6;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
const collectCfg = {
|
|
636
|
+
rootSelector: selector,
|
|
637
|
+
upTo: 'html',
|
|
638
|
+
downDepth: useDepth,
|
|
639
|
+
maxNodes: 60,
|
|
640
|
+
};
|
|
641
|
+
const snapshot = await launcher.collect(collectCfg);
|
|
642
|
+
if (snapshot.error) {
|
|
643
|
+
console.error(`Error: ${snapshot.error}`);
|
|
644
|
+
if (snapshot.candidates?.length) {
|
|
645
|
+
console.error(`Similar nodes: ${snapshot.candidates.join(', ')}`);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
else {
|
|
649
|
+
const findings = (0, analyzer_1.analyze)(snapshot);
|
|
650
|
+
console.log((0, renderer_1.renderMarkdown)(snapshot, findings, useBrief, useLayout));
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
else {
|
|
654
|
+
console.error(`Unknown command: ${cmd}. Type 'help' for available commands.`);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
catch (e) {
|
|
660
|
+
console.error(`Error: ${e.message || e}`);
|
|
661
|
+
}
|
|
662
|
+
rl.prompt();
|
|
663
|
+
});
|
|
664
|
+
rl.on('close', async () => {
|
|
665
|
+
await launcher.close().catch(() => { });
|
|
666
|
+
process.exit(0);
|
|
667
|
+
});
|
|
668
|
+
// Handle SIGINT gracefully
|
|
669
|
+
process.on('SIGINT', async () => {
|
|
670
|
+
console.error('\nClosing browser...');
|
|
671
|
+
await launcher.close().catch(() => { });
|
|
672
|
+
process.exit(0);
|
|
673
|
+
});
|
|
674
|
+
}
|
|
395
675
|
//# sourceMappingURL=program.js.map
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { Snapshot, Finding } from './types';
|
|
2
|
-
export declare function renderMarkdown(snapshot: Snapshot, findings: Finding[], brief?: boolean): string;
|
|
2
|
+
export declare function renderMarkdown(snapshot: Snapshot, findings: Finding[], brief?: boolean, layout?: boolean): string;
|
|
3
3
|
export declare function renderJSON(snapshot: Snapshot, findings: Finding[], brief?: boolean): object;
|