本文是一份技术实操文档:手把手讲清楚一个 DeepSeek Harness(DSH)插件从「是什么」到「做出来、调试好、发上 GitHub、进插件市场、发上 npm」的完整流程,包括每一步的细节、原理和注意事项。
全部内容基于插件 会话清道夫(dsh-session-sweeper) 的真实开发与上架过程验证------它已经走完了本文的每一步:GitHub 管仓、npm 在架、awesome-dsh-plugin 列表收录、dsh-market 市场上架。
环境基线:Windows · Node 24+(自带 zstd)· pnpm · 一份 DeepSeek Harness 源码检出(下文记作
<DSH检出>,示例用E:\AI_WORK\AI_DSH)。
第 1 章 认识 DSH 插件:动手前必须理解的四件事
1.1 DSH 是「一切皆插件」的微内核
DeepSeek Harness(dsh)既是一个可直接用的 Coding Agent,底层又是一套插件框架:模型接入、工具、沙箱、会话存储、Web UI、甚至 Agent 循环本身,都是插件。你写的插件和官方插件地位完全平等,能扩展官方行为,也能替换核心部件。
1.2 一个插件在物理上就是三个文件 + 两个产物
text
my-plugin/
├── package.json ① 声明「我是个可安装的插件」+「我有浏览器界面」
├── cordis.patch.yml ② 告诉 DSH 把插件插到哪(一行 insert)
├── src/index.ts ③ Host 半侧源码(跑在 DSH 主进程,Node)
├── src/client/index.tsx ④ Client 半侧源码(跑在浏览器,React)
└── lib/ ⑤ 构建产物(lib/index.js + lib/client.js)------故意入库
package.json 里的两个 manifest 是整个体系的钥匙:
jsonc
{
"name": "dsh-my-plugin", // npm 包名(发布/安装用)
"version": "1.0.0", // 严格 semver
"type": "module",
"main": "lib/index.js",
"exports": {
".": "./lib/index.js", // Host 半侧入口
"./client": "./lib/client.js", // 浏览器半侧入口(client-modules 系统读它)
"./cordis.patch.yml": "./cordis.patch.yml",
"./package.json": "./package.json"
},
"files": [ // 发布白名单:lib/ 必须在(预构建入库=免构建安装)
"lib/index.js", "lib/client.js", "lib/client.js.map",
"cordis.patch.yml", "README.md", "LICENSE", "CHANGELOG.md"
],
"dsh": {
"bundle": { "patch": "./cordis.patch.yml" }, // ← 没有它就无法安装(收录/安装的硬性条件)
"client": { // ← 有浏览器界面才需要
"platform": "web",
"inject": ["@deepseek-ai/dsh-client-runtime", "@deepseek-ai/dsh-client-ui-settings"]
}
},
"peerDependencies": { "@deepseek-ai/cordis": ">=4", "react": ">=18" },
"license": "MIT",
"repository": { "type": "git", "url": "git+https://github.com/<你>/dsh-my-plugin.git" }
}
⚠️ 收录被拒的头号原因:只声明了
dsh.client没有dsh.bundle------那样的包无法安装。dsh.client只在你确实带浏览器界面时才需要。
cordis.patch.yml(一行补丁,安装时被追加进 profile 的组合配置):
yaml
- insert:
- id: my-plugin # 插件行的配置 id(profile 补丁层可以用它做覆盖/禁用)
name: dsh-my-plugin # 按包名引用,安装后由 Node 模块解析找到代码
1.3 双半侧结构:Host 与 Client 各干什么
Host 半侧 (lib/index.js) |
Client 半侧 (lib/client.js) |
|
|---|---|---|
| 跑在哪 | DSH 主进程(Node) | 用户浏览器(React) |
| 典型职责 | 扫文件、开 HTTP 接口、读写配置、给模型注册工具 | 在设置页/侧边栏渲染界面、调 Host 接口展示数据 |
| 写法 | 导出 apply(ctx, config);inject 声明依赖;ctx.effect() 注册自动清理的资源 |
导出 apply(ctx);通过 ctx.slots.inject(...) 在 DSH 界面的槽位里注册 React 组件 |
| 互相通信 | Client 用 fetch 调 Host 注册的 HTTP 接口(同源) |
|
| 依赖纪律 | 除 Node 内建外零运行时依赖(官方包只做 type 导入,构建时被擦除) | 运行时只允许 react / react-dom(由 DSH 的模块表提供,不许打包进去) |
以会话清道夫为例 :Host 半侧扫描四种工具的会话文件、提供 /sweeper/api/* JSON 接口、执行隔离/恢复;Client 半侧在设置页渲染会话表格与回收站,用户点一下,Client 调 Host 接口完成操作。
1.4 插件如何被安装、加载、出现在界面上
理解这条链路,后面每个步骤的目的就都清晰了:
text
① 你写好插件包(package.json 带 dsh.bundle + lib/ 预构建)
② dsh plugin --profile web add <包> → 包被装进 profile,插件行插入组合配置
③ dsh web 启动 → Loader 按行加载 Host 半侧 → apply(ctx) 执行
→ client-modules 系统发现 package.json 里有 dsh.client
④ 浏览器打开页面 → 页面注入启动清单,按组合脚本加载 lib/client.js
→ 插件在 Settings 的槽位注册卡片 → 用户看到界面
⭐ 第 2 章 人机分工:AI 包办代码之后,你必须亲手做的事
本项目实战中,AI 完成了约 95% 的执行工作 :全部代码、构建、调试、测试、打包、推送到远端、甚至 API 级验证。但剩下约 5% 是只有你本人能完成 的------它们分别绑定你的账号身份 、发生在只有你能登录的网页里 、需要你的法律签字 、或需要你的最终拍板。这一章把这部分单独拎出来,照着做就行;其余一切都可以交给 AI。
2.1 身份与凭据(只有你能做)
| 事项 | 为什么必须你做 | 耗时 |
|---|---|---|
| 注册 GitHub / npm 账号 | 身份本体 | 各 5 分钟 |
| 创建 GitHub 公开仓库 | 你的账号下的资产 | 1 分钟 |
| SSH 密钥生成 + 公钥添加到 GitHub | 私钥保管是你的责任(AI 可代跑生成命令) | 5 分钟 |
| npm Granular Access Token 创建(勾选 Bypass 2FA,Packages: Read and write) | 网页会用你配置的 2FA 方式(如 Windows Hello)验证身份;token 决定谁有发布权 | 2 分钟 |
🔐 token 安全三原则:不落盘 (一次性环境变量使用)、不入库 (.gitignore 排除 .npmrc)、有效期不过长(按发版节奏设置)。泄露 = 别人可以以你的名义发包。
2.2 网页操作(需要你的 GitHub/npm 登录态)
按项目时间线排列,每一步都是网页点击:
| 时间点 | 操作 | 入口 |
|---|---|---|
| 开工当天 | 创建 GitHub 公开仓库(先不要勾自动 README,本地会推完整树) | github.com/new |
| 开工当天 | About 填一句话简介 + About 齿轮加 Topics:dsh-plugin |
仓库首页 |
| 首次发版 | GitHub Release:选 tag、上传 tgz、描述写 SHA-256 | Releases → Draft new release |
| 发版当天 | (可选)npmjs.com 创建 Granular Token 发给 AI/贴进 .npmrc;npm publish |
npmjs.com → Access Tokens |
| 发版当天 | (可选)登录 dsh-hub.cc → Publish → 提交仓库或 npm URL | dsh-hub.cc/publish |
| 仓库满 1 天后 | awesome-dsh-plugin 提收录 PR(见第 7 章:fork → 新建 yml → Create pull request) | awesome-dsh-plugin 仓库 |
| 合并后 | 打开 DSH 设置 → 插件市场,验收上架效果 | 你的 DSH |
2.3 决策与责任(AI 不替你拍板)
- LICENSE 选择:MIT / Apache-2.0 / 闭源......直接影响别人能否商用与二次分发;换协议属破坏性变更
- 插件名、定位、市场分类的最终确认------名字会跟随整个生命周期
- 通读一遍 AI 生成的代码再发布 ,三重理由:
- awesome 评审会实际阅读你的仓库源码,核对描述与代码一致
- 收录声明「非安全审查」------出了凭据外传、混淆代码等问题,第一责任人是署名作者
- 删除/清理类插件 README 里的数据安全模型是你的公开承诺(本插件承诺了:默认隔离可恢复、SHA-256 清单、审计日志、彻底删除需确认短语------每一条都必须真的做到)
- 发布时机:功能稳定、测试绿了再发;仓库满 1 天后才能提收录 PR
2.4 本机与浏览器验收
- 重启自己的
dsh web使插件生效(⚠️ 会结束当前 agent 会话,先保存工作) - 浏览器 Ctrl+Shift+R 强刷,确认界面为新版本(插件若有版本比对横幅会自动提示)
- 打开 dsh-market 搜索插件名,确认上架效果与截图展示
- 用一两个真实会话做小规模验收:预览 → 隔离 → 回收站恢复 → 逐字节一致
2.5 最小人工操作清单(照抄执行)
| 时间 | 你要做的 | 耗时 |
|---|---|---|
| 开工当天 | 建 GitHub 仓库、About、topic dsh-plugin |
5 分钟 |
| 开工当天 | (AI 完成代码/构建/推送后)无 | --- |
| 首次发版 | Release 上传 tgz + SHA-256 | 3 分钟 |
| 首次发版 | npm Granular Token 创建 → 交 AI 发布 | 5 分钟 |
| 仓库满 1 天 | awesome 提收录 PR(fork → 新建 yml → 粘贴 → PR) | 10 分钟 |
| 合并后 | dsh-market 验收 | 1 分钟 |
除上表之外的一切------写码、构建、调试、测试、打包、推送、API 验证------都可以交给 AI。本项目实际由 AI 完成了绝大部分执行,人工只做了账号、网页点击与最终拍板,合计不到 30 分钟。
第 3 章 插件工程搭建(步骤 1--5)
步骤 1:初始化目录与 package.json
按 §1.2 的模板创建,逐字段说明:
name:全网唯一的 npm 包名(建议dsh-前缀,市场搜索靠它)version:严格 semver。发版纪律:package.json= 源码里的版本常量 = git tag,三处必须一致(升级工具靠 tag 上的 package.json 判断新版本)files:发布白名单。lib/必须在------预构建产物随仓库和包走,用户安装免构建dsh.bundle.patch:没有它 = 不可安装 = 市场收录被拒repository:npm 发包后,市场靠它关联「包 ↔ 仓库」,必须指回你的仓库license:与 LICENSE 文件一致(npm 无 license 发包会警告)
步骤 2:写 cordis.patch.yml
yaml
- insert:
- id: my-plugin # 行 id:后续补丁层可用「同 id 覆盖/禁用」这一行
name: dsh-my-plugin # 模块名:按安装后的包名解析
步骤 3:写 Host 半侧
最小可用的 Host 骨架(会话清道夫的真实结构):
ts
// src/index.ts
import type { Context } from '@deepseek-ai/cordis' // 只做类型导入,构建时被擦除
export const PLUGIN_ID = 'dsh-my-plugin'
export const VERSION = '1.0.0'
export const inject: string[] = [] // 必需的宿主服务(空=无硬依赖)
export interface Config { guardHours?: number } // 补丁行 config: 传进来的配置
export function apply(ctx: Context, config: Config = {}): void {
const service = new MyService(config) // 你的业务服务
// webServer 用「可选注入」:纯 CLI profile 没有它也能正常启动
ctx.inject(['webServer'], (wctx) => {
wctx.effect(() => wctx.webServer.register({
kind: 'prefix',
path: '/myplugin',
handler: (req, res) => handle(req, res), // JSON 接口分发
}))
})
console.log(`[my-plugin] mounted v${VERSION}`)
}
Host 侧可用的能力速查:
| 能力 | 用法 |
|---|---|
| HTTP 接口 | ctx.webServer.register({ kind, path, handler });JSON 响应 + 同源校验(拒绝带 Origin 且不匹配的请求) |
| 插件配置 | apply(ctx, config) 直接收补丁行 config: 对象 |
| 模型工具 | ctx.tools.register(...) 让 agent 能调用你的功能 |
| 事件 | ctx.on('session/event', ...) 等跟随会话流转 |
| 生命周期 | ctx.effect(() => 资源, 清理函数) 注册的一切随插件卸载自动清理 |
安全清单(涉及删除/写盘的插件务必照做,收录评审也会看):
- 删除类操作默认软删:移入自己的回收目录 + manifest 记录原路径 + 每文件 SHA-256 + 提供恢复
- 永久删除需确认短语二次校验
- 活跃文件保护窗口(如 24h 内改过的不允许动)、被占用文件跳过不中断整批
- HTTP 接口做同源校验;请求体限长
- 绝不触碰工具配置、凭据、memory、todos 等非会话资产
步骤 4:写 Client 半侧
骨架(真实可用):
tsx
// src/client/index.tsx
import type { Context } from '@deepseek-ai/cordis'
import { useEffect, useState } from 'react'
export const VERSION = '1.0.0'
export const inject = ['slots'] // client 侧需要的服务
const styles = `...你的自包含 CSS(用 var(--dsw-alias-*) 适配深浅色)...`
function MyCard() {
const [data, setData] = useState(null)
useEffect(() => { fetch('/myplugin/api/data').then(r => r.json()).then(setData) }, [])
return <div className="dshmp-root">...</div> // <style>{styles}</style> 放根部
}
export function apply(ctx: Context): void {
ctx.slots.inject('settings.section', () => ctx.slots.register({
name: 'settings.section', id: 'my-plugin', order: 17, label: '我的插件',
}, MyCard)) // ← 传组件本身,不是箭头函数包装
}
Client 侧三条铁律:
- 运行时只能 import 模块表里的东西 :
react、react/jsx-runtime、react-dom。@deepseek-ai/*一律import type(构建时擦除,不违规则);其他 npm 包必须打进 bundle - 浮层(弹窗/抽屉)必须
createPortal到document.body,z-index ≥ 2000------设置页自身层叠上下文 1000+,不 portal 会被裁剪或压住 - 模板字符串里的 CSS 注释别用反引号------反引号会终止模板字符串导致构建 PARSE_ERROR(真实踩过)
步骤 5:构建(tsdown 双产物配置)
插件包根放 tsdown.config.ts(完整可复用):
ts
const TABLE_EXTERNALS = ['react', 'react/jsx-runtime', 'react-dom']
export default [
{ // Host 半侧:Node ESM
name: 'dsh-my-plugin',
entry: ['src/index.ts'], outDir: 'lib',
format: ['esm'], platform: 'node', target: 'es2023',
fixedExtension: false, dts: false, clean: false, sourcemap: false,
},
{ // Client 半侧:浏览器单文件 lazy-CJS factory
name: 'dsh-my-plugin/client',
entry: { client: 'src/client/index.tsx' }, outDir: 'lib',
format: ['cjs'], platform: 'browser', dts: false, sourcemap: true, clean: false,
fixedExtension: false,
deps: { // 模块表里的保持 require() 外部调用,其余全部内联进单文件
neverBundle: (spec) => TABLE_EXTERNALS.includes(spec),
alwaysBundle: (spec) => !TABLE_EXTERNALS.includes(spec),
},
outputOptions: {
entryFileNames: 'client.js', sourcemapExcludeSources: false,
banner: 'window.__ModuleLoader__.load({ id: "dsh-session-sweeper", factory: (require) => {',
intro: 'var module = { exports: {} }; var exports = module.exports;',
footer: 'return module.exports; } });',
},
},
]
构建:& <DSH检出>\node_modules\.bin\tsdown.CMD(cwd = 插件包根)。产物应为 lib/index.js + lib/client.js(+.map);打开 client.js 检查头三行是 factory 包装、react 是 require(...) 外部调用。
第 4 章 本地调试(dev overlay 工作流)
步骤 6:写 dev overlay
插件包根放 cordis.dev.yml(只在开发时用,不入发布的 files)。三段式,缺一不可:
yaml
# ① 禁用会与你抢单实例锁的宿主插件(否则双开实例启动即崩)
- id: ui-task-board
disabled: true
# ② 若目标 profile 已安装同名插件:禁用安装行(否则与 dev 行构成「双源」组合错误)
- id: my-plugin
disabled: true
# ③ 插入本地构建的 dev 行(Windows 绝对路径必须 file:/// URL)
- insert:
- id: my-plugin-dev
name: 'file:///E:/D_Temp/dsh/my-plugin/lib/index.js'
⚠️ patch 语义陷阱 :非 insert 补丁行里的
name是匹配守卫 ,不是可覆盖字段------想换模块必须「禁用旧行 + 插入新行」,直接写name: <新路径>会被静默跳过。
步骤 7:启动验证实例
sh
cd E:\AI_WORK\AI_DSH
pnpm dsh --profile web --patch E:\D_Temp\dsh\my-plugin\cordis.dev.yml --port 3180 --no-open
命令顺序三个坑:
| 错误写法 | 报错 |
|---|---|
dsh web --patch x.yml |
unknown option '--patch'(patch 被当成了 web 应用的参数) |
dsh --patch x.yml web ... |
web takes none of parent --patch(web 别名不吃父级 flag) |
| ✅ 正确 | dsh --profile web --patch x.yml --port 3180 --no-open(--patch 给启动器,--port/--no-open 转交 web 应用) |
启动成功的三重验证:
- 终端出现你的挂载日志(如
[my-plugin] mounted) - 终端打印带 token 的 URL:
dsh web: http://127.0.0.1:3180/?token=... - Host 接口能通:
curl http://127.0.0.1:3180/myplugin/api/ping
Client 半侧的验证:带 token 打开页面 → 查看源码搜你的包名(boot 图里有 /plugins/??<包名>/client.js&rev=... 组合脚本行)→ 设置页里出现你的卡片。
步骤 8:测试
- 纯函数单测 (
node --import tsx/esm --test,零依赖):过滤、排序、去重、解析器,全部抽成纯函数锁行为 - 集成测试 :
make-fixtures.mjs生成合成目录树 + 服务环境变量覆盖真实 home------测试永不碰真实数据 - 真实 DOM 复现 :jsdom + 真实 React + 执行部署 bundle 的字节 + mock fetch + 派发点击。静态检查全对但 UI 异常时,这是唯一能抓到真凶的手段(本项目靠它抓到 React 重复 key 导致的「列表不刷新」)
- ⚠️ 测试的 cwd 必须是 DSH 检出(tsx 从那里解析),构建的 cwd 必须是插件包------两件事别在同一条命令里混
第 5 章 GitHub 管仓与发布
步骤 9:README / .gitattributes / .gitignore / LICENSE
| 文件 | 要求 |
|---|---|
| README.md(+ 中文版) | 必须写清能力、配置、权限(读哪些写哪些)、安装、卸载、数据安全模型------dsh-hub 发布指南明确要求,awesome 评审会对照代码核读。放 1--2 张界面截图(市场不声明截图时会从 README 自动抽取) |
| .gitattributes | * text=auto eol=lf + 二进制标记,防 Windows 换行漂移 |
| .gitignore | 铁律:绝不忽略 lib/、cordis.patch.yml、package.json ------忽略 lib = 源码安装直接失败。应忽略:dist/、node_modules/、构建缓存、测试残留、凭据文件(.npmrc、.env) 。一句话:忽略规则的反面就是 files 白名单,改之前对照检查 |
| LICENSE | 官方无硬门槛,但实质必备(npm 警告、用户要授权)。建议 MIT;换协议 = 破坏性变更 |
步骤 10:建仓、推送、打 tag、发 Release
sh
git init && git add -A && git commit -m "feat: 插件首个版本"
git branch -M main
git remote add origin git@github.com:<你>/dsh-my-plugin.git
git push -u origin main
git tag v1.0.0 && git push origin v1.0.0
然后 GitHub 网页:About 填一句话简介 → Settings/About 齿轮加 Topics:dsh-plugin → Releases → Draft new release 选 tag v1.0.0 → 上传 tgz → 描述里写 SHA-256 → Publish。
Release 资产名两种合法姿势(不混用):
releases/latest/download/dsh-my-plugin.tgz(不带版本,链接永不失效)releases/download/v1.0.0/dsh-my-plugin-1.0.0.tgz(钉住 tag,带版本是正常惯例)
若远端已有初始提交(建仓时勾了自动 README),本地完整树直接
git push -f origin main覆盖即可------远端只有模板文件,无价值。
第 6 章 npm 发布
为什么发 npm(可选但强烈建议)
- 用户安装免构建授权 (
dsh plugin add dsh-my-plugin一条命令) - 升级工具自动检测:
GET registry/<pkg>/latest(国内 npmmirror 自动同步) - 市场展示下载量并按它排序;dsh-hub 可直接用 npm 包名提交
- 注意 :
repository字段必须指回收录条目对应的仓库,否则 npm 包与市场条目不关联
2FA 政策与发布方式
npm 现要求发布必须二选一:账号开启 2FA(发布带动态码) ,或使用勾选了 Bypass 2FA 的 Granular Access Token。
- Granular Token(推荐,一次创建长期用) :npmjs.com → Access Tokens → Generate New Token → Granular Access Token → Packages: Read and write → 勾选 Bypass two-factor authentication(网站会用你配置的 2FA 方式验证一次,如 Windows Hello)
- 发布用一次性环境变量,token 不落盘:
powershell
[Environment]::SetEnvironmentVariable('npm_config_//registry.npmjs.org/:_authToken', '<npm_开头的token>', 'Process')
npm publish
- 有 TOTP 验证器 :
npm publish --otp=当前6位码
发版后验证
sh
npm view dsh-my-plugin version dist-tags # registry 已是新版
第 7 章 上架 dsh-market 与 awesome 列表
7.1 机制:dsh-market 没有独立提交通道
dsh-market 的目录实时拉取 awesome-dsh-plugin.com/plugins.json(CI 每日刷新),且只允许安装精选列表内的来源。所以:
进入 awesome-dsh-plugin 列表 = 进入 dsh-market 市场(合并后通常一天内生效)。
7.2 收录流程
- 前置 :仓库公开、About 简介、topic
dsh-plugin、dsh.bundlemanifest、真实可用代码、仓库创建满 1 天(CI 自动检查,突击建仓会被拒------先建仓过一天再提 PR) - 提 PR :向
awesome-dsh-plugin/awesome-dsh-plugin只新增一个文件data/plugins/<owner>__<repo>.yml:
yaml
url: https://github.com/<你>/dsh-my-plugin # 必须与仓库完全一致
name: <你>/dsh-my-plugin
category: session # session=会话与消息;其余:ui usage theme model identity memory tools ...
description:
en: One-line description, accurate to the code, ending with a period.
zh: 一句话中文描述,与代码逐条对得上。 # 可选,维护者会补
# 可选:预构建包(市场会优先用它而不是源码下载;钉住 tag 永不失效)
tarball: https://github.com/<你>/dsh-my-plugin/releases/download/v1.0.0/dsh-my-plugin-1.0.0.tgz
- 规则 (评审逐条对代码核,不实会被打回):描述与代码一致、无营销词、含
:必须加引号、一个 PR ≤ 3 条、只动自己的文件、非纯聚合包、依赖指向上游 - 合并后:
awesome-dsh-plugin.com条目上线 → dsh-market 自动出现(含你的 screenshots.json 截图)→ dsh-hub 也可再用 npm 包名叠加提交
7.3 截图(强烈建议)
仓库根放 screenshots.json:
json
[ "assets/1-list.png", "assets/2-preview.png", "assets/3-recycle.png", "assets/4-settings.png" ]
1--8 张、相对路径、不跳出插件目录(第三方图床被拒)。截图文件名建议 ASCII。声明后市场详情页做 AppStore 式轮播;不声明则从 README 自动抽图。
第 8 章 升级机制与用户侧体验
- 自动检测 :
upgrade_dsh_plugin.py类工具检查 npm 包的registry/<pkg>/latest,或 GitHub 仓库 tag 上的 package.json - 执行升级 :重跑
dsh plugin --profile web add <新spec>覆盖安装,重启dsh web生效 - 客户端缓存 :插件浏览器 bundle 是 immutable 强缓存(内容寻址),宿主更新后用户浏览器可能仍跑旧版------在 client 里做版本比对 (client 编译期常量 vs Host
/config返回值),不一致就在界面显著提示「Ctrl+Shift+R 强制刷新」 - 回收目录与审计日志跨版本保留(那是用户的恢复通道),manifest 自带版本字段保证向后兼容
第 9 章 注意事项与踩坑实录(每条都真实发生过)
| # | 坑 | 现象 | 解法 |
|---|---|---|---|
| 1 | patch 改不动 name |
覆盖行被静默跳过 | patch 语义:非 insert 行的 name 是匹配守卫。换模块 = 禁用旧行 + 插入新行 |
| 2 | 同包双源 | 启动崩 multiple active Loader sources |
安装行与 dev 行同时激活。dev overlay 里先 disabled: true 旧行 |
| 3 | Windows 路径 | Received protocol 'e:' |
loader 条目绝对路径必须 file:///E:/... URL 形式 |
| 4 | 启动参数 | unknown option '--patch' / web takes none of parent... |
--profile web --patch x.yml 顺序;web 别名不吃父级 flag |
| 5 | 插件互锁 | ledger is already owned by process N |
双开实例撞单实例锁;dev overlay 里 disabled: true 对方行 |
| 6 | 列表过滤"不刷新" | 数据对、渲染错 | 扫描产生 React 重复 key (如 Claude 把 subagent 日志存在 <id>/subagents/)→ 调和未定义行为;数据源头去重 |
| 7 | 消息被压扁 | 有滚动条但内容压缩 | 列 flex 容器里子项 overflow:hidden 使最小尺寸归零被压缩 → flex-shrink: 0 |
| 8 | 弹窗不可见 | 打开但看不到 | portal 到 body + z-index 2000 |
| 9 | 改了没生效 | 界面还是旧的 | /plugins/* immutable 强缓存;client 内建版本比对 + 刷新提示 |
| 10 | DSH 日志解不开 | zstd 解压只有"一行" | DSH 日志是追加式 zstd 多帧;按帧结构扫描逐帧解压 |
| 11 | npm publish 403 | 2FA or granular token required |
Granular Token 勾 Bypass 2FA,或账号开 2FA 后 --otp |
| 12 | Windows Hello ≠ OTP | CLI 没法 --otp |
Passkey 出不了 6 位码;建 Granular Token |
| 13 | tsx 找不到 | 测试加载失败 | --import tsx/esm 的 cwd 必须是能解析到 tsx 的目录(DSH 检出) |
| 14 | 模板字符串炸了 | 构建 PARSE_ERROR | CSS 模板字符串内的注释别用反引号 |
第 10 章 快速参考卡
sh
# ── 开发 ─────────────────────────────────────────────
tsdown.CMD # 构建(cwd=插件包根)
pnpm dsh --profile web --patch <dev.yml> --port 3180 --no-open # 验证实例(cwd=DSH 检出)
node --import tsx/esm --test tests/*.test.ts # 测试(cwd=DSH 检出)
# ── 发布 ─────────────────────────────────────────────
pnpm pack --pack-destination dist # 打 tgz
git tag v1.0.x && git push origin v1.0.x # 打 tag → GitHub Release 附 tgz + SHA-256
npm publish # 发 npm
# ── 用户侧 ───────────────────────────────────────────
dsh plugin --profile web add dsh-my-plugin # 安装(npm)
dsh plugin --profile web remove dsh-my-plugin # 卸载(回收/审计数据保留)
提交收录前最后核对 :仓库公开 ✓ About 简介 ✓ topic dsh-plugin ✓ 仓库满 1 天 ✓ dsh.bundle ✓ lib/ 入库 ✓ README 四要素 ✓ LICENSE ✓ screenshots.json ✓ Release tgz + SHA-256 ✓ npm repository 字段 ✓
附:本文的实战来源
本文整理自 dsh-session-sweeper(会话清道夫) 的完整开发上架过程:一个扫描/查看/安全清理 Claude Code、Codex CLI、WorkBuddy、DSH 会话历史的插件,现已收录于 awesome-dsh-plugin 列表并上架 dsh-market。插件源码:HrxSpace/dsh-session-sweeper。