@evan7der/pi-monitor1.2.1
pi usage ledger and local browser dashboard (tokens, CNY cost, heatmap). Zero runtime dependencies.
Untested on omp
This is a pi package. No one has reported whether it works on omp yet. If you try it, add your result below.
Reports 0
No reports yet.
Readme
pi-monitor
跨会话、跨天的 pi 用量账本 + 本地浏览器仪表盘:token 用量、人民币成本、每日热力图、多维分解、预算提醒。
- 唯一入口:在 pi / pi-web 里输入
/tokens,浏览器随即打开仪表盘。本插件不提供 CLI、不在终端里打印报表。 - 零第三方运行时依赖:只用 Node 内置模块;
.ts源码由 pi 的 jiti 直接加载,无构建步骤。 - 数据全在本机:
~/.pi/agent/pi-monitor/,无遥测。唯一可选的出站请求是汇率查询(可在配置里关闭)。 - 货币:账本里金额恒为美元(pi 的事实口径),展示层按汇率换算为人民币
¥;汇率可手动设置,也可自动联网获取(默认开启,12 小时最多取一次)。
目录
安装
发行名是 npm 包 @evan7der/pi-monitor(与仓库/插件名一致)。用 scoped 名是因为 npm 上不带 scope 的 pi-monitor
已被另一位作者占用(macOS 后台进程扩展),而不是本插件。扩展在会话创建时加载,因此任何方式安装后都要重启 pi-web 进程或重开会话。
一、pi CLI + npm(推荐)
pi install npm:@evan7der/pi-monitor # 最新版
pi install npm:@evan7der/pi-monitor@1.2.1 # 锁定版本
pi update --extensions # 升级(含本包在内的全部包)
pi remove npm:@evan7der/pi-monitor # 卸载
二、pi-web「插件」面板
插件 → 添加包 → 填 npm:@evan7der/pi-monitor(面板同样支持 git 源与本地路径)。
三、git 源(不想经 npm,或想跟 main)
pi install git:github.com/Nana7mi-swjtu/pi-monitor
四、本地路径(开发 / 离线)
# 把 <本仓库路径> 换成本仓库在你机器上的实际目录(例如克隆后的目录)
pi install <本仓库路径> # 绝对路径,也接受 ./相对路径
pi --no-extensions -e <本仓库路径>/extensions/pi-monitor/index.ts # 临时试用,不改配置
注意:npm 上不带 scope 的
pi-monitor是别人的包(macOS 后台进程扩展),不要用npm:pi-monitor。
五、只用 npm 手工安装(等价于方式一)
pi 的 npm 包都落在 <agentDir>/npm/(默认 ~/.pi/agent/npm/),它内部执行的就是下面这条命令
(--legacy-peer-deps 与 pi 一致,宿主包由 pi 自身提供):
npm install @evan7der/pi-monitor --prefix "$env:USERPROFILE\.pi\agent\npm" --legacy-peer-deps
再把包名写进 ~/.pi/agent/settings.json:
{ "packages": ["npm:@evan7der/pi-monitor"] }
安装后重启 pi-web 进程或重开会话(扩展在会话创建时加载)。
卸载:pi remove npm:@evan7der/pi-monitor(本地路径安装则 pi remove <路径>)。
数据目录不会自动删除,手动删除 ~/.pi/agent/pi-monitor/ 即可清空统计(重装后从零开始)。
使用
/tokens # 启动/复用仪表盘并自动打开浏览器
/tokens --no-open # 只启动/复用服务并给出 URL,不开浏览器
/tokens --port 8090 # 指定首选端口(1024..65535)
- 命令本身不打印任何统计内容,只给出一行 URL(可选再发一条 ≤ 300 字符的链接卡片)。
- 服务默认绑定
127.0.0.1:30142;端口被占用时顺延最多 18 个端口,再回退系统空闲端口。 - 同一进程内重复执行
/tokens会复用现有服务(端口、进程、token 不变)。 session_shutdown时按dashboard.stopOnExit(默认true)关闭服务。
仪表盘
| 区块 | 内容 |
|---|---|
| 页头 | 窗口选择(今天/昨天/近 7 天/近 30 天/本周/本月/全部/自定义)、语言、主题、刷新(重新扫描 + 重载)、revision、汇率行(含来源) |
| 概览卡 | 计费 Token / 输入 / 输出 / 缓存读 / 缓存写 / 真实成本 / 估算成本 / 消息数 / 活跃天数 / 会话数,每张带环比 |
| 热力图 | 固定按计费 Token 统计;53 周 × 7 天(随 weekStart 对齐),上方月份标签行、左侧星期标签列(每隔一行);年份选项卡(最近一年 / 各自然年);格子可聚焦并带 aria-label |
| 每日趋势 | CSS 柱状图 + 可展开每日表格;列宽随天数与容器宽自适应,超出时横向滚动 |
| 分解表 | 模型 / Provider / 项目 / 会话 / 来源 / 类型,含占比 |
| 预算进度条 | 日/月各一条,超额变红并显示超支金额 |
| 操作区 | 重新扫描、重建索引(需输入 REBUILD)、导出 Markdown / JSON / CSV |
| 设置抽屉 | 汇率(手动值 + 自动获取开关 + 「立即更新」)、语言、主题、预算、页面自动刷新开关、局域网开关(部分键只读) |
| 自动刷新 | 页面打开期间每 30 秒增量扫描 + 重载;切回标签页时立即补一次;可由 dashboard.autoRefresh 关闭 |
| 健康面板 | 文件数、记录数、损坏行、无效会话、去重跳过、一致性不符、损坏成本、未知配置键、时区一致性、汇率来源与更新时间、上次扫描耗时、索引大小、数据目录(脱敏) |
导出的 Markdown / JSON / CSV 都在浏览器侧生成(Blob + URL.createObjectURL),不会写入服务器磁盘。
让 agent 用自然语言回答用量问题
插件注册了一个可关闭的工具 token_stats。你可以直接问:
我今天用了多少 token?这个月哪个模型最贵?
工具返回一句话 + 最多 5 行要点(≤ 1 KiB)与结构化 JSON(人民币金额 + 汇率字段)。
配置
~/.pi/agent/pi-monitor/config.json(受 PI_CODING_AGENT_DIR 影响)。文件缺失时使用默认值,不会自动创建;
只有在仪表盘里保存或手工编辑后才会写入。
| 键 | 默认 | 说明 |
|---|---|---|
locale |
"auto" |
zh-CN / en-US / auto(→ PI_MONITOR_LOCALE → LANG/LC_ALL → en-US) |
timezone |
"local" |
local / utc / IANA 名称(如 Asia/Shanghai) |
weekStart |
"monday" |
monday / sunday |
extraSessionDirs |
[] |
额外会话根目录 |
dedupe |
"fingerprint" |
fingerprint / off(关闭时仪表盘显示警告) |
ephemeralCapture |
true |
临时会话(无文件)的用量在会话结束时落盘 |
defaultWindow |
"last7d" |
仪表盘默认窗口 |
tableLimit |
20 |
分解表行数(1..200) |
tool.enabled |
true |
是否注册 token_stats(false 时不注册) |
currency.code |
"CNY" |
恒定 |
currency.rate |
7.20 |
USD → CNY(0.01..100.00,2 位小数);手动设置的值 |
currency.autoRate |
true |
是否允许联网自动获取汇率;false = 零出站请求(手动「立即更新」仍可用) |
currency.rateSource |
"manual" |
manual / auto;由插件维护(只读),页头汇率行据此标注来源 |
currency.rateFetchedAt |
null |
上次自动获取时间(ISO);超过 12 小时视作过期,下次打开页面时重取 |
dashboard.enabled |
true |
是否允许 /tokens 启动服务 |
dashboard.port |
30142 |
首选端口 |
dashboard.portRange |
18 |
顺延尝试次数 |
dashboard.allowLan |
false |
绑定 0.0.0.0(强制 token 鉴权 + 页面警告,改后需重启服务) |
dashboard.stopOnExit |
true |
session_shutdown 时关闭服务 |
dashboard.linkMessage |
true |
/tokens 时是否发一条 ≤ 300 字符的链接卡片 |
dashboard.theme |
"auto" |
auto / light / dark |
dashboard.autoRefresh |
true |
仪表盘页面存活时是否每 30 秒自动重扫 + 重载 |
budget.enabled |
false |
预算提醒总开关 |
budget.dailyCNY / budget.monthlyCNY |
null |
日/月预算(元) |
budget.warnAt |
[0.5, 0.8, 1.0] |
触发阈值(0 < w ≤ 1,升序) |
budget.includeEstimated |
false |
是否把估算成本计入预算 |
budget.injectMessage |
false |
超支时是否额外注入一条消息 |
logging.level |
"error" |
off / error / info / debug |
logging.maxFiles / logging.maxBytes |
7 / 5242880 |
日志轮转保留份数与单文件上限 |
仪表盘可写的键:currency.rate、currency.autoRate、dashboard.theme、dashboard.allowLan、dashboard.autoRefresh、locale、budget.*。
其余键只读(PUT /api/config 写入非白名单键返回 403)。未知键会被保留,不会被删除。
环境变量
| 变量 | 作用 |
|---|---|
PI_CODING_AGENT_DIR |
配置与数据目录的根(默认 ~/.pi/agent) |
PI_CODING_AGENT_SESSION_DIR |
额外的会话根目录 |
PI_MONITOR_LOCALE |
语言(zh-CN / en-US) |
统计口径
数据源是 pi 的会话文件(<agentDir>/sessions/**/*.jsonl),以下是几个容易误解的点:
- 计费 Token =
input + output + cacheRead + cacheWrite。reasoning已包含在output中,不重复累加。 - 计入用量的 entry:
assistant消息、toolResult消息(工具内的嵌套 LLM 调用)、compaction、branch_summary。compaction.retainedTail[].usage是被保留的历史消息副本,不计数(否则会虚增)。 - 去重:
/clone、pi --fork、跨目录--session会把源会话 entry 原样复制到新文件, 因此按sha1(entryId|timestamp|provider|model|四个分量)前 16 位去重,首见者胜(按时间升序,再按文件路径字典序)。 - 成本:优先用 pi 记录的
usage.cost.total(真实成本);缺失或为 0 时按~/.pi/agent/models.json的定价估算(估算成本)。两者分开显示,绝不合并;都不知道时显示—。 - 货币:账本与 HTTP API 里的金额字段一律是美元;
¥金额 = 美元合计 × 汇率(先合计再换算,不逐条换算)。 - 汇率:
currency.autoRate开启时(默认),插件在/tokens、打开仪表盘或点击「立即更新」时按需联网取值, 结果写回currency.rate并标注currency.rateSource: "auto";12 小时内不重复请求。 取值失败只降级(保留上一次的汇率并在页面上提示),绝不阻塞仪表盘。关闭后零出站请求(NFR-7)。 - 临时会话:
--no-session等无会话文件的会话不落盘,永久统计不可得;开启ephemeralCapture(默认)时会在会话结束时把 usage 写入账本并标记ephemeral: true。 - 没有「本会话(实时)」卡:它需要一份与账本竞争的内存计数,而该计数只属于单个进程 / 最后启动的会话, 在多会话与多进程(pi CLI + pi-web)下会长期显示 0;当天用量请看概览卡(页面默认每 30 秒自动重扫)。
- 时间:以消息级
message.timestamp定位日界;缺失时回退行级时间并在健康面板标记。 - 日界:默认时区为系统本地时区,可配置为
utc或任意 IANA 名称;“今天/本周/本月”均按该时区计算。 - 图表口径:热力图与每日趋势固定使用计费 Token(不提供指标切换);成本与消息数看概览卡、分解表与导出。
- 热力图网格:“最近一年” = 统计末日所在周为末列、向前 53 周(统计范围就是这 53 周本身,范围内的空白日按 0 上色); “年份” = 该自然年 1/1~12/31 对齐到整周(首尾多余日为无背景补齐格)。网格区间由服务端计算下发,前端不自行推导周对齐。
数据目录
~/.pi/agent/pi-monitor/
├─ config.json # 用户配置(可缺失)
├─ cursor.json # 每文件增量游标
├─ ledger.jsonl # 账本:一条 usage 一行(去重后)
├─ meta.json # schemaVersion / revision / 统计计数
├─ budget-state.json # 预算提醒状态
├─ logs/ # 诊断日志(轮转)
└─ *.bak-<ts> # 重建或修复时的备份
FAQ
Q:为什么 /tokens 第二次执行没有新开浏览器标签?
按 AC-6.2,服务复用时只打开一次浏览器。第二次执行会在提示里给出完整 URL(含 token),可直接粘贴打开。
Q:金额对不上账单?
本插件只做展示层换算,不承诺与供应商账单一致。汇率可手动设置,也可自动联网获取(currency.autoRate,默认开启,12 小时最多取一次)。
Q:自动汇率会联网请求什么?
只在需要时向公开汇率接口发一个 GET(按序尝试 open.er-api.com → api.frankfurter.dev → api.exchangerate-api.com),
不携带任何本地信息;三个接口都失败就保留上次的值并提示。设 currency.autoRate: false 后完全不出站(可零出站运行)。
Q:为什么点「刷新」之前数据没变? 刷新会先做一次增量扫描再重载。页面打开期间默认每 30 秒也会自动扫描 + 重载,切回标签页时会立即补一次; 若同时开着 pi CLI 与 pi-web,两边的索引会互相同步(账本变化会被另一进程重新载入)。
Q:页面提示「索引版本高于当前版本」?
说明 meta.json 的 schemaVersion 比当前插件更新(例如你降级了插件)。此时插件只读运行,不会写入任何索引文件;
升级插件或点击「重建索引」即可恢复。
Q:换了时区后日归属不对? 聚合按当前时区重算日键,页面会提示「时区已变更」。点「重建索引」可把日键持久化重写一遍。
Q:装了插件但 /tokens 不出现?
扩展在会话创建时加载。重启 pi-web 进程或重开会话;也确认 tool.enabled / dashboard.enabled 没有被设为 false。
Q:data 目录可以删吗?
可以。删掉后下次 /tokens 会重新扫描会话日志重建索引(会丢失预算提醒状态与诊断计数)。
Q:pi install npm:pi-monitor 装不到本插件?
因为 npm 上不带 scope 的 pi-monitor 属于另一位作者;本插件的 npm 包名是 @evan7der/pi-monitor,请用 pi install npm:@evan7der/pi-monitor。
Q:仓库里的 node_modules/ 是什么?
仅用于 npm run typecheck 的开发期类型检查(devDependencies)。运行时依赖为空(dependencies: {}),
纯逻辑测试不需要 node_modules。删掉它不影响插件运行。
项目结构
pi-monitor/
├─ package.json # pi manifest;dependencies 为空;files 白名单 = extensions/ + src/
├─ README.md / LICENSE
├─ extensions/pi-monitor/index.ts # 唯一扩展入口:/tokens、token_stats、事件
├─ src/
│ ├─ scanner.ts # 编排:发现 → 解析 → 去重 → 落账 → 更新 meta
│ ├─ parser.ts / dedupe.ts / ledger.ts / discover.ts
│ ├─ aggregate.ts / money.ts / time.ts / cost.ts / format.ts / budget.ts
│ ├─ config.ts / paths.ts / pricing.ts / args.ts / i18n.ts / health.ts / opener.ts / rates.ts
│ ├─ types.ts # 全部类型定义(账本 / 聚合 schema)
│ └─ dashboard/ # HTTP 服务、API 适配、内联单页应用
├─ test/
│ ├─ unit/ integration/ contract/ # 单元 / 集成 / 契约测试
│ ├─ fixtures/ # 会话 fixture + 导出 JSON Schema + 生成器
│ ├─ golden/ # 人工校对过的仪表盘文案期望值
│ └─ bench/ # 性能基准(500 MiB 语料)
└─ tools/quality-gate.mjs # 静态约束元测试(禁止“为过测试而实现”)
依赖方向(由质量门强制):extensions/** 与 src/dashboard/** 可以依赖 src/**,
但 src/** 不得 import @earendil-works/* —— 核心逻辑必须能用纯 Node 测试。
开发
npm install --include=dev # 仅类型检查需要(npm 可能默认 omit=dev)
npm run check # 质量门 + tsc --noEmit + 全部测试
npm run test # 单元 + 集成 + 契约测试
npm run typecheck # tsc --noEmit
npm run bench # 性能基准(生成 500 MiB 语料,约 25 s)
npm run gate # 静态约束元测试
npm run pack:dry # 查看 npm 打包会包含哪些文件(不落盘、不跑脚本)
npm run fixtures # 重新生成 test/fixtures/sessions/ 下的会话 fixture
约定
src/**禁止 import 任何@earendil-works/*宿主包(保证核心逻辑可用纯 Node 测试)。- 出站网络只允许来自
src/rates.ts的汇率查询(主机白名单由npm run gate强制),且必须受currency.autoRate把关。 aggregate.ts/money.ts/time.ts/cost.ts/format.ts/args.ts/dedupe.ts/i18n.ts/budget.ts必须是纯函数(无 IO)。- 账本记录的字段集合是封闭的:新增字段属于破坏性变更,需要同时改读写两侧与 fixture 断言。
- 不要为了让测试通过而放宽断言或抽掉真实逻辑;
npm run gate会拦住这类改动。 test/fixtures/sessions/**必须保持字节精确(CRLF / BOM / 无尾换行),已在.gitattributes中标记为-text。
提交信息
<type>(FR-x): <描述>
type 取 feat / fix / docs / test / chore / refactor;正文里带上对应的 FR/AC 编号,便于追溯。
License
MIT © 2026 KaaNoo