dsh-m 0.0.2 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/tools.js ADDED
@@ -0,0 +1,340 @@
1
+ import { defineTool } from '@deepseek-ai/dsh-tools';
2
+ import { installTimeoutMs } from './core/env.js';
3
+ import { installFromRegistry, listInstalledWithMeta, uninstallPlugin, upgradePlugin, withMutationLock } from './core/market.js';
4
+ import { scheduleRestart } from './core/restart.js';
5
+ export const CATEGORY_LABELS = {
6
+ market: '市场',
7
+ tools: '工具',
8
+ ui: '界面',
9
+ search: '搜索',
10
+ media: '多媒体',
11
+ other: '其他',
12
+ };
13
+ function cloneJson(value) {
14
+ return JSON.parse(JSON.stringify(value));
15
+ }
16
+ function matchInstalledByEntry(entry, installed) {
17
+ return installed.find((it) => {
18
+ if (entry.npm && (it.pkg === entry.npm || it.name === entry.npm))
19
+ return true;
20
+ if (entry.github && it.source === 'github') {
21
+ const m = /^github:([^#]+)/.exec(it.spec);
22
+ if (m && m[1] === entry.github)
23
+ return true;
24
+ }
25
+ return false;
26
+ });
27
+ }
28
+ export function registerTools(ctx, cfg) {
29
+ const timeoutMs = cfg.timeoutMs ?? 20_000;
30
+ ctx.tools.register(defineTool({
31
+ name: 'dshm_search',
32
+ description: 'Search your personal DSH plugin marketplace (dsh-m) and show clickable plugin cards. ALWAYS call this instead of web_search or bash when the user wants to find/recommend/browse their curated DSH plugins (插件). Call EXACTLY ONCE per user message; extract a real keyword (主题, 搜索) rather than pasting the whole sentence. Omit query to browse all listings. After cards appear, reply with AT MOST one short sentence. Do not print install commands.',
33
+ parameters: {
34
+ query: { type: 'string', description: 'Main keyword, e.g. 主题 or 搜索. Optional.' },
35
+ category: {
36
+ type: 'string',
37
+ description: `Optional first-level category: ${Object.keys(CATEGORY_LABELS).join(', ')}`,
38
+ },
39
+ limit: { type: 'number', description: 'Cards in this batch. Default all (registry is curated & small).' },
40
+ },
41
+ output: {
42
+ schema: { type: 'object', additionalProperties: true },
43
+ render: (_args, value) => [{ type: 'text', text: renderSearch(value) }],
44
+ presentationMeta: (_args, value) => ({ kind: 'dshm-search', ...value }),
45
+ },
46
+ presentCall: (args) => ({
47
+ card: 'generic',
48
+ title: `DSH 市场 · ${String(args.query || args.category || '浏览')}`,
49
+ kind: 'search',
50
+ content: [],
51
+ }),
52
+ presentResult: (_args, { isError, meta }) => ({
53
+ card: 'generic',
54
+ title: isError ? '市场搜索失败' : `DSH 市场 · ${meta?.items?.length ?? 0} 条`,
55
+ content: [],
56
+ }),
57
+ timeoutMs: timeoutMs + 5000,
58
+ async execute(args) {
59
+ const registry = await import('./core/registry.js');
60
+ const installed = await import('./core/installed.js');
61
+ const loaded = await registry.loadRegistry(cfg);
62
+ const inst = await installed.listInstalledPlugins();
63
+ const query = String(args.query || '').trim().toLowerCase();
64
+ const category = typeof args.category === 'string' && args.category ? args.category : null;
65
+ let items = loaded.registry.plugins.filter((e) => {
66
+ if (category && e.category !== category)
67
+ return false;
68
+ if (!query)
69
+ return true;
70
+ const hay = `${e.id} ${e.name} ${e.description} ${e.tags.join(' ')}`.toLowerCase();
71
+ return hay.includes(query);
72
+ });
73
+ const total = items.length;
74
+ const limit = Number.isFinite(Number(args.limit)) && Number(args.limit) > 0 ? clamp(Number(args.limit), 1, 80) : total;
75
+ items = items.slice(0, limit);
76
+ return cloneJson({
77
+ query: String(args.query || ''),
78
+ category,
79
+ total,
80
+ items: items.map((e) => {
81
+ const i = matchInstalledByEntry(e, inst.items);
82
+ return {
83
+ id: e.id,
84
+ name: e.name,
85
+ description: e.description,
86
+ category: e.category,
87
+ tags: e.tags,
88
+ source: e.source,
89
+ npm: e.npm,
90
+ github: e.github,
91
+ homepage: e.homepage,
92
+ installed: Boolean(i),
93
+ installedPkg: i?.pkg,
94
+ installedVersion: i?.version,
95
+ };
96
+ }),
97
+ });
98
+ },
99
+ }));
100
+ ctx.tools.register(defineTool({
101
+ name: 'dshm_list',
102
+ description: 'List plugins installed in the DSH web profile, annotated with 市场安装/非市场安装, sources, and outdated flags. Use when the user asks what plugins are installed or wants to manage local plugins.',
103
+ parameters: {},
104
+ output: {
105
+ schema: { type: 'object', additionalProperties: true },
106
+ render: (_args, value) => [{ type: 'text', text: renderList(value) }],
107
+ presentationMeta: (_args, value) => ({ kind: 'dshm-list', ...value }),
108
+ },
109
+ presentCall: () => ({ card: 'generic', title: '已装插件', kind: 'search', content: [] }),
110
+ presentResult: (_args, { isError, meta }) => ({
111
+ card: 'generic',
112
+ title: isError ? '列出失败' : `已装 · ${meta?.items?.length ?? 0} 个`,
113
+ content: [],
114
+ }),
115
+ timeoutMs: timeoutMs + 5000,
116
+ async execute() {
117
+ const result = await listInstalledWithMeta(cfg);
118
+ return cloneJson({
119
+ profileDir: result.profileDir,
120
+ others: result.others,
121
+ items: result.items.map((it) => ({
122
+ pkg: it.pkg,
123
+ name: it.name,
124
+ version: it.version,
125
+ source: it.source,
126
+ registryId: it.registryId ?? null,
127
+ latestVersion: it.latestVersion ?? null,
128
+ outdated: it.outdated,
129
+ })),
130
+ });
131
+ },
132
+ }));
133
+ ctx.tools.register(defineTool({
134
+ name: 'dshm_install',
135
+ description: 'Install a plugin from the dsh-m registry into the current web profile after the user names one (装 dsh-skins / 安装 web-search). Pass the id from dshm_search results. npm 源锁定最新精确版本,github 源锁定 commit SHA。Do not print CLI commands. After success, tell the user it needs a restart of dsh web, and offer dshm_restart.',
136
+ parameters: {
137
+ id: { type: 'string', required: true, description: '收录 id from dshm_search, e.g. dsh-skins' },
138
+ version: { type: 'string', description: 'Optional exact semver (npm 源). Default latest.' },
139
+ },
140
+ output: {
141
+ schema: { type: 'object', additionalProperties: true },
142
+ render: (_args, value) => [{ type: 'text', text: renderInstall(value) }],
143
+ presentationMeta: (_args, value) => ({ kind: 'dshm-install', ...value }),
144
+ },
145
+ presentCall: (args) => ({ card: 'generic', title: `安装 · ${String(args.id || '')}`, kind: 'search', content: [] }),
146
+ presentResult: (_args, { isError, meta }) => ({
147
+ card: 'generic',
148
+ title: isError ? '安装失败' : `已安装 · ${meta?.pkg || ''}`,
149
+ content: [],
150
+ }),
151
+ timeoutMs: installTimeoutMs() + 60_000,
152
+ async execute(args) {
153
+ const id = String(args.id || '').trim();
154
+ if (!id)
155
+ throw new Error('缺少收录 id');
156
+ const version = typeof args.version === 'string' && args.version.trim() ? args.version.trim() : undefined;
157
+ return cloneJson(await withMutationLock(() => installFromRegistry(id, cfg, { version })));
158
+ },
159
+ }));
160
+ ctx.tools.register(defineTool({
161
+ name: 'dshm_uninstall',
162
+ description: 'Uninstall a DSH plugin from the web profile by package name (pkg from dshm_list). Confirm with the user BEFORE calling. Does not delete plugin data; reports leftover paths instead.',
163
+ parameters: {
164
+ pkg: { type: 'string', required: true, description: '包名 from dshm_list, e.g. dsh-web-search' },
165
+ },
166
+ output: {
167
+ schema: { type: 'object', additionalProperties: true },
168
+ render: (_args, value) => [{ type: 'text', text: renderUninstall(value) }],
169
+ presentationMeta: (_args, value) => ({ kind: 'dshm-uninstall', ...value }),
170
+ },
171
+ presentCall: (args) => ({ card: 'generic', title: `卸载 · ${String(args.pkg || '')}`, content: [] }),
172
+ presentResult: (_args, { isError, meta }) => ({
173
+ card: 'generic',
174
+ title: isError ? '卸载失败' : `已卸载 · ${meta?.pkg || ''}`,
175
+ content: [],
176
+ }),
177
+ timeoutMs: installTimeoutMs(),
178
+ async execute(args) {
179
+ const target = String(args.pkg || '').trim();
180
+ if (!target)
181
+ throw new Error('缺少 pkg');
182
+ return cloneJson(await withMutationLock(() => uninstallPlugin(target, cfg)));
183
+ },
184
+ }));
185
+ ctx.tools.register(defineTool({
186
+ name: 'dshm_outdated',
187
+ description: 'Check installed DSH plugins for newer versions (npm latest / GitHub HEAD). Use when the user asks about updates or 升级. Read-only.',
188
+ parameters: {},
189
+ output: {
190
+ schema: { type: 'object', additionalProperties: true },
191
+ render: (_args, value) => [{ type: 'text', text: renderOutdated(value) }],
192
+ presentationMeta: (_args, value) => ({ kind: 'dshm-outdated', ...value }),
193
+ },
194
+ presentCall: () => ({ card: 'generic', title: '检查更新', kind: 'search', content: [] }),
195
+ presentResult: (_args, { isError, meta }) => {
196
+ const out = meta;
197
+ const n = out?.items?.filter((it) => it.outdated).length ?? 0;
198
+ return { card: 'generic', title: isError ? '检查失败' : n ? `${n} 个可升级` : '全部最新', content: [] };
199
+ },
200
+ timeoutMs: timeoutMs + 10_000,
201
+ async execute() {
202
+ const result = await listInstalledWithMeta(cfg);
203
+ const items = result.items.map((it) => ({
204
+ pkg: it.pkg,
205
+ name: it.name,
206
+ version: it.version,
207
+ source: it.source,
208
+ latestVersion: it.latestVersion ?? null,
209
+ outdated: it.outdated,
210
+ }));
211
+ return cloneJson({ items, outdatedCount: items.filter((it) => it.outdated).length });
212
+ },
213
+ }));
214
+ ctx.tools.register(defineTool({
215
+ name: 'dshm_upgrade',
216
+ description: 'Upgrade an installed DSH plugin to the latest version (npm 拉最新精确版 / github 重新锁 HEAD)。pkg 来自 dshm_list 或 dshm_outdated。用户确认升级哪一个之后再调用。After success, tell the user it needs a restart, and offer dshm_restart.',
217
+ parameters: {
218
+ pkg: { type: 'string', required: true, description: '包名 from dshm_list / dshm_outdated' },
219
+ },
220
+ output: {
221
+ schema: { type: 'object', additionalProperties: true },
222
+ render: (_args, value) => [{ type: 'text', text: renderUpgrade(value) }],
223
+ presentationMeta: (_args, value) => ({ kind: 'dshm-upgrade', ...value }),
224
+ },
225
+ presentCall: (args) => ({ card: 'generic', title: `升级 · ${String(args.pkg || '')}`, content: [] }),
226
+ presentResult: (_args, { isError, meta }) => ({
227
+ card: 'generic',
228
+ title: isError ? '升级失败' : `已升级 · ${meta?.pkg || ''}`,
229
+ content: [],
230
+ }),
231
+ timeoutMs: installTimeoutMs() + 60_000,
232
+ async execute(args) {
233
+ const target = String(args.pkg || '').trim();
234
+ if (!target)
235
+ throw new Error('缺少 pkg');
236
+ return cloneJson(await withMutationLock(() => upgradePlugin(target, cfg)));
237
+ },
238
+ }));
239
+ ctx.tools.register(defineTool({
240
+ name: 'dshm_restart',
241
+ description: 'Restart DSH web so newly installed/uninstalled/upgraded plugins take effect. ONLY call after the user agrees (用户同意重启后). The page reloads automatically after the service comes back.',
242
+ parameters: {},
243
+ output: {
244
+ schema: { type: 'object', additionalProperties: true },
245
+ render: (_args, value) => [{
246
+ type: 'text',
247
+ text: `已请求重启 DSH web(via ${value.via})。服务几秒内恢复,之后让用户刷新页面即可。对用户最多一句短话。`,
248
+ }],
249
+ presentationMeta: (_args, value) => ({ kind: 'dshm-restart', ...value }),
250
+ },
251
+ presentCall: () => ({ card: 'generic', title: '重启 DSH Web', content: [] }),
252
+ presentResult: (_args, { isError }) => ({
253
+ card: 'generic',
254
+ title: isError ? '重启失败' : '已请求重启',
255
+ content: [],
256
+ }),
257
+ timeoutMs: 15_000,
258
+ async execute() {
259
+ return cloneJson(scheduleRestart(null));
260
+ },
261
+ }));
262
+ ctx.inject(['systemPrompt'], (c) => {
263
+ const prompt = c.systemPrompt;
264
+ prompt.section({
265
+ name: 'tool:dshm',
266
+ order: 211,
267
+ text: [
268
+ 'Finding / recommending / browsing DSH plugins (插件) in the personal marketplace: you MUST call dshm_search, never web_search or bash. One call per user message; extract a real keyword. After cards appear, reply with AT MOST one short sentence. Do not print install commands.',
269
+ `Plugin categories: ${Object.entries(CATEGORY_LABELS).map(([k, v]) => `${k}=${v}`).join(', ')}.`,
270
+ 'Install only after the user names a card: dshm_install with its id. Then one short sentence mentioning the restart requirement; offer dshm_restart.',
271
+ 'For installed plugins, call dshm_list / dshm_outdated. Upgrade only after the user confirms which one: dshm_upgrade. Uninstall only after confirmation: dshm_uninstall.',
272
+ 'dshm_restart only after the user agrees to restart; afterwards tell them to refresh once the page recovers.',
273
+ ].join(' '),
274
+ });
275
+ });
276
+ }
277
+ function renderSearch(out) {
278
+ if (!out.items?.length)
279
+ return '收录清单中没有匹配的插件。对用户只说一句:没找到,可以换个词再搜。不要写长文。';
280
+ const lines = out.items.map((it, i) => {
281
+ const inst = it.installed ? `(已安装 v${it.installedVersion || '?'})` : '';
282
+ return `${i + 1}. ${it.name} · ${it.id}${inst} · ${CATEGORY_LABELS[it.category] || it.category}`;
283
+ });
284
+ return [
285
+ `插件卡片已展示 ${out.items.length}${out.total && out.total > out.items.length ? `/${out.total}` : ''} 条(内部序号,禁止复述给用户):`,
286
+ lines.join('\n'),
287
+ '对用户最多回一句短话。禁止清单和长文。用户点名安装时才调 dshm_install(id)。',
288
+ ].join('\n');
289
+ }
290
+ function renderList(out) {
291
+ if (!out.items?.length)
292
+ return 'web profile 还没有安装任何 dsh 插件。对用户一句短话即可。';
293
+ const lines = out.items.map((it, i) => {
294
+ const marks = [
295
+ it.registryId ? '市场' : '非市场',
296
+ it.outdated && it.latestVersion ? `可升级 → v${it.latestVersion}` : null,
297
+ ].filter(Boolean).join(',');
298
+ return `${i + 1}. ${it.name} (${it.pkg}) v${it.version || '?'} · ${it.source}${marks ? ` · ${marks}` : ''}`;
299
+ });
300
+ return [
301
+ `已安装 ${out.items.length} 个插件(内部序号,禁止复述给用户):`,
302
+ lines.join('\n'),
303
+ '对用户最多回一句短话。管理动作:dshm_upgrade / dshm_uninstall(先与用户确认)。',
304
+ ].join('\n');
305
+ }
306
+ function renderInstall(out) {
307
+ const extra = out.usedAllowAllBuilds ? '注意:该插件执行了构建脚本(已按策略放行)。' : '';
308
+ return `✅ ${out.pkg} 已安装(${out.spec})。${extra}需要重启 DSH Web 生效——告知用户并询问是否 dshm_restart。不要打印安装命令。`;
309
+ }
310
+ function renderUninstall(out) {
311
+ const parts = [`✅ ${out.pkg} 已卸载。`];
312
+ if (out.liveDisabled)
313
+ parts.push('运行中的界面已先下线。');
314
+ if (out.leftovers?.length)
315
+ parts.push(`疑似残留数据(未删除,仅供知晓):${out.leftovers.join('、')}`);
316
+ parts.push('需要重启生效——询问是否 dshm_restart。');
317
+ return parts.join(' ');
318
+ }
319
+ function renderOutdated(out) {
320
+ const outdated = (out.items || []).filter((it) => it.outdated);
321
+ if (!out.items?.length)
322
+ return 'web profile 没有已装插件。';
323
+ if (!outdated.length)
324
+ return `全部 ${out.items.length} 个插件均已是最新版本。对用户一句短话。`;
325
+ const lines = outdated.map((it) => `${it.name} (${it.pkg}):v${it.version} → ${it.latestVersion || '最新'}`);
326
+ return [
327
+ `${outdated.length}/${out.items.length} 个插件可升级:`,
328
+ lines.join('\n'),
329
+ '询问用户要升级哪个,确认后调 dshm_upgrade(pkg)。',
330
+ ].join('\n');
331
+ }
332
+ function renderUpgrade(out) {
333
+ const from = out.fromVersion ? `v${out.fromVersion} → ` : '';
334
+ const to = out.version ? `v${out.version}` : out.sha ? out.sha.slice(0, 7) : '最新';
335
+ const extra = out.usedAllowAllBuilds ? '注意:该插件执行了构建脚本。' : '';
336
+ return `✅ ${out.pkg} 已升级(${from}${to})。${extra}需要重启生效——询问是否 dshm_restart。`;
337
+ }
338
+ function clamp(n, min, max) {
339
+ return Math.min(max, Math.max(min, n));
340
+ }
package/package.json CHANGED
@@ -1,22 +1,36 @@
1
1
  {
2
2
  "name": "dsh-m",
3
- "version": "0.0.2",
4
- "description": "Marketplace / registry of install addresses for DeepSeek Harness (DSH) plugins. Name reserved — real CLI coming.",
3
+ "version": "0.1.0",
4
+ "description": "DSH Marketplace 个人自用的 DeepSeek Harness 插件市场:收录、安装、卸载、升级 DSH 插件",
5
5
  "license": "MIT",
6
- "main": "index.js",
6
+ "type": "module",
7
+ "main": "lib/host.js",
8
+ "bin": {
9
+ "dshm": "./lib/cli.js"
10
+ },
11
+ "exports": {
12
+ ".": "./lib/host.js",
13
+ "./client": "./lib/client.js",
14
+ "./cordis.patch.yml": "./cordis.patch.yml",
15
+ "./package.json": "./package.json"
16
+ },
7
17
  "files": [
8
- "index.js",
9
- "README.md"
18
+ "lib",
19
+ "cordis.patch.yml",
20
+ "registry.json",
21
+ "README.md",
22
+ "DESIGN.md",
23
+ "LICENSE"
10
24
  ],
11
25
  "keywords": [
12
26
  "dsh",
27
+ "dsh-plugin",
13
28
  "deepseek-harness",
14
- "plugin",
15
29
  "marketplace",
16
30
  "registry"
17
31
  ],
18
32
  "engines": {
19
- "node": ">=18"
33
+ "node": ">=22"
20
34
  },
21
35
  "publishConfig": {
22
36
  "registry": "https://registry.npmjs.org/",
@@ -25,5 +39,41 @@
25
39
  "repository": {
26
40
  "type": "git",
27
41
  "url": "git+https://github.com/iasiv5/dsh-m.git"
42
+ },
43
+ "scripts": {
44
+ "build": "node scripts/build.mjs",
45
+ "typecheck": "tsc -p tsconfig.json --noEmit",
46
+ "prepare": "npm run build"
47
+ },
48
+ "dsh": {
49
+ "bundle": {
50
+ "patch": "./cordis.patch.yml"
51
+ },
52
+ "client": {
53
+ "platform": "web",
54
+ "inject": [
55
+ "@deepseek-ai/dsh-client-runtime",
56
+ "@deepseek-ai/dsh-client-ui-slots",
57
+ "@deepseek-ai/dsh-client-ui-settings"
58
+ ]
59
+ }
60
+ },
61
+ "peerDependencies": {
62
+ "@deepseek-ai/cordis": "*",
63
+ "@deepseek-ai/dsh-tools": "*",
64
+ "@deepseek-ai/schemastery": "*",
65
+ "react": "^18.2.0"
66
+ },
67
+ "devDependencies": {
68
+ "@deepseek-ai/cordis": "^4.0.1",
69
+ "@deepseek-ai/dsh-tools": "^0.1.0-rc.6",
70
+ "@deepseek-ai/schemastery": "^3.18.1",
71
+ "@types/node": "^24.0.0",
72
+ "@types/react": "^18.3.0",
73
+ "@types/react-dom": "^18.3.0",
74
+ "esbuild": "^0.25.0",
75
+ "react": "^18.3.1",
76
+ "react-dom": "^18.3.1",
77
+ "typescript": "^5.6.0"
28
78
  }
29
79
  }
package/registry.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "version": 1,
3
+ "plugins": [
4
+ {
5
+ "id": "dsh-skins",
6
+ "name": "DSH Skins",
7
+ "description": "DeepSeek Harness Web 界面主题/皮肤管理插件,可切换多种视觉风格。",
8
+ "category": "ui",
9
+ "tags": ["主题", "美化"],
10
+ "source": "github",
11
+ "github": "iasiv5/skins",
12
+ "homepage": "https://github.com/iasiv5/skins"
13
+ },
14
+ {
15
+ "id": "dsh-web-search",
16
+ "name": "DSH Web Search",
17
+ "description": "多提供商网页搜索(SearXNG → Tavily → Brave → 免密 DuckDuckGo 自动回退)与 URL 内容抽取,接入 web_search 工具缝。",
18
+ "category": "search",
19
+ "tags": ["搜索", "web"],
20
+ "source": "npm",
21
+ "npm": "dsh-web-search",
22
+ "github": "haibinwang9/dsh-web-search",
23
+ "homepage": "https://github.com/haibinwang9/dsh-web-search"
24
+ }
25
+ ]
26
+ }
package/index.js DELETED
@@ -1,9 +0,0 @@
1
- 'use strict';
2
-
3
- const pkg = require('./package.json');
4
-
5
- module.exports = {
6
- name: pkg.name,
7
- version: pkg.version,
8
- description: pkg.description,
9
- };