cursorflow 1.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 +78 -0
- cursorflow/auto_updater.py +244 -0
- cursorflow/cli.py +408 -0
- cursorflow/core/agent.py +272 -0
- cursorflow/core/auth_handler.py +433 -0
- cursorflow/core/browser_controller.py +534 -0
- cursorflow/core/browser_engine.py +386 -0
- cursorflow/core/css_iterator.py +397 -0
- cursorflow/core/cursor_integration.py +744 -0
- cursorflow/core/cursorflow.py +649 -0
- cursorflow/core/error_correlator.py +322 -0
- cursorflow/core/event_correlator.py +182 -0
- cursorflow/core/file_change_monitor.py +548 -0
- cursorflow/core/log_collector.py +410 -0
- cursorflow/core/log_monitor.py +179 -0
- cursorflow/core/persistent_session.py +910 -0
- cursorflow/core/report_generator.py +282 -0
- cursorflow/log_sources/local_file.py +198 -0
- cursorflow/log_sources/ssh_remote.py +210 -0
- cursorflow/updater.py +512 -0
- cursorflow-1.2.0.dist-info/METADATA +444 -0
- cursorflow-1.2.0.dist-info/RECORD +25 -0
- cursorflow-1.2.0.dist-info/WHEEL +5 -0
- cursorflow-1.2.0.dist-info/entry_points.txt +2 -0
- cursorflow-1.2.0.dist-info/top_level.txt +1 -0
cursorflow/updater.py
ADDED
@@ -0,0 +1,512 @@
|
|
1
|
+
"""
|
2
|
+
CursorFlow Update Manager
|
3
|
+
|
4
|
+
Handles automatic updates, dependency management, and rule synchronization
|
5
|
+
across projects. Ensures CursorFlow stays current everywhere it's used.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import asyncio
|
9
|
+
import json
|
10
|
+
import subprocess
|
11
|
+
import sys
|
12
|
+
import urllib.request
|
13
|
+
import urllib.error
|
14
|
+
from pathlib import Path
|
15
|
+
from typing import Dict, List, Optional, Any
|
16
|
+
import logging
|
17
|
+
import shutil
|
18
|
+
import tempfile
|
19
|
+
import zipfile
|
20
|
+
|
21
|
+
|
22
|
+
class CursorFlowUpdater:
|
23
|
+
"""
|
24
|
+
Comprehensive update manager for CursorFlow
|
25
|
+
|
26
|
+
Handles:
|
27
|
+
- Package updates via pip
|
28
|
+
- Rule synchronization across projects
|
29
|
+
- Dependency installation
|
30
|
+
- Configuration migration
|
31
|
+
- Rollback capabilities
|
32
|
+
"""
|
33
|
+
|
34
|
+
def __init__(self, project_dir: str = "."):
|
35
|
+
self.project_dir = Path(project_dir).resolve()
|
36
|
+
self.logger = logging.getLogger(__name__)
|
37
|
+
|
38
|
+
# Update configuration
|
39
|
+
self.update_config = {
|
40
|
+
"check_interval_hours": 24,
|
41
|
+
"auto_update": False,
|
42
|
+
"include_prereleases": False,
|
43
|
+
"backup_before_update": True
|
44
|
+
}
|
45
|
+
|
46
|
+
# Load existing update preferences
|
47
|
+
self._load_update_preferences()
|
48
|
+
|
49
|
+
# GitHub repository info
|
50
|
+
self.repo_info = {
|
51
|
+
"owner": "haley-marketing-group",
|
52
|
+
"repo": "cursorflow",
|
53
|
+
"api_base": "https://api.github.com/repos/haley-marketing-group/cursorflow"
|
54
|
+
}
|
55
|
+
|
56
|
+
async def check_for_updates(self, silent: bool = False) -> Dict[str, Any]:
|
57
|
+
"""
|
58
|
+
Check for CursorFlow updates
|
59
|
+
|
60
|
+
Args:
|
61
|
+
silent: If True, don't print status messages
|
62
|
+
|
63
|
+
Returns:
|
64
|
+
Update information dictionary
|
65
|
+
"""
|
66
|
+
if not silent:
|
67
|
+
print("🔍 Checking for CursorFlow updates...")
|
68
|
+
|
69
|
+
try:
|
70
|
+
# Get current version
|
71
|
+
current_version = self._get_current_version()
|
72
|
+
|
73
|
+
# Get latest version from PyPI
|
74
|
+
latest_version = await self._get_latest_pypi_version()
|
75
|
+
|
76
|
+
# Get latest rules version from GitHub
|
77
|
+
latest_rules_version = await self._get_latest_rules_version()
|
78
|
+
current_rules_version = self._get_current_rules_version()
|
79
|
+
|
80
|
+
update_info = {
|
81
|
+
"current_version": current_version,
|
82
|
+
"latest_version": latest_version,
|
83
|
+
"version_update_available": self._is_newer_version(latest_version, current_version),
|
84
|
+
"current_rules_version": current_rules_version,
|
85
|
+
"latest_rules_version": latest_rules_version,
|
86
|
+
"rules_update_available": self._is_newer_version(latest_rules_version, current_rules_version),
|
87
|
+
"dependencies_current": await self._check_dependencies(),
|
88
|
+
"last_check": self._get_current_timestamp()
|
89
|
+
}
|
90
|
+
|
91
|
+
# Save last check info
|
92
|
+
self._save_update_info(update_info)
|
93
|
+
|
94
|
+
if not silent:
|
95
|
+
self._display_update_status(update_info)
|
96
|
+
|
97
|
+
return update_info
|
98
|
+
|
99
|
+
except Exception as e:
|
100
|
+
self.logger.error(f"Update check failed: {e}")
|
101
|
+
return {"error": str(e), "last_check": self._get_current_timestamp()}
|
102
|
+
|
103
|
+
async def update_cursorflow(self, force: bool = False) -> bool:
|
104
|
+
"""
|
105
|
+
Update CursorFlow package and rules
|
106
|
+
|
107
|
+
Args:
|
108
|
+
force: Force update even if no updates available
|
109
|
+
|
110
|
+
Returns:
|
111
|
+
True if update successful, False otherwise
|
112
|
+
"""
|
113
|
+
print("🚀 Starting CursorFlow update process...")
|
114
|
+
|
115
|
+
try:
|
116
|
+
# Check what needs updating
|
117
|
+
update_info = await self.check_for_updates(silent=True)
|
118
|
+
|
119
|
+
if not force and not (update_info.get("version_update_available") or
|
120
|
+
update_info.get("rules_update_available")):
|
121
|
+
print("✅ CursorFlow is already up to date!")
|
122
|
+
return True
|
123
|
+
|
124
|
+
# Create backup if enabled
|
125
|
+
if self.update_config.get("backup_before_update", True):
|
126
|
+
backup_path = await self._create_backup()
|
127
|
+
print(f"📦 Backup created: {backup_path}")
|
128
|
+
|
129
|
+
success = True
|
130
|
+
|
131
|
+
# Update package if needed
|
132
|
+
if force or update_info.get("version_update_available"):
|
133
|
+
print("📥 Updating CursorFlow package...")
|
134
|
+
if await self._update_package():
|
135
|
+
print("✅ Package updated successfully")
|
136
|
+
else:
|
137
|
+
print("❌ Package update failed")
|
138
|
+
success = False
|
139
|
+
|
140
|
+
# Update rules if needed
|
141
|
+
if force or update_info.get("rules_update_available"):
|
142
|
+
print("📝 Updating CursorFlow rules...")
|
143
|
+
if await self._update_rules():
|
144
|
+
print("✅ Rules updated successfully")
|
145
|
+
else:
|
146
|
+
print("❌ Rules update failed")
|
147
|
+
success = False
|
148
|
+
|
149
|
+
# Update dependencies
|
150
|
+
print("🔧 Checking dependencies...")
|
151
|
+
if await self._update_dependencies():
|
152
|
+
print("✅ Dependencies updated")
|
153
|
+
else:
|
154
|
+
print("⚠️ Some dependency updates failed")
|
155
|
+
|
156
|
+
# Update configuration if needed
|
157
|
+
if await self._migrate_configuration():
|
158
|
+
print("✅ Configuration updated")
|
159
|
+
|
160
|
+
# Verify installation
|
161
|
+
if await self._verify_installation():
|
162
|
+
print("🎉 CursorFlow update completed successfully!")
|
163
|
+
return success
|
164
|
+
else:
|
165
|
+
print("❌ Update verification failed")
|
166
|
+
return False
|
167
|
+
|
168
|
+
except Exception as e:
|
169
|
+
self.logger.error(f"Update failed: {e}")
|
170
|
+
print(f"❌ Update failed: {e}")
|
171
|
+
return False
|
172
|
+
|
173
|
+
async def install_dependencies(self) -> bool:
|
174
|
+
"""Install all required dependencies"""
|
175
|
+
print("🔧 Installing CursorFlow dependencies...")
|
176
|
+
|
177
|
+
dependencies = [
|
178
|
+
"playwright>=1.40.0",
|
179
|
+
"paramiko>=3.0.0",
|
180
|
+
"watchdog>=3.0.0",
|
181
|
+
"click>=8.0.0",
|
182
|
+
"rich>=13.0.0"
|
183
|
+
]
|
184
|
+
|
185
|
+
try:
|
186
|
+
# Install Python dependencies
|
187
|
+
for dep in dependencies:
|
188
|
+
print(f" Installing {dep}...")
|
189
|
+
result = subprocess.run([
|
190
|
+
sys.executable, "-m", "pip", "install", dep
|
191
|
+
], capture_output=True, text=True)
|
192
|
+
|
193
|
+
if result.returncode != 0:
|
194
|
+
print(f" ❌ Failed to install {dep}: {result.stderr}")
|
195
|
+
return False
|
196
|
+
else:
|
197
|
+
print(f" ✅ {dep} installed")
|
198
|
+
|
199
|
+
# Install Playwright browsers
|
200
|
+
print(" Installing Playwright browsers...")
|
201
|
+
result = subprocess.run([
|
202
|
+
sys.executable, "-m", "playwright", "install", "chromium"
|
203
|
+
], capture_output=True, text=True)
|
204
|
+
|
205
|
+
if result.returncode != 0:
|
206
|
+
print(f" ⚠️ Playwright browser install failed: {result.stderr}")
|
207
|
+
print(" You may need to run: playwright install chromium")
|
208
|
+
else:
|
209
|
+
print(" ✅ Playwright browsers installed")
|
210
|
+
|
211
|
+
return True
|
212
|
+
|
213
|
+
except Exception as e:
|
214
|
+
print(f"❌ Dependency installation failed: {e}")
|
215
|
+
return False
|
216
|
+
|
217
|
+
async def update_project_rules(self, force: bool = False) -> bool:
|
218
|
+
"""Update rules in current project"""
|
219
|
+
print("📝 Updating project rules...")
|
220
|
+
|
221
|
+
try:
|
222
|
+
# Download latest rules
|
223
|
+
rules_dir = self.project_dir / ".cursor" / "rules"
|
224
|
+
rules_dir.mkdir(parents=True, exist_ok=True)
|
225
|
+
|
226
|
+
rules_to_update = [
|
227
|
+
"cursorflow-usage.mdc",
|
228
|
+
"cursorflow-installation.mdc"
|
229
|
+
]
|
230
|
+
|
231
|
+
for rule_file in rules_to_update:
|
232
|
+
print(f" Updating {rule_file}...")
|
233
|
+
|
234
|
+
# Download from GitHub
|
235
|
+
url = f"https://raw.githubusercontent.com/haley-marketing-group/cursorflow/main/rules/{rule_file}"
|
236
|
+
|
237
|
+
try:
|
238
|
+
with urllib.request.urlopen(url) as response:
|
239
|
+
content = response.read().decode('utf-8')
|
240
|
+
|
241
|
+
# Write to project
|
242
|
+
rule_path = rules_dir / rule_file
|
243
|
+
with open(rule_path, 'w') as f:
|
244
|
+
f.write(content)
|
245
|
+
|
246
|
+
print(f" ✅ {rule_file} updated")
|
247
|
+
|
248
|
+
except urllib.error.URLError as e:
|
249
|
+
print(f" ❌ Failed to download {rule_file}: {e}")
|
250
|
+
return False
|
251
|
+
|
252
|
+
# Update rules version tracking
|
253
|
+
self._save_rules_version()
|
254
|
+
|
255
|
+
return True
|
256
|
+
|
257
|
+
except Exception as e:
|
258
|
+
print(f"❌ Rules update failed: {e}")
|
259
|
+
return False
|
260
|
+
|
261
|
+
def _get_current_version(self) -> str:
|
262
|
+
"""Get currently installed CursorFlow version"""
|
263
|
+
try:
|
264
|
+
import cursorflow
|
265
|
+
return getattr(cursorflow, '__version__', '0.0.0')
|
266
|
+
except ImportError:
|
267
|
+
return '0.0.0'
|
268
|
+
|
269
|
+
async def _get_latest_pypi_version(self) -> str:
|
270
|
+
"""Get latest version from PyPI"""
|
271
|
+
try:
|
272
|
+
url = "https://pypi.org/pypi/cursorflow/json"
|
273
|
+
with urllib.request.urlopen(url) as response:
|
274
|
+
data = json.loads(response.read())
|
275
|
+
return data["info"]["version"]
|
276
|
+
except Exception:
|
277
|
+
return "0.0.0"
|
278
|
+
|
279
|
+
async def _get_latest_rules_version(self) -> str:
|
280
|
+
"""Get latest rules version from GitHub"""
|
281
|
+
try:
|
282
|
+
url = f"{self.repo_info['api_base']}/releases/latest"
|
283
|
+
with urllib.request.urlopen(url) as response:
|
284
|
+
data = json.loads(response.read())
|
285
|
+
return data["tag_name"].lstrip('v')
|
286
|
+
except Exception:
|
287
|
+
return "0.0.0"
|
288
|
+
|
289
|
+
def _get_current_rules_version(self) -> str:
|
290
|
+
"""Get current rules version in project"""
|
291
|
+
version_file = self.project_dir / ".cursorflow" / "rules_version.json"
|
292
|
+
if version_file.exists():
|
293
|
+
try:
|
294
|
+
with open(version_file) as f:
|
295
|
+
data = json.load(f)
|
296
|
+
return data.get("version", "0.0.0")
|
297
|
+
except Exception:
|
298
|
+
pass
|
299
|
+
return "0.0.0"
|
300
|
+
|
301
|
+
def _is_newer_version(self, latest: str, current: str) -> bool:
|
302
|
+
"""Compare version strings"""
|
303
|
+
try:
|
304
|
+
latest_parts = [int(x) for x in latest.split('.')]
|
305
|
+
current_parts = [int(x) for x in current.split('.')]
|
306
|
+
|
307
|
+
# Pad shorter version with zeros
|
308
|
+
max_len = max(len(latest_parts), len(current_parts))
|
309
|
+
latest_parts.extend([0] * (max_len - len(latest_parts)))
|
310
|
+
current_parts.extend([0] * (max_len - len(current_parts)))
|
311
|
+
|
312
|
+
return latest_parts > current_parts
|
313
|
+
except Exception:
|
314
|
+
return False
|
315
|
+
|
316
|
+
async def _check_dependencies(self) -> bool:
|
317
|
+
"""Check if all dependencies are current"""
|
318
|
+
try:
|
319
|
+
import playwright
|
320
|
+
import paramiko
|
321
|
+
import watchdog
|
322
|
+
return True
|
323
|
+
except ImportError:
|
324
|
+
return False
|
325
|
+
|
326
|
+
async def _update_package(self) -> bool:
|
327
|
+
"""Update CursorFlow package via pip"""
|
328
|
+
try:
|
329
|
+
result = subprocess.run([
|
330
|
+
sys.executable, "-m", "pip", "install", "--upgrade", "cursorflow"
|
331
|
+
], capture_output=True, text=True)
|
332
|
+
|
333
|
+
return result.returncode == 0
|
334
|
+
except Exception:
|
335
|
+
return False
|
336
|
+
|
337
|
+
async def _update_rules(self) -> bool:
|
338
|
+
"""Update rules from GitHub"""
|
339
|
+
return await self.update_project_rules()
|
340
|
+
|
341
|
+
async def _update_dependencies(self) -> bool:
|
342
|
+
"""Update all dependencies"""
|
343
|
+
return await self.install_dependencies()
|
344
|
+
|
345
|
+
async def _migrate_configuration(self) -> bool:
|
346
|
+
"""Migrate configuration to new format if needed"""
|
347
|
+
config_file = self.project_dir / "cursorflow-config.json"
|
348
|
+
|
349
|
+
if not config_file.exists():
|
350
|
+
return True
|
351
|
+
|
352
|
+
try:
|
353
|
+
with open(config_file) as f:
|
354
|
+
config = json.load(f)
|
355
|
+
|
356
|
+
# Add new fields if missing
|
357
|
+
updated = False
|
358
|
+
|
359
|
+
if "_cursorflow_version" not in config:
|
360
|
+
config["_cursorflow_version"] = self._get_current_version()
|
361
|
+
updated = True
|
362
|
+
|
363
|
+
if "browser" not in config:
|
364
|
+
config["browser"] = {"headless": True, "debug_mode": False}
|
365
|
+
updated = True
|
366
|
+
|
367
|
+
if updated:
|
368
|
+
with open(config_file, 'w') as f:
|
369
|
+
json.dump(config, f, indent=2)
|
370
|
+
|
371
|
+
return True
|
372
|
+
|
373
|
+
except Exception as e:
|
374
|
+
self.logger.error(f"Configuration migration failed: {e}")
|
375
|
+
return False
|
376
|
+
|
377
|
+
async def _verify_installation(self) -> bool:
|
378
|
+
"""Verify that CursorFlow is working after update"""
|
379
|
+
try:
|
380
|
+
# Try importing
|
381
|
+
import cursorflow
|
382
|
+
|
383
|
+
# Check core components
|
384
|
+
from cursorflow import CursorFlow
|
385
|
+
from cursorflow.core.css_iterator import CSSIterator
|
386
|
+
|
387
|
+
return True
|
388
|
+
except Exception:
|
389
|
+
return False
|
390
|
+
|
391
|
+
async def _create_backup(self) -> str:
|
392
|
+
"""Create backup of current installation"""
|
393
|
+
backup_dir = self.project_dir / ".cursorflow" / "backups"
|
394
|
+
backup_dir.mkdir(parents=True, exist_ok=True)
|
395
|
+
|
396
|
+
timestamp = self._get_current_timestamp()
|
397
|
+
backup_name = f"backup_{timestamp}"
|
398
|
+
backup_path = backup_dir / backup_name
|
399
|
+
|
400
|
+
# Backup configuration and rules
|
401
|
+
if (self.project_dir / "cursorflow-config.json").exists():
|
402
|
+
shutil.copy2(
|
403
|
+
self.project_dir / "cursorflow-config.json",
|
404
|
+
backup_path.with_suffix('.config.json')
|
405
|
+
)
|
406
|
+
|
407
|
+
rules_dir = self.project_dir / ".cursor" / "rules"
|
408
|
+
if rules_dir.exists():
|
409
|
+
shutil.copytree(rules_dir, backup_path.with_suffix('.rules'), dirs_exist_ok=True)
|
410
|
+
|
411
|
+
return str(backup_path)
|
412
|
+
|
413
|
+
def _load_update_preferences(self):
|
414
|
+
"""Load update preferences from project"""
|
415
|
+
prefs_file = self.project_dir / ".cursorflow" / "update_preferences.json"
|
416
|
+
if prefs_file.exists():
|
417
|
+
try:
|
418
|
+
with open(prefs_file) as f:
|
419
|
+
prefs = json.load(f)
|
420
|
+
self.update_config.update(prefs)
|
421
|
+
except Exception:
|
422
|
+
pass
|
423
|
+
|
424
|
+
def _save_update_info(self, update_info: Dict):
|
425
|
+
"""Save update check information"""
|
426
|
+
info_dir = self.project_dir / ".cursorflow"
|
427
|
+
info_dir.mkdir(exist_ok=True)
|
428
|
+
|
429
|
+
info_file = info_dir / "update_info.json"
|
430
|
+
with open(info_file, 'w') as f:
|
431
|
+
json.dump(update_info, f, indent=2)
|
432
|
+
|
433
|
+
def _save_rules_version(self):
|
434
|
+
"""Save current rules version"""
|
435
|
+
version_dir = self.project_dir / ".cursorflow"
|
436
|
+
version_dir.mkdir(exist_ok=True)
|
437
|
+
|
438
|
+
version_file = version_dir / "rules_version.json"
|
439
|
+
with open(version_file, 'w') as f:
|
440
|
+
json.dump({
|
441
|
+
"version": self._get_current_version(),
|
442
|
+
"updated": self._get_current_timestamp()
|
443
|
+
}, f, indent=2)
|
444
|
+
|
445
|
+
def _get_current_timestamp(self) -> str:
|
446
|
+
"""Get current timestamp string"""
|
447
|
+
import datetime
|
448
|
+
return datetime.datetime.now().isoformat()
|
449
|
+
|
450
|
+
def _display_update_status(self, update_info: Dict):
|
451
|
+
"""Display update status to user"""
|
452
|
+
current_version = update_info.get("current_version", "unknown")
|
453
|
+
latest_version = update_info.get("latest_version", "unknown")
|
454
|
+
|
455
|
+
print(f"📦 Current version: {current_version}")
|
456
|
+
print(f"🌟 Latest version: {latest_version}")
|
457
|
+
|
458
|
+
if update_info.get("version_update_available"):
|
459
|
+
print("🔄 Package update available!")
|
460
|
+
|
461
|
+
if update_info.get("rules_update_available"):
|
462
|
+
print("📝 Rules update available!")
|
463
|
+
|
464
|
+
if not update_info.get("dependencies_current"):
|
465
|
+
print("🔧 Dependency updates needed!")
|
466
|
+
|
467
|
+
if not (update_info.get("version_update_available") or
|
468
|
+
update_info.get("rules_update_available")):
|
469
|
+
print("✅ CursorFlow is up to date!")
|
470
|
+
|
471
|
+
|
472
|
+
async def check_updates(project_dir: str = ".") -> Dict[str, Any]:
|
473
|
+
"""Convenience function to check for updates"""
|
474
|
+
updater = CursorFlowUpdater(project_dir)
|
475
|
+
return await updater.check_for_updates()
|
476
|
+
|
477
|
+
|
478
|
+
async def update_cursorflow(project_dir: str = ".", force: bool = False) -> bool:
|
479
|
+
"""Convenience function to update CursorFlow"""
|
480
|
+
updater = CursorFlowUpdater(project_dir)
|
481
|
+
return await updater.update_cursorflow(force=force)
|
482
|
+
|
483
|
+
|
484
|
+
async def install_dependencies(project_dir: str = ".") -> bool:
|
485
|
+
"""Convenience function to install dependencies"""
|
486
|
+
updater = CursorFlowUpdater(project_dir)
|
487
|
+
return await updater.install_dependencies()
|
488
|
+
|
489
|
+
|
490
|
+
if __name__ == "__main__":
|
491
|
+
import argparse
|
492
|
+
|
493
|
+
parser = argparse.ArgumentParser(description="CursorFlow Update Manager")
|
494
|
+
parser.add_argument("--check", action="store_true", help="Check for updates")
|
495
|
+
parser.add_argument("--update", action="store_true", help="Update CursorFlow")
|
496
|
+
parser.add_argument("--install-deps", action="store_true", help="Install dependencies")
|
497
|
+
parser.add_argument("--force", action="store_true", help="Force update")
|
498
|
+
parser.add_argument("--project-dir", default=".", help="Project directory")
|
499
|
+
|
500
|
+
args = parser.parse_args()
|
501
|
+
|
502
|
+
if args.check:
|
503
|
+
result = asyncio.run(check_updates(args.project_dir))
|
504
|
+
print(json.dumps(result, indent=2))
|
505
|
+
elif args.update:
|
506
|
+
success = asyncio.run(update_cursorflow(args.project_dir, force=args.force))
|
507
|
+
sys.exit(0 if success else 1)
|
508
|
+
elif args.install_deps:
|
509
|
+
success = asyncio.run(install_dependencies(args.project_dir))
|
510
|
+
sys.exit(0 if success else 1)
|
511
|
+
else:
|
512
|
+
parser.print_help()
|