@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.
Files changed (125) hide show
  1. package/CHANGELOG.md +333 -0
  2. package/LICENSE +21 -0
  3. package/README.md +404 -0
  4. package/bin/ctx.js +8 -0
  5. package/dist/web/assets/index-D1AYlFLZ.js +3220 -0
  6. package/dist/web/assets/index-aL3cKxSK.css +41 -0
  7. package/dist/web/favicon.ico +0 -0
  8. package/dist/web/index.html +14 -0
  9. package/dist/web/logo.png +0 -0
  10. package/docs/CHANGELOG.md +582 -0
  11. package/docs/DIRECTORY_MIGRATION.md +112 -0
  12. package/docs/PROJECT_STRUCTURE.md +396 -0
  13. package/docs/bannel.png +0 -0
  14. package/docs/home.png +0 -0
  15. package/docs/logo.png +0 -0
  16. package/docs/multi-channel-load-balancing.md +249 -0
  17. package/package.json +73 -0
  18. package/src/commands/channels.js +504 -0
  19. package/src/commands/cli-type.js +99 -0
  20. package/src/commands/daemon.js +286 -0
  21. package/src/commands/doctor.js +332 -0
  22. package/src/commands/list.js +222 -0
  23. package/src/commands/logs.js +259 -0
  24. package/src/commands/port-config.js +115 -0
  25. package/src/commands/proxy-control.js +258 -0
  26. package/src/commands/proxy.js +152 -0
  27. package/src/commands/resume.js +137 -0
  28. package/src/commands/search.js +190 -0
  29. package/src/commands/stats.js +224 -0
  30. package/src/commands/switch.js +48 -0
  31. package/src/commands/toggle-proxy.js +222 -0
  32. package/src/commands/ui.js +92 -0
  33. package/src/commands/workspace.js +454 -0
  34. package/src/config/default.js +40 -0
  35. package/src/config/loader.js +75 -0
  36. package/src/config/paths.js +121 -0
  37. package/src/index.js +373 -0
  38. package/src/reset-config.js +92 -0
  39. package/src/server/api/agents.js +248 -0
  40. package/src/server/api/aliases.js +36 -0
  41. package/src/server/api/channels.js +258 -0
  42. package/src/server/api/claude-hooks.js +480 -0
  43. package/src/server/api/codex-channels.js +312 -0
  44. package/src/server/api/codex-projects.js +91 -0
  45. package/src/server/api/codex-proxy.js +182 -0
  46. package/src/server/api/codex-sessions.js +491 -0
  47. package/src/server/api/codex-statistics.js +57 -0
  48. package/src/server/api/commands.js +245 -0
  49. package/src/server/api/config-templates.js +182 -0
  50. package/src/server/api/config.js +147 -0
  51. package/src/server/api/convert.js +127 -0
  52. package/src/server/api/dashboard.js +125 -0
  53. package/src/server/api/env.js +144 -0
  54. package/src/server/api/favorites.js +77 -0
  55. package/src/server/api/gemini-channels.js +261 -0
  56. package/src/server/api/gemini-projects.js +91 -0
  57. package/src/server/api/gemini-proxy.js +160 -0
  58. package/src/server/api/gemini-sessions.js +397 -0
  59. package/src/server/api/gemini-statistics.js +57 -0
  60. package/src/server/api/health-check.js +118 -0
  61. package/src/server/api/mcp.js +336 -0
  62. package/src/server/api/pm2-autostart.js +269 -0
  63. package/src/server/api/projects.js +124 -0
  64. package/src/server/api/prompts.js +279 -0
  65. package/src/server/api/proxy.js +235 -0
  66. package/src/server/api/rules.js +271 -0
  67. package/src/server/api/sessions.js +595 -0
  68. package/src/server/api/settings.js +61 -0
  69. package/src/server/api/skills.js +305 -0
  70. package/src/server/api/statistics.js +91 -0
  71. package/src/server/api/terminal.js +202 -0
  72. package/src/server/api/ui-config.js +64 -0
  73. package/src/server/api/workspaces.js +407 -0
  74. package/src/server/codex-proxy-server.js +538 -0
  75. package/src/server/dev-server.js +26 -0
  76. package/src/server/gemini-proxy-server.js +518 -0
  77. package/src/server/index.js +305 -0
  78. package/src/server/proxy-server.js +469 -0
  79. package/src/server/services/agents-service.js +354 -0
  80. package/src/server/services/alias.js +71 -0
  81. package/src/server/services/channel-health.js +234 -0
  82. package/src/server/services/channel-scheduler.js +234 -0
  83. package/src/server/services/channels.js +347 -0
  84. package/src/server/services/codex-channels.js +625 -0
  85. package/src/server/services/codex-config.js +90 -0
  86. package/src/server/services/codex-parser.js +322 -0
  87. package/src/server/services/codex-sessions.js +665 -0
  88. package/src/server/services/codex-settings-manager.js +397 -0
  89. package/src/server/services/codex-speed-test-template.json +24 -0
  90. package/src/server/services/codex-statistics-service.js +255 -0
  91. package/src/server/services/commands-service.js +360 -0
  92. package/src/server/services/config-templates-service.js +732 -0
  93. package/src/server/services/env-checker.js +307 -0
  94. package/src/server/services/env-manager.js +300 -0
  95. package/src/server/services/favorites.js +163 -0
  96. package/src/server/services/gemini-channels.js +333 -0
  97. package/src/server/services/gemini-config.js +73 -0
  98. package/src/server/services/gemini-sessions.js +689 -0
  99. package/src/server/services/gemini-settings-manager.js +263 -0
  100. package/src/server/services/gemini-statistics-service.js +253 -0
  101. package/src/server/services/health-check.js +399 -0
  102. package/src/server/services/mcp-service.js +1188 -0
  103. package/src/server/services/prompts-service.js +492 -0
  104. package/src/server/services/proxy-runtime.js +79 -0
  105. package/src/server/services/pty-manager.js +435 -0
  106. package/src/server/services/rules-service.js +401 -0
  107. package/src/server/services/session-cache.js +127 -0
  108. package/src/server/services/session-converter.js +577 -0
  109. package/src/server/services/sessions.js +757 -0
  110. package/src/server/services/settings-manager.js +163 -0
  111. package/src/server/services/skill-service.js +965 -0
  112. package/src/server/services/speed-test.js +545 -0
  113. package/src/server/services/statistics-service.js +386 -0
  114. package/src/server/services/terminal-commands.js +155 -0
  115. package/src/server/services/terminal-config.js +140 -0
  116. package/src/server/services/terminal-detector.js +306 -0
  117. package/src/server/services/ui-config.js +130 -0
  118. package/src/server/services/workspace-service.js +662 -0
  119. package/src/server/utils/pricing.js +41 -0
  120. package/src/server/websocket-server.js +557 -0
  121. package/src/ui/menu.js +129 -0
  122. package/src/ui/prompts.js +100 -0
  123. package/src/utils/format.js +43 -0
  124. package/src/utils/port-helper.js +94 -0
  125. package/src/utils/session.js +239 -0
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Commands API 路由
3
+ *
4
+ * 管理 Claude Code 自定义命令
5
+ */
6
+
7
+ const express = require('express');
8
+ const { CommandsService } = require('../services/commands-service');
9
+
10
+ const router = express.Router();
11
+ const commandsService = new CommandsService();
12
+
13
+ /**
14
+ * 获取命令列表
15
+ * GET /api/commands
16
+ * Query: projectPath - 项目路径(可选,用于获取项目级命令)
17
+ */
18
+ router.get('/', (req, res) => {
19
+ try {
20
+ const { projectPath } = req.query;
21
+ const result = commandsService.listCommands(projectPath || null);
22
+
23
+ res.json({
24
+ success: true,
25
+ ...result
26
+ });
27
+ } catch (err) {
28
+ console.error('[Commands API] List commands error:', err);
29
+ res.status(500).json({
30
+ success: false,
31
+ message: err.message
32
+ });
33
+ }
34
+ });
35
+
36
+ /**
37
+ * 获取命令统计
38
+ * GET /api/commands/stats
39
+ */
40
+ router.get('/stats', (req, res) => {
41
+ try {
42
+ const { projectPath } = req.query;
43
+ const stats = commandsService.getStats(projectPath || null);
44
+
45
+ res.json({
46
+ success: true,
47
+ ...stats
48
+ });
49
+ } catch (err) {
50
+ console.error('[Commands API] Get stats error:', err);
51
+ res.status(500).json({
52
+ success: false,
53
+ message: err.message
54
+ });
55
+ }
56
+ });
57
+
58
+ /**
59
+ * 获取单个命令详情
60
+ * GET /api/commands/:scope/:name
61
+ * GET /api/commands/:scope/ns/:namespace/:name
62
+ */
63
+ router.get('/:scope/:name', (req, res) => {
64
+ try {
65
+ const { scope, name } = req.params;
66
+ const { projectPath, namespace } = req.query;
67
+
68
+ if (!['user', 'project'].includes(scope)) {
69
+ return res.status(400).json({
70
+ success: false,
71
+ message: '无效的 scope,必须是 user 或 project'
72
+ });
73
+ }
74
+
75
+ if (scope === 'project' && !projectPath) {
76
+ return res.status(400).json({
77
+ success: false,
78
+ message: '获取项目级命令需要提供 projectPath'
79
+ });
80
+ }
81
+
82
+ const command = commandsService.getCommand(name, scope, projectPath || null, namespace || null);
83
+
84
+ if (!command) {
85
+ return res.status(404).json({
86
+ success: false,
87
+ message: `命令 "${name}" 不存在`
88
+ });
89
+ }
90
+
91
+ res.json({
92
+ success: true,
93
+ command
94
+ });
95
+ } catch (err) {
96
+ console.error('[Commands API] Get command error:', err);
97
+ res.status(500).json({
98
+ success: false,
99
+ message: err.message
100
+ });
101
+ }
102
+ });
103
+
104
+ /**
105
+ * 创建命令
106
+ * POST /api/commands
107
+ * Body: { name, scope, projectPath?, namespace?, description?, allowedTools?, argumentHint?, body }
108
+ */
109
+ router.post('/', (req, res) => {
110
+ try {
111
+ const { name, scope, projectPath, namespace, description, allowedTools, argumentHint, body } = req.body;
112
+
113
+ if (!name) {
114
+ return res.status(400).json({
115
+ success: false,
116
+ message: '命令名称不能为空'
117
+ });
118
+ }
119
+
120
+ if (!['user', 'project'].includes(scope)) {
121
+ return res.status(400).json({
122
+ success: false,
123
+ message: '无效的 scope,必须是 user 或 project'
124
+ });
125
+ }
126
+
127
+ if (scope === 'project' && !projectPath) {
128
+ return res.status(400).json({
129
+ success: false,
130
+ message: '创建项目级命令需要提供 projectPath'
131
+ });
132
+ }
133
+
134
+ const command = commandsService.createCommand({
135
+ name,
136
+ scope,
137
+ projectPath: projectPath || null,
138
+ namespace: namespace || null,
139
+ description: description || '',
140
+ allowedTools: allowedTools || '',
141
+ argumentHint: argumentHint || '',
142
+ body: body || ''
143
+ });
144
+
145
+ res.json({
146
+ success: true,
147
+ command,
148
+ message: '命令创建成功'
149
+ });
150
+ } catch (err) {
151
+ console.error('[Commands API] Create command error:', err);
152
+ res.status(500).json({
153
+ success: false,
154
+ message: err.message
155
+ });
156
+ }
157
+ });
158
+
159
+ /**
160
+ * 更新命令
161
+ * PUT /api/commands/:scope/:name
162
+ */
163
+ router.put('/:scope/:name', (req, res) => {
164
+ try {
165
+ const { scope, name } = req.params;
166
+ const { projectPath, namespace, description, allowedTools, argumentHint, body } = req.body;
167
+
168
+ if (!['user', 'project'].includes(scope)) {
169
+ return res.status(400).json({
170
+ success: false,
171
+ message: '无效的 scope,必须是 user 或 project'
172
+ });
173
+ }
174
+
175
+ if (scope === 'project' && !projectPath) {
176
+ return res.status(400).json({
177
+ success: false,
178
+ message: '更新项目级命令需要提供 projectPath'
179
+ });
180
+ }
181
+
182
+ const command = commandsService.updateCommand({
183
+ name,
184
+ scope,
185
+ projectPath: projectPath || null,
186
+ namespace: namespace || null,
187
+ description: description || '',
188
+ allowedTools: allowedTools || '',
189
+ argumentHint: argumentHint || '',
190
+ body: body || ''
191
+ });
192
+
193
+ res.json({
194
+ success: true,
195
+ command,
196
+ message: '命令更新成功'
197
+ });
198
+ } catch (err) {
199
+ console.error('[Commands API] Update command error:', err);
200
+ res.status(500).json({
201
+ success: false,
202
+ message: err.message
203
+ });
204
+ }
205
+ });
206
+
207
+ /**
208
+ * 删除命令
209
+ * DELETE /api/commands/:scope/:name
210
+ */
211
+ router.delete('/:scope/:name', (req, res) => {
212
+ try {
213
+ const { scope, name } = req.params;
214
+ const { projectPath, namespace } = req.query;
215
+
216
+ if (!['user', 'project'].includes(scope)) {
217
+ return res.status(400).json({
218
+ success: false,
219
+ message: '无效的 scope,必须是 user 或 project'
220
+ });
221
+ }
222
+
223
+ if (scope === 'project' && !projectPath) {
224
+ return res.status(400).json({
225
+ success: false,
226
+ message: '删除项目级命令需要提供 projectPath'
227
+ });
228
+ }
229
+
230
+ const result = commandsService.deleteCommand(name, scope, projectPath || null, namespace || null);
231
+
232
+ res.json({
233
+ success: result.success,
234
+ message: result.message
235
+ });
236
+ } catch (err) {
237
+ console.error('[Commands API] Delete command error:', err);
238
+ res.status(500).json({
239
+ success: false,
240
+ message: err.message
241
+ });
242
+ }
243
+ });
244
+
245
+ module.exports = router;
@@ -0,0 +1,182 @@
1
+ // 配置模板 API 路由
2
+ const express = require('express');
3
+ const router = express.Router();
4
+ const templatesService = require('../services/config-templates-service');
5
+
6
+ /**
7
+ * GET /api/config-templates
8
+ * 获取所有配置模板
9
+ */
10
+ router.get('/', (req, res) => {
11
+ try {
12
+ const templates = templatesService.getAllTemplates();
13
+ res.json({
14
+ success: true,
15
+ data: templates
16
+ });
17
+ } catch (error) {
18
+ res.status(500).json({
19
+ success: false,
20
+ message: error.message
21
+ });
22
+ }
23
+ });
24
+
25
+ /**
26
+ * GET /api/config-templates/available-configs
27
+ * 获取可用的配置项(用于模板编辑器选择)
28
+ * 注意:此路由必须在 /:id 之前定义
29
+ */
30
+ router.get('/available-configs', (req, res) => {
31
+ try {
32
+ const configs = templatesService.getAvailableConfigs();
33
+ res.json({
34
+ success: true,
35
+ data: configs
36
+ });
37
+ } catch (error) {
38
+ res.status(500).json({
39
+ success: false,
40
+ message: error.message
41
+ });
42
+ }
43
+ });
44
+
45
+ /**
46
+ * GET /api/config-templates/:id
47
+ * 获取单个配置模板详情
48
+ */
49
+ router.get('/:id', (req, res) => {
50
+ try {
51
+ const template = templatesService.getTemplateById(req.params.id);
52
+ if (!template) {
53
+ return res.status(404).json({
54
+ success: false,
55
+ message: '模板不存在'
56
+ });
57
+ }
58
+ res.json({
59
+ success: true,
60
+ data: template
61
+ });
62
+ } catch (error) {
63
+ res.status(500).json({
64
+ success: false,
65
+ message: error.message
66
+ });
67
+ }
68
+ });
69
+
70
+ /**
71
+ * POST /api/config-templates
72
+ * 创建自定义配置模板
73
+ */
74
+ router.post('/', (req, res) => {
75
+ try {
76
+ const template = templatesService.createCustomTemplate(req.body);
77
+ res.json({
78
+ success: true,
79
+ message: '模板创建成功',
80
+ data: template
81
+ });
82
+ } catch (error) {
83
+ res.status(400).json({
84
+ success: false,
85
+ message: error.message
86
+ });
87
+ }
88
+ });
89
+
90
+ /**
91
+ * PUT /api/config-templates/:id
92
+ * 更新自定义配置模板
93
+ */
94
+ router.put('/:id', (req, res) => {
95
+ try {
96
+ const template = templatesService.updateCustomTemplate(req.params.id, req.body);
97
+ res.json({
98
+ success: true,
99
+ message: '模板更新成功',
100
+ data: template
101
+ });
102
+ } catch (error) {
103
+ res.status(400).json({
104
+ success: false,
105
+ message: error.message
106
+ });
107
+ }
108
+ });
109
+
110
+ /**
111
+ * DELETE /api/config-templates/:id
112
+ * 删除自定义配置模板
113
+ */
114
+ router.delete('/:id', (req, res) => {
115
+ try {
116
+ templatesService.deleteCustomTemplate(req.params.id);
117
+ res.json({
118
+ success: true,
119
+ message: '模板删除成功'
120
+ });
121
+ } catch (error) {
122
+ res.status(400).json({
123
+ success: false,
124
+ message: error.message
125
+ });
126
+ }
127
+ });
128
+
129
+ /**
130
+ * POST /api/config-templates/:id/apply
131
+ * 应用模板到项目目录
132
+ */
133
+ router.post('/:id/apply', (req, res) => {
134
+ try {
135
+ const { targetPath } = req.body;
136
+ if (!targetPath) {
137
+ return res.status(400).json({
138
+ success: false,
139
+ message: '目标路径不能为空'
140
+ });
141
+ }
142
+ const result = templatesService.applyTemplateToProject(targetPath, req.params.id);
143
+ res.json({
144
+ success: true,
145
+ message: '模板应用成功',
146
+ data: result
147
+ });
148
+ } catch (error) {
149
+ res.status(400).json({
150
+ success: false,
151
+ message: error.message
152
+ });
153
+ }
154
+ });
155
+
156
+ /**
157
+ * POST /api/config-templates/:id/preview
158
+ * 预览模板应用效果
159
+ */
160
+ router.post('/:id/preview', (req, res) => {
161
+ try {
162
+ const { targetPath } = req.body;
163
+ if (!targetPath) {
164
+ return res.status(400).json({
165
+ success: false,
166
+ message: '目标路径不能为空'
167
+ });
168
+ }
169
+ const preview = templatesService.previewTemplateApplication(targetPath, req.params.id);
170
+ res.json({
171
+ success: true,
172
+ data: preview
173
+ });
174
+ } catch (error) {
175
+ res.status(400).json({
176
+ success: false,
177
+ message: error.message
178
+ });
179
+ }
180
+ });
181
+
182
+ module.exports = router;
@@ -0,0 +1,147 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const { loadConfig, saveConfig } = require('../../config/loader');
4
+ const DEFAULT_CONFIG = require('../../config/default');
5
+
6
+ function clampNumber(value, fallback) {
7
+ const num = typeof value === 'number' ? value : parseFloat(value);
8
+ if (!Number.isFinite(num)) {
9
+ return fallback;
10
+ }
11
+ if (num < 0) return 0;
12
+ if (num > 1000) return 1000;
13
+ return Math.round(num * 1000000) / 1000000;
14
+ }
15
+
16
+ function sanitizePricing(inputPricing, currentPricing) {
17
+ const defaults = DEFAULT_CONFIG.pricing;
18
+ const sanitized = {};
19
+
20
+ Object.keys(defaults).forEach((toolKey) => {
21
+ const defaultValue = defaults[toolKey];
22
+ const existingValue = currentPricing?.[toolKey] || {};
23
+ const payload = inputPricing?.[toolKey] || {};
24
+
25
+ const mode = payload.mode === 'custom' ? 'custom' : (existingValue.mode || defaultValue.mode || 'auto');
26
+ sanitized[toolKey] = { mode };
27
+
28
+ Object.keys(defaultValue)
29
+ .filter((key) => key !== 'mode')
30
+ .forEach((rateKey) => {
31
+ const fallback = existingValue[rateKey] !== undefined ? existingValue[rateKey] : defaultValue[rateKey];
32
+ sanitized[toolKey][rateKey] = clampNumber(payload[rateKey], fallback);
33
+ });
34
+ });
35
+
36
+ return sanitized;
37
+ }
38
+
39
+ /**
40
+ * GET /api/config/advanced
41
+ * 获取高级配置(端口、日志、性能等)
42
+ */
43
+ router.get('/advanced', (req, res) => {
44
+ try {
45
+ const config = loadConfig();
46
+ res.json({
47
+ ports: {
48
+ webUI: config.ports?.webUI || 10099,
49
+ proxy: config.ports?.proxy || 10088,
50
+ codexProxy: config.ports?.codexProxy || 10089,
51
+ geminiProxy: config.ports?.geminiProxy || 10090
52
+ },
53
+ maxLogs: config.maxLogs || 100,
54
+ statsInterval: config.statsInterval || 30,
55
+ enableSessionBinding: config.enableSessionBinding !== false, // 默认开启
56
+ pricing: config.pricing || DEFAULT_CONFIG.pricing
57
+ });
58
+ } catch (error) {
59
+ console.error('[Config API] Failed to get advanced config:', error);
60
+ res.status(500).json({ error: error.message });
61
+ }
62
+ });
63
+
64
+ /**
65
+ * POST /api/config/advanced
66
+ * 保存高级配置
67
+ */
68
+ router.post('/advanced', (req, res) => {
69
+ try {
70
+ const { ports, maxLogs, statsInterval, pricing, enableSessionBinding } = req.body;
71
+
72
+ // 验证端口
73
+ if (ports) {
74
+ for (const [key, value] of Object.entries(ports)) {
75
+ const port = parseInt(value);
76
+ if (isNaN(port) || port < 1024 || port > 65535) {
77
+ return res.status(400).json({
78
+ error: `Invalid port for ${key}: must be between 1024-65535`
79
+ });
80
+ }
81
+ }
82
+ }
83
+
84
+ // 验证日志数量
85
+ if (maxLogs !== undefined) {
86
+ const logs = parseInt(maxLogs);
87
+ if (isNaN(logs) || logs < 50 || logs > 500) {
88
+ return res.status(400).json({
89
+ error: 'maxLogs must be between 50-500'
90
+ });
91
+ }
92
+ }
93
+
94
+ // 验证刷新间隔
95
+ if (statsInterval !== undefined) {
96
+ const interval = parseInt(statsInterval);
97
+ if (isNaN(interval) || interval < 10 || interval > 300) {
98
+ return res.status(400).json({
99
+ error: 'statsInterval must be between 10-300'
100
+ });
101
+ }
102
+ }
103
+
104
+ // 加载当前配置
105
+ const config = loadConfig();
106
+ const sanitizedPricing = sanitizePricing(pricing, config.pricing);
107
+
108
+ let normalizedPorts = config.ports;
109
+ if (ports) {
110
+ normalizedPorts = { ...config.ports };
111
+ Object.entries(ports).forEach(([key, value]) => {
112
+ const port = parseInt(value);
113
+ normalizedPorts[key] = port;
114
+ });
115
+ }
116
+
117
+ // 更新配置
118
+ const newConfig = {
119
+ ...config,
120
+ projectsDir: config.projectsDir.replace(require('os').homedir(), '~'),
121
+ ports: normalizedPorts,
122
+ maxLogs: maxLogs !== undefined ? parseInt(maxLogs) : config.maxLogs,
123
+ statsInterval: statsInterval !== undefined ? parseInt(statsInterval) : config.statsInterval,
124
+ enableSessionBinding: enableSessionBinding !== undefined ? enableSessionBinding : (config.enableSessionBinding !== false),
125
+ pricing: sanitizedPricing
126
+ };
127
+
128
+ // 保存配置
129
+ saveConfig(newConfig);
130
+
131
+ res.json({
132
+ success: true,
133
+ config: {
134
+ ports: newConfig.ports,
135
+ maxLogs: newConfig.maxLogs,
136
+ statsInterval: newConfig.statsInterval,
137
+ enableSessionBinding: newConfig.enableSessionBinding,
138
+ pricing: newConfig.pricing
139
+ }
140
+ });
141
+ } catch (error) {
142
+ console.error('[Config API] Failed to save advanced config:', error);
143
+ res.status(500).json({ error: error.message });
144
+ }
145
+ });
146
+
147
+ module.exports = router;
@@ -0,0 +1,127 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const { convertSession, previewConversion } = require('../services/session-converter');
4
+
5
+ /**
6
+ * 获取支持的格式列表
7
+ * GET /api/convert/formats
8
+ */
9
+ router.get('/formats', (req, res) => {
10
+ res.json({
11
+ formats: [
12
+ {
13
+ id: 'claude',
14
+ name: 'Claude Code',
15
+ description: 'Anthropic Claude Code CLI session format',
16
+ extension: '.jsonl',
17
+ icon: 'claude'
18
+ },
19
+ {
20
+ id: 'codex',
21
+ name: 'OpenAI Codex',
22
+ description: 'OpenAI Codex CLI session format',
23
+ extension: '.jsonl',
24
+ icon: 'codex'
25
+ },
26
+ {
27
+ id: 'gemini',
28
+ name: 'Google Gemini',
29
+ description: 'Google Gemini CLI session format',
30
+ extension: '.json',
31
+ icon: 'gemini'
32
+ }
33
+ ],
34
+ conversions: [
35
+ { from: 'claude', to: 'codex' },
36
+ { from: 'claude', to: 'gemini' },
37
+ { from: 'codex', to: 'claude' },
38
+ { from: 'codex', to: 'gemini' },
39
+ { from: 'gemini', to: 'claude' },
40
+ { from: 'gemini', to: 'codex' }
41
+ ]
42
+ });
43
+ });
44
+
45
+ /**
46
+ * 预览转换结果
47
+ * POST /api/convert/preview
48
+ * Body: { sourceType, sessionId }
49
+ */
50
+ router.post('/preview', async (req, res) => {
51
+ try {
52
+ const { sourceType, sessionId } = req.body;
53
+
54
+ if (!sourceType || !sessionId) {
55
+ return res.status(400).json({
56
+ success: false,
57
+ error: 'Missing required parameters: sourceType, sessionId'
58
+ });
59
+ }
60
+
61
+ const preview = await previewConversion(sourceType, sessionId);
62
+
63
+ res.json({
64
+ success: true,
65
+ preview
66
+ });
67
+ } catch (error) {
68
+ console.error('[Convert API] Preview error:', error);
69
+ res.status(500).json({
70
+ success: false,
71
+ error: error.message
72
+ });
73
+ }
74
+ });
75
+
76
+ /**
77
+ * 执行会话转换
78
+ * POST /api/convert
79
+ * Body: { sourceType, targetType, sessionId, options }
80
+ */
81
+ router.post('/', async (req, res) => {
82
+ try {
83
+ const { sourceType, targetType, sessionId, options = {} } = req.body;
84
+
85
+ // 验证必需参数
86
+ if (!sourceType || !targetType || !sessionId) {
87
+ return res.status(400).json({
88
+ success: false,
89
+ error: 'Missing required parameters: sourceType, targetType, sessionId'
90
+ });
91
+ }
92
+
93
+ // 验证格式
94
+ const validTypes = ['claude', 'codex', 'gemini'];
95
+ if (!validTypes.includes(sourceType)) {
96
+ return res.status(400).json({
97
+ success: false,
98
+ error: `Invalid sourceType: ${sourceType}. Must be one of: ${validTypes.join(', ')}`
99
+ });
100
+ }
101
+ if (!validTypes.includes(targetType)) {
102
+ return res.status(400).json({
103
+ success: false,
104
+ error: `Invalid targetType: ${targetType}. Must be one of: ${validTypes.join(', ')}`
105
+ });
106
+ }
107
+ if (sourceType === targetType) {
108
+ return res.status(400).json({
109
+ success: false,
110
+ error: 'Source and target types must be different'
111
+ });
112
+ }
113
+
114
+ // 执行转换
115
+ const result = await convertSession(sourceType, targetType, sessionId, options);
116
+
117
+ res.json(result);
118
+ } catch (error) {
119
+ console.error('[Convert API] Conversion error:', error);
120
+ res.status(500).json({
121
+ success: false,
122
+ error: error.message
123
+ });
124
+ }
125
+ });
126
+
127
+ module.exports = router;