import os import sys import json import logging import subprocess import uuid from pathlib import Path from datetime import datetime from typing import Optional, Tuple, Dict class CursorResetter: """Cursor重置工具类,封装PowerShell脚本的核心功能""" def __init__(self): self.appdata = os.getenv('APPDATA') self.localappdata = os.getenv('LOCALAPPDATA') self.storage_file = Path(self.appdata) / "Cursor" / "User" / "globalStorage" / "storage.json" self.backup_dir = Path(self.appdata) / "Cursor" / "User" / "globalStorage" / "backups" self.cursor_path = Path(self.localappdata) / "Programs" / "cursor" self.app_path = self.cursor_path / "resources" / "app" self.package_json = self.app_path / "package.json" def get_random_hex(self, length: int) -> str: """生成安全的随机十六进制字符串""" import secrets return secrets.token_hex(length) def new_standard_machine_id(self) -> str: """生成标准格式的机器ID""" template = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx" import random def replace_char(match): r = random.randint(0, 15) v = r if match == 'x' else (r & 0x3 | 0x8) return hex(v)[2:] return ''.join(replace_char(c) for c in template) def generate_ids(self) -> Dict[str, str]: """生成所有需要的ID""" # 生成标准格式的ID mac_machine_id = self.new_standard_machine_id() uuid_str = str(uuid.uuid4()) # 生成带前缀的machineId prefix = "auth0|user_" prefix_hex = ''.join(hex(b)[2:].zfill(2) for b in prefix.encode()) random_part = self.get_random_hex(32) machine_id = f"{prefix_hex}{random_part}" # 生成大写的SQM ID sqm_id = "{" + str(uuid.uuid4()).upper() + "}" return { "mac_machine_id": mac_machine_id, "uuid": uuid_str, "machine_id": machine_id, "sqm_id": sqm_id } def backup_file(self, file_path: Path) -> Optional[Path]: """备份文件""" try: if not file_path.exists(): return None self.backup_dir.mkdir(parents=True, exist_ok=True) backup_name = f"{file_path.name}.backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}" backup_path = self.backup_dir / backup_name import shutil shutil.copy2(file_path, backup_path) logging.info(f"已备份文件: {backup_path}") return backup_path except Exception as e: logging.error(f"备份文件失败: {str(e)}") return None def update_machine_guid(self) -> bool: """更新系统MachineGuid""" try: import winreg new_guid = str(uuid.uuid4()) registry_path = r"SOFTWARE\Microsoft\Cryptography" # 备份原始值 with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, registry_path, 0, winreg.KEY_READ | winreg.KEY_WOW64_64KEY) as key: original_guid = winreg.QueryValueEx(key, "MachineGuid")[0] # 备份到文件 self.backup_dir.mkdir(parents=True, exist_ok=True) backup_path = self.backup_dir / f"MachineGuid.backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}" with open(backup_path, 'w', encoding='utf-8') as f: f.write(original_guid) # 更新GUID with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, registry_path, 0, winreg.KEY_WRITE | winreg.KEY_WOW64_64KEY) as key: winreg.SetValueEx(key, "MachineGuid", 0, winreg.REG_SZ, new_guid) logging.info(f"已更新系统MachineGuid: {new_guid}") return True except Exception as e: logging.error(f"更新MachineGuid失败: {str(e)}") return False def update_storage_json(self) -> bool: """更新storage.json文件""" try: if not self.storage_file.exists(): logging.error(f"未找到配置文件: {self.storage_file}") return False # 备份文件 if not self.backup_file(self.storage_file): logging.warning("配置文件备份失败") # 生成新ID ids = self.generate_ids() # 读取并更新配置 with open(self.storage_file, "r", encoding="utf-8") as f: config = json.load(f) # 更新ID config['telemetry.machineId'] = ids['machine_id'] config['telemetry.macMachineId'] = ids['mac_machine_id'] config['telemetry.devDeviceId'] = ids['uuid'] config['telemetry.sqmId'] = ids['sqm_id'] # 保存更新 with open(self.storage_file, "w", encoding="utf-8", newline='\n') as f: json.dump(config, f, indent=2) logging.info("已更新配置文件") return True except Exception as e: logging.error(f"更新配置文件失败: {str(e)}") return False def disable_auto_update(self) -> bool: """禁用自动更新""" try: updater_path = Path(self.localappdata) / "cursor-updater" # 删除现有文件/目录 if updater_path.exists(): if updater_path.is_dir(): import shutil shutil.rmtree(updater_path) else: updater_path.unlink() # 创建空文件并设置只读 updater_path.touch() import stat updater_path.chmod(stat.S_IREAD) # 设置文件权限 if os.name == 'nt': subprocess.run( f'icacls "{updater_path}" /inheritance:r /grant:r "{os.getenv("USERNAME")}:(R)"', shell=True, check=True ) logging.info("已禁用自动更新") return True except Exception as e: logging.error(f"禁用自动更新失败: {str(e)}") return False def reset_cursor(self, disable_update: bool = True) -> Tuple[bool, str]: """重置Cursor Args: disable_update: 是否禁用自动更新 Returns: Tuple[bool, str]: (是否成功, 消息) """ try: # 1. 检查管理员权限 if os.name == 'nt': import ctypes if not ctypes.windll.shell32.IsUserAnAdmin(): return False, "需要管理员权限来执行重置操作" # 2. 更新配置文件 if not self.update_storage_json(): return False, "更新配置文件失败" # 3. 更新系统MachineGuid if not self.update_machine_guid(): return False, "更新系统MachineGuid失败" # 4. 禁用自动更新(如果需要) if disable_update and not self.disable_auto_update(): logging.warning("禁用自动更新失败") # 5. 修改package.json if self.package_json.exists(): try: with open(self.package_json, "r", encoding="utf-8") as f: data = json.load(f) if "machineId" in data: del data["machineId"] data["updateUrl"] = "" data["disableUpdate"] = True with open(self.package_json, "w", encoding="utf-8", newline='\n') as f: json.dump(data, f, indent=2) except Exception as e: logging.warning(f"修改package.json失败: {str(e)}") return True, "Cursor重置成功" except Exception as e: logging.error(f"重置过程出错: {str(e)}") return False, f"重置失败: {str(e)}"