@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,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skills API 路由
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const express = require('express');
|
|
6
|
+
const { SkillService } = require('../services/skill-service');
|
|
7
|
+
|
|
8
|
+
const router = express.Router();
|
|
9
|
+
const skillService = new SkillService();
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 获取技能列表
|
|
13
|
+
* GET /api/skills
|
|
14
|
+
* Query: refresh=1 强制刷新缓存
|
|
15
|
+
*/
|
|
16
|
+
router.get('/', async (req, res) => {
|
|
17
|
+
try {
|
|
18
|
+
const forceRefresh = req.query.refresh === '1';
|
|
19
|
+
const skills = await skillService.listSkills(forceRefresh);
|
|
20
|
+
res.json({
|
|
21
|
+
success: true,
|
|
22
|
+
skills,
|
|
23
|
+
total: skills.length,
|
|
24
|
+
installed: skills.filter(s => s.installed).length
|
|
25
|
+
});
|
|
26
|
+
} catch (err) {
|
|
27
|
+
console.error('[Skills API] List skills error:', err);
|
|
28
|
+
res.status(500).json({
|
|
29
|
+
success: false,
|
|
30
|
+
message: err.message
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 获取技能详情(完整内容)
|
|
37
|
+
* GET /api/skills/detail/:directory
|
|
38
|
+
*/
|
|
39
|
+
router.get('/detail/*', async (req, res) => {
|
|
40
|
+
try {
|
|
41
|
+
const directory = req.params[0]; // 获取通配符匹配的路径
|
|
42
|
+
if (!directory) {
|
|
43
|
+
return res.status(400).json({
|
|
44
|
+
success: false,
|
|
45
|
+
message: 'Missing directory'
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const result = await skillService.getSkillDetail(directory);
|
|
50
|
+
res.json({
|
|
51
|
+
success: true,
|
|
52
|
+
...result
|
|
53
|
+
});
|
|
54
|
+
} catch (err) {
|
|
55
|
+
console.error('[Skills API] Get skill detail error:', err);
|
|
56
|
+
res.status(500).json({
|
|
57
|
+
success: false,
|
|
58
|
+
message: err.message
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 获取已安装的技能
|
|
65
|
+
* GET /api/skills/installed
|
|
66
|
+
*/
|
|
67
|
+
router.get('/installed', (req, res) => {
|
|
68
|
+
try {
|
|
69
|
+
const skills = skillService.getInstalledSkills();
|
|
70
|
+
res.json({
|
|
71
|
+
success: true,
|
|
72
|
+
skills
|
|
73
|
+
});
|
|
74
|
+
} catch (err) {
|
|
75
|
+
console.error('[Skills API] Get installed skills error:', err);
|
|
76
|
+
res.status(500).json({
|
|
77
|
+
success: false,
|
|
78
|
+
message: err.message
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 安装技能
|
|
85
|
+
* POST /api/skills/install
|
|
86
|
+
* Body: { directory, repo: { owner, name, branch } }
|
|
87
|
+
*/
|
|
88
|
+
router.post('/install', async (req, res) => {
|
|
89
|
+
try {
|
|
90
|
+
const { directory, repo } = req.body;
|
|
91
|
+
|
|
92
|
+
if (!directory) {
|
|
93
|
+
return res.status(400).json({
|
|
94
|
+
success: false,
|
|
95
|
+
message: 'Missing directory'
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!repo || !repo.owner || !repo.name) {
|
|
100
|
+
return res.status(400).json({
|
|
101
|
+
success: false,
|
|
102
|
+
message: 'Missing repo info'
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const result = await skillService.installSkill(directory, {
|
|
107
|
+
owner: repo.owner,
|
|
108
|
+
name: repo.name,
|
|
109
|
+
branch: repo.branch || 'main'
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
res.json({
|
|
113
|
+
success: true,
|
|
114
|
+
...result
|
|
115
|
+
});
|
|
116
|
+
} catch (err) {
|
|
117
|
+
console.error('[Skills API] Install skill error:', err);
|
|
118
|
+
res.status(500).json({
|
|
119
|
+
success: false,
|
|
120
|
+
message: err.message
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* 创建自定义技能
|
|
127
|
+
* POST /api/skills/create
|
|
128
|
+
* Body: { name, directory, description, content }
|
|
129
|
+
*/
|
|
130
|
+
router.post('/create', (req, res) => {
|
|
131
|
+
try {
|
|
132
|
+
const { name, directory, description, content } = req.body;
|
|
133
|
+
|
|
134
|
+
if (!directory) {
|
|
135
|
+
return res.status(400).json({
|
|
136
|
+
success: false,
|
|
137
|
+
message: '请输入目录名称'
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 校验目录名:只允许英文、数字、横杠、下划线
|
|
142
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(directory)) {
|
|
143
|
+
return res.status(400).json({
|
|
144
|
+
success: false,
|
|
145
|
+
message: '目录名只能包含英文、数字、横杠和下划线'
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (!content) {
|
|
150
|
+
return res.status(400).json({
|
|
151
|
+
success: false,
|
|
152
|
+
message: '请输入技能内容'
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const result = skillService.createCustomSkill({
|
|
157
|
+
name: name || directory,
|
|
158
|
+
directory,
|
|
159
|
+
description: description || '',
|
|
160
|
+
content
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
res.json({
|
|
164
|
+
success: true,
|
|
165
|
+
...result
|
|
166
|
+
});
|
|
167
|
+
} catch (err) {
|
|
168
|
+
console.error('[Skills API] Create skill error:', err);
|
|
169
|
+
res.status(500).json({
|
|
170
|
+
success: false,
|
|
171
|
+
message: err.message
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* 卸载技能
|
|
178
|
+
* POST /api/skills/uninstall
|
|
179
|
+
* Body: { directory }
|
|
180
|
+
*/
|
|
181
|
+
router.post('/uninstall', (req, res) => {
|
|
182
|
+
try {
|
|
183
|
+
const { directory } = req.body;
|
|
184
|
+
|
|
185
|
+
if (!directory) {
|
|
186
|
+
return res.status(400).json({
|
|
187
|
+
success: false,
|
|
188
|
+
message: 'Missing directory'
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const result = skillService.uninstallSkill(directory);
|
|
193
|
+
|
|
194
|
+
res.json({
|
|
195
|
+
success: true,
|
|
196
|
+
...result
|
|
197
|
+
});
|
|
198
|
+
} catch (err) {
|
|
199
|
+
console.error('[Skills API] Uninstall skill error:', err);
|
|
200
|
+
res.status(500).json({
|
|
201
|
+
success: false,
|
|
202
|
+
message: err.message
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* 获取仓库列表
|
|
209
|
+
* GET /api/skills/repos
|
|
210
|
+
*/
|
|
211
|
+
router.get('/repos', (req, res) => {
|
|
212
|
+
try {
|
|
213
|
+
const repos = skillService.loadRepos();
|
|
214
|
+
res.json({
|
|
215
|
+
success: true,
|
|
216
|
+
repos
|
|
217
|
+
});
|
|
218
|
+
} catch (err) {
|
|
219
|
+
console.error('[Skills API] Get repos error:', err);
|
|
220
|
+
res.status(500).json({
|
|
221
|
+
success: false,
|
|
222
|
+
message: err.message
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* 添加仓库
|
|
229
|
+
* POST /api/skills/repos
|
|
230
|
+
* Body: { owner, name, branch, enabled }
|
|
231
|
+
*/
|
|
232
|
+
router.post('/repos', (req, res) => {
|
|
233
|
+
try {
|
|
234
|
+
const { owner, name, branch = 'main', enabled = true } = req.body;
|
|
235
|
+
|
|
236
|
+
if (!owner || !name) {
|
|
237
|
+
return res.status(400).json({
|
|
238
|
+
success: false,
|
|
239
|
+
message: 'Missing owner or name'
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const repos = skillService.addRepo({ owner, name, branch, enabled });
|
|
244
|
+
|
|
245
|
+
res.json({
|
|
246
|
+
success: true,
|
|
247
|
+
repos
|
|
248
|
+
});
|
|
249
|
+
} catch (err) {
|
|
250
|
+
console.error('[Skills API] Add repo error:', err);
|
|
251
|
+
res.status(500).json({
|
|
252
|
+
success: false,
|
|
253
|
+
message: err.message
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* 删除仓库
|
|
260
|
+
* DELETE /api/skills/repos/:owner/:name
|
|
261
|
+
*/
|
|
262
|
+
router.delete('/repos/:owner/:name', (req, res) => {
|
|
263
|
+
try {
|
|
264
|
+
const { owner, name } = req.params;
|
|
265
|
+
const repos = skillService.removeRepo(owner, name);
|
|
266
|
+
|
|
267
|
+
res.json({
|
|
268
|
+
success: true,
|
|
269
|
+
repos
|
|
270
|
+
});
|
|
271
|
+
} catch (err) {
|
|
272
|
+
console.error('[Skills API] Remove repo error:', err);
|
|
273
|
+
res.status(500).json({
|
|
274
|
+
success: false,
|
|
275
|
+
message: err.message
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* 切换仓库启用状态
|
|
282
|
+
* PUT /api/skills/repos/:owner/:name/toggle
|
|
283
|
+
* Body: { enabled }
|
|
284
|
+
*/
|
|
285
|
+
router.put('/repos/:owner/:name/toggle', (req, res) => {
|
|
286
|
+
try {
|
|
287
|
+
const { owner, name } = req.params;
|
|
288
|
+
const { enabled } = req.body;
|
|
289
|
+
|
|
290
|
+
const repos = skillService.toggleRepo(owner, name, enabled);
|
|
291
|
+
|
|
292
|
+
res.json({
|
|
293
|
+
success: true,
|
|
294
|
+
repos
|
|
295
|
+
});
|
|
296
|
+
} catch (err) {
|
|
297
|
+
console.error('[Skills API] Toggle repo error:', err);
|
|
298
|
+
res.status(500).json({
|
|
299
|
+
success: false,
|
|
300
|
+
message: err.message
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
module.exports = router;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const router = express.Router();
|
|
3
|
+
const { getStatistics, getDailyStatistics, getTodayStatistics } = require('../services/statistics-service');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 获取总体统计数据
|
|
7
|
+
* GET /api/statistics/summary
|
|
8
|
+
*/
|
|
9
|
+
router.get('/summary', (req, res) => {
|
|
10
|
+
try {
|
|
11
|
+
const stats = getStatistics();
|
|
12
|
+
res.json(stats);
|
|
13
|
+
} catch (error) {
|
|
14
|
+
console.error('Failed to get statistics:', error);
|
|
15
|
+
res.status(500).json({ error: 'Failed to get statistics' });
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 获取今日统计数据
|
|
21
|
+
* GET /api/statistics/today
|
|
22
|
+
*/
|
|
23
|
+
router.get('/today', (req, res) => {
|
|
24
|
+
try {
|
|
25
|
+
const stats = getTodayStatistics();
|
|
26
|
+
res.json(stats);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error('Failed to get today statistics:', error);
|
|
29
|
+
res.status(500).json({ error: 'Failed to get today statistics' });
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 获取指定日期的统计数据
|
|
35
|
+
* GET /api/statistics/daily/:date
|
|
36
|
+
*
|
|
37
|
+
* @param {string} date - 日期,格式:YYYY-MM-DD
|
|
38
|
+
*/
|
|
39
|
+
router.get('/daily/:date', (req, res) => {
|
|
40
|
+
try {
|
|
41
|
+
const { date } = req.params;
|
|
42
|
+
|
|
43
|
+
// 验证日期格式
|
|
44
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
|
|
45
|
+
return res.status(400).json({ error: 'Invalid date format. Expected YYYY-MM-DD' });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const stats = getDailyStatistics(date);
|
|
49
|
+
res.json(stats);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error('Failed to get daily statistics:', error);
|
|
52
|
+
res.status(500).json({ error: 'Failed to get daily statistics' });
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 获取最近N天的统计数据
|
|
58
|
+
* GET /api/statistics/recent?days=7
|
|
59
|
+
*
|
|
60
|
+
* @query {number} days - 天数,默认7天
|
|
61
|
+
*/
|
|
62
|
+
router.get('/recent', (req, res) => {
|
|
63
|
+
try {
|
|
64
|
+
const days = parseInt(req.query.days) || 7;
|
|
65
|
+
|
|
66
|
+
if (days < 1 || days > 90) {
|
|
67
|
+
return res.status(400).json({ error: 'Days must be between 1 and 90' });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const result = [];
|
|
71
|
+
const today = new Date();
|
|
72
|
+
|
|
73
|
+
for (let i = 0; i < days; i++) {
|
|
74
|
+
const date = new Date(today);
|
|
75
|
+
date.setDate(date.getDate() - i);
|
|
76
|
+
const dateStr = date.toISOString().split('T')[0];
|
|
77
|
+
const stats = getDailyStatistics(dateStr);
|
|
78
|
+
result.push({
|
|
79
|
+
date: dateStr,
|
|
80
|
+
...stats
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
res.json(result);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.error('Failed to get recent statistics:', error);
|
|
87
|
+
res.status(500).json({ error: 'Failed to get recent statistics' });
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
module.exports = router;
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal REST API - Web 终端接口
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const express = require('express');
|
|
6
|
+
const router = express.Router();
|
|
7
|
+
const os = require('os');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
|
|
11
|
+
const { ptyManager } = require('../services/pty-manager');
|
|
12
|
+
const {
|
|
13
|
+
loadTerminalCommands,
|
|
14
|
+
saveTerminalCommands,
|
|
15
|
+
getCommandForChannel,
|
|
16
|
+
getDefaultCommands
|
|
17
|
+
} = require('../services/terminal-commands');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* GET /api/terminal/list - 获取所有活跃终端
|
|
21
|
+
*/
|
|
22
|
+
router.get('/list', (req, res) => {
|
|
23
|
+
try {
|
|
24
|
+
const terminals = ptyManager.list();
|
|
25
|
+
res.json({ success: true, terminals });
|
|
26
|
+
} catch (err) {
|
|
27
|
+
res.status(500).json({ success: false, error: err.message });
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* GET /api/terminal/commands/config - 获取命令配置
|
|
33
|
+
* 注意:此路由必须在 /:id 之前定义,否则会被动态路由捕获
|
|
34
|
+
*/
|
|
35
|
+
router.get('/commands/config', (req, res) => {
|
|
36
|
+
try {
|
|
37
|
+
const commands = loadTerminalCommands();
|
|
38
|
+
res.json({ success: true, commands });
|
|
39
|
+
} catch (err) {
|
|
40
|
+
res.status(500).json({ success: false, error: err.message });
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* PUT /api/terminal/commands/config - 保存命令配置
|
|
46
|
+
*/
|
|
47
|
+
router.put('/commands/config', (req, res) => {
|
|
48
|
+
try {
|
|
49
|
+
const { commands } = req.body;
|
|
50
|
+
if (!commands) {
|
|
51
|
+
return res.status(400).json({ success: false, error: 'Missing commands' });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const success = saveTerminalCommands(commands);
|
|
55
|
+
if (!success) {
|
|
56
|
+
return res.status(500).json({ success: false, error: 'Failed to save' });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
res.json({ success: true, commands: loadTerminalCommands() });
|
|
60
|
+
} catch (err) {
|
|
61
|
+
res.status(500).json({ success: false, error: err.message });
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* POST /api/terminal/commands/reset - 重置为默认配置
|
|
67
|
+
*/
|
|
68
|
+
router.post('/commands/reset', (req, res) => {
|
|
69
|
+
try {
|
|
70
|
+
const defaults = getDefaultCommands();
|
|
71
|
+
saveTerminalCommands(defaults);
|
|
72
|
+
res.json({ success: true, commands: defaults });
|
|
73
|
+
} catch (err) {
|
|
74
|
+
res.status(500).json({ success: false, error: err.message });
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* GET /api/terminal/:id - 获取终端详情
|
|
80
|
+
* 注意:动态路由必须放在静态路由之后
|
|
81
|
+
*/
|
|
82
|
+
router.get('/:id', (req, res) => {
|
|
83
|
+
try {
|
|
84
|
+
const terminal = ptyManager.get(req.params.id);
|
|
85
|
+
if (!terminal) {
|
|
86
|
+
return res.status(404).json({ success: false, error: 'Terminal not found' });
|
|
87
|
+
}
|
|
88
|
+
res.json({ success: true, terminal });
|
|
89
|
+
} catch (err) {
|
|
90
|
+
res.status(500).json({ success: false, error: err.message });
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* POST /api/terminal/create - 创建新终端
|
|
96
|
+
*/
|
|
97
|
+
router.post('/create', (req, res) => {
|
|
98
|
+
try {
|
|
99
|
+
const {
|
|
100
|
+
channel = 'claude',
|
|
101
|
+
sessionId = null,
|
|
102
|
+
projectName = null,
|
|
103
|
+
cwd = null
|
|
104
|
+
} = req.body;
|
|
105
|
+
|
|
106
|
+
// 确定工作目录
|
|
107
|
+
let workDir = cwd || os.homedir();
|
|
108
|
+
|
|
109
|
+
// 如果提供了项目名,尝试解析真实路径
|
|
110
|
+
if (projectName && !cwd) {
|
|
111
|
+
// 尝试从项目名解析路径
|
|
112
|
+
const projectsDir = path.join(os.homedir(), '.claude', 'projects');
|
|
113
|
+
const projectPath = path.join(projectsDir, projectName);
|
|
114
|
+
|
|
115
|
+
if (fs.existsSync(projectPath)) {
|
|
116
|
+
// 尝试读取会话文件获取 cwd
|
|
117
|
+
if (sessionId) {
|
|
118
|
+
const sessionFile = path.join(projectPath, sessionId + '.jsonl');
|
|
119
|
+
if (fs.existsSync(sessionFile)) {
|
|
120
|
+
try {
|
|
121
|
+
const content = fs.readFileSync(sessionFile, 'utf8');
|
|
122
|
+
const firstLine = content.split('\n')[0];
|
|
123
|
+
if (firstLine) {
|
|
124
|
+
const json = JSON.parse(firstLine);
|
|
125
|
+
if (json.cwd && fs.existsSync(json.cwd)) {
|
|
126
|
+
workDir = json.cwd;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
} catch (e) {
|
|
130
|
+
console.warn('Failed to parse session cwd:', e.message);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// 尝试从项目名直接解析路径 (URL 编码格式)
|
|
137
|
+
if (workDir === os.homedir()) {
|
|
138
|
+
const decodedPath = decodeURIComponent(projectName).replace(/-/g, '/');
|
|
139
|
+
if (fs.existsSync(decodedPath)) {
|
|
140
|
+
workDir = decodedPath;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// 获取启动命令
|
|
146
|
+
const startCommand = getCommandForChannel(channel, sessionId, workDir);
|
|
147
|
+
|
|
148
|
+
// 创建终端
|
|
149
|
+
const terminal = ptyManager.create({
|
|
150
|
+
cwd: workDir,
|
|
151
|
+
channel,
|
|
152
|
+
sessionId,
|
|
153
|
+
projectName,
|
|
154
|
+
startCommand
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
res.json({
|
|
158
|
+
success: true,
|
|
159
|
+
terminal: {
|
|
160
|
+
id: terminal.id,
|
|
161
|
+
pid: terminal.pid,
|
|
162
|
+
metadata: terminal.metadata
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
} catch (err) {
|
|
166
|
+
console.error('Failed to create terminal:', err);
|
|
167
|
+
res.status(500).json({ success: false, error: err.message });
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* DELETE /api/terminal/:id - 销毁终端
|
|
173
|
+
*/
|
|
174
|
+
router.delete('/:id', (req, res) => {
|
|
175
|
+
try {
|
|
176
|
+
const success = ptyManager.destroy(req.params.id);
|
|
177
|
+
if (!success) {
|
|
178
|
+
return res.status(404).json({ success: false, error: 'Terminal not found' });
|
|
179
|
+
}
|
|
180
|
+
res.json({ success: true });
|
|
181
|
+
} catch (err) {
|
|
182
|
+
res.status(500).json({ success: false, error: err.message });
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* POST /api/terminal/:id/resize - 调整终端大小
|
|
188
|
+
*/
|
|
189
|
+
router.post('/:id/resize', (req, res) => {
|
|
190
|
+
try {
|
|
191
|
+
const { cols, rows } = req.body;
|
|
192
|
+
const success = ptyManager.resize(req.params.id, cols, rows);
|
|
193
|
+
if (!success) {
|
|
194
|
+
return res.status(404).json({ success: false, error: 'Terminal not found' });
|
|
195
|
+
}
|
|
196
|
+
res.json({ success: true });
|
|
197
|
+
} catch (err) {
|
|
198
|
+
res.status(500).json({ success: false, error: err.message });
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
module.exports = router;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const router = express.Router();
|
|
3
|
+
const {
|
|
4
|
+
loadUIConfig,
|
|
5
|
+
saveUIConfig,
|
|
6
|
+
updateUIConfig,
|
|
7
|
+
updateNestedUIConfig
|
|
8
|
+
} = require('../services/ui-config');
|
|
9
|
+
|
|
10
|
+
// Get all UI config
|
|
11
|
+
router.get('/', (req, res) => {
|
|
12
|
+
try {
|
|
13
|
+
const config = loadUIConfig();
|
|
14
|
+
res.json({ success: true, config });
|
|
15
|
+
} catch (error) {
|
|
16
|
+
console.error('Error getting UI config:', error);
|
|
17
|
+
res.status(500).json({ error: error.message });
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// Update entire UI config
|
|
22
|
+
router.post('/', (req, res) => {
|
|
23
|
+
try {
|
|
24
|
+
const { config } = req.body;
|
|
25
|
+
if (!config) {
|
|
26
|
+
return res.status(400).json({ error: 'Missing config' });
|
|
27
|
+
}
|
|
28
|
+
saveUIConfig(config);
|
|
29
|
+
res.json({ success: true, config });
|
|
30
|
+
} catch (error) {
|
|
31
|
+
console.error('Error saving UI config:', error);
|
|
32
|
+
res.status(500).json({ error: error.message });
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Update specific config key
|
|
37
|
+
router.put('/:key', (req, res) => {
|
|
38
|
+
try {
|
|
39
|
+
const { key } = req.params;
|
|
40
|
+
const { value } = req.body;
|
|
41
|
+
|
|
42
|
+
const config = updateUIConfig(key, value);
|
|
43
|
+
res.json({ success: true, config });
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error('Error updating UI config:', error);
|
|
46
|
+
res.status(500).json({ error: error.message });
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Update nested config
|
|
51
|
+
router.put('/:parentKey/:childKey', (req, res) => {
|
|
52
|
+
try {
|
|
53
|
+
const { parentKey, childKey } = req.params;
|
|
54
|
+
const { value } = req.body;
|
|
55
|
+
|
|
56
|
+
const config = updateNestedUIConfig(parentKey, childKey, value);
|
|
57
|
+
res.json({ success: true, config });
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error('Error updating nested UI config:', error);
|
|
60
|
+
res.status(500).json({ error: error.message });
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
module.exports = router;
|