dsh-plugins
两个 DeepSeek Harness 本地插件:会话余额徽标 + 推理等级滑块,以及手机局域网免登录访问
- Stars
- 1
- Language
- JavaScript
- Created
- Oct 22, 2026
- Updated
- Oct 22, 2026
Introduction
dsh-plugins
两个给 DeepSeek Harness(DSH) 用的本地插件。装上之后:
- 会话标题栏右上角多一个余额徽标 ——
● ¥18.69,点一下立刻刷新; - 输入框右侧多一枚「推理等级」胶囊 —— 拖滑块调思考强度,档位跟着当前模型走;
- 左栏底部多一个「手机访问」按钮 —— 一键让手机在同一局域网里打开 DSH,手机端不用登录。
| 插件 | 你会看到 | 做什么 |
|---|---|---|
dsh-balance-effort | 标题栏 ● ¥xx.xx · 输入框右侧 ⚡ 高 ⌄ | 余额徽标(Host 直连 DeepSeek 余额接口,不把 Key 送到浏览器)+ 推理等级滑块 |
dsh-mobile-access | 左栏「手机访问」 | 内嵌反向代理,手机免登录打开 DSH 界面 |
两个包都是纯本地插件:没有构建步骤、没有第三方依赖,只有 lib/index.js(Host 半)和 lib/client.js(浏览器半)两份源码,直接就是本机正在跑的版本。
⚠️
dsh-mobile-access会把本机 DSH 的完整控制权开给端口上任何设备(等于本机代码执行,且不需要登录)。只在可信局域网里开,用完随手关掉 —— 详见安全说明。
安装
两个插件装法完全一样,以 dsh-balance-effort 为例(把名字换成 dsh-mobile-access 即是另一个)。
1. 放源码
# 把仓库里的插件目录整个拷到 DSH 的本地插件目录
Copy-Item -Recurse .\plugins\dsh-balance-effort "$env:USERPROFILE\.dsh\plugins\dsh-balance-effort"
目标路径是 %USERPROFILE%\.dsh\plugins\<包名>,里面应该直接就是 package.json / cordis.patch.yml / lib/。
2. 挂进 profile
DSH 按 profile 组合插件,改 %USERPROFILE%\.dsh\profiles\<profile>\package.json(<profile> 通常是 desktop 或 web)两处:
{
"dependencies": {
// ① 加一条 link 依赖,指向你刚放的目录(注意用正斜杠)
"dsh-balance-effort": "link:C:/Users/你的用户名/.dsh/plugins/dsh-balance-effort"
},
"dsh": {
"profile": {
"bundles": [
"@deepseek-ai/dsh-base",
"@deepseek-ai/dsh-web-app",
// ② 把包名追加进 bundles,它才会被真的加载
"dsh-balance-effort"
]
}
}
}
3. 重启 DSH
重启后 DSH 会按 link: 依赖在 profiles/<profile>/node_modules/ 里建好链接,并把 lib/client.js 编进页面模块图。重启完直接看界面:
- 余额徽标在会话标题栏右侧(跟
...同一行); - 推理等级胶囊在输入框右边;
- 「手机访问」在左栏底部、设置按钮旁边。
这两个插件互不依赖,可以只装其中一个。
排查:装完没反应
- 包名对不上。
bundles里的名字、dependencies的键、package.json的name、cordis.patch.yml里的id/name,四处必须完全一致。 link:路径写错。 Windows 路径照样用正斜杠;写错的话node_modules里不会出现链接。- 没重启。 Host 半(
lib/index.js)注册路由发生在启动时,改完必须重启 DSH。 - 浏览器半加载失败。
lib/client.js顶层是window.__ModuleLoader__.load({ id, ... }),id必须等于包名;改包名时别漏了它。 - 想看 Host 半的日志:插件里用的是
ctx.logger.info,正常启动会各打印一行serving .../ready ...。
1. dsh-balance-effort
余额 + 推理等级,一个包两个控件。
余额徽标
- 每 60 秒自动刷新;点一下立刻强制刷新(
?force=1绕过缓存);请求失败 5 秒后自动重试一次。 - 颜色即状态:🟢 正常 · 🟡 余额低于 ¥10 · 🔴 接口报错或
is_available: false· 脉冲 = 加载中。 - 悬停出明细:总余额、赠金 / 充值、更新时间。
- 没配 Key 时显示「未配置」,不会满屏报错。
Host 半在 DSH 自己的 webServer 上挂一条只读路由:
GET /dsh-balance/balance[?force=1]
→ { phase, total, granted, toppedUp, currency, isAvailable, updatedLabel, source }
流程是:ctx.credentials.resolve('DEEPSEEK_API_KEY') 拿到 Key → 服务端请求 https://api.deepseek.com/user/balance → 只把数字回给浏览器。安全口径在代码注释里写死了:只接受 GET、不接受任何入参(没法当 SSRF 跳板)、响应 no-store、永不回显 Key。5 秒内的重复请求合并成一次,单次请求 20 秒硬超时。
推理等级滑块
- 胶囊上写着当前档位(低 / 中 / 高 …,取模型目录里的
name),点开是滑轨面板。 - 拖动、点轨道、方向键都能改;改动立刻通过
modelDirectories.select()写回会话,左上角按钮恢复模型默认档位。 - 档位不是写死的,来自模型目录里
reasoning.efforts;换模型时胶囊跟着变。 - 拿不到档位时不会静默消失,而是渲染一枚灰胶囊并写明断点原因 ——
无服务/无目录/加载中/无选择/未收录/无档位。这是刻意的:注册成功却看不见属于最难查的哑失败。
2. dsh-mobile-access
让手机访问只监听 127.0.0.1 的 DSH Web GUI,全程在界面里开关。
用法:左栏底部「手机访问」→ 点开 → 开启 → 面板列出本机所有局域网地址(带网卡名、可复制)→ 手机连同一个 WiFi,浏览器打开任意一条即可,不用输 token、不用配对。
为什么能免登录:DSH 的 /api 信任围栏只认环回或已 trusted 的 authority,并且用 authority 绑定的签名 cookie 认浏览器。插件做两件事:
- 内嵌一个 http 反向代理,监听
0.0.0.0:3081→ 转发到127.0.0.1:<DSH 真实端口>,把每个请求的Host/Origin重写成环回 authority,围栏自然放行(不用改 DSH 配置,也不用--trusted-host); - 用本机凭据
client-connection/browser-session里的密钥现场签发一张 cookie 注入每个转发请求,所以手机侧不需要任何凭据。
普通 HTTP 和 WebSocket 升级(/api/remote.mux 流式通道)都支持,否则手机上会一直转圈。
控制面(挂在 DSH 自己的 webServer 上,只有同源浏览器会调):
GET /dsh-mobile-access/state → { running, dshPort, proxyPort, urls: [{ ifName, address, url }], error }
POST /dsh-mobile-access/start
POST /dsh-mobile-access/stop
几个实现细节:
- 默认不自动启动,必须你在界面上显式点开;插件被禁用/卸载、DSH 退出时监听器跟着关。
- 代理端口默认
3081;被占用时start会返回 500 并把错误显示在面板上。 - 会滤掉
169.254.x.x这类链路本地地址(网卡没拿到 DHCP 时出现的占位地址,手机连不上)。
安全说明
dsh-balance-effort:浏览器半只拿得到余额数字。Key 全程留在 Host 进程里,路由无入参、只读。dsh-mobile-access:开启期间,任何能访问该端口的设备都拥有对本机 DSH 的完全控制权(等价于本机代码执行),而且不需要登录。面板上这句警告是常驻的,不是装饰。- 只在你能信任的局域网里开(家里 / 自己的热点);
- 别在公共 WiFi、公司网络、或者端口被映射到公网的情况下开;
- 用完点「关闭手机访问」,不要靠关浏览器标签页。
卸载
- 从 profile 的
bundles里删掉包名,从dependencies里删掉那条link:; - 删掉
%USERPROFILE%\.dsh\plugins\<包名>; - 重启 DSH。
浏览器端注入的样式标签和监听器都跟着 effect 一起回收,不会残留。
许可
MIT —— 见 LICENSE。