@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 +158 -1248
- package/dist/index.js.map +1 -1
- package/package.json +3 -5
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 '
|
|
12
|
-
|
|
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('
|
|
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
|
-
|
|
934
|
-
|
|
935
|
-
|
|
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(
|
|
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(
|
|
270
|
+
console.log(chalk.bold(`🔴 Console Errors (${errors.length})`));
|
|
952
271
|
console.log(chalk.gray('─'.repeat(60)));
|
|
953
|
-
if (
|
|
954
|
-
|
|
272
|
+
if (errors.length === 0) {
|
|
273
|
+
log.success('No console errors found!');
|
|
955
274
|
}
|
|
956
275
|
else {
|
|
957
|
-
for (const
|
|
276
|
+
for (const err of errors.slice(0, 20)) {
|
|
958
277
|
console.log();
|
|
959
|
-
console.log(` ${chalk.
|
|
960
|
-
|
|
961
|
-
|
|
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
|
-
//
|
|
294
|
+
// NETWORK COMMAND
|
|
983
295
|
// ============================================
|
|
984
296
|
program
|
|
985
|
-
.command('
|
|
986
|
-
.
|
|
987
|
-
.
|
|
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 = '
|
|
1000
|
-
|
|
1001
|
-
|
|
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({
|
|
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('
|
|
320
|
+
console.log(chalk.bold('🌐 Network Summary'));
|
|
1012
321
|
console.log(chalk.gray('─'.repeat(60)));
|
|
1013
322
|
console.log();
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
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
|
-
|
|
1073
|
-
|
|
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
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
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
|
-
//
|
|
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('
|
|
1779
|
-
.description('Start IDE
|
|
1780
|
-
.option('-p, --port <
|
|
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.
|
|
1814
|
-
console.log(chalk.
|
|
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.
|
|
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
|
-
|
|
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 = {
|
|
939
|
+
let response = {};
|
|
1827
940
|
switch (type) {
|
|
1828
941
|
case 'GET_PROJECT_INFO':
|
|
1829
|
-
response
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
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 =
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
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 (
|
|
1876
|
-
response
|
|
962
|
+
catch (error) {
|
|
963
|
+
response = { error: error.message };
|
|
1877
964
|
}
|
|
1878
965
|
break;
|
|
1879
|
-
case '
|
|
966
|
+
case 'READ_FILE':
|
|
1880
967
|
try {
|
|
1881
|
-
const filePath =
|
|
1882
|
-
|
|
1883
|
-
|
|
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 (
|
|
1907
|
-
response
|
|
973
|
+
catch (error) {
|
|
974
|
+
response = { error: error.message };
|
|
1908
975
|
}
|
|
1909
976
|
break;
|
|
1910
|
-
case '
|
|
977
|
+
case 'FIND_FILES':
|
|
1911
978
|
try {
|
|
1912
|
-
const
|
|
1913
|
-
|
|
1914
|
-
|
|
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 (
|
|
1917
|
-
response
|
|
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
|
|
1923
|
-
const
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
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
|
-
|
|
1986
|
-
}
|
|
1987
|
-
else {
|
|
1988
|
-
response.error = 'File not found';
|
|
1020
|
+
catch { }
|
|
1989
1021
|
}
|
|
1022
|
+
response = { results };
|
|
1990
1023
|
}
|
|
1991
|
-
catch (
|
|
1992
|
-
response
|
|
1024
|
+
catch (error) {
|
|
1025
|
+
response = { error: error.message };
|
|
1993
1026
|
}
|
|
1994
1027
|
break;
|
|
1995
|
-
case '
|
|
1028
|
+
case 'GET_FILE_TREE':
|
|
1996
1029
|
try {
|
|
1997
|
-
const
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
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 (
|
|
2012
|
-
|
|
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 '
|
|
1042
|
+
case 'GET_ERROR_CONTEXT':
|
|
2020
1043
|
try {
|
|
2021
|
-
const filePath = message
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
const
|
|
2025
|
-
const
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
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 (
|
|
2037
|
-
response
|
|
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
|
|
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 (
|
|
2068
|
-
console.error(chalk.red('
|
|
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
|
-
|
|
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('
|
|
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
|
// ============================================
|