dsh-plugin 1.3.10 → 1.4.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.
@@ -78,7 +78,7 @@ function startHost() {
78
78
  const PROFILE_RE = /^[A-Za-z0-9_-]+$/;
79
79
  const BODY_LIMIT_BYTES = 4 * 1024;
80
80
  const COMMAND_TIMEOUT_MS = 5 * 60 * 1000;
81
- /** 目录/统计数据代理的本地缓存时长:1 小时内重复打开插件中心直接读盘,
81
+ /** 目录/统计数据代理的本地缓存时长:1 小时内重复打开插件市场直接读盘,
82
82
  * 不重复走 curl 拉远程 —— 重启宿主后首次打开同样秒开(缓存跨重启存活)。 */
83
83
  const CATALOG_CACHE_TTL_MS = 60 * 60 * 1000;
84
84
  function catalogCacheFile(profile, key) {
@@ -121,8 +121,13 @@ export function readInstalled(profile) {
121
121
  return {};
122
122
  }
123
123
  }
124
- /** 宿主 dsh CLI 版本:从入口(process.argv[1])向上找最近的 dsh/harness 相关 package.json;找不到返回 null。 */
124
+ /** 宿主 dsh CLI 版本:优先直接跑 `dsh --version`(与 pnpm/npm/git 同款同步探测,1500ms 超时兜底,
125
+ * 绝不挂起)—— 原先从 process.argv[1] 向上找 package.json 猜版本在部分宿主加载方式下取不到,
126
+ * 导致 issue 环境快照 DSH: unknown;拿不到再退回入口 package.json 查找(dsh 不在 PATH 时兜底)。 */
125
127
  function hostDshVersion() {
128
+ const viaCli = toolVersion('dsh', ['--version']);
129
+ if (viaCli)
130
+ return viaCli;
126
131
  const entry = process.argv[1];
127
132
  if (entry === undefined)
128
133
  return null;
@@ -512,8 +517,8 @@ export function mountPluginHubRoutes(webServer, profile, loader) {
512
517
  { key: 'npm', url: `${registry}/dsh-plugin`, display: settings.npmRegistry !== '' ? registry : '', cmd: `npm view dsh-plugin version --registry ${registry}` },
513
518
  // GitHub 行用真实 git 克隆握手(git ls-remote)打 dshplugin/hello-dsh 小仓库:
514
519
  // 「网页能打开」和「git 能克隆」是两码事,握手成功才算通道通;小仓库秒级完成,不打 17MB 的 dsh-plugin-hub。
515
- { key: 'github', url: 'https://github.com/dshplugin/hello-dsh', display: 'github.com/dshplugin/hello-dsh', cmd: 'git ls-remote https://github.com/dshplugin/hello-dsh', git: true },
516
- { key: 'catalog', url: 'https://api.dsh-plugin.org/stats.json', display: 'api.dsh-plugin.org', cmd: 'curl -s https://api.dsh-plugin.org/stats.json' },
520
+ { key: 'github', url: 'https://github.com/dshplugin/hello-dsh', display: 'github.com', cmd: 'git ls-remote https://github.com/dshplugin/hello-dsh', git: true },
521
+ { key: 'catalog', url: 'https://api.dsh-plugin.org/stats.json', display: '', cmd: 'curl -s https://api.dsh-plugin.org/stats.json' },
517
522
  ];
518
523
  // 配置了 HTTP 代理:追加一行代理诊断 —— 用该代理打 github.com(安装通道真实访问的地址),
519
524
  // 验证「代理能不能把请求带出去」。与安装同口径:curl 子进程注入该代理 env。
@@ -615,7 +620,7 @@ export function mountPluginHubRoutes(webServer, profile, loader) {
615
620
  const target = isStats
616
621
  ? 'https://api.dsh-plugin.org/stats.json'
617
622
  : `https://api.dsh-plugin.org/plugins.${lang}.json`;
618
- // 1 小时本地缓存:命中直接返回(跨重启生效,插件中心秒开),未命中才走 curl 拉远程
623
+ // 1 小时本地缓存:命中直接返回(跨重启生效,插件市场秒开),未命中才走 curl 拉远程
619
624
  const cacheFile = catalogCacheFile(profile, isStats ? 'stats' : `plugins-${lang}`);
620
625
  const cached = readCatalogCache(cacheFile);
621
626
  if (cached !== null) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin",
3
- "version": "1.3.10",
3
+ "version": "1.4.0",
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",
@@ -46,7 +46,7 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
46
46
  /** 设置状态:服务端 hub-settings.json 持久化,本地乐观更新即时生效 */
47
47
  const { settings: hubSettings, ready: settingsReady, update: updateSettings, reset: resetSettings } = useSettings()
48
48
 
49
- /** 一级导航:插件中心 / 已安装 / 自定义安装 / 设置 */
49
+ /** 一级导航:插件市场 / 已安装 / 自定义安装 / 设置 */
50
50
  const [view, setView] = useState<SectionView>('market')
51
51
  /** 外部跳转设置分组的一次性信号(错误弹窗「去系统诊断」→ 设置 → 系统诊断):
52
52
  * 传给 SettingsView 消费后即清空,避免下次进设置被强制带跳 */
@@ -195,7 +195,7 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
195
195
  }
196
196
 
197
197
  // 启动更新策略:目录数据与设置都就绪后触发一次 —— 开启「启动时检查更新」→ 发现可更新插件
198
- // 即写入通知中心(每条一条,点击可去更新),toast 同步提示已入通知。
198
+ // 即写入通知中心(每条一条,点击可去更新),不弹窗打扰,由通知中心红点提示。
199
199
  // 绝不自动安装:是否更新完全由用户决定。
200
200
  // ref 守卫保证只跑一次,后续目录刷新/设置变化不会重复打扰。
201
201
  const startupCheckedRef = useRef(false)
@@ -204,8 +204,7 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
204
204
  if (!settingsReady || catalog.plugins === null || catalog.failed) return
205
205
  startupCheckedRef.current = true
206
206
  if (!hubSettings.checkUpdatesOnStart) return
207
- const added = syncUpdateNotices()
208
- if (added > 0) setToast({ id: Date.now(), kind: 'updates', n: added })
207
+ syncUpdateNotices()
209
208
  }, [settingsReady, hubSettings, catalog.plugins, catalog.failed, catalog.installedItems, queue])
210
209
 
211
210
  // 信任/卸载/更新说明弹窗打开时按 Esc 关闭;任务进入后台队列后可随时关闭(任务继续)
@@ -271,17 +271,32 @@ export function NotificationsModal({ records, tasks, pendingRestarts, t, env, on
271
271
  // 本机 DeepSeek Harness 未正确安装或 dsh 不在 PATH,不是插件问题 —— 直接给环境修复指引,不引导提 Issue
272
272
  return h('div', { className: styles.failPrepareHint }, t('failDshMissingHint'))
273
273
  }
274
+ if (kind === 'gitMissing') {
275
+ // 找不到 git 命令(Win「'git' is not recognized」/ POSIX「git: command not found」/ spawn ENOENT):
276
+ // 本机 Git 未安装或不在 PATH,不是插件问题 —— 直接给环境修复指引,不引导提 Issue
277
+ return h('div', { className: styles.failPrepareHint }, t('failGitMissingHint'))
278
+ }
274
279
  if (kind === 'pnpmMissing') {
275
280
  // 找到了 dsh 但缺 pnpm(dsh 用它管理 profile 插件;dsh 报 pnpm not found on PATH):
276
281
  // 本机缺 pnpm,不是插件问题 —— 给安装指引,不引导提 Issue
277
282
  return h('div', { className: styles.failPrepareHint }, t('failPnpmMissingHint'))
278
283
  }
284
+ if (kind === 'npmMissing') {
285
+ // 全局 npm 安装通道 spawn 的 npm 找不到:本机 npm 未安装或不在 PATH ——
286
+ // 给安装指引,不引导提 Issue
287
+ return h('div', { className: styles.failPrepareHint }, t('failNpmMissingHint'))
288
+ }
279
289
  if (kind === 'pnpmStore') {
280
290
  // pnpm 存在但 store 大版本不匹配(ERR_PNPM_UNEXPECTED_STORE):profile 目录旧依赖是
281
291
  // 另一大版本 pnpm 装的,当前 pnpm 不认,任何插件装进该 profile 都会失败 ——
282
292
  // 给清理重建指引,不引导提 Issue
283
293
  return h('div', { className: styles.failPrepareHint }, t('failPnpmStoreHint'))
284
294
  }
295
+ if (kind === 'pnpmPolicy') {
296
+ // pnpm 11 供应链安全策略拦截(minimumReleaseAge 拒收发布太新的包 / untrusted origin):
297
+ // 本机 pnpm 策略不放行,装任何「刚发布/新来源」插件都撞墙 —— 给豁免指引,不引导提 Issue
298
+ return h('div', { className: styles.failPrepareHint }, t('failPnpmPolicyHint'))
299
+ }
285
300
  if (kind === 'network') {
286
301
  // 安装前预检 / 安装日志里的连接失败([network] / git fetch 超时 / DNS / TLS):
287
302
  // 是本机网络不通或代理有问题,不是插件问题 —— 提示检查网络 + 直达「系统诊断」,
@@ -391,15 +391,27 @@ export function ErrorModal({ message, repo, kind, command, attempts, t, env, onC
391
391
  // 找不到 dsh 命令(Win「不是内部或外部命令」/ POSIX「command not found」/ spawn ENOENT):
392
392
  // 本机 DeepSeek Harness 未正确安装或 dsh 不在 PATH,不是插件问题 —— 直接给环境修复指引,不引导提 Issue
393
393
  ? h('div', { className: styles.failPrepareHint }, t('failDshMissingHint'))
394
+ : failureKind === 'gitMissing'
395
+ // 找不到 git 命令(Win「'git' is not recognized」/ POSIX「git: command not found」/ spawn ENOENT):
396
+ // 本机 Git 未安装或不在 PATH,不是插件问题 —— 直接给环境修复指引,不引导提 Issue
397
+ ? h('div', { className: styles.failPrepareHint }, t('failGitMissingHint'))
394
398
  : failureKind === 'pnpmMissing'
395
399
  // 找到了 dsh 但缺 pnpm(dsh 用它管理 profile 插件;dsh 报 pnpm not found on PATH):
396
400
  // 本机缺 pnpm,不是插件问题 —— 给安装指引,不引导提 Issue
397
401
  ? h('div', { className: styles.failPrepareHint }, t('failPnpmMissingHint'))
402
+ : failureKind === 'npmMissing'
403
+ // 全局 npm 安装通道 spawn 的 npm 找不到(Win「'npm' is not recognized」/ POSIX
404
+ // 「npm: command not found」/ spawn ENOENT):本机 npm 未安装或不在 PATH —— 给安装指引,不引导提 Issue
405
+ ? h('div', { className: styles.failPrepareHint }, t('failNpmMissingHint'))
398
406
  : failureKind === 'pnpmStore'
399
407
  // pnpm 存在但 store 大版本不匹配(ERR_PNPM_UNEXPECTED_STORE):profile 目录旧依赖是
400
408
  // 另一大版本 pnpm 装的,当前 pnpm 不认,任何插件装进该 profile 都会失败 ——
401
409
  // 给清理重建指引,不引导提 Issue
402
410
  ? h('div', { className: styles.failPrepareHint }, t('failPnpmStoreHint'))
411
+ : failureKind === 'pnpmPolicy'
412
+ // pnpm 11 供应链安全策略拦截(minimumReleaseAge 拒收发布太新的包 / untrusted origin):
413
+ // 本机 pnpm 策略不放行,装任何「刚发布/新来源」插件都撞墙 —— 给豁免指引,不引导提 Issue
414
+ ? h('div', { className: styles.failPrepareHint }, t('failPnpmPolicyHint'))
403
415
  : failureKind === 'network'
404
416
  // 安装前预检 / 安装日志里的连接失败([network] / git fetch 超时 / DNS / TLS):
405
417
  // 是本机网络不通或代理有问题,不是插件问题 —— 提示检查网络 + 直达「系统诊断」,
@@ -452,8 +464,7 @@ export function Toast({ toast, t }: { toast: ToastState; t: Translate }) {
452
464
  : toast.kind === 'fail' ? t('installFail')
453
465
  : toast.kind === 'removed' ? t('uninstallDone')
454
466
  : toast.kind === 'revealFail' ? t('openFolderFail')
455
- : toast.kind === 'updates' ? t('updatesFound', { n: toast.n ?? 0 })
456
- : t('uninstallFail')
467
+ : t('uninstallFail')
457
468
  const fail = toast.kind === 'fail' || toast.kind === 'removeFail' || toast.kind === 'revealFail'
458
469
  return h('div', {
459
470
  key: toast.id,
@@ -179,7 +179,7 @@ export function SortArrowIcon({ up }: { up: boolean }) {
179
179
  }))
180
180
  }
181
181
 
182
- /** 插件中心图标:店面货架(stroke 继承 currentColor),一级导航「插件中心」tab 用。 */
182
+ /** 插件市场图标:店面货架(stroke 继承 currentColor),一级导航「插件市场」tab 用。 */
183
183
  export function MarketIcon() {
184
184
  return h('svg', {
185
185
  viewBox: '0 0 24 24',
@@ -42,8 +42,8 @@ interface RowState {
42
42
  const INITIAL_ROWS: RowState[] = [
43
43
  // npm 行 display 初始置空:设置里未配置镜像时显示「未配置(跟随本机)」,探测结果回来再用服务端 display 覆盖
44
44
  { key: 'npm', display: '', nameKey: 'diagNpm', status: 'idle', ms: null, statusCode: null },
45
- { key: 'github', display: 'github.com/dshplugin/hello-dsh', nameKey: 'diagGithub', status: 'idle', ms: null, statusCode: null },
46
- { key: 'catalog', display: 'api.dsh-plugin.org', nameKey: 'diagCatalog', status: 'idle', ms: null, statusCode: null },
45
+ { key: 'github', display: 'github.com', nameKey: 'diagGithub', status: 'idle', ms: null, statusCode: null },
46
+ { key: 'catalog', display: '', nameKey: 'diagCatalog', status: 'idle', ms: null, statusCode: null },
47
47
  ]
48
48
 
49
49
  export function DiagnosticsView({ t, env, proxy, onCopy }: {
@@ -172,8 +172,10 @@ export function DiagnosticsView({ t, env, proxy, onCopy }: {
172
172
  title: t('diagRecheck'),
173
173
  },
174
174
  h('span', { className: styles.name }, t(r.nameKey)),
175
- // npm 行未配置镜像:display 为空 → 显示「未配置(跟随本机)」,不误导成官方源
176
- h('span', { className: styles.display }, r.display !== '' ? r.display : t('diagNpmUnset')),
175
+ // npm 行未配置镜像:display 为空 → 显示「未配置(跟随本机)」,不误导成官方源;
176
+ // 目录站点行不暴露域名/API 地址,固定显示「插件市场」这一友好称呼
177
+ h('span', { className: styles.display },
178
+ r.key === 'catalog' ? t('diagCatalogTarget') : (r.display !== '' ? r.display : t('diagNpmUnset'))),
177
179
  h('span', { className: styles.meta }, r.status === 'ok' && r.ms !== null ? `${r.ms} ms` : ''),
178
180
  badge,
179
181
  )
@@ -111,7 +111,7 @@ function InstalledRow({ item, t, langKey, canReveal, revealLabel, onOpenDetail,
111
111
  ? h('span', { className: styles.updateBadge, title: t('updateAvailableHint') }, t('updateAvailable'))
112
112
  : null,
113
113
  ),
114
- // 来源标签列(行尾、操作按钮前,右对齐固定):目录插件 →「插件中心」(品牌色),
114
+ // 来源标签列(行尾、操作按钮前,右对齐固定):目录插件 →「插件市场」(品牌色),
115
115
  // 命令行安装 →「手动安装」(琥珀色);两词 4 字等宽,所有条目标签同列对齐
116
116
  h('span', {
117
117
  className: isCustom
@@ -200,11 +200,25 @@ export function useCatalog(lang: LocaleId) {
200
200
  * 以 Worker 版本控制中心返回的「最新版本」为准,与当前运行的版本号比对——
201
201
  * 构建时注入的 PLUGIN_VERSION 是运行 bundle 的真实版本;测试/异常场景缺失时
202
202
  * 回退到安装时记录的版本。版本号不等即新版(版本号只在发版时变更,绝无降级场景)。
203
+ *
204
+ * 另加 pnpm 供应链安全门槛:新版本发布还不满 24 小时(+30 分钟缓冲)时,pnpm 会静默
205
+ * 回退到旧版本,此时若提示「可更新」用户点了也更新不到。publishedAt 由接口中心按 npm
206
+ * registry 真实发布时间下发(同源),据此判断最准。
203
207
  */
204
208
  const hubHasUpdate = (() => {
205
209
  if (!hubUpdateInfo) return false
206
210
  const current = PLUGIN_VERSION || versions[HUB_REPO]?.version || null
207
- return current !== null && hubUpdateInfo.version !== current
211
+ if (current === null || hubUpdateInfo.version === current) return false
212
+ const publishedAt = hubUpdateInfo.publishedAt
213
+ if (publishedAt) {
214
+ const publishedMs = Date.parse(publishedAt)
215
+ if (!Number.isNaN(publishedMs)) {
216
+ // 24 小时 + 30 分钟缓冲(防网络/CDN 边缘时间偏差)
217
+ const MIN_RELEASE_AGE_MS = 24 * 60 * 60 * 1000 + 30 * 60 * 1000
218
+ if (Date.now() - publishedMs < MIN_RELEASE_AGE_MS) return false
219
+ }
220
+ }
221
+ return true
208
222
  })()
209
223
 
210
224
  /** 当前分类下的插件(「全部」时为整个目录)。 */
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * Incremental list: renders only the first batch of a (potentially huge)
7
7
  * result set and appends the next batch whenever the end sentinel scrolls
8
- * into view, so the plugin center never mounts thousands of cards at once.
8
+ * into view, so the plugin market never mounts thousands of cards at once.
9
9
  * The items reference is the list identity — category / search / sort
10
10
  * changes produce a new array, which resets the window to the first batch.
11
11
  */
@@ -3,25 +3,29 @@
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
6
- * Language state for the section: follows the host (system) locale by
7
- * default, with an optional manual toggle that wins until the user changes
8
- * it back. Exposes the localized dictionary lookup `t()` plus the zh/en
9
- * language flag and the /zh/ URL path prefix.
6
+ * Language state for the section: the Hub shares the host (system) locale —
7
+ * the header language toggle writes the host locale preference via setLocale,
8
+ * so the host chrome (left sidebar, settings dialog) and the Hub panel switch
9
+ * together instead of drifting apart. Exposes the localized dictionary lookup
10
+ * `t()` plus the zh/en language flag and the /zh/ URL path prefix.
10
11
  */
11
- import { useState } from 'react'
12
+ import { useEffect, useState } from 'react'
12
13
  import type { LocaleId, SectionProps } from '../types.ts'
13
14
  import { en, zh } from '../locales.ts'
14
15
  import { langPathOf } from '../logic/urls.ts'
15
16
 
16
17
  export function useLanguage(locale: SectionProps['locale']) {
17
- /** 界面语言:默认跟随宿主(系统)语言;右上角按钮可手动切换,切换后以手动选择为准 */
18
- const [manualLang, setManualLang] = useState<LocaleId | null>(null)
19
- const lang: LocaleId = manualLang ?? locale.getSnapshot().active
18
+ // 界面语言 = 宿主(系统)语言:右上角按钮切换时直接写入宿主 locale 偏好,
19
+ // 宿主左侧菜单/设置弹窗与 Hub 面板一起切换,不存在 Hub 内部语言与宿主语言脱节的情况。
20
+ // subscribe 订阅宿主语言变化并触发重渲染,保证 Hub 界面随时跟随宿主。
21
+ const [, setRev] = useState(0)
22
+ useEffect(() => locale.subscribe(() => setRev((r) => r + 1)), [locale])
23
+ const lang: LocaleId = locale.getSnapshot().active
20
24
  const langKey: LocaleId = lang === 'en' ? 'en' : 'zh'
21
25
  // dsh-plugin.org keeps zh pages under the /zh/ prefix; en is the root.
22
26
  const langPath = langPathOf(lang)
23
27
 
24
- // 界面语言跟随宿主(系统)locale 自动切换;宿主的 t() 绑定宿主 locale,
28
+ // 界面语言跟随宿主(系统)locale;宿主的 t() 绑定宿主 locale,
25
29
  // 这里基于本地字典自建翻译函数,与宿主 locale 保持一致。
26
30
  // (settings.section 的导航 label 仍在 apply 里用宿主 t(),跟随宿主语言。)
27
31
  const dict: Record<string, string> = langKey === 'en' ? en : zh
@@ -35,6 +39,6 @@ export function useLanguage(locale: SectionProps['locale']) {
35
39
  langKey,
36
40
  langPath,
37
41
  t,
38
- toggleLang: () => setManualLang(lang === 'en' ? 'zh' : 'en'),
42
+ toggleLang: () => locale.setLocale(lang === 'en' ? 'zh' : 'en'),
39
43
  }
40
44
  }
@@ -6,13 +6,13 @@
6
6
  * zh/en dictionaries for the DSH Plugin Hub settings section.
7
7
  */
8
8
  export const zh = {
9
- nav: '插件中心',
9
+ nav: '插件市场',
10
10
  title: 'DSH Plugin Hub',
11
11
  // 收录/精选数量由组件从官网 /api/stats.json 实时拉取填充({total}/{verified}),不写死
12
- tagline: 'DSH Plugin 插件中心:面向 DeepSeek Harness 插件收录平台,人工审验、来源可溯,免费安装',
12
+ tagline: 'DSH Plugin 插件市场:面向 DeepSeek Harness 插件收录平台,人工审验、来源可溯,免费安装',
13
13
  adBadge: '推荐',
14
14
  // 紫色广告横幅:突出数量(显眼)+ 更新节奏,与 tagline 职责分开、不重复
15
- ad: 'DSH Plugin 插件中心:收录 {total} 款插件,{verified} 款人工精选验证,每日更新',
15
+ ad: 'DSH Plugin 插件市场:收录 {total} 款插件,{verified} 款人工精选验证,每日更新',
16
16
  search: '搜索插件名称、描述、标签…',
17
17
  all: '全部',
18
18
  sortStars: 'Star',
@@ -26,8 +26,8 @@ export const zh = {
26
26
  loading: '正在加载插件数据…',
27
27
  failed: '插件数据加载失败',
28
28
  // 目录拉不出来时的精准提示:按「是否配置了代理」区分,引导排查网络 / 代理
29
- failedDescNoProxy: '你的网络似乎不通,无法获取插件目录数据。请检查网络连接后重试;或点击右上角按钮在浏览器中打开插件中心。',
30
- failedDescProxy: '你的网络似乎不通,或设置里的 HTTP 代理地址不可达,无法获取插件目录数据。请检查代理设置与网络连接后重试;或点击右上角按钮在浏览器中打开插件中心。',
29
+ failedDescNoProxy: '你的网络似乎不通,无法获取插件目录数据。请检查网络连接后重试;或点击右上角按钮在浏览器中打开插件市场。',
30
+ failedDescProxy: '你的网络似乎不通,或设置里的 HTTP 代理地址不可达,无法获取插件目录数据。请检查代理设置与网络连接后重试;或点击右上角按钮在浏览器中打开插件市场。',
31
31
  retry: '重试',
32
32
  install: '安装',
33
33
  // AI 识别可能需要命令行辅助的插件(webInstallable=false):仍可尝试一键安装,失败时复制命令去 dsh 终端
@@ -35,8 +35,8 @@ export const zh = {
35
35
  failedCopyHint: '安装失败。请复制上方安装命令到 dsh 终端手动执行(原生依赖构建脚本可能需要人工放行,如 pnpm approve-builds)',
36
36
  toastCopied: '安装命令已复制,去 dsh 终端粘贴即可安装',
37
37
  installed: '已安装',
38
- // 一级导航:插件中心 / 已安装 / 自定义安装 / 设置
39
- viewMarket: '插件中心',
38
+ // 一级导航:插件市场 / 已安装 / 自定义安装 / 设置
39
+ viewMarket: '插件市场',
40
40
  viewInstalled: '已安装',
41
41
  viewCustom: '自定义安装',
42
42
  viewSettings: '设置',
@@ -76,8 +76,8 @@ export const zh = {
76
76
  customLabel: '自定义安装',
77
77
  installedSearch: '搜索已安装的插件…',
78
78
  installedFilterCatalog: '目录插件',
79
- hubInstall: '插件中心',
80
- hubInstallHint: '来自插件中心收录的目录插件',
79
+ hubInstall: '插件市场',
80
+ hubInstallHint: '来自插件市场收录的目录插件',
81
81
  manualInstall: '手动安装',
82
82
  manualInstallHint: '非目录收录,通过命令行手动安装',
83
83
  filterByLabel: '来源',
@@ -109,7 +109,7 @@ export const zh = {
109
109
  settingsUpdate: '更新设置',
110
110
  settingsUpdateDesc: '更新检查方式与安装走到的网络通道',
111
111
  settingsCheckOnStart: '启动时检查更新',
112
- settingsCheckOnStartDesc: '打开插件中心时检查已安装插件是否有新版,发现更新会提示',
112
+ settingsCheckOnStartDesc: '打开插件市场时检查已安装插件是否有新版,发现更新会提示',
113
113
  settingsProxy: 'HTTP 代理',
114
114
  settingsProxyDesc: 'npm / git / 目录数据请求统一走该代理;留空直连',
115
115
  // 代理连通性实时探测:输入停笔后提示检测结果(不通仍可保存,先填地址后开代理也合法)
@@ -192,6 +192,7 @@ export const zh = {
192
192
  diagNpmUnset: '未配置(跟随本机 npm 配置)',
193
193
  diagGithub: 'GitHub',
194
194
  diagCatalog: '目录站点',
195
+ diagCatalogTarget: '插件市场',
195
196
  diagProxy: 'HTTP 代理',
196
197
  diagOk: 'OK',
197
198
  diagChecking: '检测中…',
@@ -212,8 +213,7 @@ export const zh = {
212
213
  settingsResetRun: '恢复默认',
213
214
  settingsResetConfirm: '确认恢复',
214
215
  settingsResetConfirmDetail: '确定要恢复默认设置吗?更新策略、镜像源、代理与安全限制将全部回到出厂默认值。',
215
- // 启动更新策略提示
216
- updatesFound: '发现 {n} 个插件可更新,已加入通知中心',
216
+ // 更新通知(通知中心条目标题)
217
217
  updateNoticeTitle: '发现新版本',
218
218
  updateNoticeGo: '去更新',
219
219
  ignoreUpdateRun: '忽略本次更新',
@@ -312,8 +312,11 @@ export const zh = {
312
312
  // 「去系统诊断」直达按钮:网络不通提示下方的品牌实底按钮,跳到设置 → 系统诊断跑连通性检测
313
313
  failNetworkRunDiag: '去系统诊断检测网络',
314
314
  failDshMissingHint: '系统找不到 dsh 命令(dsh 未加入系统 PATH),无法调用安装器执行安装。请确认 DeepSeek Harness 已正确安装、dsh 已加入 PATH(或重新安装),然后重试。这不是插件本身的问题。',
315
+ failGitMissingHint: '系统找不到 git 命令(Git 未安装或未加入系统 PATH),无法从 GitHub 源安装插件。请先安装 Git(如 git-scm.com/downloads)或把 git 加入 PATH,重启 DSH 后重试。这不是插件本身的问题。',
315
316
  failPnpmMissingHint: '安装器找到了 dsh,但系统里没有 pnpm 命令(dsh 用它来管理 profile 插件)。请先安装 pnpm(如 npm install -g pnpm),确认 pnpm 已加入 PATH 后重试。这不是插件本身的问题。',
317
+ failNpmMissingHint: '系统找不到 npm 命令(npm 未安装或未加入系统 PATH),无法执行全局安装。请先安装 Node.js(自带 npm),确认 npm 已加入 PATH 后重试。这不是插件本身的问题。',
316
318
  failPnpmStoreHint: '安装器调用 pnpm 时报「store 位置不匹配」(ERR_PNPM_UNEXPECTED_STORE):你的 profile 目录里旧插件依赖是用另一个大版本的 pnpm 生成的,当前 pnpm 出于安全不认旧目录,导致任何插件装进该 profile 都会失败。请删除该 profile 目录下的 node_modules 与 pnpm-lock.yaml(可先到「系统日志」或 ~/.dsh/profiles/<profile>/hub.log 查看完整报错),再重新安装。这不是插件本身的问题。',
319
+ failPnpmPolicyHint: '安装器调用 pnpm 时被其供应链安全策略拦截——装任何插件都会撞墙,不是插件本身的问题。分两种情况处理:① 提示「Minimum release age」(ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION):包发布还不满 24 小时,pnpm 11 出于安全会拒收这种刚发布的新包。可在 pnpm 配置(pnpm-workspace.yaml)里加 minimumReleaseAge: 0 豁免,或等满 24 小时后重试。② 提示「untrusted origin」(ERR_PNPM_UNTRUSTED_ORIGIN):依赖来源不被本机 pnpm 信任,通常是该 profile 里之前用 github: 或非官方 npm 源装过插件,锁文件记录了不受信任的来源。请删除该 profile 目录下的 node_modules 与 pnpm-lock.yaml 后重新安装(可先到「系统日志」或 ~/.dsh/profiles/<profile>/hub.log 查看完整报错)。',
317
320
  // 自定义安装三卡片入口的渠道名(报错/日志溯源:本次安装从哪个入口发起)
318
321
  installChannelNpm: 'NPM 包',
319
322
  installChannelGit: 'GitHub 源码',
@@ -550,6 +553,7 @@ export const en = {
550
553
  diagNpmUnset: 'Unset (follow local npm config)',
551
554
  diagGithub: 'GitHub',
552
555
  diagCatalog: 'Catalog site',
556
+ diagCatalogTarget: 'Plugin marketplace',
553
557
  diagProxy: 'HTTP proxy',
554
558
  diagOk: 'OK',
555
559
  diagChecking: 'Checking…',
@@ -570,8 +574,7 @@ export const en = {
570
574
  settingsResetRun: 'Restore Defaults',
571
575
  settingsResetConfirm: 'Restore Defaults',
572
576
  settingsResetConfirmDetail: 'Reset all settings to factory defaults? Update policy, npm mirror, proxy and security restrictions will be cleared.',
573
- // Startup update policy hints
574
- updatesFound: '{n} plugin(s) have updates — check the notification center',
577
+ // Update notice (notification center item title)
575
578
  updateNoticeTitle: 'Update available',
576
579
  updateNoticeGo: 'Update',
577
580
  ignoreUpdateRun: 'Ignore this update',
@@ -668,8 +671,11 @@ export const en = {
668
671
  // Diagnostics shortcut button under the network-down hint: jumps to Settings → System Diagnostics
669
672
  failNetworkRunDiag: 'Run a network diagnostic',
670
673
  failDshMissingHint: 'The dsh command could not be found (it is not on the system PATH), so the installer could not run. Please make sure DeepSeek Harness is installed correctly and dsh is on your PATH (or reinstall it), then retry. This is not a problem with the plugin itself.',
674
+ failGitMissingHint: 'The git command could not be found (Git is not installed or not on the system PATH), so GitHub-source plugins cannot be installed. Please install Git (e.g. from git-scm.com/downloads) or add git to your PATH, restart DSH, then retry. This is not a problem with the plugin itself.',
671
675
  failPnpmMissingHint: 'The installer found dsh, but pnpm is missing on this machine (dsh uses it to manage profile plugins). Please install pnpm first (e.g. npm install -g pnpm), make sure it is on your PATH, then retry. This is not a problem with the plugin itself.',
676
+ failNpmMissingHint: 'The npm command could not be found (npm is not installed or not on the system PATH), so a global install cannot run. Please install Node.js (which bundles npm), make sure npm is on your PATH, then retry. This is not a problem with the plugin itself.',
672
677
  failPnpmStoreHint: 'pnpm reported an unexpected store location (ERR_PNPM_UNEXPECTED_STORE): the profile directory was previously set up with a different major version of pnpm, and the current pnpm refuses to reuse it — no plugin can be installed into that profile. Delete node_modules and pnpm-lock.yaml under the profile directory (see System Logs or ~/.dsh/profiles/<profile>/hub.log for the full output), then reinstall. This is not a problem with the plugin itself.',
678
+ failPnpmPolicyHint: 'The installer was blocked by pnpm supply-chain security policy — every plugin install would hit the same wall, so this is not a problem with the plugin itself. Two cases: ① "Minimum release age" (ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION): the package was published less than 24 hours ago, and pnpm 11 refuses freshly-published packages for safety. Add minimumReleaseAge: 0 to your pnpm config (pnpm-workspace.yaml) to opt out, or wait until the 24 hours have passed and retry. ② "untrusted origin" (ERR_PNPM_UNTRUSTED_ORIGIN): a dependency source is not trusted by this machine, usually because plugins were previously installed via github: or a non-official npm registry, so the lockfile records an untrusted source. Delete node_modules and pnpm-lock.yaml under the profile directory (see System Logs or ~/.dsh/profiles/<profile>/hub.log for the full output), then reinstall.',
673
679
  // Custom install entry channel names (the three cards) — used in error/log traceability
674
680
  installChannelNpm: 'NPM package',
675
681
  installChannelGit: 'GitHub source',
@@ -8,10 +8,10 @@
8
8
  */
9
9
  import type { LocaleId } from '../types.ts'
10
10
 
11
- /** 插件中心官网地址。 */
11
+ /** 插件市场官网地址。 */
12
12
  export const SITE_URL = 'https://dsh-plugin.org/'
13
13
 
14
- /** 插件中心源码仓库:头部右上角 GitHub 图标的跳转地址 */
14
+ /** 插件市场源码仓库:头部右上角 GitHub 图标的跳转地址 */
15
15
  export const GITHUB_URL = 'https://github.com/dshplugin/dsh-plugin-hub'
16
16
 
17
17
  /** Hub 自身在目录数据里的仓库标识:头部版本号旁的「可更新」徽标按它定位自己。 */
@@ -151,7 +151,7 @@ export function removeNotification(id: number): NotificationRecord[] {
151
151
  return next
152
152
  }
153
153
 
154
- export type FailureKind = 'npmTooOld' | 'dshMissing' | 'pnpmMissing' | 'pnpmStore' | 'pnpmIgnoredBuild' | 'pluginPrepare' | 'network' | 'repo'
154
+ export type FailureKind = 'npmTooOld' | 'dshMissing' | 'gitMissing' | 'pnpmMissing' | 'npmMissing' | 'pnpmStore' | 'pnpmPolicy' | 'pnpmIgnoredBuild' | 'pluginPrepare' | 'network' | 'repo'
155
155
 
156
156
  /**
157
157
  * 失败归类,七态。无论底层机制如何(pnpm 白名单拦截 / 构建脚本被忽略 / prepare 失败),
@@ -163,14 +163,31 @@ export type FailureKind = 'npmTooOld' | 'dshMissing' | 'pnpmMissing' | 'pnpmStor
163
163
  * - dshMissing:安装器 spawn 的 `dsh` 命令找不到(Windows cmd「不是内部或外部命令」/ POSIX
164
164
  * 「command not found」/ spawn ENOENT)—— 是本机 DSH 未正确安装或不在 PATH,不是插件问题
165
165
  * → 提示检查 PATH/重装 DSH,不引导提 Issue
166
+ * - gitMissing:安装器调用 `git` 时找不到可执行文件(Windows cmd「'git' is not recognized」/
167
+ * POSIX「git: command not found」/ spawn ENOENT)—— 是本机 Git 未安装或不在 PATH,不是插件
168
+ * 问题。pnpm 会把缺失 git 报成 `ERR_PNPM_GIT_RESOLVE_FAILED`(git ls-remote failed),若只看
169
+ * 错误码会误归插件侧失败;且 dshMissing 的通用「not recognized」模式会先把它吞成 dsh 缺失,
170
+ * 所以必须在 dshMissing 之前判断(dsh-plugin-hub#21:Win 下装 git 源插件,
171
+ * `'git' is not recognized` 被误归仓库问题引导去提 Issue)→ 提示安装 Git / 加入 PATH,不引导提 Issue
166
172
  * - pnpmMissing:dsh 存在但调用的 `pnpm` 找不到(dsh 报 `pnpm not found on PATH`/POSIX
167
173
  * 「pnpm: command not found」/ spawn ENOENT)—— 本机缺 pnpm(dsh 用 pnpm 管理 profile 插件),
168
174
  * 不是插件问题(dsh-plugin-hub#13:Linux 下 `dsh: pnpm not found on PATH` 被误归插件侧失败)
169
175
  * → 提示安装/开启 pnpm,不引导提 Issue
176
+ * - npmMissing:全局 npm 安装通道(`npm install -g ...`)spawn 的 `npm` 命令找不到
177
+ * (Windows cmd「'npm' is not recognized」/ POSIX「npm: command not found」/ spawn ENOENT)——
178
+ * 本机 npm 未安装或不在 PATH,不是插件问题。dshMissing 的通用「not recognized」模式会把
179
+ * `'npm' is not recognized` 吞成 dsh 缺失,所以必须在 dshMissing 之前判断
180
+ * → 提示安装 npm(Node.js 自带)/加入 PATH,不引导提 Issue
170
181
  * - pnpmStore:pnpm 存在但报 store 位置不匹配(`ERR_PNPM_UNEXPECTED_STORE` / `Unexpected store
171
182
  * location`)—— profile 目录的 node_modules 是用不同大版本的 pnpm 生成的,当前 pnpm 不认,
172
183
  * 任何插件装进该 profile 都会失败;不是插件问题(dsh-plugin-hub#14:macOS 下
173
184
  * `ERR_PNPM_UNEXPECTED_STORE` 被误归插件侧失败)→ 提示清理 profile 依赖目录重建,不引导提 Issue
185
+ * - pnpmPolicy:pnpm 11 的供应链安全策略拒绝安装(`ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION` /
186
+ * `Minimum release age` —— 锁文件里包的发布时间还不满 24 小时被拒;`untrusted origin` —— 依赖来源未被
187
+ * 本机 pnpm 信任)。拦的是「刚发布的新包」与「未被信任的来源」,装任何新插件都会撞墙,不是
188
+ * 插件问题(dsh-plugin-hub#15/#16:用户装官方 dsh-plugin 也被这两类策略拦下并误归插件侧失败)
189
+ * → 提示按子场景给解法(发布未满 24 小时 → `minimumReleaseAge: 0` 豁免或等满 24 小时;untrusted
190
+ * origin → 删除 profile 的 node_modules + pnpm-lock.yaml 清掉不受信任来源后重装),不引导提 Issue
174
191
  * - network:安装前连通性预检拦截(服务端 `[network]` 标记)或底层连接失败
175
192
  * (ERR_PNPM_GIT_FETCH_FAILED / ETIMEDOUT / DNS 解析 / TLS 握手 / 代理拒绝)——
176
193
  * 是本机网络不通/被墙/代理有问题,不是插件问题 → 提示检查网络,不引导提 Issue
@@ -193,6 +210,18 @@ export function classifyFailure(message: string): FailureKind {
193
210
  // found」,会把 `pnpm: command not found` 吞成「dsh 缺失」,误引导用户去装 DSH
194
211
  // (dsh-plugin-hub#13:Linux 下 `dsh: pnpm not found on PATH` 被误归插件侧失败)
195
212
  if (/\[pnpm-missing\]|pnpm not found|pnpm: command not found|spawn pnpm ENOENT|'pnpm' 不是内部或外部命令|"pnpm" 不是内部或外部命令|pnpm['"]?\s*is not recognized/i.test(message)) return 'pnpmMissing'
213
+ // 找不到 git 命令(服务端 [git-missing] 标记 / Windows cmd 中英文「'git' is not recognized」/
214
+ // POSIX「git: command not found」/ spawn git ENOENT):本机 Git 未安装或不在 PATH,不是插件问题。
215
+ // 必须在 dshMissing 之前 —— dshMissing 正则含裸「is not recognized / command not found」,
216
+ // 会把 git 缺失('git' is not recognized as an internal or external command)吞成「dsh 缺失」,
217
+ // 误导用户去装 DSH;且 pnpm 报 `ERR_PNPM_GIT_RESOLVE_FAILED`(git ls-remote failed)时
218
+ // 若只看错误码会落到插件侧失败、引导去提 Issue(dsh-plugin-hub#21)
219
+ if (/\[git-missing\]|spawn git ENOENT|'git' 不是内部或外部命令|"git" 不是内部或外部命令|git['"]?\s*is not recognized|git: command not found/i.test(message)) return 'gitMissing'
220
+ // 找不到 npm 命令(全局 npm 安装通道 `npm install -g` spawn 的 npm 缺失 / Windows cmd 中英文
221
+ // 「'npm' is not recognized」/ POSIX「npm: command not found」/ spawn npm ENOENT):本机 npm 未安装
222
+ // 或不在 PATH,不是插件问题。必须在 dshMissing 之前 —— dshMissing 正则含裸「is not recognized /
223
+ // command not found」,会把 npm 缺失('npm' is not recognized)吞成「dsh 缺失」,误导用户去装 DSH
224
+ if (/\[npm-missing\]|spawn npm ENOENT|'npm' 不是内部或外部命令|"npm" 不是内部或外部命令|npm['"]?\s*is not recognized|npm: command not found/i.test(message)) return 'npmMissing'
196
225
  // 找不到 dsh 命令(服务端 [dsh-missing] 标记 —— 乱码免疫:Windows cmd 中文版输出 GBK,
197
226
  // 经 UTF-8 解码成乱码无法匹配原文,故服务端在 spawn 前用 which/where 探测并打 ASCII 标记;
198
227
  // 其余形态:Windows cmd「不是内部或外部命令」/ POSIX「command not found」/
@@ -206,6 +235,11 @@ export function classifyFailure(message: string): FailureKind {
206
235
  // `ERR_PNPM_UNEXPECTED_STORE` 被误归插件侧失败);提示清理依赖目录用当前 pnpm 重建,
207
236
  // 不引导提 Issue。必须在 pnpmMissing 之后 —— pnpm 在(能跑起来报错),不是「找不到命令」。
208
237
  if (/ERR_PNPM_UNEXPECTED_STORE|Unexpected store location/i.test(message)) return 'pnpmStore'
238
+ // pnpm 供应链安全策略拦截(pnpm 11:minimumReleaseAge 拒收「刚发布」的包 /
239
+ // untrusted origin 来源不受信任):pnpm 在、也连得上,纯粹是本机策略不放行 ——
240
+ // 装任何「新发布/非信任来源」的插件都会同样失败,不是插件问题(dsh-plugin-hub#15/#16)。
241
+ // 必须在 pnpmIgnoredBuild 之前 —— 该策略优先于「构建脚本被白名单拦截」,且两者都不引导提 Issue。
242
+ if (/ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION|Minimum release age|untrusted origin/i.test(message)) return 'pnpmPolicy'
209
243
  // 构建脚本被 pnpm 白名单(allowBuilds)拦截:插件的 prepare 脚本或依赖里的原生模块构建
210
244
  // 被默认拒绝(ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED / ERR_PNPM_IGNORED_BUILDS)。
211
245
  // 这类错误出现即说明 pnpm 已成功 fetch 到 tarball(网络是通的),主因是插件构建脚本
@@ -43,6 +43,7 @@ function reasonTitleOf(kind: FailureKind): string {
43
43
  case 'dshMissing': return 'dsh command not found on the user machine'
44
44
  case 'pnpmMissing': return 'pnpm command not found on the user machine'
45
45
  case 'pnpmStore': return 'pnpm store version mismatch on the user machine'
46
+ case 'pnpmPolicy': return 'pnpm supply-chain policy blocked the install on the user machine'
46
47
  case 'pnpmIgnoredBuild': return 'build scripts blocked by pnpm allowlist'
47
48
  case 'pluginPrepare': return 'plugin distribution incomplete'
48
49
  case 'network': return 'network failure on the user side'
@@ -74,7 +75,9 @@ export function pluginIssueUrl(repo: string, message: string, env?: EnvInfo | nu
74
75
  ? 'plugin prepare/build script failed during install (packaging/distribution issue)'
75
76
  : kind === 'pnpmIgnoredBuild'
76
77
  ? 'plugin depends on a native module whose build script pnpm blocks by default (use a prebuilt variant)'
77
- : 'plugin-side install failure'
78
+ : kind === 'pnpmPolicy'
79
+ ? 'the pnpm supply-chain policy on the user machine blocked the install (minimum release age for freshly published packages / untrusted origin)'
80
+ : 'plugin-side install failure'
78
81
  const code = coreErrorCode(message)
79
82
  // 按给定核心摘要预算构建完整预填 URL(含 URL 编码);预算可调,供超长时逐档缩小
80
83
  const build = (coreChars: number): string => {
@@ -84,8 +87,8 @@ export function pluginIssueUrl(repo: string, message: string, env?: EnvInfo | nu
84
87
  ...(code ? [`- Key error: \`${code}\``] : []),
85
88
  '',
86
89
  // 来源说明:标题(链官网)+ 一句来源(链仓库)+ 实际执行的安装命令(含 --profile)与执行结果,链接由常量动态拼接
87
- `## [DSH Plugin 插件中心](${SITE_URL}) · 安装 Plugin 失败错误信息`,
88
- `本错误信息由 [dsh-plugin-hub](${GITHUB_URL}) 插件中心的安装程序自动生成,随本次安装失败一并提交。`,
90
+ `## [DSH Plugin 插件市场](${SITE_URL}) · 安装 Plugin 失败错误信息`,
91
+ `本错误信息由 [dsh-plugin-hub](${GITHUB_URL}) 插件市场的安装程序自动生成,随本次安装失败一并提交。`,
89
92
  `- 实际执行的安装命令:\`${command ?? `dsh plugin${env?.profile ? ` --profile ${env.profile}` : ''} add git+https://github.com/${repo}.git`}\``,
90
93
  `- 执行结果:安装失败,未能安装该插件。`,
91
94
  // 尝试过的安装方式(npm 反查 + 实际执行命令,按先后顺序):作者据此反推正确的
@@ -123,7 +126,7 @@ export function pluginIssueUrl(repo: string, message: string, env?: EnvInfo | nu
123
126
  `- Full log: \`~/.dsh/profiles/${env.profile}/hub.log\` (paste or attach for the full output)`,
124
127
  ]
125
128
  : []),
126
- // 该插件在插件中心的收录位置(详情页链接)
129
+ // 该插件在插件市场的收录位置(详情页链接)
127
130
  `- Catalog: [${pluginSiteUrl(repo)}](${pluginSiteUrl(repo)})`,
128
131
  '',
129
132
  '## Error (core)',
@@ -270,7 +270,7 @@
270
270
  }
271
271
 
272
272
  /* 来源标签列(行尾、操作按钮前):固定宽度等宽,所有条目标签在同一列右对齐(整齐不乱)。
273
- 「插件中心」= 目录插件(品牌蓝描边),「手动安装」= 命令行安装(琥珀描边)。
273
+ 「插件市场」= 目录插件(品牌蓝描边),「手动安装」= 命令行安装(琥珀描边)。
274
274
  两词等宽(各 4 字),宽度固定后标签列严格对齐 */
275
275
  .rowSourceTag {
276
276
  flex-shrink: 0;
@@ -406,7 +406,7 @@
406
406
  border-color: var(--hub-warning);
407
407
  }
408
408
 
409
- /* 更新:琥珀色实底小按钮(与插件中心「可更新」同色),仅在「有更新」时出现 */
409
+ /* 更新:琥珀色实底小按钮(与插件市场「可更新」同色),仅在「有更新」时出现 */
410
410
  .rowUpdate {
411
411
  font-family: inherit;
412
412
  font-size: 12px;