diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e63c55..09af72a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,10 @@ 1. Add: Cursor Account Info | 增加 Cursor 賬號信息 2. Fix: Disable Auto Update | 修復禁用自動更新 3. Add: 0.48.x Version Support | 增加 0.48.x 版本支持 -4. Fix: Some Issues | 修復一些問題 +4. Revert: Totally Reser Cursor to Beta | 恢復完全重置 Cursor 到 Beta +5. Reopen: Totally Reset Cursor | 重新開啟完全重置 Cursor +6. Fix: Logo.py Center | 修復 Logo.py 居中 +7. Fix: Some Issues | 修復一些問題 ## v1.7.18 1. Fix: No Write Permission | 修復沒有寫入權限 diff --git a/logo.py b/logo.py index 83f4c76..7e8f1cd 100644 --- a/logo.py +++ b/logo.py @@ -20,7 +20,7 @@ init() # get terminal width def get_terminal_width(): try: - columns, _ = shutil.get_terminal_size() + columns, _ = shutil.get_terminal_size()/2 return columns except: return 80 # default width @@ -34,7 +34,7 @@ def center_multiline_text(text, handle_chinese=False): for line in lines: # calculate actual display width (remove ANSI color codes) clean_line = line - for color in [Fore.CYAN, Fore.YELLOW, Fore.GREEN, Fore.RED, Style.RESET_ALL]: + for color in [Fore.CYAN, Fore.YELLOW, Fore.GREEN, Fore.RED, Fore.BLUE, Style.RESET_ALL]: clean_line = clean_line.replace(color, '') # remove all ANSI escape sequences to get the actual length diff --git a/main.py b/main.py index 34e524d..4a0af37 100644 --- a/main.py +++ b/main.py @@ -504,8 +504,8 @@ def main(): print_menu() elif choice == "10": import totally_reset_cursor - # totally_reset_cursor.main(translator) - print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('menu.fixed_soon')}{Style.RESET_ALL}") + totally_reset_cursor.run(translator) + # print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('menu.fixed_soon')}{Style.RESET_ALL}") print_menu() else: print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('menu.invalid_choice')}{Style.RESET_ALL}") diff --git a/totally_reset_cursor.py b/totally_reset_cursor.py index 1f62a75..378b776 100644 --- a/totally_reset_cursor.py +++ b/totally_reset_cursor.py @@ -2,195 +2,508 @@ 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 delete_directory(path, translator=None): - """Deletes a directory and all its contents.""" +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) - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('totally_reset.removed', path=path)}{Style.RESET_ALL}") + 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.failed_to_remove', path=path, error=e)}{Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.error_deleting', path=path, error=str(e))} {Style.RESET_ALL}") else: - print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('totally_reset.not_found', path=path)}{Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.not_found', path=path)} {Style.RESET_ALL}") -def delete_file(path, translator=None): - """Deletes a file if it exists.""" +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.removed', path=path)}{Style.RESET_ALL}") + 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.failed_to_remove', path=path, error=e)}{Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.error_deleting', path=path, error=str(e))} {Style.RESET_ALL}") else: - print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('totally_reset.not_found', path=path)}{Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.not_found', path=path)} {Style.RESET_ALL}") -def reset_machine_id(translator=None): - """Resets the machine ID to a new UUID.""" - new_id = str(uuid.uuid4()) - if platform.system() == "Windows": - try: - subprocess.run( - ["reg", "add", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Cryptography", "/v", "MachineGuid", "/d", new_id, "/f"], - check=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE - ) - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('totally_reset.machine_guid_reset', new_id=new_id)}{Style.RESET_ALL}") - except subprocess.CalledProcessError as e: - print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.failed_to_reset_machine_guid', error=e)}{Style.RESET_ALL}") - elif platform.system() == "Linux": - machine_id_paths = ["/etc/machine-id", "/var/lib/dbus/machine-id"] - for path in machine_id_paths: - if os.path.exists(path): - try: - with open(path, 'w') as f: - f.write(new_id) - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('totally_reset.machine_id_reset', path=path)}{Style.RESET_ALL}") - except Exception as e: - print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.failed_to_reset_machine_id', path=path, error=e)}{Style.RESET_ALL}") - elif platform.system() == "Darwin": # macOS - print("ℹ️ macOS does not use a machine-id file. Skipping machine ID reset.") - else: - print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.unsupported_os')}{Style.RESET_ALL}") - -def display_features_and_warnings(translator=None): - """Displays features and warnings before proceeding.""" - print(f"\n{Fore.GREEN}{EMOJI['MENU']} {translator.get('totally_reset.title')}") - print("=====================================") - print(f"{translator.get('totally_reset.feature_title')}") - print(f"{Fore.GREEN}{translator.get('totally_reset.feature_1')}") - print(f"{Fore.GREEN}{translator.get('totally_reset.feature_2')}") - print(f"{Fore.GREEN}{translator.get('totally_reset.feature_3')}") - print(f"{Fore.GREEN}{translator.get('totally_reset.feature_4')}") - print(f"{Fore.GREEN}{translator.get('totally_reset.feature_5')}") - print(f"{Fore.GREEN}{translator.get('totally_reset.feature_6')}") - print(f"{Fore.GREEN}{translator.get('totally_reset.feature_7')}") - print(f"{Fore.GREEN}{translator.get('totally_reset.feature_8')}") - print(f"{Fore.GREEN}{translator.get('totally_reset.feature_9')}") - print(f"\n{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('totally_reset.warning_title')}") - print(f"{Fore.YELLOW}{translator.get('totally_reset.warning_1')}") - print(f"{Fore.YELLOW}{translator.get('totally_reset.warning_2')}") - print(f"{Fore.YELLOW}{translator.get('totally_reset.warning_3')}") - print(f"{Fore.YELLOW}{translator.get('totally_reset.warning_4')}") - print(f"{Fore.YELLOW}{translator.get('totally_reset.warning_5')}") - print(f"{Fore.YELLOW}{translator.get('totally_reset.warning_6')}") - print(f"{Fore.YELLOW}{translator.get('totally_reset.warning_7')}") - print("=====================================\n") - -def get_user_confirmation(translator=None): - """Prompts the user for confirmation to proceed.""" - while True: - response = input(f"{Fore.YELLOW} {translator.get('totally_reset.confirm_title')} {translator.get('totally_reset.invalid_choice')}: ").lower().strip() - if response in ['yes', 'y']: +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 - elif response in ['no', 'n']: - return False - else: - print(f"{Fore.RED}{translator.get('totally_reset.invalid_choice')}{Style.RESET_ALL}") + + # 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 reset_cursor(translator=None): - print(f"\n{Fore.GREEN}{EMOJI['RESET']} {translator.get('totally_reset.resetting_cursor')}\n") +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 [] - # Platform-specific paths - paths = [] - if platform.system() == "Linux": - paths = [ - os.path.expanduser("~/.cursor"), - os.path.expanduser("~/.local/share/cursor"), - os.path.expanduser("~/.config/cursor"), - os.path.expanduser("~/.cache/cursor"), - "/usr/local/bin/cursor", - "/opt/cursor", - "/usr/bin/cursor", - os.path.expanduser("~/.cursor/machine-id.db"), - os.path.expanduser("~/.local/share/Cursor"), - os.path.expanduser("~/.config/Cursor"), - os.path.expanduser("~/.cache/Cursor") +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 platform.system() == "Darwin": # macOS - paths = [ - os.path.expanduser("~/Library/Application Support/Cursor"), - os.path.expanduser("~/Library/Caches/Cursor"), - "/Applications/Cursor.app", - os.path.expanduser("~/Library/Preferences/com.cursor.app.plist"), + 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 platform.system() == "Windows": - paths = [ - os.path.expanduser("~\\AppData\\Local\\Cursor"), - os.path.expanduser("~\\AppData\\Roaming\\Cursor"), - os.path.expanduser("~\\.cursor"), - os.path.expanduser("~\\.config\\Cursor"), - os.path.expanduser("~\\.cache\\Cursor"), - "C:\\Program Files\\Cursor", - "C:\\Program Files (x86)\\Cursor", - "C:\\Users\\%USERNAME%\\AppData\\Local\\Cursor", - "C:\\Users\\%USERNAME%\\AppData\\Roaming\\Cursor", + 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"), ] - # Remove directories - for path in paths: - delete_directory(path, translator) + 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"), + ] - # Remove common files related to Cursor - files = [ - os.path.expanduser("~/.cursor/machine-id.db"), - os.path.expanduser("~/.local/share/cursor.db"), - os.path.expanduser("~/.config/cursor/preferences.json"), - os.path.expanduser("~/.cache/cursor.log"), + 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" ] - for file in files: - delete_file(file, translator) + # 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"), + ] - # Extra cleanup (wildcard search) - print(f"\n{Fore.YELLOW}{EMOJI['INFO']} {translator.get('totally_reset.deep_scanning')}") - base_dirs = ["/tmp", "/var/tmp", os.path.expanduser("~")] # Linux and macOS - if platform.system() == "Windows": - base_dirs = ["C:\\Temp", "C:\\Windows\\Temp", os.path.expanduser("~")] # Windows + # 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"), + ]) - for base in base_dirs: - for root, dirs, files in os.walk(base): - for dir in dirs: - if "cursor" in dir.lower(): - delete_directory(os.path.join(root, dir), translator) - for file in files: - if "cursor" in file.lower(): - delete_file(os.path.join(root, file), translator) + # 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) - # Reset machine ID - reset_machine_id(translator) - - print(f"\n{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('totally_reset.cursor_reset_completed')}") - -def main(translator=None): - start_time = time.time() + # 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) - # Display features and warnings - display_features_and_warnings(translator) - - # Get user confirmation - if get_user_confirmation(translator): - reset_cursor(translator) - end_time = time.time() - print(f"\n{Fore.GREEN}⏱️ {translator.get('totally_reset.completed_in', time=f'{end_time - start_time:.2f} seconds')}{Style.RESET_ALL}") + 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.RED}❌ {translator.get('totally_reset.operation_cancelled')}{Style.RESET_ALL}") + print(f"\n{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.no_additional_license_trial_files_found_in_deep_scan')} {Style.RESET_ALL}") -if __name__ == '__main__': - from main import translator - main(translator) + # 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}") \ No newline at end of file