[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-95039":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":14,"contributorsCount":15,"subscribersCount":15,"size":15,"stars1d":15,"stars7d":13,"stars30d":13,"stars90d":15,"forks30d":15,"starsTrendScore":15,"compositeScore":16,"rankGlobal":10,"rankLanguage":10,"license":17,"archived":18,"fork":18,"defaultBranch":19,"hasWiki":20,"hasPages":18,"topics":21,"createdAt":10,"pushedAt":10,"updatedAt":28,"readmeContent":29,"aiSummary":30,"trendingCount":15,"starSnapshotCount":15,"syncStatus":14,"lastSyncTime":31,"discoverSource":32},95039,"TokenLedger","zh667\u002FTokenLedger","zh667","Relay-site attributed token usage for DeepSeek Harness — zero config, no credentials","https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fdsh-tokenledger",null,"JavaScript",118,9,2,0,48.4,"MIT License",false,"main",true,[22,23,24,25,26,27],"billing","deepseek-harness","dsh-plugin","newapi","sub2api","token-usage","2026-08-24 04:01:23","# TokenLedger\n\n[![License](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Flicense-MIT-2da44e)](LICENSE)\n[![DSH](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Fdsh-%3E%3D0.1.0--rc.6-1f6feb)](https:\u002F\u002Fgithub.com\u002Fdeepseek-ai\u002Fdeepseek-harness)\n\n把 [DeepSeek Harness](https:\u002F\u002Fgithub.com\u002Fdeepseek-ai\u002Fdeepseek-harness) 的 Token 用量算清楚，并归属到**实际服务这次请求的中转站**——不用配置，不用凭据。\n\nToken-usage accounting for the DeepSeek Harness Web GUI (`dsh web`), attributed\nto the relay site that served each request. Zero configuration.\n\n![TokenLedger 面板](docs\u002Fimages\u002Fpanel.png)\n\n> 展示图使用演示数据与本机模拟中转站；面板上的每个数字都由真实代码路径算出，只是数据是造的。插件不会把 API Key 或上游原始响应发送到浏览器。\n\n## 一眼看懂 \u002F At a glance\n\n| | 能力 | 说明 |\n| --- | --- | --- |\n| 🎯 | **中转站归属** | 按 provider 的 `baseURL` 归一化 origin 分组——同一站的多把 key 合成一行，站名就是域名，不是你自己起的路由别名 |\n| 📁 | **按项目归属** | 用量按会话启动时所在的目录分组——工作区注册过的用它的标题，没注册过的用目录名，子目录里起的会话照样算进去 |\n| 🔍 | **零配置发现** | 从宿主的 provider 配置里读出中转站，不用你再填一遍。只读 `baseURL`，绝不碰旁边的凭据 |\n| 💳 | **余额** | DeepSeek 官方、New API、Sub2API，每个账户一把普通 key 即可；不限额度的 key 报\"已用\"而不是假余额 |\n| ⏳ | **订阅配额** | 卖套餐不卖余额的家数越来越多——5 小时 \u002F 每周 \u002F 每月各自滚动、各自重置，每个窗口一条进度条和一个重置时刻 |\n| 📊 | **用量分析** | 今日\u002F本月\u002F累计三窗口、按站点\u002F模型下钻、缓存命中率、一年活跃度热力图（悬停看当天模型构成） |\n| 🧮 | **费用估算** | 生效日期分段的费率表、分桶计价、峰谷时段；未定价的模型显示破折号而不是 0 |\n| 🗂 | **导出与诊断** | CSV \u002F JSON 导出，索引健康度，归因不上的行数单独列出 |\n| 🔒 | **只读回环** | 两个端点仅接受回环 GET，且在 peer socket 地址上设防；从不读取提示词、工具参数或响应内容 |\n\n## 快速安装 \u002F Quick start\n\n需要 DeepSeek Harness `web` profile（`@deepseek-ai\u002Fdsh >= 0.1.0-rc.6`）。\n\n```bash\ndsh plugin --profile web add \"github:zh667\u002FTokenLedger\"\n```\n\n重启已经在跑的 `dsh web`，浏览器硬刷新。侧边栏底部会出现「用量账本」入口。\n\n升级或卸载：\n\n```bash\ndsh plugin --profile web update dsh-tokenledger\ndsh plugin --profile web remove dsh-tokenledger\n```\n\n重装同一个 spec 会重新解析，所以 `add` 一遍就能拿到最新的 `main`（实测过，不是推测）。\n\n**要钉某个版本，必须用完整的 40 位 commit SHA。** 包管理器把 ref 拿去和 `git ls-remote` 广播的引用列表比对，而那份列表里只有完整 SHA 和分支\u002F标签名——**短 SHA 匹配不到任何东西，会直接报 `Could not resolve`**：\n\n| spec | |\n| --- | --- |\n| `github:zh667\u002FTokenLedger` | ✅ |\n| `github:zh667\u002FTokenLedger#main` | ✅ |\n| `github:zh667\u002FTokenLedger#947247a` | ❌ 短 SHA 解析不了 |\n| `github:zh667\u002FTokenLedger#947247a86f0835f0e746695538569b1a76356dd1` | ✅ |\n\n如果 `package.json` 里已经被写进了一个解析不了的 spec，`add` 也会失败——它会先解析整份清单。改掉那一行再 `install` 即可。\n\n装完之后 `\u002Ftokenledger diagnostics` 会打印当前的路由归属和索引里的路由——**排查「我的中转站为什么不显示」看这里**，不用猜。\n\n装完即可用：没有要填的配置，也不需要任何凭据就能看到全部用量与中转站分布。余额是唯一用到 key 的地方，而那把 key 宿主已经替你存着了。\n\n## 命令 \u002F Commands\n\n面板能回答的问题，命令行都能回答——两边读的是同一批查询，不存在第二套聚合。\n\n```bash\n\u002Ftokenledger                      # 全部时间\n\u002Ftokenledger 7                    # 最近 7 天\n\u002Ftokenledger 30 api.example.com   # 某个中转站，最近 30 天\n\n\u002Ftokenledger site                 # 列出发现到的中转站\n\u002Ftokenledger site add \u003C路由名> \u003C地址>\n\u002Ftokenledger site rm \u003C路由名>\n\n\u002Ftokenledger export csv 30        # 导出\n\u002Ftokenledger diagnostics          # 索引健康度\n\u002Ftokenledger reindex              # 丢弃索引，从头重建\n```\n\n## 支持的账户类型 \u002F Providers\n\n| Provider | 模式 | 凭据 | 上游接口 |\n| --- | --- | --- | --- |\n| DeepSeek 官方 | 余额 | provider 的 `apiKeyEnv` | `\u002Fuser\u002Fbalance` |\n| New API 系（含 One API、VoAPI 等分支） | 额度 | provider 的 `apiKeyEnv` | `\u002Fapi\u002Fusage\u002Ftoken\u002F` + `\u002Fapi\u002Fstatus` |\n| Sub2API | 余额 \u002F 额度 \u002F 订阅 | provider 的 `apiKeyEnv` | `\u002Fv1\u002Fusage` |\n| Moonshot \u002F Kimi | 余额 | provider 的 `apiKeyEnv` | `\u002Fv1\u002Fusers\u002Fme\u002Fbalance` |\n| 智谱 GLM \u002F Z.ai | 余额 | provider 的 `apiKeyEnv` | `\u002Fapi\u002Fpaas\u002Fv4\u002Fbalance` |\n| OpenRouter | 余额 | **Management Key** | `\u002Fapi\u002Fv1\u002Fcredits` |\n| OpenCode Go | 订阅 | provider 的 `apiKeyEnv`，或本机 `auth.json` | `\u002Fzen\u002Fgo\u002Fv1\u002Fusage` |\n| Kimi For Coding | 订阅 | provider 的 `apiKeyEnv` | `\u002Fcoding\u002Fv1\u002Fusages` |\n| MiniMax Coding Plan | 订阅 | provider 的 `apiKeyEnv` | `\u002Fv1\u002Ftoken_plan\u002Fremains` |\n| 智谱 GLM \u002F Z.ai Coding Plan | 订阅 + 余额 | provider 的 `apiKeyEnv` | `\u002Fapi\u002Fmonitor\u002Fusage\u002Fquota\u002Flimit` |\n\n除 OpenRouter 外都只需要一把**普通 API key**——就是你已经配给那条路由、用来发请求的那把。OpenRouter 的额度接口只认 Management Key，用推理 key 会 401，面板会直接说明要哪一把，而不是丢一个 401 让你去查一把本来没问题的 key。\n\n**订阅**这一档没有余额可读，读到的是若干个滚动窗口：一条进度条、一个百分比、一个重置时刻。金额和窗口**可以同时存在**——Z.ai 就是既有钱包又有 Coding Plan，两样都读，任一边读不到也不影响另一边。\n\nOpenCode Go 那条的\"本机 `auth.json`\"是个便利：路由上没配 `apiKeyEnv` 时，会去读 OpenCode 客户端自己存 key 的那个文件（`~\u002F.local\u002Fshare\u002Fopencode\u002Fauth.json`），key 只发回它本来的来处。文件不存在、读不动、格式变了，一律当作\"没有凭据\"，安静退回，不会因此报错。\n\n这几家订阅接口都没有公开 schema。字段位置不对时，卡片会**说出它去哪儿找过**，而不是留一张空卡——那句话可以直接反馈给我们。\n\n中转站跑的是哪套程序由路由指纹自动判定，第一次查余额时探测一次并记住。厂商自己的域名不需要探测——origin 直接决定用哪套读法，而且同一厂商的多条路由会合并成一个账户（一个钱包），这跟中转站正好相反。\n\nNew API 的额度是**按 key** 的：同一个站上两把 key 是两份额度，面板分别列出。\n\nSub2API 的 `\u002Fv1\u002Fusage` 有三种形态，面板都认：key 配了总额度或速率限制的，读额度和 5 小时 \u002F 每日 \u002F 每周三个窗口；key 属于套餐分组的，读日\u002F周\u002F月周期；剩下的是钱包余额。**只有最后一种带 `balance` 字段**，所以前两种以前是一张空卡。\n\n## 配置 \u002F Configuration\n\n**通常不需要任何配置。** 中转站从宿主的 provider 设置里读出来。\n\n需要覆盖时，写进你已有的 `settings.yaml`（改完热更新，不用重启）：\n\n```yaml\ntokenledger:\n  # 只在自动发现看不到时才需要——比如组合里没挂 settings 服务，\n  # 或 provider 是 agent preset 在 agent.cordis.yml 里挂的\n  relays:\n    my-route: https:\u002F\u002Frelay.example.com\u002Fv1\n\n  # 费率表，用于费用估算；不配就显示破折号，不会猜\n  rates: []\n\n  # 探测中转站跑的是哪套程序。默认关闭，第一次查余额时会自动探一次\n  fingerprint: false\n```\n\n### 自己声明一个余额接口 \u002F Declared endpoints\n\n内置表覆盖不到的供应商，可以自己声明它的接口，不用等我们发版本：\n\n```yaml\ntokenledger:\n  endpoints:\n    - origin: https:\u002F\u002Frelay.example.com   # 必须是你已配置的某条 provider 的同源地址\n      displayName: 我的中转站\n      path: \u002Fapi\u002Fquota\n      # raw: true    # 有些控制台接口要裸密钥，不要 Bearer 前缀\n      fields:        # 值是响应 JSON 里的取值路径，不是表达式\n        total: data.balance\n        granted: data.total\n        used: data.used\n        currency: data.unit\n        plan: data.plan_name\n      windows:\n        - kind: weekly          # session \u002F daily \u002F weekly \u002F monthly \u002F billing\n          usedPercent: data.week.percent\n          resetsAt: data.week.reset_at\n```\n\n`fields` 和 `windows` 里写的是**取值路径**——描述\"数字在哪儿\"，不描述\"怎么取\"。没有表达式，也没有任何东西会被求值。路径写错只会让那个字段缺席，其余照常显示。\n\n这个功能等于让配置文件决定\"往哪儿发一个带密钥的请求\"，所以边界写死在代码里，不靠自觉：\n\n- **请求发往匹配到的那条 provider 的 origin**，`origin:` 字段只是用来找它的键。声明一个没配过的地址，不会产生任何请求。\n- `path` 必须是**单斜杠开头的绝对路径**。`\u002F\u002Fhost\u002Fx` 是协议相对 URL，会解析到别的主机上；构造完还会再校验一次 origin。\n- **只发 GET**，没有请求体，声明里的 method \u002F headers 一概不生效。\n- **密钥仍然只从那条 provider 自己的 `apiKeyEnv` 取**，声明不能指定任何凭据。\n- **跨源重定向直接失败**，不跟随——那是绕过第一条最省事的办法。\n- **响应体有大小上限和超时**。\n- **不能覆盖内置读法**：只在内置表答不上来时才轮到它。\n\n这类卡片会标注「自定义端点」，因为数字是你自己配的路径取出来的——取错了是配置问题，界面得让这一点看得出来。\n\n### 「未知路由」是什么\n\n中转站分布里可能出现一行「未知路由」。它的意思是：**这些请求走的路由，现在的 provider 配置里找不到了**——改过名、删掉了，或者当时用的是另一台机器上的配置。\n\n它和「直连\u002F官方」是两件事，分开是刻意的：\n\n- **直连\u002F官方** = 这条路由配置着，而且不指向任何中转站，请求确实直接打到厂商\n- **未知路由** = 我们**不知道**它去了哪\n\n以前这两种合并成一个桶，结果是一台真实机器上 88% 的 token 显示成「直连\u002F官方」，而其中很大一部分其实走了中转站。**把「读不到」显示成一个确定的答案，是这个项目在别处一直防着的错误，这里漏了一个口子。**\n\n**数据没丢，只是标签错了。** 汇总行是按 `(sessionId, day, site, provider, model)` 存的，**路由名一直在**。把那条路由重新配回去（同名即可），目录变化会触发索引重建，历史流量自动归位。\n\n## 按项目归属 \u002F Per-project attribution\n\n面板除了「按站点」「按模型」，还有一段「按项目」：这个月的钱，是哪个项目烧的。\n\n**归组的键是会话启动时所在的目录**，不是宿主的工作区 id。这一条是刻意的：\n\nDSH 的工作区（`ctx.workspace`）是**一个目录的登记**——`create(path)` 会先 `fs.realpath`，并且同一个规范路径只会有一条记录；会话是否属于它，判定是 `sessionPath(id) === record.path`，**严格相等**。所以一个工作区就是一个项目，它不是一个能装若干项目的容器。\n\n按工作区 id 归组会无声地漏掉两类用量：\n\n- **在子目录里起的会话**（`cd src && dsh`），cwd 和登记的路径不相等，谁也不属于；\n- **从没在界面里登记过工作区的项目**，压根不存在。\n\n这两类大概率是多数。所以：**按 cwd 归组，用工作区标题命名。** 跟中转站那条规矩是一个道理——站点按 `baseURL` 归一化出的 origin 分组，不按路由别名，因为叫 `deepseek` 的路由可能指向任何地方。**cwd 是实际发生的事，工作区标题是有人打的标签。**\n\n没有 cwd 的会话单独一行标成「未记录目录」，**不会被丢掉**——不然按项目的几行加起来就对不上总数，而看的人无从察觉。\n\n`workspace` 是**可选依赖**：组合里没挂这个服务，整段照常工作，只是每行显示目录名而不是工作区标题。\n\n## 正确性与数据口径 \u002F Correctness\n\n用量折叠有三个地方容易错，都有测试覆盖（`test\u002Fusage.test.js`）：\n\n**请求失败了照样扣费。** 用量除了挂在 `assistant\u002Fmessage` 上，也会从 `assistant\u002Fchunk` 的 `{type:'usage'}` 流出。请求在报出 usage 之后失败，就永远等不到 `assistant\u002Fmessage`——但供应商已经收钱了。只订阅 `assistant\u002Fmessage` 会系统性少算这部分。\n\n**同一个 `(turn, step)` 会被报告两次。** 后来的样本是**替换**前一个，不是累加；替换时必须从**原先归属的那一天和那条路由**里减回去。\n\n**孤儿 usage chunk 不带身份。** `assistant\u002Fmessage` 自带 provider 和 model，`StreamChunk` 的 usage 变体没有。失败请求那条记录要回退到最近一次 `request\u002Fheader`，且要认得 `reason: 'resume'`（进程重启会重发 header，那不是换模型）。归不上的记为显式 `unknown`，绝不猜。\n\n口径上：`inputTokens`、`cacheReadTokens`、`cacheWriteTokens` 三个桶**互斥**，相加才是计费输入；`reasoningTokens` 是 `outputTokens` 的**子集**，只做展示，加进总数就是重复计费。天按**宿主进程的本地时间**切分，面板上会标出是哪个时区。\n\n归属在**折叠时**写死进记录，历史永不重写。中转站集合发生变化时会丢弃索引并全量重建——否则新认出的站会显示成\"你刚开始用它\"。\n\n## 隐私与安全 \u002F Privacy & security\n\n- **从不读取内容。** 只有计数和标识符：token 数、模型名、provider 路由名、站点域名。提示词、工具参数、响应正文既不读也不存。\n- **凭据只在宿主侧。** key 由宿主的 credentials 服务按引用（`apiKeyEnv`）在请求时解析、用完即弃，始终走 `Authorization` 头，绝不进 URL 查询串。浏览器永远拿不到 key。\n- **回环防护。** 两个 HTTP 端点注册为 exact 路由，因此位于 RPC 信任边界**之外**，处理器自己设防：拒绝非 GET，并同时校验 **peer socket 地址**（不可伪造）与 Host 头。\n- **中转站指纹识别不用凭据**，靠路由的 404\u002F401 特征。\n\n## API\n\n浏览器面板读这两个端点，仅限回环 GET：\n\n| 端点 | 说明 |\n| --- | --- |\n| `GET \u002Fapi\u002Ftokenledger\u002Fusage?days=&site=` | 整个面板的数据：三窗口合计、按天\u002F模型\u002F站点、一年活跃度与逐日模型构成、账户列表、索引诊断 |\n| `GET \u002Fapi\u002Ftokenledger\u002Fbalance?account=` | 某个账户的余额 |\n\n包也可作为库使用，供 DSH 之外的消费者：\n\n```js\nimport { foldUsage, bySite, byModel } from \"dsh-tokenledger\";\nimport { LedgerStore } from \"dsh-tokenledger\u002Fstore\";\nimport { readBalance } from \"dsh-tokenledger\u002Fbalance\";\n```\n\n## 开发 \u002F Development\n\n```bash\nnpm test          # 含浏览器半边——它能在 Node 里被物化和测试\nnpm pack --dry-run\n```\n\n浏览器半边**没有构建步骤**：它是一个手写的 `__ModuleLoader__` bundle，React 由宿主作为 peer 提供，样式手写注入。因此它能在 Node 里被加载和测试。\n\n## 致谢 \u002F Credits\n\n- OpenRouter、Moonshot\u002FKimi 与 Z.ai\u002FGLM 的余额响应解析参考并改编自\n  [`Ychris12138\u002Fdsh-usage-stats`](https:\u002F\u002Fgithub.com\u002FYchris12138\u002Fdsh-usage-stats)（MIT）；TokenLedger\n  在此基础上增加了按 origin 识别、账户归并、Management Key 提示和币种防猜，详见 [NOTICE](NOTICE)\n- 热力图的分位数分级与悬停详情参考 [`xiufengsun\u002FTokenTracker`](https:\u002F\u002Fgithub.com\u002Fxiufengsun\u002FTokenTracker)（MIT）\n- New API 的计费口径读自 [`QuantumNous\u002Fnew-api`](https:\u002F\u002Fgithub.com\u002FQuantumNous\u002Fnew-api) 源码\n\n## 友情链接 \u002F Links\n\n感谢 [LINUX DO](https:\u002F\u002Flinux.do\u002F) 社区的帮助与支持。\n\n*Thanks to the [LINUX DO](https:\u002F\u002Flinux.do\u002F) community for their help and support.*\n\n\u003Cdetails>\n\u003Csummary>DSH 生态 \u002F The DSH ecosystem\u003C\u002Fsummary>\n\n**宿主 \u002F The host**\n\n- [`deepseek-ai\u002Fdeepseek-harness`](https:\u002F\u002Fgithub.com\u002Fdeepseek-ai\u002Fdeepseek-harness) —— 本插件运行其上的 agent harness\n\n**插件索引 \u002F Where to find plugins** —— 这几处收录了社区插件，装之前值得先逛一圈\n\n- [`awesome-dsh-plugin\u002Fawesome-dsh-plugin`](https:\u002F\u002Fgithub.com\u002Fawesome-dsh-plugin\u002Fawesome-dsh-plugin) —— 按功能分类的中英双语列表\n- [`AdamPlatin123\u002Fawesome-dsh-plugins`](https:\u002F\u002Fgithub.com\u002FAdamPlatin123\u002Fawesome-dsh-plugins) —— 自动扫描 `dsh-plugin` topic，带兼容性状态列\n- [`0xsline\u002Fawesome-deepseek-harness`](https:\u002F\u002Fgithub.com\u002F0xsline\u002Fawesome-deepseek-harness) —— 人工精选，另有自动生成的 `CATALOG.md`\n- [`bruc3van\u002Fawesome-dsh-plugin`](https:\u002F\u002Fgithub.com\u002Fbruc3van\u002Fawesome-dsh-plugin) —— 场景导航、入门套装与热度榜\n\n**这个面板读得懂的上游 \u002F Upstreams this panel reads**\n\n- [`QuantumNous\u002Fnew-api`](https:\u002F\u002Fgithub.com\u002FQuantumNous\u002Fnew-api) —— 中转站程序；面板的额度换算口径是从它的路由与计费源码里读出来的\n\n\u003C\u002Fdetails>\n\n> ⚠️ **非官方声明**：TokenLedger 是独立的第三方社区项目，与 DeepSeek 无隶属、赞助或背书关系。以上友链亦不代表任何隶属、赞助或背书关系。「DeepSeek」及相关商标归其权利人所有。\n\n## License\n\n[MIT](LICENSE)\n","TokenLedger 是一款专为 DeepSeek Harness Web GUI 设计的零配置 Token 用量归因与计量插件，自动将 API 调用产生的 token 消耗按实际中转站（如 One API、Sub2API 等代理服务）和本地工作区进行归属统计。核心功能包括：基于 provider baseURL 的中转站自动识别、多账户类型（DeepSeek 官方\u002F新 API\u002F Sub2API）余额与订阅配额实时同步、分时段\u002F分模型\u002F分站点的用量分析与费用估算、只读回环接口保障数据安全，以及 CSV\u002FJSON 导出与诊断能力。适用于使用 DeepSeek Harness 搭建多中转站代理环境的开发者与团队，用于精细化监控、成本分摊与资源治理。","2026-08-20 02:30:09","CREATED_QUERY"]