agentic-team-templates 0.4.2 → 0.6.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.
- package/README.md +69 -5
- package/bin/cli.js +4 -1
- package/package.json +8 -3
- package/src/index.js +776 -134
- package/src/index.test.js +947 -0
- package/templates/testing/.cursorrules/advanced-techniques.md +596 -0
- package/templates/testing/.cursorrules/ci-cd-integration.md +603 -0
- package/templates/testing/.cursorrules/overview.md +163 -0
- package/templates/testing/.cursorrules/performance-testing.md +536 -0
- package/templates/testing/.cursorrules/quality-metrics.md +456 -0
- package/templates/testing/.cursorrules/reliability.md +557 -0
- package/templates/testing/.cursorrules/tdd-methodology.md +294 -0
- package/templates/testing/.cursorrules/test-data.md +565 -0
- package/templates/testing/.cursorrules/test-design.md +511 -0
- package/templates/testing/.cursorrules/test-types.md +398 -0
- package/templates/testing/CLAUDE.md +1134 -0
package/src/index.js
CHANGED
|
@@ -50,6 +50,10 @@ const SHARED_RULES = [
|
|
|
50
50
|
'security-fundamentals.md'
|
|
51
51
|
];
|
|
52
52
|
|
|
53
|
+
// Supported IDEs/tools
|
|
54
|
+
const SUPPORTED_IDES = ['cursor', 'claude', 'codex'];
|
|
55
|
+
const DEFAULT_IDES = ['cursor', 'claude', 'codex']; // Default: install for all IDEs
|
|
56
|
+
|
|
53
57
|
// Colors
|
|
54
58
|
const colors = {
|
|
55
59
|
red: (s) => `\x1b[31m${s}\x1b[0m`,
|
|
@@ -69,21 +73,44 @@ function printBanner() {
|
|
|
69
73
|
|
|
70
74
|
function printHelp() {
|
|
71
75
|
console.log(`${colors.yellow('Usage:')}
|
|
72
|
-
npx cursor-templates <templates...>
|
|
76
|
+
npx cursor-templates <templates...> [options]
|
|
77
|
+
npx cursor-templates --remove <templates...> [options]
|
|
78
|
+
npx cursor-templates --reset [options]
|
|
73
79
|
|
|
74
80
|
${colors.yellow('Options:')}
|
|
81
|
+
--ide=<name> Install for specific IDE (cursor, claude, codex)
|
|
82
|
+
Can be specified multiple times: --ide=cursor --ide=claude
|
|
83
|
+
Default: all (cursor, claude, codex)
|
|
75
84
|
--list, -l List available templates
|
|
76
85
|
--help, -h Show this help message
|
|
77
|
-
--dry-run Show what would be
|
|
78
|
-
--force, -f Overwrite
|
|
86
|
+
--dry-run Show what would be changed
|
|
87
|
+
--force, -f Overwrite/remove even if files were modified
|
|
88
|
+
--yes, -y Skip confirmation prompt (for --remove and --reset)
|
|
89
|
+
|
|
90
|
+
${colors.yellow('Removal Options:')}
|
|
91
|
+
--remove Remove specified templates (keeps shared rules and other templates)
|
|
92
|
+
--reset Remove ALL installed content (shared rules, templates, generated files)
|
|
93
|
+
|
|
94
|
+
${colors.yellow('IDE Targets:')}
|
|
95
|
+
cursor .cursorrules/ directory (Cursor IDE)
|
|
96
|
+
claude CLAUDE.md file (Claude Code, Cursor with Claude)
|
|
97
|
+
codex .github/copilot-instructions.md (GitHub Copilot)
|
|
79
98
|
|
|
80
99
|
${colors.yellow('Examples:')}
|
|
81
100
|
npx cursor-templates web-frontend
|
|
82
|
-
npx cursor-templates web-frontend
|
|
83
|
-
npx cursor-templates
|
|
84
|
-
npx cursor-templates
|
|
101
|
+
npx cursor-templates web-frontend --ide=cursor
|
|
102
|
+
npx cursor-templates web-frontend --ide=claude --ide=codex
|
|
103
|
+
npx cursor-templates fullstack --ide=codex
|
|
85
104
|
npx cursor-templates web-backend --force
|
|
86
105
|
|
|
106
|
+
${colors.yellow('Removal Examples:')}
|
|
107
|
+
npx cursor-templates --remove web-frontend
|
|
108
|
+
npx cursor-templates --remove web-frontend web-backend
|
|
109
|
+
npx cursor-templates --remove web-frontend --ide=cursor
|
|
110
|
+
npx cursor-templates --reset
|
|
111
|
+
npx cursor-templates --reset --ide=cursor
|
|
112
|
+
npx cursor-templates --reset --yes
|
|
113
|
+
|
|
87
114
|
${colors.dim('Shared rules (code-quality, security, git-workflow, etc.) are always included.')}
|
|
88
115
|
${colors.dim('Identical files are skipped. Modified files are preserved; ours saved as *-1.md.')}
|
|
89
116
|
${colors.dim('CLAUDE.md: missing sections are intelligently merged (not overwritten).')}
|
|
@@ -469,146 +496,291 @@ function generateClaudeMdToPath(targetDir, installedTemplates, destPath) {
|
|
|
469
496
|
fs.writeFileSync(destPath, content);
|
|
470
497
|
}
|
|
471
498
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
499
|
+
/**
|
|
500
|
+
* Generate content for GitHub Copilot instructions file
|
|
501
|
+
*/
|
|
502
|
+
function generateCopilotInstructionsContent(installedTemplates) {
|
|
503
|
+
const templateList = installedTemplates
|
|
504
|
+
.map(t => `- ${t}: ${TEMPLATES[t].description}`)
|
|
505
|
+
.join('\n');
|
|
506
|
+
|
|
507
|
+
// Read and concatenate all shared rules
|
|
508
|
+
const sharedRulesContent = SHARED_RULES.map(rule => {
|
|
509
|
+
const rulePath = path.join(TEMPLATES_DIR, '_shared', rule);
|
|
510
|
+
try {
|
|
511
|
+
return fs.readFileSync(rulePath, 'utf8');
|
|
512
|
+
} catch {
|
|
513
|
+
return '';
|
|
514
|
+
}
|
|
515
|
+
}).filter(Boolean).join('\n\n---\n\n');
|
|
516
|
+
|
|
517
|
+
// Read and concatenate template-specific rules
|
|
518
|
+
const templateRulesContent = installedTemplates.map(template => {
|
|
519
|
+
return TEMPLATES[template].rules.map(rule => {
|
|
520
|
+
const rulePath = path.join(TEMPLATES_DIR, template, '.cursorrules', rule);
|
|
521
|
+
try {
|
|
522
|
+
return fs.readFileSync(rulePath, 'utf8');
|
|
523
|
+
} catch {
|
|
524
|
+
return '';
|
|
525
|
+
}
|
|
526
|
+
}).filter(Boolean).join('\n\n');
|
|
527
|
+
}).join('\n\n---\n\n');
|
|
528
|
+
|
|
529
|
+
return `# Copilot Instructions
|
|
530
|
+
|
|
531
|
+
This file provides coding guidelines for GitHub Copilot in this project.
|
|
532
|
+
|
|
533
|
+
## Project Configuration
|
|
534
|
+
|
|
535
|
+
**Installed Templates:** ${installedTemplates.join(', ')}
|
|
536
|
+
|
|
537
|
+
${templateList}
|
|
538
|
+
|
|
539
|
+
---
|
|
540
|
+
|
|
541
|
+
## Core Principles
|
|
542
|
+
|
|
543
|
+
### Honesty Over Output
|
|
544
|
+
- If something doesn't work, say it doesn't work
|
|
545
|
+
- If you don't know, say you don't know
|
|
546
|
+
- Never hide errors or suppress warnings
|
|
547
|
+
|
|
548
|
+
### Security First
|
|
549
|
+
- Zero trust: Every input is hostile until proven otherwise
|
|
550
|
+
- Validate and sanitize all inputs
|
|
551
|
+
- No secrets in code or logs
|
|
552
|
+
|
|
553
|
+
### Tests Are Required
|
|
554
|
+
- No feature is complete without tests
|
|
555
|
+
- Test behavior, not implementation
|
|
556
|
+
|
|
557
|
+
### Code Quality
|
|
558
|
+
- SOLID principles
|
|
559
|
+
- DRY (Don't Repeat Yourself)
|
|
560
|
+
- Explicit over implicit
|
|
561
|
+
|
|
562
|
+
---
|
|
563
|
+
|
|
564
|
+
## Shared Guidelines
|
|
565
|
+
|
|
566
|
+
${sharedRulesContent}
|
|
567
|
+
|
|
568
|
+
---
|
|
569
|
+
|
|
570
|
+
## Template-Specific Guidelines
|
|
571
|
+
|
|
572
|
+
${templateRulesContent}
|
|
573
|
+
|
|
574
|
+
---
|
|
575
|
+
|
|
576
|
+
## Definition of Done
|
|
577
|
+
|
|
578
|
+
A feature is complete when:
|
|
579
|
+
- [ ] Code written and reviewed
|
|
580
|
+
- [ ] Tests written and passing
|
|
581
|
+
- [ ] No linting errors
|
|
582
|
+
- [ ] Security reviewed
|
|
583
|
+
- [ ] Documentation updated
|
|
584
|
+
`;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function install(targetDir, templates, dryRun = false, force = false, ides = DEFAULT_IDES) {
|
|
588
|
+
const stats = { copied: 0, skipped: 0, updated: 0, renamed: 0 };
|
|
589
|
+
const renamedFiles = [];
|
|
590
|
+
const installedFor = [];
|
|
478
591
|
|
|
479
592
|
console.log(`${colors.blue('Installing to:')} ${targetDir}`);
|
|
593
|
+
console.log(`${colors.blue('Target IDEs:')} ${ides.join(', ')}`);
|
|
480
594
|
if (!force) {
|
|
481
595
|
console.log(colors.dim('(identical files skipped, modified files preserved with ours saved as *-1.md)'));
|
|
482
|
-
console.log(colors.dim('(CLAUDE.md: missing sections merged intelligently)'));
|
|
483
596
|
}
|
|
484
597
|
console.log();
|
|
485
598
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
console.log(colors.green('► Installing shared rules...'));
|
|
491
|
-
for (const rule of SHARED_RULES) {
|
|
492
|
-
const src = path.join(TEMPLATES_DIR, '_shared', rule);
|
|
493
|
-
const dest = path.join(cursorrules, rule);
|
|
599
|
+
// 1. Install .cursorrules/ for Cursor IDE
|
|
600
|
+
if (ides.includes('cursor')) {
|
|
601
|
+
installedFor.push('cursor');
|
|
602
|
+
const cursorrules = path.join(targetDir, '.cursorrules');
|
|
494
603
|
|
|
495
|
-
if (dryRun) {
|
|
496
|
-
|
|
497
|
-
if (!exists) {
|
|
498
|
-
console.log(` ${colors.dim('[copy]')} ${rule}`);
|
|
499
|
-
} else if (force) {
|
|
500
|
-
console.log(` ${colors.dim('[update]')} ${rule}`);
|
|
501
|
-
} else if (filesMatch(src, dest)) {
|
|
502
|
-
console.log(` ${colors.yellow('[skip]')} ${rule} (identical)`);
|
|
503
|
-
} else {
|
|
504
|
-
const altName = path.basename(getAlternateFilename(dest));
|
|
505
|
-
console.log(` ${colors.blue('[rename]')} ${rule} → ${altName}`);
|
|
506
|
-
}
|
|
507
|
-
} else {
|
|
508
|
-
const result = copyFile(src, dest, force);
|
|
509
|
-
stats[result.status]++;
|
|
510
|
-
if (result.status === 'skipped') {
|
|
511
|
-
console.log(` ${colors.yellow('[skip]')} ${rule} (identical)`);
|
|
512
|
-
} else if (result.status === 'renamed') {
|
|
513
|
-
const altName = path.basename(result.destFile);
|
|
514
|
-
renamedFiles.push({ original: rule, renamed: altName });
|
|
515
|
-
console.log(` ${colors.blue('[rename]')} ${rule} → ${altName}`);
|
|
516
|
-
} else {
|
|
517
|
-
console.log(` ${colors.dim(`[${result.status}]`)} ${rule}`);
|
|
518
|
-
}
|
|
604
|
+
if (!dryRun && !fs.existsSync(cursorrules)) {
|
|
605
|
+
fs.mkdirSync(cursorrules, { recursive: true });
|
|
519
606
|
}
|
|
520
|
-
}
|
|
521
|
-
console.log();
|
|
522
607
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
const src = path.join(TEMPLATES_DIR, template, '.cursorrules', rule);
|
|
529
|
-
const dest = path.join(cursorrules, `${template}-${rule}`);
|
|
530
|
-
const destName = `${template}-${rule}`;
|
|
608
|
+
// Install shared rules
|
|
609
|
+
console.log(colors.green('► Installing shared rules (.cursorrules/)...'));
|
|
610
|
+
for (const rule of SHARED_RULES) {
|
|
611
|
+
const src = path.join(TEMPLATES_DIR, '_shared', rule);
|
|
612
|
+
const dest = path.join(cursorrules, rule);
|
|
531
613
|
|
|
532
614
|
if (dryRun) {
|
|
533
615
|
const exists = fs.existsSync(dest);
|
|
534
616
|
if (!exists) {
|
|
535
|
-
console.log(` ${colors.dim('[copy]')} ${
|
|
617
|
+
console.log(` ${colors.dim('[copy]')} ${rule}`);
|
|
536
618
|
} else if (force) {
|
|
537
|
-
console.log(` ${colors.dim('[update]')} ${
|
|
619
|
+
console.log(` ${colors.dim('[update]')} ${rule}`);
|
|
538
620
|
} else if (filesMatch(src, dest)) {
|
|
539
|
-
console.log(` ${colors.yellow('[skip]')} ${
|
|
621
|
+
console.log(` ${colors.yellow('[skip]')} ${rule} (identical)`);
|
|
540
622
|
} else {
|
|
541
623
|
const altName = path.basename(getAlternateFilename(dest));
|
|
542
|
-
console.log(` ${colors.blue('[rename]')} ${
|
|
624
|
+
console.log(` ${colors.blue('[rename]')} ${rule} → ${altName}`);
|
|
543
625
|
}
|
|
544
626
|
} else {
|
|
545
627
|
const result = copyFile(src, dest, force);
|
|
546
628
|
stats[result.status]++;
|
|
547
629
|
if (result.status === 'skipped') {
|
|
548
|
-
console.log(` ${colors.yellow('[skip]')} ${
|
|
630
|
+
console.log(` ${colors.yellow('[skip]')} ${rule} (identical)`);
|
|
549
631
|
} else if (result.status === 'renamed') {
|
|
550
632
|
const altName = path.basename(result.destFile);
|
|
551
|
-
renamedFiles.push({ original:
|
|
552
|
-
console.log(` ${colors.blue('[rename]')} ${
|
|
633
|
+
renamedFiles.push({ original: rule, renamed: altName });
|
|
634
|
+
console.log(` ${colors.blue('[rename]')} ${rule} → ${altName}`);
|
|
553
635
|
} else {
|
|
554
|
-
console.log(` ${colors.dim(`[${result.status}]`)} ${
|
|
636
|
+
console.log(` ${colors.dim(`[${result.status}]`)} ${rule}`);
|
|
555
637
|
}
|
|
556
638
|
}
|
|
557
639
|
}
|
|
558
640
|
console.log();
|
|
641
|
+
|
|
642
|
+
// Install template-specific rules
|
|
643
|
+
for (const template of templates) {
|
|
644
|
+
console.log(colors.green(`► Installing ${template} template (.cursorrules/)...`));
|
|
645
|
+
|
|
646
|
+
for (const rule of TEMPLATES[template].rules) {
|
|
647
|
+
const src = path.join(TEMPLATES_DIR, template, '.cursorrules', rule);
|
|
648
|
+
const dest = path.join(cursorrules, `${template}-${rule}`);
|
|
649
|
+
const destName = `${template}-${rule}`;
|
|
650
|
+
|
|
651
|
+
if (dryRun) {
|
|
652
|
+
const exists = fs.existsSync(dest);
|
|
653
|
+
if (!exists) {
|
|
654
|
+
console.log(` ${colors.dim('[copy]')} ${destName}`);
|
|
655
|
+
} else if (force) {
|
|
656
|
+
console.log(` ${colors.dim('[update]')} ${destName}`);
|
|
657
|
+
} else if (filesMatch(src, dest)) {
|
|
658
|
+
console.log(` ${colors.yellow('[skip]')} ${destName} (identical)`);
|
|
659
|
+
} else {
|
|
660
|
+
const altName = path.basename(getAlternateFilename(dest));
|
|
661
|
+
console.log(` ${colors.blue('[rename]')} ${destName} → ${altName}`);
|
|
662
|
+
}
|
|
663
|
+
} else {
|
|
664
|
+
const result = copyFile(src, dest, force);
|
|
665
|
+
stats[result.status]++;
|
|
666
|
+
if (result.status === 'skipped') {
|
|
667
|
+
console.log(` ${colors.yellow('[skip]')} ${destName} (identical)`);
|
|
668
|
+
} else if (result.status === 'renamed') {
|
|
669
|
+
const altName = path.basename(result.destFile);
|
|
670
|
+
renamedFiles.push({ original: destName, renamed: altName });
|
|
671
|
+
console.log(` ${colors.blue('[rename]')} ${destName} → ${altName}`);
|
|
672
|
+
} else {
|
|
673
|
+
console.log(` ${colors.dim(`[${result.status}]`)} ${destName}`);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
console.log();
|
|
678
|
+
}
|
|
559
679
|
}
|
|
560
680
|
|
|
561
|
-
//
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
681
|
+
// 2. Generate CLAUDE.md for Claude Code
|
|
682
|
+
if (ides.includes('claude')) {
|
|
683
|
+
installedFor.push('claude');
|
|
684
|
+
const claudePath = path.join(targetDir, 'CLAUDE.md');
|
|
685
|
+
const claudeExists = fs.existsSync(claudePath);
|
|
686
|
+
const templateContent = generateClaudeMdContent(templates);
|
|
687
|
+
|
|
688
|
+
console.log(colors.green('► Generating CLAUDE.md (Claude Code)...'));
|
|
689
|
+
if (dryRun) {
|
|
690
|
+
if (!claudeExists) {
|
|
691
|
+
console.log(` ${colors.dim('[copy]')} CLAUDE.md`);
|
|
692
|
+
} else if (force) {
|
|
693
|
+
console.log(` ${colors.dim('[update]')} CLAUDE.md`);
|
|
694
|
+
} else {
|
|
695
|
+
const existingContent = fs.readFileSync(claudePath, 'utf8');
|
|
696
|
+
const { missing } = findMissingSections(existingContent, templateContent);
|
|
697
|
+
if (missing.length === 0) {
|
|
698
|
+
console.log(` ${colors.yellow('[skip]')} CLAUDE.md (all sections present)`);
|
|
699
|
+
} else {
|
|
700
|
+
console.log(` ${colors.blue('[merge]')} CLAUDE.md (would add ${missing.length} section(s))`);
|
|
701
|
+
for (const section of missing) {
|
|
702
|
+
console.log(` ${colors.dim('+')} ${section.heading}`);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
} else if (!claudeExists) {
|
|
707
|
+
fs.writeFileSync(claudePath, templateContent);
|
|
708
|
+
console.log(` ${colors.dim('[copied]')} CLAUDE.md`);
|
|
709
|
+
stats.copied++;
|
|
570
710
|
} else if (force) {
|
|
571
|
-
|
|
711
|
+
fs.writeFileSync(claudePath, templateContent);
|
|
712
|
+
console.log(` ${colors.dim('[updated]')} CLAUDE.md`);
|
|
713
|
+
stats.updated++;
|
|
572
714
|
} else {
|
|
573
|
-
// Check what would be merged
|
|
574
715
|
const existingContent = fs.readFileSync(claudePath, 'utf8');
|
|
575
|
-
const {
|
|
576
|
-
|
|
716
|
+
const { merged, addedSections } = mergeClaudeContent(existingContent, templateContent);
|
|
717
|
+
|
|
718
|
+
if (addedSections.length === 0) {
|
|
577
719
|
console.log(` ${colors.yellow('[skip]')} CLAUDE.md (all sections present)`);
|
|
720
|
+
stats.skipped++;
|
|
578
721
|
} else {
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
722
|
+
fs.writeFileSync(claudePath, merged);
|
|
723
|
+
console.log(` ${colors.blue('[merged]')} CLAUDE.md`);
|
|
724
|
+
console.log(` ${colors.green('Added sections:')}`);
|
|
725
|
+
for (const heading of addedSections) {
|
|
726
|
+
console.log(` ${colors.dim('+')} ${heading}`);
|
|
582
727
|
}
|
|
728
|
+
stats.updated++;
|
|
583
729
|
}
|
|
584
730
|
}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
const existingContent = fs.readFileSync(claudePath, 'utf8');
|
|
596
|
-
const { merged, addedSections } = mergeClaudeContent(existingContent, templateContent);
|
|
731
|
+
console.log();
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// 3. Generate .github/copilot-instructions.md for GitHub Copilot (Codex)
|
|
735
|
+
if (ides.includes('codex')) {
|
|
736
|
+
installedFor.push('codex');
|
|
737
|
+
const githubDir = path.join(targetDir, '.github');
|
|
738
|
+
const copilotPath = path.join(githubDir, 'copilot-instructions.md');
|
|
739
|
+
const copilotExists = fs.existsSync(copilotPath);
|
|
740
|
+
const copilotContent = generateCopilotInstructionsContent(templates);
|
|
597
741
|
|
|
598
|
-
if (
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
742
|
+
if (!dryRun && !fs.existsSync(githubDir)) {
|
|
743
|
+
fs.mkdirSync(githubDir, { recursive: true });
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
console.log(colors.green('► Generating .github/copilot-instructions.md (GitHub Copilot)...'));
|
|
747
|
+
if (dryRun) {
|
|
748
|
+
if (!copilotExists) {
|
|
749
|
+
console.log(` ${colors.dim('[copy]')} .github/copilot-instructions.md`);
|
|
750
|
+
} else if (force) {
|
|
751
|
+
console.log(` ${colors.dim('[update]')} .github/copilot-instructions.md`);
|
|
752
|
+
} else {
|
|
753
|
+
const existingContent = fs.readFileSync(copilotPath, 'utf8');
|
|
754
|
+
const { missing } = findMissingSections(existingContent, copilotContent);
|
|
755
|
+
if (missing.length === 0) {
|
|
756
|
+
console.log(` ${colors.yellow('[skip]')} .github/copilot-instructions.md (all sections present)`);
|
|
757
|
+
} else {
|
|
758
|
+
console.log(` ${colors.blue('[merge]')} .github/copilot-instructions.md (would add ${missing.length} section(s))`);
|
|
759
|
+
}
|
|
607
760
|
}
|
|
761
|
+
} else if (!copilotExists) {
|
|
762
|
+
fs.writeFileSync(copilotPath, copilotContent);
|
|
763
|
+
console.log(` ${colors.dim('[copied]')} .github/copilot-instructions.md`);
|
|
764
|
+
stats.copied++;
|
|
765
|
+
} else if (force) {
|
|
766
|
+
fs.writeFileSync(copilotPath, copilotContent);
|
|
767
|
+
console.log(` ${colors.dim('[updated]')} .github/copilot-instructions.md`);
|
|
608
768
|
stats.updated++;
|
|
769
|
+
} else {
|
|
770
|
+
const existingContent = fs.readFileSync(copilotPath, 'utf8');
|
|
771
|
+
const { merged, addedSections } = mergeClaudeContent(existingContent, copilotContent);
|
|
772
|
+
|
|
773
|
+
if (addedSections.length === 0) {
|
|
774
|
+
console.log(` ${colors.yellow('[skip]')} .github/copilot-instructions.md (all sections present)`);
|
|
775
|
+
stats.skipped++;
|
|
776
|
+
} else {
|
|
777
|
+
fs.writeFileSync(copilotPath, merged);
|
|
778
|
+
console.log(` ${colors.blue('[merged]')} .github/copilot-instructions.md`);
|
|
779
|
+
stats.updated++;
|
|
780
|
+
}
|
|
609
781
|
}
|
|
782
|
+
console.log();
|
|
610
783
|
}
|
|
611
|
-
console.log();
|
|
612
784
|
|
|
613
785
|
// Summary
|
|
614
786
|
console.log(colors.green('════════════════════════════════════════════════════════════'));
|
|
@@ -627,7 +799,18 @@ function install(targetDir, templates, dryRun = false, force = false) {
|
|
|
627
799
|
}
|
|
628
800
|
console.log();
|
|
629
801
|
|
|
630
|
-
console.log(colors.yellow('
|
|
802
|
+
console.log(colors.yellow('Installed for:'));
|
|
803
|
+
for (const ide of installedFor) {
|
|
804
|
+
const ideInfo = {
|
|
805
|
+
cursor: '.cursorrules/ (Cursor IDE)',
|
|
806
|
+
claude: 'CLAUDE.md (Claude Code)',
|
|
807
|
+
codex: '.github/copilot-instructions.md (GitHub Copilot)'
|
|
808
|
+
};
|
|
809
|
+
console.log(` - ${ideInfo[ide]}`);
|
|
810
|
+
}
|
|
811
|
+
console.log();
|
|
812
|
+
|
|
813
|
+
console.log(colors.yellow('Templates:'));
|
|
631
814
|
console.log(' - _shared (always included)');
|
|
632
815
|
for (const template of templates) {
|
|
633
816
|
console.log(` - ${template}`);
|
|
@@ -645,51 +828,489 @@ function install(targetDir, templates, dryRun = false, force = false) {
|
|
|
645
828
|
}
|
|
646
829
|
|
|
647
830
|
console.log(colors.blue('Next steps:'));
|
|
648
|
-
|
|
649
|
-
|
|
831
|
+
if (installedFor.includes('claude')) {
|
|
832
|
+
console.log(' 1. Review CLAUDE.md for any customization');
|
|
833
|
+
}
|
|
834
|
+
if (installedFor.includes('codex')) {
|
|
835
|
+
console.log(' 2. Review .github/copilot-instructions.md');
|
|
836
|
+
}
|
|
837
|
+
console.log(' 3. Commit the new files to your repository');
|
|
838
|
+
console.log();
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
/**
|
|
842
|
+
* Prompt user for confirmation
|
|
843
|
+
* @param {string} message - The prompt message
|
|
844
|
+
* @returns {Promise<boolean>}
|
|
845
|
+
*/
|
|
846
|
+
async function confirm(message) {
|
|
847
|
+
const readline = await import('readline');
|
|
848
|
+
const rl = readline.createInterface({
|
|
849
|
+
input: process.stdin,
|
|
850
|
+
output: process.stdout
|
|
851
|
+
});
|
|
852
|
+
|
|
853
|
+
return new Promise((resolve) => {
|
|
854
|
+
rl.question(`${message} [y/N] `, (answer) => {
|
|
855
|
+
rl.close();
|
|
856
|
+
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
|
|
857
|
+
});
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
/**
|
|
862
|
+
* Check if a file was created by our installer (matches template content)
|
|
863
|
+
* @param {string} filePath - Path to the file
|
|
864
|
+
* @param {string} templatePath - Path to the template file
|
|
865
|
+
* @returns {boolean}
|
|
866
|
+
*/
|
|
867
|
+
function isOurFile(filePath, templatePath) {
|
|
868
|
+
if (!fs.existsSync(filePath)) return false;
|
|
869
|
+
if (!fs.existsSync(templatePath)) return true; // No template to compare, assume ours
|
|
870
|
+
return filesMatch(filePath, templatePath);
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
/**
|
|
874
|
+
* Remove specific templates from the installation
|
|
875
|
+
*/
|
|
876
|
+
async function remove(targetDir, templates, dryRun = false, force = false, skipConfirm = false, ides = DEFAULT_IDES) {
|
|
877
|
+
const stats = { removed: 0, skipped: 0, notFound: 0 };
|
|
878
|
+
const filesToRemove = [];
|
|
879
|
+
const modifiedFiles = [];
|
|
880
|
+
|
|
881
|
+
console.log(`${colors.blue('Removing from:')} ${targetDir}`);
|
|
882
|
+
console.log(`${colors.blue('Target IDEs:')} ${ides.join(', ')}`);
|
|
883
|
+
console.log(`${colors.blue('Templates:')} ${templates.join(', ')}`);
|
|
884
|
+
console.log();
|
|
885
|
+
|
|
886
|
+
// 1. Collect files to remove from .cursorrules/
|
|
887
|
+
if (ides.includes('cursor')) {
|
|
888
|
+
const cursorrules = path.join(targetDir, '.cursorrules');
|
|
889
|
+
|
|
890
|
+
if (fs.existsSync(cursorrules)) {
|
|
891
|
+
for (const template of templates) {
|
|
892
|
+
console.log(colors.yellow(`► Scanning ${template} template files...`));
|
|
893
|
+
|
|
894
|
+
for (const rule of TEMPLATES[template].rules) {
|
|
895
|
+
const destName = `${template}-${rule}`;
|
|
896
|
+
const destPath = path.join(cursorrules, destName);
|
|
897
|
+
const srcPath = path.join(TEMPLATES_DIR, template, '.cursorrules', rule);
|
|
898
|
+
|
|
899
|
+
if (!fs.existsSync(destPath)) {
|
|
900
|
+
console.log(` ${colors.dim('[not found]')} ${destName}`);
|
|
901
|
+
stats.notFound++;
|
|
902
|
+
continue;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
const isUnmodified = isOurFile(destPath, srcPath);
|
|
906
|
+
|
|
907
|
+
if (!isUnmodified && !force) {
|
|
908
|
+
console.log(` ${colors.yellow('[modified]')} ${destName} (use --force to remove)`);
|
|
909
|
+
modifiedFiles.push(destName);
|
|
910
|
+
stats.skipped++;
|
|
911
|
+
} else {
|
|
912
|
+
console.log(` ${colors.red('[remove]')} ${destName}${!isUnmodified ? ' (modified, --force)' : ''}`);
|
|
913
|
+
filesToRemove.push({ path: destPath, name: destName });
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
// Also check for -1 variant files
|
|
918
|
+
for (const rule of TEMPLATES[template].rules) {
|
|
919
|
+
const altName = `${template}-${rule.replace('.md', '-1.md')}`;
|
|
920
|
+
const altPath = path.join(cursorrules, altName);
|
|
921
|
+
|
|
922
|
+
if (fs.existsSync(altPath)) {
|
|
923
|
+
console.log(` ${colors.red('[remove]')} ${altName} (alternate file)`);
|
|
924
|
+
filesToRemove.push({ path: altPath, name: altName });
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
console.log();
|
|
929
|
+
}
|
|
930
|
+
} else {
|
|
931
|
+
console.log(colors.dim('No .cursorrules/ directory found.\n'));
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
// 2. Note about CLAUDE.md and copilot-instructions.md
|
|
936
|
+
// These are regenerated, not patched, so we can't easily remove just one template's content
|
|
937
|
+
// We'll warn the user about this
|
|
938
|
+
if (ides.includes('claude') || ides.includes('codex')) {
|
|
939
|
+
console.log(colors.yellow('Note: CLAUDE.md and copilot-instructions.md contain merged content.'));
|
|
940
|
+
console.log(colors.dim('To update these files, re-run the installer with the remaining templates.\n'));
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
if (filesToRemove.length === 0) {
|
|
944
|
+
console.log(colors.yellow('Nothing to remove.\n'));
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// Confirmation
|
|
949
|
+
if (!dryRun && !skipConfirm) {
|
|
950
|
+
console.log(colors.yellow(`\nAbout to remove ${filesToRemove.length} file(s).`));
|
|
951
|
+
const confirmed = await confirm(colors.red('Proceed with removal?'));
|
|
952
|
+
if (!confirmed) {
|
|
953
|
+
console.log(colors.dim('\nAborted.\n'));
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
console.log();
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// Execute removal
|
|
960
|
+
if (dryRun) {
|
|
961
|
+
console.log(colors.yellow('DRY RUN - No files were removed.\n'));
|
|
962
|
+
} else {
|
|
963
|
+
for (const file of filesToRemove) {
|
|
964
|
+
try {
|
|
965
|
+
fs.unlinkSync(file.path);
|
|
966
|
+
stats.removed++;
|
|
967
|
+
} catch (err) {
|
|
968
|
+
console.error(colors.red(`Failed to remove ${file.name}: ${err.message}`));
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// Summary
|
|
974
|
+
console.log(colors.green('════════════════════════════════════════════════════════════'));
|
|
975
|
+
console.log(colors.green(`✓ Removal complete!\n`));
|
|
976
|
+
|
|
977
|
+
console.log(colors.yellow('Summary:'));
|
|
978
|
+
console.log(` - ${stats.removed} files removed`);
|
|
979
|
+
if (stats.skipped > 0) {
|
|
980
|
+
console.log(` - ${stats.skipped} files skipped (modified, use --force)`);
|
|
981
|
+
}
|
|
982
|
+
if (stats.notFound > 0) {
|
|
983
|
+
console.log(` - ${stats.notFound} files not found`);
|
|
984
|
+
}
|
|
985
|
+
console.log();
|
|
986
|
+
|
|
987
|
+
if (modifiedFiles.length > 0) {
|
|
988
|
+
console.log(colors.yellow('Modified files preserved:'));
|
|
989
|
+
for (const file of modifiedFiles) {
|
|
990
|
+
console.log(` - ${file}`);
|
|
991
|
+
}
|
|
992
|
+
console.log(colors.dim('\nUse --force to remove modified files.\n'));
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
/**
|
|
997
|
+
* Reset - remove all installed content
|
|
998
|
+
*/
|
|
999
|
+
async function reset(targetDir, dryRun = false, force = false, skipConfirm = false, ides = DEFAULT_IDES) {
|
|
1000
|
+
const stats = { removed: 0, skipped: 0 };
|
|
1001
|
+
const filesToRemove = [];
|
|
1002
|
+
const modifiedFiles = [];
|
|
1003
|
+
const dirsToRemove = [];
|
|
1004
|
+
|
|
1005
|
+
console.log(`${colors.blue('Resetting:')} ${targetDir}`);
|
|
1006
|
+
console.log(`${colors.blue('Target IDEs:')} ${ides.join(', ')}`);
|
|
1007
|
+
console.log();
|
|
1008
|
+
|
|
1009
|
+
// 1. Remove .cursorrules/ contents for Cursor
|
|
1010
|
+
if (ides.includes('cursor')) {
|
|
1011
|
+
const cursorrules = path.join(targetDir, '.cursorrules');
|
|
1012
|
+
|
|
1013
|
+
if (fs.existsSync(cursorrules)) {
|
|
1014
|
+
console.log(colors.yellow('► Scanning .cursorrules/ directory...'));
|
|
1015
|
+
|
|
1016
|
+
// Check shared rules
|
|
1017
|
+
for (const rule of SHARED_RULES) {
|
|
1018
|
+
const destPath = path.join(cursorrules, rule);
|
|
1019
|
+
const srcPath = path.join(TEMPLATES_DIR, '_shared', rule);
|
|
1020
|
+
|
|
1021
|
+
if (!fs.existsSync(destPath)) continue;
|
|
1022
|
+
|
|
1023
|
+
const isUnmodified = isOurFile(destPath, srcPath);
|
|
1024
|
+
|
|
1025
|
+
if (!isUnmodified && !force) {
|
|
1026
|
+
console.log(` ${colors.yellow('[modified]')} ${rule} (use --force to remove)`);
|
|
1027
|
+
modifiedFiles.push(rule);
|
|
1028
|
+
stats.skipped++;
|
|
1029
|
+
} else {
|
|
1030
|
+
console.log(` ${colors.red('[remove]')} ${rule}${!isUnmodified ? ' (modified, --force)' : ''}`);
|
|
1031
|
+
filesToRemove.push({ path: destPath, name: rule });
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
// Check for -1 variant
|
|
1035
|
+
const altPath = path.join(cursorrules, rule.replace('.md', '-1.md'));
|
|
1036
|
+
if (fs.existsSync(altPath)) {
|
|
1037
|
+
console.log(` ${colors.red('[remove]')} ${rule.replace('.md', '-1.md')} (alternate file)`);
|
|
1038
|
+
filesToRemove.push({ path: altPath, name: rule.replace('.md', '-1.md') });
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
// Check template-specific rules
|
|
1043
|
+
for (const [templateName, templateInfo] of Object.entries(TEMPLATES)) {
|
|
1044
|
+
for (const rule of templateInfo.rules) {
|
|
1045
|
+
const destName = `${templateName}-${rule}`;
|
|
1046
|
+
const destPath = path.join(cursorrules, destName);
|
|
1047
|
+
const srcPath = path.join(TEMPLATES_DIR, templateName, '.cursorrules', rule);
|
|
1048
|
+
|
|
1049
|
+
if (!fs.existsSync(destPath)) continue;
|
|
1050
|
+
|
|
1051
|
+
const isUnmodified = isOurFile(destPath, srcPath);
|
|
1052
|
+
|
|
1053
|
+
if (!isUnmodified && !force) {
|
|
1054
|
+
console.log(` ${colors.yellow('[modified]')} ${destName} (use --force to remove)`);
|
|
1055
|
+
modifiedFiles.push(destName);
|
|
1056
|
+
stats.skipped++;
|
|
1057
|
+
} else {
|
|
1058
|
+
console.log(` ${colors.red('[remove]')} ${destName}${!isUnmodified ? ' (modified, --force)' : ''}`);
|
|
1059
|
+
filesToRemove.push({ path: destPath, name: destName });
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
// Check for -1 variant
|
|
1063
|
+
const altName = destName.replace('.md', '-1.md');
|
|
1064
|
+
const altPath = path.join(cursorrules, altName);
|
|
1065
|
+
if (fs.existsSync(altPath)) {
|
|
1066
|
+
console.log(` ${colors.red('[remove]')} ${altName} (alternate file)`);
|
|
1067
|
+
filesToRemove.push({ path: altPath, name: altName });
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
// Check if we should remove the directory itself (only if it would be empty)
|
|
1073
|
+
const remainingFiles = fs.readdirSync(cursorrules).filter(f => {
|
|
1074
|
+
const fullPath = path.join(cursorrules, f);
|
|
1075
|
+
const willBeRemoved = filesToRemove.some(fr => fr.path === fullPath);
|
|
1076
|
+
return !willBeRemoved;
|
|
1077
|
+
});
|
|
1078
|
+
|
|
1079
|
+
if (remainingFiles.length === 0 || force) {
|
|
1080
|
+
console.log(` ${colors.red('[remove]')} .cursorrules/ directory`);
|
|
1081
|
+
dirsToRemove.push(cursorrules);
|
|
1082
|
+
} else if (remainingFiles.length > 0) {
|
|
1083
|
+
console.log(colors.dim(` .cursorrules/ will be kept (${remainingFiles.length} non-template file(s) remain)`));
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
console.log();
|
|
1087
|
+
} else {
|
|
1088
|
+
console.log(colors.dim('No .cursorrules/ directory found.\n'));
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
// 2. Remove CLAUDE.md for Claude
|
|
1093
|
+
if (ides.includes('claude')) {
|
|
1094
|
+
const claudePath = path.join(targetDir, 'CLAUDE.md');
|
|
1095
|
+
|
|
1096
|
+
if (fs.existsSync(claudePath)) {
|
|
1097
|
+
console.log(colors.yellow('► Checking CLAUDE.md...'));
|
|
1098
|
+
|
|
1099
|
+
// Check if it contains our signature content
|
|
1100
|
+
const content = fs.readFileSync(claudePath, 'utf8');
|
|
1101
|
+
const isOurs = content.includes('# CLAUDE.md - Development Guide') &&
|
|
1102
|
+
content.includes('.cursorrules/');
|
|
1103
|
+
|
|
1104
|
+
if (!isOurs && !force) {
|
|
1105
|
+
console.log(` ${colors.yellow('[modified]')} CLAUDE.md (doesn't match template, use --force)`);
|
|
1106
|
+
modifiedFiles.push('CLAUDE.md');
|
|
1107
|
+
stats.skipped++;
|
|
1108
|
+
} else {
|
|
1109
|
+
console.log(` ${colors.red('[remove]')} CLAUDE.md${!isOurs ? ' (modified, --force)' : ''}`);
|
|
1110
|
+
filesToRemove.push({ path: claudePath, name: 'CLAUDE.md' });
|
|
1111
|
+
}
|
|
1112
|
+
console.log();
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
// 3. Remove .github/copilot-instructions.md for Codex
|
|
1117
|
+
if (ides.includes('codex')) {
|
|
1118
|
+
const copilotPath = path.join(targetDir, '.github', 'copilot-instructions.md');
|
|
1119
|
+
|
|
1120
|
+
if (fs.existsSync(copilotPath)) {
|
|
1121
|
+
console.log(colors.yellow('► Checking .github/copilot-instructions.md...'));
|
|
1122
|
+
|
|
1123
|
+
// Check if it contains our signature content
|
|
1124
|
+
const content = fs.readFileSync(copilotPath, 'utf8');
|
|
1125
|
+
const isOurs = content.includes('# Copilot Instructions') &&
|
|
1126
|
+
content.includes('Installed Templates:');
|
|
1127
|
+
|
|
1128
|
+
if (!isOurs && !force) {
|
|
1129
|
+
console.log(` ${colors.yellow('[modified]')} .github/copilot-instructions.md (doesn't match template, use --force)`);
|
|
1130
|
+
modifiedFiles.push('.github/copilot-instructions.md');
|
|
1131
|
+
stats.skipped++;
|
|
1132
|
+
} else {
|
|
1133
|
+
console.log(` ${colors.red('[remove]')} .github/copilot-instructions.md${!isOurs ? ' (modified, --force)' : ''}`);
|
|
1134
|
+
filesToRemove.push({ path: copilotPath, name: '.github/copilot-instructions.md' });
|
|
1135
|
+
}
|
|
1136
|
+
console.log();
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
if (filesToRemove.length === 0 && dirsToRemove.length === 0) {
|
|
1141
|
+
console.log(colors.yellow('Nothing to remove.\n'));
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
// Confirmation
|
|
1146
|
+
if (!dryRun && !skipConfirm) {
|
|
1147
|
+
const totalItems = filesToRemove.length + dirsToRemove.length;
|
|
1148
|
+
console.log(colors.yellow(`\nAbout to remove ${totalItems} item(s).`));
|
|
1149
|
+
const confirmed = await confirm(colors.red('Proceed with reset?'));
|
|
1150
|
+
if (!confirmed) {
|
|
1151
|
+
console.log(colors.dim('\nAborted.\n'));
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1154
|
+
console.log();
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
// Execute removal
|
|
1158
|
+
if (dryRun) {
|
|
1159
|
+
console.log(colors.yellow('DRY RUN - No files were removed.\n'));
|
|
1160
|
+
} else {
|
|
1161
|
+
// Remove files first
|
|
1162
|
+
for (const file of filesToRemove) {
|
|
1163
|
+
try {
|
|
1164
|
+
fs.unlinkSync(file.path);
|
|
1165
|
+
stats.removed++;
|
|
1166
|
+
} catch (err) {
|
|
1167
|
+
console.error(colors.red(`Failed to remove ${file.name}: ${err.message}`));
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
// Then remove directories
|
|
1172
|
+
for (const dir of dirsToRemove) {
|
|
1173
|
+
try {
|
|
1174
|
+
// Check if directory is now empty
|
|
1175
|
+
const remaining = fs.existsSync(dir) ? fs.readdirSync(dir) : [];
|
|
1176
|
+
if (remaining.length === 0) {
|
|
1177
|
+
fs.rmdirSync(dir);
|
|
1178
|
+
stats.removed++;
|
|
1179
|
+
} else if (force) {
|
|
1180
|
+
fs.rmSync(dir, { recursive: true });
|
|
1181
|
+
stats.removed++;
|
|
1182
|
+
}
|
|
1183
|
+
} catch (err) {
|
|
1184
|
+
console.error(colors.red(`Failed to remove directory: ${err.message}`));
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
// Summary
|
|
1190
|
+
console.log(colors.green('════════════════════════════════════════════════════════════'));
|
|
1191
|
+
console.log(colors.green(`✓ Reset complete!\n`));
|
|
1192
|
+
|
|
1193
|
+
console.log(colors.yellow('Summary:'));
|
|
1194
|
+
console.log(` - ${stats.removed} items removed`);
|
|
1195
|
+
if (stats.skipped > 0) {
|
|
1196
|
+
console.log(` - ${stats.skipped} files skipped (modified, use --force)`);
|
|
1197
|
+
}
|
|
650
1198
|
console.log();
|
|
1199
|
+
|
|
1200
|
+
if (modifiedFiles.length > 0) {
|
|
1201
|
+
console.log(colors.yellow('Modified files preserved:'));
|
|
1202
|
+
for (const file of modifiedFiles) {
|
|
1203
|
+
console.log(` - ${file}`);
|
|
1204
|
+
}
|
|
1205
|
+
console.log(colors.dim('\nUse --force to remove modified files.\n'));
|
|
1206
|
+
}
|
|
651
1207
|
}
|
|
652
1208
|
|
|
653
|
-
export function run(args) {
|
|
1209
|
+
export async function run(args) {
|
|
654
1210
|
const templates = [];
|
|
1211
|
+
const ides = [];
|
|
655
1212
|
let dryRun = false;
|
|
656
1213
|
let force = false;
|
|
1214
|
+
let skipConfirm = false;
|
|
1215
|
+
let removeMode = false;
|
|
1216
|
+
let resetMode = false;
|
|
657
1217
|
|
|
658
1218
|
// Parse arguments
|
|
659
1219
|
for (const arg of args) {
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
1220
|
+
if (arg === '--list' || arg === '-l') {
|
|
1221
|
+
printBanner();
|
|
1222
|
+
printTemplates();
|
|
1223
|
+
process.exit(0);
|
|
1224
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
1225
|
+
printBanner();
|
|
1226
|
+
printHelp();
|
|
1227
|
+
process.exit(0);
|
|
1228
|
+
} else if (arg === '--dry-run') {
|
|
1229
|
+
dryRun = true;
|
|
1230
|
+
} else if (arg === '--force' || arg === '-f') {
|
|
1231
|
+
force = true;
|
|
1232
|
+
} else if (arg === '--yes' || arg === '-y') {
|
|
1233
|
+
skipConfirm = true;
|
|
1234
|
+
} else if (arg === '--remove') {
|
|
1235
|
+
removeMode = true;
|
|
1236
|
+
} else if (arg === '--reset') {
|
|
1237
|
+
resetMode = true;
|
|
1238
|
+
} else if (arg.startsWith('--ide=')) {
|
|
1239
|
+
const ide = arg.slice(6).toLowerCase();
|
|
1240
|
+
if (!SUPPORTED_IDES.includes(ide)) {
|
|
1241
|
+
console.error(colors.red(`Error: Unknown IDE '${ide}'`));
|
|
1242
|
+
console.error(colors.dim(`Supported: ${SUPPORTED_IDES.join(', ')}`));
|
|
1243
|
+
process.exit(1);
|
|
1244
|
+
}
|
|
1245
|
+
if (!ides.includes(ide)) {
|
|
1246
|
+
ides.push(ide);
|
|
1247
|
+
}
|
|
1248
|
+
} else if (arg.startsWith('-')) {
|
|
1249
|
+
console.error(colors.red(`Error: Unknown option '${arg}'`));
|
|
1250
|
+
printHelp();
|
|
1251
|
+
process.exit(1);
|
|
1252
|
+
} else {
|
|
1253
|
+
templates.push(arg);
|
|
687
1254
|
}
|
|
688
1255
|
}
|
|
689
1256
|
|
|
690
1257
|
printBanner();
|
|
691
1258
|
|
|
692
|
-
//
|
|
1259
|
+
// Use default IDEs if none specified
|
|
1260
|
+
const targetIdes = ides.length > 0 ? ides : DEFAULT_IDES;
|
|
1261
|
+
|
|
1262
|
+
// Handle reset mode
|
|
1263
|
+
if (resetMode) {
|
|
1264
|
+
if (removeMode) {
|
|
1265
|
+
console.error(colors.red('Error: Cannot use --remove and --reset together\n'));
|
|
1266
|
+
process.exit(1);
|
|
1267
|
+
}
|
|
1268
|
+
if (templates.length > 0) {
|
|
1269
|
+
console.error(colors.red('Error: --reset does not accept template arguments\n'));
|
|
1270
|
+
console.error(colors.dim('Use --remove <templates...> to remove specific templates.\n'));
|
|
1271
|
+
process.exit(1);
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
if (dryRun) {
|
|
1275
|
+
console.log(colors.yellow('DRY RUN - No changes will be made\n'));
|
|
1276
|
+
}
|
|
1277
|
+
if (force) {
|
|
1278
|
+
console.log(colors.yellow('FORCE MODE - Modified files will be removed\n'));
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
await reset(process.cwd(), dryRun, force, skipConfirm, targetIdes);
|
|
1282
|
+
return;
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
// Handle remove mode
|
|
1286
|
+
if (removeMode) {
|
|
1287
|
+
if (templates.length === 0) {
|
|
1288
|
+
console.error(colors.red('Error: No templates specified for removal\n'));
|
|
1289
|
+
console.error(colors.dim('Usage: npx cursor-templates --remove <templates...>\n'));
|
|
1290
|
+
printTemplates();
|
|
1291
|
+
process.exit(1);
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
for (const template of templates) {
|
|
1295
|
+
if (!TEMPLATES[template]) {
|
|
1296
|
+
console.error(colors.red(`Error: Unknown template '${template}'\n`));
|
|
1297
|
+
printTemplates();
|
|
1298
|
+
process.exit(1);
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
if (dryRun) {
|
|
1303
|
+
console.log(colors.yellow('DRY RUN - No changes will be made\n'));
|
|
1304
|
+
}
|
|
1305
|
+
if (force) {
|
|
1306
|
+
console.log(colors.yellow('FORCE MODE - Modified files will be removed\n'));
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
await remove(process.cwd(), templates, dryRun, force, skipConfirm, targetIdes);
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
// Install mode (default)
|
|
693
1314
|
if (templates.length === 0) {
|
|
694
1315
|
console.error(colors.red('Error: No templates specified\n'));
|
|
695
1316
|
printHelp();
|
|
@@ -713,5 +1334,26 @@ export function run(args) {
|
|
|
713
1334
|
}
|
|
714
1335
|
|
|
715
1336
|
// Install to current directory
|
|
716
|
-
install(process.cwd(), templates, dryRun, force);
|
|
1337
|
+
install(process.cwd(), templates, dryRun, force, targetIdes);
|
|
717
1338
|
}
|
|
1339
|
+
|
|
1340
|
+
// Export internals for testing
|
|
1341
|
+
export const _internals = {
|
|
1342
|
+
TEMPLATES,
|
|
1343
|
+
SHARED_RULES,
|
|
1344
|
+
SUPPORTED_IDES,
|
|
1345
|
+
DEFAULT_IDES,
|
|
1346
|
+
filesMatch,
|
|
1347
|
+
parseMarkdownSections,
|
|
1348
|
+
generateSectionSignature,
|
|
1349
|
+
findMissingSections,
|
|
1350
|
+
mergeClaudeContent,
|
|
1351
|
+
getAlternateFilename,
|
|
1352
|
+
copyFile,
|
|
1353
|
+
generateClaudeMdContent,
|
|
1354
|
+
generateCopilotInstructionsContent,
|
|
1355
|
+
isOurFile,
|
|
1356
|
+
install,
|
|
1357
|
+
remove,
|
|
1358
|
+
reset,
|
|
1359
|
+
};
|