dsh-m 0.0.3 → 0.1.1

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/DESIGN.md ADDED
@@ -0,0 +1,140 @@
1
+ # dsh-m v1 设计共识(grilling 定稿)
2
+
3
+ > 2026-09-04 与 owner 三轮对齐的完整共识。实现以本文档为准;与本文冲突的实现细节以本文档为准。
4
+
5
+ ## 1. 定位与形态
6
+
7
+ - **dsh-m** = 个人自用的 DSH(DeepSeek Harness)插件市场。
8
+ - 形态:**一个 DSH web 插件**(旗舰是 Web GUI)+ **7 个 agent 工具** + **薄 CLI `dshm`**,全部在同一个 npm 包里,不搞 monorepo。
9
+ - npm 包名 `dsh-m`(已占位发布 0.0.x),插件 id `dsh-m`,显示名 **DSH Marketplace**。
10
+ - 管理对象:**只管 DSH 插件**,不管 Agent Skills(skills 归 skillhub)。
11
+ - 与 `@cocofhu/skillhub` **完全独立并存**:不读不写它的数据与配置,仅在实现机制上借鉴(其源码镜像见 §10)。
12
+ - 单机自用:无服务端、无账号、无提交入口;收录变更 = 改本仓库的 registry(他人可发 PR)。
13
+
14
+ ## 2. Registry(收录清单)
15
+
16
+ ### 2.1 载体与分发
17
+ - repo 内**单文件 `registry.json`**,手工 curated,版本号**不写死**(运行时实查 npm/GitHub)。
18
+ - 运行时获取:**jsDelivr / raw.githubusercontent 读 `@main`**,本地 **TTL 1h 缓存**;npm 包内附带一份快照作离线兜底。收录更新与插件发版**解耦**。
19
+ - 设置页提供 **registry 源地址覆盖**(可指向 raw / 本地文件,用于调试与预览未合并条目)。
20
+
21
+ ### 2.2 Schema(v1 定稿)
22
+ ```jsonc
23
+ {
24
+ "version": 1,
25
+ "plugins": [
26
+ {
27
+ "id": "dsh-skins", // slug,唯一
28
+ "name": "DSH Skins",
29
+ "description": "中文描述", // v1 只有中文
30
+ "category": "ui", // market|tools|ui|search|media|other 六选一
31
+ "tags": ["主题"],
32
+ "source": "github", // npm|github
33
+ "npm": "可选;source=npm 时必填",
34
+ "github": "owner/repo", // source=github 时必填;npm 条目也可附
35
+ "homepage": "https://...",
36
+ "icon": "可选;覆盖自动头像"
37
+ }
38
+ ]
39
+ }
40
+ ```
41
+
42
+ ### 2.3 CI 校验(`.github/workflows/registry.yml`)
43
+ PR / push 到 main 时校验:
44
+ 1. schema 合法(字段、枚举、source 与 npm/github 的必填联动);
45
+ 2. npm 条目 `npm view <pkg> version` 可查;GitHub 条目 repo 存在;
46
+ 3. 无重复 `id`;
47
+ 4. `icon` / `homepage` URL 可达(icon 允许为空)。
48
+
49
+ ## 3. 安装 / 卸载 / 升级 / 重启
50
+
51
+ 底层原语(本机实证):`dsh plugin --profile web add|remove|update`(转发 pnpm,作用于 `$DSH_HOME/profiles/web`)。**profile 的 `package.json` 就是唯一事实源**——不引入任何额外状态文件。
52
+
53
+ - **安装(npm 源)**:装最新版并以**精确版本锁定**(不用 `^` 范围);安装后校验 tarball `integrity`(对照 `npm view dist.integrity`)。
54
+ - **安装(GitHub 源)**:解析并**锁定 commit SHA**(`github:owner/repo#sha`),skillhub 同款。
55
+ - **已装识别**:读 profile `package.json` dependencies,与 registry 匹配 → 标注「市场安装」;不匹配的也列出,标注「非市场安装 / 来源未知」。卸载/升级对两类都可用。
56
+ - **卸载**:live-disable(先让 client bundle 下线,避免 404)→ `dsh plugin remove`。**不清理插件产生的数据/配置**,但把检测到的疑似残留路径(如 `~/.dsh/<plugin>.json`)列出报告。
57
+ - **升级**:**按需检查**(`dshm_outdated` / `dshm_list` 时实时比对本地版本 vs npm latest / GitHub main),半自动——展示升级计划,确认后执行。**不做后台定时器**。
58
+ - **自更新**:dsh-m 对自己同样做版本比对 + 提示升级(设置页呈现)。
59
+ - **重启**:内置**一键重启**,复用 skillhub 验证过的重启路径(本机 `dsh-web.service` 是转发 shim,不新建 systemd 单元、不监听 3080)。安装/卸载/升级完成后 GUI 弹「需重启生效 [一键重启]」横幅,工具返回重启提示。
60
+ - **安全基线(5 条)**:
61
+ 1. 所有拉取仅 HTTPS + 响应大小上限 + 超时;
62
+ 2. npm 安装校验 integrity;
63
+ 3. GitHub 安装强制 pinned SHA;
64
+ 4. pnpm 构建脚本被拦时,沿用 skillhub 的 `dangerouslyAllowAllBuilds` 重试,但**必须明确报告**「该插件需要执行构建脚本」;
65
+ 5. 不做签名/验签体系(自用,明确不做)。
66
+
67
+ ## 4. GUI(旗舰,v1 必须做好)
68
+
69
+ 3 个视图,卡片展开式详情(不做独立详情页),**中文优先**,跟随 DSH Web 深色主题:
70
+
71
+ 1. **市场页**(默认):registry 卡片流;顶部关键词搜索 + 分类筛选 + 标签;卡片 = 图标 / 名称 / 描述 / 来源徽标 / 最新版本;按钮「安装」;点卡片展开详情。
72
+ 2. **已装页**:profile 实际安装列表,标注来源(市场/非市场/未知);「可升级 → x.y.z」徽标 +「升级」;「卸载」。
73
+ 3. **设置页**:缓存状态 + 手动刷新(显示 registry commit/时间);registry 源覆盖;dsh-m 自更新提示与升级。
74
+
75
+ 技术:`src/client.js` 经 **esbuild** 打包为 `lib/client.js`;`window.__ModuleLoader__.load({ id: "dsh-m", factory })` 注册;**React 从 module loader require**(零额外运行时依赖);manifest 注入 `@deepseek-ai/dsh-client-runtime`、`@deepseek-ai/dsh-client-ui-slots`、`@deepseek-ai/dsh-client-ui-settings`。
76
+ 图标:GitHub 来源自动用 `https://github.com/<owner>.png?size=64`;`icon` 字段可覆盖;npm-only 条目首字母色块回退。
77
+
78
+ ## 5. Agent 工具(host)与 CLI
79
+
80
+ - 工具前缀 `dshm_`,共 7 个:`dshm_search` / `dshm_list` / `dshm_install` / `dshm_uninstall` / `dshm_outdated` / `dshm_upgrade` / `dshm_restart`。
81
+ - CLI bin `dshm`:同名同义命令集(`dshm list|search|install|uninstall|outdated|upgrade|restart`)。
82
+ - 实现顺序:**GUI 先行,CLI 收尾**(核心逻辑同一层,CLI 是薄封装)。
83
+
84
+ ## 6. 仓库与发版
85
+
86
+ - 单包结构;现有 `.github/workflows/publish.yml`(tag `v*` → OIDC trusted publishing)**一字不改**。
87
+ - 发版 = `npm version patch|minor|major && git push --tags`。
88
+ - npm 包 `dsh-m` 的 `files` 需覆盖:`lib/`(host+client 产物)、`registry.json`(离线快照)、`bin/`。
89
+
90
+ ## 7. 首批收录
91
+
92
+ - `dsh-skins`(github 源)、`dsh-web-search`(实现时核实其 npm/GitHub 身份后录入)。
93
+ - **skillhub 不进首批**。
94
+ - 核心 `deepseek-harness-*` 包永不收录。
95
+
96
+ ## 8. 里程碑(GUI 先行)
97
+
98
+ - **M0 脚手架**:单包结构(TS + esbuild)、manifest(`dsh.client` + `cordis.patch.yml` + `dsh.bundle.patch`)、构建脚本。
99
+ - **M1 core 层**:registry 拉取/缓存/覆盖、profile 读取、install/uninstall/upgrade/outdated 封装(spawn `dsh` CLI,非 shell 拼接,超时 + SIGTERM,保留末 256KB 输出)。
100
+ - **M2 GUI**:3 视图 + 卡片展开 + 重启横幅。
101
+ - **M3 host 工具**:7 个 `dshm_*` + cordis patch 注册。
102
+ - **M4 CLI**:`dshm` bin。
103
+ - **M5 收尾**:registry CI workflow、首批收录两条、自更新、发 `0.1.0`。
104
+
105
+ ## 9. 决策记录(三轮 19 条)
106
+
107
+ | # | 决策 | 结论 |
108
+ |---|------|------|
109
+ | Q1 | 形态 | DSH 插件主体;**GUI 旗舰(Q9 修订:比 CLI 重要,v1 必须做好)**+ 工具 + 薄 CLI |
110
+ | Q2 | 管理范围 | 只管插件,skills 归 skillhub |
111
+ | Q3 | registry 维护 | repo 内手工 curated;版本不写死 |
112
+ | Q4 | 安装来源 | npm 优先 + GitHub 兜底,schema 留 `source` |
113
+ | Q5 | 自用边界 | 单机自用,收录靠 repo/PR,无服务端 |
114
+ | Q6 | 升级提示 | v1 包含,半自动;全自动后台升级不做 |
115
+ | Q7 | 与 skillhub 关系 | 完全独立并存 |
116
+ | Q8 | 重启 | 内置一键重启,复用已验证路径 |
117
+ | Q9 | v1 边界 | ✏️ 修订:GUI 必须进 v1 且优先级最高 |
118
+ | Q10 | registry 分发 | 运行时拉 `@main` + TTL 缓存 + 包内快照兜底 |
119
+ | Q11 | schema | 精简中文 schema(icon 经 Q22 修订为可选覆盖) |
120
+ | Q12 | pin 策略 | npm 精确版本;GitHub 锁 SHA |
121
+ | Q13 | 已装识别 | 读 profile package.json,零额外状态 |
122
+ | Q14 | 卸载语义 | 删包不删数据,报告残留 |
123
+ | Q15 | 命名 | `dshm_` 前缀 / `dshm` bin |
124
+ | Q16 | 升级检查时机 | 纯按需 + 自更新检查进 v1 |
125
+ | Q17 | 安全基线 | §3 的 5 条 |
126
+ | Q18 | 仓库发版 | 单包;publish.yml 不改;tag 发版 |
127
+ | Q19 | 首批收录 | ✏️ 修订:dsh-skins + dsh-web-search(不含 skillhub) |
128
+ | Q20–24 | GUI 细节 | 3 视图 / esbuild+React / 自动头像 / CLI 薄封装 / 设置页三件事 |
129
+
130
+ ## 10. 实现参考(本地镜像)
131
+
132
+ - **skillhub 完整源码**:`~/.research-skillhub/all`(工作区内)。重点借鉴:`src/install.ts`(zip 原子写入、防穿越)、`src/plugin-market.ts`(install-plan、pinned SHA、spawn dsh CLI、dangerouslyAllowAllBuilds 重试)、`src/installed-plugins.js`(卸载前 live-disable)、`src/live-plugin.js`、`src/restart.ts`(重启路径)、`src/client.js`(GUI 模式范本)、`src/self-update.ts`。
133
+ - **dshmarketplace 源码**:`~/research-dshmarketplace/`。借鉴:registry 校验两段式(无密钥校验 + 受信写入)、HMAC 常量时间比对(若将来需要)、收录启发式(topic + 最少 commits + marker 文件,备将来自动发现)。
134
+
135
+ ## 11. 已知事实约束(本机)
136
+
137
+ - profile 根:`$DSH_HOME/profiles/web`(默认 `~/.dsh/profiles/web`);已装插件 = 其 `package.json` dependencies + `dsh.profile.bundles`。
138
+ - 新装插件需**重启 dsh web** 才加载;HMR 仅在 `pnpm run dev:web` watcher 存活时有效。
139
+ - 禁止创建监听 3080 的进程;`dsh-web.service` 为 shim,重启走其转发路径。
140
+ - 插件包协议要点:`type: module`、`main` host 入口、`exports["./client"]` 指向打包产物、`dsh.client.platform: "web"`、`dsh.bundle.patch: ./cordis.patch.yml`(`- insert: - id: dsh-m; name: dsh-m`)。
package/README.en.md ADDED
@@ -0,0 +1,89 @@
1
+ # dsh-m — Plugin Marketplace for DeepSeek Harness
2
+
3
+ [![Release](https://img.shields.io/github/v/release/iasiv5/dsh-m?label=Release&sort=semver)](../../releases)
4
+ [![npm](https://img.shields.io/npm/v/dsh-m?label=npm)](https://www.npmjs.com/package/dsh-m)
5
+ [![Registry Check](https://img.shields.io/github/actions/workflow/status/iasiv5/dsh-m/registry.yml?branch=main&label=Registry%20Check)](../../actions/workflows/registry.yml)
6
+ [![License](https://img.shields.io/github/license/iasiv5/dsh-m?label=License)](./LICENSE)
7
+ [![DSH Web](https://img.shields.io/badge/DSH%20Web-0.1.1--rc.2%20verified-2563eb)](#faq)
8
+
9
+ English · [中文](./README.md)
10
+
11
+ Your personal plugin marketplace for DeepSeek Harness (DSH): **browse, install, uninstall, upgrade** — all local. Ships as a DSH web plugin: the "Plugin Marketplace" sidebar entry opens a three-view panel, backed by seven `dshm_*` agent tools and the `dshm` CLI.
12
+
13
+ ## Quick start
14
+
15
+ ```sh
16
+ npm install -g dsh-m # or: dsh plugin --profile web add dsh-m@<version>
17
+ ```
18
+
19
+ 1. **Restart DSH Web** after installing (one-click restart works too).
20
+ 2. Refresh the page and click "**Plugin Marketplace**" at the bottom of the sidebar.
21
+
22
+ First listings include: DSH Skins, DSH Web Search, the Lark / QQ / Weixin / WeCom / DingTalk channel bridges, and dsh-m itself.
23
+
24
+ ## Panel
25
+
26
+ | View | Capabilities |
27
+ |---|---|
28
+ | **Market** | Card flow with search, category filters and tags; expandable details; "Install" — npm sources pin the latest exact version, GitHub sources pin the release/tag commit |
29
+ | **Installed** | What your web profile actually has, annotated "via market / non-market"; update badges, upgrade, two-step confirm uninstall; 📖 README preview (64KB cap) |
30
+ | **Settings** | Registry cache status + force refresh, registry source override, dsh-m self-update |
31
+
32
+ After any mutation a "⚡ Restart" banner appears — restart goes through the systemd unit (same-origin checked, detached-helper fallback) and polls by boot id until the service is back. Installs stream live pnpm progress (resolve → download → link → build).
33
+
34
+ ## Agent tools (7)
35
+
36
+ | Tool | Purpose |
37
+ |---|---|
38
+ | `dshm_search` | Search the curated registry (renders cards in chat) |
39
+ | `dshm_list` | List installed plugins (market / non-market annotated) |
40
+ | `dshm_install` | Install by listing id |
41
+ | `dshm_uninstall` | Uninstall (confirm first; data kept, leftovers reported) |
42
+ | `dshm_outdated` | Check for newer versions |
43
+ | `dshm_upgrade` | Upgrade to the latest |
44
+ | `dshm_restart` | Restart DSH Web (with user consent) |
45
+
46
+ ## CLI
47
+
48
+ ```sh
49
+ dshm search [--query topic] [--category ui]
50
+ dshm list | outdated | registry
51
+ dshm install --id dsh-web-search
52
+ dshm upgrade --pkg dsh-web-search --yes
53
+ dshm uninstall --pkg dsh-web-search --yes
54
+ dshm restart --yes
55
+ ```
56
+
57
+ ## Registry
58
+
59
+ `registry.json` is hand-curated and served at runtime via **raw.githubusercontent / jsDelivr `@main`** (local 60-min TTL cache + bundled snapshot fallback) — listing updates are decoupled from plugin releases. To add or amend a listing, edit `registry.json` and open a PR; CI validates schema, npm/GitHub existence, duplicate ids and URL reachability.
60
+
61
+ Security baseline: HTTPS-only fetches with size caps and timeouts; npm installs verified by lock integrity + post-install version match; GitHub installs pinned to commit SHA; pnpm build scripts are allowed-by-policy with an explicit report when unblocked.
62
+
63
+ ## Development
64
+
65
+ ```sh
66
+ npm ci
67
+ npm run build # tsc (host/core/cli) + esbuild (client, tree-shaking off)
68
+ npm run typecheck
69
+ node scripts/validate-registry.mjs
70
+ ```
71
+
72
+ For local iteration use a `link:` dependency (same trick as dsh-skins): point the profile dependency at this repo, then `npm run build` + restart.
73
+
74
+ Release: `npm version patch|minor|major && git push --tags` → OIDC trusted publishing.
75
+
76
+ ## FAQ
77
+
78
+ **Why don't GitHub-sourced update hints follow main?**
79
+ Intermediate commits on main can be unstable. dsh-m tracks **releases / tags** only (`releases/latest` first, tags list as fallback) and pins the commit SHA the tag points to.
80
+
81
+ **Does uninstalling delete my data?**
82
+ No. Only the package reference in the profile is removed (live UI disabled first), and suspected leftover paths are reported to you.
83
+
84
+ **Does it work with third-party skins?**
85
+ Yes. Every color comes from DSH theme semantic tokens (`state-*` / `brand-*` / `bg-overlay`), verified against the official light/dark themes and third-party skins.
86
+
87
+ ## License
88
+
89
+ MIT
package/README.md CHANGED
@@ -1,21 +1,88 @@
1
- # dsh-m
1
+ # dsh-m — DeepSeek Harness 插件市场
2
2
 
3
- **Marketplace for [DeepSeek Harness (DSH)](https://github.com/deepseek-ai) plugins.**
3
+ [![Release](https://img.shields.io/github/v/release/iasiv5/dsh-m?label=Release&sort=semver)](../../releases)
4
+ [![npm](https://img.shields.io/npm/v/dsh-m?label=npm)](https://www.npmjs.com/package/dsh-m)
5
+ [![Registry Check](https://img.shields.io/github/actions/workflow/status/iasiv5/dsh-m/registry.yml?branch=main&label=Registry%20Check)](../../actions/workflows/registry.yml)
6
+ [![License](https://img.shields.io/github/license/iasiv5/dsh-m?label=License)](./LICENSE)
7
+ [![DSH Web](https://img.shields.io/badge/DSH%20Web-0.1.1--rc.2%20verified-2563eb)](#faq)
4
8
 
5
- `dsh-m` 会收录大量 DSH 插件的安装地址,作为 DSH 插件市场的名称注册与分发入口。
6
- 当前版本为**占位版本**(name reservation),真正的 marketplace 功能开发中。
9
+ [English](./README.en.md) · 中文
7
10
 
8
- ## 安装
11
+ 个人自用的 DeepSeek Harness (DSH) 插件市场:**收录 · 安装 · 卸载 · 升级**,全部本机完成。以 DSH web 插件形态运行——侧栏「插件市场」打开三视图面板,同时提供 `dshm_*` agent 工具与 `dshm` CLI。
9
12
 
10
- ```bash
11
- npm install dsh-m
13
+ ## 30 秒上手
14
+
15
+ ```sh
16
+ npm install -g dsh-m # 或 dsh plugin --profile web add dsh-m@<version>
17
+ ```
18
+
19
+ 1. 安装后**重启 DSH Web**(一键重启亦可)。
20
+ 2. 刷新页面,点击侧栏底部的「**插件市场**」。
21
+
22
+ 收录清单里的首批插件:DSH Skins、DSH Web Search、Lark / QQ / 微信 / 企业微信 / 钉钉通道桥,以及 dsh-m 自身。
23
+
24
+ ## 界面(侧栏「插件市场」)
25
+
26
+ | 视图 | 能力 |
27
+ |---|---|
28
+ | **市场** | 收录卡片流;关键词搜索、分类筛选、标签;卡片展开详情与「安装」;npm 源锁定最新精确版本,GitHub 源锁定 release/tag 指向的 commit |
29
+ | **已装** | web profile 实装列表,标注「市场安装 / 非市场安装」;可升级徽标、升级、两段式确认卸载;📖 README 预览(64KB 截断) |
30
+ | **设置** | 收录清单缓存状态与强制刷新、registry 源覆盖、dsh-m 自更新 |
31
+
32
+ 安装 / 卸载 / 升级完成后出现「⚡ 一键重启」横幅——重启走 systemd 单元(同源校验,兜底 detached helper),按 boot id 轮询直至服务恢复。安装过程实时显示 pnpm 进度(解析 → 下载 → 链接 → 构建)。
33
+
34
+ ## Agent 工具(7 个)
35
+
36
+ | 工具 | 用途 |
37
+ |---|---|
38
+ | `dshm_search` | 搜收录清单(对话内出卡片) |
39
+ | `dshm_list` | 列已装插件(市场/非市场标注) |
40
+ | `dshm_install` | 按收录 id 安装 |
41
+ | `dshm_uninstall` | 卸载(先确认;不删数据,报告残留路径) |
42
+ | `dshm_outdated` | 检查最新版本 |
43
+ | `dshm_upgrade` | 升级到最新 |
44
+ | `dshm_restart` | 一键重启 DSH Web(先征得同意) |
45
+
46
+ ## CLI
47
+
48
+ ```sh
49
+ dshm search [--query 主题] [--category ui]
50
+ dshm list | outdated | registry
51
+ dshm install --id dsh-web-search
52
+ dshm upgrade --pkg dsh-web-search --yes
53
+ dshm uninstall --pkg dsh-web-search --yes
54
+ dshm restart --yes
12
55
  ```
13
56
 
14
- ## Roadmap
57
+ ## 收录清单(registry)
58
+
59
+ `registry.json` 手工 curated,运行时经 **raw.githubusercontent / jsDelivr `@main`** 分发(本地 60 分钟 TTL 缓存 + 包内快照兜底)——收录更新与插件发版**解耦**。收录 / 修订直接改 `registry.json` 发 PR,CI 自动校验:schema、npm 包与 GitHub 仓库存在性、重复 id、URL 可达性。
60
+
61
+ 安全基线:拉取仅 HTTPS + 体积上限 + 超时;npm 安装按 lock integrity 校验 + 装后版本核对;GitHub 安装强制锁定 commit SHA;pnpm 构建脚本被拦时按策略放行并明确报告。
62
+
63
+ ## 开发
64
+
65
+ ```sh
66
+ npm ci
67
+ npm run build # tsc(host/core/cli)+ esbuild(client,tree-shaking 已关闭)
68
+ npm run typecheck
69
+ node scripts/validate-registry.mjs
70
+ ```
71
+
72
+ 本地调试推荐 `link:` 模式(与 dsh-skins 相同):profile 依赖指向本仓库目录,`npm run build` + 重启即生效。
73
+
74
+ 发版:`npm version patch|minor|major && git push --tags` → OIDC trusted publishing 自动发布。
75
+
76
+ ## FAQ
77
+
78
+ **为什么 GitHub 来源的更新提示不走 main HEAD?**
79
+ main 上的中间提交可能不稳定。dsh-m 只跟踪 **release / tag**(优先 `releases/latest`,无 release 回退 tags 列表),安装时锁定 tag 指向的 commit SHA。
80
+
81
+ **卸载会删我的数据吗?**
82
+ 不会。只移除 profile 中的包引用(卸载前先下线运行中的界面),并把疑似残留路径报告给你。
15
83
 
16
- - [ ] 插件收录清单(名称 / 安装地址 / 版本)
17
- - [ ] 检索与分类
18
- - [ ] 与 DSH 插件安装流程集成
84
+ **支持第三方皮肤吗?**
85
+ 支持。全部配色来自 DSH 主题语义 token(`state-*` / `brand-*` / `bg-overlay` 等),官方深浅色与第三方皮肤均已验证。
19
86
 
20
87
  ## License
21
88
 
@@ -0,0 +1,3 @@
1
+ - insert:
2
+ - id: dshm
3
+ name: 'dsh-m'
package/lib/.keep ADDED
File without changes
package/lib/cli.js ADDED
@@ -0,0 +1,196 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * dshm — DSH Marketplace 薄 CLI(DESIGN.md §5)。与 dshm_* agent 工具同核。
4
+ * 变更类命令(uninstall/upgrade/restart)必须带 --yes 显式确认。
5
+ */
6
+ import { installFromRegistry, listInstalledWithMeta, uninstallPlugin, upgradePlugin, withMutationLock, } from './core/market.js';
7
+ import { loadRegistry } from './core/registry.js';
8
+ import { scheduleRestart } from './core/restart.js';
9
+ function parseArgs(argv) {
10
+ const cmd = argv[0] || 'help';
11
+ const flags = {};
12
+ for (let i = 1; i < argv.length; i++) {
13
+ const a = argv[i];
14
+ if (!a.startsWith('--'))
15
+ continue;
16
+ const key = a.slice(2);
17
+ const next = argv[i + 1];
18
+ if (next !== undefined && !next.startsWith('--')) {
19
+ flags[key] = next;
20
+ i++;
21
+ }
22
+ else {
23
+ flags[key] = true;
24
+ }
25
+ }
26
+ return { cmd, flags };
27
+ }
28
+ function cliConfig() {
29
+ return {
30
+ registryUrl: process.env.DSHM_REGISTRY_URL || undefined,
31
+ timeoutMs: Number(process.env.DSHM_TIMEOUT_MS) || 20_000,
32
+ cacheTtlMin: Number(process.env.DSHM_CACHE_TTL_MIN) || 60,
33
+ };
34
+ }
35
+ function needFlag(flags, name) {
36
+ const v = flags[name];
37
+ if (typeof v !== 'string' || !v.trim()) {
38
+ console.error(`错误:缺少 --${name} 参数`);
39
+ process.exit(1);
40
+ }
41
+ return v.trim();
42
+ }
43
+ function requireYes(flags, action) {
44
+ if (flags.yes !== true) {
45
+ console.error(`拒绝执行:${action} 是变更操作,必须带 --yes 显式确认。`);
46
+ process.exit(1);
47
+ }
48
+ }
49
+ function sourceLabel(s) {
50
+ return { npm: 'npm', github: 'github', link: '本地 link', file: '本地 file', unknown: '未知' }[s] || s;
51
+ }
52
+ const HELP = `dshm — DSH Marketplace(个人自用 DSH 插件市场)
53
+
54
+ 用法:dshm <命令> [参数]
55
+
56
+ 只读命令:
57
+ dshm search [--query 关键词] [--category market|tools|ui|search|media|other] [--limit N]
58
+ dshm list 列出 web profile 已装插件(含市场标注/可升级)
59
+ dshm outdated 检查已装插件的最新版本
60
+ dshm registry 查看收录清单来源与条目
61
+
62
+ 变更命令(必须 --yes):
63
+ dshm install --id <收录id> [--version 1.2.3]
64
+ dshm upgrade --pkg <包名> --yes
65
+ dshm uninstall --pkg <包名> --yes
66
+ dshm restart --yes
67
+
68
+ 环境变量:DSHM_REGISTRY_URL(registry 源覆盖)、DSHM_TIMEOUT_MS、DSHM_CACHE_TTL_MIN、DSHM_CACHE_DIR
69
+ `;
70
+ async function main() {
71
+ const { cmd, flags } = parseArgs(process.argv.slice(2));
72
+ const cfg = cliConfig();
73
+ switch (cmd) {
74
+ case 'help':
75
+ case '--help':
76
+ case '-h':
77
+ console.log(HELP);
78
+ return 0;
79
+ case 'search': {
80
+ const { listMarket } = await import('./core/market.js');
81
+ const result = await listMarket(cfg);
82
+ const q = typeof flags.query === 'string' ? flags.query.toLowerCase() : '';
83
+ const cat = typeof flags.category === 'string' ? flags.category : null;
84
+ const limit = Number(flags.limit) || Infinity;
85
+ const items = result.items.filter((it) => {
86
+ if (cat && it.category !== cat)
87
+ return false;
88
+ if (!q)
89
+ return true;
90
+ return `${it.id} ${it.name} ${it.description} ${it.tags.join(' ')}`.toLowerCase().includes(q);
91
+ }).slice(0, limit);
92
+ if (!items.length) {
93
+ console.log('没有匹配的收录条目。');
94
+ return 0;
95
+ }
96
+ for (const it of items) {
97
+ const inst = it.installed ? ` [已安装 v${it.installedVersion || '?'}]` : '';
98
+ const latest = it.latestVersion ? ` 最新 v${it.latestVersion}` : it.latestSha ? ` HEAD ${it.latestSha.slice(0, 7)}` : '';
99
+ console.log(`• ${it.name} (${it.id}) · ${it.category} · ${it.source}${inst}${latest}`);
100
+ console.log(` ${it.description}`);
101
+ }
102
+ console.log(`\n来源:${result.registrySource}(${result.registryFetchedAt ?? '—'}),共 ${result.items.length} 条`);
103
+ return 0;
104
+ }
105
+ case 'list': {
106
+ const result = await listInstalledWithMeta(cfg);
107
+ if (!result.items.length) {
108
+ console.log(`web profile(${result.profileDir})还没有已装的 dsh 插件。`);
109
+ return 0;
110
+ }
111
+ for (const it of result.items) {
112
+ const marks = [
113
+ it.registryId ? '市场' : '非市场',
114
+ it.outdated && it.latestVersion ? `可升级 → v${it.latestVersion}` : null,
115
+ ].filter(Boolean).join(',');
116
+ console.log(`• ${it.name} (${it.pkg}) v${it.version || '?'} · ${sourceLabel(it.source)}${marks ? ` · ${marks}` : ''}`);
117
+ }
118
+ if (result.others)
119
+ console.log(`\n另有 ${result.others} 个非 dsh 依赖未列出。`);
120
+ return 0;
121
+ }
122
+ case 'outdated': {
123
+ const result = await listInstalledWithMeta(cfg);
124
+ const outdated = result.items.filter((it) => it.outdated);
125
+ if (!outdated.length) {
126
+ console.log(`全部 ${result.items.length} 个插件均已是最新版本。`);
127
+ return 0;
128
+ }
129
+ for (const it of outdated) {
130
+ console.log(`• ${it.name} (${it.pkg}):v${it.version} → ${it.latestVersion || '最新'}`);
131
+ }
132
+ console.log(`\n升级:dshm upgrade --pkg <包名> --yes`);
133
+ return 0;
134
+ }
135
+ case 'registry': {
136
+ const loaded = await loadRegistry(cfg, { force: flags.force === true });
137
+ console.log(`来源:${loaded.source} · 更新时间:${loaded.fetchedAt ?? '—'} · 条目:${loaded.registry.plugins.length}`);
138
+ for (const e of loaded.registry.plugins) {
139
+ console.log(` • ${e.id} · ${e.name} · ${e.category} · ${e.source === 'npm' ? e.npm : e.github}`);
140
+ }
141
+ if (loaded.errors.length)
142
+ console.log(`提示:${loaded.errors.join(';')}`);
143
+ return 0;
144
+ }
145
+ case 'install': {
146
+ const id = needFlag(flags, 'id');
147
+ const version = typeof flags.version === 'string' ? flags.version : undefined;
148
+ const res = await withMutationLock(() => installFromRegistry(id, cfg, { version }));
149
+ console.log(`✅ 已安装 ${res.pkg}(${res.spec})`);
150
+ if (res.usedAllowAllBuilds)
151
+ console.log('⚠️ 该插件执行了构建脚本(已按策略放行)。');
152
+ console.log('需要重启 DSH Web 生效:dshm restart --yes');
153
+ return 0;
154
+ }
155
+ case 'upgrade': {
156
+ const target = needFlag(flags, 'pkg');
157
+ requireYes(flags, '升级');
158
+ const res = await withMutationLock(() => upgradePlugin(target, cfg));
159
+ const from = res.fromVersion ? `v${res.fromVersion} → ` : '';
160
+ const to = res.version ? `v${res.version}` : res.sha ? res.sha.slice(0, 7) : '最新';
161
+ console.log(`✅ 已升级 ${res.pkg}(${from}${to})`);
162
+ if (res.usedAllowAllBuilds)
163
+ console.log('⚠️ 该插件执行了构建脚本(已按策略放行)。');
164
+ console.log('需要重启 DSH Web 生效:dshm restart --yes');
165
+ return 0;
166
+ }
167
+ case 'uninstall': {
168
+ const target = needFlag(flags, 'pkg');
169
+ requireYes(flags, '卸载');
170
+ const res = await withMutationLock(() => uninstallPlugin(target, cfg));
171
+ console.log(`✅ 已卸载 ${res.pkg}${res.liveDisabled ? '(运行中的界面已先下线)' : ''}`);
172
+ if (res.leftovers.length)
173
+ console.log(`ℹ️ 疑似残留数据(未删除):${res.leftovers.join('、')}`);
174
+ console.log('需要重启 DSH Web 生效:dshm restart --yes');
175
+ return 0;
176
+ }
177
+ case 'restart': {
178
+ requireYes(flags, '重启 DSH Web');
179
+ const res = scheduleRestart(null);
180
+ console.log(`✅ 已请求重启(via ${res.via})。服务恢复后刷新页面即可。`);
181
+ return 0;
182
+ }
183
+ default:
184
+ console.error(`未知命令:${cmd}\n`);
185
+ console.log(HELP);
186
+ return 1;
187
+ }
188
+ }
189
+ main()
190
+ .then((code) => {
191
+ process.exitCode = code;
192
+ })
193
+ .catch((err) => {
194
+ console.error('错误:', err instanceof Error ? err.message : String(err));
195
+ process.exitCode = 1;
196
+ });