coding-tool-x 3.2.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 (185) hide show
  1. package/CHANGELOG.md +599 -0
  2. package/LICENSE +21 -0
  3. package/README.md +439 -0
  4. package/bin/ctx.js +8 -0
  5. package/dist/web/assets/Analytics-DN_YsnkW.js +39 -0
  6. package/dist/web/assets/Analytics-DuYvId7u.css +1 -0
  7. package/dist/web/assets/ConfigTemplates-Bidwfdf2.css +1 -0
  8. package/dist/web/assets/ConfigTemplates-DpXIMy0p.js +1 -0
  9. package/dist/web/assets/Home-38JTUlYt.js +1 -0
  10. package/dist/web/assets/Home-CjupSEWE.css +1 -0
  11. package/dist/web/assets/PluginManager-CX2tgq2H.js +1 -0
  12. package/dist/web/assets/PluginManager-ROyoZ-6m.css +1 -0
  13. package/dist/web/assets/ProjectList-C1lDcsn6.js +1 -0
  14. package/dist/web/assets/ProjectList-oJIyIRkP.css +1 -0
  15. package/dist/web/assets/SessionList-C55tjV7i.css +1 -0
  16. package/dist/web/assets/SessionList-CZ7T6rVx.js +1 -0
  17. package/dist/web/assets/SkillManager-D7pd-d_P.css +1 -0
  18. package/dist/web/assets/SkillManager-DLN9f79y.js +1 -0
  19. package/dist/web/assets/WorkspaceManager-CrwgQgmP.css +1 -0
  20. package/dist/web/assets/WorkspaceManager-DxlHZkpZ.js +1 -0
  21. package/dist/web/assets/icons-DRrXwWZi.js +1 -0
  22. package/dist/web/assets/index-CetESrXw.css +1 -0
  23. package/dist/web/assets/index-Cfvn-2Gb.js +2 -0
  24. package/dist/web/assets/markdown-BfC0goYb.css +10 -0
  25. package/dist/web/assets/markdown-C9MYpaSi.js +1 -0
  26. package/dist/web/assets/naive-ui-DlpKk-8M.js +1 -0
  27. package/dist/web/assets/vendors-DMjSfzlv.js +7 -0
  28. package/dist/web/assets/vue-vendor-DET08QYg.js +45 -0
  29. package/dist/web/favicon.ico +0 -0
  30. package/dist/web/index.html +20 -0
  31. package/dist/web/logo.png +0 -0
  32. package/docs/bannel.png +0 -0
  33. package/docs/home.png +0 -0
  34. package/docs/logo.png +0 -0
  35. package/docs/model-redirection.md +251 -0
  36. package/docs/multi-channel-load-balancing.md +249 -0
  37. package/package.json +80 -0
  38. package/src/commands/channels.js +551 -0
  39. package/src/commands/cli-type.js +101 -0
  40. package/src/commands/daemon.js +365 -0
  41. package/src/commands/doctor.js +333 -0
  42. package/src/commands/export-config.js +205 -0
  43. package/src/commands/list.js +222 -0
  44. package/src/commands/logs.js +261 -0
  45. package/src/commands/plugin.js +585 -0
  46. package/src/commands/port-config.js +135 -0
  47. package/src/commands/proxy-control.js +264 -0
  48. package/src/commands/proxy.js +152 -0
  49. package/src/commands/resume.js +137 -0
  50. package/src/commands/search.js +190 -0
  51. package/src/commands/security.js +37 -0
  52. package/src/commands/stats.js +398 -0
  53. package/src/commands/switch.js +48 -0
  54. package/src/commands/toggle-proxy.js +247 -0
  55. package/src/commands/ui.js +99 -0
  56. package/src/commands/update.js +97 -0
  57. package/src/commands/workspace.js +454 -0
  58. package/src/config/default.js +69 -0
  59. package/src/config/loader.js +149 -0
  60. package/src/config/model-metadata.js +167 -0
  61. package/src/config/model-metadata.json +125 -0
  62. package/src/config/model-pricing.js +35 -0
  63. package/src/config/paths.js +190 -0
  64. package/src/index.js +680 -0
  65. package/src/plugins/constants.js +15 -0
  66. package/src/plugins/event-bus.js +54 -0
  67. package/src/plugins/manifest-validator.js +129 -0
  68. package/src/plugins/plugin-api.js +128 -0
  69. package/src/plugins/plugin-installer.js +601 -0
  70. package/src/plugins/plugin-loader.js +229 -0
  71. package/src/plugins/plugin-manager.js +170 -0
  72. package/src/plugins/registry.js +152 -0
  73. package/src/plugins/schema/plugin-manifest.json +115 -0
  74. package/src/reset-config.js +94 -0
  75. package/src/server/api/agents.js +826 -0
  76. package/src/server/api/aliases.js +36 -0
  77. package/src/server/api/channels.js +368 -0
  78. package/src/server/api/claude-hooks.js +480 -0
  79. package/src/server/api/codex-channels.js +417 -0
  80. package/src/server/api/codex-projects.js +104 -0
  81. package/src/server/api/codex-proxy.js +195 -0
  82. package/src/server/api/codex-sessions.js +483 -0
  83. package/src/server/api/codex-statistics.js +57 -0
  84. package/src/server/api/commands.js +482 -0
  85. package/src/server/api/config-export.js +212 -0
  86. package/src/server/api/config-registry.js +357 -0
  87. package/src/server/api/config-sync.js +155 -0
  88. package/src/server/api/config-templates.js +248 -0
  89. package/src/server/api/config.js +521 -0
  90. package/src/server/api/convert.js +260 -0
  91. package/src/server/api/dashboard.js +142 -0
  92. package/src/server/api/env.js +144 -0
  93. package/src/server/api/favorites.js +77 -0
  94. package/src/server/api/gemini-channels.js +366 -0
  95. package/src/server/api/gemini-projects.js +91 -0
  96. package/src/server/api/gemini-proxy.js +173 -0
  97. package/src/server/api/gemini-sessions.js +376 -0
  98. package/src/server/api/gemini-statistics.js +57 -0
  99. package/src/server/api/health-check.js +31 -0
  100. package/src/server/api/mcp.js +399 -0
  101. package/src/server/api/opencode-channels.js +419 -0
  102. package/src/server/api/opencode-projects.js +99 -0
  103. package/src/server/api/opencode-proxy.js +207 -0
  104. package/src/server/api/opencode-sessions.js +327 -0
  105. package/src/server/api/opencode-statistics.js +57 -0
  106. package/src/server/api/plugins.js +463 -0
  107. package/src/server/api/pm2-autostart.js +269 -0
  108. package/src/server/api/projects.js +124 -0
  109. package/src/server/api/prompts.js +279 -0
  110. package/src/server/api/proxy.js +306 -0
  111. package/src/server/api/security.js +53 -0
  112. package/src/server/api/sessions.js +514 -0
  113. package/src/server/api/settings.js +142 -0
  114. package/src/server/api/skills.js +570 -0
  115. package/src/server/api/statistics.js +238 -0
  116. package/src/server/api/ui-config.js +64 -0
  117. package/src/server/api/workspaces.js +456 -0
  118. package/src/server/codex-proxy-server.js +681 -0
  119. package/src/server/dev-server.js +26 -0
  120. package/src/server/gemini-proxy-server.js +610 -0
  121. package/src/server/index.js +422 -0
  122. package/src/server/opencode-proxy-server.js +4771 -0
  123. package/src/server/proxy-server.js +669 -0
  124. package/src/server/services/agents-service.js +1137 -0
  125. package/src/server/services/alias.js +71 -0
  126. package/src/server/services/channel-health.js +234 -0
  127. package/src/server/services/channel-scheduler.js +240 -0
  128. package/src/server/services/channels.js +447 -0
  129. package/src/server/services/codex-channels.js +705 -0
  130. package/src/server/services/codex-config.js +90 -0
  131. package/src/server/services/codex-parser.js +322 -0
  132. package/src/server/services/codex-sessions.js +936 -0
  133. package/src/server/services/codex-settings-manager.js +619 -0
  134. package/src/server/services/codex-speed-test-template.json +24 -0
  135. package/src/server/services/codex-statistics-service.js +161 -0
  136. package/src/server/services/commands-service.js +574 -0
  137. package/src/server/services/config-export-service.js +1165 -0
  138. package/src/server/services/config-registry-service.js +828 -0
  139. package/src/server/services/config-sync-manager.js +941 -0
  140. package/src/server/services/config-sync-service.js +504 -0
  141. package/src/server/services/config-templates-service.js +913 -0
  142. package/src/server/services/enhanced-cache.js +196 -0
  143. package/src/server/services/env-checker.js +409 -0
  144. package/src/server/services/env-manager.js +436 -0
  145. package/src/server/services/favorites.js +165 -0
  146. package/src/server/services/format-converter.js +620 -0
  147. package/src/server/services/gemini-channels.js +459 -0
  148. package/src/server/services/gemini-config.js +73 -0
  149. package/src/server/services/gemini-sessions.js +689 -0
  150. package/src/server/services/gemini-settings-manager.js +263 -0
  151. package/src/server/services/gemini-statistics-service.js +157 -0
  152. package/src/server/services/health-check.js +85 -0
  153. package/src/server/services/mcp-client.js +790 -0
  154. package/src/server/services/mcp-service.js +1732 -0
  155. package/src/server/services/model-detector.js +1245 -0
  156. package/src/server/services/network-access.js +80 -0
  157. package/src/server/services/opencode-channels.js +366 -0
  158. package/src/server/services/opencode-gateway-adapters.js +1168 -0
  159. package/src/server/services/opencode-gateway-converter.js +639 -0
  160. package/src/server/services/opencode-sessions.js +931 -0
  161. package/src/server/services/opencode-settings-manager.js +478 -0
  162. package/src/server/services/opencode-statistics-service.js +161 -0
  163. package/src/server/services/plugins-service.js +1268 -0
  164. package/src/server/services/prompts-service.js +534 -0
  165. package/src/server/services/proxy-runtime.js +79 -0
  166. package/src/server/services/repo-scanner-base.js +708 -0
  167. package/src/server/services/request-logger.js +130 -0
  168. package/src/server/services/response-decoder.js +21 -0
  169. package/src/server/services/security-config.js +131 -0
  170. package/src/server/services/session-cache.js +127 -0
  171. package/src/server/services/session-converter.js +577 -0
  172. package/src/server/services/sessions.js +900 -0
  173. package/src/server/services/settings-manager.js +163 -0
  174. package/src/server/services/skill-service.js +1482 -0
  175. package/src/server/services/speed-test.js +1146 -0
  176. package/src/server/services/statistics-service.js +1043 -0
  177. package/src/server/services/ui-config.js +132 -0
  178. package/src/server/services/workspace-service.js +830 -0
  179. package/src/server/utils/pricing.js +73 -0
  180. package/src/server/websocket-server.js +513 -0
  181. package/src/ui/menu.js +139 -0
  182. package/src/ui/prompts.js +100 -0
  183. package/src/utils/format.js +43 -0
  184. package/src/utils/port-helper.js +108 -0
  185. package/src/utils/session.js +240 -0
@@ -0,0 +1,1165 @@
1
+ /**
2
+ * 配置导出/导入服务
3
+ * 支持配置模板、频道配置的导出与导入
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const os = require('os');
9
+ const AdmZip = require('adm-zip');
10
+ const configTemplatesService = require('./config-templates-service');
11
+ const channelsService = require('./channels');
12
+ const { AgentsService } = require('./agents-service');
13
+ const { CommandsService } = require('./commands-service');
14
+ const { SkillService } = require('./skill-service');
15
+
16
+ const CONFIG_VERSION = '1.2.0';
17
+ const SKILL_FILE_ENCODING = 'base64';
18
+ const SKILL_IGNORE_DIRS = new Set(['.git']);
19
+ const SKILL_IGNORE_FILES = new Set(['.DS_Store']);
20
+ const CC_TOOL_DIR = path.join(os.homedir(), '.cc-tool');
21
+ const LEGACY_CC_TOOL_DIR = path.join(os.homedir(), '.cc-tool');
22
+ const CLAUDE_SETTINGS_PATH = path.join(os.homedir(), '.claude', 'settings.json');
23
+ const LEGACY_PLUGINS_DIR = path.join(LEGACY_CC_TOOL_DIR, 'plugins', 'installed');
24
+ const LEGACY_PLUGINS_REGISTRY = path.join(LEGACY_CC_TOOL_DIR, 'plugins', 'registry.json');
25
+ const NATIVE_PLUGINS_REGISTRY = path.join(os.homedir(), '.claude', 'plugins', 'installed_plugins.json');
26
+ const PLUGIN_IGNORE_DIRS = new Set(['.git', 'node_modules', '.DS_Store']);
27
+ const PLUGIN_IGNORE_FILES = new Set(['.DS_Store']);
28
+ const PLUGIN_SENSITIVE_PATTERNS = [
29
+ /\.env$/i,
30
+ /\.env\./i,
31
+ /credentials?\.json$/i,
32
+ /secrets?\.json$/i,
33
+ /\.key$/i,
34
+ /\.pem$/i,
35
+ /\.p12$/i,
36
+ /\.pfx$/i
37
+ ];
38
+ const CC_UI_CONFIG_PATH = path.join(CC_TOOL_DIR, 'ui-config.json');
39
+ const CC_PROMPTS_PATH = path.join(CC_TOOL_DIR, 'prompts.json');
40
+ const CC_SECURITY_PATH = path.join(CC_TOOL_DIR, 'security.json');
41
+ const LEGACY_UI_CONFIG_PATH = path.join(LEGACY_CC_TOOL_DIR, 'ui-config.json');
42
+ const LEGACY_NOTIFY_HOOK_PATH = path.join(LEGACY_CC_TOOL_DIR, 'notify-hook.js');
43
+
44
+ function ensureDir(dirPath) {
45
+ if (!fs.existsSync(dirPath)) {
46
+ fs.mkdirSync(dirPath, { recursive: true });
47
+ }
48
+ }
49
+
50
+ function readJsonFileSafe(filePath) {
51
+ if (!filePath || !fs.existsSync(filePath)) return null;
52
+ try {
53
+ const content = fs.readFileSync(filePath, 'utf8');
54
+ return JSON.parse(content);
55
+ } catch (err) {
56
+ return null;
57
+ }
58
+ }
59
+
60
+ function readTextFileSafe(filePath) {
61
+ if (!filePath || !fs.existsSync(filePath)) return null;
62
+ try {
63
+ return fs.readFileSync(filePath, 'utf8');
64
+ } catch (err) {
65
+ return null;
66
+ }
67
+ }
68
+
69
+ function writeJsonFileAbsolute(filePath, data, overwrite, options = {}) {
70
+ if (data === undefined) {
71
+ return 'failed';
72
+ }
73
+ if (fs.existsSync(filePath) && !overwrite) return 'skipped';
74
+ ensureDir(path.dirname(filePath));
75
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf8');
76
+ if (options.mode) {
77
+ try {
78
+ fs.chmodSync(filePath, options.mode);
79
+ } catch (err) {
80
+ // ignore chmod errors
81
+ }
82
+ }
83
+ return 'success';
84
+ }
85
+
86
+ function writeTextFileAbsolute(filePath, content, overwrite, options = {}) {
87
+ if (content === undefined || content === null) {
88
+ return 'failed';
89
+ }
90
+ if (fs.existsSync(filePath) && !overwrite) return 'skipped';
91
+ ensureDir(path.dirname(filePath));
92
+ fs.writeFileSync(filePath, content, 'utf8');
93
+ if (options.mode) {
94
+ try {
95
+ fs.chmodSync(filePath, options.mode);
96
+ } catch (err) {
97
+ // ignore chmod errors
98
+ }
99
+ }
100
+ return 'success';
101
+ }
102
+
103
+ function getConfigFilePath() {
104
+ try {
105
+ const { getConfigFilePath: resolveConfigPath } = require('../../config/loader');
106
+ return typeof resolveConfigPath === 'function' ? resolveConfigPath() : null;
107
+ } catch (err) {
108
+ return null;
109
+ }
110
+ }
111
+
112
+ function buildExportReadme(exportData) {
113
+ const exportedAt = exportData.exportedAt ? new Date(exportData.exportedAt).toLocaleString('zh-CN') : '';
114
+ return `# Coding Tool 配置导出包
115
+
116
+ 导出时间: ${exportedAt}
117
+ 版本: ${exportData.version || CONFIG_VERSION}
118
+
119
+ ## 📦 包含内容
120
+ - 配置模板、频道配置、工作区、收藏
121
+ - Agents / Skills / Commands
122
+ - 插件 (Plugins)
123
+ - MCP 服务器配置
124
+ - UI 配置(主题、面板显示、排序等)
125
+ - Prompts 预设
126
+ - 安全配置
127
+ - 高级配置(端口、日志、性能等)
128
+ - Claude Hooks 配置(如通知脚本)
129
+
130
+ > 注意:配置包可能包含 API Key、Webhook 等敏感信息,请妥善保管。
131
+ `;
132
+ }
133
+
134
+ function resolveSafePath(baseDir, relativePath) {
135
+ if (!relativePath || typeof relativePath !== 'string') return null;
136
+ const normalized = path.normalize(relativePath);
137
+ if (path.isAbsolute(normalized)) return null;
138
+ const resolved = path.resolve(baseDir, normalized);
139
+ const relative = path.relative(baseDir, resolved);
140
+ if (relative.startsWith('..') || path.isAbsolute(relative)) return null;
141
+ return resolved;
142
+ }
143
+
144
+ function buildAgentContent(agent) {
145
+ const lines = ['---'];
146
+ if (agent.name) lines.push(`name: ${agent.name}`);
147
+ if (agent.description) lines.push(`description: "${agent.description}"`);
148
+ if (agent.tools) lines.push(`tools: ${agent.tools}`);
149
+ if (agent.model) lines.push(`model: ${agent.model}`);
150
+ if (agent.permissionMode) lines.push(`permissionMode: ${agent.permissionMode}`);
151
+ if (agent.skills) lines.push(`skills: ${agent.skills}`);
152
+ lines.push('---');
153
+ const body = agent.systemPrompt || '';
154
+ return `${lines.join('\n')}\n\n${body}`;
155
+ }
156
+
157
+ function buildCommandContent(command) {
158
+ const frontmatter = {};
159
+ if (command.description) frontmatter.description = command.description;
160
+ if (command.allowedTools) frontmatter['allowed-tools'] = command.allowedTools;
161
+ if (command.argumentHint) frontmatter['argument-hint'] = command.argumentHint;
162
+
163
+ const lines = [];
164
+ if (Object.keys(frontmatter).length > 0) {
165
+ lines.push('---');
166
+ if (frontmatter.description) {
167
+ lines.push(`description: "${frontmatter.description}"`);
168
+ }
169
+ if (frontmatter['allowed-tools']) {
170
+ lines.push(`allowed-tools: ${frontmatter['allowed-tools']}`);
171
+ }
172
+ if (frontmatter['argument-hint']) {
173
+ lines.push(`argument-hint: ${frontmatter['argument-hint']}`);
174
+ }
175
+ lines.push('---');
176
+ lines.push('');
177
+ }
178
+
179
+ const body = command.body || '';
180
+ return `${lines.join('\n')}${body}`;
181
+ }
182
+
183
+ function collectSkillFiles(baseDir) {
184
+ const files = [];
185
+ const stack = [baseDir];
186
+
187
+ while (stack.length > 0) {
188
+ const currentDir = stack.pop();
189
+ let entries = [];
190
+ try {
191
+ entries = fs.readdirSync(currentDir, { withFileTypes: true });
192
+ } catch (err) {
193
+ continue;
194
+ }
195
+
196
+ for (const entry of entries) {
197
+ if (entry.isDirectory()) {
198
+ if (SKILL_IGNORE_DIRS.has(entry.name)) {
199
+ continue;
200
+ }
201
+ stack.push(path.join(currentDir, entry.name));
202
+ } else if (entry.isFile()) {
203
+ if (SKILL_IGNORE_FILES.has(entry.name)) {
204
+ continue;
205
+ }
206
+ const fullPath = path.join(currentDir, entry.name);
207
+ const relativePath = path.relative(baseDir, fullPath);
208
+ try {
209
+ const content = fs.readFileSync(fullPath);
210
+ files.push({
211
+ path: relativePath,
212
+ encoding: SKILL_FILE_ENCODING,
213
+ content: content.toString(SKILL_FILE_ENCODING)
214
+ });
215
+ } catch (err) {
216
+ continue;
217
+ }
218
+ }
219
+ }
220
+ }
221
+
222
+ files.sort((a, b) => a.path.localeCompare(b.path));
223
+ return files;
224
+ }
225
+
226
+ function collectPluginFiles(pluginDir, basePath = '') {
227
+ const files = [];
228
+ const stack = [pluginDir];
229
+
230
+ while (stack.length > 0) {
231
+ const currentDir = stack.pop();
232
+ let entries = [];
233
+ try {
234
+ entries = fs.readdirSync(currentDir, { withFileTypes: true });
235
+ } catch (err) {
236
+ continue;
237
+ }
238
+
239
+ for (const entry of entries) {
240
+ if (entry.isDirectory()) {
241
+ if (PLUGIN_IGNORE_DIRS.has(entry.name)) {
242
+ continue;
243
+ }
244
+ stack.push(path.join(currentDir, entry.name));
245
+ } else if (entry.isFile()) {
246
+ if (PLUGIN_IGNORE_FILES.has(entry.name)) {
247
+ continue;
248
+ }
249
+ const fullPath = path.join(currentDir, entry.name);
250
+ const relativePath = path.relative(pluginDir, fullPath);
251
+
252
+ // Skip sensitive files
253
+ const isSensitive = PLUGIN_SENSITIVE_PATTERNS.some(pattern => pattern.test(entry.name));
254
+ if (isSensitive) {
255
+ continue;
256
+ }
257
+
258
+ try {
259
+ const content = fs.readFileSync(fullPath);
260
+ files.push({
261
+ path: relativePath,
262
+ encoding: SKILL_FILE_ENCODING,
263
+ content: content.toString(SKILL_FILE_ENCODING)
264
+ });
265
+ } catch (err) {
266
+ continue;
267
+ }
268
+ }
269
+ }
270
+ }
271
+
272
+ files.sort((a, b) => a.path.localeCompare(b.path));
273
+ return files;
274
+ }
275
+
276
+ function exportSkillsSnapshot() {
277
+ const skillService = new SkillService();
278
+ const installedSkills = skillService.getInstalledSkills();
279
+ const baseDir = skillService.installDir;
280
+
281
+ return installedSkills.map((skill) => {
282
+ const directory = skill.directory;
283
+ const skillDir = resolveSafePath(baseDir, directory);
284
+ if (!skillDir || !fs.existsSync(skillDir)) {
285
+ return null;
286
+ }
287
+ return {
288
+ directory,
289
+ name: skill.name || directory,
290
+ description: skill.description || '',
291
+ files: collectSkillFiles(skillDir)
292
+ };
293
+ }).filter(Boolean);
294
+ }
295
+
296
+ function exportLegacyPlugins() {
297
+ const plugins = [];
298
+
299
+ // Check if legacy plugins directory exists
300
+ if (!fs.existsSync(LEGACY_PLUGINS_DIR)) {
301
+ return plugins;
302
+ }
303
+
304
+ // Read registry.json if it exists
305
+ const registry = readJsonFileSafe(LEGACY_PLUGINS_REGISTRY) || {};
306
+
307
+ try {
308
+ const pluginDirs = fs.readdirSync(LEGACY_PLUGINS_DIR, { withFileTypes: true });
309
+
310
+ for (const entry of pluginDirs) {
311
+ if (!entry.isDirectory()) {
312
+ continue;
313
+ }
314
+
315
+ const pluginName = entry.name;
316
+ const pluginDir = path.join(LEGACY_PLUGINS_DIR, pluginName);
317
+ const manifestPath = path.join(pluginDir, 'plugin.json');
318
+
319
+ // Read plugin.json manifest
320
+ const manifest = readJsonFileSafe(manifestPath);
321
+ if (!manifest) {
322
+ continue;
323
+ }
324
+
325
+ // Collect all files in the plugin directory
326
+ const files = collectPluginFiles(pluginDir);
327
+
328
+ plugins.push({
329
+ type: 'legacy',
330
+ name: manifest.name || pluginName,
331
+ version: manifest.version || '1.0.0',
332
+ description: manifest.description || '',
333
+ author: manifest.author || '',
334
+ directory: pluginName,
335
+ manifest: manifest,
336
+ files: files,
337
+ registryEntry: registry[pluginName] || null
338
+ });
339
+ }
340
+ } catch (err) {
341
+ console.warn('[ConfigExport] Failed to export legacy plugins:', err.message);
342
+ }
343
+
344
+ return plugins;
345
+ }
346
+
347
+ function exportNativePlugins() {
348
+ const plugins = [];
349
+
350
+ // Read installed_plugins.json
351
+ const installedPlugins = readJsonFileSafe(NATIVE_PLUGINS_REGISTRY);
352
+ if (!installedPlugins || typeof installedPlugins !== 'object') {
353
+ return plugins;
354
+ }
355
+
356
+ try {
357
+ for (const [pluginId, pluginInfo] of Object.entries(installedPlugins)) {
358
+ if (!pluginInfo || !pluginInfo.installPath) {
359
+ continue;
360
+ }
361
+
362
+ const pluginDir = pluginInfo.installPath;
363
+ if (!fs.existsSync(pluginDir)) {
364
+ continue;
365
+ }
366
+
367
+ // Read package.json manifest
368
+ const manifestPath = path.join(pluginDir, 'package.json');
369
+ const manifest = readJsonFileSafe(manifestPath);
370
+ if (!manifest) {
371
+ continue;
372
+ }
373
+
374
+ // Collect all files in the plugin directory
375
+ const files = collectPluginFiles(pluginDir);
376
+
377
+ plugins.push({
378
+ type: 'native',
379
+ id: pluginId,
380
+ name: manifest.name || pluginId,
381
+ version: manifest.version || '1.0.0',
382
+ description: manifest.description || '',
383
+ author: manifest.author || '',
384
+ installPath: pluginDir,
385
+ manifest: manifest,
386
+ files: files,
387
+ registryEntry: pluginInfo
388
+ });
389
+ }
390
+ } catch (err) {
391
+ console.warn('[ConfigExport] Failed to export native plugins:', err.message);
392
+ }
393
+
394
+ return plugins;
395
+ }
396
+
397
+ function exportPluginsSnapshot() {
398
+ const legacyPlugins = exportLegacyPlugins();
399
+ const nativePlugins = exportNativePlugins();
400
+ return [...legacyPlugins, ...nativePlugins];
401
+ }
402
+
403
+ function writeTextFile(baseDir, relativePath, content, overwrite) {
404
+ if (!content && content !== '') {
405
+ return 'failed';
406
+ }
407
+ const targetPath = resolveSafePath(baseDir, relativePath);
408
+ if (!targetPath) return 'failed';
409
+ if (fs.existsSync(targetPath) && !overwrite) return 'skipped';
410
+
411
+ ensureDir(path.dirname(targetPath));
412
+ fs.writeFileSync(targetPath, content, 'utf8');
413
+ return 'success';
414
+ }
415
+
416
+ /**
417
+ * 导出所有配置为JSON
418
+ * @returns {Object} 配置导出对象
419
+ */
420
+ function exportAllConfigs() {
421
+ try {
422
+ // 获取所有配置模板(只导出自定义模板)
423
+ const allConfigTemplates = configTemplatesService.getAllTemplates();
424
+ const customConfigTemplates = allConfigTemplates.filter(t => !t.isBuiltin);
425
+
426
+ // 获取所有频道配置
427
+ const channels = channelsService.getAllChannels() || [];
428
+
429
+ // 获取工作区配置
430
+ const workspaceService = require('./workspace-service');
431
+ const workspaces = workspaceService.loadWorkspaces();
432
+
433
+ // 获取收藏配置
434
+ const favoritesService = require('./favorites');
435
+ const favorites = favoritesService.loadFavorites();
436
+
437
+ // 获取 Agents 配置
438
+ const agentsService = new AgentsService();
439
+ const { agents: rawAgents } = agentsService.listAgents();
440
+ const agents = rawAgents.map(agent => ({
441
+ fileName: agent.fileName,
442
+ name: agent.name,
443
+ description: agent.description,
444
+ tools: agent.tools,
445
+ model: agent.model,
446
+ permissionMode: agent.permissionMode,
447
+ skills: agent.skills,
448
+ path: agent.path,
449
+ systemPrompt: agent.systemPrompt,
450
+ fullContent: agent.fullContent
451
+ }));
452
+
453
+ // 获取 Skills 配置
454
+ const skills = exportSkillsSnapshot();
455
+
456
+ // 获取 Commands 配置
457
+ const commandsService = new CommandsService();
458
+ const { commands: rawCommands } = commandsService.listCommands();
459
+ const commands = rawCommands.map(command => ({
460
+ name: command.name,
461
+ namespace: command.namespace,
462
+ description: command.description,
463
+ allowedTools: command.allowedTools,
464
+ argumentHint: command.argumentHint,
465
+ path: command.path,
466
+ body: command.body,
467
+ fullContent: command.fullContent
468
+ }));
469
+
470
+ // 获取 MCP 配置
471
+ const mcpService = require('./mcp-service');
472
+ const mcpServers = mcpService.getAllServers();
473
+
474
+ // 获取 Plugins 配置
475
+ const plugins = exportPluginsSnapshot();
476
+
477
+ // 读取 Markdown 配置文件
478
+ const { PATHS } = require('../../config/paths');
479
+ const markdownFiles = {};
480
+ const mdFileNames = ['CLAUDE.md', 'AGENTS.md', 'GEMINI.md'];
481
+
482
+ for (const fileName of mdFileNames) {
483
+ const filePath = path.join(PATHS.base, fileName);
484
+ if (fs.existsSync(filePath)) {
485
+ try {
486
+ markdownFiles[fileName] = fs.readFileSync(filePath, 'utf8');
487
+ } catch (err) {
488
+ console.warn(`无法读取 ${fileName}:`, err.message);
489
+ }
490
+ }
491
+ }
492
+
493
+ // 获取 UI / 前端配置
494
+ const { loadUIConfig } = require('./ui-config');
495
+ const { loadConfig } = require('../../config/loader');
496
+
497
+ const uiConfigFile = readJsonFileSafe(CC_UI_CONFIG_PATH);
498
+ const uiConfig = uiConfigFile || loadUIConfig();
499
+ const prompts = readJsonFileSafe(CC_PROMPTS_PATH);
500
+ const security = readJsonFileSafe(CC_SECURITY_PATH);
501
+ const appConfig = loadConfig();
502
+
503
+ const claudeSettings = readJsonFileSafe(CLAUDE_SETTINGS_PATH);
504
+ const claudeHooks = {
505
+ uiConfig: readJsonFileSafe(LEGACY_UI_CONFIG_PATH),
506
+ notifyScript: readTextFileSafe(LEGACY_NOTIFY_HOOK_PATH),
507
+ stopHook: claudeSettings?.hooks?.Stop || null
508
+ };
509
+
510
+ const exportData = {
511
+ version: CONFIG_VERSION,
512
+ exportedAt: new Date().toISOString(),
513
+ data: {
514
+ configTemplates: customConfigTemplates,
515
+ channels: channels || [],
516
+ workspaces: workspaces || { workspaces: [] },
517
+ favorites: favorites || { favorites: [] },
518
+ agents: agents || [],
519
+ skills: skills || [],
520
+ commands: commands || [],
521
+ mcpServers: mcpServers || [],
522
+ plugins: plugins || [],
523
+ markdownFiles: markdownFiles,
524
+ uiConfig: uiConfig,
525
+ prompts: prompts,
526
+ security: security,
527
+ appConfig: appConfig,
528
+ claudeHooks: claudeHooks
529
+ }
530
+ };
531
+
532
+ return {
533
+ success: true,
534
+ data: exportData
535
+ };
536
+ } catch (error) {
537
+ console.error('[ConfigExport] 导出配置失败:', error);
538
+ return {
539
+ success: false,
540
+ message: error.message
541
+ };
542
+ }
543
+ }
544
+
545
+ /**
546
+ * 导出所有配置为 ZIP
547
+ * @returns {Object} { success, data: Buffer, filename }
548
+ */
549
+ function exportAllConfigsZip() {
550
+ const result = exportAllConfigs();
551
+ if (!result.success) {
552
+ return result;
553
+ }
554
+
555
+ const exportData = result.data;
556
+ const zip = new AdmZip();
557
+ const jsonContent = JSON.stringify(exportData, null, 2);
558
+
559
+ zip.addFile('config.json', Buffer.from(jsonContent, 'utf8'));
560
+ zip.addFile('README.md', Buffer.from(buildExportReadme(exportData), 'utf8'));
561
+
562
+ return {
563
+ success: true,
564
+ data: zip.toBuffer(),
565
+ filename: `ctx-config-${new Date().toISOString().split('T')[0]}.zip`
566
+ };
567
+ }
568
+
569
+ /**
570
+ * 导入配置
571
+ * @param {Object} importData - 导入的配置对象
572
+ * @param {Object} options - 导入选项 { overwrite: boolean }
573
+ * @returns {Object} 导入结果
574
+ */
575
+ function importConfigs(importData, options = {}) {
576
+ const { overwrite = true } = options; // 默认覆盖模式
577
+ const results = {
578
+ configTemplates: { success: 0, failed: 0, skipped: 0 },
579
+ channels: { success: 0, failed: 0, skipped: 0 },
580
+ workspaces: { success: 0, failed: 0, skipped: 0 },
581
+ favorites: { success: 0, failed: 0, skipped: 0 },
582
+ agents: { success: 0, failed: 0, skipped: 0 },
583
+ skills: { success: 0, failed: 0, skipped: 0 },
584
+ commands: { success: 0, failed: 0, skipped: 0 },
585
+ mcpServers: { success: 0, failed: 0, skipped: 0 },
586
+ plugins: { success: 0, failed: 0, skipped: 0 },
587
+ markdownFiles: { success: 0, failed: 0, skipped: 0 },
588
+ uiConfig: { success: 0, failed: 0, skipped: 0 },
589
+ prompts: { success: 0, failed: 0, skipped: 0 },
590
+ security: { success: 0, failed: 0, skipped: 0 },
591
+ appConfig: { success: 0, failed: 0, skipped: 0 },
592
+ claudeHooks: { success: 0, failed: 0, skipped: 0 }
593
+ };
594
+
595
+ try {
596
+ // 验证导入数据格式
597
+ if (!importData || !importData.data) {
598
+ throw new Error('无效的导入数据格式');
599
+ }
600
+
601
+ const {
602
+ configTemplates = [],
603
+ channels = [],
604
+ workspaces = null,
605
+ favorites = null,
606
+ agents = [],
607
+ skills = [],
608
+ commands = [],
609
+ mcpServers = [],
610
+ markdownFiles = {},
611
+ uiConfig = null,
612
+ prompts = null,
613
+ security = null,
614
+ appConfig = null,
615
+ claudeHooks = null
616
+ } = importData.data;
617
+
618
+ // 导入配置模板
619
+ for (const template of configTemplates) {
620
+ try {
621
+ const existing = configTemplatesService.getTemplateById(template.id);
622
+
623
+ if (existing && !overwrite) {
624
+ results.configTemplates.skipped++;
625
+ continue;
626
+ }
627
+
628
+ if (existing && overwrite) {
629
+ configTemplatesService.updateCustomTemplate(template.id, template);
630
+ } else {
631
+ const newTemplate = {
632
+ ...template,
633
+ isBuiltin: false,
634
+ importedAt: new Date().toISOString()
635
+ };
636
+ configTemplatesService.createCustomTemplate(newTemplate);
637
+ }
638
+ results.configTemplates.success++;
639
+ } catch (err) {
640
+ console.error(`[ConfigImport] 导入配置模板失败: ${template.name}`, err);
641
+ results.configTemplates.failed++;
642
+ }
643
+ }
644
+
645
+ // 导入频道配置
646
+ for (const channel of channels) {
647
+ try {
648
+ const existingChannels = channelsService.getAllChannels() || [];
649
+ const existing = existingChannels.find(c => c.id === channel.id);
650
+
651
+ if (existing && !overwrite) {
652
+ results.channels.skipped++;
653
+ continue;
654
+ }
655
+
656
+ if (existing && overwrite) {
657
+ channelsService.updateChannel(channel.id, channel);
658
+ } else {
659
+ const { name, baseUrl, apiKey, websiteUrl, ...extraConfig } = channel;
660
+ channelsService.createChannel(name, baseUrl, apiKey, websiteUrl, extraConfig);
661
+ }
662
+ results.channels.success++;
663
+ } catch (err) {
664
+ console.error(`[ConfigImport] 导入频道失败: ${channel.name}`, err);
665
+ results.channels.failed++;
666
+ }
667
+ }
668
+
669
+ // 导入工作区配置
670
+ if (workspaces && overwrite) {
671
+ try {
672
+ const workspaceService = require('./workspace-service');
673
+ workspaceService.saveWorkspaces(workspaces);
674
+ results.workspaces.success = workspaces.workspaces?.length || 0;
675
+ } catch (err) {
676
+ console.error('[ConfigImport] 导入工作区失败:', err);
677
+ results.workspaces.failed++;
678
+ }
679
+ }
680
+
681
+ // 导入收藏配置
682
+ if (favorites && overwrite) {
683
+ try {
684
+ const favoritesService = require('./favorites');
685
+ favoritesService.saveFavorites(favorites);
686
+ const count = Object.values(favorites).reduce((sum, arr) => sum + (Array.isArray(arr) ? arr.length : 0), 0);
687
+ results.favorites.success = count;
688
+ } catch (err) {
689
+ console.error('[ConfigImport] 导入收藏失败:', err);
690
+ results.favorites.failed++;
691
+ }
692
+ }
693
+
694
+ // 导入 Agents
695
+ if (agents && agents.length > 0) {
696
+ try {
697
+ const agentsService = new AgentsService();
698
+ const baseDir = agentsService.userAgentsDir;
699
+
700
+ for (const agent of agents) {
701
+ const relativePath = agent.path || agent.fileName || agent.name;
702
+ const filePath = relativePath && relativePath.endsWith('.md')
703
+ ? relativePath
704
+ : (relativePath ? `${relativePath}.md` : null);
705
+ const content = agent.fullContent || agent.content || buildAgentContent(agent);
706
+
707
+ const status = filePath ? writeTextFile(baseDir, filePath, content, overwrite) : 'failed';
708
+ if (status === 'success') {
709
+ results.agents.success++;
710
+ } else if (status === 'skipped') {
711
+ results.agents.skipped++;
712
+ } else {
713
+ results.agents.failed++;
714
+ }
715
+ }
716
+ } catch (err) {
717
+ console.error('[ConfigImport] 导入 Agents 失败:', err);
718
+ }
719
+ }
720
+
721
+ // 导入 Skills
722
+ if (skills && skills.length > 0) {
723
+ try {
724
+ const skillService = new SkillService();
725
+ const baseDir = skillService.installDir;
726
+ ensureDir(baseDir);
727
+
728
+ for (const skill of skills) {
729
+ const directory = skill.directory;
730
+ const skillDir = resolveSafePath(baseDir, directory);
731
+ if (!skillDir) {
732
+ results.skills.failed++;
733
+ continue;
734
+ }
735
+
736
+ if (fs.existsSync(skillDir)) {
737
+ if (!overwrite) {
738
+ results.skills.skipped++;
739
+ continue;
740
+ }
741
+ fs.rmSync(skillDir, { recursive: true, force: true });
742
+ }
743
+
744
+ ensureDir(skillDir);
745
+ const files = Array.isArray(skill.files) ? skill.files : [];
746
+ let failed = false;
747
+
748
+ for (const file of files) {
749
+ const filePath = resolveSafePath(skillDir, file.path);
750
+ if (!filePath) {
751
+ failed = true;
752
+ break;
753
+ }
754
+ ensureDir(path.dirname(filePath));
755
+ try {
756
+ if (file.encoding === SKILL_FILE_ENCODING) {
757
+ fs.writeFileSync(filePath, Buffer.from(file.content || '', SKILL_FILE_ENCODING));
758
+ } else {
759
+ fs.writeFileSync(filePath, file.content || '', file.encoding || 'utf8');
760
+ }
761
+ } catch (err) {
762
+ failed = true;
763
+ break;
764
+ }
765
+ }
766
+
767
+ if (failed || files.length === 0) {
768
+ results.skills.failed++;
769
+ } else {
770
+ results.skills.success++;
771
+ }
772
+ }
773
+ } catch (err) {
774
+ console.error('[ConfigImport] 导入 Skills 失败:', err);
775
+ }
776
+ }
777
+
778
+ // 导入 Plugins
779
+ if (importData.data.plugins && importData.data.plugins.length > 0) {
780
+ const plugins = importData.data.plugins;
781
+
782
+ try {
783
+ for (const plugin of plugins) {
784
+ try {
785
+ const pluginType = plugin.type || 'legacy';
786
+
787
+ // Determine target directory based on plugin type
788
+ let targetDir;
789
+ let registryPath;
790
+
791
+ if (pluginType === 'legacy') {
792
+ targetDir = path.join(LEGACY_PLUGINS_DIR, plugin.directory || plugin.name);
793
+ registryPath = LEGACY_PLUGINS_REGISTRY;
794
+ } else if (pluginType === 'native') {
795
+ // SECURITY: Never trust installPath from import data - construct it safely
796
+ const pluginId = plugin.id || plugin.name;
797
+ if (!pluginId) {
798
+ console.warn('[ConfigImport] Native plugin missing id/name, skipping');
799
+ results.plugins.failed++;
800
+ continue;
801
+ }
802
+ targetDir = path.join(os.homedir(), '.claude', 'plugins', pluginId);
803
+ registryPath = NATIVE_PLUGINS_REGISTRY;
804
+ } else {
805
+ console.warn(`[ConfigImport] Unknown plugin type: ${pluginType}`);
806
+ results.plugins.failed++;
807
+ continue;
808
+ }
809
+
810
+ // Check if plugin exists
811
+ if (fs.existsSync(targetDir)) {
812
+ if (!overwrite) {
813
+ results.plugins.skipped++;
814
+ continue;
815
+ }
816
+ // Remove existing plugin directory
817
+ fs.rmSync(targetDir, { recursive: true, force: true });
818
+ }
819
+
820
+ // Create plugin directory
821
+ ensureDir(targetDir);
822
+
823
+ // Write all plugin files from base64 content
824
+ const files = Array.isArray(plugin.files) ? plugin.files : [];
825
+ let failed = false;
826
+
827
+ for (const file of files) {
828
+ const filePath = resolveSafePath(targetDir, file.path);
829
+ if (!filePath) {
830
+ failed = true;
831
+ break;
832
+ }
833
+
834
+ ensureDir(path.dirname(filePath));
835
+
836
+ try {
837
+ if (file.encoding === SKILL_FILE_ENCODING) {
838
+ fs.writeFileSync(filePath, Buffer.from(file.content || '', SKILL_FILE_ENCODING));
839
+ } else {
840
+ fs.writeFileSync(filePath, file.content || '', file.encoding || 'utf8');
841
+ }
842
+ } catch (err) {
843
+ console.error(`[ConfigImport] Failed to write plugin file ${file.path}:`, err);
844
+ failed = true;
845
+ break;
846
+ }
847
+ }
848
+
849
+ if (failed || files.length === 0) {
850
+ results.plugins.failed++;
851
+ continue;
852
+ }
853
+
854
+ // Update appropriate registry
855
+ try {
856
+ ensureDir(path.dirname(registryPath));
857
+
858
+ if (pluginType === 'legacy') {
859
+ // Update legacy registry.json
860
+ const registry = readJsonFileSafe(registryPath) || {};
861
+ const pluginKey = plugin.directory || plugin.name;
862
+
863
+ if (plugin.registryEntry) {
864
+ registry[pluginKey] = plugin.registryEntry;
865
+ } else {
866
+ registry[pluginKey] = {
867
+ name: plugin.name,
868
+ version: plugin.version,
869
+ description: plugin.description,
870
+ author: plugin.author,
871
+ installedAt: new Date().toISOString()
872
+ };
873
+ }
874
+
875
+ fs.writeFileSync(registryPath, JSON.stringify(registry, null, 2), 'utf8');
876
+ } else if (pluginType === 'native') {
877
+ // Update native installed_plugins.json
878
+ const installedPlugins = readJsonFileSafe(registryPath) || {};
879
+ const pluginId = plugin.id || plugin.name;
880
+
881
+ if (plugin.registryEntry) {
882
+ installedPlugins[pluginId] = {
883
+ ...plugin.registryEntry,
884
+ installPath: targetDir
885
+ };
886
+ } else {
887
+ installedPlugins[pluginId] = {
888
+ name: plugin.name,
889
+ version: plugin.version,
890
+ description: plugin.description,
891
+ author: plugin.author,
892
+ installPath: targetDir,
893
+ installedAt: new Date().toISOString()
894
+ };
895
+ }
896
+
897
+ fs.writeFileSync(registryPath, JSON.stringify(installedPlugins, null, 2), 'utf8');
898
+ }
899
+
900
+ results.plugins.success++;
901
+ } catch (err) {
902
+ console.error(`[ConfigImport] Failed to update plugin registry for ${plugin.name}:`, err);
903
+ results.plugins.failed++;
904
+ }
905
+ } catch (err) {
906
+ console.error(`[ConfigImport] Failed to import plugin ${plugin.name}:`, err);
907
+ results.plugins.failed++;
908
+ }
909
+ }
910
+ } catch (err) {
911
+ console.error('[ConfigImport] 导入 Plugins 失败:', err);
912
+ }
913
+ }
914
+
915
+ // 导入 Commands
916
+ if (commands && commands.length > 0) {
917
+ try {
918
+ const commandsService = new CommandsService();
919
+ const baseDir = commandsService.userCommandsDir;
920
+
921
+ for (const command of commands) {
922
+ const relativePath = command.path || (
923
+ command.namespace
924
+ ? path.join(command.namespace, `${command.name}.md`)
925
+ : `${command.name}.md`
926
+ );
927
+ const content = command.fullContent || command.content || buildCommandContent(command);
928
+
929
+ const status = relativePath ? writeTextFile(baseDir, relativePath, content, overwrite) : 'failed';
930
+ if (status === 'success') {
931
+ results.commands.success++;
932
+ } else if (status === 'skipped') {
933
+ results.commands.skipped++;
934
+ } else {
935
+ results.commands.failed++;
936
+ }
937
+ }
938
+ } catch (err) {
939
+ console.error('[ConfigImport] 导入 Commands 失败:', err);
940
+ }
941
+ }
942
+
943
+ // 导入 MCP Servers
944
+ if (mcpServers && mcpServers.length > 0 && overwrite) {
945
+ try {
946
+ const mcpService = require('./mcp-service');
947
+ for (const server of mcpServers) {
948
+ try {
949
+ mcpService.saveServer(server);
950
+ results.mcpServers.success++;
951
+ } catch (err) {
952
+ console.error(`[ConfigImport] 导入 MCP Server 失败: ${server.name}`, err);
953
+ results.mcpServers.failed++;
954
+ }
955
+ }
956
+ } catch (err) {
957
+ console.error('[ConfigImport] 导入 MCP Servers 失败:', err);
958
+ }
959
+ }
960
+
961
+ // 导入 Markdown 文件
962
+ if (markdownFiles && Object.keys(markdownFiles).length > 0 && overwrite) {
963
+ const { PATHS } = require('../../config/paths');
964
+ // SECURITY: Whitelist allowed markdown files to prevent path traversal
965
+ const ALLOWED_MARKDOWN_FILES = new Set(['CLAUDE.md', 'AGENTS.md', 'GEMINI.md']);
966
+ for (const [fileName, content] of Object.entries(markdownFiles)) {
967
+ try {
968
+ if (!ALLOWED_MARKDOWN_FILES.has(fileName)) {
969
+ console.warn(`[ConfigImport] Skipping disallowed markdown file: ${fileName}`);
970
+ results.markdownFiles.failed++;
971
+ continue;
972
+ }
973
+ const filePath = path.join(PATHS.base, fileName);
974
+ fs.writeFileSync(filePath, content, 'utf8');
975
+ results.markdownFiles.success++;
976
+ } catch (err) {
977
+ console.error(`[ConfigImport] 导入 ${fileName} 失败:`, err);
978
+ results.markdownFiles.failed++;
979
+ }
980
+ }
981
+ }
982
+
983
+ // 导入 UI 配置
984
+ if (uiConfig && typeof uiConfig === 'object') {
985
+ try {
986
+ if (fs.existsSync(CC_UI_CONFIG_PATH) && !overwrite) {
987
+ results.uiConfig.skipped++;
988
+ } else {
989
+ const { saveUIConfig } = require('./ui-config');
990
+ saveUIConfig(uiConfig);
991
+ results.uiConfig.success++;
992
+ }
993
+ } catch (err) {
994
+ console.error('[ConfigImport] 导入 UI 配置失败:', err);
995
+ results.uiConfig.failed++;
996
+ }
997
+ }
998
+
999
+ // 导入 Prompts 配置
1000
+ if (prompts && typeof prompts === 'object') {
1001
+ try {
1002
+ const status = writeJsonFileAbsolute(CC_PROMPTS_PATH, prompts, overwrite);
1003
+ if (status === 'success') {
1004
+ results.prompts.success++;
1005
+ } else if (status === 'skipped') {
1006
+ results.prompts.skipped++;
1007
+ } else {
1008
+ results.prompts.failed++;
1009
+ }
1010
+ } catch (err) {
1011
+ console.error('[ConfigImport] 导入 Prompts 失败:', err);
1012
+ results.prompts.failed++;
1013
+ }
1014
+ }
1015
+
1016
+ // 导入安全配置
1017
+ if (security && typeof security === 'object') {
1018
+ try {
1019
+ const status = writeJsonFileAbsolute(CC_SECURITY_PATH, security, overwrite, { mode: 0o600 });
1020
+ if (status === 'success') {
1021
+ results.security.success++;
1022
+ } else if (status === 'skipped') {
1023
+ results.security.skipped++;
1024
+ } else {
1025
+ results.security.failed++;
1026
+ }
1027
+ } catch (err) {
1028
+ console.error('[ConfigImport] 导入安全配置失败:', err);
1029
+ results.security.failed++;
1030
+ }
1031
+ }
1032
+
1033
+ // 导入高级配置(config.json)
1034
+ if (appConfig && typeof appConfig === 'object') {
1035
+ try {
1036
+ const configFilePath = getConfigFilePath();
1037
+ if (configFilePath && fs.existsSync(configFilePath) && !overwrite) {
1038
+ results.appConfig.skipped++;
1039
+ } else {
1040
+ const { saveConfig } = require('../../config/loader');
1041
+ saveConfig(appConfig);
1042
+ results.appConfig.success++;
1043
+ }
1044
+ } catch (err) {
1045
+ console.error('[ConfigImport] 导入高级配置失败:', err);
1046
+ results.appConfig.failed++;
1047
+ }
1048
+ }
1049
+
1050
+ // 导入 Claude Hooks 配置
1051
+ if (claudeHooks && typeof claudeHooks === 'object') {
1052
+ let didWrite = false;
1053
+ let didSkip = false;
1054
+ let didFail = false;
1055
+
1056
+ try {
1057
+ if (claudeHooks.uiConfig && typeof claudeHooks.uiConfig === 'object') {
1058
+ const status = writeJsonFileAbsolute(LEGACY_UI_CONFIG_PATH, claudeHooks.uiConfig, overwrite);
1059
+ if (status === 'success') didWrite = true;
1060
+ else if (status === 'skipped') didSkip = true;
1061
+ else didFail = true;
1062
+ }
1063
+
1064
+ if (claudeHooks.notifyScript !== undefined && claudeHooks.notifyScript !== null) {
1065
+ const status = writeTextFileAbsolute(LEGACY_NOTIFY_HOOK_PATH, claudeHooks.notifyScript, overwrite, { mode: 0o755 });
1066
+ if (status === 'success') didWrite = true;
1067
+ else if (status === 'skipped') didSkip = true;
1068
+ else didFail = true;
1069
+ }
1070
+
1071
+ if (claudeHooks.stopHook !== undefined) {
1072
+ if (!overwrite && fs.existsSync(CLAUDE_SETTINGS_PATH)) {
1073
+ didSkip = true;
1074
+ } else {
1075
+ const settings = readJsonFileSafe(CLAUDE_SETTINGS_PATH) || {};
1076
+ settings.hooks = settings.hooks || {};
1077
+ if (claudeHooks.stopHook) {
1078
+ settings.hooks.Stop = claudeHooks.stopHook;
1079
+ } else if (settings.hooks.Stop) {
1080
+ delete settings.hooks.Stop;
1081
+ if (Object.keys(settings.hooks).length === 0) {
1082
+ delete settings.hooks;
1083
+ }
1084
+ }
1085
+ writeJsonFileAbsolute(CLAUDE_SETTINGS_PATH, settings, true);
1086
+ didWrite = true;
1087
+ }
1088
+ }
1089
+ } catch (err) {
1090
+ console.error('[ConfigImport] 导入 Claude Hooks 失败:', err);
1091
+ didFail = true;
1092
+ }
1093
+
1094
+ if (didFail) {
1095
+ results.claudeHooks.failed++;
1096
+ } else if (didWrite) {
1097
+ results.claudeHooks.success++;
1098
+ } else if (didSkip) {
1099
+ results.claudeHooks.skipped++;
1100
+ }
1101
+ }
1102
+
1103
+ return {
1104
+ success: true,
1105
+ results,
1106
+ message: generateImportSummary(results)
1107
+ };
1108
+ } catch (error) {
1109
+ console.error('[ConfigImport] 导入配置失败:', error);
1110
+ return {
1111
+ success: false,
1112
+ message: error.message,
1113
+ results
1114
+ };
1115
+ }
1116
+ }
1117
+
1118
+ /**
1119
+ * 生成导入摘要消息
1120
+ */
1121
+ function generateImportSummary(results) {
1122
+ const parts = [];
1123
+
1124
+ const types = [
1125
+ { key: 'configTemplates', label: '配置模板' },
1126
+ { key: 'channels', label: '频道' },
1127
+ { key: 'workspaces', label: '工作区' },
1128
+ { key: 'favorites', label: '收藏' },
1129
+ { key: 'agents', label: 'Agents' },
1130
+ { key: 'skills', label: 'Skills' },
1131
+ { key: 'commands', label: 'Commands' },
1132
+ { key: 'mcpServers', label: 'MCP服务器' },
1133
+ { key: 'plugins', label: '插件' },
1134
+ { key: 'markdownFiles', label: 'Markdown文件' },
1135
+ { key: 'uiConfig', label: 'UI配置' },
1136
+ { key: 'prompts', label: 'Prompts' },
1137
+ { key: 'security', label: '安全配置' },
1138
+ { key: 'appConfig', label: '高级配置' },
1139
+ { key: 'claudeHooks', label: 'Claude Hooks' }
1140
+ ];
1141
+
1142
+ for (const { key, label } of types) {
1143
+ if (results[key] && results[key].success > 0) {
1144
+ parts.push(`${label}: ${results[key].success}成功`);
1145
+ }
1146
+ }
1147
+
1148
+ const totalSkipped = Object.values(results).reduce((sum, r) => sum + (r.skipped || 0), 0);
1149
+ if (totalSkipped > 0) {
1150
+ parts.push(`${totalSkipped}项已跳过`);
1151
+ }
1152
+
1153
+ const totalFailed = Object.values(results).reduce((sum, r) => sum + (r.failed || 0), 0);
1154
+ if (totalFailed > 0) {
1155
+ parts.push(`${totalFailed}项失败`);
1156
+ }
1157
+
1158
+ return parts.join(', ') || '无数据导入';
1159
+ }
1160
+
1161
+ module.exports = {
1162
+ exportAllConfigs,
1163
+ exportAllConfigsZip,
1164
+ importConfigs
1165
+ };