@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,407 @@
1
+ // 工作区 API 路由
2
+ const express = require('express');
3
+ const router = express.Router();
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const workspaceService = require('../services/workspace-service');
7
+
8
+ /**
9
+ * GET /api/workspaces
10
+ * 获取所有工作区列表
11
+ */
12
+ router.get('/', (req, res) => {
13
+ try {
14
+ const workspaces = workspaceService.listWorkspaces();
15
+ res.json({
16
+ success: true,
17
+ data: workspaces
18
+ });
19
+ } catch (error) {
20
+ res.status(500).json({
21
+ success: false,
22
+ message: error.message
23
+ });
24
+ }
25
+ });
26
+
27
+ /**
28
+ * GET /api/workspaces/read-file
29
+ * 读取工作区中的文件内容(仅限 CLAUDE.md 等配置文件)
30
+ * Query: path=文件路径
31
+ */
32
+ router.get('/read-file', (req, res) => {
33
+ try {
34
+ const filePath = req.query.path;
35
+
36
+ if (!filePath) {
37
+ return res.status(400).json({
38
+ success: false,
39
+ message: '文件路径不能为空'
40
+ });
41
+ }
42
+
43
+ // 安全检查:只允许读取特定类型的文件
44
+ const allowedFiles = ['CLAUDE.md', 'AGENTS.md', 'GEMINI.md', '.ctx-config.json'];
45
+ const fileName = path.basename(filePath);
46
+
47
+ if (!allowedFiles.includes(fileName)) {
48
+ return res.status(403).json({
49
+ success: false,
50
+ message: '不允许读取该文件'
51
+ });
52
+ }
53
+
54
+ if (!fs.existsSync(filePath)) {
55
+ return res.status(404).json({
56
+ success: false,
57
+ message: '文件不存在'
58
+ });
59
+ }
60
+
61
+ const content = fs.readFileSync(filePath, 'utf8');
62
+
63
+ res.json({
64
+ success: true,
65
+ content: content
66
+ });
67
+ } catch (error) {
68
+ res.status(500).json({
69
+ success: false,
70
+ message: error.message
71
+ });
72
+ }
73
+ });
74
+
75
+ /**
76
+ * GET /api/workspaces/check-git/:projectPath
77
+ * 检查项目是否是 git 仓库并获取 worktrees
78
+ */
79
+ router.get('/check-git/*', (req, res) => {
80
+ try {
81
+ const projectPath = req.params[0];
82
+
83
+ if (!projectPath) {
84
+ return res.status(400).json({
85
+ success: false,
86
+ message: '项目路径不能为空'
87
+ });
88
+ }
89
+
90
+ const isGit = workspaceService.isGitRepo(projectPath);
91
+ const worktrees = isGit ? workspaceService.getGitWorktrees(projectPath) : [];
92
+
93
+ res.json({
94
+ success: true,
95
+ data: {
96
+ isGitRepo: isGit,
97
+ worktrees
98
+ }
99
+ });
100
+ } catch (error) {
101
+ res.status(500).json({
102
+ success: false,
103
+ message: error.message
104
+ });
105
+ }
106
+ });
107
+
108
+ /**
109
+ * GET /api/workspaces/:id
110
+ * 获取单个工作区详情
111
+ */
112
+ router.get('/:id', (req, res) => {
113
+ try {
114
+ const workspace = workspaceService.getWorkspace(req.params.id);
115
+ if (!workspace) {
116
+ return res.status(404).json({
117
+ success: false,
118
+ message: '工作区不存在'
119
+ });
120
+ }
121
+ res.json({
122
+ success: true,
123
+ data: workspace
124
+ });
125
+ } catch (error) {
126
+ res.status(500).json({
127
+ success: false,
128
+ message: error.message
129
+ });
130
+ }
131
+ });
132
+
133
+ /**
134
+ * POST /api/workspaces
135
+ * 创建新工作区
136
+ * Body: {
137
+ * name: string,
138
+ * description?: string,
139
+ * baseDir?: string,
140
+ * projects: [{
141
+ * sourcePath: string,
142
+ * name?: string,
143
+ * createWorktree?: boolean,
144
+ * branch?: string
145
+ * }]
146
+ * }
147
+ */
148
+ router.post('/', (req, res) => {
149
+ try {
150
+ const { name, description, baseDir, projects, configTemplateId } = req.body;
151
+
152
+ if (!name || !name.trim()) {
153
+ return res.status(400).json({
154
+ success: false,
155
+ message: '工作区名称不能为空'
156
+ });
157
+ }
158
+
159
+ if (!projects || !Array.isArray(projects) || projects.length === 0) {
160
+ return res.status(400).json({
161
+ success: false,
162
+ message: '至少需要选择一个项目'
163
+ });
164
+ }
165
+
166
+ // 验证项目配置
167
+ for (const proj of projects) {
168
+ if (!proj.sourcePath || !proj.sourcePath.trim()) {
169
+ return res.status(400).json({
170
+ success: false,
171
+ message: '项目源路径不能为空'
172
+ });
173
+ }
174
+
175
+ if (proj.createWorktree && (!proj.branch || !proj.branch.trim())) {
176
+ return res.status(400).json({
177
+ success: false,
178
+ message: '创建 worktree 时必须指定分支名'
179
+ });
180
+ }
181
+ }
182
+
183
+ const workspace = workspaceService.createWorkspace({
184
+ name,
185
+ description,
186
+ baseDir,
187
+ projects,
188
+ configTemplateId
189
+ });
190
+
191
+ res.json({
192
+ success: true,
193
+ message: '工作区创建成功',
194
+ data: workspace
195
+ });
196
+ } catch (error) {
197
+ res.status(500).json({
198
+ success: false,
199
+ message: error.message
200
+ });
201
+ }
202
+ });
203
+
204
+ /**
205
+ * DELETE /api/workspaces/:id
206
+ * 删除工作区
207
+ * Query: removeFiles=true/false
208
+ */
209
+ router.delete('/:id', (req, res) => {
210
+ try {
211
+ const { id } = req.params;
212
+ const removeFiles = req.query.removeFiles === 'true';
213
+
214
+ workspaceService.deleteWorkspace(id, removeFiles);
215
+
216
+ res.json({
217
+ success: true,
218
+ message: '工作区删除成功'
219
+ });
220
+ } catch (error) {
221
+ res.status(500).json({
222
+ success: false,
223
+ message: error.message
224
+ });
225
+ }
226
+ });
227
+
228
+ /**
229
+ * PUT /api/workspaces/:id/last-used
230
+ * 更新工作区最后使用时间
231
+ */
232
+ router.put('/:id/last-used', (req, res) => {
233
+ try {
234
+ workspaceService.updateWorkspaceLastUsed(req.params.id);
235
+ res.json({
236
+ success: true,
237
+ message: '更新成功'
238
+ });
239
+ } catch (error) {
240
+ res.status(500).json({
241
+ success: false,
242
+ message: error.message
243
+ });
244
+ }
245
+ });
246
+
247
+ /**
248
+ * POST /api/workspaces/:id/projects
249
+ * 向工作区添加项目
250
+ * Body: {
251
+ * sourcePath: string,
252
+ * name?: string,
253
+ * createWorktree?: boolean,
254
+ * branch?: string
255
+ * }
256
+ */
257
+ router.post('/:id/projects', (req, res) => {
258
+ try {
259
+ const { id } = req.params;
260
+ const { sourcePath, name, createWorktree, branch } = req.body;
261
+
262
+ if (!sourcePath || !sourcePath.trim()) {
263
+ return res.status(400).json({
264
+ success: false,
265
+ message: '项目源路径不能为空'
266
+ });
267
+ }
268
+
269
+ if (createWorktree && (!branch || !branch.trim())) {
270
+ return res.status(400).json({
271
+ success: false,
272
+ message: '创建 worktree 时必须指定分支名'
273
+ });
274
+ }
275
+
276
+ const workspace = workspaceService.addProjectToWorkspace(id, {
277
+ sourcePath,
278
+ name,
279
+ createWorktree,
280
+ branch
281
+ });
282
+
283
+ res.json({
284
+ success: true,
285
+ message: '项目添加成功',
286
+ data: workspace
287
+ });
288
+ } catch (error) {
289
+ res.status(500).json({
290
+ success: false,
291
+ message: error.message
292
+ });
293
+ }
294
+ });
295
+
296
+ /**
297
+ * DELETE /api/workspaces/:id/projects/:projectName
298
+ * 从工作区移除项目
299
+ * Query: removeWorktrees=true/false
300
+ */
301
+ router.delete('/:id/projects/:projectName', (req, res) => {
302
+ try {
303
+ const { id, projectName } = req.params;
304
+ const removeWorktrees = req.query.removeWorktrees === 'true';
305
+
306
+ const workspace = workspaceService.removeProjectFromWorkspace(
307
+ id,
308
+ projectName,
309
+ removeWorktrees
310
+ );
311
+
312
+ res.json({
313
+ success: true,
314
+ message: '项目移除成功',
315
+ data: workspace
316
+ });
317
+ } catch (error) {
318
+ res.status(500).json({
319
+ success: false,
320
+ message: error.message
321
+ });
322
+ }
323
+ });
324
+
325
+ /**
326
+ * GET /api/workspaces/available-projects
327
+ * 获取所有渠道(Claude/Codex/Gemini)的项目并集
328
+ */
329
+ router.get('/available-projects', (req, res) => {
330
+ try {
331
+ const projects = workspaceService.getAllAvailableProjects();
332
+ res.json({
333
+ success: true,
334
+ data: projects
335
+ });
336
+ } catch (error) {
337
+ res.status(500).json({
338
+ success: false,
339
+ message: error.message
340
+ });
341
+ }
342
+ });
343
+
344
+ /**
345
+ * POST /api/workspaces/:id/launch
346
+ * 获取在工作区启动 CLI 工具的命令
347
+ * Body: { tool: 'claude' | 'codex' | 'gemini', projectName?: string }
348
+ */
349
+ router.post('/:id/launch', (req, res) => {
350
+ try {
351
+ const { id } = req.params;
352
+ const { tool, projectName } = req.body;
353
+
354
+ if (!tool) {
355
+ return res.status(400).json({
356
+ success: false,
357
+ message: '请指定要启动的工具'
358
+ });
359
+ }
360
+
361
+ const launchInfo = workspaceService.getLaunchCommand(id, tool, projectName);
362
+ res.json({
363
+ success: true,
364
+ data: launchInfo
365
+ });
366
+ } catch (error) {
367
+ res.status(500).json({
368
+ success: false,
369
+ message: error.message
370
+ });
371
+ }
372
+ });
373
+
374
+ /**
375
+ * GET /api/workspaces/check-git/:projectPath
376
+ * 检查项目是否是 git 仓库并获取 worktrees
377
+ */
378
+ router.get('/check-git/*', (req, res) => {
379
+ try {
380
+ const projectPath = req.params[0];
381
+
382
+ if (!projectPath) {
383
+ return res.status(400).json({
384
+ success: false,
385
+ message: '项目路径不能为空'
386
+ });
387
+ }
388
+
389
+ const isGit = workspaceService.isGitRepo(projectPath);
390
+ const worktrees = isGit ? workspaceService.getGitWorktrees(projectPath) : [];
391
+
392
+ res.json({
393
+ success: true,
394
+ data: {
395
+ isGitRepo: isGit,
396
+ worktrees
397
+ }
398
+ });
399
+ } catch (error) {
400
+ res.status(500).json({
401
+ success: false,
402
+ message: error.message
403
+ });
404
+ }
405
+ });
406
+
407
+ module.exports = router;