@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,90 +1,149 @@
1
1
  // CTX 工具路径配置
2
- // 所有路径统一使用 ~/.claude/ctx 目录,避免与原始 cc-tool 冲突
2
+ // 所有路径统一使用 ~/.cc-tool 目录
3
+ const fs = require('fs');
3
4
  const path = require('path');
4
5
  const os = require('os');
5
6
 
6
7
  // 基础目录
7
- const CTX_BASE_DIR = path.join(os.homedir(), '.claude', 'ctx');
8
+ const CC_TOOL_BASE_DIR = path.join(os.homedir(), '.cc-tool');
9
+ // 兼容旧变量名,避免外部调用方断裂
10
+ const CTX_BASE_DIR = CC_TOOL_BASE_DIR;
11
+
12
+ // 旧目录(升级时自动合并到 ~/.cc-tool)
13
+ const LEGACY_BASE_DIRS = [
14
+ path.join(os.homedir(), '.claude', 'ctx'),
15
+ path.join(os.homedir(), '.claude', 'cc-tool')
16
+ ];
17
+
18
+ let migrationChecked = false;
19
+
20
+ function mergeDirectory(sourceDir, targetDir) {
21
+ if (!fs.existsSync(sourceDir)) return;
22
+
23
+ const sourceStat = fs.statSync(sourceDir);
24
+ if (sourceStat.isDirectory()) {
25
+ if (!fs.existsSync(targetDir)) {
26
+ fs.mkdirSync(targetDir, { recursive: true });
27
+ }
28
+
29
+ const entries = fs.readdirSync(sourceDir);
30
+ entries.forEach((entry) => {
31
+ mergeDirectory(path.join(sourceDir, entry), path.join(targetDir, entry));
32
+ });
33
+ return;
34
+ }
35
+
36
+ if (!fs.existsSync(targetDir)) {
37
+ fs.copyFileSync(sourceDir, targetDir);
38
+ }
39
+ }
40
+
41
+ function ensureStorageDirMigrated() {
42
+ if (migrationChecked) {
43
+ return CC_TOOL_BASE_DIR;
44
+ }
45
+ migrationChecked = true;
46
+
47
+ if (!fs.existsSync(CC_TOOL_BASE_DIR)) {
48
+ fs.mkdirSync(CC_TOOL_BASE_DIR, { recursive: true });
49
+ }
50
+
51
+ LEGACY_BASE_DIRS.forEach((legacyDir) => {
52
+ if (!fs.existsSync(legacyDir)) return;
53
+ try {
54
+ mergeDirectory(legacyDir, CC_TOOL_BASE_DIR);
55
+ } catch (error) {
56
+ console.warn(`[paths] 迁移目录失败: ${legacyDir} -> ${CC_TOOL_BASE_DIR}`, error.message);
57
+ }
58
+ });
59
+
60
+ return CC_TOOL_BASE_DIR;
61
+ }
8
62
 
9
63
  // 路径配置
10
64
  const PATHS = {
11
65
  // 基础目录
12
- base: CTX_BASE_DIR,
66
+ base: CC_TOOL_BASE_DIR,
13
67
 
14
68
  // 项目目录(存储项目配置和会话)
15
- projects: path.join(CTX_BASE_DIR, 'projects'),
69
+ projects: path.join(CC_TOOL_BASE_DIR, 'projects'),
16
70
 
17
71
  // 配置文件目录
18
- config: path.join(CTX_BASE_DIR, 'config'),
19
- configFile: path.join(CTX_BASE_DIR, 'config.json'),
72
+ config: path.join(CC_TOOL_BASE_DIR, 'config'),
73
+ configFile: path.join(CC_TOOL_BASE_DIR, 'config.json'),
20
74
 
21
75
  // 日志目录
22
- logs: path.join(CTX_BASE_DIR, 'logs'),
76
+ logs: path.join(CC_TOOL_BASE_DIR, 'logs'),
23
77
 
24
78
  // 别名存储
25
- aliases: path.join(CTX_BASE_DIR, 'aliases.json'),
79
+ aliases: path.join(CC_TOOL_BASE_DIR, 'aliases.json'),
26
80
 
27
81
  // 收藏夹存储
28
- favorites: path.join(CTX_BASE_DIR, 'favorites.json'),
82
+ favorites: path.join(CC_TOOL_BASE_DIR, 'favorites.json'),
29
83
 
30
84
  // 渠道配置
31
85
  channels: {
32
- claude: path.join(CTX_BASE_DIR, 'channels.json'),
33
- codex: path.join(CTX_BASE_DIR, 'codex-channels.json'),
34
- gemini: path.join(CTX_BASE_DIR, 'gemini-channels.json')
86
+ claude: path.join(CC_TOOL_BASE_DIR, 'channels.json'),
87
+ codex: path.join(CC_TOOL_BASE_DIR, 'codex-channels.json'),
88
+ gemini: path.join(CC_TOOL_BASE_DIR, 'gemini-channels.json'),
89
+ opencode: path.join(CC_TOOL_BASE_DIR, 'opencode-channels.json')
35
90
  },
36
91
 
37
92
  // 激活渠道标记
38
93
  activeChannel: {
39
- claude: path.join(CTX_BASE_DIR, 'active-channel.json'),
40
- codex: path.join(CTX_BASE_DIR, 'codex-active-channel.json'),
41
- gemini: path.join(CTX_BASE_DIR, 'gemini-active-channel.json')
94
+ claude: path.join(CC_TOOL_BASE_DIR, 'active-channel.json'),
95
+ codex: path.join(CC_TOOL_BASE_DIR, 'codex-active-channel.json'),
96
+ gemini: path.join(CC_TOOL_BASE_DIR, 'gemini-active-channel.json'),
97
+ opencode: path.join(CC_TOOL_BASE_DIR, 'opencode-active-channel.json')
42
98
  },
43
99
 
44
100
  // 统计数据
45
101
  statistics: {
46
- claude: path.join(CTX_BASE_DIR, 'statistics.json'),
47
- codex: path.join(CTX_BASE_DIR, 'codex-statistics.json'),
48
- gemini: path.join(CTX_BASE_DIR, 'gemini-statistics.json'),
102
+ claude: path.join(CC_TOOL_BASE_DIR, 'statistics.json'),
103
+ codex: path.join(CC_TOOL_BASE_DIR, 'codex-statistics.json'),
104
+ gemini: path.join(CC_TOOL_BASE_DIR, 'gemini-statistics.json'),
105
+ opencode: path.join(CC_TOOL_BASE_DIR, 'opencode-statistics.json'),
49
106
  dailyStats: {
50
- claude: path.join(CTX_BASE_DIR, 'daily-stats'),
51
- codex: path.join(CTX_BASE_DIR, 'codex-daily-stats'),
52
- gemini: path.join(CTX_BASE_DIR, 'gemini-daily-stats')
107
+ claude: path.join(CC_TOOL_BASE_DIR, 'daily-stats'),
108
+ codex: path.join(CC_TOOL_BASE_DIR, 'codex-daily-stats'),
109
+ gemini: path.join(CC_TOOL_BASE_DIR, 'gemini-daily-stats'),
110
+ opencode: path.join(CC_TOOL_BASE_DIR, 'opencode-daily-stats')
53
111
  }
54
112
  },
55
113
 
56
114
  // 会话缓存
57
- sessionCache: path.join(CTX_BASE_DIR, 'session-cache.json'),
115
+ sessionCache: path.join(CC_TOOL_BASE_DIR, 'session-cache.json'),
58
116
 
59
117
  // 项目顺序
60
- projectOrder: path.join(CTX_BASE_DIR, 'project-order.json'),
118
+ projectOrder: path.join(CC_TOOL_BASE_DIR, 'project-order.json'),
61
119
 
62
120
  // 环境备份
63
- envBackups: path.join(CTX_BASE_DIR, 'env-backups'),
121
+ envBackups: path.join(CC_TOOL_BASE_DIR, 'env-backups'),
64
122
 
65
123
  // UI 配置
66
- uiConfig: path.join(CTX_BASE_DIR, 'ui-config.json'),
124
+ uiConfig: path.join(CC_TOOL_BASE_DIR, 'ui-config.json'),
67
125
 
68
126
  // 飞书通知脚本
69
- notifyHook: path.join(CTX_BASE_DIR, 'notify-hook.js'),
127
+ notifyHook: path.join(CC_TOOL_BASE_DIR, 'notify-hook.js'),
70
128
 
71
129
  // Skills 安装目录(注意:这个仍使用 Claude 原生路径)
72
130
  skills: path.join(os.homedir(), '.claude', 'skills'),
73
131
 
74
132
  // MCP 配置(注意:这个仍使用 Claude 原生路径)
75
- mcpConfig: path.join(CTX_BASE_DIR, 'mcp-config.json'),
133
+ mcpConfig: path.join(CC_TOOL_BASE_DIR, 'mcp-config.json'),
76
134
 
77
135
  // Terminal 配置
78
- terminalConfig: path.join(CTX_BASE_DIR, 'terminal-config.json'),
136
+ terminalConfig: path.join(CC_TOOL_BASE_DIR, 'terminal-config.json'),
79
137
 
80
138
  // Prompts
81
- prompts: path.join(CTX_BASE_DIR, 'prompts.json'),
139
+ prompts: path.join(CC_TOOL_BASE_DIR, 'prompts.json'),
82
140
 
83
141
  // 代理运行时状态
84
142
  proxyRuntime: {
85
- claude: path.join(CTX_BASE_DIR, 'proxy-runtime.json'),
86
- codex: path.join(CTX_BASE_DIR, 'codex-proxy-runtime.json'),
87
- gemini: path.join(CTX_BASE_DIR, 'gemini-proxy-runtime.json')
143
+ claude: path.join(CC_TOOL_BASE_DIR, 'proxy-runtime.json'),
144
+ codex: path.join(CC_TOOL_BASE_DIR, 'codex-proxy-runtime.json'),
145
+ gemini: path.join(CC_TOOL_BASE_DIR, 'gemini-proxy-runtime.json'),
146
+ opencode: path.join(CC_TOOL_BASE_DIR, 'opencode-proxy-runtime.json')
88
147
  }
89
148
  };
90
149
 
@@ -111,11 +170,24 @@ const NATIVE_PATHS = {
111
170
  env: path.join(os.homedir(), '.gemini', '.env'),
112
171
  envBackup: path.join(os.homedir(), '.gemini', '.env.cc-tool-backup'),
113
172
  tmp: path.join(os.homedir(), '.gemini', 'tmp')
173
+ },
174
+
175
+ // OpenCode 原生配置
176
+ opencode: {
177
+ data: path.join(os.homedir(), '.local', 'share', 'opencode'),
178
+ config: path.join(os.homedir(), '.config', 'opencode'),
179
+ sessions: path.join(os.homedir(), '.local', 'share', 'opencode', 'storage', 'session'),
180
+ projects: path.join(os.homedir(), '.local', 'share', 'opencode', 'storage', 'project'),
181
+ messages: path.join(os.homedir(), '.local', 'share', 'opencode', 'storage', 'message'),
182
+ log: path.join(os.homedir(), '.local', 'share', 'opencode', 'log')
114
183
  }
115
184
  };
116
185
 
117
186
  module.exports = {
118
187
  PATHS,
119
188
  NATIVE_PATHS,
120
- CTX_BASE_DIR
189
+ CTX_BASE_DIR,
190
+ CC_TOOL_BASE_DIR,
191
+ LEGACY_BASE_DIRS,
192
+ ensureStorageDirMigrated
121
193
  };
package/src/index.js CHANGED
@@ -20,7 +20,9 @@ const { handleProxyStart: proxyStart, handleProxyStop: proxyStop, handleProxyRes
20
20
  const { handleLogs } = require('./commands/logs');
21
21
  const { handleStats, handleStatsExport } = require('./commands/stats');
22
22
  const { handleDoctor } = require('./commands/doctor');
23
+ const { handleUpdate } = require('./commands/update');
23
24
  const { workspaceMenu } = require('./commands/workspace');
25
+ const { ensureStorageDirMigrated } = require('./config/paths');
24
26
  const PluginManager = require('./plugins/plugin-manager');
25
27
  const eventBus = require('./plugins/event-bus');
26
28
  const chalk = require('chalk');
@@ -61,7 +63,8 @@ function showHelp() {
61
63
  console.log(' ctx claude status 查看 Claude 代理状态');
62
64
  console.log(' ctx codex start 启动 Codex 代理');
63
65
  console.log(' ctx gemini start 启动 Gemini 代理');
64
- console.log(chalk.gray(' (codex/gemini 命令与 claude 类似)\n'));
66
+ console.log(' ctx opencode start 启动 OpenCode 代理');
67
+ console.log(chalk.gray(' (codex/gemini/opencode 命令与 claude 类似)\n'));
65
68
 
66
69
  console.log(chalk.yellow('📋 日志管理:'));
67
70
  console.log(' ctx logs 查看所有日志');
@@ -78,7 +81,9 @@ function showHelp() {
78
81
  console.log(' ctx stats export 导出统计数据\n');
79
82
 
80
83
  console.log(chalk.yellow('🛠️ 其他命令:'));
84
+ console.log(' ctx update 检查并更新到最新版本');
81
85
  console.log(' ctx doctor 系统诊断');
86
+ console.log(' ctx port 配置端口');
82
87
  console.log(' ctx reset 重置配置');
83
88
  console.log(' ctx security reset 关闭访问密码');
84
89
  console.log(' ctx --version, -v 显示版本');
@@ -132,6 +137,8 @@ process.on('SIGINT', async () => {
132
137
  * 主函数
133
138
  */
134
139
  async function main() {
140
+ ensureStorageDirMigrated();
141
+
135
142
  // 处理命令行参数
136
143
  const args = process.argv.slice(2);
137
144
 
@@ -147,6 +154,47 @@ async function main() {
147
154
  return;
148
155
  }
149
156
 
157
+ // daemon 命令兼容(保持与 README 中旧命令一致)
158
+ if (args[0] === 'daemon') {
159
+ const subCommand = args[1] || 'status';
160
+
161
+ if (subCommand === 'start') {
162
+ await handleStart();
163
+ return;
164
+ }
165
+ if (subCommand === 'stop') {
166
+ await handleStop();
167
+ return;
168
+ }
169
+ if (subCommand === 'restart') {
170
+ await handleRestart();
171
+ return;
172
+ }
173
+ if (subCommand === 'status') {
174
+ await handleStatus();
175
+ return;
176
+ }
177
+ if (subCommand === 'logs') {
178
+ const options = {};
179
+ for (let i = 2; i < args.length; i++) {
180
+ if (args[i] === '--lines' && args[i + 1]) {
181
+ options.lines = parseInt(args[i + 1], 10);
182
+ i++;
183
+ } else if (args[i] === '--follow' || args[i] === '-f') {
184
+ options.follow = true;
185
+ } else if (args[i] === '--clear') {
186
+ options.clear = true;
187
+ }
188
+ }
189
+ await handleLogs('ui', options);
190
+ return;
191
+ }
192
+
193
+ console.log(chalk.red(`\n❌ 未知 daemon 子命令: ${subCommand}\n`));
194
+ console.log(chalk.gray('支持的命令: start, stop, restart, status, logs\n'));
195
+ return;
196
+ }
197
+
150
198
  // reset 命令 - 恢复默认配置
151
199
  if (args[0] === 'reset') {
152
200
  await resetConfig();
@@ -207,8 +255,8 @@ async function main() {
207
255
  return;
208
256
  }
209
257
 
210
- // claude/codex/gemini 代理管理命令
211
- const channels = ['claude', 'codex', 'gemini'];
258
+ // claude/codex/gemini/opencode 代理管理命令
259
+ const channels = ['claude', 'codex', 'gemini', 'opencode'];
212
260
  if (channels.includes(args[0])) {
213
261
  const channel = args[0];
214
262
  const action = args[1] || 'status';
@@ -281,6 +329,19 @@ async function main() {
281
329
  return;
282
330
  }
283
331
 
332
+ // update 命令 - 检查并更新到最新版本
333
+ if (args[0] === 'update') {
334
+ const checkOnly = args.includes('--check');
335
+ await handleUpdate({ checkOnly });
336
+ return;
337
+ }
338
+
339
+ // port 命令 - 配置端口
340
+ if (args[0] === 'port') {
341
+ await handlePortConfig();
342
+ return;
343
+ }
344
+
284
345
  // 代理命令
285
346
  if (args[0] === 'proxy') {
286
347
  const { handleProxyStart, handleProxyStop, handleProxyStatus } = require('./commands/proxy');
@@ -1,7 +1,8 @@
1
1
  const path = require('path');
2
- const os = require('os');
2
+ const { PATHS, ensureStorageDirMigrated } = require('../config/paths');
3
3
 
4
- const PLUGINS_DIR = path.join(os.homedir(), '.claude', 'cc-tool', 'plugins');
4
+ ensureStorageDirMigrated();
5
+ const PLUGINS_DIR = path.join(PATHS.base, 'plugins');
5
6
  const REGISTRY_FILE = path.join(PLUGINS_DIR, 'registry.json');
6
7
  const CONFIG_DIR = path.join(PLUGINS_DIR, 'config');
7
8
  const INSTALLED_DIR = path.join(PLUGINS_DIR, 'installed');
@@ -15,7 +15,7 @@ const packageJson = require('../../package.json');
15
15
  function createPluginContext(pluginName, pluginConfig, pluginDir) {
16
16
  const commandRegistry = new Map();
17
17
 
18
- // Storage API - persists plugin data to ~/.claude/cc-tool/plugins/config/<plugin-name>.json
18
+ // Storage API - persists plugin data to ~/.cc-tool/plugins/config/<plugin-name>.json
19
19
  const storage = createStorageAPI(pluginName);
20
20
 
21
21
  // Logger API - prefixed logging
@@ -1,10 +1,12 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
  const os = require('os');
4
+ const { PATHS, ensureStorageDirMigrated } = require('./config/paths');
4
5
 
5
6
  // 恢复配置到默认状态
6
7
  async function resetConfig() {
7
8
  console.log('\n开始恢复默认配置...\n');
9
+ ensureStorageDirMigrated();
8
10
 
9
11
  try {
10
12
  // 1. 尝试停止代理服务器(如果正在运行)
@@ -42,10 +44,10 @@ async function resetConfig() {
42
44
  console.log('检测到代理配置,尝试恢复到正常渠道...');
43
45
 
44
46
  // 读取激活的渠道
45
- const activeChannelPath = path.join(os.homedir(), '.claude', 'cc-tool', 'active-channel.json');
47
+ const activeChannelPath = PATHS.activeChannel.claude;
46
48
  if (fs.existsSync(activeChannelPath)) {
47
49
  const activeChannelData = JSON.parse(fs.readFileSync(activeChannelPath, 'utf8'));
48
- const channelsPath = path.join(os.homedir(), '.claude', 'cc-tool', 'channels.json');
50
+ const channelsPath = PATHS.channels.claude;
49
51
 
50
52
  if (fs.existsSync(channelsPath)) {
51
53
  const channelsData = JSON.parse(fs.readFileSync(channelsPath, 'utf8'));
@@ -8,7 +8,24 @@ const express = require('express');
8
8
  const { AgentsService } = require('../services/agents-service');
9
9
 
10
10
  const router = express.Router();
11
- const agentsService = new AgentsService();
11
+ const SUPPORTED_PLATFORMS = ['claude', 'opencode'];
12
+ const agentServices = new Map();
13
+
14
+ function resolvePlatform(rawPlatform) {
15
+ return SUPPORTED_PLATFORMS.includes(rawPlatform) ? rawPlatform : 'claude';
16
+ }
17
+
18
+ function getPlatform(req) {
19
+ return resolvePlatform(req.query?.platform || req.body?.platform);
20
+ }
21
+
22
+ function getAgentsService(req) {
23
+ const platform = getPlatform(req);
24
+ if (!agentServices.has(platform)) {
25
+ agentServices.set(platform, new AgentsService(platform));
26
+ }
27
+ return { platform, service: agentServices.get(platform) };
28
+ }
12
29
 
13
30
  /**
14
31
  * 获取代理列表
@@ -17,11 +34,13 @@ const agentsService = new AgentsService();
17
34
  */
18
35
  router.get('/', (req, res) => {
19
36
  try {
37
+ const { platform, service } = getAgentsService(req);
20
38
  const { projectPath } = req.query;
21
- const result = agentsService.listAgents(projectPath || null);
39
+ const result = service.listAgents(projectPath || null);
22
40
 
23
41
  res.json({
24
42
  success: true,
43
+ platform,
25
44
  ...result
26
45
  });
27
46
  } catch (err) {
@@ -39,11 +58,13 @@ router.get('/', (req, res) => {
39
58
  */
40
59
  router.get('/stats', (req, res) => {
41
60
  try {
61
+ const { platform, service } = getAgentsService(req);
42
62
  const { projectPath } = req.query;
43
- const stats = agentsService.getStats(projectPath || null);
63
+ const stats = service.getStats(projectPath || null);
44
64
 
45
65
  res.json({
46
66
  success: true,
67
+ platform,
47
68
  ...stats
48
69
  });
49
70
  } catch (err) {
@@ -61,6 +82,7 @@ router.get('/stats', (req, res) => {
61
82
  */
62
83
  router.get('/:scope/:fileName', (req, res) => {
63
84
  try {
85
+ const { platform, service } = getAgentsService(req);
64
86
  const { scope, fileName } = req.params;
65
87
  const { projectPath } = req.query;
66
88
 
@@ -78,7 +100,7 @@ router.get('/:scope/:fileName', (req, res) => {
78
100
  });
79
101
  }
80
102
 
81
- const agent = agentsService.getAgent(fileName, scope, projectPath || null);
103
+ const agent = service.getAgent(fileName, scope, projectPath || null);
82
104
 
83
105
  if (!agent) {
84
106
  return res.status(404).json({
@@ -89,6 +111,7 @@ router.get('/:scope/:fileName', (req, res) => {
89
111
 
90
112
  res.json({
91
113
  success: true,
114
+ platform,
92
115
  agent
93
116
  });
94
117
  } catch (err) {
@@ -107,6 +130,7 @@ router.get('/:scope/:fileName', (req, res) => {
107
130
  */
108
131
  router.post('/', (req, res) => {
109
132
  try {
133
+ const { platform, service } = getAgentsService(req);
110
134
  const { fileName, scope, projectPath, name, description, tools, model, permissionMode, skills, systemPrompt } = req.body;
111
135
 
112
136
  if (!fileName) {
@@ -130,7 +154,7 @@ router.post('/', (req, res) => {
130
154
  });
131
155
  }
132
156
 
133
- const agent = agentsService.createAgent({
157
+ const agent = service.createAgent({
134
158
  fileName,
135
159
  scope,
136
160
  projectPath: projectPath || null,
@@ -145,6 +169,7 @@ router.post('/', (req, res) => {
145
169
 
146
170
  res.json({
147
171
  success: true,
172
+ platform,
148
173
  agent,
149
174
  message: '代理创建成功'
150
175
  });
@@ -163,6 +188,7 @@ router.post('/', (req, res) => {
163
188
  */
164
189
  router.put('/:scope/:fileName', (req, res) => {
165
190
  try {
191
+ const { platform, service } = getAgentsService(req);
166
192
  const { scope, fileName } = req.params;
167
193
  const { projectPath, name, description, tools, model, permissionMode, skills, systemPrompt } = req.body;
168
194
 
@@ -180,7 +206,7 @@ router.put('/:scope/:fileName', (req, res) => {
180
206
  });
181
207
  }
182
208
 
183
- const agent = agentsService.updateAgent({
209
+ const agent = service.updateAgent({
184
210
  fileName,
185
211
  scope,
186
212
  projectPath: projectPath || null,
@@ -195,6 +221,7 @@ router.put('/:scope/:fileName', (req, res) => {
195
221
 
196
222
  res.json({
197
223
  success: true,
224
+ platform,
198
225
  agent,
199
226
  message: '代理更新成功'
200
227
  });
@@ -213,6 +240,7 @@ router.put('/:scope/:fileName', (req, res) => {
213
240
  */
214
241
  router.delete('/:scope/:fileName', (req, res) => {
215
242
  try {
243
+ const { platform, service } = getAgentsService(req);
216
244
  const { scope, fileName } = req.params;
217
245
  const { projectPath } = req.query;
218
246
 
@@ -230,9 +258,10 @@ router.delete('/:scope/:fileName', (req, res) => {
230
258
  });
231
259
  }
232
260
 
233
- const result = agentsService.deleteAgent(fileName, scope, projectPath || null);
261
+ const result = service.deleteAgent(fileName, scope, projectPath || null);
234
262
 
235
263
  res.json({
264
+ platform,
236
265
  success: result.success,
237
266
  message: result.message
238
267
  });
@@ -254,12 +283,14 @@ router.delete('/:scope/:fileName', (req, res) => {
254
283
  */
255
284
  router.get('/all', async (req, res) => {
256
285
  try {
286
+ const { platform, service } = getAgentsService(req);
257
287
  const { projectPath, refresh } = req.query;
258
288
  const forceRefresh = refresh === '1';
259
- const result = await agentsService.listAllAgents(projectPath || null, forceRefresh);
289
+ const result = await service.listAllAgents(projectPath || null, forceRefresh);
260
290
 
261
291
  res.json({
262
292
  success: true,
293
+ platform,
263
294
  ...result
264
295
  });
265
296
  } catch (err) {
@@ -277,9 +308,11 @@ router.get('/all', async (req, res) => {
277
308
  */
278
309
  router.get('/repos', (req, res) => {
279
310
  try {
280
- const repos = agentsService.getRepos();
311
+ const { platform, service } = getAgentsService(req);
312
+ const repos = service.getRepos();
281
313
  res.json({
282
314
  success: true,
315
+ platform,
283
316
  repos
284
317
  });
285
318
  } catch (err) {
@@ -298,6 +331,7 @@ router.get('/repos', (req, res) => {
298
331
  */
299
332
  router.post('/repos', (req, res) => {
300
333
  try {
334
+ const { platform, service } = getAgentsService(req);
301
335
  const { owner, name, branch = 'main', directory = '', enabled = true } = req.body;
302
336
 
303
337
  if (!owner || !name) {
@@ -307,10 +341,11 @@ router.post('/repos', (req, res) => {
307
341
  });
308
342
  }
309
343
 
310
- const repos = agentsService.addRepo({ owner, name, branch, directory, enabled });
344
+ const repos = service.addRepo({ owner, name, branch, directory, enabled });
311
345
 
312
346
  res.json({
313
347
  success: true,
348
+ platform,
314
349
  repos
315
350
  });
316
351
  } catch (err) {
@@ -329,12 +364,14 @@ router.post('/repos', (req, res) => {
329
364
  */
330
365
  router.delete('/repos/:owner/:name', (req, res) => {
331
366
  try {
367
+ const { platform, service } = getAgentsService(req);
332
368
  const { owner, name } = req.params;
333
369
  const { directory = '' } = req.query;
334
- const repos = agentsService.removeRepo(owner, name, directory);
370
+ const repos = service.removeRepo(owner, name, directory);
335
371
 
336
372
  res.json({
337
373
  success: true,
374
+ platform,
338
375
  repos
339
376
  });
340
377
  } catch (err) {
@@ -353,13 +390,15 @@ router.delete('/repos/:owner/:name', (req, res) => {
353
390
  */
354
391
  router.put('/repos/:owner/:name/toggle', (req, res) => {
355
392
  try {
393
+ const { platform, service } = getAgentsService(req);
356
394
  const { owner, name } = req.params;
357
395
  const { enabled, directory = '' } = req.body;
358
396
 
359
- const repos = agentsService.toggleRepo(owner, name, directory, enabled);
397
+ const repos = service.toggleRepo(owner, name, directory, enabled);
360
398
 
361
399
  res.json({
362
400
  success: true,
401
+ platform,
363
402
  repos
364
403
  });
365
404
  } catch (err) {
@@ -378,6 +417,7 @@ router.put('/repos/:owner/:name/toggle', (req, res) => {
378
417
  */
379
418
  router.post('/install', async (req, res) => {
380
419
  try {
420
+ const { platform, service } = getAgentsService(req);
381
421
  const agent = req.body;
382
422
 
383
423
  if (!agent || !agent.repoOwner || !agent.repoName) {
@@ -387,10 +427,11 @@ router.post('/install', async (req, res) => {
387
427
  });
388
428
  }
389
429
 
390
- const result = await agentsService.installFromRemote(agent);
430
+ const result = await service.installFromRemote(agent);
391
431
 
392
432
  res.json({
393
433
  success: true,
434
+ platform,
394
435
  ...result
395
436
  });
396
437
  } catch (err) {
@@ -409,6 +450,7 @@ router.post('/install', async (req, res) => {
409
450
  */
410
451
  router.post('/uninstall', (req, res) => {
411
452
  try {
453
+ const { platform, service } = getAgentsService(req);
412
454
  const { fileName } = req.body;
413
455
 
414
456
  if (!fileName) {
@@ -418,10 +460,11 @@ router.post('/uninstall', (req, res) => {
418
460
  });
419
461
  }
420
462
 
421
- const result = agentsService.uninstallAgent(fileName);
463
+ const result = service.uninstallAgent(fileName);
422
464
 
423
465
  res.json({
424
466
  success: true,
467
+ platform,
425
468
  ...result
426
469
  });
427
470
  } catch (err) {