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/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.1.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: VERSION, gateEnabled: gate, toolName: TOOL_NAME, command: '/' + COMMAND_NAME },
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: VERSION },
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.1.0",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "test": "vitest run"
7
7
  },
8
- "description": "DeepSeek Harness reference plugin: one tool, one slash command, a settings card and a right-sidebar panel.",
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,