dsh-plugin 1.2.1 → 1.3.1

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.
Files changed (96) hide show
  1. package/README.en.md +20 -15
  2. package/README.md +21 -16
  3. package/client/client.js +716 -445
  4. package/client/client.js.map +1 -1
  5. package/cordis.patch.yml +1 -1
  6. package/lib/http/routes.js +47 -3
  7. package/lib/index.js +1 -1
  8. package/lib/services/install/install-types.js +1 -1
  9. package/lib/services/install/npm-check.js +1 -1
  10. package/lib/services/install/npm-resolve.js +1 -1
  11. package/lib/services/install/preflight.js +1 -1
  12. package/lib/services/install/task-queue.js +1 -1
  13. package/lib/services/loader.js +1 -1
  14. package/lib/services/log.js +1 -1
  15. package/lib/services/probe.js +1 -1
  16. package/lib/services/profile/installed-versions.js +1 -1
  17. package/lib/services/profile/profile.js +6 -5
  18. package/lib/services/profile/progress.js +1 -1
  19. package/lib/services/settings.js +1 -1
  20. package/lib/types/index.d.ts +1 -1
  21. package/lib/types/services/install/install-types.d.ts +1 -1
  22. package/lib/types/services/install/install.d.ts +1 -1
  23. package/lib/types/services/profile/pending-restart.d.ts +1 -1
  24. package/lib/types/services/profile/progress.d.ts +1 -1
  25. package/package.json +2 -2
  26. package/src/client/components/PluginHubSection.tsx +98 -17
  27. package/src/client/components/catalog/CatalogList.tsx +1 -1
  28. package/src/client/components/catalog/PluginCard.tsx +1 -1
  29. package/src/client/components/layout/CatalogControls.tsx +1 -1
  30. package/src/client/components/layout/CatalogHeader.tsx +2 -2
  31. package/src/client/components/layout/CategoryTabs.tsx +1 -1
  32. package/src/client/components/layout/SectionTabs.tsx +1 -1
  33. package/src/client/components/modals/AboutModal.tsx +2 -2
  34. package/src/client/components/modals/HubUpdateModal.tsx +1 -1
  35. package/src/client/components/modals/InstalledDetailModal.tsx +1 -1
  36. package/src/client/components/modals/LogsModal.tsx +1 -1
  37. package/src/client/components/modals/LogsPathModal.tsx +1 -1
  38. package/src/client/components/modals/NotificationsModal.tsx +31 -14
  39. package/src/client/components/modals/ProgressView.tsx +1 -1
  40. package/src/client/components/modals/modals.tsx +1 -1
  41. package/src/client/components/ui/Dropdown.tsx +1 -1
  42. package/src/client/components/ui/Toggle.tsx +1 -1
  43. package/src/client/components/ui/icons.tsx +22 -1
  44. package/src/client/components/views/CustomInstallView.tsx +133 -44
  45. package/src/client/components/views/DiagnosticsView.tsx +2 -2
  46. package/src/client/components/views/InstalledView.tsx +1 -1
  47. package/src/client/components/views/LogsView.tsx +1 -1
  48. package/src/client/components/views/MarketView.tsx +1 -1
  49. package/src/client/components/views/SettingsView.tsx +1 -1
  50. package/src/client/data/catalog.ts +1 -1
  51. package/src/client/data/host.ts +1 -1
  52. package/src/client/data/hub.ts +12 -3
  53. package/src/client/hooks/useCatalog.ts +19 -6
  54. package/src/client/hooks/useIncrementalList.ts +1 -1
  55. package/src/client/hooks/useLanguage.ts +1 -1
  56. package/src/client/hooks/useSettings.ts +1 -1
  57. package/src/client/hooks/useTaskQueue.ts +1 -1
  58. package/src/client/index.tsx +1 -1
  59. package/src/client/locales.ts +50 -30
  60. package/src/client/logic/constants.ts +2 -2
  61. package/src/client/logic/failures.ts +48 -2
  62. package/src/client/logic/format.ts +1 -1
  63. package/src/client/logic/install-command.ts +1 -1
  64. package/src/client/logic/installed.ts +41 -2
  65. package/src/client/logic/normalize.ts +1 -1
  66. package/src/client/logic/renderMarkdown.ts +1 -1
  67. package/src/client/logic/urls.ts +2 -2
  68. package/src/client/styles/CustomInstallView.module.css +59 -1
  69. package/src/client/styles/DiagnosticsView.module.css +1 -1
  70. package/src/client/styles/Dropdown.module.css +1 -1
  71. package/src/client/styles/Header.module.css +1 -1
  72. package/src/client/styles/InstalledView.module.css +19 -6
  73. package/src/client/styles/List.module.css +11 -1
  74. package/src/client/styles/LogsView.module.css +1 -1
  75. package/src/client/styles/Modal.module.css +32 -1
  76. package/src/client/styles/SectionTabs.module.css +1 -1
  77. package/src/client/styles/SettingsView.module.css +1 -1
  78. package/src/client/styles/Toggle.module.css +1 -1
  79. package/src/client/styles/tokens.module.css +32 -5
  80. package/src/client/types.ts +1 -1
  81. package/src/server/http/routes.ts +54 -3
  82. package/src/server/index.ts +1 -1
  83. package/src/server/services/install/install-types.ts +1 -1
  84. package/src/server/services/install/install.ts +1 -1
  85. package/src/server/services/install/npm-check.ts +1 -1
  86. package/src/server/services/install/npm-resolve.ts +1 -1
  87. package/src/server/services/install/preflight.ts +1 -1
  88. package/src/server/services/install/task-queue.ts +1 -1
  89. package/src/server/services/loader.ts +1 -1
  90. package/src/server/services/log.ts +1 -1
  91. package/src/server/services/probe.ts +1 -1
  92. package/src/server/services/profile/installed-versions.ts +1 -1
  93. package/src/server/services/profile/pending-restart.ts +1 -1
  94. package/src/server/services/profile/profile.ts +6 -5
  95. package/src/server/services/profile/progress.ts +1 -1
  96. package/src/server/services/settings.ts +1 -1
package/cordis.patch.yml CHANGED
@@ -1,4 +1,4 @@
1
- # dsh bundle patch: inserts the DSH-Plugin section into a profile's layer stack.
1
+ # dsh bundle patch: inserts the DSH Plugin section into a profile's layer stack.
2
2
  - insert:
3
3
  - id: dsh-plugin
4
4
  name: 'dsh-plugin'
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -8,7 +8,7 @@
8
8
  * install handler validates the target, then spawns the official dsh CLI
9
9
  * (see services/install/install.ts) and reports the captured result back.
10
10
  */
11
- import { mkdirSync, readFileSync } from 'node:fs';
11
+ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
12
12
  import { homedir, release } from 'node:os';
13
13
  import { dirname, join, resolve } from 'node:path';
14
14
  import { spawn } from 'node:child_process';
@@ -23,6 +23,36 @@ import { appendLog, readLog, logFilePath, defaultLogFilePath, customLogFile } fr
23
23
  const PROFILE_RE = /^[A-Za-z0-9_-]+$/;
24
24
  const BODY_LIMIT_BYTES = 4 * 1024;
25
25
  const COMMAND_TIMEOUT_MS = 5 * 60 * 1000;
26
+ /** 目录/统计数据代理的本地缓存时长:1 小时内重复打开插件中心直接读盘,
27
+ * 不重复走 curl 拉远程 —— 重启宿主后首次打开同样秒开(缓存跨重启存活)。 */
28
+ const CATALOG_CACHE_TTL_MS = 60 * 60 * 1000;
29
+ function catalogCacheFile(profile, key) {
30
+ return join(profileDirectory(profile), 'cache', `catalog-${key}.json`);
31
+ }
32
+ /** 读缓存:文件存在、JSON 合法、未过期 → 返回原始响应字符串;否则 null(视为 miss)。 */
33
+ function readCatalogCache(file) {
34
+ try {
35
+ const entry = JSON.parse(readFileSync(file, 'utf8'));
36
+ if (entry && typeof entry.at === 'number' && typeof entry.body === 'string'
37
+ && Date.now() - entry.at < CATALOG_CACHE_TTL_MS) {
38
+ return entry.body;
39
+ }
40
+ }
41
+ catch {
42
+ // 文件缺失 / 解析失败 / 字段不完整 → miss,重新拉取覆盖写盘
43
+ }
44
+ return null;
45
+ }
46
+ /** 写缓存:目录不存在则创建;写失败静默忽略(缓存只是加速,不影响功能)。 */
47
+ function writeCatalogCache(file, body) {
48
+ try {
49
+ mkdirSync(dirname(file), { recursive: true });
50
+ writeFileSync(file, JSON.stringify({ at: Date.now(), body }));
51
+ }
52
+ catch {
53
+ // 磁盘只读/无权限等极端情况:忽略,继续走直连
54
+ }
55
+ }
26
56
  function profileDirectory(profile) {
27
57
  return join(process.env.DSH_HOME ?? join(homedir(), '.dsh'), 'profiles', profile);
28
58
  }
@@ -439,9 +469,22 @@ export function mountPluginHubRoutes(webServer, profile, loader) {
439
469
  ? settings.proxy
440
470
  : (systemProxy() ?? process.env.HTTPS_PROXY ?? process.env.https_proxy ?? '');
441
471
  const isStats = url.searchParams.get('stats') === '1';
472
+ const lang = url.searchParams.get('lang') === 'en' ? 'en' : 'zh';
442
473
  const target = isStats
443
474
  ? 'https://api.dsh-plugin.org/stats.json'
444
- : `https://api.dsh-plugin.org/plugins.${url.searchParams.get('lang') === 'en' ? 'en' : 'zh'}.json`;
475
+ : `https://api.dsh-plugin.org/plugins.${lang}.json`;
476
+ // 1 小时本地缓存:命中直接返回(跨重启生效,插件中心秒开),未命中才走 curl 拉远程
477
+ const cacheFile = catalogCacheFile(profile, isStats ? 'stats' : `plugins-${lang}`);
478
+ const cached = readCatalogCache(cacheFile);
479
+ if (cached !== null) {
480
+ try {
481
+ sendJson(response, 200, JSON.parse(cached));
482
+ return;
483
+ }
484
+ catch {
485
+ // 缓存正文损坏:忽略,走重新拉取并覆盖写盘
486
+ }
487
+ }
445
488
  const r = await fetchViaCurl(target, proxy, 20000);
446
489
  if (!r.ok || r.body === '') {
447
490
  sendJson(response, 502, { error: 'catalog fetch failed' });
@@ -449,6 +492,7 @@ export function mountPluginHubRoutes(webServer, profile, loader) {
449
492
  }
450
493
  try {
451
494
  sendJson(response, 200, JSON.parse(r.body));
495
+ writeCatalogCache(cacheFile, r.body);
452
496
  }
453
497
  catch {
454
498
  sendJson(response, 502, { error: 'catalog fetch returned invalid JSON' });
package/lib/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -27,11 +27,12 @@ export function githubTarget(repo) {
27
27
  return `git+https://github.com/${repo}.git`;
28
28
  }
29
29
  /**
30
- * DSH 命令行前缀(`dsh plugin [--profile|-p <p>] add <target>`)—— 提取其中的安装目标;
31
- * 非命令输入原样返回。兼容「直接粘贴完整命令」(防用户输错):--profile 段可选,
32
- * 支持 `-p` 简写与 `--profile=web` 等号形式,命令大小写不敏感。
30
+ * DSH 命令行前缀(官方唯一形式 `dsh plugin --profile <name> add <target>`)—— 提取其中的安装目标;
31
+ * 非命令输入原样返回。兼容「直接粘贴完整命令」(防用户输错):--profile 必填且只有长选项
32
+ * (官方 CLI 无 -p 简写,省略即报错),支持 `--profile=<name>` 等号形式,命令大小写不敏感。
33
+ * 与客户端 CustomInstallView 口径一致。
33
34
  */
34
- const DSH_PLUGIN_CMD_RE = /^dsh\s+plugin\s+(?:(?:--profile|-p)(?:=|\s+)\S+\s*)?add\s+(.+)$/i;
35
+ const DSH_PLUGIN_CMD_RE = /^dsh\s+plugin\s+--profile(?:\s+|=)\S+\s+add\s+(.+)$/i;
35
36
  export function installTargetOf(value) {
36
37
  const input = typeof value === 'string' ? value.trim() : '';
37
38
  const match = DSH_PLUGIN_CMD_RE.exec(input);
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin",
3
- "version": "1.2.1",
3
+ "version": "1.3.1",
4
4
  "description": "A community plugin marketplace for DeepSeek Harness, built to the official plugin spec — browse, search and install 4000+ human-curated community plugins without leaving the app. · DeepSeek Harness 社区插件市场(遵循官方开发规范):4000+ 人工精选社区插件,每日更新。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -14,7 +14,7 @@
14
14
  "bugs": {
15
15
  "url": "https://github.com/dshplugin/dsh-plugin-hub/issues"
16
16
  },
17
- "author": "DSH-Plugin Hub contributors",
17
+ "author": "DSH Plugin Hub contributors",
18
18
  "funding": {
19
19
  "type": "custom",
20
20
  "url": "https://dsh-plugin.org"
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -9,7 +9,9 @@
9
9
  * folder; it hosts the dialogs/toast and the section-level copy actions.
10
10
  */
11
11
  import { createElement as h, useEffect, useRef, useState } from 'react'
12
+ import type { MouseEvent } from 'react'
12
13
  import styles from '../styles/Header.module.css'
14
+ import modalStyles from '../styles/Modal.module.css'
13
15
  import type { EnvInfo, HubPlugin, SectionProps, ToastState } from '../types.ts'
14
16
  import { installCommandOf, repoFromInstallTarget } from '../logic/install-command.ts'
15
17
  import { PLUGIN_VERSION } from '../logic/constants.ts'
@@ -23,7 +25,7 @@ import { AboutModal } from './modals/AboutModal.tsx'
23
25
  import { HubUpdateModal } from './modals/HubUpdateModal.tsx'
24
26
  import { InstalledDetailModal } from './modals/InstalledDetailModal.tsx'
25
27
  import { NotificationsModal } from './modals/NotificationsModal.tsx'
26
- import { addFailure, addSuccess, addUpdateNotice, clearNotifications, loadNotifications, removeNotification } from '../logic/failures.ts'
28
+ import { addFailure, addSuccess, addUpdateNotice, clearNotifications, ignoreUpdate, loadIgnoredUpdates, loadNotifications, removeNotification, removeUpdateNotice } from '../logic/failures.ts'
27
29
  import type { NotificationRecord } from '../logic/failures.ts'
28
30
  import { CatalogHeader } from './layout/CatalogHeader.tsx'
29
31
  import { MarketView } from './views/MarketView.tsx'
@@ -32,8 +34,9 @@ import type { SectionView } from './layout/SectionTabs.tsx'
32
34
  import { InstalledView } from './views/InstalledView.tsx'
33
35
  import { CustomInstallView } from './views/CustomInstallView.tsx'
34
36
  import { SettingsView } from './views/SettingsView.tsx'
37
+ import { CloseIcon } from './ui/icons.tsx'
35
38
  import type { InstalledItem } from '../logic/installed.ts'
36
- import { pluginOfItem } from '../logic/installed.ts'
39
+ import { issueRepoOf, pluginOfItem } from '../logic/installed.ts'
37
40
 
38
41
  export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
39
42
  const { lang, langKey, langPath, t, toggleLang } = useLanguage(locale)
@@ -86,6 +89,8 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
86
89
  const [notifications, setNotifications] = useState<NotificationRecord[]>(() => loadNotifications())
87
90
  /** 通知中心弹窗:一级导航「设置」tab 后边的铃铛入口按钮打开 */
88
91
  const [showNotifications, setShowNotifications] = useState(false)
92
+ /** 「忽略本次更新」确认弹窗:{ repo, version };null = 关闭 */
93
+ const [ignoreTarget, setIgnoreTarget] = useState<{ repo: string; version?: string } | null>(null)
89
94
  /** 宿主机器环境快照:提交 bug 的 issue 正文附带;取不到为 null(链接少环境段,不阻塞) */
90
95
  const [env, setEnv] = useState<EnvInfo | null>(null)
91
96
  useEffect(() => {
@@ -111,9 +116,12 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
111
116
  setNotifications(addSuccess({ kind: 'uninstall', repo: repo ?? '' }))
112
117
  },
113
118
  onError: (message, repo, kind, command, attempts) => {
114
- setErrorMsg({ message, repo, kind, command, attempts })
119
+ // 提 Issue 目标先反查仓库身份:npm 包名能在已安装表/目录里反查到 owner/repo 才保留,
120
+ // 反查不到置 null —— 错误弹窗/通知中心据此不显示「一键提 Issue」按钮(防提到错误仓库)
121
+ const issueRepo = issueRepoOf(repo ?? '', catalog.installedItems, catalog.plugins)
122
+ setErrorMsg({ message, repo: issueRepo, kind, command, attempts })
115
123
  // 失败自动写入通知记录:任务在后台结束时没人盯着也能留痕
116
- setNotifications(addFailure({ kind, repo: repo ?? '', message, command, attempts }))
124
+ setNotifications(addFailure({ kind, repo: issueRepo ?? '', message, command, attempts }))
117
125
  },
118
126
  installPlugin: confirmPlugin,
119
127
  installCustomTarget: confirmCustomTarget,
@@ -128,6 +136,28 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
128
136
  },
129
137
  })
130
138
 
139
+ /** 补齐更新提醒:把「当前有更新但通知中心还没有」的插件补写进通知(repo+version 去重)。
140
+ * 返回本次实际新增的条数(0 = 无可更新或全部已提示过)。启动检查与打开通知中心共用,
141
+ * 保证通知中心始终反映可更新状态:同一版本只一条、清空/删除后重开恢复、更新完成后消失。 */
142
+ const syncUpdateNotices = (): number => {
143
+ const updatable = catalog.installedItems.filter((i) => i.hasUpdate && i.plugin)
144
+ if (updatable.length === 0) return 0
145
+ const known = new Set(loadIgnoredUpdates())
146
+ // 已忽略(repo+version 持久化)与通知中心已有的更新提醒都不再补写:
147
+ // 忽略本次版本后不再提醒,直到下一个新版本(version 变化)才重新提示
148
+ for (const r of loadNotifications()) {
149
+ if (r.kind === 'update') known.add(`${r.repo}@${r.version ?? ''}`)
150
+ }
151
+ const fresh = updatable.filter((item) =>
152
+ item.repo !== null && !known.has(`${item.repo}@${item.catalogVersion ?? ''}`))
153
+ if (fresh.length === 0) return 0
154
+ for (const item of fresh) {
155
+ if (item.repo) addUpdateNotice({ kind: 'update', repo: item.repo, version: item.catalogVersion ?? undefined })
156
+ }
157
+ setNotifications(loadNotifications())
158
+ return fresh.length
159
+ }
160
+
131
161
  // 启动更新策略:目录数据与设置都就绪后触发一次 —— 开启「启动时检查更新」→ 发现可更新插件
132
162
  // 即写入通知中心(每条一条,点击可去更新),toast 同步提示已入通知。
133
163
  // 绝不自动安装:是否更新完全由用户决定。
@@ -138,18 +168,13 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
138
168
  if (!settingsReady || catalog.plugins === null || catalog.failed) return
139
169
  startupCheckedRef.current = true
140
170
  if (!hubSettings.checkUpdatesOnStart) return
141
- const updatable = catalog.installedItems.filter((i) => i.hasUpdate && i.plugin)
142
- if (updatable.length === 0) return
143
- for (const item of updatable) {
144
- if (item.repo) addUpdateNotice({ kind: 'update', repo: item.repo, version: item.catalogVersion ?? undefined })
145
- }
146
- setNotifications(loadNotifications())
147
- setToast({ id: Date.now(), kind: 'updates', n: updatable.length })
171
+ const added = syncUpdateNotices()
172
+ if (added > 0) setToast({ id: Date.now(), kind: 'updates', n: added })
148
173
  }, [settingsReady, hubSettings, catalog.plugins, catalog.failed, catalog.installedItems, queue])
149
174
 
150
175
  // 信任/卸载/更新说明弹窗打开时按 Esc 关闭;任务进入后台队列后可随时关闭(任务继续)
151
176
  useEffect(() => {
152
- if (!confirmPlugin && !confirmCustomTarget && !confirmGlobalNpm && !uninstallPlugin && !uninstallItem && !detailItem && !showHubUpdate && !showAbout && !showNotifications) return
177
+ if (!confirmPlugin && !confirmCustomTarget && !confirmGlobalNpm && !uninstallPlugin && !uninstallItem && !detailItem && !showHubUpdate && !showAbout && !showNotifications && !ignoreTarget) return
153
178
  const onKey = (e: KeyboardEvent) => {
154
179
  if (e.key === 'Escape') {
155
180
  setConfirmPlugin(null)
@@ -162,11 +187,12 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
162
187
  setShowHubUpdate(false)
163
188
  setShowAbout(false)
164
189
  setShowNotifications(false)
190
+ setIgnoreTarget(null)
165
191
  }
166
192
  }
167
193
  window.addEventListener('keydown', onKey)
168
194
  return () => window.removeEventListener('keydown', onKey)
169
- }, [confirmPlugin, confirmCustomTarget, uninstallPlugin, uninstallItem, detailItem, showHubUpdate, showAbout, showNotifications])
195
+ }, [confirmPlugin, confirmCustomTarget, uninstallPlugin, uninstallItem, detailItem, showHubUpdate, showAbout, showNotifications, ignoreTarget])
170
196
 
171
197
  // Toast 统一自动消失
172
198
  useEffect(() => {
@@ -214,7 +240,12 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
214
240
  (i.repo !== null && i.repo.toLowerCase() === needle) ||
215
241
  repoFromInstallTarget(i.spec).toLowerCase() === needle)
216
242
  }
217
- return catalog.installedItems.some((i) => i.name === target)
243
+ // npm 包名(可带 @version):剥掉版本号后同时查已安装列表与原始依赖表。
244
+ // Hub 自身(file:/link: 安装)被 installedItems 刻意排除(防误卸载),列表里查不到,
245
+ // 但依赖表真实存在——补齐后「命令窗口重装 hub 自身」也能转「更新」语义,不再撞 already installed
246
+ const pkg = repo.replace(/@[^@]*$/, '').toLowerCase()
247
+ return catalog.installedItems.some((i) => i.name.toLowerCase() === pkg) ||
248
+ Object.keys(catalog.installed).some((k) => k.toLowerCase() === pkg)
218
249
  }
219
250
 
220
251
  /** 弹窗动作一:复制安装命令到剪贴板,引导去终端粘贴执行(npm 通道显示包名命令)。 */
@@ -299,7 +330,10 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
299
330
  // 有可用更新时版本号后紧跟红色「可更新」徽标(同一入口),点击都打开同一个更新说明弹窗。
300
331
  // 有更新弹窗显示新版本 + Markdown 变更记录,点「直接更新」进安装弹窗覆盖重装。
301
332
  hubUpdate: catalog.hubHasUpdate,
302
- onVersionClick: () => setShowHubUpdate(true),
333
+ // 点击版本号/徽标:先强制刷新拉取最新版本信息(发版后点击即见新版本),再打开弹窗
334
+ onVersionClick: () => {
335
+ void catalog.refreshHub().then(() => setShowHubUpdate(true))
336
+ },
303
337
  onAboutClick: () => setShowAbout(true),
304
338
  }),
305
339
  // 一级导航行:tab 占满左侧,通知入口靠右(「设置」tab 后边,右对齐)
@@ -312,7 +346,12 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
312
346
  t,
313
347
  // 通知中心入口:红圈徽标 = 记录数 + 进行中任务 + 待重启(有动静才醒目)
314
348
  noticeCount: notifications.length + queue.queue.length + queue.pendingRestarts.length,
315
- onOpenNotifications: () => setShowNotifications(true),
349
+ onOpenNotifications: () => {
350
+ // 打开弹窗时补齐更新提醒(受「启动时检查更新」开关约束):同一版本只一条,
351
+ // 清空/删除后重开仍恢复,直到插件更新完成才消失
352
+ if (hubSettings.checkUpdatesOnStart) syncUpdateNotices()
353
+ setShowNotifications(true)
354
+ },
316
355
  }),
317
356
  ),
318
357
  view === 'market'
@@ -466,10 +505,52 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
466
505
  queue.clearModalTask()
467
506
  setConfirmPlugin(plugin)
468
507
  },
508
+ // 更新提醒「忽略本次更新」:先弹确认弹窗,确认后该版本不再提醒(持久化),直到下一个新版本
509
+ onIgnoreUpdate: (repo: string, version?: string) => setIgnoreTarget({ repo, version }),
469
510
  cancelTask: queue.cancelTask,
470
511
  restarting,
471
512
  onRestart: () => { void requestRestart() },
513
+ // 失败记录仓库反查:历史记录可能存的是裸 npm 包名,反查成 owner/repo 才显示仓库链接
514
+ // 与提 Issue 按钮;查不到则隐藏(防提到错误仓库)
515
+ resolveRepo: (repo: string) => issueRepoOf(repo, catalog.installedItems, catalog.plugins),
472
516
  }),
517
+ // 「忽略本次更新」确认弹窗:确认后该版本写入忽略集(持久化)并从通知中心移除,
518
+ // 本次版本不再提醒,直到插件发布下一个新版本
519
+ ignoreTarget && h('div', {
520
+ className: modalStyles.overlay,
521
+ onClick: (e: MouseEvent<HTMLDivElement>) => { if (e.target === e.currentTarget) setIgnoreTarget(null) },
522
+ },
523
+ h('div', { className: modalStyles.modal, role: 'dialog', 'aria-modal': 'true' },
524
+ h('div', { className: modalStyles.modalHead },
525
+ h('div', { className: modalStyles.modalTitle }, t('ignoreUpdateConfirmTitle')),
526
+ h('button', {
527
+ className: modalStyles.modalClose,
528
+ type: 'button',
529
+ 'aria-label': t('errorClose'),
530
+ onClick: () => setIgnoreTarget(null),
531
+ }, h(CloseIcon)),
532
+ ),
533
+ h('div', { className: modalStyles.modalBody },
534
+ h('div', { className: modalStyles.failPrepareHint }, t('ignoreUpdateConfirmDetail')),
535
+ h('div', { className: modalStyles.modalActions },
536
+ h('button', {
537
+ className: modalStyles.restartLater,
538
+ type: 'button',
539
+ onClick: () => setIgnoreTarget(null),
540
+ }, t('confirmCancel')),
541
+ h('button', {
542
+ className: modalStyles.restartNow,
543
+ type: 'button',
544
+ onClick: () => {
545
+ ignoreUpdate(ignoreTarget.repo, ignoreTarget.version)
546
+ setNotifications(removeUpdateNotice(ignoreTarget.repo, ignoreTarget.version))
547
+ setIgnoreTarget(null)
548
+ },
549
+ }, t('ignoreUpdateConfirm')),
550
+ ),
551
+ ),
552
+ ),
553
+ ),
473
554
  // 安装确认弹窗:锁定/进度/结果视图逻辑收敛在 modals.tsx;更新与安装同入口,仅标记 update
474
555
  confirmPlugin && h(InstallModal, {
475
556
  plugin: confirmPlugin,
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -40,7 +40,7 @@ export function CatalogHeader({ t, langPath, statsTotal, statsVerified, onToggle
40
40
  h(LogoIcon),
41
41
  h('h1', { className: styles.title }, t('title')),
42
42
  ),
43
- // 版本号:紧跟标题「DSH-Plugin Hub」之后,常驻可点入口(<a> 内不能嵌套按钮,故从官网链接里移出放在旁边)。
43
+ // 版本号:紧跟标题「DSH Plugin Hub」之后,常驻可点入口(<a> 内不能嵌套按钮,故从官网链接里移出放在旁边)。
44
44
  // 点击打开更新记录弹窗 —— 有更新看新版本内容,无更新看当前版本内容。
45
45
  // 有更新时版本号后直接跟一个红色「可更新」徽标(同一按钮内,不分开成两个元素),静态显示不闪烁。
46
46
  h('button', {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,9 +1,9 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
6
- * 头部「关注我们」弹窗:介绍 DSH-Plugin Hub 的功能定位 + 用户反馈群二维码。
6
+ * 头部「关注我们」弹窗:介绍 DSH Plugin Hub 的功能定位 + 用户反馈群二维码。
7
7
  * 内容以 Markdown 形式由 dsh-update Worker 的 /about 接口下发(key = hub:about),
8
8
  * 客户端按接口返回渲染,不内置固定文案。
9
9
  * 图片(反馈群二维码)经 renderMarkdown 的 ![](url) 语法嵌入,居中、最大高度受控。
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *