dsh-plugin-panel 0.0.0-stage → 0.1.1
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.
- package/README.md +66 -2
- package/catalog.js +18 -0
- package/catalog.test.mjs +13 -0
- package/client.js +961 -0
- package/cordis.patch.yml +3 -0
- package/docs/DETAIL.md +7 -0
- package/docs/DETAIL.zh.md +7 -0
- package/docs/promo-3x4.png +0 -0
- package/docs/promo-wechat.png +0 -0
- package/docs/screenshot.png +0 -0
- package/icon.svg +13 -0
- package/index.js +371 -0
- package/locale/en.json +7 -0
- package/locale/zh.json +7 -0
- package/package.json +60 -4
package/cordis.patch.yml
ADDED
package/docs/DETAIL.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Open Plugin manager in the right sidebar.
|
|
2
|
+
|
|
3
|
+
Filter by Installed, Built-in, or All. Plugins with a right-sidebar page come first, then Settings, then the left menu. Plugins with no UI entry are last.
|
|
4
|
+
|
|
5
|
+
Pin moves a plugin to the front. Open switches to that plugin's right-sidebar page when it has one. Select a card to read its introduction, package, version, source, and these notes.
|
|
6
|
+
|
|
7
|
+
DSH does not record plugin call history.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/icon.svg
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
|
2
|
+
<rect width="64" height="64" rx="14" fill="#1b2430"/>
|
|
3
|
+
<ellipse cx="24" cy="30" rx="12" ry="3.2" fill="#0b1016" opacity="0.45"/>
|
|
4
|
+
<g transform="rotate(-11 22 18)">
|
|
5
|
+
<rect x="8" y="9" width="28" height="18" rx="4" fill="#f6f1e7"/>
|
|
6
|
+
<rect x="12" y="14" width="12" height="2.4" rx="1.2" fill="#1b2430" opacity="0.72"/>
|
|
7
|
+
<rect x="12" y="18.4" width="18" height="1.6" rx="0.8" fill="#1b2430" opacity="0.28"/>
|
|
8
|
+
</g>
|
|
9
|
+
<rect x="26" y="30" width="30" height="20" rx="4.5" fill="#f7f3ea"/>
|
|
10
|
+
<rect x="31" y="36" width="13" height="2.6" rx="1.3" fill="#1b2430" opacity="0.75"/>
|
|
11
|
+
<rect x="31" y="41" width="19" height="1.7" rx="0.8" fill="#1b2430" opacity="0.28"/>
|
|
12
|
+
<circle cx="49" cy="40" r="2.3" fill="#2fbe86"/>
|
|
13
|
+
</svg>
|
package/index.js
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import { createRequire } from 'node:module'
|
|
2
|
+
import { spawn } from 'node:child_process'
|
|
3
|
+
import { readFileSync, readdirSync, statSync } from 'node:fs'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import { fileURLToPath } from 'node:url'
|
|
6
|
+
|
|
7
|
+
const CHANNEL = '/dsh-plugins-ui-rpc'
|
|
8
|
+
const MAX_SOURCE_BYTES = 1_200_000
|
|
9
|
+
const MAX_FILES = 40
|
|
10
|
+
const MAX_DOC_CHARS = 2400
|
|
11
|
+
const MAX_COUNT_BATCH = 40
|
|
12
|
+
const moduleTools = new Map()
|
|
13
|
+
|
|
14
|
+
export function apply(ctx) {
|
|
15
|
+
ctx.inject(['connection', 'webServer', 'tools', 'pluginManager'], (scoped) => {
|
|
16
|
+
const tools = scoped.tools
|
|
17
|
+
const pluginManager = scoped.pluginManager
|
|
18
|
+
scoped.effect(() => registerDetailRpc(scoped, { tools, pluginManager }), 'plugin-canvas: detail rpc')
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function registerDetailRpc(ctx, services) {
|
|
23
|
+
return ctx.webServer.register({
|
|
24
|
+
kind: 'prefix',
|
|
25
|
+
path: CHANNEL,
|
|
26
|
+
handler: async (req, res) => {
|
|
27
|
+
const rejection = ctx.connection.requestRejection(req)
|
|
28
|
+
if (rejection !== undefined) {
|
|
29
|
+
res.statusCode = rejection
|
|
30
|
+
res.end()
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
if (req.method !== 'POST') {
|
|
34
|
+
res.statusCode = 404
|
|
35
|
+
res.end('not found')
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
const mediaType = String(req.headers['content-type'] ?? '').split(';', 1)[0].trim().toLowerCase()
|
|
39
|
+
if (mediaType !== 'application/json') {
|
|
40
|
+
res.statusCode = 415
|
|
41
|
+
res.end('content type must be application/json')
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
let body
|
|
45
|
+
try {
|
|
46
|
+
body = await readJson(req)
|
|
47
|
+
} catch (error) {
|
|
48
|
+
res.statusCode = 400
|
|
49
|
+
res.end(error instanceof Error ? error.message : 'invalid body')
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
const record = body && typeof body === 'object' ? body : {}
|
|
53
|
+
const rpcId = typeof record.rpcId === 'string' ? record.rpcId : ''
|
|
54
|
+
const method = typeof record.method === 'string' ? record.method : ''
|
|
55
|
+
if (!rpcId || !method) {
|
|
56
|
+
res.statusCode = 400
|
|
57
|
+
res.end('invalid client-request envelope')
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
let result
|
|
61
|
+
try {
|
|
62
|
+
if (method === 'detail') result = { ok: true, value: await readDetail(services, record.payload) }
|
|
63
|
+
else if (method === 'counts') result = { ok: true, value: countTools(services, record.payload) }
|
|
64
|
+
else if (method === 'facts') result = { ok: true, value: readFacts(record.payload) }
|
|
65
|
+
else if (method === 'reveal') result = { ok: true, value: revealPackage(record.payload) }
|
|
66
|
+
else throw new Error('Unknown detail method.')
|
|
67
|
+
} catch (error) {
|
|
68
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
69
|
+
result = { ok: false, error: { code: 'plugin-canvas/detail', message: message.slice(0, 500), details: {} } }
|
|
70
|
+
}
|
|
71
|
+
res.statusCode = 200
|
|
72
|
+
res.setHeader('content-type', 'application/json; charset=utf-8')
|
|
73
|
+
res.setHeader('cache-control', 'no-store')
|
|
74
|
+
res.end(JSON.stringify({ type: 'server-response', rpcId, result }))
|
|
75
|
+
},
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function readDetail(services, payload) {
|
|
80
|
+
const input = payload && typeof payload === 'object' ? payload : {}
|
|
81
|
+
const moduleName = typeof input.moduleName === 'string' ? input.moduleName : ''
|
|
82
|
+
const entryId = typeof input.entryId === 'string' ? input.entryId : ''
|
|
83
|
+
const language = typeof input.language === 'string' ? input.language : 'zh'
|
|
84
|
+
const tools = toolsForModule(listTools(services.tools), moduleName)
|
|
85
|
+
const located = locatePackage(moduleName)
|
|
86
|
+
const manifest = located ? readManifest(located.root) : null
|
|
87
|
+
const bundle = await findBundle(services.pluginManager, moduleName, entryId, manifest && manifest.name)
|
|
88
|
+
const document = await readDocument(services.pluginManager, bundle && bundle.name, language)
|
|
89
|
+
return {
|
|
90
|
+
summary: manifest && manifest.summary ? manifest.summary : '',
|
|
91
|
+
packageDescription: manifest && manifest.description ? manifest.description : '',
|
|
92
|
+
bundleName: bundle && bundle.name ? bundle.name : '',
|
|
93
|
+
version: bundle && bundle.version ? bundle.version : (manifest && manifest.version) || '',
|
|
94
|
+
source: bundle && bundle.source ? bundle.source : '',
|
|
95
|
+
official: bundle ? bundle.official === true : false,
|
|
96
|
+
document,
|
|
97
|
+
tools,
|
|
98
|
+
toolNote: tools.length ? '' : '没有在这个插件的代码里对上已注册、会交给 AI 的工具。',
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function revealPackage(payload) {
|
|
103
|
+
const input = payload && typeof payload === 'object' ? payload : {}
|
|
104
|
+
const moduleName = typeof input.moduleName === 'string' ? input.moduleName : ''
|
|
105
|
+
const source = typeof input.source === 'string' ? input.source : ''
|
|
106
|
+
const located = locatePackage(moduleName)
|
|
107
|
+
const dir = (located && located.root) || directoryFromSource(source)
|
|
108
|
+
if (!dir) throw new Error('找不到这个插件的目录。')
|
|
109
|
+
const command = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'explorer' : 'xdg-open'
|
|
110
|
+
const child = spawn(command, [dir], { detached: true, stdio: 'ignore' })
|
|
111
|
+
child.on('error', () => {})
|
|
112
|
+
child.unref()
|
|
113
|
+
return { path: dir }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function directoryFromSource(source) {
|
|
117
|
+
const raw = String(source || '')
|
|
118
|
+
const at = raw.lastIndexOf('@')
|
|
119
|
+
const text = (at >= 0 ? raw.slice(at + 1) : raw).replace(/^link:/, '')
|
|
120
|
+
if (!text || text.startsWith('http')) return ''
|
|
121
|
+
try {
|
|
122
|
+
const file = text.startsWith('file:') ? fileURLToPath(text) : text
|
|
123
|
+
if (!file.startsWith('/')) return ''
|
|
124
|
+
const stat = statSync(file)
|
|
125
|
+
const dir = stat.isDirectory() ? file : path.dirname(file)
|
|
126
|
+
return packageRoot(dir)
|
|
127
|
+
} catch {
|
|
128
|
+
return ''
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function readFacts(payload) {
|
|
133
|
+
const input = payload && typeof payload === 'object' ? payload : {}
|
|
134
|
+
const moduleName = typeof input.moduleName === 'string' ? input.moduleName : ''
|
|
135
|
+
const source = typeof input.source === 'string' ? input.source : ''
|
|
136
|
+
const located = locatePackage(moduleName)
|
|
137
|
+
const root = (located && located.root) || directoryFromSource(source)
|
|
138
|
+
const manifest = root ? readManifest(root) : null
|
|
139
|
+
const document = root ? readLocalDocument(root) : ''
|
|
140
|
+
if (!manifest && !document) return { manifest: null, document: '' }
|
|
141
|
+
return {
|
|
142
|
+
document,
|
|
143
|
+
manifest: manifest && {
|
|
144
|
+
name: manifest.name,
|
|
145
|
+
version: manifest.version,
|
|
146
|
+
description: manifest.description,
|
|
147
|
+
summary: manifest.summary,
|
|
148
|
+
license: manifest.license,
|
|
149
|
+
homepage: manifest.homepage,
|
|
150
|
+
repository: manifest.repository,
|
|
151
|
+
keywords: manifest.keywords,
|
|
152
|
+
engines: manifest.engines,
|
|
153
|
+
clientPlatform: manifest.clientPlatform,
|
|
154
|
+
clientInject: manifest.clientInject,
|
|
155
|
+
},
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function countTools(services, payload) {
|
|
160
|
+
const input = payload && typeof payload === 'object' ? payload : {}
|
|
161
|
+
const plugins = Array.isArray(input.plugins) ? input.plugins.slice(0, MAX_COUNT_BATCH) : []
|
|
162
|
+
const all = listTools(services.tools)
|
|
163
|
+
const counts = {}
|
|
164
|
+
for (const plugin of plugins) {
|
|
165
|
+
if (!plugin || typeof plugin.entryId !== 'string' || !plugin.entryId) continue
|
|
166
|
+
const moduleName = typeof plugin.moduleName === 'string' ? plugin.moduleName : ''
|
|
167
|
+
counts[plugin.entryId] = toolsForModule(all, moduleName).length
|
|
168
|
+
}
|
|
169
|
+
return { counts }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function toolsForModule(allTools, moduleName) {
|
|
173
|
+
const key = moduleName || ''
|
|
174
|
+
if (moduleTools.has(key)) return moduleTools.get(key)
|
|
175
|
+
const located = locatePackage(key)
|
|
176
|
+
const source = located ? readPackageSource(located.root, located.entryFile) : ''
|
|
177
|
+
const manifest = located ? readManifest(located.root) : null
|
|
178
|
+
const tools = ownedTools(allTools, source, manifest && manifest.name)
|
|
179
|
+
moduleTools.set(key, tools)
|
|
180
|
+
return tools
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function listTools(tools) {
|
|
184
|
+
if (!tools || typeof tools.definitions !== 'function') return []
|
|
185
|
+
const definitions = tools.definitions()
|
|
186
|
+
if (!Array.isArray(definitions)) return []
|
|
187
|
+
return definitions.map((tool) => ({
|
|
188
|
+
name: typeof tool.name === 'string' ? tool.name : '',
|
|
189
|
+
description: typeof tool.description === 'string' ? tool.description : '',
|
|
190
|
+
parameters: summarizeParams(tool.parameters),
|
|
191
|
+
})).filter((tool) => tool.name)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function summarizeParams(parameters) {
|
|
195
|
+
if (!parameters || typeof parameters !== 'object') return []
|
|
196
|
+
const props = parameters.properties && typeof parameters.properties === 'object'
|
|
197
|
+
? parameters.properties
|
|
198
|
+
: null
|
|
199
|
+
if (!props) return []
|
|
200
|
+
return Object.entries(props).slice(0, 16).map(([name, schema]) => ({
|
|
201
|
+
name,
|
|
202
|
+
type: schema && typeof schema.type === 'string' ? schema.type : '',
|
|
203
|
+
description: schema && typeof schema.description === 'string' ? schema.description : '',
|
|
204
|
+
}))
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function ownedTools(tools, source, packageName) {
|
|
208
|
+
if (!source) return []
|
|
209
|
+
const prefix = packagePrefix(packageName)
|
|
210
|
+
return tools.filter((tool) => {
|
|
211
|
+
if (source.includes(`'${tool.name}'`) || source.includes(`"${tool.name}"`) || source.includes(`\`${tool.name}\``)) return true
|
|
212
|
+
return prefix && (tool.name === prefix || tool.name.startsWith(`${prefix}_`) || tool.name.startsWith(`${prefix}-`))
|
|
213
|
+
})
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function packagePrefix(name) {
|
|
217
|
+
if (!name) return ''
|
|
218
|
+
const bare = name.startsWith('@') ? name.slice(name.indexOf('/') + 1) : name
|
|
219
|
+
return bare.replace(/^dsh-/, '').replace(/[^a-z0-9]+/gi, '_').replace(/^_|_$/g, '').toLowerCase()
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function findBundle(pluginManager, moduleName, entryId, packageName) {
|
|
223
|
+
if (!pluginManager || typeof pluginManager.listBundles !== 'function') return null
|
|
224
|
+
const bundles = await pluginManager.listBundles()
|
|
225
|
+
if (!Array.isArray(bundles)) return null
|
|
226
|
+
return bundles.find((bundle) => {
|
|
227
|
+
if (packageName && bundle.name === packageName) return true
|
|
228
|
+
if (moduleName && bundle.name === moduleName) return true
|
|
229
|
+
const rows = Array.isArray(bundle.rows) ? bundle.rows : []
|
|
230
|
+
return rows.some((row) => row.moduleName === moduleName || (entryId && row.entryId === entryId))
|
|
231
|
+
}) || null
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async function readDocument(pluginManager, name, language) {
|
|
235
|
+
if (!name || !pluginManager || typeof pluginManager.readBundleDetail !== 'function') return ''
|
|
236
|
+
try {
|
|
237
|
+
const detail = await pluginManager.readBundleDetail(name, language)
|
|
238
|
+
if (!detail || detail.status !== 'found' || typeof detail.markdown !== 'string') return ''
|
|
239
|
+
return detail.markdown.replace(/^---[\s\S]*?---\s*/, '').trim().slice(0, MAX_DOC_CHARS)
|
|
240
|
+
} catch {
|
|
241
|
+
return ''
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function locatePackage(moduleName) {
|
|
246
|
+
if (!moduleName) return null
|
|
247
|
+
if (moduleName.startsWith('file:')) {
|
|
248
|
+
const entryFile = fileURLToPath(moduleName)
|
|
249
|
+
return { entryFile, root: packageRoot(path.dirname(entryFile)) }
|
|
250
|
+
}
|
|
251
|
+
const profile = process.env.DSH_PROFILE_DIR || path.join(process.env.HOME || '', '.dsh/profiles/desktop')
|
|
252
|
+
try {
|
|
253
|
+
const req = createRequire(path.join(profile, 'package.json'))
|
|
254
|
+
const resolved = req.resolve(moduleName)
|
|
255
|
+
return { entryFile: resolved, root: packageRoot(path.dirname(resolved)) }
|
|
256
|
+
} catch {
|
|
257
|
+
try {
|
|
258
|
+
const direct = path.join(profile, 'node_modules', moduleName)
|
|
259
|
+
if (statSync(path.join(direct, 'package.json')).isFile()) return { entryFile: '', root: direct }
|
|
260
|
+
} catch { /* not installed under that folder name */ }
|
|
261
|
+
return null
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function packageRoot(start) {
|
|
266
|
+
let dir = start
|
|
267
|
+
for (let i = 0; i < 8; i += 1) {
|
|
268
|
+
try {
|
|
269
|
+
if (statSync(path.join(dir, 'package.json')).isFile()) return dir
|
|
270
|
+
} catch { /* keep walking */ }
|
|
271
|
+
const parent = path.dirname(dir)
|
|
272
|
+
if (parent === dir) break
|
|
273
|
+
dir = parent
|
|
274
|
+
}
|
|
275
|
+
return start
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function readManifest(root) {
|
|
279
|
+
try {
|
|
280
|
+
const manifest = JSON.parse(readFileSync(path.join(root, 'package.json'), 'utf8'))
|
|
281
|
+
const hub = manifest.dsh && manifest.dsh.hub
|
|
282
|
+
const client = manifest.dsh && manifest.dsh.client
|
|
283
|
+
return {
|
|
284
|
+
name: typeof manifest.name === 'string' ? manifest.name : '',
|
|
285
|
+
version: typeof manifest.version === 'string' ? manifest.version : '',
|
|
286
|
+
description: typeof manifest.description === 'string' ? manifest.description : '',
|
|
287
|
+
summary: hub && typeof hub.summary === 'string' ? hub.summary : '',
|
|
288
|
+
license: typeof manifest.license === 'string' ? manifest.license : '',
|
|
289
|
+
homepage: typeof manifest.homepage === 'string' ? manifest.homepage : '',
|
|
290
|
+
repository: repositoryOf(manifest.repository),
|
|
291
|
+
keywords: Array.isArray(manifest.keywords) ? manifest.keywords.filter((item) => typeof item === 'string').slice(0, 12) : [],
|
|
292
|
+
engines: manifest.engines && typeof manifest.engines.dsh === 'string' ? manifest.engines.dsh : '',
|
|
293
|
+
clientPlatform: client && typeof client.platform === 'string' ? client.platform : '',
|
|
294
|
+
clientInject: client && Array.isArray(client.inject) ? client.inject.filter((item) => typeof item === 'string').slice(0, 12) : [],
|
|
295
|
+
}
|
|
296
|
+
} catch {
|
|
297
|
+
return null
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function readLocalDocument(root) {
|
|
302
|
+
const names = ['docs/DETAIL.zh.md', 'docs/DETAIL.md', 'README.md', 'README.zh.md']
|
|
303
|
+
for (const name of names) {
|
|
304
|
+
try {
|
|
305
|
+
const text = readFileSync(path.join(root, name), 'utf8').replace(/^---[\s\S]*?---\s*/, '').trim()
|
|
306
|
+
if (text) return text.slice(0, 8000)
|
|
307
|
+
} catch { /* try the next name */ }
|
|
308
|
+
}
|
|
309
|
+
return ''
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function repositoryOf(value) {
|
|
313
|
+
if (typeof value === 'string') return value
|
|
314
|
+
if (value && typeof value.url === 'string') return value.url
|
|
315
|
+
return ''
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function readPackageSource(root, entryFile) {
|
|
319
|
+
const files = []
|
|
320
|
+
if (entryFile) files.push(entryFile)
|
|
321
|
+
collectJs(root, files, 0)
|
|
322
|
+
let text = ''
|
|
323
|
+
const seen = new Set()
|
|
324
|
+
for (const file of files) {
|
|
325
|
+
if (seen.has(file) || text.length >= MAX_SOURCE_BYTES) continue
|
|
326
|
+
seen.add(file)
|
|
327
|
+
try {
|
|
328
|
+
const chunk = readFileSync(file, 'utf8')
|
|
329
|
+
text += `\n${chunk.slice(0, 180_000)}`
|
|
330
|
+
} catch { /* unreadable file is skipped */ }
|
|
331
|
+
}
|
|
332
|
+
return text
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function collectJs(dir, files, depth) {
|
|
336
|
+
if (depth > 3 || files.length >= MAX_FILES) return
|
|
337
|
+
let entries
|
|
338
|
+
try { entries = readdirSync(dir, { withFileTypes: true }) } catch { return }
|
|
339
|
+
for (const entry of entries) {
|
|
340
|
+
if (files.length >= MAX_FILES) return
|
|
341
|
+
if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue
|
|
342
|
+
const full = path.join(dir, entry.name)
|
|
343
|
+
if (entry.isDirectory()) collectJs(full, files, depth + 1)
|
|
344
|
+
else if (/\.(js|mjs|cjs|ts)$/.test(entry.name)) files.push(full)
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function readJson(req) {
|
|
349
|
+
return new Promise((resolve, reject) => {
|
|
350
|
+
const chunks = []
|
|
351
|
+
let size = 0
|
|
352
|
+
req.on('data', (chunk) => {
|
|
353
|
+
size += chunk.length
|
|
354
|
+
if (size > 64_000) {
|
|
355
|
+
reject(new Error('Request body is too large.'))
|
|
356
|
+
req.destroy()
|
|
357
|
+
return
|
|
358
|
+
}
|
|
359
|
+
chunks.push(chunk)
|
|
360
|
+
})
|
|
361
|
+
req.on('end', () => {
|
|
362
|
+
const text = Buffer.concat(chunks).toString('utf8').trim()
|
|
363
|
+
if (!text) {
|
|
364
|
+
resolve({})
|
|
365
|
+
return
|
|
366
|
+
}
|
|
367
|
+
try { resolve(JSON.parse(text)) } catch { reject(new Error('Request body is not valid JSON.')) }
|
|
368
|
+
})
|
|
369
|
+
req.on('error', reject)
|
|
370
|
+
})
|
|
371
|
+
}
|
package/locale/en.json
ADDED
package/locale/zh.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,62 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-panel",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Plugin manager for the DeepSeek Harness right sidebar: list installed plugins, see where each one lives, pin favourites, and open a plugin folder in Finder.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./index.js",
|
|
8
|
+
"./client": "./client.js",
|
|
9
|
+
"./locale/*.json": "./locale/*.json",
|
|
10
|
+
"./icon": "./icon.svg"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"index.js",
|
|
14
|
+
"client.js",
|
|
15
|
+
"catalog.js",
|
|
16
|
+
"catalog.test.mjs",
|
|
17
|
+
"cordis.patch.yml",
|
|
18
|
+
"locale/*.json",
|
|
19
|
+
"docs",
|
|
20
|
+
"icon.svg",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
23
|
+
"dsh": {
|
|
24
|
+
"bundle": {
|
|
25
|
+
"patch": "./cordis.patch.yml"
|
|
26
|
+
},
|
|
27
|
+
"client": {
|
|
28
|
+
"platform": "web",
|
|
29
|
+
"inject": [
|
|
30
|
+
"@deepseek-ai/dsh-api-remotes",
|
|
31
|
+
"@deepseek-ai/dsh-client-ui-layout",
|
|
32
|
+
"@deepseek-ai/dsh-client-ui-sidebar",
|
|
33
|
+
"@deepseek-ai/dsh-client-ui-sidebar-right",
|
|
34
|
+
"@deepseek-ai/dsh-client-locale"
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"dsh": ">=0.2.2-rc.1"
|
|
40
|
+
},
|
|
41
|
+
"keywords": [
|
|
42
|
+
"dsh",
|
|
43
|
+
"deepseek-harness",
|
|
44
|
+
"dsh-plugin",
|
|
45
|
+
"plugin-manager",
|
|
46
|
+
"sidebar",
|
|
47
|
+
"插件管理"
|
|
48
|
+
],
|
|
49
|
+
"author": {
|
|
50
|
+
"name": "jqlong17",
|
|
51
|
+
"url": "https://github.com/jqlong17"
|
|
52
|
+
},
|
|
53
|
+
"license": "MIT",
|
|
54
|
+
"repository": {
|
|
55
|
+
"type": "git",
|
|
56
|
+
"url": "git+https://github.com/jqlong17/dsh-plugins-ui.git"
|
|
57
|
+
},
|
|
58
|
+
"homepage": "https://github.com/jqlong17/dsh-plugins-ui#readme",
|
|
59
|
+
"bugs": {
|
|
60
|
+
"url": "https://github.com/jqlong17/dsh-plugins-ui/issues"
|
|
61
|
+
}
|
|
62
|
+
}
|