@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,124 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const router = express.Router();
|
|
3
|
+
const { getProjectsWithStats, saveProjectOrder, getProjectOrder, deleteProject } = require('../services/sessions');
|
|
4
|
+
|
|
5
|
+
module.exports = (config) => {
|
|
6
|
+
// GET /api/projects - Get all projects with stats
|
|
7
|
+
router.get('/', (req, res) => {
|
|
8
|
+
try {
|
|
9
|
+
const projects = getProjectsWithStats(config);
|
|
10
|
+
const order = getProjectOrder(config);
|
|
11
|
+
|
|
12
|
+
// Sort projects by saved order
|
|
13
|
+
let sortedProjects = projects;
|
|
14
|
+
if (order && order.length > 0) {
|
|
15
|
+
const orderMap = new Map(order.map((name, idx) => [name, idx]));
|
|
16
|
+
sortedProjects = [...projects].sort((a, b) => {
|
|
17
|
+
const aIdx = orderMap.has(a.name) ? orderMap.get(a.name) : 999999;
|
|
18
|
+
const bIdx = orderMap.has(b.name) ? orderMap.get(b.name) : 999999;
|
|
19
|
+
if (aIdx === bIdx) {
|
|
20
|
+
// Both are new, sort by lastUsed
|
|
21
|
+
return (b.lastUsed || 0) - (a.lastUsed || 0);
|
|
22
|
+
}
|
|
23
|
+
return aIdx - bIdx;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
res.json({
|
|
28
|
+
projects: sortedProjects,
|
|
29
|
+
currentProject: config.currentProject || (sortedProjects[0] ? sortedProjects[0].name : null)
|
|
30
|
+
});
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error('Error fetching projects:', error);
|
|
33
|
+
res.status(500).json({ error: error.message });
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// POST /api/projects/order - Save project order
|
|
38
|
+
router.post('/order', (req, res) => {
|
|
39
|
+
try {
|
|
40
|
+
const { order } = req.body;
|
|
41
|
+
if (!Array.isArray(order)) {
|
|
42
|
+
return res.status(400).json({ error: 'Order must be an array' });
|
|
43
|
+
}
|
|
44
|
+
saveProjectOrder(config, order);
|
|
45
|
+
res.json({ success: true });
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error('Error saving project order:', error);
|
|
48
|
+
res.status(500).json({ error: error.message });
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// POST /api/projects/create - Create a new project
|
|
53
|
+
router.post('/create', (req, res) => {
|
|
54
|
+
try {
|
|
55
|
+
const { projectName, projectPath } = req.body;
|
|
56
|
+
const path = require('path');
|
|
57
|
+
const fs = require('fs');
|
|
58
|
+
|
|
59
|
+
if (!projectName || !projectPath) {
|
|
60
|
+
return res.status(400).json({ error: 'projectName and projectPath are required' });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 验证项目路径存在
|
|
64
|
+
if (!fs.existsSync(projectPath)) {
|
|
65
|
+
return res.status(400).json({ error: '项目路径不存在' });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 验证是否为目录
|
|
69
|
+
const stats = fs.statSync(projectPath);
|
|
70
|
+
if (!stats.isDirectory()) {
|
|
71
|
+
return res.status(400).json({ error: '项目路径必须是一个目录' });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 在项目路径下创建 .claude 目录(与 Claude Code 默认行为一致)
|
|
75
|
+
const claudeDir = path.join(projectPath, '.claude');
|
|
76
|
+
const sessionsDir = path.join(claudeDir, 'sessions');
|
|
77
|
+
|
|
78
|
+
// 检查是否已经初始化过
|
|
79
|
+
if (fs.existsSync(claudeDir)) {
|
|
80
|
+
return res.status(400).json({ error: '该项目已经初始化过 Claude Code' });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 创建目录结构
|
|
84
|
+
fs.mkdirSync(sessionsDir, { recursive: true });
|
|
85
|
+
|
|
86
|
+
// 创建 .claude/project.json 配置文件(可选,记录元数据)
|
|
87
|
+
const projectConfig = {
|
|
88
|
+
name: projectName,
|
|
89
|
+
createdAt: new Date().toISOString(),
|
|
90
|
+
version: '1.0'
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
fs.writeFileSync(
|
|
94
|
+
path.join(claudeDir, 'project.json'),
|
|
95
|
+
JSON.stringify(projectConfig, null, 2),
|
|
96
|
+
'utf8'
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
res.json({
|
|
100
|
+
success: true,
|
|
101
|
+
projectName,
|
|
102
|
+
projectPath,
|
|
103
|
+
claudeDir
|
|
104
|
+
});
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.error('Error creating project:', error);
|
|
107
|
+
res.status(500).json({ error: error.message });
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// DELETE /api/projects/:projectName - Delete a project
|
|
112
|
+
router.delete('/:projectName', (req, res) => {
|
|
113
|
+
try {
|
|
114
|
+
const { projectName } = req.params;
|
|
115
|
+
deleteProject(config, projectName);
|
|
116
|
+
res.json({ success: true });
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.error('Error deleting project:', error);
|
|
119
|
+
res.status(500).json({ error: error.message });
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
return router;
|
|
124
|
+
};
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompts 管理 API 路由
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const express = require('express');
|
|
6
|
+
const router = express.Router();
|
|
7
|
+
const promptsService = require('../services/prompts-service');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* GET /api/prompts/presets
|
|
11
|
+
* 获取所有预设
|
|
12
|
+
*/
|
|
13
|
+
router.get('/presets', (req, res) => {
|
|
14
|
+
try {
|
|
15
|
+
const result = promptsService.getAllPresets();
|
|
16
|
+
res.json({
|
|
17
|
+
success: true,
|
|
18
|
+
...result
|
|
19
|
+
});
|
|
20
|
+
} catch (error) {
|
|
21
|
+
console.error('[Prompts API] Get presets failed:', error);
|
|
22
|
+
res.status(500).json({
|
|
23
|
+
success: false,
|
|
24
|
+
error: error.message
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* GET /api/prompts/presets/active
|
|
31
|
+
* 获取当前激活的预设
|
|
32
|
+
*/
|
|
33
|
+
router.get('/presets/active', (req, res) => {
|
|
34
|
+
try {
|
|
35
|
+
const result = promptsService.getActivePreset();
|
|
36
|
+
res.json({
|
|
37
|
+
success: true,
|
|
38
|
+
...result
|
|
39
|
+
});
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error('[Prompts API] Get active preset failed:', error);
|
|
42
|
+
res.status(500).json({
|
|
43
|
+
success: false,
|
|
44
|
+
error: error.message
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* GET /api/prompts/presets/:id
|
|
51
|
+
* 获取单个预设
|
|
52
|
+
*/
|
|
53
|
+
router.get('/presets/:id', (req, res) => {
|
|
54
|
+
try {
|
|
55
|
+
const preset = promptsService.getPreset(req.params.id);
|
|
56
|
+
if (!preset) {
|
|
57
|
+
return res.status(404).json({
|
|
58
|
+
success: false,
|
|
59
|
+
error: `预设 "${req.params.id}" 不存在`
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
res.json({
|
|
63
|
+
success: true,
|
|
64
|
+
preset
|
|
65
|
+
});
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error('[Prompts API] Get preset failed:', error);
|
|
68
|
+
res.status(500).json({
|
|
69
|
+
success: false,
|
|
70
|
+
error: error.message
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* POST /api/prompts/presets
|
|
77
|
+
* 添加或更新预设
|
|
78
|
+
*/
|
|
79
|
+
router.post('/presets', (req, res) => {
|
|
80
|
+
try {
|
|
81
|
+
const preset = req.body;
|
|
82
|
+
|
|
83
|
+
if (!preset.id) {
|
|
84
|
+
return res.status(400).json({
|
|
85
|
+
success: false,
|
|
86
|
+
error: '预设 ID 不能为空'
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (!preset.name) {
|
|
91
|
+
return res.status(400).json({
|
|
92
|
+
success: false,
|
|
93
|
+
error: '预设名称不能为空'
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const result = promptsService.savePreset(preset);
|
|
98
|
+
res.json({
|
|
99
|
+
success: true,
|
|
100
|
+
preset: result
|
|
101
|
+
});
|
|
102
|
+
} catch (error) {
|
|
103
|
+
console.error('[Prompts API] Save preset failed:', error);
|
|
104
|
+
res.status(400).json({
|
|
105
|
+
success: false,
|
|
106
|
+
error: error.message
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* DELETE /api/prompts/presets/:id
|
|
113
|
+
* 删除预设
|
|
114
|
+
*/
|
|
115
|
+
router.delete('/presets/:id', (req, res) => {
|
|
116
|
+
try {
|
|
117
|
+
const deleted = promptsService.deletePreset(req.params.id);
|
|
118
|
+
if (!deleted) {
|
|
119
|
+
return res.status(404).json({
|
|
120
|
+
success: false,
|
|
121
|
+
error: `预设 "${req.params.id}" 不存在`
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
res.json({
|
|
125
|
+
success: true
|
|
126
|
+
});
|
|
127
|
+
} catch (error) {
|
|
128
|
+
console.error('[Prompts API] Delete preset failed:', error);
|
|
129
|
+
res.status(400).json({
|
|
130
|
+
success: false,
|
|
131
|
+
error: error.message
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* POST /api/prompts/presets/:id/activate
|
|
138
|
+
* 激活预设
|
|
139
|
+
*/
|
|
140
|
+
router.post('/presets/:id/activate', async (req, res) => {
|
|
141
|
+
try {
|
|
142
|
+
const preset = await promptsService.activatePreset(req.params.id);
|
|
143
|
+
res.json({
|
|
144
|
+
success: true,
|
|
145
|
+
preset,
|
|
146
|
+
message: `已激活预设 "${preset.name}"`
|
|
147
|
+
});
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.error('[Prompts API] Activate preset failed:', error);
|
|
150
|
+
res.status(400).json({
|
|
151
|
+
success: false,
|
|
152
|
+
error: error.message
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* POST /api/prompts/deactivate
|
|
159
|
+
* 停用/移除提示词(删除各平台文件)
|
|
160
|
+
*/
|
|
161
|
+
router.post('/deactivate', async (req, res) => {
|
|
162
|
+
try {
|
|
163
|
+
const result = await promptsService.deactivatePrompt();
|
|
164
|
+
res.json({
|
|
165
|
+
success: true,
|
|
166
|
+
result,
|
|
167
|
+
message: '已移除所有平台的提示词'
|
|
168
|
+
});
|
|
169
|
+
} catch (error) {
|
|
170
|
+
console.error('[Prompts API] Deactivate prompt failed:', error);
|
|
171
|
+
res.status(400).json({
|
|
172
|
+
success: false,
|
|
173
|
+
error: error.message
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* GET /api/prompts/platform-status
|
|
180
|
+
* 获取各平台提示词状态
|
|
181
|
+
*/
|
|
182
|
+
router.get('/platform-status', (req, res) => {
|
|
183
|
+
try {
|
|
184
|
+
const status = promptsService.getPlatformStatus();
|
|
185
|
+
res.json({
|
|
186
|
+
success: true,
|
|
187
|
+
status
|
|
188
|
+
});
|
|
189
|
+
} catch (error) {
|
|
190
|
+
console.error('[Prompts API] Get platform status failed:', error);
|
|
191
|
+
res.status(500).json({
|
|
192
|
+
success: false,
|
|
193
|
+
error: error.message
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* GET /api/prompts/platform/:platform
|
|
200
|
+
* 读取指定平台的提示词
|
|
201
|
+
*/
|
|
202
|
+
router.get('/platform/:platform', (req, res) => {
|
|
203
|
+
try {
|
|
204
|
+
const { platform } = req.params;
|
|
205
|
+
|
|
206
|
+
if (!['claude', 'codex', 'gemini'].includes(platform)) {
|
|
207
|
+
return res.status(400).json({
|
|
208
|
+
success: false,
|
|
209
|
+
error: `无效的平台: ${platform}`
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const content = promptsService.readPlatformPrompt(platform);
|
|
214
|
+
res.json({
|
|
215
|
+
success: true,
|
|
216
|
+
platform,
|
|
217
|
+
content
|
|
218
|
+
});
|
|
219
|
+
} catch (error) {
|
|
220
|
+
console.error('[Prompts API] Read platform prompt failed:', error);
|
|
221
|
+
res.status(500).json({
|
|
222
|
+
success: false,
|
|
223
|
+
error: error.message
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* POST /api/prompts/import/:platform
|
|
230
|
+
* 从指定平台导入提示词
|
|
231
|
+
*/
|
|
232
|
+
router.post('/import/:platform', (req, res) => {
|
|
233
|
+
try {
|
|
234
|
+
const { platform } = req.params;
|
|
235
|
+
const { name } = req.body;
|
|
236
|
+
|
|
237
|
+
if (!['claude', 'codex', 'gemini'].includes(platform)) {
|
|
238
|
+
return res.status(400).json({
|
|
239
|
+
success: false,
|
|
240
|
+
error: `无效的平台: ${platform}`
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const preset = promptsService.importFromPlatform(platform, name);
|
|
245
|
+
res.json({
|
|
246
|
+
success: true,
|
|
247
|
+
preset,
|
|
248
|
+
message: `成功从 ${platform} 导入提示词`
|
|
249
|
+
});
|
|
250
|
+
} catch (error) {
|
|
251
|
+
console.error('[Prompts API] Import failed:', error);
|
|
252
|
+
res.status(400).json({
|
|
253
|
+
success: false,
|
|
254
|
+
error: error.message
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* GET /api/prompts/stats
|
|
261
|
+
* 获取统计信息
|
|
262
|
+
*/
|
|
263
|
+
router.get('/stats', (req, res) => {
|
|
264
|
+
try {
|
|
265
|
+
const stats = promptsService.getStats();
|
|
266
|
+
res.json({
|
|
267
|
+
success: true,
|
|
268
|
+
stats
|
|
269
|
+
});
|
|
270
|
+
} catch (error) {
|
|
271
|
+
console.error('[Prompts API] Get stats failed:', error);
|
|
272
|
+
res.status(500).json({
|
|
273
|
+
success: false,
|
|
274
|
+
error: error.message
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
module.exports = router;
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const router = express.Router();
|
|
3
|
+
const { startProxyServer, stopProxyServer, getProxyStatus } = require('../proxy-server');
|
|
4
|
+
const {
|
|
5
|
+
setProxyConfig,
|
|
6
|
+
restoreSettings,
|
|
7
|
+
isProxyConfig,
|
|
8
|
+
getCurrentProxyPort,
|
|
9
|
+
settingsExists,
|
|
10
|
+
hasBackup,
|
|
11
|
+
readSettings
|
|
12
|
+
} = require('../services/settings-manager');
|
|
13
|
+
const { getAllChannels } = require('../services/channels');
|
|
14
|
+
const { clearAllLogs } = require('../websocket-server');
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
const os = require('os');
|
|
18
|
+
|
|
19
|
+
function sanitizeChannelForResponse(channel) {
|
|
20
|
+
if (!channel) return null;
|
|
21
|
+
return {
|
|
22
|
+
id: channel.id,
|
|
23
|
+
name: channel.name,
|
|
24
|
+
baseUrl: channel.baseUrl,
|
|
25
|
+
websiteUrl: channel.websiteUrl
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// 保存激活渠道ID
|
|
30
|
+
function saveActiveChannelId(channelId) {
|
|
31
|
+
const dir = path.join(os.homedir(), '.claude', 'cc-tool');
|
|
32
|
+
if (!fs.existsSync(dir)) {
|
|
33
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
34
|
+
}
|
|
35
|
+
const filePath = path.join(dir, 'active-channel.json');
|
|
36
|
+
fs.writeFileSync(filePath, JSON.stringify({ activeChannelId: channelId }, null, 2), 'utf8');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 从 settings.json 找到当前激活的渠道
|
|
40
|
+
function findActiveChannelFromSettings() {
|
|
41
|
+
try {
|
|
42
|
+
const settings = readSettings();
|
|
43
|
+
const baseUrl = settings?.env?.ANTHROPIC_BASE_URL || '';
|
|
44
|
+
|
|
45
|
+
// 兼容多种 API Key 格式(与 channels.js 保持一致)
|
|
46
|
+
let apiKey = settings?.env?.ANTHROPIC_API_KEY || // 标准格式
|
|
47
|
+
settings?.env?.ANTHROPIC_AUTH_TOKEN || // 88code等平台格式
|
|
48
|
+
'';
|
|
49
|
+
|
|
50
|
+
// 如果 apiKey 仍为空,尝试从 apiKeyHelper 提取
|
|
51
|
+
if (!apiKey && settings?.apiKeyHelper) {
|
|
52
|
+
const match = settings.apiKeyHelper.match(/['"]([^'"]+)['"]/);
|
|
53
|
+
if (match && match[1]) {
|
|
54
|
+
apiKey = match[1];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!baseUrl || !apiKey || baseUrl.includes('127.0.0.1')) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 找到匹配的渠道
|
|
63
|
+
const channels = getAllChannels();
|
|
64
|
+
const matchingChannel = channels.find(ch =>
|
|
65
|
+
ch.baseUrl === baseUrl && ch.apiKey === apiKey
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
return matchingChannel;
|
|
69
|
+
} catch (err) {
|
|
70
|
+
console.error('Error finding active channel:', err);
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 获取代理状态
|
|
76
|
+
router.get('/status', (req, res) => {
|
|
77
|
+
try {
|
|
78
|
+
const proxyStatus = getProxyStatus();
|
|
79
|
+
const channels = getAllChannels();
|
|
80
|
+
const configStatus = {
|
|
81
|
+
isProxyConfig: isProxyConfig(),
|
|
82
|
+
settingsExists: settingsExists(),
|
|
83
|
+
hasBackup: hasBackup(),
|
|
84
|
+
currentProxyPort: getCurrentProxyPort()
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
res.json({
|
|
88
|
+
proxy: proxyStatus,
|
|
89
|
+
config: configStatus,
|
|
90
|
+
channelsCount: channels.length,
|
|
91
|
+
enabledChannelsCount: channels.filter(ch => ch.enabled !== false).length
|
|
92
|
+
});
|
|
93
|
+
} catch (error) {
|
|
94
|
+
res.status(500).json({ error: error.message });
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// 启动代理
|
|
99
|
+
router.post('/start', async (req, res) => {
|
|
100
|
+
try {
|
|
101
|
+
// 1. 检查配置文件是否存在
|
|
102
|
+
if (!settingsExists()) {
|
|
103
|
+
return res.status(400).json({
|
|
104
|
+
error: 'Claude Code settings.json not found. Please run Claude Code at least once.'
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 2. 从 settings.json 找到当前使用的渠道
|
|
109
|
+
const currentChannel = findActiveChannelFromSettings();
|
|
110
|
+
if (!currentChannel) {
|
|
111
|
+
return res.status(400).json({
|
|
112
|
+
error: '无法从 settings.json 识别当前渠道。请先激活一个渠道。'
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 3. 保存当前激活渠道ID(用于代理模式)
|
|
117
|
+
saveActiveChannelId(currentChannel.id);
|
|
118
|
+
console.log(`✅ Saved active channel: ${currentChannel.name} (${currentChannel.id})`);
|
|
119
|
+
|
|
120
|
+
// 4. 启动代理服务器
|
|
121
|
+
const proxyResult = await startProxyServer();
|
|
122
|
+
|
|
123
|
+
if (!proxyResult.success) {
|
|
124
|
+
return res.status(500).json({ error: 'Failed to start proxy server' });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 5. 设置代理配置(备份并修改 settings.json)
|
|
128
|
+
setProxyConfig(proxyResult.port);
|
|
129
|
+
|
|
130
|
+
const updatedStatus = getProxyStatus();
|
|
131
|
+
const channels = getAllChannels();
|
|
132
|
+
const activeChannel = channels.find(ch => ch.enabled !== false);
|
|
133
|
+
|
|
134
|
+
// 6. 通过 WebSocket 推送代理状态更新
|
|
135
|
+
const { broadcastProxyState } = require('../websocket-server');
|
|
136
|
+
broadcastProxyState('claude', updatedStatus, activeChannel, channels);
|
|
137
|
+
|
|
138
|
+
res.json({
|
|
139
|
+
success: true,
|
|
140
|
+
port: proxyResult.port,
|
|
141
|
+
activeChannel: sanitizeChannelForResponse(currentChannel),
|
|
142
|
+
message: `代理已启动在端口 ${proxyResult.port},当前渠道: ${currentChannel.name}`
|
|
143
|
+
});
|
|
144
|
+
} catch (error) {
|
|
145
|
+
console.error('Error starting proxy:', error);
|
|
146
|
+
res.status(500).json({ error: error.message });
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// 停止代理
|
|
151
|
+
router.post('/stop', async (req, res) => {
|
|
152
|
+
try {
|
|
153
|
+
// 1. 停止代理服务器
|
|
154
|
+
const proxyResult = await stopProxyServer();
|
|
155
|
+
|
|
156
|
+
// 2. 恢复配置(优先从备份,否则选择权重最高的启用渠道)
|
|
157
|
+
let restoredChannel = null;
|
|
158
|
+
|
|
159
|
+
// 优先尝试从备份恢复
|
|
160
|
+
if (hasBackup()) {
|
|
161
|
+
restoreSettings();
|
|
162
|
+
console.log('✅ Restored settings from backup');
|
|
163
|
+
|
|
164
|
+
// 尝试找到匹配的渠道
|
|
165
|
+
const channels = getAllChannels();
|
|
166
|
+
const currentSettings = require('../services/channels').getCurrentSettings();
|
|
167
|
+
if (currentSettings) {
|
|
168
|
+
restoredChannel = channels.find(ch =>
|
|
169
|
+
ch.baseUrl === currentSettings.baseUrl && ch.apiKey === currentSettings.apiKey
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
// 没有备份,选择权重最高的启用渠道
|
|
174
|
+
const { getBestChannelForRestore, updateClaudeSettings } = require('../services/channels');
|
|
175
|
+
restoredChannel = getBestChannelForRestore();
|
|
176
|
+
|
|
177
|
+
if (restoredChannel) {
|
|
178
|
+
updateClaudeSettings(restoredChannel.baseUrl, restoredChannel.apiKey);
|
|
179
|
+
console.log(`✅ Restored settings to best channel: ${restoredChannel.name}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// 3. 删除备份文件和active-channel.json
|
|
184
|
+
if (hasBackup()) {
|
|
185
|
+
const backupPath = path.join(os.homedir(), '.claude', 'settings.json.cc-tool-backup');
|
|
186
|
+
if (fs.existsSync(backupPath)) {
|
|
187
|
+
fs.unlinkSync(backupPath);
|
|
188
|
+
console.log('✅ Removed backup file');
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const activeChannelPath = path.join(os.homedir(), '.claude', 'cc-tool', 'active-channel.json');
|
|
193
|
+
if (fs.existsSync(activeChannelPath)) {
|
|
194
|
+
fs.unlinkSync(activeChannelPath);
|
|
195
|
+
console.log('✅ Removed active-channel.json');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// 4. 通过 WebSocket 推送代理状态更新
|
|
199
|
+
const { broadcastProxyState } = require('../websocket-server');
|
|
200
|
+
const updatedStatus = getProxyStatus();
|
|
201
|
+
const channels = getAllChannels();
|
|
202
|
+
broadcastProxyState('claude', updatedStatus, null, channels);
|
|
203
|
+
|
|
204
|
+
if (restoredChannel) {
|
|
205
|
+
res.json({
|
|
206
|
+
success: true,
|
|
207
|
+
message: `代理已停止,配置已恢复到渠道: ${restoredChannel.name}`,
|
|
208
|
+
port: proxyResult.port,
|
|
209
|
+
restoredChannel: restoredChannel.name
|
|
210
|
+
});
|
|
211
|
+
} else {
|
|
212
|
+
res.json({
|
|
213
|
+
success: true,
|
|
214
|
+
message: '代理已停止(无配置可恢复)',
|
|
215
|
+
port: proxyResult.port
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
} catch (error) {
|
|
219
|
+
console.error('Error stopping proxy:', error);
|
|
220
|
+
res.status(500).json({ error: error.message });
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// 清空日志
|
|
225
|
+
router.post('/logs/clear', (req, res) => {
|
|
226
|
+
try {
|
|
227
|
+
clearAllLogs();
|
|
228
|
+
res.json({ success: true, message: '日志已清空' });
|
|
229
|
+
} catch (error) {
|
|
230
|
+
console.error('Error clearing logs:', error);
|
|
231
|
+
res.status(500).json({ error: error.message });
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
module.exports = router;
|