@max-null/dsh-plugin-center 0.1.7 → 0.2.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/README.md +2 -2
- package/client.js +613 -104
- package/dist/engine.d.ts +39 -10
- package/dist/engine.js +174 -4
- package/dist/market.d.ts +14 -0
- package/dist/market.js +73 -0
- package/dist/rpc.js +26 -2
- package/dist/toggle.d.ts +31 -0
- package/dist/toggle.js +160 -0
- package/dist/update.d.ts +14 -2
- package/dist/update.js +56 -9
- package/package.json +5 -6
package/dist/engine.d.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { Service, type Context } from '@deepseek-ai/cordis';
|
|
9
9
|
import { type InstalledPlugin } from './meta.ts';
|
|
10
10
|
import { type MarketPlugin } from './market.ts';
|
|
11
|
-
import { type UpdateDigest } from './update.ts';
|
|
11
|
+
import { type PnpmResult, type UpdateDigest } from './update.ts';
|
|
12
12
|
declare module '@deepseek-ai/cordis' {
|
|
13
13
|
interface Context {
|
|
14
14
|
/** The plugin-center engine (provided by this package's host half). */
|
|
@@ -16,12 +16,26 @@ declare module '@deepseek-ai/cordis' {
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
/** Which market directory the client wants to browse. */
|
|
19
|
-
export type MarketSource = 'all' | 'awesome' | 'oh-my-dsh';
|
|
19
|
+
export type MarketSource = 'all' | 'awesome' | 'oh-my-dsh' | 'dsh-market';
|
|
20
20
|
/** What's New read-mark result, returned by listMarket so the client waterfalls. */
|
|
21
21
|
export interface MarketSnapshot {
|
|
22
22
|
plugins: MarketPlugin[];
|
|
23
23
|
done: boolean;
|
|
24
24
|
}
|
|
25
|
+
/** One AI recommendation (suggest). */
|
|
26
|
+
export interface Suggestion {
|
|
27
|
+
name: string;
|
|
28
|
+
reason: string;
|
|
29
|
+
}
|
|
30
|
+
/** One-shot diagnostics report (diagnostics). */
|
|
31
|
+
export interface DiagnosticsReport {
|
|
32
|
+
dshVersion: string;
|
|
33
|
+
baseUrl: string;
|
|
34
|
+
node: string;
|
|
35
|
+
installed: InstalledPlugin[];
|
|
36
|
+
disabled: Record<string, boolean>;
|
|
37
|
+
pnpmLogTail: string;
|
|
38
|
+
}
|
|
25
39
|
export declare class PluginCenterEngine extends Service {
|
|
26
40
|
static inject: string[];
|
|
27
41
|
private awesomeCache;
|
|
@@ -30,6 +44,11 @@ export declare class PluginCenterEngine extends Service {
|
|
|
30
44
|
private ohMyDshCache;
|
|
31
45
|
private ohMyDshDone;
|
|
32
46
|
private ohMyDshFetching;
|
|
47
|
+
private dshMarketCache;
|
|
48
|
+
private dshMarketDone;
|
|
49
|
+
private dshMarketFetching;
|
|
50
|
+
/** README-extracted screenshot URL per plugin name (lazy, P2). */
|
|
51
|
+
private readonly screenshotCache;
|
|
33
52
|
private installedNamesCache;
|
|
34
53
|
private updatesCache;
|
|
35
54
|
private readonly updatesTtlMs;
|
|
@@ -59,21 +78,17 @@ export declare class PluginCenterEngine extends Service {
|
|
|
59
78
|
private fastNpmVersion;
|
|
60
79
|
/** Start the Oh-My-DSH fetch once (single PLUGINS.md parse). */
|
|
61
80
|
private prefetchOhMyDsh;
|
|
81
|
+
/** Start the dsh-market fetch once (2BingLing/dsh-market, ~3900 plugins, trimmed). */
|
|
82
|
+
private prefetchDshMarket;
|
|
62
83
|
/** Installed plugin names (no file IO) — cached so market polling stays cheap. */
|
|
63
84
|
private installedNames;
|
|
64
85
|
/** Market snapshot for one source: what is cached so far, plus whether done. */
|
|
65
86
|
listMarket(source?: MarketSource): Promise<MarketSnapshot>;
|
|
66
87
|
/** Detect updates for every installed third-party/local plugin, TTL-cached. */
|
|
67
88
|
checkUpdates(sinceIso: string): Promise<UpdateDigest[]>;
|
|
68
|
-
install(spec: string): Promise<
|
|
69
|
-
ok: boolean;
|
|
70
|
-
detail: string;
|
|
71
|
-
}>;
|
|
89
|
+
install(spec: string): Promise<PnpmResult>;
|
|
72
90
|
/** Update one installed plugin to the detected target version (exact — see update.ts). */
|
|
73
|
-
update(name: string, version: string): Promise<
|
|
74
|
-
ok: boolean;
|
|
75
|
-
detail: string;
|
|
76
|
-
}>;
|
|
91
|
+
update(name: string, version: string): Promise<PnpmResult>;
|
|
77
92
|
/** 串行执行一次 pnpm 操作并失效缓存(无论成败都放行链条后续任务)。 */
|
|
78
93
|
private enqueuePnpm;
|
|
79
94
|
/** Temporary diagnostics for the empty-update bug; removed once root-caused. */
|
|
@@ -85,4 +100,18 @@ export declare class PluginCenterEngine extends Service {
|
|
|
85
100
|
source: string;
|
|
86
101
|
}[];
|
|
87
102
|
}>;
|
|
103
|
+
/** Disable/enable one loader entry through the profile patch layer. */
|
|
104
|
+
toggle(id: string, disabled: boolean): Promise<{
|
|
105
|
+
ok: boolean;
|
|
106
|
+
detail: string;
|
|
107
|
+
nowDisabled: boolean | null;
|
|
108
|
+
}>;
|
|
109
|
+
/** One-shot diagnostics: environment, installed surface, patch stance, pnpm log tail. */
|
|
110
|
+
diagnostics(): Promise<DiagnosticsReport>;
|
|
111
|
+
/** Screenshot URL for one dsh-market plugin, lazily extracted from its README. */
|
|
112
|
+
screenshot(name: string): Promise<string | null>;
|
|
113
|
+
/** AI recommendation: keyword-filtered candidates ranked by the model. */
|
|
114
|
+
suggest(query: string): Promise<Suggestion[]>;
|
|
115
|
+
/** Wait for the dsh-market catalog (fetch or failure), with a hard deadline. */
|
|
116
|
+
private waitDshMarket;
|
|
88
117
|
}
|
package/dist/engine.js
CHANGED
|
@@ -9,9 +9,11 @@ import { Service } from '@deepseek-ai/cordis';
|
|
|
9
9
|
import { fileURLToPath } from 'node:url';
|
|
10
10
|
import { dirname, join } from 'node:path';
|
|
11
11
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
12
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
12
13
|
import { buildInstalledPlugin, clearPackageCache, resolvePackage } from "./meta.js";
|
|
13
|
-
import { fetchAwesomePluginsJson, fetchOhMyDshOverrides, fetchOhMyDshPlugins, mapConcurrent, mergePlugins } from "./market.js";
|
|
14
|
+
import { fetchAwesomePluginsJson, fetchDshMarketPlugins, fetchOhMyDshOverrides, fetchOhMyDshPlugins, mapConcurrent, mergePlugins, } from "./market.js";
|
|
14
15
|
import { detectUpdate, installPlugin, updatePlugin } from "./update.js";
|
|
16
|
+
import { readDisabledState, setDisabled } from "./toggle.js";
|
|
15
17
|
/** Runtime mirror of cordis FiberState (a cross-package const enum). */
|
|
16
18
|
const FIBER_PHASE = {
|
|
17
19
|
0: 'pending',
|
|
@@ -23,6 +25,38 @@ const FIBER_PHASE = {
|
|
|
23
25
|
};
|
|
24
26
|
/** Sources that participate in update detection (official/builtin follow DSH itself). */
|
|
25
27
|
const UPDATABLE = new Set(['installed', 'local']);
|
|
28
|
+
/**
|
|
29
|
+
* Extract the first image URL from a plugin's README (P2 screenshots):
|
|
30
|
+
* markdown or HTML image syntax, relative paths resolved against the raw
|
|
31
|
+
* branch root; only GitHub-hosted images are accepted.
|
|
32
|
+
*/
|
|
33
|
+
async function extractFirstImage(repo) {
|
|
34
|
+
for (const branch of ['HEAD', 'master', 'main']) {
|
|
35
|
+
try {
|
|
36
|
+
const res = await fetch(`https://raw.githubusercontent.com/${repo}/${branch}/README.md`, {
|
|
37
|
+
signal: AbortSignal.timeout(12000),
|
|
38
|
+
});
|
|
39
|
+
if (!res.ok)
|
|
40
|
+
continue;
|
|
41
|
+
const text = await res.text();
|
|
42
|
+
const markdown = /!\[[^\]]*\]\(([^)]+)\)/u.exec(text);
|
|
43
|
+
const html = /<img[^>]+src=["']([^"']+)["']/iu.exec(text);
|
|
44
|
+
const raw = markdown?.[1] ?? html?.[1];
|
|
45
|
+
if (raw === undefined || raw === '')
|
|
46
|
+
continue;
|
|
47
|
+
const url = /^https?:\/\//u.test(raw)
|
|
48
|
+
? raw
|
|
49
|
+
: `https://raw.githubusercontent.com/${repo}/${branch}/${raw.replace(/^\.?\//u, '')}`;
|
|
50
|
+
if (/^https:\/\/(raw\.)?githubusercontent\.com\//u.test(url))
|
|
51
|
+
return url;
|
|
52
|
+
if (/^https:\/\/github\.com\//u.test(url)) {
|
|
53
|
+
return url.replace(/^https:\/\/github\.com\/(.+?)\/blob\//u, 'https://raw.githubusercontent.com/$1/');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch { /* try next branch */ }
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
26
60
|
export class PluginCenterEngine extends Service {
|
|
27
61
|
static inject = ['loader'];
|
|
28
62
|
awesomeCache = [];
|
|
@@ -31,6 +65,11 @@ export class PluginCenterEngine extends Service {
|
|
|
31
65
|
ohMyDshCache = [];
|
|
32
66
|
ohMyDshDone = false;
|
|
33
67
|
ohMyDshFetching = false;
|
|
68
|
+
dshMarketCache = [];
|
|
69
|
+
dshMarketDone = false;
|
|
70
|
+
dshMarketFetching = false;
|
|
71
|
+
/** README-extracted screenshot URL per plugin name (lazy, P2). */
|
|
72
|
+
screenshotCache = new Map();
|
|
34
73
|
installedNamesCache = null;
|
|
35
74
|
updatesCache = null;
|
|
36
75
|
updatesTtlMs = 5 * 60_000;
|
|
@@ -51,6 +90,7 @@ export class PluginCenterEngine extends Service {
|
|
|
51
90
|
catch { /* listInstalled is re-run on demand */ }
|
|
52
91
|
this.prefetchAwesome();
|
|
53
92
|
this.prefetchOhMyDsh();
|
|
93
|
+
this.prefetchDshMarket();
|
|
54
94
|
}
|
|
55
95
|
/** The profile directory (cordis.yml anchor) — the resolution and install cwd. */
|
|
56
96
|
get baseUrl() {
|
|
@@ -179,6 +219,20 @@ export class PluginCenterEngine extends Service {
|
|
|
179
219
|
this.ohMyDshFetching = false;
|
|
180
220
|
})();
|
|
181
221
|
}
|
|
222
|
+
/** Start the dsh-market fetch once (2BingLing/dsh-market, ~3900 plugins, trimmed). */
|
|
223
|
+
prefetchDshMarket() {
|
|
224
|
+
if (this.dshMarketFetching || this.dshMarketDone)
|
|
225
|
+
return;
|
|
226
|
+
this.dshMarketFetching = true;
|
|
227
|
+
void (async () => {
|
|
228
|
+
try {
|
|
229
|
+
this.dshMarketCache = mergePlugins([await fetchDshMarketPlugins()]);
|
|
230
|
+
}
|
|
231
|
+
catch { /* empty on failure */ }
|
|
232
|
+
this.dshMarketDone = true;
|
|
233
|
+
this.dshMarketFetching = false;
|
|
234
|
+
})();
|
|
235
|
+
}
|
|
182
236
|
/** Installed plugin names (no file IO) — cached so market polling stays cheap. */
|
|
183
237
|
async installedNames() {
|
|
184
238
|
if (this.installedNamesCache !== null)
|
|
@@ -194,7 +248,7 @@ export class PluginCenterEngine extends Service {
|
|
|
194
248
|
/** Market snapshot for one source: what is cached so far, plus whether done. */
|
|
195
249
|
async listMarket(source = 'all') {
|
|
196
250
|
const installedNames = await this.installedNames();
|
|
197
|
-
const decorate = (plugins) => plugins.map(p => ({ ...p, installed: installedNames.has(p.name) }));
|
|
251
|
+
const decorate = (plugins) => plugins.map(p => ({ ...p, installed: installedNames.has(p.name) || installedNames.has(p.spec) }));
|
|
198
252
|
if (source === 'awesome') {
|
|
199
253
|
this.prefetchAwesome();
|
|
200
254
|
return { plugins: decorate(this.awesomeCache), done: this.awesomeDone };
|
|
@@ -203,11 +257,20 @@ export class PluginCenterEngine extends Service {
|
|
|
203
257
|
this.prefetchOhMyDsh();
|
|
204
258
|
return { plugins: decorate(this.ohMyDshCache), done: this.ohMyDshDone };
|
|
205
259
|
}
|
|
260
|
+
if (source === 'dsh-market') {
|
|
261
|
+
this.prefetchDshMarket();
|
|
262
|
+
// Score-first order (best first) so the big catalog reads usefully.
|
|
263
|
+
return {
|
|
264
|
+
plugins: decorate(this.dshMarketCache).sort((a, b) => (b.score?.total ?? 0) - (a.score?.total ?? 0)),
|
|
265
|
+
done: this.dshMarketDone,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
206
268
|
this.prefetchAwesome();
|
|
207
269
|
this.prefetchOhMyDsh();
|
|
270
|
+
this.prefetchDshMarket();
|
|
208
271
|
return {
|
|
209
|
-
plugins: decorate(mergePlugins([this.awesomeCache, this.ohMyDshCache])),
|
|
210
|
-
done: this.awesomeDone && this.ohMyDshDone,
|
|
272
|
+
plugins: decorate(mergePlugins([this.awesomeCache, this.ohMyDshCache, this.dshMarketCache])),
|
|
273
|
+
done: this.awesomeDone && this.ohMyDshDone && this.dshMarketDone,
|
|
211
274
|
};
|
|
212
275
|
}
|
|
213
276
|
/** Detect updates for every installed third-party/local plugin, TTL-cached. */
|
|
@@ -250,4 +313,111 @@ export class PluginCenterEngine extends Service {
|
|
|
250
313
|
installed: (await this.listInstalled()).map(p => ({ name: p.name, version: p.version, source: p.source })),
|
|
251
314
|
};
|
|
252
315
|
}
|
|
316
|
+
/** Disable/enable one loader entry through the profile patch layer. */
|
|
317
|
+
async toggle(id, disabled) {
|
|
318
|
+
const result = await setDisabled(this.baseUrl, id, disabled);
|
|
319
|
+
this.installedNamesCache = null;
|
|
320
|
+
return result;
|
|
321
|
+
}
|
|
322
|
+
/** One-shot diagnostics: environment, installed surface, patch stance, pnpm log tail. */
|
|
323
|
+
async diagnostics() {
|
|
324
|
+
const [installed, dshVersion] = await Promise.all([this.listInstalled(), this.dshVersion()]);
|
|
325
|
+
let pnpmLogTail = '';
|
|
326
|
+
try {
|
|
327
|
+
const logPath = join(this.baseUrl, 'plugin-center-pnpm.log');
|
|
328
|
+
if (existsSync(logPath)) {
|
|
329
|
+
const text = readFileSync(logPath, 'utf8');
|
|
330
|
+
pnpmLogTail = text.slice(-4000);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
catch { /* best-effort */ }
|
|
334
|
+
return {
|
|
335
|
+
dshVersion,
|
|
336
|
+
baseUrl: this.baseUrl,
|
|
337
|
+
node: process.version,
|
|
338
|
+
installed,
|
|
339
|
+
disabled: Object.fromEntries(readDisabledState(join(this.baseUrl, 'cordis.patch.yml'))),
|
|
340
|
+
pnpmLogTail,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
/** Screenshot URL for one dsh-market plugin, lazily extracted from its README. */
|
|
344
|
+
async screenshot(name) {
|
|
345
|
+
const cached = this.screenshotCache.get(name);
|
|
346
|
+
if (cached !== undefined)
|
|
347
|
+
return cached;
|
|
348
|
+
this.screenshotCache.set(name, null); // placeholder against concurrent dup fetches
|
|
349
|
+
const repo = this.dshMarketCache.find(p => p.name === name || p.spec === name)?.name ?? name;
|
|
350
|
+
const url = await extractFirstImage(repo);
|
|
351
|
+
this.screenshotCache.set(name, url);
|
|
352
|
+
return url;
|
|
353
|
+
}
|
|
354
|
+
/** AI recommendation: keyword-filtered candidates ranked by the model. */
|
|
355
|
+
async suggest(query) {
|
|
356
|
+
const q = query.trim();
|
|
357
|
+
if (q === '')
|
|
358
|
+
return [];
|
|
359
|
+
await this.waitDshMarket();
|
|
360
|
+
const llm = this.ctx.get('llm');
|
|
361
|
+
if (llm?.stream === undefined) {
|
|
362
|
+
throw new Error('llm 服务不可用(当前 profile 未提供 dsh-llm)');
|
|
363
|
+
}
|
|
364
|
+
const tokens = q.toLowerCase().split(/[\s,,、;;]+/u).filter(Boolean);
|
|
365
|
+
const scored = this.dshMarketCache
|
|
366
|
+
.map(p => ({
|
|
367
|
+
p,
|
|
368
|
+
hits: tokens.reduce((n, tok) => n + (p.name.toLowerCase().includes(tok)
|
|
369
|
+
|| p.description.zh.toLowerCase().includes(tok)
|
|
370
|
+
|| p.description.en.toLowerCase().includes(tok)
|
|
371
|
+
|| p.categories.some(c => c.includes(tok)) ? 1 : 0), 0),
|
|
372
|
+
}))
|
|
373
|
+
.sort((a, b) => (b.hits - a.hits) || (b.p.stars ?? 0) - (a.p.stars ?? 0))
|
|
374
|
+
.slice(0, 25);
|
|
375
|
+
if (scored.length === 0)
|
|
376
|
+
return [];
|
|
377
|
+
const list = scored.map(({ p }) => `- ${p.name} | ${p.stars ?? 0}★ | ${p.description.zh.slice(0, 60)}`).join('\n');
|
|
378
|
+
const system = '你是 DeepSeek Harness 插件市场的推荐助手。根据用户需求从候选插件中选择 3-5 个最合适的,只输出一个 JSON 数组(不要 markdown 代码块):[{"name":"插件名","reason":"一句话中文推荐理由"}]';
|
|
379
|
+
const chunks = llm.stream({
|
|
380
|
+
provider: 'deepseek-official',
|
|
381
|
+
model: 'deepseek-v4-flash',
|
|
382
|
+
messages: [{ role: 'user', content: `用户需求:${q}\n\n候选插件列表(名称 | 星标 | 简介):\n${list}` }],
|
|
383
|
+
system,
|
|
384
|
+
maxTokens: 800,
|
|
385
|
+
signal: AbortSignal.timeout(40000),
|
|
386
|
+
});
|
|
387
|
+
let text = '';
|
|
388
|
+
let failed = false;
|
|
389
|
+
for await (const chunk of chunks) {
|
|
390
|
+
if (chunk.type === 'text-delta')
|
|
391
|
+
text += chunk.text ?? '';
|
|
392
|
+
else if (chunk.type === 'finish' && (chunk.reason?.kind === 'error' || chunk.reason?.kind === 'aborted'))
|
|
393
|
+
failed = true;
|
|
394
|
+
}
|
|
395
|
+
if (failed || text.trim() === '')
|
|
396
|
+
throw new Error('模型推荐失败,请重试');
|
|
397
|
+
const start = text.indexOf('[');
|
|
398
|
+
const end = text.lastIndexOf(']');
|
|
399
|
+
if (start < 0 || end <= start)
|
|
400
|
+
throw new Error(`模型输出无法解析:${text.slice(0, 200)}`);
|
|
401
|
+
try {
|
|
402
|
+
const parsed = JSON.parse(text.slice(start, end + 1));
|
|
403
|
+
if (!Array.isArray(parsed))
|
|
404
|
+
throw new Error('bad shape');
|
|
405
|
+
return parsed
|
|
406
|
+
.filter((item) => item !== null && typeof item === 'object'
|
|
407
|
+
&& typeof item.name === 'string'
|
|
408
|
+
&& typeof item.reason === 'string')
|
|
409
|
+
.slice(0, 5);
|
|
410
|
+
}
|
|
411
|
+
catch {
|
|
412
|
+
throw new Error(`模型输出无法解析:${text.slice(0, 200)}`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
/** Wait for the dsh-market catalog (fetch or failure), with a hard deadline. */
|
|
416
|
+
async waitDshMarket() {
|
|
417
|
+
this.prefetchDshMarket();
|
|
418
|
+
const deadline = Date.now() + 65_000;
|
|
419
|
+
while (!this.dshMarketDone && Date.now() < deadline) {
|
|
420
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
421
|
+
}
|
|
422
|
+
}
|
|
253
423
|
}
|
package/dist/market.d.ts
CHANGED
|
@@ -21,6 +21,12 @@ export interface MarketPlugin {
|
|
|
21
21
|
/** Latest published version (npm), null until fetched / for non-npm plugins. */
|
|
22
22
|
version: string | null;
|
|
23
23
|
installed: boolean;
|
|
24
|
+
/** dsh-market five-dimension score (dsh-market source only), else null. */
|
|
25
|
+
score: {
|
|
26
|
+
total: number;
|
|
27
|
+
breakdown: Record<string, number>;
|
|
28
|
+
explanation: string;
|
|
29
|
+
} | null;
|
|
24
30
|
}
|
|
25
31
|
/** A per-source plugin record before merging. */
|
|
26
32
|
interface RawPlugin {
|
|
@@ -34,6 +40,7 @@ interface RawPlugin {
|
|
|
34
40
|
};
|
|
35
41
|
stars: number | null;
|
|
36
42
|
npm: string | null;
|
|
43
|
+
score: MarketPlugin['score'];
|
|
37
44
|
}
|
|
38
45
|
/** Map a bounded set of fetches concurrently, keeping per-fetch failures as null. */
|
|
39
46
|
export declare function mapConcurrent<T>(items: readonly string[], limit: number, fn: (item: string) => Promise<T>): Promise<(T | null)[]>;
|
|
@@ -47,6 +54,13 @@ export declare function fetchOhMyDshOverrides(): Promise<Record<string, {
|
|
|
47
54
|
category?: string;
|
|
48
55
|
note?: string;
|
|
49
56
|
}>>;
|
|
57
|
+
/**
|
|
58
|
+
* Fetch dsh-market's aggregated catalog (2BingLing/dsh-market plugins.json,
|
|
59
|
+
* ~3900 plugins with five-dimension scores and bilingual descriptions) in
|
|
60
|
+
* one request, then trim each entry to the market surface shape. The raw
|
|
61
|
+
* file is ~10 MB, so the trimmed result (~1.5 MB) is what the engine caches.
|
|
62
|
+
*/
|
|
63
|
+
export declare function fetchDshMarketPlugins(): Promise<RawPlugin[]>;
|
|
50
64
|
/** Parse Oh-My-DSH's PLUGINS.md (markdown table, sectioned by category). */
|
|
51
65
|
export declare function fetchOhMyDshPlugins(): Promise<RawPlugin[]>;
|
|
52
66
|
/** Merge raw per-source records by repo name: union categories, keep non-empty desc/stars. */
|
package/dist/market.js
CHANGED
|
@@ -41,6 +41,7 @@ export async function fetchAwesomePluginsJson() {
|
|
|
41
41
|
description: { en: p.description?.en ?? '', zh: p.description?.zh ?? '' },
|
|
42
42
|
stars: typeof p.stars === 'number' ? p.stars : null,
|
|
43
43
|
npm: typeof p.npm === 'string' && p.npm !== '' ? p.npm : null,
|
|
44
|
+
score: null,
|
|
44
45
|
}));
|
|
45
46
|
}
|
|
46
47
|
/** Fetch Oh-My-DSH's curated overrides (min_stars filter + category/note overrides). */
|
|
@@ -58,6 +59,74 @@ export async function fetchOhMyDshOverrides() {
|
|
|
58
59
|
return {};
|
|
59
60
|
}
|
|
60
61
|
}
|
|
62
|
+
/** Category keywords for dsh-market tag → CATEGORIES mapping (prefix match). */
|
|
63
|
+
const CATEGORY_KEYWORDS = [
|
|
64
|
+
['ui', ['ui', 'interface', 'sidebar', 'panel', 'widget', '界面', '面板', '侧栏', '导航']],
|
|
65
|
+
['theme', ['theme', 'skin', '主题', '皮肤', '壁纸']],
|
|
66
|
+
['tools', ['tool', 'terminal', 'bash', '工具', '终端', '命令']],
|
|
67
|
+
['model', ['model', 'llm', 'api', '模型', 'provider']],
|
|
68
|
+
['session', ['session', '会话', 'history', '记忆回']],
|
|
69
|
+
['memory', ['memory', '记忆']],
|
|
70
|
+
['vision', ['vision', 'image', '图片', '视觉', 'screenshot']],
|
|
71
|
+
['skill', ['skill', '技能', 'agent']],
|
|
72
|
+
['workflow', ['workflow', 'workflow', '流程', 'automation', '自动化']],
|
|
73
|
+
['notify', ['notify', '通知', 'toast', 'push']],
|
|
74
|
+
['dev', ['dev', 'git', 'code', '开发', '代码', 'debug', '测试']],
|
|
75
|
+
['fun', ['fun', '趣味', 'pet', '宠物', '游戏']],
|
|
76
|
+
];
|
|
77
|
+
function categorizeTags(tags) {
|
|
78
|
+
const out = new Set();
|
|
79
|
+
for (const tag of tags ?? []) {
|
|
80
|
+
const low = tag.toLowerCase();
|
|
81
|
+
for (const [category, keywords] of CATEGORY_KEYWORDS) {
|
|
82
|
+
if (keywords.some(keyword => low.includes(keyword)))
|
|
83
|
+
out.add(category);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return [...out];
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Fetch dsh-market's aggregated catalog (2BingLing/dsh-market plugins.json,
|
|
90
|
+
* ~3900 plugins with five-dimension scores and bilingual descriptions) in
|
|
91
|
+
* one request, then trim each entry to the market surface shape. The raw
|
|
92
|
+
* file is ~10 MB, so the trimmed result (~1.5 MB) is what the engine caches.
|
|
93
|
+
*/
|
|
94
|
+
export async function fetchDshMarketPlugins() {
|
|
95
|
+
const res = await fetch('https://raw.githubusercontent.com/2BingLing/dsh-market/master/data/plugins.json', {
|
|
96
|
+
headers: UA,
|
|
97
|
+
signal: AbortSignal.timeout(60000),
|
|
98
|
+
});
|
|
99
|
+
if (!res.ok)
|
|
100
|
+
throw new Error(`dsh-market plugins.json: HTTP ${res.status}`);
|
|
101
|
+
const json = await res.json();
|
|
102
|
+
const out = [];
|
|
103
|
+
for (const p of json.plugins ?? []) {
|
|
104
|
+
const name = typeof p.name === 'string' && p.name !== '' ? p.name : '';
|
|
105
|
+
const full = typeof p.fullName === 'string' && p.fullName !== '' ? p.fullName : name;
|
|
106
|
+
if (full === '')
|
|
107
|
+
continue;
|
|
108
|
+
const descEn = typeof p.description === 'string' ? p.description : '';
|
|
109
|
+
const descZh = typeof p.descriptionZh === 'string' && p.descriptionZh !== '' ? p.descriptionZh : descEn;
|
|
110
|
+
out.push({
|
|
111
|
+
// Merge key / display name: owner/repo (matches the awesome source).
|
|
112
|
+
name: full,
|
|
113
|
+
url: `https://github.com/${full}`,
|
|
114
|
+
spec: name, // npm package name is the install spec
|
|
115
|
+
categories: categorizeTags(p.tags),
|
|
116
|
+
description: { en: descEn, zh: descZh },
|
|
117
|
+
stars: typeof p.stars === 'number' ? p.stars : null,
|
|
118
|
+
npm: name === '' ? null : name,
|
|
119
|
+
score: p.score !== null && typeof p.score === 'object'
|
|
120
|
+
? {
|
|
121
|
+
total: typeof p.score.total === 'number' ? p.score.total : 0,
|
|
122
|
+
breakdown: p.score.breakdown ?? {},
|
|
123
|
+
explanation: typeof p.score.explanation === 'string' ? p.score.explanation : '',
|
|
124
|
+
}
|
|
125
|
+
: null,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
61
130
|
/** Parse Oh-My-DSH's PLUGINS.md (markdown table, sectioned by category). */
|
|
62
131
|
export async function fetchOhMyDshPlugins() {
|
|
63
132
|
try {
|
|
@@ -85,6 +154,7 @@ export async function fetchOhMyDshPlugins() {
|
|
|
85
154
|
description: { en: '', zh: cells[6] },
|
|
86
155
|
stars: Number.isFinite(stars) ? stars : null,
|
|
87
156
|
npm: null,
|
|
157
|
+
score: null,
|
|
88
158
|
});
|
|
89
159
|
}
|
|
90
160
|
return plugins;
|
|
@@ -108,6 +178,7 @@ export function mergePlugins(sources) {
|
|
|
108
178
|
npm: null,
|
|
109
179
|
version: null,
|
|
110
180
|
installed: false,
|
|
181
|
+
score: null,
|
|
111
182
|
};
|
|
112
183
|
cur.categories = [...new Set([...cur.categories, ...item.categories])];
|
|
113
184
|
if (item.description.en !== '')
|
|
@@ -118,6 +189,8 @@ export function mergePlugins(sources) {
|
|
|
118
189
|
cur.stars = item.stars;
|
|
119
190
|
if (item.npm !== null)
|
|
120
191
|
cur.npm = item.npm;
|
|
192
|
+
if (item.score !== null)
|
|
193
|
+
cur.score = item.score;
|
|
121
194
|
map.set(item.name, cur);
|
|
122
195
|
}
|
|
123
196
|
}
|
package/dist/rpc.js
CHANGED
|
@@ -31,7 +31,7 @@ export class PluginCenterRpc extends Service {
|
|
|
31
31
|
const result = await ctx.pluginCenter.install(spec);
|
|
32
32
|
if (!result.ok)
|
|
33
33
|
return internal(`install ${spec} 失败:${result.detail}`);
|
|
34
|
-
return { ok: true, value:
|
|
34
|
+
return { ok: true, value: { durationMs: result.durationMs } };
|
|
35
35
|
}
|
|
36
36
|
case 'update': {
|
|
37
37
|
const name = payload?.name;
|
|
@@ -43,7 +43,31 @@ export class PluginCenterRpc extends Service {
|
|
|
43
43
|
const result = await ctx.pluginCenter.update(name, version);
|
|
44
44
|
if (!result.ok)
|
|
45
45
|
return internal(`update ${name} 失败:${result.detail}`);
|
|
46
|
-
return { ok: true, value:
|
|
46
|
+
return { ok: true, value: { durationMs: result.durationMs } };
|
|
47
|
+
}
|
|
48
|
+
case 'toggle': {
|
|
49
|
+
const id = payload?.id;
|
|
50
|
+
const disabled = payload?.disabled;
|
|
51
|
+
if (typeof id !== 'string' || id === '')
|
|
52
|
+
return internal('toggle: id is required');
|
|
53
|
+
const result = await ctx.pluginCenter.toggle(id, disabled === true);
|
|
54
|
+
if (!result.ok)
|
|
55
|
+
return internal(`toggle ${id} 失败:${result.detail}`);
|
|
56
|
+
return { ok: true, value: { nowDisabled: result.nowDisabled } };
|
|
57
|
+
}
|
|
58
|
+
case 'diagnostics':
|
|
59
|
+
return { ok: true, value: await ctx.pluginCenter.diagnostics() };
|
|
60
|
+
case 'screenshot': {
|
|
61
|
+
const name = payload?.name;
|
|
62
|
+
if (typeof name !== 'string' || name === '')
|
|
63
|
+
return internal('screenshot: name is required');
|
|
64
|
+
return { ok: true, value: await ctx.pluginCenter.screenshot(name) };
|
|
65
|
+
}
|
|
66
|
+
case 'suggest': {
|
|
67
|
+
const query = payload?.query;
|
|
68
|
+
if (typeof query !== 'string')
|
|
69
|
+
return internal('suggest: query is required');
|
|
70
|
+
return { ok: true, value: await ctx.pluginCenter.suggest(query) };
|
|
47
71
|
}
|
|
48
72
|
case 'debug':
|
|
49
73
|
return { ok: true, value: await ctx.pluginCenter.debug() };
|
package/dist/toggle.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hot disable/enable through the profile's user patch layer
|
|
3
|
+
* (`<profileDir>/cordis.patch.yml`) — the mechanism dsh-market ports from
|
|
4
|
+
* dsh-plugin-hub: a patch row `- id: X` + `disabled: true` stops that loader
|
|
5
|
+
* entry, `disabled: false` force-enables one a lower layer disabled. The
|
|
6
|
+
* official web profile re-composes via HMR (~1s, no restart); SSiD applies
|
|
7
|
+
* the same file on every boot, so the choice survives restarts there.
|
|
8
|
+
*
|
|
9
|
+
* Writes are line-level (the patch dialect is simple for toggles: a row id
|
|
10
|
+
* followed by an optional `disabled:` line), serialized so concurrent
|
|
11
|
+
* toggles cannot interleave a read-modify-write, refused when the file is
|
|
12
|
+
* not a plain entry list, and protected for host-infrastructure rows.
|
|
13
|
+
*/
|
|
14
|
+
export interface ToggleResult {
|
|
15
|
+
ok: boolean;
|
|
16
|
+
detail: string;
|
|
17
|
+
/** The patch layer's stance after the write, or null when refused. */
|
|
18
|
+
nowDisabled: boolean | null;
|
|
19
|
+
}
|
|
20
|
+
/** What the user patch layer currently says about every row id. */
|
|
21
|
+
export declare function readDisabledState(patchPath: string): Map<string, boolean>;
|
|
22
|
+
/**
|
|
23
|
+
* Set one entry's disabled stance in the profile patch layer. The file is
|
|
24
|
+
* only touched when the stance changes; a malformed file (not a plain
|
|
25
|
+
* entry list) is reported instead of being made worse.
|
|
26
|
+
* @param profileDir - the profile directory holding cordis.patch.yml.
|
|
27
|
+
* @param id - the loader entry id to toggle.
|
|
28
|
+
* @param disabled - the target stance.
|
|
29
|
+
* @returns the outcome; `nowDisabled` mirrors the stance or null when refused.
|
|
30
|
+
*/
|
|
31
|
+
export declare function setDisabled(profileDir: string, id: string, disabled: boolean): Promise<ToggleResult>;
|