dsh-plugin-showcase 0.1.0 → 0.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.
- package/README.md +221 -126
- package/README.zh.md +180 -92
- package/lib/client.js +121 -7
- package/lib/index.js +337 -7
- package/market/index.json +53 -0
- package/package.json +4 -3
package/lib/index.js
CHANGED
|
@@ -4,13 +4,20 @@ import { join } from 'node:path'
|
|
|
4
4
|
export const name = 'showcase'
|
|
5
5
|
export const inject = ['tools', 'commands']
|
|
6
6
|
|
|
7
|
-
const VERSION = '0.
|
|
7
|
+
const VERSION = '0.2.0'
|
|
8
8
|
const TOOL_NAME = 'showcase_inspect'
|
|
9
|
+
const DOCTOR_TOOL_NAME = 'showcase_doctor'
|
|
9
10
|
const COMMAND_NAME = 'showcase'
|
|
10
11
|
const DENY_REASON = 'showcase: 拒绝执行破坏性命令'
|
|
11
|
-
const USAGE = '/showcase [on|off]'
|
|
12
|
+
const USAGE = '/showcase [on|off|doctor|about]'
|
|
12
13
|
const UNKNOWN = '未知'
|
|
13
14
|
|
|
15
|
+
const LOCALE_NAMESPACE = 'locale'
|
|
16
|
+
const LOCALE_FIELD = 'preference'
|
|
17
|
+
const APP_BOOT_PACKAGE = '@deepseek-ai/dsh-app-boot'
|
|
18
|
+
const STATUS_LABELS = Object.freeze({ ok: 'OK', warn: 'WARN', fail: 'FAIL', unknown: '?' })
|
|
19
|
+
const HINT_FAULT = '这是 showcase 自身的缺陷:重跑 /showcase doctor,仍失败请到插件页反馈'
|
|
20
|
+
|
|
14
21
|
const DESTRUCTIVE_PATTERNS = Object.freeze([
|
|
15
22
|
'rm -rf /',
|
|
16
23
|
'rm -rf ~',
|
|
@@ -198,7 +205,7 @@ async function buildSnapshot(ctx, state, agent) {
|
|
|
198
205
|
generatedAt: new Date().toISOString(),
|
|
199
206
|
workspace: { cwd, gitBranch },
|
|
200
207
|
session: { id: sessionId, model: sessionModel },
|
|
201
|
-
plugin: { version:
|
|
208
|
+
plugin: { version: (await readOwnManifest()).version, gateEnabled: gate, toolName: TOOL_NAME, command: '/' + COMMAND_NAME },
|
|
202
209
|
sections: [
|
|
203
210
|
{
|
|
204
211
|
title: '工作区',
|
|
@@ -217,7 +224,7 @@ async function buildSnapshot(ctx, state, agent) {
|
|
|
217
224
|
{
|
|
218
225
|
title: '插件',
|
|
219
226
|
items: [
|
|
220
|
-
{ label: '版本', value:
|
|
227
|
+
{ label: '版本', value: (await readOwnManifest()).version },
|
|
221
228
|
{ label: '拦截门', value: gate ? '开启' : '关闭' },
|
|
222
229
|
{ label: '工具', value: TOOL_NAME },
|
|
223
230
|
{ label: '命令', value: '/' + COMMAND_NAME },
|
|
@@ -227,6 +234,307 @@ async function buildSnapshot(ctx, state, agent) {
|
|
|
227
234
|
}
|
|
228
235
|
}
|
|
229
236
|
|
|
237
|
+
function messageOf(error) {
|
|
238
|
+
return error instanceof Error ? error.message : String(error)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
let ownManifestPromise = null
|
|
242
|
+
|
|
243
|
+
async function loadOwnManifest() {
|
|
244
|
+
try {
|
|
245
|
+
const text = await readFile(new URL('../package.json', import.meta.url), 'utf8')
|
|
246
|
+
const parsed = JSON.parse(text)
|
|
247
|
+
const engines = parsed !== null && typeof parsed === 'object' && parsed.engines !== null && typeof parsed.engines === 'object'
|
|
248
|
+
? parsed.engines
|
|
249
|
+
: {}
|
|
250
|
+
return {
|
|
251
|
+
version: typeof parsed.version === 'string' && parsed.version !== '' ? parsed.version : VERSION,
|
|
252
|
+
dshRange: typeof engines.dsh === 'string' && engines.dsh !== '' ? engines.dsh : null,
|
|
253
|
+
}
|
|
254
|
+
} catch {
|
|
255
|
+
return { version: VERSION, dshRange: null }
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function readOwnManifest() {
|
|
260
|
+
if (ownManifestPromise === null) ownManifestPromise = loadOwnManifest()
|
|
261
|
+
return ownManifestPromise
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const SEMVER = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/u
|
|
265
|
+
const COMPARATOR = /^(>=|<=|>|<|=|\^|~)?(\S+)$/u
|
|
266
|
+
|
|
267
|
+
function parseSemver(text) {
|
|
268
|
+
const match = typeof text === 'string' ? SEMVER.exec(text.trim()) : null
|
|
269
|
+
if (match === null) return null
|
|
270
|
+
return {
|
|
271
|
+
major: Number(match[1]),
|
|
272
|
+
minor: Number(match[2]),
|
|
273
|
+
patch: Number(match[3]),
|
|
274
|
+
pre: match[4] === undefined ? [] : match[4].split('.'),
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function compareIdentifier(left, right) {
|
|
279
|
+
const leftNumeric = /^\d+$/u.test(left)
|
|
280
|
+
const rightNumeric = /^\d+$/u.test(right)
|
|
281
|
+
if (leftNumeric && rightNumeric) return Number(left) - Number(right)
|
|
282
|
+
if (leftNumeric) return -1
|
|
283
|
+
if (rightNumeric) return 1
|
|
284
|
+
return left === right ? 0 : left < right ? -1 : 1
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function compareSemver(left, right) {
|
|
288
|
+
for (const key of ['major', 'minor', 'patch']) {
|
|
289
|
+
if (left[key] !== right[key]) return left[key] - right[key]
|
|
290
|
+
}
|
|
291
|
+
if (left.pre.length === 0 && right.pre.length === 0) return 0
|
|
292
|
+
if (left.pre.length === 0) return 1
|
|
293
|
+
if (right.pre.length === 0) return -1
|
|
294
|
+
const length = Math.max(left.pre.length, right.pre.length)
|
|
295
|
+
for (let index = 0; index < length; index += 1) {
|
|
296
|
+
const leftPart = left.pre[index]
|
|
297
|
+
const rightPart = right.pre[index]
|
|
298
|
+
if (leftPart === undefined) return -1
|
|
299
|
+
if (rightPart === undefined) return 1
|
|
300
|
+
const delta = compareIdentifier(leftPart, rightPart)
|
|
301
|
+
if (delta !== 0) return delta
|
|
302
|
+
}
|
|
303
|
+
return 0
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function upperBound(operator, target) {
|
|
307
|
+
if (operator === '^') {
|
|
308
|
+
if (target.major > 0) return { major: target.major + 1, minor: 0, patch: 0, pre: [] }
|
|
309
|
+
if (target.minor > 0) return { major: 0, minor: target.minor + 1, patch: 0, pre: [] }
|
|
310
|
+
return { major: 0, minor: 0, patch: target.patch + 1, pre: [] }
|
|
311
|
+
}
|
|
312
|
+
return { major: target.major, minor: target.minor + 1, patch: 0, pre: [] }
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** true/false when the range is a plain comparator set; null when it is not one this host can parse offline. */
|
|
316
|
+
function satisfiesRange(version, range) {
|
|
317
|
+
const text = typeof range === 'string' ? range.trim() : ''
|
|
318
|
+
if (text === '' || text === '*' || text === 'x') return true
|
|
319
|
+
if (text.includes('||')) return null
|
|
320
|
+
for (const token of text.split(/\s+/u).filter(part => part !== '')) {
|
|
321
|
+
const match = COMPARATOR.exec(token)
|
|
322
|
+
if (match === null) return null
|
|
323
|
+
const operator = match[1] === undefined ? '=' : match[1]
|
|
324
|
+
const target = parseSemver(match[2])
|
|
325
|
+
if (target === null) return null
|
|
326
|
+
const delta = compareSemver(version, target)
|
|
327
|
+
if (operator === '>=' && delta < 0) return false
|
|
328
|
+
if (operator === '>' && delta <= 0) return false
|
|
329
|
+
if (operator === '<=' && delta > 0) return false
|
|
330
|
+
if (operator === '<' && delta >= 0) return false
|
|
331
|
+
if (operator === '=' && delta !== 0) return false
|
|
332
|
+
if (operator === '^' || operator === '~') {
|
|
333
|
+
if (delta < 0) return false
|
|
334
|
+
if (compareSemver(version, upperBound(operator, target)) >= 0) return false
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return true
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async function readDshVersion(ctx) {
|
|
341
|
+
const profile = readService(ctx, 'profileContext')
|
|
342
|
+
const dir = profile !== null && typeof profile.dir === 'string' && profile.dir !== '' ? profile.dir : null
|
|
343
|
+
if (dir === null) return null
|
|
344
|
+
try {
|
|
345
|
+
const parts = APP_BOOT_PACKAGE.split('/')
|
|
346
|
+
const text = await readFile(join(dir, 'node_modules', ...parts, 'package.json'), 'utf8')
|
|
347
|
+
const parsed = JSON.parse(text)
|
|
348
|
+
const version = parsed !== null && typeof parsed === 'object' ? parsed.version : undefined
|
|
349
|
+
return typeof version === 'string' && version !== '' ? version : null
|
|
350
|
+
} catch {
|
|
351
|
+
return null
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function readLocale(ctx) {
|
|
356
|
+
const settings = readService(ctx, 'settings')
|
|
357
|
+
if (settings === null || typeof settings.describe !== 'function') return null
|
|
358
|
+
const descriptors = settings.describe()
|
|
359
|
+
if (!Array.isArray(descriptors)) return null
|
|
360
|
+
for (const descriptor of descriptors) {
|
|
361
|
+
if (descriptor === null || typeof descriptor !== 'object') continue
|
|
362
|
+
if (String(descriptor.ns) !== LOCALE_NAMESPACE) continue
|
|
363
|
+
const value = descriptor.value !== null && typeof descriptor.value === 'object' ? descriptor.value : {}
|
|
364
|
+
const preference = typeof value[LOCALE_FIELD] === 'string' ? value[LOCALE_FIELD] : ''
|
|
365
|
+
return preference === '' ? null : preference
|
|
366
|
+
}
|
|
367
|
+
return null
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function readMarketIndexes(ctx) {
|
|
371
|
+
const market = readService(ctx, 'pluginMarket')
|
|
372
|
+
if (market === null || typeof market.listIndexes !== 'function') return { available: false, entries: [] }
|
|
373
|
+
const entries = market.listIndexes()
|
|
374
|
+
if (!Array.isArray(entries)) return { available: true, entries: [] }
|
|
375
|
+
return { available: true, entries: entries.filter(entry => entry !== null && typeof entry === 'object') }
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function checkEngine(manifest, dshVersion) {
|
|
379
|
+
if (dshVersion === null) {
|
|
380
|
+
return {
|
|
381
|
+
status: 'unknown',
|
|
382
|
+
detail: '读不到 DSH 运行时版本',
|
|
383
|
+
hint: '需要 profileContext 服务,且 <profile>/node_modules/' + APP_BOOT_PACKAGE + '/package.json 可读',
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
const range = manifest.dshRange
|
|
387
|
+
if (range === null) {
|
|
388
|
+
return { status: 'warn', detail: '运行 ' + dshVersion + ',package.json 未声明 engines.dsh', hint: '补上 engines.dsh 后本项可比对兼容性' }
|
|
389
|
+
}
|
|
390
|
+
const parsed = parseSemver(dshVersion)
|
|
391
|
+
const satisfied = parsed === null ? null : satisfiesRange(parsed, range)
|
|
392
|
+
if (satisfied === null) {
|
|
393
|
+
return { status: 'unknown', detail: '运行 ' + dshVersion + ',要求 ' + range + '(范围写法无法离线解析)', hint: '该范围不是简单比较式,请人工确认兼容性' }
|
|
394
|
+
}
|
|
395
|
+
if (satisfied) return { status: 'ok', detail: '运行 ' + dshVersion + ',要求 ' + range, hint: null }
|
|
396
|
+
return { status: 'fail', detail: '运行 ' + dshVersion + ',要求 ' + range, hint: '升级 DSH,或改用与本机版本兼容的插件版本' }
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function checkCwd(cwd) {
|
|
400
|
+
if (cwd === '') return { status: 'warn', detail: '读不到工作目录', hint: '需要 workingDirectory 服务,或本会话尚未选定目录' }
|
|
401
|
+
return { status: 'ok', detail: cwd, hint: null }
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function checkModel(model) {
|
|
405
|
+
if (model === null) return { status: 'unknown', detail: '读不到默认模型', hint: '需要 agentDefaultModel 服务,或在设置里选定一次默认模型' }
|
|
406
|
+
return { status: 'ok', detail: model, hint: null }
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function checkGate(gateEnabled) {
|
|
410
|
+
if (gateEnabled) return { status: 'ok', detail: '拦截门已开启', hint: null }
|
|
411
|
+
return { status: 'warn', detail: '拦截门已关闭', hint: '用 /showcase on 重新开启' }
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function checkLocale(locale) {
|
|
415
|
+
if (locale === null) return { status: 'unknown', detail: '读不到宿主语言偏好', hint: '在设置里显式选择界面语言后,本项会显示 locale 偏好' }
|
|
416
|
+
return { status: 'ok', detail: locale, hint: null }
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function checkMarket(market) {
|
|
420
|
+
if (!market.available) return { status: 'unknown', detail: '读不到市场索引服务 pluginMarket', hint: '该部署未挂载插件市场时本项可以忽略' }
|
|
421
|
+
if (market.entries.length === 0) return { status: 'warn', detail: '未配置市场索引源', hint: '在设置 → 插件市场里添加索引 URL' }
|
|
422
|
+
const describe = entry => (typeof entry.url === 'string' && entry.url !== '' ? entry.url : UNKNOWN)
|
|
423
|
+
const broken = market.entries.filter(entry => typeof entry.error === 'string' && entry.error !== '')
|
|
424
|
+
if (broken.length > 0) {
|
|
425
|
+
return {
|
|
426
|
+
status: 'warn',
|
|
427
|
+
detail: market.entries.length + ' 个索引源,' + broken.length + ' 个不可用: ' + broken.map(describe).join(', '),
|
|
428
|
+
hint: '修正或删除不可用的索引 URL',
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
return { status: 'ok', detail: market.entries.length + ' 个索引源: ' + market.entries.map(describe).join(', '), hint: null }
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
async function runCheck(id, title, produce) {
|
|
435
|
+
try {
|
|
436
|
+
const result = await produce()
|
|
437
|
+
if (result === null || typeof result !== 'object') {
|
|
438
|
+
return { id, title, status: 'unknown', detail: '检查未返回结果', hint: HINT_FAULT }
|
|
439
|
+
}
|
|
440
|
+
const status = STATUS_LABELS[result.status] === undefined ? 'unknown' : result.status
|
|
441
|
+
return {
|
|
442
|
+
id,
|
|
443
|
+
title,
|
|
444
|
+
status,
|
|
445
|
+
detail: typeof result.detail === 'string' ? result.detail : '',
|
|
446
|
+
hint: typeof result.hint === 'string' && result.hint !== '' ? result.hint : null,
|
|
447
|
+
}
|
|
448
|
+
} catch (error) {
|
|
449
|
+
return { id, title, status: 'unknown', detail: '检查异常: ' + messageOf(error), hint: HINT_FAULT }
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function buildHealthSchema() {
|
|
454
|
+
return {
|
|
455
|
+
type: 'object',
|
|
456
|
+
additionalProperties: false,
|
|
457
|
+
required: ['generatedAt', 'pluginVersion', 'dshVersion', 'summary', 'checks'],
|
|
458
|
+
properties: {
|
|
459
|
+
generatedAt: { type: 'string', description: 'ISO 8601 timestamp of this report.' },
|
|
460
|
+
pluginVersion: { type: 'string' },
|
|
461
|
+
dshVersion: { ...nullableString(), description: 'Running DSH version, null when unreadable.' },
|
|
462
|
+
summary: {
|
|
463
|
+
type: 'object',
|
|
464
|
+
additionalProperties: false,
|
|
465
|
+
required: ['ok', 'warn', 'fail', 'unknown'],
|
|
466
|
+
properties: {
|
|
467
|
+
ok: { type: 'integer' },
|
|
468
|
+
warn: { type: 'integer' },
|
|
469
|
+
fail: { type: 'integer' },
|
|
470
|
+
unknown: { type: 'integer' },
|
|
471
|
+
},
|
|
472
|
+
},
|
|
473
|
+
checks: {
|
|
474
|
+
type: 'array',
|
|
475
|
+
items: {
|
|
476
|
+
type: 'object',
|
|
477
|
+
additionalProperties: false,
|
|
478
|
+
required: ['id', 'title', 'status', 'detail', 'hint'],
|
|
479
|
+
properties: {
|
|
480
|
+
id: { type: 'string' },
|
|
481
|
+
title: { type: 'string' },
|
|
482
|
+
status: { type: 'string', enum: ['ok', 'warn', 'fail', 'unknown'] },
|
|
483
|
+
detail: { type: 'string' },
|
|
484
|
+
hint: { ...nullableString(), description: 'Next step, null when there is none.' },
|
|
485
|
+
},
|
|
486
|
+
},
|
|
487
|
+
},
|
|
488
|
+
},
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function renderHealth(value) {
|
|
493
|
+
const report = value !== null && typeof value === 'object' ? value : {}
|
|
494
|
+
const generatedAt = typeof report.generatedAt === 'string' ? report.generatedAt : ''
|
|
495
|
+
const lines = ['DSH 体检报告 ' + generatedAt]
|
|
496
|
+
const checks = Array.isArray(report.checks) ? report.checks : []
|
|
497
|
+
for (const check of checks) {
|
|
498
|
+
if (check === null || typeof check !== 'object') continue
|
|
499
|
+
const label = STATUS_LABELS[check.status] === undefined ? STATUS_LABELS.unknown : STATUS_LABELS[check.status]
|
|
500
|
+
const title = typeof check.title === 'string' ? check.title : UNKNOWN
|
|
501
|
+
const detail = typeof check.detail === 'string' ? check.detail : ''
|
|
502
|
+
lines.push('[' + label + '] ' + title + ' — ' + detail)
|
|
503
|
+
if (typeof check.hint === 'string' && check.hint !== '') lines.push(' ↳ ' + check.hint)
|
|
504
|
+
}
|
|
505
|
+
const summary = report.summary !== null && typeof report.summary === 'object' ? report.summary : {}
|
|
506
|
+
const count = key => (typeof summary[key] === 'number' ? summary[key] : 0)
|
|
507
|
+
lines.push('合计: ok ' + count('ok') + ' / warn ' + count('warn') + ' / fail ' + count('fail') + ' / unknown ' + count('unknown'))
|
|
508
|
+
return lines.join('\n')
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
async function buildHealthReport(ctx, state, agent) {
|
|
512
|
+
const manifest = await readOwnManifest()
|
|
513
|
+
const [dshVersion, cwd] = await Promise.all([readDshVersion(ctx), readCwd(ctx, agent)])
|
|
514
|
+
const model = readModel(ctx)
|
|
515
|
+
const checks = await Promise.all([
|
|
516
|
+
runCheck('engine.dsh', 'DSH 版本', () => checkEngine(manifest, dshVersion)),
|
|
517
|
+
runCheck('workspace.cwd', '工作目录', () => checkCwd(cwd)),
|
|
518
|
+
runCheck('model.default', '默认模型', () => checkModel(model)),
|
|
519
|
+
runCheck('plugin.gate', '拦截门', () => checkGate(state.gateEnabled)),
|
|
520
|
+
runCheck('plugin.locale', '界面语言', () => checkLocale(readLocale(ctx))),
|
|
521
|
+
runCheck('market.index', '市场索引', () => checkMarket(readMarketIndexes(ctx))),
|
|
522
|
+
])
|
|
523
|
+
const summary = { ok: 0, warn: 0, fail: 0, unknown: 0 }
|
|
524
|
+
for (const check of checks) summary[check.status] += 1
|
|
525
|
+
return { generatedAt: new Date().toISOString(), pluginVersion: manifest.version, dshVersion, summary, checks }
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
async function aboutText(ctx, state) {
|
|
529
|
+
const manifest = await readOwnManifest()
|
|
530
|
+
const dshVersion = await readDshVersion(ctx)
|
|
531
|
+
return 'dsh-plugin-showcase ' + manifest.version
|
|
532
|
+
+ ' · 工具 ' + TOOL_NAME + '、' + DOCTOR_TOOL_NAME
|
|
533
|
+
+ ' · 命令 /' + COMMAND_NAME + ' on|off|doctor|about'
|
|
534
|
+
+ ' · 拦截门' + (state.gateEnabled ? '已开启' : '已关闭')
|
|
535
|
+
+ ' · DSH ' + (dshVersion === null ? UNKNOWN : dshVersion)
|
|
536
|
+
}
|
|
537
|
+
|
|
230
538
|
function collectCommandText(exec) {
|
|
231
539
|
const args = exec !== null && typeof exec === 'object' ? exec.arguments : null
|
|
232
540
|
if (args === null || typeof args !== 'object') return ''
|
|
@@ -278,8 +586,10 @@ async function runCommand(ctx, state, invocation) {
|
|
|
278
586
|
state.gateEnabled = false
|
|
279
587
|
return { kind: 'success', text: 'showcase 拦截门: 已关闭' }
|
|
280
588
|
}
|
|
281
|
-
if (raw !== '') return { kind: 'success', text: USAGE }
|
|
282
589
|
const agent = invocation !== null && typeof invocation === 'object' ? invocation.agent ?? null : null
|
|
590
|
+
if (raw === 'doctor') return { kind: 'success', text: renderHealth(await buildHealthReport(ctx, state, agent)) }
|
|
591
|
+
if (raw === 'about') return { kind: 'success', text: await aboutText(ctx, state) }
|
|
592
|
+
if (raw !== '') return { kind: 'success', text: USAGE }
|
|
283
593
|
return { kind: 'success', text: renderSnapshot(await buildSnapshot(ctx, state, agent)) }
|
|
284
594
|
}
|
|
285
595
|
|
|
@@ -306,7 +616,27 @@ export function apply(ctx) {
|
|
|
306
616
|
},
|
|
307
617
|
}
|
|
308
618
|
|
|
619
|
+
const doctorTool = {
|
|
620
|
+
name: DOCTOR_TOOL_NAME,
|
|
621
|
+
description: 'Run the DSH health check and return one report: engine version, working directory, default model, gate state, UI language, and market index sources.',
|
|
622
|
+
parameters: {
|
|
623
|
+
type: 'object',
|
|
624
|
+
additionalProperties: false,
|
|
625
|
+
properties: {},
|
|
626
|
+
required: [],
|
|
627
|
+
},
|
|
628
|
+
output: {
|
|
629
|
+
schema: buildHealthSchema(),
|
|
630
|
+
render: (_args, value) => [{ type: 'text', text: renderHealth(value) }],
|
|
631
|
+
},
|
|
632
|
+
async execute(_args, exec) {
|
|
633
|
+
const agent = exec !== null && typeof exec === 'object' ? exec.agent ?? null : null
|
|
634
|
+
return await buildHealthReport(ctx, state, agent)
|
|
635
|
+
},
|
|
636
|
+
}
|
|
637
|
+
|
|
309
638
|
ctx.effect(() => ctx.tools.register(inspectTool), 'showcase: inspect tool')
|
|
639
|
+
ctx.effect(() => ctx.tools.register(doctorTool), 'showcase: doctor tool')
|
|
310
640
|
|
|
311
641
|
ctx.effect(() => ctx.on('tools/pre-execute', async (exec, next) => {
|
|
312
642
|
let decision = null
|
|
@@ -321,8 +651,8 @@ export function apply(ctx) {
|
|
|
321
651
|
|
|
322
652
|
ctx.effect(() => ctx.commands.register({
|
|
323
653
|
name: COMMAND_NAME,
|
|
324
|
-
description: 'Inspect this DSH host. Usage: /showcase [on|off]',
|
|
325
|
-
input: { hint: '[on|off]' },
|
|
654
|
+
description: 'Inspect this DSH host. Usage: /showcase [on|off|doctor|about]',
|
|
655
|
+
input: { hint: '[on|off|doctor|about]' },
|
|
326
656
|
handler: invocation => runCommand(ctx, state, invocation),
|
|
327
657
|
}), 'showcase: /showcase command')
|
|
328
658
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"format": 1,
|
|
3
|
+
"title": {
|
|
4
|
+
"en": "dsh-plugin-showcase",
|
|
5
|
+
"zh": "功能展示"
|
|
6
|
+
},
|
|
7
|
+
"description": {
|
|
8
|
+
"en": "Self-hosted index shipped inside the dsh-plugin-showcase package.",
|
|
9
|
+
"zh": "随 dsh-plugin-showcase 包一起分发的自托管索引。"
|
|
10
|
+
},
|
|
11
|
+
"metadata": {
|
|
12
|
+
"categories": {},
|
|
13
|
+
"labels": {},
|
|
14
|
+
"sortKeys": {}
|
|
15
|
+
},
|
|
16
|
+
"plugins": [
|
|
17
|
+
{
|
|
18
|
+
"name": "dsh-plugin-showcase",
|
|
19
|
+
"title": {
|
|
20
|
+
"en": "Showcase",
|
|
21
|
+
"zh": "功能展示"
|
|
22
|
+
},
|
|
23
|
+
"description": {
|
|
24
|
+
"en": "DeepSeek Harness reference plugin: tools, hooks, commands, a settings card and a sidebar panel.",
|
|
25
|
+
"zh": "DeepSeek Harness 参考插件:工具、钩子、命令、设置卡片与侧栏面板。"
|
|
26
|
+
},
|
|
27
|
+
"icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCIgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0Ij4KICA8cmVjdCB4PSI0IiB5PSI0IiB3aWR0aD0iNTYiIGhlaWdodD0iNTYiIHJ4PSIxNCIgZmlsbD0iIzJmNmZlZCIvPgogIDxjaXJjbGUgY3g9IjMyIiBjeT0iMzIiIHI9IjE1IiBmaWxsPSJub25lIiBzdHJva2U9IiNmZmYiIHN0cm9rZS13aWR0aD0iMyIgc3Ryb2tlLWRhc2hhcnJheT0iNyA1Ii8+CiAgPHRleHQgeD0iMzIiIHk9IjQxIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIyMiIgZm9udC13ZWlnaHQ9IjcwMCIgZmlsbD0iI2ZmZiIgdGV4dC1hbmNob3I9Im1pZGRsZSI+UzwvdGV4dD4KPC9zdmc+Cg==",
|
|
28
|
+
"author": "antonioxue",
|
|
29
|
+
"metadata": {
|
|
30
|
+
"keywords": [
|
|
31
|
+
"deepseek-harness",
|
|
32
|
+
"dsh-plugin",
|
|
33
|
+
"showcase",
|
|
34
|
+
"example"
|
|
35
|
+
],
|
|
36
|
+
"labels": [],
|
|
37
|
+
"sortKeys": {}
|
|
38
|
+
},
|
|
39
|
+
"versions": [
|
|
40
|
+
{
|
|
41
|
+
"version": "0.2.0",
|
|
42
|
+
"publishTimestamp": 1792833123,
|
|
43
|
+
"source": {
|
|
44
|
+
"type": "npm"
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"dsh": ">=0.2.1-rc.2"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-showcase",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "vitest run"
|
|
7
7
|
},
|
|
8
|
-
"description": "DeepSeek Harness
|
|
8
|
+
"description": "DeepSeek Harness starter companion: a health-check tool, a destructive-command gate, a slash command, a settings card and a right-sidebar guide — plus a self-hosted market index.",
|
|
9
9
|
"icon": "./icon.svg",
|
|
10
10
|
"main": "./lib/index.js",
|
|
11
11
|
"exports": {
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
"icon.svg",
|
|
26
26
|
"README.md",
|
|
27
27
|
"README.zh.md",
|
|
28
|
-
"LICENSE"
|
|
28
|
+
"LICENSE",
|
|
29
|
+
"market"
|
|
29
30
|
],
|
|
30
31
|
"dsh": {
|
|
31
32
|
"manifestVersion": 1,
|