# dsh-plugin-deepseek-balance **Repository Path**: bbsmrdj/dsh-plugin-deepseek-balance ## Basic Information - **Project Name**: dsh-plugin-deepseek-balance - **Description**: A simple deepseek harness plugin for showing deepseek balance - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-10-24 - **Last Updated**: 2026-10-24 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # dsh-deepseek-balance **[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(DSH)Web 界面的 DeepSeek 账户余额卡**:左侧边栏底部常驻一行 `余额 ¥110.00`,点击即时刷新,进入页面自动读取,收起成图标栏时只显示金额。 > 官方 DSH 与社区插件都没有余额展示——它统计 token 用量,不读账户余额。本插件补上这一块:宿主侧读 `GET https://api.deepseek.com/user/balance`,浏览器侧把它挂在侧边栏的 `sidebar.footer.action` 槽上。 - 一个 cordis 插件条目,同时提供**宿主半边**(HTTP 路由)与**浏览器半边**(槽位组件) - **零运行时依赖**:不 import 任何 `@deepseek-ai/*`,浏览器半边只从外壳模块表 `require('react')` - **无构建**:`client.js` 是手写的 lazy-CJS bundle(`window.__ModuleLoader__.load({id, factory})`),不需要 tsdown / vite - 密钥不出进程:经 `ctx.credentials` 解析,配置里只有引用名 `DEEPSEEK_API_KEY` ## 效果 ``` ┌ 侧边栏 ─────────────────┐ │ … 会话列表 … │ │ ● 余额 ¥110.00 ← 本插件 │ │ ⚙ 设置 │ └─────────────────────────┘ ``` | 状态 | 宽栏 | 图标栏(56px) | |---|---|---| | 读到余额 | `● 余额 ¥110.00`(绿点) | `¥110.00` | | 余额为 0 / 不可用 | `● 余额 ¥0.00`(红点) | `¥0.00` | | 未配置密钥 | `未配置密钥`(灰) | `—` | | 上游失败 | 数值变灰 + 悬停显示原因 | 灰度数值 | 悬停显示总余额、赠金 / 充值明细、更新时间;点击绕过宿主 30 秒缓存强制刷新。 ## 安装 两条路,任选其一。 ### 一键脚本(推荐) ```sh git clone https://gitee.com/bbsmrdj/dsh-plugin-deepseek-balance.git cd dsh-plugin-deepseek-balance node scripts/install.mjs ``` 脚本会把包放进当前 DSH profile 的 `node_modules/`,并在 `cordis.patch.yml` 里插入 Loader 条目(幂等:已安装则跳过;无法安全合并时给出提示而不是改坏文件)。 指定的 profile 不是默认的 `~/.dsh/profiles/web` 时: ```sh node scripts/install.mjs --profile /path/to/.dsh/profiles/web ``` ### 手动两步 1. 把仓库内容放到 profile 的 `node_modules` 下: ``` ~/.dsh/profiles/web/node_modules/dsh-deepseek-balance/ ├── package.json ├── index.js ├── index.d.ts └── client.js ``` 2. 在 `~/.dsh/profiles/web/cordis.patch.yml` 里追加(文件原本是 `[]` 时直接替换成下面整段): ```yaml - insert: - id: deepseek-balance name: dsh-deepseek-balance config: baseUrl: 'https://api.deepseek.com' credentialRef: DEEPSEEK_API_KEY ttlMs: 30000 ``` 然后**刷新页面**。profile 的 `patchReload: live` 会即时挂载宿主半边;浏览器半边要等下一次页面加载才会进入启动图(`window.__DSH_BOOT__`)。 ### 前置条件 - DSH 0.1.5-rc.2 上验证(组合里有 `webServer`、`connection`、`credentials` 三个服务,`dsh-web-app` 默认全都有) - 凭据里已存在 `DEEPSEEK_API_KEY`(`~/.dsh/.credentials.yaml`,或在设置界面的密钥控件里写入) ## 配置 | 字段 | 默认 | 说明 | |---|---|---| | `baseUrl` | `https://api.deepseek.com` | 走网关/镜像时改这里 | | `credentialRef` | `DEEPSEEK_API_KEY` | 换账号只换引用名,密钥仍不进配置文件;必须是 POSIX 标识符,写错会在加载时报错 | | `ttlMs` | `30000` | 宿主侧缓存寿命,`0` 表示每次请求都读上游 | | `timeoutMs` | `10000` | 单次上游请求超时 | ## 工作原理 ``` 浏览器(侧边栏胶囊) │ GET /deepseek-balance?refresh=1 ← 同源 fetch,带 DSH 登录 cookie ▼ 宿主路由(本包 index.js) │ ① ctx.connection.requestRejection(req) 不是可信来源 → 401/403 │ ② ctx.credentials.resolve('DEEPSEEK_API_KEY') │ ③ GET https://api.deepseek.com/user/balance ← 30 秒缓存 ▼ { ok: true, isAvailable: true, fetchedAt, balances: [{ currency: 'CNY', total, granted, toppedUp }] } ``` `package.json` 里的 `dsh.client.platform: 'web'` 与 `exports['./client']` 让宿主把同一个 Loader 条目的浏览器半边组合进启动图——一个条目,两半齐活。 ### 线上载荷 成功: ```json { "ok": true, "fetchedAt": 1760000000000, "isAvailable": true, "balances": [{ "currency": "CNY", "total": "110.00", "granted": "0.00", "toppedUp": "110.00" }] } ``` 失败一律 HTTP 200 + `ok: false`,`code` ∈ `credential-missing` / `credential-unreadable` / `credential-rejected` / `network-error` / `upstream-error` / `upstream-invalid`;带 `lastKnown` 表示上游刚失败、载荷里附带了上次成功值。路由本身只拒绝两类请求:过不了信任围栏(401/403)与非 GET(405)。载荷里永远没有密钥、没有上游原始报文。 ## 安全边界 - **密钥只在本进程内存里过一次**:配置只携带引用名,响应只携带金额;错误信息不会带值。 - **路由受组合的信任围栏保护**:`ctx.connection.requestRejection` 覆盖 Host/Origin 围栏与浏览器会话认证,未知来源拿不到任何余额信息(未认证请求返回 401)。 - **只读**:插件不写设置、不写凭据、不落盘。 - **零第三方依赖**:没有 `dependencies`,不会因为供应链把代码带进宿主进程。 ## 验证 ```sh npm run verify ``` 离线跑一遍:宿主半边的路由围栏、缓存、错误码与 `lastKnown` 回落(用 stub ctx 与 stub `fetch`,不联网);浏览器半边的加载器注册、`apply()` 挂槽与服务端渲染(需要能解析到 `react`,通常在 DSH profile 的 `node_modules` 里;解析不到时跳过并说明)。脚本还会在存在凭据时直连一次 `/user/balance` 确认通路——**不会打印密钥**。 ## 故障排查 | 现象 | 原因 / 处理 | |---|---| | 侧边栏没出现那一行 | 宿主半边已生效但页面还是旧的启动图:**刷新页面**。仍然没有就看宿主日志里的 `client-modules` 组合告警 | | 胶囊显示「未配置密钥」 | 凭据里没有 `DEEPSEEK_API_KEY`;在设置界面写入,或 `credentialRef` 指向了别的名字 | | 胶囊显示灰值 + 悬停报 401/403 | 上游拒绝了密钥(`credential-rejected`);换 key 后点一下胶囊即可 | | 悬停显示 `network-error` | 宿主出网被挡(代理/防火墙);设 `baseUrl` 指向可用网关 | | 想把卡放到别处 | 改 `client.js` 里 `ctx.slots.inject('sidebar.footer.action', …)` 的槽名(DSH 的任何 list slot 都能挂) | ## 卸载 删掉 `cordis.patch.yml` 里那一段 `- insert: … deepseek-balance`,再删 `node_modules/dsh-deepseek-balance/`,刷新页面即可。 ## 开发 ``` index.js 宿主半边(路由 + 缓存 + 凭据) index.d.ts 配置与线上载荷类型(结构化声明,不依赖 cordis 类型) client.js 浏览器半边(手写 lazy-CJS bundle,只 require('react')) scripts/install.mjs 装进 profile 并打 patch(幂等) scripts/verify.mjs 离线验证 + 可选上游连通性检查 examples/ 可直接抄的 cordis.patch.yml 片段 ``` 改 `client.js` 后需要让宿主重建该行:把 `cordis.patch.yml` 里那行注释改一下(触发 patch 层 live 重载)再刷新页面。改 `index.js` 会在 patch 重载时随宿主半边一起重挂。 发布: ```sh npm publish # 包名 dsh-deepseek-balance;想加 scope 就改 package.json 的 name git push origin main # 远程:git@gitee.com:bbsmrdj/dsh-plugin-deepseek-balance.git ``` ## License MIT © bbsmrdj