cssprobe-cli 0.2.6 → 0.2.8
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 +86 -15
- package/README.md +128 -69
- package/dist/browser/launcher.d.ts +3 -0
- package/dist/browser/launcher.js +4 -0
- package/dist/cli/command.d.ts +1 -1
- package/dist/cli/commands.d.ts +6 -33
- package/dist/cli/commands.js +117 -48
- package/dist/cli/program.js +165 -386
- package/dist/config/helpGenerator.js +3 -1
- package/dist/daemon/daemon.d.ts +15 -0
- package/dist/daemon/daemon.js +259 -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 +1466 -0
- package/dist/help.json +82 -34
- 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/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,33 @@ 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
|
+
case 'resize':
|
|
62
|
+
case 'playwright':
|
|
63
|
+
await handleSessionCommand(commandName, command, rawArgs, output);
|
|
64
|
+
return;
|
|
53
65
|
}
|
|
54
|
-
|
|
66
|
+
output.error(`Unknown command: ${commandName}. Run 'cssprobe-cli --help' for usage.`);
|
|
55
67
|
}
|
|
56
68
|
function parseArgs(help) {
|
|
57
69
|
return (0, minimist_1.minimist)(process.argv.slice(2), {
|
|
@@ -91,121 +103,171 @@ function validateFlags(args, cmdEntry) {
|
|
|
91
103
|
if (unknownFlags.length)
|
|
92
104
|
throw new Error(`Unknown option${unknownFlags.length > 1 ? 's' : ''}: ${unknownFlags.map(f => `--${f}`).join(', ')}`);
|
|
93
105
|
}
|
|
94
|
-
// ──
|
|
95
|
-
async function
|
|
96
|
-
if (command.name !== 'inspect') {
|
|
97
|
-
output.error(`Unknown command: ${command.name}`);
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
106
|
+
// ── session commands ──
|
|
107
|
+
async function handleOpen(command, args, output) {
|
|
100
108
|
try {
|
|
101
109
|
const cmdArgs = splitArgs(args);
|
|
102
110
|
const parsed = (0, command_1.parseCommand)(command, cmdArgs);
|
|
103
|
-
const
|
|
104
|
-
const
|
|
105
|
-
const
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
let
|
|
109
|
-
if (
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
depth = Number(depthRaw);
|
|
114
|
-
}
|
|
115
|
-
const maxNodes = parsed['max-nodes'] || 60;
|
|
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, viewport: config.viewport });
|
|
129
|
-
if (!detected) {
|
|
130
|
-
output.error('Could not auto-detect a root element. Please provide a selector: cssprobe-cli inspect <url> <selector>');
|
|
111
|
+
const url = parsed.url || 'about:blank';
|
|
112
|
+
const browser = parsed.browser || 'chromium';
|
|
113
|
+
const headed = !!parsed.headed;
|
|
114
|
+
const viewportStr = parsed.viewport;
|
|
115
|
+
// Parse viewport
|
|
116
|
+
let viewport;
|
|
117
|
+
if (viewportStr) {
|
|
118
|
+
const parts = viewportStr.split('x');
|
|
119
|
+
if (parts.length !== 2) {
|
|
120
|
+
output.error('Invalid viewport format. Use WxH (e.g. 1280x720)');
|
|
131
121
|
return;
|
|
132
122
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
await launcher.waitForUser();
|
|
123
|
+
const width = parseInt(parts[0], 10);
|
|
124
|
+
const height = parseInt(parts[1], 10);
|
|
125
|
+
if (isNaN(width) || isNaN(height)) {
|
|
126
|
+
output.error('Invalid viewport dimensions. Must be numbers.');
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
viewport = { width, height };
|
|
141
130
|
}
|
|
142
|
-
//
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
131
|
+
// Start daemon
|
|
132
|
+
const { pid } = await session_1.Session.startDaemon({
|
|
133
|
+
browser,
|
|
134
|
+
headed,
|
|
135
|
+
viewport,
|
|
136
|
+
_: ['open', url],
|
|
137
|
+
}, 'open');
|
|
138
|
+
// Wait a bit for daemon to start
|
|
139
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
140
|
+
// Load session and navigate
|
|
141
|
+
const session = await (0, session_1.loadSession)();
|
|
142
|
+
if (session) {
|
|
143
|
+
await session.run({ _: ['goto', url] });
|
|
147
144
|
}
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
145
|
+
const result = {
|
|
146
|
+
sessionId: 'default',
|
|
147
|
+
pid,
|
|
148
|
+
url,
|
|
149
|
+
viewport: viewport || { width: 1280, height: 720 },
|
|
153
150
|
};
|
|
154
|
-
const snapshot = await launcher.collect(collectCfg);
|
|
155
|
-
await launcher.close();
|
|
156
|
-
const findings = (0, analyzer_1.analyze)(snapshot);
|
|
157
151
|
if (output.json) {
|
|
158
|
-
console.log(
|
|
152
|
+
console.log(JSON.stringify(result, null, 2));
|
|
159
153
|
}
|
|
160
154
|
else {
|
|
161
|
-
console.log(
|
|
155
|
+
console.log(`Browser opened at ${url}`);
|
|
156
|
+
console.log(`Session ID: default`);
|
|
157
|
+
console.log(`PID: ${pid}`);
|
|
158
|
+
console.log(`Viewport: ${viewport?.width || 1280}x${viewport?.height || 720}`);
|
|
162
159
|
}
|
|
163
160
|
}
|
|
164
161
|
catch (e) {
|
|
165
162
|
output.error(e instanceof Error ? e.message : String(e));
|
|
166
163
|
}
|
|
167
164
|
}
|
|
168
|
-
async function
|
|
169
|
-
const launcher = new launcher_1.BrowserLauncher(opts);
|
|
165
|
+
async function handleClose(output) {
|
|
170
166
|
try {
|
|
171
|
-
await
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
return
|
|
175
|
-
|
|
167
|
+
const session = await (0, session_1.loadSession)();
|
|
168
|
+
if (!session) {
|
|
169
|
+
output.error('No active session. Run: cssprobe-cli open <url>');
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
await session.stop();
|
|
173
|
+
if (output.json) {
|
|
174
|
+
console.log(JSON.stringify({ closed: true, sessionId: 'default' }, null, 2));
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
console.log('Browser closed.');
|
|
178
|
+
}
|
|
176
179
|
}
|
|
177
|
-
|
|
178
|
-
|
|
180
|
+
catch (e) {
|
|
181
|
+
output.error(e instanceof Error ? e.message : String(e));
|
|
179
182
|
}
|
|
180
183
|
}
|
|
181
|
-
|
|
182
|
-
async function handleLogin(command, cmdEntry, args, output) {
|
|
184
|
+
async function handleStatus(output) {
|
|
183
185
|
try {
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
186
|
+
const session = await (0, session_1.loadSession)();
|
|
187
|
+
if (!session) {
|
|
188
|
+
const result = { sessionId: 'default', alive: false };
|
|
189
|
+
if (output.json) {
|
|
190
|
+
console.log(JSON.stringify(result, null, 2));
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
console.log('No active session.');
|
|
194
|
+
}
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const canConnect = await session.canConnect();
|
|
198
|
+
const result = {
|
|
199
|
+
sessionId: 'default',
|
|
200
|
+
alive: canConnect,
|
|
201
|
+
config: session.config,
|
|
202
|
+
};
|
|
203
|
+
if (output.json) {
|
|
204
|
+
console.log(JSON.stringify(result, null, 2));
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
console.log(`Session: default`);
|
|
208
|
+
console.log(`Status: ${canConnect ? 'alive' : 'dead'}`);
|
|
209
|
+
if (canConnect) {
|
|
210
|
+
console.log(`Browser: ${session.config.browser.browserName}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
196
213
|
}
|
|
197
214
|
catch (e) {
|
|
198
215
|
output.error(e instanceof Error ? e.message : String(e));
|
|
199
216
|
}
|
|
200
217
|
}
|
|
201
|
-
function
|
|
218
|
+
async function handleSessionCommand(commandName, command, args, output) {
|
|
202
219
|
try {
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
220
|
+
const session = await (0, session_1.loadSession)();
|
|
221
|
+
if (!session) {
|
|
222
|
+
output.error('No active session. Run: cssprobe-cli open <url>');
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const cmdArgs = splitArgs(args);
|
|
226
|
+
const parsed = (0, command_1.parseCommand)(command, cmdArgs);
|
|
227
|
+
// Build command args for daemon
|
|
228
|
+
const daemonArgs = [commandName];
|
|
229
|
+
// Add positional args
|
|
230
|
+
if (command.args) {
|
|
231
|
+
const argsSchema = command.args;
|
|
232
|
+
const argNames = Object.keys(argsSchema.shape);
|
|
233
|
+
for (const name of argNames) {
|
|
234
|
+
if (parsed[name] !== undefined) {
|
|
235
|
+
daemonArgs.push(String(parsed[name]));
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// Add options
|
|
240
|
+
if (command.options) {
|
|
241
|
+
const optionsSchema = command.options;
|
|
242
|
+
const optionNames = Object.keys(optionsSchema.shape);
|
|
243
|
+
for (const name of optionNames) {
|
|
244
|
+
if (parsed[name] !== undefined) {
|
|
245
|
+
if (typeof parsed[name] === 'boolean') {
|
|
246
|
+
if (parsed[name])
|
|
247
|
+
daemonArgs.push(`--${name}`);
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
daemonArgs.push(`--${name}=${parsed[name]}`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// Run command in session
|
|
256
|
+
const result = await session.run({ ...cmdArgs, _: daemonArgs });
|
|
257
|
+
if (output.json) {
|
|
258
|
+
try {
|
|
259
|
+
console.log(JSON.parse(result.text));
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
console.log(result.text);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
console.log(result.text);
|
|
267
|
+
}
|
|
206
268
|
}
|
|
207
|
-
catch {
|
|
208
|
-
|
|
269
|
+
catch (e) {
|
|
270
|
+
output.error(e instanceof Error ? e.message : String(e));
|
|
209
271
|
}
|
|
210
272
|
}
|
|
211
273
|
// ── config commands ──
|
|
@@ -301,12 +363,6 @@ function handleSkillCommands(commandName, command, args, output) {
|
|
|
301
363
|
}
|
|
302
364
|
return false;
|
|
303
365
|
}
|
|
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
366
|
function parseNetscapeCookies(content) {
|
|
311
367
|
const cookies = [];
|
|
312
368
|
const lines = content.split('\n');
|
|
@@ -318,7 +374,7 @@ function parseNetscapeCookies(content) {
|
|
|
318
374
|
if (parts.length < 7)
|
|
319
375
|
continue;
|
|
320
376
|
const [domain, flag, path, secure, expires, name, ...valueParts] = parts;
|
|
321
|
-
const value = valueParts.join('\t');
|
|
377
|
+
const value = valueParts.join('\t');
|
|
322
378
|
const expiresNum = parseInt(expires, 10);
|
|
323
379
|
cookies.push({
|
|
324
380
|
name,
|
|
@@ -333,14 +389,13 @@ function parseNetscapeCookies(content) {
|
|
|
333
389
|
}
|
|
334
390
|
return cookies;
|
|
335
391
|
}
|
|
336
|
-
async function handleStateImport(command,
|
|
392
|
+
async function handleStateImport(command, args, output) {
|
|
337
393
|
try {
|
|
338
394
|
const cmdArgs = splitArgs(args);
|
|
339
395
|
const parsed = (0, command_1.parseCommand)(command, cmdArgs);
|
|
340
396
|
const filePath = parsed.file;
|
|
341
397
|
const outPath = parsed.out || path_1.default.join(os_1.default.homedir(), '.cssprobe-cli', 'states', 'imported.json');
|
|
342
398
|
const mergePath = parsed.merge;
|
|
343
|
-
// Read cookie content from file or stdin
|
|
344
399
|
let content;
|
|
345
400
|
if (filePath) {
|
|
346
401
|
const resolved = path_1.default.resolve(filePath);
|
|
@@ -351,7 +406,6 @@ async function handleStateImport(command, cmdEntry, args, output) {
|
|
|
351
406
|
content = fs_1.default.readFileSync(resolved, 'utf-8');
|
|
352
407
|
}
|
|
353
408
|
else {
|
|
354
|
-
// Read from stdin
|
|
355
409
|
content = '';
|
|
356
410
|
for await (const chunk of process.stdin) {
|
|
357
411
|
content += chunk;
|
|
@@ -361,13 +415,11 @@ async function handleStateImport(command, cmdEntry, args, output) {
|
|
|
361
415
|
output.error('No cookie data provided. Provide a file path or pipe data to stdin.');
|
|
362
416
|
return;
|
|
363
417
|
}
|
|
364
|
-
// Parse cookies
|
|
365
418
|
const newCookies = parseNetscapeCookies(content);
|
|
366
419
|
if (newCookies.length === 0) {
|
|
367
420
|
output.error('No valid cookies found. Expected Netscape format (tab-separated: domain\\tflag\\tpath\\tsecure\\texpires\\tname\\tvalue)');
|
|
368
421
|
return;
|
|
369
422
|
}
|
|
370
|
-
// Load existing state if merging
|
|
371
423
|
let state;
|
|
372
424
|
if (mergePath && fs_1.default.existsSync(mergePath)) {
|
|
373
425
|
state = JSON.parse(fs_1.default.readFileSync(mergePath, 'utf-8'));
|
|
@@ -376,7 +428,6 @@ async function handleStateImport(command, cmdEntry, args, output) {
|
|
|
376
428
|
else {
|
|
377
429
|
state = { cookies: [], origins: [] };
|
|
378
430
|
}
|
|
379
|
-
// Merge cookies (new cookies override existing ones with same domain+name+path)
|
|
380
431
|
const keyed = {};
|
|
381
432
|
for (const c of state.cookies) {
|
|
382
433
|
keyed[`${c.domain}|${c.name}|${c.path}`] = c;
|
|
@@ -385,7 +436,6 @@ async function handleStateImport(command, cmdEntry, args, output) {
|
|
|
385
436
|
keyed[`${nc.domain}|${nc.name}|${nc.path}`] = nc;
|
|
386
437
|
}
|
|
387
438
|
state.cookies = Object.values(keyed);
|
|
388
|
-
// Write output
|
|
389
439
|
const resolvedOut = path_1.default.resolve(outPath);
|
|
390
440
|
const dir = path_1.default.dirname(resolvedOut);
|
|
391
441
|
if (!fs_1.default.existsSync(dir))
|
|
@@ -402,275 +452,4 @@ async function handleStateImport(command, cmdEntry, args, output) {
|
|
|
402
452
|
output.error(e instanceof Error ? e.message : String(e));
|
|
403
453
|
}
|
|
404
454
|
}
|
|
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 config = (0, config_1.loadConfig)(args);
|
|
431
|
-
const launcher = new launcher_1.BrowserLauncher({ browser, headed: true, state, viewport: config.viewport });
|
|
432
|
-
await launcher.open(url);
|
|
433
|
-
let currentUrl = url;
|
|
434
|
-
// Resolve auto depth
|
|
435
|
-
if (depth === 0) {
|
|
436
|
-
try {
|
|
437
|
-
const candidates = await launcher.autoDetectRoot();
|
|
438
|
-
const selector = candidates[0] || 'body';
|
|
439
|
-
const measured = await launcher.measureDepth(selector);
|
|
440
|
-
depth = Math.min(measured, 20) || 6;
|
|
441
|
-
console.error(`Auto-detected depth: ${measured} (using ${depth})`);
|
|
442
|
-
}
|
|
443
|
-
catch {
|
|
444
|
-
depth = 6;
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
console.error('');
|
|
448
|
-
console.error(`Browser opened at ${currentUrl}`);
|
|
449
|
-
console.error("Type 'help' for available commands, 'quit' to exit.");
|
|
450
|
-
console.error('');
|
|
451
|
-
const rl = require('readline').createInterface({
|
|
452
|
-
input: process.stdin,
|
|
453
|
-
output: process.stderr,
|
|
454
|
-
prompt: 'inspect> ',
|
|
455
|
-
});
|
|
456
|
-
rl.prompt();
|
|
457
|
-
rl.on('line', async (line) => {
|
|
458
|
-
const trimmed = line.trim();
|
|
459
|
-
if (!trimmed) {
|
|
460
|
-
rl.prompt();
|
|
461
|
-
return;
|
|
462
|
-
}
|
|
463
|
-
// Parse: command [selector] [--options]
|
|
464
|
-
const parts = trimmed.split(/\s+/);
|
|
465
|
-
const cmd = parts[0];
|
|
466
|
-
const rest = parts.slice(1);
|
|
467
|
-
try {
|
|
468
|
-
switch (cmd) {
|
|
469
|
-
case 'help': {
|
|
470
|
-
console.error('');
|
|
471
|
-
console.error('Commands:');
|
|
472
|
-
for (const [name, desc] of Object.entries(REPL_COMMANDS)) {
|
|
473
|
-
console.error(` ${name.padEnd(12)} ${desc}`);
|
|
474
|
-
}
|
|
475
|
-
console.error('');
|
|
476
|
-
break;
|
|
477
|
-
}
|
|
478
|
-
case 'quit':
|
|
479
|
-
case 'exit': {
|
|
480
|
-
await launcher.close();
|
|
481
|
-
console.error('Browser closed.');
|
|
482
|
-
rl.close();
|
|
483
|
-
process.exit(0);
|
|
484
|
-
}
|
|
485
|
-
case 'navigate': {
|
|
486
|
-
const newUrl = rest[0];
|
|
487
|
-
if (!newUrl) {
|
|
488
|
-
console.error('Usage: navigate <url>');
|
|
489
|
-
break;
|
|
490
|
-
}
|
|
491
|
-
await launcher.navigate(newUrl);
|
|
492
|
-
currentUrl = newUrl;
|
|
493
|
-
console.error(`Navigated to ${currentUrl}`);
|
|
494
|
-
break;
|
|
495
|
-
}
|
|
496
|
-
case 'depth': {
|
|
497
|
-
const n = parseInt(rest[0], 10);
|
|
498
|
-
if (isNaN(n) || n < 1) {
|
|
499
|
-
console.error('Usage: depth <n>');
|
|
500
|
-
break;
|
|
501
|
-
}
|
|
502
|
-
depth = Math.min(n, 20);
|
|
503
|
-
console.error(`Depth set to ${depth}`);
|
|
504
|
-
break;
|
|
505
|
-
}
|
|
506
|
-
case 'tree':
|
|
507
|
-
case 'layout':
|
|
508
|
-
case 'sketch':
|
|
509
|
-
case 'findings':
|
|
510
|
-
case 'json':
|
|
511
|
-
case 'report': {
|
|
512
|
-
// Parse selector + options from remaining args
|
|
513
|
-
let selector = '';
|
|
514
|
-
let useBrief = false;
|
|
515
|
-
let useLayout = false;
|
|
516
|
-
let useJson = false;
|
|
517
|
-
for (const arg of rest) {
|
|
518
|
-
if (arg === '--brief')
|
|
519
|
-
useBrief = true;
|
|
520
|
-
else if (arg === '--layout')
|
|
521
|
-
useLayout = true;
|
|
522
|
-
else if (arg === '--json')
|
|
523
|
-
useJson = true;
|
|
524
|
-
else if (!arg.startsWith('-') && !selector)
|
|
525
|
-
selector = arg;
|
|
526
|
-
}
|
|
527
|
-
// Command-specific defaults
|
|
528
|
-
if (cmd === 'layout')
|
|
529
|
-
useLayout = true;
|
|
530
|
-
if (cmd === 'sketch')
|
|
531
|
-
useBrief = true;
|
|
532
|
-
if (cmd === 'json')
|
|
533
|
-
useJson = true;
|
|
534
|
-
if (!selector) {
|
|
535
|
-
console.error('Usage: ' + cmd + ' <selector> [--brief] [--layout] [--json]');
|
|
536
|
-
break;
|
|
537
|
-
}
|
|
538
|
-
// Measure depth if needed
|
|
539
|
-
let useDepth = depth;
|
|
540
|
-
if (useDepth === 0) {
|
|
541
|
-
try {
|
|
542
|
-
const measured = await launcher.measureDepth(selector);
|
|
543
|
-
useDepth = Math.min(measured, 20) || 6;
|
|
544
|
-
}
|
|
545
|
-
catch {
|
|
546
|
-
useDepth = 6;
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
const collectCfg = {
|
|
550
|
-
rootSelector: selector,
|
|
551
|
-
upTo: 'html',
|
|
552
|
-
downDepth: useDepth,
|
|
553
|
-
maxNodes: 60,
|
|
554
|
-
};
|
|
555
|
-
const snapshot = await launcher.collect(collectCfg);
|
|
556
|
-
if (snapshot.error) {
|
|
557
|
-
console.error(`Error: ${snapshot.error}`);
|
|
558
|
-
if (snapshot.candidates?.length) {
|
|
559
|
-
console.error(`Similar nodes: ${snapshot.candidates.join(', ')}`);
|
|
560
|
-
}
|
|
561
|
-
break;
|
|
562
|
-
}
|
|
563
|
-
const findings = (0, analyzer_1.analyze)(snapshot);
|
|
564
|
-
if (cmd === 'findings') {
|
|
565
|
-
// Only show findings section
|
|
566
|
-
const important = findings.filter(f => f.level === 'warning' || f.level === 'error');
|
|
567
|
-
if (important.length === 0) {
|
|
568
|
-
console.log('No warnings or errors');
|
|
569
|
-
}
|
|
570
|
-
else {
|
|
571
|
-
for (const f of important) {
|
|
572
|
-
const icon = f.level === 'error' ? '\u26A0' : '\u26A0';
|
|
573
|
-
console.log(`- ${icon} ${f.message} [${f.confidence}]${f.location ? ` @ ${f.location}` : ''}`);
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
else if (useJson) {
|
|
578
|
-
console.log(JSON.stringify((0, renderer_1.renderJSON)(snapshot, findings, useBrief), null, 2));
|
|
579
|
-
}
|
|
580
|
-
else if (cmd === 'tree') {
|
|
581
|
-
// Only DOM tree
|
|
582
|
-
const lines = [];
|
|
583
|
-
lines.push(`## DOM tree (${snapshot.downDepth} levels deep)`);
|
|
584
|
-
lines.push('```');
|
|
585
|
-
// Inline renderNode
|
|
586
|
-
const renderNodeLocal = (node, indent) => {
|
|
587
|
-
if (!node)
|
|
588
|
-
return;
|
|
589
|
-
const cls = node.classes?.length ? `.${node.classes.join('.')}` : '';
|
|
590
|
-
const id = node.id ? `#${node.id}` : '';
|
|
591
|
-
const m = node.metrics;
|
|
592
|
-
const s = node.shape || {};
|
|
593
|
-
const shapeStr = s.role ? `[${s.role} h:${s.heightStrategy} w:${s.widthStrategy}]` : '';
|
|
594
|
-
const flag = [];
|
|
595
|
-
if (node.flags?.overflowsParent)
|
|
596
|
-
flag.push('\u26A0parent-overflow');
|
|
597
|
-
if (node.flags?.overflowsViewport)
|
|
598
|
-
flag.push('\u26A0viewport-overflow');
|
|
599
|
-
if (m?.clientHeight === 0 && m?.offsetHeight === 0)
|
|
600
|
-
flag.push('\u26A0collapsed');
|
|
601
|
-
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(' ') : ''}`);
|
|
602
|
-
for (const child of node.children || [])
|
|
603
|
-
renderNodeLocal(child, indent + 1);
|
|
604
|
-
};
|
|
605
|
-
renderNodeLocal(snapshot.tree, 0);
|
|
606
|
-
lines.push('```');
|
|
607
|
-
console.log(lines.join('\n'));
|
|
608
|
-
}
|
|
609
|
-
else {
|
|
610
|
-
console.log((0, renderer_1.renderMarkdown)(snapshot, findings, useBrief, useLayout));
|
|
611
|
-
}
|
|
612
|
-
break;
|
|
613
|
-
}
|
|
614
|
-
default: {
|
|
615
|
-
// Try as a direct selector (shorthand for 'report <selector>')
|
|
616
|
-
if (!cmd.startsWith('-')) {
|
|
617
|
-
const selector = cmd;
|
|
618
|
-
let useBrief = false;
|
|
619
|
-
let useLayout = false;
|
|
620
|
-
for (const arg of rest) {
|
|
621
|
-
if (arg === '--brief')
|
|
622
|
-
useBrief = true;
|
|
623
|
-
if (arg === '--layout')
|
|
624
|
-
useLayout = true;
|
|
625
|
-
}
|
|
626
|
-
let useDepth = depth;
|
|
627
|
-
if (useDepth === 0) {
|
|
628
|
-
try {
|
|
629
|
-
const measured = await launcher.measureDepth(selector);
|
|
630
|
-
useDepth = Math.min(measured, 20) || 6;
|
|
631
|
-
}
|
|
632
|
-
catch {
|
|
633
|
-
useDepth = 6;
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
const collectCfg = {
|
|
637
|
-
rootSelector: selector,
|
|
638
|
-
upTo: 'html',
|
|
639
|
-
downDepth: useDepth,
|
|
640
|
-
maxNodes: 60,
|
|
641
|
-
};
|
|
642
|
-
const snapshot = await launcher.collect(collectCfg);
|
|
643
|
-
if (snapshot.error) {
|
|
644
|
-
console.error(`Error: ${snapshot.error}`);
|
|
645
|
-
if (snapshot.candidates?.length) {
|
|
646
|
-
console.error(`Similar nodes: ${snapshot.candidates.join(', ')}`);
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
else {
|
|
650
|
-
const findings = (0, analyzer_1.analyze)(snapshot);
|
|
651
|
-
console.log((0, renderer_1.renderMarkdown)(snapshot, findings, useBrief, useLayout));
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
else {
|
|
655
|
-
console.error(`Unknown command: ${cmd}. Type 'help' for available commands.`);
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
catch (e) {
|
|
661
|
-
console.error(`Error: ${e.message || e}`);
|
|
662
|
-
}
|
|
663
|
-
rl.prompt();
|
|
664
|
-
});
|
|
665
|
-
rl.on('close', async () => {
|
|
666
|
-
await launcher.close().catch(() => { });
|
|
667
|
-
process.exit(0);
|
|
668
|
-
});
|
|
669
|
-
// Handle SIGINT gracefully
|
|
670
|
-
process.on('SIGINT', async () => {
|
|
671
|
-
console.error('\nClosing browser...');
|
|
672
|
-
await launcher.close().catch(() => { });
|
|
673
|
-
process.exit(0);
|
|
674
|
-
});
|
|
675
|
-
}
|
|
676
455
|
//# sourceMappingURL=program.js.map
|
|
@@ -80,7 +80,9 @@ function generateCommandHelp(command) {
|
|
|
80
80
|
return lines.join('\n');
|
|
81
81
|
}
|
|
82
82
|
const categories = [
|
|
83
|
-
{ name: 'core', title: '
|
|
83
|
+
{ name: 'core', title: 'Session Management' },
|
|
84
|
+
{ name: 'cssprobe', title: 'CSS Inspection' },
|
|
85
|
+
{ name: 'export', title: 'Export' },
|
|
84
86
|
{ name: 'config', title: 'Configuration' },
|
|
85
87
|
{ name: 'skill', title: 'Agent Skills' },
|
|
86
88
|
];
|