cfix 1.0.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/.env.example +69 -0
- package/README.md +1590 -0
- package/bin/cfix +14 -0
- package/bin/cfix.cmd +6 -0
- package/cli/commands/config.js +58 -0
- package/cli/commands/doctor.js +240 -0
- package/cli/commands/fix.js +211 -0
- package/cli/commands/help.js +62 -0
- package/cli/commands/init.js +226 -0
- package/cli/commands/logs.js +161 -0
- package/cli/commands/monitor.js +151 -0
- package/cli/commands/project.js +331 -0
- package/cli/commands/service.js +133 -0
- package/cli/commands/status.js +115 -0
- package/cli/commands/task.js +412 -0
- package/cli/commands/version.js +19 -0
- package/cli/index.js +269 -0
- package/cli/lib/config-manager.js +612 -0
- package/cli/lib/formatter.js +224 -0
- package/cli/lib/process-manager.js +233 -0
- package/cli/lib/service-client.js +271 -0
- package/cli/scripts/install-completion.js +133 -0
- package/package.json +85 -0
- package/public/monitor.html +1096 -0
- package/scripts/completion.bash +87 -0
- package/scripts/completion.zsh +102 -0
- package/src/assets/README.md +32 -0
- package/src/assets/error.png +0 -0
- package/src/assets/icon.png +0 -0
- package/src/assets/success.png +0 -0
- package/src/claude-cli-service.js +216 -0
- package/src/config/index.js +69 -0
- package/src/database/manager.js +391 -0
- package/src/database/migration.js +252 -0
- package/src/git-service.js +1278 -0
- package/src/index.js +1658 -0
- package/src/logger.js +139 -0
- package/src/metrics/collector.js +184 -0
- package/src/middleware/auth.js +86 -0
- package/src/middleware/rate-limit.js +85 -0
- package/src/queue/integration-example.js +283 -0
- package/src/queue/task-queue.js +333 -0
- package/src/services/notification-limiter.js +48 -0
- package/src/services/notification-service.js +115 -0
- package/src/services/system-notifier.js +130 -0
- package/src/task-manager.js +289 -0
- package/src/utils/exec.js +87 -0
- package/src/utils/project-lock.js +246 -0
- package/src/utils/retry.js +110 -0
- package/src/utils/sanitizer.js +174 -0
- package/src/websocket/notifier.js +363 -0
- package/src/wechat-notifier.js +97 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 初始化命令 - 创建项目配置文件
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const inquirer = require('inquirer');
|
|
8
|
+
const config = require('../lib/config-manager');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 初始化项目配置
|
|
12
|
+
*/
|
|
13
|
+
async function handle(projectPath) {
|
|
14
|
+
const targetPath = projectPath ? path.resolve(projectPath) : process.cwd();
|
|
15
|
+
|
|
16
|
+
console.log('🚀 初始化 CodeFix 项目配置');
|
|
17
|
+
console.log('');
|
|
18
|
+
console.log('======================================');
|
|
19
|
+
console.log(`路径: ${targetPath}`);
|
|
20
|
+
console.log('======================================');
|
|
21
|
+
console.log('');
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
// 检查路径是否存在
|
|
25
|
+
if (!fs.existsSync(targetPath)) {
|
|
26
|
+
throw new Error(`路径不存在: ${targetPath}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const configFile = path.join(targetPath, '.cfix.json');
|
|
30
|
+
|
|
31
|
+
// 检查是否已存在配置文件
|
|
32
|
+
if (fs.existsSync(configFile)) {
|
|
33
|
+
console.log('⚠️ 配置文件已存在');
|
|
34
|
+
console.log('');
|
|
35
|
+
|
|
36
|
+
const { overwrite } = await inquirer.prompt([
|
|
37
|
+
{
|
|
38
|
+
type: 'confirm',
|
|
39
|
+
name: 'overwrite',
|
|
40
|
+
message: '是否覆盖现有配置?',
|
|
41
|
+
default: false
|
|
42
|
+
}
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
if (!overwrite) {
|
|
46
|
+
console.log('❌ 已取消');
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 检测项目信息
|
|
52
|
+
const projectName = path.basename(targetPath);
|
|
53
|
+
const isGitRepo = fs.existsSync(path.join(targetPath, '.git'));
|
|
54
|
+
|
|
55
|
+
console.log('检测到项目信息:');
|
|
56
|
+
console.log(` 名称: ${projectName}`);
|
|
57
|
+
console.log(` Git 仓库: ${isGitRepo ? '是' : '否'}`);
|
|
58
|
+
console.log('');
|
|
59
|
+
|
|
60
|
+
// 交互式配置
|
|
61
|
+
const answers = await inquirer.prompt([
|
|
62
|
+
{
|
|
63
|
+
type: 'input',
|
|
64
|
+
name: 'name',
|
|
65
|
+
message: '项目名称:',
|
|
66
|
+
default: projectName
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
type: 'input',
|
|
70
|
+
name: 'repoUrl',
|
|
71
|
+
message: 'Git 仓库 URL (可选):',
|
|
72
|
+
default: isGitRepo ? detectRepoUrl(targetPath) : '',
|
|
73
|
+
when: () => isGitRepo
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
type: 'list',
|
|
77
|
+
name: 'defaultBranch',
|
|
78
|
+
message: '默认分支:',
|
|
79
|
+
choices: ['main', 'master', 'develop'],
|
|
80
|
+
default: 'main'
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
type: 'confirm',
|
|
84
|
+
name: 'enableTests',
|
|
85
|
+
message: '启用测试?',
|
|
86
|
+
default: true
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
type: 'input',
|
|
90
|
+
name: 'testCommand',
|
|
91
|
+
message: '测试命令:',
|
|
92
|
+
default: 'npm test',
|
|
93
|
+
when: (ans) => ans.enableTests
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
type: 'list',
|
|
97
|
+
name: 'aiEngine',
|
|
98
|
+
message: 'AI 引擎:',
|
|
99
|
+
choices: [
|
|
100
|
+
{ name: 'Claude CLI (默认)', value: 'claude-cli' },
|
|
101
|
+
{ name: 'Claude API', value: 'claude-api' },
|
|
102
|
+
{ name: 'Zhihu GLM', value: 'glm' }
|
|
103
|
+
],
|
|
104
|
+
default: 'claude-cli'
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
type: 'input',
|
|
108
|
+
name: 'maxTurns',
|
|
109
|
+
message: '最大对话轮次:',
|
|
110
|
+
default: '10',
|
|
111
|
+
validate: (input) => {
|
|
112
|
+
const num = parseInt(input);
|
|
113
|
+
return num > 0 && num <= 50 ? true : '请输入 1-50 之间的数字';
|
|
114
|
+
},
|
|
115
|
+
filter: (input) => parseInt(input)
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
type: 'confirm',
|
|
119
|
+
name: 'autoMerge',
|
|
120
|
+
message: '自动合并到默认分支?',
|
|
121
|
+
default: false
|
|
122
|
+
}
|
|
123
|
+
]);
|
|
124
|
+
|
|
125
|
+
// 构建配置对象
|
|
126
|
+
const projectConfig = {
|
|
127
|
+
name: answers.name,
|
|
128
|
+
repoUrl: answers.repoUrl || undefined,
|
|
129
|
+
defaultBranch: answers.defaultBranch,
|
|
130
|
+
ai: {
|
|
131
|
+
engine: answers.aiEngine,
|
|
132
|
+
maxTurns: answers.maxTurns
|
|
133
|
+
},
|
|
134
|
+
git: {
|
|
135
|
+
autoMerge: answers.autoMerge
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
if (answers.enableTests) {
|
|
140
|
+
projectConfig.tests = {
|
|
141
|
+
enabled: true,
|
|
142
|
+
command: answers.testCommand
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// 写入配置文件
|
|
147
|
+
fs.writeFileSync(
|
|
148
|
+
configFile,
|
|
149
|
+
JSON.stringify(projectConfig, null, 2),
|
|
150
|
+
'utf-8'
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
console.log('');
|
|
154
|
+
console.log('======================================');
|
|
155
|
+
console.log('✅ 配置文件已创建');
|
|
156
|
+
console.log('======================================');
|
|
157
|
+
console.log('');
|
|
158
|
+
console.log('文件位置:');
|
|
159
|
+
console.log(` ${configFile}`);
|
|
160
|
+
console.log('');
|
|
161
|
+
console.log('配置内容:');
|
|
162
|
+
console.log(JSON.stringify(projectConfig, null, 2).split('\n').map(line => ' ' + line).join('\n'));
|
|
163
|
+
console.log('');
|
|
164
|
+
|
|
165
|
+
// 询问是否添加到项目注册表
|
|
166
|
+
if (config.findProject(targetPath)) {
|
|
167
|
+
console.log('项目已在注册表中');
|
|
168
|
+
} else {
|
|
169
|
+
const { addToRegistry } = await inquirer.prompt([
|
|
170
|
+
{
|
|
171
|
+
type: 'confirm',
|
|
172
|
+
name: 'addToRegistry',
|
|
173
|
+
message: '是否添加到项目注册表?',
|
|
174
|
+
default: true
|
|
175
|
+
}
|
|
176
|
+
]);
|
|
177
|
+
|
|
178
|
+
if (addToRegistry) {
|
|
179
|
+
const project = {
|
|
180
|
+
path: targetPath,
|
|
181
|
+
name: projectConfig.name,
|
|
182
|
+
repoUrl: projectConfig.repoUrl,
|
|
183
|
+
defaultBranch: projectConfig.defaultBranch,
|
|
184
|
+
createdAt: new Date().toISOString()
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
config.addProject(project);
|
|
188
|
+
console.log('✅ 已添加到项目注册表');
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
console.log('');
|
|
193
|
+
console.log('提示:');
|
|
194
|
+
console.log(` cfix "<需求>" # 在当前目录创建任务`);
|
|
195
|
+
console.log(` cfix project info "${projectConfig.name}" # 查看项目信息`);
|
|
196
|
+
console.log(` cfix config edit # 修改配置`);
|
|
197
|
+
|
|
198
|
+
} catch (error) {
|
|
199
|
+
console.error(`❌ 初始化失败: ${error.message}`);
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* 检测 Git 仓库 URL
|
|
206
|
+
*/
|
|
207
|
+
function detectRepoUrl(projectPath) {
|
|
208
|
+
try {
|
|
209
|
+
const gitConfig = path.join(projectPath, '.git', 'config');
|
|
210
|
+
if (!fs.existsSync(gitConfig)) {
|
|
211
|
+
return '';
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const content = fs.readFileSync(gitConfig, 'utf-8');
|
|
215
|
+
const match = content.match(/url = (.+)/);
|
|
216
|
+
if (match) {
|
|
217
|
+
return match[1].trim();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return '';
|
|
221
|
+
} catch {
|
|
222
|
+
return '';
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
module.exports = { handle };
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 日志命令
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const { spawn } = require('child_process');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const config = require('../lib/config-manager');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 查看日志
|
|
12
|
+
*/
|
|
13
|
+
async function handle(options) {
|
|
14
|
+
const rootDir = process.env.CFIX_ROOT || path.resolve(__dirname, '../..');
|
|
15
|
+
const logDir = path.join(rootDir, 'logs');
|
|
16
|
+
|
|
17
|
+
// 检查日志目录
|
|
18
|
+
if (!fs.existsSync(logDir)) {
|
|
19
|
+
console.log('📁 日志目录不存在');
|
|
20
|
+
console.log('');
|
|
21
|
+
console.log('提示: 启动服务后会自动创建日志目录');
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// 获取日志文件
|
|
26
|
+
const logFiles = fs.readdirSync(logDir)
|
|
27
|
+
.filter(file => file.endsWith('.log'))
|
|
28
|
+
.sort()
|
|
29
|
+
.reverse();
|
|
30
|
+
|
|
31
|
+
if (logFiles.length === 0) {
|
|
32
|
+
console.log('📄 没有找到日志文件');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 如果是错误日志模式
|
|
37
|
+
if (options.errors) {
|
|
38
|
+
showErrors(logDir);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 如果是跟踪模式
|
|
43
|
+
if (options.follow) {
|
|
44
|
+
followLog(logDir);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 显示日志文件列表
|
|
49
|
+
console.log('📄 日志文件:');
|
|
50
|
+
console.log('');
|
|
51
|
+
|
|
52
|
+
logFiles.forEach((file, index) => {
|
|
53
|
+
const filePath = path.join(logDir, file);
|
|
54
|
+
const stats = fs.statSync(filePath);
|
|
55
|
+
const size = formatBytes(stats.size);
|
|
56
|
+
const mtime = stats.mtime.toLocaleString('zh-CN');
|
|
57
|
+
|
|
58
|
+
console.log(`${index + 1}. ${file}`);
|
|
59
|
+
console.log(` 大小: ${size} | 修改时间: ${mtime}`);
|
|
60
|
+
console.log(` 路径: ${filePath}`);
|
|
61
|
+
console.log('');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
console.log('提示:');
|
|
65
|
+
console.log(' cfix logs --follow # 跟踪最新日志');
|
|
66
|
+
console.log(' cfix logs --errors # 查看错误日志');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 跟踪日志
|
|
71
|
+
*/
|
|
72
|
+
function followLog(logDir) {
|
|
73
|
+
console.log('📋 实时日志 (按 Ctrl+C 退出)');
|
|
74
|
+
console.log('======================================');
|
|
75
|
+
console.log('');
|
|
76
|
+
|
|
77
|
+
// 查找最新的综合日志文件
|
|
78
|
+
const logFiles = fs.readdirSync(logDir)
|
|
79
|
+
.filter(file => file.startsWith('combined-') && file.endsWith('.log'))
|
|
80
|
+
.sort()
|
|
81
|
+
.reverse();
|
|
82
|
+
|
|
83
|
+
if (logFiles.length === 0) {
|
|
84
|
+
console.log('没有找到日志文件');
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const latestLog = path.join(logDir, logFiles[0]);
|
|
89
|
+
|
|
90
|
+
// 使用 tail -f 跟踪日志
|
|
91
|
+
const tail = spawn('tail', ['-f', latestLog], {
|
|
92
|
+
stdio: 'inherit'
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
tail.on('error', (error) => {
|
|
96
|
+
console.error('无法跟踪日志:', error.message);
|
|
97
|
+
console.log('');
|
|
98
|
+
console.log('提示: 在 Windows 上可能需要安装 Git Bash 或使用 WSL');
|
|
99
|
+
process.exit(1);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// 处理退出
|
|
103
|
+
process.on('SIGINT', () => {
|
|
104
|
+
tail.kill();
|
|
105
|
+
process.exit(0);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 显示错误日志
|
|
111
|
+
*/
|
|
112
|
+
function showErrors(logDir) {
|
|
113
|
+
console.log('❌ 错误日志 (最近 50 行)');
|
|
114
|
+
console.log('======================================');
|
|
115
|
+
console.log('');
|
|
116
|
+
|
|
117
|
+
// 查找错误日志文件
|
|
118
|
+
const errorFiles = fs.readdirSync(logDir)
|
|
119
|
+
.filter(file => file.startsWith('error-') && file.endsWith('.log'))
|
|
120
|
+
.sort()
|
|
121
|
+
.reverse();
|
|
122
|
+
|
|
123
|
+
if (errorFiles.length === 0) {
|
|
124
|
+
console.log('没有找到错误日志');
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const latestErrorLog = path.join(logDir, errorFiles[0]);
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
const content = fs.readFileSync(latestErrorLog, 'utf-8');
|
|
132
|
+
const lines = content.split('\n').filter(line => line.trim());
|
|
133
|
+
|
|
134
|
+
if (lines.length === 0) {
|
|
135
|
+
console.log('错误日志为空');
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// 显示最后 50 行
|
|
140
|
+
const recentLines = lines.slice(-50);
|
|
141
|
+
recentLines.forEach(line => {
|
|
142
|
+
console.log(line);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
} catch (error) {
|
|
146
|
+
console.error('读取错误日志失败:', error.message);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* 格式化字节数
|
|
152
|
+
*/
|
|
153
|
+
function formatBytes(bytes) {
|
|
154
|
+
if (bytes === 0) return '0 B';
|
|
155
|
+
const k = 1024;
|
|
156
|
+
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
157
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
158
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
module.exports = { handle };
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 监控面板命令
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const { spawn } = require('child_process');
|
|
6
|
+
const config = require('../lib/config-manager');
|
|
7
|
+
const ProcessManager = require('../lib/process-manager');
|
|
8
|
+
|
|
9
|
+
const pm = new ProcessManager(config);
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 打开监控面板
|
|
13
|
+
*/
|
|
14
|
+
async function handle(options) {
|
|
15
|
+
console.log('📊 CodeFix 监控面板');
|
|
16
|
+
console.log('');
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
// 确保服务运行
|
|
20
|
+
const isRunning = await pm.isRunning();
|
|
21
|
+
|
|
22
|
+
if (!isRunning) {
|
|
23
|
+
if (options.autoStart !== false) {
|
|
24
|
+
console.log('⚠️ 服务未运行,正在自动启动...');
|
|
25
|
+
console.log('');
|
|
26
|
+
|
|
27
|
+
await pm.start({ daemon: true, wait: true });
|
|
28
|
+
|
|
29
|
+
console.log('');
|
|
30
|
+
console.log('✅ 服务已启动');
|
|
31
|
+
console.log('');
|
|
32
|
+
} else {
|
|
33
|
+
console.log('❌ 服务未运行');
|
|
34
|
+
console.log('');
|
|
35
|
+
console.log('使用 `cfix start` 启动服务');
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const serviceConfig = config.getServiceConfig();
|
|
41
|
+
const monitorUrl = `http://${serviceConfig.host}:${serviceConfig.port}/monitor.html`;
|
|
42
|
+
|
|
43
|
+
console.log('======================================');
|
|
44
|
+
console.log(`正在打开监控面板`);
|
|
45
|
+
console.log('======================================');
|
|
46
|
+
console.log('');
|
|
47
|
+
console.log(`URL: ${monitorUrl}`);
|
|
48
|
+
console.log('');
|
|
49
|
+
|
|
50
|
+
// 打开浏览器
|
|
51
|
+
await openBrowser(monitorUrl);
|
|
52
|
+
|
|
53
|
+
console.log('✅ 监控面板已在浏览器中打开');
|
|
54
|
+
console.log('');
|
|
55
|
+
console.log('提示:');
|
|
56
|
+
console.log(' 按 Ctrl+C 停止监控(服务继续运行)');
|
|
57
|
+
console.log(` 或直接访问 ${monitorUrl}`);
|
|
58
|
+
|
|
59
|
+
// 如果设置了 --follow,保持运行并显示状态
|
|
60
|
+
if (options.follow) {
|
|
61
|
+
console.log('');
|
|
62
|
+
console.log('正在监控服务状态...');
|
|
63
|
+
console.log('按 Ctrl+C 退出');
|
|
64
|
+
|
|
65
|
+
const ServiceClient = require('../lib/service-client');
|
|
66
|
+
const client = new ServiceClient();
|
|
67
|
+
|
|
68
|
+
// 定期刷新状态
|
|
69
|
+
const interval = setInterval(async () => {
|
|
70
|
+
try {
|
|
71
|
+
const health = await client.healthCheck();
|
|
72
|
+
process.stdout.write(`\r[${new Date().toLocaleTimeString('zh-CN')}] 运行中 | 任务: ${health.metrics?.tasks?.total || 0} | 队列: ${health.metrics?.queue?.waiting || 0} | 运行时间: ${formatUptime(health.uptime)}`);
|
|
73
|
+
} catch {
|
|
74
|
+
process.stdout.write(`\r[${new Date().toLocaleTimeString('zh-CN')}] 无法连接到服务`);
|
|
75
|
+
}
|
|
76
|
+
}, 2000);
|
|
77
|
+
|
|
78
|
+
// 处理退出
|
|
79
|
+
process.on('SIGINT', () => {
|
|
80
|
+
clearInterval(interval);
|
|
81
|
+
console.log('\n');
|
|
82
|
+
console.log('监控已停止');
|
|
83
|
+
process.exit(0);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error(`❌ 打开监控面板失败: ${error.message}`);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 打开浏览器
|
|
95
|
+
*/
|
|
96
|
+
async function openBrowser(url) {
|
|
97
|
+
const platform = process.platform;
|
|
98
|
+
let command;
|
|
99
|
+
|
|
100
|
+
if (platform === 'darwin') {
|
|
101
|
+
command = 'open';
|
|
102
|
+
} else if (platform === 'win32') {
|
|
103
|
+
command = 'start';
|
|
104
|
+
} else {
|
|
105
|
+
command = 'xdg-open';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return new Promise((resolve, reject) => {
|
|
109
|
+
const child = spawn(command, [url], {
|
|
110
|
+
shell: platform === 'win32',
|
|
111
|
+
stdio: 'ignore',
|
|
112
|
+
detached: platform === 'win32'
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
child.on('error', (error) => {
|
|
116
|
+
// 如果无法打开浏览器,只显示 URL
|
|
117
|
+
console.log('⚠️ 无法自动打开浏览器');
|
|
118
|
+
console.log(`请手动访问: ${url}`);
|
|
119
|
+
resolve();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
child.on('exit', () => {
|
|
123
|
+
if (platform !== 'win32') {
|
|
124
|
+
resolve();
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
if (platform === 'win32') {
|
|
129
|
+
child.unref();
|
|
130
|
+
resolve();
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* 格式化运行时间
|
|
137
|
+
*/
|
|
138
|
+
function formatUptime(seconds) {
|
|
139
|
+
if (seconds < 60) {
|
|
140
|
+
return `${seconds}秒`;
|
|
141
|
+
} else if (seconds < 3600) {
|
|
142
|
+
const minutes = Math.floor(seconds / 60);
|
|
143
|
+
return `${minutes}分钟`;
|
|
144
|
+
} else {
|
|
145
|
+
const hours = Math.floor(seconds / 3600);
|
|
146
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
147
|
+
return `${hours}小时${minutes}分钟`;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
module.exports = { handle };
|