Chuyển đến nội dung chính

dsh-comfyui-based-tools

Đã xác minh

@liaozhi/dsh-comfyui-based-tools · v0.8.6 · MIT · Giao diện web

DeepSeek Harness plugin: 把 DSH 工具接到用户自备的 ComfyUI 实例(实例管理 + 工具分配 + MiniMax H3 视频 / Qwen-Image 图像工具)。

Cài đặt

dsh plugin add @liaozhi/dsh-comfyui-based-tools

Xác nhận layer đã áp bằng dsh --profile default --dump-config — xem hướng dẫn cài plugin.

Mã nguồn

Phát hành lên npm mà không có repository công khai. Hãy kiểm tra nội dung package trước khi cài.

Thẻ

Readme

dsh-comfyui-based-tools

支持范围:本插件仅适配 DeepSeek 桌面版(desktop profile)的 Harness runtime。 当前支持的 DeepSeek 桌面版版本:0.2.0-rc.1(版本门禁 [MIN_HARNESS_VERSION, MAX_HARNESS_VERSION] = [0.2.0-rc.1, 0.2.0-rc.1] 单点窗口,见 src/version-gate.ts;桌面版应用与 runtime 锁步同版发版, 插件 peer 声明 ^0.2.0-rc.1)——桌面版版本不匹配时插件启动即软禁用, 设置里不再有本插件分区,改在「插件」页本插件详情页出现「已停用」说明面板 并给出版本指引,不影响 DSH 启动与其他插件。 dsh web、自定义 profile、SDK/headless 等形态一律不支持——启动时 profile 门禁 (src/profile-gate.ts)会自动软禁用本插件。下文沿用的 dsh web 开发流示例是 插件模板的历史写法,实际部署与运行请以桌面版为准。

DeepSeek Harness 插件模板:双端(host + client)HMR 开发,开发完成后可直接发布到 npm 或推到 GitHub 供用户安装。

模板自带三个可删改的示例功能,覆盖两端与端间调用:

  • Host half(src/index.ts):注册模型可调用的 greet 工具,演示 inject / Config schema / 事件监听;
  • Client half(src/client/index.ts):在「插件」页本插件详情页注册配置面板(plugins.bundle.config 槽位),演示 slot 组合、locale 词典(zh/en 双语键集)、CSS Modules 样式与浏览器原地热替换;
  • Host↔Client 调用(src/remote.ts + src/client/api.ts):面板上的"调用 host"按钮经 Typert Gateway 调用 host 半的 comfyui-tools/ping 端点,演示第三方插件的远程调用形态(为什么是手写描述符而非官方 @Remote 装饰器,见 src/remote.ts 头注释)。

使用本模板

通过 GitHub 的 "Use this template" 创建新的插件仓库(仓库名随意),克隆到本地后, 先改名。改名前搞懂插件身份由三样东西组成,它们互相派生:

概念 本模板取值 怎么派生
短插件 id dsh-comfyui-based-tools = 仓库名(use-this-template 建库时你起的名字;克隆到本地即根文件夹名)
npm 包名(scoped) @liaozhi/dsh-comfyui-based-tools @<scope>/ + 短 id;@scope = 你的 npm 用户名
环境前缀 DSH_COMFYUI_BASED_TOOLS 短 id 全大写、-/. 转 _

@scope 即 npm 用户名(npm whoami 可查,未登录先 npm login)——本插件取 @liaozhi, 故包名为 @liaozhi/dsh-comfyui-based-tools。

改名 = 换这三样东西,各有固定落点。漏改不报错——这些标识只是注册 key,构建/加载 都不校验一致性,漏改只会让对应功能静默失效(面板不出现、HMR 不重载、RPC 404), 所以必须逐表核对。建议执行顺序:先全局换短 id(包名里的短 id 会一并变)→ 再换 3 处 scope → 再换前缀 → 最后核对顺手项。

① npm 包名(scoped)—— 只 3 处

位置 改成
package.json 的 name @<新scope>/<新短id>
cordis.patch.yml 的 name: "@<新scope>/<新短id>"(必须加引号,YAML 里 @ 是保留指示符;按它解析到包 exports['.'])
tsdown.config.ts banner 的 __ModuleLoader__ id 同上(client 按包名发现)

② 短插件 id —— 其余 15+ 处

位置 改成
cordis.patch.yml 的 id: <新短id>
src/index.ts 的 export const name + Typert package: 字段 同上(cordis 插件名)
src/client/index.ts 的 export const name + 两处 slot id 同上
src/remote.ts 的端点描述符 id(<短id>#…) 同上(Typert 关联)
src/invariant.ts 的 PACKAGE_NAME / name 同上(伴生插件名)
tsdown.config.ts 的 CSS 虚拟模块前缀 + 插件名 同上

③ 环境前缀 —— 2 处 + 各文件错误/日志前缀

位置 改成
src/version-gate.ts 错误前缀、DSH_<前缀>_STRICT 变量名 <新前缀> + _STRICT
src/shared/disabled-flag.ts 的 __DSH_<前缀>_DISABLED__ 变量名 __<新前缀>_DISABLED__
src/self-update.ts 的错误/日志前缀(dsh-comfyui-based-tools: / [dsh-comfyui-based-tools]) 换成新短 id(包名/版本本身从自身 package.json 读,无需改)

④ 顺手项

位置 改成
cordis.dev.yml 的 root 绝对路径(root 列表 + 顶部用法注释) 你机器上实际的 lib/ 路径
src/client/locales.ts 的 title(配置页标题;插件页条目名与导航 label 由插件页按包清单自带,无需 locale key) 你的 UI 标识(如 "Meme 生成")
src/shared/update-contract.ts 端点 namespace('comfyui-tools/…')与 src/remote.ts 的 namespace / serviceKey / service 与 Typert 服务名同源,改服务名时一并改
LICENSE 版权行 你的名字
README.md / AGENTS.md / 示例文案里出现的旧短 id 同上

验证

  1. pnpm install(自动跑 prepare 完成首次构建)
  2. pnpm typecheck && pnpm build 通过
  3. git grep <旧短id> 与 git grep <旧包名>,全仓应无残留
  4. 若该目录此前已装进 profile:重跑 dsh plugin --profile web add

开发(HMR)

一次安装进 profile(client half 的发现依赖包名解析,必须安装而非 --patch 插路径):

# 安装了 dsh CLI:
dsh plugin --profile web add <本模板目录的绝对路径>

# 或用 harness 源码仓库:
cd <deepseek-harness 仓库> && pnpm dsh plugin --profile web add <本模板目录的绝对路径>

每个开发会话两个终端:

# 终端 1:构建监视器(tsc --watch + tsdown --watch,持续重写 lib/)
pnpm dev

# 终端 2:harness(叠加开发 overlay,重新启用 HMR 并监听 lib/)
dsh web --patch <本模板目录>/cordis.dev.yml --port 3081
# 或源码仓库里:pnpm dsh web --patch ... --port 3081

然后:

  • 改 src/index.ts(host 半) → tsc 重写 lib/index.js → cordis-plugin-hmr 重载插件 → 终端打印新的 host half loaded 日志。在 Web UI 让 agent 调用 greet 工具验证。
  • 改 src/client/index.ts(client 半) → tsdown 重写 lib/client.js(<100ms)→ host 的 client-hmr 轮询发现 → SSE 广播 → 浏览器不刷新页面就地更新。打开 http://127.0.0.1:3081 → 插件页 → 本插件详情页 → 配置面板可见。

无浏览器验证 client 链:curl -N http://127.0.0.1:3081/plugins/events,改 client 源码会实时收到 data: {"type":"rebuilt","id":"dsh-comfyui-based-tools",...}。

安装版 dsh 注意:cordis-plugin-hmr 需要读 Node 内部模块加载器。若启动报 --expose-internals is required for HMR,用 NODE_OPTIONS=--expose-internals dsh web ... 启动即可(从源码运行的 pnpm dsh 自带 tsx,无此问题)。

依赖版本与追新

harness 处于 0.1 rc 快速迭代期,要点:

  • peer 与 dev 写同一个 ^<已实测的最低版本>:dev = 编译所用版本(typecheck 类型面),peer = 运行时声明。窗口内没有"下限/上限/并集"之分——不要用范围去"夹"多条 harness 线(预发布线内跨版本 API 破坏频繁,范围放宽救不了,取证见 docs/dsh-0.1.5-rc.1-upgrade-report.md 与 docs/compat-plan-0.1.7-rc.1.md)。caret 在预发布线上只覆盖同一 major.minor.patch 的更高预发布号,所以 ^0.2.0-rc.1 覆盖 0.2.0 线内更高预发布号——同线内追新(如 rc.1→rc.2)默认不需要改依赖,只抬 MAX;跨 minor 迁移(如 0.1.7→0.2.0)caret 不覆盖新线,MIN/MAX 一并上移 + 依赖下限同步改写(peer precheck 按 satisfies(includePrerelease) 校验,旧 caret 对新线 runtime 直接 false → 插件被声明面禁用);只有同线内 MIN 上移(目标 tag 被上游取代 / 旧 MIN 从未通过运行时验证)时才把依赖下限同步改写到新 MIN。
  • harness 版本门禁:模板自带 src/version-gate.ts,要求 harness 版本落在 [MIN_HARNESS_VERSION, MAX_HARNESS_VERSION] 双边界窗口内(compat/0.2.0-rc.1 分支当前 [0.2.0-rc.1, 0.2.0-rc.1] 单点——跨 minor 迁移按 peer precheck 的 caret 硬约束一并上移;main 分支持已验证的 [0.1.7-rc.2, 0.1.7-rc.2])。另有 src/profile-gate.ts:无 profile 启动(SDK 嵌入等)或非 desktop profile 时插件软禁用——本插件仅支持 DeepSeek 桌面版(desktop-only 收敛,取证见该文件头注释)。0.1.7 起 harness 自身也做插件兼容性检查(peer 依赖 semver satisfies(includePrerelease) 校验,不满足的插件行在启动组装期直接 disabled),插件自身的精细窗口门禁仍在 apply 时执行,两者分工见 version-gate.ts 头注释。版本源 = runtime 内 @deepseek-ai/dsh 包 version(桌面版应用与 runtime 锁步同版发版,语义与 dsh --version 一致);【不能】读 @deepseek-ai/dsh-tools 等内嵌依赖当 harness 版本:caret 语义下实装版本可能高于 CLI 本体(实测 @deepseek-ai/[email protected] 内嵌的 dsh-tools 已解析到 0.1.5-rc.2,读它会误判软禁用)。解析锚点 = 桌面单锚:桌面版 host 子进程由 Electron spawn,process.argv[2] = runtimeDir(launcher 硬契约,apps/desktop/src/host-process.ts),读 <argv[2]>/node_modules/@deepseek-ai/dsh/package.json 并校验 name。落在窗口外默认软禁用:醒目错误日志 + 插件不注册任何业务能力,不影响 DSH 启动;DSH_COMFYUI_BASED_TOOLS_STRICT=1 恢复抛错 fail-loud。软禁用时 host 半经 webserver/index-inject 向页面注入 __DSH_COMFYUI_BASED_TOOLS_DISABLED__ 标记(fiber-bound,卸载即撤、无残留),client 半读到后改挂"已停用"说明面板并展示支持版本窗口(src/shared/disabled-flag.ts 是两侧共享的标记契约)。

追新流程(手动,无脚本):

版本抬升刻意做成显式手改——目标线选择、破坏面评估、门禁窗口三件事本来就该人工过目(实测教训:0.1.x 线内 0.1.1 → 0.1.2 这种带破坏性变更的跨 patch 追新会被脚本静默扫进来)。追新的每一步都值得慢下来看一眼:

# ① 查目标线的最新版本(各 @deepseek-ai/dsh-* 与 harness 同步发版):
pnpm view @deepseek-ai/dsh versions --json

# ② 手改 package.json:全部 @deepseek-ai/dsh-* 的 peer 与 dev 改为同一个 ^<目标版本>
#    (窗口内没有并集写法;新线才有、只 import type 的包只进 dev)
#    同 minor 线内追新(如 rc.1→rc.2)caret 已覆盖,默认无需改依赖,直接跳到 ④;
#    仅当 MIN 也要上移(旧 MIN 从未实测 / 已不是发布版)时才同步改写依赖下限

# ③ 同步与验证:
pnpm install
pnpm typecheck && pnpm build        # 红 = 上游破坏性变更,按升级报告的取证清单修

# ④ 实测通过后手动上调 src/version-gate.ts 的 MAX_HARNESS_VERSION
#    (MIN 保持已实测的最低版本;窗口保持双边界结构)

# ⑤ 同一轮同步指引:AGENTS.md / README.md / version-gate.ts 头注释里的版本表述
#    ——留旧窗口 = 让后续 agent 照旧窗口写代码。
  • 不用 pnpm update:它会剥掉 rc 包的 ^(实测),且不碰 peer 侧。
  • 窗口 = 已验证承诺:抬 MAX 等于宣布「插件已在那个版本上验证过」,没实测过的版本不要抬;MIN 保持已实测的最低版本(compat/0.2.0-rc.1 分支当前 [0.2.0-rc.1, 0.2.0-rc.1])。MIN 上移 = 收缩承诺面:跨 minor 迁移必做(peer precheck 的 caret 不跨 minor 线,双窗口需 OR 范围夹线、被禁止);同线内只在旧 MIN 从未通过运行时验证或已不是发布版本(被 npm dist-tag 接任)时做,且须同轮把依赖 caret 下限改写到新 MIN(0.1.7-rc.2 追新即此情形)。
  • 锁文件会钉住首次安装版本:想每次克隆都最新就删掉它,否则按上面流程主动追新。
  • 注意 caret 语义会匹配同 tuple 的更高预发布(^0.2.0-rc.1 会解析到 0.2.0 线内更高的 rc/正式版)——这正是同线追新无需改依赖的理由:与门禁窗口重合。反之 caret 不跨 minor:^0.1.7-rc.2 对 0.2.0-rc.1 runtime 为 false(实测 satisfies(includePrerelease)),跨线迁移必须改写依赖下限。

多线兼容(默认不做):

双线/多线支持(peer 并集 + dev 钉线 + 特征探测回退)是 0.1.1→0.1.2 时代的做法,本插件已收敛为单条线(main = 0.1.7 线、compat/0.2.0-rc.1 = 0.2.0 线;0.1.5 线已随 desktop-only 收敛放弃),不再维护。历史方法论仅存档于 docs/compat-guide-0.1.1-to-0.1.2.md(已加归档标注),仅在确需重新引入多线时参考,并须在 AGENTS.md 与迁移计划里明确标注为多线。

插件自更新

配置页(「插件」页本插件详情页)标题行右侧内置「检查更新 / 立即更新 / 取消」条(client 半 ComfySection.tsx 六态状态机 + host 半 src/self-update.ts,经 comfyui-tools/checkUpdate / comfyui-tools/updateSelf / comfyui-tools/cancelUpdateSelf RPC):

  • 检查:当前版本 vs npm registry /<pkg>/latest(严格 semver 比较,含预发布规则);候选源沿用官方 registryPlan 语义逐源降级(pluginManager 服务 registries() 计划,npm → npmmirror,registry 类失败转问下一候选);面板 mount 时静默查一次,离线不打扰、手动可重试;
  • 更新:一律走官方 pluginManager 服务通道 ctx.pluginManager.installBundle(<pkg>@<ver>)(harness 0.1.7 起 base bundle 内置的 cordis 服务,与 dsh plugin CLI / Web「插件」页同源同锁;同 pnpm 通道 + profile manifest 写锁)。进程内直达、无子进程:白送 registry 回退计划、失败/取消自动还原 package.json/pnpm-lock.yaml、安装期兼容性预检;成功后提示重启 harness 生效(对已装包做版本更新时服务明确不热重载,返回 restart-required——与旧 CLI 通道同语义);
  • 取消 + 流式日志:安装中可点「取消」(服务端 cancelInstall 硬中断 pnpm,退出并还原 profile 文件后才落定;预取阶段由协作检查点接住);pnpm 输出与阶段通告经 plugin-manager/install-log/install-state 事件(按 requestId 过滤)喂给 host 日志环形缓冲,面板实时展示,失败/被取消后保留供排查;
  • 前提:插件必须已发布到 npm。GitHub-only 分发(未发 npm)时 registry 检查失败——面板静默/手动重试,属预期行为;
  • 软禁用态没有更新条:版本门禁软禁用时 host 半不注册任何端点(RPC 必然 404),停用面板只给 harness 版本修复引导——插件更新不了 harness,也不该试图更新;
  • 并发与竞态:重复点击合并进同一次安装(host 半 in-flight 锁 + 相位单一写者);点更新时 latest 回落到 ≤ 当前则归位「已是最新」;更新只换磁盘,需重启 harness 加载新 host 半。

自更新测试桩(verdaccio 本地 registry)

npm 没有测试端点(版本号一次性,测试版本不能真发),自更新测试走本地 verdaccio(npm 官方文档推荐的本地 registry):

  • scripts/verdaccio.config.yaml:匿名 publish + npmmirror 上游代理(被测包的 @deepseek-ai/* peer 依赖自动走代理);关键设计——被测 scope @liaozhi/* 故意不配 proxy,版本空间完全本地化,上游真实已发布版本不会混进 dist-tags 干扰测试;
  • scripts/update-stub.mjs:发布测试版本(临时改 version → pnpm pack(prepack 自动 build)→ 自动注册测试用户拿 token → npm publish --tag latest → finally 恢复 package.json)。

PowerShell 测试步骤:

# ① 起服务(仓库根目录;storage 落在 .update-stub/,已 gitignore)
npx --yes verdaccio@6 --config scripts/verdaccio.config.yaml

# ② 发布测试版本(每次换新版本号——同版本重复 publish 会被拒,unpublish 也不释放)
node scripts/update-stub.mjs publish 0.5.2-test.1

# ③ 插件侧:设覆盖变量后同终端启动 harness
$env:DSH_COMFYUI_UPDATE_REGISTRY = "http://127.0.0.1:4873"
dsh web

面板「检查更新」应报测试版本 →「立即更新」(看日志面板 / 试「取消」按钮)→ 重启 harness 生效。测完 Remove-Item Env:DSH_COMFYUI_UPDATE_REGISTRY 恢复官方源。

两个前提:DSH_COMFYUI_UPDATE_REGISTRY 只被本仓库源码里的新版 self-update 读取——需以 --patch 开发 overlay 或 link 方式运行仓库源码才生效;私有源被 harness 单独询问、不回落公共源,桩挂了检查/安装就失败(不会静默装到真包,隔离性是刻意的)。测后切回 link 开发模式的标准姿势:先 dsh plugin --profile <name> remove @liaozhi/dsh-comfyui-based-tools 再 dsh plugin --profile <name> add C:\agentProjects\dsh-comfyui-based-tools\(先 remove 再 add,绕开 pnpm 的 junction 覆盖缺陷)。

发布到 npm

npm login          # 首次
pnpm publish       # prepack 会自动完成构建

files 白名单只带运行必需的产物(lib/index.js、lib/invariant.js、lib/client.js、cordis.patch.yml),发布前建议 pnpm pack 检查内容。用户安装:

dsh plugin --profile web add dsh-comfyui-based-tools

上传到 GitHub

直接 push 源码即可(不要提交 lib/,它在 .gitignore 里)。本模板带了 prepare 脚本:用户从 git 安装时 pnpm 会用它现场构建产物(自包含,只依赖 npm 上的公开包)。

用户侧安装分两步(pnpm ≥10 的构建许可要求):

# 1. 首次 add 会被 pnpm 拒绝构建脚本,按提示把包名加进 profile 的
#    pnpm-workspace.yaml(~/.dsh/profiles/<profile>/pnpm-workspace.yaml):
#    allowBuilds:
#      dsh-comfyui-based-tools: true
# 2. 重新执行
dsh plugin --profile web add github:<owner>/dsh-comfyui-based-tools#<commit-sha>

构建许可 = 允许安装时执行该包的代码,提醒你的用户只对他们信任的包开启,并建议 pin commit。 不想让用户配许可的话,也可以发 npm 或提供 pnpm pack 产物(tarball 安装不跑构建)。

原理速记

  • 一切都是 Cordis 插件;通过 ctx 注册的都是可逆 effect,卸载自动清理——这是 HMR 热替换的前提。
  • Host 半 HMR:@deepseek-ai/cordis-plugin-hmr(dsh-base 已挂载,web 模式默认禁用,cordis.dev.yml 按 id 重启用)监听构建产物,沿 Node 模块图重载受影响的插件条目。
  • Client 半 HMR:dsh.client 声明让 modules 服务把包扫进 window.__DSH_BOOT__ 并以 /plugins/<id>/client.js 供给;client-hmr stat 轮询 bundle 变化 → SSE 广播 → 浏览器按"invalidate → prefetch → 卸载旧 fiber → 物化新工厂"原地热替换。触发源是任何重写 lib/client.js 的进程,本模板用 tsdown --watch。
  • 分发形态即开发形态:exports['.'] 恒指向 lib/index.js,npm 包、git 安装、本地开发走同一条加载路径,没有"源码能跑、装上就挂"的落差。
  • 依赖规则:@deepseek-ai/* 一律进 peerDependencies(运行时由 harness 安装环境提供;dsh profile 的 pnpm 工作区是 autoInstallPeers: false + 安装目录兜底解析,不会为你的 peer 链去 npm 拉包),普通第三方库进 dependencies,构建工具进 devDependencies。不要把 @deepseek-ai/* 放进 dependencies。
  • HMR 只覆盖 link 开发的场景:cordis-plugin-hmr 沿 Node 模块图追踪时排除路径含 /node_modules/ 的模块,所以 npm/git 安装进 profile 的实体副本不参与热重载(它们是给最终用户的不可变产物);dsh plugin add <本地目录> 是符号链接,realpath 后跳出 node_modules,才能被追踪。
  • dsh.client 声明是包元数据,扫描缓存永不过期:改它要重启 harness;lib/ 内容变化才走热替换。
  • --patch overlay 不在运行时监听列表里,改 cordis.dev.yml 需重启;profile 的 cordis.patch.yml 和 $DSH_HOME/cordis.patch.yml 才是热监听的。

已知坑(模板已绕过,建议反馈上游)

Windows 下 cordis-plugin-hmr 默认的 ignored 含 **/.*,而 hmr 用 picomatch 匹配 path.relative() 的反斜杠结果时反斜杠被当转义符,..\.. 前缀会误命中 **/.*——base 目录之外的监听 root 被静默吞掉。cordis.dev.yml 用 ignored: [] 绕行。上游正解:匹配前归一化分隔符(或 picomatch windows: true)。

文件清单

├── src/index.ts                      # host half 入口(版本门禁 + greet 工具示例 + Typert 端点注册)
├── src/profile-gate.ts               # profile 启动门禁(profileContext 判空;无 profile 启动软禁用)
├── src/version-gate.ts               # harness 版本门禁(软禁用语义 + CLI 锚点解析 + semver 比较)
├── src/self-update.ts                # 插件自更新 host 半(registry 多源检查 + 官方 pluginManager 服务通道)
├── src/shared/disabled-flag.ts       # host→client 的软禁用标记契约(全局变量名 + 载荷形状)
├── src/shared/update-contract.ts     # 自更新 RPC 契约(端点名 + 请求/结果类型,两端共用)
├── src/remote.ts                     # host 半 Typert 远程端点(comfyui-tools/*,表驱动描述符 + 第三方安全形态)
├── src/invariant.ts                  # 官方 invariant 伴随件(每包必有)
├── src/css-modules.d.ts              # CSS Modules 导入声明(*.module.css)
├── src/client/index.ts               # client half 入口(词典/样式/槽位注册组装 + 停用标记分支,无 JSX)
├── src/client/locales.ts             # zh/en 词典(所有 UI 文案走 locale key)
├── src/client/api.ts                 # 浏览器 → host 的 RPC 调用(OpResult 统一错误面)
├── src/client/ComfySection.tsx         # 配置页组件(plugins.bundle.config 面板 + 插件更新条)
├── src/client/ComfyDisabledSection.tsx # 版本不兼容"已停用"说明面板
├── src/client/ComfySection.module.css  # 配置页样式(CSS Modules + --dsw 设计令牌)
├── docs/compat-guide-0.1.1-to-0.1.2.md     # 0.1.1↔0.1.2 兼容手册(两线差异取证 + 带代码方案)
├── docs/compat-plan-TEMPLATE.md            # 兼容迁移执行记录骨架(每次追新 cp 一份填写)
├── docs/compat-plan-0.1.7-rc.1.md          # 0.1.5-rc.3→0.1.7-rc.1 兼容迁移执行记录
├── docs/compat-plan-0.1.7-rc.2.md          # 0.1.7-rc.1→0.1.7-rc.2 同线追新执行记录(main 窗口)
├── docs/compat-plan-0.2.0-rc.1.md          # 0.1.7-rc.2→0.2.0-rc.1 跨 minor 迁移执行记录(当前窗口)
├── scripts/dev.mjs                   # pnpm dev:并行两个构建监视器
├── tsdown.config.ts                  # client bundle 构建(CJS 工厂 + 基线外部化 + CSS Modules 内联)
├── tsconfig.json                     # 全量类型检查(pnpm typecheck)
├── tsconfig.build.json               # host 半构建(lib/index.js + lib/invariant.js)
├── cordis.patch.yml                  # bundle 层:安装时插入插件行
├── cordis.dev.yml                    # 开发 overlay:重启用 HMR 并监听 lib/
└── package.json                      # dsh.bundle + dsh.client 双清单,prepare/prepack 构建