@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,515 @@
1
+ /**
2
+ * 配置同步服务
3
+ *
4
+ * 支持 skills, rules, agents, commands 的在工作区和全局之间同步
5
+ *
6
+ * 配置位置:
7
+ * - 全局: ~/.claude/
8
+ * - skills/
9
+ * - rules/
10
+ * - agents/
11
+ * - commands/
12
+ * - 工作区: <project>/.claude/
13
+ * - rules/
14
+ * - agents/
15
+ * - commands/
16
+ */
17
+
18
+ const fs = require('fs');
19
+ const path = require('path');
20
+ const os = require('os');
21
+
22
+ // 全局配置目录
23
+ const GLOBAL_CONFIG_DIR = path.join(os.homedir(), '.claude');
24
+
25
+ // 配置类型定义
26
+ const CONFIG_TYPES = {
27
+ skills: {
28
+ globalDir: 'skills',
29
+ projectDir: null, // skills 不支持项目级
30
+ isDirectory: true, // skills 是目录结构
31
+ markerFile: 'SKILL.md'
32
+ },
33
+ rules: {
34
+ globalDir: 'rules',
35
+ projectDir: 'rules',
36
+ isDirectory: false,
37
+ fileExtension: '.md'
38
+ },
39
+ agents: {
40
+ globalDir: 'agents',
41
+ projectDir: 'agents',
42
+ isDirectory: false,
43
+ fileExtension: '.md'
44
+ },
45
+ commands: {
46
+ globalDir: 'commands',
47
+ projectDir: 'commands',
48
+ isDirectory: false,
49
+ fileExtension: '.md'
50
+ }
51
+ };
52
+
53
+ /**
54
+ * 确保目录存在
55
+ */
56
+ function ensureDir(dirPath) {
57
+ if (!fs.existsSync(dirPath)) {
58
+ fs.mkdirSync(dirPath, { recursive: true });
59
+ }
60
+ }
61
+
62
+ /**
63
+ * 递归复制目录
64
+ */
65
+ function copyDirRecursive(src, dest) {
66
+ ensureDir(dest);
67
+ const entries = fs.readdirSync(src, { withFileTypes: true });
68
+
69
+ for (const entry of entries) {
70
+ const srcPath = path.join(src, entry.name);
71
+ const destPath = path.join(dest, entry.name);
72
+
73
+ if (entry.isDirectory()) {
74
+ copyDirRecursive(srcPath, destPath);
75
+ } else {
76
+ fs.copyFileSync(srcPath, destPath);
77
+ }
78
+ }
79
+ }
80
+
81
+ /**
82
+ * 配置同步服务类
83
+ */
84
+ class ConfigSyncService {
85
+ constructor() {
86
+ this.globalConfigDir = GLOBAL_CONFIG_DIR;
87
+ ensureDir(this.globalConfigDir);
88
+ }
89
+
90
+ /**
91
+ * 获取可用的配置项列表
92
+ * @param {string} source - 'global' 或 'workspace'
93
+ * @param {string} projectPath - 工作区项目路径(source 为 workspace 时必需)
94
+ * @returns {Object} 各类型的配置项列表
95
+ */
96
+ getAvailableConfigs(source, projectPath = null) {
97
+ const result = {
98
+ skills: [],
99
+ rules: [],
100
+ agents: [],
101
+ commands: []
102
+ };
103
+
104
+ for (const [type, config] of Object.entries(CONFIG_TYPES)) {
105
+ let dir;
106
+
107
+ if (source === 'global') {
108
+ dir = path.join(this.globalConfigDir, config.globalDir);
109
+ } else if (source === 'workspace' && projectPath) {
110
+ if (!config.projectDir) {
111
+ // skills 不支持项目级
112
+ continue;
113
+ }
114
+ dir = path.join(projectPath, '.claude', config.projectDir);
115
+ } else {
116
+ continue;
117
+ }
118
+
119
+ if (!fs.existsSync(dir)) {
120
+ continue;
121
+ }
122
+
123
+ if (config.isDirectory) {
124
+ // Skills: 扫描目录
125
+ result[type] = this._scanSkillsDir(dir);
126
+ } else {
127
+ // Rules/Agents/Commands: 扫描 md 文件
128
+ result[type] = this._scanMdFiles(dir, config.fileExtension);
129
+ }
130
+ }
131
+
132
+ return result;
133
+ }
134
+
135
+ /**
136
+ * 扫描 skills 目录
137
+ */
138
+ _scanSkillsDir(dir) {
139
+ const skills = [];
140
+
141
+ try {
142
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
143
+
144
+ for (const entry of entries) {
145
+ if (!entry.isDirectory() || entry.name.startsWith('.')) continue;
146
+
147
+ const skillPath = path.join(dir, entry.name);
148
+ const skillMdPath = path.join(skillPath, 'SKILL.md');
149
+
150
+ if (fs.existsSync(skillMdPath)) {
151
+ const content = fs.readFileSync(skillMdPath, 'utf-8');
152
+ const metadata = this._parseSkillMetadata(content);
153
+
154
+ // 获取文件列表
155
+ const files = this._getDirectoryFiles(skillPath);
156
+
157
+ skills.push({
158
+ name: metadata.name || entry.name,
159
+ directory: entry.name,
160
+ description: metadata.description || '',
161
+ files: files.length,
162
+ size: this._getDirSize(skillPath)
163
+ });
164
+ }
165
+ }
166
+ } catch (err) {
167
+ console.error('[ConfigSync] Scan skills error:', err.message);
168
+ }
169
+
170
+ return skills;
171
+ }
172
+
173
+ /**
174
+ * 扫描 md 文件(rules/agents/commands)
175
+ */
176
+ _scanMdFiles(dir, extension = '.md') {
177
+ const items = [];
178
+
179
+ const scan = (currentDir, relativePath = '') => {
180
+ try {
181
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true });
182
+
183
+ for (const entry of entries) {
184
+ if (entry.name.startsWith('.')) continue;
185
+
186
+ const fullPath = path.join(currentDir, entry.name);
187
+ const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
188
+
189
+ if (entry.isDirectory()) {
190
+ scan(fullPath, relPath);
191
+ } else if (entry.name.endsWith(extension)) {
192
+ const content = fs.readFileSync(fullPath, 'utf-8');
193
+ const metadata = this._parseFrontmatter(content);
194
+ const stats = fs.statSync(fullPath);
195
+
196
+ items.push({
197
+ name: metadata.name || path.basename(entry.name, extension),
198
+ path: relPath,
199
+ description: metadata.description || '',
200
+ size: stats.size
201
+ });
202
+ }
203
+ }
204
+ } catch (err) {
205
+ // 忽略读取错误
206
+ }
207
+ };
208
+
209
+ scan(dir);
210
+ return items;
211
+ }
212
+
213
+ /**
214
+ * 解析 SKILL.md 元数据
215
+ */
216
+ _parseSkillMetadata(content) {
217
+ const result = { name: null, description: null };
218
+
219
+ // 匹配 YAML frontmatter
220
+ const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
221
+ if (match) {
222
+ const yaml = match[1];
223
+ const nameMatch = yaml.match(/name:\s*["']?([^"'\n]+)["']?/);
224
+ const descMatch = yaml.match(/description:\s*["']?([^"'\n]+)["']?/);
225
+
226
+ if (nameMatch) result.name = nameMatch[1].trim();
227
+ if (descMatch) result.description = descMatch[1].trim();
228
+ }
229
+
230
+ return result;
231
+ }
232
+
233
+ /**
234
+ * 解析 frontmatter
235
+ */
236
+ _parseFrontmatter(content) {
237
+ const result = { name: null, description: null };
238
+
239
+ const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
240
+ if (match) {
241
+ const yaml = match[1];
242
+ const nameMatch = yaml.match(/name:\s*["']?([^"'\n]+)["']?/);
243
+ const descMatch = yaml.match(/description:\s*["']?([^"'\n]+)["']?/);
244
+
245
+ if (nameMatch) result.name = nameMatch[1].trim();
246
+ if (descMatch) result.description = descMatch[1].trim();
247
+ }
248
+
249
+ return result;
250
+ }
251
+
252
+ /**
253
+ * 获取目录下的文件列表
254
+ */
255
+ _getDirectoryFiles(dir) {
256
+ const files = [];
257
+
258
+ const scan = (currentDir, relativePath = '') => {
259
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true });
260
+
261
+ for (const entry of entries) {
262
+ const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
263
+
264
+ if (entry.isDirectory()) {
265
+ scan(path.join(currentDir, entry.name), relPath);
266
+ } else {
267
+ files.push(relPath);
268
+ }
269
+ }
270
+ };
271
+
272
+ scan(dir);
273
+ return files;
274
+ }
275
+
276
+ /**
277
+ * 获取目录大小
278
+ */
279
+ _getDirSize(dir) {
280
+ let size = 0;
281
+
282
+ const scan = (currentDir) => {
283
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true });
284
+
285
+ for (const entry of entries) {
286
+ const fullPath = path.join(currentDir, entry.name);
287
+
288
+ if (entry.isDirectory()) {
289
+ scan(fullPath);
290
+ } else {
291
+ size += fs.statSync(fullPath).size;
292
+ }
293
+ }
294
+ };
295
+
296
+ scan(dir);
297
+ return size;
298
+ }
299
+
300
+ /**
301
+ * 预览同步结果
302
+ * @param {Object} options
303
+ * @param {string} options.source - 'global' 或 'workspace'
304
+ * @param {string} options.target - 'global' 或 'workspace'
305
+ * @param {string[]} options.configTypes - 要同步的配置类型
306
+ * @param {string} options.projectPath - 工作区路径
307
+ * @param {Object} options.selectedItems - 选中的项目 { skills: [], rules: [], ... }
308
+ * @returns {Object} 预览结果
309
+ */
310
+ previewSync(options) {
311
+ const { source, target, configTypes = [], projectPath, selectedItems = {} } = options;
312
+
313
+ const preview = {
314
+ willCreate: [],
315
+ willOverwrite: [],
316
+ willSkip: [],
317
+ errors: []
318
+ };
319
+
320
+ // 验证参数
321
+ if (source === target) {
322
+ preview.errors.push('源和目标不能相同');
323
+ return preview;
324
+ }
325
+
326
+ if (target === 'workspace' && !projectPath) {
327
+ preview.errors.push('同步到工作区需要指定项目路径');
328
+ return preview;
329
+ }
330
+
331
+ for (const type of configTypes) {
332
+ const config = CONFIG_TYPES[type];
333
+ if (!config) continue;
334
+
335
+ // Skills 只支持全局
336
+ if (type === 'skills' && target === 'workspace') {
337
+ preview.errors.push('Skills 不支持同步到工作区级别');
338
+ continue;
339
+ }
340
+
341
+ const items = selectedItems[type] || [];
342
+
343
+ for (const item of items) {
344
+ const targetPath = this._getTargetPath(type, item, target, projectPath);
345
+
346
+ if (fs.existsSync(targetPath)) {
347
+ preview.willOverwrite.push({
348
+ type,
349
+ name: item.name || item.directory || item.path,
350
+ targetPath
351
+ });
352
+ } else {
353
+ preview.willCreate.push({
354
+ type,
355
+ name: item.name || item.directory || item.path,
356
+ targetPath
357
+ });
358
+ }
359
+ }
360
+ }
361
+
362
+ return preview;
363
+ }
364
+
365
+ /**
366
+ * 获取目标路径
367
+ */
368
+ _getTargetPath(type, item, target, projectPath) {
369
+ const config = CONFIG_TYPES[type];
370
+ let baseDir;
371
+
372
+ if (target === 'global') {
373
+ baseDir = path.join(this.globalConfigDir, config.globalDir);
374
+ } else {
375
+ baseDir = path.join(projectPath, '.claude', config.projectDir);
376
+ }
377
+
378
+ if (config.isDirectory) {
379
+ // Skills
380
+ return path.join(baseDir, item.directory);
381
+ } else {
382
+ // Rules/Agents/Commands
383
+ return path.join(baseDir, item.path);
384
+ }
385
+ }
386
+
387
+ /**
388
+ * 执行同步
389
+ * @param {Object} options
390
+ * @param {string} options.source - 'global' 或 'workspace'
391
+ * @param {string} options.target - 'global' 或 'workspace'
392
+ * @param {string[]} options.configTypes - 要同步的配置类型
393
+ * @param {string} options.projectPath - 工作区路径
394
+ * @param {Object} options.selectedItems - 选中的项目
395
+ * @param {boolean} options.overwrite - 是否覆盖已存在的
396
+ * @returns {Object} 同步结果
397
+ */
398
+ executeSync(options) {
399
+ const { source, target, configTypes = [], projectPath, selectedItems = {}, overwrite = false } = options;
400
+
401
+ const result = {
402
+ success: [],
403
+ failed: [],
404
+ skipped: []
405
+ };
406
+
407
+ for (const type of configTypes) {
408
+ const config = CONFIG_TYPES[type];
409
+ if (!config) continue;
410
+
411
+ // Skills 只支持全局
412
+ if (type === 'skills' && target === 'workspace') {
413
+ result.failed.push({
414
+ type,
415
+ name: 'skills',
416
+ error: 'Skills 不支持同步到工作区级别'
417
+ });
418
+ continue;
419
+ }
420
+
421
+ const items = selectedItems[type] || [];
422
+
423
+ for (const item of items) {
424
+ try {
425
+ const sourcePath = this._getSourcePath(type, item, source, projectPath);
426
+ const targetPath = this._getTargetPath(type, item, target, projectPath);
427
+
428
+ // 检查目标是否存在
429
+ if (fs.existsSync(targetPath) && !overwrite) {
430
+ result.skipped.push({
431
+ type,
432
+ name: item.name || item.directory || item.path,
433
+ reason: '已存在'
434
+ });
435
+ continue;
436
+ }
437
+
438
+ // 确保目标目录存在
439
+ ensureDir(path.dirname(targetPath));
440
+
441
+ // 执行复制
442
+ if (config.isDirectory) {
443
+ copyDirRecursive(sourcePath, targetPath);
444
+ } else {
445
+ fs.copyFileSync(sourcePath, targetPath);
446
+ }
447
+
448
+ result.success.push({
449
+ type,
450
+ name: item.name || item.directory || item.path
451
+ });
452
+ } catch (err) {
453
+ result.failed.push({
454
+ type,
455
+ name: item.name || item.directory || item.path,
456
+ error: err.message
457
+ });
458
+ }
459
+ }
460
+ }
461
+
462
+ return result;
463
+ }
464
+
465
+ /**
466
+ * 获取源路径
467
+ */
468
+ _getSourcePath(type, item, source, projectPath) {
469
+ const config = CONFIG_TYPES[type];
470
+ let baseDir;
471
+
472
+ if (source === 'global') {
473
+ baseDir = path.join(this.globalConfigDir, config.globalDir);
474
+ } else {
475
+ baseDir = path.join(projectPath, '.claude', config.projectDir);
476
+ }
477
+
478
+ if (config.isDirectory) {
479
+ // Skills
480
+ return path.join(baseDir, item.directory);
481
+ } else {
482
+ // Rules/Agents/Commands
483
+ return path.join(baseDir, item.path);
484
+ }
485
+ }
486
+
487
+ /**
488
+ * 获取同步统计信息
489
+ */
490
+ getStats(projectPath = null) {
491
+ const globalConfigs = this.getAvailableConfigs('global');
492
+ const workspaceConfigs = projectPath
493
+ ? this.getAvailableConfigs('workspace', projectPath)
494
+ : { skills: [], rules: [], agents: [], commands: [] };
495
+
496
+ return {
497
+ global: {
498
+ skills: globalConfigs.skills.length,
499
+ rules: globalConfigs.rules.length,
500
+ agents: globalConfigs.agents.length,
501
+ commands: globalConfigs.commands.length
502
+ },
503
+ workspace: {
504
+ rules: workspaceConfigs.rules.length,
505
+ agents: workspaceConfigs.agents.length,
506
+ commands: workspaceConfigs.commands.length
507
+ }
508
+ };
509
+ }
510
+ }
511
+
512
+ module.exports = {
513
+ ConfigSyncService,
514
+ CONFIG_TYPES
515
+ };
@@ -802,7 +802,8 @@ function generateRuleContent(rule) {
802
802
  * @param {string} targetDir - 目标项目目录
803
803
  * @param {string} templateId - 模板 ID
804
804
  * @param {object} options - 可选配置
805
- * @param {string} options.aiConfigType - 选择的 AI 配置类型: 'claude' | 'codex' | 'gemini'
805
+ * @param {string|string[]} options.aiConfigTypes - 选择的 AI 配置类型数组: ['claude', 'codex', 'gemini']
806
+ * @param {string} options.aiConfigType - (兼容旧版) 单个 AI 配置类型
806
807
  */
807
808
  function applyTemplateToProject(targetDir, templateId, options = {}) {
808
809
  const template = getTemplateById(templateId);
@@ -813,7 +814,7 @@ function applyTemplateToProject(targetDir, templateId, options = {}) {
813
814
  ensureDir(targetDir);
814
815
 
815
816
  const results = {
816
- aiConfig: { applied: false, path: null, type: null },
817
+ aiConfigs: [], // 改为数组存储多个 AI 配置结果
817
818
  skills: { applied: template.skills?.length || 0, items: template.skills?.map(s => s.directory || s.name) || [] },
818
819
  agents: { applied: 0, files: [] },
819
820
  commands: { applied: 0, files: [] },
@@ -822,27 +823,37 @@ function applyTemplateToProject(targetDir, templateId, options = {}) {
822
823
  };
823
824
 
824
825
  // 1. 写入 AI 配置文件(支持多 AI 类型选择)
825
- const aiConfigType = options.aiConfigType || 'claude';
826
+ // 兼容旧版单值参数
827
+ let aiConfigTypes = options.aiConfigTypes;
828
+ if (!aiConfigTypes) {
829
+ aiConfigTypes = options.aiConfigType ? [options.aiConfigType] : ['claude'];
830
+ }
831
+ if (!Array.isArray(aiConfigTypes)) {
832
+ aiConfigTypes = [aiConfigTypes];
833
+ }
834
+
826
835
  const aiConfigMap = {
827
836
  claude: { fileName: 'CLAUDE.md', name: 'Claude' },
828
- codex: { fileName: 'AGENT.md', name: 'Codex' },
837
+ codex: { fileName: 'AGENTS.md', name: 'Codex' },
829
838
  gemini: { fileName: 'GEMINI.md', name: 'Gemini' }
830
839
  };
831
840
 
832
- // 优先使用新的 aiConfigs 结构
833
- let aiConfig = null;
834
- if (template.aiConfigs && template.aiConfigs[aiConfigType]) {
835
- aiConfig = template.aiConfigs[aiConfigType];
836
- } else if (aiConfigType === 'claude' && template.claudeMd) {
837
- // 兼容旧的 claudeMd 字段
838
- aiConfig = template.claudeMd;
839
- }
841
+ // 遍历所有选中的 AI 配置类型
842
+ for (const aiConfigType of aiConfigTypes) {
843
+ let aiConfig = null;
844
+ if (template.aiConfigs && template.aiConfigs[aiConfigType]) {
845
+ aiConfig = template.aiConfigs[aiConfigType];
846
+ } else if (aiConfigType === 'claude' && template.claudeMd) {
847
+ // 兼容旧的 claudeMd 字段
848
+ aiConfig = template.claudeMd;
849
+ }
840
850
 
841
- if (aiConfig?.enabled && aiConfig?.content) {
842
- const configInfo = aiConfigMap[aiConfigType];
843
- const configPath = path.join(targetDir, configInfo.fileName);
844
- fs.writeFileSync(configPath, aiConfig.content, 'utf-8');
845
- results.aiConfig = { applied: true, path: configInfo.fileName, type: configInfo.name };
851
+ if (aiConfig?.enabled && aiConfig?.content) {
852
+ const configInfo = aiConfigMap[aiConfigType];
853
+ const configPath = path.join(targetDir, configInfo.fileName);
854
+ fs.writeFileSync(configPath, aiConfig.content, 'utf-8');
855
+ results.aiConfigs.push({ applied: true, path: configInfo.fileName, type: configInfo.name, key: aiConfigType });
856
+ }
846
857
  }
847
858
 
848
859
  // 2. 写入 Agents
@@ -932,8 +943,8 @@ function applyTemplateToProject(targetDir, templateId, options = {}) {
932
943
  templateId: template.id,
933
944
  templateName: template.name,
934
945
  appliedAt: new Date().toISOString(),
935
- aiConfigType: aiConfigType,
936
- aiConfigPath: results.aiConfig.path,
946
+ aiConfigTypes: aiConfigTypes,
947
+ aiConfigPaths: results.aiConfigs.map(c => c.path),
937
948
  skills: template.skills?.map(s => s.directory || s.name) || [],
938
949
  agents: template.agents?.map(a => a.fileName || a.name) || [],
939
950
  commands: template.commands?.map(c => c.name) || [],
@@ -955,7 +966,8 @@ function applyTemplateToProject(targetDir, templateId, options = {}) {
955
966
  * @param {string} targetDir - 目标项目目录
956
967
  * @param {string} templateId - 模板 ID
957
968
  * @param {object} options - 可选配置
958
- * @param {string} options.aiConfigType - 选择的 AI 配置类型: 'claude' | 'codex' | 'gemini'
969
+ * @param {string|string[]} options.aiConfigTypes - 选择的 AI 配置类型数组: ['claude', 'codex', 'gemini']
970
+ * @param {string} options.aiConfigType - (兼容旧版) 单个 AI 配置类型
959
971
  */
960
972
  function previewTemplateApplication(targetDir, templateId, options = {}) {
961
973
  const template = getTemplateById(templateId);
@@ -967,7 +979,7 @@ function previewTemplateApplication(targetDir, templateId, options = {}) {
967
979
  willCreate: [],
968
980
  willOverwrite: [],
969
981
  summary: {
970
- aiConfig: null,
982
+ aiConfigs: [], // 改为数组
971
983
  skills: 0,
972
984
  agents: 0,
973
985
  commands: 0,
@@ -976,30 +988,41 @@ function previewTemplateApplication(targetDir, templateId, options = {}) {
976
988
  }
977
989
  };
978
990
 
979
- // 检查 AI 配置文件
980
- const aiConfigType = options.aiConfigType || 'claude';
991
+ // 检查 AI 配置文件(支持多选)
992
+ // 兼容旧版单值参数
993
+ let aiConfigTypes = options.aiConfigTypes;
994
+ if (!aiConfigTypes) {
995
+ aiConfigTypes = options.aiConfigType ? [options.aiConfigType] : ['claude'];
996
+ }
997
+ if (!Array.isArray(aiConfigTypes)) {
998
+ aiConfigTypes = [aiConfigTypes];
999
+ }
1000
+
981
1001
  const aiConfigMap = {
982
1002
  claude: { fileName: 'CLAUDE.md', name: 'Claude' },
983
- codex: { fileName: 'AGENT.md', name: 'Codex' },
1003
+ codex: { fileName: 'AGENTS.md', name: 'Codex' },
984
1004
  gemini: { fileName: 'GEMINI.md', name: 'Gemini' }
985
1005
  };
986
1006
 
987
- let aiConfig = null;
988
- if (template.aiConfigs && template.aiConfigs[aiConfigType]) {
989
- aiConfig = template.aiConfigs[aiConfigType];
990
- } else if (aiConfigType === 'claude' && template.claudeMd) {
991
- aiConfig = template.claudeMd;
992
- }
1007
+ // 遍历所有选中的 AI 配置类型
1008
+ for (const aiConfigType of aiConfigTypes) {
1009
+ let aiConfig = null;
1010
+ if (template.aiConfigs && template.aiConfigs[aiConfigType]) {
1011
+ aiConfig = template.aiConfigs[aiConfigType];
1012
+ } else if (aiConfigType === 'claude' && template.claudeMd) {
1013
+ aiConfig = template.claudeMd;
1014
+ }
993
1015
 
994
- if (aiConfig?.enabled && aiConfig?.content) {
995
- const configInfo = aiConfigMap[aiConfigType];
996
- const configPath = path.join(targetDir, configInfo.fileName);
997
- if (fs.existsSync(configPath)) {
998
- preview.willOverwrite.push(configInfo.fileName);
999
- } else {
1000
- preview.willCreate.push(configInfo.fileName);
1016
+ if (aiConfig?.enabled && aiConfig?.content) {
1017
+ const configInfo = aiConfigMap[aiConfigType];
1018
+ const configPath = path.join(targetDir, configInfo.fileName);
1019
+ if (fs.existsSync(configPath)) {
1020
+ preview.willOverwrite.push(configInfo.fileName);
1021
+ } else {
1022
+ preview.willCreate.push(configInfo.fileName);
1023
+ }
1024
+ preview.summary.aiConfigs.push({ type: aiConfigType, fileName: configInfo.fileName, name: configInfo.name });
1001
1025
  }
1002
- preview.summary.aiConfig = { type: aiConfigType, fileName: configInfo.fileName, name: configInfo.name };
1003
1026
  }
1004
1027
 
1005
1028
  // Skills 摘要