@adversity/coding-tool-x 2.3.0 → 2.4.1

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 (35) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +8 -0
  3. package/dist/web/assets/icons-Dom8a0SN.js +1 -0
  4. package/dist/web/assets/index-CQeUIH7E.css +41 -0
  5. package/dist/web/assets/index-YrjlFzC4.js +14 -0
  6. package/dist/web/assets/naive-ui-BjMHakwv.js +1 -0
  7. package/dist/web/assets/vendors-DtJKdpSj.js +7 -0
  8. package/dist/web/assets/vue-vendor-VFuFB5f4.js +44 -0
  9. package/dist/web/index.html +6 -2
  10. package/package.json +2 -2
  11. package/src/commands/export-config.js +205 -0
  12. package/src/config/default.js +1 -1
  13. package/src/server/api/config-export.js +122 -0
  14. package/src/server/api/config-sync.js +155 -0
  15. package/src/server/api/config-templates.js +12 -6
  16. package/src/server/api/health-check.js +1 -89
  17. package/src/server/api/permissions.js +92 -69
  18. package/src/server/api/projects.js +2 -2
  19. package/src/server/api/sessions.js +70 -70
  20. package/src/server/api/skills.js +206 -0
  21. package/src/server/api/terminal.js +26 -0
  22. package/src/server/index.js +7 -11
  23. package/src/server/services/config-export-service.js +415 -0
  24. package/src/server/services/config-sync-service.js +515 -0
  25. package/src/server/services/config-templates-service.js +61 -38
  26. package/src/server/services/enhanced-cache.js +196 -0
  27. package/src/server/services/health-check.js +1 -315
  28. package/src/server/services/permission-templates-service.js +339 -0
  29. package/src/server/services/pty-manager.js +35 -1
  30. package/src/server/services/sessions.js +122 -44
  31. package/src/server/services/skill-service.js +252 -2
  32. package/src/server/services/workspace-service.js +44 -84
  33. package/src/server/websocket-server.js +4 -1
  34. package/dist/web/assets/index-dhun1bYQ.js +0 -3555
  35. package/dist/web/assets/index-hHb7DAda.css +0 -41
@@ -0,0 +1,205 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+ const archiver = require('archiver');
5
+ const chalk = require('chalk');
6
+
7
+ /**
8
+ * 导出 Claude Code 配置为 ZIP 压缩包
9
+ * 包含:全局 CLAUDE.md、项目 CLAUDE.md、settings.json、MCP 服务器配置、Skills 等
10
+ */
11
+ async function exportConfig(options = {}) {
12
+ const { output = 'claude-config.zip', includeProjects = true } = options;
13
+
14
+ try {
15
+ console.log(chalk.blue('🚀 开始导出 Claude Code 配置...'));
16
+
17
+ const homeDir = os.homedir();
18
+ const claudeDir = path.join(homeDir, '.claude');
19
+ const currentDir = process.cwd();
20
+
21
+ // 检查 .claude 目录是否存在
22
+ if (!fs.existsSync(claudeDir)) {
23
+ console.log(chalk.red('❌ 未找到 .claude 配置目录'));
24
+ return;
25
+ }
26
+
27
+ // 创建输出文件流
28
+ const outputPath = path.resolve(currentDir, output);
29
+ const outputStream = fs.createWriteStream(outputPath);
30
+ const archive = archiver('zip', { zlib: { level: 9 } });
31
+
32
+ // 监听错误
33
+ archive.on('error', (err) => {
34
+ throw err;
35
+ });
36
+
37
+ // 监听完成
38
+ archive.on('end', () => {
39
+ console.log(chalk.green(`\n✅ 配置导出成功!`));
40
+ console.log(chalk.gray(` 文件位置: ${outputPath}`));
41
+ console.log(chalk.gray(` 文件大小: ${(archive.pointer() / 1024 / 1024).toFixed(2)} MB`));
42
+ });
43
+
44
+ // 连接流
45
+ archive.pipe(outputStream);
46
+
47
+ // 收集配置清单
48
+ const manifest = {
49
+ exportTime: new Date().toISOString(),
50
+ version: '1.0.0',
51
+ globalConfig: {},
52
+ projectConfig: {},
53
+ enabledConfigs: []
54
+ };
55
+
56
+ // 1. 导出全局 CLAUDE.md
57
+ const globalClaudeMd = path.join(claudeDir, 'CLAUDE.md');
58
+ if (fs.existsSync(globalClaudeMd)) {
59
+ archive.file(globalClaudeMd, { name: 'global/CLAUDE.md' });
60
+ manifest.globalConfig.claudeMd = true;
61
+ manifest.enabledConfigs.push('global/CLAUDE.md');
62
+ console.log(chalk.gray(' ✓ 全局 CLAUDE.md'));
63
+ }
64
+
65
+ // 2. 导出 settings.json
66
+ const settingsJson = path.join(claudeDir, 'settings.json');
67
+ if (fs.existsSync(settingsJson)) {
68
+ archive.file(settingsJson, { name: 'global/settings.json' });
69
+ manifest.globalConfig.settings = true;
70
+ manifest.enabledConfigs.push('global/settings.json');
71
+ console.log(chalk.gray(' ✓ settings.json'));
72
+
73
+ // 解析 MCP 服务器配置
74
+ try {
75
+ const settings = JSON.parse(fs.readFileSync(settingsJson, 'utf-8'));
76
+ if (settings.mcpServers) {
77
+ manifest.globalConfig.mcpServers = Object.keys(settings.mcpServers);
78
+ console.log(chalk.gray(` - MCP 服务器: ${manifest.globalConfig.mcpServers.join(', ')}`));
79
+ }
80
+ if (settings.hooks) {
81
+ manifest.globalConfig.hooks = Object.keys(settings.hooks);
82
+ }
83
+ if (settings.statusLine) {
84
+ manifest.globalConfig.statusLine = true;
85
+ }
86
+ } catch (e) {
87
+ console.log(chalk.yellow(' ⚠ settings.json 解析失败'));
88
+ }
89
+ }
90
+
91
+ // 3. 导出 Skills
92
+ const skillsDir = path.join(claudeDir, 'skills');
93
+ if (fs.existsSync(skillsDir)) {
94
+ const skills = fs.readdirSync(skillsDir).filter(f => !f.startsWith('.'));
95
+ if (skills.length > 0) {
96
+ archive.directory(skillsDir, 'global/skills');
97
+ manifest.globalConfig.skills = skills;
98
+ manifest.enabledConfigs.push('global/skills');
99
+ console.log(chalk.gray(` ✓ Skills (${skills.length} 个)`));
100
+ }
101
+ }
102
+
103
+ // 4. 导出当前项目配置
104
+ if (includeProjects) {
105
+ // 项目级 CLAUDE.md
106
+ const projectClaudeMd = path.join(currentDir, 'CLAUDE.md');
107
+ if (fs.existsSync(projectClaudeMd)) {
108
+ archive.file(projectClaudeMd, { name: 'project/CLAUDE.md' });
109
+ manifest.projectConfig.claudeMd = true;
110
+ manifest.enabledConfigs.push('project/CLAUDE.md');
111
+ console.log(chalk.gray(' ✓ 项目 CLAUDE.md'));
112
+ }
113
+
114
+ // 项目级 settings.local.json
115
+ const projectSettings = path.join(currentDir, '.claude', 'settings.local.json');
116
+ if (fs.existsSync(projectSettings)) {
117
+ archive.file(projectSettings, { name: 'project/.claude/settings.local.json' });
118
+ manifest.projectConfig.settingsLocal = true;
119
+ manifest.enabledConfigs.push('project/.claude/settings.local.json');
120
+ console.log(chalk.gray(' ✓ 项目 settings.local.json'));
121
+ }
122
+
123
+ // 项目级 workspace CLAUDE.md
124
+ const workspaceClaudeMd = path.join(homeDir, 'workspace', 'CLAUDE.md');
125
+ if (fs.existsSync(workspaceClaudeMd)) {
126
+ archive.file(workspaceClaudeMd, { name: 'workspace/CLAUDE.md' });
127
+ manifest.projectConfig.workspaceClaudeMd = true;
128
+ manifest.enabledConfigs.push('workspace/CLAUDE.md');
129
+ console.log(chalk.gray(' ✓ workspace CLAUDE.md'));
130
+ }
131
+ }
132
+
133
+ // 5. 添加配置清单
134
+ archive.append(JSON.stringify(manifest, null, 2), { name: 'manifest.json' });
135
+ console.log(chalk.gray(' ✓ manifest.json'));
136
+
137
+ // 6. 添加 README
138
+ const readme = generateReadme(manifest);
139
+ archive.append(readme, { name: 'README.md' });
140
+ console.log(chalk.gray(' ✓ README.md'));
141
+
142
+ // 完成打包
143
+ await archive.finalize();
144
+
145
+ } catch (error) {
146
+ console.error(chalk.red('❌ 导出失败:'), error.message);
147
+ throw error;
148
+ }
149
+ }
150
+
151
+ /**
152
+ * 生成配置包说明文档
153
+ */
154
+ function generateReadme(manifest) {
155
+ return `# Claude Code 配置包
156
+
157
+ 导出时间: ${new Date(manifest.exportTime).toLocaleString('zh-CN')}
158
+ 版本: ${manifest.version}
159
+
160
+ ## 📦 包含内容
161
+
162
+ ### 全局配置
163
+ ${manifest.globalConfig.claudeMd ? '- ✅ 全局 CLAUDE.md(AI 行为指令)' : ''}
164
+ ${manifest.globalConfig.settings ? '- ✅ settings.json(包含 MCP 服务器、Hooks 等)' : ''}
165
+ ${manifest.globalConfig.mcpServers ? ` - MCP 服务器: ${manifest.globalConfig.mcpServers.join(', ')}` : ''}
166
+ ${manifest.globalConfig.hooks ? ` - Hooks: ${manifest.globalConfig.hooks.join(', ')}` : ''}
167
+ ${manifest.globalConfig.skills ? `- ✅ Skills (${manifest.globalConfig.skills.length} 个): ${manifest.globalConfig.skills.join(', ')}` : ''}
168
+
169
+ ### 项目配置
170
+ ${manifest.projectConfig.claudeMd ? '- ✅ 项目 CLAUDE.md' : ''}
171
+ ${manifest.projectConfig.settingsLocal ? '- ✅ 项目 settings.local.json' : ''}
172
+ ${manifest.projectConfig.workspaceClaudeMd ? '- ✅ workspace CLAUDE.md' : ''}
173
+
174
+ ## 📥 如何导入
175
+
176
+ ### 方法 1:手动导入
177
+ 1. 解压此 ZIP 文件
178
+ 2. 复制 \`global/\` 内容到 \`~/.claude/\`
179
+ 3. 复制 \`project/\` 内容到你的项目目录
180
+ 4. 复制 \`workspace/\` 内容到 \`~/workspace/\`
181
+
182
+ ### 方法 2:使用命令(计划中)
183
+ \`\`\`bash
184
+ ctx import-config claude-config.zip
185
+ \`\`\`
186
+
187
+ ## ⚠️ 注意事项
188
+
189
+ 1. **备份现有配置**:导入前请备份现有的 \`.claude\` 目录
190
+ 2. **敏感信息**:此配置包可能包含 API Keys、代理配置等敏感信息,请妥善保管
191
+ 3. **路径适配**:某些路径可能需要根据你的系统调整(如 hooks 中的脚本路径)
192
+ 4. **MCP 服务器**:需要确保目标环境已安装对应的 MCP 服务器
193
+
194
+ ## 📝 配置清单
195
+
196
+ 已启用的配置项:
197
+ ${manifest.enabledConfigs.map(c => `- ${c}`).join('\n')}
198
+
199
+ ---
200
+
201
+ 生成工具: Coding-Tool-X (ctx)
202
+ `;
203
+ }
204
+
205
+ module.exports = { exportConfig };
@@ -3,7 +3,7 @@ const path = require('path');
3
3
  const os = require('os');
4
4
 
5
5
  const DEFAULT_CONFIG = {
6
- projectsDir: path.join(os.homedir(), '.claude', 'ctx', 'projects'),
6
+ projectsDir: path.join(os.homedir(), '.claude', 'projects'),
7
7
  defaultProject: null,
8
8
  maxDisplaySessions: 100,
9
9
  pageSize: 15,
@@ -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;
@@ -0,0 +1,155 @@
1
+ /**
2
+ * 配置同步 API 路由
3
+ */
4
+
5
+ const express = require('express');
6
+ const { ConfigSyncService } = require('../services/config-sync-service');
7
+
8
+ const router = express.Router();
9
+ const configSyncService = new ConfigSyncService();
10
+
11
+ /**
12
+ * 获取可同步的配置列表
13
+ * GET /api/config-sync/available
14
+ * Query: source=global|workspace, projectPath=...
15
+ */
16
+ router.get('/available', (req, res) => {
17
+ try {
18
+ const { source = 'global', projectPath } = req.query;
19
+
20
+ if (source === 'workspace' && !projectPath) {
21
+ return res.status(400).json({
22
+ success: false,
23
+ message: '获取工作区配置需要指定 projectPath'
24
+ });
25
+ }
26
+
27
+ const configs = configSyncService.getAvailableConfigs(source, projectPath);
28
+
29
+ res.json({
30
+ success: true,
31
+ source,
32
+ configs
33
+ });
34
+ } catch (err) {
35
+ console.error('[ConfigSync API] Get available configs error:', err);
36
+ res.status(500).json({
37
+ success: false,
38
+ message: err.message
39
+ });
40
+ }
41
+ });
42
+
43
+ /**
44
+ * 获取同步统计信息
45
+ * GET /api/config-sync/stats
46
+ * Query: projectPath=...
47
+ */
48
+ router.get('/stats', (req, res) => {
49
+ try {
50
+ const { projectPath } = req.query;
51
+ const stats = configSyncService.getStats(projectPath);
52
+
53
+ res.json({
54
+ success: true,
55
+ stats
56
+ });
57
+ } catch (err) {
58
+ console.error('[ConfigSync API] Get stats error:', err);
59
+ res.status(500).json({
60
+ success: false,
61
+ message: err.message
62
+ });
63
+ }
64
+ });
65
+
66
+ /**
67
+ * 预览同步结果
68
+ * POST /api/config-sync/preview
69
+ * Body: { source, target, configTypes, projectPath, selectedItems }
70
+ */
71
+ router.post('/preview', (req, res) => {
72
+ try {
73
+ const { source, target, configTypes, projectPath, selectedItems } = req.body;
74
+
75
+ if (!source || !target) {
76
+ return res.status(400).json({
77
+ success: false,
78
+ message: '请指定源和目标'
79
+ });
80
+ }
81
+
82
+ if (!configTypes || configTypes.length === 0) {
83
+ return res.status(400).json({
84
+ success: false,
85
+ message: '请选择要同步的配置类型'
86
+ });
87
+ }
88
+
89
+ const preview = configSyncService.previewSync({
90
+ source,
91
+ target,
92
+ configTypes,
93
+ projectPath,
94
+ selectedItems
95
+ });
96
+
97
+ res.json({
98
+ success: true,
99
+ preview
100
+ });
101
+ } catch (err) {
102
+ console.error('[ConfigSync API] Preview sync error:', err);
103
+ res.status(500).json({
104
+ success: false,
105
+ message: err.message
106
+ });
107
+ }
108
+ });
109
+
110
+ /**
111
+ * 执行同步
112
+ * POST /api/config-sync/execute
113
+ * Body: { source, target, configTypes, projectPath, selectedItems, overwrite }
114
+ */
115
+ router.post('/execute', (req, res) => {
116
+ try {
117
+ const { source, target, configTypes, projectPath, selectedItems, overwrite = false } = req.body;
118
+
119
+ if (!source || !target) {
120
+ return res.status(400).json({
121
+ success: false,
122
+ message: '请指定源和目标'
123
+ });
124
+ }
125
+
126
+ if (!configTypes || configTypes.length === 0) {
127
+ return res.status(400).json({
128
+ success: false,
129
+ message: '请选择要同步的配置类型'
130
+ });
131
+ }
132
+
133
+ const result = configSyncService.executeSync({
134
+ source,
135
+ target,
136
+ configTypes,
137
+ projectPath,
138
+ selectedItems,
139
+ overwrite
140
+ });
141
+
142
+ res.json({
143
+ success: true,
144
+ result
145
+ });
146
+ } catch (err) {
147
+ console.error('[ConfigSync API] Execute sync error:', err);
148
+ res.status(500).json({
149
+ success: false,
150
+ message: err.message
151
+ });
152
+ }
153
+ });
154
+
155
+ module.exports = router;
@@ -139,7 +139,7 @@ router.delete('/:id', (req, res) => {
139
139
  */
140
140
  router.post('/:id/apply', (req, res) => {
141
141
  try {
142
- const { targetPath, aiConfigType } = req.body;
142
+ const { targetPath, aiConfigType, aiConfigTypes } = req.body;
143
143
  if (!targetPath) {
144
144
  return res.status(400).json({
145
145
  success: false,
@@ -147,8 +147,11 @@ router.post('/:id/apply', (req, res) => {
147
147
  });
148
148
  }
149
149
  const options = {};
150
- if (aiConfigType) {
151
- options.aiConfigType = aiConfigType;
150
+ // 优先使用新的数组参数,兼容旧的单值参数
151
+ if (aiConfigTypes) {
152
+ options.aiConfigTypes = aiConfigTypes;
153
+ } else if (aiConfigType) {
154
+ options.aiConfigTypes = [aiConfigType];
152
155
  }
153
156
  const result = templatesService.applyTemplateToProject(targetPath, req.params.id, options);
154
157
  res.json({
@@ -170,7 +173,7 @@ router.post('/:id/apply', (req, res) => {
170
173
  */
171
174
  router.post('/:id/preview', (req, res) => {
172
175
  try {
173
- const { targetPath, aiConfigType } = req.body;
176
+ const { targetPath, aiConfigType, aiConfigTypes } = req.body;
174
177
  if (!targetPath) {
175
178
  return res.status(400).json({
176
179
  success: false,
@@ -178,8 +181,11 @@ router.post('/:id/preview', (req, res) => {
178
181
  });
179
182
  }
180
183
  const options = {};
181
- if (aiConfigType) {
182
- options.aiConfigType = aiConfigType;
184
+ // 优先使用新的数组参数,兼容旧的单值参数
185
+ if (aiConfigTypes) {
186
+ options.aiConfigTypes = aiConfigTypes;
187
+ } else if (aiConfigType) {
188
+ options.aiConfigTypes = [aiConfigType];
183
189
  }
184
190
  const preview = templatesService.previewTemplateApplication(targetPath, req.params.id, options);
185
191
  res.json({
@@ -1,11 +1,6 @@
1
1
  const express = require('express');
2
2
  const router = express.Router();
3
- const {
4
- healthCheckAllProjects,
5
- scanLegacySessionFiles,
6
- migrateSessionFiles,
7
- cleanLegacySessionFiles
8
- } = require('../services/health-check');
3
+ const { healthCheckAllProjects } = require('../services/health-check');
9
4
  const { getProjects } = require('../services/sessions');
10
5
 
11
6
  module.exports = (config) => {
@@ -31,88 +26,5 @@ module.exports = (config) => {
31
26
  }
32
27
  });
33
28
 
34
- /**
35
- * GET /api/health-check/scan-legacy - 扫描旧文件
36
- */
37
- router.get('/scan-legacy', (req, res) => {
38
- try {
39
- const result = scanLegacySessionFiles();
40
-
41
- res.json({
42
- success: true,
43
- timestamp: new Date().toISOString(),
44
- ...result
45
- });
46
- } catch (error) {
47
- console.error('Legacy scan failed:', error);
48
- res.status(500).json({
49
- success: false,
50
- error: error.message
51
- });
52
- }
53
- });
54
-
55
- /**
56
- * POST /api/health-check/migrate-legacy - 迁移旧文件到正确位置
57
- * Body:
58
- * {
59
- * "dryRun": boolean, // 是否只是预演
60
- * "projectNames": string[] // 可选:指定要迁移的项目
61
- * }
62
- */
63
- router.post('/migrate-legacy', (req, res) => {
64
- try {
65
- const { dryRun = false, projectNames = null } = req.body;
66
-
67
- const result = migrateSessionFiles({
68
- dryRun,
69
- projectNames
70
- });
71
-
72
- res.json({
73
- success: true,
74
- timestamp: new Date().toISOString(),
75
- ...result
76
- });
77
- } catch (error) {
78
- console.error('Legacy migration failed:', error);
79
- res.status(500).json({
80
- success: false,
81
- error: error.message
82
- });
83
- }
84
- });
85
-
86
- /**
87
- * POST /api/health-check/clean-legacy - 清理旧文件
88
- * Body:
89
- * {
90
- * "dryRun": boolean, // 是否只是预演
91
- * "projectNames": string[] // 可选:指定要清理的项目
92
- * }
93
- */
94
- router.post('/clean-legacy', (req, res) => {
95
- try {
96
- const { dryRun = false, projectNames = null } = req.body;
97
-
98
- const result = cleanLegacySessionFiles({
99
- dryRun,
100
- projectNames
101
- });
102
-
103
- res.json({
104
- success: true,
105
- timestamp: new Date().toISOString(),
106
- ...result
107
- });
108
- } catch (error) {
109
- console.error('Legacy cleanup failed:', error);
110
- res.status(500).json({
111
- success: false,
112
- error: error.message
113
- });
114
- }
115
- });
116
-
117
29
  return router;
118
30
  };