@chatcode/cco-market 1.45.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.
Files changed (177) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +146 -0
  3. package/README.zh.md +143 -0
  4. package/UPDATE-API-V1.md +132 -0
  5. package/client/client.js +11534 -0
  6. package/cordis.patch.yml +5 -0
  7. package/lib/accelerate.js +184 -0
  8. package/lib/agents.js +36 -0
  9. package/lib/backup.js +572 -0
  10. package/lib/catalog-local-match.js +126 -0
  11. package/lib/catalog-npm.js +111 -0
  12. package/lib/changelog.js +242 -0
  13. package/lib/channels.js +64 -0
  14. package/lib/check.js +1067 -0
  15. package/lib/compatibility.js +198 -0
  16. package/lib/diagnostics.js +49 -0
  17. package/lib/discovery-compatibility.js +260 -0
  18. package/lib/dsh-cli.js +949 -0
  19. package/lib/dsh-install.js +108 -0
  20. package/lib/gist.js +352 -0
  21. package/lib/groups.js +97 -0
  22. package/lib/home-paths.js +42 -0
  23. package/lib/hot.js +519 -0
  24. package/lib/http.js +39 -0
  25. package/lib/index.js +97 -0
  26. package/lib/install.js +387 -0
  27. package/lib/log.js +197 -0
  28. package/lib/ndjson.js +154 -0
  29. package/lib/net.js +98 -0
  30. package/lib/order.js +284 -0
  31. package/lib/patch.js +521 -0
  32. package/lib/pnpm-compat.js +490 -0
  33. package/lib/presets.js +278 -0
  34. package/lib/profile.js +911 -0
  35. package/lib/region-probe.js +93 -0
  36. package/lib/regions.js +253 -0
  37. package/lib/registry.js +204 -0
  38. package/lib/restart.js +373 -0
  39. package/lib/routes.js +4372 -0
  40. package/lib/settings.js +109 -0
  41. package/lib/skill-market/api.js +119 -0
  42. package/lib/skill-market/config.js +29 -0
  43. package/lib/skill-market/installer.js +352 -0
  44. package/lib/skill-market/runtime.js +13 -0
  45. package/lib/skill-market/tui.js +119 -0
  46. package/lib/skill-market/types.js +1 -0
  47. package/lib/skill-market/web-routes.js +123 -0
  48. package/lib/snapshot.js +500 -0
  49. package/lib/source-migration.js +54 -0
  50. package/lib/sources.js +596 -0
  51. package/lib/store.js +91 -0
  52. package/lib/themes.js +102 -0
  53. package/lib/trial.js +116 -0
  54. package/lib/types/accelerate.d.ts +85 -0
  55. package/lib/types/agents.d.ts +22 -0
  56. package/lib/types/backup.d.ts +131 -0
  57. package/lib/types/catalog-local-match.d.ts +50 -0
  58. package/lib/types/catalog-npm.d.ts +47 -0
  59. package/lib/types/changelog.d.ts +102 -0
  60. package/lib/types/channels.d.ts +56 -0
  61. package/lib/types/check.d.ts +266 -0
  62. package/lib/types/compatibility.d.ts +64 -0
  63. package/lib/types/diagnostics.d.ts +29 -0
  64. package/lib/types/discovery-compatibility.d.ts +80 -0
  65. package/lib/types/dsh-cli.d.ts +324 -0
  66. package/lib/types/dsh-install.d.ts +46 -0
  67. package/lib/types/gist.d.ts +53 -0
  68. package/lib/types/groups.d.ts +34 -0
  69. package/lib/types/home-paths.d.ts +16 -0
  70. package/lib/types/hot.d.ts +221 -0
  71. package/lib/types/http.d.ts +12 -0
  72. package/lib/types/index.d.ts +14 -0
  73. package/lib/types/install.d.ts +172 -0
  74. package/lib/types/log.d.ts +43 -0
  75. package/lib/types/ndjson.d.ts +52 -0
  76. package/lib/types/net.d.ts +58 -0
  77. package/lib/types/order.d.ts +100 -0
  78. package/lib/types/patch.d.ts +129 -0
  79. package/lib/types/pnpm-compat.d.ts +93 -0
  80. package/lib/types/presets.d.ts +90 -0
  81. package/lib/types/profile.d.ts +253 -0
  82. package/lib/types/region-probe.d.ts +50 -0
  83. package/lib/types/regions.d.ts +122 -0
  84. package/lib/types/registry.d.ts +86 -0
  85. package/lib/types/restart.d.ts +194 -0
  86. package/lib/types/routes.d.ts +67 -0
  87. package/lib/types/settings.d.ts +78 -0
  88. package/lib/types/skill-market/api.d.ts +10 -0
  89. package/lib/types/skill-market/config.d.ts +3 -0
  90. package/lib/types/skill-market/installer.d.ts +23 -0
  91. package/lib/types/skill-market/runtime.d.ts +10 -0
  92. package/lib/types/skill-market/tui.d.ts +4 -0
  93. package/lib/types/skill-market/types.d.ts +71 -0
  94. package/lib/types/skill-market/web-routes.d.ts +19 -0
  95. package/lib/types/snapshot.d.ts +93 -0
  96. package/lib/types/source-migration.d.ts +11 -0
  97. package/lib/types/sources.d.ts +216 -0
  98. package/lib/types/store.d.ts +30 -0
  99. package/lib/types/themes.d.ts +40 -0
  100. package/lib/types/trial.d.ts +61 -0
  101. package/lib/types/update-api-v1.d.ts +66 -0
  102. package/lib/types/updates.d.ts +123 -0
  103. package/lib/types/verify.d.ts +139 -0
  104. package/lib/update-api-v1.js +215 -0
  105. package/lib/updates.js +361 -0
  106. package/lib/verify.js +453 -0
  107. package/package.json +121 -0
  108. package/src/accelerate.ts +213 -0
  109. package/src/agents.ts +43 -0
  110. package/src/backup.ts +583 -0
  111. package/src/catalog-local-match.ts +144 -0
  112. package/src/catalog-npm.ts +120 -0
  113. package/src/changelog.ts +282 -0
  114. package/src/channels.ts +70 -0
  115. package/src/check.ts +1239 -0
  116. package/src/client/CommentsModal.tsx +119 -0
  117. package/src/client/Diagnostics.tsx +907 -0
  118. package/src/client/ErrorBoundary.tsx +111 -0
  119. package/src/client/InstallToast.tsx +31 -0
  120. package/src/client/Market.module.css +830 -0
  121. package/src/client/MarketSection.tsx +5301 -0
  122. package/src/client/OperationsPanel.tsx +365 -0
  123. package/src/client/SettingsCard.tsx +621 -0
  124. package/src/client/SkillMarket.module.css +21 -0
  125. package/src/client/SkillMarketSection.tsx +162 -0
  126. package/src/client/comments.ts +54 -0
  127. package/src/client/globals.d.ts +13 -0
  128. package/src/client/index.ts +180 -0
  129. package/src/client/locales.ts +1112 -0
  130. package/src/client/market-data.ts +1321 -0
  131. package/src/client/operations.ts +201 -0
  132. package/src/client/preset-panel.tsx +263 -0
  133. package/src/client/primitives.d.ts +140 -0
  134. package/src/client/self-check.ts +147 -0
  135. package/src/client/snapshot-panel.tsx +244 -0
  136. package/src/compatibility.ts +237 -0
  137. package/src/diagnostics.ts +84 -0
  138. package/src/discovery-compatibility.ts +315 -0
  139. package/src/dsh-cli.ts +1126 -0
  140. package/src/dsh-install.ts +118 -0
  141. package/src/gist.ts +362 -0
  142. package/src/groups.ts +111 -0
  143. package/src/home-paths.ts +53 -0
  144. package/src/hot.ts +628 -0
  145. package/src/http.ts +41 -0
  146. package/src/index.ts +128 -0
  147. package/src/install.ts +420 -0
  148. package/src/log.ts +206 -0
  149. package/src/ndjson.ts +185 -0
  150. package/src/net.ts +106 -0
  151. package/src/order.ts +303 -0
  152. package/src/patch.ts +522 -0
  153. package/src/pnpm-compat.ts +527 -0
  154. package/src/presets.ts +344 -0
  155. package/src/profile.ts +940 -0
  156. package/src/region-probe.ts +97 -0
  157. package/src/regions.ts +310 -0
  158. package/src/registry.ts +250 -0
  159. package/src/restart.ts +396 -0
  160. package/src/routes.ts +4506 -0
  161. package/src/settings.ts +141 -0
  162. package/src/skill-market/api.ts +130 -0
  163. package/src/skill-market/config.ts +32 -0
  164. package/src/skill-market/installer.ts +337 -0
  165. package/src/skill-market/runtime.ts +14 -0
  166. package/src/skill-market/tui.ts +157 -0
  167. package/src/skill-market/types.ts +80 -0
  168. package/src/skill-market/web-routes.ts +132 -0
  169. package/src/snapshot.ts +532 -0
  170. package/src/source-migration.ts +61 -0
  171. package/src/sources.ts +565 -0
  172. package/src/store.ts +89 -0
  173. package/src/themes.ts +125 -0
  174. package/src/trial.ts +156 -0
  175. package/src/update-api-v1.ts +277 -0
  176. package/src/updates.ts +400 -0
  177. package/src/verify.ts +492 -0
@@ -0,0 +1,162 @@
1
+ import { useCallback, useEffect, useMemo, useState, useSyncExternalStore } from 'react'
2
+ import { Button, Input, Pill } from '@deepseek-ai/dsh-client-ui-primitives'
3
+ import { api, type Translate } from './market-data.ts'
4
+ import css from './SkillMarket.module.css'
5
+
6
+ interface SkillItem {
7
+ readonly id: number
8
+ readonly name: string
9
+ readonly description: string
10
+ readonly category: string
11
+ readonly downloads: number
12
+ readonly version: string
13
+ readonly author: string
14
+ readonly creatorName: string
15
+ }
16
+
17
+ interface ListResult {
18
+ readonly items: readonly SkillItem[]
19
+ readonly total: number
20
+ readonly page: number
21
+ readonly pageSize: number
22
+ }
23
+
24
+ interface InstalledSkill {
25
+ readonly name: string
26
+ readonly location: 'project' | 'user'
27
+ readonly path: string
28
+ }
29
+
30
+ export interface SkillMarketSessions {
31
+ readonly list: {
32
+ subscribe(callback: () => void): () => void
33
+ getSnapshot(): {
34
+ readonly current?: string
35
+ readonly byId: Readonly<Record<string, { readonly cwd?: string }>>
36
+ }
37
+ }
38
+ }
39
+
40
+ /** Browser skill catalog backed by the host's authoritative session workspace. */
41
+ export function SkillMarketSection({ t, sessions }: { readonly t: Translate; readonly sessions: SkillMarketSessions }) {
42
+ const subscribe = useMemo(() => (callback: () => void) => sessions.list.subscribe(callback), [sessions])
43
+ const getSnapshot = useCallback(() => sessions.list.getSnapshot(), [sessions])
44
+ const sessionList = useSyncExternalStore(subscribe, getSnapshot, getSnapshot)
45
+ const sessionId = sessionList.current
46
+ const cwd = sessionId === undefined ? undefined : sessionList.byId[sessionId]?.cwd
47
+ const [query, setQuery] = useState('')
48
+ const [deferredQuery, setDeferredQuery] = useState('')
49
+ const [page, setPage] = useState(1)
50
+ const [result, setResult] = useState<ListResult>({ items: [], total: 0, page: 1, pageSize: 12 })
51
+ const [installed, setInstalled] = useState<readonly InstalledSkill[]>([])
52
+ const [loading, setLoading] = useState(true)
53
+ const [working, setWorking] = useState<string | undefined>()
54
+ const [error, setError] = useState<string | undefined>()
55
+ const [notice, setNotice] = useState<string | undefined>()
56
+
57
+ useEffect(() => {
58
+ const timer = setTimeout(() => { setDeferredQuery(query); setPage(1) }, 300)
59
+ return () => clearTimeout(timer)
60
+ }, [query])
61
+
62
+ const loadInstalled = useCallback(async () => {
63
+ if (sessionId === undefined || cwd === undefined) {
64
+ setInstalled([])
65
+ return
66
+ }
67
+ const response = await fetch(api(`dsh-market/skills/installed?sessionId=${encodeURIComponent(sessionId)}`))
68
+ const body = await readResponse<{ items: readonly InstalledSkill[] }>(response)
69
+ setInstalled(body.items)
70
+ }, [cwd, sessionId])
71
+
72
+ const refresh = useCallback(async () => {
73
+ setLoading(true)
74
+ setError(undefined)
75
+ const params = new URLSearchParams({ page: String(page), pageSize: '12', sort: 'latest' })
76
+ if (deferredQuery.trim()) params.set('search', deferredQuery.trim())
77
+ try {
78
+ const [catalog] = await Promise.all([
79
+ fetch(api(`dsh-market/skills?${params.toString()}`)).then(response => readResponse<ListResult>(response)),
80
+ loadInstalled(),
81
+ ])
82
+ setResult(catalog)
83
+ } catch (cause) {
84
+ setError(cause instanceof Error ? cause.message : String(cause))
85
+ } finally {
86
+ setLoading(false)
87
+ }
88
+ }, [deferredQuery, loadInstalled, page])
89
+
90
+ useEffect(() => { void refresh() }, [refresh])
91
+ const installedNames = useMemo(() => new Set(installed.map(item => item.name)), [installed])
92
+ const pages = Math.max(1, Math.ceil(result.total / result.pageSize))
93
+
94
+ async function install(item: SkillItem, location: 'project' | 'user'): Promise<void> {
95
+ if (sessionId === undefined || cwd === undefined) return
96
+ setWorking(`${item.id}:${location}`)
97
+ setError(undefined)
98
+ setNotice(undefined)
99
+ try {
100
+ const response = await fetch(api('dsh-market/skills/install'), {
101
+ method: 'POST',
102
+ headers: { 'content-type': 'application/json' },
103
+ body: JSON.stringify({ id: item.id, name: item.name, version: item.version, location, sessionId }),
104
+ })
105
+ const installedResult = await readResponse<{ installPath: string }>(response)
106
+ setNotice(`${t('skillInstalledAt')} ${installedResult.installPath}`)
107
+ await loadInstalled()
108
+ } catch (cause) {
109
+ setError(cause instanceof Error ? cause.message : String(cause))
110
+ } finally {
111
+ setWorking(undefined)
112
+ }
113
+ }
114
+
115
+ return <section className={css.root}>
116
+ <header className={css.head}>
117
+ <div>
118
+ <h2 className={css.title}>{t('skillNav')}</h2>
119
+ <p className={css.subtitle}>{t('skillSubtitle')}</p>
120
+ </div>
121
+ <Input value={query} onChange={event => setQuery(event.currentTarget.value)} placeholder={t('skillSearch')} />
122
+ </header>
123
+ <p className={css.workspace}>{cwd === undefined ? t('skillNeedSession') : `${t('skillWorkspace')} ${cwd}`}</p>
124
+ <p className={css.warning}>{t('skillTrustWarning')}</p>
125
+ {error && <div className={css.error}>{error} <Button size="sm" variant="outline" onClick={() => void refresh()}>{t('skillRetry')}</Button></div>}
126
+ {notice && <div className={css.notice}>{notice}</div>}
127
+ {loading ? <p className={css.state}>{t('skillLoading')}</p> : result.items.length === 0 ? <p className={css.state}>{t('skillEmpty')}</p> : <div className={css.grid}>
128
+ {result.items.map(item => <article className={css.card} key={item.id}>
129
+ <div className={css.cardHead}>
130
+ <h3>{item.name}</h3>
131
+ {installedNames.has(item.name) && <Pill>{t('skillInstalled')}</Pill>}
132
+ </div>
133
+ <p className={css.description}>{item.description || t('skillNoDescription')}</p>
134
+ <p className={css.meta}>{item.version ? `v${item.version}` : ''}{item.author || item.creatorName ? ` · ${item.author || item.creatorName}` : ''}{` · ${item.downloads} ${t('skillDownloads')}`}</p>
135
+ <div className={css.actions}>
136
+ <Button className={css.projectInstall} size="sm" variant="primary" disabled={cwd === undefined || working !== undefined} onClick={() => void install(item, 'project')}>
137
+ {working === `${item.id}:project` ? t('skillInstalling') : t('skillInstallProject')}
138
+ </Button>
139
+ <Button size="sm" variant="outline" disabled={cwd === undefined || working !== undefined} onClick={() => void install(item, 'user')}>
140
+ {working === `${item.id}:user` ? t('skillInstalling') : t('skillInstallUser')}
141
+ </Button>
142
+ </div>
143
+ </article>)}
144
+ </div>}
145
+ <footer className={css.pager}>
146
+ <Button size="sm" variant="outline" disabled={page <= 1 || loading} onClick={() => setPage(value => value - 1)}>{t('skillPrevious')}</Button>
147
+ <span>{page} / {pages} · {result.total}</span>
148
+ <Button size="sm" variant="outline" disabled={page >= pages || loading} onClick={() => setPage(value => value + 1)}>{t('skillNext')}</Button>
149
+ </footer>
150
+ </section>
151
+ }
152
+
153
+ async function readResponse<T>(response: Response): Promise<T> {
154
+ const body = await response.json() as unknown
155
+ if (!response.ok) {
156
+ const message = typeof body === 'object' && body !== null && 'error' in body && typeof body.error === 'string'
157
+ ? body.error
158
+ : `HTTP ${response.status}`
159
+ throw new Error(message)
160
+ }
161
+ return body as T
162
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * giscus wiring for the in-market comment thread.
3
+ *
4
+ * A plugin has ONE discussion, whether the reader reached it from
5
+ * dshmarket.com, from the awesome-dsh-plugin catalog, or from inside the
6
+ * market itself. That is only true as long as three things agree across the
7
+ * three surfaces: the repository ids below, the `plugin:<slug>` term, and the
8
+ * slug those terms are built from. The ids here mirror site/comments.mjs and
9
+ * the slug mirrors scripts/build-site.mjs; both are asserted in
10
+ * tests/client/comments.client.spec.ts against the real files, because a
11
+ * silent disagreement does not fail — it forks the conversation in half and
12
+ * nobody notices until someone asks why their comment vanished.
13
+ */
14
+
15
+ export const GISCUS = Object.freeze({
16
+ repo: 'awesome-dsh-plugin/awesome-dsh-plugin',
17
+ repoId: 'R_kgDOT3ajCQ',
18
+ category: 'Plugins',
19
+ categoryId: 'DIC_kwDOT3ajCc4DES8_',
20
+ })
21
+
22
+ /**
23
+ * The plugin's slug, derived from its GitHub URL exactly as the two site
24
+ * builders derive it. Entries that live in a subdirectory of a monorepo
25
+ * (`.../tree/<ref>/packages/x`) get the `owner/repo--packages-x` form, so two
26
+ * plugins from one repository do not share a thread.
27
+ */
28
+ export function pluginSlug(url: string): string {
29
+ const repoPath = url.replace('https://github.com/', '')
30
+ const repo = repoPath.split('/').slice(0, 2).join('/')
31
+ const sub = repoPath.includes('/tree/')
32
+ ? repoPath.split('/tree/')[1]!.replace(/^[^/]+\//, '')
33
+ : null
34
+ return sub ? `${repo}--${sub.replaceAll('/', '-')}` : repo
35
+ }
36
+
37
+ /** The giscus discussion term for a plugin. */
38
+ export const commentsTerm = (url: string): string => `plugin:${pluginSlug(url)}`
39
+
40
+ /**
41
+ * GitHub's discussion search narrowed to this plugin's exact shared term.
42
+ *
43
+ * This is deliberately composed from constants and the catalog URL only.
44
+ * In particular it never copies the current page URL: a local ChatCode CLI Web page
45
+ * may carry its one-time host token there, and sending that to GitHub would
46
+ * trade a comment-login inconvenience for credential disclosure.
47
+ */
48
+ export function commentsDiscussionUrl(url: string): string {
49
+ const term = `"${commentsTerm(url)}"`
50
+ return `https://github.com/${GISCUS.repo}/discussions?discussions_q=${encodeURIComponent(term)}`
51
+ }
52
+
53
+ /** giscus's own locale codes, which are not the ones this plugin uses. */
54
+ export const giscusLang = (lang: string): string => (lang === 'zh' ? 'zh-CN' : 'en')
@@ -0,0 +1,13 @@
1
+ /** Ambient declarations for the browser client bundle. */
2
+
3
+ declare module '*.module.css' {
4
+ const classes: Record<string, string>
5
+ export default classes
6
+ }
7
+
8
+ interface Window {
9
+ /** Boot manifest written by the host page for bundle-layer plugins. */
10
+ __DSH_BOOT__?: {
11
+ entries?: Array<{ id: string }>
12
+ }
13
+ }
@@ -0,0 +1,180 @@
1
+ /**
2
+ * dsh-market client: registers a "Market" settings section rendering the
3
+ * plugin market UI, plus the post-install toast in the shell overlay layer.
4
+ * Built by tsdown into the __ModuleLoader__ factory bundle at
5
+ * client/client.js; the only externals are the loader module table's react
6
+ * entries.
7
+ */
8
+ import { createElement as h } from 'react'
9
+ import * as primitives from '@deepseek-ai/dsh-client-ui-primitives'
10
+ import { en, zh } from './locales.ts'
11
+ import { InstallToast } from './InstallToast.tsx'
12
+ import { MarketErrorBoundary } from './ErrorBoundary.tsx'
13
+ import { MarketSection } from './MarketSection.tsx'
14
+ import { exportMarketLog } from './self-check.ts'
15
+ import { SettingsCard } from './SettingsCard.tsx'
16
+ import type { ThemeSnapshot, Translate } from './market-data.ts'
17
+ import { SkillMarketSection, type SkillMarketSessions } from './SkillMarketSection.tsx'
18
+
19
+ const NS = 'dsh-market'
20
+
21
+ /**
22
+ * Primitives this bundle relies on that did not exist before rc.6. The
23
+ * primitives module is host-injected (external at build time), so on an
24
+ * older host the module resolves but these named exports are undefined —
25
+ * rendering would throw and blank the whole settings dialog. Returning the
26
+ * gaps lets apply() skip registration for a clean downgrade instead.
27
+ */
28
+ export const REQUIRED_PRIMITIVES = ['Menu', 'DisclosureRow', 'Tooltip', 'Toast'] as const
29
+
30
+ export function missingPrimitives(mod: Record<string, unknown>, required: readonly string[] = REQUIRED_PRIMITIVES): string[] {
31
+ return required.filter(name => mod[name] === undefined)
32
+ }
33
+
34
+ /**
35
+ * The host surface the settings card needs, present only on rc.7+.
36
+ *
37
+ * The card no longer reads or writes settings — it manages the market's own
38
+ * package — but `settingsScope` stays as the INJECTION KEY, because its
39
+ * presence is what distinguishes a host that has the plugin configuration
40
+ * page from one that does not. The market's namespace (registered in
41
+ * settings.ts) is likewise still required: the page dispatches a card keyed
42
+ * by a namespace it serves, so dropping it would take the card with it.
43
+ */
44
+ interface SettingsScopeHost {
45
+ slots: {
46
+ inject(name: string, register: () => unknown): void
47
+ register(options: Record<string, unknown>, render: () => unknown): unknown
48
+ }
49
+ }
50
+
51
+ /** The subset of the theme service this plugin touches. */
52
+ interface ThemeService {
53
+ getTheme(): ThemeSnapshot | null
54
+ setTheme(id: string): void
55
+ }
56
+
57
+ /** The subset of the locale service this plugin touches. */
58
+ interface LocaleService {
59
+ register(namespace: string, dicts: { zh: Record<string, string>; en: Record<string, string> }): unknown
60
+ bind(namespace: string): Translate
61
+ subscribe(callback: () => void): () => void
62
+ getSnapshot(): { active: string }
63
+ }
64
+
65
+ /** The subset of the slots service this plugin touches. */
66
+ interface SlotsService {
67
+ inject(slot: string, register: () => unknown): void
68
+ register(meta: Record<string, unknown>, component: () => unknown): unknown
69
+ }
70
+
71
+ /** The client cordis context shape this plugin relies on (structural: the
72
+ * host provides the real Context; typing the touched surface keeps this
73
+ * external package free of monorepo-internal type dependencies). */
74
+ interface MarketClientContext {
75
+ effect(callback: () => unknown, label?: string): void
76
+ on(event: string, callback: () => void): () => void
77
+ locale: LocaleService
78
+ slots: SlotsService
79
+ theme: ThemeService
80
+ sessions: SkillMarketSessions
81
+ }
82
+
83
+ export const name = 'dsh-market'
84
+ // 'theme' is safe to require: ui-layout (mandatory in every web composition)
85
+ // already hard-depends on it. This cordis's object-form inject means
86
+ // intercept config, NOT {required,optional} — do not use it here.
87
+ export const inject = ['slots', 'locale', 'theme', 'sessions']
88
+ export function apply(ctx: MarketClientContext): void {
89
+ // Older hosts resolve the primitives module but lack the rc.6 exports the
90
+ // market renders with. Skip registration (market simply absent from the
91
+ // settings list) rather than throwing mid-render and blanking the dialog.
92
+ const gaps = missingPrimitives(primitives as unknown as Record<string, unknown>)
93
+ if (gaps.length > 0) {
94
+ console.warn('[ChatCode CLI Market] host ui-primitives missing ' + gaps.join(', ') + ' — market section disabled (ChatCode CLI >= 0.1.0-rc.6 required)')
95
+ return
96
+ }
97
+
98
+ ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-market: dictionaries')
99
+ const t = ctx.locale.bind(NS)
100
+
101
+ // Kept so the removal flow can retire the market's own nav entry the
102
+ // moment the package is gone: leaving "插件市场" in the left menu after
103
+ // the user removed it is the card claiming something the profile no
104
+ // longer agrees with. `register` hands back its own disposer; calling it
105
+ // twice (here and again when the context unwinds) is harmless, but the
106
+ // reference is dropped after use so the intent stays readable.
107
+ let retireSection: (() => void) | null = null
108
+
109
+ ctx.slots.inject('settings.section', () => {
110
+ const off = ctx.slots.register({
111
+ name: 'settings.section',
112
+ id: 'market',
113
+ order: 40,
114
+ label: () => t('nav'),
115
+ locale: NS,
116
+ inject: () => ({ t }),
117
+ }, (ownerProps: { preferredSubsectionId?: string } = {}) => h(MarketErrorBoundary, {
118
+ // Wrapped at the registration point, so the boundary is OUTSIDE
119
+ // everything the section renders — including its portalled layers.
120
+ // A crash inside used to unmount the whole tree and leave an empty
121
+ // settings panel with no export-log button, which is how #293 went
122
+ // months without a usable report (#513 fixed that trigger; this
123
+ // covers the next one).
124
+ text: {
125
+ title: t('crashTitle'),
126
+ hint: t('crashHint'),
127
+ reload: t('crashReload'),
128
+ details: t('crashDetails'),
129
+ },
130
+ actions: h('button', {
131
+ type: 'button',
132
+ onClick: () => { void exportMarketLog().catch(() => {}) },
133
+ }, t('exportLog')),
134
+ }, h(MarketSection, {
135
+ t,
136
+ locale: ctx.locale,
137
+ theme: ctx.theme,
138
+ themeStore: {
139
+ subscribe: (cb: () => void) => ctx.on('theme/change', cb),
140
+ getSnapshot: () => ctx.theme.getTheme(),
141
+ },
142
+ preferredSubsectionId: ownerProps.preferredSubsectionId,
143
+ })))
144
+ if (typeof off === 'function') retireSection = off as () => void
145
+ return off
146
+ })
147
+
148
+ ctx.slots.inject('settings.section', () => ctx.slots.register({
149
+ name: 'settings.section',
150
+ id: 'skill-market',
151
+ order: 41,
152
+ label: () => t('skillNav'),
153
+ locale: NS,
154
+ inject: () => ({ t }),
155
+ }, () => h(SkillMarketSection, { t, sessions: ctx.sessions })))
156
+
157
+ // The settings card (dsh >= 0.1.0-rc.7). Registered through a NESTED
158
+ // inject on purpose: naming settingsScope in the module-level `inject`
159
+ // would keep this whole plugin unmounted on any host without that
160
+ // service — the market's own page would vanish on rc.6 to gain a card
161
+ // rc.6 cannot render. Nested, the card simply never appears there.
162
+ const settingsCtx = ctx as unknown as {
163
+ inject(services: string[], callback: (scoped: SettingsScopeHost) => void): void
164
+ }
165
+ settingsCtx.inject(['settingsScope'], (scoped) => {
166
+ scoped.slots.inject('settings.plugin.item', () => scoped.slots.register({
167
+ name: 'settings.plugin.item',
168
+ key: NS,
169
+ locale: NS,
170
+ inject: () => ({ t }),
171
+ }, () => h(SettingsCard, { t, onRemoved: () => { const off = retireSection; retireSection = null; off?.() } })))
172
+ })
173
+
174
+ const Toast = () => h(InstallToast, { t })
175
+ ctx.slots.inject('shell.overlay', () => ctx.slots.register({
176
+ name: 'shell.overlay',
177
+ id: 'dsh-market-toast',
178
+ label: () => 'dsh-market',
179
+ }, Toast))
180
+ }