@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,732 @@
1
+ /**
2
+ * 配置模板服务
3
+ *
4
+ * 管理工作区/项目的配置模板组合
5
+ * 支持 CLAUDE.md, skills, rules, commands, agents, MCP 等的预设组合
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const os = require('os');
11
+ const { PATHS } = require('../../config/paths');
12
+ const { AgentsService } = require('./agents-service');
13
+ const { CommandsService } = require('./commands-service');
14
+ const { RulesService } = require('./rules-service');
15
+ const { SkillService } = require('./skill-service');
16
+ const mcpService = require('./mcp-service');
17
+ const skillService = new SkillService();
18
+
19
+ // 配置模板文件路径
20
+ const TEMPLATES_FILE = path.join(PATHS.config, 'config-templates.json');
21
+
22
+ // 内置配置模板
23
+ const BUILTIN_TEMPLATES = [
24
+ {
25
+ id: 'default',
26
+ name: '默认配置',
27
+ description: '标准的开发环境配置',
28
+ claudeMd: {
29
+ enabled: true,
30
+ content: `# 项目配置
31
+
32
+ 这是一个标准的开发项目。
33
+
34
+ ## 代码规范
35
+ - 保持代码简洁
36
+ - 遵循项目现有风格
37
+ - 添加必要的注释
38
+ `
39
+ },
40
+ skills: [],
41
+ rules: [],
42
+ commands: [],
43
+ agents: [],
44
+ mcpServers: [],
45
+ isBuiltin: true
46
+ },
47
+ {
48
+ id: 'full-stack',
49
+ name: '全栈开发',
50
+ description: '前后端全栈开发配置,包含常用的开发工具和规则',
51
+ claudeMd: {
52
+ enabled: true,
53
+ content: `# 全栈开发项目
54
+
55
+ 你是一个经验丰富的全栈开发工程师。
56
+
57
+ ## 技术栈关注
58
+ - **前端**: React/Vue, TypeScript, 组件化开发
59
+ - **后端**: Node.js/Python, RESTful API, 数据库设计
60
+ - **DevOps**: Git, CI/CD, Docker
61
+
62
+ ## 开发原则
63
+ - KISS: Keep It Simple, Stupid
64
+ - DRY: Don't Repeat Yourself
65
+ - YAGNI: You Aren't Gonna Need It
66
+ - 安全优先:防止 XSS、SQL 注入等常见漏洞
67
+
68
+ ## 代码风格
69
+ - 使用 TypeScript 时启用严格模式
70
+ - 函数保持单一职责
71
+ - 添加适当的错误处理
72
+ `
73
+ },
74
+ skills: [],
75
+ rules: [],
76
+ commands: [],
77
+ agents: [],
78
+ mcpServers: ['github', 'fetch'],
79
+ isBuiltin: true
80
+ },
81
+ {
82
+ id: 'data-science',
83
+ name: '数据科学',
84
+ description: '数据分析和机器学习项目配置',
85
+ claudeMd: {
86
+ enabled: true,
87
+ content: `# 数据科学项目
88
+
89
+ 你是一个数据科学专家,擅长数据分析和机器学习。
90
+
91
+ ## 工作流程
92
+ 1. **数据探索**: 理解数据结构、分布、缺失值
93
+ 2. **数据清洗**: 处理异常值、填充缺失、特征工程
94
+ 3. **建模**: 选择合适的算法,调优参数
95
+ 4. **评估**: 使用适当的指标评估模型性能
96
+ 5. **可视化**: 清晰展示分析结果
97
+
98
+ ## 常用库
99
+ - pandas, numpy: 数据处理
100
+ - scikit-learn: 机器学习
101
+ - matplotlib, seaborn: 可视化
102
+ - jupyter: 交互式开发
103
+
104
+ ## 最佳实践
105
+ - 始终验证数据质量
106
+ - 避免数据泄露
107
+ - 记录实验过程和参数
108
+ - 可复现的分析流程
109
+ `
110
+ },
111
+ skills: [],
112
+ rules: [],
113
+ commands: [],
114
+ agents: [],
115
+ mcpServers: ['fetch'],
116
+ isBuiltin: true
117
+ },
118
+ {
119
+ id: 'code-review',
120
+ name: '代码审查',
121
+ description: '专注于代码审查和质量改进',
122
+ claudeMd: {
123
+ enabled: true,
124
+ content: `# 代码审查专家
125
+
126
+ 你是一个专业的代码审查专家。
127
+
128
+ ## 审查维度
129
+ - **可读性**: 命名、注释、代码组织
130
+ - **性能**: 算法复杂度、资源使用
131
+ - **安全**: 常见漏洞、输入验证
132
+ - **可维护性**: 模块化、耦合度
133
+ - **测试**: 测试覆盖、边界情况
134
+
135
+ ## 审查流程
136
+ 1. 理解代码意图和上下文
137
+ 2. 检查代码逻辑和实现
138
+ 3. 识别潜在问题
139
+ 4. 提供具体改进建议
140
+ 5. 给出示例代码(如需要)
141
+ `
142
+ },
143
+ skills: [],
144
+ rules: [],
145
+ commands: [],
146
+ agents: [],
147
+ mcpServers: ['github'],
148
+ isBuiltin: true
149
+ },
150
+ {
151
+ id: 'minimal',
152
+ name: '最小配置',
153
+ description: '不使用任何额外配置的纯净环境',
154
+ claudeMd: {
155
+ enabled: false,
156
+ content: ''
157
+ },
158
+ skills: [],
159
+ rules: [],
160
+ commands: [],
161
+ agents: [],
162
+ mcpServers: [],
163
+ isBuiltin: true
164
+ }
165
+ ];
166
+
167
+ /**
168
+ * 确保目录存在
169
+ */
170
+ function ensureDir(dirPath) {
171
+ if (!fs.existsSync(dirPath)) {
172
+ fs.mkdirSync(dirPath, { recursive: true });
173
+ }
174
+ }
175
+
176
+ /**
177
+ * 加载配置模板
178
+ */
179
+ function loadTemplates() {
180
+ try {
181
+ if (fs.existsSync(TEMPLATES_FILE)) {
182
+ const content = fs.readFileSync(TEMPLATES_FILE, 'utf8');
183
+ const data = JSON.parse(content);
184
+ // 合并内置模板和用户模板
185
+ return {
186
+ builtin: BUILTIN_TEMPLATES,
187
+ custom: data.custom || []
188
+ };
189
+ }
190
+ } catch (error) {
191
+ console.error('加载配置模板失败:', error.message);
192
+ }
193
+
194
+ return {
195
+ builtin: BUILTIN_TEMPLATES,
196
+ custom: []
197
+ };
198
+ }
199
+
200
+ /**
201
+ * 保存用户自定义模板
202
+ */
203
+ function saveCustomTemplates(customTemplates) {
204
+ try {
205
+ ensureDir(path.dirname(TEMPLATES_FILE));
206
+ const data = {
207
+ custom: customTemplates,
208
+ updatedAt: new Date().toISOString()
209
+ };
210
+ fs.writeFileSync(TEMPLATES_FILE, JSON.stringify(data, null, 2), 'utf8');
211
+ return true;
212
+ } catch (error) {
213
+ console.error('保存配置模板失败:', error.message);
214
+ return false;
215
+ }
216
+ }
217
+
218
+ /**
219
+ * 获取所有模板(内置+自定义)
220
+ */
221
+ function getAllTemplates() {
222
+ const { builtin, custom } = loadTemplates();
223
+ return [...builtin, ...custom];
224
+ }
225
+
226
+ /**
227
+ * 根据 ID 获取模板
228
+ */
229
+ function getTemplateById(id) {
230
+ const templates = getAllTemplates();
231
+ return templates.find(t => t.id === id);
232
+ }
233
+
234
+ /**
235
+ * 创建自定义模板
236
+ */
237
+ function createCustomTemplate(template) {
238
+ const { builtin, custom } = loadTemplates();
239
+
240
+ // 验证必填字段
241
+ if (!template.name || !template.name.trim()) {
242
+ throw new Error('模板名称不能为空');
243
+ }
244
+
245
+ // 生成唯一 ID
246
+ const id = `custom-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
247
+
248
+ const newTemplate = {
249
+ id,
250
+ name: template.name,
251
+ description: template.description || '',
252
+ claudeMd: template.claudeMd || { enabled: false, content: '' },
253
+ skills: template.skills || [],
254
+ rules: template.rules || [],
255
+ commands: template.commands || [],
256
+ agents: template.agents || [],
257
+ mcpServers: template.mcpServers || [],
258
+ isBuiltin: false,
259
+ createdAt: new Date().toISOString()
260
+ };
261
+
262
+ custom.push(newTemplate);
263
+ saveCustomTemplates(custom);
264
+
265
+ return newTemplate;
266
+ }
267
+
268
+ /**
269
+ * 更新自定义模板
270
+ */
271
+ function updateCustomTemplate(id, updates) {
272
+ const { custom } = loadTemplates();
273
+ const index = custom.findIndex(t => t.id === id);
274
+
275
+ if (index === -1) {
276
+ throw new Error('模板不存在或不可修改');
277
+ }
278
+
279
+ custom[index] = {
280
+ ...custom[index],
281
+ ...updates,
282
+ id: custom[index].id, // 保持 ID 不变
283
+ isBuiltin: false,
284
+ updatedAt: new Date().toISOString()
285
+ };
286
+
287
+ saveCustomTemplates(custom);
288
+ return custom[index];
289
+ }
290
+
291
+ /**
292
+ * 删除自定义模板
293
+ */
294
+ function deleteCustomTemplate(id) {
295
+ const { custom } = loadTemplates();
296
+ const filtered = custom.filter(t => t.id !== id);
297
+
298
+ if (filtered.length === custom.length) {
299
+ throw new Error('模板不存在或不可删除');
300
+ }
301
+
302
+ saveCustomTemplates(filtered);
303
+ return true;
304
+ }
305
+
306
+ /**
307
+ * 应用模板到指定目录
308
+ * @param {string} targetDir - 目标目录
309
+ * @param {string} templateId - 模板 ID
310
+ */
311
+ function applyTemplate(targetDir, templateId) {
312
+ const template = getTemplateById(templateId);
313
+
314
+ if (!template) {
315
+ throw new Error('模板不存在');
316
+ }
317
+
318
+ // 确保目标目录存在
319
+ ensureDir(targetDir);
320
+
321
+ const results = {
322
+ claudeMd: false,
323
+ skills: 0,
324
+ rules: 0,
325
+ commands: 0,
326
+ agents: 0,
327
+ mcpServers: 0
328
+ };
329
+
330
+ // 1. 应用 CLAUDE.md
331
+ if (template.claudeMd && template.claudeMd.enabled && template.claudeMd.content) {
332
+ const claudeMdPath = path.join(targetDir, 'CLAUDE.md');
333
+ fs.writeFileSync(claudeMdPath, template.claudeMd.content, 'utf8');
334
+ results.claudeMd = true;
335
+ }
336
+
337
+ // 2. 创建配置记录文件(记录应用了哪个模板)
338
+ const configRecord = {
339
+ templateId: template.id,
340
+ templateName: template.name,
341
+ appliedAt: new Date().toISOString(),
342
+ skills: template.skills,
343
+ rules: template.rules,
344
+ commands: template.commands,
345
+ agents: template.agents,
346
+ mcpServers: template.mcpServers
347
+ };
348
+
349
+ const recordPath = path.join(targetDir, '.ctx-config.json');
350
+ fs.writeFileSync(recordPath, JSON.stringify(configRecord, null, 2), 'utf8');
351
+
352
+ return {
353
+ success: true,
354
+ results,
355
+ template: template.name
356
+ };
357
+ }
358
+
359
+ /**
360
+ * 从目录读取当前配置
361
+ */
362
+ function readCurrentConfig(targetDir) {
363
+ const recordPath = path.join(targetDir, '.ctx-config.json');
364
+
365
+ if (fs.existsSync(recordPath)) {
366
+ try {
367
+ const content = fs.readFileSync(recordPath, 'utf8');
368
+ return JSON.parse(content);
369
+ } catch (error) {
370
+ console.error('读取配置记录失败:', error.message);
371
+ }
372
+ }
373
+
374
+ return null;
375
+ }
376
+
377
+ // ============================================================================
378
+ // 新增方法:获取可用配置、应用模板到项目、预览应用效果
379
+ // ============================================================================
380
+
381
+ /**
382
+ * 获取所有可用配置(用于模板编辑器选择)
383
+ * 返回用户级的 agents, commands, rules + MCP 服务器列表
384
+ */
385
+ function getAvailableConfigs() {
386
+ const agentsService = new AgentsService();
387
+ const commandsService = new CommandsService();
388
+ const rulesService = new RulesService();
389
+
390
+ // 只获取用户级配置
391
+ const { agents } = agentsService.listAgents();
392
+ const { commands } = commandsService.listCommands();
393
+ const { rules } = rulesService.listRules();
394
+ const installedSkills = skillService.getInstalledSkills();
395
+
396
+ // 获取 MCP 服务器
397
+ const mcpServers = mcpService.getAllServers();
398
+ const mcpServerList = Object.values(mcpServers).map(s => ({
399
+ id: s.id,
400
+ name: s.name || s.id,
401
+ description: s.description || ''
402
+ }));
403
+
404
+ // 获取 MCP 预设
405
+ const mcpPresets = mcpService.getPresets().map(p => ({
406
+ id: p.id,
407
+ name: p.name,
408
+ description: p.description
409
+ }));
410
+
411
+ return {
412
+ skills: installedSkills.map(skill => ({
413
+ directory: skill.directory,
414
+ name: skill.name || skill.directory,
415
+ description: skill.description || '',
416
+ repoOwner: skill.repoOwner || null,
417
+ repoName: skill.repoName || null,
418
+ repoBranch: skill.repoBranch || null
419
+ })),
420
+ agents: agents.filter(a => a.scope === 'user').map(a => ({
421
+ fileName: a.fileName,
422
+ name: a.name,
423
+ description: a.description,
424
+ tools: a.tools,
425
+ model: a.model,
426
+ permissionMode: a.permissionMode,
427
+ skills: a.skills,
428
+ systemPrompt: a.systemPrompt
429
+ })),
430
+ commands: commands.filter(c => c.scope === 'user').map(c => ({
431
+ name: c.name,
432
+ namespace: c.namespace,
433
+ description: c.description,
434
+ allowedTools: c.allowedTools,
435
+ argumentHint: c.argumentHint,
436
+ body: c.body
437
+ })),
438
+ rules: rules.filter(r => r.scope === 'user').map(r => ({
439
+ fileName: r.fileName,
440
+ directory: r.directory,
441
+ paths: r.paths,
442
+ body: r.body
443
+ })),
444
+ mcpServers: mcpServerList,
445
+ mcpPresets
446
+ };
447
+ }
448
+
449
+ /**
450
+ * 生成 Agent 文件内容
451
+ */
452
+ function generateAgentContent(agent) {
453
+ const lines = ['---'];
454
+ if (agent.name) lines.push(`name: ${agent.name}`);
455
+ if (agent.description) lines.push(`description: "${agent.description}"`);
456
+ if (agent.tools) lines.push(`tools: ${agent.tools}`);
457
+ if (agent.model) lines.push(`model: ${agent.model}`);
458
+ if (agent.permissionMode) lines.push(`permissionMode: ${agent.permissionMode}`);
459
+ if (agent.skills) lines.push(`skills: ${agent.skills}`);
460
+ lines.push('---');
461
+ return lines.join('\n') + '\n\n' + (agent.systemPrompt || '');
462
+ }
463
+
464
+ /**
465
+ * 生成 Command 文件内容
466
+ */
467
+ function generateCommandContent(command) {
468
+ const lines = ['---'];
469
+ if (command.description) lines.push(`description: "${command.description}"`);
470
+ if (command.allowedTools) lines.push(`allowed-tools: ${command.allowedTools}`);
471
+ if (command.argumentHint) lines.push(`argument-hint: ${command.argumentHint}`);
472
+ lines.push('---');
473
+ return lines.join('\n') + '\n\n' + (command.body || '');
474
+ }
475
+
476
+ /**
477
+ * 生成 Rule 文件内容
478
+ */
479
+ function generateRuleContent(rule) {
480
+ let content = '';
481
+ if (rule.paths) {
482
+ content = `---\npaths: ${rule.paths}\n---\n\n`;
483
+ }
484
+ return content + (rule.body || '');
485
+ }
486
+
487
+ /**
488
+ * 应用模板到项目目录(完整应用,写入实际文件)
489
+ * @param {string} targetDir - 目标项目目录
490
+ * @param {string} templateId - 模板 ID
491
+ */
492
+ function applyTemplateToProject(targetDir, templateId) {
493
+ const template = getTemplateById(templateId);
494
+ if (!template) {
495
+ throw new Error('模板不存在');
496
+ }
497
+
498
+ ensureDir(targetDir);
499
+
500
+ const results = {
501
+ claudeMd: { applied: false, path: null },
502
+ skills: { applied: template.skills?.length || 0, items: template.skills?.map(s => s.directory || s.name) || [] },
503
+ agents: { applied: 0, files: [] },
504
+ commands: { applied: 0, files: [] },
505
+ rules: { applied: 0, files: [] },
506
+ mcpServers: { applied: 0 }
507
+ };
508
+
509
+ // 1. 写入 CLAUDE.md
510
+ if (template.claudeMd?.enabled && template.claudeMd?.content) {
511
+ const claudeMdPath = path.join(targetDir, 'CLAUDE.md');
512
+ fs.writeFileSync(claudeMdPath, template.claudeMd.content, 'utf-8');
513
+ results.claudeMd = { applied: true, path: 'CLAUDE.md' };
514
+ }
515
+
516
+ // 2. 写入 Agents
517
+ if (template.agents?.length > 0) {
518
+ const agentsDir = path.join(targetDir, '.claude', 'agents');
519
+ ensureDir(agentsDir);
520
+ for (const agent of template.agents) {
521
+ const content = generateAgentContent(agent);
522
+ const fileName = agent.fileName || agent.name.toLowerCase().replace(/\s+/g, '-');
523
+ const filePath = path.join(agentsDir, `${fileName}.md`);
524
+ fs.writeFileSync(filePath, content, 'utf-8');
525
+ results.agents.files.push(`.claude/agents/${fileName}.md`);
526
+ results.agents.applied++;
527
+ }
528
+ }
529
+
530
+ // 3. 写入 Commands
531
+ if (template.commands?.length > 0) {
532
+ const commandsDir = path.join(targetDir, '.claude', 'commands');
533
+ ensureDir(commandsDir);
534
+ for (const command of template.commands) {
535
+ const content = generateCommandContent(command);
536
+ const targetCmdDir = command.namespace
537
+ ? path.join(commandsDir, command.namespace)
538
+ : commandsDir;
539
+ ensureDir(targetCmdDir);
540
+ const filePath = path.join(targetCmdDir, `${command.name}.md`);
541
+ fs.writeFileSync(filePath, content, 'utf-8');
542
+ const relativePath = command.namespace
543
+ ? `.claude/commands/${command.namespace}/${command.name}.md`
544
+ : `.claude/commands/${command.name}.md`;
545
+ results.commands.files.push(relativePath);
546
+ results.commands.applied++;
547
+ }
548
+ }
549
+
550
+ // 4. 写入 Rules
551
+ if (template.rules?.length > 0) {
552
+ const rulesDir = path.join(targetDir, '.claude', 'rules');
553
+ ensureDir(rulesDir);
554
+ for (const rule of template.rules) {
555
+ const content = generateRuleContent(rule);
556
+ const targetRuleDir = rule.directory
557
+ ? path.join(rulesDir, rule.directory)
558
+ : rulesDir;
559
+ ensureDir(targetRuleDir);
560
+ const filePath = path.join(targetRuleDir, `${rule.fileName}.md`);
561
+ fs.writeFileSync(filePath, content, 'utf-8');
562
+ const relativePath = rule.directory
563
+ ? `.claude/rules/${rule.directory}/${rule.fileName}.md`
564
+ : `.claude/rules/${rule.fileName}.md`;
565
+ results.rules.files.push(relativePath);
566
+ results.rules.applied++;
567
+ }
568
+ }
569
+
570
+ // 5. 写入 MCP 配置到 .mcp.json
571
+ if (template.mcpServers?.length > 0) {
572
+ const mcpConfig = { mcpServers: {} };
573
+ const allServers = mcpService.getAllServers();
574
+ const presets = mcpService.getPresets();
575
+
576
+ for (const serverId of template.mcpServers) {
577
+ // 先从已配置的服务器中查找
578
+ let serverSpec = allServers[serverId]?.server;
579
+ // 如果没有,从预设中查找
580
+ if (!serverSpec) {
581
+ const preset = presets.find(p => p.id === serverId);
582
+ if (preset) {
583
+ serverSpec = preset.server;
584
+ }
585
+ }
586
+ if (serverSpec) {
587
+ mcpConfig.mcpServers[serverId] = serverSpec;
588
+ results.mcpServers.applied++;
589
+ }
590
+ }
591
+
592
+ if (Object.keys(mcpConfig.mcpServers).length > 0) {
593
+ const mcpPath = path.join(targetDir, '.mcp.json');
594
+ fs.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), 'utf-8');
595
+ }
596
+ }
597
+
598
+ // 6. 创建配置记录文件
599
+ const configRecord = {
600
+ templateId: template.id,
601
+ templateName: template.name,
602
+ appliedAt: new Date().toISOString(),
603
+ skills: template.skills?.map(s => s.directory || s.name) || [],
604
+ agents: template.agents?.map(a => a.fileName || a.name) || [],
605
+ commands: template.commands?.map(c => c.name) || [],
606
+ rules: template.rules?.map(r => r.fileName) || [],
607
+ mcpServers: template.mcpServers || []
608
+ };
609
+ const recordPath = path.join(targetDir, '.ctx-config.json');
610
+ fs.writeFileSync(recordPath, JSON.stringify(configRecord, null, 2), 'utf-8');
611
+
612
+ return {
613
+ success: true,
614
+ results,
615
+ template: template.name
616
+ };
617
+ }
618
+
619
+ /**
620
+ * 预览模板应用效果
621
+ * @param {string} targetDir - 目标项目目录
622
+ * @param {string} templateId - 模板 ID
623
+ */
624
+ function previewTemplateApplication(targetDir, templateId) {
625
+ const template = getTemplateById(templateId);
626
+ if (!template) {
627
+ throw new Error('模板不存在');
628
+ }
629
+
630
+ const preview = {
631
+ willCreate: [],
632
+ willOverwrite: [],
633
+ summary: {
634
+ claudeMd: false,
635
+ skills: 0,
636
+ agents: 0,
637
+ commands: 0,
638
+ rules: 0,
639
+ mcpServers: 0
640
+ }
641
+ };
642
+
643
+ // 检查 CLAUDE.md
644
+ if (template.claudeMd?.enabled && template.claudeMd?.content) {
645
+ const claudeMdPath = path.join(targetDir, 'CLAUDE.md');
646
+ if (fs.existsSync(claudeMdPath)) {
647
+ preview.willOverwrite.push('CLAUDE.md');
648
+ } else {
649
+ preview.willCreate.push('CLAUDE.md');
650
+ }
651
+ preview.summary.claudeMd = true;
652
+ }
653
+
654
+ // Skills 摘要
655
+ if (template.skills?.length > 0) {
656
+ preview.summary.skills = template.skills.length;
657
+ }
658
+
659
+ // 检查 Agents
660
+ if (template.agents?.length > 0) {
661
+ for (const agent of template.agents) {
662
+ const fileName = agent.fileName || agent.name.toLowerCase().replace(/\s+/g, '-');
663
+ const relativePath = `.claude/agents/${fileName}.md`;
664
+ const fullPath = path.join(targetDir, relativePath);
665
+ if (fs.existsSync(fullPath)) {
666
+ preview.willOverwrite.push(relativePath);
667
+ } else {
668
+ preview.willCreate.push(relativePath);
669
+ }
670
+ preview.summary.agents++;
671
+ }
672
+ }
673
+
674
+ // 检查 Commands
675
+ if (template.commands?.length > 0) {
676
+ for (const command of template.commands) {
677
+ const relativePath = command.namespace
678
+ ? `.claude/commands/${command.namespace}/${command.name}.md`
679
+ : `.claude/commands/${command.name}.md`;
680
+ const fullPath = path.join(targetDir, relativePath);
681
+ if (fs.existsSync(fullPath)) {
682
+ preview.willOverwrite.push(relativePath);
683
+ } else {
684
+ preview.willCreate.push(relativePath);
685
+ }
686
+ preview.summary.commands++;
687
+ }
688
+ }
689
+
690
+ // 检查 Rules
691
+ if (template.rules?.length > 0) {
692
+ for (const rule of template.rules) {
693
+ const relativePath = rule.directory
694
+ ? `.claude/rules/${rule.directory}/${rule.fileName}.md`
695
+ : `.claude/rules/${rule.fileName}.md`;
696
+ const fullPath = path.join(targetDir, relativePath);
697
+ if (fs.existsSync(fullPath)) {
698
+ preview.willOverwrite.push(relativePath);
699
+ } else {
700
+ preview.willCreate.push(relativePath);
701
+ }
702
+ preview.summary.rules++;
703
+ }
704
+ }
705
+
706
+ // 检查 MCP
707
+ if (template.mcpServers?.length > 0) {
708
+ const mcpPath = path.join(targetDir, '.mcp.json');
709
+ if (fs.existsSync(mcpPath)) {
710
+ preview.willOverwrite.push('.mcp.json');
711
+ } else {
712
+ preview.willCreate.push('.mcp.json');
713
+ }
714
+ preview.summary.mcpServers = template.mcpServers.length;
715
+ }
716
+
717
+ return preview;
718
+ }
719
+
720
+ module.exports = {
721
+ getAllTemplates,
722
+ getTemplateById,
723
+ createCustomTemplate,
724
+ updateCustomTemplate,
725
+ deleteCustomTemplate,
726
+ applyTemplate,
727
+ readCurrentConfig,
728
+ getAvailableConfigs,
729
+ applyTemplateToProject,
730
+ previewTemplateApplication,
731
+ BUILTIN_TEMPLATES
732
+ };