dsh-bootstrap
给第一次用 dsh 的人一个「十分钟内把环境配好」的入口。它只做四件事,不多做:
- 按领域推荐插件 —— 用 GitHub Star 与 npm 近月下载量加权排序,数据来自 dsh 官方插件目录。
- 一键安装全部推荐 —— 先给计划(装什么、装哪个版本、会写进 profile 的哪些条目),确认后才执行。
- 生成环境码 —— 把「当前 profile 装了哪些插件、什么版本、哪些是启用的」压成一个可复制的短字符串。
- 导入环境码并一键切换开关 —— 别人把环境码发给你,粘进去就能复刻;或者你已装好插件,只想在不同组合间切换。
不做的事:不改插件运行时、不代理模型调用、不动 profile 之外的全局配置。
English — An onboarding plugin for DSH. It (1) recommends plugins across eight domains, ranked by a
weight on GitHub stars and npm monthly downloads, (2) installs the whole set in one click behind a
reviewable plan, (3) exports a compact environment code for the current profile, and (4) imports
someone else's code to clone it — or just to flip the enabled set. Install:
dsh plugin --profile <profile> add dsh-bootstrap. Panel: a floating avatar in the bottom-right corner
opens a drawer with four tabs. CLI: dsh-bootstrap recommend | install-all | env export | env import <code> | env switch <code> --only-toggle. No network calls are made at startup, every
request times out after 8 s, and a failure only ever costs you the panel, never the host.
安装
# 装进指定 profile(把 desktop 换成你的 profile 名)
dsh plugin --profile desktop add dsh-bootstrap
# 装完重启 dsh,让插件层重新加载
本地开发时可以直接装目录:
dsh plugin --profile desktop add file:C:/path/to/dsh-bootstrap
具体子命令以你所用 dsh 版本的 dsh plugin --help 为准;本插件只依赖 profile 层的 dsh.profile.bundles 语义,不依赖某个特定 CLI 参数。
装好后,web profile 会在右下角出现一个圆形按钮(🚀),点开就是四标签面板。非 web profile 请用命令行。
面板的交互约定:
- 鼠标悬停任意推荐卡片或安装计划里的任意一行,都会显示简介 + GitHub Star / npm 近月下载(两个指标分开写,不混为「安装量」)+ 需要的 dsh 版本。
- 单击「一键安装」页里的任意一行即可勾选它,底部会出现「安装选中的 N 个」,只对选中的包执行;每行右侧还有一个「单独安装」按钮,点一下就只装那一个。
- 收起:抽屉右上角有「收起」按钮和
×,按 Esc 也可以收起;抽屉展开时悬浮球会挪到抽屉左侧(窄屏下隐藏),仍然可以点它切回来。
- 独立页面入口
http://127.0.0.1:19387/dsh-bootstrap/panel,适合没有注入权限的环境(例如桌面壳的注入表是启动时快照,插件后装需要重启一次才会出现悬浮球)。
四个功能的用法
1. 推荐:recommend
dsh-bootstrap recommend
dsh-bootstrap recommend --json --per-domain 3
dsh-bootstrap recommend --star-weight 0.7 --download-weight 0.3
dsh-bootstrap recommend --offline # 只用本地快照与内置种子,不联网
输出按领域分组,每个插件给:npm 包名、一句话用途、仓库 Star、npm 近月下载量、兼容的 dsh 版本范围、推荐理由。
注意文案区分了两个概念:
- 仓库 Star 是 GitHub 仓库的热度,不等于安装量;
- npm 近月下载量 才是「有多少人在装」的近似指标。
两者按权重加权:默认 starWeight: 0.4、downloadWeight: 0.6(都会自动归一化到和为 1)。覆盖的 8 个领域:
| 领域 | 说明 |
|---|
| 插件市场与发现 | 先有市场,才知道还有什么可装 |
| 计费与用量 | 看清 token 与花费 |
| 上下文压缩 | 长任务的前提 |
| 界面工作台 | 侧栏、面板、多面板布局 |
| 多 Agent 协作 | 子代理、任务板、编排 |
| 视觉 / 多模态 | 让 Agent 看得懂图 |
| 文件与终端 | 远程主机、终端、文件浏览 |
| 安全与审计 | 权限规则、自动复核 |
候选池优先取 dshmarket / @ace-zone/dsh-market 使用的同一份官方 registry(https://awesome-dsh-plugin.com/plugins.json)。市场插件不是硬依赖:没装市场时,插件用内置种子清单 + npm/GitHub 实时查询照样能推荐。
面板的「推荐」页会提示「建议先安装 dshmarket」——这只是引导,不是强制。
2. 一键安装:install-all
dsh-bootstrap install-all # 打印计划 → 交互确认 → 执行
dsh-bootstrap install-all --yes # 非交互环境必须显式确认
dsh-bootstrap install-all --all # 装完把全部推荐都启用(默认只启用核心推荐)
dsh-bootstrap install-all --json # 只输出计划,不执行
执行前一定会先给出计划,逐条列出:包名、精确版本、所属领域、「安装 / 已安装跳过 / 版本没锁定而跳过」、「装完是否启用」。
硬性行为:
- 幂等:
dsh.profile.bundles 里已有的包不会重复 add。
- 失败隔离:单个包失败只进「失败」清单,不中断其余包。
- 版本锁定:计划阶段尽量把版本钉到精确版本,写进报告的
lock,环境码复刻时还原同一版本,而不是重新解析 latest。
- 不自动全启用:只启用推荐清单里标记为「核心推荐」的子集,其余保持 installed-but-disabled,交给用户或环境码切换。
- 串行安装:npm 会写同一个
package.json,并发必然互相覆盖。
执行完输出一份报告:成功 / 跳过 / 失败 三类,各自的包名与错误原因。
3. 环境码:env export
dsh-bootstrap env export # 打印环境码
dsh-bootstrap env export --out env.txt
dsh-bootstrap env export --json # 同时给出 manifest.yaml + lock.json
面板的「我的环境」页有复制按钮,并把历史环境码存在浏览器本地(不上传)。
4. 导入 / 切换:env import 与 env switch
# 粘贴环境码或给文件路径,先看差异,再决定
dsh-bootstrap env import DSHB1z.pdJBT8MgFMDx...
dsh-bootstrap env import env.txt --yes
# 已经装好全部插件,只想切开关
dsh-bootstrap env switch env.txt --only-toggle
env import 的流程固定为:解析 → 差异预览 → 确认 → 执行 → 收敛报告。
差异预览会分成五栏:需要启用、需要停用、需要安装、已经是启用状态(无需改动)、环境码未包含,将保持现状(不删除用户已有的插件)。
--only-toggle(面板上的「仅切换开关、不安装」)的语义是:只改 bundle 可见性,缺的包只报告、不安装也不启用。
环境码格式
环境码是一个自包含字符串,不要求上传文件:
DSHB1.<base64url(JSON)> # 明文版
DSHB1z.<base64url(deflateRaw(JSON))> # 压缩版(默认选更短的那个)
解码后的 JSON 就是 dshenv 风格的声明式清单:
{
"v": 1,
"createdAt": "2026-10-16T00:00:00.000Z",
"profile": { "name": "desktop", "dshVersion": "1.0.0", "dshRange": "^1.0.0" },
"plugins": [
{ "name": "dshmarket", "version": "1.66.27", "enabled": true },
{ "name": "dsh-cost-meter", "version": "1.8.29", "enabled": false }
],
"digest": "d63144265c9adbfb"
}
plugins[].enabled 对应 dsh.profile.bundles 是否包含该名字;false 表示「装了但停用」。
digest 是规范化 JSON(键排序)的 SHA-256 前 16 个十六进制字符。导入时校验,不一致会拒绝应用并提示内容可能被改过。
- 环境码里不含 API key、token、密码、本机绝对路径。生成前会做一次敏感内容扫描(字段名与值都查),命中就直接阻止生成,而不是悄悄脱敏。
- 太长时(默认上限 6000 字符)会提示改用
manifest.yaml + lock.json 导出;两者内容等价,环境码始终是受支持的主路径,短链/文件只是补充。
增量语义(导入时):
- 装缺失的包(精确版本优先);
- 不删除用户已有、但环境码里没有的插件,只在预览里标注「保持现状」;
- 按环境码写启用/停用;
- 执行完再读一次磁盘做收敛自证,不信任自己的返回值。
配置
改 cordis.patch.yml 里本插件的 config 即可(宿主若把插件 schema 展示到设置页,也会出现在那里):
| 键 | 默认 | 说明 |
|---|
starWeight | 0.4 | GitHub Star 权重 |
downloadWeight | 0.6 | npm 近月下载量权重 |
requestTimeoutMs | 8000 | 外部请求超时,超时即降级 |
perDomain | 2 | 每个领域推荐几个(1–5) |
autoRefresh | true | 为 false 时只在用户手动刷新时才重新拉目录(默认 5 分钟内复用内存缓存) |
profile | "" | 目标 profile,空表示当前 profile |
catalogUrl | 官方 registry | 插件目录地址 |
downloadsApi / npmRegistry / githubApi | npm / GitHub 官方 | 指标来源 |
offline | false | 强制离线 |
coreOnlyDefault | true | 一键安装后只启用核心推荐 |
maxEnvCodeLength | 6000 | 环境码长度上限 |
panel | auto | auto 按 profile 判断 / always / off |
已知限制
- 离线行为:GitHub 或 npm 不可达时,会依次降级到「本地缓存快照」→「内置种子清单(16 个插件,2026-10-16 快照)」。此时 UI 会明确标注离线快照,数据可能过期,Star/下载量数字是旧的;一键安装前会尝试对缺版本的条目做**有上限(12 条)**的实时补全,补不到就把它列为「版本没锁定,跳过」,不会盲装
latest。
- Star ≠ 安装量:UI 上两者分开显示,排序是加权结果,不代表「最多人用」。
- 文件兜底后端:如果既没有
pluginManager 也没有 dsh CLI,插件会退化为直接改 profile 清单。这种模式下不会更新 cordis.patch.yml,可能导致市场页与官方插件页状态不一致(这是 dsh 已知的坑)。插件会在 UI 与报告里显著提示,并建议重启 dsh 后复查。有 pluginManager 时优先走 setBundleEnabled / addProfileBundle / removeBundle。
- dsh 版本兼容范围:面向
manifestVersion: 1 的 profile 布局(package.json 的 dsh.profile.bundles + cordis.patch.yml)。peer 依赖声明为 @deepseek-ai/cordis ^4.0.1、@deepseek-ai/schemastery ^3.18.1(后者可选)。环境码里会写入 dshRange,导入时只做提示、不做强制阻断;环境码格式版本固定为 v1,将来不兼容时提升 v。
- 不跨机器还原路径:环境码刻意不含路径,所以任何依赖本机绝对路径的本地插件(
file: 依赖)只能人工处理。
- 不代装 dsh 本体:只处理 profile 层插件。
- 同一时间只建议一个写者:安装与切换都会写 profile;如果同时有别的进程在改,可能互相覆盖。插件在切换后会做收敛自证并给出告警,但无法阻止并发写。
工程结构
dsh-bootstrap/
├── package.json # dsh.bundle.patch 指向 cordis.patch.yml
├── cordis.patch.yml # bundle 层:把本插件 insert 进 profile 的层栈
├── bin/
│ └── dsh-bootstrap.mjs # 命令行:recommend / install-all / env export|import|switch
├── lib/
│ ├── index.js # 插件入口(apply 全程 fail-soft)
│ ├── config.js # schemastery 配置 schema
│ ├── defaults.js # 默认值 + 归一化(纯 JS,CLI 也能用)
│ ├── sources.js # 带超时的取数:registry / npm / GitHub
│ ├── cache.js # 本地快照缓存(只存指标,不存凭据)
│ ├── seed-catalog.js # 8 个领域 + 16 个种子插件(离线兜底)
│ ├── catalog.js # 候选池加载:live → cache → seed
│ ├── recommend.js # 加权打分、领域归属、推荐理由
│ ├── install.js # 计划构建 + 串行执行 + 失败隔离
│ ├── envcode.js # 环境码编解码、摘要、敏感扫描
│ ├── switch.js # 差异计算 + 开关收敛
│ ├── backend.js # pluginManager → CLI → 文件 三层降级
│ ├── profile.js # profile 清单读写(原子写)
│ ├── service.js # 服务层:把上面这些装配起来,面板与 CLI 共用
│ ├── routes.js # HTTP 路由(exact 匹配 + 同源校验)
│ ├── client/
│ │ ├── bootstrap.js # 面板(四标签,纯 DOM,无构建步骤)
│ │ └── bootstrap.css
│ └── types/index.d.ts # 类型定义
└── test/
├── bootstrap.test.mjs # 核心行为(离线降级、失败隔离、收敛、幂等、敏感拦截、同源校验)
├── load.test.mjs # 真加载:import 入口 + 假宿主 ctx(需要宿主提供的 schemastery)
└── panel.smoke.mjs # 面板脚本在最小 DOM 桩上挂载 → 点开 → 渲染
设计上的两个关键取舍:
- CLI 与面板共用
lib/service.js,所以「命令行里确认过的计划」和「面板里确认过的计划」是同一份逻辑,不会出现两套语义。
- 写操作一律走
backend:能拿到 pluginManager 就用它的 setBundleEnabled / addProfileBundle / removeBundle;拿不到才退到 dsh plugin CLI;最后才是直接改清单(并显著告警)。
测试
npm test
四个必需用例都已覆盖:
- 推荐引擎离线降级 —— 外部数据源抛
ENOTFOUND 时回退到缓存/种子,并标记 offline: true;offline: true 时确认不发生网络调用。
- 一键安装单个失败不中断 —— 三个包里中间一个抛 404,其余两个照常安装并启用,失败项被隔离进
failed。
- 环境码导入后开关收敛 —— 环境码要求 A 启用、B 停用,当前相反,执行后
converged: true,且环境码之外的 C 保持启用。
- 重复导入同一环境码幂等 —— 第一次安装+启用,第二次差异为空,不产生任何新的写调用。
另外还有五条,共 13 个用例:
- 敏感凭据拦截:环境码里出现疑似 key/token 时直接拒绝生成。
- 面板接口同源校验:跨站请求返回 403,且拒绝发生在读任何 profile 数据之前。
- 真加载:
test/load.test.mjs 真的 import 插件入口,用一个假的宿主 ctx 走一遍 apply(),断言会注册 10 条路由、把面板脚本塞进索引注入表、并且在残缺宿主(没有 inject、没有 webServer、panel: 'off')下安静降级而不抛。同时校验 schemastery 的链式调用(.min/.max/.step/.default/.description)在这一版上确实存在 —— 这类拼错在 import 期就会炸,普通单测抓不到。
- 面板冒烟:
test/panel.smoke.mjs 在最小 DOM 桩上跑面板脚本,验证「挂载 → 悬浮按钮 → 点开抽屉 → 四个标签 → 推荐卡片渲染出真实的 Star/下载量千分位」。
- 面板外观:同一套 DOM 桩上验证「悬浮球里是内嵌的 Q 版头像、标题栏有品牌区与副标题、四个标签都带图标、底部有版本与 profile 徽标」,以及「安装页每行有悬停简介、单击选中、单独安装、收起回到悬浮球」。
load.test.mjs 需要 @deepseek-ai/schemastery(dsh 宿主自带)。直接 npm install 装不到它,
所以该组用例在拿不到依赖时会整组 skip 并说明原因,而不是假装通过。想在本地跑全:
把宿主 profile 的 node_modules/@deepseek-ai/schemastery 链到本包的 node_modules/@deepseek-ai/schemastery 即可。
面板外观
面板是「悬浮球 + 右侧抽屉」:右下角一颗 56px 的圆形头像按钮(抽屉展开时让到抽屉左侧,窄屏隐藏),点开后是品牌头部(头像 + 标题 + 副标题)、分段式四个标签(推荐 / 一键安装 / 我的环境 / 导入环境)、可滚动主体、底部一条版本信息栏。
- 图标:Q 版 DeepSeek 娘(鲸鱼娘)头像,96×96,做成了圆形裁切 + 淡蓝渐变底,以 base64 内嵌在
lib/client/bootstrap.js 里。这样不额外发请求,也不受「插件路由是否已注册」影响(本插件新增路由必须重启宿主进程才生效,而面板脚本每次请求都从磁盘现读)。
- 图标来源:
fornarwhal/deepseek-whale-girl-icon(GitHub),角色形象出自 上善无形(OC「溟月」)的 DeepSeek 二创,另经 ZipZipPipe、QYQCAMIAO 修复。原图授权为 CC BY-NC-SA 4.0:仅非商业使用、需署名、衍生作品同协议。若你要把 dsh-bootstrap 用于商业场景,请自行替换 AVATAR 常量为其他图标。
- 主题:全部颜色走
light-dark() 与 --dsb-* 变量,跟随宿主明暗主题;选择器一律挂在 #dsh-bootstrap-root 下,不污染宿主页面。
安全说明
- 面板 HTTP 接口全部
kind: 'exact',会越过宿主的 /api 前缀栅栏,因此路由内部自己做同源校验:拒绝 Sec-Fetch-Site: cross-site,Origin 与 Host 不同源直接 403。
- 环境码生成前扫描敏感字段名与值,命中即拒绝,不落任何字符串。
- 所有请求有超时(默认 8 秒),超时降级,不阻塞 dsh 启动。
apply() 内任何异常只记录、不抛出;面板脚本加载失败也只是面板不出现,不影响 dsh 本身。
License
- 代码:MIT。
- 面板头像素材:Q 版鲸鱼娘,不是 MIT,也不允许商业使用。作者与授权见
docs/ART-CREDITS.md(CC BY-NC-SA 4.0)。商业场景请替换
lib/client/bootstrap.js 里的 AVATAR 常量为自有图标,或直接删掉 <img>。