cursor-usage 0.1.2 → 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
 
@@ -17,6 +20,26 @@ A CLI tool for analyzing Cursor API usage and membership information, inspired b
17
20
  npm install
18
21
  ```
19
22
 
23
+ ## Quick Test
24
+
25
+ You can test the package without installing it locally using npx or bunx:
26
+
27
+ ```bash
28
+ # Test with npx (Node.js)
29
+ npx cursor-usage@latest
30
+
31
+ # Test with bunx (Bun runtime)
32
+ bunx cursor-usage@latest
33
+
34
+ # Test specific commands
35
+ npx cursor-usage@latest daily
36
+ npx cursor-usage@latest monthly
37
+
38
+ # Test with flags
39
+ npx cursor-usage@latest daily --breakdown
40
+ npx cursor-usage@latest weekly --json
41
+ ```
42
+
20
43
  ## Usage
21
44
 
22
45
  ### Commands
@@ -44,8 +67,28 @@ npm run dev -- today
44
67
 
45
68
  # Show help
46
69
  npm run dev -- help
70
+
71
+ # Show usage with date range
72
+ npm run dev -- daily --since 2026-01-01 --until 2026-01-15
73
+
74
+ # Show usage with model breakdown
75
+ npm run dev -- daily --breakdown
76
+
77
+ # Show compact table format
78
+ npm run dev -- monthly --compact
79
+
80
+ # Output as JSON (detailed reports only)
81
+ npm run dev -- daily --json
47
82
  ```
48
83
 
84
+ ### Flags
85
+
86
+ - `--since DATE` - Start date (YYYY-MM-DD)
87
+ - `--until DATE` - End date (YYYY-MM-DD)
88
+ - `--breakdown` - Show per-model breakdown
89
+ - `--json` - Output as JSON (machine-readable format)
90
+ - `--compact` - Compact table format
91
+
49
92
  ### Development
50
93
 
51
94
  ```bash
@@ -59,98 +102,158 @@ npm run build # Compile TypeScript
59
102
  ### Summary View
60
103
 
61
104
  ```
62
- 📊 ACCOUNT INFORMATION:
63
- Membership: pro
64
- Billing Cycle: <your-billing-cycle>
105
+ ╭────────────────────────────────────────╮
106
+ │ Cursor Usage Summary │
107
+ ╰────────────────────────────────────────╯
65
108
 
66
- 📊 PLAN USAGE:
67
- Used: 150 / 2000 (7.50%)
109
+ 📋 ACCOUNT INFORMATION
110
+ Membership: pro
111
+ Billing Cycle: Dec 29, 2025 to Jan 29, 2026
112
+
113
+ 📊 PLAN USAGE
114
+ Used: 150 / 2000 (7.50%)
68
115
  Remaining: 1850
116
+ Breakdown:
117
+ - Included: 150
118
+ - Bonus: 0
119
+ - Total: 150
69
120
  ```
70
121
 
71
122
  ### Daily Report View
72
123
 
73
124
  ```
74
- ✓ Found 3 usage events
75
-
76
- ====================================================================================================
77
- DAILY USAGE REPORT (Last 7 days)
78
- ====================================================================================================
79
-
80
- +------------+--------+--------------+-------+--------+-------+----------------------------------+
81
- | Date | Events | Total Tokens | Input | Output | Cost | Models |
82
- +------------+--------+--------------+-------+--------+-------+----------------------------------+
83
- | 2026-01-11 | 3 | 539,301 | 381 | 7,081 | $0.82 | claude-4.5-opus-high-thinking(3) |
84
- +------------+--------+--------------+-------+--------+-------+----------------------------------+
85
-
86
- ====================================================================================================
87
- SUMMARY
88
- ====================================================================================================
89
- Total Events: 3
90
- Total Tokens: 539,301
91
- Total Cost: $0.82
92
- ====================================================================================================
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
+ └────────────┴────────┴──────────────┴───────┴────────┴───────┴──────────────────────────────────┘
93
136
  ```
94
137
 
95
138
  ### Monthly Report View
96
139
 
97
140
  ```
98
- ====================================================================================================
99
- MONTHLY USAGE REPORT (Last 3 months)
100
- ====================================================================================================
101
-
102
- +----------+--------+---------------+-------+-------+-------+------------------------------+
103
- | Month | Events | Total Tokens | Input | Output| Cost | Models |
104
- +----------+--------+---------------+-------+-------+-------+------------------------------+
105
- | Month 1 | 50 | 5,000,000 | 50000 | 30000 | $4.50 | claude-sonnet(50) |
106
- | Month 2 | 75 | 8,500,000 | 75000 | 45000 | $6.80 | claude-opus(75) |
107
- | Month 3 | 25 | 2,500,000 | 20000 | 10000 | $2.00 | claude-haiku(25) |
108
- +----------+--------+---------------+-------+-------+-------+------------------------------+
109
-
110
- ====================================================================================================
111
- SUMMARY
112
- ====================================================================================================
113
- Total Events: 150
114
- Total Tokens: 16,000,000
115
- Total Cost: $13.30
116
- Average per month: 5,333,333 tokens, $4.43
117
- ====================================================================================================
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
+ └──────────┴────────┴───────────────┴─────────┴─────────┴────────┴─────────────────────┘
118
156
  ```
119
157
 
120
158
  ### Detailed Event View
121
159
 
122
160
  ```
123
- ========================================================================================================================
124
- USAGE EVENTS FOR Today
125
- ========================================================================================================================
126
-
127
- +----------+---------------+-------+--------------+---------------+--------------+---------+
128
- | Time | Model | Type | Input Tokens | Output Tokens | Total Tokens | Cost |
129
- +----------+---------------+-------+--------------+---------------+--------------+---------+
130
- | 2:15 pm | claude-opus | usage | 500 | 1,200 | 1,700 | $0.0850 |
131
- | 1:45 pm | claude-sonnet | usage | 300 | 800 | 1,100 | $0.0440 |
132
- | 1:20 pm | claude-haiku | usage | 100 | 250 | 350 | $0.0070 |
133
- +----------+---------------+-------+--------------+---------------+--------------+---------+
134
-
135
- ========================================================================================================================
136
- Total Events: 3
137
- Total Tokens: 3,150
138
- Total Cost: $0.14
139
- ========================================================================================================================
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
+ └──────────┴───────────────┴───────┴──────────────┴───────────────┴──────────────┴─────────┘
140
176
  ```
141
177
 
142
- ## How It Works
178
+ ### Model Breakdown View (`--breakdown`)
143
179
 
144
- 1. **Extracts credentials** from `~/Library/Application Support/Cursor/User/globalStorage/state.vscdb`
145
- - User ID from `workbench.experiments.statsigBootstrap`
146
- - Access Token from `cursorAuth/accessToken`
147
- - Email and membership (optional)
180
+ When using `--breakdown`, reports include a detailed per-model breakdown:
148
181
 
149
- 2. **Authenticates** with Cursor API using `WorkosCursorSessionToken` cookie
182
+ ```
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
+ └───────────────┴────────┴──────────────┴─────────┴─────────┴───────┴─────────┴────────┘
196
+ ```
150
197
 
151
- 3. **Fetches usage data** from `https://cursor.com/api/usage-summary`
198
+ ### JSON Output (`--json`)
199
+
200
+ JSON output provides clean, structured data for programmatic use (no table formatting):
201
+
202
+ ```json
203
+ {
204
+ "command": "daily",
205
+ "generatedAt": "2026-01-11T10:30:00.000Z",
206
+ "period": "last 7 days",
207
+ "summary": {
208
+ "totalEvents": 45,
209
+ "totalTokens": 539301,
210
+ "totalCost": 0.82
211
+ },
212
+ "data": [
213
+ {
214
+ "date": "2026-01-11",
215
+ "eventCount": 3,
216
+ "totalTokens": 539301,
217
+ "inputTokens": 381,
218
+ "outputTokens": 7081,
219
+ "totalCost": 0.82,
220
+ "models": {
221
+ "claude-4.5-opus-high-thinking": 3
222
+ }
223
+ }
224
+ ],
225
+ "breakdown": [
226
+ {
227
+ "model": "claude-opus",
228
+ "count": 15,
229
+ "totalTokens": 250000,
230
+ "inputTokens": 20000,
231
+ "outputTokens": 15000,
232
+ "totalCost": 2.0,
233
+ "tokenPercent": 50.0,
234
+ "costPercent": 40.0
235
+ }
236
+ ]
237
+ }
238
+ ```
152
239
 
153
- 4. **Parses and displays** membership, plan usage, and billing information
240
+ ### Weekly Report View
241
+
242
+ ```
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
+ └─────────────────────────┴────────┴──────────────┴─────────┴─────────┴────────┴─────────────────────┘
256
+ ```
154
257
 
155
258
  ## Environment Variables
156
259
 
@@ -168,42 +271,19 @@ src/
168
271
  └── _consts.ts # Constants & configuration
169
272
  ```
170
273
 
171
- ## API Response Structure
172
-
173
- The tool fetches data from Cursor's `/api/usage-summary` endpoint which returns:
174
- - Billing cycle information
175
- - Membership type (pro, hobby, etc.)
176
- - Plan usage (used/limit/remaining)
177
- - On-demand usage status
178
- - Display messages about usage percentage
179
-
180
274
  ## Features Implemented
181
275
 
182
- - ✅ Daily usage reports with table formatting
276
+ - ✅ Daily, weekly, and monthly usage reports
183
277
  - ✅ Detailed event breakdown by time
184
278
  - ✅ Token count tracking (input, output, cache)
185
279
  - ✅ Cost calculation and display
186
- - ✅ Model usage breakdown
187
- - ✅ Flexible date range querying
188
- - ✅ Colorized CLI output
189
-
190
- ## Future Enhancements
191
-
192
- - [ ] JSON output format (`--json`)
193
- - [ ] Date range queries (`--from 2026-01-01 --to 2026-01-31`)
194
- - [ ] Weekly/monthly aggregations
195
- - [ ] Historical data caching
196
- - [ ] Cost projections
197
- - [ ] Team usage analysis
198
- - [ ] Export to CSV/JSON
199
- - [ ] Web dashboard
200
- - [ ] Webhook notifications
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
285
+
201
286
 
202
287
  ## License
203
288
 
204
289
  MIT
205
-
206
- ## Related Projects
207
-
208
- - [ccusage](https://github.com/ryoppippi/ccusage) - Claude Code usage analyzer
209
- - [@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
  /**
@@ -71,50 +69,54 @@ export async function showDailyReport(credentials, days = 7, options = {}, outpu
71
69
  logger.warn('No usage events found for this period');
72
70
  return;
73
71
  }
74
- logger.log('');
75
72
  // Group and calculate stats
76
73
  const grouped = groupByDay(events);
77
74
  const stats = calculateDailyStats(grouped);
78
- // Display table
79
- logger.log('\n' + '='.repeat(100));
80
- logger.log(`DAILY USAGE REPORT (Last ${days} days)`);
81
- logger.log('='.repeat(100) + '\n');
82
- const tableData = stats.map((day) => {
83
- const modelList = Array.from(day.models.entries())
84
- .map(([model, count]) => `${model}(${count})`)
85
- .join(', ');
86
- return [
87
- day.date,
88
- day.eventCount.toString(),
89
- day.totalTokens.toLocaleString(),
90
- day.inputTokens.toLocaleString(),
91
- day.outputTokens.toLocaleString(),
92
- `$${day.totalCost.toFixed(2)}`,
93
- modelList || 'N/A',
94
- ];
95
- });
96
- const table = createTable(['Date', 'Events', 'Total Tokens', 'Input', 'Output', 'Cost', 'Models'], tableData);
97
- logger.log(table);
98
- // Summary
99
- const totalEvents = stats.reduce((sum, day) => sum + day.eventCount, 0);
100
- const totalTokens = stats.reduce((sum, day) => sum + day.totalTokens, 0);
101
- const totalCost = stats.reduce((sum, day) => sum + day.totalCost, 0);
102
- logger.log('\n' + '='.repeat(100));
103
- logger.log('SUMMARY');
104
- logger.log('='.repeat(100));
105
- logger.log(`Total Events: ${totalEvents}`);
106
- logger.log(`Total Tokens: ${totalTokens.toLocaleString()}`);
107
- logger.log(`Total Cost: $${totalCost.toFixed(2)}`);
108
- logger.log('='.repeat(100) + '\n');
109
- // Show breakdown if requested
110
- if (options.breakdown) {
111
- displayBreakdown(events, 'PER-MODEL BREAKDOWN');
112
- }
113
- // Output JSON if requested
114
75
  if (outputJson) {
76
+ // Output JSON only - no table display
115
77
  const jsonOutput = statsToJSON('daily', stats, events, { breakdown: options.breakdown, period: `last ${days} days` });
116
78
  console.log(jsonOutput);
117
79
  }
80
+ else {
81
+ // Display title box
82
+ logger.log(createTitleBox(`Cursor Usage Report - Daily (Last ${days} days)`));
83
+ const tableData = stats.map((day) => {
84
+ const modelList = Array.from(day.models.entries())
85
+ .map(([model, count]) => `${model}(${count})`)
86
+ .join(', ');
87
+ return [
88
+ day.date,
89
+ day.eventCount.toString(),
90
+ formatNumber(day.totalTokens),
91
+ formatNumber(day.inputTokens),
92
+ formatNumber(day.outputTokens),
93
+ formatCurrency(day.totalCost),
94
+ modelList || 'N/A',
95
+ ];
96
+ });
97
+ // Calculate totals
98
+ const totalEvents = stats.reduce((sum, day) => sum + day.eventCount, 0);
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);
102
+ const totalCost = stats.reduce((sum, day) => sum + day.totalCost, 0);
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');
115
+ // Show breakdown if requested
116
+ if (options.breakdown) {
117
+ displayBreakdown(events, 'PER-MODEL BREAKDOWN');
118
+ }
119
+ }
118
120
  }
119
121
  /**
120
122
  * Show monthly usage report
@@ -136,38 +138,41 @@ export async function showMonthlyReport(credentials, months = 3, options = {}, o
136
138
  logger.warn('No usage events found for this period');
137
139
  return;
138
140
  }
139
- logger.log('');
140
141
  // Group and calculate stats
141
142
  const grouped = groupByMonth(events);
142
143
  const stats = calculateMonthlyStats(grouped);
143
- // Display table
144
- logger.log('\n' + '='.repeat(100));
145
- logger.log(`MONTHLY USAGE REPORT (Last ${months} months)`);
146
- logger.log('='.repeat(100) + '\n');
147
- const tableData = formatMonthlyStatsTable(stats);
148
- const table = createTable(['Month', 'Events', 'Total Tokens', 'Input', 'Output', 'Cost', 'Models'], tableData);
149
- logger.log(table);
150
- // Summary
151
- const totalEvents = stats.reduce((sum, month) => sum + month.eventCount, 0);
152
- const totalTokens = stats.reduce((sum, month) => sum + month.totalTokens, 0);
153
- const totalCost = stats.reduce((sum, month) => sum + month.totalCost, 0);
154
- logger.log('\n' + '='.repeat(100));
155
- logger.log('SUMMARY');
156
- logger.log('='.repeat(100));
157
- logger.log(`Total Events: ${totalEvents}`);
158
- logger.log(`Total Tokens: ${totalTokens.toLocaleString()}`);
159
- logger.log(`Total Cost: $${totalCost.toFixed(2)}`);
160
- logger.log(`Average per month: ${(totalTokens / Math.max(1, stats.length)).toLocaleString()} tokens, $${(totalCost / Math.max(1, stats.length)).toFixed(2)}`);
161
- logger.log('='.repeat(100) + '\n');
162
- // Show breakdown if requested
163
- if (options.breakdown) {
164
- displayBreakdown(events, 'PER-MODEL BREAKDOWN');
165
- }
166
- // Output JSON if requested
167
144
  if (outputJson) {
145
+ // Output JSON only - no table display
168
146
  const jsonOutput = statsToJSON('monthly', stats, events, { breakdown: options.breakdown, period: `last ${months} months` });
169
147
  console.log(jsonOutput);
170
148
  }
149
+ else {
150
+ // Display title box
151
+ logger.log(createTitleBox(`Cursor Usage Report - Monthly (Last ${months} months)`));
152
+ const tableData = formatMonthlyStatsTable(stats);
153
+ // Calculate totals
154
+ const totalEvents = stats.reduce((sum, month) => sum + month.eventCount, 0);
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);
158
+ const totalCost = stats.reduce((sum, month) => sum + month.totalCost, 0);
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');
171
+ // Show breakdown if requested
172
+ if (options.breakdown) {
173
+ displayBreakdown(events, 'PER-MODEL BREAKDOWN');
174
+ }
175
+ }
171
176
  }
172
177
  /**
173
178
  * Show weekly usage report
@@ -188,38 +193,41 @@ export async function showWeeklyReport(credentials, weeks = 4, options = {}, out
188
193
  logger.warn('No usage events found for this period');
189
194
  return;
190
195
  }
191
- logger.log('');
192
196
  // Group and calculate stats
193
197
  const grouped = groupByWeek(events);
194
198
  const stats = calculateWeeklyStats(grouped);
195
- // Display table
196
- logger.log('\n' + '='.repeat(120));
197
- logger.log(`WEEKLY USAGE REPORT (Last ${weeks} weeks)`);
198
- logger.log('='.repeat(120) + '\n');
199
- const tableData = formatWeeklyStatsTable(stats);
200
- const table = createTable(['Week', 'Events', 'Total Tokens', 'Input', 'Output', 'Cost', 'Models'], tableData);
201
- logger.log(table);
202
- // Summary
203
- const totalEvents = stats.reduce((sum, week) => sum + week.eventCount, 0);
204
- const totalTokens = stats.reduce((sum, week) => sum + week.totalTokens, 0);
205
- const totalCost = stats.reduce((sum, week) => sum + week.totalCost, 0);
206
- logger.log('\n' + '='.repeat(120));
207
- logger.log('SUMMARY');
208
- logger.log('='.repeat(120));
209
- logger.log(`Total Events: ${totalEvents}`);
210
- logger.log(`Total Tokens: ${totalTokens.toLocaleString()}`);
211
- logger.log(`Total Cost: $${totalCost.toFixed(2)}`);
212
- logger.log(`Average per week: ${(totalTokens / Math.max(1, stats.length)).toLocaleString()} tokens, $${(totalCost / Math.max(1, stats.length)).toFixed(2)}`);
213
- logger.log('='.repeat(120) + '\n');
214
- // Show breakdown if requested
215
- if (options.breakdown) {
216
- displayBreakdown(events, 'PER-MODEL BREAKDOWN');
217
- }
218
- // Output JSON if requested
219
199
  if (outputJson) {
200
+ // Output JSON only - no table display
220
201
  const jsonOutput = statsToJSON('weekly', stats, events, { breakdown: options.breakdown, period: `last ${weeks} weeks` });
221
202
  console.log(jsonOutput);
222
203
  }
204
+ else {
205
+ // Display title box
206
+ logger.log(createTitleBox(`Cursor Usage Report - Weekly (Last ${weeks} weeks)`));
207
+ const tableData = formatWeeklyStatsTable(stats);
208
+ // Calculate totals
209
+ const totalEvents = stats.reduce((sum, week) => sum + week.eventCount, 0);
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);
213
+ const totalCost = stats.reduce((sum, week) => sum + week.totalCost, 0);
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');
226
+ // Show breakdown if requested
227
+ if (options.breakdown) {
228
+ displayBreakdown(events, 'PER-MODEL BREAKDOWN');
229
+ }
230
+ }
223
231
  }
224
232
  /**
225
233
  * Show usage for a specific date
@@ -234,40 +242,46 @@ export async function showDateReport(credentials, date, options = {}, outputJson
234
242
  logger.warn('No usage events found for this date');
235
243
  return;
236
244
  }
237
- logger.log('');
238
- // Display detailed events
239
- logger.log('\n' + '='.repeat(120));
240
- logger.log(`USAGE EVENTS FOR ${date.toDateString()}`);
241
- logger.log('='.repeat(120) + '\n');
242
- const tableData = events.map((event) => {
243
- const timestamp = new Date(event.timestamp).toLocaleTimeString();
244
- return [
245
- timestamp,
246
- event.model,
247
- event.type,
248
- event.inputTokens.toLocaleString(),
249
- event.outputTokens.toLocaleString(),
250
- event.tokens.toLocaleString(),
251
- `$${event.cost?.toFixed(4) || '0.0000'}`,
252
- ];
253
- });
254
- const table = createTable(['Time', 'Model', 'Type', 'Input Tokens', 'Output Tokens', 'Total Tokens', 'Cost'], tableData);
255
- logger.log(table);
256
- // Summary
257
- const totalTokens = events.reduce((sum, e) => sum + e.tokens, 0);
258
- const totalCost = events.reduce((sum, e) => sum + (e.cost || 0), 0);
259
- logger.log('\n' + '='.repeat(120));
260
- logger.log(`Total Events: ${events.length}`);
261
- logger.log(`Total Tokens: ${totalTokens.toLocaleString()}`);
262
- logger.log(`Total Cost: $${totalCost.toFixed(2)}`);
263
- logger.log('='.repeat(120) + '\n');
264
- // Show breakdown if requested
265
- if (options.breakdown) {
266
- displayBreakdown(events, 'PER-MODEL BREAKDOWN');
267
- }
268
- // Output JSON if requested
269
245
  if (outputJson) {
246
+ // Output JSON only - no table display
270
247
  const jsonOutput = statsToJSON('today', events.map((e, i) => ({ ...e, eventIndex: i })), events, { breakdown: options.breakdown, period: 'today' });
271
248
  console.log(jsonOutput);
272
249
  }
250
+ else {
251
+ // Display title box
252
+ logger.log(createTitleBox(`Cursor Usage Report - ${date.toDateString()}`));
253
+ const tableData = events.map((event) => {
254
+ const timestamp = new Date(event.timestamp).toLocaleTimeString();
255
+ return [
256
+ timestamp,
257
+ event.model,
258
+ event.type,
259
+ formatNumber(event.inputTokens),
260
+ formatNumber(event.outputTokens),
261
+ formatNumber(event.tokens),
262
+ formatCurrency(event.cost || 0),
263
+ ];
264
+ });
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);
268
+ const totalTokens = events.reduce((sum, e) => sum + e.tokens, 0);
269
+ const totalCost = events.reduce((sum, e) => sum + (e.cost || 0), 0);
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');
282
+ // Show breakdown if requested
283
+ if (options.breakdown) {
284
+ displayBreakdown(events, 'PER-MODEL BREAKDOWN');
285
+ }
286
+ }
273
287
  }
@@ -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.2",
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"