dsh-plugin-scout 0.0.0-stage → 0.1.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/lib/client.js ADDED
@@ -0,0 +1,1024 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "dsh-plugin-scout",
3
+ factory: (require) => {
4
+ const module = { exports: {} };
5
+ const exports = module.exports;
6
+ var __create = Object.create;
7
+ var __defProp = Object.defineProperty;
8
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
9
+ var __getOwnPropNames = Object.getOwnPropertyNames;
10
+ var __getProtoOf = Object.getPrototypeOf;
11
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
12
+ var __export = (target, all) => {
13
+ for (var name in all)
14
+ __defProp(target, name, { get: all[name], enumerable: true });
15
+ };
16
+ var __copyProps = (to, from, except, desc) => {
17
+ if (from && typeof from === "object" || typeof from === "function") {
18
+ for (let key of __getOwnPropNames(from))
19
+ if (!__hasOwnProp.call(to, key) && key !== except)
20
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
21
+ }
22
+ return to;
23
+ };
24
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
25
+ // If the importer is in node compatibility mode or this is not an ESM
26
+ // file that has been converted to a CommonJS file using a Babel-
27
+ // compatible transform (i.e. "__esModule" has not been set), then set
28
+ // "default" to the CommonJS "module.exports" for node compatibility.
29
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
30
+ mod
31
+ ));
32
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
33
+
34
+ // src/client/index.jsx
35
+ var index_exports = {};
36
+ __export(index_exports, {
37
+ PANEL_ID: () => PANEL_ID,
38
+ PKG: () => PKG,
39
+ PanelIcon: () => PanelIcon,
40
+ apply: () => apply,
41
+ createInstalledStore: () => createInstalledStore,
42
+ inject: () => inject,
43
+ pluginManagerOf: () => pluginManagerOf
44
+ });
45
+ module.exports = __toCommonJS(index_exports);
46
+ var import_react3 = __toESM(require("react"), 1);
47
+
48
+ // src/client/Page.jsx
49
+ var import_react2 = __toESM(require("react"), 1);
50
+
51
+ // src/client/api.js
52
+ var PREFIX = "/dsh-plugin-scout";
53
+ async function call(path, init = {}) {
54
+ const res = await fetch(`${PREFIX}${path}`, {
55
+ ...init,
56
+ credentials: "same-origin",
57
+ headers: { "x-dsh-plugin-scout": "1", ...init.body ? { "content-type": "application/json" } : {}, ...init.headers }
58
+ });
59
+ let body;
60
+ try {
61
+ body = await res.json();
62
+ } catch {
63
+ body = void 0;
64
+ }
65
+ if (!body || body.ok === false) {
66
+ const err = new Error(body?.error?.message ?? `HTTP ${res.status}`);
67
+ err.code = body?.error?.code ?? "http";
68
+ err.value = body?.value;
69
+ throw err;
70
+ }
71
+ return body.value;
72
+ }
73
+ var api = {
74
+ status: () => call("/api/status"),
75
+ list: (params) => call("/api/list", { method: "POST", body: JSON.stringify(params) }),
76
+ detail: (key) => call(`/api/detail?key=${encodeURIComponent(key)}`),
77
+ badges: (names) => call("/api/badges", { method: "POST", body: JSON.stringify({ names }) }),
78
+ refresh: (full) => call("/api/refresh", { method: "POST", body: JSON.stringify({ full: !!full }) })
79
+ };
80
+ function createBadgeCache(fetchBadges = api.badges) {
81
+ const cache = /* @__PURE__ */ new Map();
82
+ const listeners = /* @__PURE__ */ new Set();
83
+ let pending = /* @__PURE__ */ new Set();
84
+ let timer;
85
+ const flush = async () => {
86
+ const names = [...pending];
87
+ pending = /* @__PURE__ */ new Set();
88
+ timer = void 0;
89
+ try {
90
+ const got = await fetchBadges(names);
91
+ for (const n of names) cache.set(n, got[n] ?? null);
92
+ } catch {
93
+ for (const n of names) cache.set(n, null);
94
+ }
95
+ for (const l of listeners) l();
96
+ };
97
+ return {
98
+ get(name) {
99
+ if (cache.has(name)) return cache.get(name);
100
+ pending.add(name);
101
+ timer ??= setTimeout(flush, 30);
102
+ return void 0;
103
+ },
104
+ subscribe(l) {
105
+ listeners.add(l);
106
+ return () => listeners.delete(l);
107
+ },
108
+ clear() {
109
+ cache.clear();
110
+ for (const l of listeners) l();
111
+ }
112
+ };
113
+ }
114
+
115
+ // src/client/view.js
116
+ var band = (score) => score >= 70 ? "hi" : score >= 45 ? "mid" : "lo";
117
+ var ICON = { ok: "\u2713", warn: "!", bad: "\u2715", unk: "?" };
118
+ function rowTags(r, labels, runtime, installed) {
119
+ const t = [];
120
+ if (!r.cats.length) t.push(["mute", "\u672A\u5F52\u7C7B"]);
121
+ else r.cats.slice(0, 2).forEach((id, i) => t.push([i ? "cat2" : "cat", labels[id] ?? id]));
122
+ t.push(r.compat === "ok" ? ["ok", `\u517C\u5BB9 ${runtime}`] : r.compat === "incompatible" ? ["warn", "\u4E0D\u517C\u5BB9\u5F53\u524D\u7248\u672C"] : ["mute", "\u672A\u58F0\u660E\u517C\u5BB9"]);
123
+ if (r.hooks.length) t.push(["bad", `\u5B89\u88C5\u811A\u672C \xB7 ${r.hooks.join("/")}`]);
124
+ if (r.chain === "mismatch") t.push(["bad", "\u6765\u6E90\u94FE\u4E0D\u4E00\u81F4"]);
125
+ if (r.chain === "unpublished") t.push(["warn", "\u4EC5 Git"]);
126
+ if (r.provenance) t.push(["ok", "provenance"]);
127
+ if (r.archived) t.push(["bad", "\u5DF2\u5F52\u6863"]);
128
+ t.push(["info", r.kinds.includes("client") ? "\u5BBF\u4E3B + \u754C\u9762" : "\u4EC5\u5BBF\u4E3B"]);
129
+ if (r.copies > 1) t.push(["mute", r.shadowOf ? `\u526F\u672C \xB7 \u4EE3\u8868 ${r.shadowOf}` : `${r.copies - 1} \u4E2A\u540C\u540D\u526F\u672C`]);
130
+ if (installed) t.push(["mute", "\u5DF2\u5B89\u88C5"]);
131
+ return t;
132
+ }
133
+ function ago(iso, now = Date.now()) {
134
+ if (!iso) return "\u4ECE\u672A";
135
+ const s = Math.max(0, Math.round((now - Date.parse(iso)) / 1e3));
136
+ if (s < 90) return "\u521A\u521A";
137
+ if (s < 5400) return `${Math.round(s / 60)} \u5206\u949F\u524D`;
138
+ if (s < 129600) return `${Math.round(s / 3600)} \u5C0F\u65F6\u524D`;
139
+ return `${Math.round(s / 86400)} \u5929\u524D`;
140
+ }
141
+ function progressText(p) {
142
+ if (!p) return "\u51C6\u5907\u4E2D\u2026";
143
+ const parts = [];
144
+ if (p.phase === "github") parts.push(`GitHub ${(p.repos ?? 0).toLocaleString()} / ~${(p.expected ?? 0).toLocaleString()}`);
145
+ else parts.push("GitHub \u5B8C\u6210");
146
+ if (p.npm?.total) parts.push(`npm ${p.npm.done.toLocaleString()} / ${p.npm.total.toLocaleString()}`);
147
+ if (p.published) parts.push(`\u5DF2\u53EF\u6D4F\u89C8 ${p.published.toLocaleString()}`);
148
+ return parts.join(" \xB7 ");
149
+ }
150
+ function progressRatio(p) {
151
+ if (!p) return 0;
152
+ const gh = p.phase === "github" ? p.expected ? Math.min(1, (p.repos ?? 0) / p.expected) : 0.05 : 1;
153
+ const npm = p.npm?.total ? p.npm.done / p.npm.total : p.phase === "github" ? 0 : 1;
154
+ return Math.min(1, gh * 0.7 + npm * 0.3);
155
+ }
156
+ function inspectText(result) {
157
+ if (!result) return null;
158
+ if (result.error) return { tone: "bad", text: `\u8C03\u7528\u5931\u8D25\uFF1A${result.error}` };
159
+ if (result.status === "accepted") {
160
+ const v = [result.name, result.version].filter(Boolean).join("@");
161
+ if (result.bundle === false) return { tone: "bad", text: `\u80FD\u89E3\u6790\u5230 ${v}\uFF0C\u4F46\u5B83\u4E0D\u662F\u7EC4\u5408\u5305\uFF0C\u5B98\u65B9\u63D2\u4EF6\u9875\u4E0D\u4F1A\u63A5\u53D7` };
162
+ return { tone: "ok", text: `\u53EF\u4EE5\u6DFB\u52A0\uFF1A\u89E3\u6790\u5230 ${v || result.host || result.kind}${result.registry ? `\uFF08${result.registry}\uFF09` : ""}` };
163
+ }
164
+ const map = { "already-installed": "\u5DF2\u7ECF\u5B89\u88C5", "not-found": "registry \u4E0A\u627E\u4E0D\u5230", network: "\u7F51\u7EDC\u4E0D\u53EF\u8FBE", "invalid-spec": "\u89C4\u683C\u65E0\u6548", "not-a-package": "\u4E0D\u662F npm \u5305", "not-a-bundle": "\u4E0D\u662F DSH \u7EC4\u5408\u5305", unknown: "\u672A\u77E5\u539F\u56E0" };
165
+ return { tone: result.problem === "already-installed" ? "ok" : "warn", text: `${map[result.problem] ?? result.problem}\uFF1A${result.reason}` };
166
+ }
167
+
168
+ // src/client/Drawer.jsx
169
+ var import_react = __toESM(require("react"), 1);
170
+ var import_jsx_runtime = require("react/jsx-runtime");
171
+ function Checklist({ checks }) {
172
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "scx-chk", children: checks.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "scx-ci", "data-check": c.id, "data-st": c.st, children: [
173
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "scx-ic", "data-st": c.st, children: ICON[c.st] }),
174
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
175
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
176
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "scx-id", children: c.id }),
177
+ c.text
178
+ ] }),
179
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "scx-ev", children: c.ev })
180
+ ] })
181
+ ] }, c.id)) });
182
+ }
183
+ function Drawer({ repoKey, face, labels, runtime, installed, onClose, say }) {
184
+ const [d, setD] = (0, import_react.useState)(null);
185
+ const [err, setErr] = (0, import_react.useState)(null);
186
+ const [inspect, setInspect] = (0, import_react.useState)(null);
187
+ const [armed, setArmed] = (0, import_react.useState)(false);
188
+ const [armInstall, setArmInstall] = (0, import_react.useState)(false);
189
+ const [armBuilds, setArmBuilds] = (0, import_react.useState)(false);
190
+ const ins = face.useInstall?.(d?.name);
191
+ (0, import_react.useEffect)(() => {
192
+ let live = true;
193
+ setD(null);
194
+ setErr(null);
195
+ setInspect(null);
196
+ setArmed(false);
197
+ setArmInstall(false);
198
+ setArmBuilds(false);
199
+ api.detail(repoKey).then((v) => {
200
+ if (live) setD(v);
201
+ }, (e) => {
202
+ if (live) setErr(e.message);
203
+ });
204
+ return () => {
205
+ live = false;
206
+ };
207
+ }, [repoKey]);
208
+ (0, import_react.useEffect)(() => {
209
+ const onKey = (e) => {
210
+ if (e.key === "Escape") onClose();
211
+ };
212
+ document.addEventListener("keydown", onKey);
213
+ return () => document.removeEventListener("keydown", onKey);
214
+ }, [onClose]);
215
+ const runInspect = async () => {
216
+ if (!d || !face.inspect) return;
217
+ setInspect({ pending: true });
218
+ setInspect(await face.inspect(d.spec));
219
+ };
220
+ const copy = async () => {
221
+ if (d.risky && !armed) {
222
+ setArmed(true);
223
+ say("\u5B58\u5728 \u2715 \u7EA7\u6838\u9A8C\u9879\uFF0C\u5148\u770B\u6E05\u5355\uFF1B\u518D\u70B9\u4E00\u6B21\u786E\u8BA4\u590D\u5236");
224
+ return;
225
+ }
226
+ setArmed(false);
227
+ try {
228
+ await navigator.clipboard.writeText(d.spec);
229
+ say(`\u5DF2\u590D\u5236 ${d.spec}`);
230
+ } catch {
231
+ say("\u526A\u8D34\u677F\u4E0D\u53EF\u7528\uFF0C\u8BF7\u624B\u52A8\u9009\u4E2D\u89C4\u683C\u590D\u5236");
232
+ }
233
+ };
234
+ const needsConfirm = d && (d.risky || d.compat === "incompatible");
235
+ const install = async () => {
236
+ if (needsConfirm && !armInstall) {
237
+ setArmInstall(true);
238
+ say(d.risky ? "\u5B58\u5728 \u2715 \u7EA7\u6838\u9A8C\u9879\uFF0C\u5148\u770B\u6E05\u5355\uFF1B\u518D\u70B9\u4E00\u6B21\u786E\u8BA4\u5B89\u88C5" : "\u4E0E\u5F53\u524D DSH \u7248\u672C\u4E0D\u517C\u5BB9\uFF0C\u5B98\u65B9\u5927\u6982\u7387\u4F1A\u62D2\u88C5\uFF1B\u518D\u70B9\u4E00\u6B21\u4ECD\u7136\u5C1D\u8BD5");
239
+ return;
240
+ }
241
+ setArmInstall(false);
242
+ await face.installer.install(d.name, d.spec);
243
+ };
244
+ const allowBuilds = async () => {
245
+ if (!armBuilds) {
246
+ setArmBuilds(true);
247
+ return;
248
+ }
249
+ setArmBuilds(false);
250
+ await face.installer.install(d.name, d.spec, ins.pendingBuilds);
251
+ };
252
+ const working = ins && ["checking", "starting", "installing", "cancelling", "applying", "enabling"].includes(ins.phase);
253
+ const isInstalled = d && (installed.has(d.name) || ins?.phase === "installed" || ins?.phase === "enabled");
254
+ const open = () => {
255
+ const how = face.openPlugins(installed.has(d?.name) ? d.name : void 0);
256
+ say(how === "bundle" ? "\u5DF2\u6253\u5F00\u5B98\u65B9\u63D2\u4EF6\u9875\u4E2D\u7684\u8FD9\u4E2A\u63D2\u4EF6" : how === "panel" ? "\u5DF2\u5207\u5230\u5B98\u65B9\u300C\u63D2\u4EF6\u300D\u9875 \xB7 \u70B9\u300C\u6DFB\u52A0\u63D2\u4EF6\u300D\u7C98\u8D34\u89C4\u683C" : "\u627E\u4E0D\u5230\u5B98\u65B9\u63D2\u4EF6\u9875\u5165\u53E3");
257
+ };
258
+ const it = inspectText(inspect && !inspect.pending ? inspect : null);
259
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
260
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "scx-scrim", onClick: onClose }),
261
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("aside", { className: "scx-sheet scx-root", "aria-label": "\u63D2\u4EF6\u8BE6\u60C5", "data-scout-drawer": "", children: [
262
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "scx-dh", children: [
263
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: d?.name ?? repoKey }),
264
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "scx-x", "aria-label": "\u5173\u95ED", onClick: onClose, children: "\xD7" }),
265
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "scx-pkg", children: [
266
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("a", { href: `https://github.com/${d?.repo ?? repoKey}`, target: "_blank", rel: "noreferrer", children: [
267
+ "github.com/",
268
+ d?.repo ?? repoKey
269
+ ] }),
270
+ " \xB7 ",
271
+ d?.license ?? "\u65E0\u8BB8\u53EF\u8BC1",
272
+ d ? ` \xB7 \u2605 ${d.stars} \xB7 \u63A8\u9001\u4E8E ${ago(d.pushedAt)}` : ""
273
+ ] })
274
+ ] }),
275
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "scx-dbody", children: [
276
+ err ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "scx-note", "data-tone": "bad", children: err }) : null,
277
+ !d && !err ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "scx-empty", children: "\u52A0\u8F7D\u4E2D\u2026" }) : null,
278
+ d ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
279
+ d.description ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { color: "var(--scx-ink2)", margin: "12px 0 0" }, children: d.description }) : null,
280
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "scx-sec", children: [
281
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h3", { children: [
282
+ "\u6838\u9A8C\u6E05\u5355 \xB7 \u53EF\u4FE1\u5EA6 ",
283
+ d.score
284
+ ] }),
285
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Checklist, { checks: d.checks })
286
+ ] }),
287
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "scx-sec", children: [
288
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: "\u8BC4\u5206\u6784\u6210" }),
289
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "scx-parts", children: [
290
+ d.scoreParts.map(([k, n]) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react.default.Fragment, { children: [
291
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: k }),
292
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
293
+ n > 0 ? "+" : "",
294
+ n
295
+ ] })
296
+ ] }, k)),
297
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: "\u53EF\u4FE1\u5EA6" }),
298
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: d.score })
299
+ ] })
300
+ ] }),
301
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "scx-sec", children: [
302
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: "\u7528\u9014\u5206\u7C7B" }),
303
+ d.cats.length ? d.cats.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "scx-catev", children: [
304
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
305
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "scx-tag", "data-tone": i ? "cat2" : "cat", children: labels[c.id] ?? c.id }),
306
+ i ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 11, color: "var(--scx-ink3)" }, children: "\u4E3B\u7C7B" }),
307
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "scx-s", children: [
308
+ "\u5F97\u5206 ",
309
+ c.score
310
+ ] })
311
+ ] }),
312
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { children: c.evidence.map((e) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: e }, e)) })
313
+ ] }, c.id)) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "var(--scx-ink3)" }, children: "\u6CA1\u6709\u8DB3\u591F\u4FE1\u53F7\uFF0C\u5F52\u5165\u300C\u672A\u5F52\u7C7B\u300D\u3002" })
314
+ ] }),
315
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "scx-sec", children: [
316
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: "\u6765\u6E90\u94FE" }),
317
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "scx-chain", children: [
318
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "scx-node", children: [
319
+ "github.com/",
320
+ d.repo
321
+ ] }),
322
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "\u2192" }),
323
+ d.npmVersion ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
324
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "scx-node", children: [
325
+ "npm ",
326
+ d.name,
327
+ "@",
328
+ d.npmVersion
329
+ ] }),
330
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "\u2192" }),
331
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "scx-node", style: { color: d.provenance ? "var(--scx-ok)" : "var(--scx-ink4)" }, children: d.provenance ? "provenance \u2713" : "\u65E0 provenance" })
332
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "scx-node", style: { color: "var(--scx-warn)" }, children: d.chain === "unknown" ? "npm \u672A\u67E5\u8BE2" : "\u672A\u53D1\u5E03 npm" })
333
+ ] }),
334
+ d.copiesList.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "scx-ev", style: { marginTop: 6 }, children: [
335
+ "\u540C\u540D\u4ED3\u5E93 ",
336
+ d.copiesList.length,
337
+ " \u4E2A\uFF1A",
338
+ d.copiesList.slice(0, 5).map((c) => c.repo).join("\u3001"),
339
+ d.copiesList.length > 5 ? " \u2026" : ""
340
+ ] }) : null
341
+ ] }),
342
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "scx-sec", children: [
343
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: "\u4F5C\u7528\u9762" }),
344
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("dl", { className: "scx-kv", children: [
345
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: "\u5F62\u6001" }),
346
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: d.kinds.join(" + ") }),
347
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: "cordis \u63D2\u5165" }),
348
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: d.inserts.length ? d.inserts.map((n) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "scx-mono", children: n }, n)) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "var(--scx-ink3)" }, children: "\u4ED3\u5E93\u65E0 cordis.patch.yml \u6216\u672A\u58F0\u660E" }) }),
349
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: "client.inject" }),
350
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: d.clientInject.length ? d.clientInject.map((m) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "scx-mono", children: m }, m)) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "var(--scx-ink3)" }, children: "\u65E0" }) }),
351
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: "DSH peer" }),
352
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { className: "scx-mono", children: Object.entries(d.compatPeers).map(([k, v]) => `${k}@${v}`).join(", ") || "\u65E0" }),
353
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: "\u8FD0\u884C\u65F6\u4F9D\u8D56" }),
354
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("dd", { children: [
355
+ d.depCount,
356
+ " \u4E2A"
357
+ ] })
358
+ ] })
359
+ ] }),
360
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "scx-sec", children: [
361
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: "\u5B98\u65B9\u9884\u68C0\uFF08pluginManager.inspect\uFF0C\u4E0D\u6267\u884C\u4EE3\u7801\uFF09" }),
362
+ !face.inspect ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "var(--scx-ink3)" }, children: "\u5F53\u524D\u7A97\u53E3\u62FF\u4E0D\u5230\u5B98\u65B9\u63D2\u4EF6\u7BA1\u7406\u670D\u52A1" }) : inspect?.pending ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "var(--scx-ink3)" }, children: "\u67E5\u8BE2\u4E2D\u2026" }) : it ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "scx-note", "data-tone": it.tone === "bad" ? "bad" : void 0, style: { color: it.tone === "ok" ? "var(--scx-ok)" : void 0 }, children: it.text }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "scx-btn", onClick: runInspect, children: [
363
+ "\u9884\u68C0 ",
364
+ d.spec
365
+ ] }),
366
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "scx-ev", style: { marginTop: 6 }, children: "\u300C\u5B89\u88C5\u300D\u8D70\u5B98\u65B9\u5B89\u88C5\u5668\uFF1Apnpm \u8FD0\u884C\u524D\u6309 peer \u89C4\u5219\u5224\u517C\u5BB9\uFF08\u4E0D\u517C\u5BB9\u5373\u62D2\u88C5\uFF09\uFF0C\u4F9D\u8D56\u5E26\u5B89\u88C5\u811A\u672C\u4F1A\u5148\u62E6\u4E0B\u7B49\u4F60\u786E\u8BA4\uFF0C\u5931\u8D25\u81EA\u52A8\u56DE\u6EDA\u3002\u88C5\u597D\u9ED8\u8BA4\u4E0D\u542F\u7528\uFF0C\u786E\u8BA4\u65E0\u8BEF\u518D\u70B9\u300C\u542F\u7528\u300D\u3002" })
367
+ ] })
368
+ ] }) : null
369
+ ] }),
370
+ d && ins ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "scx-inst", "data-phase": ins.phase, "aria-live": "polite", children: [
371
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "scx-inst-t", children: ins.text }),
372
+ working && ins.log ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "scx-ev", children: ins.log }) : null,
373
+ ins.phase === "failed" && ins.pendingBuilds?.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginTop: 6 }, children: [
374
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "scx-ev", children: [
375
+ "\u8FD9\u4E9B\u4F9D\u8D56\u8981\u5728\u5B89\u88C5\u65F6\u4EE5\u4F60\u7684\u7528\u6237\u6743\u9650\u6267\u884C\u811A\u672C\uFF1A",
376
+ ins.pendingBuilds.join("\u3001"),
377
+ "\u3002\u5141\u8BB8\u540E\u5B98\u65B9\u4F1A\u6309\u5305\u540D\u8BB0\u5165\u5F53\u524D profile\u3002"
378
+ ] }),
379
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "scx-btn", "data-tone": "bad", style: { marginTop: 6 }, onClick: allowBuilds, children: armBuilds ? "\u518D\u70B9\u4E00\u6B21\uFF1A\u5141\u8BB8\u8FD9\u4E9B\u811A\u672C\u5E76\u91CD\u8BD5" : "\u5141\u8BB8\u8FD9\u4E9B\u811A\u672C\u5E76\u91CD\u8BD5" })
380
+ ] }) : null,
381
+ ins.logPath && ins.phase === "failed" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "scx-ev", children: [
382
+ "\u65E5\u5FD7\uFF1A",
383
+ ins.logPath
384
+ ] }) : null
385
+ ] }) : null,
386
+ d ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "scx-df", children: [
387
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "scx-spec", title: d.spec, children: d.spec }),
388
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "scx-btn", "data-tone": d.risky ? "bad" : void 0, onClick: copy, children: d.risky ? armed ? "\u518D\u70B9\u4E00\u6B21\u786E\u8BA4\u590D\u5236" : "\u4E86\u89E3\u98CE\u9669\u540E\u590D\u5236" : "\u590D\u5236\u9501\u5B9A\u89C4\u683C" }),
389
+ face.installer && !isInstalled ? working ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "scx-btn", onClick: () => face.installer.cancel(d.name), disabled: ins.phase === "applying" || ins.phase === "cancelling" || ins.phase === "enabling", children: "\u53D6\u6D88\u5B89\u88C5" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "scx-btn", "data-tone": needsConfirm ? "bad" : "pri", onClick: install, children: armInstall ? "\u518D\u70B9\u4E00\u6B21\u786E\u8BA4\u5B89\u88C5" : ins?.phase === "failed" ? "\u91CD\u8BD5\u5B89\u88C5" : "\u5B89\u88C5" }) : null,
390
+ face.installer && ins?.phase === "installed" && !ins.restartRequired ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "scx-btn", "data-tone": "pri", onClick: () => face.installer.enable(d.name), children: "\u542F\u7528" }) : null,
391
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "scx-btn", "data-tone": face.installer && !isInstalled ? void 0 : "pri", onClick: open, children: isInstalled ? "\u5728\u63D2\u4EF6\u9875\u6253\u5F00" : "\u53BB\u63D2\u4EF6\u9875\u6DFB\u52A0" })
392
+ ] }) : null
393
+ ] })
394
+ ] });
395
+ }
396
+
397
+ // src/client/Page.jsx
398
+ var import_jsx_runtime2 = require("react/jsx-runtime");
399
+ var FLAGS = [
400
+ ["nohook", "\u65E0\u5B89\u88C5\u811A\u672C"],
401
+ ["chain", "\u6765\u6E90\u94FE\u543B\u5408"],
402
+ ["prov", "\u6709 provenance"],
403
+ ["norisk", "\u65E0 \u2715 \u9879"],
404
+ ["active", "30 \u5929\u5185\u6D3B\u8DC3"]
405
+ ];
406
+ var KINDS = [["client", "\u5E26\u754C\u9762"], ["hostonly", "\u4EC5\u5BBF\u4E3B"]];
407
+ var COMPAT = [["ok", "\u517C\u5BB9\u5F53\u524D\u7248\u672C"], ["incompatible", "\u4E0D\u517C\u5BB9"], ["none", "\u672A\u58F0\u660E"]];
408
+ var PAGE = 60;
409
+ function ScoutPage({ face }) {
410
+ const [status, setStatus] = (0, import_react2.useState)(null);
411
+ const [error, setError] = (0, import_react2.useState)(null);
412
+ const [q, setQ] = (0, import_react2.useState)("");
413
+ const [cat, setCat] = (0, import_react2.useState)("all");
414
+ const [sort, setSort] = (0, import_react2.useState)("score");
415
+ const [compat, setCompat] = (0, import_react2.useState)([]);
416
+ const [flags, setFlags] = (0, import_react2.useState)([]);
417
+ const [inst, setInst] = (0, import_react2.useState)("all");
418
+ const [limit, setLimit] = (0, import_react2.useState)(PAGE);
419
+ const [data, setData] = (0, import_react2.useState)(null);
420
+ const [sel, setSel] = (0, import_react2.useState)(null);
421
+ const [toast, setToast] = (0, import_react2.useState)(null);
422
+ const installed = face.useInstalled();
423
+ const seq = (0, import_react2.useRef)(0);
424
+ const say = (0, import_react2.useCallback)((text) => {
425
+ setToast(text);
426
+ clearTimeout(say.h);
427
+ say.h = setTimeout(() => setToast(null), 2200);
428
+ }, []);
429
+ const loadStatus = (0, import_react2.useCallback)(async () => {
430
+ try {
431
+ setStatus(await api.status());
432
+ setError(null);
433
+ } catch (e) {
434
+ setError(e.message);
435
+ }
436
+ }, []);
437
+ (0, import_react2.useEffect)(() => {
438
+ void loadStatus();
439
+ }, [loadStatus]);
440
+ (0, import_react2.useEffect)(() => {
441
+ const take = () => {
442
+ const p = face.takePending?.();
443
+ if (p) setSel(p);
444
+ };
445
+ take();
446
+ return face.onPending?.(take);
447
+ }, [face]);
448
+ const busy = !!status && (status.running || !status.loaded);
449
+ (0, import_react2.useEffect)(() => {
450
+ if (!busy) return void 0;
451
+ const h = setInterval(loadStatus, 1500);
452
+ return () => clearInterval(h);
453
+ }, [busy, loadStatus]);
454
+ const installedKey = installed.join("\n");
455
+ (0, import_react2.useEffect)(() => {
456
+ const id = ++seq.current;
457
+ const h = setTimeout(async () => {
458
+ try {
459
+ const v = await api.list({ q, cat, sort, compat, flags, inst, installed, limit });
460
+ if (id === seq.current) {
461
+ setData(v);
462
+ setError(null);
463
+ }
464
+ } catch (e) {
465
+ if (id === seq.current) setError(e.message);
466
+ }
467
+ }, q ? 180 : 0);
468
+ return () => clearTimeout(h);
469
+ }, [q, cat, sort, compat.join(), flags.join(), inst, installedKey, limit, status?.rev]);
470
+ const toggle = (list, set, v) => {
471
+ set(list.includes(v) ? list.filter((x) => x !== v) : [...list, v]);
472
+ setLimit(PAGE);
473
+ };
474
+ const labels = status?.categories ?? {};
475
+ const facets = status?.facets;
476
+ const instSet = (0, import_react2.useMemo)(() => new Set(installed), [installedKey]);
477
+ const refresh = async (full) => {
478
+ try {
479
+ setStatus(await api.refresh(full));
480
+ } catch (e) {
481
+ say(e.message);
482
+ void loadStatus();
483
+ }
484
+ };
485
+ const empty = status && status.loaded && !status.crawledAt && !status.running && !status.repos;
486
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "scx-root scx-page", "data-scout-page": "", children: [
487
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "scx-top", children: [
488
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h1", { children: "\u53D1\u73B0\u63D2\u4EF6" }),
489
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "scx-sub", children: "\u6765\u6E90\uFF1AGitHub topic:dsh-plugin + npm \xB7 \u53EA\u6838\u9A8C\uFF0C\u4E0D\u5B89\u88C5" }),
490
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "scx-search", children: [
491
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { "aria-hidden": "true", children: "\u2315" }),
492
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { "aria-label": "\u641C\u7D22\u63D2\u4EF6", placeholder: "\u540D\u79F0\u3001\u63CF\u8FF0\u3001\u4ED3\u5E93\u3001\u5206\u7C7B", value: q, onChange: (e) => {
493
+ setQ(e.target.value);
494
+ setLimit(PAGE);
495
+ } })
496
+ ] }),
497
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "scx-fresh", children: [
498
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { title: status?.runtimeSource, children: [
499
+ "\u8FD0\u884C\u65F6 ",
500
+ status?.runtime ?? "\u2026"
501
+ ] }),
502
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: status?.running ? progressText(status.progress) : status && !status.loaded ? "\u8BFB\u53D6\u7D22\u5F15\u2026" : `\u66F4\u65B0\u4E8E ${ago(status?.crawledAt)}` }),
503
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: "scx-btn", disabled: !status || status.running, onClick: () => refresh(false), children: status?.running ? "\u5237\u65B0\u4E2D\u2026" : "\u5237\u65B0" })
504
+ ] })
505
+ ] }),
506
+ status?.running ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "scx-progress", style: { margin: "0 16px" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("i", { style: { width: `${Math.round(progressRatio(status.progress) * 100)}%` } }) }) : null,
507
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "scx-body", children: [
508
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "scx-filters", children: [
509
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "scx-fg", children: [
510
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h4", { children: "\u517C\u5BB9\uFF08\u5B98\u65B9 peer \u89C4\u5219\uFF09" }),
511
+ COMPAT.map(([v, l]) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { children: [
512
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "checkbox", checked: compat.includes(v), onChange: () => toggle(compat, setCompat, v) }),
513
+ l,
514
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "scx-n", children: facets?.compat[v] ?? "" })
515
+ ] }, v))
516
+ ] }),
517
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "scx-fg", children: [
518
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h4", { children: "\u98CE\u9669\u4E0E\u6765\u6E90" }),
519
+ FLAGS.map(([v, l]) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { children: [
520
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "checkbox", checked: flags.includes(v), onChange: () => toggle(flags, setFlags, v) }),
521
+ l,
522
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "scx-n", children: facets?.flags[v] ?? "" })
523
+ ] }, v))
524
+ ] }),
525
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "scx-fg", children: [
526
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h4", { children: "\u5F62\u6001" }),
527
+ KINDS.map(([v, l]) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { children: [
528
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "checkbox", checked: flags.includes(v), onChange: () => toggle(flags, setFlags, v) }),
529
+ l,
530
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "scx-n", children: facets?.flags[v] ?? "" })
531
+ ] }, v))
532
+ ] }),
533
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "scx-fg", children: [
534
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h4", { children: "\u672C\u673A" }),
535
+ [["all", "\u5168\u90E8"], ["hide", "\u9690\u85CF\u5DF2\u5B89\u88C5"], ["only", "\u53EA\u770B\u5DF2\u5B89\u88C5"]].map(([v, l]) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { children: [
536
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "radio", name: "scx-inst", checked: inst === v, onChange: () => {
537
+ setInst(v);
538
+ setLimit(PAGE);
539
+ } }),
540
+ l
541
+ ] }, v))
542
+ ] }),
543
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "scx-fg", children: [
544
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h4", { children: "\u540C\u540D\u526F\u672C" }),
545
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { children: [
546
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "checkbox", checked: flags.includes("copies"), onChange: () => toggle(flags, setFlags, "copies") }),
547
+ "\u663E\u793A\u526F\u672C",
548
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "scx-n", children: facets?.copies ?? "" })
549
+ ] })
550
+ ] }),
551
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "scx-fg", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: "scx-btn", disabled: !status || status.running, onClick: () => refresh(true), children: "\u5168\u91CF\u91CD\u5EFA\u7D22\u5F15" }) })
552
+ ] }),
553
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "scx-list", children: [
554
+ error ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "scx-note", "data-tone": "bad", children: error }) : null,
555
+ status?.error ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "scx-note", "data-tone": "bad", children: [
556
+ "\u4E0A\u6B21\u5237\u65B0\u5931\u8D25\uFF1A",
557
+ status.error.message
558
+ ] }) : null,
559
+ empty ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "scx-note", children: [
560
+ "\u8FD8\u6CA1\u6709\u7D22\u5F15\u3002\u6293\u53D6\u8FB9\u8FDB\u884C\u8FB9\u51FA\u7ED3\u679C\u2014\u2014\u6700\u8FD1\u6D3B\u8DC3\u7684\u63D2\u4EF6\u51E0\u79D2\u5185\u5C31\u4F1A\u51FA\u73B0\uFF0C\u5176\u4F59\u5728\u540E\u53F0\u8865\u9F50\uFF0C\u671F\u95F4\u53EF\u6B63\u5E38\u4F7F\u7528 DSH\u3002",
561
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: "scx-btn", style: { marginLeft: 8 }, onClick: () => refresh(true), children: "\u5F00\u59CB\u6293\u53D6" })
562
+ ] }) : null,
563
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "scx-cats", role: "tablist", "aria-label": "\u7528\u9014\u5206\u7C7B", children: ["all", ...Object.keys(labels), "other"].filter((id) => id !== "other" || (data?.cats.other ?? 0) > 0 || cat === "other").map((id) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("button", { type: "button", role: "tab", className: "scx-cat", "aria-selected": cat === id, onClick: () => {
564
+ setCat(id);
565
+ setLimit(PAGE);
566
+ }, children: [
567
+ id === "all" ? "\u5168\u90E8" : id === "other" ? "\u672A\u5F52\u7C7B" : labels[id],
568
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "scx-n", children: data?.cats[id] ?? "" })
569
+ ] }, id)) }),
570
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "scx-lhead", children: [
571
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: data ? `${data.total.toLocaleString()} \u4E2A\u63D2\u4EF6${status?.running ? "\uFF08\u6293\u53D6\u4E2D\uFF0C\u6301\u7EED\u589E\u52A0\uFF09" : ""}` : "\u52A0\u8F7D\u4E2D\u2026" }),
572
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { marginLeft: "auto" }, children: "\u6392\u5E8F" }),
573
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("select", { "aria-label": "\u6392\u5E8F", value: sort, onChange: (e) => setSort(e.target.value), children: [
574
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "score", children: "\u53EF\u4FE1\u5EA6" }),
575
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "stars", children: "star" }),
576
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "recent", children: "\u6700\u8FD1\u63A8\u9001" }),
577
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "name", children: "\u540D\u79F0" })
578
+ ] })
579
+ ] }),
580
+ data && !data.rows.length && !empty ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "scx-empty", children: "\u6CA1\u6709\u7B26\u5408\u6761\u4EF6\u7684\u63D2\u4EF6\u3002\u653E\u5BBD\u5DE6\u4FA7\u7B5B\u9009\uFF0C\u6216\u6362\u4E2A\u5173\u952E\u8BCD\u3002" }) : null,
581
+ data?.rows.map((r) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("button", { type: "button", className: "scx-item", "aria-current": sel === r.repo, "data-repo": r.repo, onClick: () => setSel(r.repo), children: [
582
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "scx-score", "data-band": band(r.score), title: r.scoreParts.map(([k, n]) => `${k} ${n > 0 ? "+" : ""}${n}`).join("\n"), children: r.score }),
583
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { minWidth: 0 }, children: [
584
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "scx-rt", children: [
585
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("b", { children: r.name }),
586
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "scx-ver", children: r.npmVersion ?? r.repoVersion ?? "" })
587
+ ] }),
588
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "scx-rd", children: r.description || "\uFF08\u65E0\u63CF\u8FF0\uFF09" }),
589
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "scx-tags", children: rowTags(r, labels, status?.runtime, instSet.has(r.name)).map(([tone, text], i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "scx-tag", "data-tone": tone, children: text }, i)) })
590
+ ] }),
591
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "scx-meta", children: [
592
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
593
+ "\u2605 ",
594
+ r.stars.toLocaleString()
595
+ ] }),
596
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { children: r.repo.split("/")[0] }),
597
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { children: ago(r.pushedAt) })
598
+ ] })
599
+ ] }, r.repo)),
600
+ data && data.rows.length < data.total ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "scx-more", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("button", { type: "button", className: "scx-btn", onClick: () => setLimit(limit + PAGE), children: [
601
+ "\u518D\u663E\u793A ",
602
+ Math.min(PAGE, data.total - data.rows.length),
603
+ " \u4E2A"
604
+ ] }) }) : null
605
+ ] })
606
+ ] }),
607
+ sel ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Drawer, { repoKey: sel, face, labels, runtime: status?.runtime, installed: instSet, onClose: () => setSel(null), say }) : null,
608
+ toast ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "scx-toast", role: "status", children: toast }) : null
609
+ ] });
610
+ }
611
+
612
+ // src/client/installer.js
613
+ var PHASE_TEXT = {
614
+ checking: "\u5B98\u65B9\u9884\u68C0\u4E2D\u2026",
615
+ starting: "\u6B63\u5728\u63D0\u4EA4\u7ED9\u5B98\u65B9\u5B89\u88C5\u5668\u2026",
616
+ installing: "\u5B98\u65B9\u5B89\u88C5\u5668\u8FD0\u884C pnpm \u4E2D\u2026",
617
+ cancelling: "\u6B63\u5728\u53D6\u6D88\u2026",
618
+ applying: "\u6B63\u5728\u5199\u5165 profile\uFF08\u6B64\u9636\u6BB5\u4E0D\u53EF\u53D6\u6D88\uFF09\u2026",
619
+ enabling: "\u6B63\u5728\u542F\u7528\u2026"
620
+ };
621
+ var CODE_TEXT = {
622
+ "management-required": "\u5F53\u524D profile \u4E0D\u53D7\u63D2\u4EF6\u7BA1\u7406\u5668\u7BA1\u7406",
623
+ unaddressable: "\u65E0\u6CD5\u5B9A\u4F4D\u8FD9\u4E2A\u63D2\u4EF6",
624
+ "unknown-plugin": "\u5B98\u65B9\u4E0D\u8BA4\u8BC6\u8FD9\u4E2A\u63D2\u4EF6",
625
+ "invalid-spec": "\u89C4\u683C\u65E0\u6548",
626
+ "ambiguous-install": "\u89C4\u683C\u5BF9\u5E94\u591A\u4E2A\u5305\uFF0C\u65E0\u6CD5\u786E\u5B9A\u88C5\u54EA\u4E2A",
627
+ "not-bundle": "\u8FD9\u4E2A\u5305\u6CA1\u6709\u58F0\u660E DSH \u7EC4\u5408\u5305\uFF0C\u5B98\u65B9\u4E0D\u63A5\u53D7",
628
+ "not-removable": "\u4E0D\u53EF\u79FB\u9664",
629
+ "stop-profile": "\u9700\u8981\u5148\u505C\u6B62\u5F53\u524D profile",
630
+ "bundle-in-use": "\u7EC4\u5408\u5305\u6B63\u5728\u4F7F\u7528\u4E2D",
631
+ "stale-approval": "\u811A\u672C\u6279\u51C6\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u786E\u8BA4",
632
+ "incompatible-version": "\u4E0E\u5F53\u524D DSH \u7248\u672C\u4E0D\u517C\u5BB9",
633
+ "operation-error": "\u5B89\u88C5\u5668\u5185\u90E8\u9519\u8BEF"
634
+ };
635
+ var KIND_TEXT = {
636
+ network: "\u7F51\u7EDC\u4E0D\u53EF\u8FBE",
637
+ timeout: "\u5B89\u88C5\u8D85\u65F6",
638
+ integrity: "\u5305\u6821\u9A8C\u5931\u8D25",
639
+ "pnpm-missing": "\u627E\u4E0D\u5230 pnpm",
640
+ "not-found": "registry \u4E0A\u627E\u4E0D\u5230",
641
+ "no-matching-version": "\u6CA1\u6709\u5339\u914D\u7684\u7248\u672C",
642
+ "disk-full": "\u78C1\u76D8\u5DF2\u6EE1",
643
+ permission: "\u6CA1\u6709\u5199\u6743\u9650",
644
+ "build-blocked": "\u4F9D\u8D56\u5E26\u5B89\u88C5\u811A\u672C\uFF0C\u5DF2\u88AB\u62E6\u4E0B"
645
+ };
646
+ var PROBLEM_TEXT = {
647
+ "already-installed": "\u5DF2\u7ECF\u5B89\u88C5",
648
+ "not-found": "registry \u4E0A\u627E\u4E0D\u5230",
649
+ network: "\u7F51\u7EDC\u4E0D\u53EF\u8FBE",
650
+ "invalid-spec": "\u89C4\u683C\u65E0\u6548",
651
+ "not-a-package": "\u4E0D\u662F npm \u5305",
652
+ "not-a-bundle": "\u4E0D\u662F DSH \u7EC4\u5408\u5305",
653
+ unknown: "\u672A\u77E5\u539F\u56E0"
654
+ };
655
+ function failureText(v) {
656
+ const e = v?.error;
657
+ if (e?.code === "incompatible-version") {
658
+ const named = (e.incompatible ?? v.packageResult?.incompatible ?? []).map((p) => `${p.name}@${p.version} \u8981\u6C42 ${Object.entries(p.peers).map(([k, r]) => `${k} ${r}`).join("\u3001")}\uFF0C\u5F53\u524D\u8FD0\u884C\u65F6 ${p.runtimeVersion}`);
659
+ return `${CODE_TEXT["incompatible-version"]}${named.length ? `\uFF1A${named.join("\uFF1B")}` : ""}\u3002\u5982\u786E\u9700\u4F7F\u7528\uFF0C\u53EF\u5728\u5B98\u65B9\u63D2\u4EF6\u9875\u6309\u7248\u672C\u8C41\u514D\u3002`;
660
+ }
661
+ if (v?.pendingBuilds?.length) return `${KIND_TEXT["build-blocked"]}\uFF1A${v.pendingBuilds.join("\u3001")}`;
662
+ if (v?.failedAt === "spec-host") return "\u8FDE\u4E0D\u4E0A\u6E90\u7801\u4E3B\u673A\uFF08GitHub\uFF09\uFF0C\u6362\u7F51\u7EDC\u6216\u7A0D\u540E\u91CD\u8BD5";
663
+ const kind = v?.packageResult?.kind;
664
+ const head = e?.code && CODE_TEXT[e.code] || kind && KIND_TEXT[kind] || "\u5B89\u88C5\u5931\u8D25";
665
+ return e?.diagnostic ? `${head}\uFF1A${e.diagnostic.slice(0, 300)}` : head;
666
+ }
667
+ var unwrap = (res) => res && typeof res === "object" && "ok" in res ? res : { ok: true, value: res };
668
+ var uuid = () => globalThis.crypto?.randomUUID?.() ?? `scout-${Date.now()}-${Math.random().toString(36).slice(2)}`;
669
+ function createInstaller(getPm, { onInstalled } = {}) {
670
+ let states = {};
671
+ let active;
672
+ const listeners = /* @__PURE__ */ new Set();
673
+ const set = (name, patch) => {
674
+ states = { ...states, [name]: patch === null ? void 0 : { ...states[name], ...patch } };
675
+ for (const l of listeners) l();
676
+ };
677
+ const byRequest = (id) => active && active.requestId === id ? active.name : void 0;
678
+ async function settle(name, requestId, res) {
679
+ let r = unwrap(res);
680
+ if (!r.ok) {
681
+ try {
682
+ r = unwrap(await getPm()?.pm.waitForInstall?.(requestId));
683
+ } catch {
684
+ r = { ok: false };
685
+ }
686
+ if (!r.ok || r.value == null) {
687
+ set(name, { phase: "unconfirmed", text: "\u6CA1\u62FF\u5230\u5B89\u88C5\u7ED3\u679C\uFF1B\u8BF7\u5230\u5B98\u65B9\u63D2\u4EF6\u9875\u786E\u8BA4\u662F\u5426\u5DF2\u88C5\u4E0A\uFF0C\u518D\u51B3\u5B9A\u662F\u5426\u91CD\u8BD5" });
688
+ return;
689
+ }
690
+ }
691
+ const v = r.value;
692
+ if (v.application === "cancelled") set(name, { phase: "cancelled", text: "\u5DF2\u53D6\u6D88\uFF1B\u5B98\u65B9\u5DF2\u6062\u590D package.json \u4E0E pnpm-lock.yaml" });
693
+ else if (v.application === "failed") set(name, { phase: "failed", text: failureText(v), code: v.error?.code, pendingBuilds: v.pendingBuilds ?? [], logPath: v.packageResult?.logPath });
694
+ else {
695
+ set(name, { phase: "installed", bundle: v.bundle ?? name, restartRequired: v.application === "restart-required", text: v.application === "restart-required" ? "\u5DF2\u5B89\u88C5\uFF08\u9700\u91CD\u542F DSH \u751F\u6548\uFF09" : "\u5DF2\u5B89\u88C5\uFF0C\u5C1A\u672A\u542F\u7528" });
696
+ onInstalled?.();
697
+ }
698
+ }
699
+ return {
700
+ get: () => states,
701
+ subscribe(l) {
702
+ listeners.add(l);
703
+ return () => listeners.delete(l);
704
+ },
705
+ busy: () => !!active,
706
+ /** 官方 install-state / install-log 事件:只认自己发起的 requestId。 */
707
+ onState(p) {
708
+ const n = byRequest(p?.requestId);
709
+ if (n && PHASE_TEXT[p.phase]) set(n, { phase: p.phase, text: PHASE_TEXT[p.phase] });
710
+ },
711
+ onLog(c) {
712
+ const n = byRequest(c?.requestId);
713
+ if (!n || typeof c.text !== "string") return;
714
+ const line = c.text.split("\n").map((s) => s.trim()).filter(Boolean).at(-1);
715
+ if (line) set(n, { log: line.slice(0, 200) });
716
+ },
717
+ /**
718
+ * 先官方 inspect,再 installBundle(enabled:false)。approvedBuilds 只在用户看过名单、明确点了允许后才传。
719
+ * @returns {Promise<void>}
720
+ */
721
+ async install(name, spec, approvedBuilds) {
722
+ const got = getPm();
723
+ if (!got || typeof got.pm.installBundle !== "function") {
724
+ set(name, { phase: "failed", text: "\u5F53\u524D\u7A97\u53E3\u62FF\u4E0D\u5230\u5B98\u65B9\u63D2\u4EF6\u7BA1\u7406\u670D\u52A1" });
725
+ return;
726
+ }
727
+ if (active) {
728
+ set(name, { phase: "failed", text: `\u6B63\u5728\u5B89\u88C5 ${active.name}\uFF0C\u8BF7\u7B49\u5B83\u7ED3\u675F` });
729
+ return;
730
+ }
731
+ const requestId = uuid();
732
+ active = { name, requestId };
733
+ set(name, { phase: "checking", text: PHASE_TEXT.checking, log: void 0, pendingBuilds: [], code: void 0, logPath: void 0, requestId });
734
+ try {
735
+ const insp = unwrap(await got.pm.inspect(spec, {}));
736
+ if (!insp.ok) {
737
+ set(name, { phase: "failed", text: `\u5B98\u65B9\u9884\u68C0\u5931\u8D25\uFF1A${insp.error?.message ?? "unknown"}` });
738
+ return;
739
+ }
740
+ const iv = insp.value ?? {};
741
+ if (iv.status !== "accepted") {
742
+ if (iv.problem === "already-installed") {
743
+ set(name, { phase: "installed", text: "\u5DF2\u7ECF\u5B89\u88C5" });
744
+ onInstalled?.();
745
+ return;
746
+ }
747
+ set(name, { phase: "failed", text: `\u5B98\u65B9\u9884\u68C0\u62D2\u7EDD\uFF1A${PROBLEM_TEXT[iv.problem] ?? iv.problem}${iv.reason ? `\uFF08${iv.reason}\uFF09` : ""}` });
748
+ return;
749
+ }
750
+ if (iv.bundle === false) {
751
+ set(name, { phase: "failed", text: "\u80FD\u89E3\u6790\u5230\u5305\uFF0C\u4F46\u5B83\u4E0D\u662F DSH \u7EC4\u5408\u5305\uFF0C\u5B98\u65B9\u4E0D\u63A5\u53D7" });
752
+ return;
753
+ }
754
+ set(name, { phase: "starting", text: PHASE_TEXT.starting });
755
+ const res = await got.pm.installBundle(spec, { enabled: false, requestId, ...iv.registry ? { registry: iv.registry } : {}, ...approvedBuilds?.length ? { approvedBuilds: [...approvedBuilds] } : {} });
756
+ await settle(name, requestId, res);
757
+ } catch (e) {
758
+ set(name, { phase: "failed", text: e?.message ?? String(e) });
759
+ } finally {
760
+ if (active?.requestId === requestId) active = void 0;
761
+ }
762
+ },
763
+ async cancel(name) {
764
+ const s = states[name];
765
+ if (!active || active.name !== name || !s?.requestId) return;
766
+ try {
767
+ const r = unwrap(await getPm()?.pm.cancelInstall?.(s.requestId));
768
+ if (r.ok && r.value?.status === "too-late") set(name, { text: "\u5DF2\u8FDB\u5165\u5199\u5165\u9636\u6BB5\uFF0C\u4E0D\u80FD\u53D6\u6D88\uFF1B\u7B49\u5B83\u5B8C\u6210" });
769
+ } catch {
770
+ }
771
+ },
772
+ async enable(name) {
773
+ const got = getPm();
774
+ if (!got || typeof got.pm.setBundleEnabled !== "function") {
775
+ set(name, { phase: "failed", text: "\u5F53\u524D\u7A97\u53E3\u62FF\u4E0D\u5230\u5B98\u65B9\u63D2\u4EF6\u7BA1\u7406\u670D\u52A1" });
776
+ return;
777
+ }
778
+ set(name, { phase: "enabling", text: PHASE_TEXT.enabling });
779
+ try {
780
+ const r = unwrap(await got.pm.setBundleEnabled(states[name]?.bundle ?? name, true));
781
+ if (!r.ok) {
782
+ set(name, { phase: "installed", text: `\u542F\u7528\u5931\u8D25\uFF1A${r.error?.message ?? "unknown"}` });
783
+ return;
784
+ }
785
+ const v = r.value;
786
+ if (v.application === "failed") set(name, { phase: "installed", text: `\u542F\u7528\u5931\u8D25\uFF1A${failureText(v)}\uFF1B\u63D2\u4EF6\u4ECD\u5DF2\u5B89\u88C5\uFF0C\u53EF\u5728\u5B98\u65B9\u63D2\u4EF6\u9875\u5904\u7406\u6216\u5378\u8F7D` });
787
+ else set(name, { phase: "enabled", text: v.application === "restart-required" ? "\u5DF2\u542F\u7528\uFF0C\u91CD\u542F DSH \u540E\u751F\u6548" : "\u5DF2\u542F\u7528" });
788
+ onInstalled?.();
789
+ } catch (e) {
790
+ set(name, { phase: "installed", text: `\u542F\u7528\u5931\u8D25\uFF1A${e?.message ?? e}` });
791
+ }
792
+ },
793
+ reset(name) {
794
+ if (!active || active.name !== name) set(name, null);
795
+ }
796
+ };
797
+ }
798
+
799
+ // src/client/style.css
800
+ var style_default = '/* DSH Plugin Scout. \u53EA\u7528\u5BBF\u4E3B --dsw-alias-* token\uFF1B\u6D6E\u5C42\u8868\u9762\u7528\u5B57\u9762\u8272\uFF08\u5BBF\u4E3B bg \u522B\u540D\u53EF\u80FD\u662F\u73BB\u7483\u534A\u900F\u660E\uFF09\u3002\n \u7C7B\u540D\u7EDF\u4E00 scx- \u524D\u7F00\uFF0C\u5E76\u907F\u5F00\u5BBF\u4E3B\u76AE\u80A4\u7684\u5B50\u4E32\u89C4\u5219\uFF1Acard bubble block pill banner button panel \u7B49\u3002 */\n.scx-root {\n --scx-ink1: var(--dsw-alias-label-primary, #16181d);\n --scx-ink2: var(--dsw-alias-label-secondary, #4a4f59);\n --scx-ink3: var(--dsw-alias-label-tertiary, #7a808b);\n --scx-ink4: var(--dsw-alias-label-caption, #a3a8b1);\n --scx-hair: var(--dsw-alias-border-l2, #e6e8ec);\n --scx-hair2: var(--dsw-alias-border-l3, #d6d9df);\n --scx-hover: var(--dsw-alias-interactive-bg-hover, rgba(15, 17, 21, 0.05));\n --scx-ok: var(--dsw-alias-state-success-primary, #1f9d55);\n --scx-warn: var(--dsw-alias-state-warn-primary, #c27c0e);\n --scx-bad: var(--dsw-alias-state-error-primary, #d14343);\n --scx-info: #3563e9;\n --scx-cat: #6a3fc8;\n --scx-tile: rgba(255, 255, 255, 0.72);\n --scx-sheet: #ffffff;\n --scx-shadow: 0 1px 2px rgba(15, 17, 21, 0.06);\n --scx-mono: var(--dsw-font-mono, ui-monospace, Menlo, monospace);\n font: 13px/1.5 var(--dsw-font-family, -apple-system, BlinkMacSystemFont, "PingFang SC", sans-serif);\n color: var(--scx-ink1);\n}\nbody[data-ds-dark-theme] .scx-root {\n --scx-info: #7d9bff;\n --scx-cat: #b79cff;\n --scx-tile: rgba(255, 255, 255, 0.05);\n --scx-sheet: #111318;\n --scx-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);\n}\n.scx-root *, .scx-root *::before, .scx-root *::after { box-sizing: border-box; }\n\n/* \u2500\u2500 page \u2500\u2500 */\n.scx-page { height: 100%; display: flex; flex-direction: column; min-height: 0; overflow: hidden; }\n.scx-top { display: flex; align-items: center; gap: 12px; margin: 12px 16px 0; padding: 0 0 12px; border-bottom: 1px solid var(--scx-hair); flex-wrap: wrap; }\n.scx-top h1 { margin: 0; font-size: 15px; font-weight: 600; }\n.scx-sub { color: var(--scx-ink3); font-size: 12px; }\n.scx-search { flex: 1; min-width: 200px; max-width: 420px; display: flex; align-items: center; gap: 6px; border: 1px solid var(--scx-hair2); border-radius: 8px; padding: 4px 10px; background: var(--scx-tile); }\n.scx-search input { border: 0; outline: 0; flex: 1; font: inherit; background: transparent; color: inherit; min-width: 0; }\n.scx-fresh { margin-left: auto; display: flex; align-items: center; gap: 10px; color: var(--scx-ink3); font-size: 12px; }\n.scx-btn { border: 1px solid var(--scx-hair2); background: var(--scx-tile); border-radius: 7px; padding: 4px 11px; font: inherit; color: var(--scx-ink1); cursor: pointer; white-space: nowrap; }\n.scx-btn:hover:not(:disabled) { background: var(--scx-hover); }\n.scx-btn:disabled { opacity: 0.5; cursor: default; }\n.scx-btn[data-tone="bad"] { color: var(--scx-bad); border-color: var(--scx-bad); }\n.scx-btn[data-tone="pri"] { background: var(--scx-ink1); border-color: var(--scx-ink1); color: var(--scx-sheet); }\n.scx-body { flex: 1; display: grid; grid-template-columns: 220px 1fr; min-height: 0; }\n.scx-filters { border-right: 1px solid var(--scx-hair); padding: 14px 16px; overflow: auto; }\n.scx-fg { margin-bottom: 16px; }\n.scx-fg h4 { margin: 0 0 6px; font-size: 11px; font-weight: 600; color: var(--scx-ink3); letter-spacing: 0.04em; }\n.scx-fg label { display: flex; align-items: center; gap: 7px; padding: 2px 0; color: var(--scx-ink2); cursor: pointer; }\n.scx-fg label .scx-n { margin-left: auto; color: var(--scx-ink4); font-variant-numeric: tabular-nums; }\n.scx-list { overflow: auto; padding: 10px 16px 32px; min-width: 0; }\n.scx-cats { display: flex; gap: 6px; flex-wrap: wrap; padding: 2px 0 10px; border-bottom: 1px solid var(--scx-hair); margin-bottom: 8px; }\n.scx-cat { border: 1px solid var(--scx-hair2); background: var(--scx-tile); border-radius: 999px; padding: 3px 11px; font: inherit; font-size: 12.5px; color: var(--scx-ink2); cursor: pointer; display: inline-flex; gap: 6px; align-items: center; }\n.scx-cat:hover { background: var(--scx-hover); }\n.scx-cat .scx-n { color: var(--scx-ink4); font-variant-numeric: tabular-nums; font-size: 11.5px; }\n.scx-cat[aria-selected="true"] { background: var(--scx-ink1); border-color: var(--scx-ink1); color: var(--scx-sheet); }\n.scx-cat[aria-selected="true"] .scx-n { color: inherit; opacity: 0.7; }\n.scx-lhead { display: flex; align-items: center; gap: 10px; padding: 2px 0 8px; color: var(--scx-ink3); font-size: 12px; }\n.scx-lhead select { font: inherit; border: 1px solid var(--scx-hair2); border-radius: 6px; padding: 1px 6px; background: var(--scx-tile); color: inherit; }\n.scx-item { display: grid; grid-template-columns: 34px 1fr auto; gap: 12px; align-items: start; padding: 11px 14px; border: 1px solid var(--scx-hair); border-radius: 10px; background: var(--scx-tile); margin-bottom: 8px; cursor: pointer; box-shadow: var(--scx-shadow); width: 100%; text-align: left; font: inherit; color: inherit; }\n.scx-item:hover { border-color: var(--scx-hair2); }\n.scx-item[aria-current="true"] { border-color: var(--scx-ink1); }\n.scx-score { width: 34px; height: 34px; border-radius: 8px; display: grid; place-items: center; font-weight: 600; font-size: 12px; font-variant-numeric: tabular-nums; }\n.scx-score[data-band="hi"] { background: color-mix(in srgb, var(--scx-ok) 14%, transparent); color: var(--scx-ok); }\n.scx-score[data-band="mid"] { background: color-mix(in srgb, var(--scx-warn) 14%, transparent); color: var(--scx-warn); }\n.scx-score[data-band="lo"] { background: var(--scx-hover); color: var(--scx-ink3); }\n.scx-rt { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }\n.scx-rt b { font-size: 13.5px; font-weight: 600; }\n.scx-ver { color: var(--scx-ink3); font-size: 12px; font-family: var(--scx-mono); }\n.scx-rd { color: var(--scx-ink2); margin: 2px 0 6px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.scx-tags { display: flex; gap: 5px; flex-wrap: wrap; }\n.scx-tag { font-size: 11px; padding: 1px 7px; border-radius: 5px; white-space: nowrap; background: var(--scx-hover); color: var(--scx-ink3); }\n.scx-tag[data-tone="ok"] { background: color-mix(in srgb, var(--scx-ok) 12%, transparent); color: var(--scx-ok); }\n.scx-tag[data-tone="warn"] { background: color-mix(in srgb, var(--scx-warn) 12%, transparent); color: var(--scx-warn); }\n.scx-tag[data-tone="bad"] { background: color-mix(in srgb, var(--scx-bad) 12%, transparent); color: var(--scx-bad); }\n.scx-tag[data-tone="info"] { background: color-mix(in srgb, var(--scx-info) 12%, transparent); color: var(--scx-info); }\n.scx-tag[data-tone="cat"] { background: color-mix(in srgb, var(--scx-cat) 12%, transparent); color: var(--scx-cat); }\n.scx-tag[data-tone="cat2"] { background: transparent; color: var(--scx-cat); border: 1px solid color-mix(in srgb, var(--scx-cat) 30%, transparent); }\n.scx-meta { text-align: right; color: var(--scx-ink3); font-size: 12px; white-space: nowrap; }\n.scx-empty { padding: 56px 0; text-align: center; color: var(--scx-ink3); }\n.scx-more { display: flex; justify-content: center; padding: 8px 0; }\n.scx-note { border: 1px solid var(--scx-hair); border-radius: 9px; padding: 10px 12px; margin-bottom: 10px; color: var(--scx-ink2); font-size: 12px; }\n.scx-note[data-tone="bad"] { border-color: var(--scx-bad); color: var(--scx-bad); }\n.scx-progress { height: 3px; background: var(--scx-hair); border-radius: 2px; overflow: hidden; margin-top: 6px; }\n.scx-progress > i { display: block; height: 100%; background: var(--scx-info); transition: width 0.3s; }\n\n/* \u2500\u2500 drawer (non-modal side sheet; no role=dialog, the host skin repaints those) \u2500\u2500 */\n.scx-scrim { position: fixed; inset: 0; background: rgba(15, 17, 21, 0.18); z-index: 40; }\n.scx-sheet { position: fixed; top: 0; right: 0; bottom: 0; width: min(540px, 92vw); background: var(--scx-sheet); border-left: 1px solid var(--scx-hair); box-shadow: -12px 0 32px rgba(15, 17, 21, 0.12); display: flex; flex-direction: column; z-index: 41; }\n.scx-dh { padding: 16px 20px 12px; border-bottom: 1px solid var(--scx-hair); display: grid; grid-template-columns: 1fr auto; gap: 2px 12px; }\n.scx-dh h2 { margin: 0; font-size: 16px; font-weight: 600; word-break: break-all; }\n.scx-x { border: 0; background: none; font-size: 18px; color: var(--scx-ink3); cursor: pointer; grid-row: span 2; align-self: start; }\n.scx-pkg { font-family: var(--scx-mono); font-size: 12px; color: var(--scx-ink3); }\n.scx-pkg a { color: inherit; }\n.scx-dbody { flex: 1; overflow: auto; padding: 4px 20px 20px; }\n.scx-sec { margin-top: 16px; }\n.scx-sec h3 { font-size: 12px; font-weight: 600; color: var(--scx-ink3); margin: 0 0 8px; letter-spacing: 0.03em; }\n.scx-chk { border: 1px solid var(--scx-hair); border-radius: 9px; overflow: hidden; }\n.scx-ci { display: grid; grid-template-columns: 20px 1fr; gap: 8px; padding: 8px 12px; border-top: 1px solid var(--scx-hair); }\n.scx-ci:first-child { border-top: 0; }\n.scx-ic { font-weight: 700; text-align: center; }\n.scx-ic[data-st="ok"] { color: var(--scx-ok); }\n.scx-ic[data-st="warn"] { color: var(--scx-warn); }\n.scx-ic[data-st="bad"] { color: var(--scx-bad); }\n.scx-ic[data-st="unk"] { color: var(--scx-ink4); }\n.scx-id { font-family: var(--scx-mono); color: var(--scx-ink4); font-size: 11px; margin-right: 4px; }\n.scx-ev { color: var(--scx-ink3); font-size: 12px; font-family: var(--scx-mono); word-break: break-all; margin-top: 1px; }\n.scx-catev { border: 1px solid var(--scx-hair); border-radius: 9px; padding: 8px 12px; margin-bottom: 6px; }\n.scx-catev > div { display: flex; gap: 8px; align-items: center; }\n.scx-catev .scx-s { margin-left: auto; font-size: 11px; color: var(--scx-ink4); }\n.scx-catev ul { margin: 4px 0 0; padding-left: 16px; color: var(--scx-ink3); font-size: 12px; font-family: var(--scx-mono); }\n.scx-chain { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; font-size: 12px; }\n.scx-node { border: 1px solid var(--scx-hair2); border-radius: 6px; padding: 2px 8px; font-family: var(--scx-mono); }\n.scx-kv { display: grid; grid-template-columns: 130px 1fr; gap: 4px 10px; font-size: 12px; margin: 0; }\n.scx-kv dt { color: var(--scx-ink3); }\n.scx-kv dd { margin: 0; word-break: break-all; }\n.scx-mono { font-family: var(--scx-mono); font-size: 12px; }\n.scx-parts { display: grid; grid-template-columns: 1fr auto; gap: 2px 12px; font-size: 12px; color: var(--scx-ink2); max-width: 280px; }\n.scx-parts b { border-top: 1px solid var(--scx-hair); padding-top: 3px; }\n.scx-df { border-top: 1px solid var(--scx-hair); padding: 12px 20px; display: flex; gap: 8px; align-items: center; }\n.scx-spec { flex: 1; min-width: 0; font-family: var(--scx-mono); font-size: 12px; border: 1px solid var(--scx-hair2); border-radius: 7px; padding: 5px 9px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.scx-inst { border-top: 1px solid var(--scx-hair); padding: 10px 20px; font-size: 12px; color: var(--scx-ink2); }\n.scx-inst[data-phase="failed"], .scx-inst[data-phase="unconfirmed"] { color: var(--scx-bad); }\n.scx-inst[data-phase="installed"], .scx-inst[data-phase="enabled"] { color: var(--scx-ok); }\n.scx-inst-t { font-weight: 500; }\n.scx-toast { position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%); background: var(--scx-ink1); color: var(--scx-sheet); padding: 7px 14px; border-radius: 8px; font-size: 12px; z-index: 60; }\n\n/* \u2500\u2500 official plugin page contributions \u2500\u2500 */\n.scx-badge { font-size: 11px; padding: 1px 7px; border-radius: 5px; white-space: nowrap; margin-left: 6px; }\n.scx-badge[data-band="hi"] { background: color-mix(in srgb, var(--scx-ok) 14%, transparent); color: var(--scx-ok); }\n.scx-badge[data-band="mid"] { background: color-mix(in srgb, var(--scx-warn) 14%, transparent); color: var(--scx-warn); }\n.scx-badge[data-band="lo"], .scx-badge[data-risky="true"] { background: color-mix(in srgb, var(--scx-bad) 12%, transparent); color: var(--scx-bad); }\n.scx-section { margin-top: 16px; border: 1px solid var(--scx-hair); border-radius: 10px; padding: 12px 14px; }\n.scx-section h4 { margin: 0 0 8px; font-size: 13px; font-weight: 600; display: flex; gap: 8px; align-items: center; }\n.scx-section .scx-link { margin-left: auto; font-size: 12px; font-weight: 400; color: var(--scx-info); background: none; border: 0; cursor: pointer; padding: 0; }\n';
801
+
802
+ // src/client/index.jsx
803
+ var import_jsx_runtime3 = require("react/jsx-runtime");
804
+ var PKG = "dsh-plugin-scout";
805
+ var PANEL_ID = "dsh-plugin-scout";
806
+ var NS = "dshPluginScout";
807
+ var OFFICIAL_PANEL = "plugins";
808
+ var inject = ["slots", "locale"];
809
+ var zh = { panel: "\u53D1\u73B0\u63D2\u4EF6", badge: "\u53EF\u4FE1\u5EA6", section: "\u63D2\u4EF6\u53D1\u73B0 \xB7 \u6838\u9A8C" };
810
+ var en = { panel: "Discover", badge: "Trust", section: "Plugin Scout \xB7 checks" };
811
+ function optional(ctx, name) {
812
+ try {
813
+ return ctx.get?.(name);
814
+ } catch {
815
+ return void 0;
816
+ }
817
+ }
818
+ function pluginManagerOf(ctx) {
819
+ try {
820
+ const pm = optional(ctx, "remote.pluginManager");
821
+ if (!pm || typeof pm.listBundles !== "function") return void 0;
822
+ return { pm, remote: optional(ctx, "remote") };
823
+ } catch {
824
+ return void 0;
825
+ }
826
+ }
827
+ function createInstalledStore(ctx) {
828
+ let names = [];
829
+ const listeners = /* @__PURE__ */ new Set();
830
+ const emit = () => {
831
+ for (const l of listeners) l();
832
+ };
833
+ const load = async () => {
834
+ const got = pluginManagerOf(ctx);
835
+ if (!got) return;
836
+ try {
837
+ const res = await got.pm.listBundles();
838
+ const list = res?.ok ? res.value : Array.isArray(res) ? res : [];
839
+ const next = list.filter((b) => b.installed).map((b) => b.name).sort();
840
+ if (next.join("\n") !== names.join("\n")) {
841
+ names = next;
842
+ emit();
843
+ }
844
+ } catch {
845
+ }
846
+ };
847
+ return {
848
+ load,
849
+ get: () => names,
850
+ subscribe: (l) => {
851
+ listeners.add(l);
852
+ if (listeners.size === 1) void load();
853
+ return () => listeners.delete(l);
854
+ },
855
+ watch(extra = {}) {
856
+ const got = pluginManagerOf(ctx);
857
+ const offs = [];
858
+ try {
859
+ const off = got?.remote?.$on?.("plugin-manager/changed", load);
860
+ if (typeof off === "function") offs.push(off);
861
+ } catch {
862
+ }
863
+ for (const [event, fn] of Object.entries(extra)) {
864
+ try {
865
+ const off = got?.remote?.$on?.(event, fn);
866
+ if (typeof off === "function") offs.push(off);
867
+ } catch {
868
+ }
869
+ }
870
+ try {
871
+ const off = ctx.on?.("connection/reset", load);
872
+ if (typeof off === "function") offs.push(off);
873
+ } catch {
874
+ }
875
+ return () => offs.forEach((o) => o());
876
+ }
877
+ };
878
+ }
879
+ function installStyle() {
880
+ if (typeof document === "undefined") return () => {
881
+ };
882
+ const tag = document.createElement("style");
883
+ tag.dataset.plugin = PKG;
884
+ tag.dataset.pluginCss = `${PKG}/style`;
885
+ tag.dataset.scout = PKG;
886
+ tag.textContent = style_default;
887
+ document.head.appendChild(tag);
888
+ return () => tag.remove();
889
+ }
890
+ function PanelIcon({ size = 20 }) {
891
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("g", { stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
892
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "10.5", cy: "10.5", r: "6" }),
893
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "m15 15 5 5" }),
894
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "m8 10.5 1.8 1.8L13.2 9" })
895
+ ] }) });
896
+ }
897
+ var subjectName = (subject) => subject?.kind === "bundle" || subject?.kind === "row" ? subject.pkg?.name : void 0;
898
+ function apply(ctx) {
899
+ ctx.effect(() => ctx.locale.register(NS, { zh, en }), "scout: dictionaries");
900
+ const t = ctx.locale.bind(NS);
901
+ ctx.effect(installStyle, "scout: stylesheet");
902
+ const installed = createInstalledStore(ctx);
903
+ const installer = createInstaller(() => pluginManagerOf(ctx), { onInstalled: () => void installed.load() });
904
+ ctx.effect(() => {
905
+ try {
906
+ return installed.watch({ "plugin-manager/install-state": (p) => installer.onState(p), "plugin-manager/install-log": (c) => installer.onLog(c) });
907
+ } catch {
908
+ return void 0;
909
+ }
910
+ }, "scout: installed watch");
911
+ const badges = createBadgeCache();
912
+ let pending;
913
+ const face = {
914
+ useInstalled: () => (0, import_react3.useSyncExternalStore)(installed.subscribe, installed.get, installed.get),
915
+ /** 一键安装:拿不到官方 installBundle 时为 undefined,界面退回「复制 + 去插件页」。 */
916
+ installer: (() => {
917
+ const got = pluginManagerOf(ctx);
918
+ return got && typeof got.pm.installBundle === "function" ? installer : void 0;
919
+ })(),
920
+ useInstall: (name) => (0, import_react3.useSyncExternalStore)(installer.subscribe, () => installer.get()[name], () => void 0),
921
+ inspect: pluginManagerOf(ctx) ? async (spec) => {
922
+ try {
923
+ const got = pluginManagerOf(ctx);
924
+ if (!got) return { error: "\u5B98\u65B9\u63D2\u4EF6\u7BA1\u7406\u670D\u52A1\u6682\u4E0D\u53EF\u7528" };
925
+ const res = await got.pm.inspect(spec, {});
926
+ if (res?.ok === false) return { error: res.error?.message ?? "failed" };
927
+ return res?.ok ? res.value : res;
928
+ } catch (e) {
929
+ return { error: e.message };
930
+ }
931
+ } : void 0,
932
+ /** 已安装 → 直达官方详情;否则切到官方插件页。返回实际走了哪条路。 */
933
+ openPlugins(name) {
934
+ const nav = optional(ctx, "pluginNavigation");
935
+ if (name && typeof nav?.openBundle === "function") {
936
+ try {
937
+ nav.openBundle(name);
938
+ return "bundle";
939
+ } catch {
940
+ }
941
+ }
942
+ const layout = optional(ctx, "layout");
943
+ if (typeof layout?.selectPanel === "function") {
944
+ try {
945
+ layout.selectPanel(OFFICIAL_PANEL);
946
+ return "panel";
947
+ } catch {
948
+ }
949
+ }
950
+ return "none";
951
+ },
952
+ takePending() {
953
+ const p = pending;
954
+ pending = void 0;
955
+ return p;
956
+ },
957
+ onPending(fn) {
958
+ pendingListeners.add(fn);
959
+ return () => pendingListeners.delete(fn);
960
+ }
961
+ };
962
+ const pendingListeners = /* @__PURE__ */ new Set();
963
+ const focus = (repo) => {
964
+ pending = repo;
965
+ for (const l of pendingListeners) l();
966
+ };
967
+ function Page() {
968
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ScoutPage, { face });
969
+ }
970
+ ctx.effect(() => ctx.slots.inject("main", () => ctx.slots.register({ name: "main", key: PANEL_ID, locale: NS }, Page)), "scout: page");
971
+ ctx.effect(() => ctx.slots.inject("sidebar.panellist", () => ctx.slots.register({
972
+ name: "sidebar.panellist",
973
+ id: PANEL_ID,
974
+ order: 40,
975
+ label: () => t("panel"),
976
+ locale: NS
977
+ }, PanelIcon)), "scout: sidebar entry");
978
+ const useBadge = (name) => {
979
+ (0, import_react3.useSyncExternalStore)(badges.subscribe, () => name ? badges.get(name) : null, () => null);
980
+ return name ? badges.get(name) : null;
981
+ };
982
+ function Badge({ subject }) {
983
+ const b = useBadge(subject?.kind === "bundle" ? subjectName(subject) : void 0);
984
+ if (!b) return null;
985
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "scx-root scx-badge", "data-band": band(b.score), "data-risky": b.risky, title: `dsh-plugin-scout \xB7 ${b.repo}`, children: [
986
+ t("badge"),
987
+ " ",
988
+ b.score,
989
+ b.risky ? " \xB7 \u2715" : ""
990
+ ] });
991
+ }
992
+ function Section({ subject }) {
993
+ const name = subject?.kind === "bundle" ? subjectName(subject) : void 0;
994
+ const b = useBadge(name);
995
+ if (!b) return null;
996
+ const goto = () => {
997
+ focus(b.repo);
998
+ const layout = optional(ctx, "layout");
999
+ try {
1000
+ layout?.selectPanel?.(PANEL_ID);
1001
+ } catch {
1002
+ }
1003
+ };
1004
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { className: "scx-root scx-section", "data-scout-section": "", children: [
1005
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("h4", { children: [
1006
+ t("section"),
1007
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "scx-badge", "data-band": band(b.score), "data-risky": b.risky, children: b.score }),
1008
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "scx-link", onClick: goto, children: "\u5728\u53D1\u73B0\u9875\u67E5\u770B \u2192" })
1009
+ ] }),
1010
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "scx-ev", style: { marginBottom: 8 }, children: [
1011
+ "\u6309\u5305\u540D\u5339\u914D\u7D22\u5F15\u4E2D\u7684\u4EE3\u8868\u4ED3\u5E93 github.com/",
1012
+ b.repo,
1013
+ b.npmVersion ? ` \xB7 npm \u6700\u65B0 ${b.npmVersion}` : ""
1014
+ ] }),
1015
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Checklist, { checks: b.checks })
1016
+ ] });
1017
+ }
1018
+ ctx.effect(() => ctx.slots.inject("plugins.detail.badge", () => ctx.slots.register({ name: "plugins.detail.badge", id: `${PKG}/badge`, order: 50, locale: NS }, Badge)), "scout: official badge");
1019
+ ctx.effect(() => ctx.slots.inject("plugins.detail.section", () => ctx.slots.register({ name: "plugins.detail.section", id: `${PKG}/section`, order: 50, locale: NS }, Section)), "scout: official section");
1020
+ }
1021
+
1022
+ return module.exports;
1023
+ }
1024
+ });