# @snailuu/pi-token-use

[![npm](https://img.shields.io/npm/v/@snailuu/pi-token-use)](https://www.npmjs.com/package/@snailuu/pi-token-use)

[pi](https://github.com/badlogic/pi-mono) 扩展：**交互式**浏览本机 token 用量与花费，按时间 / 项目 / 模型多维钻取。

pi 自带的 `/session` 只能看当前会话。本扩展扫描本机全部历史会话，用一个可导航的浮层面板回答「哪个项目最花钱」「某段时间内各模型分别用了多少」「这次暴增是哪次对话干的」。

金额默认自动算出：单价优先取 pi 自己维护的模型目录，你也可以在配置文件里为自建中转等渠道指定实际单价。

## 安装

```bash
pi install npm:@snailuu/pi-token-use
```

本地开发：

```bash
pi install /path/to/pi-token-use-npm
```

## 使用

```text
/token-use                        打开面板（默认全部时间）
/token-use today                  只看今天
/token-use 7d | 30d | all         预设时间窗
/token-use 2026-07-01..2026-07-24 自定义日期区间（闭区间，本地时区）
```

面板占满整个终端。顶部三行（时间 / 分组 / 排序）和下方表格都是可聚焦区域：

| 键 | 作用 |
|---|---|
| `↑` `↓` | 在表格内移动行；到达表格顶端后继续 `↑` 依次进入 排序 → 分组 → 时间 |
| `←` `→` | 在筛选行上：切换该行的选项，立即生效<br>在表格上：展开 / 收起当前行 |
| `Enter` | 在筛选行上：回到表格<br>在表格上：展开 / 收起 |
| `Tab` | 快捷切换分组（不必先把焦点移上去） |
| `t` | 快捷切换时间窗 |
| `s` | 快捷切换排序列 |
| `p` | 在用量视图和定价视图之间切换 |
| `e` | （定价页）就地编辑当前渠道的单价 |
| `d` | （定价页）清除手工价，回退自动匹配 |
| `r` | （定价页）重读配置文件，同步外部改动 |
| `Esc` | 关闭 |

面板长这样（`▸` 标记当前聚焦的筛选行，`▶` 标记表格光标）：

```text
╭────────────────────────────────────────────────────────────────────────────╮
│ pi token 用量  [用量] 定价  (p 切换)                                       │
│  时间:  今天  7天  30天 [全部]                                             │
│  分组: [项目] 模型                                                         │
│  排序: [金额] input  output  cacheRead  命中率                             │
├────────────────────────────────────────────────────────────────────────────┤
│ 项目                            input    output cacheRead  命中率      金额│
│ 合计                            8.67M      356K    123.3M   93.4%     $7.11│
│ ▾ team/web-app ●                8.16M      310K    112.4M   93.2%     $6.86│
│▶  ▾ provider-a/model-pro        7.90M      301K    110.1M   93.3%     $4.18│
│       03-14 14:07 · 补结...     4.80M      213K     65.9M   93.2%     $2.56│
│       03-14 14:07 · 重构...     3.10M       88K     44.2M   93.4%     $1.62│
│   ▸ my-relay/model-mini          264K      9.1K     2.30M   89.7%     $2.67│
│ ▸ team/api-server                415K       38K     9.80M   95.9%     $0.26│
├────────────────────────────────────────────────────────────────────────────┤
│ 1 个渠道借用了同名模型的官方价，金额仅供参考：my-relay/model-mini          │
│ 1 个渠道无单价，其 token 未计入金额：provider-x/model-unknown              │
│ ↑↓ 移动 · ←→ 切换/展开 · Tab 分组 · t 时间 · s 排序 · p 定价 · Esc 关闭    │
╰────────────────────────────────────────────────────────────────────────────╯
```

`●` 标记当前所在项目。**合计行固定在表头下方**，不参与排序也不随滚动。价格来源可疑的渠道**不在行内打标记**，只在底部集中说明。

按 `p` 进入定价页，看每个渠道实际生效的单价及其出处，并直接改价：

```text
╭────────────────────────────────────────────────────────────────────────────╮
│ pi token 用量   用量 [定价] (p 切换)                                       │
├────────────────────────────────────────────────────────────────────────────┤
│ 渠道                                    input    output    cacheR      来源│
│▶provider-a/model-pro                    $0.44     $0.88    $0.004      目录│
│ my-relay/model-mini                      $2.5       $15     $0.25      借用│
│ provider-x/model-unknown                    —         —         —    无单价│
├────────────────────────────────────────────────────────────────────────────┤
│ 单价单位 $/百万 token · 配置文件 ~/.pi/agent/extensions/pi-token-use/con...│
│ ↑↓ 移动 · e 编辑 · d 清除手工价 · r 重读配置 · p 返回用量 · Esc 关闭       │
╰────────────────────────────────────────────────────────────────────────────╯
```

定价页列出的是**全部历史用到过的渠道**，与时间窗无关——定价是渠道的固有属性。

按 `e` 就地改价，四个字段预填当前生效值，把中转站的数字覆盖上去即可：

```text
│ 渠道                                    input    output    cacheR      来源│
│▶provider-a/model-pro                  [0.44█]      0.88     0.004    编辑中│
│ my-relay/model-mini                      $2.5       $15     $0.25      借用│
├────────────────────────────────────────────────────────────────────────────┤
│ 输入数字 · Tab/←→ 切字段 · Enter 保存 · Esc 取消编辑                       │
╰────────────────────────────────────────────────────────────────────────────╯
```

- 只接受数字和小数点，其他按键忽略
- `Enter` 写进配置文件的 `pricing` 段并**立即重算金额**，切回用量页就是新数字
- `Esc` 只取消编辑，不关闭面板
- `d` 清除该渠道的手工价，回退到自动匹配
- 编辑本身带阶梯定价的渠道时，底部会提示**保存后阶梯定价将失效**（手工价是整条替换的）

## 为什么四类 token 要分开看

因为它们的单价差着两个数量级。cacheRead（复用的 prompt 前缀）在某些模型上只有 input 的 1/120。一台机器上 90%+ 的 token 是 cacheRead 很常见——此时「总 token」这个数字几乎不携带信息，排行榜会完全被缓存复读主导。

所以本扩展**不提供「总计」列**，默认按 `input` 排序，并单独给出命中率。

## 定价配置

金额默认全自动，多数情况下不用配。只有当某个渠道的实际价格和官方价不同（自建中转常有折扣或加价），才需要改。

改单个渠道直接在定价页按 `e`；要批量导入整份价格表，就直接写配置文件——两者改的是同一份数据。

配置文件：`~/.pi/agent/extensions/pi-token-use/config.json`，**第一次打开面板时自动生成**，分两段：

```json
{
  "_note": "catalog 段由插件自动同步为 pi 的官方定价，直接修改它不会生效；要覆盖某个渠道的价格，请写到 pricing 段。",
  "catalog": {
    "provider-a/model-pro": { "input": 0.44, "output": 0.88, "cacheRead": 0.004, "cacheWrite": 0 },
    "my-relay/model-mini": { "input": 2.5, "output": 15, "cacheRead": 0.25, "cacheWrite": 0 }
  },
  "pricing": {
    "my-relay/model-mini": { "input": 3.5, "output": 21, "cacheRead": 0.35, "cacheWrite": 0 },
    "my-relay/*": { "input": 2, "output": 10, "cacheRead": 0.2, "cacheWrite": 0 }
  }
}
```

| 段 | 谁写 | 作用 |
|---|---|---|
| `catalog` | **插件自动写**，每次打开面板同步为 pi 最新官方价 | 只供查阅和复制，改它无效；官方降价会自动反映在这里 |
| `pricing` | **你或 AI 写**，插件除了 `e` 保存外绝不碰 | 实际生效的覆盖价，优先级最高 |

要改价，就把 `catalog` 里那一条复制到 `pricing` 再改数字。

`catalog` 段只列四个基础单价，不写阶梯规则（避免文件太长）。但**计价时仍会用官方阶梯**——某些模型单次请求 prompt 超过阈值后单价会翻倍，这个规则直接来自 pi 的模型目录。一旦你在 `pricing` 里覆盖了某渠道，它就按你给的单一价计算，不再有阶梯。

- key 是 `provider/model`；`provider/*` 作为该渠道的兜底默认值
- 单价单位是 **每百万 token 美元**，与 pi 自带定价表一致
- 省略的字段按 `0` 计
- 解析不出官方价的渠道**不会**写进 `catalog`——写成 0 等于把「未知」伪装成「免费」
- 阶梯定价写 `"tiers": [{ "inputTokensAbove": 272000, "input": 10, "output": 45, "cacheRead": 1 }]`

改完文件重新打开面板即生效，不需要重启 pi；面板正开着的话，在定价页按 `r` 就能重读。文件是纯 JSON，方便让 AI 读了中转站价格表后批量写入 `pricing` 段，不必一条条手填。

在定价页按 `e` 保存时，**只会改动那一条**，文件里其他内容（AI 批量写入的整份价格表、其他配置段）原样保留。如果文件当前是坏 JSON，保存会被拒绝并提示——宁可不写，也不能把你原有的内容冲掉。

配置写错不会被静默忽略——用量页底部会提示有几处问题，按 `p` 看具体是哪一条哪个字段。

### 单价从哪来

按优先级取第一个命中的：

| 优先级 | 来源 | 面板显示 |
|---|---|---|
| 1 | 配置文件里 `provider/model` 精确匹配 | 手工配置 |
| 2 | 配置文件里 `provider/*` 通配 | 手工通配 |
| 3 | pi 模型目录中同 provider 同 model | pi 定价表 |
| 4 | pi 模型目录中**其他 provider** 下的同名 model | 借用同名模型 |

第 4 种是推测——它假定你的中转按被借用方的官方价计费。这个假设未必成立，所以面板底部会明确列出哪些渠道用的是借来的价。想让数字变准，就在配置里给这些渠道填上真实单价。

四种都不命中时该渠道**没有金额**，显示 `—` 而不是 `$0`：0 表示免费，`—` 表示不知道，两者不能混。

## 关于金额的算法

金额是**逐条记录算完再相加**的，不是把 token 汇总后乘单价。

因为阶梯定价按**单次请求**的 prompt 量判定档位（且 cacheRead 计入该判定）。若先汇总，总量必然落进最高档，金额会被系统性抬高——在实测数据上这个差距达到 **71%**。

## 工作方式

数据源是 `~/.pi/agent/sessions/` 下的会话 JSONL，扩展只读不写，不 hook 模型调用，也不建任何缓存或数据库——每次打开面板全量重扫（数十个会话、数千条记录的规模下约 100ms），所以看到的永远是最新数据，包括你刚刚这轮对话产生的用量。

所有数据都留在本地，不发往任何外部服务。

术语定义见 [CONTEXT.md](./CONTEXT.md)。

## 开发

```bash
pnpm install
pnpm test        # vitest，覆盖解析与聚合纯函数
pnpm typecheck
```

## License

MIT
