@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
@@ -1,13 +1,12 @@
1
1
  /**
2
2
  * 配置导出/导入服务
3
- * 支持权限模板、配置模板、频道配置的导出与导入
3
+ * 支持配置模板、频道配置的导出与导入
4
4
  */
5
5
 
6
6
  const fs = require('fs');
7
7
  const path = require('path');
8
8
  const os = require('os');
9
9
  const AdmZip = require('adm-zip');
10
- const permissionTemplatesService = require('./permission-templates-service');
11
10
  const configTemplatesService = require('./config-templates-service');
12
11
  const channelsService = require('./channels');
13
12
  const { AgentsService } = require('./agents-service');
@@ -19,7 +18,7 @@ const CONFIG_VERSION = '1.2.0';
19
18
  const SKILL_FILE_ENCODING = 'base64';
20
19
  const SKILL_IGNORE_DIRS = new Set(['.git']);
21
20
  const SKILL_IGNORE_FILES = new Set(['.DS_Store']);
22
- const CC_TOOL_DIR = path.join(os.homedir(), '.claude', 'cc-tool');
21
+ const CC_TOOL_DIR = path.join(os.homedir(), '.cc-tool');
23
22
  const LEGACY_CC_TOOL_DIR = path.join(os.homedir(), '.cc-tool');
24
23
  const CLAUDE_SETTINGS_PATH = path.join(os.homedir(), '.claude', 'settings.json');
25
24
  const LEGACY_PLUGINS_DIR = path.join(LEGACY_CC_TOOL_DIR, 'plugins', 'installed');
@@ -106,8 +105,8 @@ function writeTextFileAbsolute(filePath, content, overwrite, options = {}) {
106
105
 
107
106
  function getConfigFilePath() {
108
107
  try {
109
- const loaderPath = require.resolve('../../config/loader');
110
- return path.resolve(path.dirname(loaderPath), '../../config.json');
108
+ const { getConfigFilePath: resolveConfigPath } = require('../../config/loader');
109
+ return typeof resolveConfigPath === 'function' ? resolveConfigPath() : null;
111
110
  } catch (err) {
112
111
  return null;
113
112
  }
@@ -121,7 +120,7 @@ function buildExportReadme(exportData) {
121
120
  版本: ${exportData.version || CONFIG_VERSION}
122
121
 
123
122
  ## 📦 包含内容
124
- - 权限模板、配置模板、频道配置、工作区、收藏
123
+ - 配置模板、频道配置、工作区、收藏
125
124
  - Agents / Skills / Commands / Rules
126
125
  - 插件 (Plugins)
127
126
  - MCP 服务器配置
@@ -436,17 +435,12 @@ function writeTextFile(baseDir, relativePath, content, overwrite) {
436
435
  */
437
436
  function exportAllConfigs() {
438
437
  try {
439
- // 获取所有权限模板(只导出自定义模板)
440
- const allPermissionTemplates = permissionTemplatesService.getAllTemplates();
441
- const customPermissionTemplates = allPermissionTemplates.filter(t => !t.isBuiltin);
442
-
443
438
  // 获取所有配置模板(只导出自定义模板)
444
439
  const allConfigTemplates = configTemplatesService.getAllTemplates();
445
440
  const customConfigTemplates = allConfigTemplates.filter(t => !t.isBuiltin);
446
441
 
447
442
  // 获取所有频道配置
448
- const channelsData = channelsService.getAllChannels();
449
- const channels = channelsData?.channels || [];
443
+ const channels = channelsService.getAllChannels() || [];
450
444
 
451
445
  // 获取工作区配置
452
446
  const workspaceService = require('./workspace-service');
@@ -549,7 +543,6 @@ function exportAllConfigs() {
549
543
  version: CONFIG_VERSION,
550
544
  exportedAt: new Date().toISOString(),
551
545
  data: {
552
- permissionTemplates: customPermissionTemplates,
553
546
  configTemplates: customConfigTemplates,
554
547
  channels: channels || [],
555
548
  workspaces: workspaces || { workspaces: [] },
@@ -617,7 +610,6 @@ function exportAllConfigsZip() {
617
610
  function importConfigs(importData, options = {}) {
618
611
  const { overwrite = true } = options; // 默认覆盖模式
619
612
  const results = {
620
- permissionTemplates: { success: 0, failed: 0, skipped: 0 },
621
613
  configTemplates: { success: 0, failed: 0, skipped: 0 },
622
614
  channels: { success: 0, failed: 0, skipped: 0 },
623
615
  workspaces: { success: 0, failed: 0, skipped: 0 },
@@ -645,7 +637,6 @@ function importConfigs(importData, options = {}) {
645
637
  }
646
638
 
647
639
  const {
648
- permissionTemplates = [],
649
640
  configTemplates = [],
650
641
  channels = [],
651
642
  workspaces = null,
@@ -665,33 +656,6 @@ function importConfigs(importData, options = {}) {
665
656
  claudeHooks = null
666
657
  } = importData.data;
667
658
 
668
- // 导入权限模板
669
- for (const template of permissionTemplates) {
670
- try {
671
- const existing = permissionTemplatesService.getTemplateById(template.id);
672
-
673
- if (existing && !overwrite) {
674
- results.permissionTemplates.skipped++;
675
- continue;
676
- }
677
-
678
- if (existing && overwrite) {
679
- permissionTemplatesService.updateTemplate(template.id, template);
680
- } else {
681
- const newTemplate = {
682
- ...template,
683
- isBuiltin: false,
684
- importedAt: new Date().toISOString()
685
- };
686
- permissionTemplatesService.createTemplate(newTemplate);
687
- }
688
- results.permissionTemplates.success++;
689
- } catch (err) {
690
- console.error(`[ConfigImport] 导入权限模板失败: ${template.name}`, err);
691
- results.permissionTemplates.failed++;
692
- }
693
- }
694
-
695
659
  // 导入配置模板
696
660
  for (const template of configTemplates) {
697
661
  try {
@@ -722,8 +686,7 @@ function importConfigs(importData, options = {}) {
722
686
  // 导入频道配置
723
687
  for (const channel of channels) {
724
688
  try {
725
- const channelsData = channelsService.getAllChannels();
726
- const existingChannels = channelsData?.channels || [];
689
+ const existingChannels = channelsService.getAllChannels() || [];
727
690
  const existing = existingChannels.find(c => c.id === channel.id);
728
691
 
729
692
  if (existing && !overwrite) {
@@ -1264,7 +1227,6 @@ function generateImportSummary(results) {
1264
1227
  const parts = [];
1265
1228
 
1266
1229
  const types = [
1267
- { key: 'permissionTemplates', label: '权限模板' },
1268
1230
  { key: 'configTemplates', label: '配置模板' },
1269
1231
  { key: 'channels', label: '频道' },
1270
1232
  { key: 'workspaces', label: '工作区' },
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * Config Registry Service
3
3
  *
4
- * Manages a unified config registry at ~/.claude/cc-tool/config-registry.json
4
+ * Manages a unified config registry at ~/.cc-tool/config-registry.json
5
5
  * that tracks skills, commands, agents, rules with enable/disable and per-platform support.
6
6
  *
7
- * Storage directories: ~/.claude/cc-tool/configs/{skills,commands,agents,rules}/
7
+ * Storage directories: ~/.cc-tool/configs/{skills,commands,agents,rules}/
8
8
  */
9
9
 
10
10
  const fs = require('fs');
@@ -12,7 +12,7 @@ const path = require('path');
12
12
  const os = require('os');
13
13
 
14
14
  // Configuration paths
15
- const CC_TOOL_DIR = path.join(os.homedir(), '.claude', 'cc-tool');
15
+ const CC_TOOL_DIR = path.join(os.homedir(), '.cc-tool');
16
16
  const REGISTRY_FILE = path.join(CC_TOOL_DIR, 'config-registry.json');
17
17
  const CONFIGS_DIR = path.join(CC_TOOL_DIR, 'configs');
18
18
 
@@ -27,6 +27,37 @@ const CLAUDE_DIRS = {
27
27
 
28
28
  // Valid config types
29
29
  const CONFIG_TYPES = ['skills', 'commands', 'agents', 'rules', 'plugins'];
30
+ const SUPPORTED_PLATFORMS = ['claude', 'codex', 'opencode'];
31
+
32
+ const PLATFORM_SUPPORT = {
33
+ skills: { claude: true, codex: true, opencode: true },
34
+ commands: { claude: true, codex: true, opencode: true },
35
+ agents: { claude: true, codex: false, opencode: true },
36
+ rules: { claude: true, codex: false, opencode: false },
37
+ plugins: { claude: true, codex: false, opencode: true }
38
+ };
39
+
40
+ function normalizePlatforms(type, platforms = {}) {
41
+ const support = PLATFORM_SUPPORT[type] || {};
42
+ const normalized = {
43
+ claude: !!platforms.claude,
44
+ codex: !!platforms.codex,
45
+ opencode: !!platforms.opencode
46
+ };
47
+
48
+ for (const platform of SUPPORTED_PLATFORMS) {
49
+ if (support[platform] === false) {
50
+ normalized[platform] = false;
51
+ }
52
+ }
53
+
54
+ // Default to Claude enabled when no platform explicitly configured
55
+ if (!platforms || Object.keys(platforms).length === 0) {
56
+ normalized.claude = true;
57
+ }
58
+
59
+ return normalized;
60
+ }
30
61
 
31
62
  // Default registry structure
32
63
  const DEFAULT_REGISTRY = {
@@ -87,6 +118,13 @@ class ConfigRegistryService {
87
118
  if (!data[type]) {
88
119
  data[type] = {};
89
120
  }
121
+
122
+ for (const [name, item] of Object.entries(data[type])) {
123
+ if (!item || typeof item !== 'object') {
124
+ continue;
125
+ }
126
+ data[type][name].platforms = normalizePlatforms(type, item.platforms);
127
+ }
90
128
  }
91
129
 
92
130
  return data;
@@ -147,12 +185,12 @@ class ConfigRegistryService {
147
185
 
148
186
  const existing = registry[type][name];
149
187
  const entry = {
188
+ ...data,
150
189
  enabled: data.enabled !== undefined ? data.enabled : true,
151
- platforms: data.platforms || { claude: true, codex: false },
190
+ platforms: normalizePlatforms(type, data.platforms),
152
191
  createdAt: existing?.createdAt || now,
153
192
  updatedAt: now,
154
- source: data.source || 'local',
155
- ...data
193
+ source: data.source || 'local'
156
194
  };
157
195
 
158
196
  registry[type][name] = entry;
@@ -244,7 +282,7 @@ class ConfigRegistryService {
244
282
  * Toggle platform support for an item
245
283
  * @param {string} type - Config type
246
284
  * @param {string} name - Item name/key
247
- * @param {string} platform - Platform name (claude, codex)
285
+ * @param {string} platform - Platform name (claude, codex, opencode)
248
286
  * @param {boolean} enabled - New platform status
249
287
  * @returns {Object} Updated entry
250
288
  */
@@ -253,10 +291,14 @@ class ConfigRegistryService {
253
291
  throw new Error(`Invalid config type: ${type}`);
254
292
  }
255
293
 
256
- if (!['claude', 'codex'].includes(platform)) {
294
+ if (!SUPPORTED_PLATFORMS.includes(platform)) {
257
295
  throw new Error(`Invalid platform: ${platform}`);
258
296
  }
259
297
 
298
+ if (PLATFORM_SUPPORT[type] && PLATFORM_SUPPORT[type][platform] === false) {
299
+ throw new Error(`Platform "${platform}" is not supported for ${type}`);
300
+ }
301
+
260
302
  const registry = this._readRegistry();
261
303
  const entry = registry[type][name];
262
304
 
@@ -265,7 +307,7 @@ class ConfigRegistryService {
265
307
  }
266
308
 
267
309
  if (!entry.platforms) {
268
- entry.platforms = { claude: true, codex: false };
310
+ entry.platforms = normalizePlatforms(type, {});
269
311
  }
270
312
 
271
313
  entry.platforms[platform] = enabled;
@@ -353,7 +395,7 @@ class ConfigRegistryService {
353
395
  // Add to registry
354
396
  registry.skills[name] = {
355
397
  enabled: true,
356
- platforms: { claude: true, codex: false },
398
+ platforms: normalizePlatforms('skills', { claude: true }),
357
399
  createdAt: new Date().toISOString(),
358
400
  updatedAt: new Date().toISOString(),
359
401
  source: 'imported'
@@ -409,7 +451,7 @@ class ConfigRegistryService {
409
451
  // Add to registry
410
452
  registry.plugins[name] = {
411
453
  enabled: true,
412
- platforms: { claude: true, codex: false },
454
+ platforms: normalizePlatforms('plugins', { claude: true }),
413
455
  createdAt: new Date().toISOString(),
414
456
  updatedAt: new Date().toISOString(),
415
457
  source: 'imported'
@@ -473,7 +515,7 @@ class ConfigRegistryService {
473
515
  // Add to registry
474
516
  registry[type][name] = {
475
517
  enabled: true,
476
- platforms: { claude: true, codex: false },
518
+ platforms: normalizePlatforms(type, { claude: true }),
477
519
  createdAt: new Date().toISOString(),
478
520
  updatedAt: new Date().toISOString(),
479
521
  source: 'imported'
@@ -523,7 +565,8 @@ class ConfigRegistryService {
523
565
  byType: {},
524
566
  byPlatform: {
525
567
  claude: 0,
526
- codex: 0
568
+ codex: 0,
569
+ opencode: 0
527
570
  }
528
571
  };
529
572
 
@@ -534,13 +577,15 @@ class ConfigRegistryService {
534
577
  enabled: items.filter(i => i.enabled).length,
535
578
  disabled: items.filter(i => !i.enabled).length,
536
579
  claude: items.filter(i => i.platforms?.claude).length,
537
- codex: items.filter(i => i.platforms?.codex).length
580
+ codex: items.filter(i => i.platforms?.codex).length,
581
+ opencode: items.filter(i => i.platforms?.opencode).length
538
582
  };
539
583
 
540
584
  stats.byType[type] = typeStats;
541
585
  stats.total += typeStats.total;
542
586
  stats.byPlatform.claude += typeStats.claude;
543
587
  stats.byPlatform.codex += typeStats.codex;
588
+ stats.byPlatform.opencode += typeStats.opencode;
544
589
  }
545
590
 
546
591
  return stats;
@@ -660,7 +705,7 @@ class ConfigRegistryService {
660
705
  if (!registry.skills[name]) {
661
706
  registry.skills[name] = {
662
707
  enabled: true,
663
- platforms: { claude: true, codex: false },
708
+ platforms: normalizePlatforms('skills', { claude: true }),
664
709
  createdAt: new Date().toISOString(),
665
710
  updatedAt: new Date().toISOString(),
666
711
  source: 'synced'
@@ -697,7 +742,7 @@ class ConfigRegistryService {
697
742
  if (!registry.plugins[name]) {
698
743
  registry.plugins[name] = {
699
744
  enabled: true,
700
- platforms: { claude: true, codex: false },
745
+ platforms: normalizePlatforms('plugins', { claude: true }),
701
746
  createdAt: new Date().toISOString(),
702
747
  updatedAt: new Date().toISOString(),
703
748
  source: 'synced'
@@ -739,7 +784,7 @@ class ConfigRegistryService {
739
784
  if (!registry[type][name]) {
740
785
  registry[type][name] = {
741
786
  enabled: true,
742
- platforms: { claude: true, codex: false },
787
+ platforms: normalizePlatforms(type, { claude: true }),
743
788
  createdAt: new Date().toISOString(),
744
789
  updatedAt: new Date().toISOString(),
745
790
  source: 'synced'
@@ -757,6 +802,7 @@ class ConfigRegistryService {
757
802
  module.exports = {
758
803
  ConfigRegistryService,
759
804
  CONFIG_TYPES,
805
+ SUPPORTED_PLATFORMS,
760
806
  CONFIGS_DIR,
761
807
  REGISTRY_FILE
762
808
  };
@@ -4,24 +4,28 @@
4
4
  * Manages file synchronization between cc-tool central storage and CLI directories:
5
5
  * - Claude Code: ~/.claude/{skills,commands,agents,rules}/
6
6
  * - Codex CLI: ~/.codex/skills/, ~/.codex/prompts/
7
+ * - OpenCode CLI: ~/.config/opencode/{skills,commands,agents,plugins}/
7
8
  *
8
9
  * Config types:
9
10
  * - skills: directory-based (each skill is a dir with SKILL.md)
10
11
  * - commands: file-based (.md), may be nested in subdirectories
11
12
  * - agents: file-based (.md), flat directory
12
13
  * - rules: file-based (.md), may be nested in subdirectories
14
+ * - plugins: directory-based
13
15
  */
14
16
 
15
17
  const fs = require('fs');
16
18
  const path = require('path');
17
19
  const os = require('os');
18
20
  const { convertSkillToCodex, convertCommandToCodex } = require('./format-converter');
21
+ const { PATHS, NATIVE_PATHS, ensureStorageDirMigrated } = require('../../config/paths');
19
22
 
20
23
  // Paths
21
24
  const HOME = os.homedir();
22
- const CC_TOOL_CONFIGS = path.join(HOME, '.claude', 'cc-tool', 'configs');
25
+ const CC_TOOL_CONFIGS = path.join(PATHS.base, 'configs');
23
26
  const CLAUDE_CODE_DIR = path.join(HOME, '.claude');
24
27
  const CODEX_DIR = path.join(HOME, '.codex');
28
+ const OPENCODE_DIR = NATIVE_PATHS.opencode.config;
25
29
 
26
30
  // Config type definitions
27
31
  const CONFIG_TYPES = {
@@ -31,7 +35,10 @@ const CONFIG_TYPES = {
31
35
  claudeTarget: 'skills',
32
36
  codexTarget: 'skills',
33
37
  codexSupported: true,
34
- convertForCodex: true
38
+ convertForCodex: true,
39
+ opencodeTarget: 'skills',
40
+ opencodeLegacyTarget: 'skill',
41
+ opencodeSupported: true
35
42
  },
36
43
  commands: {
37
44
  isDirectory: false,
@@ -39,27 +46,44 @@ const CONFIG_TYPES = {
39
46
  claudeTarget: 'commands',
40
47
  codexTarget: 'prompts',
41
48
  codexSupported: true,
42
- convertForCodex: true
49
+ convertForCodex: true,
50
+ opencodeTarget: 'commands',
51
+ opencodeLegacyTarget: 'command',
52
+ opencodeSupported: true
43
53
  },
44
54
  agents: {
45
55
  isDirectory: false,
46
56
  extension: '.md',
47
57
  claudeTarget: 'agents',
48
- codexSupported: false
58
+ codexSupported: false,
59
+ opencodeTarget: 'agents',
60
+ opencodeLegacyTarget: 'agent',
61
+ opencodeSupported: true
49
62
  },
50
63
  rules: {
51
64
  isDirectory: false,
52
65
  extension: '.md',
53
66
  claudeTarget: 'rules',
54
- codexSupported: false
67
+ codexSupported: false,
68
+ opencodeSupported: false
69
+ },
70
+ plugins: {
71
+ isDirectory: true,
72
+ claudeTarget: 'plugins',
73
+ codexSupported: false,
74
+ opencodeTarget: 'plugins',
75
+ opencodeLegacyTarget: 'plugin',
76
+ opencodeSupported: true
55
77
  }
56
78
  };
57
79
 
58
80
  class ConfigSyncManager {
59
81
  constructor() {
82
+ ensureStorageDirMigrated();
60
83
  this.ccToolConfigs = CC_TOOL_CONFIGS;
61
84
  this.claudeDir = CLAUDE_CODE_DIR;
62
85
  this.codexDir = CODEX_DIR;
86
+ this.opencodeDir = OPENCODE_DIR;
63
87
  this.configTypes = CONFIG_TYPES;
64
88
  }
65
89
 
@@ -262,10 +286,96 @@ class ConfigSyncManager {
262
286
  }
263
287
  }
264
288
 
289
+ /**
290
+ * Sync a config item to OpenCode CLI
291
+ * Supports skills, commands, agents, plugins
292
+ * @param {string} type - Config type
293
+ * @param {string} name - Item name
294
+ * @returns {Object} Result with success status
295
+ */
296
+ syncToOpenCode(type, name) {
297
+ const config = this.configTypes[type];
298
+ if (!config) {
299
+ return { success: false, error: `Unknown config type: ${type}` };
300
+ }
301
+
302
+ if (!config.opencodeSupported) {
303
+ console.log(`[ConfigSyncManager] ${type} not supported by OpenCode, skipping`);
304
+ return { success: true, skipped: true, reason: 'Not supported by OpenCode' };
305
+ }
306
+
307
+ const sourcePath = path.join(this.ccToolConfigs, type, name);
308
+ if (!fs.existsSync(sourcePath)) {
309
+ console.log(`[ConfigSyncManager] Source not found: ${sourcePath}`);
310
+ return { success: false, error: 'Source not found' };
311
+ }
312
+
313
+ try {
314
+ const targetBaseDir = this._getOpenCodeTypeBaseDir(config);
315
+ const targetPath = path.join(targetBaseDir, name);
316
+
317
+ if (config.isDirectory) {
318
+ this._ensureDir(path.dirname(targetPath));
319
+ this._copyDirRecursive(sourcePath, targetPath);
320
+ console.log(`[ConfigSyncManager] Synced ${type}/${name} to OpenCode (directory)`);
321
+ } else {
322
+ this._ensureDir(path.dirname(targetPath));
323
+ this._copyFile(sourcePath, targetPath);
324
+ console.log(`[ConfigSyncManager] Synced ${type}/${name} to OpenCode (file)`);
325
+ }
326
+
327
+ return { success: true, target: targetPath };
328
+ } catch (err) {
329
+ console.error(`[ConfigSyncManager] Sync to OpenCode failed:`, err.message);
330
+ return { success: false, error: err.message };
331
+ }
332
+ }
333
+
334
+ /**
335
+ * Remove a config item from OpenCode CLI
336
+ * @param {string} type - Config type
337
+ * @param {string} name - Item name
338
+ * @returns {Object} Result with success status
339
+ */
340
+ removeFromOpenCode(type, name) {
341
+ const config = this.configTypes[type];
342
+ if (!config) {
343
+ return { success: false, error: `Unknown config type: ${type}` };
344
+ }
345
+
346
+ if (!config.opencodeSupported) {
347
+ return { success: true, skipped: true, reason: 'Not supported by OpenCode' };
348
+ }
349
+
350
+ const targetBaseDir = this._getOpenCodeTypeBaseDir(config);
351
+ const targetPath = path.join(targetBaseDir, name);
352
+
353
+ if (!fs.existsSync(targetPath)) {
354
+ console.log(`[ConfigSyncManager] Target not found (already removed): ${targetPath}`);
355
+ return { success: true, message: 'Already removed' };
356
+ }
357
+
358
+ try {
359
+ if (config.isDirectory) {
360
+ this._removeRecursive(targetPath);
361
+ console.log(`[ConfigSyncManager] Removed ${type}/${name} from OpenCode (directory)`);
362
+ } else {
363
+ fs.unlinkSync(targetPath);
364
+ console.log(`[ConfigSyncManager] Removed ${type}/${name} from OpenCode (file)`);
365
+ this._cleanupEmptyParents(path.dirname(targetPath), targetBaseDir);
366
+ }
367
+
368
+ return { success: true };
369
+ } catch (err) {
370
+ console.error(`[ConfigSyncManager] Remove from OpenCode failed:`, err.message);
371
+ return { success: false, error: err.message };
372
+ }
373
+ }
374
+
265
375
  /**
266
376
  * Batch sync based on registry data
267
377
  * @param {string} type - Config type
268
- * @param {Object} registryItems - Registry items { name: { enabled, platforms: { claude, codex } } }
378
+ * @param {Object} registryItems - Registry items { name: { enabled, platforms: { claude, codex, opencode } } }
269
379
  * @returns {Object} Results summary
270
380
  */
271
381
  syncAll(type, registryItems) {
@@ -319,6 +429,20 @@ class ConfigSyncManager {
319
429
  results.removed.push({ type, name, platform: 'codex' });
320
430
  }
321
431
  }
432
+
433
+ if (platforms.opencode) {
434
+ const result = this.syncToOpenCode(type, name);
435
+ if (result.success && !result.skipped) {
436
+ results.synced.push({ type, name, platform: 'opencode' });
437
+ } else if (!result.success) {
438
+ results.errors.push({ type, name, platform: 'opencode', error: result.error });
439
+ }
440
+ } else {
441
+ const result = this.removeFromOpenCode(type, name);
442
+ if (result.success && !result.message && !result.skipped) {
443
+ results.removed.push({ type, name, platform: 'opencode' });
444
+ }
445
+ }
322
446
  } else {
323
447
  // Item disabled, remove from all platforms
324
448
  const claudeResult = this.removeFromClaude(type, name);
@@ -330,6 +454,11 @@ class ConfigSyncManager {
330
454
  if (codexResult.success && !codexResult.message && !codexResult.skipped) {
331
455
  results.removed.push({ type, name, platform: 'codex' });
332
456
  }
457
+
458
+ const opencodeResult = this.removeFromOpenCode(type, name);
459
+ if (opencodeResult.success && !opencodeResult.message && !opencodeResult.skipped) {
460
+ results.removed.push({ type, name, platform: 'opencode' });
461
+ }
333
462
  }
334
463
  }
335
464
 
@@ -409,6 +538,29 @@ class ConfigSyncManager {
409
538
  fs.copyFileSync(src, dest);
410
539
  }
411
540
 
541
+ /**
542
+ * Resolve OpenCode base target directory for a config type.
543
+ * OpenCode supports both plural (new) and singular (legacy) folder names.
544
+ */
545
+ _getOpenCodeTypeBaseDir(config) {
546
+ const modernDir = path.join(this.opencodeDir, config.opencodeTarget);
547
+ // 技能目录强制使用 modern/plural 形式,避免 legacy 目录带来的跨平台历史污染
548
+ if (config === this.configTypes.skills) {
549
+ return modernDir;
550
+ }
551
+
552
+ if (!config.opencodeLegacyTarget) {
553
+ return modernDir;
554
+ }
555
+
556
+ const legacyDir = path.join(this.opencodeDir, config.opencodeLegacyTarget);
557
+ if (fs.existsSync(legacyDir) && !fs.existsSync(modernDir)) {
558
+ return legacyDir;
559
+ }
560
+
561
+ return modernDir;
562
+ }
563
+
412
564
  /**
413
565
  * Recursively remove a file or directory
414
566
  */
@@ -452,5 +604,6 @@ module.exports = {
452
604
  CONFIG_TYPES,
453
605
  CC_TOOL_CONFIGS,
454
606
  CLAUDE_CODE_DIR,
455
- CODEX_DIR
607
+ CODEX_DIR,
608
+ OPENCODE_DIR
456
609
  };