Skip to content

把 Claude Code CLI 的 statusLine 做成可观测面板:一段 Python 脚本的完整拆解与落地记录 ​

这篇文章只做一件事:

把下面这段可运行的 Python 脚本和 settings.json 片段,整理成一份可以直接复用、可验证、可维护的技术记录。


先看结果:它到底实现了什么 ​

这段脚本被 Claude Code CLI 作为 statusLine.command 执行后,会在状态栏输出两行信息:

  1. 当前项目目录 + 当前模型 ID(以及可选的 API host)
  2. 本次上下文输入/输出 token 与总量(K/M 缩写)

对应配置入口是:

json
{
  "statusLine": {
    "command": "uv run /home/forty-wsl/dev49/AI49/claude-statusLine.py",
    "type": "command"
  }
}

这意味着:Claude Code 每次刷新状态栏时,会把一段 JSON payload 通过标准输入(stdin)喂给你的脚本;脚本处理后,把最终文本写到标准输出(stdout)。


为什么我需要这条 statusLine ​

我实际想解决的是三个高频问题:

  • 我现在到底在什么目录操作?(多仓库并行时很容易串)
  • 这次用的是哪个模型?(排查行为差异和成本时非常关键)
  • 上下文 token 走到什么量级了?(避免“感觉上很多”,而是直接看到数字)

默认信息不一定总是符合自己的工作流,所以我把它做成一个本地可控脚本。


全量代码(原始版本) ​

python
# /// 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 步:

  1. Claude Code 触发 statusLine 命令
  2. Claude Code 将运行时 JSON payload 写入脚本 stdin
  3. 脚本读取并解析 payload,计算需要展示的字段
  4. 脚本将两行文本写到 stdout,Claude Code 渲染到状态栏

这个链路很重要,因为它决定了你该从哪里排错:

  • 看不到内容:先看脚本是否有 stdout
  • 字段为空:看 payload 路径是否取对
  • host 异常:看环境变量 ANTHROPIC_BASE_URL

逐段拆解(详细) ​

1) 头部元信息与依赖 ​

python
# /// script
# requires-python = ">=3.12"
# dependencies = []
# ///

这段是脚本元信息:

  • requires-python = ">=3.12":声明运行版本要求
  • dependencies = []:无第三方依赖,纯标准库可跑

优点是部署简单:环境里有 Python 3.12 即可。


2) 常量设计 ​

python
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():把数字变成可读量级 ​

python
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 → 987
  • 12_345 → 12.35K
  • 2_830_000 → 2.83M

这样做的价值是把状态栏长度控制在稳定范围,避免大数字挤压可读性。


4) get_base_host():从 ANTHROPIC_BASE_URL 提取主机名 ​

python
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 起点 ​

python
scheme_end = base_url.find("://")
host_start = scheme_end + 3 if scheme_end >= 0 else 0

如果 URL 有 scheme://,host 从 :// 之后开始;没有则从字符串开头开始。

4.3 定位 host 终点 ​

python
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 去掉用户信息 ​

python
host = base_url[host_start:host_end].rsplit("@", 1)[-1]

如果 URL 是 user:pass@host 形式,会保留 @ 后面的 host。

4.5 IPv6 与端口处理 ​

python
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.com
  • https://[email protected]:8443/path → proxy.example.com
  • http://127.0.0.1:8080/query?a=1 → 127.0.0.1

这一步的目标是:状态栏只显示“关键信息 host”,不显示冗余 URL 细节。


5) main():读取输入、提取字段、输出最终文本 ​

5.1 读取原始 payload ​

python
raw_payload = sys.stdin.buffer.read()

这里读的是二进制流,再交给 JSON 解析。

5.2 落盘一份输入快照 ​

python
with open(TMP_INPUT_PATH, "wb") as file:
    file.write(raw_payload)

这对排查非常有用:

  • 可以确认 Claude Code 实际传了什么字段
  • 字段路径变更时,能快速定位不匹配

5.3 JSON 解析与字段提取 ​

python
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_dir
  • model.id
  • context_window.total_input_tokens
  • context_window.total_output_tokens

5.4 组合 summary ​

python
summary = f"项目目录:{workspace} | 模型:{model}"
base_host = get_base_host()
if base_host:
    summary = f"{summary} | API URL:{base_host}"

如果能拿到 host,则动态追加,否则保持基础信息不变。

5.5 输出两行 ​

python
sys.stdout.write(
    f"{summary}\n"
    f"输入:{format_tokens(input_tokens)} | 输出:{format_tokens(output_tokens)} | 总数:{format_tokens(total_tokens)}\n"
)

最终状态栏内容稳定为两行,第二行固定是 token 统计。


6) 这段代码隐含的设计取舍 ​

从实现细节看,它的设计偏向“稳定可观测”:

  1. 无外部依赖:降低环境差异导致的故障概率
  2. 落盘原始输入:优先保障可排错性
  3. host 解析独立函数化:便于单点修改与复用
  4. 数字缩写统一函数化:输出风格一致
  5. 输出结构固定:statusLine 的阅读负担更低

7) 实际部署步骤(可直接复现) ​

下面是按当前代码片段可执行的最小步骤。

  1. 把 Python 脚本保存到本地(例如 /home/forty-wsl/dev49/AI49/claude-statusLine.py)
  2. 在 Claude Code 配置里写入:
json
{
  "statusLine": {
    "command": "uv run /home/forty-wsl/dev49/AI49/claude-statusLine.py",
    "type": "command"
  }
}
  1. 重启或重新加载 Claude Code 会话
  2. 观察状态栏是否出现两行信息
  3. 如异常,检查 /tmp/claude-statusLine-9999.json 是否成功写入

8) 我自己会用的排错清单 ​

如果状态栏异常,我通常按这个顺序检查:

  1. 脚本是否可执行:uv run <script> 是否能正常启动
  2. stdin 是否有数据:/tmp/claude-statusLine-9999.json 是否有内容
  3. 字段路径是否命中:payload 是否包含 workspace/model/context_window
  4. 环境变量是否存在:ANTHROPIC_BASE_URL 是否设置
  5. 输出是否有换行:确保是两行文本,不要混入日志输出

9) 可选改进方向(不改变当前行为) ​

当前实现已经可用。如果你要继续增强,建议仍保持最小改动原则:

  • 在不改输出格式前提下,增加异常时的降级文本(例如 N/A)
  • 将 TMP_INPUT_PATH 改为可配置环境变量,便于多实例隔离
  • 控制路径显示长度(超长目录做中间省略)

这些都是“增强可维护性”,不是重写架构。


结语 ​

这段 statusLine 脚本看起来不长,但它把一个很实用的习惯固化了:

  • 用脚本掌控自己真正关心的运行态信息
  • 把排错入口预先做在链路里(输入落盘)
  • 用固定输出结构降低认知成本

对 CLI 工作流来说,这类“小而稳”的工具,长期价值非常高。

Nine and AI — Licensed under the MIT License