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

插件合集 dennisrongo/dsh-plugins

DeepSeek Harness
dennisrongo48 天前有提交已提供中文文档扫描:高危

为 dsh 提供跨 CLI 与桌面端即装即用的插件集合

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

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

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

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:需留意
是什么[目录]—
dsh 原生插件 · tool
暂无时间戳·判据 →
装得上吗[实装验证]!
实装验证未通过(unknown),装前请到仓库确认最近更新与 issue
L4 · 真实安装等级说明 →
数据截至 2026/10/02·判据 →
安全吗[自动扫描]—
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
数据截至 2026/11/03·判据 →
还在维护吗[仓库]—
更新放缓:最近一次提交在 48 天前
暂无时间戳·判据 →
图例:✓ 通过! 需注意— 未判定 / 无数据· 「未判定」= 本站尚无数据,不代表插件有问题
档位由哪些信号合成

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

验证判据与等级说明 →

可信度证据

⚠︎ 实装验证未通过(unknown · 2026/10/2) ——可能是验证环境差异,装前建议到 GitHub 仓库确认最近更新与 issue。
🔴 安全扫描发现高危能力自动 · 静态分析源码
v2.7.0
🔴 扫描:高危尚无人工评估

静态扫描发现安装期即执行代码,或命令参数由运行时变量拼接(参数来源无法静态判定,从严处理)。

安装时即执行脚本可执行外部命令读写/删除本地文件写入剪贴板(不计风险)发起外部网络请求
扫描于 2026/11/3(1 天前) · 范围 入口闭包 + 全包扫描 · 来源 GitHub 仓库

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

社区实装还没有人打卡 · 装过的第一个就是你
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 跨CLI与桌面端安装
  • 即装即用插件集合

README

由 DeepSeek 最新模型翻译生成
dsh-plugins Dennis Rongo 为 DeepSeek Harness (dsh) 打造的插件集合——这是一个 MIT 许可的 agent harness,其中每一项能力都是一个插件。 这里的一切都构建在 harness 的公共接缝之上:cordis 服务键、Typert 宿主/客户端桥接、shell. 槽位、系统提示词区段注册表,以及 ctx.cmdlineArgs。没有 fork,没有打过补丁的启动器,也没有内嵌的 harness 代码。 两个界面都受支持。 每个插件在 dsh CLI 下以及* DSH Desktop 下都能原样运行——后者是一个社区桌面封装,预打包了 harness,并支持多提供商模型(“任意模型,零配置”),适用于 Windows 和 macOS。它是一个独立项目,与 DeepSeek 无隶属关系。 桌面版维护自己的 DSH_HOME(Windows 上为 %APPDATA%\dsh-desktop\harness),拥有自己的配置文件,因此无论你使用哪个界面,都需要按配置文件安装一次插件。这里没有任何内容是 CLI 专属的:同一个包、同一行 cordis.patch.yml、同一组 /api 端点,同时服务于两者。 状态: 针对 dsh 0.1.5-rc.2 开发和验证,即 DSH Desktop 0.9.0 所捆绑的版本。
展开完整文档(README 剩余 1 节)
dsh-plugins

Dennis Rongo 为 DeepSeek Harness (dsh) 打造的插件集合——这是一个 MIT 许可的 agent harness,其中每一项能力都是一个插件。

这里的一切都构建在 harness 的公共接缝之上:cordis 服务键、Typert 宿主/客户端桥接、shell. 槽位、系统提示词区段注册表,以及 ctx.cmdlineArgs。没有 fork,没有打过补丁的启动器,也没有内嵌的 harness 代码。

两个界面都受支持。 每个插件在 dsh CLI 下以及* DSH Desktop 下都能原样运行——后者是一个社区桌面封装,预打包了 harness,并支持多提供商模型(“任意模型,零配置”),适用于 Windows 和 macOS。它是一个独立项目,与 DeepSeek 无隶属关系。

桌面版维护自己的 DSH_HOME(Windows 上为 %APPDATA%\dsh-desktop\harness),拥有自己的配置文件,因此无论你使用哪个界面,都需要按配置文件安装一次插件。这里没有任何内容是 CLI 专属的:同一个包、同一行 cordis.patch.yml、同一组 /api 端点,同时服务于两者。

状态: 针对 dsh 0.1.5-rc.2 开发和验证,即 DSH Desktop 0.9.0 所捆绑的版本。dsh 是一个快速迭代的 0.1.x 开发预览版,承诺会有破坏性变更——请针对你安装的版本重新验证,并阅读 Harness 兼容性 了解目前已经出现的破坏以及仍未解决的问题。

平台: 这些插件是纯 Node 实现且与平台无关,而 DSH Desktop 面向 Windows 和 macOS 发布,因此它们应当能在任一平台上运行。工具链是可移植的——verify.mjs、anchor.mjs、link-superpowers-skills.mjs 都是纯 Node,配置文件实时链接为每个平台各有一个脚本(Windows 上为 dev-link.ps1,macOS/Linux 上为 dev-link.sh),统一在一个 dev-link.mjs 入口点之后。这里的一切都已在 Windows 上针对 CLI 和 DSH Desktop 进行过验证;macOS 和 Linux 尚未测试。

Harness 兼容性

| | 验证对象 |
| DSH Desktop | 0.9.0 |
| dsh (harness) | 0.1.5-rc.2 —— DSH Desktop 0.9.0 捆绑的副本 |
| dsh (CLI, 全局) | 0.1.5-rc.1 —— npm i -g @deepseek-ai/dsh 解析到的版本,也是 anchor.mjs 链接所针对的版本 |

较旧的 harness 未做固定或测试。由于 dsh 是一个 0.1.x 预览版,这里的插件在升级时预期会直接损坏,而不会提前收到警告;以下是持续记录,最新的在前。

何时损坏了什么

2026-09-16 —— DSH Desktop 0.9.0 (dsh 0.1.5-rc.2)。 三处独立的破坏,其中两处是我们的问题:

1. harness 完全无法启动。 不是本仓库中的插件:DSH Desktop 自带的 dshmarket 从 @deepseek-ai/dsh-settings 导入了 installSettingsSection 和 settingsNamespace,而 dsh 把两者都删除了(dshmarket 自己的源码将此追溯到 0.1.2-alpha.1)。缺失的 ESM 具名导出在模块求值时就是一个 SyntaxError,因此加载器使该条目失败,宿主以 1 退出:

dsh:插件树加载失败:…… 请求的模块 '@deepseek-ai/dsh-settings' 未提供名为 'installSettingsSection' 的导出

dshmarket 1.45.1 内联了该辅助函数并修复了此问题。问题在于,只有在插件迁移完成后,配置文件才会收到该版本——而迁移又被接下来的两项中止了,这导致配置文件被固定到 dshmarket 1.36.0。因此,任何阻止迁移的因素都会表现为这次崩溃,无论真正的原因是什么。请阅读 %APPDATA%\dsh-desktop\logs\harness.log 中的 migration failed, restoring the pre-upgrade profile: 行;该原因才是真正的 bug。

2. dsh-skills 中止了插件迁移。 0.9.0 使用裸 require.resolve 校验每一代所需的依赖。@dennisrongo/skills 是一个仅含 bin 的 CLI 包——没有 main、没有 exports、没有 index.js——因此它无法以这种方式解析,迁移便以失败关闭:

@dennisrongo/dsh-skills failed peer validation: @dennisrongo/skills does not
resolve from the generation or installation closure

已在 dsh-skills 0.1.1 中修复:将目录声明为 optionalDependency,这与该插件原本对待它的方式一致——它会解析 manifest 子路径,并回退到 skillsRoot,然后是 DSH_SKILLS_ROOT,再进行克隆探测。pnpm 仍会安装它,因此目录的发布内容保持不变。

3. dsh-plan-board 的面板滑到了 dsh-mission-control 的侧栏下方。
会话列的 shell 槽位从 conversation 重命名为 main.conversation。查找未命中,useDock 落入其无列分支,而该分支将 right: 0 固定到视口——使整个面板位于 400px 侧栏之下,而不是其旁边。没有任何日志记录;它只表现为文本被裁剪。已在 dsh-plan-board 0.3.1 中修复:槽位名称现在是一个候选列表(最新的在前),并且回退逻辑也遵循框架预留,因此下一次重命名会退化为宽度略有偏差,而不是面板被埋没。

2026-09-04 — 从 dsh 安装中移除了 @deepseek-ai/dsh-client-runtime。
七个插件(dsh-git、dsh-memory、dsh-mission-control、dsh-plan-board、 dsh-theme、dsh-todo、dsh-weather)以仅类型导入的方式从它导入 ClientContext。anchor.mjs 现在会为每个插件报告 MISS … is not in the dsh install,并且 tsc 会失败并报 TS2307,以及参数失去推断类型所导致的 TS7006 连锁错误。CI 自该日期起一直为红色,最后一次绿色运行是在 2026-09-02。

状态:未解决,且是有意为之。 该导入仅为类型——它在构建时被擦除,每个运行时测试套件都通过,全部十一个包都能正确构建和发布。修复它意味着要么将类型 vendoring 进来,要么在七个插件中降级为 unknown,这值得在发布之外刻意去做。有两个连锁影响值得了解:
- pnpm run typecheck 在本地会因同样的原因失败。在归咎于自己的改动之前,先与某个 stash 进行对比。
- 在包仍然存在时创建的锚点符号链接被悬空留在了七个插件的 node_modules/@deepseek-ai/ 中。在 Windows 上,这破坏了 DSH Desktop 0.9.0 的迁移预检(ENOENT … stat … dsh-client-runtime),这就是它导致上述启动失败的原因。删除它们;当前的 anchor.mjs 会跳过缺失的包,而不是重新创建链接,因此它们不会再回来。

插件

| 包 | npm | 新增内容 | 涉及端 | 端点 |
| dsh-todo | npm | 按工作区的冲刺/路线图任务列表,外加一个 dsh-todo CLI | 宿主 + 客户端 + CLI | dshTodo/list、replace、scanDigest、readSuggestions |
| dsh-git | npm | 源代码管理标签页——变更、历史、分支、暂存、工作树 | 宿主 + 客户端 | dshGit/status、diff、commitFiles、commitDiff、stage、commit、init、sync、suggestMessage、changeToken、refs、branch、merge、stash、worktree、suggestBranch、stashFiles、stashDiff |
| dsh-weather | npm | shell 覆盖层中的天气栏 | 仅客户端 | — |
| dsh-headless-plus | npm | 为无头应用提供 --model / --resume / --continue | CLI 应用 | — |
| dsh-superpowers | npm | 将 Superpowers 方法论作为系统提示词部分以及技能提供者(已捆绑,无需克隆),并提示通过 ask_user_question 而非散文来提问 | 宿主 | — |
| dsh-skills | npm | 将 @dennisrongo/skills 库作为可安装的技能目录 | 宿主 | — |
| dsh-mission-control | npm | 集群仪表盘覆盖层——会话、swarm 树、token 消耗、权限收件箱、番茄钟计时器 | 宿主 + 客户端 | dshMissionControl/load、save |
| dsh-theme | npm | 十二种主题、八种强调色、对比度和缩放滑块,以及三种字体(其中两种已捆绑),支持实时预览 | 客户端 + 微型宿主 | — |
| dsh-hooks | npm | 兼容 Claude Code 的钩子生命周期——在八个生命周期点执行 shell 命令,并在编辑器旁提供上下文技能提示 | 宿主 + 客户端 | dshHooks/describe、recent、hints、hintsToken、dismissHint |
| dsh-plan-board | npm | 持久化计划——将每个 exit_plan_mode 计划捕获到磁盘,打开一个可从中批准或修订的审阅面板,并保留历史记录 | 主机 + 客户端 | dshPlans/list、get、changeToken、discard、pin |
| dsh-memory | npm | 通过 /remember 写入 AGENTS.md 层级结构,并提供一个标签页,显示加载器保留了哪些指令文件 | 主机 + 客户端 | dshMemory/inspect、remember、read |

dsh-todo

一个限定于你当前工作工作区的任务标签页,因此每个项目都保留自己的待办列表,而不是一个全局的大杂烩。它是一个冲刺/路线图列表,而不是检查清单:每个任务都带有一个状态、一个优先级,以及可选的发布版本和冲刺标签。

你将获得什么。 一个位于 Chat 和 Trajectory 旁边的标签页。任务会在真实的工作流状态之间流转——backlog · todo · in-progress · blocked · done——通过行上的一个胶囊控件来更改,因为布尔值无法表达站会实际要问的两件事:什么在推进,什么被卡住了。优先级以 P0–P3 芯片形式呈现,只有 P0/P1 着色,这样列表会标出紧急事项,而不会变成一道彩虹。发布版本(1.5 或 0.5.1,即一起发布的内容)和冲刺(24,即何时进行)被刻意设计为两个独立的数值轴——发布版本最多接受三个数字,因此补丁可以有自己的标签;冲刺是一个单一小数;两者都不接受字母;它们按版本语义排序(1.10 排在 1.9 之上),且无需管理发布版本表。分组依据 None · Status · Release · Sprint · Priority 会给出可折叠的分区,每个分区都有自己的进度条;按状态分组就是一个没有拖放功能的看板。点击标题会打开一个任务详情模态框(描述、状态、优先级、标签、截止日期),其中Done 就是保存——这是唯一一个在标签无效时拒绝继续的控件,而 Esc、背景遮罩和 X 始终允许你退出并放弃更改;折叠箭头仍然提供一种低成本的行内预览,双击可进行内联重命名。截止日期以日历日存储,因此“14 号到期”在任何时区都读作 14 号;逾期标红,今天到期标琥珀色。已完成的工作会被归档,而不是删除——一个条目带有一个可选的 archivedAt 时间戳,其存在即为归档状态,因此只有一个事实来源,并且无法存储一个没有日期的已归档条目。每个不可逆操作都会经过一个确认对话框,该对话框会按名称引用该任务。
建议。 标签页头部的一个按钮会扫描工作区,并提出具体的后续任务——未解决的 TODO/FIXME/HACK 注释、文档承诺但代码未实现的功能,以及没有测试的模块——每项都附有理由、优先级,以及在有据可查时的 file:line 指针。勾选值得做的项,然后 Add selected 会将它们归入待办列表;在此之前它们只是提案,不存储在任何地方。Refresh 会返回真正全新的想法,因为每个已显示的标题都会与现有待办列表一起加入排除集。扫描作为后台会话运行,写入一个结果文件,由模态框轮询,基于有界摘要而非整个仓库——因此它会消耗 token,而且它从不自动运行。

CLI。 该包附带一个 dsh-todo 二进制文件,因此 AI 代理可以通过 shell 调用并管理你在标签页中看到的同一列表——无需配置文件、无需会话、无需运行中的服务器:

npx dsh-todo list --open --json
npx dsh-todo add "Fix token refresh" --priority p0 --release 1.5 --due 2026-03-14
npx dsh-todo update t1a2 --status in-progress --sprint 24
npx dsh-todo done t1a2 && npx dsh-todo archive

它面向一个工作区目录(--workspace,默认为 cwd),并与宿主使用同一个 SQLite 文件通信,这在构造上就是安全的:SQLite 的文件锁会拒绝落入另一个进程事务中的写入者,而 CLI 设置了 busy_timeout,因此它会等待 harness 提交,而不是失败。已实时验证——当运行中的服务器持有其句柄时,CLI 进行了写入,API 返回了新任务,无需重启。Id 接受任何无歧义的前缀,空值会清除字段(--release ""),无效值会被拒绝而不是丢弃——不会写入任何内容。每个 --json 载荷都以 ok 开头,因此代理永远不会从结构推断结论:成功会带回存储的任务以供确认,拒绝则会在消息旁附带 field、expected 和 got。退出码各不相同:0 表示成功,2 表示用法错误,3 表示未找到。

工作原理。 TodoService extends TypertRemoteService 在 cordis 键 dshTodo 下注册,并为每个项目在 /.dsh/todo.db 拥有一个 SQLite 数据库,通过 workspaceRegistry 解析。写入使用乐观并发:每次 replace 都会声明它观察到的 revision,宿主会以 ok:false, code:'revision-conflict' 和当前列表拒绝过期的写入,而不是静默覆盖。旧的中央 ~/.dsh/storages/dsh_todo.json 会在首次读取时迁移。

端点。 POST /api/dshTodo/list → { list: { items, revision, updatedAt } };POST /api/dshTodo/replace → 新列表或修订冲突;POST /api/dshTodo/scanDigest → 扫描所依据的有界工作区摘要;POST /api/dshTodo/readSuggestions → 在扫描结果文件到达之前为 pending,之后为 ready 并带有解析后的建议,或为 error,无论哪种情况都会消费该文件。这四个端点都接受一个名为 request 的单一参数。
任务形状是 { id, title, description?, status, priority, release?, sprint?, dueDate?, createdAt, completedAt?, archivedAt? }。status 是唯一事实来源——不存在单独的 done 标志会与之失去同步——并且只有状态转换才会写入 completedAt。缺失的可选字段是缺失的键,绝不是 ''。现有的 v1 数据库在首次打开时会原地迁移:CREATE TABLE IF NOT EXISTS 不会向已存在的表添加列,因此在查询 PRAGMA table_info 之后,每个新列都通过 ALTER TABLE 添加,然后从旧的 text 回填 title,并将 done = 1 转换为 status = 'done'。v1 的列在每次插入时仍会被写入,因此降级后仍能读取到合理的列表。src/db.ts 被宿主和 CLI 有意共享——迁移的第二份副本是唯一可能真正损坏数据库的重复。

依赖。 来自 dsh-base 的 workspaceRegistry,@deepseek-ai/dsh-web-app 默认会组合它。CLI 不需要其中任何一项。

dsh-git

npm: @dennisrongo/dsh-git

工作区的源代码控制,无需离开 harness。查看变更、暂存变更、让模型编写提交信息,然后推送——就在你已经查看的标签页中,针对你的 agent 正在编辑的同一个仓库。

你将获得。 一个 Changes 标签页,显示分支、上游领先/落后情况,以及分为已暂存和未暂存的文件列表,每行都有暂存/取消暂存/丢弃操作和一个 diff 窗格。一个 ✦AI message 按钮通过 harness 自有的 llm 服务根据已暂存的 diff 起草提交信息,然后 Commit all 提交。最近历史记录列在下方。不是仓库的目录会报告 repo: false,并提供 Initialize repository,而不是报错。

分支、合并、储藏和工作树。 标题中的分支名称是一个菜单:切换分支、创建分支、将另一个分支合并到其中,或删除分支。第三个 Repo 标签页列出你的储藏(push / pop / apply / drop)和工作树(add / remove / prune)。每个工作树行都有一个 Open 按钮,它会将该目录注册为工作区并切换到它——一个你能看到却无法进入的工作树只是一个列表。

工作树放在项目旁边,命名为 -。 输入分支名,路径会自动填充:myproj 中的 feature/login 会建议 ../myproj-feature-login,这会将工作树放在磁盘上项目旁边,也放在 dsh 工作区列表中项目旁边。斜杠会被展平,因为 ../myproj-feature/login 会悄悄创建一个 myproj-feature 目录,并将工作树埋在其中。该建议可编辑,一旦你输入自己的路径,就会停止自动填充。
模型负责命名分支,而不是路径。 在分支框中输入一段粗略描述——“fix login retry”——点击 ✦,它就会变成 fix/login-retry,路径随之变为 ../myproj-fix-login-retry。命名是一种判断,值得交给模型;而从分支推导路径是有唯一正确答案的算术,所以用正则来做。它的失败方式正是你想要的:没有配置提供方,或出现任何错误,都会保持你的文本不变,并把原因留在日志条中——自己输入名称永远是最短路径。

工作树可以从你当前所在分支之外的分支派生。 表单中有一个针对本地分支的 from 选择框,默认是当前分支——这是 git 自己的默认值,也是常见情况,但这意味着在 feature-test 上创建工作树时,它会从 feature-test 派生,这偶尔并不是你想要的。路径会预填一个可读的 形容词-名词 名称(../myproj-brave-otter),所以“就给我一个工作树”完全不需要输入;而空的分支框会基于这个生成的名称创建一个新分支——工作树不能检出已在其他地方检出的分支,所以复用当前分支不是 git 会允许的选项。主工作树和你当前所在工作树的行没有 Remove 按钮:git 会拒绝这两者,而在 Windows 上,它会以文件锁 Permission denied 拒绝第二个,看起来像是该标签页的 bug。移除一个 dsh 已注册为工作区的工作树时,会询问是否也移除该工作区。

路径解析就像在仓库根目录打开的终端——../worktree-test 会创建 GitHub/worktree-test,正是 git worktree add ../worktree-test 会放置它的位置。落在仓库内部的路径会被拒绝:检出不属于项目内部,而且它是否干净还取决于该项目的 .gitignore。表单会在你输入时实时显示解析后的绝对路径,所以“这会放到哪里?”不需要靠尝试来回答。

有两个行为是刻意设计的。合并允许冲突。 该标签页不会拒绝任何非快进操作,而是让仓库停留在合并中途,并显示带有 Abort 和 Continue 的横幅;冲突会出现在你已经在用的 Changes 列表中,暂存文件即将其标记为已解决——这是 git 自己的模型。横幅依据 merging 标志,而不是依据“是否存在冲突”,因为一个冲突都已解决的合并已经没有冲突,但仍未完成。而且永远不会自动 stash。 在有未提交工作时切换分支,会让 git 拒绝,然后提供一个“Stash changes and switch”按钮;自动 stash 如果之后的 pop 发生冲突,会把工作困在你从未选择进入的状态之后。
这些都不会给轮询循环带来任何开销。status 增加了 merging、mergeHead 和 stashCount,却没有增加任何一个 git 进程——前两个是对 MERGE_HEAD 和 MERGE_MSG 的文件系统检查,第三个统计 refs/stash reflog(git 的 stash 就是那个 reflog),而三个仓库目录现在通过一次 rev-parse 调用返回,而不是各调用一次。分支、stash 和工作树列表在你打开菜单或 Repo 标签页时才惰性获取,从不轮询。

布局响应的是标签页,而不是窗口。 当有空间时,diff 位于文件列表旁边;没有空间时,则位于其下方,通过容器查询(@container dshgit (min-width: 720px))切换——标签页由 shell 自身的侧边栏和面板独立于视口调整大小,因此真正重要的宽度是它自身的宽度。打开 diff 永远不会移动某一行:列表的列宽在两种状态下都被预留,因此第一次点击不会导致重排并重新截断指针下的每个文件名。当补丁正在传输时,窗格显示一个形似 diff 的骨架屏——闪烁的 meta/hunk/add/del 条带,尺寸基于真实的 18px diff 行——而不是一个加载旋转图标,后者会让一大片区域变空白;闪烁动画作用于 background-position,从不作用于 transform 或盒子尺寸,因此不会导致布局偏移,而 prefers-reduced-motion 会将其扁平化。点击列表会开始重叠的请求,因此每个请求都打上一个单调递增的序列号,过期的回复会被丢弃,而不是绘制到错误的文件名下。图标是内联的 16px SVG,位于匹配的 0 0 16 16 viewBox 中,放在 20px 按钮里,这使文件行保持在 32px。

工作原理。 GitService extends TypertRemoteService,位于 cordis 键 dshGit 下,在工作区目录中调用 git。写入操作按仓库根目录串行化,通过内部队列,因此两个标签页无法交错执行暂存和提交。来自客户端的路径会经过 assertSafePath,它会拒绝绝对路径和 .. 逃逸。未跟踪文件没有 git 可生成的 diff,因此其内容被合成为 /dev/null 补丁——否则点击一个新文件会显示空白窗格,看起来像是坏了。没有远程的 push 会作为数据失败({ ok: false, output }),而不是抛出异常。

列表会自我更新,无需轮询 git。 来自你的 agent、你的编辑器或终端 git checkout 的编辑会在大约一秒内出现,无需点击刷新。用显而易见的方式做到这一点——定时重新读取 status——会为每个打开的标签页每秒生成四个 git 进程。相反,宿主用 fs.watch 监视仓库,并维护一个单调递增的变更令牌;标签页轮询一个 changeToken 端点,该端点从该计数器作答而不运行 git,只有当令牌变化时才重新读取完整状态。针对实时测试装置测量:每次轮询 52 ms,而 status 为 141 ms,且轮询路径上不生成任何 git 进程。隐藏的标签页完全不轮询,而一个 focus 处理器一瞥之间就会重新检查,因此空闲成本为零。

有三个细节让它不仅快,而且安全。.git 过滤器是一个允许列表,因为仅仅读取一个仓库就会触及 .git/objects——如果用拒绝列表,标签页自身的状态读取就会增加令牌,于是这个功能会在一个空闲仓库上永远自我喂养。高变动目录(node_modules、dist、.next……)被忽略,因此安装依赖不会刷爆监视器。而且防抖有一个最大等待时间,因为纯粹的尾沿防抖是一个饥饿 bug:监视模式下的构建会永远重新触发它,列表会在工作持续进行的整个期间一直保持陈旧。

端点。 status、diff、commitFiles、commitDiff、stage、commit、init、sync、suggestMessage、changeToken、refs、branch、merge、stash、worktree、suggestBranch、stashFiles、stashDiff,位于 POST /api/dshGit/ 下,每个都接收一个名为 request 的参数。

依赖。 workspaceRegistry 和 llm(两者都由 dsh-base 组合)以及用于起草消息的 agentDefaultModel。

dsh-weather

当前天气状况固定在 Web UI 顶部:温度、天气状况、位置、简短的逐小时展望、湿度和风速。它居中于没有任何停靠覆盖层占用的区域,因此会为计划面板和任务控制让位,而不是位于它们下方。

工作原理。 一个纯消费者客户端插件,注册到可叠加的 shell.overlay 插槽中——没有宿主服务、没有端点、没有 API 密钥。它直接从浏览器获取 Open-Meteo 数据(已启用 CORS)。读数始终以摄氏度获取,并在渲染时转换,因此 °F/°C 切换无需重新获取;该选择持久化在 localStorage["dsh-weather:unit"] 中,默认为 °F。位置依次从 localStorage["dsh-weather:location"]、地理提供程序链解析,最后硬回退到纽约;每条网络路径都会降级为可见的错误状态,而不是向 shell 抛出异常。

无障碍性是刻意设计的: 单位切换是一个真正的 ,带有 aria-label,该栏为 aria-live="polite",焦点通过 :focus-visible 设置样式,并且 prefers-reduced-motion 会禁用刷新加载动画。

dsh-headless-plus

原版无头应用只回答一个任务然后退出,无法选择模型或继续对话。这个插件取代了它。

| 原版无头 | headless-plus |
| dsh --profile headless "task" | 不变 |
| — | --model provider/model — 每次调用的模型覆盖 |
| — | --resume — 继续一个持久化会话 |
| — | --resume latest / --continue / -c — 继续此工作区最近的会话 |
| — | --session-info — 退出时在 stderr 上打印新会话 id |

dsh --profile headless-plus --model anthropic/claude-sonnet-4-6 "refactor the auth module"
dsh --profile headless-plus --continue "now add tests"markdown
dsh --profile headless-plus --resume session-6f2ca6dc-… "pick up where we left off"

工作原理。 两行取代了原有的两行:一行启动行通过 ctx.cmdlineArgs 拥有该标志族(每个应用拥有自己的标志,因此不会与启动器冲突),以及一行运行器行,通过注入接收解析后的任务。恢复通过公共的 ctx.agents.resume() 和 ResumeAgentOptions 进行;--resume latest 使用与 dsh-session-persistence-jsonl 相同的 slug 规则将工作区映射到其会话目录。模型覆盖通过 ctx.agentDefaultModel 进行。注意文件夹是 plugins/dsh-headless-plus,但包是 @dennisrongo/dsh-headless-plus,而挂载行使用包名。

dsh-superpowers

使 Superpowers 方法论成为配置文件中每个代理的强制首选,而不是模型可能或可能不会去使用的技能。

工作原理。 上游通过一个 SessionStart 钩子传递其引导程序,该钩子必须在 startup|clear|compact 时重新触发。dsh 没有钩子外壳,但它的系统提示是一个分层、有序的区段注册表,在压缩后重新组装——因此一个注册的区段就能覆盖会话生命周期内所有三个上游触发点,不存在引导程序脱落的间隙。它位于顺序 -50,紧接在 persona 之前。

第二个不相关的区段:“将选项作为选项提供”。 dsh 已经可以渲染真正的选择器——ask_user_question 接受带有标签和一行描述的 options[],以及 multi_select,并且随附的问题 UI 会将其转换为单选按钮组或复选框组。它做不到的是将散文转换为控件:结构化界面仅存在于实际的 question/requested 帧中,而工具调用只能在回合期间发生。因此,以 markdown 中“A 还是 B?”结尾的答案将永远保持为 markdown,而你要为此付出代价:输入一个模型随后必须解释的回复。这个区段正是在那个时刻请求该工具,推荐选项放在最前面。它在计划模式下让位,在计划模式下 dsh 自己的规则使 exit_plan_mode 成为唯一的交互,并以覆盖后续指导的措辞说明了这一点。它是手写的,而不是从克隆中读取的,并且无论该克隆是否存在都会注册——askWithOptions: false 会将其移除。

它也为技能本身服务。 提示区段告诉模型在响应之前调用技能;目录是使它们可被命名的东西,而这两者过去会独立失败。一台有区段但没有目录的机器是三种状态中最糟糕的——模型被告知要使用技能,却没有现有技能的列表,于是猜测一个名称,并报告技能缺失,而按确切名称加载一直都能正常工作。因此,该插件还注册了一个技能提供者,排名为 550(低于 dsh-skills 的 600,因此精选库在名称冲突时胜出)。
解析,先命中者胜: superpowersRoot → SUPERPOWERS_ROOT → 对 $HOME 下常见克隆路径的探测 → 14 个上游技能的内置快照(obra/superpowers@b36e082,MIT)。真实的克隆始终优先,因此当你拥有克隆时,git pull + 重启配置文件仍然是更新路径;快照只是让全新机器能够工作的底线,因为 dsh 会将缺失的技能根目录报告为空列表,而不是错误。用 node scripts/vendor-superpowers.mjs 刷新它,该脚本会拒绝脏克隆,因此记录下来的来源信息不会变成谎言。

已经将技能 junction 到 /skills 了吗?设置 skillProvider: false,否则它们会被列出两次:

node scripts/link-superpowers-skills.mjs # --dry-run to preview, --restore to undo

dsh-skills

将 @dennisrongo/skills 库转换为可安装的目录,这样一台全新机器只需一条命令即可获得所有技能:

dsh plugin --profile add @dennisrongo/dsh-skills

Why this is a plugin and not a link script. Skills are normally dropped into /skills and discovered from there — which is what link-superpowers-skills.mjs above does, and it is the right tool for keeping an already installed catalog fresh. But it is not an install path: it presumes ~/.agents/skills exists, and nothing in the harness creates it. A missing root is discovered as an empty list rather than an error, so a fresh machine gets a silently empty catalog and no diagnostic. Making the library an npm dependency of a plugin hands that whole problem to pnpm, which already solves it — and dsh plugin is a thin pnpm forwarder, so update, outdated and remove all work.

How it works. ctx.skills.registerProvider publishes a second, independent skill provider serving every skills//SKILL.md in the resolved library. Registration files into the profile's global layer, which every agent preset's scope chain includes, so one row reaches every agent without touching the presets' own skill-filesystem rows. A nearer layer still wins outright, so a project's .dsh/skills shadows a library skill of the same name — the library is a baseline, not an override.

Nothing is vendored. Bodies are read when the catalog is collected, and resolution runs skillsRoot → DSH_SKILLS_ROOT → the packaged dependency → a probe of common clone paths under $HOME. Point skillsRoot at a working clone and edits land with no reinstall.

dsh-mission-control

One glass panel over the whole agent fleet, floating above the stock web UI.

What you get. A shell.overlay dashboard, docked as a right rail, with a Fleet list of every session (root and subagents) grouped by workspace and showing running / waiting / done, a swarm tree of coordinator → worker lineages, a stats strip (session count, running, subagents, waiting-on-you), estimated token burn broken down by model, and a permission inbox surfacing sessions blocked on approval / question / plan-review.

Stage is a full-screen takeover — press it and the rail swaps for a live grid of tiles, one per session that is running, waiting on you, or was touched inside the activity window (30m / 2h toggle), busiest first. A tile carries the session's live conversation and answers a pending permission in place, so you never lose the tab you came from; Esc or × returns to the panel. Because Stage covers the whole viewport it also spans DSH Desktop's 36px window-drag strip, which swallows clicks before hit-testing — its bar clears that band and every control opts out with data-dsh-no-drag.

A settings drawer persists to localStorage (bad shapes fall back to defaults, and a storage failure degrades to in-memory rather than throwing): sessions listed per workspace group, fleet sort order, and an optional pomodoro timer in the footer with configurable work / short-break / long-break lengths and a desktop notification on phase change. The timer itself is also mirrored to the host cell so it survives a Desktop restart; the drawer keys stay origin-local.

How it works. Fleet rendering is a pure consumer on public faces only — ctx.sessions.list and ctx.workspaces.list as ObservableSnapshots bridged into React, sessionStats projections (turns / steps / llmMs / decodeTokens), and PendingInteraction off the session summaries. No tools, no presets. One minimal host service, dshMissionControl, owns a single opaque JSON cell at /storages/dsh-mission-control.json so the pomodoro survives a DSH Desktop restart: Desktop serves the UI from an ephemeral port per launch, and localStorage is origin-scoped, so without the host cell every restart was a fresh website. The client owns the envelope shape; the host never parses it. Without the host half (an older install) the panel degrades to localStorage. CSS is namespaced dshmc-, and control metrics are CSS custom properties so the 400px rail's compact sizing and Stage's full-screen sizing derive from one set of tokens rather than diverging.

Endpoints. POST /api/dshMissionControl/load → { state } (a string or null); POST /api/dshMissionControl/save → { ok: true }. Both take one parameter named request.

dsh-theme

A Themes page in Settings: twelve curated palettes — Bumble Bee, Catppuccin, Citron, Claude, Everforest, Gruvbox, Nord, One, Rosé Pine, Sakura, Solarized, Tokyo Night — eight accent colours, a contrast slider and a UI scale slider, and a font axis whose two non-default entries — Geist Mono and JetBrains Mono — ship inside the bundle as data URLs, so they render identically everywhere with nothing to install. One choice sets both the interface and the code face. Hovering a theme swaps its one-line description for its full authored palette, so you can read the colours without applying it. Picking any of them previews across the whole app immediately; nothing is saved until you press Apply, and Revert or closing the modal puts back what was committed.

Every theme ships both palettes, so it layers over your existing Light/Dark/System choice rather than replacing it. Flip appearance in General and the theme swaps variant; leave it on System and it follows the OS.

How it works. Four independent ctx.theme.overrideTokens layers — palette (110 tokens), accent (17), font (2), scale (1). The runtime keeps one layer per source and replaces it wholesale when the same source overrides again, which is the entire preview mechanism: no stacking bookkeeping and no way to leak a half-applied palette. Because layers compose per token, theme × accent × font × scale costs the sum of those lists rather than their product. The accent drives the send button — the one control prominent enough to carry a theme's identity — and because that button hardcodes a white icon, each accent passes through a solver that finds the nearest shade clearing 3:1 both for the icon on the fill and for the fill against the page; an accent that already clears both is used unchanged. The font layer is two tokens because one choice drives both faces: all ~200 of the harness's composed typography tokens read var(--dsw-font-family), and --ds-font-family-code covers code. Both non-default faces are bundled as data URLs, so they render identically everywhere with nothing to install. A contrast slider pushes surfaces and text apart while leaving the palette's accent and state colours untouched, and a UI scale slider zooms the whole interface.

Selection rides a cookie, deliberately. DSH Desktop serves the UI from a new ephemeral port every launch and localStorage is origin-scoped, so a localStorage-only plugin forgets your theme on every Desktop restart — the same trap that gave dsh-mission-control its host half. Cookies are not isolated by port, so the choice survives the relaunch; localStorage is kept as a fallback. The settings document was rejected because its writes silently no-op on a non-loopback connection. A first-paint bootstrap inlined by the tiny host half stops the shell flashing the stock palette on load — measured at roughly half a second without it, and it reads the cookie first so the fix holds on the Desktop too.

Adding a theme is data. One file of ~15 colours per variant in src/themes/, one line in the catalogue; the builder derives every custom property the harness reads, and the test suite enforces WCAG contrast floors on all of them and reports every failure at once. See the package README.

dsh-hooks

npm: @dennisrongo/dsh-hooks

Attach a shell command to a lifecycle point. Block a tool call, feed the model context, format a file after an edit, or get told when the agent stops — configured, not coded.

dsh already had the lifecycle. tools/pre-execute is a waterfall returning allow | deny | ask; tools/post-execute can block a settled result or attach model-facing context; agent/pre-step can reject or rewrite the messages entering a step; agent/turn-stopping can steer an agent back into work. What it had no way to do was attach a command to any of that from configuration. This plugin is that runner and nothing more — it owns no policy, and with an empty config it is inert.

Eight events, Claude Code's names. PreToolUse · PostToolUse · UserPromptSubmit · SessionStart · SessionEnd · Stop · SubagentStop · Notification. UserPromptSubmit is gated to steps that actually claimed a user-sourced message, because agent/pre-step fires on every step — without the gate a prompt hook would run once per tool round-trip.

The protocol is Claude Code's, so your existing hook scripts run unchanged. JSON payload on stdin, exit 0 allows, exit 2 blocks with stderr as the reason, structured hookSpecificOutput on stdout for permissionDecision and additionalContext. Hooks are handed DSH_PROJECT_DIR, DSH_SESSION_ID and DSH_HOOK_EVENT — plus CLAUDE_PROJECT_DIR as the same value, so a ported script finds its project without edits. Those names have to be passed explicitly: the subprocess seam scrubs all DSH_ and credential-shaped variables out of a child's ambient environment, and the spec's explicit env is the documented opt-in.

Two configuration layers, and they are additive. A dsh-hooks settings namespace (so $DSH_HOME/settings.yaml, schema-validated, live-reloading) plus /.dsh/hooks.json that a repo can commit. Every matching hook from both runs — a checked-out repository cannot silently disable your global guard by declaring an empty list for the same event. The project document is read per dispatch and cached against mtime+size, so an edit takes effect with no restart and no watcher handle held open per workspace. matcher is a real regex over the tool name; an invalid one matches nothing and warns once, because one typo must not become a hook that fires on every call in the session.

Failure is fail-open, deliberately. tools/pre-execute is awaited before every dispatch, so a hanging hook would stall the session: the deadline is owned by the runner (never the hook), expiry escalates through the seam's tree-scoped terminate, and a crash or timeout is not a denial unless the entry sets failClosed: true. One broken hook bricking every tool call is the failure mode that makes people delete their hooks entirely — a security gate opts into failClosed and accepts that breaking it stops the work; a formatter does not. Stop gets a second guard: it passes stop_hook_active faithfully, and caps consecutive hook-driven continuations at five for the hook that ignores it.

Two Claude Code capabilities dsh cannot express, both reported rather than dropped. Tool arguments cannot be rewritten — PreToolDecision is allow | deny | ask, and the registry's own docs say input rewriting is excluded because arguments are already logged and presented; a hook returning updatedInput gets a warning naming the alternative. And SessionEnd cannot block teardown, because agent/disposed is emit-mode. A hook that believes it sanitized an argument it never changed is worse than one that cannot try.

The same signals also suggest a skill. A chip strip in conversation.input.dock names a skill you could run next — from what is on disk (a .csproj, a next dependency, a shadcn components.json), from a trigger phrase your prompt matched in a skill's own description, and from what the turn just did (three source edits and no test run, or a git commit). Clicking a chip writes /skill-name into the composer, which is exactly what picking it from the slash menu does; ▶ runs it, × dismisses it for the session. Every rule carries a pattern, resolved against ctx.skills.list() for your agent's scope — so no chip ever names a skill this deployment does not have, and one you have already used is never suggested again. The heuristics are cheap regexes on purpose: a false positive is one ignorable chip, and a rule you can predict is one you can silence (hints.disableRules).

Endpoints. POST /api/dshHooks/describe → every hook in force across both layers with the document each came from; POST /api/dshHooks/recent → the last 200 settled runs with exit codes, durations and output tails; hints / hintsToken / dismissHint → the chip strip, where hintsToken is an in-memory counter that moves only when the computed list actually changes, so the 1.5s poll costs nothing on an idle session. All take a single parameter named request.

Requires. ctx.tools and ctx.subprocess. ctx.settings, ctx.workspaceRegistry and ctx.skills are used when present and deliberately not injected — this cordis has no optional-inject form, so listing them would make the plugin never mount in a deployment that composes none of them. Without a skill registry the strip simply stays empty.

dsh-plan-board

npm: @dennisrongo/dsh-plan-board

Plans that outlive the scrollback. Every plan the agent presents through exit_plan_mode is written to /.dsh/plans/ as markdown, a window opens so you can read it at full size, and a Plans tab keeps the history with each plan's outcome.

dsh already has plan mode, and it is good: dsh-plan-mode logs a plan/mode event that survives resume and fork, registers /plan, and presents the complete markdown for Approve / Keep planning. What it does not do is keep the plan. The markdown exists only inside the tool-call event in the session log — scroll past it and it is gone, it is not a file you can diff or commit, and the reviewer's feedback exists only as the text of a thrown error. The most reviewed artefact in the session was the least durable one.

Files, not rows. /.dsh/plans/20260829T121500123-add-a-hook-lifecycle.md, markdown with a small metadata block, so a plan can be opened in an editor, diffed, committed, or handed to someone who is not running the harness. dsh-todo keeps a database because a task list is rows; this is the other case. The metadata is JSON-per-line rather than YAML: it reads the same, but it has to survive a model-written title and free-form human feedback full of quotes and newlines, which is exactly where a hand-rolled YAML subset eventually breaks.

The capture point is tools/execute, not tools/pre-execute. next() runs the tool body — the call that blocks on the human — so wrapping it is what makes the outcome* observable: the plan is written pending before, and settled to approved or rejected after, with the rejection feedback lifted out of the error the tool threw. pre-execute sees the plan but never the answer.

Two ways a plan is captured, because most sessions never enter plan mode. Explicitly, from exit_plan_mode, which carries the real review outcome. Implicitly, from a fenced
The plan docks beside the chat, taking its right half and pushing it aside rather than covering it — closable, and only present while the session actually has a plan. It is a shell.overlay entry rather than a tab, and that is not a style choice: views render one-at-a-time (only: ), so an inactive tab is not mounted and cannot open itself when a plan appears. But an overlay is not a layout sibling of the chat either, so the panel measures the conversation column and pushes it with an inline padding-right. It anchors on [data-slot="conversation"] because slot names are the documented API while the class names beside them are hashed per build, and a MutationObserver re-applies the push if a React re-render drops it. The Plans tab remains as the history browser.

It approves without owning the question service. exit_plan_mode presents through ctx.userQuestions.ask(), and that service documents one active provider per context — the shipped question UI holds it, and registering a second one would hijack every question in the harness, not just plan reviews. It never does. A raised question also reaches the browser as a pending-interaction carrier on the session's conversation snapshot, and the carrier owns the answer: the panel reads sessions.binding(id).session.getSnapshot().pending, narrows it with the same rules the shipped decision card uses, and calls respond(). Two remote controls for one wait — whichever answers first settles it, and the other gets a not-pending receipt it reports rather than swallows. Revise goes down the same channel: exit_plan_mode treats free text as keep-planning before it looks at the button, so an edited plan cannot be approved — it is sent back as feedback and presented again.

Freshness rides a change token like dsh-git's, but here it is a plain in-memory counter — this process is the only writer, so there is no fs.watch and no handle per workspace. Polling stops while the document is hidden. Plan bodies never ride the list: list returns metadata only, so 200 plans do not ship a megabyte of markdown to draw a sidebar. Markdown renders to React elements, never dangerouslySetInnerHTML — a plan is model-written text that may quote something off the internet — and links render as label (url) rather than as anchors, so a model-authored destination stays inspectable and inert. Writes go through a temp file and rename: the plan you are about to approve is exactly the file that must not be half-written.

dsh-memory

npm: @dennisrongo/dsh-memory

A /remember command that writes a fact into the instruction hierarchy dsh already reads, and a Memory tab that shows which of those files the loader actually kept.

This one is deliberately small, because dsh already has the read half and it is more capable than it looks. dsh-agent-instructions loads the user-global $DSH_HOME/AGENTS.md, then every AGENTS.md and CLAUDE.md from the project root down to the session's directory, plus .local overlays, deduplicated per directory, budgeted by bytes, pulling in nested files as the agent touches them — and sessions are already bound to workspaces by canonical path.

What was missing was writing and seeing. There is no #-style capture, so a fact learned mid-session is one you retype by hand later. And the byte budget silently omits files: a file that exists, is discovered, and is dropped for budget looks exactly like a file the agent is ignoring for no reason — with the shipped code preset's 64 KiB budget, which a real monorepo reaches.

Capture. /remember files it under a ## Memories heading in /AGENTS.md; --local targets the gitignored AGENTS.local.md overlay and --user the machine-wide $DSH_HOME/AGENTS.md. The reply always names the exact path — "saved" would leave you guessing which of four candidate files it landed in. A second fact joins the existing list rather than starting a new one, and nothing a human wrote is ever moved: the new item goes at the end of that section, not after whatever heading happens to be last.

Inspection. The tab lists every discovered file in model precedence order with its size, not loaded when the budget dropped it, and cut to 96 B when it survived truncated. Both facts come from the loader's own discoverBaselineInstructionFiles and loadBaselineInstructions — nothing here reimplements the walk, because an inspector that drifts from the loader is worse than none: it is trusted precisely when it contradicts you.

Not a second memory store. A parallel fact database beside the instruction files would mean two things to keep in sync, two precedence orders, and a place for facts to hide from a loader that already works.

Every package carries an AGENTS.md with its endpoints, mount row, dev loop and a verification recipe. See AGENTS.md for the repo as a whole.

Install a plugin

All eleven are on npm, and each declares dsh.bundle — so one command installs and mounts it. dsh plugin forwards to pnpm inside the profile directory:

Profile names carry templates. dsh plugin --profile add ... scaffolds a new
profile if doesn't exist — but only web and headless get a full template. Any other name is scaffolded with @deepseek-ai/dsh-base alone, which does not provide workspaceRegistry, and a UI plugin that needs it then refuses to boot with 1 entry did not activate — pending (waiting for service: workspaceRegistry). Use web for UI profiles, or add @deepseek-ai/dsh-web-app to dsh.profile.bundles yourself. bash web/desktop UI plugins
dsh plugin --profile web add @dennisrongo/dsh-todo
dsh plugin --profile web add @dennisrongo/dsh-git
dsh plugin --profile web add @dennisrongo/dsh-weather
dsh plugin --profile web add @dennisrongo/dsh-mission-control
dsh plugin --profile web add @dennisrongo/dsh-theme
dsh plugin --profile web add @dennisrongo/dsh-skills

CLI-app and prompt plugins, in a headless-style profile
dsh plugin --profile headless add @dennisrongo/dsh-headless-plus
dsh plugin --profile headless add @dennisrongo/dsh-superpowers
dsh plugin --profile headless add @dennisrongo/dsh-skills

Several at once is fine. Restart the profile and it's live — there is no cordis.patch.yml row to write; see step 4 if you're upgrading from a version that needed one.

Prefer the git source — to track main, or to pick up a change before it's released?
bash
dsh plugin --profile web add "github:dennisrongo/dsh-plugins#path:/plugins/dsh-todo"
dsh plugin --profile web add "github:dennisrongo/dsh-plugins#main&path:/plugins/dsh-todo" # pin a ref

Quote the argument — # and & are shell metacharacters. Every package ships its built lib/, so a git install works even though it runs no build step.

Update to the latest

dsh plugin is a thin pnpm forwarder, so update, outdated and remove all work the
way they do in any pnpm project.
bash
what is out of date in this profile?
dsh plugin --profile web outdated

update one plugin, or several
dsh plugin --profile web update @dennisrongo/dsh-git
dsh plugin --profile web update @dennisrongo/dsh-todo @dennisrongo/dsh-mission-control

Restart the profile afterwards. A client-half change lands on a browser refresh, but a
host-half change — a new /api endpoint, a changed service — does not: the Typert loader caches its per-package verdict for the life of the process, so a plugin that gained an endpoint will 404 on it until the profile restarts.

Two behaviours worth knowing. A caret range only moves within its major, so a major bump
needs the explicit tag: dsh plugin --profile web add @dennisrongo/dsh-git@latest. And
bundles reconcile by installed state, not by dependency diff — a version that newly gains a dsh.bundle activates on its own, with no cordis.patch.yml edit.

From a clone rather than npm, the equivalent is git pull then a root pnpm install and
pnpm run build. pnpm replaces the profile links with frozen copies on every install, so the
root postinstall re-links them for you — check its output, since it warns rather than fails (see step 5).

Updating the skills catalog

The skills themselves live in @dennisrongo/skills,
a plain library that dsh-skills depends on — so refreshing the catalog is a dependency update, not a plugin reinstall: bash newest catalog for a profile that has dsh-skills installed
dsh plugin --profile web update @dennisrongo/skills

check what is available first
npm view @dennisrongo/skills version

Nothing is vendored — skill bodies are read at catalog-collection time — so pointing skillsRoot at a working clone picks up edits with no reinstall and no restart. That is the fast loop while authoring a skill; the npm update is for consuming a published one.

Expect this on install, and ignore it — it is orientation, not a fault:

warning: @dennisrongo/skills declares no dsh.bundle — installed as a plain dependency

The library is a plain library; only the plugin is a bundle.

The dsh-todo command

dsh-todo needs no profile and no running server, so it can also be installed on its own — useful for CI, or to let an agent manage a project's tasks without the web UI: bash
npx @dennisrongo/dsh-todo list --open # no install at all
pnpm add -g @dennisrongo/dsh-todo # then: dsh-todo list --open

Installing it into a profile already puts the binary on that profile's node_modules/.bin.
Full command and flag reference: plugins/dsh-todo.

Installing from a clone

Clone if you want to edit the plugins or run the tests.

Prerequisites
bash
node --version # 22+
pnpm --version # 11+
npm i -g @deepseek-ai/dsh
dsh --version

These plugins declare their @deepseek-ai/ packages as peers and deliberately don't install their own copies — they resolve to the ones inside your global dsh install. Step 5 is what wires that up.

Using DSH Desktop only? You don't need the CLI to install or run the plugins — steps 3, 4 and 6 work against the desktop's own profiles. You only need a copy of the harness packages for this repo's tooling (tests, typecheck, live editing), and you can point at the ones the desktop already bundles instead of installing the CLI:

powershell
$env:DSH_HOST_DEPS = "$env:LOCALAPPDATA\Programs\DSH Desktop\resources\app\node_modules\@deepseek-ai"

Verified working: anchoring resolves to the desktop bundle rather than the npm host. Keep it consistent — don't anchor against the bundle while a CLI profile serves the same plugin, or the two surfaces load different physical copies of the harness packages.

You also need the harness itself configured with a model provider before any of this is
useful — that's dsh's own setup (~/.dsh/settings.yaml and credentials), not something these plugins touch.

1. Clone and build
bash
git clone https://github.com/dennisrongo/dsh-plugins.git
cd dsh-plugins
pnpm install
pnpm run build # rebuild lib/ from src/

pnpm run test and pnpm run typecheck need step 5's anchoring first — dsh-todo's smoke
test imports @deepseek-ai/cordis directly, and on a bare clone that fails with
ERR_MODULE_NOT_FOUND. Build works without it (the harness packages are marked external).
If you want the tests before touching a profile, run just the anchoring half now:
powershell
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\dev-link.ps1 -IdentityOnly
pnpm run test

2. Pick or create a profile

A profile is a directory under $DSH_HOME/profiles/ holding a package.json and a cordis.patch.yml. The CLI uses ~/.dsh; DSH Desktop keeps its own DSH_HOME — on Windows that's %APPDATA%\dsh-desktop\harness, and the desktop logs the path it booted with at startup if you need to confirm it.

web and headless have built-in templates, so installing into them creates the directory, its manifest, an empty cordis.patch.yml, and the pnpm settings below. Any other name you must scaffold yourself — two files: json // /package.json { "name": "dsh-profile-my-web", "private": true,
"dsh": {
"profile": { "bundles": ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app"] }
}
}
yaml
/pnpm-workspace.yaml — do not skip this packages:
- .

nodeLinker: hoisted
autoInstallPeers: false

That second file is what dsh writes for its own template profiles, and autoInstallPeers: false matters: these plugins declare the harness packages as peers, and with auto-install on, pnpm fetches its own copies from npm — at versions that don't even match the declared ranges, since the published @deepseek-ai/ releases lag the version bundled with dsh. You end up with duplicate harness packages in the profile and a latent module-identity problem.

Use @deepseek-ai/dsh-headless instead of dsh-web-app for a headless profile. Don't edit cordis.yml — it's generated; cordis.patch.yml is your layer.

3. Install the plugins

dsh plugin forwards to pnpm inside the profile directory:
bash
dsh plugin --profile web add \
"file:C:/absolute/path/to/dsh-plugins/plugins/dsh-todo" \
"file:C:/absolute/path/to/dsh-plugins/plugins/dsh-git" \
"file:C:/absolute/path/to/dsh-plugins/plugins/dsh-weather" \
"file:C:/absolute/path/to/dsh-plugins/plugins/dsh-mission-control"

On Windows use a native absolute path with forward slashes — the MSYS /c/... form fails with LINKED_PKG_DIR_NOT_FOUND.

Each package declares dsh.bundle, so dsh plugin registers it as a profile layer and prints
the package it added. (If you see declares no dsh.bundle — installed as a plain dependency, you're on an older version of that plugin and will need a manual row.)

DSH Desktop: same command with the desktop's profile. If that profile's node_modules was created by a different pnpm major you'll get ERR_PNPM_UNEXPECTED_STORE; edit the profile's package.json by hand instead and let step 5 supply the live module.

4. Nothing to mount — but read this if you're upgrading

Each package declares dsh.bundle, so dsh plugin add appends it to the profile's
dsh.profile.bundles and it self-mounts. One entry brings up both halves of a plugin: the
host service and the browser tab. Restart the profile and it's live.

Breaking change from earlier versions of these plugins. They used to require a hand-written
insert: row in your profile's cordis.patch.yml. That row is now a duplicate of the one
the bundle provides, and the harness treats that as fatal:

Error: dsh: plugin tree failed to load: failed to apply loader entry include
(cordis:include): duplicate loader entry id: dsh-weather

If you have rows for dsh-weather, dsh-todo, dsh-git, dsh-mission-control,
superpowers, headless-plus-startup or headless-plus-runner — or the headless-startup / headless-runner disables, which dsh-headless-plus now carries itself — delete them. Check with dsh --profile --dump-config, which labels each row with the layer it came from; you want exactly one per plugin.

You still edit cordis.patch.yml to configure a row, which is what a bare id: is for:
yaml
pin the superpowers clone instead of letting the plugin probe for it
- id: superpowers
config:
superpowersRoot: /absolute/path/to/superpowers

dsh-superpowers no longer needs a clone — it ships a pinned snapshot of the 14 obra/superpowers skills and serves them itself, so the config row above is optional. Point superpowersRoot at a clone when you want that clone to be authoritative; it takes precedence over the snapshot, and git pull + a profile restart is then the update path. Only if you prefer delivering the skills as junctions under /skills do you need node scripts/link-superpowers-skills.mjs — and in that case set skillProvider: false, or the same skills appear twice.

dsh-headless-plus replaces the stock headless app, so its bundle disables the two stock rows
for you — it needs a profile built on @deepseek-ai/dsh-base + @deepseek-ai/dsh-headless.

5. Optional: link for live editing

Installing is done — the plugins work at this point. A plain install resolves the harness packages through the profile, so you can skip straight to step 6. This step is for editing this repo and seeing the change without reinstalling, plus it's what makes this repo's own
pnpm run test and pnpm run typecheck resolve.

Most of the time you do not run anything here. pnpm install at the root ends in a
postinstall that runs scripts/dev-link.mjs, which does this for you on the platform you
are on. Run it by hand only to re-link without installing, or to pass targets:
bash
node scripts/dev-link.mjs # what postinstall runs: junctions on Windows, symlinks elsewhere
node scripts/anchor.mjs # the anchoring half on its own, any platform

Two jobs, and both are undone by any pnpm install — which is exactly why the postinstall exists, so the rule is enforced mechanically rather than from memory:

- Profile links. Points each profile's installed plugin at this repo so a rebuild
self-deploys; pnpm otherwise materialises file: deps as copies frozen at install time.
- Dependency anchoring. Points each package's node_modules/@deepseek-ai/ at your
global dsh install. Once a plugin is linked it resolves through its real path, so
Node looks for dependencies here rather than in the profile — without anchoring a linked* plugin dies at boot with ERR_MODULE_NOT_FOUND. node scripts/anchor.mjs does this half alone on any platform (-IdentityOnly on the PowerShell script); the shell script's --no-anchor is the inverse — link the profiles, skip the anchoring.

It only touches plugins a profile actually declares, follows the package name (so unscoped
packages work), and prints what it skipped. It is deliberately non-fatal: a machine with
no dsh installed, or without one of the profiles, gets a warning rather than a failed install
— so check its output rather than assuming it ran. node scripts/verify.mjs is the gate that catches a genuinely unwired workspace.

dev-link.mjs dispatches by platform — scripts/dev-link.ps1 (junctions) on Windows,
scripts/dev-link.sh (symlinks) on macOS and Linux — and either can be run directly:
powershell
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\dev-link.ps1 -Profiles web -DesktopProfiles web
bash
scripts/dev-link.sh --profiles web --desktop-profiles web

Both default to a profile named web on both surfaces, which is why the postinstall
usually needs no configuration. If your profiles are named something else, put them in a
.dev-link.json at the repo root — it is gitignored, so per-machine profile names stay out of the shared manifest: json { "profiles": "web,mission-control", "desktopProfiles": "web" }

plugins is accepted too, to link just one package. An absent file means the underlying
script's own defaults.

Adding a plugin to a profile that is already linked

Linking follows the profile's manifest, so a plugin added to this repo after the profile was
set up is not picked up by re-running dev-link alone. It is skipped, by design and out loud:

-- dsh-headless-plus not a dependency of web - left alone

Junctioning a package a profile never declared would leave a module its own manifest does not
mention, which the next install removes again. So install it into the profile first (step 3, and
mind the absolute-path rule there), then link:
bash
dsh plugin --profile web add "file:C:/absolute/path/to/dsh-plugins/plugins/dsh-hooks"
node scripts/dev-link.mjs

Do the same for the desktop's profile — including its ERR_PNPM_UNEXPECTED_STORE fallback in step 3, where you add the dependencies line and the dsh.profile.bundles entry by hand. That path needs no pnpm at all, so it is also the safe one while the desktop is running.

One asymmetry worth knowing: pnpm records a directory argument as link: rather than file:,
so newer entries in a profile look different from older ones. link: is the stronger of the
two — a file: dep is re-materialised as a frozen copy by every install, which is the whole reason dev-link exists, while a link: stays a symlink to the source. Both work, and dev-link matches on dependency names, so a profile mixing the two is managed identically.

Confirm the result rather than assuming it, because the failure is silent — a declared but unlinked plugin serves a frozen copy, and edits simply never appear however often you rebuild: bash
node scripts/verify.mjs # want: "N profile install(s) track this repo, 0 frozen"

0 frozen is the number that matters.

A newly added plugin needs a profile restart before it exists at all; a browser refresh only
picks up client-half edits to plugins the harness has already mounted.

6. Verify
bash
node scripts/verify.mjs # static checks, no server needed
dsh --profile web --port 38111 --no-open # then, in another shell:
node scripts/verify.mjs --port=38111 # adds the live /api probes

Cross-platform, exits non-zero on failure. It reports the dsh version on each surface, then for every package resolves each @deepseek-ai specifier its built output imports and asserts it lands in the dsh CLI's own copy (never a .pnpm store path), checks the other runtime dependencies resolve, imports each host entry point, and confirms each client bundle still carries its window.__ModuleLoader__.load wrapper. With --port it POSTs each host endpoint and requires 200 with "ok":true.

404 on a probe means the package's ./typert export wasn't registered — see the plugin's AGENTS.md. Then open the UI and confirm the tabs render; Promise.allSettled in the client swallows failures, so a tab can render while every call fails.

Run this after upgrading dsh. A harness upgrade doesn't break pnpm test — that only proves this repo is self-consistent. What it breaks is resolution and the wire contract, which is what this checks.

When something is wrong at the harness level rather than the plugin level — a session that won't load, a workspace showing no history, a hand-edited registry that reverts — see TROUBLESHOOTING.md. Most of those failures are silent.

CI

.github/workflows/ci.yml runs on every push to main and every pull request: install,
anchor the peers against a real dsh, build, typecheck, pnpm run test, and then a
git diff --exit-code that fails if the committed lib/ bundles no longer match their
sources. The harness loads lib/, not src/, and those bundles are committed — so a stale one ships code that was never reviewed.

Until it existed the suites ran only inside publish.yml, which is to say at release time,
after the code was already merged. The .githooks/pre-commit hook runs the same checks
locally, but it validates the working tree and --no-verify skips it; this runs against the
real commit on a clean machine.

Publishing

.github/workflows/publish.yml publishes every package that isn't already on npm at its
current version, so a re-run after a partial failure is safe. It installs the harness and runs
scripts/anchor.mjs first (the tests can't resolve the peers otherwise), then build, test and
verify.mjs, and publishes with npm provenance.

Trigger it by publishing a GitHub release — the tag must match the package versions — or manually via workflow_dispatch, which also takes a dist-tag and a dry-run toggle.

It needs an NPM_TOKEN secret on this repository: Actions secrets are write-only and
cannot be shared between user-account repos, so a token set elsewhere isn't visible here.

Development
bash
pnpm install # all packages
pnpm run build # pnpm -r --if-present run build
pnpm run test # pnpm -r --if-present run test

Client-half edits deploy on a browser refresh; host-half edits need a profile restart. Registering a new ./typert export needs a full restart either way — the loader caches its per-package verdict for the process lifetime.

pnpm run test also runs check-type-scale.mjs and check-tokens.mjs across every plugin —
one type scale (11/12/13/14/16/20/24 px) and no var(--dsw-) naming a token the harness does
not define. A misspelt token never errors: CSS falls back to the second argument and silently
stops following the theme. The .githooks/pre-commit hook runs the same two.

It runs check-suites.mjs first, which fails any plugin that declares no test script.
That check exists because pnpm -r --if-present run test skips such a package silently and
still reports success — which is how dsh-skills shipped eleven releases with no tests while
pnpm test passed throughout.

Each package also ships opt-in probes that are not part of pnpm test — mostly headless Chrome against the built* bundle, asserting things a unit test cannot see: that opening a diff does not move a row, that the branch menu stacks above the panes, that the todo modal clears DSH Desktop's window-drag strip, that the git watcher wakes on a real commit. They are listed in each package's AGENTS.md. Two are worth knowing about before you run them: dsh-todo's test:agent drives a real model (needs credentials, runs in a throwaway DSH_HOME), and dsh-mission-control's test:installed reads machine state — the copies a profile actually serves — rather than anything the repo owns.

The root postinstall re-links profiles after every install (see step 5).

Workspace configuration lives in pnpm-workspace.yaml: pnpm 11 ignores pnpm blocks in package.json, build permissions are allowBuilds (a map, not the older onlyBuiltDependencies list), and autoInstallPeers is off because the @deepseek-ai/ peers are dev-preview and partly unpublished.

One trap worth knowing: DSH Desktop runs a profile-repair install on startup that prunes this repo's per-package node_modules — which takes zod with it and makes the harness refuse to boot (Cannot find package 'zod'). Recovery is pnpm install at the root, whose postinstall re-links the profiles.

The other trap has no recovery that cheap: never point DSH_HOME at a home another harness is already using. Testing against DSH Desktop's home while the app is open corrupts the sessions the app has open — silently, across workspaces, and you only find out at a later restart when the history refuses to load. A different --profile does not help: sessions/ is a sibling of profiles/, so every profile shares one session store. Use a throwaway DSH_HOME, or query the running app's own /api endpoint instead of starting a second harness. Repair is possible but manual — see TROUBLESHOOTING.md.

Repository layout

plugins/ one self-contained package each (pnpm workspace members)
scripts/ verify.mjs — check the plugins against your installed dsh anchor.mjs — point each package's @deepseek-ai at that dsh host-deps.mjs — locate the installed dsh (shared by the two above) dev-link.mjs — postinstall entry; dispatches to .ps1 / .sh dev-link.ps1 — anchor + junction into profiles (Windows) dev-link.sh — anchor + symlink into profiles (macOS/Linux) check-type-scale.mjs — one type scale across every plugin check-tokens.mjs — every var(--dsw-) must be a real token check-suites.mjs — every plugin must declare a test script that runs link-superpowers-skills.mjs — link an upstream superpowers clone's skills

AGENTS.md repo conventions and the rules that are not obvious
TROUBLESHOOTING.md harness-level failure modes, mostly silent ones

dsh-todo, dsh-git and dsh-weather were consolidated here from standalone repos.

License

MIT

About the author

Plugins and walkthroughs on YouTube @codingmenace — I build AI coding tools in public, including DeepSeek Harness itself. More at dennisrongo.com.
预览分享卡片长什么样 ↓dennisrongo/dsh-plugins
分享 · DeepSeek Harness Hub
dennisrongo/dsh-plugins

为 dsh 提供跨 CLI 与桌面端即装即用的插件集合

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