一篇面向 Claude Code 重度用户的踩坑指南,带你用纯 Python 打造一个功能完备的终端状态栏。


一、背景与痛点

作为 Claude Code 的日常用户,我在 Windows 终端里一待就是几个小时。随着使用深入,有几个信息我迫切需要在状态栏里一眼看到:

  1. 当前模型与上下文 Token 使用情况 — 我经常在多个模型间切换,而且 Claude Code 的上下文窗口有限,一旦接近上限就可能截断对话,我需要一个直观的进度条来提醒自己及时 /compact
  2. 当前正在调用的代理/技能名称 — 我用子代理(sub-agent)做并行代码审查、安全审计,想知道后台正在跑什么
  3. 已加载的技能总数 — 我装了 32 个自定义技能,想知道它们是否都被正确加载
  4. Git 分支、会话费用与耗时 — 在多项目间切换时,瞄一眼就知道自己在哪个分支上,花了多少钱

踩过的坑

一开始我听说有个叫 claude-hud 的第三方插件可以做状态栏增强,但在官方插件市场找了半天也没找到。后来又尝试 claude mcp 相关的方案,同样不生效。

翻遍了 Claude Code 官方文档,终于找到了 statusLine 配置项——它支持自定义命令来生成状态栏内容。这意味着我完全可以自己写一个脚本,Claude Code 每 5 秒调用一次,输出一行格式化的文本就行了。

于是,这套零外部依赖、纯 Python 实现的状态栏脚本诞生了。


二、最终效果展示

这是我的 Claude Code 终端状态栏的实际效果:

1
DeepSeek V4 Pro·high │ ████████·· 76% 148k/200k │ ⚡code-reviewer │ 📦32 │ git:main │ $0.42 · 12m30s

从左到右,6 个模块依次是:

序号 模块 示例 说明
1 模型名 DeepSeek V4 Pro·high·🧠 显示模型的 display_name,附带推理模式(effort level)和思考状态
2 上下文进度条 ████████·· 76% 148k/200k 10 格 Unicode 可视化进度条 + 百分比 + used/max tokens
3 当前代理/技能 ⚡code-reviewer 仅当子代理运行时显示,告诉你谁在后台干活
4 技能总数 📦32 自动扫描 ~/.claude/skills/ 目录统计
5 Git 分支 git:main 当前项目的 Git 分支名
6 费用/耗时 $0.42 · 12m30s 当前会话累计费用和持续时长

进度条颜色预警

这是最实用的功能——不用再手动 /context 查看了:

用量 颜色 含义
0–49% 🟢 绿色 ✅ 充足,放心继续
50–79% 🟡 黄色 ⚠️ 注意,建议整理上下文
80–100% 🔴 红色 🚨 接近上限,强烈建议 /compact

三、完整配置步骤

前提条件

  • Python 3.x(我用的是 3.11.9),无需安装任何第三方包
  • Claude Code 已安装并可用
  • 终端支持 UTF-8 和 ANSI 颜色(Windows Terminal、Git Bash 均可)

第一步:创建状态栏脚本

~/.claude/statusline-tools/ 目录下创建 statusline.py

statusline.pylink
1
2
3
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
142
143
144
145
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
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
#!/usr/bin/env python3
"""
Claude Code Status Line — comprehensive, beautiful, Windows-compatible.

Displays:
1. Current model name
2. Context token usage (used/max/remaining, percentage, visual progress bar)
3. Current agent/skill being invoked
4. Total loaded skills count
5. Git branch (bonus)
6. Session cost & duration (bonus)

Input: JSON from Claude Code via stdin
Output: Single-line formatted status string
"""

import json
import os
import subprocess
import sys
from pathlib import Path

# ---------- Windows encoding setup ----------
if sys.platform == "win32":
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
sys.stdin.reconfigure(encoding="utf-8", errors="replace")

# ---------- helpers ----------

def get_key(d, *keys, default=None):
"""Safe nested dict access."""
for k in keys:
if isinstance(d, dict):
d = d.get(k)
else:
return default
return d if d is not None else default


def color(text: str, code: str) -> str:
"""Apply ANSI color if terminal."""
if hasattr(sys.stdout, "isatty") and sys.stdout.isatty():
reset = "\033[0m"
return f"{code}{text}{reset}"
return text


# ANSI codes
BOLD = "\033[1m"
DIM = "\033[2m"

# Colors (foreground)
CYAN = "\033[36m"
GREEN = "\033[32m"
YELLOW = "\033[33m"
RED = "\033[31m"
MAGENTA = "\033[35m"
BLUE = "\033[34m"

# ---------- progress bar ----------

def progress_bar(percentage: float, width: int = 10) -> str:
"""Build a color-coded Unicode progress bar.
Width 10: each cell = 10%. Uses eighths for partial fill.
"""
pct = max(0.0, min(100.0, percentage))
full_blocks = int(pct / (100.0 / width))
remainder = pct / (100.0 / width) - full_blocks

# Partial block characters (eighths)
partial_chars = ["", "▏", "▎", "▍", "▌", "▋", "▊", "▉"]
partial_idx = int(remainder * 8)
partial = partial_chars[partial_idx] if partial_idx > 0 and full_blocks < width else ""

empty_blocks = width - full_blocks - (1 if partial else 0)

# Color by usage level
if pct < 50:
bar_color = GREEN
elif pct < 80:
bar_color = YELLOW
else:
bar_color = RED

filled = color("█" * full_blocks, bar_color)
part = color(partial, bar_color) if partial else ""
empty = color("·" * max(0, empty_blocks), DIM)

return f"{filled}{part}{empty}"


# ---------- git ----------

def get_git_branch(cwd: str) -> str:
"""Get current git branch name."""
try:
result = subprocess.run(
["git", "--no-optional-locks", "-C", cwd, "symbolic-ref", "--short", "HEAD"],
capture_output=True, text=True, timeout=2,
creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == "win32" else 0,
)
if result.returncode == 0:
return result.stdout.strip()
except Exception:
pass
return ""


# ---------- skills count ----------

def count_skills() -> int:
"""Count skill directories in the user's skills folder."""
skills_dir = Path.home() / ".claude" / "skills"
if not skills_dir.is_dir():
return 0
count = 0
try:
for entry in skills_dir.iterdir():
if entry.is_dir() and (entry / "SKILL.md").exists():
count += 1
except OSError:
pass
return count


# ---------- format helpers ----------

def fmt_tokens(n: int) -> str:
"""Format token count in human-readable form."""
if n >= 1_000_000:
return f"{n / 1_000_000:.1f}M"
if n >= 1_000:
return f"{n / 1_000:.1f}k"
return str(n)


def fmt_duration(ms: int) -> str:
"""Format milliseconds to human-readable duration."""
if ms < 1000:
return f"{ms}ms"
if ms < 60_000:
return f"{ms / 1000:.0f}s"
mins = ms // 60_000
secs = (ms % 60_000) // 1000
return f"{mins}m{secs}s"


def fmt_cost(usd: float) -> str:
"""Format USD cost."""
if usd < 0.01:
return f"${usd:.4f}"
if usd < 1:
return f"${usd:.2f}"
return f"${usd:.2f}"


# ---------- main ----------

def main():
try:
data = json.load(sys.stdin)
except (json.JSONDecodeError, Exception):
print(" Claude Code")
return

parts = []

# 1. Model
model_name = get_key(data, "model", "display_name", default="")
model_id = get_key(data, "model", "id", default="")
effort = get_key(data, "effort", "level", default="")
thinking = get_key(data, "thinking", "enabled", default=False)

if model_name:
model_str = color(model_name, CYAN)
if effort:
model_str += color(f"·{effort}", DIM)
if thinking:
model_str += color("·🧠", MAGENTA)
parts.append(model_str)
elif model_id:
parts.append(color(model_id, CYAN))

# 2. Context window usage
cw = data.get("context_window", {}) or {}
used_pct = cw.get("used_percentage")
window_size = cw.get("context_window_size", 0)

current = cw.get("current_usage") or {}
current_input = current.get("input_tokens", 0) if current else 0

if used_pct is not None:
pct_val = float(used_pct)
bar = progress_bar(pct_val, width=10)

if current_input > 0:
token_info = f"{fmt_tokens(current_input)}/{fmt_tokens(window_size)}"
elif cw.get("total_input_tokens", 0) > 0:
token_info = f"{fmt_tokens(cw['total_input_tokens'])}/{fmt_tokens(window_size)}"
else:
token_info = f"0/{fmt_tokens(window_size)}"

context_part = f"{bar} {color(f'{pct_val:.0f}%', BOLD)} {color(token_info, DIM)}"
parts.append(context_part)
elif window_size > 0:
parts.append(color(f"⊡ 0/{fmt_tokens(window_size)}", DIM))

# 3. Current agent / skill
agent_name = get_key(data, "agent", "name", default="")

if agent_name:
parts.append(color(f"⚡{agent_name}", MAGENTA))

# 4. Skills count
skills_n = count_skills()
if skills_n > 0:
parts.append(color(f"📦{skills_n}", BLUE))

# 5. Git branch (bonus)
cwd = data.get("cwd") or get_key(data, "workspace", "current_dir", default="") or os.getcwd()
branch = get_git_branch(cwd)
if branch:
parts.append(color(f"git:{branch}", YELLOW))

# 6. Cost & duration (bonus, compact)
cost = data.get("cost", {}) or {}
total_cost = cost.get("total_cost_usd", 0)
total_dur = cost.get("total_duration_ms", 0)

cost_parts = []
if total_cost > 0:
cost_parts.append(fmt_cost(total_cost))
if total_dur > 1000:
cost_parts.append(fmt_duration(total_dur))

if cost_parts:
parts.append(color(" · ".join(cost_parts), DIM))

# Assemble
separator = color(" │ ", DIM)
line = separator.join(parts)

if not line.strip():
line = " Claude Code"

print(f" {line}", flush=True)


if __name__ == "__main__":
main()

第二步:配置 Claude Code

编辑 ~/.claude/settings.json,添加 statusLine 配置:

1
2
3
4
5
6
7
{
"statusLine": {
"type": "command",
"command": "python /c/Users/你的用户名/.claude/statusline-tools/statusline.py",
"refreshInterval": 5
}
}

注意:如果你用的是 Git Bash/MSYS2,路径请用 Unix 风格(/c/Users/...),不要用 C:\Users\...

完整配置文件示例(含我的其他设置):

1
2
3
4
5
6
7
8
9
10
11
12
{
"enabledPlugins": {
"claude-code-setup@claude-plugins-official": true,
"security-guidance@claude-plugins-official": true,
"commit-commands@claude-plugins-official": true
},
"statusLine": {
"type": "command",
"command": "python /c/Users/16138/.claude/statusline-tools/statusline.py",
"refreshInterval": 5
}
}

第三步:重启 Claude Code

保存配置后,重启 Claude Code(关闭终端窗口重新打开即可),状态栏会在启动后自动加载,每 5 秒刷新一次


四、Windows 专属适配细节

Windows 环境下做终端工具有几个坑,这些都在脚本里处理好了:

1. UTF-8 编码问题

Windows 终端的默认编码不是 UTF-8,输出 Unicode 字符(如 🧠)会乱码。解决方案:

1
2
3
if sys.platform == "win32":
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
sys.stdin.reconfigure(encoding="utf-8", errors="replace")

2. Git 命令弹窗问题

在 Windows 上调用 subprocess.run 执行 Git 命令时,默认会弹出一个命令行窗口——非常影响体验。使用 CREATE_NO_WINDOW 标志即可解决:

1
creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == "win32" else 0

3. 路径兼容

脚本使用 Path.home() 来获取用户目录,自动适配不同平台。Git Bash/MSYS2 用户注意在 settings.json 中用 /c/Users/... 而非 C:\Users\...

4. 零外部依赖

整个脚本只用到了 Python 标准库(jsonossubprocesssyspathlib),无需 pip install 任何东西。只要你有 Python 3.x,开箱即用。


五、使用说明与常见问题

日常使用

安装完成后,你什么都不用做——状态栏会自动显示在 Claude Code 界面的底部。输入框上方的状态栏区域会实时刷新。

如果需要查看详细的会话状态摘要,在 Claude Code 中输入:

1
/status

进度条颜色含义

颜色 百分比 建议操作
绿色 0–49% 放心使用
黄色 50–79% 注意上下文增长,可以考虑整理
红色 80–100% 强烈建议 /compact 压缩上下文

常见问题

Q: 状态栏不显示?

检查以下几点:

  1. Python 是否在 PATH 中:python --version
  2. 配置文件路径是否正确:确认 settings.jsonstatusLine.command 的脚本路径存在
  3. 手动测试脚本是否能运行:
    1
    echo '{"model":{"display_name":"Test"}}' | python ~/.claude/statusline-tools/statusline.py

Q: 出现乱码?

确认脚本中有 UTF-8 编码配置(见上文 Windows 适配部分)。同时建议使用 Windows Terminal 而非系统自带的 cmd,对 Unicode 支持更好。

Q: 状态栏不刷新/卡住?

Claude Code 默认每 5 秒调用一次状态栏脚本。如果完全不刷新,检查 settings.json 中的 refreshInterval 配置。另外确保脚本在执行 print 时使用了 flush=True(脚本中已包含)。

Q: 技能数量显示为 0?

脚本通过扫描 ~/.claude/skills/ 目录下包含 SKILL.md 文件的子文件夹来统计技能数量。确认你的技能目录结构正确。

Q: 子代理运行时状态栏不显示代理名称?

受 Claude Code 接口限制,目前 agent.name 字段只在子代理运行时才会被填充——普通的工具调用(如 Read、Write、Bash)不会显示。这是 Claude Code 本身的设计,暂时无解。如果你有更好的发现,欢迎交流。


六、扩展建议

这套脚本的设计是模块化的,你可以轻松自定义:

自定义颜色主题

修改脚本顶部的 ANSI 颜色常量即可:

1
2
3
4
5
6
7
# 默认颜色
CYAN = "\033[36m" # 模型名
GREEN = "\033[32m" # 低用量
YELLOW = "\033[33m" # 中用量
RED = "\033[31m" # 高用量
MAGENTA = "\033[35m" # 代理名
BLUE = "\033[34m" # 技能数

例如换成 Solarized 风格:

1
2
3
4
CYAN = "\033[38;5;37m"
GREEN = "\033[38;5;100m"
YELLOW = "\033[38;5;136m"
RED = "\033[38;5;124m"

添加更多模块

main() 函数中的 parts 列表就是最终显示的模块序列。你可以轻松添加:

  • 当前时间datetime.now().strftime("%H:%M")
  • Python 版本:方便多版本切换时确认
  • 当前目录名os.path.basename(os.getcwd())
  • 系统负载/内存:用 psutil(如果愿意引入一个依赖)

只需在 parts.append(...) 中加入新内容,就会按顺序显示。

调试技巧:发现可用的数据字段

我还写了一个辅助脚本 discover_schema.py,它会将 Claude Code 传入的完整 JSON 保存到文件中,方便你探索有哪些字段可用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
"""Capture status line JSON input to discover available fields."""
import json
import sys
import os

OUTPUT_FILE = os.path.expanduser("~/.claude/statusline-tools/schema_dump.json")

try:
data = json.load(sys.stdin)
with open(OUTPUT_FILE, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False, default=str)
model = data.get("model", {}).get("display_name", "?")
cw = data.get("context_window", {})
used = cw.get("used_percentage", 0)
print(f"🔍 Model:{model} | Context:{used:.0f}% | Schema saved to ~/.claude/statusline-tools/schema_dump.json")
except Exception as e:
print(f"Schema discovery error: {e}", file=sys.stderr)
print("🔍 Status line active - schema discovery")

把这个脚本临时设为状态栏命令,跑几分钟后再切回主脚本,就能在 schema_dump.json 里看到所有可用字段了。


七、写在最后

这套脚本我从踩坑到稳定使用大约花了一个下午的时间。最大的感受是:Claude Code 的可扩展性其实很不错——statusLine 就是一个”给你 JSON,你随便输出”的简单接口,给了充分的自由度。

在此之前,我需要频繁地敲 /context 查看 Token 用量,现在余光扫一眼状态栏就够了。进度条从绿色变黄再到红色的过程,就像一个温和的提醒,帮我养成了及时 /compact 的习惯。

如果你也是 Claude Code 的深度用户,强烈建议花半小时照着配一下。有什么问题或改进想法,欢迎在评论区交流 👋


环境信息

  • OS: Windows 11 Home China + Git Bash (MSYS2/MinGW64)
  • Python: 3.11.9
  • Claude Code: 最新版
  • 技能数量: 32 个
  • 模型: DeepSeek V4 Pro (via API proxy)