dsh-skill-deck
Verifieddsh-skill-deck · v0.1.2 · MIT · Web UI
DSH 技能牌组:在一个面板里浏览、导入、创建、启停与回收本机技能;每个技能可加只保存在本机的中文别名 / 备注 / 分类 / 标签(不写入 SKILL.md,模型读不到);输入栏提供技能快捷插入浮层。
Install
dsh plugin add dsh-skill-deck Confirm the layer applied with dsh --profile default --dump-config — see the install guide.
Source
Tags
Creators
Readme
dsh-skill-deck
DSH 技能牌组:本机技能统一管理 + 纯本地中文别名/描述/分类/标签 + 输入栏「技能」快捷插入 +
.md一键导入。
它为三个具体痛点而做,功能上与 @linxin666/dsh-client-ui-skill-explorer(旧「技能中心」面板)和
@weibaohui/skills-management(输入栏快捷发送)有重叠——开发时参考过这两个插件的做法:
对前者是纯设计参考(无代码派生),对后者有少量代码派生(浮层组件与技能目录扫描辅助),
已按 MIT 要求在 NOTICE 里随附版权声明与许可全文。来源边界见 THIRD_PARTY.md。
它解决的三件事
1. 中文别名与自定义描述:只给你看,AI 永远读不到
技能名和 description 就是 SKILL.md 里的 frontmatter,想改成看得懂的中文,就等于改模型读到的东西。
本插件把展示层独立出来:
| 数据 | 存放位置 | 模型能看到吗 |
|---|---|---|
| 技能真名 / description / whenToUse / 正文 | SKILL.md |
能(这是技能的实质) |
| 中文别名 / 本地描述 / 分类 / 标签 | <DSH_HOME>/skill-deck/meta.json |
不能 |
- 别名只被本插件的界面读取:技能列表里显示
动画制作,旁边永远并排显示真名animate。 - 本地元数据不写进 SKILL.md、不注册进
ctx.skills注册表、不进模型目录, 所以模型既看不到、也不受影响。 - 编辑弹窗把这两块明确分区:左边「只给你自己看(AI 看不到)」、右边 「写给模型看的(会改 SKILL.md)」,避免误改。
实测证明(本项目验收记录):给 animate 加别名 动画制作 后,
~/.dsh/skills 下 13 个 SKILL.md 的 SHA256 全部一字未变,
且 skill 工具仍能正常按真名 animate 加载该技能。
标注的数据安全(两条被真实踩过之后立的规矩)
- 删除技能绝不丢标注:
POST /delete只把技能目录移进回收站,meta.json里那条 标注原样保留——所以「回收站 → 还原到原位」之后别名/描述/分类会一起回来,回收站 列表也会显示别名,便于辨认是哪一条。真正丢弃标注的时机只有一个:彻底删除 (单条trash/purge,或「清空回收站」)。早先
/delete里顺手delete current.skills[name],于是「删除 → 还原」之后别名 凭空消失——这是用户实测发现的;scripts/smoke-host-routes.js第 15 步现在锁住它。 - 「读不出来」不等于「空仓库」:
meta.json存在但解析失败时,插件拒绝任何写入 并报错,同时把原始内容另存为meta.json.corrupt-<时间>。绝不能用空仓库覆盖用户数据。 - 每次写入前备份上一代到
meta.json.bak,误操作还有一次回滚机会。 - 清空全部字段再点「保存本地标注」= 清掉该技能的标注(把字段清空的自然预期); 它只写一条空记录,不删除条目本身,空记录会在下次进程启动时被清洗掉。
2. 分类:按来源自动分组 + 自定义分类/标签
- 自动分组:按技能来源分(用户技能库
~/.dsh/skills、共享池~/.agents/skills、 项目.dsh/skills/.agents/skills、自定义目录、系统内置、运行时注册)。 - 自定义分类:自己建分类(可配色)给技能归类,列表顶部按分类筛选。
- 两种分组视图:工具条上「按来源分组」/「按分类分组」一键切换。
- 标签:逗号分隔的任意标签,参与搜索。
3. 输入栏「技能」:点开即插入,底色跟宿主菜单一致
旧插件的浮层背景直接用 var(--dsw-specific-menu)——那是个半透明菜单色,盖在会话上会
透出后面的正文,很难看清。本插件的浮层换个做法:
/* 菜单色铺面 + 不透明底色托底 */
background-color: var(--dsw-alias-bg-base, #16161a);
background-image: linear-gradient(var(--sh-surface), var(--sh-surface));
/* --sh-surface: var(--dsw-specific-menu, var(--dsw-alias-bg-overlay, #26262c)) */
于是它和「仅可查看 / 工作区内修改 / 完全权限」那类宿主菜单是同一个颜色(换主题也跟着换),
又不会透出底下的正文——两个问题一起解决。浮层的底色由测试锁住(smoke-client.js 第 20 步)。
浮层里显示中文别名(主标题)+ 真实技能名(副标题),但插入的永远是真实技能名
/<name> (底部常驻一行说明这一点)。所以你看中文挑,AI 收到英文真名。
4. 一键导入:把桌面上的 .md 直接变成技能
「新建」页顶部有一个导入区:选择 .md 文件,或把文件直接拖进去。
- 自动读取 frontmatter:
name→ 技能名,description→ 简介,whenToUse→ 适用场景,disable-model-invocation→ 模型可见性;frontmatter 之外的正文原样成为技能正文。 - 中文文件名自动变成中文别名(只给你自己看),技能名仍是合法的 kebab-case,
例如
Skill-ComfyUI图片反推.md→ 技能名skill-comfyui、别名Skill-ComfyUI图片反推。 - 缺
description时用正文首个段落顶替;都没有才退回文件名,保证一定能建出来。 - 导入失败(重名、超过 64 KB、名字不合法)不吞错:把解析结果回填到页面表单, 改好点「创建技能」即可,文件内容不会白读一次。
插入用的是官方 InputActions(一个踩过的坑)
会话作用域槽位(conversation.input.left)的标准 prop 里有:
inputActions: InputActions // captureInsertion() + insertText(text, span)
sessionId: SessionId
input 不是标准 prop。insertText 的落笔条件是
span.draftRev === 当前 rev(见 ui-conversation 的 InputActions),
而 span 必须来自 captureInsertion()——它是唯一能拿到正确 rev 的入口。
早期只走 slash/input-insert-text 事件(自己拼一个 {start, end, draftRev: 0}),
结果是 CAS 静默拒绝:草稿里什么都出现不了,也不报错。
现在两条路都留着,官方通道优先,事件通道仅作老内核回退;两条都失败时浮层会明确
提示「没能写进输入框,请手动输入 /技能名」,不再静默吞掉。
功能一览
| 面板 tab | 能力 |
|---|---|
| 技能 | 搜索(别名/真名/描述/标签/分类)、按来源或分类分组、分类筛选、模型可调用开关(改写 disable-model-invocation)、编辑、删除(移入回收站) |
| 新建 | 一键导入 .md(读取 frontmatter,中文文件名转为中文别名);或填表建到用户库 / 当前项目 |
| 回收站 | 查看/还原到原位/彻底删除/清空(默认保留 30 天,还原时本地标注一起回来) |
另外:面板顶部「管理分类」可新建/改名/删除分类。
模型侧不受影响
本插件在 ctx.skills 上注册了一个文件系统技能 provider(skill-deck-filesystem),
按官方 dsh-skill-filesystem 的根约定扫描技能。这是有意为之:本 profile 里官方
filesystem provider 与 @weibaohui/skills-management(它的 provider 名是 ntd-skills)
都被停用,如果本插件不顶上,模型侧就一个技能都看不到。
provider 只输出技能的真实字段;本地别名/描述/分类完全不在其中 (有离线测试专门断言候选里绝不出现别名)。
安装
推荐:从 npm 装(已发布,包名 dsh-skill-deck)
打开 DSH 设置 → 插件 → 安装,在输入框里填包名:
dsh-skill-deck
同一个输入框也接受另外两种来源:
| 填什么 | 什么时候用 | 代价 |
|---|---|---|
dsh-skill-deck |
正常安装(推荐) | 无;插件管理器的「检查更新」认得它 |
git+https://github.com/sallygulaus-stack/dsh-skill-deck.git |
想装仓库当前状态 | 慢;不参与更新检查 |
…\dsh-skill-deck-0.1.0.tgz |
离线 / 内网机器 | 升级要手动重装 |
⚠️ 必须走 GUI 里的插件管理器。 命令行
dsh plugin --profile desktop add …对desktopprofile 会被拒绝——桌面端的 profile 由正在运行的 DSH 进程持有, CLI 写不进去。(link:本地目录那条路只在自用调试时有意义,同样走 GUI 填目录路径。)
首次安装或升级后请「完全退出 DSH(含托盘)再启动」,原因见下:
- 宿主半区的代码改动(
src/index.js)必须重启进程才能生效——Node 的require缓存不会 因为插件行重新挂载而失效。 - 客户端半区的 bundle 改动:重新
node scripts/build-client.mjs后,页面刷新即可。
开发
node scripts/build-client.mjs # 拼接 client-src/*.js → client/bundle.js
node scripts/smoke-host.js # 宿主纯函数冒烟(frontmatter 解析/外科式改写/仓库清洗)
node scripts/smoke-host-routes.js # 宿主路由端到端(临时 DSH_HOME,不碰真实技能)
node scripts/smoke-client.js # 客户端 bundle 装载 + 组件渲染覆盖
client-src/ 的分片按文件名顺序拼接,共享同一个 factory 作用域:
| 分片 | 内容 |
|---|---|
00-head.js |
__ModuleLoader__ 包装、常量、样式表、ensureStyles |
10-core.js |
路由探活与 fetch、小工具、基础组件 |
20-panel.js |
「技能牌组」中栏页面(列表/分组/编辑/分类/新建导入/回收站) |
30-picker.js |
输入栏「技能」按钮 + 快捷插入浮层 + slash/input-insert-text 插入 |
99-tail.js |
apply/inject 导出与槽位注册 |
发布(CI 自动,不需要令牌)
发布由 GitHub Actions + npm Trusted Publishing 完成:GitHub 用仓库身份(OIDC)向 npm 换取 一次性发布凭证,所以本地不需要任何 npm 令牌,也不需要 2FA。两处配置:
| 在哪 | 内容 |
|---|---|
| 仓库 | .github/workflows/publish.yml(推 tag 触发,先跑三套冒烟测试再发) |
| npm 网站 | 包页面 → Settings → Trusted publishing 绑定 sallygulaus-stack/dsh-skill-deck + publish.yml |
发一版:
npm version patch -m "chore: release v%s" # 升版本 + 本地打 tag
git push --follow-tags # 分支和 tag 一起推
Actions 依次做:跑三套冒烟测试 → 校验 tag 与 package.json 版本一致 → npm publish --provenance
(带 Sigstore 来源签名)。
npm 有 2~4 分钟的后台处理:推完注册表不会立刻出现新版本,这期间别人
npm install装到的还是旧版。 确认是否上线用npm view dsh-skill-deck version。
配置
profile 的 cordis.patch.yml 里可以写:
- id: skill-deck
name: dsh-skill-deck
config:
customSkillDirs: [] # 额外扫描的技能根目录
dataDir: ~/.dsh/skill-deck # 本地元数据 + 回收站 + 诊断产物的落盘目录
trashRetentionDays: 30 # 回收站保留天数,<=0 关闭自动清理
providerName: skill-deck-filesystem
dumpClientGraph: true # 启动时把客户端模块图写到 dataDir/client-graph.json
对外路由
| 路由 | 方法 | 说明 |
|---|---|---|
api/dsh-skill-deck/list |
GET | 分组技能列表 + 本地元数据 + 分类(可带 ?cwd=) |
api/dsh-skill-deck/picker |
GET | 快捷插入浮层的候选(小投影) |
api/dsh-skill-deck/read |
GET | 单个技能的可编辑字段与正文(?name=&path=) |
api/dsh-skill-deck/meta |
POST | 保存本地标注(别名/描述/分类/标签)——只写 meta.json |
api/dsh-skill-deck/categories |
POST | 分类 create/update/delete |
api/dsh-skill-deck/set-enabled |
POST | 切换 disable-model-invocation(外科式改写 frontmatter) |
api/dsh-skill-deck/create / update / delete |
POST | 创建 / 就地改写 / 移入回收站 |
api/dsh-skill-deck/trash / trash/restore / trash/purge |
GET/POST | 回收站 |
api/dsh-skill-deck/health / client-graph |
GET | 健康检查 / 客户端模块图诊断 |
所有路由先过 ctx.connection.requestRejection 的信任栅栏;写路由还会把「面板显示的路径」
当身份声明,执行前必须由一次全新扫描解析出同名且路径完全一致的技能,否则拒绝
(防止项目技能消失后改到同名的用户级技能)。
已知限制
SKILL.md的 frontmatter 用零依赖轻量解析(块标量、布尔、嵌套块);改动用外科式替换 ——只替换自己那几个键的行区间,注释、input:嵌套块、其余键原样保留。生僻 YAML 特性 以官方 provider 的解析为准。- 链接(符号链接)技能可列出、可切换模型可见性,但不可编辑、不可删除(会越出当前技能根)。
- 宿主代码改动必须重启 DSH 进程才生效(见「安装」)。
- 删除确认用面板自带的
ConfirmDialog,不用原生confirm()。DSH Desktop 的 GUI 走dsh-app://自定义协议,实测原生确认框不弹窗(枚举全系统窗口——含不可见窗口——查不到 任何对话框),因此用原生确认时「删除」会静默什么都不做。scripts/smoke-client.js里 有一条护栏:客户端 bundle 中一旦再出现原生确认调用,测试立刻失败。 - 同理,DSH Desktop 的渲染器在本机自动化下表现异常(
window.confirm不弹、部分表单字段 收不到注入的键盘/粘贴事件),排查界面问题时别把「注入无效」当成「功能无效」。
来源与致谢
开发时参考过的两个插件、它们与本项目重合/不重合的边界、以及宿主接口的归属, 见 THIRD_PARTY.md。