@adversity/coding-tool-x 3.1.0 → 3.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/CHANGELOG.md +39 -18
  2. package/README.md +8 -8
  3. package/dist/web/assets/ConfigTemplates-Bidwfdf2.css +1 -0
  4. package/dist/web/assets/ConfigTemplates-DvcbKKdS.js +1 -0
  5. package/dist/web/assets/Home-BJKPCBuk.css +1 -0
  6. package/dist/web/assets/Home-Cw-F_Wnu.js +1 -0
  7. package/dist/web/assets/PluginManager-ROyoZ-6m.css +1 -0
  8. package/dist/web/assets/PluginManager-jy_4GVxI.js +1 -0
  9. package/dist/web/assets/ProjectList-C1fQb9OW.css +1 -0
  10. package/dist/web/assets/ProjectList-Df1-NcNr.js +1 -0
  11. package/dist/web/assets/SessionList-BGJWyneI.css +1 -0
  12. package/dist/web/assets/SessionList-UWcZtC2r.js +1 -0
  13. package/dist/web/assets/SkillManager-D7pd-d_P.css +1 -0
  14. package/dist/web/assets/SkillManager-IRdseMKB.js +1 -0
  15. package/dist/web/assets/Terminal-BasTyDut.js +1 -0
  16. package/dist/web/assets/Terminal-DGNJeVtc.css +1 -0
  17. package/dist/web/assets/WorkspaceManager-CrwgQgmP.css +1 -0
  18. package/dist/web/assets/WorkspaceManager-D-D2kK1V.js +1 -0
  19. package/dist/web/assets/icons-kcfLIMBB.js +1 -0
  20. package/dist/web/assets/index-CoB3zF0K.css +1 -0
  21. package/dist/web/assets/index-CryrSLv8.js +2 -0
  22. package/dist/web/assets/markdown-BfC0goYb.css +10 -0
  23. package/dist/web/assets/markdown-C9MYpaSi.js +1 -0
  24. package/dist/web/assets/naive-ui-CSrLusZZ.js +1 -0
  25. package/dist/web/assets/{vendors-D2HHw_aW.js → vendors-CO3Upi1d.js} +2 -2
  26. package/dist/web/assets/vue-vendor-DqyWIXEb.js +45 -0
  27. package/dist/web/assets/xterm-6GBZ9nXN.css +32 -0
  28. package/dist/web/assets/xterm-BJzAjXCH.js +13 -0
  29. package/dist/web/index.html +8 -6
  30. package/package.json +4 -2
  31. package/src/commands/channels.js +48 -1
  32. package/src/commands/cli-type.js +4 -2
  33. package/src/commands/daemon.js +81 -12
  34. package/src/commands/doctor.js +10 -9
  35. package/src/commands/list.js +1 -1
  36. package/src/commands/logs.js +6 -4
  37. package/src/commands/port-config.js +24 -4
  38. package/src/commands/proxy-control.js +12 -6
  39. package/src/commands/search.js +1 -1
  40. package/src/commands/security.js +3 -2
  41. package/src/commands/stats.js +226 -52
  42. package/src/commands/switch.js +1 -1
  43. package/src/commands/toggle-proxy.js +31 -6
  44. package/src/commands/update.js +97 -0
  45. package/src/commands/workspace.js +1 -1
  46. package/src/config/default.js +41 -2
  47. package/src/config/loader.js +74 -8
  48. package/src/config/model-metadata.js +415 -0
  49. package/src/config/model-pricing.js +23 -93
  50. package/src/config/paths.js +105 -33
  51. package/src/index.js +64 -3
  52. package/src/plugins/constants.js +3 -2
  53. package/src/plugins/plugin-api.js +1 -1
  54. package/src/reset-config.js +4 -2
  55. package/src/server/api/agents.js +57 -14
  56. package/src/server/api/channels.js +112 -33
  57. package/src/server/api/codex-channels.js +111 -18
  58. package/src/server/api/codex-proxy.js +14 -8
  59. package/src/server/api/commands.js +71 -18
  60. package/src/server/api/config-export.js +0 -6
  61. package/src/server/api/config-registry.js +11 -3
  62. package/src/server/api/config.js +376 -5
  63. package/src/server/api/convert.js +133 -0
  64. package/src/server/api/dashboard.js +22 -6
  65. package/src/server/api/gemini-channels.js +107 -18
  66. package/src/server/api/gemini-proxy.js +14 -8
  67. package/src/server/api/gemini-sessions.js +1 -1
  68. package/src/server/api/health-check.js +4 -3
  69. package/src/server/api/mcp.js +3 -3
  70. package/src/server/api/opencode-channels.js +497 -0
  71. package/src/server/api/opencode-projects.js +99 -0
  72. package/src/server/api/opencode-proxy.js +207 -0
  73. package/src/server/api/opencode-sessions.js +345 -0
  74. package/src/server/api/opencode-statistics.js +57 -0
  75. package/src/server/api/plugins.js +66 -19
  76. package/src/server/api/prompts.js +2 -2
  77. package/src/server/api/proxy.js +7 -4
  78. package/src/server/api/sessions.js +3 -0
  79. package/src/server/api/settings.js +111 -0
  80. package/src/server/api/skills.js +69 -18
  81. package/src/server/api/workspaces.js +78 -6
  82. package/src/server/codex-proxy-server.js +36 -22
  83. package/src/server/dev-server.js +1 -1
  84. package/src/server/gemini-proxy-server.js +21 -7
  85. package/src/server/index.js +174 -58
  86. package/src/server/opencode-proxy-server.js +5486 -0
  87. package/src/server/proxy-server.js +33 -22
  88. package/src/server/services/agents-service.js +61 -24
  89. package/src/server/services/channel-scheduler.js +9 -5
  90. package/src/server/services/channels.js +64 -37
  91. package/src/server/services/codex-channels.js +56 -43
  92. package/src/server/services/codex-sessions.js +105 -6
  93. package/src/server/services/codex-settings-manager.js +271 -49
  94. package/src/server/services/codex-statistics-service.js +2 -2
  95. package/src/server/services/commands-service.js +84 -25
  96. package/src/server/services/config-export-service.js +7 -45
  97. package/src/server/services/config-registry-service.js +63 -17
  98. package/src/server/services/config-sync-manager.js +160 -7
  99. package/src/server/services/config-templates-service.js +204 -51
  100. package/src/server/services/env-checker.js +50 -13
  101. package/src/server/services/env-manager.js +155 -19
  102. package/src/server/services/favorites.js +5 -3
  103. package/src/server/services/gemini-channels.js +33 -44
  104. package/src/server/services/gemini-statistics-service.js +2 -2
  105. package/src/server/services/mcp-service.js +350 -9
  106. package/src/server/services/model-detector.js +707 -221
  107. package/src/server/services/network-access.js +80 -0
  108. package/src/server/services/opencode-channels.js +208 -0
  109. package/src/server/services/opencode-gateway-converter.js +639 -0
  110. package/src/server/services/opencode-sessions.js +931 -0
  111. package/src/server/services/opencode-settings-manager.js +478 -0
  112. package/src/server/services/opencode-statistics-service.js +255 -0
  113. package/src/server/services/plugins-service.js +479 -22
  114. package/src/server/services/prompts-service.js +53 -11
  115. package/src/server/services/proxy-runtime.js +1 -1
  116. package/src/server/services/repo-scanner-base.js +1 -1
  117. package/src/server/services/response-decoder.js +21 -0
  118. package/src/server/services/security-config.js +1 -1
  119. package/src/server/services/session-cache.js +1 -1
  120. package/src/server/services/skill-service.js +300 -46
  121. package/src/server/services/speed-test.js +464 -186
  122. package/src/server/services/statistics-service.js +2 -2
  123. package/src/server/services/terminal-commands.js +10 -3
  124. package/src/server/services/terminal-config.js +1 -1
  125. package/src/server/services/ui-config.js +1 -1
  126. package/src/server/services/workspace-service.js +57 -100
  127. package/src/server/websocket-server.js +156 -8
  128. package/src/ui/menu.js +49 -40
  129. package/src/utils/port-helper.js +22 -8
  130. package/src/utils/session.js +5 -4
  131. package/dist/web/assets/icons-CO_2OFES.js +0 -1
  132. package/dist/web/assets/index-DI8QOi-E.js +0 -14
  133. package/dist/web/assets/index-uLHGdeZh.css +0 -41
  134. package/dist/web/assets/naive-ui-B1re3c-e.js +0 -1
  135. package/dist/web/assets/vue-vendor-6JaYHOiI.js +0 -44
  136. package/src/server/api/oauth.js +0 -294
  137. package/src/server/api/permissions.js +0 -385
  138. package/src/server/config/oauth-providers.js +0 -68
  139. package/src/server/services/oauth-callback-server.js +0 -284
  140. package/src/server/services/oauth-service.js +0 -378
  141. package/src/server/services/oauth-token-storage.js +0 -135
  142. package/src/server/services/permission-templates-service.js +0 -308
@@ -12,15 +12,22 @@ const { spawn } = require('child_process');
12
12
  const http = require('http');
13
13
  const https = require('https');
14
14
  const { McpClient } = require('./mcp-client');
15
+ const { NATIVE_PATHS } = require('../../config/paths');
15
16
 
16
17
  // MCP 配置文件路径
17
- const CC_TOOL_DIR = path.join(os.homedir(), '.claude', 'cc-tool');
18
+ const CC_TOOL_DIR = path.join(os.homedir(), '.cc-tool');
18
19
  const MCP_SERVERS_FILE = path.join(CC_TOOL_DIR, 'mcp-servers.json');
19
20
 
20
21
  // 各平台配置文件路径
21
22
  const CLAUDE_CONFIG_PATH = path.join(os.homedir(), '.claude.json');
22
23
  const CODEX_CONFIG_PATH = path.join(os.homedir(), '.codex', 'config.toml');
23
24
  const GEMINI_CONFIG_PATH = path.join(os.homedir(), '.gemini', 'settings.json');
25
+ const OPENCODE_CONFIG_DIR = NATIVE_PATHS.opencode.config;
26
+ const OPENCODE_CONFIG_PATHS = {
27
+ jsonc: path.join(OPENCODE_CONFIG_DIR, 'opencode.jsonc'),
28
+ json: path.join(OPENCODE_CONFIG_DIR, 'opencode.json'),
29
+ legacy: path.join(OPENCODE_CONFIG_DIR, 'config.json')
30
+ };
24
31
 
25
32
  // MCP 客户端连接池
26
33
  // serverId -> { client, timestamp }
@@ -301,15 +308,140 @@ function writeTomlFile(filePath, data) {
301
308
  fs.renameSync(tempPath, filePath);
302
309
  }
303
310
 
311
+ /**
312
+ * 去除 JSONC 注释
313
+ */
314
+ function stripJsonComments(input) {
315
+ let result = '';
316
+ let inString = false;
317
+ let quote = '';
318
+ let index = 0;
319
+
320
+ while (index < input.length) {
321
+ const ch = input[index];
322
+ const next = input[index + 1];
323
+
324
+ if (inString) {
325
+ result += ch;
326
+ if (ch === '\\') {
327
+ if (next) {
328
+ result += next;
329
+ index += 2;
330
+ continue;
331
+ }
332
+ } else if (ch === quote) {
333
+ inString = false;
334
+ }
335
+ index += 1;
336
+ continue;
337
+ }
338
+
339
+ if (ch === '"' || ch === '\'') {
340
+ inString = true;
341
+ quote = ch;
342
+ result += ch;
343
+ index += 1;
344
+ continue;
345
+ }
346
+
347
+ if (ch === '/' && next === '/') {
348
+ index += 2;
349
+ while (index < input.length && input[index] !== '\n') {
350
+ index += 1;
351
+ }
352
+ continue;
353
+ }
354
+
355
+ if (ch === '/' && next === '*') {
356
+ index += 2;
357
+ while (index < input.length - 1 && !(input[index] === '*' && input[index + 1] === '/')) {
358
+ index += 1;
359
+ }
360
+ index += 2;
361
+ continue;
362
+ }
363
+
364
+ result += ch;
365
+ index += 1;
366
+ }
367
+
368
+ return result;
369
+ }
370
+
371
+ /**
372
+ * 选择 OpenCode 配置文件路径
373
+ */
374
+ function selectOpenCodeConfigPath() {
375
+ if (fs.existsSync(OPENCODE_CONFIG_PATHS.jsonc)) return OPENCODE_CONFIG_PATHS.jsonc;
376
+ if (fs.existsSync(OPENCODE_CONFIG_PATHS.json)) return OPENCODE_CONFIG_PATHS.json;
377
+ if (fs.existsSync(OPENCODE_CONFIG_PATHS.legacy)) return OPENCODE_CONFIG_PATHS.legacy;
378
+ return OPENCODE_CONFIG_PATHS.json;
379
+ }
380
+
381
+ /**
382
+ * 读取 OpenCode 配置
383
+ */
384
+ function readOpenCodeConfig() {
385
+ const filePath = selectOpenCodeConfigPath();
386
+
387
+ if (!fs.existsSync(filePath)) {
388
+ return { path: filePath, config: {} };
389
+ }
390
+
391
+ try {
392
+ const raw = fs.readFileSync(filePath, 'utf-8');
393
+ if (!raw.trim()) {
394
+ return { path: filePath, config: {} };
395
+ }
396
+
397
+ const content = filePath.endsWith('.jsonc') ? stripJsonComments(raw) : raw;
398
+ return {
399
+ path: filePath,
400
+ config: JSON.parse(content)
401
+ };
402
+ } catch (err) {
403
+ console.error(`[MCP] Failed to read OpenCode config:`, err.message);
404
+ return { path: filePath, config: {} };
405
+ }
406
+ }
407
+
408
+ /**
409
+ * 写入 OpenCode 配置(保持 JSON 格式)
410
+ */
411
+ function writeOpenCodeConfig(filePath, data) {
412
+ ensureDir(path.dirname(filePath));
413
+ const tempPath = filePath + '.tmp';
414
+ fs.writeFileSync(tempPath, JSON.stringify(data, null, 2), 'utf-8');
415
+ fs.renameSync(tempPath, filePath);
416
+ }
417
+
304
418
  // ============================================================================
305
419
  // MCP 数据管理
306
420
  // ============================================================================
307
421
 
422
+ function normalizeServerApps(apps = {}) {
423
+ return {
424
+ claude: apps.claude !== undefined ? !!apps.claude : true,
425
+ codex: !!apps.codex,
426
+ gemini: !!apps.gemini,
427
+ opencode: !!apps.opencode
428
+ };
429
+ }
430
+
308
431
  /**
309
432
  * 获取所有 MCP 服务器
310
433
  */
311
434
  function getAllServers() {
312
- return readJsonFile(MCP_SERVERS_FILE, {});
435
+ const servers = readJsonFile(MCP_SERVERS_FILE, {});
436
+
437
+ for (const server of Object.values(servers)) {
438
+ if (!server || typeof server !== 'object') {
439
+ continue;
440
+ }
441
+ server.apps = normalizeServerApps(server.apps);
442
+ }
443
+
444
+ return servers;
313
445
  }
314
446
 
315
447
  /**
@@ -341,7 +473,9 @@ async function saveServer(server) {
341
473
 
342
474
  // 确保 apps 字段存在
343
475
  if (!server.apps) {
344
- server.apps = { claude: true, codex: false, gemini: false };
476
+ server.apps = { claude: true, codex: false, gemini: false, opencode: false };
477
+ } else {
478
+ server.apps = normalizeServerApps(server.apps);
345
479
  }
346
480
 
347
481
  servers[server.id] = server;
@@ -384,7 +518,7 @@ async function toggleServerApp(serverId, app, enabled) {
384
518
  throw new Error(`MCP 服务器 "${serverId}" 不存在`);
385
519
  }
386
520
 
387
- if (!['claude', 'codex', 'gemini'].includes(app)) {
521
+ if (!['claude', 'codex', 'gemini', 'opencode'].includes(app)) {
388
522
  throw new Error(`无效的平台: ${app}`);
389
523
  }
390
524
 
@@ -466,6 +600,12 @@ async function syncServerToAllPlatforms(server) {
466
600
  } else {
467
601
  await removeServerFromPlatform(server.id, 'gemini');
468
602
  }
603
+
604
+ if (apps.opencode) {
605
+ await syncServerToPlatform(server, 'opencode');
606
+ } else {
607
+ await removeServerFromPlatform(server.id, 'opencode');
608
+ }
469
609
  }
470
610
 
471
611
  /**
@@ -475,6 +615,7 @@ async function removeServerFromAllPlatforms(serverId) {
475
615
  await removeServerFromPlatform(serverId, 'claude');
476
616
  await removeServerFromPlatform(serverId, 'codex');
477
617
  await removeServerFromPlatform(serverId, 'gemini');
618
+ await removeServerFromPlatform(serverId, 'opencode');
478
619
  }
479
620
 
480
621
  /**
@@ -492,6 +633,9 @@ async function syncServerToPlatform(server, platform) {
492
633
  case 'gemini':
493
634
  syncToGeminiConfig(server);
494
635
  break;
636
+ case 'opencode':
637
+ syncToOpenCodeConfig(server);
638
+ break;
495
639
  }
496
640
  console.log(`[MCP] Synced "${server.id}" to ${platform}`);
497
641
  } catch (err) {
@@ -515,6 +659,9 @@ async function removeServerFromPlatform(serverId, platform) {
515
659
  case 'gemini':
516
660
  removeFromGeminiConfig(serverId);
517
661
  break;
662
+ case 'opencode':
663
+ removeFromOpenCodeConfig(serverId);
664
+ break;
518
665
  }
519
666
  console.log(`[MCP] Removed "${serverId}" from ${platform}`);
520
667
  } catch (err) {
@@ -647,6 +794,143 @@ function removeFromGeminiConfig(serverId) {
647
794
  }
648
795
  }
649
796
 
797
+ // ============================================================================
798
+ // OpenCode 配置同步
799
+ // ============================================================================
800
+
801
+ /**
802
+ * 转换为 OpenCode 配置格式
803
+ */
804
+ function convertToOpenCodeFormat(spec) {
805
+ const sourceType = spec.type || 'stdio';
806
+
807
+ if (sourceType === 'local' || sourceType === 'remote') {
808
+ const result = { ...spec };
809
+ result.enabled = spec.enabled !== false;
810
+ if (sourceType === 'local' && typeof result.command === 'string') {
811
+ result.command = result.command ? [result.command] : [];
812
+ }
813
+ return result;
814
+ }
815
+
816
+ if (sourceType === 'stdio') {
817
+ const command = [];
818
+ if (spec.command) {
819
+ command.push(spec.command);
820
+ }
821
+ if (Array.isArray(spec.args) && spec.args.length > 0) {
822
+ command.push(...spec.args);
823
+ }
824
+
825
+ const result = {
826
+ type: 'local',
827
+ command,
828
+ enabled: true
829
+ };
830
+
831
+ if (spec.env && Object.keys(spec.env).length > 0) {
832
+ result.environment = spec.env;
833
+ }
834
+ if (spec.cwd) {
835
+ result.cwd = spec.cwd;
836
+ }
837
+
838
+ return result;
839
+ }
840
+
841
+ const result = {
842
+ type: 'remote',
843
+ url: spec.url || '',
844
+ enabled: true
845
+ };
846
+
847
+ if (spec.headers && Object.keys(spec.headers).length > 0) {
848
+ result.headers = spec.headers;
849
+ }
850
+
851
+ return result;
852
+ }
853
+
854
+ /**
855
+ * 从 OpenCode 格式转换到通用格式
856
+ */
857
+ function convertFromOpenCodeFormat(spec) {
858
+ const sourceType = spec.type || (Array.isArray(spec.command) ? 'local' : 'remote');
859
+
860
+ if (sourceType === 'local') {
861
+ const result = { type: 'stdio' };
862
+ if (Array.isArray(spec.command) && spec.command.length > 0) {
863
+ result.command = spec.command[0];
864
+ if (spec.command.length > 1) {
865
+ result.args = spec.command.slice(1);
866
+ }
867
+ } else if (typeof spec.command === 'string') {
868
+ result.command = spec.command;
869
+ } else {
870
+ result.command = '';
871
+ }
872
+
873
+ if (spec.environment && typeof spec.environment === 'object') {
874
+ result.env = spec.environment;
875
+ } else if (spec.env && typeof spec.env === 'object') {
876
+ result.env = spec.env;
877
+ }
878
+ if (spec.cwd) {
879
+ result.cwd = spec.cwd;
880
+ }
881
+ return result;
882
+ }
883
+
884
+ if (sourceType === 'remote') {
885
+ const result = {
886
+ type: 'http',
887
+ url: spec.url || ''
888
+ };
889
+ if (spec.headers && typeof spec.headers === 'object') {
890
+ result.headers = spec.headers;
891
+ }
892
+ return result;
893
+ }
894
+
895
+ // 已经是通用格式时直接兼容处理
896
+ if (sourceType === 'stdio' || sourceType === 'http' || sourceType === 'sse') {
897
+ return convertFromCodexFormat(spec);
898
+ }
899
+
900
+ return {
901
+ type: 'stdio',
902
+ command: ''
903
+ };
904
+ }
905
+
906
+ /**
907
+ * 同步到 OpenCode 配置
908
+ */
909
+ function syncToOpenCodeConfig(server) {
910
+ const { path: configPath, config } = readOpenCodeConfig();
911
+ const nextConfig = config && typeof config === 'object' ? config : {};
912
+
913
+ if (!nextConfig.mcp || typeof nextConfig.mcp !== 'object') {
914
+ nextConfig.mcp = {};
915
+ }
916
+
917
+ nextConfig.mcp[server.id] = convertToOpenCodeFormat(server.server);
918
+ writeOpenCodeConfig(configPath, nextConfig);
919
+ }
920
+
921
+ /**
922
+ * 从 OpenCode 配置移除
923
+ */
924
+ function removeFromOpenCodeConfig(serverId) {
925
+ const { path: configPath, config } = readOpenCodeConfig();
926
+ const nextConfig = config && typeof config === 'object' ? config : {};
927
+
928
+ if (nextConfig.mcp && nextConfig.mcp[serverId]) {
929
+ delete nextConfig.mcp[serverId];
930
+ writeOpenCodeConfig(configPath, nextConfig);
931
+ }
932
+ }
933
+
650
934
  // ============================================================================
651
935
  // 导入功能
652
936
  // ============================================================================
@@ -668,6 +952,9 @@ async function importFromPlatform(platform) {
668
952
  case 'gemini':
669
953
  importedCount = importFromGemini(servers);
670
954
  break;
955
+ case 'opencode':
956
+ importedCount = importFromOpenCode(servers);
957
+ break;
671
958
  default:
672
959
  throw new Error(`无效的平台: ${platform}`);
673
960
  }
@@ -700,7 +987,7 @@ function importFromClaude(servers) {
700
987
  id,
701
988
  name: id,
702
989
  server: spec,
703
- apps: { claude: true, codex: false, gemini: false },
990
+ apps: { claude: true, codex: false, gemini: false, opencode: false },
704
991
  createdAt: Date.now(),
705
992
  updatedAt: Date.now()
706
993
  };
@@ -735,7 +1022,7 @@ function importFromCodex(servers) {
735
1022
  id,
736
1023
  name: id,
737
1024
  server: convertedSpec,
738
- apps: { claude: false, codex: true, gemini: false },
1025
+ apps: { claude: false, codex: true, gemini: false, opencode: false },
739
1026
  createdAt: Date.now(),
740
1027
  updatedAt: Date.now()
741
1028
  };
@@ -767,7 +1054,39 @@ function importFromGemini(servers) {
767
1054
  id,
768
1055
  name: id,
769
1056
  server: spec,
770
- apps: { claude: false, codex: false, gemini: true },
1057
+ apps: { claude: false, codex: false, gemini: true, opencode: false },
1058
+ createdAt: Date.now(),
1059
+ updatedAt: Date.now()
1060
+ };
1061
+ count++;
1062
+ }
1063
+ }
1064
+
1065
+ return count;
1066
+ }
1067
+
1068
+ /**
1069
+ * 从 OpenCode 导入
1070
+ */
1071
+ function importFromOpenCode(servers) {
1072
+ const { config } = readOpenCodeConfig();
1073
+ const mcpServers = config.mcp || {};
1074
+ let count = 0;
1075
+
1076
+ for (const [id, spec] of Object.entries(mcpServers)) {
1077
+ const convertedSpec = convertFromOpenCodeFormat(spec || {});
1078
+
1079
+ if (servers[id]) {
1080
+ if (!servers[id].apps.opencode) {
1081
+ servers[id].apps.opencode = true;
1082
+ count++;
1083
+ }
1084
+ } else {
1085
+ servers[id] = {
1086
+ id,
1087
+ name: id,
1088
+ server: convertedSpec,
1089
+ apps: { claude: false, codex: false, gemini: false, opencode: true },
771
1090
  createdAt: Date.now(),
772
1091
  updatedAt: Date.now()
773
1092
  };
@@ -838,7 +1157,8 @@ function getStats() {
838
1157
  total: serverList.length,
839
1158
  claude: serverList.filter(s => s.apps?.claude).length,
840
1159
  codex: serverList.filter(s => s.apps?.codex).length,
841
- gemini: serverList.filter(s => s.apps?.gemini).length
1160
+ gemini: serverList.filter(s => s.apps?.gemini).length,
1161
+ opencode: serverList.filter(s => s.apps?.opencode).length
842
1162
  };
843
1163
  }
844
1164
 
@@ -1300,7 +1620,7 @@ function updateServerOrder(serverIds) {
1300
1620
 
1301
1621
  /**
1302
1622
  * 导出所有 MCP 配置
1303
- * @param {string} format - 导出格式: 'json' | 'claude' | 'codex'
1623
+ * @param {string} format - 导出格式: 'json' | 'claude' | 'codex' | 'opencode'
1304
1624
  */
1305
1625
  function exportServers(format = 'json') {
1306
1626
  const servers = getAllServers();
@@ -1310,6 +1630,8 @@ function exportServers(format = 'json') {
1310
1630
  return exportForClaude(servers);
1311
1631
  case 'codex':
1312
1632
  return exportForCodex(servers);
1633
+ case 'opencode':
1634
+ return exportForOpenCode(servers);
1313
1635
  case 'json':
1314
1636
  default:
1315
1637
  return exportAsJson(servers);
@@ -1371,6 +1693,25 @@ function exportForCodex(servers) {
1371
1693
  };
1372
1694
  }
1373
1695
 
1696
+ /**
1697
+ * 导出为 OpenCode 格式
1698
+ */
1699
+ function exportForOpenCode(servers) {
1700
+ const mcp = {};
1701
+
1702
+ for (const [id, server] of Object.entries(servers)) {
1703
+ if (server.apps?.opencode) {
1704
+ mcp[id] = convertToOpenCodeFormat(server.server);
1705
+ }
1706
+ }
1707
+
1708
+ return {
1709
+ format: 'opencode',
1710
+ content: JSON.stringify({ mcp }, null, 2),
1711
+ filename: 'opencode-mcp-config.json'
1712
+ };
1713
+ }
1714
+
1374
1715
  module.exports = {
1375
1716
  getAllServers,
1376
1717
  getServer,