diff --git a/totally_reset_cursor.py b/totally_reset_cursor.py index 378b776..78dcad3 100644 --- a/totally_reset_cursor.py +++ b/totally_reset_cursor.py @@ -1,509 +1,789 @@ import os -import shutil -import platform -import time import sys -import glob import json import uuid -import random -import string +import hashlib +import shutil +import sqlite3 +import platform import re -from datetime import datetime -import subprocess +import tempfile from colorama import Fore, Style, init -from main import translator -from main import EMOJI +from typing import Tuple +import configparser +from new_signup import get_user_documents_path +import traceback +from config import get_config # Initialize colorama init() -# Define emoji and color constants +# Define emoji constants EMOJI = { "FILE": "📄", "BACKUP": "💾", "SUCCESS": "✅", "ERROR": "❌", "INFO": "ℹ️", - "RESET": "🔄", - "MENU": "📋", - "ARROW": "➜", - "LANG": "🌐", - "UPDATE": "🔄", - "ADMIN": "🔐", - "STOP": "🛑", - "DISCLAIMER": "⚠️", - "WARNING": "⚠️" + "RESET": "��", + "WARNING": "⚠️", } -def display_banner(): - """Displays a stylized banner for the tool.""" - print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}") - print(f"{Fore.CYAN}{EMOJI['STOP']} {translator.get('totally_reset.title')} {Style.RESET_ALL}") - print(f"{Fore.CYAN}{'='*50}{Style.RESET_ALL}") - -def display_features(): - """Displays the features of the Cursor AI Reset Tool.""" - print(f"\n{Fore.CYAN}{EMOJI['MENU']} {translator.get('totally_reset.feature_title')}{Style.RESET_ALL}") - print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.feature_1')} {Style.RESET_ALL}") - print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.feature_2')} {Style.RESET_ALL}") - print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.feature_3')} {Style.RESET_ALL}") - print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.feature_4')} {Style.RESET_ALL}") - print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.feature_5')} {Style.RESET_ALL}") - print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.feature_6')} {Style.RESET_ALL}") - print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.feature_7')} {Style.RESET_ALL}") - print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.feature_8')} {Style.RESET_ALL}") - print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.feature_9')} {Style.RESET_ALL}\n") - -def display_disclaimer(): - """Displays a disclaimer for the user.""" - print(f"\n{Fore.RED}{EMOJI['DISCLAIMER']} {translator.get('totally_reset.disclaimer_title')}{Style.RESET_ALL}") - print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.disclaimer_1')} {Style.RESET_ALL}") - print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.disclaimer_2')} {Style.RESET_ALL}") - print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.disclaimer_3')} {Style.RESET_ALL}") - print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.disclaimer_4')} {Style.RESET_ALL}") - print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.disclaimer_5')} {Style.RESET_ALL}") - print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.disclaimer_6')} {Style.RESET_ALL}") - print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.disclaimer_7')} {Style.RESET_ALL} \n") - -def get_confirmation(): - """Gets confirmation from the user to proceed.""" - while True: - choice = input(f"{Fore.RED}{EMOJI['WARNING']} {translator.get('totally_reset.confirm_title')} (Y/n): ").strip().lower() - if choice == "y" or choice == "": - return True - elif choice == "n": - return False - else: - print(f"{EMOJI['ERROR']} {translator.get('totally_reset.invalid_choice')}") - -def remove_dir(path): - """Removes a directory if it exists and logs the action.""" - # Safety check to ensure we're only deleting Cursor-related directories - if not is_cursor_related(path): - print(f"{Fore.RED}{EMOJI['WARNING']} {translator.get('totally_reset.skipped_for_safety', path=path)} {Style.RESET_ALL}") - return - - if os.path.exists(path): - try: - shutil.rmtree(path, ignore_errors=True) - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('totally_reset.deleted', path=path)} {Style.RESET_ALL}") - except Exception as e: - print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.error_deleting', path=path, error=str(e))} {Style.RESET_ALL}") - else: - print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.not_found', path=path)} {Style.RESET_ALL}") - -def remove_file(path): - """Removes a file if it exists and logs the action.""" - # Safety check to ensure we're only deleting Cursor-related files - if not is_cursor_related(path): - print(f"{Fore.RED}{EMOJI['WARNING']} {translator.get('totally_reset.skipped_for_safety', path=path)} {Style.RESET_ALL}") - return - - if os.path.isfile(path): - try: - os.remove(path) - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('totally_reset.deleted', path=path)} {Style.RESET_ALL}") - except Exception as e: - print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.error_deleting', path=path, error=str(e))} {Style.RESET_ALL}") - else: - print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.not_found', path=path)} {Style.RESET_ALL}") - -def is_cursor_related(path): - """ - Safety function to verify a path is related to Cursor before deletion. - Returns True if the path appears to be related to Cursor AI. - """ - # Skip .vscode check as it's shared with VS Code - if path.endswith(".vscode"): - return False - - # Check if path contains cursor-related terms - cursor_terms = ["cursor", "cursorai", "cursor-electron"] - - # Convert path to lowercase for case-insensitive matching - lower_path = path.lower() - - # Return True if any cursor term is present in the path - for term in cursor_terms: - if term in lower_path: - return True - - # Check specific known Cursor file patterns - cursor_patterns = [ - r"\.cursor_.*$", - r"cursor-.*\.json$", - r"cursor_.*\.json$", - r"cursor-machine-id$", - r"trial_info\.json$", - r"license\.json$" - ] - - for pattern in cursor_patterns: - if re.search(pattern, lower_path): - return True - - # If it's a specific file that we know is only for Cursor - if os.path.basename(lower_path) in [ - "cursor_trial_data", - "cursor-state.json", - "cursor-machine-id", - "ai-settings.json", - "cursor.desktop" - ]: - return True - - return False - -def find_cursor_license_files(base_path, pattern): - """Finds files matching a pattern that might contain license information.""" - try: - matches = [] - for root, dirnames, filenames in os.walk(base_path): - for filename in filenames: - # Check if filename matches any pattern before adding to matches - if any(p.lower() in filename.lower() for p in pattern): - full_path = os.path.join(root, filename) - # Extra safety check to ensure it's cursor-related - if is_cursor_related(full_path): - matches.append(full_path) - return matches - except Exception as e: - print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.error_searching', path=base_path, error=str(e))} {Style.RESET_ALL}") - return [] - -def generate_new_machine_id(): - """Generates a new random machine ID.""" - return str(uuid.uuid4()) - -def create_fake_machine_id(path): - """Creates a new machine ID file with random ID.""" - if not is_cursor_related(path): - return - - try: - new_id = generate_new_machine_id() - directory = os.path.dirname(path) - - # Ensure directory exists - if not os.path.exists(directory): - os.makedirs(directory) - - with open(path, 'w') as f: - f.write(new_id) - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('totally_reset.created_machine_id', path=path)} {Style.RESET_ALL}") - except Exception as e: - print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.error_creating_machine_id', path=path, error=str(e))} {Style.RESET_ALL}") - -def reset_machine_id(system, home): - """Resets machine ID in all possible locations.""" - print(f"\n{Fore.CYAN}{EMOJI['RESET']} {translator.get('totally_reset.resetting_machine_id')} {Style.RESET_ALL}") - - # Common machine ID locations based on OS - if system == "Windows": - machine_id_paths = [ - os.path.join(home, "AppData", "Roaming", "Cursor", "cursor-machine-id"), - os.path.join(home, "AppData", "Local", "Cursor", "cursor-machine-id"), - os.path.join(home, "AppData", "Roaming", "cursor-electron", "cursor-machine-id"), - os.path.join(home, "AppData", "Local", "cursor-electron", "cursor-machine-id"), - os.path.join(home, ".cursor-machine-id"), - ] - elif system == "Darwin": # macOS - machine_id_paths = [ - os.path.join(home, "Library", "Application Support", "Cursor", "cursor-machine-id"), - os.path.join(home, "Library", "Application Support", "cursor-electron", "cursor-machine-id"), - os.path.join(home, ".cursor-machine-id"), - ] - elif system == "Linux": - machine_id_paths = [ - os.path.join(home, ".config", "Cursor", "cursor-machine-id"), - os.path.join(home, ".config", "cursor-electron", "cursor-machine-id"), - os.path.join(home, ".cursor-machine-id"), - ] - - # First remove existing machine IDs - for path in machine_id_paths: - remove_file(path) - - # Then create new randomized IDs - for path in machine_id_paths: - create_fake_machine_id(path) - - # Try to reset system machine ID if possible (with appropriate permissions) - if system == "Windows": - try: - # Windows: Create a temporary VBS script to reset machine GUID - print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.note_complete_machine_id_reset_may_require_running_as_administrator')} {Style.RESET_ALL}") - except Exception as e: - print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.windows_machine_id_modification_skipped', error=str(e))} {Style.RESET_ALL}") - - elif system == "Linux": - try: - # Linux: Create a random machine-id in /etc/ (needs sudo) - print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.note_complete_system_machine_id_reset_may_require_sudo_privileges')} {Style.RESET_ALL}") - except Exception as e: - print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.linux_machine_id_modification_skipped', error=str(e))} {Style.RESET_ALL}") - -def create_fake_trial_info(path, system, home): - """Creates fake trial information to extend trial period.""" - if not is_cursor_related(path): - return - - try: - # Generate future expiry date (90 days from now) - future_date = (datetime.now().timestamp() + (90 * 24 * 60 * 60)) * 1000 # milliseconds - - # Create fake trial info - fake_trial = { - "trialStartTimestamp": datetime.now().timestamp() * 1000, - "trialEndTimestamp": future_date, - "hasUsedTrial": False, - "machineId": generate_new_machine_id() - } - - directory = os.path.dirname(path) - - # Ensure directory exists - if not os.path.exists(directory): - os.makedirs(directory) - - with open(path, 'w') as f: - json.dump(fake_trial, f) - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('totally_reset.created_extended_trial_info', path=path)} {Style.RESET_ALL}") - except Exception as e: - print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.error_creating_trial_info', path=path, error=str(e))} {Style.RESET_ALL}") - -def reset_cursor(): - """Completely resets Cursor AI by removing all settings, caches, and extensions.""" +def get_cursor_paths(translator=None) -> Tuple[str, str]: + """ Get Cursor related paths""" system = platform.system() - home = os.path.expanduser("~") - - display_banner() - display_features() - display_disclaimer() - if not get_confirmation(): - print(f"\n{Fore.CYAN}{EMOJI['STOP']} {translator.get('totally_reset.reset_cancelled')} {Style.RESET_ALL}") - return - - print(f"\n{Fore.CYAN}{EMOJI['RESET']} {translator.get('totally_reset.resetting_cursor_ai_editor')} {Style.RESET_ALL}") - - # Define paths based on OS - if system == "Windows": - cursor_paths = [ - os.path.join(home, "AppData", "Roaming", "Cursor"), - os.path.join(home, "AppData", "Local", "Cursor"), - os.path.join(home, "AppData", "Roaming", "cursor-electron"), - os.path.join(home, "AppData", "Local", "cursor-electron"), - os.path.join(home, "AppData", "Local", "CursorAI"), - os.path.join(home, "AppData", "Roaming", "CursorAI"), - # os.path.join(home, ".vscode"), # Removed to avoid affecting VS Code - os.path.join(home, "AppData", "Local", "Temp", "Cursor"), # Temporary data - os.path.join(home, "AppData", "Local", "Temp", "cursor-updater"), - os.path.join(home, "AppData", "Local", "Programs", "cursor"), - ] + # Read config file + config = configparser.ConfigParser() + config_dir = os.path.join(get_user_documents_path(), ".cursor-free-vip") + config_file = os.path.join(config_dir, "config.ini") + + # Create config directory if it doesn't exist + if not os.path.exists(config_dir): + os.makedirs(config_dir) + + # Default paths for different systems + default_paths = { + "Darwin": "/Applications/Cursor.app/Contents/Resources/app", + "Windows": os.path.join(os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app"), + "Linux": ["/opt/Cursor/resources/app", "/usr/share/cursor/resources/app", os.path.expanduser("~/.local/share/cursor/resources/app")] + } + + # If config doesn't exist, create it with default paths + if not os.path.exists(config_file): + for section in ['MacPaths', 'WindowsPaths', 'LinuxPaths']: + if not config.has_section(section): + config.add_section(section) - # Additional locations for license/trial files on Windows - license_search_paths = [ - os.path.join(home, "AppData", "Roaming"), - os.path.join(home, "AppData", "Local"), - os.path.join(home, "AppData", "LocalLow"), - ] + if system == "Darwin": + config.set('MacPaths', 'cursor_path', default_paths["Darwin"]) + elif system == "Windows": + config.set('WindowsPaths', 'cursor_path', default_paths["Windows"]) + elif system == "Linux": + # For Linux, try to find the first existing path + for path in default_paths["Linux"]: + if os.path.exists(path): + config.set('LinuxPaths', 'cursor_path', path) + break + else: + # If no path exists, use the first one as default + config.set('LinuxPaths', 'cursor_path', default_paths["Linux"][0]) - # Registry instructions for Windows - print(f"\n{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.windows_registry_instructions')} {Style.RESET_ALL}") - print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.windows_registry_instructions_2')} {Style.RESET_ALL}") - - elif system == "Darwin": # macOS - cursor_paths = [ - os.path.join(home, "Library", "Application Support", "Cursor"), - os.path.join(home, "Library", "Application Support", "cursor-electron"), - os.path.join(home, "Library", "Caches", "Cursor"), - os.path.join(home, "Library", "Caches", "cursor-electron"), - os.path.join(home, "Library", "Preferences", "Cursor"), - os.path.join(home, "Library", "Preferences", "cursor-electron"), - os.path.join(home, "Library", "Saved Application State", "com.cursor.Cursor.savedState"), - os.path.join(home, "Library", "HTTPStorages", "com.cursor.Cursor"), - os.path.join(home, "Library", "WebKit", "com.cursor.Cursor"), - # os.path.join(home, ".vscode"), # Removed to avoid affecting VS Code - "/Applications/Cursor.app", # Main application location - ] - - # Additional locations for license/trial files on macOS - license_search_paths = [ - os.path.join(home, "Library", "Application Support"), - os.path.join(home, "Library", "Preferences"), - os.path.join(home, "Library", "Caches"), - ] - - elif system == "Linux": - cursor_paths = [ - os.path.join(home, ".config", "Cursor"), - os.path.join(home, ".config", "cursor-electron"), - os.path.join(home, ".cache", "Cursor"), - os.path.join(home, ".cache", "cursor-electron"), - os.path.join(home, ".local", "share", "Cursor"), - os.path.join(home, ".local", "share", "cursor-electron"), - # os.path.join(home, ".vscode"), # Removed to avoid affecting VS Code - os.path.join(home, ".local", "share", "applications", "cursor.desktop"), - os.path.join("/usr", "share", "applications", "cursor.desktop"), - os.path.join("/opt", "Cursor"), - ] - - # Additional locations for license/trial files on Linux - license_search_paths = [ - os.path.join(home, ".config"), - os.path.join(home, ".local", "share"), - os.path.join(home, ".cache"), - ] - + with open(config_file, 'w', encoding='utf-8') as f: + config.write(f) else: - print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.unsupported_os')} {Style.RESET_ALL}") - return - - # Remove main Cursor directories - print(f"\n{Fore.CYAN}{EMOJI['RESET']} {translator.get('totally_reset.removing_main_cursor_directories_and_files')} {Style.RESET_ALL}") - for path in cursor_paths: - remove_dir(path) - - # Reset machine identifiers (this creates new ones) - reset_machine_id(system, home) - - # Known trial/license file patterns - file_patterns = [ - ".cursor_trial_data", - "trial_info.json", - "license.json", - "cursor-license", - "cursor_license", - "cursor-auth", - "cursor_auth", - "cursor_subscription", - "cursor-subscription", - "cursor-state", - "cursorstate", - "cursorsettings", - "cursor-settings", - "ai-settings.json", - "cursor-machine-id", - "cursor_machine_id", - "cursor-storage" - ] - - # Direct known trial file paths - cursor_trial_files = [ - os.path.join(home, ".cursor_trial_data"), - os.path.join(home, ".cursor_license"), - os.path.join(home, ".cursor-machine-id"), - os.path.join(home, ".cursor-state.json"), - ] - - # OS-specific known trial/license files - if system == "Windows": - cursor_trial_files.extend([ - os.path.join(home, "AppData", "Local", "Cursor", "trial_info.json"), - os.path.join(home, "AppData", "Local", "Cursor", "license.json"), - os.path.join(home, "AppData", "Roaming", "Cursor", "trial_info.json"), - os.path.join(home, "AppData", "Roaming", "Cursor", "license.json"), - os.path.join(home, "AppData", "Roaming", "Cursor", "cursor-machine-id"), - os.path.join(home, "AppData", "Local", "Cursor", "cursor-machine-id"), - os.path.join(home, "AppData", "Local", "Cursor", "ai-settings.json"), - os.path.join(home, "AppData", "Roaming", "Cursor", "ai-settings.json"), - ]) - elif system == "Darwin": # macOS - cursor_trial_files.extend([ - os.path.join(home, "Library", "Application Support", "Cursor", "trial_info.json"), - os.path.join(home, "Library", "Application Support", "Cursor", "license.json"), - os.path.join(home, "Library", "Preferences", "Cursor", "trial_info.json"), - os.path.join(home, "Library", "Preferences", "Cursor", "license.json"), - os.path.join(home, "Library", "Application Support", "Cursor", "cursor-machine-id"), - os.path.join(home, "Library", "Application Support", "Cursor", "ai-settings.json"), - ]) - elif system == "Linux": - cursor_trial_files.extend([ - os.path.join(home, ".config", "Cursor", "trial_info.json"), - os.path.join(home, ".config", "Cursor", "license.json"), - os.path.join(home, ".local", "share", "Cursor", "trial_info.json"), - os.path.join(home, ".local", "share", "Cursor", "license.json"), - os.path.join(home, ".config", "Cursor", "cursor-machine-id"), - os.path.join(home, ".config", "Cursor", "ai-settings.json"), - ]) - - # Remove known trial/license files - print(f"\n{Fore.CYAN}{EMOJI['RESET']} {translator.get('totally_reset.removing_known')} {Style.RESET_ALL}") - for path in cursor_trial_files: - remove_file(path) - - # Deep search for additional trial/license files - print(f"\n{Fore.CYAN}{EMOJI['RESET']} {translator.get('totally_reset.performing_deep_scan')} {Style.RESET_ALL}") - all_found_files = [] - for base_path in license_search_paths: - if os.path.exists(base_path): - found_files = find_cursor_license_files(base_path, file_patterns) - all_found_files.extend(found_files) + config.read(config_file, encoding='utf-8') - if all_found_files: - print(f"\n🔎 {translator.get('totally_reset.found_additional_potential_license_trial_files', count=len(all_found_files))}\n") - for file_path in all_found_files: - remove_file(file_path) + # Get path based on system + if system == "Darwin": + section = 'MacPaths' + elif system == "Windows": + section = 'WindowsPaths' + elif system == "Linux": + section = 'LinuxPaths' else: - print(f"\n{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.no_additional_license_trial_files_found_in_deep_scan')} {Style.RESET_ALL}") - - # Check for and remove localStorage files that might contain settings - print(f"\n{Fore.CYAN}{EMOJI['RESET']} {translator.get('totally_reset.checking_for_electron_localstorage_files')} {Style.RESET_ALL}") - if system == "Windows": - local_storage_paths = glob.glob(os.path.join(home, "AppData", "Roaming", "*cursor*", "Local Storage", "leveldb", "*")) - local_storage_paths += glob.glob(os.path.join(home, "AppData", "Local", "*cursor*", "Local Storage", "leveldb", "*")) - elif system == "Darwin": - local_storage_paths = glob.glob(os.path.join(home, "Library", "Application Support", "*cursor*", "Local Storage", "leveldb", "*")) - elif system == "Linux": - local_storage_paths = glob.glob(os.path.join(home, ".config", "*cursor*", "Local Storage", "leveldb", "*")) + raise OSError(translator.get('reset.unsupported_os', system=system) if translator else f"不支持的操作系统: {system}") - for path in local_storage_paths: - if is_cursor_related(path): - remove_file(path) - - # Create new trial files with extended expiration - print(f"\n{Fore.CYAN}{EMOJI['RESET']} {translator.get('totally_reset.creating_new_trial_information_with_extended_period')} {Style.RESET_ALL}") - if system == "Windows": - create_fake_trial_info(os.path.join(home, "AppData", "Local", "Cursor", "trial_info.json"), system, home) - create_fake_trial_info(os.path.join(home, "AppData", "Roaming", "Cursor", "trial_info.json"), system, home) - elif system == "Darwin": - create_fake_trial_info(os.path.join(home, "Library", "Application Support", "Cursor", "trial_info.json"), system, home) - elif system == "Linux": - create_fake_trial_info(os.path.join(home, ".config", "Cursor", "trial_info.json"), system, home) - - print(f"\n{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('totally_reset.reset_log_1')}") - print(f" {translator.get('totally_reset.reset_log_2')}") - print(f" {translator.get('totally_reset.reset_log_3')}") + if not config.has_section(section) or not config.has_option(section, 'cursor_path'): + raise OSError(translator.get('reset.path_not_configured') if translator else "未配置 Cursor 路徑") - print(f"\n{Fore.GREEN}{EMOJI['INFO']} {translator.get('totally_reset.reset_log_4')} {Style.RESET_ALL}") - print(f" {translator.get('totally_reset.reset_log_5')} {Style.RESET_ALL}") - print(f" {translator.get('totally_reset.reset_log_6')} {Style.RESET_ALL}") - print(f" {translator.get('totally_reset.reset_log_7')} {Style.RESET_ALL}") - print(f" {translator.get('totally_reset.reset_log_8')} {Style.RESET_ALL}") + base_path = config.get(section, 'cursor_path') - print(f"\n{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.reset_log_9')} {Style.RESET_ALL}") + # For Linux, try to find the first existing path if the configured one doesn't exist + if system == "Linux" and not os.path.exists(base_path): + for path in default_paths["Linux"]: + if os.path.exists(path): + base_path = path + # Update config with the found path + config.set(section, 'cursor_path', path) + with open(config_file, 'w', encoding='utf-8') as f: + config.write(f) + break + + if not os.path.exists(base_path): + raise OSError(translator.get('reset.path_not_found', path=base_path) if translator else f"找不到 Cursor 路徑: {base_path}") + + pkg_path = os.path.join(base_path, "package.json") + main_path = os.path.join(base_path, "out/main.js") + + # Check if files exist + if not os.path.exists(pkg_path): + raise OSError(translator.get('reset.package_not_found', path=pkg_path) if translator else f"找不到 package.json: {pkg_path}") + if not os.path.exists(main_path): + raise OSError(translator.get('reset.main_not_found', path=main_path) if translator else f"找不到 main.js: {main_path}") + + return (pkg_path, main_path) -if __name__ == "__main__": +def get_cursor_machine_id_path(translator=None) -> str: + """ + Get Cursor machineId file path based on operating system + Returns: + str: Path to machineId file + """ + # Read configuration + config_dir = os.path.join(get_user_documents_path(), ".cursor-free-vip") + config_file = os.path.join(config_dir, "config.ini") + config = configparser.ConfigParser() + + if os.path.exists(config_file): + config.read(config_file) + + if sys.platform == "win32": # Windows + if not config.has_section('WindowsPaths'): + config.add_section('WindowsPaths') + config.set('WindowsPaths', 'machine_id_path', + os.path.join(os.getenv("APPDATA"), "Cursor", "machineId")) + return config.get('WindowsPaths', 'machine_id_path') + + elif sys.platform == "linux": # Linux + if not config.has_section('LinuxPaths'): + config.add_section('LinuxPaths') + config.set('LinuxPaths', 'machine_id_path', + os.path.expanduser("~/.config/cursor/machineid")) + return config.get('LinuxPaths', 'machine_id_path') + + elif sys.platform == "darwin": # macOS + if not config.has_section('MacPaths'): + config.add_section('MacPaths') + config.set('MacPaths', 'machine_id_path', + os.path.expanduser("~/Library/Application Support/Cursor/machineId")) + return config.get('MacPaths', 'machine_id_path') + + else: + raise OSError(f"Unsupported operating system: {sys.platform}") + + # Save any changes to config file + with open(config_file, 'w', encoding='utf-8') as f: + config.write(f) + +def get_workbench_cursor_path(translator=None) -> str: + """Get Cursor workbench.desktop.main.js path""" + system = platform.system() + + paths_map = { + "Darwin": { # macOS + "base": "/Applications/Cursor.app/Contents/Resources/app", + "main": "out/vs/workbench/workbench.desktop.main.js" + }, + "Windows": { + "base": os.path.join(os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app"), + "main": "out/vs/workbench/workbench.desktop.main.js" + }, + "Linux": { + "bases": ["/opt/Cursor/resources/app", "/usr/share/cursor/resources/app"], + "main": "out/vs/workbench/workbench.desktop.main.js" + } + } + + if system not in paths_map: + raise OSError(translator.get('reset.unsupported_os', system=system) if translator else f"不支持的操作系统: {system}") + + if system == "Linux": + for base in paths_map["Linux"]["bases"]: + main_path = os.path.join(base, paths_map["Linux"]["main"]) + if os.path.exists(main_path): + return main_path + raise OSError(translator.get('reset.linux_path_not_found') if translator else "在 Linux 系统上未找到 Cursor 安装路径") + + base_path = paths_map[system]["base"] + main_path = os.path.join(base_path, paths_map[system]["main"]) + + if not os.path.exists(main_path): + raise OSError(translator.get('reset.file_not_found', path=main_path) if translator else f"未找到 Cursor main.js 文件: {main_path}") + + return main_path + +def version_check(version: str, min_version: str = "", max_version: str = "", translator=None) -> bool: + """Version number check""" + version_pattern = r"^\d+\.\d+\.\d+$" try: - reset_cursor() - except KeyboardInterrupt: - print(f"\n\n{Fore.RED}{EMOJI['STOP']} {translator.get('totally_reset.keyboard_interrupt')} {Style.RESET_ALL}") - sys.exit(1) + if not re.match(version_pattern, version): + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.invalid_version_format', version=version)}{Style.RESET_ALL}") + return False + + def parse_version(ver: str) -> Tuple[int, ...]: + return tuple(map(int, ver.split("."))) + + current = parse_version(version) + + if min_version and current < parse_version(min_version): + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.version_too_low', version=version, min_version=min_version)}{Style.RESET_ALL}") + return False + + if max_version and current > parse_version(max_version): + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.version_too_high', version=version, max_version=max_version)}{Style.RESET_ALL}") + return False + + return True + except Exception as e: - print(f"\n{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.unexpected_error', error=str(e))}{Style.RESET_ALL}") - print(f" {translator.get('totally_reset.report_issue')}") - sys.exit(1) + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.version_check_error', error=str(e))}{Style.RESET_ALL}") + return False + +def check_cursor_version(translator) -> bool: + """Check Cursor version""" + try: + pkg_path, _ = get_cursor_paths(translator) + print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('reset.reading_package_json', path=pkg_path)}{Style.RESET_ALL}") + + try: + with open(pkg_path, "r", encoding="utf-8") as f: + data = json.load(f) + except UnicodeDecodeError: + # If UTF-8 reading fails, try other encodings + with open(pkg_path, "r", encoding="latin-1") as f: + data = json.load(f) + + if not isinstance(data, dict): + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.invalid_json_object')}{Style.RESET_ALL}") + return False + + if "version" not in data: + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.no_version_field')}{Style.RESET_ALL}") + return False + + version = str(data["version"]).strip() + if not version: + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.version_field_empty')}{Style.RESET_ALL}") + return False + + print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('reset.found_version', version=version)}{Style.RESET_ALL}") + + # Check version format + if not re.match(r"^\d+\.\d+\.\d+$", version): + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.invalid_version_format', version=version)}{Style.RESET_ALL}") + return False + + # Compare versions + try: + current = tuple(map(int, version.split("."))) + min_ver = (0, 45, 0) # Use tuple directly instead of string + + if current >= min_ver: + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('reset.version_check_passed', version=version, min_version='0.45.0')}{Style.RESET_ALL}") + return True + else: + print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('reset.version_too_low', version=version, min_version='0.45.0')}{Style.RESET_ALL}") + return False + except ValueError as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.version_parse_error', error=str(e))}{Style.RESET_ALL}") + return False + + except FileNotFoundError as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.package_not_found', path=pkg_path)}{Style.RESET_ALL}") + return False + except json.JSONDecodeError as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.invalid_json_object')}{Style.RESET_ALL}") + return False + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.check_version_failed', error=str(e))}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('reset.stack_trace')}: {traceback.format_exc()}{Style.RESET_ALL}") + return False + +def modify_workbench_js(file_path: str, translator=None) -> bool: + """ + Modify file content + """ + try: + # Save original file permissions + original_stat = os.stat(file_path) + original_mode = original_stat.st_mode + original_uid = original_stat.st_uid + original_gid = original_stat.st_gid + + # Create temporary file + with tempfile.NamedTemporaryFile(mode="w", encoding="utf-8", errors="ignore", delete=False) as tmp_file: + # Read original content + with open(file_path, "r", encoding="utf-8", errors="ignore") as main_file: + content = main_file.read() + + if sys.platform == "win32": + # Define replacement patterns + CButton_old_pattern = r'$(k,E(Ks,{title:"Upgrade to Pro",size:"small",get codicon(){return F.rocket},get onClick(){return t.pay}}),null)' + CButton_new_pattern = r'$(k,E(Ks,{title:"yeongpin GitHub",size:"small",get codicon(){return F.rocket},get onClick(){return function(){window.open("https://github.com/yeongpin/cursor-free-vip","_blank")}}}),null)' + elif sys.platform == "linux": + CButton_old_pattern = r'$(k,E(Ks,{title:"Upgrade to Pro",size:"small",get codicon(){return F.rocket},get onClick(){return t.pay}}),null)' + CButton_new_pattern = r'$(k,E(Ks,{title:"yeongpin GitHub",size:"small",get codicon(){return F.rocket},get onClick(){return function(){window.open("https://github.com/yeongpin/cursor-free-vip","_blank")}}}),null)' + elif sys.platform == "darwin": + CButton_old_pattern = r'M(x,I(as,{title:"Upgrade to Pro",size:"small",get codicon(){return $.rocket},get onClick(){return t.pay}}),null)' + CButton_new_pattern = r'M(x,I(as,{title:"yeongpin GitHub",size:"small",get codicon(){return $.rocket},get onClick(){return function(){window.open("https://github.com/yeongpin/cursor-free-vip","_blank")}}}),null)' + + CBadge_old_pattern = r'
Pro Trial' + CBadge_new_pattern = r'
Pro' + + CToast_old_pattern = r'notifications-toasts' + CToast_new_pattern = r'notifications-toasts hidden' + + # Replace content + content = content.replace(CButton_old_pattern, CButton_new_pattern) + content = content.replace(CBadge_old_pattern, CBadge_new_pattern) + content = content.replace(CToast_old_pattern, CToast_new_pattern) + + # Write to temporary file + tmp_file.write(content) + tmp_path = tmp_file.name + + # Backup original file + backup_path = file_path + ".backup" + if os.path.exists(backup_path): + os.remove(backup_path) + shutil.copy2(file_path, backup_path) + + # Move temporary file to original position + if os.path.exists(file_path): + os.remove(file_path) + shutil.move(tmp_path, file_path) + + # Restore original permissions + os.chmod(file_path, original_mode) + if os.name != "nt": # Not Windows + os.chown(file_path, original_uid, original_gid) + + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('reset.file_modified')}{Style.RESET_ALL}") + return True + + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.modify_file_failed', error=str(e))}{Style.RESET_ALL}") + if "tmp_path" in locals(): + try: + os.unlink(tmp_path) + except: + pass + return False + +def modify_main_js(main_path: str, translator) -> bool: + """Modify main.js file""" + try: + original_stat = os.stat(main_path) + original_mode = original_stat.st_mode + original_uid = original_stat.st_uid + original_gid = original_stat.st_gid + + with tempfile.NamedTemporaryFile(mode="w", delete=False) as tmp_file: + with open(main_path, "r", encoding="utf-8") as main_file: + content = main_file.read() + + patterns = { + r"async getMachineId\(\)\{return [^??]+\?\?([^}]+)\}": r"async getMachineId(){return \1}", + r"async getMacMachineId\(\)\{return [^??]+\?\?([^}]+)\}": r"async getMacMachineId(){return \1}", + } + + for pattern, replacement in patterns.items(): + content = re.sub(pattern, replacement, content) + + tmp_file.write(content) + tmp_path = tmp_file.name + + shutil.copy2(main_path, main_path + ".old") + shutil.move(tmp_path, main_path) + + os.chmod(main_path, original_mode) + if os.name != "nt": + os.chown(main_path, original_uid, original_gid) + + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('reset.file_modified')}{Style.RESET_ALL}") + return True + + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.modify_file_failed', error=str(e))}{Style.RESET_ALL}") + if "tmp_path" in locals(): + os.unlink(tmp_path) + return False + +def patch_cursor_get_machine_id(translator) -> bool: + """Patch Cursor getMachineId function""" + try: + print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('reset.start_patching')}...{Style.RESET_ALL}") + + # Get paths + pkg_path, main_path = get_cursor_paths(translator) + + # Check file permissions + for file_path in [pkg_path, main_path]: + if not os.path.isfile(file_path): + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.file_not_found', path=file_path)}{Style.RESET_ALL}") + return False + if not os.access(file_path, os.W_OK): + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.no_write_permission', path=file_path)}{Style.RESET_ALL}") + return False + + # Get version number + try: + with open(pkg_path, "r", encoding="utf-8") as f: + version = json.load(f)["version"] + print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('reset.current_version', version=version)}{Style.RESET_ALL}") + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.read_version_failed', error=str(e))}{Style.RESET_ALL}") + return False + + # Check version + if not version_check(version, min_version="0.45.0", translator=translator): + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.version_not_supported')}{Style.RESET_ALL}") + return False + + print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('reset.version_check_passed')}{Style.RESET_ALL}") + + # Backup file + backup_path = main_path + ".bak" + if not os.path.exists(backup_path): + shutil.copy2(main_path, backup_path) + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('reset.backup_created', path=backup_path)}{Style.RESET_ALL}") + + # Modify file + if not modify_main_js(main_path, translator): + return False + + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('reset.patch_completed')}{Style.RESET_ALL}") + return True + + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.patch_failed', error=str(e))}{Style.RESET_ALL}") + return False + +class MachineIDResetter: + def __init__(self, translator=None): + self.translator = translator + + # Read configuration + config_dir = os.path.join(get_user_documents_path(), ".cursor-free-vip") + config_file = os.path.join(config_dir, "config.ini") + config = configparser.ConfigParser() + + if not os.path.exists(config_file): + raise FileNotFoundError(f"Config file not found: {config_file}") + + config.read(config_file, encoding='utf-8') + + # Check operating system + if sys.platform == "win32": # Windows + appdata = os.getenv("APPDATA") + if appdata is None: + raise EnvironmentError("APPDATA Environment Variable Not Set") + + if not config.has_section('WindowsPaths'): + config.add_section('WindowsPaths') + config.set('WindowsPaths', 'storage_path', os.path.join( + appdata, "Cursor", "User", "globalStorage", "storage.json" + )) + config.set('WindowsPaths', 'sqlite_path', os.path.join( + appdata, "Cursor", "User", "globalStorage", "state.vscdb" + )) + + self.db_path = config.get('WindowsPaths', 'storage_path') + self.sqlite_path = config.get('WindowsPaths', 'sqlite_path') + + elif sys.platform == "darwin": # macOS + if not config.has_section('MacPaths'): + config.add_section('MacPaths') + config.set('MacPaths', 'storage_path', os.path.abspath(os.path.expanduser( + "~/Library/Application Support/Cursor/User/globalStorage/storage.json" + ))) + config.set('MacPaths', 'sqlite_path', os.path.abspath(os.path.expanduser( + "~/Library/Application Support/Cursor/User/globalStorage/state.vscdb" + ))) + + self.db_path = config.get('MacPaths', 'storage_path') + self.sqlite_path = config.get('MacPaths', 'sqlite_path') + + elif sys.platform == "linux": # Linux + if not config.has_section('LinuxPaths'): + config.add_section('LinuxPaths') + # Get actual user's home directory + sudo_user = os.environ.get('SUDO_USER') + actual_home = f"/home/{sudo_user}" if sudo_user else os.path.expanduser("~") + + config.set('LinuxPaths', 'storage_path', os.path.abspath(os.path.join( + actual_home, + ".config/cursor/User/globalStorage/storage.json" + ))) + config.set('LinuxPaths', 'sqlite_path', os.path.abspath(os.path.join( + actual_home, + ".config/cursor/User/globalStorage/state.vscdb" + ))) + + self.db_path = config.get('LinuxPaths', 'storage_path') + self.sqlite_path = config.get('LinuxPaths', 'sqlite_path') + + else: + raise NotImplementedError(f"Not Supported OS: {sys.platform}") + + # Save any changes to config file + with open(config_file, 'w', encoding='utf-8') as f: + config.write(f) + + def generate_new_ids(self): + """Generate new machine ID""" + # Generate new UUID + dev_device_id = str(uuid.uuid4()) + + # Generate new machineId (64 characters of hexadecimal) + machine_id = hashlib.sha256(os.urandom(32)).hexdigest() + + # Generate new macMachineId (128 characters of hexadecimal) + mac_machine_id = hashlib.sha512(os.urandom(64)).hexdigest() + + # Generate new sqmId + sqm_id = "{" + str(uuid.uuid4()).upper() + "}" + + self.update_machine_id_file(dev_device_id) + + return { + "telemetry.devDeviceId": dev_device_id, + "telemetry.macMachineId": mac_machine_id, + "telemetry.machineId": machine_id, + "telemetry.sqmId": sqm_id, + "storage.serviceMachineId": dev_device_id, # Add storage.serviceMachineId + } + + def update_sqlite_db(self, new_ids): + """Update machine ID in SQLite database""" + try: + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('reset.updating_sqlite')}...{Style.RESET_ALL}") + + conn = sqlite3.connect(self.sqlite_path) + cursor = conn.cursor() + + cursor.execute(""" + CREATE TABLE IF NOT EXISTS ItemTable ( + key TEXT PRIMARY KEY, + value TEXT + ) + """) + + updates = [ + (key, value) for key, value in new_ids.items() + ] + + for key, value in updates: + cursor.execute(""" + INSERT OR REPLACE INTO ItemTable (key, value) + VALUES (?, ?) + """, (key, value)) + print(f"{EMOJI['INFO']} {Fore.CYAN} {self.translator.get('reset.updating_pair')}: {key}{Style.RESET_ALL}") + + conn.commit() + conn.close() + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.sqlite_success')}{Style.RESET_ALL}") + return True + + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.sqlite_error', error=str(e))}{Style.RESET_ALL}") + return False + + def update_system_ids(self, new_ids): + """Update system-level IDs""" + try: + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('reset.updating_system_ids')}...{Style.RESET_ALL}") + + if sys.platform.startswith("win"): + self._update_windows_machine_guid() + self._update_windows_machine_id() + elif sys.platform == "darwin": + self._update_macos_platform_uuid(new_ids) + + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.system_ids_updated')}{Style.RESET_ALL}") + return True + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.system_ids_update_failed', error=str(e))}{Style.RESET_ALL}") + return False + + def _update_windows_machine_guid(self): + """Update Windows MachineGuid""" + try: + import winreg + key = winreg.OpenKey( + winreg.HKEY_LOCAL_MACHINE, + "SOFTWARE\\Microsoft\\Cryptography", + 0, + winreg.KEY_WRITE | winreg.KEY_WOW64_64KEY + ) + new_guid = str(uuid.uuid4()) + winreg.SetValueEx(key, "MachineGuid", 0, winreg.REG_SZ, new_guid) + winreg.CloseKey(key) + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.windows_machine_guid_updated')}{Style.RESET_ALL}") + except PermissionError: + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.permission_denied')}{Style.RESET_ALL}") + raise + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.update_windows_machine_guid_failed', error=str(e))}{Style.RESET_ALL}") + raise + + def _update_windows_machine_id(self): + """Update Windows MachineId in SQMClient registry""" + try: + import winreg + # 1. Generate new GUID + new_guid = "{" + str(uuid.uuid4()).upper() + "}" + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('reset.new_machine_id')}: {new_guid}{Style.RESET_ALL}") + + # 2. Open the registry key + try: + key = winreg.OpenKey( + winreg.HKEY_LOCAL_MACHINE, + r"SOFTWARE\Microsoft\SQMClient", + 0, + winreg.KEY_WRITE | winreg.KEY_WOW64_64KEY + ) + except FileNotFoundError: + # If the key does not exist, create it + key = winreg.CreateKey( + winreg.HKEY_LOCAL_MACHINE, + r"SOFTWARE\Microsoft\SQMClient" + ) + + # 3. Set MachineId value + winreg.SetValueEx(key, "MachineId", 0, winreg.REG_SZ, new_guid) + winreg.CloseKey(key) + + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.windows_machine_id_updated')}{Style.RESET_ALL}") + return True + + except PermissionError: + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.permission_denied')}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['WARNING']} {self.translator.get('reset.run_as_admin')}{Style.RESET_ALL}") + return False + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.update_windows_machine_id_failed', error=str(e))}{Style.RESET_ALL}") + return False + + + def _update_macos_platform_uuid(self, new_ids): + """Update macOS Platform UUID""" + try: + uuid_file = "/var/root/Library/Preferences/SystemConfiguration/com.apple.platform.uuid.plist" + if os.path.exists(uuid_file): + # Use sudo to execute plutil command + cmd = f'sudo plutil -replace "UUID" -string "{new_ids["telemetry.macMachineId"]}" "{uuid_file}"' + result = os.system(cmd) + if result == 0: + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.macos_platform_uuid_updated')}{Style.RESET_ALL}") + else: + raise Exception(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.failed_to_execute_plutil_command')}{Style.RESET_ALL}") + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.update_macos_platform_uuid_failed', error=str(e))}{Style.RESET_ALL}") + raise + + def reset_machine_ids(self): + """Reset machine ID and backup original file""" + try: + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('reset.checking')}...{Style.RESET_ALL}") + + if not os.path.exists(self.db_path): + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.not_found')}: {self.db_path}{Style.RESET_ALL}") + return False + + if not os.access(self.db_path, os.R_OK | os.W_OK): + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.no_permission')}{Style.RESET_ALL}") + return False + + print(f"{Fore.CYAN}{EMOJI['FILE']} {self.translator.get('reset.reading')}...{Style.RESET_ALL}") + with open(self.db_path, "r", encoding="utf-8") as f: + config = json.load(f) + + backup_path = self.db_path + ".bak" + if not os.path.exists(backup_path): + print(f"{Fore.YELLOW}{EMOJI['BACKUP']} {self.translator.get('reset.creating_backup')}: {backup_path}{Style.RESET_ALL}") + shutil.copy2(self.db_path, backup_path) + else: + print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('reset.backup_exists')}{Style.RESET_ALL}") + + print(f"{Fore.CYAN}{EMOJI['RESET']} {self.translator.get('reset.generating')}...{Style.RESET_ALL}") + new_ids = self.generate_new_ids() + + # Update configuration file + config.update(new_ids) + + print(f"{Fore.CYAN}{EMOJI['FILE']} {self.translator.get('reset.saving_json')}...{Style.RESET_ALL}") + with open(self.db_path, "w", encoding="utf-8") as f: + json.dump(config, f, indent=4) + + # Update SQLite database + self.update_sqlite_db(new_ids) + + # Update system IDs + self.update_system_ids(new_ids) + + + # Modify workbench.desktop.main.js + workbench_path = get_workbench_cursor_path(self.translator) + modify_workbench_js(workbench_path, self.translator) + + # Check Cursor version and perform corresponding actions + + greater_than_0_45 = check_cursor_version(self.translator) + if greater_than_0_45: + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('reset.detecting_version')} >= 0.45.0,{self.translator.get('reset.patching_getmachineid')}{Style.RESET_ALL}") + patch_cursor_get_machine_id(self.translator) + else: + print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('reset.version_less_than_0_45')}{Style.RESET_ALL}") + + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.success')}{Style.RESET_ALL}") + print(f"\n{Fore.CYAN}{self.translator.get('reset.new_id')}:{Style.RESET_ALL}") + for key, value in new_ids.items(): + print(f"{EMOJI['INFO']} {key}: {Fore.GREEN}{value}{Style.RESET_ALL}") + + return True + + except PermissionError as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.permission_error', error=str(e))}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('reset.run_as_admin')}{Style.RESET_ALL}") + return False + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.process_error', error=str(e))}{Style.RESET_ALL}") + return False + + def update_machine_id_file(self, machine_id: str) -> bool: + """ + Update machineId file with new machine_id + Args: + machine_id (str): New machine ID to write + Returns: + bool: True if successful, False otherwise + """ + try: + # Get the machineId file path + machine_id_path = get_cursor_machine_id_path() + + # Create directory if it doesn't exist + os.makedirs(os.path.dirname(machine_id_path), exist_ok=True) + + # Create backup if file exists + if os.path.exists(machine_id_path): + backup_path = machine_id_path + ".backup" + try: + shutil.copy2(machine_id_path, backup_path) + print(f"{Fore.GREEN}{EMOJI['INFO']} {self.translator.get('reset.backup_created', path=backup_path) if self.translator else f'Backup created at: {backup_path}'}{Style.RESET_ALL}") + except Exception as e: + print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('reset.backup_creation_failed', error=str(e)) if self.translator else f'Could not create backup: {str(e)}'}{Style.RESET_ALL}") + + # Write new machine ID to file + with open(machine_id_path, "w", encoding="utf-8") as f: + f.write(machine_id) + + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.update_success') if self.translator else 'Successfully updated machineId file'}{Style.RESET_ALL}") + return True + + except Exception as e: + error_msg = f"Failed to update machineId file: {str(e)}" + if self.translator: + error_msg = self.translator.get('reset.update_failed', error=str(e)) + print(f"{Fore.RED}{EMOJI['ERROR']} {error_msg}{Style.RESET_ALL}") + return False def run(translator=None): - """Entry point for the totally reset cursor functionality when called from the main menu.""" - try: - reset_cursor() - input(f"\n\n{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.return_to_main_menu')} {Style.RESET_ALL}") - except KeyboardInterrupt: - print(f"\n\n{Fore.RED}{EMOJI['STOP']} {translator.get('totally_reset.process_interrupted')} {Style.RESET_ALL}") - except Exception as e: - print(f"\n{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.unexpected_error', error=str(e))}{Style.RESET_ALL}") - print(f" {translator.get('totally_reset.report_issue')}") - input(f"\n{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.press_enter_to_return_to_main_menu')} {Style.RESET_ALL}") \ No newline at end of file + config = get_config(translator) + if not config: + return False + print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['RESET']} {translator.get('reset.title')}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{'='*50}{Style.RESET_ALL}") + + resetter = MachineIDResetter(translator) # Correctly pass translator + resetter.reset_machine_ids() + + print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}") + input(f"{EMOJI['INFO']} {translator.get('reset.press_enter')}...") + +if __name__ == "__main__": + from main import translator as main_translator + run(main_translator)