Merge branch 'yeongpin:main' into main

This commit is contained in:
Luiz Henrique 2025-03-28 10:25:43 -03:00 committed by GitHub
commit 3e3cd40e3f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 672 additions and 17 deletions

4
.env
View File

@ -1,2 +1,2 @@
version=1.7.18
VERSION=1.7.18
version=1.7.19
VERSION=1.7.19

View File

@ -1,5 +1,11 @@
# Change Log
## v1.7.19
1. Add: Cursor Account Info | 增加 Cursor 賬號信息
2. Fix: Disable Auto Update | 修復禁用自動更新
3. Add: 0.48.x Version Support | 增加 0.48.x 版本支持
4. Fix: Some Issues | 修復一些問題
## v1.7.18
1. Fix: No Write Permission | 修復沒有寫入權限
2. Fix: Improve Linux path detection and config handling 修正 linux 路徑和config寫入讀取

View File

@ -49,7 +49,8 @@ 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")
}
elif sys.platform == "darwin":
default_config['MacPaths'] = {
@ -57,7 +58,8 @@ def setup_config(translator=None):
'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')
@ -68,7 +70,8 @@ def setup_config(translator=None):
'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"),
'cursor_path': get_linux_cursor_path(),
'updater_path': os.path.expanduser("~/.config/cursor-updater")
'updater_path': os.path.expanduser("~/.config/cursor-updater"),
'update_yml_path': "/Applications/Cursor.app/Contents/Resources/app-update.yml"
}
# Read existing configuration and merge

469
cursor_acc_info.py Normal file
View File

@ -0,0 +1,469 @@
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()
# 获取 GPT-4 使用量和限制
gpt4_data = data.get("gpt-4", {})
premium_usage = gpt4_data.get("numRequestsTotal", 0)
max_premium_usage = gpt4_data.get("maxRequestUsage", 999)
# 获取 GPT-3.5 使用量,但将限制设为 "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" # 将 GPT-3.5 的限制设为 "No Limit"
}
except requests.RequestException as e:
logger.error(f"获取使用量失败: {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"获取订阅信息失败: {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"获取配置路径失败: {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 == "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 "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}{'' * 40}{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}{'' * 40}{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
subscription_info = UsageManager.get_stripe_profile(token)
# 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
usage_info = UsageManager.get_usage(token)
# display account info
if email:
print(f"{Fore.GREEN}{EMOJI['USER']} {translator.get('account_info.email') if translator else 'Email'}: {Fore.WHITE}{email}{Style.RESET_ALL}")
else:
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('account_info.email_not_found') if translator else 'Email not found'}{Style.RESET_ALL}")
# display subscription type
if subscription_info:
subscription_type = format_subscription_type(subscription_info)
print(f"{Fore.GREEN}{EMOJI['SUBSCRIPTION']} {translator.get('account_info.subscription') if translator else 'Subscription'}: {Fore.WHITE}{subscription_type}{Style.RESET_ALL}")
# display remaining trial days
days_remaining = subscription_info.get("daysRemainingOnTrial")
if days_remaining is not None and days_remaining > 0:
print(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:
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('account_info.subscription_not_found') if translator else 'Subscription information not found'}{Style.RESET_ALL}")
# display usage info
if usage_info:
print(f"\n{Fore.GREEN}{EMOJI['USAGE']} {translator.get('account_info.usage') if translator else 'Usage Statistics'}:{Style.RESET_ALL}")
# GPT-4 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}%)"
print(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}%)"
print(f"{Fore.BLUE}{EMOJI['BASIC']} {translator.get('account_info.basic_usage') if translator else 'Slow Response'}: {basic_color}{basic_display}{Style.RESET_ALL}")
else:
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('account_info.usage_not_found') if translator else 'Usage information not found'}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{'' * 40}{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

@ -31,10 +31,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 +46,13 @@ 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 _kill_cursor_processes(self):
"""End all Cursor processes"""
@ -81,27 +91,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,11 +170,14 @@ 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

View File

@ -287,7 +287,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...",
@ -440,5 +447,36 @@
"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"
}
}

View File

@ -282,7 +282,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": "检查更新...",
@ -435,5 +442,37 @@
"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": "基础使用量"
}
}

View File

@ -261,7 +261,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": "檢查更新...",
@ -414,5 +421,36 @@
"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": "基礎使用量"
}
}

View File

@ -242,6 +242,12 @@ translator = Translator()
def print_menu():
"""Print menu options"""
try:
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')}")