Pro',
r'py-1">Auto-select': r'py-1">Bypass-Version-Pin',
#
r'async getEffectiveTokenLimit(e){const n=e.modelName;if(!n)return 2e5;':r'async getEffectiveTokenLimit(e){return 9000000;const n=e.modelName;if(!n)return 9e5;',
# Pro
r'var DWr=ne("
You are currently signed in with
.");': r'var DWr=ne("
You are currently signed in with .
Pro
");',
# Toast 替换
r'notifications-toasts': r'notifications-toasts hidden'
}
# 使用patterns进行替换
for old_pattern, new_pattern in patterns.items():
content = content.replace(old_pattern, new_pattern)
# Write to temporary file
tmp_file.write(content)
tmp_path = tmp_file.name
# Backup original file with timestamp
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
backup_path = f"{file_path}.backup.{timestamp}"
shutil.copy2(file_path, backup_path)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('reset.backup_created', path=backup_path)}{Style.RESET_ALL}")
# 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 run(translator=None):
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('bypass_token_limit.title')}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
modify_workbench_js(get_workbench_cursor_path(translator), translator)
print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
input(f"{EMOJI['INFO']} {translator.get('bypass_token_limit.press_enter')}...")
if __name__ == "__main__":
from main import translator as main_translator
run(main_translator)
```
## /bypass_version.py
```py path="/bypass_version.py"
import os
import json
import shutil
import platform
import configparser
import time
from colorama import Fore, Style, init
import sys
import traceback
from utils import get_user_documents_path
# Initialize colorama
init()
# Define emoji constants
EMOJI = {
'INFO': 'ℹ️',
'SUCCESS': '✅',
'ERROR': '❌',
'WARNING': '⚠️',
'FILE': '📄',
'BACKUP': '💾',
'RESET': '🔄',
'VERSION': '🏷️'
}
def get_product_json_path(translator=None):
"""Get Cursor product.json path"""
system = platform.system()
# 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 system == "Windows":
localappdata = os.environ.get("LOCALAPPDATA")
if not localappdata:
raise OSError(translator.get('bypass.localappdata_not_found') if translator else "LOCALAPPDATA environment variable not found")
product_json_path = os.path.join(localappdata, "Programs", "Cursor", "resources", "app", "product.json")
# Check if path exists in config
if 'WindowsPaths' in config and 'cursor_path' in config['WindowsPaths']:
cursor_path = config.get('WindowsPaths', 'cursor_path')
product_json_path = os.path.join(cursor_path, "product.json")
elif system == "Darwin": # macOS
product_json_path = "/Applications/Cursor.app/Contents/Resources/app/product.json"
if config.has_section('MacPaths') and config.has_option('MacPaths', 'product_json_path'):
product_json_path = config.get('MacPaths', 'product_json_path')
elif system == "Linux":
# Try multiple common paths
possible_paths = [
"/opt/Cursor/resources/app/product.json",
"/usr/share/cursor/resources/app/product.json",
"/usr/lib/cursor/app/product.json"
]
# Add extracted AppImage paths
extracted_usr_paths = os.path.expanduser("~/squashfs-root/usr/share/cursor/resources/app/product.json")
if os.path.exists(extracted_usr_paths):
possible_paths.append(extracted_usr_paths)
for path in possible_paths:
if os.path.exists(path):
product_json_path = path
break
else:
raise OSError(translator.get('bypass.product_json_not_found') if translator else "product.json not found in common Linux paths")
else:
raise OSError(translator.get('bypass.unsupported_os', system=system) if translator else f"Unsupported operating system: {system}")
if not os.path.exists(product_json_path):
raise OSError(translator.get('bypass.file_not_found', path=product_json_path) if translator else f"File not found: {product_json_path}")
return product_json_path
def compare_versions(version1, version2):
"""Compare two version strings"""
v1_parts = [int(x) for x in version1.split('.')]
v2_parts = [int(x) for x in version2.split('.')]
for i in range(max(len(v1_parts), len(v2_parts))):
v1 = v1_parts[i] if i < len(v1_parts) else 0
v2 = v2_parts[i] if i < len(v2_parts) else 0
if v1 < v2:
return -1
elif v1 > v2:
return 1
return 0
def bypass_version(translator=None):
"""Bypass Cursor version check by modifying product.json"""
try:
print(f"\n{Fore.CYAN}{EMOJI['INFO']} {translator.get('bypass.starting') if translator else 'Starting Cursor version bypass...'}{Style.RESET_ALL}")
# Get product.json path
product_json_path = get_product_json_path(translator)
print(f"{Fore.CYAN}{EMOJI['FILE']} {translator.get('bypass.found_product_json', path=product_json_path) if translator else f'Found product.json: {product_json_path}'}{Style.RESET_ALL}")
# Check file permissions
if not os.access(product_json_path, os.W_OK):
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('bypass.no_write_permission', path=product_json_path) if translator else f'No write permission for file: {product_json_path}'}{Style.RESET_ALL}")
return False
# Read product.json
try:
with open(product_json_path, "r", encoding="utf-8") as f:
product_data = json.load(f)
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('bypass.read_failed', error=str(e)) if translator else f'Failed to read product.json: {str(e)}'}{Style.RESET_ALL}")
return False
# Get current version
current_version = product_data.get("version", "0.0.0")
print(f"{Fore.CYAN}{EMOJI['VERSION']} {translator.get('bypass.current_version', version=current_version) if translator else f'Current version: {current_version}'}{Style.RESET_ALL}")
# Check if version needs to be modified
if compare_versions(current_version, "0.46.0") < 0:
# Create backup
timestamp = time.strftime("%Y%m%d%H%M%S")
backup_path = f"{product_json_path}.{timestamp}"
shutil.copy2(product_json_path, backup_path)
print(f"{Fore.GREEN}{EMOJI['BACKUP']} {translator.get('bypass.backup_created', path=backup_path) if translator else f'Backup created: {backup_path}'}{Style.RESET_ALL}")
# Modify version
new_version = "0.48.7"
product_data["version"] = new_version
# Save modified product.json
try:
with open(product_json_path, "w", encoding="utf-8") as f:
json.dump(product_data, f, indent=2)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('bypass.version_updated', old=current_version, new=new_version) if translator else f'Version updated from {current_version} to {new_version}'}{Style.RESET_ALL}")
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('bypass.write_failed', error=str(e)) if translator else f'Failed to write product.json: {str(e)}'}{Style.RESET_ALL}")
return False
else:
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('bypass.no_update_needed', version=current_version) if translator else f'No update needed. Current version {current_version} is already >= 0.46.0'}{Style.RESET_ALL}")
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('bypass.bypass_failed', error=str(e)) if translator else f'Version bypass failed: {str(e)}'}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('bypass.stack_trace') if translator else 'Stack trace'}: {traceback.format_exc()}{Style.RESET_ALL}")
return False
def main(translator=None):
"""Main function"""
return bypass_version(translator)
if __name__ == "__main__":
main()
```
## /check_user_authorized.py
```py path="/check_user_authorized.py"
import os
import requests
import time
import hashlib
import base64
import struct
from colorama import Fore, Style, init
# Initialize colorama
init()
# Define emoji constants
EMOJI = {
"SUCCESS": "✅",
"ERROR": "❌",
"INFO": "ℹ️",
"WARNING": "⚠️",
"KEY": "🔑",
"CHECK": "🔍"
}
def generate_hashed64_hex(input_str: str, salt: str = '') -> str:
"""Generate a SHA-256 hash of input + salt and return as hex"""
hash_obj = hashlib.sha256()
hash_obj.update((input_str + salt).encode('utf-8'))
return hash_obj.hexdigest()
def obfuscate_bytes(byte_array: bytearray) -> bytearray:
"""Obfuscate bytes using the algorithm from utils.js"""
t = 165
for r in range(len(byte_array)):
byte_array[r] = ((byte_array[r] ^ t) + (r % 256)) & 0xFF
t = byte_array[r]
return byte_array
def generate_cursor_checksum(token: str, translator=None) -> str:
"""Generate Cursor checksum from token using the algorithm"""
try:
# Clean the token
clean_token = token.strip()
# Generate machineId and macMachineId
machine_id = generate_hashed64_hex(clean_token, 'machineId')
mac_machine_id = generate_hashed64_hex(clean_token, 'macMachineId')
# Get timestamp and convert to byte array
timestamp = int(time.time() * 1000) // 1000000
byte_array = bytearray(struct.pack('>Q', timestamp)[-6:]) # Take last 6 bytes
# Obfuscate bytes and encode as base64
obfuscated_bytes = obfuscate_bytes(byte_array)
encoded_checksum = base64.b64encode(obfuscated_bytes).decode('utf-8')
# Combine final checksum
return f"{encoded_checksum}{machine_id}/{mac_machine_id}"
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('auth_check.error_generating_checksum', error=str(e)) if translator else f'Error generating checksum: {str(e)}'}{Style.RESET_ALL}")
return ""
def check_user_authorized(token: str, translator=None) -> bool:
"""
Check if the user is authorized with the given token
Args:
token (str): The authorization token
translator: Optional translator for internationalization
Returns:
bool: True if authorized, False otherwise
"""
try:
print(f"{Fore.CYAN}{EMOJI['CHECK']} {translator.get('auth_check.checking_authorization') if translator else 'Checking authorization...'}{Style.RESET_ALL}")
# Clean the token
if token and '%3A%3A' in token:
token = token.split('%3A%3A')[1]
elif token and '::' in token:
token = token.split('::')[1]
# Remove any whitespace
token = token.strip()
if not token or len(token) < 10: # Add a basic validation for token length
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('auth_check.invalid_token') if translator else 'Invalid token'}{Style.RESET_ALL}")
return False
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('auth_check.token_length', length=len(token)) if translator else f'Token length: {len(token)} characters'}{Style.RESET_ALL}")
# Try to get usage info using the DashboardService API
try:
# Generate checksum
checksum = generate_cursor_checksum(token, translator)
# Create request headers
headers = {
'accept-encoding': 'gzip',
'authorization': f'Bearer {token}',
'connect-protocol-version': '1',
'content-type': 'application/proto',
'user-agent': 'connect-es/1.6.1',
'x-cursor-checksum': checksum,
'x-cursor-client-version': '0.48.7',
'x-cursor-timezone': 'Asia/Shanghai',
'x-ghost-mode': 'false',
'Host': 'api2.cursor.sh'
}
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('auth_check.checking_usage_information') if translator else 'Checking usage information...'}{Style.RESET_ALL}")
# Make the request - this endpoint doesn't need a request body
usage_response = requests.post(
'https://api2.cursor.sh/aiserver.v1.DashboardService/GetUsageBasedPremiumRequests',
headers=headers,
data=b'', # Empty body
timeout=10
)
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('auth_check.usage_response', response=usage_response.status_code) if translator else f'Usage response status: {usage_response.status_code}'}{Style.RESET_ALL}")
if usage_response.status_code == 200:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('auth_check.user_authorized') if translator else 'User is authorized'}{Style.RESET_ALL}")
return True
elif usage_response.status_code == 401 or usage_response.status_code == 403:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('auth_check.user_unauthorized') if translator else 'User is unauthorized'}{Style.RESET_ALL}")
return False
else:
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('auth_check.unexpected_status_code', code=usage_response.status_code) if translator else f'Unexpected status code: {usage_response.status_code}'}{Style.RESET_ALL}")
# If the token at least looks like a valid JWT, consider it valid
if token.startswith('eyJ') and '.' in token and len(token) > 100:
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('auth_check.jwt_token_warning') if translator else 'Token appears to be in JWT format, but API check returned an unexpected status code. The token might be valid but API access is restricted.'}{Style.RESET_ALL}")
return True
return False
except Exception as e:
print(f"{Fore.YELLOW}{EMOJI['WARNING']} Error checking usage: {str(e)}{Style.RESET_ALL}")
# If the token at least looks like a valid JWT, consider it valid even if the API check fails
if token.startswith('eyJ') and '.' in token and len(token) > 100:
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('auth_check.jwt_token_warning') if translator else 'Token appears to be in JWT format, but API check failed. The token might be valid but API access is restricted.'}{Style.RESET_ALL}")
return True
return False
except requests.exceptions.Timeout:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('auth_check.request_timeout') if translator else 'Request timed out'}{Style.RESET_ALL}")
return False
except requests.exceptions.ConnectionError:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('auth_check.connection_error') if translator else 'Connection error'}{Style.RESET_ALL}")
return False
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('auth_check.check_error', error=str(e)) if translator else f'Error checking authorization: {str(e)}'}{Style.RESET_ALL}")
return False
def run(translator=None):
"""Run function to be called from main.py"""
try:
# Ask user if they want to get token from database or input manually
choice = input(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('auth_check.token_source') if translator else 'Get token from database or input manually? (d/m, default: d): '}{Style.RESET_ALL}").strip().lower()
token = None
# If user chooses database or default
if not choice or choice == 'd':
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('auth_check.getting_token_from_db') if translator else 'Getting token from database...'}{Style.RESET_ALL}")
try:
# Import functions from cursor_acc_info.py
from cursor_acc_info import get_token
# Get token using the get_token function
token = get_token()
if token:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('auth_check.token_found_in_db') if translator else 'Token found in database'}{Style.RESET_ALL}")
else:
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('auth_check.token_not_found_in_db') if translator else 'Token not found in database'}{Style.RESET_ALL}")
except ImportError:
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('auth_check.cursor_acc_info_not_found') if translator else 'cursor_acc_info.py not found'}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('auth_check.error_getting_token_from_db', error=str(e)) if translator else f'Error getting token from database: {str(e)}'}{Style.RESET_ALL}")
# If token not found in database or user chooses manual input
if not token:
# Try to get token from environment
token = os.environ.get('CURSOR_TOKEN')
# If not in environment, ask user to input
if not token:
token = input(f"{Fore.CYAN}{EMOJI['KEY']} {translator.get('auth_check.enter_token') if translator else 'Enter your Cursor token: '}{Style.RESET_ALL}")
# Check authorization
is_authorized = check_user_authorized(token, translator)
if is_authorized:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('auth_check.authorization_successful') if translator else 'Authorization successful!'}{Style.RESET_ALL}")
else:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('auth_check.authorization_failed') if translator else 'Authorization failed!'}{Style.RESET_ALL}")
return is_authorized
except KeyboardInterrupt:
print(f"\n{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('auth_check.operation_cancelled') if translator else 'Operation cancelled by user'}{Style.RESET_ALL}")
return False
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('auth_check.unexpected_error', error=str(e)) if translator else f'Unexpected error: {str(e)}'}{Style.RESET_ALL}")
return False
def main(translator=None):
"""Main function to check user authorization"""
return run(translator)
if __name__ == "__main__":
main()
```
## /config.py
```py path="/config.py"
import os
import sys
import configparser
from colorama import Fore, Style
from utils import get_user_documents_path, get_linux_cursor_path, get_default_driver_path, get_default_browser_path
import shutil
import datetime
EMOJI = {
"INFO": "ℹ️",
"WARNING": "⚠️",
"ERROR": "❌",
"SUCCESS": "✅",
"ADMIN": "🔒",
"ARROW": "➡️",
"USER": "👤",
"KEY": "🔑",
"SETTINGS": "⚙️"
}
# global config cache
_config_cache = None
def setup_config(translator=None):
"""Setup configuration file and return config object"""
try:
# get documents path
docs_path = get_user_documents_path()
if not docs_path or not os.path.exists(docs_path):
# if documents path not found, use current directory
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('config.documents_path_not_found', fallback='Documents path not found, using current directory') if translator else 'Documents path not found, using current directory'}{Style.RESET_ALL}")
docs_path = os.path.abspath('.')
# normalize path
config_dir = os.path.normpath(os.path.join(docs_path, ".cursor-free-vip"))
config_file = os.path.normpath(os.path.join(config_dir, "config.ini"))
# create config directory, only print message when directory not exists
dir_exists = os.path.exists(config_dir)
try:
os.makedirs(config_dir, exist_ok=True)
if not dir_exists: # only print message when directory not exists
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('config.config_dir_created', path=config_dir) if translator else f'Config directory created: {config_dir}'}{Style.RESET_ALL}")
except Exception as e:
# if cannot create directory, use temporary directory
import tempfile
temp_dir = os.path.normpath(os.path.join(tempfile.gettempdir(), ".cursor-free-vip"))
temp_exists = os.path.exists(temp_dir)
config_dir = temp_dir
config_file = os.path.normpath(os.path.join(config_dir, "config.ini"))
os.makedirs(config_dir, exist_ok=True)
if not temp_exists: # only print message when temporary directory not exists
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('config.using_temp_dir', path=config_dir, error=str(e)) if translator else f'Using temporary directory due to error: {config_dir} (Error: {str(e)})'}{Style.RESET_ALL}")
# create config object
config = configparser.ConfigParser()
# Default configuration
default_config = {
'Browser': {
'default_browser': 'chrome',
'chrome_path': get_default_browser_path('chrome'),
'chrome_driver_path': get_default_driver_path('chrome'),
'edge_path': get_default_browser_path('edge'),
'edge_driver_path': get_default_driver_path('edge'),
'firefox_path': get_default_browser_path('firefox'),
'firefox_driver_path': get_default_driver_path('firefox'),
'brave_path': get_default_browser_path('brave'),
'brave_driver_path': get_default_driver_path('brave'),
'opera_path': get_default_browser_path('opera'),
'opera_driver_path': get_default_driver_path('opera'),
'operagx_path': get_default_browser_path('operagx'),
'operagx_driver_path': get_default_driver_path('chrome') # Opera GX 使用 Chrome 驱动
},
'Turnstile': {
'handle_turnstile_time': '2',
'handle_turnstile_random_time': '1-3'
},
'Timing': {
'min_random_time': '0.1',
'max_random_time': '0.8',
'page_load_wait': '0.1-0.8',
'input_wait': '0.3-0.8',
'submit_wait': '0.5-1.5',
'verification_code_input': '0.1-0.3',
'verification_success_wait': '2-3',
'verification_retry_wait': '2-3',
'email_check_initial_wait': '4-6',
'email_refresh_wait': '2-4',
'settings_page_load_wait': '1-2',
'failed_retry_time': '0.5-1',
'retry_interval': '8-12',
'max_timeout': '160'
},
'Utils': {
'enabled_update_check': 'True',
'enabled_force_update': 'False',
'enabled_account_info': 'True'
},
'OAuth': {
'show_selection_alert': False, # 默认不显示选择提示弹窗
'timeout': 120,
'max_attempts': 3
},
'Token': {
'refresh_server': 'https://token.cursorpro.com.cn',
'enable_refresh': True
},
'Language': {
'current_language': '', # Set by local system detection if empty
'fallback_language': 'en',
'auto_update_languages': 'True',
'language_cache_dir': os.path.join(config_dir, "language_cache")
}
}
# Add system-specific path configuration
if sys.platform == "win32":
appdata = os.getenv("APPDATA")
localappdata = os.getenv("LOCALAPPDATA", "")
default_config['WindowsPaths'] = {
'storage_path': os.path.join(appdata, "Cursor", "User", "globalStorage", "storage.json"),
'sqlite_path': os.path.join(appdata, "Cursor", "User", "globalStorage", "state.vscdb"),
'machine_id_path': os.path.join(appdata, "Cursor", "machineId"),
'cursor_path': os.path.join(localappdata, "Programs", "Cursor", "resources", "app"),
'updater_path': os.path.join(localappdata, "cursor-updater"),
'update_yml_path': os.path.join(localappdata, "Programs", "Cursor", "resources", "app-update.yml"),
'product_json_path': os.path.join(localappdata, "Programs", "Cursor", "resources", "app", "product.json")
}
# Create storage directory
os.makedirs(os.path.dirname(default_config['WindowsPaths']['storage_path']), exist_ok=True)
elif sys.platform == "darwin":
default_config['MacPaths'] = {
'storage_path': os.path.abspath(os.path.expanduser("~/Library/Application Support/Cursor/User/globalStorage/storage.json")),
'sqlite_path': os.path.abspath(os.path.expanduser("~/Library/Application Support/Cursor/User/globalStorage/state.vscdb")),
'machine_id_path': os.path.expanduser("~/Library/Application Support/Cursor/machineId"),
'cursor_path': "/Applications/Cursor.app/Contents/Resources/app",
'updater_path': os.path.expanduser("~/Library/Application Support/cursor-updater"),
'update_yml_path': "/Applications/Cursor.app/Contents/Resources/app-update.yml",
'product_json_path': "/Applications/Cursor.app/Contents/Resources/app/product.json"
}
# Create storage directory
os.makedirs(os.path.dirname(default_config['MacPaths']['storage_path']), exist_ok=True)
elif sys.platform == "linux":
# Get the actual user's home directory, handling both sudo and normal cases
sudo_user = os.environ.get('SUDO_USER')
current_user = sudo_user if sudo_user else (os.getenv('USER') or os.getenv('USERNAME'))
if not current_user:
current_user = os.path.expanduser('~').split('/')[-1]
# Handle sudo case
if sudo_user:
actual_home = f"/home/{sudo_user}"
root_home = "/root"
else:
actual_home = f"/home/{current_user}"
root_home = None
if not os.path.exists(actual_home):
actual_home = os.path.expanduser("~")
# Define base config directory
config_base = os.path.join(actual_home, ".config")
# Try both "Cursor" and "cursor" directory names in both user and root locations
cursor_dir = None
possible_paths = [
os.path.join(config_base, "Cursor"),
os.path.join(config_base, "cursor"),
os.path.join(root_home, ".config", "Cursor") if root_home else None,
os.path.join(root_home, ".config", "cursor") if root_home else None
]
for path in possible_paths:
if path and os.path.exists(path):
cursor_dir = path
break
if not cursor_dir:
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('config.neither_cursor_nor_cursor_directory_found', config_base=config_base) if translator else f'Neither Cursor nor cursor directory found in {config_base}'}{Style.RESET_ALL}")
if root_home:
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.also_checked', path=f'{root_home}/.config') if translator else f'Also checked {root_home}/.config'}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.please_make_sure_cursor_is_installed_and_has_been_run_at_least_once') if translator else 'Please make sure Cursor is installed and has been run at least once'}{Style.RESET_ALL}")
# Define Linux paths using the found cursor directory
storage_path = os.path.abspath(os.path.join(cursor_dir, "User/globalStorage/storage.json")) if cursor_dir else ""
storage_dir = os.path.dirname(storage_path) if storage_path else ""
# Verify paths and permissions
try:
# Check storage directory
if storage_dir and not os.path.exists(storage_dir):
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('config.storage_directory_not_found', storage_dir=storage_dir) if translator else f'Storage directory not found: {storage_dir}'}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.please_make_sure_cursor_is_installed_and_has_been_run_at_least_once') if translator else 'Please make sure Cursor is installed and has been run at least once'}{Style.RESET_ALL}")
# Check storage.json with more detailed verification
if storage_path and os.path.exists(storage_path):
# Get file stats
try:
stat = os.stat(storage_path)
print(f"{Fore.GREEN}{EMOJI['INFO']} {translator.get('config.storage_file_found', storage_path=storage_path) if translator else f'Storage file found: {storage_path}'}{Style.RESET_ALL}")
print(f"{Fore.GREEN}{EMOJI['INFO']} {translator.get('config.file_size', size=stat.st_size) if translator else f'File size: {stat.st_size} bytes'}{Style.RESET_ALL}")
print(f"{Fore.GREEN}{EMOJI['INFO']} {translator.get('config.file_permissions', permissions=oct(stat.st_mode & 0o777)) if translator else f'File permissions: {oct(stat.st_mode & 0o777)}'}{Style.RESET_ALL}")
print(f"{Fore.GREEN}{EMOJI['INFO']} {translator.get('config.file_owner', owner=stat.st_uid) if translator else f'File owner: {stat.st_uid}'}{Style.RESET_ALL}")
print(f"{Fore.GREEN}{EMOJI['INFO']} {translator.get('config.file_group', group=stat.st_gid) if translator else f'File group: {stat.st_gid}'}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('config.error_getting_file_stats', error=str(e)) if translator else f'Error getting file stats: {str(e)}'}{Style.RESET_ALL}")
# Check if file is readable and writable
if not os.access(storage_path, os.R_OK | os.W_OK):
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('config.permission_denied', storage_path=storage_path) if translator else f'Permission denied: {storage_path}'}{Style.RESET_ALL}")
if sudo_user:
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.try_running', command=f'chown {sudo_user}:{sudo_user} {storage_path}') if translator else f'Try running: chown {sudo_user}:{sudo_user} {storage_path}'}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.and') if translator else 'And'}: chmod 644 {storage_path}{Style.RESET_ALL}")
else:
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.try_running', command=f'chown {current_user}:{current_user} {storage_path}') if translator else f'Try running: chown {current_user}:{current_user} {storage_path}'}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.and') if translator else 'And'}: chmod 644 {storage_path}{Style.RESET_ALL}")
# Try to read the file to verify it's not corrupted
try:
with open(storage_path, 'r') as f:
content = f.read()
if not content.strip():
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('config.storage_file_is_empty', storage_path=storage_path) if translator else f'Storage file is empty: {storage_path}'}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.the_file_might_be_corrupted_please_reinstall_cursor') if translator else 'The file might be corrupted, please reinstall Cursor'}{Style.RESET_ALL}")
else:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('config.storage_file_is_valid_and_contains_data') if translator else 'Storage file is valid and contains data'}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('config.error_reading_storage_file', error=str(e)) if translator else f'Error reading storage file: {str(e)}'}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.the_file_might_be_corrupted_please_reinstall_cursor') if translator else 'The file might be corrupted. Please reinstall Cursor'}{Style.RESET_ALL}")
elif storage_path:
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('config.storage_file_not_found', storage_path=storage_path) if translator else f'Storage file not found: {storage_path}'}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.please_make_sure_cursor_is_installed_and_has_been_run_at_least_once') if translator else 'Please make sure Cursor is installed and has been run at least once'}{Style.RESET_ALL}")
except (OSError, IOError) as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('config.error_checking_linux_paths', error=str(e)) if translator else f'Error checking Linux paths: {str(e)}'}{Style.RESET_ALL}")
# Define all paths using the found cursor directory
default_config['LinuxPaths'] = {
'storage_path': storage_path,
'sqlite_path': os.path.abspath(os.path.join(cursor_dir, "User/globalStorage/state.vscdb")) if cursor_dir else "",
'machine_id_path': os.path.join(cursor_dir, "machineid") if cursor_dir else "",
'cursor_path': get_linux_cursor_path(),
'updater_path': os.path.join(config_base, "cursor-updater"),
'update_yml_path': os.path.join(cursor_dir, "resources/app-update.yml") if cursor_dir else "",
'product_json_path': os.path.join(cursor_dir, "resources/app/product.json") if cursor_dir else ""
}
# Add tempmail_plus configuration
default_config['TempMailPlus'] = {
'enabled': 'false',
'email': '',
'epin': ''
}
# Read existing configuration and merge
if os.path.exists(config_file):
config.read(config_file, encoding='utf-8')
config_modified = False
for section, options in default_config.items():
if not config.has_section(section):
config.add_section(section)
config_modified = True
for option, value in options.items():
if not config.has_option(section, option):
config.set(section, option, str(value))
config_modified = True
if translator:
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.config_option_added', option=f'{section}.{option}') if translator else f'Config option added: {section}.{option}'}{Style.RESET_ALL}")
if config_modified:
with open(config_file, 'w', encoding='utf-8') as f:
config.write(f)
if translator:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('config.config_updated') if translator else 'Config updated'}{Style.RESET_ALL}")
else:
for section, options in default_config.items():
config.add_section(section)
for option, value in options.items():
config.set(section, option, str(value))
with open(config_file, 'w', encoding='utf-8') as f:
config.write(f)
if translator:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('config.config_created', config_file=config_file) if translator else f'Config created: {config_file}'}{Style.RESET_ALL}")
return config
except Exception as e:
if translator:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('config.config_setup_error', error=str(e)) if translator else f'Error setting up config: {str(e)}'}{Style.RESET_ALL}")
return None
def print_config(config, translator=None):
"""Print configuration in a readable format"""
if not config:
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('config.config_not_available') if translator else 'Configuration not available'}{Style.RESET_ALL}")
return
print(f"\n{Fore.CYAN}{EMOJI['INFO']} {translator.get('config.configuration') if translator else 'Configuration'}:{Style.RESET_ALL}")
print(f"\n{Fore.CYAN}{'─' * 70}{Style.RESET_ALL}")
for section in config.sections():
print(f"{Fore.GREEN}[{section}]{Style.RESET_ALL}")
for key, value in config.items(section):
# 对布尔值进行特殊处理,使其显示为彩色
if value.lower() in ('true', 'yes', 'on', '1'):
value_display = f"{Fore.GREEN}{translator.get('config.enabled') if translator else 'Enabled'}{Style.RESET_ALL}"
elif value.lower() in ('false', 'no', 'off', '0'):
value_display = f"{Fore.RED}{translator.get('config.disabled') if translator else 'Disabled'}{Style.RESET_ALL}"
else:
value_display = value
print(f" {key} = {value_display}")
print(f"\n{Fore.CYAN}{'─' * 70}{Style.RESET_ALL}")
config_dir = os.path.join(get_user_documents_path(), ".cursor-free-vip", "config.ini")
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('config.config_directory') if translator else 'Config Directory'}: {config_dir}{Style.RESET_ALL}")
print()
def force_update_config(translator=None):
"""
Force update configuration file with latest defaults if update check is enabled.
Args:
translator: Translator instance
Returns:
ConfigParser instance or None if failed
"""
try:
config_dir = os.path.join(get_user_documents_path(), ".cursor-free-vip")
config_file = os.path.join(config_dir, "config.ini")
current_time = datetime.datetime.now()
# If the config file exists, check if forced update is enabled
if os.path.exists(config_file):
# First, read the existing configuration
existing_config = configparser.ConfigParser()
existing_config.read(config_file, encoding='utf-8')
# Check if "enabled_update_check" is True
update_enabled = True # Default to True if not set
if existing_config.has_section('Utils') and existing_config.has_option('Utils', 'enabled_force_update'):
update_enabled = existing_config.get('Utils', 'enabled_force_update').strip().lower() in ('true', 'yes', '1', 'on')
if update_enabled:
try:
# Create a backup
backup_file = f"{config_file}.bak.{current_time.strftime('%Y%m%d_%H%M%S')}"
shutil.copy2(config_file, backup_file)
if translator:
print(f"\n{Fore.CYAN}{EMOJI['INFO']} {translator.get('config.backup_created', path=backup_file) if translator else f'Backup created: {backup_file}'}{Style.RESET_ALL}")
print(f"\n{Fore.CYAN}{EMOJI['INFO']} {translator.get('config.config_force_update_enabled') if translator else 'Config file force update enabled'}{Style.RESET_ALL}")
# Delete the original config file (forced update)
os.remove(config_file)
if translator:
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('config.config_removed') if translator else 'Config file removed for forced update'}{Style.RESET_ALL}")
except Exception as e:
if translator:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('config.backup_failed', error=str(e)) if translator else f'Failed to backup config: {str(e)}'}{Style.RESET_ALL}")
else:
if translator:
print(f"\n{Fore.CYAN}{EMOJI['INFO']} {translator.get('config.config_force_update_disabled', fallback='Config file force update disabled by configuration. Keeping existing config file.') if translator else 'Config file force update disabled by configuration. Keeping existing config file.'}{Style.RESET_ALL}")
# Generate a new (or updated) configuration if needed
return setup_config(translator)
except Exception as e:
if translator:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('config.force_update_failed', error=str(e)) if translator else f'Force update config failed: {str(e)}'}{Style.RESET_ALL}")
return None
def get_config(translator=None):
"""Get existing config or create new one"""
global _config_cache
if _config_cache is None:
_config_cache = setup_config(translator)
return _config_cache
```
## /cursor_acc_info.py
```py path="/cursor_acc_info.py"
import os
import sys
import json
import requests
import sqlite3
from typing import Dict, Optional
import platform
from colorama import Fore, Style, init
import logging
import re
# Initialize colorama
init()
# Setup logger
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
# Define emoji constants
EMOJI = {
"USER": "👤",
"USAGE": "📊",
"PREMIUM": "⭐",
"BASIC": "📝",
"SUBSCRIPTION": "💳",
"INFO": "ℹ️",
"ERROR": "❌",
"SUCCESS": "✅",
"WARNING": "⚠️",
"TIME": "🕒"
}
class Config:
"""Config"""
NAME_LOWER = "cursor"
NAME_CAPITALIZE = "Cursor"
BASE_HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
"Accept": "application/json",
"Content-Type": "application/json"
}
class UsageManager:
"""Usage Manager"""
@staticmethod
def get_proxy():
"""get proxy"""
# from config import get_config
proxy = os.environ.get("HTTP_PROXY") or os.environ.get("HTTPS_PROXY")
if proxy:
return {"http": proxy, "https": proxy}
return None
@staticmethod
def get_usage(token: str) -> Optional[Dict]:
"""get usage"""
url = f"https://www.{Config.NAME_LOWER}.com/api/usage"
headers = Config.BASE_HEADERS.copy()
headers.update({"Cookie": f"Workos{Config.NAME_CAPITALIZE}SessionToken=user_01OOOOOOOOOOOOOOOOOOOOOOOO%3A%3A{token}"})
try:
proxies = UsageManager.get_proxy()
response = requests.get(url, headers=headers, timeout=10, proxies=proxies)
response.raise_for_status()
data = response.json()
# get Premium usage and limit
gpt4_data = data.get("gpt-4", {})
premium_usage = gpt4_data.get("numRequestsTotal", 0)
max_premium_usage = gpt4_data.get("maxRequestUsage", 999)
# get Basic usage, but set limit to "No Limit"
gpt35_data = data.get("gpt-3.5-turbo", {})
basic_usage = gpt35_data.get("numRequestsTotal", 0)
return {
'premium_usage': premium_usage,
'max_premium_usage': max_premium_usage,
'basic_usage': basic_usage,
'max_basic_usage': "No Limit" # set Basic limit to "No Limit"
}
except requests.RequestException as e:
# only log error
logger.error(f"Get usage info failed: {str(e)}")
return None
except Exception as e:
# catch all other exceptions
logger.error(f"Get usage info failed: {str(e)}")
return None
@staticmethod
def get_stripe_profile(token: str) -> Optional[Dict]:
"""get user subscription info"""
url = f"https://api2.{Config.NAME_LOWER}.sh/auth/full_stripe_profile"
headers = Config.BASE_HEADERS.copy()
headers.update({"Authorization": f"Bearer {token}"})
try:
proxies = UsageManager.get_proxy()
response = requests.get(url, headers=headers, timeout=10, proxies=proxies)
response.raise_for_status()
return response.json()
except requests.RequestException as e:
logger.error(f"Get subscription info failed: {str(e)}")
return None
def get_token_from_config():
"""get path info from config"""
try:
from config import get_config
config = get_config()
if not config:
return None
system = platform.system()
if system == "Windows" and config.has_section('WindowsPaths'):
return {
'storage_path': config.get('WindowsPaths', 'storage_path'),
'sqlite_path': config.get('WindowsPaths', 'sqlite_path'),
'session_path': os.path.join(os.getenv("APPDATA"), "Cursor", "Session Storage")
}
elif system == "Darwin" and config.has_section('MacPaths'): # macOS
return {
'storage_path': config.get('MacPaths', 'storage_path'),
'sqlite_path': config.get('MacPaths', 'sqlite_path'),
'session_path': os.path.expanduser("~/Library/Application Support/Cursor/Session Storage")
}
elif system == "Linux" and config.has_section('LinuxPaths'):
return {
'storage_path': config.get('LinuxPaths', 'storage_path'),
'sqlite_path': config.get('LinuxPaths', 'sqlite_path'),
'session_path': os.path.expanduser("~/.config/Cursor/Session Storage")
}
except Exception as e:
logger.error(f"Get config path failed: {str(e)}")
return None
def get_token_from_storage(storage_path):
"""get token from storage.json"""
if not os.path.exists(storage_path):
return None
try:
with open(storage_path, 'r', encoding='utf-8') as f:
data = json.load(f)
# try to get accessToken
if 'cursorAuth/accessToken' in data:
return data['cursorAuth/accessToken']
# try other possible keys
for key in data:
if 'token' in key.lower() and isinstance(data[key], str) and len(data[key]) > 20:
return data[key]
except Exception as e:
logger.error(f"get token from storage.json failed: {str(e)}")
return None
def get_token_from_sqlite(sqlite_path):
"""get token from sqlite"""
if not os.path.exists(sqlite_path):
return None
try:
conn = sqlite3.connect(sqlite_path)
cursor = conn.cursor()
cursor.execute("SELECT value FROM ItemTable WHERE key LIKE '%token%'")
rows = cursor.fetchall()
conn.close()
for row in rows:
try:
value = row[0]
if isinstance(value, str) and len(value) > 20:
return value
# try to parse JSON
data = json.loads(value)
if isinstance(data, dict) and 'token' in data:
return data['token']
except:
continue
except Exception as e:
logger.error(f"get token from sqlite failed: {str(e)}")
return None
def get_token_from_session(session_path):
"""get token from session"""
if not os.path.exists(session_path):
return None
try:
# try to find all possible session files
for file in os.listdir(session_path):
if file.endswith('.log'):
file_path = os.path.join(session_path, file)
try:
with open(file_path, 'rb') as f:
content = f.read().decode('utf-8', errors='ignore')
# find token pattern
token_match = re.search(r'"token":"([^"]+)"', content)
if token_match:
return token_match.group(1)
except:
continue
except Exception as e:
logger.error(f"get token from session failed: {str(e)}")
return None
def get_token():
"""get Cursor token"""
# get path from config
paths = get_token_from_config()
if not paths:
return None
# try to get token from different locations
token = get_token_from_storage(paths['storage_path'])
if token:
return token
token = get_token_from_sqlite(paths['sqlite_path'])
if token:
return token
token = get_token_from_session(paths['session_path'])
if token:
return token
return None
def format_subscription_type(subscription_data: Dict) -> str:
"""format subscription type"""
if not subscription_data:
return "Free"
# handle new API response format
if "membershipType" in subscription_data:
membership_type = subscription_data.get("membershipType", "").lower()
subscription_status = subscription_data.get("subscriptionStatus", "").lower()
if subscription_status == "active":
if membership_type == "pro":
return "Pro"
elif membership_type == "free_trial":
return "Free Trial"
elif membership_type == "pro_trial":
return "Pro Trial"
elif membership_type == "team":
return "Team"
elif membership_type == "enterprise":
return "Enterprise"
elif membership_type:
return membership_type.capitalize()
else:
return "Active Subscription"
elif subscription_status:
return f"{membership_type.capitalize()} ({subscription_status})"
# compatible with old API response format
subscription = subscription_data.get("subscription")
if subscription:
plan = subscription.get("plan", {}).get("nickname", "Unknown")
status = subscription.get("status", "unknown")
if status == "active":
if "pro" in plan.lower():
return "Pro"
elif "pro_trial" in plan.lower():
return "Pro Trial"
elif "free_trial" in plan.lower():
return "Free Trial"
elif "team" in plan.lower():
return "Team"
elif "enterprise" in plan.lower():
return "Enterprise"
else:
return plan
else:
return f"{plan} ({status})"
return "Free"
def get_email_from_storage(storage_path):
"""get email from storage.json"""
if not os.path.exists(storage_path):
return None
try:
with open(storage_path, 'r', encoding='utf-8') as f:
data = json.load(f)
# try to get email
if 'cursorAuth/cachedEmail' in data:
return data['cursorAuth/cachedEmail']
# try other possible keys
for key in data:
if 'email' in key.lower() and isinstance(data[key], str) and '@' in data[key]:
return data[key]
except Exception as e:
logger.error(f"get email from storage.json failed: {str(e)}")
return None
def get_email_from_sqlite(sqlite_path):
"""get email from sqlite"""
if not os.path.exists(sqlite_path):
return None
try:
conn = sqlite3.connect(sqlite_path)
cursor = conn.cursor()
# try to query records containing email
cursor.execute("SELECT value FROM ItemTable WHERE key LIKE '%email%' OR key LIKE '%cursorAuth%'")
rows = cursor.fetchall()
conn.close()
for row in rows:
try:
value = row[0]
# if it's a string and contains @, it might be an email
if isinstance(value, str) and '@' in value:
return value
# try to parse JSON
try:
data = json.loads(value)
if isinstance(data, dict):
# check if there's an email field
if 'email' in data:
return data['email']
# check if there's a cachedEmail field
if 'cachedEmail' in data:
return data['cachedEmail']
except:
pass
except:
continue
except Exception as e:
logger.error(f"get email from sqlite failed: {str(e)}")
return None
def display_account_info(translator=None):
"""display account info"""
print(f"\n{Fore.CYAN}{'─' * 70}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['USER']} {translator.get('account_info.title') if translator else 'Cursor Account Information'}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{'─' * 70}{Style.RESET_ALL}")
# get token
token = get_token()
if not token:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('account_info.token_not_found') if translator else 'Token not found. Please login to Cursor first.'}{Style.RESET_ALL}")
return
# get path info
paths = get_token_from_config()
if not paths:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('account_info.config_not_found') if translator else 'Configuration not found.'}{Style.RESET_ALL}")
return
# get email info - try multiple sources
email = get_email_from_storage(paths['storage_path'])
# if not found in storage, try from sqlite
if not email:
email = get_email_from_sqlite(paths['sqlite_path'])
# get subscription info
try:
subscription_info = UsageManager.get_stripe_profile(token)
except Exception as e:
logger.error(f"Get subscription info failed: {str(e)}")
subscription_info = None
# if not found in storage and sqlite, try from subscription info
if not email and subscription_info:
# try to get email from subscription info
if 'customer' in subscription_info and 'email' in subscription_info['customer']:
email = subscription_info['customer']['email']
# get usage info - silently handle errors
try:
usage_info = UsageManager.get_usage(token)
except Exception as e:
logger.error(f"Get usage info failed: {str(e)}")
usage_info = None
# Prepare left and right info
left_info = []
right_info = []
# Left side shows account info
if email:
left_info.append(f"{Fore.GREEN}{EMOJI['USER']} {translator.get('account_info.email') if translator else 'Email'}: {Fore.WHITE}{email}{Style.RESET_ALL}")
else:
left_info.append(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('account_info.email_not_found') if translator else 'Email not found'}{Style.RESET_ALL}")
# Add an empty line
# left_info.append("")
# Show subscription type
if subscription_info:
subscription_type = format_subscription_type(subscription_info)
left_info.append(f"{Fore.GREEN}{EMOJI['SUBSCRIPTION']} {translator.get('account_info.subscription') if translator else 'Subscription'}: {Fore.WHITE}{subscription_type}{Style.RESET_ALL}")
# Show remaining trial days
days_remaining = subscription_info.get("daysRemainingOnTrial")
if days_remaining is not None and days_remaining > 0:
left_info.append(f"{Fore.GREEN}{EMOJI['TIME']} {translator.get('account_info.trial_remaining') if translator else 'Remaining Pro Trial'}: {Fore.WHITE}{days_remaining} {translator.get('account_info.days') if translator else 'days'}{Style.RESET_ALL}")
else:
left_info.append(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('account_info.subscription_not_found') if translator else 'Subscription information not found'}{Style.RESET_ALL}")
# Right side shows usage info - only if available
if usage_info:
right_info.append(f"{Fore.GREEN}{EMOJI['USAGE']} {translator.get('account_info.usage') if translator else 'Usage Statistics'}:{Style.RESET_ALL}")
# Premium usage
premium_usage = usage_info.get('premium_usage', 0)
max_premium_usage = usage_info.get('max_premium_usage', "No Limit")
# make sure the value is not None
if premium_usage is None:
premium_usage = 0
# handle "No Limit" case
if isinstance(max_premium_usage, str) and max_premium_usage == "No Limit":
premium_color = Fore.GREEN # when there is no limit, use green
premium_display = f"{premium_usage}/{max_premium_usage}"
else:
# calculate percentage when the value is a number
if max_premium_usage is None or max_premium_usage == 0:
max_premium_usage = 999
premium_percentage = 0
else:
premium_percentage = (premium_usage / max_premium_usage) * 100
# select color based on usage percentage
premium_color = Fore.GREEN
if premium_percentage > 70:
premium_color = Fore.YELLOW
if premium_percentage > 90:
premium_color = Fore.RED
premium_display = f"{premium_usage}/{max_premium_usage} ({premium_percentage:.1f}%)"
right_info.append(f"{Fore.YELLOW}{EMOJI['PREMIUM']} {translator.get('account_info.premium_usage') if translator else 'Fast Response'}: {premium_color}{premium_display}{Style.RESET_ALL}")
# Slow Response
basic_usage = usage_info.get('basic_usage', 0)
max_basic_usage = usage_info.get('max_basic_usage', "No Limit")
# make sure the value is not None
if basic_usage is None:
basic_usage = 0
# handle "No Limit" case
if isinstance(max_basic_usage, str) and max_basic_usage == "No Limit":
basic_color = Fore.GREEN # when there is no limit, use green
basic_display = f"{basic_usage}/{max_basic_usage}"
else:
# calculate percentage when the value is a number
if max_basic_usage is None or max_basic_usage == 0:
max_basic_usage = 999
basic_percentage = 0
else:
basic_percentage = (basic_usage / max_basic_usage) * 100
# select color based on usage percentage
basic_color = Fore.GREEN
if basic_percentage > 70:
basic_color = Fore.YELLOW
if basic_percentage > 90:
basic_color = Fore.RED
basic_display = f"{basic_usage}/{max_basic_usage} ({basic_percentage:.1f}%)"
right_info.append(f"{Fore.BLUE}{EMOJI['BASIC']} {translator.get('account_info.basic_usage') if translator else 'Slow Response'}: {basic_color}{basic_display}{Style.RESET_ALL}")
else:
# if get usage info failed, only log in log, not show in interface
# you can choose to not show any usage info, or show a simple prompt
# right_info.append(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('account_info.usage_unavailable') if translator else 'Usage information unavailable'}{Style.RESET_ALL}")
pass # not show any usage info
# Calculate the maximum display width of left info
ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
def get_display_width(s):
"""Calculate the display width of a string, considering Chinese characters and emojis"""
# Remove ANSI color codes
clean_s = ansi_escape.sub('', s)
width = 0
for c in clean_s:
# Chinese characters and some emojis occupy two character widths
if ord(c) > 127:
width += 2
else:
width += 1
return width
max_left_width = 0
for item in left_info:
width = get_display_width(item)
max_left_width = max(max_left_width, width)
# Set the starting position of right info
fixed_spacing = 4 # Fixed spacing
right_start = max_left_width + fixed_spacing
# Calculate the number of spaces needed for right info
spaces_list = []
for i in range(len(left_info)):
if i < len(left_info):
left_item = left_info[i]
left_width = get_display_width(left_item)
spaces = right_start - left_width
spaces_list.append(spaces)
# Print info
max_rows = max(len(left_info), len(right_info))
for i in range(max_rows):
# Print left info
if i < len(left_info):
left_item = left_info[i]
print(left_item, end='')
# Use pre-calculated spaces
spaces = spaces_list[i]
else:
# If left side has no items, print only spaces
spaces = right_start
print('', end='')
# Print right info
if i < len(right_info):
print(' ' * spaces + right_info[i])
else:
print() # Change line
print(f"{Fore.CYAN}{'─' * 70}{Style.RESET_ALL}")
def main(translator=None):
"""main function"""
try:
display_account_info(translator)
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('account_info.error') if translator else 'Error'}: {str(e)}{Style.RESET_ALL}")
if __name__ == "__main__":
main()
```
## /cursor_auth.py
```py path="/cursor_auth.py"
import sqlite3
import os
import sys
from colorama import Fore, Style, init
from config import get_config
# Initialize colorama
init()
# Define emoji and color constants
EMOJI = {
'DB': '🗄️',
'UPDATE': '🔄',
'SUCCESS': '✅',
'ERROR': '❌',
'WARN': '⚠️',
'INFO': 'ℹ️',
'FILE': '📄',
'KEY': '🔐'
}
class CursorAuth:
def __init__(self, translator=None):
self.translator = translator
# Get configuration
config = get_config(translator)
if not config:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.config_error') if self.translator else 'Failed to load configuration'}{Style.RESET_ALL}")
sys.exit(1)
# Get path based on operating system
try:
if sys.platform == "win32": # Windows
if not config.has_section('WindowsPaths'):
raise ValueError("Windows paths not configured")
self.db_path = config.get('WindowsPaths', 'sqlite_path')
elif sys.platform == 'linux': # Linux
if not config.has_section('LinuxPaths'):
raise ValueError("Linux paths not configured")
self.db_path = config.get('LinuxPaths', 'sqlite_path')
elif sys.platform == 'darwin': # macOS
if not config.has_section('MacPaths'):
raise ValueError("macOS paths not configured")
self.db_path = config.get('MacPaths', 'sqlite_path')
else:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.unsupported_platform') if self.translator else 'Unsupported platform'}{Style.RESET_ALL}")
sys.exit(1)
# Verify if the path exists
if not os.path.exists(os.path.dirname(self.db_path)):
raise FileNotFoundError(f"Database directory not found: {os.path.dirname(self.db_path)}")
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.path_error', error=str(e)) if self.translator else f'Error getting database path: {str(e)}'}{Style.RESET_ALL}")
sys.exit(1)
# Check if the database file exists
if not os.path.exists(self.db_path):
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.db_not_found', path=self.db_path) if self.translator else f'Database not found: {self.db_path}'}{Style.RESET_ALL}")
return
# Check file permissions
if not os.access(self.db_path, os.R_OK | os.W_OK):
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.db_permission_error') if self.translator else 'Database permission error'}{Style.RESET_ALL}")
return
try:
self.conn = sqlite3.connect(self.db_path)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('auth.connected_to_database') if self.translator else 'Connected to database'}{Style.RESET_ALL}")
except sqlite3.Error as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.db_connection_error', error=str(e)) if self.translator else f'Database connection error: {str(e)}'}{Style.RESET_ALL}")
return
def update_auth(self, email=None, access_token=None, refresh_token=None, auth_type="Auth_0"):
conn = None
try:
# Ensure the directory exists and set the correct permissions
db_dir = os.path.dirname(self.db_path)
if not os.path.exists(db_dir):
os.makedirs(db_dir, mode=0o755, exist_ok=True)
# If the database file does not exist, create a new one
if not os.path.exists(self.db_path):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS ItemTable (
key TEXT PRIMARY KEY,
value TEXT
)
''')
conn.commit()
if sys.platform != "win32":
os.chmod(self.db_path, 0o644)
conn.close()
# Reconnect to the database
conn = sqlite3.connect(self.db_path)
print(f"{EMOJI['INFO']} {Fore.GREEN} {self.translator.get('auth.connected_to_database') if self.translator else 'Connected to database'}{Style.RESET_ALL}")
cursor = conn.cursor()
# Add timeout and other optimization settings
conn.execute("PRAGMA busy_timeout = 5000")
conn.execute("PRAGMA journal_mode = WAL")
conn.execute("PRAGMA synchronous = NORMAL")
# Set the key-value pairs to update
updates = []
updates.append(("cursorAuth/cachedSignUpType", auth_type))
if email is not None:
updates.append(("cursorAuth/cachedEmail", email))
if access_token is not None:
updates.append(("cursorAuth/accessToken", access_token))
if refresh_token is not None:
updates.append(("cursorAuth/refreshToken", refresh_token))
# Use transactions to ensure data integrity
cursor.execute("BEGIN TRANSACTION")
try:
for key, value in updates:
# Check if the key exists
cursor.execute("SELECT COUNT(*) FROM ItemTable WHERE key = ?", (key,))
if cursor.fetchone()[0] == 0:
cursor.execute("""
INSERT INTO ItemTable (key, value)
VALUES (?, ?)
""", (key, value))
else:
cursor.execute("""
UPDATE ItemTable SET value = ?
WHERE key = ?
""", (value, key))
print(f"{EMOJI['INFO']} {Fore.CYAN} {self.translator.get('auth.updating_pair') if self.translator else 'Updating key-value pair:'} {key.split('/')[-1]}...{Style.RESET_ALL}")
cursor.execute("COMMIT")
print(f"{EMOJI['SUCCESS']} {Fore.GREEN}{self.translator.get('auth.database_updated_successfully') if self.translator else 'Database updated successfully'}{Style.RESET_ALL}")
return True
except Exception as e:
cursor.execute("ROLLBACK")
raise e
except sqlite3.Error as e:
print(f"\n{EMOJI['ERROR']} {Fore.RED} {self.translator.get('auth.database_error', error=str(e)) if self.translator else f'Database error: {str(e)}'}{Style.RESET_ALL}")
return False
except Exception as e:
print(f"\n{EMOJI['ERROR']} {Fore.RED} {self.translator.get('auth.an_error_occurred', error=str(e)) if self.translator else f'An error occurred: {str(e)}'}{Style.RESET_ALL}")
return False
finally:
if conn:
conn.close()
print(f"{EMOJI['DB']} {Fore.CYAN} {self.translator.get('auth.database_connection_closed') if self.translator else 'Database connection closed'}{Style.RESET_ALL}")
```
## /cursor_register_manual.py
```py path="/cursor_register_manual.py"
import os
from colorama import Fore, Style, init
import time
import random
from faker import Faker
from cursor_auth import CursorAuth
from reset_machine_manual import MachineIDResetter
from get_user_token import get_token_from_cookie
from config import get_config
os.environ["PYTHONVERBOSE"] = "0"
os.environ["PYINSTALLER_VERBOSE"] = "0"
# Initialize colorama
init()
# Define emoji constants
EMOJI = {
'START': '🚀',
'FORM': '📝',
'VERIFY': '🔄',
'PASSWORD': '🔑',
'CODE': '📱',
'DONE': '✨',
'ERROR': '❌',
'WAIT': '⏳',
'SUCCESS': '✅',
'MAIL': '📧',
'KEY': '🔐',
'UPDATE': '🔄',
'INFO': 'ℹ️'
}
class CursorRegistration:
def __init__(self, translator=None):
self.translator = translator
# Set to display mode
os.environ['BROWSER_HEADLESS'] = 'False'
self.browser = None
self.controller = None
self.sign_up_url = "https://authenticator.cursor.sh/sign-up"
self.settings_url = "https://www.cursor.com/settings"
self.email_address = None
self.signup_tab = None
self.email_tab = None
# initialize Faker instance
self.faker = Faker()
# generate account information
self.password = self._generate_password()
self.first_name = self.faker.first_name()
self.last_name = self.faker.last_name()
# modify the first letter of the first name(keep the original function)
new_first_letter = random.choice("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
self.first_name = new_first_letter + self.first_name[1:]
print(f"\n{Fore.CYAN}{EMOJI['PASSWORD']} {self.translator.get('register.password')}: {self.password} {Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['FORM']} {self.translator.get('register.first_name')}: {self.first_name} {Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['FORM']} {self.translator.get('register.last_name')}: {self.last_name} {Style.RESET_ALL}")
def _generate_password(self, length=12):
"""Generate password"""
return self.faker.password(length=length, special_chars=True, digits=True, upper_case=True, lower_case=True)
def setup_email(self):
"""Setup Email"""
try:
print(f"{Fore.CYAN}{EMOJI['START']} {self.translator.get('register.manual_email_input') if self.translator else 'Please enter your email address:'}")
self.email_address = input().strip()
if '@' not in self.email_address:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.invalid_email') if self.translator else '无效的邮箱地址'}{Style.RESET_ALL}")
return False
print(f"{Fore.CYAN}{EMOJI['MAIL']} {self.translator.get('register.email_address')}: {self.email_address}" + "\n" + f"{Style.RESET_ALL}")
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.email_setup_failed', error=str(e))}{Style.RESET_ALL}")
return False
def get_verification_code(self):
"""Manually Get Verification Code"""
try:
print(f"{Fore.CYAN}{EMOJI['CODE']} {self.translator.get('register.manual_code_input') if self.translator else 'Please enter the verification code:'}")
code = input().strip()
if not code.isdigit() or len(code) != 6:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.invalid_code') if self.translator else '无效的验证码'}{Style.RESET_ALL}")
return None
return code
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.code_input_failed', error=str(e))}{Style.RESET_ALL}")
return None
def register_cursor(self):
"""Register Cursor"""
browser_tab = None
try:
print(f"{Fore.CYAN}{EMOJI['START']} {self.translator.get('register.register_start')}...{Style.RESET_ALL}")
# Check if tempmail_plus is enabled
config = get_config(self.translator)
email_tab = None
if config and config.has_section('TempMailPlus'):
if config.getboolean('TempMailPlus', 'enabled'):
email = config.get('TempMailPlus', 'email')
epin = config.get('TempMailPlus', 'epin')
if email and epin:
from email_tabs.tempmail_plus_tab import TempMailPlusTab
email_tab = TempMailPlusTab(email, epin)
print(f"{Fore.CYAN}{EMOJI['MAIL']} {self.translator.get('register.using_tempmail_plus')}{Style.RESET_ALL}")
# Use new_signup.py directly for registration
from new_signup import main as new_signup_main
# Execute new registration process, passing translator
result, browser_tab = new_signup_main(
email=self.email_address,
password=self.password,
first_name=self.first_name,
last_name=self.last_name,
email_tab=email_tab, # Pass email_tab if tempmail_plus is enabled
controller=self, # Pass self instead of self.controller
translator=self.translator
)
if result:
# Use the returned browser instance to get account information
self.signup_tab = browser_tab # Save browser instance
success = self._get_account_info()
# Close browser after getting information
if browser_tab:
try:
browser_tab.quit()
except:
pass
return success
return False
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.register_process_error', error=str(e))}{Style.RESET_ALL}")
return False
finally:
# Ensure browser is closed in any case
if browser_tab:
try:
browser_tab.quit()
except:
pass
def _get_account_info(self):
"""Get Account Information and Token"""
try:
self.signup_tab.get(self.settings_url)
time.sleep(2)
usage_selector = (
"css:div.col-span-2 > div > div > div > div > "
"div:nth-child(1) > div.flex.items-center.justify-between.gap-2 > "
"span.font-mono.text-sm\\/\\[0\\.875rem\\]"
)
usage_ele = self.signup_tab.ele(usage_selector)
total_usage = "未知"
if usage_ele:
total_usage = usage_ele.text.split("/")[-1].strip()
print(f"Total Usage: {total_usage}\n")
print(f"{Fore.CYAN}{EMOJI['WAIT']} {self.translator.get('register.get_token')}...{Style.RESET_ALL}")
max_attempts = 30
retry_interval = 2
attempts = 0
while attempts < max_attempts:
try:
cookies = self.signup_tab.cookies()
for cookie in cookies:
if cookie.get("name") == "WorkosCursorSessionToken":
token = get_token_from_cookie(cookie["value"], self.translator)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('register.token_success')}{Style.RESET_ALL}")
self._save_account_info(token, total_usage)
return True
attempts += 1
if attempts < max_attempts:
print(f"{Fore.YELLOW}{EMOJI['WAIT']} {self.translator.get('register.token_attempt', attempt=attempts, time=retry_interval)}{Style.RESET_ALL}")
time.sleep(retry_interval)
else:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.token_max_attempts', max=max_attempts)}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.token_failed', error=str(e))}{Style.RESET_ALL}")
attempts += 1
if attempts < max_attempts:
print(f"{Fore.YELLOW}{EMOJI['WAIT']} {self.translator.get('register.token_attempt', attempt=attempts, time=retry_interval)}{Style.RESET_ALL}")
time.sleep(retry_interval)
return False
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.account_error', error=str(e))}{Style.RESET_ALL}")
return False
def _save_account_info(self, token, total_usage):
"""Save Account Information to File"""
try:
# Update authentication information first
print(f"{Fore.CYAN}{EMOJI['KEY']} {self.translator.get('register.update_cursor_auth_info')}...{Style.RESET_ALL}")
if self.update_cursor_auth(email=self.email_address, access_token=token, refresh_token=token, auth_type="Auth_0"):
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('register.cursor_auth_info_updated')}...{Style.RESET_ALL}")
else:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.cursor_auth_info_update_failed')}...{Style.RESET_ALL}")
# Reset machine ID
print(f"{Fore.CYAN}{EMOJI['UPDATE']} {self.translator.get('register.reset_machine_id')}...{Style.RESET_ALL}")
resetter = MachineIDResetter(self.translator) # Create instance with translator
if not resetter.reset_machine_ids(): # Call reset_machine_ids method directly
raise Exception("Failed to reset machine ID")
# Save account information to file
with open('cursor_accounts.txt', 'a', encoding='utf-8') as f:
f.write(f"\n{'='*50}\n")
f.write(f"Email: {self.email_address}\n")
f.write(f"Password: {self.password}\n")
f.write(f"Token: {token}\n")
f.write(f"Usage Limit: {total_usage}\n")
f.write(f"{'='*50}\n")
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('register.account_info_saved')}...{Style.RESET_ALL}")
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.save_account_info_failed', error=str(e))}{Style.RESET_ALL}")
return False
def start(self):
"""Start Registration Process"""
try:
if self.setup_email():
if self.register_cursor():
print(f"\n{Fore.GREEN}{EMOJI['DONE']} {self.translator.get('register.cursor_registration_completed')}...{Style.RESET_ALL}")
return True
return False
finally:
# Close email tab
if hasattr(self, 'temp_email'):
try:
self.temp_email.close()
except:
pass
def update_cursor_auth(self, email=None, access_token=None, refresh_token=None, auth_type="Auth_0"):
"""Convenient function to update Cursor authentication information"""
auth_manager = CursorAuth(translator=self.translator)
return auth_manager.update_auth(email, access_token, refresh_token, auth_type)
def main(translator=None):
"""Main function to be called from main.py"""
print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['START']} {translator.get('register.title')}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
registration = CursorRegistration(translator)
registration.start()
print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
input(f"{EMOJI['INFO']} {translator.get('register.press_enter')}...")
if __name__ == "__main__":
from main import translator as main_translator
main(main_translator)
```
## /delete_cursor_google.py
```py path="/delete_cursor_google.py"
from oauth_auth import OAuthHandler
import time
from colorama import Fore, Style, init
import sys
# Initialize colorama
init()
# Define emoji constants
EMOJI = {
'START': '🚀',
'DELETE': '🗑️',
'SUCCESS': '✅',
'ERROR': '❌',
'WAIT': '⏳',
'INFO': 'ℹ️',
'WARNING': '⚠️'
}
class CursorGoogleAccountDeleter(OAuthHandler):
def __init__(self, translator=None):
super().__init__(translator, auth_type='google')
def delete_google_account(self):
"""Delete Cursor account using Google OAuth"""
try:
# Setup browser and select profile
if not self.setup_browser():
return False
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('account_delete.starting_process') if self.translator else 'Starting account deletion process...'}{Style.RESET_ALL}")
# Navigate to Cursor auth page - using the same URL as in registration
self.browser.get("https://authenticator.cursor.sh/sign-up")
time.sleep(2)
# Click Google auth button using same selectors as in registration
selectors = [
"//a[contains(@href,'GoogleOAuth')]",
"//a[contains(@class,'auth-method-button') and contains(@href,'GoogleOAuth')]",
"(//a[contains(@class,'auth-method-button')])[1]" # First auth button as fallback
]
auth_btn = None
for selector in selectors:
try:
auth_btn = self.browser.ele(f"xpath:{selector}", timeout=2)
if auth_btn:
break
except:
continue
if not auth_btn:
raise Exception(self.translator.get('account_delete.google_button_not_found') if self.translator else "Google login button not found")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('account_delete.logging_in') if self.translator else 'Logging in with Google...'}{Style.RESET_ALL}")
auth_btn.click()
# Wait for authentication to complete using a more robust method
print(f"{Fore.CYAN}{EMOJI['WAIT']} {self.translator.get('account_delete.waiting_for_auth', fallback='Waiting for Google authentication...')}{Style.RESET_ALL}")
# Dynamic wait for authentication
max_wait_time = 120 # Increase maximum wait time to 120 seconds
start_time = time.time()
check_interval = 3 # Check every 3 seconds
google_account_alert_shown = False # Track if we've shown the alert already
while time.time() - start_time < max_wait_time:
current_url = self.browser.url
# If we're already on the settings or dashboard page, we're successful
if "/dashboard" in current_url or "/settings" in current_url or "cursor.com" in current_url:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('account_delete.login_successful') if self.translator else 'Login successful'}{Style.RESET_ALL}")
break
# If we're on Google accounts page or accounts.google.com, wait for user selection
if "accounts.google.com" in current_url:
# Only show the alert once to avoid spamming
if not google_account_alert_shown:
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('account_delete.select_google_account', fallback='Please select your Google account...')}{Style.RESET_ALL}")
# Alert to indicate user action needed
try:
self.browser.run_js("""
alert('Please select your Google account to continue with Cursor authentication');
""")
google_account_alert_shown = True # Mark that we've shown the alert
except:
pass # Alert is optional
# Sleep before checking again
time.sleep(check_interval)
else:
# If the loop completed without breaking, it means we hit the timeout
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {self.translator.get('account_delete.auth_timeout', fallback='Authentication timeout, continuing anyway...')}{Style.RESET_ALL}")
# Check current URL to determine next steps
current_url = self.browser.url
# If we're already on the settings page, no need to navigate
if "/settings" in current_url and "cursor.com" in current_url:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('account_delete.already_on_settings', fallback='Already on settings page')}{Style.RESET_ALL}")
# If we're on the dashboard or any Cursor page but not settings, navigate to settings
elif "cursor.com" in current_url or "authenticator.cursor.sh" in current_url:
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('account_delete.navigating_to_settings', fallback='Navigating to settings page...')}{Style.RESET_ALL}")
self.browser.get("https://www.cursor.com/settings")
# If we're still on Google auth or somewhere else, try directly going to settings
else:
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {self.translator.get('account_delete.login_redirect_failed', fallback='Login redirection failed, trying direct navigation...')}{Style.RESET_ALL}")
self.browser.get("https://www.cursor.com/settings")
# Wait for the settings page to load
time.sleep(3) # Reduced from 5 seconds
# First look for the email element to confirm we're logged in
try:
email_element = self.browser.ele("css:div[class='flex w-full flex-col gap-2'] div:nth-child(2) p:nth-child(2)")
if email_element:
email = email_element.text
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('account_delete.found_email', email=email, fallback=f'Found email: {email}')}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {self.translator.get('account_delete.email_not_found', error=str(e), fallback=f'Email not found: {str(e)}')}{Style.RESET_ALL}")
# Click on "Advanced" tab or dropdown - keep only the successful approach
advanced_found = False
# Direct JavaScript querySelector approach that worked according to logs
try:
advanced_element_js = self.browser.run_js("""
// Try to find the Advanced dropdown using querySelector with the exact classes
let advancedElement = document.querySelector('div.mb-0.flex.cursor-pointer.items-center.text-xs:not([style*="display: none"])');
// If not found, try a more general approach
if (!advancedElement) {
const allDivs = document.querySelectorAll('div');
for (const div of allDivs) {
if (div.textContent.includes('Advanced') &&
div.className.includes('mb-0') &&
div.className.includes('flex') &&
div.className.includes('cursor-pointer')) {
advancedElement = div;
break;
}
}
}
// Click the element if found
if (advancedElement) {
advancedElement.click();
return true;
}
return false;
""")
if advanced_element_js:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('account_delete.advanced_tab_clicked', fallback='Found and clicked Advanced using direct JavaScript selector')}{Style.RESET_ALL}")
advanced_found = True
time.sleep(1) # Reduced from 2 seconds
except Exception as e:
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {self.translator.get('account_delete.advanced_tab_error', error=str(e), fallback='JavaScript querySelector approach failed: {str(e)}')}{Style.RESET_ALL}")
if not advanced_found:
# Fallback to direct URL navigation which is faster and more reliable
try:
self.browser.get("https://www.cursor.com/settings?tab=advanced")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('account_delete.direct_advanced_navigation', fallback='Trying direct navigation to advanced tab')}{Style.RESET_ALL}")
advanced_found = True
except:
raise Exception(self.translator.get('account_delete.advanced_tab_not_found') if self.translator else "Advanced option not found after multiple attempts")
# Wait for dropdown/tab content to load
time.sleep(2) # Reduced from 4 seconds
# Find and click the "Delete Account" button
delete_button_found = False
# Simplified approach for delete button based on what worked
delete_button_selectors = [
'xpath://button[contains(., "Delete Account")]',
'xpath://button[text()="Delete Account"]',
'xpath://div[contains(text(), "Delete Account")]',
'xpath://button[contains(text(), "Delete") and contains(text(), "Account")]'
]
for selector in delete_button_selectors:
try:
delete_button = self.browser.ele(selector, timeout=2)
if delete_button:
delete_button.click()
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('account_delete.delete_button_clicked') if self.translator else 'Clicked on Delete Account button'}{Style.RESET_ALL}")
delete_button_found = True
break
except:
continue
if not delete_button_found:
raise Exception(self.translator.get('account_delete.delete_button_not_found') if self.translator else "Delete Account button not found")
# Wait for confirmation dialog to appear
time.sleep(2)
# Check if we need to input "Delete" at all - some modals might not require it
input_required = True
try:
# Try detecting if the DELETE button is already enabled
delete_button_enabled = self.browser.run_js("""
const buttons = Array.from(document.querySelectorAll('button'));
const deleteButtons = buttons.filter(btn =>
btn.textContent.trim() === 'DELETE' ||
btn.textContent.trim() === 'Delete'
);
if (deleteButtons.length > 0) {
return !deleteButtons.some(btn => btn.disabled);
}
return false;
""")
if delete_button_enabled:
print(f"{Fore.CYAN}{EMOJI['INFO']} DELETE button appears to be enabled already. Input may not be required.{Style.RESET_ALL}")
input_required = False
except:
pass
# Type "Delete" in the confirmation input - only if required
delete_input_found = False
if input_required:
# Try common selectors for the input field
delete_input_selectors = [
'xpath://input[@placeholder="Delete"]',
'xpath://div[contains(@class, "modal")]//input',
'xpath://input',
'css:input'
]
for selector in delete_input_selectors:
try:
delete_input = self.browser.ele(selector, timeout=3)
if delete_input:
delete_input.clear()
delete_input.input("Delete")
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('account_delete.typed_delete', fallback='Typed \"Delete\" in confirmation box')}{Style.RESET_ALL}")
delete_input_found = True
time.sleep(2)
break
except:
# Try direct JavaScript input as fallback
try:
self.browser.run_js(r"""
arguments[0].value = "Delete";
const event = new Event('input', { bubbles: true });
arguments[0].dispatchEvent(event);
const changeEvent = new Event('change', { bubbles: true });
arguments[0].dispatchEvent(changeEvent);
""", delete_input)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('account_delete.typed_delete_js', fallback='Typed \"Delete\" using JavaScript')}{Style.RESET_ALL}")
delete_input_found = True
time.sleep(2)
break
except:
continue
if not delete_input_found:
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {self.translator.get('account_delete.delete_input_not_found', fallback='Delete confirmation input not found, continuing anyway')}{Style.RESET_ALL}")
time.sleep(2)
# Wait before clicking the final DELETE button
time.sleep(2)
# Click on the final DELETE button
confirm_button_found = False
# Use JavaScript approach for the DELETE button
try:
delete_button_js = self.browser.run_js("""
// Try to find the DELETE button by exact text content
const buttons = Array.from(document.querySelectorAll('button'));
const deleteButton = buttons.find(btn =>
btn.textContent.trim() === 'DELETE' ||
btn.textContent.trim() === 'Delete'
);
if (deleteButton) {
console.log("Found DELETE button with JavaScript");
deleteButton.click();
return true;
}
// If not found by text, try to find right-most button in the modal
const modalButtons = Array.from(document.querySelectorAll('.relative button, [role="dialog"] button, .modal button, [aria-modal="true"] button'));
if (modalButtons.length > 1) {
modalButtons.sort((a, b) => {
const rectA = a.getBoundingClientRect();
const rectB = b.getBoundingClientRect();
return rectB.right - rectA.right;
});
console.log("Clicking right-most button in modal");
modalButtons[0].click();
return true;
} else if (modalButtons.length === 1) {
console.log("Clicking single button found in modal");
modalButtons[0].click();
return true;
}
return false;
""")
if delete_button_js:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('account_delete.delete_button_clicked', fallback='Clicked DELETE button')}{Style.RESET_ALL}")
confirm_button_found = True
except:
pass
if not confirm_button_found:
# Fallback to simple selectors
delete_button_selectors = [
'xpath://button[text()="DELETE"]',
'xpath://div[contains(@class, "modal")]//button[last()]'
]
for selector in delete_button_selectors:
try:
delete_button = self.browser.ele(selector, timeout=2)
if delete_button:
delete_button.click()
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('account_delete.delete_button_clicked', fallback='Account deleted successfully!')}{Style.RESET_ALL}")
confirm_button_found = True
break
except:
continue
if not confirm_button_found:
raise Exception(self.translator.get('account_delete.confirm_button_not_found') if self.translator else "Confirm button not found")
# Wait a moment to see the confirmation
time.sleep(2)
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('account_delete.error', error=str(e)) if self.translator else f'Error during account deletion: {str(e)}'}{Style.RESET_ALL}")
return False
finally:
# Clean up browser
if self.browser:
try:
self.browser.quit()
except:
pass
def main(translator=None):
"""Main function to handle Google account deletion"""
print(f"\n{Fore.CYAN}{EMOJI['START']} {translator.get('account_delete.title') if translator else 'Cursor Google Account Deletion Tool'}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{'─' * 50}{Style.RESET_ALL}")
deleter = CursorGoogleAccountDeleter(translator)
try:
# Ask for confirmation
print(f"{Fore.RED}{EMOJI['WARNING']} {translator.get('account_delete.warning') if translator else 'WARNING: This will permanently delete your Cursor account. This action cannot be undone.'}{Style.RESET_ALL}")
confirm = input(f"{Fore.RED} {translator.get('account_delete.confirm_prompt') if translator else 'Are you sure you want to proceed? (y/N): '}{Style.RESET_ALL}").lower()
if confirm != 'y':
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('account_delete.cancelled') if translator else 'Account deletion cancelled.'}{Style.RESET_ALL}")
return
success = deleter.delete_google_account()
if success:
print(f"\n{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('account_delete.success') if translator else 'Your Cursor account has been successfully deleted!'}{Style.RESET_ALL}")
else:
print(f"\n{Fore.RED}{EMOJI['ERROR']} {translator.get('account_delete.failed') if translator else 'Account deletion process failed or was cancelled.'}{Style.RESET_ALL}")
except KeyboardInterrupt:
print(f"\n{Fore.YELLOW}{EMOJI['INFO']} {translator.get('account_delete.interrupted') if translator else 'Account deletion process interrupted by user.'}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('account_delete.unexpected_error', error=str(e)) if translator else f'Unexpected error: {str(e)}'}{Style.RESET_ALL}")
finally:
print(f"{Fore.YELLOW}{'─' * 50}{Style.RESET_ALL}")
if __name__ == "__main__":
main()
```
## /disable_auto_update.py
```py path="/disable_auto_update.py"
import os
import sys
import platform
import shutil
from colorama import Fore, Style, init
import subprocess
from config import get_config
import re
import tempfile
# Initialize colorama
init()
# Define emoji constants
EMOJI = {
"PROCESS": "🔄",
"SUCCESS": "✅",
"ERROR": "❌",
"INFO": "ℹ️",
"FOLDER": "📁",
"FILE": "📄",
"STOP": "🛑",
"CHECK": "✔️"
}
class AutoUpdateDisabler:
def __init__(self, translator=None):
self.translator = translator
self.system = platform.system()
# Get path from configuration file
config = get_config(translator)
if config:
if self.system == "Windows":
self.updater_path = config.get('WindowsPaths', 'updater_path', fallback=os.path.join(os.getenv("LOCALAPPDATA", ""), "cursor-updater"))
self.update_yml_path = config.get('WindowsPaths', 'update_yml_path', fallback=os.path.join(os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app", "update.yml"))
self.product_json_path = config.get('WindowsPaths', 'product_json_path', fallback=os.path.join(os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app", "product.json"))
elif self.system == "Darwin":
self.updater_path = config.get('MacPaths', 'updater_path', fallback=os.path.expanduser("~/Library/Application Support/cursor-updater"))
self.update_yml_path = config.get('MacPaths', 'update_yml_path', fallback="/Applications/Cursor.app/Contents/Resources/app-update.yml")
self.product_json_path = config.get('MacPaths', 'product_json_path', fallback="/Applications/Cursor.app/Contents/Resources/app/product.json")
elif self.system == "Linux":
self.updater_path = config.get('LinuxPaths', 'updater_path', fallback=os.path.expanduser("~/.config/cursor-updater"))
self.update_yml_path = config.get('LinuxPaths', 'update_yml_path', fallback=os.path.expanduser("~/.config/cursor/resources/app-update.yml"))
self.product_json_path = config.get('LinuxPaths', 'product_json_path', fallback=os.path.expanduser("~/.config/cursor/resources/app/product.json"))
else:
# If configuration loading fails, use default paths
self.updater_paths = {
"Windows": os.path.join(os.getenv("LOCALAPPDATA", ""), "cursor-updater"),
"Darwin": os.path.expanduser("~/Library/Application Support/cursor-updater"),
"Linux": os.path.expanduser("~/.config/cursor-updater")
}
self.updater_path = self.updater_paths.get(self.system)
self.update_yml_paths = {
"Windows": os.path.join(os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app", "update.yml"),
"Darwin": "/Applications/Cursor.app/Contents/Resources/app-update.yml",
"Linux": os.path.expanduser("~/.config/cursor/resources/app-update.yml")
}
self.update_yml_path = self.update_yml_paths.get(self.system)
self.product_json_paths = {
"Windows": os.path.join(os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app", "product.json"),
"Darwin": "/Applications/Cursor.app/Contents/Resources/app/product.json",
"Linux": os.path.expanduser("~/.config/cursor/resources/app/product.json")
}
self.product_json_path = self.product_json_paths.get(self.system)
def _remove_update_url(self):
"""Remove update URL"""
try:
original_stat = os.stat(self.product_json_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(self.product_json_path, "r", encoding="utf-8") as product_json_file:
content = product_json_file.read()
patterns = {
r"https://api2.cursor.sh/aiserver.v1.AuthService/DownloadUpdate": r"",
r"https://api2.cursor.sh/updates": r"",
r"http://cursorapi.com/updates": r"",
}
for pattern, replacement in patterns.items():
content = re.sub(pattern, replacement, content)
tmp_file.write(content)
tmp_path = tmp_file.name
shutil.copy2(self.product_json_path, self.product_json_path + ".old")
shutil.move(tmp_path, self.product_json_path)
os.chmod(self.product_json_path, original_mode)
if os.name != "nt":
os.chown(self.product_json_path, original_uid, original_gid)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.file_modified')}{Style.RESET_ALL}")
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.modify_file_failed', error=str(e))}{Style.RESET_ALL}")
if "tmp_path" in locals():
os.unlink(tmp_path)
return False
def _kill_cursor_processes(self):
"""End all Cursor processes"""
try:
print(f"{Fore.CYAN}{EMOJI['PROCESS']} {self.translator.get('update.killing_processes') if self.translator else '正在结束 Cursor 进程...'}{Style.RESET_ALL}")
if self.system == "Windows":
subprocess.run(['taskkill', '/F', '/IM', 'Cursor.exe', '/T'], capture_output=True)
else:
subprocess.run(['pkill', '-f', 'Cursor'], capture_output=True)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('update.processes_killed') if self.translator else 'Cursor 进程已结束'}{Style.RESET_ALL}")
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('update.kill_process_failed', error=str(e)) if self.translator else f'结束进程失败: {e}'}{Style.RESET_ALL}")
return False
def _remove_updater_directory(self):
"""Delete updater directory"""
try:
updater_path = self.updater_path
if not updater_path:
raise OSError(self.translator.get('update.unsupported_os', system=self.system) if self.translator else f"不支持的操作系统: {self.system}")
print(f"{Fore.CYAN}{EMOJI['FOLDER']} {self.translator.get('update.removing_directory') if self.translator else '正在删除更新程序目录...'}{Style.RESET_ALL}")
if os.path.exists(updater_path):
try:
if os.path.isdir(updater_path):
shutil.rmtree(updater_path)
else:
os.remove(updater_path)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('update.directory_removed') if self.translator else '更新程序目录已删除'}{Style.RESET_ALL}")
except PermissionError:
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('update.directory_locked', path=updater_path) if self.translator else f'更新程序目录已被锁定,跳过删除: {updater_path}'}{Style.RESET_ALL}")
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('update.remove_directory_failed', error=str(e)) if self.translator else f'删除目录失败: {e}'}{Style.RESET_ALL}")
return True
def _clear_update_yml_file(self):
"""Clear update.yml file"""
try:
update_yml_path = self.update_yml_path
if not update_yml_path:
raise OSError(self.translator.get('update.unsupported_os', system=self.system) if self.translator else f"不支持的操作系统: {self.system}")
print(f"{Fore.CYAN}{EMOJI['FILE']} {self.translator.get('update.clearing_update_yml') if self.translator else '正在清空更新配置文件...'}{Style.RESET_ALL}")
if os.path.exists(update_yml_path):
try:
with open(update_yml_path, 'w') as f:
f.write('')
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('update.update_yml_cleared') if self.translator else '更新配置文件已清空'}{Style.RESET_ALL}")
except PermissionError:
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('update.yml_locked') if self.translator else '更新配置文件已被锁定,跳过清空'}{Style.RESET_ALL}")
else:
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('update.update_yml_not_found') if self.translator else '更新配置文件不存在'}{Style.RESET_ALL}")
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('update.clear_update_yml_failed', error=str(e)) if self.translator else f'清空更新配置文件失败: {e}'}{Style.RESET_ALL}")
return False
def _create_blocking_file(self):
"""Create blocking files"""
try:
# 检查 updater_path
updater_path = self.updater_path
if not updater_path:
raise OSError(self.translator.get('update.unsupported_os', system=self.system) if self.translator else f"不支持的操作系统: {self.system}")
print(f"{Fore.CYAN}{EMOJI['FILE']} {self.translator.get('update.creating_block_file') if self.translator else '正在创建阻止文件...'}{Style.RESET_ALL}")
# 创建 updater_path 阻止文件
try:
os.makedirs(os.path.dirname(updater_path), exist_ok=True)
open(updater_path, 'w').close()
# 设置 updater_path 为只读
if self.system == "Windows":
os.system(f'attrib +r "{updater_path}"')
else:
os.chmod(updater_path, 0o444) # 设置为只读
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('update.block_file_created') if self.translator else '阻止文件已创建'}: {updater_path}{Style.RESET_ALL}")
except PermissionError:
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('update.block_file_locked') if self.translator else '阻止文件已被锁定,跳过创建'}{Style.RESET_ALL}")
# 检查 update_yml_path
update_yml_path = self.update_yml_path
if update_yml_path and os.path.exists(os.path.dirname(update_yml_path)):
try:
# 创建 update_yml_path 阻止文件
with open(update_yml_path, 'w') as f:
f.write('# This file is locked to prevent auto-updates\nversion: 0.0.0\n')
# 设置 update_yml_path 为只读
if self.system == "Windows":
os.system(f'attrib +r "{update_yml_path}"')
else:
os.chmod(update_yml_path, 0o444) # 设置为只读
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('update.yml_locked') if self.translator else '更新配置文件已锁定'}: {update_yml_path}{Style.RESET_ALL}")
except PermissionError:
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('update.yml_already_locked') if self.translator else '更新配置文件已被锁定,跳过修改'}{Style.RESET_ALL}")
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('update.create_block_file_failed', error=str(e)) if self.translator else f'创建阻止文件失败: {e}'}{Style.RESET_ALL}")
return True # 返回 True 以继续执行后续步骤
def disable_auto_update(self):
"""Disable auto update"""
try:
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('update.start_disable') if self.translator else '开始禁用自动更新...'}{Style.RESET_ALL}")
# 1. End processes
if not self._kill_cursor_processes():
return False
# 2. Delete directory - 即使失败也继续执行
self._remove_updater_directory()
# 3. Clear update.yml file
if not self._clear_update_yml_file():
return False
# 4. Create blocking file
if not self._create_blocking_file():
return False
# 5. Remove update URL from product.json
if not self._remove_update_url():
return False
print(f"{Fore.GREEN}{EMOJI['CHECK']} {self.translator.get('update.disable_success') if self.translator else '自动更新已禁用'}{Style.RESET_ALL}")
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('update.disable_failed', error=str(e)) if self.translator else f'禁用自动更新失败: {e}'}{Style.RESET_ALL}")
return False
def run(translator=None):
"""Convenient function for directly calling the disable function"""
print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['STOP']} {translator.get('update.title') if translator else 'Disable Cursor Auto Update'}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
disabler = AutoUpdateDisabler(translator)
disabler.disable_auto_update()
print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
input(f"{EMOJI['INFO']} {translator.get('update.press_enter') if translator else 'Press Enter to Continue...'}")
if __name__ == "__main__":
from main import translator as main_translator
run(main_translator)
```
## /email_tabs/email_tab_interface.py
```py path="/email_tabs/email_tab_interface.py"
from abc import ABC, abstractmethod
class EmailTabInterface(ABC):
"""Email tab interface for handling email verification"""
@abstractmethod
def refresh_inbox(self) -> None:
"""Refresh the email inbox"""
pass
@abstractmethod
def check_for_cursor_email(self) -> bool:
"""Check if there is a verification email from Cursor
Returns:
bool: True if verification email exists, False otherwise
"""
pass
@abstractmethod
def get_verification_code(self) -> str:
"""Get the verification code from the email
Returns:
str: The verification code if found, empty string otherwise
"""
pass
```
## /email_tabs/tempmail_plus_tab.py
```py path="/email_tabs/tempmail_plus_tab.py"
import requests
import re
from typing import Optional
from .email_tab_interface import EmailTabInterface
class TempMailPlusTab(EmailTabInterface):
"""Implementation of EmailTabInterface for tempmail.plus"""
def __init__(self, email: str, epin: str):
"""Initialize TempMailPlusTab
Args:
email: The email address to check
epin: The epin token for authentication
"""
self.email = email
self.epin = epin
self.base_url = "https://tempmail.plus/api"
self.headers = {
'accept': 'application/json',
'accept-language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7,zh-TW;q=0.6',
'cache-control': 'no-cache',
'pragma': 'no-cache',
'referer': 'https://tempmail.plus/zh/',
'sec-ch-ua': '"Google Chrome";v="135", "Not-A.Brand";v="8", "Chromium";v="135"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"macOS"',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'same-origin',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36',
'x-requested-with': 'XMLHttpRequest'
}
self.cookies = {'email': email}
self._cached_mail_id = None # 缓存mail_id
def refresh_inbox(self) -> None:
"""Refresh the email inbox"""
pass
def check_for_cursor_email(self) -> bool:
"""Check if there is a verification email from Cursor
Returns:
bool: True if verification email exists, False otherwise
"""
try:
params = {
'email': self.email,
'epin': self.epin
}
response = requests.get(
f"{self.base_url}/mails",
params=params,
headers=self.headers,
cookies=self.cookies
)
response.raise_for_status()
data = response.json()
if data.get('result') and data.get('mail_list'):
for mail in data['mail_list']:
if 'cursor.sh' in mail.get('from_mail', '') and mail.get('is_new') == True:
self._cached_mail_id = mail.get('mail_id') # 缓存mail_id
return True
return False
except Exception as e:
print(f"检查Cursor邮件失败: {str(e)}")
return False
def get_verification_code(self) -> str:
"""Get the verification code from the email
Returns:
str: The verification code if found, empty string otherwise
"""
try:
# 如果没有缓存的mail_id,先检查是否有新邮件
if not self._cached_mail_id:
if not self.check_for_cursor_email():
return ""
# 使用缓存的mail_id获取邮件内容
params = {
'email': self.email,
'epin': self.epin
}
response = requests.get(
f"{self.base_url}/mails/{self._cached_mail_id}",
params=params,
headers=self.headers,
cookies=self.cookies
)
response.raise_for_status()
data = response.json()
if not data.get('result'):
return ""
# Extract verification code from text content using regex
text = data.get('text', '')
match = re.search(r'\n\n(\d{6})\n\n', text)
if match:
return match.group(1)
return ""
except Exception as e:
print(f"获取验证码失败: {str(e)}")
return ""
```
## /fill_missing_translations.py
```py path="/fill_missing_translations.py"
"""
Compares two JSON translation files in /locales (e.g., en.json and ar.json).
Finds keys missing in the target file, translates their values using Google Translate API (googletrans 4.0.2),
and inserts the translations. Runs in parallel for speed and creates a backup of the target file.
"""
import json
import sys
import os
from pathlib import Path
import re
from concurrent.futures import ThreadPoolExecutor, as_completed
from colorama import init, Fore, Style
import time
import shutil
import asyncio
# Import googletrans with error handling
try:
from googletrans import Translator as GoogleTranslator
GOOGLETRANS_AVAILABLE = True
print(f"{Fore.GREEN}Using googletrans for translation.{Style.RESET_ALL}")
except ImportError:
GOOGLETRANS_AVAILABLE = False
print(f"{Fore.YELLOW}googletrans library not found. Will use web scraping fallback method.{Style.RESET_ALL}")
print(f"{Fore.YELLOW}To install googletrans: pip install googletrans==4.0.2{Style.RESET_ALL}")
import requests
init(autoreset=True)
# Language code mapping to Google Translate language codes
LANGUAGE_MAPPING = {
"zh_cn": "zh-CN", # Simplified Chinese
"zh_tw": "zh-TW", # Traditional Chinese
"ar": "ar", # Arabic
"bg": "bg", # Bulgarian
"de": "de", # German
"en": "en", # English
"es": "es", # Spanish
"fr": "fr", # French
"it": "it", # Italian
"ja": "ja", # Japanese
"ko": "ko", # Korean
"nl": "nl", # Dutch
"pt": "pt", # Portuguese
"ru": "ru", # Russian
"tr": "tr", # Turkish
"vi": "vi", # Vietnamese
# Add more mappings as needed
}
# Recursively get all keys in the JSON as dot-separated paths
def get_keys(d, prefix=''):
keys = set()
for k, v in d.items():
full_key = f"{prefix}.{k}" if prefix else k
if isinstance(v, dict):
keys |= get_keys(v, full_key)
else:
keys.add(full_key)
return keys
# Get value from nested dict by dot-separated path
def get_by_path(d, path):
for p in path.split('.'):
d = d[p]
return d
# Set value in nested dict by dot-separated path
def set_by_path(d, path, value):
parts = path.split('.')
for p in parts[:-1]:
if p not in d:
d[p] = {}
d = d[p]
d[parts[-1]] = value
# Get Google Translate language code from file name
def get_google_lang_code(file_lang):
# Remove .json extension if present
if file_lang.endswith('.json'):
file_lang = file_lang[:-5]
# Return mapped language code or the original if not in mapping
return LANGUAGE_MAPPING.get(file_lang, file_lang)
# Translate text using Google Translate API if available, otherwise fallback to web scraping
def translate(text, source, target):
# Map language codes to Google Translate format
source_lang = get_google_lang_code(source)
target_lang = get_google_lang_code(target)
print(f"{Fore.CYAN}Translating from {source_lang} to {target_lang}{Style.RESET_ALL}")
if GOOGLETRANS_AVAILABLE:
try:
# Use synchronous web scraping instead of async googletrans
return translate_web_scraping(text, source_lang, target_lang)
except Exception as e:
print(Fore.YELLOW + f"Translation error: {e}. Trying alternative method.")
return translate_web_scraping(text, source_lang, target_lang)
else:
return translate_web_scraping(text, source_lang, target_lang)
# Fallback translation method using web scraping
def translate_web_scraping(text, source, target):
try:
import requests
# 使用更可靠的 Google 翻译 API URL
url = f"https://translate.googleapis.com/translate_a/single?client=gtx&sl={source}&tl={target}&dt=t&q={requests.utils.quote(text)}"
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"}
response = requests.get(url, headers=headers, timeout=10)
if response.status_code == 200:
# 解析 JSON 响应
result = response.json()
# 提取翻译结果
translated_text = ''
for sentence in result[0]:
if len(sentence) > 0:
translated_text += sentence[0]
if translated_text:
return translated_text
else:
print(Fore.RED + f"Translation not found for: {text}")
return text
else:
print(Fore.RED + f"Request failed with status code {response.status_code} for: {text}")
return text
except Exception as e:
print(Fore.RED + f"Web scraping translation error: {e}")
return text
# Process a single language file
def process_language(en_filename, other_filename, create_backup=None):
# Always use the /locales directory
en_path = Path("locales") / en_filename
other_path = Path("locales") / other_filename
# Infer language code from filename (before .json)
en_lang = Path(en_filename).stem
other_lang = Path(other_filename).stem
print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
print(f"{Fore.CYAN}Processing: {other_filename} (Translating to {get_google_lang_code(other_lang)}){Style.RESET_ALL}")
print(f"{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
print(f"{Fore.CYAN}Reading source file: {en_path}{Style.RESET_ALL}")
with open(en_path, encoding='utf-8') as f:
en = json.load(f)
print(f"{Fore.CYAN}Reading target file: {other_path}{Style.RESET_ALL}")
try:
with open(other_path, encoding='utf-8') as f:
other = json.load(f)
except FileNotFoundError:
# If target file doesn't exist, create an empty one
print(f"{Fore.YELLOW}Target file not found. Creating a new file.{Style.RESET_ALL}")
other = {}
except json.JSONDecodeError:
# If target file is invalid JSON, create an empty one
print(f"{Fore.YELLOW}Target file contains invalid JSON. Creating a new file.{Style.RESET_ALL}")
other = {}
en_keys = get_keys(en)
other_keys = get_keys(other)
missing = en_keys - other_keys
print(f"{Fore.YELLOW}Found {len(missing)} missing keys{Style.RESET_ALL}")
if not missing:
print(f"{Fore.GREEN}No missing keys found. Translation is complete!{Style.RESET_ALL}")
return True
# Parallel translation using ThreadPoolExecutor
with ThreadPoolExecutor(max_workers=3) as executor: # Further reduced workers for googletrans 4.0.2
future_to_key = {
executor.submit(translate, get_by_path(en, key), en_lang, other_lang): key
for key in missing
}
completed = 0
total = len(missing)
for future in as_completed(future_to_key):
key = future_to_key[future]
value = get_by_path(en, key)
try:
translated = future.result()
completed += 1
print(f"{Fore.CYAN}[{completed}/{total}] Translated [{key}]: '{value}' -> " + Fore.MAGENTA + f"'{translated}'")
except Exception as exc:
print(f"{Fore.RED}Error translating {key}: {exc}")
translated = value
set_by_path(other, key, translated)
# Ask about backup if not specified
if create_backup is None and os.path.exists(other_path):
while True:
backup_choice = input(f"{Fore.CYAN}Create backup file? (y/N): {Style.RESET_ALL}").lower()
if backup_choice in ['y', 'yes']:
create_backup = True
break
elif backup_choice in ['', 'n', 'no']:
create_backup = False
break
else:
print(f"{Fore.RED}Invalid choice. Please enter 'y' or 'n'.{Style.RESET_ALL}")
# Create backup if requested and file exists
if create_backup and os.path.exists(other_path):
backup_path = other_path.with_suffix('.bak.json')
shutil.copy2(other_path, backup_path)
print(f"{Fore.GREEN}Backup created at {backup_path}{Style.RESET_ALL}")
# Save the updated file
with open(other_path, 'w', encoding='utf-8') as f:
json.dump(other, f, ensure_ascii=False, indent=4)
print(f"{Fore.GREEN}File updated: {other_path}{Style.RESET_ALL}")
return True
# Main function with interactive menu
def main():
# Check if locales directory exists
locales_dir = Path("locales")
if not locales_dir.exists():
print(f"{Fore.YELLOW}Creating 'locales' directory...{Style.RESET_ALL}")
locales_dir.mkdir(parents=True)
# Get all JSON files in locales directory (excluding backup files)
json_files = [f for f in os.listdir(locales_dir) if f.endswith('.json') and not f.endswith('.bak.json')]
# Check if en.json exists (source file)
if 'en.json' not in json_files:
print(f"{Fore.RED}Error: 'en.json' not found in locales directory. This file is required as the source for translations.{Style.RESET_ALL}")
return False
# Get all target language files (excluding en.json)
target_files = [f for f in json_files if f != 'en.json']
# Add option to create a new language file
available_languages = list(LANGUAGE_MAPPING.keys())
if 'en' in available_languages:
available_languages.remove('en') # Remove English as it's the source
# Filter out languages that already have files
existing_lang_codes = [f.split('.')[0] for f in target_files]
available_languages = [lang for lang in available_languages if lang not in existing_lang_codes]
# Display menu
print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
print(f"{Fore.CYAN}Translation Tool - Select target language to update{Style.RESET_ALL}")
print(f"{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
print(f"{Fore.GREEN}0{Style.RESET_ALL}. Translate all existing language files")
# List existing language files
for i, file in enumerate(target_files, 1):
lang_code = file.split('.')[0]
google_lang = get_google_lang_code(lang_code)
print(f"{Fore.GREEN}{i}{Style.RESET_ALL}. {lang_code} ({google_lang})")
# Option to create a new language file (only if there are available languages)
next_option = len(target_files) + 1
if available_languages:
print(f"\n{Fore.CYAN}Create new language file:{Style.RESET_ALL}")
print(f"{Fore.GREEN}{next_option}{Style.RESET_ALL}. Create a new language file")
max_choice = next_option
else:
max_choice = len(target_files)
# Get user choice
while True:
try:
choice = input(f"\n{Fore.CYAN}Enter your choice (0-{max_choice}): {Style.RESET_ALL}")
if choice.strip() == '':
print(f"{Fore.RED}Please enter a number.{Style.RESET_ALL}")
continue
choice = int(choice)
if choice < 0 or choice > max_choice:
print(f"{Fore.RED}Invalid choice. Please enter a number between 0 and {max_choice}.{Style.RESET_ALL}")
continue
break
except ValueError:
print(f"{Fore.RED}Invalid input. Please enter a number.{Style.RESET_ALL}")
# Ask about backup for all files
create_backup = None
if choice == 0:
while True:
backup_choice = input(f"{Fore.CYAN}Create backup files? (y/N): {Style.RESET_ALL}").lower()
if backup_choice in ['y', 'yes']:
create_backup = True
break
elif backup_choice in ['', 'n', 'no']:
create_backup = False
break
else:
print(f"{Fore.RED}Invalid choice. Please enter 'y' or 'n'.{Style.RESET_ALL}")
# Process selected language(s)
if choice == 0:
print(f"{Fore.CYAN}Translating all existing languages...{Style.RESET_ALL}")
success_count = 0
for target_file in target_files:
try:
if process_language('en.json', target_file, create_backup):
success_count += 1
except Exception as e:
print(f"{Fore.RED}Error processing {target_file}: {str(e)}{Style.RESET_ALL}")
print(f"\n{Fore.GREEN}Translation completed for {success_count} out of {len(target_files)} languages.{Style.RESET_ALL}")
elif available_languages and choice == next_option:
# Create a new language file
print(f"\n{Fore.CYAN}Available languages:{Style.RESET_ALL}")
for i, lang in enumerate(available_languages):
google_lang = get_google_lang_code(lang)
print(f"{Fore.GREEN}{i+1}{Style.RESET_ALL}. {lang} ({google_lang})")
while True:
try:
lang_choice = input(f"\n{Fore.CYAN}Select language (1-{len(available_languages)}): {Style.RESET_ALL}")
lang_choice = int(lang_choice)
if lang_choice < 1 or lang_choice > len(available_languages):
print(f"{Fore.RED}Invalid choice. Please enter a number between 1 and {len(available_languages)}.{Style.RESET_ALL}")
continue
selected_lang = available_languages[lang_choice-1]
new_file = f"{selected_lang}.json"
if new_file in json_files:
print(f"{Fore.YELLOW}Warning: {new_file} already exists. It will be updated with missing translations.{Style.RESET_ALL}")
process_language('en.json', new_file)
print(f"\n{Fore.GREEN}Created and translated {new_file}.{Style.RESET_ALL}")
break
except ValueError:
print(f"{Fore.RED}Invalid input. Please enter a number.{Style.RESET_ALL}")
else:
target_file = target_files[choice - 1]
try:
process_language('en.json', target_file)
print(f"\n{Fore.GREEN}Translation completed for {target_file}.{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.RED}Error processing {target_file}: {str(e)}{Style.RESET_ALL}")
return True
if __name__ == "__main__":
# If arguments are provided, use the old method
if len(sys.argv) == 3:
process_language(sys.argv[1], sys.argv[2])
else:
# Otherwise, show the interactive menu
main()
```
## /get_user_token.py
```py path="/get_user_token.py"
import requests
import json
import time
from colorama import Fore, Style
import os
from config import get_config
# Define emoji constants
EMOJI = {
'START': '🚀',
'OAUTH': '🔑',
'SUCCESS': '✅',
'ERROR': '❌',
'WAIT': '⏳',
'INFO': 'ℹ️',
'WARNING': '⚠️'
}
def refresh_token(token, translator=None):
"""Refresh the token using the Chinese server API
Args:
token (str): The full WorkosCursorSessionToken cookie value
translator: Optional translator object
Returns:
str: The refreshed access token or original token if refresh fails
"""
try:
config = get_config(translator)
# Get refresh_server URL from config or use default
refresh_server = config.get('Token', 'refresh_server', fallback='https://token.cursorpro.com.cn')
# Ensure the token is URL encoded properly
if '%3A%3A' not in token and '::' in token:
# Replace :: with URL encoded version if needed
token = token.replace('::', '%3A%3A')
# Make the request to the refresh server
url = f"{refresh_server}/reftoken?token={token}"
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('token.refreshing') if translator else 'Refreshing token...'}{Style.RESET_ALL}")
response = requests.get(url, timeout=30)
if response.status_code == 200:
try:
data = response.json()
if data.get('code') == 0 and data.get('msg') == "获取成功":
access_token = data.get('data', {}).get('accessToken')
days_left = data.get('data', {}).get('days_left', 0)
expire_time = data.get('data', {}).get('expire_time', 'Unknown')
if access_token:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('token.refresh_success', days=days_left, expire=expire_time) if translator else f'Token refreshed successfully! Valid for {days_left} days (expires: {expire_time})'}{Style.RESET_ALL}")
return access_token
else:
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('token.no_access_token') if translator else 'No access token in response'}{Style.RESET_ALL}")
else:
error_msg = data.get('msg', 'Unknown error')
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('token.refresh_failed', error=error_msg) if translator else f'Token refresh failed: {error_msg}'}{Style.RESET_ALL}")
except json.JSONDecodeError:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('token.invalid_response') if translator else 'Invalid JSON response from refresh server'}{Style.RESET_ALL}")
else:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('token.server_error', status=response.status_code) if translator else f'Refresh server error: HTTP {response.status_code}'}{Style.RESET_ALL}")
except requests.exceptions.Timeout:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('token.request_timeout') if translator else 'Request to refresh server timed out'}{Style.RESET_ALL}")
except requests.exceptions.ConnectionError:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('token.connection_error') if translator else 'Connection error to refresh server'}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('token.unexpected_error', error=str(e)) if translator else f'Unexpected error during token refresh: {str(e)}'}{Style.RESET_ALL}")
# Return original token if refresh fails
return token.split('%3A%3A')[-1] if '%3A%3A' in token else token.split('::')[-1] if '::' in token else token
def get_token_from_cookie(cookie_value, translator=None):
"""Extract and process token from cookie value
Args:
cookie_value (str): The WorkosCursorSessionToken cookie value
translator: Optional translator object
Returns:
str: The processed token
"""
try:
# Try to refresh the token with the API first
refreshed_token = refresh_token(cookie_value, translator)
# If refresh succeeded and returned a different token, use it
if refreshed_token and refreshed_token != cookie_value:
return refreshed_token
# If refresh failed or returned same token, use traditional extraction method
if '%3A%3A' in cookie_value:
return cookie_value.split('%3A%3A')[-1]
elif '::' in cookie_value:
return cookie_value.split('::')[-1]
else:
return cookie_value
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('token.extraction_error', error=str(e)) if translator else f'Error extracting token: {str(e)}'}{Style.RESET_ALL}")
# Fall back to original behavior
if '%3A%3A' in cookie_value:
return cookie_value.split('%3A%3A')[-1]
elif '::' in cookie_value:
return cookie_value.split('::')[-1]
else:
return cookie_value
```
## /images/cloudflare_2025-02-12_13-43-21.png
Binary file available at https://raw.githubusercontent.com/yeongpin/cursor-free-vip/refs/heads/main/images/cloudflare_2025-02-12_13-43-21.png
## /images/fix_2025-01-14_21-30-43.png
Binary file available at https://raw.githubusercontent.com/yeongpin/cursor-free-vip/refs/heads/main/images/fix_2025-01-14_21-30-43.png
## /images/free_2025-01-14_14-59-15.png
Binary file available at https://raw.githubusercontent.com/yeongpin/cursor-free-vip/refs/heads/main/images/free_2025-01-14_14-59-15.png
## /images/locale_2025-01-15_13-40-08.png
Binary file available at https://raw.githubusercontent.com/yeongpin/cursor-free-vip/refs/heads/main/images/locale_2025-01-15_13-40-08.png
## /images/logo.png
Binary file available at https://raw.githubusercontent.com/yeongpin/cursor-free-vip/refs/heads/main/images/logo.png
## /images/new107_2025-01-15_13-53-56.png
Binary file available at https://raw.githubusercontent.com/yeongpin/cursor-free-vip/refs/heads/main/images/new107_2025-01-15_13-53-56.png
## /images/new_2025-02-27_10-42-44.png
Binary file available at https://raw.githubusercontent.com/yeongpin/cursor-free-vip/refs/heads/main/images/new_2025-02-27_10-42-44.png
## /images/new_2025-03-19_00-19-09.png
Binary file available at https://raw.githubusercontent.com/yeongpin/cursor-free-vip/refs/heads/main/images/new_2025-03-19_00-19-09.png
## /images/new_2025-03-22_19-53-10.png
Binary file available at https://raw.githubusercontent.com/yeongpin/cursor-free-vip/refs/heads/main/images/new_2025-03-22_19-53-10.png
## /images/pass_2025-02-08_21-48-36.png
Binary file available at https://raw.githubusercontent.com/yeongpin/cursor-free-vip/refs/heads/main/images/pass_2025-02-08_21-48-36.png
## /images/paypal.png
Binary file available at https://raw.githubusercontent.com/yeongpin/cursor-free-vip/refs/heads/main/images/paypal.png
## /images/pro_2025-01-11_00-50-40.png
Binary file available at https://raw.githubusercontent.com/yeongpin/cursor-free-vip/refs/heads/main/images/pro_2025-01-11_00-50-40.png
## /images/pro_2025-01-11_00-51-07.png
Binary file available at https://raw.githubusercontent.com/yeongpin/cursor-free-vip/refs/heads/main/images/pro_2025-01-11_00-51-07.png
## /images/pro_2025-01-11_16-24-03.png
Binary file available at https://raw.githubusercontent.com/yeongpin/cursor-free-vip/refs/heads/main/images/pro_2025-01-11_16-24-03.png
## /images/pro_2025-01-11_22-33-09.gif
Binary file available at https://raw.githubusercontent.com/yeongpin/cursor-free-vip/refs/heads/main/images/pro_2025-01-11_22-33-09.gif
## /images/pro_2025-01-13_13-49-55.png
Binary file available at https://raw.githubusercontent.com/yeongpin/cursor-free-vip/refs/heads/main/images/pro_2025-01-13_13-49-55.png
## /images/pro_2025-01-14_14-40-37.png
Binary file available at https://raw.githubusercontent.com/yeongpin/cursor-free-vip/refs/heads/main/images/pro_2025-01-14_14-40-37.png
## /images/pro_2025-04-05_18-47-56.png
Binary file available at https://raw.githubusercontent.com/yeongpin/cursor-free-vip/refs/heads/main/images/pro_2025-04-05_18-47-56.png
## /images/product_2025-04-16_10-40-21.png
Binary file available at https://raw.githubusercontent.com/yeongpin/cursor-free-vip/refs/heads/main/images/product_2025-04-16_10-40-21.png
## /images/pronew_2025-02-13_15-01-32.png
Binary file available at https://raw.githubusercontent.com/yeongpin/cursor-free-vip/refs/heads/main/images/pronew_2025-02-13_15-01-32.png
## /images/provi-code.jpg
Binary file available at https://raw.githubusercontent.com/yeongpin/cursor-free-vip/refs/heads/main/images/provi-code.jpg
## /images/what_2025-01-13_13-32-54.png
Binary file available at https://raw.githubusercontent.com/yeongpin/cursor-free-vip/refs/heads/main/images/what_2025-01-13_13-32-54.png
The content has been capped at 50000 tokens, and files over NaN bytes have been omitted. The user could consider applying other filters to refine the result. The better and more specific the context, the better the LLM can follow instructions. If the context seems verbose, the user can refine the filter using uithub. Thank you for using https://uithub.com - Perfect LLM context for any GitHub repo.