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 CHANGED
@@ -1,15 +1,18 @@
1
1
  # Cursor Usage Analyzer
2
2
 
3
+ ![Cursor Usage Banner](banner.png)
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
- - Show usage breakdown (included vs bonus)
11
- - Track percentage of included usage consumed
12
- - Colorful terminal output
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
- ACCOUNT INFORMATION:
103
- Membership: pro
104
- Billing Cycle: <your-billing-cycle>
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: 150 / 2000 (7.50%)
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
- ✓ Found 3 usage events
115
-
116
- ====================================================================================================
117
- DAILY USAGE REPORT (Last 7 days)
118
- ====================================================================================================
119
-
120
- +------------+--------+--------------+-------+--------+-------+----------------------------------+
121
- | Date | Events | Total Tokens | Input | Output | Cost | Models |
122
- +------------+--------+--------------+-------+--------+-------+----------------------------------+
123
- | 2026-01-11 | 3 | 539,301 | 381 | 7,081 | $0.82 | claude-4.5-opus-high-thinking(3) |
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
- MONTHLY USAGE REPORT (Last 3 months)
140
- ====================================================================================================
141
-
142
- +----------+--------+---------------+-------+-------+-------+------------------------------+
143
- | Month | Events | Total Tokens | Input | Output| Cost | Models |
144
- +----------+--------+---------------+-------+-------+-------+------------------------------+
145
- | Month 1 | 50 | 5,000,000 | 50000 | 30000 | $4.50 | claude-sonnet(50) |
146
- | Month 2 | 75 | 8,500,000 | 75000 | 45000 | $6.80 | claude-opus(75) |
147
- | Month 3 | 25 | 2,500,000 | 20000 | 10000 | $2.00 | claude-haiku(25) |
148
- +----------+--------+---------------+-------+-------+-------+------------------------------+
149
-
150
- ====================================================================================================
151
- SUMMARY
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
- USAGE EVENTS FOR Today
165
- ========================================================================================================================
166
-
167
- +----------+---------------+-------+--------------+---------------+--------------+---------+
168
- | Time | Model | Type | Input Tokens | Output Tokens | Total Tokens | Cost |
169
- +----------+---------------+-------+--------------+---------------+--------------+---------+
170
- | 2:15 pm | claude-opus | usage | 500 | 1,200 | 1,700 | $0.0850 |
171
- | 1:45 pm | claude-sonnet | usage | 300 | 800 | 1,100 | $0.0440 |
172
- | 1:20 pm | claude-haiku | usage | 100 | 250 | 350 | $0.0070 |
173
- +----------+---------------+-------+--------------+---------------+--------------+---------+
174
-
175
- ========================================================================================================================
176
- Total Events: 3
177
- Total Tokens: 3,150
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
- | Model | Events | Total Tokens | Input | Output| Cost | Token % | Cost % |
193
- +---------------+--------+--------------+-------+-------+-------+---------+------------+
194
- | claude-opus | 15 | 2,500,000 | 20000 | 15000 | $2.00 | 50.00% | 40.00% |
195
- | claude-sonnet | 10 | 2,000,000 | 15000 | 12000 | $1.60 | 40.00% | 32.00% |
196
- | claude-haiku | 5 | 500,000 | 5000 | 3000 | $0.40 | 10.00% | 8.00% |
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
- ### Compact Table Format (`--compact`)
243
-
244
- The `--compact` flag provides a more condensed table layout:
240
+ ### Weekly Report View
245
241
 
246
242
  ```
247
- ==================================================================================================================
248
- DAILY USAGE REPORT (Last 7 days)
249
- ==================================================================================================================
250
-
251
- Date Events Tokens Cost Models
252
- 2026-01-11 3 539,301 $0.82 claude-4.5-opus-high-thinking(3)
253
- 2026-01-10 5 1,200,450 $1.45 claude-sonnet(4), claude-opus(1)
254
- 2026-01-09 2 89,320 $0.12 claude-haiku(2)
255
-
256
- ==================================================================================================================
257
- SUMMARY
258
- ==================================================================================================================
259
- Total Events: 10
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 usage reports with table formatting
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
- - ✅ Colorized CLI output
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('='.repeat(70));
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('\n📋 ACCOUNT INFORMATION:');
78
- logger.log(` Membership: ${data.membershipType || 'N/A'}`);
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('\n📊 PLAN USAGE:');
84
- logger.log(` Used: ${plan.used} / ${plan.limit} (${plan.limit > 0 ? ((plan.used / plan.limit) * 100).toFixed(2) : '0.00'}%)`);
85
- logger.log(` Remaining: ${plan.limit - plan.used}`);
86
- logger.log(` Breakdown:`);
87
- logger.log(` - Included: ${plan.breakdown?.included || 0}`);
88
- logger.log(` - Bonus: ${plan.breakdown?.bonus || 0}`);
89
- logger.log(` - Total: ${plan.breakdown?.total || 0}`);
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('\n💰 ON-DEMAND USAGE:');
95
- logger.log(` Used: ${onDemand.used}`);
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: ${onDemand.limit}`);
98
- logger.log(` Remaining: ${onDemand.remaining !== null ? onDemand.remaining : (onDemand.limit - onDemand.used)}`);
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('\n📢 AUTO MODEL MESSAGE:');
105
- logger.log(` ${data.autoModelSelectedDisplayMessage}`);
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('\n💬 NAMED MODEL MESSAGE:');
109
- logger.log(` ${data.namedModelSelectedDisplayMessage}`);
112
+ logger.log('');
113
+ logger.log(colors.cyan('💬 NAMED MODEL MESSAGE'));
114
+ logger.log(` ${colors.dim(data.namedModelSelectedDisplayMessage)}`);
110
115
  }
111
- logger.log('\n' + '='.repeat(70) + '\n');
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('\n' + '='.repeat(100));
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 table and summary
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.toLocaleString(),
97
- day.inputTokens.toLocaleString(),
98
- day.outputTokens.toLocaleString(),
99
- `$${day.totalCost.toFixed(2)}`,
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
- const table = createTable(['Date', 'Events', 'Total Tokens', 'Input', 'Output', 'Cost', 'Models'], tableData);
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
- logger.log('\n' + '='.repeat(100));
110
- logger.log('SUMMARY');
111
- logger.log('='.repeat(100));
112
- logger.log(`Total Events: ${totalEvents}`);
113
- logger.log(`Total Tokens: ${totalTokens.toLocaleString()}`);
114
- logger.log(`Total Cost: $${totalCost.toFixed(2)}`);
115
- logger.log('='.repeat(100) + '\n');
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 table and summary
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
- const table = createTable(['Month', 'Events', 'Total Tokens', 'Input', 'Output', 'Cost', 'Models'], tableData);
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
- logger.log('\n' + '='.repeat(100));
165
- logger.log('SUMMARY');
166
- logger.log('='.repeat(100));
167
- logger.log(`Total Events: ${totalEvents}`);
168
- logger.log(`Total Tokens: ${totalTokens.toLocaleString()}`);
169
- logger.log(`Total Cost: $${totalCost.toFixed(2)}`);
170
- logger.log(`Average per month: ${(totalTokens / Math.max(1, stats.length)).toLocaleString()} tokens, $${(totalCost / Math.max(1, stats.length)).toFixed(2)}`);
171
- logger.log('='.repeat(100) + '\n');
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 table and summary
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
- const table = createTable(['Week', 'Events', 'Total Tokens', 'Input', 'Output', 'Cost', 'Models'], tableData);
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
- logger.log('\n' + '='.repeat(120));
220
- logger.log('SUMMARY');
221
- logger.log('='.repeat(120));
222
- logger.log(`Total Events: ${totalEvents}`);
223
- logger.log(`Total Tokens: ${totalTokens.toLocaleString()}`);
224
- logger.log(`Total Cost: $${totalCost.toFixed(2)}`);
225
- logger.log(`Average per week: ${(totalTokens / Math.max(1, stats.length)).toLocaleString()} tokens, $${(totalCost / Math.max(1, stats.length)).toFixed(2)}`);
226
- logger.log('='.repeat(120) + '\n');
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 table and summary
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.toLocaleString(),
265
- event.outputTokens.toLocaleString(),
266
- event.tokens.toLocaleString(),
267
- `$${event.cost?.toFixed(4) || '0.0000'}`,
259
+ formatNumber(event.inputTokens),
260
+ formatNumber(event.outputTokens),
261
+ formatNumber(event.tokens),
262
+ formatCurrency(event.cost || 0),
268
263
  ];
269
264
  });
270
- const table = createTable(['Time', 'Model', 'Type', 'Input Tokens', 'Output Tokens', 'Total Tokens', 'Cost'], tableData);
271
- logger.log(table);
272
- // Summary
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
- logger.log('\n' + '='.repeat(120));
276
- logger.log(`Total Events: ${events.length}`);
277
- logger.log(`Total Tokens: ${totalTokens.toLocaleString()}`);
278
- logger.log(`Total Cost: $${totalCost.toFixed(2)}`);
279
- logger.log('='.repeat(120) + '\n');
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
- * Simple table formatting utilities
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
- maxWidth?: number;
8
+ rows: (string | number)[][];
9
+ colAligns?: TableCellAlign[];
10
+ headerStyle?: 'cyan' | 'yellow' | 'green' | 'magenta' | 'white';
8
11
  }
9
12
  /**
10
- * Format data as a simple ASCII table
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;
@@ -1,40 +1,135 @@
1
1
  /**
2
- * Simple table formatting utilities
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 simple ASCII table
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
- // 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);
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
- // 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 });
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.3",
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
- "typescript": "^5.3.3",
48
- "tsx": "^4.7.0"
49
+ "tsx": "^4.7.0",
50
+ "typescript": "^5.3.3"
49
51
  },
50
52
  "engines": {
51
53
  "node": ">=18.0.0"