打造你的 Claude Code 仪表盘:StatusLine 从零到完整版

从 10 行基础脚本到 455 行完整版,手把手带你构建 Claude Code 终端状态栏。

每次都想知道"花了多少"

我在使用 Claude Code 时,经常会冒出一个念头:这次对话花了多少钱?上下文窗口还剩多少?缓存命中率高不高?

默认终端什么都不告诉你。你只能凭感觉判断——对话变长了,响应好像慢了,大概快到窗口上限了吧?至于费用,只有关掉 Claude Code 之后才能去 Dashboard 查。

后来我发现 Claude Code 有一个内置功能叫 StatusLine——它可以在终端底部显示一行(或多行)自定义状态信息。只需要写一个 shell 脚本,就能获得实时的费用、上下文、缓存等关键指标。

这篇文章分享我从 10 行最小脚本到 455 行完整仪表盘的渐进过程。你可以按自己的需求停在任何一个阶段——每个版本都是完整可用的。


StatusLine 是什么

StatusLine 的工作原理用一句话概括:Claude Code 每次回复完成后,将一段 JSON 数据通过 stdin 传给你指定的脚本,脚本的 stdout 输出会显示在终端底部。

它在以下时机触发:

  • assistant 消息完成后
  • 权限模式变更时
  • Vim 模式切换时

几个关键特性:

  • 300ms 防抖:快速连续更新会合并,不会闪烁
  • 本地运行:脚本在本地执行,不消耗 API tokens
  • 支持多行 + ANSI 颜色:你可以输出多行内容,还能用 ANSI 转义码给文字上色

Claude Code 传入的 JSON 包含丰富的会话信息。我们最常用的核心字段是:

字段含义示例值
cost.total_cost_usd会话累计费用(美元)0.4567
context_window.used_percentage上下文窗口已用百分比42
model.display_name当前模型名称"Opus"
context_window.current_usage.cache_read_input_tokens缓存读取 tokens15000
context_window.current_usage.input_tokens本次输入 tokens3000
context_window.current_usage.cache_creation_input_tokens缓存创建 tokens5000
cost.total_api_duration_ms累计 API 响应时长(毫秒)120000

完整的 JSON 结构大致长这样:

{
  "model": {
    "id": "claude-opus-4-6",
    "display_name": "Opus"
  },
  "cost": {
    "total_cost_usd": 0.4567,
    "total_duration_ms": 180000,
    "total_api_duration_ms": 120000,
    "total_lines_added": 156,
    "total_lines_removed": 23
  },
  "context_window": {
    "used_percentage": 42,
    "remaining_percentage": 58,
    "context_window_size": 200000,
    "total_input_tokens": 84000,
    "total_output_tokens": 12000,
    "current_usage": {
      "input_tokens": 3000,
      "cache_read_input_tokens": 15000,
      "cache_creation_input_tokens": 5000,
      "output_tokens": 800
    }
  },
  "session_id": "abc123...",
  "transcript_path": "/path/to/transcript.jsonl"
  // ...更多字段
}

了解了这些,就可以开始写第一个版本了。


版本 1:10 行最小可用脚本

从最简单的开始。我们的目标是:在终端底部显示当前模型、累计费用、上下文百分比——三个最核心的信息。

~/.claude/statusline.sh
#!/bin/bash
input=$(cat)
COST=$(echo "$input" | jq -r '.cost.total_cost_usd // 0')
MODEL=$(echo "$input" | jq -r '.model.display_name // "unknown"')
PCT=$(echo "$input" | jq -r '.context_window.used_percentage // 0')
printf "[%s] $%.4f | Context: %s%%" "$MODEL" "$COST" "$PCT"

逐行看:

  1. input=$(cat) —— 从 stdin 读取 Claude Code 传入的 JSON。这是 StatusLine 脚本的基本约定:数据通过 stdin 进来
  2. jq -r '.field // default' —— 用 jq 提取 JSON 字段。// 是 jq 的空值替代运算符,字段不存在时用默认值,避免脚本出错
  3. printf —— 格式化输出到 stdout,Claude Code 会把这个输出显示在终端底部

保存脚本后,给它执行权限:

$ chmod +x ~/.claude/statusline.sh

然后在 settings.json 中启用:

~/.claude/settings.json
{
  "statusLine": {
    "type": "command",
    "command": "~/.claude/statusline.sh",
    "padding": 0
  }
}

也可以用更简短的内联写法(适合快速测试):

~/.claude/settings.json
{
  "statusLine": "jq -r '\"[\\(.model.display_name)] \\(.context_window.used_percentage // 0)% context\"'"
}

重启 Claude Code,发送一条消息。你应该能在终端底部看到类似这样的输出:

[Opus] $0.0042 | Context: 8%

六行脚本,三个核心指标,整个过程不超过两分钟。这已经比"什么都看不到"好太多了。


版本 2:增强版——缓存、进度条和时长

用了一阵版本 1 之后,我发现自己总想看更多信息:缓存命中率高不高(直接影响费用)?上下文到底用了多少(数字不如进度条直观)?这次对话跑了多久?

增强版在版本 1 的基础上加入三个功能。

缓存命中率

缓存命中率的公式是:

命中率 = cache_read / (input + cache_read + cache_creation) × 100

在 Bash 中实现:

CACHE_READ=$(echo "$input" | jq -r '.context_window.current_usage.cache_read_input_tokens // 0')
CACHE_CREATE=$(echo "$input" | jq -r '.context_window.current_usage.cache_creation_input_tokens // 0')
CURRENT_INPUT=$(echo "$input" | jq -r '.context_window.current_usage.input_tokens // 0')
 
CACHE_TOTAL=$((CURRENT_INPUT + CACHE_READ + CACHE_CREATE))
 
CACHE_FMT=""
if (( CACHE_TOTAL > 0 )); then
  CACHE_HIT=$((CACHE_READ * 100 / CACHE_TOTAL))
  CACHE_FMT="Cached ${CACHE_HIT}% · "
fi

注意最后的条件判断:首次 API 调用前 current_usage 可能为 null,分母为 0 时我们优雅地隐藏缓存率,而不是显示一个无意义的 Cached 0%

上下文进度条

数字看百分比不够直观,一个 20 字符宽的进度条一眼就能看出"快满了"还是"刚开始":

PCT_INT=$(printf "%.0f" "$PCT")
FILLED=$((PCT_INT * 20 / 100))
EMPTY=$((20 - FILLED))
BAR=""
[ "$FILLED" -gt 0 ] && BAR=$(printf "%${FILLED}s" | tr ' ' '█')
[ "$EMPTY" -gt 0 ] && BAR="${BAR}$(printf "%${EMPTY}s" | tr ' ' '░')"
echo "Context: $BAR $PCT_INT%"

这里用到一个 Bash 技巧:printf "%Ns" 输出 N 个空格,再通过 tr ' ' '█' 替换成重复字符。这是 Bash 中生成重复字符串的惯用写法。

会话时长

cost.total_api_duration_ms 记录了累计等待 API 响应的时长。我们把毫秒格式化为人类可读的时间:

DURATION_MS=$(echo "$input" | jq -r '.cost.total_api_duration_ms // 0')
DURATION_SEC=$((DURATION_MS / 1000))
HOURS=$((DURATION_SEC / 3600))
MINS=$(((DURATION_SEC % 3600) / 60))
 
if (( HOURS > 0 && MINS > 0 )); then
  DURATION_FMT="⏱ ${HOURS}h ${MINS}m"
elif (( HOURS > 0 )); then
  DURATION_FMT="⏱ ${HOURS}h"
elif (( MINS > 0 )); then
  DURATION_FMT="⏱ ${MINS}m"
else
  DURATION_FMT="⏱ <1m"
fi

四级条件格式化是为了避免 0h 45m2h 0m 这种不自然的显示。

增强版效果

把上面三个功能组合起来,终端底部现在显示这样的信息:

49.7M tokens · $29.46 | Context: ████████████░░░░░░░░ 60% | Cached 85% · ⏱ 2h 15m

一行之内:token 用量、累计费用、上下文进度条、缓存命中率、会话时长——五个关键指标尽收眼底。


版本 3:完整版预览——455 行做了什么

增强版用了一段时间后,我遇到了一个让人困惑的问题:时间显示不准确

某次我用了 SubAgent 并行执行任务,实际等了大约 39 分钟,但 StatusLine 显示 1 小时 29 分钟。原因在于 total_api_duration_ms 是所有 API 调用耗时的简单累加——5 个并行 SubAgent 各自执行 20 分钟,累加就变成了 100 分钟。

这个问题直接推动了完整版的开发。455 行脚本的核心创新就是活跃时间算法:从 transcript 文件中解析 wall-clock 时间戳,计算用户实际等待的时间,而非 API 累加时间。

完整版输出两行信息:

49.7M tokens · $29.46 | Context: ████████████░░░░░░░░ 60% | Cached 85% · ⏱ 2h 15m [2.7×]
───
main · 2 active | +156 -23 · 12 files · 89 lines/$ | Quality: ▁▁▂▃▅▆▇█ ↑

相比增强版,主要新增了这些能力:

活跃时间算法

核心思路是分析 Claude Code 的 transcript 文件(JSONL 格式),识别每个"用户发起 → Claude 完成"的 turn,取 wall-clock 时间差之和。这样即使 5 个 SubAgent 并行,wall-clock 时间也只计一次。

算法还会排除用户空闲时间(turn 之间的等待)和交互工具的等待时间(比如弹出权限确认框后用户去喝了杯咖啡)。

并行加速比

当活跃时间和 API 累积时间存在显著差异时(比值达到 1.2 倍以上),StatusLine 会显示一个加速比标识 [2.7×]——表示并行 SubAgent 为你节省了 2.7 倍的时间。这个数字在单 agent 场景下不会出现,不占空间。

跨 /clear 时间累积

Claude Code 的 /clear 命令会创建新的 session,但进程不重启。完整版通过 PID 追踪机制,即使 /clear 之后也能正确累积活跃时间,不会突然重置为 <1m

Git 开发信息(第二行)

第二行是 additive 设计——只有在 Git 仓库中才出现:

  • 分支名 + 协作者数量main · 2 active
  • Diff 统计 + 代码效率+156 -23 · 12 files · 89 lines/$(每花 1 美元产出多少行代码变更)
  • 质量火花图:8 个 Unicode block 字符追踪最近 8 次调用的代码变更速度趋势

火花图有一个细节值得一提:它使用了离群值抵抗归一化——如果某次变更量是中位数的 10 倍,也只映射到最高的 ,不会把其他数据点全部压扁成

完整版的实现细节(活跃时间算法的 Python 辅助脚本、PID 追踪机制、火花图的归一化算法等)涉及不少代码量,这里不展开。如果你感兴趣,可以在文末的进阶阅读中找到完整教程。


社区方案速览

StatusLine 在社区中已经发展出一个活跃的生态。如果你不想从零开始写,这里有几个代表性的项目:

项目特色适合场景
ccstatusline20+ Widget、TUI 配置界面,被官方推荐想要开箱即用、可视化配置
CCometixLineRust 编写,亚毫秒启动追求极致性能
Claude HUD从 StatusLine 到 HUD 的概念跃迁想要更丰富的可观测性

自研和社区方案的取舍,本质上是学习深度 vs 开箱即用。如果你的目标是理解 Claude Code 的内部数据流、练习 shell 脚本、定制独有功能(比如活跃时间算法),自研是更好的选择。如果你只是想快速拥有一个漂亮的状态栏,社区方案可以在五分钟内搞定。

两者也不矛盾——我建议先自己写一个最小版本理解原理,再根据需要决定是继续扩展还是切换到社区工具。


进阶阅读

这篇文章展示了 StatusLine 从最小脚本到完整版的渐进路径。如果你想深入了解完整版 455 行背后的设计决策、活跃时间算法的实现细节、以及 14 轮设计迭代的过程,可以阅读完整教程:

StatusLine 深度配置教程 →

另外,StatusLine 解决的是"看到信息"的问题,而 Hooks 系统解决的是"接收通知"的问题。如果你也想在 Claude Code 完成任务后收到 macOS 通知,可以看这篇:

永远不再错过 Claude 的消息:macOS 通知系统完整方案 →

StatusLine + Hooks,构成完整的 Claude Code 终端体验。