dsh-plugin 1.6.12 → 2.0.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 (128) hide show
  1. package/README.en.md +1 -1
  2. package/README.md +1 -1
  3. package/client/client.js +615 -741
  4. package/client/client.js.map +1 -1
  5. package/lib/http/catalog.js +2 -3
  6. package/lib/http/diagnostics.js +6 -7
  7. package/lib/http/install.js +47 -213
  8. package/lib/http/logs.js +1 -2
  9. package/lib/http/npm-release-age.js +42 -0
  10. package/lib/http/routes.js +8 -7
  11. package/lib/http/settings.js +2 -2
  12. package/lib/http/shared.js +2 -2
  13. package/lib/http/task.js +1 -2
  14. package/lib/index.js +5 -6
  15. package/lib/services/install/install-types.js +4 -4
  16. package/lib/services/install/install.js +1 -1
  17. package/lib/services/install/npm-check.js +3 -2
  18. package/lib/services/install/npm-resolve.js +9 -96
  19. package/lib/services/install/package-entry.js +6 -11
  20. package/lib/services/install/registry-check.js +2 -2
  21. package/lib/services/install/release-target.js +8 -9
  22. package/lib/services/install/task-queue.js +40 -48
  23. package/lib/services/npm-release-age.js +119 -0
  24. package/lib/services/probe.js +4 -4
  25. package/lib/services/profile/installed-versions.js +15 -21
  26. package/lib/services/profile/profile.js +16 -44
  27. package/lib/services/profile/progress.js +8 -11
  28. package/lib/services/settings.js +2 -5
  29. package/lib/types/http/catalog.d.ts +1 -1
  30. package/lib/types/http/install.d.ts +2 -2
  31. package/lib/types/http/npm-release-age.d.ts +2 -0
  32. package/lib/types/http/routes.d.ts +10 -12
  33. package/lib/types/http/shared.d.ts +5 -6
  34. package/lib/types/index.d.ts +7 -8
  35. package/lib/types/services/install/install-types.d.ts +12 -13
  36. package/lib/types/services/install/install.d.ts +1 -1
  37. package/lib/types/services/install/npm-resolve.d.ts +4 -16
  38. package/lib/types/services/install/package-entry.d.ts +6 -11
  39. package/lib/types/services/install/release-target.d.ts +7 -8
  40. package/lib/types/services/install/task-queue.d.ts +25 -30
  41. package/lib/types/services/npm-release-age.d.ts +15 -0
  42. package/lib/types/services/probe.d.ts +4 -4
  43. package/lib/types/services/profile/installed-versions.d.ts +5 -7
  44. package/lib/types/services/profile/profile.d.ts +14 -18
  45. package/lib/types/services/profile/progress.d.ts +7 -10
  46. package/lib/types/services/settings.d.ts +0 -4
  47. package/package.json +1 -1
  48. package/src/client/components/PluginHubSection.tsx +17 -22
  49. package/src/client/components/catalog/CatalogList.tsx +2 -4
  50. package/src/client/components/catalog/PluginCard.tsx +2 -3
  51. package/src/client/components/layout/CatalogControls.tsx +5 -6
  52. package/src/client/components/layout/CatalogHeader.tsx +1 -2
  53. package/src/client/components/layout/CategoryTabs.tsx +2 -3
  54. package/src/client/components/layout/SectionTabs.tsx +3 -5
  55. package/src/client/components/modals/ConfirmDialog.tsx +4 -6
  56. package/src/client/components/modals/InstalledDetailModal.tsx +3 -6
  57. package/src/client/components/modals/LogsModal.tsx +7 -12
  58. package/src/client/components/modals/NotificationsModal.tsx +35 -16
  59. package/src/client/components/modals/ProgressView.tsx +3 -4
  60. package/src/client/components/modals/modals.tsx +35 -15
  61. package/src/client/components/ui/Dropdown.tsx +4 -6
  62. package/src/client/components/ui/Toggle.tsx +2 -3
  63. package/src/client/components/ui/icons.tsx +1 -2
  64. package/src/client/components/views/CustomInstallView.tsx +20 -178
  65. package/src/client/components/views/DiagnosticsView.tsx +3 -6
  66. package/src/client/components/views/InstalledView.tsx +6 -11
  67. package/src/client/components/views/MarketView.tsx +2 -4
  68. package/src/client/components/views/SettingsView.tsx +9 -32
  69. package/src/client/css-modules.d.ts +1 -1
  70. package/src/client/data/catalog.ts +2 -3
  71. package/src/client/data/host.ts +26 -3
  72. package/src/client/data/hub.ts +2 -3
  73. package/src/client/hooks/useCatalog.ts +79 -24
  74. package/src/client/hooks/useIncrementalList.ts +3 -5
  75. package/src/client/hooks/useLanguage.ts +4 -6
  76. package/src/client/hooks/useSettings.ts +4 -11
  77. package/src/client/hooks/useTaskQueue.ts +36 -42
  78. package/src/client/index.tsx +2 -3
  79. package/src/client/locales.ts +75 -114
  80. package/src/client/logic/constants.ts +1 -2
  81. package/src/client/logic/ensure-plugin-css.ts +8 -12
  82. package/src/client/logic/failures.ts +16 -18
  83. package/src/client/logic/format.ts +1 -1
  84. package/src/client/logic/install-command.ts +14 -35
  85. package/src/client/logic/installed.ts +15 -10
  86. package/src/client/logic/normalize.ts +3 -3
  87. package/src/client/logic/release-age.ts +19 -0
  88. package/src/client/logic/urls.ts +20 -20
  89. package/src/client/styles/CustomInstallView.module.css +3 -30
  90. package/src/client/styles/DiagnosticsView.module.css +3 -5
  91. package/src/client/styles/Dropdown.module.css +2 -4
  92. package/src/client/styles/Header.module.css +6 -7
  93. package/src/client/styles/InstalledView.module.css +4 -7
  94. package/src/client/styles/List.module.css +4 -5
  95. package/src/client/styles/LogsView.module.css +2 -3
  96. package/src/client/styles/Modal.module.css +4 -5
  97. package/src/client/styles/SectionTabs.module.css +2 -4
  98. package/src/client/styles/SettingsView.module.css +3 -5
  99. package/src/client/styles/Toggle.module.css +2 -4
  100. package/src/client/styles/tokens.module.css +14 -18
  101. package/src/client/types.ts +11 -11
  102. package/src/server/http/catalog.ts +2 -3
  103. package/src/server/http/diagnostics.ts +6 -7
  104. package/src/server/http/install.ts +47 -208
  105. package/src/server/http/logs.ts +1 -2
  106. package/src/server/http/npm-release-age.ts +42 -0
  107. package/src/server/http/routes.ts +12 -12
  108. package/src/server/http/settings.ts +2 -2
  109. package/src/server/http/shared.ts +5 -6
  110. package/src/server/http/task.ts +1 -2
  111. package/src/server/index.ts +7 -8
  112. package/src/server/services/install/install-types.ts +12 -13
  113. package/src/server/services/install/install.ts +0 -1
  114. package/src/server/services/install/npm-check.ts +3 -2
  115. package/src/server/services/install/npm-resolve.ts +9 -99
  116. package/src/server/services/install/package-entry.ts +6 -11
  117. package/src/server/services/install/registry-check.ts +2 -2
  118. package/src/server/services/install/release-target.ts +8 -9
  119. package/src/server/services/install/task-queue.ts +47 -54
  120. package/src/server/services/npm-release-age.ts +107 -0
  121. package/src/server/services/probe.ts +4 -4
  122. package/src/server/services/profile/installed-versions.ts +16 -22
  123. package/src/server/services/profile/profile.ts +16 -42
  124. package/src/server/services/profile/progress.ts +8 -11
  125. package/src/server/services/settings.ts +2 -9
  126. package/lib/services/install/preflight.js +0 -178
  127. package/lib/types/services/install/preflight.d.ts +0 -23
  128. package/src/server/services/install/preflight.ts +0 -184
@@ -3,13 +3,10 @@
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
6
- * Failure classification, issue-body summarization and persistent
7
- * install/remove notifications.
6
+ * 失败归类、Issue 正文摘要与持久化的安装/卸载通知。
8
7
  *
9
- * Every settled task is recorded two ways: the server-side system log
10
- * (hub.log) and a client-side localStorage record that feeds the
11
- * notification center — so a result is never lost even when the dialog
12
- * was dismissed or nobody was watching the progress strip.
8
+ * 每个结算后的任务都会双重留痕:服务端系统日志(hub.log)与馈送通知中心的客户端
9
+ * localStorage 记录 —— 即使弹窗被关闭、无人盯着进度条,结果也不会丢失。
13
10
  */
14
11
  export interface NotificationRecord {
15
12
  id: number
@@ -27,7 +24,7 @@ export interface NotificationRecord {
27
24
  version?: string
28
25
  /** 实际执行的安装/卸载命令(issue 预填时如实展示);历史记录可能缺失 */
29
26
  command?: string
30
- /** 尝试过的安装方式(npm registry 反查 + 实际执行命令,按先后顺序):失败提 Issue 时贴给作者,便于反推正确的 npm 包名 */
27
+ /** 尝试过的安装方式(实际执行过的命令,按先后顺序):失败提 Issue 时贴给作者,便于反推正确的 npm 包名 */
31
28
  attempts?: string[]
32
29
  /** 结束时间(epoch ms) */
33
30
  at: number
@@ -172,7 +169,7 @@ export function actionOfRecord(rec: { kind: NotificationRecord['kind']; action?:
172
169
  return rec.kind === 'uninstall' ? 'uninstall' : rec.kind === 'update' ? 'update' : 'install'
173
170
  }
174
171
 
175
- /** Match the pnpm error code, not a generic mention of checksum or integrity. */
172
+ /** 只匹配 pnpm 的错误码,不匹配泛泛出现的 checksum / integrity 字样。 */
176
173
  function missingTarballIntegrity(message: string): boolean {
177
174
  return /\bERR_PNPM_MISSING_TARBALL_INTEGRITY\b/.test(message)
178
175
  }
@@ -187,9 +184,9 @@ export function releaseAgeRetryAfter(message: string): string | null {
187
184
  }
188
185
 
189
186
  /**
190
- * The same policy subtype selects recovery copy in live and saved failures.
191
- * minimumReleaseAge 只有在能算出可重试时刻时才走专属文案(给出确切的「何时可再试」),
192
- * 算不出时退回通用文案,避免出现「预计可重试时间:」后接空值的残缺句子。
187
+ * 同一策略子类型也用于选择实时/已保存失败的提示文案:minimumReleaseAge 只有在能算出
188
+ * 可重试时刻时才走专属文案(给出确切的「何时可再试」),算不出时退回通用文案,
189
+ * 避免出现「预计可重试时间:」后接空值的残缺句子。
193
190
  */
194
191
  export function pnpmPolicyHintOf(message: string): 'failPnpmTarballIntegrityHint' | 'failPnpmReleaseAgeHint' | 'failPnpmPolicyHint' {
195
192
  if (missingTarballIntegrity(message)) return 'failPnpmTarballIntegrityHint'
@@ -245,8 +242,9 @@ export function classifyFailure(message: string): FailureKind {
245
242
  // 构建脚本被 allowBuilds 拦截。必须在 network 之前判:日志尾部混重试残留的连接失败特征
246
243
  if (/ERR_PNPM_IGNORED_BUILDS|Ignored build scripts:|ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED/i.test(message)) return 'pnpmIgnoredBuild'
247
244
  // 网络/代理/源配置问题。必须在 prepare 之前判:git fetch 失败常被外层包成 "Command failed"
248
- if (/\[network\]|ERR_PNPM_GIT_FETCH_FAILED|ETIMEDOUT|ECONNREFUSED|ECONNRESET|ENOTFOUND|EAI_AGAIN|EPIPE|EHOSTUNREACH|ENETUNREACH|getaddrinfo|Could not connect|Could not resolve host|Network unreachable|Failed to connect|socket hang up|CERT_HAS_EXPIRED|SSL certificate problem|\bTLS\b|\bSSL\b|fetch failed|failed to fetch|\bGET https?:\/\/\S+\.tgz\s+error \(\d+\)/i.test(message)) return 'network'
249
- // 装后校验发现入口文件缺失([packaging])= git 分发缺构建产物
245
+ // tarball 下载失败的括号里可能是数字码(pnpm 自己的)或符号码(undici 的 UND_ERR_*),两种都要认
246
+ if (/\[network\]|ERR_PNPM_GIT_FETCH_FAILED|ETIMEDOUT|ECONNREFUSED|ECONNRESET|ENOTFOUND|EAI_AGAIN|EPIPE|EHOSTUNREACH|ENETUNREACH|UND_ERR_[A-Z0-9_]+|getaddrinfo|Could not connect|Could not resolve host|Network unreachable|Failed to connect|socket hang up|CERT_HAS_EXPIRED|SSL certificate problem|\bTLS\b|\bSSL\b|fetch failed|failed to fetch|\bGET https?:\/\/\S+\.tgz\s+error \([A-Za-z0-9_]+\)/i.test(message)) return 'network'
247
+ // 装后校验发现入口文件缺失([packaging])= 分发物缺构建产物
250
248
  if (/\[packaging\]|entry file missing/i.test(message)) return 'pluginPrepare'
251
249
  // registry 上不存在的依赖(404),profile 任何安装都会卡在这条。必须在 pluginPrepare 之前判
252
250
  // [registry-missing](服务端官方源复核后注入)= 作者侧缺依赖,须先于 pnpmMissingDep(同样含 404 文本)
@@ -262,7 +260,7 @@ export function classifyFailure(message: string): FailureKind {
262
260
  // 安装器自身产生的状态类消息(未安装 / 不在此处卸载 / 已在队列中 / 已安装):这几句由本服务的
263
261
  // 守卫发出,不是 CLI 输出、也不是插件问题,而是插件中心的已安装列表与实读快照不同步。
264
262
  // 单独归类,让前端给「刷新后重试」这类可行动提示,而不是当作未知失败引导去提 Issue。
265
- // already installed 只认「整行就是它」的短形态(旧版本守卫留下的历史记录);带「——」说明的
263
+ // already installed 只认「整行就是它」的短形态;带「——」说明的
266
264
  // 包名冲突另有处置,不能按状态不同步提示。
267
265
  if (/plugin is not installed|plugin cannot be uninstalled here|already queued:|^already installed: \S+$/im.test(message)) return 'hubState'
268
266
  // 安装器自己的超时标记(命令跑满上限被强杀):既不是插件问题、也不是用户环境问题,而且很可能是
@@ -345,8 +343,8 @@ export function coreErrorCode(message: string): string | null {
345
343
  }
346
344
 
347
345
  /** 「包在 registry 不存在」失败的提示变体:服务端用官方源复核过(会注入 `[registry-mirror-lag]`)
348
- * 时,说明包在官方源上存在、是本机镜像源未同步 → 提示换源;否则沿用既有的「profile 残留依赖」
349
- * 提示(#73:指向从未发布包的残留条目,任何插件都装不进来)。 */
346
+ * 时,说明包在官方源上存在、是本机镜像源未同步 → 提示换源;否则按「profile 残留依赖」提示
347
+ * (删掉 profile 里那条依赖即可)。 */
350
348
  export function registryMissingHintOf(message: string): 'failPnpmMissingDepHint' | 'failRegistryMirrorLagHint' {
351
349
  return /\[registry-mirror-lag\]/i.test(message) ? 'failRegistryMirrorLagHint' : 'failPnpmMissingDepHint'
352
350
  }
@@ -365,12 +363,12 @@ export function missingRegistryPackageOf(message: string): string | null {
365
363
 
366
364
  /**
367
365
  * 从网络类失败消息里提取「具体连不上的地址」,供弹窗精准提示(你的网络无法访问什么)。
368
- * 优先取服务端 [network] 预检消息括号里的探测地址(中文「(…)」/ 英文「(…)」),
366
+ * 优先取服务端 [network] 网络探测消息括号里的探测地址(中文「(…)」/ 英文「(…)」),
369
367
  * 否则取消息里出现的第一个 URL(git fetch 的仓库地址、registry 域名等)。
370
368
  * 提取不到返回 null —— 调用方据此跳过地址行。
371
369
  */
372
370
  export function unreachableTargetOf(message: string): string | null {
373
- // [network] 预检消息:`无法连接到 GitHub(https://github.com/)` / `cannot reach the npm registry (https://registry.npmjs.org/)`
371
+ // [network] 网络探测消息:`无法连接到 GitHub(https://github.com/)` / `cannot reach the npm registry (https://registry.npmjs.org/)`
374
372
  const paren = message.match(/[((](https?:\/\/[^\s'"`<>()+]+)[))]/)
375
373
  if (paren) return paren[1]
376
374
  // 其余网络失败(git fetch / npm 连接失败):取第一个 URL,去掉行尾标点
@@ -3,7 +3,7 @@
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
6
- * Presentation formatters shared across the catalog list and the cards.
6
+ * 目录列表与卡片共用的展示格式化函数。
7
7
  */
8
8
  import type { Translate } from '../types.ts'
9
9
 
@@ -3,15 +3,13 @@
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
6
- * Install target/command helpers: decide the install channel (npm package
7
- * vs prebuilt GitHub release vs explicit-HTTPS GitHub), build the display
8
- * command and normalize a raw install spec back to its owner/repo identity.
6
+ * 安装目标/命令工具:为目录条目取 npm 包名、构造展示命令,并把原始安装 spec 归一化回 owner/repo 身份。
9
7
  */
10
8
  import type { HubPlugin } from '../types.ts'
11
9
  import { githubReleaseTarget } from '../../server/services/install/release-target.ts'
12
10
 
13
- /** Backend attempts are chronological; report the last executed command before a display fallback.
14
- * These prefixes are the task queue's recorded command shapes, never commands to execute here.
11
+ /** 后端 attempts 按时间先后排列:取最后一次实际执行的命令,取不到再回退到展示用命令。
12
+ * 这里匹配的是任务队列记录的指令形态,不是要在此执行的命令。
15
13
  */
16
14
  export function executedCommandOf(command?: string, attempts?: string[]): string | undefined {
17
15
  if (attempts === undefined) return command
@@ -21,44 +19,25 @@ export function executedCommandOf(command?: string, attempts?: string[]): string
21
19
  }
22
20
 
23
21
  /**
24
- * 安装通道决策(用户无感知):目录探测到 npm 包名 → 用 npm 包名安装
25
- * (走 npm registry tarball,更快、与 GitHub 网络无关);其次用「同仓库」的权威
26
- * release 包直链(仓库 HEAD 往往是 monorepo 根、装不上,release .tgz 才是完整产物);
27
- * 都没有 → git 直装。
28
- * 返回值 target 即传给后端 /install 的安装目标(npm 包名 / release URL / owner/repo)。
22
+ * 目录条目的安装目标(用户无感知):目录条目必带 npm 包名,直接用该包名安装
23
+ * (走 npm registry tarball)。
24
+ * 返回值即传给后端 /install 的安装目标(npm 包名)。
29
25
  */
30
- export function installTargetOf(p: HubPlugin): { target: string; via: 'npm' | 'release' | 'github' } {
31
- const pkg = (p.source?.npmPackage ?? '').trim()
32
- const repo = (p.source?.repo ?? '').trim()
33
- if (pkg && repo) return { target: pkg, via: 'npm' }
34
- // 目录下发的权威命令若是「同仓库」的固定 release 直链,改走该预构建包;
35
- // 跨仓 / 非法 release 目标不采信,退回 git 直装。
36
- const release = githubReleaseTarget(p.install?.githubCommand ?? '')
37
- if (release !== null && repo !== '' && release.repo.toLowerCase() === repo.toLowerCase()) {
38
- return { target: release.target, via: 'release' }
39
- }
40
- return { target: repo, via: 'github' }
26
+ export function installTargetOf(p: HubPlugin): string {
27
+ return (p.source?.npmPackage ?? '').trim()
41
28
  }
42
29
 
43
30
  /** 展示用安装命令(复制/弹窗):目录下发的权威命令优先(CLI-only 插件如 dsh-tui
44
- * 需专属 profile(--profile dsh-tui),无法从 repo/npm 包名推断,必须用官方命令);
45
- * 常规插件无目录命令时按通道回退生成:npm 显示包名,git 显示显式 HTTPS URL。 */
31
+ * 需专属 profile(--profile dsh-tui),无法从 npm 包名推断,必须用官方命令);
32
+ * 常规插件无目录命令时用 npm 包名回退生成。 */
46
33
  export function installCommandOf(p: HubPlugin, withProfile = false): string {
47
- const { target, via } = installTargetOf(p)
48
- if (via === 'npm') {
49
- const cmd = p.install?.command
50
- if (cmd) return cmd
51
- } else {
52
- // release 与 git 通道共用目录下发的 githubCommand(git+ 显式 HTTPS 或 release 直链)
53
- const cmd = p.install?.githubCommand
54
- if (cmd) return cmd
55
- }
34
+ const cmd = p.install?.command
35
+ if (cmd) return cmd
56
36
  const prefix = `dsh plugin${withProfile ? ' --profile web' : ''} add `
57
- if (via === 'npm' || via === 'release') return `${prefix}${target}`
58
- return `${prefix}git+https://github.com/${target}.git`
37
+ return `${prefix}${installTargetOf(p)}`
59
38
  }
60
39
 
61
- /** Normalize a task/install target to its owner/repo display identity. */
40
+ /** 把任务/安装目标归一化为 owner/repo 展示身份。 */
62
41
  export function repoFromInstallTarget(value: string): string {
63
42
  const input = value.trim()
64
43
  // release 直链(预构建 .tgz)走同一条身份归一化:解析出所属仓库,已装态/更新信号才能
@@ -3,13 +3,11 @@
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
6
- * Installed-plugin model (pure logic, zero React/UI dependencies).
6
+ * 已安装插件模型(纯逻辑,不依赖 React/UI)。
7
7
  *
8
- * Merges the local installed table (from the host's `/installed` route:
9
- * npm name → manifest spec) with the catalog projection into a single
10
- * `InstalledItem` view-model consumed by the Installed view. Also hosts the
11
- * install matching / update detection used by the market view, so both
12
- * views share one source of truth.
8
+ * 把宿主 `/installed` 路由下发的本地已装表(npm 包名 → manifest spec)与目录投影合并成
9
+ * 统一的 `InstalledItem` 视图模型,供「已安装」视图消费;同时承载市场视图用的安装匹配
10
+ * 与更新检测,让两个视图共享唯一数据源。
13
11
  */
14
12
  import type { HubPlugin } from '../types.ts'
15
13
  import { HUB_REPO } from './constants.ts'
@@ -56,7 +54,7 @@ function signalOf(repo: string | null, versions: Record<string, InstalledVersion
56
54
  return versions[repo.toLowerCase()] ?? null
57
55
  }
58
56
 
59
- /** 插件是否已安装:匹配 Git spec 中的 owner/repo,或 npm 通道安装的依赖包名;命中返回 npm 包名。 */
57
+ /** 插件是否已安装:匹配 Git spec 中的 owner/repo,或 npm 安装的依赖包名;命中返回 npm 包名。 */
60
58
  export function installedNameOf(
61
59
  plugin: HubPlugin,
62
60
  installed: Record<string, string>,
@@ -68,8 +66,8 @@ export function installedNameOf(
68
66
  for (const [name, spec] of Object.entries(installed)) {
69
67
  if (repoFromInstallTarget(spec).toLowerCase() === needle) return name
70
68
  }
71
- // npm 通道安装:profile 依赖 key 直接是 npm 包名。包名来源两处——目录数据(npmPackage),
72
- // 或服务端 npm 优先反查持久化的映射(versions[repo].npmPackage,覆盖组织 scope 与 GitHub
69
+ // npm 安装:profile 依赖 key 直接是 npm 包名。包名来源两处——目录数据(npmPackage),
70
+ // 或服务端安装时持久化的映射(versions[repo].npmPackage,覆盖组织 scope 与 GitHub
73
71
  // 用户名不一致、目录未下发包名的场景);命中任一并依赖 key 真实存在即视为已安装。
74
72
  // 只认 npm 安装的依赖(spec 无法解析出仓库身份):git/本地安装的依赖身份由仓库匹配决定,
75
73
  // 包名同名不代表同仓库——否则「git 装 A 仓库」会被「npm 包名同名但仓库不同」的目录条目
@@ -91,6 +89,13 @@ export function installedVersionOf(plugin: HubPlugin, versions: Record<string, I
91
89
  return versions[repo.toLowerCase()]?.version ?? null
92
90
  }
93
91
 
92
+ /** 该插件对应的 npm 包名(目录下发,或安装时持久化的映射);无则 null。决定更新是否走 npm registry。 */
93
+ export function npmPackageOf(plugin: HubPlugin, versions: Record<string, InstalledVersionSignal>): string | null {
94
+ const repo = plugin.source?.repo
95
+ const pkg = (plugin.source?.npmPackage || (repo ? versions[repo.toLowerCase()]?.npmPackage : undefined) || '').trim()
96
+ return pkg === '' ? null : pkg
97
+ }
98
+
94
99
  /**
95
100
  * 是否有更新:仅对已安装插件有意义。
96
101
  * 双信号判定——有 release 版本的比版本;无版本(repo 不打 tag)的比仓库最近更新时间
@@ -174,7 +179,7 @@ function isHubSelf(name: string, spec: string): boolean {
174
179
  if (spec.startsWith('file:') || spec.startsWith('link:')) return true
175
180
  const repo = repoFromInstallTarget(spec)
176
181
  if (isRepoLike(repo) && repo.toLowerCase() === HUB_REPO.toLowerCase()) return true
177
- // npm 通道覆盖重装 hub 自身后,file:/link: 本地链接会被替换成 registry 版本号/范围
182
+ // npm 覆盖重装 hub 自身后,file:/link: 本地链接会被替换成 registry 版本号/范围
178
183
  // (如 1.3.0、^1.3.0)——依赖 key `dsh-plugin` 在 npm 上唯一属于本插件(不存在第三方
179
184
  // 同名包),版本号形态与仓库身份同样视为 hub 自身:命令窗口「自己装自己」成功后,
180
185
  // 条目仍不进已安装列表(卸载接口对它的 400 拒绝是服务端最后一道防线)。
@@ -3,8 +3,8 @@
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
6
- * Normalizer: maps the online API's short-key payload (s/o/n/vr/c/t/f/d/r/v/u/a/sg/fk)
7
- * to HubPlugin so rendering only ever sees one shape.
6
+ * 归一化器:把在线 API 的短 key 载荷(s/o/n/vr/c/t/f/d/r/v/u/a/sg/fk)映射为
7
+ * HubPlugin,让渲染层只面对一种数据形态。
8
8
  */
9
9
  import type { HubPlugin } from '../types.ts'
10
10
 
@@ -30,7 +30,7 @@ export function normalize(raw: Record<string, unknown>): HubPlugin {
30
30
  : undefined,
31
31
  // 安装信息:wi 仅在 false 时下发(缺省视为 true 可一键安装);
32
32
  // mi 仅在 true 时下发(人工干预:网页端替不了终端,只引导用户自己去终端装);
33
- // ic/igc = 目录下发的权威安装命令(npm/git 通道各一条,CLI-only 插件如 dsh-tui 必须用)
33
+ // ic/igc = 目录下发的权威安装命令(CLI-only 插件如 dsh-tui 必须用)
34
34
  install: raw.wi === false || raw.mi === true || typeof raw.ic === 'string' || typeof raw.igc === 'string'
35
35
  ? {
36
36
  webInstallable: raw.wi === false ? false : undefined,
@@ -0,0 +1,19 @@
1
+ /**
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
+ * Website: https://dsh-plugin.org
4
+ * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
+ *
6
+ * 供应链门禁的时间判定(纯函数):pnpm 会静默回退发布未满门槛的版本,
7
+ * 故发布太新的版本不该提示更新。Hub 自身更新与插件更新共用同一门槛。
8
+ */
9
+
10
+ /** pnpm 供应链门禁:dsh profile 默认 minimumReleaseAge=1440(分钟);+30 分钟缓冲抵消网络/CDN 边缘时间偏差。 */
11
+ export const MIN_RELEASE_AGE_MS = 24 * 60 * 60 * 1000 + 30 * 60 * 1000
12
+
13
+ /** 发布时间是否仍在门禁窗口内(太新 → 暂缓提示);无值/解析不出返回 false(不拦真实更新)。 */
14
+ export function isReleaseTooFresh(publishedAt: string | null | undefined, now: number): boolean {
15
+ if (typeof publishedAt !== 'string' || publishedAt === '') return false
16
+ const ms = Date.parse(publishedAt)
17
+ if (Number.isNaN(ms)) return false
18
+ return now - ms < MIN_RELEASE_AGE_MS
19
+ }
@@ -3,9 +3,8 @@
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
6
- * URL builders: catalog detail pages, plugin site links and the pre-filled
7
- * GitHub issue URL used by the failure modal. Pure functions — the issue
8
- * URL stays within GitHub's 8192-byte request limit.
6
+ * URL 构造:目录详情页、插件官网链接,以及失败弹窗用的预填 GitHub Issue 链接。
7
+ * 均为纯函数,保证 issue URL 不超过 GitHub 8192 字节的请求上限。
9
8
  */
10
9
  import type { EnvInfo, HubPlugin, LocaleId } from '../types.ts'
11
10
  import { GITHUB_URL, HUB_REPO, PLUGIN_VERSION, SITE_URL } from './constants.ts'
@@ -79,13 +78,13 @@ function reasonTitleOf(kind: FailureKind, action: FailureAction): string {
79
78
  * 错误核心摘要(完整日志太长,塞进 URL 会被 GitHub 以「request URL too long」拒绝,
80
79
  * 故只收集重点)。错误弹窗、失败记录共用此逻辑。
81
80
  * env 取不到时为 null,链接照常生成、只是少环境段。
82
- * attempts(尝试过的安装方式,npm 反查 + 实际执行命令):作者看到我们查过/试过的命令,
81
+ * attempts(尝试过的安装方式,实际执行过的命令):作者看到我们试过的命令,
83
82
  * 组织 scope 与 GitHub 用户名不一致时也能直接指认正确的 npm 包名。
84
83
  */
85
84
  export function pluginIssueUrl(repo: string, message: string, env?: EnvInfo | null, command?: string, attempts?: string[], action: FailureAction = 'install'): string {
86
85
  // 原因判定:按失败类型归类,便于作者判断问题是否出在插件侧。
87
- // [packaging](预检/装后校验拦截)单独列:无论走 npm 还是 git 通道,都是分发物不完整
88
- // (package.json 声明的文件在发布物里缺失),作者照此补齐即可。
86
+ // [packaging](装后校验拦截)单独列:分发物不完整(package.json 声明的文件
87
+ // 在发布物里缺失),作者照此补齐即可。
89
88
  const kind = classifyFailure(message)
90
89
  const executed = executedCommandOf(command, attempts)
91
90
  // 动作词:标题与正文的「安装/更新/卸载」统一取自这里
@@ -93,7 +92,7 @@ export function pluginIssueUrl(repo: string, message: string, env?: EnvInfo | nu
93
92
  // 标题 = 错误原因(非动作词),让 issue 列表一眼可分流
94
93
  const title = `[dsh-plugin.org | dsh-plugin-hub] ${reasonTitleOf(kind, action)}: ${repo}`
95
94
  const reason = /\[packaging\]/i.test(message)
96
- ? 'plugin distribution is incomplete — a file the package needs (its entry from main / exports, an entry a bundled patch mounts, or the patch declared by dsh.bundle.patch) is missing from the published package (github tarball or npm package); please commit build output or publish a complete package'
95
+ ? 'plugin distribution is incomplete — a file the package needs (its entry from main / exports, an entry a bundled patch mounts, or the patch declared by dsh.bundle.patch) is missing from the published npm package; please publish a complete package'
97
96
  : /\[registry-missing\]/i.test(message)
98
97
  // 服务端用官方 registry 复核过:缺的包在官方源上也不存在,且不是用户 profile 的残留依赖
99
98
  ? 'a dependency declared by the plugin is not published on the public npm registry (missing, removed, or never released); please publish or correct that dependency, or ship it with the plugin'
@@ -131,17 +130,17 @@ export function pluginIssueUrl(repo: string, message: string, env?: EnvInfo | nu
131
130
  `本错误信息由 [dsh-plugin-hub](${GITHUB_URL}) 插件市场的安装程序自动生成,随本次${zhWord}失败一并提交。`,
132
131
  `- 实际执行的${zhWord}命令:${executed ? `\`${executed}\`` : 'unknown (no executed command recorded)'}`,
133
132
  `- 执行结果:${zhWord}失败,未能${zhWord}该插件。`,
134
- // 尝试过的通道(npm 反查 + 实际执行命令,按先后顺序):作者据此指认正确的 npm 包名。
133
+ // 尝试过的安装方式(实际执行过的命令,按先后顺序):作者据此指认正确的 npm 包名。
135
134
  // 不写「安装」:卸载失败也走这条正文。
136
135
  ...(attempts && attempts.length > 0
137
136
  ? ['', '## Attempted channels(已尝试的方式)', ...attempts.map((a) => `- ${a}`)]
138
137
  : []),
139
- // [packaging] 场景:说明三种官方安装通道(npm 包 / git 直装 / 预构建 release .tgz),当前插件在对应通道下分发不完整
138
+ // [packaging] 场景:说明当前插件的 npm 分发物不完整
140
139
  ...(/\[packaging\]/i.test(message)
141
140
  ? [
142
141
  '',
143
142
  '## 安装方式说明',
144
- 'DSH 插件支持三种官方安装通道:`dsh plugin add <npm-package>`(npm 分发,需发布完整构建产物)、`dsh plugin add git+https://github.com/owner/repo.git`(Git 直装,仓库需提交构建产物),以及预构建 release 包 `dsh plugin add https://github.com/owner/repo/releases/download/<tag>/<asset>.tgz`(仓库不便提交构建产物时的推荐方式)。注意:pnpm ≥10 默认拦截依赖构建脚本,Git 直装不会执行包内 `prepare`,请勿依赖它生成产物。当前插件的分发物缺少它需要的文件(`main` / `exports` 声明的入口、补丁按本包名 mount 的入口,或 `dsh.bundle.patch` 声明的补丁),请提交构建产物、改用 release `.tgz` 直链,或发布完整的 npm 包。',
143
+ '当前插件的 npm 分发物缺少它需要的文件(`main` / `exports` 声明的入口、补丁按本包名 mount 的入口,或 `dsh.bundle.patch` 声明的补丁)。请发布包含完整构建产物的 npm 包后重试。',
145
144
  ]
146
145
  : []),
147
146
  '',
@@ -194,14 +193,14 @@ export function pluginIssueUrl(repo: string, message: string, env?: EnvInfo | nu
194
193
  }
195
194
 
196
195
  /**
197
- * 插件中心自身仓库的预填 Issue 链接:用于「非插件侧」失败(无法识别的未知原因、
198
- * 以及 pnpm allowBuilds 拦截这类不再归到插件作者的情形)。正文带上目标插件与完整
196
+ * 插件中心自身仓库的预填 Issue 链接:用于「非插件侧」失败(无法识别的未知原因,
197
+ * 以及判定由插件中心自己产出、可能误报时的次要反馈入口)。正文带上目标插件与完整
199
198
  * 错误摘要,供插件中心维护者分诊 —— 不再把环境问题/插件中心自身问题误投给插件作者。
200
199
  */
201
200
  export type HubIssueVariant =
202
- /** 缺省:安装器主动把失败送到插件中心分诊(未分类兜底 / allowBuilds 门禁) */
201
+ /** 缺省:安装器主动把失败送到插件中心分诊(未分类兜底) */
203
202
  | 'triage'
204
- /** 从「插件打包问题」分支的次要入口提交:该判定由插件中心自己产出,用户怀疑是误报 */
203
+ /** 从判定由插件中心自己产出的分支(插件打包问题 / allowBuilds 拦截)的次要入口提交:用户怀疑是误报 */
205
204
  | 'misjudge'
206
205
 
207
206
  export function hubIssueUrl(
@@ -230,16 +229,17 @@ export function hubIssueUrl(
230
229
  `- Auto-classified cause: ${reasonTitleOf(kind, action)}`,
231
230
  `- Target plugin: \`${repo}\``,
232
231
  ...(code ? [`- Key error: \`${code}\``] : []),
233
- // 为什么投递到这里而非插件仓库:只有确有证据是插件自身构建失败(pluginPrepare)才投插件仓库
232
+ // 为什么投递到这里而非插件仓库:只有确有证据是插件自身构建/分发失败(pluginPrepare、
233
+ // 或自动放行 allowBuilds 重试后仍失败的构建拦截)才投插件仓库
234
234
  ...(variant === 'misjudge'
235
- // 这条入口的判据([packaging] 预检/装后校验、prepare 摘要)是插件中心自己产出的,
236
- // 不是 pnpm 原文 —— 一旦我们自己误报,用户就被送去作者仓库、我们永远收不到反馈。
235
+ // 这些分支的判据由插件中心自己产出、或掺了我们的动作(自动放行 allowBuilds)——
236
+ // 一旦我们自己误报,用户就被送去作者仓库、我们永远收不到反馈。
237
237
  // 正文写清它来自哪条分支、判据是谁给的,维护者才不会把它当成未分类兜底。
238
238
  ? [
239
- '- Filed from: the plugin-prepare path (which normally routes to the plugin repo) — filed here because that verdict may be our own misjudgment.',
240
- '- Source of that verdict: produced by dsh-plugin-hub\'s own pre/post-install check, not by pnpm output.',
239
+ '- Filed from: a branch that normally routes to the plugin repo — filed here because that verdict may be our own misjudgment.',
240
+ '- Source of that verdict: produced by dsh-plugin-hub\'s own check or its auto-allow retry, not by plain pnpm output.',
241
241
  ]
242
- : ['- Why filed here: the installer only routes a failure to the plugin repo when there is positive evidence the plugin build itself failed (pluginPrepare). This failure does not match that, so it is routed here for triage.']),
242
+ : ['- Why filed here: the installer only routes a failure to the plugin repo when the cause is demonstrably the plugin\'s own build/distribution (pluginPrepare, or build scripts pnpm blocks that even an auto-allow retry cannot get past). This failure does not match that, so it is routed here for triage.']),
243
243
  '',
244
244
  `## [DSH Plugin 插件市场](${SITE_URL}) · ${zhWord} Plugin 失败错误信息`,
245
245
  `本错误信息由 [dsh-plugin-hub](${GITHUB_URL}) 插件市场的安装程序自动生成,随本次${zhWord}失败一并提交。`,
@@ -2,11 +2,9 @@
2
2
  * Website: https://dsh-plugin.org
3
3
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
4
4
  *
5
- * Custom install view: a standalone top-level section for manually
6
- * installing npm packages or GitHub sources outside the catalog. The two
7
- * installer cards (npm / GitHub) stack one per line; each card = channel
8
- * label + example hint above the input + input row (input + install button)
9
- * + optional format-error note. Colors resolve through the --hub-* tokens.
5
+ * 自定义安装视图:独立一级板块,用于手动安装目录之外的 npm 包。安装卡片逐行堆叠:
6
+ * 渠道标签 + 输入框上方的示例提示 + 输入行(输入框 + 安装按钮)+ 可选的格式错误提示。
7
+ * 颜色经 --hub-* 变量解析。
10
8
  */
11
9
 
12
10
  .root {
@@ -84,31 +82,6 @@
84
82
  border-color: var(--hub-brand-border-soft);
85
83
  }
86
84
 
87
- /* 命令卡片标题旁的「一键插入更新命令」:右对齐小号描边按钮,弱化但可点,hover 提亮品牌色 */
88
- .installInsertBtn {
89
- flex-shrink: 0;
90
- margin-left: auto;
91
- font-family: inherit;
92
- font-size: 11px;
93
- line-height: 16px;
94
- font-weight: 500;
95
- color: var(--hub-text-secondary);
96
- background: transparent;
97
- border: 1px solid var(--hub-border-2);
98
- border-radius: 6px;
99
- padding: 2px 9px;
100
- cursor: pointer;
101
- white-space: nowrap;
102
- user-select: none;
103
- transition: color 120ms ease, border-color 120ms ease, background 120ms ease;
104
- }
105
-
106
- .installInsertBtn:hover {
107
- color: var(--hub-brand);
108
- border-color: var(--hub-brand-border-soft);
109
- background: var(--hub-brand-tint);
110
- }
111
-
112
85
  /* 文本框上方的示例:灰字小字,告诉用户该通道的推荐写法 */
113
86
  .installExample {
114
87
  font-size: 11px;
@@ -2,11 +2,9 @@
2
2
  * Website: https://dsh-plugin.org
3
3
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
4
4
  *
5
- * System diagnostics list: every connectivity probe is one row — name,
6
- * target address, latency and a live status badge (200 OK / Unreachable /
7
- * Checking…). The list runs automatically when the page opens; each row is
8
- * a button — click to re-probe just that channel with a visible
9
- * running→result feedback loop. Colors resolve through the --hub-* tokens. */
5
+ * 系统诊断列表:每个连通性探测一行 —— 名称、目标地址、延迟与实时状态徽标
6
+ * (200 OK / 不可达 / 检测中…)。页面打开时自动跑一轮;每行都是按钮 ——
7
+ * 点击只重测该通道,形成「运行→结果」的可见反馈闭环。颜色经 --hub-* 变量解析。 */
10
8
 
11
9
  .panel {
12
10
  display: flex;
@@ -2,10 +2,8 @@
2
2
  * Website: https://dsh-plugin.org
3
3
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
4
4
  *
5
- * Button-style dropdown: the trigger reads as a compact toolbar button
6
- * (hairline border + subtle fill + current value + chevron), the panel is
7
- * a floating menu with hover fills and a brand-highlighted active option.
8
- * Colors resolve through the --hub-* design tokens. */
5
+ * 按钮式下拉框:触发器呈现为紧凑工具栏按钮(细边框 + 淡填充 + 当前值 + 箭头),
6
+ * 面板是浮层菜单,选项有 hover 填充、激活项品牌高亮。颜色经 --hub-* 设计变量解析。 */
9
7
 
10
8
  .dropdown {
11
9
  position: relative;
@@ -2,9 +2,8 @@
2
2
  * Website: https://dsh-plugin.org
3
3
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
4
4
  *
5
- * Plugin Hub section header: brand row, tagline, ad banner, search input,
6
- * filter toolbar (installed / not installed, failures entry) and category
7
- * tabs. Colors resolve through the --hub-* design tokens. */
5
+ * 插件中心板块头部:品牌行、副标题、广告条、搜索框、筛选工具栏
6
+ * (已安装/未安装、失败入口)与分类 tab。颜色经 --hub-* 设计变量解析。 */
8
7
 
9
8
  .root {
10
9
  height: 100%;
@@ -15,7 +14,7 @@
15
14
  color: var(--hub-text-primary);
16
15
  }
17
16
 
18
- /* —— header —— */
17
+ /* —— 头部 —— */
19
18
 
20
19
  .header {
21
20
  display: flex;
@@ -241,7 +240,7 @@
241
240
  text-align: center;
242
241
  }
243
242
 
244
- /* —— result count —— */
243
+ /* —— 筛选结果数 —— */
245
244
 
246
245
  /* 筛选结果数:弱化灰字信息「结果 N 条插件」,位于工具栏行最左侧 */
247
246
  .resultSeg {
@@ -324,7 +323,7 @@
324
323
  background: var(--hub-brand);
325
324
  }
326
325
 
327
- /* —— ad banner —— */
326
+ /* —— 广告条 —— */
328
327
 
329
328
  /* 广告条:固定蓝紫渐变 + 白字,不随主题变量漂移,两种主题下都醒目。
330
329
  与 toast 相同思路:固定颜色保证可读性。 */
@@ -411,7 +410,7 @@
411
410
  border-color: var(--hub-brand);
412
411
  }
413
412
 
414
- /* —— category chips —— */
413
+ /* —— 分类 chips —— */
415
414
 
416
415
  .tabs {
417
416
  display: flex;
@@ -2,13 +2,10 @@
2
2
  * Website: https://dsh-plugin.org
3
3
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
4
4
  *
5
- * Installed view: searchable, sortable list of installed plugins mixing
6
- * catalog plugins (full metadata) and custom installs (runtime info only,
7
- * amber custom badge). The toolbar holds a search box plus two segmented
8
- * radio-button groups (source filter + sort). Each row has three tidy
9
- * lines: title line (running-status dot + name + version/update badges +
10
- * fixed action buttons on the right), meta line (repo · category), and a
11
- * one-line description. Colors resolve through the --hub-* tokens. */
5
+ * 已安装视图:可搜索、可排序的已安装插件清单,混排目录插件(完整元数据)与自定义安装
6
+ * (仅运行时信息,琥珀色自定义徽标)。工具栏含搜索框与两组分段单选(来源筛选 + 排序)。
7
+ * 每行三行整洁内容:标题行(运行状态点 + 名称 + 版本/更新徽标 + 右侧固定操作按钮)、
8
+ * 元信息行(仓库 · 分类)与单行描述。颜色经 --hub-* 变量解析。 */
12
9
 
13
10
  .root {
14
11
  display: flex;
@@ -2,9 +2,8 @@
2
2
  * Website: https://dsh-plugin.org
3
3
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
4
4
  *
5
- * Plugin Hub catalog list: the scrollable body, plugin cards (name, badges,
6
- * description, topics, stats, actions), loading / empty states and the
7
- * browse-all footer. Colors resolve through the --hub-* design tokens. */
5
+ * 插件中心目录列表:可滚动主体、插件卡片(名称、徽标、描述、标签、统计、动作)、
6
+ * 加载/空态与「浏览全部」页脚。颜色经 --hub-* 设计变量解析。 */
8
7
 
9
8
  .body {
10
9
  flex: 1;
@@ -273,7 +272,7 @@ body[data-ds-dark-theme] .uninstallBtn:hover {
273
272
  user-select: none;
274
273
  }
275
274
 
276
- /* —— states —— */
275
+ /* —— 状态 —— */
277
276
 
278
277
  .state {
279
278
  flex: 1;
@@ -341,7 +340,7 @@ body[data-ds-dark-theme] .uninstallBtn:hover {
341
340
  background: var(--hub-brand-hover);
342
341
  }
343
342
 
344
- /* —— footer —— */
343
+ /* —— 页脚 —— */
345
344
 
346
345
  .footer {
347
346
  flex-shrink: 0;
@@ -2,9 +2,8 @@
2
2
  * Website: https://dsh-plugin.org
3
3
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
4
4
  *
5
- * System logs: one row per entry — time, level badge (OK green / FAIL red /
6
- * WARN amber / INFO grey), event code and the message. Header holds the
7
- * refresh / copy-all / export (.log download) actions. */
5
+ * 系统日志:每条一行 —— 时间、级别徽标(OK 绿 / FAIL 红 / WARN 琥珀 / INFO 灰)、
6
+ * 事件码与消息。头部含 刷新 / 全部复制 / 导出(.log 下载)动作。 */
8
7
 
9
8
  .panel {
10
9
  display: flex;
@@ -2,11 +2,10 @@
2
2
  * Website: https://dsh-plugin.org
3
3
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
4
4
  *
5
- * Plugin Hub dialogs and overlays: install / uninstall modals, error dialog,
6
- * failure-record dialog, task-queue dialog, progress strip, inline progress
7
- * and the global toast. Colors resolve through the --hub-* design tokens. */
5
+ * 插件中心弹窗与遮罩:安装/卸载弹窗、错误弹窗、失败记录弹窗、任务队列弹窗、进度条、
6
+ * 内联进度与全局 Toast。颜色经 --hub-* 设计变量解析。 */
8
7
 
9
- /* —— modal base —— */
8
+ /* —— 弹窗基座 —— */
10
9
 
11
10
  .overlay {
12
11
  position: fixed;
@@ -364,7 +363,7 @@
364
363
  overflow-y: auto;
365
364
  }
366
365
 
367
- /* —— toast —— */
366
+ /* —— Toast 提示条 —— */
368
367
 
369
368
  /* 复制成功提示条:窗口正中央浮出,无遮罩无图标。采用反色浮层——亮色模式
370
369
  黑底白字、暗色模式白底深字(复用主按钮 token:primary-fill 与
@@ -2,10 +2,8 @@
2
2
  * Website: https://dsh-plugin.org
3
3
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
4
4
  *
5
- * Primary navigation (section tabs): an underline tab bar under the header
6
- * switching between Market / Installed / Settings. The active tab carries a
7
- * brand-colored underline; the bar itself draws a hairline bottom border so
8
- * the selected state reads clearly without extra chrome. */
5
+ * 一级导航(section tabs):头部下方的下划线 tab 栏,在 市场 / 已安装 / 设置 间切换。
6
+ * 激活 tab 带品牌色下划线;栏本身画一条细底边,让选中态清晰、不多余装饰。 */
9
7
 
10
8
  .root {
11
9
  display: flex;