dsh-armory 2.3.0 → 2.3.2
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/README.en.md +21 -15
- package/README.md +24 -14
- package/docs/installation.md +0 -2
- package/lib/client.js +2490 -736
- package/lib/community-meta.js +222 -0
- package/lib/expert-market.js +119 -23
- package/lib/index.js +261 -95
- package/lib/loadout.js +178 -0
- package/lib/mcp-config.js +0 -171
- package/lib/mcp-oauth.js +131 -26
- package/lib/npm-downloads.js +262 -0
- package/lib/suites-registry.js +10 -4
- package/lib/typert.js +13 -10
- package/lib/wb-awesome-market.js +706 -214
- package/lib/wb-install-state.js +10 -4
- package/lib/wb-market.js +164 -41
- package/lib/wb-registry.js +32 -31
- package/lib/whats-new-en.js +27 -0
- package/lib/whats-new.js +33 -0
- package/package.json +2 -2
- package/docs/mcp-market-design.md +0 -617
- package/lib/mcp-catalog-en.js +0 -244
- package/lib/mcp-catalog.js +0 -806
- package/lib/wb-plugin-market.js +0 -291
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
// npm 下载量(只读):插件卡片底栏那个「↓N」的唯一真实来源。
|
|
2
|
+
//
|
|
3
|
+
// 为什么单独一层:
|
|
4
|
+
// - 社区目录(awesome-dsh-plugins 的 slim.json)每条只带 11 个字段,**没有任何下载量**,
|
|
5
|
+
// 也没有插件自身的更新时间。技能卡上的 ↓8.5M 来自 SkillHub 上游,插件这边对应的
|
|
6
|
+
// 真实信号只有 npm 的下载统计 —— 所以去 npm 取,绝不为让卡片"看起来丰富"而造数。
|
|
7
|
+
// - 一页 50 张卡片,全量拉取就是 50 次外网请求;因此采集必须**按可见卡片懒加载**,
|
|
8
|
+
// 缓存与限流收在这一层(口径照 lib/community-meta.js:命中缓存不联网、失败回落旧数据)。
|
|
9
|
+
//
|
|
10
|
+
// 边界:
|
|
11
|
+
// - 只读公开接口 https://api.npmjs.org/downloads/point/last-month/<pkg>,不带任何凭据。
|
|
12
|
+
// - 单次最多 12 个包;进程内并发 2 + 每次请求起手间隔 ≥220ms;单请求 8 秒超时。
|
|
13
|
+
// 这两个数是**实测出来的**(2026-10-24,本机出口):12 个请求并发打过去 429 占一半以上,
|
|
14
|
+
// 而 3 req/s 连发 15 个全绿 —— 所以宁可慢一点,也不要把公开接口打到限流。
|
|
15
|
+
// - 数字是**近 30 天窗口**,不是累计(SkillHub 那个 ↓ 是累计)—— 所以 UI 必须带
|
|
16
|
+
// tooltip 说明区间,不能让它被读成"总下载量"。
|
|
17
|
+
// - 缓存落在 ~/.dsh/armory/npm-downloads.json,staging + rename 原子写;测试可注入
|
|
18
|
+
// rootDir / fetchImpl / paceMs(paceMs 只是把节奏调快,逻辑与生产同一条路)。
|
|
19
|
+
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
20
|
+
import { homedir } from "node:os";
|
|
21
|
+
import { join } from "node:path";
|
|
22
|
+
|
|
23
|
+
export const NPM_DOWNLOADS_VERSION = 1;
|
|
24
|
+
// npm 的下载统计本身按天更新,7 天缓存在"数字够新"和"少打外网"之间取值。
|
|
25
|
+
export const NPM_DOWNLOADS_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
26
|
+
export const NPM_DOWNLOADS_MAX_PACKAGES = 12;
|
|
27
|
+
// 429 之后冷却 60 秒(不是 10 分钟):实测这个限流是**速率型**的,一分钟内就回血;
|
|
28
|
+
// 冷却太长会让整页卡片在用户来回翻看时一直空着。
|
|
29
|
+
export const NPM_DOWNLOADS_COOLDOWN_MS = 60_000;
|
|
30
|
+
const NPM_DOWNLOADS_CONCURRENCY = 2;
|
|
31
|
+
const NPM_DOWNLOADS_PACE_MS = 220;
|
|
32
|
+
const NPM_DOWNLOADS_TIMEOUT_MS = 8_000;
|
|
33
|
+
const NPM_DOWNLOADS_RETRY_MS = 900;
|
|
34
|
+
|
|
35
|
+
const NPM_STATS_API = "https://api.npmjs.org/downloads/point/last-month/";
|
|
36
|
+
const USER_AGENT = "@2026heshao/dsh-armory (+https://github.com/2026heshao/dsh-armory)";
|
|
37
|
+
|
|
38
|
+
/** 存储根目录:显式 rootDir > 环境变量 > 真实 ~/.dsh/armory(与 community-meta 同一解析序)。 */
|
|
39
|
+
function armoryRoot(options = {}) {
|
|
40
|
+
return options.rootDir || process.env.DSH_ARMORY_HOME || join(homedir(), ".dsh", "armory");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function npmDownloadsPath(options = {}) {
|
|
44
|
+
return join(armoryRoot(options), "npm-downloads.json");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* npm 包名合法性:unscoped `foo`、scoped `@scope/foo`,可带 . _ - 数字。
|
|
49
|
+
* 返回规范化后的名字,不合法返回 ""(脏数据绝不发进外网请求)。
|
|
50
|
+
*/
|
|
51
|
+
export function normalizePackageName(raw) {
|
|
52
|
+
const text = String(raw || "").trim();
|
|
53
|
+
if (!text) return "";
|
|
54
|
+
if (/^@[a-z0-9][a-z0-9._-]{0,39}\/[a-z0-9][a-z0-9._~!()'*-]{0,214}$/i.test(text)) return text;
|
|
55
|
+
if (/^[a-z0-9][a-z0-9._~!()'*-]{0,214}$/i.test(text)) return text;
|
|
56
|
+
return "";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** 入参归一:字符串 / 数组 / {npm|package} 对象都收,去重、去空、最多 12 条。 */
|
|
60
|
+
export function normalizePackageList(value) {
|
|
61
|
+
const rows = Array.isArray(value) ? value : [value];
|
|
62
|
+
const seen = new Set();
|
|
63
|
+
const out = [];
|
|
64
|
+
for (const row of rows) {
|
|
65
|
+
const raw = row && typeof row === "object" ? row.npm || row.package || row.name || "" : row;
|
|
66
|
+
const name = normalizePackageName(raw);
|
|
67
|
+
if (!name) continue;
|
|
68
|
+
const key = name.toLowerCase();
|
|
69
|
+
if (seen.has(key)) continue;
|
|
70
|
+
seen.add(key);
|
|
71
|
+
out.push(name);
|
|
72
|
+
if (out.length >= NPM_DOWNLOADS_MAX_PACKAGES) break;
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** npm 统计响应 → 窄投影(不落全量 JSON,缓存小且字段稳定)。 */
|
|
78
|
+
export function projectDownloads(pkg, data) {
|
|
79
|
+
const downloads = typeof data?.downloads === "number" && Number.isFinite(data.downloads) ? data.downloads : null;
|
|
80
|
+
return {
|
|
81
|
+
package: String(data?.package || pkg),
|
|
82
|
+
downloads,
|
|
83
|
+
start: typeof data?.start === "string" ? data.start : "",
|
|
84
|
+
end: typeof data?.end === "string" ? data.end : "",
|
|
85
|
+
period: "last-month",
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function readCache(options = {}) {
|
|
90
|
+
try {
|
|
91
|
+
const raw = JSON.parse(await readFile(npmDownloadsPath(options), "utf8"));
|
|
92
|
+
const packages = raw && typeof raw.packages === "object" && raw.packages !== null && !Array.isArray(raw.packages) ? raw.packages : {};
|
|
93
|
+
return {
|
|
94
|
+
version: raw && typeof raw.version === "number" ? raw.version : NPM_DOWNLOADS_VERSION,
|
|
95
|
+
packages,
|
|
96
|
+
unavailableUntil: typeof raw?.unavailableUntil === "number" ? raw.unavailableUntil : 0,
|
|
97
|
+
};
|
|
98
|
+
} catch {
|
|
99
|
+
return { version: NPM_DOWNLOADS_VERSION, packages: {}, unavailableUntil: 0 };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function dirnameOf(path) {
|
|
104
|
+
const cut = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
|
|
105
|
+
return cut > 0 ? path.slice(0, cut) : path;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function writeCache(cache, options = {}) {
|
|
109
|
+
const path = npmDownloadsPath(options);
|
|
110
|
+
await mkdir(dirnameOf(path), { recursive: true });
|
|
111
|
+
// 唯一临时名 + rename:固定 `.staging` 在并发写(refresh 与懒加载两条路同时回来)时
|
|
112
|
+
// 会互相踩踏出撕坏的文件。rename 覆盖目标,不再需要先 rm(那一步还会留出空窗)。
|
|
113
|
+
const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
114
|
+
try {
|
|
115
|
+
await writeFile(tmp, JSON.stringify(cache, null, 2));
|
|
116
|
+
await rename(tmp, path);
|
|
117
|
+
} catch (error) {
|
|
118
|
+
await rm(tmp, { force: true }).catch(() => {});
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function errorText(error) {
|
|
124
|
+
return String(error?.message || error || "读取失败").slice(0, 200);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** scoped 名要把斜杠转义成 %2F,前导 @ 保留 —— npm 统计接口认这个写法。 */
|
|
128
|
+
function statsUrl(pkg) {
|
|
129
|
+
return `${NPM_STATS_API}${pkg.replace("/", "%2F")}`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
|
|
133
|
+
|
|
134
|
+
/** retry-after 可能是秒数,也可能是 HTTP-date;只认秒数,其它一律按 0(回落到默认退避)。 */
|
|
135
|
+
function retryAfterMs(res) {
|
|
136
|
+
const raw = Number(res?.headers?.get?.("retry-after"));
|
|
137
|
+
return Number.isFinite(raw) && raw > 0 ? Math.min(raw * 1000, 30_000) : 0;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* 全局节流闸门:所有请求(含 429 重试)都排这一个队,保证**起手间隔 ≥paceMs**。
|
|
142
|
+
* 并发数管不住速率——两个 worker 各自连发照样能冲到 9 req/s,实测那就是 429 的成因。
|
|
143
|
+
* 单次调用内共享一个 gater,调用之间不跨批持有(每批重新起算,缓存命中根本不进这里)。
|
|
144
|
+
*/
|
|
145
|
+
function createPacer(paceMs) {
|
|
146
|
+
let next = 0;
|
|
147
|
+
let chain = Promise.resolve();
|
|
148
|
+
return () => {
|
|
149
|
+
const run = () => {
|
|
150
|
+
const now = Date.now();
|
|
151
|
+
const at = Math.max(now, next);
|
|
152
|
+
next = at + paceMs;
|
|
153
|
+
return at > now ? wait(at - now) : undefined;
|
|
154
|
+
};
|
|
155
|
+
chain = chain.then(run, run);
|
|
156
|
+
return chain;
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** 取一个包的下载量;调用方已保证并发与节流,这里只管单次请求、一次退避重试与失败口径。 */
|
|
161
|
+
async function fetchOne(pkg, cache, options, now, fetchImpl, pace) {
|
|
162
|
+
const key = pkg.toLowerCase();
|
|
163
|
+
const entry = cache.packages[key];
|
|
164
|
+
const send = async () => fetchImpl(statsUrl(pkg), {
|
|
165
|
+
headers: { "user-agent": USER_AGENT, accept: "application/json" },
|
|
166
|
+
signal: AbortSignal.timeout(NPM_DOWNLOADS_TIMEOUT_MS),
|
|
167
|
+
});
|
|
168
|
+
try {
|
|
169
|
+
await pace();
|
|
170
|
+
let response = await send();
|
|
171
|
+
// 429 是速率限流、不是「这个包没有」:等一会儿再试一次,很多数字就是这么救回来的。
|
|
172
|
+
if (response.status === 429) {
|
|
173
|
+
const backoff = Math.min(retryAfterMs(response) || NPM_DOWNLOADS_RETRY_MS, 5_000);
|
|
174
|
+
await wait(backoff);
|
|
175
|
+
await pace();
|
|
176
|
+
response = await send();
|
|
177
|
+
}
|
|
178
|
+
if (response.status === 404) {
|
|
179
|
+
// 包下架 / 改名 / 从未被统计:这一条没有下载量,卡片就少这一枚,绝不显示 0 冒充"没人下"。
|
|
180
|
+
cache.packages[key] = { fetchedAt: now, missing: true, data: null };
|
|
181
|
+
return { ok: false, missing: true, package: pkg };
|
|
182
|
+
}
|
|
183
|
+
if (response.status === 429 || response.status === 500 || response.status === 502 || response.status === 503) {
|
|
184
|
+
cache.unavailableUntil = now + NPM_DOWNLOADS_COOLDOWN_MS;
|
|
185
|
+
throw new Error(`npm 统计接口暂时不可用(HTTP ${response.status})`);
|
|
186
|
+
}
|
|
187
|
+
if (!response.ok) throw new Error(`npm 统计接口返回 HTTP ${response.status}`);
|
|
188
|
+
const data = projectDownloads(pkg, await response.json());
|
|
189
|
+
if (data.downloads === null) {
|
|
190
|
+
cache.packages[key] = { fetchedAt: now, missing: true, data: null };
|
|
191
|
+
return { ok: false, missing: true, package: pkg };
|
|
192
|
+
}
|
|
193
|
+
cache.packages[key] = { fetchedAt: now, missing: false, data };
|
|
194
|
+
return { ok: true, cached: false, ...data };
|
|
195
|
+
} catch (error) {
|
|
196
|
+
const message = errorText(error);
|
|
197
|
+
// 有旧值就继续显示旧值(stale),网络抖动绝不能把卡片上的数字打没。
|
|
198
|
+
if (entry?.data) return { ok: true, cached: true, stale: true, ...entry.data, error: message };
|
|
199
|
+
return { ok: false, error: message, package: pkg };
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* 批量读取 npm 包近 30 天下载量。
|
|
205
|
+
*
|
|
206
|
+
* 入参:`{ packages: string[] | {npm}[] | string, refresh?: boolean }`
|
|
207
|
+
* 返回:`{ packages: { "<pkg>": { ok, downloads?, start?, end?, cached?, stale?, missing?, error? } }, fetchedAt }`
|
|
208
|
+
*
|
|
209
|
+
* 单条失败不抛整体错误:UI 要能区分「这个包查不到」和「整个接口挂了」。
|
|
210
|
+
* 失败的包**不写缓存**(下次翻页/重载会再试),确认没有的包才粘住 7 天。
|
|
211
|
+
*/
|
|
212
|
+
export async function wbNpmDownloads(payload = {}, options = {}) {
|
|
213
|
+
const packages = normalizePackageList(payload?.packages ?? payload?.package ?? payload?.npm);
|
|
214
|
+
const now = Date.now();
|
|
215
|
+
if (!packages.length) return { packages: {}, fetchedAt: now };
|
|
216
|
+
const cache = await readCache(options);
|
|
217
|
+
const fetchImpl = typeof options.fetchImpl === "function" ? options.fetchImpl : fetch;
|
|
218
|
+
const pace = createPacer(Number.isFinite(Number(options.paceMs)) ? Math.max(0, Number(options.paceMs)) : NPM_DOWNLOADS_PACE_MS);
|
|
219
|
+
const results = {};
|
|
220
|
+
const pending = [];
|
|
221
|
+
|
|
222
|
+
for (const pkg of packages) {
|
|
223
|
+
const entry = cache.packages[pkg.toLowerCase()];
|
|
224
|
+
const fresh = entry && entry.data && now - Number(entry.fetchedAt || 0) < NPM_DOWNLOADS_TTL_MS;
|
|
225
|
+
if (entry && entry.missing && now - Number(entry.fetchedAt || 0) < NPM_DOWNLOADS_TTL_MS && payload?.refresh !== true) {
|
|
226
|
+
// 7 天内查过且确认没有(下架 / 未统计):不重复打外网。
|
|
227
|
+
results[pkg] = { ok: false, missing: true, cached: true, package: pkg };
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
if (fresh && payload?.refresh !== true) {
|
|
231
|
+
results[pkg] = { ok: true, cached: true, ...entry.data };
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
if (cache.unavailableUntil && now < cache.unavailableUntil) {
|
|
235
|
+
results[pkg] = entry?.data
|
|
236
|
+
? { ok: true, cached: true, stale: true, ...entry.data, error: "npm 统计接口冷却中,显示的是缓存" }
|
|
237
|
+
: { ok: false, error: "npm 统计接口冷却中,稍后再试", package: pkg };
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
pending.push(pkg);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// 并发 2 + 起手节流:并发只是别把连接数堆起来,真正决定 429 的是**速率**。
|
|
244
|
+
const cursor = { i: 0 };
|
|
245
|
+
const workers = Array.from({ length: Math.min(NPM_DOWNLOADS_CONCURRENCY, pending.length) }, async () => {
|
|
246
|
+
for (;;) {
|
|
247
|
+
const index = cursor.i;
|
|
248
|
+
cursor.i += 1;
|
|
249
|
+
if (index >= pending.length) return;
|
|
250
|
+
const pkg = pending[index];
|
|
251
|
+
results[pkg] = await fetchOne(pkg, cache, options, now, fetchImpl, pace);
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
await Promise.all(workers);
|
|
255
|
+
|
|
256
|
+
try {
|
|
257
|
+
await writeCache(cache, options);
|
|
258
|
+
} catch {
|
|
259
|
+
// 缓存写失败不影响本次结果;下次只是多打一次网络。
|
|
260
|
+
}
|
|
261
|
+
return { packages: results, fetchedAt: now };
|
|
262
|
+
}
|
package/lib/suites-registry.js
CHANGED
|
@@ -111,12 +111,18 @@ function dirnameOf(path) {
|
|
|
111
111
|
return cut > 0 ? path.slice(0, cut) : path;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
/**
|
|
114
|
+
/** 唯一临时名 + rename 原子写(与 wb-registry.writeAtomic 同一模式):rename 覆盖已存在
|
|
115
|
+
* 文件(Windows 实测),固定 `.staging` 反而在并发保存时互相消费对方的临时文件。 */
|
|
115
116
|
async function writeAtomic(path, doc) {
|
|
116
117
|
await mkdir(dirnameOf(path), { recursive: true });
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
119
|
+
try {
|
|
120
|
+
await writeFile(tmp, JSON.stringify(doc, null, 2));
|
|
121
|
+
await rename(tmp, path);
|
|
122
|
+
} catch (error) {
|
|
123
|
+
await rm(tmp, { force: true }).catch(() => {});
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
120
126
|
}
|
|
121
127
|
|
|
122
128
|
/** 读取时同样过一遍 normalize:手改坏的条目在读侧就被拉回合法形状。 */
|
package/lib/typert.js
CHANGED
|
@@ -33,7 +33,6 @@ const mv = (method, params, resultSym) => ({
|
|
|
33
33
|
const nameParam = { name: 'name', wire: 'name', source: 'json', codec: codec('String', $name) }
|
|
34
34
|
const specParam = { name: 'spec', wire: 'spec', source: 'json', codec: codec('Spec', $any) }
|
|
35
35
|
const payloadParam = { name: 'payload', wire: 'payload', source: 'json', codec: codec('ImportPayload', $any) }
|
|
36
|
-
const builtinInstallParam = { name: 'payload', wire: 'payload', source: 'json', codec: codec('BuiltinInstallPayload', $any) }
|
|
37
36
|
|
|
38
37
|
export const TYPERT = {
|
|
39
38
|
package: 'dsh-armory',
|
|
@@ -46,8 +45,6 @@ export const TYPERT = {
|
|
|
46
45
|
mv('disable', [nameParam], 'NoteResult'),
|
|
47
46
|
mv('reconnect', [nameParam], 'NoteResult'),
|
|
48
47
|
mv('tools', [payloadParam], 'ToolsResult'),
|
|
49
|
-
mv('builtins', [], 'BuiltinCatalogResult'),
|
|
50
|
-
mv('installBuiltins', [builtinInstallParam], 'BuiltinInstallResult'),
|
|
51
48
|
mv('add', [specParam], 'NoteResult'),
|
|
52
49
|
mv('update', [specParam], 'NoteResult'),
|
|
53
50
|
mv('removeServer', [nameParam], 'NoteResult'),
|
|
@@ -63,7 +60,6 @@ export const TYPERT = {
|
|
|
63
60
|
mv('updateWorkspaceServer', [payloadParam], 'NoteResult'),
|
|
64
61
|
mv('removeWorkspaceServer', [payloadParam], 'NoteResult'),
|
|
65
62
|
mv('setWorkspaceExclude', [payloadParam], 'NoteResult'),
|
|
66
|
-
mv('installWorkspaceBuiltins', [builtinInstallParam], 'BuiltinInstallResult'),
|
|
67
63
|
mv('previewWorkspaceImport', [payloadParam], 'ImportPreview'),
|
|
68
64
|
mv('importWorkspaceJson', [payloadParam], 'ImportResult'),
|
|
69
65
|
mv('revealWorkspaceServer', [payloadParam], 'RevealResult'),
|
|
@@ -92,11 +88,13 @@ export const TYPERT = {
|
|
|
92
88
|
// lib/tool-ledger.js 校验,与 wb* 系列同一宽容度。
|
|
93
89
|
mv('toolTailor', [payloadParam], 'TailorReport'),
|
|
94
90
|
// WorkBuddy 市场直连(连接器 MCP + SkillHub 技能)。payload 全部 $any:数据形状由
|
|
95
|
-
// lib/wb-market.js 校验,契约层与
|
|
91
|
+
// lib/wb-market.js 校验,契约层与 importJson 同一宽容度。
|
|
96
92
|
mv('wbConnectorCatalog', [], 'WbCatalogResult'),
|
|
97
93
|
// 顶栏「市场」chip 常驻计数:只读磁盘缓存,零网络;无缓存返回 { total: null }。
|
|
98
94
|
mv('wbConnectorCachedCount', [], 'WbCachedCountResult'),
|
|
99
95
|
mv('wbConnectorPreview', [payloadParam], 'WbPreviewResult'),
|
|
96
|
+
// 市场连接器卡片头像:payload {connectorId},出 {ok, dataUrl}。卡片进视口才调。
|
|
97
|
+
mv('wbConnectorIcon', [payloadParam], 'WbIconResult'),
|
|
100
98
|
mv('wbConnectorInstall', [payloadParam], 'NoteResult'),
|
|
101
99
|
mv('wbSkillCatalog', [payloadParam], 'WbSkillListResult'),
|
|
102
100
|
mv('wbSkillDetail', [payloadParam], 'WbSkillDetailResult'),
|
|
@@ -106,21 +104,26 @@ export const TYPERT = {
|
|
|
106
104
|
// T03/R4:多市场注册表(读 / 增改)。payload $any,形状由 lib/wb-registry.js 校验。
|
|
107
105
|
mv('wbMarketRegistry', [], 'WbMarketRegistryResult'),
|
|
108
106
|
mv('wbMarketRegistryUpsert', [payloadParam], 'WbMarketRegistryResult'),
|
|
109
|
-
// T03/R5:codebuddy 插件市场(列表 / 详情)。payload $any,形状由 lib/wb-plugin-market.js 校验。
|
|
110
|
-
mv('wbPluginCatalog', [], 'WbPluginCatalogResult'),
|
|
111
|
-
mv('wbPluginDetail', [payloadParam], 'WbPluginDetailResult'),
|
|
112
107
|
// T04/R8:安装状态登记与查询(宿主侧落盘,客户端「已安装」筛选的数据源)。
|
|
113
108
|
mv('wbInstallState', [], 'InstallStateResult'),
|
|
114
109
|
mv('wbInstallStateRecord', [payloadParam], 'NoteResult'),
|
|
115
110
|
mv('wbInstallStateForget', [payloadParam], 'NoteResult'),
|
|
116
|
-
// T05/R9:插件市场更新探测(只做条件请求,不落新包)。
|
|
117
|
-
mv('wbPluginUpdateCheck', [], 'WbUpdateHintResult'),
|
|
118
111
|
// T06/Awesome:DSH 社区插件市场(awesome-dsh-plugins,数据层 lib/wb-awesome-market.js)。
|
|
119
112
|
mv('wbAwesomeCatalog', [payloadParam], 'AwesomeCatalogResult'),
|
|
120
113
|
mv('wbAwesomeDetail', [payloadParam], 'AwesomeDetailResult'),
|
|
121
114
|
// README 预览:点击卡片按需取一次(npm registry / GitHub raw),落盘缓存 7 天。
|
|
122
115
|
mv('wbAwesomeReadme', [payloadParam], 'AwesomeReadmeResult'),
|
|
123
116
|
mv('wbAwesomeUpdateCheck', [], 'WbUpdateHintResult'),
|
|
117
|
+
// 社区元信息:打开详情时按需查 GitHub 仓库(维护者 / 最近提交 / issue / star),
|
|
118
|
+
// 6 小时缓存。数据层 lib/community-meta.js,payload $any({repos, refresh})。
|
|
119
|
+
mv('wbRepoMeta', [payloadParam], 'WbRepoMetaResult'),
|
|
120
|
+
// npm 近 30 天下载量:插件卡片底栏的「↓N」。目录里没有这个字段,只能去 npm 统计接口取,
|
|
121
|
+
// 7 天缓存 + 按可见卡片懒加载。数据层 lib/npm-downloads.js,payload $any({packages, refresh})。
|
|
122
|
+
mv('wbNpmDownloads', [payloadParam], 'WbNpmDownloadsResult'),
|
|
123
|
+
// 装备清单:导出本机全局配置 / 与贴入的清单做差异。payload $any,形状由
|
|
124
|
+
// lib/loadout.js 校验;只读,不安装、不写盘、不接收任何凭据。
|
|
125
|
+
mv('wbLoadoutExport', [payloadParam], 'LoadoutResult'),
|
|
126
|
+
mv('wbLoadoutDiff', [payloadParam], 'LoadoutDiffResult'),
|
|
124
127
|
// WorkBuddy 专家直连(召唤预设 + 捆绑技能)。payload 全部 $any:数据形状由
|
|
125
128
|
// lib/expert-market.js 校验,与 wb* 系列同一宽容度。
|
|
126
129
|
mv('wbExpertCatalog', [], 'WbExpertCatalogResult'),
|