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
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event loading utilities for detailed usage analysis
|
|
3
|
+
* Fetches granular usage events for building daily/weekly reports
|
|
4
|
+
*/
|
|
5
|
+
import { CURSOR_API_URL_EVENTS, DEFAULT_TIMEOUT, USER_AGENT, } from './_consts';
|
|
6
|
+
import { logger } from './logger';
|
|
7
|
+
/**
|
|
8
|
+
* Fetch usage events for a date range
|
|
9
|
+
*/
|
|
10
|
+
export async function fetchUsageEvents(credentials, startDate, endDate, pageSize = 100) {
|
|
11
|
+
try {
|
|
12
|
+
const sessionToken = `${credentials.userId}::${credentials.accessToken}`;
|
|
13
|
+
const headers = {
|
|
14
|
+
Accept: 'application/json',
|
|
15
|
+
'Content-Type': 'application/json',
|
|
16
|
+
'User-Agent': USER_AGENT,
|
|
17
|
+
Origin: 'https://cursor.com',
|
|
18
|
+
};
|
|
19
|
+
const cookieHeader = `WorkosCursorSessionToken=${encodeURIComponent(sessionToken)}`;
|
|
20
|
+
// Convert dates to millisecond timestamps
|
|
21
|
+
const startTimestamp = startDate.getTime().toString();
|
|
22
|
+
const endTimestamp = endDate.getTime().toString();
|
|
23
|
+
const body = {
|
|
24
|
+
teamId: 0,
|
|
25
|
+
startDate: startTimestamp,
|
|
26
|
+
endDate: endTimestamp,
|
|
27
|
+
page: 1,
|
|
28
|
+
pageSize,
|
|
29
|
+
};
|
|
30
|
+
const controller = new AbortController();
|
|
31
|
+
const timeoutId = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT);
|
|
32
|
+
const response = await fetch(CURSOR_API_URL_EVENTS, {
|
|
33
|
+
method: 'POST',
|
|
34
|
+
headers: {
|
|
35
|
+
...headers,
|
|
36
|
+
Cookie: cookieHeader,
|
|
37
|
+
},
|
|
38
|
+
body: JSON.stringify(body),
|
|
39
|
+
signal: controller.signal,
|
|
40
|
+
});
|
|
41
|
+
clearTimeout(timeoutId);
|
|
42
|
+
if (!response.ok) {
|
|
43
|
+
logger.error(`Events API request failed with status ${response.status}: ${response.statusText}`);
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
const data = (await response.json());
|
|
47
|
+
return parseEvents(data);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
logger.error(`Error fetching usage events: ${String(error)}`);
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Parse and validate events response
|
|
56
|
+
*/
|
|
57
|
+
function parseEvents(data) {
|
|
58
|
+
try {
|
|
59
|
+
if (!Array.isArray(data?.usageEventsDisplay)) {
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
return data.usageEventsDisplay.map((event) => {
|
|
63
|
+
const tokenUsage = event.tokenUsage || {};
|
|
64
|
+
const totalTokens = (tokenUsage.inputTokens || 0) +
|
|
65
|
+
(tokenUsage.outputTokens || 0) +
|
|
66
|
+
(tokenUsage.cacheWriteTokens || 0) +
|
|
67
|
+
(tokenUsage.cacheReadTokens || 0);
|
|
68
|
+
return {
|
|
69
|
+
id: event.id || `${event.timestamp}-${event.model}`,
|
|
70
|
+
timestamp: Number(event.timestamp) || Date.now(),
|
|
71
|
+
model: event.model || 'unknown',
|
|
72
|
+
tokens: totalTokens,
|
|
73
|
+
inputTokens: Number(tokenUsage.inputTokens) || 0,
|
|
74
|
+
outputTokens: Number(tokenUsage.outputTokens) || 0,
|
|
75
|
+
cacheWriteTokens: Number(tokenUsage.cacheWriteTokens) || 0,
|
|
76
|
+
cacheReadTokens: Number(tokenUsage.cacheReadTokens) || 0,
|
|
77
|
+
type: 'usage',
|
|
78
|
+
kind: event.kind || 'unknown',
|
|
79
|
+
cost: Number(tokenUsage.totalCents) / 100 || 0, // Convert cents to dollars
|
|
80
|
+
maxMode: event.maxMode || false,
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
logger.error(`Error parsing events: ${String(error)}`);
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Group events by day
|
|
91
|
+
*/
|
|
92
|
+
export function groupByDay(events) {
|
|
93
|
+
const grouped = new Map();
|
|
94
|
+
events.forEach((event) => {
|
|
95
|
+
const date = new Date(event.timestamp);
|
|
96
|
+
const dateKey = date.toISOString().split('T')[0]; // YYYY-MM-DD
|
|
97
|
+
if (!grouped.has(dateKey)) {
|
|
98
|
+
grouped.set(dateKey, []);
|
|
99
|
+
}
|
|
100
|
+
grouped.get(dateKey).push(event);
|
|
101
|
+
});
|
|
102
|
+
return grouped;
|
|
103
|
+
}
|
|
104
|
+
export function calculateDailyStats(groupedEvents) {
|
|
105
|
+
const stats = [];
|
|
106
|
+
Array.from(groupedEvents.entries())
|
|
107
|
+
.sort()
|
|
108
|
+
.forEach(([date, events]) => {
|
|
109
|
+
const models = new Map();
|
|
110
|
+
let totalTokens = 0;
|
|
111
|
+
let inputTokens = 0;
|
|
112
|
+
let outputTokens = 0;
|
|
113
|
+
let totalCost = 0;
|
|
114
|
+
events.forEach((event) => {
|
|
115
|
+
totalTokens += event.tokens;
|
|
116
|
+
inputTokens += event.inputTokens;
|
|
117
|
+
outputTokens += event.outputTokens;
|
|
118
|
+
totalCost += event.cost || 0;
|
|
119
|
+
const count = models.get(event.model) || 0;
|
|
120
|
+
models.set(event.model, count + 1);
|
|
121
|
+
});
|
|
122
|
+
stats.push({
|
|
123
|
+
date,
|
|
124
|
+
eventCount: events.length,
|
|
125
|
+
totalTokens,
|
|
126
|
+
inputTokens,
|
|
127
|
+
outputTokens,
|
|
128
|
+
totalCost,
|
|
129
|
+
models,
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
return stats;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Format daily stats for table display
|
|
136
|
+
*/
|
|
137
|
+
export function formatDailyStatsTable(stats) {
|
|
138
|
+
return stats.map((day) => {
|
|
139
|
+
const modelList = Array.from(day.models.entries())
|
|
140
|
+
.map(([model, count]) => `${model}(${count})`)
|
|
141
|
+
.join(', ');
|
|
142
|
+
return [
|
|
143
|
+
day.date,
|
|
144
|
+
day.eventCount.toString(),
|
|
145
|
+
day.totalTokens.toLocaleString(),
|
|
146
|
+
day.inputTokens.toLocaleString(),
|
|
147
|
+
day.outputTokens.toLocaleString(),
|
|
148
|
+
`$${day.totalCost.toFixed(2)}`,
|
|
149
|
+
modelList || 'N/A',
|
|
150
|
+
];
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Group events by month
|
|
155
|
+
*/
|
|
156
|
+
export function groupByMonth(events) {
|
|
157
|
+
const grouped = new Map();
|
|
158
|
+
events.forEach((event) => {
|
|
159
|
+
const date = new Date(event.timestamp);
|
|
160
|
+
const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`; // YYYY-MM
|
|
161
|
+
if (!grouped.has(monthKey)) {
|
|
162
|
+
grouped.set(monthKey, []);
|
|
163
|
+
}
|
|
164
|
+
grouped.get(monthKey).push(event);
|
|
165
|
+
});
|
|
166
|
+
return grouped;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Calculate monthly statistics
|
|
170
|
+
*/
|
|
171
|
+
export function calculateMonthlyStats(groupedEvents) {
|
|
172
|
+
const monthNames = [
|
|
173
|
+
'January', 'February', 'March', 'April', 'May', 'June',
|
|
174
|
+
'July', 'August', 'September', 'October', 'November', 'December',
|
|
175
|
+
];
|
|
176
|
+
const stats = [];
|
|
177
|
+
Array.from(groupedEvents.entries())
|
|
178
|
+
.sort()
|
|
179
|
+
.forEach(([monthKey, events]) => {
|
|
180
|
+
const [yearStr, monthStr] = monthKey.split('-');
|
|
181
|
+
const year = Number(yearStr);
|
|
182
|
+
const month = Number(monthStr);
|
|
183
|
+
const models = new Map();
|
|
184
|
+
let totalTokens = 0;
|
|
185
|
+
let inputTokens = 0;
|
|
186
|
+
let outputTokens = 0;
|
|
187
|
+
let totalCost = 0;
|
|
188
|
+
events.forEach((event) => {
|
|
189
|
+
totalTokens += event.tokens;
|
|
190
|
+
inputTokens += event.inputTokens;
|
|
191
|
+
outputTokens += event.outputTokens;
|
|
192
|
+
totalCost += event.cost || 0;
|
|
193
|
+
const count = models.get(event.model) || 0;
|
|
194
|
+
models.set(event.model, count + 1);
|
|
195
|
+
});
|
|
196
|
+
stats.push({
|
|
197
|
+
year,
|
|
198
|
+
month,
|
|
199
|
+
monthName: monthNames[month - 1],
|
|
200
|
+
monthKey,
|
|
201
|
+
eventCount: events.length,
|
|
202
|
+
totalTokens,
|
|
203
|
+
inputTokens,
|
|
204
|
+
outputTokens,
|
|
205
|
+
totalCost,
|
|
206
|
+
models,
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
return stats;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Format monthly stats for table display
|
|
213
|
+
*/
|
|
214
|
+
export function formatMonthlyStatsTable(stats) {
|
|
215
|
+
return stats.map((month) => {
|
|
216
|
+
const modelList = Array.from(month.models.entries())
|
|
217
|
+
.map(([model, count]) => `${model}(${count})`)
|
|
218
|
+
.join(', ');
|
|
219
|
+
return [
|
|
220
|
+
`${month.monthName} ${month.year}`,
|
|
221
|
+
month.eventCount.toString(),
|
|
222
|
+
month.totalTokens.toLocaleString(),
|
|
223
|
+
month.inputTokens.toLocaleString(),
|
|
224
|
+
month.outputTokens.toLocaleString(),
|
|
225
|
+
`$${month.totalCost.toFixed(2)}`,
|
|
226
|
+
modelList || 'N/A',
|
|
227
|
+
];
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Calculate per-model breakdown
|
|
232
|
+
*/
|
|
233
|
+
export function calculateModelBreakdown(events) {
|
|
234
|
+
const breakdown = new Map();
|
|
235
|
+
events.forEach((event) => {
|
|
236
|
+
const model = event.model || 'unknown';
|
|
237
|
+
if (!breakdown.has(model)) {
|
|
238
|
+
breakdown.set(model, {
|
|
239
|
+
model,
|
|
240
|
+
count: 0,
|
|
241
|
+
totalTokens: 0,
|
|
242
|
+
inputTokens: 0,
|
|
243
|
+
outputTokens: 0,
|
|
244
|
+
totalCost: 0,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
const stats = breakdown.get(model);
|
|
248
|
+
stats.count += 1;
|
|
249
|
+
stats.totalTokens += event.tokens;
|
|
250
|
+
stats.inputTokens += event.inputTokens;
|
|
251
|
+
stats.outputTokens += event.outputTokens;
|
|
252
|
+
stats.totalCost += event.cost || 0;
|
|
253
|
+
});
|
|
254
|
+
return breakdown;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Format model breakdown for display
|
|
258
|
+
*/
|
|
259
|
+
export function formatModelBreakdownTable(breakdown, totalTokens, totalCost) {
|
|
260
|
+
const sorted = Array.from(breakdown.values()).sort((a, b) => b.totalTokens - a.totalTokens);
|
|
261
|
+
return sorted.map((model) => {
|
|
262
|
+
const tokenPercent = totalTokens > 0 ? (model.totalTokens / totalTokens * 100).toFixed(1) : '0.0';
|
|
263
|
+
const costPercent = totalCost > 0 ? (model.totalCost / totalCost * 100).toFixed(1) : '0.0';
|
|
264
|
+
return [
|
|
265
|
+
model.model,
|
|
266
|
+
model.count.toString(),
|
|
267
|
+
model.totalTokens.toLocaleString(),
|
|
268
|
+
`$${model.totalCost.toFixed(2)}`,
|
|
269
|
+
`${tokenPercent}%`,
|
|
270
|
+
`${costPercent}%`,
|
|
271
|
+
];
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Group events by week
|
|
276
|
+
*/
|
|
277
|
+
export function groupByWeek(events) {
|
|
278
|
+
const grouped = new Map();
|
|
279
|
+
events.forEach((event) => {
|
|
280
|
+
const date = new Date(event.timestamp);
|
|
281
|
+
// ISO week starts on Monday
|
|
282
|
+
const weekStart = new Date(date);
|
|
283
|
+
const day = weekStart.getUTCDay();
|
|
284
|
+
const diff = weekStart.getUTCDate() - day + (day === 0 ? -6 : 1);
|
|
285
|
+
weekStart.setUTCDate(diff);
|
|
286
|
+
weekStart.setUTCHours(0, 0, 0, 0);
|
|
287
|
+
const weekKey = weekStart.toISOString().split('T')[0];
|
|
288
|
+
if (!grouped.has(weekKey)) {
|
|
289
|
+
grouped.set(weekKey, []);
|
|
290
|
+
}
|
|
291
|
+
grouped.get(weekKey).push(event);
|
|
292
|
+
});
|
|
293
|
+
return grouped;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Calculate weekly statistics
|
|
297
|
+
*/
|
|
298
|
+
export function calculateWeeklyStats(groupedEvents) {
|
|
299
|
+
const stats = [];
|
|
300
|
+
Array.from(groupedEvents.entries())
|
|
301
|
+
.sort()
|
|
302
|
+
.forEach(([weekKey, events]) => {
|
|
303
|
+
const weekStart = new Date(weekKey);
|
|
304
|
+
const weekEnd = new Date(weekStart);
|
|
305
|
+
weekEnd.setDate(weekEnd.getDate() + 6);
|
|
306
|
+
const models = new Map();
|
|
307
|
+
let totalTokens = 0;
|
|
308
|
+
let inputTokens = 0;
|
|
309
|
+
let outputTokens = 0;
|
|
310
|
+
let totalCost = 0;
|
|
311
|
+
events.forEach((event) => {
|
|
312
|
+
totalTokens += event.tokens;
|
|
313
|
+
inputTokens += event.inputTokens;
|
|
314
|
+
outputTokens += event.outputTokens;
|
|
315
|
+
totalCost += event.cost || 0;
|
|
316
|
+
const count = models.get(event.model) || 0;
|
|
317
|
+
models.set(event.model, count + 1);
|
|
318
|
+
});
|
|
319
|
+
stats.push({
|
|
320
|
+
weekStart: weekKey,
|
|
321
|
+
weekEnd: weekEnd.toISOString().split('T')[0],
|
|
322
|
+
eventCount: events.length,
|
|
323
|
+
totalTokens,
|
|
324
|
+
inputTokens,
|
|
325
|
+
outputTokens,
|
|
326
|
+
totalCost,
|
|
327
|
+
models,
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
return stats;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Format weekly stats for table display
|
|
334
|
+
*/
|
|
335
|
+
export function formatWeeklyStatsTable(stats) {
|
|
336
|
+
return stats.map((week) => {
|
|
337
|
+
const modelList = Array.from(week.models.entries())
|
|
338
|
+
.map(([model, count]) => `${model}(${count})`)
|
|
339
|
+
.join(', ');
|
|
340
|
+
return [
|
|
341
|
+
`${week.weekStart} to ${week.weekEnd}`,
|
|
342
|
+
week.eventCount.toString(),
|
|
343
|
+
week.totalTokens.toLocaleString(),
|
|
344
|
+
week.inputTokens.toLocaleString(),
|
|
345
|
+
week.outputTokens.toLocaleString(),
|
|
346
|
+
`$${week.totalCost.toFixed(2)}`,
|
|
347
|
+
modelList || 'N/A',
|
|
348
|
+
];
|
|
349
|
+
});
|
|
350
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Cursor Usage Analyzer
|
|
4
|
+
* A CLI tool for analyzing Cursor API usage and token consumption
|
|
5
|
+
*/
|
|
6
|
+
import { runCLI } from './cli.js';
|
|
7
|
+
const argv = process.argv.slice(2);
|
|
8
|
+
runCLI(argv).catch((error) => {
|
|
9
|
+
console.error(`Fatal error: ${String(error)}`);
|
|
10
|
+
process.exit(1);
|
|
11
|
+
});
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple logging utilities
|
|
3
|
+
*/
|
|
4
|
+
export declare const logger: {
|
|
5
|
+
log: (message: string) => void;
|
|
6
|
+
info: (message: string) => void;
|
|
7
|
+
success: (message: string) => void;
|
|
8
|
+
warn: (message: string) => void;
|
|
9
|
+
error: (message: string) => void;
|
|
10
|
+
debug: (message: string) => void;
|
|
11
|
+
};
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple logging utilities
|
|
3
|
+
*/
|
|
4
|
+
const COLORS = {
|
|
5
|
+
reset: '\x1b[0m',
|
|
6
|
+
dim: '\x1b[2m',
|
|
7
|
+
red: '\x1b[31m',
|
|
8
|
+
green: '\x1b[32m',
|
|
9
|
+
yellow: '\x1b[33m',
|
|
10
|
+
blue: '\x1b[34m',
|
|
11
|
+
cyan: '\x1b[36m',
|
|
12
|
+
};
|
|
13
|
+
export const logger = {
|
|
14
|
+
log: (message) => {
|
|
15
|
+
console.log(message);
|
|
16
|
+
},
|
|
17
|
+
info: (message) => {
|
|
18
|
+
console.log(`${COLORS.cyan}ℹ ${COLORS.reset}${message}`);
|
|
19
|
+
},
|
|
20
|
+
success: (message) => {
|
|
21
|
+
console.log(`${COLORS.green}✓ ${COLORS.reset}${message}`);
|
|
22
|
+
},
|
|
23
|
+
warn: (message) => {
|
|
24
|
+
console.warn(`${COLORS.yellow}⚠ ${COLORS.reset}${message}`);
|
|
25
|
+
},
|
|
26
|
+
error: (message) => {
|
|
27
|
+
console.error(`${COLORS.red}✗ ${COLORS.reset}${message}`);
|
|
28
|
+
},
|
|
29
|
+
debug: (message) => {
|
|
30
|
+
if (process.env.DEBUG) {
|
|
31
|
+
console.log(`${COLORS.dim}[DEBUG] ${message}${COLORS.reset}`);
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple table formatting utilities
|
|
3
|
+
*/
|
|
4
|
+
export interface TableOptions {
|
|
5
|
+
headers: string[];
|
|
6
|
+
rows: string[][];
|
|
7
|
+
maxWidth?: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Format data as a simple ASCII table
|
|
11
|
+
*/
|
|
12
|
+
export declare function formatTable(options: TableOptions): string;
|
|
13
|
+
/**
|
|
14
|
+
* Create a simple data table with borders
|
|
15
|
+
*/
|
|
16
|
+
export declare function createTable(headers: string[], rows: string[][]): string;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple table formatting utilities
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Format data as a simple ASCII table
|
|
6
|
+
*/
|
|
7
|
+
export function formatTable(options) {
|
|
8
|
+
const { headers, rows } = options;
|
|
9
|
+
if (rows.length === 0) {
|
|
10
|
+
return 'No data to display';
|
|
11
|
+
}
|
|
12
|
+
// Calculate column widths
|
|
13
|
+
const colWidths = headers.map((h, i) => {
|
|
14
|
+
const headerWidth = h.length;
|
|
15
|
+
const maxRowWidth = Math.max(...rows.map((row) => (row[i] ? row[i].length : 0)));
|
|
16
|
+
return Math.max(headerWidth, maxRowWidth);
|
|
17
|
+
});
|
|
18
|
+
// Build separator line
|
|
19
|
+
const separator = '+' + colWidths.map((w) => '-'.repeat(w + 2)).join('+') + '+';
|
|
20
|
+
// Build header line
|
|
21
|
+
const headerLine = '| ' +
|
|
22
|
+
headers
|
|
23
|
+
.map((h, i) => h.padEnd(colWidths[i]))
|
|
24
|
+
.join(' | ') +
|
|
25
|
+
' |';
|
|
26
|
+
// Build data lines
|
|
27
|
+
const dataLines = rows.map((row) => '| ' +
|
|
28
|
+
row
|
|
29
|
+
.map((cell, i) => (cell || '').padEnd(colWidths[i]))
|
|
30
|
+
.join(' | ') +
|
|
31
|
+
' |');
|
|
32
|
+
// Combine all parts
|
|
33
|
+
return [separator, headerLine, separator, ...dataLines, separator].join('\n');
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Create a simple data table with borders
|
|
37
|
+
*/
|
|
38
|
+
export function createTable(headers, rows) {
|
|
39
|
+
return formatTable({ headers, rows });
|
|
40
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cursor-usage",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "CLI tool for analyzing Cursor usage and token consumption",
|
|
6
|
+
"author": "yifen <anthonyeef@gmail.com>",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/Anthonyeef/cursor-usage.git"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"cursor",
|
|
14
|
+
"cli",
|
|
15
|
+
"usage",
|
|
16
|
+
"analytics",
|
|
17
|
+
"tokens",
|
|
18
|
+
"api",
|
|
19
|
+
"cursor-ai"
|
|
20
|
+
],
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/Anthonyeef/cursor-usage/issues"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://github.com/Anthonyeef/cursor-usage#readme",
|
|
25
|
+
"main": "./dist/index.js",
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"bin": "dist/index.js",
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"README.md",
|
|
31
|
+
"LICENSE"
|
|
32
|
+
],
|
|
33
|
+
"scripts": {
|
|
34
|
+
"dev": "tsx src/index.ts",
|
|
35
|
+
"build": "tsc",
|
|
36
|
+
"prepublishOnly": "npm run build",
|
|
37
|
+
"start": "tsx src/index.ts",
|
|
38
|
+
"test": "vitest",
|
|
39
|
+
"lint": "eslint src/",
|
|
40
|
+
"format": "prettier --write src/"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"sql.js": "^1.8.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^20.10.6",
|
|
47
|
+
"typescript": "^5.3.3",
|
|
48
|
+
"tsx": "^4.7.0"
|
|
49
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=18.0.0"
|
|
52
|
+
}
|
|
53
|
+
}
|