@winston.wan/burn-your-money 2.0.4 → 2.1.0
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 +71 -113
- package/bash.exe.stackdump +28 -0
- package/bin/burn-your-money +40 -20
- package/docs/ARCHITECTURE.md +58 -0
- package/docs/images/architecture_sketch.png +0 -0
- package/fix-git-bash.ps1 +154 -0
- package/install.js +254 -25
- package/package.json +3 -2
- package/src/statusline.sh +250 -205
- package/src/token-history.sh +268 -238
- package/uninstall.js +94 -0
package/src/statusline.sh
CHANGED
|
@@ -1,205 +1,250 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Burn Your Money - Claude Code 状态栏脚本
|
|
3
|
-
# 格式:[模型] 今日:token 费用 🔥速度 | 总计:token 费用
|
|
4
|
-
|
|
5
|
-
#
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
#
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
#
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
fi
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
fi
|
|
146
|
-
|
|
147
|
-
#
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
#
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Burn Your Money - Claude Code 状态栏脚本
|
|
3
|
+
# 格式:[模型] 今日:token 费用 🔥速度 | 总计:token 费用
|
|
4
|
+
|
|
5
|
+
# 设置 jq 命令路径(支持自定义安装的 jq)
|
|
6
|
+
if [ -n "$JQ_PATH" ]; then
|
|
7
|
+
JQ_CMD="$JQ_PATH"
|
|
8
|
+
elif [ -f "$HOME/.claude/bin/jq.exe" ]; then
|
|
9
|
+
JQ_CMD="$HOME/.claude/bin/jq.exe"
|
|
10
|
+
elif [ -f "$HOME/.claude/bin/jq" ]; then
|
|
11
|
+
JQ_CMD="$HOME/.claude/bin/jq"
|
|
12
|
+
else
|
|
13
|
+
JQ_CMD="jq"
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
# 验证 jq 是否可用
|
|
17
|
+
if ! command -v "$JQ_CMD" >/dev/null 2>&1; then
|
|
18
|
+
# jq 不可用,返回简单错误信息
|
|
19
|
+
printf "[BurnYourMoney] Error: jq not found. Run: npm install -g @winston.wan/burn-your-money"
|
|
20
|
+
exit 0
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
# 配置文件
|
|
24
|
+
CONFIG_FILE="$HOME/.claude/burn-your-money-config.json"
|
|
25
|
+
SESSION_STATE="$HOME/.claude/cache/session-state.json"
|
|
26
|
+
TODAY_STATE="$HOME/.claude/cache/today-state.json"
|
|
27
|
+
HISTORY_CACHE="$HOME/.claude/cache/history-cache.json"
|
|
28
|
+
|
|
29
|
+
# 初始化配置
|
|
30
|
+
init_config() {
|
|
31
|
+
if [ ! -f "$CONFIG_FILE" ]; then
|
|
32
|
+
$JQ_CMD -n '{
|
|
33
|
+
theme: "fire",
|
|
34
|
+
alert_daily: 10.0,
|
|
35
|
+
alert_weekly: 50.0,
|
|
36
|
+
show_burn_rate: true,
|
|
37
|
+
show_eta: true,
|
|
38
|
+
show_trend: false
|
|
39
|
+
}' > "$CONFIG_FILE"
|
|
40
|
+
fi
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# 1. 批量读取配置 (One JQ pass)
|
|
44
|
+
init_config
|
|
45
|
+
if [ -f "$CONFIG_FILE" ]; then
|
|
46
|
+
# 使用 eval 批量赋值变量
|
|
47
|
+
eval $("$JQ_CMD" -r '@sh "THEME=\(.theme // "fire") ALERT_DAILY=\(.alert_daily // 10) SHOW_BURN_RATE=\(.show_burn_rate // true)"' "$CONFIG_FILE" 2>/dev/null)
|
|
48
|
+
else
|
|
49
|
+
THEME="fire"
|
|
50
|
+
ALERT_DAILY=10
|
|
51
|
+
SHOW_BURN_RATE=true
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
# 主题颜色定义
|
|
55
|
+
case "$THEME" in
|
|
56
|
+
fire) TODAY_COLOR="0;31"; COST_COLOR="0;31"; BURN_COLOR="0;31" ;;
|
|
57
|
+
ocean) TODAY_COLOR="0;34"; COST_COLOR="0;36"; BURN_COLOR="0;36" ;;
|
|
58
|
+
forest) TODAY_COLOR="0;32"; COST_COLOR="0;33"; BURN_COLOR="0;32" ;;
|
|
59
|
+
golden) TODAY_COLOR="0;33"; COST_COLOR="0;33"; BURN_COLOR="1;33" ;;
|
|
60
|
+
*) TODAY_COLOR="0;36"; COST_COLOR="0;34"; BURN_COLOR="0;31" ;;
|
|
61
|
+
esac
|
|
62
|
+
|
|
63
|
+
# 格式化函数 (纯 Bash/Awk 实现,无外部进程调用)
|
|
64
|
+
format_number() {
|
|
65
|
+
local num=$1
|
|
66
|
+
if [ "$num" -ge 1000000000 ]; then awk "BEGIN {printf \"%.1fB\", $num/1000000000}";
|
|
67
|
+
elif [ "$num" -ge 1000000 ]; then awk "BEGIN {printf \"%.1fM\", $num/1000000}";
|
|
68
|
+
elif [ "$num" -ge 1000 ]; then awk "BEGIN {printf \"%.1fK\", $num/1000}";
|
|
69
|
+
else echo "$num"; fi
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
format_cost() {
|
|
73
|
+
awk "BEGIN {printf \"\$%.2f\", $1}"
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
format_burn_rate() {
|
|
77
|
+
local rate=$1
|
|
78
|
+
if [ "$rate" -ge 1000 ]; then awk "BEGIN {printf \"%.1fK tok/s\", $rate/1000}";
|
|
79
|
+
else awk "BEGIN {printf \"%.0f tok/s\", $rate}"; fi
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
# 2. 核心数据解析 (One JQ pass from stdin)
|
|
83
|
+
# 将 stdin 存入临时文件(避免大 JSON 管道卡顿)
|
|
84
|
+
TEMP_JSON=$(mktemp)
|
|
85
|
+
trap "rm -f $TEMP_JSON" EXIT
|
|
86
|
+
cat > "$TEMP_JSON"
|
|
87
|
+
|
|
88
|
+
# 一次性提取所有需要的字段
|
|
89
|
+
# 注意:使用 @sh 确保字符串安全引用
|
|
90
|
+
eval $("$JQ_CMD" -r '
|
|
91
|
+
@sh "model_name=\(.model.display_name // "Unknown")
|
|
92
|
+
session_id=\(.session_id // "default")
|
|
93
|
+
input=\(.context_window.total_input_tokens // 0)
|
|
94
|
+
output=\(.context_window.total_output_tokens // 0)
|
|
95
|
+
cache=\(.context_window.current_usage.cache_read_input_tokens // 0)
|
|
96
|
+
current_cost=\(.cost.total_cost_usd // 0)"
|
|
97
|
+
' "$TEMP_JSON" 2>/dev/null)
|
|
98
|
+
|
|
99
|
+
# 计算当前会话 Token 总量
|
|
100
|
+
current_session_tokens=$((input + output + cache))
|
|
101
|
+
|
|
102
|
+
# 3. 燃烧速度计算 (Burn Rate)
|
|
103
|
+
current_time=$(date +%s)
|
|
104
|
+
start_time_cache=0
|
|
105
|
+
|
|
106
|
+
# 读取 Session State (尽可能不做写入,除非是新 Session)
|
|
107
|
+
if [ -f "$SESSION_STATE" ]; then
|
|
108
|
+
eval $("$JQ_CMD" -r '@sh "cached_id=\(.session_id // "") cached_time=\(.start_time // 0)"' "$SESSION_STATE" 2>/dev/null)
|
|
109
|
+
else
|
|
110
|
+
cached_id=""
|
|
111
|
+
cached_time=0
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
if [ "$cached_id" = "$session_id" ] && [ "$cached_time" -ne 0 ]; then
|
|
115
|
+
start_time_cache=$cached_time
|
|
116
|
+
else
|
|
117
|
+
# New session detected - 使用原子写入
|
|
118
|
+
start_time_cache=$current_time
|
|
119
|
+
SESSION_STATE_TMP="${SESSION_STATE}.tmp.$$"
|
|
120
|
+
$JQ_CMD -n --arg id "$session_id" --arg time "$current_time" '{session_id: $id, start_time: ($time | tonumber)}' > "$SESSION_STATE_TMP" 2>/dev/null && mv "$SESSION_STATE_TMP" "$SESSION_STATE"
|
|
121
|
+
fi
|
|
122
|
+
|
|
123
|
+
elapsed=$((current_time - start_time_cache))
|
|
124
|
+
burn_rate=0
|
|
125
|
+
if [ "$elapsed" -gt 0 ] && [ "$current_session_tokens" -gt 0 ]; then
|
|
126
|
+
burn_rate=$(awk "BEGIN {printf \"%.0f\", ${current_session_tokens} / ${elapsed}}")
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
# 4. 历史数据缓存 (每 30 秒刷新)
|
|
130
|
+
# 确保 cache 目录存在
|
|
131
|
+
mkdir -p "$(dirname "$HISTORY_CACHE")" 2>/dev/null
|
|
132
|
+
|
|
133
|
+
now=$(date +%s)
|
|
134
|
+
cache_age=999999
|
|
135
|
+
if [ -f "$HISTORY_CACHE" ]; then
|
|
136
|
+
# Portable modification time check
|
|
137
|
+
if [ "$(uname)" = "Darwin" ]; then
|
|
138
|
+
cache_time=$(stat -f %m "$HISTORY_CACHE" 2>/dev/null || echo 0)
|
|
139
|
+
else
|
|
140
|
+
cache_time=$(stat -c %Y "$HISTORY_CACHE" 2>/dev/null || echo 0)
|
|
141
|
+
fi
|
|
142
|
+
cache_age=$((now - cache_time))
|
|
143
|
+
else
|
|
144
|
+
echo '{}' > "$HISTORY_CACHE"
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
# 如果缓存太旧,并在后台刷新(虽然后台刷新可能在 statusline 不合适,但我们可以同步调用历史脚本)
|
|
148
|
+
# 为了性能,这里我们仍然做同步调用,但减少频率
|
|
149
|
+
if [ $cache_age -gt 30 ]; then
|
|
150
|
+
# 重新生成历史数据
|
|
151
|
+
~/.claude/scripts/token-history.sh summary > "$HISTORY_CACHE" 2>/dev/null
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
# 读取历史总计 (One pass)
|
|
155
|
+
eval $("$JQ_CMD" -r '@sh "history_total_tokens=\(.total_tokens_all // 0) history_total_cost=\(.total_cost // 0)"' "$HISTORY_CACHE" 2>/dev/null)
|
|
156
|
+
|
|
157
|
+
# 5. 今日数据持久化 (One-pass Read & Write)
|
|
158
|
+
# 逻辑:读取旧状态 -> 计算差值 -> 写入新状态
|
|
159
|
+
today_date=$(date +%Y-%m-%d)
|
|
160
|
+
|
|
161
|
+
if [ -f "$TODAY_STATE" ]; then
|
|
162
|
+
eval $("$JQ_CMD" -r '@sh "saved_date=\(.date // "") saved_tokens=\(.tokens // 0) saved_cost=\(.cost // 0) last_session_tokens=\(.last_session_tokens // 0) last_session_cost=\(.last_session_cost // 0)"' "$TODAY_STATE" 2>/dev/null)
|
|
163
|
+
else
|
|
164
|
+
saved_date=""
|
|
165
|
+
saved_tokens=0
|
|
166
|
+
saved_cost=0
|
|
167
|
+
last_session_tokens=0
|
|
168
|
+
last_session_cost=0
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
if [ "$saved_date" != "$today_date" ]; then
|
|
172
|
+
# 新的一天,重置累计数据
|
|
173
|
+
saved_tokens=0
|
|
174
|
+
saved_cost=0
|
|
175
|
+
last_session_tokens=0
|
|
176
|
+
last_session_cost=0
|
|
177
|
+
fi
|
|
178
|
+
|
|
179
|
+
# 计算增量
|
|
180
|
+
token_increment=$((current_session_tokens - last_session_tokens))
|
|
181
|
+
if [ "$token_increment" -lt 0 ]; then token_increment=0; fi # 防御性编程
|
|
182
|
+
|
|
183
|
+
# 浮点数增量计算
|
|
184
|
+
cost_increment=$(awk "BEGIN {print ($current_cost - $last_session_cost < 0 ? 0 : $current_cost - $last_session_cost)}")
|
|
185
|
+
|
|
186
|
+
# 计算新的今日累计
|
|
187
|
+
new_today_tokens=$((saved_tokens + token_increment))
|
|
188
|
+
new_today_cost=$(awk "BEGIN {printf \"%.5f\", $saved_cost + $cost_increment}")
|
|
189
|
+
|
|
190
|
+
# 写入新状态 (一次性写入,使用原子写入避免并发问题)
|
|
191
|
+
# 先写入临时文件,然后重命名,确保原子性
|
|
192
|
+
TODAY_STATE_TMP="${TODAY_STATE}.tmp.$$"
|
|
193
|
+
$JQ_CMD -n \
|
|
194
|
+
--arg date "$today_date" \
|
|
195
|
+
--arg tokens "$new_today_tokens" \
|
|
196
|
+
--arg cost "$new_today_cost" \
|
|
197
|
+
--arg last_sess_tok "$current_session_tokens" \
|
|
198
|
+
--arg last_sess_cost "$current_cost" \
|
|
199
|
+
'{
|
|
200
|
+
date: $date,
|
|
201
|
+
tokens: ($tokens|tonumber),
|
|
202
|
+
cost: ($cost|tonumber),
|
|
203
|
+
last_session_tokens: ($last_sess_tok|tonumber),
|
|
204
|
+
last_session_cost: ($last_sess_cost|tonumber)
|
|
205
|
+
}' > "$TODAY_STATE_TMP" 2>/dev/null && mv "$TODAY_STATE_TMP" "$TODAY_STATE"
|
|
206
|
+
|
|
207
|
+
# 6. 最终计算与显示
|
|
208
|
+
total_tokens_all=$((history_total_tokens + current_session_tokens))
|
|
209
|
+
total_cost_all=$(awk "BEGIN {print $history_total_cost + $current_cost}")
|
|
210
|
+
|
|
211
|
+
# 警报检查
|
|
212
|
+
is_alert=false
|
|
213
|
+
if awk "BEGIN {exit !($new_today_cost >= $ALERT_DAILY)}"; then
|
|
214
|
+
is_alert=true
|
|
215
|
+
fi
|
|
216
|
+
|
|
217
|
+
# 构建输出字符串
|
|
218
|
+
output=""
|
|
219
|
+
|
|
220
|
+
# [Model]
|
|
221
|
+
if [ "$is_alert" = true ]; then
|
|
222
|
+
output+="\\033[1;31m[${model_name}]\\033[0m "
|
|
223
|
+
else
|
|
224
|
+
output+="\\033[0;90m[${model_name}]\\033[0m "
|
|
225
|
+
fi
|
|
226
|
+
|
|
227
|
+
# Today
|
|
228
|
+
d_today_tokens=$(format_number $new_today_tokens)
|
|
229
|
+
d_today_cost=$(format_cost $new_today_cost)
|
|
230
|
+
|
|
231
|
+
if [ "$new_today_tokens" -gt 0 ]; then
|
|
232
|
+
output+="\\033[${TODAY_COLOR}m今日:${d_today_tokens}\\033[0m "
|
|
233
|
+
output+="\\033[${COST_COLOR}m${d_today_cost}\\033[0m "
|
|
234
|
+
else
|
|
235
|
+
output+="\\033[0;90m今日:0\\033[0m \\033[0;90m\$0.00\\033[0m "
|
|
236
|
+
fi
|
|
237
|
+
|
|
238
|
+
# Burn Rate
|
|
239
|
+
if [ "$SHOW_BURN_RATE" = "true" ] && [ "$burn_rate" -gt 0 ]; then
|
|
240
|
+
d_burn=$(format_burn_rate $burn_rate)
|
|
241
|
+
output+="\\033[${BURN_COLOR}m🔥${d_burn}\\033[0m "
|
|
242
|
+
fi
|
|
243
|
+
|
|
244
|
+
# Total
|
|
245
|
+
d_total_tokens=$(format_number $total_tokens_all)
|
|
246
|
+
d_total_cost=$(format_cost $total_cost_all)
|
|
247
|
+
output+="| \\033[0;90m总计:${d_total_tokens}\\033[0m \\033[0;90m${d_total_cost}\\033[0m "
|
|
248
|
+
|
|
249
|
+
# Print final result
|
|
250
|
+
printf "%b" "$output"
|