🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

Token 用量统计 zhujunzhujunzhu/ai-token-report

DeepSeek Harnessv0.1.0
zhujunzhujunzhu22 天前有提交已提供中文文档扫描:含敏感能力

AI token 用量统计平台。四种形态:命令行 / 本地页面 / 部门看板 / DSH 插件。

30.8/100
综合分
1
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/14 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

自动兼容检查未通过:缺少 main/exports/bin 入口声明。以下命令仍可尝试,但可能失败。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add zhujunzhujunzhu/ai-token-report
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么
dsh 原生插件 · other
装得上吗
本站已在 dsh 0.2.1-rc.5 下真实安装成功
非静态推断
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 22 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查需源码安装

缺少 main/exports/bin 入口声明

展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✓npm 包ai-token-report @ 0.1.0
✓Node 引擎要求 >=22 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✗入口文件缺少入口声明

源码安装时若 pnpm 拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑。

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/14 00:32:49

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

可执行外部命令读写/删除本地文件读取剪贴板或密钥环境变量发起外部网络请求
查看扫描证据(共 87 处,展示前 76 条)
  • 写入本地文件
    packages/cli/src/cli.ts:1498
    await writeFile(opts.out, text, 'utf8')
  • 写入本地文件
    packages/cli/src/cli.ts:1522
    await writeFile(opts.out, text, 'utf8')
  • 读取本地文件
    packages/cli/src/cli.ts:1795
    if (existsSync(join(dir, 'index.html'))) return dir
  • 执行外部命令来自配置/变量
    packages/cli/src/cli.ts:1815
    const child = spawn(cmd[0] as string, cmd[1] as string[], {
  • 读取本地文件
    packages/core/src/connection-store.ts:94
    return { text: readFileSync(path, 'utf8') }
  • 删除本地文件
    packages/core/src/connection-store.ts:158
    rmSync(path, { force: true })
  • 写入本地文件
    packages/core/src/connection-store.ts:161
    mkdirSync(dirname(path), { recursive: true })
  • 写入本地文件
    packages/core/src/connection-store.ts:165
    writeFileSync(temp, text, { mode: 0o600 })
  • 写入本地文件
    packages/core/src/connection-store.ts:166
    renameSync(temp, path)
  • 删除本地文件
    packages/core/src/connection-store.ts:169
    rmSync(temp, { force: true })
  • 读取本地文件
    packages/core/src/home.ts:232
    return readdirSync(dir, { withFileTypes: true })
  • 读取本地文件
    packages/core/src/home.ts:449
    return { home, root, exists: existsSync(root) }
  • 读取本地文件
    packages/core/src/identity-store.ts:68
    if (!existsSync(path)) {
  • 读取本地文件
    packages/core/src/identity-store.ts:74
    raw = readFileSync(path, 'utf8')
  • 写入本地文件
    packages/core/src/identity-store.ts:170
    mkdirSync(dirname(path), { recursive: true })
  • 写入本地文件
    packages/core/src/identity-store.ts:174
    writeFileSync(tmp, JSON.stringify(identity, null, 2) + '\n', 'utf8')
  • 写入本地文件
    packages/core/src/identity-store.ts:178
    chmodSync(tmp, 0o600)
  • 写入本地文件
    packages/core/src/identity-store.ts:183
    renameSync(tmp, path)
  • 读取本地文件
    packages/core/src/identity-store.ts:193
    if (!existsSync(path)) return false
  • 删除本地文件
    packages/core/src/identity-store.ts:195
    unlinkSync(path)
  • 读取本地文件
    packages/core/src/scanner.ts:108
    projects = await readdir(sessionsRoot, { withFileTypes: true })
  • 读取本地文件
    packages/core/src/scanner.ts:119
    if (!project.isDirectory() && !(project.isSymbolicLink() && (await stat(projPath)).isDirectory())) continue
  • 读取本地文件
    packages/core/src/scanner.ts:127
    sessionIds = await readdir(projPath, { withFileTypes: true })
  • 读取本地文件
    packages/core/src/scanner.ts:137
    if (!session.isDirectory() && !(session.isSymbolicLink() && (await stat(sessPath)).isDirectory())) continue
  • 读取本地文件
    packages/core/src/scanner.ts:146
    entries = await readdir(sessPath)
  • 读取本地文件
    packages/core/src/scanner.ts:212
    try { entries = await readdir(dir) } catch {
  • 读取本地文件
    packages/core/src/state.ts:138
    raw = readFileSync(path, 'utf8')
  • 写入本地文件
    packages/core/src/state.ts:192
    mkdirSync(dirname(path), { recursive: true })
  • 写入本地文件
    packages/core/src/state.ts:194
    writeFileSync(tmp, JSON.stringify(state, null, 2), 'utf8')
  • 写入本地文件
    packages/core/src/state.ts:196
    renameSync(tmp, path)
  • 删除本地文件
    packages/core/src/state.ts:200
    rmSync(tmp, { force: true })
  • 删除本地文件
    packages/core/src/state.ts:210
    rmSync(path, { force: true })
  • 删除本地文件
    packages/core/src/state.ts:211
    rmSync(`${path}.tmp`, { force: true })
  • 读取本地文件
    packages/dsh-plugin/src/backfill-runner.ts:97
    const value = JSON.parse(await readFile(path, 'utf8')) as Partial<Checkpoint>
  • 写入本地文件
    packages/dsh-plugin/src/backfill-runner.ts:113
    await writeFile(temporary, JSON.stringify({ version: 1, file } satisfies Checkpoint), 'utf8')
  • 写入本地文件
    packages/dsh-plugin/src/backfill-runner.ts:114
    await rename(temporary, path)
  • 删除本地文件
    packages/dsh-plugin/src/backfill-runner.ts:116
    try { await unlink(temporary) } catch { }
  • 读取本地文件
    packages/dsh-plugin/src/backfill-runner.ts:123
    const value = JSON.parse(await readFile(path, 'utf8')) as Partial<PlainWatermarks>
  • 写入本地文件
    packages/dsh-plugin/src/backfill-runner.ts:138
    await writeFile(temporary, JSON.stringify(value), 'utf8')
  • 写入本地文件
    packages/dsh-plugin/src/backfill-runner.ts:139
    await rename(temporary, path)
  • 删除本地文件
    packages/dsh-plugin/src/backfill-runner.ts:141
    try { await unlink(temporary) } catch { }
  • 读取本地文件
    packages/dsh-plugin/src/backfill-runner.ts:240
    if (!existsSync(root)) throw new Error(`会话日志根不存在:${root}`)
  • 读取本地文件
    packages/dsh-plugin/src/backfill-runner.ts:241
    if (!(await stat(root)).isDirectory()) throw new Error(`会话日志根路径不是目录:${root}`)
  • 读取本地文件
    packages/dsh-plugin/src/backfill-runner.ts:242
    await readdir(root)
  • 写入本地文件
    packages/dsh-plugin/src/backfill-runner.ts:247
    await mkdir(checkpointDir, { recursive: true })
  • 写入本地文件
    packages/dsh-plugin/src/outbox.ts:107
    mkdirSync(this.#dir, { recursive: true })
  • 读取本地文件
    packages/dsh-plugin/src/outbox.ts:122
    return readdirSync(this.#dir)
  • 写入本地文件
    packages/dsh-plugin/src/outbox.ts:161
    writeFileSync(temporary, text, 'utf8')
  • 写入本地文件
    packages/dsh-plugin/src/outbox.ts:162
    renameSync(temporary, this.#pathOf(file))
  • 删除本地文件
    packages/dsh-plugin/src/outbox.ts:164
    try { unlinkSync(temporary) } catch { }
  • 写入本地文件
    packages/dsh-plugin/src/outbox.ts:183
    renameSync(this.#pathOf(file), this.#pathOf(file.replace(PENDING_PREFIX, INFLIGHT_PREFIX)))
  • 删除本地文件
    packages/dsh-plugin/src/outbox.ts:199
    unlinkSync(this.#pathOf(candidate))
  • 读取本地文件
    packages/dsh-plugin/src/outbox.ts:217
    if (!existsSync(from)) return
  • 写入本地文件
    packages/dsh-plugin/src/outbox.ts:219
    renameSync(from, this.#pathOf(file))
  • 写入本地文件
    packages/dsh-plugin/src/outbox.ts:239
    renameSync(this.#pathOf(f), this.#pathOf(back))
  • 读取本地文件
    packages/dsh-plugin/src/outbox.ts:279
    text = readFileSync(this.#pathOf(file), 'utf8')
  • 删除本地文件
    packages/dsh-plugin/src/outbox.ts:325
    unlinkSync(this.#pathOf(f))
  • 读取本地文件
    packages/dsh-plugin/src/outbox.ts:335
    try { return statSync(this.#pathOf(file)).mtimeMs } catch { return -1 }
  • 读取本地文件
    packages/dsh-plugin/src/outbox.ts:340
    const s = statSync(this.#dir)
  • 读取本地文件
    packages/dsh-plugin/src/outbox.ts:395
    const st = statSync(this.#pathOf(file))
  • 删除本地文件
    packages/dsh-plugin/src/outbox.ts:412
    unlinkSync(this.#pathOf(f))
  • 发起网络请求
    packages/dsh-plugin/src/reporter.ts:490
    const res = await this.#fetch(this.#config.endpoint, {
  • 读取本地文件
    packages/server/src/credentials.ts:307
    if (!existsSync(path)) {
  • 读取本地文件
    packages/server/src/credentials.ts:313
    raw = readFileSync(path, 'utf8')
  • 发起网络请求
    packages/server/src/identity-route.ts:279
    const res = await this.#fetch(url, {
  • 读取密钥环境变量
    packages/server/src/index.ts:265
    adminToken: options.adminToken ?? process.env.ATR_ADMIN_TOKEN,
  • 读取密钥环境变量
    packages/server/src/index.ts:268
    adminPassword: options.adminPassword ?? process.env.ATR_ADMIN_PASSWORD,
  • 读取密钥环境变量
    packages/server/src/index.ts:380
    captchaHmacKey: options.captchaHmacKey ?? process.env.ATR_CAPTCHA_HMAC_KEY,
  • 发起网络请求
    packages/server/src/index.ts:409
    handler: (req: Request) => app.fetch(req),
  • 读取本地文件
    packages/server/src/main.ts:272
    if (existsSync(resolve(dir, 'index.html'))) return dir
  • 写入本地文件
    packages/server/src/member-admin.ts:425
    mkdirSync(dirname(this.#credentialsPath), { recursive: true })
  • 写入本地文件
    packages/server/src/member-admin.ts:429
    writeFileSync(tmp, serializeCredentials(next), 'utf8')
  • 写入本地文件
    packages/server/src/member-admin.ts:433
    chmodSync(tmp, 0o600)
  • 写入本地文件
    packages/server/src/member-admin.ts:438
    renameSync(tmp, this.#credentialsPath)
  • 读取本地文件
    packages/server/src/member-admin.ts:530
    if (existsSync(dir)) {
  • 读取本地文件
    packages/server/src/member-admin.ts:532
    accessSync(dir, constants.W_OK)
扫描于 2026/11/3(1 天前) · 范围 全包扫描 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 统计本机Token消耗
  • 统计部门Token消耗
  • 生成消耗看板

README

由 DeepSeek 最新模型翻译生成
AI Token Report AI token 用量统计平台。四种形态:命令行 / 本地页面 / 部门看板 / DSH 插件。 📖 先读 ARCHITECTURE.md —— 整体结构与职责划分。 口径细节见 docs/口径实测结论.md,插件方案见 docs/插件方案.md。 快速开始 bun install # 安装全部工作区依赖 ① 终端里看统计(读本机日志,不联网)—— ✅ 已可用 bun run stats -- --period today ② 本地页面:内嵌服务 + 自动开浏览器 —— ✅ 已可用 bun run build:local # 首次需先构建前端产物 bun run web ③ 独立部署部门服务端(含部门看板页面)—— ✅ 已可用 bun run build:portal # 首次需先构建看板前端产物 bun run server # 先按下文配置数据库及首次管理员账号 ④ 增量上报到部门服务端(由计划任务每 10 分钟调用)—— ✅ 已可用 bun run report -- --endpoint http://<服务端>:8787/api/v1/token-usage --token <管理员发的 token>
展开完整文档(README 剩余 1 节)
AI Token Report

AI token 用量统计平台。四种形态:命令行 / 本地页面 / 部门看板 / DSH 插件。

📖 先读 ARCHITECTURE.md —— 整体结构与职责划分。
口径细节见 docs/口径实测结论.md,插件方案见 docs/插件方案.md。

快速开始

bun install # 安装全部工作区依赖

① 终端里看统计(读本机日志,不联网)—— ✅ 已可用
bun run stats -- --period today

② 本地页面:内嵌服务 + 自动开浏览器 —— ✅ 已可用
bun run build:local # 首次需先构建前端产物
bun run web

③ 独立部署部门服务端(含部门看板页面)—— ✅ 已可用
bun run build:portal # 首次需先构建看板前端产物
bun run server # 先按下文配置数据库及首次管理员账号

④ 增量上报到部门服务端(由计划任务每 10 分钟调用)—— ✅ 已可用
bun run report -- --endpoint http://<服务端>:8787/api/v1/token-usage --token <管理员发的 token>

实现进度:stats(终端统计)、web(本地页面)、server(上报接收 + 部门看板)与 report(CLI 上报)均已可用。
四种形态(CLI / 本地页面 / 部门看板 / DSH 插件)全部落地,详见 ARCHITECTURE.md §8 阶段表。

部门看板:bun run server 起服务后打开 http://<服务端>:8787/ (页面由服务端静态托管,构建产物在 packages/web-portal/dist),使用账号密码和验证码登录,即可看到本部门的人员排行、部门趋势、模型分布、用量明细与采集覆盖率诊断。
筛选栏支持时间窗(今天 / 上周 / 本月 / 最近 90 天…或自定义起止时间)、人员多选、分组多选与厂商 / 模型。

人员管理:管理员登录后右上多一个「人员管理」页签 ——在页面上维护人员、分组(可多选)、角色、登录账号及上报 Token,数据统一保存在部门数据库。分组本身在「分组管理」页维护(/groups)。

目录

| 路径 | 职责 |
| packages/shared | 契约单一真源:上报 DTO、查询响应、口径公式 |
| packages/core | 统计内核:解码 / 扫描 / 聚合 / 时间范围 / 水位线 / 身份存储 |
| packages/cli | 命令行入口:stats / report / --web |
| packages/server | 后端:上报接收 + 本地直查 + 部门统计(含分组目录与分组维度查询)+ 数据库身份、人员与分组管理 + 静态托管 |
| packages/web-local | 本地页面:只看本机,数据来自 /api/local/ |
| packages/web-portal | 部门看板 + 人员管理页 + 分组管理页:看全员,数据来自 /api/v1/stats/(含分组候选项 /api/v1/stats/groups 与人员候选项 /api/v1/stats/members)、/api/v1/admin/members 与 /api/v1/admin/groups(数据库会话鉴权) |
| packages/dsh-plugin | DSH 插件:实时上报 + 界面用量面板(安装含 DSH Desktop 桌面端,见 docs/桌面端安装交付清单.md) |

DSH 插件安装

命令行版 DSH:一条命令,add 会自动登记到 profile 的 dsh.profile.bundles
dsh plugin --profile web add dsh-plugin-token-report@latest

DSH Desktop(桌面端)的图形入口装不了本插件 —— 侧边栏「插件」市场只接受 awesome-dsh-plugin 精选列表内的来源,本插件不在其中。
走命令行,把 home / pnpm 换成 Desktop 自己那一套即可:

$env:DSH_HOME = "$env:APPDATA\dsh-desktop\harness"
$env:PATH = "$env:DSH_HOME\.desktop-bin;$env:PATH" # ★ 用 Desktop 自己的 pnpm / node
node "<安装目录>\resources\app.asar.unpacked\node_modules\@deepseek-ai\dsh\lib\bin.js"
plugin --profile web add dsh-plugin-token-report@latest

逐条步骤、核对命令、日志取证与回滚见 docs/桌面端安装交付清单.md,插件自身的配置与排障见 packages/dsh-plugin/README.md。

🚨 三件事必须同时成立,否则症状是「面板不见了、而且一条也不上报」,不是报错: ① 官方 OTel 后端已 disabled: true(同一时刻只能有一个 sessionTelemetry); ② 装的是发布包 / 构建产物,不是仓内源码包(它的 main 指向 src/index.ts,Node 加载即失败); ③ 宿主的 @deepseek-ai/dsh 落在插件的 peer 兼容窗口 >=0.1.7-rc.2 <0.3.0-0 内。

三条铁律

1. 口径只在 shared/src/metrics.ts 定义。 任何地方要算缓存命中率等指标,必须调用它,不要各自重写 —— 两端口径不一致的 bug 极难排查。
2. cacheRead 不是 input 的一部分。 input 是「未命中缓存」那部分,实测 cacheRead 占总用量 94.3%,漏掉它等于漏掉 94% 的用量。
3. 上报只需 at-least-once。 幂等键 event_id = sessionId:seq 由服务端 ON CONFLICT DO NOTHING 去重,所以插件与命令行可以同时上报,无需协调。

身份署名

首次使用本地页面或插件时只要求填两样:部门服务端地址 + appKey(管理员发放)。
姓名与分组不由用户填 —— 它们由服务端按 appKey 解析(填了也不作数)。在此之前:

- ❌ 不采集、也不向任何服务端发送数据
- ✅ 仍可查看本机统计(那是你自己的数据)

填写后保存在数据目录下:identity.json(谁)+ plugin-connection.json(连哪台、拿什么凭证),缺省 ~/.ai-token-report/,可用配置项 dataDir 或 DSH_TOKEN_REPORT_DATA_DIR 覆盖; 本地页与插件的「配置」是同一形态、写同一份配置,在哪里填一次都生效。
appKey 是身份凭证 —— 姓名以服务端凭证表为准,改本地文件无法冒用他人身份。
DSH Desktop 与命令行版 DSH 缺省就共用这份署名(数据目录不再跟随各自的 home),想各用一套时才在插件配置里指 dataDir(不要改 dshHome),见 packages/dsh-plugin/README.md §1.1。

数据库与管理员初始化

正式部署采用 MySQL,本地运行和测试保留 SQLite。人员、分组、角色权限、账号、Token、会话、验证码、限流和审计均落数据库;正常请求不再读取凭证文件。

空库首次启动时,通过部署秘密配置注入 ATR_ADMIN_USERNAME、ATR_ADMIN_PASSWORD 及至少 32 个字符的 ATR_CAPTCHA_HMAC_KEY;MySQL 再配置 ATR_MYSQL_URL。
管理员配置只初始化一次,后续以数据库为准。MySQL 正式部署建议使用已验证的 Node/mysql2 入口,完整命令见 数据库部署与迁移。

本机自用不必手配这些:bun run server 在「本机 SQLite 上报库 + 监听 127.0.0.1」时,会把缺失的验证码密钥与随机生成的管理员口令写进数据目录下的 server.env (缺省 ~/.ai-token-report/server.env),下次启动直接复用 —— 配了 --mysql / ATR_MYSQL_URL(共享库)或 --host 非回环地址时不自动生成,仍按上面显式配置。

管理员登录后创建人员,分配分组(一个人可同属多个分组)和角色,再按需要开通登录或签发上报 Token。
默认 Token 只有署名与上报权限,明文只显示一次。轮换、撤销和停用立即生效。
人员用稳定 ID 标识,可以同名;姓名不能作为权限或归属的依据。

最后一个有效管理员入口不可被删除、停用或降级。旧 v3 / v4 数据库和 credentials.json 必须按文档显式迁移(v3 → v4 冻结基线 → v5)并保留备份,服务不会自动重建唯一的历史数据。

环境要求

- Bun ≥ 1.1
- Node ≥ 22(zlib.zstdDecompressSync 需要)

开发

bun test # 全仓测试
bun run --filter '' typecheck
bun run build # 构建两个 web 应用
预览分享卡片长什么样 ↓zhujunzhujunzhu/ai-token-report
分享 · DeepSeek Harness Hub
zhujunzhujunzhu/ai-token-report

AI token 用量统计平台。四种形态:命令行 / 本地页面 / 部门看板 / DSH 插件。

这是你的仓库?认领条目

回到顶部