跳过主要内容
F

dsh-onshelf

frogflaying/dsh-onshelf

扫描本地插件、体检收录规则、生成投稿材料,一键开 PR 到 awesome-dsh-plugin。

安装

dsh plugin --profile web add github:frogflaying/dsh-onshelf

README

上架台 · dsh-onshelf

把本地 DSH 插件一路送到 awesome-dsh-plugin: 扫描 → 体检 → 生成材料 → 上传截图 → 开 PR。

针对的是没有技术背景、只有 GitHub 账号的作者。五个步骤里,前三个完全不需要 token、不联网。

它解决什么

contributing.md 里的规则全写在散文里,没人读。这个插件把每一条变成检查项, 把"评审清单"变成"引导表单":

步骤做什么需要 token
① 扫描找出声明了 dsh.bundle 的本地插件目录;把只声明 dsh.client 的列为近失(CI 最常见的拒绝原因)否
② 体检按 contributing.md 逐条检查,能自动修的就修(补 dsh.bundle、生成 cordis.patch.yml、官方包挪到 peerDependencies、修预发布范围陷阱)否
③ 条目条目 YAML、中英双语描述、分类、screenshots.json 声明、PR 描述(截图本体在 ④)否
③.5 建仓上传建 GitHub 仓库 + 把整个插件目录推上去,并把仓库地址填回条目是
④ 截图添加(粘贴/拖入/选择)、预览、上传截图到你自己的插件仓库是
⑤ 提交fork → 建分支 → 提交 → 开 PR(已收录的插件走「更新」)是

能代做的就不再让人做(0.5.0)

之前 CI 有两条门槛,插件只是告诉你去 GitHub 手动处理:

门槛之前现在
缺 dsh-plugin topic让你去仓库页 About → Topics → 手输 → 保存一个按钮直接加上,提交时也会自动补
仓库不满 1 天只能等仍然只能等,但会算出「还差约 9 小时」

topic 是一次 API 调用(PUT /repos/{owner}/{repo}/topics),用建仓库时已经申请的 Administration 权限就能做。让人为了一个勾选手动跑一趟 GitHub,正是这个插件应该消灭的那类步骤。

两条原则没变:只改自己仓库(token 属于别人时直接拒绝),以及改了就说 (✓ 已加上 topic: dsh-plugin)。

推送前检查本机痕迹(0.5.0)

插件 README 在一台机器上写、在几千台机器上读,所以「安装路径」和「这台机器上有 267 个程序」 这类内容对读者没用,而 C:\Users\<你的用户名> 往往是真实姓名。

上传前会扫一遍将要推送的文本文件,命中三类:

  • home-path —— 硬编码的家目录,含用户名的那个
  • absolute-path —— 其它 X:\... 绝对路径
  • local-figure —— 「这台机器上有 N 个」这类只对本机成立的数字

它不阻止上传,只是在计划里列出来(文件:行 + 类型),让你自己判断——有些本地路径是开发说明里 合理存在的。二进制文件跳过,每个文件只读前 256 KB。

这一版之前的真实案例:README.md 里写了 C:\Users\<用户名>\.dsh\... 的 node 路径、 D:\DeepSeek\harness\... 的安装命令、以及「这台机器上是 267 个」。都改成了通用写法。

「PR 已创建」不等于「能过 CI」(0.4.1)

第一次真的提到上游 PR 时,界面显示的是这样一个绿框:

PR 已创建:https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/pull/8375 ! 仓库创建不满 1 天(0.66 天),CI 会拒绝。 ! 仓库缺少 dsh-plugin topic,CI 会拒绝。

PR 确实建成了,但绿色成功框里挂着两行红叹号,看起来像失败。这是展示的问题,不是提交的问题。

现在拆成两块:

  • 绿框只说成功,并明写「这是成功,不是报错」;
  • 红框单独说明「这个 PR 现在会被 CI 拒绝」,指出原因不在插件代码,而是两条仓库条件,并把它们列成编号清单。

两条条件的处理方式完全不同,所以警告文案也写清楚了:

条件怎么修多久生效
仓库不满 1 天等到点自动满足;还差几小时会算给你
缺 dsh-plugin topic仓库页右侧 About 齿轮 → Topics → 加 dsh-plugin立刻

两条都满足后,再点一次「创建 PR」推同一个分支,CI 就会重跑。0.4.0 起分支名稳定复用,所以不会变成第二个 PR。

界面上还有个「重新检查仓库状态」按钮,直接把当前年龄和 topic 状态读给你看,不用猜。

已收录之后再更新(0.4.0)

这里原来有个真实的坑:⑤ 遇到「条目已存在」直接拒绝,并让你自己去改,但没有告诉你该怎么改。而最常见的情形恰恰是「我发了新版本,要更新描述」。

现在分三种情况处理:

你的情况⑤ 会怎么做
第一次投稿新增 data/plugins/<owner>__<repo>.yml,开 PR
已在列表里、内容有变化判为更新:改现有文件、开 PR,标题是 Update …,正文列出逐行改动
已在列表里、内容没变直接拦住,不给你开一个空 PR
上次的 PR 还开着找到那个分支,推送上去让 PR 自动更新,不新开第二个

两个配套改动:

  • 分支名不再带版本号。以前是 add-owner__repo-v0.1.0,每发一版就是一个新分支 + 新 PR,对维护者是噪音。现在固定 add-owner__repo,同一个插件永远复用同一分支。
  • 旧版本留下的 -v<版本> 分支仍然能被找到(升级后第一次更新不会丢失之前的 PR)。

「先演练一遍」会明确告诉你这次是 新增条目 还是 更新已有条目,以及改了几行。

③ 那两个产物是干嘛的

③ 点「生成投稿材料」会出现两块东西,它们不是表单,不需要你填:

产物是什么谁用它
条目文件 data/plugins/owner__repo.yml就是这个插件在 awesome 列表里的那一行⑤ 把它提交进 awesome-dsh-plugin 仓库
PR 描述那个 PR 的正文⑤ 自动填进 PR 的 body

所以 ③ 只是让你先看一眼将要提交什么。点「复制」有两个用途: 确认内容没问题;或者你不想提供 token,就照着它自己在 GitHub 上提 PR——效果完全一样。

截图可以直接粘贴 / 拖进来(0.3.0)

原来只能手写相对路径:你得先把图存到 assets/、找到它、再照着敲一遍。这对小白是多出来的一步。

现在「截图路径」上方有一个拖放区,三种方式都行:

  • 粘贴:鼠标点一下那个框,Ctrl+V
  • 拖入:把图片文件拖进去
  • 选择:点「选择图片」,或点整个区域

图片会由宿主写进插件的 assets/ 目录,并把路径自动填进草稿。已加的图会显示缩略图和删除按钮。

几个实现上的取舍:

  • 浏览器给的是字节,不是路径——粘贴的截图在磁盘上根本不存在,所以必须由宿主写文件。
  • 文件名保留你的(main-window.png 就是 main-window.png),重名才加 -1; 剪贴板那种 image.png 会改成 screenshot-20261020103559.png,否则第二次粘贴就覆盖了第一次。
  • 扩展名以真实文件名为准,没有才看 MIME type。
  • 路径不能跳出插件目录,也不能写进 node_modules——../outside 这种直接 400 拒绝, 而不是"清理掉 .. 然后写到别处"。这个边界是上一轮实现时漏掉的,测试抓到了。
  • 缩略图走插件自己的 /shot 路由:图片在插件的目录里,不在 web 根下,不这么走浏览器读不到。

0.3.1:③ 和 ④ 曾经各说各话

在 ③ 贴了截图,④ 却提示「没有截图可上传」。原因是同一件事有两个答案:

  • ③ 把截图写进草稿(~/.dsh/onshelf/drafts.json);
  • ④ 读的是插件目录里的 screenshots.json。

而 screenshots.json 之前只在点「生成投稿材料」时可能产生,贴图那一刻并没有写。于是草稿里有、磁盘上没有,④ 当然看不到。

现在草稿是唯一事实来源,任何改动都会同步写出 screenshots.json:

  • 贴图 / 拖图 → 立刻写;
  • 手动编辑「截图路径」→ 立刻写;
  • 打开 ③ 时如果发现旧草稿有图而磁盘没声明 → 自动补齐(老用户不需要重做)。

④ 也跟着改了:

  • 列出将要上传的截图缩略图和数量,不再是黑箱;
  • 没有截图时直接给出粘贴/拖放区,不用退回 ③;
  • 一张截图都没有时上传按钮是禁用的,而不是点下去才报错。

分类为什么是 ui(0.2.4)

两件事要分开看:

  • 下拉框里的值可能是你早先手选的。手动选过的分类不会被自动推荐覆盖(这是对的,否则你改一下描述推荐就把你的选择冲掉了)。0.2.4 起界面会明说这一点,并给一个「改用推荐的分类」按钮。
  • 自动推荐本身也错了。早先的规则是「带浏览器半身 ⇒ ui」,于是这个操作 Windows 桌面的插件被推荐成了 ui。0.2.4 起先看功能信号(desktop / file / folder / process…),dsh-desktop-organizer 现在推荐 tools。

顺带修了一个同类问题:把分类或描述清空时,?? 会保留空字符串而不回落到草稿,界面就剩一个空下拉框。现在空值会回落到推荐/草稿。

contributing.md 的原话:分类选不准不会被打回,维护者会直接改。所以这两个都合规,tools 更贴合。

建仓上传(0.2.0)

第一版把你自己的代码当成"你的事":让你去 GitHub 建仓库、推代码,再回来填仓库地址。 这正是"只有 GitHub 账号、不会用 git"的人做不到的一步,也是这个插件的立身之本。现在它做完了:

  • 仓库不存在 → 按你给的名字新建(默认公开,因为 awesome 列表只收公开仓库);
  • 仓库存在但为空 → 直接推;
  • 仓库已有提交 → 默认拒绝,必须你明确勾选覆盖,不会悄悄替换你的历史;
  • 先写 repository 字段再推送,所以仓库的第一个提交里就有它;
  • 推完把仓库地址填回 ③ 的条目,repository-missing 这条警告随之消失。

推什么:整个插件目录,但 node_modules、.git、.dsh、各种备份与系统垃圾不传 (.github 保留),并且遵守插件自己的 .gitignore:运行产物(会话快照、缓存、本机配置覆盖) 通常只写在 .gitignore 里,那正是作者本人说"这些不是源码"的地方,工具不替你绕过它。 有上限:单文件 5 MB、总计 20 MB、最多 400 个文件——点错到放视频的文件夹也不会把仓库塞爆。

先看再传:点「先看一眼要上传什么」会列出仓库名、公开/私有、文件数、顶层目录、以及被跳过的文件, 按 .gitignore 排除掉的文件也会算在一起告诉你,确认后才真正写入。

用到的 GitHub 能力:Contents: read/write(推代码)+ Administration: read/write(建仓库)。

0.2.2 修的两个问题

空仓库被当成"有历史"(真 bug,用户实际踩到)。 GitHub 对没有提交的仓库,从 git-data 端点返回的是 409 "Git Repository is empty",不是 404。 原来只把 404 当空仓库,于是新建的仓库被判定为"已有提交",弹出一个不该有的覆盖确认, 最终提交里 parents: [null] 被 API 拒绝——仓库一直是空的,界面也没说清楚。 现在 409/404 都算空,并且"是不是根提交"改成看有没有 commit sha,而不是只看 empty 标志, 这样即使判断错了也不会生成 parents: [null]。

上传没有任何反馈(体验 bug)。 上传是一个文件一个请求,十几秒里按钮毫无变化,用户无法判断成功还是失败。 现在改成后台任务:

  • 按钮下方出现进度条和「已上传 N / M 个文件」;
  • 阶段文字会变:检查仓库 → 创建仓库 → 写入 repository 字段 → 读取文件 → 上传;
  • 失败信息就出现在点下去的那个按钮下面,不再是页面顶部一条容易被忽略的横幅;
  • 成功会给两个可点的链接:仓库地址和这次提交的 commit;
  • 顺带把 blob 上传改成 4 并发,14 个文件的插件快了不少。

0.2.3:空仓库根本不能直接写 blob

0.2.2 只修了"读取 ref 时把 409 当空仓库",但写入路径同样会撞 409: 在一个还没有任何提交的仓库里,/git/blobs、/git/trees、/git/commits、/git/refs 全部返回 409 Git Repository is empty。所以真实的报错是:

✗ 上传 index.js 失败:Git Repository is empty.

正确做法是:空仓库只能从 Contents API 进门,因为那个端点会替你提交。 现在的流程是:

  1. 用 Contents API 提交第一个文件(优先 package.json)——这一步同时创建了分支;
  2. 仓库于是有了一个提交,git-data 端点开始可用;
  3. 其余文件走正常的 blob → tree → commit → 更新分支。

代价是两次提交而不是一次(很便宜的代价),换来的是空仓库真的能推上去。 另外 commitFiles(④ 上传截图走的路)遇到 409 时会直接说人话: 「仓库还没有任何提交,先上传一次代码再来这一步」,而不是甩出 GitHub 的原文。

三条设计原则

1. 描述只写代码里能读到的事实。 生成器只声明它在 package.json 和入口文件里实际观察到的东西(注册了几个工具、命令、UI 挂载点), 不发明能力。每条可核实的声明都会单独列出来给你确认——因为 contributing.md 说描述会被对着代码核对。

2. 不夸大,也不替你决定事实。 机械性的形状问题(缺字段、patch 文件形状不对、官方包放错依赖块)才自动修。 描述、分类、截图这些"关于插件的事实"永远只是草稿。

3. 没有 token 也能走完 ③。 第 ③ 步的产物(YAML + PR 描述)就是完整的投稿内容,复制走自己提 PR 效果完全一样。 token 只存在会话内存里,不落盘、不写日志。

体检里的每个「→」都是真按钮(0.1.3 已修)

第一版有个明显的毛病:→ 按功能自动推荐 之类是纯文本,看着像按钮、点了没反应; 而且大部分问题根本没写修复实现,所以「一键修复」按钮压根不出现。

现在每条 finding 都带一个动作,由规则决定、UI 照做,两者不可能再漂移:

动作类型适用做了什么
file缺 dsh.bundle、patch 形状不对、官方包放错依赖块、预发布范围改 package.json / cordis.patch.yml,改前备份,可撤销
draft分类没选、缺英文描述、描述含营销词、缺中文描述直接填进 ③ 的草稿(分类推荐、按代码生成描述、去营销词、补句号)
manual缺 github topic、仓库不满 1 天、repository 字段、截图只给一句"你要做什么",不假装能自动修

「一键修复」= 先跑 file 修复,再跑所有 draft 动作,最多三轮(填完描述可能顺带解决几条)。 返回结果会明确说"改了 N 个文件、填了 N 项,还有 M 条只能手动"。

实测一个真实插件(dsh-desktop-organizer 副本):2 个错误 → 0 个,只剩 1 条手动项。

英文描述曾经是中文(0.1.3 已修)

description.en 直接取了 package.json#description,而中文插件的那个字段是中文—— 结果"英文描述"里全是中文。这是会被打回的形状。

现在按语言分流:

  • 每个来源先判语言(有 CJK 就不是英文),英文行只用英文来源;
  • 没有英文来源时,用代码能证明的事实拼一句:Desktop organizer for DeepSeek Harness. It registers 1 agent tool (desktop_organize).
  • 这句会标 needsTranslation,界面提示"这是推导出来的,请核对后改写"——不假装它是你写的英文;
  • 中文行只用中文来源;没有就留空(contributing.md 说缺翻译是维护者的活)。
  • 另外新增一条检查:en 字段里出现中文直接报错,并给"改成一句英文事实陈述"的按钮。

扫描目录

默认扫描本插件所在的上一级目录(本地插件通常放在一起),可以在向导里「修改扫描目录」增删。 插件不知道你的项目放在哪里,所以这个默认值只是猜测,界面上会标明它来自哪里。

两条容易踩的坑,0.1.1 已修:

  • link: 装的本插件是 junction/symlink,readdirSync 对它们报 isDirectory() === false。 只按 isDirectory() 下行会把你自己开发的插件全部漏掉。现在会跟随链接、按真实源码路径上报、按真实路径去重、并防环。
  • 默认根目录曾经指向 ~/.dsh/profiles/<name>,那是已安装插件的地方, 结果列表里全是别人的市场插件,你自己的一个都没有。安装目录里的第三方包会标「已安装」并排在你自己的插件后面。

扫描为什么"卡在加载中"(0.1.2 已修)

实测本机扫描 12ms、体检 3ms,代码从来都不慢。真正的原因有两个:

  • 标签页永远可点,但 ②–⑤ 步在无数据时都渲染 加载中…。 没选插件时 check 是 null,模板就把"没有数据"显示成"正在加载",看上去永远卡住。 现在每一步都有明确的空状态("先在 ① 扫描里选一个插件")。
  • 一次请求说完所有事,中间没有任何反馈。现在扫描是后台任务:/scan/start 立即返回, 前端轮询 /scan/status 显示「已扫描目录数 / 已找到候选 / 耗时」和进度条。

另外做了三处提速:

改动效果
只在遇到链接时 statSync,普通目录直接用 Dirent每个条目少一次系统调用,本机 59ms → 12ms
扫描器每 64 个目录让出事件循环大目录不再冻结宿主,进度请求能返回
重新打开设置页先读 /scan/cached秒开,只有冷启动才真正扫盘

还加了两个保护:目录数上限 20000(指向 C:\ 会返回截断结果而不是永挂),以及前端 60s 请求超时(真卡住会报错而不是无声转圈)。

安装

dsh plugin --profile desktop add link:D:/DeepSeek/harness/dsh-onshelf

重启 DSH 后,设置 → 上架台。

作为 agent 工具

模型也可以直接调用:

onshelf action="scan"
onshelf action="check" dir="D:/path/to/your-plugin"
onshelf action="fix" dir="..." dryRun=true
onshelf action="prepare" dir="..." category="tools" repoUrl="you/repo"
onshelf action="submit" dir="..." dryRun=true

结构

index.js              host 半身:agent 工具 + /api/onshelf/* 路由
client.js             浏览器半身:设置页向导(无构建,仅依赖 shell 提供的 react)
lib/scan.js           ① 目录扫描与 manifest 解析
lib/rules.js          ② 规则引擎——contributing.md 的逐条转写
lib/check.js          ② 诊断与可撤销的自动修复
lib/entry.js          ③ 投稿材料生成与校验
lib/github.js         ③.5④⑤ GitHub REST(建仓 / 推目录 / 截图 / fork / commit / PR),token 不落盘`nlib/upload.js         ③.5 收集待上传文件(忽略规则 + 上限)
test/                 282 项测试 + 真实机器冒烟脚本

lib/rules.js 是唯一允许存放规则的地方。上游指南变了,只改这一个文件。

测试

node test/all.mjs          # 全部 282 项
node test/smoke-real.mjs   # 扫本机自己的插件(依赖环境,不属于测试套件)

已知限制

  • latest/download/ 的 tarball 陷阱会被检测并警告,但修不了——那是发布侧的问题。
  • 分类只是推荐,contributing.md 明确说选不准不会被打回,维护者会改。
  • 仓库年龄 1 天门槛和 dsh-plugin topic 需要 token 才能读到真实值,没 token 时这两项不检查。
  • 第 ⑤ 步创建的 PR 不会自动合并,也不是安全审查——收录不等于背书。

License

MIT

相关插件