dsh-plugin 1.6.4 → 1.6.6

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 (51) hide show
  1. package/README.en.md +11 -11
  2. package/README.md +11 -11
  3. package/client/client.js +450 -405
  4. package/client/client.js.map +1 -1
  5. package/lib/http/catalog.js +132 -0
  6. package/lib/http/diagnostics.js +201 -0
  7. package/lib/http/install.js +399 -0
  8. package/lib/http/logs.js +164 -0
  9. package/lib/http/routes.js +19 -1261
  10. package/lib/http/settings.js +77 -0
  11. package/lib/http/shared.js +169 -0
  12. package/lib/http/task.js +208 -0
  13. package/lib/services/install/install.js +1 -1
  14. package/lib/services/install/npm-resolve.js +44 -15
  15. package/lib/services/install/task-queue.js +45 -16
  16. package/lib/services/probe.js +18 -1
  17. package/lib/types/http/catalog.d.ts +10 -0
  18. package/lib/types/http/diagnostics.d.ts +4 -0
  19. package/lib/types/http/install.d.ts +11 -0
  20. package/lib/types/http/logs.d.ts +2 -0
  21. package/lib/types/http/routes.d.ts +15 -20
  22. package/lib/types/http/settings.d.ts +2 -0
  23. package/lib/types/http/shared.d.ts +44 -0
  24. package/lib/types/http/task.d.ts +3 -0
  25. package/lib/types/services/install/install.d.ts +1 -1
  26. package/lib/types/services/install/npm-resolve.d.ts +3 -2
  27. package/lib/types/services/probe.d.ts +8 -0
  28. package/package.json +1 -1
  29. package/src/client/components/PluginHubSection.tsx +7 -2
  30. package/src/client/components/modals/HubUpdateModal.tsx +19 -10
  31. package/src/client/components/modals/NotificationsModal.tsx +34 -18
  32. package/src/client/components/modals/modals.tsx +38 -28
  33. package/src/client/components/views/InstalledView.tsx +23 -11
  34. package/src/client/hooks/useCatalog.ts +13 -9
  35. package/src/client/locales.ts +28 -8
  36. package/src/client/logic/failures.ts +36 -157
  37. package/src/client/logic/urls.ts +72 -1
  38. package/src/client/types.ts +11 -0
  39. package/src/server/http/catalog.ts +143 -0
  40. package/src/server/http/diagnostics.ts +193 -0
  41. package/src/server/http/install.ts +391 -0
  42. package/src/server/http/logs.ts +149 -0
  43. package/src/server/http/routes.ts +22 -1220
  44. package/src/server/http/settings.ts +73 -0
  45. package/src/server/http/shared.ts +191 -0
  46. package/src/server/http/task.ts +194 -0
  47. package/src/server/services/install/install-types.ts +9 -1
  48. package/src/server/services/install/install.ts +11 -1
  49. package/src/server/services/install/npm-resolve.ts +41 -13
  50. package/src/server/services/install/task-queue.ts +45 -16
  51. package/src/server/services/probe.ts +19 -1
@@ -0,0 +1,149 @@
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
+ * Log routes: paginated log viewer, clearing, reveal-in-file-manager and the
7
+ * native folder picker for choosing a log directory.
8
+ */
9
+ import { spawn } from 'node:child_process'
10
+ import { dirname } from 'node:path'
11
+ import { appendLog, clearLog, readLog, logFilePath, defaultLogFilePath } from '../services/log.ts'
12
+ import { requireMethod, requireTrustedPost, sendJson, type WebRoute, type WebServerService } from './shared.ts'
13
+
14
+ export function registerLogsRoutes(webServer: WebServerService, profile: string): WebRoute[] {
15
+ return [
16
+ {
17
+ kind: 'exact',
18
+ path: '/dsh-plugin-hub/logs',
19
+ handler: (request, response) => {
20
+ if (!requireMethod(request, response, 'GET')) return
21
+ // 日志查看器分页接口:?offset=&limit=&category=&level=&query=,返回过滤后的倒序分页
22
+ try {
23
+ const url = new URL(request.url ?? '/', 'http://localhost')
24
+ const num = (name: string): number | undefined => {
25
+ const v = url.searchParams.get(name)
26
+ if (v === null || v === '') return undefined
27
+ const n = Number(v)
28
+ return Number.isFinite(n) && n >= 0 ? Math.floor(n) : undefined
29
+ }
30
+ const limit = Math.min(num('limit') ?? 200, 500)
31
+ const category = url.searchParams.get('category') ?? 'all'
32
+ const level = url.searchParams.get('level') ?? 'all'
33
+ const query = url.searchParams.get('query') ?? ''
34
+ const result = readLog(profile, {
35
+ offset: num('offset') ?? 0,
36
+ limit,
37
+ category: category as 'all' | 'install' | 'uninstall' | 'update' | 'diagnostics' | 'settings' | 'system',
38
+ level: level as 'all' | 'debug' | 'info' | 'success' | 'warn' | 'error',
39
+ query,
40
+ })
41
+ sendJson(response, 200, {
42
+ entries: result.entries,
43
+ total: result.total,
44
+ offset: num('offset') ?? 0,
45
+ limit,
46
+ hasMore: result.total > (num('offset') ?? 0) + result.entries.length,
47
+ path: logFilePath(profile),
48
+ defaultPath: defaultLogFilePath(profile),
49
+ })
50
+ } catch {
51
+ sendJson(response, 400, { error: 'invalid log query' })
52
+ }
53
+ },
54
+ },
55
+ {
56
+ kind: 'exact',
57
+ path: '/dsh-plugin-hub/clear-log',
58
+ handler: (request, response) => {
59
+ if (!requireTrustedPost(request, response)) return
60
+ // 清空所有日志:截断当前日志文件为空
61
+ try {
62
+ const ok = clearLog(profile)
63
+ if (!ok) {
64
+ sendJson(response, 500, { error: 'failed to clear log' })
65
+ return
66
+ }
67
+ appendLog(profile, { at: Date.now(), level: 'info', category: 'system', event: 'system.clear', message: '日志已由用户清空' })
68
+ sendJson(response, 200, { ok: true })
69
+ } catch {
70
+ sendJson(response, 500, { error: 'failed to clear log' })
71
+ }
72
+ },
73
+ },
74
+ {
75
+ kind: 'exact',
76
+ path: '/dsh-plugin-hub/open-log',
77
+ handler: (request, response) => {
78
+ if (!requireTrustedPost(request, response)) return
79
+ // 在系统文件管理器里定位日志文件(macOS Finder -R / Windows explorer /select, / Linux xdg-open)
80
+ try {
81
+ const file = logFilePath(profile)
82
+ const bin = process.platform === 'darwin' ? 'open'
83
+ : process.platform === 'win32' ? 'explorer'
84
+ : 'xdg-open'
85
+ const args = process.platform === 'darwin' ? ['-R', file]
86
+ : process.platform === 'win32' ? ['/select,', file]
87
+ : [file]
88
+ const child = spawn(bin, args, { stdio: 'ignore', detached: true })
89
+ child.once('error', () => sendJson(response, 500, { error: 'failed to open log file' }))
90
+ child.once('spawn', () => sendJson(response, 200, { ok: true }))
91
+ } catch {
92
+ sendJson(response, 500, { error: 'failed to open log file' })
93
+ }
94
+ },
95
+ },
96
+ // 系统目录选择器:弹原生文件夹对话框让用户挑日志存放目录(macOS osascript /
97
+ // Windows FolderBrowserDialog / Linux zenity),选中返回绝对路径,取消返回 cancelled。
98
+ // 与 open-log 不同,这里必须等对话框关闭才能拿结果,所以阻塞收集 stdout 再应答。
99
+ // 对话框默认定位到当前日志文件所在目录 —— 每台机器/平台的默认位置都不同,
100
+ // 这里动态取 logFilePath() 的目录,用户打开就在「它原来的位置」附近。
101
+ {
102
+ kind: 'exact',
103
+ path: '/dsh-plugin-hub/choose-log-dir',
104
+ handler: (request, response) => {
105
+ if (!requireTrustedPost(request, response)) return
106
+ try {
107
+ const title = 'DSH Plugin Hub — choose log folder'
108
+ const startDir = dirname(logFilePath(profile))
109
+ let bin: string
110
+ let args: string[]
111
+ if (process.platform === 'darwin') {
112
+ bin = 'osascript'
113
+ args = ['-e', `POSIX path of (choose folder with prompt "${title}" default location (POSIX file "${startDir}"))`]
114
+ } else if (process.platform === 'win32') {
115
+ bin = 'powershell.exe'
116
+ args = ['-NoProfile', '-STA', '-Command', [
117
+ 'Add-Type -AssemblyName System.Windows.Forms',
118
+ '$f = New-Object System.Windows.Forms.FolderBrowserDialog',
119
+ `$f.Description = '${title}'`,
120
+ // 初始定位到当前日志目录(单引号字符串内反斜杠不转义,Windows 路径原样传入)
121
+ `$f.SelectedPath = '${startDir}'`,
122
+ 'if ($f.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) { $f.SelectedPath } else { "" }',
123
+ ].join('; ')]
124
+ } else {
125
+ bin = 'zenity'
126
+ args = ['--file-selection', '--directory', `--title=${title}`, `--filename=${startDir}`]
127
+ }
128
+ const child = spawn(bin, args)
129
+ let out = ''
130
+ child.stdout.on('data', (d) => { out += String(d) })
131
+ // spawn 失败(如 Linux 无 zenity)会先触发 error 再触发 close(code=-2):
132
+ // 只允许第一个事件应答,否则第二次 sendJson 对已结束的响应再 writeHead,
133
+ // 抛 ERR_HTTP_HEADERS_SENT 成为宿主进程内的未捕获异常。
134
+ let done = false
135
+ const finish = (reply: () => void) => { if (!done) { done = true; reply() } }
136
+ child.on('error', () => finish(() => sendJson(response, 500, { error: 'directory picker unavailable' })))
137
+ child.on('close', (code) => finish(() => {
138
+ // macOS osascript 输出带引号(如 "/Users/x"),统一剥掉首尾空白与引号
139
+ const picked = out.trim().replace(/^"|"$/g, '')
140
+ if (code === 0 && picked !== '') sendJson(response, 200, { path: picked })
141
+ else sendJson(response, 200, { cancelled: true })
142
+ }))
143
+ } catch {
144
+ sendJson(response, 500, { error: 'directory picker unavailable' })
145
+ }
146
+ },
147
+ },
148
+ ]
149
+ }