dsh-enhancement-suite 0.1.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 +122 -0
- package/README.zh.md +122 -0
- package/bin/dsh-enhance.mjs +467 -0
- package/bin/validate-manifest.mjs +44 -0
- package/docs/architecture.md +102 -0
- package/package.json +44 -0
- package/plugins.json +47 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Scorp1o117
|
|
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
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# DeepSeek Harness Enhancement Suite
|
|
2
|
+
|
|
3
|
+
[](README.zh.md) [](LICENSE) [](package.json)
|
|
4
|
+
|
|
5
|
+
**Vision · Soul / Persona · Long-term Memory · Plugin Marketplace**
|
|
6
|
+
|
|
7
|
+
The official entry point and one-command installer for the four Scorp1o117 plugins that enhance [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH). This suite is a real installer — not a link collection: it drives the official `dsh plugin` CLI, mounts what needs mounting, and checks the result.
|
|
8
|
+
|
|
9
|
+
> **This repository does not vendor or replace the individual plugins.**
|
|
10
|
+
> **Each plugin remains independently maintained in its own upstream repository.**
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 👁️ dsh-tool-vision
|
|
15
|
+
|
|
16
|
+
给文本模型增加视觉能力和图片桥接。
|
|
17
|
+
|
|
18
|
+
[](https://www.npmjs.com/package/dsh-tool-vision) [](https://github.com/Scorp1o117/dsh-tool-vision/blob/main/LICENSE)
|
|
19
|
+
|
|
20
|
+
[→ upstream repository](https://github.com/Scorp1o117/dsh-tool-vision)
|
|
21
|
+
|
|
22
|
+
## 🎭 dsh-soul-md
|
|
23
|
+
|
|
24
|
+
为 Agent 增加 soul.md 人设、会话/工作区 Persona 和可演化人格。
|
|
25
|
+
|
|
26
|
+
[](https://www.npmjs.com/package/dsh-soul-md) [](https://github.com/Scorp1o117/dsh-soul-md/blob/main/LICENSE)
|
|
27
|
+
|
|
28
|
+
[→ upstream repository](https://github.com/Scorp1o117/dsh-soul-md)
|
|
29
|
+
|
|
30
|
+
## 🧠 dsh-tdai-memory
|
|
31
|
+
|
|
32
|
+
完整的长期分层记忆、召回与搜索。
|
|
33
|
+
|
|
34
|
+
[](https://www.npmjs.com/package/dsh-tdai-memory) [](https://github.com/Scorp1o117/dsh-tdai-memory/blob/main/LICENSE)
|
|
35
|
+
|
|
36
|
+
[→ upstream repository](https://github.com/Scorp1o117/dsh-tdai-memory)
|
|
37
|
+
|
|
38
|
+
## 🛒 dsh-plugin-marketplace
|
|
39
|
+
|
|
40
|
+
直接在 DSH Web UI 中发现和浏览插件。
|
|
41
|
+
|
|
42
|
+
[](https://www.npmjs.com/package/dsh-plugin-marketplace) [](https://github.com/Scorp1o117/dsh-plugin-marketplace/blob/main/LICENSE)
|
|
43
|
+
|
|
44
|
+
[→ upstream repository](https://github.com/Scorp1o117/dsh-plugin-marketplace)
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Requirements
|
|
49
|
+
|
|
50
|
+
- **Node.js >= 22.18** — the full suite inherits the `dsh-tdai-memory` engine requirement
|
|
51
|
+
- **dsh CLI** on `PATH` — the official DeepSeek Harness CLI (`dsh plugin --profile <p> add <pkg>` is the install mechanism)
|
|
52
|
+
- A target profile (default: `web`)
|
|
53
|
+
|
|
54
|
+
## Install & usage
|
|
55
|
+
|
|
56
|
+
From this repository (no install needed):
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
node bin/dsh-enhance.mjs list
|
|
60
|
+
node bin/dsh-enhance.mjs install --dry-run
|
|
61
|
+
node bin/dsh-enhance.mjs install
|
|
62
|
+
node bin/dsh-enhance.mjs doctor
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
As a global command (once published to npm):
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npm install -g dsh-enhancement-suite
|
|
69
|
+
dsh-enhance list
|
|
70
|
+
dsh-enhance install
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## CLI
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
dsh-enhance list Show the plugin registry (installed state per profile)
|
|
77
|
+
dsh-enhance install Install all four plugins into a profile
|
|
78
|
+
dsh-enhance update Update installed plugins to the latest versions
|
|
79
|
+
dsh-enhance doctor Check Node, dsh CLI, profile and per-plugin state
|
|
80
|
+
|
|
81
|
+
--profile <name> target profile (default: web)
|
|
82
|
+
--only <keys> comma-separated selection: vision,soul,memory,marketplace (or package names)
|
|
83
|
+
--dry-run print what would run without executing anything
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Examples:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
dsh-enhance install --dry-run # preview everything
|
|
90
|
+
dsh-enhance install --profile web --only vision,soul
|
|
91
|
+
dsh-enhance update --profile headless
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## How it installs
|
|
95
|
+
|
|
96
|
+
1. Every plugin is installed through the **official CLI**: `dsh plugin --profile <profile> add <package>`. This suite never loads the plugins through its own dependency graph or its own cordis patch — the four upstream packages stay independent in the profile.
|
|
97
|
+
2. `dsh-plugin-marketplace` declares `dsh.bundle.patch`, so `dsh plugin add` reconciles it into `dsh.profile.bundles` and its bundle layer auto-mounts it.
|
|
98
|
+
3. The other three are plain dependencies; the suite appends their mount rows to the profile's `cordis.patch.yml` with the same safe, idempotent logic the marketplace's own one-click install uses (an empty `[]` patch is replaced correctly, duplicates are skipped, and a flow-style array is refused instead of corrupted).
|
|
99
|
+
4. Restart `dsh web` and the plugins load.
|
|
100
|
+
|
|
101
|
+
Every step reports a clear `✓` / `✗`, failures are never swallowed, and no API key is read, printed, or forwarded.
|
|
102
|
+
|
|
103
|
+
## Integration / Adoption
|
|
104
|
+
|
|
105
|
+
The following plugins are adopted and shipped built-in by the **Deepseek Harness EAC** desktop client:
|
|
106
|
+
|
|
107
|
+
- 👁️ `dsh-tool-vision`
|
|
108
|
+
- 🎭 `dsh-soul-md`
|
|
109
|
+
- 🧠 `dsh-tdai-memory`
|
|
110
|
+
|
|
111
|
+
[Deepseek-Harness-EAC](https://github.com/zouyuxuan122/Deepseek-Harness-EAC)
|
|
112
|
+
|
|
113
|
+
> Note: EAC's built-in marketplace implementation is a separate project; it does not use `dsh-plugin-marketplace`.
|
|
114
|
+
|
|
115
|
+
## Documentation
|
|
116
|
+
|
|
117
|
+
- [Architecture](docs/architecture.md) — design decisions, manifest schema, safety rules
|
|
118
|
+
- [plugins.json](plugins.json) — the plugin manifest (npm name, repo, category, display name, description)
|
|
119
|
+
|
|
120
|
+
## License
|
|
121
|
+
|
|
122
|
+
MIT — see [LICENSE](LICENSE). Each plugin keeps its own MIT license in its upstream repository.
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# DeepSeek Harness Enhancement Suite
|
|
2
|
+
|
|
3
|
+
[](README.md) [](LICENSE) [](package.json)
|
|
4
|
+
|
|
5
|
+
**Vision · Soul / Persona · Long-term Memory · Plugin Marketplace**
|
|
6
|
+
|
|
7
|
+
四个 Scorp1o117 插件的官方总入口和一键安装套件,为 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(DSH)增强能力。这是一个真正可用的一键安装器,而不是链接合集:它驱动官方 `dsh plugin` CLI、自动处理挂载、并检查结果。
|
|
8
|
+
|
|
9
|
+
> **本仓库不复制、不替代任何独立插件。**
|
|
10
|
+
> **每个插件都在各自的 upstream 仓库独立维护。**
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 👁️ dsh-tool-vision
|
|
15
|
+
|
|
16
|
+
给文本模型增加视觉能力和图片桥接。
|
|
17
|
+
|
|
18
|
+
[](https://www.npmjs.com/package/dsh-tool-vision) [](https://github.com/Scorp1o117/dsh-tool-vision/blob/main/LICENSE)
|
|
19
|
+
|
|
20
|
+
[→ upstream 仓库](https://github.com/Scorp1o117/dsh-tool-vision)
|
|
21
|
+
|
|
22
|
+
## 🎭 dsh-soul-md
|
|
23
|
+
|
|
24
|
+
为 Agent 增加 soul.md 人设、会话/工作区 Persona 和可演化人格。
|
|
25
|
+
|
|
26
|
+
[](https://www.npmjs.com/package/dsh-soul-md) [](https://github.com/Scorp1o117/dsh-soul-md/blob/main/LICENSE)
|
|
27
|
+
|
|
28
|
+
[→ upstream 仓库](https://github.com/Scorp1o117/dsh-soul-md)
|
|
29
|
+
|
|
30
|
+
## 🧠 dsh-tdai-memory
|
|
31
|
+
|
|
32
|
+
完整的长期分层记忆、召回与搜索。
|
|
33
|
+
|
|
34
|
+
[](https://www.npmjs.com/package/dsh-tdai-memory) [](https://github.com/Scorp1o117/dsh-tdai-memory/blob/main/LICENSE)
|
|
35
|
+
|
|
36
|
+
[→ upstream 仓库](https://github.com/Scorp1o117/dsh-tdai-memory)
|
|
37
|
+
|
|
38
|
+
## 🛒 dsh-plugin-marketplace
|
|
39
|
+
|
|
40
|
+
直接在 DSH Web UI 中发现和浏览插件。
|
|
41
|
+
|
|
42
|
+
[](https://www.npmjs.com/package/dsh-plugin-marketplace) [](https://github.com/Scorp1o117/dsh-plugin-marketplace/blob/main/LICENSE)
|
|
43
|
+
|
|
44
|
+
[→ upstream 仓库](https://github.com/Scorp1o117/dsh-plugin-marketplace)
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 环境要求
|
|
49
|
+
|
|
50
|
+
- **Node.js >= 22.18** —— 完整套件继承 `dsh-tdai-memory` 的引擎要求
|
|
51
|
+
- **dsh CLI** 在 `PATH` 上 —— 安装机制是官方 CLI:`dsh plugin --profile <p> add <pkg>`
|
|
52
|
+
- 目标 profile(默认:`web`)
|
|
53
|
+
|
|
54
|
+
## 安装与使用
|
|
55
|
+
|
|
56
|
+
从本仓库直接运行(无需安装):
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
node bin/dsh-enhance.mjs list
|
|
60
|
+
node bin/dsh-enhance.mjs install --dry-run
|
|
61
|
+
node bin/dsh-enhance.mjs install
|
|
62
|
+
node bin/dsh-enhance.mjs doctor
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
发布到 npm 后作为全局命令使用:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npm install -g dsh-enhancement-suite
|
|
69
|
+
dsh-enhance list
|
|
70
|
+
dsh-enhance install
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## CLI
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
dsh-enhance list 查看插件清单(含 profile 内的安装状态)
|
|
77
|
+
dsh-enhance install 把四个插件安装进 profile
|
|
78
|
+
dsh-enhance update 更新已安装插件到最新版本
|
|
79
|
+
dsh-enhance doctor 检查 Node、dsh CLI、profile 与各插件状态
|
|
80
|
+
|
|
81
|
+
--profile <name> 目标 profile(默认:web)
|
|
82
|
+
--only <keys> 逗号分隔的选择:vision,soul,memory,marketplace(或包名)
|
|
83
|
+
--dry-run 只打印将要执行的操作,不实际执行
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
示例:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
dsh-enhance install --dry-run # 预览全部操作
|
|
90
|
+
dsh-enhance install --profile web --only vision,soul
|
|
91
|
+
dsh-enhance update --profile headless
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## 安装方式
|
|
95
|
+
|
|
96
|
+
1. 每个插件都通过**官方 CLI** 安装:`dsh plugin --profile <profile> add <package>`。本套件绝不通过自身的依赖图或自己的 cordis patch 间接加载插件——四个 upstream 包在 profile 中保持独立。
|
|
97
|
+
2. `dsh-plugin-marketplace` 声明了 `dsh.bundle.patch`,`dsh plugin add` 会把它对账进 `dsh.profile.bundles`,由 bundle 层自动挂载。
|
|
98
|
+
3. 其余三个是普通依赖;套件用与 marketplace 一键安装相同的安全、幂等逻辑,把挂载行追加到 profile 的 `cordis.patch.yml`(空 `[]` 补丁会被正确替换、重复挂载会被跳过、流式数组会被拒绝而不是损坏)。
|
|
99
|
+
4. 重启 `dsh web` 即可加载。
|
|
100
|
+
|
|
101
|
+
每一步都有清晰的 `✓` / `✗` 反馈,失败绝不吞掉,且不读取、不打印任何 API Key。
|
|
102
|
+
|
|
103
|
+
## 集成 / 采用
|
|
104
|
+
|
|
105
|
+
以下插件已被 **Deepseek Harness EAC** 桌面客户端内置采用:
|
|
106
|
+
|
|
107
|
+
- 👁️ `dsh-tool-vision`
|
|
108
|
+
- 🎭 `dsh-soul-md`
|
|
109
|
+
- 🧠 `dsh-tdai-memory`
|
|
110
|
+
|
|
111
|
+
[Deepseek-Harness-EAC](https://github.com/zouyuxuan122/Deepseek-Harness-EAC)
|
|
112
|
+
|
|
113
|
+
> 注意:EAC 内置的 marketplace 实现是另一个项目,并没有使用 `dsh-plugin-marketplace`。
|
|
114
|
+
|
|
115
|
+
## 文档
|
|
116
|
+
|
|
117
|
+
- [架构说明](docs/architecture.md) —— 设计决策、manifest 结构、安全规则
|
|
118
|
+
- [plugins.json](plugins.json) —— 插件 manifest(npm 包名、仓库、分类、显示名、描述)
|
|
119
|
+
|
|
120
|
+
## License
|
|
121
|
+
|
|
122
|
+
MIT —— 见 [LICENSE](LICENSE)。各插件在各自 upstream 仓库保留独立的 MIT License。
|
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* dsh-enhance — DeepSeek Harness Enhancement Suite CLI.
|
|
4
|
+
*
|
|
5
|
+
* Zero-dependency, cross-platform (Windows / Linux / macOS) installer and
|
|
6
|
+
* registry for the four Scorp1o117 DSH plugins:
|
|
7
|
+
*
|
|
8
|
+
* vision dsh-tool-vision Vision
|
|
9
|
+
* soul dsh-soul-md Persona
|
|
10
|
+
* memory dsh-tdai-memory Memory
|
|
11
|
+
* marketplace dsh-plugin-marketplace Marketplace
|
|
12
|
+
*
|
|
13
|
+
* Install always prefers the official DeepSeek Harness plugin CLI
|
|
14
|
+
* (`dsh plugin --profile <profile> add <package>`); this suite never loads
|
|
15
|
+
* the plugins through its own dependency graph. Non-bundle plugins are then
|
|
16
|
+
* mounted into the profile's cordis.patch.yml with the same safe, idempotent
|
|
17
|
+
* logic the marketplace itself uses.
|
|
18
|
+
*
|
|
19
|
+
* Safety rules:
|
|
20
|
+
* - subprocesses are spawned with argument ARRAYS, never shell strings;
|
|
21
|
+
* - no API keys are read, printed, or forwarded;
|
|
22
|
+
* - every failure is reported explicitly with a non-zero exit code.
|
|
23
|
+
*/
|
|
24
|
+
import { existsSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
25
|
+
import { dirname, join } from "node:path";
|
|
26
|
+
import { homedir } from "node:os";
|
|
27
|
+
import { fileURLToPath } from "node:url";
|
|
28
|
+
import { spawn } from "node:child_process";
|
|
29
|
+
|
|
30
|
+
const ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
31
|
+
const MANIFEST = JSON.parse(readFileSync(join(ROOT, "plugins.json"), "utf8"));
|
|
32
|
+
const PKG = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8"));
|
|
33
|
+
const PLUGINS = MANIFEST.plugins;
|
|
34
|
+
const DEFAULT_PROFILE = MANIFEST.defaultProfile ?? "web";
|
|
35
|
+
/** Short keys and full package names are both accepted for --only. */
|
|
36
|
+
const KEY_ALIASES = new Map(PLUGINS.map((p) => [p.key, p.name]));
|
|
37
|
+
const NODE_MIN = MANIFEST.node ?? ">=22.18";
|
|
38
|
+
|
|
39
|
+
const ok = (msg) => console.log(` ✓ ${msg}`);
|
|
40
|
+
const bad = (msg) => console.log(` ✗ ${msg}`);
|
|
41
|
+
const info = (msg) => console.log(` · ${msg}`);
|
|
42
|
+
const head = (msg) => console.log(`\n${msg}`);
|
|
43
|
+
|
|
44
|
+
// ── tiny helpers ────────────────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
class CliError extends Error {}
|
|
47
|
+
|
|
48
|
+
function dshHome() {
|
|
49
|
+
return process.env.DSH_HOME || join(homedir(), ".dsh");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function profileDir(profile) {
|
|
53
|
+
return join(dshHome(), "profiles", profile);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function profileManifest(profile) {
|
|
57
|
+
try {
|
|
58
|
+
return JSON.parse(readFileSync(join(profileDir(profile), "package.json"), "utf8"));
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function installedManifest(pkg, profile) {
|
|
65
|
+
try {
|
|
66
|
+
return JSON.parse(readFileSync(join(profileDir(profile), "node_modules", pkg, "package.json"), "utf8"));
|
|
67
|
+
} catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Locate the dsh CLI on PATH (dsh.cmd / dsh.exe on Windows, dsh elsewhere). */
|
|
73
|
+
function findDsh() {
|
|
74
|
+
const override = process.env.DSH_ENHANCE_DSH_BIN;
|
|
75
|
+
if (override) return override;
|
|
76
|
+
const names = process.platform === "win32" ? ["dsh.cmd", "dsh.exe", "dsh"] : ["dsh"];
|
|
77
|
+
const entries = (process.env.PATH ?? "").split(process.platform === "win32" ? ";" : ":").filter(Boolean);
|
|
78
|
+
for (const dir of entries) {
|
|
79
|
+
for (const name of names) {
|
|
80
|
+
const candidate = join(dir, name);
|
|
81
|
+
if (existsSync(candidate)) return candidate;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Spawn one subprocess with an argument array (never a shell string).
|
|
89
|
+
* On Windows the resolved .cmd shim is executed through the shell with
|
|
90
|
+
* per-argument quoting applied by Node, which keeps every argument opaque.
|
|
91
|
+
*/
|
|
92
|
+
function run(bin, args, { capture = false } = {}) {
|
|
93
|
+
return new Promise((resolve) => {
|
|
94
|
+
const child = spawn(bin, args, {
|
|
95
|
+
shell: process.platform === "win32",
|
|
96
|
+
stdio: capture ? ["ignore", "pipe", "pipe"] : "inherit",
|
|
97
|
+
windowsHide: true,
|
|
98
|
+
});
|
|
99
|
+
let stdout = "";
|
|
100
|
+
let stderr = "";
|
|
101
|
+
if (capture) {
|
|
102
|
+
child.stdout.on("data", (d) => { stdout += d; });
|
|
103
|
+
child.stderr.on("data", (d) => { stderr += d; });
|
|
104
|
+
}
|
|
105
|
+
child.on("error", (error) => resolve({ code: -1, stdout, stderr, error }));
|
|
106
|
+
child.on("close", (code) => resolve({ code: code ?? -1, stdout, stderr }));
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Minimal semver compare (numeric dotted triples), enough for the Node gate. */
|
|
111
|
+
function parseVersion(v) {
|
|
112
|
+
const m = /^(\d+)\.(\d+)(?:\.(\d+))?/.exec(String(v).trim());
|
|
113
|
+
return m ? [Number(m[1]), Number(m[2]), Number(m[3] ?? 0)] : null;
|
|
114
|
+
}
|
|
115
|
+
function versionGte(current, minimum) {
|
|
116
|
+
const c = parseVersion(current);
|
|
117
|
+
const m = parseVersion(minimum);
|
|
118
|
+
if (!c || !m) return false;
|
|
119
|
+
for (let i = 0; i < 3; i += 1) {
|
|
120
|
+
if (c[i] > m[i]) return true;
|
|
121
|
+
if (c[i] < m[i]) return false;
|
|
122
|
+
}
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function installedVersion(pkg, profile) {
|
|
127
|
+
return installedManifest(pkg, profile)?.version ?? null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Bundle plugins are auto-mounted by their own dsh.bundle layer. */
|
|
131
|
+
function isBundlePackage(pkg, profile) {
|
|
132
|
+
const manifest = installedManifest(pkg, profile);
|
|
133
|
+
return Boolean(manifest && typeof manifest.dsh?.bundle?.patch === "string" && manifest.dsh.bundle.patch.length > 0);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function isInProfileBundles(pkg, profile) {
|
|
137
|
+
return (profileManifest(profile)?.dsh?.profile?.bundles ?? []).includes(pkg);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function isMountedInPatch(pkg, profile) {
|
|
141
|
+
try {
|
|
142
|
+
const src = readFileSync(join(profileDir(profile), "cordis.patch.yml"), "utf8");
|
|
143
|
+
return src.includes(`name: '${pkg}'`) || src.includes(`name: "${pkg}"`);
|
|
144
|
+
} catch {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Append a mount row for `pkg` to the profile's cordis.patch.yml (idempotent).
|
|
151
|
+
* Mirrors the marketplace's own logic: bundle plugins are skipped (their
|
|
152
|
+
* bundle layer auto-mounts them), an empty `[]` patch is replaced with a
|
|
153
|
+
* block-style insert list, and a non-empty flow-style array is refused
|
|
154
|
+
* rather than corrupted.
|
|
155
|
+
*/
|
|
156
|
+
function ensureMounted(pkg, profile) {
|
|
157
|
+
const patch = join(profileDir(profile), "cordis.patch.yml");
|
|
158
|
+
if (!existsSync(patch)) return { changed: false, note: "cordis.patch.yml not found; mount manually" };
|
|
159
|
+
if (isInProfileBundles(pkg, profile)) return { changed: false, note: "already mounted via dsh.profile.bundles" };
|
|
160
|
+
if (isBundlePackage(pkg, profile)) return { changed: false, note: "bundle plugin: auto-mounted via its own dsh.bundle layer" };
|
|
161
|
+
let src = readFileSync(patch, "utf8");
|
|
162
|
+
if (src.includes(`name: '${pkg}'`) || src.includes(`name: "${pkg}"`)) return { changed: false, note: "already mounted in cordis.patch.yml" };
|
|
163
|
+
const id = pkg.replace(/^@[^/]+\//, "").replace(/[^a-z0-9-]/g, "-") || pkg;
|
|
164
|
+
const row = `\n - id: ${id}\n name: '${pkg}'`;
|
|
165
|
+
const emptyArray = /^(\s*(?:#[^\n]*\n?)*)\[\s*\]\s*$/.exec(src);
|
|
166
|
+
if (emptyArray) {
|
|
167
|
+
const next = `${emptyArray[1]}- insert:${row}\n`;
|
|
168
|
+
if (process.env.DSH_ENHANCE_DRY_RUN !== "1") writeFileSync(patch, next, "utf8");
|
|
169
|
+
return { changed: true, note: "mounted in cordis.patch.yml" };
|
|
170
|
+
}
|
|
171
|
+
const insertAt = src.search(/^- insert:\s*$/m);
|
|
172
|
+
if (insertAt >= 0) {
|
|
173
|
+
const next = src.slice(0, insertAt + "- insert:".length) + row + src.slice(insertAt + "- insert:".length);
|
|
174
|
+
if (process.env.DSH_ENHANCE_DRY_RUN !== "1") writeFileSync(patch, next, "utf8");
|
|
175
|
+
return { changed: true, note: "mounted in cordis.patch.yml" };
|
|
176
|
+
}
|
|
177
|
+
if (/^\s*\[/.test(src)) return { changed: false, note: "patch file uses a flow-style array; mount manually" };
|
|
178
|
+
if (process.env.DSH_ENHANCE_DRY_RUN !== "1") writeFileSync(patch, `${src}\n- insert:${row}\n`, "utf8");
|
|
179
|
+
return { changed: true, note: "mounted in cordis.patch.yml" };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function resolveSelection(only) {
|
|
183
|
+
if (only.length === 0) return [...PLUGINS];
|
|
184
|
+
const seen = new Set();
|
|
185
|
+
const selected = [];
|
|
186
|
+
for (const raw of only) {
|
|
187
|
+
const token = raw.trim();
|
|
188
|
+
const name = KEY_ALIASES.get(token) ?? token;
|
|
189
|
+
const plugin = PLUGINS.find((p) => p.name === name);
|
|
190
|
+
if (!plugin) throw new CliError(`unknown plugin "${token}" (valid: ${PLUGINS.map((p) => `${p.key} (${p.name})`).join(", ")})`);
|
|
191
|
+
if (!seen.has(plugin.name)) {
|
|
192
|
+
seen.add(plugin.name);
|
|
193
|
+
selected.push(plugin);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return selected;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function parseArgs(argv) {
|
|
200
|
+
const out = {
|
|
201
|
+
command: null,
|
|
202
|
+
profile: DEFAULT_PROFILE,
|
|
203
|
+
only: [],
|
|
204
|
+
dryRun: false,
|
|
205
|
+
help: false,
|
|
206
|
+
version: false,
|
|
207
|
+
unknown: [],
|
|
208
|
+
};
|
|
209
|
+
const positional = [];
|
|
210
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
211
|
+
const arg = argv[i];
|
|
212
|
+
if (arg === "--help" || arg === "-h") out.help = true;
|
|
213
|
+
else if (arg === "--version" || arg === "-V") out.version = true;
|
|
214
|
+
else if (arg === "--dry-run") out.dryRun = true;
|
|
215
|
+
else if (arg === "--profile") {
|
|
216
|
+
out.profile = argv[i + 1];
|
|
217
|
+
if (!out.profile) throw new CliError("--profile requires a value");
|
|
218
|
+
i += 1;
|
|
219
|
+
} else if (arg.startsWith("--profile=")) out.profile = arg.slice("--profile=".length);
|
|
220
|
+
else if (arg === "--only") {
|
|
221
|
+
const value = argv[i + 1];
|
|
222
|
+
if (!value) throw new CliError("--only requires a value (comma-separated keys or package names)");
|
|
223
|
+
out.only.push(...value.split(",").map((s) => s.trim()).filter(Boolean));
|
|
224
|
+
i += 1;
|
|
225
|
+
} else if (arg.startsWith("--only=")) {
|
|
226
|
+
out.only.push(...arg.slice("--only=".length).split(",").map((s) => s.trim()).filter(Boolean));
|
|
227
|
+
} else if (arg.startsWith("-") && arg !== "-") out.unknown.push(arg);
|
|
228
|
+
else positional.push(arg);
|
|
229
|
+
}
|
|
230
|
+
out.command = positional[0] ?? null;
|
|
231
|
+
return out;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function printHelp() {
|
|
235
|
+
console.log(`dsh-enhance — DeepSeek Harness Enhancement Suite v${PKG.version}
|
|
236
|
+
|
|
237
|
+
Usage:
|
|
238
|
+
dsh-enhance <command> [options]
|
|
239
|
+
|
|
240
|
+
Commands:
|
|
241
|
+
list Show the plugin registry (installed state per profile)
|
|
242
|
+
install Install all four plugins into a profile
|
|
243
|
+
update Update installed plugins to the latest versions
|
|
244
|
+
doctor Check Node, dsh CLI, profile and per-plugin state
|
|
245
|
+
|
|
246
|
+
Options:
|
|
247
|
+
--profile <name> Target profile (default: ${DEFAULT_PROFILE})
|
|
248
|
+
--only <keys> Comma-separated selection: ${PLUGINS.map((p) => p.key).join(",")} (or package names)
|
|
249
|
+
--dry-run Print what would run without executing anything
|
|
250
|
+
--help, -h Show this help
|
|
251
|
+
--version, -V Show the suite version
|
|
252
|
+
|
|
253
|
+
Examples:
|
|
254
|
+
dsh-enhance list
|
|
255
|
+
dsh-enhance install --dry-run
|
|
256
|
+
dsh-enhance install --profile web --only vision,soul
|
|
257
|
+
dsh-enhance doctor
|
|
258
|
+
|
|
259
|
+
Notes:
|
|
260
|
+
- Install prefers the official CLI: dsh plugin --profile <profile> add <package>
|
|
261
|
+
- Requires Node ${NODE_MIN} (dsh-tdai-memory constraint)
|
|
262
|
+
- This suite never loads plugins through its own dependency graph`);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// ── commands ────────────────────────────────────────────────────────────────
|
|
266
|
+
|
|
267
|
+
function cmdList(profile) {
|
|
268
|
+
console.log(`DeepSeek Harness Enhancement Suite v${PKG.version} — ${PLUGINS.length} plugins`);
|
|
269
|
+
console.log(`Profile: ${profile}`);
|
|
270
|
+
head("");
|
|
271
|
+
for (const p of PLUGINS) {
|
|
272
|
+
const version = installedVersion(p.name, profile);
|
|
273
|
+
const state = version ? `✓ installed (${version})` : "✗ not installed";
|
|
274
|
+
console.log(` ${p.emoji} ${p.key.padEnd(12)} ${p.name.padEnd(26)} ${state}`);
|
|
275
|
+
console.log(` ${p.descriptionEn} — ${p.repo}`);
|
|
276
|
+
}
|
|
277
|
+
head("");
|
|
278
|
+
console.log(`Node requirement: ${NODE_MIN} (running ${process.version})`);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async function cmdInstall(profile, only, dryRun) {
|
|
282
|
+
const plugins = resolveSelection(only);
|
|
283
|
+
const dsh = findDsh();
|
|
284
|
+
if (!dsh) {
|
|
285
|
+
bad("dsh CLI not found on PATH — install dsh first (https://github.com/deepseek-ai/deepseek-harness)");
|
|
286
|
+
process.exitCode = 1;
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
head(`Installing ${plugins.length} plugin(s) into profile "${profile}"`);
|
|
290
|
+
const failures = [];
|
|
291
|
+
for (const p of plugins) {
|
|
292
|
+
info(`${p.emoji} ${p.name}`);
|
|
293
|
+
const addArgs = ["plugin", "--profile", profile, "add", p.name];
|
|
294
|
+
if (dryRun) {
|
|
295
|
+
ok(`would run: dsh ${addArgs.join(" ")}`);
|
|
296
|
+
} else {
|
|
297
|
+
const result = await run(dsh, addArgs);
|
|
298
|
+
if (result.code !== 0) {
|
|
299
|
+
const tail = (result.stderr || result.stdout || "").trim().split("\n").slice(-3).join(" ");
|
|
300
|
+
bad(`${p.name} install failed (${result.code}): ${tail || result.error?.message || "unknown error"}`);
|
|
301
|
+
failures.push(`${p.name}: dsh plugin add exited ${result.code}`);
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
ok(`installed via dsh plugin add`);
|
|
305
|
+
}
|
|
306
|
+
const mount = ensureMounted(p.name, profile);
|
|
307
|
+
if (dryRun) {
|
|
308
|
+
info(`would ${mount.changed ? "mount" : "skip mounting"}: ${mount.note}`);
|
|
309
|
+
ok(`OK (dry-run)`);
|
|
310
|
+
} else if (mount.changed) {
|
|
311
|
+
ok(mount.note);
|
|
312
|
+
} else {
|
|
313
|
+
info(mount.note);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
head("");
|
|
317
|
+
const failed = failures.length;
|
|
318
|
+
const succeeded = plugins.length - failed;
|
|
319
|
+
console.log(`${succeeded}/${plugins.length} plugin(s) OK${failed > 0 ? `, ${failed} failed` : ""}.`);
|
|
320
|
+
if (failed > 0) {
|
|
321
|
+
console.log("Failures:");
|
|
322
|
+
for (const f of failures) console.log(` ✗ ${f}`);
|
|
323
|
+
console.log("Fix the reported plugin(s) and re-run dsh-enhance install. No partial state was hidden.");
|
|
324
|
+
process.exitCode = 1;
|
|
325
|
+
} else {
|
|
326
|
+
console.log("Restart dsh web for the new plugins to load.");
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async function cmdUpdate(profile, only, dryRun) {
|
|
331
|
+
const plugins = resolveSelection(only);
|
|
332
|
+
const dsh = findDsh();
|
|
333
|
+
if (!dsh) {
|
|
334
|
+
bad("dsh CLI not found on PATH");
|
|
335
|
+
process.exitCode = 1;
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
head(`Updating ${plugins.length} plugin(s) in profile "${profile}"`);
|
|
339
|
+
const failures = [];
|
|
340
|
+
for (const p of plugins) {
|
|
341
|
+
if (!installedManifest(p.name, profile)) {
|
|
342
|
+
info(`${p.emoji} ${p.name}: not installed — run "dsh-enhance install" first`);
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
const args = ["plugin", "--profile", profile, "update", p.name];
|
|
346
|
+
if (dryRun) {
|
|
347
|
+
ok(`would run: dsh ${args.join(" ")}`);
|
|
348
|
+
} else {
|
|
349
|
+
const result = await run(dsh, args);
|
|
350
|
+
if (result.code !== 0) {
|
|
351
|
+
const tail = (result.stderr || result.stdout || "").trim().split("\n").slice(-3).join(" ");
|
|
352
|
+
bad(`${p.name} update failed (${result.code}): ${tail || result.error?.message || "unknown error"}`);
|
|
353
|
+
failures.push(`${p.name}: dsh plugin update exited ${result.code}`);
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
ok(`updated: ${p.name} → ${installedVersion(p.name, profile) ?? "latest"}`);
|
|
357
|
+
}
|
|
358
|
+
const mount = ensureMounted(p.name, profile);
|
|
359
|
+
if (!dryRun && mount.changed) ok(mount.note);
|
|
360
|
+
}
|
|
361
|
+
head("");
|
|
362
|
+
if (failures.length > 0) {
|
|
363
|
+
for (const f of failures) console.log(` ✗ ${f}`);
|
|
364
|
+
process.exitCode = 1;
|
|
365
|
+
} else {
|
|
366
|
+
console.log("Update finished. Restart dsh web to load the new versions.");
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function cmdDoctor(profile) {
|
|
371
|
+
let healthy = true;
|
|
372
|
+
head("DeepSeek Harness Enhancement Suite — doctor");
|
|
373
|
+
const nodeOk = versionGte(process.versions.node, NODE_MIN.replace(/^>=\s*/, ""));
|
|
374
|
+
if (nodeOk) ok(`Node ${process.versions.node} (>= ${NODE_MIN.replace(/^>=\s*/, "")})`);
|
|
375
|
+
else {
|
|
376
|
+
bad(`Node ${process.versions.node} — this suite requires ${NODE_MIN} (dsh-tdai-memory constraint)`);
|
|
377
|
+
healthy = false;
|
|
378
|
+
}
|
|
379
|
+
const dsh = findDsh();
|
|
380
|
+
if (dsh) {
|
|
381
|
+
ok(`dsh CLI found: ${dsh}`);
|
|
382
|
+
} else {
|
|
383
|
+
bad("dsh CLI not found on PATH — install DeepSeek Harness first");
|
|
384
|
+
healthy = false;
|
|
385
|
+
}
|
|
386
|
+
const pdir = profileDir(profile);
|
|
387
|
+
if (existsSync(pdir)) ok(`profile "${profile}" exists: ${pdir}`);
|
|
388
|
+
else {
|
|
389
|
+
bad(`profile "${profile}" not found at ${pdir} — create it with "dsh plugin --profile ${profile} add <package>"`);
|
|
390
|
+
healthy = false;
|
|
391
|
+
}
|
|
392
|
+
head("");
|
|
393
|
+
for (const p of PLUGINS) {
|
|
394
|
+
const version = installedVersion(p.name, profile);
|
|
395
|
+
if (!version) {
|
|
396
|
+
bad(`${p.emoji} ${p.name}: not installed`);
|
|
397
|
+
healthy = false;
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
ok(`${p.emoji} ${p.name}: installed (${version})`);
|
|
401
|
+
const mounted = isInProfileBundles(p.name, profile) || isMountedInPatch(p.name, profile) || isBundlePackage(p.name, profile);
|
|
402
|
+
if (mounted) info(` mounted: ${isInProfileBundles(p.name, profile) ? "dsh.profile.bundles" : isMountedInPatch(p.name, profile) ? "cordis.patch.yml" : "own dsh.bundle layer"}`);
|
|
403
|
+
else {
|
|
404
|
+
bad(` not mounted — run "dsh-enhance install"`);
|
|
405
|
+
healthy = false;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
head("");
|
|
409
|
+
if (healthy) {
|
|
410
|
+
console.log("Doctor finished: all checks passed. No API keys are read or printed.");
|
|
411
|
+
} else {
|
|
412
|
+
console.log("Doctor finished with failures — fix the reported items, then re-run.");
|
|
413
|
+
process.exitCode = 1;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// ── entry ───────────────────────────────────────────────────────────────────
|
|
418
|
+
|
|
419
|
+
async function main() {
|
|
420
|
+
const args = parseArgs(process.argv.slice(2));
|
|
421
|
+
if (args.unknown.length > 0) {
|
|
422
|
+
bad(`unknown option(s): ${args.unknown.join(", ")}`);
|
|
423
|
+
printHelp();
|
|
424
|
+
process.exitCode = 1;
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
if (args.version) {
|
|
428
|
+
console.log(PKG.version);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
if (args.help || !args.command) {
|
|
432
|
+
printHelp();
|
|
433
|
+
if (!args.help) process.exitCode = 1;
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
if (args.dryRun) process.env.DSH_ENHANCE_DRY_RUN = "1";
|
|
437
|
+
try {
|
|
438
|
+
switch (args.command) {
|
|
439
|
+
case "list":
|
|
440
|
+
cmdList(args.profile);
|
|
441
|
+
break;
|
|
442
|
+
case "install":
|
|
443
|
+
await cmdInstall(args.profile, args.only, args.dryRun);
|
|
444
|
+
break;
|
|
445
|
+
case "update":
|
|
446
|
+
await cmdUpdate(args.profile, args.only, args.dryRun);
|
|
447
|
+
break;
|
|
448
|
+
case "doctor":
|
|
449
|
+
cmdDoctor(args.profile);
|
|
450
|
+
break;
|
|
451
|
+
default:
|
|
452
|
+
bad(`unknown command "${args.command}"`);
|
|
453
|
+
printHelp();
|
|
454
|
+
process.exitCode = 1;
|
|
455
|
+
}
|
|
456
|
+
} catch (error) {
|
|
457
|
+
if (error instanceof CliError) {
|
|
458
|
+
bad(error.message);
|
|
459
|
+
process.exitCode = 1;
|
|
460
|
+
} else {
|
|
461
|
+
bad(`unexpected error: ${error?.stack ?? error}`);
|
|
462
|
+
process.exitCode = 1;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
main();
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* validate-manifest — validates plugins.json for the suite.
|
|
4
|
+
*
|
|
5
|
+
* Checks: exactly four plugins, no duplicate names/keys, all required
|
|
6
|
+
* fields present, repo URLs are https github.com links. Exits non-zero on
|
|
7
|
+
* any violation. Used by CI and `npm run check`.
|
|
8
|
+
*/
|
|
9
|
+
import { readFileSync } from "node:fs";
|
|
10
|
+
import { dirname, join } from "node:path";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
|
|
13
|
+
const ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
14
|
+
const REQUIRED = ["key", "name", "displayName", "emoji", "category", "repo", "description", "descriptionEn"];
|
|
15
|
+
|
|
16
|
+
let failed = false;
|
|
17
|
+
const fail = (message) => {
|
|
18
|
+
failed = true;
|
|
19
|
+
console.error(`✗ ${message}`);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const manifest = JSON.parse(readFileSync(join(ROOT, "plugins.json"), "utf8"));
|
|
23
|
+
if (!Array.isArray(manifest.plugins)) {
|
|
24
|
+
fail("plugins.json: missing plugins array");
|
|
25
|
+
} else {
|
|
26
|
+
if (manifest.plugins.length !== 4) fail(`expected exactly 4 plugins, got ${manifest.plugins.length}`);
|
|
27
|
+
const names = manifest.plugins.map((p) => p.name);
|
|
28
|
+
const keys = manifest.plugins.map((p) => p.key);
|
|
29
|
+
if (new Set(names).size !== names.length) fail("duplicate plugin names");
|
|
30
|
+
if (new Set(keys).size !== keys.length) fail("duplicate plugin keys");
|
|
31
|
+
for (const p of manifest.plugins) {
|
|
32
|
+
for (const field of REQUIRED) {
|
|
33
|
+
if (typeof p[field] !== "string" || p[field].length === 0) fail(`${p.name ?? "?"}: missing required field ${field}`);
|
|
34
|
+
}
|
|
35
|
+
if (!/^https:\/\/github\.com\//.test(p.repo ?? "")) fail(`${p.name ?? "?"}: repo must be an https github.com URL`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (failed) {
|
|
40
|
+
console.error("plugins.json validation FAILED");
|
|
41
|
+
process.exitCode = 1;
|
|
42
|
+
} else {
|
|
43
|
+
console.log(`plugins.json OK: ${manifest.plugins.map((p) => p.name).join(", ")}`);
|
|
44
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Architecture — dsh-enhancement-suite
|
|
2
|
+
|
|
3
|
+
## Positioning
|
|
4
|
+
|
|
5
|
+
`dsh-enhancement-suite` is the **official entry point and installer** for the four
|
|
6
|
+
Scorp1o117 DeepSeek Harness plugins. It is deliberately **not** a wrapper that
|
|
7
|
+
owns the plugins:
|
|
8
|
+
|
|
9
|
+
- The four upstream repositories are the single source of truth for each plugin.
|
|
10
|
+
- This repository vendors **no plugin source code**.
|
|
11
|
+
- The suite's only runtime artifact is a zero-dependency CLI plus a manifest.
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
15
|
+
│ dsh-enhancement-suite (this repo) │
|
|
16
|
+
│ README / docs / manifest / CLI │
|
|
17
|
+
│ └── bin/dsh-enhance.mjs ── spawns ──▶ dsh plugin CLI │
|
|
18
|
+
│ │ │
|
|
19
|
+
│ plugins.json (registry metadata only) │ installs │
|
|
20
|
+
└──────────────────────────────────────────────│──────────────┘
|
|
21
|
+
▼
|
|
22
|
+
┌───────────────┬───────────────┬───────────────┬──────────────────┐
|
|
23
|
+
│ dsh-tool-vision│ dsh-soul-md │ dsh-tdai-memory│ dsh-plugin- │
|
|
24
|
+
│ (upstream) │ (upstream) │ (upstream) │ marketplace │
|
|
25
|
+
└───────────────┴───────────────┴───────────────┴──────────────────┘
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Why the official CLI, and why not transitive dependencies
|
|
29
|
+
|
|
30
|
+
Installing through `dsh plugin --profile <p> add <pkg>` (which forwards to pnpm
|
|
31
|
+
inside the profile and reconciles `dsh.profile.bundles`) keeps the plugins
|
|
32
|
+
resolvable from the **profile root**, exactly as if the user installed them by
|
|
33
|
+
hand. Loading the plugins as dependencies of this suite package would make them
|
|
34
|
+
resolve through the suite's `node_modules` chain, which pnpm may place outside
|
|
35
|
+
the profile — plugins then fail to load at boot. Hence:
|
|
36
|
+
|
|
37
|
+
- `package.json` has **zero dependencies**.
|
|
38
|
+
- The suite never emits its own cordis patch for the four plugins.
|
|
39
|
+
- Mounting (when required) is written into the **profile's own**
|
|
40
|
+
`cordis.patch.yml`, with the same idempotent logic the marketplace uses.
|
|
41
|
+
|
|
42
|
+
## Plugin manifest (`plugins.json`)
|
|
43
|
+
|
|
44
|
+
| Field | Meaning |
|
|
45
|
+
|---|---|
|
|
46
|
+
| `key` | Short CLI key (`vision`, `soul`, `memory`, `marketplace`) |
|
|
47
|
+
| `name` | npm package name (also the `dsh plugin add` argument) |
|
|
48
|
+
| `displayName` | Display name |
|
|
49
|
+
| `emoji` | Card emoji used by the CLI/README |
|
|
50
|
+
| `category` | Suite category |
|
|
51
|
+
| `repo` | Upstream GitHub repository (source of truth) |
|
|
52
|
+
| `description` / `descriptionEn` | Bilingual one-line descriptions |
|
|
53
|
+
|
|
54
|
+
CI validates: exactly four plugins, no duplicate names/keys, all required
|
|
55
|
+
fields present, `repo` is an https GitHub URL.
|
|
56
|
+
|
|
57
|
+
## Install flow
|
|
58
|
+
|
|
59
|
+
1. `dsh plugin --profile <profile> add <package>` (official CLI).
|
|
60
|
+
2. If the installed package declares `dsh.bundle.patch` (currently only
|
|
61
|
+
`dsh-plugin-marketplace`) → the reconcile step already registered it in
|
|
62
|
+
`dsh.profile.bundles`; the bundle layer auto-mounts it. **No patch edit.**
|
|
63
|
+
3. Otherwise (plain dependency) → append a mount row to the profile's
|
|
64
|
+
`cordis.patch.yml`:
|
|
65
|
+
- idempotent (scans for `name: '<pkg>'`);
|
|
66
|
+
- an empty `[]` patch (optionally after comment lines) is **replaced**
|
|
67
|
+
with a block-style `- insert:` list — appending after `[]` would create
|
|
68
|
+
two top-level YAML documents and crash boot;
|
|
69
|
+
- a non-empty flow-style array is **refused** with a manual-mount message;
|
|
70
|
+
- a package already in `dsh.profile.bundles` is skipped.
|
|
71
|
+
4. `update` maps to `dsh plugin --profile <profile> update <package>`, then
|
|
72
|
+
re-runs the idempotent mount check.
|
|
73
|
+
|
|
74
|
+
## Safety rules
|
|
75
|
+
|
|
76
|
+
- **No shell string concatenation.** Every subprocess is spawned with an
|
|
77
|
+
argument array; on Windows the `.cmd` shim is executed with Node's
|
|
78
|
+
per-argument quoting (`shell: true` + args array, never a joined command
|
|
79
|
+
string).
|
|
80
|
+
- **No secrets.** The CLI never reads, prints, or forwards API keys; it does
|
|
81
|
+
not touch environment secrets or the credentials store.
|
|
82
|
+
- **No swallowed failures.** Each plugin's install/update reports its own
|
|
83
|
+
`✓`/`✗`; failures are collected, summarized, and reflected in the exit code
|
|
84
|
+
(non-zero on any failure). Partial state is never hidden.
|
|
85
|
+
- **`--dry-run`** prints the exact commands and patch edits without executing.
|
|
86
|
+
|
|
87
|
+
## Doctor checks
|
|
88
|
+
|
|
89
|
+
- Node version ≥ `22.18` (suite-wide requirement inherited from
|
|
90
|
+
`dsh-tdai-memory`).
|
|
91
|
+
- `dsh` CLI present on `PATH` (override with `DSH_ENHANCE_DSH_BIN`).
|
|
92
|
+
- Target profile directory exists.
|
|
93
|
+
- Per plugin: installed version, mount state (bundles / patch / own bundle
|
|
94
|
+
layer).
|
|
95
|
+
|
|
96
|
+
## Versioning and release
|
|
97
|
+
|
|
98
|
+
- Suite version follows semver; the README badges query npm **live** for the
|
|
99
|
+
four plugins, so version numbers are never hard-coded.
|
|
100
|
+
- npm publishing is intentionally not automated yet; CI only syntax-checks the
|
|
101
|
+
CLI, validates the manifest, and optionally cross-checks npm availability
|
|
102
|
+
(network failures are informational, `continue-on-error`).
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-enhancement-suite",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Enhancement suite for DeepSeek Harness — the official installer and registry for Scorp1o117's Vision, Soul/Persona, Memory & Marketplace plugins.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=22.18"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"dsh-enhance": "./bin/dsh-enhance.mjs"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"bin",
|
|
15
|
+
"plugins.json",
|
|
16
|
+
"README.md",
|
|
17
|
+
"README.zh.md",
|
|
18
|
+
"LICENSE",
|
|
19
|
+
"docs"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"check": "node --check bin/dsh-enhance.mjs && node bin/validate-manifest.mjs && node bin/dsh-enhance.mjs --help && node bin/dsh-enhance.mjs list",
|
|
23
|
+
"prepublishOnly": "npm run check"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"deepseek-harness",
|
|
27
|
+
"dsh",
|
|
28
|
+
"dsh-plugin",
|
|
29
|
+
"ai-agent",
|
|
30
|
+
"vision",
|
|
31
|
+
"memory",
|
|
32
|
+
"persona",
|
|
33
|
+
"plugin-marketplace"
|
|
34
|
+
],
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/Scorp1o117/dsh-enhancement-suite.git"
|
|
38
|
+
},
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/Scorp1o117/dsh-enhancement-suite/issues"
|
|
41
|
+
},
|
|
42
|
+
"homepage": "https://github.com/Scorp1o117/dsh-enhancement-suite",
|
|
43
|
+
"dependencies": {}
|
|
44
|
+
}
|
package/plugins.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"suite": "dsh-enhancement-suite",
|
|
3
|
+
"node": ">=22.18",
|
|
4
|
+
"defaultProfile": "web",
|
|
5
|
+
"plugins": [
|
|
6
|
+
{
|
|
7
|
+
"key": "vision",
|
|
8
|
+
"name": "dsh-tool-vision",
|
|
9
|
+
"displayName": "dsh-tool-vision",
|
|
10
|
+
"emoji": "👁️",
|
|
11
|
+
"category": "Vision",
|
|
12
|
+
"repo": "https://github.com/Scorp1o117/dsh-tool-vision",
|
|
13
|
+
"description": "给文本模型增加视觉能力和图片桥接。",
|
|
14
|
+
"descriptionEn": "Adds vision capability and image bridging to text-only models."
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"key": "soul",
|
|
18
|
+
"name": "dsh-soul-md",
|
|
19
|
+
"displayName": "dsh-soul-md",
|
|
20
|
+
"emoji": "🎭",
|
|
21
|
+
"category": "Persona",
|
|
22
|
+
"repo": "https://github.com/Scorp1o117/dsh-soul-md",
|
|
23
|
+
"description": "为 Agent 增加 soul.md 人设、会话/工作区 Persona 和可演化人格。",
|
|
24
|
+
"descriptionEn": "soul.md personas, session/workspace persona switching, and an evolving personality with long-term memory."
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"key": "memory",
|
|
28
|
+
"name": "dsh-tdai-memory",
|
|
29
|
+
"displayName": "dsh-tdai-memory",
|
|
30
|
+
"emoji": "🧠",
|
|
31
|
+
"category": "Memory",
|
|
32
|
+
"repo": "https://github.com/Scorp1o117/dsh-tdai-memory",
|
|
33
|
+
"description": "完整的长期分层记忆、召回与搜索。",
|
|
34
|
+
"descriptionEn": "Full long-term layered memory (L0-L3), recall and search."
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"key": "marketplace",
|
|
38
|
+
"name": "dsh-plugin-marketplace",
|
|
39
|
+
"displayName": "dsh-plugin-marketplace",
|
|
40
|
+
"emoji": "🛒",
|
|
41
|
+
"category": "Marketplace",
|
|
42
|
+
"repo": "https://github.com/Scorp1o117/dsh-plugin-marketplace",
|
|
43
|
+
"description": "直接在 DSH Web UI 中发现和浏览插件。",
|
|
44
|
+
"descriptionEn": "Discover and browse plugins directly inside the DSH Web UI."
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
}
|