Local LLM coding usage collector and Feishu Bitable sync
Project description
llm-usage-horizon
本地优先的 LLM 编码工具用量采集器,支持聚合以下来源并输出到终端、CSV 或飞书多维表格:
- Claude Code
- Codex
- Cursor
- GitHub Copilot CLI
- GitHub Copilot VS Code Chat
- OpenCode
- 通过 SSH 拉取的远端日志
设计目标:
- 默认只在本地读取原始日志
- 上传时只发送聚合后的白名单字段
- 支持桌面端统一汇总多台服务器数据
功能概览
llm-usage collect:采集并汇总本地 + 已选远端数据,输出终端表格和reports/usage_report.csvllm-usage sync:在collect基础上,将聚合结果同步到飞书多维表格llm-usage export-bundle:采集并生成离线 bundle,拷回联网机器后可用sync --from-bundle上传llm-usage doctor:检查配置和各采集器可用性llm-usage init:生成.env、.env.example和reports/llm-usage config:打开交互式菜单编辑器,编辑当前运行时.env
快速开始
python -m venv .venv
source .venv/bin/activate
pip install -e '.[dev]'
llm-usage init
# 用菜单编辑器配置当前运行时 .env,至少补全 HASH_SALT
llm-usage config
# ORG_USERNAME 缺失时,命令行运行会提示输入并自动写回 .env
llm-usage doctor
llm-usage collect --ui auto
如果你要同步到飞书,再补全飞书相关环境变量后执行:
llm-usage sync --ui auto
Node 版本
仓库中的 node/ 目录提供一个 Node.js CLI 实现。
当前 Node 版本特性:
- 已补齐
init、whoami、import-config、export-bundle和sync --from-bundle - 本地采集、聚合、报表输出、飞书同步均可直接由 Node 执行
- 运行本地命令时不再依赖 Python collector bridge
- 远端 SSH 采集暂未在 Node 版本中实现;检测到远端配置时会提示并忽略
如果你要验证 Node 版本:
cd node
node --test
node ./bin/llm-usage-node.js init
node ./bin/llm-usage-node.js whoami
node ./bin/llm-usage-node.js import-config --from /path/to/old/repo --dry-run
node ./bin/llm-usage-node.js doctor
node ./bin/llm-usage-node.js collect --ui none
node ./bin/llm-usage-node.js export-bundle
node ./bin/llm-usage-node.js sync --from-bundle /tmp/offline.zip --dry-run
最小配置
.env 中至少建议配置:
ORG_USERNAME=san.zhang
HASH_SALT=change-me
TIMEZONE=Asia/Shanghai
LOOKBACK_DAYS=30
说明:
ORG_USERNAME:必填,用于生成稳定的匿名身份哈希HASH_SALT:必填,决定匿名字段的稳定性与不可逆性TIMEZONE:聚合时按该时区落到date_localLOOKBACK_DAYS:采集窗口,默认30
如果缺少 ORG_USERNAME,交互终端下运行时会提示输入并写回 .env。
远端不建议手工编辑 REMOTE_* 配置。推荐直接运行 llm-usage config,在菜单里编辑当前运行时 .env,包括远端 SSH 主机、用户、端口和路径列表。保存前的修改都停留在草稿里,不会直接写盘。
如果你是从旧版仓库迁移,旧配置通常还在仓库根目录的 .env 和 reports/runtime_state.json。这类配置现在需要一次性迁移到新的运行时路径,推荐在旧仓库根目录执行:
llm-usage import-config --from /path/to/old/repo
可选参数:
--dry-run:先预览会复制哪些文件--force:覆盖新位置里已存在的目标文件
如果你就在旧仓库根目录执行,也可以省略 --from。迁移完成后,后续直接使用 llm-usage doctor、llm-usage whoami、llm-usage collect 或 llm-usage sync 即可。
命令说明
先查看顶层帮助:
llm-usage --help
顶层 help 会列出所有子命令,并附带常用示例:
llm-usage doctorllm-usage whoamillm-usage configllm-usage collect --ui autollm-usage sync --ui clillm-usage export-bundle --output /tmp/offline.zipllm-usage import-config --from /path/to/legacy/repo
llm-usage init
初始化:
.env.example.envreports/- 默认写入
LOOKBACK_DAYS=30
llm-usage doctor
检查:
ORG_USERNAME、HASH_SALT、TIMEZONE- 本地采集器是否能找到对应数据源
.env中配置的远端采集器是否可探测
查看帮助:
llm-usage doctor --help
常用参数:
--lookback-days N:覆盖.env中的LOOKBACK_DAYS
llm-usage config
推荐的配置编辑入口:
- 打开当前运行时
.env的交互式菜单编辑器 - 支持分组编辑基础配置、飞书配置、Cursor 配置、远端配置和原始环境变量
- 修改先保存在草稿里,确认
Save后才写回文件
查看帮助:
llm-usage config --help
llm-usage whoami
输出:
- 当前
ORG_USERNAME - 当前
user_hash source_host_hash(local)- 每个已配置远端各自的
source_host_hash(<alias>)
查看帮助:
llm-usage whoami --help
llm-usage collect
行为:
- 读取本地日志
- 按需选择远端 SSH 来源
- 输出终端汇总表
- 写入
reports/usage_report.csv
说明:终端表格按 日期 + 工具 + 模型 合并展示,不区分单个 session 或来源机器。CSV 仍保留原始聚合结果,不会因为终端显示合并而改变存储内容。
查看帮助:
llm-usage collect --help
常用参数:
--lookback-days N:覆盖.env中的LOOKBACK_DAYS--ui auto|tui|cli|none:远端选择界面。auto自动选最合适的交互方式,tui强制终端选择器,cli使用逐项提示,none跳过远端选择--cursor-login-mode:Cursor 登录模式。默认auto;Windows Chromium 浏览器下会自动切到managed-profile;也可显式选择manual--cursor-login-timeout-sec:Cursor 浏览器登录等待时间,默认600--cursor-login-browser:指定 Cursor 登录捕获所用浏览器;默认default--cursor-login-user-data-dir:managed-profile模式下的专用浏览器 profile 目录;留空时使用工具默认的受控目录
示例:
llm-usage collect --ui auto
llm-usage collect --ui cli --cursor-login-browser safari
llm-usage sync
与 collect 相同,但会额外:
- 自动获取飞书访问令牌
- 在目标多维表格中按
row_key执行插入或更新
说明:sync 的终端显示也按 日期 + 工具 + 模型 合并,但上传到飞书的记录内容和粒度保持不变。
查看帮助:
llm-usage sync --help
常用参数:
--lookback-days N:覆盖.env中的LOOKBACK_DAYS--ui auto|tui|cli|none:远端选择界面。auto自动选最合适的交互方式,tui强制终端选择器,cli使用逐项提示,none跳过远端选择--cursor-login-mode:Cursor 登录模式。默认auto;Windows Chromium 浏览器下会自动切到managed-profile;也可显式选择manual--cursor-login-timeout-sec:Cursor 浏览器登录等待时间,默认600--cursor-login-browser:指定 Cursor 登录捕获所用浏览器;默认default--cursor-login-user-data-dir:managed-profile模式下的专用浏览器 profile 目录;留空时使用工具默认的受控目录
示例:
llm-usage sync --ui auto
llm-usage sync --ui cli --cursor-login-browser chrome
llm-usage sync --from-bundle ~/Downloads/llm-usage-devbox-a.zip --dry-run
离线导入参数:
--from-bundle PATH:从离线 bundle 读取聚合结果,跳过本地/远端在线采集--dry-run:只校验 bundle 并输出终端汇总,不上传到飞书
注意:--from-bundle 模式下不应再同时传 --ui、--lookback-days 或 Cursor 登录相关参数。
llm-usage export-bundle
行为:
- 读取本地日志并按当前配置聚合
- 生成单个 zip bundle,默认写到
reports/llm-usage-bundle-<timestamp>.zip - bundle 内固定包含
manifest.json和rows.jsonl - 默认还会附带
usage_report.csv,方便人工检查
查看帮助:
llm-usage export-bundle --help
常用参数:
--output PATH:指定输出 zip 路径--lookback-days N:覆盖.env中的LOOKBACK_DAYS--ui auto|tui|cli|none:与collect相同,用于远端选择--no-csv:bundle 中不附带usage_report.csv
示例:
llm-usage export-bundle
llm-usage export-bundle --output ~/llm-usage-devbox-a.zip
llm-usage export-bundle --no-csv
离线 Bundle 工作流
适用场景:
- 开发机无法联网
- 只能通过远程桌面进入
- 但可以把文件从远端拷回本地联网机器
推荐流程:
- 在远端开发机执行:
llm-usage export-bundle --output ~/llm-usage-devbox-a.zip
-
把
~/llm-usage-devbox-a.zip拷回本地联网机器 -
在本地先校验:
llm-usage sync --from-bundle ~/Downloads/llm-usage-devbox-a.zip --dry-run
- 校验无误后正式上传:
llm-usage sync --from-bundle ~/Downloads/llm-usage-devbox-a.zip
bundle 只包含聚合后的白名单字段,不包含提示词原文、响应原文、本地路径、命令内容或原始主机名。
读取端除了支持默认的 .zip bundle,也兼容已经解压出来、且目录中包含 manifest.json 与 rows.jsonl 的 bundle 目录。
输出与隐私
上传到飞书的字段是固定白名单:
date_localuser_hashsource_host_hashtoolmodelinput_tokens_sumcache_tokens_sumoutput_tokens_sumrow_keyupdated_at
不会上传:
- 提示词 / 响应原文
- 会话 ID
- 本地路径
- 命令内容
- 原始主机名或 SSH 连接信息
其中:
user_hash基于ORG_USERNAME + HASH_SALTsource_host_hash基于ORG_USERNAME + source_label + HASH_SALT
如需查看当前机器和已配置远端对应的哈希值,可直接运行:
llm-usage whoami
这意味着同一台共享服务器上的不同用户不会发生来源冲突。
支持的数据源
本地来源
默认支持:
- Claude Code
- Codex
- Cursor
- Copilot CLI
- Copilot VS Code Chat
- OpenCode
如默认路径不足,可在 .env 中覆盖:
CLAUDE_LOG_PATHSCODEX_LOG_PATHSCOPILOT_CLI_LOG_PATHSCOPILOT_VSCODE_SESSION_PATHSCURSOR_LOG_PATHS
这些值使用逗号分隔的 glob 匹配模式。
OpenCode
OpenCode 采集器从 SQLite 读取 token 使用量:
- 默认路径:
~/.local/share/opencode/opencode.db - 可通过
OPENCODE_DB_PATH覆盖
Cursor 网页仪表盘(可选)
如果本地 Cursor 日志不可用,或当前 lookback 内没有数据,collect / sync 会尝试使用 Cursor 网页端数据。
相关环境变量:
CURSOR_WEB_SESSION_TOKENCURSOR_WEB_WORKOS_IDCURSOR_DASHBOARD_BASE_URL,默认https://cursor.comCURSOR_DASHBOARD_TEAM_ID,默认0CURSOR_DASHBOARD_PAGE_SIZE,默认300CURSOR_DASHBOARD_TIMEOUT_SEC,默认15
行为说明:
- 若
CURSOR_WEB_SESSION_TOKEN已配置,优先使用网页仪表盘接口 - 若 token 失效,会清空旧 token,并引导重新登录
- 若 token 为空且本地日志不可用,会尝试打开登录页并刷新
.env中的网页登录凭证
Windows 下使用 default / chrome / chromium / edge / msedge 时,默认不会扫描系统浏览器默认 profile 的 cookie。
程序会优先使用受控浏览器 profile 登录流程;若失败,再回退到手动粘贴 WorkosCursorSessionToken。
Windows 受控 profile 登录流程:
- 运行
llm-usage collect或llm-usage sync - 若是 Windows Chromium 浏览器,程序会打开一个由工具管理的专用浏览器 profile
- 在该窗口中完成
https://cursor.com/dashboard/usage登录 - 程序会轮询该 profile 中的 cookie,并自动写入
.env - 如果自动捕获失败,程序才会回退到手动粘贴
WorkosCursorSessionToken
远端 SSH 采集
远端采集由桌面机发起,通过 SSH 拉取日志后在本地统一聚合。
当前远端支持:
- Claude Code
- Codex
- Copilot CLI
- Copilot VS Code Chat
不支持远端 Cursor,本项目中的 Cursor 仍以桌面端本地 / 网页数据为主。
推荐使用命令行交互添加远端,而不是手工编辑 .env:
llm-usage collect --ui auto
典型流程:
- 首次运行时选择
+新增一个临时远端 - 按提示输入
SSH 主机、SSH 用户、SSH 端口 - 程序会先执行 SSH 连通性检查
- 检查通过后继续采集
- 退出前会询问是否将该远端保存到
.env
如果远端机器在堡垒机 / 跳板机后面,当前也支持将目标机器信息直接嵌入 SSH 登录串,例如:
ssh username@username@host_server_ip@host_jumpserver_ip
这种场景下,命令行交互录入时可按下面填写:
SSH 主机:username@host_server_ip@host_jumpserver_ipSSH 用户:usernameSSH 端口:按实际堡垒机端口填写
这样程序最终拼接出的 SSH 目标会是:
username@username@host_server_ip@host_jumpserver_ip
只要你的堡垒机环境本身支持这种格式,llm-usage 当前也可以正常连通并采集。
只有在需要批量预置配置、或做非交互部署时,才建议手工维护 REMOTE_*。
运行时行为:
--ui auto优先使用轻量 TUI,失败时回落到 CLI--ui none会禁用所有远端,不会沿用runtime_state.json里上次选中的静态远端- 上次选择的静态远端会保存到当前运行时数据目录下的
runtime_state.json - 可以在运行时临时添加远端
- 推荐通过运行时交互添加远端,临时远端只有确认后才会追加写入
.env - 临时远端默认来源标签为
ssh_user@ssh_host - 远端机器只要求有
ssh和基础python3/python
飞书多维表格同步
兼容性说明:
- 旧版单目标
.env配置无需修改即可继续使用 - 不带新的目标选择参数时,
sync仍只上传到默认目标
单目标配置(兼容旧版)
需要的环境变量:
FEISHU_APP_TOKENFEISHU_TABLE_ID,可选;为空时自动选择第一个表FEISHU_APP_IDFEISHU_APP_SECRETFEISHU_BOT_TOKEN,可选;若提供则直接作为 bearer token 使用
示例:
FEISHU_APP_TOKEN=app_default
FEISHU_TABLE_ID=tbl_default
FEISHU_APP_ID=cli_default
FEISHU_APP_SECRET=sec_default
FEISHU_BOT_TOKEN=
多目标配置
新增 FEISHU_TARGETS 和每个目标的 FEISHU_<TARGET>_* 键:
FEISHU_APP_TOKEN=app_default
FEISHU_TABLE_ID=tbl_default
FEISHU_APP_ID=cli_default
FEISHU_APP_SECRET=sec_default
FEISHU_TARGETS=team_b,finance
FEISHU_TEAM_B_APP_TOKEN=app_team_b
FEISHU_TEAM_B_TABLE_ID=tbl_team_b
FEISHU_FINANCE_APP_TOKEN=app_finance
FEISHU_FINANCE_TABLE_ID=
FEISHU_FINANCE_APP_ID=cli_finance
FEISHU_FINANCE_APP_SECRET=sec_finance
规则:
- named target 会继承顶层
FEISHU_APP_ID/FEISHU_APP_SECRET/FEISHU_BOT_TOKEN(若对应前缀键为空) - named target 不继承
FEISHU_APP_TOKEN - 某个 target 的
TABLE_ID为空时,会为该 target 自动选择第一个表
检查目标表结构
可以只读检查目标表是否可访问、以及标准字段是否齐全:
llm-usage doctor --feishu
llm-usage doctor --feishu --feishu-target team_b
llm-usage doctor --feishu --all-feishu-targets
说明:
- 缺失字段和类型不匹配会显示为 warning
- 认证失败、无权限、无法读取字段列表等会返回非零退出码
初始化目标表结构
可以按标准 schema 自动补齐缺失字段:
llm-usage init --feishu-bitable-schema --dry-run
llm-usage init --feishu-bitable-schema --feishu-target finance
llm-usage init --feishu-bitable-schema --all-feishu-targets
说明:
- 该命令只会创建缺失字段
- 不会删除字段
- 不会重命名字段
- 不会修改已有字段类型
同步到一个或多个目标表
llm-usage sync
llm-usage sync --feishu-target team_b
llm-usage sync --feishu-target team_b --feishu-target finance
llm-usage sync --all-feishu-targets
默认行为:
- 不带目标参数时,只同步到默认目标
--all-feishu-targets才会显式 fan-out 到所有目标
标准字段参考
| 字段名 | 作用 | 推荐类型 | 说明 |
|---|---|---|---|
date_local |
聚合日期 | 日期时间 | 当前实现会按日期/时间字符串做兼容归一化 |
user_hash |
脱敏后的用户标识 | 文本 | 与 whoami 输出一致 |
source_host_hash |
脱敏后的主机标识 | 文本 | 区分 local 和远端来源 |
tool |
工具名称 | 文本 | 例如 codex、cursor |
model |
模型名称 | 文本 | 原始聚合维度之一 |
input_tokens_sum |
输入 tokens | 数字 | 聚合求和 |
cache_tokens_sum |
cache tokens | 数字 | 聚合求和 |
output_tokens_sum |
输出 tokens | 数字 | 聚合求和 |
row_key |
幂等 upsert 键 | 文本 | 用于在飞书侧定位已存在记录 |
updated_at |
最近更新时间 | 日期时间 | sync 会按毫秒时间戳归一化 |
注意:
- 飞书应用权限不等于多维表格协作权限
- 即使应用有写权限,如果表格本身只读,写入仍会失败
- 应确保应用或其运行身份对目标表保有编辑权限
开发
安装开发依赖:
pip install -e '.[dev]'
pytest
发布到 PyPI
先准备发布工具:
python -m pip install -U build twine
每次发布前都要先修改 pyproject.toml 里的 version,避免重复上传同一版本。
构建并检查 PyPI 分发文件:
./scripts/build_pypi_release.sh
这个脚本会把产物单独输出到 dist/pypi/,不会碰现有 dist/ 目录中的业务压缩包。
如果你想自定义输出目录:
./scripts/build_pypi_release.sh /tmp/llm-usage-pypi
上传命令单独执行,不放进脚本里:
python -m twine upload dist/pypi/*
如果要先用 TestPyPI 验证:
python -m twine upload --repository testpypi dist/pypi/*
GitHub Actions 自动发布也已支持:
- 推送 tag
py-vX.Y.Z会触发.github/workflows/publish-pypi.yml - 也可以在 Actions 页面手动运行
Publish PyPI - tag 触发时,workflow 会校验 tag 版本和 pyproject.toml 中的
version完全一致
启用前提:
- 在 PyPI 项目
llm-usage-horizon中配置 GitHub trusted publisher - publisher 的仓库填
zaxliu/agent_coding_usage - workflow 名称填
Publish PyPI - environment 名称填
pypi
手动触发不会改版本号,只会发布当前仓库里已经写入 pyproject.toml 的版本。
发布到 npm
先确认 node/package.json 里的 version 已更新,且包名仍然是 @llm-usage-horizon/llm-usage-node。
本地发布前校验:
cd node
npm install
npm test
npm pack --dry-run
GitHub Actions 自动发布也已支持:
- 推送 tag
node-vX.Y.Z会触发.github/workflows/publish-npm.yml - 也可以在 Actions 页面手动运行
Publish npm - tag 触发时,workflow 会校验 tag 版本和 node/package.json 中的
version完全一致
启用前提:
- 在 npm 上为包
@llm-usage-horizon/llm-usage-node启用 trusted publishing - 关联 GitHub 仓库
zaxliu/agent_coding_usage - workflow 名称填
Publish npm - environment 名称填
npm
手动触发不会改版本号,只会发布当前仓库里已经写入 node/package.json 的版本。
采集器扩展说明见 docs/ADAPTERS.md。
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file llm_usage_horizon-0.1.5.tar.gz.
File metadata
- Download URL: llm_usage_horizon-0.1.5.tar.gz
- Upload date:
- Size: 127.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
aa2bff3c886803c56765a549a10a1505068a5fb504270d6f6386a718452525cf
|
|
| MD5 |
c8d547e725d97a8628bb3c0f7bbaff89
|
|
| BLAKE2b-256 |
efc99ab02bd753cef6022ee9495f666c1d5a58951b754e6f410c2694c1522f4d
|
Provenance
The following attestation bundles were made for llm_usage_horizon-0.1.5.tar.gz:
Publisher:
publish-pypi.yml on zaxliu/agent_coding_usage
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
llm_usage_horizon-0.1.5.tar.gz -
Subject digest:
aa2bff3c886803c56765a549a10a1505068a5fb504270d6f6386a718452525cf - Sigstore transparency entry: 1242251313
- Sigstore integration time:
-
Permalink:
zaxliu/agent_coding_usage@e9705e98685829c83fc38aed7da833b29e28639d -
Branch / Tag:
refs/heads/main - Owner: https://github.com/zaxliu
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@e9705e98685829c83fc38aed7da833b29e28639d -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file llm_usage_horizon-0.1.5-py3-none-any.whl.
File metadata
- Download URL: llm_usage_horizon-0.1.5-py3-none-any.whl
- Upload date:
- Size: 90.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5e85afd993447a26d2e02c3686ecb9f5c267fa952653128b78592834413fe925
|
|
| MD5 |
4cb98a58f335594f7bac2e1fe37fa5c1
|
|
| BLAKE2b-256 |
998f77e47efd008a612fda4ad00c376da6c0156400224e3c152bdb970169af02
|
Provenance
The following attestation bundles were made for llm_usage_horizon-0.1.5-py3-none-any.whl:
Publisher:
publish-pypi.yml on zaxliu/agent_coding_usage
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
llm_usage_horizon-0.1.5-py3-none-any.whl -
Subject digest:
5e85afd993447a26d2e02c3686ecb9f5c267fa952653128b78592834413fe925 - Sigstore transparency entry: 1242251314
- Sigstore integration time:
-
Permalink:
zaxliu/agent_coding_usage@e9705e98685829c83fc38aed7da833b29e28639d -
Branch / Tag:
refs/heads/main - Owner: https://github.com/zaxliu
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@e9705e98685829c83fc38aed7da833b29e28639d -
Trigger Event:
workflow_dispatch
-
Statement type: