@adversity/coding-tool-x 2.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.
- package/CHANGELOG.md +333 -0
- package/LICENSE +21 -0
- package/README.md +404 -0
- package/bin/ctx.js +8 -0
- package/dist/web/assets/index-D1AYlFLZ.js +3220 -0
- package/dist/web/assets/index-aL3cKxSK.css +41 -0
- package/dist/web/favicon.ico +0 -0
- package/dist/web/index.html +14 -0
- package/dist/web/logo.png +0 -0
- package/docs/CHANGELOG.md +582 -0
- package/docs/DIRECTORY_MIGRATION.md +112 -0
- package/docs/PROJECT_STRUCTURE.md +396 -0
- package/docs/bannel.png +0 -0
- package/docs/home.png +0 -0
- package/docs/logo.png +0 -0
- package/docs/multi-channel-load-balancing.md +249 -0
- package/package.json +73 -0
- package/src/commands/channels.js +504 -0
- package/src/commands/cli-type.js +99 -0
- package/src/commands/daemon.js +286 -0
- package/src/commands/doctor.js +332 -0
- package/src/commands/list.js +222 -0
- package/src/commands/logs.js +259 -0
- package/src/commands/port-config.js +115 -0
- package/src/commands/proxy-control.js +258 -0
- package/src/commands/proxy.js +152 -0
- package/src/commands/resume.js +137 -0
- package/src/commands/search.js +190 -0
- package/src/commands/stats.js +224 -0
- package/src/commands/switch.js +48 -0
- package/src/commands/toggle-proxy.js +222 -0
- package/src/commands/ui.js +92 -0
- package/src/commands/workspace.js +454 -0
- package/src/config/default.js +40 -0
- package/src/config/loader.js +75 -0
- package/src/config/paths.js +121 -0
- package/src/index.js +373 -0
- package/src/reset-config.js +92 -0
- package/src/server/api/agents.js +248 -0
- package/src/server/api/aliases.js +36 -0
- package/src/server/api/channels.js +258 -0
- package/src/server/api/claude-hooks.js +480 -0
- package/src/server/api/codex-channels.js +312 -0
- package/src/server/api/codex-projects.js +91 -0
- package/src/server/api/codex-proxy.js +182 -0
- package/src/server/api/codex-sessions.js +491 -0
- package/src/server/api/codex-statistics.js +57 -0
- package/src/server/api/commands.js +245 -0
- package/src/server/api/config-templates.js +182 -0
- package/src/server/api/config.js +147 -0
- package/src/server/api/convert.js +127 -0
- package/src/server/api/dashboard.js +125 -0
- package/src/server/api/env.js +144 -0
- package/src/server/api/favorites.js +77 -0
- package/src/server/api/gemini-channels.js +261 -0
- package/src/server/api/gemini-projects.js +91 -0
- package/src/server/api/gemini-proxy.js +160 -0
- package/src/server/api/gemini-sessions.js +397 -0
- package/src/server/api/gemini-statistics.js +57 -0
- package/src/server/api/health-check.js +118 -0
- package/src/server/api/mcp.js +336 -0
- package/src/server/api/pm2-autostart.js +269 -0
- package/src/server/api/projects.js +124 -0
- package/src/server/api/prompts.js +279 -0
- package/src/server/api/proxy.js +235 -0
- package/src/server/api/rules.js +271 -0
- package/src/server/api/sessions.js +595 -0
- package/src/server/api/settings.js +61 -0
- package/src/server/api/skills.js +305 -0
- package/src/server/api/statistics.js +91 -0
- package/src/server/api/terminal.js +202 -0
- package/src/server/api/ui-config.js +64 -0
- package/src/server/api/workspaces.js +407 -0
- package/src/server/codex-proxy-server.js +538 -0
- package/src/server/dev-server.js +26 -0
- package/src/server/gemini-proxy-server.js +518 -0
- package/src/server/index.js +305 -0
- package/src/server/proxy-server.js +469 -0
- package/src/server/services/agents-service.js +354 -0
- package/src/server/services/alias.js +71 -0
- package/src/server/services/channel-health.js +234 -0
- package/src/server/services/channel-scheduler.js +234 -0
- package/src/server/services/channels.js +347 -0
- package/src/server/services/codex-channels.js +625 -0
- package/src/server/services/codex-config.js +90 -0
- package/src/server/services/codex-parser.js +322 -0
- package/src/server/services/codex-sessions.js +665 -0
- package/src/server/services/codex-settings-manager.js +397 -0
- package/src/server/services/codex-speed-test-template.json +24 -0
- package/src/server/services/codex-statistics-service.js +255 -0
- package/src/server/services/commands-service.js +360 -0
- package/src/server/services/config-templates-service.js +732 -0
- package/src/server/services/env-checker.js +307 -0
- package/src/server/services/env-manager.js +300 -0
- package/src/server/services/favorites.js +163 -0
- package/src/server/services/gemini-channels.js +333 -0
- package/src/server/services/gemini-config.js +73 -0
- package/src/server/services/gemini-sessions.js +689 -0
- package/src/server/services/gemini-settings-manager.js +263 -0
- package/src/server/services/gemini-statistics-service.js +253 -0
- package/src/server/services/health-check.js +399 -0
- package/src/server/services/mcp-service.js +1188 -0
- package/src/server/services/prompts-service.js +492 -0
- package/src/server/services/proxy-runtime.js +79 -0
- package/src/server/services/pty-manager.js +435 -0
- package/src/server/services/rules-service.js +401 -0
- package/src/server/services/session-cache.js +127 -0
- package/src/server/services/session-converter.js +577 -0
- package/src/server/services/sessions.js +757 -0
- package/src/server/services/settings-manager.js +163 -0
- package/src/server/services/skill-service.js +965 -0
- package/src/server/services/speed-test.js +545 -0
- package/src/server/services/statistics-service.js +386 -0
- package/src/server/services/terminal-commands.js +155 -0
- package/src/server/services/terminal-config.js +140 -0
- package/src/server/services/terminal-detector.js +306 -0
- package/src/server/services/ui-config.js +130 -0
- package/src/server/services/workspace-service.js +662 -0
- package/src/server/utils/pricing.js +41 -0
- package/src/server/websocket-server.js +557 -0
- package/src/ui/menu.js +129 -0
- package/src/ui/prompts.js +100 -0
- package/src/utils/format.js +43 -0
- package/src/utils/port-helper.js +94 -0
- package/src/utils/session.js +239 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
const pm2 = require('pm2');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const { loadConfig } = require('../config/loader');
|
|
5
|
+
|
|
6
|
+
const PM2_APP_NAME = 'cc-tool';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 连接到 PM2
|
|
10
|
+
*/
|
|
11
|
+
function connectPM2() {
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
pm2.connect((err) => {
|
|
14
|
+
if (err) {
|
|
15
|
+
reject(err);
|
|
16
|
+
} else {
|
|
17
|
+
resolve();
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 断开 PM2 连接
|
|
25
|
+
*/
|
|
26
|
+
function disconnectPM2() {
|
|
27
|
+
pm2.disconnect();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 获取进程列表
|
|
32
|
+
*/
|
|
33
|
+
function getProcessList() {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
pm2.list((err, list) => {
|
|
36
|
+
if (err) {
|
|
37
|
+
reject(err);
|
|
38
|
+
} else {
|
|
39
|
+
resolve(list);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 获取 Coding-Tool 进程
|
|
47
|
+
*/
|
|
48
|
+
async function getCCToolProcess() {
|
|
49
|
+
const list = await getProcessList();
|
|
50
|
+
return list.find(proc => proc.name === PM2_APP_NAME);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 启动服务(后台)
|
|
55
|
+
*/
|
|
56
|
+
async function handleStart() {
|
|
57
|
+
try {
|
|
58
|
+
await connectPM2();
|
|
59
|
+
|
|
60
|
+
// 检查是否已经在运行
|
|
61
|
+
const existing = await getCCToolProcess();
|
|
62
|
+
if (existing && existing.pm2_env.status === 'online') {
|
|
63
|
+
console.log(chalk.yellow('\n⚠️ 服务已在运行中\n'));
|
|
64
|
+
console.log(chalk.gray(`进程 ID: ${existing.pid}`));
|
|
65
|
+
console.log(chalk.gray(`运行时长: ${formatUptime(existing.pm2_env.pm_uptime)}`));
|
|
66
|
+
console.log(chalk.gray('\n使用 ') + chalk.cyan('ctx status') + chalk.gray(' 查看详细状态'));
|
|
67
|
+
console.log(chalk.gray('使用 ') + chalk.cyan('ctx restart') + chalk.gray(' 重启服务\n'));
|
|
68
|
+
disconnectPM2();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const config = loadConfig();
|
|
73
|
+
const port = config.ports?.webUI || 10099;
|
|
74
|
+
|
|
75
|
+
// 启动 PM2 进程
|
|
76
|
+
pm2.start({
|
|
77
|
+
name: PM2_APP_NAME,
|
|
78
|
+
script: path.join(__dirname, '../index.js'),
|
|
79
|
+
args: ['ui', '--daemon'],
|
|
80
|
+
interpreter: 'node',
|
|
81
|
+
autorestart: true,
|
|
82
|
+
max_memory_restart: '500M',
|
|
83
|
+
env: {
|
|
84
|
+
NODE_ENV: 'production',
|
|
85
|
+
CC_TOOL_PORT: port
|
|
86
|
+
},
|
|
87
|
+
output: path.join(require('os').homedir(), '.claude/logs/cc-tool-out.log'),
|
|
88
|
+
error: path.join(require('os').homedir(), '.claude/logs/cc-tool-error.log'),
|
|
89
|
+
merge_logs: true,
|
|
90
|
+
log_date_format: 'YYYY-MM-DD HH:mm:ss'
|
|
91
|
+
}, (err) => {
|
|
92
|
+
if (err) {
|
|
93
|
+
console.error(chalk.red('\n❌ 启动服务失败:'), err.message);
|
|
94
|
+
disconnectPM2();
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
console.log(chalk.green('\n✅ Coding-Tool 服务已启动(后台运行)\n'));
|
|
99
|
+
console.log(chalk.gray(`Web UI: http://localhost:${port}`));
|
|
100
|
+
console.log(chalk.gray('\n可以安全关闭此终端窗口'));
|
|
101
|
+
console.log(chalk.gray('\n常用命令:'));
|
|
102
|
+
console.log(chalk.gray(' ') + chalk.cyan('ctx status') + chalk.gray(' - 查看服务状态'));
|
|
103
|
+
console.log(chalk.gray(' ') + chalk.cyan('ctx logs') + chalk.gray(' - 查看实时日志'));
|
|
104
|
+
console.log(chalk.gray(' ') + chalk.cyan('ctx stop') + chalk.gray(' - 停止服务\n'));
|
|
105
|
+
|
|
106
|
+
// 保存进程列表
|
|
107
|
+
pm2.dump((err) => {
|
|
108
|
+
disconnectPM2();
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error(chalk.red('启动失败:'), error.message);
|
|
113
|
+
disconnectPM2();
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* 停止服务
|
|
120
|
+
*/
|
|
121
|
+
async function handleStop() {
|
|
122
|
+
try {
|
|
123
|
+
await connectPM2();
|
|
124
|
+
|
|
125
|
+
const existing = await getCCToolProcess();
|
|
126
|
+
if (!existing) {
|
|
127
|
+
console.log(chalk.yellow('\n⚠️ 服务未在运行\n'));
|
|
128
|
+
disconnectPM2();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
pm2.stop(PM2_APP_NAME, (err) => {
|
|
133
|
+
if (err) {
|
|
134
|
+
console.error(chalk.red('\n❌ 停止服务失败:'), err.message);
|
|
135
|
+
disconnectPM2();
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// 删除进程
|
|
140
|
+
pm2.delete(PM2_APP_NAME, (err) => {
|
|
141
|
+
if (err) {
|
|
142
|
+
console.error(chalk.red('删除进程失败:'), err.message);
|
|
143
|
+
} else {
|
|
144
|
+
console.log(chalk.green('\n✅ Coding-Tool 服务已停止\n'));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
pm2.dump((err) => {
|
|
148
|
+
disconnectPM2();
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
} catch (error) {
|
|
153
|
+
console.error(chalk.red('停止失败:'), error.message);
|
|
154
|
+
disconnectPM2();
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* 重启服务
|
|
161
|
+
*/
|
|
162
|
+
async function handleRestart() {
|
|
163
|
+
try {
|
|
164
|
+
await connectPM2();
|
|
165
|
+
|
|
166
|
+
const existing = await getCCToolProcess();
|
|
167
|
+
if (!existing) {
|
|
168
|
+
console.log(chalk.yellow('\n⚠️ 服务未在运行,请使用 ') + chalk.cyan('ctx start') + chalk.yellow(' 启动\n'));
|
|
169
|
+
disconnectPM2();
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
pm2.restart(PM2_APP_NAME, (err) => {
|
|
174
|
+
if (err) {
|
|
175
|
+
console.error(chalk.red('\n❌ 重启服务失败:'), err.message);
|
|
176
|
+
disconnectPM2();
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
console.log(chalk.green('\n✅ Coding-Tool 服务已重启\n'));
|
|
181
|
+
|
|
182
|
+
pm2.dump((err) => {
|
|
183
|
+
disconnectPM2();
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
} catch (error) {
|
|
187
|
+
console.error(chalk.red('重启失败:'), error.message);
|
|
188
|
+
disconnectPM2();
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* 查看服务状态
|
|
195
|
+
*/
|
|
196
|
+
async function handleStatus() {
|
|
197
|
+
try {
|
|
198
|
+
await connectPM2();
|
|
199
|
+
|
|
200
|
+
const existing = await getCCToolProcess();
|
|
201
|
+
const config = loadConfig();
|
|
202
|
+
|
|
203
|
+
console.log(chalk.bold.cyan('\n╔══════════════════════════════════════╗'));
|
|
204
|
+
console.log(chalk.bold.cyan('║ Coding-Tool 服务状态 ║'));
|
|
205
|
+
console.log(chalk.bold.cyan('╚══════════════════════════════════════╝\n'));
|
|
206
|
+
|
|
207
|
+
// UI 服务状态
|
|
208
|
+
console.log(chalk.bold('📱 Web UI 服务:'));
|
|
209
|
+
if (existing && existing.pm2_env.status === 'online') {
|
|
210
|
+
console.log(chalk.green(' ✅ 状态: 运行中'));
|
|
211
|
+
console.log(chalk.gray(` 🌐 地址: http://localhost:${config.ports?.webUI || 10099}`));
|
|
212
|
+
console.log(chalk.gray(` 🔑 进程 ID: ${existing.pid}`));
|
|
213
|
+
console.log(chalk.gray(` ⏱️ 运行时长: ${formatUptime(existing.pm2_env.pm_uptime)}`));
|
|
214
|
+
console.log(chalk.gray(` 💾 内存使用: ${formatMemory(existing.monit?.memory)}`));
|
|
215
|
+
console.log(chalk.gray(` 🔄 重启次数: ${existing.pm2_env.restart_time}`));
|
|
216
|
+
} else {
|
|
217
|
+
console.log(chalk.gray(' ❌ 状态: 未运行'));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// 代理服务状态(从运行时文件检测)
|
|
221
|
+
const fs = require('fs');
|
|
222
|
+
const os = require('os');
|
|
223
|
+
const claudeActive = fs.existsSync(path.join(os.homedir(), '.claude/.proxy-active-claude'));
|
|
224
|
+
const codexActive = fs.existsSync(path.join(os.homedir(), '.claude/.proxy-active-codex'));
|
|
225
|
+
const geminiActive = fs.existsSync(path.join(os.homedir(), '.claude/.proxy-active-gemini'));
|
|
226
|
+
|
|
227
|
+
console.log(chalk.bold('\n🔌 代理服务:'));
|
|
228
|
+
|
|
229
|
+
console.log(chalk.gray(' Claude: ') + (claudeActive ? chalk.green('✅ 运行中') : chalk.gray('⏹️ 未启动')) +
|
|
230
|
+
chalk.gray(` (http://localhost:${config.ports?.proxy || 10088})`));
|
|
231
|
+
|
|
232
|
+
console.log(chalk.gray(' Codex: ') + (codexActive ? chalk.green('✅ 运行中') : chalk.gray('⏹️ 未启动')) +
|
|
233
|
+
chalk.gray(` (http://localhost:${config.ports?.codexProxy || 10089})`));
|
|
234
|
+
|
|
235
|
+
console.log(chalk.gray(' Gemini: ') + (geminiActive ? chalk.green('✅ 运行中') : chalk.gray('⏹️ 未启动')) +
|
|
236
|
+
chalk.gray(` (http://localhost:${config.ports?.geminiProxy || 10090})`));
|
|
237
|
+
|
|
238
|
+
console.log(chalk.bold('\n💡 提示:'));
|
|
239
|
+
console.log(chalk.gray(' • 代理服务通过 Web UI 界面控制'));
|
|
240
|
+
console.log(chalk.gray(' • 使用 ') + chalk.cyan('ctx logs [type]') + chalk.gray(' 查看日志'));
|
|
241
|
+
console.log(chalk.gray(' • 使用 ') + chalk.cyan('ctx stats [type]') + chalk.gray(' 查看统计信息\n'));
|
|
242
|
+
|
|
243
|
+
disconnectPM2();
|
|
244
|
+
} catch (error) {
|
|
245
|
+
console.error(chalk.red('查询状态失败:'), error.message);
|
|
246
|
+
disconnectPM2();
|
|
247
|
+
process.exit(1);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* 格式化运行时长
|
|
253
|
+
*/
|
|
254
|
+
function formatUptime(startTime) {
|
|
255
|
+
const uptime = Date.now() - startTime;
|
|
256
|
+
const seconds = Math.floor(uptime / 1000);
|
|
257
|
+
const minutes = Math.floor(seconds / 60);
|
|
258
|
+
const hours = Math.floor(minutes / 60);
|
|
259
|
+
const days = Math.floor(hours / 24);
|
|
260
|
+
|
|
261
|
+
if (days > 0) {
|
|
262
|
+
return `${days}天 ${hours % 24}小时`;
|
|
263
|
+
} else if (hours > 0) {
|
|
264
|
+
return `${hours}小时 ${minutes % 60}分钟`;
|
|
265
|
+
} else if (minutes > 0) {
|
|
266
|
+
return `${minutes}分钟`;
|
|
267
|
+
} else {
|
|
268
|
+
return `${seconds}秒`;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* 格式化内存使用
|
|
274
|
+
*/
|
|
275
|
+
function formatMemory(bytes) {
|
|
276
|
+
if (!bytes) return '0 MB';
|
|
277
|
+
const mb = bytes / 1024 / 1024;
|
|
278
|
+
return `${mb.toFixed(2)} MB`;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
module.exports = {
|
|
282
|
+
handleStart,
|
|
283
|
+
handleStop,
|
|
284
|
+
handleRestart,
|
|
285
|
+
handleStatus
|
|
286
|
+
};
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const { exec } = require('child_process');
|
|
6
|
+
const { promisify } = require('util');
|
|
7
|
+
const { loadConfig } = require('../config/loader');
|
|
8
|
+
const { isPortInUse } = require('../utils/port-helper');
|
|
9
|
+
|
|
10
|
+
const execAsync = promisify(exec);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 诊断系统
|
|
14
|
+
*/
|
|
15
|
+
async function handleDoctor() {
|
|
16
|
+
console.log(chalk.bold.cyan('\n╔══════════════════════════════════════╗'));
|
|
17
|
+
console.log(chalk.bold.cyan('║ Coding-Tool 系统诊断 ║'));
|
|
18
|
+
console.log(chalk.bold.cyan('╚══════════════════════════════════════╝\n'));
|
|
19
|
+
|
|
20
|
+
const checks = [];
|
|
21
|
+
|
|
22
|
+
// 1. 检查 Node.js 版本
|
|
23
|
+
checks.push(await checkNodeVersion());
|
|
24
|
+
|
|
25
|
+
// 2. 检查配置文件
|
|
26
|
+
checks.push(await checkConfigFiles());
|
|
27
|
+
|
|
28
|
+
// 3. 检查端口
|
|
29
|
+
checks.push(await checkPorts());
|
|
30
|
+
|
|
31
|
+
// 4. 检查 Claude Code 配置
|
|
32
|
+
checks.push(await checkClaudeConfig());
|
|
33
|
+
|
|
34
|
+
// 5. 检查日志目录
|
|
35
|
+
checks.push(await checkLogsDirectory());
|
|
36
|
+
|
|
37
|
+
// 6. 检查进程状态
|
|
38
|
+
checks.push(await checkProcessStatus());
|
|
39
|
+
|
|
40
|
+
// 7. 检查磁盘空间
|
|
41
|
+
checks.push(await checkDiskSpace());
|
|
42
|
+
|
|
43
|
+
// 显示结果
|
|
44
|
+
console.log(chalk.bold('\n📋 诊断结果:\n'));
|
|
45
|
+
|
|
46
|
+
let passedCount = 0;
|
|
47
|
+
let warningCount = 0;
|
|
48
|
+
let failedCount = 0;
|
|
49
|
+
|
|
50
|
+
checks.forEach(check => {
|
|
51
|
+
const icon = check.status === 'pass' ? chalk.green('✅') :
|
|
52
|
+
check.status === 'warning' ? chalk.yellow('⚠️') :
|
|
53
|
+
chalk.red('❌');
|
|
54
|
+
|
|
55
|
+
console.log(`${icon} ${check.name}`);
|
|
56
|
+
if (check.message) {
|
|
57
|
+
console.log(chalk.gray(` ${check.message}`));
|
|
58
|
+
}
|
|
59
|
+
if (check.suggestion) {
|
|
60
|
+
console.log(chalk.cyan(` 💡 ${check.suggestion}`));
|
|
61
|
+
}
|
|
62
|
+
console.log('');
|
|
63
|
+
|
|
64
|
+
if (check.status === 'pass') passedCount++;
|
|
65
|
+
else if (check.status === 'warning') warningCount++;
|
|
66
|
+
else failedCount++;
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// 总结
|
|
70
|
+
console.log(chalk.bold('📊 总结:'));
|
|
71
|
+
console.log(chalk.green(` ✅ 通过: ${passedCount}`));
|
|
72
|
+
if (warningCount > 0) {
|
|
73
|
+
console.log(chalk.yellow(` ⚠️ 警告: ${warningCount}`));
|
|
74
|
+
}
|
|
75
|
+
if (failedCount > 0) {
|
|
76
|
+
console.log(chalk.red(` ❌ 失败: ${failedCount}`));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
console.log('');
|
|
80
|
+
|
|
81
|
+
if (failedCount > 0) {
|
|
82
|
+
console.log(chalk.red('⚠️ 发现问题,请根据上述建议进行修复\n'));
|
|
83
|
+
} else if (warningCount > 0) {
|
|
84
|
+
console.log(chalk.yellow('⚠️ 发现一些警告,建议查看并处理\n'));
|
|
85
|
+
} else {
|
|
86
|
+
console.log(chalk.green('✅ 系统运行正常!\n'));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* 检查 Node.js 版本
|
|
92
|
+
*/
|
|
93
|
+
async function checkNodeVersion() {
|
|
94
|
+
const version = process.version;
|
|
95
|
+
const major = parseInt(version.slice(1).split('.')[0]);
|
|
96
|
+
|
|
97
|
+
if (major >= 14) {
|
|
98
|
+
return {
|
|
99
|
+
name: 'Node.js 版本',
|
|
100
|
+
status: 'pass',
|
|
101
|
+
message: `当前版本: ${version}`
|
|
102
|
+
};
|
|
103
|
+
} else {
|
|
104
|
+
return {
|
|
105
|
+
name: 'Node.js 版本',
|
|
106
|
+
status: 'fail',
|
|
107
|
+
message: `当前版本: ${version}`,
|
|
108
|
+
suggestion: '需要 Node.js 14.0.0 或更高版本'
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 检查配置文件
|
|
115
|
+
*/
|
|
116
|
+
async function checkConfigFiles() {
|
|
117
|
+
const configPath = path.join(os.homedir(), '.claude/config.json');
|
|
118
|
+
const exists = fs.existsSync(configPath);
|
|
119
|
+
|
|
120
|
+
if (exists) {
|
|
121
|
+
try {
|
|
122
|
+
const config = loadConfig();
|
|
123
|
+
return {
|
|
124
|
+
name: '配置文件',
|
|
125
|
+
status: 'pass',
|
|
126
|
+
message: `配置文件正常 (${configPath})`
|
|
127
|
+
};
|
|
128
|
+
} catch (err) {
|
|
129
|
+
return {
|
|
130
|
+
name: '配置文件',
|
|
131
|
+
status: 'fail',
|
|
132
|
+
message: '配置文件存在但解析失败',
|
|
133
|
+
suggestion: '使用 ctx config reset 重置配置'
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
return {
|
|
138
|
+
name: '配置文件',
|
|
139
|
+
status: 'warning',
|
|
140
|
+
message: '配置文件不存在,将使用默认配置',
|
|
141
|
+
suggestion: '首次运行时会自动创建'
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* 检查端口占用
|
|
148
|
+
*/
|
|
149
|
+
async function checkPorts() {
|
|
150
|
+
const config = loadConfig();
|
|
151
|
+
const ports = {
|
|
152
|
+
'Web UI': config.ports?.webUI || 10099,
|
|
153
|
+
'Claude Proxy': config.ports?.proxy || 10088,
|
|
154
|
+
'Codex Proxy': config.ports?.codexProxy || 10089,
|
|
155
|
+
'Gemini Proxy': config.ports?.geminiProxy || 10090
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const conflicts = [];
|
|
159
|
+
|
|
160
|
+
for (const [name, port] of Object.entries(ports)) {
|
|
161
|
+
const inUse = await isPortInUse(port);
|
|
162
|
+
if (inUse) {
|
|
163
|
+
conflicts.push(`${name} (${port})`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (conflicts.length === 0) {
|
|
168
|
+
return {
|
|
169
|
+
name: '端口检查',
|
|
170
|
+
status: 'pass',
|
|
171
|
+
message: '所有端口可用'
|
|
172
|
+
};
|
|
173
|
+
} else {
|
|
174
|
+
return {
|
|
175
|
+
name: '端口检查',
|
|
176
|
+
status: 'warning',
|
|
177
|
+
message: `以下端口被占用: ${conflicts.join(', ')}`,
|
|
178
|
+
suggestion: '如果服务正在运行这是正常的;否则使用 ctx config port 修改端口'
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* 检查 Claude Code 配置
|
|
185
|
+
*/
|
|
186
|
+
async function checkClaudeConfig() {
|
|
187
|
+
const settingsPath = path.join(os.homedir(), '.claude/settings.json');
|
|
188
|
+
const exists = fs.existsSync(settingsPath);
|
|
189
|
+
|
|
190
|
+
if (exists) {
|
|
191
|
+
return {
|
|
192
|
+
name: 'Claude Code 配置',
|
|
193
|
+
status: 'pass',
|
|
194
|
+
message: 'Claude Code 配置文件存在'
|
|
195
|
+
};
|
|
196
|
+
} else {
|
|
197
|
+
return {
|
|
198
|
+
name: 'Claude Code 配置',
|
|
199
|
+
status: 'warning',
|
|
200
|
+
message: 'Claude Code 配置文件不存在',
|
|
201
|
+
suggestion: '请至少运行一次 Claude Code 以生成配置文件'
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* 检查日志目录
|
|
208
|
+
*/
|
|
209
|
+
async function checkLogsDirectory() {
|
|
210
|
+
const logsDir = path.join(os.homedir(), '.claude/logs');
|
|
211
|
+
const exists = fs.existsSync(logsDir);
|
|
212
|
+
|
|
213
|
+
if (exists) {
|
|
214
|
+
// 检查日志文件大小
|
|
215
|
+
const files = fs.readdirSync(logsDir);
|
|
216
|
+
let totalSize = 0;
|
|
217
|
+
|
|
218
|
+
files.forEach(file => {
|
|
219
|
+
try {
|
|
220
|
+
const stats = fs.statSync(path.join(logsDir, file));
|
|
221
|
+
totalSize += stats.size;
|
|
222
|
+
} catch (err) {
|
|
223
|
+
// 忽略错误
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
const sizeMB = (totalSize / 1024 / 1024).toFixed(2);
|
|
228
|
+
|
|
229
|
+
if (totalSize > 100 * 1024 * 1024) { // 超过 100MB
|
|
230
|
+
return {
|
|
231
|
+
name: '日志目录',
|
|
232
|
+
status: 'warning',
|
|
233
|
+
message: `日志文件过大 (${sizeMB} MB)`,
|
|
234
|
+
suggestion: '使用 ctx logs --clear 清理日志'
|
|
235
|
+
};
|
|
236
|
+
} else {
|
|
237
|
+
return {
|
|
238
|
+
name: '日志目录',
|
|
239
|
+
status: 'pass',
|
|
240
|
+
message: `日志目录正常 (${sizeMB} MB)`
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
} else {
|
|
244
|
+
return {
|
|
245
|
+
name: '日志目录',
|
|
246
|
+
status: 'warning',
|
|
247
|
+
message: '日志目录不存在',
|
|
248
|
+
suggestion: '首次运行时会自动创建'
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* 检查进程状态
|
|
255
|
+
*/
|
|
256
|
+
async function checkProcessStatus() {
|
|
257
|
+
try {
|
|
258
|
+
const { exec } = require('child_process');
|
|
259
|
+
const { promisify } = require('util');
|
|
260
|
+
const execAsync = promisify(exec);
|
|
261
|
+
|
|
262
|
+
// 检查是否有 PM2 进程
|
|
263
|
+
try {
|
|
264
|
+
const { stdout } = await execAsync('pm2 list');
|
|
265
|
+
if (stdout.includes('cc-tool')) {
|
|
266
|
+
return {
|
|
267
|
+
name: '进程状态',
|
|
268
|
+
status: 'pass',
|
|
269
|
+
message: 'Coding-Tool 进程运行中'
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
} catch (err) {
|
|
273
|
+
// PM2 未安装或没有进程
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return {
|
|
277
|
+
name: '进程状态',
|
|
278
|
+
status: 'warning',
|
|
279
|
+
message: 'Coding-Tool 服务未运行',
|
|
280
|
+
suggestion: '使用 ctx start 启动服务'
|
|
281
|
+
};
|
|
282
|
+
} catch (err) {
|
|
283
|
+
return {
|
|
284
|
+
name: '进程状态',
|
|
285
|
+
status: 'warning',
|
|
286
|
+
message: '无法检查进程状态',
|
|
287
|
+
suggestion: '请手动检查'
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* 检查磁盘空间
|
|
294
|
+
*/
|
|
295
|
+
async function checkDiskSpace() {
|
|
296
|
+
try {
|
|
297
|
+
const { stdout } = await execAsync('df -h ~');
|
|
298
|
+
const lines = stdout.trim().split('\n');
|
|
299
|
+
if (lines.length > 1) {
|
|
300
|
+
const parts = lines[1].split(/\s+/);
|
|
301
|
+
const usage = parts[4];
|
|
302
|
+
const usagePercent = parseInt(usage);
|
|
303
|
+
|
|
304
|
+
if (usagePercent > 90) {
|
|
305
|
+
return {
|
|
306
|
+
name: '磁盘空间',
|
|
307
|
+
status: 'warning',
|
|
308
|
+
message: `磁盘使用率: ${usage}`,
|
|
309
|
+
suggestion: '磁盘空间不足,建议清理'
|
|
310
|
+
};
|
|
311
|
+
} else {
|
|
312
|
+
return {
|
|
313
|
+
name: '磁盘空间',
|
|
314
|
+
status: 'pass',
|
|
315
|
+
message: `磁盘使用率: ${usage}`
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
} catch (err) {
|
|
320
|
+
// Windows 或其他系统可能没有 df 命令
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return {
|
|
324
|
+
name: '磁盘空间',
|
|
325
|
+
status: 'pass',
|
|
326
|
+
message: '磁盘空间检查跳过'
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
module.exports = {
|
|
331
|
+
handleDoctor
|
|
332
|
+
};
|