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,247 @@
1
+ // 动态切换开关命令
2
+ const chalk = require('chalk');
3
+ const inquirer = require('inquirer');
4
+ const { loadConfig } = require('../config/loader');
5
+ const SETTINGS_MANAGERS = {
6
+ claude: () => require('../server/services/settings-manager'),
7
+ codex: () => require('../server/services/codex-settings-manager'),
8
+ gemini: () => require('../server/services/gemini-settings-manager'),
9
+ opencode: () => require('../server/services/opencode-settings-manager')
10
+ };
11
+
12
+ /**
13
+ * 获取当前类型的代理服务
14
+ */
15
+ function getProxyServices(cliType) {
16
+ if (cliType === 'claude') {
17
+ const { getProxyStatus, startProxyServer, stopProxyServer } = require('../server/proxy-server');
18
+ return { getProxyStatus, startProxyServer, stopProxyServer, defaultPort: 20088 };
19
+ } else if (cliType === 'codex') {
20
+ const { getCodexProxyStatus, startCodexProxyServer, stopCodexProxyServer } = require('../server/codex-proxy-server');
21
+ return {
22
+ getProxyStatus: getCodexProxyStatus,
23
+ startProxyServer: startCodexProxyServer,
24
+ stopProxyServer: stopCodexProxyServer,
25
+ defaultPort: 20089
26
+ };
27
+ } else if (cliType === 'gemini') {
28
+ const { getGeminiProxyStatus, startGeminiProxyServer, stopGeminiProxyServer } = require('../server/gemini-proxy-server');
29
+ return {
30
+ getProxyStatus: getGeminiProxyStatus,
31
+ startProxyServer: startGeminiProxyServer,
32
+ stopProxyServer: stopGeminiProxyServer,
33
+ defaultPort: 20090
34
+ };
35
+ } else if (cliType === 'opencode') {
36
+ const { getOpenCodeProxyStatus, startOpenCodeProxyServer, stopOpenCodeProxyServer } = require('../server/opencode-proxy-server');
37
+ return {
38
+ getProxyStatus: getOpenCodeProxyStatus,
39
+ startProxyServer: startOpenCodeProxyServer,
40
+ stopProxyServer: stopOpenCodeProxyServer,
41
+ defaultPort: 20091
42
+ };
43
+ }
44
+ }
45
+
46
+ function getSettingsManager(cliType) {
47
+ const loader = SETTINGS_MANAGERS[cliType] || SETTINGS_MANAGERS.claude;
48
+ const manager = loader();
49
+ return {
50
+ setProxyConfig: manager.setProxyConfig,
51
+ restoreSettings: manager.restoreSettings,
52
+ hasBackup: manager.hasBackup
53
+ };
54
+ }
55
+
56
+ /**
57
+ * 切换动态切换功能
58
+ */
59
+ async function handleToggleProxy() {
60
+ const config = loadConfig();
61
+ const cliType = config.currentCliType || 'claude';
62
+ const services = getProxyServices(cliType);
63
+ if (!services) {
64
+ console.log(chalk.red(`\n❌ 当前 CLI 类型 (${cliType}) 暂不支持动态切换\n`));
65
+ return;
66
+ }
67
+
68
+ const proxyStatus = services.getProxyStatus();
69
+
70
+ if (proxyStatus.running) {
71
+ // 当前代理正在运行,提示关闭
72
+ await handleStopProxy(cliType, services);
73
+ } else {
74
+ // 当前代理未运行,提示开启
75
+ await handleStartProxy(cliType, services);
76
+ }
77
+ }
78
+
79
+ /**
80
+ * 开启动态切换
81
+ */
82
+ async function handleStartProxy(cliType, services) {
83
+ console.clear();
84
+ console.log(chalk.bold.cyan('\n╔═══════════════════════════════════════╗'));
85
+ console.log(chalk.bold.cyan('║ 开启动态切换 ║'));
86
+ console.log(chalk.bold.cyan('╚═══════════════════════════════════════╝\n'));
87
+
88
+ const toolNameMap = {
89
+ claude: 'Claude Code',
90
+ codex: 'Codex',
91
+ gemini: 'Gemini',
92
+ opencode: 'OpenCode'
93
+ };
94
+ const toolName = toolNameMap[cliType] || 'Claude Code';
95
+ const defaultPort = services.defaultPort;
96
+
97
+ console.log(chalk.cyan('动态切换功能说明:'));
98
+ console.log(chalk.gray('• 开启后会在本地启动一个代理服务'));
99
+ console.log(chalk.gray(`• 可以在不重启 ${toolName} 的情况下动态管理渠道`));
100
+ console.log(chalk.gray('• 通过 Web UI 或"渠道管理"功能快速调整启用的线路'));
101
+ console.log(chalk.gray(`• 代理服务地址: http://127.0.0.1:${defaultPort}\n`));
102
+
103
+ console.log(chalk.yellow('⚠️ 重要提示:'));
104
+ console.log(chalk.yellow('• 开启期间请勿关闭 CLI 终端窗口'));
105
+ console.log(chalk.yellow('• 如果异常关闭导致代理失效,请运行: ctx reset'));
106
+ console.log(chalk.yellow('• 或使用主菜单的"恢复默认配置"功能\n'));
107
+
108
+ const { confirm } = await inquirer.prompt([
109
+ {
110
+ type: 'confirm',
111
+ name: 'confirm',
112
+ message: '是否开启动态切换?',
113
+ default: true,
114
+ },
115
+ ]);
116
+
117
+ if (!confirm) {
118
+ console.log(chalk.gray('\n已取消\n'));
119
+ return;
120
+ }
121
+
122
+ try {
123
+ console.log(chalk.cyan('\n🚀 正在启动代理服务...\n'));
124
+
125
+ // 启动代理服务器
126
+ const proxyResult = await services.startProxyServer();
127
+
128
+ if (!proxyResult.success) {
129
+ throw new Error('代理服务器启动失败');
130
+ }
131
+
132
+ console.log(chalk.green(`✅ 代理服务已启动: http://127.0.0.1:${proxyResult.port}`));
133
+
134
+ // 修改配置文件
135
+ const settingsManager = getSettingsManager(cliType);
136
+ settingsManager.setProxyConfig(proxyResult.port);
137
+ console.log(chalk.green('✅ 配置文件已更新'));
138
+
139
+ if (settingsManager.hasBackup()) {
140
+ console.log(chalk.green('✅ 原配置已备份'));
141
+ }
142
+
143
+ console.log(chalk.cyan('\n💡 动态切换已启用!'));
144
+ console.log(chalk.gray(` 现在可以通过"渠道管理"功能快速调整,无需重启 ${toolName}\n`));
145
+
146
+ await inquirer.prompt([
147
+ {
148
+ type: 'input',
149
+ name: 'continue',
150
+ message: '按回车继续...',
151
+ },
152
+ ]);
153
+ } catch (error) {
154
+ console.log(chalk.red(`\n❌ 启动失败: ${error.message}\n`));
155
+
156
+ await inquirer.prompt([
157
+ {
158
+ type: 'input',
159
+ name: 'continue',
160
+ message: '按回车继续...',
161
+ },
162
+ ]);
163
+ }
164
+ }
165
+
166
+ /**
167
+ * 关闭动态切换
168
+ */
169
+ async function handleStopProxy(cliType, services) {
170
+ console.clear();
171
+ console.log(chalk.bold.cyan('\n╔═══════════════════════════════════════╗'));
172
+ console.log(chalk.bold.cyan('║ 关闭动态切换 ║'));
173
+ console.log(chalk.bold.cyan('╚═══════════════════════════════════════╝\n'));
174
+
175
+ const toolNameMap = {
176
+ claude: 'Claude Code',
177
+ codex: 'Codex',
178
+ gemini: 'Gemini',
179
+ opencode: 'OpenCode'
180
+ };
181
+ const toolName = toolNameMap[cliType] || 'Claude Code';
182
+ const proxyStatus = services.getProxyStatus();
183
+
184
+ console.log(chalk.cyan('当前状态:'));
185
+ console.log(chalk.gray(`• 代理服务: ${chalk.green('运行中')}`));
186
+ console.log(chalk.gray(`• 代理端口: ${proxyStatus.port}`));
187
+ console.log(chalk.gray(`• 代理地址: http://127.0.0.1:${proxyStatus.port}\n`));
188
+
189
+ console.log(chalk.yellow('关闭后:'));
190
+ console.log(chalk.gray('• 代理服务将被停止'));
191
+ console.log(chalk.gray('• 配置将恢复到关闭前的状态'));
192
+ console.log(chalk.gray(`• 之后管理渠道将需要重启 ${toolName}\n`));
193
+
194
+ const { confirm } = await inquirer.prompt([
195
+ {
196
+ type: 'confirm',
197
+ name: 'confirm',
198
+ message: '是否关闭动态切换?',
199
+ default: false,
200
+ },
201
+ ]);
202
+
203
+ if (!confirm) {
204
+ console.log(chalk.gray('\n已取消\n'));
205
+ return;
206
+ }
207
+
208
+ try {
209
+ console.log(chalk.cyan('\n⏹️ 正在停止代理服务...\n'));
210
+
211
+ // 停止代理服务器
212
+ await services.stopProxyServer();
213
+ console.log(chalk.green('✅ 代理服务已停止'));
214
+
215
+ // 恢复配置文件
216
+ const settingsManager = getSettingsManager(cliType);
217
+ if (settingsManager.hasBackup()) {
218
+ settingsManager.restoreSettings();
219
+ console.log(chalk.green('✅ 配置文件已恢复'));
220
+ }
221
+
222
+ console.log(chalk.cyan('\n💡 动态切换已关闭'));
223
+ console.log(chalk.gray(` 现在调整渠道需要重启 ${toolName} 才能生效\n`));
224
+
225
+ await inquirer.prompt([
226
+ {
227
+ type: 'input',
228
+ name: 'continue',
229
+ message: '按回车继续...',
230
+ },
231
+ ]);
232
+ } catch (error) {
233
+ console.log(chalk.red(`\n❌ 停止失败: ${error.message}\n`));
234
+
235
+ await inquirer.prompt([
236
+ {
237
+ type: 'input',
238
+ name: 'continue',
239
+ message: '按回车继续...',
240
+ },
241
+ ]);
242
+ }
243
+ }
244
+
245
+ module.exports = {
246
+ handleToggleProxy,
247
+ };
@@ -0,0 +1,99 @@
1
+ const chalk = require('chalk');
2
+ const { startServer } = require('../server');
3
+ const open = require('open');
4
+ const { getProxyStatus } = require('../server/proxy-server');
5
+ const { loadConfig } = require('../config/loader');
6
+
7
+ async function handleUI() {
8
+ // 检查是否为 daemon 模式(PM2 启动)
9
+ const isDaemon = process.argv.includes('--daemon');
10
+
11
+ // 检查是否启用 LAN 访问 (--host 标志)
12
+ const enableHost = process.argv.includes('--host');
13
+ const host = enableHost ? '0.0.0.0' : '127.0.0.1';
14
+
15
+ if (!isDaemon) {
16
+ console.clear();
17
+ console.log(chalk.cyan.bold('\n🌐 启动 Coding-Tool Web UI...\n'));
18
+ if (enableHost) {
19
+ console.log(chalk.yellow('⚠️ LAN 访问已启用 (--host)\n'));
20
+ }
21
+ }
22
+
23
+ // 从配置加载端口
24
+ const config = loadConfig();
25
+ const port = config.ports?.webUI || 19999;
26
+ const url = `http://localhost:${port}`;
27
+
28
+ try {
29
+ await startServer(port, host);
30
+
31
+ // 自动打开浏览器(仅非 daemon 模式)
32
+ if (!isDaemon) {
33
+ setTimeout(async () => {
34
+ try {
35
+ await open(url);
36
+ console.log(chalk.green(`✅ 已在浏览器中打开: ${url}\n`));
37
+ } catch (err) {
38
+ console.log(chalk.yellow(`💡 请手动打开: ${url}\n`));
39
+ }
40
+ }, 1000);
41
+ }
42
+
43
+ // 处理退出信号(仅非 daemon 模式)
44
+ if (!isDaemon) {
45
+ process.on('SIGINT', async () => {
46
+ console.log(chalk.yellow('\n\n👋 正在停止服务器...\n'));
47
+
48
+ // 检查代理状态并询问是否停止
49
+ try {
50
+ const proxyStatus = getProxyStatus();
51
+ if (proxyStatus.running) {
52
+ console.log(chalk.yellow('⚠️ 检测到代理服务正在运行'));
53
+ console.log(chalk.gray(' - 代理端口: ' + proxyStatus.port));
54
+ console.log(chalk.gray(' - 如需保持代理运行,请直接关闭此窗口\n'));
55
+
56
+ // 自动停止代理(3秒后)
57
+ console.log(chalk.cyan('⏳ 将在 3 秒后自动停止代理服务...'));
58
+ console.log(chalk.gray(' 按 Ctrl+C 再次可立即退出并保持代理运行\n'));
59
+
60
+ let stopProxy = true;
61
+ const secondSigint = () => {
62
+ stopProxy = false;
63
+ process.off('SIGINT', secondSigint);
64
+ };
65
+ process.on('SIGINT', secondSigint);
66
+
67
+ await new Promise(resolve => setTimeout(resolve, 3000));
68
+ process.off('SIGINT', secondSigint);
69
+
70
+ if (stopProxy) {
71
+ const { stopProxyServer } = require('../server/proxy-server');
72
+ await stopProxyServer();
73
+ console.log(chalk.green('✅ 代理服务已停止\n'));
74
+ } else {
75
+ console.log(chalk.yellow('⚠️ 代理服务保持运行状态'));
76
+ console.log(chalk.gray(' - 如需停止,请运行: ctx proxy stop\n'));
77
+ }
78
+ }
79
+ } catch (err) {
80
+ // 忽略错误
81
+ }
82
+
83
+ console.log(chalk.green('✅ Web UI 已停止\n'));
84
+ process.exit(0);
85
+ });
86
+
87
+ console.log(chalk.gray('按 Ctrl+C 停止服务器'));
88
+ } else {
89
+ // Daemon 模式:保持运行
90
+ console.log(chalk.green(`✅ Coding-Tool 服务已在后台启动 (端口: ${port})`));
91
+ }
92
+
93
+ } catch (error) {
94
+ console.error(chalk.red('启动服务器失败:'), error.message);
95
+ process.exit(1);
96
+ }
97
+ }
98
+
99
+ module.exports = { handleUI };
@@ -0,0 +1,97 @@
1
+ const { spawn } = require('child_process');
2
+ const { promisify } = require('util');
3
+ const { exec } = require('child_process');
4
+ const semver = require('semver');
5
+ const chalk = require('chalk');
6
+ const packageInfo = require('../../package.json');
7
+
8
+ const execAsync = promisify(exec);
9
+
10
+ async function getLatestVersion(packageName) {
11
+ const { stdout } = await execAsync(`npm view ${packageName} version --json`, { timeout: 15000 });
12
+ const parsed = JSON.parse(stdout.trim());
13
+ if (typeof parsed === 'string') return parsed;
14
+ throw new Error('无法解析 npm 返回的版本号');
15
+ }
16
+
17
+ function runNpmInstall(packageName, version) {
18
+ return new Promise((resolve, reject) => {
19
+ const child = spawn('npm', ['install', '-g', `${packageName}@${version}`], {
20
+ stdio: 'inherit'
21
+ });
22
+
23
+ child.on('error', reject);
24
+ child.on('exit', (code) => {
25
+ if (code === 0) {
26
+ resolve();
27
+ } else {
28
+ reject(new Error(`npm install 失败,退出码 ${code}`));
29
+ }
30
+ });
31
+ });
32
+ }
33
+
34
+ async function handleUpdate(options = {}) {
35
+ const checkOnly = options.checkOnly === true;
36
+ const packageCandidates = Array.from(new Set([
37
+ packageInfo.name,
38
+ 'coding-tool-x'
39
+ ].filter(Boolean)));
40
+ const currentVersion = packageInfo.version;
41
+
42
+ console.log(chalk.cyan('\n🔍 检查更新中...\n'));
43
+
44
+ let latestVersion;
45
+ let packageName = packageCandidates[0];
46
+ try {
47
+ let lastError = null;
48
+ for (const candidate of packageCandidates) {
49
+ try {
50
+ latestVersion = await getLatestVersion(candidate);
51
+ packageName = candidate;
52
+ lastError = null;
53
+ break;
54
+ } catch (error) {
55
+ lastError = error;
56
+ }
57
+ }
58
+ if (!latestVersion) {
59
+ throw lastError || new Error('无法获取最新版本');
60
+ }
61
+ } catch (error) {
62
+ console.error(chalk.red(`❌ 检查更新失败: ${error.message}`));
63
+ console.log(chalk.gray('💡 可手动执行: npm view coding-tool-x version'));
64
+ return;
65
+ }
66
+
67
+ if (!semver.valid(currentVersion) || !semver.valid(latestVersion)) {
68
+ console.log(chalk.yellow(`⚠️ 版本格式异常,当前: ${currentVersion}, 最新: ${latestVersion}`));
69
+ return;
70
+ }
71
+
72
+ if (!semver.gt(latestVersion, currentVersion)) {
73
+ console.log(chalk.green(`✅ 已是最新版本: ${currentVersion}\n`));
74
+ return;
75
+ }
76
+
77
+ console.log(chalk.yellow(`发现新版本: ${currentVersion} -> ${latestVersion}`));
78
+ if (checkOnly) {
79
+ console.log(chalk.gray(`\n可执行更新命令: npm install -g ${packageName}@${latestVersion}\n`));
80
+ return;
81
+ }
82
+
83
+ console.log(chalk.cyan('\n⬇️ 正在更新...\n'));
84
+
85
+ try {
86
+ await runNpmInstall(packageName, latestVersion);
87
+ console.log(chalk.green(`\n✅ 更新完成: ${latestVersion}`));
88
+ console.log(chalk.gray('请重新打开终端或重新执行 ctx --version 验证版本。\n'));
89
+ } catch (error) {
90
+ console.error(chalk.red(`\n❌ 更新失败: ${error.message}`));
91
+ console.log(chalk.gray(`💡 可手动执行: npm install -g ${packageName}@${latestVersion}\n`));
92
+ }
93
+ }
94
+
95
+ module.exports = {
96
+ handleUpdate
97
+ };