Bundle
dsh-tokens-ci
tokens.ci usage dashboard for DeepSeek Harness: daily token activity, cost, token mix, model shares and leaderboard standing, as a settings page.
- Source
- winniesi
- License
- MIT
- Updated
- Updated 10 days ago
Readme
# dsh-tokens-ci
在 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(`dsh`)的设置面板里加一页 **Tokens.ci**:你的每日 AI 编程用量、费用、token 构成、模型占比和榜单排名。
```
设置 → Tokens.ci
```
数据来自 [tokens.ci](https://tokens.ci) 的公开 API——也就是 [这个榜单](https://tokens.ci/leaderboard?period=week) 背后的同一份数据。
---
## 一、先把自己的用量交上去
这一页显示的是 tokens.ci 上**你账号的公开数据**。那边什么都没有的话,这一页就是空的。所以先花两分钟把用量交上去。
流程是:装一个叫 **Tokens CLI** 的命令行工具 → 用 GitHub 登录 → 它扫描本机已经装过的 AI 编程客户端(**DeepSeek Harness 就在支持列表里**,一共 55 个),在**本机**把用量统计好,只把汇总数字提交上去(token 数、模型名、时间戳;不传 prompt、补全和文件内容)。
**macOS**(Homebrew):
```sh
brew install owo-network/brew/tokens
```
**Linux**(预编译二进制;root 时装到 `/usr/local/bin`,否则装到 `~/.local/bin`):
```sh
curl -fsSL https://tokens.ci/install.sh | sh
```
**Windows**:见 [tokens.ci/docs](https://tokens.ci/docs) 的 Windows 标签页。上面两条我实际验证过,Windows 那条没有,所以不在这里写。
装完三件事:
```sh
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)
```sh
# 从 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
```
装完**重载浏览器页面**。
卸载:
```sh
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 的链接、更换账号 |
---
## 四、账号从哪里来
**没有任何账号是写死在代码里的。** 宿主端按这个顺序决定报哪个账号:
1. **插件行的 `config.username`** —— 部署决定,页面永远覆盖不了它;
2. **你在页面上填过的名字**(存在 `$DSH_HOME/dsh-tokens-ci/settings.json`);
3. Git 全局配置里的 **`[github] user`**(约定俗成的那个键);
4. **`gh api user`** —— 装了 `gh` 且登录过时这是真正的 GitHub 身份(但 dsh 作为系统服务运行时 `PATH` 里常常没有 `gh`,所以只当加分项);
5. 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`):
```yaml
- 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 头),这也是为什么数据必须从宿主端过一遍。
### 数据上的两个坑(页面已经处理)
1. **`modelUsage[].percentage` 是费用占比,不是 token 占比。** 一个模型可以占 39% 的 token 却只占 19% 的份额。插件把两种占比都算出来并各自标注,模型卡让你自己选排序。
2. **榜单的 `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`。)
---
## 六、自检
```sh
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
Install
dsh plugin --profile web add github:winniesi/dsh-tokens-ci
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-tokens-ci from the hub
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.