dsh-m 0.0.2 → 0.1.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/client.js ADDED
@@ -0,0 +1,786 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "dsh-m",
3
+ factory: (require) => {
4
+ "use strict";
5
+
6
+ // src/client/main.jsx
7
+ var React = require("react");
8
+ var rd = require("react-dom");
9
+ var h = React.createElement;
10
+ var { useState, useEffect, useCallback, useMemo } = React;
11
+ var PLUGIN_ID = "dsh-m";
12
+ var API = "/dshm";
13
+ var CATEGORIES = [
14
+ ["market", "\u5E02\u573A"],
15
+ ["tools", "\u5DE5\u5177"],
16
+ ["ui", "\u754C\u9762"],
17
+ ["search", "\u641C\u7D22"],
18
+ ["media", "\u591A\u5A92\u4F53"],
19
+ ["other", "\u5176\u4ED6"]
20
+ ];
21
+ var CSS = `
22
+ .dshm-overlay{position:fixed;inset:0;z-index:2147483000;background:var(--dsw-alias-bg-mask-3,rgba(15,23,42,.48));display:flex;align-items:center;justify-content:center;padding:24px 16px;box-sizing:border-box}
23
+ .dshm-panel{width:min(920px,100%);height:min(680px,86vh);display:flex;flex-direction:column;background:var(--dsw-alias-bg-layer-3,#fff);border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:14px;box-shadow:0 18px 48px rgba(2,6,23,.25);overflow:hidden;font-family:inherit;color:var(--dsw-alias-label-primary,inherit)}
24
+ .dshm-head{display:flex;align-items:center;gap:8px;padding:10px 14px;border-bottom:1px solid var(--dsw-alias-border-l2,#e5e7eb)}
25
+ .dshm-title{font-weight:700;font-size:15px;margin-right:6px}
26
+ .dshm-tab{border:1px solid transparent;background:transparent;color:var(--dsw-alias-label-secondary,#4b5563);border-radius:8px;padding:5px 12px;font:inherit;font-size:13px;cursor:pointer}
27
+ .dshm-tab:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(38,49,72,.06))}
28
+ .dshm-tab.on{background:var(--dsw-alias-bg-layer-4,#eef2ff);color:var(--dsw-alias-label-primary,inherit);border-color:var(--dsw-alias-border-l2,#e5e7eb);font-weight:600}
29
+ .dshm-spacer{flex:1}
30
+ .dshm-body{flex:1;overflow:auto;padding:14px;display:flex;flex-direction:column;gap:12px}
31
+ .dshm-hint{color:var(--dsw-alias-label-caption,#6b7280);font-size:12px;line-height:18px;margin:0}
32
+ .dshm-err{color:var(--dsw-alias-state-danger-primary,#dc2626);font-size:12px;line-height:18px}
33
+ .dshm-btn{border:1px solid var(--dsw-alias-border-l2,#e5e7eb);background:var(--dsw-alias-bg-layer-3,#fff);color:var(--dsw-alias-label-primary,inherit);border-radius:8px;padding:5px 12px;font:inherit;font-size:12px;cursor:pointer;white-space:nowrap}
34
+ .dshm-btn:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(38,49,72,.06))}
35
+ .dshm-btn:disabled{opacity:.5;cursor:default}
36
+ .dshm-btn.primary{background:var(--dsw-alias-interactive-bg-selected,#4f46e5);border-color:var(--dsw-alias-interactive-bg-selected,#4f46e5);color:#fff}
37
+ .dshm-btn.primary:hover{filter:brightness(1.08)}
38
+ .dshm-btn.danger{color:var(--dsw-alias-state-danger-primary,#dc2626);border-color:var(--dsw-alias-state-danger-primary,#dc2626)}
39
+ .dshm-btn.sm{padding:3px 9px;font-size:11px}
40
+ .dshm-input{flex:1;min-width:120px;border:1px solid var(--dsw-alias-border-l2,#c7d2fe);background:var(--dsw-alias-bg-layer-2,transparent);color:var(--dsw-alias-label-primary,inherit);border-radius:8px;padding:6px 10px;font:inherit;font-size:13px;outline:none}
41
+ .dshm-input:focus{border-color:var(--dsw-alias-interactive-bg-selected,#4f46e5)}
42
+ .dshm-chips{display:flex;flex-wrap:wrap;gap:6px}
43
+ .dshm-chip{border:1px solid var(--dsw-alias-border-l2,#e5e7eb);background:transparent;color:var(--dsw-alias-label-secondary,#4b5563);border-radius:999px;padding:2px 10px;font:inherit;font-size:11px;cursor:pointer}
44
+ .dshm-chip.on{background:var(--dsw-alias-bg-layer-4,#eef2ff);color:var(--dsw-alias-label-primary,inherit);border-color:var(--dsw-alias-interactive-bg-selected,#4f46e5)}
45
+ .dshm-cards{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}
46
+ @media (max-width:680px){.dshm-cards{grid-template-columns:1fr}}
47
+ .dshm-card{display:flex;gap:12px;align-items:flex-start;background:var(--dsw-alias-bg-layer-2,rgba(38,49,72,.04));border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:12px;padding:12px;cursor:pointer;text-align:left;width:100%;box-sizing:border-box;min-width:0;font:inherit;color:var(--dsw-alias-label-primary,inherit);transition:border-color .16s,background .16s}
48
+ .dshm-card:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(38,49,72,.06));border-color:var(--dsw-alias-label-dimmed,#c7d2fe)}
49
+ .dshm-icon{width:40px;height:40px;border-radius:10px;object-fit:cover;border:1px solid var(--dsw-alias-border-l2,#e5e7eb);flex-shrink:0;background:linear-gradient(135deg,#c7d2fe,#fbcfe8);display:grid;place-items:center;font-weight:700;font-size:16px;color:#374151}
50
+ .dshm-meta{min-width:0;flex:1;display:flex;flex-direction:column;gap:2px}
51
+ .dshm-top{display:flex;align-items:center;gap:8px;min-width:0}
52
+ .dshm-name{flex:1;min-width:0;font-weight:600;font-size:14px;line-height:20px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
53
+ .dshm-badge{flex:none;font-size:11px;line-height:16px;padding:0 6px;border-radius:999px;background:var(--dsw-alias-state-success-tertiary,#ecfdf5);color:var(--dsw-alias-state-success-primary,#047857)}
54
+ .dshm-badge.warn{background:var(--dsw-alias-state-warning-tertiary,#fffbeb);color:var(--dsw-alias-state-warning-primary,#b45309)}
55
+ .dshm-badge.info{background:var(--dsw-alias-bg-layer-4,#eef2ff);color:var(--dsw-alias-label-secondary,#4b5563)}
56
+ .dshm-badge.err{background:var(--dsw-alias-state-danger-tertiary,#fef2f2);color:var(--dsw-alias-state-danger-primary,#dc2626)}
57
+ .dshm-desc{color:var(--dsw-alias-label-tertiary,#6b7280);font-size:12px;line-height:18px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
58
+ .dshm-sub{display:flex;align-items:center;gap:8px;font-size:11px;color:var(--dsw-alias-label-caption,#6b7280)}
59
+ .dshm-detail{margin-top:8px;border-top:1px dashed var(--dsw-alias-border-l2,#e5e7eb);padding-top:8px;display:flex;flex-direction:column;gap:6px;font-size:12px;color:var(--dsw-alias-label-secondary,#4b5563)}
60
+ .dshm-actions{display:flex;gap:6px;flex-wrap:wrap;margin-top:4px}
61
+ .dshm-banner{display:flex;align-items:center;gap:10px;padding:10px 14px;border-top:1px solid var(--dsw-alias-border-l2,#e5e7eb);background:var(--dsw-alias-state-warning-tertiary,#fffbeb);color:var(--dsw-alias-state-warning-primary,#b45309);font-size:12px}
62
+ .dshm-banner .dshm-banner-text{flex:1}
63
+ .dshm-row{display:flex;align-items:center;gap:8px}
64
+ .dshm-kv{display:grid;grid-template-columns:110px 1fr;gap:4px 10px;font-size:12px}
65
+ .dshm-kv .k{color:var(--dsw-alias-label-caption,#6b7280)}
66
+ .dshm-spin{display:inline-block;width:12px;height:12px;border:2px solid var(--dsw-alias-border-l2,#c7d2fe);border-top-color:var(--dsw-alias-interactive-bg-selected,#4f46e5);border-radius:50%;animation:dshm-rot .8s linear infinite;vertical-align:-2px}
67
+ @keyframes dshm-rot{to{transform:rotate(360deg)}}
68
+ .dshm-empty{text-align:center;color:var(--dsw-alias-label-caption,#6b7280);font-size:13px;padding:32px 0}
69
+ `;
70
+ function ensureCss() {
71
+ if (typeof document === "undefined" || document.getElementById("dshm-css")) return;
72
+ const el = document.createElement("style");
73
+ el.id = "dshm-css";
74
+ el.textContent = CSS;
75
+ document.head.appendChild(el);
76
+ }
77
+ async function api(method, params) {
78
+ const res = await fetch(API, {
79
+ method: "POST",
80
+ headers: { "content-type": "application/json" },
81
+ body: JSON.stringify({ method, ...params || {} })
82
+ });
83
+ const data = await res.json().catch(() => ({}));
84
+ if (!res.ok || data.ok === false) throw new Error(data.error || `API ${res.status}`);
85
+ return data;
86
+ }
87
+ function fmtDate(iso) {
88
+ if (!iso) return "\u2014";
89
+ const d = new Date(iso);
90
+ return Number.isFinite(d.getTime()) ? d.toLocaleString("zh-CN", { hour12: false }) : "\u2014";
91
+ }
92
+ function useAsync(fn, deps) {
93
+ const [state, setState] = useState({ loading: true, data: null, error: null });
94
+ const run = useCallback((force) => {
95
+ setState((s) => ({ ...s, loading: true, error: null }));
96
+ return fn(force).then((data) => setState({ loading: false, data, error: null })).catch((err) => setState({ loading: false, data: null, error: String(err && err.message || err) }));
97
+ }, deps);
98
+ useEffect(() => {
99
+ run(false);
100
+ }, [run]);
101
+ return { ...state, reload: run };
102
+ }
103
+ function Icon({ entry }) {
104
+ const [broken, setBroken] = useState(false);
105
+ const letter = String(entry.name || entry.id || "?").charAt(0).toUpperCase();
106
+ const url = entry.icon || (entry.github ? `https://github.com/${entry.github.split("/")[0]}.png?size=64` : null);
107
+ if (!url || broken) {
108
+ return h("div", { className: "dshm-icon", "aria-hidden": "true" }, letter);
109
+ }
110
+ return h("img", {
111
+ className: "dshm-icon",
112
+ src: url,
113
+ alt: "",
114
+ onError: () => setBroken(true),
115
+ referrerPolicy: "no-referrer"
116
+ });
117
+ }
118
+ function Spin() {
119
+ return h("span", { className: "dshm-spin" });
120
+ }
121
+ function TwoStepButton({ label, confirmLabel, className, onConfirm, disabled }) {
122
+ const [arm, setArm] = useState(false);
123
+ useEffect(() => {
124
+ if (!arm) return;
125
+ const t = setTimeout(() => setArm(false), 4e3);
126
+ return () => clearTimeout(t);
127
+ }, [arm]);
128
+ return h(
129
+ "button",
130
+ {
131
+ className: `${className || "dshm-btn"} ${arm ? "danger" : ""}`.trim(),
132
+ disabled,
133
+ onClick: (e) => {
134
+ e.stopPropagation();
135
+ if (!arm) {
136
+ setArm(true);
137
+ } else {
138
+ setArm(false);
139
+ onConfirm();
140
+ }
141
+ }
142
+ },
143
+ arm ? confirmLabel : label
144
+ );
145
+ }
146
+ function RestartBanner({ note, onDone }) {
147
+ const [phase, setPhase] = useState("idle");
148
+ const [err, setErr] = useState(null);
149
+ const restart = useCallback(async () => {
150
+ setErr(null);
151
+ setPhase("restarting");
152
+ try {
153
+ const ping0 = await api("ping");
154
+ await api("restart");
155
+ setPhase("waiting");
156
+ const deadline = Date.now() + 9e4;
157
+ for (; ; ) {
158
+ await new Promise((r) => setTimeout(r, 2e3));
159
+ if (Date.now() > deadline) throw new Error("\u91CD\u542F\u8D85\u65F6\uFF0C\u8BF7\u624B\u52A8\u68C0\u67E5 dsh web \u670D\u52A1\u72B6\u6001");
160
+ try {
161
+ const ping = await api("ping");
162
+ if (ping.boot !== ping0.boot) break;
163
+ } catch {
164
+ }
165
+ }
166
+ setPhase("idle");
167
+ onDone(true);
168
+ } catch (e) {
169
+ setPhase("idle");
170
+ setErr(String(e && e.message || e));
171
+ }
172
+ }, [onDone]);
173
+ return h(
174
+ "div",
175
+ { className: "dshm-banner" },
176
+ h(
177
+ "span",
178
+ { className: "dshm-banner-text" },
179
+ phase === "restarting" ? "\u6B63\u5728\u8BF7\u6C42\u91CD\u542F\u2026" : phase === "waiting" ? "\u5DF2\u8BF7\u6C42\u91CD\u542F\uFF0C\u7B49\u5F85 DSH Web \u6062\u590D\u2026" : err ? `\u91CD\u542F\u5931\u8D25\uFF1A${err}` : note || "\u672C\u6B21\u53D8\u66F4\u9700\u8981\u91CD\u542F DSH Web \u540E\u751F\u6548\u3002"
180
+ ),
181
+ phase === "idle" && !err ? h("button", { className: "dshm-btn primary sm", onClick: restart }, "\u26A1 \u4E00\u952E\u91CD\u542F") : null,
182
+ phase === "restarting" || phase === "waiting" ? Spin() : null,
183
+ phase === "idle" && err ? h("button", { className: "dshm-btn sm", onClick: () => onDone(false) }, "\u77E5\u9053\u4E86") : null,
184
+ phase === "idle" && !err ? h("button", { className: "dshm-btn sm", onClick: () => onDone(false) }, "\u7A0D\u540E") : null
185
+ );
186
+ }
187
+ function MarketTab({ notify }) {
188
+ const { loading, data, error, reload } = useAsync((force) => api("market", force ? { force: true } : {}), []);
189
+ const [q, setQ] = useState("");
190
+ const [cat, setCat] = useState(null);
191
+ const [openId, setOpenId] = useState(null);
192
+ const [busyId, setBusyId] = useState(null);
193
+ const items = useMemo(() => {
194
+ const list = data && data.items || [];
195
+ const kw = q.trim().toLowerCase();
196
+ return list.filter((it) => {
197
+ if (cat && it.category !== cat) return false;
198
+ if (!kw) return true;
199
+ const hay = `${it.id} ${it.name} ${it.description} ${(it.tags || []).join(" ")}`.toLowerCase();
200
+ return hay.includes(kw);
201
+ });
202
+ }, [data, q, cat]);
203
+ const doInstall = async (it, version) => {
204
+ setBusyId(it.id);
205
+ try {
206
+ const res = await api("install", { id: it.id, ...version ? { version } : {} });
207
+ notify({
208
+ kind: "ok",
209
+ text: `\u5DF2\u5B89\u88C5 ${res.pkg}${res.version ? ` v${res.version}` : ""}` + (res.usedAllowAllBuilds ? "\uFF08\u6CE8\u610F\uFF1A\u8BE5\u63D2\u4EF6\u6267\u884C\u4E86\u6784\u5EFA\u811A\u672C\uFF0C\u5DF2\u6309\u7B56\u7565\u653E\u884C\uFF09" : "")
210
+ });
211
+ await reload(false);
212
+ } catch (e) {
213
+ notify({ kind: "err", text: `\u5B89\u88C5\u5931\u8D25\uFF1A${e && e.message || e}` });
214
+ } finally {
215
+ setBusyId(null);
216
+ }
217
+ };
218
+ return h(
219
+ React.Fragment,
220
+ null,
221
+ h(
222
+ "div",
223
+ { className: "dshm-row" },
224
+ h("input", {
225
+ className: "dshm-input",
226
+ placeholder: "\u641C\u7D22\u540D\u79F0 / \u63CF\u8FF0 / \u6807\u7B7E\u2026",
227
+ value: q,
228
+ onChange: (e) => setQ(e.target.value)
229
+ }),
230
+ h("button", { className: "dshm-btn", onClick: () => reload(true), title: "\u5F3A\u5236\u5237\u65B0\uFF08\u8DF3\u8FC7\u7F13\u5B58\uFF09" }, loading ? Spin() : "\u21BB \u5237\u65B0")
231
+ ),
232
+ h(
233
+ "div",
234
+ { className: "dshm-chips" },
235
+ h("button", { className: `dshm-chip${cat === null ? " on" : ""}`, onClick: () => setCat(null) }, "\u5168\u90E8"),
236
+ CATEGORIES.map(([key, label]) => {
237
+ const n = (data && data.items || []).filter((it) => it.category === key).length;
238
+ return h(
239
+ "button",
240
+ { key, className: `dshm-chip${cat === key ? " on" : ""}`, onClick: () => setCat(cat === key ? null : key) },
241
+ `${label}${n ? ` ${n}` : ""}`
242
+ );
243
+ })
244
+ ),
245
+ loading && !data ? h("div", { className: "dshm-empty" }, "\u52A0\u8F7D\u6536\u5F55\u6E05\u5355\u4E2D\u2026 ", Spin()) : error ? h("div", { className: "dshm-err" }, `\u52A0\u8F7D\u5931\u8D25\uFF1A${error}`) : items.length === 0 ? h("div", { className: "dshm-empty" }, "\u6CA1\u6709\u5339\u914D\u7684\u6536\u5F55\u6761\u76EE") : h(
246
+ "div",
247
+ { className: "dshm-cards" },
248
+ items.map((it) => Card({
249
+ key: it.id,
250
+ icon: h(Icon, { entry: it }),
251
+ name: it.name,
252
+ badges: [
253
+ it.outdated ? h("span", { className: "dshm-badge warn", key: "u" }, "\u53EF\u5347\u7EA7") : null,
254
+ it.installed ? h("span", { className: "dshm-badge", key: "i" }, "\u5DF2\u5B89\u88C5") : null,
255
+ h("span", { className: "dshm-badge info", key: "s" }, it.source === "npm" ? "npm" : "github")
256
+ ],
257
+ desc: it.description,
258
+ sub: [
259
+ it.latestVersion ? `\u6700\u65B0 v${it.latestVersion}` : it.latestSha ? `HEAD ${it.latestSha.slice(0, 7)}` : null,
260
+ it.installedVersion ? `\u5DF2\u88C5 v${it.installedVersion}` : null,
261
+ it.latestError ? `\u7248\u672C\u67E5\u8BE2\u5931\u8D25` : null
262
+ ].filter(Boolean).join(" \xB7 "),
263
+ open: openId === it.id,
264
+ onToggle: () => setOpenId(openId === it.id ? null : it.id),
265
+ detail: DetailRows([
266
+ ["\u6536\u5F55 id", it.id],
267
+ ["\u6765\u6E90", it.source === "npm" ? `npm \xB7 ${it.npm}` : `GitHub \xB7 ${it.github}`],
268
+ ["\u6700\u65B0", it.latestVersion ? `v${it.latestVersion}` : it.latestSha ? it.latestSha : it.latestError || "\u2014"],
269
+ ["\u5DF2\u88C5", it.installedPkg ? `${it.installedPkg} v${it.installedVersion || "?"}` : "\u672A\u5B89\u88C5"],
270
+ ["\u6807\u7B7E", (it.tags || []).join("\u3001") || "\u2014"],
271
+ it.latestError ? ["\u63D0\u793A", `\u7248\u672C\u67E5\u8BE2\u5931\u8D25\uFF1A${it.latestError}`] : null
272
+ ]),
273
+ actions: [
274
+ it.installed ? h("span", { className: "dshm-hint", key: "hint" }, "\u5DF2\u5B89\u88C5\uFF0C\u53EF\u5728\u300C\u5DF2\u88C5\u300D\u9875\u7BA1\u7406") : h(
275
+ "button",
276
+ {
277
+ key: "install",
278
+ className: "dshm-btn primary sm",
279
+ disabled: busyId === it.id,
280
+ onClick: (e) => {
281
+ e.stopPropagation();
282
+ doInstall(it);
283
+ }
284
+ },
285
+ busyId === it.id ? h(Spin) : "\u5B89\u88C5"
286
+ )
287
+ ]
288
+ }))
289
+ )
290
+ );
291
+ }
292
+ function InstalledTab({ notify }) {
293
+ const { loading, data, error, reload } = useAsync(() => api("installed"), []);
294
+ const [openPkg, setOpenPkg] = useState(null);
295
+ const [busyPkg, setBusyPkg] = useState(null);
296
+ const doUninstall = async (it) => {
297
+ setBusyPkg(it.pkg);
298
+ try {
299
+ const res = await api("uninstall", { pkg: it.pkg });
300
+ notify({
301
+ kind: "ok",
302
+ text: `\u5DF2\u5378\u8F7D ${res.pkg}${res.liveDisabled ? "\uFF08\u5DF2\u5148\u4E0B\u7EBF\u8FD0\u884C\u4E2D\u7684\u754C\u9762\uFF09" : ""}` + (res.leftovers && res.leftovers.length ? `\uFF1B\u68C0\u6D4B\u5230\u7591\u4F3C\u6B8B\u7559\u6570\u636E\uFF1A${res.leftovers.join("\u3001")}` : "")
303
+ });
304
+ await reload();
305
+ } catch (e) {
306
+ notify({ kind: "err", text: `\u5378\u8F7D\u5931\u8D25\uFF1A${e && e.message || e}` });
307
+ } finally {
308
+ setBusyPkg(null);
309
+ }
310
+ };
311
+ const doUpgrade = async (it) => {
312
+ setBusyPkg(it.pkg);
313
+ try {
314
+ const res = await api("upgrade", { pkg: it.pkg });
315
+ notify({
316
+ kind: "ok",
317
+ text: `\u5DF2\u5347\u7EA7 ${res.pkg}${res.fromVersion ? `\uFF08v${res.fromVersion} \u2192 ${res.version ? `v${res.version}` : res.sha ? res.sha.slice(0, 7) : "\u6700\u65B0"}\uFF09` : ""}` + (res.usedAllowAllBuilds ? "\uFF08\u6CE8\u610F\uFF1A\u8BE5\u63D2\u4EF6\u6267\u884C\u4E86\u6784\u5EFA\u811A\u672C\uFF09" : "")
318
+ });
319
+ await reload();
320
+ } catch (e) {
321
+ notify({ kind: "err", text: `\u5347\u7EA7\u5931\u8D25\uFF1A${e && e.message || e}` });
322
+ } finally {
323
+ setBusyPkg(null);
324
+ }
325
+ };
326
+ if (loading && !data) return h("div", { className: "dshm-empty" }, "\u8BFB\u53D6 web profile \u4E2D\u2026 ", Spin());
327
+ if (error) return h("div", { className: "dshm-err" }, `\u8BFB\u53D6\u5931\u8D25\uFF1A${error}`);
328
+ const items = data && data.items || [];
329
+ if (!items.length) return h("div", { className: "dshm-empty" }, `web profile \u5C1A\u672A\u5B89\u88C5\u4EFB\u4F55 dsh \u63D2\u4EF6\uFF08${data.profileDir}\uFF09`);
330
+ return h(
331
+ React.Fragment,
332
+ null,
333
+ h("div", { className: "dshm-hint" }, `web profile\uFF1A${data.profileDir}${data.others ? ` \xB7 \u53E6\u6709 ${data.others} \u4E2A\u975E dsh \u4F9D\u8D56\u672A\u5217\u51FA` : ""}`),
334
+ h(
335
+ "div",
336
+ { className: "dshm-cards" },
337
+ items.map((it) => Card({
338
+ key: it.pkg,
339
+ icon: h(Icon, { entry: { name: it.name, github: it.spec.startsWith("github:") ? it.spec.slice(7).split("#")[0] : null, icon: null } }),
340
+ name: it.name,
341
+ badges: [
342
+ it.outdated ? h("span", { className: "dshm-badge warn", key: "u" }, `\u53EF\u5347\u7EA7${it.latestVersion ? ` \u2192 v${it.latestVersion}` : ""}`) : null,
343
+ it.registryId ? h("span", { className: "dshm-badge", key: "r" }, "\u5E02\u573A\u5B89\u88C5") : h("span", { className: "dshm-badge info", key: "r" }, "\u975E\u5E02\u573A\u5B89\u88C5")
344
+ ],
345
+ desc: it.description || "\uFF08\u65E0\u63CF\u8FF0\uFF09",
346
+ sub: [
347
+ `v${it.version || "?"}`,
348
+ { npm: "npm", github: "github", link: "\u672C\u5730 link", file: "\u672C\u5730 file", unknown: "\u672A\u77E5" }[it.source] || it.source
349
+ ].join(" \xB7 "),
350
+ open: openPkg === it.pkg,
351
+ onToggle: () => setOpenPkg(openPkg === it.pkg ? null : it.pkg),
352
+ detail: DetailRows([
353
+ ["\u5305\u540D", it.pkg],
354
+ ["\u5B89\u88C5 spec", it.spec],
355
+ ["\u6700\u65B0", it.latestVersion ? `v${it.latestVersion}` : "\u2014"],
356
+ ["\u6536\u5F55", it.registryId || "\u4E0D\u5728\u6536\u5F55\u6E05\u5355\u4E2D"],
357
+ ["\u8DEF\u5F84", it.path]
358
+ ]),
359
+ actions: [
360
+ it.outdated ? h(
361
+ "button",
362
+ {
363
+ key: "up",
364
+ className: "dshm-btn primary sm",
365
+ disabled: busyPkg === it.pkg,
366
+ onClick: (e) => {
367
+ e.stopPropagation();
368
+ doUpgrade(it);
369
+ }
370
+ },
371
+ busyPkg === it.pkg ? h(Spin) : "\u5347\u7EA7"
372
+ ) : null,
373
+ it.registryId || it.source === "npm" ? h(TwoStepButton, {
374
+ key: "un",
375
+ label: "\u5378\u8F7D",
376
+ confirmLabel: "\u786E\u8BA4\u5378\u8F7D\uFF1F",
377
+ className: "dshm-btn sm",
378
+ disabled: busyPkg === it.pkg,
379
+ onConfirm: () => doUninstall(it)
380
+ }) : null
381
+ ]
382
+ }))
383
+ )
384
+ );
385
+ }
386
+ function SettingsTab({ notify }) {
387
+ const reg = useAsync((force) => api("registry", force ? { force: true } : {}), []);
388
+ const self = useAsync(() => api("self-check"), []);
389
+ const [busy, setBusy] = useState(false);
390
+ const [upgrading, setUpgrading] = useState(false);
391
+ const refresh = async () => {
392
+ setBusy(true);
393
+ try {
394
+ await reg.reload(true);
395
+ notify({ kind: "ok", text: "\u6536\u5F55\u6E05\u5355\u5DF2\u5F3A\u5236\u5237\u65B0" });
396
+ } finally {
397
+ setBusy(false);
398
+ }
399
+ };
400
+ const upgradeSelf = async () => {
401
+ setUpgrading(true);
402
+ try {
403
+ const res = await api("self-upgrade");
404
+ notify({ kind: "ok", text: `dsh-m \u5DF2\u66F4\u65B0\u5230 v${res.version}\uFF0C\u91CD\u542F\u540E\u751F\u6548` });
405
+ await self.reload();
406
+ } catch (e) {
407
+ notify({ kind: "err", text: `\u81EA\u66F4\u65B0\u5931\u8D25\uFF1A${e && e.message || e}` });
408
+ } finally {
409
+ setUpgrading(false);
410
+ }
411
+ };
412
+ return h(
413
+ React.Fragment,
414
+ null,
415
+ Section(
416
+ "\u6536\u5F55\u6E05\u5355\uFF08registry\uFF09",
417
+ h(
418
+ "div",
419
+ { className: "dshm-kv" },
420
+ h("span", { className: "k" }, "\u5F53\u524D\u6765\u6E90"),
421
+ h("span", null, regSourceLabel(reg.data)),
422
+ h("span", { className: "k" }, "\u66F4\u65B0\u65F6\u95F4"),
423
+ h("span", null, fmtDate(reg.data && reg.data.fetchedAt)),
424
+ h("span", { className: "k" }, "\u6761\u76EE\u6570"),
425
+ h("span", null, reg.data ? `${reg.data.plugins.length} \u6761` : "\u2014"),
426
+ h("span", { className: "k" }, "\u7F13\u5B58\u7B56\u7565"),
427
+ h("span", null, "TTL 60 \u5206\u949F\uFF1B\u8BBE\u7F6E registryUrl \u53EF\u8986\u76D6\u6E90")
428
+ ),
429
+ reg.data && reg.data.errors && reg.data.errors.length ? h("div", { className: "dshm-err" }, `\u8FDC\u7AEF\u63D0\u793A\uFF1A${reg.data.errors.join("\uFF1B")}`) : null,
430
+ h(
431
+ "div",
432
+ { className: "dshm-actions" },
433
+ h("button", { className: "dshm-btn sm", disabled: busy || reg.loading, onClick: refresh }, busy || reg.loading ? h(Spin) : "\u5F3A\u5236\u5237\u65B0")
434
+ )
435
+ ),
436
+ Section(
437
+ "dsh-m \u81EA\u8EAB",
438
+ h(
439
+ "div",
440
+ { className: "dshm-kv" },
441
+ h("span", { className: "k" }, "\u5F53\u524D\u7248\u672C"),
442
+ h("span", null, self.data ? `v${self.data.current}` : "\u2014"),
443
+ h("span", { className: "k" }, "npm \u6700\u65B0"),
444
+ h("span", null, self.data ? self.data.latest ? `v${self.data.latest}` : `\u67E5\u8BE2\u5931\u8D25${self.data.error ? `\uFF1A${self.data.error}` : ""}` : "\u2026")
445
+ ),
446
+ self.data && self.data.outdated ? h(
447
+ "div",
448
+ { className: "dshm-actions" },
449
+ h("button", { className: "dshm-btn primary sm", disabled: upgrading, onClick: upgradeSelf }, upgrading ? h(Spin) : "\u5347\u7EA7 dsh-m"),
450
+ h("span", { className: "dshm-hint" }, "\u5347\u7EA7\u540E\u540C\u6837\u9700\u8981\u91CD\u542F\u751F\u6548")
451
+ ) : null
452
+ ),
453
+ Section(
454
+ "\u5173\u4E8E",
455
+ h(
456
+ "div",
457
+ { className: "dshm-hint" },
458
+ "DSH Marketplace\uFF08dsh-m\uFF09\u2014 \u4E2A\u4EBA\u81EA\u7528\u7684 DeepSeek Harness \u63D2\u4EF6\u5E02\u573A\u3002\u6536\u5F55\u3001\u5B89\u88C5\u3001\u5378\u8F7D\u3001\u5347\u7EA7\uFF0C\u5168\u90E8\u672C\u673A\u5B8C\u6210\u3002"
459
+ )
460
+ )
461
+ );
462
+ }
463
+ function regSourceLabel(data) {
464
+ if (!data) return "\u2014";
465
+ const map = { override: "\u81EA\u5B9A\u4E49\u6E90", jsdelivr: "jsDelivr\uFF08@main\uFF09", raw: "raw.githubusercontent\uFF08@main\uFF09", cache: "\u672C\u5730\u7F13\u5B58", bundled: "\u5305\u5185\u5FEB\u7167\uFF08\u515C\u5E95\uFF09" };
466
+ return map[data.source] || data.source;
467
+ }
468
+ function Section(title, ...children) {
469
+ return h(
470
+ "div",
471
+ { style: { display: "flex", flexDirection: "column", gap: "8px" } },
472
+ h("div", { style: { fontWeight: 600, fontSize: "13px" } }, title),
473
+ ...children
474
+ );
475
+ }
476
+ function DetailRows(rows) {
477
+ const list = rows.filter(Boolean);
478
+ return h(
479
+ "div",
480
+ { className: "dshm-kv" },
481
+ list.flatMap(([k, v]) => [
482
+ h("span", { className: "k", key: `${k}-k` }, k),
483
+ h("span", { key: `${k}-v`, style: { wordBreak: "break-all" } }, String(v))
484
+ ])
485
+ );
486
+ }
487
+ function Card({ icon, name, badges, desc, sub, open, onToggle, detail, actions }) {
488
+ return h(
489
+ "div",
490
+ {
491
+ className: "dshm-card",
492
+ role: "button",
493
+ tabIndex: 0,
494
+ onClick: onToggle,
495
+ onKeyDown: (e) => {
496
+ if (e.key === "Enter" || e.key === " ") onToggle();
497
+ }
498
+ },
499
+ icon,
500
+ h(
501
+ "div",
502
+ { className: "dshm-meta" },
503
+ h("div", { className: "dshm-top" }, h("span", { className: "dshm-name" }, name), ...badges.filter(Boolean)),
504
+ h("div", { className: "dshm-desc", style: open ? { WebkitLineClamp: "unset" } : null }, desc),
505
+ sub ? h("div", { className: "dshm-sub" }, sub) : null,
506
+ open ? h("div", { className: "dshm-detail" }, detail) : null,
507
+ open && actions && actions.length ? h("div", { className: "dshm-actions" }, ...actions) : null
508
+ )
509
+ );
510
+ }
511
+ var TABS = [
512
+ ["market", "\u5E02\u573A"],
513
+ ["installed", "\u5DF2\u88C5"],
514
+ ["settings", "\u8BBE\u7F6E"]
515
+ ];
516
+ function MarketPanel({ onClose }) {
517
+ const [tab, setTab] = useState("market");
518
+ const [banner, setBanner] = useState(null);
519
+ const [toast, setToast] = useState(null);
520
+ useEffect(() => {
521
+ const onKey = (e) => {
522
+ if (e.key === "Escape" && onClose) onClose();
523
+ };
524
+ document.addEventListener("keydown", onKey);
525
+ return () => document.removeEventListener("keydown", onKey);
526
+ }, [onClose]);
527
+ useEffect(() => {
528
+ if (!toast) return;
529
+ const t = setTimeout(() => setToast(null), 6e3);
530
+ return () => clearTimeout(t);
531
+ }, [toast]);
532
+ const notify = useCallback(({ kind, text }) => {
533
+ setToast({ kind, text });
534
+ if (kind === "ok") setBanner({ text: "\u53D8\u66F4\u5B8C\u6210\uFF0C\u9700\u8981\u91CD\u542F DSH Web \u540E\u751F\u6548\u3002" });
535
+ }, []);
536
+ return h(
537
+ "div",
538
+ { className: "dshm-overlay", onClick: onClose },
539
+ h(
540
+ "div",
541
+ { className: "dshm-panel", onClick: (e) => e.stopPropagation() },
542
+ h(
543
+ "div",
544
+ { className: "dshm-head" },
545
+ h("span", { className: "dshm-title" }, "\u{1F6CD} DSH \u5E02\u573A"),
546
+ TABS.map(
547
+ ([key, label]) => h("button", { key, className: `dshm-tab${tab === key ? " on" : ""}`, onClick: () => setTab(key) }, label)
548
+ ),
549
+ h("span", { className: "dshm-spacer" }),
550
+ h("button", { className: "dshm-btn", onClick: onClose }, "\u5173\u95ED")
551
+ ),
552
+ h(
553
+ "div",
554
+ { className: "dshm-body" },
555
+ tab === "market" ? h(MarketTab, { notify }) : null,
556
+ tab === "installed" ? h(InstalledTab, { notify }) : null,
557
+ tab === "settings" ? h(SettingsTab, { notify }) : null
558
+ ),
559
+ toast ? h(
560
+ "div",
561
+ { className: `dshm-banner`, style: toast.kind === "err" ? { background: "var(--dsw-alias-state-danger-tertiary,#fef2f2)", color: "var(--dsw-alias-state-danger-primary,#dc2626)" } : null },
562
+ h("span", { className: "dshm-banner-text" }, toast.text)
563
+ ) : null,
564
+ banner ? h(RestartBanner, { note: banner.text, onDone: () => setBanner(null) }) : null
565
+ )
566
+ );
567
+ }
568
+ function mountPanel() {
569
+ if (typeof document === "undefined") return;
570
+ ensureCss();
571
+ if (document.getElementById("dshm-panel-root")) return;
572
+ const container = document.createElement("div");
573
+ container.id = "dshm-panel-root";
574
+ document.body.appendChild(container);
575
+ const rdom = rd && rd.default && (rd.default.createRoot || rd.default.render) ? rd.default : rd;
576
+ let root = null;
577
+ const close = () => {
578
+ try {
579
+ if (root && typeof root.unmount === "function") root.unmount();
580
+ else if (rdom && typeof rdom.unmountComponentAtNode === "function") rdom.unmountComponentAtNode(container);
581
+ } catch {
582
+ }
583
+ container.remove();
584
+ };
585
+ if (rdom && typeof rdom.createRoot === "function") {
586
+ root = rdom.createRoot(container);
587
+ root.render(h(MarketPanel, { onClose: close }));
588
+ } else if (rdom && typeof rdom.render === "function") {
589
+ rdom.render(h(MarketPanel, { onClose: close }), container);
590
+ } else {
591
+ container.remove();
592
+ }
593
+ }
594
+ function MarketEntry(props) {
595
+ useEffect(() => ensureCss(), []);
596
+ return h(
597
+ "button",
598
+ {
599
+ className: "dshm-btn dshm-entry",
600
+ onClick: () => {
601
+ try {
602
+ mountPanel();
603
+ } catch (e) {
604
+ console.error("[dsh-m] \u6253\u5F00\u5E02\u573A\u9762\u677F\u5931\u8D25:", e);
605
+ }
606
+ },
607
+ title: "DSH Marketplace",
608
+ style: { margin: "4px" }
609
+ },
610
+ "\u{1F6CD}",
611
+ props && props.wide ? " DSH \u5E02\u573A" : null
612
+ );
613
+ }
614
+ function registerSlot(slots, options, component) {
615
+ const next = { ...options };
616
+ if (next.id == null && next.key != null) next.id = String(next.key);
617
+ if (next.key == null && next.id != null) next.key = next.id;
618
+ return slots.register(next, component);
619
+ }
620
+ function pickPayload(props) {
621
+ const found = [];
622
+ const visit = (node, depth) => {
623
+ if (!node || depth > 6) return;
624
+ if (typeof node === "string") {
625
+ const t = node.trim();
626
+ if ((t.startsWith("{") || t.startsWith("[")) && t.length > 8) {
627
+ try {
628
+ visit(JSON.parse(t), depth + 1);
629
+ } catch {
630
+ }
631
+ }
632
+ return;
633
+ }
634
+ if (typeof node !== "object") return;
635
+ if (Array.isArray(node)) {
636
+ for (const x of node) visit(x, depth + 1);
637
+ return;
638
+ }
639
+ if (Array.isArray(node.items)) found.push(node);
640
+ for (const key of ["block", "meta", "result", "resultView", "view", "data", "value", "payload", "content", "message"]) {
641
+ if (node[key] != null) visit(node[key], depth + 1);
642
+ }
643
+ };
644
+ visit(props, 0);
645
+ return found.find((x) => x && Array.isArray(x.items)) || null;
646
+ }
647
+ function parseToolArgs(props) {
648
+ const block = props?.block;
649
+ const raw = (block && "kind" in block ? block.call?.argsRaw : block?.argsRaw) || "";
650
+ if (!raw || typeof raw !== "string") return {};
651
+ try {
652
+ return JSON.parse(raw);
653
+ } catch {
654
+ return {};
655
+ }
656
+ }
657
+ function ToolCardRow({ it, onInstalled }) {
658
+ const [busy, setBusy] = useState(false);
659
+ const install = async (e) => {
660
+ e.stopPropagation();
661
+ setBusy(true);
662
+ try {
663
+ const res = await api("install", { id: it.id });
664
+ onInstalled({ ...it, installed: true, installedPkg: res.pkg, installedVersion: res.version });
665
+ } catch {
666
+ } finally {
667
+ setBusy(false);
668
+ }
669
+ };
670
+ return h(
671
+ "div",
672
+ { className: "dshm-card", style: { cursor: "default" } },
673
+ h(Icon, { entry: it }),
674
+ h(
675
+ "div",
676
+ { className: "dshm-meta" },
677
+ h(
678
+ "div",
679
+ { className: "dshm-top" },
680
+ h("span", { className: "dshm-name" }, it.name),
681
+ h("span", { className: "dshm-badge info" }, it.source === "npm" ? "npm" : "github"),
682
+ it.installed ? h("span", { className: "dshm-badge" }, "\u5DF2\u5B89\u88C5") : null
683
+ ),
684
+ h("div", { className: "dshm-desc" }, it.description),
685
+ h("div", { className: "dshm-sub" }, `${it.id} \xB7 ${(it.tags || []).join("\u3001") || it.category}`),
686
+ h(
687
+ "div",
688
+ { className: "dshm-actions" },
689
+ !it.installed ? h("button", { className: "dshm-btn primary sm", disabled: busy, onClick: install }, busy ? h(Spin) : "\u5B89\u88C5") : null
690
+ )
691
+ )
692
+ );
693
+ }
694
+ function SearchToolView(props) {
695
+ useEffect(() => ensureCss(), []);
696
+ const payload = pickPayload(props);
697
+ const args = parseToolArgs(props);
698
+ const query = String(payload?.query || args.query || "").trim();
699
+ const fromTool = Array.isArray(payload?.items) && payload.items.length ? payload.items : null;
700
+ const running = !!(props?.block && !("kind" in props.block));
701
+ const [items, setItems] = useState(fromTool || []);
702
+ const [err, setErr] = useState("");
703
+ useEffect(() => {
704
+ if (fromTool) setItems(fromTool);
705
+ }, [fromTool]);
706
+ useEffect(() => {
707
+ if (fromTool || running) return;
708
+ let live = true;
709
+ api("search", { query, category: args.category, limit: args.limit }).then((d) => {
710
+ if (live) setItems(d.items || []);
711
+ }).catch((e) => {
712
+ if (live) {
713
+ setItems([]);
714
+ setErr(e && e.message || String(e));
715
+ }
716
+ });
717
+ return () => {
718
+ live = false;
719
+ };
720
+ }, [query, running, !!fromTool]);
721
+ if (running) return null;
722
+ if (!items.length) return err ? h("div", { className: "dshm-err" }, err) : null;
723
+ return h(
724
+ "div",
725
+ { style: { display: "flex", flexDirection: "column", gap: "8px" } },
726
+ items.map(
727
+ (it) => h(ToolCardRow, {
728
+ key: it.id,
729
+ it,
730
+ onInstalled: (next) => setItems((cur) => cur.map((x) => x.id === next.id ? next : x))
731
+ })
732
+ )
733
+ );
734
+ }
735
+ function ListToolView(props) {
736
+ useEffect(() => ensureCss(), []);
737
+ const payload = pickPayload(props);
738
+ const items = Array.isArray(payload?.items) ? payload.items : [];
739
+ if (!items.length) return null;
740
+ return h(
741
+ "div",
742
+ { style: { display: "flex", flexDirection: "column", gap: "4px", fontSize: "12px" } },
743
+ items.map(
744
+ (it) => h(
745
+ "div",
746
+ { key: it.pkg, className: "dshm-row" },
747
+ h("span", { style: { fontWeight: 600 } }, it.name),
748
+ h("span", { className: "dshm-hint" }, `(${it.pkg})`),
749
+ h("span", { className: "dshm-hint" }, `v${it.version || "?"}`),
750
+ it.registryId ? h("span", { className: "dshm-badge" }, "\u5E02\u573A") : h("span", { className: "dshm-badge info" }, "\u975E\u5E02\u573A"),
751
+ it.outdated ? h("span", { className: "dshm-badge warn" }, `\u53EF\u5347\u7EA7${it.latestVersion ? ` \u2192 v${it.latestVersion}` : ""}`) : null
752
+ )
753
+ )
754
+ );
755
+ }
756
+ var inject = ["slots"];
757
+ function apply(ctx) {
758
+ const slots = ctx.slots;
759
+ if (!slots) return;
760
+ ctx.effect(() => ensureCss(), "dshm-style");
761
+ slots.inject(
762
+ "sidebar.footer.action",
763
+ () => slots.register(
764
+ { name: "sidebar.footer.action", id: "dshm-market", key: "dshm-market", order: 9, label: () => "DSH \u5E02\u573A" },
765
+ function DshmMarketEntry(actionProps) {
766
+ return h(MarketEntry, actionProps);
767
+ }
768
+ )
769
+ );
770
+ slots.inject(
771
+ "tool.call.toolview",
772
+ () => registerSlot(slots, { name: "tool.call.toolview", key: "dshm_search" }, SearchToolView)
773
+ );
774
+ slots.inject(
775
+ "tool.call.toolview",
776
+ () => registerSlot(slots, { name: "tool.call.toolview", key: "dshm_list" }, ListToolView)
777
+ );
778
+ slots.inject(
779
+ "tool.call.toolview",
780
+ () => registerSlot(slots, { name: "tool.call.toolview", key: "dshm_outdated" }, ListToolView)
781
+ );
782
+ }
783
+ return { inject, apply };
784
+ },
785
+ });
786
+