coding-tool-x 3.2.0

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 (185) hide show
  1. package/CHANGELOG.md +599 -0
  2. package/LICENSE +21 -0
  3. package/README.md +439 -0
  4. package/bin/ctx.js +8 -0
  5. package/dist/web/assets/Analytics-DN_YsnkW.js +39 -0
  6. package/dist/web/assets/Analytics-DuYvId7u.css +1 -0
  7. package/dist/web/assets/ConfigTemplates-Bidwfdf2.css +1 -0
  8. package/dist/web/assets/ConfigTemplates-DpXIMy0p.js +1 -0
  9. package/dist/web/assets/Home-38JTUlYt.js +1 -0
  10. package/dist/web/assets/Home-CjupSEWE.css +1 -0
  11. package/dist/web/assets/PluginManager-CX2tgq2H.js +1 -0
  12. package/dist/web/assets/PluginManager-ROyoZ-6m.css +1 -0
  13. package/dist/web/assets/ProjectList-C1lDcsn6.js +1 -0
  14. package/dist/web/assets/ProjectList-oJIyIRkP.css +1 -0
  15. package/dist/web/assets/SessionList-C55tjV7i.css +1 -0
  16. package/dist/web/assets/SessionList-CZ7T6rVx.js +1 -0
  17. package/dist/web/assets/SkillManager-D7pd-d_P.css +1 -0
  18. package/dist/web/assets/SkillManager-DLN9f79y.js +1 -0
  19. package/dist/web/assets/WorkspaceManager-CrwgQgmP.css +1 -0
  20. package/dist/web/assets/WorkspaceManager-DxlHZkpZ.js +1 -0
  21. package/dist/web/assets/icons-DRrXwWZi.js +1 -0
  22. package/dist/web/assets/index-CetESrXw.css +1 -0
  23. package/dist/web/assets/index-Cfvn-2Gb.js +2 -0
  24. package/dist/web/assets/markdown-BfC0goYb.css +10 -0
  25. package/dist/web/assets/markdown-C9MYpaSi.js +1 -0
  26. package/dist/web/assets/naive-ui-DlpKk-8M.js +1 -0
  27. package/dist/web/assets/vendors-DMjSfzlv.js +7 -0
  28. package/dist/web/assets/vue-vendor-DET08QYg.js +45 -0
  29. package/dist/web/favicon.ico +0 -0
  30. package/dist/web/index.html +20 -0
  31. package/dist/web/logo.png +0 -0
  32. package/docs/bannel.png +0 -0
  33. package/docs/home.png +0 -0
  34. package/docs/logo.png +0 -0
  35. package/docs/model-redirection.md +251 -0
  36. package/docs/multi-channel-load-balancing.md +249 -0
  37. package/package.json +80 -0
  38. package/src/commands/channels.js +551 -0
  39. package/src/commands/cli-type.js +101 -0
  40. package/src/commands/daemon.js +365 -0
  41. package/src/commands/doctor.js +333 -0
  42. package/src/commands/export-config.js +205 -0
  43. package/src/commands/list.js +222 -0
  44. package/src/commands/logs.js +261 -0
  45. package/src/commands/plugin.js +585 -0
  46. package/src/commands/port-config.js +135 -0
  47. package/src/commands/proxy-control.js +264 -0
  48. package/src/commands/proxy.js +152 -0
  49. package/src/commands/resume.js +137 -0
  50. package/src/commands/search.js +190 -0
  51. package/src/commands/security.js +37 -0
  52. package/src/commands/stats.js +398 -0
  53. package/src/commands/switch.js +48 -0
  54. package/src/commands/toggle-proxy.js +247 -0
  55. package/src/commands/ui.js +99 -0
  56. package/src/commands/update.js +97 -0
  57. package/src/commands/workspace.js +454 -0
  58. package/src/config/default.js +69 -0
  59. package/src/config/loader.js +149 -0
  60. package/src/config/model-metadata.js +167 -0
  61. package/src/config/model-metadata.json +125 -0
  62. package/src/config/model-pricing.js +35 -0
  63. package/src/config/paths.js +190 -0
  64. package/src/index.js +680 -0
  65. package/src/plugins/constants.js +15 -0
  66. package/src/plugins/event-bus.js +54 -0
  67. package/src/plugins/manifest-validator.js +129 -0
  68. package/src/plugins/plugin-api.js +128 -0
  69. package/src/plugins/plugin-installer.js +601 -0
  70. package/src/plugins/plugin-loader.js +229 -0
  71. package/src/plugins/plugin-manager.js +170 -0
  72. package/src/plugins/registry.js +152 -0
  73. package/src/plugins/schema/plugin-manifest.json +115 -0
  74. package/src/reset-config.js +94 -0
  75. package/src/server/api/agents.js +826 -0
  76. package/src/server/api/aliases.js +36 -0
  77. package/src/server/api/channels.js +368 -0
  78. package/src/server/api/claude-hooks.js +480 -0
  79. package/src/server/api/codex-channels.js +417 -0
  80. package/src/server/api/codex-projects.js +104 -0
  81. package/src/server/api/codex-proxy.js +195 -0
  82. package/src/server/api/codex-sessions.js +483 -0
  83. package/src/server/api/codex-statistics.js +57 -0
  84. package/src/server/api/commands.js +482 -0
  85. package/src/server/api/config-export.js +212 -0
  86. package/src/server/api/config-registry.js +357 -0
  87. package/src/server/api/config-sync.js +155 -0
  88. package/src/server/api/config-templates.js +248 -0
  89. package/src/server/api/config.js +521 -0
  90. package/src/server/api/convert.js +260 -0
  91. package/src/server/api/dashboard.js +142 -0
  92. package/src/server/api/env.js +144 -0
  93. package/src/server/api/favorites.js +77 -0
  94. package/src/server/api/gemini-channels.js +366 -0
  95. package/src/server/api/gemini-projects.js +91 -0
  96. package/src/server/api/gemini-proxy.js +173 -0
  97. package/src/server/api/gemini-sessions.js +376 -0
  98. package/src/server/api/gemini-statistics.js +57 -0
  99. package/src/server/api/health-check.js +31 -0
  100. package/src/server/api/mcp.js +399 -0
  101. package/src/server/api/opencode-channels.js +419 -0
  102. package/src/server/api/opencode-projects.js +99 -0
  103. package/src/server/api/opencode-proxy.js +207 -0
  104. package/src/server/api/opencode-sessions.js +327 -0
  105. package/src/server/api/opencode-statistics.js +57 -0
  106. package/src/server/api/plugins.js +463 -0
  107. package/src/server/api/pm2-autostart.js +269 -0
  108. package/src/server/api/projects.js +124 -0
  109. package/src/server/api/prompts.js +279 -0
  110. package/src/server/api/proxy.js +306 -0
  111. package/src/server/api/security.js +53 -0
  112. package/src/server/api/sessions.js +514 -0
  113. package/src/server/api/settings.js +142 -0
  114. package/src/server/api/skills.js +570 -0
  115. package/src/server/api/statistics.js +238 -0
  116. package/src/server/api/ui-config.js +64 -0
  117. package/src/server/api/workspaces.js +456 -0
  118. package/src/server/codex-proxy-server.js +681 -0
  119. package/src/server/dev-server.js +26 -0
  120. package/src/server/gemini-proxy-server.js +610 -0
  121. package/src/server/index.js +422 -0
  122. package/src/server/opencode-proxy-server.js +4771 -0
  123. package/src/server/proxy-server.js +669 -0
  124. package/src/server/services/agents-service.js +1137 -0
  125. package/src/server/services/alias.js +71 -0
  126. package/src/server/services/channel-health.js +234 -0
  127. package/src/server/services/channel-scheduler.js +240 -0
  128. package/src/server/services/channels.js +447 -0
  129. package/src/server/services/codex-channels.js +705 -0
  130. package/src/server/services/codex-config.js +90 -0
  131. package/src/server/services/codex-parser.js +322 -0
  132. package/src/server/services/codex-sessions.js +936 -0
  133. package/src/server/services/codex-settings-manager.js +619 -0
  134. package/src/server/services/codex-speed-test-template.json +24 -0
  135. package/src/server/services/codex-statistics-service.js +161 -0
  136. package/src/server/services/commands-service.js +574 -0
  137. package/src/server/services/config-export-service.js +1165 -0
  138. package/src/server/services/config-registry-service.js +828 -0
  139. package/src/server/services/config-sync-manager.js +941 -0
  140. package/src/server/services/config-sync-service.js +504 -0
  141. package/src/server/services/config-templates-service.js +913 -0
  142. package/src/server/services/enhanced-cache.js +196 -0
  143. package/src/server/services/env-checker.js +409 -0
  144. package/src/server/services/env-manager.js +436 -0
  145. package/src/server/services/favorites.js +165 -0
  146. package/src/server/services/format-converter.js +620 -0
  147. package/src/server/services/gemini-channels.js +459 -0
  148. package/src/server/services/gemini-config.js +73 -0
  149. package/src/server/services/gemini-sessions.js +689 -0
  150. package/src/server/services/gemini-settings-manager.js +263 -0
  151. package/src/server/services/gemini-statistics-service.js +157 -0
  152. package/src/server/services/health-check.js +85 -0
  153. package/src/server/services/mcp-client.js +790 -0
  154. package/src/server/services/mcp-service.js +1732 -0
  155. package/src/server/services/model-detector.js +1245 -0
  156. package/src/server/services/network-access.js +80 -0
  157. package/src/server/services/opencode-channels.js +366 -0
  158. package/src/server/services/opencode-gateway-adapters.js +1168 -0
  159. package/src/server/services/opencode-gateway-converter.js +639 -0
  160. package/src/server/services/opencode-sessions.js +931 -0
  161. package/src/server/services/opencode-settings-manager.js +478 -0
  162. package/src/server/services/opencode-statistics-service.js +161 -0
  163. package/src/server/services/plugins-service.js +1268 -0
  164. package/src/server/services/prompts-service.js +534 -0
  165. package/src/server/services/proxy-runtime.js +79 -0
  166. package/src/server/services/repo-scanner-base.js +708 -0
  167. package/src/server/services/request-logger.js +130 -0
  168. package/src/server/services/response-decoder.js +21 -0
  169. package/src/server/services/security-config.js +131 -0
  170. package/src/server/services/session-cache.js +127 -0
  171. package/src/server/services/session-converter.js +577 -0
  172. package/src/server/services/sessions.js +900 -0
  173. package/src/server/services/settings-manager.js +163 -0
  174. package/src/server/services/skill-service.js +1482 -0
  175. package/src/server/services/speed-test.js +1146 -0
  176. package/src/server/services/statistics-service.js +1043 -0
  177. package/src/server/services/ui-config.js +132 -0
  178. package/src/server/services/workspace-service.js +830 -0
  179. package/src/server/utils/pricing.js +73 -0
  180. package/src/server/websocket-server.js +513 -0
  181. package/src/ui/menu.js +139 -0
  182. package/src/ui/prompts.js +100 -0
  183. package/src/utils/format.js +43 -0
  184. package/src/utils/port-helper.js +108 -0
  185. package/src/utils/session.js +240 -0
@@ -0,0 +1,422 @@
1
+ const express = require('express');
2
+ const path = require('path');
3
+ const chalk = require('chalk');
4
+ const inquirer = require('inquirer');
5
+ const { loadConfig } = require('../config/loader');
6
+ const { ensureStorageDirMigrated } = require('../config/paths');
7
+ const { startWebSocketServer: attachWebSocketServer } = require('./websocket-server');
8
+ const { isPortInUse, killProcessByPort, waitForPortRelease } = require('../utils/port-helper');
9
+ const { isProxyConfig } = require('./services/settings-manager');
10
+ const {
11
+ isProxyConfig: isCodexProxyConfig,
12
+ setProxyConfig: setCodexProxyConfig
13
+ } = require('./services/codex-settings-manager');
14
+ const { setProxyConfig: setOpenCodeProxyConfig } = require('./services/opencode-settings-manager');
15
+ const { startProxyServer } = require('./proxy-server');
16
+ const { startCodexProxyServer } = require('./codex-proxy-server');
17
+ const { startGeminiProxyServer } = require('./gemini-proxy-server');
18
+ const { startOpenCodeProxyServer, collectProxyModelList } = require('./opencode-proxy-server');
19
+ const { createRemoteMutationGuard } = require('./services/network-access');
20
+ const { createApiRequestLogger } = require('./services/request-logger');
21
+
22
+ function isInteractivePortConflictMode(options = {}) {
23
+ if (options.interactive === false) {
24
+ return false;
25
+ }
26
+ if (process.argv.includes('--daemon')) {
27
+ return false;
28
+ }
29
+ return Boolean(process.stdin && process.stdin.isTTY && process.stdout && process.stdout.isTTY);
30
+ }
31
+
32
+ function printPortConflictHelp(port) {
33
+ console.log(chalk.yellow('\n💡 解决方案:'));
34
+ console.log(chalk.gray(' 1. 运行 ctx 命令,选择"配置端口"修改端口'));
35
+ console.log(chalk.gray(` 2. 或手动关闭占用端口 ${port} 的程序\n`));
36
+ }
37
+
38
+ async function startServer(port, host = '127.0.0.1', options = {}) {
39
+ ensureStorageDirMigrated();
40
+ const config = loadConfig();
41
+ // 使用配置的端口,如果没有传入参数
42
+ if (!port) {
43
+ port = config.ports?.webUI || 19999;
44
+ }
45
+
46
+ // 检查端口是否被占用
47
+ const portInUse = await isPortInUse(port, host);
48
+ if (portInUse) {
49
+ console.log(chalk.yellow(`\n⚠️ 端口 ${port} 已被占用\n`));
50
+
51
+ const interactiveMode = isInteractivePortConflictMode(options);
52
+ let shouldKill = false;
53
+
54
+ if (options.forceKillPort === true) {
55
+ shouldKill = true;
56
+ } else if (interactiveMode) {
57
+ // 询问用户是否关闭占用端口的进程
58
+ const answer = await inquirer.prompt([
59
+ {
60
+ type: 'list',
61
+ name: 'shouldKill',
62
+ message: '是否关闭占用该端口的进程并启动服务?',
63
+ choices: [
64
+ { name: '是,关闭进程并启动', value: true },
65
+ { name: '否,取消启动', value: false }
66
+ ],
67
+ default: 0 // 默认选择"是"
68
+ }
69
+ ]);
70
+ shouldKill = answer.shouldKill;
71
+ } else {
72
+ console.error(chalk.red('❌ 当前为非交互模式,无法确认端口清理操作,已取消启动。'));
73
+ printPortConflictHelp(port);
74
+ process.exit(1);
75
+ }
76
+
77
+ if (!shouldKill) {
78
+ console.log(chalk.gray('\n已取消启动'));
79
+ printPortConflictHelp(port);
80
+ process.exit(0);
81
+ }
82
+
83
+ // 尝试杀掉占用端口的进程
84
+ console.log(chalk.cyan('正在关闭占用端口的进程...'));
85
+ const killed = killProcessByPort(port);
86
+
87
+ if (!killed) {
88
+ console.error(chalk.red('\n❌ 无法关闭占用端口的进程'));
89
+ console.error(chalk.yellow('\n💡 请手动关闭占用端口的程序,或使用其他端口\n'));
90
+ process.exit(1);
91
+ }
92
+
93
+ // 等待端口释放
94
+ console.log(chalk.cyan('等待端口释放...'));
95
+ const released = await waitForPortRelease(port, 3000, host);
96
+
97
+ if (!released) {
98
+ console.error(chalk.red('\n❌ 端口释放超时'));
99
+ console.error(chalk.yellow('\n💡 请稍后重试,或手动检查端口占用情况\n'));
100
+ process.exit(1);
101
+ }
102
+
103
+ console.log(chalk.green('✓ 端口已释放\n'));
104
+ }
105
+
106
+ const app = express();
107
+ const lanMode = host === '0.0.0.0';
108
+ const allowRemoteMutation = process.env.CC_TOOL_ALLOW_REMOTE_WRITE === 'true';
109
+
110
+ // Middleware
111
+ app.use(express.json({ limit: '100mb' }));
112
+ app.use(express.urlencoded({ limit: '100mb', extended: true }));
113
+
114
+ // CORS for development
115
+ app.use((req, res, next) => {
116
+ res.header('Access-Control-Allow-Origin', '*');
117
+ res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
118
+ res.header('Access-Control-Allow-Headers', 'Content-Type');
119
+ if (req.method === 'OPTIONS') {
120
+ return res.sendStatus(200);
121
+ }
122
+ next();
123
+ });
124
+
125
+ // API 请求日志(由 CC_TOOL_LOG_API_REQUESTS=true 环境变量控制,默认关闭)
126
+ app.use('/api', createApiRequestLogger());
127
+
128
+ if (lanMode) {
129
+ app.use('/api', createRemoteMutationGuard({
130
+ enabled: true,
131
+ allowRemoteMutation,
132
+ message: '出于安全考虑,LAN 模式默认仅允许本机执行写操作。可设置 CC_TOOL_ALLOW_REMOTE_WRITE=true 覆盖。'
133
+ }));
134
+
135
+ }
136
+
137
+ // API Routes
138
+ app.use('/api/projects', require('./api/projects')(config));
139
+ app.use('/api/sessions', require('./api/sessions')(config));
140
+
141
+ // Codex API Routes
142
+ app.use('/api/codex/projects', require('./api/codex-projects')(config));
143
+ app.use('/api/codex/sessions', require('./api/codex-sessions')(config));
144
+ app.use('/api/codex/channels', require('./api/codex-channels')(config));
145
+
146
+ // Gemini API Routes
147
+ app.use('/api/gemini/projects', require('./api/gemini-projects')(config));
148
+ app.use('/api/gemini/sessions', require('./api/gemini-sessions')(config));
149
+ app.use('/api/gemini/channels', require('./api/gemini-channels')(config));
150
+ app.use('/api/gemini/proxy', require('./api/gemini-proxy'));
151
+
152
+ // OpenCode API Routes
153
+ app.use('/api/opencode/projects', require('./api/opencode-projects')(config));
154
+ app.use('/api/opencode/sessions', require('./api/opencode-sessions')(config));
155
+ app.use('/api/opencode/channels', require('./api/opencode-channels')(config));
156
+ app.use('/api/opencode/proxy', require('./api/opencode-proxy'));
157
+ app.use('/api/opencode/statistics', require('./api/opencode-statistics'));
158
+
159
+ app.use('/api/aliases', require('./api/aliases')());
160
+ app.use('/api/favorites', require('./api/favorites'));
161
+ app.use('/api/ui-config', require('./api/ui-config'));
162
+ app.use('/api/security', require('./api/security'));
163
+ app.use('/api/channels', require('./api/channels'));
164
+ app.use('/api/proxy', require('./api/proxy'));
165
+ app.use('/api/codex/proxy', require('./api/codex-proxy'));
166
+ app.use('/api/settings', require('./api/settings'));
167
+ app.use('/api/config', require('./api/config'));
168
+ app.use('/api/convert', require('./api/convert'));
169
+ app.use('/api/statistics', require('./api/statistics'));
170
+ app.use('/api/codex/statistics', require('./api/codex-statistics'));
171
+ app.use('/api/gemini/statistics', require('./api/gemini-statistics'));
172
+ app.use('/api/pm2-autostart', require('./api/pm2-autostart')());
173
+ app.use('/api/dashboard', require('./api/dashboard'));
174
+ app.use('/api/mcp', require('./api/mcp'));
175
+ app.use('/api/prompts', require('./api/prompts'));
176
+ app.use('/api/env', require('./api/env'));
177
+ app.use('/api/skills', require('./api/skills'));
178
+ const claudeHooks = require('./api/claude-hooks');
179
+ app.use('/api/claude/hooks', claudeHooks);
180
+
181
+ // 初始化 Claude hooks 默认配置(自动开启任务完成通知)
182
+ claudeHooks.initDefaultHooks();
183
+
184
+ // Claude Code 专有功能 API
185
+ app.use('/api/commands', require('./api/commands'));
186
+ app.use('/api/agents', require('./api/agents'));
187
+ app.use('/api/plugins', require('./api/plugins'));
188
+
189
+ // 工作区 API
190
+ app.use('/api/workspaces', require('./api/workspaces'));
191
+
192
+ // 配置模板 API
193
+ app.use('/api/config-templates', require('./api/config-templates'));
194
+
195
+ // 配置导出/导入 API
196
+ app.use('/api/config-export', require('./api/config-export'));
197
+
198
+ // 配置同步 API
199
+ app.use('/api/config-sync', require('./api/config-sync'));
200
+
201
+ // 配置注册表 API (集中管理 skills/commands/agents/plugins 的启用/禁用)
202
+ app.use('/api/config-registry', require('./api/config-registry'));
203
+
204
+ // 健康检查 API
205
+ app.use('/api/health-check', require('./api/health-check')(config));
206
+
207
+ // Serve static files in production
208
+ const distPath = path.join(__dirname, '../../dist/web');
209
+ if (require('fs').existsSync(distPath)) {
210
+ app.use(express.static(distPath));
211
+ app.get('*', (req, res) => {
212
+ res.sendFile(path.join(distPath, 'index.html'));
213
+ });
214
+ }
215
+
216
+ // Start server(确保监听成功后才返回,避免命令误报“已启动”)
217
+ const server = app.listen(port, host);
218
+ await new Promise((resolve) => {
219
+ const onListening = () => {
220
+ server.off('error', onError);
221
+ resolve();
222
+ };
223
+
224
+ const onError = (err) => {
225
+ server.off('listening', onListening);
226
+ if (err.code === 'EADDRINUSE') {
227
+ console.error(chalk.red(`\n❌ 端口 ${port} 已被占用`));
228
+ console.error(chalk.yellow('\n💡 解决方案:'));
229
+ console.error(chalk.gray(' 1. 运行 ctx 命令,选择"配置端口"修改端口'));
230
+ console.error(chalk.gray(` 2. 或关闭占用端口 ${port} 的程序\n`));
231
+ } else {
232
+ console.error(chalk.red(`\n❌ 启动服务器失败: ${err.message}\n`));
233
+ }
234
+ process.exit(1);
235
+ };
236
+
237
+ server.once('listening', onListening);
238
+ server.once('error', onError);
239
+ });
240
+
241
+ console.log(`\n🚀 Coding-Tool Web UI running at:`);
242
+ if (host === '0.0.0.0') {
243
+ console.log(chalk.yellow(` ⚠️ 警告: 服务正在监听所有网络接口 (LAN 可访问)`));
244
+ console.log(` http://localhost:${port}`);
245
+ console.log(chalk.gray(` http://<your-ip>:${port} (LAN 访问)`));
246
+ } else {
247
+ console.log(` http://localhost:${port}`);
248
+ }
249
+
250
+ // 附加 WebSocket 服务器到同一个端口
251
+ attachWebSocketServer(server, { host });
252
+ console.log(` ws://localhost:${port}/ws\n`);
253
+
254
+ if (host === '0.0.0.0' && !allowRemoteMutation) {
255
+ console.log(chalk.yellow(' 🔒 已启用 LAN 安全保护:远程写操作默认禁用'));
256
+ }
257
+ // 自动恢复代理状态
258
+ autoRestoreProxies();
259
+
260
+ // 延迟执行健康检查,避免阻塞启动
261
+ setTimeout(() => performStartupHealthCheck(), 2000);
262
+
263
+ return server;
264
+ }
265
+
266
+ // 自动恢复代理状态
267
+ function autoRestoreProxies() {
268
+ const config = loadConfig();
269
+ const os = require('os');
270
+ const fs = require('fs');
271
+ const path = require('path');
272
+
273
+ const ccToolDir = path.join(os.homedir(), '.cc-tool');
274
+
275
+ // 检查 Claude 代理状态文件
276
+ const claudeActiveFile = path.join(ccToolDir, 'active-channel.json');
277
+ if (fs.existsSync(claudeActiveFile)) {
278
+ console.log(chalk.cyan('\n🔄 检测到 Claude 代理状态文件,正在自动启动...'));
279
+ const proxyPort = config.ports?.proxy || 20088;
280
+ startProxyServer(proxyPort)
281
+ .then(() => {
282
+ console.log(chalk.green(`✅ Claude 代理已自动启动,端口: ${proxyPort}`));
283
+ })
284
+ .catch((err) => {
285
+ console.error(chalk.red(`❌ Claude 代理启动失败: ${err.message}`));
286
+ });
287
+ }
288
+
289
+ // 检查 Codex 代理状态文件
290
+ const codexActiveFile = path.join(ccToolDir, 'codex-active-channel.json');
291
+ if (fs.existsSync(codexActiveFile)) {
292
+ console.log(chalk.cyan('\n🔄 检测到 Codex 代理状态文件,正在自动启动...'));
293
+ const codexProxyPort = config.ports?.codexProxy || 20089;
294
+ startCodexProxyServer(codexProxyPort)
295
+ .then((result) => {
296
+ const port = result?.port || codexProxyPort;
297
+ console.log(chalk.green(`✅ Codex 代理已自动启动,端口: ${port}`));
298
+
299
+ // 重启后重新写入 cc-proxy 配置与环境变量,避免缺少 provider/env 导致报错
300
+ try {
301
+ const cfgResult = setCodexProxyConfig(port);
302
+ if (cfgResult?.success) {
303
+ console.log(chalk.gray(' 已同步 codex config.toml 与 CC_PROXY_KEY'));
304
+ }
305
+ } catch (err) {
306
+ console.error(chalk.red(`❌ Codex 代理配置同步失败: ${err.message}`));
307
+ }
308
+ })
309
+ .catch((err) => {
310
+ console.error(chalk.red(`❌ Codex 代理启动失败: ${err.message}`));
311
+ });
312
+ }
313
+
314
+ // 检查 Gemini 代理状态文件
315
+ const geminiActiveFile = path.join(ccToolDir, 'gemini-active-channel.json');
316
+ if (fs.existsSync(geminiActiveFile)) {
317
+ console.log(chalk.cyan('\n🔄 检测到 Gemini 代理状态文件,正在自动启动...'));
318
+ const geminiProxyPort = config.ports?.geminiProxy || 20090;
319
+ startGeminiProxyServer(geminiProxyPort)
320
+ .then((result) => {
321
+ if (result.success) {
322
+ console.log(chalk.green(`✅ Gemini 代理已自动启动,端口: ${result.port}`));
323
+ } else {
324
+ console.error(chalk.red(`❌ Gemini 代理启动失败: ${result.error || 'Unknown error'}`));
325
+ }
326
+ })
327
+ .catch((err) => {
328
+ console.error(chalk.red(`❌ Gemini 代理启动失败: ${err.message}`));
329
+ });
330
+ } else {
331
+ console.log(chalk.gray('\n💡 提示: 如需使用 Gemini 代理,请在前端界面激活 Gemini 渠道'));
332
+ }
333
+
334
+ // 检查 OpenCode 代理状态文件
335
+ const opencodeActiveFile = path.join(ccToolDir, 'opencode-active-channel.json');
336
+ if (fs.existsSync(opencodeActiveFile)) {
337
+ console.log(chalk.cyan('\n🔄 检测到 OpenCode 代理状态文件,正在自动启动...'));
338
+ const opencodeProxyPort = config.ports?.opencodeProxy || 20091;
339
+ startOpenCodeProxyServer(opencodeProxyPort)
340
+ .then(async (result) => {
341
+ if (result.success) {
342
+ console.log(chalk.green(`✅ OpenCode 代理已自动启动,端口: ${result.port}`));
343
+ try {
344
+ const { getEnabledChannels: getEnabledOpenCodeChannels } = require('./services/opencode-channels');
345
+ const enabledChs = getEnabledOpenCodeChannels();
346
+ const allModels = [];
347
+ const seen = new Set();
348
+ enabledChs.forEach((ch) => {
349
+ [ch.model, ch.speedTestModel].forEach((m) => {
350
+ if (typeof m === 'string' && m.trim() && !seen.has(m.trim().toLowerCase())) {
351
+ seen.add(m.trim().toLowerCase());
352
+ allModels.push(m.trim());
353
+ }
354
+ });
355
+ });
356
+ const detectedModels = await collectProxyModelList(enabledChs, { useCacheOnly: true });
357
+ if (Array.isArray(detectedModels)) {
358
+ detectedModels.forEach((m) => {
359
+ if (typeof m === 'string' && m.trim() && !seen.has(m.trim().toLowerCase())) {
360
+ seen.add(m.trim().toLowerCase());
361
+ allModels.push(m.trim());
362
+ }
363
+ });
364
+ }
365
+ const firstChannel = enabledChs[0];
366
+ const activeModel = firstChannel && (firstChannel.model || firstChannel.speedTestModel) || null;
367
+ const cfgResult = setOpenCodeProxyConfig(result.port, { model: activeModel, models: allModels });
368
+ if (cfgResult?.success) {
369
+ console.log(chalk.gray(' 已同步 OpenCode 配置文件'));
370
+ }
371
+ } catch (err) {
372
+ console.error(chalk.red(`❌ OpenCode 代理配置同步失败: ${err.message}`));
373
+ }
374
+ } else {
375
+ console.error(chalk.red(`❌ OpenCode 代理启动失败: ${result.error || 'Unknown error'}`));
376
+ }
377
+ })
378
+ .catch((err) => {
379
+ console.error(chalk.red(`❌ OpenCode 代理启动失败: ${err.message}`));
380
+ });
381
+ }
382
+ }
383
+
384
+ // 启动时执行健康检查
385
+ async function performStartupHealthCheck() {
386
+ const { healthCheckAllProjects } = require('./services/health-check');
387
+ const { getProjects } = require('./services/sessions');
388
+
389
+ try {
390
+ console.log(chalk.cyan('\n🔍 正在进行启动健康检查...'));
391
+
392
+ // 获取所有项目
393
+ const config = loadConfig();
394
+ const projects = await getProjects(config);
395
+
396
+ if (projects.length === 0) {
397
+ console.log(chalk.gray(' 未发现项目,跳过健康检查'));
398
+ return;
399
+ }
400
+
401
+ // 检查并创建缺失的目录
402
+ const healthResult = healthCheckAllProjects(projects);
403
+
404
+ if (healthResult.summary.created > 0) {
405
+ console.log(chalk.green(` ✓ 已为 ${healthResult.summary.created} 个项目创建 .claude/sessions 目录`));
406
+ }
407
+
408
+ if (healthResult.summary.errors > 0) {
409
+ console.log(chalk.yellow(` ⚠ ${healthResult.summary.errors} 个项目检查失败`));
410
+ }
411
+
412
+ if (healthResult.summary.created === 0 && healthResult.summary.errors === 0) {
413
+ console.log(chalk.green(` ✓ 所有 ${healthResult.summary.healthy} 个项目状态正常`));
414
+ }
415
+
416
+ console.log('');
417
+ } catch (err) {
418
+ console.error(chalk.red(' ✗ 健康检查失败:'), err.message);
419
+ }
420
+ }
421
+
422
+ module.exports = { startServer };