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.
@@ -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 launcher_1 = require("../browser/launcher");
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
- if (commandName === 'login') {
43
- await handleLogin(command, cmdEntry, rawArgs, output);
44
- return;
45
- }
46
- if (commandName === 'state-import') {
47
- await handleStateImport(command, cmdEntry, rawArgs, output);
48
- return;
49
- }
50
- if (commandName === 'interactive') {
51
- await handleInteractive(command, cmdEntry, rawArgs, output);
52
- return;
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
- await handleInspect(command, cmdEntry, rawArgs, output);
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
- // ── inspect ──
95
- async function handleInspect(command, cmdEntry, args, output) {
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 config = (0, config_1.loadConfig)(args);
104
- const url = parsed.url;
105
- const selector = parsed.selector;
106
- const zoom = !!parsed.zoom;
107
- const depthRaw = parsed.depth;
108
- let depth;
109
- if (depthRaw === 'auto' || depthRaw === undefined) {
110
- depth = 0; // placeholder, resolved after browser opens
111
- }
112
- else {
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
- rootSelector = detected;
134
- console.error(`Auto-detected root: ${rootSelector}`);
135
- }
136
- const launcher = new launcher_1.BrowserLauncher({ browser, headed, state, viewport: config.viewport });
137
- await launcher.open(url);
138
- // Wait for user interaction if --wait
139
- if (wait) {
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
- // 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})`);
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 collectCfg = {
149
- rootSelector,
150
- upTo,
151
- downDepth: depth,
152
- maxNodes,
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(output.format((0, renderer_1.renderJSON)(snapshot, findings, brief)));
152
+ console.log(JSON.stringify(result, null, 2));
159
153
  }
160
154
  else {
161
- console.log((0, renderer_1.renderMarkdown)(snapshot, findings, brief, layout));
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 autoDetectRoot(url, opts) {
169
- const launcher = new launcher_1.BrowserLauncher(opts);
165
+ async function handleClose(output) {
170
166
  try {
171
- await launcher.open(url);
172
- const candidates = await launcher.autoDetectRoot();
173
- if (candidates.length > 0)
174
- return candidates[0];
175
- return null;
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
- finally {
178
- await launcher.close();
180
+ catch (e) {
181
+ output.error(e instanceof Error ? e.message : String(e));
179
182
  }
180
183
  }
181
- // ── login ──
182
- async function handleLogin(command, cmdEntry, args, output) {
184
+ async function handleStatus(output) {
183
185
  try {
184
- const cmdArgs = splitArgs(args);
185
- const parsed = (0, command_1.parseCommand)(command, cmdArgs);
186
- const config = (0, config_1.loadConfig)(args);
187
- const url = parsed.url;
188
- const browser = parsed.browser || config.browser;
189
- const outPath = parsed.out || defaultStatePath(url);
190
- if (!url)
191
- output.error('URL is required. Usage: cssprobe-cli login <url>');
192
- const launcher = new launcher_1.BrowserLauncher({ browser, headed: true });
193
- const savedPath = await launcher.loginAndSave(url, outPath);
194
- await launcher.close();
195
- console.log(output.format({ saved: savedPath, usage: `cssprobe-cli inspect <url> --state ${savedPath}` }));
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 defaultStatePath(url) {
218
+ async function handleSessionCommand(commandName, command, args, output) {
202
219
  try {
203
- const hostname = new URL(url).hostname.replace(/[^a-z0-9.-]/g, '_');
204
- const dir = path_1.default.join(os_1.default.homedir(), '.cssprobe-cli', 'states');
205
- return path_1.default.join(dir, `${hostname}.json`);
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
- return path_1.default.join(os_1.default.homedir(), '.cssprobe-cli', 'states', 'default.json');
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'); // value may contain tabs
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, cmdEntry, args, output) {
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: 'Inspection' },
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
  ];