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 +185 -105
- package/dist/cli.js +28 -23
- package/dist/commands.js +138 -124
- 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
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
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
|
|
|
@@ -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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
105
|
+
╭────────────────────────────────────────╮
|
|
106
|
+
│ Cursor Usage Summary │
|
|
107
|
+
╰────────────────────────────────────────╯
|
|
65
108
|
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
Total
|
|
137
|
-
|
|
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
|
-
|
|
178
|
+
### Model Breakdown View (`--breakdown`)
|
|
143
179
|
|
|
144
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
- ✅
|
|
189
|
-
|
|
190
|
-
|
|
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('
|
|
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
|
/**
|
|
@@ -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
|
-
*
|
|
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"
|