## 当前状态 - 插件界面已完成重命名 (cursorpro → hummingbird) - 双账号池 UI 已实现 (Auto/Pro 卡片) - 后端已切换到 MySQL 数据库 - 添加了 Cursor 官方用量 API 文档 ## 已知问题 (待修复) 1. 激活时检查账号导致无账号时激活失败 2. 未启用无感换号时不应获取账号 3. 账号用量模块不显示 (seamless 未启用时应隐藏) 4. 积分显示为 0 (后端未正确返回) 5. Auto/Pro 双密钥逻辑混乱,状态不同步 6. 账号添加后无自动分析功能 ## 下一版本计划 - 重构数据模型,优化账号状态管理 - 实现 Cursor API 自动分析账号 - 修复激活流程,不依赖账号 - 启用无感时才分配账号 - 完善账号用量实时显示 ## 文件说明 - docs/系统设计文档.md - 完整架构设计 - cursor 官方用量接口.md - Cursor API 文档 - 参考计费/ - Vibeviewer 开源项目参考 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2132 lines
79 KiB
JavaScript
2132 lines
79 KiB
JavaScript
'use strict';
|
||
|
||
var __createBinding = this && this.__createBinding || (Object.create ? function (param0, param1, param2, param3) {
|
||
if (param3 === undefined) {
|
||
param3 = param2;
|
||
}
|
||
var descriptor = Object.getOwnPropertyDescriptor(param1, param2);
|
||
if (!descriptor || ("get" in descriptor ? !param1.__esModule : descriptor.writable || descriptor.configurable)) {
|
||
descriptor = {
|
||
enumerable: true,
|
||
get: function () {
|
||
return param1[param2];
|
||
}
|
||
};
|
||
}
|
||
Object.defineProperty(param0, param3, descriptor);
|
||
} : function (param0, param1, param2, param3) {
|
||
if (param3 === undefined) {
|
||
param3 = param2;
|
||
}
|
||
param0[param3] = param1[param2];
|
||
});
|
||
var __setModuleDefault = this && this.__setModuleDefault || (Object.create ? function (param0, param1) {
|
||
Object.defineProperty(param0, "default", {
|
||
enumerable: true,
|
||
value: param1
|
||
});
|
||
} : function (param0, param1) {
|
||
param0.default = param1;
|
||
});
|
||
var __importStar = this && this.__importStar || function () {
|
||
var getOwnPropNames = function (param0) {
|
||
getOwnPropNames = Object.getOwnPropertyNames || function (param0) {
|
||
var items = [];
|
||
for (var propKey in param0) if (Object.prototype.hasOwnProperty.call(param0, propKey)) {
|
||
items[items.length] = propKey;
|
||
}
|
||
return items;
|
||
};
|
||
return getOwnPropNames(param0);
|
||
};
|
||
return function (param0) {
|
||
if (param0 && param0.__esModule) {
|
||
return param0;
|
||
}
|
||
var obj = {};
|
||
if (param0 != null) {
|
||
var items = getOwnPropNames(param0);
|
||
for (var count = 0; count < items.length; count++) {
|
||
if (items[count] !== "default") {
|
||
__createBinding(obj, param0, items[count]);
|
||
}
|
||
}
|
||
}
|
||
__setModuleDefault(obj, param0);
|
||
return obj;
|
||
};
|
||
}();
|
||
Object.defineProperty(exports, '__esModule', {
|
||
value: true
|
||
});
|
||
exports.HummingbirdProViewProvider = undefined;
|
||
const vscode = __importStar(require("vscode"));
|
||
const client_1 = require("../api/client");
|
||
const extension_1 = require("../extension");
|
||
const account_1 = require('../utils/account');
|
||
const path = __importStar(require("path"));
|
||
const fs = __importStar(require('fs'));
|
||
const child_process_1 = require('child_process');
|
||
const util_1 = require("util");
|
||
const sqlite_1 = require('../utils/sqlite');
|
||
const execAsync = util_1.promisify(child_process_1.exec);
|
||
class HummingbirdProViewProvider {
|
||
constructor(extensionUri, context) {
|
||
this._extensionUri = extensionUri;
|
||
this._context = context;
|
||
this._hostsPermissionGranted = false;
|
||
this.SNI_PROXY_IP = "154.36.154.163";
|
||
this.CURSOR_DOMAINS = ["api2.cursor.sh", "api3.cursor.sh"];
|
||
this.HOSTS_MARKER_START = "# ===== HummingbirdPro SNI Proxy Start =====";
|
||
this.HOSTS_MARKER_END = "# ===== HummingbirdPro SNI Proxy End =====";
|
||
this._cachedCursorPath = null;
|
||
this._onlineStatusUnsubscribe = client_1.onOnlineStatusChange(status => {
|
||
this._postMessage({
|
||
'type': "networkStatus",
|
||
'online': status
|
||
});
|
||
});
|
||
}
|
||
resolveWebviewView(webviewView, context, token) {
|
||
this._view = webviewView;
|
||
webviewView.webview.options = {
|
||
'enableScripts': true,
|
||
'localResourceRoots': [this._extensionUri]
|
||
};
|
||
webviewView.webview.html = this._getHtmlContent(webviewView.webview);
|
||
webviewView.webview.onDidReceiveMessage(async msg => {
|
||
const config = {
|
||
'WZyWQ': "没有写入权限",
|
||
'ZXhkG': "seamlessRestored"
|
||
};
|
||
switch (msg.type) {
|
||
case "activate":
|
||
await this._handleActivate(msg.key);
|
||
break;
|
||
case "switch":
|
||
await this._handleSwitch();
|
||
break;
|
||
case "resetMachineId":
|
||
await this._handleResetMachineId();
|
||
break;
|
||
case "disableUpdate":
|
||
await this._handleDisableUpdate();
|
||
break;
|
||
case "cleanEnv":
|
||
await this._handleCleanEnv();
|
||
break;
|
||
case "disable":
|
||
await this._handleDisable();
|
||
break;
|
||
case "toggleProxy":
|
||
await this._handleToggleProxy(msg.enabled, msg.url);
|
||
break;
|
||
case 'getProxyStatus':
|
||
await this._handleGetProxyStatus();
|
||
break;
|
||
case "getState":
|
||
await this._sendState();
|
||
break;
|
||
case "retryConnect":
|
||
await this._handleRetryConnect();
|
||
break;
|
||
case "getSeamlessStatus":
|
||
await this._handleGetSeamlessStatus();
|
||
break;
|
||
case "injectSeamless":
|
||
await this._handleInjectSeamless();
|
||
break;
|
||
case "restoreSeamless":
|
||
await this._handleRestoreSeamless();
|
||
break;
|
||
case "toggleSeamless":
|
||
await this._handleToggleSeamless(msg.enabled);
|
||
break;
|
||
case "getUserSwitchStatus":
|
||
await this._handleGetUserSwitchStatus();
|
||
break;
|
||
case "manualSeamlessSwitch":
|
||
await this._handleManualSeamlessSwitch();
|
||
break;
|
||
case "checkUsageBeforeSwitch":
|
||
await this._handleCheckUsageBeforeSwitch(msg.email);
|
||
break;
|
||
case "confirmSwitch":
|
||
await this._handleManualSeamlessSwitch();
|
||
break;
|
||
case "getCursorPath":
|
||
await this._handleGetCursorPath();
|
||
break;
|
||
case 'getAccountUsage':
|
||
await this._handleGetAccountUsage(msg.email);
|
||
break;
|
||
case "getAnnouncement":
|
||
await this._handleGetAnnouncement();
|
||
break;
|
||
case "checkVersion":
|
||
await this._handleCheckVersion();
|
||
break;
|
||
case "getCursorRunningPath":
|
||
await this._handleGetCursorRunningPath();
|
||
break;
|
||
case "reloadWindow":
|
||
vscode.commands.executeCommand("workbench.action.reloadWindow");
|
||
break;
|
||
case 'closeCursor':
|
||
await account_1.closeCursor();
|
||
break;
|
||
case 'selectPool':
|
||
await this._handleSelectPool(msg.pool);
|
||
break;
|
||
case 'clearKey':
|
||
await this._handleClearKey(msg.keyType);
|
||
break;
|
||
}
|
||
});
|
||
this._sendState();
|
||
this._checkKeyStatus();
|
||
}
|
||
async _checkKeyStatus() {
|
||
// 检查 Auto 和 Pro 两个密钥的状态
|
||
const autoKey = this._context.globalState.get("hummingbird.autoKey");
|
||
const proKey = this._context.globalState.get("hummingbird.proKey");
|
||
|
||
// 兼容旧版本:如果有旧的 hummingbird.key,迁移到 autoKey
|
||
const oldKey = this._context.globalState.get("hummingbird.key");
|
||
if (oldKey && !autoKey && !proKey) {
|
||
await this._context.globalState.update("hummingbird.autoKey", oldKey);
|
||
await this._context.globalState.update("hummingbird.key", undefined);
|
||
}
|
||
|
||
const keyStatus = { auto: null, pro: null };
|
||
|
||
// 检查 Auto 密钥
|
||
if (autoKey) {
|
||
try {
|
||
const verifyResult = await client_1.verifyKey(autoKey);
|
||
if (verifyResult.success && verifyResult.valid) {
|
||
keyStatus.auto = {
|
||
valid: true,
|
||
expireDate: verifyResult.expire_date,
|
||
switchRemaining: verifyResult.switch_remaining,
|
||
mergedCount: verifyResult.merged_count || 0
|
||
};
|
||
await this._context.globalState.update("hummingbird.autoExpireDate", verifyResult.expire_date);
|
||
} else {
|
||
keyStatus.auto = { valid: false, error: verifyResult.error };
|
||
}
|
||
} catch (err) {
|
||
console.error("[蜂鸟Pro] 检查 Auto 密钥状态失败:", err);
|
||
}
|
||
}
|
||
|
||
// 检查 Pro 密钥
|
||
if (proKey) {
|
||
try {
|
||
const verifyResult = await client_1.verifyKey(proKey);
|
||
if (verifyResult.success && verifyResult.valid) {
|
||
keyStatus.pro = {
|
||
valid: true,
|
||
quota: verifyResult.quota,
|
||
quotaUsed: verifyResult.quota_used,
|
||
quotaRemaining: verifyResult.switch_remaining,
|
||
mergedCount: verifyResult.merged_count || 0
|
||
};
|
||
await this._context.globalState.update("hummingbird.proQuota", verifyResult.quota);
|
||
await this._context.globalState.update("hummingbird.proQuotaUsed", verifyResult.quota_used);
|
||
} else {
|
||
keyStatus.pro = { valid: false, error: verifyResult.error };
|
||
}
|
||
} catch (err) {
|
||
console.error("[蜂鸟Pro] 检查 Pro 密钥状态失败:", err);
|
||
}
|
||
}
|
||
|
||
this._postMessage({
|
||
'type': "keyStatusChecked",
|
||
'auto': keyStatus.auto,
|
||
'pro': keyStatus.pro
|
||
});
|
||
}
|
||
async _handleActivate(key) {
|
||
try {
|
||
const isSeamlessInjected = await this._isSeamlessInjected();
|
||
if (isSeamlessInjected) {
|
||
this._postMessage({
|
||
'type': "activated",
|
||
'success': false,
|
||
'error': "无感换号已启用,请先禁用后再更换授权码"
|
||
});
|
||
return;
|
||
}
|
||
this._cleanProxySettings();
|
||
const verifyResult = await client_1.verifyKey(key);
|
||
if (verifyResult.success && verifyResult.valid) {
|
||
const membershipType = verifyResult.membership_type || 'free';
|
||
const isAuto = membershipType === 'free';
|
||
|
||
console.log("[蜂鸟Pro] 激活成功,类型:", membershipType, "后端返回:", {
|
||
'expire_date': verifyResult.expire_date,
|
||
'switch_remaining': verifyResult.switch_remaining,
|
||
'quota': verifyResult.quota,
|
||
'merged_count': verifyResult.merged_count
|
||
});
|
||
|
||
// 根据类型存储到不同字段
|
||
if (isAuto) {
|
||
await this._context.globalState.update("hummingbird.autoKey", key);
|
||
await this._context.globalState.update("hummingbird.autoExpireDate", verifyResult.expire_date);
|
||
await this._context.globalState.update("hummingbird.autoMergedCount", verifyResult.merged_count || 0);
|
||
} else {
|
||
await this._context.globalState.update("hummingbird.proKey", key);
|
||
await this._context.globalState.update("hummingbird.proQuota", verifyResult.quota);
|
||
await this._context.globalState.update("hummingbird.proQuotaUsed", verifyResult.quota_used || 0);
|
||
await this._context.globalState.update("hummingbird.proMergedCount", verifyResult.merged_count || 0);
|
||
}
|
||
|
||
this._postMessage({
|
||
'type': "activated",
|
||
'success': true,
|
||
'key': key,
|
||
'membershipType': membershipType,
|
||
'expireDate': verifyResult.expire_date,
|
||
'quota': verifyResult.quota,
|
||
'quotaUsed': verifyResult.quota_used,
|
||
'switchRemaining': verifyResult.switch_remaining,
|
||
'mergedCount': verifyResult.merged_count || 0,
|
||
'masterKey': verifyResult.master_key
|
||
});
|
||
extension_1.showStatusBar();
|
||
await this._handleGetUserSwitchStatus();
|
||
} else {
|
||
this._postMessage({
|
||
'type': "activated",
|
||
'success': false,
|
||
'error': verifyResult.error || "授权码无效"
|
||
});
|
||
}
|
||
} catch (activateErr) {
|
||
this._postMessage({
|
||
'type': "activated",
|
||
'success': false,
|
||
'error': "连接服务器失败"
|
||
});
|
||
}
|
||
}
|
||
async _handleSwitch() {
|
||
// 获取当前选择的号池
|
||
const selectedPool = this._context.globalState.get("hummingbird.selectedPool") || "auto";
|
||
const savedKey = selectedPool === "pro"
|
||
? this._context.globalState.get("hummingbird.proKey")
|
||
: this._context.globalState.get("hummingbird.autoKey");
|
||
|
||
if (!savedKey) {
|
||
this._postMessage({
|
||
'type': "showToast",
|
||
'message': selectedPool === "pro" ? "请先激活Pro授权码" : "请先激活Auto授权码",
|
||
'icon': '⚠️'
|
||
});
|
||
return;
|
||
}
|
||
try {
|
||
const switchResult = await client_1.switchSeamlessToken(savedKey);
|
||
if (switchResult.switched) {
|
||
await this._context.globalState.update("hummingbird.switchRemaining", switchResult.switchRemaining);
|
||
this._postMessage({
|
||
'type': "switched",
|
||
'success': true,
|
||
'email': switchResult.email,
|
||
'switchRemaining': switchResult.switchRemaining,
|
||
'switchLimit': this._context.globalState.get("hummingbird.switchLimit") || 100
|
||
});
|
||
const condition = switchResult.switchRemaining ?? 0;
|
||
this._postMessage({
|
||
'type': "userSwitchStatus",
|
||
'switchRemaining': condition,
|
||
'canSwitch': condition > 0,
|
||
'lockedAccount': switchResult.email ? {
|
||
'email': switchResult.email
|
||
} : null
|
||
});
|
||
} else {
|
||
this._postMessage({
|
||
'type': "switched",
|
||
'success': false,
|
||
'error': switchResult.message || '换号失败'
|
||
});
|
||
}
|
||
} catch (switchErr) {
|
||
this._postMessage({
|
||
'type': 'switched',
|
||
'success': false,
|
||
'error': "连接服务器失败"
|
||
});
|
||
}
|
||
}
|
||
async _writeAccountToLocal(accountData) {
|
||
try {
|
||
const condition = process.env.APPDATA || '';
|
||
const joinedPath = path.join(condition, "Cursor", "User", "globalStorage", "state.vscdb");
|
||
const joinedPath1 = path.join(condition, "Cursor", "User", "globalStorage", 'storage.json');
|
||
const joinedPath2 = path.join(condition, "Cursor", "machineid");
|
||
if (fs.existsSync(joinedPath)) {
|
||
const items = [];
|
||
if (accountData.accessToken) {
|
||
items.push(["cursorAuth/accessToken", accountData.accessToken]);
|
||
}
|
||
if (accountData.refreshToken) {
|
||
items.push(["cursorAuth/refreshToken", accountData.refreshToken]);
|
||
}
|
||
if (accountData.email) {
|
||
items.push(["cursorAuth/cachedEmail", accountData.email]);
|
||
}
|
||
if (accountData.membership_type) {
|
||
items.push(["cursorAuth/stripeMembershipType", accountData.membership_type]);
|
||
}
|
||
if (accountData.sign_up_type) {
|
||
items.push(["cursorAuth/cachedSignUpType", accountData.sign_up_type]);
|
||
}
|
||
if (accountData.serviceMachineId) {
|
||
items.push(["storage.serviceMachineId", accountData.serviceMachineId]);
|
||
}
|
||
await sqlite_1.sqliteSetBatch(joinedPath, items);
|
||
console.log("[蜂鸟Pro] SQLite 数据库已更新");
|
||
}
|
||
if (fs.existsSync(joinedPath1)) {
|
||
const parsed = JSON.parse(fs.readFileSync(joinedPath1, 'utf-8'));
|
||
if (accountData.machineId) {
|
||
parsed["telemetry.machineId"] = accountData.machineId;
|
||
}
|
||
if (accountData.macMachineId) {
|
||
parsed['telemetry.macMachineId'] = accountData.macMachineId;
|
||
}
|
||
if (accountData.devDeviceId) {
|
||
parsed["telemetry.devDeviceId"] = accountData.devDeviceId;
|
||
}
|
||
if (accountData.sqmId) {
|
||
parsed["telemetry.sqmId"] = accountData.sqmId;
|
||
}
|
||
fs.writeFileSync(joinedPath1, JSON.stringify(parsed, null, 4));
|
||
console.log("[蜂鸟Pro] storage.json 已更新");
|
||
}
|
||
if (accountData.machineId) {
|
||
fs.writeFileSync(joinedPath2, accountData.machineId);
|
||
console.log("[蜂鸟Pro] machineid 文件已更新");
|
||
}
|
||
if (accountData.registryGuid && process.platform === "win32") {
|
||
try {
|
||
const result = 'reg add "HKLM\SOFTWARE\Microsoft\Cryptography" /v MachineGuid /t REG_SZ /d "' + accountData.registryGuid + '" /f';
|
||
await execAsync(result);
|
||
console.log("[蜂鸟Pro] 注册表 MachineGuid 已更新");
|
||
} catch (parseErr) {
|
||
console.warn("[蜂鸟Pro] 注册表写入失败(可能需要管理员权限):", parseErr);
|
||
}
|
||
}
|
||
return true;
|
||
} catch (writeErr) {
|
||
console.error("[蜂鸟Pro] 写入本地失败:", writeErr);
|
||
vscode.window.showErrorMessage("写入失败: " + writeErr);
|
||
return false;
|
||
}
|
||
}
|
||
async _handleReset() {
|
||
await this._context.globalState.update("hummingbird.key", undefined);
|
||
await this._context.globalState.update("hummingbird.expireDate", undefined);
|
||
await this._context.globalState.update("hummingbird.switchRemaining", undefined);
|
||
extension_1.hideStatusBar();
|
||
this._postMessage({
|
||
'type': 'reset',
|
||
'success': true
|
||
});
|
||
vscode.window.showInformationMessage("插件已重置");
|
||
}
|
||
async _handleDisable() {
|
||
await this._handleReset();
|
||
vscode.window.showInformationMessage("插件已停用");
|
||
}
|
||
async _checkAdminPrivilege() {
|
||
if (process.platform !== "win32") {
|
||
return true;
|
||
}
|
||
try {
|
||
await execAsync('reg query "HKLM\SOFTWARE\Microsoft\Cryptography" /v MachineGuid 2>nul');
|
||
const hostEntry = await execAsync("net session 2>nul").catch(() => ({
|
||
'stdout': '',
|
||
'stderr': 'error'
|
||
}));
|
||
return !hostEntry.stderr;
|
||
} catch (jsonErr) {
|
||
return false;
|
||
}
|
||
}
|
||
async _handleResetMachineId() {
|
||
try {
|
||
const platform = process.platform;
|
||
if (platform === 'win32') {
|
||
const adminprivilegeResult = await this._checkAdminPrivilege();
|
||
if (!adminprivilegeResult) {
|
||
this._postMessage({
|
||
'type': "adminPermissionRequired"
|
||
});
|
||
return;
|
||
}
|
||
}
|
||
const hostLine = account_1.getCursorPaths();
|
||
const {
|
||
dbPath: charIdx,
|
||
storagePath: lineItem,
|
||
machineidPath: lineIdx
|
||
} = hostLine;
|
||
const module = require("crypto");
|
||
const str = module.randomBytes(32).toString("hex");
|
||
const str1 = module.randomBytes(32).toString("hex");
|
||
const proxyLine = module.randomUUID();
|
||
const result = '{' + module.randomUUID().toUpperCase() + '}';
|
||
let count = 0;
|
||
let items = [];
|
||
if (fs.existsSync(lineItem)) {
|
||
let num = 3;
|
||
while (num > 0) {
|
||
try {
|
||
const parsed = JSON.parse(fs.readFileSync(lineItem, "utf-8"));
|
||
parsed["telemetry.machineId"] = str;
|
||
parsed["telemetry.macMachineId"] = str1;
|
||
parsed["telemetry.devDeviceId"] = proxyLine;
|
||
parsed["telemetry.sqmId"] = result;
|
||
fs.writeFileSync(lineItem, JSON.stringify(parsed, null, 4));
|
||
console.log("[蜂鸟Pro] storage.json 已更新");
|
||
count++;
|
||
break;
|
||
} catch (readErr) {
|
||
num--;
|
||
if (num === 0) {
|
||
console.warn("[蜂鸟Pro] storage.json 更新失败:", readErr.message);
|
||
items.push("storage.json");
|
||
} else {
|
||
await new Promise(param0 => setTimeout(param0, 100));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
{
|
||
let num = 3;
|
||
while (num > 0) {
|
||
try {
|
||
const dirPath = path.dirname(lineIdx);
|
||
if (!fs.existsSync(dirPath)) {
|
||
fs.mkdirSync(dirPath, {
|
||
'recursive': true
|
||
});
|
||
}
|
||
fs.writeFileSync(lineIdx, str);
|
||
console.log("[蜂鸟Pro] machineid 文件已更新");
|
||
count++;
|
||
break;
|
||
} catch (writeErr) {
|
||
num--;
|
||
if (num === 0) {
|
||
console.warn("[蜂鸟Pro] machineid 更新失败:", writeErr.message);
|
||
items.push("machineid");
|
||
} else {
|
||
await new Promise(param0 => setTimeout(param0, 100));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (fs.existsSync(charIdx)) {
|
||
let num = 3;
|
||
while (num > 0) {
|
||
try {
|
||
const proxyEntry = module.randomUUID();
|
||
const newHostsContent = await sqlite_1.sqliteSetBatch(charIdx, [['storage.serviceMachineId', proxyEntry]]);
|
||
if (newHostsContent) {
|
||
console.log("[蜂鸟Pro] SQLite 数据库已更新");
|
||
count++;
|
||
break;
|
||
} else {
|
||
throw new Error("sqliteSetBatch 返回 false");
|
||
}
|
||
} catch (grantErr) {
|
||
num--;
|
||
if (num === 0) {
|
||
console.warn("[蜂鸟Pro] SQLite 更新失败:", grantErr.message);
|
||
items.push("SQLite");
|
||
} else {
|
||
await new Promise(param0 => setTimeout(param0, 500));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (platform === "win32") {
|
||
const hostsLines = module.randomUUID();
|
||
try {
|
||
await execAsync('reg add "HKLM\SOFTWARE\Microsoft\Cryptography" /v MachineGuid /t REG_SZ /d "' + hostsLines + '" /f');
|
||
console.log("[蜂鸟Pro] 注册表 MachineGuid 已更新");
|
||
count++;
|
||
} catch (regWriteErr) {
|
||
console.warn("[蜂鸟Pro] 注册表更新失败(需要管理员权限),已跳过");
|
||
items.push("注册表");
|
||
}
|
||
}
|
||
if (count >= 2) {
|
||
this._postMessage({
|
||
'type': "machineIdReset",
|
||
'success': true,
|
||
'needRestart': true,
|
||
'message': items.length > 0 ? "机器码重置成功(" + items.join(", ") + " 更新失败,不影响使用)" : "机器码重置成功"
|
||
});
|
||
} else {
|
||
this._postMessage({
|
||
'type': "showToast",
|
||
'message': "重置部分失败: " + items.join(", ") + "。请先完全关闭 Cursor 再试",
|
||
'icon': '⚠️'
|
||
});
|
||
}
|
||
} catch (hostsErr) {
|
||
this._postMessage({
|
||
'type': "showToast",
|
||
'message': "重置机器码失败: " + hostsErr,
|
||
'icon': '❌'
|
||
});
|
||
}
|
||
}
|
||
_generateRandomMAC() {
|
||
const module = require("crypto");
|
||
const dbPath = module.randomBytes(6);
|
||
dbPath[0] = (dbPath[0] | 2) & 254;
|
||
return Array.from(dbPath).map(item => item.toString(16).padStart(2, '0')).join(':');
|
||
}
|
||
async _handleDisableUpdate() {
|
||
try {
|
||
const condition = process.env.LOCALAPPDATA || '';
|
||
const joinedPath = path.join(condition, "cursor-updater");
|
||
if (fs.existsSync(joinedPath)) {
|
||
if (fs.statSync(joinedPath).isDirectory()) {
|
||
fs.rmSync(joinedPath, {
|
||
'recursive': true,
|
||
'force': true
|
||
});
|
||
} else {
|
||
fs.unlinkSync(joinedPath);
|
||
}
|
||
}
|
||
fs.writeFileSync(joinedPath, '');
|
||
this._postMessage({
|
||
'type': 'showToast',
|
||
'message': "已禁用 Cursor 自动更新",
|
||
'icon': '✅'
|
||
});
|
||
} catch (toggleErr) {
|
||
this._postMessage({
|
||
'type': "showToast",
|
||
'message': "禁用自动更新失败: " + toggleErr,
|
||
'icon': '❌'
|
||
});
|
||
}
|
||
}
|
||
async _handleCleanEnv() {
|
||
try {
|
||
if (process.platform === "win32") {
|
||
await execAsync("taskkill /F /IM Cursor.exe").catch(() => {});
|
||
} else {
|
||
await execAsync("pkill -f Cursor").catch(() => {});
|
||
}
|
||
await new Promise(param0 => setTimeout(param0, 2000));
|
||
const condition = process.env.APPDATA || '';
|
||
const condition1 = process.env.LOCALAPPDATA || '';
|
||
const condition2 = process.env.HOME || process.env.USERPROFILE || '';
|
||
let count = 0;
|
||
if (process.platform === "win32") {
|
||
const items = [path.join(condition, "Cursor"), path.join(condition1, "Cursor"), path.join(condition1, "cursor-updater"), path.join(condition2, ".cursor")];
|
||
for (const macPath of items) {
|
||
try {
|
||
if (fs.existsSync(macPath)) {
|
||
fs.rmSync(macPath, {
|
||
'recursive': true,
|
||
'force': true
|
||
});
|
||
count++;
|
||
console.log("[蜂鸟Pro] 已清理: " + macPath);
|
||
}
|
||
} catch (statusErr) {
|
||
console.warn("[蜂鸟Pro] 清理失败: " + macPath, statusErr);
|
||
}
|
||
}
|
||
} else {
|
||
if (process.platform === "darwin") {
|
||
const items = [path.join(condition2, "Library", "Application Support", "Cursor"), path.join(condition2, "Library", "Caches", "Cursor"), path.join(condition2, "Library", "Logs", "Cursor"), path.join(condition2, 'Library', "Application Support", 'Caches', "cursor-updater"), path.join(condition2, ".cursor")];
|
||
for (const storagePath of items) {
|
||
try {
|
||
if (fs.existsSync(storagePath)) {
|
||
fs.rmSync(storagePath, {
|
||
'recursive': true,
|
||
'force': true
|
||
});
|
||
count++;
|
||
}
|
||
} catch (pathErr) {
|
||
console.warn("[蜂鸟Pro] 清理失败: " + storagePath, pathErr);
|
||
}
|
||
}
|
||
} else {
|
||
const items = [path.join(condition2, ".config", "Cursor"), path.join(condition2, ".cache", "Cursor"), path.join(condition2, ".local", "share", "Cursor"), path.join(condition2, ".cursor")];
|
||
for (const machineIdPath of items) {
|
||
try {
|
||
if (fs.existsSync(machineIdPath)) {
|
||
fs.rmSync(machineIdPath, {
|
||
'recursive': true,
|
||
'force': true
|
||
});
|
||
count++;
|
||
}
|
||
} catch (seamlessErr) {
|
||
console.warn("[蜂鸟Pro] 清理失败: " + machineIdPath, seamlessErr);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
vscode.window.showInformationMessage("✅ Cursor 环境清理完成!已清理 " + count + " 个目录。请重新启动 Cursor。");
|
||
} catch (cleanErr) {
|
||
vscode.window.showErrorMessage("清理失败: " + cleanErr);
|
||
}
|
||
}
|
||
_cleanProxySettings() {
|
||
try {
|
||
const platform = process.platform;
|
||
const condition = process.env.HOME || process.env.USERPROFILE || '';
|
||
let settingsPath;
|
||
if (platform === "win32") {
|
||
const condition1 = process.env.APPDATA || '';
|
||
settingsPath = path.join(condition1, "Cursor", "User", "settings.json");
|
||
} else {
|
||
if (platform === "darwin") {
|
||
settingsPath = path.join(condition, "Library", "Application Support", "Cursor", 'User', "settings.json");
|
||
} else {
|
||
settingsPath = path.join(condition, ".config", "Cursor", "User", "settings.json");
|
||
}
|
||
}
|
||
if (!fs.existsSync(settingsPath)) {
|
||
return;
|
||
}
|
||
const fileContent = fs.readFileSync(settingsPath, 'utf-8');
|
||
let settingsObj;
|
||
try {
|
||
settingsObj = JSON.parse(fileContent);
|
||
} catch {
|
||
return;
|
||
}
|
||
const items = ["http.proxy", "http.proxyStrictSSL", "http.proxySupport", "cursor.general.disableHttp2", "http.noProxy"];
|
||
let isFalse = false;
|
||
for (const tokenData of items) {
|
||
if (tokenData in settingsObj) {
|
||
isFalse = true;
|
||
delete settingsObj[tokenData];
|
||
}
|
||
}
|
||
if (isFalse) {
|
||
fs.writeFileSync(settingsPath, JSON.stringify(settingsObj, null, 4), "utf-8");
|
||
console.log("[蜂鸟Pro] 已清理 settings.json 中的旧代理配置");
|
||
}
|
||
} catch (proxyErr) {
|
||
console.warn("[蜂鸟Pro] 清理 settings.json 代理配置失败:", proxyErr);
|
||
}
|
||
}
|
||
_getHostsPath() {
|
||
return process.platform === "win32" ? "C:\\Windows\\System32\\drivers\\etc\\hosts" : '/etc/hosts';
|
||
}
|
||
_readHostsFile() {
|
||
try {
|
||
const accountInfo = this._getHostsPath();
|
||
if (fs.existsSync(accountInfo)) {
|
||
return fs.readFileSync(accountInfo, "utf-8");
|
||
}
|
||
} catch (readErr) {
|
||
console.error("[蜂鸟Pro] Read hosts error:", readErr);
|
||
}
|
||
return '';
|
||
}
|
||
_hasHostsConfig() {
|
||
const switchResponse = this._readHostsFile();
|
||
return switchResponse.includes(this.HOSTS_MARKER_START);
|
||
}
|
||
async _grantHostsWritePermission() {
|
||
if (process.platform !== "win32") {
|
||
return false;
|
||
}
|
||
try {
|
||
const content = this._getHostsPath();
|
||
const condition = process.env.USERNAME || '';
|
||
if (!condition) {
|
||
return false;
|
||
}
|
||
const replaced = content.replace(/\\/g, "\\\\");
|
||
const result = "powershell -WindowStyle Hidden -Command \"Start-Process powershell -ArgumentList '-WindowStyle Hidden -Command icacls \\\"" + replaced + '\" /grant ' + condition + ":M' -Verb RunAs -Wait\"";
|
||
await execAsync(result);
|
||
this._hostsPermissionGranted = true;
|
||
console.log("[蜂鸟Pro] Hosts file permission granted to user:", condition);
|
||
return true;
|
||
} catch (switchErr) {
|
||
console.error("[蜂鸟Pro] Grant hosts permission error:", switchErr);
|
||
return false;
|
||
}
|
||
}
|
||
async _writeHostsFile(content) {
|
||
const content1 = this._getHostsPath();
|
||
try {
|
||
if (process.platform === "win32") {
|
||
let isFalse = false;
|
||
try {
|
||
fs.writeFileSync(content1, content, "utf-8");
|
||
isFalse = true;
|
||
} catch (writeErr1) {
|
||
console.log("[蜂鸟Pro] Direct write failed, trying to grant permission");
|
||
}
|
||
if (!isFalse) {
|
||
if (!this._hostsPermissionGranted) {
|
||
const lockedInfo = await this._grantHostsWritePermission();
|
||
if (lockedInfo) {
|
||
try {
|
||
fs.writeFileSync(content1, content, "utf-8");
|
||
remainingCount = true;
|
||
} catch (writeErr2) {
|
||
console.log("[蜂鸟Pro] Write still failed after permission grant");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (!isFalse) {
|
||
const joinedPath = path.join(process.env.TEMP || '', "hummingbird_hosts_temp.txt");
|
||
fs.writeFileSync(joinedPath, content, "utf-8");
|
||
const replaced = joinedPath.replace(/\\/g, "\\\\");
|
||
const replaced1 = content1.replace(/\\/g, "\\\\");
|
||
const result = "powershell -WindowStyle Hidden -Command \"Start-Process powershell -ArgumentList '-WindowStyle Hidden -Command Copy-Item -Path \\\"" + replaced + '\" -Destination \"' + replaced1 + "\\\" -Force' -Verb RunAs -Wait\"";
|
||
await execAsync(result);
|
||
try {
|
||
fs.unlinkSync(joinedPath);
|
||
} catch {}
|
||
}
|
||
try {
|
||
await execAsync("ipconfig /flushdns");
|
||
console.log("[蜂鸟Pro] Windows DNS 缓存已刷新");
|
||
} catch (resetErr) {
|
||
console.warn("[蜂鸟Pro] Windows DNS 刷新失败:", resetErr);
|
||
}
|
||
} else {
|
||
if (process.platform === "darwin") {
|
||
const pathStr = "/tmp/hosts_cursor_temp";
|
||
fs.writeFileSync(pathStr, content, "utf-8");
|
||
const content1 = "do shell script \"cp '" + pathStr + "' '" + content1 + "' && rm '" + pathStr + "' && dscacheutil -flushcache && killall -HUP mDNSResponder\" with administrator privileges";
|
||
await execAsync('osascript -e "' + content1.replace(/"/g, "\\\"") + "\"");
|
||
} else {
|
||
fs.writeFileSync(content1, content, "utf-8");
|
||
}
|
||
}
|
||
return true;
|
||
} catch (disableErr) {
|
||
console.error("[蜂鸟Pro] Write hosts error:", disableErr);
|
||
return false;
|
||
}
|
||
}
|
||
async _handleToggleProxy(enabled, silent) {
|
||
try {
|
||
if (enabled) {
|
||
const savedKey = this._context.globalState.get("hummingbird.key");
|
||
const expireDate = this._context.globalState.get('hummingbird.expireDate');
|
||
if (!savedKey) {
|
||
this._postMessage({
|
||
'type': "proxyUpdated",
|
||
'success': false,
|
||
'error': "请先激活授权码"
|
||
});
|
||
this._postMessage({
|
||
'type': "showToast",
|
||
'message': '请先激活授权码',
|
||
'icon': '⚠️'
|
||
});
|
||
return;
|
||
}
|
||
if (expireDate) {
|
||
const resetResponse = new Date(expireDate).getTime();
|
||
if (Date.now() > resetResponse) {
|
||
this._postMessage({
|
||
'type': "proxyUpdated",
|
||
'success': false,
|
||
'error': "授权码已过期,无法开启免魔法"
|
||
});
|
||
this._postMessage({
|
||
'type': "showToast",
|
||
'message': "授权码已过期,无法开启免魔法",
|
||
'icon': '⚠️'
|
||
});
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
this._cleanProxySettings();
|
||
let content = this._readHostsFile();
|
||
const index = content.indexOf(this.HOSTS_MARKER_START);
|
||
const index1 = content.indexOf(this.HOSTS_MARKER_END);
|
||
if (index !== -1 && index1 !== -1) {
|
||
content = content.substring(0, index) + content.substring(index1 + this.HOSTS_MARKER_END.length);
|
||
}
|
||
content = content.replace(/\n{3,}/g, "\n\n").trim();
|
||
if (enabled) {
|
||
const joinedPath = this.CURSOR_DOMAINS.map(item => this.SNI_PROXY_IP + " " + item).join("\n");
|
||
const result = "\n\n" + this.HOSTS_MARKER_START + "\n" + joinedPath + "\n" + this.HOSTS_MARKER_END + "\n";
|
||
content += result;
|
||
}
|
||
const disableResponse = await this._writeHostsFile(content);
|
||
if (disableResponse) {
|
||
await client_1.updateProxyConfig(enabled, this.SNI_PROXY_IP);
|
||
this._postMessage({
|
||
'type': "proxyUpdated",
|
||
'success': true,
|
||
'enabled': enabled,
|
||
'url': this.SNI_PROXY_IP
|
||
});
|
||
this._postMessage({
|
||
'type': "showToast",
|
||
'message': enabled ? "免魔法已开启" : "免魔法已关闭",
|
||
'icon': '✅'
|
||
});
|
||
} else {
|
||
this._postMessage({
|
||
'type': "proxyUpdated",
|
||
'success': false,
|
||
'error': "修改 hosts 文件失败,请确保有管理员权限"
|
||
});
|
||
this._postMessage({
|
||
'type': "showToast",
|
||
'message': "需要管理员权限修改 hosts 文件",
|
||
'icon': '⚠️'
|
||
});
|
||
}
|
||
} catch (updateErr) {
|
||
console.error("[蜂鸟Pro] Toggle proxy error:", updateErr);
|
||
this._postMessage({
|
||
'type': "proxyUpdated",
|
||
'success': false,
|
||
'error': "更新配置失败"
|
||
});
|
||
}
|
||
}
|
||
async _handleGetProxyStatus() {
|
||
try {
|
||
const enabled = this._hasHostsConfig();
|
||
this._postMessage({
|
||
'type': "proxyStatus",
|
||
'enabled': enabled,
|
||
'url': enabled ? this.SNI_PROXY_IP : ''
|
||
});
|
||
} catch (envErr) {
|
||
console.error("[蜂鸟Pro] Get proxy status error:", envErr);
|
||
this._postMessage({
|
||
'type': "proxyStatus",
|
||
'enabled': false,
|
||
'url': ''
|
||
});
|
||
}
|
||
}
|
||
async _handleGetSeamlessStatus() {
|
||
try {
|
||
const workbenchPath = await this._getWorkbenchPathAsync();
|
||
let isInjected = false;
|
||
if (workbenchPath && fs.existsSync(workbenchPath)) {
|
||
const fileContent = fs.readFileSync(workbenchPath, 'utf-8');
|
||
isInjected = this._checkInjected(fileContent);
|
||
}
|
||
this._postMessage({
|
||
'type': "seamlessStatus",
|
||
'is_injected': isInjected,
|
||
'workbench_path': workbenchPath || '未找到'
|
||
});
|
||
} catch (e1) {
|
||
this._postMessage({
|
||
'type': "seamlessStatus",
|
||
'is_injected': false,
|
||
'error': "检测状态失败"
|
||
});
|
||
}
|
||
}
|
||
async _getCursorInstallPath() {
|
||
if (this._cachedCursorPath) {
|
||
return this._cachedCursorPath;
|
||
}
|
||
const config = vscode.workspace.getConfiguration("hummingbird");
|
||
const configValue = config.get("cursorPath");
|
||
if (configValue && fs.existsSync(configValue)) {
|
||
console.log("[蜂鸟Pro] 使用用户配置的 Cursor 路径:", configValue);
|
||
this._cachedCursorPath = configValue;
|
||
return configValue;
|
||
}
|
||
const platform = process.platform;
|
||
let result = null;
|
||
try {
|
||
if (platform === "win32") {
|
||
try {
|
||
const {
|
||
stdout: wmicOut
|
||
} = await execAsync("wmic process where \"name='Cursor.exe'\" get ExecutablePath /format:list 2>nul");
|
||
if (wmicOut) {
|
||
const matchResult = wmicOut.match(/ExecutablePath=(.+)/);
|
||
if (matchResult && matchResult[1]) {
|
||
const trimmed = matchResult[1].trim();
|
||
result = path.dirname(trimmed);
|
||
}
|
||
}
|
||
} catch (e2) {
|
||
console.log("[蜂鸟Pro] WMIC 获取路径失败");
|
||
}
|
||
if (!result) {
|
||
try {
|
||
const {
|
||
stdout: psOut
|
||
} = await execAsync("powershell -Command \"Get-Process Cursor -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty Path\"");
|
||
if (psOut && psOut.trim()) {
|
||
result = path.dirname(psOut.trim());
|
||
}
|
||
} catch (e3) {
|
||
console.log("[蜂鸟Pro] PowerShell Get-Process 获取路径失败");
|
||
}
|
||
}
|
||
if (!result) {
|
||
try {
|
||
const {
|
||
stdout: regOut
|
||
} = await execAsync("reg query \"HKCUSoftwareMicrosoftWindowsCurrentVersionUninstall\" /s /f \"Cursor\" 2>nul | findstr \"InstallLocation\"");
|
||
if (regOut && regOut.trim()) {
|
||
const matchResult = regOut.match(/InstallLocation\s+REG_SZ\s+(.+)/);
|
||
if (matchResult && matchResult[1] && fs.existsSync(matchResult[1].trim())) {
|
||
result = matchResult[1].trim();
|
||
}
|
||
}
|
||
} catch (e4) {
|
||
console.log("[蜂鸟Pro] 注册表方法1获取路径失败");
|
||
}
|
||
}
|
||
if (!result) {
|
||
try {
|
||
const {
|
||
stdout: regOut2
|
||
} = await execAsync("reg query \"HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\" /s /f \"Cursor\" 2>nul | findstr \"InstallLocation\"");
|
||
if (regOut2 && regOut2.trim()) {
|
||
const matchResult = regOut2.match(/InstallLocation\s+REG_SZ\s+(.+)/);
|
||
if (matchResult && matchResult[1] && fs.existsSync(matchResult[1].trim())) {
|
||
cursorPath = matchResult[1].trim();
|
||
}
|
||
}
|
||
} catch (e5) {
|
||
console.log("[蜂鸟Pro] 注册表方法2获取路径失败");
|
||
}
|
||
}
|
||
if (!result) {
|
||
try {
|
||
const joinedPath = path.join(process.env.APPDATA || '', "Microsoft", "Windows", "Start Menu", 'Programs', "Cursor.lnk");
|
||
const joinedPath1 = path.join("C:\\ProgramData", "Microsoft", 'Windows', "Start Menu", "Programs", "Cursor.lnk");
|
||
for (const content of [joinedPath, joinedPath1]) {
|
||
if (fs.existsSync(content)) {
|
||
const {
|
||
stdout: lnkOut
|
||
} = await execAsync("powershell -Command \"(New-Object -ComObject WScript.Shell).CreateShortcut('" + content.replace(/'/g, "''") + "').TargetPath\"");
|
||
if (lnkOut && lnkOut.trim() && fs.existsSync(lnkOut.trim())) {
|
||
result = path.dirname(lnkOut.trim());
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
} catch (e6) {
|
||
console.log("[蜂鸟Pro] 快捷方式解析获取路径失败");
|
||
}
|
||
}
|
||
if (!result) {
|
||
try {
|
||
const {
|
||
stdout: whereOut
|
||
} = await execAsync("where cursor 2>nul");
|
||
if (whereOut && whereOut.trim()) {
|
||
const parts = whereOut.trim().split("\n");
|
||
for (const str of parts) {
|
||
const trimmed = str.trim();
|
||
if (trimmed && fs.existsSync(trimmed)) {
|
||
cursorPath = path.dirname(trimmed);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
} catch (whereErr) {
|
||
console.log("[蜂鸟Pro] where 命令获取路径失败");
|
||
}
|
||
}
|
||
if (!result) {
|
||
const condition = process.env.LOCALAPPDATA || '';
|
||
const condition1 = process.env.USERPROFILE || '';
|
||
const condition2 = process.env.ProgramFiles || "C:\\Program Files";
|
||
const condition3 = process.env["ProgramFiles(x86)"] || "C:\\Program Files (x86)";
|
||
const items = [path.join(condition, "Programs", "Cursor"), path.join(condition, "Programs", "cursor"), path.join(condition1, "AppData", "Local", "Programs", "Cursor"), path.join(condition2, "Cursor"), path.join(condition3, "Cursor"), path.join(condition, "Cursor"), path.join(condition, "cursor")];
|
||
for (const cursorDbPath of items) {
|
||
if (cursorDbPath && fs.existsSync(cursorDbPath)) {
|
||
result = cursorDbPath;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
if (platform === "darwin") {
|
||
try {
|
||
const {
|
||
stdout: dirEntry
|
||
} = await execAsync("lsof -c Cursor 2>/dev/null | grep \"txt\" | grep -i \"Cursor.app\" | head -1 | awk '{print $9}'");
|
||
if (dirEntry && dirEntry.trim()) {
|
||
const matchResult = dirEntry.trim().match(/(.+\.app)/);
|
||
if (matchResult) {
|
||
result = matchResult[1];
|
||
}
|
||
}
|
||
} catch (e) {}
|
||
if (!result) {
|
||
try {
|
||
const {
|
||
stdout: fileItem
|
||
} = await execAsync("ps -eo comm,args | grep -i \"[C]ursor\" | grep -v \"grep\" | head -1");
|
||
if (fileItem && fileItem.trim()) {
|
||
const matchResult = fileItem.match(/(\/.+\.app)/);
|
||
if (matchResult) {
|
||
cursorPath = matchResult[1];
|
||
}
|
||
}
|
||
} catch (findErr) {
|
||
console.warn("[蜂鸟Pro] macOS 获取进程路径失败:", findErr);
|
||
}
|
||
}
|
||
if (!result) {
|
||
try {
|
||
const {
|
||
stdout: childPath
|
||
} = await execAsync("mdfind \"kMDItemCFBundleIdentifier == 'com.todesktop.*cursor*'\" 2>/dev/null | head -1");
|
||
if (childPath && childPath.trim() && fs.existsSync(childPath.trim())) {
|
||
result = childPath.trim();
|
||
}
|
||
} catch (e) {}
|
||
}
|
||
if (!result && fs.existsSync('/Applications/Cursor.app')) {
|
||
result = "/Applications/Cursor.app";
|
||
}
|
||
} else {
|
||
try {
|
||
const {
|
||
stdout: pathItem
|
||
} = await execAsync('pgrep -f "[c]ursor" | head -1');
|
||
const condition = pathItem && pathItem.trim();
|
||
if (condition) {
|
||
const {
|
||
stdout: subDir
|
||
} = await execAsync("readlink -f /proc/" + condition + "/exe 2>/dev/null");
|
||
if (subDir && subDir.trim()) {
|
||
const trimmed = subDir.trim();
|
||
cursorPath = path.dirname(trimmed);
|
||
if (result.endsWith("/bin")) {
|
||
result = path.dirname(result);
|
||
}
|
||
}
|
||
}
|
||
} catch (e) {}
|
||
if (!result) {
|
||
try {
|
||
const {
|
||
stdout: subItem
|
||
} = await execAsync("which cursor 2>/dev/null");
|
||
if (subItem && subItem.trim()) {
|
||
const execResult = await execAsync('readlink -f "' + subItem.trim() + '" 2>/dev/null');
|
||
if (execResult.stdout && execResult.stdout.trim()) {
|
||
cursorPath = path.dirname(execResult.stdout.trim());
|
||
if (result.endsWith('/bin')) {
|
||
cursorPath = path.dirname(result);
|
||
}
|
||
}
|
||
}
|
||
} catch (checkErr) {
|
||
console.warn("[蜂鸟Pro] Linux 获取进程路径失败:", checkErr);
|
||
}
|
||
}
|
||
if (!result) {
|
||
const items = ["/opt/Cursor", "/opt/cursor", "/usr/share/cursor", "/usr/lib/cursor", path.join(process.env.HOME || '', ".local/share/cursor"), path.join(process.env.HOME || '', "Applications/cursor")];
|
||
for (const statusInfo of items) {
|
||
if (fs.existsSync(statusInfo)) {
|
||
result = statusInfo;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} catch (injectErr) {
|
||
console.error("[蜂鸟Pro] 获取 Cursor 安装路径失败:", injectErr);
|
||
}
|
||
if (result) {
|
||
this._cachedCursorPath = result;
|
||
}
|
||
return result;
|
||
}
|
||
_getWorkbenchPath() {
|
||
return this._getWorkbenchPathSync();
|
||
}
|
||
_getWorkbenchPathSync() {
|
||
const platform = process.platform;
|
||
if (this._cachedCursorPath) {
|
||
let entry;
|
||
if (platform === "darwin") {
|
||
entry = path.join(this._cachedCursorPath, 'Contents', "Resources", "app", "out", 'vs', "workbench", "workbench.desktop.main.js");
|
||
} else {
|
||
entry = path.join(this._cachedCursorPath, "resources", "app", "out", 'vs', "workbench", "workbench.desktop.main.js");
|
||
}
|
||
if (fs.existsSync(entry)) {
|
||
return entry;
|
||
}
|
||
}
|
||
if (platform === 'win32') {
|
||
return null;
|
||
}
|
||
let items = [];
|
||
if (platform === "darwin") {
|
||
items = ["/Applications/Cursor.app/Contents/Resources/app/out/vs/workbench/workbench.desktop.main.js"];
|
||
} else {
|
||
items = ["/opt/Cursor/resources/app/out/vs/workbench/workbench.desktop.main.js", '/usr/share/cursor/resources/app/out/vs/workbench/workbench.desktop.main.js'];
|
||
}
|
||
for (const switchInfo of items) {
|
||
if (fs.existsSync(switchInfo)) {
|
||
return switchInfo;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
async _getWorkbenchPathAsync() {
|
||
const platform = process.platform;
|
||
const cursorPath = await this._getCursorInstallPath();
|
||
if (cursorPath) {
|
||
let workbenchSubPath;
|
||
if (platform === "darwin") {
|
||
workbenchSubPath = path.join(cursorPath, "Contents", "Resources", "app", "out", 'vs', "workbench", "workbench.desktop.main.js");
|
||
} else {
|
||
workbenchSubPath = path.join(cursorPath, "resources", "app", "out", 'vs', "workbench", "workbench.desktop.main.js");
|
||
}
|
||
if (fs.existsSync(workbenchSubPath)) {
|
||
return workbenchSubPath;
|
||
}
|
||
}
|
||
return this._getWorkbenchPathSync();
|
||
}
|
||
_checkInjected(cbArg) {
|
||
return cbArg.includes("/*i0*/") || cbArg.includes('/*i1s*/');
|
||
}
|
||
async _isSeamlessInjected() {
|
||
try {
|
||
const workbenchPath = await this._getWorkbenchPathAsync();
|
||
if (workbenchPath && fs.existsSync(workbenchPath)) {
|
||
const fileContent = fs.readFileSync(workbenchPath, "utf-8");
|
||
return this._checkInjected(fileContent);
|
||
}
|
||
return false;
|
||
} catch (restoreErr) {
|
||
console.error("[蜂鸟Pro] 检测无感换号状态失败:", restoreErr);
|
||
return false;
|
||
}
|
||
}
|
||
_getInjectionConfig(msgData, dataArg) {
|
||
return [{
|
||
'name': "注入点0: 完整性检查绕过",
|
||
'scode': "_showNotification(){",
|
||
'replacement': "_showNotification(){/*i0*/}_showNotificationOld(){",
|
||
'restore': {
|
||
'find': "_showNotification(){/*i0*/}_showNotificationOld(){",
|
||
'replace_with': "_showNotification(){"
|
||
}
|
||
}, {
|
||
'name': "注入点1: 核心模块初始化",
|
||
'scode': "this.database.getItems()))",
|
||
'replacement': "this.database.getItems()))/*i1s*/;await(async function(e){if(e.get('releaseNotes/lastVersion')){window.store=e;window.__cpKey='HummingbirdPro2024!@#';window.__cpEnc=function(t){var k=window.__cpKey,r='';for(var i=0;i<t.length;i++)r+=String.fromCharCode(t.charCodeAt(i)^k.charCodeAt(i%k.length));return btoa(r)};window.__cpDec=function(t){var k=window.__cpKey,d=atob(t),r='';for(var i=0;i<d.length;i++)r+=String.fromCharCode(d.charCodeAt(i)^k.charCodeAt(i%k.length));return r};window.__cpGet=function(){try{var d=localStorage.getItem('__cp_token');return d?JSON.parse(window.__cpDec(d)):null}catch(e){return null}};window.__cpSet=function(data){try{localStorage.setItem('__cp_token',window.__cpEnc(JSON.stringify(data)))}catch(e){}};window.__cpApi='" + msgData + "';window.__cpUserKey='" + dataArg + "';window.__cpVersion=0;console.log('[CP] Initialized with key:','" + dataArg + "'.substring(0,15)+'...')}})(this)/*i1e*/",
|
||
'restore': {
|
||
'find_start': "/*i1s*/",
|
||
'find_end': "/*i1e*/"
|
||
}
|
||
}, {
|
||
'name': "注入点2: 启动时Token同步",
|
||
'scode': "/*i1e*/",
|
||
'replacement': "/*i1e*//*i2s*/;(function(){window.__cpSyncing=false;window.__cpSync=function(){if(window.__cpSyncing){console.log('[CP] Sync already in progress, skip');return}var userKey=window.__cpUserKey;if(!userKey){console.log('[CP] No userKey, skip sync');return}window.__cpSyncing=true;console.log('[CP] Sync with key:',userKey.substring(0,15)+'...');fetch(window.__cpApi+'/api/seamless/get-token?userKey='+encodeURIComponent(userKey)).then(function(r){return r.json()}).then(function(d){window.__cpSyncing=false;if(d.error){console.error('[CP] Sync error:',d.error);return}if(d&&d.accessToken){var oldToken=window.__cpGet();var needUpdate=!oldToken||oldToken.email!==d.email||window.__cpVersion!==d.switchVersion;if(needUpdate){window.__cpVersion=d.switchVersion||0;window.__cpSet({accessToken:d.accessToken,refreshToken:d.refreshToken||'',email:d.email||'',machineIds:d.machineIds||null,switchRemaining:d.switchRemaining,switchVersion:d.switchVersion||0});window.store.set('cursorAuth/accessToken',d.accessToken,-1);if(d.refreshToken)window.store.set('cursorAuth/refreshToken',d.refreshToken,-1);if(d.email)window.store.set('cursor.email',d.email,-1);if(d.is_new&&d.machineIds){if(d.machineIds.devDeviceId)window.store.set('telemetry.devDeviceId',d.machineIds.devDeviceId,-1);if(d.machineIds.machineId)window.store.set('telemetry.machineId',d.machineIds.machineId,-1);if(d.machineIds.macMachineId)window.store.set('telemetry.macMachineId',d.machineIds.macMachineId,-1);if(d.machineIds.sqmId)window.store.set('telemetry.sqmId',d.machineIds.sqmId,-1)}console.log('[CP] Token UPDATED:',d.email,'v'+d.switchVersion)}}}).catch(function(e){window.__cpSyncing=false;console.error('[CP] Sync error:',e)})};console.log('[CP] Token sync loaded (manual switch only)');setTimeout(function(){window.__cpSync()},2000);setInterval(function(){window.__cpSync()},10000)})()/*i2e*/",
|
||
'restore': {
|
||
'find_start': "/*i2s*/",
|
||
'find_end': "/*i2e*/"
|
||
}
|
||
}];
|
||
}
|
||
async _handleInjectSeamless() {
|
||
try {
|
||
const savedKey = this._context.globalState.get("hummingbird.key");
|
||
if (!savedKey) {
|
||
this._postMessage({
|
||
'type': "seamlessInjected",
|
||
'success': false,
|
||
'error': "请先激活授权码"
|
||
});
|
||
return;
|
||
}
|
||
const status = await client_1.getUserSwitchStatus(savedKey);
|
||
if (!status.valid) {
|
||
this._postMessage({
|
||
'type': "seamlessInjected",
|
||
'success': false,
|
||
'error': status.error || "授权码无效"
|
||
});
|
||
return;
|
||
}
|
||
const workbenchPath = await this._getWorkbenchPathAsync();
|
||
if (!workbenchPath) {
|
||
this._postMessage({
|
||
'type': "seamlessInjected",
|
||
'success': false,
|
||
'error': "启用失败"
|
||
});
|
||
return;
|
||
}
|
||
const result = workbenchPath + ".backup";
|
||
const not = !this._context.globalState.get("hummingbird.seamlessInjected");
|
||
if (not && fs.existsSync(result)) {
|
||
console.log("[蜂鸟Pro] 首次启用,从备份恢复干净的 workbench 文件");
|
||
try {
|
||
fs.copyFileSync(result, workbenchPath);
|
||
console.log("[蜂鸟Pro] 备份恢复成功");
|
||
} catch (usageErr) {
|
||
console.error("[蜂鸟Pro] 备份恢复失败:", usageErr);
|
||
}
|
||
}
|
||
let fileContent = fs.readFileSync(workbenchPath, 'utf-8');
|
||
if (this._checkInjected(fileContent)) {
|
||
this._postMessage({
|
||
'type': "showToast",
|
||
'message': "已启用",
|
||
'icon': '✅'
|
||
});
|
||
return;
|
||
}
|
||
if (!fs.existsSync(result)) {
|
||
fs.copyFileSync(workbenchPath, result);
|
||
console.log("[蜂鸟Pro] 创建备份文件");
|
||
}
|
||
const announceList = client_1.getApiUrl();
|
||
const latestAnnounce = this._getInjectionConfig(announceList, savedKey);
|
||
const items = [];
|
||
const items1 = [];
|
||
for (const linuxPath of latestAnnounce) {
|
||
if (fileContent.includes(linuxPath.scode)) {
|
||
fileContent = fileContent.replace(linuxPath.scode, linuxPath.replacement);
|
||
items.push(linuxPath.name);
|
||
} else {
|
||
items1.push(linuxPath.name);
|
||
}
|
||
}
|
||
if (items.length === 0) {
|
||
console.error("[蜂鸟Pro] 注入失败,未找到任何注入点");
|
||
console.error("[蜂鸟Pro] 文件路径:", workbenchPath);
|
||
console.error("[蜂鸟Pro] 文件大小:", fileContent.length);
|
||
console.error("[蜂鸟Pro] 未找到的注入点:", items1);
|
||
const versionResponse = fileContent.includes("_showNotification");
|
||
const latestVersion = fileContent.includes("getItems()");
|
||
console.error("[蜂鸟Pro] 包含 _showNotification:", versionResponse);
|
||
console.error("[蜂鸟Pro] 包含 getItems():", latestVersion);
|
||
this._postMessage({
|
||
'type': "seamlessInjected",
|
||
'success': false,
|
||
'error': "Cursor 版本不兼容,注入点未找到",
|
||
'details': "路径: " + workbenchPath
|
||
});
|
||
return;
|
||
}
|
||
console.log("[蜂鸟Pro] 注入成功,应用的注入点:", items);
|
||
if (items1.length > 0) {
|
||
console.warn("[蜂鸟Pro] 未找到的注入点:", items1);
|
||
}
|
||
try {
|
||
fs.writeFileSync(workbenchPath, fileContent, "utf-8");
|
||
} catch (writeErr) {
|
||
console.error("[蜂鸟Pro] 写入文件失败:", writeErr);
|
||
if (writeErr.code === "EPERM" || writeErr.code === "EACCES" || writeErr.code === "EROFS") {
|
||
const platform = process.platform;
|
||
let errorMsg = "没有写入权限";
|
||
if (platform === "darwin") {
|
||
errorMsg = "没有写入权限,请在终端执行: sudo chmod -R 777 /Applications/Cursor.app";
|
||
} else if (platform === "linux") {
|
||
errorMsg = "没有写入权限,请使用 sudo 权限运行或修改文件权限";
|
||
}
|
||
this._postMessage({
|
||
'type': "seamlessInjected",
|
||
'success': false,
|
||
'error': errorMsg,
|
||
'needAdmin': true,
|
||
'path': workbenchPath
|
||
});
|
||
return;
|
||
}
|
||
throw writeErr;
|
||
}
|
||
await this._context.globalState.update("hummingbird.seamlessInjected", true);
|
||
this._postMessage({
|
||
'type': 'seamlessInjected',
|
||
'success': true,
|
||
'applied': items,
|
||
'needRestart': true,
|
||
'message': "无感换号已启用"
|
||
});
|
||
} catch (appDir) {
|
||
console.error("[蜂鸟Pro] Inject error:", appDir);
|
||
if (appDir.code === "EPERM" || appDir.code === "EACCES") {
|
||
const errorMsg = "没有写入权限";
|
||
this._postMessage({
|
||
'type': "seamlessInjected",
|
||
'success': false,
|
||
'error': errorMsg,
|
||
'needAdmin': true
|
||
});
|
||
return;
|
||
}
|
||
this._postMessage({
|
||
'type': "seamlessInjected",
|
||
'success': false,
|
||
'error': appDir.message || '注入失败'
|
||
});
|
||
}
|
||
}
|
||
async _handleRestoreSeamless() {
|
||
try {
|
||
const workbenchPath = await this._getWorkbenchPathAsync();
|
||
if (!workbenchPath) {
|
||
this._postMessage({
|
||
'type': "seamlessRestored",
|
||
'success': false,
|
||
'error': '未找到Cursor安装目录'
|
||
});
|
||
return;
|
||
}
|
||
let fileContent = fs.readFileSync(workbenchPath, "utf-8");
|
||
if (!this._checkInjected(fileContent)) {
|
||
return;
|
||
}
|
||
fileContent = fileContent.replace("_showNotification(){/*i0*/}_showNotificationOld(){", "_showNotification(){");
|
||
const index = fileContent.indexOf("/*i1s*/");
|
||
const index1 = fileContent.indexOf("/*i1e*/");
|
||
if (index !== -1 && index1 !== -1) {
|
||
fileContent = fileContent.substring(0, index) + fileContent.substring(index1 + 7);
|
||
}
|
||
const index2 = fileContent.indexOf("/*i2s*/");
|
||
const index3 = fileContent.indexOf("/*i2e*/");
|
||
if (index2 !== -1 && index3 !== -1) {
|
||
fileContent = fileContent.substring(0, index2) + fileContent.substring(index3 + 7);
|
||
}
|
||
try {
|
||
fs.writeFileSync(workbenchPath, fileContent, "utf-8");
|
||
} catch (writeErr) {
|
||
if (writeErr.code === "EPERM" || writeErr.code === "EACCES") {
|
||
const errorMsg = "没有写入权限";
|
||
this._postMessage({
|
||
'type': "seamlessRestored",
|
||
'success': false,
|
||
'error': errorMsg,
|
||
'needAdmin': true
|
||
});
|
||
return;
|
||
}
|
||
throw writeErr;
|
||
}
|
||
this._postMessage({
|
||
'type': "seamlessRestored",
|
||
'success': true,
|
||
'needRestart': true,
|
||
'message': "无感换号已禁用"
|
||
});
|
||
} catch (restoreErr) {
|
||
console.error("[蜂鸟Pro] Restore error:", restoreErr);
|
||
if (restoreErr.code === "EPERM" || restoreErr.code === "EACCES") {
|
||
const errorMsg = "没有写入权限";
|
||
this._postMessage({
|
||
'type': "seamlessRestored",
|
||
'success': false,
|
||
'error': errorMsg,
|
||
'needAdmin': true
|
||
});
|
||
return;
|
||
}
|
||
this._postMessage({
|
||
'type': "seamlessRestored",
|
||
'success': false,
|
||
'error': restoreErr.message || '还原失败'
|
||
});
|
||
}
|
||
}
|
||
async _handleToggleSeamless(enabled) {
|
||
try {
|
||
await client_1.updateSeamlessConfig({
|
||
'enabled': enabled
|
||
});
|
||
this._postMessage({
|
||
'type': "seamlessConfigUpdated",
|
||
'success': true,
|
||
'enabled': enabled
|
||
});
|
||
} catch (configErr) {
|
||
this._postMessage({
|
||
'type': "seamlessConfigUpdated",
|
||
'success': false,
|
||
'error': "更新配置失败"
|
||
});
|
||
}
|
||
}
|
||
async _handleGetUserSwitchStatus() {
|
||
try {
|
||
const savedKey = this._context.globalState.get('hummingbird.key');
|
||
if (!savedKey) {
|
||
this._postMessage({
|
||
'type': "userSwitchStatus",
|
||
'valid': false,
|
||
'switchRemaining': 0,
|
||
'canSwitch': false,
|
||
'error': "未激活授权码"
|
||
});
|
||
return;
|
||
}
|
||
const status = await client_1.getUserSwitchStatus(savedKey);
|
||
let isFalse = false;
|
||
try {
|
||
const status1 = await client_1.getSeamlessStatus();
|
||
isFalse = status1.is_injected || false;
|
||
} catch (psOut2) {}
|
||
this._postMessage({
|
||
'type': 'userSwitchStatus',
|
||
...status,
|
||
'seamlessEnabled': isFalse
|
||
});
|
||
} catch (e24) {
|
||
this._postMessage({
|
||
'type': "userSwitchStatus",
|
||
'valid': false,
|
||
'switchRemaining': 0,
|
||
'canSwitch': false,
|
||
'error': "获取状态失败"
|
||
});
|
||
}
|
||
}
|
||
async _handleGetAccountUsage(forceRefresh) {
|
||
try {
|
||
if (!forceRefresh) {
|
||
this._postMessage({
|
||
'type': "accountUsage",
|
||
'success': false,
|
||
'error': "未提供账号邮箱"
|
||
});
|
||
return;
|
||
}
|
||
const result1 = client_1.getApiUrl() + "/api/cursor-accounts/query?email=" + encodeURIComponent(forceRefresh) + '&refresh=true';
|
||
const cursorRunning = await fetch(result1);
|
||
const result = await cursorRunning.json();
|
||
if (result.success && result.data) {
|
||
this._postMessage({
|
||
'type': "accountUsage",
|
||
'success': true,
|
||
'data': result.data
|
||
});
|
||
const condition = result.data.usage || {};
|
||
const condition1 = condition.totalUsageCount || 0;
|
||
const num = parseFloat(condition.totalCostUSD || 0);
|
||
extension_1.updateUsageStatusBar(condition1, num);
|
||
} else {
|
||
this._postMessage({
|
||
'type': "accountUsage",
|
||
'success': false,
|
||
'error': result.error || "获取用量失败"
|
||
});
|
||
}
|
||
} catch (announceErr) {
|
||
this._postMessage({
|
||
'type': "accountUsage",
|
||
'success': false,
|
||
'error': announceErr.message || "请求失败"
|
||
});
|
||
}
|
||
}
|
||
async _handleGetAnnouncement() {
|
||
try {
|
||
const result1 = client_1.getApiUrl() + "/api/announcements/latest";
|
||
const switchCheck = await fetch(result1);
|
||
const result = await switchCheck.json();
|
||
if (result.success && result.data) {
|
||
this._postMessage({
|
||
'type': "announcement",
|
||
'success': true,
|
||
'data': result.data
|
||
});
|
||
} else {
|
||
this._postMessage({
|
||
'type': "announcement",
|
||
'success': false,
|
||
'error': result.error || "获取公告失败"
|
||
});
|
||
}
|
||
} catch (versionErr) {
|
||
this._postMessage({
|
||
'type': "announcement",
|
||
'success': false,
|
||
'error': versionErr.message || "请求失败"
|
||
});
|
||
}
|
||
}
|
||
async _handleCheckVersion() {
|
||
try {
|
||
const result = await client_1.getLatestVersion();
|
||
if (result.success && result.version) {
|
||
const versionInfo = result.version;
|
||
const seamlessPath = HummingbirdProViewProvider.CURRENT_VERSION;
|
||
const isMatch = this._compareVersions(versionInfo, seamlessPath) > 0;
|
||
this._postMessage({
|
||
'type': "versionCheck",
|
||
'success': true,
|
||
'currentVersion': seamlessPath,
|
||
'latestVersion': versionInfo,
|
||
'hasUpdate': isMatch
|
||
});
|
||
} else {
|
||
this._postMessage({
|
||
'type': "versionCheck",
|
||
'success': false,
|
||
'currentVersion': HummingbirdProViewProvider.CURRENT_VERSION,
|
||
'error': result.error || "获取版本失败"
|
||
});
|
||
}
|
||
} catch (runningErr) {
|
||
this._postMessage({
|
||
'type': "versionCheck",
|
||
'success': false,
|
||
'currentVersion': HummingbirdProViewProvider.CURRENT_VERSION,
|
||
'error': runningErr.message || "请求失败"
|
||
});
|
||
}
|
||
}
|
||
_compareVersions(toggleArg, silentArg) {
|
||
const mapped = toggleArg.split('.').map(Number);
|
||
const mapped1 = silentArg.split('.').map(Number);
|
||
const beforeSwitch = Math.max(mapped.length, mapped1.length);
|
||
for (let count = 0; count < beforeSwitch; count++) {
|
||
const condition = mapped[count] || 0;
|
||
const condition1 = mapped1[count] || 0;
|
||
if (condition > condition1) {
|
||
return 1;
|
||
}
|
||
if (condition < condition1) {
|
||
return -1;
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
async _handleGetCursorRunningPath() {
|
||
try {
|
||
const platform = process.platform;
|
||
let filePath = "未找到";
|
||
let str = '';
|
||
const config = vscode.workspace.getConfiguration("hummingbird");
|
||
const configValue = config.get("cursorPath");
|
||
if (configValue && fs.existsSync(configValue)) {
|
||
filePath = configValue;
|
||
if (platform === "darwin") {
|
||
str = path.join(configValue, "Contents", "Resources", "app", "package.json");
|
||
} else {
|
||
str = path.join(configValue, "resources", "app", "package.json");
|
||
}
|
||
console.log("[蜂鸟Pro] 使用用户配置的路径:", configValue);
|
||
} else {
|
||
if (platform === "win32") {
|
||
try {
|
||
const {
|
||
stdout: manualErr
|
||
} = await execAsync("wmic process where \"name='Cursor.exe'\" get ExecutablePath /format:list 2>nul");
|
||
const matchResult = manualErr.match(/ExecutablePath=(.+)/);
|
||
if (matchResult && matchResult[1]) {
|
||
const trimmed = matchResult[1].trim();
|
||
filePath = path.dirname(trimmed);
|
||
str = path.join(filePath, "resources", "app", "package.json");
|
||
}
|
||
} catch (beforeErr) {
|
||
console.log("[蜂鸟Pro] WMIC 获取路径失败:", beforeErr);
|
||
}
|
||
if (filePath === "未找到") {
|
||
const condition = process.env.LOCALAPPDATA || '';
|
||
const items = [path.join(condition, "Programs", 'cursor'), path.join(condition, "cursor")];
|
||
for (const originalCode of items) {
|
||
const joinedPath = path.join(originalCode, "resources", "app", "package.json");
|
||
if (fs.existsSync(joinedPath)) {
|
||
filePath = originalCode;
|
||
str = joinedPath;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
if (platform === "darwin") {
|
||
filePath = (await this._getCursorInstallPath()) || "/Applications/Cursor.app";
|
||
str = path.join(filePath, "Contents", "Resources", 'app', "package.json");
|
||
} else {
|
||
const condition = process.env.HOME || '';
|
||
const items = ["/usr/share/cursor", path.join(condition, ".local", "share", "cursor")];
|
||
for (const backupDir of items) {
|
||
if (fs.existsSync(backupDir)) {
|
||
filePath = backupDir;
|
||
str = path.join(backupDir, "resources", 'app', "package.json");
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
const condition = str && fs.existsSync(str);
|
||
let str1 = '';
|
||
if (condition) {
|
||
try {
|
||
const fileContent = fs.readFileSync(str, "utf-8");
|
||
const parsed = JSON.parse(fileContent);
|
||
str1 = parsed.version || '';
|
||
console.log("[蜂鸟Pro] 从路径获取 Cursor 版本:", str1);
|
||
} catch (backupErr) {
|
||
console.log("[蜂鸟Pro] 读取 package.json 失败:", backupErr);
|
||
}
|
||
}
|
||
this._postMessage({
|
||
'type': 'cursorRunningPath',
|
||
'path': filePath,
|
||
'packageJsonPath': str,
|
||
'packageExists': condition,
|
||
'cursorVersion': str1
|
||
});
|
||
} catch (codeItem) {
|
||
this._postMessage({
|
||
'type': "cursorRunningPath",
|
||
'path': "获取失败: " + (codeItem.message || codeItem),
|
||
'packageJsonPath': '',
|
||
'packageExists': false,
|
||
'cursorVersion': ''
|
||
});
|
||
}
|
||
}
|
||
async _handleCheckUsageBeforeSwitch(silent) {
|
||
try {
|
||
const savedKey = this._context.globalState.get("hummingbird.key");
|
||
if (!savedKey) {
|
||
this._postMessage({
|
||
'type': "usageCheckResult",
|
||
'success': false,
|
||
'error': "未激活授权码"
|
||
});
|
||
return;
|
||
}
|
||
if (!silent) {
|
||
this._postMessage({
|
||
'type': "usageCheckResult",
|
||
'success': true,
|
||
'needConfirm': false
|
||
});
|
||
return;
|
||
}
|
||
const result1 = client_1.getApiUrl() + '/api/cursor-accounts/query?email=' + encodeURIComponent(silent) + "&refresh=false";
|
||
const seamlessBackup = await fetch(result1);
|
||
const result = await seamlessBackup.json();
|
||
if (result.success && result.data) {
|
||
const condition = result.data.usage || {};
|
||
const num = parseFloat(condition.totalCostUSD || 0);
|
||
if (num < 10) {
|
||
this._postMessage({
|
||
'type': "usageCheckResult",
|
||
'success': true,
|
||
'needConfirm': true,
|
||
'costUSD': num.toFixed(2),
|
||
'email': silent
|
||
});
|
||
} else {
|
||
this._postMessage({
|
||
'type': "usageCheckResult",
|
||
'success': true,
|
||
'needConfirm': false
|
||
});
|
||
}
|
||
} else {
|
||
this._postMessage({
|
||
'type': "usageCheckResult",
|
||
'success': true,
|
||
'needConfirm': false
|
||
});
|
||
}
|
||
} catch (execOut) {
|
||
this._postMessage({
|
||
'type': 'usageCheckResult',
|
||
'success': true,
|
||
'needConfirm': false
|
||
});
|
||
}
|
||
}
|
||
async _handleManualSeamlessSwitch() {
|
||
try {
|
||
// 获取当前选择的号池
|
||
const selectedPool = this._context.globalState.get("hummingbird.selectedPool") || "auto";
|
||
const savedKey = selectedPool === "pro"
|
||
? this._context.globalState.get("hummingbird.proKey")
|
||
: this._context.globalState.get("hummingbird.autoKey");
|
||
|
||
if (!savedKey) {
|
||
this._postMessage({
|
||
'type': "manualSeamlessSwitched",
|
||
'success': false,
|
||
'error': selectedPool === "pro" ? "未激活Pro授权码" : "未激活Auto授权码"
|
||
});
|
||
return;
|
||
}
|
||
const switchResult = await client_1.switchSeamlessToken(savedKey);
|
||
if (switchResult.switched) {
|
||
if (switchResult.email) {
|
||
await this._context.globalState.update("hummingbird.seamlessCurrentAccount", switchResult.email);
|
||
}
|
||
// 写入 token 到本地 Cursor 存储
|
||
if (switchResult.data) {
|
||
try {
|
||
await this._writeAccountToLocal(switchResult.data);
|
||
console.log("[蜂鸟Pro] 换号成功,已写入新 token");
|
||
} catch (writeErr) {
|
||
console.error("[蜂鸟Pro] 写入 token 失败:", writeErr);
|
||
}
|
||
}
|
||
this._postMessage({
|
||
'type': "manualSeamlessSwitched",
|
||
'success': true,
|
||
'email': switchResult.email,
|
||
'switchRemaining': switchResult.switchRemaining
|
||
});
|
||
} else {
|
||
const condition = switchResult.message || switchResult.error || "换号失败";
|
||
this._postMessage({
|
||
'type': "manualSeamlessSwitched",
|
||
'success': false,
|
||
'error': condition
|
||
});
|
||
}
|
||
} catch (tmpErr6) {
|
||
const condition = tmpErr6?.message || "连接服务器失败";
|
||
this._postMessage({
|
||
'type': "manualSeamlessSwitched",
|
||
'success': false,
|
||
'error': condition
|
||
});
|
||
}
|
||
}
|
||
async _handleSelectPool(pool) {
|
||
// 切换号池选择 (auto / pro)
|
||
if (pool === "auto" || pool === "pro") {
|
||
await this._context.globalState.update("hummingbird.selectedPool", pool);
|
||
this._postMessage({
|
||
'type': "poolSelected",
|
||
'pool': pool
|
||
});
|
||
console.log("[蜂鸟Pro] 切换号池:", pool);
|
||
}
|
||
}
|
||
async _handleClearKey(keyType) {
|
||
// 清除指定类型的密钥
|
||
if (keyType === "auto") {
|
||
await this._context.globalState.update("hummingbird.autoKey", undefined);
|
||
await this._context.globalState.update("hummingbird.autoExpireDate", undefined);
|
||
await this._context.globalState.update("hummingbird.autoMergedCount", undefined);
|
||
} else if (keyType === "pro") {
|
||
await this._context.globalState.update("hummingbird.proKey", undefined);
|
||
await this._context.globalState.update("hummingbird.proQuota", undefined);
|
||
await this._context.globalState.update("hummingbird.proQuotaUsed", undefined);
|
||
await this._context.globalState.update("hummingbird.proMergedCount", undefined);
|
||
}
|
||
this._postMessage({
|
||
'type': "keyCleared",
|
||
'keyType': keyType
|
||
});
|
||
// 刷新状态
|
||
this._sendState();
|
||
this._checkKeyStatus();
|
||
}
|
||
async _handleGetCursorPath() {
|
||
try {
|
||
const platform = process.platform;
|
||
let str = '';
|
||
let str1 = '';
|
||
if (platform === "win32") {
|
||
try {
|
||
const {
|
||
stdout: patchErr
|
||
} = await execAsync("wmic process where \"name='Cursor.exe'\" get ExecutablePath /format:list 2>nul");
|
||
const matchResult = patchErr.match(/ExecutablePath=(.+)/);
|
||
if (matchResult && matchResult[1]) {
|
||
const trimmed = matchResult[1].trim();
|
||
str = path.dirname(trimmed);
|
||
}
|
||
} catch (shellOut) {
|
||
try {
|
||
const {
|
||
stdout: lineContent
|
||
} = await execAsync('powershell -Command "Get-Process Cursor -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty Path"');
|
||
if (lineContent.trim()) {
|
||
str = path.dirname(lineContent.trim());
|
||
}
|
||
} catch (restoreErr2) {
|
||
console.warn("[蜂鸟Pro] 获取进程路径失败:", restoreErr2);
|
||
}
|
||
}
|
||
const condition = process.env.APPDATA || '';
|
||
str1 = path.join(condition, "Cursor");
|
||
} else {
|
||
if (platform === "darwin") {
|
||
try {
|
||
const {
|
||
stdout: e34
|
||
} = await execAsync("ps aux | grep -i \"[C]ursor\" | head -1 | awk '{print $11}'");
|
||
if (e34.trim()) {
|
||
const trimmed = e34.trim();
|
||
const matchResult = trimmed.match(/(.+\.app)/);
|
||
if (matchResult) {
|
||
str = matchResult[1];
|
||
} else {
|
||
str = path.dirname(trimmed);
|
||
}
|
||
}
|
||
} catch (toggleErr2) {
|
||
console.warn("[蜂鸟Pro] 获取进程路径失败:", toggleErr2);
|
||
}
|
||
const condition = process.env.HOME || '';
|
||
str1 = path.join(condition, 'Library', "Application Support", "Cursor");
|
||
} else {
|
||
try {
|
||
const {
|
||
stdout: e35
|
||
} = await execAsync("ps aux | grep -i \"[c]ursor\" | head -1 | awk '{print $11}'");
|
||
if (e35.trim()) {
|
||
str = path.dirname(e35.trim());
|
||
}
|
||
} catch (seamlessErr2) {
|
||
console.warn("[蜂鸟Pro] 获取进程路径失败:", seamlessErr2);
|
||
}
|
||
const condition = process.env.HOME || '';
|
||
str1 = path.join(condition, ".config", "Cursor");
|
||
}
|
||
}
|
||
if (!str) {
|
||
str = "未检测到运行中的Cursor进程";
|
||
}
|
||
let str2 = '';
|
||
if (str && !str.includes("未检测")) {
|
||
if (platform === "win32") {
|
||
str2 = path.join(str, 'resources', "app", 'out', 'vs', 'workbench', "workbench.desktop.main.js");
|
||
} else {
|
||
if (platform === "darwin") {
|
||
str2 = path.join(str, "Contents", "Resources", "app", "out", 'vs', "workbench", 'workbench.desktop.main.js');
|
||
} else {
|
||
str2 = path.join(str, "resources", "app", "out", 'vs', "workbench", "workbench.desktop.main.js");
|
||
}
|
||
}
|
||
if (!fs.existsSync(str2)) {
|
||
str2 = (await this._getWorkbenchPathAsync()) || "未找到";
|
||
}
|
||
} else {
|
||
str2 = (await this._getWorkbenchPathAsync()) || "未找到";
|
||
}
|
||
const value = str && !str.includes("未检测") ? fs.existsSync(str) : false;
|
||
const value1 = str1 ? fs.existsSync(str1) : false;
|
||
this._postMessage({
|
||
'type': "cursorPath",
|
||
'cursorPath': value ? str : str || "未找到",
|
||
'dataPath': value1 ? str1 : "未找到",
|
||
'workbenchPath': str2,
|
||
'platform': platform
|
||
});
|
||
} catch (e37) {
|
||
this._postMessage({
|
||
'type': "cursorPath",
|
||
'cursorPath': "获取失败",
|
||
'dataPath': '获取失败',
|
||
'workbenchPath': "获取失败",
|
||
'error': e37.message
|
||
});
|
||
}
|
||
}
|
||
async _loadAccountsFromDB() {
|
||
try {
|
||
const patchedContent = account_1.getCursorPaths();
|
||
const {
|
||
dbPath: psOut3
|
||
} = patchedContent;
|
||
if (!fs.existsSync(psOut3)) {
|
||
return [];
|
||
}
|
||
const workbenchContent = await sqlite_1.sqliteGet(psOut3, "cursorAuth/accessToken");
|
||
const patchContent = await sqlite_1.sqliteGet(psOut3, "cursorAuth/refreshToken");
|
||
const email = await sqlite_1.sqliteGet(psOut3, "cursorAuth/cachedEmail");
|
||
if (workbenchContent && email) {
|
||
return [{
|
||
'email': email,
|
||
'access_token': workbenchContent,
|
||
'refresh_token': patchContent || workbenchContent
|
||
}];
|
||
}
|
||
return [];
|
||
} catch (e38) {
|
||
console.error("[蜂鸟Pro] 读取账号失败:", e38);
|
||
return [];
|
||
}
|
||
}
|
||
async _sendState() {
|
||
// 获取双密钥状态
|
||
const autoKey = this._context.globalState.get("hummingbird.autoKey");
|
||
const proKey = this._context.globalState.get("hummingbird.proKey");
|
||
const selectedPool = this._context.globalState.get("hummingbird.selectedPool") || "auto";
|
||
|
||
// Auto 密钥信息
|
||
const autoExpireDate = this._context.globalState.get('hummingbird.autoExpireDate');
|
||
const autoMergedCount = this._context.globalState.get("hummingbird.autoMergedCount") || 0;
|
||
|
||
// Pro 密钥信息
|
||
const proQuota = this._context.globalState.get("hummingbird.proQuota");
|
||
const proQuotaUsed = this._context.globalState.get("hummingbird.proQuotaUsed") || 0;
|
||
const proMergedCount = this._context.globalState.get("hummingbird.proMergedCount") || 0;
|
||
|
||
// 兼容旧字段
|
||
const savedKey = this._context.globalState.get("hummingbird.key");
|
||
const expireDate = this._context.globalState.get('hummingbird.expireDate');
|
||
const switchData = this._context.globalState.get("hummingbird.switchRemaining");
|
||
const switchData1 = this._context.globalState.get("hummingbird.switchLimit");
|
||
|
||
const cursorversionResult = await this._getCursorVersion();
|
||
const restoreCode = client_1.getOnlineStatus();
|
||
|
||
this._postMessage({
|
||
'type': "state",
|
||
// 双密钥状态
|
||
'autoKey': autoKey || '',
|
||
'proKey': proKey || '',
|
||
'selectedPool': selectedPool,
|
||
'autoExpireDate': autoExpireDate || '',
|
||
'autoMergedCount': autoMergedCount,
|
||
'proQuota': proQuota || 0,
|
||
'proQuotaUsed': proQuotaUsed,
|
||
'proQuotaRemaining': (proQuota || 0) - proQuotaUsed,
|
||
'proMergedCount': proMergedCount,
|
||
// 激活状态:任一密钥有效即为已激活
|
||
'isActivated': !!(autoKey || proKey || savedKey),
|
||
// 兼容旧字段
|
||
'key': savedKey || autoKey || '',
|
||
'expireDate': expireDate || autoExpireDate || '',
|
||
'switchRemaining': switchData ?? 0,
|
||
'switchLimit': switchData1 ?? 100,
|
||
'cursorVersion': cursorversionResult,
|
||
'isOnline': restoreCode
|
||
});
|
||
}
|
||
async _handleRetryConnect() {
|
||
try {
|
||
const savedKey = this._context.globalState.get("hummingbird.key");
|
||
if (savedKey) {
|
||
await client_1.verifyKey(savedKey);
|
||
} else {
|
||
const result = client_1.getApiUrl() + '/api/announcements/latest';
|
||
await fetch(result, {
|
||
'method': 'GET'
|
||
});
|
||
}
|
||
await this._sendState();
|
||
this._postMessage({
|
||
'type': "networkStatus",
|
||
'online': true
|
||
});
|
||
} catch (execErr) {
|
||
console.error("[蜂鸟Pro] Retry connect failed:", execErr);
|
||
this._postMessage({
|
||
'type': "networkStatus",
|
||
'online': false
|
||
});
|
||
}
|
||
}
|
||
async _getCursorVersion() {
|
||
try {
|
||
const platform = process.platform;
|
||
const items = [];
|
||
const cursorPath = await this._getCursorInstallPath();
|
||
if (cursorPath) {
|
||
if (platform === "darwin") {
|
||
items.push(path.join(cursorPath, "Contents", "Resources", "app", 'package.json'));
|
||
} else {
|
||
items.push(path.join(cursorPath, "resources", 'app', "package.json"));
|
||
}
|
||
}
|
||
if (platform === "win32") {
|
||
const condition = process.env.LOCALAPPDATA || '';
|
||
const condition1 = process.env.USERPROFILE || '';
|
||
const condition2 = process.env.ProgramFiles || "C:\\Program Files";
|
||
const condition3 = process.env['ProgramFiles(x86)'] || "C:\\Program Files (x86)";
|
||
items.push(path.join(condition, "Programs", "Cursor", "resources", "app", "package.json"), path.join(condition, "Programs", "cursor", "resources", 'app', "package.json"), path.join(condition1, "AppData", "Local", "Programs", "Cursor", "resources", "app", "package.json"), path.join(condition2, "Cursor", "resources", 'app', "package.json"), path.join(condition2, "cursor", "resources", "app", "package.json"), path.join(condition3, "Cursor", "resources", "app", "package.json"));
|
||
} else {
|
||
if (platform === "darwin") {
|
||
items.push("/Applications/Cursor.app/Contents/Resources/app/package.json");
|
||
} else {
|
||
const condition = process.env.HOME || '';
|
||
items.push("/usr/share/cursor/resources/app/package.json", "/opt/Cursor/resources/app/package.json", "/opt/cursor/resources/app/package.json", path.join(condition, ".local", 'share', "cursor", "resources", 'app', "package.json"));
|
||
}
|
||
}
|
||
for (const seamlessCode of items) {
|
||
try {
|
||
if (fs.existsSync(seamlessCode)) {
|
||
const fileContent = fs.readFileSync(seamlessCode, "utf-8");
|
||
const parsed = JSON.parse(fileContent);
|
||
if (parsed.version) {
|
||
console.log("[蜂鸟Pro] 找到 Cursor 版本:", parsed.version, "路径:", seamlessCode);
|
||
return parsed.version;
|
||
}
|
||
}
|
||
} catch (fsErr) {
|
||
console.log("[蜂鸟Pro] 尝试路径失败:", seamlessCode, fsErr);
|
||
}
|
||
}
|
||
try {
|
||
const module = require("vscode");
|
||
if (module.version) {
|
||
console.log("[蜂鸟Pro] 使用 VS Code API 获取版本:", module.version);
|
||
return module.version;
|
||
}
|
||
} catch (cmdOut2) {}
|
||
console.log("[蜂鸟Pro] 未找到 Cursor 版本,尝试的路径:", items);
|
||
return '未知';
|
||
} catch (finalErr) {
|
||
console.error("[蜂鸟Pro] 获取 Cursor 版本失败:", finalErr);
|
||
return '未知';
|
||
}
|
||
}
|
||
_postMessage(message) {
|
||
this._view?.webview.postMessage(message);
|
||
}
|
||
_getNonce() {
|
||
let str = '';
|
||
const items = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||
for (let count = 0; count < 32; count++) {
|
||
str += items.charAt(Math.floor(Math.random() * items.length));
|
||
}
|
||
return str;
|
||
}
|
||
_getHtmlContent(lineStr) {
|
||
const nonce = this._getNonce();
|
||
// 从外部文件读取 HTML
|
||
const htmlPath = path.join(__dirname, 'panel.html');
|
||
let html = fs.readFileSync(htmlPath, 'utf8');
|
||
// 替换占位符
|
||
html = html.replace(/\{\{NONCE\}\}/g, nonce);
|
||
html = html.replace(/\{\{CSP_SOURCE\}\}/g, lineStr.cspSource);
|
||
return html;
|
||
}
|
||
}
|
||
// ========== 原始内嵌HTML代码已移至 panel.html ==========
|
||
// 以下为占位注释,保持文件结构不变
|
||
|
||
exports.HummingbirdProViewProvider = HummingbirdProViewProvider;
|
||
HummingbirdProViewProvider.CURRENT_VERSION = '2.0.0'; |