Merge branch 'yeongpin:main' into feat/feature-request-template-new

This commit is contained in:
Nigel 2025-04-03 01:11:38 +02:00 committed by GitHub
commit 2595c4c95d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 2512 additions and 370 deletions

4
.env
View File

@ -1,2 +1,2 @@
version=1.7.17
VERSION=1.7.17
version=1.8.05
VERSION=1.8.05

View File

@ -6,7 +6,7 @@ on:
version:
description: 'Version number (e.g. 1.0.9)'
required: true
default: '1.7.17'
default: '1.8.04'
permissions:
contents: write

2
.gitignore vendored
View File

@ -14,6 +14,8 @@ build.py
build.sh
ENV/
test.py
new_tempemail_smail.py
new_tempemail_api.py
install.bat
run.bat

View File

@ -1,9 +1,49 @@
# Change Log
## v1.7.18 (Pre-Release)
1. Fix: No Write Permission | 修復沒有寫入權限
## v1.8.05
1. Fix: Linux Path Not Found 修復linuxpath問題
2. Add: support for detecting both 150/150 and 50/50 usage limits 添加偵測50 或者150的使用量
3. Improve: usage parsing and validation 檢測使用量
## v1.8.04
1. Update totally_reset_cursor.py | 更新 totally_reset_cursor.py
2. Fix: improve Linux Chrome visibility and root user handling | 修復 Linux Chrome 可見性以及 root 用戶處理
3. Fix: improve Linux path handling and fix permission issues | 修復 Linux 路徑處理以及修復權限問題
4. Fix: Some Issues | 修復一些問題
## v1.8.03
1. Fix: Improve Linux path handling and add case-insensitive Cursor directory detection 修復Linux系統路徑錯誤以及添加cursor 路徑偵測
2. Fix: Some Issues | 修復一些問題
## v1.8.02
1. Add: New Temp Email | 增加新臨時郵箱
2. Add: Config Options | 增加配置選項
3. Add: Update Windows Machine ID | 增加更新 Windows 機器 ID
4. Add: Contributors Options | 增加貢獻者選項
5. Add: Check update enable Options In config | 增加在 config 中檢查更新選項
6. Add: Show account info enabled options in config | 增加在 config 中顯示賬號信息選項
7. Optimize Row & Colume Options | 優化行與列選項
8. Fix: Too Many Free Trial On Some Machine | 修復某些機器上太多免費試用
9. Fix: Disable Auto Update | 修復禁用自動更新
10. Fix: Linux Chrome Not Open Correct | 修復 Linux Chrome 未正確打開
11. Fix: Some Issues | 修復一些問題
## v1.8.01
1. Add: Cursor Account Info | 增加 Cursor 賬號信息
2. Fix: Disable Auto Update | 修復禁用自動更新
3. Add: 0.48.x Version Support | 增加 0.48.x 版本支持
4. Revert: Totally Reser Cursor to Beta | 恢復完全重置 Cursor 到 Beta
5. Reopen: Totally Reset Cursor | 重新開啟完全重置 Cursor
6. Fix: Logo.py Center | 修復 Logo.py 居中
7. Fix: Linux Chrome Not Open Correct | 修復 Linux Chrome 未正確打開
8. Fix: Some Issues | 修復一些問題
## v1.7.18
1. Fix: No Write Permission | 修復沒有寫入權限
2. Fix: Improve Linux path detection and config handling 修正 linux 路徑和config寫入讀取
3. Fix: Locale path_no_exist missing 修正 path_no_exist 語言遺失
4. Fix: Some Issues | 修復一些問題
## v1.7.17
1. Fix: Remove 10 options Totally Reset Cursor | 修復完全重置 Cursor 選項

View File

@ -13,10 +13,16 @@
[![Download](https://img.shields.io/github/downloads/yeongpin/cursor-free-vip/total?style=flat-square&logo=github&color=52c41a1)](https://github.com/yeongpin/cursor-free-vip/releases/latest)
</p>
<h4>Support Latest 0.47.x Version | 支持最新 0.47.x 版本</h4>
<h4>Support Latest 0.48.x Version | 支持最新 0.48.x 版本</h4>
This tool registers accounts with custom emails, support Google and GitHub account registrations, temporary GitHub account registration, kills all Cursor's running processes, resets and wipes Cursor data and hardware info.
Supports Windows, macOS and Linux.
For optimal performance, run with privileges and always stay up to date.
Always clean your browser's cache and cookies. If possible, use a VPN to create new accounts.
This is a tool to automatically register, support Windows and macOS systems, complete Auth verification, and reset
Cursor's configuration.
這是一個自動化工具,自動註冊,支持 Windows 和 macOS 系統,完成 Auth 驗證,重置 Cursor 的配置。
@ -94,7 +100,7 @@ irm https://raw.githubusercontent.com/yeongpin/cursor-free-vip/main/scripts/rese
</details>
2. If you want to stop the script, please press Ctrl+C<br>要停止腳本,請按 Ctrl+C
If you want to stop the script, please press Ctrl+C<br>要停止腳本,請按 Ctrl+C
## ❗ Note | 注意事項
@ -109,9 +115,9 @@ irm https://raw.githubusercontent.com/yeongpin/cursor-free-vip/main/scripts/rese
chromepath = C:\Program Files\Google/Chrome/Application/chrome.exe
[Turnstile]
# Handle Tuenstile Wait Time | 等待人機驗證時間
# Handle Turnstile Wait Time | 等待人機驗證時間
handle_turnstile_time = 2
# Handle Tuenstile Wait Random Time (must merge 1-3 or 1,3) | 等待人機驗證隨機時間(必須是 1-3 或者 1,3 這樣的組合)
# Handle Turnstile Wait Random Time (must merge 1-3 or 1,3) | 等待人機驗證隨機時間(必須是 1-3 或者 1,3 這樣的組合)
handle_turnstile_random_time = 1-3
[OSPaths]
@ -152,11 +158,17 @@ failed_retry_time = 0.5-1
retry_interval = 8-12
# Max Timeout | 最大超時時間
max_timeout = 160
[Utils]
# Check Update | 檢查更新
check_update = True
# Show Account Info | 顯示賬號信息
show_account_info = True
```
</details>
* Use administrator to run the script <br>請使用管理員身份運行腳本
* Use administrator privileges to run the script <br>請使用管理員身份運行腳本
* Confirm that Cursor is closed before running the script <br>請確保在運行腳本前已經關閉 Cursor<br>

View File

@ -16,3 +16,7 @@ ikomail.com
mailpull.com
drewzen.com
begemail.com
dugmail.com
solerbe.net
corhash.net
mailshou.com

172
config.py
View File

@ -4,6 +4,18 @@ import configparser
from colorama import Fore, Style
from utils import get_user_documents_path, get_default_chrome_path, get_linux_cursor_path
EMOJI = {
"INFO": "",
"WARNING": "⚠️",
"ERROR": "",
"SUCCESS": "",
"ADMIN": "🔒",
"ARROW": "➡️",
"USER": "👤",
"KEY": "🔑",
"SETTINGS": "⚙️"
}
def setup_config(translator=None):
"""Setup configuration file and return config object"""
try:
@ -37,6 +49,10 @@ def setup_config(translator=None):
'failed_retry_time': '0.5-1',
'retry_interval': '8-12',
'max_timeout': '160'
},
'Utils': {
'enabled_update_check': 'True',
'enabled_account_info': 'True'
}
}
@ -49,26 +65,127 @@ def setup_config(translator=None):
'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")
'updater_path': os.path.join(localappdata, "cursor-updater"),
'update_yml_path': os.path.join(localappdata, "Programs", "Cursor", "resources", "app-update.yml")
}
# 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")
'updater_path': os.path.expanduser("~/Library/Application Support/cursor-updater"),
'update_yml_path': "/Applications/Cursor.app/Contents/Resources/app-update.yml"
}
elif sys.platform == "linux":
sudo_user = os.environ.get('SUDO_USER')
actual_home = f"/home/{sudo_user}" if sudo_user else os.path.expanduser("~")
# 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': os.path.abspath(os.path.join(actual_home, ".config/cursor/User/globalStorage/storage.json")),
'sqlite_path': os.path.abspath(os.path.join(actual_home, ".config/cursor/User/globalStorage/state.vscdb")),
'machine_id_path': os.path.expanduser("~/.config/cursor/machineid"),
'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.expanduser("~/.config/cursor-updater")
'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 ""
}
# Read existing configuration and merge
@ -85,13 +202,13 @@ def setup_config(translator=None):
config.set(section, option, str(value))
config_modified = True
if translator:
print(f"{Fore.YELLOW} {translator.get('register.config_option_added', option=f'{section}.{option}')}{Style.RESET_ALL}")
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} {translator.get('register.config_updated')}{Style.RESET_ALL}")
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)
@ -101,16 +218,41 @@ def setup_config(translator=None):
with open(config_file, 'w', encoding='utf-8') as f:
config.write(f)
if translator:
print(f"{Fore.GREEN} {translator.get('register.config_created')}: {config_file}{Style.RESET_ALL}")
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}{translator.get('register.config_setup_error', error=str(e))}{Style.RESET_ALL}")
else:
print(f"{Fore.RED}❌ Error setting up config: {e}{Style.RESET_ALL}")
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 get_config(translator=None):
"""Get existing config or create new one"""

552
cursor_acc_info.py Normal file
View File

@ -0,0 +1,552 @@
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()

View File

@ -5,6 +5,8 @@ import shutil
from colorama import Fore, Style, init
import subprocess
from config import get_config
import re
import tempfile
# Initialize colorama
init()
@ -31,10 +33,13 @@ class AutoUpdateDisabler:
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"))
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")
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"))
else:
# If configuration loading fails, use default paths
self.updater_paths = {
@ -43,6 +48,52 @@ class AutoUpdateDisabler:
"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)
def _change_main_js(self):
"""Change main.js"""
try:
main_path = get_config(self.translator).get('main_js_path', fallback=os.path.expanduser("~/.config/cursor/resources/app/main.js"))
original_stat = os.stat(main_path)
original_mode = original_stat.st_mode
original_uid = original_stat.st_uid
original_gid = original_stat.st_gid
with tempfile.NamedTemporaryFile(mode="w", delete=False) as tmp_file:
with open(main_path, "r", encoding="utf-8") as main_file:
content = main_file.read()
patterns = {
r"https://api2.cursor.sh/aiserver.v1.AuthService/DownloadUpdate": r"",
}
for pattern, replacement in patterns.items():
content = re.sub(pattern, replacement, content)
tmp_file.write(content)
tmp_path = tmp_file.name
shutil.copy2(main_path, main_path + ".old")
shutil.move(tmp_path, main_path)
os.chmod(main_path, original_mode)
if os.name != "nt":
os.chown(main_path, original_uid, original_gid)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {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"""
@ -81,27 +132,70 @@ class AutoUpdateDisabler:
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}")
# 即使删除失败,也返回 True继续执行下一步
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):
# 清空文件内容
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}")
return True
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 file"""
"""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}")
# Create empty file
# 创建 updater_path 阻止文件
os.makedirs(os.path.dirname(updater_path), exist_ok=True)
open(updater_path, 'w').close()
# Set read-only attribute
# 设置 updater_path 为只读
if self.system == "Windows":
os.system(f'attrib +r "{updater_path}"')
else:
os.chmod(updater_path, 0o444) # Set to read-only
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}")
# 检查 update_yml_path
update_yml_path = self.update_yml_path
if update_yml_path and os.path.exists(os.path.dirname(update_yml_path)):
# 创建 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')
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('update.block_file_created') if self.translator else '阻止文件已创建'}{Style.RESET_ALL}")
# 设置 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}")
return True
except Exception as e:
@ -117,14 +211,21 @@ class AutoUpdateDisabler:
if not self._kill_cursor_processes():
return False
# 2. Delete directory
if not self._remove_updater_directory():
# 2. Delete directory - 即使失败也继续执行
self._remove_updater_directory()
# 3. Clear update.yml file
if not self._clear_update_yml_file():
return False
# 3. Create blocking file
# 4. Create blocking file
if not self._create_blocking_file():
return False
# 5. Change main.js
if not self._change_main_js():
return False
print(f"{Fore.GREEN}{EMOJI['CHECK']} {self.translator.get('update.disable_success') if self.translator else '自动更新已禁用'}{Style.RESET_ALL}")
return True

View File

@ -22,7 +22,9 @@
"admin_required": "Running as executable, administrator privileges required.",
"admin_required_continue": "Continuing without administrator privileges.",
"coming_soon": "Coming Soon",
"fixed_soon": "Fixed Soon"
"fixed_soon": "Fixed Soon",
"contribute": "Contribute to the Project",
"config": "Show Config"
},
"languages": {
"en": "English",
@ -104,7 +106,12 @@
"check_version_failed": "Check Version Failed: {error}",
"stack_trace": "Stack Trace",
"version_too_low": "Cursor Version Too Low: {version} < 0.45.0",
"no_write_permission": "No Write Permission: {path}"
"no_write_permission": "No Write Permission: {path}",
"path_not_found": "Path Not Found: {path}",
"modify_file_failed": "Modify File Failed: {error}",
"windows_machine_id_updated": "Windows Machine ID Updated Successfully",
"update_windows_machine_id_failed": "Update Windows Machine ID Failed: {error}",
"update_windows_machine_guid_failed": "Update Windows Machine GUID Failed: {error}"
},
"register": {
"title": "Cursor Registration Tool",
@ -273,7 +280,13 @@
"available_domains_loaded": "Available Domains Loaded: {count}",
"domains_filtered": "Domains Filtered: {count}",
"trying_to_create_email": "Trying to create email: {email}",
"domain_blocked": "Domain Blocked: {domain}"
"domain_blocked": "Domain Blocked: {domain}",
"using_chrome_profile": "Using Chrome profile from: {user_data_dir}",
"no_display_found": "No display found. Make sure X server is running.",
"try_export_display": "Try: export DISPLAY=:0",
"extension_load_error": "Extension Load Error: {error}",
"make_sure_chrome_chromium_is_properly_installed": "Make sure Chrome/Chromium is properly installed",
"try_install_chromium": "Try: sudo apt install chromium-browser"
},
"update": {
"title": "Disable Cursor Auto Update",
@ -286,7 +299,14 @@
"removing_directory": "Removing Directory",
"directory_removed": "Directory Removed",
"creating_block_file": "Creating Block File",
"block_file_created": "Block File Created"
"block_file_created": "Block File Created",
"clearing_update_yml": "Clearing update.yml file",
"update_yml_cleared": "update.yml file cleared",
"update_yml_not_found": "update.yml file not found",
"clear_update_yml_failed": "Failed to clear update.yml file: {error}",
"unsupported_os": "Unsupported OS: {system}",
"remove_directory_failed": "Failed to remove directory: {error}",
"create_block_file_failed": "Failed to create block file: {error}"
},
"updater": {
"checking": "Checking for updates...",
@ -299,7 +319,8 @@
"update_skipped": "Skipping update.",
"invalid_choice": "Invalid choice. Please enter 'Y' or 'n'.",
"development_version": "Development Version {current} > {latest}",
"changelog_title": "Changelog"
"changelog_title": "Changelog",
"rate_limit_exceeded": "GitHub API rate limit exceeded. Skipping update check."
},
"totally_reset": {
"title": "Totally Reset Cursor",
@ -439,5 +460,126 @@
"completed_successfully": "GitHub + Cursor registration completed successfully!",
"registration_encountered_issues": "GitHub + Cursor registration encountered issues.",
"check_browser_windows_for_manual_intervention_or_try_again_later": "Check browser windows for manual intervention or try again later."
}
}
},
"account_info": {
"subscription": "Subscription",
"trial_remaining": "Remaining Pro Trial",
"days": "days",
"subscription_not_found": "Subscription information not found",
"email_not_found": "Email not found",
"failed_to_get_account": "Failed to get account information",
"config_not_found": "Configuration not found.",
"failed_to_get_usage": "Failed to get usage information",
"failed_to_get_subscription": "Failed to get subscription information",
"failed_to_get_email": "Failed to get email address",
"failed_to_get_token": "Failed to get token",
"failed_to_get_account_info": "Failed to get account information",
"title": "Account Information",
"email": "Email",
"token": "Token",
"usage": "Usage",
"subscription_type": "Subscription Type",
"remaining_trial": "Remaining Trial",
"days_remaining": "Days Remaining",
"premium": "Premium",
"pro": "Pro",
"pro_trial": "Pro Trial",
"team": "Team",
"enterprise": "Enterprise",
"free": "Free",
"active": "Active",
"inactive": "Inactive",
"premium_usage": "Premium Usage",
"basic_usage": "Basic Usage",
"usage_not_found": "Usage not found",
"lifetime_access_enabled": "Lifetime Access Enabled"
},
"config": {
"config_not_available": "Configuration not available",
"configuration": "Configuration",
"enabled": "Enabled",
"disabled": "Disabled",
"config_directory": "Config Directory",
"neither_cursor_nor_cursor_directory_found": "Neither Cursor nor Cursor directory found in {config_base}",
"please_make_sure_cursor_is_installed_and_has_been_run_at_least_once": "Please make sure Cursor is installed and has been run at least once",
"storage_directory_not_found": "Storage directory not found: {storage_dir}",
"storage_file_found": "Storage file found: {storage_path}",
"file_size": "File size: {size} bytes",
"file_permissions": "File permissions: {permissions}",
"file_owner": "File owner: {owner}",
"file_group": "File group: {group}",
"error_getting_file_stats": "Error getting file stats: {error}",
"permission_denied": "Permission denied: {storage_path}",
"try_running": "Try running: {command}",
"and": "And",
"storage_file_is_empty": "Storage file is empty: {storage_path}",
"the_file_might_be_corrupted_please_reinstall_cursor": "The file might be corrupted, please reinstall Cursor",
"storage_file_not_found": "Storage file not found: {storage_path}",
"error_checking_linux_paths": "Error checking Linux paths: {error}",
"config_option_added": "Config option added: {option}",
"config_updated": "Config updated",
"config_created": "Config created: {config_file}",
"config_setup_error": "Error setting up config: {error}",
"storage_file_is_valid_and_contains_data": "Storage file is valid and contains data",
"error_reading_storage_file": "Error reading storage file: {error}",
"also_checked": "Also checked {path}"
},
"oauth": {
"authentication_button_not_found": "Authentication button not found",
"authentication_failed": "Authentication failed: {error}",
"found_cookies": "Found {count} cookies",
"token_extraction_error": "Token extraction error: {error}",
"authentication_successful": "Authentication successful - Email: {email}",
"missing_authentication_data": "Missing authentication data: {data}",
"failed_to_delete_account": "Failed to delete account: {error}",
"invalid_authentication_type": "Invalid authentication type",
"auth_update_success": "Auth update success",
"browser_closed": "Browser closed",
"auth_update_failed": "Auth update failed",
"google_start": "Google start",
"github_start": "Github start",
"usage_count": "Usage count: {usage}",
"account_has_reached_maximum_usage": "Account has reached maximum usage, {deleting}",
"starting_new_authentication_process": "Starting new authentication process...",
"failed_to_delete_expired_account": "Failed to delete expired account",
"could_not_check_usage_count": "Could not check usage count: {error}",
"found_email": "Found email: {email}",
"could_not_find_email": "Could not find email: {error}",
"could_not_find_usage_count": "Could not find usage count: {error}",
"already_on_settings_page": "Already on settings page!",
"failed_to_extract_auth_info": "Failed to extract auth info: {error}",
"no_chrome_profiles_found": "No Chrome profiles found, using Default",
"found_default_chrome_profile": "Found Default Chrome profile",
"using_first_available_chrome_profile": "Using first available Chrome profile: {profile}",
"error_finding_chrome_profile": "Error finding Chrome profile, using Default: {error}",
"initializing_browser_setup": "Initializing browser setup...",
"detected_platform": "Detected platform: {platform}",
"running_as_root_warning": "Running as root is not recommended for browser automation",
"consider_running_without_sudo": "Consider running the script without sudo",
"no_compatible_browser_found": "No compatible browser found. Please install Google Chrome or Chromium.",
"supported_browsers": "Supported browsers for {platform}",
"using_browser_profile": "Using browser profile: {profile}",
"starting_browser": "Starting browser at: {path}",
"browser_setup_completed": "Browser setup completed successfully",
"browser_setup_failed": "Browser setup failed: {error}",
"try_running_without_sudo_admin": "Try running without sudo/administrator privileges",
"redirecting_to_authenticator_cursor_sh": "Redirecting to authenticator.cursor.sh...",
"starting_google_authentication": "Starting Google authentication...",
"starting_github_authentication": "Starting GitHub authentication...",
"waiting_for_authentication": "Waiting for authentication...",
"page_changed_checking_auth": "Page changed, checking auth...",
"status_check_error": "Status check error: {error}",
"authentication_timeout": "Authentication timeout",
"account_is_still_valid": "Account is still valid (Usage: {usage})",
"starting_re_authentication_process": "Starting re-authentication process...",
"starting_new_google_authentication": "Starting new Google authentication...",
"failed_to_delete_account_or_re_authenticate": "Failed to delete account or re-authenticate: {error}",
"navigating_to_authentication_page": "Navigating to authentication page...",
"please_select_your_google_account_to_continue": "Please select your Google account to continue...",
"found_browser_data_directory": "Found browser data directory: {path}",
"authentication_successful_getting_account_info": "Authentication successful, getting account info...",
"warning_could_not_kill_existing_browser_processes": "Warning: Could not kill existing browser processes: {error}",
"browser_failed_to_start": "Browser failed to start: {error}",
"browser_failed": "Browser failed to start: {error}"
}
}

View File

@ -22,7 +22,9 @@
"admin_required": "运行可执行文件,需要管理员权限",
"admin_required_continue": "继续使用当前版本...",
"coming_soon": "即将推出",
"fixed_soon": "即将修复"
"fixed_soon": "即将修复",
"contribute": "贡献项目",
"config": "显示配置"
},
"languages": {
"en": "英语",
@ -104,7 +106,12 @@
"check_version_failed": "检查版本失败: {error}",
"stack_trace": "堆栈跟踪",
"version_too_low": "Cursor版本太低: {version} < 0.45.0",
"no_write_permission": "没有写入权限: {path}"
"no_write_permission": "没有写入权限: {path}",
"path_not_found": "路径未找到: {path}",
"modify_file_failed": "修改文件失败: {error}",
"windows_machine_id_updated": "Windows机器ID更新成功",
"update_windows_machine_id_failed": "更新Windows机器ID失败: {error}",
"update_windows_machine_guid_failed": "更新Windows机器GUID失败: {error}"
},
"register": {
"title": "Cursor 注册工具",
@ -268,7 +275,13 @@
"available_domains_loaded": "获取到 {count} 个可用域名",
"domains_filtered": "过滤后剩餘 {count} 個可用域名",
"trying_to_create_email": "尝试创建邮箱: {email}",
"domain_blocked": "域名被屏蔽: {domain}"
"domain_blocked": "域名被屏蔽: {domain}",
"using_chrome_profile": "使用 Chrome 配置文件: {user_data_dir}",
"no_display_found": "未找到显示器。确保 X 服务器正在运行。",
"try_export_display": "尝试: export DISPLAY=:0",
"extension_load_error": "加载插件失败: {error}",
"make_sure_chrome_chromium_is_properly_installed": "确保 Chrome/Chromium 已正确安装",
"try_install_chromium": "尝试: sudo apt install chromium-browser"
},
"update": {
"title": "禁用 Cursor 自动更新",
@ -281,7 +294,14 @@
"removing_directory": "删除目录",
"directory_removed": "目录已删除",
"creating_block_file": "创建阻止文件",
"block_file_created": "阻止文件已创建"
"block_file_created": "阻止文件已创建",
"clearing_update_yml": "清空 update.yml 文件",
"update_yml_cleared": "update.yml 文件已清空",
"update_yml_not_found": "update.yml 文件未找到",
"clear_update_yml_failed": "清空 update.yml 文件失败: {error}",
"unsupported_os": "不支持的操作系统: {system}",
"remove_directory_failed": "删除目录失败: {error}",
"create_block_file_failed": "创建阻止文件失败: {error}"
},
"updater": {
"checking": "检查更新...",
@ -294,7 +314,8 @@
"update_skipped": "跳过更新。",
"invalid_choice": "选择无效。请输入 'Y' 或 'n'.",
"development_version": "开发版本 {current} > {latest}",
"changelog_title": "更新日志"
"changelog_title": "更新日志",
"rate_limit_exceeded": "GitHub API 速率限制超过。跳过更新检查。"
},
"totally_reset": {
"title": "完全重置 Cursor",
@ -434,5 +455,127 @@
"completed_successfully": "GitHub + Cursor 注册成功",
"registration_encountered_issues": "GitHub + Cursor 注册遇到问题",
"check_browser_windows_for_manual_intervention_or_try_again_later": "检查浏览器窗口进行手动干预或稍后再试"
},
"account_info": {
"subscription": "订阅",
"trial_remaining": "剩余试用",
"days": "天",
"subscription_not_found": "订阅信息未找到",
"email_not_found": "邮箱未找到",
"failed_to_get_account": "获取账户信息失败",
"config_not_found": "配置未找到。",
"failed_to_get_usage": "获取使用信息失败",
"failed_to_get_subscription": "获取订阅信息失败",
"failed_to_get_email": "获取邮箱地址失败",
"failed_to_get_token": "获取 token 失败",
"failed_to_get_account_info": "获取账户信息失败",
"title": "账户信息",
"email": "邮箱",
"token": "Token",
"usage": "使用量",
"subscription_type": "订阅类型",
"remaining_trial": "剩余试用",
"days_remaining": "剩余天数",
"premium": "高级",
"pro": "专业",
"pro_trial": "专业试用",
"team": "团队",
"enterprise": "企业",
"free": "免费",
"active": "活跃",
"inactive": "非活跃",
"premium_usage": "高级使用量",
"basic_usage": "基础使用量",
"usage_not_found": "使用量未找到",
"lifetime_access_enabled": "永久访问已启用"
},
"config": {
"config_not_available": "配置未找到。",
"configuration": "配置",
"enabled": "已启用",
"disabled": "已禁用",
"config_directory": "配置目录",
"neither_cursor_nor_cursor_directory_found": "未找到 Cursor 或 Cursor 目录",
"please_make_sure_cursor_is_installed_and_has_been_run_at_least_once": "请确保 Cursor 已安装并至少运行一次",
"storage_directory_not_found": "未找到存储目录",
"storage_file_found": "找到存储文件",
"file_size": "文件大小",
"file_permissions": "文件权限",
"file_owner": "文件所有者",
"file_group": "文件组",
"error_getting_file_stats": "获取文件统计信息时出错",
"permission_denied": "权限拒绝",
"try_running": "尝试运行: {command}",
"and": "和",
"storage_file_is_empty": "存储文件为空",
"the_file_might_be_corrupted_please_reinstall_cursor": "文件可能已损坏,请重新安装 Cursor",
"storage_file_not_found": "未找到存储文件",
"error_checking_linux_paths": "检查 Linux 路径时出错",
"config_option_added": "添加配置选项",
"config_updated": "配置更新",
"config_created": "配置已创建",
"config_setup_error": "配置设置错误",
"storage_file_is_valid_and_contains_data": "存储文件有效且包含数据",
"error_reading_storage_file": "读取存储文件时出错",
"also_checked": "也检查了 {path}"
},
"oauth": {
"authentication_button_not_found": "未找到认证按钮",
"authentication_failed": "认证失败: {error}",
"found_cookies": "找到 {count} 个 Cookie",
"token_extraction_error": "Token 提取错误: {error}",
"authentication_successful": "认证成功 - 邮箱: {email}",
"missing_authentication_data": "缺少认证数据: {data}",
"failed_to_delete_account": "删除账户失败: {error}",
"invalid_authentication_type": "无效的认证类型",
"auth_update_success": "认证更新成功",
"browser_closed": "浏览器已关闭",
"auth_update_failed": "认证更新失败",
"google_start": "Google 开始",
"github_start": "Github 开始",
"usage_count": "使用次数: {usage}",
"account_has_reached_maximum_usage": "账户已达到最大使用量, {deleting}",
"starting_new_authentication_process": "开始新的认证过程...",
"failed_to_delete_expired_account": "删除过期账户失败",
"could_not_check_usage_count": "无法检查使用次数: {error}",
"found_email": "找到邮箱: {email}",
"could_not_find_email": "未找到邮箱: {error}",
"could_not_find_usage_count": "未找到使用次数: {error}",
"already_on_settings_page": "已处于设置页面",
"failed_to_extract_auth_info": "提取认证信息失败: {error}",
"no_chrome_profiles_found": "未找到 Chrome 配置文件, 使用默认配置文件",
"found_default_chrome_profile": "找到默认 Chrome 配置文件",
"using_first_available_chrome_profile": "使用第一个可用的 Chrome 配置文件: {profile}",
"error_finding_chrome_profile": "找不到 Chrome 配置文件, 使用默认配置文件: {error}",
"initializing_browser_setup": "初始化浏览器设置...",
"detected_platform": "检测平台: {platform}",
"running_as_root_warning": "以 root 运行不推荐用于浏览器自动化",
"consider_running_without_sudo": "考虑不使用 sudo 运行脚本",
"no_compatible_browser_found": "未找到兼容的浏览器。请安装 Google Chrome 或 Chromium。",
"supported_browsers": "支持的浏览器: {platform}",
"using_browser_profile": "使用浏览器配置文件: {profile}",
"starting_browser": "正在启动浏览器: {path}",
"browser_setup_completed": "浏览器设置完成",
"browser_setup_failed": "浏览器设置失败: {error}",
"try_running_without_sudo_admin": "尝试不使用 sudo/管理员权限运行",
"redirecting_to_authenticator_cursor_sh": "重定向到 authenticator.cursor.sh...",
"starting_google_authentication": "开始 Google 认证...",
"starting_github_authentication": "开始 Github 认证...",
"waiting_for_authentication": "等待认证...",
"page_changed_checking_auth": "页面改变, 检查认证...",
"status_check_error": "状态检查错误: {error}",
"authentication_timeout": "认证超时",
"account_is_still_valid": "账户仍然有效 (使用量: {usage})",
"starting_re_authentication_process": "开始重新认证过程...",
"starting_new_google_authentication": "开始新的 Google 认证...",
"failed_to_delete_account_or_re_authenticate": "删除账户或重新认证失败: {error}",
"navigating_to_authentication_page": "正在导航到认证页面...",
"please_select_your_google_account_to_continue": "请选择您的 Google 账户以继续...",
"found_browser_data_directory": "找到浏览器数据目录: {path}",
"authentication_successful_getting_account_info": "认证成功, 获取账户信息...",
"warning_could_not_kill_existing_browser_processes": "警告: 无法杀死现有浏览器进程: {error}",
"browser_failed_to_start": "浏览器启动失败: {error}",
"browser_failed": "浏览器启动失败: {error}"
}
}

View File

@ -20,7 +20,9 @@
"admin_required": "運行可執行文件,需要管理員權限",
"admin_required_continue": "繼續使用當前版本...",
"coming_soon": "即將推出",
"fixed_soon": "即將修復"
"fixed_soon": "即將修復",
"contribute": "貢獻項目",
"config": "顯示配置"
},
"languages": {
"en": "英文",
@ -102,7 +104,12 @@
"check_version_failed": "檢查版本失敗: {error}",
"stack_trace": "堆疊跟踪",
"version_too_low": "Cursor版本太低: {version} < 0.45.0",
"no_write_permission": "沒有寫入權限: {path}"
"no_write_permission": "沒有寫入權限: {path}",
"path_not_found": "路徑未找到: {path}",
"modify_file_failed": "修改文件失敗: {error}",
"windows_machine_id_updated": "Windows機器ID更新成功",
"update_windows_machine_id_failed": "更新Windows機器ID失敗: {error}",
"update_windows_machine_guid_failed": "更新Windows機器GUID失敗: {error}"
},
"register": {
@ -247,7 +254,14 @@
"blocked_domains_loaded_timeout_error": "加載被屏蔽的域名超時錯誤: {error}",
"available_domains_loaded": "獲取到 {count} 個可用域名",
"domains_filtered": "過濾後剩餘 {count} 個可用域名",
"trying_to_create_email": "嘗試創建郵箱: {email}"
"trying_to_create_email": "嘗試創建郵箱: {email}",
"domain_blocked": "域名被屏蔽: {domain}",
"using_chrome_profile": "使用 Chrome 配置文件: {user_data_dir}",
"no_display_found": "未找到顯示器。確保 X 伺服器正在運行。",
"try_export_display": "嘗試: export DISPLAY=:0",
"extension_load_error": "加載插件失敗: {error}",
"make_sure_chrome_chromium_is_properly_installed": "確保 Chrome/Chromium 已正確安裝",
"try_install_chromium": "嘗試: sudo apt install chromium-browser"
},
"update": {
"title": "禁用 Cursor 自动更新",
@ -260,7 +274,14 @@
"removing_directory": "刪除目錄",
"directory_removed": "目錄已刪除",
"creating_block_file": "創建阻止文件",
"block_file_created": "阻止文件已創建"
"block_file_created": "阻止文件已創建",
"clearing_update_yml": "清空 update.yml 文件",
"update_yml_cleared": "update.yml 文件已清空",
"update_yml_not_found": "update.yml 文件未找到",
"clear_update_yml_failed": "清空 update.yml 文件失败: {error}",
"unsupported_os": "不支持的操作系统: {system}",
"remove_directory_failed": "刪除目錄失败: {error}",
"create_block_file_failed": "創建阻止文件失败: {error}"
},
"updater": {
"checking": "檢查更新...",
@ -273,7 +294,8 @@
"update_skipped": "跳過更新。",
"invalid_choice": "選擇無效。請輸入 'Y' 或 'n'.",
"development_version": "開發版本 {current} > {latest}",
"changelog_title": "更新日誌"
"changelog_title": "更新日誌",
"rate_limit_exceeded": "GitHub API 速率限制超過。跳過更新檢查。"
},
"totally_reset": {
"title": "完全重置 Cursor",
@ -413,5 +435,126 @@
"completed_successfully": "GitHub + Cursor 註冊成功",
"registration_encountered_issues": "GitHub + Cursor 註冊遇到問題",
"check_browser_windows_for_manual_intervention_or_try_again_later": "檢查瀏覽器視窗進行手動干預或稍後再試"
},
"account_info": {
"subscription": "訂閱",
"trial_remaining": "剩餘試用",
"days": "天",
"subscription_not_found": "訂閱信息未找到",
"email_not_found": "郵箱未找到",
"failed_to_get_account": "獲取帳戶信息失敗",
"config_not_found": "配置未找到。",
"failed_to_get_usage": "獲取使用信息失敗",
"failed_to_get_subscription": "獲取訂閱信息失敗",
"failed_to_get_email": "獲取郵箱地址失敗",
"failed_to_get_token": "獲取 token 失敗",
"failed_to_get_account_info": "獲取帳戶信息失敗",
"title": "帳戶信息",
"email": "郵箱",
"token": "Token",
"usage": "使用量",
"subscription_type": "訂閱類型",
"remaining_trial": "剩餘試用",
"days_remaining": "剩餘天數",
"premium": "高級",
"pro": "專業",
"pro_trial": "專業試用",
"team": "團隊",
"enterprise": "企業",
"free": "免費",
"active": "活躍",
"inactive": "非活躍",
"premium_usage": "高級使用量",
"basic_usage": "基礎使用量",
"usage_not_found": "使用量未找到",
"lifetime_access_enabled": "永久訪問已啟用"
},
"config": {
"config_not_available": "配置未找到。",
"configuration": "配置",
"enabled": "已啟用",
"disabled": "已禁用",
"config_directory": "配置目錄",
"neither_cursor_nor_cursor_directory_found": "未找到 Cursor 或 Cursor 目錄",
"please_make_sure_cursor_is_installed_and_has_been_run_at_least_once": "請確保 Cursor 已安裝並至少運行一次",
"storage_directory_not_found": "未找到儲存目錄",
"storage_file_found": "找到儲存文件",
"file_size": "文件大小",
"file_permissions": "文件權限",
"file_owner": "文件所有者",
"file_group": "文件組",
"error_getting_file_stats": "獲取文件統計信息時出錯",
"permission_denied": "權限拒絕",
"try_running": "嘗試運行: {command}",
"and": "和",
"storage_file_is_empty": "儲存文件為空",
"the_file_might_be_corrupted_please_reinstall_cursor": "文件可能已損壞,請重新安裝 Cursor",
"storage_file_not_found": "未找到儲存文件",
"error_checking_linux_paths": "檢查 Linux 路徑時出錯",
"config_option_added": "添加配置選項",
"config_updated": "配置更新",
"config_created": "配置已創建",
"config_setup_error": "配置設置錯誤",
"storage_file_is_valid_and_contains_data": "儲存文件有效且包含數據",
"error_reading_storage_file": "讀取儲存文件時出錯",
"also_checked": "也檢查了 {path}"
},
"oauth": {
"authentication_button_not_found": "未找到認證按鈕",
"authentication_failed": "認證失敗: {error}",
"found_cookies": "找到 {count} 個 Cookie",
"token_extraction_error": "Token 提取錯誤: {error}",
"authentication_successful": "認證成功 - 郵箱: {email}",
"missing_authentication_data": "缺少認證數據: {data}",
"failed_to_delete_account": "刪除帳戶失敗: {error}",
"invalid_authentication_type": "無效的認證類型",
"auth_update_success": "認證更新成功",
"browser_closed": "瀏覽器已關閉",
"auth_update_failed": "認證更新失敗",
"google_start": "Google 開始",
"github_start": "Github 開始",
"usage_count": "使用量: {usage}",
"account_has_reached_maximum_usage": "帳戶已達到最大使用量, {deleting}",
"starting_new_authentication_process": "開始新的認證過程...",
"failed_to_delete_expired_account": "刪除過期帳戶失敗",
"could_not_check_usage_count": "無法檢查使用量: {error}",
"found_email": "找到郵箱: {email}",
"could_not_find_email": "未找到郵箱: {error}",
"could_not_find_usage_count": "未找到使用量: {erro r}",
"already_on_settings_page": "已處於設置頁面",
"failed_to_extract_auth_info": "提取認證信息失敗: {error}",
"no_chrome_profiles_found": "未找到 Chrome 配置文件, 使用默認配置文件",
"found_default_chrome_profile": "找到默認 Chrome 配置文件",
"using_first_available_chrome_profile": "使用第一個可用的 Chrome 配置文件: {profile}",
"error_finding_chrome_profile": "找不到 Chrome 配置文件, 使用默認配置文件: {error}",
"initializing_browser_setup": "初始化瀏覽器設置...",
"detected_platform": "檢測平台: {platform}",
"running_as_root_warning": "以 root 運行不推薦用於瀏覽器自動化",
"consider_running_without_sudo": "考慮不使用 sudo 運行腳本",
"no_compatible_browser_found": "未找到兼容的瀏覽器。請安裝 Google Chrome 或 Chromium。",
"supported_browsers": "支持的瀏覽器: {platform}",
"using_browser_profile": "使用瀏覽器配置文件: {profile}",
"starting_browser": "正在啟動瀏覽器: {path}",
"browser_setup_completed": "瀏覽器設置完成成功",
"browser_setup_failed": "瀏覽器設置失敗: {error}",
"try_running_without_sudo_admin": "嘗試不使用 sudo/管理員權限運行",
"redirecting_to_authenticator_cursor_sh": "重定向到 authenticator.cursor.sh...",
"starting_github_authentication": "開始 Github 認證...",
"waiting_for_authentication": "等待認證...",
"page_changed_checking_auth": "頁面改變, 檢查認證...",
"status_check_error": "狀態檢查錯誤: {error}",
"authentication_timeout": "認證超時",
"account_is_still_valid": "帳戶仍然有效 (使用量: {usage})",
"starting_re_authentication_process": "開始重新認證過程...",
"starting_new_google_authentication": "開始新的 Google 認證...",
"failed_to_delete_account_or_re_authenticate": "刪除帳戶或重新認證失敗: {error}",
"navigating_to_authentication_page": "正在導航到認證頁面...",
"please_select_your_google_account_to_continue": "請選擇您的 Google 帳戶以繼續...",
"found_browser_data_directory": "找到瀏覽器數據目錄: {path}",
"authentication_successful_getting_account_info": "認證成功, 獲取帳戶信息...",
"warning_could_not_kill_existing_browser_processes": "警告: 無法殺死現有瀏覽器進程: {error}",
"browser_failed_to_start": "瀏覽器啟動失敗: {error}",
"browser_failed": "瀏覽器啟動失敗: {error}"
}
}

10
logo.py
View File

@ -20,7 +20,7 @@ init()
# get terminal width
def get_terminal_width():
try:
columns, _ = shutil.get_terminal_size()
columns, _ = shutil.get_terminal_size()/2
return columns
except:
return 80 # default width
@ -34,7 +34,7 @@ def center_multiline_text(text, handle_chinese=False):
for line in lines:
# calculate actual display width (remove ANSI color codes)
clean_line = line
for color in [Fore.CYAN, Fore.YELLOW, Fore.GREEN, Fore.RED, Style.RESET_ALL]:
for color in [Fore.CYAN, Fore.YELLOW, Fore.GREEN, Fore.RED, Fore.BLUE, Style.RESET_ALL]:
clean_line = clean_line.replace(color, '')
# remove all ANSI escape sequences to get the actual length
@ -83,7 +83,7 @@ muhammedfurkan plamkatawe
"""
OTHER_INFO_TEXT = f"""{Fore.YELLOW}
Github: https://github.com/yeongpin/cursor-free-vip{Fore.RED}
Press 7 to change language | 按下 7 键切换语言{Style.RESET_ALL}"""
Press 8 to change language | 按下 8 键切换语言{Style.RESET_ALL}"""
# center display LOGO and DESCRIPTION
CURSOR_LOGO = center_multiline_text(LOGO_TEXT, handle_chinese=False)
@ -94,8 +94,8 @@ CURSOR_OTHER_INFO = center_multiline_text(OTHER_INFO_TEXT, handle_chinese=True)
def print_logo():
print(CURSOR_LOGO)
print(CURSOR_DESCRIPTION)
print(CURSOR_CONTRIBUTORS)
# print(CURSOR_CONTRIBUTORS)
print(CURSOR_OTHER_INFO)
if __name__ == "__main__":
print_logo()
print_logo()

156
main.py
View File

@ -9,12 +9,14 @@ import locale
import platform
import requests
import subprocess
from config import get_config
from config import get_config
import shutil
import re
# Only import windll on Windows systems
if platform.system() == 'Windows':
import ctypes
# 只在 Windows 上导入 windll
# Only import windll on Windows systems
from ctypes import windll
# Initialize colorama
@ -32,7 +34,13 @@ EMOJI = {
"ARROW": "",
"LANG": "🌐",
"UPDATE": "🔄",
"ADMIN": "🔐"
"ADMIN": "🔐",
"AIRDROP": "💰",
"ROCKET": "🚀",
"STAR": "",
"SUN": "🌟",
"CONTRIBUTE": "🤝",
"SETTINGS": "⚙️"
}
# Function to check if running as frozen executable
@ -242,22 +250,111 @@ translator = Translator()
def print_menu():
"""Print menu options"""
try:
config = get_config()
if config.getboolean('Utils', 'enabled_account_info'):
import cursor_acc_info
cursor_acc_info.display_account_info(translator)
except Exception as e:
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('menu.account_info_error', error=str(e))}{Style.RESET_ALL}")
print(f"\n{Fore.CYAN}{EMOJI['MENU']} {translator.get('menu.title')}:{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{'' * 40}{Style.RESET_ALL}")
print(f"{Fore.GREEN}0{Style.RESET_ALL}. {EMOJI['ERROR']} {translator.get('menu.exit')}")
print(f"{Fore.GREEN}1{Style.RESET_ALL}. {EMOJI['RESET']} {translator.get('menu.reset')}")
print(f"{Fore.GREEN}2{Style.RESET_ALL}. {EMOJI['SUCCESS']} {translator.get('menu.register')} ({Fore.RED}{translator.get('menu.outdate')}{Style.RESET_ALL})")
print(f"{Fore.GREEN}3{Style.RESET_ALL}. 🌟 {translator.get('menu.register_google')}")
print(f"{Fore.YELLOW} ┗━━ 🔥 {translator.get('menu.lifetime_access_enabled')} 🔥{Style.RESET_ALL}")
print(f"{Fore.GREEN}4{Style.RESET_ALL}. ⭐ {translator.get('menu.register_github')}")
print(f"{Fore.YELLOW} ┗━━ 🚀 {translator.get('menu.lifetime_access_enabled')} 🚀{Style.RESET_ALL}")
print(f"{Fore.GREEN}5{Style.RESET_ALL}. {EMOJI['SUCCESS']} {translator.get('menu.register_manual')}")
print(f"{Fore.GREEN}6{Style.RESET_ALL}. {EMOJI['RESET']} {translator.get('menu.temp_github_register')}")
print(f"{Fore.GREEN}7{Style.RESET_ALL}. {EMOJI['ERROR']} {translator.get('menu.quit')}")
print(f"{Fore.GREEN}8{Style.RESET_ALL}. {EMOJI['LANG']} {translator.get('menu.select_language')}")
print(f"{Fore.GREEN}9{Style.RESET_ALL}. {EMOJI['UPDATE']} {translator.get('menu.disable_auto_update')}")
print(f"{Fore.GREEN}10{Style.RESET_ALL}. {EMOJI['RESET']} {translator.get('menu.totally_reset')}")
print(f"{Fore.YELLOW}{'' * 40}{Style.RESET_ALL}")
if translator.current_language == 'zh_cn' or translator.current_language == 'zh_tw':
print(f"{Fore.YELLOW}{'' * 70}{Style.RESET_ALL}")
else:
print(f"{Fore.YELLOW}{'' * 110}{Style.RESET_ALL}")
# Get terminal width
try:
terminal_width = shutil.get_terminal_size().columns
except:
terminal_width = 80 # Default width
# Define all menu items
menu_items = {
0: f"{Fore.GREEN}0{Style.RESET_ALL}. {EMOJI['ERROR']} {translator.get('menu.exit')}",
1: f"{Fore.GREEN}1{Style.RESET_ALL}. {EMOJI['RESET']} {translator.get('menu.reset')}",
2: f"{Fore.GREEN}2{Style.RESET_ALL}. {EMOJI['SUCCESS']} {translator.get('menu.register')} ({Fore.RED}{translator.get('menu.outdate')}{Style.RESET_ALL})",
3: f"{Fore.GREEN}3{Style.RESET_ALL}. {EMOJI['SUN']} {translator.get('menu.register_google')} {EMOJI['ROCKET']} ({Fore.YELLOW}{translator.get('menu.lifetime_access_enabled')}{Style.RESET_ALL})",
4: f"{Fore.GREEN}4{Style.RESET_ALL}. {EMOJI['STAR']} {translator.get('menu.register_github')} {EMOJI['ROCKET']} ({Fore.YELLOW}{translator.get('menu.lifetime_access_enabled')}{Style.RESET_ALL})",
5: f"{Fore.GREEN}5{Style.RESET_ALL}. {EMOJI['SUCCESS']} {translator.get('menu.register_manual')}",
6: f"{Fore.GREEN}6{Style.RESET_ALL}. {EMOJI['RESET']} {translator.get('menu.temp_github_register')}",
7: f"{Fore.GREEN}7{Style.RESET_ALL}. {EMOJI['ERROR']} {translator.get('menu.quit')}",
8: f"{Fore.GREEN}8{Style.RESET_ALL}. {EMOJI['LANG']} {translator.get('menu.select_language')}",
9: f"{Fore.GREEN}9{Style.RESET_ALL}. {EMOJI['UPDATE']} {translator.get('menu.disable_auto_update')}",
10: f"{Fore.GREEN}10{Style.RESET_ALL}. {EMOJI['RESET']} {translator.get('menu.totally_reset')}",
11: f"{Fore.GREEN}11{Style.RESET_ALL}. {EMOJI['CONTRIBUTE']} {translator.get('menu.contribute')}",
12: f"{Fore.GREEN}12{Style.RESET_ALL}. {EMOJI['SETTINGS']} {translator.get('menu.config')}"
}
# Automatically calculate the number of menu items in the left and right columns
total_items = len(menu_items)
left_column_count = (total_items + 1) // 2 # The number of options displayed on the left (rounded up)
# Build left and right columns of menus
sorted_indices = sorted(menu_items.keys())
left_menu = [menu_items[i] for i in sorted_indices[:left_column_count]]
right_menu = [menu_items[i] for i in sorted_indices[left_column_count:]]
# Calculate the maximum display width of left menu items
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_menu:
width = get_display_width(item)
max_left_width = max(max_left_width, width)
# Set the starting position of right menu
fixed_spacing = 4 # Fixed spacing
right_start = max_left_width + fixed_spacing
# Calculate the number of spaces needed for right menu items
spaces_list = []
for i in range(len(left_menu)):
if i < len(left_menu):
left_item = left_menu[i]
left_width = get_display_width(left_item)
spaces = right_start - left_width
spaces_list.append(spaces)
# Print menu items
max_rows = max(len(left_menu), len(right_menu))
for i in range(max_rows):
# Print left menu items
if i < len(left_menu):
left_item = left_menu[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 menu items
if i < len(right_menu):
print(' ' * spaces + right_menu[i])
else:
print() # Change line
if translator.current_language == 'zh_cn' or translator.current_language == 'zh_tw':
print(f"{Fore.YELLOW}{'' * 70}{Style.RESET_ALL}")
else:
print(f"{Fore.YELLOW}{'' * 110}{Style.RESET_ALL}")
def select_language():
"""Language selection menu"""
@ -297,6 +394,11 @@ def check_latest_version():
timeout=10
)
# Check if rate limit exceeded
if response.status_code == 403 and "rate limit exceeded" in response.text.lower():
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('updater.rate_limit_exceeded', fallback='GitHub API rate limit exceeded. Skipping update check.')}{Style.RESET_ALL}")
return
# Check if response is successful
if response.status_code != 200:
raise Exception(f"GitHub API returned status code {response.status_code}")
@ -448,12 +550,14 @@ def main():
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('menu.config_init_failed')}{Style.RESET_ALL}")
return
check_latest_version() # Add version check before showing menu
if config.getboolean('Utils', 'enabled_update_check'):
check_latest_version() # Add version check before showing menu
print_menu()
while True:
try:
choice = input(f"\n{EMOJI['ARROW']} {Fore.CYAN}{translator.get('menu.input_choice', choices='0-10')}: {Style.RESET_ALL}")
choice_num = 12
choice = input(f"\n{EMOJI['ARROW']} {Fore.CYAN}{translator.get('menu.input_choice', choices=f'0-{choice_num}')}: {Style.RESET_ALL}")
if choice == "0":
print(f"\n{Fore.YELLOW}{EMOJI['INFO']} {translator.get('menu.exit')}...{Style.RESET_ALL}")
@ -498,8 +602,16 @@ def main():
print_menu()
elif choice == "10":
import totally_reset_cursor
# totally_reset_cursor.main(translator)
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('menu.fixed_soon')}{Style.RESET_ALL}")
totally_reset_cursor.run(translator)
# print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('menu.fixed_soon')}{Style.RESET_ALL}")
print_menu()
elif choice == "11":
import logo
print(logo.CURSOR_CONTRIBUTORS)
print_menu()
elif choice == "12":
from config import print_config
print_config(get_config(), translator)
print_menu()
else:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('menu.invalid_choice')}{Style.RESET_ALL}")

View File

@ -203,9 +203,10 @@ def setup_driver(translator=None):
# Use incognito mode
co.set_argument("--incognito")
# Set random port
co.set_argument("--no-sandbox")
if sys.platform == "linux":
# Set random port
co.set_argument("--no-sandbox")
# Set random port
co.auto_port()

View File

@ -106,7 +106,31 @@ class NewTempEmail:
# 创建浏览器选项
co = ChromiumOptions()
co.set_argument("--headless=new")
# Only use headless for non-OAuth operations
if not hasattr(self, 'auth_type') or self.auth_type != 'oauth':
co.set_argument("--headless=new")
if sys.platform == "linux":
# Check if DISPLAY is set when not in headless mode
if not co.arguments.get("--headless=new") and not os.environ.get('DISPLAY'):
print(f"{Fore.RED}{self.translator.get('email.no_display_found') if self.translator else 'No display found. Make sure X server is running.'}{Style.RESET_ALL}")
print(f"{Fore.YELLOW} {self.translator.get('email.try_export_display') if self.translator else 'Try: export DISPLAY=:0'}{Style.RESET_ALL}")
return False
co.set_argument("--no-sandbox")
co.set_argument("--disable-dev-shm-usage")
co.set_argument("--disable-gpu")
# If running as root, try to use actual user's Chrome profile
if os.geteuid() == 0:
sudo_user = os.environ.get('SUDO_USER')
if sudo_user:
actual_home = f"/home/{sudo_user}"
user_data_dir = os.path.join(actual_home, ".config", "google-chrome")
if os.path.exists(user_data_dir):
print(f"{Fore.CYAN} {self.translator.get('email.using_chrome_profile', user_data_dir=user_data_dir) if self.translator else f'Using Chrome profile from: {user_data_dir}'}{Style.RESET_ALL}")
co.set_argument(f"--user-data-dir={user_data_dir}")
co.auto_port() # 自动设置端口
@ -128,6 +152,10 @@ class NewTempEmail:
print(f"{Fore.RED}{self.translator.get('email.browser_start_error')}: {str(e)}{Style.RESET_ALL}")
else:
print(f"{Fore.RED}❌ 启动浏览器失败: {str(e)}{Style.RESET_ALL}")
if sys.platform == "linux":
print(f"{Fore.YELLOW} {self.translator.get('email.make_sure_chrome_chromium_is_properly_installed') if self.translator else 'Make sure Chrome/Chromium is properly installed'}{Style.RESET_ALL}")
print(f"{Fore.YELLOW} {self.translator.get('email.try_install_chromium') if self.translator else 'Try: sudo apt install chromium-browser'}{Style.RESET_ALL}")
return False
def create_email(self):

View File

@ -21,13 +21,15 @@ EMOJI = {
'SUCCESS': '',
'ERROR': '',
'WAIT': '',
'INFO': ''
'INFO': '',
'WARNING': '⚠️'
}
class OAuthHandler:
def __init__(self, translator=None):
def __init__(self, translator=None, auth_type=None):
self.translator = translator
self.config = get_config(translator)
self.auth_type = auth_type # make sure the auth_type is not None
os.environ['BROWSER_HEADLESS'] = 'False'
self.browser = None
@ -41,12 +43,12 @@ class OAuthHandler:
profiles.append(item)
if not profiles:
print(f"{Fore.YELLOW}{EMOJI['INFO']} No Chrome profiles found, using Default{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.no_chrome_profiles_found') if self.translator else 'No Chrome profiles found, using Default'}{Style.RESET_ALL}")
return 'Default'
# First check if Default profile exists
if 'Default' in profiles:
print(f"{Fore.CYAN}{EMOJI['INFO']} Found Default Chrome profile{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.found_default_chrome_profile') if self.translator else 'Found Default Chrome profile'}{Style.RESET_ALL}")
return 'Default'
# If no Default profile, check Local State for last used profile
@ -68,21 +70,35 @@ class OAuthHandler:
return profile
# If no profile found in Local State, use the first available profile
print(f"{Fore.CYAN}{EMOJI['INFO']} Using first available Chrome profile: {profiles[0]}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.using_first_available_chrome_profile', profile=profiles[0]) if self.translator else f'Using first available Chrome profile: {profiles[0]}'}{Style.RESET_ALL}")
return profiles[0]
except Exception as e:
print(f"{Fore.YELLOW}{EMOJI['INFO']} Error finding Chrome profile, using Default: {str(e)}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.error_finding_chrome_profile', error=str(e)) if self.translator else f'Error finding Chrome profile, using Default: {str(e)}'}{Style.RESET_ALL}")
return 'Default'
def setup_browser(self):
"""Setup browser for OAuth flow using active profile"""
try:
print(f"{Fore.CYAN}{EMOJI['INFO']} Initializing browser setup...{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.initializing_browser_setup') if self.translator else 'Initializing browser setup...'}{Style.RESET_ALL}")
# Platform-specific initialization
platform_name = platform.system().lower()
print(f"{Fore.CYAN}{EMOJI['INFO']} Detected platform: {platform_name}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.detected_platform', platform=platform_name) if self.translator else f'Detected platform: {platform_name}'}{Style.RESET_ALL}")
# Linux-specific checks
if platform_name == 'linux':
# Check if DISPLAY is set
display = os.environ.get('DISPLAY')
if not display:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.no_display_found') if self.translator else 'No display found. Make sure X server is running.'}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.try_export_display') if self.translator else 'Try: export DISPLAY=:0'}{Style.RESET_ALL}")
return False
# Check if running as root
if os.geteuid() == 0:
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {self.translator.get('oauth.running_as_root_warning') if self.translator else 'Running as root is not recommended for browser automation'}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.consider_running_without_sudo') if self.translator else 'Consider running the script without sudo'}{Style.RESET_ALL}")
# Kill existing browser processes
self._kill_browser_processes()
@ -92,34 +108,64 @@ class OAuthHandler:
chrome_path = self._get_browser_path()
if not chrome_path:
raise Exception(f"No compatible browser found. Please install Google Chrome or Chromium.\nSupported browsers for {platform_name}:\n" +
raise Exception(f"{self.translator.get('oauth.no_compatible_browser_found') if self.translator else 'No compatible browser found. Please install Google Chrome or Chromium.'}\n{self.translator.get('oauth.supported_browsers', platform=platform_name)}\n" +
"- Windows: Google Chrome, Chromium\n" +
"- macOS: Google Chrome, Chromium\n" +
"- Linux: Google Chrome, Chromium, chromium-browser")
# Get active profile
active_profile = self._get_active_profile(user_data_dir)
print(f"{Fore.CYAN}{EMOJI['INFO']} Using browser profile: {active_profile}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.using_browser_profile', profile=active_profile) if self.translator else f'Using browser profile: {active_profile}'}{Style.RESET_ALL}")
# Configure browser options
co = self._configure_browser_options(chrome_path, user_data_dir, active_profile)
co = ChromiumOptions()
print(f"{Fore.CYAN}{EMOJI['INFO']} Starting browser at: {chrome_path}{Style.RESET_ALL}")
# Never use headless mode for OAuth flows
co.headless(False)
# Platform-specific options
if os.name == 'linux':
co.set_argument('--no-sandbox')
co.set_argument('--disable-dev-shm-usage')
co.set_argument('--disable-gpu')
# If running as root, try to use actual user's Chrome profile
if os.geteuid() == 0:
sudo_user = os.environ.get('SUDO_USER')
if sudo_user:
actual_home = f"/home/{sudo_user}"
user_data_dir = os.path.join(actual_home, ".config", "google-chrome")
if os.path.exists(user_data_dir):
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.using_chrome_profile_from', user_data_dir=user_data_dir) if self.translator else f'Using Chrome profile from: {user_data_dir}'}{Style.RESET_ALL}")
co.set_argument(f"--user-data-dir={user_data_dir}")
# Set paths and profile
co.set_paths(browser_path=chrome_path, user_data_path=user_data_dir)
co.set_argument(f'--profile-directory={active_profile}')
# Basic options
co.set_argument('--no-first-run')
co.set_argument('--no-default-browser-check')
# co.auto_port()
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.starting_browser', path=chrome_path) if self.translator else f'Starting browser at: {chrome_path}'}{Style.RESET_ALL}")
self.browser = ChromiumPage(co)
# Verify browser launched successfully
if not self.browser:
raise Exception("Failed to initialize browser instance")
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Browser setup completed successfully{Style.RESET_ALL}")
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('oauth.browser_setup_completed') if self.translator else 'Browser setup completed successfully'}{Style.RESET_ALL}")
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} Browser setup failed: {str(e)}{Style.RESET_ALL}")
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.browser_setup_failed', error=str(e)) if self.translator else f'Browser setup failed: {str(e)}'}{Style.RESET_ALL}")
if "DevToolsActivePort file doesn't exist" in str(e):
print(f"{Fore.YELLOW}{EMOJI['INFO']} Try running with administrator/root privileges{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.try_running_without_sudo_admin') if self.translator else 'Try running without sudo/administrator privileges'}{Style.RESET_ALL}")
elif "Chrome failed to start" in str(e):
print(f"{Fore.YELLOW}{EMOJI['INFO']} Make sure Chrome/Chromium is properly installed{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.make_sure_chrome_chromium_is_properly_installed') if self.translator else 'Make sure Chrome/Chromium is properly installed'}{Style.RESET_ALL}")
if platform_name == 'linux':
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.try_install_chromium') if self.translator else 'Try: sudo apt install chromium-browser'}{Style.RESET_ALL}")
return False
def _kill_browser_processes(self):
@ -136,7 +182,7 @@ class OAuthHandler:
time.sleep(1) # Wait for processes to close
except Exception as e:
print(f"{Fore.YELLOW}{EMOJI['INFO']} Warning: Could not kill existing browser processes: {e}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.warning_could_not_kill_existing_browser_processes', error=str(e)) if self.translator else f'Warning: Could not kill existing browser processes: {e}'}{Style.RESET_ALL}")
def _get_user_data_directory(self):
"""Get the appropriate user data directory based on platform"""
@ -162,17 +208,17 @@ class OAuthHandler:
# Try each possible path
for path in possible_paths:
if os.path.exists(path):
print(f"{Fore.CYAN}{EMOJI['INFO']} Found browser data directory: {path}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.found_browser_data_directory', path=path) if self.translator else f'Found browser data directory: {path}'}{Style.RESET_ALL}")
return path
# Create temporary profile if no existing profile found
temp_profile = os.path.join(os.path.expanduser('~'), '.cursor_temp_profile')
print(f"{Fore.YELLOW}{EMOJI['INFO']} Creating temporary profile at: {temp_profile}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.creating_temporary_profile', path=temp_profile) if self.translator else f'Creating temporary profile at: {temp_profile}'}{Style.RESET_ALL}")
os.makedirs(temp_profile, exist_ok=True)
return temp_profile
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} Error getting user data directory: {e}{Style.RESET_ALL}")
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.error_getting_user_data_directory', error=str(e)) if self.translator else f'Error getting user data directory: {e}'}{Style.RESET_ALL}")
raise
def _get_browser_path(self):
@ -183,7 +229,7 @@ class OAuthHandler:
if chrome_path and os.path.exists(chrome_path):
return chrome_path
print(f"{Fore.YELLOW}{EMOJI['INFO']} Searching for alternative browser installations...{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.searching_for_alternative_browser_installations') if self.translator else 'Searching for alternative browser installations...'}{Style.RESET_ALL}")
# Platform-specific paths
if os.name == 'nt': # Windows
@ -215,13 +261,13 @@ class OAuthHandler:
for path in alt_paths:
expanded_path = os.path.expanduser(path)
if os.path.exists(expanded_path):
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Found browser at: {expanded_path}{Style.RESET_ALL}")
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('oauth.found_browser_at', path=expanded_path) if self.translator else f'Found browser at: {expanded_path}'}{Style.RESET_ALL}")
return expanded_path
return None
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} Error finding browser path: {e}{Style.RESET_ALL}")
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.error_finding_browser_path', error=str(e)) if self.translator else f'Error finding browser path: {e}'}{Style.RESET_ALL}")
return None
def _configure_browser_options(self, chrome_path, user_data_dir, active_profile):
@ -250,22 +296,22 @@ class OAuthHandler:
return co
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} Error configuring browser options: {e}{Style.RESET_ALL}")
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.error_configuring_browser_options', error=str(e)) if self.translator else f'Error configuring browser options: {e}'}{Style.RESET_ALL}")
raise
def handle_google_auth(self):
"""Handle Google OAuth authentication"""
try:
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.google_start')}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.google_start') if self.translator else 'Starting Google OAuth authentication...'}{Style.RESET_ALL}")
# Setup browser
if not self.setup_browser():
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.browser_failed')}{Style.RESET_ALL}")
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.browser_failed') if self.translator else 'Browser failed to initialize'}{Style.RESET_ALL}")
return False, None
# Navigate to auth URL
try:
print(f"{Fore.CYAN}{EMOJI['INFO']} Navigating to authentication page...{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.navigating_to_authentication_page') if self.translator else 'Navigating to authentication page...'}{Style.RESET_ALL}")
self.browser.get("https://authenticator.cursor.sh/sign-up")
time.sleep(get_random_wait_time(self.config, 'page_load_wait'))
@ -289,16 +335,17 @@ class OAuthHandler:
raise Exception("Could not find Google authentication button")
# Click the button and wait for page load
print(f"{Fore.CYAN}{EMOJI['INFO']} Starting Google authentication...{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.starting_google_authentication') if self.translator else 'Starting Google authentication...'}{Style.RESET_ALL}")
auth_btn.click()
time.sleep(get_random_wait_time(self.config, 'page_load_wait'))
# Check if we're on account selection page
if "accounts.google.com" in self.browser.url:
print(f"{Fore.CYAN}{EMOJI['INFO']} Please select your Google account to continue...{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.please_select_your_google_account_to_continue') if self.translator else 'Please select your Google account to continue...'}{Style.RESET_ALL}")
alert_message = self.translator.get('oauth.please_select_your_google_account_to_continue') if self.translator else 'Please select your Google account to continue with Cursor authentication'
try:
self.browser.run_js("""
alert('Please select your Google account to continue with Cursor authentication');
self.browser.run_js(f"""
alert('{alert_message}');
""")
except:
pass # Alert is optional
@ -306,14 +353,14 @@ class OAuthHandler:
# Wait for authentication to complete
auth_info = self._wait_for_auth()
if not auth_info:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.timeout')}{Style.RESET_ALL}")
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.timeout') if self.translator else 'Timeout'}{Style.RESET_ALL}")
return False, None
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('oauth.success')}{Style.RESET_ALL}")
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('oauth.success') if self.translator else 'Success'}{Style.RESET_ALL}")
return True, auth_info
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} Authentication error: {str(e)}{Style.RESET_ALL}")
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.authentication_error', error=str(e)) if self.translator else f'Authentication error: {str(e)}'}{Style.RESET_ALL}")
return False, None
finally:
try:
@ -333,7 +380,7 @@ class OAuthHandler:
start_time = time.time()
check_interval = 2 # Check every 2 seconds
print(f"{Fore.CYAN}{EMOJI['WAIT']} Waiting for authentication (timeout: 5 minutes)...{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['WAIT']} {self.translator.get('oauth.waiting_for_authentication', timeout='5 minutes') if self.translator else 'Waiting for authentication (timeout: 5 minutes)'}{Style.RESET_ALL}")
while time.time() - start_time < max_wait:
try:
@ -352,7 +399,7 @@ class OAuthHandler:
if token:
# Get email from settings page
print(f"{Fore.CYAN}{EMOJI['INFO']} Authentication successful, getting account info...{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.authentication_successful_getting_account_info') if self.translator else 'Authentication successful, getting account info...'}{Style.RESET_ALL}")
self.browser.get("https://www.cursor.com/settings")
time.sleep(3)
@ -361,7 +408,7 @@ class OAuthHandler:
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']} Found email: {email}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.found_email', email=email) if self.translator else f'Found email: {email}'}{Style.RESET_ALL}")
except:
email = "user@cursor.sh" # Fallback email
@ -370,42 +417,42 @@ class OAuthHandler:
usage_element = self.browser.ele("css:div[class='flex flex-col gap-4 lg:flex-row'] div:nth-child(1) div:nth-child(1) span:nth-child(2)")
if usage_element:
usage_text = usage_element.text
print(f"{Fore.CYAN}{EMOJI['INFO']} Usage count: {usage_text}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.usage_count', usage=usage_text) if self.translator else f'Usage count: {usage_text}'}{Style.RESET_ALL}")
# Check if account is expired
if usage_text.strip() == "150 / 150":
print(f"{Fore.YELLOW}{EMOJI['INFO']} Account has reached maximum usage, creating new account...{Style.RESET_ALL}")
# Check if account is expired (both 150/150 and 50/50 cases)
if usage_text.strip() == "150 / 150" or usage_text.strip() == "50 / 50":
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.account_has_reached_maximum_usage', creating_new_account='creating new account') if self.translator else 'Account has reached maximum usage, creating new account...'}{Style.RESET_ALL}")
# Delete current account
if self._delete_current_account():
# Start new authentication based on auth type
print(f"{Fore.CYAN}{EMOJI['INFO']} Starting new authentication process...{Style.RESET_ALL}")
if auth_type == "google":
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.starting_new_authentication_process') if self.translator else 'Starting new authentication process...'}{Style.RESET_ALL}")
if self.auth_type == "google":
return self.handle_google_auth()
else: # github
return self.handle_github_auth()
else:
print(f"{Fore.RED}{EMOJI['ERROR']} Failed to delete expired account{Style.RESET_ALL}")
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.failed_to_delete_expired_account') if self.translator else 'Failed to delete expired account'}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.YELLOW}{EMOJI['INFO']} Could not check usage count: {str(e)}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.could_not_check_usage_count', error=str(e)) if self.translator else f'Could not check usage count: {str(e)}'}{Style.RESET_ALL}")
return {"email": email, "token": token}
# Also check URL as backup
if "cursor.com/settings" in self.browser.url:
print(f"{Fore.CYAN}{EMOJI['INFO']} Detected successful login{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.detected_successful_login') if self.translator else 'Detected successful login'}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.YELLOW}{EMOJI['INFO']} Waiting for authentication... ({str(e)}){Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.waiting_for_authentication', error=str(e)) if self.translator else f'Waiting for authentication... ({str(e)})'}{Style.RESET_ALL}")
time.sleep(check_interval)
print(f"{Fore.RED}{EMOJI['ERROR']} Authentication timeout{Style.RESET_ALL}")
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.authentication_timeout') if self.translator else 'Authentication timeout'}{Style.RESET_ALL}")
return None
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} Error while waiting for authentication: {str(e)}{Style.RESET_ALL}")
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.error_waiting_for_authentication', error=str(e)) if self.translator else f'Error while waiting for authentication: {str(e)}'}{Style.RESET_ALL}")
return None
def handle_github_auth(self):
@ -420,7 +467,7 @@ class OAuthHandler:
# Navigate to auth URL
try:
print(f"{Fore.CYAN}{EMOJI['INFO']} Navigating to authentication page...{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.navigating_to_authentication_page') if self.translator else 'Navigating to authentication page...'}{Style.RESET_ALL}")
self.browser.get("https://authenticator.cursor.sh/sign-up")
time.sleep(get_random_wait_time(self.config, 'page_load_wait'))
@ -444,21 +491,21 @@ class OAuthHandler:
raise Exception("Could not find GitHub authentication button")
# Click the button and wait for page load
print(f"{Fore.CYAN}{EMOJI['INFO']} Starting GitHub authentication...{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.starting_github_authentication') if self.translator else 'Starting GitHub authentication...'}{Style.RESET_ALL}")
auth_btn.click()
time.sleep(get_random_wait_time(self.config, 'page_load_wait'))
# Wait for authentication to complete
auth_info = self._wait_for_auth()
if not auth_info:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.timeout')}{Style.RESET_ALL}")
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.timeout') if self.translator else 'Timeout'}{Style.RESET_ALL}")
return False, None
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('oauth.success')}{Style.RESET_ALL}")
return True, auth_info
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} Authentication error: {str(e)}{Style.RESET_ALL}")
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.authentication_error', error=str(e)) if self.translator else f'Authentication error: {str(e)}'}{Style.RESET_ALL}")
return False, None
finally:
try:
@ -520,23 +567,23 @@ class OAuthHandler:
# Check if we're on account selection page
if auth_type == "google" and "accounts.google.com" in self.browser.url:
alert_js = """
alert('Please select your Google account manually to continue with Cursor authentication');
"""
alert_message = self.translator.get('oauth.please_select_your_google_account_to_continue') if self.translator else 'Please select your Google account to continue with Cursor authentication'
try:
self.browser.run_js(alert_js)
self.browser.run_js(f"""
alert('{alert_message}');
""")
except Exception as e:
print(f"{Fore.YELLOW}{EMOJI['INFO']} Alert display failed: {str(e)}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} Please select your Google account manually to continue with Cursor authentication...{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.alert_display_failed', error=str(e)) if self.translator else f'Alert display failed: {str(e)}'}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.please_select_your_google_account_manually_to_continue_with_cursor_authentication') if self.translator else 'Please select your Google account manually to continue with Cursor authentication...'}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} Waiting for authentication to complete...{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.waiting_for_authentication_to_complete') if self.translator else 'Waiting for authentication to complete...'}{Style.RESET_ALL}")
# Wait for authentication to complete
max_wait = 300 # 5 minutes
start_time = time.time()
last_url = self.browser.url
print(f"{Fore.CYAN}{EMOJI['WAIT']} Checking authentication status...{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['WAIT']} {self.translator.get('oauth.checking_authentication_status') if self.translator else 'Checking authentication status...'}{Style.RESET_ALL}")
while time.time() - start_time < max_wait:
try:
@ -552,9 +599,9 @@ class OAuthHandler:
token = value.split("%3A%3A")[-1]
if token:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Authentication successful!{Style.RESET_ALL}")
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('oauth.authentication_successful') if self.translator else 'Authentication successful!'}{Style.RESET_ALL}")
# Navigate to settings page
print(f"{Fore.CYAN}{EMOJI['INFO']} Navigating to settings page...{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.navigating_to_settings_page') if self.translator else 'Navigating to settings page...'}{Style.RESET_ALL}")
self.browser.get("https://www.cursor.com/settings")
time.sleep(3) # Wait for settings page to load
@ -563,9 +610,9 @@ class OAuthHandler:
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:
actual_email = email_element.text
print(f"{Fore.CYAN}{EMOJI['INFO']} Found email: {actual_email}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.found_email', email=actual_email) if self.translator else f'Found email: {actual_email}'}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.YELLOW}{EMOJI['INFO']} Could not find email: {str(e)}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.could_not_find_email', error=str(e)) if self.translator else f'Could not find email: {str(e)}'}{Style.RESET_ALL}")
actual_email = "user@cursor.sh"
# Check usage count
@ -573,11 +620,11 @@ class OAuthHandler:
usage_element = self.browser.ele("css:div[class='flex flex-col gap-4 lg:flex-row'] div:nth-child(1) div:nth-child(1) span:nth-child(2)")
if usage_element:
usage_text = usage_element.text
print(f"{Fore.CYAN}{EMOJI['INFO']} Usage count: {usage_text}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.usage_count', usage=usage_text) if self.translator else f'Usage count: {usage_text}'}{Style.RESET_ALL}")
# Check if account is expired
if usage_text.strip() == "150 / 150":
print(f"{Fore.YELLOW}{EMOJI['INFO']} Account has reached maximum usage, deleting...{Style.RESET_ALL}")
# Check if account is expired (both 150/150 and 50/50 cases)
if usage_text.strip() == "150 / 150" or usage_text.strip() == "50 / 50":
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.account_has_reached_maximum_usage', deleting='deleting') if self.translator else 'Account has reached maximum usage, deleting...'}{Style.RESET_ALL}")
delete_js = """
function deleteAccount() {
@ -609,23 +656,23 @@ class OAuthHandler:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Delete account result: {result}{Style.RESET_ALL}")
# Navigate back to auth page and repeat authentication
print(f"{Fore.CYAN}{EMOJI['INFO']} Starting re-authentication process...{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} Redirecting to authenticator.cursor.sh...{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.starting_re_authentication_process') if self.translator else 'Starting re-authentication process...'}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.redirecting_to_authenticator_cursor_sh') if self.translator else 'Redirecting to authenticator.cursor.sh...'}{Style.RESET_ALL}")
# Explicitly navigate to the authentication page
self.browser.get("https://authenticator.cursor.sh/sign-up")
time.sleep(get_random_wait_time(self.config, 'page_load_wait'))
# Call handle_google_auth again to repeat the entire process
print(f"{Fore.CYAN}{EMOJI['INFO']} Starting new Google authentication...{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.starting_new_google_authentication') if self.translator else 'Starting new Google authentication...'}{Style.RESET_ALL}")
return self.handle_google_auth()
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} Failed to delete account or re-authenticate: {str(e)}{Style.RESET_ALL}")
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.failed_to_delete_account_or_re_authenticate', error=str(e)) if self.translator else f'Failed to delete account or re-authenticate: {str(e)}'}{Style.RESET_ALL}")
else:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Account is still valid (Usage: {usage_text}){Style.RESET_ALL}")
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('oauth.account_is_still_valid', usage=usage_text) if self.translator else f'Account is still valid (Usage: {usage_text})'}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.YELLOW}{EMOJI['INFO']} Could not find usage count: {str(e)}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.could_not_find_usage_count', error=str(e)) if self.translator else f'Could not find usage count: {str(e)}'}{Style.RESET_ALL}")
# Remove the browser stay open prompt and input wait
return True, {"email": actual_email, "token": token}
@ -633,7 +680,7 @@ class OAuthHandler:
# Also check URL as backup
current_url = self.browser.url
if "cursor.com/settings" in current_url:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Already on settings page!{Style.RESET_ALL}")
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('oauth.already_on_settings_page') if self.translator else 'Already on settings page!'}{Style.RESET_ALL}")
time.sleep(1)
cookies = self.browser.cookies()
for cookie in cookies:
@ -649,9 +696,9 @@ class OAuthHandler:
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:
actual_email = email_element.text
print(f"{Fore.CYAN}{EMOJI['INFO']} Found email: {actual_email}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.found_email', email=actual_email) if self.translator else f'Found email: {actual_email}'}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.YELLOW}{EMOJI['INFO']} Could not find email: {str(e)}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.could_not_find_email', error=str(e)) if self.translator else f'Could not find email: {str(e)}'}{Style.RESET_ALL}")
actual_email = "user@cursor.sh"
# Check usage count
@ -659,11 +706,11 @@ class OAuthHandler:
usage_element = self.browser.ele("css:div[class='flex flex-col gap-4 lg:flex-row'] div:nth-child(1) div:nth-child(1) span:nth-child(2)")
if usage_element:
usage_text = usage_element.text
print(f"{Fore.CYAN}{EMOJI['INFO']} Usage count: {usage_text}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.usage_count', usage=usage_text) if self.translator else f'Usage count: {usage_text}'}{Style.RESET_ALL}")
# Check if account is expired
if usage_text.strip() == "150 / 150":
print(f"{Fore.YELLOW}{EMOJI['INFO']} Account has reached maximum usage, deleting...{Style.RESET_ALL}")
# Check if account is expired (both 150/150 and 50/50 cases)
if usage_text.strip() == "150 / 150" or usage_text.strip() == "50 / 50":
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.account_has_reached_maximum_usage', deleting='deleting') if self.translator else 'Account has reached maximum usage, deleting...'}{Style.RESET_ALL}")
delete_js = """
function deleteAccount() {
@ -695,44 +742,44 @@ class OAuthHandler:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Delete account result: {result}{Style.RESET_ALL}")
# Navigate back to auth page and repeat authentication
print(f"{Fore.CYAN}{EMOJI['INFO']} Starting re-authentication process...{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} Redirecting to authenticator.cursor.sh...{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.starting_re_authentication_process') if self.translator else 'Starting re-authentication process...'}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.redirecting_to_authenticator_cursor_sh') if self.translator else 'Redirecting to authenticator.cursor.sh...'}{Style.RESET_ALL}")
# Explicitly navigate to the authentication page
self.browser.get("https://authenticator.cursor.sh/sign-up")
time.sleep(get_random_wait_time(self.config, 'page_load_wait'))
# Call handle_google_auth again to repeat the entire process
print(f"{Fore.CYAN}{EMOJI['INFO']} Starting new Google authentication...{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.starting_new_google_authentication') if self.translator else 'Starting new Google authentication...'}{Style.RESET_ALL}")
return self.handle_google_auth()
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} Failed to delete account or re-authenticate: {str(e)}{Style.RESET_ALL}")
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.failed_to_delete_account_or_re_authenticate', error=str(e)) if self.translator else f'Failed to delete account or re-authenticate: {str(e)}'}{Style.RESET_ALL}")
else:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Account is still valid (Usage: {usage_text}){Style.RESET_ALL}")
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('oauth.account_is_still_valid', usage=usage_text) if self.translator else f'Account is still valid (Usage: {usage_text})'}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.YELLOW}{EMOJI['INFO']} Could not find usage count: {str(e)}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.could_not_find_usage_count', error=str(e)) if self.translator else f'Could not find usage count: {str(e)}'}{Style.RESET_ALL}")
# Remove the browser stay open prompt and input wait
return True, {"email": actual_email, "token": token}
elif current_url != last_url:
print(f"{Fore.CYAN}{EMOJI['INFO']} Page changed, checking auth...{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.page_changed_checking_auth') if self.translator else 'Page changed, checking auth...'}{Style.RESET_ALL}")
last_url = current_url
time.sleep(get_random_wait_time(self.config, 'page_load_wait'))
except Exception as e:
print(f"{Fore.YELLOW}{EMOJI['INFO']} Status check error: {str(e)}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.status_check_error', error=str(e)) if self.translator else f'Status check error: {str(e)}'}{Style.RESET_ALL}")
time.sleep(1)
continue
time.sleep(1)
print(f"{Fore.RED}{EMOJI['ERROR']} Authentication timeout{Style.RESET_ALL}")
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.authentication_timeout') if self.translator else 'Authentication timeout'}{Style.RESET_ALL}")
return False, None
print(f"{Fore.RED}{EMOJI['ERROR']} Authentication button not found{Style.RESET_ALL}")
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.authentication_button_not_found') if self.translator else 'Authentication button not found'}{Style.RESET_ALL}")
return False, None
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} Authentication failed: {str(e)}{Style.RESET_ALL}")
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.authentication_failed', error=str(e)) if self.translator else f'Authentication failed: {str(e)}'}{Style.RESET_ALL}")
return False, None
finally:
if self.browser:
@ -755,7 +802,7 @@ class OAuthHandler:
time.sleep(1)
# Debug cookie information
print(f"{Fore.CYAN}{EMOJI['INFO']} Found {len(cookies)} cookies{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.found_cookies', count=len(cookies)) if self.translator else f'Found {len(cookies)} cookies'}{Style.RESET_ALL}")
email = None
token = None
@ -770,12 +817,12 @@ class OAuthHandler:
elif "%3A%3A" in value:
token = value.split("%3A%3A")[-1]
except Exception as e:
print(f"{Fore.YELLOW}{EMOJI['INFO']} Token extraction error: {str(e)}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.token_extraction_error', error=str(e)) if self.translator else f'Token extraction error: {str(e)}'}{Style.RESET_ALL}")
elif name == "cursor_email":
email = cookie.get("value")
if email and token:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Authentication successful - Email: {email}{Style.RESET_ALL}")
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('oauth.authentication_successful', email=email) if self.translator else f'Authentication successful - Email: {email}'}{Style.RESET_ALL}")
return True, {"email": email, "token": token}
else:
missing = []
@ -783,11 +830,11 @@ class OAuthHandler:
missing.append("email")
if not token:
missing.append("token")
print(f"{Fore.RED}{EMOJI['ERROR']} Missing authentication data: {', '.join(missing)}{Style.RESET_ALL}")
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.missing_authentication_data', data=', '.join(missing)) if self.translator else f'Missing authentication data: {", ".join(missing)}'}{Style.RESET_ALL}")
return False, None
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} Failed to extract auth info: {str(e)}{Style.RESET_ALL}")
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.failed_to_extract_auth_info', error=str(e)) if self.translator else f'Failed to extract auth info: {str(e)}'}{Style.RESET_ALL}")
return False, None
def _delete_current_account(self):
@ -822,14 +869,14 @@ class OAuthHandler:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Delete account result: {result}{Style.RESET_ALL}")
# Navigate back to auth page
print(f"{Fore.CYAN}{EMOJI['INFO']} Redirecting to authenticator.cursor.sh...{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.redirecting_to_authenticator_cursor_sh') if self.translator else 'Redirecting to authenticator.cursor.sh...'}{Style.RESET_ALL}")
self.browser.get("https://authenticator.cursor.sh/sign-up")
time.sleep(get_random_wait_time(self.config, 'page_load_wait'))
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} Failed to delete account: {str(e)}{Style.RESET_ALL}")
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.failed_to_delete_account', error=str(e)) if self.translator else f'Failed to delete account: {str(e)}'}{Style.RESET_ALL}")
return False
def main(auth_type, translator=None):
@ -839,16 +886,16 @@ def main(auth_type, translator=None):
auth_type (str): Type of authentication ('google' or 'github')
translator: Translator instance for internationalization
"""
handler = OAuthHandler(translator)
handler = OAuthHandler(translator, auth_type)
if auth_type.lower() == 'google':
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('oauth.google_start')}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('oauth.google_start') if translator else 'Google start'}{Style.RESET_ALL}")
success, auth_info = handler.handle_google_auth()
elif auth_type.lower() == 'github':
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('oauth.github_start')}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('oauth.github_start') if translator else 'Github start'}{Style.RESET_ALL}")
success, auth_info = handler.handle_github_auth()
else:
print(f"{Fore.RED}{EMOJI['ERROR']} Invalid authentication type{Style.RESET_ALL}")
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('oauth.invalid_authentication_type') if translator else 'Invalid authentication type'}{Style.RESET_ALL}")
return False
if success and auth_info:
@ -859,13 +906,13 @@ def main(auth_type, translator=None):
access_token=auth_info["token"],
refresh_token=auth_info["token"]
):
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('oauth.auth_update_success')}{Style.RESET_ALL}")
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('oauth.auth_update_success') if translator else 'Auth update success'}{Style.RESET_ALL}")
# Close the browser after successful authentication
if handler.browser:
handler.browser.quit()
print(f"{Fore.CYAN}{EMOJI['INFO']} Browser closed{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('oauth.browser_closed') if translator else 'Browser closed'}{Style.RESET_ALL}")
return True
else:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('oauth.auth_update_failed')}{Style.RESET_ALL}")
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('oauth.auth_update_failed') if translator else 'Auth update failed'}{Style.RESET_ALL}")
return False

View File

@ -26,6 +26,7 @@ EMOJI = {
"ERROR": "",
"INFO": "",
"RESET": "🔄",
"WARNING": "⚠️",
}
def get_cursor_paths(translator=None) -> Tuple[str, str]:
@ -37,10 +38,41 @@ def get_cursor_paths(translator=None) -> Tuple[str, str]:
config_dir = os.path.join(get_user_documents_path(), ".cursor-free-vip")
config_file = os.path.join(config_dir, "config.ini")
# Create config directory if it doesn't exist
if not os.path.exists(config_dir):
os.makedirs(config_dir)
# Default paths for different systems
default_paths = {
"Darwin": "/Applications/Cursor.app/Contents/Resources/app",
"Windows": os.path.join(os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app"),
"Linux": ["/opt/Cursor/resources/app", "/usr/share/cursor/resources/app", os.path.expanduser("~/.local/share/cursor/resources/app"), "/usr/lib/cursor/app/"]
}
# If config doesn't exist, create it with default paths
if not os.path.exists(config_file):
raise OSError(translator.get('reset.config_not_found') if translator else "找不到配置文件")
for section in ['MacPaths', 'WindowsPaths', 'LinuxPaths']:
if not config.has_section(section):
config.add_section(section)
config.read(config_file, encoding='utf-8') # Specify encoding
if system == "Darwin":
config.set('MacPaths', 'cursor_path', default_paths["Darwin"])
elif system == "Windows":
config.set('WindowsPaths', 'cursor_path', default_paths["Windows"])
elif system == "Linux":
# For Linux, try to find the first existing path
for path in default_paths["Linux"]:
if os.path.exists(path):
config.set('LinuxPaths', 'cursor_path', path)
break
else:
# If no path exists, use the first one as default
config.set('LinuxPaths', 'cursor_path', default_paths["Linux"][0])
with open(config_file, 'w', encoding='utf-8') as f:
config.write(f)
else:
config.read(config_file, encoding='utf-8')
# Get path based on system
if system == "Darwin":
@ -51,15 +83,26 @@ def get_cursor_paths(translator=None) -> Tuple[str, str]:
section = 'LinuxPaths'
else:
raise OSError(translator.get('reset.unsupported_os', system=system) if translator else f"不支持的操作系统: {system}")
if not config.has_section(section) or not config.has_option(section, 'cursor_path'):
raise OSError(translator.get('reset.path_not_configured') if translator else "未配置 Cursor 路徑")
base_path = config.get(section, 'cursor_path')
# For Linux, try to find the first existing path if the configured one doesn't exist
if system == "Linux" and not os.path.exists(base_path):
for path in default_paths["Linux"]:
if os.path.exists(path):
base_path = path
# Update config with the found path
config.set(section, 'cursor_path', path)
with open(config_file, 'w', encoding='utf-8') as f:
config.write(f)
break
if not os.path.exists(base_path):
raise OSError(translator.get('reset.path_not_found', path=base_path) if translator else f"找不到 Cursor 路徑: {base_path}")
pkg_path = os.path.join(base_path, "package.json")
main_path = os.path.join(base_path, "out/main.js")
@ -127,7 +170,7 @@ def get_workbench_cursor_path(translator=None) -> str:
"main": "out/vs/workbench/workbench.desktop.main.js"
},
"Linux": {
"bases": ["/opt/Cursor/resources/app", "/usr/share/cursor/resources/app"],
"bases": ["/opt/Cursor/resources/app", "/usr/share/cursor/resources/app", "/usr/lib/cursor/app/"],
"main": "out/vs/workbench/workbench.desktop.main.js"
}
}
@ -256,11 +299,11 @@ def modify_workbench_js(file_path: str, translator=None) -> bool:
if sys.platform == "win32":
# Define replacement patterns
CButton_old_pattern = r'$(k,E(Ks,{title:"Upgrade to Pro",size:"small",get codicon(){return F.rocket},get onClick(){return t.pay}}),null)'
CButton_new_pattern = r'$(k,E(Ks,{title:"yeongpin GitHub",size:"small",get codicon(){return F.rocket},get onClick(){return function(){window.open("https://github.com/yeongpin/cursor-free-vip","_blank")}}}),null)'
CButton_old_pattern = r'M(x,I(as,{title:"Upgrade to Pro",size:"small",get codicon(){return $.rocket},get onClick(){return t.pay}}),null)'
CButton_new_pattern = r'M(x,I(as,{title:"yeongpin GitHub",size:"small",get codicon(){return $.rocket},get onClick(){return function(){window.open("https://github.com/yeongpin/cursor-free-vip","_blank")}}}),null)'
elif sys.platform == "linux":
CButton_old_pattern = r'$(k,E(Ks,{title:"Upgrade to Pro",size:"small",get codicon(){return F.rocket},get onClick(){return t.pay}}),null)'
CButton_new_pattern = r'$(k,E(Ks,{title:"yeongpin GitHub",size:"small",get codicon(){return F.rocket},get onClick(){return function(){window.open("https://github.com/yeongpin/cursor-free-vip","_blank")}}}),null)'
CButton_old_pattern = r'M(x,I(as,{title:"Upgrade to Pro",size:"small",get codicon(){return $.rocket},get onClick(){return t.pay}}),null)'
CButton_new_pattern = r'M(x,I(as,{title:"yeongpin GitHub",size:"small",get codicon(){return $.rocket},get onClick(){return function(){window.open("https://github.com/yeongpin/cursor-free-vip","_blank")}}}),null)'
elif sys.platform == "darwin":
CButton_old_pattern = r'M(x,I(as,{title:"Upgrade to Pro",size:"small",get codicon(){return $.rocket},get onClick(){return t.pay}}),null)'
CButton_new_pattern = r'M(x,I(as,{title:"yeongpin GitHub",size:"small",get codicon(){return $.rocket},get onClick(){return function(){window.open("https://github.com/yeongpin/cursor-free-vip","_blank")}}}),null)'
@ -534,6 +577,7 @@ class MachineIDResetter:
if sys.platform.startswith("win"):
self._update_windows_machine_guid()
self._update_windows_machine_id()
elif sys.platform == "darwin":
self._update_macos_platform_uuid(new_ids)
@ -563,6 +607,45 @@ class MachineIDResetter:
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.update_windows_machine_guid_failed', error=str(e))}{Style.RESET_ALL}")
raise
def _update_windows_machine_id(self):
"""Update Windows MachineId in SQMClient registry"""
try:
import winreg
# 1. Generate new GUID
new_guid = "{" + str(uuid.uuid4()).upper() + "}"
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('reset.new_machine_id')}: {new_guid}{Style.RESET_ALL}")
# 2. Open the registry key
try:
key = winreg.OpenKey(
winreg.HKEY_LOCAL_MACHINE,
r"SOFTWARE\Microsoft\SQMClient",
0,
winreg.KEY_WRITE | winreg.KEY_WOW64_64KEY
)
except FileNotFoundError:
# If the key does not exist, create it
key = winreg.CreateKey(
winreg.HKEY_LOCAL_MACHINE,
r"SOFTWARE\Microsoft\SQMClient"
)
# 3. Set MachineId value
winreg.SetValueEx(key, "MachineId", 0, winreg.REG_SZ, new_guid)
winreg.CloseKey(key)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.windows_machine_id_updated')}{Style.RESET_ALL}")
return True
except PermissionError:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.permission_denied')}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {self.translator.get('reset.run_as_admin')}{Style.RESET_ALL}")
return False
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.update_windows_machine_id_failed', error=str(e))}{Style.RESET_ALL}")
return False
def _update_macos_platform_uuid(self, new_ids):
"""Update macOS Platform UUID"""
@ -621,13 +704,10 @@ class MachineIDResetter:
self.update_system_ids(new_ids)
### Remove In v1.7.02
# Modify workbench.desktop.main.js
# workbench_path = get_workbench_cursor_path(self.translator)
# modify_workbench_js(workbench_path, self.translator)
workbench_path = get_workbench_cursor_path(self.translator)
modify_workbench_js(workbench_path, self.translator)
### Remove In v1.7.02
# Check Cursor version and perform corresponding actions
greater_than_0_45 = check_cursor_version(self.translator)
@ -706,4 +786,4 @@ def run(translator=None):
if __name__ == "__main__":
from main import translator as main_translator
run(main_translator)
run(main_translator)

View File

@ -1,196 +1,789 @@
import os
import shutil
import platform
import time
import sys
import json
import uuid
import subprocess
import hashlib
import shutil
import sqlite3
import platform
import re
import tempfile
from colorama import Fore, Style, init
from typing import Tuple
import configparser
from new_signup import get_user_documents_path
import traceback
from config import get_config
# Initialize colorama
init()
# Define emoji constants
EMOJI = {
"FILE": "📄",
"BACKUP": "💾",
"SUCCESS": "",
"ERROR": "",
"INFO": "",
"RESET": "🔄",
"MENU": "📋",
"WARNING": "⚠️"
"RESET": "<EFBFBD><EFBFBD>",
"WARNING": "⚠️",
}
def delete_directory(path, translator=None):
"""Deletes a directory and all its contents."""
if os.path.exists(path):
try:
shutil.rmtree(path)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('totally_reset.removed', path=path)}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.failed_to_remove', path=path, error=e)}{Style.RESET_ALL}")
def get_cursor_paths(translator=None) -> Tuple[str, str]:
""" Get Cursor related paths"""
system = platform.system()
# Read config file
config = configparser.ConfigParser()
config_dir = os.path.join(get_user_documents_path(), ".cursor-free-vip")
config_file = os.path.join(config_dir, "config.ini")
# Create config directory if it doesn't exist
if not os.path.exists(config_dir):
os.makedirs(config_dir)
# Default paths for different systems
default_paths = {
"Darwin": "/Applications/Cursor.app/Contents/Resources/app",
"Windows": os.path.join(os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app"),
"Linux": ["/opt/Cursor/resources/app", "/usr/share/cursor/resources/app", os.path.expanduser("~/.local/share/cursor/resources/app")]
}
# If config doesn't exist, create it with default paths
if not os.path.exists(config_file):
for section in ['MacPaths', 'WindowsPaths', 'LinuxPaths']:
if not config.has_section(section):
config.add_section(section)
if system == "Darwin":
config.set('MacPaths', 'cursor_path', default_paths["Darwin"])
elif system == "Windows":
config.set('WindowsPaths', 'cursor_path', default_paths["Windows"])
elif system == "Linux":
# For Linux, try to find the first existing path
for path in default_paths["Linux"]:
if os.path.exists(path):
config.set('LinuxPaths', 'cursor_path', path)
break
else:
# If no path exists, use the first one as default
config.set('LinuxPaths', 'cursor_path', default_paths["Linux"][0])
with open(config_file, 'w', encoding='utf-8') as f:
config.write(f)
else:
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('totally_reset.not_found', path=path)}{Style.RESET_ALL}")
def delete_file(path, translator=None):
"""Deletes a file if it exists."""
if os.path.isfile(path):
try:
os.remove(path)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('totally_reset.removed', path=path)}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.failed_to_remove', path=path, error=e)}{Style.RESET_ALL}")
config.read(config_file, encoding='utf-8')
# Get path based on system
if system == "Darwin":
section = 'MacPaths'
elif system == "Windows":
section = 'WindowsPaths'
elif system == "Linux":
section = 'LinuxPaths'
else:
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('totally_reset.not_found', path=path)}{Style.RESET_ALL}")
def reset_machine_id(translator=None):
"""Resets the machine ID to a new UUID."""
new_id = str(uuid.uuid4())
if platform.system() == "Windows":
try:
subprocess.run(
["reg", "add", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Cryptography", "/v", "MachineGuid", "/d", new_id, "/f"],
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('totally_reset.machine_guid_reset', new_id=new_id)}{Style.RESET_ALL}")
except subprocess.CalledProcessError as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.failed_to_reset_machine_guid', error=e)}{Style.RESET_ALL}")
elif platform.system() == "Linux":
machine_id_paths = ["/etc/machine-id", "/var/lib/dbus/machine-id"]
for path in machine_id_paths:
raise OSError(translator.get('reset.unsupported_os', system=system) if translator else f"不支持的操作系统: {system}")
if not config.has_section(section) or not config.has_option(section, 'cursor_path'):
raise OSError(translator.get('reset.path_not_configured') if translator else "未配置 Cursor 路徑")
base_path = config.get(section, 'cursor_path')
# For Linux, try to find the first existing path if the configured one doesn't exist
if system == "Linux" and not os.path.exists(base_path):
for path in default_paths["Linux"]:
if os.path.exists(path):
try:
with open(path, 'w') as f:
f.write(new_id)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('totally_reset.machine_id_reset', path=path)}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.failed_to_reset_machine_id', path=path, error=e)}{Style.RESET_ALL}")
elif platform.system() == "Darwin": # macOS
print(" macOS does not use a machine-id file. Skipping machine ID reset.")
base_path = path
# Update config with the found path
config.set(section, 'cursor_path', path)
with open(config_file, 'w', encoding='utf-8') as f:
config.write(f)
break
if not os.path.exists(base_path):
raise OSError(translator.get('reset.path_not_found', path=base_path) if translator else f"找不到 Cursor 路徑: {base_path}")
pkg_path = os.path.join(base_path, "package.json")
main_path = os.path.join(base_path, "out/main.js")
# Check if files exist
if not os.path.exists(pkg_path):
raise OSError(translator.get('reset.package_not_found', path=pkg_path) if translator else f"找不到 package.json: {pkg_path}")
if not os.path.exists(main_path):
raise OSError(translator.get('reset.main_not_found', path=main_path) if translator else f"找不到 main.js: {main_path}")
return (pkg_path, main_path)
def get_cursor_machine_id_path(translator=None) -> str:
"""
Get Cursor machineId file path based on operating system
Returns:
str: Path to machineId file
"""
# Read configuration
config_dir = os.path.join(get_user_documents_path(), ".cursor-free-vip")
config_file = os.path.join(config_dir, "config.ini")
config = configparser.ConfigParser()
if os.path.exists(config_file):
config.read(config_file)
if sys.platform == "win32": # Windows
if not config.has_section('WindowsPaths'):
config.add_section('WindowsPaths')
config.set('WindowsPaths', 'machine_id_path',
os.path.join(os.getenv("APPDATA"), "Cursor", "machineId"))
return config.get('WindowsPaths', 'machine_id_path')
elif sys.platform == "linux": # Linux
if not config.has_section('LinuxPaths'):
config.add_section('LinuxPaths')
config.set('LinuxPaths', 'machine_id_path',
os.path.expanduser("~/.config/cursor/machineid"))
return config.get('LinuxPaths', 'machine_id_path')
elif sys.platform == "darwin": # macOS
if not config.has_section('MacPaths'):
config.add_section('MacPaths')
config.set('MacPaths', 'machine_id_path',
os.path.expanduser("~/Library/Application Support/Cursor/machineId"))
return config.get('MacPaths', 'machine_id_path')
else:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.unsupported_os')}{Style.RESET_ALL}")
raise OSError(f"Unsupported operating system: {sys.platform}")
def display_features_and_warnings(translator=None):
"""Displays features and warnings before proceeding."""
print(f"\n{Fore.GREEN}{EMOJI['MENU']} {translator.get('totally_reset.title')}")
print("=====================================")
print(f"{translator.get('totally_reset.feature_title')}")
print(f"{Fore.GREEN}{translator.get('totally_reset.feature_1')}")
print(f"{Fore.GREEN}{translator.get('totally_reset.feature_2')}")
print(f"{Fore.GREEN}{translator.get('totally_reset.feature_3')}")
print(f"{Fore.GREEN}{translator.get('totally_reset.feature_4')}")
print(f"{Fore.GREEN}{translator.get('totally_reset.feature_5')}")
print(f"{Fore.GREEN}{translator.get('totally_reset.feature_6')}")
print(f"{Fore.GREEN}{translator.get('totally_reset.feature_7')}")
print(f"{Fore.GREEN}{translator.get('totally_reset.feature_8')}")
print(f"{Fore.GREEN}{translator.get('totally_reset.feature_9')}")
print(f"\n{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('totally_reset.warning_title')}")
print(f"{Fore.YELLOW}{translator.get('totally_reset.warning_1')}")
print(f"{Fore.YELLOW}{translator.get('totally_reset.warning_2')}")
print(f"{Fore.YELLOW}{translator.get('totally_reset.warning_3')}")
print(f"{Fore.YELLOW}{translator.get('totally_reset.warning_4')}")
print(f"{Fore.YELLOW}{translator.get('totally_reset.warning_5')}")
print(f"{Fore.YELLOW}{translator.get('totally_reset.warning_6')}")
print(f"{Fore.YELLOW}{translator.get('totally_reset.warning_7')}")
print("=====================================\n")
# Save any changes to config file
with open(config_file, 'w', encoding='utf-8') as f:
config.write(f)
def get_user_confirmation(translator=None):
"""Prompts the user for confirmation to proceed."""
while True:
response = input(f"{Fore.YELLOW} {translator.get('totally_reset.confirm_title')} {translator.get('totally_reset.invalid_choice')}: ").lower().strip()
if response in ['yes', 'y']:
return True
elif response in ['no', 'n']:
def get_workbench_cursor_path(translator=None) -> str:
"""Get Cursor workbench.desktop.main.js path"""
system = platform.system()
paths_map = {
"Darwin": { # macOS
"base": "/Applications/Cursor.app/Contents/Resources/app",
"main": "out/vs/workbench/workbench.desktop.main.js"
},
"Windows": {
"base": os.path.join(os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app"),
"main": "out/vs/workbench/workbench.desktop.main.js"
},
"Linux": {
"bases": ["/opt/Cursor/resources/app", "/usr/share/cursor/resources/app"],
"main": "out/vs/workbench/workbench.desktop.main.js"
}
}
if system not in paths_map:
raise OSError(translator.get('reset.unsupported_os', system=system) if translator else f"不支持的操作系统: {system}")
if system == "Linux":
for base in paths_map["Linux"]["bases"]:
main_path = os.path.join(base, paths_map["Linux"]["main"])
if os.path.exists(main_path):
return main_path
raise OSError(translator.get('reset.linux_path_not_found') if translator else "在 Linux 系统上未找到 Cursor 安装路径")
base_path = paths_map[system]["base"]
main_path = os.path.join(base_path, paths_map[system]["main"])
if not os.path.exists(main_path):
raise OSError(translator.get('reset.file_not_found', path=main_path) if translator else f"未找到 Cursor main.js 文件: {main_path}")
return main_path
def version_check(version: str, min_version: str = "", max_version: str = "", translator=None) -> bool:
"""Version number check"""
version_pattern = r"^\d+\.\d+\.\d+$"
try:
if not re.match(version_pattern, version):
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.invalid_version_format', version=version)}{Style.RESET_ALL}")
return False
def parse_version(ver: str) -> Tuple[int, ...]:
return tuple(map(int, ver.split(".")))
current = parse_version(version)
if min_version and current < parse_version(min_version):
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.version_too_low', version=version, min_version=min_version)}{Style.RESET_ALL}")
return False
if max_version and current > parse_version(max_version):
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.version_too_high', version=version, max_version=max_version)}{Style.RESET_ALL}")
return False
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.version_check_error', error=str(e))}{Style.RESET_ALL}")
return False
def check_cursor_version(translator) -> bool:
"""Check Cursor version"""
try:
pkg_path, _ = get_cursor_paths(translator)
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('reset.reading_package_json', path=pkg_path)}{Style.RESET_ALL}")
try:
with open(pkg_path, "r", encoding="utf-8") as f:
data = json.load(f)
except UnicodeDecodeError:
# If UTF-8 reading fails, try other encodings
with open(pkg_path, "r", encoding="latin-1") as f:
data = json.load(f)
if not isinstance(data, dict):
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.invalid_json_object')}{Style.RESET_ALL}")
return False
if "version" not in data:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.no_version_field')}{Style.RESET_ALL}")
return False
version = str(data["version"]).strip()
if not version:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.version_field_empty')}{Style.RESET_ALL}")
return False
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('reset.found_version', version=version)}{Style.RESET_ALL}")
# Check version format
if not re.match(r"^\d+\.\d+\.\d+$", version):
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.invalid_version_format', version=version)}{Style.RESET_ALL}")
return False
# Compare versions
try:
current = tuple(map(int, version.split(".")))
min_ver = (0, 45, 0) # Use tuple directly instead of string
if current >= min_ver:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('reset.version_check_passed', version=version, min_version='0.45.0')}{Style.RESET_ALL}")
return True
else:
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('reset.version_too_low', version=version, min_version='0.45.0')}{Style.RESET_ALL}")
return False
except ValueError as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.version_parse_error', error=str(e))}{Style.RESET_ALL}")
return False
except FileNotFoundError as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.package_not_found', path=pkg_path)}{Style.RESET_ALL}")
return False
except json.JSONDecodeError as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.invalid_json_object')}{Style.RESET_ALL}")
return False
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.check_version_failed', error=str(e))}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('reset.stack_trace')}: {traceback.format_exc()}{Style.RESET_ALL}")
return False
def modify_workbench_js(file_path: str, translator=None) -> bool:
"""
Modify file content
"""
try:
# Save original file permissions
original_stat = os.stat(file_path)
original_mode = original_stat.st_mode
original_uid = original_stat.st_uid
original_gid = original_stat.st_gid
# Create temporary file
with tempfile.NamedTemporaryFile(mode="w", encoding="utf-8", errors="ignore", delete=False) as tmp_file:
# Read original content
with open(file_path, "r", encoding="utf-8", errors="ignore") as main_file:
content = main_file.read()
if sys.platform == "win32":
# Define replacement patterns
CButton_old_pattern = r'$(k,E(Ks,{title:"Upgrade to Pro",size:"small",get codicon(){return F.rocket},get onClick(){return t.pay}}),null)'
CButton_new_pattern = r'$(k,E(Ks,{title:"yeongpin GitHub",size:"small",get codicon(){return F.rocket},get onClick(){return function(){window.open("https://github.com/yeongpin/cursor-free-vip","_blank")}}}),null)'
elif sys.platform == "linux":
CButton_old_pattern = r'$(k,E(Ks,{title:"Upgrade to Pro",size:"small",get codicon(){return F.rocket},get onClick(){return t.pay}}),null)'
CButton_new_pattern = r'$(k,E(Ks,{title:"yeongpin GitHub",size:"small",get codicon(){return F.rocket},get onClick(){return function(){window.open("https://github.com/yeongpin/cursor-free-vip","_blank")}}}),null)'
elif sys.platform == "darwin":
CButton_old_pattern = r'M(x,I(as,{title:"Upgrade to Pro",size:"small",get codicon(){return $.rocket},get onClick(){return t.pay}}),null)'
CButton_new_pattern = r'M(x,I(as,{title:"yeongpin GitHub",size:"small",get codicon(){return $.rocket},get onClick(){return function(){window.open("https://github.com/yeongpin/cursor-free-vip","_blank")}}}),null)'
CBadge_old_pattern = r'<div>Pro Trial'
CBadge_new_pattern = r'<div>Pro'
CToast_old_pattern = r'notifications-toasts'
CToast_new_pattern = r'notifications-toasts hidden'
# Replace content
content = content.replace(CButton_old_pattern, CButton_new_pattern)
content = content.replace(CBadge_old_pattern, CBadge_new_pattern)
content = content.replace(CToast_old_pattern, CToast_new_pattern)
# Write to temporary file
tmp_file.write(content)
tmp_path = tmp_file.name
# Backup original file
backup_path = file_path + ".backup"
if os.path.exists(backup_path):
os.remove(backup_path)
shutil.copy2(file_path, backup_path)
# Move temporary file to original position
if os.path.exists(file_path):
os.remove(file_path)
shutil.move(tmp_path, file_path)
# Restore original permissions
os.chmod(file_path, original_mode)
if os.name != "nt": # Not Windows
os.chown(file_path, original_uid, original_gid)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('reset.file_modified')}{Style.RESET_ALL}")
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.modify_file_failed', error=str(e))}{Style.RESET_ALL}")
if "tmp_path" in locals():
try:
os.unlink(tmp_path)
except:
pass
return False
def modify_main_js(main_path: str, translator) -> bool:
"""Modify main.js file"""
try:
original_stat = os.stat(main_path)
original_mode = original_stat.st_mode
original_uid = original_stat.st_uid
original_gid = original_stat.st_gid
with tempfile.NamedTemporaryFile(mode="w", delete=False) as tmp_file:
with open(main_path, "r", encoding="utf-8") as main_file:
content = main_file.read()
patterns = {
r"async getMachineId\(\)\{return [^??]+\?\?([^}]+)\}": r"async getMachineId(){return \1}",
r"async getMacMachineId\(\)\{return [^??]+\?\?([^}]+)\}": r"async getMacMachineId(){return \1}",
}
for pattern, replacement in patterns.items():
content = re.sub(pattern, replacement, content)
tmp_file.write(content)
tmp_path = tmp_file.name
shutil.copy2(main_path, main_path + ".old")
shutil.move(tmp_path, main_path)
os.chmod(main_path, original_mode)
if os.name != "nt":
os.chown(main_path, original_uid, original_gid)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('reset.file_modified')}{Style.RESET_ALL}")
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.modify_file_failed', error=str(e))}{Style.RESET_ALL}")
if "tmp_path" in locals():
os.unlink(tmp_path)
return False
def patch_cursor_get_machine_id(translator) -> bool:
"""Patch Cursor getMachineId function"""
try:
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('reset.start_patching')}...{Style.RESET_ALL}")
# Get paths
pkg_path, main_path = get_cursor_paths(translator)
# Check file permissions
for file_path in [pkg_path, main_path]:
if not os.path.isfile(file_path):
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.file_not_found', path=file_path)}{Style.RESET_ALL}")
return False
if not os.access(file_path, os.W_OK):
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.no_write_permission', path=file_path)}{Style.RESET_ALL}")
return False
# Get version number
try:
with open(pkg_path, "r", encoding="utf-8") as f:
version = json.load(f)["version"]
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('reset.current_version', version=version)}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.read_version_failed', error=str(e))}{Style.RESET_ALL}")
return False
# Check version
if not version_check(version, min_version="0.45.0", translator=translator):
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.version_not_supported')}{Style.RESET_ALL}")
return False
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('reset.version_check_passed')}{Style.RESET_ALL}")
# Backup file
backup_path = main_path + ".bak"
if not os.path.exists(backup_path):
shutil.copy2(main_path, backup_path)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('reset.backup_created', path=backup_path)}{Style.RESET_ALL}")
# Modify file
if not modify_main_js(main_path, translator):
return False
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('reset.patch_completed')}{Style.RESET_ALL}")
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.patch_failed', error=str(e))}{Style.RESET_ALL}")
return False
class MachineIDResetter:
def __init__(self, translator=None):
self.translator = translator
# Read configuration
config_dir = os.path.join(get_user_documents_path(), ".cursor-free-vip")
config_file = os.path.join(config_dir, "config.ini")
config = configparser.ConfigParser()
if not os.path.exists(config_file):
raise FileNotFoundError(f"Config file not found: {config_file}")
config.read(config_file, encoding='utf-8')
# Check operating system
if sys.platform == "win32": # Windows
appdata = os.getenv("APPDATA")
if appdata is None:
raise EnvironmentError("APPDATA Environment Variable Not Set")
if not config.has_section('WindowsPaths'):
config.add_section('WindowsPaths')
config.set('WindowsPaths', 'storage_path', os.path.join(
appdata, "Cursor", "User", "globalStorage", "storage.json"
))
config.set('WindowsPaths', 'sqlite_path', os.path.join(
appdata, "Cursor", "User", "globalStorage", "state.vscdb"
))
self.db_path = config.get('WindowsPaths', 'storage_path')
self.sqlite_path = config.get('WindowsPaths', 'sqlite_path')
elif sys.platform == "darwin": # macOS
if not config.has_section('MacPaths'):
config.add_section('MacPaths')
config.set('MacPaths', 'storage_path', os.path.abspath(os.path.expanduser(
"~/Library/Application Support/Cursor/User/globalStorage/storage.json"
)))
config.set('MacPaths', 'sqlite_path', os.path.abspath(os.path.expanduser(
"~/Library/Application Support/Cursor/User/globalStorage/state.vscdb"
)))
self.db_path = config.get('MacPaths', 'storage_path')
self.sqlite_path = config.get('MacPaths', 'sqlite_path')
elif sys.platform == "linux": # Linux
if not config.has_section('LinuxPaths'):
config.add_section('LinuxPaths')
# Get actual user's home directory
sudo_user = os.environ.get('SUDO_USER')
actual_home = f"/home/{sudo_user}" if sudo_user else os.path.expanduser("~")
config.set('LinuxPaths', 'storage_path', os.path.abspath(os.path.join(
actual_home,
".config/cursor/User/globalStorage/storage.json"
)))
config.set('LinuxPaths', 'sqlite_path', os.path.abspath(os.path.join(
actual_home,
".config/cursor/User/globalStorage/state.vscdb"
)))
self.db_path = config.get('LinuxPaths', 'storage_path')
self.sqlite_path = config.get('LinuxPaths', 'sqlite_path')
else:
print(f"{Fore.RED}{translator.get('totally_reset.invalid_choice')}{Style.RESET_ALL}")
raise NotImplementedError(f"Not Supported OS: {sys.platform}")
def reset_cursor(translator=None):
print(f"\n{Fore.GREEN}{EMOJI['RESET']} {translator.get('totally_reset.resetting_cursor')}\n")
# Save any changes to config file
with open(config_file, 'w', encoding='utf-8') as f:
config.write(f)
# Platform-specific paths
paths = []
if platform.system() == "Linux":
paths = [
os.path.expanduser("~/.cursor"),
os.path.expanduser("~/.local/share/cursor"),
os.path.expanduser("~/.config/cursor"),
os.path.expanduser("~/.cache/cursor"),
"/usr/local/bin/cursor",
"/opt/cursor",
"/usr/bin/cursor",
os.path.expanduser("~/.cursor/machine-id.db"),
os.path.expanduser("~/.local/share/Cursor"),
os.path.expanduser("~/.config/Cursor"),
os.path.expanduser("~/.cache/Cursor")
]
elif platform.system() == "Darwin": # macOS
paths = [
os.path.expanduser("~/Library/Application Support/Cursor"),
os.path.expanduser("~/Library/Caches/Cursor"),
"/Applications/Cursor.app",
os.path.expanduser("~/Library/Preferences/com.cursor.app.plist"),
]
elif platform.system() == "Windows":
paths = [
os.path.expanduser("~\\AppData\\Local\\Cursor"),
os.path.expanduser("~\\AppData\\Roaming\\Cursor"),
os.path.expanduser("~\\.cursor"),
os.path.expanduser("~\\.config\\Cursor"),
os.path.expanduser("~\\.cache\\Cursor"),
"C:\\Program Files\\Cursor",
"C:\\Program Files (x86)\\Cursor",
"C:\\Users\\%USERNAME%\\AppData\\Local\\Cursor",
"C:\\Users\\%USERNAME%\\AppData\\Roaming\\Cursor",
]
def generate_new_ids(self):
"""Generate new machine ID"""
# Generate new UUID
dev_device_id = str(uuid.uuid4())
# Remove directories
for path in paths:
delete_directory(path, translator)
# Generate new machineId (64 characters of hexadecimal)
machine_id = hashlib.sha256(os.urandom(32)).hexdigest()
# Remove common files related to Cursor
files = [
os.path.expanduser("~/.cursor/machine-id.db"),
os.path.expanduser("~/.local/share/cursor.db"),
os.path.expanduser("~/.config/cursor/preferences.json"),
os.path.expanduser("~/.cache/cursor.log"),
]
# Generate new macMachineId (128 characters of hexadecimal)
mac_machine_id = hashlib.sha512(os.urandom(64)).hexdigest()
for file in files:
delete_file(file, translator)
# Generate new sqmId
sqm_id = "{" + str(uuid.uuid4()).upper() + "}"
# Extra cleanup (wildcard search)
print(f"\n{Fore.YELLOW}{EMOJI['INFO']} {translator.get('totally_reset.deep_scanning')}")
base_dirs = ["/tmp", "/var/tmp", os.path.expanduser("~")] # Linux and macOS
if platform.system() == "Windows":
base_dirs = ["C:\\Temp", "C:\\Windows\\Temp", os.path.expanduser("~")] # Windows
self.update_machine_id_file(dev_device_id)
for base in base_dirs:
for root, dirs, files in os.walk(base):
for dir in dirs:
if "cursor" in dir.lower():
delete_directory(os.path.join(root, dir), translator)
for file in files:
if "cursor" in file.lower():
delete_file(os.path.join(root, file), translator)
return {
"telemetry.devDeviceId": dev_device_id,
"telemetry.macMachineId": mac_machine_id,
"telemetry.machineId": machine_id,
"telemetry.sqmId": sqm_id,
"storage.serviceMachineId": dev_device_id, # Add storage.serviceMachineId
}
# Reset machine ID
reset_machine_id(translator)
def update_sqlite_db(self, new_ids):
"""Update machine ID in SQLite database"""
try:
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('reset.updating_sqlite')}...{Style.RESET_ALL}")
conn = sqlite3.connect(self.sqlite_path)
cursor = conn.cursor()
print(f"\n{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('totally_reset.cursor_reset_completed')}")
cursor.execute("""
CREATE TABLE IF NOT EXISTS ItemTable (
key TEXT PRIMARY KEY,
value TEXT
)
""")
def main(translator=None):
start_time = time.time()
updates = [
(key, value) for key, value in new_ids.items()
]
for key, value in updates:
cursor.execute("""
INSERT OR REPLACE INTO ItemTable (key, value)
VALUES (?, ?)
""", (key, value))
print(f"{EMOJI['INFO']} {Fore.CYAN} {self.translator.get('reset.updating_pair')}: {key}{Style.RESET_ALL}")
conn.commit()
conn.close()
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.sqlite_success')}{Style.RESET_ALL}")
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.sqlite_error', error=str(e))}{Style.RESET_ALL}")
return False
def update_system_ids(self, new_ids):
"""Update system-level IDs"""
try:
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('reset.updating_system_ids')}...{Style.RESET_ALL}")
if sys.platform.startswith("win"):
self._update_windows_machine_guid()
self._update_windows_machine_id()
elif sys.platform == "darwin":
self._update_macos_platform_uuid(new_ids)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.system_ids_updated')}{Style.RESET_ALL}")
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.system_ids_update_failed', error=str(e))}{Style.RESET_ALL}")
return False
def _update_windows_machine_guid(self):
"""Update Windows MachineGuid"""
try:
import winreg
key = winreg.OpenKey(
winreg.HKEY_LOCAL_MACHINE,
"SOFTWARE\\Microsoft\\Cryptography",
0,
winreg.KEY_WRITE | winreg.KEY_WOW64_64KEY
)
new_guid = str(uuid.uuid4())
winreg.SetValueEx(key, "MachineGuid", 0, winreg.REG_SZ, new_guid)
winreg.CloseKey(key)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.windows_machine_guid_updated')}{Style.RESET_ALL}")
except PermissionError:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.permission_denied')}{Style.RESET_ALL}")
raise
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.update_windows_machine_guid_failed', error=str(e))}{Style.RESET_ALL}")
raise
# Display features and warnings
display_features_and_warnings(translator)
# Get user confirmation
if get_user_confirmation(translator):
reset_cursor(translator)
end_time = time.time()
print(f"\n{Fore.GREEN}⏱️ {translator.get('totally_reset.completed_in', time=f'{end_time - start_time:.2f} seconds')}{Style.RESET_ALL}")
else:
print(f"\n{Fore.RED}{translator.get('totally_reset.operation_cancelled')}{Style.RESET_ALL}")
def _update_windows_machine_id(self):
"""Update Windows MachineId in SQMClient registry"""
try:
import winreg
# 1. Generate new GUID
new_guid = "{" + str(uuid.uuid4()).upper() + "}"
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('reset.new_machine_id')}: {new_guid}{Style.RESET_ALL}")
# 2. Open the registry key
try:
key = winreg.OpenKey(
winreg.HKEY_LOCAL_MACHINE,
r"SOFTWARE\Microsoft\SQMClient",
0,
winreg.KEY_WRITE | winreg.KEY_WOW64_64KEY
)
except FileNotFoundError:
# If the key does not exist, create it
key = winreg.CreateKey(
winreg.HKEY_LOCAL_MACHINE,
r"SOFTWARE\Microsoft\SQMClient"
)
# 3. Set MachineId value
winreg.SetValueEx(key, "MachineId", 0, winreg.REG_SZ, new_guid)
winreg.CloseKey(key)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.windows_machine_id_updated')}{Style.RESET_ALL}")
return True
except PermissionError:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.permission_denied')}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {self.translator.get('reset.run_as_admin')}{Style.RESET_ALL}")
return False
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.update_windows_machine_id_failed', error=str(e))}{Style.RESET_ALL}")
return False
if __name__ == '__main__':
from main import translator
main(translator)
def _update_macos_platform_uuid(self, new_ids):
"""Update macOS Platform UUID"""
try:
uuid_file = "/var/root/Library/Preferences/SystemConfiguration/com.apple.platform.uuid.plist"
if os.path.exists(uuid_file):
# Use sudo to execute plutil command
cmd = f'sudo plutil -replace "UUID" -string "{new_ids["telemetry.macMachineId"]}" "{uuid_file}"'
result = os.system(cmd)
if result == 0:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.macos_platform_uuid_updated')}{Style.RESET_ALL}")
else:
raise Exception(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.failed_to_execute_plutil_command')}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.update_macos_platform_uuid_failed', error=str(e))}{Style.RESET_ALL}")
raise
def reset_machine_ids(self):
"""Reset machine ID and backup original file"""
try:
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('reset.checking')}...{Style.RESET_ALL}")
if not os.path.exists(self.db_path):
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.not_found')}: {self.db_path}{Style.RESET_ALL}")
return False
if not os.access(self.db_path, os.R_OK | os.W_OK):
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.no_permission')}{Style.RESET_ALL}")
return False
print(f"{Fore.CYAN}{EMOJI['FILE']} {self.translator.get('reset.reading')}...{Style.RESET_ALL}")
with open(self.db_path, "r", encoding="utf-8") as f:
config = json.load(f)
backup_path = self.db_path + ".bak"
if not os.path.exists(backup_path):
print(f"{Fore.YELLOW}{EMOJI['BACKUP']} {self.translator.get('reset.creating_backup')}: {backup_path}{Style.RESET_ALL}")
shutil.copy2(self.db_path, backup_path)
else:
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('reset.backup_exists')}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['RESET']} {self.translator.get('reset.generating')}...{Style.RESET_ALL}")
new_ids = self.generate_new_ids()
# Update configuration file
config.update(new_ids)
print(f"{Fore.CYAN}{EMOJI['FILE']} {self.translator.get('reset.saving_json')}...{Style.RESET_ALL}")
with open(self.db_path, "w", encoding="utf-8") as f:
json.dump(config, f, indent=4)
# Update SQLite database
self.update_sqlite_db(new_ids)
# Update system IDs
self.update_system_ids(new_ids)
# Modify workbench.desktop.main.js
workbench_path = get_workbench_cursor_path(self.translator)
modify_workbench_js(workbench_path, self.translator)
# Check Cursor version and perform corresponding actions
greater_than_0_45 = check_cursor_version(self.translator)
if greater_than_0_45:
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('reset.detecting_version')} >= 0.45.0{self.translator.get('reset.patching_getmachineid')}{Style.RESET_ALL}")
patch_cursor_get_machine_id(self.translator)
else:
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('reset.version_less_than_0_45')}{Style.RESET_ALL}")
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.success')}{Style.RESET_ALL}")
print(f"\n{Fore.CYAN}{self.translator.get('reset.new_id')}:{Style.RESET_ALL}")
for key, value in new_ids.items():
print(f"{EMOJI['INFO']} {key}: {Fore.GREEN}{value}{Style.RESET_ALL}")
return True
except PermissionError as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.permission_error', error=str(e))}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('reset.run_as_admin')}{Style.RESET_ALL}")
return False
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.process_error', error=str(e))}{Style.RESET_ALL}")
return False
def update_machine_id_file(self, machine_id: str) -> bool:
"""
Update machineId file with new machine_id
Args:
machine_id (str): New machine ID to write
Returns:
bool: True if successful, False otherwise
"""
try:
# Get the machineId file path
machine_id_path = get_cursor_machine_id_path()
# Create directory if it doesn't exist
os.makedirs(os.path.dirname(machine_id_path), exist_ok=True)
# Create backup if file exists
if os.path.exists(machine_id_path):
backup_path = machine_id_path + ".backup"
try:
shutil.copy2(machine_id_path, backup_path)
print(f"{Fore.GREEN}{EMOJI['INFO']} {self.translator.get('reset.backup_created', path=backup_path) if self.translator else f'Backup created at: {backup_path}'}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('reset.backup_creation_failed', error=str(e)) if self.translator else f'Could not create backup: {str(e)}'}{Style.RESET_ALL}")
# Write new machine ID to file
with open(machine_id_path, "w", encoding="utf-8") as f:
f.write(machine_id)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.update_success') if self.translator else 'Successfully updated machineId file'}{Style.RESET_ALL}")
return True
except Exception as e:
error_msg = f"Failed to update machineId file: {str(e)}"
if self.translator:
error_msg = self.translator.get('reset.update_failed', error=str(e))
print(f"{Fore.RED}{EMOJI['ERROR']} {error_msg}{Style.RESET_ALL}")
return False
def run(translator=None):
config = get_config(translator)
if not config:
return False
print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['RESET']} {translator.get('reset.title')}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
resetter = MachineIDResetter(translator) # Correctly pass translator
resetter.reset_machine_ids()
print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
input(f"{EMOJI['INFO']} {translator.get('reset.press_enter')}...")
if __name__ == "__main__":
from main import translator as main_translator
run(main_translator)