8000 feat(accounts): 支持 Codex 本地采集与账户额度、用量合并 by thsrite · Pull Request #27 · InfinityPacer/meterleaf · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

AI 用量账本,账户额度、Token 消耗、费用估算与多维统计。

Meterleaf 可以只读采集一个 Sub2API PostgreSQL 实例,也可以接收采集器推送的 Claude Code 本地直连用量和 CLIProxyAPI(CPA)插件账本。各来源可单独或同时使ç 8000 ”¨,用量保存到本地 SQLite 并生成独立报表。不代理模型请求、不修改上游,也不直接请求 OpenAI 或 Anthropic;账本服务单个容器即可运行,CPA 接入另启一个同步容器。
Meterleaf 可以只读采集一个 Sub2API PostgreSQL 实例,也可以接收采集器推送的 Claude Code、Codex 本地用量和 CLIProxyAPI(CPA)插件账本。各来源可单独或同时使用,用量保存到本地 SQLite 并生成独立报表。不代理模型请求、不修改上游,也不直接请求 OpenAI 或 Anthropic;账本服务单个容器即可运行,CPA 接入另启一个同步容器。

## 能做什么

- **用量总览**:默认看历史至今的 Tokens、费用、请求和缓存命中率,可切到今天、近 7 天等范围;下方是同一范围的消耗趋势和模型分布。
- **账户额度**:总览中按当前周期显示 5 小时与 7 天窗口、重置时间及七天额度预估,并可直接排序、重命名和归档账户。
- **账户额度**:总览中按当前周期显示 5 小时与 7 天窗口、重置时间及七天额度预估;支持排序、重命名、归档,以及将多个来源账户的额度与用量合并到目标账户,随时可解除。
- **统计报表**:顶部是所选范围的摘要与环比,按小时、天、周、模型或账户汇总 Tokens、缓存命中率、请求数和费用,附合计行。
- **请求明细**:查看模型、来源、推理强度、Token 拆分与计价依据。
- **Claude Code 本地用量**:不经过网关的 Claude Code 由 Mac 上的本机采集器推送用量、套餐和 5 小时、7 天及 Fable 周额度,与网关用量在同一账本中统计。
Expand Down Expand Up @@ -55,6 +55,7 @@ Meterleaf 支持以下数据来源,至少配置一种。
- **Sub2API 网关**:在 `.env` 填写 `SUB2API_DATABASE_URL`。数据库账号只需读取 `public.accounts` 和 `public.usage_logs`,不需要写权限。
- **本地直连的 Claude Code**:在 `.env` 填写本机采集器生成的 `METERLEAF_INGEST_KEYS`,见下文[接入 Claude Code](#接入-claude-code)。只用 Claude Code 时把 `SUB2API_DATABASE_URL` 留空,并先生成写入密钥再启动服务。
- **CLIProxyAPI(CPA)**:使用可选同步容器只读接入 usage-report 插件的历史和新增请求,见[接入 CPA](docs/cpa.md)。它统计经过 CPA 的请求,不读取本机 Codex 会话。
- **Codex 本地用量**:独立采集器只读扫描 Codex 会话记录,分批补采历史,同步记录中的套餐与额度快照,支持与 CC 同时运行。安装方式和统计边界见[接入 Codex](docs/codex-collector.md)。

在仓库目录中准备配置:

Expand Down Expand Up @@ -99,6 +100,7 @@ bun run build:collector
- [计价说明](docs/pricing.md):USD 分支、费率版本与自定义 JSON。
- [本机采集器](docs/collector.md):接入本地直连的 Claude Code 用量、账户与额度。
- [接入 CPA](docs/cpa.md):只读同步 CPA 插件账本、按来源查看,以及重复统计和费用估算的边界。
- [Codex 本地采集器](docs/codex-collector.md):接入 Codex 会话用量,与网关按来源分别查看。
- [贡献指南](AGENTS.md):目录结构、代码规范、测试与提交要求。
- [版本与发布](docs/releasing.md):版本递增、双架构镜像及 Release 流程。

Expand Down
64 changes: 64 additions & 0 deletions docs/codex-collector.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# 接入 Codex 本地用量

[返回部署指南](deployment.md)

Codex 本地采集器只读扫描 `$CODEX_HOME/sessions` 和 `$CODEX_HOME/archived_sessions` 下的 JSONL 记录,未设置 `CODEX_HOME` 时使用 `~/.codex`。它读取模型、时间戳和每次请求的 Token 用量,通过独立写入密钥推送到 Meterleaf,不需要请求经过网关。

它不读取 `auth.json`,不改 Codex 的配置、会话文件、登录状态或请求路径,也不调用 OpenAI API。上传内容仅含模型、时间、计量数字和不可逆摘要;对话、工具输出、项目路径、会话原始 ID 和凭据不会上传。

本地 CC、Codex 和网关是独立来源,**不做跨来源去重**。同一请求同时被网关和本机记录时,合并总览可能重复,应按来源或账户分别查看。定时任务重复读取同一份 Codex 记录、会话转入归档或网络重试不会重复入账。

## 安装与连接

从仓库构建,需要 Bun 1.4.2 或更新版本:

```sh
bun install --frozen-lockfile
bun run build:codex-collector
mkdir -p ~/.local/bin
cp dist/meterleaf-codex-collector ~/.local/bin/
~/.local/bin/meterleaf-codex-collector init --server https://meterleaf.example.com --source-id codex-local
```

`--server` 填本机可访问的 Meterleaf 地址。命令打印 `METERLEAF_INGEST_KEYS=codex-local:摘要`,把这一对来源和摘要追加到服务端配置,已有 CC 或其他来源时用英文逗号连接,不要覆盖旧值。然后重建 Meterleaf 容器使配置生效,详见[写入密钥](deployment.md#写入密钥)。完整密钥仅保存在本机配置目录。

每台电脑使用不同的来源名称,已有账本不要更换来源名。`init` 不会覆盖已有配置;更改服务地址可以编辑采集器自己的 `config.json`,不要更改密钥和来源名称。

macOS 上安装后台任务:

```sh
~/.local/bin/meterleaf-codex-collector install-launchd
~/.local/bin/meterleaf-codex-collector status
```

采集器每分钟低优先级运行一次,单轮读取上限为 64 MiB,优先补采最近修改的会话。未完成的历史在后续轮次继续;没有变化的文件不会重读正文。首次历史较多时可能需要数小时,期间可以继续正常使用 Codex。进程结束后不占常驻内存。

此后台任务与 CC 采集器使用不同名称和数据目录,可以同时启用。不安装后台任务时,也可以直接执行 `meterleaf-codex-collector sync` 同步一轮。

## 统计口径

- 使用每次请求的用量,不把累计计数反复相加;重复的额度通知不作为新请求。切换模型以对应的会话上下文为准,不用当前全局配置替历史补模型。
- 缓存读取从输入中扣除,推理量保留为输出的子集;新记录中的缓存写入单独保存。旧记录缺失的字段保持未知,不猜测缓存写入 TTL。
- 从会话记录的额度通知读取订阅等级、5 小时和 7 天额度及重置时间。窗口按实际时长识别,主窗口不一定是 5 小时。仅保留采样时间最新的观察,补采旧历史不会覆盖新套餐或额度;没有新增请求时也可更新这些信息。
- 这些信息是当时的快照,不保证实时。记录没有可靠的历史登录账号归属,因此统一显示在「Codex 本机」账户下,不自动绑定当前登录账号;换号后的快照以最新观察为准。账户可以在 Meterleaf 中改名。其他模型专属额度不会冒充账户通用额度;缺失字段不猜测,过期窗口按服务端规则显示。
- Credits 余额不等于美元余额,目前不写入费用或额度百分比。旧版采集器升级后会按原有 64 MiB 上限重读记录以补齐套餐和额度,保留已上传的用量及发送状态,不重复入账。
- 没有保存用量的请求、已经删除的历史,以及缺少模型或计量异常的记录无法凭空恢复。模型不在 Meterleaf 价格表中时可保留用量,但费用可能未计价。
- 本地 JSONL 是 Codex 的内部持久化格式,并非稳定计量 API;升级 Codex 后应留意采集日志的异常计数。

## 状态、日志与停用

默认数据目录是 `~/Library/Application Support/Meterleaf Codex Collector`,可通过 `METERLEAF_CODEX_HOME` 更改,但不能放进 Codex 目录。后台任务安装时记录当前的两个目录设置,修改路径后需要重新执行 `install-launchd`。

`status` 显示已跟踪文件、事件数、待发送数及最近成功时间。日志在数据目录的 `logs/sync.log`,达到 1 MiB 后保留一份旧日志:

- `deferred: true` 表示本轮达到读取上限,后台会继续补采。
- `invalid` 表示本轮遇到格式或计量异常,其他记录继续处理;`unreadable` 表示存在不可读取的文件。
- 网络或服务端失败时保留待发送记录,下次自动重试,不影响 Codex 使用。

停止后台任务:

```sh
~/.local/bin/meterleaf-codex-collector uninstall-launchd
```

停用不会删除采集器或 Codex 的数据。备份时保留采集器完整数据目录,包括配置、SQLite 主文件及运行时可能存在的 WAL 文件;最好先停止后台任务。恢复服务端账本时,应同时恢复相应时间点的采集器进度,或在保存旧采集器状态后重新补采仍然存在的本地记录。
19 changes: 19 additions & 0 deletions docs/collector.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,25 @@ alias meterleaf-collector="/Applications/Meterleaf.app/Contents/MacOS/meterleaf-

Claude Code 每一轮都会把最新的 5 小时与 7 天额度传给状态栏命令。如果你的状态栏脚本把它写成文件,采集器可以读取这个文件,额度会比 `~/.claude.json` 里的缓存新得多。

没有现成状态栏脚本时,可使用仓库中的 `scripts/claude-statusline.sh`(需要 Bash 与 `jq`)。在 Claude Code 的 `~/.claude/settings.json` 中添加下面一项,将路径改为脚本的实际绝对路径;如果已配置状态栏,请把额度缓存逻辑整合进现有脚本,不要覆盖原命令:

```json
{
"statusLine": {
"type": "command",
"command": "bash /absolute/path/to/meterleaf/scripts/claude-statusline.sh"
}
}
```

脚本在终端显示简短的 5h / 7d 百分比,只把额度和真实重置时间保存到采集器自己的目录,不存储完整状态栏内容,不读凭据、不发网络请求。macOS 上启用默认缓存路径:

```sh
meterleaf-collector statusline-cache "$HOME/Library/Application Support/Meterleaf Collector/statusline-quota.tsv"
```

Claude Code 下一轮提供额度后,等待每分钟同步,Meterleaf 的账户卡片就会显示重置时间(跨天时附日期)。没有提供额度时保留未知,不从首次请求时间推算。非 macOS 或自定义采集器目录时,给状态栏命令设置 `METERLEAF_STATUSLINE_CACHE`,并让采集器读取同一个路径。

文件每行一个窗口,三列用制表符分隔,依次是窗口名 `five_hour` 或 `seven_day`、已用百分比、重置时间的 Unix 秒。例如 `five_hour 18 1790277600`。启用时提供绝对路径:

```sh
Expand Down
4 changes: 3 additions & 1 deletion docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[返回首页](../README.md)

Meterleaf 单容器提供前端与 API,可以只读访问 Sub2API PostgreSQL,也可以接收采集器推送的 Claude Code 用量或 CPA 插件账本。至少配置一种来源,使用本地 SQLite 保存账本。不代理模型请求,不修改上游,也不直接请求 OpenAI 或 Anthropic。
Meterleaf 单容器提供前端与 API,可以只读访问 Sub2API PostgreSQL,也可以接收采集器推送的 Claude Code、Codex 用量或 CPA 插件账本。至少配置一种来源,使用本地 SQLite 保存账本。不代理模型请求,不修改上游,也不直接请求 OpenAI 或 Anthropic。

CLIProxyAPI(CPA)需要额外启动可选同步容器,读取 usage-report 插件保存的账本。安装、密钥追加、来源筛选和重复统计说明见[接入 CPA](cpa.md)。该方式复用 `METERLEAF_INGEST_KEYS`,不需要 Sub2API 数据库。

Expand Down Expand Up @@ -77,6 +77,8 @@ Compose 将容器内数据目录、监听地址和端口固定为 `/app/app_data

## 接入本机采集器

Codex 本地用量使用独立采集器和来源名称,安装与后台任务见[接入 Codex](codex-collector.md)。同样通过下面的写入密钥接入,不需要 Sub2API;与 CC 或网关记录不做跨来源去重。

Claude Code 等本地直连的客户端不经过网关,需要在使用它的电脑上运行本机采集器,由采集器把用量、账户和额度快照推送到 Meterleaf。安装和日常使用见[本机采集器](collector.md)。

采集器可以与 Sub2API 同时使用,也可以单独使用。只用采集器时 `SUB2API_DATABASE_URL` 留空,页面不显示「数据同步」,采集器推送后数据自动出现。
Expand Down
Binary file added docs/images/account-merge-mobile.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/account-merge.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 5 additions & 1 deletion docs/reporting.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,17 @@
账户管理都在总览的账户区完成,手机首页相同。

- 点击标题右侧的「排序」后,每个账户下方出现上移和下移按钮,点「完成」结束。新账户追加在末尾。
- 每个账户右侧的「⋯」菜单提供重命名、归档和删除。
- 每个账户右侧的「⋯」菜单提供合并额度与用量、重命名、归档和删除。
- 同一账号从多个来源采集时,在来源账户菜单选择「合并额度与用量」,选定目标账户后确认。全部历史及后续用量都会归入目标,原来源记录仍可在请求明细查看,不做跨来源去重;若两个来源都记录了同一次请求,会累计两次。同一额度窗口使用最新快照,百分比不会相加,未知或到期状态仍如实显示。目标账户的名称和套餐保留。
- 在目标账户的「合并额度与用量」弹窗查看已合并账户,点击「解除合并」恢复各自展示。原始记录不删除,同步不会覆盖合并关系;连续合并多个账户时,解除某一层也会带回合并到它的账户。合并和解除后报表需要重新计算,数据较多时稍等片刻。
- 归档账户不在账户区显示。有归档账户时,标题右侧出现「已归档 N」,点击后查看归档列表,可在菜单中恢复或删除,点「使用中账户」返回。
- 删除账户只移除账户展示,不会删除历史请求、累计统计,也不影响 Sub2API 或本机采集器。
- 重命名的名称只在 Meterleaf 中显示,最长 40 个字,不会改动上游。清空名称或选择恢复原名后,重新显示上游提供的名称。
- 套餐徽标按使用倍数分三档显示,同一档外观相同。Claude Pro 与 ChatGPT Plus 为一档,Claude Max 5x 与 ChatGPT Pro 5x 为二档,Claude Max 20x 与 ChatGPT Pro 20x 为三档。无法识别的套餐只显示名称。
- 本地归档、删除状态和账户名称不会被同步覆盖。上游删除账户后,本地历史请求仍保留。

![账户合并示例](images/account-merge.png)

## 额度与金额

5 小时和 7 天窗口独立显示,过期窗口不沿用上一周期的百分比和统计。Claude 账户如果上报了 Fable 周额度,会在 7 天窗口下方单独显示一行。它只统计 Fable 模型的请求,费用和预估都不含其他模型;没有上报时不显示,也不按套餐推断有没有这项额度。账户整体 7 天额度用满时所有模型都不可用,这时只显示 7 天窗口。7 天额度有效时,5 小时窗口过期或暂时没有数据会保留位置并显示“等待更新”,收到新周期的额度数据后恢复;7 天额度用满时只显示 7 天窗口。页面停留或从后台恢复时也会检查到期时间。
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
"test": "bun test tests",
"test:browser": "bun tests/browser.smoke.ts",
"collector": "bun src/collector/main.ts",
"build:collector": "bun scripts/build-collector.ts"
"build:collector": "bun scripts/build-collector.ts",
"build:codex-collector": "bun build --compile src/collector/codex-main.ts --outfile dist/meterleaf-codex-collector"
},
"dependencies": {
"@base-ui/react": "^1.8.0",
Expand Down
26 changes: 26 additions & 0 deletions scripts/claude-statusline.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/bin/bash
# 只保存 Claude Code 提供的额度窗口;不落盘完整状态栏输入或读取登录凭据。
set -eu
umask 077
cache="${METERLEAF_STATUSLINE_CACHE:-$HOME/Library/Application Support/Meterleaf Collector/statusline-quota.tsv}"
mkdir -p "$(dirname "$cache")"
pending=$(mktemp "$cache.tmp.XXXXXX")
trap 'rm -f "$pending"' EXIT
if ! jq -r '
(.rate_limits // {}) | to_entries[]
| select(.key == "five_hour" or .key == "seven_day")
| select(.value | type == "object")
| select(.value.used_percentage | type == "number")
| select(.value.used_percentage >= 0 and .value.used_percentage <= 1000)
| select(.value.resets_at | type == "number")
| select(.value.resets_at >= 100000000 and .value.resets_at < 100000000000)
| select(.value.resets_at == (.value.resets_at | floor))
| [.key, .value.used_percentage, .value.resets_at] | @tsv
' > "$pending" 2>/dev/null; then
exit 0
fi
# 无额度的消息不刷新旧快照时间;临时文件 + rename 避免采集到半行。
if [ -s "$pending" ]; then
mv "$pending" "$cache"
awk -F '\t' '{ printf "%s%s %s%%", (NR > 1 ? " · " : ""), ($1 == "five_hour" ? "5h" : "7d"), $2 } END { print "" }' "$cache"
fi
Loading
Loading
0