dsh-m 0.9.15 → 0.9.17
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 +13 -1
- package/docs/DESIGN.md +4 -3
- package/lib/cli.js +6 -6
- package/lib/client.js +19 -17
- package/lib/core/host-api.js +1 -1
- package/lib/core/market.js +11 -3
- package/lib/core/profile-ops.js +58 -20
- package/lib/core/registry.js +34 -2
- package/lib/tools.js +5 -5
- package/package.json +1 -1
- package/registry.json +131 -173
package/README.md
CHANGED
|
@@ -48,6 +48,18 @@
|
|
|
48
48
|
|
|
49
49
|
以下重启链路仅适用于 **Web**(Desktop 的生效走官方应用生命周期:安装完成后退出并重新打开 Desktop 应用即可,见 0.9.0 节)。安装 / 卸载 / 升级完成后,当前已打开的市场页与已装页会一起重新读取 profile 状态并同步徽标/卡片,不需要关闭后重新打开插件市场;随后出现「⚡ 一键重启」横幅——受 systemd 管理时通过 DSH launcher 的 `appExit` 交给服务的 `Restart` 策略,避免在待停止 unit 的 cgroup 内启动 `systemctl` helper;无 `appExit` 的 systemd 兜底改用 manager-owned transient `systemd-run`,最后才退回 detached-helper。客户端按 boot id 确认新进程已恢复后关闭横幅,交由 DSH Web 自身的后台连接重试恢复页面,不强制整页刷新,避免认证/路由切换期间白屏。重启链路已在当前 DSH Web `0.2.0-rc.2` 实机核验(2026-10-01):dsh-m 探测 unit `Restart=` 策略后经 `appExit` 交还 systemd,`status=75/TEMPFAIL` 退出由 Restart 策略接住自动拉起,服务恢复后页面后台重连、面板全功能可用。历史口径:`0.1.5-rc.1` 时代已核验 `/dshm` ping 与带认证 `303 → 200`;`0.1.2-rc.1` 按契约核对 + 形态探测兜底收录(verified 数组),部署面演进后不再追旧代 live E2E;transient `systemd-run` 兜底仅适用于无 `appExit` 的宿主(受支持代际均提供 `appExit`),保持设计兜底而非发布门槛;连续安装/卸载由 profile 事务测试面(补偿事务、装后守卫等 813 用例)与 0.4.0→0.4.2 连续发布实证覆盖。安装过程实时显示 pnpm 进度(解析 → 下载 → 链接 → 构建)。
|
|
50
50
|
|
|
51
|
+
### 0.9.17 变更:策展桶次级归属(一插件跨桶)+「iasi自研」更名
|
|
52
|
+
|
|
53
|
+
- **次级归属 `alsoCategories`**:收录条目可声明次级策展桶——chips 计数与桶过滤按「主桶 ∪ 次桶」计(跨桶条目在每个桶里都出现),详情页分类标签仍随主桶。首批双席位:**better-sidebar**(装机必备 ⊕ 崔添翼精选)、**dsh-m**(装机必备 ⊕ iasi自研);better-sidebar 原第 5 个「崔添翼精选」tag 由真实席位取代(tags 回归 ≤4 软规范)。
|
|
54
|
+
- **更名**:策展桶「我的自研」→「**iasi自研**」(slug `self-dev` 不变,chips/工具/CLI/详情标签四端同步)。
|
|
55
|
+
- **例行过渡**:schema 加字段后旧客户端校验拒收新 registry → 回落缓存/包内快照显示旧数据,升级即愈。
|
|
56
|
+
|
|
57
|
+
### 0.9.16 变更:精选策展五桶分类法 + 严筛收录(23→19)+ 新收录 DSH TUI
|
|
58
|
+
|
|
59
|
+
- **策展分类法**:精选区分类从功能五分类(市场/工具/界面/搜索/其他)改为**策展五桶**——装机必备 / 崔添翼精选 / iasi自研 / 腾讯轻量云专区 / 观察区(chips 按此序,腾讯轻量云垫后)。分类语义从「插件是什么」转为「为什么值得进精选」;重叠归属按 装机必备 > 崔添翼精选 > 自研 > 腾讯轻量云 优先级归桶,功能属性转 tags 保留检索,旧分类值仍可作开放 slug 命中自定义源(ADR-0004 修订)。
|
|
60
|
+
- **严筛收录 23→19**:通道五件套(lark/qqbot/weixin/wecom/dingtalk)退出精选——社区层(4,000+ 条)仍收录可装,只是退出策展位;新增收录 **DSH TUI**(崔添翼 9/26 X 推荐:终端 TUI 客户端,`dsh-tui`,走 DSH 客户端契约 ctx.remote);better-sidebar 因优先级归装机必备,以 tag「崔添翼精选」保留 9/27 推荐出处。
|
|
61
|
+
- **同版本携带**(本地增强随发版收编):desktop 安装 enable 阶段失败自动重试一次(高频装卸/插件树重载竞速实证)+ 失败文案按 packageResult 精确化;市场页「缓存快照」横幅退役——stale 状态由设置页社区卡承接,浏览页不再提示临时缓存状态。
|
|
62
|
+
|
|
51
63
|
### 0.9.15 变更:安装进度/终态就地进详情 Modal
|
|
52
64
|
|
|
53
65
|
- **场景**:从详情 Modal 点「安装」后,安装信息(pnpm 阶段进度行、「变更完成」横幅/toast)都渲染在面板底层,隔着 Modal 遮罩半透明透出——弹窗内只有按钮转圈,看不出装到哪一步(实机截图反馈 2026-10-02)。卸载无此问题:不弹 Modal,状态本来就挂在已装卡片上。
|
|
@@ -282,7 +294,7 @@ dshm restart --yes
|
|
|
282
294
|
|
|
283
295
|
除手工 curated 的主清单外,市场还会叠加一层**只读的社区清单**:锚定 npm 包 [`dsh-plugin-catalog`](https://www.npmjs.com/package/dsh-plugin-catalog)(CC0-1.0,awesome-dsh-plugin 全量目录,4,000+ 条),数据层与主清单合并去重(**主清单恒优先**,重名条目社区侧让位),展示层按「社区 / 精选」分区呈现(ADR-0004)——社区区不再显示与主清单重复的条目,精选区保持策展序单页直出。
|
|
284
296
|
|
|
285
|
-
- **数据获取**:dist-tags 探测最新版本 → jsDelivr 按精确版本直取 → npmmirror → unpkg 三线路兜底;版本未变不重拉正文,TTL 内跳过探测。失败回落 `<缓存目录>/awesome/`
|
|
297
|
+
- **数据获取**:dist-tags 探测最新版本 → jsDelivr 按精确版本直取 → npmmirror → unpkg 三线路兜底;版本未变不重拉正文,TTL 内跳过探测。失败回落 `<缓存目录>/awesome/` 运行时缓存先展示(先回缓存、后台自愈)——过期数据绝不冒充最新;缓存状态(缓存快照/目录版本/获取线路/更新时间)在**设置页**可见,浏览页不打扰。
|
|
286
298
|
- **开关与锁定**:设置页 `communityCatalog` 开关(默认开,live 生效)、`communityCatalogPin` 可锁定目录版本(精确 semver);CLI 用 `DSHM_COMMUNITY_CATALOG=0` 退出、`DSHM_COMMUNITY_CATALOG_PIN` 锁版本。
|
|
287
299
|
- **分类与搜索**:社区分类是开放集(已知 23 个分类带中英双语标签:中文由包内单一事实源维护、英文取上游目录,按界面语言下发);搜索走相关性加权管线(中英文双匹配);社区区排序可切(下载量/Star/收录日期 × 升降,默认下载量降序,无下载量 ≠ 0 下载)。
|
|
288
300
|
- **安装语义**:社区条目与主清单走同一 `installEntry`(npm 锁精确版本 / GitHub 锁 commit SHA);市场浏览页只对 npm 条目做更新探测,GitHub 条目不做页面级探测(匿名配额 60 次/小时不可控)。
|
package/docs/DESIGN.md
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
- **严格 v1 schema**:顶层只允许 `version/plugins`,条目只允许 `id/name/description/category/tags/source/npm/github/homepage/icon`;未知字段、非法 ID/npm/GitHub/URL、重复 ID/tag、字段超限、`plugins` 超过 1,000 条均拒绝整份清单(不截断、不部分加载);超过 200 条提示性能边界。
|
|
22
22
|
- **缓存模型**:cache 按 namespace 分目录(Host 与 Agent tools 用 `host/`,独立 CLI 用 `cli/`,互不删除),按带算法版本的 cacheKey 分文件(`CacheFile v2`:原子临时文件 + fsync + rename,0700/0600,symlink 拒写,同 key 进程内写锁)。**候选验证只写候选 cache,绝不 prune 旧源**;settings 写入成功后才 `commitActiveSource`:先原子写 accepted-source metadata(`host/active-source.json`,仅 Host),再清理非当前 custom cache;metadata 失败不 prune、prune 失败保留旧 cache,均只降级为 warning,不撤销已生效的配置。切换后只保留默认缓存与当前 custom 缓存。
|
|
23
23
|
- **统一安全 HTTP**:所有 JSON/text/HEAD 请求共享同一 primitive——手动重定向(每跳校验协议、最多 3 跳、循环检测、signal 传播、返回最终 URL)+ 响应大小上限 + 超时。
|
|
24
|
-
- **秒开缓存语义(0.9.14 SWR)**:`loadRegistry` 与社区获取链的 TTL 过期不再同步等网络——磁盘缓存存在即**先回 stale**(registry `source='default-cache'`、社区 `status='stale'`
|
|
24
|
+
- **秒开缓存语义(0.9.14 SWR)**:`loadRegistry` 与社区获取链的 TTL 过期不再同步等网络——磁盘缓存存在即**先回 stale**(registry `source='default-cache'`、社区 `status='stale'` 既有契约;stale 状态由设置页社区卡承接,市场页不提示临时缓存状态——0.9.15 修订),后台**单飞**刷新写回 cache(同 key 进行中复用;不接调用者 signal,后台自愈不被请求 abort 腰斩;`force`/candidate/显式下载三路径不经 SWR)。默认双线路**粘性**:`loadDefaultChain` 读 cache 记录的上次成功 `source` 排序线路(稳定排序,cache 缺失 = 原序,force 同样生效)。社区 bg 自愈是 force flight——**不走同版本短路**(`if (!opts.force)` 守卫),每 TTL 至多全量重拉一次正文(同步路径的短路优化保留)。页条目 latest 探测缓存整体迁入 `latest-cache.ts`:内存 Map + `<cacheRoot>/latest/<ns>.json` 信封 write-through(`latest/` 在 nsDir 之外,规避 `pruneCaches` 顶层 `*.json` 清扫;懒 seed、损坏/版本不符整弃、24h 硬上限淘汰),跨服务重启存活。客户端默认首页响应存 localStorage(10min TTL、双 zone 分键、读侧再过 normalize 收敛)——面板打开先渲染快照后 background 换新。
|
|
25
25
|
- **live 语义**:registry 地址是 live 设置,应用成功即时生效(无需重启);只有**首次部署新 Host 代码**需要一次用户确认后的 DSH Web 重启。`timeoutMs`/`cacheTtlMin` 亦为 live 读取;agent 框架注册时的静态工具 deadline 不承诺热更新。
|
|
26
26
|
|
|
27
27
|
### 2.2 Schema(v1 定稿,严格校验)
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"id": "dsh-skins", // slug,唯一
|
|
41
41
|
"name": "DSH Skins",
|
|
42
42
|
"description": "中文描述", // v1 只有中文
|
|
43
|
-
"category": "
|
|
43
|
+
"category": "essentials", // 策展五桶:essentials|cui-picks|self-dev|tencent-lighthouse|watchlist 五选一(0.9.16 策展分类法;旧功能五分类退役)
|
|
44
44
|
"tags": ["主题"],
|
|
45
45
|
"source": "github", // npm|github
|
|
46
46
|
"npm": "可选;source=npm 时必填",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
- **目录适配层**:原生条目 → 收录条目。`description.zh`(缺省回退 `en`;>500 字符截断并计数)→ description;`npm` 非空 → `source: npm`,否则 `source: github`(owner/repo 取自 url);`url` → homepage;tags 置空;**分类保留原生值不转译**(开放集,Q40);`stars/downloads/capabilities/screenshots` 等不进收录条目本体,仅在市场层作为旁路数据透传展示。id 合成:原生 `name` 是裸名(实测 4,377 条中 190 个重名),合成小写 `owner--name` 形态并满足 v1 id 规则(非法字符折叠为 `-`,超 64 字符截断加哈希尾缀),适配层内冲突追加序号。
|
|
73
73
|
- **校验语义(Q43)**:社区清单独立校验——容器层严格(顶层键白名单、`plugins` 数组、32 MiB / 30,000 条上限,超限**整份拒收**)+ 条目层宽松(单条不合格**跳过 + 计数**,汇入 warnings,不整份拒绝)。主清单严格校验(§2.2)不变。**无 npm 的 monorepo 子包条目跳过并计数**(repo 根不是正确安装目标);有 npm 的子包条目按 npm 安装、正常收录;tarball-only 条目按 github 源收录(dsh-m 不安装 tarball)。
|
|
74
74
|
- **分类(Q40)**:精选分类(5 个,主清单 schema 不变)∪ 社区分类(开放集)。已知 20 个社区分类带中文标签进筛选栏「社区」组;`ui`/`tools`/`market` 三 id 与精选同名、共享过滤桶;上游新增的未知分类原样渲染进「社区·新分类」临时组,发版收录标签。
|
|
75
|
-
- **降级语义(Q42)**:主清单 unavailable + 社区可用 → 显示社区条目 + 顶部错误横幅 + 安装不禁用;社区 unavailable → 主清单照常 + 静默 notice;探测失败时回落 `<ns>/awesome/` **运行时缓存**并显式标注 stale
|
|
75
|
+
- **降级语义(Q42)**:主清单 unavailable + 社区可用 → 显示社区条目 + 顶部错误横幅 + 安装不禁用;社区 unavailable → 主清单照常 + 静默 notice;探测失败时回落 `<ns>/awesome/` **运行时缓存**并显式标注 stale(绝不冒充 ready)——运行时缓存语义与主清单自定义源一致,被禁止的只是**包内快照**;社区**绝不**回退主清单伪装。(**0.9.15 修订**:SWR 常态化后市场页不再显示「缓存快照」临时横幅——stale 标注移至设置页社区卡,「绝不冒充 ready / 过期可被观察到」的诚实约束不变。)
|
|
76
76
|
- **开关与缓存**:`communityCatalog`(默认 true,volatile live 生效)+ `communityCatalogPin`(可选锁 npm 版本);CLI 用 `DSHM_COMMUNITY_CATALOG=0` 退出。社区缓存放 `<namespace>/awesome/` 子目录(不参与 `pruneCaches` 的顶层 `*.json` 清理),正文按版本文件缓存,`fetchedAt`/版本号/线路进设置页展示;TTL 复用 `cacheTtlMin`(只约束 dist-tags 探测频率)。
|
|
77
77
|
- **市场行为(Q45/Q46)**:默认排序主清单置顶(组内维持原顺序)+ 社区按 30 天下载量降序(无数据按名称);搜索同时匹配中英文描述;「只看主清单」chip 常驻筛选栏首位。**探测边界**:市场浏览页对社区 npm 条目做 latest 探测(registry 无配额限制);社区 github 条目**不做**浏览页 REST 探测(GitHub 匿名 60 次/小时在 50 条/页 × 2 调用下不可控),更新检查收敛到详情/安装时的按需解析——配额耗尽时已有可读提示(versions.ts `githubRateLimitMessage`)。**已装页豁免**:探测对象受已装数量天然约束,继续探测;计量在**真实 GitHub HTTP 请求层**(一次 `githubLatestTag` ≈ 1–3 个请求:release 路径 1–2 个、fallback 路径 releases→tags→commits 最多 3 个):单请求 ≤25 次、宿主进程滚动 1 小时 ≤50 次(**仅作用于被动探测**——用户主动 install/upgrade/诊断不经此预算)、同仓库 in-flight single-flight、超限标 `latestError` 不阻塞列表——否则用户装的社区 github 插件永远没有更新徽标。独立 CLI 进程不共享宿主内预算,文档如实标注 best-effort(并发场景不承诺 60/h 绝不耗尽)。能力披露(capabilities/红线)只在详情折叠区展示(缺省 = 未扫描 ≠ 未检出),卡片不打标(Q44,防警告疲劳);截图仅详情层加载(GitHub 图床白名单由上游保证)。
|
|
78
78
|
|
|
@@ -118,6 +118,7 @@ npm 安装 / GitHub 安装 / 升级 / 自升级 / 卸载是**同一个事务模
|
|
|
118
118
|
- **安装(GitHub 源)**:解析并**锁定 commit SHA**(`github:owner/repo#sha`)。核验采用**前态比对**:新 spec 必须相对快照是新写入(预置同 spec 旧依赖不误命中),否则 `GITHUB_SPEC_MISMATCH` 回滚。
|
|
119
119
|
- **已装识别**:读 profile `package.json` dependencies,与 registry 匹配 → 标注「市场安装」;不匹配的也列出,标注「非市场安装 / 来源未知」。卸载/升级对两类都可用。
|
|
120
120
|
- **卸载**(事务定序写死):validate(严格读取:NOT_INSTALLED / NOT_DSH_PLUGIN / PLUGIN_METADATA_UNREADABLE,全部零写入零快照)→ 快照 → live-disable(先让 client bundle 下线,避免 404;回滚时尽力反向)→ 摘除该包补丁条目(`pnpm-workspace.yaml` 顶层 `patchedDependencies` 与 `package.json#pnpm.patchedDependencies`;残留条目会令 pnpm 以 `ERR_PNPM_UNUSED_PATCH` 整单失败,只精确匹配 `pkg` / `pkg@ver`,补丁文件本体保留并计入残留报告)→ remove → verify gone(仍在则回滚)。**不清理插件产生的数据/配置**,但把检测到的疑似残留路径(如 `~/.dsh/<plugin>.json`)列出报告。
|
|
121
|
+
- **安装(Desktop profile 委派,0.9.0+)**:官方 pluginManager `installBundle(enabled: true)` → ChangeResult 以 application/stage 为准判定:cancelled 拒绝;build-blocked 走结构化审批重试(pending 名单原样回传);minimumReleaseAge 翻译成诚实等待指引(策略正确工作非故障);enable 失败按 `packageResult` 如实区分「包已写入 / 未能写入 / 官方未回传(未知)」——enable 先行失败(pnpm 未跑)时不得断言「包已写入」→ `listBundles` 复读在装(不冒充成功)。**enable 阶段失败自动重试一次**(本地增强 2026-10-02,未发版):应用侧 enable 与高频装卸/插件树重载竞速返回 `operation-error`(实机实证 dsh-copilot-auth 三装卸两败,失败 operation 的 pnpm.log 均为 0 字节),单次退避重试即愈;其余失败形态不重试。
|
|
121
122
|
- **升级**:**按需检查**(`dshm_outdated` / `dshm_list` 时实时比对本地版本 vs npm latest / GitHub release,半自动——展示升级计划,确认后执行,即重开一次安装事务)。**不做后台定时器**。
|
|
122
123
|
- **自更新**:dsh-m 对自己同样做版本比对 + 提示升级(设置页呈现);执行即 `install-npm` 事务(integrity 缺失直接拒绝,不进事务)。
|
|
123
124
|
- **开关(0.4.0,术语见 GLOSSARY.md「开关/行覆盖/Bundle 选择/委派降级」)**:已装插件运行状态的可逆切换,**不经 Profile 变更事务**。粒度自动路由——包内补丁层单 insert 行且无配置补丁行 → 行覆盖(`cordis.patch.yml` 保注释 `disabled` 编辑,官方 `writePluginEnabled` 同款语义,启用写显式 `disabled: false`);多行/不可解析 → Bundle 选择(`dsh.profile.bundles` 数组增删)。写路径委派优先:运行时探测官方 `pluginManager` 服务(`ctx.get`,按存在性不判版本号)→ `setPluginEnabled`/`setBundleEnabled`(白拿保护判定、unaddressable 识别、hmr 活体重组;applied 以复读 `listPlugins()` 为准);服务缺席降级 loader `entry.update()` 直操作 + 锁内文件编辑(bundle 级恒 restart-required)。**必须持官方同款锁**(`withFileLock(<profile>/package.json)`)防与官方 UI 并发丢更新。架构决策见 [ADR-0001](./adr/0001-delegate-with-fallback-for-plugin-manager.md)。
|
package/lib/cli.js
CHANGED
|
@@ -12,13 +12,13 @@ import { COMMUNITY_CATEGORY_LABELS } from './core/community.js';
|
|
|
12
12
|
import { scheduleRestart } from './core/restart.js';
|
|
13
13
|
import { pathToFileURL } from 'node:url';
|
|
14
14
|
import { resolve } from 'node:path';
|
|
15
|
-
/** 分类中文标签(0.7.0 Task 6
|
|
15
|
+
/** 分类中文标签(0.7.0 Task 6;0.9.16 策展五桶):精选策展桶本地表 + 社区已知标签单一事实源;未知 slug 原样。 */
|
|
16
16
|
const CLI_CATEGORY_LABELS = {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
essentials: '装机必备',
|
|
18
|
+
'cui-picks': '崔添翼精选',
|
|
19
|
+
'self-dev': 'iasi自研',
|
|
20
|
+
'tencent-lighthouse': '腾讯轻量云专区',
|
|
21
|
+
watchlist: '观察区',
|
|
22
22
|
};
|
|
23
23
|
function categoryLabelOf(category) {
|
|
24
24
|
return CLI_CATEGORY_LABELS[category] ?? COMMUNITY_CATEGORY_LABELS[category] ?? category;
|
package/lib/client.js
CHANGED
|
@@ -188,7 +188,6 @@ function zoneChips(categoryCounts, categoryLabels, zone) {
|
|
|
188
188
|
function marketNotice(registryState, community) {
|
|
189
189
|
const out = {};
|
|
190
190
|
const c = community && typeof community === "object" ? community : {};
|
|
191
|
-
if (c.status === "stale") out.communityStale = true;
|
|
192
191
|
if (registryState && registryState.status === "unavailable" && (c.status === "ready" || c.status === "stale")) {
|
|
193
192
|
out.communityFallback = true;
|
|
194
193
|
}
|
|
@@ -202,8 +201,14 @@ var init_market_state = __esm({
|
|
|
202
201
|
"use strict";
|
|
203
202
|
MARKET_PAGE_SIZES = [24, 48, 96];
|
|
204
203
|
DEFAULT_PAGE_SIZE = 24;
|
|
205
|
-
CURATED_ORDER = ["
|
|
206
|
-
CURATED_LABELS = {
|
|
204
|
+
CURATED_ORDER = ["essentials", "cui-picks", "self-dev", "tencent-lighthouse", "watchlist"];
|
|
205
|
+
CURATED_LABELS = {
|
|
206
|
+
essentials: "\u88C5\u673A\u5FC5\u5907",
|
|
207
|
+
"cui-picks": "\u5D14\u6DFB\u7FFC\u7CBE\u9009",
|
|
208
|
+
"self-dev": "iasi\u81EA\u7814",
|
|
209
|
+
"tencent-lighthouse": "\u817E\u8BAF\u8F7B\u91CF\u4E91\u4E13\u533A",
|
|
210
|
+
watchlist: "\u89C2\u5BDF\u533A"
|
|
211
|
+
};
|
|
207
212
|
CURATED_IDS = new Set(CURATED_ORDER);
|
|
208
213
|
SLUG_RE = /^[a-z0-9-]{1,32}$/;
|
|
209
214
|
SORT_FIELDS = /* @__PURE__ */ new Set(["downloads", "stars", "added"]);
|
|
@@ -1168,11 +1173,11 @@ var ZH = {
|
|
|
1168
1173
|
"tab.installed": "\u5DF2\u88C5",
|
|
1169
1174
|
"tab.settings": "\u8BBE\u7F6E",
|
|
1170
1175
|
"cat.all": "\u5168\u90E8",
|
|
1171
|
-
"cat.
|
|
1172
|
-
"cat.
|
|
1173
|
-
"cat.
|
|
1174
|
-
"cat.
|
|
1175
|
-
"cat.
|
|
1176
|
+
"cat.essentials": "\u88C5\u673A\u5FC5\u5907",
|
|
1177
|
+
"cat.cui-picks": "\u5D14\u6DFB\u7FFC\u7CBE\u9009",
|
|
1178
|
+
"cat.self-dev": "iasi\u81EA\u7814",
|
|
1179
|
+
"cat.tencent-lighthouse": "\u817E\u8BAF\u8F7B\u91CF\u4E91\u4E13\u533A",
|
|
1180
|
+
"cat.watchlist": "\u89C2\u5BDF\u533A",
|
|
1176
1181
|
"zone.community": "\u793E\u533A",
|
|
1177
1182
|
"zone.primary": "\u7CBE\u9009",
|
|
1178
1183
|
"zone.favorites": "\u6536\u85CF",
|
|
@@ -1329,7 +1334,6 @@ var ZH = {
|
|
|
1329
1334
|
"market.page.info": "\u7B2C {page} / {pages} \u9875 \xB7 \u5171 {total} \u6761",
|
|
1330
1335
|
"notice.toolview.err": "\u6536\u5F55\u6E05\u5355\u6682\u4E0D\u53EF\u7528",
|
|
1331
1336
|
"badge.community": "\u793E\u533A\u6536\u5F55",
|
|
1332
|
-
"community.stale": "\u793E\u533A\u76EE\u5F55\u4E3A\u7F13\u5B58\u5FEB\u7167\uFF08\u663E\u793A\u7684\u4E0D\u662F\u6700\u65B0\u6570\u636E\uFF09",
|
|
1333
1337
|
"community.fallback": "\u6536\u5F55\u6E05\u5355\u4E0D\u53EF\u7528\uFF0C\u5F53\u524D\u5C55\u793A\u793E\u533A\u6E05\u5355\u6761\u76EE",
|
|
1334
1338
|
"detail.capabilities": "\u80FD\u529B\u62AB\u9732",
|
|
1335
1339
|
"detail.capabilities.unscanned": "\u672A\u626B\u63CF \u2260 \u672A\u68C0\u51FA",
|
|
@@ -1429,11 +1433,11 @@ var EN = {
|
|
|
1429
1433
|
"tab.installed": "Installed",
|
|
1430
1434
|
"tab.settings": "Settings",
|
|
1431
1435
|
"cat.all": "All",
|
|
1432
|
-
"cat.
|
|
1433
|
-
"cat.
|
|
1434
|
-
"cat.
|
|
1435
|
-
"cat.
|
|
1436
|
-
"cat.
|
|
1436
|
+
"cat.essentials": "Essentials",
|
|
1437
|
+
"cat.cui-picks": "Cui Picks",
|
|
1438
|
+
"cat.self-dev": "iasi Self-dev",
|
|
1439
|
+
"cat.tencent-lighthouse": "Tencent Lighthouse",
|
|
1440
|
+
"cat.watchlist": "Watchlist",
|
|
1437
1441
|
"zone.community": "Community",
|
|
1438
1442
|
"zone.primary": "Curated",
|
|
1439
1443
|
"zone.favorites": "Favorites",
|
|
@@ -1590,7 +1594,6 @@ var EN = {
|
|
|
1590
1594
|
"market.page.info": "Page {page} / {pages} \xB7 {total} listings",
|
|
1591
1595
|
"notice.toolview.err": "Registry temporarily unavailable",
|
|
1592
1596
|
"badge.community": "Community",
|
|
1593
|
-
"community.stale": "Community catalog served from cache (not the latest data)",
|
|
1594
1597
|
"community.fallback": "Registry unavailable \u2014 showing community listings",
|
|
1595
1598
|
"detail.capabilities": "Capabilities",
|
|
1596
1599
|
"detail.capabilities.unscanned": "Not scanned \u2260 not detected",
|
|
@@ -2408,7 +2411,7 @@ function DetailModal({ it, labels, busy, onClose, onInstall, profileKind, instal
|
|
|
2408
2411
|
document.addEventListener("keydown", onKey);
|
|
2409
2412
|
return () => document.removeEventListener("keydown", onKey);
|
|
2410
2413
|
}, [lb, shots.length, onClose]);
|
|
2411
|
-
const catLabel = labels && labels[it.category] || (["
|
|
2414
|
+
const catLabel = labels && labels[it.category] || (["essentials", "cui-picks", "self-dev", "tencent-lighthouse", "watchlist"].includes(it.category) ? lookup("cat." + it.category) : it.category);
|
|
2412
2415
|
const installCmd = typeof it.install === "string" && it.install !== "" ? it.install : it.source === "npm" ? `dsh plugin --profile web add ${it.npm}` : `dsh plugin --profile web add github:${it.github}`;
|
|
2413
2416
|
const copyCmd = async () => {
|
|
2414
2417
|
try {
|
|
@@ -2751,7 +2754,6 @@ function MarketTab({ notify, markets, onMutation, ops, favorites, profileKind })
|
|
|
2751
2754
|
searchRow,
|
|
2752
2755
|
notice && notice.notice ? h("div", { className: "dshm-err" }, lookup(notice.notice.key)) : null,
|
|
2753
2756
|
notice && notice.communityFallback ? h("div", { className: "dshm-hint" }, lookup("community.fallback")) : null,
|
|
2754
|
-
notice && notice.communityStale ? h("div", { className: "dshm-hint" }, lookup("community.stale")) : null,
|
|
2755
2757
|
h(ZoneChips, {
|
|
2756
2758
|
zone,
|
|
2757
2759
|
counts,
|
package/lib/core/host-api.js
CHANGED
|
@@ -329,7 +329,7 @@ export function createApiDispatcher(ctx) {
|
|
|
329
329
|
const limit = Number.isFinite(limitRaw) && limitRaw > 0 ? Math.min(96, Math.max(1, Math.floor(limitRaw))) : 24;
|
|
330
330
|
const offsetRaw = Number(body.offset);
|
|
331
331
|
const offset = Number.isFinite(offsetRaw) && offsetRaw > 0 ? Math.floor(offsetRaw) : 0;
|
|
332
|
-
// category
|
|
332
|
+
// category:策展五桶 + 社区开放 slug(非法 slug → 400,不静默吞)
|
|
333
333
|
const categoryRaw = typeof body.category === 'string' ? body.category.trim() : '';
|
|
334
334
|
let category = null;
|
|
335
335
|
if (categoryRaw !== '') {
|
package/lib/core/market.js
CHANGED
|
@@ -90,7 +90,7 @@ export async function mapWithConcurrency(items, limit, worker) {
|
|
|
90
90
|
return results;
|
|
91
91
|
}
|
|
92
92
|
function zeroCounts() {
|
|
93
|
-
return {
|
|
93
|
+
return { essentials: 0, 'cui-picks': 0, 'self-dev': 0, 'tencent-lighthouse': 0, watchlist: 0 };
|
|
94
94
|
}
|
|
95
95
|
function clampLimit(raw, max) {
|
|
96
96
|
const n = typeof raw === 'number' && Number.isFinite(raw) ? Math.floor(raw) : max;
|
|
@@ -471,14 +471,22 @@ export async function listMarket(cfg = {}, opts = {}, deps = {}) {
|
|
|
471
471
|
? merged.filter((entry) => !isCommunityEntry(entry))
|
|
472
472
|
: merged;
|
|
473
473
|
const counts = zeroCounts();
|
|
474
|
-
for (const entry of zoned)
|
|
474
|
+
for (const entry of zoned) {
|
|
475
475
|
counts[entry.category] = (counts[entry.category] ?? 0) + 1;
|
|
476
|
+
// 0.9.17 次级策展桶:跨桶条目在每个归属桶里都计一次(Σcounts 可大于去重总数);
|
|
477
|
+
// 社区条目无 alsoCategories(开放集 string),cast 到 RegistryEntry 侧读取
|
|
478
|
+
const also = entry.alsoCategories;
|
|
479
|
+
for (const extra of also ?? []) {
|
|
480
|
+
counts[extra] = (counts[extra] ?? 0) + 1;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
476
483
|
const cat = opts.category ?? null;
|
|
477
484
|
// 相关性搜索(0.7.0 Task 3):归一化分词 + 字段加权评分;0 分不返回;
|
|
478
485
|
// id 整串精确匹配保证命中(收藏 stale 检测依赖)。
|
|
479
486
|
const terms = tokenizeSearchText(normalizeSearchText(opts.query ?? ''));
|
|
480
487
|
const filtered = zoned.filter((entry) => {
|
|
481
|
-
|
|
488
|
+
const also = entry.alsoCategories ?? [];
|
|
489
|
+
if (cat && entry.category !== cat && !also.includes(cat))
|
|
482
490
|
return false;
|
|
483
491
|
if (terms.length === 0)
|
|
484
492
|
return true;
|
package/lib/core/profile-ops.js
CHANGED
|
@@ -179,17 +179,42 @@ export async function desktopToggle(pkg, enabled, deps = {}) {
|
|
|
179
179
|
* desktopInstall / desktopUpgrade 共用;build-blocked 以 DesktopBuildApprovalNeeded 抛出,
|
|
180
180
|
* 由调用方映射各自的返回形态。判定纪律沿用文件头「报告 §5.2」(dsh-market #703/#772 实证):
|
|
181
181
|
* 以 application/stage 为准,绝不只看 packageResult.exitCode;overridden 非失败。
|
|
182
|
+
*
|
|
183
|
+
* 本地增强(2026-10-02,未随计划发版):enable 阶段失败自动重试一次 + 文案按 packageResult 精确化。
|
|
184
|
+
* 背景:desktop 高频装卸/插件树重载窗口下,官方管理器 enable 阶段与应用侧操作竞速,返回
|
|
185
|
+
* application=failed/stage=enable/error=operation-error(实机实证 dsh-copilot-auth 三装卸两败,
|
|
186
|
+
* 失败 operation 的 pnpm.log 均为 0 字节——失败发生在 pnpm 之前的应用侧 enable,重试即愈)。
|
|
182
187
|
*/
|
|
188
|
+
let enableRetryDelayMs = 1_200;
|
|
189
|
+
/** 测试钩子:调整 enable 重试退避毫秒(0 = 立即重试)。 */
|
|
190
|
+
export function _setEnableRetryDelayForTests(ms) {
|
|
191
|
+
enableRetryDelayMs = Math.max(0, ms);
|
|
192
|
+
}
|
|
193
|
+
const sleepMs = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
194
|
+
/** enable 阶段失败文案:按 packageResult 如实区分「已写入 / 未写入 / 未知」——
|
|
195
|
+
* enable 先行失败(pnpm 未跑)时旧文案「包已写入」是错误断言(实机实证)。 */
|
|
196
|
+
function enableStageFailureMessage(pkg, change) {
|
|
197
|
+
const pr = change.packageResult;
|
|
198
|
+
let writtenText;
|
|
199
|
+
if (pr && typeof pr === 'object') {
|
|
200
|
+
const written = pr.exitCode === 0 || (typeof pr.kind === 'string' && /applied|ok|installed|success/i.test(pr.kind));
|
|
201
|
+
writtenText = written ? '包已写入但未能启用' : '包未能写入(启用注册先行失败,安装步骤未执行)';
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
writtenText = '启用阶段失败(官方未回传包写入结果,是否写入未知)';
|
|
205
|
+
}
|
|
206
|
+
return `安装后启用阶段失败(${pkg};application=failed、stage=enable${change.error?.code ? `、error=${change.error.code}` : ''}):${writtenText},请在官方 Desktop 插件页查看状态或重试`;
|
|
207
|
+
}
|
|
183
208
|
async function runManagedInstall(service, pkg, spec, approvedBuilds) {
|
|
184
|
-
const
|
|
209
|
+
const installOnce = async () => changeOf(await service.installBundle(spec, {
|
|
185
210
|
enabled: true,
|
|
186
211
|
...(approvedBuilds && approvedBuilds.length > 0 ? { approvedBuilds } : {}),
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
212
|
+
}));
|
|
213
|
+
/** 失败映射(既有语义不变:cancelled / build-blocked 审批 / release-age 等待 / enable / 通用)。 */
|
|
214
|
+
const mapFailure = (change) => {
|
|
215
|
+
if (change.application === 'cancelled') {
|
|
216
|
+
throw new DesktopOpsError('install-refused', `安装已被取消(${pkg}):desktop profile 文件已由官方管理器恢复,未安装`);
|
|
217
|
+
}
|
|
193
218
|
const pending = Array.isArray(change.pendingBuilds) ? change.pendingBuilds.filter((n) => typeof n === 'string') : [];
|
|
194
219
|
if (change.packageResult?.kind === 'build-blocked' && pending.length > 0) {
|
|
195
220
|
throw new DesktopBuildApprovalNeeded(pending);
|
|
@@ -200,23 +225,36 @@ async function runManagedInstall(service, pkg, spec, approvedBuilds) {
|
|
|
200
225
|
throw new DesktopOpsError('release-age-wait', releaseAgeWaitMessage(diagnosticText));
|
|
201
226
|
}
|
|
202
227
|
if (change.stage === 'enable') {
|
|
203
|
-
throw new DesktopOpsError('enable-failed',
|
|
228
|
+
throw new DesktopOpsError('enable-failed', enableStageFailureMessage(pkg, change));
|
|
204
229
|
}
|
|
205
230
|
throw new DesktopOpsError('install-refused', `官方管理器安装失败(${pkg};application=${change.application ?? 'failed'}${change.error?.code ? `、error=${change.error.code}` : ''}${change.error?.diagnostic ? `:${change.error.diagnostic}` : ''})`);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
231
|
+
};
|
|
232
|
+
/** 复读校验:listBundles 可用必须见目标在装;不可用(老管理器)至少要求官方宣称产出 bundle。 */
|
|
233
|
+
const verifyAndBundle = async (change) => {
|
|
234
|
+
const bundleName = change.bundle ?? pkg;
|
|
235
|
+
if (typeof service.listBundles === 'function') {
|
|
236
|
+
const bundles = await service.listBundles();
|
|
237
|
+
const found = (bundles ?? []).find((b) => b && b.name === bundleName);
|
|
238
|
+
if (!found || found.installed === false) {
|
|
239
|
+
throw new DesktopOpsError('verify-failed', `安装复读失败(${pkg}):官方结果为 ${change.application ?? 'unknown'} 但 listBundles 未見目标 bundle 在装——不冒充安装成功`);
|
|
240
|
+
}
|
|
214
241
|
}
|
|
242
|
+
else if (!change.bundle) {
|
|
243
|
+
throw new DesktopOpsError('verify-failed', `安装复读不可用(${pkg}):官方结果未携带 bundle 字段且 listBundles 缺席——不冒充安装成功`);
|
|
244
|
+
}
|
|
245
|
+
return { change, bundleName };
|
|
246
|
+
};
|
|
247
|
+
let change = await installOnce();
|
|
248
|
+
// 本地增强:enable 阶段失败自动重试一次(受限单次 + 短退避;与高频装卸/插件树重载的竞速重试即愈;
|
|
249
|
+
// cancelled / build-blocked / release-age 等其他失败形态不重试)
|
|
250
|
+
if (change.stage === 'enable' && (change.application === 'failed' || change.error)) {
|
|
251
|
+
if (enableRetryDelayMs > 0)
|
|
252
|
+
await sleepMs(enableRetryDelayMs);
|
|
253
|
+
change = await installOnce();
|
|
215
254
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
return { change, bundleName };
|
|
255
|
+
if (change.application === 'failed' || change.application === 'cancelled' || change.error)
|
|
256
|
+
mapFailure(change);
|
|
257
|
+
return verifyAndBundle(change);
|
|
220
258
|
}
|
|
221
259
|
/**
|
|
222
260
|
* Desktop 卸载(0.9.8,dsh-market 同策略):removeBundle 委派 + ChangeResult 判定 +
|
package/lib/core/registry.js
CHANGED
|
@@ -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 { WEB_PROFILE, cacheRoot } from './env.js';
|
|
12
12
|
import { decodeUtf8Fatal, fetchJsonLimitedMeta, describeFetchFailure } from './httpx.js';
|
|
13
|
-
export const CATEGORIES = ['
|
|
13
|
+
export const CATEGORIES = ['essentials', 'cui-picks', 'self-dev', 'tencent-lighthouse', 'watchlist'];
|
|
14
14
|
/** 容量上限:2 MiB 是原始 UTF-8 bytes(不是字符数);条目超限拒绝整份清单。 */
|
|
15
15
|
export const MAX_REGISTRY_BYTES = 2 * 1024 * 1024;
|
|
16
16
|
export const MAX_PLUGINS = 1000;
|
|
@@ -124,7 +124,7 @@ export function parseRegistryAddress(raw) {
|
|
|
124
124
|
}
|
|
125
125
|
// ---------- 严格 v1 校验 ----------
|
|
126
126
|
const TOP_LEVEL_KEYS = new Set(['version', 'plugins']);
|
|
127
|
-
const ENTRY_KEYS = new Set(['id', 'name', 'description', 'category', 'tags', 'source', 'npm', 'github', 'homepage', 'icon', 'verified']);
|
|
127
|
+
const ENTRY_KEYS = new Set(['id', 'name', 'description', 'category', 'tags', 'source', 'npm', 'github', 'homepage', 'icon', 'verified', 'alsoCategories']);
|
|
128
128
|
/** 精确 semver 判定(与 versions.ts EXACT_VERSION_RE 同语义;接受 prerelease/build,拒绝 range/前缀)。 */
|
|
129
129
|
const EXACT_SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
130
130
|
function semverParse(value) {
|
|
@@ -307,6 +307,37 @@ export function validateRegistry(raw) {
|
|
|
307
307
|
verified = list;
|
|
308
308
|
}
|
|
309
309
|
}
|
|
310
|
+
// alsoCategories(0.9.17):可选次级策展桶——值 ∈ CATEGORIES、不含主 category、去重;
|
|
311
|
+
// 空数组 = 无次级归属(不产生键)
|
|
312
|
+
let alsoCategories;
|
|
313
|
+
if (e.alsoCategories !== undefined) {
|
|
314
|
+
if (!Array.isArray(e.alsoCategories)) {
|
|
315
|
+
errors.push(`${where}.alsoCategories: 必须是字符串数组`);
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
const list = [];
|
|
319
|
+
const seenAlso = new Set();
|
|
320
|
+
e.alsoCategories.forEach((c, ci) => {
|
|
321
|
+
const cs = typeof c === 'string' ? c : '';
|
|
322
|
+
if (!CATEGORIES.includes(cs)) {
|
|
323
|
+
errors.push(`${where}.alsoCategories[${ci}]: 无效 ${JSON.stringify(c)}(须为策展桶)`);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
if (cs === category) {
|
|
327
|
+
errors.push(`${where}.alsoCategories[${ci}]: 与主 category 重复 ${cs}`);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
if (seenAlso.has(cs)) {
|
|
331
|
+
errors.push(`${where}.alsoCategories[${ci}]: 重复 ${cs}`);
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
seenAlso.add(cs);
|
|
335
|
+
list.push(cs);
|
|
336
|
+
});
|
|
337
|
+
if (list.length > 0)
|
|
338
|
+
alsoCategories = list;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
310
341
|
plugins.push({
|
|
311
342
|
id,
|
|
312
343
|
name,
|
|
@@ -319,6 +350,7 @@ export function validateRegistry(raw) {
|
|
|
319
350
|
...(entryHomepage !== undefined ? { homepage: entryHomepage } : {}),
|
|
320
351
|
...(entryIcon !== undefined ? { icon: entryIcon } : {}),
|
|
321
352
|
...(verified !== undefined ? { verified } : {}),
|
|
353
|
+
...(alsoCategories !== undefined ? { alsoCategories } : {}),
|
|
322
354
|
});
|
|
323
355
|
});
|
|
324
356
|
return { ok: errors.length === 0, errors, registry: errors.length === 0 ? { version: 1, plugins } : null };
|
package/lib/tools.js
CHANGED
|
@@ -7,11 +7,11 @@ import { togglePlugin as coreTogglePlugin } from './core/toggle.js';
|
|
|
7
7
|
import { resolveActiveProfile, assertWriteAllowed } from './core/active-profile.js';
|
|
8
8
|
import { desktopInstallFromRegistry, desktopToggle, desktopUninstall, desktopUpgradeFromRegistry } from './core/profile-ops.js';
|
|
9
9
|
export const CATEGORY_LABELS = {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
essentials: '装机必备',
|
|
11
|
+
'cui-picks': '崔添翼精选',
|
|
12
|
+
'self-dev': 'iasi自研',
|
|
13
|
+
'tencent-lighthouse': '腾讯轻量云专区',
|
|
14
|
+
watchlist: '观察区',
|
|
15
15
|
};
|
|
16
16
|
/** 分类中文标签(0.7.0 Task 5):精选 5 类 + 社区已知标签(单一事实源 COMMUNITY_CATEGORY_LABELS);未知 slug 原样。 */
|
|
17
17
|
function categoryLabelOf(category) {
|
package/package.json
CHANGED
package/registry.json
CHANGED
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
"id": "dsh-m",
|
|
6
6
|
"name": "DSH Marketplace",
|
|
7
7
|
"description": "自定义清单的 DSH 插件市场,收录/安装/卸载/升级;已适配 DSH 0.2.0-rc.2,支持 web 与 desktop 双 profile,详见仓库。",
|
|
8
|
-
"category": "
|
|
8
|
+
"category": "essentials",
|
|
9
|
+
"alsoCategories": [
|
|
10
|
+
"self-dev"
|
|
11
|
+
],
|
|
9
12
|
"tags": [
|
|
10
13
|
"市场",
|
|
11
14
|
"插件管理"
|
|
@@ -24,15 +27,81 @@
|
|
|
24
27
|
"github": "iasiv5/dsh-m",
|
|
25
28
|
"homepage": "https://github.com/iasiv5/dsh-m"
|
|
26
29
|
},
|
|
30
|
+
{
|
|
31
|
+
"id": "dsh-better-sidebar",
|
|
32
|
+
"name": "DSH Better Sidebar",
|
|
33
|
+
"description": "开放的侧边栏底座,支持三方拓展注册侧边栏页面;内置文件编辑、终端、侧边对话、Git 与子代理页。",
|
|
34
|
+
"category": "essentials",
|
|
35
|
+
"alsoCategories": [
|
|
36
|
+
"cui-picks"
|
|
37
|
+
],
|
|
38
|
+
"tags": [
|
|
39
|
+
"侧边栏",
|
|
40
|
+
"文件编辑",
|
|
41
|
+
"终端",
|
|
42
|
+
"Git"
|
|
43
|
+
],
|
|
44
|
+
"source": "npm",
|
|
45
|
+
"npm": "dsh-better-sidebar",
|
|
46
|
+
"github": "omdsh-dev/DSH-better-sidebar",
|
|
47
|
+
"homepage": "https://github.com/omdsh-dev/DSH-better-sidebar"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"id": "modsearch",
|
|
51
|
+
"name": "ModSearch",
|
|
52
|
+
"description": "免费网页搜索、X 搜索与页面抓取插件,支持 Firecrawl、Tavily、Exa 等引擎自动故障切换。",
|
|
53
|
+
"category": "essentials",
|
|
54
|
+
"tags": [
|
|
55
|
+
"搜索",
|
|
56
|
+
"网页抓取",
|
|
57
|
+
"X"
|
|
58
|
+
],
|
|
59
|
+
"source": "npm",
|
|
60
|
+
"npm": "@liustack/modsearch",
|
|
61
|
+
"github": "liustack/modsearch",
|
|
62
|
+
"homepage": "https://github.com/liustack/modsearch"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"id": "dsh-tui",
|
|
66
|
+
"name": "DSH TUI",
|
|
67
|
+
"description": "终端 TUI 客户端:把 DeepSeek Harness 会话搬进终端,基于 DSH 客户端契约远程连接使用。",
|
|
68
|
+
"category": "cui-picks",
|
|
69
|
+
"tags": [
|
|
70
|
+
"终端",
|
|
71
|
+
"TUI",
|
|
72
|
+
"远程"
|
|
73
|
+
],
|
|
74
|
+
"source": "npm",
|
|
75
|
+
"npm": "dsh-tui",
|
|
76
|
+
"github": "MashedPotato817/dsh-tui",
|
|
77
|
+
"homepage": "https://github.com/MashedPotato817/dsh-tui"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"id": "dsh-context",
|
|
81
|
+
"name": "DSH Context",
|
|
82
|
+
"description": "上下文透视:Context 仪表盘、侧栏面板与 /context 命令,查看上下文组成、演进趋势与压缩剪枝等事件,支持跨会话用量统计。",
|
|
83
|
+
"category": "cui-picks",
|
|
84
|
+
"tags": [
|
|
85
|
+
"上下文",
|
|
86
|
+
"Token 统计",
|
|
87
|
+
"可视化",
|
|
88
|
+
"仪表盘"
|
|
89
|
+
],
|
|
90
|
+
"source": "npm",
|
|
91
|
+
"npm": "dsh-context",
|
|
92
|
+
"github": "bowenliang123/dsh-context",
|
|
93
|
+
"homepage": "https://github.com/bowenliang123/dsh-context"
|
|
94
|
+
},
|
|
27
95
|
{
|
|
28
96
|
"id": "dsh-skins",
|
|
29
97
|
"name": "DSH Skins",
|
|
30
98
|
"description": "DSH Web 皮肤插件:多套品牌皮肤侧栏热切换,一键回到官方界面。已适配 0.1.7-rc.2 与 0.2.0-rc.1/rc.2,详见仓库。",
|
|
31
|
-
"category": "
|
|
99
|
+
"category": "self-dev",
|
|
32
100
|
"tags": [
|
|
33
101
|
"皮肤",
|
|
34
102
|
"主题",
|
|
35
|
-
"美化"
|
|
103
|
+
"美化",
|
|
104
|
+
"界面"
|
|
36
105
|
],
|
|
37
106
|
"verified": [
|
|
38
107
|
"0.1.2-rc.1",
|
|
@@ -49,11 +118,12 @@
|
|
|
49
118
|
"id": "dsh-copilot-auth",
|
|
50
119
|
"name": "DSH Copilot Auth",
|
|
51
120
|
"description": "为内置 Copilot 提供方补登录/注销设置页与开箱路由;模型目录兜底不覆盖定制。适配 0.1.7-rc.2 与 0.2.0-rc.1,旧版用 1.1.x。",
|
|
52
|
-
"category": "
|
|
121
|
+
"category": "self-dev",
|
|
53
122
|
"tags": [
|
|
54
123
|
"Copilot",
|
|
55
124
|
"认证",
|
|
56
|
-
"模型接入"
|
|
125
|
+
"模型接入",
|
|
126
|
+
"工具"
|
|
57
127
|
],
|
|
58
128
|
"verified": [
|
|
59
129
|
"0.1.7-rc.2",
|
|
@@ -68,11 +138,12 @@
|
|
|
68
138
|
"id": "dsh-skip-browser-auth",
|
|
69
139
|
"name": "DSH Skip Browser Auth",
|
|
70
140
|
"description": "替换官方 Connection 跳过 BrowserAuth,免 Token;已适配 0.1.2-rc.1、0.1.5-rc.1/rc.2、0.1.7-rc.1/rc.2 与 0.2.0-rc.1/rc.2。",
|
|
71
|
-
"category": "
|
|
141
|
+
"category": "self-dev",
|
|
72
142
|
"tags": [
|
|
73
143
|
"BrowserAuth",
|
|
74
144
|
"连接",
|
|
75
|
-
"安全"
|
|
145
|
+
"安全",
|
|
146
|
+
"工具"
|
|
76
147
|
],
|
|
77
148
|
"verified": [
|
|
78
149
|
"0.1.2-rc.1",
|
|
@@ -92,7 +163,7 @@
|
|
|
92
163
|
"id": "dsh-surf",
|
|
93
164
|
"name": "DSH Surf",
|
|
94
165
|
"description": "网络冲浪入口:设置一级菜单一键打开自托管 Selkies/Chrome 远程桌面,视频流直连;需自建容器并挂认证反代。",
|
|
95
|
-
"category": "
|
|
166
|
+
"category": "self-dev",
|
|
96
167
|
"tags": [
|
|
97
168
|
"浏览器",
|
|
98
169
|
"远程桌面",
|
|
@@ -111,7 +182,7 @@
|
|
|
111
182
|
"id": "dsh-obmc-web",
|
|
112
183
|
"name": "DSH OBMC Web",
|
|
113
184
|
"description": "在 DSH 里打开 OpenBMC 控制台:同源代理走 SSH 隧道,设置面板一键开启,支持 IP 探测与隧道重定向;需 SSH 跳板可达 BMC。",
|
|
114
|
-
"category": "
|
|
185
|
+
"category": "self-dev",
|
|
115
186
|
"tags": [
|
|
116
187
|
"OpenBMC",
|
|
117
188
|
"远程管理",
|
|
@@ -131,11 +202,12 @@
|
|
|
131
202
|
"id": "dsh-onetree-log",
|
|
132
203
|
"name": "OneTree Log",
|
|
133
204
|
"description": "OneTree/dreport 日志包上传解包,14 域清单与预览;可登记服务器本地路径;已适配 0.1.7-rc.2,详见仓库。",
|
|
134
|
-
"category": "
|
|
205
|
+
"category": "self-dev",
|
|
135
206
|
"tags": [
|
|
136
207
|
"日志",
|
|
137
208
|
"OneTree",
|
|
138
|
-
"诊断"
|
|
209
|
+
"诊断",
|
|
210
|
+
"工具"
|
|
139
211
|
],
|
|
140
212
|
"verified": [
|
|
141
213
|
"0.1.7-rc.2"
|
|
@@ -149,7 +221,7 @@
|
|
|
149
221
|
"id": "dsh-quota-watch",
|
|
150
222
|
"name": "DSH Quota Watch",
|
|
151
223
|
"description": "GLM 与 Copilot 额度悬浮球:胶囊、详情面板与侧栏卡片,宿主解析凭据,适配 web 与 desktop。",
|
|
152
|
-
"category": "
|
|
224
|
+
"category": "self-dev",
|
|
153
225
|
"tags": [
|
|
154
226
|
"配额",
|
|
155
227
|
"悬浮球",
|
|
@@ -166,56 +238,56 @@
|
|
|
166
238
|
"homepage": "https://github.com/iasiv5/dsh-quota-watch"
|
|
167
239
|
},
|
|
168
240
|
{
|
|
169
|
-
"id": "dsh-
|
|
170
|
-
"name": "DSH
|
|
171
|
-
"description": "
|
|
172
|
-
"category": "
|
|
241
|
+
"id": "dsh-auth",
|
|
242
|
+
"name": "DSH Auth",
|
|
243
|
+
"description": "DSH Web 管理员认证:Argon2id 口令与可撤销会话,经 Caddy forward_auth 保护页面、API、下载、SSE 与 WebSocket。",
|
|
244
|
+
"category": "tencent-lighthouse",
|
|
173
245
|
"tags": [
|
|
174
|
-
"
|
|
175
|
-
"
|
|
176
|
-
"
|
|
177
|
-
"Git"
|
|
246
|
+
"认证",
|
|
247
|
+
"安全",
|
|
248
|
+
"工具"
|
|
178
249
|
],
|
|
179
250
|
"source": "npm",
|
|
180
|
-
"npm": "dsh-
|
|
181
|
-
"github": "
|
|
182
|
-
"homepage": "https://github.com/
|
|
251
|
+
"npm": "dsh-auth",
|
|
252
|
+
"github": "hxy91819/dsh-auth",
|
|
253
|
+
"homepage": "https://github.com/hxy91819/dsh-auth"
|
|
183
254
|
},
|
|
184
255
|
{
|
|
185
|
-
"id": "
|
|
186
|
-
"name": "
|
|
187
|
-
"description": "
|
|
188
|
-
"category": "
|
|
256
|
+
"id": "browser-skill-dsh-plugin",
|
|
257
|
+
"name": "BrowserSkill",
|
|
258
|
+
"description": "浏览器自动化:把 BrowserSkill 的 browser_* 工具暴露给模型,让模型在对话中操作浏览器,详见仓库。",
|
|
259
|
+
"category": "tencent-lighthouse",
|
|
189
260
|
"tags": [
|
|
190
|
-
"
|
|
191
|
-
"
|
|
192
|
-
"
|
|
261
|
+
"浏览器",
|
|
262
|
+
"自动化",
|
|
263
|
+
"工具调用"
|
|
193
264
|
],
|
|
194
265
|
"source": "npm",
|
|
195
|
-
"npm": "@
|
|
196
|
-
"github": "
|
|
197
|
-
"homepage": "https://github.com/
|
|
266
|
+
"npm": "@wxg-prc-cpg/browser-skill-dsh-plugin",
|
|
267
|
+
"github": "Tencent/BrowserSkill",
|
|
268
|
+
"homepage": "https://github.com/Tencent/BrowserSkill/tree/main/packages/dsh-plugin-browserskill#readme"
|
|
198
269
|
},
|
|
199
270
|
{
|
|
200
|
-
"id": "dsh-
|
|
201
|
-
"name": "
|
|
202
|
-
"description": "
|
|
203
|
-
"category": "
|
|
271
|
+
"id": "dsh-weknora",
|
|
272
|
+
"name": "WeKnora",
|
|
273
|
+
"description": "知识库检索:把 WeKnora 接入模型,提供语义搜索、文档阅读与 RAG 问答等工具,可用自有知识库,详见仓库。",
|
|
274
|
+
"category": "tencent-lighthouse",
|
|
204
275
|
"tags": [
|
|
205
|
-
"
|
|
206
|
-
"
|
|
207
|
-
"
|
|
276
|
+
"知识库",
|
|
277
|
+
"RAG",
|
|
278
|
+
"检索",
|
|
279
|
+
"工具"
|
|
208
280
|
],
|
|
209
281
|
"source": "npm",
|
|
210
|
-
"npm": "dsh-
|
|
211
|
-
"github": "
|
|
212
|
-
"homepage": "https://github.com/
|
|
282
|
+
"npm": "@wxg-prc-cpg/dsh-weknora",
|
|
283
|
+
"github": "Tencent/WeKnora",
|
|
284
|
+
"homepage": "https://github.com/Tencent/WeKnora/tree/main/packages/dsh-weknora"
|
|
213
285
|
},
|
|
214
286
|
{
|
|
215
287
|
"id": "dsh-server-deck",
|
|
216
288
|
"name": "DSH Server Deck",
|
|
217
289
|
"description": "服务器卡片仪表盘:实时 CPU/内存/磁盘与趋势图(1h~30d),点卡片进 xterm 终端;可选集成 better-sidebar。",
|
|
218
|
-
"category": "
|
|
290
|
+
"category": "watchlist",
|
|
219
291
|
"tags": [
|
|
220
292
|
"服务器",
|
|
221
293
|
"监控",
|
|
@@ -231,137 +303,23 @@
|
|
|
231
303
|
"id": "dsh-flowglass",
|
|
232
304
|
"name": "DSH Flowglass",
|
|
233
305
|
"description": "流镜:把当前会话画成三列泳道流程图,展示工具调用、并行分组与子代理分支;可选集成 better-sidebar。",
|
|
234
|
-
"category": "
|
|
306
|
+
"category": "watchlist",
|
|
235
307
|
"tags": [
|
|
236
308
|
"可视化",
|
|
237
309
|
"流程图",
|
|
238
|
-
"会话"
|
|
310
|
+
"会话",
|
|
311
|
+
"界面"
|
|
239
312
|
],
|
|
240
313
|
"source": "npm",
|
|
241
314
|
"npm": "dsh-flowglass",
|
|
242
315
|
"github": "Iwctwbh/dsh-flowglass",
|
|
243
316
|
"homepage": "https://github.com/Iwctwbh/dsh-flowglass"
|
|
244
317
|
},
|
|
245
|
-
{
|
|
246
|
-
"id": "dsh-auth",
|
|
247
|
-
"name": "DSH Auth",
|
|
248
|
-
"description": "DSH Web 管理员认证:Argon2id 口令与可撤销会话,经 Caddy forward_auth 保护页面、API、下载、SSE 与 WebSocket。",
|
|
249
|
-
"category": "tools",
|
|
250
|
-
"tags": [
|
|
251
|
-
"认证",
|
|
252
|
-
"安全"
|
|
253
|
-
],
|
|
254
|
-
"source": "npm",
|
|
255
|
-
"npm": "dsh-auth",
|
|
256
|
-
"github": "hxy91819/dsh-auth",
|
|
257
|
-
"homepage": "https://github.com/hxy91819/dsh-auth"
|
|
258
|
-
},
|
|
259
|
-
{
|
|
260
|
-
"id": "deepseek-harness-lark",
|
|
261
|
-
"name": "DeepSeek Harness Lark",
|
|
262
|
-
"description": "飞书 / Lark 通道插件:把飞书消息桥接进 DeepSeek Harness 对话,支持文本与图片收发。",
|
|
263
|
-
"category": "other",
|
|
264
|
-
"tags": [
|
|
265
|
-
"飞书",
|
|
266
|
-
"Lark",
|
|
267
|
-
"通道"
|
|
268
|
-
],
|
|
269
|
-
"source": "npm",
|
|
270
|
-
"npm": "deepseek-harness-lark",
|
|
271
|
-
"github": "sliverp/DeepSeek-harness-lark",
|
|
272
|
-
"homepage": "https://github.com/sliverp/DeepSeek-harness-lark"
|
|
273
|
-
},
|
|
274
|
-
{
|
|
275
|
-
"id": "deepseek-harness-qqbot",
|
|
276
|
-
"name": "DeepSeek Harness QQBot",
|
|
277
|
-
"description": "QQ Bot 通道插件:把 QQ 机器人消息桥接进 DeepSeek Harness 对话,支持文本与图片收发。",
|
|
278
|
-
"category": "other",
|
|
279
|
-
"tags": [
|
|
280
|
-
"QQ",
|
|
281
|
-
"通道"
|
|
282
|
-
],
|
|
283
|
-
"source": "npm",
|
|
284
|
-
"npm": "deepseek-harness-qqbot",
|
|
285
|
-
"github": "sliverp/DeepSeek-harness-qqbot",
|
|
286
|
-
"homepage": "https://github.com/sliverp/DeepSeek-harness-qqbot"
|
|
287
|
-
},
|
|
288
|
-
{
|
|
289
|
-
"id": "deepseek-harness-weixin",
|
|
290
|
-
"name": "DeepSeek Harness Weixin",
|
|
291
|
-
"description": "微信通道插件:扫码登录,收发文本与图片消息,桥接进 DeepSeek Harness 对话。",
|
|
292
|
-
"category": "other",
|
|
293
|
-
"tags": [
|
|
294
|
-
"微信",
|
|
295
|
-
"通道"
|
|
296
|
-
],
|
|
297
|
-
"source": "npm",
|
|
298
|
-
"npm": "deepseek-harness-weixin",
|
|
299
|
-
"github": "sliverp/DeepSeek-harness-weixin",
|
|
300
|
-
"homepage": "https://github.com/sliverp/DeepSeek-harness-weixin"
|
|
301
|
-
},
|
|
302
|
-
{
|
|
303
|
-
"id": "deepseek-harness-wecom",
|
|
304
|
-
"name": "DeepSeek Harness WeCom",
|
|
305
|
-
"description": "企业微信 AI Bot 通道插件:把企业微信消息桥接进 DeepSeek Harness 对话,支持文本与图片收发。",
|
|
306
|
-
"category": "other",
|
|
307
|
-
"tags": [
|
|
308
|
-
"企业微信",
|
|
309
|
-
"通道"
|
|
310
|
-
],
|
|
311
|
-
"source": "npm",
|
|
312
|
-
"npm": "deepseek-harness-wecom",
|
|
313
|
-
"github": "sliverp/DeepSeek-harness-wecom",
|
|
314
|
-
"homepage": "https://github.com/sliverp/DeepSeek-harness-wecom"
|
|
315
|
-
},
|
|
316
|
-
{
|
|
317
|
-
"id": "deepseek-harness-dingtalk",
|
|
318
|
-
"name": "DeepSeek Harness DingTalk",
|
|
319
|
-
"description": "钉钉 Stream 通道插件:把钉钉消息桥接进 DeepSeek Harness 对话,支持文本与图片收发。",
|
|
320
|
-
"category": "other",
|
|
321
|
-
"tags": [
|
|
322
|
-
"钉钉",
|
|
323
|
-
"通道"
|
|
324
|
-
],
|
|
325
|
-
"source": "npm",
|
|
326
|
-
"npm": "deepseek-harness-dingtalk",
|
|
327
|
-
"github": "sliverp/DeepSeek-harness-dingtalk",
|
|
328
|
-
"homepage": "https://github.com/sliverp/DeepSeek-harness-dingtalk"
|
|
329
|
-
},
|
|
330
|
-
{
|
|
331
|
-
"id": "browser-skill-dsh-plugin",
|
|
332
|
-
"name": "BrowserSkill",
|
|
333
|
-
"description": "浏览器自动化:把 BrowserSkill 的 browser_* 工具暴露给模型,让模型在对话中操作浏览器,详见仓库。",
|
|
334
|
-
"category": "tools",
|
|
335
|
-
"tags": [
|
|
336
|
-
"浏览器",
|
|
337
|
-
"自动化",
|
|
338
|
-
"工具调用"
|
|
339
|
-
],
|
|
340
|
-
"source": "npm",
|
|
341
|
-
"npm": "@wxg-prc-cpg/browser-skill-dsh-plugin",
|
|
342
|
-
"github": "Tencent/BrowserSkill",
|
|
343
|
-
"homepage": "https://github.com/Tencent/BrowserSkill/tree/main/packages/dsh-plugin-browserskill#readme"
|
|
344
|
-
},
|
|
345
|
-
{
|
|
346
|
-
"id": "dsh-weknora",
|
|
347
|
-
"name": "WeKnora",
|
|
348
|
-
"description": "知识库检索:把 WeKnora 接入模型,提供语义搜索、文档阅读与 RAG 问答等工具,可用自有知识库,详见仓库。",
|
|
349
|
-
"category": "tools",
|
|
350
|
-
"tags": [
|
|
351
|
-
"知识库",
|
|
352
|
-
"RAG",
|
|
353
|
-
"检索"
|
|
354
|
-
],
|
|
355
|
-
"source": "npm",
|
|
356
|
-
"npm": "@wxg-prc-cpg/dsh-weknora",
|
|
357
|
-
"github": "Tencent/WeKnora",
|
|
358
|
-
"homepage": "https://github.com/Tencent/WeKnora/tree/main/packages/dsh-weknora"
|
|
359
|
-
},
|
|
360
318
|
{
|
|
361
319
|
"id": "dsh-chat-manager",
|
|
362
320
|
"name": "DSH Chat Manager",
|
|
363
321
|
"description": "会话管理器:在原生侧边栏搜索归档聊天、恢复会话并安全永久删除;需按内核版本选装,详见仓库。",
|
|
364
|
-
"category": "
|
|
322
|
+
"category": "watchlist",
|
|
365
323
|
"tags": [
|
|
366
324
|
"会话管理",
|
|
367
325
|
"归档",
|
|
@@ -374,20 +332,20 @@
|
|
|
374
332
|
"homepage": "https://github.com/WSL043/dsh-chat-manager"
|
|
375
333
|
},
|
|
376
334
|
{
|
|
377
|
-
"id": "dsh-
|
|
378
|
-
"name": "DSH
|
|
379
|
-
"description": "
|
|
380
|
-
"category": "
|
|
335
|
+
"id": "dsh-docs-panel",
|
|
336
|
+
"name": "DSH Docs Panel",
|
|
337
|
+
"description": "侧边栏「全局文档」:全局 Markdown 笔记任何工作区可读,支持大纲跳转与一键转编辑器;需 better-sidebar ≥0.4.0。",
|
|
338
|
+
"category": "watchlist",
|
|
381
339
|
"tags": [
|
|
382
|
-
"
|
|
383
|
-
"
|
|
384
|
-
"
|
|
385
|
-
"
|
|
340
|
+
"文档",
|
|
341
|
+
"Markdown",
|
|
342
|
+
"侧边栏",
|
|
343
|
+
"界面"
|
|
386
344
|
],
|
|
387
345
|
"source": "npm",
|
|
388
|
-
"npm": "dsh-
|
|
389
|
-
"github": "
|
|
390
|
-
"homepage": "https://github.com/
|
|
346
|
+
"npm": "dsh-docs-panel",
|
|
347
|
+
"github": "mlosun/dsh-docs-panel",
|
|
348
|
+
"homepage": "https://github.com/mlosun/dsh-docs-panel"
|
|
391
349
|
}
|
|
392
350
|
]
|
|
393
351
|
}
|