dsh-plugin-showcase 0.0.0-stage → 0.2.0
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/LICENSE +21 -0
- package/README.md +286 -2
- package/README.zh.md +262 -0
- package/cordis.patch.yml +5 -0
- package/icon.svg +5 -0
- package/lib/client.js +570 -0
- package/lib/index.js +664 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/market/index.json +53 -0
- package/package.json +53 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 antonioxue
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,287 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-plugin-showcase
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A starter companion for **DeepSeek Harness (DSH)**: it says one useful thing about the
|
|
4
|
+
host it runs in — and covers all eight plugin surfaces while doing it, so the whole
|
|
5
|
+
surface area can be read in one small package.
|
|
6
|
+
|
|
7
|
+
Version **0.2.0**. The v1 surface is unchanged (`showcase_inspect`, `/showcase on|off`,
|
|
8
|
+
the gate, the three slots, locale, icon, patch); v2 adds a health check, two command
|
|
9
|
+
branches, a first-run guide in the panel, and a self-hosted market index.
|
|
10
|
+
|
|
11
|
+
| Surface | Anchor (v0.2.0) |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| Tool `showcase_inspect` | `lib/index.js:600`, registered `:638` |
|
|
14
|
+
| Tool `showcase_doctor` | `lib/index.js:619`, registered `:639` |
|
|
15
|
+
| Hook `tools/pre-execute` | `lib/index.js:641` (gate), patterns `:21`–`:38` |
|
|
16
|
+
| Slash command `/showcase` | `lib/index.js:652` |
|
|
17
|
+
| Settings card | `lib/client.js:531` (`settings.section`, id `showcase`) |
|
|
18
|
+
| Right-sidebar panel | body `lib/client.js:551`, tab type `:539` |
|
|
19
|
+
| i18n | `lib/client.js:13` (19 keys), `locale/en.json`, `locale/zh.json` |
|
|
20
|
+
| Market index | `market/index.json` (shipped through `files`) |
|
|
21
|
+
| Tests | `tests/host.test.mjs`, `tests/client.test.mjs` |
|
|
22
|
+
|
|
23
|
+
Line numbers are as of v0.2.0. Nothing here is compiled: native ESM only, no
|
|
24
|
+
third-party runtime dependency, no `@deepseek-ai` import.
|
|
25
|
+
|
|
26
|
+
## Install and uninstall
|
|
27
|
+
|
|
28
|
+
Profile on this machine: `C:\Users\baba\.dsh\profiles\web`.
|
|
29
|
+
|
|
30
|
+
### A. From the market — the normal path
|
|
31
|
+
|
|
32
|
+
1. Publish 0.2.0 to npm (see `PUBLISH.md`), so the index inside the package is
|
|
33
|
+
fetchable at
|
|
34
|
+
`https://cdn.jsdelivr.net/npm/dsh-plugin-showcase@latest/market/index.json`.
|
|
35
|
+
2. Open DSH → Settings → the market / Discover page and add that URL as an index source.
|
|
36
|
+
The host service `pluginMarket` keeps that list (`indexes`), and it starts empty:
|
|
37
|
+
the Discover page does **not** search npm, so nothing shows up until an index is
|
|
38
|
+
configured.
|
|
39
|
+
3. Search for `dsh-plugin-showcase` → install. The package lands in the profile and its
|
|
40
|
+
`dsh.bundle` entry is appended, so the layer activates.
|
|
41
|
+
|
|
42
|
+
The index ships inside the package, so it only exists from **0.2.0** onward: 0.1.0 was
|
|
43
|
+
published before `market/` existed and has no index. On any version >= 0.2.0 the CDN
|
|
44
|
+
URL above serves the index directly — no local server, no process to keep alive.
|
|
45
|
+
|
|
46
|
+
If you do not want to wait for the index, install by registry name instead:
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
dsh plugin --profile web add dsh-plugin-showcase
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### B. From a checkout, with the documented command
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
dsh plugin --profile web add <path-to-this-directory>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
There is no global `dsh` on this machine's `PATH`, so the same command through the
|
|
59
|
+
checkout:
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
node "C:\Users\baba\Desktop\deepseek-harness\apps\cli\lib\bin.js" plugin --profile web add "C:\Users\baba\Desktop\DSH工作区\plugins\dsh-plugin-showcase"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Removal: `... plugin --profile web remove dsh-plugin-showcase`.
|
|
66
|
+
|
|
67
|
+
### C. What this machine actually uses — the manual equivalent
|
|
68
|
+
|
|
69
|
+
Three edits, no install run:
|
|
70
|
+
|
|
71
|
+
1. `C:\Users\baba\.dsh\profiles\web\package.json` → `dependencies`:
|
|
72
|
+
`"dsh-plugin-showcase": "link:C:/Users/baba/Desktop/DSH工作区/plugins/dsh-plugin-showcase"`
|
|
73
|
+
2. The same file → `dsh.profile.bundles`: append `"dsh-plugin-showcase"`.
|
|
74
|
+
3. A directory junction so Node resolves the name:
|
|
75
|
+
`C:\Users\baba\.dsh\profiles\web\node_modules\dsh-plugin-showcase`
|
|
76
|
+
→ `C:\Users\baba\Desktop\DSH工作区\plugins\dsh-plugin-showcase`
|
|
77
|
+
(verified `<JUNCTION>` reparse point; recreate with `cmd /c mklink /J <link> <target>`).
|
|
78
|
+
|
|
79
|
+
**Why not the install command here.** It runs an install inside the profile, and that
|
|
80
|
+
re-resolves the *whole* tree. This profile already carries eleven third-party plugins
|
|
81
|
+
(`dsh-cost-meter`, `dsh-better-sidebar`, `dsh-chat-import`, `dsh-context`,
|
|
82
|
+
`dsh-memgas`, `dsh-wallpaper-bg`, `@linxin666/*`, `@liustack/*`, `@tt-a1i/archify-dsh`).
|
|
83
|
+
The three edits above are exactly the state a link install produces, without a resolver
|
|
84
|
+
run that could rewrite one of those eleven.
|
|
85
|
+
|
|
86
|
+
Check the layer without starting the host (verified output):
|
|
87
|
+
|
|
88
|
+
```sh
|
|
89
|
+
node "C:\Users\baba\Desktop\deepseek-harness\apps\cli\lib\bin.js" --profile web --dump-config
|
|
90
|
+
# ...
|
|
91
|
+
# == dsh-plugin-showcase
|
|
92
|
+
# - id: showcase
|
|
93
|
+
# name: dsh-plugin-showcase
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
DSH's HMR watches the profile `package.json` (the `bundles` array) and the profile patch
|
|
97
|
+
files, so appending the entry recomposes the running host without a restart; the browser
|
|
98
|
+
half needs one page refresh. Undo by removing the three edits.
|
|
99
|
+
|
|
100
|
+
## The health check (new in 0.2.0)
|
|
101
|
+
|
|
102
|
+
`showcase_doctor` takes no arguments and returns a **HealthReport**:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
{
|
|
106
|
+
generatedAt: "ISO", pluginVersion: "0.2.0",
|
|
107
|
+
dshVersion: "0.2.1-rc.2", // string | null
|
|
108
|
+
summary: { ok, warn, fail, unknown }, // integers, always equal to the checks below
|
|
109
|
+
checks: [{ id, title, status, detail, hint }] // hint: string | null
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Two ways to ask:
|
|
114
|
+
|
|
115
|
+
- tool: run `showcase_doctor` (`showcase_inspect` still returns the v1 snapshot)
|
|
116
|
+
- command: `/showcase doctor` — the same text, straight into the conversation
|
|
117
|
+
|
|
118
|
+
The rendered text is `DSH 体检报告 <generatedAt>`, then one
|
|
119
|
+
`[OK|WARN|FAIL|?] <title> — <detail>` line per check, an indented ` ↳ <hint>` line when
|
|
120
|
+
there is a next step, and a closing `合计: ok N / warn N / fail N / unknown N`.
|
|
121
|
+
|
|
122
|
+
The six checks, in order:
|
|
123
|
+
|
|
124
|
+
| id | What it looks at | ok / warn / fail / unknown |
|
|
125
|
+
| --- | --- | --- |
|
|
126
|
+
| `engine.dsh` | running DSH version (read from `<profile>/node_modules/@deepseek-ai/dsh-app-boot/package.json`) against `engines.dsh` | ok = satisfies the range, fail = does not (hint: upgrade), warn = no `engines.dsh`, unknown = version unreadable or range not parseable offline |
|
|
127
|
+
| `workspace.cwd` | `workingDirectory` | ok = a directory, warn = none |
|
|
128
|
+
| `model.default` | `agentDefaultModel` | ok = `provider/model`, unknown = service missing |
|
|
129
|
+
| `plugin.gate` | this plugin's gate | ok = on, warn = off (hint: `/showcase on`) |
|
|
130
|
+
| `plugin.locale` | host language preference from `settings.describe()` | ok = a value, unknown = unreadable |
|
|
131
|
+
| `market.index` | index list from `pluginMarket.listIndexes()` | ok = every source readable, warn = none configured or some broken, unknown = no market service |
|
|
132
|
+
|
|
133
|
+
**A check that throws never fails the run.** Its own row degrades to `unknown` with
|
|
134
|
+
`detail: "检查异常: <message>"` plus a hint; the other five keep their verdicts and the
|
|
135
|
+
summary still adds up to six.
|
|
136
|
+
|
|
137
|
+
## Commands
|
|
138
|
+
|
|
139
|
+
| Input | Answer |
|
|
140
|
+
| --- | --- |
|
|
141
|
+
| `/showcase` | the v1 inspection report text |
|
|
142
|
+
| `/showcase on` / `off` | `showcase 拦截门: 已开启` / `已关闭` |
|
|
143
|
+
| `/showcase doctor` | the health report text (same render as the tool) |
|
|
144
|
+
| `/showcase about` | one line: version, both tools, command branches, gate state, DSH version |
|
|
145
|
+
| anything else | `/showcase [on|off|doctor|about]` |
|
|
146
|
+
|
|
147
|
+
Input is trimmed and lower-cased first, so ` ON ` works and `Dr. Who` gets the usage line.
|
|
148
|
+
|
|
149
|
+
## The guard gate
|
|
150
|
+
|
|
151
|
+
On by default. A tool call is denied when its `command`, `cmd`, `script` or `code`
|
|
152
|
+
argument (case-insensitive) contains one of:
|
|
153
|
+
|
|
154
|
+
- `rm -rf /`, `rm -rf ~`
|
|
155
|
+
- fork bombs: `:(){:|:&;}`, `:(){ :|:& };:`, `.(){ :|:& };:`, `bomb(){ bomb|bomb& };bomb`
|
|
156
|
+
- `del /f /s /q c:\`, `format c:`
|
|
157
|
+
- the `shutdown` / `shutdown.exe` word in command position, optionally behind `sudo`,
|
|
158
|
+
`doas` or `pkexec`, e.g. `shutdown /s /t 0` or `sudo shutdown -h now`
|
|
159
|
+
|
|
160
|
+
Whitespace-collapsed spellings such as `rm -rf /tmp`, or the same commands written with
|
|
161
|
+
tabs instead of spaces, are caught too. The deny reason is
|
|
162
|
+
`showcase: 拒绝执行破坏性命令`; everything else calls `next()` and is allowed. Text
|
|
163
|
+
outside those four argument keys is ignored, and a fault inside the hook still calls
|
|
164
|
+
`next()` — a plugin bug must never block tool calls.
|
|
165
|
+
|
|
166
|
+
## Settings card and panel
|
|
167
|
+
|
|
168
|
+
**Settings card** — Settings → nav row **功能展示 / Showcase** (id `showcase`, order 90):
|
|
169
|
+
title and subtitle, the doctor hint, a copyable `/showcase doctor` row, refresh and
|
|
170
|
+
"open the panel" buttons, a footer with `v0.2.0` and the gate state, the three capability
|
|
171
|
+
rows, then either the snapshot or the degraded copy.
|
|
172
|
+
|
|
173
|
+
**Right-sidebar panel** — tab type id `showcase-panel`, kind `showcase`, title
|
|
174
|
+
**巡检面板 / Showcase panel**, body seated under the same key. Open it from the ◎ button
|
|
175
|
+
in the left sidebar footer (beside Settings) or from the settings card's button; both end
|
|
176
|
+
in `sidebarRight.openTab('showcase')`. The type declares no `guide` entry, so it does not
|
|
177
|
+
appear on the right sidebar's guide page.
|
|
178
|
+
|
|
179
|
+
The panel opens on a three-step guide: 1) run a health check — the `/showcase doctor`
|
|
180
|
+
text is shown and copyable, 2) open the settings card, 3) understand the gate —
|
|
181
|
+
`/showcase on|off` is shown and copyable. Each step has its own copy button; pressing it
|
|
182
|
+
flips the label to **已复制 / Copied** for about 1.6 s. The footer carries `v0.2.0` and,
|
|
183
|
+
while no snapshot is reachable, the degraded line.
|
|
184
|
+
|
|
185
|
+
## i18n
|
|
186
|
+
|
|
187
|
+
19 runtime keys, each present in `en` and `zh`, translated rather than copied:
|
|
188
|
+
|
|
189
|
+
`title` `subtitle` `panelTitle` `panelEmpty` `gateOn` `gateOff` `openPanel` `refresh`
|
|
190
|
+
(the v1 eight) plus `welcome` `stepDoctorTitle` `stepDoctorBody` `stepSettingsTitle`
|
|
191
|
+
`stepSettingsBody` `stepGateTitle` `stepGateBody` `copyHint` `copied` `doctorHint`
|
|
192
|
+
`aboutBody`.
|
|
193
|
+
|
|
194
|
+
Language resolution is `ctx.locale` → `navigator.language` → `en`; a host without a
|
|
195
|
+
locale service still renders real copy.
|
|
196
|
+
|
|
197
|
+
## Market index (self-hosted inside the package)
|
|
198
|
+
|
|
199
|
+
Since v0.2.0 the index travels with the package: `market/index.json` (format 1, listing
|
|
200
|
+
this plugin) is whitelisted in `package.json` → `files`, so it is inside the published
|
|
201
|
+
tarball. Public address:
|
|
202
|
+
|
|
203
|
+
```
|
|
204
|
+
https://cdn.jsdelivr.net/npm/dsh-plugin-showcase@latest/market/index.json
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Add that URL as an index source on the Discover page and the plugin is installable
|
|
208
|
+
without any local server.
|
|
209
|
+
|
|
210
|
+
The temporary service this machine used during development still listens on
|
|
211
|
+
`http://127.0.0.1:8791` (its index answers 200 at `/index.json`). It is **no longer
|
|
212
|
+
needed**: pointing the Discover page at the URL above replaces it, and after that the
|
|
213
|
+
local service can be stopped.
|
|
214
|
+
|
|
215
|
+
Marketplace metadata still comes from `locale/en.json` (the anchor) and `locale/zh.json`,
|
|
216
|
+
so the Plugins page shows the localized title and description next to `icon.svg`.
|
|
217
|
+
|
|
218
|
+
## Honest limitations
|
|
219
|
+
|
|
220
|
+
1. **The client still cannot read live host data, and under the zero-build rule it
|
|
221
|
+
cannot.** The documented remote-API path needs typert decorators, a code-generation
|
|
222
|
+
step and a third-party dependency — three hard conflicts with "native ESM, no build,
|
|
223
|
+
no deps". `lib/client.js` probes the plausible shapes (`remote.showcase`,
|
|
224
|
+
`ctx.showcase`) and degrades to `null`. So the card and the panel show the static
|
|
225
|
+
capability rows and the degraded copy, never a live snapshot; that is also why the
|
|
226
|
+
first-run guide hands you the `/showcase doctor` text instead of calling the host.
|
|
227
|
+
2. **`sidebar.footer.action` does not project `label`.** The sidebar shell renders that
|
|
228
|
+
seat with only `{ wide }`, so the visible text is what the component draws (`◎`, with
|
|
229
|
+
the label carried as `aria-label`). The `label` thunk is a legal `SlotLabel` and is
|
|
230
|
+
correct, but no shell reads it for this seat.
|
|
231
|
+
3. **Around 390 px the layout is the host's call, not ours.** The seats stack when the
|
|
232
|
+
*measured box* drops below 320 px, and every box is `min-width: 0` plus
|
|
233
|
+
`overflow-x: hidden`, so a squeezed container can only shorten lines. But the right
|
|
234
|
+
column's width is clamped by DSH itself: the plugin cannot make the pane narrower or
|
|
235
|
+
wider than the host allows, and behaviour below roughly 390 px was only reached
|
|
236
|
+
through container measurement, not through a real narrow viewport.
|
|
237
|
+
4. **No visual regression in a real browser — the tests cover facts, not pixels.** The
|
|
238
|
+
client evidence comes from Node stub tests: `react` is replaced by a minimal
|
|
239
|
+
`createElement`/`useState`/`useEffect`/`useRef` stand-in, so the assertions are the
|
|
240
|
+
element tree and its text (19 bilingual keys, three step titles, the copied label after
|
|
241
|
+
a click, the degraded copy). There is no screenshot baseline and no pixel comparison.
|
|
242
|
+
|
|
243
|
+
## Extending it
|
|
244
|
+
|
|
245
|
+
- **New tool.** Copy the raw JSON-Schema `ToolDefinition` shape of `inspectTool`
|
|
246
|
+
(`lib/index.js:600`) or `doctorTool` (`:619`) and register it with
|
|
247
|
+
`ctx.effect(() => ctx.tools.register(<tool>), 'showcase: <name>')` (`:638` / `:639`).
|
|
248
|
+
`output` must carry `{ schema, render }`.
|
|
249
|
+
- **New check.** Add one `runCheck('<id>', '<title>', () => …)` entry to the array in
|
|
250
|
+
`buildHealthReport` (`lib/index.js:511`), keep it wrapped so a throw becomes
|
|
251
|
+
`unknown`, and extend `buildHealthSchema` only if the shape really changes.
|
|
252
|
+
- **New slot.** In `apply()` (`lib/client.js:525`) copy the
|
|
253
|
+
`slots.inject(<slot>, () => slots.register({ name, … }, Component))` shape:
|
|
254
|
+
`settings.section` at `:531`, `sidebar.right.pane.tab` at `:551`,
|
|
255
|
+
`sidebar.footer.action` at `:559`. A right-sidebar tab needs both halves — the body key
|
|
256
|
+
must equal the id of the type registered through `sidebarRightTabs`.
|
|
257
|
+
- **New runtime string.** Add the key to both `dict.en` and `dict.zh`
|
|
258
|
+
(`lib/client.js:13`) and extend `DICT_KEYS` in `tests/helpers/client-harness.mjs`, so
|
|
259
|
+
the bilingual test keeps guarding it.
|
|
260
|
+
|
|
261
|
+
## Tests
|
|
262
|
+
|
|
263
|
+
```sh
|
|
264
|
+
npm install # once, needs registry access (devDependency: vitest)
|
|
265
|
+
npm test # offline from then on, one command, whole suite
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
98 tests in 2 files, no machine path baked in — every path derives from
|
|
269
|
+
`import.meta.url`, and the fixtures (a workspace, a two-version DSH profile) are temp
|
|
270
|
+
directories created per run.
|
|
271
|
+
|
|
272
|
+
- `tests/host.test.mjs` (66): module shape, the `ctx.effect` lifecycle of all four
|
|
273
|
+
registrations, both tools' parameters and `output` contract with returned values
|
|
274
|
+
validated against their own schemas, snapshot shape and `""`/`null` degradation,
|
|
275
|
+
git-branch discovery, all eight destructive patterns plus collapsed spellings and key
|
|
276
|
+
scoping, the word-boundary cases for power commands, hook-throw still allowing, gate
|
|
277
|
+
on/off, the HealthReport shape with "no `undefined`" and summary-vs-checks agreement,
|
|
278
|
+
each of the six checks in its ok / warn / unknown / fail branches, a throwing check
|
|
279
|
+
degrading on its own, the report render layout, and the five command inputs.
|
|
280
|
+
- `tests/client.test.mjs` (32): lazy-CJS registration, factory exports including
|
|
281
|
+
`VERSION`, exactly three seats, tab-type/body-key agreement and late service arrival,
|
|
282
|
+
the footer's open path, all 19 bilingual dict keys, the three step titles, language
|
|
283
|
+
resolution including a host with no locale service, `fetchSnapshot` degradation,
|
|
284
|
+
rendering with and without data, the first-run guide, the copy-to-"已复制" flip, and
|
|
285
|
+
refresh clicks.
|
|
286
|
+
|
|
287
|
+
Runner: **vitest 3** (the declared devDependency).
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# dsh-plugin-showcase
|
|
2
|
+
|
|
3
|
+
一个 **DeepSeek Harness(DSH)** 的新人首装搭档:用一句话说明它所在的这台 DSH 主机
|
|
4
|
+
是什么状况,同时把八个插件能力面一次性用满,因此整块接口面可以在一个小包里读完。
|
|
5
|
+
|
|
6
|
+
版本 **0.2.0**。v1 的能力面一个都没退化(`showcase_inspect`、`/showcase on|off`、
|
|
7
|
+
拦截门、三处 slot、locale、icon、patch 全部保持),v2 增加了体检、两个命令分支、
|
|
8
|
+
面板里的三步引导,以及随包分发的自托管市场索引。
|
|
9
|
+
|
|
10
|
+
| 能力面 | 位置(v0.2.0) |
|
|
11
|
+
| --- | --- |
|
|
12
|
+
| 工具 `showcase_inspect` | `lib/index.js:600`,注册在 `:638` |
|
|
13
|
+
| 工具 `showcase_doctor` | `lib/index.js:619`,注册在 `:639` |
|
|
14
|
+
| 钩子 `tools/pre-execute` | `lib/index.js:641`(拦截门),模式表 `:21`–`:38` |
|
|
15
|
+
| 斜杠命令 `/showcase` | `lib/index.js:652` |
|
|
16
|
+
| 设置卡片 | `lib/client.js:531`(`settings.section`,id `showcase`) |
|
|
17
|
+
| 右侧栏面板 | 面板体 `lib/client.js:551`,tab 类型 `:539` |
|
|
18
|
+
| 国际化 | `lib/client.js:13`(19 键)、`locale/en.json`、`locale/zh.json` |
|
|
19
|
+
| 市场索引 | `market/index.json`(在 `files` 白名单里随包发布) |
|
|
20
|
+
| 测试 | `tests/host.test.mjs`、`tests/client.test.mjs` |
|
|
21
|
+
|
|
22
|
+
行号对应 v0.2.0。全程零构建:只有原生 ESM JavaScript,没有第三方运行时依赖,也没有
|
|
23
|
+
任何 `@deepseek-ai` import。
|
|
24
|
+
|
|
25
|
+
## 安装与卸载
|
|
26
|
+
|
|
27
|
+
本机使用的 profile:`C:\Users\baba\.dsh\profiles\web`。
|
|
28
|
+
|
|
29
|
+
### 方式一:从市场安装(正常路径)
|
|
30
|
+
|
|
31
|
+
1. 把 0.2.0 发布到 npm(流程见 `PUBLISH.md`),包内索引就能在
|
|
32
|
+
`https://cdn.jsdelivr.net/npm/dsh-plugin-showcase@latest/market/index.json` 取到。
|
|
33
|
+
2. 打开 DSH → 设置 → 市场/发现页,把这个 URL 添加为索引源。宿主服务
|
|
34
|
+
`pluginMarket` 维护这份 `indexes` 列表,它的初始值是空数组:发现页**不搜索
|
|
35
|
+
npm**,所以没配索引之前什么都不会出现。
|
|
36
|
+
3. 搜索 `dsh-plugin-showcase` → 安装。包会装进 profile,并把它在 `dsh.bundle` 里
|
|
37
|
+
对应的条目追加进 `dsh.profile.bundles`,层随即生效。
|
|
38
|
+
|
|
39
|
+
索引随包分发,所以从 **0.2.0** 起才有:0.1.0 发布时包里没有 `market/`,它没有索引。
|
|
40
|
+
0.2.0 及以后的版本,上面那条 CDN 地址直接就能取到索引——不需要本机服务,也没有任何
|
|
41
|
+
进程要你养着。
|
|
42
|
+
|
|
43
|
+
不想等索引,也可以直接按 registry 名安装:
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
dsh plugin --profile web add dsh-plugin-showcase
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 方式二:从 checkout 安装(官方命令)
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
dsh plugin --profile web add <本目录路径>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
本机 `PATH` 上没有全局 `dsh`,所以同一个命令改从检出调用:
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
node "C:\Users\baba\Desktop\deepseek-harness\apps\cli\lib\bin.js" plugin --profile web add "C:\Users\baba\Desktop\DSH工作区\plugins\dsh-plugin-showcase"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
卸载:`... plugin --profile web remove dsh-plugin-showcase`。
|
|
62
|
+
|
|
63
|
+
### 方式三:本机实际使用的手动等价物
|
|
64
|
+
|
|
65
|
+
三处改动,不跑安装:
|
|
66
|
+
|
|
67
|
+
1. `C:\Users\baba\.dsh\profiles\web\package.json` 的 `dependencies` 加一行
|
|
68
|
+
`"dsh-plugin-showcase": "link:C:/Users/baba/Desktop/DSH工作区/plugins/dsh-plugin-showcase"`;
|
|
69
|
+
2. 同一个文件的 `dsh.profile.bundles` 末尾追加 `"dsh-plugin-showcase"`;
|
|
70
|
+
3. 建一个目录 junction,让 Node 能解析这个包名:
|
|
71
|
+
`C:\Users\baba\.dsh\profiles\web\node_modules\dsh-plugin-showcase`
|
|
72
|
+
→ `C:\Users\baba\Desktop\DSH工作区\plugins\dsh-plugin-showcase`
|
|
73
|
+
(本机实测为 `<JUNCTION>` 重解析点;重建命令 `cmd /c mklink /J <链接> <目标>`)。
|
|
74
|
+
|
|
75
|
+
**为什么本机不用安装命令。** 它会在 profile 内跑一次安装,而这次安装会重新解析整棵树。
|
|
76
|
+
这个 profile 里已经装着十一个第三方插件(`dsh-cost-meter`、`dsh-better-sidebar`、
|
|
77
|
+
`dsh-chat-import`、`dsh-context`、`dsh-memgas`、`dsh-wallpaper-bg`、`@linxin666/*`、
|
|
78
|
+
`@liustack/*`、`@tt-a1i/archify-dsh`)。上面三处改动正是链接安装后的最终状态,但没有那次
|
|
79
|
+
可能改写这十一个插件版本的解析过程。
|
|
80
|
+
|
|
81
|
+
不启动、只验证层(以下为实测输出):
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
node "C:\Users\baba\Desktop\deepseek-harness\apps\cli\lib\bin.js" --profile web --dump-config
|
|
85
|
+
# ...
|
|
86
|
+
# == dsh-plugin-showcase
|
|
87
|
+
# - id: showcase
|
|
88
|
+
# name: dsh-plugin-showcase
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
DSH 的 HMR 监视 profile 的 `package.json`(只看 `bundles` 数组)与 profile 的 patch
|
|
92
|
+
文件,因此追加条目会在不重启宿主进程的情况下重新组合;浏览器那一半需要刷新一次页面。
|
|
93
|
+
撤销同样只是把这三处改回去。
|
|
94
|
+
|
|
95
|
+
## 体检(0.2.0 新增)
|
|
96
|
+
|
|
97
|
+
`showcase_doctor` 无参数,返回一份 **HealthReport**:
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
{
|
|
101
|
+
generatedAt: "ISO", pluginVersion: "0.2.0",
|
|
102
|
+
dshVersion: "0.2.1-rc.2", // string | null
|
|
103
|
+
summary: { ok, warn, fail, unknown }, // 整数,与下面 checks 的统计恒等
|
|
104
|
+
checks: [{ id, title, status, detail, hint }] // hint: string | null
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
两种问法:
|
|
109
|
+
|
|
110
|
+
- 工具:跑一次 `showcase_doctor`(`showcase_inspect` 仍然返回 v1 那份巡检快照)
|
|
111
|
+
- 命令:`/showcase doctor` —— 同一段文本直接回在对话里
|
|
112
|
+
|
|
113
|
+
渲染文本首行是 `DSH 体检报告 <generatedAt>`,随后每条 check 一行
|
|
114
|
+
`[OK|WARN|FAIL|?] <title> — <detail>`,有下一步时再缩进一行 ` ↳ <hint>`,末行是
|
|
115
|
+
`合计: ok N / warn N / fail N / unknown N`。
|
|
116
|
+
|
|
117
|
+
六项 check(按顺序):
|
|
118
|
+
|
|
119
|
+
| id | 看什么 | ok / warn / fail / unknown |
|
|
120
|
+
| --- | --- | --- |
|
|
121
|
+
| `engine.dsh` | 运行中的 DSH 版本(读 `<profile>/node_modules/@deepseek-ai/dsh-app-boot/package.json`)与 `engines.dsh` 比对 | ok = 满足范围,fail = 不满足(hint 提示升级),warn = 未声明 `engines.dsh`,unknown = 版本读不到或范围无法离线解析 |
|
|
122
|
+
| `workspace.cwd` | `workingDirectory` | ok = 有目录,warn = 取不到 |
|
|
123
|
+
| `model.default` | `agentDefaultModel` | ok = `provider/model`,unknown = 无该服务 |
|
|
124
|
+
| `plugin.gate` | 本插件的拦截门 | ok = 开启,warn = 关闭(hint: `/showcase on`) |
|
|
125
|
+
| `plugin.locale` | `settings.describe()` 里的界面语言偏好 | ok = 有值,unknown = 读不到 |
|
|
126
|
+
| `market.index` | `pluginMarket.listIndexes()` 的索引列表 | ok = 每个源都可用,warn = 一条都没配或部分不可用,unknown = 没有市场服务 |
|
|
127
|
+
|
|
128
|
+
**任何一条 check 内部抛错都不会让整次体检失败。** 只有它自己那行退化成 `unknown`,
|
|
129
|
+
`detail` 写成 `检查异常: <message>` 并带 hint,其余五项判决保持不变,合计仍然是六项。
|
|
130
|
+
|
|
131
|
+
## 命令
|
|
132
|
+
|
|
133
|
+
| 输入 | 回答 |
|
|
134
|
+
| --- | --- |
|
|
135
|
+
| `/showcase` | v1 那份巡检报告文本 |
|
|
136
|
+
| `/showcase on` / `off` | `showcase 拦截门: 已开启` / `已关闭` |
|
|
137
|
+
| `/showcase doctor` | 体检报告文本(与工具同一份渲染) |
|
|
138
|
+
| `/showcase about` | 一行:版本、两个工具、命令分支、拦截门状态、DSH 版本 |
|
|
139
|
+
| 其它 | `/showcase [on|off|doctor|about]` |
|
|
140
|
+
|
|
141
|
+
输入先 trim 再转小写,所以 ` ON ` 有效,`Dr. Who` 得到用法那一行。
|
|
142
|
+
|
|
143
|
+
## 拦截门
|
|
144
|
+
|
|
145
|
+
默认开启。当一次工具调用的 `command`、`cmd`、`script` 或 `code` 参数(不区分大小写)
|
|
146
|
+
命中下列任一模式时拒绝执行:
|
|
147
|
+
|
|
148
|
+
- `rm -rf /`、`rm -rf ~`
|
|
149
|
+
- fork 炸弹:`:(){:|:&;}`、`:(){ :|:& };:`、`.(){ :|:& };:`、`bomb(){ bomb|bomb& };bomb`
|
|
150
|
+
- `del /f /s /q c:\`、`format c:`
|
|
151
|
+
- 处在命令位置上的 `shutdown` / `shutdown.exe` 词(前面可以带 `sudo`、`doas` 或
|
|
152
|
+
`pkexec`),例如 `shutdown /s /t 0`、`sudo shutdown -h now`
|
|
153
|
+
|
|
154
|
+
把空格抹掉的写法(例如 `rm -rf /tmp`),或把空格换成制表符,同样拦得住。拒绝理由固定为
|
|
155
|
+
`showcase: 拒绝执行破坏性命令`;其余一律调用 `next()` 放行。这四个参数键之外的文本不
|
|
156
|
+
参与匹配;钩子内部出故障也照样 `next()` —— 插件 bug 绝不能阻塞工具调用。
|
|
157
|
+
|
|
158
|
+
## 设置卡与面板
|
|
159
|
+
|
|
160
|
+
**设置卡片** —— 设置里的导航行 **功能展示 / Showcase**(id `showcase`,order 90):
|
|
161
|
+
标题与说明、体检提示、一行可复制的 `/showcase doctor`、刷新与"打开巡检面板"按钮、带
|
|
162
|
+
`v0.2.0` 与拦截门状态的页脚、三条能力行,最后是快照或降级文案。
|
|
163
|
+
|
|
164
|
+
**右侧栏面板** —— tab 类型 id `showcase-panel`、kind `showcase`、标题
|
|
165
|
+
**巡检面板 / Showcase panel**,面板体挂在同一个 key 上。打开它有两个入口:左侧栏底部
|
|
166
|
+
Settings 旁边的 ◎ 按钮,以及设置卡上的按钮,两者最终都走
|
|
167
|
+
`sidebarRight.openTab('showcase')`。这个 tab 类型没有声明 `guide` 条目,所以它不会出现
|
|
168
|
+
在右侧栏的引导页里。
|
|
169
|
+
|
|
170
|
+
面板一打开就是三步引导:1)跑一次体检 —— 把 `/showcase doctor` 作为可复制文本交给你;
|
|
171
|
+
2)打开设置卡;3)了解拦截门 —— 给出 `/showcase on|off` 的可复制文本。每一步都有自己
|
|
172
|
+
的复制按钮,按下后标签变成 **已复制 / Copied**,约 1.6 秒后回退。页脚显示 `v0.2.0`,
|
|
173
|
+
取不到快照时再加上降级那一行。
|
|
174
|
+
|
|
175
|
+
## 国际化
|
|
176
|
+
|
|
177
|
+
19 个运行期键,`en` 与 `zh` 各一份,都是真翻译而不是照抄:
|
|
178
|
+
|
|
179
|
+
v1 的八个 `title` `subtitle` `panelTitle` `panelEmpty` `gateOn` `gateOff`
|
|
180
|
+
`openPanel` `refresh`,加 v2 的 `welcome` `stepDoctorTitle` `stepDoctorBody`
|
|
181
|
+
`stepSettingsTitle` `stepSettingsBody` `stepGateTitle` `stepGateBody` `copyHint`
|
|
182
|
+
`copied` `doctorHint` `aboutBody`。
|
|
183
|
+
|
|
184
|
+
语言解析顺序是 `ctx.locale` → `navigator.language` → `en`;没有 locale 服务的宿主照样
|
|
185
|
+
渲染真实文案。
|
|
186
|
+
|
|
187
|
+
## 市场索引(随包自托管)
|
|
188
|
+
|
|
189
|
+
v0.2.0 起索引跟包一起走:`market/index.json`(format 1,收录本插件),并在
|
|
190
|
+
`package.json` 的 `files` 白名单里,所以它在发布出去的 tarball 里。对外地址:
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
https://cdn.jsdelivr.net/npm/dsh-plugin-showcase@latest/market/index.json
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
在发现页把它添加为索引源,就不用任何本地服务器也能装。
|
|
197
|
+
|
|
198
|
+
开发期本机用过的那套临时服务仍在 `http://127.0.0.1:8791` 上监听(它的索引在
|
|
199
|
+
`/index.json` 回答 200)。**它已经不再需要**:把发现页的索引源换成上面那条 CDN 地址
|
|
200
|
+
即可,之后那套本地服务可以停掉。
|
|
201
|
+
|
|
202
|
+
市场元数据仍然来自 `locale/en.json`(锚点)与 `locale/zh.json`,所以插件页会显示本地化
|
|
203
|
+
标题与描述,配上 `icon.svg`。
|
|
204
|
+
|
|
205
|
+
## 诚实的限制清单
|
|
206
|
+
|
|
207
|
+
1. **client 侧仍然拿不到宿主实时数据,零构建约束下也拿不到。** 官方 remote API 路线需要
|
|
208
|
+
typert 装饰器、代码生成步骤和第三方依赖——这三条与"原生 ESM、不构建、不加依赖"逐条
|
|
209
|
+
冲突。`lib/client.js` 会去探测几种可能的形状(`remote.showcase`、`ctx.showcase`),
|
|
210
|
+
失败即降级为 `null`。所以设置卡与面板显示的是静态能力行加降级文案,不是实时快照;
|
|
211
|
+
也正因如此,首启引导只能把 `/showcase doctor` 文本交给你,而不是替你去调用宿主。
|
|
212
|
+
2. **`sidebar.footer.action` 座位不投影 `label`。** 侧栏外壳只以 `{ wide }` 渲染这个
|
|
213
|
+
座位,可见文本由组件自己画(`◎`,标签通过 `aria-label` 承载)。那个 `label` thunk
|
|
214
|
+
是合法的 `SlotLabel` 也没写错,只是这个座位没有壳去读它。
|
|
215
|
+
3. **390px 左右的表现由宿主决定,不由插件决定。** 实测容器宽度低于 320px 时座位才改为
|
|
216
|
+
堆叠,且每个盒子都是 `min-width: 0` 加 `overflow-x: hidden`,被压窄时只会让行变短、
|
|
217
|
+
不会撑出横向滚动条。但右列宽度是 DSH 自己钳制的:插件既不能把面板做得比宿主允许的
|
|
218
|
+
更窄,也不能更宽,而 390px 以下的情况只是通过容器实测走到的,没有在真实窄视口里验证。
|
|
219
|
+
4. **没有真实浏览器的视觉回归 —— 测试覆盖事实,不覆盖像素。** client 侧证据来自 Node 桩
|
|
220
|
+
测试:`react` 被换成最小的 `createElement`/`useState`/`useEffect`/`useRef` 替身,
|
|
221
|
+
所以断言的是元素树与文本(19 键双语、三条引导标题、点击后的"已复制"、降级文案)。
|
|
222
|
+
既没有截图基线,也没有像素比对。
|
|
223
|
+
|
|
224
|
+
## 二次开发指引
|
|
225
|
+
|
|
226
|
+
- **加一个工具。** 照抄 `inspectTool`(`lib/index.js:600`)或 `doctorTool`(`:619`)
|
|
227
|
+
那种原始 JSON-Schema `ToolDefinition` 形状,用
|
|
228
|
+
`ctx.effect(() => ctx.tools.register(<tool>), 'showcase: <name>')` 注册
|
|
229
|
+
(`:638` / `:639`)。`output` 必须带 `{ schema, render }`。
|
|
230
|
+
- **加一项 check。** 在 `buildHealthReport`(`lib/index.js:511`)的数组里加一行
|
|
231
|
+
`runCheck('<id>', '<标题>', () => …)`,保持这层包裹(抛错才会退化成 `unknown`);
|
|
232
|
+
只有形状真的变了才动 `buildHealthSchema`。
|
|
233
|
+
- **加一个新 slot。** 在 `apply()`(`lib/client.js:525`)里照抄
|
|
234
|
+
`slots.inject(<slot>, () => slots.register({ name, … }, 组件))` 这个形状:
|
|
235
|
+
`settings.section` 在 `:531`、`sidebar.right.pane.tab` 在 `:551`、
|
|
236
|
+
`sidebar.footer.action` 在 `:559`。右侧栏 tab 必须两半都注册——面板体的 key 要等于
|
|
237
|
+
通过 `sidebarRightTabs` 注册的类型 id。
|
|
238
|
+
- **加一条运行期文案。** 同时加进 `dict.en` 与 `dict.zh`(`lib/client.js:13`),并把
|
|
239
|
+
`tests/helpers/client-harness.mjs` 里的 `DICT_KEYS` 一起扩展,双语用例才会继续守住它。
|
|
240
|
+
|
|
241
|
+
## 测试
|
|
242
|
+
|
|
243
|
+
```sh
|
|
244
|
+
npm install # 只做一次,需要访问 registry(devDependency: vitest)
|
|
245
|
+
npm test # 之后完全离线,一条命令跑完整个套件
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
两个文件共 98 个用例,不写死任何机器路径——所有路径都由 `import.meta.url` 推导,夹具
|
|
249
|
+
(一个工作区、一个双版本 DSH profile)是每次运行现建的临时目录。
|
|
250
|
+
|
|
251
|
+
- `tests/host.test.mjs`(66 个):模块形状、四处注册的 `ctx.effect` 生命周期、两个工具
|
|
252
|
+
的参数与 `output` 契约(返回值用各自声明的 schema 反向校验)、快照形状与 `""`/`null`
|
|
253
|
+
降级、git 分支发现、八条破坏性模式外加抹空格写法与参数键范围、关机类命令的词边界、
|
|
254
|
+
钩子内部抛错仍放行、拦截门开关、HealthReport 形状与"不出现 `undefined`"、summary 与
|
|
255
|
+
checks 恒等、六项 check 各自的 ok/warn/unknown/fail 分支、单条 check 抛错只降级自己、
|
|
256
|
+
报告渲染版式,以及命令的五种输入。
|
|
257
|
+
- `tests/client.test.mjs`(32 个):lazy-CJS 登记、factory 导出(含 `VERSION`)、恰好三个
|
|
258
|
+
座位、tab 类型与面板体 key 一致且服务晚到也能补注册、侧栏按钮的打开路径、19 个双语键、
|
|
259
|
+
三条引导标题、语言解析(含没有 locale 服务的宿主)、`fetchSnapshot` 的各级降级、
|
|
260
|
+
有数据与无数据两种渲染、首启引导、点击复制后变"已复制"、刷新点击。
|
|
261
|
+
|
|
262
|
+
运行器:**vitest 3**(已声明的 devDependency)。
|
package/cordis.patch.yml
ADDED
package/icon.svg
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
|
|
2
|
+
<rect x="4" y="4" width="56" height="56" rx="14" fill="#2f6fed"/>
|
|
3
|
+
<circle cx="32" cy="32" r="15" fill="none" stroke="#fff" stroke-width="3" stroke-dasharray="7 5"/>
|
|
4
|
+
<text x="32" y="41" font-family="sans-serif" font-size="22" font-weight="700" fill="#fff" text-anchor="middle">S</text>
|
|
5
|
+
</svg>
|