8000 feat(server): run without Sub2API when only the collector pushes by InfinityPacer · Pull Request #14 · 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
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
METERLEAF_SOURCE_ID=sub2api
METERLEAF_BIND_ADDRESS=127.0.0.1
METERLEAF_PUBLISHED_PORT=4318
# Sub2API 只读连接地址;只接入本机采集器时留空,但它与 METERLEAF_INGEST_KEYS 至少填一项。
SUB2API_DATABASE_URL=postgresql://readonly:replace-me@postgres:5432/sub2api
# 以下三项用于直接以 Bun 启动;Docker 部署使用 compose.yaml 中的容器配置。
METERLEAF_DATA_DIR=app_data
Expand Down
5 changes: 3 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Meterleaf 是独立 AI 用量账本,只读采集上游用量并在本地计价

## 项目结构与架构

- `src/connectors` 解释上游字段;`src/domain` 定义用量和计价规则;`src/storage` 负责持久化与汇总索引。
- `src/connectors` 解释上游字段;`src/collector` 是 macOS 本机采集器,只读解析 Claude Code 本地记录并推送到服务端;`src/domain` 定义用量和计价规则;`src/storage` 负责持久化与汇总索引。
- `src/server` 提供 API 和同步服务;`src/shared` 定义共享契约;`src/web` 实现 React 界面;`public` 存放静态资源。
- `tests` 存放测试,`prices` 管理版本化费率 JSON,`docs` 存放中文文档。
- 领域模型与报表不得依赖 Sub2API SQL 或列名。用量与额度快照是独立能力;未知值不得补零,缓存与推理 Tokens 不得重复累计,网关扣费与独立估值必须区分。
Expand All @@ -22,6 +22,7 @@ Meterleaf 是独立 AI 用量账本,只读采集上游用量并在本地计价
- `bun run dev:server` 与 `bun run dev`:分别启动真实后端与前端。
- `bun run typecheck`:检查 TypeScript 类型;`bun test tests`:运行测试。
- `bun run build`:类型检查并构建前端;`bun run start`:启动应用。
- `bun run build:collector`:构建采集器命令行与 macOS 上的 `dist/Meterleaf.app`。

## 代码风格与注释

Expand Down Expand Up @@ -57,6 +58,6 @@ Meterleaf 是独立 AI 用量账本,只读采集上游用量并在本地计价

## 配置与协作边界

- 当前按 `.env.example` 通过环境变量连接一个 Sub2API 实例,来源管理、内置认证和用户角色系统不在当前产品范围。
- 当前按 `.env.example` 通过环境变量配置来源,可连接一个 Sub2API 实例、通过 `METERLEAF_INGEST_KEYS` 接收本机采集器推送,两者至少一项;来源管理、内置认证和用户角色系统不在当前产品范围。
- 项目采用 Apache-2.0。凭据、真实账户数据、运行数据库、机器路径及构建产物不得进入公开交付内容;真实数据失败不得静默替换为演示数据。
- 工作台不属于产品交付内容。排查报表或同步失败先区分读取和采集阶段;清库、修改同步设置不作为默认恢复动作。
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## [未发布]

- Sub2API 改为可选来源。只使用本地直连 Claude Code 的用户,填写本机采集器的写入密钥即可启动 Meterleaf,不再需要网关数据库;这时页面不显示「数据同步」,采集器推送后报表自动更新。`SUB2API_DATABASE_URL` 与 `METERLEAF_INGEST_KEYS` 至少填写一项。
- README 与部署指南补充 Claude Code 的接入步骤、写入密钥配置和采集器访问服务的方式,并说明修改 `.env` 后需要重新执行 `docker compose up -d`,`restart` 不会读取新配置。

## [0.5.1] - 2026-09-26

- 「账户额度」页并入用量总览。排序、重命名、归档和删除直接在总览的账户区完成,有归档账户时标题旁出现「已归档 N」,可查看和恢复。手机底部导航减为首页、统计、明细和关于。
Expand Down
29 changes: 25 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@

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

Meterleaf 只读采集一个 Sub2API PostgreSQL 实例,也可以接收本机采集器推送的 Claude Code 本地直连用量,将用量保存到本地 SQLite 并生成独立报表。不代理模型请求、不修改 Sub2API,也不直接请求 OpenAI 或 Anthropic;单个容器即可运行。
Meterleaf 可以只读采集一个 Sub2API PostgreSQL 实例,也可以接收本机采集器推送的 Claude Code 本地直连用量,两种来源可单独或同时使用,用量保存到本地 SQLite 并生成独立报表。不代理模型请求、不修改 Sub2API,也不直接请求 OpenAI 或 Anthropic;单个容器即可运行。

## 能做什么

- **用量总览**:默认看历史至今的 Tokens、费用、请求和缓存命中率,可切到今天、近 7 天等范围;下方是同一范围的消耗趋势和模型分布。
- **账户额度**:总览中按当前周期显示 5 小时与 7 天窗口、重置时间及七天额度预估,并可直接排序、重命名和归档账户。
- **统计报表**:顶部是所选范围的摘要与环比,按小时、天、周、模型或账户汇总 Tokens、缓存命中率、请求数和费用,附合计行。
- **请求明细**:查看模型、来源、推理强度、Token 拆分与计价依据。
- **Claude Code 本地用量**:不经过网关的 Claude Code 由 Mac 上的本机采集器推送用量、套餐和 5 小时、7 天及 Fable 周额度,与网关用量在同一账本中统计。
- **独立估值**:同时保留订阅 Credits 与 USD,美元可切换订阅等价和标准 API 口径。

支持自定义日期、页面筛选、明暗主题和移动端;同步在后台进行,已有报表继续可读。
Expand Down Expand Up @@ -49,15 +50,18 @@ Meterleaf 只读采集一个 Sub2API PostgreSQL 实例,也可以接收本机

## 快速开始

当前支持一个 Sub2API PostgreSQL 实例。数据库账号只需读取 `public.accounts` 和 `public.usage_logs`,不需要写权限。
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` 留空,并先生成写入密钥再启动服务。

在仓库目录中准备配置:

```sh
cp .env.example .env
```

填写 `.env` 中的 `SUB2API_DATABASE_URL`,然后运行:
按上面填写 `.env`,然后运行:

```sh
docker compose build
Expand All @@ -66,10 +70,27 @@ docker compose run --rm --no-deps --pull never --user root --entrypoint sh meter
docker compose up -d --pull never
```

访问 `http://127.0.0.1:4318`,从右上角「数据同步」启动首次采集。默认不自动采集,可自行开启自动同步。
访问 `http://127.0.0.1:4318`。连接了 Sub2API 时,从右上角「数据同步」启动首次采集,默认不自动采集,可自行开启自动同步。只用 Claude Code 时没有这个入口,采集器推送后数据自动出现。

已发布镜像的拉取方式、跨主机访问、反向代理、配置项及备份恢复见[部署指南](docs/deployment.md)。应用不内置认证,可在反向代理层接入 OAuth/OIDC 认证。

### 接入 Claude Code

本地直连的 Claude Code 需要在使用它的 Mac 上安装本机采集器。采集器暂不提供下载,需要在 Mac 上构建,要求 Bun 与 Xcode 命令行工具:

```sh
bun install --frozen-lockfile
bun run build:collector
```

把 `dist/Meterleaf.app` 移到「应用程序」,然后生成写入密钥。`--server` 填这台 Mac 能访问到的 Meterleaf 地址,服务端不在本机时先按[部署指南](docs/deployment.md#接入本机采集器)开放访问:

```sh
/Applications/Meterleaf.app/Contents/MacOS/meterleaf-collector init --server https://meterleaf.example.com
```

命令会打印一行 `METERLEAF_INGEST_KEYS=来源标识:摘要`。把它加到服务端的 `.env`,再执行一次 `docker compose up -d --pull never` 让配置生效(`docker compose restart` 不会读取新配置)。之后在 Mac 上依次执行 `bind-history`、`sync` 和 `install-launchd`,每一步的含义见[本机采集器](docs/collector.md)。

## 文档

- [使用与报表](docs/reporting.md):筛选、账户管理、同步显示及统计口径。
Expand Down
2 changes: 1 addition & 1 deletion compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ services:
ports:
- "${METERLEAF_BIND_ADDRESS:-127.0.0.1}:${METERLEAF_PUBLISHED_PORT:-4318}:4318"
environment:
SUB2API_DATABASE_URL: ${SUB2API_DATABASE_URL:?Set the read-only Sub2API PostgreSQL connection URL}
SUB2API_DATABASE_URL: ${SUB2API_DATABASE_URL:-}
METERLEAF_SOURCE_ID: ${METERLEAF_SOURCE_ID:-sub2api}
METERLEAF_LOG_LEVEL: ${METERLEAF_LOG_LEVEL:-info}
METERLEAF_REPORT_REFRESH_INTERVAL_MS: ${METERLEAF_REPORT_REFRESH_INTERVAL_MS:-300000}
Expand Down
8 changes: 5 additions & 3 deletions docs/collector.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

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

本地直连的 Claude Code 不经过网关,它的用量只保存在使用它的电脑上。本机采集器在这台电脑上读取 Claude Code 的本地记录,把用量、账户和额度快照推送到 Meterleaf。Meterleaf 可以部署在 NAS 或另一台主机上,与这台电脑分开。
本地直连的 Claude Code 不经过网关,它的用量只保存在使用它的电脑上。本机采集器在这台电脑上读取 Claude Code 的本地记录,把用量、账户和额度快照推送到 Meterleaf。Meterleaf 可以部署在 NAS 或另一台主机上,与这台电脑分开。没有 Sub2API 网关时,Meterleaf 也可以只接收采集器的数据。

## 它读什么、不碰什么

Expand All @@ -21,7 +21,7 @@ Claude Code 会把一次请求按内容块拆成多行保存,每行带着同

## 安装

采集器需要 macOS,在仓库中构建。
采集器需要 macOS 13 或更新版本,目前不提供下载,需要在这台 Mac 上从仓库构建。构建需要 Bun 1.4.2 或更新版本,以及 Xcode 命令行工具(没有时执行 `xcode-select --install`)。

```sh
bun install --frozen-lockfile
Expand All @@ -42,7 +42,9 @@ alias meterleaf-collector="/Applications/Meterleaf.app/Contents/MacOS/meterleaf-
meterleaf-collector init --server https://meterleaf.example.com
```

命令会打印一行 `METERLEAF_INGEST_KEYS=来源标识:摘要`。按[部署指南](deployment.md#接入本机采集器)把它加到 Meterleaf 的环境变量并重启服务。完整密钥只保存在本机配置文件中,不需要也不应转交。
命令会打印一行 `METERLEAF_INGEST_KEYS=来源标识:摘要`。把它加到 Meterleaf 服务端的 `.env`,多台电脑用英文逗号连接,再执行一次 `docker compose up -d` 让配置生效,细节见[部署指南](deployment.md#写入密钥)。完整密钥只保存在本机配置文件中,不需要也不应转交。

服务端在 NAS 等其他主机时,这台电脑需要能访问它,局域网访问与公网推送的设置见[部署指南](deployment.md#让采集器访问服务)。

2. 声明历史归属。会话记录里没有账户信息,采集器靠每次运行时观察到的登录账户来归属用量。首次运行前的历史无法自动判断,如果这台电脑一直只登录过当前账户,在首次同步前执行下面的命令。

Expand Down
39 changes: 28 additions & 11 deletions docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@

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

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

## 安装

需要 Docker Compose,以及从容器内可访问的 Sub2API 数据库。数据库账号需对 `public.accounts` 和 `public.usage_logs` 有 SELECT 权限,不需要写权限。
需要 Docker Compose。连接 Sub2API 时,数据库需从容器内可访问,账号需对 `public.accounts` 和 `public.usage_logs` 有 SELECT 权限,不需要写权限。只用本机采集器时不需要数据库,但要先按[接入本机采集器](#接入本机采集器)生成写入密钥。

```sh
cp .env.example .env
```

填写 `SUB2API_DATABASE_URL` 后先校验配置:
填写 `SUB2API_DATABASE_URL` 或 `METERLEAF_INGEST_KEYS`,两者都没有时服务不会启动。先校验配置:

```sh
docker compose config -q
Expand Down Expand Up @@ -52,7 +52,7 @@ docker compose up -d --no-build --pull never

| 变量 | 默认值 | 说明 |
| -------------------------------------- | -------------- | ------------------------------------------ |
| `SUB2API_DATABASE_URL` | 真实模式必填 | 单实例 PostgreSQL 只读连接地址 |
| `SUB2API_DATABASE_URL` | 未启用 | 单实例 PostgreSQL 只读连接地址 |
| `METERLEAF_SOURCE_ID` | `sub2api` | 稳定来源标识;已有账本不要改名 |
| `METERLEAF_BIND_ADDRESS` | `127.0.0.1` | Compose 对外绑定地址 |
| `METERLEAF_PUBLISHED_PORT` | `4318` | Compose 对外端口 |
Expand All @@ -64,6 +64,8 @@ docker compose up -d --no-build --pull never
| `METERLEAF_LOG_LEVEL` | `info` | `debug`、`info`、`warn`、`error`、`silent` |
| `METERLEAF_INGEST_KEYS` | 未启用 | 本机采集器写入密钥,见下文 |

`SUB2API_DATABASE_URL` 与 `METERLEAF_INGEST_KEYS` 至少填写一项,未填写的来源不启用。

Compose 将容器内数据目录、监听地址和端口固定为 `/app/app_data`、`0.0.0.0` 和 `4318`;`METERLEAF_DATA_DIR`、`METERLEAF_HOST`、`METERLEAF_PORT`、`METERLEAF_DEMO` 仅用于直接以 Bun 启动时的本地配置。

自定义价格文件可放在挂载的 `prices` 目录,设置 `METERLEAF_PRICE_BOOK=/app/prices/custom.json`。修改后更新费率版本并重启,详见[计价说明](pricing.md)。
Expand All @@ -72,15 +74,30 @@ Compose 将容器内数据目录、监听地址和端口固定为 `/app/app_data

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

每台电脑一个写入密钥。采集器执行 `init` 时生成密钥,并打印一行 `来源标识:摘要`。把这些行用英 106E9 文逗号连接后填入 `METERLEAF_INGEST_KEYS`,重启服务即可:
采集器可以与 Sub2API 同时使用,也可以单独使用。只用采集器时 `SUB2API_DATABASE_URL` 留空,页面不显示「数据同步」,采集器推送后数据自动出现。

### 写入密钥

每台电脑一个写入密钥。在电脑上执行采集器的 `init` 时生成密钥,并打印一行 `METERLEAF_INGEST_KEYS=来源标识:摘要`。把它加到服务端 `.env`,多台电脑的 `来源标识:摘要` 用英文逗号连接:

```dotenv
METERLEAF_INGEST_KEYS=claude-code-macbook:<64 位十六进制摘要>,claude-code-mini:<摘要>
```

Meterleaf 只保存密钥的 SHA-256 摘要,每个密钥只能写入自己绑定的来源,不能读取报表,删除对应一行即可吊销。来源标识不能与 `METERLEAF_SOURCE_ID` 相同,已有账本不要改名。未设置该变量时不开放写入接口。
然后按实际使用的来源再执行一次启动命令,让容器按新配置重建。`docker compose restart` 只重启旧容器,不会读取新的 `.env`。

```sh
docker compose up -d --pull never # 源码构建
docker compose up -d --no-build --pull never # 已发布镜像
```

完整密钥只保存在采集器所在电脑上,服务端只保存 SHA-256 摘要,每个密钥只能写入自己绑定的来源,不能读取报表,删除对应一行即可吊销。来源标识不能与 `METERLEAF_SOURCE_ID` 相同,已有账本不要改名。未设置该变量时不开放写入接口。

### 让采集器访问服务

采集器需要能访问 Meterleaf。采集器与服务在同一台电脑时使用 `http://127.0.0.1:4318`。服务在 NAS 等其他主机时,随附 Compose 默认只监听回环地址,局域网访问需把 `METERLEAF_BIND_ADDRESS` 改为 `0.0.0.0` 或宿主机局域网地址,或者经由已有的反向代理访问。HTTP 下写入密钥明文传输,只在可信局域网中使用。

采集器需要能访问 Meterleaf。若要让外网的电脑推送,只在反向代理上为 `POST /api/ingest/v1/batches` 绕过原有登录保护,其余页面照旧。这个地址用写入密钥鉴权,必须走 HTTPS。反向代理的请求体上限至少设为 8 MB,Nginx 默认 1 MB 会拒绝大批次。以 Nginx 为例:
若要让外网的电脑推送,只在反向代理上为 `POST /api/ingest/v1/batches` 绕过原有登录保护,其余页面照旧。这个地址用写入密钥鉴权,必须走 HTTPS。反向代理的请求体上限至少设为 8 MB,Nginx 默认 1 MB 会拒绝大批次。以 Nginx 为例:

```nginx
location = /api/ingest/v1/batches {
Expand All @@ -94,11 +111,11 @@ location = /api/ingest/v1/batches {

## 首次采集与自动同步

首次启动默认不采集。在「数据同步」中点击「立即同步」启动历史补采;服务保持在线,重复点击不会建立并发任务。自动同步默认关闭,开关会保存在本地账本并在重启后恢复。
本节适用于 Sub2API 来源。首次启动默认不采集。在「数据同步」中点击「立即同步」启动历史补采;服务保持在线,重复点击不会建立并发任务。自动同步默认关闭,开关会保存在本地账本并在重启后恢复。

同步失败时保留已成功采集的账本,手动模式可重试,自动模式按配置间隔继续尝试;重启后可继续未完成采集。源端已清理的历史记录或未保留的额度快照无法补回。

账户额度来自 Sub2API 的缓存字段,新鲜度不能超过上游缓存。窗口过期后显示未知,不把上一周期消费带入新周期。
Sub2API 账户的额度来自它的缓存字段,新鲜度不能超过上游缓存。窗口过期后显示未知,不把上一周期消费带入新周期。

## 升级与备份恢复

Expand All @@ -125,7 +142,7 @@ docker compose pull
docker compose up -d --no-build --pull never
```

更新后用 `docker compose ps` 检查状态。
更新后用 `docker compose ps` 检查状态。接入了本机采集器时,先升级服务端再升级采集器,见[本机采集器](collector.md#升级)。

恢复前先准备备份对应的程序版本,再停止服务、保留当前数据并恢复完整备份。将下面的备份目录替换为实际名称:

Expand All @@ -137,7 +154,7 @@ cp -a app_data.backup-YYYYMMDD-HHMMSS app_data
docker compose up -d --no-build --pull never
```

必须恢复整个数据目录,不要只复制运行中的 SQLite 主文件而遗漏 WAL。这些操作只针对 Meterleaf,无需停止 Sub2API。
必须恢复整个数据目录,不要只复制运行中的 SQLite 主文件而遗漏 WAL。这些操作只针对 Meterleaf,无需停止 Sub2API 或本机采集器,停机期间采集器未送达的数据会在恢复后补推。

升级会自动执行数据库迁移;遇到不兼容或无法识别的结构时服务会停止,不要通过清空数据绕过。

Expand Down
Loading
Loading
0