dsh-theme-pack
提供四款一键切换的界面主题皮肤,基于官方主题机制构建。 适合想要美化界面且不希望破坏布局或修改DOM的用户。
同名包的其他仓库
安装
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:MoYu-CN/dsh-theme-pack说明文档
阅读完整 README ↗dsh-theme-pack —— 主题皮肤包
English summary — A theme skin pack for DSH (DeepSeek Harness): Celadon and Mist (light), Ocean and Violet (dark), plus a Default card that goes back to the stock look. Install: reference this folder from your DSH profile (
"dsh-theme-pack": "link:/dsh-theme-pack"), then restart DSH for the host half and reload the page for the client half — the picker shows up in Settings → General. Highlights: registers officialctx.themealias tokens only — no layout changes, no DOM surgery, no prompt injection. Language: the settings UI follows the host language setting — Chinese and English dictionaries are both built in. Model-facing prompts stay in Chinese by design. License: MIT.
给 DSH 界面换一套配色:青瓷 / 晨雾(浅色)、深海 / 紫夜(深色),外加一项「默认」切回官方外观。
它做的事很小、很干净:只用官方 ctx.theme.register() 注册 alias token 的颜色值,不改布局、不动 DOM 结构、不注入任何提示词。
1. 它是什么
一个 DSH(DeepSeek Harness)插件,分两半:
| 半 | 文件 | 干什么 |
|---|---|---|
| Host | lib/index.js | 把客户端 bundle 经 webServer 路由发给浏览器;皮肤选择读写 /skin.json,经 GET/POST /__themepack/skin 暴露 |
| Client | lib/client.js | 注册 4 套主题(ctx.theme.register),并在「设置 → 通用」里加一行皮肤选择器 |
- 客户端路由:
/__themepack/client.js - 皮肤路由:
/__themepack/skin(GET 读 / POST 写,只接受同源请求) - 数据文件:
/skin.json,内容形如{"skin":"celadon"} - 数据目录解析顺序:
DSH_THEME_PACK_DATA→ 由process.argv[1]/process.execPath反推安装根 →DSH_INSTALL_DIR→ 兜底插件源码目录往上两级
2. 怎么装
插件的安装由 DSH 的 bundle / 插件管理器负责,本仓库只提供可被安装的插件目录。通用做法(在 DSH 运行的那个 profile 里):
- 把本目录放到一个稳定位置(例:
/dsh-theme-pack)。 - 在 profile 的配置里以本地路径引用它,例如
"dsh-theme-pack": "link:/dsh-theme-pack"。- 用
link:而不是file::不拷贝、直接软链,改代码立刻生效。
- 用
- Host 半需要重启 DSH 才换模块代际;客户端半需要刷新页面。
- 装好后进「设置 → 通用」,应能看到「Theme skin / 主题皮肤」那一行,点卡片即可切换。
三个安装期的坑(都实测踩过)
- 别声明
peerDependencies。 桌面端启动时会把link:插件搬进profiles/.generations/live/,迁移中做 peer 校验;宿主的@deepseek-ai/cordis在 staging closure 里解析不到 → 整批迁移失败、profile maintenance被冻结。cordis 是宿主提供的运行时,插件不该声明它。 - 别
import @deepseek-ai/schemastery。link:形态下插件从源码目录解析依赖,那里没有node_modules→ERR_MODULE_NOT_FOUND会拖垮整批启动。所以本插件的持久化走自建文件 + webServer 路由,不挂 cordis 设置系统。 - 别硬编码安装路径。 换台机器、换个安装位置就会写到不存在的盘上。数据目录一律从运行环境反推(见上)。
3. 有哪些主题
| id | 名字 | 配色 |
|---|---|---|
official | 默认 / Default | 切回官方外观(theme.setTheme("system")) |
celadon | 青瓷 / Celadon | 浅色,主色 #0f766e,底色 #f6f8f7 |
mist | 晨雾 / Mist | 浅色,主色 #b45309,底色 #f8f5f0 |
ocean | 深海 / Ocean | 深色,主色 #6ea8fe,底色 #0d1522 |
violet | 紫夜 / Violet | 深色,主色 #b79cff,底色 #16121e |
每套主题覆盖 72 个 token:--dsw-alias-*(背景 / 文字 / 按钮 / 交互态 / Markdown)、--dsw-specific-*(侧栏 / 气泡 / 输入框)、--dsw-linear-gradient-* 与 --shiki-token-*(代码高亮)。
中英文案随界面语言切换(settings.themePack 命名空间,zh / en 内联在 lib/client.js)。
顶部「默认」卡片与官方 AppearanceRow 联动:在官方那一行切走时,这里的选中态同步跳回「默认」;但持久化的皮肤不丢,重启仍会恢复。
4. 已知边界
- 只覆盖颜色 token,不改布局。 主题里没有任何尺寸 / 结构类改动,所以「布局长得不对」不是本插件的问题。
- 依赖宿主的 token 名。 主题注册的是 alias token;宿主换版本如果改了 token 名,未被覆盖的那些会退回官方值(不会报错,只是看着不一致)。
- 风格是「注册一套主题」,不是「覆盖官方那套」。 所以官方外观那一行切 light/dark 与本插件互不干扰,两边各记各的。
- 皮肤是每台机器 / 每份数据目录一份(
skin.json),不跟随 profile 同步。 - 路由无内置鉴权,靠同源校验(
Sec-Fetch-Site/Origin/Referer与Host对齐);因此 DSH Web 必须绑回环地址。 - 本仓库自带测试,见 §5「怎么跑测试」。 覆盖:4 套主题都能注册、注册的 token 只有颜色类且名字都在宿主注册表内、中英字典键集合一致、启用/禁用时路由与 tapIndex 的注册与注销、
skin.json读写的降级路径,外加一组「把已知有病的坏数据喂给同一批校验函数」的对照。
5. 怎么跑测试
测试在 test/ 下,从干净检出就能跑:插件位置由 import.meta.url 反推(test/_harness.mjs),没有写死的安装路径;数据目录只走 DSH_THEME_PACK_DATA —— 没设就自建临时目录并在进程退出时删掉。
node test/theme-pack.test.mjs # 主验收(下面说明为什么通常要配 DSH_APP_DIR)
node test/theme-skin-concurrent.test.mjs # 并发写皮肤:临时名不撞车、`.tmp` 不残留(只要 node,不用 DSH_APP_DIR)
node test/parse-check.mjs # 真解析校验(含 test/_broken-control.js 这个必须 FAIL 的对照组)
为什么这一份常常需要 Electron-as-node
theme-pack.test.mjs 有几节刻意用部署里真的东西,而不是自己抄一份替身:
| 节 | 借的是什么 | 没配 DSH_APP_DIR 时会怎样 |
|---|---|---|
| ②元素树 / ⑤偏好层 | 安装目录里的真 react 与 react/jsx-runtime | 整份脚本 SKIP 并退出 0 |
| ④ token 审计 | 部署自带 @deepseek-ai/dsh-client-* 的 lib/client.js(抽 --* 声明) | 同上 |
所以要么给它安装目录:
$env:DSH_APP_DIR = "\resources\app" # 例:\resources\app
node test/theme-pack.test.mjs
要么直接用 Electron-as-node 跑(这时 process.execPath 就是 DSH Desktop.exe,脚本自己能反推出安装根):
$env:ELECTRON_RUN_AS_NODE = "1"
& "\DSH Desktop.exe" test/theme-pack.test.mjs
⚠ 用 & 直接调 .exe 时 PowerShell 不会等它,退出码是空的 —— 那种「没输出」不是证据。要拿退出码请用 Start-Process -Wait -PassThru。
拿不到安装根时脚本会打印 SKIP … 说明并退出 0 —— 是「跳过」,不是「通过」。 别把退出码 0 当成验收通过。
环境变量
| 变量 | 要不要设 | 说明 |
|---|---|---|
DSH_APP_DIR | 见上 | DSH 安装目录里的 resources/app。不设时依次试 DSH_INSTALL_DIR → 由 process.execPath 反推 → 常见默认安装位置。 |
DSH_THEME_PACK_DATA | 可选 | 插件的 skin.json 所在目录。不设就自建临时目录并自动清理。 |
DSH_TEST_OUT_DIR | 可选 | 机器可读产物 theme-findings.json 写到哪。不设就写系统临时目录 —— 测试绝不往仓库里写文件。 |
DSH_THEME_PACK_SRC | 可选 | 把「被测插件」整个换成另一份副本。这是对照组开关:指向一份故意改坏的副本时,这份验收必须变红。 |
对照组:证明判据会失败
主脚本第 ⑥ 节会把已知有病的坏数据喂给第 ②③④ 节用的同一批校验函数,必须全部报病(实测 8 条对照全中):
| 喂进去的坏数据 | 期望报出的病 |
|---|---|
--dsh-sidebar-inline-padding: 12px(官方的布局类 token) | 不是颜色类 token |
--dsw-alias-bad name!: #fff | 变量名不符合 CSS 自定义属性语法 |
--dsw-alias-bg-base:(空白值) | 值为空 |
--dsw-alias-bg-base-secondary(部署里不存在的名字) | 部署里没有这个名字(会静默失效) |
缺一条 en 的字典 | 中英键集合不一致 |
重名注册 / 未注册 id 的 setTheme | 假 ctx 会像宿主一样拒绝 |
另外 test/_broken-control.js 是语法上就写坏的对照文件,parse-check.mjs 对它必须报 RESULT … :: FAIL;若它也 OK,说明那份解析检查是空转的假证据。
还有一条端到端的对照:拿一份改了 className 的插件副本跑同一份验收,必须变红。做法(副本只能建在插件目录之外):
# 1) 在插件目录之外复制一份完整插件(lib/client.js 与 lib/index.js 都要,index.js 会被 import)
# 2) 在副本里把 client.js 的 `className: "tp-group",` 改成 `className: "tp-group-BROKEN",`
$env:DSH_THEME_PACK_SRC = ""
node test/theme-pack.test.mjs
实测:主组 EXITCODE=0 / PASS=88 / FAIL=0;对照组 EXITCODE=1 / PASS=87 / FAIL=1,
红的那条正是 FAIL 第一层是一组 .tp-group :: tp-group-BROKEN。
并发写皮肤那一份(theme-skin-concurrent.test.mjs)自带对照组:test/_before-concurrent-tmp/
是共写同一个 ${file}.tmp 的改动前快照。指向它时那份测试必须变红:
$env:DSH_THEME_PACK_SRC = "$PWD/test/_before-concurrent-tmp"
node test/theme-skin-concurrent.test.mjs # 必须 FAIL
实测:主组(真源码)EXITCODE=0 / PASS=4 / FAIL=0;对照组 EXITCODE=1 / PASS=2 / FAIL=2
(红的两条是「每次 POST 都回 200」与「落盘是某一次的完整内容」—— 共写 tmp 会把文件写成半截)。
⚠ 只把临时名改成唯一名不够:Windows 上两个 rename 同时覆盖同一个 skin.json
会有一个 EPERM 失败、失败那次的 .tmp 还留在数据目录里(实测 30 轮 × 4 并发里 37 次抛错)。
所以 writeSkin 同时做了两件事:唯一临时名 + 串行队列。
6. 兼容性
- DSH Desktop 2.0.17(官方包
@deepseek-ai/*0.2.0-rc.2)实测可用。 - 需要宿主提供
theme/slots/locale三个服务,以及官方@deepseek-ai/dsh-client-locale、@deepseek-ai/dsh-client-ui-slots、@deepseek-ai/dsh-client-ui-theme(见package.json的dsh.client.inject)。 - 运行时为 Electron 自带的 Node(实测 v24.18.1);
engines.node标为^22.19.0 || >=24.0.0。
7. 目录结构
dsh-theme-pack/
├── lib/
│ ├── index.js # host 半:路由 + skin.json 读写
│ └── client.js # 客户端 bundle:4 套主题 + 设置行
├── test/ # 见 §5「怎么跑测试」
│ ├── _harness.mjs # 插件路径 / 数据目录 / 官方包定位(各测试共用)
│ ├── theme-pack.test.mjs # 主验收(①②③④⑤⑥ 六节)
│ ├── theme-skin-concurrent.test.mjs # 并发写皮肤(`.tmp` 不撞车 / 不残留)
│ ├── _before-concurrent-tmp/ # 上面那份的改动前快照(对照组:必须变红)
│ ├── parse-check.mjs # 真解析校验
│ └── _broken-control.js # 故意写坏的对照组(必须 FAIL)
├── icon.svg
├── cordis.patch.yml # bundle 注入声明
├── package.json
├── LICENSE # MIT
└── README.md
8. 贡献
- 提 PR 前请说明「改了什么、为什么这么改、怎么验证的」;只改颜色值的 PR 最好附上前后对比截图。
- 改主题 token 时注意
lib/client.js里THEMES与lib/index.js里SKIN_IDS是两处对齐的清单,新增皮肤必须同时改。 - 不要引入
node_modules依赖,不要声明peerDependencies(理由见上)。
9. License
MIT —— Copyright (c) 2026 MoYu-CN