dsh-desktop-kit 0.0.0-stage → 1.0.0

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.
Files changed (177) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +87 -2
  3. package/client.js +3349 -0
  4. package/cordis.patch.yml +5 -0
  5. package/index.js +80 -0
  6. package/package.json +60 -4
  7. package/plugins/dsh-gkb-ru-ui/cordis.patch.yml +6 -0
  8. package/plugins/dsh-gkb-ru-ui/lib/client.js +1083 -0
  9. package/plugins/dsh-gkb-ru-ui/lib/index.js +142 -0
  10. package/plugins/dsh-gkb-ru-ui/lib/skills.js +376 -0
  11. package/plugins/dsh-gkb-ru-ui/package.json +28 -0
  12. package/plugins/dsh-peak-guard/README.md +70 -0
  13. package/plugins/dsh-peak-guard/client.js +741 -0
  14. package/plugins/dsh-peak-guard/cordis.patch.yml +5 -0
  15. package/plugins/dsh-peak-guard/icon.svg +12 -0
  16. package/plugins/dsh-peak-guard/index.js +2 -0
  17. package/plugins/dsh-peak-guard/locale/en.json +6 -0
  18. package/plugins/dsh-peak-guard/locale/ru.json +6 -0
  19. package/plugins/dsh-peak-guard/locale/zh.json +6 -0
  20. package/plugins/dsh-peak-guard/package.json +33 -0
  21. package/plugins/dsh-profile-transfer/README.md +62 -0
  22. package/plugins/dsh-profile-transfer/bootstrap/01-INSTALL.cmd +7 -0
  23. package/plugins/dsh-profile-transfer/bootstrap/README-ARCHIVE.md +54 -0
  24. package/plugins/dsh-profile-transfer/bootstrap/README-/320/243/320/241/320/242/320/220/320/235/320/236/320/222/320/232/320/220.md +42 -0
  25. package/plugins/dsh-profile-transfer/bootstrap/install-on-clean-machine.ps1 +119 -0
  26. package/plugins/dsh-profile-transfer/bootstrap//320/243/320/241/320/242/320/220/320/235/320/236/320/222/320/230/320/242/320/254.cmd +5 -0
  27. package/plugins/dsh-profile-transfer/client.js +806 -0
  28. package/plugins/dsh-profile-transfer/cordis.patch.yml +3 -0
  29. package/plugins/dsh-profile-transfer/icon.svg +13 -0
  30. package/plugins/dsh-profile-transfer/index.js +375 -0
  31. package/plugins/dsh-profile-transfer/locale/en.json +6 -0
  32. package/plugins/dsh-profile-transfer/locale/ru.json +6 -0
  33. package/plugins/dsh-profile-transfer/locale/zh.json +6 -0
  34. package/plugins/dsh-profile-transfer/package.json +36 -0
  35. package/plugins/dsh-profile-transfer/transfer.js +1004 -0
  36. package/plugins/dsh-project-memory/README.md +71 -0
  37. package/plugins/dsh-project-memory/cordis.patch.yml +4 -0
  38. package/plugins/dsh-project-memory/icon.svg +12 -0
  39. package/plugins/dsh-project-memory/index.js +1240 -0
  40. package/plugins/dsh-project-memory/locale/en.json +6 -0
  41. package/plugins/dsh-project-memory/locale/ru.json +6 -0
  42. package/plugins/dsh-project-memory/locale/zh.json +6 -0
  43. package/plugins/dsh-project-memory/package.json +26 -0
  44. package/plugins/dsh-session-move/README.md +67 -0
  45. package/plugins/dsh-session-move/client.js +719 -0
  46. package/plugins/dsh-session-move/cordis.patch.yml +5 -0
  47. package/plugins/dsh-session-move/icon.svg +14 -0
  48. package/plugins/dsh-session-move/index.js +497 -0
  49. package/plugins/dsh-session-move/locale/en.json +6 -0
  50. package/plugins/dsh-session-move/locale/ru.json +6 -0
  51. package/plugins/dsh-session-move/locale/zh.json +6 -0
  52. package/plugins/dsh-session-move/package.json +31 -0
  53. package/skills/code-review-checklist/SKILL.md +37 -0
  54. package/skills/content-writing/README.md +87 -0
  55. package/skills/content-writing/SKILL.md +348 -0
  56. package/skills/content-writing/assets/checklists/fact-check.md +53 -0
  57. package/skills/content-writing/assets/checklists/publish.md +65 -0
  58. package/skills/content-writing/assets/checklists/social.md +40 -0
  59. package/skills/content-writing/assets/templates/article.md +47 -0
  60. package/skills/content-writing/assets/templates/brief.md +44 -0
  61. package/skills/content-writing/assets/templates/doctor-card.md +41 -0
  62. package/skills/content-writing/assets/templates/image-prompts.md +128 -0
  63. package/skills/content-writing/assets/templates/post-telegram.md +36 -0
  64. package/skills/content-writing/assets/templates/post-vk.md +42 -0
  65. package/skills/content-writing/assets/templates/press-release.md +56 -0
  66. package/skills/content-writing/assets/templates/science-pop-note.md +55 -0
  67. package/skills/content-writing/assets/templates/service-card.md +46 -0
  68. package/skills/content-writing/references/anti-ai-style.md +248 -0
  69. package/skills/content-writing/references/editing.md +185 -0
  70. package/skills/content-writing/references/fact-checking.md +205 -0
  71. package/skills/content-writing/references/government-medical.md +246 -0
  72. package/skills/content-writing/references/images.md +212 -0
  73. package/skills/content-writing/references/journalism.md +252 -0
  74. package/skills/content-writing/references/science-popular.md +218 -0
  75. package/skills/content-writing/references/seo-content.md +172 -0
  76. package/skills/content-writing/references/social-media.md +253 -0
  77. package/skills/content-writing/references/templates.md +291 -0
  78. package/skills/database-safety/SKILL.md +31 -0
  79. package/skills/debugging-playbook/SKILL.md +39 -0
  80. package/skills/design-critique/SKILL.md +118 -0
  81. package/skills/dev-workflow/SKILL.md +41 -0
  82. package/skills/git-and-ci/SKILL.md +40 -0
  83. package/skills/pg-upgrade-verify/SKILL.md +48 -0
  84. package/skills/project-deep-dive/SKILL.md +170 -0
  85. package/skills/project-deep-dive/references/recon-commands.md +78 -0
  86. package/skills/project-deep-dive/references/section-templates.md +102 -0
  87. package/skills/project-memory-maintenance/SKILL.md +170 -0
  88. package/skills/project-memory-maintenance/references/journal-template.md +48 -0
  89. package/skills/project-memory-maintenance/references/section-rules.md +57 -0
  90. package/skills/release-and-deploy/SKILL.md +39 -0
  91. package/skills/requirements-and-planning/SKILL.md +115 -0
  92. package/skills/security-review/SKILL.md +36 -0
  93. package/skills/seo/README.md +60 -0
  94. package/skills/seo/SKILL.md +329 -0
  95. package/skills/seo/assets/README.md +66 -0
  96. package/skills/seo/assets/scripts/LICENSE-playwright-core.txt +202 -0
  97. package/skills/seo/assets/scripts/README.md +179 -0
  98. package/skills/seo/assets/scripts/lib/args.mjs +105 -0
  99. package/skills/seo/assets/scripts/lib/browser.mjs +620 -0
  100. package/skills/seo/assets/scripts/lib/zip.mjs +191 -0
  101. package/skills/seo/assets/scripts/seo-audit.mjs +1675 -0
  102. package/skills/seo/assets/snippets/jsonld-breadcrumb.json +30 -0
  103. package/skills/seo/assets/snippets/jsonld-faq.json +39 -0
  104. package/skills/seo/assets/snippets/jsonld-medical-clinic.json +49 -0
  105. package/skills/seo/assets/snippets/jsonld-medical-webpage.json +33 -0
  106. package/skills/seo/assets/snippets/jsonld-organization.json +74 -0
  107. package/skills/seo/assets/snippets/jsonld-physician.json +46 -0
  108. package/skills/seo/assets/snippets/meta.blade.php +61 -0
  109. package/skills/seo/assets/snippets/robots.txt +61 -0
  110. package/skills/seo/assets/snippets/sitemap-urlset-example.xml +72 -0
  111. package/skills/seo/assets/snippets/sitemap.xml +36 -0
  112. package/skills/seo/references/audit-checklist.md +305 -0
  113. package/skills/seo/references/content-seo.md +198 -0
  114. package/skills/seo/references/eeat-medical.md +217 -0
  115. package/skills/seo/references/measurement.md +223 -0
  116. package/skills/seo/references/on-page.md +209 -0
  117. package/skills/seo/references/stack-recipes.md +693 -0
  118. package/skills/seo/references/structured-data.md +485 -0
  119. package/skills/seo/references/technical-seo.md +294 -0
  120. package/skills/service-desk-conventions/SKILL.md +40 -0
  121. package/skills/service-desk-db-safety/SKILL.md +52 -0
  122. package/skills/service-desk-release/SKILL.md +43 -0
  123. package/skills/service-desk-testing/SKILL.md +47 -0
  124. package/skills/service-desk-ui/SKILL.md +33 -0
  125. package/skills/skills-router/SKILL.md +91 -0
  126. package/skills/testing-strategy/SKILL.md +37 -0
  127. package/skills/ui-design-system/SKILL.md +45 -0
  128. package/skills/web-research/SKILL.md +40 -0
  129. package/skills/wow-design/SKILL.md +168 -0
  130. package/skills/wow-design/assets/kit/README.md +195 -0
  131. package/skills/wow-design/assets/kit/assets/css/base.css +158 -0
  132. package/skills/wow-design/assets/kit/assets/css/components.css +1001 -0
  133. package/skills/wow-design/assets/kit/assets/css/fonts.css +73 -0
  134. package/skills/wow-design/assets/kit/assets/css/pages.css +737 -0
  135. package/skills/wow-design/assets/kit/assets/css/tokens.css +218 -0
  136. package/skills/wow-design/assets/kit/assets/fonts/LICENSE-golos-text.txt +93 -0
  137. package/skills/wow-design/assets/kit/assets/fonts/LICENSE-lora.txt +93 -0
  138. package/skills/wow-design/assets/kit/assets/fonts/LICENSE-unbounded.txt +93 -0
  139. package/skills/wow-design/assets/kit/assets/fonts/golos-text-cyrillic-400-normal.woff2 +0 -0
  140. package/skills/wow-design/assets/kit/assets/fonts/golos-text-cyrillic-500-normal.woff2 +0 -0
  141. package/skills/wow-design/assets/kit/assets/fonts/golos-text-cyrillic-600-normal.woff2 +0 -0
  142. package/skills/wow-design/assets/kit/assets/fonts/golos-text-cyrillic-700-normal.woff2 +0 -0
  143. package/skills/wow-design/assets/kit/assets/fonts/golos-text-latin-400-normal.woff2 +0 -0
  144. package/skills/wow-design/assets/kit/assets/fonts/golos-text-latin-500-normal.woff2 +0 -0
  145. package/skills/wow-design/assets/kit/assets/fonts/golos-text-latin-600-normal.woff2 +0 -0
  146. package/skills/wow-design/assets/kit/assets/fonts/golos-text-latin-700-normal.woff2 +0 -0
  147. package/skills/wow-design/assets/kit/assets/fonts/lora-cyrillic-400-italic.woff2 +0 -0
  148. package/skills/wow-design/assets/kit/assets/fonts/lora-cyrillic-400-normal.woff2 +0 -0
  149. package/skills/wow-design/assets/kit/assets/fonts/lora-cyrillic-500-normal.woff2 +0 -0
  150. package/skills/wow-design/assets/kit/assets/fonts/lora-cyrillic-600-normal.woff2 +0 -0
  151. package/skills/wow-design/assets/kit/assets/fonts/lora-latin-400-normal.woff2 +0 -0
  152. package/skills/wow-design/assets/kit/assets/fonts/lora-latin-500-normal.woff2 +0 -0
  153. package/skills/wow-design/assets/kit/assets/fonts/lora-latin-600-normal.woff2 +0 -0
  154. package/skills/wow-design/assets/kit/assets/fonts/unbounded-cyrillic-400-normal.woff2 +0 -0
  155. package/skills/wow-design/assets/kit/assets/fonts/unbounded-cyrillic-500-normal.woff2 +0 -0
  156. package/skills/wow-design/assets/kit/assets/fonts/unbounded-cyrillic-600-normal.woff2 +0 -0
  157. package/skills/wow-design/assets/kit/assets/fonts/unbounded-latin-400-normal.woff2 +0 -0
  158. package/skills/wow-design/assets/kit/assets/fonts/unbounded-latin-500-normal.woff2 +0 -0
  159. package/skills/wow-design/assets/kit/assets/fonts/unbounded-latin-600-normal.woff2 +0 -0
  160. package/skills/wow-design/assets/kit/assets/js/kit.js +550 -0
  161. package/skills/wow-design/assets/kit/pages/dashboard.html +539 -0
  162. package/skills/wow-design/assets/kit/pages/hospital.html +430 -0
  163. package/skills/wow-design/assets/kit/showcase.html +1164 -0
  164. package/skills/wow-design/assets/scripts/LICENSE-axe-core.txt +362 -0
  165. package/skills/wow-design/assets/scripts/LICENSE-playwright-core.txt +202 -0
  166. package/skills/wow-design/assets/scripts/README.md +312 -0
  167. package/skills/wow-design/assets/scripts/audit.mjs +1200 -0
  168. package/skills/wow-design/assets/scripts/capture.mjs +337 -0
  169. package/skills/wow-design/assets/scripts/package.json +30 -0
  170. package/skills/wow-design/references/anti-patterns.md +249 -0
  171. package/skills/wow-design/references/critique-rubric.md +259 -0
  172. package/skills/wow-design/references/design-brief-template.md +46 -0
  173. package/skills/wow-design/references/inspiration.md +200 -0
  174. package/skills/wow-design/references/presets.md +96 -0
  175. package/skills/wow-design/references/principles.md +368 -0
  176. package/skills/wow-design/references/stack-recipes.md +93 -0
  177. package/skills/wow-design/references/visual-qa.md +79 -0
@@ -0,0 +1,3 @@
1
+ - insert:
2
+ - id: profile-transfer
3
+ name: dsh-profile-transfer
@@ -0,0 +1,13 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64" role="img" aria-label="Transfer and backups">
2
+ <defs>
3
+ <linearGradient id="tr-a" x1="0" y1="0" x2="1" y2="1">
4
+ <stop offset="0" stop-color="#2f7df6"/>
5
+ <stop offset="1" stop-color="#173a8f"/>
6
+ </linearGradient>
7
+ </defs>
8
+ <rect x="2" y="2" width="60" height="60" rx="14" fill="url(#tr-a)"/>
9
+ <path d="M15 24h27l-6-6" fill="none" stroke="#ffffff" stroke-width="3.4" stroke-linecap="round" stroke-linejoin="round" opacity=".95"/>
10
+ <path d="M49 40H22l6 6" fill="none" stroke="#ffffff" stroke-width="3.4" stroke-linecap="round" stroke-linejoin="round" opacity=".95"/>
11
+ <rect x="13" y="30" width="13" height="4" rx="2" fill="#ffffff" opacity=".5"/>
12
+ <rect x="38" y="30" width="13" height="4" rx="2" fill="#ffffff" opacity=".5"/>
13
+ </svg>
@@ -0,0 +1,375 @@
1
+ /**
2
+ * dsh-profile-transfer — Host half.
3
+ *
4
+ * Authenticated Connection RPC routes for the Settings page:
5
+ * options — what can be exported and how big it is
6
+ * export — build a .tar.gz transfer archive
7
+ * inspect — read an archive manifest without applying it
8
+ * import — restore an archive into <DSH_HOME> with a backup
9
+ * install — run `pnpm install` in the restored profile
10
+ * reveal — open the archive folder in the OS file manager
11
+ *
12
+ * All file work lives in transfer.js (pure Node, testable on temporary homes).
13
+ */
14
+
15
+ import fs from "node:fs";
16
+ import os from "node:os";
17
+ import path from "node:path";
18
+ import { spawn } from "node:child_process";
19
+ import {
20
+ applyBundle,
21
+ defaultHome,
22
+ describeSources,
23
+ exportBundle,
24
+ humanSize,
25
+ inspectBundle,
26
+ normalizeSelection,
27
+ } from "./transfer.js";
28
+
29
+ const ROUTE_BASE = "/api/dsh-profile-transfer";
30
+ const RPC_NAMESPACE = "dsh-profile-transfer";
31
+ const INSTALL_TIMEOUT_MS = 5 * 60 * 1000;
32
+
33
+ export const inject = ["connection"];
34
+
35
+ class TransferError extends Error {
36
+ constructor(code, message, details = {}) {
37
+ super(message);
38
+ this.code = code;
39
+ this.details = details;
40
+ }
41
+ }
42
+
43
+ function environment(ctx) {
44
+ const home = defaultHome();
45
+ let profileName = process.env.DSH_PROFILE ?? "desktop";
46
+ if (typeof profileName !== "string" || profileName.length === 0) profileName = "desktop";
47
+ const profileDir = path.join(home, "profiles", profileName);
48
+ return { home, profileName, profileDir };
49
+ }
50
+
51
+ function suggestedDestination() {
52
+ return path.join(os.homedir(), "Documents", "DSH-обмены");
53
+ }
54
+
55
+ function lastSummary(home) {
56
+ try {
57
+ return JSON.parse(fs.readFileSync(path.join(home, "profile-transfer-last.json"), "utf8"));
58
+ } catch {
59
+ return undefined;
60
+ }
61
+ }
62
+
63
+ /** Candidate pnpm invocations, most portable first: bundled runtime of the Desktop app, then PATH. */
64
+ function pnpmCandidates() {
65
+ const candidates = [];
66
+ const configured = process.env.PNPM_EXECUTABLE ?? process.env.DSH_PNPM;
67
+ if (typeof configured === "string" && configured.trim().length > 0) {
68
+ candidates.push({ command: configured.trim(), prefix: [], shell: false, label: "env" });
69
+ }
70
+ try {
71
+ // The Desktop app bundles pnpm and runs it through its own Electron binary
72
+ // with ELECTRON_RUN_AS_NODE, exactly like resources/runtime/bin/node.cmd does.
73
+ const resources = path.join(path.dirname(process.execPath), "resources");
74
+ const pnpmCjs = path.join(resources, "runtime", "primary-runtime", "dependencies", "pnpm", "bin", "pnpm.cjs");
75
+ if (fs.existsSync(pnpmCjs)) {
76
+ candidates.push({ command: process.execPath, prefix: [pnpmCjs], shell: false, label: "bundled", env: { ELECTRON_RUN_AS_NODE: "1" } });
77
+ }
78
+ } catch {
79
+ /* non-Desktop installs have no bundled runtime */
80
+ }
81
+ candidates.push({ command: "pnpm", prefix: [], shell: process.platform === "win32", label: "path" });
82
+ return candidates;
83
+ }
84
+
85
+ function runPnpmInstall(profileDir) {
86
+ const candidates = pnpmCandidates();
87
+ return new Promise((resolve) => {
88
+ const started = Date.now();
89
+ let output = "";
90
+ let index = 0;
91
+ const attempt = () => {
92
+ const candidate = candidates[index];
93
+ if (candidate === undefined) {
94
+ resolve({ ok: false, code: "spawn-failed", output: `${output}\nни один из pnpm-кандидатов не запустился`, ms: Date.now() - started });
95
+ return;
96
+ }
97
+ let child;
98
+ try {
99
+ child = spawn(candidate.command, [...candidate.prefix, "install"], {
100
+ cwd: profileDir,
101
+ shell: candidate.shell,
102
+ windowsHide: true,
103
+ env: { ...process.env, ...(candidate.env ?? {}) },
104
+ });
105
+ } catch (error) {
106
+ output += `\n[${candidate.label}] ${String(error)}`;
107
+ index += 1;
108
+ attempt();
109
+ return;
110
+ }
111
+ let settled = false;
112
+ const timer = setTimeout(() => {
113
+ try {
114
+ child.kill();
115
+ } catch {
116
+ /* ignore */
117
+ }
118
+ }, INSTALL_TIMEOUT_MS);
119
+ const finish = (result) => {
120
+ if (settled) return;
121
+ settled = true;
122
+ clearTimeout(timer);
123
+ resolve({ ...result, executable: candidate.command, via: candidate.label });
124
+ };
125
+ const push = (chunk) => {
126
+ output += chunk.toString();
127
+ if (output.length > 20000) output = output.slice(-20000);
128
+ };
129
+ child.stdout?.on("data", push);
130
+ child.stderr?.on("data", push);
131
+ child.on("error", (error) => {
132
+ output += `\n[${candidate.label}] ${String(error)}`;
133
+ clearTimeout(timer);
134
+ if (settled) return;
135
+ settled = true;
136
+ index += 1;
137
+ attempt();
138
+ });
139
+ child.on("close", (code) => {
140
+ finish({ ok: code === 0, code: String(code), output: output.slice(-6000), ms: Date.now() - started });
141
+ });
142
+ };
143
+ attempt();
144
+ });
145
+ }
146
+
147
+ function runReveal(target) {
148
+ try {
149
+ const platform = process.platform;
150
+ let child;
151
+ if (platform === "win32") {
152
+ child = spawn("explorer.exe", [fs.existsSync(target) && fs.statSync(target).isFile() ? `/select,${target}` : target], {
153
+ detached: true,
154
+ stdio: "ignore",
155
+ });
156
+ } else if (platform === "darwin") {
157
+ child = spawn("open", [fs.existsSync(target) && fs.statSync(target).isFile() ? path.dirname(target) : target], { detached: true, stdio: "ignore" });
158
+ } else {
159
+ child = spawn("xdg-open", [fs.existsSync(target) && fs.statSync(target).isFile() ? path.dirname(target) : target], { detached: true, stdio: "ignore" });
160
+ }
161
+ child.unref?.();
162
+ return true;
163
+ } catch {
164
+ return false;
165
+ }
166
+ }
167
+
168
+ /* ------------------------------------------------------------------ */
169
+ /* handlers */
170
+ /* ------------------------------------------------------------------ */
171
+
172
+ async function handleOptions(ctx) {
173
+ const { home, profileName, profileDir } = environment(ctx);
174
+ const description = describeSources({ home, profile: profileName });
175
+ return {
176
+ home,
177
+ profile: profileName,
178
+ profileExists: fs.existsSync(profileDir),
179
+ destination: suggestedDestination(),
180
+ selection: description.selection,
181
+ groups: description.groups,
182
+ last: lastSummary(home),
183
+ platform: process.platform,
184
+ };
185
+ }
186
+
187
+ async function handleExport(ctx, payload) {
188
+ const { home, profileName } = environment(ctx);
189
+ const selection = normalizeSelection(payload?.selection);
190
+ const destinationDirectory = typeof payload?.destinationDirectory === "string" && payload.destinationDirectory.trim().length > 0
191
+ ? path.resolve(payload.destinationDirectory.trim())
192
+ : suggestedDestination();
193
+ const result = exportBundle({ home, profile: profileName, selection, destinationDirectory });
194
+ return {
195
+ ok: true,
196
+ archivePath: result.archivePath,
197
+ human: result.human,
198
+ bytes: result.bytes,
199
+ manifest: {
200
+ createdAt: result.manifest.createdAt,
201
+ totals: result.manifest.totals,
202
+ selection: result.manifest.selection,
203
+ },
204
+ };
205
+ }
206
+
207
+ async function handleInspect(ctx, payload) {
208
+ const archivePath = typeof payload?.archivePath === "string" ? payload.archivePath.trim() : "";
209
+ if (archivePath.length === 0) throw new TransferError("transfer/bad-request", "укажите путь к архиву");
210
+ if (!fs.existsSync(archivePath)) throw new TransferError("transfer/not-found", `архив не найден: ${archivePath}`);
211
+ const inspected = inspectBundle(archivePath);
212
+ return {
213
+ ok: true,
214
+ archivePath,
215
+ human: inspected.human,
216
+ manifest: {
217
+ createdAt: inspected.manifest.createdAt,
218
+ host: inspected.manifest.host,
219
+ platform: inspected.manifest.platform,
220
+ dshVersion: inspected.manifest.dshVersion,
221
+ profile: inspected.manifest.profile,
222
+ selection: inspected.manifest.selection,
223
+ totals: inspected.manifest.totals,
224
+ localPlugins: inspected.manifest.localPlugins,
225
+ },
226
+ files: inspected.files.length,
227
+ };
228
+ }
229
+
230
+ async function handleImport(ctx, payload) {
231
+ const { home, profileName } = environment(ctx);
232
+ const archivePath = typeof payload?.archivePath === "string" ? payload.archivePath.trim() : "";
233
+ if (archivePath.length === 0) throw new TransferError("transfer/bad-request", "укажите путь к архиву");
234
+ if (!fs.existsSync(archivePath)) throw new TransferError("transfer/not-found", `архив не найден: ${archivePath}`);
235
+ const apply = applyBundle({
236
+ home,
237
+ profile: profileName,
238
+ archivePath,
239
+ selection: payload?.selection,
240
+ backup: payload?.backup !== false,
241
+ });
242
+ let install;
243
+ if (payload?.installDependencies === true) {
244
+ install = await runPnpmInstall(path.join(home, "profiles", profileName));
245
+ }
246
+ return {
247
+ ok: true,
248
+ archivePath,
249
+ restored: apply.restored,
250
+ backupDir: apply.backupDir,
251
+ needsPnpm: apply.needsPnpm,
252
+ warnings: apply.warnings,
253
+ manifest: { createdAt: apply.manifest.createdAt, totals: apply.manifest.totals, host: apply.manifest.host },
254
+ install,
255
+ summaryPath: apply.summaryPath,
256
+ restartRequired: true,
257
+ };
258
+ }
259
+
260
+ async function handleInstall(ctx) {
261
+ const { home, profileName, profileDir } = environment(ctx);
262
+ if (!fs.existsSync(profileDir)) throw new TransferError("transfer/not-found", `профиль не найден: ${profileDir}`);
263
+ const result = await runPnpmInstall(profileDir);
264
+ return { ok: result.ok, code: result.code, ms: result.ms, output: result.output, profileDir, home, profile: profileName };
265
+ }
266
+
267
+ async function handleReveal(ctx, payload) {
268
+ const target = typeof payload?.path === "string" ? payload.path.trim() : "";
269
+ if (target.length === 0) throw new TransferError("transfer/bad-request", "укажите путь");
270
+ if (!fs.existsSync(target)) throw new TransferError("transfer/not-found", `путь не найден: ${target}`);
271
+ const opened = runReveal(target);
272
+ return { ok: opened, path: target };
273
+ }
274
+
275
+ async function handleDiagnose(ctx) {
276
+ const { home, profileName, profileDir } = environment(ctx);
277
+ const manifest = (() => {
278
+ try {
279
+ return JSON.parse(fs.readFileSync(path.join(profileDir, "package.json"), "utf8"));
280
+ } catch {
281
+ return undefined;
282
+ }
283
+ })();
284
+ return {
285
+ home,
286
+ profile: profileName,
287
+ profileExists: fs.existsSync(profileDir),
288
+ profileDependencies: manifest?.dependencies === null || typeof manifest?.dependencies !== "object" ? {} : manifest.dependencies,
289
+ nodeModules: fs.existsSync(path.join(profileDir, "node_modules")),
290
+ destination: suggestedDestination(),
291
+ last: lastSummary(home),
292
+ platform: process.platform,
293
+ node: process.version,
294
+ pnpmCandidates: pnpmCandidates().map((candidate) => candidate.label + ":" + candidate.command),
295
+ };
296
+ }
297
+
298
+ /* ------------------------------------------------------------------ */
299
+ /* wire */
300
+ /* ------------------------------------------------------------------ */
301
+
302
+ function okEnvelope(rpcId, value) {
303
+ return Response.json({ type: "server-response", rpcId, result: { ok: true, value } });
304
+ }
305
+
306
+ function errorEnvelope(rpcId, code, message, details = {}) {
307
+ return Response.json({ type: "server-response", rpcId, result: { ok: false, error: { code, message, details } } });
308
+ }
309
+
310
+ async function readEnvelope(request) {
311
+ let body;
312
+ try {
313
+ body = await request.json();
314
+ } catch {
315
+ throw new TransferError("transfer/bad-request", "тело запроса не JSON");
316
+ }
317
+ if (body === null || typeof body !== "object" || body.type !== "client-request" || typeof body.rpcId !== "string" || typeof body.method !== "string") {
318
+ throw new TransferError("transfer/bad-request", "некорректный конверт Connection");
319
+ }
320
+ return { rpcId: body.rpcId, method: body.method, payload: body.payload ?? {} };
321
+ }
322
+
323
+ function registerOperation(ctx, operation, handler) {
324
+ const route = {
325
+ path: `${ROUTE_BASE}/${operation}`,
326
+ methods: ["POST"],
327
+ requestBody: "buffered",
328
+ fetch: async (request) => {
329
+ let rpcId = "invalid-request";
330
+ try {
331
+ const envelope = await readEnvelope(request);
332
+ rpcId = envelope.rpcId;
333
+ if (envelope.method !== `${RPC_NAMESPACE}/${operation}`) {
334
+ return errorEnvelope(rpcId, "transfer/bad-request", "метод конверта не совпадает с endpoint", {});
335
+ }
336
+ const value = await handler(ctx, envelope.payload);
337
+ return okEnvelope(rpcId, value);
338
+ } catch (error) {
339
+ const code = typeof error?.code === "string" ? error.code : "transfer/internal";
340
+ if (code === "transfer/internal") ctx.logger?.warn?.(`[profile-transfer] ${operation} failed: ${String(error)}`);
341
+ return errorEnvelope(rpcId, code, error instanceof Error ? error.message : String(error));
342
+ }
343
+ },
344
+ };
345
+ ctx.effect(() => {
346
+ const dispose = ctx.connection.fetch.register(route);
347
+ return () => {
348
+ try {
349
+ void Promise.resolve(dispose()).catch(() => {});
350
+ } catch {
351
+ /* carrier already unloading */
352
+ }
353
+ };
354
+ }, `profile-transfer: ${operation} route`);
355
+ }
356
+
357
+ export function apply(ctx) {
358
+ const logger = ctx.logger;
359
+ const { home, profileName } = environment(ctx);
360
+ try {
361
+ fs.mkdirSync(home, { recursive: true });
362
+ } catch (error) {
363
+ logger?.warn?.(`[profile-transfer] cannot prepare ${home}: ${String(error)}`);
364
+ }
365
+ registerOperation(ctx, "options", (context) => handleOptions(context));
366
+ registerOperation(ctx, "export", (context, payload) => handleExport(context, payload));
367
+ registerOperation(ctx, "inspect", (context, payload) => handleInspect(context, payload));
368
+ registerOperation(ctx, "import", (context, payload) => handleImport(context, payload));
369
+ registerOperation(ctx, "install", (context) => handleInstall(context));
370
+ registerOperation(ctx, "reveal", (context, payload) => handleReveal(context, payload));
371
+ registerOperation(ctx, "diagnose", (context) => handleDiagnose(context));
372
+ logger?.info?.(`[profile-transfer] ready: home ${home}, profile ${profileName}`);
373
+ }
374
+
375
+ export { humanSize, normalizeSelection };
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Transfer and backups",
4
+ "description": "Pack the whole DSH setup into one archive and restore it on another machine: profile and configs, skills, project memory, local plugins, sessions, secrets — with a backup before restore."
5
+ }
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Перенос и резервные копии",
4
+ "description": "Собрать всю настройку DSH в один архив и развернуть на другой машине: профиль и конфиги, навыки, память проектов, локальные плагины, сессии, секреты — с бэкапом перед восстановлением."
5
+ }
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "迁移与备份",
4
+ "description": "把整套 DSH 配置打包成一个归档并在另一台机器上恢复:配置与档案、技能、项目记忆、本地插件、会话、密钥——恢复前自动备份。"
5
+ }
6
+ }
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "dsh-profile-transfer",
3
+ "version": "1.3.0",
4
+ "private": true,
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
+ "type": "module",
7
+ "main": "index.js",
8
+ "exports": {
9
+ ".": "./index.js",
10
+ "./client": "./client.js",
11
+ "./transfer": "./transfer.js",
12
+ "./locale/*.json": "./locale/*.json",
13
+ "./icon": "./icon.svg"
14
+ },
15
+ "files": [
16
+ "index.js",
17
+ "client.js",
18
+ "transfer.js",
19
+ "cordis.patch.yml",
20
+ "locale/*.json",
21
+ "icon.svg",
22
+ "README.md",
23
+ "bootstrap/*"
24
+ ],
25
+ "dsh": {
26
+ "manifestVersion": 1,
27
+ "bundle": {
28
+ "patch": "./cordis.patch.yml"
29
+ },
30
+ "client": {
31
+ "platform": "web",
32
+ "immediately": true,
33
+ "inject": []
34
+ }
35
+ }
36
+ }