import os import shutil import platform import time import sys import glob import json import uuid import random import string import re from datetime import datetime import subprocess from colorama import Fore, Style, init from main import translator from main import EMOJI # Initialize colorama init() # Define emoji and color constants EMOJI = { "FILE": "šŸ“„", "BACKUP": "šŸ’¾", "SUCCESS": "āœ…", "ERROR": "āŒ", "INFO": "ā„¹ļø", "RESET": "šŸ”„", "MENU": "šŸ“‹", "ARROW": "āžœ", "LANG": "🌐", "UPDATE": "šŸ”„", "ADMIN": "šŸ”", "STOP": "šŸ›‘", "DISCLAIMER": "āš ļø", "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.""" 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"), ] # 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"), ] # 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"), ] 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) 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) 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", "*")) 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')}") 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}") print(f"\n{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.reset_log_9')} {Style.RESET_ALL}") if __name__ == "__main__": 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) 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) 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}")