# statusline

[English](README.md) | 中文

[npm](https://www.npmjs.com/package/@reedchan/statusline) · [pi packages 画廊](https://pi.dev/packages/@reedchan/statusline) · [仓库](https://github.com/reedchan7/useful-pi-extensions)

替换 pi 的 footer，改成带文字标签的两行。每个值都带一个词，不需要靠符号猜、也不需要记图例。

```
Context  █████████▍░░░░░░░░░░  47.1%   471k / 1.0M                   Input 194k  ·  Output 89k  ·  Cache hit 99.9%  ·  Cost $0.229
~/.pi (master)                                                               deepseek-flash · Effort high · TTFT 482ms · 729 tok/s
LSP Active: typescript
```

终端变窄时，这一行是**按固定顺序**逐项舍弃的，而不是整体消失：先丢 `471k / 1.0M`，
再丢输入/输出量，最后保留命中率与花费。窄于约 60 列时就只剩仪表了。
每一步丢的都是完整的一项，绝不出现被截掉一半的数字。

## 每一项是什么

| 部分                       | 含义                                                                                                                                    |
| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `Context` + 仪表 + `47.1%` | 已占模型上下文窗口的比例。填充超过 70% 转 `warning`、超过 90% 转 `error`（与 pi 原生 footer 同一套阈值），百分比同步变色                |
| `471k / 1.0M`              | 上下文已用 token / 窗口大小。终端变窄时**第一个被舍弃**                                                                                 |
| `Input` / `Output`         | 会话的输入与输出 token 量。`Input` 指既没命中缓存、也没写入缓存的那部分 prompt token，因为 pi 把这两种情况在同一个 `usage` 对象里分开报 |
| `Cache hit`                | 最近一轮的缓存命中率，`cacheRead / (input + cacheRead + cacheWrite)`                                                                    |
| `Cost`                     | 会话花费，默认美元；配置了其他币种则换算显示（见下）                                                                                    |
| Effort                     | 当前思考等级                                                                                                                            |
| `TTFT`                     | 从发出请求到第一个流式 token 的耗时                                                                                                     |
| `tok/s`                    | 解码吞吐，即每秒解码时间产出的输出 token 数                                                                                             |
| 最后一行                   | 其他扩展通过 `ctx.ui.setStatus()` 设的状态，否则它们会静默消失                                                                          |

## 关于这个仪表

仪表**固定 20 格，永不拉伸到终端宽度**。这是从 [cli-progress](https://www.npmjs.com/package/cli-progress)
的 `shades_classic` 预设（约 1000 万次/周下载）里抄来的唯一一条结构性规则：它的进度条有固定 `barsize`，
永远不会被撑满整行。之前的版本正是撑满了整行，于是它读起来像"装饰线"而不是"仪表"。

字形也来自那个预设（实心块填充 + 阴影块轨道），额外加了 1/8 格收尾（`▏▎▍▌▋▊▉`），
让填充平滑增长，而不是一次跳一整格。

可调项在 [`render.ts`](render.ts) 顶部：

| 常量           | 默认值  | 说明                                                    |
| -------------- | ------- | ------------------------------------------------------- |
| `BAR_CELLS`    | `20`    | 仪表宽度。想更安静就调窄，想要更高分辨率就调宽          |
| `BAR_FILL`     | `█`     | 填充字形                                                |
| `BAR_TRACK`    | `░`     | 设为 `""` 得到无轨道仪表，设为 `"─"` 得到细线轨道       |
| `QUIET_STATUS` | pi-lens | `[状态键, 正则]` 列表，匹配到的文本视为"无事发生"而隐藏 |

## 币种

pi 里所有模型价格都是美元，而且它的 `cost` 字段**不带任何单位**，所以 footer 无从知道你实际是按什么币种付的。
这次会话折合多少人民币，取决于卖你额度那家给的汇率，而不是市场汇率——所以汇率是**配置**出来的，不是联网抓的。

创建 `~/.pi/agent/statusline.json`：

```json
{
  "currency": {
    "code": "CNY",
    "perUsd": 7.12
  }
}
```

- `code` 决定符号（`CNY` 与 `RMB` 得到 `¥`；符号表还认 `USD`、`EUR`、`GBP`、`JPY`、`HKD`、`TWD`、
  `SGD`、`KRW`、`INR`）。表里没有的代码就原样打印。
- `symbol` 可覆盖符号表，用于表里没列的币种，或想换分隔方式。
- `perUsd` 是一美元等于多少该币种，也就是**你实际被收的汇率**。必须是正数；
  否则整块被拒绝，footer 继续显示美元。

配置成上面这样，同一个会话就从 `Cost $0.229` 变成 `Cost ¥1.63`。该文件每个会话只读一次，
改完需 `/reload`。文件存在但不可用时会给出通知，而不是静默继续显示美元。

## 指标口径

吞吐与延迟遵循 [deepseek-harness](https://github.com/deepseek-ai/deepseek-harness) 的 turn-metrics 契约
（`packages/client/ui-chat/src/client/contract/turn-metrics.ts`），数字格式也照抄：

```
ttftMs   = firstTokenTime - stepStartTime
decodeMs = completedTime  - firstTokenTime
tok/s    = usage.output / (decodeMs / 1000)
```

所以速率**不含 prefill 和首 token 延迟**，这是业界标准的"输出 token/秒"。

- **回合结束后是精确值**：用 provider 上报的 `usage.output` 除以实测解码窗口。
- **流式期间是估算值**，前面带 `~`。provider 只在最后一个流式分片才回 usage，所以流式期间的 token 数
  由已流出字符数乘以"每字符 token 比"推断，该比值从本会话自己上报的 usage 学到。
- **TTFT 在首个 token 到达时就精确**，那一轮内不再变化。
- 读数在会话中**第一轮结束后**才出现，因为它只能来自流式事件。

## 要求与限制

- footer 只能有一个扩展占用。`ctx.ui.setFooter()` 是排他的，所以再装一个 footer 扩展是**竞争**而不是叠加。
  本扩展在检测到 [pi-fancy-footer](https://github.com/mavam/pi-fancy-footer) 时会主动让位，两者不会互相打架。
- footer 只读取 `ctx.sessionManager.getEntries()` 和 `ctx.getContextUsage()`，都是公开 API，
  没有触碰 pi 内部实现。
- `MCP: 2 servers enabled` 这类信息性状态可以在源头关掉：`~/.pi/agent/mcp.json` 的
  `settings.mcpFooterStatus`。pi-lens 的 `LSP Inactive` 没有类似开关，所以在本地按"安静状态"过滤。

## 安装

```sh
# 单独安装
pi install npm:@reedchan/statusline

# 或作为合集的一部分安装，会装上本仓库的全部扩展
pi install npm:useful-pi-extensions
```

然后 `/reload`。
