coding-tool-x 3.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 (185) hide show
  1. package/CHANGELOG.md +599 -0
  2. package/LICENSE +21 -0
  3. package/README.md +439 -0
  4. package/bin/ctx.js +8 -0
  5. package/dist/web/assets/Analytics-DN_YsnkW.js +39 -0
  6. package/dist/web/assets/Analytics-DuYvId7u.css +1 -0
  7. package/dist/web/assets/ConfigTemplates-Bidwfdf2.css +1 -0
  8. package/dist/web/assets/ConfigTemplates-DpXIMy0p.js +1 -0
  9. package/dist/web/assets/Home-38JTUlYt.js +1 -0
  10. package/dist/web/assets/Home-CjupSEWE.css +1 -0
  11. package/dist/web/assets/PluginManager-CX2tgq2H.js +1 -0
  12. package/dist/web/assets/PluginManager-ROyoZ-6m.css +1 -0
  13. package/dist/web/assets/ProjectList-C1lDcsn6.js +1 -0
  14. package/dist/web/assets/ProjectList-oJIyIRkP.css +1 -0
  15. package/dist/web/assets/SessionList-C55tjV7i.css +1 -0
  16. package/dist/web/assets/SessionList-CZ7T6rVx.js +1 -0
  17. package/dist/web/assets/SkillManager-D7pd-d_P.css +1 -0
  18. package/dist/web/assets/SkillManager-DLN9f79y.js +1 -0
  19. package/dist/web/assets/WorkspaceManager-CrwgQgmP.css +1 -0
  20. package/dist/web/assets/WorkspaceManager-DxlHZkpZ.js +1 -0
  21. package/dist/web/assets/icons-DRrXwWZi.js +1 -0
  22. package/dist/web/assets/index-CetESrXw.css +1 -0
  23. package/dist/web/assets/index-Cfvn-2Gb.js +2 -0
  24. package/dist/web/assets/markdown-BfC0goYb.css +10 -0
  25. package/dist/web/assets/markdown-C9MYpaSi.js +1 -0
  26. package/dist/web/assets/naive-ui-DlpKk-8M.js +1 -0
  27. package/dist/web/assets/vendors-DMjSfzlv.js +7 -0
  28. package/dist/web/assets/vue-vendor-DET08QYg.js +45 -0
  29. package/dist/web/favicon.ico +0 -0
  30. package/dist/web/index.html +20 -0
  31. package/dist/web/logo.png +0 -0
  32. package/docs/bannel.png +0 -0
  33. package/docs/home.png +0 -0
  34. package/docs/logo.png +0 -0
  35. package/docs/model-redirection.md +251 -0
  36. package/docs/multi-channel-load-balancing.md +249 -0
  37. package/package.json +80 -0
  38. package/src/commands/channels.js +551 -0
  39. package/src/commands/cli-type.js +101 -0
  40. package/src/commands/daemon.js +365 -0
  41. package/src/commands/doctor.js +333 -0
  42. package/src/commands/export-config.js +205 -0
  43. package/src/commands/list.js +222 -0
  44. package/src/commands/logs.js +261 -0
  45. package/src/commands/plugin.js +585 -0
  46. package/src/commands/port-config.js +135 -0
  47. package/src/commands/proxy-control.js +264 -0
  48. package/src/commands/proxy.js +152 -0
  49. package/src/commands/resume.js +137 -0
  50. package/src/commands/search.js +190 -0
  51. package/src/commands/security.js +37 -0
  52. package/src/commands/stats.js +398 -0
  53. package/src/commands/switch.js +48 -0
  54. package/src/commands/toggle-proxy.js +247 -0
  55. package/src/commands/ui.js +99 -0
  56. package/src/commands/update.js +97 -0
  57. package/src/commands/workspace.js +454 -0
  58. package/src/config/default.js +69 -0
  59. package/src/config/loader.js +149 -0
  60. package/src/config/model-metadata.js +167 -0
  61. package/src/config/model-metadata.json +125 -0
  62. package/src/config/model-pricing.js +35 -0
  63. package/src/config/paths.js +190 -0
  64. package/src/index.js +680 -0
  65. package/src/plugins/constants.js +15 -0
  66. package/src/plugins/event-bus.js +54 -0
  67. package/src/plugins/manifest-validator.js +129 -0
  68. package/src/plugins/plugin-api.js +128 -0
  69. package/src/plugins/plugin-installer.js +601 -0
  70. package/src/plugins/plugin-loader.js +229 -0
  71. package/src/plugins/plugin-manager.js +170 -0
  72. package/src/plugins/registry.js +152 -0
  73. package/src/plugins/schema/plugin-manifest.json +115 -0
  74. package/src/reset-config.js +94 -0
  75. package/src/server/api/agents.js +826 -0
  76. package/src/server/api/aliases.js +36 -0
  77. package/src/server/api/channels.js +368 -0
  78. package/src/server/api/claude-hooks.js +480 -0
  79. package/src/server/api/codex-channels.js +417 -0
  80. package/src/server/api/codex-projects.js +104 -0
  81. package/src/server/api/codex-proxy.js +195 -0
  82. package/src/server/api/codex-sessions.js +483 -0
  83. package/src/server/api/codex-statistics.js +57 -0
  84. package/src/server/api/commands.js +482 -0
  85. package/src/server/api/config-export.js +212 -0
  86. package/src/server/api/config-registry.js +357 -0
  87. package/src/server/api/config-sync.js +155 -0
  88. package/src/server/api/config-templates.js +248 -0
  89. package/src/server/api/config.js +521 -0
  90. package/src/server/api/convert.js +260 -0
  91. package/src/server/api/dashboard.js +142 -0
  92. package/src/server/api/env.js +144 -0
  93. package/src/server/api/favorites.js +77 -0
  94. package/src/server/api/gemini-channels.js +366 -0
  95. package/src/server/api/gemini-projects.js +91 -0
  96. package/src/server/api/gemini-proxy.js +173 -0
  97. package/src/server/api/gemini-sessions.js +376 -0
  98. package/src/server/api/gemini-statistics.js +57 -0
  99. package/src/server/api/health-check.js +31 -0
  100. package/src/server/api/mcp.js +399 -0
  101. package/src/server/api/opencode-channels.js +419 -0
  102. package/src/server/api/opencode-projects.js +99 -0
  103. package/src/server/api/opencode-proxy.js +207 -0
  104. package/src/server/api/opencode-sessions.js +327 -0
  105. package/src/server/api/opencode-statistics.js +57 -0
  106. package/src/server/api/plugins.js +463 -0
  107. package/src/server/api/pm2-autostart.js +269 -0
  108. package/src/server/api/projects.js +124 -0
  109. package/src/server/api/prompts.js +279 -0
  110. package/src/server/api/proxy.js +306 -0
  111. package/src/server/api/security.js +53 -0
  112. package/src/server/api/sessions.js +514 -0
  113. package/src/server/api/settings.js +142 -0
  114. package/src/server/api/skills.js +570 -0
  115. package/src/server/api/statistics.js +238 -0
  116. package/src/server/api/ui-config.js +64 -0
  117. package/src/server/api/workspaces.js +456 -0
  118. package/src/server/codex-proxy-server.js +681 -0
  119. package/src/server/dev-server.js +26 -0
  120. package/src/server/gemini-proxy-server.js +610 -0
  121. package/src/server/index.js +422 -0
  122. package/src/server/opencode-proxy-server.js +4771 -0
  123. package/src/server/proxy-server.js +669 -0
  124. package/src/server/services/agents-service.js +1137 -0
  125. package/src/server/services/alias.js +71 -0
  126. package/src/server/services/channel-health.js +234 -0
  127. package/src/server/services/channel-scheduler.js +240 -0
  128. package/src/server/services/channels.js +447 -0
  129. package/src/server/services/codex-channels.js +705 -0
  130. package/src/server/services/codex-config.js +90 -0
  131. package/src/server/services/codex-parser.js +322 -0
  132. package/src/server/services/codex-sessions.js +936 -0
  133. package/src/server/services/codex-settings-manager.js +619 -0
  134. package/src/server/services/codex-speed-test-template.json +24 -0
  135. package/src/server/services/codex-statistics-service.js +161 -0
  136. package/src/server/services/commands-service.js +574 -0
  137. package/src/server/services/config-export-service.js +1165 -0
  138. package/src/server/services/config-registry-service.js +828 -0
  139. package/src/server/services/config-sync-manager.js +941 -0
  140. package/src/server/services/config-sync-service.js +504 -0
  141. package/src/server/services/config-templates-service.js +913 -0
  142. package/src/server/services/enhanced-cache.js +196 -0
  143. package/src/server/services/env-checker.js +409 -0
  144. package/src/server/services/env-manager.js +436 -0
  145. package/src/server/services/favorites.js +165 -0
  146. package/src/server/services/format-converter.js +620 -0
  147. package/src/server/services/gemini-channels.js +459 -0
  148. package/src/server/services/gemini-config.js +73 -0
  149. package/src/server/services/gemini-sessions.js +689 -0
  150. package/src/server/services/gemini-settings-manager.js +263 -0
  151. package/src/server/services/gemini-statistics-service.js +157 -0
  152. package/src/server/services/health-check.js +85 -0
  153. package/src/server/services/mcp-client.js +790 -0
  154. package/src/server/services/mcp-service.js +1732 -0
  155. package/src/server/services/model-detector.js +1245 -0
  156. package/src/server/services/network-access.js +80 -0
  157. package/src/server/services/opencode-channels.js +366 -0
  158. package/src/server/services/opencode-gateway-adapters.js +1168 -0
  159. package/src/server/services/opencode-gateway-converter.js +639 -0
  160. package/src/server/services/opencode-sessions.js +931 -0
  161. package/src/server/services/opencode-settings-manager.js +478 -0
  162. package/src/server/services/opencode-statistics-service.js +161 -0
  163. package/src/server/services/plugins-service.js +1268 -0
  164. package/src/server/services/prompts-service.js +534 -0
  165. package/src/server/services/proxy-runtime.js +79 -0
  166. package/src/server/services/repo-scanner-base.js +708 -0
  167. package/src/server/services/request-logger.js +130 -0
  168. package/src/server/services/response-decoder.js +21 -0
  169. package/src/server/services/security-config.js +131 -0
  170. package/src/server/services/session-cache.js +127 -0
  171. package/src/server/services/session-converter.js +577 -0
  172. package/src/server/services/sessions.js +900 -0
  173. package/src/server/services/settings-manager.js +163 -0
  174. package/src/server/services/skill-service.js +1482 -0
  175. package/src/server/services/speed-test.js +1146 -0
  176. package/src/server/services/statistics-service.js +1043 -0
  177. package/src/server/services/ui-config.js +132 -0
  178. package/src/server/services/workspace-service.js +830 -0
  179. package/src/server/utils/pricing.js +73 -0
  180. package/src/server/websocket-server.js +513 -0
  181. package/src/ui/menu.js +139 -0
  182. package/src/ui/prompts.js +100 -0
  183. package/src/utils/format.js +43 -0
  184. package/src/utils/port-helper.js +108 -0
  185. package/src/utils/session.js +240 -0
@@ -0,0 +1,260 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const { convertSession, previewConversion } = require('../services/session-converter');
4
+ const {
5
+ SUPPORTED_SOURCE_TYPES,
6
+ SUPPORTED_TARGET_APIS,
7
+ convertToOpenCodePayload,
8
+ convertClaudeToOpenCodePayload,
9
+ convertCodexToOpenCodePayload,
10
+ convertGeminiToOpenCodePayload,
11
+ normalizeSourceType
12
+ } = require('../services/opencode-gateway-converter');
13
+
14
+ /**
15
+ * 获取支持的格式列表
16
+ * GET /api/convert/formats
17
+ */
18
+ router.get('/formats', (req, res) => {
19
+ res.json({
20
+ formats: [
21
+ {
22
+ id: 'claude',
23
+ name: 'Claude Code',
24
+ description: 'Anthropic Claude Code CLI session format',
25
+ extension: '.jsonl',
26
+ icon: 'claude'
27
+ },
28
+ {
29
+ id: 'codex',
30
+ name: 'OpenAI Codex',
31
+ description: 'OpenAI Codex CLI session format',
32
+ extension: '.jsonl',
33
+ icon: 'codex'
34
+ },
35
+ {
36
+ id: 'gemini',
37
+ name: 'Google Gemini',
38
+ description: 'Google Gemini CLI session format',
39
+ extension: '.json',
40
+ icon: 'gemini'
41
+ }
42
+ ],
43
+ conversions: [
44
+ { from: 'claude', to: 'codex' },
45
+ { from: 'claude', to: 'gemini' },
46
+ { from: 'codex', to: 'claude' },
47
+ { from: 'codex', to: 'gemini' },
48
+ { from: 'gemini', to: 'claude' },
49
+ { from: 'gemini', to: 'codex' }
50
+ ]
51
+ });
52
+ });
53
+
54
+ /**
55
+ * 获取 OpenCode 网关支持格式
56
+ * GET /api/convert/opencode/formats
57
+ */
58
+ router.get('/opencode/formats', (req, res) => {
59
+ res.json({
60
+ sourceTypes: SUPPORTED_SOURCE_TYPES.map(type => ({
61
+ id: type,
62
+ name: type === 'claude'
63
+ ? 'Claude Code'
64
+ : type === 'codex'
65
+ ? 'Codex'
66
+ : 'Gemini'
67
+ })),
68
+ target: 'opencode',
69
+ targetApis: SUPPORTED_TARGET_APIS,
70
+ defaultTargetApi: 'responses',
71
+ endpoints: {
72
+ responses: '/v1/responses',
73
+ 'chat.completions': '/v1/chat/completions'
74
+ },
75
+ sourceEndpoints: {
76
+ claude: '/api/convert/opencode/claude',
77
+ codex: '/api/convert/opencode/codex',
78
+ gemini: '/api/convert/opencode/gemini'
79
+ }
80
+ });
81
+ });
82
+
83
+ function handleOpenCodeConvert(req, res, sourceType, converter) {
84
+ try {
85
+ const { payload, options = {} } = req.body || {};
86
+
87
+ if (!payload) {
88
+ return res.status(400).json({
89
+ success: false,
90
+ error: 'Missing required parameters: payload'
91
+ });
92
+ }
93
+
94
+ const result = converter({ payload, options });
95
+ return res.json({
96
+ success: true,
97
+ ...result
98
+ });
99
+ } catch (error) {
100
+ console.error(`[Convert API] OpenCode ${sourceType} convert error:`, error);
101
+ return res.status(500).json({
102
+ success: false,
103
+ error: error.message
104
+ });
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Claude Code -> OpenCode
110
+ * POST /api/convert/opencode/claude
111
+ * Body: { payload, options? }
112
+ */
113
+ router.post('/opencode/claude', (req, res) => {
114
+ return handleOpenCodeConvert(req, res, 'claude', convertClaudeToOpenCodePayload);
115
+ });
116
+
117
+ /**
118
+ * Codex -> OpenCode
119
+ * POST /api/convert/opencode/codex
120
+ * Body: { payload, options? }
121
+ */
122
+ router.post('/opencode/codex', (req, res) => {
123
+ return handleOpenCodeConvert(req, res, 'codex', convertCodexToOpenCodePayload);
124
+ });
125
+
126
+ /**
127
+ * Gemini -> OpenCode
128
+ * POST /api/convert/opencode/gemini
129
+ * Body: { payload, options? }
130
+ */
131
+ router.post('/opencode/gemini', (req, res) => {
132
+ return handleOpenCodeConvert(req, res, 'gemini', convertGeminiToOpenCodePayload);
133
+ });
134
+
135
+ /**
136
+ * 在线转换为 OpenCode 可处理格式
137
+ * POST /api/convert/opencode
138
+ * Body: { sourceType, payload, options? }
139
+ */
140
+ router.post('/opencode', (req, res) => {
141
+ try {
142
+ const { sourceType, payload, options = {} } = req.body || {};
143
+
144
+ if (!sourceType || !payload) {
145
+ return res.status(400).json({
146
+ success: false,
147
+ error: 'Missing required parameters: sourceType, payload'
148
+ });
149
+ }
150
+
151
+ const normalized = normalizeSourceType(sourceType);
152
+ if (!SUPPORTED_SOURCE_TYPES.includes(normalized)) {
153
+ return res.status(400).json({
154
+ success: false,
155
+ error: `Invalid sourceType: ${sourceType}. Must be one of: ${SUPPORTED_SOURCE_TYPES.join(', ')}`
156
+ });
157
+ }
158
+
159
+ const result = convertToOpenCodePayload({
160
+ sourceType: normalized,
161
+ payload,
162
+ options
163
+ });
164
+
165
+ return res.json({
166
+ success: true,
167
+ ...result
168
+ });
169
+ } catch (error) {
170
+ console.error('[Convert API] OpenCode gateway convert error:', error);
171
+ return res.status(500).json({
172
+ success: false,
173
+ error: error.message
174
+ });
175
+ }
176
+ });
177
+
178
+ /**
179
+ * 预览转换结果
180
+ * POST /api/convert/preview
181
+ * Body: { sourceType, sessionId }
182
+ */
183
+ router.post('/preview', async (req, res) => {
184
+ try {
185
+ const { sourceType, sessionId } = req.body;
186
+
187
+ if (!sourceType || !sessionId) {
188
+ return res.status(400).json({
189
+ success: false,
190
+ error: 'Missing required parameters: sourceType, sessionId'
191
+ });
192
+ }
193
+
194
+ const preview = await previewConversion(sourceType, sessionId);
195
+
196
+ res.json({
197
+ success: true,
198
+ preview
199
+ });
200
+ } catch (error) {
201
+ console.error('[Convert API] Preview error:', error);
202
+ res.status(500).json({
203
+ success: false,
204
+ error: error.message
205
+ });
206
+ }
207
+ });
208
+
209
+ /**
210
+ * 执行会话转换
211
+ * POST /api/convert
212
+ * Body: { sourceType, targetType, sessionId, options }
213
+ */
214
+ router.post('/', async (req, res) => {
215
+ try {
216
+ const { sourceType, targetType, sessionId, options = {} } = req.body;
217
+
218
+ // 验证必需参数
219
+ if (!sourceType || !targetType || !sessionId) {
220
+ return res.status(400).json({
221
+ success: false,
222
+ error: 'Missing required parameters: sourceType, targetType, sessionId'
223
+ });
224
+ }
225
+
226
+ // 验证格式
227
+ const validTypes = ['claude', 'codex', 'gemini'];
228
+ if (!validTypes.includes(sourceType)) {
229
+ return res.status(400).json({
230
+ success: false,
231
+ error: `Invalid sourceType: ${sourceType}. Must be one of: ${validTypes.join(', ')}`
232
+ });
233
+ }
234
+ if (!validTypes.includes(targetType)) {
235
+ return res.status(400).json({
236
+ success: false,
237
+ error: `Invalid targetType: ${targetType}. Must be one of: ${validTypes.join(', ')}`
238
+ });
239
+ }
240
+ if (sourceType === targetType) {
241
+ return res.status(400).json({
242
+ success: false,
243
+ error: 'Source and target types must be different'
244
+ });
245
+ }
246
+
247
+ // 执行转换
248
+ const result = await convertSession(sourceType, targetType, sessionId, options);
249
+
250
+ res.json(result);
251
+ } catch (error) {
252
+ console.error('[Convert API] Conversion error:', error);
253
+ res.status(500).json({
254
+ success: false,
255
+ error: error.message
256
+ });
257
+ }
258
+ });
259
+
260
+ module.exports = router;
@@ -0,0 +1,142 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const { loadConfig } = require('../../config/loader');
4
+
5
+ // Services
6
+ const { loadUIConfig } = require('../services/ui-config');
7
+ const { loadFavorites } = require('../services/favorites');
8
+ const { getAllChannels } = require('../services/channels');
9
+ const { getProxyStatus } = require('../proxy-server');
10
+ const { getCodexProxyStatus } = require('../codex-proxy-server');
11
+ const { getGeminiProxyStatus } = require('../gemini-proxy-server');
12
+ const { getOpenCodeProxyStatus } = require('../opencode-proxy-server');
13
+ const { getProjectAndSessionCounts: getClaudeCounts } = require('../services/sessions');
14
+ const { getProjectAndSessionCounts: getCodexCounts } = require('../services/codex-sessions');
15
+ const { getProjectAndSessionCounts: getGeminiCounts } = require('../services/gemini-sessions');
16
+ const { getProjectAndSessionCounts: getOpenCodeCounts } = require('../services/opencode-sessions');
17
+
18
+ // Channel-specific services
19
+ const { getChannels: getCodexChannels } = require('../services/codex-channels');
20
+ const { getChannels: getGeminiChannels } = require('../services/gemini-channels');
21
+ const { getChannels: getOpenCodeChannels } = require('../services/opencode-channels');
22
+
23
+ // Statistics
24
+ const { getTodayStatistics } = require('../services/statistics-service');
25
+ const { getTodayStatistics: getCodexTodayStatistics } = require('../services/codex-statistics-service');
26
+ const { getTodayStatistics: getGeminiTodayStatistics } = require('../services/gemini-statistics-service');
27
+ const { getTodayStatistics: getOpenCodeTodayStatistics } = require('../services/opencode-statistics-service');
28
+
29
+ /**
30
+ * GET /api/dashboard/init
31
+ * 聚合首页所需的所有数据,一次请求返回
32
+ */
33
+ router.get('/init', async (req, res) => {
34
+ try {
35
+ const config = loadConfig();
36
+
37
+ // 并行获取所有数据
38
+ const [
39
+ uiConfig,
40
+ favorites,
41
+ claudeChannels,
42
+ codexChannels,
43
+ geminiChannels,
44
+ opencodeChannels,
45
+ claudeProxyStatus,
46
+ codexProxyStatus,
47
+ geminiProxyStatus,
48
+ opencodeProxyStatus,
49
+ claudeTodayStats,
50
+ codexTodayStats,
51
+ geminiTodayStats,
52
+ opencodeTodayStats,
53
+ claudeCounts,
54
+ codexCounts,
55
+ geminiCounts,
56
+ opencodeCounts
57
+ ] = await Promise.all([
58
+ // UI Config
59
+ Promise.resolve(loadUIConfig()),
60
+
61
+ // Favorites
62
+ Promise.resolve(loadFavorites()),
63
+
64
+ // Channels
65
+ Promise.resolve(getAllChannels()),
66
+ Promise.resolve(getCodexChannels()),
67
+ Promise.resolve(getGeminiChannels()),
68
+ Promise.resolve(getOpenCodeChannels()),
69
+
70
+ // Proxy Status
71
+ Promise.resolve(getProxyStatus()),
72
+ Promise.resolve(getCodexProxyStatus()),
73
+ Promise.resolve(getGeminiProxyStatus()),
74
+ Promise.resolve(getOpenCodeProxyStatus()),
75
+
76
+ // Today Stats (所有平台)
77
+ Promise.resolve(getTodayStatistics()),
78
+ Promise.resolve(getCodexTodayStatistics()),
79
+ Promise.resolve(getGeminiTodayStatistics()),
80
+ Promise.resolve(getOpenCodeTodayStatistics()),
81
+
82
+ // 轻量级统计
83
+ Promise.resolve(getClaudeCounts(config)),
84
+ Promise.resolve(getCodexCounts()),
85
+ Promise.resolve(getGeminiCounts()),
86
+ Promise.resolve(getOpenCodeCounts())
87
+ ]);
88
+
89
+ // 格式化统计数据:取 summary 和 byModel 中的数据
90
+ const formatStats = (stats) => {
91
+ if (stats && stats.summary) {
92
+ return {
93
+ requests: stats.summary.requests || 0,
94
+ tokens: stats.summary.tokens || 0,
95
+ cost: stats.summary.cost || 0,
96
+ byModel: stats.byModel || {}
97
+ };
98
+ }
99
+ return { requests: 0, tokens: 0, cost: 0, byModel: {} };
100
+ };
101
+
102
+ res.json({
103
+ success: true,
104
+ data: {
105
+ uiConfig,
106
+ favorites,
107
+ channels: {
108
+ claude: claudeChannels,
109
+ codex: codexChannels,
110
+ gemini: geminiChannels,
111
+ opencode: opencodeChannels.channels || []
112
+ },
113
+ proxyStatus: {
114
+ claude: claudeProxyStatus,
115
+ codex: codexProxyStatus,
116
+ gemini: geminiProxyStatus,
117
+ opencode: opencodeProxyStatus
118
+ },
119
+ counts: {
120
+ claude: claudeCounts || { projectCount: 0, sessionCount: 0 },
121
+ codex: codexCounts || { projectCount: 0, sessionCount: 0 },
122
+ gemini: geminiCounts || { projectCount: 0, sessionCount: 0 },
123
+ opencode: opencodeCounts || { projectCount: 0, sessionCount: 0 }
124
+ },
125
+ todayStats: {
126
+ claude: formatStats(claudeTodayStats),
127
+ codex: formatStats(codexTodayStats),
128
+ gemini: formatStats(geminiTodayStats),
129
+ opencode: formatStats(opencodeTodayStats)
130
+ }
131
+ }
132
+ });
133
+ } catch (error) {
134
+ console.error('Dashboard init error:', error);
135
+ res.status(500).json({
136
+ success: false,
137
+ message: error.message
138
+ });
139
+ }
140
+ });
141
+
142
+ module.exports = router;
@@ -0,0 +1,144 @@
1
+ /**
2
+ * 环境变量检测 API 路由
3
+ */
4
+
5
+ const express = require('express');
6
+ const router = express.Router();
7
+ const envChecker = require('../services/env-checker');
8
+ const envManager = require('../services/env-manager');
9
+
10
+ /**
11
+ * GET /api/env/check
12
+ * 检测环境变量冲突
13
+ * Query: platform (可选) - claude/codex/gemini
14
+ */
15
+ router.get('/check', (req, res) => {
16
+ try {
17
+ const { platform } = req.query;
18
+ const conflicts = envChecker.checkEnvConflicts(platform || null);
19
+ const stats = envChecker.getConflictStats(conflicts);
20
+
21
+ res.json({
22
+ success: true,
23
+ conflicts,
24
+ stats
25
+ });
26
+ } catch (error) {
27
+ console.error('[Env API] Check failed:', error);
28
+ res.status(500).json({
29
+ success: false,
30
+ error: error.message
31
+ });
32
+ }
33
+ });
34
+
35
+ /**
36
+ * POST /api/env/delete
37
+ * 删除选中的环境变量(带自动备份)
38
+ * Body: { conflicts: Array }
39
+ */
40
+ router.post('/delete', (req, res) => {
41
+ try {
42
+ const { conflicts } = req.body;
43
+
44
+ if (!conflicts || !Array.isArray(conflicts) || conflicts.length === 0) {
45
+ return res.status(400).json({
46
+ success: false,
47
+ error: '请选择要删除的环境变量'
48
+ });
49
+ }
50
+
51
+ const result = envManager.deleteEnvVars(conflicts);
52
+
53
+ res.json({
54
+ success: true,
55
+ ...result,
56
+ message: `已删除 ${result.results.filter(r => r.success).length} 个环境变量,备份已保存`
57
+ });
58
+ } catch (error) {
59
+ console.error('[Env API] Delete failed:', error);
60
+ res.status(400).json({
61
+ success: false,
62
+ error: error.message
63
+ });
64
+ }
65
+ });
66
+
67
+ /**
68
+ * GET /api/env/backups
69
+ * 获取备份列表
70
+ */
71
+ router.get('/backups', (req, res) => {
72
+ try {
73
+ const backups = envManager.getBackupList();
74
+
75
+ res.json({
76
+ success: true,
77
+ backups
78
+ });
79
+ } catch (error) {
80
+ console.error('[Env API] Get backups failed:', error);
81
+ res.status(500).json({
82
+ success: false,
83
+ error: error.message
84
+ });
85
+ }
86
+ });
87
+
88
+ /**
89
+ * POST /api/env/restore
90
+ * 从备份恢复
91
+ * Body: { backupPath: string }
92
+ */
93
+ router.post('/restore', (req, res) => {
94
+ try {
95
+ const { backupPath } = req.body;
96
+
97
+ if (!backupPath) {
98
+ return res.status(400).json({
99
+ success: false,
100
+ error: '请指定备份文件路径'
101
+ });
102
+ }
103
+
104
+ const result = envManager.restoreFromBackup(backupPath);
105
+
106
+ res.json({
107
+ success: true,
108
+ ...result,
109
+ message: '环境变量已恢复'
110
+ });
111
+ } catch (error) {
112
+ console.error('[Env API] Restore failed:', error);
113
+ res.status(400).json({
114
+ success: false,
115
+ error: error.message
116
+ });
117
+ }
118
+ });
119
+
120
+ /**
121
+ * DELETE /api/env/backups/:fileName
122
+ * 删除备份文件
123
+ */
124
+ router.delete('/backups/:fileName', (req, res) => {
125
+ try {
126
+ const { fileName } = req.params;
127
+ const backupPath = `${envManager.BACKUP_DIR}/${fileName}`;
128
+
129
+ envManager.deleteBackup(backupPath);
130
+
131
+ res.json({
132
+ success: true,
133
+ message: '备份已删除'
134
+ });
135
+ } catch (error) {
136
+ console.error('[Env API] Delete backup failed:', error);
137
+ res.status(400).json({
138
+ success: false,
139
+ error: error.message
140
+ });
141
+ }
142
+ });
143
+
144
+ module.exports = router;
@@ -0,0 +1,77 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const {
4
+ addFavorite,
5
+ removeFavorite,
6
+ isFavorite,
7
+ getFavorites,
8
+ getAllFavorites
9
+ } = require('../services/favorites');
10
+
11
+ // Get all favorites
12
+ router.get('/', (req, res) => {
13
+ try {
14
+ const favorites = getAllFavorites();
15
+ res.json({ success: true, favorites });
16
+ } catch (error) {
17
+ console.error('Error getting favorites:', error);
18
+ res.status(500).json({ error: error.message });
19
+ }
20
+ });
21
+
22
+ // Get favorites for a specific channel
23
+ router.get('/:channel', (req, res) => {
24
+ try {
25
+ const { channel } = req.params;
26
+ const favorites = getFavorites(channel);
27
+ res.json({ success: true, favorites });
28
+ } catch (error) {
29
+ console.error('Error getting channel favorites:', error);
30
+ res.status(500).json({ error: error.message });
31
+ }
32
+ });
33
+
34
+ // Add a favorite
35
+ router.post('/', (req, res) => {
36
+ try {
37
+ const { channel, sessionData } = req.body;
38
+
39
+ if (!channel || !sessionData) {
40
+ return res.status(400).json({ error: 'Missing required fields' });
41
+ }
42
+
43
+ const result = addFavorite(channel, sessionData);
44
+ res.json(result);
45
+ } catch (error) {
46
+ console.error('Error adding favorite:', error);
47
+ res.status(500).json({ error: error.message });
48
+ }
49
+ });
50
+
51
+ // Remove a favorite
52
+ router.delete('/:channel/:projectName/:sessionId', (req, res) => {
53
+ try {
54
+ const { channel, projectName, sessionId } = req.params;
55
+
56
+ const result = removeFavorite(channel, decodeURIComponent(projectName), sessionId);
57
+ res.json(result);
58
+ } catch (error) {
59
+ console.error('Error removing favorite:', error);
60
+ res.status(500).json({ error: error.message });
61
+ }
62
+ });
63
+
64
+ // Check if favorited
65
+ router.get('/check/:channel/:projectName/:sessionId', (req, res) => {
66
+ try {
67
+ const { channel, projectName, sessionId } = req.params;
68
+
69
+ const favorited = isFavorite(channel, decodeURIComponent(projectName), sessionId);
70
+ res.json({ success: true, isFavorite: favorited });
71
+ } catch (error) {
72
+ console.error('Error checking favorite:', error);
73
+ res.status(500).json({ error: error.message });
74
+ }
75
+ });
76
+
77
+ module.exports = router;