dev3000 0.0.24 → 0.0.26

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.
@@ -1,11 +1,11 @@
1
1
  import { spawn } from 'child_process';
2
- import { chromium } from 'playwright';
3
2
  import { writeFileSync, appendFileSync, mkdirSync, existsSync, copyFileSync, readFileSync, cpSync, lstatSync, symlinkSync, unlinkSync, readdirSync, statSync } from 'fs';
4
3
  import { join, dirname, basename } from 'path';
5
4
  import { fileURLToPath } from 'url';
6
5
  import { tmpdir } from 'os';
7
6
  import chalk from 'chalk';
8
7
  import * as cliProgress from 'cli-progress';
8
+ import { CDPMonitor } from './cdp-monitor.js';
9
9
  class Logger {
10
10
  logFile;
11
11
  constructor(logFile) {
@@ -24,15 +24,6 @@ class Logger {
24
24
  appendFileSync(this.logFile, logEntry);
25
25
  }
26
26
  }
27
- function detectPackageManager() {
28
- if (existsSync('pnpm-lock.yaml'))
29
- return 'pnpx';
30
- if (existsSync('yarn.lock'))
31
- return 'yarn dlx';
32
- if (existsSync('package-lock.json'))
33
- return 'npx';
34
- return 'npx'; // fallback
35
- }
36
27
  function detectPackageManagerForRun() {
37
28
  if (existsSync('pnpm-lock.yaml'))
38
29
  return 'pnpm';
@@ -116,11 +107,8 @@ function pruneOldLogs(baseDir, cwdName) {
116
107
  export class DevEnvironment {
117
108
  serverProcess = null;
118
109
  mcpServerProcess = null;
119
- browser = null;
120
- browserContext = null;
110
+ cdpMonitor = null;
121
111
  logger;
122
- stateTimer = null;
123
- browserType = null;
124
112
  options;
125
113
  screenshotDir;
126
114
  mcpPublicDir;
@@ -144,9 +132,19 @@ export class DevEnvironment {
144
132
  const packageJsonPath = join(packageRoot, 'package.json');
145
133
  const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
146
134
  this.version = packageJson.version;
147
- // Add -dev suffix for local development installs
148
- if (packageRoot.includes('vercel-labs/dev3000')) {
149
- this.version += '-dev';
135
+ // Use git to detect if we're in the dev3000 source repository
136
+ try {
137
+ const { execSync } = require('child_process');
138
+ const gitRemote = execSync('git remote get-url origin 2>/dev/null', {
139
+ cwd: packageRoot,
140
+ encoding: 'utf8'
141
+ }).trim();
142
+ if (gitRemote.includes('vercel-labs/dev3000') && !this.version.includes('canary')) {
143
+ this.version += '-local';
144
+ }
145
+ }
146
+ catch {
147
+ // Not in git repo or no git - use version as-is
150
148
  }
151
149
  }
152
150
  catch (error) {
@@ -168,11 +166,6 @@ export class DevEnvironment {
168
166
  mkdirSync(this.mcpPublicDir, { recursive: true });
169
167
  }
170
168
  }
171
- debugLog(message) {
172
- if (this.options.debug) {
173
- console.log(chalk.gray(`[DEBUG] ${message}`));
174
- }
175
- }
176
169
  async checkPortsAvailable() {
177
170
  const ports = [this.options.port, this.options.mcpPort];
178
171
  for (const port of ports) {
@@ -221,8 +214,8 @@ export class DevEnvironment {
221
214
  this.progressBar.update(60, { stage: 'Waiting for MCP server...' });
222
215
  await this.waitForMcpServer();
223
216
  this.progressBar.update(80, { stage: 'Starting browser...' });
224
- // Start browser monitoring but don't wait for full setup
225
- this.startBrowserMonitoringAsync();
217
+ // Start CDP monitoring but don't wait for full setup
218
+ this.startCDPMonitoringAsync();
226
219
  this.progressBar.update(100, { stage: 'Complete!' });
227
220
  // Stop progress bar and show results immediately
228
221
  this.progressBar.stop();
@@ -383,131 +376,6 @@ export class DevEnvironment {
383
376
  }
384
377
  });
385
378
  }
386
- startStateSaving() {
387
- if (this.stateTimer) {
388
- clearInterval(this.stateTimer);
389
- }
390
- // Start continuous autosave timer (no focus dependency since it's non-intrusive)
391
- this.stateTimer = setInterval(async () => {
392
- if (this.browserContext) {
393
- try {
394
- this.debugLog('Running periodic context autosave (non-intrusive)...');
395
- await this.saveStateManually();
396
- this.debugLog('Context autosave completed successfully');
397
- }
398
- catch (error) {
399
- const errorMessage = error instanceof Error ? error.message : String(error);
400
- this.debugLog(`Context autosave failed: ${errorMessage}`);
401
- // If context is closed, stop the timer
402
- if (errorMessage.includes('closed') || errorMessage.includes('destroyed')) {
403
- this.debugLog('Browser context appears closed, stopping autosave timer');
404
- this.stopStateSaving();
405
- }
406
- }
407
- }
408
- }, 15000); // Save every 15 seconds
409
- }
410
- stopStateSaving() {
411
- if (this.stateTimer) {
412
- clearInterval(this.stateTimer);
413
- this.stateTimer = null;
414
- }
415
- }
416
- async saveStateManually() {
417
- if (!this.browserContext)
418
- return;
419
- const stateDir = this.options.profileDir;
420
- const cookiesFile = join(stateDir, 'cookies.json');
421
- const storageFile = join(stateDir, 'storage.json');
422
- try {
423
- // Save cookies (non-intrusive)
424
- const cookies = await this.browserContext.cookies();
425
- writeFileSync(cookiesFile, JSON.stringify(cookies, null, 2));
426
- // Save localStorage and sessionStorage from current pages (non-intrusive)
427
- const pages = this.browserContext.pages();
428
- if (pages.length > 0) {
429
- const page = pages[0]; // Use first page to avoid creating new ones
430
- if (!page.isClosed()) {
431
- const storageData = await page.evaluate(() => {
432
- return {
433
- localStorage: JSON.stringify(localStorage),
434
- sessionStorage: JSON.stringify(sessionStorage),
435
- url: window.location.href
436
- };
437
- });
438
- writeFileSync(storageFile, JSON.stringify(storageData, null, 2));
439
- }
440
- }
441
- }
442
- catch (error) {
443
- // Re-throw to be handled by caller
444
- throw error;
445
- }
446
- }
447
- async loadStateManually() {
448
- if (!this.browserContext)
449
- return;
450
- const stateDir = this.options.profileDir;
451
- const cookiesFile = join(stateDir, 'cookies.json');
452
- const storageFile = join(stateDir, 'storage.json');
453
- try {
454
- // Load cookies if they exist
455
- if (existsSync(cookiesFile)) {
456
- const cookies = JSON.parse(readFileSync(cookiesFile, 'utf8'));
457
- if (Array.isArray(cookies) && cookies.length > 0) {
458
- await this.browserContext.addCookies(cookies);
459
- this.debugLog(`Restored ${cookies.length} cookies`);
460
- }
461
- }
462
- // Load storage data for later restoration (we'll apply it after page navigation)
463
- if (existsSync(storageFile)) {
464
- const storageData = JSON.parse(readFileSync(storageFile, 'utf8'));
465
- if (storageData.localStorage || storageData.sessionStorage) {
466
- // Store this for restoration after page loads
467
- this.browserContext._dev3000_storageData = storageData;
468
- this.debugLog('Loaded storage data for restoration');
469
- }
470
- }
471
- }
472
- catch (error) {
473
- this.debugLog(`Failed to load saved state: ${error instanceof Error ? error.message : String(error)}`);
474
- }
475
- }
476
- async restoreStorageData(page) {
477
- if (!this.browserContext || page.isClosed())
478
- return;
479
- const storageData = this.browserContext._dev3000_storageData;
480
- if (!storageData)
481
- return;
482
- try {
483
- await page.evaluate((data) => {
484
- // Restore localStorage
485
- if (data.localStorage) {
486
- const localStorageData = JSON.parse(data.localStorage);
487
- Object.keys(localStorageData).forEach(key => {
488
- localStorage.setItem(key, localStorageData[key]);
489
- });
490
- }
491
- // Restore sessionStorage
492
- if (data.sessionStorage) {
493
- const sessionStorageData = JSON.parse(data.sessionStorage);
494
- Object.keys(sessionStorageData).forEach(key => {
495
- sessionStorage.setItem(key, sessionStorageData[key]);
496
- });
497
- }
498
- }, storageData);
499
- this.debugLog('Restored localStorage and sessionStorage');
500
- // Clear the stored data since it's been applied
501
- delete this.browserContext._dev3000_storageData;
502
- }
503
- catch (error) {
504
- this.debugLog(`Failed to restore storage data: ${error instanceof Error ? error.message : String(error)}`);
505
- }
506
- }
507
- async setupFocusHandlers(page) {
508
- // Note: Focus handlers removed since autosave is now continuous and non-intrusive
509
- // The autosave timer will detect context closure through its own error handling
510
- }
511
379
  async waitForServer() {
512
380
  const maxAttempts = 30;
513
381
  let attempts = 0;
@@ -641,393 +509,33 @@ export class DevEnvironment {
641
509
  }
642
510
  // Continue anyway if health check fails
643
511
  }
644
- startBrowserMonitoringAsync() {
645
- // Start browser monitoring in background without blocking completion
646
- this.startBrowserMonitoring().catch(error => {
647
- console.error(chalk.red('⚠️ Browser monitoring setup failed:'), error);
512
+ startCDPMonitoringAsync() {
513
+ // Start CDP monitoring in background without blocking completion
514
+ this.startCDPMonitoring().catch(error => {
515
+ console.error(chalk.red('⚠️ CDP monitoring setup failed:'), error);
648
516
  });
649
517
  }
650
- async startBrowserMonitoring() {
518
+ async startCDPMonitoring() {
651
519
  // Ensure profile directory exists
652
520
  if (!existsSync(this.options.profileDir)) {
653
521
  mkdirSync(this.options.profileDir, { recursive: true });
654
522
  }
523
+ // Initialize CDP monitor with enhanced logging
524
+ this.cdpMonitor = new CDPMonitor(this.options.profileDir, (source, message) => {
525
+ this.logger.log('browser', message);
526
+ }, this.options.debug);
655
527
  try {
656
- // Try to use system Chrome first
657
- this.browser = await chromium.launch({
658
- headless: false,
659
- channel: 'chrome', // Use system Chrome
660
- // Remove automation flags to allow normal dialog behavior
661
- args: [
662
- '--disable-web-security', // Keep this for dev server access
663
- '--hide-crash-restore-bubble', // Don't ask to restore pages
664
- '--disable-infobars', // Remove info bars
665
- '--disable-blink-features=AutomationControlled', // Hide automation detection
666
- '--disable-features=VizDisplayCompositor', // Reduce automation fingerprinting
667
- ],
668
- });
669
- this.browserType = 'system-chrome';
528
+ // Start CDP monitoring
529
+ await this.cdpMonitor.start();
530
+ this.logger.log('browser', '[CDP] Chrome launched with DevTools Protocol monitoring');
531
+ // Navigate to the app
532
+ await this.cdpMonitor.navigateToApp(this.options.port);
533
+ this.logger.log('browser', `[CDP] Navigated to http://localhost:${this.options.port}`);
670
534
  }
671
535
  catch (error) {
672
- // Fallback to Playwright's bundled chromium
673
- try {
674
- this.browser = await chromium.launch({
675
- headless: false,
676
- // Remove automation flags to allow normal dialog behavior
677
- args: [
678
- '--disable-web-security', // Keep this for dev server access
679
- '--hide-crash-restore-bubble', // Don't ask to restore pages
680
- '--disable-infobars', // Remove info bars
681
- '--disable-blink-features=AutomationControlled', // Hide automation detection
682
- '--disable-features=VizDisplayCompositor', // Reduce automation fingerprinting
683
- ],
684
- });
685
- this.browserType = 'playwright-chromium';
686
- }
687
- catch (playwrightError) {
688
- if (playwrightError.message?.includes('Executable doesn\'t exist')) {
689
- detectPackageManager();
690
- console.log(chalk.yellow('📦 Installing Playwright chromium browser...'));
691
- await this.installPlaywrightBrowsers();
692
- // Retry with bundled chromium
693
- this.browser = await chromium.launch({
694
- headless: false,
695
- // Remove automation flags to allow normal dialog behavior
696
- args: [
697
- '--disable-web-security', // Keep this for dev server access
698
- '--hide-crash-restore-bubble', // Don't ask to restore pages
699
- '--disable-infobars', // Remove info bars
700
- ],
701
- });
702
- this.browserType = 'playwright-chromium';
703
- }
704
- else {
705
- throw playwrightError;
706
- }
707
- }
708
- }
709
- // Create context with viewport: null to enable window resizing
710
- this.browserContext = await this.browser.newContext({
711
- viewport: null, // This makes the page size depend on the window size
712
- });
713
- // Restore state manually (non-intrusive)
714
- await this.loadStateManually();
715
- // Set up focus-aware periodic storage state saving
716
- this.startStateSaving();
717
- // Navigate to the app using the existing blank page
718
- const pages = this.browserContext.pages();
719
- const page = pages.length > 0 ? pages[0] : await this.browserContext.newPage();
720
- // Disable automatic dialog handling - let dialogs behave naturally
721
- page.removeAllListeners('dialog');
722
- // Add a no-op dialog handler to prevent auto-dismissal
723
- page.on('dialog', async (dialog) => {
724
- // Don't accept or dismiss - let user handle it manually
725
- // This prevents Playwright from auto-handling the dialog
726
- });
727
- await page.goto(`http://localhost:${this.options.port}`);
728
- // Restore localStorage and sessionStorage after navigation
729
- await this.restoreStorageData(page);
730
- // Set up focus detection after navigation to prevent context execution errors
731
- await this.setupFocusHandlers(page);
732
- // Take initial screenshot
733
- const initialScreenshot = await this.takeScreenshot(page, 'initial-load');
734
- if (initialScreenshot) {
735
- this.logger.log('browser', `[SCREENSHOT] ${initialScreenshot}`);
736
- }
737
- // Set up monitoring
738
- await this.setupPageMonitoring(page);
739
- // Monitor new pages
740
- this.browserContext.on('page', async (newPage) => {
741
- // Disable automatic dialog handling for new pages too
742
- newPage.removeAllListeners('dialog');
743
- // Add a no-op dialog handler to prevent auto-dismissal
744
- newPage.on('dialog', async (dialog) => {
745
- // Don't accept or dismiss - let user handle it manually
746
- });
747
- await this.setupPageMonitoring(newPage);
748
- });
749
- }
750
- async installPlaywrightBrowsers() {
751
- this.progressBar.update(75, { stage: 'Installing Playwright browser (2-3 min)...' });
752
- return new Promise((resolve, reject) => {
753
- const packageManager = detectPackageManager();
754
- const [command, ...args] = packageManager.split(' ');
755
- console.log(chalk.gray(`Running: ${command} ${[...args, 'playwright', 'install', 'chromium'].join(' ')}`));
756
- const installProcess = spawn(command, [...args, 'playwright', 'install', 'chromium'], {
757
- stdio: ['inherit', 'pipe', 'pipe'],
758
- shell: true,
759
- });
760
- // Add timeout (5 minutes)
761
- const timeout = setTimeout(() => {
762
- installProcess.kill('SIGKILL');
763
- reject(new Error('Playwright installation timed out after 5 minutes'));
764
- }, 5 * 60 * 1000);
765
- let hasOutput = false;
766
- installProcess.stdout?.on('data', (data) => {
767
- hasOutput = true;
768
- const message = data.toString().trim();
769
- if (message) {
770
- console.log(chalk.gray('[PLAYWRIGHT]'), message);
771
- }
772
- });
773
- installProcess.stderr?.on('data', (data) => {
774
- hasOutput = true;
775
- const message = data.toString().trim();
776
- if (message) {
777
- console.log(chalk.gray('[PLAYWRIGHT]'), message);
778
- }
779
- });
780
- installProcess.on('exit', (code) => {
781
- clearTimeout(timeout);
782
- if (code === 0) {
783
- console.log(chalk.green('✅ Playwright chromium installed successfully!'));
784
- resolve();
785
- }
786
- else {
787
- reject(new Error(`Playwright installation failed with exit code ${code}`));
788
- }
789
- });
790
- installProcess.on('error', (error) => {
791
- clearTimeout(timeout);
792
- reject(new Error(`Failed to start Playwright installation: ${error.message}`));
793
- });
794
- // Check if process seems stuck
795
- setTimeout(() => {
796
- if (!hasOutput) {
797
- console.log(chalk.yellow('⚠️ Installation seems stuck. This is normal for the first run - downloading ~100MB...'));
798
- }
799
- }, 10000); // Show message after 10 seconds of no output
800
- });
801
- }
802
- async takeScreenshot(page, event) {
803
- try {
804
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
805
- const filename = `${timestamp}-${event}.png`;
806
- const screenshotPath = join(this.screenshotDir, filename);
807
- await page.screenshot({
808
- path: screenshotPath,
809
- fullPage: false, // Just viewport for speed
810
- animations: 'disabled' // Disable animations during screenshot
811
- });
812
- // Return web-accessible URL (no need to copy since we save directly to MCP public dir)
813
- return `http://localhost:${this.options.mcpPort}/screenshots/${filename}`;
814
- }
815
- catch (error) {
816
- console.error(chalk.red('[SCREENSHOT ERROR]'), error);
817
- return null;
818
- }
819
- }
820
- async setupPageMonitoring(page) {
821
- const url = page.url();
822
- // Only monitor localhost pages
823
- if (!url.includes(`localhost:${this.options.port}`) && url !== 'about:blank') {
824
- return;
825
- }
826
- this.logger.log('browser', `📄 New page: ${url}`);
827
- // Console logs
828
- page.on('console', async (msg) => {
829
- if (page.url().includes(`localhost:${this.options.port}`)) {
830
- // Handle our interaction tracking logs specially
831
- const text = msg.text();
832
- if (text.startsWith('[DEV3000_INTERACTION]')) {
833
- const interaction = text.replace('[DEV3000_INTERACTION] ', '');
834
- this.logger.log('browser', `[INTERACTION] ${interaction}`);
835
- return;
836
- }
837
- // Try to reconstruct the console message properly
838
- let logMessage;
839
- try {
840
- // Get all arguments from the console message
841
- const args = msg.args();
842
- if (args.length === 0) {
843
- logMessage = text;
844
- }
845
- else if (args.length === 1) {
846
- // Single argument - use text() which is already formatted
847
- logMessage = text;
848
- }
849
- else {
850
- // Multiple arguments - format them properly
851
- const argValues = await Promise.all(args.map(async (arg) => {
852
- try {
853
- const value = await arg.jsonValue();
854
- return typeof value === 'object' ? JSON.stringify(value) : String(value);
855
- }
856
- catch {
857
- return '[object]';
858
- }
859
- }));
860
- // Join all arguments with spaces (like normal console output)
861
- logMessage = argValues.join(' ');
862
- }
863
- }
864
- catch (error) {
865
- // Fallback to original text if args processing fails
866
- logMessage = text;
867
- }
868
- const level = msg.type().toUpperCase();
869
- this.logger.log('browser', `[CONSOLE ${level}] ${logMessage}`);
870
- }
871
- });
872
- // Page errors
873
- page.on('pageerror', async (error) => {
874
- if (page.url().includes(`localhost:${this.options.port}`)) {
875
- const screenshotPath = await this.takeScreenshot(page, 'error');
876
- this.logger.log('browser', `[PAGE ERROR] ${error.message}`);
877
- if (screenshotPath) {
878
- this.logger.log('browser', `[SCREENSHOT] ${screenshotPath}`);
879
- }
880
- if (error.stack) {
881
- this.logger.log('browser', `[PAGE ERROR STACK] ${error.stack}`);
882
- }
883
- }
884
- });
885
- // Network requests
886
- page.on('request', (request) => {
887
- if (page.url().includes(`localhost:${this.options.port}`) && !request.url().includes(`localhost:${this.options.mcpPort}`)) {
888
- this.logger.log('browser', `[NETWORK REQUEST] ${request.method()} ${request.url()}`);
889
- }
890
- });
891
- page.on('response', async (response) => {
892
- if (page.url().includes(`localhost:${this.options.port}`) && !response.url().includes(`localhost:${this.options.mcpPort}`)) {
893
- const status = response.status();
894
- const url = response.url();
895
- if (status >= 400) {
896
- const screenshotPath = await this.takeScreenshot(page, 'network-error');
897
- this.logger.log('browser', `[NETWORK ERROR] ${status} ${url}`);
898
- if (screenshotPath) {
899
- this.logger.log('browser', `[SCREENSHOT] ${screenshotPath}`);
900
- }
901
- }
902
- }
903
- });
904
- // Navigation (only screenshot on route changes, not every navigation)
905
- let lastRoute = '';
906
- page.on('framenavigated', async (frame) => {
907
- if (frame === page.mainFrame() && frame.url().includes(`localhost:${this.options.port}`)) {
908
- const currentRoute = new URL(frame.url()).pathname;
909
- this.logger.log('browser', `[NAVIGATION] ${frame.url()}`);
910
- // Only screenshot if route actually changed
911
- if (currentRoute !== lastRoute) {
912
- const screenshotPath = await this.takeScreenshot(page, 'route-change');
913
- if (screenshotPath) {
914
- this.logger.log('browser', `[SCREENSHOT] ${screenshotPath}`);
915
- }
916
- lastRoute = currentRoute;
917
- }
918
- }
919
- });
920
- // Set up user interaction tracking (clicks, scrolls, etc.)
921
- await this.setupInteractionTracking(page);
922
- }
923
- async setupInteractionTracking(page) {
924
- if (!page.url().includes(`localhost:${this.options.port}`)) {
925
- return;
926
- }
927
- try {
928
- // Inject interaction tracking scripts into the page (both at init and after load)
929
- const trackingScript = () => {
930
- // Only inject once
931
- if (window.__dev3000_tracking_injected)
932
- return;
933
- window.__dev3000_tracking_injected = true;
934
- // Track clicks and taps
935
- document.addEventListener('click', (event) => {
936
- const target = event.target;
937
- const targetSelector = target.tagName.toLowerCase() +
938
- (target.id ? `#${target.id}` : '') +
939
- (target.className ? `.${target.className.split(' ').join('.')}` : '');
940
- const interactionData = {
941
- type: 'CLICK',
942
- coordinates: { x: event.clientX, y: event.clientY },
943
- target: targetSelector,
944
- text: target.textContent?.slice(0, 50) || null,
945
- viewport: { width: window.innerWidth, height: window.innerHeight },
946
- scroll: { x: window.scrollX, y: window.scrollY }
947
- };
948
- console.log(`[DEV3000_INTERACTION] ${JSON.stringify(interactionData)}`);
949
- }, true);
950
- // Track touch events (mobile/tablet)
951
- document.addEventListener('touchstart', (event) => {
952
- if (event.touches.length > 0) {
953
- const touch = event.touches[0];
954
- const target = event.target;
955
- const targetSelector = target.tagName.toLowerCase() +
956
- (target.id ? `#${target.id}` : '') +
957
- (target.className ? `.${target.className.split(' ').join('.')}` : '');
958
- const interactionData = {
959
- type: 'TAP',
960
- coordinates: { x: Math.round(touch.clientX), y: Math.round(touch.clientY) },
961
- target: targetSelector,
962
- text: target.textContent?.slice(0, 50) || null,
963
- viewport: { width: window.innerWidth, height: window.innerHeight },
964
- scroll: { x: window.scrollX, y: window.scrollY }
965
- };
966
- console.log(`[DEV3000_INTERACTION] ${JSON.stringify(interactionData)}`);
967
- }
968
- }, true);
969
- // Track scrolling with throttling to avoid spam
970
- let lastScrollTime = 0;
971
- let lastScrollY = window.scrollY;
972
- let lastScrollX = window.scrollX;
973
- document.addEventListener('scroll', () => {
974
- const now = Date.now();
975
- if (now - lastScrollTime > 500) { // Throttle to max once per 500ms
976
- const deltaY = window.scrollY - lastScrollY;
977
- const deltaX = window.scrollX - lastScrollX;
978
- if (Math.abs(deltaY) > 10 || Math.abs(deltaX) > 10) { // Only log significant scrolls
979
- const direction = deltaY > 0 ? 'DOWN' : deltaY < 0 ? 'UP' : deltaX > 0 ? 'RIGHT' : 'LEFT';
980
- const distance = Math.round(Math.sqrt(deltaX * deltaX + deltaY * deltaY));
981
- const interactionData = {
982
- type: 'SCROLL',
983
- direction: direction,
984
- distance: distance,
985
- from: { x: lastScrollX, y: lastScrollY },
986
- to: { x: window.scrollX, y: window.scrollY },
987
- viewport: { width: window.innerWidth, height: window.innerHeight }
988
- };
989
- console.log(`[DEV3000_INTERACTION] ${JSON.stringify(interactionData)}`);
990
- lastScrollTime = now;
991
- lastScrollY = window.scrollY;
992
- lastScrollX = window.scrollX;
993
- }
994
- }
995
- }, true);
996
- // Track keyboard events (for form interactions)
997
- document.addEventListener('keydown', (event) => {
998
- const target = event.target;
999
- if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.contentEditable === 'true') {
1000
- const targetSelector = target.tagName.toLowerCase() +
1001
- (target.id ? `#${target.id}` : '') +
1002
- (target.type ? `[type=${target.type}]` : '') +
1003
- (target.className ? `.${target.className.split(' ').join('.')}` : '');
1004
- // Log special keys, but not every character to avoid logging sensitive data
1005
- if (event.key.length > 1 || event.key === ' ') {
1006
- const interactionData = {
1007
- type: 'KEY',
1008
- key: event.key === ' ' ? 'Space' : event.key,
1009
- target: targetSelector,
1010
- modifiers: {
1011
- ctrl: event.ctrlKey,
1012
- alt: event.altKey,
1013
- shift: event.shiftKey,
1014
- meta: event.metaKey
1015
- },
1016
- inputType: target.type || target.tagName.toLowerCase()
1017
- };
1018
- console.log(`[DEV3000_INTERACTION] ${JSON.stringify(interactionData)}`);
1019
- }
1020
- }
1021
- }, true);
1022
- // Log that tracking is active
1023
- console.log('[DEV3000_INTERACTION] Tracking initialized');
1024
- };
1025
- // Add to page init - this should be sufficient
1026
- await page.addInitScript(trackingScript);
1027
- // Note: Interaction logs will be captured by the existing console handler in setupPageMonitoring
1028
- }
1029
- catch (error) {
1030
- console.warn('Could not set up interaction tracking:', error);
536
+ // Log error but don't crash - we want the servers to keep running
537
+ this.logger.log('browser', `[CDP ERROR] Failed to start CDP monitoring: ${error}`);
538
+ console.error(chalk.red('⚠️ CDP monitoring failed, but servers are still running'));
1031
539
  }
1032
540
  }
1033
541
  setupCleanupHandlers() {
@@ -1058,42 +566,15 @@ export class DevEnvironment {
1058
566
  killPortProcess(this.options.port, 'your app server'),
1059
567
  killPortProcess(this.options.mcpPort, 'dev3000 MCP server')
1060
568
  ]);
1061
- // Clear the state saving timer
1062
- this.stopStateSaving();
1063
- // Try to save browser state quickly (with timeout) - non-intrusive
1064
- if (this.browserContext) {
569
+ // Shutdown CDP monitor
570
+ if (this.cdpMonitor) {
1065
571
  try {
1066
- console.log(chalk.blue('💾 Saving browser state...'));
1067
- const savePromise = this.saveStateManually();
1068
- const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 2000));
1069
- await Promise.race([savePromise, timeoutPromise]);
1070
- console.log(chalk.green('✅ Browser state saved'));
572
+ console.log(chalk.blue('🔄 Closing CDP monitor...'));
573
+ await this.cdpMonitor.shutdown();
574
+ console.log(chalk.green('✅ CDP monitor closed'));
1071
575
  }
1072
576
  catch (error) {
1073
- console.log(chalk.gray('⚠️ Could not save browser state (timed out)'));
1074
- }
1075
- }
1076
- // Close browser quickly (with timeout)
1077
- if (this.browser) {
1078
- try {
1079
- if (this.browserType === 'system-chrome') {
1080
- console.log(chalk.blue('🔄 Closing browser tab (keeping Chrome open)...'));
1081
- }
1082
- else {
1083
- console.log(chalk.blue('🔄 Closing browser...'));
1084
- }
1085
- const closePromise = this.browser.close();
1086
- const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 2000));
1087
- await Promise.race([closePromise, timeoutPromise]);
1088
- console.log(chalk.green('✅ Browser closed'));
1089
- }
1090
- catch (error) {
1091
- if (this.browserType === 'system-chrome') {
1092
- console.log(chalk.gray('⚠️ Chrome tab close failed (this is normal - your Chrome stays open)'));
1093
- }
1094
- else {
1095
- console.log(chalk.gray('⚠️ Browser close timed out'));
1096
- }
577
+ console.log(chalk.gray('⚠️ CDP monitor shutdown failed'));
1097
578
  }
1098
579
  }
1099
580
  console.log(chalk.green('✅ Cleanup complete'));