# dsh web 端 & 插件市场 调研报告 > 调研对象:本机 `DeepSeek Harness 0.2.0-rc.2` + `dshmarket 1.66.45` > 调研时间:2026-10-21 > 用途:为「dsh web UI 重构插件」定技术底座,作为后续写代码的唯一事实来源 > 证据原则:本文每条结论都标了可复核的路径/源码位置,未经验证的推断会明确写成「推断」 --- ## 0. 一句话结论 dsh 的 web 界面不是一个大前端,而是**一个插件组合器的产物**:70+ 个官方 UI 包按 slot 拼装,主题只靠一层 107 个 CSS 变量的语义色阶。所以「重构 UI」**不需要 fork**——写一个带 `dsh.bundle` + `dsh.client` 双半包声明的插件,用 `ctx.theme` 改色阶、用 `ctx.slots` 换组件、再用自有 CSS 覆盖布局,就能达到绝大多数视觉重构目标;上架只需要往 `awesome-dsh-plugin` 提交**一个 YAML 文件**。 --- ## 1. 本机基线(已实测) | 项目 | 值 | 来源 | | --- | --- | --- | | dsh 版本 | `0.2.0-rc.2` | `~/.local/bin/dsh --version` | | 可执行 | `~/.local/bin/dsh` → `~/.local/lib/node_modules/@deepseek-ai/dsh/lib/bin.js` | 软链 | | 包本体 | `~/.local/lib/node_modules/@deepseek-ai/dsh`(约 495 MB,288 个 `@deepseek-ai/*` 子包) | `du -sh` | | 数据目录 | `~/.dsh`(**与 WorkBuddy AI 共用**,`.workbuddy-*` 属 WorkBuddy,别碰) | 目录内容 | | 当前 profile | `~/.dsh/profiles/web`(`patchReload: live`) | `package.json` | | 插件市场 | `dshmarket` **1.66.45**(设置页标题显示 1.66.42,是页面渲染时的旧值) | 包 `package.json` | | 市场数据目录 | `~/.dsh/dsh-market/`(只有 `printed-build-keys.json`,**不存目录快照**) | 目录内容 | | Node / pnpm | nvm v24.18.0 / pnpm 11.5.1(`dsh plugin` 直接转发 pnpm) | `node -v`、`pnpm -v` | | 运行状态 | `127.0.0.1:3080` 有 node 监听 | `lsof` | **已装的第三方插件(profile bundle 栈)**:`dshmarket`、`@liustack/modlens`、`@liustack/modsearch`、`dsh-workbuddy-connect`、`dsh-whale-widget`,以及本地 `link:` 的 `dsh-prompt-injector`(`~/SillyTaven/制卡/dsh-prompt-injector`)。 --- ## 2. 架构:web 界面是怎么拼出来的 ### 2.1 运行时链路 ``` 浏览器 (127.0.0.1:3080) └─ index.html → assets/index-*.js(前端壳,minified 633 KB) └─ window.__ModuleLoader__(模块系统引导器) └─ 逐个 fetch /plugins//client.js?rev=…(每个插件的前端半包,未压缩) └─ factory(require) → 注册进 cordis 客户端容器 → 填 slot → 渲染 └─ 宿主进程(node)= cordis 容器:模型、工具、会话、webserver、插件……全部是插件 ``` ### 2.2 三层结构 | 层 | 位置 | 说明 | | --- | --- | --- | | 前端壳 | `@deepseek-ai/dsh-web-frontend/dist/` | `index.html` + `index-*.js` + `index-*.css`(76 KB) + `vendor-*.js`(740 KB),已 minify,无源码 | | UI 包(约 70 个) | `@deepseek-ai/dsh-client-ui-*` | 每个含 `lib/client.js`(**未压缩**,`window.__ModuleLoader__.load({id,factory})` 格式)+ `lib/types/*.d.ts` + `.module.css`,无 `.ts/.tsx` 源码、无 sourcemap | | 组合层(可编辑) | `~/.dsh/profiles/web/` | `cordis.yml` 是空 `[]`;**`cordis.patch.yml` 才是编辑层**,`patchReload: live` | UI 包的粒度细得可以单点改造,现成可用的有:`ui-layout`、`ui-sidebar`、`ui-chat`、`ui-conversation`、`ui-settings`(+8 个 settings 子页)、`ui-primitives`、`ui-theme`、`ui-renderer`、`ui-tool`、`ui-trajectory`、`ui-composer` 相关(`ui-commands`/`ui-input-trigger`)等。 ### 2.3 双半包模型(这是全篇最关键的一条) 一个插件 = **宿主半 + 前端半**,通过 `package.json` 的 `dsh` 字段声明: ```jsonc { "exports": { ".": "./lib/index.js", // 宿主半(cordis 插件,导出 apply(ctx)) "./client": "./lib/client.js" // 前端半(必须是 loader 期望的 factory 格式) }, "dsh": { "bundle": { "patch": "./cordis.patch.yml" }, // ← 让 dsh plugin add 认得它 "client": { "platform": "web", "inject": ["slots", "theme"] } } } ``` `cordis.patch.yml` 负责把宿主半挂进容器: ```yaml - insert: - id: your-plugin-id name: your-package-name ``` **扫描机制(已读源码)**:`@deepseek-ai/dsh-client-modules/lib/index.js` - L212 `resolveMeta()`:只处理 **loader 已挂载的行**,读该包 `package.json` 的 `dsh.client`; - L714:`decl.platform !== "web"` → 直接忽略(不报错); - L719:声明了 `dsh.client` 但 `exports` 里没有 `"./client"` → **抛错**(`declares dsh.client but exports no "./client" bundle`); - L65–73 `parseDshClient()`:`platform` 必须是 string;`inject`/`external` 必须是 string[];`immediately` 必须是 boolean,否则抛错。 - 前端半通过 `/plugins//client.js` 从**磁盘实读**,带 `rev` 缓存戳(`lib/client.js` L200 附近),dev 下还能取 `client.js.map`。 **推论(已由下游行为印证)**:插件必须有宿主半被挂载,前端半才会被收集。因此「纯前端插件」也需要一个 `lib/index.js`(可以是极简 `apply(ctx){}`)。 ### 2.4 Slot 组合 UI 包之间不互相 import,只往命名插槽里塞组件。已确认存在的插槽(`.d.ts` 里能查到): - 侧栏:`sidebar.brand.mark`、`sidebar.brand.name`、`sidebar.panellist`、`sidebar.settings`、`sidebar.workspaces`、`sidebar.toggle.badge` - 外壳:`shell.leading`(macOS/Windows 窗口 chrome 区)、`main`(keyed) - 设置:`settings.trigger`、`settings.launcher`、`settings.section`、`settings.general.item`(**外观行就挂在这**)、`settings.plugins.tab`、`settings.action`、`settings.close`、`settings.onboarding`、`settings.header` 容器访问 `ctx.slots` 前必须把 `'slots'` 写进 `dsh.client.inject`,否则运行时抛错(`dsh-prompt-injector` 源码注释明确写了这一点:缺 inject 会让整个 entry 被 boot 审计判为 failed)。 --- ## 3. 插件契约(硬性要求,别踩) ### 3.1 必需字段 | 字段 | 必需性 | 说明 | | --- | --- | --- | | `dsh.bundle.patch` | **必需** | 指向 `cordis.patch.yml`。**只声明 `dsh.client` 的包装不上**——这是官方收录规范里「最常见的被拒原因」 | | `dsh.client.platform` | 有前端时必需 | 固定 `"web"` | | `dsh.client.inject` | 有前端时必需 | 要用 `slots`/`theme`/`locale`/`configForms` 就得逐个声明 | | `exports["./client"]` | 有前端时必需 | 否则 loader 抛错 | | `engines.dsh` | 强烈建议 | 市场用它判断宿主兼容 | | `icon` | 建议 | 市场卡片图标(如 `"icon": "icon.png"`) | | `type: "module"` | 建议 | 现有主题/UI 插件全部是 ESM | ### 3.2 前端半的产物格式 必须是 `window.__ModuleLoader__.load({ id, factory })` 包裹的 lazy-CJS factory,`factory(require)` 内 `require('react')`、`require('react/jsx-runtime')`、`require('@deepseek-ai/dsh-client-ui-primitives')` 等平台模块。两种构建路线都已被市场验证: 1. **不引入打包器**(`dsh-prompt-injector` 路线):源码写纯 JS + `React.createElement`(刻意不用 JSX),自己写 40 行的 `build-client.mjs` 手动拼 factory。 2. **tsdown/TS + TSX**(`dshmarket`、`dsh-catppuccin` 路线):正常写 `.tsx`,构建产出 factory 格式,并附 `.map`。 --- ## 4. 主题系统(我们能利用的最强杠杆) ### 4.1 权威 API(`@deepseek-ai/dsh-client-ui-theme@0.2.0-rc.2` 的 `lib/types/client/index.d.ts`) ```ts class ThemeRuntime { getTheme(): ThemeSnapshot // 当前快照(含 active/themes/revision) setTheme(id: string): void // 切换主题,未注册 id 抛错 setFontSize(px: number): void // 10–22 整数 register(definition: ThemeDefinition): () => void // 注册主题,重复 id 抛错 overrideTokens(source: string, tokens: ThemeTokenOverrides): () => void // 叠加覆盖层 exportInspectTokens(): ThemeTokenInspection[] // 导出 token 目录(含描述) } // ThemeDefinition = { id, colorScheme: 'light'|'dark', tokens: Record } // ThemeTokenOverrides = Record ← 两模式都必须给 // 事件:'theme/change'(snapshot) ``` `inject` 至少要含 `'theme'`,通常还要 `'slots'`(挂设置行)和 `'locale'`。 ### 4.2 token 两层色阶 ``` --dsw-static-* 原始色板(neutral-bluish-00…950、各色相、a08/a12 透明变体) ↓ 官方 base.css 映射 --dsw-alias-* 语义层 = 107 个 ← 主题插件唯一应该覆盖的层 ↓ 各 UI 包的 .module.css / 打包 CSS ``` 107 个 token 的完整清单见 [`theme-tokens.md`](./reference/theme-tokens.md)。分布:`bg-*` 15、`button-*` 15、`label-*` 13、`state-*` 12、`markdown-*` 8、`border-*` 7、`file-*` 6、`interactive-*` 5、`brand-*` 4、`scrollbar-*` 4、`onboarding-*` 4,以及 code/menu/link/switch/toast/tooltip/turn/settings 若干。 ### 4.3 `register` vs `overrideTokens`(本项目必须先做的选型) | | `register` | `overrideTokens` | | --- | --- | --- | | 形态 | 注册成一个**可选主题**,出现在外观设置里 | 叠加层,**不改注册表** | | 互斥 | 主题互斥(选中即替换当前主题) | 与任意主题共存,可叠加多层,后注册的赢 | | 用户感知 | 「换主题」 | 「加特效/调色」 | | 卸载 | 自动恢复默认 | 层被移除,露出下面那层 | | 适合 | 完整配色方案(Catppuccin、Bloom 那类) | 视觉增强(毛玻璃、壁纸、排版/密度调整) | **重要**:官方外观设置只持久化 `light/dark/system` + 字号,**没有 custom CSS 入口**。所以任何超出基础配色的东西都必须由插件自己持久化(现有插件普遍是 localStorage + `ctx.configForms` 双层)。 ### 4.4 设置行怎么挂 `settings.general.item`(外观行占的就是这个位置)。参考实现:`dsh-catppuccin` 的 `src/client/settings-section.tsx` + `CatppuccinRow.tsx`。 持久化服务在 **0.2.0-rc.2 上是 `ctx.configForms`**(`dsh-client-ui-settings/lib/types/client/config-form.d.ts` L96)。注意历史坑:`0.1.6` 及以前叫 `ctx.settingsScope`,`0.1.7-alpha.1` 起换成 `configForms`;如果把任一名字写进硬 `inject`,会在另一半版本上永久 pending——`dsh-catppuccin` 的做法是「包一层 facade,两个都用可选注入探测」。 --- ## 5. 插件市场(dshmarket 1.66.45)机制 ### 5.1 数据源 - 每次打开实时请求 `https://awesome-dsh-plugin.com/plugins.json`(**刻意不做本地快照兜底**,理由是过期目录比错误更糟)。 - 中国大陆网络优先从 npm 读同一份目录(`dsh-plugin-catalog`),并可用 `DSHM_REGISTRY_URL` 整体改指镜像。 - 实测该 JSON:**6,254 个插件**,`updated: 2026-10-20`,与截图里「240 款主题」完全对上(`category: theme` 恰为 240)。 单条 schema(实测字段): ```jsonc { "name": "dsh-deep-whale#maid-atelier", // 展示名,owner/repo 或 owner/repo#sub "owner": "…", "url": "https://github.com/…", "page": "https://awesome-dsh-plugin.com/p/…", "category": "theme", // agi ui usage theme model identity session memory tools wsl browser vision voice docs skill workflow git notify dev security remote market fun "description": { "en": "…", "zh": "…" }, // 只有 en 必需 "npm": "@smalltailqwq/dsh-client-ui-skin-maid-atelier", // 可能和仓库名不同! "version": "0.1.9", "stars": 2740, "downloads": 42382, "capabilities": ["fs-read"], "capabilityRedLines": ["reads credentials/secrets AND network access"], "install": "dsh plugin --profile web add …", "added": "…", "screenshots": [ … ] } ``` ### 5.2 安装、安全边界 - 安装一律走 pnpm(`dsh plugin` 就是 pnpm 的转发封装),写进 profile 的 `package.json` + `node_modules`。 - **下载优先级**:经验证的 npm 包 → 作者提供的 GitHub Release 预构建 tarball → 整仓源码下载。→ **我们应该发 npm 包,且不要把构建脚本放在 install 阶段**(pnpm ≥10 默认禁止构建脚本,需用户逐包放行)。 - **只允许装 awesome 精选列表内的来源**,其它一律拒绝。 - 终端/命令行类插件装进网页版会额外提醒;安装接口只接受同源 POST。 - 收录 ≠ 背书(官方原文)。 ### 5.3 「兼容模式」到底是什么(用户关心的点,已查证) 界面上那个东西在 **筛选 → 宿主版本** 里,两个选项: - `显示全部` - `兼容当前 DSH {版本}` —— 选中后提示「已隐藏 N 个与 DSH x.y.z 不兼容的插件;未声明版本的仍在列表里」 判定依据(`src/discovery-compatibility.ts` 文件头注释 + 实现): 1. 读 npm manifest 的 **`engines.dsh`**,和/或 peerDependencies 里同版本线的 **`@deepseek-ai/dsh-*`**; 2. 结论三态:`compatible` / `incompatible` / `unknown`; 3. `unknown`(未声明、格式异常、暂时取不到、仅 GitHub 发布)**永远保持可见**——「不猜成不兼容」; 4. manifest 事实缓存 **24 小时**,结论不缓存(因为结论取决于当前宿主版本); 5. 打开市场时不会拉全部 manifest(否则一次变成上千个请求),是按需有界索引。 **注意(易踩坑)**:`dsh-catppuccin` 在 `package.json` 里写了一大段 `dsh.compatibility.{dsh, dshReleases, dshOperations, profiles}`,但**市场源码里没有任何地方读这些字段**(`grep dshReleases|dshOperations` 在 `dshmarket/src` 下零命中)。市场只认 `engines.dsh` 和 peer。→ 我们要面向市场,就声明 `engines.dsh`;`dsh.compatibility` 可写但不指望它生效。 另一个相关能力:**按宿主发现**的版本降级建议——若最新版要求更高宿主,界面会提示「正在查找还兼容当前 DSH 的版本…」,找到则显示「可以装 {version}:它声明兼容当前 DSH」。所以 `engines.dsh` 别写得过窄。 ### 5.4 市场的其它能力(对产品有影响) | 能力 | 对我们的意义 | | --- | --- | | **主题 tab** | 独立主题页,装完即生效、点一下切换、主题互斥、选择跨重启保留、卸载即恢复 → 注册成「主题」比做「开关式特效」用户路径更短 | | **分组** | 一个分组最多容纳**一个主题** | | **热禁用/启用** | 往 profile 的 `cordis.patch.yml` 写 `- id: … disabled: true/false`,HMR 约 1 秒重组 → 我们的插件必须能被单独禁用而不炸 boot | | **按需重启** | 免重启是卖点:前端半改动刷新即可,宿主半改动才需要重启 | | **启动全有全无** | 一个插件 `apply()` 抛错 → 整个 boot 中止;桌面端 crash rescue 会禁用**所有**第三方插件 → 注册类调用必须容错(见 §7) | | **更新通道** | 比 npm 版本或 lock commit,逐插件检测 | | **导出日志 / 诊断** | 反馈时附日志 | ### 5.5 上架流程(GitHub → 市场) 1. 仓库要有真实代码、**创建满 1 天**、加 `dsh-plugin` topic、声明 `dsh.bundle`。 2. 往 `awesome-dsh-plugin/awesome-dsh-plugin` 提 PR,**只加一个文件**:`data/plugins/__.yml` ```yaml url: https://github.com/owner/repo name: owner/repo category: theme # 想做主题 tab 就用 theme description: en: One-line description ending with a period. zh: 一句话描述,以句号结尾。 ``` 3. README 由脚本从 `data/plugins/*.yml` 生成,**不要手改**。 4. CI 校验形式,维护者读仓库确认描述属实(「描述会被当声明核对」——夸大是主要打回原因)。 5. 合并后站点与市场自动收录,通常 1 天内生效。monorepo 子包:`url` 指到子目录,`name` 用 `owner/repo#subname`。 --- ## 6. 版本与兼容性策略 | 事实 | 影响 | | --- | --- | | 市场要求 dsh web ≥ `0.1.0-rc.6`;本机 `0.2.0-rc.2` ✓ | 可以正常用 | | `0.2.0` 及以前的设置入口在**侧边栏**;`0.2.1` 起移到**账号菜单** | 不要把入口硬编码成「有设置按钮的布局」,别依赖 `sidebar.settings` 一定存在 | | `0.1.7-alpha.1`:`settingsScope` → `configForms` | 持久化要双层探测(可选注入) | | 0.1.7+ 宿主改为从插件自己的 Config schema 派生设置,不再为插件注册的 settings 命名空间提供服务 | 参考市场 README 的说明:设置项要写进 profile patch 的 `config:` 下(**写在顶层会静默失效**) | | `overrideTokens` 要求 light/dark 两套值都必填 | 我们的配色必须双模式成对设计,否则模式切换后不可读 | **建议声明**:`engines: { "dsh": ">=0.2.0-rc.1" }`(我们只用 0.2.x 已确认存在的 API),必要时用 `peerDependencies` 再钉一次。 --- ## 7. 参考实现解剖:`@nonamelego/dsh-catppuccin@0.6.4` 为什么要看它:它是**唯一把「配色 + 毛玻璃特效 + 设置行 + 持久化 + 更新检查」全做齐**的开源主题,且发布包里带 sourcemap,能还原出完整 TypeScript 源码。 ### 7.1 包结构 ``` package.json # engines.dsh、dsh.bundle、dsh.client.inject、files、icon cordis.patch.yml # 两行 insert:主插件 + 一个 TUI 主题同步插件(无 inject → 在所有 profile 激活) themes/*.json # 4 套主题定义:{ name, displayName, base, colors{…} } lib/index.js # 宿主半:settings schema、状态迁移、TUI 同步 lib/client.js # 前端半(244 KB) lib/client.js.map # ← sourcesContent 完整,18 个原始 .ts/.tsx 全在里面 icon.png ``` 还原出的 15 个源码文件(已解到 `/tmp/theme-inspect/src/`,可复用为脚手架参考): ``` client/index.ts client/palettes.ts client/locales.ts client/state-sync.ts client/settings-section.tsx client/CatppuccinRow.tsx client/glass/glass-layer.ts client/glass/glass-row.tsx client/glass/glass-css.gen.ts client/UpdateRow.tsx client/detail-card.tsx client/LinksRow.tsx state.ts update-check.ts registration-tolerance.ts ``` ### 7.2 关键代码形态 ```ts export const inject = ['slots', 'locale', 'theme'] // 注意:持久化服务不在硬 inject 里 export function apply(ctx: ClientContext): void { ctx.effect(() => { /* 注册 locale,返回合并 disposer */ }, 'ns: dictionaries') const theme = ctx.get('theme') as ThemeRuntime const disposer = theme.register({ id: 'catppuccin-mocha', colorScheme: 'dark', tokens: { ...flavor.tokens, ...shikiTokens, ...hostShapePatch, ...userOverrides }, }) // 选中:theme.setTheme('catppuccin-mocha') } ``` 几个值得抄的工程手法: 1. **`tolerantRegistration()`**:所有 `register` 调用都包一层,命中「already registered / has locale / duplicate route / has an entry」就静默跳过。原因写在源码注释里:**`apply()` 抛错会让整个 web boot 中止(`web boot: N entries did not activate`),桌面端随后 `disableAllPlugins()` 把全部第三方插件一起带走**。一个语言包或重复安装就能拖垮用户整套插件——所以宁可跳过自己那条注册。 2. **懒注册**:只注册当前在用的那一套主题,首次选中再注册其余,fiber disposer 统一释放。 3. **持久化三层**:localStorage(首屏即时恢复、跨标签页 `storage` 事件、传输不可用时的兜底)+ `configForms`(机器级真源)+ 设置行 UI;启动先快速应用 localStorage,再等 scope 快照 hydrate 回来覆盖。 4. **宿主差异探测**:`hostHasMenuBlur()` 之类按 0.1.7 / 0.1.5 线打补丁,而不是假设单一宿主形态。 --- ## 8. 竞品格局(来自实时目录) 主题类共 **240** 个,UI 增强类 **1,156** 个。头部按 star: | 插件 | star | 下载/月 | 定位 | | --- | --- | --- | --- | | `dsh-deep-whale#maid-atelier` | 2740 | 42.4k | 鲸鱼娘 IP 皮肤系列 | | `dsh-wallpaper-engine` | 1721 | 57.6k | 把 Wallpaper Engine 视频/Web/Scene 壁纸播到界面后面 | | `open-sea-skin` | 401 | 6.1k | 实时 WebGPU 海洋 + 毛玻璃 | | `dsh-liang-skin` | 273 | 2.4k | 推理档位滑块 ↔ 视觉强度联动 | | `dsh-dream-skin` | 258 | 32.3k | 8 套主题 + 壁纸 + 主题包导入导出 | | `dsh-claude-style` | 250 | 18.0k | 复刻 Claude Code Desktop(CSS + DOM override) | | `dsh-theme-endfield` | 150 | 1.0k | 明日方舟终末地工业风 | | `dsh-bloom-theme` | 95 | 5.4k | 10 套莫兰迪,OKLCH,WCAG AA 实测 | | `beautiCode` | 91 | 13.4k | 本地图片/MP4 背景 + `/bg` 命令 | | `dsh-opencode-palette` | 71 | 8.6k | opencode 38 套主题搬迁 + 色板编辑器 | **四类做法几乎覆盖了全部**:①壁纸/毛玻璃特效;②配色方案包;③复刻别家产品外观;④角色 IP 皮肤。 **空白点(推断,需你验证是否想要)**: - 绝大多数停在「颜色 + 背景图」,**极少动信息密度与排版**(行高、块间距、消息气泡形态、侧栏分区结构)。唯一往布局走的是 `dsh-chatgpt-style`(居中 768px 单栏 + 右对齐气泡)。 - 基本没有面向「长时间读代码/读长对话」的可读性优化(代码块、工具调用卡片、轨迹视图的视觉降噪)。 - 也没有做「双模式成对设计 + 对比度自检」作为卖点的(只有 bloom 提了 WCAG)。 --- ## 9. 技术方案建议(待风格确定后细化) ### 9.1 目录草案 ``` dsh-ui-refactor/ ├─ package.json # dsh.bundle + dsh.client + engines.dsh ├─ cordis.patch.yml # insert 自己 ├─ src/ # 宿主半(TS) ├─ client/ # 前端半(TS + TSX) ├─ styles/ # 自有 CSS(token 优先,硬编码兜底) ├─ docs/ # 本文档 └─ scripts/build.mjs # 产出 loader factory 格式 ``` ### 9.2 三个待定选型 | 决策 | 选项 A | 选项 B | 建议 | | --- | --- | --- | --- | | 色彩接入 | `theme.register()` 注册成套主题 | `theme.overrideTokens()` 叠加层 | 纯配色走 A;「增强/特效/排版」走 B。若两者都要:A 出主题包,B 出增强层 | | 构建 | 手写 40 行打包器(无依赖,纯 createElement) | tsdown + TSX(正常写法,产物带 map) | 有设计稿和大量组件时选 B;想零依赖、极简选 A | | 宿主半 | 极简 `apply(){}` + config schema | 完整设置/持久化/更新检查 | 先在极简上跑通链路,再加设置 | ### 9.3 风险清单 | 风险 | 缓解 | | --- | --- | | `apply()` 抛错 → 整个 boot 中止、桌面端禁用所有第三方插件 | 所有 register 走容错包装;前端半的初始化也包 try/catch | | 升级 dsh 后 token 名或 API 变动 | 只依赖 `--dsw-alias-*` 与 `ctx.theme`,不碰内部实现;`engines.dsh` 收窄 | | `overrideTokens` 两模式必填 | 双模式成对定义,禁止单值 | | 与已装主题插件冲突(重复 slot id / theme id / locale id) | 用自己的命名空间前缀;id 一律加前缀 | | pnpm ≥10 装包不执行构建脚本 | 发 npm 包时**预构建**好 `client/client.js`,别依赖 install 阶段构建 | | 设置入口在 0.2.0 / 0.2.1 位置不同 | 不硬编码入口;用 `settings.general.item` 挂行,或直接在自己面板里给开关 | | `~/.dsh` 与 WorkBuddy 共用 | 只读写 `profiles/web/{package.json,cordis.patch.yml}`,其它目录不动;改动前备份 | --- ## 10. 待你确认(下一轮前需要) 1. **视觉方向**:具体风格(配色倾向、参考产品、要不要壁纸/毛玻璃、明暗双模式怎么处理)。 2. **改造深度**:只换皮(token + CSS)/动布局(侧栏结构、会话区、输入区重排)/加功能面板。 3. **是否接受「注册成主题」**:如果要做完整配色且希望出现在市场「主题」tab 一键切换,就得走 `register`;如果只是增强,走 `overrideTokens`。 4. **目标宿主范围**:只保 `0.2.0-rc.2`(本机),还是要兼容 0.2.1+(设置入口已搬家)。 5. **项目命名与 npm scope**:上架需要 npm 包名(可与仓库名不同,例如 `dsh-plugin-*` 或 `@scope/dsh-*`)。 --- ## 附录 A:关键路径速查 ``` dsh 可执行 ~/.local/bin/dsh dsh 包本体 ~/.local/lib/node_modules/@deepseek-ai/dsh UI 包集合 ~/.local/lib/node_modules/@deepseek-ai/dsh/node_modules/@deepseek-ai/dsh-client-ui-* 前端壳 …/@deepseek-ai/dsh-web-frontend/dist/ 模块加载器(宿主半) …/@deepseek-ai/dsh-client-modules/lib/index.js 主题服务类型 …/@deepseek-ai/dsh-client-ui-theme/lib/types/client/index.d.ts 设置服务类型 …/@deepseek-ai/dsh-client-ui-settings/lib/types/client/config-form.d.ts profile ~/.dsh/profiles/web/ ├─ package.json dsh.profile.bundles ├─ cordis.yml 空 [] └─ cordis.patch.yml ← 编辑这里(patchReload: live) 插件市场 ~/.dsh/profiles/web/node_modules/dshmarket/ (带完整 src/) 市场状态 ~/.dsh/dsh-market/ 机制研究样本 /tmp/theme-inspect/(catppuccin 解包 + 还原的 TS 源码) 本地 UI 插件范例 ~/SillyTaven/制卡/dsh-prompt-injector ``` ## 附录 B:常用命令 ```sh dsh --version dsh plugin --profile web add # 转发 pnpm dsh plugin --profile web remove dsh web # 默认 127.0.0.1:3080 # 改完前端半:刷新浏览器即可(模块带 rev 戳);改宿主半:重启 dsh web curl -s https://awesome-dsh-plugin.com/plugins.json | head -c 500 # 目录实时快照 ``` ## 附录 C:证据索引 | 结论 | 证据位置 | | --- | --- | | 只认 `dsh.client.platform === 'web'` 的包 | `dsh-client-modules/lib/index.js` L714 | | 声明 client 但无 `./client` 导出 → 抛错 | 同上 L719 | | `dsh.client` 字段校验规则 | 同上 L61–74 `parseDshClient()` | | 前端半按 `/plugins//client.js?rev=` 取 | `dsh-client-modules/lib/index.js` L204、L293 | | `ThemeRuntime` 六个方法签名 | `dsh-client-ui-theme/lib/types/client/index.d.ts` L109–188 | | 107 个 alias token | 同上包 `lib/client.js` 的 `body{--dsw-alias-*}` 段 | | `ctx.configForms` 存在于 0.2.0-rc.2 | `dsh-client-ui-settings/lib/types/client/config-form.d.ts` L96 | | 市场兼容筛选只读 `engines.dsh` + peer | `dshmarket/src/discovery-compatibility.ts` 文件头注释 + `compatibility.ts` | | 市场不读 `dsh.compatibility` | `grep -rn "dshReleases\|dshOperations" dshmarket/src` → 零命中 | | 安装优先级 / 安全边界 / 只收精选来源 | `dshmarket/README.zh.md`「速度」「安全」两节 | | 收录规范(一个 YAML、满 1 天、`dsh.bundle` 必需) | `awesome-dsh-plugin/awesome-dsh-plugin` 的 `contributing.md` | | `apply()` 抛错会拖垮整套插件 | `dsh-catppuccin` 还原源码 `src/registration-tolerance.ts` 注释 |