## 当前状态 - 插件界面已完成重命名 (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>
200 lines
7.1 KiB
JavaScript
200 lines
7.1 KiB
JavaScript
/**
|
||
* Cursor 官方用量接口测试脚本
|
||
*/
|
||
const https = require('https');
|
||
|
||
const TOKEN = 'user_01KCP4PQM80HPAZA7NY8RFR1V6::eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhdXRoMHx1c2VyXzAxS0NQNFBRTTgwSFBBWkE3Tlk4UkZSMVY2IiwidGltZSI6IjE3NjU5NzQ3MTEiLCJyYW5kb21uZXNzIjoiNzMyNGMwOWItZTk2ZS00Y2YzIiwiZXhwIjoxNzcxMTU4NzExLCJpc3MiOiJodHRwczovL2F1dGhlbnRpY2F0aW9uLmN1cnNvci5zaCIsInNjb3BlIjoib3BlbmlkIHByb2ZpbGUgZW1haWwgb2ZmbGluZV9hY2Nlc3MiLCJhdWQiOiJodHRwczovL2N1cnNvci5jb20iLCJ0eXBlIjoid2ViIn0.oy_GRvz-3hIUj5BlXahE1QeTb5NuOrM-3pqemw_FEQw';
|
||
|
||
const COOKIE = `WorkosCursorSessionToken=${TOKEN}`;
|
||
|
||
function request(options, postData = null) {
|
||
return new Promise((resolve, reject) => {
|
||
const req = https.request(options, (res) => {
|
||
let data = '';
|
||
res.on('data', chunk => data += chunk);
|
||
res.on('end', () => {
|
||
try {
|
||
resolve({ status: res.statusCode, data: JSON.parse(data) });
|
||
} catch (e) {
|
||
resolve({ status: res.statusCode, data: data });
|
||
}
|
||
});
|
||
});
|
||
req.on('error', reject);
|
||
if (postData) req.write(postData);
|
||
req.end();
|
||
});
|
||
}
|
||
|
||
function getHeaders(isPost = false) {
|
||
const headers = {
|
||
'Cookie': COOKIE,
|
||
'accept': '*/*',
|
||
'origin': 'https://cursor.com',
|
||
'referer': 'https://cursor.com/dashboard'
|
||
};
|
||
if (isPost) {
|
||
headers['content-type'] = 'application/json';
|
||
}
|
||
return headers;
|
||
}
|
||
|
||
async function testGetMe() {
|
||
console.log('\n========== 1. 获取用户信息 (GET /api/dashboard/get-me) ==========');
|
||
const result = await request({
|
||
hostname: 'cursor.com',
|
||
path: '/api/dashboard/get-me',
|
||
method: 'GET',
|
||
headers: getHeaders()
|
||
});
|
||
console.log('Status:', result.status);
|
||
console.log('Response:', JSON.stringify(result.data, null, 2));
|
||
return result.data;
|
||
}
|
||
|
||
async function testUsageSummary() {
|
||
console.log('\n========== 2. 获取用量摘要 (GET /api/usage-summary) ==========');
|
||
const result = await request({
|
||
hostname: 'cursor.com',
|
||
path: '/api/usage-summary',
|
||
method: 'GET',
|
||
headers: getHeaders()
|
||
});
|
||
console.log('Status:', result.status);
|
||
console.log('Response:', JSON.stringify(result.data, null, 2));
|
||
return result.data;
|
||
}
|
||
|
||
async function testBillingCycle() {
|
||
console.log('\n========== 3. 获取计费周期 (POST /api/dashboard/get-current-billing-cycle) ==========');
|
||
const result = await request({
|
||
hostname: 'cursor.com',
|
||
path: '/api/dashboard/get-current-billing-cycle',
|
||
method: 'POST',
|
||
headers: getHeaders(true)
|
||
}, '{}');
|
||
console.log('Status:', result.status);
|
||
console.log('Response:', JSON.stringify(result.data, null, 2));
|
||
return result.data;
|
||
}
|
||
|
||
async function testFilteredUsage(userId, startMs, endMs) {
|
||
console.log('\n========== 4. 获取使用事件 (POST /api/dashboard/get-filtered-usage-events) ==========');
|
||
// 尝试不同的参数组合
|
||
const body = JSON.stringify({
|
||
startDate: startMs,
|
||
endDate: endMs,
|
||
userId: userId || undefined,
|
||
page: 1,
|
||
pageSize: 5
|
||
});
|
||
console.log('Request body:', body);
|
||
const result = await request({
|
||
hostname: 'cursor.com',
|
||
path: '/api/dashboard/get-filtered-usage-events',
|
||
method: 'POST',
|
||
headers: getHeaders(true)
|
||
}, body);
|
||
console.log('Status:', result.status);
|
||
console.log('Response:', JSON.stringify(result.data, null, 2));
|
||
|
||
if (result.data && result.data.totalUsageEventsCount !== undefined) {
|
||
console.log('\n>>> 总对话次数 (totalUsageEventsCount):', result.data.totalUsageEventsCount);
|
||
}
|
||
return result.data;
|
||
}
|
||
|
||
async function testFilteredUsageNoUser(startMs, endMs) {
|
||
console.log('\n========== 4b. 获取使用事件 - 不带userId ==========');
|
||
const body = JSON.stringify({
|
||
startDate: startMs,
|
||
endDate: endMs,
|
||
page: 1,
|
||
pageSize: 5
|
||
});
|
||
console.log('Request body:', body);
|
||
const result = await request({
|
||
hostname: 'cursor.com',
|
||
path: '/api/dashboard/get-filtered-usage-events',
|
||
method: 'POST',
|
||
headers: getHeaders(true)
|
||
}, body);
|
||
console.log('Status:', result.status);
|
||
console.log('Response:', JSON.stringify(result.data, null, 2));
|
||
|
||
if (result.data && result.data.totalUsageEventsCount !== undefined) {
|
||
console.log('\n>>> 总对话次数 (totalUsageEventsCount):', result.data.totalUsageEventsCount);
|
||
}
|
||
return result.data;
|
||
}
|
||
|
||
async function testAggregatedUsage(startMs) {
|
||
console.log('\n========== 5. 获取聚合用量 (POST /api/dashboard/get-aggregated-usage-events) ==========');
|
||
const body = JSON.stringify({
|
||
startDate: parseInt(startMs)
|
||
});
|
||
const result = await request({
|
||
hostname: 'cursor.com',
|
||
path: '/api/dashboard/get-aggregated-usage-events',
|
||
method: 'POST',
|
||
headers: getHeaders(true)
|
||
}, body);
|
||
console.log('Status:', result.status);
|
||
console.log('Response:', JSON.stringify(result.data, null, 2));
|
||
return result.data;
|
||
}
|
||
|
||
async function main() {
|
||
console.log('====================================================');
|
||
console.log(' Cursor 官方用量接口测试');
|
||
console.log('====================================================');
|
||
console.log('Cookie:', COOKIE.substring(0, 50) + '...');
|
||
|
||
try {
|
||
// 1. 获取用户信息
|
||
const me = await testGetMe();
|
||
const userId = me.userId;
|
||
console.log('\n>>> 用户ID:', userId);
|
||
console.log('>>> 邮箱:', me.email);
|
||
console.log('>>> 团队ID:', me.teamId);
|
||
|
||
// 2. 获取用量摘要
|
||
const summary = await testUsageSummary();
|
||
console.log('\n>>> 会员类型:', summary.membershipType);
|
||
console.log('>>> 计费周期:', summary.billingCycleStart, '至', summary.billingCycleEnd);
|
||
if (summary.individualUsage) {
|
||
const plan = summary.individualUsage.plan;
|
||
console.log('>>> 套餐用量:', plan.used, '/', plan.limit, '(剩余', plan.remaining, ')');
|
||
}
|
||
|
||
// 3. 获取计费周期
|
||
const billing = await testBillingCycle();
|
||
const startMs = billing.startDateEpochMillis;
|
||
const endMs = billing.endDateEpochMillis;
|
||
console.log('\n>>> 计费开始:', new Date(parseInt(startMs)).toISOString());
|
||
console.log('>>> 计费结束:', new Date(parseInt(endMs)).toISOString());
|
||
|
||
// 4. 获取使用事件 - 不带 userId
|
||
await testFilteredUsageNoUser(startMs, endMs);
|
||
|
||
// 4b. 如果有 userId,也试试带 userId 的
|
||
if (userId) {
|
||
await testFilteredUsage(userId, startMs, endMs);
|
||
}
|
||
|
||
// 5. 获取聚合用量
|
||
if (startMs) {
|
||
await testAggregatedUsage(startMs);
|
||
}
|
||
|
||
console.log('\n====================================================');
|
||
console.log(' 测试完成');
|
||
console.log('====================================================');
|
||
|
||
} catch (error) {
|
||
console.error('测试出错:', error.message);
|
||
}
|
||
}
|
||
|
||
main();
|