agent-configs 1.0.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 (85) hide show
  1. package/README.md +223 -0
  2. package/agents/architect.md +211 -0
  3. package/agents/code-reviewer.md +104 -0
  4. package/agents/planner.md +119 -0
  5. package/agents/refactor-cleaner.md +306 -0
  6. package/agents/security-reviewer.md +545 -0
  7. package/agents/tdd-guide.md +280 -0
  8. package/bundles/bk-chat-bundle/README.md +48 -0
  9. package/bundles/bk-chat-bundle/manifest.json +10 -0
  10. package/bundles/continuous-learning/.claude/commands/evolve.md +190 -0
  11. package/bundles/continuous-learning/.claude/commands/instinct-status.md +64 -0
  12. package/bundles/continuous-learning/.claude/commands/learn.md +83 -0
  13. package/bundles/continuous-learning/.claude/hooks/learning-end.js +85 -0
  14. package/bundles/continuous-learning/.claude/hooks/observe.js +131 -0
  15. package/bundles/continuous-learning/.claude/lib/learning.js +559 -0
  16. package/bundles/continuous-learning/.claude/lib/utils.js +312 -0
  17. package/bundles/continuous-learning/.claude/skills/continuous-learning/SKILL.md +200 -0
  18. package/bundles/continuous-learning/.cursor/hooks/learning-end.js +102 -0
  19. package/bundles/continuous-learning/.cursor/rules/continuous-learning.mdc +34 -0
  20. package/bundles/continuous-learning/.cursor/skills/continuous-learning/SKILL.md +77 -0
  21. package/bundles/continuous-learning/README.md +159 -0
  22. package/bundles/continuous-learning/manifest.json +51 -0
  23. package/bundles/planning-bundle/README.md +34 -0
  24. package/bundles/planning-bundle/manifest.json +10 -0
  25. package/bundles/review-bundle/README.md +43 -0
  26. package/bundles/review-bundle/manifest.json +11 -0
  27. package/bundles/shared-memory/.claude/commands/list-sessions.md +124 -0
  28. package/bundles/shared-memory/.claude/commands/load-session.md +169 -0
  29. package/bundles/shared-memory/.claude/commands/save-session.md +137 -0
  30. package/bundles/shared-memory/.claude/hooks/memory-compact.js +43 -0
  31. package/bundles/shared-memory/.claude/hooks/memory-end.js +42 -0
  32. package/bundles/shared-memory/.claude/hooks/memory-start.js +59 -0
  33. package/bundles/shared-memory/.claude/lib/memory.js +416 -0
  34. package/bundles/shared-memory/.claude/lib/utils.js +209 -0
  35. package/bundles/shared-memory/.claude/skills/shared-memory/SKILL.md +183 -0
  36. package/bundles/shared-memory/.cursor/hooks/memory-start.js +42 -0
  37. package/bundles/shared-memory/.cursor/rules/shared-memory.mdc +37 -0
  38. package/bundles/shared-memory/.cursor/skills/shared-memory/SKILL.md +183 -0
  39. package/bundles/tdd-bundle/README.md +33 -0
  40. package/bundles/tdd-bundle/manifest.json +10 -0
  41. package/cli.js +978 -0
  42. package/commands/build-fix.md +29 -0
  43. package/commands/code-review.md +40 -0
  44. package/commands/e2e.md +363 -0
  45. package/commands/learn.md +114 -0
  46. package/commands/plan.md +113 -0
  47. package/commands/refactor-clean.md +28 -0
  48. package/commands/tdd.md +326 -0
  49. package/commands/test-coverage.md +27 -0
  50. package/commands/update-codemaps.md +17 -0
  51. package/commands/update-docs.md +31 -0
  52. package/configs.json +158 -0
  53. package/hooks/hooks.json +101 -0
  54. package/package.json +58 -0
  55. package/rules/agents.md +49 -0
  56. package/rules/coding-style.md +70 -0
  57. package/rules/git-workflow.md +45 -0
  58. package/rules/hooks.md +46 -0
  59. package/rules/patterns.md +55 -0
  60. package/rules/performance.md +47 -0
  61. package/rules/security.md +36 -0
  62. package/rules/testing.md +30 -0
  63. package/skills/ai-config-architect/SKILL.md +59 -0
  64. package/skills/ai-config-architect/references/agents.md +77 -0
  65. package/skills/ai-config-architect/references/commands.md +66 -0
  66. package/skills/ai-config-architect/references/hooks.md +70 -0
  67. package/skills/ai-config-architect/references/patterns.md +66 -0
  68. package/skills/ai-config-architect/references/platforms.md +82 -0
  69. package/skills/ai-config-architect/references/rules.md +66 -0
  70. package/skills/ai-config-architect/references/skills.md +67 -0
  71. package/skills/bk-chat-helper/SKILL.md +398 -0
  72. package/skills/bk-chat-helper/references/api-reference.md +606 -0
  73. package/skills/bk-chat-helper/references/examples.md +789 -0
  74. package/skills/bk-chat-helper/references/integration-guide.md +583 -0
  75. package/skills/bk-chat-x/SKILL.md +400 -0
  76. package/skills/bk-chat-x/references/components-api.md +340 -0
  77. package/skills/bk-chat-x/references/examples.md +386 -0
  78. package/skills/bk-chat-x/references/shortcuts-guide.md +375 -0
  79. package/skills/coding-standards/SKILL.md +523 -0
  80. package/skills/security-review/SKILL.md +497 -0
  81. package/skills/security-review/references/cloud-infrastructure-security.md +361 -0
  82. package/skills/strategic-compact/SKILL.md +66 -0
  83. package/skills/strategic-compact/scripts/suggest-compact.sh +52 -0
  84. package/skills/tdd-workflow/SKILL.md +412 -0
  85. package/skills/verification-loop/SKILL.md +128 -0
package/cli.js ADDED
@@ -0,0 +1,978 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * agent-configs CLI
5
+ *
6
+ * Interactive CLI tool for installing Claude Code and Cursor configurations.
7
+ *
8
+ * Usage:
9
+ * npx agent-configs # Interactive mode
10
+ * npx agent-configs list # Interactive category browser
11
+ * npx agent-configs install <name> [--agent <platform>] [--scope <scope>]
12
+ * npx agent-configs uninstall <name> [--agent <platform>]
13
+ */
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+ const os = require('os');
18
+ const readline = require('readline');
19
+ const { select, Separator } = require('@inquirer/prompts');
20
+
21
+ // Terminal colors
22
+ const colors = {
23
+ reset: '\x1b[0m',
24
+ bold: '\x1b[1m',
25
+ dim: '\x1b[2m',
26
+ green: '\x1b[32m',
27
+ yellow: '\x1b[33m',
28
+ blue: '\x1b[34m',
29
+ cyan: '\x1b[36m',
30
+ red: '\x1b[31m',
31
+ magenta: '\x1b[35m',
32
+ };
33
+
34
+ // Load configs.json
35
+ function loadConfigs() {
36
+ const configsPath = path.join(__dirname, 'configs.json');
37
+ return JSON.parse(fs.readFileSync(configsPath, 'utf8'));
38
+ }
39
+
40
+ // Platform paths
41
+ const PLATFORM_PATHS = {
42
+ claude: {
43
+ global: {
44
+ commands: path.join(os.homedir(), '.claude', 'commands'),
45
+ skills: path.join(os.homedir(), '.claude', 'skills'),
46
+ agents: path.join(os.homedir(), '.claude', 'agents'),
47
+ settings: path.join(os.homedir(), '.claude', 'settings.json'),
48
+ },
49
+ project: {
50
+ commands: '.claude/commands',
51
+ skills: '.claude/skills',
52
+ agents: '.claude/agents',
53
+ settings: '.claude/settings.json',
54
+ }
55
+ },
56
+ cursor: {
57
+ global: {
58
+ commands: path.join(os.homedir(), '.cursor', 'commands'),
59
+ skills: path.join(os.homedir(), '.cursor', 'skills'),
60
+ agents: path.join(os.homedir(), '.cursor', 'agents'),
61
+ },
62
+ project: {
63
+ commands: '.cursor/commands',
64
+ skills: '.cursor/skills',
65
+ agents: '.cursor/agents',
66
+ rules: '.cursor/rules',
67
+ hooks: '.cursor/hooks.json',
68
+ }
69
+ }
70
+ };
71
+
72
+ // Copy file or directory
73
+ function copyRecursive(src, dest) {
74
+ const stat = fs.statSync(src);
75
+
76
+ if (stat.isDirectory()) {
77
+ if (!fs.existsSync(dest)) {
78
+ fs.mkdirSync(dest, { recursive: true });
79
+ }
80
+ const entries = fs.readdirSync(src);
81
+ for (const entry of entries) {
82
+ copyRecursive(path.join(src, entry), path.join(dest, entry));
83
+ }
84
+ } else {
85
+ const destDir = path.dirname(dest);
86
+ if (!fs.existsSync(destDir)) {
87
+ fs.mkdirSync(destDir, { recursive: true });
88
+ }
89
+ fs.copyFileSync(src, dest);
90
+ }
91
+ }
92
+
93
+ // Merge JSON files (for hooks/settings)
94
+ function mergeJson(targetPath, sourceData) {
95
+ let existing = {};
96
+ if (fs.existsSync(targetPath)) {
97
+ try {
98
+ existing = JSON.parse(fs.readFileSync(targetPath, 'utf8'));
99
+ } catch (e) {
100
+ existing = {};
101
+ }
102
+ }
103
+
104
+ // Deep merge
105
+ function deepMerge(target, source) {
106
+ for (const key in source) {
107
+ if (Array.isArray(source[key]) && Array.isArray(target[key])) {
108
+ // Merge arrays, avoiding duplicates by description
109
+ const existingDescs = new Set(target[key].map(h => h.description || JSON.stringify(h)));
110
+ for (const item of source[key]) {
111
+ const desc = item.description || JSON.stringify(item);
112
+ if (!existingDescs.has(desc)) {
113
+ target[key].push(item);
114
+ }
115
+ }
116
+ } else if (typeof source[key] === 'object' && source[key] !== null && typeof target[key] === 'object' && target[key] !== null) {
117
+ deepMerge(target[key], source[key]);
118
+ } else {
119
+ target[key] = source[key];
120
+ }
121
+ }
122
+ return target;
123
+ }
124
+
125
+ const merged = deepMerge(existing, sourceData);
126
+ const destDir = path.dirname(targetPath);
127
+ if (!fs.existsSync(destDir)) {
128
+ fs.mkdirSync(destDir, { recursive: true });
129
+ }
130
+ fs.writeFileSync(targetPath, JSON.stringify(merged, null, 2));
131
+ }
132
+
133
+ // Delete file or directory
134
+ function deleteRecursive(targetPath) {
135
+ if (!fs.existsSync(targetPath)) return false;
136
+
137
+ const stat = fs.statSync(targetPath);
138
+ if (stat.isDirectory()) {
139
+ fs.rmSync(targetPath, { recursive: true, force: true });
140
+ } else {
141
+ fs.unlinkSync(targetPath);
142
+ }
143
+ return true;
144
+ }
145
+
146
+ // Find config item by name
147
+ function findConfig(configs, name) {
148
+ for (const category of configs.categories) {
149
+ for (const item of category.items) {
150
+ if (item.name === name) {
151
+ return { category, item };
152
+ }
153
+ }
154
+ }
155
+ return null;
156
+ }
157
+
158
+ // List command (static output)
159
+ function listConfigsStatic(options = {}) {
160
+ const configs = loadConfigs();
161
+ const { category: filterCategory } = options;
162
+
163
+ console.log(`\n${colors.bold}${colors.cyan}agent-configs${colors.reset} - Claude Code & Cursor 配置共享工具\n`);
164
+
165
+ for (const category of configs.categories) {
166
+ if (filterCategory && category.id !== filterCategory) continue;
167
+
168
+ console.log(`${colors.bold}${colors.blue}${category.name}${colors.reset} (${category.items.length})`);
169
+ console.log(`${colors.dim}${category.description}${colors.reset}`);
170
+
171
+ if (category.id !== 'bundles') {
172
+ console.log(`${colors.dim}平台: ${(category.platforms || ['claude', 'cursor']).join(', ')}${colors.reset}`);
173
+ }
174
+ console.log();
175
+
176
+ for (const item of category.items) {
177
+ const featured = item.featured ? `${colors.yellow}★${colors.reset} ` : ' ';
178
+ const tags = item.tags ? ` ${colors.dim}[${item.tags.join(', ')}]${colors.reset}` : '';
179
+ console.log(` ${featured}${colors.green}${item.name}${colors.reset} - ${item.description}${tags}`);
180
+ }
181
+ console.log();
182
+ }
183
+
184
+ console.log(`${colors.dim}使用 ${colors.cyan}npx agent-configs install <name>${colors.dim} 安装配置${colors.reset}`);
185
+ console.log(`${colors.dim}使用 ${colors.cyan}npx agent-configs list --category bundles${colors.dim} 查看特定类别${colors.reset}\n`);
186
+ }
187
+
188
+ // Custom select menu with Esc support - optimized for performance
189
+ async function customSelect({ message, choices, pageSize = 10 }) {
190
+ return new Promise((resolve) => {
191
+ let selectedIndex = 0;
192
+ const items = choices.filter(c => c.value !== undefined);
193
+ const allItems = choices;
194
+
195
+ // Pre-calculate selectable indices
196
+ const selectableIndices = [];
197
+ allItems.forEach((item, idx) => {
198
+ if (item.value !== undefined) {
199
+ selectableIndices.push(idx);
200
+ }
201
+ });
202
+
203
+ // Pre-build all line variants (selected and unselected)
204
+ const headerLine = `${colors.cyan}?${colors.reset} ${colors.bold}${message}${colors.reset}`;
205
+ const footerLine = `${colors.dim}↑↓ 导航 • ⏎ 确认 • Esc 返回${colors.reset}`;
206
+ const separatorLine = `${colors.dim}──────────────${colors.reset}`;
207
+
208
+ const normalLines = [];
209
+ const selectedLines = [];
210
+ const descriptions = [];
211
+
212
+ allItems.forEach((item, idx) => {
213
+ if (item.value === undefined) {
214
+ normalLines.push(separatorLine);
215
+ selectedLines.push(separatorLine);
216
+ descriptions.push('');
217
+ } else {
218
+ normalLines.push(` ${item.name}`);
219
+ selectedLines.push(`${colors.cyan}❯ ${item.name}${colors.reset}`);
220
+ descriptions.push(item.description ? `${colors.dim}${item.description}${colors.reset}` : '');
221
+ }
222
+ });
223
+
224
+ const totalLines = allItems.length + 3;
225
+
226
+ // Build complete output buffer
227
+ const buildOutput = () => {
228
+ const lines = [headerLine];
229
+ for (let i = 0; i < allItems.length; i++) {
230
+ lines.push(selectableIndices[selectedIndex] === i ? selectedLines[i] : normalLines[i]);
231
+ }
232
+ lines.push(descriptions[selectableIndices[selectedIndex]]);
233
+ lines.push(footerLine);
234
+ return lines.join('\n');
235
+ };
236
+
237
+ const render = (isFirst = false) => {
238
+ if (!isFirst) {
239
+ process.stdout.write(`\x1b[${totalLines}A\x1b[J`);
240
+ }
241
+ process.stdout.write(buildOutput() + '\n');
242
+ };
243
+
244
+ // Setup keyboard input - use raw data handler for faster Esc response
245
+ if (process.stdin.isTTY) {
246
+ process.stdin.setRawMode(true);
247
+ }
248
+ process.stdin.resume();
249
+
250
+ // Direct data handler - faster than readline keypress events for Esc
251
+ const dataHandler = (data) => {
252
+ const input = data.toString();
253
+
254
+ // Esc key (single byte 0x1b, not followed by [ for escape sequences)
255
+ if (input === '\x1b' || input === '\u001b') {
256
+ cleanup();
257
+ resolve('__escape__');
258
+ return;
259
+ }
260
+
261
+ // Arrow keys: Esc + [ + A/B/C/D
262
+ if (input === '\x1b[A' || input === '\u001b[A') { // Up
263
+ if (selectedIndex > 0) {
264
+ selectedIndex--;
265
+ render();
266
+ }
267
+ return;
268
+ }
269
+ if (input === '\x1b[B' || input === '\u001b[B') { // Down
270
+ if (selectedIndex < items.length - 1) {
271
+ selectedIndex++;
272
+ render();
273
+ }
274
+ return;
275
+ }
276
+
277
+ // Enter key
278
+ if (input === '\r' || input === '\n') {
279
+ cleanup();
280
+ resolve(items[selectedIndex].value);
281
+ return;
282
+ }
283
+
284
+ // k/j for vim-style navigation
285
+ if (input === 'k') {
286
+ if (selectedIndex > 0) {
287
+ selectedIndex--;
288
+ render();
289
+ }
290
+ return;
291
+ }
292
+ if (input === 'j') {
293
+ if (selectedIndex < items.length - 1) {
294
+ selectedIndex++;
295
+ render();
296
+ }
297
+ return;
298
+ }
299
+
300
+ // Ctrl+C
301
+ if (input === '\x03') {
302
+ cleanup();
303
+ process.exit(0);
304
+ }
305
+ };
306
+
307
+ const cleanup = () => {
308
+ process.stdin.removeListener('data', dataHandler);
309
+ if (process.stdin.isTTY) {
310
+ process.stdin.setRawMode(false);
311
+ }
312
+ process.stdin.pause();
313
+ process.stdout.write('\x1b[?25h');
314
+ };
315
+
316
+ process.stdin.on('data', dataHandler);
317
+ process.stdout.write('\x1b[?25l');
318
+ render(true);
319
+ });
320
+ }
321
+
322
+ // Interactive menu - Main category selection
323
+ async function interactiveMenu(options = {}) {
324
+ const configs = loadConfigs();
325
+
326
+ console.clear();
327
+ console.log(`\n${colors.bold}${colors.cyan}agent-configs${colors.reset} - Claude Code & Cursor 配置共享工具\n`);
328
+ console.log(`${colors.dim}使用 ↑↓ 选择,Enter 进入,Esc 退出${colors.reset}\n`);
329
+
330
+ while (true) {
331
+ try {
332
+ // Build category choices
333
+ const categoryChoices = configs.categories.map(cat => {
334
+ const featuredCount = cat.items.filter(i => i.featured).length;
335
+ const featuredMark = featuredCount > 0 ? ` ${colors.yellow}★${featuredCount}${colors.reset}` : '';
336
+ const platformInfo = cat.id !== 'bundles' && cat.platforms
337
+ ? ` ${colors.dim}(${cat.platforms.join('/')})${colors.reset}`
338
+ : '';
339
+
340
+ return {
341
+ name: `${colors.blue}${cat.name}${colors.reset} (${cat.items.length})${featuredMark}${platformInfo}`,
342
+ value: cat.id,
343
+ description: cat.description
344
+ };
345
+ });
346
+
347
+ categoryChoices.push(new Separator());
348
+ categoryChoices.push({
349
+ name: `${colors.dim}退出${colors.reset}`,
350
+ value: '__exit__'
351
+ });
352
+
353
+ const selectedCategory = await customSelect({
354
+ message: '选择类别',
355
+ choices: categoryChoices,
356
+ pageSize: 10
357
+ });
358
+
359
+ if (selectedCategory === '__exit__' || selectedCategory === '__escape__') {
360
+ console.log(`\n${colors.dim}再见!${colors.reset}\n`);
361
+ break;
362
+ }
363
+
364
+ // Enter category submenu
365
+ await categorySubMenu(configs, selectedCategory, options);
366
+
367
+ } catch (error) {
368
+ if (error.message?.includes('User force closed') || error.name === 'ExitPromptError') {
369
+ console.log(`\n${colors.dim}再见!${colors.reset}\n`);
370
+ break;
371
+ }
372
+ throw error;
373
+ }
374
+ }
375
+ }
376
+
377
+ // Interactive menu - Category items
378
+ async function categorySubMenu(configs, categoryId, options = {}) {
379
+ const category = configs.categories.find(c => c.id === categoryId);
380
+ if (!category) return;
381
+
382
+ while (true) {
383
+ try {
384
+ console.clear();
385
+ console.log(`\n${colors.bold}${colors.cyan}agent-configs${colors.reset} > ${colors.blue}${category.name}${colors.reset}\n`);
386
+ console.log(`${colors.dim}${category.description}${colors.reset}`);
387
+ if (category.id !== 'bundles' && category.platforms) {
388
+ console.log(`${colors.dim}平台: ${category.platforms.join(', ')}${colors.reset}`);
389
+ }
390
+ console.log(`${colors.dim}使用 ↑↓ 选择,Enter 安装,Esc 返回${colors.reset}\n`);
391
+
392
+ // Build item choices
393
+ const itemChoices = category.items.map(item => {
394
+ const featured = item.featured ? `${colors.yellow}★${colors.reset} ` : ' ';
395
+ const tags = item.tags ? ` ${colors.dim}[${item.tags.join(', ')}]${colors.reset}` : '';
396
+
397
+ return {
398
+ name: `${featured}${colors.green}${item.name}${colors.reset}${tags}`,
399
+ value: item.name,
400
+ description: item.description
401
+ };
402
+ });
403
+
404
+ itemChoices.push(new Separator());
405
+ itemChoices.push({
406
+ name: `${colors.dim}← 返回${colors.reset}`,
407
+ value: '__back__'
408
+ });
409
+
410
+ const selectedItem = await customSelect({
411
+ message: `选择配置`,
412
+ choices: itemChoices,
413
+ pageSize: 15
414
+ });
415
+
416
+ if (selectedItem === '__back__' || selectedItem === '__escape__') {
417
+ console.clear();
418
+ console.log(`\n${colors.bold}${colors.cyan}agent-configs${colors.reset} - Claude Code & Cursor 配置共享工具\n`);
419
+ console.log(`${colors.dim}使用 ↑↓ 选择,Enter 进入,Esc 退出${colors.reset}\n`);
420
+ return;
421
+ }
422
+
423
+ // Show install options
424
+ await installSubMenu(selectedItem, category, options);
425
+
426
+ } catch (error) {
427
+ if (error.message?.includes('User force closed') || error.name === 'ExitPromptError') {
428
+ console.clear();
429
+ console.log(`\n${colors.bold}${colors.cyan}agent-configs${colors.reset} - Claude Code & Cursor 配置共享工具\n`);
430
+ console.log(`${colors.dim}使用 ↑↓ 选择,Enter 进入,Esc 退出${colors.reset}\n`);
431
+ return;
432
+ }
433
+ throw error;
434
+ }
435
+ }
436
+ }
437
+
438
+ // Interactive menu - Install options
439
+ async function installSubMenu(itemName, category, options = {}) {
440
+ const item = category.items.find(i => i.name === itemName);
441
+ if (!item) return;
442
+
443
+ try {
444
+ console.clear();
445
+ console.log(`\n${colors.bold}${colors.cyan}agent-configs${colors.reset} > ${colors.blue}${category.name}${colors.reset} > ${colors.green}${item.name}${colors.reset}\n`);
446
+ console.log(`${colors.dim}${item.description}${colors.reset}`);
447
+ if (item.tags) {
448
+ console.log(`${colors.dim}标签: ${item.tags.join(', ')}${colors.reset}`);
449
+ }
450
+ if (item.includes) {
451
+ console.log(`${colors.dim}包含: ${item.includes.join(', ')}${colors.reset}`);
452
+ }
453
+ console.log();
454
+
455
+ // Build action choices based on category
456
+ const actionChoices = [];
457
+
458
+ // Determine available platforms
459
+ const platforms = category.platforms || ['claude', 'cursor'];
460
+
461
+ if (platforms.includes('cursor')) {
462
+ actionChoices.push({
463
+ name: `${colors.green}安装到 Cursor${colors.reset} ${colors.dim}(项目级别)${colors.reset}`,
464
+ value: 'install_cursor_project'
465
+ });
466
+ actionChoices.push({
467
+ name: `${colors.cyan}安装到 Cursor${colors.reset} ${colors.dim}(全局)${colors.reset}`,
468
+ value: 'install_cursor_global'
469
+ });
470
+ }
471
+
472
+ if (platforms.includes('claude')) {
473
+ actionChoices.push({
474
+ name: `${colors.green}安装到 Claude Code${colors.reset} ${colors.dim}(项目级别)${colors.reset}`,
475
+ value: 'install_claude_project'
476
+ });
477
+ actionChoices.push({
478
+ name: `${colors.cyan}安装到 Claude Code${colors.reset} ${colors.dim}(全局)${colors.reset}`,
479
+ value: 'install_claude_global'
480
+ });
481
+ }
482
+
483
+ if (platforms.length === 2) {
484
+ actionChoices.push(new Separator());
485
+ actionChoices.push({
486
+ name: `${colors.magenta}安装到所有平台${colors.reset} ${colors.dim}(项目级别)${colors.reset}`,
487
+ value: 'install_all_project'
488
+ });
489
+ }
490
+
491
+ actionChoices.push(new Separator());
492
+ actionChoices.push({
493
+ name: `${colors.dim}← 返回${colors.reset}`,
494
+ value: '__back__'
495
+ });
496
+
497
+ const action = await customSelect({
498
+ message: '选择操作',
499
+ choices: actionChoices,
500
+ pageSize: 10
501
+ });
502
+
503
+ if (action === '__back__' || action === '__escape__') {
504
+ return;
505
+ }
506
+
507
+ // Parse action
508
+ const [, platform, scope] = action.split('_');
509
+
510
+ console.log();
511
+ installConfig(item.name, {
512
+ agent: platform,
513
+ scope: scope,
514
+ ...options
515
+ });
516
+
517
+ // Wait for user to see result
518
+ console.log(`${colors.dim}按任意键继续...${colors.reset}`);
519
+ await waitForKey();
520
+
521
+ } catch (error) {
522
+ if (error.message?.includes('User force closed') || error.name === 'ExitPromptError') {
523
+ return;
524
+ }
525
+ throw error;
526
+ }
527
+ }
528
+
529
+ // Wait for any key press
530
+ function waitForKey() {
531
+ return new Promise(resolve => {
532
+ if (process.stdin.isTTY) {
533
+ process.stdin.setRawMode(true);
534
+ process.stdin.resume();
535
+ process.stdin.once('data', () => {
536
+ process.stdin.setRawMode(false);
537
+ process.stdin.pause();
538
+ resolve();
539
+ });
540
+ } else {
541
+ resolve();
542
+ }
543
+ });
544
+ }
545
+
546
+ // Install command
547
+ function installConfig(name, options = {}) {
548
+ const configs = loadConfigs();
549
+ const platform = options.agent || configs.defaultPlatform || 'cursor';
550
+ const scope = options.scope || configs.defaultScope || 'project';
551
+ const dryRun = options.dryRun || false;
552
+
553
+ // Handle 'all' platform
554
+ const platforms = platform === 'all' ? ['claude', 'cursor'] : [platform];
555
+
556
+ // Find config
557
+ const found = findConfig(configs, name);
558
+ if (!found) {
559
+ console.error(`${colors.red}Error: Config '${name}' not found${colors.reset}`);
560
+ console.log(`${colors.dim}Use 'npx agent-configs list' to see available configs${colors.reset}`);
561
+ process.exit(1);
562
+ }
563
+
564
+ const { category, item } = found;
565
+
566
+ console.log(`\n${colors.bold}Installing ${colors.green}${item.name}${colors.reset}${colors.bold}...${colors.reset}\n`);
567
+
568
+ // Handle bundles
569
+ if (category.id === 'bundles') {
570
+ if (item.hasFiles) {
571
+ // File-based bundle (like shared-memory)
572
+ installFileBundle(item, platforms, scope, dryRun);
573
+ } else if (item.includes) {
574
+ // Reference bundle
575
+ installReferenceBundle(item, configs, platforms, scope, dryRun);
576
+ }
577
+ return;
578
+ }
579
+
580
+ // Handle hooks (special case)
581
+ if (category.id === 'hooks') {
582
+ installHooks(item, platforms, scope, dryRun);
583
+ return;
584
+ }
585
+
586
+ // Regular config installation
587
+ for (const p of platforms) {
588
+ if (!category.platforms || !category.platforms.includes(p)) {
589
+ console.log(`${colors.yellow}⚠ ${item.name} not supported on ${p}, skipping${colors.reset}`);
590
+ continue;
591
+ }
592
+
593
+ const targetBase = PLATFORM_PATHS[p][scope];
594
+ if (!targetBase) {
595
+ console.log(`${colors.yellow}⚠ ${scope} scope not supported on ${p}, skipping${colors.reset}`);
596
+ continue;
597
+ }
598
+
599
+ const categoryPath = targetBase[category.id] || targetBase[category.id.replace(/s$/, '')];
600
+ if (!categoryPath) {
601
+ console.log(`${colors.yellow}⚠ ${category.id} not supported on ${p}, skipping${colors.reset}`);
602
+ continue;
603
+ }
604
+
605
+ const sourcePath = path.join(__dirname, item.path);
606
+ const isDir = fs.statSync(sourcePath).isDirectory();
607
+ const targetPath = isDir
608
+ ? path.join(categoryPath, item.name)
609
+ : path.join(categoryPath, path.basename(item.path));
610
+
611
+ if (dryRun) {
612
+ console.log(`${colors.dim}[dry-run]${colors.reset} Would copy ${sourcePath} -> ${targetPath}`);
613
+ } else {
614
+ copyRecursive(sourcePath, targetPath);
615
+ console.log(`${colors.green}✓${colors.reset} Installed to ${targetPath}`);
616
+ }
617
+ }
618
+
619
+ console.log();
620
+ }
621
+
622
+ // Install file-based bundle
623
+ function installFileBundle(item, platforms, scope, dryRun) {
624
+ const bundlePath = path.join(__dirname, item.path);
625
+
626
+ for (const p of platforms) {
627
+ const platformDir = path.join(bundlePath, `.${p}`);
628
+ if (!fs.existsSync(platformDir)) {
629
+ console.log(`${colors.yellow}⚠ No ${p} config in ${item.name} bundle${colors.reset}`);
630
+ continue;
631
+ }
632
+
633
+ const entries = fs.readdirSync(platformDir);
634
+ for (const entry of entries) {
635
+ const sourcePath = path.join(platformDir, entry);
636
+ let targetPath;
637
+
638
+ if (scope === 'global') {
639
+ targetPath = path.join(os.homedir(), `.${p}`, entry);
640
+ } else {
641
+ targetPath = path.join(`.${p}`, entry);
642
+ }
643
+
644
+ if (dryRun) {
645
+ console.log(`${colors.dim}[dry-run]${colors.reset} Would copy ${sourcePath} -> ${targetPath}`);
646
+ } else {
647
+ copyRecursive(sourcePath, targetPath);
648
+ console.log(`${colors.green}✓${colors.reset} Installed ${entry} to ${targetPath}`);
649
+ }
650
+ }
651
+ }
652
+
653
+ console.log(`\n${colors.green}✓${colors.reset} Bundle ${colors.bold}${item.name}${colors.reset} installed successfully\n`);
654
+ }
655
+
656
+ // Install reference bundle
657
+ function installReferenceBundle(item, configs, platforms, scope, dryRun) {
658
+ console.log(`${colors.dim}Bundle includes: ${item.includes.join(', ')}${colors.reset}\n`);
659
+
660
+ for (const ref of item.includes) {
661
+ const [categoryId, configName] = ref.split('/');
662
+
663
+ // Find the config
664
+ const category = configs.categories.find(c => c.id === categoryId);
665
+ if (!category) {
666
+ console.log(`${colors.yellow}⚠ Category '${categoryId}' not found${colors.reset}`);
667
+ continue;
668
+ }
669
+
670
+ const configItem = category.items.find(i => i.name === configName);
671
+ if (!configItem) {
672
+ console.log(`${colors.yellow}⚠ Config '${configName}' not found in ${categoryId}${colors.reset}`);
673
+ continue;
674
+ }
675
+
676
+ // Install the referenced config
677
+ for (const p of platforms) {
678
+ if (category.platforms && !category.platforms.includes(p)) {
679
+ continue;
680
+ }
681
+
682
+ const targetBase = PLATFORM_PATHS[p][scope];
683
+ if (!targetBase) continue;
684
+
685
+ const categoryPath = targetBase[categoryId] || targetBase[categoryId.replace(/s$/, '')];
686
+ if (!categoryPath) continue;
687
+
688
+ const sourcePath = path.join(__dirname, configItem.path);
689
+ const isDir = fs.existsSync(sourcePath) && fs.statSync(sourcePath).isDirectory();
690
+ const targetPath = isDir
691
+ ? path.join(categoryPath, configItem.name)
692
+ : path.join(categoryPath, path.basename(configItem.path));
693
+
694
+ if (dryRun) {
695
+ console.log(`${colors.dim}[dry-run]${colors.reset} Would copy ${sourcePath} -> ${targetPath}`);
696
+ } else {
697
+ copyRecursive(sourcePath, targetPath);
698
+ console.log(`${colors.green}✓${colors.reset} Installed ${configItem.name} to ${targetPath}`);
699
+ }
700
+ }
701
+ }
702
+
703
+ console.log(`\n${colors.green}✓${colors.reset} Bundle ${colors.bold}${item.name}${colors.reset} installed successfully\n`);
704
+ }
705
+
706
+ // Install hooks
707
+ function installHooks(item, platforms, scope, dryRun) {
708
+ const sourceFile = path.join(__dirname, 'hooks/hooks.json');
709
+ const sourceData = JSON.parse(fs.readFileSync(sourceFile, 'utf8'));
710
+
711
+ for (const p of platforms) {
712
+ let targetPath;
713
+ if (p === 'claude') {
714
+ targetPath = scope === 'global'
715
+ ? PLATFORM_PATHS.claude.global.settings
716
+ : PLATFORM_PATHS.claude.project.settings;
717
+ } else {
718
+ targetPath = scope === 'global'
719
+ ? path.join(os.homedir(), '.cursor', 'hooks.json')
720
+ : PLATFORM_PATHS.cursor.project.hooks;
721
+ }
722
+
723
+ if (dryRun) {
724
+ console.log(`${colors.dim}[dry-run]${colors.reset} Would merge hooks into ${targetPath}`);
725
+ } else {
726
+ mergeJson(targetPath, sourceData);
727
+ console.log(`${colors.green}✓${colors.reset} Merged hooks into ${targetPath}`);
728
+ }
729
+ }
730
+
731
+ console.log();
732
+ }
733
+
734
+ // Uninstall command
735
+ function uninstallConfig(name, options = {}) {
736
+ const configs = loadConfigs();
737
+ const platform = options.agent || configs.defaultPlatform || 'cursor';
738
+ const scope = options.scope || configs.defaultScope || 'project';
739
+
740
+ const platforms = platform === 'all' ? ['claude', 'cursor'] : [platform];
741
+
742
+ const found = findConfig(configs, name);
743
+ if (!found) {
744
+ console.error(`${colors.red}Error: Config '${name}' not found${colors.reset}`);
745
+ process.exit(1);
746
+ }
747
+
748
+ const { category, item } = found;
749
+
750
+ console.log(`\n${colors.bold}Uninstalling ${colors.red}${item.name}${colors.reset}${colors.bold}...${colors.reset}\n`);
751
+
752
+ // Handle bundles with includes
753
+ if (category.id === 'bundles' && item.includes) {
754
+ for (const ref of item.includes) {
755
+ const [categoryId, configName] = ref.split('/');
756
+ const cat = configs.categories.find(c => c.id === categoryId);
757
+ if (!cat) continue;
758
+
759
+ const configItem = cat.items.find(i => i.name === configName);
760
+ if (!configItem) continue;
761
+
762
+ for (const p of platforms) {
763
+ const targetBase = PLATFORM_PATHS[p][scope];
764
+ if (!targetBase) continue;
765
+
766
+ const categoryPath = targetBase[categoryId];
767
+ if (!categoryPath) continue;
768
+
769
+ const isDir = configItem.path.endsWith('/');
770
+ const targetPath = isDir
771
+ ? path.join(categoryPath, configItem.name)
772
+ : path.join(categoryPath, path.basename(configItem.path));
773
+
774
+ if (deleteRecursive(targetPath)) {
775
+ console.log(`${colors.red}✗${colors.reset} Removed ${targetPath}`);
776
+ }
777
+ }
778
+ }
779
+ console.log();
780
+ return;
781
+ }
782
+
783
+ // Regular uninstall
784
+ for (const p of platforms) {
785
+ const targetBase = PLATFORM_PATHS[p][scope];
786
+ if (!targetBase) continue;
787
+
788
+ const categoryPath = targetBase[category.id];
789
+ if (!categoryPath) continue;
790
+
791
+ const isDir = item.path.endsWith('/');
792
+ const targetPath = isDir
793
+ ? path.join(categoryPath, item.name)
794
+ : path.join(categoryPath, path.basename(item.path));
795
+
796
+ if (deleteRecursive(targetPath)) {
797
+ console.log(`${colors.red}✗${colors.reset} Removed ${targetPath}`);
798
+ } else {
799
+ console.log(`${colors.dim}Not found: ${targetPath}${colors.reset}`);
800
+ }
801
+ }
802
+
803
+ console.log();
804
+ }
805
+
806
+ // Show help
807
+ function showHelp() {
808
+ console.log(`
809
+ ${colors.bold}${colors.cyan}agent-configs${colors.reset} - Claude Code & Cursor 配置共享工具
810
+
811
+ ${colors.bold}Usage:${colors.reset}
812
+ npx agent-configs ${colors.dim}# 交互式菜单${colors.reset}
813
+ npx agent-configs <command> [options]
814
+
815
+ ${colors.bold}Commands:${colors.reset}
816
+ ${colors.green}(无)${colors.reset} 进入交互式菜单
817
+ ${colors.green}list${colors.reset} 交互式浏览配置
818
+ ${colors.green}list --static${colors.reset} 静态列表输出
819
+ ${colors.green}install${colors.reset} <name> 安装指定配置
820
+ ${colors.green}uninstall${colors.reset} <name> 卸载配置
821
+
822
+ ${colors.bold}Options:${colors.reset}
823
+ --static 静态输出模式(不进入交互)
824
+ --category <cat> 过滤类别: commands, skills, rules, agents, bundles
825
+ --agent <platform> 目标平台: cursor (默认), claude, all
826
+ --scope <scope> 安装级别: project (默认), global
827
+ --dry-run 预览模式,不实际执行
828
+ --preset <name> 预设方案: minimal, recommended, full
829
+
830
+ ${colors.bold}Interactive Mode:${colors.reset}
831
+ ↑↓ 上下选择
832
+ Enter 进入子菜单 / 安装
833
+ Esc 返回上一层 / 退出
834
+
835
+ ${colors.bold}Examples:${colors.reset}
836
+ npx agent-configs ${colors.dim}# 交互式选择${colors.reset}
837
+ npx agent-configs list --static ${colors.dim}# 静态列表${colors.reset}
838
+ npx agent-configs install plan ${colors.dim}# 直接安装${colors.reset}
839
+ npx agent-configs install --preset recommended
840
+
841
+ ${colors.bold}Bundles (推荐):${colors.reset}
842
+ ${colors.green}tdd-bundle${colors.reset} TDD 完整工作流
843
+ ${colors.green}review-bundle${colors.reset} 代码审查全套
844
+ ${colors.green}planning-bundle${colors.reset} 规划和架构设计
845
+ ${colors.green}bk-chat-bundle${colors.reset} 蓝鲸 AI Chat 开发
846
+ ${colors.green}shared-memory${colors.reset} 跨会话记忆系统
847
+ `);
848
+ }
849
+
850
+ // Install preset
851
+ function installPreset(presetName, options = {}) {
852
+ const configs = loadConfigs();
853
+ const preset = configs.presets[presetName];
854
+
855
+ if (!preset) {
856
+ console.error(`${colors.red}Error: Preset '${presetName}' not found${colors.reset}`);
857
+ console.log(`${colors.dim}Available presets: ${Object.keys(configs.presets).join(', ')}${colors.reset}`);
858
+ process.exit(1);
859
+ }
860
+
861
+ console.log(`\n${colors.bold}Installing preset ${colors.cyan}${presetName}${colors.reset}${colors.bold}...${colors.reset}\n`);
862
+
863
+ for (const pattern of preset) {
864
+ if (pattern === '*') {
865
+ // Install everything
866
+ for (const category of configs.categories) {
867
+ for (const item of category.items) {
868
+ installConfig(item.name, options);
869
+ }
870
+ }
871
+ } else if (pattern.endsWith('/*')) {
872
+ // Install all in category
873
+ const categoryId = pattern.replace('/*', '');
874
+ const category = configs.categories.find(c => c.id === categoryId);
875
+ if (category) {
876
+ for (const item of category.items) {
877
+ installConfig(item.name, options);
878
+ }
879
+ }
880
+ } else {
881
+ // Install specific config
882
+ const configName = pattern.split('/').pop();
883
+ installConfig(configName, options);
884
+ }
885
+ }
886
+ }
887
+
888
+ // Parse arguments
889
+ function parseArgs(args) {
890
+ const result = { _: [], options: {} };
891
+
892
+ for (let i = 0; i < args.length; i++) {
893
+ const arg = args[i];
894
+ if (arg.startsWith('--')) {
895
+ const key = arg.slice(2);
896
+ if (key === 'dry-run') {
897
+ result.options.dryRun = true;
898
+ } else if (i + 1 < args.length && !args[i + 1].startsWith('-')) {
899
+ result.options[key] = args[++i];
900
+ } else {
901
+ result.options[key] = true;
902
+ }
903
+ } else if (arg.startsWith('-') && arg.length === 2) {
904
+ // Short flags like -h
905
+ const key = arg.slice(1);
906
+ result.options[key] = true;
907
+ } else {
908
+ result._.push(arg);
909
+ }
910
+ }
911
+
912
+ return result;
913
+ }
914
+
915
+ // Main entry
916
+ async function main() {
917
+ const args = parseArgs(process.argv.slice(2));
918
+ const command = args._[0];
919
+ const name = args._[1];
920
+
921
+ // Handle --help and -h flags
922
+ if (args.options.help || args.options.h) {
923
+ showHelp();
924
+ return;
925
+ }
926
+
927
+ switch (command) {
928
+ case 'list':
929
+ case 'ls':
930
+ if (args.options.static) {
931
+ // Static output mode
932
+ listConfigsStatic({ category: args.options.category });
933
+ } else if (args.options.category) {
934
+ // Filter by category but still static
935
+ listConfigsStatic({ category: args.options.category });
936
+ } else {
937
+ // Interactive mode
938
+ await interactiveMenu(args.options);
939
+ }
940
+ break;
941
+
942
+ case 'install':
943
+ case 'i':
944
+ if (args.options.preset) {
945
+ installPreset(args.options.preset, args.options);
946
+ } else if (name) {
947
+ installConfig(name, args.options);
948
+ } else {
949
+ // No name specified, enter interactive mode
950
+ await interactiveMenu(args.options);
951
+ }
952
+ break;
953
+
954
+ case 'uninstall':
955
+ case 'rm':
956
+ if (name) {
957
+ uninstallConfig(name, args.options);
958
+ } else {
959
+ console.error(`${colors.red}Error: Please specify a config name${colors.reset}`);
960
+ process.exit(1);
961
+ }
962
+ break;
963
+
964
+ case 'help':
965
+ showHelp();
966
+ break;
967
+
968
+ default:
969
+ // No command, enter interactive mode
970
+ await interactiveMenu(args.options);
971
+ break;
972
+ }
973
+ }
974
+
975
+ main().catch(err => {
976
+ console.error(`${colors.red}Error: ${err.message}${colors.reset}`);
977
+ process.exit(1);
978
+ });