@winston.wan/burn-your-money 2.3.1 → 3.0.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 +25 -9
- package/install.js +2 -1
- package/package.json +29 -29
- package/src/statusline.js +36 -13
- package/src/token-history.js +92 -21
- package/uninstall.js +2 -1
package/README.md
CHANGED
|
@@ -71,6 +71,7 @@ npm install -g "@winston.wan/burn-your-money"
|
|
|
71
71
|
| `/burn-your-money` | 查看详细账单(今日/周/月/总计 + 有趣统计) |
|
|
72
72
|
| `/burn-your-money-stats` | 查看 token 使用趋势图 |
|
|
73
73
|
| `/burn-your-money-export` | 导出 token 数据(CSV 格式) |
|
|
74
|
+
| `/burn-your-money-uninstall` | **完全卸载插件(推荐使用此命令卸载)** |
|
|
74
75
|
|
|
75
76
|
---
|
|
76
77
|
|
|
@@ -89,6 +90,18 @@ npm install -g "@winston.wan/burn-your-money"
|
|
|
89
90
|
- **历史统计**:从 `~/.claude/stats-cache.json` 读取
|
|
90
91
|
- **本地计算**:零网络请求,纯本地处理
|
|
91
92
|
|
|
93
|
+
### 💰 价格说明
|
|
94
|
+
|
|
95
|
+
费用计算基于 **Claude 模型官方定价**(截至 2025 年):
|
|
96
|
+
|
|
97
|
+
| Token 类型 | 价格 |
|
|
98
|
+
|-----------|------|
|
|
99
|
+
| Input Tokens | $3 / 百万 tokens |
|
|
100
|
+
| Output Tokens | $15 / 百万 tokens |
|
|
101
|
+
| Cache Read Tokens | $0.30 / 百万 tokens (90% 折扣) |
|
|
102
|
+
|
|
103
|
+
> **注意**:如果你使用的是其他兼容模型(如本地部署的模型),实际费用可能不同。本插件仅按 Claude 官方价格估算,不代表实际账单。
|
|
104
|
+
|
|
92
105
|
---
|
|
93
106
|
|
|
94
107
|
## 📋 常见问题
|
|
@@ -108,21 +121,16 @@ npm install -g "@winston.wan/burn-your-money"
|
|
|
108
121
|
|
|
109
122
|
### 3. 卸载后状态栏还显示?
|
|
110
123
|
|
|
111
|
-
npm 的 `
|
|
112
|
-
|
|
113
|
-
**方案 1**:手动运行清理脚本
|
|
114
|
-
```bash
|
|
115
|
-
node D:/code/burn-your-money/uninstall.js
|
|
116
|
-
```
|
|
124
|
+
npm 的 `postuninstall` 钩子在某些情况下可能不触发。如果卸载后状态栏还显示:
|
|
117
125
|
|
|
118
|
-
|
|
126
|
+
**手动清理方法**:
|
|
119
127
|
```bash
|
|
120
128
|
# 删除插件文件
|
|
121
129
|
rm -f ~/.claude/statusline.js
|
|
122
130
|
rm -f ~/.claude/scripts/token-history.js
|
|
123
131
|
rm -f ~/.claude/commands/burn-your-money-*.md
|
|
124
132
|
|
|
125
|
-
#
|
|
133
|
+
# 编辑 ~/.claude/settings.json,删除 statusLine 配置项
|
|
126
134
|
```
|
|
127
135
|
|
|
128
136
|
然后重启 Claude Code。
|
|
@@ -131,7 +139,15 @@ rm -f ~/.claude/commands/burn-your-money-*.md
|
|
|
131
139
|
|
|
132
140
|
## 🗑️ 卸载
|
|
133
141
|
|
|
134
|
-
|
|
142
|
+
**推荐方式**:在 Claude Code 中使用自定义命令
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
/burn-your-money-uninstall
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
这个命令会自动删除所有插件文件和配置。
|
|
149
|
+
|
|
150
|
+
**备选方式**:使用 npm 卸载(可能需要手动清理)
|
|
135
151
|
|
|
136
152
|
```bash
|
|
137
153
|
npm uninstall -g "@winston.wan/burn-your-money"
|
package/install.js
CHANGED
|
@@ -127,7 +127,8 @@ function configureSettings() {
|
|
|
127
127
|
const commands = [
|
|
128
128
|
'burn-your-money.md',
|
|
129
129
|
'burn-your-money-stats.md',
|
|
130
|
-
'burn-your-money-export.md'
|
|
130
|
+
'burn-your-money-export.md',
|
|
131
|
+
'burn-your-money-uninstall.md'
|
|
131
132
|
];
|
|
132
133
|
|
|
133
134
|
const projectCommandsDir = path.join(projectRoot, 'src', 'commands');
|
package/package.json
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@winston.wan/burn-your-money",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "💸 Burn Your Money - 实时显示 Claude Code 的 token 消耗,看着你的钱包燃烧!",
|
|
5
|
-
"main": "src/statusline.js",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"postinstall": "node install.js",
|
|
8
|
-
"postuninstall": "node uninstall.js"
|
|
9
|
-
},
|
|
10
|
-
"repository": {
|
|
11
|
-
"type": "git",
|
|
12
|
-
"url": "git+https://github.com/winston-wwzhen/burn-your-money.git"
|
|
13
|
-
},
|
|
14
|
-
"keywords": [
|
|
15
|
-
"claude-code",
|
|
16
|
-
"token-monitor",
|
|
17
|
-
"statusline",
|
|
18
|
-
"claude",
|
|
19
|
-
"ai",
|
|
20
|
-
"cost-tracker",
|
|
21
|
-
"burn-money"
|
|
22
|
-
],
|
|
23
|
-
"author": "winston-wwzhen",
|
|
24
|
-
"license": "MIT",
|
|
25
|
-
"bugs": {
|
|
26
|
-
"url": "https://github.com/winston-wwzhen/burn-your-money/issues"
|
|
27
|
-
},
|
|
28
|
-
"homepage": "https://github.com/winston-wwzhen/burn-your-money#readme"
|
|
29
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@winston.wan/burn-your-money",
|
|
3
|
+
"version": "3.0.1",
|
|
4
|
+
"description": "💸 Burn Your Money - 实时显示 Claude Code 的 token 消耗,看着你的钱包燃烧!",
|
|
5
|
+
"main": "src/statusline.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"postinstall": "node install.js",
|
|
8
|
+
"postuninstall": "node uninstall.js"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/winston-wwzhen/burn-your-money.git"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"claude-code",
|
|
16
|
+
"token-monitor",
|
|
17
|
+
"statusline",
|
|
18
|
+
"claude",
|
|
19
|
+
"ai",
|
|
20
|
+
"cost-tracker",
|
|
21
|
+
"burn-money"
|
|
22
|
+
],
|
|
23
|
+
"author": "winston-wwzhen",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/winston-wwzhen/burn-your-money/issues"
|
|
27
|
+
},
|
|
28
|
+
"homepage": "https://github.com/winston-wwzhen/burn-your-money#readme"
|
|
29
|
+
}
|
package/src/statusline.js
CHANGED
|
@@ -166,6 +166,21 @@ function main() {
|
|
|
166
166
|
const now = Math.floor(Date.now() / 1000);
|
|
167
167
|
let historyData = readJsonFile(HISTORY_CACHE, {});
|
|
168
168
|
|
|
169
|
+
// 如果缓存过期(超过 5 分钟),重新获取
|
|
170
|
+
const CACHE_EXPIRY = 300; // 5 分钟
|
|
171
|
+
if (!historyData._cache_time || (now - historyData._cache_time) > CACHE_EXPIRY) {
|
|
172
|
+
try {
|
|
173
|
+
const { execSync } = require('child_process');
|
|
174
|
+
const scriptPath = path.join(__dirname, 'token-history.js');
|
|
175
|
+
const freshData = execSync(`node "${scriptPath}" summary`, { encoding: 'utf8' });
|
|
176
|
+
historyData = JSON.parse(freshData);
|
|
177
|
+
historyData._cache_time = now;
|
|
178
|
+
writeJsonFile(HISTORY_CACHE, historyData);
|
|
179
|
+
} catch (e) {
|
|
180
|
+
// 如果获取失败,使用旧缓存
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
169
184
|
// 如果缓存不存在或无效,使用默认值(但保留已有数据)
|
|
170
185
|
if (!historyData || typeof historyData !== 'object') {
|
|
171
186
|
historyData = {};
|
|
@@ -176,34 +191,42 @@ function main() {
|
|
|
176
191
|
historyData._cache_time = now;
|
|
177
192
|
}
|
|
178
193
|
|
|
179
|
-
//
|
|
194
|
+
// 使用历史数据的今日统计(包含缓存 tokens)
|
|
195
|
+
const historyTodayTokens = historyData.today_tokens || 0;
|
|
196
|
+
const historyTodayCost = parseFloat(historyData.today_cost || 0);
|
|
197
|
+
|
|
198
|
+
// 今日数据持久化(用于当前会话增量)
|
|
180
199
|
const todayDate = new Date().toISOString().split('T')[0];
|
|
181
200
|
ensureDir(TODAY_STATE);
|
|
182
201
|
const todayState = readJsonFile(TODAY_STATE, {
|
|
183
202
|
date: todayDate,
|
|
184
|
-
tokens:
|
|
185
|
-
cost:
|
|
203
|
+
tokens: historyTodayTokens,
|
|
204
|
+
cost: historyTodayCost,
|
|
186
205
|
last_session_tokens: 0,
|
|
187
206
|
last_session_cost: 0
|
|
188
207
|
});
|
|
189
208
|
|
|
190
|
-
//
|
|
209
|
+
// 如果是新的一天,重置为历史数据
|
|
191
210
|
if (todayState.date !== todayDate) {
|
|
192
211
|
todayState.date = todayDate;
|
|
193
|
-
todayState.tokens =
|
|
194
|
-
todayState.cost =
|
|
212
|
+
todayState.tokens = historyTodayTokens;
|
|
213
|
+
todayState.cost = historyTodayCost;
|
|
195
214
|
todayState.last_session_tokens = 0;
|
|
196
215
|
todayState.last_session_cost = 0;
|
|
197
216
|
}
|
|
198
217
|
|
|
199
|
-
//
|
|
218
|
+
// 计算增量(当前会话相对于历史数据的增量)
|
|
200
219
|
let tokenIncrement = currentSessionTokens - (todayState.last_session_tokens || 0);
|
|
201
|
-
if (tokenIncrement < 0)
|
|
220
|
+
if (tokenIncrement < 0) {
|
|
221
|
+
tokenIncrement = currentSessionTokens;
|
|
222
|
+
}
|
|
202
223
|
|
|
203
224
|
let costIncrement = currentCost - (todayState.last_session_cost || 0);
|
|
204
|
-
if (costIncrement < 0
|
|
225
|
+
if (costIncrement < 0 || todayState.last_session_tokens === 0) {
|
|
226
|
+
costIncrement = currentCost;
|
|
227
|
+
}
|
|
205
228
|
|
|
206
|
-
//
|
|
229
|
+
// 新的今日数据 = 历史基础 + 当前增量
|
|
207
230
|
const newTodayTokens = (todayState.tokens || 0) + tokenIncrement;
|
|
208
231
|
const newTodayCost = (todayState.cost || 0) + costIncrement;
|
|
209
232
|
|
|
@@ -215,9 +238,9 @@ function main() {
|
|
|
215
238
|
last_session_cost: currentCost
|
|
216
239
|
});
|
|
217
240
|
|
|
218
|
-
//
|
|
219
|
-
const totalTokens = (historyData.total_tokens_all || 0)
|
|
220
|
-
const totalCost = (historyData.total_cost || 0)
|
|
241
|
+
// 计算总计(使用历史数据 + 当前会话)
|
|
242
|
+
const totalTokens = (historyData.total_tokens_all || 0);
|
|
243
|
+
const totalCost = parseFloat(historyData.total_cost || 0);
|
|
221
244
|
|
|
222
245
|
// 检查是否超过警报阈值
|
|
223
246
|
const isAlert = newTodayCost >= config.alert_daily;
|
package/src/token-history.js
CHANGED
|
@@ -78,18 +78,38 @@ function readStats() {
|
|
|
78
78
|
const totalOutputTokens = Object.values(data.modelUsage || {}).reduce((sum, model) => sum + (model.outputTokens || 0), 0);
|
|
79
79
|
const totalCacheReadTokens = Object.values(data.modelUsage || {}).reduce((sum, model) => sum + (model.cacheReadInputTokens || 0), 0);
|
|
80
80
|
|
|
81
|
-
//
|
|
81
|
+
// 获取今日 tokens
|
|
82
|
+
// 注意:dailyModelTokens 不包含缓存 tokens,且可能更新不及时
|
|
83
|
+
// 如果 lastComputedDate 不是今天,今日数据可能不准确
|
|
84
|
+
const lastComputedDate = data.lastComputedDate || '';
|
|
82
85
|
const dailyTokens = data.dailyModelTokens || [];
|
|
83
|
-
let historySum = 0;
|
|
84
|
-
dailyTokens.forEach(day => {
|
|
85
|
-
if (day.date !== TODAY) {
|
|
86
|
-
const models = day.tokensByModel || {};
|
|
87
|
-
historySum += Object.values(models).reduce((sum, tokens) => sum + (tokens || 0), 0);
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
86
|
|
|
91
|
-
const
|
|
92
|
-
|
|
87
|
+
const totalTokensAll = totalInputTokens + totalOutputTokens + totalCacheReadTokens;
|
|
88
|
+
|
|
89
|
+
// 检查是否有今日的 dailyModelTokens 记录
|
|
90
|
+
const todayEntry = dailyTokens.find(day => day.date === TODAY);
|
|
91
|
+
let todayTokens = 0;
|
|
92
|
+
if (todayEntry) {
|
|
93
|
+
const models = todayEntry.tokensByModel || {};
|
|
94
|
+
todayTokens = Object.values(models).reduce((sum, tokens) => sum + (tokens || 0), 0);
|
|
95
|
+
// 补偿缓存 tokens(按比例估算)
|
|
96
|
+
const cacheRatio = totalCacheReadTokens / (totalTokensAll || 1);
|
|
97
|
+
todayTokens = Math.floor(todayTokens * (1 + cacheRatio));
|
|
98
|
+
} else if (lastComputedDate !== TODAY) {
|
|
99
|
+
// lastComputedDate 不是今天,无法准确计算今日 tokens
|
|
100
|
+
todayTokens = 0;
|
|
101
|
+
} else {
|
|
102
|
+
// 尝试从总量推算(可能不准确)
|
|
103
|
+
let historySum = 0;
|
|
104
|
+
dailyTokens.forEach(day => {
|
|
105
|
+
if (day.date !== TODAY) {
|
|
106
|
+
const models = day.tokensByModel || {};
|
|
107
|
+
historySum += Object.values(models).reduce((sum, tokens) => sum + (tokens || 0), 0);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
todayTokens = totalTokensAll - historySum;
|
|
111
|
+
if (todayTokens < 0) todayTokens = 0;
|
|
112
|
+
}
|
|
93
113
|
|
|
94
114
|
// 本周 tokens(排除今天,用上面的 todayTokens)
|
|
95
115
|
const WEEK_START = getWeekStart();
|
|
@@ -123,18 +143,17 @@ function readStats() {
|
|
|
123
143
|
}
|
|
124
144
|
});
|
|
125
145
|
|
|
126
|
-
//
|
|
146
|
+
// 计算费用(按 3/15/0.3 规则:Input $3/M, Output $15/M, Cache $0.3/M)
|
|
127
147
|
const totalCost = (totalInputTokens / 1000000 * 3) +
|
|
128
148
|
(totalOutputTokens / 1000000 * 15) +
|
|
129
149
|
(totalCacheReadTokens / 1000000 * 0.3);
|
|
130
150
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const
|
|
135
|
-
const
|
|
136
|
-
const
|
|
137
|
-
const lastMonthCost = lastMonthTokens > 0 ? (lastMonthTokens / (totalInputTokens + totalOutputTokens) * totalCost) : 0;
|
|
151
|
+
// 计算各周期费用(按 token 数量比例分摊)
|
|
152
|
+
const totalTokensForRatio = totalTokensAll || 1;
|
|
153
|
+
const todayCost = todayTokens > 0 ? (todayTokens / totalTokensForRatio * totalCost) : 0;
|
|
154
|
+
const weekCost = weekTokens > 0 ? (weekTokens / totalTokensForRatio * totalCost) : 0;
|
|
155
|
+
const monthCost = monthTokens > 0 ? (monthTokens / totalTokensForRatio * totalCost) : 0;
|
|
156
|
+
const lastMonthCost = lastMonthTokens > 0 ? (lastMonthTokens / totalTokensForRatio * totalCost) : 0;
|
|
138
157
|
|
|
139
158
|
// 获取每日数据(用于趋势图)
|
|
140
159
|
const dailyData = dailyTokens.slice(-7).map(day => ({
|
|
@@ -257,10 +276,11 @@ function main() {
|
|
|
257
276
|
console.log('');
|
|
258
277
|
|
|
259
278
|
// 统计数据
|
|
260
|
-
console.log('
|
|
279
|
+
console.log('💰 Token 价格说明');
|
|
261
280
|
console.log('─'.repeat(40));
|
|
262
|
-
console.log(
|
|
263
|
-
console.log(
|
|
281
|
+
console.log(' Input: $3.00 / 百万 tokens');
|
|
282
|
+
console.log(' Output: $15.00 / 百万 tokens');
|
|
283
|
+
console.log(' Cache: $0.30 / 百万 tokens (90% 折扣)');
|
|
264
284
|
console.log('');
|
|
265
285
|
|
|
266
286
|
console.log('📆 本周消费');
|
|
@@ -321,6 +341,57 @@ function main() {
|
|
|
321
341
|
console.log(' 🎮 "知识就是财富,而财富就是知识" 🎮');
|
|
322
342
|
console.log('='.repeat(50) + '\n');
|
|
323
343
|
break;
|
|
344
|
+
case 'uninstall':
|
|
345
|
+
// 卸载插件
|
|
346
|
+
console.log('\n🗑️ 卸载 Burn Your Money...\n');
|
|
347
|
+
|
|
348
|
+
const homeDir = os.homedir();
|
|
349
|
+
const filesToDelete = [
|
|
350
|
+
path.join(homeDir, '.claude', 'statusline.js'),
|
|
351
|
+
path.join(homeDir, '.claude', 'scripts', 'token-history.js'),
|
|
352
|
+
path.join(homeDir, '.claude', 'commands', 'burn-your-money.md'),
|
|
353
|
+
path.join(homeDir, '.claude', 'commands', 'burn-your-money-stats.md'),
|
|
354
|
+
path.join(homeDir, '.claude', 'commands', 'burn-your-money-export.md'),
|
|
355
|
+
path.join(homeDir, '.claude', 'commands', 'burn-your-money-uninstall.md')
|
|
356
|
+
];
|
|
357
|
+
|
|
358
|
+
// 删除文件
|
|
359
|
+
let deletedCount = 0;
|
|
360
|
+
filesToDelete.forEach(file => {
|
|
361
|
+
if (fs.existsSync(file)) {
|
|
362
|
+
try {
|
|
363
|
+
fs.unlinkSync(file);
|
|
364
|
+
console.log(` ✓ 已删除: ${path.basename(file)}`);
|
|
365
|
+
deletedCount++;
|
|
366
|
+
} catch (e) {
|
|
367
|
+
console.log(` ✗ 删除失败: ${path.basename(file)} - ${e.message}`);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
// 移除 settings.json 中的 statusLine 配置
|
|
373
|
+
const settingsFile = path.join(homeDir, '.claude', 'settings.json');
|
|
374
|
+
if (fs.existsSync(settingsFile)) {
|
|
375
|
+
try {
|
|
376
|
+
const settings = JSON.parse(fs.readFileSync(settingsFile, 'utf8'));
|
|
377
|
+
if (settings.statusLine) {
|
|
378
|
+
delete settings.statusLine;
|
|
379
|
+
fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2));
|
|
380
|
+
console.log(` ✓ 已移除 settings.json 中的 statusLine 配置`);
|
|
381
|
+
deletedCount++;
|
|
382
|
+
}
|
|
383
|
+
} catch (e) {
|
|
384
|
+
console.log(` ✗ 更新 settings.json 失败: ${e.message}`);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
console.log(`\n✅ 卸载完成!共处理 ${deletedCount} 项。`);
|
|
389
|
+
console.log(`\n请重启 Claude Code 使更改生效。\n`);
|
|
390
|
+
|
|
391
|
+
// 如果是通过 npm 安装的,提示也运行 npm uninstall
|
|
392
|
+
console.log('💡 提示:如果你是通过 npm 全局安装的,也可以运行:');
|
|
393
|
+
console.log(' npm uninstall -g @winston.wan/burn-your-money\n');
|
|
394
|
+
break;
|
|
324
395
|
default:
|
|
325
396
|
// summary
|
|
326
397
|
console.log(JSON.stringify(stats, null, 2));
|
package/uninstall.js
CHANGED
|
@@ -35,7 +35,8 @@ function uninstallFiles() {
|
|
|
35
35
|
// 命令文件
|
|
36
36
|
path.join(home, '.claude', 'commands', 'burn-your-money.md'),
|
|
37
37
|
path.join(home, '.claude', 'commands', 'burn-your-money-stats.md'),
|
|
38
|
-
path.join(home, '.claude', 'commands', 'burn-your-money-export.md')
|
|
38
|
+
path.join(home, '.claude', 'commands', 'burn-your-money-export.md'),
|
|
39
|
+
path.join(home, '.claude', 'commands', 'burn-your-money-uninstall.md')
|
|
39
40
|
];
|
|
40
41
|
|
|
41
42
|
log("Removing plugin files...", colors.cyan);
|