dsh-m 0.2.1 → 0.2.3

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/docs/DESIGN.md CHANGED
@@ -66,7 +66,7 @@
66
66
  - **安装(npm 源)**:装最新版并以**精确版本锁定**(不用 `^` 范围;用户指定版本必须为精确 semver,经该精确版本 endpoint 查询)。安装前对 profile 的 `package.json` / `pnpm-lock.yaml` / `pnpm-workspace.yaml` 做字节快照;安装后核验 importer 依赖为该精确版本,并在 lockfile `packages` 条目中比对与 npm dist 一致的 `resolution.integrity`——缺失或不一致 **fail closed** 并执行 **best-effort dependency rollback**(原子恢复快照 + `pnpm install --frozen-lockfile`;恢复失败同时报告两类错误并提示人工修复)。不声称 node_modules 与间接依赖已字节级回滚。
67
67
  - **安装(GitHub 源)**:解析并**锁定 commit SHA**(`github:owner/repo#sha`),skillhub 同款。
68
68
  - **已装识别**:读 profile `package.json` dependencies,与 registry 匹配 → 标注「市场安装」;不匹配的也列出,标注「非市场安装 / 来源未知」。卸载/升级对两类都可用。
69
- - **卸载**:live-disable(先让 client bundle 下线,避免 404)→ `dsh plugin remove`。**不清理插件产生的数据/配置**,但把检测到的疑似残留路径(如 `~/.dsh/<plugin>.json`)列出报告。
69
+ - **卸载**:live-disable(先让 client bundle 下线,避免 404)→ 摘除该包在 profile 的补丁条目(`pnpm-workspace.yaml` 顶层 `patchedDependencies` 与 `package.json#pnpm.patchedDependencies`;依赖移除后残留条目会令 pnpm 以 `ERR_PNPM_UNUSED_PATCH` 整单失败,只精确匹配 `pkg` / `pkg@ver`,补丁文件本体保留并计入残留报告)→ `dsh plugin remove`。**不清理插件产生的数据/配置**,但把检测到的疑似残留路径(如 `~/.dsh/<plugin>.json`)列出报告。
70
70
  - **升级**:**按需检查**(`dshm_outdated` / `dshm_list` 时实时比对本地版本 vs npm latest / GitHub main),半自动——展示升级计划,确认后执行。**不做后台定时器**。
71
71
  - **自更新**:dsh-m 对自己同样做版本比对 + 提示升级(设置页呈现)。
72
72
  - **重启**:内置**一键重启**,复用 skillhub 验证过的重启路径(本机 `dsh-web.service` 是转发 shim,不新建 systemd 单元、不监听 3080)。安装/卸载/升级完成后 GUI 弹「需重启生效 [一键重启]」横幅,工具返回重启提示。
package/lib/cli.js CHANGED
@@ -82,7 +82,7 @@ const HELP = `dshm — DSH Marketplace(个人自用 DSH 插件市场)
82
82
  用法:dshm <命令> [参数]
83
83
 
84
84
  只读命令:
85
- dshm search [--query 关键词] [--category market|tools|ui|search|media|other] [--limit N]
85
+ dshm search [--query 关键词] [--category market|tools|ui|search|other] [--limit N]
86
86
  dshm list 列出 web profile 已装插件(含市场标注/可升级)
87
87
  dshm outdated 检查已装插件的最新版本
88
88
  dshm registry 查看收录清单来源与条目
package/lib/client.js CHANGED
@@ -97,7 +97,7 @@ var init_market_state = __esm({
97
97
  "src/client/market-state.js"() {
98
98
  "use strict";
99
99
  MARKET_PAGE_SIZE = 50;
100
- CATEGORIES = ["market", "tools", "ui", "search", "media", "other"];
100
+ CATEGORIES = ["market", "tools", "ui", "search", "other"];
101
101
  FALLBACK_REGISTRY_STATE = {
102
102
  configuredAddress: "",
103
103
  activeAddress: null,
@@ -130,7 +130,6 @@ var ZH = {
130
130
  "cat.tools": "\u5DE5\u5177",
131
131
  "cat.ui": "\u754C\u9762",
132
132
  "cat.search": "\u641C\u7D22",
133
- "cat.media": "\u591A\u5A92\u4F53",
134
133
  "cat.other": "\u5176\u4ED6",
135
134
  "search.ph": "\u641C\u7D22\u540D\u79F0 / \u63CF\u8FF0 / \u6807\u7B7E\u2026",
136
135
  "common.refresh": "\u5237\u65B0",
@@ -299,7 +298,6 @@ var EN = {
299
298
  "cat.tools": "Tools",
300
299
  "cat.ui": "UI",
301
300
  "cat.search": "Search",
302
- "cat.media": "Media",
303
301
  "cat.other": "Other",
304
302
  "search.ph": "Search name, description, tags\u2026",
305
303
  "common.refresh": "Refresh",
@@ -468,7 +466,7 @@ function lookup(key, params) {
468
466
  const dict = browserLang() === "en" ? EN : ZH;
469
467
  return interpolate(dict[key] ?? ZH[key] ?? key, params);
470
468
  }
471
- var CATEGORIES2 = ["market", "tools", "ui", "search", "media", "other"];
469
+ var CATEGORIES2 = ["market", "tools", "ui", "search", "other"];
472
470
  var CSS = `
473
471
  .dshm-overlay{position:fixed;inset:0;z-index:2147483000;background:var(--dsw-alias-bg-mask-3,rgba(15,23,42,.48));display:flex;align-items:center;justify-content:center;padding:24px 16px;box-sizing:border-box}
474
472
  .dshm-panel{width:min(920px,100%);height:min(680px,86vh);display:flex;flex-direction:column;background:var(--dsw-alias-bg-overlay,var(--dsw-alias-bg-layer-3,#fff));border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:14px;box-shadow:0 18px 48px rgba(2,6,23,.25);overflow:hidden;font-family:inherit;color:var(--dsw-alias-label-primary,inherit)}
@@ -514,7 +512,7 @@ var CSS = `
514
512
  .dshm-detail{margin-top:8px;border-top:1px dashed var(--dsw-alias-border-l2,#e5e7eb);padding-top:8px;display:flex;flex-direction:column;gap:6px;font-size:12px;color:var(--dsw-alias-label-secondary,#4b5563)}
515
513
  .dshm-actions{display:flex;gap:6px;flex-wrap:wrap;margin-top:4px}
516
514
  .dshm-banner{display:flex;align-items:center;gap:10px;padding:10px 14px;border-top:1px solid var(--dsw-alias-border-l2,#e5e7eb);background:var(--dsw-alias-state-warn-tertiary,#fffbeb);color:var(--dsw-alias-state-warn-primary,#b45309);font-size:12px}
517
- .dshm-banner .dshm-banner-text{flex:1}
515
+ .dshm-banner .dshm-banner-text{flex:1;max-height:140px;overflow:auto;overscroll-behavior:contain;white-space:pre-wrap;word-break:break-word;line-height:18px}
518
516
  .dshm-row{display:flex;align-items:center;gap:8px}
519
517
  .dshm-kv{display:grid;grid-template-columns:96px 1fr;gap:6px 10px;font-size:12px;align-items:baseline}
520
518
  .dshm-kv .k{color:var(--dsw-alias-label-caption,#6b7280);font-size:11px}
@@ -560,6 +558,10 @@ var CSS = `
560
558
  .dshm-entry:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(38,49,72,.06))}
561
559
  .dshm-entry svg{flex:none;width:16px;height:16px}
562
560
  .dshm-entry span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
561
+ /* \u6536\u8D77\uFF08rail\uFF09\u6001\uFF1A\u4E0E\u5BBF\u4E3B\u8BBE\u7F6E\u5165\u53E3 / dsh-skins \u540C\u51E0\u4F55\u2014\u201436\xD736 \u5706\u5F62\u3001margin 8/10\u3001
562
+ \u56FE\u6807 18\u3001\u5185\u5BB9\u6C34\u5E73\u5C45\u4E2D\u3002\u7F3A\u8FD9\u5957\u4F1A\u5BFC\u81F4\u6536\u8D77\u65F6\u56FE\u6807\u504F\u79BB\u8F74\u7EBF\u4E14\u884C\u8DDD\u4E0D\u9F50\uFF08\u5B9E\u6D4B\u5DE6\u504F ~4px\uFF09\u3002 */
563
+ .dshm-entry.rail{width:36px;height:36px;margin:8px 0 10px;padding:0;justify-content:center;border-radius:50%;gap:0}
564
+ .dshm-entry.rail svg{width:18px;height:18px}
563
565
  [data-slot="sidebar.footer.action"]{display:flex!important;flex-direction:column;width:100%;min-width:0}
564
566
  [data-slot="sidebar.footer.action"]>*{flex:none;min-width:0}
565
567
  .dshm-empty{text-align:center;color:var(--dsw-alias-label-caption,#6b7280);font-size:13px;padding:32px 0}
@@ -1619,10 +1621,11 @@ function MarketIcon() {
1619
1621
  }
1620
1622
  function MarketEntry(props) {
1621
1623
  useEffect(() => ensureCss(), []);
1624
+ const rail = !(props && props.wide);
1622
1625
  return h(
1623
1626
  "button",
1624
1627
  {
1625
- className: "dshm-entry",
1628
+ className: rail ? "dshm-entry rail" : "dshm-entry",
1626
1629
  onClick: () => {
1627
1630
  try {
1628
1631
  mountPanel();
@@ -10,6 +10,119 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
10
10
  import { dirname, isAbsolute, join, resolve } from 'node:path';
11
11
  import { installTimeoutMs, WEB_PROFILE, webProfileDir } from './env.js';
12
12
  import { createProgressTracker } from './progress.js';
13
+ /** pnpm patchedDependencies 条目键与目标包匹配:`pkg` 或 `pkg@任意版本/区间`。 */
14
+ function matchesPatchedKey(key, pkg) {
15
+ return key === pkg || key.startsWith(`${pkg}@`);
16
+ }
17
+ /**
18
+ * 摘除 profile 里目标包的 pnpm 补丁条目:pnpm-workspace.yaml 顶层
19
+ * `patchedDependencies` 与 package.json 的 `pnpm.patchedDependencies` 两处。
20
+ * 卸载场景下依赖被移除后,残留补丁条目会让 pnpm 以 ERR_PNPM_UNUSED_PATCH
21
+ * 拒绝整个 remove/install。只精确匹配目标包的键(`pkg` / `pkg@ver`),
22
+ * 其他包的补丁不动;补丁文件本体保留在磁盘(删包不删数据,DESIGN.md §3)。
23
+ */
24
+ export function removePatchedDependencyEntries(profileDirectory, pkg, deps = {}) {
25
+ const target = String(pkg || '').trim();
26
+ if (!target || !isSafePluginTarget(target))
27
+ return { changed: false, orphanedPatchFiles: [] };
28
+ const exists = deps.existsSync ?? existsSync;
29
+ const read = deps.readFileSync ?? readFileSync;
30
+ const write = deps.writeFileSync ?? writeFileSync;
31
+ const orphanedPatchFiles = [];
32
+ let changed = false;
33
+ // --- pnpm-workspace.yaml(pnpm≥10 补丁配置落点;行级手术,只摘匹配键) ---
34
+ const wsFile = join(profileDirectory, 'pnpm-workspace.yaml');
35
+ let ws = '';
36
+ try {
37
+ ws = read(wsFile, 'utf8');
38
+ }
39
+ catch {
40
+ /* 无文件则跳过 */
41
+ }
42
+ if (ws) {
43
+ const srcLines = ws.split('\n');
44
+ let start = -1;
45
+ for (let i = 0; i < srcLines.length; i++) {
46
+ if (/^patchedDependencies:\s*$/.test(srcLines[i])) {
47
+ start = i;
48
+ break;
49
+ }
50
+ }
51
+ if (start >= 0) {
52
+ // 块边界:下一个顶层键(无缩进行)或文件尾
53
+ let end = srcLines.length;
54
+ for (let i = start + 1; i < srcLines.length; i++) {
55
+ if (/^\S/.test(srcLines[i])) {
56
+ end = i;
57
+ break;
58
+ }
59
+ }
60
+ const keptBlock = [];
61
+ for (const line of srcLines.slice(start + 1, end)) {
62
+ // 条目行:缩进键 + `:` + 补丁文件路径(键可带引号)
63
+ const m = /^\s+(["']?)([^"':]+?)\1\s*:\s*(.+?)\s*$/.exec(line);
64
+ if (m && matchesPatchedKey(m[2].trim(), target)) {
65
+ changed = true;
66
+ const file = resolve(profileDirectory, m[3].trim());
67
+ if (exists(file))
68
+ orphanedPatchFiles.push(file);
69
+ }
70
+ else {
71
+ keptBlock.push(line);
72
+ }
73
+ }
74
+ if (changed) {
75
+ // 块内条目被摘空 → 连 `patchedDependencies:` 头一起移除,避免留下空映射
76
+ const stillHasEntry = keptBlock.some((l) => /^\s+\S/.test(l));
77
+ const next = stillHasEntry
78
+ ? [...srcLines.slice(0, start + 1), ...keptBlock, ...srcLines.slice(end)]
79
+ : [...srcLines.slice(0, start), ...srcLines.slice(end)];
80
+ write(wsFile, next.join('\n'));
81
+ }
82
+ }
83
+ }
84
+ // --- package.json#pnpm.patchedDependencies(pnpm<10 落点,兼容清理) ---
85
+ const pkgJsonFile = join(profileDirectory, 'package.json');
86
+ let raw = '';
87
+ try {
88
+ raw = read(pkgJsonFile, 'utf8');
89
+ }
90
+ catch {
91
+ /* 无文件则跳过 */
92
+ }
93
+ if (raw) {
94
+ try {
95
+ const doc = JSON.parse(raw);
96
+ const patched = doc?.pnpm?.patchedDependencies;
97
+ if (patched && typeof patched === 'object') {
98
+ let touched = false;
99
+ for (const [key, file] of Object.entries(patched)) {
100
+ if (!matchesPatchedKey(key, target))
101
+ continue;
102
+ delete patched[key];
103
+ touched = true;
104
+ changed = true;
105
+ if (typeof file === 'string') {
106
+ const abs = resolve(profileDirectory, file);
107
+ if (exists(abs))
108
+ orphanedPatchFiles.push(abs);
109
+ }
110
+ }
111
+ if (touched) {
112
+ if (Object.keys(patched).length === 0)
113
+ delete doc.pnpm.patchedDependencies;
114
+ if (Object.keys(doc.pnpm).length === 0)
115
+ delete doc.pnpm;
116
+ write(pkgJsonFile, `${JSON.stringify(doc, null, 2)}\n`);
117
+ }
118
+ }
119
+ }
120
+ catch {
121
+ /* package.json 不是合法 JSON:不动 */
122
+ }
123
+ }
124
+ return { changed, orphanedPatchFiles };
125
+ }
13
126
  const TARGET_RE = /^[A-Za-z0-9@:./_#+-]+$/;
14
127
  const NDJSON_COMMANDS = new Set(['add', 'remove', 'install']);
15
128
  export const BOOT_ID = `${String(process.pid)}-${String(Date.now())}`;
@@ -163,6 +276,9 @@ function writeDangerouslyAllowAllBuilds(profileDirectory) {
163
276
  }
164
277
  export function rewritePnpmError(err) {
165
278
  const text = err instanceof Error ? err.message : String(err);
279
+ if (/ERR_PNPM_UNUSED_PATCH/.test(text)) {
280
+ return new Error('profile 的补丁配置(patchedDependencies)里存在不再使用的条目,pnpm 拒绝执行。卸载时 dsh-m 会自动摘除目标包自己的补丁条目;仍报此错通常是其他包留有失效补丁,请手工清理 profile 的 pnpm-workspace.yaml。');
281
+ }
166
282
  if (isPrepareBlocked(text)) {
167
283
  return new Error('该插件需要执行构建脚本(prepare),pnpm 默认拦截。dsh-m 已写入 profile 的 dangerouslyAllowAllBuilds 并重试;若仍失败请检查 web profile 是否可写。');
168
284
  }
@@ -7,7 +7,7 @@
7
7
  import { existsSync } from 'node:fs';
8
8
  import { readFile } from 'node:fs/promises';
9
9
  import { join } from 'node:path';
10
- import { addDshPlugin, removeDshPlugin, runCommand } from './dsh-cli.js';
10
+ import { addDshPlugin, removeDshPlugin, removePatchedDependencyEntries, runCommand } from './dsh-cli.js';
11
11
  import { dshHome, installTimeoutMs, webProfileDir } from './env.js';
12
12
  import { assertNpmIntegrity, readPnpmLockIntegrity, restoreSnapshots, snapshotFiles } from './npm-integrity.js';
13
13
  import { listInstalledPlugins as defaultListInstalledPlugins, readProfileDeps, removeInstalledPlugin, } from './installed.js';
@@ -42,7 +42,7 @@ export async function mapWithConcurrency(items, limit, worker) {
42
42
  return results;
43
43
  }
44
44
  function zeroCounts() {
45
- return { market: 0, tools: 0, ui: 0, search: 0, media: 0, other: 0 };
45
+ return { market: 0, tools: 0, ui: 0, search: 0, other: 0 };
46
46
  }
47
47
  function clampLimit(raw, max) {
48
48
  const n = typeof raw === 'number' && Number.isFinite(raw) ? Math.floor(raw) : max;
@@ -528,11 +528,14 @@ export async function installEntry(entry, cfg = {}, opts = {}, deps) {
528
528
  }
529
529
  throw new Error(`条目 ${entry.id} 缺少可安装来源`);
530
530
  }
531
- /** 卸载:live-disable → pnpm remove → 报告疑似残留(DESIGN.md §3:删包不删数据)。 */
532
- export async function uninstallPlugin(pkg, _cfg = {}, _opts = {}) {
531
+ /** 卸载:live-disable → 摘除该包补丁条目 → pnpm remove → 报告疑似残留(DESIGN.md §3:删包不删数据)。 */
532
+ export async function uninstallPlugin(pkg, _cfg = {}, _opts = {}, deps = {}) {
533
533
  const liveDisabled = await setLivePluginDisabled(pkg, true);
534
- await removeInstalledPlugin(pkg);
535
- return { pkg, liveDisabled, needsRestart: true, leftovers: leftoverCandidates(pkg) };
534
+ // 依赖移除后残留的 patchedDependencies 条目会让 pnpm 以 ERR_PNPM_UNUSED_PATCH 整单失败,先摘掉
535
+ const patchCleanup = (deps.removePatchedEntries ?? removePatchedDependencyEntries)(webProfileDir(), pkg);
536
+ await (deps.removeInstalled ?? removeInstalledPlugin)(pkg);
537
+ const leftovers = [...new Set([...leftoverCandidates(pkg), ...patchCleanup.orphanedPatchFiles])];
538
+ return { pkg, liveDisabled, needsRestart: true, leftovers };
536
539
  }
537
540
  /** 升级 = 按最新重新安装(npm 拉最新精确版;github 重新锁 HEAD)。 */
538
541
  export async function upgradePlugin(pkg, cfg = {}, opts = {}, deps) {
@@ -10,7 +10,7 @@ import { constants as fsConstants, mkdirSync } from 'node:fs';
10
10
  import { lstat, mkdir, open, readFile, readdir, realpath, rename, rm } from 'node:fs/promises';
11
11
  import { cacheDir } from './env.js';
12
12
  import { decodeUtf8Fatal, fetchJsonLimitedMeta } from './httpx.js';
13
- export const CATEGORIES = ['market', 'tools', 'ui', 'search', 'media', 'other'];
13
+ export const CATEGORIES = ['market', 'tools', 'ui', 'search', 'other'];
14
14
  /** 容量上限:2 MiB 是原始 UTF-8 bytes(不是字符数);条目超限拒绝整份清单。 */
15
15
  export const MAX_REGISTRY_BYTES = 2 * 1024 * 1024;
16
16
  export const MAX_PLUGINS = 1000;
package/lib/tools.js CHANGED
@@ -7,7 +7,6 @@ export const CATEGORY_LABELS = {
7
7
  tools: '工具',
8
8
  ui: '界面',
9
9
  search: '搜索',
10
- media: '多媒体',
11
10
  other: '其他',
12
11
  };
13
12
  function cloneJson(value) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-m",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "DSH Marketplace — 个人自用的 DeepSeek Harness 插件市场:收录、安装、卸载、升级 DSH 插件",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/registry.json CHANGED
@@ -7,9 +7,43 @@
7
7
  "description": "DeepSeek Harness Web 界面主题/皮肤管理插件,可切换多种视觉风格。",
8
8
  "category": "ui",
9
9
  "tags": ["主题", "美化"],
10
- "source": "github",
11
- "github": "iasiv5/skins",
12
- "homepage": "https://github.com/iasiv5/skins"
10
+ "source": "npm",
11
+ "npm": "@iasiv5/dsh-skins",
12
+ "github": "iasiv5/dsh-skins",
13
+ "homepage": "https://github.com/iasiv5/dsh-skins"
14
+ },
15
+ {
16
+ "id": "dsh-copilot-auth",
17
+ "name": "DSH Copilot Auth",
18
+ "description": "为 DSH 内置 GitHub Copilot 提供方补上设备码(device flow)登录/注销设置页,并预置开箱即用的 Copilot 路由;凭据托管 DSH 内置凭据库,模型目录自动同步。",
19
+ "category": "tools",
20
+ "tags": ["Copilot", "认证", "模型接入"],
21
+ "source": "npm",
22
+ "npm": "@inventec/dsh-copilot-auth",
23
+ "github": "iasiv5/dsh-copilot-auth",
24
+ "homepage": "https://github.com/iasiv5/dsh-copilot-auth"
25
+ },
26
+ {
27
+ "id": "dsh-m",
28
+ "name": "DSH Marketplace",
29
+ "description": "可自定义 Registry 内容的 DeepSeek Harness 插件市场:收录、安装、卸载、升级 DSH 插件,一站搞定。",
30
+ "category": "market",
31
+ "tags": ["市场", "插件管理"],
32
+ "source": "npm",
33
+ "npm": "dsh-m",
34
+ "github": "iasiv5/dsh-m",
35
+ "homepage": "https://github.com/iasiv5/dsh-m"
36
+ },
37
+ {
38
+ "id": "dsh-skip-browser-auth",
39
+ "name": "DSH Skip Browser Auth",
40
+ "description": "在已适配版本上替换官方 Connection 提供方,跳过 BrowserAuth,不再需要输入烦人的Token。(当前已适配:0.1.2-rc.1)",
41
+ "category": "tools",
42
+ "tags": ["BrowserAuth", "连接", "安全"],
43
+ "source": "npm",
44
+ "npm": "@iasiv5/dsh-skip-browser-auth",
45
+ "github": "iasiv5/dsh-skip-browser-auth",
46
+ "homepage": "https://github.com/iasiv5/dsh-skip-browser-auth"
13
47
  },
14
48
  {
15
49
  "id": "modsearch",
@@ -22,6 +56,17 @@
22
56
  "github": "liustack/modsearch",
23
57
  "homepage": "https://github.com/liustack/modsearch"
24
58
  },
59
+ {
60
+ "id": "dsh-auth",
61
+ "name": "DSH Auth",
62
+ "description": "DSH Web 管理员认证插件:Argon2id 口令、可撤销会话、双语界面,经 Caddy forward_auth 边缘保护页面/API/下载/SSE/WebSocket,Harness 保持仅回环监听。",
63
+ "category": "tools",
64
+ "tags": ["认证", "安全"],
65
+ "source": "npm",
66
+ "npm": "dsh-auth",
67
+ "github": "hxy91819/dsh-auth",
68
+ "homepage": "https://github.com/hxy91819/dsh-auth"
69
+ },
25
70
  {
26
71
  "id": "deepseek-harness-lark",
27
72
  "name": "DeepSeek Harness Lark",
@@ -76,39 +121,6 @@
76
121
  "npm": "deepseek-harness-dingtalk",
77
122
  "github": "sliverp/DeepSeek-harness-dingtalk",
78
123
  "homepage": "https://github.com/sliverp/DeepSeek-harness-dingtalk"
79
- },
80
- {
81
- "id": "dsh-auth",
82
- "name": "DSH Auth",
83
- "description": "DSH Web 管理员认证插件:Argon2id 口令、可撤销会话、双语界面,经 Caddy forward_auth 边缘保护页面/API/下载/SSE/WebSocket,Harness 保持仅回环监听。",
84
- "category": "tools",
85
- "tags": ["认证", "安全"],
86
- "source": "npm",
87
- "npm": "dsh-auth",
88
- "github": "hxy91819/dsh-auth",
89
- "homepage": "https://github.com/hxy91819/dsh-auth"
90
- },
91
- {
92
- "id": "dsh-copilot-auth",
93
- "name": "DSH Copilot Auth",
94
- "description": "为 DSH 内置 GitHub Copilot 提供方补上设备码(device flow)登录/注销设置页,并预置开箱即用的 Copilot 路由;凭据托管 DSH 内置凭据库,模型目录自动同步,中英双语。",
95
- "category": "tools",
96
- "tags": ["Copilot", "认证", "模型接入"],
97
- "source": "npm",
98
- "npm": "@inventec/dsh-copilot-auth",
99
- "github": "iasiv5/dsh-copilot-auth",
100
- "homepage": "https://github.com/iasiv5/dsh-copilot-auth"
101
- },
102
- {
103
- "id": "dsh-m",
104
- "name": "DSH Marketplace",
105
- "description": "个人自用的 DeepSeek Harness 插件市场:收录、安装、卸载、升级 DSH 插件,含 Web 界面、agent 工具与 CLI。",
106
- "category": "market",
107
- "tags": ["市场", "插件管理"],
108
- "source": "npm",
109
- "npm": "dsh-m",
110
- "github": "iasiv5/dsh-m",
111
- "homepage": "https://github.com/iasiv5/dsh-m"
112
124
  }
113
125
  ]
114
126
  }