@1e0zj/dsh-plugin-mall 0.1.18 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,9 +11,11 @@ Two surfaces: a **Settings → Plugins → Marketplace** tab in the dsh web UI,
11
11
  Curated lists only show what has been reviewed and merged. This marketplace is open by construction: **any repo tagged `topic:dsh-plugin` is discoverable the moment it is pushed** — no submission, no approval queue. To keep that openness usable:
12
12
 
13
13
  - **Automatic verification** — every search result's `package.json` is fetched (jsDelivr/raw dual-source CDN, no API quota) and checked for the official `dsh.bundle` / `dsh.client` manifest. Verified plugins get a green badge; the default "verified only" view filters out ~73% of topic noise (empty repos and unrelated projects riding the tag).
14
+ - **Browse-time compatibility badges** — each card is also statically scanned against your profile before you click anything: declared conflicts, exclusive groups, loader-id collisions (from the repo's patch file), host-module shadowing, and peer/Node/OS ranges. Cards show 适配 / 有风险 / 冲突 / 适配未知 accordingly — advisory only; the install preflight remains the enforcing gate.
14
15
  - **Anti-squatting** — an install prefers the npm tarball only when the registry entry's `repository` URL points back to the same GitHub repo; anything else falls back to the explicit `github:` spec.
15
16
  - **npm-first installs** — registry tarballs are smaller than whole-repo GitHub downloads and come with integrity checks. Lookups follow the registry pnpm actually installs from (profile `.npmrc` → `pnpm config get registry` → npmjs), so a mirror user keeps npm-first instead of silently falling back to whole-repo clones.
16
17
  - **Update management** — installed plugins are compared against the registry `latest`; one-click update per plugin.
18
+ - **Conflict guard** — every install runs an isolated preflight first: the candidate is installed with scripts disabled into a throwaway directory and scanned against the live profile for loader-id collisions, double mounts, host-module shadowing and version/OS/peer ranges. A hard conflict is blocked, warnings require explicit confirmation. The profile's load-bearing files are snapshotted before `pnpm` touches them and restored on failure. A pending install is resolved on the next start, however you start it: this plugin runs recovery as it loads, which only happens because dsh booted far enough to compose the profile. Starting through `guard launch` adds a grace window on top, so a plugin that boots and then crashes seconds later is rolled back and restarted once (see [Startup protection](#startup-protection-guard-cli)).
17
19
  - **Resilience** — rate-limit circuit breaker, GitHub's 1000-result search window handled gracefully, `corepack enable pnpm` self-heal when pnpm is missing, one-click dsh restart (loopback-only, `allowRestart: false` to disable).
18
20
 
19
21
  ## Install
@@ -46,6 +48,48 @@ Restart dsh after installing.
46
48
  > `node_modules`: they are hard-linked into pnpm's global store, and any later
47
49
  > `pnpm add/remove` rebuilds the tree and restores them anyway.
48
50
 
51
+ ## Startup protection (guard CLI)
52
+
53
+ The last line of defense is at **startup**. The package ships a standalone, host-independent CLI (bin `dsh-plugin-guard`, also runnable by path):
54
+
55
+ ```powershell
56
+ # guarded install: isolated preflight → snapshot → dsh plugin add → on-disk validation
57
+ dsh-plugin-guard guard add <spec> --profile web
58
+ # start dsh under startup probation
59
+ dsh-plugin-guard guard launch --profile web -- dsh web
60
+ ```
61
+
62
+ The bare `dsh-plugin-guard` bin resolves only when the profile's (or global) `.bin` is on `PATH`. Two PATH-independent forms:
63
+
64
+ ```powershell
65
+ # installed profile: run the bin from the profile's own node_modules/.bin
66
+ pnpm --dir <profile> exec dsh-plugin-guard guard add <spec> --profile web
67
+ pnpm --dir <profile> exec dsh-plugin-guard guard launch --profile web -- dsh web
68
+
69
+ # development: run by source path
70
+ node <profile>/node_modules/@1e0zj/dsh-plugin-mall/src/cli.js guard add <spec> --profile web
71
+ node <profile>/node_modules/@1e0zj/dsh-plugin-mall/src/cli.js guard launch --profile web -- dsh web
72
+ ```
73
+
74
+ **A plain `dsh web` already resolves pending installs.** The marketplace plugin runs
75
+ recovery when it loads: reaching that point proves dsh booted far enough to compose
76
+ the profile, so the pending marker is committed (or rolled back when the profile
77
+ fails validation). Without this a single install would wedge the profile — every
78
+ later install and uninstall refuses while a marker is outstanding.
79
+
80
+ **`guard launch` is still strictly better**, because it also covers what a plain
81
+ start cannot: a plugin that boots fine and then crashes seconds later. It checks the
82
+ profile's pending-install marker before starting the command after `--`:
83
+
84
+ - **No pending install** — the command runs as-is, inheriting the terminal, and its exit code is preserved.
85
+ - **Clearly broken on disk** — the profile is rolled back to its pre-install snapshot *before* launch, then the command starts on the restored state.
86
+ - **Alive through the grace period** (default **10 seconds**; `--grace-ms <ms>` to change) — the pending snapshot is committed and the wrapper keeps waiting on the process.
87
+ - **Exits 0 inside the grace period** (one-shot command) — the pending snapshot is committed.
88
+ - **Crashes or exits nonzero inside the grace period** — the profile is rolled back and the *exact same command* is restarted once with the restored state (never in a loop); the restarted process's exit code is preserved. SIGINT/SIGTERM are forwarded to the child where the platform supports it; on Windows `.cmd`/`.bat` shims go through `%ComSpec%` with strict per-argument quoting.
89
+
90
+ Limitations: the grace window is the probation period — a failure that only surfaces **after** it (a plugin that crashes minutes in, or on a specific interaction) cannot be rolled back automatically, because committing deletes the active snapshot and `guard recover` then has nothing to restore. `guard validate` still diagnoses the on-disk state, but a post-commit failure needs manual repair — uninstall and reinstall the plugin, or restore a backup you kept separately. Both commands do only **static on-disk validation**; neither proves the plugin actually loads. A corrupt pending marker fails closed: the command is not launched and no unvalidated path is deleted. Preserve the snapshot and repair or restore a trustworthy marker, then run `guard recover`; quarantine the marker only after you have independently verified the profile, or decided to abandon automatic recovery.
91
+
92
+
49
93
  ## Agent tools
50
94
 
51
95
  | Tool | What it does |
@@ -65,9 +109,11 @@ Restart dsh after installing.
65
109
  与策展列表不同:**任何打上 `topic:dsh-plugin` 的仓库推送后立即可被发现**——无需投稿、无需审批。为保证开放性可用,做了这些事:
66
110
 
67
111
  - **自动验证**:逐仓库拉取 `package.json`(jsDelivr/raw 双源 CDN,不占 API 配额),按官方 `dsh.bundle` / `dsh.client` 声明打徽章;默认"只看已验证"视图过滤约 73% 的话题噪音
112
+ - **浏览期适配徽章**:点安装之前,每张卡片就已对照你的 profile 做过一次静态扫描——声明冲突、独占组、loader-id 冲突(取自仓库补丁文件)、宿主模块遮蔽、peer/Node/OS 范围;卡片上直接显示 适配 / 有风险 / 冲突 / 适配未知。徽章只是提示,真正的拦截闸门仍是安装预检
68
113
  - **防抢注**:仅当 npm registry 条目的 `repository` 指回同一 GitHub 仓库时才用 npm 安装,否则回退 `github:` 源
69
114
  - **npm 优先安装**:registry tarball 比整仓库下载更小且带完整性校验;查询用的 registry 跟随 pnpm 实际安装源(profile `.npmrc` → `pnpm config get registry` → npmjs),换了镜像也不会退化成整仓库克隆
70
115
  - **更新管理**:已装插件与 registry `latest` 比对,逐个一键更新
116
+ - **冲突防护**:每次安装先跑隔离预检——候选包在一次性目录里以禁用脚本的方式装好后,对照 live profile 扫描 loader-id 冲突、重复挂载、宿主模块遮蔽和版本/OS/peer 范围;硬冲突直接拦截,警告需显式确认。安装前给 profile 的承重文件拍快照、失败即回滚;pending 安装在下次启动时自动了结,**不挑启动方式**:本插件加载时就跑恢复,而能加载本身就证明 dsh 已经组装好 profile、活到了这一步。经 `guard launch` 启动则多一层观察期——插件启动几秒后才崩的情况也能回滚并原样重启一次(见下方「启动保护」)。
71
117
  - **工程韧性**:限流熔断、GitHub 5xx/超时退避重试(504 瞬时故障不再直达用户)、GitHub 1000 条搜索上限优雅处理、pnpm 缺失时 `corepack` 自愈、一键重启 dsh(仅 loopback,可 `allowRestart: false` 关闭)
72
118
 
73
119
  ## 安装
@@ -121,6 +167,46 @@ dsh plugin --profile web add link:C:\path\to\dsh-plugin-mall
121
167
  > Node 只把参数用空格拼接、不逐参加引号,带空格的路径会被拆成两个参数;
122
168
  > 自己加引号也不行(`"` 属于被拦截的 shell 元字符)。市场会直接拒绝并说明原因。
123
169
 
170
+ ## 启动保护(guard CLI)
171
+
172
+ 最后一道防线在**启动**时。包自带一个独立于宿主的 CLI(bin 名 `dsh-plugin-guard`,也可按路径直接跑):
173
+
174
+ ```powershell
175
+ # 受 guard 保护的安装:隔离预检 → 快照 → dsh plugin add → 落盘校验
176
+ dsh-plugin-guard guard add <spec> --profile web
177
+ # 带启动缓刑期地启动 dsh
178
+ dsh-plugin-guard guard launch --profile web -- dsh web
179
+ ```
180
+
181
+ 裸的 `dsh-plugin-guard` 只有在 profile(或全局)的 `.bin` 在 `PATH` 上时才解析得到。两种不依赖 `PATH` 的写法:
182
+
183
+ ```powershell
184
+ # 已装 profile:从 profile 自己的 node_modules/.bin 里跑
185
+ pnpm --dir <profile> exec dsh-plugin-guard guard add <spec> --profile web
186
+ pnpm --dir <profile> exec dsh-plugin-guard guard launch --profile web -- dsh web
187
+
188
+ # 开发:按源码路径直接跑
189
+ node <profile>/node_modules/@1e0zj/dsh-plugin-mall/src/cli.js guard add <spec> --profile web
190
+ node <profile>/node_modules/@1e0zj/dsh-plugin-mall/src/cli.js guard launch --profile web -- dsh web
191
+ ```
192
+
193
+ **普通的 `dsh web` 就会了结 pending 安装。** 本插件加载时即执行恢复——能加载
194
+ 本身就证明 dsh 已经组装好 profile、启动到了这一步,于是提交 pending 标记
195
+ (profile 校验不过则回滚)。没有这一步的话,装完一个插件就会把 profile 卡住:
196
+ 只要标记还在,之后所有安装和卸载都会被拒绝。
197
+
198
+ **`guard launch` 仍然更强**,因为它覆盖普通启动覆盖不了的情况:插件启动正常、
199
+ 几秒后才崩。它在启动 `--` 之后的命令前检查该 profile 的 pending 安装标记:
200
+
201
+ - **无 pending 安装** —— 命令原样运行(继承终端),透传退出码;
202
+ - **静态校验明显过不了** —— 启动*之前*先把 profile 回滚到安装前快照,再在恢复后的状态上启动;
203
+ - **活过缓刑期**(默认 **10 秒**,`--grace-ms <ms>` 可调)—— 提交 pending 快照,包装器继续守候该进程;
204
+ - **缓刑期内以 0 退出**(一次性命令)—— 同样提交 pending 快照;
205
+ - **缓刑期内崩溃或非零退出** —— 回滚 profile,并用恢复后的状态**原样重启同一命令一次**(绝不循环),透传重启进程的退出码。支持的平台会把 SIGINT/SIGTERM 转发给子进程;Windows 上 `.cmd`/`.bat` 经 `%ComSpec%` 启动,逐参数严格加引号。
206
+
207
+ 限制:缓刑期就是观察期——**之后**才暴露的故障(跑了几分钟才崩、或某个特定操作才触发)无法自动回滚:提交会删掉当前快照,此时 `guard recover` 已无可恢复的东西。`guard validate` 仍能诊断落盘状态,但提交之后的故障只能手工修复——卸载并重装插件(或恢复你另行保留的备份)。两条命令都只做**静态落盘校验**,都不证明插件真的能加载。pending 标记损坏时关闭式失败:不启动命令、不删除任何未校验路径。要**保留快照**、修复或恢复一个可信的标记后再跑 `guard recover`;只有在你已经独立核实过 profile、或决定放弃自动恢复之后,才去隔离(删除/移走)标记。
208
+
209
+
124
210
  ## 工作原理
125
211
 
126
212
  - 双面包(dual-face)插件:`dsh.bundle` 半边挂在 **host 平面**(profile bundle 层),
@@ -148,6 +234,12 @@ dsh plugin --profile web add link:C:\path\to\dsh-plugin-mall
148
234
  (这行会出现在任务日志里)。更新检查读的是 registry 的 `/latest` 端点,
149
235
  不经过该策略,所以两边看到的「最新版本」本就可能不同。
150
236
  首次安装(卡片按钮)不带版本,沿用 pnpm 的策略默认值即可。
237
+ - **一次点击一个任务,日志从第一毫秒开始流**:预检本身就是一个任务,点安装
238
+ 的瞬间就出现在面板里,隔离探针的 pnpm 输出实时写入——而不是让按钮干等几秒
239
+ 再冒出结果。预检通过后由安装任务接管同一条日志、撤掉预检条目,所以面板上
240
+ 始终只有一条记录。运行中只露最后 8 行,落定后折叠成「查看日志(N 行)」。
241
+ - **预检通过直接装**:verdict 为 safe 时不弹任何确认;只有有风险或被阻止才
242
+ 出面板内联卡片(原因列表 + 取消 / 继续),不用模态弹窗打断。
151
243
  - **安装期代码要用户点头**:pnpm 默认拦掉依赖的构建脚本,放行等于让那些命令
152
244
  以用户的权限在其机器上运行(早于任何插件代码加载)——这个决定属于用户。
153
245
  所以被拦时安装**停下**,如实列出要批准的到底是什么:包名@版本、确切的
@@ -184,9 +276,11 @@ git push --follow-tags
184
276
  之间那道缝是可验证地闭合的(`npm view <pkg> dist.attestations` 可查)。
185
277
 
186
278
  workflow 会先校验 tag 与 `package.json` 版本一致、再跑离线 fixture,任一不过
187
- 就不发。**它故意不跑 `npm ci`** —— 框架包的 dist-tags 问题会让它 ERESOLVE
188
- 失败(见上方 `link:` 说明),而这个包没有构建步骤,`npm publish` 也不读
189
- `node_modules`。
279
+ 就不发。它不在仓库根目录跑 `npm ci` —— `package.json` 里的框架包是宿主
280
+ 提供的 peer,并非需要装进发布包的开发副本;这个包也没有构建步骤,
281
+ `npm publish` 本身不读 `node_modules`。guard/cli 自测需要的测试依赖单独放在
282
+ `.github/fixtures/guard-tests`,由提交进仓库的 `package-lock.json` 固定完整解析树,
283
+ CI 只在该隔离目录运行 `npm ci --ignore-scripts`。
190
284
 
191
285
  > 首次配置需在 npmjs.com 的包设置里添加 Trusted Publisher(GitHub Actions +
192
286
  > 仓库名 + `release.yml`),之后所有长期 token 都可以删掉。
@@ -210,6 +304,18 @@ workflow 会先校验 tag 与 `package.json` 版本一致、再跑离线 fixture
210
304
  加 `--offline` 只跑不联网的 fixture(宿主依赖检测的判据固化在那里 ——
211
305
  它当初的实测对象 dsh-TUI 已被上报修复,网络上不再有可复现的回归用例,
212
306
  所以改 `HOST_PACKAGES` 前请先跑这组)。
213
- - `src/installer.js` 也有一组 fixture,固化 `allowBuilds` 合并的全部形状
214
- (改 `mergeAllowBuilds` 前必跑)。它有宿主依赖,所以要从**已安装副本**运行:
307
+ - `src/installer.js` 也有一组 fixture,固化 `allowBuilds` 合并 + 事务串行化/回滚
308
+ 的形状(改 `mergeAllowBuilds` 或事务逻辑前必跑)。它 import 宿主
309
+ `@deepseek-ai/dsh-app-boot`,CI 会从专用 fixture lock 重放宿主及 peer 依赖后跑
310
+ `node src/installer.js --self-test`;本地也可从**已安装副本**运行同一命令:
215
311
  `node ~/.dsh/profiles/web/node_modules/@1e0zj/dsh-plugin-mall/src/installer.js --self-test`
312
+ - `src/guard.js` 与 `src/cli.js` 各自带一组离线 fixture(无网络、无 pnpm/dsh、
313
+ 无宿主框架依赖),固化冲突扫描、快照/pending/回滚,以及 CLI 参数与启动缓刑
314
+ 的判据:`node src/guard.js --self-test`、`node src/cli.js self-test`。两者只
315
+ import `js-yaml` + `semver` 两个叶子包,裸 checkout 里单点装这两个即可跑:
316
+ `npm install --no-save --no-package-lock --ignore-scripts --legacy-peer-deps
317
+ js-yaml@4 semver@7`,或从已安装副本跑同两条命令。改 guard 逻辑前必跑这组。
318
+ - `src/index.js` 的离线 fixture 固化 profile fingerprint、构建脚本审批 token
319
+ 与 job/session 隔离:`node src/index.js --self-test`。它会静态 import
320
+ `installer.js` 及宿主的 `dsh-tools` / `schemastery`,所以由发布 CI 在隔离目录
321
+ 从 `.github/fixtures/guard-tests/package-lock.json` 重放同发布线的最小宿主依赖后运行。
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "@1e0zj/dsh-plugin-mall",
3
- "version": "0.1.18",
3
+ "version": "0.2.1",
4
4
  "description": "dsh 插件市场:搜索 GitHub dsh-plugin 话题下的插件仓库,一键安装到本地 dsh profile(agent 工具 + 设置页插件市场 tab)",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
7
+ "bin": {
8
+ "dsh-plugin-guard": "src/cli.js"
9
+ },
7
10
  "exports": {
8
11
  ".": "./src/index.js",
9
12
  "./client": "./src/client.js",
@@ -35,7 +38,8 @@
35
38
  }
36
39
  },
37
40
  "dependencies": {
38
- "js-yaml": "^4.2.0"
41
+ "js-yaml": "^4.2.0",
42
+ "semver": "^7.6.3"
39
43
  },
40
44
  "peerDependencies": {
41
45
  "@deepseek-ai/cordis": "^4.0.1",