cursorflow 2.1.6__py3-none-any.whl → 2.2.1__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/cli.py +371 -11
- cursorflow/core/action_validator.py +199 -0
- cursorflow/core/browser_controller.py +325 -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 +4 -4
- cursorflow/log_sources/local_file.py +20 -1
- cursorflow/log_sources/ssh_remote.py +19 -0
- cursorflow/rules/cursorflow-installation.mdc +1 -0
- cursorflow/rules/cursorflow-usage.mdc +7 -1
- {cursorflow-2.1.6.dist-info → cursorflow-2.2.1.dist-info}/METADATA +66 -14
- {cursorflow-2.1.6.dist-info → cursorflow-2.2.1.dist-info}/RECORD +18 -16
- {cursorflow-2.1.6.dist-info → cursorflow-2.2.1.dist-info}/WHEEL +0 -0
- {cursorflow-2.1.6.dist-info → cursorflow-2.2.1.dist-info}/entry_points.txt +0 -0
- {cursorflow-2.1.6.dist-info → cursorflow-2.2.1.dist-info}/licenses/LICENSE +0 -0
- {cursorflow-2.1.6.dist-info → cursorflow-2.2.1.dist-info}/top_level.txt +0 -0
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:
|
@@ -128,9 +128,9 @@ def create_config_template(project_path: Path, force: bool = False):
|
|
128
128
|
# Get current version
|
129
129
|
try:
|
130
130
|
import cursorflow
|
131
|
-
current_version = getattr(cursorflow, '__version__', '2.
|
131
|
+
current_version = getattr(cursorflow, '__version__', '2.2.0')
|
132
132
|
except ImportError:
|
133
|
-
current_version = '2.
|
133
|
+
current_version = '2.2.0'
|
134
134
|
|
135
135
|
if config_path.exists():
|
136
136
|
if not force:
|
@@ -305,9 +305,9 @@ def setup_update_checking(project_path: Path):
|
|
305
305
|
# Create initial version tracking
|
306
306
|
try:
|
307
307
|
import cursorflow
|
308
|
-
current_version = getattr(cursorflow, '__version__', '2.
|
308
|
+
current_version = getattr(cursorflow, '__version__', '2.2.0')
|
309
309
|
except ImportError:
|
310
|
-
current_version = '2.
|
310
|
+
current_version = '2.2.0'
|
311
311
|
|
312
312
|
version_info = {
|
313
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,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
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: cursorflow
|
3
|
-
Version: 2.1
|
3
|
+
Version: 2.2.1
|
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)
|
@@ -160,11 +197,26 @@ This creates:
|
|
160
197
|
- `.gitignore` entries for CursorFlow artifacts
|
161
198
|
|
162
199
|
### Step 3: Start Testing
|
200
|
+
|
201
|
+
**Simple page capture:**
|
163
202
|
```bash
|
164
|
-
|
165
|
-
|
203
|
+
cursorflow test --base-url http://localhost:3000 --path /dashboard
|
204
|
+
```
|
166
205
|
|
167
|
-
|
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
|
168
220
|
cursorflow test --base-url http://localhost:3000 --actions '[
|
169
221
|
{"navigate": "/login"},
|
170
222
|
{"fill": {"selector": "#email", "value": "test@example.com"}},
|
@@ -1,17 +1,19 @@
|
|
1
|
-
cursorflow/__init__.py,sha256=
|
1
|
+
cursorflow/__init__.py,sha256=3szUCSDj7rxLr4NkfDoXWq7BWqMf7__8vTFg15LWcXo,2763
|
2
2
|
cursorflow/auto_init.py,sha256=dXQaXXiXe4wkUP-jd8fcJ5fYVt7ASdTb47b7SzXymOM,6122
|
3
3
|
cursorflow/auto_updater.py,sha256=oQ12TIMZ6Cm3HF-x9iRWFtvOLkRh-JWPqitS69-4roE,7851
|
4
|
-
cursorflow/cli.py,sha256=
|
5
|
-
cursorflow/install_cursorflow_rules.py,sha256=
|
4
|
+
cursorflow/cli.py,sha256=qSungpsi9OgL55w-LqgmGj-S7eJhfJq7ve_QwSoSEhI,43588
|
5
|
+
cursorflow/install_cursorflow_rules.py,sha256=DsZ0680y9JMuTKFXjdgYtOKIEAjBMsdwL8LmA9WEb5A,11864
|
6
6
|
cursorflow/post_install.py,sha256=WieBiKWG0qBAQpF8iMVWUyb9Fr2Xky9qECTMPrlAbpE,2678
|
7
7
|
cursorflow/updater.py,sha256=SroSQHQi5cYyzcOK_bf-WzmQmE7yeOs8qo3r__j-Z6E,19583
|
8
|
+
cursorflow/core/action_validator.py,sha256=SCk3w_62D1y0cCRDOajK8L44-abSj_KpnUBgR_yNVW4,6846
|
8
9
|
cursorflow/core/agent.py,sha256=f3lecgEzDRDdGTVccAtorpLGfNJJ49bbsQAmgr0vNGg,10136
|
9
10
|
cursorflow/core/auth_handler.py,sha256=oRafO6ZdxoHryBIvHsrNV8TECed4GXpJsdEiH0KdPPk,17149
|
10
|
-
cursorflow/core/browser_controller.py,sha256=
|
11
|
-
cursorflow/core/browser_engine.py,sha256=
|
11
|
+
cursorflow/core/browser_controller.py,sha256=jA3zmdULJb4FLn3pS3zlK5RmbpRK8k4UVgwiVM6_pts,146990
|
12
|
+
cursorflow/core/browser_engine.py,sha256=7N9hPOyDrEhLWYgZW2981N9gKlHF6Lbp7D7h0zBzuz8,14851
|
13
|
+
cursorflow/core/config_validator.py,sha256=HRtONSOmM0Xxt3-ok3xwnBADRiNnI0nNOMaS2OqOkDk,7286
|
12
14
|
cursorflow/core/css_iterator.py,sha256=whLCIwbHZEWaH1HCbmqhNX5zrh_fL-r3hsxKjYsukcE,16478
|
13
15
|
cursorflow/core/cursor_integration.py,sha256=MAeHjXYeqzaXnhskqkTDB-n5ixIHqapGe93X0lLKhws,67501
|
14
|
-
cursorflow/core/cursorflow.py,sha256=
|
16
|
+
cursorflow/core/cursorflow.py,sha256=oPe5R_m3V0DWy0FZcb0TL0rPDdz0KXw7KDcdR3Eg2Xs,45588
|
15
17
|
cursorflow/core/error_context_collector.py,sha256=so-aveqwb6_vRDbtKR2ln8_F84aaVIceNi1UHsaVPgc,26196
|
16
18
|
cursorflow/core/error_correlator.py,sha256=g6YaIF2yFXUDrTuWHCyuES6K0696H8LDWmXH1qI8ddA,13367
|
17
19
|
cursorflow/core/event_correlator.py,sha256=lCzXFnii17AQt3rOVOclez_AnjvBCF_2ZlnFG0mIN6c,7808
|
@@ -23,14 +25,14 @@ cursorflow/core/mockup_comparator.py,sha256=VLfEEaTgbcY0oTb-bhBD2uTbXGjbnOV7LXNV
|
|
23
25
|
cursorflow/core/persistent_session.py,sha256=FsEHj4wKkycmdp6PFRHv3g333Y74yqra0x_qhUTQpik,36075
|
24
26
|
cursorflow/core/report_generator.py,sha256=-vosfyrnfVyWDbAIMlMurl90xOXqBae8d6aLd9sEqiY,10113
|
25
27
|
cursorflow/core/trace_manager.py,sha256=Jj9ultZrL1atiZXfcRVI6ynCnnfqZM-X0_taxt-llJ0,7189
|
26
|
-
cursorflow/log_sources/local_file.py,sha256=
|
27
|
-
cursorflow/log_sources/ssh_remote.py,sha256=
|
28
|
+
cursorflow/log_sources/local_file.py,sha256=IEUslfRLwp19J4upMAnKNY9aoVl7zeIrrGbS-qQfyEw,8154
|
29
|
+
cursorflow/log_sources/ssh_remote.py,sha256=kZRpLpiO7cLd67wlCiTvz4Prwx1_Vu3ytB5KHzOUFxc,8220
|
28
30
|
cursorflow/rules/__init__.py,sha256=gPcA-IkhXj03sl7cvZV0wwo7CtEkcyuKs4y0F5oQbqE,458
|
29
|
-
cursorflow/rules/cursorflow-installation.mdc,sha256=
|
30
|
-
cursorflow/rules/cursorflow-usage.mdc,sha256=
|
31
|
-
cursorflow-2.1.
|
32
|
-
cursorflow-2.1.
|
33
|
-
cursorflow-2.1.
|
34
|
-
cursorflow-2.1.
|
35
|
-
cursorflow-2.1.
|
36
|
-
cursorflow-2.1.
|
31
|
+
cursorflow/rules/cursorflow-installation.mdc,sha256=D55pzzDPAVVbE3gAtKPUGoT-2fvB-FI2l6yrTdzUIEo,10208
|
32
|
+
cursorflow/rules/cursorflow-usage.mdc,sha256=hCbA9koCtRoeLOkB-PXmLlGzsag_15RuOveOE_R4JZ0,21628
|
33
|
+
cursorflow-2.2.1.dist-info/licenses/LICENSE,sha256=e4QbjAsj3bW-xgQOvQelr8sGLYDoqc48k6cKgCr_pBU,1080
|
34
|
+
cursorflow-2.2.1.dist-info/METADATA,sha256=gnoZ9OAIuFBdOyJ9Kcow_yIB4kGQwTt4G7KD6nkWxcQ,14014
|
35
|
+
cursorflow-2.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
36
|
+
cursorflow-2.2.1.dist-info/entry_points.txt,sha256=-Ed_n4Uff7wClEtWS-Py6xmQabecB9f0QAOjX0w7ljA,51
|
37
|
+
cursorflow-2.2.1.dist-info/top_level.txt,sha256=t1UZwRyZP4u-ng2CEcNHmk_ZT4ibQxoihB2IjTF7ovc,11
|
38
|
+
cursorflow-2.2.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|