cursorflow 1.3.5__py3-none-any.whl → 1.3.7__py3-none-any.whl
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.
- cursorflow/core/browser_controller.py +55 -21
- cursorflow/core/mockup_comparator.py +32 -6
- cursorflow/log_sources/local_file.py +5 -1
- cursorflow/log_sources/ssh_remote.py +7 -2
- {cursorflow-1.3.5.dist-info → cursorflow-1.3.7.dist-info}/METADATA +3 -1
- {cursorflow-1.3.5.dist-info → cursorflow-1.3.7.dist-info}/RECORD +10 -10
- {cursorflow-1.3.5.dist-info → cursorflow-1.3.7.dist-info}/WHEEL +0 -0
- {cursorflow-1.3.5.dist-info → cursorflow-1.3.7.dist-info}/entry_points.txt +0 -0
- {cursorflow-1.3.5.dist-info → cursorflow-1.3.7.dist-info}/licenses/LICENSE +0 -0
- {cursorflow-1.3.5.dist-info → cursorflow-1.3.7.dist-info}/top_level.txt +0 -0
@@ -48,7 +48,7 @@ class BrowserController:
|
|
48
48
|
self.logger = logging.getLogger(__name__)
|
49
49
|
|
50
50
|
# Ensure artifacts directory exists
|
51
|
-
Path("artifacts/screenshots").mkdir(parents=True, exist_ok=True)
|
51
|
+
Path(".cursorflow/artifacts/screenshots").mkdir(parents=True, exist_ok=True)
|
52
52
|
|
53
53
|
async def initialize(self):
|
54
54
|
"""Initialize browser with universal settings"""
|
@@ -75,7 +75,7 @@ class BrowserController:
|
|
75
75
|
context_config = {
|
76
76
|
"viewport": viewport,
|
77
77
|
"ignore_https_errors": True,
|
78
|
-
"record_video_dir": "artifacts/videos" if self.config.get("record_video") else None
|
78
|
+
"record_video_dir": ".cursorflow/artifacts/videos" if self.config.get("record_video") else None
|
79
79
|
}
|
80
80
|
|
81
81
|
self.context = await self.browser.new_context(**context_config)
|
@@ -317,7 +317,7 @@ class BrowserController:
|
|
317
317
|
"""Take screenshot with comprehensive page analysis - universal"""
|
318
318
|
try:
|
319
319
|
timestamp = int(time.time())
|
320
|
-
screenshot_filename = f"artifacts/screenshots/{name}_{timestamp}.png"
|
320
|
+
screenshot_filename = f".cursorflow/artifacts/screenshots/{name}_{timestamp}.png"
|
321
321
|
|
322
322
|
# Take the visual screenshot
|
323
323
|
await self.page.screenshot(
|
@@ -338,7 +338,7 @@ class BrowserController:
|
|
338
338
|
comprehensive_data = await self._capture_comprehensive_page_analysis()
|
339
339
|
|
340
340
|
# Save comprehensive data alongside screenshot
|
341
|
-
data_filename = f"artifacts/screenshots/{name}_{timestamp}_comprehensive_data.json"
|
341
|
+
data_filename = f".cursorflow/artifacts/screenshots/{name}_{timestamp}_comprehensive_data.json"
|
342
342
|
import json
|
343
343
|
with open(data_filename, 'w') as f:
|
344
344
|
json.dump(comprehensive_data, f, indent=2, default=str)
|
@@ -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
|
1004
|
-
loadComplete: navigation.loadEventEnd
|
1005
|
-
domInteractive: navigation.domInteractive
|
1006
|
-
domComplete: navigation.domComplete
|
1007
|
-
redirectTime: navigation.redirectEnd
|
1008
|
-
dnsTime: navigation.domainLookupEnd
|
1009
|
-
connectTime: navigation.connectEnd
|
1010
|
-
requestTime: navigation.responseStart
|
1011
|
-
responseTime: navigation.responseEnd
|
1012
|
-
|
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 ||
|
1015
|
-
firstContentfulPaint: paint.find(p => p.name === 'first-contentful-paint')?.startTime ||
|
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"
|
1040
|
-
"dom_content_loaded": additional_metrics.get("navigation", {}).get("domContentLoaded"
|
1041
|
-
"first_paint": additional_metrics.get("paint", {}).get("firstPaint"
|
1042
|
-
"first_contentful_paint": additional_metrics.get("paint", {}).get("firstContentfulPaint"
|
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
|
-
|
15
|
-
import
|
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:
|
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:
|
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:
|
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:
|
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
|
-
|
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
|
-
|
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):
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: cursorflow
|
3
|
-
Version: 1.3.
|
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"
|
@@ -5,7 +5,7 @@ cursorflow/install_cursorflow_rules.py,sha256=ny4c-S1O-V2sITunZO2kk6sCf1yec42UJO
|
|
5
5
|
cursorflow/updater.py,sha256=pvbSZgg6hgWZBE5AAUPppS5Yzv0yNMp2X_CL2GALg_w,18783
|
6
6
|
cursorflow/core/agent.py,sha256=f3lecgEzDRDdGTVccAtorpLGfNJJ49bbsQAmgr0vNGg,10136
|
7
7
|
cursorflow/core/auth_handler.py,sha256=oRafO6ZdxoHryBIvHsrNV8TECed4GXpJsdEiH0KdPPk,17149
|
8
|
-
cursorflow/core/browser_controller.py,sha256=
|
8
|
+
cursorflow/core/browser_controller.py,sha256=fqbCAXyPXY8_k1gNNwETjNVVPfLSNdGLOcGvpHUkMV4,59333
|
9
9
|
cursorflow/core/browser_engine.py,sha256=Glq8U_bXRkO2Yal0nku7Z2wKwOftY4So2XN4oy56WLo,13732
|
10
10
|
cursorflow/core/css_iterator.py,sha256=whLCIwbHZEWaH1HCbmqhNX5zrh_fL-r3hsxKjYsukcE,16478
|
11
11
|
cursorflow/core/cursor_integration.py,sha256=MAeHjXYeqzaXnhskqkTDB-n5ixIHqapGe93X0lLKhws,67501
|
@@ -15,17 +15,17 @@ cursorflow/core/event_correlator.py,sha256=lCzXFnii17AQt3rOVOclez_AnjvBCF_2ZlnFG
|
|
15
15
|
cursorflow/core/file_change_monitor.py,sha256=tl_ZdsGW8LZBTImniD-lqaGgNb9dYjlDsP2XmevE0xk,21258
|
16
16
|
cursorflow/core/log_collector.py,sha256=vZ2Slm6C_OOi68xROWpXPSZqzzQAWK0sk5xEybGlA4w,14217
|
17
17
|
cursorflow/core/log_monitor.py,sha256=pONMu_JHEnT0T62OA5KRZ4nClzKgNpifPyrfN5w_RM8,6704
|
18
|
-
cursorflow/core/mockup_comparator.py,sha256=
|
18
|
+
cursorflow/core/mockup_comparator.py,sha256=VLfEEaTgbcY0oTb-bhBD2uTbXGjbnOV7LXNVvXpWMrM,64695
|
19
19
|
cursorflow/core/persistent_session.py,sha256=FsEHj4wKkycmdp6PFRHv3g333Y74yqra0x_qhUTQpik,36075
|
20
20
|
cursorflow/core/report_generator.py,sha256=-vosfyrnfVyWDbAIMlMurl90xOXqBae8d6aLd9sEqiY,10113
|
21
|
-
cursorflow/log_sources/local_file.py,sha256=
|
22
|
-
cursorflow/log_sources/ssh_remote.py,sha256=
|
21
|
+
cursorflow/log_sources/local_file.py,sha256=Hn3hDQjAqgNBQN8b5AbC6IQAzgY_ekLgePwDuq9wBlA,7101
|
22
|
+
cursorflow/log_sources/ssh_remote.py,sha256=zSnX8mpa5G86UDWEm3_FqYwo1PjBW67C1LPkJ5Ta_ho,7482
|
23
23
|
cursorflow/rules/__init__.py,sha256=gPcA-IkhXj03sl7cvZV0wwo7CtEkcyuKs4y0F5oQbqE,458
|
24
24
|
cursorflow/rules/cursorflow-installation.mdc,sha256=Nnu7PuP5nGD6hGAV_x2SX5Y2_uPz3oNA5XUjNJBFC1M,6985
|
25
25
|
cursorflow/rules/cursorflow-usage.mdc,sha256=Zv0TWuo7S3iJHq8R6oBaHRI3V6h-T81pdb7vi-b8NoU,14157
|
26
|
-
cursorflow-1.3.
|
27
|
-
cursorflow-1.3.
|
28
|
-
cursorflow-1.3.
|
29
|
-
cursorflow-1.3.
|
30
|
-
cursorflow-1.3.
|
31
|
-
cursorflow-1.3.
|
26
|
+
cursorflow-1.3.7.dist-info/licenses/LICENSE,sha256=e4QbjAsj3bW-xgQOvQelr8sGLYDoqc48k6cKgCr_pBU,1080
|
27
|
+
cursorflow-1.3.7.dist-info/METADATA,sha256=ViqtojUrMloBzdc_FQgTysm-OJkeLVnyw3rRBO7AD-Y,7716
|
28
|
+
cursorflow-1.3.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
29
|
+
cursorflow-1.3.7.dist-info/entry_points.txt,sha256=-Ed_n4Uff7wClEtWS-Py6xmQabecB9f0QAOjX0w7ljA,51
|
30
|
+
cursorflow-1.3.7.dist-info/top_level.txt,sha256=t1UZwRyZP4u-ng2CEcNHmk_ZT4ibQxoihB2IjTF7ovc,11
|
31
|
+
cursorflow-1.3.7.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|