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.
@@ -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,31 @@ 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
+ await handleSessionCommand(commandName, command, rawArgs, output);
62
+ return;
53
63
  }
54
- await handleInspect(command, cmdEntry, rawArgs, output);
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
- // ── inspect ──
95
- async function handleInspect(command, cmdEntry, args, output) {
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 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
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
- 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 });
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(output.format((0, renderer_1.renderJSON)(snapshot, findings, brief)));
131
+ console.log(JSON.stringify(result, null, 2));
159
132
  }
160
133
  else {
161
- console.log((0, renderer_1.renderMarkdown)(snapshot, findings, brief, layout));
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 autoDetectRoot(url, opts) {
169
- const launcher = new launcher_1.BrowserLauncher(opts);
143
+ async function handleClose(output) {
170
144
  try {
171
- await launcher.open(url);
172
- const candidates = await launcher.autoDetectRoot();
173
- if (candidates.length > 0)
174
- return candidates[0];
175
- return null;
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
- finally {
178
- await launcher.close();
158
+ catch (e) {
159
+ output.error(e instanceof Error ? e.message : String(e));
179
160
  }
180
161
  }
181
- // ── login ──
182
- async function handleLogin(command, cmdEntry, args, output) {
162
+ async function handleStatus(output) {
183
163
  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}` }));
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 defaultStatePath(url) {
196
+ async function handleSessionCommand(commandName, command, args, output) {
202
197
  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`);
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
- return path_1.default.join(os_1.default.homedir(), '.cssprobe-cli', 'states', 'default.json');
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'); // value may contain tabs
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, cmdEntry, args, output) {
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
@@ -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
  }