cssprobe-cli 0.2.5 → 0.2.7
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/AGENTS.md +83 -15
- package/README.md +124 -68
- package/dist/browser/launcher.d.ts +8 -0
- package/dist/browser/launcher.js +8 -2
- package/dist/cli/command.d.ts +1 -1
- package/dist/cli/commands.d.ts +6 -30
- package/dist/cli/commands.js +100 -47
- package/dist/cli/program.js +146 -388
- package/dist/config/config.d.ts +5 -0
- package/dist/config/config.js +14 -0
- package/dist/config/helpGenerator.js +3 -1
- package/dist/daemon/daemon.d.ts +11 -0
- package/dist/daemon/daemon.js +233 -0
- package/dist/daemon/daemonEntry.d.ts +6 -0
- package/dist/daemon/daemonEntry.js +27 -0
- package/dist/daemon/session.d.ts +52 -0
- package/dist/daemon/session.js +220 -0
- package/dist/daemonEntry.js +1440 -0
- package/dist/help.json +66 -32
- package/dist/utils/socketConnection.d.ts +21 -0
- package/dist/utils/socketConnection.js +77 -0
- package/docs/code-design-session-mode.md +393 -0
- package/docs/guide/installation.md +3 -2
- package/package.json +1 -1
package/dist/cli/program.js
CHANGED
|
@@ -12,9 +12,7 @@ const command_1 = require("./command");
|
|
|
12
12
|
const commands_1 = require("./commands");
|
|
13
13
|
const output_1 = require("./output");
|
|
14
14
|
const config_1 = require("../config/config");
|
|
15
|
-
const
|
|
16
|
-
const analyzer_1 = require("../engine/analyzer");
|
|
17
|
-
const renderer_1 = require("../engine/renderer");
|
|
15
|
+
const session_1 = require("../daemon/session");
|
|
18
16
|
const globalOptions = ['json', 'raw', 'help', 'h', 'version', 'v', 'p', 'profile'];
|
|
19
17
|
const booleanGlobalOptions = ['help', 'json', 'raw', 'version', 'v', 'h'];
|
|
20
18
|
async function program() {
|
|
@@ -39,19 +37,31 @@ async function program() {
|
|
|
39
37
|
return;
|
|
40
38
|
if (handleSkillCommands(commandName, command, rawArgs, output))
|
|
41
39
|
return;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
40
|
+
// Session-based commands
|
|
41
|
+
switch (commandName) {
|
|
42
|
+
case 'open':
|
|
43
|
+
await handleOpen(command, rawArgs, output);
|
|
44
|
+
return;
|
|
45
|
+
case 'close':
|
|
46
|
+
await handleClose(output);
|
|
47
|
+
return;
|
|
48
|
+
case 'status':
|
|
49
|
+
await handleStatus(output);
|
|
50
|
+
return;
|
|
51
|
+
case 'state-import':
|
|
52
|
+
await handleStateImport(command, rawArgs, output);
|
|
53
|
+
return;
|
|
54
|
+
case 'inspect':
|
|
55
|
+
case 'tree':
|
|
56
|
+
case 'layout':
|
|
57
|
+
case 'findings':
|
|
58
|
+
case 'inject-css':
|
|
59
|
+
case 'screenshot':
|
|
60
|
+
case 'eval':
|
|
61
|
+
await handleSessionCommand(commandName, command, rawArgs, output);
|
|
62
|
+
return;
|
|
53
63
|
}
|
|
54
|
-
|
|
64
|
+
output.error(`Unknown command: ${commandName}. Run 'cssprobe-cli --help' for usage.`);
|
|
55
65
|
}
|
|
56
66
|
function parseArgs(help) {
|
|
57
67
|
return (0, minimist_1.minimist)(process.argv.slice(2), {
|
|
@@ -91,121 +101,151 @@ function validateFlags(args, cmdEntry) {
|
|
|
91
101
|
if (unknownFlags.length)
|
|
92
102
|
throw new Error(`Unknown option${unknownFlags.length > 1 ? 's' : ''}: ${unknownFlags.map(f => `--${f}`).join(', ')}`);
|
|
93
103
|
}
|
|
94
|
-
// ──
|
|
95
|
-
async function
|
|
96
|
-
if (command.name !== 'inspect') {
|
|
97
|
-
output.error(`Unknown command: ${command.name}`);
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
104
|
+
// ── session commands ──
|
|
105
|
+
async function handleOpen(command, args, output) {
|
|
100
106
|
try {
|
|
101
107
|
const cmdArgs = splitArgs(args);
|
|
102
108
|
const parsed = (0, command_1.parseCommand)(command, cmdArgs);
|
|
103
|
-
const
|
|
104
|
-
const
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
const url = parsed.url || 'about:blank';
|
|
110
|
+
const browser = parsed.browser || 'chromium';
|
|
111
|
+
const headed = !!parsed.headed;
|
|
112
|
+
// Start daemon
|
|
113
|
+
const { pid } = await session_1.Session.startDaemon({
|
|
114
|
+
browser,
|
|
115
|
+
headed,
|
|
116
|
+
_: ['open', url],
|
|
117
|
+
}, 'open');
|
|
118
|
+
// Wait a bit for daemon to start
|
|
119
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
120
|
+
// Load session and navigate
|
|
121
|
+
const session = await (0, session_1.loadSession)();
|
|
122
|
+
if (session) {
|
|
123
|
+
await session.run({ _: ['goto', url] });
|
|
111
124
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const upTo = parsed['up-to'] || 'html';
|
|
117
|
-
const wait = !!parsed.wait;
|
|
118
|
-
const headed = !!parsed.headed || config.headed || wait;
|
|
119
|
-
const browser = parsed.browser || config.browser;
|
|
120
|
-
const state = parsed.state || undefined;
|
|
121
|
-
const brief = !!parsed.brief;
|
|
122
|
-
const layout = !!parsed.layout;
|
|
123
|
-
if (!url)
|
|
124
|
-
output.error('URL is required. Usage: cssprobe-cli inspect <url> [selector]');
|
|
125
|
-
// Auto-detect root selector if not provided
|
|
126
|
-
let rootSelector = selector;
|
|
127
|
-
if (!rootSelector) {
|
|
128
|
-
const detected = await autoDetectRoot(url, { browser, headed, state });
|
|
129
|
-
if (!detected) {
|
|
130
|
-
output.error('Could not auto-detect a root element. Please provide a selector: cssprobe-cli inspect <url> <selector>');
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
rootSelector = detected;
|
|
134
|
-
console.error(`Auto-detected root: ${rootSelector}`);
|
|
135
|
-
}
|
|
136
|
-
const launcher = new launcher_1.BrowserLauncher({ browser, headed, state });
|
|
137
|
-
await launcher.open(url);
|
|
138
|
-
// Wait for user interaction if --wait
|
|
139
|
-
if (wait) {
|
|
140
|
-
await launcher.waitForUser();
|
|
141
|
-
}
|
|
142
|
-
// Resolve auto depth
|
|
143
|
-
if (depth === 0) {
|
|
144
|
-
const measured = await launcher.measureDepth(rootSelector);
|
|
145
|
-
depth = Math.min(measured, 20) || 6;
|
|
146
|
-
console.error(`Auto-detected depth: ${measured} (using ${depth})`);
|
|
147
|
-
}
|
|
148
|
-
const collectCfg = {
|
|
149
|
-
rootSelector,
|
|
150
|
-
upTo,
|
|
151
|
-
downDepth: depth,
|
|
152
|
-
maxNodes,
|
|
125
|
+
const result = {
|
|
126
|
+
sessionId: 'default',
|
|
127
|
+
pid,
|
|
128
|
+
url,
|
|
153
129
|
};
|
|
154
|
-
const snapshot = await launcher.collect(collectCfg);
|
|
155
|
-
await launcher.close();
|
|
156
|
-
const findings = (0, analyzer_1.analyze)(snapshot);
|
|
157
130
|
if (output.json) {
|
|
158
|
-
console.log(
|
|
131
|
+
console.log(JSON.stringify(result, null, 2));
|
|
159
132
|
}
|
|
160
133
|
else {
|
|
161
|
-
console.log(
|
|
134
|
+
console.log(`Browser opened at ${url}`);
|
|
135
|
+
console.log(`Session ID: default`);
|
|
136
|
+
console.log(`PID: ${pid}`);
|
|
162
137
|
}
|
|
163
138
|
}
|
|
164
139
|
catch (e) {
|
|
165
140
|
output.error(e instanceof Error ? e.message : String(e));
|
|
166
141
|
}
|
|
167
142
|
}
|
|
168
|
-
async function
|
|
169
|
-
const launcher = new launcher_1.BrowserLauncher(opts);
|
|
143
|
+
async function handleClose(output) {
|
|
170
144
|
try {
|
|
171
|
-
await
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
return
|
|
175
|
-
|
|
145
|
+
const session = await (0, session_1.loadSession)();
|
|
146
|
+
if (!session) {
|
|
147
|
+
output.error('No active session. Run: cssprobe-cli open <url>');
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
await session.stop();
|
|
151
|
+
if (output.json) {
|
|
152
|
+
console.log(JSON.stringify({ closed: true, sessionId: 'default' }, null, 2));
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
console.log('Browser closed.');
|
|
156
|
+
}
|
|
176
157
|
}
|
|
177
|
-
|
|
178
|
-
|
|
158
|
+
catch (e) {
|
|
159
|
+
output.error(e instanceof Error ? e.message : String(e));
|
|
179
160
|
}
|
|
180
161
|
}
|
|
181
|
-
|
|
182
|
-
async function handleLogin(command, cmdEntry, args, output) {
|
|
162
|
+
async function handleStatus(output) {
|
|
183
163
|
try {
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
164
|
+
const session = await (0, session_1.loadSession)();
|
|
165
|
+
if (!session) {
|
|
166
|
+
const result = { sessionId: 'default', alive: false };
|
|
167
|
+
if (output.json) {
|
|
168
|
+
console.log(JSON.stringify(result, null, 2));
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
console.log('No active session.');
|
|
172
|
+
}
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const canConnect = await session.canConnect();
|
|
176
|
+
const result = {
|
|
177
|
+
sessionId: 'default',
|
|
178
|
+
alive: canConnect,
|
|
179
|
+
config: session.config,
|
|
180
|
+
};
|
|
181
|
+
if (output.json) {
|
|
182
|
+
console.log(JSON.stringify(result, null, 2));
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
console.log(`Session: default`);
|
|
186
|
+
console.log(`Status: ${canConnect ? 'alive' : 'dead'}`);
|
|
187
|
+
if (canConnect) {
|
|
188
|
+
console.log(`Browser: ${session.config.browser.browserName}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
196
191
|
}
|
|
197
192
|
catch (e) {
|
|
198
193
|
output.error(e instanceof Error ? e.message : String(e));
|
|
199
194
|
}
|
|
200
195
|
}
|
|
201
|
-
function
|
|
196
|
+
async function handleSessionCommand(commandName, command, args, output) {
|
|
202
197
|
try {
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
198
|
+
const session = await (0, session_1.loadSession)();
|
|
199
|
+
if (!session) {
|
|
200
|
+
output.error('No active session. Run: cssprobe-cli open <url>');
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
const cmdArgs = splitArgs(args);
|
|
204
|
+
const parsed = (0, command_1.parseCommand)(command, cmdArgs);
|
|
205
|
+
// Build command args for daemon
|
|
206
|
+
const daemonArgs = [commandName];
|
|
207
|
+
// Add positional args
|
|
208
|
+
if (command.args) {
|
|
209
|
+
const argsSchema = command.args;
|
|
210
|
+
const argNames = Object.keys(argsSchema.shape);
|
|
211
|
+
for (const name of argNames) {
|
|
212
|
+
if (parsed[name] !== undefined) {
|
|
213
|
+
daemonArgs.push(String(parsed[name]));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
// Add options
|
|
218
|
+
if (command.options) {
|
|
219
|
+
const optionsSchema = command.options;
|
|
220
|
+
const optionNames = Object.keys(optionsSchema.shape);
|
|
221
|
+
for (const name of optionNames) {
|
|
222
|
+
if (parsed[name] !== undefined) {
|
|
223
|
+
if (typeof parsed[name] === 'boolean') {
|
|
224
|
+
if (parsed[name])
|
|
225
|
+
daemonArgs.push(`--${name}`);
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
daemonArgs.push(`--${name}=${parsed[name]}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// Run command in session
|
|
234
|
+
const result = await session.run({ ...cmdArgs, _: daemonArgs });
|
|
235
|
+
if (output.json) {
|
|
236
|
+
try {
|
|
237
|
+
console.log(JSON.parse(result.text));
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
console.log(result.text);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
console.log(result.text);
|
|
245
|
+
}
|
|
206
246
|
}
|
|
207
|
-
catch {
|
|
208
|
-
|
|
247
|
+
catch (e) {
|
|
248
|
+
output.error(e instanceof Error ? e.message : String(e));
|
|
209
249
|
}
|
|
210
250
|
}
|
|
211
251
|
// ── config commands ──
|
|
@@ -301,12 +341,6 @@ function handleSkillCommands(commandName, command, args, output) {
|
|
|
301
341
|
}
|
|
302
342
|
return false;
|
|
303
343
|
}
|
|
304
|
-
/**
|
|
305
|
-
* Parse Netscape cookie format (tab-separated):
|
|
306
|
-
* domain flag path secure expires name value
|
|
307
|
-
*
|
|
308
|
-
* Also supports lines starting with # as comments, and blank lines.
|
|
309
|
-
*/
|
|
310
344
|
function parseNetscapeCookies(content) {
|
|
311
345
|
const cookies = [];
|
|
312
346
|
const lines = content.split('\n');
|
|
@@ -318,7 +352,7 @@ function parseNetscapeCookies(content) {
|
|
|
318
352
|
if (parts.length < 7)
|
|
319
353
|
continue;
|
|
320
354
|
const [domain, flag, path, secure, expires, name, ...valueParts] = parts;
|
|
321
|
-
const value = valueParts.join('\t');
|
|
355
|
+
const value = valueParts.join('\t');
|
|
322
356
|
const expiresNum = parseInt(expires, 10);
|
|
323
357
|
cookies.push({
|
|
324
358
|
name,
|
|
@@ -333,14 +367,13 @@ function parseNetscapeCookies(content) {
|
|
|
333
367
|
}
|
|
334
368
|
return cookies;
|
|
335
369
|
}
|
|
336
|
-
async function handleStateImport(command,
|
|
370
|
+
async function handleStateImport(command, args, output) {
|
|
337
371
|
try {
|
|
338
372
|
const cmdArgs = splitArgs(args);
|
|
339
373
|
const parsed = (0, command_1.parseCommand)(command, cmdArgs);
|
|
340
374
|
const filePath = parsed.file;
|
|
341
375
|
const outPath = parsed.out || path_1.default.join(os_1.default.homedir(), '.cssprobe-cli', 'states', 'imported.json');
|
|
342
376
|
const mergePath = parsed.merge;
|
|
343
|
-
// Read cookie content from file or stdin
|
|
344
377
|
let content;
|
|
345
378
|
if (filePath) {
|
|
346
379
|
const resolved = path_1.default.resolve(filePath);
|
|
@@ -351,7 +384,6 @@ async function handleStateImport(command, cmdEntry, args, output) {
|
|
|
351
384
|
content = fs_1.default.readFileSync(resolved, 'utf-8');
|
|
352
385
|
}
|
|
353
386
|
else {
|
|
354
|
-
// Read from stdin
|
|
355
387
|
content = '';
|
|
356
388
|
for await (const chunk of process.stdin) {
|
|
357
389
|
content += chunk;
|
|
@@ -361,13 +393,11 @@ async function handleStateImport(command, cmdEntry, args, output) {
|
|
|
361
393
|
output.error('No cookie data provided. Provide a file path or pipe data to stdin.');
|
|
362
394
|
return;
|
|
363
395
|
}
|
|
364
|
-
// Parse cookies
|
|
365
396
|
const newCookies = parseNetscapeCookies(content);
|
|
366
397
|
if (newCookies.length === 0) {
|
|
367
398
|
output.error('No valid cookies found. Expected Netscape format (tab-separated: domain\\tflag\\tpath\\tsecure\\texpires\\tname\\tvalue)');
|
|
368
399
|
return;
|
|
369
400
|
}
|
|
370
|
-
// Load existing state if merging
|
|
371
401
|
let state;
|
|
372
402
|
if (mergePath && fs_1.default.existsSync(mergePath)) {
|
|
373
403
|
state = JSON.parse(fs_1.default.readFileSync(mergePath, 'utf-8'));
|
|
@@ -376,7 +406,6 @@ async function handleStateImport(command, cmdEntry, args, output) {
|
|
|
376
406
|
else {
|
|
377
407
|
state = { cookies: [], origins: [] };
|
|
378
408
|
}
|
|
379
|
-
// Merge cookies (new cookies override existing ones with same domain+name+path)
|
|
380
409
|
const keyed = {};
|
|
381
410
|
for (const c of state.cookies) {
|
|
382
411
|
keyed[`${c.domain}|${c.name}|${c.path}`] = c;
|
|
@@ -385,7 +414,6 @@ async function handleStateImport(command, cmdEntry, args, output) {
|
|
|
385
414
|
keyed[`${nc.domain}|${nc.name}|${nc.path}`] = nc;
|
|
386
415
|
}
|
|
387
416
|
state.cookies = Object.values(keyed);
|
|
388
|
-
// Write output
|
|
389
417
|
const resolvedOut = path_1.default.resolve(outPath);
|
|
390
418
|
const dir = path_1.default.dirname(resolvedOut);
|
|
391
419
|
if (!fs_1.default.existsSync(dir))
|
|
@@ -402,274 +430,4 @@ async function handleStateImport(command, cmdEntry, args, output) {
|
|
|
402
430
|
output.error(e instanceof Error ? e.message : String(e));
|
|
403
431
|
}
|
|
404
432
|
}
|
|
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
|
-
}
|
|
675
433
|
//# sourceMappingURL=program.js.map
|
package/dist/config/config.d.ts
CHANGED
|
@@ -3,11 +3,16 @@ export interface ProfileConfig {
|
|
|
3
3
|
browser?: string;
|
|
4
4
|
depth?: string;
|
|
5
5
|
headed?: string;
|
|
6
|
+
viewport?: string;
|
|
6
7
|
}
|
|
7
8
|
export interface Config {
|
|
8
9
|
browser: string;
|
|
9
10
|
depth: number;
|
|
10
11
|
headed: boolean;
|
|
12
|
+
viewport?: {
|
|
13
|
+
width: number;
|
|
14
|
+
height: number;
|
|
15
|
+
};
|
|
11
16
|
output: 'text' | 'json';
|
|
12
17
|
raw: boolean;
|
|
13
18
|
}
|