Token Usage Tracker(每轮 token 消耗追踪)
环境要求(新用户先看)
- 仅适配 WorkBuddy / CodeBuddy 桌面端(Windows 10/11):本技能的数据源是客户端落盘的
~/.workbuddy/traces/<pid>/trace_*.json(每轮模型调用结束自动生成)+ 客户端 hooks 挂载点——其他 AI 工具/平台(Claude Code、Cursor、ChatGPT 桌面版、其他 OpenClaw 客户端等)没有这套机制,装上也不会工作,请勿在其他环境安装。 - Windows 10/11:系统通知(toast)仅 Windows 支持;macOS/Linux 可正常手动使用(方式 A),但不弹通知。
- Node.js ≥ 20:脚本零依赖单文件,无需 npm install。
安装与启用(新用户必读:装完必须配 hooks 才自动弹通知)
从技能市场安装 = 文件拷入 skills 目录,不会自动挂 hook。请让 WorkBuddy 助手帮你把下面配置合并进 settings.json(或手动添加):
{
"hooks": {
"UserPromptSubmit": [
{ "matcher": ".*", "hooks": [ { "type": "command", "command": "node <技能目录>/token-tracker.js --hook" } ] }
],
"Stop": [
{ "matcher": ".*", "hooks": [ { "type": "command", "command": "node <技能目录>/token-tracker.js --stop" } ] }
]
}
}
<技能目录> 替换为实际安装路径(如 C:/Users/你的用户名/.workbuddy/skills/token-usage-tracker)。效果:
- 挂好
Stophook → 每轮回答结束自动弹「本条消耗」Windows 通知(核心体验) - 挂好
UserPromptSubmithook → 下轮提问时自动注入上一轮用量 - 不挂 hooks 也能用:手动运行
node <技能目录>/token-tracker.js --stop查看最近一轮消耗(方式 A) - 不需要通知:只删
Stop段即可,其余功能不受影响
为什么需要
WorkBuddy 客户端 UI 不显示每轮对话的 token 用量:内置模型只显示「积分」,自有 API 模式也不展示 token。但平台在每次模型调用整轮结束后都会把真实用量写进一个新的 traces/<pid>/trace_*.json(含 totalTokens / totalInputTokens / totalOutputTokens / totalCachedTokens / duration / startedAt / endedAt)。本技能把这些数据读出来,让你每轮都能看到真实消耗。
触发条件
- 用户明确要求看 token / 消耗 / 用时
- 或作为默认习惯:每次生成最终回复时,都在末尾附上最近一轮用量
使用方式(两种,任选/并用)
方式 A:手动(技能指令驱动,最贴合「回复末尾」)
在生成最终可见回复时,先运行读数脚本,把其输出作为回复的最后单独一行:
node ~/.workbuddy/skills/token-usage-tracker/token-tracker.js
脚本输出形如:
GLM-4.6V | 耗时 3m 43s | 输入 287.9万 / 输出 1.2万 tokens(该轮累计 289.1万,缓存命中 287.2万)
模型名完整显示(去括号说明,不截断);未识别到模型名时省略模型段。数字自动用「万/亿」单位(保留 1 位小数),低于 1 万显示原值,方便速读。
把这行原样贴在回复最末尾(独占一行,前面空一行与其它内容隔开)。2026-08 起以系统通知(方式 C)为准:每次回答结束 Stop hook 已自动弹「本条 Token 消耗」通知,回复末尾的「上一轮」行时效性差且冗余——默认省略;仅在用户明确要求("贴一下用量/这次用了多少")时才运行脚本贴出。
方式 B:自动(hook 注入,不依赖记得)
settings.json 的 hooks.UserPromptSubmit 已挂接本技能的 --hook 模式,会在你提交下一轮时自动把「上一轮」的 token 注入上下文,无需手动跑脚本。
方式 C:Stop 事件 + Windows 系统通知(目标:本条回答显示本条消耗与费用)
WorkBuddy 是 Claude Code fork,支持 Stop 事件(回答结束后触发)。settings.json 的 hooks.Stop 已挂接本技能 --stop 模式:回答结束时本轮 trace 已落盘(实测 Stop 比落盘早 ~15ms,脚本会轮询等待最多 3 秒),读到最新文件即为本条回答的精确统计,然后:
v2.19(2026-08-06 修复):旧逻辑只在
sameRound || !snap时等待;若入口文件恰是"另一个旧文件"(如会话起标题的 terminalTitleGenerator 小 trace)且 ≠ 快照文件,会被误判为"本条"直接弹 toast(曾把 744 tokens 当成本轮展示,真实 122.3 万)。现改为:入口文件非"刚落盘"(≤1s)时一律轮询等待"比入口更新的有效 trace"(≤3s);超时且入口明显是旧文件(落盘 >3s 前)→ 标"上一轮"不冒充本条。备份:token-tracker.js.bak-20260806。
v2.20(2026-08-06,完整消耗聚合):一个用户轮次会落盘多个 trace(起标题内部调用 + 主任务,实测 744 + 122.3 万),v2.19 仍只取最新一个——用户明确要求完整数据。现增加整轮聚合:
--hook(用户提交时)把本轮起点时间戳lastUserMsgAt写入快照;--stop聚合「起点之后、同 pid 目录、(无 sessionId 的内部调用 或 sessionId 与 Stop payload 一致)」的全部有效 trace,累加 in/out/cached/total,耗时 = 窗口内最早 startedAt → 最新 endedAt;无起点记录(手动运行)退化为单 trace(v2.19 行为)。快照保存时保留 lastUserMsgAt。备份:token-tracker.js.v2.19-20260806。
v2.21(2026-08-06,多会话并发快照隔离):v2.20 的
.snapshot.json是全局单文件、不带 sessionId,hook matcher 为.*全局触发——同时开多个会话时,后提交的会话会把lastUserMsgAt覆盖成自己的时间 → 先结束的会话 Stop 聚合起点错乱(用户问"多任务并发时 token 计算是不是没用了")。现改为快照按session_id拆分:hook/stop 都从 payload 读session_id(WorkBuddy 所有 hook payload 均带此字段,实测/官方文档确认),有 sid →.snapshot-<sid>.json(各会话隔离);无 sid(手动运行)→ 全局.snapshot.json(行为不变)。sid 来自外部 payload,只留[a-zA-Z0-9_-]防路径注入。备份:token-tracker.js.v2.20-20260806。验证:隔离测试 7/7 通过(A/B 双会话互不覆盖、起点先后正确、手动全局快照兼容、单会话聚合回归)。已知限制(P1 沿用):无 sessionId 的内部调用(起标题等)在同一 pid 目录同时活跃多会话时无法归属会话,理论上会串入窗口内的其他会话——仅限"同进程多会话真并发",日常单会话/顺序多会话不受影响。
v2.22(2026-08-06,内部调用按最近主任务归属):v2.21 遗留 P1——无 sessionId 的内部调用(起标题等)只看"起点之后",B 会话在 A 任务中途提交时,A 的内部调用时间戳落在 B 起点之后会被 B 误收。用户洞察:"各会话任务结束时间不可能在同一秒"。现改为最近主任务归属:对无 sessionId 的内部调用,找到时间距离最近的主任务 trace(有 sessionId 的,窗口内距离=0,否则取端点最近者),归属该会话;只有归属本会话的才累加。这样利用任务时间线天然分隔并发会话,比"±N 秒容差窗口"精确。备份:
token-tracker.js.v2.21-20260806。验证:隔离测试 10/10 通过(含核心 S4:真并发交错时 A 的内部调用归 A、B 不误收;S5 反向验证)。剩余极限:两个会话的内部调用与各自主任务时间线完全重合(同秒级真并发)时仍无法区分——数据源无 sessionId 标记,属平台限制。
- Windows 系统通知(toast,两行,Windows 通知默认只显示两行):调用 PowerShell WinRT Toast API 弹出
——布局原则(避免 ToastText02 正文超长自动换行变 3 行;v2.6 起行1 带时段标注、行2 去「约」字、v2.7 输入/输出写完整、v2.8 余额移至行1、v2.11 余额紧跟时间、v2.15 去「¥」符号、v2.16 分隔符全角→半角、v2.17 实测上限 47u + 分隔符两侧加空格、v2.18 恢复「¥」符号):行1 = 模型完整名(去括号说明,不截断)** | 时段标注 | 耗时 + 余额¥X(耗时↔余额 1 空格)。⚠️ v2.17 实测修正(关键):行1 标题大字真实上限 = 47u**——用户弹 5 个通知逐步加空格定位:测试四(模型名后 4 空格=47u)不换行、测试五(5 空格=48u)换行 →DeepSeek-V4 Flash | 高峰 | 4m 26s 余额¥2.77 输入 391.2万 / 输出 1.9万|缓存99.74%|¥0.25TOAST_ROW1_MAX_W=47(此前 42u 是保守估算,低估 5u;放宽后分隔符两侧可加空格提升可读性)。时段标注periodNote():模型声明peak_multiplier>1(DeepSeek 原厂系=2)且当前在高峰时段(工作日 9-12/14-18)→高峰(两字,v2.14 用户拍板:不带 ×N;v2.12「峰×N」太简被否、v2.13「高峰×N」实测 1 分钟+耗时余额必丢被否);其余模型统一定价不显示;预留night_discount夜间折扣字段。耗时保持1m 40s单位格式(用户要求,不压缩;分钟可为 2-3 位如12m/123m,fmtDur 上限999m 59s=9u)。v2.17 分隔符规则(定稿):半角|(dispWidth 1u,禁止全角「|」2u),两侧各 1 空格——模型名 | 时段 | 耗时(用户反馈"模型名和分隔符挨太近像一体",加空格后DeepSeek-V4 Flash | 高峰 | 12m 34s 余额¥123.45);无时段 →模型名 | 耗时;余额带「¥」符号(v2.18 恢复:实测上限 47u 后空间充裕,峰值场景 45u+1u=46u 仍有富余;v2.15 曾去符号省宽度)。极限宽度矩阵(v2.18 定稿验证,上限 47u):高峰场景 12 组合 9 个 ≤47u 含余额(用户峰值场景"12m 34s 几十分钟+余额¥123.45 上百块"=46u ✅;1m 40s+万位=47u 卡线;仅"长耗时+万位余额"丢,现实不存在);无高峰最坏 42u。行2 = 输入 X / 输出 Y|缓存NN.NN%(两位小数)|¥费用(不带「约」字,未收录显示「未收录」;行2 保持全角「|」正文小字视觉统一,有空间)。余额放行1:行2 在输入/输出+缓存+费用后已无空间(实测约 49u/上限 52u),追加余额会折叠变 3 行。布局演进(用户多轮实测纠正):v2.8 行2 追加 → 折叠;v2.9 行1 对齐行中线 → base 长时 pad=1 贴死;v2.10 剩余空间居中 → 标题大字超宽变 3 行;v2.11 紧跟时间 + 行1 上限 42u(保守);v2.15 去「¥」;v2.16 全角「|」→半角|(省 2u);v2.17 用户弹 5 通知实测极限 47u → 上限放宽 + 分隔符两侧加空格;v2.18 恢复「¥」符号(用户:极限有富余就加回)。宽度压力测试(用户要求算极端,含时间变长维度):行1 标题大字上限 47u(v2.18 高峰 43-47u / 无高峰 34-42u);行2 正文双千万级(1e8 以下最坏10000万)输入+输出 + 缓存99.99% + 价格 ¥3000 级 = 49u / 上限 52u,余 3u + 保护兜底(丢缓存占比);行1 超宽(长模型名)余额自动让位不折叠。说明:Windows toast 第二行默认即「正文小字号」(ToastText02=标题大字+正文小字);更小字号(Caption)需 AdaptiveGroup+HintStyle 自定义 XML(Win10 周年更新+),兼容性有风险,暂未采用。这是当前唯一确认有效的"本条可见"通道(UI 内systemMessage通道实测不显示,已放弃)。 - 时段折扣数据(2026-08-05 搜索核验):目前仅 DeepSeek 原厂系有峰谷定价(V4 起推出,工作日北京时间 9-12/14-18 高峰,所有计费项 ×2,含缓存价;原因=算力挤兑削峰填谷;2025 年的「夜间错峰优惠」已被高峰溢价模式取代)。智谱 GLM / MiniMax / Kimi / 混元均为统一定价无峰谷;小米 MiMo 是 2026-05 永久降价 99%(非时段折扣)。新模型自动补录时默认
peak_multiplier:1,提示里会要求搜索核验时段折扣策略。 - 仍同时输出
hookSpecificOutput.systemMessage(保留,若未来平台支持即生效,无副作用)。 - 探针
.stop-probe.json记录每次触发:sameRound=false+waited=ok= 成功拿到本条;waited=timeout= 3 秒内 trace 未落盘,退化为「上一轮」且不弹通知。 - 若用户不需要系统通知,可删除
settings.json中hooks.Stop配置(--hook/手动模式不受影响)。
费用估算(pricing.json + 高峰时段 + 每日自动刷新)
- 模型名读取:
trace.modelInfo.models[0](空壳 trace 从 spans 的toolOutput[].model取);带厂商前缀的模型名(如moonshotai/kimi-k2.7-code、deepseek/deepseek-v4-flash)由findModel()做包含匹配,自动落到本地 key。 - 价格缓存:
~/.workbuddy/skills/token-usage-tracker/pricing.json(官方人民币价:输入/缓存命中输入/输出,元每百万 tokens;peak_multiplier高峰倍率;or_id关联 OpenRouter 模型 id;usd_input_price/usd_output_price为自动刷新写入的 USD 参考价)。 - 已收录模型(2026-08-04 官方定价页 + OpenRouter 交叉核对):deepseek-v4-flash(1/0.02/2,峰谷×2)/v4-pro(3/0.025/6,峰谷×2)/v3.2/v3.1/r1、glm-5.2(8/2/28)/5.1(8/2/28)/5-turbo(7/1.8/26)/5(6/1.5/22)/5v-turbo(8.6/1.7/28.8)/4.7/4.7-flash(免费)、kimi-k3(20/2/100)/k2.7-code(6.5/1.3/27)/k2.7-code-highspeed(13/2.6/54)/k2.6(6.5/1.1/27)/k2.5(4/0.7/21)、minimax-m3(≤512k 标准 4.2/0.84/16.8,>512k 翻倍,促销五折 2.1/0.42/8.4)/m2.7(2.1/0.42/8.4)、hy3(1/0.25/4)/hy3-preview/hunyuan-a13b。
- 新模型自动补录(用户要求"检测到未收录模型立即联网查"):trace 读到未收录模型(pricing.json 无匹配且无 input_price)时,
token-tracker.js自动执行:- 立即联网查 OpenRouter(
openrouter.ai/api/v1/models,无需 key)按模型名精确/包含匹配; - 找到 → 按 USD×汇率(7.2)补入
pricing.json(auto_converted: true,缓存价按输入 10% 估算,标 note "待人工核验官方价"),同时 hook/手动输出附提示「已自动补录估算价」; - OpenRouter 确认无此模型 → 记入
pricing._lookedup_models(同一模型当天不再重复联网),输出提示「请用 unified-search 搜官方定价页补录」; - 联网失败 → 不记已查(下次重试),输出提示「联网查价失败」。
- 维护原则:不追求收录所有模型,只维护应用内置 + 用户常用模型;新模型由上述自动补录 + 人工核验(unified-search 搜厂商官方定价页)补齐。
- 立即联网查 OpenRouter(
- 高峰时段(仅 DeepSeek 原厂系):北京时间 9:00-12:00、14:00-18:00 价格翻倍;其余模型无峰谷。
- 计费公式:
未命中输入×输入价 + 命中输入×缓存价 + 输出×输出价,按当前时段取倍率;结果不足 ¥0.01 显示¥<0.01。 - toast 为两行:行1
模型名 | 时段标注 | 耗时[ 空格]余额¥X(半角 | 两侧各 1 空格),行2输入 X / 输出 Y|缓存NN.NN%|¥费用(详见方式 C)。
余额显示(v2.8 新增,v2.10 改为"默认隐藏 + 变化检测",仅自定义 API 的 DeepSeek 官方模型)
- 原理(NIX 客户端同款):DeepSeek 官方接口
GET https://api.deepseek.com/user/balance+Authorization: Bearer <API key>即可查询账户余额,无需网页登录——这就是 NIX 等 DeepSeek 客户端"只给 API key 就能显示余额"的原因。 - 启用条件:仅当
~/.workbuddy/models.json里存在 url 指向api.deepseek.com的模型(即用户自己的 DeepSeek API key)时启用;无 key → 不显示余额,不影响其他功能。 - 模式识别(v2.10,用户定调"默认不显示、抓到变化才显示"):积分模式与自定义 API 模式无法从本地数据区分——官方文档证实内置模型列表就有
Deepseek-V4-Flash(与自定义 id 同名);trace/hook payload/transcript 无模式标记;进程级探测(tasklist/wmic/netstat)被本机安全策略禁用。"密钥是否在用"信号抓不到,但 余额变化 = 账户在真实消耗 是其等价信号(有密钥才有消耗)。判定规则:每次查询与上次观测对比(toFixed(2)字符串比较避免浮点陷阱)——- 余额变了(降=消费 / 升=充值)→ 显示
余额¥X(自定义 API 模式,或其他处用同一 key,显示的是真实余额); - 余额不变 → 不显示(积分模式余额恒定 → 永不显示);
- 首次观测只记录 baseline 不显示(用户:"宁愿先几轮不显示")。
- 余额变了(降=消费 / 升=充值)→ 显示
- 显示位置:Stop toast 行1 紧跟耗时(1 空格分隔)
余额¥2.77(金额两位小数,带 ¥ 符号——v2.18 恢复:实测行1 上限 47u 后空间充裕,峰值场景 46u 仍有富余)。放行1 的原因:行2 已满(实测约 49u/上限 52u),追加余额会折叠变 3 行。行1 是标题大字,宽度上限TOAST_ROW1_MAX_W=47u(v2.17 实测:47u 不换行 / 48u 换行);超宽(长模型名)时余额自动让位,不折叠。v2.17 定稿:行1 分隔符用半角|(全角「|」dispWidth 算 2u 太浪费)且两侧各 1 空格——模型名 | 高峰 | 耗时 余额¥X,模型名与分隔符不再贴死。 - 缓存(实时性):余额写入
.balance.json(含 history 数组,保留最近 20 条观测),15 秒 TTL(v2.18 从 60s 压短:用户要求实时,接口实测 300ms 级,正常轮询间隔 >15s 即每轮拿实时数;15s 内连发 toast 才复用缓存秒回)——查询失败降级用旧缓存,无缓存则隐藏余额(不报错、不影响 toast)。 - 隐私:API key 仅用于本机向官方
api.deepseek.com发请求,缓存文件只存余额数值与观测历史、不存 key;脚本不打印、不上传 key。 - 每日刷新策略(用户要求"当天第一次打开软件/第一次回答才搜,当天搜过就不搜"):
- 脚本自动兜底:每次运行
token-tracker.js时检查pricing.json的date——过期才同步调用refresh-prices.js联网拉 OpenRouter 免费 API(openrouter.ai/api/v1/models,无需 key,每 12 小时更新)更新所有or_id匹配模型的 USD 参考价 + 补未收录模型的估算价,写回date=今天;当天已刷新则直接跳过、不联网;拉取失败保留本地价、date不变(次日重试),stderr 如实报错。 - 人工权威核验:每日首次对话时,按 SKILL.md 数据源清单联网核对各厂商官方定价页(DeepSeek api-docs / 智谱 open.bigmodel.cn / Kimi platform.moonshot.cn / MiniMax platform.minimaxi.com / 混元 cloud.tencent.com),把变化的官方人民币主价更新进
pricing.json(自动刷新只动 USD 参考字段,不覆盖本地权威主价)。 - 开源/实时价格源清单(供每日核验):OpenRouter API(首选自动源)、LiteLLM
model_prices_and_context_window.json(BerriAI/litellm,静态聚合)、Portkeyhttps://configs.portkey.ai/pricing/<provider>.json(免费 API 每日更新)、厂商官方定价页(权威)。
- 脚本自动兜底:每次运行
⏱️ 时序限制(务必理解,不要对用户造假)
- 一个 trace 文件 = 一轮完整 LLM 调用,整轮结束后才落盘。因此在「回答生成完毕」那一刻,本轮自己的 trace 还没写出来——手动/
--hook模式读到的永远是最新已完成的一轮(即上一条回答,精确但滞后一轮)。回答末尾贴出的行不可能显示本条(本条尚未落盘),必须如实标注「上一轮/最近完成轮」,不许冒充"本条"。 - 例外(方式 C):
Stop事件在本轮回答完全结束后触发,此时(等待后)本轮 trace 已写完,--stop模式能拿到本条精确数据,通过 Windows 系统通知展示——这是唯一能显示「本条」的通道。 - 首次运行(无快照)或新轮次:直接显示该轮统计,无"上一轮"前缀。
- 同一轮被第二次读到(例如 hook 刚记录过、或上一条回复末尾已贴过):输出会带「上一轮」前缀,避免同一行重复出现。
数据源与准确性
- 数据:最新
traces/<pid>/trace_*.json的trace.modelInfo/trace.duration。 - 轮次识别:脚本用
~/.workbuddy/skills/token-usage-tracker/.snapshot.json记录「上次已统计的 trace 文件路径 + 该轮统计」,只用于去重;不做总量 diff,因此换会话 / 清空上下文导致总量变小也不会出现负数或 0。 - 适用范围:自有 API 与内置积分模式都统计——只要走了模型调用就有 trace。
- 统计口径:
该轮累计是该 trace 内多次模型调用的合计;duration为平台记录的该轮总耗时(含工具调用等)。
注意事项
- 不要伪造数字:脚本读的是平台真实落盘数据,直接输出即可;读不到/解析失败时如实显示"暂无数据/尚未完成写入",绝不编造。
- 若输出显示「上一轮」,说明这一轮已统计过(数字与上次相同属正常)。
- 本技能不修改任何平台文件,仅读取 traces 与维护自身快照。
