cursor-usage 0.1.3 → 0.1.4
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 +96 -106
- package/dist/cli.js +28 -23
- package/dist/commands.js +79 -77
- package/dist/table-formatter.d.ts +64 -6
- package/dist/table-formatter.js +125 -30
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
# Cursor Usage Analyzer
|
|
2
2
|
|
|
3
|
+

|
|
4
|
+
|
|
3
5
|
A CLI tool for analyzing Cursor API usage and membership information, inspired by [ccusage](https://github.com/ryoppippi/ccusage).
|
|
4
6
|
|
|
5
7
|
## Features
|
|
6
8
|
|
|
7
|
-
- Extract Cursor credentials from local database
|
|
8
|
-
- Fetch usage data from Cursor API
|
|
9
|
-
- Display membership and plan usage information
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
9
|
+
- 📊 Extract Cursor credentials from local database
|
|
10
|
+
- 🔄 Fetch usage data from Cursor API
|
|
11
|
+
- 📋 Display membership and plan usage information
|
|
12
|
+
- 📈 Daily, weekly, and monthly usage reports
|
|
13
|
+
- 💰 Token count and cost tracking
|
|
14
|
+
- 🎨 Beautiful Unicode tables with colored output (inspired by [ccusage](https://github.com/ryoppippi/ccusage))
|
|
15
|
+
- 📦 JSON output for programmatic use
|
|
13
16
|
|
|
14
17
|
## Installation
|
|
15
18
|
|
|
@@ -99,84 +102,77 @@ npm run build # Compile TypeScript
|
|
|
99
102
|
### Summary View
|
|
100
103
|
|
|
101
104
|
```
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
+
╭────────────────────────────────────────╮
|
|
106
|
+
│ Cursor Usage Summary │
|
|
107
|
+
╰────────────────────────────────────────╯
|
|
108
|
+
|
|
109
|
+
📋 ACCOUNT INFORMATION
|
|
110
|
+
Membership: pro
|
|
111
|
+
Billing Cycle: Dec 29, 2025 to Jan 29, 2026
|
|
105
112
|
|
|
106
|
-
PLAN USAGE
|
|
107
|
-
Used:
|
|
113
|
+
📊 PLAN USAGE
|
|
114
|
+
Used: 150 / 2000 (7.50%)
|
|
108
115
|
Remaining: 1850
|
|
116
|
+
Breakdown:
|
|
117
|
+
- Included: 150
|
|
118
|
+
- Bonus: 0
|
|
119
|
+
- Total: 150
|
|
109
120
|
```
|
|
110
121
|
|
|
111
122
|
### Daily Report View
|
|
112
123
|
|
|
113
124
|
```
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
====================================================================================================
|
|
127
|
-
SUMMARY
|
|
128
|
-
====================================================================================================
|
|
129
|
-
Total Events: 3
|
|
130
|
-
Total Tokens: 539,301
|
|
131
|
-
Total Cost: $0.82
|
|
132
|
-
====================================================================================================
|
|
125
|
+
╭──────────────────────────────────────────────╮
|
|
126
|
+
│ Cursor Usage Report - Daily (Last 7 days) │
|
|
127
|
+
╰──────────────────────────────────────────────╯
|
|
128
|
+
|
|
129
|
+
┌────────────┬────────┬──────────────┬───────┬────────┬───────┬──────────────────────────────────┐
|
|
130
|
+
│ Date │ Events │ Total Tokens │ Input │ Output │ Cost │ Models │
|
|
131
|
+
├────────────┼────────┼──────────────┼───────┼────────┼───────┼──────────────────────────────────┤
|
|
132
|
+
│ 2026-01-11 │ 3 │ 539,301 │ 381 │ 7,081 │ $0.82 │ claude-4.5-opus-high-thinking(3) │
|
|
133
|
+
├────────────┼────────┼──────────────┼───────┼────────┼───────┼──────────────────────────────────┤
|
|
134
|
+
│ Total │ 3 │ 539,301 │ 381 │ 7,081 │ $0.82 │ │
|
|
135
|
+
└────────────┴────────┴──────────────┴───────┴────────┴───────┴──────────────────────────────────┘
|
|
133
136
|
```
|
|
134
137
|
|
|
135
138
|
### Monthly Report View
|
|
136
139
|
|
|
137
140
|
```
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
Total Events: 150
|
|
154
|
-
Total Tokens: 16,000,000
|
|
155
|
-
Total Cost: $13.30
|
|
156
|
-
Average per month: 5,333,333 tokens, $4.43
|
|
157
|
-
====================================================================================================
|
|
141
|
+
╭────────────────────────────────────────────────────╮
|
|
142
|
+
│ Cursor Usage Report - Monthly (Last 3 months) │
|
|
143
|
+
╰────────────────────────────────────────────────────╯
|
|
144
|
+
|
|
145
|
+
┌──────────┬────────┬───────────────┬─────────┬─────────┬────────┬─────────────────────┐
|
|
146
|
+
│ Month │ Events │ Total Tokens │ Input │ Output │ Cost │ Models │
|
|
147
|
+
├──────────┼────────┼───────────────┼─────────┼─────────┼────────┼─────────────────────┤
|
|
148
|
+
│ 2026-01 │ 50 │ 5,000,000 │ 50,000 │ 30,000 │ $4.50 │ claude-sonnet(50) │
|
|
149
|
+
├──────────┼────────┼───────────────┼─────────┼─────────┼────────┼─────────────────────┤
|
|
150
|
+
│ 2025-12 │ 75 │ 8,500,000 │ 75,000 │ 45,000 │ $6.80 │ claude-opus(75) │
|
|
151
|
+
├──────────┼────────┼───────────────┼─────────┼─────────┼────────┼─────────────────────┤
|
|
152
|
+
│ 2025-11 │ 25 │ 2,500,000 │ 20,000 │ 10,000 │ $2.00 │ claude-haiku(25) │
|
|
153
|
+
├──────────┼────────┼───────────────┼─────────┼─────────┼────────┼─────────────────────┤
|
|
154
|
+
│ Total │ 150 │ 16,000,000 │ 145,000 │ 85,000 │ $13.30 │ │
|
|
155
|
+
└──────────┴────────┴───────────────┴─────────┴─────────┴────────┴─────────────────────┘
|
|
158
156
|
```
|
|
159
157
|
|
|
160
158
|
### Detailed Event View
|
|
161
159
|
|
|
162
160
|
```
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
Total
|
|
177
|
-
|
|
178
|
-
Total Cost: $0.14
|
|
179
|
-
==================================================================================================================
|
|
161
|
+
╭─────────────────────────────────────────╮
|
|
162
|
+
│ Cursor Usage Report - Sun Jan 11 2026 │
|
|
163
|
+
╰─────────────────────────────────────────╯
|
|
164
|
+
|
|
165
|
+
┌──────────┬───────────────┬───────┬──────────────┬───────────────┬──────────────┬─────────┐
|
|
166
|
+
│ Time │ Model │ Type │ Input Tokens │ Output Tokens │ Total Tokens │ Cost │
|
|
167
|
+
├──────────┼───────────────┼───────┼──────────────┼───────────────┼──────────────┼─────────┤
|
|
168
|
+
│ 2:15 pm │ claude-opus │ usage │ 500 │ 1,200 │ 1,700 │ $0.0850 │
|
|
169
|
+
├──────────┼───────────────┼───────┼──────────────┼───────────────┼──────────────┼─────────┤
|
|
170
|
+
│ 1:45 pm │ claude-sonnet │ usage │ 300 │ 800 │ 1,100 │ $0.0440 │
|
|
171
|
+
├──────────┼───────────────┼───────┼──────────────┼───────────────┼──────────────┼─────────┤
|
|
172
|
+
│ 1:20 pm │ claude-haiku │ usage │ 100 │ 250 │ 350 │ $0.0070 │
|
|
173
|
+
├──────────┼───────────────┼───────┼──────────────┼───────────────┼──────────────┼─────────┤
|
|
174
|
+
│ Total │ │ │ 900 │ 2,250 │ 3,150 │ $0.14 │
|
|
175
|
+
└──────────┴───────────────┴───────┴──────────────┴───────────────┴──────────────┴─────────┘
|
|
180
176
|
```
|
|
181
177
|
|
|
182
178
|
### Model Breakdown View (`--breakdown`)
|
|
@@ -184,17 +180,19 @@ Total Cost: $0.14
|
|
|
184
180
|
When using `--breakdown`, reports include a detailed per-model breakdown:
|
|
185
181
|
|
|
186
182
|
```
|
|
187
|
-
|
|
188
|
-
PER-MODEL BREAKDOWN
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
183
|
+
╭────────────────────────────────────────╮
|
|
184
|
+
│ PER-MODEL BREAKDOWN │
|
|
185
|
+
╰────────────────────────────────────────╯
|
|
186
|
+
|
|
187
|
+
┌───────────────┬────────┬──────────────┬─────────┬─────────┬───────┬─────────┬────────┐
|
|
188
|
+
│ Model │ Events │ Total Tokens │ Input │ Output │ Cost │ Token % │ Cost % │
|
|
189
|
+
├───────────────┼────────┼──────────────┼─────────┼─────────┼───────┼─────────┼────────┤
|
|
190
|
+
│ claude-opus │ 15 │ 2,500,000 │ 20,000 │ 15,000 │ $2.00 │ 50.00% │ 40.00% │
|
|
191
|
+
├───────────────┼────────┼──────────────┼─────────┼─────────┼───────┼─────────┼────────┤
|
|
192
|
+
│ claude-sonnet │ 10 │ 2,000,000 │ 15,000 │ 12,000 │ $1.60 │ 40.00% │ 32.00% │
|
|
193
|
+
├───────────────┼────────┼──────────────┼─────────┼─────────┼───────┼─────────┼────────┤
|
|
194
|
+
│ claude-haiku │ 5 │ 500,000 │ 5,000 │ 3,000 │ $0.40 │ 10.00% │ 8.00% │
|
|
195
|
+
└───────────────┴────────┴──────────────┴─────────┴─────────┴───────┴─────────┴────────┘
|
|
198
196
|
```
|
|
199
197
|
|
|
200
198
|
### JSON Output (`--json`)
|
|
@@ -239,27 +237,22 @@ JSON output provides clean, structured data for programmatic use (no table forma
|
|
|
239
237
|
}
|
|
240
238
|
```
|
|
241
239
|
|
|
242
|
-
###
|
|
243
|
-
|
|
244
|
-
The `--compact` flag provides a more condensed table layout:
|
|
240
|
+
### Weekly Report View
|
|
245
241
|
|
|
246
242
|
```
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
2026-01-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
Total Tokens: 1,829,071
|
|
261
|
-
Total Cost: $2.39
|
|
262
|
-
==================================================================================================================
|
|
243
|
+
╭─────────────────────────────────────────────────╮
|
|
244
|
+
│ Cursor Usage Report - Weekly (Last 4 weeks) │
|
|
245
|
+
╰─────────────────────────────────────────────────╯
|
|
246
|
+
|
|
247
|
+
┌─────────────────────────┬────────┬──────────────┬─────────┬─────────┬────────┬─────────────────────┐
|
|
248
|
+
│ Week │ Events │ Total Tokens │ Input │ Output │ Cost │ Models │
|
|
249
|
+
├─────────────────────────┼────────┼──────────────┼─────────┼─────────┼────────┼─────────────────────┤
|
|
250
|
+
│ 2026-01-06 - 2026-01-12 │ 25 │ 3,500,000 │ 35,000 │ 21,000 │ $3.50 │ claude-opus(25) │
|
|
251
|
+
├─────────────────────────┼────────┼──────────────┼─────────┼─────────┼────────┼─────────────────────┤
|
|
252
|
+
│ 2025-12-30 - 2026-01-05 │ 40 │ 5,200,000 │ 52,000 │ 31,200 │ $5.20 │ claude-sonnet(40) │
|
|
253
|
+
├─────────────────────────┼────────┼──────────────┼─────────┼─────────┼────────┼─────────────────────┤
|
|
254
|
+
│ Total │ 65 │ 8,700,000 │ 87,000 │ 52,200 │ $8.70 │ │
|
|
255
|
+
└─────────────────────────┴────────┴──────────────┴─────────┴─────────┴────────┴─────────────────────┘
|
|
263
256
|
```
|
|
264
257
|
|
|
265
258
|
## Environment Variables
|
|
@@ -280,20 +273,17 @@ src/
|
|
|
280
273
|
|
|
281
274
|
## Features Implemented
|
|
282
275
|
|
|
283
|
-
- ✅ Daily
|
|
276
|
+
- ✅ Daily, weekly, and monthly usage reports
|
|
284
277
|
- ✅ Detailed event breakdown by time
|
|
285
278
|
- ✅ Token count tracking (input, output, cache)
|
|
286
279
|
- ✅ Cost calculation and display
|
|
287
|
-
- ✅ Model usage breakdown
|
|
288
|
-
- ✅ Flexible date range querying
|
|
289
|
-
- ✅
|
|
280
|
+
- ✅ Model usage breakdown with `--breakdown` flag
|
|
281
|
+
- ✅ Flexible date range querying with `--since` and `--until`
|
|
282
|
+
- ✅ Beautiful Unicode tables with box-drawing characters
|
|
283
|
+
- ✅ Colorized CLI output (cyan headers, yellow totals)
|
|
284
|
+
- ✅ JSON output for programmatic use
|
|
290
285
|
|
|
291
286
|
|
|
292
287
|
## License
|
|
293
288
|
|
|
294
289
|
MIT
|
|
295
|
-
|
|
296
|
-
## Related Projects
|
|
297
|
-
|
|
298
|
-
- [ccusage](https://github.com/ryoppippi/ccusage) - Claude Code usage analyzer
|
|
299
|
-
- [@ccusage/amp](https://www.npmjs.com/package/@ccusage/amp) - Amp usage analyzer
|
package/dist/cli.js
CHANGED
|
@@ -5,6 +5,7 @@ import { getCursorCredentials, fetchUsageData } from './data-loader.js';
|
|
|
5
5
|
import { showDailyReport, showMonthlyReport, showWeeklyReport, showDateReport, } from './commands.js';
|
|
6
6
|
import { logger } from './logger.js';
|
|
7
7
|
import { parseArgs, getNumberFlag, getDateFlag, getBoolFlag, } from './args-parser.js';
|
|
8
|
+
import { createTitleBox, colors } from './table-formatter.js';
|
|
8
9
|
export async function runCLI(argv) {
|
|
9
10
|
const parsed = parseArgs(argv);
|
|
10
11
|
const { command, params, flags } = parsed;
|
|
@@ -70,45 +71,49 @@ export async function runCLI(argv) {
|
|
|
70
71
|
}
|
|
71
72
|
}
|
|
72
73
|
function displayResults(data) {
|
|
73
|
-
logger.log('
|
|
74
|
-
logger.log('CURSOR USAGE SUMMARY');
|
|
75
|
-
logger.log('='.repeat(70));
|
|
74
|
+
logger.log(createTitleBox('Cursor Usage Summary'));
|
|
76
75
|
// Membership and plan info
|
|
77
|
-
logger.log('
|
|
78
|
-
logger.log(` Membership:
|
|
79
|
-
logger.log(` Billing Cycle: ${formatDate(data.billingCycleStart)} to ${formatDate(data.billingCycleEnd)}`);
|
|
76
|
+
logger.log(colors.cyan('📋 ACCOUNT INFORMATION'));
|
|
77
|
+
logger.log(` ${colors.dim('Membership:')} ${colors.yellow(data.membershipType || 'N/A')}`);
|
|
78
|
+
logger.log(` ${colors.dim('Billing Cycle:')} ${colors.yellow(formatDate(data.billingCycleStart))} to ${colors.yellow(formatDate(data.billingCycleEnd))}`);
|
|
80
79
|
// Plan usage
|
|
81
80
|
const plan = data.individualUsage?.plan;
|
|
82
81
|
if (plan) {
|
|
83
|
-
logger.log('
|
|
84
|
-
logger.log(
|
|
85
|
-
|
|
86
|
-
logger.log(`
|
|
87
|
-
logger.log(`
|
|
88
|
-
logger.log(`
|
|
89
|
-
logger.log(` -
|
|
82
|
+
logger.log('');
|
|
83
|
+
logger.log(colors.cyan('📊 PLAN USAGE'));
|
|
84
|
+
const percentage = plan.limit > 0 ? ((plan.used / plan.limit) * 100).toFixed(2) : '0.00';
|
|
85
|
+
logger.log(` ${colors.dim('Used:')} ${colors.yellow(`${plan.used} / ${plan.limit}`)} (${colors.green(percentage + '%')})`);
|
|
86
|
+
logger.log(` ${colors.dim('Remaining:')} ${colors.yellow(String(plan.limit - plan.used))}`);
|
|
87
|
+
logger.log(` ${colors.dim('Breakdown:')}`);
|
|
88
|
+
logger.log(` ${colors.dim('- Included:')} ${colors.yellow(String(plan.breakdown?.included || 0))}`);
|
|
89
|
+
logger.log(` ${colors.dim('- Bonus:')} ${colors.yellow(String(plan.breakdown?.bonus || 0))}`);
|
|
90
|
+
logger.log(` ${colors.dim('- Total:')} ${colors.yellow(String(plan.breakdown?.total || 0))}`);
|
|
90
91
|
}
|
|
91
92
|
// On-demand usage
|
|
92
93
|
const onDemand = data.individualUsage?.onDemand;
|
|
93
94
|
if (onDemand && onDemand.enabled) {
|
|
94
|
-
logger.log('
|
|
95
|
-
logger.log(
|
|
95
|
+
logger.log('');
|
|
96
|
+
logger.log(colors.cyan('💰 ON-DEMAND USAGE'));
|
|
97
|
+
logger.log(` ${colors.dim('Used:')} ${colors.yellow(String(onDemand.used))}`);
|
|
96
98
|
if (onDemand.limit !== null) {
|
|
97
|
-
logger.log(` Limit:
|
|
98
|
-
|
|
99
|
+
logger.log(` ${colors.dim('Limit:')} ${colors.yellow(String(onDemand.limit))}`);
|
|
100
|
+
const remaining = onDemand.remaining !== null ? onDemand.remaining : (onDemand.limit - onDemand.used);
|
|
101
|
+
logger.log(` ${colors.dim('Remaining:')} ${colors.yellow(String(remaining))}`);
|
|
99
102
|
}
|
|
100
|
-
logger.log(` Status: Enabled`);
|
|
103
|
+
logger.log(` ${colors.dim('Status:')} ${colors.green('Enabled')}`);
|
|
101
104
|
}
|
|
102
105
|
// Display messages
|
|
103
106
|
if (data.autoModelSelectedDisplayMessage) {
|
|
104
|
-
logger.log('
|
|
105
|
-
logger.log(
|
|
107
|
+
logger.log('');
|
|
108
|
+
logger.log(colors.cyan('📢 AUTO MODEL MESSAGE'));
|
|
109
|
+
logger.log(` ${colors.dim(data.autoModelSelectedDisplayMessage)}`);
|
|
106
110
|
}
|
|
107
111
|
if (data.namedModelSelectedDisplayMessage) {
|
|
108
|
-
logger.log('
|
|
109
|
-
logger.log(
|
|
112
|
+
logger.log('');
|
|
113
|
+
logger.log(colors.cyan('💬 NAMED MODEL MESSAGE'));
|
|
114
|
+
logger.log(` ${colors.dim(data.namedModelSelectedDisplayMessage)}`);
|
|
110
115
|
}
|
|
111
|
-
logger.log('
|
|
116
|
+
logger.log('');
|
|
112
117
|
}
|
|
113
118
|
function formatDate(dateString) {
|
|
114
119
|
try {
|
package/dist/commands.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* CLI command handlers
|
|
3
3
|
*/
|
|
4
4
|
import { fetchUsageEvents, groupByDay, calculateDailyStats, groupByMonth, calculateMonthlyStats, formatMonthlyStatsTable, groupByWeek, calculateWeeklyStats, formatWeeklyStatsTable, calculateModelBreakdown, formatModelBreakdownTable, } from './event-loader.js';
|
|
5
|
-
import { createTable } from './table-formatter.js';
|
|
5
|
+
import { createTable, createTitleBox, formatNumber, formatCurrency, formatTotalsRow, } from './table-formatter.js';
|
|
6
6
|
import { logger } from './logger.js';
|
|
7
7
|
/**
|
|
8
8
|
* Convert stats to JSON
|
|
@@ -45,11 +45,9 @@ function displayBreakdown(events, title = 'MODEL BREAKDOWN') {
|
|
|
45
45
|
const breakdown = calculateModelBreakdown(events);
|
|
46
46
|
const totalTokens = events.reduce((sum, e) => sum + e.tokens, 0);
|
|
47
47
|
const totalCost = events.reduce((sum, e) => sum + (e.cost || 0), 0);
|
|
48
|
-
logger.log(
|
|
49
|
-
logger.log(title);
|
|
50
|
-
logger.log('='.repeat(100) + '\n');
|
|
48
|
+
logger.log(createTitleBox(title));
|
|
51
49
|
const tableData = formatModelBreakdownTable(breakdown, totalTokens, totalCost);
|
|
52
|
-
const table = createTable(['Model', 'Events', 'Total Tokens', 'Cost', 'Token %', 'Cost %'], tableData);
|
|
50
|
+
const table = createTable(['Model', 'Events', 'Total Tokens', 'Cost', 'Token %', 'Cost %'], tableData, ['left', 'right', 'right', 'right', 'right', 'right']);
|
|
53
51
|
logger.log(table + '\n');
|
|
54
52
|
}
|
|
55
53
|
/**
|
|
@@ -80,12 +78,8 @@ export async function showDailyReport(credentials, days = 7, options = {}, outpu
|
|
|
80
78
|
console.log(jsonOutput);
|
|
81
79
|
}
|
|
82
80
|
else {
|
|
83
|
-
// Display
|
|
84
|
-
logger.log(
|
|
85
|
-
// Display table
|
|
86
|
-
logger.log('\n' + '='.repeat(100));
|
|
87
|
-
logger.log(`DAILY USAGE REPORT (Last ${days} days)`);
|
|
88
|
-
logger.log('='.repeat(100) + '\n');
|
|
81
|
+
// Display title box
|
|
82
|
+
logger.log(createTitleBox(`Cursor Usage Report - Daily (Last ${days} days)`));
|
|
89
83
|
const tableData = stats.map((day) => {
|
|
90
84
|
const modelList = Array.from(day.models.entries())
|
|
91
85
|
.map(([model, count]) => `${model}(${count})`)
|
|
@@ -93,26 +87,31 @@ export async function showDailyReport(credentials, days = 7, options = {}, outpu
|
|
|
93
87
|
return [
|
|
94
88
|
day.date,
|
|
95
89
|
day.eventCount.toString(),
|
|
96
|
-
day.totalTokens
|
|
97
|
-
day.inputTokens
|
|
98
|
-
day.outputTokens
|
|
99
|
-
|
|
90
|
+
formatNumber(day.totalTokens),
|
|
91
|
+
formatNumber(day.inputTokens),
|
|
92
|
+
formatNumber(day.outputTokens),
|
|
93
|
+
formatCurrency(day.totalCost),
|
|
100
94
|
modelList || 'N/A',
|
|
101
95
|
];
|
|
102
96
|
});
|
|
103
|
-
|
|
104
|
-
logger.log(table);
|
|
105
|
-
// Summary
|
|
97
|
+
// Calculate totals
|
|
106
98
|
const totalEvents = stats.reduce((sum, day) => sum + day.eventCount, 0);
|
|
107
99
|
const totalTokens = stats.reduce((sum, day) => sum + day.totalTokens, 0);
|
|
100
|
+
const totalInputTokens = stats.reduce((sum, day) => sum + day.inputTokens, 0);
|
|
101
|
+
const totalOutputTokens = stats.reduce((sum, day) => sum + day.outputTokens, 0);
|
|
108
102
|
const totalCost = stats.reduce((sum, day) => sum + day.totalCost, 0);
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
103
|
+
// Add totals row
|
|
104
|
+
tableData.push(formatTotalsRow([
|
|
105
|
+
'Total',
|
|
106
|
+
totalEvents.toString(),
|
|
107
|
+
formatNumber(totalTokens),
|
|
108
|
+
formatNumber(totalInputTokens),
|
|
109
|
+
formatNumber(totalOutputTokens),
|
|
110
|
+
formatCurrency(totalCost),
|
|
111
|
+
'',
|
|
112
|
+
]));
|
|
113
|
+
const table = createTable(['Date', 'Events', 'Total Tokens', 'Input', 'Output', 'Cost', 'Models'], tableData, ['left', 'right', 'right', 'right', 'right', 'right', 'left']);
|
|
114
|
+
logger.log(table + '\n');
|
|
116
115
|
// Show breakdown if requested
|
|
117
116
|
if (options.breakdown) {
|
|
118
117
|
displayBreakdown(events, 'PER-MODEL BREAKDOWN');
|
|
@@ -148,27 +147,27 @@ export async function showMonthlyReport(credentials, months = 3, options = {}, o
|
|
|
148
147
|
console.log(jsonOutput);
|
|
149
148
|
}
|
|
150
149
|
else {
|
|
151
|
-
// Display
|
|
152
|
-
logger.log(
|
|
153
|
-
// Display table
|
|
154
|
-
logger.log('\n' + '='.repeat(100));
|
|
155
|
-
logger.log(`MONTHLY USAGE REPORT (Last ${months} months)`);
|
|
156
|
-
logger.log('='.repeat(100) + '\n');
|
|
150
|
+
// Display title box
|
|
151
|
+
logger.log(createTitleBox(`Cursor Usage Report - Monthly (Last ${months} months)`));
|
|
157
152
|
const tableData = formatMonthlyStatsTable(stats);
|
|
158
|
-
|
|
159
|
-
logger.log(table);
|
|
160
|
-
// Summary
|
|
153
|
+
// Calculate totals
|
|
161
154
|
const totalEvents = stats.reduce((sum, month) => sum + month.eventCount, 0);
|
|
162
155
|
const totalTokens = stats.reduce((sum, month) => sum + month.totalTokens, 0);
|
|
156
|
+
const totalInputTokens = stats.reduce((sum, month) => sum + month.inputTokens, 0);
|
|
157
|
+
const totalOutputTokens = stats.reduce((sum, month) => sum + month.outputTokens, 0);
|
|
163
158
|
const totalCost = stats.reduce((sum, month) => sum + month.totalCost, 0);
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
159
|
+
// Add totals row
|
|
160
|
+
tableData.push(formatTotalsRow([
|
|
161
|
+
'Total',
|
|
162
|
+
totalEvents.toString(),
|
|
163
|
+
formatNumber(totalTokens),
|
|
164
|
+
formatNumber(totalInputTokens),
|
|
165
|
+
formatNumber(totalOutputTokens),
|
|
166
|
+
formatCurrency(totalCost),
|
|
167
|
+
'',
|
|
168
|
+
]));
|
|
169
|
+
const table = createTable(['Month', 'Events', 'Total Tokens', 'Input', 'Output', 'Cost', 'Models'], tableData, ['left', 'right', 'right', 'right', 'right', 'right', 'left']);
|
|
170
|
+
logger.log(table + '\n');
|
|
172
171
|
// Show breakdown if requested
|
|
173
172
|
if (options.breakdown) {
|
|
174
173
|
displayBreakdown(events, 'PER-MODEL BREAKDOWN');
|
|
@@ -203,27 +202,27 @@ export async function showWeeklyReport(credentials, weeks = 4, options = {}, out
|
|
|
203
202
|
console.log(jsonOutput);
|
|
204
203
|
}
|
|
205
204
|
else {
|
|
206
|
-
// Display
|
|
207
|
-
logger.log(
|
|
208
|
-
// Display table
|
|
209
|
-
logger.log('\n' + '='.repeat(120));
|
|
210
|
-
logger.log(`WEEKLY USAGE REPORT (Last ${weeks} weeks)`);
|
|
211
|
-
logger.log('='.repeat(120) + '\n');
|
|
205
|
+
// Display title box
|
|
206
|
+
logger.log(createTitleBox(`Cursor Usage Report - Weekly (Last ${weeks} weeks)`));
|
|
212
207
|
const tableData = formatWeeklyStatsTable(stats);
|
|
213
|
-
|
|
214
|
-
logger.log(table);
|
|
215
|
-
// Summary
|
|
208
|
+
// Calculate totals
|
|
216
209
|
const totalEvents = stats.reduce((sum, week) => sum + week.eventCount, 0);
|
|
217
210
|
const totalTokens = stats.reduce((sum, week) => sum + week.totalTokens, 0);
|
|
211
|
+
const totalInputTokens = stats.reduce((sum, week) => sum + week.inputTokens, 0);
|
|
212
|
+
const totalOutputTokens = stats.reduce((sum, week) => sum + week.outputTokens, 0);
|
|
218
213
|
const totalCost = stats.reduce((sum, week) => sum + week.totalCost, 0);
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
214
|
+
// Add totals row
|
|
215
|
+
tableData.push(formatTotalsRow([
|
|
216
|
+
'Total',
|
|
217
|
+
totalEvents.toString(),
|
|
218
|
+
formatNumber(totalTokens),
|
|
219
|
+
formatNumber(totalInputTokens),
|
|
220
|
+
formatNumber(totalOutputTokens),
|
|
221
|
+
formatCurrency(totalCost),
|
|
222
|
+
'',
|
|
223
|
+
]));
|
|
224
|
+
const table = createTable(['Week', 'Events', 'Total Tokens', 'Input', 'Output', 'Cost', 'Models'], tableData, ['left', 'right', 'right', 'right', 'right', 'right', 'left']);
|
|
225
|
+
logger.log(table + '\n');
|
|
227
226
|
// Show breakdown if requested
|
|
228
227
|
if (options.breakdown) {
|
|
229
228
|
displayBreakdown(events, 'PER-MODEL BREAKDOWN');
|
|
@@ -249,34 +248,37 @@ export async function showDateReport(credentials, date, options = {}, outputJson
|
|
|
249
248
|
console.log(jsonOutput);
|
|
250
249
|
}
|
|
251
250
|
else {
|
|
252
|
-
// Display
|
|
253
|
-
logger.log(
|
|
254
|
-
// Display detailed events
|
|
255
|
-
logger.log('\n' + '='.repeat(120));
|
|
256
|
-
logger.log(`USAGE EVENTS FOR ${date.toDateString()}`);
|
|
257
|
-
logger.log('='.repeat(120) + '\n');
|
|
251
|
+
// Display title box
|
|
252
|
+
logger.log(createTitleBox(`Cursor Usage Report - ${date.toDateString()}`));
|
|
258
253
|
const tableData = events.map((event) => {
|
|
259
254
|
const timestamp = new Date(event.timestamp).toLocaleTimeString();
|
|
260
255
|
return [
|
|
261
256
|
timestamp,
|
|
262
257
|
event.model,
|
|
263
258
|
event.type,
|
|
264
|
-
event.inputTokens
|
|
265
|
-
event.outputTokens
|
|
266
|
-
event.tokens
|
|
267
|
-
|
|
259
|
+
formatNumber(event.inputTokens),
|
|
260
|
+
formatNumber(event.outputTokens),
|
|
261
|
+
formatNumber(event.tokens),
|
|
262
|
+
formatCurrency(event.cost || 0),
|
|
268
263
|
];
|
|
269
264
|
});
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
265
|
+
// Calculate totals
|
|
266
|
+
const totalInputTokens = events.reduce((sum, e) => sum + e.inputTokens, 0);
|
|
267
|
+
const totalOutputTokens = events.reduce((sum, e) => sum + e.outputTokens, 0);
|
|
273
268
|
const totalTokens = events.reduce((sum, e) => sum + e.tokens, 0);
|
|
274
269
|
const totalCost = events.reduce((sum, e) => sum + (e.cost || 0), 0);
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
270
|
+
// Add totals row
|
|
271
|
+
tableData.push(formatTotalsRow([
|
|
272
|
+
'Total',
|
|
273
|
+
'',
|
|
274
|
+
'',
|
|
275
|
+
formatNumber(totalInputTokens),
|
|
276
|
+
formatNumber(totalOutputTokens),
|
|
277
|
+
formatNumber(totalTokens),
|
|
278
|
+
formatCurrency(totalCost),
|
|
279
|
+
]));
|
|
280
|
+
const table = createTable(['Time', 'Model', 'Type', 'Input Tokens', 'Output Tokens', 'Total Tokens', 'Cost'], tableData, ['left', 'left', 'left', 'right', 'right', 'right', 'right']);
|
|
281
|
+
logger.log(table + '\n');
|
|
280
282
|
// Show breakdown if requested
|
|
281
283
|
if (options.breakdown) {
|
|
282
284
|
displayBreakdown(events, 'PER-MODEL BREAKDOWN');
|
|
@@ -1,16 +1,74 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Table formatting utilities with polished Unicode styling
|
|
3
|
+
* Inspired by ccusage (https://github.com/ryoppippi/ccusage)
|
|
3
4
|
*/
|
|
5
|
+
export type TableCellAlign = 'left' | 'right' | 'center';
|
|
4
6
|
export interface TableOptions {
|
|
5
7
|
headers: string[];
|
|
6
|
-
rows: string[][];
|
|
7
|
-
|
|
8
|
+
rows: (string | number)[][];
|
|
9
|
+
colAligns?: TableCellAlign[];
|
|
10
|
+
headerStyle?: 'cyan' | 'yellow' | 'green' | 'magenta' | 'white';
|
|
8
11
|
}
|
|
9
12
|
/**
|
|
10
|
-
* Format data as a
|
|
13
|
+
* Format data as a styled Unicode table
|
|
11
14
|
*/
|
|
12
15
|
export declare function formatTable(options: TableOptions): string;
|
|
13
16
|
/**
|
|
14
|
-
* Create a simple data table with borders
|
|
17
|
+
* Create a simple data table with Unicode borders
|
|
15
18
|
*/
|
|
16
|
-
export declare function createTable(headers: string[], rows: string[][]): string;
|
|
19
|
+
export declare function createTable(headers: string[], rows: (string | number)[][], colAligns?: TableCellAlign[]): string;
|
|
20
|
+
/**
|
|
21
|
+
* Format a number with locale-specific thousand separators
|
|
22
|
+
*/
|
|
23
|
+
export declare function formatNumber(num: number): string;
|
|
24
|
+
/**
|
|
25
|
+
* Format a number as USD currency
|
|
26
|
+
*/
|
|
27
|
+
export declare function formatCurrency(amount: number): string;
|
|
28
|
+
/**
|
|
29
|
+
* Create a styled title box for report headers
|
|
30
|
+
*/
|
|
31
|
+
export declare function createTitleBox(title: string): string;
|
|
32
|
+
/**
|
|
33
|
+
* Style text with color
|
|
34
|
+
*/
|
|
35
|
+
export declare const colors: {
|
|
36
|
+
cyan: (text: string) => string;
|
|
37
|
+
yellow: (text: string) => string;
|
|
38
|
+
green: (text: string) => string;
|
|
39
|
+
red: (text: string) => string;
|
|
40
|
+
gray: (text: string) => string;
|
|
41
|
+
bold: (text: string) => string;
|
|
42
|
+
dim: (text: string) => string;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Format a row with yellow highlighting (for totals)
|
|
46
|
+
*/
|
|
47
|
+
export declare function formatTotalsRow(cells: (string | number)[]): string[];
|
|
48
|
+
/**
|
|
49
|
+
* Create a usage report table with consistent styling
|
|
50
|
+
*/
|
|
51
|
+
export interface UsageReportOptions {
|
|
52
|
+
title: string;
|
|
53
|
+
headers: string[];
|
|
54
|
+
rows: (string | number)[][];
|
|
55
|
+
totals?: (string | number)[];
|
|
56
|
+
colAligns?: TableCellAlign[];
|
|
57
|
+
}
|
|
58
|
+
export declare function createUsageReportTable(options: UsageReportOptions): string;
|
|
59
|
+
/**
|
|
60
|
+
* Create a summary section with styled output
|
|
61
|
+
*/
|
|
62
|
+
export declare function createSummary(items: {
|
|
63
|
+
label: string;
|
|
64
|
+
value: string | number;
|
|
65
|
+
}[]): string;
|
|
66
|
+
/**
|
|
67
|
+
* Shorten model names for display
|
|
68
|
+
* e.g., "claude-sonnet-4-20250514" -> "sonnet-4"
|
|
69
|
+
*/
|
|
70
|
+
export declare function formatModelName(modelName: string): string;
|
|
71
|
+
/**
|
|
72
|
+
* Format an array of model names for display
|
|
73
|
+
*/
|
|
74
|
+
export declare function formatModelsDisplay(models: string[]): string;
|
package/dist/table-formatter.js
CHANGED
|
@@ -1,40 +1,135 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Table formatting utilities with polished Unicode styling
|
|
3
|
+
* Inspired by ccusage (https://github.com/ryoppippi/ccusage)
|
|
3
4
|
*/
|
|
5
|
+
import Table from 'cli-table3';
|
|
6
|
+
import pc from 'picocolors';
|
|
4
7
|
/**
|
|
5
|
-
* Format data as a
|
|
8
|
+
* Format data as a styled Unicode table
|
|
6
9
|
*/
|
|
7
10
|
export function formatTable(options) {
|
|
8
|
-
const { headers, rows } = options;
|
|
11
|
+
const { headers, rows, colAligns, headerStyle = 'cyan' } = options;
|
|
9
12
|
if (rows.length === 0) {
|
|
10
13
|
return 'No data to display';
|
|
11
14
|
}
|
|
12
|
-
//
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
// Create table with styled headers
|
|
16
|
+
const table = new Table({
|
|
17
|
+
head: headers,
|
|
18
|
+
style: { head: [headerStyle] },
|
|
19
|
+
colAligns: colAligns || headers.map(() => 'left'),
|
|
20
|
+
wordWrap: true,
|
|
17
21
|
});
|
|
18
|
-
//
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
22
|
+
// Add rows
|
|
23
|
+
for (const row of rows) {
|
|
24
|
+
table.push(row.map((cell) => String(cell)));
|
|
25
|
+
}
|
|
26
|
+
return table.toString();
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Create a simple data table with Unicode borders
|
|
30
|
+
*/
|
|
31
|
+
export function createTable(headers, rows, colAligns) {
|
|
32
|
+
return formatTable({ headers, rows, colAligns });
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Format a number with locale-specific thousand separators
|
|
36
|
+
*/
|
|
37
|
+
export function formatNumber(num) {
|
|
38
|
+
return num.toLocaleString('en-US');
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Format a number as USD currency
|
|
42
|
+
*/
|
|
43
|
+
export function formatCurrency(amount) {
|
|
44
|
+
return `$${amount.toFixed(2)}`;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Create a styled title box for report headers
|
|
48
|
+
*/
|
|
49
|
+
export function createTitleBox(title) {
|
|
50
|
+
const innerWidth = Math.max(title.length + 4, 40);
|
|
51
|
+
const padding = Math.floor((innerWidth - title.length) / 2);
|
|
52
|
+
const extraPadding = (innerWidth - title.length) % 2;
|
|
53
|
+
const top = '╭' + '─'.repeat(innerWidth) + '╮';
|
|
54
|
+
const middle = '│' + ' '.repeat(padding) + title + ' '.repeat(padding + extraPadding) + '│';
|
|
55
|
+
const bottom = '╰' + '─'.repeat(innerWidth) + '╯';
|
|
56
|
+
return '\n' + top + '\n' + middle + '\n' + bottom + '\n';
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Style text with color
|
|
60
|
+
*/
|
|
61
|
+
export const colors = {
|
|
62
|
+
cyan: (text) => pc.cyan(text),
|
|
63
|
+
yellow: (text) => pc.yellow(text),
|
|
64
|
+
green: (text) => pc.green(text),
|
|
65
|
+
red: (text) => pc.red(text),
|
|
66
|
+
gray: (text) => pc.gray(text),
|
|
67
|
+
bold: (text) => pc.bold(text),
|
|
68
|
+
dim: (text) => pc.dim(text),
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Format a row with yellow highlighting (for totals)
|
|
72
|
+
*/
|
|
73
|
+
export function formatTotalsRow(cells) {
|
|
74
|
+
return cells.map((cell) => pc.yellow(String(cell)));
|
|
75
|
+
}
|
|
76
|
+
export function createUsageReportTable(options) {
|
|
77
|
+
const { title, headers, rows, totals, colAligns } = options;
|
|
78
|
+
const output = [];
|
|
79
|
+
// Title box
|
|
80
|
+
output.push(createTitleBox(title));
|
|
81
|
+
// Create table
|
|
82
|
+
const table = new Table({
|
|
83
|
+
head: headers,
|
|
84
|
+
style: { head: ['cyan'] },
|
|
85
|
+
colAligns: colAligns || headers.map((_, i) => (i === 0 ? 'left' : 'right')),
|
|
86
|
+
wordWrap: true,
|
|
87
|
+
});
|
|
88
|
+
// Add data rows
|
|
89
|
+
for (const row of rows) {
|
|
90
|
+
table.push(row.map((cell) => String(cell)));
|
|
91
|
+
}
|
|
92
|
+
// Add totals row with yellow highlighting
|
|
93
|
+
if (totals) {
|
|
94
|
+
const formattedTotals = totals.map((cell, i) => i === 0 ? pc.yellow(String(cell)) : pc.yellow(String(cell)));
|
|
95
|
+
table.push(formattedTotals);
|
|
96
|
+
}
|
|
97
|
+
output.push(table.toString());
|
|
98
|
+
return output.join('\n');
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Create a summary section with styled output
|
|
102
|
+
*/
|
|
103
|
+
export function createSummary(items) {
|
|
104
|
+
const maxLabelWidth = Math.max(...items.map((item) => item.label.length));
|
|
105
|
+
const lines = items.map((item) => {
|
|
106
|
+
const label = pc.cyan(item.label.padEnd(maxLabelWidth));
|
|
107
|
+
const value = pc.yellow(String(item.value));
|
|
108
|
+
return ` ${label} ${value}`;
|
|
109
|
+
});
|
|
110
|
+
return lines.join('\n');
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Shorten model names for display
|
|
114
|
+
* e.g., "claude-sonnet-4-20250514" -> "sonnet-4"
|
|
115
|
+
*/
|
|
116
|
+
export function formatModelName(modelName) {
|
|
117
|
+
// Match claude-{type}-{version}-{date} pattern
|
|
118
|
+
const match = modelName.match(/^claude-(\w+)-([\d-]+)-(\d{8})$/);
|
|
119
|
+
if (match) {
|
|
120
|
+
return `${match[1]}-${match[2]}`;
|
|
121
|
+
}
|
|
122
|
+
// Match claude-{type}-{version} without date
|
|
123
|
+
const noDateMatch = modelName.match(/^claude-(\w+)-([\d-]+)$/);
|
|
124
|
+
if (noDateMatch) {
|
|
125
|
+
return `${noDateMatch[1]}-${noDateMatch[2]}`;
|
|
126
|
+
}
|
|
127
|
+
return modelName;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Format an array of model names for display
|
|
131
|
+
*/
|
|
132
|
+
export function formatModelsDisplay(models) {
|
|
133
|
+
const uniqueModels = [...new Set(models.map(formatModelName))];
|
|
134
|
+
return uniqueModels.sort().join(', ');
|
|
40
135
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cursor-usage",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.4",
|
|
5
5
|
"description": "CLI tool for analyzing Cursor usage and token consumption",
|
|
6
6
|
"author": "yifen <anthonyeef@gmail.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -40,12 +40,14 @@
|
|
|
40
40
|
"format": "prettier --write src/"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
+
"cli-table3": "^0.6.5",
|
|
44
|
+
"picocolors": "^1.1.1",
|
|
43
45
|
"sql.js": "^1.8.0"
|
|
44
46
|
},
|
|
45
47
|
"devDependencies": {
|
|
46
48
|
"@types/node": "^20.10.6",
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
+
"tsx": "^4.7.0",
|
|
50
|
+
"typescript": "^5.3.3"
|
|
49
51
|
},
|
|
50
52
|
"engines": {
|
|
51
53
|
"node": ">=18.0.0"
|