npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@zfdx123/dsh-skills-manager

v1.0.13

Published

DSH 技能管理器:诊断、新建、编辑、开关、搬运磁盘上的技能(SKILL.md),补上官方 provider 静默跳过坏文件的那半边,并给模型一个 skill_manager 工具

Readme

@zfdx123/dsh-skills-manager

给 DeepSeek Harness(DSH)用的技能管理器:把散落在磁盘上的技能(<root>/<name>/SKILL.md 目录包,或 <root>/<name>.md 单文件)当成可管理的对象——能诊断、能编辑、能开关、能搬运、能删除,并补上官方 provider 缺的那一半。它同时给模型一个 skill_manager 工具,让「给某类任务现场造一个技能」变成对话里的一句话。

安装

npm 安装(推荐):

dsh plugin --profile web add @zfdx123/dsh-skills-manager

想把整套插件(目前 7 个)一次装齐,用聚合包:

dsh plugin --profile web add @zfdx123/dsh-atelier

两者不要同时装:同一个插件 id 会被插进组合树两次。要单个就只装那一个。

本插件是一个 profile bundle(与 dsh-mcp-manager 同形态),装进哪个 profile 就在哪个 profile 生效;装完重启 dsh web(组合树在进程启动时装配)。

本地目录安装(改代码时用):

# 1. 把包加进 profile 的依赖
cd ~/.dsh/profiles/web
# 编辑 package.json 的 dependencies,加一行:
#   "@zfdx123/dsh-skills-manager": "link:E:/work/ai/dsh-atelier/packages/dsh-skills-manager"
# 并把包名加进 dsh.profile.bundles 数组

# 2. 安装(建立符号链接)
pnpm install

# 3. 重启 dsh(组合树在进程启动时装配)

验证安装

重启后:

  1. 设置页应出现 技能管理;
  2. 侧栏左下应出现 技能;
  3. 让模型调 skill_manager(action: list)应能列出技能。

如果设置页没出现,先看宿主日志里有没有 loaded without registering "<id>" via __ModuleLoader__.load——那是客户端注册 id 与包名不一致(客户端 bundle 的注册 id 必须等于包名 @zfdx123/dsh-skills-manager,写成短名会在挂载阶段直接抛错)。

禁用

在 profile 的 cordis.patch.yml 里按插件行 id skill-manager 覆盖:

- id: skill-manager
  disabled: true

快速上手

# 1) 装 + 重启
dsh plugin --profile web add @zfdx123/dsh-skills-manager

# 2) 打开 设置 → 技能管理:列表顶部就是诊断摘要
#    「N 个技能 · 正常 x · 警告 y · DSH 会跳过 z」
#    有 broken 的先在这一屏修掉——那些技能官方 provider 只会 warn 然后丢掉

# 3) 侧栏左下「技能」:就地浮层,随手开关或改正文,不用跳进设置页

# 4) 想加自己的技能库:设置页「自定义技能文件夹」→ 填目录(或用「浏览…」)
#    仓库式技能库(技能在 <集合>/skills/<名称>/SKILL.md)记得勾「递归」

让模型用它:

用 skill_manager 列出所有技能,把 DSH 会跳过的那些连同原因一起告诉我。
用 skill_manager 新建一个技能:name=changelog-entry,description=…,写进当前项目根目录。

它做什么

官方 provider 缺的那一半

官方 provider(@deepseek-ai/dsh-skill-filesystem)负责发现并加载,但它有几个让技能作者很难受的特性:

| 官方行为 | 后果 | |---|---| | 坏 frontmatter 只 logger.warn,然后静默跳过 | 模型侧分不清「技能不存在」和「技能写坏了」 | | 会话目录里看不到路径、层、rank、遮蔽关系 | 改了文件不生效时,不知道是哪一份在生效 | | 完全没有写操作 | 新建/改名/开关/搬运只能靠手改文件 | | name 语法、布尔语法、遗留键都是硬规则 | 写错了没提示,技能直接消失 |

本插件补上这些:诊断 + 读写 + 分层可视化。

交付了什么

| 面 | 位置 | 说明 | |---|---|---| | 设置页 | 设置 → 技能管理 | 完整管理台:列表 / 诊断 / 详情编辑 / 新建 / 搬运 / 回收站 | | 侧栏入口 | 侧栏左下角 技能 | 就地浮层,随手开关或改正文 | | 模型工具 | skill_manager | 让模型在对话里给某类任务现场造技能或修技能 | | 宿主 API | /api/skills/manager | 上面三者共用的唯一数据通道,带回环来源护栏 |

诊断(核心价值)

  • 列出磁盘上全部技能,包括官方会静默跳过的那批,并说明会被跳过的原因;
  • 按官方语法逐项校验:name kebab-case、description 必填、布尔语法(boolean | 1 | 0 | "1" | "0" | true/false/yes/no/on/off,大小写不敏感)、遗留键(disableModelInvocation / modelInvocable / userInvocable 会让 DSH 整条丢弃该技能);
  • 标注遮蔽关系:同名多副本时谁是胜者、谁被遮蔽、为什么;
  • 与 ctx.skills 注册表交叉核对,指出「磁盘上有但 DSH 还没认」的技能;
  • 提示 frontmatter.name 与文件/目录名不一致(改名请用「重命名」)。

编辑

  • 新建:名字 + 一句话说明 + 目标根目录 + 形态(目录包 / 单文件),自动写入可用骨架;
  • 只改正文 → frontmatter 逐字节保留(注释、键顺序、本插件读不懂的结构都活着);
  • 改字段 → 只重写对应那一行,其余行不动;
  • 写盘前先校验:会让 DSH 跳过这个技能的内容拒绝落盘,避免把好文件改坏;
  • 并发保护:expectedText 不匹配(文件被外部改过)时拒绝覆盖。

生命周期

  • 开关:一键切换「模型可见」/「/ 可见」(写官方支持的 disable-model-invocation / user-invocable);
  • 重命名:连同 frontmatter 的 name 一起改,不会留下名字与文件不符的坏状态;
  • 移动 / 复制:跨根目录搬运,复制时同步改写副本的 name;
  • 删除:移入 <root>/.trash/<时间戳>-<名字>,可恢复;点开头目录天然不会被 provider 扫到。

自定义技能文件夹

  • 把任意目录加成技能根(rank 300):设置页有输入框 + 「浏览…」(调宿主原生目录选择器;宿主没有 directoryPickerController 时会明确提示手填路径);
  • 两种扫描方式,每个文件夹独立切换:
    • 一层(默认,与 DSH 官方一致)——只认 <目录>/<名称>/SKILL.md 与 <目录>/<名称>.md;
    • 递归——继续向下最多 3 层找 SKILL.md,用于仓库式技能库:技能在 <集合>/skills/<名称>/SKILL.md 这种更深的层级时,一层扫描只会认出顶层几个;
  • 递归的护栏:跳过 node_modules 与点开头目录;命中一个技能后不再下探(它内部的嵌套 SKILL.md 属于该技能的资源,如 references/,不是独立技能);
  • 目录不存在会被拒绝;勾选创建后只在上级目录已存在时创建,不会递归造一棵目录树;
  • 自动去重:重复添加、或该目录已经是内置技能根,都会被明确拒绝;
  • 移除只解除管理,绝不动目录与其中的技能文件;
  • 遮蔽关系即刻生效:自定义目录是 rank 300,低于项目层(100/200)、高于用户层(400/500);
  • 设置写在 skill-manager 命名空间;多窗口并发添加时有 revision 冲突重试,不会互相覆盖。

把管理的技能真正交给 DSH

  • 注册一个技能提供者(skills-manager,与官方的 filesystem / 保留名不冲突),管理器里的技能因此进入 DSH 会话目录、模型可按名加载;
  • 为什么必须有这一层:官方 provider 只扫它自己配置里的根(组合文件里那一行的 customSkillDirs),管理器面板添加的目录它根本不知道——不注册提供者的话,面板列出的技能就是「看得见、用不上」;
  • 提供者与面板共用同一套发现逻辑,所以递归目录里的技能也一并生效;
  • 每次写操作(新建/改/开关/搬运/删除/增删自定义文件夹)之后立刻让 DSH 重扫目录:注册表的目录缓存按 revision 键控,而本提供者没有文件 watcher,不主动失效的话面板里删掉的技能仍被广告、刚建的进不去;
  • inventory: false 可关掉登记(只想看、不想让模型加载到);开关是动态的——提供者每次 list() 现读配置,改完立即生效,不用拆装 provider。

安全

  • 所有写操作必须落在已解析出的可写根目录之内;
  • 路径段禁止 ..,点开头目录(.git / .system / .trash 内部)一律拒绝;
  • bundled 根(安装产物)只读;
  • 包含关系判定按最严格语义实现:跨盘符(Windows 上 path.relative() 会返回绝对路径)、同级目录、父目录、自身、空值输入一律判「不在里面」——这是所有写护栏的地基;
  • 原子落盘:同目录临时文件 + rename,避免半截文件被 provider 读到;
  • HTTP 接口只接受本机回环来源,且 Host 头必须指向本机(挡 DNS rebinding)。

界面:与外壳同一套原生组件

  • 弹窗、提示、按钮、输入框、开关、标签、状态点都用外壳自己暴露的 UI 原语(@deepseek-ai/dsh-client-ui-primitives 的 Modal / Button / Input / Switch / Tag / StateDot / Toast),不自己抄样式,所以暗色、Esc/点遮罩关闭、焦点行为、动效都与设置页其余部分一致;
  • 删除 / 移除文件夹 / 重命名 / 移动 / 复制不再用浏览器自带的 confirm / prompt:删除与移除是原生确认弹窗(危险操作用外壳的红字写法),重命名与复制是带输入框的弹窗,移动/复制列出可写根目录点选,成功提示是顶部那条会自动淡出的原生 Toast;
  • 写入过程中弹窗关不掉(遮罩、Esc、× 全部拦下),避免写到一半状态错乱;
  • 界面上的图标同样取自外壳图标集(IconPlusOutline16 / IconCheckOutline16 / IconWarningOutline16 / IconSkillOutline16):按钮走 Button 的 icon 通道,其余直接渲染图标节点;
  • 拿不到原语(或外壳版本里没有这个图标)时自动降级(退回浏览器弹窗 + 自带样式 + 原来的字形 + / ✓ / ✖ / ◈),老外壳上不会白屏;
  • 文案跟随外壳语言:注册 skill-manager 命名空间的 zh/en 两套字典(键集一致);ctx.locale 缺席时回落中文;
  • 客户端半体是 classic script、无构建步骤,并在 dsh.client.inject 里声明它依赖的设置页与侧栏 UI 包。

配置

设置命名空间 skill-manager(由宿主持久化),全部有默认值,不配也能用:

| 字段 | 默认 | 含义 | |---|---|---| | customSkillDirs | [] | 额外的技能根目录(对应官方 provider 的 customSkillDirs,rank 300)——只扫一层 | | deepSkillDirs | [] | 递归扫描的技能根(rank 300):向下最多 3 层找 SKILL.md,用于仓库式技能库 | | bundledSkillDir | '' | 捆绑技能根(rank 600,只读) | | projects | [] | 除了进程 cwd 之外还要管理的项目目录 | | inventory | true | 是否把管理的技能登记给 DSH(关掉则只在面板可见,模型加载不到) |

前两项就是设置页「自定义技能文件夹」卡片管理的两个列表(含每个文件夹的「一层 / 递归」开关);后三项没有图形入口,写在设置里即可,inventory 改完立即生效。

根目录解析规则与官方完全一致(rank 越小越优先):

| rank | source | 路径 | |---|---|---| | 100 | project-dsh | <projectRoot>/.dsh/skills | | 200 | project-agents | <projectRoot>/.agents/skills | | 300 | custom | 配置的 customSkillDirs / deepSkillDirs | | 400 | user-dsh | <dshHome>/skills(跳过 .system) | | 500 | user-agents | <agentsHome>/skills | | 600 | bundled | 配置的 bundledSkillDir(只读) |

projectRoot = 最近的含 .git 的祖先目录;没有就退回 cwd。bundledSkillDir 为空串时不会有第 6 个根。

模型工具 skill_manager

工具用 @deepseek-ai/dsh-tools 的 defineTool 定义(该包 DSH 自带,本包声明为 optional peer):下面这张表就是参数 schema 本身,同一份声明既编译成给模型看的 JSON Schema,也在 execute 之前做参数校验——类型或枚举不符的调用会在进入业务代码前被拒(未知 action 由校验层抛错,而不是靠 execute 里的兜底分支),声明与校验不会各写一份。

| action | 必填 | 说明 | |---|---|---| | list | — | 列出全部技能 + 诊断;status 可过滤 ok / warning / broken | | roots | — | 列出根目录、rank、是否可写、各有多少技能 | | read | path | 读一条技能的完整内容与诊断 | | create | name, description | 新建;可选 rootPath / kind / whenToUse / body / modelInvocable / userInvocable | | update | path | 改 description / whenToUse / body | | toggle | path, modelInvocable 或 userInvocable | 开关入口 | | rename | path, newName | 改名 + 同步 frontmatter | | move | path, targetRoot | 换根目录 | | copy | path, targetRoot | 复制(可给 newName) | | delete | path | 移入回收站 | | lint | — | 只列有问题的技能 | | dirs | — | 列出自定义技能文件夹(含「一层/递归」与技能数) | | addDir | path | 添加自定义技能文件夹;create: true 时目录不存在则创建;deep: true 递归扫描 | | removeDir | path | 从管理器移除(不改动磁盘文件) | | setDirDeep | path, deep | 就地切换某个已添加目录的扫描方式 |

前置要求

  • DSH ^0.2.1-alpha.2(package.json 的 engines.dsh;dsh-settings / dsh-tools 两个 peer 的范围相同);
  • Node ≥ 22(engines.node);
  • 装成 profile bundle(dsh.bundle.patch → cordis.patch.yml),并在 dsh.client.platform: web 的前端下使用——设置页整页与侧栏入口都只在 web 前端出现;
  • peer:@deepseek-ai/cordis ~4.0.5-alpha.1(必需)、@deepseek-ai/dsh-settings ^0.2.1-alpha.2(optional)、@deepseek-ai/dsh-tools ^0.2.1-alpha.2(optional);
  • 运行时依赖 @deepseek-ai/schemastery ~3.18.5-alpha.1(settings.register 的 schema 必须是可调用的 schemastery 对象,所以它是运行时依赖,锁文件里必须有;test/manifest.test.js 会核对);
  • settings 服务是硬依赖(inject = ['settings']),因为它提供可配置的技能根目录;
  • 不需要任何外部服务、端口、网络或 API key。

已知限制

  • 侧栏入口依赖外壳的 data-slot 锚点,锚点消失时 CSS 规则是静默空转的。外壳的 sidebar.footer.action 是一个 nowrap 的横向 flex 动作行,而 dsh-memery 这类邻座的入口用 width:100%(里面的按钮再向外出血 4px)占满整行,会把本插件的入口推到侧栏右边缘之外、只剩一截圆角边框。插件注入的规则让这一行可以换行:

    :has(> [data-slot="sidebar.footer.action"] > .dsh-skills-manager){flex-wrap:wrap}

    它从本插件注册进去的那个 slot 锚点(data-slot,渲染器给每个 slot 出的锚点)出发,并用 :has() 要求本插件确实在座,所以外壳自己的其它 *_footerActions 行(例如 user-questions 弹窗的按钮行)不受影响,也不依赖 CSS Module 的局部名——早期版本用的 [class*="_footerActions"] 全局片段选择器两个毛病都有,已弃用。代价是:锚点改名、或浏览器不认 :has(),规则就不命中,而且是静默的。缓解措施只在「入口真的被挤出动作行」时生效——入口挂载时会量一次几何,出界(或找不到锚点)就在浏览器控制台打一条 [dsh-skills-manager] 开头的警告;只要没被挤出界,规则失效不会有任何提示,入口看起来一切正常。

  • 面板读的是磁盘,不是注册表。注册表已经被 provider 过滤过一遍,用它做数据源会漏掉所有被跳过的坏技能——而那正是最需要被看到的东西。代价是面板可能列出 DSH 此刻还没加载的技能(提供者刷新延迟,界面会单独标出来);反过来,「面板里能看到」不等于「模型现在能用」。

  • 坏 frontmatter 的判定是复刻官方语法,不是调用官方实现。官方改语法(SKILL_NAME 正则、布尔拼写集合、遗留键黑名单)而本插件没跟上时,会出现「面板说没问题、DSH 还是跳过」或反之。判据都集中在 lib/logic.js,对齐的是当时的官方实现。

  • frontmatter 是「行级手术」,不是 YAML 引擎。只改正文时逐字节保留,改字段时只重写那一行;代价是本插件读不懂的 YAML 结构(多行块标量的复杂变体、锚点/别名、嵌套对象)不会被打平或规范化——它保持原样,某些结构下的字段编辑会做不了。

  • 写入边界是硬约束:路径段禁止 ..,点开头目录一律拒绝——想把技能放进 .system、.hidden 之类的目录做不到;bundled 根只读;所有写操作必须落在已解析出的可写根内。这些拒绝是设计目标,不是缺陷。

  • 单文件上限 2 MiB(MAX_SKILL_FILE_BYTES):超过这个大小的技能不会被读取,也不会被写入。

  • 并发保护是乐观锁。expectedText 不匹配(文件被外部改过)时拒绝覆盖;两个窗口在同一瞬间先后写时,后写者拿到的是新的 expectedText,所以它挡的是「外部改动」而不是「同时写入」。

  • HTTP API 的信任边界是「本机」:只接受回环来源且 Host 头指向本机,因此不能通过远程访问或反向代理访问管理界面。这是挡 DNS rebinding 的护栏,也是使用上的限制。

  • 原生目录选择器可能不存在:宿主没有 directoryPickerController 时 pickDir 返回 supported: false,界面提示手填路径——降级而已,不影响添加目录。

  • 回收站不自动清理:删除把文件移进 <root>/.trash/<时间戳>-<名字> 就结束,插件不清理、不做保留期;攒多了要自己删。

  • 没有文件 watcher:提供者只在写操作之后主动让 DSH 重扫目录缓存;你在编辑器/脚本里改了磁盘,面板要手动刷新才看得到,DSH 的目录也要等下一次写操作或它自己的重扫。

  • settings 不可用时功能缩水:settings.register 抛错时插件降级为「全部用默认配置」并记一条 warn(一个可选的技能管理器不该让整个 profile 起不来);此时改配置不生效,且设置页对只读配置会明确拒绝而不是静默失败。

开发

npm test        # node --test

测试分七层(共 162 项,全绿):

  • test/logic.test.js —— 纯逻辑(解析、校验、序列化保真、诊断、遮蔽);
  • test/store.test.js —— 真实临时目录上的 I/O(发现、写入保真、搬运、回收站、路径护栏);
  • test/deepscan.test.js —— 递归扫描(层级上限、跳过 node_modules/点目录、命中后不再下探);
  • test/provider.test.js —— 技能提供者(注册表契约、动态 inventory、未知 host / 失败不抛);
  • test/assembly.test.js —— 宿主装配自检(假 Cordis 上下文:路由、工具、参数校验、护栏、优雅降级、卸载);
  • test/client.test.js —— 客户端装配自检(假 __ModuleLoader__ 与假 UI 原语:注册 id、Slot 参数、样式、弹窗/Toast 走原生组件、拿不到原语时的降级路径、entryWrapWarning、纯函数);
  • test/manifest.test.js —— 依赖清单自检(锁文件必须记录 package.json 的每一个运行时依赖)。

Windows 上 node --test 会为每个测试文件起子进程;若运行在受限沙箱里会报 spawn EPERM,用 node --test --test-isolation=none 在同进程里跑即可。

代码结构

index.js            宿主半:settings 命名空间、/api/skills/manager、skill_manager 工具
client.js           客户端半:设置页整页 + 侧栏浮层(classic script,无构建步骤)
lib/logic.js        纯函数:frontmatter 解析/序列化/行级手术、校验、诊断、遮蔽、面板渲染
lib/roots.js        根目录解析与发现(对齐官方 rank 表、「只认一层」与递归扫描)
lib/store.js        受护栏保护的读写:路径校验、原子写、回收站、搬运
lib/provider.js     技能提供者:把管理器里的技能登记给 DSH(含 inventory 开关)
lib/schema.js       settings 命名空间 schema(必须是可调用的 schemastery 对象)
test/               node:test 单元测试 + 宿主/客户端装配自检
cordis.patch.yml    bundle patch:往组合树插入本插件的行

两个刻意的设计决定

为什么自己解析 frontmatter。 官方用 yaml 包,但那个包不在 web profile 的解析路径上,而本插件要在不新增依赖的前提下工作。更关键的是诊断:官方遇到坏 frontmatter 只 warn 然后静默跳过,本插件要把这些信息变成可展示的 issue。所以 lib/logic.js 实现了一个严格对齐官方语法的读取器(SKILL_NAME 正则、必填字段、frontmatterBoolean 的 8 种拼写、遗留键黑名单),并遵循「读不懂就不猜、原样保留」的原则。

数据源为什么是磁盘而不是 ctx.skills。 注册表只用来做交叉核对(「DSH 当前认这些」),不用来列表。

许可

MIT。完整文本见包内的 LICENSE。