🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

技能管理页 unera/dsh-skills

DeepSeek Harness
unera12 天前有提交已提供中文文档扫描:含敏感能力

在 Web 界面里查看、编辑、上传和下载技能文件

29/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/11/2 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add unera/dsh-skills
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:仅索引
是什么[目录]—
dsh 原生插件 · market
暂无时间戳·判据 →
装得上吗[实装验证]—
本站尚未做安装检查
暂无时间戳·判据 →
安全吗[自动扫描]—
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
数据截至 2026/11/03·判据 →
还在维护吗[仓库]✓
活跃:最近一次提交在 12 天前
暂无时间戳·判据 →
图例:✓ 通过! 需注意— 未判定 / 无数据· 「未判定」= 本站尚无数据,不代表插件有问题
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →

可信度证据

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

读写/删除本地文件发起外部网络请求
查看扫描证据(共 43 处,展示前 35 条)
  • 写入本地文件
    lib/index.js:32
    mkdirSync(skillsDirAbsolute, { recursive: true });
  • 发起网络请求
    lib/client.js:277
    response = await fetch(`${API_PREFIX}${path}`, init);
  • 读取本地文件
    lib/host/catalog.js:19
    if (existsSync(manifest)) {
  • 读取本地文件
    lib/host/catalog.js:21
    const parsed = JSON.parse(readFileSync(manifest, "utf8"));
  • 读取本地文件
    lib/host/catalog.js:39
    for (const name of readdirSync(profiles).sort()) {
  • 读取本地文件
    lib/host/catalog.js:57
    const parsed = JSON.parse(readFileSync(manifest, "utf8"));
  • 读取本地文件
    lib/host/catalog.js:65
    entries = readdirSync(nodeModules).sort();
  • 读取本地文件
    lib/host/catalog.js:74
    scoped = readdirSync(join(nodeModules, entry)).sort();
  • 读取本地文件
    lib/host/paths.js:135
    stats = lstatSync(path);
  • 读取本地文件
    lib/host/paths.js:146
    return readdirSync(path);
  • 读取本地文件
    lib/host/scan.js:48
    const stats = lstatSync(file);
  • 读取本地文件
    lib/host/scan.js:75
    names = readdirSync(absolute).sort();
  • 读取本地文件
    lib/host/scan.js:107
    const stats = lstatSync(join(absolute, fileName));
  • 读取本地文件
    lib/host/scan.js:134
    const mode = statSync(target).mode & 511;
  • 写入本地文件
    lib/host/scan.js:135
    writeFileSync(temporary, content, { mode });
  • 写入本地文件
    lib/host/scan.js:136
    renameSync(temporary, target);
  • 读取本地文件
    lib/host/scan.js:137
    return { fileName, mtimeMs: Math.round(statSync(target).mtimeMs) };
  • 删除本地文件
    lib/host/scan.js:139
    rmSync(temporary, { force: true });
  • 写入本地文件
    lib/host/scan.js:159
    renameSync(source, target);
  • 写入本地文件
    lib/host/scan.js:186
    renameSync(source, target);
  • 读取本地文件
    lib/host/scan.js:201
    const names = readdirSync(path).sort();
  • 写入本地文件
    lib/host/scan.js:227
    mkdirSync(path, { recursive: false, mode: 448 });
  • 删除本地文件递归
    lib/host/scan.js:231
    rmSync(path, { recursive: true, force: true });
  • 写入本地文件
    lib/host/upload.js:110
    mkdirSync(target, { recursive: false, mode: 448 });
  • 写入本地文件
    lib/host/upload.js:119
    mkdirSync(destination, { recursive: true, mode: entry.mode });
  • 写入本地文件
    lib/host/upload.js:122
    mkdirSync(join(destination, ".."), { recursive: true });
  • 读取本地文件
    lib/host/upload.js:123
    if (existsSync(destination) && lstatSync(destination).isDirectory()) {
  • 读取本地文件
    lib/host/upload.js:123
    if (existsSync(destination) && lstatSync(destination).isDirectory()) {
  • 写入本地文件
    lib/host/upload.js:126
    writeFileSync(destination, entry.data, { mode: entry.mode });
  • 写入本地文件
    lib/host/upload.js:136
    renameSync(live, aside);
  • 写入本地文件
    lib/host/upload.js:138
    renameSync(unpacked, live);
  • 读取本地文件
    lib/host/upload.js:178
    const mode = memberKind(target) === "file" ? lstatSync(target).mode & 511 : 420;
  • 删除本地文件
    lib/host/upload.js:183
    rmSync(temporary, { force: true });
  • 读取本地文件
    lib/host/upload.js:195
    for (const name of readdirSync(absolute).sort()) {
  • 读取本地文件
    lib/host/upload.js:198
    const stats = lstatSync(child);
扫描于 2026/11/3(1 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

社区实装还没有人打卡 · 装过的第一个就是你
依赖的 DSH / Cordis 模块
@deepseek-ai/dsh-host-webserver
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

README

由 DeepSeek 最新模型翻译生成
@dr-co/dsh-skills 为 DeepSeek Harness Web GUI 提供技能页:侧边栏多一行,点开是完整主面板,列出宿主知道的所有技能——我们自己的、已安装包内置的、插件不靠文件提供的,全都列出来,并内置下载、上传、重命名、启用/禁用和编辑功能。 页面自己读文件系统,不只问 ctx.skills,因为那个服务只报告它能加载的技能。文件名是 _SKILL.md 的技能,核心故意看不见——而技能停止工作时,你恰恰需要看到它。 功能 列表分三段,每段有自己的标题、计数和空状态: | 分区 | 来源 | 颜色 | 操作 | | $DSH_HOME/skills 中的标准技能 | 我们自己的目录 | 启用时绿点,禁用时灰点,名称用常规颜色 | 全部操作:查看、编辑、重命名、上传、两种下载、启用/禁用 | | 插件内置技能 | 每个已安装包的 <pkg>/skills/ 和 <pkg>/assets/,通过各自的 manifest 找到 | 启用时粉点和粉色名称,禁用时灰粉色 | 查看和两种下载——它们住在 node_modules 里 | | 插件提供(无文件) | ctx.skills.list() 中磁盘上没有文件的条目 | 粉色,虚线圆点 | 查看和 Download SKILL.md;没有 ZIP,因为没有目录可打包 |
展开完整文档(README 剩余 1 节)
@dr-co/dsh-skills

为 DeepSeek Harness Web GUI 提供技能页:侧边栏多一行,点开是完整主面板,列出宿主知道的所有技能——我们自己的、已安装包内置的、插件不靠文件提供的,全都列出来,并内置下载、上传、重命名、启用/禁用和编辑功能。

页面自己读文件系统,不只问 ctx.skills,因为那个服务只报告它能加载的技能。文件名是 _SKILL.md 的技能,核心故意看不见——而技能停止工作时,你恰恰需要看到它。

功能

列表分三段,每段有自己的标题、计数和空状态:

| 分区 | 来源 | 颜色 | 操作 |
| $DSH_HOME/skills 中的标准技能 | 我们自己的目录 | 启用时绿点,禁用时灰点,名称用常规颜色 | 全部操作:查看、编辑、重命名、上传、两种下载、启用/禁用 |
| 插件内置技能 | 每个已安装包的 <pkg>/skills/ 和 <pkg>/assets/,通过各自的 manifest 找到 | 启用时粉点和粉色名称,禁用时灰粉色 | 查看和两种下载——它们住在 node_modules 里 |
| 插件提供(无文件) | ctx.skills.list() 中磁盘上没有文件的条目 | 粉色,虚线圆点 | 查看和 Download SKILL.md;没有 ZIP,因为没有目录可打包 |

- 状态是一个 11px 的圆点:SKILL.md 为绿色,_SKILL.md(我们自己的)为灰色;已启用的打包技能为粉色,禁用的为灰粉色。目录里两个文件都有,报告为冲突,用红色。状态文字放在圆点的 tooltip 和无障碍名称里,不占行内空间。
- 名称占满行内剩余的全部像素,下面一行是 frontmatter 里的 description;两者都用省略号截断,绝不换行。
- 路径在 $DSH_HOME 内时显示相对路径,否则相对包路径;15 个字符以内完整显示,超出则显示前 7 个和后 7 个字符,中间三个点(split/p...ath/foo)。完整路径在 tooltip 里。宽度低于 640px 时整列不渲染,路径改放在行的无障碍名称里。
- 查看——每行菜单的第一项,三个分区都有。它打开和编辑器相同的整页框架,但只读:原始文本,没有 textarea,没有 Save,带 Read-only 徽标,Close 返回列表,技能可编辑时旁边还有 Edit。不做任何渲染——不渲染 markdown,不高亮——也不能从中写入任何内容。插件技能的文本来自 ctx.skills.get(name);如果返回空内容,页面显示content is not available for this skill,而不是一片空白。
- 行菜单 — 依次为 View、Edit、Rename、Download SKILL.md、Download ZIP、Upload、Enable/Disable,按组分隔。宽屏下第一个操作也能从拆分按钮进入:铅笔图标打开编辑器,旁边的箭头展开菜单。窄屏下只有一个 ⋯,Edit 是第一项。
- Edit — 面板内的整页视图(不是对话框,也不是 URL):一个纯等宽 textarea 铺满面板,底部是 Cancel 和 Save。任何宽度下都没有预览。没有改动前 Save 一直禁用;打开后磁盘上的文件若发生变化,首次保存前会弹出警告,再按一次即覆盖。
- Rename — 重命名技能的目录(绝不改 frontmatter 里的 name),行内完成,预填当前名称,校验 kebab-case,在同一父目录内原子执行 rename()。名称已被占用则拒绝,失败时原目录保持不变。
- Enable / Disable — harness 自带的开关:SKILL.md → _SKILL.md,再切回来。重命名后该行就地更新(圆点、路径、菜单),并提示磁盘上实际发生了什么:File SKILL.md renamed to _SKILL.md。
- Download SKILL.md — 原样文件字节,Content-Disposition: attachment。
- Download ZIP — 整个技能目录,以自身名称作为根,包含附件(bin/、data/ 及其他内容),因此压缩包可以原样上传回来。
- 英语和俄语 — 整页覆盖,包括提示和错误消息。俄语是完整词典,通过 ctx.locale.addLanguage({ id: 'ru', label: 'Русский', fallback: 'en' }) 注册;句子中间不会回退到英语。

颜色与主题

绿色和灰色用外壳自己的 token(开启用 --dsw-alias-state-success-primary,关闭用 --dsw-alias-state-idle-primary),因此在所有主题下都自动与界面其余部分一致。粉色不在 token 集合里,所以插件定义了两个自己的变量,并给两套配色分别赋值:

| 配色 | --dshsk-plugin-accent | 面板上的对比度 | --dshsk-plugin-accent-muted | 对比度 |
| 浅色(#ffffff) | #c2185b | 5.87:1 | #8d5f72 | 5.23:1 |
| 深色(#232324) | #f472b6 | 5.93:1 | #c98ba6 | 5.78:1 |

两者都满足文字 4.5:1 的门槛和圆点 3:1 的门槛,也都高于外壳绿色配白色的对比度(2.28:1)。深色块依据 body[data-ds-dark-theme] 判断——这是外壳自己的主题呈现器应用快照时写入的属性——而不是依据 prefers-color-scheme,因为最终生效的配色跟随用户设置(包括「跟随系统」深色和已注册主题),而不是操作系统。测试会从 src/client/styles.js 里的值重新计算这两个比值。

两种上传,两套约定

页面按钮和行菜单不是同一种操作,这个区别是刻意设计的:误拖放不该覆盖掉正在使用的技能。

| 上传方式 | 接受的内容 | 名称来源 | 名称已被占用时 |
| 页面按钮(新建) | 只接受 ZIP,且其中只有一个顶层文件夹,内含 SKILL.md | 文件夹名(kebab-case) | 拒绝:a skill with this name already exists —— 请改用行菜单替换 |
| 行菜单(替换) | 单个 SKILL.md,或 ZIP 且其文件夹名就是该技能自己的名称 | 该行 | ——(这是唯一的替换方式) |

新建技能要用 ZIP,其顶层文件夹名就是技能名;单个 SKILL.md 只在替换已有技能时才接受。其余情况一律拒绝,报 Unrecognized skill format(«Нераспознаваемый формат скилла»),磁盘上不会有任何改动。这些情况包括:有多个顶层条目、SKILL.md 位于压缩包根目录、压缩包里没有 SKILL.md、文件夹名不合法、压缩包损坏、新建上传传了非 ZIP 文件、替换上传传了不叫 SKILL.md 的文件。

安全措施按执行顺序如下:每个路径都会先解析,并验证其确实留在根目录内(路径穿越片段、绝对路径、盘符和开头的点,都会在文件系统看到它们之前被拒绝);技能目录及其文件必须是真实存在的 —— 符号链接的 SKILL.md 会被列表跳过,永远不会被读取、写入或打包;打包好的技能,任何写入都会被宿主拒绝(403 read-only),而不只是浏览器里藏起来;上传有大小上限;ZIP 会解压到以点开头的暂存目录,等每个成员都校验通过后,才通过重命名换入,换入失败则恢复原目录;压缩包里的文件权限会保留。被拒绝的上传会让目录树保持逐字节不变,测试断言的正是这一点。

安装

包内自带 dsh.bundle 层,所以 dsh plugin add 会同时安装并挂载它;之后它会出现在设置里的插件列表中,可以在那里关闭或移除。

dsh plugin --profile <profile> add @dr-co/dsh-skills

从代码检出开发时:

npm install
npm run build
dsh plugin --profile <profile> add file:$PWD

之后重启 Web GUI。侧边栏会在 Plugins 和 Tasks 之后多出一行 Skills。

配置

- id: dsh-skills
name: '@dr-co/dsh-skills'
config:
dshHome: /home/dsh/work/ai/.dsh # default: $DSH_HOME, then ~/.dsh
dshInstall: /opt/dsh # default: found from the running CLI
skillsDir: skills # our own skills, relative to the home
maxUploadBytes: 8388608 # default 8 MiB
主目录遵循 harness 自身的优先级:先看显式指定的 dshHome,再看 $DSH_HOME,最后是 ~/.dsh;开头的 ~ 会展开。$DSH_HOME 为空时按未设置处理,所以空变量绝不会把路径解析成工作目录。查找安装位置时,从正在运行的 dsh 入口点向上走,找到清单名为 @deepseek-ai/dsh 的包;在它的 node_modules 和每个配置档的 node_modules 里,只从清单读取包(绝不进入包自己的 node_modules)。打包的技能缓存 30 秒——除非安装,否则运行中的宿主下它们不会变——而我们自己的技能始终实时读取。

宿主 API

一个前缀路由 /dsh-skills/api,返回 JSON,其中 code 由客户端本地化:

| 方法 | 路径 | 用途 |
| GET | /skills | 主目录、根目录,以及每一行及其 actions |
| GET | /skill/<id> | 文本:文件内容,或来自 ctx.skills.get() 的服务商技能正文 |
| PUT | /skill/<id> | {content} → 原子写入,保留模式和状态 |
| POST | /skill/<id>/enable · /disable | 重命名文件,即 harness 自带的开关 |
| POST | /skill/<id>/rename | {name} → 在其父目录内重命名目录 |
| GET | /skill/<id>/download.md | 精确的文件字节 |
| GET | /skill/<id>/download.zip | 整个目录,以目录名为根 |
| POST | /upload?mode=create | 一个 ZIP,用于创建新技能 |
| POST | /upload?mode=replace&id=<id> | 一个文件或 ZIP,用于替换该行的技能 |

行 id 是 <origin>:<rootKey>:<relative path>(user:home:skills/foo),每次调用都针对实时目录解析——目录里没有的 id 就是 not-found,绝不会变成可读取的路径。

错误码:unrecognized-format(415)、name-mismatch(409)、already-exists(409)、conflict(409)、read-only(403)、not-found(404)、too-large(413)、bad-request(400)、write-failed(500)。英文 error 字段是诊断信息;页面按错误码显示自己的语言。

访问控制归部署方,不归插件。 这些是 Web GUI 自己监听器上的普通同源路由:只要能访问 GUI,就能列出、读取、下载、保存和上传技能。这与 GUI 已经授予的权限一致(有 shell 访问权的会话),而且 harness 只绑定一个接口;但公开暴露 GUI 的部署方应该在整个源前面加上自己的认证——插件不添加任何认证。

平台说明(插件做不到的事)

以下内容针对已安装的 harness(0.2.1-rc.2)验证,不是偏好:
- 侧边栏不能靠加 slot 来扩展。 sidebar slot 是 kind: 'single',已被 shell 自己的 SidebarRoot 占用。支持的扩展点是它声明的列表 slot sidebar.panellist(SidebarPanelIconOwnerProps { size, active }),条目的 id 必须等于 main slot 的 key(MainPanelId)。本插件注册的正是这个,另外还注册了 ctx.slots.register({ name: 'main', key: 'dsh-skills', locale: NS }, SkillsPage) —— 一个真正的主面板,不是对话框。面板切换用 shell 的 ctx.layout.selectPanel(id)。图标以内联方式绘制,尺寸 24×24,stroke="currentColor"、stroke-width="1.75"、fill="none",圆角连接,并遵循 shell 传入的 size。
- 没有可导入的共享 markdown 渲染器。 客户端模块表是固定的(react、react/jsx-runtime、react-dom、react-dom/client、@deepseek-ai/cordis、zod、@tanstack/react-virtual、@deepseek-ai/dsh-client-store、@deepseek-ai/dsh-client-ui-slots、@deepseek-ai/dsh-client-ui-primitives、@deepseek-ai/dsh-client-ui-dockkit);Shiki 和 harness 的 markdown 组件都在 shell bundle 内部,不对外导出。本版本没有预览,所以插件干脆不带渲染器 —— 早先的草稿自带一个,原因正在于此。
- 没有 split-button 基础组件。 @deepseek-ai/dsh-client-ui-primitives 提供 Button、值类型的 SegmentedControl 和菜单界面 —— 没有那种「一个控件带第二个动作」的东西。这里的拆分是两个 元素放在同一个带边框容器里,中间一条细线。
- 插件没有用于 bundle 包装的构建插件。 页面加载 bundle 时,把它当作脚本执行 window.__ModuleLoader__.load({ id, factory });harness 用一个内部 tsdown 插件生成这层包装,该插件未发布。scripts/build-client.mjs 用 esbuild 加一小段包装代码得到同样结果。bundle 只依赖 react,模块表里有。
- 宿主侧是 TypeScript,按已发布的 @deepseek-ai/dsh-host-webserver 类型做类型检查。浏览器侧是纯 ESM JavaScript:slot 和 locale 服务在这个宿主上没有已发布的类型包,所以只保留已发布 bundle 明确接受的部分,并用测试驱动真实组件来覆盖。

开发

npm install
npm run typecheck # tsc --noEmit over the host half
npm test # builds both halves, then runs the node:test suites
npm run build # lib/index.js + lib/host/*.js, and the wrapped lib/client.js
测试(99 项):frontmatter 解析;自有扫描与打包扫描,包括双文件冲突和符号链接规则;ZIP 读取器与写入器;两种上传契约,每次拒绝后目录树字节完全一致;目录的三种来源、去重与展示路径;经由真实 HTTP 服务器的路由,包括 read-only、already-exists、conflict 和重命名;以及客户端——三个区块、点号与名称配色、菜单顺序、拆分控件与单一控件、只读视图(无 textarea、无 Save、Close/Edit、内容为空)、启用/停用提示、带校验的重命名与就地更新、无预览的编辑器、图标,以及两本词典。

License

MIT。
预览分享卡片长什么样 ↓unera/dsh-skills
分享 · DeepSeek Harness Hub
unera/dsh-skills

在 Web 界面里查看、编辑、上传和下载技能文件

这个插件是你维护的吗?
作者本人可认领并核对本条目;认领通过后,详情页会显示「作者已认领」标记。
认领条目 →
回到顶部