@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,118 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const router = express.Router();
|
|
3
|
+
const {
|
|
4
|
+
healthCheckAllProjects,
|
|
5
|
+
scanLegacySessionFiles,
|
|
6
|
+
migrateSessionFiles,
|
|
7
|
+
cleanLegacySessionFiles
|
|
8
|
+
} = require('../services/health-check');
|
|
9
|
+
const { getProjects } = require('../services/sessions');
|
|
10
|
+
|
|
11
|
+
module.exports = (config) => {
|
|
12
|
+
/**
|
|
13
|
+
* GET /api/health-check - 健康检查所有项目
|
|
14
|
+
*/
|
|
15
|
+
router.get('/', (req, res) => {
|
|
16
|
+
try {
|
|
17
|
+
const projects = getProjects(config);
|
|
18
|
+
const result = healthCheckAllProjects(projects);
|
|
19
|
+
|
|
20
|
+
res.json({
|
|
21
|
+
success: true,
|
|
22
|
+
timestamp: new Date().toISOString(),
|
|
23
|
+
...result
|
|
24
|
+
});
|
|
25
|
+
} catch (error) {
|
|
26
|
+
console.error('Health check failed:', error);
|
|
27
|
+
res.status(500).json({
|
|
28
|
+
success: false,
|
|
29
|
+
error: error.message
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* GET /api/health-check/scan-legacy - 扫描旧文件
|
|
36
|
+
*/
|
|
37
|
+
router.get('/scan-legacy', (req, res) => {
|
|
38
|
+
try {
|
|
39
|
+
const result = scanLegacySessionFiles();
|
|
40
|
+
|
|
41
|
+
res.json({
|
|
42
|
+
success: true,
|
|
43
|
+
timestamp: new Date().toISOString(),
|
|
44
|
+
...result
|
|
45
|
+
});
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error('Legacy scan failed:', error);
|
|
48
|
+
res.status(500).json({
|
|
49
|
+
success: false,
|
|
50
|
+
error: error.message
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* POST /api/health-check/migrate-legacy - 迁移旧文件到正确位置
|
|
57
|
+
* Body:
|
|
58
|
+
* {
|
|
59
|
+
* "dryRun": boolean, // 是否只是预演
|
|
60
|
+
* "projectNames": string[] // 可选:指定要迁移的项目
|
|
61
|
+
* }
|
|
62
|
+
*/
|
|
63
|
+
router.post('/migrate-legacy', (req, res) => {
|
|
64
|
+
try {
|
|
65
|
+
const { dryRun = false, projectNames = null } = req.body;
|
|
66
|
+
|
|
67
|
+
const result = migrateSessionFiles({
|
|
68
|
+
dryRun,
|
|
69
|
+
projectNames
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
res.json({
|
|
73
|
+
success: true,
|
|
74
|
+
timestamp: new Date().toISOString(),
|
|
75
|
+
...result
|
|
76
|
+
});
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.error('Legacy migration failed:', error);
|
|
79
|
+
res.status(500).json({
|
|
80
|
+
success: false,
|
|
81
|
+
error: error.message
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* POST /api/health-check/clean-legacy - 清理旧文件
|
|
88
|
+
* Body:
|
|
89
|
+
* {
|
|
90
|
+
* "dryRun": boolean, // 是否只是预演
|
|
91
|
+
* "projectNames": string[] // 可选:指定要清理的项目
|
|
92
|
+
* }
|
|
93
|
+
*/
|
|
94
|
+
router.post('/clean-legacy', (req, res) => {
|
|
95
|
+
try {
|
|
96
|
+
const { dryRun = false, projectNames = null } = req.body;
|
|
97
|
+
|
|
98
|
+
const result = cleanLegacySessionFiles({
|
|
99
|
+
dryRun,
|
|
100
|
+
projectNames
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
res.json({
|
|
104
|
+
success: true,
|
|
105
|
+
timestamp: new Date().toISOString(),
|
|
106
|
+
...result
|
|
107
|
+
});
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.error('Legacy cleanup failed:', error);
|
|
110
|
+
res.status(500).json({
|
|
111
|
+
success: false,
|
|
112
|
+
error: error.message
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
return router;
|
|
118
|
+
};
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP 服务器管理 API 路由
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const express = require('express');
|
|
6
|
+
const router = express.Router();
|
|
7
|
+
const mcpService = require('../services/mcp-service');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* GET /api/mcp/servers
|
|
11
|
+
* 获取所有 MCP 服务器
|
|
12
|
+
*/
|
|
13
|
+
router.get('/servers', (req, res) => {
|
|
14
|
+
try {
|
|
15
|
+
const servers = mcpService.getAllServers();
|
|
16
|
+
res.json({
|
|
17
|
+
success: true,
|
|
18
|
+
servers
|
|
19
|
+
});
|
|
20
|
+
} catch (error) {
|
|
21
|
+
console.error('[MCP API] Get servers failed:', error);
|
|
22
|
+
res.status(500).json({
|
|
23
|
+
success: false,
|
|
24
|
+
error: error.message
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* GET /api/mcp/servers/:id
|
|
31
|
+
* 获取单个 MCP 服务器
|
|
32
|
+
*/
|
|
33
|
+
router.get('/servers/:id', (req, res) => {
|
|
34
|
+
try {
|
|
35
|
+
const server = mcpService.getServer(req.params.id);
|
|
36
|
+
if (!server) {
|
|
37
|
+
return res.status(404).json({
|
|
38
|
+
success: false,
|
|
39
|
+
error: `MCP 服务器 "${req.params.id}" 不存在`
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
res.json({
|
|
43
|
+
success: true,
|
|
44
|
+
server
|
|
45
|
+
});
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error('[MCP API] Get server failed:', error);
|
|
48
|
+
res.status(500).json({
|
|
49
|
+
success: false,
|
|
50
|
+
error: error.message
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* POST /api/mcp/servers
|
|
57
|
+
* 添加或更新 MCP 服务器
|
|
58
|
+
*/
|
|
59
|
+
router.post('/servers', async (req, res) => {
|
|
60
|
+
try {
|
|
61
|
+
const server = req.body;
|
|
62
|
+
|
|
63
|
+
if (!server.id) {
|
|
64
|
+
return res.status(400).json({
|
|
65
|
+
success: false,
|
|
66
|
+
error: 'MCP 服务器 ID 不能为空'
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!server.server) {
|
|
71
|
+
return res.status(400).json({
|
|
72
|
+
success: false,
|
|
73
|
+
error: '服务器配置不能为空'
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const result = await mcpService.saveServer(server);
|
|
78
|
+
res.json({
|
|
79
|
+
success: true,
|
|
80
|
+
server: result
|
|
81
|
+
});
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.error('[MCP API] Save server failed:', error);
|
|
84
|
+
res.status(400).json({
|
|
85
|
+
success: false,
|
|
86
|
+
error: error.message
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* DELETE /api/mcp/servers/:id
|
|
93
|
+
* 删除 MCP 服务器
|
|
94
|
+
*/
|
|
95
|
+
router.delete('/servers/:id', async (req, res) => {
|
|
96
|
+
try {
|
|
97
|
+
const deleted = await mcpService.deleteServer(req.params.id);
|
|
98
|
+
if (!deleted) {
|
|
99
|
+
return res.status(404).json({
|
|
100
|
+
success: false,
|
|
101
|
+
error: `MCP 服务器 "${req.params.id}" 不存在`
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
res.json({
|
|
105
|
+
success: true
|
|
106
|
+
});
|
|
107
|
+
} catch (error) {
|
|
108
|
+
console.error('[MCP API] Delete server failed:', error);
|
|
109
|
+
res.status(500).json({
|
|
110
|
+
success: false,
|
|
111
|
+
error: error.message
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* POST /api/mcp/servers/:id/toggle
|
|
118
|
+
* 切换 MCP 服务器在某平台的启用状态
|
|
119
|
+
*/
|
|
120
|
+
router.post('/servers/:id/toggle', async (req, res) => {
|
|
121
|
+
try {
|
|
122
|
+
const { app, enabled } = req.body;
|
|
123
|
+
|
|
124
|
+
if (!app) {
|
|
125
|
+
return res.status(400).json({
|
|
126
|
+
success: false,
|
|
127
|
+
error: '必须指定平台 (app)'
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (typeof enabled !== 'boolean') {
|
|
132
|
+
return res.status(400).json({
|
|
133
|
+
success: false,
|
|
134
|
+
error: '必须指定启用状态 (enabled)'
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const server = await mcpService.toggleServerApp(req.params.id, app, enabled);
|
|
139
|
+
res.json({
|
|
140
|
+
success: true,
|
|
141
|
+
server
|
|
142
|
+
});
|
|
143
|
+
} catch (error) {
|
|
144
|
+
console.error('[MCP API] Toggle server failed:', error);
|
|
145
|
+
res.status(400).json({
|
|
146
|
+
success: false,
|
|
147
|
+
error: error.message
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* GET /api/mcp/presets
|
|
154
|
+
* 获取 MCP 预设模板列表
|
|
155
|
+
*/
|
|
156
|
+
router.get('/presets', (req, res) => {
|
|
157
|
+
try {
|
|
158
|
+
const presets = mcpService.getPresets();
|
|
159
|
+
res.json({
|
|
160
|
+
success: true,
|
|
161
|
+
presets
|
|
162
|
+
});
|
|
163
|
+
} catch (error) {
|
|
164
|
+
console.error('[MCP API] Get presets failed:', error);
|
|
165
|
+
res.status(500).json({
|
|
166
|
+
success: false,
|
|
167
|
+
error: error.message
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* POST /api/mcp/import/:platform
|
|
174
|
+
* 从指定平台导入 MCP 配置
|
|
175
|
+
*/
|
|
176
|
+
router.post('/import/:platform', async (req, res) => {
|
|
177
|
+
try {
|
|
178
|
+
const { platform } = req.params;
|
|
179
|
+
|
|
180
|
+
if (!['claude', 'codex', 'gemini'].includes(platform)) {
|
|
181
|
+
return res.status(400).json({
|
|
182
|
+
success: false,
|
|
183
|
+
error: `无效的平台: ${platform}`
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const count = await mcpService.importFromPlatform(platform);
|
|
188
|
+
res.json({
|
|
189
|
+
success: true,
|
|
190
|
+
imported: count,
|
|
191
|
+
message: count > 0
|
|
192
|
+
? `成功从 ${platform} 导入 ${count} 个 MCP 服务器`
|
|
193
|
+
: `${platform} 没有可导入的 MCP 服务器`
|
|
194
|
+
});
|
|
195
|
+
} catch (error) {
|
|
196
|
+
console.error('[MCP API] Import failed:', error);
|
|
197
|
+
res.status(500).json({
|
|
198
|
+
success: false,
|
|
199
|
+
error: error.message
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* GET /api/mcp/stats
|
|
206
|
+
* 获取 MCP 统计信息
|
|
207
|
+
*/
|
|
208
|
+
router.get('/stats', (req, res) => {
|
|
209
|
+
try {
|
|
210
|
+
const stats = mcpService.getStats();
|
|
211
|
+
res.json({
|
|
212
|
+
success: true,
|
|
213
|
+
stats
|
|
214
|
+
});
|
|
215
|
+
} catch (error) {
|
|
216
|
+
console.error('[MCP API] Get stats failed:', error);
|
|
217
|
+
res.status(500).json({
|
|
218
|
+
success: false,
|
|
219
|
+
error: error.message
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* POST /api/mcp/servers/:id/test
|
|
226
|
+
* 测试 MCP 服务器连接
|
|
227
|
+
*/
|
|
228
|
+
router.post('/servers/:id/test', async (req, res) => {
|
|
229
|
+
try {
|
|
230
|
+
const result = await mcpService.testServer(req.params.id);
|
|
231
|
+
|
|
232
|
+
// 更新服务器状态
|
|
233
|
+
const status = result.success ? 'online' : 'error';
|
|
234
|
+
await mcpService.updateServerStatus(req.params.id, status);
|
|
235
|
+
|
|
236
|
+
res.json({
|
|
237
|
+
success: true,
|
|
238
|
+
result
|
|
239
|
+
});
|
|
240
|
+
} catch (error) {
|
|
241
|
+
console.error('[MCP API] Test server failed:', error);
|
|
242
|
+
res.status(500).json({
|
|
243
|
+
success: false,
|
|
244
|
+
error: error.message
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* POST /api/mcp/servers/order
|
|
251
|
+
* 更新服务器排序
|
|
252
|
+
*/
|
|
253
|
+
router.post('/servers/order', (req, res) => {
|
|
254
|
+
try {
|
|
255
|
+
const { serverIds } = req.body;
|
|
256
|
+
|
|
257
|
+
if (!Array.isArray(serverIds)) {
|
|
258
|
+
return res.status(400).json({
|
|
259
|
+
success: false,
|
|
260
|
+
error: 'serverIds 必须是数组'
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const servers = mcpService.updateServerOrder(serverIds);
|
|
265
|
+
res.json({
|
|
266
|
+
success: true,
|
|
267
|
+
servers
|
|
268
|
+
});
|
|
269
|
+
} catch (error) {
|
|
270
|
+
console.error('[MCP API] Update order failed:', error);
|
|
271
|
+
res.status(500).json({
|
|
272
|
+
success: false,
|
|
273
|
+
error: error.message
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* GET /api/mcp/export
|
|
280
|
+
* 导出 MCP 配置
|
|
281
|
+
*/
|
|
282
|
+
router.get('/export', (req, res) => {
|
|
283
|
+
try {
|
|
284
|
+
const format = req.query.format || 'json';
|
|
285
|
+
|
|
286
|
+
if (!['json', 'claude', 'codex'].includes(format)) {
|
|
287
|
+
return res.status(400).json({
|
|
288
|
+
success: false,
|
|
289
|
+
error: `无效的导出格式: ${format}`
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const result = mcpService.exportServers(format);
|
|
294
|
+
res.json({
|
|
295
|
+
success: true,
|
|
296
|
+
...result
|
|
297
|
+
});
|
|
298
|
+
} catch (error) {
|
|
299
|
+
console.error('[MCP API] Export failed:', error);
|
|
300
|
+
res.status(500).json({
|
|
301
|
+
success: false,
|
|
302
|
+
error: error.message
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* GET /api/mcp/export/download
|
|
309
|
+
* 下载导出的配置文件
|
|
310
|
+
*/
|
|
311
|
+
router.get('/export/download', (req, res) => {
|
|
312
|
+
try {
|
|
313
|
+
const format = req.query.format || 'json';
|
|
314
|
+
|
|
315
|
+
if (!['json', 'claude', 'codex'].includes(format)) {
|
|
316
|
+
return res.status(400).json({
|
|
317
|
+
success: false,
|
|
318
|
+
error: `无效的导出格式: ${format}`
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const result = mcpService.exportServers(format);
|
|
323
|
+
|
|
324
|
+
res.setHeader('Content-Type', format === 'codex' ? 'application/toml' : 'application/json');
|
|
325
|
+
res.setHeader('Content-Disposition', `attachment; filename="${result.filename}"`);
|
|
326
|
+
res.send(result.content);
|
|
327
|
+
} catch (error) {
|
|
328
|
+
console.error('[MCP API] Export download failed:', error);
|
|
329
|
+
res.status(500).json({
|
|
330
|
+
success: false,
|
|
331
|
+
error: error.message
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
module.exports = router;
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const { exec } = require('child_process');
|
|
3
|
+
const { promisify } = require('util');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
const pm2 = require('pm2');
|
|
8
|
+
|
|
9
|
+
const execAsync = promisify(exec);
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Check if PM2 autostart is enabled
|
|
13
|
+
* by looking for PM2 startup script in system
|
|
14
|
+
*/
|
|
15
|
+
async function checkAutoStartStatus() {
|
|
16
|
+
try {
|
|
17
|
+
const platform = process.platform;
|
|
18
|
+
|
|
19
|
+
if (platform === 'darwin') {
|
|
20
|
+
// macOS - check for LaunchDaemon
|
|
21
|
+
const launchDaemonsPath = path.join(os.homedir(), 'Library/LaunchDaemons');
|
|
22
|
+
const pm2Files = fs.existsSync(launchDaemonsPath)
|
|
23
|
+
? fs.readdirSync(launchDaemonsPath).filter(f => f.includes('pm2'))
|
|
24
|
+
: [];
|
|
25
|
+
|
|
26
|
+
return { enabled: pm2Files.length > 0, platform: 'darwin' };
|
|
27
|
+
} else if (platform === 'linux') {
|
|
28
|
+
// Linux - check for systemd service
|
|
29
|
+
const systemdPath = '/etc/systemd/system/pm2-root.service';
|
|
30
|
+
const userSystemdPath = path.join(os.homedir(), '.config/systemd/user/pm2-*.service');
|
|
31
|
+
|
|
32
|
+
const rootExists = fs.existsSync(systemdPath);
|
|
33
|
+
const userExists = fs.existsSync(path.join(os.homedir(), '.config/systemd/user')) &&
|
|
34
|
+
fs.readdirSync(path.join(os.homedir(), '.config/systemd/user')).some(f => f.includes('pm2'));
|
|
35
|
+
|
|
36
|
+
return { enabled: rootExists || userExists, platform: 'linux' };
|
|
37
|
+
} else if (platform === 'win32') {
|
|
38
|
+
// Windows - check for PM2 service in registry (simplified check)
|
|
39
|
+
// For now, assume Windows support via pm2 package manager
|
|
40
|
+
return { enabled: false, platform: 'win32', note: '暂不支持 Windows' };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return { enabled: false, platform };
|
|
44
|
+
} catch (err) {
|
|
45
|
+
console.error('Error checking autostart status:', err);
|
|
46
|
+
return { enabled: false, error: err.message };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Enable PM2 autostart
|
|
52
|
+
* Runs: pm2 startup && pm2 save
|
|
53
|
+
*/
|
|
54
|
+
async function enableAutoStart() {
|
|
55
|
+
return new Promise((resolve) => {
|
|
56
|
+
pm2.connect((err) => {
|
|
57
|
+
if (err) {
|
|
58
|
+
console.error('PM2 connect error:', err);
|
|
59
|
+
return resolve({
|
|
60
|
+
success: false,
|
|
61
|
+
message: '无法连接到 PM2:' + (err.message || '未知错误'),
|
|
62
|
+
error: err.message
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Get current process list
|
|
67
|
+
pm2.list((listErr, processes) => {
|
|
68
|
+
if (listErr) {
|
|
69
|
+
pm2.disconnect();
|
|
70
|
+
console.error('PM2 list error:', listErr);
|
|
71
|
+
return resolve({
|
|
72
|
+
success: false,
|
|
73
|
+
message: '无法获取 PM2 进程列表:' + (listErr.message || '未知错误')
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// If no processes are running, we can't really set up autostart
|
|
78
|
+
if (!processes || processes.length === 0) {
|
|
79
|
+
pm2.disconnect();
|
|
80
|
+
return resolve({
|
|
81
|
+
success: false,
|
|
82
|
+
message: '暂无运行中的进程,无法启用开机自启。请先启动服务:ctx start'
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Save current process list
|
|
87
|
+
pm2.save((saveErr) => {
|
|
88
|
+
if (saveErr) {
|
|
89
|
+
pm2.disconnect();
|
|
90
|
+
console.error('PM2 save error:', saveErr);
|
|
91
|
+
return resolve({
|
|
92
|
+
success: false,
|
|
93
|
+
message: '无法保存 PM2 配置:' + (saveErr.message || '未知错误')
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Run startup command
|
|
98
|
+
const platform = process.platform;
|
|
99
|
+
const command = platform === 'darwin'
|
|
100
|
+
? 'pm2 startup launchd -u $(whoami) --hp $(eval echo ~$(whoami))'
|
|
101
|
+
: platform === 'linux'
|
|
102
|
+
? 'pm2 startup systemd -u $(whoami) --hp $(eval echo ~$(whoami))'
|
|
103
|
+
: 'pm2 startup';
|
|
104
|
+
|
|
105
|
+
console.log(`Running startup command: ${command}`);
|
|
106
|
+
|
|
107
|
+
exec(command, { shell: '/bin/bash', timeout: 30000 }, (execErr, stdout, stderr) => {
|
|
108
|
+
pm2.disconnect();
|
|
109
|
+
|
|
110
|
+
if (execErr) {
|
|
111
|
+
console.error('Startup command error:', execErr);
|
|
112
|
+
console.error('stderr:', stderr);
|
|
113
|
+
|
|
114
|
+
// Check if it's already enabled
|
|
115
|
+
if (stderr && stderr.includes('already')) {
|
|
116
|
+
return resolve({
|
|
117
|
+
success: true,
|
|
118
|
+
message: '开机自启已启用(或已存在)'
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return resolve({
|
|
123
|
+
success: false,
|
|
124
|
+
message: '启用失败。' + (stderr || execErr.message || '请确保已安装 PM2 且有足够权限'),
|
|
125
|
+
error: execErr.message
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
console.log('Startup command output:', stdout);
|
|
130
|
+
return resolve({
|
|
131
|
+
success: true,
|
|
132
|
+
message: '开机自启已启用。重启电脑后自动启动'
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Disable PM2 autostart
|
|
143
|
+
* Runs: pm2 unstartup
|
|
144
|
+
*/
|
|
145
|
+
async function disableAutoStart() {
|
|
146
|
+
return new Promise((resolve) => {
|
|
147
|
+
pm2.connect((err) => {
|
|
148
|
+
if (err) {
|
|
149
|
+
console.error('PM2 connect error:', err);
|
|
150
|
+
return resolve({
|
|
151
|
+
success: false,
|
|
152
|
+
message: '无法连接到 PM2:' + (err.message || '未知错误')
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Run unstartup command
|
|
157
|
+
const platform = process.platform;
|
|
158
|
+
const command = platform === 'darwin'
|
|
159
|
+
? 'pm2 unstartup launchd -u $(whoami)'
|
|
160
|
+
: platform === 'linux'
|
|
161
|
+
? 'pm2 unstartup systemd -u $(whoami)'
|
|
162
|
+
: 'pm2 unstartup';
|
|
163
|
+
|
|
164
|
+
console.log(`Running unstartup command: ${command}`);
|
|
165
|
+
|
|
166
|
+
exec(command, { shell: '/bin/bash', timeout: 30000 }, (execErr, stdout, stderr) => {
|
|
167
|
+
pm2.disconnect();
|
|
168
|
+
|
|
169
|
+
if (execErr) {
|
|
170
|
+
console.error('Unstartup command error:', execErr);
|
|
171
|
+
console.error('stderr:', stderr);
|
|
172
|
+
|
|
173
|
+
// Check if it's not set up
|
|
174
|
+
if (stderr && stderr.includes('not set')) {
|
|
175
|
+
return resolve({
|
|
176
|
+
success: true,
|
|
177
|
+
message: '开机自启已禁用(或未启用)'
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return resolve({
|
|
182
|
+
success: false,
|
|
183
|
+
message: '禁用失败。' + (stderr || execErr.message || '请确保已安装 PM2 且有足够权限'),
|
|
184
|
+
error: execErr.message
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
console.log('Unstartup command output:', stdout);
|
|
189
|
+
return resolve({
|
|
190
|
+
success: true,
|
|
191
|
+
message: '开机自启已禁用'
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
module.exports = () => {
|
|
199
|
+
const router = express.Router();
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* GET /api/pm2-autostart
|
|
203
|
+
* Get current PM2 autostart status
|
|
204
|
+
*/
|
|
205
|
+
router.get('/', async (req, res) => {
|
|
206
|
+
try {
|
|
207
|
+
const status = await checkAutoStartStatus();
|
|
208
|
+
res.json({
|
|
209
|
+
success: true,
|
|
210
|
+
data: status
|
|
211
|
+
});
|
|
212
|
+
} catch (err) {
|
|
213
|
+
console.error('Failed to check autostart status:', err);
|
|
214
|
+
// 返回 200 状态码,让前端通过 success 字段判断
|
|
215
|
+
res.json({
|
|
216
|
+
success: false,
|
|
217
|
+
message: 'Failed to check autostart status: ' + err.message
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* POST /api/pm2-autostart
|
|
224
|
+
* Enable or disable PM2 autostart
|
|
225
|
+
* Body: { action: 'enable' | 'disable' }
|
|
226
|
+
*/
|
|
227
|
+
router.post('/', async (req, res) => {
|
|
228
|
+
try {
|
|
229
|
+
const { action } = req.body;
|
|
230
|
+
|
|
231
|
+
if (!action || !['enable', 'disable'].includes(action)) {
|
|
232
|
+
return res.status(400).json({
|
|
233
|
+
success: false,
|
|
234
|
+
message: 'Invalid action. Must be "enable" or "disable"'
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
let result;
|
|
239
|
+
if (action === 'enable') {
|
|
240
|
+
result = await enableAutoStart();
|
|
241
|
+
} else {
|
|
242
|
+
result = await disableAutoStart();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (result.success) {
|
|
246
|
+
res.json({
|
|
247
|
+
success: true,
|
|
248
|
+
message: result.message,
|
|
249
|
+
data: { action, enabled: action === 'enable' }
|
|
250
|
+
});
|
|
251
|
+
} else {
|
|
252
|
+
// 返回 200 状态码,让前端通过 success 字段判断
|
|
253
|
+
res.json({
|
|
254
|
+
success: false,
|
|
255
|
+
message: result.message
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
} catch (err) {
|
|
259
|
+
console.error('Failed to configure autostart:', err);
|
|
260
|
+
// 真正的服务器错误才返回 500
|
|
261
|
+
res.status(500).json({
|
|
262
|
+
success: false,
|
|
263
|
+
message: '服务器错误:' + err.message
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
return router;
|
|
269
|
+
};
|