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 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
+ [![中文文档](https://img.shields.io/badge/%E4%B8%AD%E6%96%87%E6%96%87%E6%A1%A3-blue)](README.zh.md) [![License](https://img.shields.io/badge/license-MIT-blue)](LICENSE) [![Node](https://img.shields.io/badge/node-%3E%3D22.18-green)](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
+ [![npm](https://img.shields.io/npm/v/dsh-tool-vision)](https://www.npmjs.com/package/dsh-tool-vision) [![license](https://img.shields.io/npm/l/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
+ [![npm](https://img.shields.io/npm/v/dsh-soul-md)](https://www.npmjs.com/package/dsh-soul-md) [![license](https://img.shields.io/npm/l/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
+ [![npm](https://img.shields.io/npm/v/dsh-tdai-memory)](https://www.npmjs.com/package/dsh-tdai-memory) [![license](https://img.shields.io/npm/l/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
+ [![npm](https://img.shields.io/npm/v/dsh-plugin-marketplace)](https://www.npmjs.com/package/dsh-plugin-marketplace) [![license](https://img.shields.io/npm/l/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
+ [![English](https://img.shields.io/badge/English-blue)](README.md) [![License](https://img.shields.io/badge/license-MIT-blue)](LICENSE) [![Node](https://img.shields.io/badge/node-%3E%3D22.18-green)](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
+ [![npm](https://img.shields.io/npm/v/dsh-tool-vision)](https://www.npmjs.com/package/dsh-tool-vision) [![license](https://img.shields.io/npm/l/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
+ [![npm](https://img.shields.io/npm/v/dsh-soul-md)](https://www.npmjs.com/package/dsh-soul-md) [![license](https://img.shields.io/npm/l/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
+ [![npm](https://img.shields.io/npm/v/dsh-tdai-memory)](https://www.npmjs.com/package/dsh-tdai-memory) [![license](https://img.shields.io/npm/l/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
+ [![npm](https://img.shields.io/npm/v/dsh-plugin-marketplace)](https://www.npmjs.com/package/dsh-plugin-marketplace) [![license](https://img.shields.io/npm/l/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
+ }