dsh-config-manager 0.1.2 → 0.1.4

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.zh-CN.md CHANGED
@@ -1,252 +1,209 @@
1
- # DSH Config Manager
1
+ # 🎒 DSH Config Manager
2
2
 
3
- **Backup · Export · Import · Migrate · Restore** —— DSH 配置备份 / 导出 / 导入 / 迁移管理器。
3
+ **把你的 DSH 配置打包带走 —— 换台电脑,环境一键恢复。**
4
4
 
5
- [English](README.md) | [简体中文](README.zh-CN.md)
5
+ [English](README.md) · [简体中文](README.zh-CN.md)
6
6
 
7
- 在一台 DSH 中一键导出主要配置为 ZIP,在另一台 DSH 中导入并尽可能恢复原来的使用环境。
7
+ ---
8
+
9
+ ## 这是什么?🤔
10
+
11
+ DSH 是你的 AI 助手工作台,里面存着你的各种设置:模型配置、插件、常用技能、工作区……
12
+
13
+ **DSH Config Manager 就是它的「搬家工具」**:
14
+
15
+ ```
16
+ ┌──────────────┐ ① 一键导出 ┌─────────────────┐ ② 一键导入 ┌──────────────┐
17
+ │ 电脑 A │ ─────────────► │ dsh-config.zip │ ─────────────► │ 电脑 B │
18
+ │ 我的配置 │ │ (一个文件) │ │ 配置全部恢复 │
19
+ └──────────────┘ └─────────────────┘ └──────────────┘
20
+ ```
21
+
22
+ > ⚠️ **安全第一**:默认**不导出**任何密钥(API Key / Token / 密码)。详见 [安全](#-安全)。
23
+
24
+ ---
25
+
26
+ ## ✨ 核心亮点
8
27
 
9
- > ⚠️ **安全第一:默认不导出任何 Secret(API Key / Token / 密码)。** 详见 [Security](#security)。
28
+ | 图标 | 功能 | 一句话说明 |
29
+ |:---:|---|---|
30
+ | 🚀 | **一键导出** | 点一下,把推荐配置打包成一个 ZIP |
31
+ | 📦 | **一键导入** | 在另一台电脑点一下,环境就回来了 |
32
+ | 👀 | **先预览再导入** | 导入前完整预览,**绝不偷偷改你的配置** |
33
+ | ⚔️ | **冲突处理** | 遇到同名配置,让你自己选:保留现有的 / 用导入的 |
34
+ | 🗺️ | **路径自动映射** | 换了电脑路径变了?自动检测并让你重新指定 |
35
+ | 🔒 | **密钥安全** | API Key 默认不导出;导入后提醒你重新填写 |
36
+ | ↩️ | **自动回滚** | 导入失败自动恢复原样,不会弄坏现有配置 |
37
+ | 🗂️ | **配置档案 Profiles** | 保存多套配置(工作 / 个人),随时切换 |
10
38
 
11
39
  ---
12
40
 
13
- ## What it does
41
+ ## 🔄 它是怎么工作的?
14
42
 
15
- DSH 的配置是「一个集中 `settings.yaml` + 多个独立文件 + 插件自有文件」的混合模型(见 `Docs/research/dsh-architecture.md`)。
16
- 本插件**不整包复制 `~/.dsh`**,而是按真实配置类别分区收集、打包为带清单(manifest)与校验和(checksums)的 ZIP 备份,并在导入端执行
43
+ ### 导出(打包带走)
17
44
 
18
45
  ```
19
- Analyze → Preview → Snapshot → Apply → Validate → Rollback(if needed)
46
+ 读取你的配置 → 剔除密钥(安全) → 生成清单 → 计算校验和 → 打包成 ZIP
20
47
  ```
21
48
 
22
- 的安全导入流程。
49
+ ### 导入(恢复环境)
23
50
 
24
- ## Features
51
+ 每一步都先确认、先备份,**绝不直接改你的配置**:
25
52
 
26
- - **Export**:Quick Export(推荐配置一键导出)与按分区选择导出。
27
- - **Import**:ZIP 校验 → manifest 读取 → 完整性校验 → schema 检查 → 兼容性检查 → 内容扫描 → 导入计划预览 → 用户确认 → 自动快照 → 执行 → 校验 → 结果(失败自动回滚)。
28
- - **Dry Run / Preview**:`analyzeImport()` + `createImportPlan()` 纯计算零写入,导入前完整预览。
29
- - **冲突处理**:`merge`(默认)/ `replace` / `skipExisting` 全局策略 + 逐项 `Keep Current / Use Imported / Review`。
30
- - **路径映射**:跨设备绝对路径检测与批量前缀映射(workspace 路径 / MCP cwd / 插件配置路径)。
31
- - **Secret 安全**:默认剥离所有敏感字段;加密完整备份(scrypt + AES-256-GCM)为可选高级功能。
32
- - **自动快照与回滚**:导入前备份将被修改的目标,失败时逆序补偿恢复。
33
- - **Schema 版本与迁移**:`schemaVersion` 独立演进,迁移逻辑集中在 `src/migrations/`。
34
- - **幂等**:同一 ZIP 重复导入不产生重复数据(按 Plugin ID / MCP serverName / Prompt name / Workspace id / Credential ref 识别)。
35
- - **兼容性评分**:Excellent / Good / Partial / Unsupported(规则驱动)。
36
- - **Profiles(配置 Profile)**:保存当前配置为 Profile / 切换 / 复制 / 重命名 / 导出 / 导入 / 删除;切换带 Preview + 快照 + 回滚(`src/profiles/`)。
53
+ ```
54
+ 选择 ZIP → 校验文件 → 检查完整性 → 检查版本 → 兼容性检查
55
+ → 扫描内容 → 生成导入计划 → 预览确认
56
+ → 自动备份当前配置 → 执行导入 → 验证 → 完成
57
+ │
58
+ └─ 中途失败?→ 自动恢复原样(回滚)
59
+ ```
37
60
 
38
- ## Installation
61
+ ---
39
62
 
40
- 本插件是标准的 **DSH bundle 插件**(仿 dsh-ssh 工程范式,见研究报告 §5.1):`package.json` 声明
41
- `dsh.bundle.patch`(指向 `cordis.patch.yml`,CLI 的 bundle 硬判据)与 `dsh.client`(浏览器半声明),
42
- `npm run build` 产出双半产物(`lib/index.js` Host 半 + `lib/client.js` 浏览器半,后者以
43
- `window.__ModuleLoader__.load(...)` 装载进 Web GUI)。
63
+ ## 📥 安装
44
64
 
45
- 两种安装方式(二选一):
65
+ 本插件是标准的 **DSH 插件**,安装只需要两步:
46
66
 
47
67
  ```bash
48
- # ① 推荐 —— 从 npm registry 安装
49
- dsh plugin --profile web add dsh-config-manager --config.auto-install-peers=false
68
+ # ① 安装插件
69
+ dsh plugin --profile web add dsh-config-manager@latest --config.auto-install-peers=false
50
70
 
51
- # ② 备选 —— 本地 tgz / 目录安装(开发与测试)
52
- npm run build
53
- npm pack # 产出 dsh-config-manager-0.1.0.tgz
54
- dsh plugin --profile web add file:/absolute/path/to/dsh-config-manager-0.1.0.tgz
71
+ # ② 重启 DSH(设置页就会出现「备份与迁移」入口)
55
72
  ```
56
73
 
57
- > **`--legacy-peer-deps` 说明**:peerDependencies 中的部分 DSH 核心包(如
58
- > `@deepseek-ai/dsh-plugin-marketplace`、`dsh-host-plugin-inventory`)尚未发布到公共 npm registry,
59
- > 只在本地 DSH profile 环境存在。安装时若 npm/pnpm 尝试解析 peer 依赖并失败,请跳过 peer 自动安装:
60
- > - npm 直接安装:`npm install --legacy-peer-deps`
61
- > - `dsh plugin add`(内部转发 pnpm):`dsh plugin --profile web add <spec> --config.auto-install-peers=false`
62
- >
63
- > 运行时这些包由 DSH profile 自身提供(peerDependencies 语义),插件不重复安装。
74
+ > 💡 照着复制就行:`--config.auto-install-peers=false` 跳过几个尚未公开发布的 DSH 核心依赖(运行时由 DSH 自己提供),`@latest` 确保装到最新版。
64
75
 
65
- > **本地验证提示**:可用 `$DSH_HOME=<临时目录>` 环境变量隔离测试,完全不触碰 `~/.dsh`:
66
- > ```bash
67
- > $env:DSH_HOME = "D:\tmp\dsh-home" # Windows PowerShell
68
- > dsh plugin --profile test add file:<tgz> --config.auto-install-peers=false
69
- > dsh --profile test --dump-config | Select-String config-manager # 应看到挂载行
70
- > ```
76
+ ---
71
77
 
72
- ## Export
78
+ ## 🚀 快速上手(3 分钟体验)
73
79
 
74
- 两种方式:
80
+ ```
81
+ 电脑 A(导出)
82
+ 1. 打开 DSH → 设置 → 「备份与迁移」
83
+ 2. 点「导出配置」→ 选「快速导出」
84
+ 3. 得到一个 dsh-config-2026-08-14.zip(可确认报告里没有密钥)
85
+
86
+ 把 ZIP 拷到电脑 B(导入)
87
+ 1. 打开 DSH → 「备份与迁移」→「导入配置」
88
+ 2. 选择 ZIP → 等待分析 → 查看「导入预览」
89
+ 3. 有路径问题?→ 选择新路径(可批量映射)
90
+ 4. 有同名配置冲突?→ 选择 保留现有的 / 用导入的
91
+ 5. 确认导入 → 等待完成
92
+ 6. 按提示重新填写缺失的 API Key
93
+ 7. ✅ 设置 / 插件 / MCP / 技能 / 工作区都回来了
94
+ ```
75
95
 
76
- - **Quick Export**:一键导出推荐分区(settings / ui / providers / plugins / mcp / prompts / skills / agentPresets / workspaces / credentialsStatus)。
77
- - **Custom Export**:按分区逐项选择(可选 `pluginFiles`、`sessions`;`sessions` 默认关闭)。
96
+ ---
78
97
 
79
- 导出产物:`dsh-config-<yyyy-MM-dd>.zip`,含 `manifest.json` + 各分区数据 + `integrity/checksums.json`(SHA-256)。
98
+ ## 🧩 功能详解
80
99
 
81
- ## Import
100
+ ### 📤 导出(两种方式)
82
101
 
83
- ```
84
- Select ZIP → Validate ZIP → Read Manifest → Check Integrity → Check Schema
85
- → Check Compatibility → Scan Contents → Generate Import Plan → Show Preview
86
- → User Confirms → Create Backup (Snapshot) → Import → Validate → Show Result
87
- ```
102
+ | 方式 | 说明 |
103
+ |---|---|
104
+ | **快速导出**(推荐) | 一键导出推荐配置:设置 / UI / 模型 / 插件 / MCP / 技能 / 工作区等 |
105
+ | **自定义导出** | 自己勾选要导出的分类 |
88
106
 
89
- 导入流程强制:**未确认不执行任何写入**;**导入前必须生成快照**;失败按 `rollbackOnError` 决定整体回滚或单项如实记录。
107
+ > 导出文件:`dsh-config-<日期>.zip`,内含清单 + 各分类数据 + SHA-256 校验和。
90
108
 
91
- ## Security
109
+ ### 📥 导入(安全流程)
92
110
 
93
- > **默认备份不包含任何 Secret 值。** 这是硬性安全不变量,由 `Exporter` 强制:
111
+ - **未确认不写入**:分析、预览阶段零修改
112
+ - **先备份再导入**:执行前自动备份将被修改的配置
113
+ - **失败自动回滚**:按你的选择整体回滚或跳过失败项继续
94
114
 
95
- - 所有结构化分区数据在写入 ZIP 前经过敏感字段扫描(字段名黑名单:password / token / apiKey / secret / credential / authorization / cookie / privateKey / clientSecret 等,大小写不敏感),命中即剥离。
96
- - `ctx.settings.describe({ redactSecrets: true })` 作为第一道防线剥离 DSH 已知秘密;敏感字段扫描器作为第二道防线兜底插件自定义字段。
97
- - 凭据(`.credentials.yaml`)**永不导出值**,只导出状态(`{ref, required, configured, hasValue:false}`),导入后生成「N credentials need attention」补录清单。
98
- - **加密完整备份(可选)**:显式勾选「Include secrets」时,要求设置备份密码,使用 `node:crypto`(scrypt 派生 + AES-256-GCM 加密),**密码绝不写入 manifest**;`secrets.enc` 只有解密后经 `ctx.credentials.set()` 写回。
99
- - 无加密提供者时 `includeSecrets: true` 会被拒绝(绝不明文泄密)。
100
- - 日志系统全部经过 redaction,Secret 值永不进入日志。
101
- - ZIP 属于不可信输入:防御 Zip Slip / 绝对路径 / 符号链接 / zip bomb(条目数 / 压缩体积 / 解压体积 / 压缩比上限)/ 畸形 ZIP / checksum 不匹配,任何一条触发即整体拒绝。
115
+ ### 👀 导入预览(Dry Run)
102
116
 
103
- ## What is NOT exported
117
+ 导入前完整展示:
104
118
 
105
- 默认**不**导出(规范 §34.19/20):
119
+ ```
120
+ ✓ 18 项设置将被更新 ✓ 6 个插件已安装
121
+ ⚠ 2 个插件需要安装 ⚠ 3 个密钥需要重新填写
122
+ ⚠ 1 个路径需要映射 ⚠ 2 处冲突需要处理
123
+ ```
106
124
 
107
- - API Key / Password / Token / Cookie / Session / 认证凭据(值)
108
- - `~/.dsh/.anonymous-user-id`(设备唯一 ID)
109
- - 会话历史(`sessions/`,默认关闭;v1 仅支持文件级复制)
110
- - Logs / Cache / 临时文件
111
- - 浏览器 localStorage 中的 UI 状态(Host 侧无通道,仅导出 `uiMigrationNotes` 说明)
112
- - 插件二进制(绝不打包,只迁移清单并走官方安装机制)
125
+ ### ⚔️ 冲突处理
113
126
 
114
- ## Secrets
127
+ 目标电脑已有同名配置时,让你选:
115
128
 
116
- | 备份类型 | 导入行为 |
129
+ | 选项 | 含义 |
117
130
  |---|---|
118
- | 普通备份(无 secrets.enc) | 全部凭据 → `MissingSecret`,导入后由用户补录 |
119
- | 加密备份 + 正确密码 | 自动解密并经 `credentials.set()` 恢复(预览时可逐条确认) |
120
- | 加密备份 + 不输密码 | 同普通备份:状态补录 |
131
+ | **保留现有的** | 不动目标电脑的原配置 |
132
+ | **用导入的** | 用备份里的配置覆盖 |
133
+
134
+ > 说明:刻意**不提供**「稍后决定 / 再看看」选项——未决的冲突会让导入无法继续,每个冲突都必须在继续前做出选择。
121
135
 
122
- ## Compatibility
136
+ ### 🗺️ 路径映射
123
137
 
124
- | 状态 | 规则 |
138
+ 换了电脑,`C:\Users\alice\projects` 在另一台机器上不存在?插件会:
139
+ 1. 自动检测失效的绝对路径
140
+ 2. 让你选择新路径
141
+ 3. 支持**批量前缀映射**(如 `C:\Users\alice\` → `/Users/bob/` 一键替换所有相关路径)
142
+
143
+ ### 🔒 密钥处理
144
+
145
+ | 场景 | 行为 |
125
146
  |---|---|
126
- | Excellent | 同平台、无分区缺失、schema 受支持 |
127
- | Good | 备份来自更旧 DSH(目标向后兼容) |
128
- | Partial | 跨平台 / 分区缺失 / 备份比目标新 |
129
- | Unsupported | schema 超出支持范围 |
147
+ | 默认备份 | **不含任何密钥值**,只记录"哪些密钥需要填写" |
148
+ | 加密备份(可选) | 设置密码后用 AES-256-GCM 加密,密码**绝不写入备份文件** |
149
+ | 导入后 | 提示「3 个密钥需要重新填写」,输入后仅保存在内存中写入 |
130
150
 
131
- ## Backup format
151
+ ### 🗂️ 配置档案(Profiles)
132
152
 
133
- ```
134
- dsh-config-2026-08-14.zip
135
- ├── manifest.json # schemaVersion / exporter / source / sections / security
136
- ├── config/settings.json # 非 UI settings namespace(redacted + revision)
137
- ├── config/ui.json # UI namespace + uiMigrationNotes
138
- ├── ai/providers.json # llm-* providers/models(同 section 不拆分)
139
- ├── plugins/plugins.json + patch.json
140
- ├── mcp/servers.json # 组合 patch 提取的 dsh-mcp-client 条目
141
- ├── custom/prompts.json + skills/
142
- ├── agents/presets/
143
- ├── workspaces/workspaces.json
144
- ├── plugin-files/ # 可选
145
- ├── security/credentials.json # 凭据状态(永不含值)
146
- ├── security/secrets.enc # 仅加密备份
147
- └── integrity/checksums.json # SHA-256
148
- ```
153
+ 保存多套配置(如「工作」「个人」),随时切换;切换同样带预览 + 自动备份 + 回滚。
149
154
 
150
- ## Development
155
+ ---
151
156
 
152
- ```bash
153
- npm install --legacy-peer-deps # peer 含未发布公共 registry 的 DSH 核心包,见 Installation
154
- npm run typecheck # tsc --noEmit
155
- npm run build # tsc -p tsconfig.build.json(Host 半 lib/)+ tsdown(client bundle lib/client.js)
156
- npm run bundle # 仅重新构建 client bundle(tsdown)
157
- npm test # node --test "src/**/*.test.ts" "tests/**/*.test.ts"
158
- ```
157
+ ## 🛡️ 安全
159
158
 
160
- 架构:核心引擎(`src/core`)只依赖 `ConfigAdapter` / `HostContext` 接口(与 DSH 运行时解耦,可用内存 mock 测试);`src/adapters` 实现各配置类别;`src/security` 提供秘密扫描 / 加密 / 完整性 / ZIP 安全 / redaction;`src/migrations` 集中 schema 迁移。
159
+ - **默认备份不包含任何密钥值** —— 这是硬性规则,导出时强制执行
160
+ - **不导出**:API Key / 密码 / Token / Cookie / 会话 / 设备唯一 ID / 日志缓存 / 插件二进制
161
+ - **ZIP 是"不可信输入"**:防御 Zip Slip、恶意路径、压缩炸弹、损坏文件——任何一项触发就整体拒绝
162
+ - **日志全程脱敏**:密钥值永不进入日志
163
+ - **加密备份**:scrypt + AES-256-GCM,密码只在内存中,绝不写入文件
161
164
 
162
- ## Publishing(GitHub Actions · npm Trusted Publishing / OIDC)
165
+ ---
163
166
 
164
- 推送版本标签会触发 `.github/workflows/publish.yml` 的 CI 流水线:typecheck → 测试 → 构建 → 自动发布到 npm。发布使用 **npm Trusted Publishing(OIDC)**——仓库不存储任何长期令牌,npm CLI 通过 GitHub Actions 的 OIDC 与 registry 交换短期身份。
167
+ ## 🤝 兼容性
165
168
 
166
- ```bash
167
- npm version patch # 0.1.0 → 0.1.1(同时创建 tag)
168
- git push origin main --tags
169
- ```
169
+ | 状态 | 含义 |
170
+ |---|---|
171
+ | ✅ Excellent | 同平台、配置齐全、版本兼容 |
172
+ | 👍 Good | 备份来自更旧版本的 DSH |
173
+ | ⚠️ Partial | 跨平台 / 部分配置缺失 / 备份比当前新 |
174
+ | ❌ Unsupported | 备份版本超出支持范围(无法导入) |
170
175
 
171
- npmjs.com 一次性配置(首次 OIDC 发布前必须完成):
176
+ ---
172
177
 
173
- **方案 A — 网页**(有 scope 包或 UI 入口可用时):
174
- 1. 打开包页面:https://www.npmjs.com/package/dsh-config-manager → **Settings → Publishing access**。
175
- 2. **Add trusted publisher**:Provider **GitHub Actions** · owner `xiajiajun516` · repository `dsh-config-manager` · workflow filename `publish.yml`。
178
+ ## ❓ 常见问题
176
179
 
177
- **方案 B — CLI(推荐;无 scope 包 UI 无入口时用这个)**:
178
- ```bash
179
- npm logout
180
- npm login # 网页 OAuth + 2FA 验证码
181
- npm trust github dsh-config-manager \
182
- --file publish.yml \
183
- --repo xiajiajun516/dsh-config-manager \
184
- --allow-publish # 首次会要求输入 2FA 验证码
185
- npm trust list dsh-config-manager # 验证
186
- ```
180
+ **Q:备份会包含我的 API Key 吗?**
181
+ 不会。默认备份**绝不包含任何密钥**,只记录哪些密钥需要重新填写。
187
182
 
188
- 无需 `NPM_TOKEN` 仓库密钥——之前配置的令牌可以撤销(npm 也正在逐步废弃 bypass-2FA 令牌)。
183
+ **Q:导入会不会覆盖我现有的配置?**
184
+ 不会偷偷覆盖。有冲突时让你选择:保留现有的 / 用导入的;导入前还会自动备份,失败可回滚。
189
185
 
190
- 说明:
186
+ **Q:换电脑(Windows → macOS)能用吗?**
187
+ 能。插件会自动检测失效的绝对路径,让你重新映射(支持批量替换)。
191
188
 
192
- - `package.json` 中的版本须与 tag 一致(`npm version` 会自动同步)。
193
- - 流水线会先升级 npm(`npm install -g npm@latest`),因为 OIDC 发布要求 npm ≥ 11.5.1。
194
- - 也可在 Actions 页面点 **Run workflow** 手动触发。
189
+ **Q:导出的 ZIP 被改坏了还能导入吗?**
190
+ 不能。校验和检查不通过会直接拒绝导入(防止损坏或篡改)。
195
191
 
196
- ## Testing
192
+ **Q:重复导入会重复吗?**
193
+ 不会。按插件 ID / MCP 名称 / 技能名等稳定标识去重,重复导入自动跳过已有项。
197
194
 
198
- 测试框架为 **node:test(Node 内置,零依赖)**,沿用核心模块的既有选择(不引入 vitest)。测试位于 `src/**/*.test.ts` 与 `tests/**/*.test.ts`。
195
+ ---
199
196
 
200
- 覆盖矩阵(规范 §33 + 验收场景 A-G):
197
+ ## 📋 已知限制(用户须知)
201
198
 
202
- | 组 | 覆盖 |
203
- |---|---|
204
- | Export | 正常 / 空配置 / 大配置(1MB+) / Unicode / 特殊字符 / Secret 过滤 |
205
- | Import | 正常 / Merge / Replace / Skip(不删目标独有 §32)/ Conflict / Missing plugin / Missing dependency / Missing secret / 未确认拒绝 |
206
- | Rollback(场景 E) | 多 adapter 混合中途失败 → 整体恢复(settings / 文件 blob / workspace / patch 行);`rollbackOnError=false` 对照;部分回滚诚实报告 |
207
- | Migration(场景 G) | `migrateToCurrent` 机制级:同版本 / 过新 / 低于最低 / 无路径 / 注册重叠 / 链式推进(**如实说明:当前 v1 即最新,无真实 v2 可端到端验证**) |
208
- | Security(场景 F) | 畸形 ZIP / 超大条目数 / checksum 不匹配与缺失 / Zip Slip / 绝对路径 |
209
- | Cross-platform(场景 B) | win32→darwin / darwin→win32 / linux→win32 批量前缀映射 |
210
- | Redaction | 日志消息 / meta / 全链路不泄 Secret 值 |
211
- | Schema | manifest 结构校验 / 版本判定函数 |
212
-
213
- 当前测试结果:**186 tests, all passing**(`npm test`),`npm run typecheck` 与 `npm run build` 均通过。
214
-
215
- ## Known limitations
216
-
217
- 1. **Workspace 只能建/改标题**:DSH 的 workspace 服务没有「整体覆盖」写通道——导入时可创建 workspace 与更新标题;path 与会话列表由 DSH 依真实目录自行维护,跨设备路径通过路径映射适配。
218
- 2. **部分 DSH 核心包未发布公共 npm registry**(如 `@deepseek-ai/dsh-plugin-marketplace`、`dsh-host-plugin-inventory`):依赖其 API 的功能只在本地 profile 环境可用;安装本插件需跳过 peer 自动安装(见 [Installation](#installation) 的 `--legacy-peer-deps` 说明)。
219
- 3. **MCP 无管理 API**(研究报告 §4.3):MCP 以组合 patch 行导入,需重启 DSH 生效;无增删改 API。
220
- 4. **插件安装需重启**:`pluginMarketplace.installPlugin` 只返回 `needsRestart`,重启依赖 DSH Desktop。
221
- 5. **浏览器 localStorage UI 状态不迁移**(任务看板数据、面板宽度等):Host 无通道。
222
- 6. **keybindings / workflows 配置 / commands / rules 文件**:DSH 当前无这些概念,不实现分区(不发明)。
223
- 7. **凭据值无法回滚**:DSH 不回读凭据值,导入中覆盖的凭据在回滚时只能标记 `manualHint` 人工补录。
224
- 8. **新建项无法删除回滚**:DSH settings 无删除语义,导入新建的 namespace 在回滚时只能人工处理(如实报告 partial)。
225
- 9. **Schema 迁移**:v1→v2 为占位(当前 `CURRENT_SCHEMA_VERSION=1`),机制已就绪但无真实 v2 可验证。
226
- 10. **历史会话迁移**:默认关闭,v1 仅文件级复制。
227
- 11. **加密备份**:依赖用户设置强密码;密码丢失则 `secrets.enc` 无法解密(设计使然)。
228
-
229
- ## Manual Test(最短人工测试流程)
230
-
231
- > 前提:两台 DSH(或同一台的两份配置目录);本插件已按 [Installation](#installation) 打包并安装。
199
+ 1. **安装/更新插件或 MCP 后需重启 DSH 才生效**
200
+ 2. **部分界面状态不迁移**(如任务看板数据、面板宽度——它们存在浏览器里,不在 DSH 配置文件内)
201
+ 3. **keybindings / workflows 配置 / commands / rules**:DSH 当前没有这些概念,因此不会导出相关内容
202
+ 4. **历史会话默认不迁移**(v1 仅支持文件级复制)
203
+ 5. **加密备份**:密码丢失则无法解密(设计使然——请牢记密码)
232
204
 
233
- ```
234
- DSH A
235
- → 打开 Config Manager → Export Configuration
236
- → 选择 Quick Export → 导出 dsh-config-<date>.zip(确认报告中 Secret 均被排除)
237
- → 将 ZIP 复制到 DSH B
238
-
239
- DSH B
240
- → 打开 Config Manager → Import Configuration
241
- → 选择 ZIP → 等待 Analyzing... → 查看 Import Preview(分区/插件/路径映射/凭据补录清单)
242
- → 如有路径问题 → 选择映射目录(批量前缀映射)
243
- → 解决冲突(Keep Current / Use Imported / Review)
244
- → 确认 Import → 观察进度 → 查看结果报告
245
- → 补充缺失凭据(N credentials need attention)
246
- → Verify:确认 settings / 插件 / MCP / Prompts / Skills / Workspaces 已恢复;
247
- 若导入中途失败 → 确认已自动回滚、原配置可正常使用
248
- ```
205
+ > 维护者与开发者:构建、测试、自动发布与完整技术说明见 [DEVELOPERS.md](DEVELOPERS.md)。
249
206
 
250
207
  ---
251
208
 
252
- **产品原则**:宁可少迁移一个配置,也不要破坏用户现有配置。任何 Import 都遵循 `Analyze → Preview → Backup → Modify → Validate → Rollback`;任何 Secret 都遵循 `不默认导出 / 不记日志 / 不暴露 / 不静默转移`。
209
+ **产品原则**:宁可少迁移一个配置,也不要破坏你现有的配置。任何导入都遵循 `分析 → 预览 → 备份 → 修改 → 验证 → 回滚(如需要)`;任何密钥都遵循 `不默认导出 / 不记日志 / 不暴露 / 不静默转移`。
package/lib/client.js CHANGED
@@ -559,93 +559,93 @@ if (typeof document !== "undefined" && document.querySelector("style[data-tag=\"
559
559
  document.head.appendChild(tag);
560
560
  }
561
561
  var config_manager_module_css_default = {
562
+ "actionRow": "_367UGW_actionRow",
563
+ "formError": "_367UGW_formError",
564
+ "errorItem": "_367UGW_errorItem",
565
+ "secretFields": "_367UGW_secretFields",
566
+ "section": "_367UGW_section",
567
+ "sectionBody": "_367UGW_sectionBody",
568
+ "groupHeader": "_367UGW_groupHeader",
569
+ "fieldLabel": "_367UGW_fieldLabel",
562
570
  "input": "_367UGW_input",
563
- "dangerButton": "_367UGW_dangerButton",
564
- "badgeInfo": "_367UGW_badgeInfo",
565
- "errorReason": "_367UGW_errorReason",
566
- "pathMappingList": "_367UGW_pathMappingList",
567
- "sectionTitleBlock": "_367UGW_sectionTitleBlock",
568
- "radioLabel": "_367UGW_radioLabel",
569
- "modeTab": "_367UGW_modeTab",
570
571
  "groupLabel": "_367UGW_groupLabel",
571
- "actionRow": "_367UGW_actionRow",
572
- "spinnerWrap": "_367UGW_spinnerWrap",
573
- "groupNote": "_367UGW_groupNote",
574
- "dshCmSpin": "_367UGW_dshCmSpin",
572
+ "radioLabel": "_367UGW_radioLabel",
573
+ "modeTabs": "_367UGW_modeTabs",
575
574
  "progressMeta": "_367UGW_progressMeta",
576
- "groupItems": "_367UGW_groupItems",
577
- "categoryName": "_367UGW_categoryName",
578
- "section": "_367UGW_section",
579
- "badgeOk": "_367UGW_badgeOk",
575
+ "conflictHead": "_367UGW_conflictHead",
576
+ "conflictOptions": "_367UGW_conflictOptions",
577
+ "conflictId": "_367UGW_conflictId",
578
+ "pathIssueKind": "_367UGW_pathIssueKind",
579
+ "iconButton": "_367UGW_iconButton",
580
+ "empty": "_367UGW_empty",
581
+ "dshCmSpin": "_367UGW_dshCmSpin",
582
+ "errorActionLabel": "_367UGW_errorActionLabel",
583
+ "progressLabel": "_367UGW_progressLabel",
580
584
  "errorList": "_367UGW_errorList",
581
- "progressBarDone": "_367UGW_progressBarDone",
585
+ "conflictList": "_367UGW_conflictList",
586
+ "severityError": "_367UGW_severityError",
587
+ "banner": "_367UGW_banner",
588
+ "card": "_367UGW_card",
589
+ "pathMappingList": "_367UGW_pathMappingList",
590
+ "spinnerWrap": "_367UGW_spinnerWrap",
591
+ "badgeInfo": "_367UGW_badgeInfo",
592
+ "pathOld": "_367UGW_pathOld",
593
+ "pathRow": "_367UGW_pathRow",
594
+ "spinner": "_367UGW_spinner",
595
+ "groupItems": "_367UGW_groupItems",
596
+ "errorLine": "_367UGW_errorLine",
582
597
  "conflictItem": "_367UGW_conflictItem",
583
- "errorItem": "_367UGW_errorItem",
598
+ "progressBarDone": "_367UGW_progressBarDone",
584
599
  "sectionTitle": "_367UGW_sectionTitle",
585
- "sectionBody": "_367UGW_sectionBody",
600
+ "pathValue": "_367UGW_pathValue",
601
+ "dshCmIndeterminate": "_367UGW_dshCmIndeterminate",
586
602
  "progressBlock": "_367UGW_progressBlock",
587
- "hint": "_367UGW_hint",
603
+ "progressPercent": "_367UGW_progressPercent",
604
+ "badgeWarn": "_367UGW_badgeWarn",
605
+ "sectionSubtitle": "_367UGW_sectionSubtitle",
606
+ "conflictDetail": "_367UGW_conflictDetail",
588
607
  "hiddenFile": "_367UGW_hiddenFile",
589
- "groupList": "_367UGW_groupList",
590
- "progressDetail": "_367UGW_progressDetail",
591
- "conflictHead": "_367UGW_conflictHead",
592
- "errorLine": "_367UGW_errorLine",
593
- "optionsCard": "_367UGW_optionsCard",
594
608
  "groupCard": "_367UGW_groupCard",
595
- "card": "_367UGW_card",
609
+ "viewTabs": "_367UGW_viewTabs",
610
+ "spinnerLabel": "_367UGW_spinnerLabel",
611
+ "viewTab": "_367UGW_viewTab",
612
+ "progressIndeterminate": "_367UGW_progressIndeterminate",
613
+ "dangerButton": "_367UGW_dangerButton",
614
+ "field": "_367UGW_field",
615
+ "progressBar": "_367UGW_progressBar",
616
+ "sectionTitleBlock": "_367UGW_sectionTitleBlock",
617
+ "groupList": "_367UGW_groupList",
618
+ "progressTrack": "_367UGW_progressTrack",
619
+ "reportText": "_367UGW_reportText",
620
+ "rollbackBox": "_367UGW_rollbackBox",
621
+ "secretsList": "_367UGW_secretsList",
596
622
  "warnList": "_367UGW_warnList",
597
- "dshCmIndeterminate": "_367UGW_dshCmIndeterminate",
598
- "reportFooter": "_367UGW_reportFooter",
599
- "errorFooter": "_367UGW_errorFooter",
623
+ "pathNew": "_367UGW_pathNew",
624
+ "badgeOk": "_367UGW_badgeOk",
625
+ "statRow": "_367UGW_statRow",
600
626
  "errorBanner": "_367UGW_errorBanner",
601
- "groupHeader": "_367UGW_groupHeader",
627
+ "hint": "_367UGW_hint",
628
+ "sectionHeader": "_367UGW_sectionHeader",
629
+ "progressDetail": "_367UGW_progressDetail",
630
+ "reportFooter": "_367UGW_reportFooter",
631
+ "ghostButton": "_367UGW_ghostButton",
632
+ "checkboxRow": "_367UGW_checkboxRow",
633
+ "groupNote": "_367UGW_groupNote",
602
634
  "categoryItem": "_367UGW_categoryItem",
603
635
  "errorAction": "_367UGW_errorAction",
604
636
  "categoryDesc": "_367UGW_categoryDesc",
605
- "secretsList": "_367UGW_secretsList",
606
- "viewTab": "_367UGW_viewTab",
607
- "progressPercent": "_367UGW_progressPercent",
608
- "reportView": "_367UGW_reportView",
609
- "conflictDetail": "_367UGW_conflictDetail",
610
- "ghostButton": "_367UGW_ghostButton",
611
- "checkboxRow": "_367UGW_checkboxRow",
612
- "iconButton": "_367UGW_iconButton",
613
637
  "primaryButton": "_367UGW_primaryButton",
638
+ "categoryName": "_367UGW_categoryName",
639
+ "reportView": "_367UGW_reportView",
640
+ "errorFooter": "_367UGW_errorFooter",
641
+ "optionsCard": "_367UGW_optionsCard",
614
642
  "errorTitle": "_367UGW_errorTitle",
615
- "conflictOptions": "_367UGW_conflictOptions",
616
- "sectionHeader": "_367UGW_sectionHeader",
617
- "progressTrack": "_367UGW_progressTrack",
618
- "progressIndeterminate": "_367UGW_progressIndeterminate",
619
- "formError": "_367UGW_formError",
620
- "empty": "_367UGW_empty",
621
- "spinner": "_367UGW_spinner",
622
- "pathRow": "_367UGW_pathRow",
623
- "pathIssueKind": "_367UGW_pathIssueKind",
624
- "sectionSubtitle": "_367UGW_sectionSubtitle",
625
643
  "badgeError": "_367UGW_badgeError",
626
- "conflictId": "_367UGW_conflictId",
627
- "viewTabs": "_367UGW_viewTabs",
628
- "modeHint": "_367UGW_modeHint",
629
- "pathOld": "_367UGW_pathOld",
630
- "field": "_367UGW_field",
631
- "statRow": "_367UGW_statRow",
632
- "banner": "_367UGW_banner",
633
- "fieldLabel": "_367UGW_fieldLabel",
634
- "modeTabs": "_367UGW_modeTabs",
635
- "progressBar": "_367UGW_progressBar",
636
- "errorActionLabel": "_367UGW_errorActionLabel",
637
- "pathNew": "_367UGW_pathNew",
638
- "spinnerLabel": "_367UGW_spinnerLabel",
639
- "rollbackBox": "_367UGW_rollbackBox",
640
- "reportText": "_367UGW_reportText",
644
+ "badge": "_367UGW_badge",
645
+ "errorReason": "_367UGW_errorReason",
646
+ "modeTab": "_367UGW_modeTab",
641
647
  "viewBody": "_367UGW_viewBody",
642
- "severityError": "_367UGW_severityError",
643
- "conflictList": "_367UGW_conflictList",
644
- "badgeWarn": "_367UGW_badgeWarn",
645
- "progressLabel": "_367UGW_progressLabel",
646
- "secretFields": "_367UGW_secretFields",
647
- "pathValue": "_367UGW_pathValue",
648
- "badge": "_367UGW_badge"
648
+ "modeHint": "_367UGW_modeHint"
649
649
  };
650
650
  //#endregion
651
651
  //#region src/client/common/ui.tsx
@@ -1651,23 +1651,19 @@ function nextFlowPhase(list, from) {
1651
1651
  * 冲突决策列表(规范 §11,绑 src/ui/conflict-view.ts 的 ConflictCollector)。
1652
1652
  *
1653
1653
  * 渲染 plan 中 kind === 'Conflict' 的项,用户逐项选择
1654
- * Keep Current / Use Imported / Review;决策实时写入 collector,
1654
+ * Keep Current / Use Imported;决策实时写入 collector,
1655
1655
  * 完成时父组件调用 collector.toResolutions() → wizard.setResolutions()。
1656
+ *
1657
+ * 注意:不提供 "Review(稍后决定)" 选项——Review 会被收集器计为
1658
+ * unresolved,导致「下一步」永远禁用(死路)。要么决策,要么不进入本步。
1656
1659
  */
1657
- const RESOLUTION_OPTIONS = [
1658
- {
1659
- value: "keepCurrent",
1660
- key: "import.conflicts.keepCurrent"
1661
- },
1662
- {
1663
- value: "useImported",
1664
- key: "import.conflicts.useImported"
1665
- },
1666
- {
1667
- value: "review",
1668
- key: "import.conflicts.review"
1669
- }
1670
- ];
1660
+ const RESOLUTION_OPTIONS = [{
1661
+ value: "keepCurrent",
1662
+ key: "import.conflicts.keepCurrent"
1663
+ }, {
1664
+ value: "useImported",
1665
+ key: "import.conflicts.useImported"
1666
+ }];
1671
1667
  /** 冲突项决策列表 */
1672
1668
  function ConflictList({ collector, t, onChanged }) {
1673
1669
  const [tick, setTick] = (0, react.useState)(0);