文章

【教程】打造Agent状态栏:给AI编程助手装上仪表盘

本文详解AI编程助手状态栏的原理与实现,从二十行最小可用版到九段完整版,涵盖模型、目录、分支、上下文使用率、任务进度、工具调用统计等九项信息。基于Unix管道哲学,状态栏本地运行、不进模型上下文、零token消耗,通过stdin接收JSON状态,stdout输出带颜色的仪表盘文本。支持Claude Code、Cursor等多终端,提供实时会话可视化,提升开发效率与系统可见性。

【教程】打造Agent状态栏:给AI编程助手装上仪表盘

文章信息

  • 原文链接:https://jiayq.blog.csdn.net/article/details/166368891
  • 发布时间:2026-09-22 17:30:05
  • 标签:#Agent, #AI, #Claude, #StatusLine, #Agent工具, #状态栏, #协作

【教程】打造Agent状态栏:给AI编程助手装上仪表盘

摘要

本文介绍 AI 编程助手的状态栏(Status Line):原理、构成、与大模型交互的时序,以及如何从零搭建。从二十行最小可用版起步,到带 AI 会话进度与 tool/MCP 调用统计的九段完整版(含四版演进的踩坑记录),再到让 AI 自己规划每次会话的状态内容,最后打通 claude-code、CodeBuddy、Cursor 等多终端。所有代码本机实测,完整脚本托管在我的 Agent 工具箱,开箱即用。

思维导图

1. Agent 状态栏的理论基础

1.0 先看最终效果

先上成品,这是本文结束时你会得到的状态栏(演示数据):

完整版状态栏

就这一行:当前用的什么模型、在哪个目录哪个分支、上下文还剩多少(绿黄红三级预警)、AI 把任务干到哪个阶段、工具和 MCP 调了多少次,一眼全有。它常驻终端底部,本地渲染,不进模型上下文,不消耗一个 token。

它是什么、怎么和大模型交互、怎么从零搭出来,下文展开。

1.1 状态栏是哪一块

用 Claude Code(或 CodeBuddy Code、Cursor CLI 这类终端形态的 AI 编程助手)干活时,终端界面就三块:中间对话区、底部输入区、输入区下面那一小行 ——就是状态栏。它常驻不动,内容随会话实时刷新:当前模型、在哪个目录、上下文用了多少。说白了,它是整个 Agent 会话的仪表盘。

1.2 三条道理

第一条:系统状态可见性。 Nielsen 十大可用性启发式排第一的就是它——系统应该让用户随时知道正在发生什么。汽车有仪表盘、电梯有楼层数字,AI 编程助手凭什么例外?没有状态栏,它就像一辆没有油表的车,油烧完了(上下文爆了)你才知道该加油。

第二条:Unix 管道哲学。 实现方式非常 Unix:宿主把会话状态打包成 JSON,通过 stdin 喂给你指定的任意一条命令,命令往 stdout 吐一行文本,宿主拿去渲染。就这么一个过滤器,没有 SDK、没有私有协议。bash、python、node 随便写,jq、git 随便调。这也是它被多终端”抄来抄去”的原因——协议简单到没有抄的成本。

第三条:上下文经济学。 状态栏在本地运行,不进模型上下文,不消耗任何 token(官方文档原话:“runs locally and does not consume API tokens”)。你每次问 AI”我们在哪个目录”,都要占上下文、走一轮往返;状态栏把这类高频低价值的信息查询成本降到了零。

所以我的定位是:状态栏 = 挂在会话上的只读探针 + 一块零成本的信息仪表盘 。它不参与对话、不影响模型行为,只负责把会话状态用你喜欢的样子画在屏幕上。

2. Agent 状态栏的构成

2.1 一行输出的九个段位

回看 1.0 节那行成品,竖线分隔,九个段位:

颜色示例信息来源
1青色Sonnet 5stdin JSON 的 model.display_name
2蓝色demo-appJSON 目录字段取 basename
3绿色feature/pay-refactor本地 git rev-parse 实时查询
4绿/黄/红63%JSON 的 context_window.used_percentage
5紫色highJSON 的 effort.level
6灰色(支付模块重构)JSON 的 session_name
7黄色1/3 梳理支付回调逻辑AI 自己写的会话状态文件(第 6 节)
8灰色tool 11 · MCP 2脚本自己读会话流水统计(5.4 节)
9青色#128(open)JSON 的 pr.number / pr.review_state

这九段的信息来源正好是三层 :1~6、9 是宿主给的稳态数据;7 是 AI 写的意图(任务进行到哪);8 是从流水算出的过程数据(tool/MCP 调了多少)。三层都不进模型上下文。

第 4 段是唯一做了阈值配色的:上下文用量 <50% 绿(安心),50%~80% 黄(留意),>80% 红(该 /compact 了)。颜色本身就是信息,一眼扫过去不用读数字:

三级配色效果

第 6 段的会话名值得一提:它是 AI 根据对话内容自动生成的会话标题(也可 /rename 手动指定),tmux 多窗口并行时靠它区分”这个窗口在干嘛”。

2.2 三要素与数据流

任何状态栏拆到底就三样:数据源(宿主喂的 stdin JSON)、加工(你的命令:解析 JSON、补本地信息、配色)、展示(一段带 ANSI 转义码的文本,宿主原样渲染)。串起来:

Agent状态栏数据流

整条链路里,状态栏命令只是个旁路,不碰对话主链路。

3. 状态栏与大模型交互的 message 顺序

3.1 一次交互的完整时序

你敲下一个 prompt 之后,到状态栏刷新出来,消息先后顺序是:

状态栏刷新的消息时序

  1. 用户输入 prompt;
  2. Claude Code 把 messages(系统提示 + CLAUDE.md 等规则 + 历史对话 + 工具结果)发给大模型 API——状态栏相关的一切都不在里面 ;
  3. 模型流式返回,宿主拿到本轮 usage 统计,更新内部状态;
  4. 触发状态栏刷新(300ms 防抖),把最新状态打成 JSON,从 stdin 喂给状态栏命令;
  5. 命令输出一行文本,终端底部渲染出来。

除”新助手消息到达”外,这些事件也触发刷新:/compact 完成、权限模式变化、vim 模式切换、statusLine 配置被修改、refreshInterval 定时器到期等。想让它不看消息也自己走表,配个 refreshInterval 就行。

3.2 stdin JSON 长什么样

看一份真实结构(字段值为演示数据):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
  "session_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "transcript_path": "/home/dev/.claude/projects/-home-dev-demo-app/a1b2c3d4.jsonl",
  "cwd": "/tmp/demo-app",
  "effort": { "level": "high" },
  "session_name": "支付模块重构",
  "model": { "id": "claude-sonnet-5", "display_name": "Sonnet 5" },
  "workspace": { "current_dir": "/tmp/demo-app", "project_dir": "/tmp/demo-app", "added_dirs": [] },
  "cost": {
    "total_cost_usd": 0.42,
    "total_duration_ms": 618000,
    "total_lines_added": 128,
    "total_lines_removed": 36
  },
  "context_window": {
    "total_input_tokens": 8642,
    "total_output_tokens": 1234,
    "context_window_size": 200000,
    "used_percentage": 63,
    "remaining_percentage": 37
  }
}

常用字段速查:

字段含义备注
model.display_name当前模型名换模型自动跟着变
workspace.current_dir / cwd当前工作目录两者一致,优先取前者
context_window.used_percentage上下文已用百分比宿主已算好,直接用
cost.total_cost_usd会话估算花费想盯钱就显示它
effort.level推理强度low~max
session_name会话名/rename 指定或 AI 自动生成
session_id会话唯一 ID缓存文件的隔离键
transcript_path会话流水文件路径第 6 节统计段的数据源

两个细节:不适用就不出现 ——没开 vim 模式就没有 vim 字段,所以取值统统要空值兜底;字段随版本增减,jq// 兜底操作符是标准姿势。

3.3 关键边界:什么进模型上下文,什么不进

数据进模型上下文?去向
CLAUDE.md / 各种规则文件✅ 进成为系统提示的一部分
历史对话 + 工具执行结果✅ 进messages 主体
状态栏收到的 stdin JSON❌ 不进只到本地脚本为止
状态栏吐出的 stdout❌ 不进只渲染到终端

状态栏是纯观察者 :它看得到会话,会话感知不到它;不影响模型行为,不占一个 token。

顺带回答一个我真实问过的问题:状态栏能不能显示 subagent 活动、每次 toolcall 的明细?stdin JSON 里没有这些字段 。但 toolcall 有变通:transcript_path 指向的会话流水文件里记着每次 tool_use,我的第 8 段统计就是从那儿算的(见 5.4)。subagent 的活动则真到头了——状态栏适合显示稳态信息,不适合流水。

4. Agent 状态栏解决了什么问题

痛点具体场景状态栏的答案
上下文余量不可见长会话干着干着模型”忘事”,一查 95%百分比常显 + 三级配色,提前 /compact
切错目录多仓库并行,在 A 仓库的会话里问 B 仓库的事目录 + 分支常显
模型漂移/model 切来切去忘了当前用的谁模型名 + 推理强度常显
会话身份混乱tmux 五个窗口,忘了哪个在干嘛AI 生成的会话名常显
过程黑盒AI 闷头干了一百多次工具调用,你毫无感知tool 11 · MCP 2 常显,工具数飙升说明在反复试错
状态询问浪费问 AI”我们在哪个目录”要花 token + 往返本地渲染,0 token

前四条防事故,后两条降成本。MCP 计数还有个隐藏用途:它一直是 0,大概率你的 MCP server 没配好,根本没被调到。

5. 如何搭建:配置入口与最小可用版

5.1 配置入口

Claude Code 的配置在 settings.json(全局 ~/.claude/settings.json,项目级在项目 .claude/settings.json),就一个 statusLine 字段:

1
2
3
4
5
6
7
8
{
  "statusLine": {
    "type": "command",
    "command": "~/.claude/statusline.sh",
    "padding": 0,
    "refreshInterval": 5
  }
}

type 固定 command;command 是接收 stdin JSON 的命令,可内联可指向脚本;padding 左右留白默认 0;refreshInterval 定时刷新(秒,最小 1)。

三条路起步:用现成轮子(5.3 节有对比)、让 AI 写自己写 ——5.2 的最小版就是起点,推荐脚本文件方式,内联命令扩展性差(踩过坑的经验,见 5.4)。让 AI 写最省事,我当时就一句:

1
/statusline 搞一个好看的,并且有用的statusline

AI 连脚本带配置一次到位,我的 v1 就这么来的;生成完记得把命令读一遍,毕竟它要常驻你的终端。

5.2 最小可用版

二十来行,macOS 实测可跑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env bash
# 最小可用状态栏:模型 | 上下文用量%
input=$(cat)

# jq 提取字段(// 做空值兜底,floor 取整)
model=$(printf '%s' "$input" | jq -r '.model.display_name // "?"')
pct=$(printf '%s' "$input" | jq -r '.context_window.used_percentage // 0 | floor')

# ANSI 颜色定义
c=$(printf '\033[36m'); g=$(printf '\033[32m'); y=$(printf '\033[33m')
rd=$(printf '\033[31m'); r=$(printf '\033[0m')

# 上下文用量三级配色:80+ 红,50+ 黄,其余绿
if   [ "$pct" -ge 80 ]; then cc=$rd
elif [ "$pct" -ge 50 ]; then cc=$y
else                          cc=$g
fi

printf '%s%s%s | %s%s%%%s' "$c" "$model" "$r" "$cc" "$pct" "$r"



chmod +x ~/.claude/statusline.sh   # 忘了这步,状态栏会安静地空白

三条官方文档级别的最佳实践:慢操作(git status 类)用 session_id 做缓存键,别每次真跑;所有字段取值带空值兜底;输出保持短(宿主会设 COLUMNS 环境变量,但窄终端仍会换行)。另外两个能力知道就行:输出多行就是多个状态栏行;支持 OSC 8 超链接,可以让 PR 号变成可点击链接。

5.3 现成的轮子,和这篇为什么还要自己搓

先说实话:社区里现成的 statusline 方案已经很成熟,想省事直接用:

项目热度一句话
ccstatusline12k+ ★生态一哥,Powerline 风格 + TUI 配置界面,widget 从 git 分支到 CI 状态应有尽有
claude-powerline1k+ ★vim 风 powerline,npx 一行接入
ccusage statusline成本党必备专盯钱:会话花费、今日累计、5 小时 block 余量、烧钱速率

只想好看好用,装 ccstatusline,别手搓。

那这篇为什么还要搓?回想 2.1 节的三层信息:现成方案全部只在第①层(宿主给的稳态数据)发力,②③两层,主流项目截至本文(2026-09)都没有内置。原因不复杂:

  • 第①层的数据是 stdin JSON 直接喂到嘴边的 ,工具就是个格式化器,没门槛,所以人人做得,是红海;
  • 第③层(tool/MCP 统计)要自己解析 transcript——那是个没有文档的内部文件 ,格式随时可能变,主流工具不愿替用户兜这个险。ccstatusline 留了 Custom Command 的口子可以挂自定义命令,但没人把这层做成内置;
  • 第②层(AI 会话状态)的核心根本不是状态栏代码,是那条 CLAUDE.md 规则——它改变的是 AI 的行为 ,一个显示工具管不到这里。

一句话:显示层是红海,协作层是空白 。自己搓,搓的就是后面这两层——这也是 5.4 和第 6 节真正交付的东西。

5.4 完整版:统计段、四版演进与踩坑

最小版之外,完整版多出两个扩展段:tool/MCP 统计段(本节)和会话状态段(第 6 节的主角)。基础七段只是 5.2 最小版的直白扩展——逐字段取值、逐段拼接,段位含义见 2.1 那张表——完整脚本开箱即用,托管在我的 Agent 工具箱,含安装说明、CLAUDE.md 规则和验证数据。这里只展开有技术含量的统计段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# ---- tool / MCP 调用统计段(读 transcript,带缓存) ----
tp=$(printf '%s' "$input" | jq -r '.transcript_path // empty')
if [ -n "$tp" ] && [ -f "$tp" ]; then
  sid=$(printf '%s' "$input" | jq -r '.session_id // "default"')
  cache="/tmp/cc-statusline-toolstats-${sid}"
  sz=$(wc -c < "$tp" 2>/dev/null | tr -d ' ')
  # 缓存命中:文件大小没变(流水只追加)就直接用
  if [ -s "$cache" ] && [ "$(head -1 "$cache" 2>/dev/null)" = "$sz" ]; then
    stats=$(tail -1 "$cache")
  else
    stats=$(jq -r 'select(.type=="assistant") | .message.content[]? | select(.type=="tool_use") | .name' "$tp" 2>/dev/null | awk '/^mcp__/{m++} {t++} END{printf "%d %d", t+0, m+0}')
    printf '%s\n%s\n' "$sz" "$stats" > "$cache" 2>/dev/null
  fi
  total=${stats%% *}; mcp=${stats##* }
  if [ "${total:-0}" -gt 0 ] 2>/dev/null; then
    o="$o $s tool ${total}$r"                        # 总调用数
    # ${d} 必须带花括号,原因见下文坑一
    [ "${mcp:-0}" -gt 0 ] 2>/dev/null && o="$o ${d}· MCP ${mcp}$r"
  fi
fi

原理:transcript 是 JSONL 流水,type=="assistant" 事件的 message.content[] 里带 tool_use 块,name 就是工具名,MCP 工具一律带mcp__ 前缀,和内置工具一筛就分开。性能拿真实会话验证过:4.7MB 流水全量扫描 50ms,远小于 300ms 防抖窗口;再加一层缓存——键是文件大小(流水只追加,大小不变即无新调用)+ session_id(多会话互不污染)。想看更细的,把 awk 换成 sort | uniq -c

装完离线验证(演示数据 demo-input.json 在工具箱仓库):

手动验证statusline

1
cat demo-input.json | ~/.claude/statusline.sh   # 注意用管道,原因见坑二

这套东西我迭代过四版,顺带踩了两个阴坑,一并记录。v1 是 AI 一句话生成的进度条版(就是 5.1 那句提示词的产物),死在窄终端换行;v2 的 token 计数和百分比是同一信息的两种写法,砍了;v3 极简七段,留了个调试后门——脚本开头 input=$(tee /tmp/cc-statusline-input.json),宿主每次喂的 JSON 都落一份盘,3.2 节那份样例就来自它;v4 为加两个扩展段迁成脚本文件,坑就踩在这一步:

坑一:macOS 自带的 bash 是 3.2(2007 年的版本,bash --version 吓一跳)。"$d·" 这种变量后紧跟多字节字符,bash 3.2 会把 $d 连同 · 的首字节一起解析成一个未定义的变量名,展开为空还吞掉一个字节,· 直接变乱码。zsh 没这问题,终端手测发现不了——偏偏状态栏是被 bash 执行的。修法:写 ${d}·,变量后不是空格一律加花括号。

坑二:测试用重定向喂脚本,读到的是空。 脚本开头的 tee 会先把这个正被读取的文件截断成 0 字节——statusline.sh < /tmp/cc-statusline-input.json 看着天经地义,实则读到空输入,状态栏”安静地空白”(宿主不报错)。用管道就没事。

6. 进阶:加一条规则,让 AI 自己规划每次会话的状态栏

6.1 一条规则

2.1 节的第 7 段(黄色会话状态)是整套里唯一”AI 主导”的信息:宿主不知道任务进行到哪,只有 AI 自己知道,那就让它写出来。在 CLAUDE.md 里加一条规则:

1
2
3
4
5
6
7
## 会话状态栏协作规则
- 接到任务后,先规划执行阶段,然后把当前进度写入
  ~/.claude/session-status.txt:单行文本,不超过 24 个字,
  格式如 "1/3 梳理支付回调逻辑"。
- 每完成一个阶段、或工作焦点切换时,立即更新该文件。
- 这个文件会被终端状态栏实时展示,内容是写给用户一眼看懂的,
  不要写技术细节,只写"进度 + 正在做什么"。

脚本侧在状态栏末尾加一段(完整脚本里的”扩展段 1”):

1
2
3
4
5
6
# ---- 会话状态段:显示 AI 维护的进度文件 ----
f="$HOME/.claude/session-status.txt"
if [ -f "$f" ]; then
  st=$(head -1 "$f" 2>/dev/null | cut -c1-48)   # 只取首行,限长防换行
  [ -n "$st" ] && o="$o $s $y$st$r"             # 黄色显示
fi

6.2 效果与边界

同一个会话里,AI 随任务推进更新状态文件,状态栏跟着变:

AI自己规划的会话状态段

从上到下是三个时刻:1/3 梳理支付回调逻辑2/3 重构回调分发器3/3 回归验证 + 提交。tmux 里扫一眼每个窗口,谁在哪个阶段清清楚楚。

边界:更新依赖 AI 的自觉(规则驱动),忘了就停在旧进度;同项目多会话共用一个文件路径会互相覆盖,更严谨的做法是按会话隔离——hook 的 stdin JSON 里也有 session_id,可以让 hook 把状态文件按会话归档,留给你发挥。

我喜欢这个玩法的原因:它完全没改宿主行为,只是”AI 写文件 + 状态栏读文件”两个独立动作的组合,却把状态栏的信息上限从”宿主知道什么”拉高到了”AI 打算干什么”。组合简单、收益不小,很 Unix。

7. 多终端接入:claude-code、CodeBuddy、Cursor、Codex

7.1 一套协议,多处开花

终端配置文件配置方式兼容情况
Claude Code~/.claude/settings.jsonstatusLine{type,command,padding,refreshInterval}本尊,字段最全
CodeBuddy Code~/.codebuddy/settings.json同款 statusLine实测 v2.32.0 原生支持,同一套脚本直接用
Cursor CLI~/.cursor/cli-config.jsonstatusLine{type,command,padding}stdin JSON 同构,另有 model.max_modevim.modeworktree.name 等私有字段
Codex CLI~/.codex/config.toml无 statusline 配置项截至本文(2026-09)无等价机制,以官方仓库为准

对 CodeBuddy 的”实测”说明方法:它是 Claude Code 的同源衍生品,我在它的安装包里确认了 statusLine 是合法配置键、状态栏渲染组件处理 ANSI 文本的实现存在,然后把同一份脚本挂进 ~/.codebuddy/settings.json 就能工作。

7.2 一份脚本,处处接入

三家的 stdin JSON 高度同构,工具箱那份完整脚本几乎不用改就能三处通用:

1
2
3
4
5
# 1. 拷脚本(各终端共用一份,维护一处)
cp ~/.claude/statusline.sh ~/.codebuddy/statusline.sh

# 2. CodeBuddy:settings.json 顶层加 statusLine 字段,command 指向脚本
# 3. Cursor:cli-config.json 里加同款 statusLine 配置

写通用脚本记住一条:所有字段取值带 // 兜底,任何一家缺字段,对应段位静默消失,不会报错。我移植的真实过程更省事——直接在会话里说”打印 statusline 的配置,我需要移植到别的环境中”,AI 把配置原样吐出来,贴到另一个环境就完事了。

Codex 目前没有等价机制,兜底思路是”宿主不给仪表盘,就让终端给”:tmux statusbar(配 #(shell命令) 能跑脚本)或 zsh precmd 钩子。但这两个方案覆盖不了”模型是谁、上下文剩多少”——这些数据只有宿主里有,别硬凑,等官方。

总结

回头看:状态栏本身只有一行 ANSI 文本,脚本不到一百行,但它把”状态可见性”这条最基本的交互原则补上了,而且补得很便宜——本地渲染、零 token、纯观察者、不碰对话。

我的四版演进,前两版是做减法(v1 进度条费地方、v2 token 数和百分比冗余),第四版是开外挂(读 transcript 统计、让 AI 写进度)。减法靠判断力,外挂靠想象力,两样都不贵。沿途踩的坑(bash 3.2 吞字节、tee 自截断)单独看都挺阴,记下来就变成了别人的路标。

状态栏的信息三层——宿主给稳态、AI 给意图、流水给过程——还能继续往前走:hook 按会话归档状态文件、按任务类型动态决定盯哪些指标。这些我还没做,做了再写。

如果你也天天泡在终端里和 AI 结对编程,建议今天就花十分钟把 5.2 节那个最小版挂上,先用起来,再慢慢长成你自己的样子。

参考资料


版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文出处:jiayq的博客

本文由作者按照 CC BY 4.0 进行授权