dsh-plugin-marketplace 0.2.3 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,6 +4,10 @@
4
4
 
5
5
  **GitHub**: [Scorp1o117/dsh-plugin-marketplace](https://github.com/Scorp1o117/dsh-plugin-marketplace) · **npm**: [dsh-plugin-marketplace](https://www.npmjs.com/package/dsh-plugin-marketplace)
6
6
 
7
+ [![Enhancement Suite](https://img.shields.io/badge/part%20of-Enhancement%20Suite-3964fe)](https://github.com/Scorp1o117/dsh-enhancement-suite) [![npm](https://img.shields.io/npm/v/dsh-enhancement-suite)](https://www.npmjs.com/package/dsh-enhancement-suite)
8
+
9
+ Part of the [DeepSeek Harness Enhancement Suite](https://github.com/Scorp1o117/dsh-enhancement-suite) — Vision · Soul/Persona · Long-term Memory · Plugin Marketplace.
10
+
7
11
  A **plugin marketplace** inside the DeepSeek Harness Web UI: browse
8
12
  [github.com/topics/dsh-plugin](https://github.com/topics/dsh-plugin) right
9
13
  from the settings page — no terminal needed.
package/README.zh.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  **GitHub**: [Scorp1o117/dsh-plugin-marketplace](https://github.com/Scorp1o117/dsh-plugin-marketplace) · **npm**: [dsh-plugin-marketplace](https://www.npmjs.com/package/dsh-plugin-marketplace) · [English](README.md)
4
4
 
5
+ [![Enhancement Suite](https://img.shields.io/badge/part%20of-Enhancement%20Suite-3964fe)](https://github.com/Scorp1o117/dsh-enhancement-suite) [![npm](https://img.shields.io/npm/v/dsh-enhancement-suite)](https://www.npmjs.com/package/dsh-enhancement-suite)
6
+
7
+ 属于 [DeepSeek Harness Enhancement Suite](https://github.com/Scorp1o117/dsh-enhancement-suite) —— Vision · Soul/Persona · 长期记忆 · 插件市场。
8
+
5
9
  在 DeepSeek Harness **Web UI 里内置的插件市场**:直接在设置页浏览
6
10
  [github.com/topics/dsh-plugin](https://github.com/topics/dsh-plugin),无需打开终端。
7
11
 
package/client.js CHANGED
@@ -64,6 +64,7 @@ window.__ModuleLoader__.load({
64
64
  empty: "没有找到插件",
65
65
  install: "安装方式",
66
66
  installHint: "在 profile 目录执行 pnpm 安装,然后在 cordis.patch.yml 挂载:",
67
+ installHintBundle: "标准 bundle 插件:dsh plugin add 后由 bundle 层自动挂载,无需手动改 cordis.patch.yml:",
67
68
  readme: "README 摘要",
68
69
  readmeEmpty: "(该仓库没有 README)",
69
70
  openRepo: "打开 GitHub 仓库 ↗",
@@ -94,6 +95,7 @@ window.__ModuleLoader__.load({
94
95
  empty: "No plugins found",
95
96
  install: "Install",
96
97
  installHint: "Run pnpm in your profile dir, then mount in cordis.patch.yml:",
98
+ installHintBundle: "Standard bundle plugin: dsh plugin add auto-mounts it via its bundle layer — no manual cordis.patch.yml edit needed:",
97
99
  readme: "README summary",
98
100
  readmeEmpty: "(no README in this repo)",
99
101
  openRepo: "Open GitHub repo ↗",
@@ -163,6 +165,24 @@ window.__ModuleLoader__.load({
163
165
  for (var i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
164
166
  return new TextDecoder("utf-8").decode(bytes);
165
167
  }
168
+ // npm metadata (bundle detection for the install hint); CORS-enabled, cached.
169
+ var npmCache = new Map(); // pkg -> {isBundle} | null
170
+ async function fetchNpmInfo(pkg) {
171
+ if (npmCache.has(pkg)) return npmCache.get(pkg);
172
+ var promise = fetch("https://registry.npmjs.org/" + encodeURIComponent(pkg) + "/latest", {
173
+ headers: { Accept: "application/vnd.npm.install-v1+json" }
174
+ }).then(function (res) {
175
+ if (!res.ok) return null;
176
+ return res.json();
177
+ }).then(function (data) {
178
+ if (!data || !data.dsh) return { isBundle: false };
179
+ return { isBundle: typeof data.dsh.bundle === "object" && data.dsh.bundle !== null && typeof data.dsh.bundle.patch === "string" && data.dsh.bundle.patch.length > 0 };
180
+ }).catch(function () {
181
+ return null;
182
+ });
183
+ npmCache.set(pkg, promise);
184
+ return promise;
185
+ }
166
186
 
167
187
  // ── components ────────────────────────────────────────────────────────
168
188
  function PluginCard(props) {
@@ -224,11 +244,13 @@ window.__ModuleLoader__.load({
224
244
  h("a", { className: "__mp_link", href: "https://www.npmjs.com/search?q=" + encodeURIComponent(installName), target: "_blank", rel: "noreferrer" }, props.t("openNpm"))
225
245
  ),
226
246
  h("div", { className: "__mp_detailTitle" }, props.t("install")),
227
- h("div", { className: "__mp_readme" }, props.t("installHint")),
247
+ h("div", { className: "__mp_readme" }, props.bundleInfo === true ? props.t("installHintBundle") : props.t("installHint")),
228
248
  h("code", { className: "__mp_code" },
229
249
  "dsh plugin --profile web add " + installName + "\n" +
230
- "# then in $DSH_HOME/profiles/web/cordis.patch.yml:\n" +
231
- "- insert:\n - id: " + installName + "\n name: '" + installName + "'"
250
+ (props.bundleInfo === true
251
+ ? "# standard bundle: auto-mounted via dsh.profile.bundles"
252
+ : "# then in $DSH_HOME/profiles/web/cordis.patch.yml:\n" +
253
+ "- insert:\n - id: " + installName + "\n name: '" + installName + "'")
232
254
  ),
233
255
  btn,
234
256
  statusNode,
@@ -260,7 +282,7 @@ window.__ModuleLoader__.load({
260
282
  var t = props.t;
261
283
  var scope = props.scope;
262
284
  var api = props.api;
263
- var state = react.useState({ q: "", sort: "stars", page: 1, items: [], loading: false, error: null, total: 0, open: null, readme: null, readmeLoading: false, readmeError: false, readmeRateLimited: false });
285
+ var state = react.useState({ q: "", sort: "stars", page: 1, items: [], loading: false, error: null, total: 0, open: null, readme: null, readmeLoading: false, readmeError: false, readmeRateLimited: false, bundleInfo: null });
264
286
  var s = state[0], set = state[1];
265
287
  // install-state subscription (stable pattern: useState + subscribe, never
266
288
  // depend on getSnapshot() reference identity).
@@ -342,6 +364,14 @@ window.__ModuleLoader__.load({
342
364
  return Object.assign({}, prev, { readmeLoading: false, readmeError: !rateLimited, readmeRateLimited: rateLimited });
343
365
  });
344
366
  });
367
+ // npm metadata: does the target package declare dsh.bundle.patch?
368
+ // (determines whether the manual cordis.patch.yml row is needed)
369
+ fetchNpmInfo(plugin.fullName.split("/")[1]).then(function (info) {
370
+ set(function (prev) {
371
+ if (!prev.open || prev.open.fullName !== plugin.fullName) return prev;
372
+ return Object.assign({}, prev, { bundleInfo: info ? Boolean(info.isBundle) : null });
373
+ });
374
+ });
345
375
  }, [s.open]);
346
376
  var submit = function (e) {
347
377
  e.preventDefault();
@@ -364,7 +394,7 @@ window.__ModuleLoader__.load({
364
394
  var open = s.open && s.open.fullName === p.fullName;
365
395
  return h("div", { key: p.fullName, className: "__mp_item" },
366
396
  h(PluginCard, { plugin: p, t: t, onOpen: function () { openDetail(p); } }),
367
- open ? h(DetailPanel, { plugin: s.open, t: t, readme: s.readme, readmeLoading: s.readmeLoading, readmeError: s.readmeError, readmeRateLimited: s.readmeRateLimited, lang: s.open.lang, installState: installState && installState.status === "ready" ? installState.value.installState : null, onInstall: onInstall, ghError: t("ghError"), explainState: installState && installState.status === "ready" ? installState.value.aiExplainResult : null, explainError: s.explainError, onExplain: onExplain }) : null
397
+ open ? h(DetailPanel, { plugin: s.open, t: t, readme: s.readme, readmeLoading: s.readmeLoading, readmeError: s.readmeError, readmeRateLimited: s.readmeRateLimited, lang: s.open.lang, installState: installState && installState.status === "ready" ? installState.value.installState : null, onInstall: onInstall, ghError: t("ghError"), explainState: installState && installState.status === "ready" ? installState.value.aiExplainResult : null, explainError: s.explainError, onExplain: onExplain, bundleInfo: s.bundleInfo }) : null
368
398
  );
369
399
  })
370
400
  ),
package/index.js CHANGED
@@ -109,21 +109,72 @@ function apply(ctx, config) {
109
109
  return process.argv[1];
110
110
  }
111
111
 
112
- /** Append a mount row for `pkg` to cordis.patch.yml (idempotent). */
112
+ /** Read the profile manifest (its `dsh.profile.bundles` list), or null. */
113
+ function profileManifest(profile) {
114
+ try {
115
+ const dshHome = process.env.DSH_HOME || join(os.homedir(), ".dsh");
116
+ return JSON.parse(readFileSync(join(dshHome, "profiles", profile, "package.json"), "utf8"));
117
+ } catch {
118
+ return null;
119
+ }
120
+ }
121
+
122
+ /** Whether the installed package declares dsh.bundle.patch (auto-mounted via its bundle layer). */
123
+ function isBundlePackage(pkg, profile) {
124
+ try {
125
+ const dshHome = process.env.DSH_HOME || join(os.homedir(), ".dsh");
126
+ const manifest = JSON.parse(readFileSync(join(dshHome, "profiles", profile, "node_modules", pkg, "package.json"), "utf8"));
127
+ return typeof manifest?.dsh?.bundle?.patch === "string" && manifest.dsh.bundle.patch.length > 0;
128
+ } catch {
129
+ return false;
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Append a mount row for `pkg` to cordis.patch.yml (idempotent).
135
+ * Bundle plugins (already in `dsh.profile.bundles`, or whose installed
136
+ * package declares `dsh.bundle.patch`) are mounted by their own bundle
137
+ * layer — adding a manual row would create a duplicate loader entry and
138
+ * abort boot. A STALE manual row left by an earlier non-bundle version is
139
+ * removed instead, so reinstalling a bundle plugin never breaks boot.
140
+ */
113
141
  function ensureMounted(pkg, profile) {
114
142
  const dshHome = process.env.DSH_HOME || join(os.homedir(), ".dsh");
115
143
  const patchPath = join(dshHome, "profiles", profile, "cordis.patch.yml");
116
144
  if (!existsSync(patchPath)) return " (patch file not found; mount manually)";
145
+ const bundles = profileManifest(profile)?.dsh?.profile?.bundles ?? [];
146
+ if (bundles.includes(pkg) || isBundlePackage(pkg, profile)) {
147
+ // Bundle layer auto-mounts; remove any stale manual row (pre-bundle).
148
+ const src = readFileSync(patchPath, "utf8");
149
+ const cleaned = src.replace(new RegExp(`[ \\t]*- id: [^\\n]*\\n[ \\t]*name: ['"]${pkg.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}['"]\\n?`, "g"), "");
150
+ if (cleaned !== src) {
151
+ writeFileSync(patchPath, cleaned, "utf8");
152
+ return " (removed stale manual mount row; bundle layer auto-mounts)";
153
+ }
154
+ return bundles.includes(pkg) ? " (already mounted via dsh.profile.bundles)" : " (bundle plugin: auto-mounted via its own dsh.bundle layer)";
155
+ }
117
156
  const id = pkg.replace(/^@[^/]+\//, "").replace(/[^a-z0-9-]/g, "-") || pkg;
118
157
  let src = readFileSync(patchPath, "utf8");
119
158
  if (src.includes(`name: '${pkg}'`) || src.includes(`name: "${pkg}"`)) return " (already mounted)";
120
- // Append under the first `- insert:` list; match its indentation (4 spaces
121
- // is the convention used by dsh profiles).
122
- const insertAt = src.search(/^- insert:\s*$/m);
123
159
  const row = `\n - id: ${id}\n name: '${pkg}'`;
160
+ // A flow-style empty array (`[]`, optionally after comment lines) is a
161
+ // valid empty patch: replace it with the block-style insert list.
162
+ // Appending after `[]` would produce two top-level documents in one
163
+ // stream and crash boot with a YAML parse error.
164
+ const emptyArray = /^(\s*(?:#[^\n]*\n?)*)\[\s*\]\s*$/.exec(src);
165
+ if (emptyArray) {
166
+ writeFileSync(patchPath, `${emptyArray[1]}- insert:${row}\n`, "utf8");
167
+ return " (mounted in cordis.patch.yml)";
168
+ }
169
+ // Insert under the first `- insert:` list; match its indentation (4
170
+ // spaces is the convention used by dsh profiles).
171
+ const insertAt = src.search(/^- insert:\s*$/m);
124
172
  if (insertAt >= 0) {
125
173
  src = src.slice(0, insertAt + "- insert:".length) + row + src.slice(insertAt + "- insert:".length);
126
174
  } else {
175
+ // A non-empty flow-style array cannot be safely extended with string
176
+ // surgery — refuse rather than corrupt the file.
177
+ if (/^\s*\[/.test(src)) return " (patch file uses a flow-style array; mount manually)";
127
178
  src += `\n- insert:${row}\n`;
128
179
  }
129
180
  writeFileSync(patchPath, src, "utf8");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-marketplace",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "DeepSeek Harness Web UI 内置插件市场:在设置页直接浏览 github.com/topics/dsh-plugin,支持搜索、按 Star 排序、查看安装方式。",
5
5
  "type": "module",
6
6
  "main": "index.js",