dsh-desktop-kit 2.2.1 → 2.7.2
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/CHANGELOG.md +80 -0
- package/README.md +113 -27
- package/client.js +3121 -32
- package/docs/AGENT-HARNESS.md +144 -0
- package/docs/ARCHITECTURE.md +38 -7
- package/docs/COMPATIBILITY.md +4 -2
- package/docs/DEVELOPMENT.md +56 -0
- package/docs/INSTALL.md +20 -11
- package/docs/LAPTOP-INSTALL.md +96 -0
- package/docs/ROADMAP-3.0.md +353 -0
- package/docs/ROADMAP.md +1 -1
- package/docs/SUBAGENTS.md +25 -0
- package/docs/TEST-0.2.2.md +5 -5
- package/docs/TEST-2.3.0.md +49 -0
- package/docs/TEST-2.7.2.md +46 -0
- package/docs/TOKENS.md +2 -2
- package/docs/TROUBLESHOOTING.md +27 -0
- package/docs/UI-QA.md +28 -0
- package/docs/images/peak-toast-peak.png +0 -0
- package/docs/images/peak-toast-soon.png +0 -0
- package/docs/preview/peak-toast.html +155 -0
- package/index.js +18 -1
- package/package.json +26 -6
- package/plugins/dsh-agent-extensions/README.md +99 -0
- package/plugins/dsh-agent-extensions/index.js +167 -0
- package/plugins/dsh-agent-extensions/lib/agents.js +76 -0
- package/plugins/dsh-agent-extensions/lib/checkpoints.js +103 -0
- package/plugins/dsh-agent-extensions/lib/commands.js +255 -0
- package/plugins/dsh-agent-extensions/lib/hooks.js +182 -0
- package/plugins/dsh-agent-extensions/lib/policy.js +84 -0
- package/plugins/dsh-agent-extensions/lib/project.js +144 -0
- package/plugins/dsh-agent-extensions/lib/scaffold.js +151 -0
- package/plugins/dsh-agent-extensions/package.json +9 -0
- package/plugins/dsh-browser-qa/README.md +221 -0
- package/plugins/dsh-browser-qa/cordis.patch.yml +4 -0
- package/plugins/dsh-browser-qa/index.js +165 -0
- package/plugins/dsh-browser-qa/lib/cdp.js +224 -0
- package/plugins/dsh-browser-qa/lib/chrome.js +466 -0
- package/plugins/dsh-browser-qa/lib/pages.js +552 -0
- package/plugins/dsh-browser-qa/lib/qa.js +827 -0
- package/plugins/dsh-browser-qa/lib/ui-gate.js +256 -0
- package/plugins/dsh-browser-qa/package.json +31 -0
- package/plugins/dsh-checkpoints/README.md +22 -0
- package/plugins/dsh-checkpoints/cordis.patch.yml +3 -0
- package/plugins/dsh-checkpoints/index.js +80 -0
- package/plugins/dsh-checkpoints/lib/checkpoints.js +111 -0
- package/plugins/dsh-checkpoints/package.json +25 -0
- package/plugins/dsh-github-review/README.md +131 -0
- package/plugins/dsh-github-review/cordis.patch.yml +3 -0
- package/plugins/dsh-github-review/index.js +536 -0
- package/plugins/dsh-github-review/lib/gh.js +494 -0
- package/plugins/dsh-github-review/lib/normalize.js +662 -0
- package/plugins/dsh-github-review/package.json +28 -0
- package/plugins/dsh-guardrails/README.md +264 -0
- package/plugins/dsh-guardrails/cordis.patch.yml +5 -0
- package/plugins/dsh-guardrails/index.js +389 -0
- package/plugins/dsh-guardrails/lib/hooks.js +262 -0
- package/plugins/dsh-guardrails/lib/modes.js +77 -0
- package/plugins/dsh-guardrails/lib/policy.js +592 -0
- package/plugins/dsh-guardrails/lib/scan.js +451 -0
- package/plugins/dsh-guardrails/lib/secrets.js +274 -0
- package/plugins/dsh-guardrails/package.json +26 -0
- package/plugins/dsh-mcp-manager/README.md +78 -0
- package/plugins/dsh-mcp-manager/client.js +373 -0
- package/plugins/dsh-mcp-manager/index.js +159 -0
- package/plugins/dsh-mcp-manager/lib/api.js +250 -0
- package/plugins/dsh-mcp-manager/lib/client.js +219 -0
- package/plugins/dsh-mcp-manager/lib/config.js +217 -0
- package/plugins/dsh-mcp-manager/lib/manager.js +146 -0
- package/plugins/dsh-mcp-manager/package.json +18 -0
- package/plugins/dsh-mcp-manager/screenshots/modal-390.png +0 -0
- package/plugins/dsh-mcp-manager/screenshots/settings-1440.png +0 -0
- package/plugins/dsh-mcp-manager/screenshots/settings-390.png +0 -0
- package/plugins/dsh-memory-bridge/README.md +161 -0
- package/plugins/dsh-memory-bridge/client.js +2264 -0
- package/plugins/dsh-memory-bridge/cordis.patch.yml +5 -0
- package/plugins/dsh-memory-bridge/docs/images/conflicts.png +0 -0
- package/plugins/dsh-memory-bridge/docs/images/hero.png +0 -0
- package/plugins/dsh-memory-bridge/docs/images/settings-dark.png +0 -0
- package/plugins/dsh-memory-bridge/docs/images/settings-light.png +0 -0
- package/plugins/dsh-memory-bridge/docs/images/settings-mobile.png +0 -0
- package/plugins/dsh-memory-bridge/docs/images/setup.png +0 -0
- package/plugins/dsh-memory-bridge/icon.svg +13 -0
- package/plugins/dsh-memory-bridge/index.js +378 -0
- package/plugins/dsh-memory-bridge/lib/bridge.js +636 -0
- package/plugins/dsh-memory-bridge/lib/github.js +473 -0
- package/plugins/dsh-memory-bridge/lib/merge.js +205 -0
- package/plugins/dsh-memory-bridge/lib/portable.js +314 -0
- package/plugins/dsh-memory-bridge/lib/state.js +565 -0
- package/plugins/dsh-memory-bridge/lib/sync.js +984 -0
- package/plugins/dsh-memory-bridge/locale/en.json +6 -0
- package/plugins/dsh-memory-bridge/locale/ru.json +6 -0
- package/plugins/dsh-memory-bridge/package.json +67 -0
- package/plugins/dsh-peak-guard/README.md +16 -3
- package/plugins/dsh-peak-guard/client.js +223 -8
- package/plugins/dsh-peak-guard/package.json +1 -1
- package/plugins/dsh-plan-mode/README.md +24 -0
- package/plugins/dsh-plan-mode/cordis.patch.yml +3 -0
- package/plugins/dsh-plan-mode/index.js +103 -0
- package/plugins/dsh-plan-mode/lib/plan.js +89 -0
- package/plugins/dsh-plan-mode/package.json +18 -0
- package/plugins/dsh-profile-transfer/client.js +277 -17
- package/plugins/dsh-profile-transfer/index.js +10 -0
- package/plugins/dsh-profile-transfer/package.json +3 -1
- package/plugins/dsh-profile-transfer/transfer.js +101 -2
- package/plugins/dsh-project-memory/index.js +92 -2
- package/plugins/dsh-project-memory/package.json +1 -1
- package/plugins/dsh-repo-map/README.md +26 -0
- package/plugins/dsh-repo-map/cordis.patch.yml +3 -0
- package/plugins/dsh-repo-map/index.js +99 -0
- package/plugins/dsh-repo-map/lib/repo-map.js +170 -0
- package/plugins/dsh-repo-map/package.json +25 -0
- package/plugins/dsh-skill-autopilot/index.js +135 -11
- package/skills/delivery-gate/SKILL.md +31 -0
- package/skills/git-and-ci/SKILL.md +5 -1
- package/skills/manifest.json +31 -6
- package/skills/requirements-and-planning/SKILL.md +3 -0
- package/skills/research-first/SKILL.md +35 -0
- package/skills/research-first/references/query-patterns.md +20 -0
- package/skills/subagent-playbook/SKILL.md +36 -0
- package/skills/subagent-playbook/references/prompts.md +39 -0
- package/skills/ui-craft/SKILL.md +43 -0
- package/skills/visual-qa/SKILL.md +29 -0
- package/skills/visual-qa/references/commands.md +32 -0
- package/skills-baseline.json +219 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
const PLAN_FILE = ".dsh/PLAN.md";
|
|
5
|
+
|
|
6
|
+
export function planPath(cwd) {
|
|
7
|
+
return path.join(cwd, PLAN_FILE);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function parsePlan(text) {
|
|
11
|
+
const match = String(text ?? "").match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/u);
|
|
12
|
+
const front = match ? match[1] : "";
|
|
13
|
+
const body = (match ? String(text).slice(match[0].length) : String(text ?? "")).trim();
|
|
14
|
+
const field = (name) => {
|
|
15
|
+
const result = front.match(new RegExp(`^${name}:\\s*(.+)$`, "mu"));
|
|
16
|
+
return result ? result[1].trim().replace(/^"|"$/gu, "") : "";
|
|
17
|
+
};
|
|
18
|
+
const status = field("status") === "approved" ? "approved" : "draft";
|
|
19
|
+
return {
|
|
20
|
+
status,
|
|
21
|
+
createdAt: field("createdAt") || null,
|
|
22
|
+
updatedAt: field("updatedAt") || null,
|
|
23
|
+
approvedAt: field("approvedAt") || null,
|
|
24
|
+
body,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function readPlan(cwd) {
|
|
29
|
+
const file = planPath(cwd);
|
|
30
|
+
try {
|
|
31
|
+
const text = fs.readFileSync(file, "utf8");
|
|
32
|
+
return { exists: true, path: file, ...parsePlan(text) };
|
|
33
|
+
} catch {
|
|
34
|
+
return { exists: false, path: file, status: "draft", body: "" };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function serialize(plan) {
|
|
39
|
+
return [
|
|
40
|
+
"---",
|
|
41
|
+
`status: ${plan.status}`,
|
|
42
|
+
`createdAt: ${plan.createdAt ?? new Date().toISOString()}`,
|
|
43
|
+
`updatedAt: ${new Date().toISOString()}`,
|
|
44
|
+
`approvedAt: ${plan.approvedAt ?? ""}`,
|
|
45
|
+
"---",
|
|
46
|
+
"",
|
|
47
|
+
plan.body.trim(),
|
|
48
|
+
"",
|
|
49
|
+
].join("\n");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function writePlan(cwd, body, { status = "draft", approvedAt = null, force = false } = {}) {
|
|
53
|
+
const file = planPath(cwd);
|
|
54
|
+
if (fs.existsSync(file) && !force) {
|
|
55
|
+
throw new Error("PLAN.md уже существует; используйте update или force");
|
|
56
|
+
}
|
|
57
|
+
const now = new Date().toISOString();
|
|
58
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
59
|
+
const plan = { status, createdAt: now, updatedAt: now, approvedAt, body: String(body ?? "").trim() };
|
|
60
|
+
fs.writeFileSync(file, serialize(plan), "utf8");
|
|
61
|
+
return { path: file, ...plan };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function updatePlan(cwd, body) {
|
|
65
|
+
const current = readPlan(cwd);
|
|
66
|
+
return writePlan(cwd, body, {
|
|
67
|
+
status: current.status,
|
|
68
|
+
approvedAt: current.approvedAt,
|
|
69
|
+
force: true,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Approve only with explicit confirmation from the caller. The tool layer must
|
|
75
|
+
* pass `confirm: true` (the model is instructed to ask the user first).
|
|
76
|
+
*/
|
|
77
|
+
export function approvePlan(cwd, { confirm = false } = {}) {
|
|
78
|
+
if (confirm !== true) throw new Error("approve требует confirm: true после подтверждения пользователем");
|
|
79
|
+
const current = readPlan(cwd);
|
|
80
|
+
if (!current.exists) throw new Error("PLAN.md не найден");
|
|
81
|
+
const plan = writePlan(cwd, current.body, { status: "approved", approvedAt: new Date().toISOString(), force: true });
|
|
82
|
+
return plan;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function planSummary(plan, maxChars = 1200) {
|
|
86
|
+
if (!plan.exists) return "";
|
|
87
|
+
const body = plan.body.length <= maxChars ? plan.body : `${plan.body.slice(0, maxChars)}\n…`;
|
|
88
|
+
return body;
|
|
89
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-plan-mode",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "PLAN.md workflow for DSH: create/read/update a project plan and block code changes until the user explicitly approves it.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "index.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./index.js",
|
|
10
|
+
"./lib": "./lib/plan.js",
|
|
11
|
+
"./package.json": "./package.json"
|
|
12
|
+
},
|
|
13
|
+
"files": ["index.js", "lib/**", "cordis.patch.yml", "README.md"],
|
|
14
|
+
"dsh": {
|
|
15
|
+
"manifestVersion": 1,
|
|
16
|
+
"bundle": { "patch": "./cordis.patch.yml" }
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -79,8 +79,17 @@ window.__ModuleLoader__.load({
|
|
|
79
79
|
groupSkillsDesc: "All file-based skills (without node_modules)",
|
|
80
80
|
groupMemory: "Memory",
|
|
81
81
|
groupMemoryDesc: "memory/ — session log, project dossiers, environment facts",
|
|
82
|
-
groupProjectsMemory: "Project memory",
|
|
83
|
-
groupProjectsMemoryDesc: "projects-memory/ — dossiers and journals",
|
|
82
|
+
groupProjectsMemory: "Project memory (full backup)",
|
|
83
|
+
groupProjectsMemoryDesc: "projects-memory/ — dossiers and journals. For a USB drive or GitHub use Settings → Project memory; this item is only part of a full profile backup.",
|
|
84
|
+
modeTitle: "Transfer mode",
|
|
85
|
+
modeFull: "Profile and environment",
|
|
86
|
+
modeFullDesc: "Settings, plugins, skills and workspaces; project memory is separate",
|
|
87
|
+
modeProject: "Projects and code",
|
|
88
|
+
modeProjectDesc: "Source folders and project memory; settings stay untouched",
|
|
89
|
+
projectExportTitle: "Save projects and code",
|
|
90
|
+
projectExportSub: "Workspaces + source folders. Project memory is transferred separately in Project memory → USB.",
|
|
91
|
+
projectExportButton: "Save to USB",
|
|
92
|
+
projectModeHint: "Project memory dossiers live in Settings → Project memory → USB. This mode carries code and workspace folders.",
|
|
84
93
|
groupConfig: "Global configs",
|
|
85
94
|
groupConfigDesc: "AGENTS.md, dream-skin.json",
|
|
86
95
|
groupSessions: "Session history",
|
|
@@ -96,6 +105,18 @@ window.__ModuleLoader__.load({
|
|
|
96
105
|
noteLabel: "Note",
|
|
97
106
|
copiedPath: "Path copied",
|
|
98
107
|
archivePathHint: "Use the path shown after export, or type the archive path from a USB drive / cloud folder.",
|
|
108
|
+
usbTitle: "USB: projects and code",
|
|
109
|
+
usbHint: "Save workspaces and source folders to a USB drive, then find the archive on the other machine.",
|
|
110
|
+
usbPreset: "Select projects + code",
|
|
111
|
+
usbPresetHint: "Workspaces, workspace folders and project memory; profile settings are left out.",
|
|
112
|
+
usbPathLabel: "USB drive path",
|
|
113
|
+
usbPathPlaceholder: "E:\\",
|
|
114
|
+
usbScan: "Find archives",
|
|
115
|
+
usbScanning: "Scanning…",
|
|
116
|
+
usbNoArchives: "No dsh-profile-*.tar.gz archives found on this drive.",
|
|
117
|
+
usbFound: "{count} archive(s) found",
|
|
118
|
+
usbPick: "Check",
|
|
119
|
+
usbArchiveMeta: "{size} · {date}",
|
|
99
120
|
};
|
|
100
121
|
const RU = {
|
|
101
122
|
nav: "Перенос и резервные копии",
|
|
@@ -156,8 +177,17 @@ window.__ModuleLoader__.load({
|
|
|
156
177
|
groupSkillsDesc: "Все файловые навыки (без node_modules)",
|
|
157
178
|
groupMemory: "Память",
|
|
158
179
|
groupMemoryDesc: "memory/ — журнал сессий, досье проектов, факты окружения",
|
|
159
|
-
groupProjectsMemory: "Память проектов",
|
|
160
|
-
groupProjectsMemoryDesc: "projects-memory/ — досье и
|
|
180
|
+
groupProjectsMemory: "Память проектов (полный бэкап)",
|
|
181
|
+
groupProjectsMemoryDesc: "projects-memory/ — досье и журналы. Для флешки и GitHub используйте «Память проектов»; этот пункт нужен только для полного бэкапа профиля.",
|
|
182
|
+
modeTitle: "Режим переноса",
|
|
183
|
+
modeFull: "Профиль и окружение",
|
|
184
|
+
modeFullDesc: "Настройки, плагины, навыки и рабочие области; память проектов — отдельно",
|
|
185
|
+
modeProject: "Проекты и код",
|
|
186
|
+
modeProjectDesc: "Папки с исходниками и память проектов; настройки не трогаем",
|
|
187
|
+
projectExportTitle: "Сохранить проекты и код",
|
|
188
|
+
projectExportSub: "Рабочие области + папки с исходниками. Память проектов переносится отдельно, в «Память проектов → Флешка».",
|
|
189
|
+
projectExportButton: "Сохранить на флешку",
|
|
190
|
+
projectModeHint: "Досье памяти проектов — в «Память проектов → Флешка». Здесь — код и рабочие папки.",
|
|
161
191
|
groupConfig: "Глобальные конфиги",
|
|
162
192
|
groupConfigDesc: "AGENTS.md, dream-skin.json",
|
|
163
193
|
groupSessions: "История сессий",
|
|
@@ -173,6 +203,18 @@ window.__ModuleLoader__.load({
|
|
|
173
203
|
noteLabel: "Примечание",
|
|
174
204
|
copiedPath: "Путь скопирован",
|
|
175
205
|
archivePathHint: "Используйте путь из результата экспорта или впишите путь к архиву с флешки / из облака.",
|
|
206
|
+
usbTitle: "Флешка: проекты и код",
|
|
207
|
+
usbHint: "Сохраните рабочие области и папки с исходниками на флешку, а на другой машине найдите архив и восстановите.",
|
|
208
|
+
usbPreset: "Выбрать «Проекты и код»",
|
|
209
|
+
usbPresetHint: "Рабочие области, папки с кодом и память проектов; настройки профиля не затрагиваются.",
|
|
210
|
+
usbPathLabel: "Путь к флешке",
|
|
211
|
+
usbPathPlaceholder: "E:\\",
|
|
212
|
+
usbScan: "Найти архивы",
|
|
213
|
+
usbScanning: "Сканирую…",
|
|
214
|
+
usbNoArchives: "На флешке не найдено архивов dsh-profile-*.tar.gz.",
|
|
215
|
+
usbFound: "Найдено архивов: {count}",
|
|
216
|
+
usbPick: "Проверить",
|
|
217
|
+
usbArchiveMeta: "{size} · {date}",
|
|
176
218
|
};
|
|
177
219
|
const ZH = {
|
|
178
220
|
nav: "迁移与备份",
|
|
@@ -233,8 +275,17 @@ window.__ModuleLoader__.load({
|
|
|
233
275
|
groupSkillsDesc: "全部文件型技能(不含 node_modules)",
|
|
234
276
|
groupMemory: "记忆",
|
|
235
277
|
groupMemoryDesc: "memory/ — 会话日志、项目档案、环境事实",
|
|
236
|
-
groupProjectsMemory: "
|
|
237
|
-
groupProjectsMemoryDesc: "projects-memory/ —
|
|
278
|
+
groupProjectsMemory: "项目记忆(完整备份)",
|
|
279
|
+
groupProjectsMemoryDesc: "projects-memory/ — 档案与日志。U 盘或 GitHub 请使用「项目记忆」;此项仅用于完整配置备份。",
|
|
280
|
+
modeTitle: "传输模式",
|
|
281
|
+
modeFull: "配置与环境",
|
|
282
|
+
modeFullDesc: "设置、插件、技能和工作区;项目记忆单独传输",
|
|
283
|
+
modeProject: "项目与代码",
|
|
284
|
+
modeProjectDesc: "源码文件夹和项目记忆;不修改设置",
|
|
285
|
+
projectExportTitle: "保存项目与代码",
|
|
286
|
+
projectExportSub: "工作区 + 源码文件夹。项目记忆在「项目记忆 → U 盘」中单独传输。",
|
|
287
|
+
projectExportButton: "保存到 U 盘",
|
|
288
|
+
projectModeHint: "项目记忆档案在「项目记忆 → U 盘」。此处传输代码和工作区文件夹。",
|
|
238
289
|
groupConfig: "全局配置",
|
|
239
290
|
groupConfigDesc: "AGENTS.md、dream-skin.json",
|
|
240
291
|
groupSessions: "会话历史",
|
|
@@ -250,6 +301,18 @@ window.__ModuleLoader__.load({
|
|
|
250
301
|
noteLabel: "说明",
|
|
251
302
|
copiedPath: "路径已复制",
|
|
252
303
|
archivePathHint: "使用导出结果中的路径,或输入 U 盘 / 云盘中的归档路径。",
|
|
304
|
+
usbTitle: "U 盘:项目与代码",
|
|
305
|
+
usbHint: "将工作区和源码文件夹保存到 U 盘,在另一台机器上找到归档并恢复。",
|
|
306
|
+
usbPreset: "选择「项目 + 代码」",
|
|
307
|
+
usbPresetHint: "工作区、工作区文件夹和项目记忆;不包含配置设置。",
|
|
308
|
+
usbPathLabel: "U 盘路径",
|
|
309
|
+
usbPathPlaceholder: "E:\\",
|
|
310
|
+
usbScan: "查找归档",
|
|
311
|
+
usbScanning: "扫描中…",
|
|
312
|
+
usbNoArchives: "U 盘上没有 dsh-profile-*.tar.gz 归档。",
|
|
313
|
+
usbFound: "找到 {count} 个归档",
|
|
314
|
+
usbPick: "检查",
|
|
315
|
+
usbArchiveMeta: "{size} · {date}",
|
|
253
316
|
};
|
|
254
317
|
|
|
255
318
|
const inject = ["slots", "locale", "connection"];
|
|
@@ -316,9 +379,34 @@ window.__ModuleLoader__.load({
|
|
|
316
379
|
.dpt-step-num{font-variant-numeric:tabular-nums;font-size:11px;font-weight:700;letter-spacing:.08em;color:var(--dsw-alias-brand-primary,#2f7df6)}
|
|
317
380
|
.dpt-step-label{font-size:12px;font-weight:600;letter-spacing:.04em;color:var(--dsw-alias-label-secondary,#5b6472);text-transform:uppercase}
|
|
318
381
|
.dpt-card{display:flex;flex-direction:column;gap:14px;padding:18px 18px 16px;border:1px solid var(--dsw-alias-border-l1,rgba(0,0,0,.1));border-radius:14px;background:var(--dsw-alias-bg-layer-1,rgba(0,0,0,.02))}
|
|
319
|
-
.dpt-card-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px}
|
|
382
|
+
.dpt-card-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;flex-wrap:wrap}
|
|
320
383
|
.dpt-card-title{font-size:15px;line-height:20px;font-weight:650;color:var(--dsw-alias-label-primary,#111)}
|
|
321
384
|
.dpt-card-sub{font-size:12.5px;line-height:18px;color:var(--dsw-alias-label-secondary,#5b6472);margin-top:3px;max-width:62ch}
|
|
385
|
+
.dpt-modes{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin:2px 0 4px}
|
|
386
|
+
.dpt-mode{display:flex;align-items:flex-start;gap:10px;text-align:left;padding:11px 12px;border-radius:12px;border:1px solid var(--dsw-alias-border-l1,rgba(0,0,0,.1));background:var(--dsw-alias-bg-base,#fff);color:var(--dsw-alias-label-primary,#111);cursor:pointer;font:inherit;transition:border-color .16s ease,background .16s ease,box-shadow .16s ease}
|
|
387
|
+
.dpt-mode:hover{border-color:color-mix(in srgb,var(--dsw-alias-brand-primary,#2f7df6) 35%,transparent)}
|
|
388
|
+
.dpt-mode:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#2f7df6);outline-offset:2px}
|
|
389
|
+
.dpt-mode[data-active="true"]{border-color:color-mix(in srgb,var(--dsw-alias-brand-primary,#2f7df6) 48%,transparent);background:color-mix(in srgb,var(--dsw-alias-brand-primary,#2f7df6) 6%,var(--dsw-alias-bg-base,#fff));box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--dsw-alias-brand-primary,#2f7df6) 14%,transparent)}
|
|
390
|
+
.dpt-mode-icon{flex:none;width:30px;height:30px;border-radius:9px;display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,var(--dsw-alias-label-primary,#111) 6%,transparent);color:var(--dsw-alias-label-secondary,#5b6472)}
|
|
391
|
+
.dpt-mode[data-active="true"] .dpt-mode-icon{background:color-mix(in srgb,var(--dsw-alias-brand-primary,#2f7df6) 13%,transparent);color:var(--dsw-alias-brand-primary,#2f7df6)}
|
|
392
|
+
.dpt-mode-copy{display:flex;flex-direction:column;gap:2px;min-width:0}
|
|
393
|
+
.dpt-mode-title{font-size:12.5px;line-height:17px;font-weight:650}
|
|
394
|
+
.dpt-mode-desc{font-size:10.5px;line-height:14px;color:var(--dsw-alias-label-secondary,#6b7280)}
|
|
395
|
+
@media (max-width:640px){.dpt-modes{grid-template-columns:1fr}}
|
|
396
|
+
.dpt-kv{flex-wrap:wrap;min-width:0}
|
|
397
|
+
.dpt-kv span{min-width:0}
|
|
398
|
+
.dpt-mono{overflow-wrap:anywhere}
|
|
399
|
+
.dpt-row-inline{min-width:0}
|
|
400
|
+
@media (max-width:560px){
|
|
401
|
+
.dpt-kv{flex-direction:column;align-items:flex-start;gap:3px}
|
|
402
|
+
.dpt-kv span:last-child{text-align:left}
|
|
403
|
+
.dpt-row-inline{width:100%;justify-content:flex-start}
|
|
404
|
+
.dpt-card{padding:14px 13px 13px}
|
|
405
|
+
.dpt-input,.dpt-btn{max-width:100%}
|
|
406
|
+
.dpt-row{flex-wrap:wrap}
|
|
407
|
+
.dpt-row-text{flex:1 1 calc(100% - 27px);min-width:0}
|
|
408
|
+
.dpt-row-size{width:100%;padding-left:27px;text-align:left;white-space:normal}
|
|
409
|
+
}
|
|
322
410
|
.dpt-chip{flex:none;font-size:11px;font-weight:600;letter-spacing:.04em;padding:3px 8px;border-radius:999px;background:var(--dsw-alias-bg-layer-2,rgba(0,0,0,.06));color:var(--dsw-alias-label-secondary,#4b5563);font-variant-numeric:tabular-nums;white-space:nowrap}
|
|
323
411
|
.dpt-list{display:flex;flex-direction:column;border-top:1px solid var(--dsw-alias-border-l1,rgba(0,0,0,.07))}
|
|
324
412
|
.dpt-row{display:flex;align-items:flex-start;gap:12px;padding:10px 2px;border-bottom:1px solid var(--dsw-alias-border-l1,rgba(0,0,0,.07));cursor:pointer}
|
|
@@ -355,6 +443,7 @@ window.__ModuleLoader__.load({
|
|
|
355
443
|
.dpt-kv{display:flex;justify-content:space-between;gap:16px;font-size:12px;line-height:18px}
|
|
356
444
|
.dpt-kv span:first-child{color:var(--dsw-alias-label-secondary,#6b7280)}
|
|
357
445
|
.dpt-kv span:last-child{color:var(--dsw-alias-label-primary,#111);font-variant-numeric:tabular-nums;text-align:right}
|
|
446
|
+
.dpt-row-inline{display:flex;align-items:center;justify-content:flex-end;gap:8px;flex-wrap:wrap}
|
|
358
447
|
.dpt-divider{height:1px;background:var(--dsw-alias-border-l1,rgba(0,0,0,.08));margin:2px 0}
|
|
359
448
|
.dpt-grid2{display:grid;grid-template-columns:1fr 1fr;gap:10px}
|
|
360
449
|
@media (max-width:560px){.dpt-grid2{grid-template-columns:1fr}.dpt-steps{flex-direction:column}.dpt-step + .dpt-step{border-left:none;border-top:1px solid var(--dsw-alias-border-l1,rgba(0,0,0,.08));padding-left:0}}
|
|
@@ -427,6 +516,10 @@ window.__ModuleLoader__.load({
|
|
|
427
516
|
const [confirmed, setConfirmed] = useState(false);
|
|
428
517
|
const [imported, setImported] = useState(null);
|
|
429
518
|
const [copied, setCopied] = useState(false);
|
|
519
|
+
const [usbPath, setUsbPath] = useState("");
|
|
520
|
+
const [usbArchives, setUsbArchives] = useState([]);
|
|
521
|
+
const [usbBusy, setUsbBusy] = useState(false);
|
|
522
|
+
const [mode, setMode] = useState("full");
|
|
430
523
|
|
|
431
524
|
useEffect(() => {
|
|
432
525
|
let alive = true;
|
|
@@ -440,7 +533,7 @@ window.__ModuleLoader__.load({
|
|
|
440
533
|
}
|
|
441
534
|
const value = result.value;
|
|
442
535
|
setOptions(value);
|
|
443
|
-
setSelected({ ...value.selection });
|
|
536
|
+
setSelected({ ...value.selection, projectsMemory: false });
|
|
444
537
|
setDestination(value.destination ?? "");
|
|
445
538
|
setPhase("ready");
|
|
446
539
|
})
|
|
@@ -461,7 +554,42 @@ window.__ModuleLoader__.load({
|
|
|
461
554
|
.filter((row) => row !== undefined);
|
|
462
555
|
}, [options]);
|
|
463
556
|
|
|
464
|
-
const
|
|
557
|
+
const visibleGroups = useMemo(
|
|
558
|
+
() =>
|
|
559
|
+
groups.filter(
|
|
560
|
+
(group) =>
|
|
561
|
+
group.id !== "projectsMemory" &&
|
|
562
|
+
(mode === "full" || group.id === "workspaces" || group.id === "workspaceFolders"),
|
|
563
|
+
),
|
|
564
|
+
[groups, mode],
|
|
565
|
+
);
|
|
566
|
+
|
|
567
|
+
const selectedCount = visibleGroups.filter((group) => selected[group.id] === true).length;
|
|
568
|
+
|
|
569
|
+
function switchMode(next) {
|
|
570
|
+
setMode(next);
|
|
571
|
+
if (next === "project") {
|
|
572
|
+
setSelected({
|
|
573
|
+
profile: false,
|
|
574
|
+
localPlugins: false,
|
|
575
|
+
workspaces: true,
|
|
576
|
+
workspaceFolders: true,
|
|
577
|
+
skills: false,
|
|
578
|
+
memory: false,
|
|
579
|
+
projectsMemory: false,
|
|
580
|
+
config: false,
|
|
581
|
+
sessions: false,
|
|
582
|
+
attachments: false,
|
|
583
|
+
credentials: false,
|
|
584
|
+
});
|
|
585
|
+
} else {
|
|
586
|
+
setSelected({ ...(options?.selection ?? {}), projectsMemory: false });
|
|
587
|
+
}
|
|
588
|
+
setPreview(null);
|
|
589
|
+
setImported(null);
|
|
590
|
+
setMessage(null);
|
|
591
|
+
if (next === "project" && usbPath.trim().length > 0) setDestination(usbPath.trim());
|
|
592
|
+
}
|
|
465
593
|
|
|
466
594
|
function toggle(id, value) {
|
|
467
595
|
setSelected((current) => ({ ...current, [id]: value }));
|
|
@@ -491,18 +619,20 @@ window.__ModuleLoader__.load({
|
|
|
491
619
|
}
|
|
492
620
|
}
|
|
493
621
|
|
|
494
|
-
async function doInspect() {
|
|
622
|
+
async function doInspect(targetPath) {
|
|
495
623
|
if (busy !== null) return;
|
|
496
|
-
|
|
624
|
+
const requested = typeof targetPath === "string" ? targetPath.trim() : archivePath.trim();
|
|
625
|
+
if (requested.length === 0) {
|
|
497
626
|
setMessage({ tone: "error", text: t("missingArchive") });
|
|
498
627
|
return;
|
|
499
628
|
}
|
|
629
|
+
setArchivePath(requested);
|
|
500
630
|
setBusy("inspect");
|
|
501
631
|
setMessage(null);
|
|
502
632
|
setPreview(null);
|
|
503
633
|
setImported(null);
|
|
504
634
|
try {
|
|
505
|
-
const result = await call("inspect", { archivePath:
|
|
635
|
+
const result = await call("inspect", { archivePath: requested });
|
|
506
636
|
if (!result?.ok) {
|
|
507
637
|
setMessage({ tone: "error", text: `${t("errorPrefix")} ${result?.error?.message ?? "unknown"}` });
|
|
508
638
|
return;
|
|
@@ -515,6 +645,34 @@ window.__ModuleLoader__.load({
|
|
|
515
645
|
}
|
|
516
646
|
}
|
|
517
647
|
|
|
648
|
+
async function findArchives() {
|
|
649
|
+
if (busy !== null || usbBusy) return;
|
|
650
|
+
if (usbPath.trim().length === 0) {
|
|
651
|
+
setMessage({ tone: "error", text: t("usbPathPlaceholder") });
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
setUsbBusy(true);
|
|
655
|
+
setMessage(null);
|
|
656
|
+
try {
|
|
657
|
+
const result = await call("find-archives", { directory: usbPath.trim() });
|
|
658
|
+
if (!result?.ok) {
|
|
659
|
+
setMessage({ tone: "error", text: `${t("errorPrefix")} ${result?.error?.message ?? "unknown"}` });
|
|
660
|
+
setUsbArchives([]);
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
const archives = Array.isArray(result.value?.archives) ? result.value.archives : [];
|
|
664
|
+
setUsbArchives(archives);
|
|
665
|
+
setMessage({
|
|
666
|
+
tone: archives.length > 0 ? "ok" : "error",
|
|
667
|
+
text: archives.length > 0 ? t("usbFound").replace("{count}", String(archives.length)) : t("usbNoArchives"),
|
|
668
|
+
});
|
|
669
|
+
} catch (error) {
|
|
670
|
+
setMessage({ tone: "error", text: `${t("errorPrefix")} ${String(error?.message ?? error)}` });
|
|
671
|
+
} finally {
|
|
672
|
+
setUsbBusy(false);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
518
676
|
async function doImport() {
|
|
519
677
|
if (busy !== null) return;
|
|
520
678
|
if (!confirmed) {
|
|
@@ -571,7 +729,7 @@ window.__ModuleLoader__.load({
|
|
|
571
729
|
const stepOneGroups = h(
|
|
572
730
|
"div",
|
|
573
731
|
{ className: "dpt-list" },
|
|
574
|
-
|
|
732
|
+
visibleGroups.map((group) => h(GroupRow, {
|
|
575
733
|
key: group.id,
|
|
576
734
|
group,
|
|
577
735
|
checked: selected[group.id] === true,
|
|
@@ -600,12 +758,48 @@ window.__ModuleLoader__.load({
|
|
|
600
758
|
"div",
|
|
601
759
|
{ className: "dpt-card-head" },
|
|
602
760
|
h("div", null,
|
|
603
|
-
h("div", { className: "dpt-card-title", id: "dpt-export-title" }, t("exportTitle")),
|
|
604
|
-
h("div", { className: "dpt-card-sub" }, t("exportSubtitle")),
|
|
761
|
+
h("div", { className: "dpt-card-title", id: "dpt-export-title" }, mode === "project" ? t("projectExportTitle") : t("exportTitle")),
|
|
762
|
+
h("div", { className: "dpt-card-sub" }, mode === "project" ? t("projectExportSub") : t("exportSubtitle")),
|
|
605
763
|
),
|
|
606
|
-
h("span", { className: "dpt-chip" }, phase === "loading" ? "…" : `${selectedCount} / ${
|
|
764
|
+
h("span", { className: "dpt-chip" }, phase === "loading" ? "…" : `${selectedCount} / ${visibleGroups.length}`),
|
|
607
765
|
),
|
|
608
766
|
phase === "loading" && h("div", { className: "dpt-hint" }, t("optionsLoading")),
|
|
767
|
+
phase !== "loading"
|
|
768
|
+
? h(
|
|
769
|
+
"div",
|
|
770
|
+
{ className: "dpt-modes", role: "tablist", "aria-label": t("modeTitle") },
|
|
771
|
+
[
|
|
772
|
+
["full", "modeFull", "modeFullDesc"],
|
|
773
|
+
["project", "modeProject", "modeProjectDesc"],
|
|
774
|
+
].map(([id, titleKey, descKey]) =>
|
|
775
|
+
h(
|
|
776
|
+
"button",
|
|
777
|
+
{
|
|
778
|
+
key: id,
|
|
779
|
+
type: "button",
|
|
780
|
+
role: "tab",
|
|
781
|
+
className: "dpt-mode",
|
|
782
|
+
"data-active": mode === id ? "true" : "false",
|
|
783
|
+
"aria-selected": mode === id ? "true" : "false",
|
|
784
|
+
onClick: () => switchMode(id),
|
|
785
|
+
},
|
|
786
|
+
h(
|
|
787
|
+
"span",
|
|
788
|
+
{ className: "dpt-mode-icon", "aria-hidden": "true" },
|
|
789
|
+
id === "full"
|
|
790
|
+
? h("svg", { viewBox: "0 0 20 20", width: 17, height: 17, fill: "none" }, h("path", { d: "M3.5 6.5h13v8h-13zM3.5 6.5 5 3.5h10l1.5 3M6.5 14v2h7v-2", stroke: "currentColor", strokeWidth: 1.4, strokeLinejoin: "round" }))
|
|
791
|
+
: h("svg", { viewBox: "0 0 20 20", width: 17, height: 17, fill: "none" }, h("path", { d: "M3.5 5.5h4l1.5 2h7.5v7.5h-13zM3.5 5.5V4h5l1.5 1.5", stroke: "currentColor", strokeWidth: 1.4, strokeLinejoin: "round" })),
|
|
792
|
+
),
|
|
793
|
+
h(
|
|
794
|
+
"span",
|
|
795
|
+
{ className: "dpt-mode-copy" },
|
|
796
|
+
h("span", { className: "dpt-mode-title" }, t(titleKey)),
|
|
797
|
+
h("span", { className: "dpt-mode-desc" }, t(descKey)),
|
|
798
|
+
),
|
|
799
|
+
),
|
|
800
|
+
),
|
|
801
|
+
)
|
|
802
|
+
: null,
|
|
609
803
|
phase !== "loading" && stepOneGroups,
|
|
610
804
|
h(
|
|
611
805
|
"div",
|
|
@@ -628,7 +822,7 @@ window.__ModuleLoader__.load({
|
|
|
628
822
|
"button",
|
|
629
823
|
{ type: "button", className: "dpt-btn", "data-tone": "primary", disabled: busy !== null || phase !== "ready", onClick: doExport },
|
|
630
824
|
busy === "export" ? h(Spinner) : null,
|
|
631
|
-
busy === "export" ? t("exporting") : t("exportButton"),
|
|
825
|
+
busy === "export" ? t("exporting") : mode === "project" ? t("projectExportButton") : t("exportButton"),
|
|
632
826
|
),
|
|
633
827
|
exported !== null
|
|
634
828
|
? h(
|
|
@@ -653,6 +847,72 @@ window.__ModuleLoader__.load({
|
|
|
653
847
|
: null,
|
|
654
848
|
),
|
|
655
849
|
|
|
850
|
+
/* ---------------- USB / projects and code ---------------- */
|
|
851
|
+
mode === "project"
|
|
852
|
+
? h(
|
|
853
|
+
"section",
|
|
854
|
+
{ className: "dpt-card", "aria-labelledby": "dpt-usb-title" },
|
|
855
|
+
h(
|
|
856
|
+
"div",
|
|
857
|
+
{ className: "dpt-card-head" },
|
|
858
|
+
h(
|
|
859
|
+
"div",
|
|
860
|
+
null,
|
|
861
|
+
h("div", { className: "dpt-card-title", id: "dpt-usb-title" }, t("usbTitle")),
|
|
862
|
+
h("div", { className: "dpt-card-sub" }, t("usbHint")),
|
|
863
|
+
),
|
|
864
|
+
h("span", { className: "dpt-chip" }, "USB"),
|
|
865
|
+
),
|
|
866
|
+
h(
|
|
867
|
+
"div",
|
|
868
|
+
{ className: "dpt-field" },
|
|
869
|
+
h("label", { className: "dpt-label", htmlFor: "dpt-usb-path" }, t("usbPathLabel")),
|
|
870
|
+
h("input", {
|
|
871
|
+
id: "dpt-usb-path",
|
|
872
|
+
className: "dpt-input dpt-input--path",
|
|
873
|
+
type: "text",
|
|
874
|
+
value: usbPath,
|
|
875
|
+
placeholder: t("usbPathPlaceholder"),
|
|
876
|
+
spellCheck: false,
|
|
877
|
+
onChange: (event) => setUsbPath(event.target.value),
|
|
878
|
+
}),
|
|
879
|
+
),
|
|
880
|
+
h(
|
|
881
|
+
"div",
|
|
882
|
+
{ className: "dpt-actions" },
|
|
883
|
+
h(
|
|
884
|
+
"button",
|
|
885
|
+
{ type: "button", className: "dpt-btn", disabled: busy !== null || usbBusy, onClick: findArchives },
|
|
886
|
+
usbBusy ? h(Spinner) : null,
|
|
887
|
+
usbBusy ? t("usbScanning") : t("usbScan"),
|
|
888
|
+
),
|
|
889
|
+
),
|
|
890
|
+
usbArchives.length > 0
|
|
891
|
+
? h(
|
|
892
|
+
"div",
|
|
893
|
+
{ className: "dpt-preview" },
|
|
894
|
+
usbArchives.map((archive) =>
|
|
895
|
+
h(
|
|
896
|
+
"div",
|
|
897
|
+
{ className: "dpt-kv", key: archive.path },
|
|
898
|
+
h("span", { className: "dpt-mono" }, archive.name),
|
|
899
|
+
h(
|
|
900
|
+
"span",
|
|
901
|
+
{ className: "dpt-row-inline" },
|
|
902
|
+
h("span", null, t("usbArchiveMeta").replace("{size}", archive.human ?? "").replace("{date}", new Date(archive.mtimeMs).toLocaleDateString())),
|
|
903
|
+
h(
|
|
904
|
+
"button",
|
|
905
|
+
{ type: "button", className: "dpt-btn", disabled: busy !== null, onClick: () => void doInspect(archive.path) },
|
|
906
|
+
t("usbPick"),
|
|
907
|
+
),
|
|
908
|
+
),
|
|
909
|
+
),
|
|
910
|
+
),
|
|
911
|
+
)
|
|
912
|
+
: null,
|
|
913
|
+
)
|
|
914
|
+
: null,
|
|
915
|
+
|
|
656
916
|
/* ---------------- import ---------------- */
|
|
657
917
|
h(
|
|
658
918
|
"section",
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
defaultHome,
|
|
22
22
|
describeSources,
|
|
23
23
|
exportBundle,
|
|
24
|
+
findTransferArchives,
|
|
24
25
|
humanSize,
|
|
25
26
|
inspectBundle,
|
|
26
27
|
normalizeSelection,
|
|
@@ -204,6 +205,14 @@ async function handleExport(ctx, payload) {
|
|
|
204
205
|
};
|
|
205
206
|
}
|
|
206
207
|
|
|
208
|
+
async function handleFindArchives(ctx, payload) {
|
|
209
|
+
const directory = typeof payload?.directory === "string" ? payload.directory.trim() : "";
|
|
210
|
+
if (directory.length === 0) throw new TransferError("transfer/bad-request", "укажите путь к флешке");
|
|
211
|
+
if (!fs.existsSync(directory)) throw new TransferError("transfer/not-found", `папка не найдена: ${directory}`);
|
|
212
|
+
const result = findTransferArchives(directory);
|
|
213
|
+
return { ok: true, root: result.root, archives: result.archives };
|
|
214
|
+
}
|
|
215
|
+
|
|
207
216
|
async function handleInspect(ctx, payload) {
|
|
208
217
|
const archivePath = typeof payload?.archivePath === "string" ? payload.archivePath.trim() : "";
|
|
209
218
|
if (archivePath.length === 0) throw new TransferError("transfer/bad-request", "укажите путь к архиву");
|
|
@@ -364,6 +373,7 @@ export function apply(ctx) {
|
|
|
364
373
|
}
|
|
365
374
|
registerOperation(ctx, "options", (context) => handleOptions(context));
|
|
366
375
|
registerOperation(ctx, "export", (context, payload) => handleExport(context, payload));
|
|
376
|
+
registerOperation(ctx, "find-archives", (context, payload) => handleFindArchives(context, payload));
|
|
367
377
|
registerOperation(ctx, "inspect", (context, payload) => handleInspect(context, payload));
|
|
368
378
|
registerOperation(ctx, "import", (context, payload) => handleImport(context, payload));
|
|
369
379
|
registerOperation(ctx, "install", (context) => handleInstall(context));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-profile-transfer",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.3",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Move a complete DSH setup between machines: pick what to export (profile, skills, memory, local plugins, sessions, secrets), pack one archive, restore it on a fresh install.",
|
|
6
6
|
"type": "module",
|
|
@@ -34,3 +34,5 @@
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
|
+
|
|
38
|
+
|