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 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
- ![Toast Screenshot](./docs/toast-demo.png)
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. **User model config** - Custom model pricing in config file
295
- 3. **Built-in pricing** - Default pricing table
296
- 4. **Fallback** - $1/M input, $4/M output
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 |
@@ -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
- return JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
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
- return {};
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-05
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?.[model]) {
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
- const entries = loadEntries();
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;