cursor-usage 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +209 -0
- package/dist/_consts.d.ts +13 -0
- package/dist/_consts.js +16 -0
- package/dist/_types.d.ts +60 -0
- package/dist/_types.js +4 -0
- package/dist/args-parser.d.ts +32 -0
- package/dist/args-parser.js +104 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.js +162 -0
- package/dist/commands.d.ts +42 -0
- package/dist/commands.js +273 -0
- package/dist/data-loader.d.ts +13 -0
- package/dist/data-loader.js +134 -0
- package/dist/event-loader.d.ts +82 -0
- package/dist/event-loader.js +350 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +11 -0
- package/dist/logger.d.ts +11 -0
- package/dist/logger.js +34 -0
- package/dist/table-formatter.d.ts +16 -0
- package/dist/table-formatter.js +40 -0
- package/package.json +53 -0
package/dist/commands.js
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI command handlers
|
|
3
|
+
*/
|
|
4
|
+
import { fetchUsageEvents, groupByDay, calculateDailyStats, groupByMonth, calculateMonthlyStats, formatMonthlyStatsTable, groupByWeek, calculateWeeklyStats, formatWeeklyStatsTable, calculateModelBreakdown, formatModelBreakdownTable, } from './event-loader';
|
|
5
|
+
import { createTable } from './table-formatter';
|
|
6
|
+
import { logger } from './logger';
|
|
7
|
+
/**
|
|
8
|
+
* Convert stats to JSON
|
|
9
|
+
*/
|
|
10
|
+
export function statsToJSON(command, stats, events, options = {}) {
|
|
11
|
+
const totalTokens = events.reduce((sum, e) => sum + e.tokens, 0);
|
|
12
|
+
const totalCost = events.reduce((sum, e) => sum + (e.cost || 0), 0);
|
|
13
|
+
const totalEvents = events.length;
|
|
14
|
+
const data = {
|
|
15
|
+
command,
|
|
16
|
+
generatedAt: new Date().toISOString(),
|
|
17
|
+
period: options.period || command,
|
|
18
|
+
timeRange: options.timeRange,
|
|
19
|
+
summary: {
|
|
20
|
+
totalEvents,
|
|
21
|
+
totalTokens,
|
|
22
|
+
totalCost: parseFloat(totalCost.toFixed(2)),
|
|
23
|
+
},
|
|
24
|
+
data: stats,
|
|
25
|
+
};
|
|
26
|
+
if (options.breakdown) {
|
|
27
|
+
const breakdown = calculateModelBreakdown(events);
|
|
28
|
+
data.breakdown = Array.from(breakdown.values()).map((model) => ({
|
|
29
|
+
model: model.model,
|
|
30
|
+
count: model.count,
|
|
31
|
+
totalTokens: model.totalTokens,
|
|
32
|
+
inputTokens: model.inputTokens,
|
|
33
|
+
outputTokens: model.outputTokens,
|
|
34
|
+
totalCost: parseFloat(model.totalCost.toFixed(2)),
|
|
35
|
+
tokenPercent: parseFloat(((model.totalTokens / totalTokens) * 100).toFixed(2)),
|
|
36
|
+
costPercent: parseFloat(((model.totalCost / totalCost) * 100).toFixed(2)),
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
return JSON.stringify(data, null, 2);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Display model breakdown
|
|
43
|
+
*/
|
|
44
|
+
function displayBreakdown(events, title = 'MODEL BREAKDOWN') {
|
|
45
|
+
const breakdown = calculateModelBreakdown(events);
|
|
46
|
+
const totalTokens = events.reduce((sum, e) => sum + e.tokens, 0);
|
|
47
|
+
const totalCost = events.reduce((sum, e) => sum + (e.cost || 0), 0);
|
|
48
|
+
logger.log('\n' + '='.repeat(100));
|
|
49
|
+
logger.log(title);
|
|
50
|
+
logger.log('='.repeat(100) + '\n');
|
|
51
|
+
const tableData = formatModelBreakdownTable(breakdown, totalTokens, totalCost);
|
|
52
|
+
const table = createTable(['Model', 'Events', 'Total Tokens', 'Cost', 'Token %', 'Cost %'], tableData);
|
|
53
|
+
logger.log(table + '\n');
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Show daily usage report for a date range
|
|
57
|
+
*/
|
|
58
|
+
export async function showDailyReport(credentials, days = 7, options = {}, outputJson = false) {
|
|
59
|
+
let startDate = options.startDate;
|
|
60
|
+
let endDate = options.endDate;
|
|
61
|
+
if (!startDate || !endDate) {
|
|
62
|
+
endDate = endDate || new Date();
|
|
63
|
+
startDate = startDate || new Date();
|
|
64
|
+
startDate.setDate(startDate.getDate() - days);
|
|
65
|
+
// Set to start of day
|
|
66
|
+
startDate.setHours(0, 0, 0, 0);
|
|
67
|
+
endDate.setHours(23, 59, 59, 999);
|
|
68
|
+
}
|
|
69
|
+
const events = await fetchUsageEvents(credentials, startDate, endDate);
|
|
70
|
+
if (events.length === 0) {
|
|
71
|
+
logger.warn('No usage events found for this period');
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
logger.log('');
|
|
75
|
+
// Group and calculate stats
|
|
76
|
+
const grouped = groupByDay(events);
|
|
77
|
+
const stats = calculateDailyStats(grouped);
|
|
78
|
+
// Display table
|
|
79
|
+
logger.log('\n' + '='.repeat(100));
|
|
80
|
+
logger.log(`DAILY USAGE REPORT (Last ${days} days)`);
|
|
81
|
+
logger.log('='.repeat(100) + '\n');
|
|
82
|
+
const tableData = stats.map((day) => {
|
|
83
|
+
const modelList = Array.from(day.models.entries())
|
|
84
|
+
.map(([model, count]) => `${model}(${count})`)
|
|
85
|
+
.join(', ');
|
|
86
|
+
return [
|
|
87
|
+
day.date,
|
|
88
|
+
day.eventCount.toString(),
|
|
89
|
+
day.totalTokens.toLocaleString(),
|
|
90
|
+
day.inputTokens.toLocaleString(),
|
|
91
|
+
day.outputTokens.toLocaleString(),
|
|
92
|
+
`$${day.totalCost.toFixed(2)}`,
|
|
93
|
+
modelList || 'N/A',
|
|
94
|
+
];
|
|
95
|
+
});
|
|
96
|
+
const table = createTable(['Date', 'Events', 'Total Tokens', 'Input', 'Output', 'Cost', 'Models'], tableData);
|
|
97
|
+
logger.log(table);
|
|
98
|
+
// Summary
|
|
99
|
+
const totalEvents = stats.reduce((sum, day) => sum + day.eventCount, 0);
|
|
100
|
+
const totalTokens = stats.reduce((sum, day) => sum + day.totalTokens, 0);
|
|
101
|
+
const totalCost = stats.reduce((sum, day) => sum + day.totalCost, 0);
|
|
102
|
+
logger.log('\n' + '='.repeat(100));
|
|
103
|
+
logger.log('SUMMARY');
|
|
104
|
+
logger.log('='.repeat(100));
|
|
105
|
+
logger.log(`Total Events: ${totalEvents}`);
|
|
106
|
+
logger.log(`Total Tokens: ${totalTokens.toLocaleString()}`);
|
|
107
|
+
logger.log(`Total Cost: $${totalCost.toFixed(2)}`);
|
|
108
|
+
logger.log('='.repeat(100) + '\n');
|
|
109
|
+
// Show breakdown if requested
|
|
110
|
+
if (options.breakdown) {
|
|
111
|
+
displayBreakdown(events, 'PER-MODEL BREAKDOWN');
|
|
112
|
+
}
|
|
113
|
+
// Output JSON if requested
|
|
114
|
+
if (outputJson) {
|
|
115
|
+
const jsonOutput = statsToJSON('daily', stats, events, { breakdown: options.breakdown, period: `last ${days} days` });
|
|
116
|
+
console.log(jsonOutput);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Show monthly usage report
|
|
121
|
+
*/
|
|
122
|
+
export async function showMonthlyReport(credentials, months = 3, options = {}, outputJson = false) {
|
|
123
|
+
let startDate = options.startDate;
|
|
124
|
+
let endDate = options.endDate;
|
|
125
|
+
if (!startDate || !endDate) {
|
|
126
|
+
endDate = endDate || new Date();
|
|
127
|
+
startDate = startDate || new Date();
|
|
128
|
+
startDate.setMonth(startDate.getMonth() - months);
|
|
129
|
+
// Set to start of month
|
|
130
|
+
startDate.setDate(1);
|
|
131
|
+
startDate.setHours(0, 0, 0, 0);
|
|
132
|
+
endDate.setHours(23, 59, 59, 999);
|
|
133
|
+
}
|
|
134
|
+
const events = await fetchUsageEvents(credentials, startDate, endDate);
|
|
135
|
+
if (events.length === 0) {
|
|
136
|
+
logger.warn('No usage events found for this period');
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
logger.log('');
|
|
140
|
+
// Group and calculate stats
|
|
141
|
+
const grouped = groupByMonth(events);
|
|
142
|
+
const stats = calculateMonthlyStats(grouped);
|
|
143
|
+
// Display table
|
|
144
|
+
logger.log('\n' + '='.repeat(100));
|
|
145
|
+
logger.log(`MONTHLY USAGE REPORT (Last ${months} months)`);
|
|
146
|
+
logger.log('='.repeat(100) + '\n');
|
|
147
|
+
const tableData = formatMonthlyStatsTable(stats);
|
|
148
|
+
const table = createTable(['Month', 'Events', 'Total Tokens', 'Input', 'Output', 'Cost', 'Models'], tableData);
|
|
149
|
+
logger.log(table);
|
|
150
|
+
// Summary
|
|
151
|
+
const totalEvents = stats.reduce((sum, month) => sum + month.eventCount, 0);
|
|
152
|
+
const totalTokens = stats.reduce((sum, month) => sum + month.totalTokens, 0);
|
|
153
|
+
const totalCost = stats.reduce((sum, month) => sum + month.totalCost, 0);
|
|
154
|
+
logger.log('\n' + '='.repeat(100));
|
|
155
|
+
logger.log('SUMMARY');
|
|
156
|
+
logger.log('='.repeat(100));
|
|
157
|
+
logger.log(`Total Events: ${totalEvents}`);
|
|
158
|
+
logger.log(`Total Tokens: ${totalTokens.toLocaleString()}`);
|
|
159
|
+
logger.log(`Total Cost: $${totalCost.toFixed(2)}`);
|
|
160
|
+
logger.log(`Average per month: ${(totalTokens / Math.max(1, stats.length)).toLocaleString()} tokens, $${(totalCost / Math.max(1, stats.length)).toFixed(2)}`);
|
|
161
|
+
logger.log('='.repeat(100) + '\n');
|
|
162
|
+
// Show breakdown if requested
|
|
163
|
+
if (options.breakdown) {
|
|
164
|
+
displayBreakdown(events, 'PER-MODEL BREAKDOWN');
|
|
165
|
+
}
|
|
166
|
+
// Output JSON if requested
|
|
167
|
+
if (outputJson) {
|
|
168
|
+
const jsonOutput = statsToJSON('monthly', stats, events, { breakdown: options.breakdown, period: `last ${months} months` });
|
|
169
|
+
console.log(jsonOutput);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Show weekly usage report
|
|
174
|
+
*/
|
|
175
|
+
export async function showWeeklyReport(credentials, weeks = 4, options = {}, outputJson = false) {
|
|
176
|
+
let startDate = options.startDate;
|
|
177
|
+
let endDate = options.endDate;
|
|
178
|
+
if (!startDate || !endDate) {
|
|
179
|
+
endDate = endDate || new Date();
|
|
180
|
+
startDate = startDate || new Date();
|
|
181
|
+
startDate.setDate(startDate.getDate() - weeks * 7);
|
|
182
|
+
// Set to start of day
|
|
183
|
+
startDate.setHours(0, 0, 0, 0);
|
|
184
|
+
endDate.setHours(23, 59, 59, 999);
|
|
185
|
+
}
|
|
186
|
+
const events = await fetchUsageEvents(credentials, startDate, endDate);
|
|
187
|
+
if (events.length === 0) {
|
|
188
|
+
logger.warn('No usage events found for this period');
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
logger.log('');
|
|
192
|
+
// Group and calculate stats
|
|
193
|
+
const grouped = groupByWeek(events);
|
|
194
|
+
const stats = calculateWeeklyStats(grouped);
|
|
195
|
+
// Display table
|
|
196
|
+
logger.log('\n' + '='.repeat(120));
|
|
197
|
+
logger.log(`WEEKLY USAGE REPORT (Last ${weeks} weeks)`);
|
|
198
|
+
logger.log('='.repeat(120) + '\n');
|
|
199
|
+
const tableData = formatWeeklyStatsTable(stats);
|
|
200
|
+
const table = createTable(['Week', 'Events', 'Total Tokens', 'Input', 'Output', 'Cost', 'Models'], tableData);
|
|
201
|
+
logger.log(table);
|
|
202
|
+
// Summary
|
|
203
|
+
const totalEvents = stats.reduce((sum, week) => sum + week.eventCount, 0);
|
|
204
|
+
const totalTokens = stats.reduce((sum, week) => sum + week.totalTokens, 0);
|
|
205
|
+
const totalCost = stats.reduce((sum, week) => sum + week.totalCost, 0);
|
|
206
|
+
logger.log('\n' + '='.repeat(120));
|
|
207
|
+
logger.log('SUMMARY');
|
|
208
|
+
logger.log('='.repeat(120));
|
|
209
|
+
logger.log(`Total Events: ${totalEvents}`);
|
|
210
|
+
logger.log(`Total Tokens: ${totalTokens.toLocaleString()}`);
|
|
211
|
+
logger.log(`Total Cost: $${totalCost.toFixed(2)}`);
|
|
212
|
+
logger.log(`Average per week: ${(totalTokens / Math.max(1, stats.length)).toLocaleString()} tokens, $${(totalCost / Math.max(1, stats.length)).toFixed(2)}`);
|
|
213
|
+
logger.log('='.repeat(120) + '\n');
|
|
214
|
+
// Show breakdown if requested
|
|
215
|
+
if (options.breakdown) {
|
|
216
|
+
displayBreakdown(events, 'PER-MODEL BREAKDOWN');
|
|
217
|
+
}
|
|
218
|
+
// Output JSON if requested
|
|
219
|
+
if (outputJson) {
|
|
220
|
+
const jsonOutput = statsToJSON('weekly', stats, events, { breakdown: options.breakdown, period: `last ${weeks} weeks` });
|
|
221
|
+
console.log(jsonOutput);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Show usage for a specific date
|
|
226
|
+
*/
|
|
227
|
+
export async function showDateReport(credentials, date, options = {}, outputJson = false) {
|
|
228
|
+
const startDate = new Date(date);
|
|
229
|
+
startDate.setHours(0, 0, 0, 0);
|
|
230
|
+
const endDate = new Date(date);
|
|
231
|
+
endDate.setHours(23, 59, 59, 999);
|
|
232
|
+
const events = await fetchUsageEvents(credentials, startDate, endDate);
|
|
233
|
+
if (events.length === 0) {
|
|
234
|
+
logger.warn('No usage events found for this date');
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
logger.log('');
|
|
238
|
+
// Display detailed events
|
|
239
|
+
logger.log('\n' + '='.repeat(120));
|
|
240
|
+
logger.log(`USAGE EVENTS FOR ${date.toDateString()}`);
|
|
241
|
+
logger.log('='.repeat(120) + '\n');
|
|
242
|
+
const tableData = events.map((event) => {
|
|
243
|
+
const timestamp = new Date(event.timestamp).toLocaleTimeString();
|
|
244
|
+
return [
|
|
245
|
+
timestamp,
|
|
246
|
+
event.model,
|
|
247
|
+
event.type,
|
|
248
|
+
event.inputTokens.toLocaleString(),
|
|
249
|
+
event.outputTokens.toLocaleString(),
|
|
250
|
+
event.tokens.toLocaleString(),
|
|
251
|
+
`$${event.cost?.toFixed(4) || '0.0000'}`,
|
|
252
|
+
];
|
|
253
|
+
});
|
|
254
|
+
const table = createTable(['Time', 'Model', 'Type', 'Input Tokens', 'Output Tokens', 'Total Tokens', 'Cost'], tableData);
|
|
255
|
+
logger.log(table);
|
|
256
|
+
// Summary
|
|
257
|
+
const totalTokens = events.reduce((sum, e) => sum + e.tokens, 0);
|
|
258
|
+
const totalCost = events.reduce((sum, e) => sum + (e.cost || 0), 0);
|
|
259
|
+
logger.log('\n' + '='.repeat(120));
|
|
260
|
+
logger.log(`Total Events: ${events.length}`);
|
|
261
|
+
logger.log(`Total Tokens: ${totalTokens.toLocaleString()}`);
|
|
262
|
+
logger.log(`Total Cost: $${totalCost.toFixed(2)}`);
|
|
263
|
+
logger.log('='.repeat(120) + '\n');
|
|
264
|
+
// Show breakdown if requested
|
|
265
|
+
if (options.breakdown) {
|
|
266
|
+
displayBreakdown(events, 'PER-MODEL BREAKDOWN');
|
|
267
|
+
}
|
|
268
|
+
// Output JSON if requested
|
|
269
|
+
if (outputJson) {
|
|
270
|
+
const jsonOutput = statsToJSON('today', events.map((e, i) => ({ ...e, eventIndex: i })), events, { breakdown: options.breakdown, period: 'today' });
|
|
271
|
+
console.log(jsonOutput);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data loading utilities for Cursor usage analysis
|
|
3
|
+
* Extracts credentials from local database and fetches usage data from API
|
|
4
|
+
*/
|
|
5
|
+
import type { CursorCredentials, UsageSummary } from './_types';
|
|
6
|
+
/**
|
|
7
|
+
* Extract Cursor credentials from local database
|
|
8
|
+
*/
|
|
9
|
+
export declare function getCursorCredentials(): Promise<CursorCredentials | null>;
|
|
10
|
+
/**
|
|
11
|
+
* Fetch usage data from Cursor API
|
|
12
|
+
*/
|
|
13
|
+
export declare function fetchUsageData(credentials: CursorCredentials): Promise<UsageSummary | null>;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data loading utilities for Cursor usage analysis
|
|
3
|
+
* Extracts credentials from local database and fetches usage data from API
|
|
4
|
+
*/
|
|
5
|
+
import { readFileSync } from 'node:fs';
|
|
6
|
+
import { CURSOR_API_URL, CURSOR_DB_PATH, DB_KEY_ACCESS_TOKEN, DB_KEY_EMAIL, DB_KEY_MEMBERSHIP, DB_KEY_STATSIG_BOOTSTRAP, DEFAULT_TIMEOUT, USER_AGENT, } from './_consts';
|
|
7
|
+
import { logger } from './logger';
|
|
8
|
+
import initSqlJs from 'sql.js';
|
|
9
|
+
let sqlJs = null;
|
|
10
|
+
/**
|
|
11
|
+
* Initialize SQL.js
|
|
12
|
+
*/
|
|
13
|
+
async function initSql() {
|
|
14
|
+
if (!sqlJs) {
|
|
15
|
+
sqlJs = await initSqlJs();
|
|
16
|
+
}
|
|
17
|
+
return sqlJs;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Query database value
|
|
21
|
+
*/
|
|
22
|
+
function queryDB(db, key) {
|
|
23
|
+
try {
|
|
24
|
+
const stmt = db.prepare(`SELECT value FROM ItemTable WHERE key = ?`);
|
|
25
|
+
stmt.bind([key]);
|
|
26
|
+
if (stmt.step()) {
|
|
27
|
+
const row = stmt.getAsObject();
|
|
28
|
+
return row.value || null;
|
|
29
|
+
}
|
|
30
|
+
stmt.free();
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Extract Cursor credentials from local database
|
|
39
|
+
*/
|
|
40
|
+
export async function getCursorCredentials() {
|
|
41
|
+
try {
|
|
42
|
+
const sql = await initSql();
|
|
43
|
+
const data = readFileSync(CURSOR_DB_PATH);
|
|
44
|
+
const db = new sql.Database(data);
|
|
45
|
+
// Get user ID from statsigBootstrap
|
|
46
|
+
let userId = null;
|
|
47
|
+
const bootstrapValue = queryDB(db, DB_KEY_STATSIG_BOOTSTRAP);
|
|
48
|
+
if (bootstrapValue) {
|
|
49
|
+
try {
|
|
50
|
+
const parsed = JSON.parse(bootstrapValue);
|
|
51
|
+
userId = parsed?.user?.userID;
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
logger.warn('Failed to extract user ID from statsigBootstrap');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Get access token
|
|
58
|
+
const accessToken = queryDB(db, DB_KEY_ACCESS_TOKEN);
|
|
59
|
+
// Get email
|
|
60
|
+
const email = queryDB(db, DB_KEY_EMAIL) || undefined;
|
|
61
|
+
// Get membership
|
|
62
|
+
const membership = queryDB(db, DB_KEY_MEMBERSHIP) || undefined;
|
|
63
|
+
db.close();
|
|
64
|
+
if (!userId) {
|
|
65
|
+
logger.error('Could not extract user ID from database');
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
if (!accessToken) {
|
|
69
|
+
logger.error('Could not extract access token from database');
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
userId,
|
|
74
|
+
accessToken,
|
|
75
|
+
email,
|
|
76
|
+
membership,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
logger.error(`Error reading database: ${String(error)}`);
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Fetch usage data from Cursor API
|
|
86
|
+
*/
|
|
87
|
+
export async function fetchUsageData(credentials) {
|
|
88
|
+
try {
|
|
89
|
+
const sessionToken = `${credentials.userId}::${credentials.accessToken}`;
|
|
90
|
+
const headers = {
|
|
91
|
+
Accept: 'application/json',
|
|
92
|
+
'Content-Type': 'application/json',
|
|
93
|
+
'User-Agent': USER_AGENT,
|
|
94
|
+
};
|
|
95
|
+
const cookieHeader = `WorkosCursorSessionToken=${encodeURIComponent(sessionToken)}`;
|
|
96
|
+
const controller = new AbortController();
|
|
97
|
+
const timeoutId = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT);
|
|
98
|
+
const response = await fetch(CURSOR_API_URL, {
|
|
99
|
+
method: 'GET',
|
|
100
|
+
headers: {
|
|
101
|
+
...headers,
|
|
102
|
+
Cookie: cookieHeader,
|
|
103
|
+
},
|
|
104
|
+
signal: controller.signal,
|
|
105
|
+
});
|
|
106
|
+
clearTimeout(timeoutId);
|
|
107
|
+
if (!response.ok) {
|
|
108
|
+
logger.error(`API request failed with status ${response.status}: ${response.statusText}`);
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
const data = (await response.json());
|
|
112
|
+
return validateAndParse(data);
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
logger.error(`Error fetching usage data: ${String(error)}`);
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Validate and parse API response
|
|
121
|
+
*/
|
|
122
|
+
function validateAndParse(data) {
|
|
123
|
+
try {
|
|
124
|
+
if (typeof data !== 'object' || data === null) {
|
|
125
|
+
logger.error('Invalid API response format');
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
return data;
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
logger.error(`Error parsing API response: ${String(error)}`);
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event loading utilities for detailed usage analysis
|
|
3
|
+
* Fetches granular usage events for building daily/weekly reports
|
|
4
|
+
*/
|
|
5
|
+
import type { CursorCredentials } from './_types';
|
|
6
|
+
export interface UsageEvent {
|
|
7
|
+
id: string;
|
|
8
|
+
timestamp: number;
|
|
9
|
+
model: string;
|
|
10
|
+
tokens: number;
|
|
11
|
+
inputTokens: number;
|
|
12
|
+
outputTokens: number;
|
|
13
|
+
cacheWriteTokens: number;
|
|
14
|
+
cacheReadTokens: number;
|
|
15
|
+
type: string;
|
|
16
|
+
kind: string;
|
|
17
|
+
cost: number;
|
|
18
|
+
maxMode: boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface EventsResponse {
|
|
21
|
+
events: UsageEvent[];
|
|
22
|
+
total: number;
|
|
23
|
+
page: number;
|
|
24
|
+
pageSize: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Fetch usage events for a date range
|
|
28
|
+
*/
|
|
29
|
+
export declare function fetchUsageEvents(credentials: CursorCredentials, startDate: Date, endDate: Date, pageSize?: number): Promise<UsageEvent[]>;
|
|
30
|
+
/**
|
|
31
|
+
* Group events by day
|
|
32
|
+
*/
|
|
33
|
+
export declare function groupByDay(events: UsageEvent[]): Map<string, UsageEvent[]>;
|
|
34
|
+
/**
|
|
35
|
+
* Calculate daily statistics
|
|
36
|
+
*/
|
|
37
|
+
export interface DailyStats {
|
|
38
|
+
date: string;
|
|
39
|
+
eventCount: number;
|
|
40
|
+
totalTokens: number;
|
|
41
|
+
inputTokens: number;
|
|
42
|
+
outputTokens: number;
|
|
43
|
+
totalCost: number;
|
|
44
|
+
models: Map<string, number>;
|
|
45
|
+
}
|
|
46
|
+
export declare function calculateDailyStats(groupedEvents: Map<string, UsageEvent[]>): DailyStats[];
|
|
47
|
+
/**
|
|
48
|
+
* Format daily stats for table display
|
|
49
|
+
*/
|
|
50
|
+
export declare function formatDailyStatsTable(stats: DailyStats[]): string[][];
|
|
51
|
+
/**
|
|
52
|
+
* Group events by month
|
|
53
|
+
*/
|
|
54
|
+
export declare function groupByMonth(events: UsageEvent[]): Map<string, UsageEvent[]>;
|
|
55
|
+
/**
|
|
56
|
+
* Calculate monthly statistics
|
|
57
|
+
*/
|
|
58
|
+
export declare function calculateMonthlyStats(groupedEvents: Map<string, UsageEvent[]>): any[];
|
|
59
|
+
/**
|
|
60
|
+
* Format monthly stats for table display
|
|
61
|
+
*/
|
|
62
|
+
export declare function formatMonthlyStatsTable(stats: any[]): string[][];
|
|
63
|
+
/**
|
|
64
|
+
* Calculate per-model breakdown
|
|
65
|
+
*/
|
|
66
|
+
export declare function calculateModelBreakdown(events: UsageEvent[]): Map<string, any>;
|
|
67
|
+
/**
|
|
68
|
+
* Format model breakdown for display
|
|
69
|
+
*/
|
|
70
|
+
export declare function formatModelBreakdownTable(breakdown: Map<string, any>, totalTokens: number, totalCost: number): string[][];
|
|
71
|
+
/**
|
|
72
|
+
* Group events by week
|
|
73
|
+
*/
|
|
74
|
+
export declare function groupByWeek(events: UsageEvent[]): Map<string, UsageEvent[]>;
|
|
75
|
+
/**
|
|
76
|
+
* Calculate weekly statistics
|
|
77
|
+
*/
|
|
78
|
+
export declare function calculateWeeklyStats(groupedEvents: Map<string, UsageEvent[]>): any[];
|
|
79
|
+
/**
|
|
80
|
+
* Format weekly stats for table display
|
|
81
|
+
*/
|
|
82
|
+
export declare function formatWeeklyStatsTable(stats: any[]): string[][];
|