DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

VCPr0j3k7 /

VCPr0j3k7/dsh-plugin-market

Verified

Discover and install DSH plugins from the GitHub topic dsh-plugin — browse, read READMEs with optional Chinese translation, install in one click

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@66d679e9

dsh-plugin-market

License: MIT Node.js

English | 简体中文

为 DeepSeek Harness (DSH) 桌面版补上第三方插件的发现能力:从 GitHub topic dsh-plugin 拉取插件列表,支持浏览、预览 README(可选 DeepL 中文翻译与本地关键词提取)、一键装入当前 profile,并负责 GitHub 加速代理在客户端的配置读取。

本插件以独立 bundle 形式装入 DSH profile,同时提供宿主半边(Node.js)与客户端半边(浏览器)。


目录

  • 问题背景
  • 环境要求
  • 安装
  • 卸载
  • 工作原理
  • 路由表
  • README 预览与翻译
  • GitHub 加速代理
  • 配置项
  • 启用条件
  • 测试
  • 已知限制
  • 许可

问题背景

DSH 官方桌面版自带的插件管理器只覆盖「已经知道名字」的插件:填入一个包名或规格,它负责 安装与卸载。它缺少的是发现 —— 用户无从知道存在哪些第三方插件、它们各自做什么。

DSH 官方没有插件市场。社区实际使用的「插件库」是 GitHub topic dsh-plugin:仓库打上该 topic 即可被搜到。本插件 就是建立在该 topic 之上的浏览与安装界面。

补充说明两点:

  1. 本插件不替代官方插件管理器。装、卸、启停等写操作仍与官方 profile 语义一致(改写 dsh.profile.bundles),本插件只是把入口放到了同一个页面里。
  2. 发现完全依赖 GitHub 公开 API,不经过任何中间服务。

环境要求

项 要求
Node.js ^22.19.0 || >=24
DeepSeek Harness 官方桌面版(使用 desktop profile;插件在该 profile 下开发与验证)
pnpm 无需单独安装,使用官方桌面版随包的 pnpm
DeepL API Key 可选,仅 README 机器翻译需要;不配置时其余功能不受影响

安装

# 用官方桌面版随包的 pnpm 装进 profile
pnpm add "file:<插件目录绝对路径>"
# 或者直接从 GitHub 装
pnpm add "github:VCPr0j3k7/dsh-plugin-market"

装完必须登记 bundle(关键步骤)

pnpm add 只写 profile 的 dependencies,不会把包登记进 bundle 层栈。DSH 只在启动时 按 dsh.profile.bundles 装配插件树,因此还需要手动把包名加进 profile package.json 的 dsh.profile.bundles 数组:

// ~/.dsh/profiles/desktop/package.json
{
  "dependencies": {
    "dsh-plugin-market": "file:/path/to/dsh-plugin-market"
  },
  "dsh": {
    "profile": {
      "bundles": [
        // …官方 bundle…
        "dsh-plugin-market"
      ]
    }
  }
}

漏掉这一步的表现是:依赖装好了、node_modules 里也有这个包,但界面上一片空白、日志里也 没有任何报错 —— 因为它从未被装载。

替代做法是改用官方 CLI 安装,它会在 pnpm 执行完毕后运行一次 reconcilePlugins(),把声明了 dsh.bundle.patch 的依赖自动追加进 dsh.profile.bundles:

dsh plugin --profile desktop add github:VCPr0j3k7/dsh-plugin-market

无论用哪种方式,安装后都需要重启 harness:插件在启动时装载,运行中的实例不会热更新。

卸载

pnpm remove dsh-plugin-market

若 dsh.profile.bundles 中仍有残留条目,手动删除它,然后重启 harness。

工作原理

本插件由两个半边组成,两半都是独立装载的。

宿主半边(index.js)是一个 Cordis 插件,导出 name / inject / apply(ctx)。它在 ctx.webServer 上只注册一条 kind: 'prefix' 的 HTTP 路由 /dsh-plugin-market/api, 前缀内部的派发由 host/router.mjs 完成。这样做的好处是注册点只有一处,卸载时由框架自动 清理,不会在 webServer 里留下半张路由表。

约定所有接口统一返回 { ok, data | error },业务失败也回 HTTP 200(ok: false);HTTP 状态码 只用于表达「路由不存在」这类传输层事实。

客户端半边(client.js)通过 window.__ModuleLoader__.load({ id, factory }) 装载,注册到 三个官方槽位:

槽位 内容
sidebar.panellist 侧边栏入口「插件市场」
main 插件获取页本体
shell.overlay README 全文预览抽屉

客户端不写死宿主基址。页面来源(origin)有三种可能 —— 宿主自己的 HTTP 地址、外壳的自定义 scheme、以及不透明来源(location.origin === "null")—— 因此第一次请求时按 ["", "http://dsh.internal"] 顺序逐条探测,用 /info 返回的 data.plugin 是否等于本插件模块 id 来判定哪条真的通。 仅凭「拿到 200」不够:SPA 的兜底路由会把未知路径也回成 200 + HTML。选定的基址会打印到浏览器 控制台。

安装进度走事件队列:宿主半边维护一个 seq 单调递增的环形队列(最多 400 条),客户端每 300ms 轮询 GET /events?since=<seq> 拉增量。选择轮询而不是 SSE,是因为外壳对自定义 scheme 的转发是否会缓冲响应体无法确认;轮询没有这个未知数,代价只是 300ms 的粒度。

取消安装由 AbortController 实现:POST /market/install 为每个仓库登记一个 controller, POST /market/cancel 触发 abort(),下载循环随之中断。

重启宿主做不到。 插件本身就是宿主加载的一部分,没有让宿主重启自身的办法,而尝试杀进程 会连带丢掉用户的会话。因此 POST /restart-host 如实拒绝并返回「请手动退出并重新打开」的 提示;界面上的「立即重启」按钮据此显示失败原因。

路由表

前缀:/dsh-plugin-market/api。路径均相对于该前缀。

通用路由(host/router.mjs,各插件共用):

方法 路径 作用
GET /info 宿主状态与插件 id(客户端据此判定基址是否可用)
GET /config 读共享配置
POST /config 深合并写回共享配置
POST /open-external 用系统默认程序打开 http/https 地址
POST /pick-directory 选择目录(优先官方 directoryPicker)
GET /restart-pending profile 是否有改动尚未生效
POST /restart-host 如实拒绝(插件无法重启宿主)
GET /events 按 ?since= 拉取事件增量

本插件路由(index.js):

方法 路径 作用
POST /market/list 列出 topic 下的仓库(带 1 小时磁盘缓存)
POST /market/details 取单个仓库的 README / 译文 / 关键词
POST /market/install 下载并装入当前 profile(支持取消)
POST /market/cancel 取消指定仓库正在进行的下载
GET /market/translate-status 翻译功能的当前状态(是否开启、是否有密钥)

客户端半边只声明本页实际用到的路由,不留无人调用的死路由。test/check.mjs 会从 client.js 提取它调用的全部路由,逐条确认宿主路由表里有实现 —— 漏一条的表现通常是「某个按钮永远没 反应」,排查成本很高,因此纳入自检。

README 预览与翻译

展开一张卡片时会按顺序做四件事:

  1. 找作者自带的中文 README:列出仓库根目录,按常见文件名(README.zh-CN.md、 README_zh.md、README.zh.md …)匹配;命中的话直接使用,比机器翻译准确,也省掉一次 翻译配额。正文走 raw.githubusercontent.com,不消耗 API 配额。
  2. 抓 README 正文:用 GitHub 的 readme 端点(自动处理 .rst、docs/ 下的文件、大小写 差异),而不是猜 README.md 的路径。
  3. 提取中文关键词:完全本地的词典匹配,不消耗任何配额,翻译关闭时照常工作。
  4. 翻译(可选):仅当正文基本不含中文时调用 DeepL。判据是 CJK 字符占比低于 8% 且中文 绝对数少于 30。

界面上的 README 抽屉提供三种视图:

视图 内容 上限
完整原文 原始 Markdown,由内置渲染器渲染 40 万字符(超出则截断并如实标注)
中文译文 DeepL 译文,纯文本 6000 字符(README_LIMIT,受翻译配额约束)
Markdown 源码 原始 Markdown 原文 同「完整原文」

译文另按仓库缓存在磁盘上,重复打开不再计费。详情结果在进程内按仓库名缓存(最多 60 条)。

README 渲染器是自写的子集实现:支持围栏代码块、ATX 标题、水平线、引用、有序/无序列表 (含嵌套)、表格、段落,以及行内代码、粗体、斜体、删除线、链接、图片;另有一个 HTML 子集 渲染器处理 README 里常见的内联排版(<table> 导航、<details> 折叠块、居中 logo 等)。

输出的是 React 元素树而不是 HTML 字符串:README 来自任意第三方仓库,属于不可信输入,走 dangerouslySetInnerHTML 等于把 XSS 装进门。HTML 子集渲染器采用标签与属性白名单,名单外的 标签剥掉但保留文字,名单外的属性直接丢弃,因此即使 README 里塞了 <script> 或 onclick=, 也只会变成一段无害的文字。

GitHub 加速代理

直连 GitHub 在部分网络环境下又慢又容易失败,瓶颈通常是 TLS 握手与首字节延迟,而不是带宽。 本插件支持把对 GitHub 的请求改写成走用户自建的反向代理。

三条设计约束:

  1. 默认关闭,地址必须由用户显式填写。 关闭时所有请求走官方地址,行为与没有这个功能时 完全一致。
  2. 只改地址,不改语义。 路径前缀与上游域名一一对应,令牌、Accept 头、请求方法都原样 透传,因此开不开代理拿到的响应体是同一份。
  3. 自己跟随 302。 GitHub 会把下载类请求重定向到另一个域名(api.github.com → codeload.github.com,release 则跳到 release-assets / objects)。若交给 fetch 自动 跟随,它会直接连那个域名,等于绕开了代理。因此这里手动跟随,把 Location 里的 GitHub 域名重新改写成代理地址再请求。

路径前缀与上游域名的映射:

前缀 上游域名
/api api.github.com
/raw raw.githubusercontent.com
/codeload codeload.github.com
/gh github.com
/objects objects.githubusercontent.com
/release-assets release-assets.githubusercontent.com

这张表必须与服务端反向代理的 location 配置一一对应。 对不上不会报错,只会静默 404。 头像(avatars.githubusercontent.com)与 README 里的配图不在代理范围内:它们是渲染器直接 拉取的装饰性资源,代理它们不会增加功能,反而扩大了暴露面。

设置页的「测试连通性」会打两个探针 —— 代理根路径与一条真实的 raw 请求 —— 两个都通过才算 通过。只看根路径是不够的:它由反向代理直接应答,上游通不通是另一回事(resolver 未配置、 上游证书过期、proxy_pass 缺少 rewrite,都会让根路径正常而正文 502)。

注意:自建反向代理可能是明文 HTTP。配置了 githubToken 时 Authorization 头会经它明文发出, 开启前请确认链路可信。

配置项

本插件与同族的桌面设置插件共用一份配置:<DSH_HOME>/dsh-extras.json(默认 ~/.dsh/dsh-extras.json)。 固定使用同一路径是为了避免「用户改了一处、另一处不生效」这类在界面上看不出来的问题;用文件 而不是插件间调用,则是因为文件没有「另一个插件尚未启动」的时序问题。

字段 默认值 说明
appName "DeepSeek Harness" 显示名(官方桌面版自己管理窗口标题)
githubProxy.enabled false 是否启用 GitHub 加速代理
githubProxy.baseUrl "" 反向代理基地址;留空时即使开关打开也回落到直连
githubToken "" GitHub API 令牌;留空按匿名请求发送(60 次/小时),填写后走认证额度
translate.enabled true 是否对英文 README 调用机器翻译
translate.endpoint https://api-free.deepl.com/v2/translate DeepL 端点;Free 版密钥以 :fx 结尾,必须走 api-free 主机
translate.apiKey "" DeepL API Key;不内置任何密钥
translate.targetLang "ZH" 目标语言代码(DeepL 使用大写)
translate.characterCount 0 本地累计的已翻译字符数,仅用于显示大致用量
translate.maxCharacters 4000 单次翻译的字符上限

首次运行且配置文件不存在时,会尝试从 DSH-Desktop 外壳的配置(%APPDATA%/DSH-Desktop/config.json) 迁移一次 translate 与 githubProxy,使用户此前填过的密钥与地址继续生效。

配置不做进程内缓存:四个插件是四个独立的模块实例,各自缓存会互相看不到对方的写入。文件只有 几百字节,每次重读的成本可以忽略,换来的是「改完立刻生效」。

启用条件

插件要被装载,必须同时满足两条:

  1. 包出现在 profile 的 dependencies 中(node_modules 里能解析到);
  2. 包名出现在 profile package.json 的 dsh.profile.bundles 数组中。

第二条尤其容易被忽略,见安装一节。

排障提示:官方桌面版的「插件库」开关会重写 dsh.profile.bundles(内部调用 sanitizeProfile(...) 清理非官方 bundle)。若插件在正常运行一段时间后突然失效,首先检查 bundles 中该条目是否仍然 存在。

测试

npm test
# 等价于 node test/check.mjs

test/check.mjs 是纯逻辑自检(34 项),不依赖 DSH 运行时,也不需要把插件装进 profile。 覆盖:

  • package.json 的 dsh.bundle.patch / dsh.client / exports['./client'] 声明及其指向的 文件是否真实存在;
  • 宿主半边能被 import,且导出 name / inject / apply;
  • apply() 不抛错,路由注册在预期的前缀上、使用 prefix 匹配,并交给 ctx.effect 收尾;
  • 路由对齐:从 client.js 提取客户端调用的全部路由,逐条确认宿主路由表里有实现;另以 真实请求验证 GET /info 回 200 且 data.plugin 等于本插件 id,未知路由回 404,前缀之外 的请求不命中;
  • 共享配置的默认值包含 githubToken 字段。

自检会把 DSH_HOME 指向临时目录,不会读写用户真实的 ~/.dsh。

端到端验证:安装并重启桌面版后,侧边栏出现「插件市场」入口,页面能拉到仓库列表;点「README」 从右侧滑出预览;点「安装」在应用内完成下载并在结束后提示需要重启。

已知限制

  • 装完必须重启宿主才会生效。 插件在启动时装配,运行中的实例不会热更新;POST /restart-host 只能如实拒绝,不会真的重启。
  • GitHub API 有速率限制。 未认证时搜索接口约 10 次/分钟、其余接口 60 次/小时。列表结果 缓存 1 小时(磁盘),详情在进程内按仓库缓存最多 60 条;展开详情每次最多发起两次 API 请求。
  • 译文只覆盖前 6000 字符(README_LIMIT)。这是 DeepL 按字符计费带来的硬约束,界面会 如实标注覆盖范围。想看全文请切到「完整原文」。
  • 全文预览上限 40 万字符(README_MARKDOWN_LIMIT),超出部分截断并在界面上标注。
  • 内置 Markdown 渲染器是子集实现。 脚注、定义列表、数学公式、任务列表勾选框等不支持, 遇到时按原文显示。白名单之外的 HTML 标签会被剥掉(保留文字)。
  • README 里的相对图片路径依赖 GitHub raw 地址。若仓库使用 Git LFS 或私有资源,图片仍可能 无法显示。
  • 代理只覆盖上表列出的 6 个 GitHub 域名,头像与 README 配图不走代理。
  • 宿主半边在模块加载期读 process.argv 来定位 profile 目录与随包 pnpm(布局见 host/env.mjs)。脱离官方宿主进程单独运行时,这些位置取不到,安装相关功能不可用。
  • host/yaml-libs.mjs 等通用工具当前在本包内没有调用点,作为宿主层工具保留。

许可

MIT

—/ 5

No ratings yet

Verified DSH bundle

Commit 66d679e92135

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout