dsh-plugin 1.1.1 → 1.2.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 (113) hide show
  1. package/README.en.md +5 -5
  2. package/README.md +5 -5
  3. package/client/client.js +4772 -1659
  4. package/client/client.js.map +1 -1
  5. package/lib/http/routes.js +451 -28
  6. package/lib/index.js +5 -1
  7. package/lib/services/{install-types.js → install/install-types.js} +8 -2
  8. package/lib/services/install/install.js +4 -0
  9. package/lib/services/install/npm-check.js +55 -0
  10. package/lib/services/{npm-resolve.js → install/npm-resolve.js} +11 -5
  11. package/lib/services/{preflight.js → install/preflight.js} +23 -7
  12. package/lib/services/{task-queue.js → install/task-queue.js} +166 -71
  13. package/lib/services/loader.js +60 -1
  14. package/lib/services/log.js +138 -0
  15. package/lib/services/probe.js +157 -0
  16. package/lib/services/{installed-versions.js → profile/installed-versions.js} +5 -1
  17. package/lib/services/{pending-restart.js → profile/pending-restart.js} +1 -1
  18. package/lib/services/{profile.js → profile/profile.js} +59 -4
  19. package/lib/services/{progress.js → profile/progress.js} +4 -0
  20. package/lib/services/settings.js +60 -0
  21. package/lib/types/http/routes.d.ts +1 -1
  22. package/lib/types/index.d.ts +5 -1
  23. package/lib/types/services/{install-types.d.ts → install/install-types.d.ts} +13 -4
  24. package/lib/types/services/{install.d.ts → install/install.d.ts} +9 -5
  25. package/lib/types/services/install/npm-check.d.ts +12 -0
  26. package/lib/types/services/{npm-resolve.d.ts → install/npm-resolve.d.ts} +2 -1
  27. package/lib/types/services/{task-queue.d.ts → install/task-queue.d.ts} +5 -1
  28. package/lib/types/services/loader.d.ts +14 -4
  29. package/lib/types/services/log.d.ts +49 -0
  30. package/lib/types/services/probe.d.ts +30 -0
  31. package/lib/types/services/{pending-restart.d.ts → profile/pending-restart.d.ts} +9 -3
  32. package/lib/types/services/{profile.d.ts → profile/profile.d.ts} +6 -2
  33. package/lib/types/services/{progress.d.ts → profile/progress.d.ts} +4 -0
  34. package/lib/types/services/settings.d.ts +24 -0
  35. package/package.json +1 -1
  36. package/src/client/components/PluginHubSection.tsx +346 -121
  37. package/src/client/components/{CatalogList.tsx → catalog/CatalogList.tsx} +15 -7
  38. package/src/client/components/{PluginCard.tsx → catalog/PluginCard.tsx} +10 -5
  39. package/src/client/components/layout/CatalogControls.tsx +80 -0
  40. package/src/client/components/{CatalogHeader.tsx → layout/CatalogHeader.tsx} +8 -4
  41. package/src/client/components/{CategoryTabs.tsx → layout/CategoryTabs.tsx} +7 -3
  42. package/src/client/components/layout/SectionTabs.tsx +60 -0
  43. package/src/client/components/{AboutModal.tsx → modals/AboutModal.tsx} +11 -7
  44. package/src/client/components/{HubUpdateModal.tsx → modals/HubUpdateModal.tsx} +9 -5
  45. package/src/client/components/modals/InstalledDetailModal.tsx +193 -0
  46. package/src/client/components/modals/LogsModal.tsx +301 -0
  47. package/src/client/components/modals/LogsPathModal.tsx +161 -0
  48. package/src/client/components/{NotificationsModal.tsx → modals/NotificationsModal.tsx} +51 -20
  49. package/src/client/components/{ProgressView.tsx → modals/ProgressView.tsx} +6 -2
  50. package/src/client/components/{modals.tsx → modals/modals.tsx} +121 -40
  51. package/src/client/components/{Dropdown.tsx → ui/Dropdown.tsx} +18 -6
  52. package/src/client/components/ui/Toggle.tsx +28 -0
  53. package/src/client/components/ui/icons.tsx +343 -0
  54. package/src/client/components/views/CustomInstallView.tsx +250 -0
  55. package/src/client/components/views/DiagnosticsView.tsx +172 -0
  56. package/src/client/components/views/InstalledView.tsx +328 -0
  57. package/src/client/components/views/LogsView.tsx +150 -0
  58. package/src/client/components/views/MarketView.tsx +66 -0
  59. package/src/client/components/views/SettingsView.tsx +273 -0
  60. package/src/client/data/catalog.ts +38 -0
  61. package/src/client/data/host.ts +107 -0
  62. package/src/client/data/hub.ts +41 -0
  63. package/src/client/hooks/useCatalog.ts +77 -117
  64. package/src/client/hooks/useIncrementalList.ts +46 -0
  65. package/src/client/hooks/useLanguage.ts +40 -0
  66. package/src/client/hooks/useSettings.ts +90 -0
  67. package/src/client/hooks/useTaskQueue.ts +168 -47
  68. package/src/client/index.tsx +4 -0
  69. package/src/client/locales.ts +365 -39
  70. package/src/client/logic/constants.ts +87 -0
  71. package/src/client/{lib → logic}/failures.ts +99 -74
  72. package/src/client/{lib → logic}/format.ts +4 -0
  73. package/src/client/logic/install-command.ts +56 -0
  74. package/src/client/logic/installed.ts +207 -0
  75. package/src/client/logic/normalize.ts +52 -0
  76. package/src/client/{lib → logic}/renderMarkdown.ts +4 -0
  77. package/src/client/logic/urls.ts +125 -0
  78. package/src/client/styles/CustomInstallView.module.css +191 -0
  79. package/src/client/styles/DiagnosticsView.module.css +231 -0
  80. package/src/client/styles/Dropdown.module.css +46 -41
  81. package/src/client/styles/Header.module.css +105 -133
  82. package/src/client/styles/InstalledView.module.css +447 -0
  83. package/src/client/styles/List.module.css +11 -1
  84. package/src/client/styles/LogsView.module.css +503 -0
  85. package/src/client/styles/Modal.module.css +357 -71
  86. package/src/client/styles/SectionTabs.module.css +142 -0
  87. package/src/client/styles/SettingsView.module.css +232 -0
  88. package/src/client/styles/Toggle.module.css +60 -0
  89. package/src/client/styles/tokens.module.css +6 -1
  90. package/src/client/types.ts +28 -4
  91. package/src/server/http/routes.ts +426 -28
  92. package/src/server/index.ts +5 -1
  93. package/src/server/services/{install-types.ts → install/install-types.ts} +13 -5
  94. package/src/server/services/{install.ts → install/install.ts} +9 -5
  95. package/src/server/services/install/npm-check.ts +53 -0
  96. package/src/server/services/{npm-resolve.ts → install/npm-resolve.ts} +11 -5
  97. package/src/server/services/{preflight.ts → install/preflight.ts} +24 -7
  98. package/src/server/services/{task-queue.ts → install/task-queue.ts} +168 -68
  99. package/src/server/services/loader.ts +59 -1
  100. package/src/server/services/log.ts +167 -0
  101. package/src/server/services/probe.ts +157 -0
  102. package/src/server/services/{installed-versions.ts → profile/installed-versions.ts} +5 -1
  103. package/src/server/services/{pending-restart.ts → profile/pending-restart.ts} +9 -3
  104. package/src/server/services/{profile.ts → profile/profile.ts} +58 -4
  105. package/src/server/services/{progress.ts → profile/progress.ts} +4 -0
  106. package/src/server/services/settings.ts +79 -0
  107. package/lib/services/install.js +0 -4
  108. package/src/client/components/CatalogControls.tsx +0 -104
  109. package/src/client/components/icons.tsx +0 -125
  110. package/src/client/lib/catalog.ts +0 -272
  111. package/src/client/lib/env.ts +0 -16
  112. /package/lib/types/services/{preflight.d.ts → install/preflight.d.ts} +0 -0
  113. /package/lib/types/services/{installed-versions.d.ts → profile/installed-versions.d.ts} +0 -0
@@ -0,0 +1,343 @@
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
+ * Inline SVG icons used across the client. Icons inherit currentColor so
7
+ * they adapt to themes and disabled states automatically.
8
+ */
9
+ import { createElement as h } from 'react'
10
+ import headerStyles from '../../styles/Header.module.css'
11
+ import modalStyles from '../../styles/Modal.module.css'
12
+
13
+ /** 弹窗右上角关闭按钮图标:内联 SVG 十字(stroke 继承 currentColor),随按钮禁用态一起变淡。 */
14
+ export function CloseIcon() {
15
+ return h('svg', {
16
+ className: modalStyles.modalCloseIcon,
17
+ viewBox: '0 0 16 16',
18
+ width: 14,
19
+ height: 14,
20
+ fill: 'none',
21
+ 'aria-hidden': 'true',
22
+ }, h('path', {
23
+ d: 'M3 3l10 10M13 3L3 13',
24
+ stroke: 'currentColor',
25
+ strokeWidth: 1.6,
26
+ strokeLinecap: 'round',
27
+ }))
28
+ }
29
+
30
+ /** 来源链接图标:链节(stroke 继承 currentColor),弹窗里官网收录地址旁提示「可点击跳转」。 */
31
+ export function LinkIcon() {
32
+ return h('svg', {
33
+ className: modalStyles.linkIcon,
34
+ viewBox: '0 0 24 24',
35
+ width: 13,
36
+ height: 13,
37
+ fill: 'none',
38
+ 'aria-hidden': 'true',
39
+ },
40
+ h('path', {
41
+ d: 'M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71',
42
+ stroke: 'currentColor',
43
+ strokeWidth: 1.8,
44
+ strokeLinecap: 'round',
45
+ strokeLinejoin: 'round',
46
+ }),
47
+ h('path', {
48
+ d: 'M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71',
49
+ stroke: 'currentColor',
50
+ strokeWidth: 1.8,
51
+ strokeLinecap: 'round',
52
+ strokeLinejoin: 'round',
53
+ }))
54
+ }
55
+
56
+ /** 品牌 logo 图标:蓝紫渐变圆角方块 + 白色拼图块(2x2 错落),标题左侧的品牌标识。 */
57
+ export function LogoIcon() {
58
+ return h('svg', {
59
+ className: headerStyles.logoIcon,
60
+ viewBox: '0 0 24 24',
61
+ width: 20,
62
+ height: 20,
63
+ 'aria-hidden': 'true',
64
+ },
65
+ h('defs', null,
66
+ h('linearGradient', { id: 'dshLogoGrad', x1: '0', y1: '0', x2: '1', y2: '1' },
67
+ h('stop', { offset: '0%', stopColor: '#4f46e5' }),
68
+ h('stop', { offset: '100%', stopColor: '#7c3aed' }),
69
+ ),
70
+ ),
71
+ h('rect', { x: 1, y: 1, width: 22, height: 22, rx: 6, fill: 'url(#dshLogoGrad)' }),
72
+ h('rect', { x: 5.5, y: 5.5, width: 5.5, height: 5.5, rx: 1.5, fill: '#ffffff' }),
73
+ h('rect', { x: 13, y: 5.5, width: 5.5, height: 5.5, rx: 1.5, fill: '#ffffff', opacity: 0.55 }),
74
+ h('rect', { x: 5.5, y: 13, width: 5.5, height: 5.5, rx: 1.5, fill: '#ffffff', opacity: 0.55 }),
75
+ h('rect', { x: 13, y: 13, width: 5.5, height: 5.5, rx: 1.5, fill: '#ffffff' }),
76
+ )
77
+ }
78
+
79
+ /** GitHub 图标:官方 GitHub Mark(octocat),fill 继承 currentColor,头部右上角源码链接用。 */
80
+ export function GitHubIcon() {
81
+ return h('svg', {
82
+ className: headerStyles.githubIcon,
83
+ viewBox: '0 0 24 24',
84
+ width: 18,
85
+ height: 18,
86
+ fill: 'currentColor',
87
+ 'aria-hidden': 'true',
88
+ }, h('path', {
89
+ d: 'M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12',
90
+ }))
91
+ }
92
+
93
+ /** 复制图标:双层矩形(stroke 继承 currentColor),手动命令旁的复制按钮用。 */
94
+ export function CopyIcon() {
95
+ return h('svg', {
96
+ className: headerStyles.copyIcon,
97
+ viewBox: '0 0 16 16',
98
+ width: 12,
99
+ height: 12,
100
+ fill: 'none',
101
+ 'aria-hidden': 'true',
102
+ },
103
+ h('rect', {
104
+ x: 5.5, y: 5.5, width: 7, height: 7, rx: 1.5,
105
+ stroke: 'currentColor', strokeWidth: 1.4,
106
+ }),
107
+ h('path', {
108
+ d: 'M10.5 5.5v-2a1.5 1.5 0 0 0-1.5-1.5H4.5A1.5 1.5 0 0 0 3 3.5v4A1.5 1.5 0 0 0 4.5 9h1',
109
+ stroke: 'currentColor', strokeWidth: 1.4, strokeLinecap: 'round',
110
+ }),
111
+ )
112
+ }
113
+
114
+ /** 文件夹图标(Material folder):「在文件夹中显示」按钮用,fill 继承 currentColor。 */
115
+ export function FolderIcon() {
116
+ return h('svg', {
117
+ viewBox: '0 0 24 24',
118
+ width: 13,
119
+ height: 13,
120
+ fill: 'currentColor',
121
+ 'aria-hidden': 'true',
122
+ }, h('path', {
123
+ d: 'M10 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z',
124
+ }))
125
+ }
126
+
127
+ /** 下拉箭头图标:向下 V 形(stroke 继承 currentColor),下拉按钮触发器右侧提示可展开。 */
128
+ export function ChevronDownIcon() {
129
+ return h('svg', {
130
+ viewBox: '0 0 16 16',
131
+ width: 12,
132
+ height: 12,
133
+ fill: 'none',
134
+ 'aria-hidden': 'true',
135
+ }, h('path', {
136
+ d: 'M3.5 5.5L8 10l4.5-4.5',
137
+ stroke: 'currentColor',
138
+ strokeWidth: 1.6,
139
+ strokeLinecap: 'round',
140
+ strokeLinejoin: 'round',
141
+ }))
142
+ }
143
+
144
+ /** 排序方向小箭头:正序(up) 朝上 / 倒序(down) 朝下,随 currentColor 着色,仅当前排序按钮显示 */
145
+ export function SortArrowIcon({ up }: { up: boolean }) {
146
+ return h('svg', {
147
+ viewBox: '0 0 16 16',
148
+ width: 10,
149
+ height: 10,
150
+ fill: 'none',
151
+ 'aria-hidden': 'true',
152
+ }, h('path', {
153
+ d: up ? 'M4 9.5L8 5.5l4 4' : 'M4 6.5L8 10.5l4-4',
154
+ stroke: 'currentColor',
155
+ strokeWidth: 1.8,
156
+ strokeLinecap: 'round',
157
+ strokeLinejoin: 'round',
158
+ }))
159
+ }
160
+
161
+ /** 插件中心图标:店面货架(stroke 继承 currentColor),一级导航「插件中心」tab 用。 */
162
+ export function MarketIcon() {
163
+ return h('svg', {
164
+ viewBox: '0 0 24 24',
165
+ width: 16,
166
+ height: 16,
167
+ fill: 'none',
168
+ 'aria-hidden': 'true',
169
+ },
170
+ h('path', {
171
+ d: 'M4 10L5.6 4.5h12.8L20 10',
172
+ stroke: 'currentColor', strokeWidth: 1.8, strokeLinecap: 'round', strokeLinejoin: 'round',
173
+ }),
174
+ h('path', {
175
+ d: 'M4 10a2.2 2.2 0 0 0 4.4 0 2.2 2.2 0 0 0 4.4 0 2.2 2.2 0 0 0 4.4 0',
176
+ stroke: 'currentColor', strokeWidth: 1.8, strokeLinecap: 'round', strokeLinejoin: 'round',
177
+ }),
178
+ h('path', {
179
+ d: 'M5 13v6.5A1.5 1.5 0 0 0 6.5 21h11a1.5 1.5 0 0 0 1.5-1.5V13',
180
+ stroke: 'currentColor', strokeWidth: 1.8, strokeLinecap: 'round', strokeLinejoin: 'round',
181
+ }))
182
+ }
183
+
184
+ /** 自定义安装图标:终端窗口 + 提示符(stroke 继承 currentColor),一级导航「自定义安装」tab 用。 */
185
+ export function TerminalIcon() {
186
+ return h('svg', {
187
+ viewBox: '0 0 24 24',
188
+ width: 16,
189
+ height: 16,
190
+ fill: 'none',
191
+ 'aria-hidden': 'true',
192
+ },
193
+ h('rect', {
194
+ x: 3, y: 4.5, width: 18, height: 15, rx: 2.5,
195
+ stroke: 'currentColor', strokeWidth: 1.8,
196
+ }),
197
+ h('path', {
198
+ d: 'M6.8 9.2l3.4 2.8-3.4 2.8',
199
+ stroke: 'currentColor', strokeWidth: 1.8, strokeLinecap: 'round', strokeLinejoin: 'round',
200
+ }),
201
+ h('path', {
202
+ d: 'M12.5 14.8h4.5',
203
+ stroke: 'currentColor', strokeWidth: 1.8, strokeLinecap: 'round',
204
+ }),
205
+ )
206
+ }
207
+
208
+ /** 已安装图标:插件包(圆角方盒 + 盒盖线)+ 对勾(stroke 继承 currentColor),一级导航「已安装」tab 用。 */
209
+ export function InstalledIcon() {
210
+ return h('svg', {
211
+ viewBox: '0 0 24 24',
212
+ width: 16,
213
+ height: 16,
214
+ fill: 'none',
215
+ 'aria-hidden': 'true',
216
+ },
217
+ h('rect', {
218
+ x: 4.5, y: 6.5, width: 15, height: 13, rx: 2.5,
219
+ stroke: 'currentColor', strokeWidth: 1.8,
220
+ }),
221
+ h('path', {
222
+ d: 'M4.5 11h15',
223
+ stroke: 'currentColor', strokeWidth: 1.8, strokeLinecap: 'round',
224
+ }),
225
+ h('path', {
226
+ d: 'M9.2 15.2l2.1 2.1 3.6-4',
227
+ stroke: 'currentColor', strokeWidth: 1.8, strokeLinecap: 'round', strokeLinejoin: 'round',
228
+ }))
229
+ }
230
+
231
+ /** 通知铃铛图标:实心铃铛(fill 继承 currentColor),采用 Material 官方 notifications 路径,一级导航通知中心入口用。 */
232
+ export function BellIcon() {
233
+ return h('svg', {
234
+ viewBox: '0 0 24 24',
235
+ width: 15,
236
+ height: 15,
237
+ fill: 'currentColor',
238
+ 'aria-hidden': 'true',
239
+ }, h('path', {
240
+ d: 'M12 22c1.1 0 2-.9 2-2h-4c0 1.1.89 2 2 2zm6-6v-5c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2z',
241
+ }))
242
+ }
243
+
244
+ /** 设置图标:实心齿轮(fill 继承 currentColor),采用 Material 官方 settings 路径,齿形清晰、辨识度高。 */
245
+ export function SettingsIcon() {
246
+ return h('svg', {
247
+ viewBox: '0 0 24 24',
248
+ width: 16,
249
+ height: 16,
250
+ fill: 'currentColor',
251
+ 'aria-hidden': 'true',
252
+ }, h('path', {
253
+ d: 'M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94L14.4 2.81c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41L9.25 5.35C8.66 5.59 8.12 5.92 7.63 6.29L5.24 5.33c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.22-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61L19.14 12.94zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z',
254
+ }))
255
+ }
256
+
257
+ /** 更新与通道图标:循环同步箭头(fill 继承 currentColor),采用 Material 官方 sync 路径,设置左侧导航「更新与通道」用。 */
258
+ export function UpdatesIcon() {
259
+ return h('svg', {
260
+ viewBox: '0 0 24 24',
261
+ width: 15,
262
+ height: 15,
263
+ fill: 'currentColor',
264
+ 'aria-hidden': 'true',
265
+ }, h('path', {
266
+ d: 'M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z',
267
+ }))
268
+ }
269
+
270
+ /** 安全与信任图标:盾牌 + 对勾(stroke 继承 currentColor),设置左侧导航「安全与信任」用。 */
271
+ export function SecurityIcon() {
272
+ return h('svg', {
273
+ viewBox: '0 0 16 16',
274
+ width: 15,
275
+ height: 15,
276
+ fill: 'none',
277
+ 'aria-hidden': 'true',
278
+ },
279
+ h('path', {
280
+ d: 'M8 1.8L13.8 3.8v4.3c0 3.5-2.2 6.3-5.8 7.6-3.6-1.3-5.8-4.1-5.8-7.6V3.8L8 1.8z',
281
+ stroke: 'currentColor', strokeWidth: 1.5, strokeLinecap: 'round', strokeLinejoin: 'round',
282
+ }),
283
+ h('path', {
284
+ d: 'M5.6 8.1l1.7 1.7 3.2-3.4',
285
+ stroke: 'currentColor', strokeWidth: 1.5, strokeLinecap: 'round', strokeLinejoin: 'round',
286
+ }))
287
+ }
288
+
289
+ /** 诊断图标:心跳脉冲线(stroke 继承 currentColor),设置左侧导航「诊断」用。 */
290
+ export function DiagnosticsIcon() {
291
+ return h('svg', {
292
+ viewBox: '0 0 16 16',
293
+ width: 15,
294
+ height: 15,
295
+ fill: 'none',
296
+ 'aria-hidden': 'true',
297
+ }, h('path', {
298
+ d: 'M1.6 8h3l1.9-4.2 2.9 8.4 1.9-4.2h3.1',
299
+ stroke: 'currentColor', strokeWidth: 1.5, strokeLinecap: 'round', strokeLinejoin: 'round',
300
+ }))
301
+ }
302
+
303
+ /** 恢复默认图标:逆时针还原箭头(stroke 继承 currentColor),设置左侧导航「恢复默认」用。 */
304
+ export function ResetIcon() {
305
+ return h('svg', {
306
+ viewBox: '0 0 16 16',
307
+ width: 15,
308
+ height: 15,
309
+ fill: 'none',
310
+ 'aria-hidden': 'true',
311
+ },
312
+ h('path', {
313
+ d: 'M3 5.2A5.6 5.6 0 1 1 2.4 9.6',
314
+ stroke: 'currentColor', strokeWidth: 1.5, strokeLinecap: 'round', strokeLinejoin: 'round',
315
+ }),
316
+ h('path', {
317
+ d: 'M2.6 2.6v2.6h2.6',
318
+ stroke: 'currentColor', strokeWidth: 1.5, strokeLinecap: 'round', strokeLinejoin: 'round',
319
+ }))
320
+ }
321
+
322
+ /** 系统日志图标:文档纸 + 三行文字(stroke 继承 currentColor),设置左侧导航「系统日志」用。 */
323
+ export function LogsIcon() {
324
+ return h('svg', {
325
+ viewBox: '0 0 16 16',
326
+ width: 15,
327
+ height: 15,
328
+ fill: 'none',
329
+ 'aria-hidden': 'true',
330
+ },
331
+ h('path', {
332
+ d: 'M4.2 1.8h4.8l2.8 2.8v9.6H4.2z',
333
+ stroke: 'currentColor', strokeWidth: 1.5, strokeLinejoin: 'round',
334
+ }),
335
+ h('path', {
336
+ d: 'M9 1.8v2.8h2.8',
337
+ stroke: 'currentColor', strokeWidth: 1.5, strokeLinecap: 'round', strokeLinejoin: 'round',
338
+ }),
339
+ h('path', {
340
+ d: 'M5.6 9.2h4.8M5.6 11.2h4.8',
341
+ stroke: 'currentColor', strokeWidth: 1.5, strokeLinecap: 'round',
342
+ }))
343
+ }
@@ -0,0 +1,250 @@
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
+ * Custom install view: a standalone top-level section for manually
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.
14
+ *
15
+ * Pure presentational: owns only the three local input/error states; the
16
+ * actual install (custom source, gated by the security toggles) bubbles up
17
+ * via onInstallCustom.
18
+ */
19
+ import { createElement as h, useState } from 'react'
20
+ import type { FormEvent } from 'react'
21
+ import styles from '../../styles/CustomInstallView.module.css'
22
+ import type { Translate } from '../../types.ts'
23
+
24
+ /* —— 自定义安装输入预检 ——
25
+ * 提交前校验「输入是否符合该通道格式」,识别不了的输入不发请求,就地提示推荐格式。
26
+ * 三个通道严格分开:npm 框只认包名,git 框只认 GitHub 地址,命令框认两种完整命令
27
+ * (`dsh plugin [--profile|-p <p>] add <target>` 与官方全局安装 `npm install -g <pkgs>`)。
28
+ * 这里只做格式校验,真正归一化/安装由服务端完成。 */
29
+
30
+ /** GitHub 地址(带前缀形态):github:、https://github.com/、git+https://github.com/、git@github.com: */
31
+ const GITHUB_URL_RE = /^(?:github:|https?:\/\/github\.com\/|git\+https?:\/\/github\.com\/|git@github\.com:)([A-Za-z0-9._-]+)\/([A-Za-z0-9._-]+?)(?:\.git)?$/
32
+ /** 裸 owner/repo(npm 包名只有 @scope/pkg 才带斜杠,无 @ 前缀的 a/b 一定是 GitHub 仓库) */
33
+ const GITHUB_BARE_RE = /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/
34
+ /** npm 包名(含 @scope/pkg) */
35
+ const NPM_PACKAGE_RE = /^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+$/
36
+
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
40
+
41
+ /** npm 全局安装命令(官方 README 格式,如 `npm install -g @deepseek-ai/dsh @deepseek-harness-tui/dsh-tui`)——
42
+ * 提取 -g 之后的包列表;任一包名非法返回 null(交给格式错误提示)。与 server 端 globalNpmPackagesOf 口径一致。 */
43
+ const NPM_GLOBAL_CMD_RE = /^npm\s+(?:install|i)\s+(?:-g|--global)\s+(.+)$/i
44
+
45
+ function installTargetOf(raw: string): string {
46
+ const input = raw.trim()
47
+ const match = DSH_PLUGIN_CMD_RE.exec(input)
48
+ return match !== null ? match[1].trim() : input
49
+ }
50
+
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 端同口径
57
+ if (packages.length === 0 || packages.some((p) => !NPM_PACKAGE_RE.test(p) || p.startsWith('-'))) return null
58
+ return packages
59
+ }
60
+
61
+ function isGitHubInput(raw: string): boolean {
62
+ const input = raw.trim()
63
+ return GITHUB_URL_RE.test(input) || GITHUB_BARE_RE.test(input)
64
+ }
65
+
66
+ function isNpmInput(raw: string): boolean {
67
+ return NPM_PACKAGE_RE.test(raw.trim())
68
+ }
69
+
70
+ function isDshCommand(raw: string): boolean {
71
+ return DSH_PLUGIN_CMD_RE.test(raw.trim())
72
+ }
73
+
74
+ export function CustomInstallView({ t, onInstallCustom, enableNpm, enableGit, enableDsh, onOpenSettings }: {
75
+ t: Translate
76
+ /** 命令行安装:输入 npm 包名 / GitHub 地址 / 安装命令(dsh plugin add 或 npm install -g)即装
77
+ * (custom 源,受安全设置开关控制);opts.globalNpm 表示官方 npm 全局安装命令,附带解析出的包列表 */
78
+ onInstallCustom: (raw: string, opts?: { globalNpm?: string[] }) => void
79
+ /** 安全信任三通道开关:关掉后对应输入卡片禁用(不提交),并引导去设置打开 */
80
+ enableNpm: boolean
81
+ enableGit: boolean
82
+ enableDsh: boolean
83
+ /** 引导去「设置 → 安全信任」打开被关闭的通道开关 */
84
+ onOpenSettings: () => void
85
+ }) {
86
+ /** 自定义安装:NPM 包输入与格式错误('' = 无错误) */
87
+ const [npmQuery, setNpmQuery] = useState('')
88
+ const [npmError, setNpmError] = useState('')
89
+ /** 自定义安装:GitHub 源码输入与格式错误('' = 无错误) */
90
+ const [gitQuery, setGitQuery] = useState('')
91
+ const [gitError, setGitError] = useState('')
92
+ /** 自定义安装:DSH 命令输入与格式错误('' = 无错误) */
93
+ const [cmdQuery, setCmdQuery] = useState('')
94
+ const [cmdError, setCmdError] = useState('')
95
+
96
+ /** 通道关闭提示行:开关关掉后替代示例与输入行,引导去设置打开 */
97
+ const channelOffRow = h('div', { className: styles.channelOff },
98
+ h('span', { className: styles.channelOffText }, t('channelDisabledHint')),
99
+ h('button', {
100
+ type: 'button',
101
+ className: styles.channelOffBtn,
102
+ onClick: onOpenSettings,
103
+ }, t('goToSettings')),
104
+ )
105
+
106
+ // 三个通道分开校验 —— 识别不了的输入不发请求,就地提示推荐写法。
107
+ // NPM / GitHub 框只认各自裸目标;命令框认两种完整命令(dsh plugin add / npm install -g),
108
+ // 提交时 dsh 命令剥成裸目标发给服务端,npm install -g 解析成包列表走全局安装通道。
109
+ const submitNpm = () => {
110
+ const target = npmQuery.trim()
111
+ if (!target) return
112
+ if (!isNpmInput(target)) { setNpmError(t('npmInstallInvalid')); return }
113
+ onInstallCustom(target)
114
+ setNpmQuery('')
115
+ setNpmError('')
116
+ }
117
+ const submitGit = () => {
118
+ const target = gitQuery.trim()
119
+ if (!target) return
120
+ if (!isGitHubInput(target)) { setGitError(t('gitInstallInvalid')); return }
121
+ onInstallCustom(target)
122
+ setGitQuery('')
123
+ setGitError('')
124
+ }
125
+ const submitCmd = () => {
126
+ const raw = cmdQuery.trim()
127
+ 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
+ if (!isDshCommand(raw)) { setCmdError(t('dshCmdInvalid')); return }
137
+ onInstallCustom(installTargetOf(raw))
138
+ setCmdQuery('')
139
+ setCmdError('')
140
+ }
141
+
142
+ return h('div', { className: styles.root },
143
+ // 页面说明:独立一级导航,交代「装完去哪看」
144
+ h('p', { className: styles.desc }, t('customViewDesc')),
145
+ // 三块卡片(一行一个):label + 文本框上方示例 + 输入行 + 错误区;
146
+ // 安全信任开关关掉对应通道时,卡片禁用并显示「前往设置」提示行
147
+ h('div', { className: styles.installCards },
148
+ // NPM 包:输入包名即装
149
+ h('div', { className: enableNpm ? styles.installCard : `${styles.installCard} ${styles.installCardDisabled}` },
150
+ h('div', { className: styles.installCardHead },
151
+ h('span', { className: styles.installLabel }, t('npmInstallLabel')),
152
+ ),
153
+ !enableNpm
154
+ ? channelOffRow
155
+ : [
156
+ // 文本框上方的示例:告诉用户怎么写
157
+ h('div', { className: styles.installExample }, t('npmInstallExample')),
158
+ h('div', { className: styles.installRow },
159
+ h('input', {
160
+ className: npmError ? `${styles.installInput} ${styles.installInputError}` : styles.installInput,
161
+ type: 'text',
162
+ placeholder: t('npmInstallPlaceholder'),
163
+ value: npmQuery,
164
+ spellCheck: false,
165
+ onInput: (e: FormEvent<HTMLInputElement>) => {
166
+ setNpmQuery((e.target as HTMLInputElement).value)
167
+ // 重新输入即清掉格式错误提示
168
+ setNpmError('')
169
+ },
170
+ onKeyDown: (e: KeyboardEvent) => { if (e.key === 'Enter') submitNpm() },
171
+ }),
172
+ h('button', {
173
+ className: styles.installBtn,
174
+ type: 'button',
175
+ disabled: npmQuery.trim() === '',
176
+ onClick: submitNpm,
177
+ }, t('installCliBtn')),
178
+ ),
179
+ // 格式错误:识别不了的包名就地说明,并给出推荐写法
180
+ npmError ? h('div', { className: styles.installError }, npmError) : null,
181
+ ],
182
+ ),
183
+ // GitHub 源码:输入仓库地址即装
184
+ h('div', { className: enableGit ? styles.installCard : `${styles.installCard} ${styles.installCardDisabled}` },
185
+ h('div', { className: styles.installCardHead },
186
+ h('span', { className: styles.installLabel }, t('gitInstallLabel')),
187
+ ),
188
+ !enableGit
189
+ ? channelOffRow
190
+ : [
191
+ h('div', { className: styles.installExample }, t('gitInstallExample')),
192
+ h('div', { className: styles.installRow },
193
+ h('input', {
194
+ className: gitError ? `${styles.installInput} ${styles.installInputError}` : styles.installInput,
195
+ type: 'text',
196
+ placeholder: t('gitInstallPlaceholder'),
197
+ value: gitQuery,
198
+ spellCheck: false,
199
+ onInput: (e: FormEvent<HTMLInputElement>) => {
200
+ setGitQuery((e.target as HTMLInputElement).value)
201
+ setGitError('')
202
+ },
203
+ onKeyDown: (e: KeyboardEvent) => { if (e.key === 'Enter') submitGit() },
204
+ }),
205
+ h('button', {
206
+ className: styles.installBtn,
207
+ type: 'button',
208
+ disabled: gitQuery.trim() === '',
209
+ onClick: submitGit,
210
+ }, t('installCliBtn')),
211
+ ),
212
+ gitError ? h('div', { className: styles.installError }, gitError) : null,
213
+ ],
214
+ ),
215
+ // 命令卡片:粘贴完整安装命令即装 —— 官方全局安装 `npm install -g <pkgs>`(装系统级 CLI 工具)
216
+ // 或完整 dsh plugin 命令(可含 --profile 段,剥成裸目标安装)
217
+ h('div', { className: enableDsh ? styles.installCard : `${styles.installCard} ${styles.installCardDisabled}` },
218
+ h('div', { className: styles.installCardHead },
219
+ h('span', { className: styles.installLabel }, t('dshCmdLabel')),
220
+ ),
221
+ !enableDsh
222
+ ? channelOffRow
223
+ : [
224
+ h('div', { className: styles.installExample }, t('dshCmdExample')),
225
+ h('div', { className: styles.installRow },
226
+ h('input', {
227
+ className: cmdError ? `${styles.installInput} ${styles.installInputError}` : styles.installInput,
228
+ type: 'text',
229
+ placeholder: t('dshCmdPlaceholder'),
230
+ value: cmdQuery,
231
+ spellCheck: false,
232
+ onInput: (e: FormEvent<HTMLInputElement>) => {
233
+ setCmdQuery((e.target as HTMLInputElement).value)
234
+ setCmdError('')
235
+ },
236
+ onKeyDown: (e: KeyboardEvent) => { if (e.key === 'Enter') submitCmd() },
237
+ }),
238
+ h('button', {
239
+ className: styles.installBtn,
240
+ type: 'button',
241
+ disabled: cmdQuery.trim() === '',
242
+ onClick: submitCmd,
243
+ }, t('installCliBtn')),
244
+ ),
245
+ cmdError ? h('div', { className: styles.installError }, cmdError) : null,
246
+ ],
247
+ ),
248
+ ),
249
+ )
250
+ }