coding-tool-x 3.3.5 → 3.3.7

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 (27) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/web/assets/{Analytics-B6CWdkhx.js → Analytics-IW6eAy9u.js} +1 -1
  3. package/dist/web/assets/{ConfigTemplates-BW6LEgd8.js → ConfigTemplates-BPtkTMSc.js} +1 -1
  4. package/dist/web/assets/{Home-B2B2gS2-.js → Home-obifg_9E.js} +1 -1
  5. package/dist/web/assets/{PluginManager-Bqc7ldY-.js → PluginManager-BGx9MSDV.js} +1 -1
  6. package/dist/web/assets/{ProjectList-BFdZZm_8.js → ProjectList-BCn-mrCx.js} +1 -1
  7. package/dist/web/assets/{SessionList-B_Tp37kM.js → SessionList-CzLfebJQ.js} +1 -1
  8. package/dist/web/assets/{SkillManager-ul2rcS3o.js → SkillManager-CXz2vBQx.js} +1 -1
  9. package/dist/web/assets/{WorkspaceManager-Dp5Jvdtu.js → WorkspaceManager-CHtgMfKc.js} +1 -1
  10. package/dist/web/assets/index-C7LPdVsN.js +2 -0
  11. package/dist/web/assets/{index-DxRneGyu.css → index-eEmjZKWP.css} +1 -1
  12. package/dist/web/index.html +2 -2
  13. package/package.json +1 -1
  14. package/src/commands/daemon.js +44 -6
  15. package/src/commands/update.js +21 -6
  16. package/src/config/default.js +1 -1
  17. package/src/config/model-metadata.js +2 -2
  18. package/src/config/model-metadata.json +7 -2
  19. package/src/server/api/config-export.js +21 -2
  20. package/src/server/api/mcp.js +26 -4
  21. package/src/server/index.js +25 -2
  22. package/src/server/services/config-export-service.js +639 -138
  23. package/src/server/services/mcp-client.js +162 -18
  24. package/src/server/services/mcp-service.js +130 -15
  25. package/src/server/services/model-detector.js +1 -0
  26. package/src/utils/port-helper.js +87 -2
  27. package/dist/web/assets/index-CSBDZxYn.js +0 -2
@@ -8,12 +8,15 @@ const path = require('path');
8
8
  const AdmZip = require('adm-zip');
9
9
  const configTemplatesService = require('./config-templates-service');
10
10
  const channelsService = require('./channels');
11
+ const codexChannelsService = require('./codex-channels');
12
+ const geminiChannelsService = require('./gemini-channels');
13
+ const opencodeChannelsService = require('./opencode-channels');
11
14
  const { AgentsService } = require('./agents-service');
12
15
  const { CommandsService } = require('./commands-service');
13
16
  const { SkillService } = require('./skill-service');
14
17
  const { PATHS, NATIVE_PATHS } = require('../../config/paths');
15
18
 
16
- const CONFIG_VERSION = '1.2.0';
19
+ const CONFIG_VERSION = '1.3.0';
17
20
  const SKILL_FILE_ENCODING = 'base64';
18
21
  const SKILL_IGNORE_DIRS = new Set(['.git']);
19
22
  const SKILL_IGNORE_FILES = new Set(['.DS_Store']);
@@ -41,6 +44,41 @@ const CC_PROMPTS_PATH = path.join(CC_TOOL_DIR, 'prompts.json');
41
44
  const CC_SECURITY_PATH = path.join(CC_TOOL_DIR, 'security.json');
42
45
  const LEGACY_UI_CONFIG_PATH = path.join(LEGACY_CC_TOOL_DIR, 'ui-config.json');
43
46
  const LEGACY_NOTIFY_HOOK_PATH = path.join(LEGACY_CC_TOOL_DIR, 'notify-hook.js');
47
+ const GEMINI_SETTINGS_PATH = path.join(path.dirname(NATIVE_PATHS.gemini.env), 'settings.json');
48
+ const AGENT_PLATFORMS = ['claude', 'codex', 'opencode'];
49
+ const COMMAND_PLATFORMS = ['claude', 'opencode'];
50
+ const SKILL_PLATFORMS = ['claude', 'codex', 'gemini', 'opencode'];
51
+
52
+ function getOpenCodeConfigPaths() {
53
+ try {
54
+ const { CONFIG_PATHS } = require('./opencode-settings-manager');
55
+ return CONFIG_PATHS || {};
56
+ } catch (err) {
57
+ return {};
58
+ }
59
+ }
60
+
61
+ function getNativeConfigSpecs() {
62
+ const openCodeConfigPaths = getOpenCodeConfigPaths();
63
+ return {
64
+ claude: {
65
+ settings: { path: NATIVE_PATHS.claude.settings, format: 'json' }
66
+ },
67
+ codex: {
68
+ config: { path: NATIVE_PATHS.codex.config, format: 'text' },
69
+ auth: { path: NATIVE_PATHS.codex.auth, format: 'json', mode: 0o600 }
70
+ },
71
+ gemini: {
72
+ env: { path: NATIVE_PATHS.gemini.env, format: 'text', mode: 0o600 },
73
+ settings: { path: GEMINI_SETTINGS_PATH, format: 'json' }
74
+ },
75
+ opencode: {
76
+ opencodeJsonc: { path: openCodeConfigPaths.opencodec, format: 'text' },
77
+ opencodeJson: { path: openCodeConfigPaths.opencode, format: 'text' },
78
+ configJson: { path: openCodeConfigPaths.config, format: 'text' }
79
+ }
80
+ };
81
+ }
44
82
 
45
83
  function ensureDir(dirPath) {
46
84
  if (!fs.existsSync(dirPath)) {
@@ -67,6 +105,39 @@ function readTextFileSafe(filePath) {
67
105
  }
68
106
  }
69
107
 
108
+ function readNativeConfigSnapshot(spec) {
109
+ if (!spec?.path || !fs.existsSync(spec.path)) {
110
+ return null;
111
+ }
112
+
113
+ try {
114
+ const rawContent = fs.readFileSync(spec.path, 'utf8');
115
+ if (spec.format === 'json') {
116
+ try {
117
+ return {
118
+ format: 'json',
119
+ fileName: path.basename(spec.path),
120
+ content: JSON.parse(rawContent)
121
+ };
122
+ } catch (err) {
123
+ return {
124
+ format: 'text',
125
+ fileName: path.basename(spec.path),
126
+ content: rawContent
127
+ };
128
+ }
129
+ }
130
+
131
+ return {
132
+ format: spec.format || 'text',
133
+ fileName: path.basename(spec.path),
134
+ content: rawContent
135
+ };
136
+ } catch (err) {
137
+ return null;
138
+ }
139
+ }
140
+
70
141
  function writeJsonFileAbsolute(filePath, data, overwrite, options = {}) {
71
142
  if (data === undefined) {
72
143
  return 'failed';
@@ -101,6 +172,19 @@ function writeTextFileAbsolute(filePath, content, overwrite, options = {}) {
101
172
  return 'success';
102
173
  }
103
174
 
175
+ function writeNativeConfigAbsolute(spec, entry, overwrite) {
176
+ if (!spec?.path || !entry || entry.content === undefined) {
177
+ return 'failed';
178
+ }
179
+
180
+ const format = entry.format || spec.format || 'text';
181
+ if (format === 'json' && entry.content && typeof entry.content === 'object') {
182
+ return writeJsonFileAbsolute(spec.path, entry.content, overwrite, { mode: spec.mode });
183
+ }
184
+
185
+ return writeTextFileAbsolute(spec.path, String(entry.content), overwrite, { mode: spec.mode });
186
+ }
187
+
104
188
  function getConfigFilePath() {
105
189
  try {
106
190
  const { getConfigFilePath: resolveConfigPath } = require('../../config/loader');
@@ -122,6 +206,7 @@ function buildExportReadme(exportData) {
122
206
  - Agents / Skills / Commands
123
207
  - 插件 (Plugins)
124
208
  - MCP 服务器配置
209
+ - 各平台原生配置(Claude / Codex / Gemini / OpenCode)
125
210
  - UI 配置(主题、面板显示、排序等)
126
211
  - Prompts 预设
127
212
  - 安全配置
@@ -181,6 +266,288 @@ function buildCommandContent(command) {
181
266
  return `${lines.join('\n')}${body}`;
182
267
  }
183
268
 
269
+ function buildAgentExportItem(agent, platform) {
270
+ return {
271
+ platform,
272
+ fileName: agent.fileName,
273
+ name: agent.name,
274
+ description: agent.description,
275
+ tools: agent.tools,
276
+ model: agent.model,
277
+ permissionMode: agent.permissionMode,
278
+ skills: agent.skills,
279
+ path: agent.path,
280
+ systemPrompt: agent.systemPrompt,
281
+ fullContent: agent.fullContent
282
+ };
283
+ }
284
+
285
+ function buildCommandExportItem(command, platform) {
286
+ return {
287
+ platform,
288
+ name: command.name,
289
+ namespace: command.namespace,
290
+ description: command.description,
291
+ allowedTools: command.allowedTools,
292
+ argumentHint: command.argumentHint,
293
+ path: command.path,
294
+ body: command.body,
295
+ fullContent: command.fullContent
296
+ };
297
+ }
298
+
299
+ function exportAgentsSnapshotByPlatform() {
300
+ return AGENT_PLATFORMS.reduce((result, platform) => {
301
+ try {
302
+ const agentsService = new AgentsService(platform);
303
+ const { agents: rawAgents = [] } = agentsService.listAgents();
304
+ result[platform] = rawAgents.map(agent => buildAgentExportItem(agent, platform));
305
+ } catch (err) {
306
+ console.warn(`[ConfigExport] Failed to export agents for ${platform}:`, err.message);
307
+ result[platform] = [];
308
+ }
309
+ return result;
310
+ }, {});
311
+ }
312
+
313
+ function exportCommandsSnapshotByPlatform() {
314
+ return COMMAND_PLATFORMS.reduce((result, platform) => {
315
+ try {
316
+ const commandsService = new CommandsService(platform);
317
+ const { commands: rawCommands = [] } = commandsService.listCommands();
318
+ result[platform] = rawCommands.map(command => buildCommandExportItem(command, platform));
319
+ } catch (err) {
320
+ console.warn(`[ConfigExport] Failed to export commands for ${platform}:`, err.message);
321
+ result[platform] = [];
322
+ }
323
+ return result;
324
+ }, {});
325
+ }
326
+
327
+ function normalizePlatformItems(legacyItems = [], byPlatform = {}, supportedPlatforms = [], defaultPlatform = 'claude') {
328
+ const result = Object.fromEntries(supportedPlatforms.map(platform => [platform, []]));
329
+ const structuredPlatforms = new Set();
330
+
331
+ for (const platform of supportedPlatforms) {
332
+ if (Array.isArray(byPlatform?.[platform])) {
333
+ result[platform] = byPlatform[platform].map(item => ({
334
+ ...item,
335
+ platform: supportedPlatforms.includes(item?.platform) ? item.platform : platform
336
+ }));
337
+ structuredPlatforms.add(platform);
338
+ }
339
+ }
340
+
341
+ if (!Array.isArray(legacyItems)) {
342
+ return result;
343
+ }
344
+
345
+ for (const item of legacyItems) {
346
+ const platform = supportedPlatforms.includes(item?.platform) ? item.platform : defaultPlatform;
347
+ if (structuredPlatforms.has(platform)) {
348
+ continue;
349
+ }
350
+ result[platform].push({ ...item, platform });
351
+ }
352
+
353
+ return result;
354
+ }
355
+
356
+ function resolveAgentRelativePath(agent, platform) {
357
+ const pathCandidates = [agent?.path, agent?.fileName]
358
+ .filter(value => typeof value === 'string' && value.trim())
359
+ .map(value => value.trim());
360
+
361
+ for (const candidate of pathCandidates) {
362
+ if (path.extname(candidate)) {
363
+ return candidate;
364
+ }
365
+ }
366
+
367
+ const baseName = agent?.fileName || agent?.name;
368
+ if (!baseName) {
369
+ return null;
370
+ }
371
+
372
+ return platform === 'codex' ? `${baseName}.toml` : `${baseName}.md`;
373
+ }
374
+
375
+ function resolveCommandRelativePath(command) {
376
+ if (typeof command?.path === 'string' && command.path.trim()) {
377
+ return command.path.trim();
378
+ }
379
+ if (command?.namespace) {
380
+ return path.join(command.namespace, `${command.name}.md`);
381
+ }
382
+ return command?.name ? `${command.name}.md` : null;
383
+ }
384
+
385
+ function pickPrimaryChannel(channels = []) {
386
+ if (!Array.isArray(channels) || channels.length === 0) {
387
+ return null;
388
+ }
389
+ return channels.find(channel => channel.enabled !== false) || channels[0] || null;
390
+ }
391
+
392
+ function findMatchingPersistedChannel(existingChannels = [], importedChannel = {}, extraMatchers = []) {
393
+ if (!Array.isArray(existingChannels) || existingChannels.length === 0 || !importedChannel) {
394
+ return null;
395
+ }
396
+
397
+ if (importedChannel.id) {
398
+ const exactMatch = existingChannels.find(channel => channel.id === importedChannel.id);
399
+ if (exactMatch) {
400
+ return exactMatch;
401
+ }
402
+ }
403
+
404
+ for (const matcher of extraMatchers) {
405
+ const matchedChannel = existingChannels.find(channel => matcher(channel, importedChannel));
406
+ if (matchedChannel) {
407
+ return matchedChannel;
408
+ }
409
+ }
410
+
411
+ return null;
412
+ }
413
+
414
+ function sanitizeOpenCodeProviderId(value) {
415
+ return String(value || '')
416
+ .trim()
417
+ .toLowerCase()
418
+ .replace(/[^a-z0-9-]/g, '-')
419
+ .replace(/-+/g, '-')
420
+ .replace(/^-|-$/g, '') || 'channel';
421
+ }
422
+
423
+ function buildOpenCodeNativeConfig(channels = []) {
424
+ const config = { provider: {} };
425
+ const usedProviderIds = new Set();
426
+ let defaultModelRef = '';
427
+
428
+ channels.forEach((channel) => {
429
+ const baseProviderId = sanitizeOpenCodeProviderId(channel.providerKey || channel.name);
430
+ let providerId = baseProviderId;
431
+ let suffix = 2;
432
+ while (usedProviderIds.has(providerId)) {
433
+ providerId = `${baseProviderId}-${suffix}`;
434
+ suffix += 1;
435
+ }
436
+ usedProviderIds.add(providerId);
437
+
438
+ const provider = {
439
+ npm: '@ai-sdk/openai-compatible',
440
+ name: channel.name || providerId,
441
+ options: {
442
+ baseURL: channel.baseUrl || '',
443
+ apiKey: channel.apiKey || ''
444
+ }
445
+ };
446
+
447
+ const models = {};
448
+ const allowedModels = Array.isArray(channel.allowedModels) ? channel.allowedModels : [];
449
+ const allModels = allowedModels.length > 0
450
+ ? allowedModels
451
+ : [channel.model].filter(Boolean);
452
+
453
+ allModels.forEach((modelId) => {
454
+ const normalizedModel = String(modelId || '').trim();
455
+ if (!normalizedModel) return;
456
+ models[normalizedModel] = { name: normalizedModel };
457
+ if (!defaultModelRef && (channel.enabled !== false || !pickPrimaryChannel(channels))) {
458
+ defaultModelRef = `${providerId}/${normalizedModel}`;
459
+ }
460
+ });
461
+
462
+ if (Object.keys(models).length > 0) {
463
+ provider.models = models;
464
+ }
465
+
466
+ config.provider[providerId] = provider;
467
+ });
468
+
469
+ const preferredChannel = pickPrimaryChannel(channels);
470
+ if (!defaultModelRef && preferredChannel?.model) {
471
+ const providerId = sanitizeOpenCodeProviderId(preferredChannel.providerKey || preferredChannel.name);
472
+ defaultModelRef = `${providerId}/${preferredChannel.model}`;
473
+ }
474
+
475
+ if (defaultModelRef) {
476
+ config.model = defaultModelRef;
477
+ }
478
+
479
+ return config;
480
+ }
481
+
482
+ function syncImportedChannelsToNativeConfigs(importChannelsByType, nativeConfigs, overwrite) {
483
+ const hasNativeConfigFor = (platform) => !!nativeConfigs?.[platform] && Object.keys(nativeConfigs[platform]).length > 0;
484
+
485
+ try {
486
+ if (!hasNativeConfigFor('claude')) {
487
+ const primaryClaudeChannel = pickPrimaryChannel(importChannelsByType.claude);
488
+ const persistedClaudeChannel = findMatchingPersistedChannel(
489
+ channelsService.getAllChannels?.() || [],
490
+ primaryClaudeChannel,
491
+ [
492
+ (channel, imported) => channel.name === imported.name && channel.baseUrl === imported.baseUrl
493
+ ]
494
+ );
495
+ if (persistedClaudeChannel?.id) {
496
+ ensureDir(path.dirname(NATIVE_PATHS.claude.settings));
497
+ channelsService.applyChannelToSettings(persistedClaudeChannel.id);
498
+ }
499
+ }
500
+ } catch (err) {
501
+ console.warn('[ConfigImport] Claude native sync fallback failed:', err.message);
502
+ }
503
+
504
+ try {
505
+ if (!hasNativeConfigFor('codex')) {
506
+ codexChannelsService.writeCodexConfigForMultiChannel(importChannelsByType.codex || []);
507
+ }
508
+ } catch (err) {
509
+ console.warn('[ConfigImport] Codex native sync fallback failed:', err.message);
510
+ }
511
+
512
+ try {
513
+ if (!hasNativeConfigFor('gemini')) {
514
+ const primaryGeminiChannel = pickPrimaryChannel(importChannelsByType.gemini);
515
+ const persistedGeminiChannel = findMatchingPersistedChannel(
516
+ geminiChannelsService.getChannels?.().channels || [],
517
+ primaryGeminiChannel,
518
+ [
519
+ (channel, imported) => channel.name === imported.name && channel.baseUrl === imported.baseUrl
520
+ ]
521
+ );
522
+ if (persistedGeminiChannel?.id) {
523
+ geminiChannelsService.applyChannelToSettings(persistedGeminiChannel.id);
524
+ }
525
+ }
526
+ } catch (err) {
527
+ console.warn('[ConfigImport] Gemini native sync fallback failed:', err.message);
528
+ }
529
+
530
+ try {
531
+ if (!hasNativeConfigFor('opencode')) {
532
+ const openCodeSpecs = getNativeConfigSpecs().opencode || {};
533
+ const preferredSpec = [
534
+ openCodeSpecs.opencodeJsonc,
535
+ openCodeSpecs.opencodeJson,
536
+ openCodeSpecs.configJson
537
+ ].find(spec => spec?.path && fs.existsSync(spec.path))
538
+ || openCodeSpecs.opencodeJson
539
+ || openCodeSpecs.configJson;
540
+
541
+ if (preferredSpec?.path) {
542
+ const payload = buildOpenCodeNativeConfig(importChannelsByType.opencode || []);
543
+ writeTextFileAbsolute(preferredSpec.path, JSON.stringify(payload, null, 2), overwrite);
544
+ }
545
+ }
546
+ } catch (err) {
547
+ console.warn('[ConfigImport] OpenCode native sync fallback failed:', err.message);
548
+ }
549
+ }
550
+
184
551
  function collectSkillFiles(baseDir) {
185
552
  const files = [];
186
553
  const stack = [baseDir];
@@ -274,8 +641,8 @@ function collectPluginFiles(pluginDir, basePath = '') {
274
641
  return files;
275
642
  }
276
643
 
277
- function exportSkillsSnapshot() {
278
- const skillService = new SkillService();
644
+ function exportSkillsSnapshot(platform = 'claude') {
645
+ const skillService = new SkillService(platform);
279
646
  const installedSkills = skillService.getInstalledSkills();
280
647
  const baseDir = skillService.installDir;
281
648
 
@@ -286,6 +653,7 @@ function exportSkillsSnapshot() {
286
653
  return null;
287
654
  }
288
655
  return {
656
+ platform,
289
657
  directory,
290
658
  name: skill.name || directory,
291
659
  description: skill.description || '',
@@ -294,6 +662,36 @@ function exportSkillsSnapshot() {
294
662
  }).filter(Boolean);
295
663
  }
296
664
 
665
+ function exportSkillsSnapshotByPlatform() {
666
+ return SKILL_PLATFORMS.reduce((result, platform) => {
667
+ try {
668
+ result[platform] = exportSkillsSnapshot(platform);
669
+ } catch (err) {
670
+ console.warn(`[ConfigExport] Failed to export skills for ${platform}:`, err.message);
671
+ result[platform] = [];
672
+ }
673
+ return result;
674
+ }, {});
675
+ }
676
+
677
+ function exportNativeConfigs() {
678
+ const specs = getNativeConfigSpecs();
679
+ return Object.entries(specs).reduce((result, [platform, platformSpecs]) => {
680
+ const exportedEntries = Object.entries(platformSpecs).reduce((entries, [key, spec]) => {
681
+ const snapshot = readNativeConfigSnapshot(spec);
682
+ if (snapshot) {
683
+ entries[key] = snapshot;
684
+ }
685
+ return entries;
686
+ }, {});
687
+
688
+ if (Object.keys(exportedEntries).length > 0) {
689
+ result[platform] = exportedEntries;
690
+ }
691
+ return result;
692
+ }, {});
693
+ }
694
+
297
695
  function exportLegacyPlugins() {
298
696
  const plugins = [];
299
697
 
@@ -414,6 +812,14 @@ function writeTextFile(baseDir, relativePath, content, overwrite) {
414
812
  return 'success';
415
813
  }
416
814
 
815
+ function getAllChannelsByType() {
816
+ const claude = channelsService.getAllChannels() || [];
817
+ const codex = codexChannelsService.getChannels()?.channels || [];
818
+ const gemini = geminiChannelsService.getChannels()?.channels || [];
819
+ const opencode = opencodeChannelsService.getChannels()?.channels || [];
820
+ return { claude, codex, gemini, opencode };
821
+ }
822
+
417
823
  /**
418
824
  * 导出所有配置为JSON
419
825
  * @returns {Object} 配置导出对象
@@ -424,8 +830,9 @@ function exportAllConfigs() {
424
830
  const allConfigTemplates = configTemplatesService.getAllTemplates();
425
831
  const customConfigTemplates = allConfigTemplates.filter(t => !t.isBuiltin);
426
832
 
427
- // 获取所有频道配置
428
- const channels = channelsService.getAllChannels() || [];
833
+ // 获取所有频道配置(向后兼容:channels 仍保留 Claude 渠道)
834
+ const channelsByType = getAllChannelsByType();
835
+ const channels = channelsByType.claude || [];
429
836
 
430
837
  // 获取工作区配置
431
838
  const workspaceService = require('./workspace-service');
@@ -435,38 +842,13 @@ function exportAllConfigs() {
435
842
  const favoritesService = require('./favorites');
436
843
  const favorites = favoritesService.loadFavorites();
437
844
 
438
- // 获取 Agents 配置
439
- const agentsService = new AgentsService();
440
- const { agents: rawAgents } = agentsService.listAgents();
441
- const agents = rawAgents.map(agent => ({
442
- fileName: agent.fileName,
443
- name: agent.name,
444
- description: agent.description,
445
- tools: agent.tools,
446
- model: agent.model,
447
- permissionMode: agent.permissionMode,
448
- skills: agent.skills,
449
- path: agent.path,
450
- systemPrompt: agent.systemPrompt,
451
- fullContent: agent.fullContent
452
- }));
453
-
454
- // 获取 Skills 配置
455
- const skills = exportSkillsSnapshot();
456
-
457
- // 获取 Commands 配置
458
- const commandsService = new CommandsService();
459
- const { commands: rawCommands } = commandsService.listCommands();
460
- const commands = rawCommands.map(command => ({
461
- name: command.name,
462
- namespace: command.namespace,
463
- description: command.description,
464
- allowedTools: command.allowedTools,
465
- argumentHint: command.argumentHint,
466
- path: command.path,
467
- body: command.body,
468
- fullContent: command.fullContent
469
- }));
845
+ // 获取 Agents / Skills / Commands 配置(多平台)
846
+ const agentsByPlatform = exportAgentsSnapshotByPlatform();
847
+ const skillsByPlatform = exportSkillsSnapshotByPlatform();
848
+ const commandsByPlatform = exportCommandsSnapshotByPlatform();
849
+ const agents = agentsByPlatform.claude || [];
850
+ const skills = skillsByPlatform.claude || [];
851
+ const commands = commandsByPlatform.claude || [];
470
852
 
471
853
  // 获取 MCP 配置
472
854
  const mcpService = require('./mcp-service');
@@ -474,6 +856,7 @@ function exportAllConfigs() {
474
856
 
475
857
  // 获取 Plugins 配置
476
858
  const plugins = exportPluginsSnapshot();
859
+ const nativeConfigs = exportNativeConfigs();
477
860
 
478
861
  // 读取 Markdown 配置文件
479
862
  const { PATHS } = require('../../config/paths');
@@ -514,11 +897,15 @@ function exportAllConfigs() {
514
897
  data: {
515
898
  configTemplates: customConfigTemplates,
516
899
  channels: channels || [],
900
+ channelsByType,
517
901
  workspaces: workspaces || { workspaces: [] },
518
902
  favorites: favorites || { favorites: [] },
519
903
  agents: agents || [],
904
+ agentsByPlatform,
520
905
  skills: skills || [],
906
+ skillsByPlatform,
521
907
  commands: commands || [],
908
+ commandsByPlatform,
522
909
  mcpServers: mcpServers || [],
523
910
  plugins: plugins || [],
524
911
  markdownFiles: markdownFiles,
@@ -526,6 +913,7 @@ function exportAllConfigs() {
526
913
  prompts: prompts,
527
914
  security: security,
528
915
  appConfig: appConfig,
916
+ nativeConfigs,
529
917
  claudeHooks: claudeHooks
530
918
  }
531
919
  };
@@ -590,6 +978,7 @@ async function importConfigs(importData, options = {}) {
590
978
  prompts: { success: 0, failed: 0, skipped: 0 },
591
979
  security: { success: 0, failed: 0, skipped: 0 },
592
980
  appConfig: { success: 0, failed: 0, skipped: 0 },
981
+ nativeConfigs: { success: 0, failed: 0, skipped: 0 },
593
982
  claudeHooks: { success: 0, failed: 0, skipped: 0 }
594
983
  };
595
984
 
@@ -602,20 +991,39 @@ async function importConfigs(importData, options = {}) {
602
991
  const {
603
992
  configTemplates = [],
604
993
  channels = [],
994
+ channelsByType = null,
605
995
  workspaces = null,
606
996
  favorites = null,
607
997
  agents = [],
998
+ agentsByPlatform = {},
608
999
  skills = [],
1000
+ skillsByPlatform = {},
609
1001
  commands = [],
1002
+ commandsByPlatform = {},
610
1003
  mcpServers = [],
611
1004
  markdownFiles = {},
612
1005
  uiConfig = null,
613
1006
  prompts = null,
614
1007
  security = null,
615
1008
  appConfig = null,
1009
+ nativeConfigs = {},
616
1010
  claudeHooks = null
617
1011
  } = importData.data;
618
1012
 
1013
+ const importAgentsByPlatform = normalizePlatformItems(agents, agentsByPlatform, AGENT_PLATFORMS);
1014
+ const importSkillsByPlatform = normalizePlatformItems(skills, skillsByPlatform, SKILL_PLATFORMS);
1015
+ const importCommandsByPlatform = normalizePlatformItems(commands, commandsByPlatform, COMMAND_PLATFORMS);
1016
+
1017
+ const hasTypedChannels = channelsByType && typeof channelsByType === 'object';
1018
+ const importChannelsByType = {
1019
+ claude: hasTypedChannels && Array.isArray(channelsByType.claude)
1020
+ ? channelsByType.claude
1021
+ : (Array.isArray(channels) ? channels : []),
1022
+ codex: hasTypedChannels && Array.isArray(channelsByType.codex) ? channelsByType.codex : [],
1023
+ gemini: hasTypedChannels && Array.isArray(channelsByType.gemini) ? channelsByType.gemini : [],
1024
+ opencode: hasTypedChannels && Array.isArray(channelsByType.opencode) ? channelsByType.opencode : []
1025
+ };
1026
+
619
1027
  // 导入配置模板
620
1028
  for (const template of configTemplates) {
621
1029
  try {
@@ -643,29 +1051,72 @@ async function importConfigs(importData, options = {}) {
643
1051
  }
644
1052
  }
645
1053
 
646
- // 导入频道配置
647
- for (const channel of channels) {
648
- try {
649
- const existingChannels = channelsService.getAllChannels() || [];
650
- const existing = existingChannels.find(c => c.id === channel.id);
651
-
652
- if (existing && !overwrite) {
653
- results.channels.skipped++;
654
- continue;
655
- }
1054
+ // 导入频道配置(兼容旧结构 channels 和新结构 channelsByType)
1055
+ const importTypedChannels = (type, service, createChannel, findExisting = null) => {
1056
+ const sourceChannels = importChannelsByType[type];
1057
+ for (const channel of sourceChannels) {
1058
+ try {
1059
+ const existingChannels = service.getChannels
1060
+ ? (service.getChannels()?.channels || [])
1061
+ : (service.getAllChannels?.() || []);
1062
+ const existing = typeof findExisting === 'function'
1063
+ ? findExisting(existingChannels, channel)
1064
+ : existingChannels.find(c => c.id === channel.id);
1065
+
1066
+ if (existing && !overwrite) {
1067
+ results.channels.skipped++;
1068
+ continue;
1069
+ }
656
1070
 
657
- if (existing && overwrite) {
658
- channelsService.updateChannel(channel.id, channel);
659
- } else {
660
- const { name, baseUrl, apiKey, websiteUrl, ...extraConfig } = channel;
661
- channelsService.createChannel(name, baseUrl, apiKey, websiteUrl, extraConfig);
1071
+ if (existing && overwrite) {
1072
+ service.updateChannel(existing.id, { ...channel, id: existing.id });
1073
+ } else {
1074
+ createChannel(channel);
1075
+ }
1076
+ results.channels.success++;
1077
+ } catch (err) {
1078
+ console.error(`[ConfigImport] 导入${type}频道失败: ${channel.name}`, err);
1079
+ results.channels.failed++;
662
1080
  }
663
- results.channels.success++;
664
- } catch (err) {
665
- console.error(`[ConfigImport] 导入频道失败: ${channel.name}`, err);
666
- results.channels.failed++;
667
1081
  }
668
- }
1082
+ };
1083
+
1084
+ importTypedChannels('claude', channelsService, channel => {
1085
+ const { name, baseUrl, apiKey, websiteUrl, ...extraConfig } = channel;
1086
+ channelsService.createChannel(name, baseUrl, apiKey, websiteUrl, extraConfig);
1087
+ }, (existingChannels, channel) => existingChannels.find(c => c.id === channel.id));
1088
+
1089
+ importTypedChannels('codex', codexChannelsService, channel => {
1090
+ const {
1091
+ name,
1092
+ providerKey,
1093
+ baseUrl,
1094
+ apiKey,
1095
+ wireApi,
1096
+ ...extraConfig
1097
+ } = channel;
1098
+ codexChannelsService.createChannel(name, providerKey, baseUrl, apiKey, wireApi, extraConfig);
1099
+ }, (existingChannels, channel) => existingChannels.find(c =>
1100
+ (channel.id && c.id === channel.id) ||
1101
+ (channel.providerKey && c.providerKey === channel.providerKey)
1102
+ ));
1103
+
1104
+ importTypedChannels('gemini', geminiChannelsService, channel => {
1105
+ const { name, baseUrl, apiKey, model, ...extraConfig } = channel;
1106
+ geminiChannelsService.createChannel(name, baseUrl, apiKey, model, extraConfig);
1107
+ }, (existingChannels, channel) => existingChannels.find(c =>
1108
+ (channel.id && c.id === channel.id) ||
1109
+ (channel.name && c.name === channel.name)
1110
+ ));
1111
+
1112
+ importTypedChannels('opencode', opencodeChannelsService, channel => {
1113
+ const { name, baseUrl, apiKey, ...extraConfig } = channel;
1114
+ opencodeChannelsService.createChannel(name, baseUrl, apiKey, extraConfig);
1115
+ }, (existingChannels, channel) => existingChannels.find(c =>
1116
+ (channel.id && c.id === channel.id) ||
1117
+ (channel.providerKey && c.providerKey === channel.providerKey) ||
1118
+ (channel.name && channel.baseUrl && c.name === channel.name && c.baseUrl === channel.baseUrl)
1119
+ ));
669
1120
 
670
1121
  // 导入工作区配置
671
1122
  if (workspaces && overwrite) {
@@ -692,26 +1143,28 @@ async function importConfigs(importData, options = {}) {
692
1143
  }
693
1144
  }
694
1145
 
695
- // 导入 Agents
696
- if (agents && agents.length > 0) {
1146
+ // 导入 Agents(多平台)
1147
+ if (AGENT_PLATFORMS.some(platform => importAgentsByPlatform[platform]?.length > 0)) {
697
1148
  try {
698
- const agentsService = new AgentsService();
699
- const baseDir = agentsService.userAgentsDir;
700
-
701
- for (const agent of agents) {
702
- const relativePath = agent.path || agent.fileName || agent.name;
703
- const filePath = relativePath && relativePath.endsWith('.md')
704
- ? relativePath
705
- : (relativePath ? `${relativePath}.md` : null);
706
- const content = agent.fullContent || agent.content || buildAgentContent(agent);
707
-
708
- const status = filePath ? writeTextFile(baseDir, filePath, content, overwrite) : 'failed';
709
- if (status === 'success') {
710
- results.agents.success++;
711
- } else if (status === 'skipped') {
712
- results.agents.skipped++;
713
- } else {
714
- results.agents.failed++;
1149
+ for (const platform of AGENT_PLATFORMS) {
1150
+ const platformAgents = importAgentsByPlatform[platform] || [];
1151
+ if (platformAgents.length === 0) continue;
1152
+
1153
+ const agentsService = new AgentsService(platform);
1154
+ const baseDir = agentsService.userAgentsDir;
1155
+
1156
+ for (const agent of platformAgents) {
1157
+ const filePath = resolveAgentRelativePath(agent, platform);
1158
+ const content = agent.fullContent || agent.content || buildAgentContent(agent);
1159
+ const status = filePath ? writeTextFile(baseDir, filePath, content, overwrite) : 'failed';
1160
+
1161
+ if (status === 'success') {
1162
+ results.agents.success++;
1163
+ } else if (status === 'skipped') {
1164
+ results.agents.skipped++;
1165
+ } else {
1166
+ results.agents.failed++;
1167
+ }
715
1168
  }
716
1169
  }
717
1170
  } catch (err) {
@@ -719,56 +1172,61 @@ async function importConfigs(importData, options = {}) {
719
1172
  }
720
1173
  }
721
1174
 
722
- // 导入 Skills
723
- if (skills && skills.length > 0) {
1175
+ // 导入 Skills(多平台)
1176
+ if (SKILL_PLATFORMS.some(platform => importSkillsByPlatform[platform]?.length > 0)) {
724
1177
  try {
725
- const skillService = new SkillService();
726
- const baseDir = skillService.installDir;
727
- ensureDir(baseDir);
728
-
729
- for (const skill of skills) {
730
- const directory = skill.directory;
731
- const skillDir = resolveSafePath(baseDir, directory);
732
- if (!skillDir) {
733
- results.skills.failed++;
734
- continue;
735
- }
736
-
737
- if (fs.existsSync(skillDir)) {
738
- if (!overwrite) {
739
- results.skills.skipped++;
1178
+ for (const platform of SKILL_PLATFORMS) {
1179
+ const platformSkills = importSkillsByPlatform[platform] || [];
1180
+ if (platformSkills.length === 0) continue;
1181
+
1182
+ const skillService = new SkillService(platform);
1183
+ const baseDir = skillService.installDir;
1184
+ ensureDir(baseDir);
1185
+
1186
+ for (const skill of platformSkills) {
1187
+ const directory = skill.directory;
1188
+ const skillDir = resolveSafePath(baseDir, directory);
1189
+ if (!skillDir) {
1190
+ results.skills.failed++;
740
1191
  continue;
741
1192
  }
742
- fs.rmSync(skillDir, { recursive: true, force: true });
743
- }
744
-
745
- ensureDir(skillDir);
746
- const files = Array.isArray(skill.files) ? skill.files : [];
747
- let failed = false;
748
1193
 
749
- for (const file of files) {
750
- const filePath = resolveSafePath(skillDir, file.path);
751
- if (!filePath) {
752
- failed = true;
753
- break;
1194
+ if (fs.existsSync(skillDir)) {
1195
+ if (!overwrite) {
1196
+ results.skills.skipped++;
1197
+ continue;
1198
+ }
1199
+ fs.rmSync(skillDir, { recursive: true, force: true });
754
1200
  }
755
- ensureDir(path.dirname(filePath));
756
- try {
757
- if (file.encoding === SKILL_FILE_ENCODING) {
758
- fs.writeFileSync(filePath, Buffer.from(file.content || '', SKILL_FILE_ENCODING));
759
- } else {
760
- fs.writeFileSync(filePath, file.content || '', file.encoding || 'utf8');
1201
+
1202
+ ensureDir(skillDir);
1203
+ const files = Array.isArray(skill.files) ? skill.files : [];
1204
+ let failed = false;
1205
+
1206
+ for (const file of files) {
1207
+ const filePath = resolveSafePath(skillDir, file.path);
1208
+ if (!filePath) {
1209
+ failed = true;
1210
+ break;
1211
+ }
1212
+ ensureDir(path.dirname(filePath));
1213
+ try {
1214
+ if (file.encoding === SKILL_FILE_ENCODING) {
1215
+ fs.writeFileSync(filePath, Buffer.from(file.content || '', SKILL_FILE_ENCODING));
1216
+ } else {
1217
+ fs.writeFileSync(filePath, file.content || '', file.encoding || 'utf8');
1218
+ }
1219
+ } catch (err) {
1220
+ failed = true;
1221
+ break;
761
1222
  }
762
- } catch (err) {
763
- failed = true;
764
- break;
765
1223
  }
766
- }
767
1224
 
768
- if (failed || files.length === 0) {
769
- results.skills.failed++;
770
- } else {
771
- results.skills.success++;
1225
+ if (failed || files.length === 0) {
1226
+ results.skills.failed++;
1227
+ } else {
1228
+ results.skills.success++;
1229
+ }
772
1230
  }
773
1231
  }
774
1232
  } catch (err) {
@@ -913,27 +1371,28 @@ async function importConfigs(importData, options = {}) {
913
1371
  }
914
1372
  }
915
1373
 
916
- // 导入 Commands
917
- if (commands && commands.length > 0) {
1374
+ // 导入 Commands(多平台)
1375
+ if (COMMAND_PLATFORMS.some(platform => importCommandsByPlatform[platform]?.length > 0)) {
918
1376
  try {
919
- const commandsService = new CommandsService();
920
- const baseDir = commandsService.userCommandsDir;
921
-
922
- for (const command of commands) {
923
- const relativePath = command.path || (
924
- command.namespace
925
- ? path.join(command.namespace, `${command.name}.md`)
926
- : `${command.name}.md`
927
- );
928
- const content = command.fullContent || command.content || buildCommandContent(command);
929
-
930
- const status = relativePath ? writeTextFile(baseDir, relativePath, content, overwrite) : 'failed';
931
- if (status === 'success') {
932
- results.commands.success++;
933
- } else if (status === 'skipped') {
934
- results.commands.skipped++;
935
- } else {
936
- results.commands.failed++;
1377
+ for (const platform of COMMAND_PLATFORMS) {
1378
+ const platformCommands = importCommandsByPlatform[platform] || [];
1379
+ if (platformCommands.length === 0) continue;
1380
+
1381
+ const commandsService = new CommandsService(platform);
1382
+ const baseDir = commandsService.userCommandsDir;
1383
+
1384
+ for (const command of platformCommands) {
1385
+ const relativePath = resolveCommandRelativePath(command);
1386
+ const content = command.fullContent || command.content || buildCommandContent(command);
1387
+
1388
+ const status = relativePath ? writeTextFile(baseDir, relativePath, content, overwrite) : 'failed';
1389
+ if (status === 'success') {
1390
+ results.commands.success++;
1391
+ } else if (status === 'skipped') {
1392
+ results.commands.skipped++;
1393
+ } else {
1394
+ results.commands.failed++;
1395
+ }
937
1396
  }
938
1397
  }
939
1398
  } catch (err) {
@@ -1006,6 +1465,12 @@ async function importConfigs(importData, options = {}) {
1006
1465
  try {
1007
1466
  const status = writeJsonFileAbsolute(CC_PROMPTS_PATH, prompts, overwrite);
1008
1467
  if (status === 'success') {
1468
+ const promptsService = require('./prompts-service');
1469
+ if (prompts.activePresetId && prompts.presets?.[prompts.activePresetId]) {
1470
+ await promptsService.activatePreset(prompts.activePresetId);
1471
+ } else if (overwrite) {
1472
+ await promptsService.deactivatePrompt();
1473
+ }
1009
1474
  results.prompts.success++;
1010
1475
  } else if (status === 'skipped') {
1011
1476
  results.prompts.skipped++;
@@ -1052,6 +1517,39 @@ async function importConfigs(importData, options = {}) {
1052
1517
  }
1053
1518
  }
1054
1519
 
1520
+ // 导入各平台原生配置
1521
+ if (nativeConfigs && typeof nativeConfigs === 'object' && Object.keys(nativeConfigs).length > 0) {
1522
+ const nativeConfigSpecs = getNativeConfigSpecs();
1523
+
1524
+ for (const [platform, platformEntries] of Object.entries(nativeConfigs)) {
1525
+ const platformSpecs = nativeConfigSpecs[platform];
1526
+ if (!platformSpecs || !platformEntries || typeof platformEntries !== 'object') {
1527
+ continue;
1528
+ }
1529
+
1530
+ for (const [key, entry] of Object.entries(platformEntries)) {
1531
+ const spec = platformSpecs[key];
1532
+ if (!spec) {
1533
+ continue;
1534
+ }
1535
+
1536
+ try {
1537
+ const status = writeNativeConfigAbsolute(spec, entry, overwrite);
1538
+ if (status === 'success') {
1539
+ results.nativeConfigs.success++;
1540
+ } else if (status === 'skipped') {
1541
+ results.nativeConfigs.skipped++;
1542
+ } else {
1543
+ results.nativeConfigs.failed++;
1544
+ }
1545
+ } catch (err) {
1546
+ console.error(`[ConfigImport] 导入 ${platform}.${key} 原生配置失败:`, err);
1547
+ results.nativeConfigs.failed++;
1548
+ }
1549
+ }
1550
+ }
1551
+ }
1552
+
1055
1553
  // 导入 Claude Hooks 配置
1056
1554
  if (claudeHooks && typeof claudeHooks === 'object') {
1057
1555
  let didWrite = false;
@@ -1105,6 +1603,8 @@ async function importConfigs(importData, options = {}) {
1105
1603
  }
1106
1604
  }
1107
1605
 
1606
+ syncImportedChannelsToNativeConfigs(importChannelsByType, nativeConfigs, overwrite);
1607
+
1108
1608
  return {
1109
1609
  success: true,
1110
1610
  results,
@@ -1141,6 +1641,7 @@ function generateImportSummary(results) {
1141
1641
  { key: 'prompts', label: 'Prompts' },
1142
1642
  { key: 'security', label: '安全配置' },
1143
1643
  { key: 'appConfig', label: '高级配置' },
1644
+ { key: 'nativeConfigs', label: '原生配置' },
1144
1645
  { key: 'claudeHooks', label: 'Claude Hooks' }
1145
1646
  ];
1146
1647