@max-null/dsh-plugin-center 0.1.1 → 0.1.3

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/client.js CHANGED
@@ -203,9 +203,143 @@ async function checkWhatNew() {
203
203
  } catch {
204
204
  }
205
205
  }
206
- var SOURCE_LABEL = { official: "\u5B98\u65B9", installed: "\u7528\u6237\u5B89\u88C5", local: "\u672C\u5730\u5F00\u53D1", builtin: "\u5185\u7F6E" };
207
206
  var CATEGORIES = ["ui", "usage", "theme", "model", "session", "memory", "tools", "vision", "skill", "workflow", "notify", "dev", "market", "fun"];
207
+ var STRINGS = {
208
+ zh: {
209
+ title: "\u63D2\u4EF6\u4E2D\u5FC3",
210
+ tabInstalled: "\u5DF2\u5B89\u88C5",
211
+ tabMarket: "\u5E02\u573A",
212
+ tabUpdates: "\u66F4\u65B0",
213
+ headSummary: "\u5DF2\u5B89\u88C5 {a} \xB7 \u6709\u66F4\u65B0 {b} \xB7 \u5931\u6548 {c}",
214
+ check: "\u68C0\u67E5\u66F4\u65B0",
215
+ checking: "\u68C0\u67E5\u4E2D\u2026",
216
+ updateAll: "\u66F4\u65B0\u5168\u90E8\uFF08{n}\uFF09",
217
+ searchInstalled: "\u641C\u7D22\u5DF2\u5B89\u88C5\u63D2\u4EF6",
218
+ allSources: "\u5168\u90E8\u6765\u6E90",
219
+ srcOfficial: "\u5B98\u65B9",
220
+ srcInstalled: "\u7528\u6237\u5B89\u88C5",
221
+ srcLocal: "\u672C\u5730\u5F00\u53D1",
222
+ srcBuiltin: "\u5185\u7F6E",
223
+ all: "\u5168\u90E8",
224
+ searchMarket: "\u641C\u7D22\u793E\u533A\u63D2\u4EF6",
225
+ allMarkets: "\u5168\u90E8\u6E90",
226
+ gridDouble: "\u53CC\u5217\u7F51\u683C",
227
+ gridSingle: "\u5355\u5217\u5217\u8868",
228
+ loadFailed: "\u52A0\u8F7D\u5931\u8D25\uFF1A{e}",
229
+ loading: "\u52A0\u8F7D\u4E2D\u2026",
230
+ marketLoading: "\u52A0\u8F7D\u5E02\u573A\u76EE\u5F55\u4E2D\u2026",
231
+ loadMore: "\u6B63\u5728\u52A0\u8F7D\u66F4\u591A\u2026\uFF08\u5DF2\u52A0\u8F7D {n} \u4E2A\uFF09",
232
+ checkingUpdates: "\u68C0\u67E5\u66F4\u65B0\u4E2D\u2026",
233
+ noUpdates: "\u6CA1\u6709\u53EF\u7528\u7684\u66F4\u65B0\u3002",
234
+ recheck: "\u91CD\u65B0\u68C0\u67E5",
235
+ incompat: "\u4E0D\u517C\u5BB9\u5F53\u524D DSH",
236
+ update: "\u66F4\u65B0",
237
+ updating: "\u66F4\u65B0\u4E2D\u2026",
238
+ install: "\u5B89\u88C5",
239
+ installing: "\u5B89\u88C5\u4E2D\u2026",
240
+ pendingRestart: "\u5F85\u91CD\u542F\u751F\u6548",
241
+ installedTag: "\u5DF2\u5B89\u88C5",
242
+ disabledTag: "\u5DF2\u7981\u7528",
243
+ requiresDsh: "\u8981\u6C42 DSH {r}",
244
+ installQueued: "\u5DF2\u53D1\u8D77\u5B89\u88C5 {n}\uFF0C\u91CD\u542F dsh web \u540E\u751F\u6548",
245
+ installFailed: "\u5B89\u88C5\u5931\u8D25\uFF1A{e}",
246
+ installNotApplied: "\u5B89\u88C5\u672A\u751F\u6548",
247
+ updatedOne: "\u5DF2\u66F4\u65B0 {n}\uFF0C\u91CD\u542F dsh web \u540E\u751F\u6548",
248
+ updateFailed: "\u66F4\u65B0\u5931\u8D25\uFF1A{e}",
249
+ updateNotApplied: "\u66F4\u65B0\u672A\u751F\u6548",
250
+ updatedMany: "\u5DF2\u66F4\u65B0 {n} \u4E2A\u63D2\u4EF6\uFF0C\u91CD\u542F dsh web \u540E\u751F\u6548",
251
+ updateSummary: "\u66F4\u65B0\u5B8C\u6210\uFF1A\u6210\u529F {a}\uFF0C\u5931\u8D25 {b}\uFF08{c}\uFF09",
252
+ whatsNewTitle: "\u63D2\u4EF6\u66F4\u65B0",
253
+ whatsNewSub: "{n} \u4E2A\u63D2\u4EF6\u6709\u65B0\u7248\u672C",
254
+ later: "\u7A0D\u540E",
255
+ markAllRead: "\u5168\u90E8\u6807\u8BB0\u5DF2\u8BFB",
256
+ updateNow: "\u7ACB\u5373\u66F4\u65B0",
257
+ close: "\u5173\u95ED",
258
+ checkFail: "\u68C0\u67E5\u66F4\u65B0\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5",
259
+ foundUpdates: "\u53D1\u73B0 {n} \u4E2A\u53EF\u66F4\u65B0\u63D2\u4EF6",
260
+ allUpToDate: "\u6240\u6709\u63D2\u4EF6\u5747\u4E3A\u6700\u65B0"
261
+ },
262
+ en: {
263
+ title: "Plugin Center",
264
+ tabInstalled: "Installed",
265
+ tabMarket: "Market",
266
+ tabUpdates: "Updates",
267
+ headSummary: "{a} installed \xB7 {b} updates \xB7 {c} failed",
268
+ check: "Check updates",
269
+ checking: "Checking\u2026",
270
+ updateAll: "Update all\uFF08{n}\uFF09",
271
+ searchInstalled: "Search installed plugins",
272
+ allSources: "All sources",
273
+ srcOfficial: "Official",
274
+ srcInstalled: "User installed",
275
+ srcLocal: "Local dev",
276
+ srcBuiltin: "Built-in",
277
+ all: "All",
278
+ searchMarket: "Search community plugins",
279
+ allMarkets: "All sources",
280
+ gridDouble: "Two-column grid",
281
+ gridSingle: "Single-column list",
282
+ loadFailed: "Failed to load: {e}",
283
+ loading: "Loading\u2026",
284
+ marketLoading: "Loading market catalog\u2026",
285
+ loadMore: "Loading more\u2026 ({n} loaded)",
286
+ checkingUpdates: "Checking for updates\u2026",
287
+ noUpdates: "No updates available.",
288
+ recheck: "Check again",
289
+ incompat: "Incompatible with current DSH",
290
+ update: "Update",
291
+ updating: "Updating\u2026",
292
+ install: "Install",
293
+ installing: "Installing\u2026",
294
+ pendingRestart: "Restart pending",
295
+ installedTag: "Installed",
296
+ disabledTag: "Disabled",
297
+ requiresDsh: "Requires DSH {r}",
298
+ installQueued: "Install of {n} started; restart dsh web to take effect",
299
+ installFailed: "Install failed: {e}",
300
+ installNotApplied: "Install did not take effect",
301
+ updatedOne: "Updated {n}; restart dsh web to take effect",
302
+ updateFailed: "Update failed: {e}",
303
+ updateNotApplied: "Update did not take effect",
304
+ updatedMany: "Updated {n} plugins; restart dsh web to take effect",
305
+ updateSummary: "Update done: {a} succeeded, {b} failed ({c})",
306
+ whatsNewTitle: "Plugin updates",
307
+ whatsNewSub: "{n} plugins have new versions",
308
+ later: "Later",
309
+ markAllRead: "Mark all read",
310
+ updateNow: "Update now",
311
+ close: "Close",
312
+ checkFail: "Failed to check updates, please retry later",
313
+ foundUpdates: "{n} updates found",
314
+ allUpToDate: "All plugins are up to date"
315
+ }
316
+ };
317
+ var localeId = "zh";
318
+ var localeListeners = /* @__PURE__ */ new Set();
319
+ function adoptLocale(id) {
320
+ const next = id === "en" ? "en" : "zh";
321
+ if (next === localeId) return;
322
+ localeId = next;
323
+ localeListeners.forEach((l) => l());
324
+ }
325
+ function fmt(tpl, vars = {}) {
326
+ return tpl.replace(/\{(\w+)\}/g, (_, k) => String(vars[k] ?? ""));
327
+ }
328
+ function useT() {
329
+ const [id, setId] = (0, import_react.useState)(localeId);
330
+ (0, import_react.useEffect)(() => {
331
+ const l = () => {
332
+ setId(localeId);
333
+ };
334
+ localeListeners.add(l);
335
+ return () => {
336
+ localeListeners.delete(l);
337
+ };
338
+ }, []);
339
+ return (key, vars) => fmt(STRINGS[id][key] ?? STRINGS.zh[key], vars);
340
+ }
208
341
  function InstalledView({ search, category, source }) {
342
+ const t = useT();
209
343
  const [items, setItems] = (0, import_react.useState)(installedCache);
210
344
  const [error, setError] = (0, import_react.useState)(null);
211
345
  (0, import_react.useEffect)(() => {
@@ -224,11 +358,14 @@ function InstalledView({ search, category, source }) {
224
358
  alive = false;
225
359
  };
226
360
  }, []);
227
- if (error !== null) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "pc-sub", children: [
228
- "\u52A0\u8F7D\u5931\u8D25\uFF1A",
229
- error
230
- ] });
231
- if (items === null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: "\u52A0\u8F7D\u4E2D\u2026" });
361
+ if (error !== null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("loadFailed", { e: error }) });
362
+ if (items === null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("loading") });
363
+ const srcLabel = {
364
+ official: t("srcOfficial"),
365
+ installed: t("srcInstalled"),
366
+ local: t("srcLocal"),
367
+ builtin: t("srcBuiltin")
368
+ };
232
369
  const q = search.trim().toLowerCase();
233
370
  const filtered = items.filter((p) => {
234
371
  const matchSearch = q === "" || p.displayName.toLowerCase().includes(q) || (p.description ?? "").toLowerCase().includes(q) || p.name.toLowerCase().includes(q);
@@ -243,22 +380,20 @@ function InstalledView({ search, category, source }) {
243
380
  "v",
244
381
  p.version
245
382
  ] }),
246
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `pc-badge ${p.source}`, children: SOURCE_LABEL[p.source] }),
383
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `pc-badge ${p.source}`, children: srcLabel[p.source] }),
247
384
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-spacer" }),
248
385
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `pc-dot${p.fiberPhase === "failed" ? " failed" : ""}` })
249
386
  ] }),
250
387
  p.description !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-desc", children: p.description }),
251
388
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-meta", children: [
252
389
  p.categories.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag", children: c }, c)),
253
- p.compatRange !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "pc-tag", children: [
254
- "\u8981\u6C42 DSH ",
255
- p.compatRange
256
- ] }),
257
- !p.enabled && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag", children: "\u5DF2\u7981\u7528" })
390
+ p.compatRange !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag", children: t("requiresDsh", { r: p.compatRange }) }),
391
+ !p.enabled && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag", children: t("disabledTag") })
258
392
  ] })
259
393
  ] }, p.entryId)) });
260
394
  }
261
395
  function MarketView({ category, single, source, search, onCount }) {
396
+ const t = useT();
262
397
  const [items, setItems] = (0, import_react.useState)(marketCache[source]?.plugins ?? []);
263
398
  const [done, setDone] = (0, import_react.useState)(marketCache[source]?.done ?? false);
264
399
  const [error, setError] = (0, import_react.useState)(null);
@@ -296,20 +431,19 @@ function MarketView({ category, single, source, search, onCount }) {
296
431
  if (timer !== void 0) clearTimeout(timer);
297
432
  };
298
433
  }, [source, onCount]);
299
- if (error !== null) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "pc-sub", children: [
300
- "\u52A0\u8F7D\u5931\u8D25\uFF1A",
301
- error
302
- ] });
303
- if (items.length === 0 && !done) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: "\u52A0\u8F7D\u5E02\u573A\u76EE\u5F55\u4E2D\u2026" });
434
+ if (error !== null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("loadFailed", { e: error }) });
435
+ if (items.length === 0 && !done) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("marketLoading") });
304
436
  const q = search.trim().toLowerCase();
305
437
  const filtered = items.filter((m) => {
306
438
  const matchSearch = q === "" || m.name.toLowerCase().includes(q) || (m.description.zh || m.description.en).toLowerCase().includes(q);
307
439
  return matchSearch && (category === null || m.categories.includes(category));
308
440
  });
441
+ const descOf = (m) => localeId === "en" ? m.description.en || m.description.zh : m.description.zh || m.description.en;
309
442
  const install = (m) => {
310
443
  setBusy(m.name);
311
444
  void rpc("install", { spec: m.spec }).then(
312
- () => {
445
+ (v) => {
446
+ if (v !== true) throw new Error(t("installNotApplied"));
313
447
  setBusy(null);
314
448
  pendingInstall.add(m.spec);
315
449
  for (const key of Object.keys(marketCache)) {
@@ -317,11 +451,11 @@ function MarketView({ category, single, source, search, onCount }) {
317
451
  if (c !== void 0) c.plugins = c.plugins.map((p) => p.name === m.name ? { ...p, installed: true } : p);
318
452
  }
319
453
  setItems((prev) => prev.map((p) => p.name === m.name ? { ...p, installed: true } : p));
320
- showToast(`\u5DF2\u53D1\u8D77\u5B89\u88C5 ${m.name}\uFF0C\u91CD\u542F dsh web \u540E\u751F\u6548`);
454
+ showToast(t("installQueued", { n: m.name }));
321
455
  },
322
456
  (e) => {
323
457
  setBusy(null);
324
- showToast(`\u5B89\u88C5\u5931\u8D25\uFF1A${e instanceof Error ? e.message : String(e)}`, "error");
458
+ showToast(t("installFailed", { e: e instanceof Error ? e.message : String(e) }), "error");
325
459
  }
326
460
  );
327
461
  };
@@ -338,27 +472,24 @@ function MarketView({ category, single, source, search, onCount }) {
338
472
  m.version
339
473
  ] })
340
474
  ] }),
341
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-desc", children: m.description.zh || m.description.en }),
475
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-desc", children: descOf(m) }),
342
476
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-meta", children: [
343
477
  m.categories.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag", children: c }, c)),
344
478
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-spacer" }),
345
- m.installed || pendingInstall.has(m.spec) ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag", children: pendingInstall.has(m.spec) ? "\u5F85\u91CD\u542F\u751F\u6548" : "\u5DF2\u5B89\u88C5" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn primary", disabled: busy !== null, onClick: () => {
479
+ m.installed || pendingInstall.has(m.spec) ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag", children: pendingInstall.has(m.spec) ? t("pendingRestart") : t("installedTag") }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn primary", disabled: busy !== null, onClick: () => {
346
480
  install(m);
347
- }, children: busy === m.name ? "\u5B89\u88C5\u4E2D\u2026" : "\u5B89\u88C5" })
481
+ }, children: busy === m.name ? t("installing") : t("install") })
348
482
  ] })
349
483
  ] }, m.name)) }),
350
- !done && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "pc-sub", children: [
351
- "\u6B63\u5728\u52A0\u8F7D\u66F4\u591A\u2026\uFF08\u5DF2\u52A0\u8F7D ",
352
- items.length,
353
- " \u4E2A\uFF09"
354
- ] })
484
+ !done && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("loadMore", { n: items.length }) })
355
485
  ] });
356
486
  }
357
487
  function UpdatesView({ updates, refresh, updateOne, busy }) {
358
- if (updates === null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: "\u68C0\u67E5\u66F4\u65B0\u4E2D\u2026" });
488
+ const t = useT();
489
+ if (updates === null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("checkingUpdates") });
359
490
  if (updates.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
360
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: "\u6CA1\u6709\u53EF\u7528\u7684\u66F4\u65B0\u3002" }),
361
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn", onClick: refresh, children: "\u91CD\u65B0\u68C0\u67E5" })
491
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("noUpdates") }),
492
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn", onClick: refresh, children: t("recheck") })
362
493
  ] });
363
494
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: updates.map((u) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-card", children: [
364
495
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-row", children: [
@@ -366,16 +497,17 @@ function UpdatesView({ updates, refresh, updateOne, busy }) {
366
497
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-ver", children: u.fromVersion }),
367
498
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-ver", children: "\u2192" }),
368
499
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "var(--dsw-alias-state-business-primary)", fontWeight: 500 }, children: u.toVersion }),
369
- u.compat === "incompatible" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag danger", children: "\u4E0D\u517C\u5BB9\u5F53\u524D DSH" }),
500
+ u.compat === "incompatible" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag danger", children: t("incompat") }),
370
501
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-spacer" }),
371
502
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn primary", disabled: busy !== null, onClick: () => {
372
503
  updateOne(u.name);
373
- }, children: busy === u.name || busy === "__all__" ? "\u66F4\u65B0\u4E2D\u2026" : "\u66F4\u65B0" })
504
+ }, children: busy === u.name || busy === "__all__" ? t("updating") : t("update") })
374
505
  ] }),
375
506
  u.changelog.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "pc-wn-list", children: u.changelog.slice(0, 5).map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: line }, i)) })
376
507
  ] }, u.name)) });
377
508
  }
378
509
  function CenterPanel({ variant = "section" }) {
510
+ const t = useT();
379
511
  const [view, setView] = (0, import_react.useState)("installed");
380
512
  const [category, setCategory] = (0, import_react.useState)(null);
381
513
  const [single, setSingle] = (0, import_react.useState)(false);
@@ -398,15 +530,15 @@ function CenterPanel({ variant = "section" }) {
398
530
  setUpdates(digests);
399
531
  setChecking(false);
400
532
  if (!silent) {
401
- showToast(digests.length > 0 ? `\u53D1\u73B0 ${digests.length} \u4E2A\u53EF\u66F4\u65B0\u63D2\u4EF6` : "\u6240\u6709\u63D2\u4EF6\u5747\u4E3A\u6700\u65B0");
533
+ showToast(digests.length > 0 ? t("foundUpdates", { n: digests.length }) : t("allUpToDate"));
402
534
  }
403
535
  },
404
536
  () => {
405
537
  setChecking(false);
406
- if (!silent) showToast("\u68C0\u67E5\u66F4\u65B0\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5", "error");
538
+ if (!silent) showToast(t("checkFail"), "error");
407
539
  }
408
540
  );
409
- }, []);
541
+ }, [t]);
410
542
  (0, import_react.useEffect)(() => {
411
543
  void rpc("listInstalled").then(
412
544
  (v) => {
@@ -447,13 +579,14 @@ function CenterPanel({ variant = "section" }) {
447
579
  const updateOne = (name) => {
448
580
  setBusyUpdate(name);
449
581
  void rpc("update", { name }).then(
450
- () => {
582
+ (v) => {
583
+ if (v !== true) throw new Error(t("updateNotApplied"));
451
584
  setBusyUpdate(null);
452
- showToast(`\u5DF2\u66F4\u65B0 ${name}\uFF0C\u91CD\u542F dsh web \u540E\u751F\u6548`);
585
+ showToast(t("updatedOne", { n: name }));
453
586
  },
454
587
  (e) => {
455
588
  setBusyUpdate(null);
456
- showToast(`\u66F4\u65B0\u5931\u8D25\uFF1A${e instanceof Error ? e.message : String(e)}`, "error");
589
+ showToast(t("updateFailed", { e: e instanceof Error ? e.message : String(e) }), "error");
457
590
  }
458
591
  );
459
592
  };
@@ -464,7 +597,8 @@ function CenterPanel({ variant = "section" }) {
464
597
  const failures = [];
465
598
  for (const u of updates) {
466
599
  try {
467
- await rpc("update", { name: u.name });
600
+ const v = await rpc("update", { name: u.name });
601
+ if (v !== true) throw new Error(t("updateNotApplied"));
468
602
  okCount++;
469
603
  } catch (e) {
470
604
  failures.push(`${u.name}\uFF1A${e instanceof Error ? e.message : String(e)}`);
@@ -472,9 +606,9 @@ function CenterPanel({ variant = "section" }) {
472
606
  }
473
607
  setBusyUpdate(null);
474
608
  if (failures.length === 0) {
475
- showToast(`\u5DF2\u66F4\u65B0 ${okCount} \u4E2A\u63D2\u4EF6\uFF0C\u91CD\u542F dsh web \u540E\u751F\u6548`);
609
+ showToast(t("updatedMany", { n: okCount }));
476
610
  } else {
477
- showToast(`\u66F4\u65B0\u5B8C\u6210\uFF1A\u6210\u529F ${okCount}\uFF0C\u5931\u8D25 ${failures.length}\uFF08${failures.join("\uFF1B")}\uFF09`, "error");
611
+ showToast(t("updateSummary", { a: okCount, b: failures.length, c: failures.join("\uFF1B") }), "error");
478
612
  }
479
613
  refreshUpdates();
480
614
  };
@@ -485,48 +619,37 @@ function CenterPanel({ variant = "section" }) {
485
619
  count !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-count", children: count })
486
620
  ] }, v);
487
621
  const tabs = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-tabs", children: [
488
- tab("installed", "\u5DF2\u5B89\u88C5", installedCount),
489
- tab("market", "\u5E02\u573A", marketCount),
490
- tab("updates", "\u66F4\u65B0", updates?.length ?? 0)
622
+ tab("installed", t("tabInstalled"), installedCount),
623
+ tab("market", t("tabMarket"), marketCount),
624
+ tab("updates", t("tabUpdates"), updates?.length ?? 0)
491
625
  ] });
492
626
  const head = (showTitle) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-head", children: [
493
- showTitle && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-title", children: "\u63D2\u4EF6\u4E2D\u5FC3" }),
494
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "pc-sub", children: [
495
- "\u5DF2\u5B89\u88C5 ",
496
- installedCount,
497
- " \xB7 \u6709\u66F4\u65B0 ",
498
- updates?.length ?? 0,
499
- " \xB7 \u5931\u6548 ",
500
- failedCount
501
- ] }),
627
+ showTitle && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-title", children: t("title") }),
628
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-sub", children: t("headSummary", { a: installedCount, b: updates?.length ?? 0, c: failedCount }) }),
502
629
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-spacer" }),
503
630
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn", disabled: checking, onClick: () => {
504
631
  refreshUpdates();
505
- }, children: checking ? "\u68C0\u67E5\u4E2D\u2026" : "\u68C0\u67E5\u66F4\u65B0" }),
506
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { className: "pc-btn primary", disabled: !updates?.length || busyUpdate !== null, onClick: () => {
632
+ }, children: checking ? t("checking") : t("check") }),
633
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn primary", disabled: !updates?.length || busyUpdate !== null, onClick: () => {
507
634
  void updateAll();
508
- }, children: [
509
- "\u66F4\u65B0\u5168\u90E8\uFF08",
510
- updates?.length ?? 0,
511
- "\uFF09"
512
- ] })
635
+ }, children: t("updateAll", { n: updates?.length ?? 0 }) })
513
636
  ] });
514
637
  const installedToolbar = view === "installed" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-toolbar", children: [
515
638
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { className: "pc-search", value: search, onChange: (e) => {
516
639
  setSearch(e.target.value);
517
- }, placeholder: "\u641C\u7D22\u5DF2\u5B89\u88C5\u63D2\u4EF6" }),
640
+ }, placeholder: t("searchInstalled") }),
518
641
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { className: "pc-select", value: installedSource ?? "", onChange: (e) => {
519
642
  setInstalledSource(e.target.value === "" ? null : e.target.value);
520
643
  }, children: [
521
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: "\u5168\u90E8\u6765\u6E90" }),
522
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "official", children: "\u5B98\u65B9" }),
523
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "installed", children: "\u7528\u6237\u5B89\u88C5" }),
524
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "local", children: "\u672C\u5730\u5F00\u53D1" }),
525
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "builtin", children: "\u5185\u7F6E" })
644
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: t("allSources") }),
645
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "official", children: t("srcOfficial") }),
646
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "installed", children: t("srcInstalled") }),
647
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "local", children: t("srcLocal") }),
648
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "builtin", children: t("srcBuiltin") })
526
649
  ] }),
527
650
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: `pc-chip${installedCategory === null ? " active" : ""}`, onClick: () => {
528
651
  setInstalledCategory(null);
529
- }, children: "\u5168\u90E8" }),
652
+ }, children: t("all") }),
530
653
  CATEGORIES.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: `pc-chip${installedCategory === c ? " active" : ""}`, onClick: () => {
531
654
  setInstalledCategory(c);
532
655
  }, children: c }, c))
@@ -534,23 +657,23 @@ function CenterPanel({ variant = "section" }) {
534
657
  const marketToolbar = view === "market" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-toolbar", children: [
535
658
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { className: "pc-search", value: marketSearch, onChange: (e) => {
536
659
  setMarketSearch(e.target.value);
537
- }, placeholder: "\u641C\u7D22\u793E\u533A\u63D2\u4EF6" }),
660
+ }, placeholder: t("searchMarket") }),
538
661
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { className: "pc-select", value: source, onChange: (e) => {
539
662
  setSource(e.target.value);
540
663
  setCategory(null);
541
664
  }, children: [
542
665
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "awesome", children: "awesome-dsh-plugin" }),
543
666
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "oh-my-dsh", children: "Oh-My-DSH" }),
544
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "all", children: "\u5168\u90E8\u6E90" })
667
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "all", children: t("allMarkets") })
545
668
  ] }),
546
669
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: `pc-chip${category === null ? " active" : ""}`, onClick: () => {
547
670
  setCategory(null);
548
- }, children: "\u5168\u90E8" }),
671
+ }, children: t("all") }),
549
672
  CATEGORIES.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: `pc-chip${category === c ? " active" : ""}`, onClick: () => {
550
673
  setCategory(c);
551
674
  }, children: c }, c)),
552
675
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-spacer" }),
553
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", title: single ? "\u53CC\u5217\u7F51\u683C" : "\u5355\u5217\u5217\u8868", "aria-label": "\u5207\u6362\u5E03\u5C40", className: "pc-iconbtn", onClick: () => {
676
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", title: single ? t("gridDouble") : t("gridSingle"), "aria-label": single ? t("gridDouble") : t("gridSingle"), className: "pc-iconbtn", onClick: () => {
554
677
  setSingle((v) => !v);
555
678
  }, children: single ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [
556
679
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "2", y: "2", width: "5", height: "5", rx: "1" }),
@@ -589,7 +712,8 @@ function CenterPanel({ variant = "section" }) {
589
712
  ] });
590
713
  }
591
714
  function HeaderButton() {
592
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", title: "\u63D2\u4EF6\u4E2D\u5FC3", "aria-label": "\u63D2\u4EF6\u4E2D\u5FC3", className: "pc-headerbtn", onClick: openOverlay, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [
715
+ const t = useT();
716
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", title: t("title"), "aria-label": t("title"), className: "pc-headerbtn", onClick: openOverlay, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [
593
717
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "2", y: "2", width: "5", height: "5", rx: "1" }),
594
718
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "9", y: "2", width: "5", height: "5", rx: "1" }),
595
719
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "2", y: "9", width: "5", height: "5", rx: "1" }),
@@ -597,13 +721,14 @@ function HeaderButton() {
597
721
  ] }) });
598
722
  }
599
723
  function OverlayPanel() {
724
+ const t = useT();
600
725
  const open = useOverlayOpen();
601
726
  if (!open) return null;
602
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-overlay", role: "presentation", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-panel", role: "dialog", "aria-modal": "true", "aria-label": "\u63D2\u4EF6\u4E2D\u5FC3", children: [
727
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-overlay", role: "presentation", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-panel", role: "dialog", "aria-modal": "true", "aria-label": t("title"), children: [
603
728
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-panel-head", children: [
604
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-title", children: "\u63D2\u4EF6\u4E2D\u5FC3" }),
729
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-title", children: t("title") }),
605
730
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-spacer" }),
606
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "pc-close", onClick: closeOverlay, "aria-label": "\u5173\u95ED", children: "\u2715" })
731
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "pc-close", onClick: closeOverlay, "aria-label": t("close"), children: "\u2715" })
607
732
  ] }),
608
733
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-panel-body", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CenterPanel, { variant: "overlay" }) })
609
734
  ] }) });
@@ -614,6 +739,7 @@ function Toast() {
614
739
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: `pc-toast ${t.kind}`, children: t.message });
615
740
  }
616
741
  function WhatsNewDialog() {
742
+ const t = useT();
617
743
  const open = useWhatsNewOpen();
618
744
  const [busy, setBusy] = (0, import_react.useState)(false);
619
745
  if (!open || whatsNewDigests.length === 0) return null;
@@ -623,7 +749,8 @@ function WhatsNewDialog() {
623
749
  const failures = [];
624
750
  for (const u of whatsNewDigests) {
625
751
  try {
626
- await rpc("update", { name: u.name });
752
+ const v = await rpc("update", { name: u.name });
753
+ if (v !== true) throw new Error(t("updateNotApplied"));
627
754
  okCount++;
628
755
  } catch (e) {
629
756
  failures.push(`${u.name}\uFF1A${e instanceof Error ? e.message : String(e)}`);
@@ -631,21 +758,18 @@ function WhatsNewDialog() {
631
758
  }
632
759
  setBusy(false);
633
760
  if (failures.length === 0) {
634
- showToast(`\u5DF2\u66F4\u65B0 ${okCount} \u4E2A\u63D2\u4EF6\uFF0C\u91CD\u542F dsh web \u540E\u751F\u6548`);
761
+ showToast(t("updatedMany", { n: okCount }));
635
762
  closeWhatsNew();
636
763
  } else {
637
- showToast(`\u66F4\u65B0\u5B8C\u6210\uFF1A\u6210\u529F ${okCount}\uFF0C\u5931\u8D25 ${failures.length}\uFF08${failures.join("\uFF1B")}\uFF09`, "error");
764
+ showToast(t("updateSummary", { a: okCount, b: failures.length, c: failures.join("\uFF1B") }), "error");
638
765
  }
639
766
  };
640
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-overlay", role: "presentation", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-panel", style: { width: "540px" }, role: "dialog", "aria-modal": "true", "aria-label": "\u63D2\u4EF6\u66F4\u65B0", children: [
767
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-overlay", role: "presentation", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-panel", style: { width: "540px" }, role: "dialog", "aria-modal": "true", "aria-label": t("whatsNewTitle"), children: [
641
768
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-panel-head", children: [
642
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-title", children: "\u63D2\u4EF6\u66F4\u65B0" }),
643
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "pc-sub", style: { marginTop: 0 }, children: [
644
- whatsNewDigests.length,
645
- " \u4E2A\u63D2\u4EF6\u6709\u65B0\u7248\u672C"
646
- ] }),
769
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-title", children: t("whatsNewTitle") }),
770
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-sub", style: { marginTop: 0 }, children: t("whatsNewSub", { n: whatsNewDigests.length }) }),
647
771
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-spacer" }),
648
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "pc-close", onClick: closeWhatsNew, "aria-label": "\u5173\u95ED", children: "\u2715" })
772
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "pc-close", onClick: closeWhatsNew, "aria-label": t("close"), children: "\u2715" })
649
773
  ] }),
650
774
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-panel-body", style: { overflow: "auto" }, children: [
651
775
  whatsNewDigests.map((u) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-wn-item", children: [
@@ -658,11 +782,11 @@ function WhatsNewDialog() {
658
782
  u.changelog.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "pc-wn-list", children: u.changelog.slice(0, 5).map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: line }, i)) })
659
783
  ] }, u.name)),
660
784
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", justifyContent: "flex-end", gap: "8px", marginTop: "16px" }, children: [
661
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn", onClick: closeWhatsNew, children: "\u7A0D\u540E" }),
662
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn", onClick: closeWhatsNew, children: "\u5168\u90E8\u6807\u8BB0\u5DF2\u8BFB" }),
785
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn", onClick: closeWhatsNew, children: t("later") }),
786
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn", onClick: closeWhatsNew, children: t("markAllRead") }),
663
787
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn primary", disabled: busy, onClick: () => {
664
788
  void updateNow();
665
- }, children: busy ? "\u66F4\u65B0\u4E2D\u2026" : "\u7ACB\u5373\u66F4\u65B0" })
789
+ }, children: busy ? t("updating") : t("updateNow") })
666
790
  ] })
667
791
  ] })
668
792
  ] }) });
@@ -675,11 +799,17 @@ function apply(ctx) {
675
799
  if (result.ok) return result.value;
676
800
  throw new Error(result.error?.message ?? `plugin-center: ${endpoint} failed`);
677
801
  };
802
+ const locale = ctx.get?.("locale");
803
+ const initial = locale?.getLocale?.()?.active;
804
+ if (typeof initial === "string") adoptLocale(initial);
805
+ ctx.on?.("locale/change", (snap) => {
806
+ adoptLocale(snap?.active);
807
+ });
678
808
  ctx.slots.inject("settings.section", () => ctx.slots.register({
679
809
  name: "settings.section",
680
810
  id: "plugin-center",
681
811
  order: 50,
682
- label: () => "\u63D2\u4EF6\u4E2D\u5FC3"
812
+ label: () => STRINGS[localeId].title
683
813
  }, CenterPanel));
684
814
  ctx.slots.inject("conversation.session.header.utilities", () => ctx.slots.register({
685
815
  name: "conversation.session.header.utilities",
package/dist/engine.d.ts CHANGED
@@ -65,8 +65,14 @@ export declare class PluginCenterEngine extends Service {
65
65
  listMarket(source?: MarketSource): Promise<MarketSnapshot>;
66
66
  /** Detect updates for every installed third-party/local plugin, TTL-cached. */
67
67
  checkUpdates(sinceIso: string): Promise<UpdateDigest[]>;
68
- install(spec: string): Promise<boolean>;
69
- update(name: string): Promise<boolean>;
68
+ install(spec: string): Promise<{
69
+ ok: boolean;
70
+ detail: string;
71
+ }>;
72
+ update(name: string): Promise<{
73
+ ok: boolean;
74
+ detail: string;
75
+ }>;
70
76
  /** 串行执行一次 pnpm 操作并失效缓存(无论成败都放行链条后续任务)。 */
71
77
  private enqueuePnpm;
72
78
  /** Temporary diagnostics for the empty-update bug; removed once root-caused. */
package/dist/engine.js CHANGED
@@ -231,10 +231,10 @@ export class PluginCenterEngine extends Service {
231
231
  /** 串行执行一次 pnpm 操作并失效缓存(无论成败都放行链条后续任务)。 */
232
232
  enqueuePnpm(op) {
233
233
  const run = this.pnpmChain.then(async () => {
234
- const ok = await op();
234
+ const result = await op();
235
235
  this.installedNamesCache = null;
236
236
  this.updatesCache = null;
237
- return ok;
237
+ return result;
238
238
  });
239
239
  this.pnpmChain = run.catch(() => { });
240
240
  return run;
package/dist/rpc.js CHANGED
@@ -28,13 +28,19 @@ export class PluginCenterRpc extends Service {
28
28
  const spec = payload?.spec;
29
29
  if (typeof spec !== 'string' || spec === '')
30
30
  return internal('install: spec is required');
31
- return { ok: true, value: await ctx.pluginCenter.install(spec) };
31
+ const result = await ctx.pluginCenter.install(spec);
32
+ if (!result.ok)
33
+ return internal(`install ${spec} 失败:${result.detail}`);
34
+ return { ok: true, value: true };
32
35
  }
33
36
  case 'update': {
34
37
  const name = payload?.name;
35
38
  if (typeof name !== 'string' || name === '')
36
39
  return internal('update: name is required');
37
- return { ok: true, value: await ctx.pluginCenter.update(name) };
40
+ const result = await ctx.pluginCenter.update(name);
41
+ if (!result.ok)
42
+ return internal(`update ${name} 失败:${result.detail}`);
43
+ return { ok: true, value: true };
38
44
  }
39
45
  case 'debug':
40
46
  return { ok: true, value: await ctx.pluginCenter.debug() };
package/dist/update.d.ts CHANGED
@@ -17,14 +17,22 @@ export declare function fetchCommitChangelog(repoUrl: string | null, sinceIso: s
17
17
  * DSH version via the plugin's peer-dependency range.
18
18
  */
19
19
  export declare function detectUpdate(name: string, localVersion: string, repoUrl: string | null, compatRange: string | null, localDshVersion: string, sinceIso: string): Promise<UpdateDigest | null>;
20
+ /** One pnpm invocation result: success plus a failure detail for diagnostics. */
21
+ export interface PnpmResult {
22
+ ok: boolean;
23
+ detail: string;
24
+ }
25
+ /** pnpm 候选命令:GUI 进程 PATH 常缺用户级 npm 全局目录;且 Windows 上
26
+ * CreateProcess 只找 pnpm.exe(.cmd/.ps1 必须经 shell)——2026-08-17 实测
27
+ * spawn('pnpm', shell:false) 直接 ENOENT,更新永远假成功。 */
28
+ export declare function pnpmCandidates(): string[];
20
29
  /**
21
- * Run pnpm in the profile directory. Output inherits the process stdio (no
22
- * pipe capture the host sandbox forbids named-pipe stdio); the exit code is
23
- * the only result this layer needs.
24
- * @returns the child exit code, or 1 when spawn itself fails.
30
+ * Run pnpm in the profile directory, trying each candidate command in turn.
31
+ * Output inherits the process stdio (no pipe capture the host sandbox
32
+ * forbids named-pipe stdio); the exit code is the only result this layer needs.
25
33
  */
26
- export declare function runPnpm(args: readonly string[], cwd: string): Promise<number>;
34
+ export declare function runPnpm(args: readonly string[], cwd: string): Promise<PnpmResult>;
27
35
  /** Install a package into the web profile, mirroring `dsh plugin add` semantics. */
28
- export declare function installPlugin(packageSpec: string, profileDir: string): Promise<boolean>;
36
+ export declare function installPlugin(packageSpec: string, profileDir: string): Promise<PnpmResult>;
29
37
  /** Update a package to latest, mirroring `dsh plugin add <pkg>` (pnpm installs latest). */
30
- export declare function updatePlugin(packageName: string, profileDir: string): Promise<boolean>;
38
+ export declare function updatePlugin(packageName: string, profileDir: string): Promise<PnpmResult>;
package/dist/update.js CHANGED
@@ -4,6 +4,9 @@
4
4
  * first (many community repos ship no release/tag/CHANGELOG — verified §7.2).
5
5
  */
6
6
  import { spawn } from 'node:child_process';
7
+ import { existsSync } from 'node:fs';
8
+ import { homedir } from 'node:os';
9
+ import { join } from 'node:path';
7
10
  import { compareVersions, satisfies } from "./semver.js";
8
11
  const UA = { 'User-Agent': 'dsh-plugin-center' };
9
12
  /** Latest published version on the npm registry; null when unreachable/unpublished. */
@@ -67,34 +70,54 @@ export async function detectUpdate(name, localVersion, repoUrl, compatRange, loc
67
70
  compatRange,
68
71
  };
69
72
  }
70
- /**
71
- * Run pnpm in the profile directory. Output inherits the process stdio (no
72
- * pipe capture — the host sandbox forbids named-pipe stdio); the exit code is
73
- * the only result this layer needs.
74
- * @returns the child exit code, or 1 when spawn itself fails.
75
- */
76
- export function runPnpm(args, cwd) {
73
+ /** pnpm 候选命令:GUI 进程 PATH 常缺用户级 npm 全局目录;且 Windows 上
74
+ * CreateProcess 只找 pnpm.exe(.cmd/.ps1 必须经 shell)——2026-08-17 实测
75
+ * spawn('pnpm', shell:false) 直接 ENOENT,更新永远假成功。 */
76
+ export function pnpmCandidates() {
77
+ const commands = ['pnpm', 'pnpm.cmd'];
78
+ const userNpm = join(homedir(), 'AppData', 'Roaming', 'npm');
79
+ for (const name of ['pnpm.cmd', 'pnpm.exe', 'pnpm']) {
80
+ const candidate = join(userNpm, name);
81
+ if (existsSync(candidate))
82
+ commands.push(candidate);
83
+ }
84
+ return commands;
85
+ }
86
+ function runOne(command, args, cwd) {
77
87
  return new Promise((resolve) => {
78
88
  let child;
79
89
  try {
80
- child = spawn('pnpm', [...args], { cwd, stdio: 'inherit', shell: false });
90
+ // shell: true —— Windows .cmd shim 必须经 shell 才能 spawn。
91
+ child = spawn(command, [...args], { cwd, stdio: 'inherit', shell: true });
81
92
  }
82
- catch {
83
- resolve(1);
93
+ catch (e) {
94
+ resolve({ ok: false, detail: e instanceof Error ? e.message : String(e) });
84
95
  return;
85
96
  }
86
- child.on('error', () => resolve(1));
87
- child.on('close', code => resolve(code ?? 1));
97
+ child.on('error', e => resolve({ ok: false, detail: e.message }));
98
+ child.on('close', code => resolve(code === 0 ? { ok: true, detail: '' } : { ok: false, detail: `exit code ${code ?? 1}` }));
88
99
  });
89
100
  }
101
+ /**
102
+ * Run pnpm in the profile directory, trying each candidate command in turn.
103
+ * Output inherits the process stdio (no pipe capture — the host sandbox
104
+ * forbids named-pipe stdio); the exit code is the only result this layer needs.
105
+ */
106
+ export async function runPnpm(args, cwd) {
107
+ let last = { ok: false, detail: 'no pnpm candidate found' };
108
+ for (const command of pnpmCandidates()) {
109
+ last = await runOne(command, args, cwd);
110
+ if (last.ok)
111
+ return last;
112
+ }
113
+ return last;
114
+ }
90
115
  /** Install a package into the web profile, mirroring `dsh plugin add` semantics. */
91
116
  export async function installPlugin(packageSpec, profileDir) {
92
117
  // `-w` is required: every profile ships a pnpm-workspace.yaml.
93
- const code = await runPnpm(['add', '-w', packageSpec], profileDir);
94
- return code === 0;
118
+ return runPnpm(['add', '-w', packageSpec], profileDir);
95
119
  }
96
120
  /** Update a package to latest, mirroring `dsh plugin add <pkg>` (pnpm installs latest). */
97
121
  export async function updatePlugin(packageName, profileDir) {
98
- const code = await runPnpm(['add', '-w', packageName], profileDir);
99
- return code === 0;
122
+ return runPnpm(['add', '-w', packageName], profileDir);
100
123
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@max-null/dsh-plugin-center",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Plugin center for DeepSeek Harness — installed metadata, community market, update detection, and What's New",
5
5
  "license": "MIT",
6
6
  "publishConfig": {