npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

dsh-token-usage-dashboard

v1.3.2

Published

Token 用量统计看板:扫描 $DSH_HOME/sessions 下的持久化会话日志(token-meter 的同一份用量记录),聚合出 时间×模型 的输入/输出/缓存用量,生成自包含 HTML 仪表盘

Readme

dsh-token-usage-dashboard

DeepSeek Harness(dsh)插件:Token 用量统计看板

扫描 $DSH_HOME/sessions 下的持久化会话日志(与 dsh-token-meter 计量的同一份 assistant/message.usage 记录),按「日 × 模型」聚合输入 / 缓存读 / 缓存写 / 输出 / 推理 token,渲染一份自包含、可离线打开的 HTML 仪表盘(KPI 卡 + 每日堆积条 + 日×模型热力图 + 明细表),并在 dsh web 侧边栏底部加入口按钮。

  • 许可:MIT(Copyright (c) 2026 skjie)
  • 分支:plugin-token-usage(本仓库采用「分支即插件」组织,本分支即插件本体)

✨ 功能

| 功能 | 说明 | |---|---| | HTML 看板 | 自包含单文件,可离线打开;排版对齐 dsh web 自带 /token-usage.html 页面(深色主题、四张 stat 卡、热力图 / 按天堆叠 / 饼图三块 canvas、每日明细表格、tooltip);页头范围按钮(今日 / 三天 / 七天 / 半月 / 一月 / 三月,默认今日),点击在浏览器端重算视图;今日 = 按每小时展示(柱状图 / 热力图 / 明细表均为小时粒度) | | 数据口径 | 与 dsh-token-meter 投影一致:同一 (turn, step) 只保留最后一条 assistant/message 用量(重试 / 改写不重复计数);reasoningTokens 独立成列;inputTokens 为未缓存输入,计费输入 = input + cacheRead + cacheWrite | | 模型归因 | 取事件之前最新的 request/context(缺失回退 request/header 的 config),两者皆无记 unknown | | 自动刷新 | 启动即生成;监听 session/flush 防抖重生成(默认 5s),文件打开时聚焦自动刷新 | | 容错 | 损坏尾部按「连续前缀」语义截断,坏日志计入 warnings 跳过,不影响整体;zstd 用 Node 内置 node:zlib,零额外依赖 | | 防注入 | 模型名等日志派生字符串经 HTML 转义后渲染;内联 JSON 数据区的 < 序列化为 \u003c,杜绝 </script> 逃逸 | | 增量重扫 | mtime+size 解析缓存:session/flush 触发的重生成只重读有变化的日志(热扫描约为全量的 1/200) | | 侧边栏入口 | 在 dsh web 侧边栏底部(sidebar.footer.action 槽位)注册「📊 Token 用量」按钮,点击打开看板 |


🚀 一键安装

前提:已安装 dsh CLI 与 pnpmdsh plugin 内部调用 pnpm;没有 pnpm 可 corepack enable pnpm 启用),以及 Node ≥ 22.15(依赖 node:zlib 内置的 zstd 解压,旧版本会在模块加载期直接报错)。

# git 地址直装(dist 已提交,安装时无需 prepare 构建,不触发 allowBuilds 拦截)
dsh plugin --profile web add "git+https://cnb.cool/skjli/dsh-plugin.git#plugin-token-usage"

# 重启 dsh web 生效
dsh web

dsh plugin 装完会自动 reconcile:包声明了 dsh.bundle.patch,会自动加入 dsh.profile.bundles 层栈,无需手改配置。

其他安装源(任选其一):

# npm registry(若已发布)
dsh plugin --profile web add dsh-token-usage-dashboard

# 本地路径(开发联调,pnpm link 语义)
dsh plugin --profile web add .

验证安装

dsh --profile web --dump-config   # 应看到 id: token-usage-dashboard 的插件行

完整操作流(安装 → 验证 → 重启 → 确认生效):

# 1. 安装(若报 EPERM 锁文件错误,先停 web 再重跑)
dsh plugin --profile web add "git+https://cnb.cool/skjli/dsh-plugin.git#plugin-token-usage"

# 2. 验证三处已写入
$pkg = Get-Content "$env:USERPROFILE\.dsh\profiles\web\package.json" -Raw | ConvertFrom-Json
$pkg.dependencies                          # ① 应有 git URL
$pkg.dsh.profile.bundles                   # ② 应含 dsh-token-usage-dashboard
Test-Path "$env:USERPROFILE\.dsh\profiles\web\node_modules\dsh-token-usage-dashboard"  # ③ True

# 3. 重启 web 生效(运行中的进程不会感知新装包,必须重启)
Get-CimInstance Win32_Process -Filter "Name='node.exe'" | Where-Object { $_.CommandLine -match 'dsh.*bin.js.*web' } | Stop-Process -Force
Start-Process "C:\Program Files\nodejs\node.exe" -ArgumentList '"C:\Users\skyli\.dsh\profiles\node_modules\@deepseek-ai\dsh\lib\bin.js"','web' -WindowStyle Hidden
Start-Sleep -Seconds 12
Invoke-WebRequest -Uri "http://127.0.0.1:3080/" -UseBasicParsing | Select-Object StatusCode   # 200

# 4. 浏览器打开 http://127.0.0.1:3080/ ,侧边栏底部应出现「📊 Token 用量」按钮

一键卸载

dsh plugin --profile web remove dsh-token-usage-dashboard
dsh web   # 重启生效

完整操作流(卸载 → 验证干净 → 重启):

# 1. 停 web(避免 pnpm 写锁文件时 EPERM)
Get-CimInstance Win32_Process -Filter "Name='node.exe'" | Where-Object { $_.CommandLine -match 'dsh.*bin.js.*web' } | Stop-Process -Force

# 2. 官方卸载命令(预期输出 dependencies: - dsh-token-usage-dashboard git+...)
dsh plugin --profile web remove dsh-token-usage-dashboard

# 3. 验证四处全部干净
$pkg = Get-Content "$env:USERPROFILE\.dsh\profiles\web\package.json" -Raw | ConvertFrom-Json
$pkg.dependencies                          # ① 应为空 / 无该键
$pkg.dsh.profile.bundles                   # ② 应只剩 dsh-base、dsh-web-app
Test-Path "$env:USERPROFILE\.dsh\profiles\web\node_modules\dsh-token-usage-dashboard"  # ③ False
Select-String -Path "$env:USERPROFILE\.dsh\profiles\web\pnpm-lock.yaml" -Pattern 'token-usage'  # ④ 无输出

# 4. 重启 web
Start-Process "C:\Program Files\nodejs\node.exe" -ArgumentList '"C:\Users\skyli\.dsh\profiles\node_modules\@deepseek-ai\dsh\lib\bin.js"','web' -WindowStyle Hidden
Start-Sleep -Seconds 12
Invoke-WebRequest -Uri "http://127.0.0.1:3080/" -UseBasicParsing | Select-Object StatusCode   # 200

卸载会同时移除 dependenciesdsh.profile.bundles 中的条目,node_modules 无残留。


⬆️ 升级与版本感知

dsh 没有第三方插件的自动更新(pnpm 锁定安装时的 commit / 版本,dsh plugin 只是 pnpm 透传),本插件内置轻量版本探测,帮助已装用户感知新版本:

| 感知渠道 | 说明 | |---|---| | 侧边栏角标 + 升级面板 | web 启动时探测远端最新版本,有新版时「Token 用量」按钮右上角出现与 dsh 自身更新提示同款的圆点;点击弹主题化升级面板(对齐 dsh 自带「检查更新」面板的样式语言):开始更新 / 复制命令 / 直接打开看板 | | 看板页 footer | /token-usage.html 页脚显示「版本 x.y.z · 🔄 有新版 a.b.c(升级命令)」 | | JSON 接口 | GET /token-usage-version.json{current, latest, hasUpdate, canSelfUpdate, checkedAt},供其他工具消费 |

「开始更新」由 host 端执行固定命令:正式安装在 profile 目录跑 pnpm update dsh-token-usage-dashboard;link/junction 开发形态在仓库根跑 git pull --ff-only(dist 已提交)。更新在下次 dsh web 启动时自然生效 (开机自启 / 手动启动),不做进程内重启。动作路由仅接受同源请求且命令固定, 不接受任意输入。

探测机制(lib/version.js,全程静默容错,失败只显示当前版本、绝不阻塞启动):

  1. 配置了 updateUrl 时优先请求该 JSON(取 .version 字段)——包发布到 npm 后可填 https://registry.npmjs.org/dsh-token-usage-dashboard/latest
  2. 否则 git ls-remote --tags 匿名读取远端仓库的 semver 标签取最大者 (git 协议对公开仓库匿名开放,无需平台 REST API / 登录令牌)。

发布新版本的配套动作(仓库维护者:打标签即发布信号):

git tag v1.2.0 && git push origin v1.2.0

用户升级

dsh plugin --profile web update dsh-token-usage-dashboard   # git 源重新解析到最新 commit
dsh web                                                     # 重启生效

⚠️ profile 的供应链策略(minimumReleaseAge)可能拦截刚发布的版本:把新版本号加进 profile pnpm-workspace.yamlminimumReleaseAgeExclude 列表,或等待冷却期。

⚠️ 装 / 卸后必须重启 dsh web 才生效。DSH 的热加载只覆盖「已装载插件的配置 变化」(如改 cordis.patch.yml),不覆盖安装 / 卸载 npm 包:运行中的进程 启动时已读取 bundles 与插件 roster,新装包不会被感知(/plugins/<id>/client.js 会 404)。Windows 下若 pnpm 报 EPERM: rename ... pnpm-lock.yaml,先停 web 再执行(锁文件被占用,偶发,重试即可)。


⚙️ 配置

插件行 config 键(cordis.patch.yml 或 profile 用户层的 id 覆盖),均为默认值:

| key | 默认 | 说明 | |---|---|---| | root | '' | 会话根目录;空 = dshHomePath('sessions') | | scope | 'all' | 'all' | 'cwd'(仅当前工作目录)| 目录数组 | | days | 0 | 时间窗天数;0 = 全部 | | outFile | 'token-usage.html' | 输出路径(相对 cwd 解析,可绝对路径) | | generateOnBoot | true | 启动即生成 | | watch | true | 监听 session/flush 防抖重生成 | | watchDebounceMs | 5000 | 防抖毫秒 | | excludeOrigin | [] | 排除的会话 origin(如 ['subagent']) | | updateCheck | true | 启动时探测远端最新版本(看板 footer 提示 + 侧边栏角标) | | updateUrl | '' | 自定义版本 JSON 源(取 .version,如 npm 的 /latest);空 = git ls-remote --tags | | updateRepo | '' | 版本标签所在 git 仓库;空 = 本插件官方仓库 |

⚠️ outFile 的 cwd 陷阱:web 常以「SYSTEM 调度任务 / 隐藏窗口」方式启动 (如 start-dsh-web.vbs),此时 node 的 process.cwd()C:\Windows\System32 而非工作区,相对 outFile 会把看板写进系统目录。建议在 profile 用户层 cordis.patch.yml 里覆盖为绝对路径或 !!js dshHomePath('token-usage.html') (用户层热重载,改完无需重启 dsh)。

覆盖示例(profile 用户层 cordis.patch.yml

- id: token-usage-dashboard
  config:
    scope: cwd
    days: 7
    outFile: !!js dshHomePath('token-usage.html')   # 或绝对路径,如 /home/user/token-usage.html

🔧 开发

结构

lib/index.js          服务端(Cordis Service:generateOnBoot + watch + 服务方法)
lib/usage.js          数据读取与聚合(纯模块,无 Cordis 依赖)
lib/render.js         HTML 看板渲染
lib/format.js         展示辅助(日期键 / 千分位)
src/client.js         browser 端:侧边栏按钮(esbuild 打包 → dist/client.js)
dist/client.js        已提交的构建产物(一键安装免构建)
cordis.patch.yml      插件 patch 层(插入插件行)
scripts/build-client.mjs   esbuild 构建脚本
test/                 自测脚本(run.mjs / diagnostics.mjs)
DESIGN.md             详细设计说明

数据源:为什么读持久化日志,而不是 ctx.tokenMeter

dsh-token-meterctx.tokenMeter.measure(session)实时回放感知的: 返回「最新请求的压力 + 当前表层」,不是历史累计账本。历史账单藏在会话日志的 累积方式里,token-meter 自己也是回放这份日志做锚定计价的。因此本插件直接读取 这份持久日志,即使用了与 token-meter 完全相同的事件事实。

磁盘形态:

<root>/<projectKey(cwd)>/<encodeSegment(sessionId)>/session.jsonl[.zstd]
  • 首行 = header 记录 { type:'session', version, id, createdAt, cwd, ... }
  • 其余行 = 存储记录,经 decodeStorageRecord@deepseek-ai/dsh-session 官方导出) 还原为 SessionEvent[]
  • .jsonl.zstd多帧容器(每次 flush 追加一个独立帧),用 Node 内置 node:zlibzstdDecompressSync 逐帧解压,尾部不完整帧跳过。

聚合语义

  • 模型归因:事件之前最新的 request/contextprovider/model),缺失回退 request/header 的 config,两者皆无记 unknown
  • 去重:同一 (turn, step) 只保留最后一条 assistant/message 的用量;
  • 时间窗:按事件 time(Unix ms)归本地日 YYYY-MM-DDdays > 0 时过滤 早于 now - days*86400e3 的样本。

构建

node scripts/build-client.mjs    # esbuild 打包 src/client.js → dist/client.js

自测

# 需要先让 @deepseek-ai/dsh-session 可解析:
#   在仓库根目录建一个指向 profile 扁平 node_modules 的 junction(Windows):
#   cmd /c mklink /J node_modules %USERPROFILE%\.dsh\profiles\node_modules
node test/run.mjs [会话根目录]   # 默认 $USERPROFILE\.dsh\sessions

test/run.mjs 输出聚合摘要的 JSON 并写出 test-out.html 供人工抽查。

node test/xss-check.mjs     # 渲染防注入回归(转义 / \u003c 序列化 / esc 插入点)
node test/cache-check.mjs   # mtime 解析缓存回归(命中 / 失效 / 清理)

客户端槽位机制(重要)

侧边栏按钮注册在 sidebar.footer.action 槽位。该槽位由 @deepseek-ai/dsh-client-ui-sidebarsidebar entry 声明,而 client 插件的 激活顺序不受 dsh.client.inject 边约束(那是信息性元数据,非装载顺序), 因此不能直接 ctx.slots.register——那会在本插件先于 sidebar 激活时抛 slot "sidebar.footer.action" is not declared。官方约定(同 ui-workspace)是:

ctx.slots.inject("sidebar.footer.action", () =>
  ctx.slots.register({ name: "sidebar.footer.action", id: "dsh-token-usage-dashboard-action", order: 100, label: "Token 用量" }, component)
);

inject 在声明存在时同步注册、声明折叠时自动 dispose、重新声明时再次注册。

⚠️ 槽位组件不得使用 React hooks(1.2.0 的真实事故,1.2.1 修复):client bundle 经 esbuild 自带一份 React 拷贝,而槽位组件由 SPA 自身的 React 渲染器渲染; 普通元素因 $$typeofSymbol.for('react.element') 跨拷贝安全,但 hooks 的 内部 dispatcher 是每份 React 的模块级私有状态,跨拷贝调用 useState/useEffect 会抛 "Invalid hook call",槽位渲染失败、按钮整体消失。需要"状态"的行为改用 DOM 属性 + CSS 选择器实现(升级角标的 body[data-token-usage-update] 方案)。


📦 发布

npm pack --dry-run   # 预览包内容(files 白名单:lib/dist/src/scripts/cordis.patch.yml/README/LICENSE)
npm publish          # prepack 自动构建 dist/client.js

🗂 相关文档