Skip to content
dsh.fish
Bundle

dsh-deepseek-balance-badge

DeepSeek account balance badge in the DSH sidebar footer, with a settings card for the refresh period. The API key stays in the host process.

Source
chenjingchun
License
MIT
Updated
Updated 20 days ago

Readme

# dsh-deepseek-balance-badge

DSH 插件:在 Web GUI 侧边栏底部常驻一个 DeepSeek 账户余额徽标,点击展开明细浮层,内含**手动刷新**按钮,并按固定周期**自动刷新**;刷新周期与宿主缓存窗口可在 **设置 → 插件 → 插件配置** 里改。

![侧边栏徽标与浮层](assets/screenshot-1.png)

```
侧边栏底部  [◈ ¥354.64]  ← 展开态
            [◈]          ← 收起态(56px 轨道)

点击后浮层:
  DeepSeek 余额            账户可用
  总余额                   ¥354.64
  赠送余额                 0.00
  充值余额                 354.64
  ─────────────────────────────────
  更新于 2 分钟前          [刷新]
```

## 组成

| 半边 | 位置 | 职责 |
|---|---|---|
| 宿主 | `src/host/index.js` | 从 `ctx.credentials` 解析 API Key,请求 `api.deepseek.com/user/balance`,在 `GET /balance/api/summary` 上返回归一化 JSON;经 `ctx.settings` 注册本插件的设置 namespace |
| 客户端 | `src/client/client.js` | 注册 `sidebar.footer.action` 席位(徽标与浮层)与 `settings.plugin.item` 卡片(轮询周期 / 缓存窗口),轮询宿主路由 |

**API Key 永不进入浏览器**:浏览器只访问同源宿主路由,密钥留在宿主进程。这也是必需的——`api.deepseek.com` 不返回 CORS 头,浏览器直连本来就会失败。

## 安装

从 npm 安装(推荐,预构建、无需本地构建授权):

```sh
dsh plugin --profile web add dsh-deepseek-balance-badge
```

或直接从 GitHub 安装源码:

```sh
dsh plugin --profile web add github:<your-github-user>/dsh-deepseek-balance-badge
```

装完刷新页面即可(客户端半走 HMR);宿主半在首次安装后需要重启一次应用。

### 本地开发安装

开发时把工作区以 `file:` 依赖装进 desktop profile:

```powershell
# 1. profile 清单:dependencies 与 dsh.profile.bundles 两处都要写
#    C:\Users\User\.dsh\profiles\desktop\package.json
#      "dependencies":  { "dsh-deepseek-balance-badge": "file:D:/Projects/DeepSeekHarness插件/dsh-deepseek-balance" }
#      "dsh.profile.bundles": [ ..., "dsh-deepseek-balance-badge" ]

# 2. 安装
pnpm install --dir "C:\Users\User\.dsh\profiles\desktop" --no-frozen-lockfile

# 3. 校验解析(不重启就能发现清单错误)
node scripts/verify-install.mjs

# 4. 完全退出并重启 DSH Desktop —— 宿主半不会热加载
```

### 改了代码之后

`file:` 依赖是**拷贝**而非软链,且 pnpm 按版本号缓存。改了源码后必须强制重装,否则运行的是旧副本:

```powershell
node scripts/pack.mjs          # 源码 → client/client.js(客户端半的唯一构建步骤)
node scripts/resync.mjs        # 删除已安装副本 + 重装 + 校验
```

客户端 bundle 改动后,删除副本重装即会触发 `dsh-client-hmr` 的 rev 变更,浏览器端会自行重载;**宿主半改动始终需要重启应用**。

### 卸载

从 profile 的 `dependencies` 与 `dsh.profile.bundles` 删除 `dsh-deepseek-balance-badge`,执行一次 `pnpm install`,重启应用即可。

## 配置

配置有**两层**,用户层在上:

| 层 | 位置 | 谁能改 |
|---|---|---|
| 用户层 | `~/.dsh/settings.yaml` 的 `dsh-deepseek-balance:` 分节 | **设置 → 插件 → 插件配置** 里的卡片,或直接编辑该文件(改完即生效) |
| 组合层(base) | profile 的 `cordis.patch.yml` 里本插件条目的 `config:` | 部署者 |

没有用户覆盖时,值来自组合层,缺失字段回落到 schema 默认值;卡片里留空某个字段就是删除用户覆盖,该字段重新继承组合层。**两层都写了同一个键时,用户层生效**——所以改了 patch 里的值却不生效时,先去卡片里看看是不是覆盖过。

```yaml
# profile 的 cordis.patch.yml(组合层)
- id: dsh-deepseek-balance
  config:
    pollSeconds: 300      # 自动刷新周期(秒),钳制到 [30, 86400],默认 300
    apiKeyRef: DEEPSEEK_API_KEY
    baseUrl: https://api.deepseek.com
    cacheTtlSeconds: 10   # 宿主结果缓存窗口,手动刷新绕过它
```

卡片只暴露 `pollSeconds` 与 `cacheTtlSeconds` 两个数值;`apiKeyRef`、`baseUrl` 属于部署级配置,仍走组合层。**改完立即生效**:宿主监听 settings 提交后重新解析,客户端下一次轮询就采用新的周期,不需要重启应用。部署没有 settings 提供方(或存储的分节被 schema 拒绝)时,插件回落到组合层继续工作,卡片显示为「未就绪 / 只读」。

## 行为细节

- **徽标左对齐**:`sidebar.footer.action` 席位是 `align-items: center` 的纵向 flex,插件自带一小段样式表:外层容器 `display: flex; width: 100%` 先把整行占住,触发器再 `width: 100%` + `justify-content: flex-start`,并用 `margin-inline: -2px` 吃掉页脚 house rule 的 4px bleed,于是与市场/Cordis 启动器和下方设置行共用同一条左边线。触发器宽度刻意用 `100%` 而不是 house rule 的 `calc(100% + 4px)`:桌面外壳会自行加宽并重新居中这个席位,左对齐不该取决于百分比在一个被重新居中的盒子里怎么解算。水平内边距取设置行自己的 `0 10px 0 8px`,因为 Button 原语统一的 `0 10px` 会让图标比设置齿轮右偏 4px。收起态轨道仍还原成居中的 36px 圆形图标按钮。
- **打开即取**:面板挂载时立刻拉取一次;此后按 `pollSeconds` 轮询。
- **设置卡片**:在 **设置 → 插件 → 插件配置** 里编辑 `pollSeconds` 与 `cacheTtlSeconds`。卡片自身不画外壳——表头、未保存标记、保存/放弃按钮与只读提示都由设置外壳渲染,插件只提供两个输入框与写入逻辑。写入走设置 seam 的 revision 栅:文档在别处被改过就拒绝本次写入并重新载入,而不是覆盖别人的修改;空输入框表示删除该字段的用户覆盖。
- **手动刷新**:`?force=1` 跳过宿主缓存;请求期间按钮禁用并旋转,且不会被同刻的在途轮询"顶包"。
- **后台省电**:页面不可见时跳过轮询;回到前台且数据已过期则立即补拉。
- **单飞**:宿主的并发轮询共享一次上游调用,避免刷新风暴。
- **失败不清空**:上游出错时保留上次成功数据,浮层内联显示错误码与原因。
- **多币种**:逐行展示,徽标显示 `balance_currency` 对应币种。

### 错误码

| 错误码 | 含义 |
|---|---|
| `credential-missing` | 未找到 `DEEPSEEK_API_KEY` |
| `credential-rejected` | 上游 401/403,密钥无效或被吊销 |
| `credential-error` | 凭据服务读取失败 |
| `rate-limited` | 上游 429 |
| `upstream-timeout` | 上游请求超时(10s) |
| `upstream-unreachable` | 无法连接上游 |
| `upstream-error` | 上游返回其它错误状态 |
| `upstream-malformed` | 上游响应无法解析,或没有可用余额条目 |
| `internal-error` | 宿主内部错误 |

## 开发

```powershell
node --test "tests/*.test.mjs"   # 44 个用例:宿主路由/错误映射/缓存/单飞/设置分节 + 客户端 bundle 契约、样式表、设置卡片与真实 React 渲染
node scripts/pack.mjs            # 生成 client/client.js
node scripts/pack.mjs --check    # 校验产物与源码一致
node scripts/publish-check.mjs   # 发布前置检查:manifest、dsh.bundle、repository、tarball 内容
node scripts/settings-check.mjs  # 用真实的 dsh-settings-file provider 验证「卡片写入 → 落盘 → 即时生效」
node scripts/live-check.mjs      # 真实凭据 + 真实上游(需已配置 Key 且联网)
node scripts/verify-install.mjs  # 已安装副本能否被 Loader 正确解析
node scripts/resync.mjs          # 强制把当前源码同步进 profile
```

`node_modules/@deepseek-ai/*` 与 `node_modules/react` 是**指向 DSH 安装的 junction**,仅供本地跑测试;它们已从 `package.json` 的 `files` 白名单排除,不会进入安装副本。运行时这些包由 DSH 安装锚点解析(与所有 `@deepseek-ai/*` 消费者相同)。

## 发布与收录

本包只通过 [awesome-dsh-plugin](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin) 精选列表分发:`dsh-market`、DSH Desktop 内置社区市场、dshget、1024store、dshfind 等商店的目录都源自该列表,且内置市场**只允许安装列表内的来源**。想让插件上架,只需要向那个仓库提一个 PR。

```powershell
# 0. 把 package.json 里的 author / repository / homepage / bugs 换成你自己的 GitHub 账号
#    仓库名默认是 dsh-deepseek-balance-badge;若你换了仓库名,一并替换:
#    (Get-Content package.json -Raw).Replace('REPLACE_WITH_YOUR_GITHUB_USERNAME','<owner>') |
#      Set-Content package.json -Encoding utf8 -NoNewline
node scripts/publish-check.mjs    # 占位符没换掉会直接 FAIL

# 1. 建仓并推送(仓库名需与 package.json 的 repository 一致)
git init; git add -A; git commit -m "feat: DeepSeek balance badge for the DSH sidebar"
git remote add origin https://github.com/<owner>/dsh-deepseek-balance-badge.git
git push -u origin main
# 再给仓库加上 `dsh-plugin` topic,并确认仓库创建满 1 天(列表 CI 会校验)

# 2. 发布到 npm(可选,但预构建安装体验更好;package 的 repository 字段负责与列表条目关联)
npm publish

# 3. 向列表投稿:只加一个文件,不要改 README
#    data/plugins/<owner>__dsh-deepseek-balance-badge.yml
#      url: https://github.com/<owner>/dsh-deepseek-balance-badge
#      name: <owner>/dsh-deepseek-balance-badge
#      category: usage
#      description:
#        en: DeepSeek account balance in the DSH sidebar footer, ...
#        zh: 在 DSH 侧边栏底部显示 DeepSeek 账户余额,...
```

商店截图由仓库自带的 `screenshots.json` 声明(相对路径,1–8 张),这样以后换图只需推自己的仓库。

## 已知限制

- **客户端 bundle 手写、无打包器**:浏览器 seed 表只有 `react`、`react/jsx-runtime`、`react-dom`、`@deepseek-ai/cordis`、`dsh-client-store`、`dsh-client-ui-slots`、`dsh-client-ui-primitives`、`dsh-client-ui-dockkit`。因此源码只用 `React.createElement`(不用 JSX),也不 import `dsh-client-ui-settings` 等非 seed 包——设置卡片是通过 `ctx.get('settingsScope')` 这个运行时服务拿到的,不产生模块依赖。测试中的 `require` 白名单会挡住违规。
- **UI 测试不自建 DOM**:`react-dom` 不是本包依赖,渲染用例用真实 `react` 加手写 dispatcher 展开组件树,覆盖组件逻辑但不覆盖真实布局与样式。
- **新增宿主插件必须重启应用**:DSH 不热加载宿主半。
- **客户端 seed 包不作为 peerDependency 声明**:`@deepseek-ai/dsh-client-ui-primitives` 与 `dsh-client-ui-settings` 由浏览器 seed 表提供(`dsh.client.inject` 里已声明),不是 Node 解析的依赖。声明成 peer 反而会在 harness 换预发布版本时给用户制造 ERESOLVE,而那些版本本来就是 seed 提供的。宿主半边真正 import 的 `@deepseek-ai/schemastery` 是唯一的 peerDependency。

Install

dsh plugin --profile web add github:chenjingchun/dsh-deepseek-balance-badge

Profile: web

  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source