opencode-token-tracker 1.4.0 → 1.5.1
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 +31 -4
- package/README.zh-CN.md +385 -0
- package/dist/bin/opencode-tokens.js +30 -94
- package/dist/index.js +136 -147
- package/dist/lib/shared.d.ts +48 -0
- package/dist/lib/shared.js +357 -0
- package/dist/test/shared.test.d.ts +1 -0
- package/dist/test/shared.test.js +378 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Real-time token usage and cost tracking plugin for [OpenCode](https://opencode.ai).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[English](./README.md) | [简体中文](./README.zh-CN.md)
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
@@ -14,6 +14,15 @@ Real-time token usage and cost tracking plugin for [OpenCode](https://opencode.a
|
|
|
14
14
|
- **JSONL logging** - All usage data saved locally for analysis
|
|
15
15
|
- **Multi-model support** - Claude, GPT, DeepSeek, Gemini, and more
|
|
16
16
|
|
|
17
|
+
## AI Engineering Framework
|
|
18
|
+
|
|
19
|
+
This project uses the [AI Engineering Framework (AIEF)](https://github.com/tongsh6/ai-engineering-framework) to organize AI collaboration context and conventions.
|
|
20
|
+
|
|
21
|
+
- `AGENTS.md` defines repository-level collaboration rules
|
|
22
|
+
- `context/` stores technical snapshots, coding conventions, and business semantics
|
|
23
|
+
|
|
24
|
+
If you are building AI-assisted engineering workflows, we strongly recommend adopting AIEF in your own repositories for clearer context management and more consistent agent outputs.
|
|
25
|
+
|
|
17
26
|
## Installation
|
|
18
27
|
|
|
19
28
|
Add to your OpenCode config file (`~/.config/opencode/opencode.json`):
|
|
@@ -291,9 +300,10 @@ All prices are in **USD per 1 million tokens**:
|
|
|
291
300
|
Pricing is resolved in this order (first match wins):
|
|
292
301
|
|
|
293
302
|
1. **Provider-level** - Override all models for a provider
|
|
294
|
-
2. **
|
|
295
|
-
3. **
|
|
296
|
-
4. **
|
|
303
|
+
2. **Provider-specific model config** - Custom pricing for the same model under different providers
|
|
304
|
+
3. **User model config** - Generic custom model pricing in config file
|
|
305
|
+
4. **Built-in pricing** - Default pricing table
|
|
306
|
+
5. **Fallback** - $1/M input, $4/M output
|
|
297
307
|
|
|
298
308
|
#### Example: Free providers
|
|
299
309
|
|
|
@@ -321,6 +331,23 @@ Override or add pricing for specific models (prices in USD per 1M tokens):
|
|
|
321
331
|
}
|
|
322
332
|
```
|
|
323
333
|
|
|
334
|
+
#### Example: Same model, different provider pricing
|
|
335
|
+
|
|
336
|
+
If the same model has different prices under different providers, nest provider names under the model key:
|
|
337
|
+
|
|
338
|
+
```json
|
|
339
|
+
{
|
|
340
|
+
"models": {
|
|
341
|
+
"deepseek/deepseek-v4-flash": {
|
|
342
|
+
"openrouter": { "input": 0.14, "output": 0.28, "cacheRead": 0.0028 },
|
|
343
|
+
"siliconflow": { "input": 0.2, "output": 0.4 }
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
You can still mix this with the original flat model pricing format.
|
|
350
|
+
|
|
324
351
|
### Toast Settings
|
|
325
352
|
|
|
326
353
|
| Option | Type | Default | Description |
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
# opencode-token-tracker
|
|
2
|
+
|
|
3
|
+
面向 [OpenCode](https://opencode.ai) 的实时 token 用量与成本追踪插件。
|
|
4
|
+
|
|
5
|
+
[English](./README.md) | [简体中文](./README.zh-CN.md)
|
|
6
|
+
|
|
7
|
+
## 功能特性
|
|
8
|
+
|
|
9
|
+
- **实时 Toast 提示**:每次 AI 响应后展示 token 用量和成本
|
|
10
|
+
- **预算控制**:支持日/周/月预算与阈值预警
|
|
11
|
+
- **会话统计**:跟踪整个 session 的累计消耗
|
|
12
|
+
- **CLI 统计工具**:按 day/week/month 查看统计,支持按 model/agent/provider 分组
|
|
13
|
+
- **成本估算**:基于模型定价自动计算估算成本
|
|
14
|
+
- **JSONL 日志**:本地持久化所有用量记录,便于分析
|
|
15
|
+
- **多模型支持**:Claude、GPT、DeepSeek、Gemini 等
|
|
16
|
+
|
|
17
|
+
## AI Engineering Framework
|
|
18
|
+
|
|
19
|
+
本项目使用 [AI Engineering Framework (AIEF)](https://github.com/tongsh6/ai-engineering-framework) 组织 AI 协作上下文与规范。
|
|
20
|
+
|
|
21
|
+
- `AGENTS.md`:仓库级 AI 协作规则
|
|
22
|
+
- `context/`:技术快照、编码约定、业务语义文档
|
|
23
|
+
|
|
24
|
+
如果你在构建 AI-assisted engineering 工作流,推荐在你的仓库中采用 AIEF,以获得更清晰的上下文管理与更稳定的 agent 输出。
|
|
25
|
+
|
|
26
|
+
## 安装
|
|
27
|
+
|
|
28
|
+
在 OpenCode 配置文件 `~/.config/opencode/opencode.json` 中添加插件:
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"$schema": "https://opencode.ai/config.json",
|
|
33
|
+
"plugin": ["opencode-token-tracker"]
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
重启 OpenCode 后会自动安装插件。
|
|
38
|
+
|
|
39
|
+
## 使用
|
|
40
|
+
|
|
41
|
+
### Toast 提示
|
|
42
|
+
|
|
43
|
+
安装后,每次 AI 响应会看到类似提示:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
12.5K tokens
|
|
47
|
+
$0.023 | Session: $0.156
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
配置预算后,超阈值会显示预警:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
⚠️ Budget exceeded!
|
|
54
|
+
Daily: $5.50/$5.00 (110%)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
当会话 idle 时,会显示会话摘要:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
Session: 45.2K tokens
|
|
61
|
+
$0.156 | 8 msgs | 5min
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 预算控制
|
|
65
|
+
|
|
66
|
+
设置预算限额,避免意外超支:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# 查看预算状态
|
|
70
|
+
opencode-tokens budget
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
示例输出:
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
Budget Status
|
|
77
|
+
══════════════════════════════════════════════════════════════════
|
|
78
|
+
|
|
79
|
+
🟢 Daily
|
|
80
|
+
$3.50 / $10.00 [███████░░░░░░░░░░░░░] 35%
|
|
81
|
+
Remaining: $6.50
|
|
82
|
+
|
|
83
|
+
🟡 Weekly
|
|
84
|
+
$42.00 / $50.00 [████████████████░░░░] 84%
|
|
85
|
+
Remaining: $8.00
|
|
86
|
+
|
|
87
|
+
🟢 Monthly
|
|
88
|
+
$120.00 / $200.00 [████████████░░░░░░░░] 60%
|
|
89
|
+
Remaining: $80.00
|
|
90
|
+
|
|
91
|
+
Legend: 🟢 OK 🟡 Warning (>80%) 🔴 Exceeded
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
预算配置文件:`~/.config/opencode/token-tracker.json`
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"budget": {
|
|
99
|
+
"daily": 10,
|
|
100
|
+
"weekly": 50,
|
|
101
|
+
"monthly": 200,
|
|
102
|
+
"warnAt": 0.8
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### CLI 统计
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
# 全量汇总
|
|
111
|
+
opencode-tokens
|
|
112
|
+
|
|
113
|
+
# 今日统计
|
|
114
|
+
opencode-tokens today
|
|
115
|
+
|
|
116
|
+
# 本周统计(按模型分组)
|
|
117
|
+
opencode-tokens week --by model
|
|
118
|
+
|
|
119
|
+
# 本月统计(展示全部分组)
|
|
120
|
+
opencode-tokens month --by all
|
|
121
|
+
|
|
122
|
+
# 按天拆分
|
|
123
|
+
opencode-tokens --by daily
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`--by` 可选:
|
|
127
|
+
- `model`:按模型分组
|
|
128
|
+
- `agent`:按 agent 分组
|
|
129
|
+
- `provider`:按 provider 分组
|
|
130
|
+
- `daily`:按天分组
|
|
131
|
+
- `all`:显示全部
|
|
132
|
+
|
|
133
|
+
示例输出:
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
Today's Usage
|
|
137
|
+
──────────────────────────────────────────────────
|
|
138
|
+
Total Tokens: 2.81M
|
|
139
|
+
Input: 2.74M
|
|
140
|
+
Output: 72.9K
|
|
141
|
+
Reasoning: 7.1K
|
|
142
|
+
Cache Read: 12.62M
|
|
143
|
+
Total Cost: $32.93
|
|
144
|
+
Messages: 230
|
|
145
|
+
|
|
146
|
+
By Model
|
|
147
|
+
─────────────────────────────────────────────────────
|
|
148
|
+
Model Tokens Cost Msgs
|
|
149
|
+
--------------- ---------- ---------- ------
|
|
150
|
+
claude-opus-4.5 2.70M $32.93 206
|
|
151
|
+
deepseek-chat 23.4K $0.0025 6
|
|
152
|
+
gpt-5.2 86.9K $0.0000 18
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### 定价与配置命令
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
# 查看预算状态
|
|
159
|
+
opencode-tokens budget
|
|
160
|
+
|
|
161
|
+
# 查看内置定价表
|
|
162
|
+
opencode-tokens pricing
|
|
163
|
+
|
|
164
|
+
# 查看你实际使用的模型与定价来源
|
|
165
|
+
opencode-tokens models
|
|
166
|
+
|
|
167
|
+
# 查看当前配置
|
|
168
|
+
opencode-tokens config
|
|
169
|
+
|
|
170
|
+
# 基于当前使用情况生成示例配置
|
|
171
|
+
opencode-tokens config init
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
`models` 示例输出:
|
|
175
|
+
|
|
176
|
+
```
|
|
177
|
+
Model Provider Msgs Pricing
|
|
178
|
+
------------------------ ---------------- -------- ------------
|
|
179
|
+
claude-opus-4.5 github-copilot 379 provider cfg
|
|
180
|
+
deepseek-chat deepseek 6 built-in
|
|
181
|
+
gpt-5.2 openai 18 built-in
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
可以帮助你了解:
|
|
185
|
+
- 当前使用了哪些模型和 provider
|
|
186
|
+
- 定价来源是内置表、用户配置还是默认回退
|
|
187
|
+
- 需要在配置文件中补充哪些模型定价
|
|
188
|
+
|
|
189
|
+
## 日志文件
|
|
190
|
+
|
|
191
|
+
token 记录保存在:
|
|
192
|
+
|
|
193
|
+
```
|
|
194
|
+
~/.config/opencode/logs/token-tracker/tokens.jsonl
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
单行示例:
|
|
198
|
+
|
|
199
|
+
```json
|
|
200
|
+
{
|
|
201
|
+
"type": "tokens",
|
|
202
|
+
"sessionId": "ses_xxx",
|
|
203
|
+
"messageId": "msg_xxx",
|
|
204
|
+
"agent": "build",
|
|
205
|
+
"model": "claude-opus-4.5",
|
|
206
|
+
"provider": "github-copilot",
|
|
207
|
+
"input": 1500,
|
|
208
|
+
"output": 350,
|
|
209
|
+
"reasoning": 0,
|
|
210
|
+
"cacheRead": 5000,
|
|
211
|
+
"cacheWrite": 0,
|
|
212
|
+
"cost": 0.0234,
|
|
213
|
+
"_ts": 1234567890123
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## 支持模型
|
|
218
|
+
|
|
219
|
+
| Provider | Models |
|
|
220
|
+
| --- | --- |
|
|
221
|
+
| Anthropic | Claude Opus 4.5, Sonnet 4/4.5, Haiku 4/4.5 |
|
|
222
|
+
| OpenAI | GPT-5.x, GPT-4.x, o1, o3 |
|
|
223
|
+
| DeepSeek | deepseek-chat, deepseek-reasoner |
|
|
224
|
+
| Google | Gemini 2.x, 3.x |
|
|
225
|
+
|
|
226
|
+
未知模型会使用默认定价估算。
|
|
227
|
+
|
|
228
|
+
## 配置说明
|
|
229
|
+
|
|
230
|
+
在 `~/.config/opencode/token-tracker.json` 创建配置:
|
|
231
|
+
|
|
232
|
+
```json
|
|
233
|
+
{
|
|
234
|
+
"providers": {
|
|
235
|
+
"github-copilot": { "input": 0, "output": 0 }
|
|
236
|
+
},
|
|
237
|
+
"models": {
|
|
238
|
+
"my-custom-model": { "input": 1, "output": 2 }
|
|
239
|
+
},
|
|
240
|
+
"toast": {
|
|
241
|
+
"enabled": true,
|
|
242
|
+
"duration": 3000,
|
|
243
|
+
"showOnIdle": true
|
|
244
|
+
},
|
|
245
|
+
"budget": {
|
|
246
|
+
"daily": 10,
|
|
247
|
+
"weekly": 50,
|
|
248
|
+
"monthly": 200,
|
|
249
|
+
"warnAt": 0.8
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### 定价字段
|
|
255
|
+
|
|
256
|
+
定价单位均为 **USD / 1M tokens**:
|
|
257
|
+
|
|
258
|
+
| 字段 | 说明 | 示例 |
|
|
259
|
+
| --- | --- | --- |
|
|
260
|
+
| `input` | 输入 token 单价 | `15` = $15 / 1M tokens |
|
|
261
|
+
| `output` | 输出 token 单价 | `75` = $75 / 1M tokens |
|
|
262
|
+
| `cacheRead` | 缓存读取 token 单价(可选) | `1.5` = $1.5 / 1M tokens |
|
|
263
|
+
| `cacheWrite` | 缓存写入 token 单价(可选) | `18.75` = $18.75 / 1M tokens |
|
|
264
|
+
|
|
265
|
+
**如何查找模型定价:**
|
|
266
|
+
|
|
267
|
+
1. 查看各厂商官方定价页:
|
|
268
|
+
- [Anthropic Claude](https://www.anthropic.com/pricing)
|
|
269
|
+
- [OpenAI](https://openai.com/pricing)
|
|
270
|
+
- [DeepSeek](https://platform.deepseek.com/api-docs/pricing)
|
|
271
|
+
- [Google Gemini](https://ai.google.dev/pricing)
|
|
272
|
+
|
|
273
|
+
2. 或执行 `opencode-tokens pricing` 查看内置定价表
|
|
274
|
+
|
|
275
|
+
**常见场景:**
|
|
276
|
+
|
|
277
|
+
| 场景 | 配置 |
|
|
278
|
+
| --- | --- |
|
|
279
|
+
| 订阅制服务(GitHub Copilot、Cursor) | `{ "input": 0, "output": 0 }` |
|
|
280
|
+
| 免费/本地模型 | `{ "input": 0, "output": 0 }` |
|
|
281
|
+
| 自定义 API(已知定价) | 查看 provider 官方定价页 |
|
|
282
|
+
|
|
283
|
+
### 定价优先级
|
|
284
|
+
|
|
285
|
+
定价解析顺序(命中即止):
|
|
286
|
+
|
|
287
|
+
1. **Provider 覆盖** — 为某个 provider 的所有模型统一设置
|
|
288
|
+
2. **按 provider 的 model 配置** — 同一模型在不同 provider 下使用不同定价
|
|
289
|
+
3. **用户 model 配置** — 为特定模型自定义通用定价
|
|
290
|
+
4. **内置定价** — 默认定价表
|
|
291
|
+
5. **默认回退** — $1/M input,$4/M output
|
|
292
|
+
|
|
293
|
+
#### 示例:免费 provider
|
|
294
|
+
|
|
295
|
+
使用 GitHub Copilot 等订阅制服务时,将成本设为 $0:
|
|
296
|
+
|
|
297
|
+
```json
|
|
298
|
+
{
|
|
299
|
+
"providers": {
|
|
300
|
+
"github-copilot": { "input": 0, "output": 0 },
|
|
301
|
+
"cursor": { "input": 0, "output": 0 }
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
#### 示例:自定义模型定价
|
|
307
|
+
|
|
308
|
+
为特定模型覆盖或新增定价(单位 USD / 1M tokens):
|
|
309
|
+
|
|
310
|
+
```json
|
|
311
|
+
{
|
|
312
|
+
"models": {
|
|
313
|
+
"claude-opus-4.5": { "input": 12, "output": 60, "cacheRead": 1.2 },
|
|
314
|
+
"my-local-model": { "input": 0, "output": 0 }
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
#### 示例:同一模型在不同 provider 下使用不同价格
|
|
320
|
+
|
|
321
|
+
如果同一个模型在不同 provider 下价格不同,可以在模型名下继续按 provider 配置:
|
|
322
|
+
|
|
323
|
+
```json
|
|
324
|
+
{
|
|
325
|
+
"models": {
|
|
326
|
+
"deepseek/deepseek-v4-flash": {
|
|
327
|
+
"openrouter": { "input": 0.14, "output": 0.28, "cacheRead": 0.0028 },
|
|
328
|
+
"siliconflow": { "input": 0.2, "output": 0.4 }
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
这种写法可以和原来的扁平 `models` 配置同时使用。
|
|
335
|
+
|
|
336
|
+
### 预算设置
|
|
337
|
+
|
|
338
|
+
| 选项 | 类型 | 默认值 | 说明 |
|
|
339
|
+
| --- | --- | --- | --- |
|
|
340
|
+
| `daily` | number | - | 每日预算上限(USD) |
|
|
341
|
+
| `weekly` | number | - | 每周预算上限(USD) |
|
|
342
|
+
| `monthly` | number | - | 每月预算上限(USD) |
|
|
343
|
+
| `warnAt` | number | `0.8` | 预警阈值(0-1),如 0.8 = 达到 80% 时预警 |
|
|
344
|
+
|
|
345
|
+
超出预算时:
|
|
346
|
+
- Toast 提示切换为预警/错误样式
|
|
347
|
+
- 使用 `opencode-tokens budget` 查看详细状态
|
|
348
|
+
- 预算在每日零点(daily)、每周一(weekly)、每月 1 日(monthly)自动重置
|
|
349
|
+
|
|
350
|
+
### Toast 设置
|
|
351
|
+
|
|
352
|
+
| 选项 | 类型 | 默认值 | 说明 |
|
|
353
|
+
| --- | --- | --- | --- |
|
|
354
|
+
| `enabled` | boolean | `true` | 是否显示 Toast 提示 |
|
|
355
|
+
| `duration` | number | `3000` | Toast 显示时长(毫秒) |
|
|
356
|
+
| `showOnIdle` | boolean | `true` | 会话 idle 时是否显示会话摘要 |
|
|
357
|
+
|
|
358
|
+
## 开发
|
|
359
|
+
|
|
360
|
+
```bash
|
|
361
|
+
# 克隆仓库
|
|
362
|
+
git clone https://github.com/tongsh6/opencode-token-tracker.git
|
|
363
|
+
cd opencode-token-tracker
|
|
364
|
+
|
|
365
|
+
# 安装依赖
|
|
366
|
+
npm install
|
|
367
|
+
|
|
368
|
+
# 构建
|
|
369
|
+
npm run build
|
|
370
|
+
|
|
371
|
+
# 本地联调
|
|
372
|
+
npm link
|
|
373
|
+
cd ~/.config/opencode
|
|
374
|
+
npm link opencode-token-tracker
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
## License
|
|
378
|
+
|
|
379
|
+
MIT © [tongsh6](https://github.com/tongsh6)
|
|
380
|
+
|
|
381
|
+
## Related
|
|
382
|
+
|
|
383
|
+
- [OpenCode](https://opencode.ai) - AI coding assistant
|
|
384
|
+
- [OpenCode Plugins](https://opencode.ai/docs/plugins) - 插件文档
|
|
385
|
+
- [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) - OpenCode 增强插件
|
|
@@ -1,95 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { BUILTIN_PRICING, DEFAULT_CONFIG, findModelConfigPricing, formatCost, formatTokens, getStartOfDay, getStartOfWeek, getStartOfMonth, validateConfig } from "../lib/shared.js";
|
|
2
3
|
import { readFileSync, existsSync, writeFileSync } from "fs";
|
|
3
4
|
import { join } from "path";
|
|
4
5
|
import { homedir } from "os";
|
|
5
6
|
const CONFIG_DIR = join(homedir(), ".config", "opencode");
|
|
6
7
|
const CONFIG_FILE = join(CONFIG_DIR, "token-tracker.json");
|
|
7
8
|
const LOG_FILE = join(CONFIG_DIR, "logs", "token-tracker", "tokens.jsonl");
|
|
8
|
-
// Built-in pricing (USD per 1M tokens) - Updated 2026-02-05
|
|
9
|
-
// Keep in sync with index.ts BUILTIN_PRICING
|
|
10
|
-
const BUILTIN_PRICING = {
|
|
11
|
-
// Anthropic Claude (https://www.anthropic.com/pricing#api)
|
|
12
|
-
"claude-opus-4.5": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
13
|
-
"claude-sonnet-4.5": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
|
|
14
|
-
"claude-sonnet-4": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
|
|
15
|
-
"claude-haiku-4.5": { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 },
|
|
16
|
-
"claude-haiku-4": { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 },
|
|
17
|
-
"claude-opus-4.1": { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 },
|
|
18
|
-
"claude-opus-4": { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 },
|
|
19
|
-
"claude-haiku-3": { input: 0.25, output: 1.25, cacheRead: 0.03, cacheWrite: 0.3 },
|
|
20
|
-
// OpenAI GPT (https://openai.com/api/pricing/)
|
|
21
|
-
"gpt-5.2": { input: 1.75, output: 14, cacheRead: 0.175 },
|
|
22
|
-
"gpt-5.2-pro": { input: 21, output: 168 },
|
|
23
|
-
"gpt-5-mini": { input: 0.25, output: 2, cacheRead: 0.025 },
|
|
24
|
-
"gpt-5.1": { input: 2, output: 8 },
|
|
25
|
-
"gpt-5": { input: 5, output: 15 },
|
|
26
|
-
"gpt-4.1": { input: 3, output: 12, cacheRead: 0.75 },
|
|
27
|
-
"gpt-4.1-mini": { input: 0.8, output: 3.2, cacheRead: 0.2 },
|
|
28
|
-
"gpt-4.1-nano": { input: 0.2, output: 0.8, cacheRead: 0.05 },
|
|
29
|
-
"gpt-4o": { input: 2.5, output: 10 },
|
|
30
|
-
"gpt-4o-mini": { input: 0.15, output: 0.6 },
|
|
31
|
-
"o3": { input: 10, output: 40 },
|
|
32
|
-
"o3-mini": { input: 1.1, output: 4.4 },
|
|
33
|
-
"o4-mini": { input: 4, output: 16, cacheRead: 1 },
|
|
34
|
-
"o1": { input: 15, output: 60 },
|
|
35
|
-
"o1-mini": { input: 1.1, output: 4.4 },
|
|
36
|
-
// DeepSeek (https://api-docs.deepseek.com/quick_start/pricing)
|
|
37
|
-
"deepseek-chat": { input: 0.28, output: 0.42, cacheRead: 0.028 },
|
|
38
|
-
"deepseek-reasoner": { input: 0.28, output: 0.42, cacheRead: 0.028 },
|
|
39
|
-
// Google Gemini (https://cloud.google.com/vertex-ai/generative-ai/pricing)
|
|
40
|
-
"gemini-3-pro": { input: 2, output: 12, cacheRead: 0.2 },
|
|
41
|
-
"gemini-3-pro-preview": { input: 2, output: 12, cacheRead: 0.2 },
|
|
42
|
-
"gemini-3-flash": { input: 0.5, output: 2, cacheRead: 0.05 },
|
|
43
|
-
"gemini-3-flash-preview": { input: 0.5, output: 2, cacheRead: 0.05 },
|
|
44
|
-
"gemini-2.5-pro": { input: 1.25, output: 10, cacheRead: 0.125 },
|
|
45
|
-
"gemini-2.5-flash": { input: 0.1, output: 0.4, cacheRead: 0.01 },
|
|
46
|
-
"gemini-2.5-flash-lite": { input: 0.1, output: 0.4, cacheRead: 0.01 },
|
|
47
|
-
"gemini-2.0-flash": { input: 0.15, output: 0.6, cacheRead: 0.015 },
|
|
48
|
-
// Fallback
|
|
49
|
-
"_default": { input: 1, output: 4 },
|
|
50
|
-
};
|
|
51
9
|
// ============================================================================
|
|
52
10
|
// Helpers
|
|
53
11
|
// ============================================================================
|
|
54
|
-
function formatTokens(tokens) {
|
|
55
|
-
if (tokens >= 1_000_000)
|
|
56
|
-
return `${(tokens / 1_000_000).toFixed(2)}M`;
|
|
57
|
-
if (tokens >= 1_000)
|
|
58
|
-
return `${(tokens / 1_000).toFixed(1)}K`;
|
|
59
|
-
return tokens.toString();
|
|
60
|
-
}
|
|
61
|
-
function formatCost(cost) {
|
|
62
|
-
if (cost < 0.01)
|
|
63
|
-
return `$${cost.toFixed(4)}`;
|
|
64
|
-
if (cost < 1)
|
|
65
|
-
return `$${cost.toFixed(3)}`;
|
|
66
|
-
return `$${cost.toFixed(2)}`;
|
|
67
|
-
}
|
|
68
12
|
function padRight(str, len) {
|
|
69
13
|
return str.length >= len ? str : str + " ".repeat(len - str.length);
|
|
70
14
|
}
|
|
71
15
|
function padLeft(str, len) {
|
|
72
16
|
return str.length >= len ? str : " ".repeat(len - str.length) + str;
|
|
73
17
|
}
|
|
74
|
-
function getStartOfDay(date) {
|
|
75
|
-
const d = new Date(date);
|
|
76
|
-
d.setHours(0, 0, 0, 0);
|
|
77
|
-
return d.getTime();
|
|
78
|
-
}
|
|
79
|
-
function getStartOfWeek(date) {
|
|
80
|
-
const d = new Date(date);
|
|
81
|
-
const day = d.getDay();
|
|
82
|
-
const diff = d.getDate() - day + (day === 0 ? -6 : 1);
|
|
83
|
-
d.setDate(diff);
|
|
84
|
-
d.setHours(0, 0, 0, 0);
|
|
85
|
-
return d.getTime();
|
|
86
|
-
}
|
|
87
|
-
function getStartOfMonth(date) {
|
|
88
|
-
const d = new Date(date);
|
|
89
|
-
d.setDate(1);
|
|
90
|
-
d.setHours(0, 0, 0, 0);
|
|
91
|
-
return d.getTime();
|
|
92
|
-
}
|
|
93
18
|
// ============================================================================
|
|
94
19
|
// Data Loading
|
|
95
20
|
// ============================================================================
|
|
@@ -120,11 +45,20 @@ function loadEntries(since) {
|
|
|
120
45
|
function loadConfig() {
|
|
121
46
|
try {
|
|
122
47
|
if (existsSync(CONFIG_FILE)) {
|
|
123
|
-
|
|
48
|
+
const raw = JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
|
|
49
|
+
const result = validateConfig(raw);
|
|
50
|
+
if (result.warnings.length > 0) {
|
|
51
|
+
for (const w of result.warnings) {
|
|
52
|
+
console.error(` [token-tracker] config warning: ${w}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return result.config;
|
|
124
56
|
}
|
|
125
57
|
}
|
|
126
|
-
catch {
|
|
127
|
-
|
|
58
|
+
catch {
|
|
59
|
+
console.error(" [token-tracker] config warning: Config file is not valid JSON, using defaults");
|
|
60
|
+
}
|
|
61
|
+
return DEFAULT_CONFIG;
|
|
128
62
|
}
|
|
129
63
|
// ============================================================================
|
|
130
64
|
// Stats Aggregation
|
|
@@ -171,13 +105,13 @@ function printSummary(title, stats) {
|
|
|
171
105
|
console.log();
|
|
172
106
|
console.log(` ${title}`);
|
|
173
107
|
console.log(` ${"─".repeat(50)}`);
|
|
174
|
-
console.log(` Total Tokens: ${padLeft(formatTokens(totalTokens), 12)}`);
|
|
175
|
-
console.log(` Input: ${padLeft(formatTokens(stats.input), 12)}`);
|
|
176
|
-
console.log(` Output: ${padLeft(formatTokens(stats.output), 12)}`);
|
|
108
|
+
console.log(` Total Tokens: ${padLeft(formatTokens(totalTokens, 2), 12)}`);
|
|
109
|
+
console.log(` Input: ${padLeft(formatTokens(stats.input, 2), 12)}`);
|
|
110
|
+
console.log(` Output: ${padLeft(formatTokens(stats.output, 2), 12)}`);
|
|
177
111
|
if (stats.reasoning > 0) {
|
|
178
|
-
console.log(` Reasoning: ${padLeft(formatTokens(stats.reasoning), 12)}`);
|
|
112
|
+
console.log(` Reasoning: ${padLeft(formatTokens(stats.reasoning, 2), 12)}`);
|
|
179
113
|
}
|
|
180
|
-
console.log(` Cache Read: ${padLeft(formatTokens(stats.cacheRead), 12)}`);
|
|
114
|
+
console.log(` Cache Read: ${padLeft(formatTokens(stats.cacheRead, 2), 12)}`);
|
|
181
115
|
console.log(` Total Cost: ${padLeft(formatCost(stats.cost), 12)}`);
|
|
182
116
|
console.log(` Messages: ${padLeft(stats.count.toString(), 12)}`);
|
|
183
117
|
console.log();
|
|
@@ -199,7 +133,7 @@ function printTable(title, groups, labelHeader) {
|
|
|
199
133
|
console.log(` ${"-".repeat(labelWidth)} ${"-".repeat(tokensWidth)} ${"-".repeat(costWidth)} ${"-".repeat(countWidth)}`);
|
|
200
134
|
for (const [label, stats] of sorted) {
|
|
201
135
|
const totalTokens = stats.input + stats.output;
|
|
202
|
-
console.log(` ${padRight(label, labelWidth)} ${padLeft(formatTokens(totalTokens), tokensWidth)} ${padLeft(formatCost(stats.cost), costWidth)} ${padLeft(stats.count.toString(), countWidth)}`);
|
|
136
|
+
console.log(` ${padRight(label, labelWidth)} ${padLeft(formatTokens(totalTokens, 2), tokensWidth)} ${padLeft(formatCost(stats.cost), costWidth)} ${padLeft(stats.count.toString(), countWidth)}`);
|
|
203
137
|
}
|
|
204
138
|
console.log();
|
|
205
139
|
}
|
|
@@ -224,7 +158,7 @@ function printDailyBreakdown(entries) {
|
|
|
224
158
|
console.log(` ${"-".repeat(dateWidth)} ${"-".repeat(tokensWidth)} ${"-".repeat(costWidth)} ${"-".repeat(countWidth)}`);
|
|
225
159
|
for (const [date, stats] of sorted) {
|
|
226
160
|
const totalTokens = stats.input + stats.output;
|
|
227
|
-
console.log(` ${padRight(date, dateWidth)} ${padLeft(formatTokens(totalTokens), tokensWidth)} ${padLeft(formatCost(stats.cost), costWidth)} ${padLeft(stats.count.toString(), countWidth)}`);
|
|
161
|
+
console.log(` ${padRight(date, dateWidth)} ${padLeft(formatTokens(totalTokens, 2), tokensWidth)} ${padLeft(formatCost(stats.cost), costWidth)} ${padLeft(stats.count.toString(), countWidth)}`);
|
|
228
162
|
}
|
|
229
163
|
console.log();
|
|
230
164
|
}
|
|
@@ -285,15 +219,15 @@ function cmdStats(period, breakdown) {
|
|
|
285
219
|
function cmdPricing() {
|
|
286
220
|
const config = loadConfig();
|
|
287
221
|
console.log(`
|
|
288
|
-
Built-in Pricing Table (USD per 1M tokens) - Updated 2026-02-
|
|
222
|
+
Built-in Pricing Table (USD per 1M tokens) - Updated 2026-02-11
|
|
289
223
|
══════════════════════════════════════════════════════════════════
|
|
290
224
|
`);
|
|
291
225
|
// Group by provider
|
|
292
226
|
const groups = {
|
|
293
|
-
"Anthropic Claude": ["claude-opus-4.5", "claude-sonnet-4.5", "claude-sonnet-4", "claude-haiku-4.5", "claude-haiku-4", "claude-opus-4.1", "claude-opus-4", "claude-haiku-3"],
|
|
227
|
+
"Anthropic Claude": ["claude-opus-4.6", "claude-opus-4.5", "claude-sonnet-4.5", "claude-sonnet-4", "claude-haiku-4.5", "claude-haiku-4", "claude-opus-4.1", "claude-opus-4", "claude-haiku-3"],
|
|
294
228
|
"OpenAI": ["gpt-5.2", "gpt-5.2-pro", "gpt-5-mini", "gpt-5.1", "gpt-5", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "gpt-4o", "gpt-4o-mini", "o3", "o3-mini", "o4-mini", "o1", "o1-mini"],
|
|
295
229
|
"DeepSeek": ["deepseek-chat", "deepseek-reasoner"],
|
|
296
|
-
"Google Gemini": ["gemini-3-pro", "gemini-3-pro-preview", "gemini-3-flash", "gemini-3-flash-preview", "gemini-2.5-pro", "gemini-2.5-flash", "gemini-2.5-flash-lite", "gemini-2.0-flash"],
|
|
230
|
+
"Google Gemini": ["gemini-3-pro", "gemini-3-pro-preview", "gemini-3-flash", "gemini-3-flash-preview", "gemini-2.5-pro", "gemini-2.5-flash", "gemini-2.5-flash-lite", "gemini-2.0-flash", "gemini-2.0-flash-lite"],
|
|
297
231
|
};
|
|
298
232
|
const modelWidth = 20;
|
|
299
233
|
const priceWidth = 10;
|
|
@@ -358,7 +292,7 @@ function cmdModels() {
|
|
|
358
292
|
if (config.providers?.[provider]) {
|
|
359
293
|
status = "provider cfg";
|
|
360
294
|
}
|
|
361
|
-
else if (config.models
|
|
295
|
+
else if (findModelConfigPricing(config.models, model, provider)) {
|
|
362
296
|
status = "model cfg";
|
|
363
297
|
}
|
|
364
298
|
else if (!BUILTIN_PRICING[model]) {
|
|
@@ -548,10 +482,14 @@ function cmdBudget() {
|
|
|
548
482
|
return "🟡";
|
|
549
483
|
return "🟢";
|
|
550
484
|
};
|
|
551
|
-
|
|
485
|
+
// Calculate the earliest period start to minimize data loaded
|
|
486
|
+
const dayStart = getStartOfDay(now);
|
|
487
|
+
const weekStart = getStartOfWeek(now);
|
|
488
|
+
const monthStart = getStartOfMonth(now);
|
|
489
|
+
const earliestSince = Math.min(budget.daily ? dayStart : Infinity, budget.weekly ? weekStart : Infinity, budget.monthly ? monthStart : Infinity);
|
|
490
|
+
const entries = loadEntries(earliestSince);
|
|
552
491
|
// Daily budget
|
|
553
492
|
if (budget.daily) {
|
|
554
|
-
const dayStart = getStartOfDay(now);
|
|
555
493
|
const dayEntries = entries.filter(e => e._ts >= dayStart);
|
|
556
494
|
const spent = dayEntries.reduce((sum, e) => sum + (e.cost ?? 0), 0);
|
|
557
495
|
const pct = spent / budget.daily;
|
|
@@ -563,7 +501,6 @@ function cmdBudget() {
|
|
|
563
501
|
}
|
|
564
502
|
// Weekly budget
|
|
565
503
|
if (budget.weekly) {
|
|
566
|
-
const weekStart = getStartOfWeek(now);
|
|
567
504
|
const weekEntries = entries.filter(e => e._ts >= weekStart);
|
|
568
505
|
const spent = weekEntries.reduce((sum, e) => sum + (e.cost ?? 0), 0);
|
|
569
506
|
const pct = spent / budget.weekly;
|
|
@@ -575,7 +512,6 @@ function cmdBudget() {
|
|
|
575
512
|
}
|
|
576
513
|
// Monthly budget
|
|
577
514
|
if (budget.monthly) {
|
|
578
|
-
const monthStart = getStartOfMonth(now);
|
|
579
515
|
const monthEntries = entries.filter(e => e._ts >= monthStart);
|
|
580
516
|
const spent = monthEntries.reduce((sum, e) => sum + (e.cost ?? 0), 0);
|
|
581
517
|
const pct = spent / budget.monthly;
|