- 博客
- 打造你的 Claude Code 仪表盘:StatusLine 从零到完整版
- 永远不再错过 Claude 的消息:macOS 通知系统完整方案
- 我的学术 MCP 矩阵:6 个工具组合的学术搜索策略
- 455 行代码背后的设计思考:终端 UI 设计方法论
- CLAUDE.md 进阶:从 10 行到 607 行的配置艺术
- 让 Claude 当领导:SubAgent 编排方法论
- 2-2-1 冗余写作法:让 AI 输出质量翻倍
- 22 个场景看 Claude Code 的学术研究表现
- 如何为 Claude Code 创建高质量 Skill:完整案例
- 复盘驱动的 AI 使用改进
- 用 Claude Code 完成一篇文献综述
- 从 0 到发表:Claude Code 统计分析全流程
- 用 AI 管理生活:我的 Logseq + 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 | 缓存读取 tokens | 15000 |
context_window.current_usage.input_tokens | 本次输入 tokens | 3000 |
context_window.current_usage.cache_creation_input_tokens | 缓存创建 tokens | 5000 |
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 行最小可用脚本
从最简单的开始。我们的目标是:在终端底部显示当前模型、累计费用、上下文百分比——三个最核心的信息。
#!/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"逐行看:
input=$(cat)—— 从 stdin 读取 Claude Code 传入的 JSON。这是 StatusLine 脚本的基本约定:数据通过 stdin 进来jq -r '.field // default'—— 用 jq 提取 JSON 字段。//是 jq 的空值替代运算符,字段不存在时用默认值,避免脚本出错printf—— 格式化输出到 stdout,Claude Code 会把这个输出显示在终端底部
保存脚本后,给它执行权限:
$ chmod +x ~/.claude/statusline.sh然后在 settings.json 中启用:
{
"statusLine": {
"type": "command",
"command": "~/.claude/statusline.sh",
"padding": 0
}
}也可以用更简短的内联写法(适合快速测试):
{
"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 45m 或 2h 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 在社区中已经发展出一个活跃的生态。如果你不想从零开始写,这里有几个代表性的项目:
| 项目 | 特色 | 适合场景 |
|---|---|---|
| ccstatusline | 20+ Widget、TUI 配置界面,被官方推荐 | 想要开箱即用、可视化配置 |
| CCometixLine | Rust 编写,亚毫秒启动 | 追求极致性能 |
| Claude HUD | 从 StatusLine 到 HUD 的概念跃迁 | 想要更丰富的可观测性 |
自研和社区方案的取舍,本质上是学习深度 vs 开箱即用。如果你的目标是理解 Claude Code 的内部数据流、练习 shell 脚本、定制独有功能(比如活跃时间算法),自研是更好的选择。如果你只是想快速拥有一个漂亮的状态栏,社区方案可以在五分钟内搞定。
两者也不矛盾——我建议先自己写一个最小版本理解原理,再根据需要决定是继续扩展还是切换到社区工具。
进阶阅读
这篇文章展示了 StatusLine 从最小脚本到完整版的渐进路径。如果你想深入了解完整版 455 行背后的设计决策、活跃时间算法的实现细节、以及 14 轮设计迭代的过程,可以阅读完整教程:
另外,StatusLine 解决的是"看到信息"的问题,而 Hooks 系统解决的是"接收通知"的问题。如果你也想在 Claude Code 完成任务后收到 macOS 通知,可以看这篇:
永远不再错过 Claude 的消息:macOS 通知系统完整方案 →
StatusLine + Hooks,构成完整的 Claude Code 终端体验。