@probebrowser/trace 1.5.0 → 1.5.1

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/dist/index.js CHANGED
@@ -8,11 +8,8 @@ import { program } from 'commander';
8
8
  import chalk from 'chalk';
9
9
  import ora from 'ora';
10
10
  import readline from 'readline';
11
- import { Trace } from '@probebrowser/sdk';
12
- import { exec } from 'child_process';
13
- import { promisify } from 'util';
14
- const execAsync = promisify(exec);
15
- const VERSION = '1.5.0';
11
+ import { Trace } from '../../sdk/dist/index.js';
12
+ const VERSION = '1.0.4';
16
13
  // ============================================
17
14
  // LOGGING HELPERS
18
15
  // ============================================
@@ -136,7 +133,6 @@ program
136
133
  .option('--api-key <key>', 'API key')
137
134
  .option('--headless', 'Run in headless mode', true)
138
135
  .option('--no-headless', 'Run with visible browser')
139
- .option('--connect <port>', 'Connect to existing Chrome instance on port (e.g. 9222)')
140
136
  .option('--json', 'Output as JSON')
141
137
  .option('-v, --verbose', 'Verbose output')
142
138
  .action(async (url, options) => {
@@ -149,7 +145,6 @@ program
149
145
  apiKey: getApiKey(options),
150
146
  headless: options.headless,
151
147
  verbose: options.verbose,
152
- connectPort: options.connect ? parseInt(options.connect) : undefined,
153
148
  });
154
149
  spinner.text = `Connecting to ${url}`;
155
150
  await trace.connect(url);
@@ -199,7 +194,6 @@ program
199
194
  .description('Full debug scan of a URL')
200
195
  .option('--api-url <url>', 'API endpoint', 'https://dev-intelli-api.azurewebsites.net')
201
196
  .option('--api-key <key>', 'API key')
202
- .option('--connect <port>', 'Connect to existing Chrome instance on port (e.g. 9222)')
203
197
  .option('--json', 'Output as JSON')
204
198
  .action(async (url, options) => {
205
199
  const spinner = ora('Connecting...').start();
@@ -209,7 +203,6 @@ program
209
203
  trace = new Trace({
210
204
  apiUrl: options.apiUrl,
211
205
  apiKey: getApiKey(options),
212
- connectPort: options.connect ? parseInt(options.connect) : undefined,
213
206
  });
214
207
  spinner.text = `Connecting to ${url}`;
215
208
  await trace.connect(url);
@@ -256,715 +249,36 @@ program
256
249
  program
257
250
  .command('errors <url>')
258
251
  .alias('e')
259
- .description('AI-powered error analysis with code context and fix suggestions')
260
- .option('--api-url <url>', 'API endpoint', 'https://dev-intelli-api.azurewebsites.net')
261
- .option('--api-key <key>', 'API key')
262
- .option('--connect <port>', 'Connect to existing Chrome instance on port (e.g. 9222)')
263
- .option('--raw', 'Show raw errors without AI analysis')
264
- .option('--json', 'Output as JSON')
265
- .action(async (url, options) => {
266
- const spinner = ora('Connecting...').start();
267
- let trace = null;
268
- try {
269
- trace = new Trace({
270
- apiUrl: options.apiUrl,
271
- apiKey: getApiKey(options),
272
- headless: true,
273
- connectPort: options.connect ? parseInt(options.connect) : undefined
274
- });
275
- spinner.text = `Connecting to ${url}`;
276
- await trace.connect(url);
277
- // Raw mode - old behavior without AI
278
- if (options.raw) {
279
- spinner.text = 'Checking for errors...';
280
- const errors = await trace.getConsoleErrors();
281
- const groups = await trace.getErrorGroups();
282
- await trace.disconnect();
283
- spinner.stop();
284
- if (options.json) {
285
- console.log(JSON.stringify({ errors, groups }, null, 2));
286
- return;
287
- }
288
- console.log();
289
- console.log(chalk.bold(`🔴 Console Errors (${errors.length})`));
290
- console.log(chalk.gray('─'.repeat(60)));
291
- if (errors.length === 0) {
292
- log.success('No console errors found!');
293
- }
294
- else {
295
- for (const err of errors.slice(0, 20)) {
296
- console.log();
297
- console.log(` ${chalk.red('●')} ${chalk.bold(err.errorType || 'Error')}`);
298
- console.log(` ${err.message.substring(0, 200)}`);
299
- if (err.location) {
300
- console.log(` ${chalk.dim(err.location)}`);
301
- }
302
- }
303
- }
304
- console.log();
305
- return;
306
- }
307
- // AI-Powered Analysis (new behavior)
308
- spinner.text = chalk.cyan('🔍 Analyzing errors with AI...');
309
- const analysis = await trace.analyzeErrors();
310
- await trace.disconnect();
311
- spinner.stop();
312
- if (options.json) {
313
- console.log(JSON.stringify(analysis, null, 2));
314
- return;
315
- }
316
- console.log();
317
- console.log(chalk.bold.cyan('🧠 AI Error Analysis'));
318
- console.log(chalk.gray('─'.repeat(60)));
319
- console.log();
320
- if (analysis.totalErrors === 0) {
321
- console.log(chalk.green('✓ No errors detected on this page!'));
322
- console.log();
323
- return;
324
- }
325
- // Summary
326
- console.log(chalk.yellow(`Found ${analysis.totalErrors} error(s)`));
327
- console.log(chalk.dim(analysis.summary));
328
- console.log();
329
- // Each analyzed error
330
- for (const err of analysis.errors) {
331
- console.log(chalk.red('━'.repeat(60)));
332
- console.log();
333
- console.log(` ${chalk.red('❌')} ${chalk.bold(err.summary)}`);
334
- console.log();
335
- console.log(` ${chalk.cyan('Why:')} ${err.explanation}`);
336
- if (err.location) {
337
- console.log(` ${chalk.cyan('Location:')} ${chalk.dim(err.location)}`);
338
- }
339
- if (err.codeSnippet) {
340
- console.log();
341
- console.log(` ${chalk.cyan('Code:')}`);
342
- // Indent code snippet
343
- const lines = err.codeSnippet.split('\n');
344
- for (const line of lines.slice(0, 10)) {
345
- console.log(` ${chalk.dim(line)}`);
346
- }
347
- }
348
- if (err.suggestedFix) {
349
- console.log();
350
- console.log(` ${chalk.green('Fix:')} ${err.suggestedFix}`);
351
- }
352
- if (err.codeChange) {
353
- console.log();
354
- console.log(` ${chalk.green('Suggested Code Change:')}`);
355
- const changes = err.codeChange.split('\n');
356
- for (const line of changes.slice(0, 5)) {
357
- console.log(` ${chalk.green(line)}`);
358
- }
359
- }
360
- console.log();
361
- }
362
- // Recommendations
363
- if (analysis.recommendations.length > 0) {
364
- console.log(chalk.cyan('📋 Recommendations:'));
365
- for (const rec of analysis.recommendations) {
366
- console.log(` ${chalk.cyan('→')} ${rec}`);
367
- }
368
- console.log();
369
- }
370
- console.log(chalk.dim('Powered by Trace Intelligence Engine'));
371
- console.log();
372
- }
373
- catch (error) {
374
- spinner.stop();
375
- if (trace)
376
- await trace.disconnect().catch(() => { });
377
- if (isAuthError(error)) {
378
- log.authError();
379
- process.exit(1);
380
- }
381
- log.error(`Failed: ${error}`);
382
- process.exit(1);
383
- }
384
- });
385
- // ============================================
386
- // EVAL COMMAND - Execute JavaScript
387
- // ============================================
388
- program
389
- .command('eval <expression>')
390
- .description('Execute JavaScript in the browser context')
391
- .requiredOption('-u, --url <url>', 'URL to connect to')
392
- .option('--connect <port>', 'Connect to existing Chrome instance on port (e.g. 9222)')
393
- .option('--json', 'Output as JSON')
394
- .action(async (expression, options) => {
395
- const spinner = ora('Connecting...').start();
396
- let trace = null;
397
- try {
398
- trace = new Trace({
399
- headless: true,
400
- connectPort: options.connect ? parseInt(options.connect) : undefined
401
- });
402
- spinner.text = `Connecting to ${options.url}`;
403
- await trace.connect(options.url);
404
- spinner.text = 'Evaluating...';
405
- const result = await trace.evaluate(expression);
406
- await trace.disconnect();
407
- spinner.stop();
408
- if (options.json) {
409
- console.log(JSON.stringify({ expression, result }, null, 2));
410
- return;
411
- }
412
- console.log();
413
- console.log(chalk.cyan('Expression:'), expression);
414
- console.log(chalk.green('Result:'));
415
- console.log(JSON.stringify(result, null, 2));
416
- console.log();
417
- }
418
- catch (error) {
419
- spinner.stop();
420
- if (trace)
421
- await trace.disconnect().catch(() => { });
422
- log.error(`Failed: ${error}`);
423
- process.exit(1);
424
- }
425
- });
426
- // ============================================
427
- // EXPLORE COMMAND - Tree Object Exploration
428
- // ============================================
429
- program
430
- .command('explore <expression>')
431
- .alias('x')
432
- .description('Explore an object with tree-like property display')
433
- .requiredOption('-u, --url <url>', 'URL to connect to')
434
- .option('--connect <port>', 'Connect to existing Chrome instance on port (e.g. 9222)')
435
- .option('--depth <n>', 'Max depth to expand', '3')
436
- .option('--json', 'Output as JSON')
437
- .action(async (expression, options) => {
438
- const spinner = ora('Connecting...').start();
439
- let trace = null;
440
- try {
441
- trace = new Trace({
442
- headless: true,
443
- connectPort: options.connect ? parseInt(options.connect) : undefined
444
- });
445
- spinner.text = `Connecting to ${options.url}`;
446
- await trace.connect(options.url);
447
- spinner.text = 'Inspecting...';
448
- // Evaluate and get detailed object info
449
- const maxDepth = parseInt(options.depth) || 3;
450
- const inspectCode = `
451
- (function inspect(obj, depth = 0, maxDepth = ${maxDepth}, path = '') {
452
- if (depth > maxDepth) return { _truncated: true, _type: typeof obj };
453
- if (obj === null) return { _value: null, _type: 'null' };
454
- if (obj === undefined) return { _value: undefined, _type: 'undefined' };
455
-
456
- const type = typeof obj;
457
-
458
- if (type === 'function') {
459
- return { _type: 'function', _name: obj.name || 'anonymous', _length: obj.length };
460
- }
461
- if (type !== 'object') {
462
- return { _value: obj, _type: type };
463
- }
464
- if (Array.isArray(obj)) {
465
- return {
466
- _type: 'array',
467
- _length: obj.length,
468
- _items: obj.slice(0, 10).map((item, i) => ({ key: i, ...inspect(item, depth + 1, maxDepth, path + '[' + i + ']') })),
469
- _truncated: obj.length > 10
470
- };
471
- }
472
- if (obj instanceof Date) {
473
- return { _type: 'Date', _value: obj.toISOString() };
474
- }
475
- if (obj instanceof RegExp) {
476
- return { _type: 'RegExp', _value: obj.toString() };
477
- }
478
- if (obj instanceof Error) {
479
- return { _type: 'Error', _name: obj.name, _message: obj.message, _stack: obj.stack?.split('\\n').slice(0, 5) };
480
- }
481
-
482
- // Regular object
483
- const keys = Object.keys(obj).slice(0, 20);
484
- const result = {
485
- _type: obj.constructor?.name || 'Object',
486
- _keys: keys.length,
487
- _properties: {}
488
- };
489
- for (const key of keys) {
490
- try {
491
- result._properties[key] = inspect(obj[key], depth + 1, maxDepth, path + '.' + key);
492
- } catch (e) {
493
- result._properties[key] = { _error: 'Cannot access' };
494
- }
495
- }
496
- if (Object.keys(obj).length > 20) {
497
- result._truncated = true;
498
- }
499
- return result;
500
- })(${expression})
501
- `;
502
- const result = await trace.evaluate(inspectCode);
503
- await trace.disconnect();
504
- spinner.stop();
505
- if (options.json) {
506
- console.log(JSON.stringify({ expression, result }, null, 2));
507
- return;
508
- }
509
- console.log();
510
- console.log(chalk.bold.cyan('🔍 Object Inspector'));
511
- console.log(chalk.gray('─'.repeat(60)));
512
- console.log(chalk.dim(`Expression: ${expression}`));
513
- console.log();
514
- // Pretty print the tree
515
- function printTree(obj, indent = '', isLast = true, keyName = '') {
516
- const prefix = indent + (isLast ? '└─ ' : '├─ ');
517
- const childIndent = indent + (isLast ? ' ' : '│ ');
518
- if (!obj || typeof obj !== 'object') {
519
- console.log(`${prefix}${keyName ? chalk.cyan(keyName) + ': ' : ''}${chalk.yellow(String(obj))}`);
520
- return;
521
- }
522
- if (obj._error) {
523
- console.log(`${prefix}${chalk.cyan(keyName)}: ${chalk.red(obj._error)}`);
524
- return;
525
- }
526
- if (obj._truncated && !obj._properties && !obj._items) {
527
- console.log(`${prefix}${keyName ? chalk.cyan(keyName) + ': ' : ''}${chalk.dim(`[${obj._type}...]`)}`);
528
- return;
529
- }
530
- if (obj._type && obj._value !== undefined) {
531
- const typeColor = obj._type === 'string' ? chalk.green :
532
- obj._type === 'number' ? chalk.yellow :
533
- obj._type === 'boolean' ? chalk.magenta : chalk.white;
534
- const displayValue = obj._type === 'string' ? `"${obj._value}"` : String(obj._value);
535
- console.log(`${prefix}${keyName ? chalk.cyan(keyName) + ': ' : ''}${typeColor(displayValue)} ${chalk.dim(`(${obj._type})`)}`);
536
- return;
537
- }
538
- if (obj._type === 'function') {
539
- console.log(`${prefix}${keyName ? chalk.cyan(keyName) + ': ' : ''}${chalk.magenta(`ƒ ${obj._name}(${obj._length} args)`)}`);
540
- return;
541
- }
542
- if (obj._type === 'array') {
543
- console.log(`${prefix}${keyName ? chalk.cyan(keyName) + ': ' : ''}${chalk.dim(`Array(${obj._length})`)}${obj._truncated ? chalk.dim(' [truncated]') : ''}`);
544
- const items = obj._items || [];
545
- items.forEach((item, i) => {
546
- printTree(item, childIndent, i === items.length - 1, String(item.key));
547
- });
548
- return;
549
- }
550
- if (obj._type === 'Error') {
551
- console.log(`${prefix}${keyName ? chalk.cyan(keyName) + ': ' : ''}${chalk.red(`${obj._name}: ${obj._message}`)}`);
552
- return;
553
- }
554
- // Object with properties
555
- const typeName = obj._type || 'Object';
556
- console.log(`${prefix}${keyName ? chalk.cyan(keyName) + ': ' : ''}${chalk.dim(typeName)}${obj._truncated ? chalk.dim(' [truncated]') : ''}`);
557
- if (obj._properties) {
558
- const keys = Object.keys(obj._properties);
559
- keys.forEach((key, i) => {
560
- printTree(obj._properties[key], childIndent, i === keys.length - 1, key);
561
- });
562
- }
563
- }
564
- printTree(result);
565
- console.log();
566
- }
567
- catch (error) {
568
- spinner.stop();
569
- if (trace)
570
- await trace.disconnect().catch(() => { });
571
- log.error(`Failed: ${error}`);
572
- process.exit(1);
573
- }
574
- });
575
- // ============================================
576
- // LOGS COMMAND - All Console Logs
577
- // ============================================
578
- program
579
- .command('logs <url>')
580
- .alias('l')
581
- .description('Get all console logs from a URL')
582
- .option('--connect <port>', 'Connect to existing Chrome instance on port (e.g. 9222)')
583
- .option('--filter <type>', 'Filter by type: log, info, warn, error, debug')
584
- .option('--limit <n>', 'Limit number of logs shown', '50')
585
- .option('--json', 'Output as JSON')
586
- .action(async (url, options) => {
587
- const spinner = ora('Connecting...').start();
588
- let trace = null;
589
- try {
590
- trace = new Trace({
591
- headless: true,
592
- connectPort: options.connect ? parseInt(options.connect) : undefined
593
- });
594
- spinner.text = `Connecting to ${url}`;
595
- await trace.connect(url);
596
- spinner.text = 'Collecting logs...';
597
- const summary = await trace.getConsoleSummary();
598
- await trace.disconnect();
599
- spinner.stop();
600
- let logs = summary?.logs || [];
601
- // Apply filter
602
- if (options.filter) {
603
- logs = logs.filter((l) => l.type === options.filter);
604
- }
605
- // Apply limit
606
- const limit = parseInt(options.limit) || 50;
607
- logs = logs.slice(-limit);
608
- if (options.json) {
609
- console.log(JSON.stringify({ total: logs.length, logs }, null, 2));
610
- return;
611
- }
612
- console.log();
613
- console.log(chalk.bold(`📋 Console Logs (${logs.length})`));
614
- console.log(chalk.gray('─'.repeat(60)));
615
- if (logs.length === 0) {
616
- console.log(chalk.dim('No logs captured.'));
617
- }
618
- else {
619
- for (const log of logs) {
620
- const typeColors = {
621
- error: chalk.red,
622
- warn: chalk.yellow,
623
- log: chalk.white,
624
- info: chalk.blue,
625
- debug: chalk.gray
626
- };
627
- const color = typeColors[log.type] || chalk.white;
628
- const icon = log.type === 'error' ? '●' : log.type === 'warn' ? '▲' : '○';
629
- console.log();
630
- console.log(` ${color(icon)} ${color.bold(log.type.toUpperCase())}`);
631
- console.log(` ${log.message.substring(0, 200)}`);
632
- if (log.location) {
633
- console.log(` ${chalk.dim(log.location)}`);
634
- }
635
- }
636
- }
637
- console.log();
638
- }
639
- catch (error) {
640
- spinner.stop();
641
- if (trace)
642
- await trace.disconnect().catch(() => { });
643
- log.error(`Failed: ${error}`);
644
- process.exit(1);
645
- }
646
- });
647
- // ============================================
648
- // STREAM COMMAND - Real-time Console Streaming
649
- // ============================================
650
- program
651
- .command('stream <url>')
652
- .alias('s')
653
- .description('Stream console logs in real-time (like tail -f)')
654
- .option('--connect <port>', 'Connect to existing Chrome instance on port (e.g. 9222)')
655
- .option('--filter <type>', 'Filter by type: log, info, warn, error, debug')
656
- .action(async (url, options) => {
657
- let trace = null;
658
- try {
659
- console.log();
660
- console.log(chalk.bold.cyan('🔴 Live Console Stream'));
661
- console.log(chalk.gray('─'.repeat(60)));
662
- console.log(chalk.dim(`Connecting to ${url}...`));
663
- console.log(chalk.dim('Press Ctrl+C to stop'));
664
- console.log();
665
- trace = new Trace({
666
- headless: true,
667
- connectPort: options.connect ? parseInt(options.connect) : undefined,
668
- verbose: false
669
- });
670
- await trace.connect(url);
671
- console.log(chalk.green('✓ Connected. Streaming logs...'));
672
- console.log();
673
- // Get initial logs
674
- const summary = await trace.getConsoleSummary();
675
- let seenIds = new Set((summary?.logs || []).map((l) => l.id));
676
- // Poll for new logs
677
- const pollInterval = setInterval(async () => {
678
- try {
679
- const current = await trace.getConsoleSummary();
680
- const logs = current?.logs || [];
681
- for (const log of logs) {
682
- if (!seenIds.has(log.id)) {
683
- seenIds.add(log.id);
684
- // Apply filter
685
- if (options.filter && log.type !== options.filter) {
686
- continue;
687
- }
688
- const time = new Date(log.timestamp).toLocaleTimeString();
689
- const typeColors = {
690
- error: chalk.red,
691
- warn: chalk.yellow,
692
- log: chalk.white,
693
- info: chalk.blue,
694
- debug: chalk.gray
695
- };
696
- const color = typeColors[log.type] || chalk.white;
697
- console.log(`${chalk.dim(`[${time}]`)} ${color(log.type.toUpperCase().padEnd(5))} ${log.message.substring(0, 150)}`);
698
- }
699
- }
700
- }
701
- catch (e) {
702
- // Ignore polling errors
703
- }
704
- }, 500);
705
- // Handle Ctrl+C
706
- process.on('SIGINT', async () => {
707
- clearInterval(pollInterval);
708
- console.log();
709
- console.log(chalk.dim('Disconnecting...'));
710
- if (trace)
711
- await trace.disconnect().catch(() => { });
712
- process.exit(0);
713
- });
714
- // Keep alive
715
- await new Promise(() => { });
716
- }
717
- catch (error) {
718
- if (trace)
719
- await trace.disconnect().catch(() => { });
720
- log.error(`Failed: ${error}`);
721
- process.exit(1);
722
- }
723
- });
724
- // ============================================
725
- // NETWORK COMMAND
726
- // ============================================
727
- program
728
- .command('network <url>')
729
- .alias('n')
730
- .description('Analyze network requests from a URL')
731
- .option('--failed', 'Show only failed requests')
732
- .option('--json', 'Output as JSON')
733
- .action(async (url, options) => {
734
- const spinner = ora('Connecting...').start();
735
- try {
736
- const trace = new Trace({ headless: true });
737
- spinner.text = `Connecting to ${url}`;
738
- await trace.connect(url);
739
- spinner.text = 'Capturing network...';
740
- // Wait a bit for network activity
741
- await new Promise(r => setTimeout(r, 2000));
742
- const summary = await trace.getNetworkSummary();
743
- const failed = await trace.getNetworkFailed();
744
- await trace.disconnect();
745
- spinner.stop();
746
- if (options.json) {
747
- console.log(JSON.stringify({ summary, failed }, null, 2));
748
- return;
749
- }
750
- console.log();
751
- console.log(chalk.bold('🌐 Network Summary'));
752
- console.log(chalk.gray('─'.repeat(60)));
753
- console.log();
754
- log.table({
755
- 'Total Requests': summary.total || 0,
756
- 'Failed': summary.failed || 0,
757
- 'Cached': summary.cached || 0,
758
- 'Total Size': `${Math.round((summary.totalSize || 0) / 1024)} KB`,
759
- 'Avg Duration': `${Math.round(summary.avgDuration || 0)} ms`,
760
- });
761
- console.log();
762
- if (failed.length > 0 || options.failed) {
763
- console.log(chalk.bold(`❌ Failed Requests (${failed.length})`));
764
- console.log();
765
- for (const req of failed.slice(0, 10)) {
766
- console.log(` ${chalk.red('●')} ${chalk.bold(req.status || 'ERR')} ${req.method || 'GET'}`);
767
- console.log(` ${req.url?.substring(0, 80)}`);
768
- if (req.errorText) {
769
- console.log(` ${chalk.dim(req.errorText)}`);
770
- }
771
- console.log();
772
- }
773
- }
774
- }
775
- catch (error) {
776
- spinner.stop();
777
- log.error(`Failed: ${error}`);
778
- process.exit(1);
779
- }
780
- });
781
- // ============================================
782
- // COOKIES COMMAND
783
- // ============================================
784
- program
785
- .command('cookies <url>')
786
- .description('View and manage browser cookies')
787
- .option('--connect <port>', 'Connect to existing Chrome instance on port (e.g. 9222)')
788
- .option('--clear', 'Clear all cookies')
789
- .option('--delete <name>', 'Delete a specific cookie by name')
790
- .option('--json', 'Output as JSON')
791
- .action(async (url, options) => {
792
- const spinner = ora('Connecting...').start();
793
- let trace = null;
794
- try {
795
- trace = new Trace({
796
- headless: true,
797
- connectPort: options.connect ? parseInt(options.connect) : undefined
798
- });
799
- spinner.text = `Connecting to ${url}`;
800
- await trace.connect(url);
801
- if (options.clear) {
802
- spinner.text = 'Clearing cookies...';
803
- await trace.executeTool('clear_all_cookies', {});
804
- await trace.disconnect();
805
- spinner.stop();
806
- console.log(chalk.green('✓ All cookies cleared'));
807
- return;
808
- }
809
- if (options.delete) {
810
- spinner.text = `Deleting cookie: ${options.delete}`;
811
- await trace.executeTool('delete_cookies', { name: options.delete });
812
- await trace.disconnect();
813
- spinner.stop();
814
- console.log(chalk.green(`✓ Deleted cookie: ${options.delete}`));
815
- return;
816
- }
817
- spinner.text = 'Fetching cookies...';
818
- const result = await trace.executeTool('get_cookies', {});
819
- await trace.disconnect();
820
- spinner.stop();
821
- if (options.json) {
822
- console.log(JSON.stringify(result.data, null, 2));
823
- return;
824
- }
825
- const cookies = result.data?.cookies || [];
826
- console.log();
827
- console.log(chalk.bold(`🍪 Cookies (${cookies.length})`));
828
- console.log(chalk.gray('─'.repeat(60)));
829
- if (cookies.length === 0) {
830
- console.log(chalk.dim('No cookies found.'));
831
- }
832
- else {
833
- for (const cookie of cookies) {
834
- console.log();
835
- console.log(` ${chalk.cyan(cookie.name)}`);
836
- console.log(` Value: ${chalk.dim(cookie.value)}`);
837
- console.log(` Domain: ${cookie.domain}${cookie.path}`);
838
- console.log(` Expires: ${cookie.expires}`);
839
- console.log(` ${cookie.httpOnly ? chalk.yellow('HttpOnly') : ''} ${cookie.secure ? chalk.green('Secure') : ''} ${chalk.dim(cookie.sameSite)}`);
840
- }
841
- }
842
- console.log();
843
- }
844
- catch (error) {
845
- spinner.stop();
846
- if (trace)
847
- await trace.disconnect().catch(() => { });
848
- log.error(`Failed: ${error}`);
849
- process.exit(1);
850
- }
851
- });
852
- // ============================================
853
- // STORAGE COMMAND
854
- // ============================================
855
- program
856
- .command('storage <url>')
857
- .description('View localStorage and sessionStorage')
858
- .option('--connect <port>', 'Connect to existing Chrome instance on port (e.g. 9222)')
859
- .option('--session', 'View sessionStorage instead of localStorage')
860
- .option('--clear', 'Clear storage')
861
- .option('--json', 'Output as JSON')
862
- .action(async (url, options) => {
863
- const spinner = ora('Connecting...').start();
864
- let trace = null;
865
- try {
866
- trace = new Trace({
867
- headless: true,
868
- connectPort: options.connect ? parseInt(options.connect) : undefined
869
- });
870
- spinner.text = `Connecting to ${url}`;
871
- await trace.connect(url);
872
- const storageType = options.session ? 'Session' : 'Local';
873
- const toolName = options.session ? 'get_session_storage' : 'get_local_storage';
874
- if (options.clear) {
875
- spinner.text = `Clearing ${storageType}Storage...`;
876
- await trace.executeTool('clear_storage', { session: !!options.session });
877
- await trace.disconnect();
878
- spinner.stop();
879
- console.log(chalk.green(`✓ ${storageType}Storage cleared`));
880
- return;
881
- }
882
- spinner.text = `Fetching ${storageType}Storage...`;
883
- const result = await trace.executeTool(toolName, {});
884
- await trace.disconnect();
885
- spinner.stop();
886
- if (options.json) {
887
- console.log(JSON.stringify(result.data, null, 2));
888
- return;
889
- }
890
- const items = result.data?.items || [];
891
- console.log();
892
- console.log(chalk.bold(`💾 ${storageType}Storage (${items.length} items)`));
893
- console.log(chalk.gray('─'.repeat(60)));
894
- if (items.length === 0) {
895
- console.log(chalk.dim('No items found.'));
896
- }
897
- else {
898
- for (const item of items) {
899
- console.log();
900
- console.log(` ${chalk.cyan(item.key)} ${chalk.dim(`(${item.size} bytes)`)}`);
901
- console.log(` ${chalk.dim(item.value)}`);
902
- }
903
- }
904
- console.log();
905
- }
906
- catch (error) {
907
- spinner.stop();
908
- if (trace)
909
- await trace.disconnect().catch(() => { });
910
- log.error(`Failed: ${error}`);
911
- process.exit(1);
912
- }
913
- });
914
- // ============================================
915
- // SERVICE WORKERS COMMAND
916
- // ============================================
917
- program
918
- .command('sw <url>')
919
- .description('View and manage service workers')
920
- .option('--connect <port>', 'Connect to existing Chrome instance on port (e.g. 9222)')
921
- .option('--unregister', 'Unregister all service workers')
252
+ .description('Get console errors from a URL')
922
253
  .option('--json', 'Output as JSON')
923
254
  .action(async (url, options) => {
924
255
  const spinner = ora('Connecting...').start();
925
- let trace = null;
926
256
  try {
927
- trace = new Trace({
928
- headless: true,
929
- connectPort: options.connect ? parseInt(options.connect) : undefined
930
- });
257
+ const trace = new Trace({ headless: true });
931
258
  spinner.text = `Connecting to ${url}`;
932
259
  await trace.connect(url);
933
- if (options.unregister) {
934
- spinner.text = 'Unregistering service workers...';
935
- await trace.executeTool('unregister_service_worker', {});
936
- await trace.disconnect();
937
- spinner.stop();
938
- console.log(chalk.green('✓ All service workers unregistered'));
939
- return;
940
- }
941
- spinner.text = 'Fetching service workers...';
942
- const result = await trace.executeTool('get_service_workers', {});
260
+ spinner.text = 'Checking for errors...';
261
+ const errors = await trace.getConsoleErrors();
262
+ const groups = await trace.getErrorGroups();
943
263
  await trace.disconnect();
944
264
  spinner.stop();
945
265
  if (options.json) {
946
- console.log(JSON.stringify(result.data, null, 2));
266
+ console.log(JSON.stringify({ errors, groups }, null, 2));
947
267
  return;
948
268
  }
949
- const workers = result.data?.workers || [];
950
269
  console.log();
951
- console.log(chalk.bold(`⚙️ Service Workers (${workers.length})`));
270
+ console.log(chalk.bold(`🔴 Console Errors (${errors.length})`));
952
271
  console.log(chalk.gray('─'.repeat(60)));
953
- if (workers.length === 0) {
954
- console.log(chalk.dim('No service workers registered.'));
272
+ if (errors.length === 0) {
273
+ log.success('No console errors found!');
955
274
  }
956
275
  else {
957
- for (const sw of workers) {
276
+ for (const err of errors.slice(0, 20)) {
958
277
  console.log();
959
- console.log(` ${chalk.cyan('Scope:')} ${sw.scope}`);
960
- if (sw.active) {
961
- console.log(` ${chalk.green('Active:')} ${sw.active.scriptURL} (${sw.active.state})`);
962
- }
963
- if (sw.waiting) {
964
- console.log(` ${chalk.yellow('Waiting:')} ${sw.waiting.scriptURL}`);
965
- }
966
- if (sw.installing) {
967
- console.log(` ${chalk.blue('Installing:')} ${sw.installing.scriptURL}`);
278
+ console.log(` ${chalk.red('')} ${chalk.bold(err.errorType || 'Error')}`);
279
+ console.log(` ${err.message.substring(0, 200)}`);
280
+ if (err.location) {
281
+ console.log(` ${chalk.dim(err.location)}`);
968
282
  }
969
283
  }
970
284
  }
@@ -972,123 +286,63 @@ program
972
286
  }
973
287
  catch (error) {
974
288
  spinner.stop();
975
- if (trace)
976
- await trace.disconnect().catch(() => { });
977
289
  log.error(`Failed: ${error}`);
978
290
  process.exit(1);
979
291
  }
980
292
  });
981
293
  // ============================================
982
- // SECURITY COMMAND
294
+ // NETWORK COMMAND
983
295
  // ============================================
984
296
  program
985
- .command('security <url>')
986
- .description('Check page security status')
987
- .option('--connect <port>', 'Connect to existing Chrome instance on port (e.g. 9222)')
297
+ .command('network <url>')
298
+ .alias('n')
299
+ .description('Analyze network requests from a URL')
300
+ .option('--failed', 'Show only failed requests')
988
301
  .option('--json', 'Output as JSON')
989
302
  .action(async (url, options) => {
990
303
  const spinner = ora('Connecting...').start();
991
- let trace = null;
992
304
  try {
993
- trace = new Trace({
994
- headless: true,
995
- connectPort: options.connect ? parseInt(options.connect) : undefined
996
- });
305
+ const trace = new Trace({ headless: true });
997
306
  spinner.text = `Connecting to ${url}`;
998
307
  await trace.connect(url);
999
- spinner.text = 'Checking security...';
1000
- const securityInfo = await trace.executeTool('get_security_info', {});
1001
- const mixedContent = await trace.executeTool('check_mixed_content', {});
308
+ spinner.text = 'Capturing network...';
309
+ // Wait a bit for network activity
310
+ await new Promise(r => setTimeout(r, 2000));
311
+ const summary = await trace.getNetworkSummary();
312
+ const failed = await trace.getNetworkFailed();
1002
313
  await trace.disconnect();
1003
314
  spinner.stop();
1004
315
  if (options.json) {
1005
- console.log(JSON.stringify({ security: securityInfo.data, mixedContent: mixedContent.data }, null, 2));
316
+ console.log(JSON.stringify({ summary, failed }, null, 2));
1006
317
  return;
1007
318
  }
1008
- const info = securityInfo.data || {};
1009
- const mixed = mixedContent.data || {};
1010
319
  console.log();
1011
- console.log(chalk.bold('🔒 Security Status'));
320
+ console.log(chalk.bold('🌐 Network Summary'));
1012
321
  console.log(chalk.gray('─'.repeat(60)));
1013
322
  console.log();
1014
- const secureIcon = info.secure ? chalk.green('✓') : chalk.red('✗');
1015
- console.log(` ${secureIcon} ${info.secure ? 'Connection is secure' : 'Connection is NOT secure'}`);
1016
- console.log(` Protocol: ${info.protocol}`);
1017
- console.log(` URL: ${info.url}`);
1018
- console.log();
1019
- if (mixed.hasMixedContent) {
1020
- console.log(chalk.yellow(` ⚠️ Mixed Content Issues (${mixed.count})`));
1021
- for (const issue of (mixed.issues || []).slice(0, 5)) {
1022
- console.log(` - ${issue.tag}: ${issue.src}`);
1023
- }
1024
- }
1025
- else {
1026
- console.log(chalk.green(' ✓ No mixed content issues'));
1027
- }
1028
- console.log();
1029
- }
1030
- catch (error) {
1031
- spinner.stop();
1032
- if (trace)
1033
- await trace.disconnect().catch(() => { });
1034
- log.error(`Failed: ${error}`);
1035
- process.exit(1);
1036
- }
1037
- });
1038
- // ============================================
1039
- // PROFILE COMMAND - CPU Profiler
1040
- // ============================================
1041
- program
1042
- .command('profile <url>')
1043
- .description('CPU profile the page (like Performance tab)')
1044
- .option('--connect <port>', 'Connect to existing Chrome instance on port (e.g. 9222)')
1045
- .option('--duration <ms>', 'Profile duration in milliseconds', '3000')
1046
- .option('--json', 'Output as JSON')
1047
- .action(async (url, options) => {
1048
- const spinner = ora('Connecting...').start();
1049
- let trace = null;
1050
- try {
1051
- trace = new Trace({
1052
- headless: true,
1053
- connectPort: options.connect ? parseInt(options.connect) : undefined
323
+ log.table({
324
+ 'Total Requests': summary.total || 0,
325
+ 'Failed': summary.failed || 0,
326
+ 'Cached': summary.cached || 0,
327
+ 'Total Size': `${Math.round((summary.totalSize || 0) / 1024)} KB`,
328
+ 'Avg Duration': `${Math.round(summary.avgDuration || 0)} ms`,
1054
329
  });
1055
- spinner.text = `Connecting to ${url}`;
1056
- await trace.connect(url);
1057
- const duration = parseInt(options.duration) || 3000;
1058
- spinner.text = `Profiling for ${duration}ms...`;
1059
- const result = await trace.executeTool('get_profile_snapshot', { duration_ms: duration });
1060
- await trace.disconnect();
1061
- spinner.stop();
1062
- if (options.json) {
1063
- console.log(JSON.stringify(result.data, null, 2));
1064
- return;
1065
- }
1066
- const profile = result.data || {};
1067
- console.log();
1068
- console.log(chalk.bold(`🔥 CPU Profile (${profile.totalTime})`));
1069
- console.log(chalk.gray('─'.repeat(60)));
1070
- console.log(`Samples: ${profile.sampleCount}`);
1071
330
  console.log();
1072
- const hotPaths = profile.hotPaths || [];
1073
- if (hotPaths.length === 0) {
1074
- console.log(chalk.dim('No significant CPU activity captured.'));
1075
- }
1076
- else {
1077
- console.log(chalk.bold('Hot Paths:'));
331
+ if (failed.length > 0 || options.failed) {
332
+ console.log(chalk.bold(`❌ Failed Requests (${failed.length})`));
1078
333
  console.log();
1079
- for (const path of hotPaths.slice(0, 15)) {
1080
- const barLength = Math.round(parseFloat(path.percentage) / 3);
1081
- const bar = '█'.repeat(barLength) + '░'.repeat(Math.max(0, 20 - barLength));
1082
- console.log(` ${chalk.yellow(bar)} ${path.percentage.padStart(5)}% ${chalk.cyan(path.function)}`);
1083
- console.log(` ${' '.repeat(20)} ${chalk.dim(path.file)}:${path.line || '?'}`);
334
+ for (const req of failed.slice(0, 10)) {
335
+ console.log(` ${chalk.red('●')} ${chalk.bold(req.status || 'ERR')} ${req.method || 'GET'}`);
336
+ console.log(` ${req.url?.substring(0, 80)}`);
337
+ if (req.errorText) {
338
+ console.log(` ${chalk.dim(req.errorText)}`);
339
+ }
340
+ console.log();
1084
341
  }
1085
342
  }
1086
- console.log();
1087
343
  }
1088
344
  catch (error) {
1089
345
  spinner.stop();
1090
- if (trace)
1091
- await trace.disconnect().catch(() => { });
1092
346
  log.error(`Failed: ${error}`);
1093
347
  process.exit(1);
1094
348
  }
@@ -1653,525 +907,181 @@ program
1653
907
  }
1654
908
  });
1655
909
  // ============================================
1656
- // FILES COMMAND - Direct code access
1657
- // ============================================
1658
- program
1659
- .command('files')
1660
- .alias('f')
1661
- .description('Show project file tree (no browser needed)')
1662
- .option('-d, --depth <n>', 'Directory depth', '3')
1663
- .option('-p, --path <dir>', 'Project directory', process.cwd())
1664
- .option('--json', 'Output as JSON')
1665
- .action(async (options) => {
1666
- const projectPath = path.resolve(options.path);
1667
- const depth = parseInt(options.depth, 10);
1668
- console.log();
1669
- console.log(chalk.bold('📁 Project Files'));
1670
- console.log(chalk.gray('─'.repeat(60)));
1671
- console.log(chalk.dim(`Path: ${projectPath}`));
1672
- console.log();
1673
- try {
1674
- const tree = getFileTree(projectPath, depth);
1675
- if (options.json) {
1676
- console.log(JSON.stringify(tree, null, 2));
1677
- return;
1678
- }
1679
- printTree(tree, '');
1680
- console.log();
1681
- }
1682
- catch (error) {
1683
- log.error(`Failed to read directory: ${error}`);
1684
- process.exit(1);
1685
- }
1686
- });
1687
- program
1688
- .command('read <file>')
1689
- .description('Read a file from the project')
1690
- .option('-p, --path <dir>', 'Project directory', process.cwd())
1691
- .option('-l, --lines <range>', 'Line range (e.g., 1-50)')
1692
- .action(async (file, options) => {
1693
- const projectPath = path.resolve(options.path);
1694
- const filePath = path.resolve(projectPath, file);
1695
- console.log();
1696
- console.log(chalk.bold(`📄 ${file}`));
1697
- console.log(chalk.gray('─'.repeat(60)));
1698
- try {
1699
- if (!fs.existsSync(filePath)) {
1700
- log.error(`File not found: ${file}`);
1701
- process.exit(1);
1702
- }
1703
- const content = fs.readFileSync(filePath, 'utf-8');
1704
- const lines = content.split('\n');
1705
- let startLine = 1;
1706
- let endLine = lines.length;
1707
- if (options.lines) {
1708
- const [s, e] = options.lines.split('-').map(Number);
1709
- startLine = s || 1;
1710
- endLine = e || lines.length;
1711
- }
1712
- for (let i = startLine - 1; i < Math.min(endLine, lines.length); i++) {
1713
- const lineNum = chalk.dim(`${String(i + 1).padStart(4)} │`);
1714
- console.log(`${lineNum} ${lines[i]}`);
1715
- }
1716
- console.log();
1717
- }
1718
- catch (error) {
1719
- log.error(`Failed to read file: ${error}`);
1720
- process.exit(1);
1721
- }
1722
- });
1723
- program
1724
- .command('search <query>')
1725
- .description('Search for text in the codebase')
1726
- .option('-p, --path <dir>', 'Project directory', process.cwd())
1727
- .option('-n, --max <n>', 'Max results', '20')
1728
- .action(async (query, options) => {
1729
- const projectPath = path.resolve(options.path);
1730
- const maxResults = parseInt(options.max, 10);
1731
- console.log();
1732
- console.log(chalk.bold(`🔍 Searching: "${query}"`));
1733
- console.log(chalk.gray('─'.repeat(60)));
1734
- try {
1735
- const results = searchInFiles(projectPath, query, maxResults);
1736
- if (results.length === 0) {
1737
- console.log(chalk.dim(' No matches found'));
1738
- }
1739
- else {
1740
- for (const match of results) {
1741
- console.log();
1742
- console.log(` ${chalk.cyan(match.file)}:${chalk.yellow(match.line)}`);
1743
- console.log(` ${match.content.trim()}`);
1744
- }
1745
- }
1746
- console.log();
1747
- console.log(chalk.dim(`Found ${results.length} matches`));
1748
- console.log();
1749
- }
1750
- catch (error) {
1751
- log.error(`Search failed: ${error}`);
1752
- process.exit(1);
1753
- }
1754
- });
1755
- function printTree(tree, prefix) {
1756
- const entries = Object.entries(tree);
1757
- for (let i = 0; i < entries.length; i++) {
1758
- const [name, value] = entries[i];
1759
- const isLast = i === entries.length - 1;
1760
- const connector = isLast ? '└── ' : '├── ';
1761
- const isDir = typeof value === 'object' && value !== null;
1762
- const icon = isDir ? '📁' : '📄';
1763
- console.log(`${prefix}${connector}${icon} ${name}`);
1764
- if (isDir) {
1765
- const newPrefix = prefix + (isLast ? ' ' : '│ ');
1766
- printTree(value, newPrefix);
1767
- }
1768
- }
1769
- }
1770
- // ============================================
1771
- // CONNECT COMMAND - IDE Bridge for Extension
910
+ // IDE BRIDGE - WebSocket Server
1772
911
  // ============================================
1773
- import { WebSocketServer } from 'ws';
1774
- import * as fs from 'fs';
1775
- import * as path from 'path';
1776
912
  program
1777
913
  .command('connect')
1778
- .alias('c')
1779
- .description('Start IDE bridge for Chrome extension (enables source code access)')
1780
- .option('-p, --port <port>', 'WebSocket port', '8765')
914
+ .alias('ide')
915
+ .description('Start IDE Bridge WebSocket server for extension <-> VS Code connection')
916
+ .option('-p, --port <number>', 'WebSocket server port', '8765')
1781
917
  .action(async (options) => {
918
+ const { WebSocketServer } = await import('ws');
919
+ const fs = await import('fs/promises');
920
+ const path = await import('path');
921
+ const { glob } = await import('glob');
1782
922
  const port = parseInt(options.port);
1783
- const projectPath = process.cwd();
1784
- console.log();
1785
- console.log(chalk.bold.cyan('🔗 Trace IDE Bridge'));
1786
- console.log(chalk.gray('─'.repeat(55)));
1787
- console.log();
1788
- console.log(`📁 Project: ${chalk.green(projectPath)}`);
1789
- console.log(`🌐 Port: ${chalk.cyan(port)}`);
1790
- console.log();
1791
- // Read package.json for project info
1792
- let projectInfo = { projectPath };
1793
- try {
1794
- const pkgPath = path.join(projectPath, 'package.json');
1795
- if (fs.existsSync(pkgPath)) {
1796
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
1797
- projectInfo = {
1798
- ...projectInfo,
1799
- name: pkg.name,
1800
- version: pkg.version,
1801
- description: pkg.description
1802
- };
1803
- console.log(`📦 Package: ${chalk.yellow(pkg.name)} v${pkg.version}`);
1804
- }
1805
- }
1806
- catch (e) {
1807
- // No package.json, that's fine
1808
- }
1809
- // Start WebSocket server
1810
923
  const wss = new WebSocketServer({ port });
1811
- let clientCount = 0;
1812
924
  console.log();
1813
- console.log(chalk.green('✓') + ' WebSocket server started');
1814
- console.log(chalk.dim('Waiting for extension to connect...'));
925
+ console.log(chalk.bold.cyan('🔌 IDE Bridge Server'));
926
+ console.log(chalk.gray(''.repeat(60)));
927
+ console.log(` ${chalk.green('✓')} WebSocket server running on ${chalk.cyan(`ws://localhost:${port}`)}`);
928
+ console.log(` ${chalk.dim('→')} Project path: ${chalk.white(process.cwd())}`);
1815
929
  console.log();
1816
- console.log(chalk.gray(''.repeat(55)));
930
+ console.log(chalk.dim('Waiting for extension to connect...'));
1817
931
  console.log(chalk.dim('Press Ctrl+C to stop'));
1818
932
  console.log();
1819
933
  wss.on('connection', (ws) => {
1820
- clientCount++;
1821
- console.log(chalk.green('●') + ` Extension connected (${clientCount} client${clientCount > 1 ? 's' : ''})`);
934
+ console.log(chalk.green('✓') + ' Extension connected');
1822
935
  ws.on('message', async (data) => {
1823
936
  try {
1824
937
  const message = JSON.parse(data.toString());
1825
938
  const { id, type } = message;
1826
- let response = { id };
939
+ let response = {};
1827
940
  switch (type) {
1828
941
  case 'GET_PROJECT_INFO':
1829
- response.data = projectInfo;
1830
- break;
1831
- case 'READ_FILE':
1832
- try {
1833
- const filePath = path.resolve(projectPath, message.filePath);
1834
- // Security: ensure file is within project
1835
- if (!filePath.startsWith(projectPath)) {
1836
- response.error = 'Access denied';
1837
- }
1838
- else if (fs.existsSync(filePath)) {
1839
- response.data = {
1840
- content: fs.readFileSync(filePath, 'utf-8'),
1841
- exists: true,
1842
- path: filePath
1843
- };
1844
- }
1845
- else {
1846
- response.data = { exists: false };
1847
- }
1848
- }
1849
- catch (e) {
1850
- response.error = e.message;
1851
- }
942
+ response = {
943
+ projectPath: process.cwd(),
944
+ projectName: path.basename(process.cwd()),
945
+ };
1852
946
  break;
1853
947
  case 'GET_SOURCE':
1854
948
  try {
1855
- const filePath = path.resolve(projectPath, message.filePath);
1856
- if (!filePath.startsWith(projectPath)) {
1857
- response.error = 'Access denied';
1858
- }
1859
- else if (fs.existsSync(filePath)) {
1860
- const content = fs.readFileSync(filePath, 'utf-8');
1861
- const lines = content.split('\n');
1862
- const start = Math.max(0, (message.lineStart || 1) - 1);
1863
- const end = message.lineEnd ? Math.min(lines.length, message.lineEnd) : lines.length;
1864
- response.data = {
1865
- lines: lines.slice(start, end),
1866
- startLine: start + 1,
1867
- endLine: end,
1868
- totalLines: lines.length
1869
- };
1870
- }
1871
- else {
1872
- response.error = 'File not found';
1873
- }
949
+ const { filePath, lineStart, lineEnd } = message;
950
+ const fullPath = path.resolve(process.cwd(), filePath);
951
+ const content = await fs.readFile(fullPath, 'utf-8');
952
+ const lines = content.split('\n');
953
+ const start = Math.max(0, (lineStart || 1) - 1);
954
+ const end = lineEnd ? Math.min(lines.length, lineEnd) : lines.length;
955
+ response = {
956
+ filePath,
957
+ content: lines.slice(start, end).join('\n'),
958
+ lineStart: start + 1,
959
+ lineEnd: end,
960
+ };
1874
961
  }
1875
- catch (e) {
1876
- response.error = e.message;
962
+ catch (error) {
963
+ response = { error: error.message };
1877
964
  }
1878
965
  break;
1879
- case 'GET_ERROR_CONTEXT':
966
+ case 'READ_FILE':
1880
967
  try {
1881
- const filePath = path.resolve(projectPath, message.filePath);
1882
- if (!filePath.startsWith(projectPath)) {
1883
- response.error = 'Access denied';
1884
- }
1885
- else if (fs.existsSync(filePath)) {
1886
- const content = fs.readFileSync(filePath, 'utf-8');
1887
- const lines = content.split('\n');
1888
- const targetLine = message.line || 1;
1889
- const contextLines = message.contextLines || 5;
1890
- const start = Math.max(0, targetLine - contextLines - 1);
1891
- const end = Math.min(lines.length, targetLine + contextLines);
1892
- response.data = {
1893
- lines: lines.slice(start, end).map((line, i) => ({
1894
- number: start + i + 1,
1895
- content: line,
1896
- isError: start + i + 1 === targetLine
1897
- })),
1898
- errorLine: targetLine,
1899
- filePath: message.filePath
1900
- };
1901
- }
1902
- else {
1903
- response.error = 'File not found';
1904
- }
968
+ const { filePath } = message;
969
+ const fullPath = path.resolve(process.cwd(), filePath);
970
+ const content = await fs.readFile(fullPath, 'utf-8');
971
+ response = content; // Return content directly
1905
972
  }
1906
- catch (e) {
1907
- response.error = e.message;
973
+ catch (error) {
974
+ response = { error: error.message };
1908
975
  }
1909
976
  break;
1910
- case 'GET_FILE_TREE':
977
+ case 'FIND_FILES':
1911
978
  try {
1912
- const depth = message.depth || 3;
1913
- const tree = getFileTree(projectPath, depth);
1914
- response.data = { tree };
979
+ const { pattern } = message;
980
+ // Use glob to find files matching the pattern
981
+ const files = await glob(pattern, {
982
+ cwd: process.cwd(),
983
+ ignore: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.git/**', '**/__pycache__/**', '**/.next/**'],
984
+ nodir: true, // Only files, not directories
985
+ });
986
+ response = files; // Return array of matching file paths
1915
987
  }
1916
- catch (e) {
1917
- response.error = e.message;
988
+ catch (error) {
989
+ response = { error: error.message };
1918
990
  }
1919
991
  break;
1920
992
  case 'SEARCH_CODE':
1921
993
  try {
1922
- const query = message.query;
1923
- const matches = searchInFiles(projectPath, query, 20);
1924
- response.data = { matches };
1925
- }
1926
- catch (e) {
1927
- response.error = e.message;
1928
- }
1929
- break;
1930
- // ========== DEEP DEBUGGING TOOLS ==========
1931
- case 'GIT_BLAME':
1932
- try {
1933
- const filePath = message.filePath;
1934
- const line = message.line;
1935
- // git blame -L n,n --porcelain file
1936
- const { stdout } = await execAsync(`git blame -L ${line},${line} --porcelain "${filePath}"`, { cwd: projectPath });
1937
- // Parse porcelain output
1938
- const lines = stdout.split('\n');
1939
- const commitHash = lines[0].split(' ')[0];
1940
- const author = lines.find(l => l.startsWith('author '))?.substring(7);
1941
- const email = lines.find(l => l.startsWith('author-mail '))?.substring(12);
1942
- const date = lines.find(l => l.startsWith('author-time '))?.substring(12);
1943
- const summary = lines.find(l => l.startsWith('summary '))?.substring(8);
1944
- response.data = {
1945
- commit: commitHash,
1946
- author,
1947
- email,
1948
- date: new Date(parseInt(date) * 1000).toISOString().split('T')[0],
1949
- message: summary
1950
- };
1951
- }
1952
- catch (e) {
1953
- response.error = `Git blame failed: ${e.message}`;
1954
- }
1955
- break;
1956
- case 'GIT_RECENT_CHANGES':
1957
- try {
1958
- const filePath = message.filePath;
1959
- const days = message.days || 7;
1960
- // git log -n 10 --pretty=format:"%h|%an|%ad|%s" --date=short file
1961
- const { stdout } = await execAsync(`git log -n 10 --since="${days} days ago" --pretty=format:"%h|%an|%ad|%s" --date=short "${filePath}"`, { cwd: projectPath });
1962
- response.data = {
1963
- history: stdout.split('\n').filter(Boolean).map(line => {
1964
- const [hash, author, date, message] = line.split('|');
1965
- return { hash, author, date, message };
1966
- })
1967
- };
1968
- }
1969
- catch (e) {
1970
- response.error = `Git log failed: ${e.message}`;
1971
- }
1972
- break;
1973
- case 'GET_IMPORTS':
1974
- try {
1975
- const filePath = path.resolve(projectPath, message.filePath);
1976
- if (fs.existsSync(filePath)) {
1977
- const content = fs.readFileSync(filePath, 'utf-8');
1978
- // Basic match for imports
1979
- const importRegex = /import\s+(?:[\w*\s{},]*)\s+from\s+['"]([^'"]+)['"]/g;
1980
- const imports = [];
1981
- let match;
1982
- while ((match = importRegex.exec(content)) !== null) {
1983
- imports.push(match[1]);
994
+ const { query, options = {} } = message;
995
+ const pattern = options.filePattern || '**/*.{js,ts,jsx,tsx,vue,css,html,py,go,java,rb,rs}';
996
+ const files = await glob(pattern, {
997
+ cwd: process.cwd(),
998
+ ignore: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.git/**'],
999
+ });
1000
+ const results = [];
1001
+ for (const file of files.slice(0, 100)) { // Limit to 100 files
1002
+ try {
1003
+ const fullPath = path.resolve(process.cwd(), file);
1004
+ const content = await fs.readFile(fullPath, 'utf-8');
1005
+ if (content.toLowerCase().includes(query.toLowerCase())) {
1006
+ const lines = content.split('\n');
1007
+ const matches = lines
1008
+ .map((line, idx) => ({ line, lineNumber: idx + 1 }))
1009
+ .filter(({ line }) => line.toLowerCase().includes(query.toLowerCase()))
1010
+ .slice(0, 5); // Max 5 matches per file
1011
+ results.push({
1012
+ file,
1013
+ matches: matches.map((m) => ({
1014
+ line: m.lineNumber,
1015
+ text: m.line.trim(),
1016
+ })),
1017
+ });
1018
+ }
1984
1019
  }
1985
- response.data = { imports };
1986
- }
1987
- else {
1988
- response.error = 'File not found';
1020
+ catch { }
1989
1021
  }
1022
+ response = { results };
1990
1023
  }
1991
- catch (e) {
1992
- response.error = e.message;
1024
+ catch (error) {
1025
+ response = { error: error.message };
1993
1026
  }
1994
1027
  break;
1995
- case 'FIND_USAGES':
1028
+ case 'GET_FILE_TREE':
1996
1029
  try {
1997
- const query = message.query;
1998
- // git grep -n "query"
1999
- const { stdout } = await execAsync(`git grep -n "${query}"`, { cwd: projectPath });
2000
- response.data = {
2001
- usages: stdout.split('\n').filter(Boolean).slice(0, 20).map(line => {
2002
- const parts = line.split(':');
2003
- return {
2004
- file: parts[0],
2005
- line: parseInt(parts[1]),
2006
- content: parts.slice(2).join(':').trim()
2007
- };
2008
- })
2009
- };
1030
+ const { depth = 3 } = message;
1031
+ const files = await glob('**/*', {
1032
+ cwd: process.cwd(),
1033
+ ignore: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.git/**'],
1034
+ maxDepth: depth,
1035
+ });
1036
+ response = { tree: files };
2010
1037
  }
2011
- catch (e) {
2012
- // grep returns 1 provided no matches found
2013
- if (e.code === 1)
2014
- response.data = { usages: [] };
2015
- else
2016
- response.error = `Grep failed: ${e.message}`;
1038
+ catch (error) {
1039
+ response = { error: error.message };
2017
1040
  }
2018
1041
  break;
2019
- case 'GET_RELATED_FILES':
1042
+ case 'GET_ERROR_CONTEXT':
2020
1043
  try {
2021
- const filePath = message.filePath;
2022
- // Find files often committed together
2023
- // git log --name-only --pretty=format:"" file | sort | uniq -c | sort -nr | head -n 10
2024
- const cmd = `git log --name-only --pretty=format:"" "${filePath}" | sort | uniq -c | sort -nr | head -n 10`;
2025
- const { stdout } = await execAsync(cmd, { cwd: projectPath });
2026
- response.data = {
2027
- related: stdout.split('\n').filter(Boolean).map(line => {
2028
- const parts = line.trim().split(' ');
2029
- return {
2030
- count: parseInt(parts[0]),
2031
- file: parts[1]
2032
- };
2033
- }).filter(r => r.file && r.file !== filePath)
1044
+ const { filePath: file, line, contextLines = 10 } = message;
1045
+ const fullPath = path.resolve(process.cwd(), file);
1046
+ const content = await fs.readFile(fullPath, 'utf-8');
1047
+ const lines = content.split('\n');
1048
+ const start = Math.max(0, line - contextLines);
1049
+ const end = Math.min(lines.length, line + contextLines);
1050
+ response = {
1051
+ file,
1052
+ line,
1053
+ context: lines.slice(start, end).join('\n'),
1054
+ lineStart: start + 1,
2034
1055
  };
2035
1056
  }
2036
- catch (e) {
2037
- response.error = `Analysis failed: ${e.message}`;
2038
- }
2039
- break;
2040
- case 'GET_ENV_VARS':
2041
- try {
2042
- const filePath = path.resolve(projectPath, message.filePath);
2043
- if (fs.existsSync(filePath)) {
2044
- const content = fs.readFileSync(filePath, 'utf-8');
2045
- // Match process.env.VAR or import.meta.env.VAR
2046
- const envRegex = /(?:process\.env\.|import\.meta\.env\.)([A-Z_][A-Z0-9_]*)/g;
2047
- const vars = new Set();
2048
- let match;
2049
- while ((match = envRegex.exec(content)) !== null) {
2050
- vars.add(match[1]);
2051
- }
2052
- response.data = { envVars: Array.from(vars) };
2053
- }
2054
- else {
2055
- response.error = 'File not found';
2056
- }
2057
- }
2058
- catch (e) {
2059
- response.error = e.message;
1057
+ catch (error) {
1058
+ response = { error: error.message };
2060
1059
  }
2061
1060
  break;
2062
1061
  default:
2063
- response.error = `Unknown message type: ${type}`;
1062
+ response = { error: `Unknown command: ${type}` };
2064
1063
  }
2065
- ws.send(JSON.stringify(response));
1064
+ ws.send(JSON.stringify({ id, type, data: response }));
2066
1065
  }
2067
- catch (e) {
2068
- console.error(chalk.red('Parse error:'), e.message);
1066
+ catch (error) {
1067
+ console.error(chalk.red('Error:'), error.message);
1068
+ ws.send(JSON.stringify({
1069
+ id: JSON.parse(data.toString()).id,
1070
+ error: error.message
1071
+ }));
2069
1072
  }
2070
1073
  });
2071
1074
  ws.on('close', () => {
2072
- clientCount--;
2073
- console.log(chalk.yellow('●') + ` Extension disconnected (${clientCount} client${clientCount > 1 ? 's' : ''})`);
2074
- });
2075
- ws.on('error', (error) => {
2076
- console.error(chalk.red('WebSocket error:'), error.message);
1075
+ console.log(chalk.yellow('⚠') + ' Extension disconnected');
2077
1076
  });
2078
1077
  });
2079
- wss.on('error', (error) => {
2080
- if (error.code === 'EADDRINUSE') {
2081
- console.log(chalk.red(`✗ Port ${port} is already in use`));
2082
- console.log(chalk.dim('Try: trace connect --port 8766'));
2083
- }
2084
- else {
2085
- console.error(chalk.red('Server error:'), error.message);
2086
- }
2087
- process.exit(1);
2088
- });
2089
- // Keep alive
2090
1078
  process.on('SIGINT', () => {
2091
1079
  console.log();
2092
- console.log(chalk.dim('Stopping...'));
1080
+ console.log(chalk.dim('Shutting down...'));
2093
1081
  wss.close();
2094
1082
  process.exit(0);
2095
1083
  });
2096
1084
  });
2097
- // Helper: Get file tree
2098
- function getFileTree(dir, depth, currentDepth = 0) {
2099
- if (currentDepth >= depth)
2100
- return null;
2101
- const result = {};
2102
- const ignorePatterns = ['node_modules', '.git', 'dist', 'build', '.next', 'coverage', '.cache'];
2103
- try {
2104
- const items = fs.readdirSync(dir);
2105
- for (const item of items.slice(0, 50)) { // Limit items
2106
- if (ignorePatterns.includes(item) || item.startsWith('.'))
2107
- continue;
2108
- const fullPath = path.join(dir, item);
2109
- try {
2110
- const stat = fs.statSync(fullPath);
2111
- if (stat.isDirectory()) {
2112
- result[item] = getFileTree(fullPath, depth, currentDepth + 1);
2113
- }
2114
- else {
2115
- result[item] = 'file';
2116
- }
2117
- }
2118
- catch (e) {
2119
- // Skip inaccessible files
2120
- }
2121
- }
2122
- }
2123
- catch (e) {
2124
- // Skip inaccessible directories
2125
- }
2126
- return result;
2127
- }
2128
- // Helper: Simple search in files
2129
- function searchInFiles(dir, query, maxResults) {
2130
- const results = [];
2131
- const extensions = ['.js', '.ts', '.jsx', '.tsx', '.vue', '.svelte', '.css', '.html', '.json'];
2132
- const ignorePatterns = ['node_modules', '.git', 'dist', 'build', '.next'];
2133
- function searchDir(currentDir) {
2134
- if (results.length >= maxResults)
2135
- return;
2136
- try {
2137
- const items = fs.readdirSync(currentDir);
2138
- for (const item of items) {
2139
- if (results.length >= maxResults)
2140
- return;
2141
- if (ignorePatterns.includes(item) || item.startsWith('.'))
2142
- continue;
2143
- const fullPath = path.join(currentDir, item);
2144
- try {
2145
- const stat = fs.statSync(fullPath);
2146
- if (stat.isDirectory()) {
2147
- searchDir(fullPath);
2148
- }
2149
- else if (extensions.some(ext => item.endsWith(ext))) {
2150
- const content = fs.readFileSync(fullPath, 'utf-8');
2151
- const lines = content.split('\n');
2152
- for (let i = 0; i < lines.length && results.length < maxResults; i++) {
2153
- if (lines[i].includes(query)) {
2154
- results.push({
2155
- file: path.relative(dir, fullPath),
2156
- line: i + 1,
2157
- content: lines[i].trim().substring(0, 200)
2158
- });
2159
- }
2160
- }
2161
- }
2162
- }
2163
- catch (e) {
2164
- // Skip inaccessible files
2165
- }
2166
- }
2167
- }
2168
- catch (e) {
2169
- // Skip inaccessible directories
2170
- }
2171
- }
2172
- searchDir(dir);
2173
- return results;
2174
- }
2175
1085
  // ============================================
2176
1086
  // RUN CLI
2177
1087
  // ============================================