@adversity/coding-tool-x 2.4.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.
@@ -312,6 +312,212 @@ router.put('/repos/:owner/:name/toggle', (req, res) => {
312
312
  }
313
313
  });
314
314
 
315
+ // ==================== 多文件技能管理 API ====================
316
+
317
+ /**
318
+ * 创建带多文件的技能
319
+ * POST /api/skills/create-with-files
320
+ * Body: { directory, files: [{path, content, isBase64?}] }
321
+ */
322
+ router.post('/create-with-files', (req, res) => {
323
+ try {
324
+ const { directory, files } = req.body;
325
+
326
+ if (!directory) {
327
+ return res.status(400).json({
328
+ success: false,
329
+ message: '请输入目录名称'
330
+ });
331
+ }
332
+
333
+ // 校验目录名:只允许英文、数字、横杠、下划线
334
+ if (!/^[a-zA-Z0-9_-]+$/.test(directory)) {
335
+ return res.status(400).json({
336
+ success: false,
337
+ message: '目录名只能包含英文、数字、横杠和下划线'
338
+ });
339
+ }
340
+
341
+ if (!files || !Array.isArray(files) || files.length === 0) {
342
+ return res.status(400).json({
343
+ success: false,
344
+ message: '请提供文件列表'
345
+ });
346
+ }
347
+
348
+ const result = skillService.createSkillWithFiles({ directory, files });
349
+
350
+ res.json({
351
+ success: true,
352
+ ...result
353
+ });
354
+ } catch (err) {
355
+ console.error('[Skills API] Create skill with files error:', err);
356
+ res.status(500).json({
357
+ success: false,
358
+ message: err.message
359
+ });
360
+ }
361
+ });
362
+
363
+ /**
364
+ * 获取技能文件列表
365
+ * GET /api/skills/:directory/files
366
+ */
367
+ router.get('/:directory/files', (req, res) => {
368
+ try {
369
+ const { directory } = req.params;
370
+ const files = skillService.getSkillFiles(directory);
371
+
372
+ res.json({
373
+ success: true,
374
+ directory,
375
+ files
376
+ });
377
+ } catch (err) {
378
+ console.error('[Skills API] Get skill files error:', err);
379
+ res.status(500).json({
380
+ success: false,
381
+ message: err.message
382
+ });
383
+ }
384
+ });
385
+
386
+ /**
387
+ * 获取技能文件内容
388
+ * GET /api/skills/:directory/files/:filePath
389
+ * 注意:filePath 可能包含子目录,使用通配符
390
+ */
391
+ router.get('/:directory/file/*', (req, res) => {
392
+ try {
393
+ const { directory } = req.params;
394
+ const filePath = req.params[0];
395
+
396
+ if (!filePath) {
397
+ return res.status(400).json({
398
+ success: false,
399
+ message: '请指定文件路径'
400
+ });
401
+ }
402
+
403
+ const result = skillService.getSkillFileContent(directory, filePath);
404
+
405
+ res.json({
406
+ success: true,
407
+ ...result
408
+ });
409
+ } catch (err) {
410
+ console.error('[Skills API] Get skill file content error:', err);
411
+ res.status(500).json({
412
+ success: false,
413
+ message: err.message
414
+ });
415
+ }
416
+ });
417
+
418
+ /**
419
+ * 添加文件到技能
420
+ * POST /api/skills/:directory/files
421
+ * Body: { files: [{path, content, isBase64?}] }
422
+ */
423
+ router.post('/:directory/files', (req, res) => {
424
+ try {
425
+ const { directory } = req.params;
426
+ const { files } = req.body;
427
+
428
+ if (!files || !Array.isArray(files) || files.length === 0) {
429
+ return res.status(400).json({
430
+ success: false,
431
+ message: '请提供文件列表'
432
+ });
433
+ }
434
+
435
+ const result = skillService.addSkillFiles(directory, files);
436
+
437
+ res.json({
438
+ success: true,
439
+ ...result
440
+ });
441
+ } catch (err) {
442
+ console.error('[Skills API] Add skill files error:', err);
443
+ res.status(500).json({
444
+ success: false,
445
+ message: err.message
446
+ });
447
+ }
448
+ });
449
+
450
+ /**
451
+ * 删除技能中的文件
452
+ * DELETE /api/skills/:directory/file/*
453
+ */
454
+ router.delete('/:directory/file/*', (req, res) => {
455
+ try {
456
+ const { directory } = req.params;
457
+ const filePath = req.params[0];
458
+
459
+ if (!filePath) {
460
+ return res.status(400).json({
461
+ success: false,
462
+ message: '请指定文件路径'
463
+ });
464
+ }
465
+
466
+ const result = skillService.deleteSkillFile(directory, filePath);
467
+
468
+ res.json({
469
+ success: true,
470
+ ...result
471
+ });
472
+ } catch (err) {
473
+ console.error('[Skills API] Delete skill file error:', err);
474
+ res.status(500).json({
475
+ success: false,
476
+ message: err.message
477
+ });
478
+ }
479
+ });
480
+
481
+ /**
482
+ * 更新技能文件内容
483
+ * PUT /api/skills/:directory/file/*
484
+ * Body: { content, isBase64? }
485
+ */
486
+ router.put('/:directory/file/*', (req, res) => {
487
+ try {
488
+ const { directory } = req.params;
489
+ const filePath = req.params[0];
490
+ const { content, isBase64 = false } = req.body;
491
+
492
+ if (!filePath) {
493
+ return res.status(400).json({
494
+ success: false,
495
+ message: '请指定文件路径'
496
+ });
497
+ }
498
+
499
+ if (content === undefined) {
500
+ return res.status(400).json({
501
+ success: false,
502
+ message: '请提供文件内容'
503
+ });
504
+ }
505
+
506
+ const result = skillService.updateSkillFile(directory, filePath, content, isBase64);
507
+
508
+ res.json({
509
+ success: true,
510
+ ...result
511
+ });
512
+ } catch (err) {
513
+ console.error('[Skills API] Update skill file error:', err);
514
+ res.status(500).json({
515
+ success: false,
516
+ message: err.message
517
+ });
518
+ }
519
+ });
520
+
315
521
  // ==================== 格式转换 API ====================
316
522
 
317
523
  /**
@@ -28,6 +28,32 @@ router.get('/list', (req, res) => {
28
28
  }
29
29
  });
30
30
 
31
+ /**
32
+ * GET /api/terminal/health - 检查终端健康状态
33
+ */
34
+ router.get('/health', (req, res) => {
35
+ try {
36
+ const isPtyAvailable = ptyManager.isPtyAvailable();
37
+ const ptyError = ptyManager.getPtyError();
38
+
39
+ res.json({
40
+ success: isPtyAvailable,
41
+ pty: {
42
+ available: isPtyAvailable,
43
+ error: ptyError,
44
+ nodeVersion: process.version,
45
+ platform: process.platform
46
+ },
47
+ shell: {
48
+ default: ptyManager.getDefaultShell(),
49
+ env: process.env.SHELL
50
+ }
51
+ });
52
+ } catch (err) {
53
+ res.status(500).json({ success: false, error: err.message });
54
+ }
55
+ });
56
+
31
57
  /**
32
58
  * GET /api/terminal/commands/config - 获取命令配置
33
59
  * 注意:此路由必须在 /:id 之前定义,否则会被动态路由捕获
@@ -149,6 +149,9 @@ async function startServer(port) {
149
149
  // 配置导出/导入 API
150
150
  app.use('/api/config-export', require('./api/config-export'));
151
151
 
152
+ // 配置同步 API
153
+ app.use('/api/config-sync', require('./api/config-sync'));
154
+
152
155
  // 健康检查 API
153
156
  app.use('/api/health-check', require('./api/health-check')(config));
154
157
 
@@ -17,11 +17,11 @@ const CONFIG_VERSION = '1.0.0';
17
17
  */
18
18
  function exportAllConfigs() {
19
19
  try {
20
- // 获取所有权限模板(只导出自定义模板)
20
+ // 获取所有权限模板(只导出自定义模板)
21
21
  const allPermissionTemplates = permissionTemplatesService.getAllTemplates();
22
22
  const customPermissionTemplates = allPermissionTemplates.filter(t => !t.isBuiltin);
23
23
 
24
- // 获取所有配置模板(只导出自定义模板)
24
+ // 获取所有配置模板(只导出自定义模板)
25
25
  const allConfigTemplates = configTemplatesService.getAllTemplates();
26
26
  const customConfigTemplates = allConfigTemplates.filter(t => !t.isBuiltin);
27
27
 
@@ -29,13 +29,65 @@ function exportAllConfigs() {
29
29
  const channelsData = channelsService.getAllChannels();
30
30
  const channels = channelsData?.channels || [];
31
31
 
32
+ // 获取工作区配置
33
+ const workspaceService = require('./workspace-service');
34
+ const workspaces = workspaceService.loadWorkspaces();
35
+
36
+ // 获取收藏配置
37
+ const favoritesService = require('./favorites');
38
+ const favorites = favoritesService.loadFavorites();
39
+
40
+ // 获取 Agents 配置
41
+ const agentsService = require('./agents-service');
42
+ const agents = agentsService.getAllAgents();
43
+
44
+ // 获取 Skills 配置
45
+ const skillService = require('./skill-service');
46
+ const skills = skillService.getAllSkills();
47
+
48
+ // 获取 Commands 配置
49
+ const commandsService = require('./commands-service');
50
+ const commands = commandsService.getAllCommands();
51
+
52
+ // 获取 Rules 配置
53
+ const rulesService = require('./rules-service');
54
+ const rules = rulesService.getAllRules();
55
+
56
+ // 获取 MCP 配置
57
+ const mcpService = require('./mcp-service');
58
+ const mcpServers = mcpService.getAllServers();
59
+
60
+ // 读取 Markdown 配置文件
61
+ const { PATHS } = require('../../config/paths');
62
+ const markdownFiles = {};
63
+ const mdFileNames = ['CLAUDE.md', 'AGENTS.md', 'GEMINI.md'];
64
+
65
+ for (const fileName of mdFileNames) {
66
+ const filePath = path.join(PATHS.base, fileName);
67
+ if (fs.existsSync(filePath)) {
68
+ try {
69
+ markdownFiles[fileName] = fs.readFileSync(filePath, 'utf8');
70
+ } catch (err) {
71
+ console.warn(`无法读取 ${fileName}:`, err.message);
72
+ }
73
+ }
74
+ }
75
+
32
76
  const exportData = {
33
77
  version: CONFIG_VERSION,
34
78
  exportedAt: new Date().toISOString(),
35
79
  data: {
36
80
  permissionTemplates: customPermissionTemplates,
37
81
  configTemplates: customConfigTemplates,
38
- channels: channels || []
82
+ channels: channels || [],
83
+ workspaces: workspaces || { workspaces: [] },
84
+ favorites: favorites || { favorites: [] },
85
+ agents: agents || [],
86
+ skills: skills || [],
87
+ commands: commands || [],
88
+ rules: rules || [],
89
+ mcpServers: mcpServers || [],
90
+ markdownFiles: markdownFiles
39
91
  }
40
92
  };
41
93
 
@@ -59,11 +111,19 @@ function exportAllConfigs() {
59
111
  * @returns {Object} 导入结果
60
112
  */
61
113
  function importConfigs(importData, options = {}) {
62
- const { overwrite = false } = options;
114
+ const { overwrite = true } = options; // 默认覆盖模式
63
115
  const results = {
64
116
  permissionTemplates: { success: 0, failed: 0, skipped: 0 },
65
117
  configTemplates: { success: 0, failed: 0, skipped: 0 },
66
- channels: { success: 0, failed: 0, skipped: 0 }
118
+ channels: { success: 0, failed: 0, skipped: 0 },
119
+ workspaces: { success: 0, failed: 0, skipped: 0 },
120
+ favorites: { success: 0, failed: 0, skipped: 0 },
121
+ agents: { success: 0, failed: 0, skipped: 0 },
122
+ skills: { success: 0, failed: 0, skipped: 0 },
123
+ commands: { success: 0, failed: 0, skipped: 0 },
124
+ rules: { success: 0, failed: 0, skipped: 0 },
125
+ mcpServers: { success: 0, failed: 0, skipped: 0 },
126
+ markdownFiles: { success: 0, failed: 0, skipped: 0 }
67
127
  };
68
128
 
69
129
  try {
@@ -72,7 +132,19 @@ function importConfigs(importData, options = {}) {
72
132
  throw new Error('无效的导入数据格式');
73
133
  }
74
134
 
75
- const { permissionTemplates = [], configTemplates = [], channels = [] } = importData.data;
135
+ const {
136
+ permissionTemplates = [],
137
+ configTemplates = [],
138
+ channels = [],
139
+ workspaces = null,
140
+ favorites = null,
141
+ agents = [],
142
+ skills = [],
143
+ commands = [],
144
+ rules = [],
145
+ mcpServers = [],
146
+ markdownFiles = {}
147
+ } = importData.data;
76
148
 
77
149
  // 导入权限模板
78
150
  for (const template of permissionTemplates) {
@@ -87,7 +159,6 @@ function importConfigs(importData, options = {}) {
87
159
  if (existing && overwrite) {
88
160
  permissionTemplatesService.updateTemplate(template.id, template);
89
161
  } else {
90
- // 创建新模板(使用原ID)
91
162
  const newTemplate = {
92
163
  ...template,
93
164
  isBuiltin: false,
@@ -144,7 +215,6 @@ function importConfigs(importData, options = {}) {
144
215
  if (existing && overwrite) {
145
216
  channelsService.updateChannel(channel.id, channel);
146
217
  } else {
147
- // createChannel 需要单独的参数,不是一个对象
148
218
  const { name, baseUrl, apiKey, websiteUrl, ...extraConfig } = channel;
149
219
  channelsService.createChannel(name, baseUrl, apiKey, websiteUrl, extraConfig);
150
220
  }
@@ -155,6 +225,136 @@ function importConfigs(importData, options = {}) {
155
225
  }
156
226
  }
157
227
 
228
+ // 导入工作区配置
229
+ if (workspaces && overwrite) {
230
+ try {
231
+ const workspaceService = require('./workspace-service');
232
+ workspaceService.saveWorkspaces(workspaces);
233
+ results.workspaces.success = workspaces.workspaces?.length || 0;
234
+ } catch (err) {
235
+ console.error('[ConfigImport] 导入工作区失败:', err);
236
+ results.workspaces.failed++;
237
+ }
238
+ }
239
+
240
+ // 导入收藏配置
241
+ if (favorites && overwrite) {
242
+ try {
243
+ const favoritesService = require('./favorites');
244
+ favoritesService.saveFavorites(favorites);
245
+ const count = Object.values(favorites).reduce((sum, arr) => sum + (Array.isArray(arr) ? arr.length : 0), 0);
246
+ results.favorites.success = count;
247
+ } catch (err) {
248
+ console.error('[ConfigImport] 导入收藏失败:', err);
249
+ results.favorites.failed++;
250
+ }
251
+ }
252
+
253
+ // 导入 Agents
254
+ if (agents && agents.length > 0 && overwrite) {
255
+ try {
256
+ const agentsService = require('./agents-service');
257
+ for (const agent of agents) {
258
+ try {
259
+ agentsService.saveAgent(agent);
260
+ results.agents.success++;
261
+ } catch (err) {
262
+ console.error(`[ConfigImport] 导入 Agent 失败: ${agent.name}`, err);
263
+ results.agents.failed++;
264
+ }
265
+ }
266
+ } catch (err) {
267
+ console.error('[ConfigImport] 导入 Agents 失败:', err);
268
+ }
269
+ }
270
+
271
+ // 导入 Skills
272
+ if (skills && skills.length > 0 && overwrite) {
273
+ try {
274
+ const skillService = require('./skill-service');
275
+ for (const skill of skills) {
276
+ try {
277
+ skillService.saveSkill(skill);
278
+ results.skills.success++;
279
+ } catch (err) {
280
+ console.error(`[ConfigImport] 导入 Skill 失败: ${skill.name}`, err);
281
+ results.skills.failed++;
282
+ }
283
+ }
284
+ } catch (err) {
285
+ console.error('[ConfigImport] 导入 Skills 失败:', err);
286
+ }
287
+ }
288
+
289
+ // 导入 Commands
290
+ if (commands && commands.length > 0 && overwrite) {
291
+ try {
292
+ const commandsService = require('./commands-service');
293
+ for (const command of commands) {
294
+ try {
295
+ commandsService.saveCommand(command);
296
+ results.commands.success++;
297
+ } catch (err) {
298
+ console.error(`[ConfigImport] 导入 Command 失败: ${command.name}`, err);
299
+ results.commands.failed++;
300
+ }
301
+ }
302
+ } catch (err) {
303
+ console.error('[ConfigImport] 导入 Commands 失败:', err);
304
+ }
305
+ }
306
+
307
+ // 导入 Rules
308
+ if (rules && rules.length > 0 && overwrite) {
309
+ try {
310
+ const rulesService = require('./rules-service');
311
+ for (const rule of rules) {
312
+ try {
313
+ rulesService.saveRule(rule);
314
+ results.rules.success++;
315
+ } catch (err) {
316
+ console.error(`[ConfigImport] 导入 Rule 失败: ${rule.name}`, err);
317
+ results.rules.failed++;
318
+ }
319
+ }
320
+ } catch (err) {
321
+ console.error('[ConfigImport] 导入 Rules 失败:', err);
322
+ }
323
+ }
324
+
325
+ // 导入 MCP Servers
326
+ if (mcpServers && mcpServers.length > 0 && overwrite) {
327
+ try {
328
+ const mcpService = require('./mcp-service');
329
+ for (const server of mcpServers) {
330
+ try {
331
+ mcpService.saveServer(server);
332
+ results.mcpServers.success++;
333
+ } catch (err) {
334
+ console.error(`[ConfigImport] 导入 MCP Server 失败: ${server.name}`, err);
335
+ results.mcpServers.failed++;
336
+ }
337
+ }
338
+ } catch (err) {
339
+ console.error('[ConfigImport] 导入 MCP Servers 失败:', err);
340
+ }
341
+ }
342
+
343
+ // 导入 Markdown 文件
344
+ if (markdownFiles && Object.keys(markdownFiles).length > 0 && overwrite) {
345
+ const { PATHS } = require('../../config/paths');
346
+ for (const [fileName, content] of Object.entries(markdownFiles)) {
347
+ try {
348
+ const filePath = path.join(PATHS.base, fileName);
349
+ fs.writeFileSync(filePath, content, 'utf8');
350
+ results.markdownFiles.success++;
351
+ } catch (err) {
352
+ console.error(`[ConfigImport] 导入 ${fileName} 失败:`, err);
353
+ results.markdownFiles.failed++;
354
+ }
355
+ }
356
+ }
357
+
158
358
  return {
159
359
  success: true,
160
360
  results,
@@ -176,31 +376,37 @@ function importConfigs(importData, options = {}) {
176
376
  function generateImportSummary(results) {
177
377
  const parts = [];
178
378
 
179
- if (results.permissionTemplates.success > 0) {
180
- parts.push(`权限模板: ${results.permissionTemplates.success}成功`);
181
- }
182
- if (results.configTemplates.success > 0) {
183
- parts.push(`配置模板: ${results.configTemplates.success}成功`);
184
- }
185
- if (results.channels.success > 0) {
186
- parts.push(`频道: ${results.channels.success}成功`);
379
+ const types = [
380
+ { key: 'permissionTemplates', label: '权限模板' },
381
+ { key: 'configTemplates', label: '配置模板' },
382
+ { key: 'channels', label: '频道' },
383
+ { key: 'workspaces', label: '工作区' },
384
+ { key: 'favorites', label: '收藏' },
385
+ { key: 'agents', label: 'Agents' },
386
+ { key: 'skills', label: 'Skills' },
387
+ { key: 'commands', label: 'Commands' },
388
+ { key: 'rules', label: 'Rules' },
389
+ { key: 'mcpServers', label: 'MCP服务器' },
390
+ { key: 'markdownFiles', label: 'Markdown文件' }
391
+ ];
392
+
393
+ for (const { key, label } of types) {
394
+ if (results[key] && results[key].success > 0) {
395
+ parts.push(`${label}: ${results[key].success}成功`);
396
+ }
187
397
  }
188
398
 
189
- const totalSkipped = results.permissionTemplates.skipped +
190
- results.configTemplates.skipped +
191
- results.channels.skipped;
399
+ const totalSkipped = Object.values(results).reduce((sum, r) => sum + (r.skipped || 0), 0);
192
400
  if (totalSkipped > 0) {
193
401
  parts.push(`${totalSkipped}项已跳过`);
194
402
  }
195
403
 
196
- const totalFailed = results.permissionTemplates.failed +
197
- results.configTemplates.failed +
198
- results.channels.failed;
404
+ const totalFailed = Object.values(results).reduce((sum, r) => sum + (r.failed || 0), 0);
199
405
  if (totalFailed > 0) {
200
406
  parts.push(`${totalFailed}项失败`);
201
407
  }
202
408
 
203
- return parts.join(', ');
409
+ return parts.join(', ') || '无数据导入';
204
410
  }
205
411
 
206
412
  module.exports = {