dsh-m 0.2.9 → 0.2.10

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/lib/client.js CHANGED
@@ -112,6 +112,299 @@ var init_market_state = __esm({
112
112
  }
113
113
  });
114
114
 
115
+ // src/client/markdown.js
116
+ var markdown_exports = {};
117
+ __export(markdown_exports, {
118
+ createMarkdown: () => createMarkdown,
119
+ safeUrl: () => safeUrl
120
+ });
121
+ function safeUrl(u) {
122
+ const t = String(u || "").trim();
123
+ if (/^(https?:\/\/|mailto:)/i.test(t)) return t;
124
+ if (/^[/#]/.test(t)) return t;
125
+ return "#";
126
+ }
127
+ function createMarkdown(h2) {
128
+ function ExtLink2({ href, className, children }) {
129
+ return h2(
130
+ "a",
131
+ {
132
+ className: className || "dshm-md-a",
133
+ href: safeUrl(href),
134
+ target: "_blank",
135
+ rel: "noopener noreferrer",
136
+ onClick: (e) => e.stopPropagation()
137
+ },
138
+ children
139
+ );
140
+ }
141
+ function MdImg2({ src, alt }) {
142
+ return h2("img", {
143
+ className: "dshm-md-img",
144
+ src: safeUrl(src),
145
+ alt: alt || "",
146
+ referrerPolicy: "no-referrer",
147
+ onError: (e) => {
148
+ e.currentTarget.style.display = "none";
149
+ }
150
+ });
151
+ }
152
+ function mdInline(text, kb) {
153
+ const re = /(\[!\[[^\]]*\]\([^)]*\)\]\([^)]*\))|(!\[[^\]]*\]\([^)]*\))|(\[[^\]]*\]\([^)]*\))|(`[^`]+`)|(\*\*[^*]+\*\*)|(~~[^~]+~~)|(\*[^*\s][^*]*\*)|(<https?:\/\/[^>\s]+>)|(https?:\/\/[^\s<>()\[\]{}"'「」【】]+[^\s<>()\[\]{}"'「」【】.,;:!?…,。;:!?)】」"')])/g;
154
+ const src = String(text);
155
+ const nodes = [];
156
+ let last = 0;
157
+ let m;
158
+ let i = 0;
159
+ while (m = re.exec(src)) {
160
+ if (m.index > last) nodes.push(src.slice(last, m.index));
161
+ const tok = m[0];
162
+ const k = `${kb}-${i++}`;
163
+ if (tok.startsWith("[![")) {
164
+ const im = /^!\[([^\]]*)\]\(([^)]*)\)/.exec(tok.slice(1));
165
+ const lm = /\]\(([^)]*)\)\s*$/.exec(tok);
166
+ const img = h2(MdImg2, { src: im && im[2], alt: im && im[1] });
167
+ const href = lm && lm[1];
168
+ nodes.push(href && safeUrl(href) !== "#" ? h2(ExtLink2, { key: k, href }, img) : h2("span", { key: k }, img));
169
+ } else if (tok.startsWith("![") || tok.startsWith("<![")) {
170
+ const im = /^!\[([^\]]*)\]\(([^)]*)\)$/.exec(tok);
171
+ nodes.push(h2(MdImg2, { key: k, src: im && im[2], alt: im && im[1] }));
172
+ } else if (tok.startsWith("[")) {
173
+ const lm = /^\[([^\]]*)\]\(([^)]*)\)$/.exec(tok);
174
+ nodes.push(h2(ExtLink2, { key: k, href: lm && lm[2] }, mdInline(lm ? lm[1] : tok, k)));
175
+ } else if (tok.startsWith("`")) {
176
+ nodes.push(h2("code", { key: k }, tok.slice(1, -1)));
177
+ } else if (tok.startsWith("**")) {
178
+ nodes.push(h2("strong", { key: k }, mdInline(tok.slice(2, -2), k)));
179
+ } else if (tok.startsWith("~~")) {
180
+ nodes.push(h2("del", { key: k }, mdInline(tok.slice(2, -2), k)));
181
+ } else if (tok.startsWith("*")) {
182
+ nodes.push(h2("em", { key: k }, mdInline(tok.slice(1, -1), k)));
183
+ } else if (tok.startsWith("<")) {
184
+ const u = tok.slice(1, -1);
185
+ nodes.push(h2(ExtLink2, { key: k, href: u }, u));
186
+ } else {
187
+ nodes.push(h2(ExtLink2, { key: k, href: tok }, tok.length > 72 ? `${tok.slice(0, 69)}\u2026` : tok));
188
+ }
189
+ last = m.index + tok.length;
190
+ }
191
+ if (last < src.length) nodes.push(src.slice(last));
192
+ return nodes;
193
+ }
194
+ function mdBlocks(lines, kb) {
195
+ const out = [];
196
+ let i = 0;
197
+ let n = 0;
198
+ const isFence = (s) => /^\s*```/.test(s);
199
+ const isHeading = (s) => /^#{1,6}\s+/.test(s);
200
+ const isHr = (s) => /^\s*(-{3,}|\*{3,}|_{3,})\s*$/.test(s);
201
+ const isQuote = (s) => /^\s*>/.test(s);
202
+ const isUl = (s) => /^\s*[-*+]\s+/.test(s);
203
+ const isOl = (s) => /^\s*\d+[.)]\s+/.test(s);
204
+ const isTableRow = (s) => s.includes("|") && /^\s*\|/.test(s);
205
+ while (i < lines.length) {
206
+ const line = lines[i];
207
+ if (!line.trim()) {
208
+ i++;
209
+ continue;
210
+ }
211
+ const k = `${kb}-b${n++}`;
212
+ if (isFence(line)) {
213
+ const buf2 = [];
214
+ i++;
215
+ while (i < lines.length && !/^\s*```\s*$/.test(lines[i])) buf2.push(lines[i++]);
216
+ i++;
217
+ out.push(h2("pre", { key: k }, h2("code", null, buf2.join("\n"))));
218
+ continue;
219
+ }
220
+ if (isHeading(line)) {
221
+ const hm = /^(#{1,6})\s+(.*)$/.exec(line);
222
+ out.push(h2(`h${hm[1].length}`, { key: k }, mdInline(hm[2], k)));
223
+ i++;
224
+ continue;
225
+ }
226
+ if (isHr(line)) {
227
+ out.push(h2("hr", { key: k }));
228
+ i++;
229
+ continue;
230
+ }
231
+ if (isQuote(line)) {
232
+ const buf2 = [];
233
+ while (i < lines.length && isQuote(lines[i])) buf2.push(lines[i++].replace(/^\s*>\s?/, ""));
234
+ out.push(h2("blockquote", { key: k }, mdBlocks(buf2, k)));
235
+ continue;
236
+ }
237
+ if (isUl(line) || isOl(line)) {
238
+ const ordered = isOl(line);
239
+ const re = ordered ? /^\s*\d+[.)]\s+(.*)$/ : /^\s*[-*+]\s+(.*)$/;
240
+ const items = [];
241
+ while (i < lines.length && (ordered ? isOl(lines[i]) : isUl(lines[i]))) {
242
+ items.push(h2("li", { key: `li${items.length}` }, mdInline(re.exec(lines[i])[1], `${k}-${items.length}`)));
243
+ i++;
244
+ }
245
+ out.push(h2(ordered ? "ol" : "ul", { key: k }, items));
246
+ continue;
247
+ }
248
+ if (isTableRow(line) && i + 1 < lines.length && lines[i + 1].includes("-") && /^\s*\|?[\s:|-]+\|?\s*$/.test(lines[i + 1])) {
249
+ const cells = (s) => s.trim().replace(/^\|/, "").replace(/\|$/, "").split("|").map((c) => c.trim());
250
+ const head = cells(lines[i]);
251
+ i += 2;
252
+ const rows = [];
253
+ while (i < lines.length && isTableRow(lines[i])) {
254
+ rows.push(cells(lines[i]));
255
+ i++;
256
+ }
257
+ out.push(
258
+ h2(
259
+ "table",
260
+ { key: k },
261
+ h2("thead", null, h2("tr", null, head.map((c, x) => h2("th", { key: x }, mdInline(c, `${k}h${x}`))))),
262
+ h2("tbody", null, rows.map((r, y) => h2("tr", { key: y }, r.map((c, x) => h2("td", { key: x }, mdInline(c, `${k}${y}x${x}`))))))
263
+ )
264
+ );
265
+ continue;
266
+ }
267
+ const buf = [line];
268
+ i++;
269
+ while (i < lines.length && lines[i].trim() && !isFence(lines[i]) && !isHeading(lines[i]) && !isHr(lines[i]) && !isQuote(lines[i]) && !isUl(lines[i]) && !isOl(lines[i])) {
270
+ buf.push(lines[i]);
271
+ i++;
272
+ }
273
+ out.push(h2("p", { key: k }, mdInline(buf.join(" "), k)));
274
+ }
275
+ return out;
276
+ }
277
+ function renderMarkdown2(src) {
278
+ return mdBlocks(String(src || "").replace(/\r\n?/g, "\n").split("\n"), "md");
279
+ }
280
+ return { ExtLink: ExtLink2, MdImg: MdImg2, renderMarkdown: renderMarkdown2 };
281
+ }
282
+ var init_markdown = __esm({
283
+ "src/client/markdown.js"() {
284
+ "use strict";
285
+ }
286
+ });
287
+
288
+ // src/client/installed-view.js
289
+ var installed_view_exports = {};
290
+ __export(installed_view_exports, {
291
+ installedViewModel: () => installedViewModel,
292
+ registrySourceKey: () => registrySourceKey
293
+ });
294
+ function uninstallGuardKeys(it) {
295
+ if (it.source === "link") {
296
+ return { confirmKey: "confirm.unlink", warnKey: "warn.unlink", warnParams: { path: it.path } };
297
+ }
298
+ if (it.source === "file") {
299
+ return { confirmKey: "confirm.core", warnKey: "warn.core", warnParams: {} };
300
+ }
301
+ return { confirmKey: "confirm.uninstall", warnKey: null, warnParams: {} };
302
+ }
303
+ function installedViewModel(it) {
304
+ return {
305
+ githubRepo: it.registryGithub || it.githubRepo || (it.spec.startsWith("github:") ? it.spec.slice(7).split("#")[0] : null),
306
+ sourceLabelKey: { npm: "src.npm", github: "src.github", link: "src.link", file: "src.file", unknown: "src.unknown" }[it.source] || null,
307
+ guard: uninstallGuardKeys(it),
308
+ latestLabel: it.latestTag || (it.latestVersion ? `v${it.latestVersion}` : ""),
309
+ latestLabelDetail: it.latestTag || (it.latestVersion ? `v${it.latestVersion}` : "\u2014")
310
+ };
311
+ }
312
+ function registrySourceKey(data) {
313
+ if (!data) return null;
314
+ const map = {
315
+ "default-raw": "src.default.raw",
316
+ "default-jsdelivr": "src.default.jsdelivr",
317
+ "default-cache": "src.default.cache",
318
+ bundled: "src.bundled",
319
+ "custom-url": "src.custom.url",
320
+ "custom-file": "src.custom.file",
321
+ "custom-cache": "src.custom.cache",
322
+ "custom-unavailable": "src.custom.unavailable",
323
+ // 旧字段兼容
324
+ override: "src.override",
325
+ jsdelivr: "src.jsdelivr",
326
+ raw: "src.raw",
327
+ cache: "src.cache"
328
+ };
329
+ return map[data.source] || data.source;
330
+ }
331
+ var init_installed_view = __esm({
332
+ "src/client/installed-view.js"() {
333
+ "use strict";
334
+ }
335
+ });
336
+
337
+ // src/client/tool-view.js
338
+ var tool_view_exports = {};
339
+ __export(tool_view_exports, {
340
+ parseToolArgs: () => parseToolArgs,
341
+ pickPayload: () => pickPayload
342
+ });
343
+ function pickPayload(props) {
344
+ const found = [];
345
+ const visit = (node, depth) => {
346
+ if (!node || depth > 6) return;
347
+ if (typeof node === "string") {
348
+ const t = node.trim();
349
+ if ((t.startsWith("{") || t.startsWith("[")) && t.length > 8) {
350
+ try {
351
+ visit(JSON.parse(t), depth + 1);
352
+ } catch {
353
+ }
354
+ }
355
+ return;
356
+ }
357
+ if (typeof node !== "object") return;
358
+ if (Array.isArray(node)) {
359
+ for (const x of node) visit(x, depth + 1);
360
+ return;
361
+ }
362
+ if (Array.isArray(node.items)) found.push(node);
363
+ for (const key of ["block", "meta", "result", "resultView", "view", "data", "value", "payload", "content", "message"]) {
364
+ if (node[key] != null) visit(node[key], depth + 1);
365
+ }
366
+ };
367
+ visit(props, 0);
368
+ return found.find((x) => x && Array.isArray(x.items)) || null;
369
+ }
370
+ function parseToolArgs(props) {
371
+ const block = props?.block;
372
+ const raw = (block && "kind" in block ? block.call?.argsRaw : block?.argsRaw) || "";
373
+ if (!raw || typeof raw !== "string") return {};
374
+ try {
375
+ return JSON.parse(raw);
376
+ } catch {
377
+ return {};
378
+ }
379
+ }
380
+ var init_tool_view = __esm({
381
+ "src/client/tool-view.js"() {
382
+ "use strict";
383
+ }
384
+ });
385
+
386
+ // src/client/restart-wait.js
387
+ var restart_wait_exports = {};
388
+ __export(restart_wait_exports, {
389
+ RESTART_DEADLINE_MS: () => RESTART_DEADLINE_MS,
390
+ RESTART_POLL_MS: () => RESTART_POLL_MS,
391
+ nextRestartWait: () => nextRestartWait
392
+ });
393
+ function nextRestartWait({ phase, now = 0, deadlineAt = Infinity, bootChanged = false }) {
394
+ if (phase === "before-ping") {
395
+ return now > deadlineAt ? "timeout" : "continue";
396
+ }
397
+ return bootChanged ? "done" : "continue";
398
+ }
399
+ var RESTART_POLL_MS, RESTART_DEADLINE_MS;
400
+ var init_restart_wait = __esm({
401
+ "src/client/restart-wait.js"() {
402
+ "use strict";
403
+ RESTART_POLL_MS = 2e3;
404
+ RESTART_DEADLINE_MS = 9e4;
405
+ }
406
+ });
407
+
115
408
  // src/client/main.jsx
116
409
  var React = require("react");
117
410
  var rd = require("react-dom");
@@ -120,6 +413,11 @@ var { useState, useEffect, useCallback, useMemo, useRef } = React;
120
413
  var PLUGIN_ID = "dsh-m";
121
414
  var API = "/dshm";
122
415
  var { MARKET_PAGE_SIZE: MARKET_PAGE_SIZE2, normalizeMarketQuery: normalizeMarketQuery2, resetPageOnFilterChange: resetPageOnFilterChange2, normalizeMarketResponse: normalizeMarketResponse2, registryNotice: registryNotice2 } = (init_market_state(), __toCommonJS(market_state_exports));
416
+ var { createMarkdown: createMarkdown2 } = (init_markdown(), __toCommonJS(markdown_exports));
417
+ var { ExtLink, MdImg, renderMarkdown } = createMarkdown2(h);
418
+ var { installedViewModel: installedViewModel2, registrySourceKey: registrySourceKey2 } = (init_installed_view(), __toCommonJS(installed_view_exports));
419
+ var { pickPayload: pickPayload2, parseToolArgs: parseToolArgs2 } = (init_tool_view(), __toCommonJS(tool_view_exports));
420
+ var { RESTART_POLL_MS: RESTART_POLL_MS2, RESTART_DEADLINE_MS: RESTART_DEADLINE_MS2, nextRestartWait: nextRestartWait2 } = (init_restart_wait(), __toCommonJS(restart_wait_exports));
123
421
  var ZH = {
124
422
  "market.title": "\u63D2\u4EF6\u5E02\u573A",
125
423
  "tab.market": "\u5E02\u573A",
@@ -664,164 +962,6 @@ function Icon({ entry }) {
664
962
  function Spin() {
665
963
  return h("span", { className: "dshm-spin" });
666
964
  }
667
- function safeUrl(u) {
668
- const t = String(u || "").trim();
669
- if (/^(https?:\/\/|mailto:)/i.test(t)) return t;
670
- if (/^[/#]/.test(t)) return t;
671
- return "#";
672
- }
673
- function ExtLink({ href, className, children }) {
674
- return h(
675
- "a",
676
- {
677
- className: className || "dshm-md-a",
678
- href: safeUrl(href),
679
- target: "_blank",
680
- rel: "noopener noreferrer",
681
- onClick: (e) => e.stopPropagation()
682
- },
683
- children
684
- );
685
- }
686
- function MdImg({ src, alt }) {
687
- return h("img", {
688
- className: "dshm-md-img",
689
- src: safeUrl(src),
690
- alt: alt || "",
691
- referrerPolicy: "no-referrer",
692
- onError: (e) => {
693
- e.currentTarget.style.display = "none";
694
- }
695
- });
696
- }
697
- function mdInline(text, kb) {
698
- const re = /(\[!\[[^\]]*\]\([^)]*\)\]\([^)]*\))|(!\[[^\]]*\]\([^)]*\))|(\[[^\]]*\]\([^)]*\))|(`[^`]+`)|(\*\*[^*]+\*\*)|(~~[^~]+~~)|(\*[^*\s][^*]*\*)|(<https?:\/\/[^>\s]+>)|(https?:\/\/[^\s<>()\[\]{}"'「」【】]+[^\s<>()\[\]{}"'「」【】.,;:!?…,。;:!?)】」"')])/g;
699
- const src = String(text);
700
- const nodes = [];
701
- let last = 0;
702
- let m;
703
- let i = 0;
704
- while (m = re.exec(src)) {
705
- if (m.index > last) nodes.push(src.slice(last, m.index));
706
- const tok = m[0];
707
- const k = `${kb}-${i++}`;
708
- if (tok.startsWith("[![")) {
709
- const im = /^!\[([^\]]*)\]\(([^)]*)\)/.exec(tok.slice(1));
710
- const lm = /\]\(([^)]*)\)\s*$/.exec(tok);
711
- const img = h(MdImg, { src: im && im[2], alt: im && im[1] });
712
- const href = lm && lm[1];
713
- nodes.push(href && safeUrl(href) !== "#" ? h(ExtLink, { key: k, href }, img) : h("span", { key: k }, img));
714
- } else if (tok.startsWith("![") || tok.startsWith("<![")) {
715
- const im = /^!\[([^\]]*)\]\(([^)]*)\)$/.exec(tok);
716
- nodes.push(h(MdImg, { key: k, src: im && im[2], alt: im && im[1] }));
717
- } else if (tok.startsWith("[")) {
718
- const lm = /^\[([^\]]*)\]\(([^)]*)\)$/.exec(tok);
719
- nodes.push(h(ExtLink, { key: k, href: lm && lm[2] }, mdInline(lm ? lm[1] : tok, k)));
720
- } else if (tok.startsWith("`")) {
721
- nodes.push(h("code", { key: k }, tok.slice(1, -1)));
722
- } else if (tok.startsWith("**")) {
723
- nodes.push(h("strong", { key: k }, mdInline(tok.slice(2, -2), k)));
724
- } else if (tok.startsWith("~~")) {
725
- nodes.push(h("del", { key: k }, mdInline(tok.slice(2, -2), k)));
726
- } else if (tok.startsWith("*")) {
727
- nodes.push(h("em", { key: k }, mdInline(tok.slice(1, -1), k)));
728
- } else if (tok.startsWith("<")) {
729
- const u = tok.slice(1, -1);
730
- nodes.push(h(ExtLink, { key: k, href: u }, u));
731
- } else {
732
- nodes.push(h(ExtLink, { key: k, href: tok }, tok.length > 72 ? `${tok.slice(0, 69)}\u2026` : tok));
733
- }
734
- last = m.index + tok.length;
735
- }
736
- if (last < src.length) nodes.push(src.slice(last));
737
- return nodes;
738
- }
739
- function mdBlocks(lines, kb) {
740
- const out = [];
741
- let i = 0;
742
- let n = 0;
743
- const isFence = (s) => /^\s*```/.test(s);
744
- const isHeading = (s) => /^#{1,6}\s+/.test(s);
745
- const isHr = (s) => /^\s*(-{3,}|\*{3,}|_{3,})\s*$/.test(s);
746
- const isQuote = (s) => /^\s*>/.test(s);
747
- const isUl = (s) => /^\s*[-*+]\s+/.test(s);
748
- const isOl = (s) => /^\s*\d+[.)]\s+/.test(s);
749
- const isTableRow = (s) => s.includes("|") && /^\s*\|/.test(s);
750
- while (i < lines.length) {
751
- const line = lines[i];
752
- if (!line.trim()) {
753
- i++;
754
- continue;
755
- }
756
- const k = `${kb}-b${n++}`;
757
- if (isFence(line)) {
758
- const buf2 = [];
759
- i++;
760
- while (i < lines.length && !/^\s*```\s*$/.test(lines[i])) buf2.push(lines[i++]);
761
- i++;
762
- out.push(h("pre", { key: k }, h("code", null, buf2.join("\n"))));
763
- continue;
764
- }
765
- if (isHeading(line)) {
766
- const hm = /^(#{1,6})\s+(.*)$/.exec(line);
767
- out.push(h(`h${hm[1].length}`, { key: k }, mdInline(hm[2], k)));
768
- i++;
769
- continue;
770
- }
771
- if (isHr(line)) {
772
- out.push(h("hr", { key: k }));
773
- i++;
774
- continue;
775
- }
776
- if (isQuote(line)) {
777
- const buf2 = [];
778
- while (i < lines.length && isQuote(lines[i])) buf2.push(lines[i++].replace(/^\s*>\s?/, ""));
779
- out.push(h("blockquote", { key: k }, mdBlocks(buf2, k)));
780
- continue;
781
- }
782
- if (isUl(line) || isOl(line)) {
783
- const ordered = isOl(line);
784
- const re = ordered ? /^\s*\d+[.)]\s+(.*)$/ : /^\s*[-*+]\s+(.*)$/;
785
- const items = [];
786
- while (i < lines.length && (ordered ? isOl(lines[i]) : isUl(lines[i]))) {
787
- items.push(h("li", { key: `li${items.length}` }, mdInline(re.exec(lines[i])[1], `${k}-${items.length}`)));
788
- i++;
789
- }
790
- out.push(h(ordered ? "ol" : "ul", { key: k }, items));
791
- continue;
792
- }
793
- if (isTableRow(line) && i + 1 < lines.length && lines[i + 1].includes("-") && /^\s*\|?[\s:|-]+\|?\s*$/.test(lines[i + 1])) {
794
- const cells = (s) => s.trim().replace(/^\|/, "").replace(/\|$/, "").split("|").map((c) => c.trim());
795
- const head = cells(lines[i]);
796
- i += 2;
797
- const rows = [];
798
- while (i < lines.length && isTableRow(lines[i])) {
799
- rows.push(cells(lines[i]));
800
- i++;
801
- }
802
- out.push(
803
- h(
804
- "table",
805
- { key: k },
806
- h("thead", null, h("tr", null, head.map((c, x) => h("th", { key: x }, mdInline(c, `${k}h${x}`))))),
807
- h("tbody", null, rows.map((r, y) => h("tr", { key: y }, r.map((c, x) => h("td", { key: x }, mdInline(c, `${k}${y}x${x}`))))))
808
- )
809
- );
810
- continue;
811
- }
812
- const buf = [line];
813
- i++;
814
- while (i < lines.length && lines[i].trim() && !isFence(lines[i]) && !isHeading(lines[i]) && !isHr(lines[i]) && !isQuote(lines[i]) && !isUl(lines[i]) && !isOl(lines[i])) {
815
- buf.push(lines[i]);
816
- i++;
817
- }
818
- out.push(h("p", { key: k }, mdInline(buf.join(" "), k)));
819
- }
820
- return out;
821
- }
822
- function renderMarkdown(src) {
823
- return mdBlocks(String(src || "").replace(/\r\n?/g, "\n").split("\n"), "md");
824
- }
825
965
  function officialLinks({ npm, github, homepage }) {
826
966
  const links = [];
827
967
  if (github) links.push(["GitHub", `https://github.com/${github}`]);
@@ -874,15 +1014,19 @@ function RestartBanner({ note, onDone }) {
874
1014
  const ping0 = await api("ping");
875
1015
  await api("restart");
876
1016
  setPhase("waiting");
877
- const deadline = Date.now() + 9e4;
1017
+ const deadlineAt = Date.now() + RESTART_DEADLINE_MS2;
878
1018
  for (; ; ) {
879
- await new Promise((r) => setTimeout(r, 2e3));
880
- if (Date.now() > deadline) throw new Error(lookup("restart.timeout"));
1019
+ await new Promise((r) => setTimeout(r, RESTART_POLL_MS2));
1020
+ if (nextRestartWait2({ phase: "before-ping", now: Date.now(), deadlineAt }) === "timeout") {
1021
+ throw new Error(lookup("restart.timeout"));
1022
+ }
1023
+ let bootChanged = false;
881
1024
  try {
882
1025
  const ping = await api("ping");
883
- if (ping.boot !== ping0.boot) break;
1026
+ bootChanged = ping.boot !== ping0.boot;
884
1027
  } catch {
885
1028
  }
1029
+ if (nextRestartWait2({ phase: "after-ping", bootChanged }) === "done") break;
886
1030
  }
887
1031
  setPhase("idle");
888
1032
  onDone(true);
@@ -1091,18 +1235,6 @@ function ReadmeBlock({ pkg }) {
1091
1235
  state.truncated ? h("div", { className: "dshm-md-note" }, lookup("readme.truncated")) : null
1092
1236
  );
1093
1237
  }
1094
- function uninstallGuard(it) {
1095
- if (it.source === "link") {
1096
- return {
1097
- confirm: lookup("confirm.unlink"),
1098
- warn: lookup("warn.unlink", { path: it.path })
1099
- };
1100
- }
1101
- if (it.source === "file") {
1102
- return { confirm: lookup("confirm.core"), warn: lookup("warn.core") };
1103
- }
1104
- return { confirm: lookup("confirm.uninstall"), warn: null };
1105
- }
1106
1238
  function InstalledTab({ notify, installed }) {
1107
1239
  const { loading, data, error, reload } = installed;
1108
1240
  const [openPkg, setOpenPkg] = useState(null);
@@ -1158,33 +1290,33 @@ function InstalledTab({ notify, installed }) {
1158
1290
  "div",
1159
1291
  { className: "dshm-cards" },
1160
1292
  items.map((it) => {
1161
- const guard = uninstallGuard(it);
1293
+ const vm = installedViewModel2(it);
1162
1294
  return Card({
1163
1295
  key: it.pkg,
1164
- icon: h(Icon, { entry: { name: it.name, github: it.registryGithub || it.githubRepo || (it.spec.startsWith("github:") ? it.spec.slice(7).split("#")[0] : null), icon: null } }),
1296
+ icon: h(Icon, { entry: { name: it.name, github: vm.githubRepo, icon: null } }),
1165
1297
  name: it.name,
1166
1298
  badges: [
1167
- it.outdated ? h("span", { className: "dshm-badge warn", key: "u" }, `\u2B06 ${it.latestTag || (it.latestVersion ? `v${it.latestVersion}` : "")}`.trim()) : null,
1299
+ it.outdated ? h("span", { className: "dshm-badge warn", key: "u" }, `\u2B06 ${vm.latestLabel}`.trim()) : null,
1168
1300
  it.registryId ? h("span", { className: "dshm-badge", key: "r" }, lookup("badge.market")) : h("span", { className: "dshm-badge info", key: "r" }, lookup("badge.nonmarket"))
1169
1301
  ],
1170
1302
  desc: it.description || "\uFF08\u65E0\u63CF\u8FF0\uFF09",
1171
1303
  sub: [
1172
1304
  `v${it.version || "?"}`,
1173
- { npm: lookup("src.npm"), github: lookup("src.github"), link: lookup("src.link"), file: lookup("src.file"), unknown: lookup("src.unknown") }[it.source] || it.source
1305
+ vm.sourceLabelKey ? lookup(vm.sourceLabelKey) : it.source
1174
1306
  ].join(" \xB7 "),
1175
1307
  links: h(LinksRow, {
1176
1308
  npm: it.source === "npm" ? it.pkg : null,
1177
- github: it.registryGithub || it.githubRepo || (it.spec.startsWith("github:") ? it.spec.slice(7).split("#")[0] : null)
1309
+ github: vm.githubRepo
1178
1310
  }),
1179
1311
  open: openPkg === it.pkg,
1180
1312
  onToggle: () => setOpenPkg(openPkg === it.pkg ? null : it.pkg),
1181
1313
  detail: readmePkg === it.pkg ? h(ReadmeBlock, { pkg: it.pkg }) : DetailRows([
1182
1314
  [lookup("detail.pkg"), it.pkg],
1183
1315
  [lookup("detail.spec"), it.spec],
1184
- [lookup("detail.latest"), it.latestTag || (it.latestVersion ? `v${it.latestVersion}` : "\u2014")],
1316
+ [lookup("detail.latest"), vm.latestLabelDetail],
1185
1317
  [lookup("detail.listed"), it.registryId || lookup("detail.listed.no")],
1186
1318
  [lookup("detail.path"), it.path],
1187
- guard.warn ? [lookup("detail.note"), guard.warn] : null
1319
+ vm.guard.warnKey ? [lookup("detail.note"), lookup(vm.guard.warnKey, vm.guard.warnParams)] : null
1188
1320
  ]),
1189
1321
  actions: [
1190
1322
  h("button", {
@@ -1211,7 +1343,7 @@ function InstalledTab({ notify, installed }) {
1211
1343
  h(TwoStepButton, {
1212
1344
  key: "un",
1213
1345
  label: lookup("action.uninstall"),
1214
- confirmLabel: guard.confirm,
1346
+ confirmLabel: lookup(vm.guard.confirmKey),
1215
1347
  className: "dshm-btn sm",
1216
1348
  disabled: busyPkg === it.pkg,
1217
1349
  onConfirm: () => doUninstall(it)
@@ -1223,23 +1355,8 @@ function InstalledTab({ notify, installed }) {
1223
1355
  );
1224
1356
  }
1225
1357
  function regSourceLabel(data) {
1226
- if (!data) return "\u2014";
1227
- const map = {
1228
- "default-raw": "src.default.raw",
1229
- "default-jsdelivr": "src.default.jsdelivr",
1230
- "default-cache": "src.default.cache",
1231
- bundled: "src.bundled",
1232
- "custom-url": "src.custom.url",
1233
- "custom-file": "src.custom.file",
1234
- "custom-cache": "src.custom.cache",
1235
- "custom-unavailable": "src.custom.unavailable",
1236
- // 旧字段兼容
1237
- override: "src.override",
1238
- jsdelivr: "src.jsdelivr",
1239
- raw: "src.raw",
1240
- cache: "src.cache"
1241
- };
1242
- return lookup(map[data.source] || data.source);
1358
+ const key = registrySourceKey2(data);
1359
+ return key === null ? "\u2014" : lookup(key);
1243
1360
  }
1244
1361
  function configStatusLabel(status) {
1245
1362
  return lookup(`settings.status.${status || "loading"}` || "settings.status.loading");
@@ -1646,43 +1763,6 @@ function registerSlot(slots, options, component) {
1646
1763
  if (next.key == null && next.id != null) next.key = next.id;
1647
1764
  return slots.register(next, component);
1648
1765
  }
1649
- function pickPayload(props) {
1650
- const found = [];
1651
- const visit = (node, depth) => {
1652
- if (!node || depth > 6) return;
1653
- if (typeof node === "string") {
1654
- const t = node.trim();
1655
- if ((t.startsWith("{") || t.startsWith("[")) && t.length > 8) {
1656
- try {
1657
- visit(JSON.parse(t), depth + 1);
1658
- } catch {
1659
- }
1660
- }
1661
- return;
1662
- }
1663
- if (typeof node !== "object") return;
1664
- if (Array.isArray(node)) {
1665
- for (const x of node) visit(x, depth + 1);
1666
- return;
1667
- }
1668
- if (Array.isArray(node.items)) found.push(node);
1669
- for (const key of ["block", "meta", "result", "resultView", "view", "data", "value", "payload", "content", "message"]) {
1670
- if (node[key] != null) visit(node[key], depth + 1);
1671
- }
1672
- };
1673
- visit(props, 0);
1674
- return found.find((x) => x && Array.isArray(x.items)) || null;
1675
- }
1676
- function parseToolArgs(props) {
1677
- const block = props?.block;
1678
- const raw = (block && "kind" in block ? block.call?.argsRaw : block?.argsRaw) || "";
1679
- if (!raw || typeof raw !== "string") return {};
1680
- try {
1681
- return JSON.parse(raw);
1682
- } catch {
1683
- return {};
1684
- }
1685
- }
1686
1766
  function ToolCardRow({ it, onInstalled }) {
1687
1767
  const [busy, setBusy] = useState(false);
1688
1768
  const install = async (e) => {
@@ -1723,8 +1803,8 @@ function ToolCardRow({ it, onInstalled }) {
1723
1803
  }
1724
1804
  function SearchToolView(props) {
1725
1805
  useEffect(() => ensureCss(), []);
1726
- const payload = pickPayload(props);
1727
- const args = parseToolArgs(props);
1806
+ const payload = pickPayload2(props);
1807
+ const args = parseToolArgs2(props);
1728
1808
  const query = String(payload?.query || args.query || "").trim();
1729
1809
  const fromTool = Array.isArray(payload?.items) && payload.items.length ? payload.items : null;
1730
1810
  const running = !!(props?.block && !("kind" in props.block));
@@ -1764,7 +1844,7 @@ function SearchToolView(props) {
1764
1844
  }
1765
1845
  function ListToolView(props) {
1766
1846
  useEffect(() => ensureCss(), []);
1767
- const payload = pickPayload(props);
1847
+ const payload = pickPayload2(props);
1768
1848
  const items = Array.isArray(payload?.items) ? payload.items : [];
1769
1849
  if (!items.length) return null;
1770
1850
  return h(
@@ -314,19 +314,6 @@ function writeDangerouslyAllowAllBuilds(profileDirectory) {
314
314
  writeFileSync(file, next);
315
315
  return true;
316
316
  }
317
- export function rewritePnpmError(err) {
318
- const text = err instanceof Error ? err.message : String(err);
319
- if (/ERR_PNPM_UNUSED_PATCH/.test(text)) {
320
- return new Error('profile 的补丁配置(patchedDependencies)里存在不再使用的条目,pnpm 拒绝执行。卸载时 dsh-m 会自动摘除目标包自己的补丁条目;仍报此错通常是其他包留有失效补丁,请手工清理 profile 的 pnpm-workspace.yaml。');
321
- }
322
- if (isPrepareBlocked(text)) {
323
- return new Error('该插件需要执行构建脚本(prepare),pnpm 默认拦截。dsh-m 已写入 profile 的 dangerouslyAllowAllBuilds 并重试;若仍失败请检查 web profile 是否可写。');
324
- }
325
- if (/ERR_PNPM_PUBLIC_HOIST_PATTERN_DIFF/.test(text)) {
326
- return new Error('当前 profile 的 node_modules 由不同主版本的 pnpm 生成,安装前需要先重建依赖。');
327
- }
328
- return err instanceof Error ? err : new Error(text);
329
- }
330
317
  /**
331
318
  * 失败摘要:命令失败时从完整输出里提取可诊断的行,而不是盲取末尾。
332
319
  * 2026-09-05 实证(dsh-better-sidebar 安装失败):pnpm ndjson 错误行 ~1.2KB,
@@ -618,22 +605,6 @@ export function makeAddViaLadder(deps) {
618
605
  function errText(err) {
619
606
  return err instanceof Error ? err.message : String(err);
620
607
  }
621
- /**
622
- * 安装。返回 usedAllowAllBuilds 供 UI 明确报告「该插件执行了构建脚本」。
623
- * source 形如:`pkg@1.2.3`(npm 精确锁定)或 `github:owner/repo#sha`(锁 SHA)。
624
- * 薄包装:调 makeAddViaLadder,非 ok 时 throw(对 legacy 调用方保持现行报错形状)。
625
- */
626
- export async function addDshPlugin(source, deps = {}) {
627
- const ladder = makeAddViaLadder({
628
- runDshPlugin: deps.runDshPlugin ?? runDshPlugin,
629
- allowAllBuilds: deps.allowAllBuilds,
630
- });
631
- const outcome = await ladder(source, deps.profileDir ?? webProfileDir());
632
- if (outcome.class === 'ok') {
633
- return { output: outcome.output, usedAllowAllBuilds: outcome.usedAllowAllBuilds === true };
634
- }
635
- throw rewritePnpmError(new Error(outcome.output));
636
- }
637
608
  /** 卸载(转发 pnpm remove;调用方须先做 live-disable)。 */
638
609
  export async function removeDshPlugin(pkg, deps = {}) {
639
610
  if (!isSafePluginTarget(pkg))
@@ -1,10 +1,16 @@
1
1
  /**
2
2
  * 已装插件识别(DESIGN.md §3):profile 的 package.json 是唯一事实源,
3
3
  * 不引入额外状态文件。移植自 skillhub installed-plugins.ts(去 README 暂缓)。
4
+ *
5
+ * 完整性契约(2026-09-09):枚举结果必含 `complete`——只有当顶层 manifest 与每个
6
+ * 依赖的 package.json 都可读、可解析为非数组对象、且每项都能归类(DSH 插件 → items /
7
+ * 确认非 DSH → others)时才为 true;任一项「无法判断」即 complete:false(partial 结果
8
+ * 保留,该依赖不计入 others)。当前 complete 仅被 listMarket → dshm_search 消费;
9
+ * 已装列表(listInstalledWithMeta)/ dshm_list / CLI list·outdated / 升级路径的
10
+ * incomplete 展示与处理为后续独立任务。
4
11
  */
5
12
  import { open, readFile } from 'node:fs/promises';
6
13
  import { join, resolve } from 'node:path';
7
- import { isSafePluginTarget, removeDshPlugin } from './dsh-cli.js';
8
14
  import { webProfileDir } from './env.js';
9
15
  const PKG_NAME_RE = /^(@[A-Za-z0-9-*~][A-Za-z0-9-*._~]*\/)?[A-Za-z0-9-._~]+$/;
10
16
  export function isSafePkgName(raw) {
@@ -58,30 +64,74 @@ export function githubRepoFromRepository(raw) {
58
64
  const m = /github\.com[/:]([A-Za-z0-9._-]+)\/([A-Za-z0-9._-]+?)(?:\.git)?$/i.exec(url.trim());
59
65
  return m ? `${m[1]}/${m[2]}` : null;
60
66
  }
61
- export async function readPkgJson(dir) {
67
+ /** JSON 合法根:非 null、非数组的对象(typeof [] === 'object',数组必须显式排除)。 */
68
+ function isRecord(value) {
69
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
70
+ }
71
+ /** 单包 package.json 唯一读取实现:读不到 / 坏 JSON / 根非对象一律 ok:false。 */
72
+ async function readPkgJsonResult(dir) {
73
+ let raw;
62
74
  try {
63
- const raw = JSON.parse(await readFile(join(dir, 'package.json'), 'utf8'));
64
- return raw && typeof raw === 'object' ? raw : null;
75
+ raw = JSON.parse(await readFile(join(dir, 'package.json'), 'utf8'));
65
76
  }
66
77
  catch {
67
- return null;
78
+ return { ok: false };
68
79
  }
80
+ if (!isRecord(raw))
81
+ return { ok: false };
82
+ return { ok: true, value: raw };
69
83
  }
70
- export async function readProfileDeps(profileDir) {
84
+ export async function readPkgJson(dir) {
85
+ const result = await readPkgJsonResult(dir);
86
+ return result.ok ? result.value ?? null : null;
87
+ }
88
+ /** 空 deps 统一无原型容器:与逐项解析产物保持同一原型语义,继承属性不得伪装成依赖成员。 */
89
+ function emptyDeps() {
90
+ return Object.create(null);
91
+ }
92
+ /**
93
+ * 顶层 profile package.json 唯一读取实现(完整性 + partial 语义):
94
+ * - 读不到 / 坏 JSON / 根非数组对象 → complete:false, deps:{}(manifest 缺失 ≠ 合法空 profile);
95
+ * - 合法但无 dependencies 字段,或 dependencies 为空对象 → complete:true, deps:{}(唯一合法空形态);
96
+ * - dependencies 存在但类型非法(null/数组/标量)→ complete:false, deps:{};
97
+ * - 逐项:key 不安全(isSafePkgName 拒绝 `__proto__`、`_`/`.` 开头分段等)或 spec 非字符串/纯空白
98
+ * → 该项计入 incomplete(complete:false)并跳过,其余合法项保留。
99
+ * 容器为无原型对象(Object.create(null))——第二层防御:特殊属性名不受 Object.prototype
100
+ * setter/继承语义影响,数据结构与早退路径的空容器保持一致。
101
+ */
102
+ async function readProfileDepsResult(profileDir) {
103
+ let raw;
71
104
  try {
72
- const raw = JSON.parse(await readFile(join(profileDir, 'package.json'), 'utf8'));
73
- if (!raw || typeof raw !== 'object' || !raw.dependencies || typeof raw.dependencies !== 'object')
74
- return {};
75
- const out = {};
76
- for (const [name, spec] of Object.entries(raw.dependencies)) {
77
- if (typeof spec === 'string' && spec !== '')
78
- out[name] = spec;
79
- }
80
- return out;
105
+ raw = JSON.parse(await readFile(join(profileDir, 'package.json'), 'utf8'));
81
106
  }
82
107
  catch {
83
- return {};
108
+ return { complete: false, deps: emptyDeps() };
109
+ }
110
+ if (!isRecord(raw))
111
+ return { complete: false, deps: emptyDeps() };
112
+ if (!Object.prototype.hasOwnProperty.call(raw, 'dependencies'))
113
+ return { complete: true, deps: emptyDeps() };
114
+ const dependencies = raw.dependencies;
115
+ if (!isRecord(dependencies))
116
+ return { complete: false, deps: emptyDeps() };
117
+ let complete = true;
118
+ const deps = emptyDeps();
119
+ for (const [name, spec] of Object.entries(dependencies)) {
120
+ if (!isSafePkgName(name)) {
121
+ complete = false;
122
+ continue;
123
+ }
124
+ if (typeof spec !== 'string' || spec.trim() === '') {
125
+ complete = false;
126
+ continue;
127
+ }
128
+ deps[name] = spec;
84
129
  }
130
+ return { complete, deps };
131
+ }
132
+ /** 宽松读取(README 路径):只返回可确认的依赖项,个别非法项被跳过而不是整体丢弃。 */
133
+ export async function readProfileDeps(profileDir) {
134
+ return (await readProfileDepsResult(profileDir)).deps;
85
135
  }
86
136
  function sanitizePkgJson(raw, fallbackName) {
87
137
  return {
@@ -93,17 +143,31 @@ function sanitizePkgJson(raw, fallbackName) {
93
143
  githubRepo: githubRepoFromRepository(raw.repository),
94
144
  };
95
145
  }
96
- /** 枚举 web profile 已安装插件(只读)。 */
146
+ /** 枚举 web profile 已安装插件(只读)。complete:false 时 items/others 为 partial 结果(已确认部分保留)。 */
97
147
  export async function listInstalledPlugins(profileDir = webProfileDir()) {
98
148
  const root = resolve(profileDir);
99
- const deps = await readProfileDeps(root);
149
+ const result = await readProfileDepsResult(root);
150
+ let complete = result.complete;
151
+ const deps = result.deps;
100
152
  const items = [];
101
153
  let others = 0;
102
154
  for (const pkg of Object.keys(deps).sort()) {
103
155
  const spec = deps[pkg];
104
156
  const dir = resolvePluginDir(root, pkg, spec);
105
- const raw = dir ? await readPkgJson(dir) : null;
106
- if (!raw || !('dsh' in raw)) {
157
+ if (!dir) {
158
+ // 依赖键不安全、目录无法解析 → 无法判断(不冒充非 DSH)
159
+ complete = false;
160
+ continue;
161
+ }
162
+ const rawResult = await readPkgJsonResult(dir);
163
+ if (!rawResult.ok) {
164
+ // package.json 缺失/不可读/坏 JSON/根非对象 → 无法判断
165
+ complete = false;
166
+ continue;
167
+ }
168
+ const raw = rawResult.value;
169
+ if (!('dsh' in raw)) {
170
+ // 确认非 DSH 依赖
107
171
  others += 1;
108
172
  continue;
109
173
  }
@@ -121,23 +185,7 @@ export async function listInstalledPlugins(profileDir = webProfileDir()) {
121
185
  githubRepo: githubRepoFromRepository(raw.repository),
122
186
  });
123
187
  }
124
- return { items, others, profileDir: root };
125
- }
126
- /** 从 web profile 卸载已安装的 dsh 插件。pkg 必须来自 profile 依赖(先 live-disable,见 market.ts)。 */
127
- export async function removeInstalledPlugin(pkg, profileDir = webProfileDir(), deps = {}) {
128
- const key = String(pkg || '').trim();
129
- if (!isSafePkgName(key) || !isSafePluginTarget(key))
130
- throw new Error(`无效插件包名: ${pkg}`);
131
- const root = resolve(profileDir);
132
- const listed = await readProfileDeps(root);
133
- if (!(key in listed))
134
- throw new Error(`web profile 未安装该插件: ${key}`);
135
- const dir = resolvePluginDir(root, key, listed[key]);
136
- const raw = dir ? await readPkgJson(dir) : null;
137
- if (!raw || !('dsh' in raw))
138
- throw new Error(`不是 dsh 插件: ${key}`);
139
- await removeDshPlugin(key, deps);
140
- return { pkg: key };
188
+ return { items, others, complete, profileDir: root };
141
189
  }
142
190
  // ---------- README 预览(借鉴 skillhub,64KB 截断) ----------
143
191
  const README_MAX_BYTES = 64 * 1024;
@@ -170,8 +218,11 @@ export async function readInstalledPluginReadme(pkg, profileDir = webProfileDir(
170
218
  throw new Error(`无效插件包名: ${pkg}`);
171
219
  const root = resolve(profileDir);
172
220
  const deps = await readProfileDeps(root);
173
- if (!(key in deps))
221
+ // 授权边界必须用 own-property 判定:`in` 会沿原型链命中继承属性(如 'constructor'),
222
+ // 绕过「pkg 必须来自 profile dependencies」的成员约束
223
+ if (!Object.hasOwn(deps, key)) {
174
224
  throw new Error(`web profile 未安装该插件: ${key}`);
225
+ }
175
226
  const dir = resolvePluginDir(root, key, deps[key]);
176
227
  if (!dir)
177
228
  throw new Error(`无法解析插件目录: ${key}`);
@@ -227,7 +227,8 @@ export async function listMarket(cfg = {}, opts = {}, deps = {}) {
227
227
  };
228
228
  }
229
229
  const installed = await installedTask;
230
- const installedComplete = installed !== null;
230
+ // 完整性两层来源:枚举 throw → null;枚举 partial resolve → complete:false(installed.ts 完整性契约)
231
+ const installedComplete = installed !== null && installed.complete === true;
231
232
  const installedItems = installed?.items ?? [];
232
233
  // 全量统计 + query/category 过滤 + 分页(同步,极轻)
233
234
  const all = loaded.registry.plugins;
package/lib/tools.js CHANGED
@@ -15,18 +15,6 @@ function cloneJson(value) {
15
15
  function summaryOf(state) {
16
16
  return { isDefault: state.isDefault, status: state.status, stale: state.stale };
17
17
  }
18
- function matchInstalledByEntry(entry, installed) {
19
- return installed.find((it) => {
20
- if (entry.npm && (it.pkg === entry.npm || it.name === entry.npm))
21
- return true;
22
- if (entry.github && it.source === 'github') {
23
- const m = /^github:([^#]+)/.exec(it.spec);
24
- if (m && m[1] === entry.github)
25
- return true;
26
- }
27
- return false;
28
- });
29
- }
30
18
  export function registerTools(ctx, cfg, deps = {}) {
31
19
  const timeoutMs = cfg.timeoutMs ?? 20_000;
32
20
  const m = {
@@ -77,18 +65,18 @@ export function registerTools(ctx, cfg, deps = {}) {
77
65
  withLatest: false,
78
66
  namespace: 'host',
79
67
  });
80
- const installed = await import('./core/installed.js');
81
- const inst = await installed.listInstalledPlugins();
82
- const merged = result.items.map((e) => {
83
- const i = matchInstalledByEntry(e, inst.items);
84
- return { ...e, installed: Boolean(i), installedPkg: i?.pkg, installedVersion: i?.version };
85
- });
68
+ // 安装标注唯一来源:listMarket 的单次 profile 快照。
69
+ // 状态不完整且存在可被误标的条目时 fail-closed——不把未知安装状态呈现成未安装;
70
+ // 空结果(registry 不可用/超时/无匹配)无可误标条目,维持优雅空结果。
71
+ if (!result.installedComplete && result.items.length > 0) {
72
+ throw new Error('读取 web profile 安装状态失败,安装标注不可用;请稍后重试');
73
+ }
86
74
  return cloneJson({
87
75
  query: String(args.query || ''),
88
76
  category,
89
77
  total: result.total,
90
78
  registry: summaryOf(result.registryState),
91
- items: merged.map((e) => ({
79
+ items: result.items.map((e) => ({
92
80
  id: e.id,
93
81
  name: e.name,
94
82
  description: e.description,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-m",
3
- "version": "0.2.9",
3
+ "version": "0.2.10",
4
4
  "description": "DSH Marketplace — 个人自用的 DeepSeek Harness 插件市场:收录、安装、卸载、升级 DSH 插件",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -45,6 +45,7 @@
45
45
  "build": "node scripts/build.mjs",
46
46
  "typecheck": "tsc -p tsconfig.json --noEmit",
47
47
  "test": "node --test tests/*.test.mjs",
48
+ "pretest": "npm run build",
48
49
  "prepare": "npm run build"
49
50
  },
50
51
  "dsh": {