dsh-plugin 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/README.en.md +20 -15
  2. package/README.md +21 -16
  3. package/client/client.js +684 -446
  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 +83 -13
  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 +11 -2
  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 +8 -9
  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 +1 -1
  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
@@ -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
  *
@@ -53,6 +53,27 @@ export function LinkIcon() {
53
53
  }))
54
54
  }
55
55
 
56
+ /** 帮助图标:圆圈 + 问号(stroke 继承 currentColor),自定义安装卡片标题旁的「帮助」按钮用。 */
57
+ export function HelpIcon() {
58
+ return h('svg', {
59
+ viewBox: '0 0 16 16',
60
+ width: 13,
61
+ height: 13,
62
+ fill: 'none',
63
+ 'aria-hidden': 'true',
64
+ },
65
+ h('circle', {
66
+ cx: 8, cy: 8, r: 6.3,
67
+ stroke: 'currentColor', strokeWidth: 1.4,
68
+ }),
69
+ h('path', {
70
+ d: 'M6.3 6.5a1.9 1.9 0 1 1 2.7 1.7c-.7.3-1 .8-1 1.4',
71
+ stroke: 'currentColor', strokeWidth: 1.4, strokeLinecap: 'round',
72
+ }),
73
+ h('circle', { cx: 8, cy: 11.6, r: 0.95, fill: 'currentColor' }),
74
+ )
75
+ }
76
+
56
77
  /** 品牌 logo 图标:蓝紫渐变圆角方块 + 白色拼图块(2x2 错落),标题左侧的品牌标识。 */
57
78
  export function LogoIcon() {
58
79
  return h('svg', {
@@ -1,30 +1,32 @@
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
6
  * Custom install view: a standalone top-level section for manually
7
7
  * installing npm packages or GitHub sources outside the catalog. Three
8
- * installer cards, one per channel — npm package, GitHub source and a raw
9
- * install command (a `dsh plugin ... add <target>` or the official global
10
- * `npm install -g <pkgs>`) — each with a placeholder example above the
11
- * input and its own format check. The cards stay separate so a user never
12
- * has to guess which format goes where. Finished installs surface in the
13
- * Installed view, marked as custom.
8
+ * installer cards, one per channel — npm package (bare name or a full
9
+ * npm/pnpm install command, including the official global `npm install -g`),
10
+ * GitHub source and the official `dsh plugin ... add <target>` command —
11
+ * each with a placeholder example above the input and its own format check.
12
+ * The cards stay separate so a user never has to guess which format goes
13
+ * where. Finished installs surface in the Installed view, marked as custom.
14
14
  *
15
15
  * Pure presentational: owns only the three local input/error states; the
16
16
  * actual install (custom source, gated by the security toggles) bubbles up
17
17
  * via onInstallCustom.
18
18
  */
19
19
  import { createElement as h, useState } from 'react'
20
- import type { FormEvent } from 'react'
20
+ import type { FormEvent, MouseEvent } from 'react'
21
21
  import styles from '../../styles/CustomInstallView.module.css'
22
+ import modal from '../../styles/Modal.module.css'
22
23
  import type { Translate } from '../../types.ts'
24
+ import { CloseIcon, HelpIcon } from '../ui/icons.tsx'
23
25
 
24
26
  /* —— 自定义安装输入预检 ——
25
27
  * 提交前校验「输入是否符合该通道格式」,识别不了的输入不发请求,就地提示推荐格式。
26
- * 三个通道严格分开:npm 框只认包名,git 框只认 GitHub 地址,命令框认两种完整命令
27
- * (`dsh plugin [--profile|-p <p>] add <target>` 与官方全局安装 `npm install -g <pkgs>`)。
28
+ * 三个通道严格分开:npm 框认裸包名或完整 npm/pnpm 安装命令(含全局 `npm install -g`),
29
+ * git 框只认 GitHub 地址,命令框只认官方 `dsh plugin --profile <name> add <target>`(--profile 必填,无 -p 简写)。
28
30
  * 这里只做格式校验,真正归一化/安装由服务端完成。 */
29
31
 
30
32
  /** GitHub 地址(带前缀形态):github:、https://github.com/、git+https://github.com/、git@github.com: */
@@ -34,47 +36,83 @@ const GITHUB_BARE_RE = /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/
34
36
  /** npm 包名(含 @scope/pkg) */
35
37
  const NPM_PACKAGE_RE = /^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+$/
36
38
 
37
- /** DSH 命令行前缀(`dsh plugin [--profile|-p <p>] add <target>`)—— 提取其中的安装目标;
38
- * 命令大小写不敏感,支持 `-p` 简写与 `--profile=web` 等号形式,与 server 端 installTargetOf 口径一致。 */
39
- const DSH_PLUGIN_CMD_RE = /^dsh\s+plugin\s+(?:(?:--profile|-p)(?:=|\s+)\S+\s*)?add\s+(.+)$/i
39
+ /** DSH 命令行前缀(官方唯一形式 `dsh plugin --profile <name> add <target>`)—— 提取其中的安装目标。
40
+ * 官方 CLI(apps/cli/src/args.ts,commander):--profile 为必填长选项(无 -p 简写,省略即报错),
41
+ * 支持 `--profile=<name>` 等号形式;命令大小写不敏感。与 server 端 installTargetOf 口径一致。 */
42
+ const DSH_PLUGIN_CMD_RE = /^dsh\s+plugin\s+--profile(?:\s+|=)\S+\s+add\s+(.+)$/i
40
43
 
41
44
  /** npm 全局安装命令(官方 README 格式,如 `npm install -g @deepseek-ai/dsh @deepseek-harness-tui/dsh-tui`)——
42
45
  * 提取 -g 之后的包列表;任一包名非法返回 null(交给格式错误提示)。与 server 端 globalNpmPackagesOf 口径一致。 */
43
46
  const NPM_GLOBAL_CMD_RE = /^npm\s+(?:install|i)\s+(?:-g|--global)\s+(.+)$/i
44
47
 
48
+ /** 常规 npm/pnpm 安装命令(装进当前 profile,如 `npm install lodash`、`pnpm add @scope/pkg`)——
49
+ * 提取命令后的包列表;包列表在 splitPackages 里逐个校验。 */
50
+ const NPM_INSTALL_CMD_RE = /^(?:npm\s+(?:install|i)|pnpm\s+(?:add|i|install))\s+(.+)$/i
51
+
45
52
  function installTargetOf(raw: string): string {
46
53
  const input = raw.trim()
47
54
  const match = DSH_PLUGIN_CMD_RE.exec(input)
48
55
  return match !== null ? match[1].trim() : input
49
56
  }
50
57
 
51
- function parseNpmGlobalPackages(raw: string): string[] | null {
52
- const input = raw.trim()
53
- const match = NPM_GLOBAL_CMD_RE.exec(input)
54
- if (match === null) return null
55
- const packages = match[1].trim().split(/\s+/).filter((p) => p !== '')
56
- // 包名逐个校验 + 拒绝以 - 开头的 token(npm 会把 --xxx 当参数而非包名),与 server 端同口径
58
+ /** 空格分隔的包列表:逐个按 npm 包名语法校验 + 拒绝以 - 开头的 token(npm 会把 --xxx 当参数而非包名),
59
+ * 任一非法返回 null(交给格式错误提示)。与 server 端 globalNpmPackagesOf 同口径。 */
60
+ function splitPackages(raw: string): string[] | null {
61
+ const packages = raw.trim().split(/\s+/).filter((p) => p !== '')
57
62
  if (packages.length === 0 || packages.some((p) => !NPM_PACKAGE_RE.test(p) || p.startsWith('-'))) return null
58
63
  return packages
59
64
  }
60
65
 
61
- function isGitHubInput(raw: string): boolean {
66
+ /** NPM 框输入解析结果:
67
+ * - pkg:裸包名或常规安装命令(npm install / npm i / pnpm add / pnpm i),提取单个安装目标;
68
+ * - global:官方全局安装命令(npm install -g <pkgs>),提取包列表走全局通道。 */
69
+ type NpmInputParse =
70
+ | { kind: 'pkg'; target: string }
71
+ | { kind: 'global'; pkgs: string[] }
72
+
73
+ function parseNpmInput(raw: string): NpmInputParse | null {
62
74
  const input = raw.trim()
63
- return GITHUB_URL_RE.test(input) || GITHUB_BARE_RE.test(input)
75
+ // 裸包名(含 @scope/pkg)
76
+ if (NPM_PACKAGE_RE.test(input)) return { kind: 'pkg', target: input }
77
+ // 官方全局安装 npm install -g <pkgs>(装系统级 CLI 工具)
78
+ const global = NPM_GLOBAL_CMD_RE.exec(input)
79
+ if (global !== null) {
80
+ const pkgs = splitPackages(global[1])
81
+ if (pkgs !== null) return { kind: 'global', pkgs }
82
+ return null
83
+ }
84
+ // 常规安装命令 npm install <pkgs> / pnpm add <pkgs> —— 装进当前 profile,一次一个包
85
+ const install = NPM_INSTALL_CMD_RE.exec(input)
86
+ if (install !== null) {
87
+ const pkgs = splitPackages(install[1])
88
+ if (pkgs !== null && pkgs.length === 1) return { kind: 'pkg', target: pkgs[0] }
89
+ return null
90
+ }
91
+ return null
64
92
  }
65
93
 
66
- function isNpmInput(raw: string): boolean {
67
- return NPM_PACKAGE_RE.test(raw.trim())
94
+ function isGitHubInput(raw: string): boolean {
95
+ const input = raw.trim()
96
+ return GITHUB_URL_RE.test(input) || GITHUB_BARE_RE.test(input)
68
97
  }
69
98
 
70
99
  function isDshCommand(raw: string): boolean {
71
100
  return DSH_PLUGIN_CMD_RE.test(raw.trim())
72
101
  }
73
102
 
103
+ /** 卡片标题旁的「帮助」弹窗:三块卡片各自的标题与格式清单语言包 key。 */
104
+ const HELP_CARDS = {
105
+ npm: { titleKey: 'npmInstallLabel', contentKey: 'npmInstallHelp' },
106
+ git: { titleKey: 'gitInstallLabel', contentKey: 'gitInstallHelp' },
107
+ cmd: { titleKey: 'dshCmdLabel', contentKey: 'dshCmdHelp' },
108
+ } as const
109
+
110
+ type HelpTarget = keyof typeof HELP_CARDS
111
+
74
112
  export function CustomInstallView({ t, onInstallCustom, enableNpm, enableGit, enableDsh, onOpenSettings }: {
75
113
  t: Translate
76
- /** 命令行安装:输入 npm 包名 / GitHub 地址 / 安装命令(dsh plugin add 或 npm install -g)即装
77
- * (custom 源,受安全设置开关控制);opts.globalNpm 表示官方 npm 全局安装命令,附带解析出的包列表 */
114
+ /** 命令行安装:输入 npm 包名 / npm、pnpm 安装命令 / GitHub 地址 / dsh plugin 命令即装
115
+ * (custom 源,受安全设置开关控制);opts.globalNpm 表示官方 npm 全局安装命令(npm install -g),附带解析出的包列表 */
78
116
  onInstallCustom: (raw: string, opts?: { globalNpm?: string[] }) => void
79
117
  /** 安全信任三通道开关:关掉后对应输入卡片禁用(不提交),并引导去设置打开 */
80
118
  enableNpm: boolean
@@ -92,6 +130,8 @@ export function CustomInstallView({ t, onInstallCustom, enableNpm, enableGit, en
92
130
  /** 自定义安装:DSH 命令输入与格式错误('' = 无错误) */
93
131
  const [cmdQuery, setCmdQuery] = useState('')
94
132
  const [cmdError, setCmdError] = useState('')
133
+ /** 卡片标题旁「帮助」弹窗:null = 未打开;'npm' | 'git' | 'cmd' = 打开对应卡片的格式清单 */
134
+ const [helpFor, setHelpFor] = useState<HelpTarget | null>(null)
95
135
 
96
136
  /** 通道关闭提示行:开关关掉后替代示例与输入行,引导去设置打开 */
97
137
  const channelOffRow = h('div', { className: styles.channelOff },
@@ -104,13 +144,18 @@ export function CustomInstallView({ t, onInstallCustom, enableNpm, enableGit, en
104
144
  )
105
145
 
106
146
  // 三个通道分开校验 —— 识别不了的输入不发请求,就地提示推荐写法。
107
- // NPM / GitHub 框只认各自裸目标;命令框认两种完整命令(dsh plugin add / npm install -g),
108
- // 提交时 dsh 命令剥成裸目标发给服务端,npm install -g 解析成包列表走全局安装通道。
147
+ // NPM 框认裸包名或完整 npm/pnpm 命令(含全局 npm install -g,走 globalNpm 通道);
148
+ // 命令框只认官方 dsh plugin 命令,提交时剥成裸目标发给服务端。
109
149
  const submitNpm = () => {
110
- const target = npmQuery.trim()
111
- if (!target) return
112
- if (!isNpmInput(target)) { setNpmError(t('npmInstallInvalid')); return }
113
- onInstallCustom(target)
150
+ const raw = npmQuery.trim()
151
+ if (!raw) return
152
+ const parsed = parseNpmInput(raw)
153
+ if (parsed === null) { setNpmError(t('npmInstallInvalid')); return }
154
+ if (parsed.kind === 'global') {
155
+ onInstallCustom(raw, { globalNpm: parsed.pkgs })
156
+ } else {
157
+ onInstallCustom(parsed.target)
158
+ }
114
159
  setNpmQuery('')
115
160
  setNpmError('')
116
161
  }
@@ -125,14 +170,6 @@ export function CustomInstallView({ t, onInstallCustom, enableNpm, enableGit, en
125
170
  const submitCmd = () => {
126
171
  const raw = cmdQuery.trim()
127
172
  if (!raw) return
128
- // 官方 npm 全局安装命令(npm install -g <pkgs>)优先识别:解析出包列表走全局安装通道
129
- const globalPkgs = parseNpmGlobalPackages(raw)
130
- if (globalPkgs !== null) {
131
- onInstallCustom(raw, { globalNpm: globalPkgs })
132
- setCmdQuery('')
133
- setCmdError('')
134
- return
135
- }
136
173
  if (!isDshCommand(raw)) { setCmdError(t('dshCmdInvalid')); return }
137
174
  onInstallCustom(installTargetOf(raw))
138
175
  setCmdQuery('')
@@ -145,16 +182,25 @@ export function CustomInstallView({ t, onInstallCustom, enableNpm, enableGit, en
145
182
  // 三块卡片(一行一个):label + 文本框上方示例 + 输入行 + 错误区;
146
183
  // 安全信任开关关掉对应通道时,卡片禁用并显示「前往设置」提示行
147
184
  h('div', { className: styles.installCards },
148
- // NPM 包:输入包名即装
185
+ // NPM 包:输入包名或原生 npm/pnpm 安装命令即装(含官方全局 npm install -g)
149
186
  h('div', { className: enableNpm ? styles.installCard : `${styles.installCard} ${styles.installCardDisabled}` },
150
187
  h('div', { className: styles.installCardHead },
151
188
  h('span', { className: styles.installLabel }, t('npmInstallLabel')),
189
+ // 标题旁帮助:点开该卡片的格式清单弹窗
190
+ h('button', {
191
+ type: 'button',
192
+ className: styles.installHelpBtn,
193
+ title: t('installHelp'),
194
+ 'aria-label': t('installHelp'),
195
+ onClick: () => setHelpFor('npm'),
196
+ }, h(HelpIcon)),
152
197
  ),
153
198
  !enableNpm
154
199
  ? channelOffRow
155
200
  : [
156
- // 文本框上方的示例:告诉用户怎么写
157
- h('div', { className: styles.installExample }, t('npmInstallExample')),
201
+ // 文本框上方的示例:把支持的原生命令模式写清楚(两行)
202
+ h('div', { className: styles.installExample },
203
+ t('npmInstallExample'), h('br'), t('npmInstallExample2')),
158
204
  h('div', { className: styles.installRow },
159
205
  h('input', {
160
206
  className: npmError ? `${styles.installInput} ${styles.installInputError}` : styles.installInput,
@@ -184,6 +230,13 @@ export function CustomInstallView({ t, onInstallCustom, enableNpm, enableGit, en
184
230
  h('div', { className: enableGit ? styles.installCard : `${styles.installCard} ${styles.installCardDisabled}` },
185
231
  h('div', { className: styles.installCardHead },
186
232
  h('span', { className: styles.installLabel }, t('gitInstallLabel')),
233
+ h('button', {
234
+ type: 'button',
235
+ className: styles.installHelpBtn,
236
+ title: t('installHelp'),
237
+ 'aria-label': t('installHelp'),
238
+ onClick: () => setHelpFor('git'),
239
+ }, h(HelpIcon)),
187
240
  ),
188
241
  !enableGit
189
242
  ? channelOffRow
@@ -212,11 +265,17 @@ export function CustomInstallView({ t, onInstallCustom, enableNpm, enableGit, en
212
265
  gitError ? h('div', { className: styles.installError }, gitError) : null,
213
266
  ],
214
267
  ),
215
- // 命令卡片:粘贴完整安装命令即装 —— 官方全局安装 `npm install -g <pkgs>`(装系统级 CLI 工具)
216
- // 或完整 dsh plugin 命令(可含 --profile 段,剥成裸目标安装)
268
+ // 命令卡片:粘贴官方 dsh plugin 命令即装 —— 只认完整命令(--profile 必填,可含 = 等号,剥成裸目标安装)
217
269
  h('div', { className: enableDsh ? styles.installCard : `${styles.installCard} ${styles.installCardDisabled}` },
218
270
  h('div', { className: styles.installCardHead },
219
271
  h('span', { className: styles.installLabel }, t('dshCmdLabel')),
272
+ h('button', {
273
+ type: 'button',
274
+ className: styles.installHelpBtn,
275
+ title: t('installHelp'),
276
+ 'aria-label': t('installHelp'),
277
+ onClick: () => setHelpFor('cmd'),
278
+ }, h(HelpIcon)),
220
279
  ),
221
280
  !enableDsh
222
281
  ? channelOffRow
@@ -246,5 +305,35 @@ export function CustomInstallView({ t, onInstallCustom, enableNpm, enableGit, en
246
305
  ],
247
306
  ),
248
307
  ),
308
+ // 帮助弹窗:点卡片标题旁的问号打开 —— 宽窗(720px)把该通道支持的命令格式整行展示,防止折行
309
+ helpFor !== null
310
+ ? h('div', {
311
+ className: modal.overlay,
312
+ onClick: (e: MouseEvent<HTMLDivElement>) => { if (e.target === e.currentTarget) setHelpFor(null) },
313
+ },
314
+ h('div', { className: `${modal.errorModal} ${modal.helpModal}`, role: 'dialog', 'aria-modal': 'true' },
315
+ h('div', { className: modal.modalHead },
316
+ h('div', { className: modal.modalTitle }, `${t(HELP_CARDS[helpFor].titleKey)} · ${t('helpModalTitle')}`),
317
+ h('button', {
318
+ className: modal.modalClose,
319
+ 'aria-label': t('errorClose'),
320
+ onClick: () => setHelpFor(null),
321
+ }, h(CloseIcon)),
322
+ ),
323
+ h('div', { className: modal.modalBody },
324
+ // 格式清单:一行一条;`命令\t说明` 的说明跟在命令后,纯说明行以 \t 开头
325
+ h('div', { className: styles.helpBody },
326
+ t(HELP_CARDS[helpFor].contentKey).split('\n').map((line, i) => {
327
+ const [cmd, note] = line.split('\t')
328
+ return h('div', { key: i, className: styles.helpLine },
329
+ cmd !== '' ? h('span', { className: styles.helpCmd }, cmd) : null,
330
+ note !== undefined ? h('span', { className: styles.helpNote }, note) : null,
331
+ )
332
+ }),
333
+ ),
334
+ ),
335
+ ),
336
+ )
337
+ : null,
249
338
  )
250
339
  }
@@ -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
  *
@@ -20,7 +20,7 @@ type ProbeKey = 'npm' | 'github' | 'catalog'
20
20
  /** 系统版本文本:提交 Issue 时粘贴到正文,方便作者复现。 */
21
21
  function formatEnv(env: EnvInfo): string {
22
22
  return [
23
- `DSH-Plugin Hub ${PLUGIN_VERSION}`,
23
+ `DSH Plugin Hub ${PLUGIN_VERSION}`,
24
24
  `DSH ${env.dshVersion ?? 'unknown'}`,
25
25
  `Node ${env.nodeVersion}`,
26
26
  `${env.platform} ${env.arch} (${env.release})`,
@@ -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
  *
@@ -10,10 +10,19 @@
10
10
  import type { HubAboutInfo, HubUpdateInfo } from '../types.ts'
11
11
  import { HUB_ABOUT_URL, HUB_UPDATE_URL } from '../logic/constants.ts'
12
12
 
13
+ /** 版本接口是 Pages 静态文件,默认带 CDN 缓存(Cache-Control: public, max-age=43200,
14
+ * 即 12 小时),客户端 no-store 管不到 CDN 边缘,会拿到旧公告。
15
+ * 静态资产请求免费且无限,因此所有版本检查一律带时间戳 query:URL 每次不同 →
16
+ * CDN 缓存 key 不同 → 强制回源拉到最新公告,无任何配额顾虑。 */
17
+ function busted(url: string): string {
18
+ const sep = url.includes('?') ? '&' : '?'
19
+ return `${url}${sep}_t=${Date.now()}`
20
+ }
21
+
13
22
  /** Worker 版本控制中心:最新版本 + Markdown 变更记录;不可用返回 null。 */
14
23
  export async function fetchHubUpdate(): Promise<HubUpdateInfo | null> {
15
24
  try {
16
- const res = await fetch(HUB_UPDATE_URL, { cache: 'no-store' })
25
+ const res = await fetch(busted(HUB_UPDATE_URL), { cache: 'no-store' })
17
26
  if (!res.ok) return null
18
27
  const data = await res.json() as HubUpdateInfo | null
19
28
  if (!data) return null
@@ -28,7 +37,7 @@ export async function fetchHubUpdate(): Promise<HubUpdateInfo | null> {
28
37
  /** Worker「关注我们」内容(平台介绍 + 反馈群二维码,Markdown);未推送返回 null。 */
29
38
  export async function fetchHubAbout(): Promise<HubAboutInfo | null> {
30
39
  try {
31
- const res = await fetch(HUB_ABOUT_URL, { cache: 'no-store' })
40
+ const res = await fetch(busted(HUB_ABOUT_URL), { cache: 'no-store' })
32
41
  if (!res.ok) return null
33
42
  const data = await res.json() as HubAboutInfo | null
34
43
  if (!data || data.content === null || data.content === undefined) return null
@@ -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
  *
@@ -22,7 +22,7 @@ import {
22
22
  } from '../logic/installed.ts'
23
23
  import type { InstalledItem, InstalledVersionSignal } from '../logic/installed.ts'
24
24
 
25
- /** 插件市场自身仓库:DSH-Plugin Hub 不显示在目录里(自己不进自己的插件列表) */
25
+ /** 插件市场自身仓库:DSH Plugin Hub 不显示在目录里(自己不进自己的插件列表) */
26
26
  const SELF_REPO = 'dshplugin/dsh-plugin-hub'
27
27
 
28
28
  /** 市场各排序的默认方向:全部按倒序(Star/Fork 多、更新/收录近的在前) */
@@ -51,6 +51,19 @@ export function useCatalog(lang: LocaleId) {
51
51
  if (key === sort) setSortDir((d) => (d === 'asc' ? 'desc' : 'asc'))
52
52
  else { setSort(key); setSortDir(SORT_DEFAULT_DIR[key]) }
53
53
  }
54
+ /**
55
+ * 强制刷新 Hub 版本与「关注我们」信息:点击头部版本号/徽标时触发。
56
+ * 挂载期的 useEffect 只在进入/reload 时拉一次,发版后已打开的界面点版本号
57
+ * 仍显示旧数据;此处重新拉接口中心,最新版本与变更记录点击即见。
58
+ * 失败静默为 null(弹窗兜底展示当前版本号),与挂载期拉取口径一致。
59
+ */
60
+ const refreshHub = async () => {
61
+ // 版本接口已恒带时间戳绕过 CDN,手动/自动拉取都能拿到最新公告
62
+ const [info, about] = await Promise.all([fetchHubUpdate(), fetchHubAbout()])
63
+ setHubUpdateInfo(info)
64
+ setHubAboutInfo(about)
65
+ }
66
+
54
67
  /** 当前 profile 已安装插件:npm 包名 -> manifest spec(来自宿主本地路由) */
55
68
  const [installed, setInstalled] = useState<Record<string, string>>({})
56
69
  /** 安装时记录的目录信号:repo(小写) -> { version, updatedAt }(来自宿主本地路由);
@@ -74,15 +87,14 @@ export function useCatalog(lang: LocaleId) {
74
87
  setHubPlugin(null)
75
88
  setStats(null)
76
89
  setFailed(false)
77
- // 目录数据直拉:hub 自身版本由独立 Worker 版本控制中心管理,不再依赖主站版本号接口;
78
- // 数据接口 CDN 缓存 1 小时,发布新数据后最长 1 小时全网生效。
90
+ // 目录数据直拉:hub 自身版本由独立 Worker 版本控制中心管理,不再依赖主站版本号接口。
79
91
  const load = () => Promise.all([fetchCatalog(lang), fetchStats()])
80
92
  load()
81
93
  .then(([list, stats]) => {
82
94
  if (cancelled) return
83
- // 插件市场不显示自己:DSH-Plugin Hub 从目录里排除自身条目,
95
+ // 插件市场不显示自己:DSH Plugin Hub 从目录里排除自身条目,
84
96
  // 防止「自己出现在自己的插件列表里、还能自己安装自己」;统计计数同步减 1 保持与列表一致。
85
- // 主站数据已同步排除自身,此处为 CDN 缓存期的兜底。
97
+ // 目录数据已排除自身,此处再兜底过滤一次,防旧快照仍含自身条目。
86
98
  const hadSelf = list.some((p) => p.source?.repo === SELF_REPO)
87
99
  // 过滤前单独保留 hub 自身条目:它不进目录列表,但「可更新」徽标的直接更新需要它作为重装目标
88
100
  setHubPlugin(list.find((p) => p.source?.repo === SELF_REPO) ?? null)
@@ -215,6 +227,7 @@ export function useCatalog(lang: LocaleId) {
215
227
  hubHasUpdate,
216
228
  hubUpdateInfo,
217
229
  hubAboutInfo,
230
+ refreshHub,
218
231
  refreshInstalled,
219
232
  category,
220
233
  setCategory,
@@ -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
  *