dsh-plugin-capabilities 0.3.0 → 0.3.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.
@@ -129,6 +129,8 @@ export function MarketTab(props: { t: Translate; market: MarketInjected; mcp: Mc
129
129
  const pick = <T,>(base: T, zh: T | undefined): T => (zhUi() && zh !== undefined ? zh : base)
130
130
  const title = (entry: { name: string; nameZh?: string }): string => pick(entry.name, entry.nameZh)
131
131
  const desc = (entry: { description: string; descriptionZh?: string }): string => pick(entry.description, entry.descriptionZh)
132
+ const longText = (entry: { description: string; descriptionZh?: string; detail?: string; detailZh?: string }): string =>
133
+ entry.detail !== undefined ? pick(entry.detail, entry.detailZh) : desc(entry)
132
134
 
133
135
  const cardStop = (event: { stopPropagation(): void }): void => { event.stopPropagation() }
134
136
 
@@ -296,20 +298,27 @@ export function MarketTab(props: { t: Translate; market: MarketInjected; mcp: Mc
296
298
  >
297
299
  {detailRepo !== null && (
298
300
  <>
299
- <p className="dpc-detailDesc">{desc(detailRepo)}</p>
301
+ <p className="dpc-detailDesc">{longText(detailRepo)}</p>
300
302
  {detailRepo.skills !== undefined && detailRepo.skills.length > 0 && (
301
303
  <div className="dpc-detailSection">
302
304
  <div className="dpc-detailLabel">{t('marketSkillListLabel')}</div>
303
- <div className="dpc-detailTags">
304
- {detailRepo.skills.map(name => <span className="dpc-tag" key={name}>{name}</span>)}
305
- </div>
305
+ <ul className="dpc-skillList">
306
+ {detailRepo.skills.map(item => (
307
+ <li className="dpc-skillItem" key={item.name}>
308
+ <code className="dpc-skillName">{item.name}</code>
309
+ {item.description !== undefined && (
310
+ <span className="dpc-skillDesc">{pick(item.description, item.descriptionZh)}</span>
311
+ )}
312
+ </li>
313
+ ))}
314
+ </ul>
306
315
  </div>
307
316
  )}
308
317
  </>
309
318
  )}
310
319
  {detailServer !== null && (
311
320
  <>
312
- <p className="dpc-detailDesc">{desc(detailServer)}</p>
321
+ <p className="dpc-detailDesc">{longText(detailServer)}</p>
313
322
  {detailServer.command !== undefined && (
314
323
  <div className="dpc-detailSection">
315
324
  <div className="dpc-detailLabel">{t('marketCommandLabel')}</div>
package/src/client/css.ts CHANGED
@@ -113,5 +113,9 @@ export const CSS = `
113
113
  .dpc-detailSection{margin-top:12px;display:flex;flex-direction:column;gap:6px}
114
114
  .dpc-detailLabel{font-size:11px;line-height:16px;color:var(--dsw-alias-label-secondary)}
115
115
  .dpc-detailTags{display:flex;flex-wrap:wrap;gap:4px}
116
+ .dpc-skillList{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:4px}
117
+ .dpc-skillItem{display:flex;gap:8px;align-items:baseline;font-size:12px;line-height:18px;min-width:0}
118
+ .dpc-skillName{font-family:var(--ds-font-family-code);color:var(--dsw-alias-label-primary);white-space:nowrap;flex:none}
119
+ .dpc-skillDesc{color:var(--dsw-alias-label-secondary);min-width:0}
116
120
  @media(max-width:680px){.dpc-cards{grid-template-columns:minmax(0,1fr)}}
117
121
  `
@@ -131,6 +131,13 @@ export function apply(ctx: CapabilitiesClientContext): void {
131
131
  })
132
132
  }
133
133
 
134
+ /** One skill inside a market repository's inventory, as the browser sees it. */
135
+ export interface MarketSkillItemView {
136
+ name: string
137
+ description?: string
138
+ descriptionZh?: string
139
+ }
140
+
134
141
  /** One skills-market repository as the browser sees it. */
135
142
  export interface MarketRepoView {
136
143
  id: string
@@ -138,10 +145,12 @@ export interface MarketRepoView {
138
145
  nameZh?: string
139
146
  description: string
140
147
  descriptionZh?: string
148
+ detail?: string
149
+ detailZh?: string
141
150
  url: string
142
151
  homepage?: string
143
152
  skillCount?: number
144
- skills?: string[]
153
+ skills?: MarketSkillItemView[]
145
154
  }
146
155
 
147
156
  /** One MCP-market server as the browser sees it. */
@@ -160,4 +169,6 @@ export interface MarketServerView {
160
169
  category?: string
161
170
  runtime?: string
162
171
  tools?: string[]
172
+ detail?: string
173
+ detailZh?: string
163
174
  }
@@ -8,11 +8,13 @@ const okJson = (body: unknown): typeof fetch =>
8
8
  describe('loadMarketIndex', () => {
9
9
  it('prefers a valid remote index', async () => {
10
10
  const index = await loadMarketIndex('skills', {
11
- fetcher: okJson({ repos: [{ id: 'r1', name: 'Repo', description: 'd', url: 'https://github.com/a/b', skills: ['s1', 's2'] }] }),
11
+ fetcher: okJson({ repos: [{ id: 'r1', name: 'Repo', description: 'd', url: 'https://github.com/a/b', detail: 'long', detailZh: '长文', skills: [{ name: 's1', description: 'first', descriptionZh: '第一个' }, { name: 's2' }, 'junk'] }] }),
12
12
  })
13
13
  expect(index?.source).toBe('remote')
14
14
  expect(index?.skills?.[0]?.id).toBe('r1')
15
- expect(index?.skills?.[0]?.skills).toEqual(['s1', 's2'])
15
+ expect(index?.skills?.[0]?.detail).toBe('long')
16
+ expect(index?.skills?.[0]?.detailZh).toBe('长文')
17
+ expect(index?.skills?.[0]?.skills).toEqual([{ name: 's1', description: 'first', descriptionZh: '第一个' }, { name: 's2' }])
16
18
  })
17
19
 
18
20
  it('falls back to the bundled snapshot when remote is unreachable or invalid', async () => {
@@ -30,7 +32,7 @@ describe('loadMarketIndex', () => {
30
32
  const index = await loadMarketIndex('mcp', {
31
33
  fetcher: okJson({
32
34
  servers: [
33
- { id: 'ok', name: 'OK', description: 'd', transport: 'stdio', command: 'npx', args: ['-y', 'x'], envKeys: ['K'], homepage: 'https://x', tools: ['t1'] },
35
+ { id: 'ok', name: 'OK', description: 'd', transport: 'stdio', command: 'npx', args: ['-y', 'x'], envKeys: ['K'], homepage: 'https://x', tools: ['t1'], detail: 'long' },
34
36
  { id: 'no-command', name: 'Bad', description: 'd', transport: 'stdio', homepage: 'https://x' },
35
37
  { id: 'no-home', name: 'Bad2', description: 'd', transport: 'stdio', command: 'npx' },
36
38
  'not an object',
@@ -39,7 +41,7 @@ describe('loadMarketIndex', () => {
39
41
  })
40
42
  expect(index?.source).toBe('remote')
41
43
  expect(index?.servers).toHaveLength(1)
42
- expect(index?.servers?.[0]).toMatchObject({ id: 'ok', envKeys: ['K'], tools: ['t1'] })
44
+ expect(index?.servers?.[0]).toMatchObject({ id: 'ok', envKeys: ['K'], tools: ['t1'], detail: 'long' })
43
45
  })
44
46
 
45
47
  it('ships parseable bundled snapshots with expected top-level shape', () => {
package/src/market.ts CHANGED
@@ -13,6 +13,13 @@ import { fileURLToPath } from 'node:url'
13
13
  /** Which catalog a request wants. */
14
14
  export type MarketKind = 'skills' | 'mcp'
15
15
 
16
+ /** One skill inside a market repository's inventory. */
17
+ export interface MarketSkillItem {
18
+ name: string
19
+ description?: string
20
+ descriptionZh?: string
21
+ }
22
+
16
23
  /** One installable skill repository in the skills index. */
17
24
  export interface MarketSkillRepo {
18
25
  id: string
@@ -20,11 +27,14 @@ export interface MarketSkillRepo {
20
27
  nameZh?: string
21
28
  description: string
22
29
  descriptionZh?: string
30
+ /** Long-form intro for the detail view (falls back to description). */
31
+ detail?: string
32
+ detailZh?: string
23
33
  url: string
24
34
  homepage?: string
25
35
  skillCount?: number
26
- /** Skill names inside the repository, for the market detail view. */
27
- skills?: string[]
36
+ /** Skill inventory for the market detail view. */
37
+ skills?: MarketSkillItem[]
28
38
  }
29
39
 
30
40
  /** One installable server in the MCP index. */
@@ -45,6 +55,9 @@ export interface MarketMcpServer {
45
55
  runtime?: string
46
56
  /** Tool names the server exposes, for the market detail view. */
47
57
  tools?: string[]
58
+ /** Long-form intro for the detail view (falls back to description). */
59
+ detail?: string
60
+ detailZh?: string
48
61
  }
49
62
 
50
63
  export interface MarketIndex {
@@ -66,6 +79,22 @@ export function bundledMarketPath(kind: MarketKind): string {
66
79
  const isString = (value: unknown): value is string => typeof value === 'string' && value !== ''
67
80
  const isStringArray = (value: unknown): value is string[] => Array.isArray(value) && value.every(isString)
68
81
 
82
+ function parseSkillItems(value: unknown): MarketSkillItem[] | null {
83
+ if (!Array.isArray(value)) return null
84
+ const out: MarketSkillItem[] = []
85
+ for (const item of value) {
86
+ if (typeof item !== 'object' || item === null) continue
87
+ const record = item as Record<string, unknown>
88
+ if (!isString(record.name)) continue
89
+ out.push({
90
+ name: record.name,
91
+ ...isString(record.description) ? { description: record.description } : {},
92
+ ...isString(record.descriptionZh) ? { descriptionZh: record.descriptionZh } : {},
93
+ })
94
+ }
95
+ return out.length > 0 ? out : null
96
+ }
97
+
69
98
  function parseSkillsIndex(parsed: unknown): MarketSkillRepo[] | null {
70
99
  if (typeof parsed !== 'object' || parsed === null) return null
71
100
  const repos = (parsed as { repos?: unknown }).repos
@@ -75,16 +104,19 @@ function parseSkillsIndex(parsed: unknown): MarketSkillRepo[] | null {
75
104
  if (typeof entry !== 'object' || entry === null) continue
76
105
  const record = entry as Record<string, unknown>
77
106
  if (!isString(record.id) || !isString(record.name) || !isString(record.description) || !isString(record.url)) continue
107
+ const skills = parseSkillItems(record.skills)
78
108
  out.push({
79
109
  id: record.id,
80
110
  name: record.name,
81
111
  ...isString(record.nameZh) ? { nameZh: record.nameZh } : {},
82
112
  description: record.description,
83
113
  ...isString(record.descriptionZh) ? { descriptionZh: record.descriptionZh } : {},
114
+ ...isString(record.detail) ? { detail: record.detail } : {},
115
+ ...isString(record.detailZh) ? { detailZh: record.detailZh } : {},
84
116
  url: record.url,
85
117
  ...isString(record.homepage) ? { homepage: record.homepage } : {},
86
118
  ...(typeof record.skillCount === 'number' ? { skillCount: record.skillCount } : {}),
87
- ...isStringArray(record.skills) ? { skills: record.skills } : {},
119
+ ...(skills !== null ? { skills } : {}),
88
120
  })
89
121
  }
90
122
  return out.length > 0 ? out : null
@@ -117,6 +149,8 @@ function parseMcpIndex(parsed: unknown): MarketMcpServer[] | null {
117
149
  ...isString(record.category) ? { category: record.category } : {},
118
150
  ...isString(record.runtime) ? { runtime: record.runtime } : {},
119
151
  ...isStringArray(record.tools) ? { tools: record.tools } : {},
152
+ ...isString(record.detail) ? { detail: record.detail } : {},
153
+ ...isString(record.detailZh) ? { detailZh: record.detailZh } : {},
120
154
  })
121
155
  }
122
156
  return out.length > 0 ? out : null