coding-tool-x 3.3.6 → 3.3.8

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 (56) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/web/assets/{Analytics-TtaduRqL.js → Analytics-DLpoDZ2M.js} +1 -1
  3. package/dist/web/assets/{ConfigTemplates-BP2lLBMN.js → ConfigTemplates-D_hRb55W.js} +1 -1
  4. package/dist/web/assets/Home-BMoFdAwy.css +1 -0
  5. package/dist/web/assets/Home-DNwp-0J-.js +1 -0
  6. package/dist/web/assets/{PluginManager-HmISlyMK.js → PluginManager-JXsyym1s.js} +1 -1
  7. package/dist/web/assets/{ProjectList-DoN8Hjbu.js → ProjectList-DZWSeb-q.js} +1 -1
  8. package/dist/web/assets/{SessionList-Da8BYzNi.js → SessionList-Cs624DR3.js} +1 -1
  9. package/dist/web/assets/{SkillManager-DqLAXh9o.js → SkillManager-bEliz7qz.js} +1 -1
  10. package/dist/web/assets/{WorkspaceManager-B_TxOgPW.js → WorkspaceManager-J3RecFGn.js} +1 -1
  11. package/dist/web/assets/{icons-B29onFfZ.js → icons-Cuc23WS7.js} +1 -1
  12. package/dist/web/assets/index-BXeSvAwU.js +2 -0
  13. package/dist/web/assets/index-DWAC3Tdv.css +1 -0
  14. package/dist/web/index.html +3 -3
  15. package/package.json +3 -2
  16. package/src/commands/daemon.js +44 -6
  17. package/src/commands/toggle-proxy.js +100 -5
  18. package/src/config/default.js +1 -1
  19. package/src/config/model-metadata.js +2 -2
  20. package/src/config/model-metadata.json +7 -2
  21. package/src/config/paths.js +102 -19
  22. package/src/server/api/channels.js +9 -0
  23. package/src/server/api/codex-channels.js +9 -0
  24. package/src/server/api/codex-proxy.js +22 -11
  25. package/src/server/api/gemini-proxy.js +22 -11
  26. package/src/server/api/mcp.js +26 -4
  27. package/src/server/api/oauth-credentials.js +163 -0
  28. package/src/server/api/opencode-proxy.js +22 -10
  29. package/src/server/api/plugins.js +3 -1
  30. package/src/server/api/proxy.js +39 -44
  31. package/src/server/api/skills.js +91 -13
  32. package/src/server/codex-proxy-server.js +1 -11
  33. package/src/server/index.js +26 -2
  34. package/src/server/services/channels.js +18 -22
  35. package/src/server/services/codex-channels.js +124 -175
  36. package/src/server/services/codex-config.js +2 -5
  37. package/src/server/services/codex-settings-manager.js +12 -348
  38. package/src/server/services/config-export-service.js +572 -117
  39. package/src/server/services/gemini-channels.js +11 -9
  40. package/src/server/services/mcp-client.js +70 -13
  41. package/src/server/services/mcp-service.js +74 -29
  42. package/src/server/services/model-detector.js +1 -0
  43. package/src/server/services/native-keychain.js +243 -0
  44. package/src/server/services/native-oauth-adapters.js +890 -0
  45. package/src/server/services/oauth-credentials-service.js +786 -0
  46. package/src/server/services/oauth-utils.js +49 -0
  47. package/src/server/services/opencode-channels.js +13 -9
  48. package/src/server/services/opencode-settings-manager.js +169 -16
  49. package/src/server/services/plugins-service.js +22 -1
  50. package/src/server/services/settings-manager.js +13 -0
  51. package/src/server/services/skill-service.js +712 -332
  52. package/src/utils/port-helper.js +87 -2
  53. package/dist/web/assets/Home-BsSioaaB.css +0 -1
  54. package/dist/web/assets/Home-CbbyopS-.js +0 -1
  55. package/dist/web/assets/index-By3mDEvx.js +0 -2
  56. package/dist/web/assets/index-CsWInMQV.css +0 -1
@@ -16,7 +16,7 @@ const { CommandsService } = require('./commands-service');
16
16
  const { SkillService } = require('./skill-service');
17
17
  const { PATHS, NATIVE_PATHS } = require('../../config/paths');
18
18
 
19
- const CONFIG_VERSION = '1.2.0';
19
+ const CONFIG_VERSION = '1.4.0';
20
20
  const SKILL_FILE_ENCODING = 'base64';
21
21
  const SKILL_IGNORE_DIRS = new Set(['.git']);
22
22
  const SKILL_IGNORE_FILES = new Set(['.DS_Store']);
@@ -44,6 +44,41 @@ const CC_PROMPTS_PATH = path.join(CC_TOOL_DIR, 'prompts.json');
44
44
  const CC_SECURITY_PATH = path.join(CC_TOOL_DIR, 'security.json');
45
45
  const LEGACY_UI_CONFIG_PATH = path.join(LEGACY_CC_TOOL_DIR, 'ui-config.json');
46
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
+ }
47
82
 
48
83
  function ensureDir(dirPath) {
49
84
  if (!fs.existsSync(dirPath)) {
@@ -70,6 +105,39 @@ function readTextFileSafe(filePath) {
70
105
  }
71
106
  }
72
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
+
73
141
  function writeJsonFileAbsolute(filePath, data, overwrite, options = {}) {
74
142
  if (data === undefined) {
75
143
  return 'failed';
@@ -104,6 +172,19 @@ function writeTextFileAbsolute(filePath, content, overwrite, options = {}) {
104
172
  return 'success';
105
173
  }
106
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
+
107
188
  function getConfigFilePath() {
108
189
  try {
109
190
  const { getConfigFilePath: resolveConfigPath } = require('../../config/loader');
@@ -125,6 +206,8 @@ function buildExportReadme(exportData) {
125
206
  - Agents / Skills / Commands
126
207
  - 插件 (Plugins)
127
208
  - MCP 服务器配置
209
+ - OAuth 凭证管理池
210
+ - 各平台原生配置(Claude / Codex / Gemini / OpenCode)
128
211
  - UI 配置(主题、面板显示、排序等)
129
212
  - Prompts 预设
130
213
  - 安全配置
@@ -184,6 +267,288 @@ function buildCommandContent(command) {
184
267
  return `${lines.join('\n')}${body}`;
185
268
  }
186
269
 
270
+ function buildAgentExportItem(agent, platform) {
271
+ return {
272
+ platform,
273
+ fileName: agent.fileName,
274
+ name: agent.name,
275
+ description: agent.description,
276
+ tools: agent.tools,
277
+ model: agent.model,
278
+ permissionMode: agent.permissionMode,
279
+ skills: agent.skills,
280
+ path: agent.path,
281
+ systemPrompt: agent.systemPrompt,
282
+ fullContent: agent.fullContent
283
+ };
284
+ }
285
+
286
+ function buildCommandExportItem(command, platform) {
287
+ return {
288
+ platform,
289
+ name: command.name,
290
+ namespace: command.namespace,
291
+ description: command.description,
292
+ allowedTools: command.allowedTools,
293
+ argumentHint: command.argumentHint,
294
+ path: command.path,
295
+ body: command.body,
296
+ fullContent: command.fullContent
297
+ };
298
+ }
299
+
300
+ function exportAgentsSnapshotByPlatform() {
301
+ return AGENT_PLATFORMS.reduce((result, platform) => {
302
+ try {
303
+ const agentsService = new AgentsService(platform);
304
+ const { agents: rawAgents = [] } = agentsService.listAgents();
305
+ result[platform] = rawAgents.map(agent => buildAgentExportItem(agent, platform));
306
+ } catch (err) {
307
+ console.warn(`[ConfigExport] Failed to export agents for ${platform}:`, err.message);
308
+ result[platform] = [];
309
+ }
310
+ return result;
311
+ }, {});
312
+ }
313
+
314
+ function exportCommandsSnapshotByPlatform() {
315
+ return COMMAND_PLATFORMS.reduce((result, platform) => {
316
+ try {
317
+ const commandsService = new CommandsService(platform);
318
+ const { commands: rawCommands = [] } = commandsService.listCommands();
319
+ result[platform] = rawCommands.map(command => buildCommandExportItem(command, platform));
320
+ } catch (err) {
321
+ console.warn(`[ConfigExport] Failed to export commands for ${platform}:`, err.message);
322
+ result[platform] = [];
323
+ }
324
+ return result;
325
+ }, {});
326
+ }
327
+
328
+ function normalizePlatformItems(legacyItems = [], byPlatform = {}, supportedPlatforms = [], defaultPlatform = 'claude') {
329
+ const result = Object.fromEntries(supportedPlatforms.map(platform => [platform, []]));
330
+ const structuredPlatforms = new Set();
331
+
332
+ for (const platform of supportedPlatforms) {
333
+ if (Array.isArray(byPlatform?.[platform])) {
334
+ result[platform] = byPlatform[platform].map(item => ({
335
+ ...item,
336
+ platform: supportedPlatforms.includes(item?.platform) ? item.platform : platform
337
+ }));
338
+ structuredPlatforms.add(platform);
339
+ }
340
+ }
341
+
342
+ if (!Array.isArray(legacyItems)) {
343
+ return result;
344
+ }
345
+
346
+ for (const item of legacyItems) {
347
+ const platform = supportedPlatforms.includes(item?.platform) ? item.platform : defaultPlatform;
348
+ if (structuredPlatforms.has(platform)) {
349
+ continue;
350
+ }
351
+ result[platform].push({ ...item, platform });
352
+ }
353
+
354
+ return result;
355
+ }
356
+
357
+ function resolveAgentRelativePath(agent, platform) {
358
+ const pathCandidates = [agent?.path, agent?.fileName]
359
+ .filter(value => typeof value === 'string' && value.trim())
360
+ .map(value => value.trim());
361
+
362
+ for (const candidate of pathCandidates) {
363
+ if (path.extname(candidate)) {
364
+ return candidate;
365
+ }
366
+ }
367
+
368
+ const baseName = agent?.fileName || agent?.name;
369
+ if (!baseName) {
370
+ return null;
371
+ }
372
+
373
+ return platform === 'codex' ? `${baseName}.toml` : `${baseName}.md`;
374
+ }
375
+
376
+ function resolveCommandRelativePath(command) {
377
+ if (typeof command?.path === 'string' && command.path.trim()) {
378
+ return command.path.trim();
379
+ }
380
+ if (command?.namespace) {
381
+ return path.join(command.namespace, `${command.name}.md`);
382
+ }
383
+ return command?.name ? `${command.name}.md` : null;
384
+ }
385
+
386
+ function pickPrimaryChannel(channels = []) {
387
+ if (!Array.isArray(channels) || channels.length === 0) {
388
+ return null;
389
+ }
390
+ return channels.find(channel => channel.enabled !== false) || channels[0] || null;
391
+ }
392
+
393
+ function findMatchingPersistedChannel(existingChannels = [], importedChannel = {}, extraMatchers = []) {
394
+ if (!Array.isArray(existingChannels) || existingChannels.length === 0 || !importedChannel) {
395
+ return null;
396
+ }
397
+
398
+ if (importedChannel.id) {
399
+ const exactMatch = existingChannels.find(channel => channel.id === importedChannel.id);
400
+ if (exactMatch) {
401
+ return exactMatch;
402
+ }
403
+ }
404
+
405
+ for (const matcher of extraMatchers) {
406
+ const matchedChannel = existingChannels.find(channel => matcher(channel, importedChannel));
407
+ if (matchedChannel) {
408
+ return matchedChannel;
409
+ }
410
+ }
411
+
412
+ return null;
413
+ }
414
+
415
+ function sanitizeOpenCodeProviderId(value) {
416
+ return String(value || '')
417
+ .trim()
418
+ .toLowerCase()
419
+ .replace(/[^a-z0-9-]/g, '-')
420
+ .replace(/-+/g, '-')
421
+ .replace(/^-|-$/g, '') || 'channel';
422
+ }
423
+
424
+ function buildOpenCodeNativeConfig(channels = []) {
425
+ const config = { provider: {} };
426
+ const usedProviderIds = new Set();
427
+ let defaultModelRef = '';
428
+
429
+ channels.forEach((channel) => {
430
+ const baseProviderId = sanitizeOpenCodeProviderId(channel.providerKey || channel.name);
431
+ let providerId = baseProviderId;
432
+ let suffix = 2;
433
+ while (usedProviderIds.has(providerId)) {
434
+ providerId = `${baseProviderId}-${suffix}`;
435
+ suffix += 1;
436
+ }
437
+ usedProviderIds.add(providerId);
438
+
439
+ const provider = {
440
+ npm: '@ai-sdk/openai-compatible',
441
+ name: channel.name || providerId,
442
+ options: {
443
+ baseURL: channel.baseUrl || '',
444
+ apiKey: channel.apiKey || ''
445
+ }
446
+ };
447
+
448
+ const models = {};
449
+ const allowedModels = Array.isArray(channel.allowedModels) ? channel.allowedModels : [];
450
+ const allModels = allowedModels.length > 0
451
+ ? allowedModels
452
+ : [channel.model].filter(Boolean);
453
+
454
+ allModels.forEach((modelId) => {
455
+ const normalizedModel = String(modelId || '').trim();
456
+ if (!normalizedModel) return;
457
+ models[normalizedModel] = { name: normalizedModel };
458
+ if (!defaultModelRef && (channel.enabled !== false || !pickPrimaryChannel(channels))) {
459
+ defaultModelRef = `${providerId}/${normalizedModel}`;
460
+ }
461
+ });
462
+
463
+ if (Object.keys(models).length > 0) {
464
+ provider.models = models;
465
+ }
466
+
467
+ config.provider[providerId] = provider;
468
+ });
469
+
470
+ const preferredChannel = pickPrimaryChannel(channels);
471
+ if (!defaultModelRef && preferredChannel?.model) {
472
+ const providerId = sanitizeOpenCodeProviderId(preferredChannel.providerKey || preferredChannel.name);
473
+ defaultModelRef = `${providerId}/${preferredChannel.model}`;
474
+ }
475
+
476
+ if (defaultModelRef) {
477
+ config.model = defaultModelRef;
478
+ }
479
+
480
+ return config;
481
+ }
482
+
483
+ function syncImportedChannelsToNativeConfigs(importChannelsByType, nativeConfigs, overwrite) {
484
+ const hasNativeConfigFor = (platform) => !!nativeConfigs?.[platform] && Object.keys(nativeConfigs[platform]).length > 0;
485
+
486
+ try {
487
+ if (!hasNativeConfigFor('claude')) {
488
+ const primaryClaudeChannel = pickPrimaryChannel(importChannelsByType.claude);
489
+ const persistedClaudeChannel = findMatchingPersistedChannel(
490
+ channelsService.getAllChannels?.() || [],
491
+ primaryClaudeChannel,
492
+ [
493
+ (channel, imported) => channel.name === imported.name && channel.baseUrl === imported.baseUrl
494
+ ]
495
+ );
496
+ if (persistedClaudeChannel?.id) {
497
+ ensureDir(path.dirname(NATIVE_PATHS.claude.settings));
498
+ channelsService.applyChannelToSettings(persistedClaudeChannel.id);
499
+ }
500
+ }
501
+ } catch (err) {
502
+ console.warn('[ConfigImport] Claude native sync fallback failed:', err.message);
503
+ }
504
+
505
+ try {
506
+ if (!hasNativeConfigFor('codex')) {
507
+ codexChannelsService.writeCodexConfigForMultiChannel(importChannelsByType.codex || []);
508
+ }
509
+ } catch (err) {
510
+ console.warn('[ConfigImport] Codex native sync fallback failed:', err.message);
511
+ }
512
+
513
+ try {
514
+ if (!hasNativeConfigFor('gemini')) {
515
+ const primaryGeminiChannel = pickPrimaryChannel(importChannelsByType.gemini);
516
+ const persistedGeminiChannel = findMatchingPersistedChannel(
517
+ geminiChannelsService.getChannels?.().channels || [],
518
+ primaryGeminiChannel,
519
+ [
520
+ (channel, imported) => channel.name === imported.name && channel.baseUrl === imported.baseUrl
521
+ ]
522
+ );
523
+ if (persistedGeminiChannel?.id) {
524
+ geminiChannelsService.applyChannelToSettings(persistedGeminiChannel.id);
525
+ }
526
+ }
527
+ } catch (err) {
528
+ console.warn('[ConfigImport] Gemini native sync fallback failed:', err.message);
529
+ }
530
+
531
+ try {
532
+ if (!hasNativeConfigFor('opencode')) {
533
+ const openCodeSpecs = getNativeConfigSpecs().opencode || {};
534
+ const preferredSpec = [
535
+ openCodeSpecs.opencodeJsonc,
536
+ openCodeSpecs.opencodeJson,
537
+ openCodeSpecs.configJson
538
+ ].find(spec => spec?.path && fs.existsSync(spec.path))
539
+ || openCodeSpecs.opencodeJson
540
+ || openCodeSpecs.configJson;
541
+
542
+ if (preferredSpec?.path) {
543
+ const payload = buildOpenCodeNativeConfig(importChannelsByType.opencode || []);
544
+ writeTextFileAbsolute(preferredSpec.path, JSON.stringify(payload, null, 2), overwrite);
545
+ }
546
+ }
547
+ } catch (err) {
548
+ console.warn('[ConfigImport] OpenCode native sync fallback failed:', err.message);
549
+ }
550
+ }
551
+
187
552
  function collectSkillFiles(baseDir) {
188
553
  const files = [];
189
554
  const stack = [baseDir];
@@ -277,8 +642,8 @@ function collectPluginFiles(pluginDir, basePath = '') {
277
642
  return files;
278
643
  }
279
644
 
280
- function exportSkillsSnapshot() {
281
- const skillService = new SkillService();
645
+ function exportSkillsSnapshot(platform = 'claude') {
646
+ const skillService = new SkillService(platform);
282
647
  const installedSkills = skillService.getInstalledSkills();
283
648
  const baseDir = skillService.installDir;
284
649
 
@@ -289,6 +654,7 @@ function exportSkillsSnapshot() {
289
654
  return null;
290
655
  }
291
656
  return {
657
+ platform,
292
658
  directory,
293
659
  name: skill.name || directory,
294
660
  description: skill.description || '',
@@ -297,6 +663,36 @@ function exportSkillsSnapshot() {
297
663
  }).filter(Boolean);
298
664
  }
299
665
 
666
+ function exportSkillsSnapshotByPlatform() {
667
+ return SKILL_PLATFORMS.reduce((result, platform) => {
668
+ try {
669
+ result[platform] = exportSkillsSnapshot(platform);
670
+ } catch (err) {
671
+ console.warn(`[ConfigExport] Failed to export skills for ${platform}:`, err.message);
672
+ result[platform] = [];
673
+ }
674
+ return result;
675
+ }, {});
676
+ }
677
+
678
+ function exportNativeConfigs() {
679
+ const specs = getNativeConfigSpecs();
680
+ return Object.entries(specs).reduce((result, [platform, platformSpecs]) => {
681
+ const exportedEntries = Object.entries(platformSpecs).reduce((entries, [key, spec]) => {
682
+ const snapshot = readNativeConfigSnapshot(spec);
683
+ if (snapshot) {
684
+ entries[key] = snapshot;
685
+ }
686
+ return entries;
687
+ }, {});
688
+
689
+ if (Object.keys(exportedEntries).length > 0) {
690
+ result[platform] = exportedEntries;
691
+ }
692
+ return result;
693
+ }, {});
694
+ }
695
+
300
696
  function exportLegacyPlugins() {
301
697
  const plugins = [];
302
698
 
@@ -447,38 +843,13 @@ function exportAllConfigs() {
447
843
  const favoritesService = require('./favorites');
448
844
  const favorites = favoritesService.loadFavorites();
449
845
 
450
- // 获取 Agents 配置
451
- const agentsService = new AgentsService();
452
- const { agents: rawAgents } = agentsService.listAgents();
453
- const agents = rawAgents.map(agent => ({
454
- fileName: agent.fileName,
455
- name: agent.name,
456
- description: agent.description,
457
- tools: agent.tools,
458
- model: agent.model,
459
- permissionMode: agent.permissionMode,
460
- skills: agent.skills,
461
- path: agent.path,
462
- systemPrompt: agent.systemPrompt,
463
- fullContent: agent.fullContent
464
- }));
465
-
466
- // 获取 Skills 配置
467
- const skills = exportSkillsSnapshot();
468
-
469
- // 获取 Commands 配置
470
- const commandsService = new CommandsService();
471
- const { commands: rawCommands } = commandsService.listCommands();
472
- const commands = rawCommands.map(command => ({
473
- name: command.name,
474
- namespace: command.namespace,
475
- description: command.description,
476
- allowedTools: command.allowedTools,
477
- argumentHint: command.argumentHint,
478
- path: command.path,
479
- body: command.body,
480
- fullContent: command.fullContent
481
- }));
846
+ // 获取 Agents / Skills / Commands 配置(多平台)
847
+ const agentsByPlatform = exportAgentsSnapshotByPlatform();
848
+ const skillsByPlatform = exportSkillsSnapshotByPlatform();
849
+ const commandsByPlatform = exportCommandsSnapshotByPlatform();
850
+ const agents = agentsByPlatform.claude || [];
851
+ const skills = skillsByPlatform.claude || [];
852
+ const commands = commandsByPlatform.claude || [];
482
853
 
483
854
  // 获取 MCP 配置
484
855
  const mcpService = require('./mcp-service');
@@ -486,9 +857,10 @@ function exportAllConfigs() {
486
857
 
487
858
  // 获取 Plugins 配置
488
859
  const plugins = exportPluginsSnapshot();
860
+ const nativeConfigs = exportNativeConfigs();
861
+ const oauthCredentials = readJsonFileSafe(PATHS.oauthCredentials);
489
862
 
490
863
  // 读取 Markdown 配置文件
491
- const { PATHS } = require('../../config/paths');
492
864
  const markdownFiles = {};
493
865
  const mdFileNames = ['CLAUDE.md', 'AGENTS.md', 'GEMINI.md'];
494
866
 
@@ -530,8 +902,11 @@ function exportAllConfigs() {
530
902
  workspaces: workspaces || { workspaces: [] },
531
903
  favorites: favorites || { favorites: [] },
532
904
  agents: agents || [],
905
+ agentsByPlatform,
533
906
  skills: skills || [],
907
+ skillsByPlatform,
534
908
  commands: commands || [],
909
+ commandsByPlatform,
535
910
  mcpServers: mcpServers || [],
536
911
  plugins: plugins || [],
537
912
  markdownFiles: markdownFiles,
@@ -539,6 +914,8 @@ function exportAllConfigs() {
539
914
  prompts: prompts,
540
915
  security: security,
541
916
  appConfig: appConfig,
917
+ nativeConfigs,
918
+ oauthCredentials,
542
919
  claudeHooks: claudeHooks
543
920
  }
544
921
  };
@@ -603,6 +980,8 @@ async function importConfigs(importData, options = {}) {
603
980
  prompts: { success: 0, failed: 0, skipped: 0 },
604
981
  security: { success: 0, failed: 0, skipped: 0 },
605
982
  appConfig: { success: 0, failed: 0, skipped: 0 },
983
+ nativeConfigs: { success: 0, failed: 0, skipped: 0 },
984
+ oauthCredentials: { success: 0, failed: 0, skipped: 0 },
606
985
  claudeHooks: { success: 0, failed: 0, skipped: 0 }
607
986
  };
608
987
 
@@ -619,17 +998,26 @@ async function importConfigs(importData, options = {}) {
619
998
  workspaces = null,
620
999
  favorites = null,
621
1000
  agents = [],
1001
+ agentsByPlatform = {},
622
1002
  skills = [],
1003
+ skillsByPlatform = {},
623
1004
  commands = [],
1005
+ commandsByPlatform = {},
624
1006
  mcpServers = [],
625
1007
  markdownFiles = {},
626
1008
  uiConfig = null,
627
1009
  prompts = null,
628
1010
  security = null,
629
1011
  appConfig = null,
1012
+ nativeConfigs = {},
1013
+ oauthCredentials = null,
630
1014
  claudeHooks = null
631
1015
  } = importData.data;
632
1016
 
1017
+ const importAgentsByPlatform = normalizePlatformItems(agents, agentsByPlatform, AGENT_PLATFORMS);
1018
+ const importSkillsByPlatform = normalizePlatformItems(skills, skillsByPlatform, SKILL_PLATFORMS);
1019
+ const importCommandsByPlatform = normalizePlatformItems(commands, commandsByPlatform, COMMAND_PLATFORMS);
1020
+
633
1021
  const hasTypedChannels = channelsByType && typeof channelsByType === 'object';
634
1022
  const importChannelsByType = {
635
1023
  claude: hasTypedChannels && Array.isArray(channelsByType.claude)
@@ -759,26 +1147,28 @@ async function importConfigs(importData, options = {}) {
759
1147
  }
760
1148
  }
761
1149
 
762
- // 导入 Agents
763
- if (agents && agents.length > 0) {
1150
+ // 导入 Agents(多平台)
1151
+ if (AGENT_PLATFORMS.some(platform => importAgentsByPlatform[platform]?.length > 0)) {
764
1152
  try {
765
- const agentsService = new AgentsService();
766
- const baseDir = agentsService.userAgentsDir;
767
-
768
- for (const agent of agents) {
769
- const relativePath = agent.path || agent.fileName || agent.name;
770
- const filePath = relativePath && relativePath.endsWith('.md')
771
- ? relativePath
772
- : (relativePath ? `${relativePath}.md` : null);
773
- const content = agent.fullContent || agent.content || buildAgentContent(agent);
774
-
775
- const status = filePath ? writeTextFile(baseDir, filePath, content, overwrite) : 'failed';
776
- if (status === 'success') {
777
- results.agents.success++;
778
- } else if (status === 'skipped') {
779
- results.agents.skipped++;
780
- } else {
781
- results.agents.failed++;
1153
+ for (const platform of AGENT_PLATFORMS) {
1154
+ const platformAgents = importAgentsByPlatform[platform] || [];
1155
+ if (platformAgents.length === 0) continue;
1156
+
1157
+ const agentsService = new AgentsService(platform);
1158
+ const baseDir = agentsService.userAgentsDir;
1159
+
1160
+ for (const agent of platformAgents) {
1161
+ const filePath = resolveAgentRelativePath(agent, platform);
1162
+ const content = agent.fullContent || agent.content || buildAgentContent(agent);
1163
+ const status = filePath ? writeTextFile(baseDir, filePath, content, overwrite) : 'failed';
1164
+
1165
+ if (status === 'success') {
1166
+ results.agents.success++;
1167
+ } else if (status === 'skipped') {
1168
+ results.agents.skipped++;
1169
+ } else {
1170
+ results.agents.failed++;
1171
+ }
782
1172
  }
783
1173
  }
784
1174
  } catch (err) {
@@ -786,56 +1176,61 @@ async function importConfigs(importData, options = {}) {
786
1176
  }
787
1177
  }
788
1178
 
789
- // 导入 Skills
790
- if (skills && skills.length > 0) {
1179
+ // 导入 Skills(多平台)
1180
+ if (SKILL_PLATFORMS.some(platform => importSkillsByPlatform[platform]?.length > 0)) {
791
1181
  try {
792
- const skillService = new SkillService();
793
- const baseDir = skillService.installDir;
794
- ensureDir(baseDir);
795
-
796
- for (const skill of skills) {
797
- const directory = skill.directory;
798
- const skillDir = resolveSafePath(baseDir, directory);
799
- if (!skillDir) {
800
- results.skills.failed++;
801
- continue;
802
- }
803
-
804
- if (fs.existsSync(skillDir)) {
805
- if (!overwrite) {
806
- results.skills.skipped++;
1182
+ for (const platform of SKILL_PLATFORMS) {
1183
+ const platformSkills = importSkillsByPlatform[platform] || [];
1184
+ if (platformSkills.length === 0) continue;
1185
+
1186
+ const skillService = new SkillService(platform);
1187
+ const baseDir = skillService.installDir;
1188
+ ensureDir(baseDir);
1189
+
1190
+ for (const skill of platformSkills) {
1191
+ const directory = skill.directory;
1192
+ const skillDir = resolveSafePath(baseDir, directory);
1193
+ if (!skillDir) {
1194
+ results.skills.failed++;
807
1195
  continue;
808
1196
  }
809
- fs.rmSync(skillDir, { recursive: true, force: true });
810
- }
811
1197
 
812
- ensureDir(skillDir);
813
- const files = Array.isArray(skill.files) ? skill.files : [];
814
- let failed = false;
815
-
816
- for (const file of files) {
817
- const filePath = resolveSafePath(skillDir, file.path);
818
- if (!filePath) {
819
- failed = true;
820
- break;
1198
+ if (fs.existsSync(skillDir)) {
1199
+ if (!overwrite) {
1200
+ results.skills.skipped++;
1201
+ continue;
1202
+ }
1203
+ fs.rmSync(skillDir, { recursive: true, force: true });
821
1204
  }
822
- ensureDir(path.dirname(filePath));
823
- try {
824
- if (file.encoding === SKILL_FILE_ENCODING) {
825
- fs.writeFileSync(filePath, Buffer.from(file.content || '', SKILL_FILE_ENCODING));
826
- } else {
827
- fs.writeFileSync(filePath, file.content || '', file.encoding || 'utf8');
1205
+
1206
+ ensureDir(skillDir);
1207
+ const files = Array.isArray(skill.files) ? skill.files : [];
1208
+ let failed = false;
1209
+
1210
+ for (const file of files) {
1211
+ const filePath = resolveSafePath(skillDir, file.path);
1212
+ if (!filePath) {
1213
+ failed = true;
1214
+ break;
1215
+ }
1216
+ ensureDir(path.dirname(filePath));
1217
+ try {
1218
+ if (file.encoding === SKILL_FILE_ENCODING) {
1219
+ fs.writeFileSync(filePath, Buffer.from(file.content || '', SKILL_FILE_ENCODING));
1220
+ } else {
1221
+ fs.writeFileSync(filePath, file.content || '', file.encoding || 'utf8');
1222
+ }
1223
+ } catch (err) {
1224
+ failed = true;
1225
+ break;
828
1226
  }
829
- } catch (err) {
830
- failed = true;
831
- break;
832
1227
  }
833
- }
834
1228
 
835
- if (failed || files.length === 0) {
836
- results.skills.failed++;
837
- } else {
838
- results.skills.success++;
1229
+ if (failed || files.length === 0) {
1230
+ results.skills.failed++;
1231
+ } else {
1232
+ results.skills.success++;
1233
+ }
839
1234
  }
840
1235
  }
841
1236
  } catch (err) {
@@ -980,27 +1375,28 @@ async function importConfigs(importData, options = {}) {
980
1375
  }
981
1376
  }
982
1377
 
983
- // 导入 Commands
984
- if (commands && commands.length > 0) {
1378
+ // 导入 Commands(多平台)
1379
+ if (COMMAND_PLATFORMS.some(platform => importCommandsByPlatform[platform]?.length > 0)) {
985
1380
  try {
986
- const commandsService = new CommandsService();
987
- const baseDir = commandsService.userCommandsDir;
988
-
989
- for (const command of commands) {
990
- const relativePath = command.path || (
991
- command.namespace
992
- ? path.join(command.namespace, `${command.name}.md`)
993
- : `${command.name}.md`
994
- );
995
- const content = command.fullContent || command.content || buildCommandContent(command);
996
-
997
- const status = relativePath ? writeTextFile(baseDir, relativePath, content, overwrite) : 'failed';
998
- if (status === 'success') {
999
- results.commands.success++;
1000
- } else if (status === 'skipped') {
1001
- results.commands.skipped++;
1002
- } else {
1003
- results.commands.failed++;
1381
+ for (const platform of COMMAND_PLATFORMS) {
1382
+ const platformCommands = importCommandsByPlatform[platform] || [];
1383
+ if (platformCommands.length === 0) continue;
1384
+
1385
+ const commandsService = new CommandsService(platform);
1386
+ const baseDir = commandsService.userCommandsDir;
1387
+
1388
+ for (const command of platformCommands) {
1389
+ const relativePath = resolveCommandRelativePath(command);
1390
+ const content = command.fullContent || command.content || buildCommandContent(command);
1391
+
1392
+ const status = relativePath ? writeTextFile(baseDir, relativePath, content, overwrite) : 'failed';
1393
+ if (status === 'success') {
1394
+ results.commands.success++;
1395
+ } else if (status === 'skipped') {
1396
+ results.commands.skipped++;
1397
+ } else {
1398
+ results.commands.failed++;
1399
+ }
1004
1400
  }
1005
1401
  }
1006
1402
  } catch (err) {
@@ -1073,6 +1469,12 @@ async function importConfigs(importData, options = {}) {
1073
1469
  try {
1074
1470
  const status = writeJsonFileAbsolute(CC_PROMPTS_PATH, prompts, overwrite);
1075
1471
  if (status === 'success') {
1472
+ const promptsService = require('./prompts-service');
1473
+ if (prompts.activePresetId && prompts.presets?.[prompts.activePresetId]) {
1474
+ await promptsService.activatePreset(prompts.activePresetId);
1475
+ } else if (overwrite) {
1476
+ await promptsService.deactivatePrompt();
1477
+ }
1076
1478
  results.prompts.success++;
1077
1479
  } else if (status === 'skipped') {
1078
1480
  results.prompts.skipped++;
@@ -1119,6 +1521,55 @@ async function importConfigs(importData, options = {}) {
1119
1521
  }
1120
1522
  }
1121
1523
 
1524
+ // 导入各平台原生配置
1525
+ if (nativeConfigs && typeof nativeConfigs === 'object' && Object.keys(nativeConfigs).length > 0) {
1526
+ const nativeConfigSpecs = getNativeConfigSpecs();
1527
+
1528
+ for (const [platform, platformEntries] of Object.entries(nativeConfigs)) {
1529
+ const platformSpecs = nativeConfigSpecs[platform];
1530
+ if (!platformSpecs || !platformEntries || typeof platformEntries !== 'object') {
1531
+ continue;
1532
+ }
1533
+
1534
+ for (const [key, entry] of Object.entries(platformEntries)) {
1535
+ const spec = platformSpecs[key];
1536
+ if (!spec) {
1537
+ continue;
1538
+ }
1539
+
1540
+ try {
1541
+ const status = writeNativeConfigAbsolute(spec, entry, overwrite);
1542
+ if (status === 'success') {
1543
+ results.nativeConfigs.success++;
1544
+ } else if (status === 'skipped') {
1545
+ results.nativeConfigs.skipped++;
1546
+ } else {
1547
+ results.nativeConfigs.failed++;
1548
+ }
1549
+ } catch (err) {
1550
+ console.error(`[ConfigImport] 导入 ${platform}.${key} 原生配置失败:`, err);
1551
+ results.nativeConfigs.failed++;
1552
+ }
1553
+ }
1554
+ }
1555
+ }
1556
+
1557
+ if (oauthCredentials && typeof oauthCredentials === 'object') {
1558
+ try {
1559
+ const status = writeJsonFileAbsolute(PATHS.oauthCredentials, oauthCredentials, overwrite, { mode: 0o600 });
1560
+ if (status === 'success') {
1561
+ results.oauthCredentials.success++;
1562
+ } else if (status === 'skipped') {
1563
+ results.oauthCredentials.skipped++;
1564
+ } else {
1565
+ results.oauthCredentials.failed++;
1566
+ }
1567
+ } catch (err) {
1568
+ console.error('[ConfigImport] 导入 OAuth 凭证失败:', err);
1569
+ results.oauthCredentials.failed++;
1570
+ }
1571
+ }
1572
+
1122
1573
  // 导入 Claude Hooks 配置
1123
1574
  if (claudeHooks && typeof claudeHooks === 'object') {
1124
1575
  let didWrite = false;
@@ -1172,6 +1623,8 @@ async function importConfigs(importData, options = {}) {
1172
1623
  }
1173
1624
  }
1174
1625
 
1626
+ syncImportedChannelsToNativeConfigs(importChannelsByType, nativeConfigs, overwrite);
1627
+
1175
1628
  return {
1176
1629
  success: true,
1177
1630
  results,
@@ -1208,6 +1661,8 @@ function generateImportSummary(results) {
1208
1661
  { key: 'prompts', label: 'Prompts' },
1209
1662
  { key: 'security', label: '安全配置' },
1210
1663
  { key: 'appConfig', label: '高级配置' },
1664
+ { key: 'nativeConfigs', label: '原生配置' },
1665
+ { key: 'oauthCredentials', label: 'OAuth凭证' },
1211
1666
  { key: 'claudeHooks', label: 'Claude Hooks' }
1212
1667
  ];
1213
1668