dsh-balance-chip
版本号见 package.json 与 CHANGELOG.md。
DSH(DeepSeek Harness)Web GUI 插件:在输入框下方那一排显示 DeepSeek 余额 与 消费,点击可展开明细。

◉ 余额 ¥554.68 ● 会话累计 ¥19.33 ● 本轮消耗 ¥0.054 空闲

三个数各带标签,含义必须分清(混淆会得出完全错误的结论);末尾还有一个时段结论:
顺序按「变化速度」由慢到快排列,最活跃的数字在最右边。三段数字始终占位,
算不出来时显示 —(不整段省略,否则数字会左右横移):
| 数 | 含义 | 口径 |
|---|---|---|
| 总余额 | 账户可用总额 = 充值余额 + 赠金余额 | 快照值。浮层标题也是它;正文前两行是构成:第一排「充值余额」、第二排「赠金余额」 |
| 会话累计 | 本会话建立至今的累计 | 官方 tokenUsage.totals |
| 本轮消耗 | 你这一句话花了多少 —— 从发出到此刻的合计 | 当前 totals − 本轮起点的 totals,实时累加,答完停住 |
末尾的时段结论也是常驻的:空闲 / 高峰 / 假日。它决定单价是 1 倍还是 2 倍 ——
看到「高峰」就知道为什么这一轮贵。法定节假日按官方规则全天算空闲,但仍单独显示
「假日」,否则容易被误认为没认出节假日、按工作日算错了。拿不到时段信息时显示 —,
不省略这一段(四种状态都是两字,宽度恒定,切换时右边元素不会横移)。
位置与交互对齐原厂 StatsPills(会话统计 / token 用量 / 上下文那三个胶囊):透明胶囊、悬停出底色、点击弹浮层、外部点击或 Esc 关闭。
官方 UI 从不展示消费金额,也不展示单次/单轮用量 —— 因为
tokenUsage的wire.view只把累计值发给客户端。 本插件读取 host 原始状态并自行计价,详见下面「数据来源」。
特点
- 实时:余额与会话累计 15 秒一轮;「本轮消耗」2 秒一轮 —— 回答过程中能看到它在涨
- 胶囊看总额、浮层看构成:胶囊上是总余额(充值 + 赠金)—— 一眼知道还有多少钱;
展开浮层看构成:标题「总余额」,正文第一排「充值余额」、第二排「赠金余额(优先消耗)」。赠金是会过期的那部分,
官方规则是先扣赠金;没有赠金时显示
— - 本日消耗汇总当天所有会话:扫会话日志按事件时间求和,不是只看当前会话
- 峰谷自适应:单价按官方规则实时切换(高峰 = 工作日 9-12 / 14-18 点;法定节假日全天按空闲),
且当前时段直接印在胶囊上(
空闲/高峰/假日),不必点开浮层 - 外观对齐原厂:字号、字重、行高、内边距、分割线逐条抄自官方 CSS 模块,不是估的(见「实现要点」)
- 零依赖:纯 JavaScript,无原生模块,无 install / postinstall 脚本
- 首选路径不联网:余额走本机 DSH 账号服务,费用从本机会话日志与会话投影计算。
⚠️ 但有一条兜底路径会联网 —— 账号服务不可用时回退到
api.deepseek.com/user/balance,带上本机已配置的 API Key。详见「它联不联网」
快速开始
1)拿到 tgz —— 从 Releases
下载最新的 dsh-balance-chip-<版本>.tgz(或直接用仓库里 releases/ 目录中的那一份)。
2)装:
dsh plugin --profile desktop add dsh-balance-chip-<版本>.tgz
装完重启 DSH。desktop 是 profile 名,换成你自己的。
本插件没有发布到 npm —— 安装一律走 tgz,不依赖任何账号; 装的过程也不需要联网(只有下载 tgz 那一步要)。
完整安装说明与排查清单见 INSTALL.md。
想先看效果、不用装:
node tools/gen-preview.mjs # 生成 docs/preview.html
打开 docs/preview.html —— 里面把原厂胶囊与本插件胶囊并排渲染,可直接比对样式。
安全边界与下载来源
只从本仓库的 Releases 下载安装包。
本插件会读取本机 ~/.dsh/.credentials.yaml,用于账号服务不可用时直连
api.deepseek.com/user/balance 的兜底查询。默认配置下凭据只发往 api.deepseek.com:
不写入日志、不返回给浏览器、不发往任何第三方。
⚠️ 两个例外,必须说清:
baseURL是可配置的(见「配置」一节)。若你把它改成别的地址,兜底路径就会把 API Key 发往那个地址 —— 上面那条保证只对默认值成立。- 被改过的副本(fork、二次打包、第三方网盘)不受任何保证 —— 这也是要核对 SHA256 的原因。
另外,HTTP 接口只校验回环地址与 Host,不做 DSH 登录校验:同一台机器上的其他进程 也能读取余额。它只能读 —— 插件对磁盘、对配置都没有写操作。
每个 Release 附带 SHA256SUMS.txt,下载后可自行核对:
# Windows
Get-FileHash .\dsh-balance-chip-<版本>.tgz -Algorithm SHA256
# macOS / Linux
sha256sum dsh-balance-chip-<版本>.tgz
与 Release 页面上的值一致即可。
其他边界:零依赖、无 install / postinstall 脚本;运行时对磁盘只读
(不写任何文件);HTTP 路由只接受回环地址(127.0.0.1)的请求。
测试
五套测试全是纯 Node、零依赖、不联网,也不用启动 DSH。
运行环境要求 Node ≥ 22.15.0 —— host 端用
node:zlib的zstdDecompressSync解压会话日志,该 API 自 Node 22.15.0 起提供。(DSH 桌面版内置的是 Node 24,满足。)
node tools/selftest.mjs # 68 项 host 端到端(余额、计费、回环校验、换轮基准)
node tools/render-test.mjs . # 110 项 渲染(金额精度、标签配对、胶囊可见性、面板结构)
node tools/heartbeat-test.mjs . # 6 项 心跳独立于组件、节奏参数
node tools/refresh-test.mjs . # 13 项 「立即刷新」的排队与补发
node tools/gen-preview.mjs # 28 项 预览页自检
退出码 0 即全部通过。CI 在 Node 22 / 24 上跑(.github/workflows/tests.yml)。
⚠️ 测试里刻意放了几条只有实现正确时才成立的等式断言 (例如"第二次开新轮时本轮消耗 ≈ 0 且小于会话累计")。 它们不是装饰:0.10.0 修掉的那个高危 bug(换轮不重置基准 →「本轮消耗」等于「会话累计」) 就是靠一条这样的断言才被抓住的,并用变异测试验证过"把缺陷注回去测试确实会失败"。 详见 CHANGELOG 的 0.10.0 与 0.10.2。
独立审计记录
这个项目经历过两轮外部独立审计(在 macOS 上做:浅克隆 + 字节级比对 +
ditto 实测解压 + 向断言注入缺陷验证其有效性),共报出 16 条问题,
绝大多数自测发现不了。
完整记录见 docs/audits/ —— 包含每条问题的 证据、根因、修法,以及我为什么会漏掉它们。
其中最值得看的一条:某次修复和对应的断言犯了同一个盲区 (都只覆盖平台表格、漏掉散文段落),于是同一份发布包里两份文档的平台结论 互相矛盾 —— 而测试全绿。
那条矛盾的原文是:README 写"macOS 装不了"、INSTALL 写"macOS 实测可用"。 放在引用块里是因为它是错的,只是被引来作说明;
tools/pack-release.mjs的文档断言以"是否在引用块内"区分主张与更正说明。
项目结构
lib/
client.js 浏览器端:胶囊 + 浮层(手写 createElement,无构建步骤)
index.js host 端:余额读取、token 计费、本日跨会话扫描
holidays.js 法定节假日表(唯一的年度维护点)
tools/ 五套测试 + 打包脚本 pack-release.mjs + 预览生成器
docs/preview.html 样式预览(由 gen-preview.mjs 生成,需与源码同步)
docs/audits/ 两轮独立审计的完整记录(问题、证据、根因、修法)
releases/ 发布包(.tgz 安装包,每个版本一个 tag)
QUICKSTART.md 中文上手(含每个数字的定义、联网边界、年度维护)
CHANGELOG.md 完整版本历史(含每个 bug 的根因与修法)
INSTALL.md 安装与排查
安装位置
插件目录可以放在任意位置,下例用 ~/.dsh/plugins/dsh-balance-chip:
~/.dsh/plugins/dsh-balance-chip ← 插件源码(位置可自选)
├─ lib/index.js host 半身
├─ lib/client.js client 半身(浏览器)
├─ lib/holidays.js 法定节假日数据(唯一的年度维护点)
├─ cordis.patch.yml bundle 激活层
├─ docs/preview.html 样式预览(浏览器打开)
└─ tools/gen-preview.mjs 预览生成器(从 client.js 求值,见「预览」一节)
profile 侧用 link 依赖指向它,不需要把代码拷进 profile 目录:
~/.dsh/profiles/<profile>/
├─ package.json dependencies: dsh-balance-chip → link:<插件目录>
├─ pnpm-lock.yaml 同上(specifier / version)
└─ node_modules/dsh-balance-chip → 软链到插件目录
装完(改完代码后同理)都要重启 DSH 才生效:host 半身必须重启,client 半身随 host 一起重新生成。
平台支持
插件代码本身是跨平台的:
- 纯 JavaScript,无原生模块(无
.node)、无 install/postinstall 脚本、无运行时依赖 - 无
win32/process.platform/process.arch判断,无盘符或反斜杠硬编码 - 唯一的文件路径访问走
path.join(os.homedir(), ".dsh"),在 macOS / Linux 上自动解析为/Users/<你>/.dsh、/home/<你>/.dsh cordis.patch.yml用的是 POSIX 相对路径./cordis.patch.yml- client bundle 运行在浏览器里,与操作系统无关
- 节假日表是内置静态数据,运行时不访问任何外部接口
平台支持:Windows 与 macOS 均已实机验证。
| 平台 | 状态 | 已验证的组合 |
|---|---|---|
| Windows x64 | ✅ 实机验证 | win32-x64 + DSH 0.2.0-rc.2 / 0.2.1-rc.3(Node 24.18.1) |
| macOS arm64 | ✅ 实机验证 | darwin-arm64 + DSH 0.2.0-rc.2(Node 24.18.1) |
| macOS x64 / Linux | 预期可用 | 同一份代码,未单独验证 |
判断你自己的 DSH 是哪个平台,读安装目录里的 resources/app.asar → dsh/desktop-runtime.json:
{ "platform": "darwin", "arch": "arm64", "release": { "version": "0.2.0-rc.2" } }
platform 在 Windows 上是 win32、macOS 上是 darwin、Linux 上是 linux。
⚠️ 这一节曾经写错过两次,都记在这里以免重犯:
- 引用过一个更新源地址并声称"实测 200 / 其余 404" —— 那些 URL 是我猜的, 实测全部 404,没有依据。
- 更严重的是据此断言"官方只有 Windows x64 一个构建、macOS 装不了"。 错在把本机安装实例的构建元数据(
platform: win32、更新地址feeds/win-x64/) 当成了官方平台列表。macOS 版确实存在,且已在 darwin-arm64 上实测装通、 接口正常返回余额。教训:一个描述「这台机器」的字段,不能拿来推断「所有平台」。
安装命令(各平台相同,只是路径写法不同):
# macOS / Linux
dsh plugin --profile desktop add ~/Downloads/dsh-balance-chip-<版本>.tgz
# Windows (PowerShell)
dsh plugin --profile desktop add "C:\路径\dsh-balance-chip-<版本>.tgz"
把版本号换成你实际下载到的那个文件名 —— 包名里的版本会随发布变化。 运行环境要求 Node ≥ 22.15.0(DSH 桌面版内置的 Node 已满足),详见 INSTALL.md。
完整的平台说明、三种安装方式、以及"装完没反应"的排查步骤,见 INSTALL.md。
数据来源(都是官方接口,不自己造数据)
| 显示项 | 来源 | 说明 |
|---|---|---|
| 总余额 | 下面两者之和(host 计算) | 胶囊与浮层标题都显示它。账号侧返回的是 normal_wallets 与 bonus_wallets 两个并列钱包,total 由插件相加得出;赠金缺省时就等于充值余额 |
| 充值余额 | ctx.deepseekAccount.getBalance(client) 的 normal_wallets |
DSH 账号服务;已处理凭据读取、Platform 的五个 x-client-* 头、余额超时与 401 失效。兜底可走 GET api.deepseek.com/user/balance(API Key 账号,那里 total_balance 本身就是总额)。浮层正文第一排显示的就是这个数 |
| 赠金余额 | 同上返回值的 bonusWallets |
账号的第二个钱包(送的那部分,会过期)。官方账号设置页并列展示「充值余额 / 赠金余额」;本插件放在浮层正文第二排,没有时显示 —。官方扣费规则:两种余额同时存在时优先扣减赠送余额(文档),所以行标签写作「赠金余额(优先消耗)」 |
| 会话累计 | ctx.sessionProjections.stateOf(session, "tokenUsage") 的 totals |
官方 @deepseek-ai/dsh-token-meter 注册的会话投影 |
| 本轮消耗 | 当前 totals − 本轮起点 totals |
本插件补出来的口径。官方只有 totals(整会话)与 last(最近一步),没有"一轮合计"。做法:在 turn/start 事件记下当时的 totals 作基准(按会话存 WeakMap),每次请求做差 |
| 每轮平均消耗 | 会话累计 ÷ sessionStats.turns |
官方 session-stats 投影提供轮数。这是现有数据下唯一能回答"一句话大约多少钱"的数 |
| 缓存占比 | totals.cacheReadTokens ÷ 四项之和 |
解释成本为何低:缓存读取单价 0.02,未缓存输入 1,差 50 倍 |
为什么要自己补「本轮」:官方
tokenUsage的wire.view是view: state => state.totals—— 只把累计值发给客户端,last(最近一步) 与turnBoundary都留在 host 侧,官方 UI 因此从不展示"单次/单轮花了多少"。 本插件用stateOf()读 host 原始状态才拿到last.buckets;而"本轮合计" 官方根本没算,是用turn/start基准做差补出来的。三个数的含义务必分清,混淆会得出完全错误的结论: 余额(账户快照)· 本轮(这一句话从发问到此刻)· 会话(本会话建立至今)。
计价按 DeepSeek 官方价目表(元/百万 tokens,deepseek-flash):
| 空闲时段 | 高峰时段 | |
|---|---|---|
| 输入(缓存命中) | 0.02 | 0.04 |
| 输入(缓存未命中) | 1 | 2 |
| 输出 | 4 | 8 |
高峰 / 空闲是怎么判断的
依据 DeepSeek 官方文档(模型 & 价格 注 2)原文:
空闲时段价格为高峰时段价格的一半。北京时间周一至周五(不含中国法定节假日) 9:00 - 12:00、14:00 - 18:00 为高峰时段;其余时段,包括周末及中国法定节假日 全天均为空闲时段。
译成判定式(lib/index.js 的 computeCost):
// 1. 先把本机时间换算成北京时间(getTimezoneOffset() 是"UTC 减本地"的分钟数)
const beijing = new Date(now.getTime() + (now.getTimezoneOffset() + 480) * 60000);
const weekday = day >= 1 && day <= 5; // 周一~周五
const inPeak = (hour >= 9 && hour < 12) || (hour >= 14 && hour < 18); // 9-12、14-18
const holiday = isStatutoryHoliday(年, 月, 日); // 见 lib/holidays.js
const peak = weekday && inPeak && !holiday;
四条规则,缺一不可:
| 情况 | 判定 |
|---|---|
| 周末(含调休补班的周六日) | 空闲 |
| 法定节假日(即使落在周一至周五) | 空闲 |
| 周一至周五、非节假日、9–12 或 14–18 点 | 高峰 |
| 其余一切 | 空闲 |
为什么不需要"调休补班日"数据:官方原文是
北京时间周一至周五(不含中国法定节假日)9:00 - 12:00、14:00 - 18:00 为高峰时段; 其余时段,包括周末及中国法定节假日全天均为空闲时段。 —— 模型 & 价格
判据是"星期几",不是"这天是否上班"。所以调休补班的周六日仍然是周末 → 空闲, 不会产生新的高峰时段。于是只需要"法定节假日"这一张表,不需要再维护一张补班表。
节假日数据与维护
lib/holidays.js 内置"放假日期表",2025 与 2026 两年已收录。数据来自国务院办公厅
每年发布的《关于XXXX年部分节假日安排的通知》,2026 年已与
官方原文逐条核对一致。
每年 11 月前后国务院会公布次年安排。届时重新生成即可(把年份换成需要的):
node -e "fetch('https://timor.tech/api/holiday/year/2027').then(r=>r.json()).then(b=>console.log(Object.entries(b.holiday).filter(([,v])=>v.holiday===true).map(([k])=>k).sort().join(' ')))"把输出填进
STATUTORY_HOLIDAYS对应年份即可。
未收录的年份会自动回退:isStatutoryHoliday() 返回 false,退化为"仅按工作日 + 时段"
判断,不会报错,只是那年的法定节假日会偏高估约一倍。浮层底部「当前时段」只显示结论
(空闲 绿 / 高峰 橙),不再带括号里的判断依据 —— 依据看下面那行警示即可。
数据缺失的那一年,浮层里会额外多出一行「节假日数据」(
本年未收录 · 节假日期间费用可能偏高) —— 只在缺数据时出现,数据齐全时不显示:这一行的意义是警示,不是汇报"一切正常"。
另一层近似(无法消除):
tokenUsage投影给的是累计总数,把不同时段的用量 加总在一起了。所以跨时段的长会话,累计金额是按"当前时刻的费率"折算的近似值。 要精确需从会话日志逐步读每次请求的时间戳与用量、分段计价 —— 成本过高,未实现。
配置
可选,在 profile 的 cordis.patch.yml 里按 id 覆盖:
- id: balance-mini
name: dsh-balance-chip
config:
refreshMs: 30000 # 余额刷新间隔(毫秒,默认 60000)
timeoutMs: 15000 # 余额查询超时(毫秒,默认 15000)
baseURL: https://api.deepseek.com # 仅 API Key 兜底路径使用
实现要点(踩过的坑,改代码前请先看)
client bundle 必须是经典脚本,不是 ES module。 宿主用
window.__ModuleLoader__.load({ id, factory })加载,factory里用module.exports导出。 写成顶层export会直接SyntaxError→ web 启动失败,宿主为了自保会把整个 bundle 从dsh.profile.bundles摘掉(历史 crash 日志里是web boot: 1 entry did not activate)。 插件加载失败时,宿主提供"禁用第三方插件"恢复入口,那会备份并重置 profile patch。不要用
ctx.connection.rpc.handle()注册通道。 它内部走owner.webServer.register(),而owner取的是 connection 实例自己的 ctx, 读不到webServer(与本插件加载时机无关,重试 15 次错误一字不变),恒抛cannot get property "webServer" without inject。 改用本插件自己的 ctx:ctx.webServer.register({ kind:"prefix", path, handler })。客户端 RPC 是带信封的协议,不是普通 POST。 请求
{ type:"client-request", rpcId, method, payload }→ 响应必须是{ type:"server-response", rpcId, result:{ ok:true, value } }; 失败时result.error的code/message/details三者都必需,details还必须是对象, 否则客户端解析时抛TypeError。rpcId必须原样回传(否则rpcId mismatch)。绕过了 Connection 的 BrowserAuth,所以自己把门。 路由只接受 Host 为
127.0.0.1/localhost/::1的请求,否则 403 —— 别把这个检查删掉。会话刚建立的头几秒取不到费用是正常的。
ctx.sessions.get()只认"活着"的会话;此时computeCost静默返回undefined, 由客户端 3 秒快速重试补上(取到后回到 60 秒节奏)。这是设计,不是 bug。不要给 design token 的
var()加兜底值。 原厂组件一律写var(--token),不加 fallback。加兜底看似更健壮,实际会让该属性 解析出与原厂不同的值,产生难以察觉的视觉偏差 —— 本插件在这上面栽过两次:token 加兜底的后果 --dsh-content-font-size-secondary胶囊字号与另外三个胶囊不一致(0.1.1 修) --dsw-alias-separator-primary分隔点亮度只有原厂的一半,看着更小更虚(0.2.2 修) 只有 token 确实可能缺失时才加兜底。DSH 的主题层保证这些 token 存在。
投影形状是扁平的。
useProjection("tokenUsage")返回的对象直接带uncachedInputTokens/outputTokens/cacheReadTokens/cacheWriteTokens,没有totals包一层(host 侧stateOf返回的 才有{ totals, last })。两处读取形状不同,别混。
卸载
dsh plugin --profile desktop remove dsh-balance-chip
然后删掉本目录即可。也可以手动把 dsh-balance-chip 从 profile 的
package.json 的 dsh.profile.bundles 数组里移除、并删除
node_modules\dsh-balance-chip 链接。
刷新节奏
| 常量 | 值 | 含义 |
|---|---|---|
BEAT_MS |
1000 | 心跳检查频率(醒来判断是否需要取数,不代表每次都发请求)。必须 ≤ TURN_MS,否则检查粒度成为瓶颈 |
TURN_MS |
2000 | 「本轮消耗」距上次取数超过此值就重取 —— 它是活跃字段,一个回答里会跳好几次 |
refreshMs |
15000 | 其余字段(余额、会话累计)的常规周期,可用插件 config 覆盖。活跃周期取 min(refreshMs, TURN_MS),所以配得比 2000 小才有意义 |
DAY_SCAN_TTL_MS |
60000 | host 端「本日消耗」跨会话扫描的缓存时长(扫描约 150ms,不能每 2 秒做一次) |
失败退避:连续取数失败时重试门槛按 refreshMs × 2^(n-1) 递增,上限 60 秒,成功后清零。
没有它的话"取不到数"会退化成 1 秒一次的永久轮询(未配置 key 的用户挂机一天约 8 万次请求)。
⚠️ 这张表曾长期与源码不符(写着 2000 / 8000 / 60000,实际是 1000 / 2000 / 15000)。 现在
tools/selftest.mjs有一条断言直接比对文档与源码,改常量不改文档会被测出来。
心跳在模块级(beatStart() 在 apply 里调用一次),组件卸载不会终止它。
这是刻意设计:插槽会被宿主反复重挂,轮询若挂在组件 effect 上就会被 cleanup 清掉
(实测曾导致每次启动只请求 1 次)。
node tools/heartbeat-test.mjs
覆盖:心跳只创建一个、节奏参数与源码一致、数据缺失时持续重试、 数据新鲜时不重复取数、组件卸载后心跳仍存活。
两个已知权衡(审查时确认过,不是 bug)
切会话瞬间可能显示上一个会话的数据:心跳只有一个全局
sessionId槽位, 切换会话时新值在 render 阶段写入,但已在途的请求返回的仍是旧会话的结果, 会先渲染一次。下一次心跳(≤TURN_MS)就被新数据覆盖。 刻意不为此加防护 —— 账户余额本来就与会话无关,而"会话累计"在切换瞬间 两个值都是合理的,"先显示旧值再刷新"比"先清空再填"更不刺眼。连点「立即刷新」可能被吞掉一次:
beatFetchNow()有inflight去重, 若手动刷新恰好撞上进行中的请求就直接返回。2 秒后心跳自然会补上。
自测
node tools/selftest.mjs
不需要启动 DSH。 它加载真实的 lib/index.js、用桩 ctx 调 apply()、从注册的
路由里取出 handler 直接喂 HTTP 形状的假请求,然后断言响应内容(22 项)。
覆盖关键路径:新会话(turnBoundary.openTurnStartSeq 为 null,这里曾导致
cost 整块消失)→ 本轮建立 → 同轮累加 → 新轮重置 → 无 sessionId →
会话不存在 → 非回环请求被拒(403) → GET 可用 → 未知 RPC 方法。
金额断言用同一套价目表独立算一遍再比对,不是"看着像对"。
为什么值得维护这个脚本:host 半身的问题过去只能靠"改代码 → 让用户重启 → 肉眼看界面"发现,一轮好几分钟,而且我为此误判过两次 —— 一次把"进程还跑着旧代码"当成逻辑 bug,一次因 mock 里
turnSeq一直有值 而漏掉null分支。这类错误在真机上要用户配合才能暴露。
预览
docs/preview.html 用浏览器直接打开。该文件的 CSS 由生成器从 lib/client.js
求值得到,不是手抄 —— 手抄必然会与实现脱节:
⚠️ 本项目吃过这个亏:预览里曾写着手抄的
margin:0 1px(分隔点间距)、gap:6px、缺少 hover 变亮,与实机全都不符,而 README 却声称"与实机一致"。 现在预览页的样式不可能脱节,因为它是从同一份定义生成的。
预览页包含 5 节:
- 实机排布(上下文圆圈 + 余额胶囊)
- 与原厂胶囊并排 —— 直接比对分隔点大小/亮度/间距、字号
- 点击展开的浮层(底部一行是「↻ 立即刷新」+「当前时段:空闲/高峰」)
- 余额分档与异常状态
- 字号跟随设置 —— 演示 13 / 14 / 15px 下与原厂胶囊同步变化
改动样式后重新生成
node tools/gen-preview.mjs # 写回 docs/preview.html
node tools/gen-preview.mjs <路径> # 或写到指定位置
生成器会自检 5 条关键规则(gap:0、sep 的 margin:0 6px、状态点间距、
hover 变亮、颜色 token 无兜底);任一条不通过就以非零码退出,提示你可能改了
client.js 的 CSS 数组结构。
需要位图时用浏览器截图(不随包分发,因为它无法自动保持最新):
msedge --headless=new --force-device-scale-factor=2 --window-size=780,900 \
--screenshot=preview.png file:///<绝对路径>/docs/preview.html
两条硬约束
字号必须用同一个 calc 表达式,别写死 px:
font-size: calc(var(--dsh-content-font-size-secondary,13px) - 1px); line-height: calc(20px + var(--dsh-content-font-delta-secondary,0px));写死 px(曾用过
font-size:12px)会与另外三个原厂胶囊不一致 —— 用户能一眼看出来。
不要给颜色类 design token 的
var()加兜底值。 原厂一律写var(--token)。加兜底(如var(--dsw-alias-separator-primary, rgba(128,128,128,.5))) 可能让该属性解析出与原厂不同的值 —— 分隔点变暗后会显得更小。 详见下面「实现要点」第 6 条。
许可与致谢
本插件以 MIT 发布(见 LICENSE)。
胶囊与浮层的视觉(字号、字重、行高、内边距、分割线、状态色)参考并对齐 DeepSeek Harness 的官方组件。 该项目同为 MIT 许可,版权归 DeepSeek 及其贡献者所有 —— 此处保留该声明,以符合 MIT 对再分发的要求。若有不周之处,欢迎指出。
No comments yet. Be the first to write one.