cursorflow 1.3.6__tar.gz → 1.3.7__tar.gz

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.
Files changed (38) hide show
  1. {cursorflow-1.3.6 → cursorflow-1.3.7}/PKG-INFO +3 -1
  2. {cursorflow-1.3.6 → cursorflow-1.3.7}/cursorflow/core/browser_controller.py +51 -17
  3. {cursorflow-1.3.6 → cursorflow-1.3.7}/cursorflow/core/mockup_comparator.py +32 -6
  4. {cursorflow-1.3.6 → cursorflow-1.3.7}/cursorflow/log_sources/local_file.py +5 -1
  5. {cursorflow-1.3.6 → cursorflow-1.3.7}/cursorflow/log_sources/ssh_remote.py +7 -2
  6. {cursorflow-1.3.6 → cursorflow-1.3.7}/docs/USER_MANUAL.md +62 -0
  7. {cursorflow-1.3.6 → cursorflow-1.3.7}/pyproject.toml +4 -2
  8. {cursorflow-1.3.6 → cursorflow-1.3.7}/LICENSE +0 -0
  9. {cursorflow-1.3.6 → cursorflow-1.3.7}/MANIFEST.in +0 -0
  10. {cursorflow-1.3.6 → cursorflow-1.3.7}/README.md +0 -0
  11. {cursorflow-1.3.6 → cursorflow-1.3.7}/cursorflow/__init__.py +0 -0
  12. {cursorflow-1.3.6 → cursorflow-1.3.7}/cursorflow/auto_updater.py +0 -0
  13. {cursorflow-1.3.6 → cursorflow-1.3.7}/cursorflow/cli.py +0 -0
  14. {cursorflow-1.3.6 → cursorflow-1.3.7}/cursorflow/core/agent.py +0 -0
  15. {cursorflow-1.3.6 → cursorflow-1.3.7}/cursorflow/core/auth_handler.py +0 -0
  16. {cursorflow-1.3.6 → cursorflow-1.3.7}/cursorflow/core/browser_engine.py +0 -0
  17. {cursorflow-1.3.6 → cursorflow-1.3.7}/cursorflow/core/css_iterator.py +0 -0
  18. {cursorflow-1.3.6 → cursorflow-1.3.7}/cursorflow/core/cursor_integration.py +0 -0
  19. {cursorflow-1.3.6 → cursorflow-1.3.7}/cursorflow/core/cursorflow.py +0 -0
  20. {cursorflow-1.3.6 → cursorflow-1.3.7}/cursorflow/core/error_correlator.py +0 -0
  21. {cursorflow-1.3.6 → cursorflow-1.3.7}/cursorflow/core/event_correlator.py +0 -0
  22. {cursorflow-1.3.6 → cursorflow-1.3.7}/cursorflow/core/file_change_monitor.py +0 -0
  23. {cursorflow-1.3.6 → cursorflow-1.3.7}/cursorflow/core/log_collector.py +0 -0
  24. {cursorflow-1.3.6 → cursorflow-1.3.7}/cursorflow/core/log_monitor.py +0 -0
  25. {cursorflow-1.3.6 → cursorflow-1.3.7}/cursorflow/core/persistent_session.py +0 -0
  26. {cursorflow-1.3.6 → cursorflow-1.3.7}/cursorflow/core/report_generator.py +0 -0
  27. {cursorflow-1.3.6 → cursorflow-1.3.7}/cursorflow/install_cursorflow_rules.py +0 -0
  28. {cursorflow-1.3.6 → cursorflow-1.3.7}/cursorflow/rules/__init__.py +0 -0
  29. {cursorflow-1.3.6 → cursorflow-1.3.7}/cursorflow/rules/cursorflow-installation.mdc +0 -0
  30. {cursorflow-1.3.6 → cursorflow-1.3.7}/cursorflow/rules/cursorflow-usage.mdc +0 -0
  31. {cursorflow-1.3.6 → cursorflow-1.3.7}/cursorflow/updater.py +0 -0
  32. {cursorflow-1.3.6 → cursorflow-1.3.7}/cursorflow.egg-info/SOURCES.txt +0 -0
  33. {cursorflow-1.3.6 → cursorflow-1.3.7}/examples/comprehensive_screenshot_example.py +0 -0
  34. {cursorflow-1.3.6 → cursorflow-1.3.7}/examples/mockup_comparison_example.py +0 -0
  35. {cursorflow-1.3.6 → cursorflow-1.3.7}/examples/opensas_example.py +0 -0
  36. {cursorflow-1.3.6 → cursorflow-1.3.7}/examples/react_example.py +0 -0
  37. {cursorflow-1.3.6 → cursorflow-1.3.7}/setup.cfg +0 -0
  38. {cursorflow-1.3.6 → cursorflow-1.3.7}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cursorflow
3
- Version: 1.3.6
3
+ Version: 1.3.7
4
4
  Summary: Complete page intelligence for AI-driven development - captures DOM, network, console, and performance data
5
5
  Author-email: GeekWarrior Development <rbush@cooltheory.com>
6
6
  License-Expression: MIT
@@ -32,6 +32,8 @@ Requires-Dist: jinja2>=3.1.2
32
32
  Requires-Dist: python-dateutil>=2.8.2
33
33
  Requires-Dist: watchdog>=3.0.0
34
34
  Requires-Dist: docker>=6.1.3
35
+ Requires-Dist: pillow>=10.0.0
36
+ Requires-Dist: numpy>=1.24.0
35
37
  Provides-Extra: dev
36
38
  Requires-Dist: pytest>=7.4.0; extra == "dev"
37
39
  Requires-Dist: pytest-asyncio>=0.21.1; extra == "dev"
@@ -982,6 +982,13 @@ class BrowserController:
982
982
  const paint = perf.getEntriesByType('paint');
983
983
  const resources = perf.getEntriesByType('resource');
984
984
 
985
+ // Helper function to safely calculate timing differences
986
+ const safeTiming = (end, start) => {
987
+ if (!end || !start || end === 0 || start === 0) return null;
988
+ const diff = end - start;
989
+ return diff >= 0 ? diff : null;
990
+ };
991
+
985
992
  // Memory usage (if available)
986
993
  const memory = performance.memory ? {
987
994
  usedJSHeapSize: performance.memory.usedJSHeapSize,
@@ -1000,19 +1007,40 @@ class BrowserController:
1000
1007
 
1001
1008
  return {
1002
1009
  navigation: navigation ? {
1003
- domContentLoaded: navigation.domContentLoadedEventEnd - navigation.domContentLoadedEventStart,
1004
- loadComplete: navigation.loadEventEnd - navigation.loadEventStart,
1005
- domInteractive: navigation.domInteractive - navigation.navigationStart,
1006
- domComplete: navigation.domComplete - navigation.navigationStart,
1007
- redirectTime: navigation.redirectEnd - navigation.redirectStart,
1008
- dnsTime: navigation.domainLookupEnd - navigation.domainLookupStart,
1009
- connectTime: navigation.connectEnd - navigation.connectStart,
1010
- requestTime: navigation.responseStart - navigation.requestStart,
1011
- responseTime: navigation.responseEnd - navigation.responseStart
1012
- } : null,
1010
+ domContentLoaded: safeTiming(navigation.domContentLoadedEventEnd, navigation.domContentLoadedEventStart),
1011
+ loadComplete: safeTiming(navigation.loadEventEnd, navigation.loadEventStart),
1012
+ domInteractive: safeTiming(navigation.domInteractive, navigation.navigationStart),
1013
+ domComplete: safeTiming(navigation.domComplete, navigation.navigationStart),
1014
+ redirectTime: safeTiming(navigation.redirectEnd, navigation.redirectStart),
1015
+ dnsTime: safeTiming(navigation.domainLookupEnd, navigation.domainLookupStart),
1016
+ connectTime: safeTiming(navigation.connectEnd, navigation.connectStart),
1017
+ requestTime: safeTiming(navigation.responseStart, navigation.requestStart),
1018
+ responseTime: safeTiming(navigation.responseEnd, navigation.responseStart),
1019
+ // Add raw values for debugging
1020
+ _raw: {
1021
+ navigationStart: navigation.navigationStart,
1022
+ domContentLoadedEventStart: navigation.domContentLoadedEventStart,
1023
+ domContentLoadedEventEnd: navigation.domContentLoadedEventEnd,
1024
+ loadEventStart: navigation.loadEventStart,
1025
+ loadEventEnd: navigation.loadEventEnd
1026
+ }
1027
+ } : {
1028
+ domContentLoaded: null,
1029
+ loadComplete: null,
1030
+ domInteractive: null,
1031
+ domComplete: null,
1032
+ redirectTime: null,
1033
+ dnsTime: null,
1034
+ connectTime: null,
1035
+ requestTime: null,
1036
+ responseTime: null,
1037
+ _raw: null,
1038
+ _note: "Navigation timing not available (likely headless mode)"
1039
+ },
1013
1040
  paint: {
1014
- firstPaint: paint.find(p => p.name === 'first-paint')?.startTime || 0,
1015
- firstContentfulPaint: paint.find(p => p.name === 'first-contentful-paint')?.startTime || 0
1041
+ firstPaint: paint.find(p => p.name === 'first-paint')?.startTime || null,
1042
+ firstContentfulPaint: paint.find(p => p.name === 'first-contentful-paint')?.startTime || null,
1043
+ _available: paint.length > 0
1016
1044
  },
1017
1045
  memory: memory,
1018
1046
  resources: {
@@ -1036,12 +1064,18 @@ class BrowserController:
1036
1064
  "browser_metrics": browser_metrics,
1037
1065
  "detailed_metrics": additional_metrics,
1038
1066
  "performance_summary": {
1039
- "page_load_time": additional_metrics.get("navigation", {}).get("loadComplete", 0),
1040
- "dom_content_loaded": additional_metrics.get("navigation", {}).get("domContentLoaded", 0),
1041
- "first_paint": additional_metrics.get("paint", {}).get("firstPaint", 0),
1042
- "first_contentful_paint": additional_metrics.get("paint", {}).get("firstContentfulPaint", 0),
1067
+ "page_load_time": additional_metrics.get("navigation", {}).get("loadComplete"),
1068
+ "dom_content_loaded": additional_metrics.get("navigation", {}).get("domContentLoaded"),
1069
+ "first_paint": additional_metrics.get("paint", {}).get("firstPaint"),
1070
+ "first_contentful_paint": additional_metrics.get("paint", {}).get("firstContentfulPaint"),
1043
1071
  "total_resources": additional_metrics.get("resources", {}).get("summary", {}).get("totalResources", 0),
1044
- "memory_usage_mb": additional_metrics.get("memory", {}).get("usedJSHeapSize", 0) / (1024 * 1024) if additional_metrics.get("memory") else None
1072
+ "memory_usage_mb": additional_metrics.get("memory", {}).get("usedJSHeapSize", 0) / (1024 * 1024) if additional_metrics.get("memory") else None,
1073
+ "_reliability": {
1074
+ "navigation_timing_available": additional_metrics.get("navigation", {}).get("_raw") is not None,
1075
+ "paint_timing_available": additional_metrics.get("paint", {}).get("_available", False),
1076
+ "memory_available": additional_metrics.get("memory") is not None,
1077
+ "note": "Some metrics may be null in headless mode - this is expected behavior"
1078
+ }
1045
1079
  }
1046
1080
  }
1047
1081
 
@@ -11,8 +11,20 @@ import json
11
11
  from typing import Dict, List, Optional, Any, Tuple
12
12
  from pathlib import Path
13
13
  import logging
14
- from PIL import Image, ImageDraw, ImageChops
15
- import numpy as np
14
+ try:
15
+ from PIL import Image, ImageDraw, ImageChops
16
+ import numpy as np
17
+ VISUAL_COMPARISON_AVAILABLE = True
18
+ # Type aliases for when PIL is available
19
+ PILImage = Image.Image
20
+ NDArray = np.ndarray
21
+ except ImportError:
22
+ # PIL/numpy not available - visual comparison features disabled
23
+ Image = ImageDraw = ImageChops = np = None
24
+ VISUAL_COMPARISON_AVAILABLE = False
25
+ # Dummy types for when PIL/numpy are not available
26
+ PILImage = Any
27
+ NDArray = Any
16
28
 
17
29
  from .browser_controller import BrowserController
18
30
  from .css_iterator import CSSIterator
@@ -327,6 +339,20 @@ class MockupComparator:
327
339
  config: Dict
328
340
  ) -> Dict[str, Any]:
329
341
  """Create visual difference analysis between mockup and implementation"""
342
+
343
+ if not VISUAL_COMPARISON_AVAILABLE:
344
+ self.logger.warning("Visual comparison unavailable - PIL/numpy not installed")
345
+ return {
346
+ "error": "Visual comparison requires PIL and numpy",
347
+ "diff_path": None,
348
+ "highlighted_path": None,
349
+ "similarity_score": 0.0,
350
+ "difference_areas": [],
351
+ "pixel_differences": 0,
352
+ "total_pixels": 0,
353
+ "note": "Install with: pip install pillow numpy"
354
+ }
355
+
330
356
  try:
331
357
  # Load images
332
358
  mockup_img = Image.open(mockup_path)
@@ -369,7 +395,7 @@ class MockupComparator:
369
395
  self.logger.error(f"Visual diff creation failed: {e}")
370
396
  return {"error": str(e)}
371
397
 
372
- def _create_highlighted_diff(self, mockup_img: Image.Image, implementation_img: Image.Image, diff_img: Image.Image, config: Dict) -> Image.Image:
398
+ def _create_highlighted_diff(self, mockup_img: PILImage, implementation_img: PILImage, diff_img: PILImage, config: Dict) -> PILImage:
373
399
  """Create highlighted difference image with colored regions"""
374
400
  # Convert to RGBA for overlay
375
401
  highlighted = implementation_img.convert("RGBA")
@@ -393,7 +419,7 @@ class MockupComparator:
393
419
 
394
420
  return highlighted.convert("RGB")
395
421
 
396
- def _calculate_visual_diff_metrics(self, mockup_img: Image.Image, implementation_img: Image.Image, diff_img: Image.Image, config: Dict) -> Dict[str, Any]:
422
+ def _calculate_visual_diff_metrics(self, mockup_img: PILImage, implementation_img: PILImage, diff_img: PILImage, config: Dict) -> Dict[str, Any]:
397
423
  """Calculate detailed visual difference metrics"""
398
424
  try:
399
425
  # Convert to numpy arrays for analysis
@@ -426,7 +452,7 @@ class MockupComparator:
426
452
  self.logger.error(f"Visual diff metrics calculation failed: {e}")
427
453
  return {"error": str(e)}
428
454
 
429
- def _find_difference_regions(self, diff_array: np.ndarray, threshold: float) -> List[Dict]:
455
+ def _find_difference_regions(self, diff_array: NDArray, threshold: float) -> List[Dict]:
430
456
  """Find regions with significant visual differences"""
431
457
  # This is a simplified implementation - could be enhanced with computer vision
432
458
  significant_diff = diff_array > threshold
@@ -448,7 +474,7 @@ class MockupComparator:
448
474
 
449
475
  return regions
450
476
 
451
- def _calculate_color_metrics(self, mockup_array: np.ndarray, implementation_array: np.ndarray) -> Dict[str, Any]:
477
+ def _calculate_color_metrics(self, mockup_array: NDArray, implementation_array: NDArray) -> Dict[str, Any]:
452
478
  """Calculate color-based difference metrics"""
453
479
  try:
454
480
  # Calculate average colors
@@ -98,7 +98,11 @@ class LocalFileLogSource:
98
98
  return True
99
99
 
100
100
  except Exception as e:
101
- self.logger.error(f"Failed to start tail for {log_name}: {e}")
101
+ # Log as debug instead of error for non-critical issues
102
+ if "No such file or directory" in str(e) or "Permission denied" in str(e):
103
+ self.logger.debug(f"Non-critical log file issue for {log_name}: {e}")
104
+ else:
105
+ self.logger.error(f"Failed to start tail for {log_name}: {e}")
102
106
  return False
103
107
 
104
108
  def _read_tail_output(self, log_name: str, log_path: str, process: subprocess.Popen):
@@ -10,8 +10,9 @@ import threading
10
10
  import queue
11
11
  import time
12
12
  import logging
13
+ import re
13
14
  from typing import Dict, List, Optional
14
- from datetime import datetime
15
+ from datetime import datetime, timedelta
15
16
 
16
17
  class SSHRemoteLogSource:
17
18
  """Remote log monitoring via SSH"""
@@ -89,7 +90,11 @@ class SSHRemoteLogSource:
89
90
  return result == 'test'
90
91
 
91
92
  except Exception as e:
92
- self.logger.error(f"SSH connection test failed: {e}")
93
+ # Log SSH connection issues as debug for non-critical cases
94
+ if "Connection refused" in str(e) or "timeout" in str(e).lower():
95
+ self.logger.debug(f"SSH connection issue (may be expected): {e}")
96
+ else:
97
+ self.logger.error(f"SSH connection test failed: {e}")
93
98
  return False
94
99
 
95
100
  def _monitor_single_log(self, log_name: str, log_path: str):
@@ -514,6 +514,68 @@ All data is structured for AI analysis:
514
514
  - Error correlation with confidence scoring
515
515
  - Actionable recommendations based on data
516
516
 
517
+ ## 🔧 **Troubleshooting**
518
+
519
+ ### **Performance Metrics Reliability**
520
+
521
+ CursorFlow captures comprehensive performance data, but some metrics behave differently in headless vs headed mode:
522
+
523
+ #### **Always Reliable (Both Modes):**
524
+ - ✅ **DOM Analysis**: Element properties, computed CSS, bounding boxes
525
+ - ✅ **Network Data**: Request timing, response codes, resource sizes
526
+ - ✅ **Console Logs**: JavaScript errors, warnings, debug messages
527
+ - ✅ **Memory Usage**: JavaScript heap size (when available)
528
+ - ✅ **Resource Count**: Total loaded resources, slowest resource
529
+
530
+ #### **Headless Mode Limitations:**
531
+ - ⚠️ **Navigation Timing**: May return `null` instead of 0/NaN
532
+ - `domContentLoaded`, `loadComplete`, `domInteractive` timing
533
+ - Raw timing values available in `_raw` object for debugging
534
+ - ⚠️ **Paint Timing**: `firstPaint`, `firstContentfulPaint` may be `null`
535
+ - Check `_available` flag to see if paint metrics are captured
536
+
537
+ #### **Understanding the Data:**
538
+ ```json
539
+ {
540
+ "performance_summary": {
541
+ "page_load_time": null, // ← null = not available (expected in headless)
542
+ "dom_content_loaded": 45.2, // ← actual timing when available
543
+ "_reliability": {
544
+ "navigation_timing_available": false,
545
+ "paint_timing_available": true,
546
+ "note": "Some metrics may be null in headless mode - this is expected behavior"
547
+ }
548
+ }
549
+ }
550
+ ```
551
+
552
+ **Key Point**: `null` values are **expected and normal** in headless mode. The CSS/DOM data (which is what matters most for UI testing) is always 100% reliable.
553
+
554
+ ### **Common Issues**
555
+
556
+ #### **Missing Dependencies**
557
+ ```bash
558
+ # If you see PIL/Pillow errors:
559
+ pip install --upgrade cursorflow # v1.3.7+ includes all dependencies
560
+
561
+ # Manual fix for older versions:
562
+ pip install pillow numpy
563
+ ```
564
+
565
+ #### **Log Source Warnings**
566
+ Non-critical log warnings are normal and can be ignored:
567
+ - `SSH connection issue (may be expected)` - appears when SSH monitoring isn't configured
568
+ - `Non-critical log file issue` - appears when log files don't exist yet
569
+
570
+ #### **Browser Launch Issues**
571
+ ```bash
572
+ # Install browser dependencies:
573
+ playwright install chromium
574
+
575
+ # For Docker/CI environments:
576
+ playwright install-deps chromium
577
+ ```
578
+
517
579
  ---
518
580
 
519
581
  **That's everything you need to know!** CursorFlow is designed to be simple: point it at your app, and it captures everything an AI needs to understand and improve your UI. 🚀
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cursorflow"
7
- version = "1.3.6"
7
+ version = "1.3.7"
8
8
  description = "Complete page intelligence for AI-driven development - captures DOM, network, console, and performance data"
9
9
  authors = [
10
10
  {name = "GeekWarrior Development", email = "rbush@cooltheory.com"}
@@ -36,7 +36,9 @@ dependencies = [
36
36
  "jinja2>=3.1.2",
37
37
  "python-dateutil>=2.8.2",
38
38
  "watchdog>=3.0.0",
39
- "docker>=6.1.3"
39
+ "docker>=6.1.3",
40
+ "pillow>=10.0.0",
41
+ "numpy>=1.24.0"
40
42
  ]
41
43
 
42
44
  [project.optional-dependencies]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes