cursorflow 2.1.5__py3-none-any.whl → 2.2.0__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/__init__.py +1 -1
- cursorflow/auto_init.py +17 -13
- cursorflow/cli.py +389 -23
- cursorflow/core/action_validator.py +199 -0
- cursorflow/core/browser_controller.py +300 -5
- cursorflow/core/browser_engine.py +13 -0
- cursorflow/core/config_validator.py +216 -0
- cursorflow/core/cursorflow.py +68 -32
- cursorflow/install_cursorflow_rules.py +14 -11
- cursorflow/log_sources/local_file.py +20 -1
- cursorflow/log_sources/ssh_remote.py +19 -0
- cursorflow/rules/cursorflow-installation.mdc +2 -1
- cursorflow/rules/cursorflow-usage.mdc +7 -1
- cursorflow/updater.py +4 -3
- {cursorflow-2.1.5.dist-info → cursorflow-2.2.0.dist-info}/METADATA +70 -15
- {cursorflow-2.1.5.dist-info → cursorflow-2.2.0.dist-info}/RECORD +20 -18
- {cursorflow-2.1.5.dist-info → cursorflow-2.2.0.dist-info}/WHEEL +0 -0
- {cursorflow-2.1.5.dist-info → cursorflow-2.2.0.dist-info}/entry_points.txt +0 -0
- {cursorflow-2.1.5.dist-info → cursorflow-2.2.0.dist-info}/licenses/LICENSE +0 -0
- {cursorflow-2.1.5.dist-info → cursorflow-2.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,216 @@
|
|
1
|
+
"""
|
2
|
+
Configuration Validation
|
3
|
+
|
4
|
+
Validates user-provided configuration against Playwright API.
|
5
|
+
Provides clear error messages with links to documentation.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from typing import Dict, Any, Set
|
9
|
+
import logging
|
10
|
+
|
11
|
+
|
12
|
+
class ConfigValidationError(Exception):
|
13
|
+
"""Raised when configuration is invalid"""
|
14
|
+
pass
|
15
|
+
|
16
|
+
|
17
|
+
class ConfigValidator:
|
18
|
+
"""
|
19
|
+
Validates CursorFlow and Playwright configuration
|
20
|
+
|
21
|
+
Strategy: We don't strictly validate - we warn about likely errors
|
22
|
+
and let Playwright do final validation. This keeps us forward-compatible.
|
23
|
+
"""
|
24
|
+
|
25
|
+
# Common browser launch options (for helpful warnings)
|
26
|
+
# See: https://playwright.dev/python/docs/api/class-browsertype#browser-type-launch
|
27
|
+
KNOWN_BROWSER_OPTIONS = {
|
28
|
+
'args', 'channel', 'chromium_sandbox', 'devtools', 'downloads_path',
|
29
|
+
'env', 'executable_path', 'firefox_user_prefs', 'handle_sigint',
|
30
|
+
'handle_sigterm', 'handle_sighup', 'headless', 'ignore_default_args',
|
31
|
+
'proxy', 'slow_mo', 'timeout', 'traces_dir'
|
32
|
+
}
|
33
|
+
|
34
|
+
# Common context options (for helpful warnings)
|
35
|
+
# See: https://playwright.dev/python/docs/api/class-browser#browser-new-context
|
36
|
+
KNOWN_CONTEXT_OPTIONS = {
|
37
|
+
'accept_downloads', 'base_url', 'bypass_csp', 'color_scheme',
|
38
|
+
'device_scale_factor', 'extra_http_headers', 'forced_colors',
|
39
|
+
'geolocation', 'has_touch', 'http_credentials', 'ignore_https_errors',
|
40
|
+
'is_mobile', 'java_script_enabled', 'locale', 'no_viewport',
|
41
|
+
'offline', 'permissions', 'proxy', 'record_har_content',
|
42
|
+
'record_har_mode', 'record_har_omit_content', 'record_har_path',
|
43
|
+
'record_har_url_filter', 'record_video_dir', 'record_video_size',
|
44
|
+
'reduced_motion', 'screen', 'service_workers', 'storage_state',
|
45
|
+
'strict_selectors', 'timezone_id', 'user_agent', 'viewport'
|
46
|
+
}
|
47
|
+
|
48
|
+
@classmethod
|
49
|
+
def validate_browser_options(cls, options: Dict[str, Any]) -> Dict[str, Any]:
|
50
|
+
"""
|
51
|
+
Validate browser launch options
|
52
|
+
|
53
|
+
Args:
|
54
|
+
options: User-provided browser options
|
55
|
+
|
56
|
+
Returns:
|
57
|
+
Validated options (unchanged - just warnings logged)
|
58
|
+
"""
|
59
|
+
logger = logging.getLogger(__name__)
|
60
|
+
|
61
|
+
# Warn about unknown options (might be typos)
|
62
|
+
for key in options.keys():
|
63
|
+
if key not in cls.KNOWN_BROWSER_OPTIONS:
|
64
|
+
logger.warning(
|
65
|
+
f"Unknown browser option '{key}' - will pass to Playwright anyway. "
|
66
|
+
f"Check spelling or see: https://playwright.dev/python/docs/api/class-browsertype#browser-type-launch"
|
67
|
+
)
|
68
|
+
|
69
|
+
# Validate specific option types
|
70
|
+
if 'headless' in options and not isinstance(options['headless'], bool):
|
71
|
+
raise ConfigValidationError(
|
72
|
+
f"'headless' must be boolean, got {type(options['headless']).__name__}: {options['headless']}"
|
73
|
+
)
|
74
|
+
|
75
|
+
if 'timeout' in options and not isinstance(options['timeout'], (int, float)):
|
76
|
+
raise ConfigValidationError(
|
77
|
+
f"'timeout' must be number, got {type(options['timeout']).__name__}: {options['timeout']}"
|
78
|
+
)
|
79
|
+
|
80
|
+
if 'args' in options and not isinstance(options['args'], list):
|
81
|
+
raise ConfigValidationError(
|
82
|
+
f"'args' must be list of strings, got {type(options['args']).__name__}"
|
83
|
+
)
|
84
|
+
|
85
|
+
return options
|
86
|
+
|
87
|
+
@classmethod
|
88
|
+
def validate_context_options(cls, options: Dict[str, Any]) -> Dict[str, Any]:
|
89
|
+
"""
|
90
|
+
Validate browser context options
|
91
|
+
|
92
|
+
Args:
|
93
|
+
options: User-provided context options
|
94
|
+
|
95
|
+
Returns:
|
96
|
+
Validated options (unchanged - just warnings logged)
|
97
|
+
"""
|
98
|
+
logger = logging.getLogger(__name__)
|
99
|
+
|
100
|
+
# Warn about unknown options
|
101
|
+
for key in options.keys():
|
102
|
+
if key not in cls.KNOWN_CONTEXT_OPTIONS:
|
103
|
+
logger.warning(
|
104
|
+
f"Unknown context option '{key}' - will pass to Playwright anyway. "
|
105
|
+
f"Check spelling or see: https://playwright.dev/python/docs/api/class-browser#browser-new-context"
|
106
|
+
)
|
107
|
+
|
108
|
+
# Validate specific option types
|
109
|
+
if 'viewport' in options:
|
110
|
+
viewport = options['viewport']
|
111
|
+
if not isinstance(viewport, dict):
|
112
|
+
raise ConfigValidationError(
|
113
|
+
f"'viewport' must be dict with width/height, got {type(viewport).__name__}"
|
114
|
+
)
|
115
|
+
if 'width' in viewport and not isinstance(viewport['width'], int):
|
116
|
+
raise ConfigValidationError(
|
117
|
+
f"viewport width must be integer, got {type(viewport['width']).__name__}"
|
118
|
+
)
|
119
|
+
if 'height' in viewport and not isinstance(viewport['height'], int):
|
120
|
+
raise ConfigValidationError(
|
121
|
+
f"viewport height must be integer, got {type(viewport['height']).__name__}"
|
122
|
+
)
|
123
|
+
|
124
|
+
if 'geolocation' in options:
|
125
|
+
geo = options['geolocation']
|
126
|
+
if not isinstance(geo, dict) or 'latitude' not in geo or 'longitude' not in geo:
|
127
|
+
raise ConfigValidationError(
|
128
|
+
f"'geolocation' must be dict with latitude/longitude: "
|
129
|
+
f"{{'latitude': 40.7128, 'longitude': -74.0060}}"
|
130
|
+
)
|
131
|
+
|
132
|
+
if 'timezone_id' in options and not isinstance(options['timezone_id'], str):
|
133
|
+
raise ConfigValidationError(
|
134
|
+
f"'timezone_id' must be string like 'America/New_York', got {type(options['timezone_id']).__name__}"
|
135
|
+
)
|
136
|
+
|
137
|
+
return options
|
138
|
+
|
139
|
+
@classmethod
|
140
|
+
def get_config_examples(cls) -> str:
|
141
|
+
"""Get example configurations for documentation"""
|
142
|
+
return """
|
143
|
+
Browser Configuration Examples:
|
144
|
+
|
145
|
+
Enable DevTools (non-headless):
|
146
|
+
{
|
147
|
+
"headless": false,
|
148
|
+
"browser_launch_options": {
|
149
|
+
"devtools": true
|
150
|
+
}
|
151
|
+
}
|
152
|
+
|
153
|
+
Use specific Chrome channel:
|
154
|
+
{
|
155
|
+
"browser_launch_options": {
|
156
|
+
"channel": "chrome"
|
157
|
+
}
|
158
|
+
}
|
159
|
+
|
160
|
+
Custom proxy:
|
161
|
+
{
|
162
|
+
"browser_launch_options": {
|
163
|
+
"proxy": {
|
164
|
+
"server": "http://myproxy.com:3128",
|
165
|
+
"username": "user",
|
166
|
+
"password": "pass"
|
167
|
+
}
|
168
|
+
}
|
169
|
+
}
|
170
|
+
|
171
|
+
Context Configuration Examples:
|
172
|
+
|
173
|
+
Test in dark mode:
|
174
|
+
{
|
175
|
+
"context_options": {
|
176
|
+
"color_scheme": "dark"
|
177
|
+
}
|
178
|
+
}
|
179
|
+
|
180
|
+
Test with geolocation:
|
181
|
+
{
|
182
|
+
"context_options": {
|
183
|
+
"geolocation": {"latitude": 40.7128, "longitude": -74.0060},
|
184
|
+
"permissions": ["geolocation"]
|
185
|
+
}
|
186
|
+
}
|
187
|
+
|
188
|
+
Test offline behavior:
|
189
|
+
{
|
190
|
+
"context_options": {
|
191
|
+
"offline": true
|
192
|
+
}
|
193
|
+
}
|
194
|
+
|
195
|
+
Custom timezone:
|
196
|
+
{
|
197
|
+
"context_options": {
|
198
|
+
"timezone_id": "America/Los_Angeles"
|
199
|
+
}
|
200
|
+
}
|
201
|
+
|
202
|
+
HTTP authentication:
|
203
|
+
{
|
204
|
+
"context_options": {
|
205
|
+
"http_credentials": {
|
206
|
+
"username": "admin",
|
207
|
+
"password": "secret"
|
208
|
+
}
|
209
|
+
}
|
210
|
+
}
|
211
|
+
|
212
|
+
See Playwright documentation for all available options:
|
213
|
+
Browser: https://playwright.dev/python/docs/api/class-browsertype#browser-type-launch
|
214
|
+
Context: https://playwright.dev/python/docs/api/class-browser#browser-new-context
|
215
|
+
"""
|
216
|
+
|
cursorflow/core/cursorflow.py
CHANGED
@@ -669,6 +669,15 @@ class CursorFlow:
|
|
669
669
|
|
670
670
|
async def _execute_actions(self, actions: List[Dict]) -> bool:
|
671
671
|
"""Execute list of declarative actions"""
|
672
|
+
# Validate all actions before execution
|
673
|
+
from .action_validator import ActionValidator, ActionValidationError
|
674
|
+
|
675
|
+
try:
|
676
|
+
actions = ActionValidator.validate_list(actions)
|
677
|
+
except ActionValidationError as e:
|
678
|
+
self.logger.error(f"Action validation failed: {e}")
|
679
|
+
return False
|
680
|
+
|
672
681
|
try:
|
673
682
|
for action in actions:
|
674
683
|
await self._execute_single_action(action)
|
@@ -678,44 +687,26 @@ class CursorFlow:
|
|
678
687
|
return False
|
679
688
|
|
680
689
|
async def _execute_single_action(self, action: Dict):
|
681
|
-
"""
|
690
|
+
"""
|
691
|
+
Execute a single declarative action and track it
|
692
|
+
|
693
|
+
Pass-through architecture: CursorFlow handles a few special actions,
|
694
|
+
then passes everything else directly to Playwright Page API.
|
695
|
+
"""
|
682
696
|
action_start = time.time()
|
683
697
|
|
684
698
|
try:
|
685
|
-
#
|
686
|
-
|
699
|
+
# Extract action type
|
700
|
+
action_type = action.get('type') or list(action.keys())[0]
|
701
|
+
|
702
|
+
# CursorFlow-specific actions (not direct Playwright methods)
|
703
|
+
if action_type == "navigate":
|
687
704
|
url = action["navigate"]
|
688
705
|
if isinstance(url, dict):
|
689
706
|
url = url["url"]
|
690
707
|
await self.browser.navigate(url)
|
691
708
|
|
692
|
-
|
693
|
-
elif "click" in action:
|
694
|
-
selector = action["click"]
|
695
|
-
if isinstance(selector, dict):
|
696
|
-
selector = selector["selector"]
|
697
|
-
await self.browser.click(selector)
|
698
|
-
|
699
|
-
elif "fill" in action:
|
700
|
-
config = action["fill"]
|
701
|
-
await self.browser.fill(config["selector"], config["value"])
|
702
|
-
|
703
|
-
elif "type" in action:
|
704
|
-
config = action["type"]
|
705
|
-
await self.browser.type(config["selector"], config["text"])
|
706
|
-
|
707
|
-
# Waiting actions
|
708
|
-
elif "wait" in action:
|
709
|
-
await asyncio.sleep(action["wait"])
|
710
|
-
|
711
|
-
elif "wait_for" in action:
|
712
|
-
selector = action["wait_for"]
|
713
|
-
if isinstance(selector, dict):
|
714
|
-
selector = selector["selector"]
|
715
|
-
await self.browser.wait_for_element(selector)
|
716
|
-
|
717
|
-
# Capture actions
|
718
|
-
elif "screenshot" in action:
|
709
|
+
elif action_type == "screenshot":
|
719
710
|
screenshot_config = action["screenshot"]
|
720
711
|
|
721
712
|
# Handle both string and dict formats
|
@@ -734,15 +725,19 @@ class CursorFlow:
|
|
734
725
|
screenshot_data = await self.browser.screenshot(name, options)
|
735
726
|
self.artifacts["screenshots"].append(screenshot_data)
|
736
727
|
|
737
|
-
elif "authenticate"
|
728
|
+
elif action_type == "authenticate":
|
738
729
|
if self.auth_handler:
|
739
730
|
await self.auth_handler.authenticate(self.browser.page, action["authenticate"])
|
731
|
+
|
732
|
+
# Pass-through to Playwright Page API (hover, dblclick, press, etc.)
|
733
|
+
else:
|
734
|
+
await self._execute_playwright_action(action_type, action)
|
740
735
|
|
741
736
|
# Record action in timeline
|
742
737
|
self.timeline.append({
|
743
738
|
"timestamp": action_start,
|
744
739
|
"type": "browser",
|
745
|
-
"event":
|
740
|
+
"event": action_type,
|
746
741
|
"data": action,
|
747
742
|
"duration": time.time() - action_start
|
748
743
|
})
|
@@ -751,6 +746,47 @@ class CursorFlow:
|
|
751
746
|
self.logger.error(f"Action failed: {action}, error: {e}")
|
752
747
|
raise
|
753
748
|
|
749
|
+
async def _execute_playwright_action(self, action_type: str, action: Dict):
|
750
|
+
"""
|
751
|
+
Pass-through to Playwright Page API
|
752
|
+
|
753
|
+
Enables using any Playwright method without hardcoding them:
|
754
|
+
- hover, dblclick, drag_and_drop
|
755
|
+
- press, focus, blur, check, uncheck
|
756
|
+
- evaluate, route, expose_function
|
757
|
+
- And 90+ more methods
|
758
|
+
|
759
|
+
See: https://playwright.dev/python/docs/api/class-page
|
760
|
+
"""
|
761
|
+
# Get the action config
|
762
|
+
action_config = action.get(action_type)
|
763
|
+
|
764
|
+
# Check if method exists on Playwright Page
|
765
|
+
if hasattr(self.browser.page, action_type):
|
766
|
+
method = getattr(self.browser.page, action_type)
|
767
|
+
|
768
|
+
# Call with appropriate args based on config format
|
769
|
+
if isinstance(action_config, str):
|
770
|
+
# Simple format: {"hover": ".selector"}
|
771
|
+
await method(action_config)
|
772
|
+
elif isinstance(action_config, dict):
|
773
|
+
# Config format: {"hover": {"selector": ".item", "timeout": 5000}}
|
774
|
+
await method(**action_config)
|
775
|
+
elif action_config is None:
|
776
|
+
# No args: {"reload": null}
|
777
|
+
await method()
|
778
|
+
else:
|
779
|
+
# Numeric or other: {"wait": 2}
|
780
|
+
await method(action_config)
|
781
|
+
else:
|
782
|
+
# Method doesn't exist on Page - provide helpful error
|
783
|
+
available_methods = [m for m in dir(self.browser.page) if not m.startswith('_') and callable(getattr(self.browser.page, m))]
|
784
|
+
raise AttributeError(
|
785
|
+
f"Unknown Playwright action: '{action_type}'\n"
|
786
|
+
f"Available methods: {', '.join(sorted(available_methods[:20]))}...\n"
|
787
|
+
f"Full API: https://playwright.dev/python/docs/api/class-page"
|
788
|
+
)
|
789
|
+
|
754
790
|
async def _cleanup_session(self, session_options: Dict):
|
755
791
|
"""Clean up browser session"""
|
756
792
|
try:
|
@@ -80,10 +80,9 @@ def install_cursorflow_rules(project_dir: str = ".", force: bool = False):
|
|
80
80
|
setup_update_checking(project_path)
|
81
81
|
|
82
82
|
print(f"\n📝 Next steps:")
|
83
|
-
print(f" 1. Review cursorflow
|
84
|
-
print(f" 2. Install
|
85
|
-
print(f" 3.
|
86
|
-
print(f" 4. Start using CursorFlow for UI testing and CSS iteration!")
|
83
|
+
print(f" 1. Review .cursorflow/config.json and update for your project")
|
84
|
+
print(f" 2. Install Playwright: playwright install chromium")
|
85
|
+
print(f" 3. Start using CursorFlow for UI testing and CSS iteration!")
|
87
86
|
print(f"\n🔄 Update commands:")
|
88
87
|
print(f" - Check for updates: python -m cursorflow check-updates")
|
89
88
|
print(f" - Update CursorFlow: python -m cursorflow update")
|
@@ -120,18 +119,22 @@ cursorflow_session_*.json
|
|
120
119
|
def create_config_template(project_path: Path, force: bool = False):
|
121
120
|
"""Create or update CursorFlow configuration template"""
|
122
121
|
|
123
|
-
|
122
|
+
# Create .cursorflow directory if it doesn't exist
|
123
|
+
cursorflow_dir = project_path / ".cursorflow"
|
124
|
+
cursorflow_dir.mkdir(exist_ok=True)
|
125
|
+
|
126
|
+
config_path = cursorflow_dir / "config.json"
|
124
127
|
|
125
128
|
# Get current version
|
126
129
|
try:
|
127
130
|
import cursorflow
|
128
|
-
current_version = getattr(cursorflow, '__version__', '2.
|
131
|
+
current_version = getattr(cursorflow, '__version__', '2.2.0')
|
129
132
|
except ImportError:
|
130
|
-
current_version = '2.
|
133
|
+
current_version = '2.2.0'
|
131
134
|
|
132
135
|
if config_path.exists():
|
133
136
|
if not force:
|
134
|
-
print("ℹ️ cursorflow
|
137
|
+
print("ℹ️ .cursorflow/config.json already exists (use --force to recreate)")
|
135
138
|
# Smart update: only update version and add missing fields
|
136
139
|
try:
|
137
140
|
with open(config_path) as f:
|
@@ -198,7 +201,7 @@ def create_config_template(project_path: Path, force: bool = False):
|
|
198
201
|
json.dump(config_template, f, indent=2)
|
199
202
|
|
200
203
|
action = "Recreated" if force else "Created"
|
201
|
-
print(f"✅ {action} configuration
|
204
|
+
print(f"✅ {action} configuration: .cursorflow/config.json")
|
202
205
|
print(f" Detected project type: {project_type}")
|
203
206
|
print(f" CursorFlow version: {current_version}")
|
204
207
|
|
@@ -302,9 +305,9 @@ def setup_update_checking(project_path: Path):
|
|
302
305
|
# Create initial version tracking
|
303
306
|
try:
|
304
307
|
import cursorflow
|
305
|
-
current_version = getattr(cursorflow, '__version__', '2.
|
308
|
+
current_version = getattr(cursorflow, '__version__', '2.2.0')
|
306
309
|
except ImportError:
|
307
|
-
current_version = '2.
|
310
|
+
current_version = '2.2.0'
|
308
311
|
|
309
312
|
version_info = {
|
310
313
|
"installed_version": current_version,
|
@@ -168,8 +168,27 @@ class LocalFileLogSource:
|
|
168
168
|
"""Connect to log sources - compatibility method for log_collector"""
|
169
169
|
return await self.start_monitoring()
|
170
170
|
|
171
|
+
async def get_new_entries(self) -> List[Dict]:
|
172
|
+
"""
|
173
|
+
Get new log entries since last call (required interface method)
|
174
|
+
|
175
|
+
Returns:
|
176
|
+
List of log entry dicts with timestamp, content, source
|
177
|
+
"""
|
178
|
+
new_entries = []
|
179
|
+
|
180
|
+
# Drain queue of all available entries
|
181
|
+
while not self.log_queue.empty():
|
182
|
+
try:
|
183
|
+
log_entry = self.log_queue.get_nowait()
|
184
|
+
new_entries.append(log_entry)
|
185
|
+
except queue.Empty:
|
186
|
+
break
|
187
|
+
|
188
|
+
return new_entries
|
189
|
+
|
171
190
|
def get_recent_logs(self, seconds: int = 10) -> List[Dict]:
|
172
|
-
"""Get logs from the last N seconds"""
|
191
|
+
"""Get logs from the last N seconds (non-destructive)"""
|
173
192
|
from datetime import timedelta
|
174
193
|
|
175
194
|
cutoff_time = datetime.now() - timedelta(seconds=seconds)
|
@@ -165,6 +165,25 @@ class SSHRemoteLogSource:
|
|
165
165
|
"""Connect to SSH log sources - compatibility method for log_collector"""
|
166
166
|
return await self.start_monitoring()
|
167
167
|
|
168
|
+
async def get_new_entries(self) -> List[Dict]:
|
169
|
+
"""
|
170
|
+
Get new log entries since last call (required interface method)
|
171
|
+
|
172
|
+
Returns:
|
173
|
+
List of log entry dicts with timestamp, content, source
|
174
|
+
"""
|
175
|
+
new_entries = []
|
176
|
+
|
177
|
+
# Drain queue of all available entries
|
178
|
+
while not self.log_queue.empty():
|
179
|
+
try:
|
180
|
+
log_entry = self.log_queue.get_nowait()
|
181
|
+
new_entries.append(log_entry)
|
182
|
+
except queue.Empty:
|
183
|
+
break
|
184
|
+
|
185
|
+
return new_entries
|
186
|
+
|
168
187
|
def get_recent_logs(self, seconds: int = 10) -> List[Dict]:
|
169
188
|
"""Get logs from the last N seconds without stopping monitoring"""
|
170
189
|
|
@@ -1,6 +1,7 @@
|
|
1
1
|
---
|
2
2
|
title: CursorFlow Installation & Setup Guide for Cursor
|
3
3
|
description: How to install and configure CursorFlow for complete page intelligence in AI-driven development
|
4
|
+
alwaysApply: true
|
4
5
|
---
|
5
6
|
|
6
7
|
# CursorFlow Installation & Setup Guide for Cursor
|
@@ -24,7 +25,7 @@ CursorFlow requires **TWO installations**:
|
|
24
25
|
This step created THIS FILE you're reading right now.
|
25
26
|
|
26
27
|
**If Cursor reports "command not found: cursorflow"**, the user needs Step 1.
|
27
|
-
**If you can run cursorflow but tests fail**, check
|
28
|
+
**If you can run cursorflow but tests fail**, check `.cursorflow/config.json` configuration.
|
28
29
|
|
29
30
|
## 🚀 **When to Install CursorFlow**
|
30
31
|
|
@@ -1,6 +1,12 @@
|
|
1
|
+
---
|
2
|
+
title: CursorFlow CLI Usage Guide for Cursor AI
|
3
|
+
description: Complete guide for using CursorFlow CLI with comprehensive page intelligence and data collection
|
4
|
+
alwaysApply: true
|
5
|
+
---
|
6
|
+
|
1
7
|
# CursorFlow Usage Rules for Cursor AI
|
2
8
|
|
3
|
-
## 🔥 **CursorFlow
|
9
|
+
## 🔥 **CursorFlow: Complete Page Intelligence for AI-Driven Development**
|
4
10
|
|
5
11
|
**CursorFlow provides comprehensive data collection for rapid UI iteration:**
|
6
12
|
|
cursorflow/updater.py
CHANGED
@@ -363,7 +363,7 @@ class CursorFlowUpdater:
|
|
363
363
|
|
364
364
|
async def _migrate_configuration(self) -> bool:
|
365
365
|
"""Migrate configuration to new format if needed"""
|
366
|
-
config_file = self.project_dir / "cursorflow
|
366
|
+
config_file = self.project_dir / ".cursorflow" / "config.json"
|
367
367
|
|
368
368
|
if not config_file.exists():
|
369
369
|
return True
|
@@ -417,9 +417,10 @@ class CursorFlowUpdater:
|
|
417
417
|
backup_path = backup_dir / backup_name
|
418
418
|
|
419
419
|
# Backup configuration and rules
|
420
|
-
|
420
|
+
config_file = self.project_dir / ".cursorflow" / "config.json"
|
421
|
+
if config_file.exists():
|
421
422
|
shutil.copy2(
|
422
|
-
|
423
|
+
config_file,
|
423
424
|
backup_path.with_suffix('.config.json')
|
424
425
|
)
|
425
426
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: cursorflow
|
3
|
-
Version: 2.
|
3
|
+
Version: 2.2.0
|
4
4
|
Summary: 🔥 Complete page intelligence for AI-driven development with Hot Reload Intelligence - captures DOM, network, console, performance, HMR events, and comprehensive page analysis
|
5
5
|
Author-email: GeekWarrior Development <rbush@cooltheory.com>
|
6
6
|
License-Expression: MIT
|
@@ -82,20 +82,57 @@ When CursorFlow reports `"average_response_time": 416.58ms`, you can tell stakeh
|
|
82
82
|
|
83
83
|
**Documentary vs Movie:** Both are valuable, but if you're trying to understand reality, you watch the documentary. CursorFlow is the documentary of web testing.
|
84
84
|
|
85
|
+
## 🎯 Pass-Through Architecture
|
86
|
+
|
87
|
+
CursorFlow doesn't limit you - it exposes the full power of Playwright:
|
88
|
+
|
89
|
+
**94+ Playwright actions available:**
|
90
|
+
```bash
|
91
|
+
# Any Playwright Page method works
|
92
|
+
cursorflow test --actions '[
|
93
|
+
{"hover": ".menu"},
|
94
|
+
{"dblclick": ".editable"},
|
95
|
+
{"press": "Enter"},
|
96
|
+
{"drag_and_drop": {"source": ".item", "target": ".zone"}},
|
97
|
+
{"check": "#checkbox"},
|
98
|
+
{"evaluate": "window.scrollTo(0, 500)"}
|
99
|
+
]'
|
100
|
+
```
|
101
|
+
|
102
|
+
**Full configuration pass-through:**
|
103
|
+
```json
|
104
|
+
{
|
105
|
+
"browser_config": {
|
106
|
+
"browser_launch_options": {"devtools": true, "channel": "chrome"}
|
107
|
+
},
|
108
|
+
"context_options": {
|
109
|
+
"color_scheme": "dark",
|
110
|
+
"geolocation": {"latitude": 40.7128, "longitude": -74.0060},
|
111
|
+
"timezone_id": "America/Los_Angeles"
|
112
|
+
}
|
113
|
+
}
|
114
|
+
```
|
115
|
+
|
116
|
+
**Forward-compatible:** New Playwright features work immediately without CursorFlow updates.
|
117
|
+
|
118
|
+
**See:** [Playwright API Documentation](https://playwright.dev/python/docs/api/class-page)
|
119
|
+
|
120
|
+
---
|
121
|
+
|
85
122
|
## 🚀 Complete Page Intelligence
|
86
123
|
|
87
|
-
Every
|
124
|
+
Every test captures everything needed for debugging:
|
88
125
|
|
89
126
|
### **📊 Comprehensive Data Collection**
|
90
|
-
- **DOM**: All elements with 7 selector strategies
|
91
|
-
- **Network**:
|
92
|
-
- **Console**: All logs, errors,
|
93
|
-
- **
|
94
|
-
- **
|
95
|
-
- **
|
96
|
-
- **
|
97
|
-
- **
|
98
|
-
- **
|
127
|
+
- **DOM**: All elements with 7 selector strategies + event handlers
|
128
|
+
- **Network**: Requests, responses, and complete request/response bodies
|
129
|
+
- **Console**: All logs, errors, warnings - displayed prominently
|
130
|
+
- **JavaScript**: Global functions, variables, specific window objects
|
131
|
+
- **Storage**: localStorage, sessionStorage, cookies (sensitive data masked)
|
132
|
+
- **Forms**: All field values at capture time (passwords masked)
|
133
|
+
- **Performance**: Load times, memory usage, reliability indicators
|
134
|
+
- **Visual**: Screenshots with comprehensive page analysis
|
135
|
+
- **Sessions**: Save/restore browser state for authenticated testing
|
99
136
|
|
100
137
|
### **🔄 Hot Reload Intelligence**
|
101
138
|
- **Framework auto-detection** (Vite, Webpack, Next.js, Parcel, Laravel Mix)
|
@@ -148,20 +185,38 @@ playwright install chromium
|
|
148
185
|
```bash
|
149
186
|
cd /path/to/your/project
|
150
187
|
cursorflow install-rules
|
188
|
+
|
189
|
+
# Or skip prompts for automation/CI
|
190
|
+
cursorflow install-rules --yes
|
151
191
|
```
|
152
192
|
|
153
193
|
This creates:
|
154
194
|
- `.cursor/rules/` - Cursor AI integration rules
|
155
|
-
-
|
195
|
+
- `.cursorflow/config.json` - Project-specific configuration
|
156
196
|
- `.cursorflow/` - Artifacts and session storage
|
157
197
|
- `.gitignore` entries for CursorFlow artifacts
|
158
198
|
|
159
199
|
### Step 3: Start Testing
|
200
|
+
|
201
|
+
**Simple page capture:**
|
160
202
|
```bash
|
161
|
-
|
162
|
-
|
203
|
+
cursorflow test --base-url http://localhost:3000 --path /dashboard
|
204
|
+
```
|
163
205
|
|
164
|
-
|
206
|
+
**Interactive testing with inline actions:**
|
207
|
+
```bash
|
208
|
+
cursorflow test --base-url http://localhost:3000 \
|
209
|
+
--path /messages \
|
210
|
+
--wait-for ".message-item" \
|
211
|
+
--hover ".message-item:first-child" \
|
212
|
+
--click ".message-item:first-child" \
|
213
|
+
--screenshot "clicked" \
|
214
|
+
--show-console \
|
215
|
+
--open-trace
|
216
|
+
```
|
217
|
+
|
218
|
+
**Custom actions with JSON:**
|
219
|
+
```bash
|
165
220
|
cursorflow test --base-url http://localhost:3000 --actions '[
|
166
221
|
{"navigate": "/login"},
|
167
222
|
{"fill": {"selector": "#email", "value": "test@example.com"}},
|