@boostkit-dsh/skill-market 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.
package/client.js ADDED
@@ -0,0 +1,464 @@
1
+ // Generated from src/client.ts by scripts/build-clients.mjs. Do not edit.
2
+ window.__ModuleLoader__.load({ id: "@boostkit-dsh/skill-market", factory: (require2) => {
3
+ const module = { exports: {} };
4
+ const exports = module.exports;
5
+ const { createElement: h, useCallback, useEffect, useMemo, useState, useSyncExternalStore } = require2("react");
6
+ const { MarkdownText, Switch } = require2("@deepseek-ai/dsh-client-ui-primitives");
7
+ const NS = "boostkit-skill-market";
8
+ const PANEL_ID = "boostkit-skill-market";
9
+ const dictionaries = {
10
+ zh: {
11
+ nav: "BoostKit Skill \u5E02\u573A",
12
+ subtitle: "\u9879\u76EE\u7EA7 Skill \xB7 npm \u5B89\u5168\u5206\u53D1",
13
+ all: "\u5168\u90E8",
14
+ installed: "\u5DF2\u5B89\u88C5",
15
+ search: "\u641C\u7D22 Skill \u540D\u79F0\u3001\u8BF4\u660E\u3001\u5206\u7C7B\u6216\u6807\u7B7E\u2026",
16
+ allCategories: "\u5168\u90E8\u5206\u7C7B",
17
+ install: "\u5B89\u88C5",
18
+ update: "\u66F4\u65B0",
19
+ uninstall: "\u5378\u8F7D",
20
+ working: "\u5904\u7406\u4E2D\u2026",
21
+ refresh: "\u5237\u65B0",
22
+ share: "\u5206\u4EAB",
23
+ shared: "\u5DF2\u590D\u5236\u5206\u4EAB\u4FE1\u606F",
24
+ contribute: "\u8D21\u732E Skill",
25
+ results: "\u4E2A\u7ED3\u679C",
26
+ loading: "\u6B63\u5728\u52A0\u8F7D Skill \u76EE\u5F55\u2026",
27
+ installedBadge: "\u5DF2\u5B89\u88C5",
28
+ disabledBadge: "\u5DF2\u7981\u7528",
29
+ updateBadge: "\u53EF\u66F4\u65B0",
30
+ missingBadge: "\u6587\u4EF6\u7F3A\u5931",
31
+ unlistedBadge: "\u76EE\u5F55\u672A\u6536\u5F55",
32
+ empty: "\u6CA1\u6709\u7B26\u5408\u6761\u4EF6\u7684 Skill",
33
+ failed: "\u64CD\u4F5C\u5931\u8D25",
34
+ success: "\u64CD\u4F5C\u5B8C\u6210",
35
+ offlineTitle: "\u65E0\u6CD5\u8FDE\u63A5 BoostKit Skill npm \u76EE\u5F55",
36
+ offlineBody: "\u4ECD\u53EF\u67E5\u770B\u3001\u542F\u505C\u6216\u5378\u8F7D\u5DF2\u7ECF\u5B89\u88C5\u7684 Skill\uFF1B\u5B89\u88C5\u548C\u66F4\u65B0\u6682\u4E0D\u53EF\u7528\u3002",
37
+ catalog: "\u76EE\u5F55\u5305",
38
+ release: "\u76EE\u5F55\u7248\u672C",
39
+ source: "\u6E90\u7801",
40
+ version: "\u7248\u672C",
41
+ confirmInstall: "\u786E\u8BA4\u5C06\u6B64 Skill \u5B89\u88C5\u5230\u5F53\u524D\u9879\u76EE\uFF1F",
42
+ confirmUpdate: "\u786E\u8BA4\u66F4\u65B0\u6B64 Skill\uFF1F\u672C\u5730\u76EE\u5F55\u5C06\u66FF\u6362\u4E3A catalog \u7248\u672C\u3002",
43
+ confirmRemove: "\u786E\u8BA4\u5378\u8F7D\u6B64 Skill\uFF1F\u53EA\u4F1A\u5220\u9664\u7531\u672C\u5E02\u573A\u7BA1\u7406\u7684\u76EE\u5F55\u3002",
44
+ agentBusy: "\u6709 Agent \u6B63\u5728\u8FD0\u884C\uFF0C\u8BF7\u7B49\u5F85\u5F53\u524D\u4EFB\u52A1\u7ED3\u675F\u540E\u518D\u4FEE\u6539 Skill\u3002",
45
+ availableImmediately: "\u5B89\u88C5\u3001\u542F\u7528\u6216\u7981\u7528\u540E\uFF0CDSH \u6587\u4EF6\u7CFB\u7EDF Skill Provider \u4F1A\u81EA\u52A8\u53D1\u73B0\u53D8\u66F4\u3002",
46
+ openDetails: "\u67E5\u770B\u5B8C\u6574 SKILL.md",
47
+ skillDetails: "Skill \u8BE6\u60C5",
48
+ skillFile: "SKILL.md",
49
+ close: "\u5173\u95ED",
50
+ enable: "\u542F\u7528",
51
+ disable: "\u7981\u7528",
52
+ enabled: "\u5DF2\u542F\u7528",
53
+ disabled: "\u5DF2\u7981\u7528",
54
+ detailLoading: "\u6B63\u5728\u8BFB\u53D6 SKILL.md\u2026",
55
+ detailUnavailable: "\u65E0\u6CD5\u8BFB\u53D6 SKILL.md",
56
+ copyCode: "\u590D\u5236",
57
+ copiedCode: "\u5DF2\u590D\u5236",
58
+ footnotes: "\u811A\u6CE8",
59
+ toggleSkill: "\u542F\u7528\u6B64 Skill",
60
+ skillMetadata: "Skill \u5143\u6570\u636E",
61
+ targetWorkspace: "\u5B89\u88C5\u5230\u5DE5\u4F5C\u533A",
62
+ noWorkspace: "\u8BF7\u5148\u5728\u5DE6\u4FA7\u6DFB\u52A0\u4E00\u4E2A DSH \u5DE5\u4F5C\u533A\uFF0C\u518D\u5B89\u88C5\u9879\u76EE\u7EA7 Skill\u3002"
63
+ },
64
+ en: {
65
+ nav: "BoostKit Skill Market",
66
+ subtitle: "Project Skills \xB7 verified npm distribution",
67
+ all: "All",
68
+ installed: "Installed",
69
+ search: "Search Skill name, description, category, or tag\u2026",
70
+ allCategories: "All categories",
71
+ install: "Install",
72
+ update: "Update",
73
+ uninstall: "Uninstall",
74
+ working: "Working\u2026",
75
+ refresh: "Refresh",
76
+ share: "Share",
77
+ shared: "Share information copied",
78
+ contribute: "Contribute Skill",
79
+ results: "results",
80
+ loading: "Loading Skill catalog\u2026",
81
+ installedBadge: "Installed",
82
+ disabledBadge: "Disabled",
83
+ updateBadge: "Update available",
84
+ missingBadge: "Files missing",
85
+ unlistedBadge: "Not listed",
86
+ empty: "No matching Skills",
87
+ failed: "Operation failed",
88
+ success: "Operation complete",
89
+ offlineTitle: "Cannot reach the BoostKit Skill npm catalog",
90
+ offlineBody: "Installed Skills remain readable, toggleable, and removable; install and update are unavailable.",
91
+ catalog: "Catalog",
92
+ release: "Catalog release",
93
+ source: "Source",
94
+ version: "Version",
95
+ confirmInstall: "Install this Skill into the current project?",
96
+ confirmUpdate: "Update this Skill? The managed local directory will be replaced.",
97
+ confirmRemove: "Remove this Skill? Only the market-managed directory will be deleted.",
98
+ agentBusy: "An Agent is running. Wait for the current task to finish before changing Skills.",
99
+ availableImmediately: "The DSH filesystem Skill provider discovers installs and enablement changes automatically.",
100
+ openDetails: "View the complete SKILL.md",
101
+ skillDetails: "Skill details",
102
+ skillFile: "SKILL.md",
103
+ close: "Close",
104
+ enable: "Enable",
105
+ disable: "Disable",
106
+ enabled: "Enabled",
107
+ disabled: "Disabled",
108
+ detailLoading: "Loading SKILL.md\u2026",
109
+ detailUnavailable: "Could not read SKILL.md",
110
+ copyCode: "Copy",
111
+ copiedCode: "Copied",
112
+ footnotes: "Footnotes",
113
+ toggleSkill: "Enable this Skill",
114
+ skillMetadata: "Skill metadata",
115
+ targetWorkspace: "Install to workspace",
116
+ noWorkspace: "Add a DSH workspace in the sidebar before installing project Skills."
117
+ }
118
+ };
119
+ const styles = {
120
+ page: { flex: 1, minHeight: 0, overflow: "auto", boxSizing: "border-box", padding: "24px clamp(18px,4vw,48px)", background: "var(--dsw-alias-bg-base)" },
121
+ content: { width: "100%", maxWidth: 1240, margin: "0 auto", display: "flex", flexDirection: "column", gap: 14 },
122
+ row: { display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap" },
123
+ grow: { flex: 1 },
124
+ title: { margin: 0, fontSize: 20, fontWeight: 700, color: "var(--dsw-alias-label-primary)" },
125
+ muted: { fontSize: 12, color: "var(--dsw-alias-label-tertiary)", wordBreak: "break-word" },
126
+ catalogLine: { display: "flex", gap: 8, alignItems: "center", fontSize: 12, color: "var(--dsw-alias-label-tertiary)" },
127
+ workspacePicker: { display: "inline-flex", alignItems: "center", gap: 8, fontSize: 13, color: "var(--dsw-alias-label-secondary)" },
128
+ tabs: { display: "flex", alignItems: "center", gap: 4, borderBottom: "1px solid var(--dsw-alias-border-l1)" },
129
+ tab: { padding: "7px 12px", border: 0, borderBottom: "2px solid transparent", background: "none", color: "var(--dsw-alias-label-secondary)", cursor: "pointer" },
130
+ tabOn: { color: "var(--dsw-alias-label-primary)", borderBottomColor: "var(--dsw-alias-state-business-primary)" },
131
+ search: { minWidth: 260, flex: "0 1 390px", padding: "7px 10px", borderRadius: 7, border: "1px solid var(--dsw-alias-border-l2)", color: "var(--dsw-alias-label-primary)", background: "var(--dsw-alias-bg-layer-1)" },
132
+ select: { minWidth: 150, padding: "7px 10px", borderRadius: 7, border: "1px solid var(--dsw-alias-border-l2)", color: "var(--dsw-alias-label-primary)", background: "var(--dsw-alias-bg-layer-1)" },
133
+ grid: { display: "grid", gridTemplateColumns: "repeat(auto-fill,minmax(300px,1fr))", gap: 12 },
134
+ card: { display: "flex", flexDirection: "column", gap: 9, minHeight: 174, padding: 14, borderRadius: 10, border: "1px solid var(--dsw-alias-border-l1)", background: "var(--dsw-alias-bg-layer-1)" },
135
+ cardClickable: { cursor: "pointer" },
136
+ cardTitle: { margin: 0, fontSize: 15, fontWeight: 700, color: "var(--dsw-alias-label-primary)" },
137
+ cardTitleButton: { margin: 0, padding: 0, border: 0, background: "none", color: "inherit", font: "inherit", cursor: "pointer", textAlign: "left" },
138
+ description: { margin: 0, fontSize: 13, lineHeight: 1.55, color: "var(--dsw-alias-label-secondary)", display: "-webkit-box", WebkitBoxOrient: "vertical", WebkitLineClamp: 2, overflow: "hidden" },
139
+ badge: { padding: "1px 7px", borderRadius: 999, border: "1px solid var(--dsw-alias-border-l2)", fontSize: 11, color: "var(--dsw-alias-label-secondary)" },
140
+ successBadge: { padding: "1px 7px", borderRadius: 999, border: "1px solid var(--dsw-alias-state-success-primary)", fontSize: 11, color: "var(--dsw-alias-state-success-primary)" },
141
+ warningBadge: { padding: "1px 7px", borderRadius: 999, border: "1px solid #c58a18", fontSize: 11, color: "#c58a18" },
142
+ button: { padding: "6px 12px", borderRadius: 7, border: "1px solid var(--dsw-alias-border-l2)", background: "var(--dsw-alias-bg-layer-1)", color: "var(--dsw-alias-label-primary)", cursor: "pointer" },
143
+ primary: { padding: "6px 12px", borderRadius: 7, border: "1px solid var(--dsw-alias-state-business-primary)", background: "var(--dsw-alias-state-business-primary)", color: "#fff", cursor: "pointer" },
144
+ danger: { padding: "6px 12px", borderRadius: 7, border: "1px solid var(--dsw-alias-state-error-primary)", background: "transparent", color: "var(--dsw-alias-state-error-primary)", cursor: "pointer" },
145
+ error: { padding: 12, borderRadius: 9, border: "1px solid var(--dsw-alias-state-error-primary)", color: "var(--dsw-alias-state-error-primary)" },
146
+ notice: { padding: 12, borderRadius: 9, border: "1px solid var(--dsw-alias-state-success-primary)", color: "var(--dsw-alias-label-primary)" },
147
+ link: { color: "var(--dsw-alias-state-business-primary)", textDecoration: "none" },
148
+ overlay: { position: "fixed", inset: 0, zIndex: 1e4, display: "grid", placeItems: "center", padding: 20, background: "rgba(0,0,0,.48)" },
149
+ modal: { width: 860, maxWidth: "calc(100vw - 40px)", height: "min(760px,calc(100vh - 48px))", display: "flex", flexDirection: "column", borderRadius: 14, border: "1px solid var(--dsw-alias-border-l2)", background: "var(--dsw-alias-bg-layer-1)", boxShadow: "0 22px 64px rgba(0,0,0,.3)", overflow: "hidden", color: "var(--dsw-alias-label-primary)" },
150
+ modalHeader: { display: "flex", alignItems: "center", gap: 12, padding: "16px 18px", borderBottom: "1px solid var(--dsw-alias-border-l1)" },
151
+ modalTitle: { margin: 0, fontSize: 17, fontWeight: 700 },
152
+ skillBody: { flex: 1, minHeight: 0, overflow: "auto", margin: 0, padding: "20px 24px 28px", color: "var(--dsw-alias-label-primary)", background: "var(--dsw-alias-bg-layer-2)" },
153
+ metadata: { marginBottom: 18, border: "1px solid var(--dsw-alias-border-l2)", borderRadius: 8, padding: "8px 10px", color: "var(--dsw-alias-label-secondary)", background: "var(--dsw-alias-bg-layer-1)" },
154
+ metadataSummary: { cursor: "pointer", fontSize: 13, fontWeight: 600, color: "var(--dsw-alias-label-secondary)" },
155
+ metadataBody: { margin: "10px 0 0", padding: 10, overflow: "auto", whiteSpace: "pre-wrap", overflowWrap: "anywhere", borderRadius: 6, font: "12px/1.55 var(--ds-font-family-code)", color: "var(--dsw-alias-label-secondary)", background: "var(--dsw-alias-bg-module-platform)" },
156
+ modalFooter: { display: "flex", alignItems: "center", gap: 10, padding: "14px 18px", borderTop: "1px solid var(--dsw-alias-border-l1)" }
157
+ };
158
+ function disabled(style, value) {
159
+ return value ? { ...style, opacity: 0.55, cursor: "not-allowed" } : style;
160
+ }
161
+ async function responseJson(response) {
162
+ const value = await response.json();
163
+ const code = value?.error?.code;
164
+ if (code === "AUTH_REQUIRED") window.dispatchEvent(new CustomEvent("kunpeng-auth-required"));
165
+ if (!response.ok || value?.ok === false) throw Object.assign(new Error(value?.error?.message ?? value?.error ?? `HTTP ${response.status}`), { ...value, code });
166
+ return value;
167
+ }
168
+ function stop(event) {
169
+ event.stopPropagation();
170
+ }
171
+ function splitSkillDocument(content) {
172
+ const text = typeof content === "string" ? content : "";
173
+ const match = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(text);
174
+ return match ? { metadata: match[1], markdown: text.slice(match[0].length) } : { metadata: "", markdown: text };
175
+ }
176
+ function MarketIcon({ size, active }) {
177
+ const color = active ? "var(--dsw-alias-state-business-primary)" : "currentColor";
178
+ return h(
179
+ "svg",
180
+ { width: size, height: size, viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true" },
181
+ h("path", { d: "M4 7.5 12 3l8 4.5-8 4.5-8-4.5Zm0 4.5 8 4.5 8-4.5M4 16.5l8 4.5 8-4.5", stroke: color, strokeWidth: 1.7, strokeLinecap: "round", strokeLinejoin: "round" })
182
+ );
183
+ }
184
+ function SkillCard({ entry, t, language, busy, connected, run, share, openDetail }) {
185
+ const displayName = entry.displayName?.[language] ?? entry.displayName?.zh ?? entry.name;
186
+ const canUpdate = entry.installed && entry.listed !== false && (entry.updateAvailable || !entry.present);
187
+ const clickable = entry.installed;
188
+ const actionProps = { onClick: stop, onMouseDown: stop };
189
+ return h(
190
+ "article",
191
+ {
192
+ style: clickable ? { ...styles.card, ...styles.cardClickable } : styles.card,
193
+ title: clickable ? t("openDetails") : void 0,
194
+ onClick: clickable ? () => openDetail(entry) : void 0
195
+ },
196
+ h(
197
+ "div",
198
+ { style: styles.row },
199
+ h("h3", { style: styles.cardTitle }, clickable ? h("button", { type: "button", style: styles.cardTitleButton, onClick: (event) => {
200
+ stop(event);
201
+ openDetail(entry);
202
+ } }, displayName) : displayName),
203
+ entry.installed ? h("span", { style: styles.successBadge }, t("installedBadge")) : null,
204
+ entry.installed && entry.enabled === false ? h("span", { style: styles.warningBadge }, t("disabledBadge")) : null,
205
+ entry.updateAvailable ? h("span", { style: styles.warningBadge }, t("updateBadge")) : null,
206
+ entry.installed && !entry.present ? h("span", { style: styles.warningBadge }, t("missingBadge")) : null,
207
+ entry.listed === false ? h("span", { style: styles.warningBadge }, t("unlistedBadge")) : null
208
+ ),
209
+ h("p", { style: styles.description }, entry.description),
210
+ h("div", { style: styles.row }, h("span", { style: styles.badge }, entry.category), ...(entry.tags ?? []).slice(0, 3).map((tag) => h("span", { key: tag, style: styles.badge }, tag))),
211
+ h("div", { style: styles.muted }, `${t("version")} ${entry.installed ? entry.currentVersion : entry.version}`),
212
+ h(
213
+ "div",
214
+ { style: { ...styles.row, marginTop: "auto" } },
215
+ entry.sourceUrl ? h("a", { ...actionProps, href: entry.sourceUrl, target: "_blank", rel: "noreferrer", style: styles.link }, t("source")) : null,
216
+ entry.sourceUrl ? h("button", { ...actionProps, type: "button", disabled: busy !== null, style: disabled(styles.button, busy !== null), onClick: (event) => {
217
+ stop(event);
218
+ void share(entry, displayName);
219
+ } }, t("share")) : null,
220
+ h("span", { style: styles.grow }),
221
+ canUpdate ? h("button", { ...actionProps, type: "button", disabled: busy !== null || !connected, style: disabled(styles.primary, busy !== null || !connected), onClick: (event) => {
222
+ stop(event);
223
+ void run("update", entry);
224
+ } }, busy === `update:${entry.name}` ? t("working") : t("update")) : null,
225
+ !entry.installed ? h("button", { ...actionProps, type: "button", disabled: busy !== null || !connected, style: disabled(styles.primary, busy !== null || !connected), onClick: (event) => {
226
+ stop(event);
227
+ void run("install", entry);
228
+ } }, busy === `install:${entry.name}` ? t("working") : t("install")) : null
229
+ )
230
+ );
231
+ }
232
+ function SkillDetail({ detail, t, busy, close, toggle, remove }) {
233
+ if (!detail) return null;
234
+ const entry = detail.entry;
235
+ const isEnabled = detail.enabled !== false;
236
+ const markdownLabels = { code: { copyLabel: t("copyCode"), copiedLabel: t("copiedCode") }, footnotes: t("footnotes") };
237
+ const skillDocument = splitSkillDocument(detail.content);
238
+ return h(
239
+ "div",
240
+ { style: styles.overlay, role: "presentation", onMouseDown: (event) => {
241
+ if (event.target === event.currentTarget && busy === null) close();
242
+ } },
243
+ h(
244
+ "section",
245
+ { role: "dialog", "aria-modal": "true", "aria-label": t("skillDetails"), style: styles.modal },
246
+ h(
247
+ "header",
248
+ { style: styles.modalHeader },
249
+ h("div", null, h("h3", { style: styles.modalTitle }, entry.displayName ?? entry.name), h("div", { style: styles.muted }, `${entry.name} \xB7 ${t("skillFile")}`)),
250
+ h("span", { style: styles.grow }),
251
+ h("span", { style: styles.muted }, isEnabled ? t("enabled") : t("disabled")),
252
+ h(Switch, {
253
+ checked: isEnabled,
254
+ label: t("toggleSkill"),
255
+ disabled: busy !== null || !entry.present,
256
+ onChange: () => void toggle()
257
+ })
258
+ ),
259
+ detail.loading ? h("div", { style: styles.skillBody }, t("detailLoading")) : detail.error ? h("div", { style: { ...styles.skillBody, color: "var(--dsw-alias-state-error-primary)" } }, `${t("detailUnavailable")}\uFF1A${detail.error}`) : h(
260
+ "div",
261
+ { style: styles.skillBody },
262
+ skillDocument.metadata ? h(
263
+ "details",
264
+ { style: styles.metadata },
265
+ h("summary", { style: styles.metadataSummary }, t("skillMetadata")),
266
+ h("pre", { style: styles.metadataBody }, skillDocument.metadata)
267
+ ) : null,
268
+ h(MarkdownText, { text: skillDocument.markdown, streaming: false, labels: markdownLabels })
269
+ ),
270
+ h(
271
+ "footer",
272
+ { style: styles.modalFooter },
273
+ h("button", { type: "button", disabled: busy !== null, style: disabled(styles.danger, busy !== null), onClick: () => void remove() }, busy === `remove:${entry.name}` ? t("working") : t("uninstall")),
274
+ h("span", { style: styles.grow }),
275
+ entry.sourceUrl ? h("a", { href: entry.sourceUrl, target: "_blank", rel: "noreferrer", style: styles.link }, t("source")) : null,
276
+ h("button", { type: "button", disabled: busy !== null, style: disabled(styles.button, busy !== null), onClick: close }, t("close"))
277
+ )
278
+ )
279
+ );
280
+ }
281
+ function MarketPage({ t, list, workspaceSource }) {
282
+ const [catalog, setCatalog] = useState(null);
283
+ const [query, setQuery] = useState("");
284
+ const [category, setCategory] = useState("all");
285
+ const [tab, setTab] = useState("all");
286
+ const [busy, setBusy] = useState(null);
287
+ const [message, setMessage] = useState(null);
288
+ const [detail, setDetail] = useState(null);
289
+ const [selectedWorkspaceId, setSelectedWorkspaceId] = useState(null);
290
+ const subscribeWorkspaces = useCallback((listener) => workspaceSource.subscribe(listener), [workspaceSource]);
291
+ const readWorkspaces = useCallback(() => workspaceSource.getSnapshot(), [workspaceSource]);
292
+ const workspaceSnapshot = useSyncExternalStore(subscribeWorkspaces, readWorkspaces);
293
+ const workspaces = workspaceSnapshot.items ?? [];
294
+ const selectedWorkspace = workspaces.find((workspace) => workspace.workspaceId === selectedWorkspaceId) ?? workspaces[0] ?? null;
295
+ const workspaceId = selectedWorkspace?.workspaceId ?? null;
296
+ const load = useCallback(async (refresh = false) => {
297
+ setMessage(null);
298
+ if (workspaceId === null) {
299
+ setCatalog(null);
300
+ setMessage({ ok: false, error: t("noWorkspace") });
301
+ return;
302
+ }
303
+ try {
304
+ setCatalog(await list(refresh, workspaceId));
305
+ } catch (error) {
306
+ setMessage({ ok: false, error: error?.message ?? String(error) });
307
+ }
308
+ }, [list, t, workspaceId]);
309
+ useEffect(() => {
310
+ void load(false);
311
+ }, [load]);
312
+ useEffect(() => {
313
+ if (!detail) return;
314
+ const onKeyDown = (event) => {
315
+ if (event.key === "Escape" && busy === null) setDetail(null);
316
+ };
317
+ window.addEventListener("keydown", onKeyDown);
318
+ return () => window.removeEventListener("keydown", onKeyDown);
319
+ }, [busy, detail]);
320
+ const language = t("nav") === dictionaries.en.nav ? "en" : "zh";
321
+ const categories = useMemo(() => [...new Set((catalog?.skills ?? []).map((entry) => entry.category).filter(Boolean))].sort(), [catalog]);
322
+ const entries = useMemo(() => {
323
+ const needle = query.trim().toLowerCase();
324
+ return (catalog?.skills ?? []).filter((entry) => {
325
+ if (tab === "installed" && !entry.installed) return false;
326
+ if (category !== "all" && entry.category !== category) return false;
327
+ if (needle === "") return true;
328
+ return [entry.name, entry.package, entry.description, entry.category, ...entry.tags ?? [], entry.displayName?.zh, entry.displayName?.en].filter(Boolean).join(" ").toLowerCase().includes(needle);
329
+ });
330
+ }, [catalog, category, query, tab]);
331
+ const installedCount = (catalog?.skills ?? []).filter((entry) => entry.installed).length;
332
+ const share = useCallback(async (entry, displayName) => {
333
+ try {
334
+ if (!navigator.clipboard?.writeText) throw new Error("Clipboard API unavailable");
335
+ await navigator.clipboard.writeText(`${displayName}
336
+ ${entry.package}@${entry.version}
337
+ ${entry.sourceUrl}`);
338
+ setMessage({ ok: true, text: `${t("shared")}\uFF1A${entry.name}` });
339
+ } catch (error) {
340
+ setMessage({ ok: false, error: error?.message ?? String(error) });
341
+ }
342
+ }, [t]);
343
+ const updateLocalCatalog = useCallback((action, name, skill) => {
344
+ setCatalog((current) => {
345
+ if (!current) return current;
346
+ const skills = (current.skills ?? []).flatMap((candidate) => {
347
+ if (candidate.name !== name) return [candidate];
348
+ if (action === "remove") {
349
+ if (candidate.listed === false) return [];
350
+ return [{ ...candidate, installed: false, present: false, enabled: true, currentVersion: "", updateAvailable: false, managed: false }];
351
+ }
352
+ return [{ ...candidate, ...skill, installed: true, present: true, enabled: action === "enable" }];
353
+ });
354
+ return { ...current, skills };
355
+ });
356
+ }, []);
357
+ const run = useCallback(async (action, entry) => {
358
+ if (workspaceId === null) return false;
359
+ const prompt = action === "install" ? t("confirmInstall") : action === "update" ? t("confirmUpdate") : action === "remove" ? t("confirmRemove") : null;
360
+ if (prompt && !window.confirm(`${entry.displayName?.[language] ?? entry.name}
361
+
362
+ ${prompt}`)) return false;
363
+ setBusy(`${action}:${entry.name}`);
364
+ setMessage(null);
365
+ try {
366
+ const result = await responseJson(await fetch(`/boostkit-skill-market/${action}`, {
367
+ method: "POST",
368
+ headers: { "content-type": "application/json" },
369
+ body: JSON.stringify({ name: entry.name, workspaceId })
370
+ }));
371
+ if (action === "enable" || action === "disable" || action === "remove") updateLocalCatalog(action, entry.name, result.skill);
372
+ else setCatalog(await list(false));
373
+ setMessage({ ok: true, text: `${t("success")}\uFF1A${result.skill.name}@${result.skill.version}` });
374
+ return true;
375
+ } catch (error) {
376
+ setMessage({ ok: false, error: error?.agentsBusy ? t("agentBusy") : error?.message ?? String(error) });
377
+ return false;
378
+ } finally {
379
+ setBusy(null);
380
+ }
381
+ }, [language, list, t, updateLocalCatalog, workspaceId]);
382
+ const openDetail = useCallback(async (entry) => {
383
+ if (workspaceId === null) return;
384
+ const displayName = entry.displayName?.[language] ?? entry.displayName?.zh ?? entry.name;
385
+ setDetail({ entry: { ...entry, displayName }, enabled: entry.enabled !== false, loading: true });
386
+ try {
387
+ const value = await responseJson(await fetch(`/boostkit-skill-market/detail?name=${encodeURIComponent(entry.name)}&workspaceId=${encodeURIComponent(workspaceId)}`, { cache: "no-store" }));
388
+ setDetail((current) => current?.entry.name === entry.name ? { ...current, enabled: value.enabled, loading: false, content: value.content } : current);
389
+ } catch (error) {
390
+ setDetail((current) => current?.entry.name === entry.name ? { ...current, loading: false, error: error?.message ?? String(error) } : current);
391
+ }
392
+ }, [language, workspaceId]);
393
+ const toggleDetail = useCallback(async () => {
394
+ if (!detail) return;
395
+ const action = detail.enabled === false ? "enable" : "disable";
396
+ if (await run(action, detail.entry)) setDetail((current) => current ? { ...current, enabled: action === "enable" } : current);
397
+ }, [detail, run]);
398
+ const removeDetail = useCallback(async () => {
399
+ if (detail && await run("remove", detail.entry)) setDetail(null);
400
+ }, [detail, run]);
401
+ return h("main", { style: styles.page }, h(
402
+ "div",
403
+ { style: styles.content },
404
+ h(
405
+ "div",
406
+ { style: styles.row },
407
+ h("div", null, h("h2", { style: styles.title }, t("nav")), h("div", { style: styles.muted }, t("subtitle"))),
408
+ h("span", { style: styles.grow }),
409
+ h("label", { style: styles.workspacePicker }, h("span", null, t("targetWorkspace")), h("select", {
410
+ value: workspaceId ?? "",
411
+ disabled: busy !== null || workspaces.length === 0,
412
+ style: disabled(styles.select, busy !== null || workspaces.length === 0),
413
+ onChange: (event) => {
414
+ setSelectedWorkspaceId(event.target.value);
415
+ setDetail(null);
416
+ }
417
+ }, ...workspaces.map((workspace) => h("option", { key: workspace.workspaceId, value: workspace.workspaceId }, workspace.title)))),
418
+ catalog?.repositoryUrl ? h("a", { href: catalog.repositoryUrl, target: "_blank", rel: "noreferrer", style: styles.link }, t("contribute")) : null,
419
+ h("button", { type: "button", disabled: busy !== null || workspaceId === null, style: disabled(styles.button, busy !== null || workspaceId === null), onClick: () => void load(true) }, t("refresh"))
420
+ ),
421
+ catalog ? h("div", { style: styles.catalogLine }, h("span", null, `${t("catalog")}\uFF1A${catalog.catalogPackage}`), h("span", null, "\xB7"), h("span", null, `${t("release")}\uFF1A${catalog.release || "\u2014"}`)) : null,
422
+ catalog && !catalog.connected ? h("div", { style: styles.error }, h("strong", null, t("offlineTitle")), h("div", null, t("offlineBody")), h("div", { style: styles.muted }, catalog.error)) : null,
423
+ message ? h("div", { style: message.ok ? styles.notice : styles.error }, h("strong", null, message.ok ? t("success") : t("failed")), h("div", null, message.text ?? message.error)) : null,
424
+ h(
425
+ "div",
426
+ { style: styles.tabs },
427
+ h("button", { type: "button", style: tab === "all" ? { ...styles.tab, ...styles.tabOn } : styles.tab, onClick: () => setTab("all") }, t("all")),
428
+ h("button", { type: "button", style: tab === "installed" ? { ...styles.tab, ...styles.tabOn } : styles.tab, onClick: () => setTab("installed") }, `${t("installed")} (${installedCount})`),
429
+ h("span", { style: styles.grow }),
430
+ h("span", { style: styles.muted }, `${entries.length} ${t("results")}`),
431
+ h("select", { value: category, style: styles.select, onChange: (event) => setCategory(event.target.value) }, h("option", { value: "all" }, t("allCategories")), ...categories.map((value) => h("option", { key: value, value }, value))),
432
+ h("input", { value: query, placeholder: t("search"), style: styles.search, onChange: (event) => setQuery(event.target.value) })
433
+ ),
434
+ h("div", { style: styles.grid }, entries.length ? entries.map((entry) => h(SkillCard, { key: entry.name, entry, t, language, busy, connected: catalog?.connected === true, run, share, openDetail })) : h("div", { style: styles.muted }, catalog ? t("empty") : t("loading"))),
435
+ h("div", { style: styles.muted }, t("availableImmediately")),
436
+ h(SkillDetail, { detail, t, busy, close: () => setDetail(null), toggle: toggleDetail, remove: removeDetail })
437
+ ));
438
+ }
439
+ function apply(ctx) {
440
+ ctx.effect(() => ctx.locale.register(NS, dictionaries), "boostkit-skill-market: dictionaries");
441
+ const t = ctx.locale.bind(NS);
442
+ const inject = () => ({
443
+ t,
444
+ workspaceSource: ctx.workspaces.list,
445
+ list: (refresh = false, workspaceId = "") => {
446
+ const params = new URLSearchParams({ workspaceId });
447
+ if (refresh) params.set("refresh", "1");
448
+ return fetch(`/boostkit-skill-market/catalog?${params.toString()}`, { cache: "no-store" }).then(responseJson);
449
+ }
450
+ });
451
+ ctx.slots.inject("main", () => ctx.slots.register({ name: "main", key: PANEL_ID, locale: NS, inject }, MarketPage));
452
+ ctx.slots.inject("sidebar.panellist", () => ctx.slots.register({
453
+ name: "sidebar.panellist",
454
+ id: PANEL_ID,
455
+ order: 42,
456
+ label: () => t("nav"),
457
+ locale: NS
458
+ }, MarketIcon));
459
+ }
460
+ exports.name = "boostkit-skill-market-client";
461
+ exports.inject = ["slots", "locale", "workspaces", "authManagementClient"];
462
+ exports.apply = apply;
463
+ return module.exports;
464
+ } });
@@ -0,0 +1,4 @@
1
+ # BoostKit Skill 市场
2
+ - insert:
3
+ - id: boostkit-skill-market
4
+ name: '@boostkit-dsh/skill-market'