SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor

在 VPS 上自建 Agent Console 追踪 Claude Code 花费

Claude Code 的额度被哪个会话、哪个子代理、哪个模型吃掉了?把 Agent Console v0.4.1 的团队 Hub 放在一台 VPS 上,所有笔记本和服务器汇报到同一个控制台。

Agent Console 是什么:一个只读会话文件的控制台

Agent Console 是一个本地优先的控制台,它读取 Claude Code 和 Codex 在你自己机器上留下的会话文件,把会话、子代理、模型和 token 花费摊开成一个页面。它不要求你换 API key,也不要求你改变调用 Claude Code 的方式。截至 2026 年 9 月,它的最新标记版本是 v0.4.1,本文所有命令都钉在这个版本上。

先说清楚它不做什么,因为这决定了你该拿它解决哪类问题。它读的是本地 transcript(会话记录文件),默认位置是 ~/.claude/projects 和 ~/.codex/sessions。它是只读的:它不会改动这些文件,也不会去和 Anthropic 那边的账单对账。官方的计价文档说得很直接:transcript 是“工具记录了什么”的证据,不是供应商的发票。所以它能回答“这个月哪个项目、哪个模型吃掉了额度”,不能回答“我的账单为什么是这个数”。

本文不会告诉你它的某个界面会显示什么数字,因为那要看你自己的会话历史。每一处该看数字的地方,都请你打开自己的控制台去看。如果你对 Claude Code 的 token 分类本身还不熟,先读 Claude Code 的 token 用量是怎么算出来的,再回来看这个控制台的数字会顺很多。

为什么团队 Hub 该放在 VPS 上

单机跑它就是个桌面工具,一台机器一个页面。真正需要托管的是多机模式:一个 Hub,所有笔记本和服务器作为 reporter 向它汇报,你在一个页面里看到全部。

一个人的 Claude Code 花费很少只发生在一台机器上。笔记本上写代码,VPS 上跑长任务,CI 机器上跑批量改写,三份会话文件散在三台机器上,谁也没法加总。Hub 要长期在线才有意义,而笔记本会合盖,会断网,所以 Hub 属于一台一直开着的 VPS。这就是它成为一篇托管文章而不是一篇桌面软件文章的原因。

Hub 有两个端口,先记住它们的区别,后面所有网络问题都由此而来。管理页面在 6787,只监听回环地址。接收上报的监听端口是 6788,接受来自私有网络的加入请求。

版本和 Node.js:两个前置条件

CLI 需要 Node.js 22 或更新的版本,这是硬要求。Ubuntu 24.04 仓库里的 nodejs 比这个旧,所以直接 apt install nodejs 装出来的版本大概率不够。先确认:

node --version

输出低于 v22 就换一个来源,用 nvm,或者用官方发布的独立可执行文件,后者不需要 Node.js。

VPS 上的 Hub 用官方 Docker 镜像跑,镜像里自带 Node,所以 VPS 自己的 Node 版本只影响你在那台机器上直接调用 CLI 的情况。

在 VPS 上跑 Hub:官方 Docker 镜像

官方镜像是 ghcr.io/lockedinlabs-ai/agent-console,有 linux amd64 和 arm64 两个架构。先建一个持久卷,再启动容器:

docker volume create agent-console-state
docker run --name agent-console-hub --network host \
  -v agent-console-state:/home/dev/.agent-console/hub \
  ghcr.io/lockedinlabs-ai/agent-console:v0.4.1

三处细节值得解释。卷挂在 /home/dev/.agent-console/hub,因为 Hub 的状态就放在那里,不挂卷的话容器一删,历史数据跟着消失。--network host 是文档给的方式,因为 Hub 要求 6787 只在回环上、6788 面向私有网络,这个组合用端口映射表达起来很别扭。容器以非特权的 node 用户运行,并且带 --no-local:它不读所在机器上的 Claude Code 或 Codex 历史。

容器的健康状态在上报端口开始响应之后转为 healthy,用这个判断它是否真的起来了:

docker ps --filter name=agent-console-hub
docker logs --tail 50 agent-console-hub

务必写死版本 tag。文档明确要求不要用浮动的 latest,理由很实在:这个工具指着你的会话历史,你应该随时知道自己跑的是哪一次构建。

macOS 和 Windows 上不要走 Docker,Docker 在那两个平台上的 host 网络行为和 Linux 不一样,请改用官方独立可执行文件。

想推倒重来:

docker rm -f agent-console-hub
docker volume rm agent-console-state
docker image rm ghcr.io/lockedinlabs-ai/agent-console:v0.4.1

管理页只监听回环,怎么从自己的电脑打开

这是故意的设计:管理页面只监听回环地址,Hub 的默认配置还会拒绝来自公网的客户端。不要为了方便把 6787 直接暴露出去。

正确做法是 SSH 端口转发。在你自己的电脑上开一条隧道,再用本地浏览器访问:

ssh -N -L 6787:127.0.0.1:6787 youruser@your-vps

隧道开着的时候,浏览器打开 http://127.0.0.1:6787。你看到的是 VPS 上那个页面,流量走 SSH,不用多开一个公网端口。

上报端口 6788 面向的是私有网络。所以最省事的拓扑是先把所有机器放进同一个私有网络,比如一条 WireGuard 隧道或者 Tailscale,再让它们通过那个地址加入。这条限制反而省掉了一个公网端口加一份鉴权配置带来的全部风险。

把每台机器接进同一个控制台

加入靠的是一次性链接,不需要在机器之间共享长期密钥。在控制台里按 Add a machine,填这台机器的归属和名字,按 Create join link,再按 Copy link。

然后在要上报的那台机器上执行。链接必须用单引号包住,因为它里面有会被 shell 解释的字符:

agent-console join '<join link>'

从源码跑的形式是 node bin/agent-console.mjs join '<join link>'。链接只能用一次,有效期最长一小时。加入过程是加密的,并且会核对你控制台的证书指纹,所以一条被别人看到的过期链接没有用。

笔记本上装 CLI 有几种方式,都钉在 v0.4.1。最短的一条:

npx --yes https://github.com/LockedinLabs-AI/agent-console/releases/download/v0.4.1/lockedinlabs-agent-console-0.4.1.tgz --open

想常驻就全局装:

npm install --global --ignore-scripts \
  https://github.com/LockedinLabs-AI/agent-console/releases/download/v0.4.1/lockedinlabs-agent-console-0.4.1.tgz
agent-console --open

macOS 上还有 Homebrew:brew install lockedinlabs-ai/tap/agent-console。

入组之后,重启机器不需要新链接,agent-console report 会接着上报。要退出用 agent-console leave:它停掉 reporter,告诉控制台这台机器离开了,并删掉本机的入组文件。

让 reporter 在重启之后自己回来

一台 Linux 机器上用用户级 systemd 单元就够了,不需要 root。写入 ~/.config/systemd/user/agent-console-reporter.service:

[Unit]
Description=Agent Console reporter
After=network-online.target

[Service]
ExecStart=%h/.local/bin/agent-console report --interval 60
# Only for the npm package or the release link: where node is.
# Environment=PATH=/usr/local/bin:/usr/bin:/bin
Restart=on-failure
RestartSec=30
RestartPreventExitStatus=2 3 4

[Install]
WantedBy=default.target

启用它,并盯一会儿日志:

systemctl --user daemon-reload
systemctl --user enable --now agent-console-reporter
journalctl --user -u agent-console-reporter -f

两个地方容易踩。第一,用户级单元默认只在你登录期间运行,构建服务器上平时没人登录,所以要先执行 loginctl enable-linger,否则 SSH 一断服务就跟着停。第二,RestartPreventExitStatus=2 3 4 不是随手写的:这三个退出码代表“有理由地停下来”,命令写错了、这台机器已经被控制台移除了、或者已经有另一个 reporter 在跑。这几种情况重启一万次也不会变好,所以单元故意不重启,你应该去读日志。

ExecStart 里的路径按你的安装方式改。npm 全局安装的可执行文件不一定落在 %h/.local/bin,用 command -v agent-console 查出真实路径再填。如果 node 不在 systemd 的默认 PATH 里,把那行 Environment=PATH=... 的注释去掉。

它上报的到底是什么

这是决定你敢不敢在公司机器上装它的那个问题,所以说具体一点。官方的说法是除了计数之外没有东西离开机器:模型 id、精确到分钟的时间戳、token 计数,以及加了盐的哈希。它不上报源代码,也不上报 prompt 的内容。工具本身没有遥测、没有更新检查、没有崩溃上报。

项目名默认也不上报,哈希之后你只能看到“有这么一个项目”。想在控制台里看到真实的项目目录名,要显式打开 --share-project-names。reporter 还有 --share-alerts 和 --share-tool-activity 两个同类开关,都是默认关闭、由你主动打开。

Hub 默认保留 8 天的元数据,存在 ~/.agent-console/hub/;reporter 自己的状态在 ~/.agent-console/reporter/。非 Docker 的 Hub 上可以用 --retention-days 调整,范围是 1 到 90。短保留是有意的:它要回答的是本月到现在的问题。长期留存和按调用链下钻属于另一类工具的活,参见 自建 Langfuse 追踪 agent 调用链。

这里要说清“本地优先”保护了什么,又没有保护什么。它保护的是内容:代码和 prompt 留在本机,汇总的是计数。它没有改变的是供应商那一侧:控制台只是读你机器上的会话文件,它不修改、也不核销 Anthropic 那边的任何东西。你的用量和账单该怎么算,还是怎么算。

已经在跑网关的话:--interop 才是入口

如果你的 Claude Code 流量本来就穿过一个自建网关,控制台不必只靠读会话文件。加上 --interop,Hub 会开出一个本地 Prometheus /metrics 端点、一个 Claude Code 的 OpenTelemetry 接收端点,以及两条网关指标的接收路径。

这些端点各要一份凭证,而且读和写是分开的两个 scope。在 Hub 上签发:

node bin/agent-console.mjs metrics-token --scope read
node bin/agent-console.mjs metrics-token --scope ingest

全局安装了 CLI 的话,形式是 agent-console metrics-token --scope read。read 给抓取 /metrics 的 Prometheus,ingest 给往里推数据的导出器。凭证泄露了就轮换:

node bin/agent-console.mjs metrics-token --scope ingest --rotate

Hub 跑在容器里的时候,这些命令要在容器内执行,用法和源码安装一致,具体入口按仓库 docs/docker-hub.md 与 docs/INTEROP.md 给的方式来。

Prometheus 侧的抓取配置,凭证从文件读而不是写进配置:

scrape_configs:
  - job_name: agent-console
    static_configs: [{ targets: ["127.0.0.1:6787"] }]
    authorization: { type: Bearer, credentials_file: /path/to/agent-console-scrape-token }

网关那边,Kong 和 LiteLLM 都是把 Prometheus 文本格式推到各自的路径,/ingest/gateway/kong 和 /ingest/gateway/litellm。请求头要带 Content-Type: text/plain、X-Agent-Console-Interop: 1,以及 ingest scope 的 Authorization: Bearer 头。如果你还没有网关而正在考虑上一个,自建 LiteLLM 网关 的那套装法正好对得上这条接收路径。

仓库里带了一份 Grafana 面板 docs/grafana-agent-console.json,导入后选你的 Prometheus 数据源即可。

让 Claude Code 直接把指标发过来

Claude Code 自己支持 OpenTelemetry 导出,把它指向控制台的接收端点就行。在跑 claude 之前设置这些环境变量:

export CLAUDE_CODE_ENABLE_TELEMETRY=1
export OTEL_METRICS_EXPORTER=otlp
export OTEL_LOGS_EXPORTER=none
export OTEL_EXPORTER_OTLP_METRICS_PROTOCOL=http/json
export OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=http://127.0.0.1:6787/v1/metrics
export OTEL_EXPORTER_OTLP_METRICS_HEADERS='X-Agent-Console-Interop=1,Authorization=Bearer%20YOUR_INGEST_TOKEN'
claude

OTEL_LOGS_EXPORTER=none 是有意的:你要的是计数,不需要把 prompt 日志再送进一个系统。请求头那一行里的空格必须写成 %20,因为多个头之间用逗号分隔、键值之间用等号分隔,一个真实空格会让这一行解析错位。端口按你实际跑的改。如果 Hub 在 VPS 上而 Claude Code 在笔记本上,这里要填的是你能到达 Hub 的那个私有地址,不是 127.0.0.1。

设完之后打开你自己的控制台看:如果 interop 通了,这条来源会出现在页面上;如果没有,先回头查凭证的 scope 是不是用错了,read 的凭证是不能用来写入的。

和同事一起看花费的时候,先按 P

这是个小功能,但它决定了这个控制台能不能出现在投屏上。按 P,或者在命令面板(⌘K)里选 Present,控制台进入 presenting mode:项目名、机器名和人名都被替换成占位标签,比如 project A、machine 1,估算值和控制台地址也会被收敛。再按一次 P 退出。

为什么需要它:花费讨论天然是个跨团队会议,而项目目录名往往就是客户名或者一个还没发布的产品代号。把这个开关放在一个按键上,比要求每个人在开会前手工改配置要现实得多。

为什么可以信任一个指向你会话历史的工具

你要把它装在开发机上,它要读你的 transcript,这份信任需要证据,不能只靠一句“本地优先”。

v0.4.1 本身就是一次安全发布。对用户来说 0.4.0 的功能没有变化,改的是这些:代码扫描的告警被逐条从源追到汇,修掉两个缺陷(一个是 AGENT_CONSOLE_POLL_MS 可以被设成过大的值,现在上限一小时;另一个是保存的读取位置损坏时,现在会从头重读 transcript);发布脚本改成校验输入、不走 shell 执行,并把第三方 GitHub Action 钉到完整的 commit SHA;每个发布件附带 CycloneDX 格式的 SBOM(软件物料清单)并带 attestation;文档里加了威胁模型、数据流图、对 NIST SSDF(安全软件开发框架)的对应关系,以及一份企业安全问答。

拿到文件之后自己验。校验和用发布页上的 SHA256SUMS:

sha256sum -c SHA256SUMS --ignore-missing

macOS 上对应的写法是 shasum -a 256 -c SHA256SUMS --ignore-missing。再用 GitHub CLI 验构建来源和 SBOM:

gh attestation verify lockedinlabs-agent-console-0.4.1.tgz \
  -R LockedinLabs-AI/agent-console

gh attestation verify lockedinlabs-agent-console-0.4.1.tgz \
  -R LockedinLabs-AI/agent-console \
  --predicate-type https://cyclonedx.org/bom

平台差异也摆在明处。macOS 的 Intel 和 ARM 构建都有 Apple Developer ID 签名(Team ID 643FW3ZH6M)并经过 Apple 公证。Linux 的 x64 和 arm64 没有原生代码签名,你的凭据就是 SHA256SUMS 加构建 attestation。Windows x64 没有代码签名,install.ps1 靠 SHA256SUMS 校验。reporter 到 Hub 的连接做了 TLS(传输层安全)证书固定,join 链接里带着证书指纹,这就是前面说的“加入过程会核对证书”的实现。

数字是估算,不是发票

这一节请读完再去找财务讨论。

控制台按每次 API 响应计价,用一份带版本的价格表:里面是精确的模型 id、每百万 token 各类别的美元单价、每一行的核对日期,以及核对时看的厂商页面。表在 lib/collector/prices.json,你可以自己打开看。算法就是输入、输出、缓存读、缓存写这四类 token 各乘各自的单价,再除以一百万。

它自称的是“按 API 标价的标准估算,不是发票”。它不知道你的订阅,不知道你谈下来的折扣,不管批量定价,也不管数据驻留和税。所以包月订阅的读者要把这个数字理解成“如果按 API 标价买,这些 token 值多少钱”。它是一个相对量,用来横向比较项目和模型,不是用来核对账单。

另外三件容易误读的事。没有经过核对的模型或服务档次不贡献金额,会被单独报成未计价的 token,而不是记成 0,这样缺价格的行就不会悄悄稀释总额。缓存写里按 5 分钟和 1 小时生命周期分开的那两项,本身是缓存写的组成部分,不要再加回总数一遍;Codex 不上报生命周期,所以它的缓存写全部归到未知那一类。归属是按入组的设备算的,不是按谁在敲键盘,一台共享构建机上跑出来的花费会记在那台机器名下。

想把这些估算变成真正的预算护栏,控制台只负责看见的那一半,另一半在 给 VPS 上的 AI agent 设成本上限。如果你还在选工具,Claude Code 花费追踪工具的横向比较 里有几种定位不同的做法。

一套能长期跑的最小拓扑

把上面串起来,一套可以长期维持的配置是这样的。VPS 上用固定版本 tag 的 Docker 镜像跑 Hub,状态放在命名卷里。所有参与的机器进同一个私有网络。管理页面从不暴露到公网,你通过 SSH 隧道访问。每台机器用 join 链接入组一次,之后由用户级 systemd 单元维持上报,并且打开了 linger。保留期先用默认的 8 天,足够回答本月的问题。如果你已经有网关和 Prometheus,再加 --interop 和两个 scope 的凭证,把网关侧的口径也并进来。

这套东西里没有一条需要你把 API key 交给第三方,也没有一条需要把代码送出机器。这就是自建它的意义,也是它和一个 SaaS 花费面板的根本区别。

FAQ

Agent Console 会把我的代码或 prompt 上传吗?

不会。按官方说明,离开机器的只有计数:模型 id、精确到分钟的时间戳、token 计数和加盐哈希。项目目录名默认也不上报,要显式加 --share-project-names 才会带上。工具自身没有遥测、没有更新检查、没有崩溃上报。它读的是本地 transcript 文件(~/.claude/projects 和 ~/.codex/sessions),而且是只读。

它显示的花费能和我的 Anthropic 账单对上吗?

对不上,它自己也不声称能对上。它算的是按 API 标价的估算,不考虑订阅、协商价、批量定价、数据驻留和税。文档的原话是:transcript 是工具记录了什么的证据,不是供应商的发票。请把它当成横向比较项目、会话和模型的相对量,账单本身仍然看供应商的控制台。

为什么我用 VPS 的公网地址打不开控制台页面?

因为管理页面只监听回环地址,这是有意为之,而且 Hub 的默认配置会拒绝公网客户端。用 SSH 端口转发访问:ssh -N -L 6787:127.0.0.1:6787 youruser@your-vps,然后在本地浏览器打开 http://127.0.0.1:6787。上报端口 6788 同样面向私有网络,所以把参与的机器放进同一条 WireGuard 或 Tailscale 隧道,比开一个公网端口更符合它的设计。

机器重启之后要重新生成 join 链接吗?

不需要。join 链接只用于第一次入组,一次有效,最长一小时。之后 agent-console report 就能继续上报,所以正确做法是把它写成用户级 systemd 单元并启用 linger。只有在你用 agent-console leave 主动退出、或者在控制台里移除了这台机器之后,才需要一条新链接。

VPS 上的 Hub 会统计这台 VPS 自己的 Claude Code 会话吗?

不会,除非你在这台 VPS 上另外跑一个 reporter。Docker 镜像里的 Hub 带 --no-local,它不读所在机器的 Claude Code 或 Codex 历史,只做汇总。想把 VPS 上的会话也算进去,就在这台 VPS 上用 join 链接再入组一个 reporter,和其他机器一样。