@adversity/coding-tool-x 2.2.0 → 2.4.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 (34) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +12 -14
  3. package/dist/web/assets/index-Bu1oPcKu.js +4009 -0
  4. package/dist/web/assets/index-XSok7-mN.css +41 -0
  5. package/dist/web/index.html +2 -2
  6. package/package.json +5 -4
  7. package/src/config/default.js +1 -1
  8. package/src/index.js +2 -2
  9. package/src/server/api/agents.js +188 -0
  10. package/src/server/api/commands.js +261 -0
  11. package/src/server/api/config-export.js +122 -0
  12. package/src/server/api/config-templates.js +26 -5
  13. package/src/server/api/health-check.js +1 -89
  14. package/src/server/api/permissions.js +370 -0
  15. package/src/server/api/rules.js +188 -0
  16. package/src/server/api/skills.js +66 -14
  17. package/src/server/api/workspaces.js +30 -55
  18. package/src/server/index.js +7 -11
  19. package/src/server/services/agents-service.js +179 -1
  20. package/src/server/services/commands-service.js +231 -47
  21. package/src/server/services/config-export-service.js +209 -0
  22. package/src/server/services/config-templates-service.js +481 -107
  23. package/src/server/services/format-converter.js +506 -0
  24. package/src/server/services/health-check.js +1 -315
  25. package/src/server/services/permission-templates-service.js +339 -0
  26. package/src/server/services/repo-scanner-base.js +678 -0
  27. package/src/server/services/rules-service.js +179 -1
  28. package/src/server/services/skill-service.js +114 -61
  29. package/src/server/services/workspace-service.js +52 -1
  30. package/dist/web/assets/index-D1AYlFLZ.js +0 -3220
  31. package/dist/web/assets/index-aL3cKxSK.css +0 -41
  32. package/docs/CHANGELOG.md +0 -582
  33. package/docs/DIRECTORY_MIGRATION.md +0 -112
  34. package/docs/PROJECT_STRUCTURE.md +0 -396
@@ -245,4 +245,192 @@ router.delete('/:scope/:fileName', (req, res) => {
245
245
  }
246
246
  });
247
247
 
248
+ // ==================== 仓库管理 API ====================
249
+
250
+ /**
251
+ * 获取所有代理(包括远程仓库)
252
+ * GET /api/agents/all
253
+ * Query: projectPath, refresh=1 强制刷新缓存
254
+ */
255
+ router.get('/all', async (req, res) => {
256
+ try {
257
+ const { projectPath, refresh } = req.query;
258
+ const forceRefresh = refresh === '1';
259
+ const result = await agentsService.listAllAgents(projectPath || null, forceRefresh);
260
+
261
+ res.json({
262
+ success: true,
263
+ ...result
264
+ });
265
+ } catch (err) {
266
+ console.error('[Agents API] List all agents error:', err);
267
+ res.status(500).json({
268
+ success: false,
269
+ message: err.message
270
+ });
271
+ }
272
+ });
273
+
274
+ /**
275
+ * 获取仓库列表
276
+ * GET /api/agents/repos
277
+ */
278
+ router.get('/repos', (req, res) => {
279
+ try {
280
+ const repos = agentsService.getRepos();
281
+ res.json({
282
+ success: true,
283
+ repos
284
+ });
285
+ } catch (err) {
286
+ console.error('[Agents API] Get repos error:', err);
287
+ res.status(500).json({
288
+ success: false,
289
+ message: err.message
290
+ });
291
+ }
292
+ });
293
+
294
+ /**
295
+ * 添加仓库
296
+ * POST /api/agents/repos
297
+ * Body: { owner, name, branch, directory, enabled }
298
+ */
299
+ router.post('/repos', (req, res) => {
300
+ try {
301
+ const { owner, name, branch = 'main', directory = '', enabled = true } = req.body;
302
+
303
+ if (!owner || !name) {
304
+ return res.status(400).json({
305
+ success: false,
306
+ message: 'Missing owner or name'
307
+ });
308
+ }
309
+
310
+ const repos = agentsService.addRepo({ owner, name, branch, directory, enabled });
311
+
312
+ res.json({
313
+ success: true,
314
+ repos
315
+ });
316
+ } catch (err) {
317
+ console.error('[Agents API] Add repo error:', err);
318
+ res.status(500).json({
319
+ success: false,
320
+ message: err.message
321
+ });
322
+ }
323
+ });
324
+
325
+ /**
326
+ * 删除仓库
327
+ * DELETE /api/agents/repos/:owner/:name
328
+ * Query: directory - 可选,子目录路径
329
+ */
330
+ router.delete('/repos/:owner/:name', (req, res) => {
331
+ try {
332
+ const { owner, name } = req.params;
333
+ const { directory = '' } = req.query;
334
+ const repos = agentsService.removeRepo(owner, name, directory);
335
+
336
+ res.json({
337
+ success: true,
338
+ repos
339
+ });
340
+ } catch (err) {
341
+ console.error('[Agents API] Remove repo error:', err);
342
+ res.status(500).json({
343
+ success: false,
344
+ message: err.message
345
+ });
346
+ }
347
+ });
348
+
349
+ /**
350
+ * 切换仓库启用状态
351
+ * PUT /api/agents/repos/:owner/:name/toggle
352
+ * Body: { enabled, directory }
353
+ */
354
+ router.put('/repos/:owner/:name/toggle', (req, res) => {
355
+ try {
356
+ const { owner, name } = req.params;
357
+ const { enabled, directory = '' } = req.body;
358
+
359
+ const repos = agentsService.toggleRepo(owner, name, directory, enabled);
360
+
361
+ res.json({
362
+ success: true,
363
+ repos
364
+ });
365
+ } catch (err) {
366
+ console.error('[Agents API] Toggle repo error:', err);
367
+ res.status(500).json({
368
+ success: false,
369
+ message: err.message
370
+ });
371
+ }
372
+ });
373
+
374
+ /**
375
+ * 从远程仓库安装代理
376
+ * POST /api/agents/install
377
+ * Body: agent object from listAllAgents
378
+ */
379
+ router.post('/install', async (req, res) => {
380
+ try {
381
+ const agent = req.body;
382
+
383
+ if (!agent || !agent.repoOwner || !agent.repoName) {
384
+ return res.status(400).json({
385
+ success: false,
386
+ message: 'Missing agent info or repo info'
387
+ });
388
+ }
389
+
390
+ const result = await agentsService.installFromRemote(agent);
391
+
392
+ res.json({
393
+ success: true,
394
+ ...result
395
+ });
396
+ } catch (err) {
397
+ console.error('[Agents API] Install agent error:', err);
398
+ res.status(500).json({
399
+ success: false,
400
+ message: err.message
401
+ });
402
+ }
403
+ });
404
+
405
+ /**
406
+ * 卸载代理
407
+ * POST /api/agents/uninstall
408
+ * Body: { fileName } - 代理的文件名(不含扩展名)
409
+ */
410
+ router.post('/uninstall', (req, res) => {
411
+ try {
412
+ const { fileName } = req.body;
413
+
414
+ if (!fileName) {
415
+ return res.status(400).json({
416
+ success: false,
417
+ message: 'Missing fileName'
418
+ });
419
+ }
420
+
421
+ const result = agentsService.uninstallAgent(fileName);
422
+
423
+ res.json({
424
+ success: true,
425
+ ...result
426
+ });
427
+ } catch (err) {
428
+ console.error('[Agents API] Uninstall agent error:', err);
429
+ res.status(500).json({
430
+ success: false,
431
+ message: err.message
432
+ });
433
+ }
434
+ });
435
+
248
436
  module.exports = router;
@@ -242,4 +242,265 @@ router.delete('/:scope/:name', (req, res) => {
242
242
  }
243
243
  });
244
244
 
245
+ // ==================== 仓库管理 API ====================
246
+
247
+ /**
248
+ * 获取所有命令(包括远程仓库)
249
+ * GET /api/commands/all
250
+ * Query: projectPath, refresh=1 强制刷新缓存
251
+ */
252
+ router.get('/all', async (req, res) => {
253
+ try {
254
+ const { projectPath, refresh } = req.query;
255
+ const forceRefresh = refresh === '1';
256
+ const result = await commandsService.listAllCommands(projectPath || null, forceRefresh);
257
+
258
+ res.json({
259
+ success: true,
260
+ ...result
261
+ });
262
+ } catch (err) {
263
+ console.error('[Commands API] List all commands error:', err);
264
+ res.status(500).json({
265
+ success: false,
266
+ message: err.message
267
+ });
268
+ }
269
+ });
270
+
271
+ /**
272
+ * 获取仓库列表
273
+ * GET /api/commands/repos
274
+ */
275
+ router.get('/repos', (req, res) => {
276
+ try {
277
+ const repos = commandsService.getRepos();
278
+ res.json({
279
+ success: true,
280
+ repos
281
+ });
282
+ } catch (err) {
283
+ console.error('[Commands API] Get repos error:', err);
284
+ res.status(500).json({
285
+ success: false,
286
+ message: err.message
287
+ });
288
+ }
289
+ });
290
+
291
+ /**
292
+ * 添加仓库
293
+ * POST /api/commands/repos
294
+ * Body: { owner, name, branch, directory, enabled }
295
+ */
296
+ router.post('/repos', (req, res) => {
297
+ try {
298
+ const { owner, name, branch = 'main', directory = '', enabled = true } = req.body;
299
+
300
+ if (!owner || !name) {
301
+ return res.status(400).json({
302
+ success: false,
303
+ message: 'Missing owner or name'
304
+ });
305
+ }
306
+
307
+ const repos = commandsService.addRepo({ owner, name, branch, directory, enabled });
308
+
309
+ res.json({
310
+ success: true,
311
+ repos
312
+ });
313
+ } catch (err) {
314
+ console.error('[Commands API] Add repo error:', err);
315
+ res.status(500).json({
316
+ success: false,
317
+ message: err.message
318
+ });
319
+ }
320
+ });
321
+
322
+ /**
323
+ * 删除仓库
324
+ * DELETE /api/commands/repos/:owner/:name
325
+ * Query: directory - 可选,子目录路径
326
+ */
327
+ router.delete('/repos/:owner/:name', (req, res) => {
328
+ try {
329
+ const { owner, name } = req.params;
330
+ const { directory = '' } = req.query;
331
+ const repos = commandsService.removeRepo(owner, name, directory);
332
+
333
+ res.json({
334
+ success: true,
335
+ repos
336
+ });
337
+ } catch (err) {
338
+ console.error('[Commands API] Remove repo error:', err);
339
+ res.status(500).json({
340
+ success: false,
341
+ message: err.message
342
+ });
343
+ }
344
+ });
345
+
346
+ /**
347
+ * 切换仓库启用状态
348
+ * PUT /api/commands/repos/:owner/:name/toggle
349
+ * Body: { enabled, directory }
350
+ */
351
+ router.put('/repos/:owner/:name/toggle', (req, res) => {
352
+ try {
353
+ const { owner, name } = req.params;
354
+ const { enabled, directory = '' } = req.body;
355
+
356
+ const repos = commandsService.toggleRepo(owner, name, directory, enabled);
357
+
358
+ res.json({
359
+ success: true,
360
+ repos
361
+ });
362
+ } catch (err) {
363
+ console.error('[Commands API] Toggle repo error:', err);
364
+ res.status(500).json({
365
+ success: false,
366
+ message: err.message
367
+ });
368
+ }
369
+ });
370
+
371
+ /**
372
+ * 从远程仓库安装命令
373
+ * POST /api/commands/install
374
+ * Body: command object from listAllCommands
375
+ */
376
+ router.post('/install', async (req, res) => {
377
+ try {
378
+ const command = req.body;
379
+
380
+ if (!command || !command.repoOwner || !command.repoName) {
381
+ return res.status(400).json({
382
+ success: false,
383
+ message: 'Missing command info or repo info'
384
+ });
385
+ }
386
+
387
+ const result = await commandsService.installFromRemote(command);
388
+
389
+ res.json({
390
+ success: true,
391
+ ...result
392
+ });
393
+ } catch (err) {
394
+ console.error('[Commands API] Install command error:', err);
395
+ res.status(500).json({
396
+ success: false,
397
+ message: err.message
398
+ });
399
+ }
400
+ });
401
+
402
+ /**
403
+ * 卸载命令
404
+ * POST /api/commands/uninstall
405
+ * Body: { path } - 命令的相对路径
406
+ */
407
+ router.post('/uninstall', (req, res) => {
408
+ try {
409
+ const { path } = req.body;
410
+
411
+ if (!path) {
412
+ return res.status(400).json({
413
+ success: false,
414
+ message: 'Missing path'
415
+ });
416
+ }
417
+
418
+ const result = commandsService.uninstallCommand(path);
419
+
420
+ res.json({
421
+ success: true,
422
+ ...result
423
+ });
424
+ } catch (err) {
425
+ console.error('[Commands API] Uninstall command error:', err);
426
+ res.status(500).json({
427
+ success: false,
428
+ message: err.message
429
+ });
430
+ }
431
+ });
432
+
433
+ // ==================== 格式转换 API ====================
434
+
435
+ /**
436
+ * 转换命令格式
437
+ * POST /api/commands/convert
438
+ * Body: { content, targetFormat }
439
+ * - content: 命令内容
440
+ * - targetFormat: 目标格式 ('claude' | 'codex')
441
+ */
442
+ router.post('/convert', (req, res) => {
443
+ try {
444
+ const { content, targetFormat } = req.body;
445
+
446
+ if (!content) {
447
+ return res.status(400).json({
448
+ success: false,
449
+ message: '请提供命令内容'
450
+ });
451
+ }
452
+
453
+ if (!['claude', 'codex'].includes(targetFormat)) {
454
+ return res.status(400).json({
455
+ success: false,
456
+ message: '目标格式必须是 claude 或 codex'
457
+ });
458
+ }
459
+
460
+ const result = commandsService.convertCommandFormat(content, targetFormat);
461
+
462
+ res.json({
463
+ success: true,
464
+ ...result
465
+ });
466
+ } catch (err) {
467
+ console.error('[Commands API] Convert command error:', err);
468
+ res.status(500).json({
469
+ success: false,
470
+ message: err.message
471
+ });
472
+ }
473
+ });
474
+
475
+ /**
476
+ * 检测命令格式
477
+ * POST /api/commands/detect-format
478
+ * Body: { content }
479
+ */
480
+ router.post('/detect-format', (req, res) => {
481
+ try {
482
+ const { content } = req.body;
483
+
484
+ if (!content) {
485
+ return res.status(400).json({
486
+ success: false,
487
+ message: '请提供命令内容'
488
+ });
489
+ }
490
+
491
+ const format = commandsService.detectFormat(content);
492
+
493
+ res.json({
494
+ success: true,
495
+ format
496
+ });
497
+ } catch (err) {
498
+ console.error('[Commands API] Detect format error:', err);
499
+ res.status(500).json({
500
+ success: false,
501
+ message: err.message
502
+ });
503
+ }
504
+ });
505
+
245
506
  module.exports = router;
@@ -0,0 +1,122 @@
1
+ /**
2
+ * 配置导出/导入 API 路由
3
+ */
4
+
5
+ const express = require('express');
6
+ const configExportService = require('../services/config-export-service');
7
+
8
+ const router = express.Router();
9
+
10
+ /**
11
+ * 导出所有配置
12
+ * GET /api/config-export
13
+ */
14
+ router.get('/', (req, res) => {
15
+ try {
16
+ const result = configExportService.exportAllConfigs();
17
+
18
+ if (result.success) {
19
+ // 设置响应头,触发文件下载
20
+ const filename = `ctx-config-${new Date().toISOString().split('T')[0]}.json`;
21
+ res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
22
+ res.setHeader('Content-Type', 'application/json');
23
+ res.json(result.data);
24
+ } else {
25
+ res.status(500).json({
26
+ success: false,
27
+ message: result.message
28
+ });
29
+ }
30
+ } catch (err) {
31
+ console.error('[ConfigExport API] 导出失败:', err);
32
+ res.status(500).json({
33
+ success: false,
34
+ message: err.message
35
+ });
36
+ }
37
+ });
38
+
39
+ /**
40
+ * 导入配置
41
+ * POST /api/config-export/import
42
+ * Body: { data: {...}, overwrite: boolean }
43
+ */
44
+ router.post('/import', (req, res) => {
45
+ try {
46
+ const { data, overwrite = false } = req.body;
47
+
48
+ if (!data) {
49
+ return res.status(400).json({
50
+ success: false,
51
+ message: '缺少导入数据'
52
+ });
53
+ }
54
+
55
+ const result = configExportService.importConfigs(data, { overwrite });
56
+
57
+ res.json(result);
58
+ } catch (err) {
59
+ console.error('[ConfigExport API] 导入失败:', err);
60
+ res.status(500).json({
61
+ success: false,
62
+ message: err.message
63
+ });
64
+ }
65
+ });
66
+
67
+ /**
68
+ * 预览导入配置(不实际导入)
69
+ * POST /api/config-export/preview
70
+ */
71
+ router.post('/preview', (req, res) => {
72
+ try {
73
+ const { data } = req.body;
74
+
75
+ if (!data || !data.data) {
76
+ return res.status(400).json({
77
+ success: false,
78
+ message: '无效的导入数据格式'
79
+ });
80
+ }
81
+
82
+ const summary = {
83
+ version: data.version,
84
+ exportedAt: data.exportedAt,
85
+ counts: {
86
+ permissionTemplates: (data.data.permissionTemplates || []).length,
87
+ configTemplates: (data.data.configTemplates || []).length,
88
+ channels: (data.data.channels || []).length
89
+ },
90
+ items: {
91
+ permissionTemplates: (data.data.permissionTemplates || []).map(t => ({
92
+ id: t.id,
93
+ name: t.name,
94
+ description: t.description
95
+ })),
96
+ configTemplates: (data.data.configTemplates || []).map(t => ({
97
+ id: t.id,
98
+ name: t.name,
99
+ description: t.description
100
+ })),
101
+ channels: (data.data.channels || []).map(c => ({
102
+ id: c.id,
103
+ name: c.name,
104
+ type: c.type
105
+ }))
106
+ }
107
+ };
108
+
109
+ res.json({
110
+ success: true,
111
+ data: summary
112
+ });
113
+ } catch (err) {
114
+ console.error('[ConfigExport API] 预览失败:', err);
115
+ res.status(500).json({
116
+ success: false,
117
+ message: err.message
118
+ });
119
+ }
120
+ });
121
+
122
+ module.exports = router;
@@ -46,8 +46,15 @@ router.get('/available-configs', (req, res) => {
46
46
  * GET /api/config-templates/:id
47
47
  * 获取单个配置模板详情
48
48
  */
49
- router.get('/:id', (req, res) => {
49
+ // 注意:此路由需要放在所有静态子路由之后,避免把 /available-configs 等路径当成 id
50
+ router.get('/:id', (req, res, next) => {
50
51
  try {
52
+ // 兜底:即便路由顺序被改动,也避免把保留路径当成模板 ID
53
+ const reservedIds = new Set(['available-configs']);
54
+ if (reservedIds.has(req.params.id)) {
55
+ return next();
56
+ }
57
+
51
58
  const template = templatesService.getTemplateById(req.params.id);
52
59
  if (!template) {
53
60
  return res.status(404).json({
@@ -132,14 +139,21 @@ router.delete('/:id', (req, res) => {
132
139
  */
133
140
  router.post('/:id/apply', (req, res) => {
134
141
  try {
135
- const { targetPath } = req.body;
142
+ const { targetPath, aiConfigType, aiConfigTypes } = req.body;
136
143
  if (!targetPath) {
137
144
  return res.status(400).json({
138
145
  success: false,
139
146
  message: '目标路径不能为空'
140
147
  });
141
148
  }
142
- const result = templatesService.applyTemplateToProject(targetPath, req.params.id);
149
+ const options = {};
150
+ // 优先使用新的数组参数,兼容旧的单值参数
151
+ if (aiConfigTypes) {
152
+ options.aiConfigTypes = aiConfigTypes;
153
+ } else if (aiConfigType) {
154
+ options.aiConfigTypes = [aiConfigType];
155
+ }
156
+ const result = templatesService.applyTemplateToProject(targetPath, req.params.id, options);
143
157
  res.json({
144
158
  success: true,
145
159
  message: '模板应用成功',
@@ -159,14 +173,21 @@ router.post('/:id/apply', (req, res) => {
159
173
  */
160
174
  router.post('/:id/preview', (req, res) => {
161
175
  try {
162
- const { targetPath } = req.body;
176
+ const { targetPath, aiConfigType, aiConfigTypes } = req.body;
163
177
  if (!targetPath) {
164
178
  return res.status(400).json({
165
179
  success: false,
166
180
  message: '目标路径不能为空'
167
181
  });
168
182
  }
169
- const preview = templatesService.previewTemplateApplication(targetPath, req.params.id);
183
+ const options = {};
184
+ // 优先使用新的数组参数,兼容旧的单值参数
185
+ if (aiConfigTypes) {
186
+ options.aiConfigTypes = aiConfigTypes;
187
+ } else if (aiConfigType) {
188
+ options.aiConfigTypes = [aiConfigType];
189
+ }
190
+ const preview = templatesService.previewTemplateApplication(targetPath, req.params.id, options);
170
191
  res.json({
171
192
  success: true,
172
193
  data: preview