首页
/ caveman `/caveman-stats` 技能解析:从会话日志读取真实 Token 用量并计算净节省

caveman `/caveman-stats` 技能解析:从会话日志读取真实 Token 用量并计算净节省

2026-09-06 11:30:31作者:劳婵绚Shirley

caveman-stats 是 caveman 项目用于回答"这个会话到底用了多少 token、省了多少"的统计技能。它的核心特点是不依赖模型估算:数字直接读自 Claude Code 的会话日志(JSONL transcript),由 hooks/caveman-stats.js 脚本计算后,经 mode-tracker 钩子注入给用户。读完后你能掌握该技能的完整触发链路、会话日志解析方式、节省/开销/净节省三项指标的精确计算口径、每模式归属(per-mode attribution)机制,以及如何直接用命令行运行脚本做离线验证。

技能定位与触发方式

技能定义见 SKILL.md(仓库中还有同内容的镜像 SKILL.md),其 frontmatter 声明:

  • name: caveman-stats
  • 用途:Show real token usage and estimated savings for the current session. Reads directly from the Claude Code session log — no AI estimation.
  • 触发词:/caveman-stats
  • 关键设计:模型本身不计算任何数字,输出由 mode-tracker 钩子注入。SKILL.md 原文表述为"hook 以格式化统计作为 reason 返回 decision: "block"",而当前 mode-tracker 实现 的做法等价且更温和:把脚本输出放进 hookSpecificOutput.additionalContext,并附一句指令"Print this stats block verbatim inside a fenced code block. Say nothing else.",让模型原样转发统计块。用户随即看到数字,无需等待模型生成。

对应的斜杠命令包装在 caveman-stats.toml 中,仅两行:prompt = "/caveman-stats {{args}}",即把附加参数透传给技能。

执行链路:从斜杠命令到统计输出

当用户在 Claude Code 中输入 /caveman-stats 时,链路如下(依据 caveman-mode-tracker.js):

  1. UserPromptSubmit 钩子拦截。钩子注册为 UserPromptSubmit 事件,收到用户输入的 JSON payload;
  2. 匹配 stats 命令。正则 /^\/caveman(?::caveman)?-stats(?:\s+(.*))?$/ 同时接受 /caveman-stats 与命名空间形式 /caveman:caveman-stats
  3. 组装子进程参数并执行 src/hooks/caveman-stats.js
    • --session-file <transcript_path>:透传 hook payload 中的 transcript 路径,保证读取当前活动会话,而不是最近修改的某个 JSONL;
    • --session-id <id>:让统计脚本丢弃属于其他窗口的模式切换日志行;
    • 透传尾部参数:--share--all--since <Nh|Nd>
  4. 2.5 秒看门狗。子进程 execFileSynctimeout: 2500(hook 注册允许 30 秒,Windows 进程启动慢,脚本自身已有 Node 启动开销,故留了余量);超时或失败时降级输出 caveman-stats: could not run stats script. 并提示手动运行 node hooks/caveman-stats.js
  5. 注入 additionalContext,模型按要求原样输出统计块。

一个值得注意的实现细节:钩子按 chunk 解析 stdin JSON(而非等 EOF),因为 Windows 管道上 EOF 关闭可能任意延迟,而该钩子只有 5 秒预算;解析完成后对 stdin 执行 pause() + unref(),避免事件循环空转直到宿主杀掉进程(见 caveman-mode-tracker.js 尾部的 stdin 处理)。

数据来源:会话 JSONL 的解析口径

caveman-stats.js 的 parseSession 逐行读取会话 transcript(每行一个 JSON 对象),只统计 type === 'assistant' 且带 message.usage 的行:

字段 来源 含义
Output tokens usage.output_tokens 累加 模型实际产出的输出 token
Cache-read tokens usage.cache_read_input_tokens 累加 缓存命中的输入 token(只读展示,不参与节省计算)
Turns assistant 带 usage 的行数 轮次,用于规则开销计算
Model 首个 message.model 用于匹配输出单价
messages 每行 {ts, outputTokens} 供按模式归属使用

找不到会话时(未传 --session-file~/.claude/projects 下没有 .jsonl),脚本向 stderr 输出 caveman-stats: no Claude Code session found. 并以非零码退出。配置目录可由环境变量 CLAUDE_CONFIG_DIR 覆盖,默认为 ~/.claude

节省估算:压缩率与输出单价

压缩率COMPRESSION 目前只有一个条目 { 'full': 0.65 },来自 benchmarks/ 中 10 个任务、sonnet-4-20250514 的平均 avg_savings: 65lite / ultra / wenyan 等模式没有基准数据,输出会直接写明 No savings estimate for '<mode>' mode — only 'full' has benchmark data.,不做猜测。

估算公式(deriveSavings):

estSavedTokens = round( tokens / (1 - ratio) ) - tokens

即:已知压缩率 65% 时,用当前输出量反推"不用 caveman 时大约会有多少输出",差额即节省量。

美元换算:脚本内置一张按模型 id 前缀匹配的输出单价表(MODEL_OUTPUT_PRICE_PER_M,取第一个命中的前缀,最具体的前缀必须排在最前),例如:

前缀 输出单价(USD/M tokens)
claude-fable-5 / claude-mythos-5 50
claude-opus-5 25
claude-sonnet-5 10
claude-opus-4-0 / claude-opus-4-1 / claude-opus-4-2025(4.0/4.1 旧档) 75
claude-opus-4(4.5 及以上) 25
claude-sonnet-4 15
claude-haiku-4 5
claude-3-5-sonnet / claude-3-5-haiku / claude-3-opus 15 / 4 / 75

模型无法匹配任何前缀时,只报 token 数、不报美元,避免给出无依据的金额。

规则开销与净节省:不藏着"净负"区间

这是 SKILL.md 强调的第二部分。只要存在已知轮次的节省估算,输出就会包含两行:

  • Est. rule overhead:规则开销 = 每轮注入 caveman 规则的输入 token 成本 × 轮次。默认 1,250 tokens/turn(SKILL.md ~5 KB 规则注入 + 每轮强化提醒的量级),可用环境变量 CAVEMAN_RULE_OVERHEAD_TOKENS 覆盖——ruleOverheadPerTurn 要求它是正整数,否则回落到默认值;
  • Est. net:净节省 = 节省量 − 开销(deriveNet)。注意节省量是 输出 token、开销是 输入 token,分属不同计费桶,但把两者相加是"整个预算口径"下唯一诚实的差值(脚本注释明确引用 HONEST-NUMBERS.md)。

当净值为负时,netLines 会直白输出:

Est. net: -2350 (caveman cost more than it saved for this workload — consider turning it off)

即直接建议对该工作负载关闭 caveman,而不是用总节省数字掩盖净负区间。这与 docs/HONEST-NUMBERS.md 的立场一致:该页面明确列出 caveman 净负的场景(简短编码问答、按请求计费的 Copilot 场景等),并建议用 A/B 对照服务商账单做最终裁决。

另外,Est. output reduction: ~X% 这一行(终身视图)在 outputReductionPct 中有严格限定:它只是 saved / (saved + used)输出 token 缩减比例,不是"占会话用量或配额的比例"——因为代理式会话中输入 + 缓存 token 才是大头且不受 caveman 影响,源码注释特意要求永远不要把它标成 usage/budget 占比。

每模式归属:会话中途切换模式时不虚报

attributeByMode 解决"会话中途换过模式"的归属问题:不能把整个会话的 token 全记在统计时刻的 flag 值上(verbose 轮次会被算成压缩轮次,或反过来)。归属依据按精确度分三档:

  1. log:mode-tracker 的 recordModeChange 会在每次真实切换时向 .caveman-mode-log.jsonl 追加 {ts, mode, prev} 行;stats 用消息时间戳与切换日志做 join,第一条行之前的区间归属该行的 prevreadModeLog 会按 session_id 过滤掉其他窗口的行,防止多窗口交错污染时间线;
  2. flag-mtime:无切换日志、但 flag 文件 mtime 晚于首条消息——说明模式是会话中途写的,只有写入之后的 token 可归属当前模式,之前的 token 记为 unattributed 并排除出估算(宁可少算,不猜);
  3. whole-session:既无日志也无中途变更证据——按当前模式覆盖全会话(模式从未改变时即为正确答案)。

混合模式时,输出会给出逐模式明细(Mode changed mid-session — output attributed per mode:),无基准数据模式标注 no benchmark estimate,未知区间标注 unattributed: N tokens (mode unknown — excluded from estimate)

终身统计、记忆压缩与状态栏后缀

  • 终身视图:每次运行(turns > 0)都会向 ~/.claude/.caveman-history.jsonl 追加一条会话快照(含 session_idoutput_tokensturnsest_saved_tokensest_saved_usd)。--all--since 7d / --since 24hparseDuration 仅接受 Nh/Nd,非法格式退出码 2)触发终身聚合;aggregateHistory 对同一 session 只取最新一条快照,且净节省只累加真实记录了 turns 的行(老格式行没有 turns 字段,混入会扭曲开销计算)。
  • 记忆压缩findCompressedPairs 扫描 *.original.md 备份对(caveman-compress 的产物),以字节差 ÷ 4(英文约 4 字符/token)估算每次会话启动的被动输入节省,输出 Memory compressed: N files, ~X tokens saved per session start (approx)
  • 状态栏:每次运行都会把聚合节省写成 ~/.claude/.caveman-statusline-suffix(如 ⛏ 1.2M),shell 状态栏脚本可直接 cat 该文件,无需解析 JSONL。
  • --share:输出一行可分享的总结,例如 🪨 Saved 650 output tokens (~$0.010) across 3 turns this session — caveman.sh

直接运行与测试验证

不依赖 Claude Code 也可以直接运行脚本:

# 读取最近修改的会话
node src/hooks/caveman-stats.js

# 指定 transcript 与窗口过滤(配置目录可用 CLAUDE_CONFIG_DIR 覆盖)
CLAUDE_CONFIG_DIR=~/.claude node src/hooks/caveman-stats.js --session-file <path>.jsonl --since 7d

完整性守护方面,脚本对同目录的 caveman-config.js 做了两层防护:文件缺失时输出一行可操作的报错(提示 /plugin update caveman 或重跑 install.sh)并退出码 1;文件存在但导出形状不符(插件缓存漂移)同样拒绝运行——因为 stats 没有"降级半报表"的可用输出。opencode 安装布局把同目录模块改名为 .cjs,脚本按"错误信息点名本模块"这一条件做了一次精确的重试。

tests/test_caveman_stats.js(约 900 行)用临时目录伪造 .claude/projects 下的会话 JSONL 验证了这些口径,例如:两条 assistant 消息分别 100/50 输出 token、200/50 缓存 token 时,断言输出含 Turns: 2Output tokens: 150Cache-read tokens: 250;flag 为 full、输出 350 token 时断言 Est. without caveman: 1,000Est. tokens saved: 650 (~65% of output)(即 350/0.35 = 1000);flag 为 ultra 时断言出现 No savings estimate for 'ultra' mode。另有针对 mode-tracker 注入路径的用例,确认 /caveman-stats 经钩子投递。

适用前提小结

  • 数字的"真实"部分(输出/缓存 token、轮次)严格来自会话日志;节省估算是基准压缩率 × 前缀匹配的公开输出单价,属于估计值,输出中始终带 Est.approx 标注;
  • 规则开销默认 1,250/turn,若你的注入规则集不同,建议实测后用 CAVEMAN_RULE_OVERHEAD_TOKENS 覆盖;
  • 该技能面向 Claude Code 会话日志格式;HONEST-NUMBERS.md 建议最终裁决以同一任务开/关 caveman 的服务商账单 A/B 为准,/caveman-stats 提供的是会话内可复核的明细与口径。
登录后查看全文
热门项目推荐
相关项目推荐