可审计的 DSH token 账本:把持久化会话日志折算成重启安全的总量,可从原始日志重算并比对差异,并按工作区、会话、模型或厂商生成账单,可导出 CSV 或 JSON。
安装
# npm 包(预构建)
dsh plugin --profile web add @chenmiao8563/dsh-token-ledger
# GitHub 源码(首次需按提示配置 allowBuilds 构建授权后重试)
dsh plugin --profile web add github:chenmiao8563/dsh-token-ledger
装任何插件都等于在你的机器上跑第三方代码,权限和你本人一样大——能读你的文件、用你的凭据、访问网络,工具审批管不到它。GitHub 来源的插件还会在安装时执行构建脚本——pnpm 默认拦截,所以安装可能停在 ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED 或 ERR_PNPM_IGNORED_BUILDS;dsh 会打印出需要添加的确切键名,把它加进该 profile 的 pnpm-workspace.yaml 的 allowBuilds 下,重跑一次即可装上。放行构建本身就是一次信任判断:请只安装可信来源,并尽量锁定 commit(github:owner/repo#sha)。
README
为 DeepSeek Harness 提供透明、可审计的 token 记账。
English | 中文
这是什么
这是一本账本,不是一块仪表盘。它把 DSH 的持久会话日志折叠成 token 用量,保证重启不丢, 而且——这是关键——允许你从原始日志重算同一批数字并与之对账。
$ dsh-token-ledger audit
scanned 139 session log(s), 344203 events, 29 fork(s), 0 unreadable
stored 6901 calls 1205685663 tokens
recomputed 6901 calls 1205685663 tokens
audit: match — the stored ledger equals a fresh fold of the raw logs
只想看漂亮的图表,市面上有十几个同类插件。这个插件是给你需要为这个数字辩护的时候用的。
从 0.5 起,设置页有三个页签:概览(账本本身,含每档区间的预计花费)、费率(各厂商 最新模型的价格表与实时美元汇率)、账单(把同一批用量折算成钱——按工作区 / 会话 / 模型 / 供应商四个分组自上而下展开,可整页导出 CSV 或 JSON)。账单页会写明价格来源、汇率, 以及每一个没能套上价格的模型,所以这个数字是可以核对的;概览页只数 Token, 费用是拿账单那套算法算出来的估算值。详见设置页。
为什么别的插件装不上时它能装上
| 特性 | 为什么重要 |
|---|---|
| 没有任何东西会被装给你 | 无运行时依赖;声明的 peer 全都是可选宿主包——DSH 内部包的版本漂移不可能把第二份 harness 拖进你的 profile。 |
| 零安装脚本 | 直接用 git URL dsh plugin add 即可——pnpm 没有构建要拦,你也不必去 allowBuilds 里加白名单。 |
只 import node: |
宿主端可以从任意 profile(web / desktop / headless / TUI)加载,不需要解析任何包。 |
| 不碰模型可见面 | 它不注册任何提示词段、消息或工具,因此不会改变请求前缀,也不会损害 KV cache 复用。 |
| 故障降级 | 每个钩子都有保护。账本出问题只记一条警告,绝不让会话失败。 |
安装
# 从 npm
dsh plugin --profile web add @chenmiao8563/dsh-token-ledger
# 从 git URL(不涉及任何构建步骤)
dsh plugin --profile web add github:chenmiao8563/dsh-token-ledger
# 从本地仓库
dsh plugin --profile web add /absolute/path/to/dsh-token-ledger
npm 包名带 scope,是因为 npm 会把分隔符归一化后比较,无 scope 的
dsh-token-ledger 被判为与已有包过于相似而拒绝发布。CLI 命令名仍然是
dsh-token-ledger。
重启 DSH,然后确认那一行进去了:
dsh --profile web --dump-config | grep token-ledger
在对话里用 /tokens:
/tokens
/tokens export # 把 CSV 与 JSON 写到 <DSH_HOME>/token-ledger/exports/
/tokens json # 原始快照
/tokens path # 账本文件位置
账本写在 <DSH_HOME>/token-ledger/ledger.json。
需要的 DSH 版本
DSH ^0.1.2-rc.1 —— 0.1.2-rc.1 是本插件开发与实测所针对的版本,也就是这个区间的下界。
^ 把上界放在 0.2.0:这里用到的接口面是在 0.1.2 这一线上看过的,不是对未来版本测过的。
这个要求按生态里真正会被读取的方式声明——写成对两半各自绑定到的五个宿主包的
peerDependencies:
| Peer | 为什么要写它 |
|---|---|
@deepseek-ai/dsh-session-persistence |
它提供 sessionPersistence,账本的用量数据来自这里。 |
@deepseek-ai/dsh-commands |
它提供 commands,/tokens 靠它注册。 |
@deepseek-ai/dsh-host-webserver |
它提供 webServer,设置页那三个路由靠它提供。 |
@deepseek-ai/dsh-client-locale |
浏览器半边注入它来拿翻译后的文案。 |
@deepseek-ai/dsh-client-ui-settings-general |
浏览器半边注入它,把分区加进设置侧边栏。 |
五个都是可选(optional),这是刻意的而不是含糊其辞:DSH 不会把宿主包提升进 profile 的
node_modules,所以写成必需 peer 只会在每次安装时报告一个"未满足"、却什么也不说明。可选也正是
插件自身行为的如实描述——没有 web 服务器的 profile 少掉设置页,/tokens 与 CLI 照常可用;
headless 或 TUI profile 则根本没有那两个客户端包。
这个声明之所以有用,是因为 DSH 会拿实际在跑的那套安装去解析它:profile 检查器先看插件自己的
node_modules,再看 profile 目录树,最后回落到 DSH 安装目录本身
(node_modules/dshmarket/lib/check.js),把解析到的版本与上面的区间比对——于是不受支持的
harness 会被报出来,而不是悄悄挂载。早先的版本声明的是 dsh.compatibility.dsh,而 DSH 里
没有任何代码读它;1.0 把它删掉,而不是留一个看起来像承诺、实际不是的字段。
这些声明对消费者零成本:可选 peer 不会被安装,包本身依旧没有运行时依赖、没有安装脚本。
计数规则
只有知道它到底在数什么,这些数字才有用。
| 规则 | 行为 |
|---|---|
| 只认成功锚点 | 用量取自 assistant/message(已完成的步)与 compaction/summary(一次压缩调用)。失败或被取消的尝试不会追加这两种事件,因此永远不计入。 |
| 流式样本是替换,不是相加 | 同一步先出 usage chunk、后出最终消息时,最终值替换早期样本——无论哪种情况都算一次调用。 |
totalTokens 是推导出来的 |
它是四个桶之和,绝不采信提供方自己的总量字段。在 15,778 份真实用量报告上两者完全一致,而推导能让桶与总量在构造上永远自洽。 |
| 推理 token 是子集 | 单独报告,绝不相加进总量,因为它本来就在 outputTokens 里面。 |
| 按本地日历日 | 是你所在时区的日,不是 UTC 日。 |
| fork 切、resume 不切 | 见下。 |
fork 与 resume 的区别
存储的日志可能以一段"已经记录过的历史"开头。有两种完全不同的情况会产生这种前缀, 把它们搞混是用量插件静默算错的最常见原因:
- fork(有
parentSession):前缀是父会话的历史,已经在父会话自己的日志里计过。 在这里再计一次就是重复计入——必须切掉。 - resume(没有父会话):前缀是这个会话自己更早的历史,只存了一次。 切掉就是漏计——不能切。
这不是猜的。对照真实日志:所有父日志仍在磁盘上的 fork 会话,其边界标记之前的用量指纹 都包含在父会话里;而带同样标记但没有父会话的日志,其前缀在文件后半段从未重复出现。 在一个真实的 139 会话 home 上,这个区别意味着 7,992 万 token 的重复计入——那是 "把每个日志都完整计一遍"的天真做法会报出来的数字。
命令行
不需要 DSH 在运行——它直接读原始日志。
dsh-token-ledger [summary] [选项] 打印已存账本(默认)
dsh-token-ledger audit [选项] 从原始日志重算并对比
dsh-token-ledger rebuild [选项] 从原始日志重算
dsh-token-ledger export [选项] 导出 CSV 与 JSON
--home <path> DSH home(默认 $DSH_HOME,其次 ~/.dsh)
--ledger <path> 要读写的账本文件
--out <path> 导出目录
--days <n> 摘要显示天数(默认 7)
--models <n> 摘要显示模型数(默认 5)
--write 配合 rebuild:覆盖已存账本
--json 机器可读输出
--quiet 抑制人类可读摘要,只保留退出码
退出码:0 成功或审计通过,1 审计发现真实差异,2 用法错误或输入不可读。
因此它可以挂进定时任务。
审计能区分两类差异
运行中的宿主是按防抖写入账本的,所以活跃会话比文件"新一点"是常态。
把这种情况报成数据损坏,审计就废了。账本自己的 updatedAt 可以裁决:
- 有差异的会话,其最新事件比账本更新 → 只是还在跑 → 通过,并报出尚未落盘的量;
- 有差异的会话,其最新事件早于账本,或者某条日/模型行与它本该汇总的折叠结果矛盾 → 不通过,退出码 1。
audit: match — 1 session(s) advanced after the ledger was written
(1 calls, 5100 tokens not yet flushed)
设置页
浏览器端会在设置侧边栏注册一个 用量账本 分区,顶部有 概览 / 费率 / 账单 三个按钮。
概览
- 区间总计:本月 / 本年 / 近 7 天 / 全部四档可切换,每档显示 Token 总计、缓存命中率、
调用次数、预计花费,以及背后的四桶明细。「全部」就是账本里所有天数,起止跟着第一条记录走。
缓存命中率定义为
缓存读入 / (缓存读入 + 未命中输入)——即输入中被缓存吸收的比例,因此完全不缓存的 路由读数是 0%,而不是空白。 - 预计花费用的就是账单那套算法:由宿主针对概览的每个区间各算一次,因此「本月」这一个
数字和账单页本月那一行是同一个数,而不是两套算法今天恰好对得上。以人民币显示、保留两位
小数,并注明换算用的汇率。宿主还没拿到价格时显示的是短横线并说明原因,而不是一个理直气壮的
¥0.00;账单算不出价的那部分 Token 会写在它缺席的那个数字下面。 - 今日实时:今天的 Token、命中率、调用次数与预计花费,每分钟刷新一次。
- 用量热力图:年 / 月 / 一周三档可切换;年和月是热力图,切到一周改为每天一行 的横向条形图。 热力档位相对窗口内最忙的一天取平方根,避免某一天特别大把其余全部压成最淡档。 月视图右侧另给本月小结:最忙的一天、最轻松的一天(仅工作日)与工作到最晚的一天 (仅工作日,按当天最后一次调用的时刻比较)。
- 按模型:各模型的用量、各自命中率,以及一条展示用量构成的堆叠条,下方图例只列出真正画出来的 分段。某个桶在整个 payload 里都是 0(比如厂商不公布缓存写价格时的缓存写入),它既不会出现在条里, 也不会出现在图例里——图例里有一个条形图中永远找不到的颜色,那不是信息而是谜题。
费率
- 美元汇率单独一个框,保留四位小数,并标注来源与获取时间。这一页是价目表: 它只报价格、并按汇率换算显示;把价格乘上用量做成费用的是账单页,页面上也写明了这一点。
- 各厂商最新模型:每个厂商只列最新的 2~3 个,给出输入 / 输出 / 缓存读 / 缓存写
每百万 Token 的价格。价格是各家自己公布的价目,取自一份按厂商整理的价格清单,并按
上方汇率换算成人民币显示;每个换算后的单元格悬停时仍能看到原始的美元报价。没取到汇率时
表格回退成美元并在列头标明单位,而不是硬凑一个自己都站不住的数字。厂商没有公布的价格显示为
短横线(「没有标价」和「免费」是两回事);公布为
0的会额外标注「不一定是免费」,因为有些 平台按 GPU 小时计费、根本不按 Token 标价。 - 价格来自厂商自己公布的地方:这些厂商没有任何一家提供价格 API——它们的模型列表接口
只返回模型 id、不含价格,价格只存在于官网 pricing 页面。能直接读的就去读:DeepSeek、
Z.ai、腾讯三家的价格是当场从他们自己的定价页解析出来的,分组前面带「官方」标记;其余来自一份
按厂商整理的公开数据集(models.dev)。配置
rates.source: openrouter可把整张表切成该网关自己的报价——覆盖模型更多,但不是各家官方价目。页面会写明哪家是哪种来源。 - DeepSeek 区分高峰与空闲时段:它家页面按人民币报价,分缓存命中输入 / 缓存未命中输入 / 输出 三条线,每条都有高峰与空闲两列——空闲时段价格为高峰的一半,高峰时段是北京时间周一至周五 9:00-12:00、14:00-18:00,其余为空闲。因此每个时段各占一行并带标签,时段说明直接引用厂商原文。 用人民币标价的厂商就按人民币显示,不做换算。
- 只列这 12 家,并用官方标识:OpenAI、Anthropic、Google、DeepSeek、Qwen、xAI、Z.ai、Kimi、
MiniMax、腾讯、小米、字节跳动。图形取自各家官网或 Simple Icons 的官方标识,内联进包里、页面
加载时不联网;它们是各家的商标,仅用于标识价格属于谁。名单外的厂商若数据源里有也会照常出现,
rates.vendors: 0则列出全部厂商。 - 手动填写:任何一项价格、以及汇率本身,都可以直接改写——按表格当前显示的币种填写。 手动值优先级最高,不会被后续刷新覆盖,确实改动了数值的行在表格里带「手动」标记,也可以一键 「恢复自动」或「清除」;若填入的值与自动获取的相同,则不带标记——此时刷新本来就不会改变这一行。 另有一行自由录入,用于自动获取没覆盖到的模型。
- 随时刷新,逐个确认:价格卡片上有刷新按钮,立即执行一次与定时任务相同的抓取,不必再等半小时; 某个源没连上时页面会说明这次刷新失败,而不是报「已保存」。当你手填过价的模型官方价变了,页面会 单独列出该模型、并排显示两个价格和一个问题——用官方价,还是保留你的。选「用官方价」会删掉这个 手填值,把该字段交还给数据源,而不是把它冻结在今天的价格上;选「保留我的」则记录下当时看到的 官方价,同样的分歧不会再问,直到官价真的再次变动。
- 离线是一种状态,不是错误:宿主每 30 分钟刷新一次价格与汇率。刷新失败不会清空
已有结果,而是把这次尝试标记为失败,因此被防火墙挡住时看到的是「上次已知价格 +
可见的陈旧时间」,而不是一片空白。从未联网的宿主会明确说明并引导到手动录入;
配置
rates: false的宿主完全不发请求,页面就是一份你自己维护的价目表。
账单
同一批用量,按账单的读法分组。四个分组直接自上而下展开——工作区、会话、模型、供应商—— 而不是藏在切换按钮后面,因为账单要回答的通常是「比较」类问题;每个分组各有自己的时间段, 所以「本月的按工作区」可以和「今日的按模型」同时摆在页面上。
- 每行显示:分组名、实际花费、缓存命中输入、未命中输入、输出、缓存命中率、 调用次数。两个输入列分开列,是因为凡是给它们定价的厂商,两者价格都不一样。缓存写入 放在每张表下面一行说明,而不是做成一个几乎处处是短横线的列。
- 名字是有意义的:工作区一行就是账本记录的
cwd;会话一行显示工作区/会话名,用的正是 DSH 自己给这个会话起的名字(DSH 没起过名的回退成会话 id, 完整 id 与路径放在悬停提示里);模型一行显示提供商/模型——就是账本记录的那条路由, 与概览页「按模型」列出的名字完全一致——具体按哪份价目计价放在悬停提示里。 - 供应商一行是「你接入的提供商」,不是一份价目表:
bos、qwen-plan、deepseek-official、zai是接入的端点,deepseek、qwen、z-ai是这些 Token 按谁家的 价目计价。所以这一行显示的是提供商——用设置页给它的显示名(BOS-API而不是bos, 这个名字是从 harness 的 settings 文件里读出来的,绝不写入),下面再写明计价来源 (计价来源 deepseek)。因此同一个模型经由两个端点调用,就是两行——这正是供应商账单要回答的问题。 - 每个分组五个时间段:本月 / 本年 / 7 天 / 今日 / 全部,与概览页同一套定义。
- 会话列表在页面上汇总,导出的文件不汇总:只问了一句就归档的会话不值得单独占一行,而一台机器会攒下几百个,
所以花费很少或几乎没用过的会话——低于 ¥1,或调用少于 10 次——在页面上合并成一行
(
其余 N 个低频会话,带「汇总」标记,各列是它们的合计)。总额一分不动:各组行之和仍等于分组合计。 表格下方会写明汇总了多少个会话、规则是什么,并说明导出永远是完整明细——format=csv里每个会话都在。 两种情况永不折叠:算不出价的会话,以及整张表每一行都符合条件时(那种情况下列表本来就短,折叠反而把列表藏起来)。bill.smallSessionCost、bill.smallSessionCalls、bill.foldSmallSessions: false可改规则或关掉。 只有「会话」这一维度会折叠,工作区 / 模型 / 供应商本来就只有几行。 - 右上角一个导出,导出全部:CSV 或 JSON,包含四个分组 × 五个时间段的全部信息,
每行带币种,每个分组各自一行
TOTAL,而且刻意不给跨时间段的总计——这些时间段互相重叠, 把今日加进本周再加进本月会把同一批 Token 数四遍。CSV 文件开头带 UTF-8 BOM: 没有这三个字节,中文版 Excel 会按系统代码页打开它,会话名「编写统计」会显示成 「缂栧啓缁熻」;JSON 导出不带 BOM,因为 JSON 解析器会直接拒绝开头有 BOM 的文件。 - 按套餐计费且不重复计费:配置里的
subscriptions填月度套餐({ vendor, plan, amount, currency, startedAt?, endedAt?, note? }),月费会按账单覆盖的 天数摊分——¥199 的套餐算到 1 日至 10 日就是 ¥66.33,不是 ¥199。上了套餐的厂商按套餐计费, 套餐覆盖掉的用量在它旁边单独显示而不是相加:这两个数分开正是为了不把同一批调用算两遍。 套餐是一笔钱,所以在「同一厂商出现在多行」的分组里(工作区 / 会话 / 模型),它按各行用量 占比摊分到这些行上,这样每个分组的各行之和就等于该分组的合计;被套餐摊到的行会在费用 下面写明「其中套餐摊分 ¥…」。区间内完全没有用量、但确实买了套餐的厂商照样计入,因为钱确实花了。 套餐的vendor既可以写提供商、也可以写计价厂商(bos或deepseek),两种都会落到它覆盖的用量上。 - 算不出价的不按 0 计:每个未定价的模型会连同 Token 数、调用次数和原因一起列出—— 该模型没有价格、同名模型被两家同时公布(不敢猜是哪家)、或价格币种无法换算。 靠名字(而非 id)匹配上的价格也会列出,方便核对而不是盲信。
- 按时段报价的厂商按时段结算:DeepSeek 高峰与空闲价格不同,账本记录了每次调用落在 哪一侧——北京时间周一至周五 9:00-12:00、14:00-18:00 为高峰——于是两边各按自己的价格算, 而不是统统按其中一边。
- 账单是估算,并且写在页面上:它是「会话日志记下的 Token × 路由真正指向的那个模型 的公开价目」。它不代表厂商最终开给你的账单——失败重试、部分返回的请求都会不一样; 套餐自带的额度也只按你在配置里填的来。
页面读三个仅限回环的路由:概览用 GET /api/token-ledger/summary,价格与汇率用
GET|POST /api/token-ledger/rates,账单用 GET /api/token-ledger/bill。
概览每分钟轮询一次,价格与账单只在切到各自页面时才拉取。
三个路由都会拒绝非回环来源,因此即使 web 服务器绑定到 0.0.0.0 也不会外泄;
写入那半边还额外要求 JSON 内容类型(跨站表单发不出这种类型),并把请求体限制在 256 KiB。
它需要带 web 服务器的 profile(web 或 desktop)。没有的话 /tokens 与 CLI
照常可用,分区会明确说明而不是直接失败。注意手工填价格是在那个页面上填的:目前
还没有等价命令行入口,所以 headless profile 能读用量账本,但没法录入价格。
三个视图都刻意不通过设置命名空间下发:那需要 schema(一个真实依赖,而本包零依赖),
而且每次防抖都会用可推导、可重放的数据重写一遍 settings.yaml。
账本文件始终是唯一的记录来源,价格存在它旁边的 rates.json 里。
配置
按 id 覆盖组合条目:
- id: token-ledger
config:
ledgerPath: 'D:/dsh/ledger.json' # 默认 <DSH_HOME>/token-ledger/ledger.json
backfill: false # 默认 true——启动时折叠已存历史
rates: false # 默认 true——false 时完全不发网络请求
# rates 也可以写成对象:
# rates:
# source: modelsdev # 默认按厂商数据集;改成 openrouter 则用网关报价
# refreshIntervalMs: 1800000 # 默认 30 分钟
# perVendor: 3 # 默认每厂商取最新 3 个
# vendors: 15 # 默认最多列几家厂商;填 0 则列出全部
# modelsUrl: 'https://…' # 默认随 source——https://models.dev/api.json
# fxUrl: 'https://…/latest/USD' # 默认 open.er-api.com
# 月度套餐,供账单页的「按套餐」分组使用。每个套餐按账单覆盖的天数摊分,
# 因此不满一个月就只计一部分。
subscriptions:
- vendor: deepseek # 这个套餐覆盖哪家厂商的用量
plan: 'DeepSeek 包月' # 账单上显示的套餐名
amount: 199 # 每月费用
currency: CNY # 该费用的币种
startedAt: '2026-09-01' # 可选:这天之前不计
endedAt: null # 可选:这天之后不计
note: null # 可选:备注,随套餐一起显示
# 页面如何缩短「会话」列表。导出从不缩短,所以这三项只影响页面上显示什么。
# bill:
# foldSmallSessions: true # 默认 true;设为 false 则页面上也列出每一个会话
# smallSessionCost: 1 # 默认 1,按账单当前币种
# smallSessionCalls: 10 # 默认 10
rates: false 会彻底关掉定价功能的联网,只保留手动填写的值——这正是严格离线环境
需要的配置。费率页本身照常可用。
它刻意不做的事
- 概览页给出写明口径的预计花费,账单页给出逐行明细。 概览页在区间总计与今日实时里各显示 一个预计花费(人民币、两位小数),用的就是账单那套算法;账单页则给出每个分组、每个时间段 的逐行费用。两者都会写明价格来源、换算用的汇率、每一个靠名字匹配上的价格,以及每一个没能 定价的模型,因此这些数字是用来核对的,不是用来信的。它们都不是厂商开给你的账单, 也不声称是——具体漏了什么见上面的「账单」一节。
- 不内置价目表。 价格要么从一个可选的源联网取(默认是各家官方价目),要么你自己填。 内置一份价目表几周内就会过期,而且每次更新都得发一个版本。
- 不注册面向模型的工具。 工具 schema 会在每次请求上花提示词 token 并改变缓存前缀——
对一个 token 记账插件来说这很荒唐。人类场景由
/tokens与 CLI 覆盖。
兼容性
- Node: ≥ 22.15.0(CLI 需要解码 Zstandard 帧)。宿主端本身没有版本相关要求。
- DSH:
^0.1.2-rc.1,以对五个宿主包的可选 peer 形式声明——见 需要的 DSH 版本。在0.1.2-rc.1上验证过。所用到的接口面——ctx.on、ctx.inject、ctx.get、ctx.effect、commands.register、sessionPersistence.list()/inspect()——在0.1.2线上一致。 - Profile: 任意。没有 profile 相关代码。
- 网络: 费率页会通过 HTTPS 取价格与美元汇率,但这完全是可选的:没有外网时仍然
提供本机缓存的上次结果,手动填写的值照常可用,
rates: false则连请求都省掉。
卸载
dsh plugin --profile web remove @chenmiao8563/dsh-token-ledger
账本文件是故意不删的——要清空历史请自行删除 <DSH_HOME>/token-ledger/。
开发
npm install # 两个 devDependency:react 与 react-dom,供渲染测试使用
npm test # 288 个测试,14 个文件
npm run verify # 打包不变式(零依赖、无安装脚本、无裸模块说明符)
npm test 使用 Node 内置测试运行器。在禁止逐文件 spawn 子进程的受限环境里,
改用 npm run test:single-process。
React 只是 devDependency,消费者永远不会安装它:本包不带任何运行时依赖、不带任何
安装脚本,声明的 peer 全是可选声明、没有任何东西会去抓取,而 pnpm 不会为依赖安装其
devDependencies。它在这里的作用是让
浏览器端能用真库渲染并断言——这能抓到替身抓不到的东西:hook 顺序违规与非法
DOM 属性,React 会报出来,而手写的 createElement 会默默接受。没装它时这些渲染
测试会带明确原因跳过而不是失败,所以全新克隆、无网络也能跑 npm test。
改动的生效方式。 两半都是在插件挂载时读取的,所以改 lib/index.js 或
lib/client.js 都需要重启 DSH(或重载插件)——刷新页面不够,因为 bundle 的 rev
在挂载时算定,URL 没变浏览器就继续用缓存那份。这是量出来的不是猜的:在隔离 host
运行时,改 lib/client.js 之后被服务的 bundle 与它的 rev(d683dd523466)都纹丝不动;
重启后 rev 变成 f55ee321db50 并提供了新内容。导出的是文件,所以如果你手上的那份
来自某次修复之前,看时间戳就知道。
实际验证了什么、怎么验证的(包括 fork 规则背后的证据)见 docs/VERIFICATION.md。
发布
打一个 tag 就是发布。不需要在任何地方存放 token。
git tag v1.0.0 && git push origin v1.0.0
release.yml 会跑测试与打包检查、确认 tag 与 package.json 一致、用 OIDC
trusted publishing 发布、把 tarball 挂到 GitHub Release 上,并在报告成功之前
断言该版本确实已在 registry 上——所以绿色意味着已发布,而不只是尝试过。
最后这条断言不是装饰。这条流水线从 v0.5.0 到 v0.8.0 每个 tag 都失败,报的是
404 Not Found - PUT——它说的是"包不存在",而真正的问题是缺凭据;而且失败之后
registry 会有几分钟看起来是空的,尽管发布其实已经成功。两个原因都已在 workflow 里
加了防线:
- npm CLI 必须 ≥ 11.5.1。 Node 22 自带 npm 10,根本做不了 OIDC 交换。workflow 跑在 Node 24 上,并把版本打进日志。
- 不能用
actions/setup-node的registry-url输入。 它会往.npmrc写//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN},npm 看到这一行就认定 "已有凭据",直接跳过 trusted publishing,报ENEEDAUTH——在一个本就不持有凭据的 任务里,这读起来像"你忘了登录"。发布前现在有一道步骤:只要.npmrc里出现_authToken就大声失败。
npm 那边要接受它,需要在 npmjs.com 上给包配 Trusted Publisher(包 → Settings →
Trusted Publisher → GitHub Actions),仓库填 chenmiao8563/dsh-token-ledger、
workflow 填 release.yml、Environment 留空——填了一个 workflow 没有声明的
environment 就匹配不上,registry 只会回一句
OIDC token exchange error - package not found。
在 trusted publishing 出现之前,一个版本的首发是用
npm publish --access public --otp=<认证器里的 6 位数字> 手动发的,因为 OIDC 没法在包
存在之前配置。那条路现在依然可用,也依然需要带 2FA bypass 的 granular token,
但它不产生 provenance 证明,而且 v0.8.0 那次排查正是从这条路走起——从一个过期的
token 开始。优先用打 tag。
重复运行是安全的:版本已在 registry 上时跳过发布步骤,GitHub Release 只在不存在时创建。
许可证
MIT
链接
同类插件
bowenliang123/dsh-context★ 1456
DSH 上下文洞察面板:Context 仪表盘 + /context命令 + Context 浏览器,查看 Context的分类组成、内容详情、演进趋势、压缩/注入事件、统计等一站式 Context 全生命周期管理。
Han-1413141/dsh-cost-meter★ 316
会话与当日 API 费用统计、预算图框(已用%)、官方余额、历史看板,支持峰谷计价与官方价格一键同步。
zh667/TokenLedger★ 202
侧边栏用量面板:把 Token 归属到实际服务该请求的中转站,站点从已有的 provider 配置中读出,无需额外配置;含今日/本月/累计三窗口、按站点与模型下钻、一年活跃度热力图,以及 New API / Sub2API / DeepSeek 余额。
wssfk12138/dsh-damage-pulse★ 184
在 DSH Web 界面追踪 DeepSeek Token 用量、单次与会话费用及账户余额,并显示缓存感知的扣费动画。
Ychris12138/dsh-usage-stats★ 162
多供应商用量看板:按供应商/模型统计 Token 与日期下钻,统一展示账户余额,并追踪 OpenCode Go / Z.ai 订阅额度。
PolinniZhong/dsh-personal-center★ 120
DeepSeek Harness 个人中心:跨会话用量统计、按模型成本估算、全局自定义指令、外观全局字号、数据驱动的桌面宠物(位图/矢量皮肤)与会话状态概览,纯本地离线运行。
社区评论
评论公开保存在 GitHub Discussions。加载评论会连接 GitHub 和 Giscus;发表内容需要 GitHub 账号。