dsh-tokens-ci
在 DeepSeek Harness(dsh)的设置面板里加一页 Tokens.ci:你的每日 AI 编程用量、费用、token 构成、模型占比和榜单排名。
设置 → Tokens.ci
数据来自 tokens.ci 的公开 API——也就是 这个榜单 背后的同一份数据。
一、先把自己的用量交上去
这一页显示的是 tokens.ci 上你账号的公开数据。那边什么都没有的话,这一页就是空的。所以先花两分钟把用量交上去。
流程是:装一个叫 Tokens CLI 的命令行工具 → 用 GitHub 登录 → 它扫描本机已经装过的 AI 编程客户端(DeepSeek Harness 就在支持列表里,一共 55 个),在本机把用量统计好,只把汇总数字提交上去(token 数、模型名、时间戳;不传 prompt、补全和文件内容)。
macOS(Homebrew):
brew install owo-network/brew/tokens
Linux(预编译二进制;root 时装到 /usr/local/bin,否则装到 ~/.local/bin):
curl -fsSL https://tokens.ci/install.sh | sh
Windows:见 tokens.ci/docs 的 Windows 标签页。上面两条我实际验证过,Windows 那条没有,所以不在这里写。
装完三件事:
tokens login # 用 GitHub 账号登录
tokens status # 看看已经交上去什么了
tokens serve # 让它在后台持续提交
macOS 上可以用 brew services start tokens 代替最后一条,交给系统托管,用量就一直是新的。
之后日常只会用到五条命令:tokens login / tokens submit / tokens serve / tokens status / tokens help。
提交后等一会儿再回来看这一页(服务端有缓存)。这一页显示的是那一刻已经提交上去的数字。
想要名字旁边那个认证勾
在 GitHub 个人资料的 Social accounts 里至少填两个社交链接(个人网站、X、LinkedIn、Mastodon、YouTube 任意两个)。tokens.ci 每天 03:20 UTC 重新读一次,所以加完要等下一次生效,重新登录不会加速。这个勾只表示"是个真实存在的人",跟排名无关。
手机上
官方有 iOS app(App Store 搜 Tokens),带锁屏和主屏小组件。
二、装这个插件
客户端半边声明的是 platform: web,和 dsh 自带的那些设置页一样——桌面端加载的就是这套客户端,所以两种端都适用。
要求 dsh >= 0.2.0-rc.1 < 0.3.0-0(安装和启动时会校验,详见文末「兼容性」)。
桌面端(Desktop app)
桌面端不要用 dsh plugin --profile desktop:desktop 这个 profile 由 Electron 应用独占,全局 npm 装的那个 CLI 会直接拒绝管理它(应用自带的那份命令可以,但用界面更省事)。用应用内置的插件管理器:
设置 → 插件(英文界面里这个分区叫 Built-in plugins)→ 点 Add plugin,来源选:
- Git repository — 填
github:winniesi/dsh-tokens-ci;或 - Local directory — 填这个仓库在本机的绝对路径(自己在改这个插件时用这个,改完重载页面即生效)
安装对话框最后会说 "Installed; it loads at the next start."——重启应用即可。它可能还会列出一批待批准的安装脚本;这个插件没有任何依赖、也没有安装脚本,所以正常不会出现。
命令行(web 及其他 profile)
# 从 GitHub 装(会钉在这次 push 的 commit 上)
dsh plugin --profile web add github:winniesi/dsh-tokens-ci
# 或者从本地目录装 —— 开发时用这个:pnpm 建的是 link,改完重载页面就生效
dsh plugin --profile web add /path/to/dsh-tokens-ci
装完重载浏览器页面。
卸载:
dsh plugin --profile web remove dsh-tokens-ci
改动哪一半需要什么才生效
本地目录安装是 link:(pnpm 直接把仓库链进 profile 的 node_modules),所以两边的新鲜度不一样:
| 改了 | 需要 |
|---|---|
client.js(页面、样式、文案) |
重载浏览器页面 |
package.json / cordis.patch.yml |
通常热重载自动重组合 |
index.js / tokens-ci.mjs(宿主端) |
重启 dsh |
profile 的 hmr 配置是 root: [],也就是只监听 profile 的清单和 patch 文件、不监听插件源码。所以宿主端的改动不会自己生效,systemctl --user restart dsh-web.service(或桌面端重启应用)才会重新加载。
三、这一页显示什么
| 区块 | 内容 |
|---|---|
| 身份行 | 头像、GitHub 名、加入时间、统计窗口的实际日期区间(Sep 25 – Oct 1, 2026)、今天的数字、周期切换 Week / Month / All time、刷新按钮 |
| 首次使用 | 没有任何东西能说明账号时,页面直接给出输入框让你填自己的 GitHub 用户名;页脚也有「更换账号」。填完存在本机($DSH_HOME/dsh-tokens-ci/settings.json,0600),下次自动用它 |
| 每日用量 | 页面的主角:窗口内每一天一根柱,今天高亮。可切 Token / 费用;鼠标悬停或 ← → Home End 逐日查看,读数条显示当天 token、费用、消息数和四类 token 明细 |
| 四个数字 | Token 总量(附活跃日均)、费用(附每百万 token 单价)、活跃天数(附连续天数)、消息数(附提示缓存命中率) |
| 榜单排名 | 名次 / 总人数、领先百分比(附领先人数)、占全站 token 比例(附全站总量) |
| Token 构成 | 堆叠条 + 图例:缓存读取 / 输入 / 输出 / 推理 / 缓存写入,各带绝对值与占比 |
| 模型 | 可在 按费用 / 按 Token 之间切换排序(两种排序真的不一样:这个账号就有一个模型占 39% 的 token、只占 19% 的账单),带占比条、token 量、费用 |
| 客户端 | 窗口内上报用量的工具(zcode / dsh / pi …)及各占份额 |
| 页脚 | 数据更新时间、CLI 版本、会话数(仅全部时间)、跳转 tokens.ci 的链接、更换账号 |
四、账号从哪里来
没有任何账号是写死在代码里的。 宿主端按这个顺序决定报哪个账号:
- 插件行的
config.username—— 部署决定,页面永远覆盖不了它; - 你在页面上填过的名字(存在
$DSH_HOME/dsh-tokens-ci/settings.json); - Git 全局配置里的
[github] user(约定俗成的那个键); gh api user—— 装了gh且登录过时这是真正的 GitHub 身份(但 dsh 作为系统服务运行时PATH里常常没有gh,所以只当加分项);- Git 全局配置里的
[user] name—— 最弱的信号,放在最后兜底。
第 3、5 步读的是 $XDG_CONFIG_HOME/git/config(默认 ~/.config/git/config)和 ~/.gitconfig 两个文件,按 Git 自己的顺序(后者优先)。这一点很容易搞错:不少机器上 ~/.gitconfig 根本不存在,Git 的全局身份在 ~/.config/git/config 里。
猜错的代价只是页面显示「@某名字 暂无用量」——那里有一个直接改名按钮,不用去翻配置文件。
固定账号或默认周期(可选)
都不是必须的。要固定,编辑 $DSH_HOME/profiles/web/cordis.patch.yml(不要改 bundle 自带的 cordis.patch.yml):
- id: tokens-ci
config:
username: your-github-handle # 固定账号;设置后页面不再显示输入框
period: month # 打开时的默认周期:week | month | all
改完重启 dsh。config.period 是真的生效的:页面首次请求不带周期,宿主用这里的值回答,页面再采用它。
五、它是怎么工作的
两个半边,各一个文件:
index.js(宿主) 在 Connection 的共享/api通道上注册一条精确 Fetch 路由POST /api/tokens-ci/report。它并发请求 tokens.ci 的两个文档(个人资料 + 榜单行)、归一化成一个扁平 JSON 报告,并按「账号 + 周期」缓存 60 秒。挂载时把最近一次好结果原子写入$DSH_HOME/dsh-tokens-ci/report-<period>.json(0600):冷启动先用手上的快照立刻出图并标记为「上次保存的读数」,同时后台重新读取,所以第一次打开几乎不用等。读取时会校验快照里的账号名,另一个账号的缓存不会被端上来。client.js(浏览器) 注册进settings.section槽位,通过ctx.connection.rpc.call拿报告,纯 React 渲染。样式全部走--dsw-*主题 token,明暗两套主题自动跟随,窄窗口下用容器查询退成单列;文案走 locale 服务,中英随界面切换。
浏览器不能直接请求 tokens.ci(响应没有 CORS 头),这也是为什么数据必须从宿主端过一遍。
数据上的两个坑(页面已经处理)
modelUsage[].percentage是费用占比,不是 token 占比。 一个模型可以占 39% 的 token 却只占 19% 的份额。插件把两种占比都算出来并各自标注,模型卡让你自己选排序。- 榜单的
month是自然月至今,而个人主页的month是最近 30 天。 月初两者能差三个数量级。插件会比较两边的 token 总数:窗口一致就正常显示名次;不一致(例如选了 Month)就明确标注「榜单按自然月统计」并给出解释,而不是把两个不同窗口的数字并排摆着。
(顺带:榜单自己的周期是 today / week / month / last month / all,个人主页是 week / month / all 且 month 是滚动 30 天——两边只有 week 和 all 定义一致,所以插件只用了这三个周期。另外 stats.sessionCount 只有全部时间有值,每日 totals.messages 上游恒为 0、真实值在 clients[].messages。)
六、自检
node test/all.mjs
三个互相独立的套件,共 210 多项断言:
test/live-report.mjs—— 拿真实的 tokens.ci API 跑三个周期,校验归一化结果自洽(每日之和 ≈ 总量、构成不超过总量、模型两种占比都在且确有分歧、未知账号返回状态而不是抛错、公开 HTML 不会漏进报告);另用注入的 fetch 钉住上游失败分类(404=账号不存在 / 500=http / HTML 200=格式错误 / socket=网络),并确认榜单那一路挂掉时报告仍然完整(只是没有排名)。test/host-route.mjs—— 用桩上下文挂载宿主端、抓住它注册的路由,用真实Request走完整条路径:信封形状、周期校验、内存缓存、快照交接、坏请求、本机无账号的降级,以及账号这条线(写入 / 落盘 0600 / 非法拒绝 / 清空 / 切号不串缓存 / 保存的账号跨重启生效),还有 Git 配置四个位置(XDG / 传统 /XDG_CONFIG_HOME覆盖 / 空机器)的读取优先级。test/render.mjs—— 按模块加载器的真实方式加载client.js,检查注册项(槽位、id、order、locale、label thunk、inject 面),然后用react-dom/server把 20 多种状态渲染成静态标记。断言没有NaN、undefined、未插值的占位符,图表每根柱都有可访问名且只有一个 tab 停留点,并校验样式表:每个选择器都带前缀、没有字面色、焦点环保留主题回退值、用到的每个--dsw-*token 都存在于当前安装的主题里、以及没有一条规则是死代码。
还有一类专门用来"找崩"的输入:缺 totals 的报告、not-a-date 的日期、没有用户名/头像/加入时间的报告、15 位数的 token 量、140 字符的模型名。浏览器里组件一旦抛异常会静默清空整个槽位,所以这些确实值得跑——它们已经抓出过四个真 bug。
渲染检查不等于截图。它证明的是组件树能构建、没有状态会抛异常、数字格式化后确实进了标记。视觉效果仍然需要你自己在页面上看一眼。
跑之前需要把插件装进某个 profile(或让 DSH_PROFILE_DIR / $DSH_HOME 指向一个有 React 的 profile)——React 不是这个包的依赖,检查借用的是 profile 里那一份,也就是页面实际用的那一份。
七、兼容性
- dsh
>=0.2.0-rc.1 <0.3.0-0。package.json的peerDependencies会在安装和启动时校验(engines.dsh只是声明性的)。注意^0.2.0这类范围匹配不到0.2.0-rc.2这样的预发布版本,所以这里用的是显式的预发布区间。 - 只用
settings.section、slots、connection.rpc、locale这些当前公开的接缝;不 import 任何@deepseek-ai/*运行时包(profile 安装的插件解析不到 dsh 自带的node_modules),只依赖 Node 内置模块。 - 手写 bundle,没有构建步骤:
client.js用React.createElement而不是 JSX。
License
MIT
No comments yet. Be the first to write one.