把 Claude Code CLI 的 statusLine 做成可观测面板:一段 Python 脚本的完整拆解与落地记录
这篇文章只做一件事:
把下面这段可运行的 Python 脚本和 settings.json 片段,整理成一份可以直接复用、可验证、可维护的技术记录。
先看结果:它到底实现了什么
这段脚本被 Claude Code CLI 作为 statusLine.command 执行后,会在状态栏输出两行信息:
- 当前项目目录 + 当前模型 ID(以及可选的 API host)
- 本次上下文输入/输出 token 与总量(K/M 缩写)
对应配置入口是:
{
"statusLine": {
"command": "uv run /home/forty-wsl/dev49/AI49/claude-statusLine.py",
"type": "command"
}
}这意味着:Claude Code 每次刷新状态栏时,会把一段 JSON payload 通过标准输入(stdin)喂给你的脚本;脚本处理后,把最终文本写到标准输出(stdout)。
为什么我需要这条 statusLine
我实际想解决的是三个高频问题:
- 我现在到底在什么目录操作?(多仓库并行时很容易串)
- 这次用的是哪个模型?(排查行为差异和成本时非常关键)
- 上下文 token 走到什么量级了?(避免“感觉上很多”,而是直接看到数字)
默认信息不一定总是符合自己的工作流,所以我把它做成一个本地可控脚本。
全量代码(原始版本)
# /// script
# requires-python = ">=3.12"
# dependencies = []
# ///
import json
import os
import sys
TMP_INPUT_PATH = "/tmp/claude-statusLine-9999.json"
THOUSAND = 1_000
MILLION = 1_000_000
URL_END_CHARS = "/?#"
def format_tokens(value: int) -> str:
if value >= MILLION:
return f"{value / MILLION:.2f}M"
if value >= THOUSAND:
return f"{value / THOUSAND:.2f}K"
return str(value)
def get_base_host() -> str | None:
base_url = os.environ.get("ANTHROPIC_BASE_URL")
if not base_url:
return None
scheme_end = base_url.find("://")
host_start = scheme_end + 3 if scheme_end >= 0 else 0
host_end = len(base_url)
for char in URL_END_CHARS:
index = base_url.find(char, host_start)
if index >= 0 and index < host_end:
host_end = index
host = base_url[host_start:host_end].rsplit("@", 1)[-1]
if host.startswith("["):
bracket_end = host.find("]")
return host[1:bracket_end] if bracket_end > 0 else host
colon_index = host.find(":")
return host[:colon_index] if colon_index >= 0 else host
def main() -> None:
raw_payload = sys.stdin.buffer.read()
with open(TMP_INPUT_PATH, "wb") as file:
file.write(raw_payload)
payload = json.loads(raw_payload)
workspace = payload["workspace"]["current_dir"]
model = payload["model"]["id"]
context_window = payload["context_window"]
input_tokens = context_window["total_input_tokens"]
output_tokens = context_window["total_output_tokens"]
total_tokens = input_tokens + output_tokens
summary = f"项目目录:{workspace} | 模型:{model}"
base_host = get_base_host()
if base_host:
summary = f"{summary} | API URL:{base_host}"
sys.stdout.write(
f"{summary}\n"
f"输入:{format_tokens(input_tokens)} | 输出:{format_tokens(output_tokens)} | 总数:{format_tokens(total_tokens)}\n"
)
if __name__ == "__main__":
main()执行链路:从 Claude Code 到终端状态栏
完整数据流可以概括成 4 步:
- Claude Code 触发 statusLine 命令
- Claude Code 将运行时 JSON payload 写入脚本 stdin
- 脚本读取并解析 payload,计算需要展示的字段
- 脚本将两行文本写到 stdout,Claude Code 渲染到状态栏
这个链路很重要,因为它决定了你该从哪里排错:
- 看不到内容:先看脚本是否有 stdout
- 字段为空:看 payload 路径是否取对
- host 异常:看环境变量
ANTHROPIC_BASE_URL
逐段拆解(详细)
1) 头部元信息与依赖
# /// script
# requires-python = ">=3.12"
# dependencies = []
# ///这段是脚本元信息:
requires-python = ">=3.12":声明运行版本要求dependencies = []:无第三方依赖,纯标准库可跑
优点是部署简单:环境里有 Python 3.12 即可。
2) 常量设计
TMP_INPUT_PATH = "/tmp/claude-statusLine-9999.json"
THOUSAND = 1_000
MILLION = 1_000_000
URL_END_CHARS = "/?#"每个常量都对应一个明确意图:
TMP_INPUT_PATH:把原始输入落盘,便于回放与排查THOUSAND/MILLION:token 数字缩写阈值URL_END_CHARS:截取 host 时,遇到路径/查询/锚点的停止符
3) format_tokens():把数字变成可读量级
def format_tokens(value: int) -> str:
if value >= MILLION:
return f"{value / MILLION:.2f}M"
if value >= THOUSAND:
return f"{value / THOUSAND:.2f}K"
return str(value)规则很直接:
>= 1,000,000:显示x.xxM>= 1,000:显示x.xxK- 否则显示原始整数
示例:
987→98712_345→12.35K2_830_000→2.83M
这样做的价值是把状态栏长度控制在稳定范围,避免大数字挤压可读性。
4) get_base_host():从 ANTHROPIC_BASE_URL 提取主机名
def get_base_host() -> str | None:
base_url = os.environ.get("ANTHROPIC_BASE_URL")
if not base_url:
return None
...这个函数是整段代码里最“工程化”的部分。它不是简单 split('/'),而是做了多步兼容处理。
4.1 空值分支
- 环境变量不存在时,直接
None - 状态栏最终不追加 API host
4.2 定位 host 起点
scheme_end = base_url.find("://")
host_start = scheme_end + 3 if scheme_end >= 0 else 0如果 URL 有 scheme://,host 从 :// 之后开始;没有则从字符串开头开始。
4.3 定位 host 终点
host_end = len(base_url)
for char in URL_END_CHARS:
index = base_url.find(char, host_start)
if index >= 0 and index < host_end:
host_end = index遇到 /、?、# 中最早出现的位置就停止。这样能去掉路径、查询参数和锚点。
4.4 去掉用户信息
host = base_url[host_start:host_end].rsplit("@", 1)[-1]如果 URL 是 user:pass@host 形式,会保留 @ 后面的 host。
4.5 IPv6 与端口处理
if host.startswith("["):
bracket_end = host.find("]")
return host[1:bracket_end] if bracket_end > 0 else host
colon_index = host.find(":")
return host[:colon_index] if colon_index >= 0 else host- IPv6 场景处理形如
[::1]:8080 - 普通域名/IP 场景移除端口
4.6 结果示例
https://api.anthropic.com/v1/messages→api.anthropic.comhttps://[email protected]:8443/path→proxy.example.comhttp://127.0.0.1:8080/query?a=1→127.0.0.1
这一步的目标是:状态栏只显示“关键信息 host”,不显示冗余 URL 细节。
5) main():读取输入、提取字段、输出最终文本
5.1 读取原始 payload
raw_payload = sys.stdin.buffer.read()这里读的是二进制流,再交给 JSON 解析。
5.2 落盘一份输入快照
with open(TMP_INPUT_PATH, "wb") as file:
file.write(raw_payload)这对排查非常有用:
- 可以确认 Claude Code 实际传了什么字段
- 字段路径变更时,能快速定位不匹配
5.3 JSON 解析与字段提取
payload = json.loads(raw_payload)
workspace = payload["workspace"]["current_dir"]
model = payload["model"]["id"]
context_window = payload["context_window"]
input_tokens = context_window["total_input_tokens"]
output_tokens = context_window["total_output_tokens"]它依赖了这几个关键路径:
workspace.current_dirmodel.idcontext_window.total_input_tokenscontext_window.total_output_tokens
5.4 组合 summary
summary = f"项目目录:{workspace} | 模型:{model}"
base_host = get_base_host()
if base_host:
summary = f"{summary} | API URL:{base_host}"如果能拿到 host,则动态追加,否则保持基础信息不变。
5.5 输出两行
sys.stdout.write(
f"{summary}\n"
f"输入:{format_tokens(input_tokens)} | 输出:{format_tokens(output_tokens)} | 总数:{format_tokens(total_tokens)}\n"
)最终状态栏内容稳定为两行,第二行固定是 token 统计。
6) 这段代码隐含的设计取舍
从实现细节看,它的设计偏向“稳定可观测”:
- 无外部依赖:降低环境差异导致的故障概率
- 落盘原始输入:优先保障可排错性
- host 解析独立函数化:便于单点修改与复用
- 数字缩写统一函数化:输出风格一致
- 输出结构固定:statusLine 的阅读负担更低
7) 实际部署步骤(可直接复现)
下面是按当前代码片段可执行的最小步骤。
- 把 Python 脚本保存到本地(例如
/home/forty-wsl/dev49/AI49/claude-statusLine.py) - 在 Claude Code 配置里写入:
{
"statusLine": {
"command": "uv run /home/forty-wsl/dev49/AI49/claude-statusLine.py",
"type": "command"
}
}- 重启或重新加载 Claude Code 会话
- 观察状态栏是否出现两行信息
- 如异常,检查
/tmp/claude-statusLine-9999.json是否成功写入
8) 我自己会用的排错清单
如果状态栏异常,我通常按这个顺序检查:
- 脚本是否可执行:
uv run <script>是否能正常启动 - stdin 是否有数据:
/tmp/claude-statusLine-9999.json是否有内容 - 字段路径是否命中:payload 是否包含
workspace/model/context_window - 环境变量是否存在:
ANTHROPIC_BASE_URL是否设置 - 输出是否有换行:确保是两行文本,不要混入日志输出
9) 可选改进方向(不改变当前行为)
当前实现已经可用。如果你要继续增强,建议仍保持最小改动原则:
- 在不改输出格式前提下,增加异常时的降级文本(例如
N/A) - 将
TMP_INPUT_PATH改为可配置环境变量,便于多实例隔离 - 控制路径显示长度(超长目录做中间省略)
这些都是“增强可维护性”,不是重写架构。
结语
这段 statusLine 脚本看起来不长,但它把一个很实用的习惯固化了:
- 用脚本掌控自己真正关心的运行态信息
- 把排错入口预先做在链路里(输入落盘)
- 用固定输出结构降低认知成本
对 CLI 工作流来说,这类“小而稳”的工具,长期价值非常高。