@adversity/coding-tool-x 3.0.6 → 3.1.1
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.
- package/CHANGELOG.md +38 -18
- package/README.md +8 -8
- package/dist/web/assets/ConfigTemplates-Bidwfdf2.css +1 -0
- package/dist/web/assets/ConfigTemplates-ZrK_s7ma.js +1 -0
- package/dist/web/assets/Home-B8YfhZ3c.js +1 -0
- package/dist/web/assets/Home-Di2qsylF.css +1 -0
- package/dist/web/assets/PluginManager-BD7QUZbU.js +1 -0
- package/dist/web/assets/PluginManager-ROyoZ-6m.css +1 -0
- package/dist/web/assets/ProjectList-C1fQb9OW.css +1 -0
- package/dist/web/assets/ProjectList-DRb1DuHV.js +1 -0
- package/dist/web/assets/SessionList-BGJWyneI.css +1 -0
- package/dist/web/assets/SessionList-lZ0LKzfT.js +1 -0
- package/dist/web/assets/SkillManager-C1xG5B4Q.js +1 -0
- package/dist/web/assets/SkillManager-D7pd-d_P.css +1 -0
- package/dist/web/assets/Terminal-DGNJeVtc.css +1 -0
- package/dist/web/assets/Terminal-DksBo_lM.js +1 -0
- package/dist/web/assets/WorkspaceManager-Burx7XOo.js +1 -0
- package/dist/web/assets/WorkspaceManager-CrwgQgmP.css +1 -0
- package/dist/web/assets/icons-kcfLIMBB.js +1 -0
- package/dist/web/assets/index-Ufv5rCa5.css +1 -0
- package/dist/web/assets/index-lAkrRC3h.js +2 -0
- package/dist/web/assets/markdown-BfC0goYb.css +10 -0
- package/dist/web/assets/markdown-C9MYpaSi.js +1 -0
- package/dist/web/assets/naive-ui-CSrLusZZ.js +1 -0
- package/dist/web/assets/{vendors-D2HHw_aW.js → vendors-CO3Upi1d.js} +2 -2
- package/dist/web/assets/vue-vendor-DqyWIXEb.js +45 -0
- package/dist/web/assets/xterm-6GBZ9nXN.css +32 -0
- package/dist/web/assets/xterm-BJzAjXCH.js +13 -0
- package/dist/web/index.html +8 -6
- package/package.json +4 -2
- package/src/commands/channels.js +48 -1
- package/src/commands/cli-type.js +4 -2
- package/src/commands/daemon.js +92 -13
- package/src/commands/doctor.js +10 -9
- package/src/commands/list.js +1 -1
- package/src/commands/logs.js +6 -4
- package/src/commands/port-config.js +24 -4
- package/src/commands/proxy-control.js +12 -6
- package/src/commands/search.js +1 -1
- package/src/commands/security.js +3 -2
- package/src/commands/stats.js +226 -52
- package/src/commands/switch.js +1 -1
- package/src/commands/toggle-proxy.js +31 -6
- package/src/commands/ui.js +8 -1
- package/src/commands/update.js +97 -0
- package/src/commands/workspace.js +1 -1
- package/src/config/default.js +39 -2
- package/src/config/loader.js +74 -8
- package/src/config/paths.js +105 -33
- package/src/index.js +67 -4
- package/src/plugins/constants.js +3 -2
- package/src/plugins/plugin-api.js +1 -1
- package/src/reset-config.js +4 -2
- package/src/server/api/agents.js +57 -14
- package/src/server/api/channels.js +112 -33
- package/src/server/api/codex-channels.js +111 -18
- package/src/server/api/codex-proxy.js +14 -8
- package/src/server/api/commands.js +71 -18
- package/src/server/api/config-export.js +0 -6
- package/src/server/api/config-registry.js +11 -3
- package/src/server/api/config.js +376 -5
- package/src/server/api/convert.js +133 -0
- package/src/server/api/dashboard.js +22 -6
- package/src/server/api/gemini-channels.js +107 -18
- package/src/server/api/gemini-proxy.js +14 -8
- package/src/server/api/gemini-sessions.js +1 -1
- package/src/server/api/health-check.js +4 -3
- package/src/server/api/mcp.js +3 -3
- package/src/server/api/opencode-channels.js +419 -0
- package/src/server/api/opencode-projects.js +99 -0
- package/src/server/api/opencode-proxy.js +198 -0
- package/src/server/api/opencode-sessions.js +403 -0
- package/src/server/api/opencode-statistics.js +57 -0
- package/src/server/api/plugins.js +66 -19
- package/src/server/api/prompts.js +2 -2
- package/src/server/api/proxy.js +7 -4
- package/src/server/api/sessions.js +3 -0
- package/src/server/api/skills.js +69 -18
- package/src/server/api/workspaces.js +78 -6
- package/src/server/codex-proxy-server.js +32 -19
- package/src/server/dev-server.js +1 -1
- package/src/server/gemini-proxy-server.js +17 -3
- package/src/server/index.js +164 -48
- package/src/server/opencode-proxy-server.js +4375 -0
- package/src/server/proxy-server.js +30 -19
- package/src/server/services/agents-service.js +61 -24
- package/src/server/services/channel-scheduler.js +9 -5
- package/src/server/services/channels.js +70 -12
- package/src/server/services/codex-channels.js +61 -23
- package/src/server/services/codex-settings-manager.js +271 -49
- package/src/server/services/codex-statistics-service.js +2 -2
- package/src/server/services/commands-service.js +84 -25
- package/src/server/services/config-export-service.js +7 -45
- package/src/server/services/config-registry-service.js +63 -17
- package/src/server/services/config-sync-manager.js +160 -7
- package/src/server/services/config-templates-service.js +204 -51
- package/src/server/services/env-checker.js +26 -12
- package/src/server/services/env-manager.js +126 -18
- package/src/server/services/favorites.js +5 -3
- package/src/server/services/gemini-channels.js +37 -15
- package/src/server/services/gemini-statistics-service.js +2 -2
- package/src/server/services/mcp-service.js +350 -9
- package/src/server/services/model-detector.js +707 -221
- package/src/server/services/network-access.js +80 -0
- package/src/server/services/opencode-channels.js +206 -0
- package/src/server/services/opencode-gateway-converter.js +639 -0
- package/src/server/services/opencode-sessions.js +663 -0
- package/src/server/services/opencode-settings-manager.js +342 -0
- package/src/server/services/opencode-statistics-service.js +255 -0
- package/src/server/services/plugins-service.js +479 -22
- package/src/server/services/prompts-service.js +53 -11
- package/src/server/services/proxy-runtime.js +1 -1
- package/src/server/services/repo-scanner-base.js +1 -1
- package/src/server/services/security-config.js +1 -1
- package/src/server/services/session-cache.js +1 -1
- package/src/server/services/skill-service.js +300 -46
- package/src/server/services/speed-test.js +464 -186
- package/src/server/services/statistics-service.js +2 -2
- package/src/server/services/terminal-commands.js +10 -3
- package/src/server/services/terminal-config.js +1 -1
- package/src/server/services/ui-config.js +1 -1
- package/src/server/services/workspace-service.js +57 -100
- package/src/server/websocket-server.js +132 -3
- package/src/ui/menu.js +49 -40
- package/src/utils/port-helper.js +22 -8
- package/src/utils/session.js +5 -4
- package/dist/web/assets/icons-BxudHPiX.js +0 -1
- package/dist/web/assets/index-D2VfwJBa.js +0 -14
- package/dist/web/assets/index-oXBzu0bd.css +0 -41
- package/dist/web/assets/naive-ui-DT-Uur8K.js +0 -1
- package/dist/web/assets/vue-vendor-6JaYHOiI.js +0 -44
- package/src/server/api/permissions.js +0 -385
- 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(), '.
|
|
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
|
|
110
|
-
return
|
|
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
|
|
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
|
|
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 ~/.
|
|
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: ~/.
|
|
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(), '.
|
|
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
|
|
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 (!
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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
|
};
|