anchi-kit 1.0.3 → 2.0.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.
- package/README.md +16 -15
- package/docs/ONBOARDING_CHECKLIST.md +2 -2
- package/package.json +1 -1
- package/src/cli.js +9 -3
- package/src/commands/init.js +218 -113
- package/src/commands/uninstall.js +152 -0
- package/src/lib/detector.js +140 -0
- package/src/lib/profiles.js +100 -0
- package/src/lib/sizeCalculator.js +121 -0
- package/src/lib/skillPacks.js +109 -0
package/README.md
CHANGED
|
@@ -26,34 +26,35 @@ Template toàn diện cho việc phát triển webapp với **Cursor AI**.
|
|
|
26
26
|
|
|
27
27
|
## 🚀 Quick Start
|
|
28
28
|
|
|
29
|
-
### Phương thức 1:
|
|
29
|
+
### Phương thức 1: Smart Install (Khuyến nghị)
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
32
|
cd your-existing-project
|
|
33
33
|
npx anchi-kit init
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
> 💡
|
|
36
|
+
> 💡 **v2.0**: Chọn role profile + tech auto-detect + skill packs
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
**Profiles có sẵn:**
|
|
39
|
+
| Profile | Mô tả |
|
|
40
|
+
|---------|-------|
|
|
41
|
+
| Minimal | Core commands (5 files) |
|
|
42
|
+
| Developer | Daily coding |
|
|
43
|
+
| UI/UX | Frontend & design |
|
|
44
|
+
| Full Stack | Everything |
|
|
45
|
+
|
|
46
|
+
### Phương thức 2: Cài Nhanh
|
|
39
47
|
|
|
40
48
|
```bash
|
|
41
|
-
|
|
42
|
-
npx anchi-kit
|
|
49
|
+
npx anchi-kit install # Tất cả files
|
|
50
|
+
npx anchi-kit uninstall # Gỡ cài đặt
|
|
43
51
|
```
|
|
44
52
|
|
|
45
53
|
### Phương thức 3: Tạo Dự Án Mới
|
|
46
54
|
|
|
47
55
|
```bash
|
|
48
56
|
git clone https://github.com/ANCHI-STE/anchi-webkit-dev.git my-project
|
|
49
|
-
cd my-project
|
|
50
|
-
cursor .
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
### Kiểm tra cập nhật
|
|
54
|
-
|
|
55
|
-
```bash
|
|
56
|
-
npx anchi-kit update
|
|
57
|
+
cd my-project && cursor .
|
|
57
58
|
```
|
|
58
59
|
|
|
59
60
|
### Bước tiếp theo
|
|
@@ -65,8 +66,8 @@ Mở **Chat Panel** (`Ctrl+L`) và chạy:
|
|
|
65
66
|
```
|
|
66
67
|
|
|
67
68
|
AI sẽ hỏi:
|
|
68
|
-
- **"1" (Dự án có sẵn)** → Auto scan
|
|
69
|
-
- **"2" (Dự án mới)** → Chọn preset → Setup
|
|
69
|
+
- **"1" (Dự án có sẵn)** → Auto scan → Fill docs
|
|
70
|
+
- **"2" (Dự án mới)** → Chọn preset → Setup
|
|
70
71
|
|
|
71
72
|
```
|
|
72
73
|
/plan "First feature"
|
|
@@ -13,8 +13,8 @@ Checklist cho member mới vào team. Đánh dấu `[x]` khi hoàn thành.
|
|
|
13
13
|
- [ ] Đăng nhập Cursor (Google/GitHub)
|
|
14
14
|
|
|
15
15
|
### Project Setup
|
|
16
|
-
- [ ] Cài anchi-kit: `npx anchi-kit
|
|
17
|
-
- [ ] Mở project
|
|
16
|
+
- [ ] Cài anchi-kit: `npx anchi-kit init` (chọn profile phù hợp)
|
|
17
|
+
- [ ] Mở project: `cursor .`
|
|
18
18
|
- [ ] Chạy `/start` và chọn option
|
|
19
19
|
- [ ] Chọn preset: `/use-preset [name]`
|
|
20
20
|
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -259,6 +259,11 @@ async function main() {
|
|
|
259
259
|
await update();
|
|
260
260
|
break;
|
|
261
261
|
|
|
262
|
+
case 'uninstall':
|
|
263
|
+
const { uninstall } = require('./commands/uninstall');
|
|
264
|
+
await uninstall();
|
|
265
|
+
break;
|
|
266
|
+
|
|
262
267
|
default:
|
|
263
268
|
showHelp();
|
|
264
269
|
break;
|
|
@@ -270,9 +275,10 @@ function showHelp() {
|
|
|
270
275
|
log.info('🚀 anchi-kit - Cursor AI Toolkit');
|
|
271
276
|
console.log('');
|
|
272
277
|
console.log('Commands:');
|
|
273
|
-
log.success(' npx anchi-kit install Cài đặt nhanh (tất cả
|
|
274
|
-
log.success(' npx anchi-kit init Cài đặt
|
|
275
|
-
log.success(' npx anchi-kit update Kiểm tra
|
|
278
|
+
log.success(' npx anchi-kit install Cài đặt nhanh (tất cả)');
|
|
279
|
+
log.success(' npx anchi-kit init Cài đặt thông minh (chọn profile)');
|
|
280
|
+
log.success(' npx anchi-kit update Kiểm tra cập nhật');
|
|
281
|
+
log.success(' npx anchi-kit uninstall Gỡ cài đặt');
|
|
276
282
|
console.log('');
|
|
277
283
|
console.log('Documentation:');
|
|
278
284
|
console.log(' https://github.com/ANCHI-STE/anchi-webkit-dev');
|
package/src/commands/init.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// =============================================================================
|
|
3
|
-
// anchi-kit Init Command
|
|
3
|
+
// anchi-kit Enhanced Init Command
|
|
4
4
|
// Usage: npx anchi-kit init
|
|
5
5
|
// =============================================================================
|
|
6
6
|
|
|
@@ -8,6 +8,12 @@ const fs = require('fs');
|
|
|
8
8
|
const path = require('path');
|
|
9
9
|
const readline = require('readline');
|
|
10
10
|
|
|
11
|
+
const { PROFILES, getProfile, getProfileList, getProfileEstimate } = require('../lib/profiles');
|
|
12
|
+
const { getSkillPackList, resolveSkillPacks } = require('../lib/skillPacks');
|
|
13
|
+
const { detectTechStack, suggestSkills, formatDetectedStack } = require('../lib/detector');
|
|
14
|
+
const { calculateInstallSize, getComponentPaths, formatSize } = require('../lib/sizeCalculator');
|
|
15
|
+
|
|
16
|
+
// Colors
|
|
11
17
|
const colors = {
|
|
12
18
|
reset: '\x1b[0m',
|
|
13
19
|
red: '\x1b[31m',
|
|
@@ -38,50 +44,47 @@ function ask(question) {
|
|
|
38
44
|
});
|
|
39
45
|
}
|
|
40
46
|
|
|
41
|
-
function askChoice(question, options) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
while (true) {
|
|
51
|
-
const answer = await ask(`${colors.gray}Chọn (1-${options.length}): ${colors.reset}`);
|
|
52
|
-
const num = parseInt(answer);
|
|
53
|
-
if (num >= 1 && num <= options.length) {
|
|
54
|
-
resolve(options[num - 1].value);
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
log.warn(' Vui lòng chọn số hợp lệ');
|
|
47
|
+
async function askChoice(question, options) {
|
|
48
|
+
console.log('');
|
|
49
|
+
log.bold(question);
|
|
50
|
+
options.forEach((opt, i) => {
|
|
51
|
+
const estimate = opt.estimate ? ` ${colors.gray}(${opt.estimate})${colors.reset}` : '';
|
|
52
|
+
console.log(` ${colors.cyan}${i + 1}${colors.reset}) ${opt.label}${estimate}`);
|
|
53
|
+
if (opt.description) {
|
|
54
|
+
log.gray(` ${opt.description}`);
|
|
58
55
|
}
|
|
59
56
|
});
|
|
60
|
-
|
|
57
|
+
console.log('');
|
|
61
58
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
console.log('');
|
|
68
|
-
options.forEach((opt, i) => {
|
|
69
|
-
const checked = opt.default ? `${colors.green}◉${colors.reset}` : `${colors.gray}○${colors.reset}`;
|
|
70
|
-
console.log(` ${checked} ${colors.cyan}${i + 1}${colors.reset}) ${opt.label}`);
|
|
71
|
-
});
|
|
72
|
-
console.log('');
|
|
73
|
-
|
|
74
|
-
const answer = await ask(`${colors.gray}Chọn: ${colors.reset}`);
|
|
75
|
-
|
|
76
|
-
if (!answer) {
|
|
77
|
-
// Return defaults
|
|
78
|
-
resolve(options.filter(o => o.default).map(o => o.value));
|
|
79
|
-
return;
|
|
59
|
+
while (true) {
|
|
60
|
+
const answer = await ask(`${colors.gray}Chọn (1-${options.length}): ${colors.reset}`);
|
|
61
|
+
const num = parseInt(answer);
|
|
62
|
+
if (num >= 1 && num <= options.length) {
|
|
63
|
+
return options[num - 1].value;
|
|
80
64
|
}
|
|
65
|
+
log.warn(' Vui lòng chọn số hợp lệ');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
81
68
|
|
|
82
|
-
|
|
83
|
-
|
|
69
|
+
async function askMultiSelect(question, options) {
|
|
70
|
+
console.log('');
|
|
71
|
+
log.bold(question);
|
|
72
|
+
log.gray(' (Nhập số cách nhau bởi dấu phẩy, VD: 1,2,3. Enter để chọn mặc định)');
|
|
73
|
+
console.log('');
|
|
74
|
+
options.forEach((opt, i) => {
|
|
75
|
+
const checked = opt.default ? `${colors.green}◉${colors.reset}` : `${colors.gray}○${colors.reset}`;
|
|
76
|
+
console.log(` ${checked} ${colors.cyan}${i + 1}${colors.reset}) ${opt.label}`);
|
|
84
77
|
});
|
|
78
|
+
console.log('');
|
|
79
|
+
|
|
80
|
+
const answer = await ask(`${colors.gray}Chọn: ${colors.reset}`);
|
|
81
|
+
|
|
82
|
+
if (!answer) {
|
|
83
|
+
return options.filter(o => o.default).map(o => o.value);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const nums = answer.split(',').map(s => parseInt(s.trim())).filter(n => n >= 1 && n <= options.length);
|
|
87
|
+
return nums.map(n => options[n - 1].value);
|
|
85
88
|
}
|
|
86
89
|
|
|
87
90
|
function copyDirRecursive(src, dest) {
|
|
@@ -100,132 +103,234 @@ function copyDirRecursive(src, dest) {
|
|
|
100
103
|
}
|
|
101
104
|
}
|
|
102
105
|
|
|
106
|
+
function copyFile(src, dest) {
|
|
107
|
+
const destDir = path.dirname(dest);
|
|
108
|
+
if (!fs.existsSync(destDir)) {
|
|
109
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
110
|
+
}
|
|
111
|
+
fs.copyFileSync(src, dest);
|
|
112
|
+
}
|
|
113
|
+
|
|
103
114
|
async function init() {
|
|
104
115
|
console.log('');
|
|
105
116
|
log.info('╔═══════════════════════════════════════════════════════════╗');
|
|
106
|
-
log.info('║ 🚀 anchi-kit
|
|
117
|
+
log.info('║ 🚀 anchi-kit Smart Install ║');
|
|
107
118
|
log.info('╚═══════════════════════════════════════════════════════════╝');
|
|
108
119
|
console.log('');
|
|
109
120
|
|
|
110
121
|
const targetPath = process.cwd();
|
|
111
|
-
const kitPath = path.resolve(__dirname, '
|
|
122
|
+
const kitPath = path.resolve(__dirname, '../..');
|
|
112
123
|
|
|
113
124
|
log.gray(`📂 Target: ${targetPath}`);
|
|
125
|
+
|
|
126
|
+
// Step 1: Detect tech stack
|
|
114
127
|
console.log('');
|
|
128
|
+
log.warn('🔍 Detecting tech stack...');
|
|
129
|
+
const detected = detectTechStack(targetPath);
|
|
130
|
+
const detectedStr = formatDetectedStack(detected);
|
|
131
|
+
log.info(` ${detectedStr}`);
|
|
132
|
+
|
|
133
|
+
const suggestedSkills = suggestSkills(detected);
|
|
134
|
+
if (suggestedSkills.length > 0) {
|
|
135
|
+
log.success(` 💡 Suggested skills: ${suggestedSkills.join(', ')}`);
|
|
136
|
+
}
|
|
115
137
|
|
|
116
|
-
// Step
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
138
|
+
// Step 2: Choose profile
|
|
139
|
+
const profiles = getProfileList();
|
|
140
|
+
const profileOptions = profiles.map(p => ({
|
|
141
|
+
label: `${p.name}`,
|
|
142
|
+
description: p.description,
|
|
143
|
+
estimate: getProfileEstimate(p.key).formatted || '',
|
|
144
|
+
value: p.key,
|
|
145
|
+
}));
|
|
146
|
+
|
|
147
|
+
const selectedProfile = await askChoice('Chọn profile:', profileOptions);
|
|
148
|
+
const profile = getProfile(selectedProfile);
|
|
149
|
+
|
|
150
|
+
// Step 3: Customize (if not minimal or fullstack)
|
|
151
|
+
let finalConfig = { ...profile };
|
|
152
|
+
|
|
153
|
+
if (selectedProfile !== 'minimal' && selectedProfile !== 'fullstack') {
|
|
154
|
+
const customize = await ask(`\n${colors.yellow}Customize thêm? (y/N): ${colors.reset}`);
|
|
155
|
+
|
|
156
|
+
if (customize.toLowerCase() === 'y') {
|
|
157
|
+
// Skill packs
|
|
158
|
+
const packs = getSkillPackList();
|
|
159
|
+
const packOptions = packs.map(p => ({
|
|
160
|
+
label: `${p.name} (${p.skillCount} skills)`,
|
|
161
|
+
value: p.key,
|
|
162
|
+
default: false,
|
|
163
|
+
}));
|
|
164
|
+
|
|
165
|
+
const selectedPacks = await askMultiSelect('Thêm skill packs:', packOptions);
|
|
166
|
+
if (selectedPacks.length > 0) {
|
|
167
|
+
const extraSkills = resolveSkillPacks(selectedPacks);
|
|
168
|
+
finalConfig.skills = [...new Set([...finalConfig.skills, ...extraSkills])];
|
|
169
|
+
}
|
|
121
170
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
{ label: 'Docs (docs/) - Documentation templates', value: 'docs', default: true },
|
|
128
|
-
{ label: 'Presets (presets/) - Architecture presets', value: 'presets', default: false },
|
|
129
|
-
]);
|
|
171
|
+
// Include presets?
|
|
172
|
+
if (!finalConfig.includePresets) {
|
|
173
|
+
const includePresets = await ask(`\n${colors.yellow}Include presets folder? (y/N): ${colors.reset}`);
|
|
174
|
+
finalConfig.includePresets = includePresets.toLowerCase() === 'y';
|
|
175
|
+
}
|
|
130
176
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
177
|
+
// Include docs?
|
|
178
|
+
if (!finalConfig.includeDocs) {
|
|
179
|
+
const includeDocs = await ask(`${colors.yellow}Include docs folder? (y/N): ${colors.reset}`);
|
|
180
|
+
finalConfig.includeDocs = includeDocs.toLowerCase() === 'y';
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
137
184
|
|
|
138
|
-
// Step 4:
|
|
139
|
-
console.log('');
|
|
140
|
-
log.info('📋 Preview cài đặt:');
|
|
185
|
+
// Step 4: Size preview
|
|
141
186
|
console.log('');
|
|
187
|
+
log.warn('📊 Install Preview:');
|
|
188
|
+
const components = getComponentPaths(finalConfig, kitPath);
|
|
189
|
+
const sizeInfo = calculateInstallSize(kitPath, components);
|
|
142
190
|
|
|
143
|
-
|
|
191
|
+
console.log(` Files: ~${sizeInfo.files}`);
|
|
192
|
+
console.log(` Size: ~${sizeInfo.formatted}`);
|
|
193
|
+
console.log('');
|
|
194
|
+
log.info(' Components:');
|
|
144
195
|
|
|
145
|
-
if (
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
if (components.includes('agents')) {
|
|
150
|
-
filesToInstall.push('.cursor/agents/');
|
|
151
|
-
log.success(' ✓ .cursor/agents/');
|
|
152
|
-
}
|
|
153
|
-
if (components.includes('skills')) {
|
|
154
|
-
filesToInstall.push('.cursor/skills/');
|
|
155
|
-
log.success(' ✓ .cursor/skills/');
|
|
196
|
+
if (finalConfig.commands === 'all') {
|
|
197
|
+
log.success(' ✓ All commands');
|
|
198
|
+
} else {
|
|
199
|
+
log.success(` ✓ Commands: ${finalConfig.commands.join(', ')}`);
|
|
156
200
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
log.success(
|
|
201
|
+
|
|
202
|
+
if (finalConfig.agents === 'all' || (Array.isArray(finalConfig.agents) && finalConfig.agents.length > 0)) {
|
|
203
|
+
log.success(` ✓ Agents: ${finalConfig.agents === 'all' ? 'All' : finalConfig.agents.length + ' agents'}`);
|
|
160
204
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
log.success(
|
|
205
|
+
|
|
206
|
+
if (finalConfig.skills === 'all' || (Array.isArray(finalConfig.skills) && finalConfig.skills.length > 0)) {
|
|
207
|
+
log.success(` ✓ Skills: ${finalConfig.skills === 'all' ? 'All' : finalConfig.skills.length + ' skills'}`);
|
|
164
208
|
}
|
|
165
209
|
|
|
166
|
-
log.success(
|
|
167
|
-
log.success('
|
|
210
|
+
if (finalConfig.includePresets) log.success(' ✓ Presets');
|
|
211
|
+
if (finalConfig.includeDocs) log.success(' ✓ Docs');
|
|
168
212
|
|
|
213
|
+
// Step 5: Confirm
|
|
169
214
|
console.log('');
|
|
170
|
-
const confirm = await ask(`${colors.yellow}
|
|
215
|
+
const confirm = await ask(`${colors.yellow}Proceed with installation? (Y/n): ${colors.reset}`);
|
|
171
216
|
|
|
172
217
|
if (confirm.toLowerCase() === 'n') {
|
|
173
|
-
log.error('❌
|
|
218
|
+
log.error('❌ Cancelled.');
|
|
174
219
|
rl.close();
|
|
175
220
|
return;
|
|
176
221
|
}
|
|
177
222
|
|
|
178
|
-
// Step
|
|
223
|
+
// Step 6: Install
|
|
179
224
|
console.log('');
|
|
180
|
-
log.warn('📦
|
|
225
|
+
log.warn('📦 Installing...');
|
|
181
226
|
console.log('');
|
|
182
227
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
228
|
+
let installedFiles = 0;
|
|
229
|
+
|
|
230
|
+
// Install commands
|
|
231
|
+
const commandsPath = path.join(kitPath, '.cursor/commands');
|
|
232
|
+
const destCommandsPath = path.join(targetPath, '.cursor/commands');
|
|
233
|
+
|
|
234
|
+
if (finalConfig.commands === 'all') {
|
|
235
|
+
copyDirRecursive(commandsPath, destCommandsPath);
|
|
236
|
+
installedFiles += fs.readdirSync(commandsPath).length;
|
|
237
|
+
log.success(' ✅ Commands (all)');
|
|
238
|
+
} else {
|
|
239
|
+
for (const cmd of finalConfig.commands) {
|
|
240
|
+
const srcFile = path.join(commandsPath, `${cmd}.md`);
|
|
241
|
+
if (fs.existsSync(srcFile)) {
|
|
242
|
+
copyFile(srcFile, path.join(destCommandsPath, `${cmd}.md`));
|
|
243
|
+
installedFiles++;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
log.success(` ✅ Commands (${finalConfig.commands.length})`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Install agents
|
|
250
|
+
const agentsPath = path.join(kitPath, '.cursor/agents');
|
|
251
|
+
const destAgentsPath = path.join(targetPath, '.cursor/agents');
|
|
252
|
+
|
|
253
|
+
if (finalConfig.agents === 'all') {
|
|
254
|
+
copyDirRecursive(agentsPath, destAgentsPath);
|
|
255
|
+
log.success(' ✅ Agents (all)');
|
|
256
|
+
} else if (Array.isArray(finalConfig.agents) && finalConfig.agents.length > 0) {
|
|
257
|
+
for (const agent of finalConfig.agents) {
|
|
258
|
+
const srcFile = path.join(agentsPath, `${agent}.md`);
|
|
259
|
+
if (fs.existsSync(srcFile)) {
|
|
260
|
+
copyFile(srcFile, path.join(destAgentsPath, `${agent}.md`));
|
|
261
|
+
installedFiles++;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
log.success(` ✅ Agents (${finalConfig.agents.length})`);
|
|
265
|
+
}
|
|
191
266
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
267
|
+
// Install skills
|
|
268
|
+
const skillsPath = path.join(kitPath, '.cursor/skills');
|
|
269
|
+
const destSkillsPath = path.join(targetPath, '.cursor/skills');
|
|
270
|
+
|
|
271
|
+
if (finalConfig.skills === 'all') {
|
|
272
|
+
copyDirRecursive(skillsPath, destSkillsPath);
|
|
273
|
+
log.success(' ✅ Skills (all)');
|
|
274
|
+
} else if (Array.isArray(finalConfig.skills) && finalConfig.skills.length > 0) {
|
|
275
|
+
for (const skill of finalConfig.skills) {
|
|
276
|
+
const srcDir = path.join(skillsPath, skill);
|
|
277
|
+
if (fs.existsSync(srcDir)) {
|
|
278
|
+
copyDirRecursive(srcDir, path.join(destSkillsPath, skill));
|
|
279
|
+
installedFiles++;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
log.success(` ✅ Skills (${finalConfig.skills.length})`);
|
|
283
|
+
}
|
|
195
284
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
285
|
+
// Install extras
|
|
286
|
+
if (finalConfig.includePresets) {
|
|
287
|
+
const presetsPath = path.join(kitPath, 'presets');
|
|
288
|
+
if (fs.existsSync(presetsPath)) {
|
|
289
|
+
copyDirRecursive(presetsPath, path.join(targetPath, 'presets'));
|
|
290
|
+
log.success(' ✅ Presets');
|
|
199
291
|
}
|
|
200
292
|
}
|
|
201
293
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
294
|
+
if (finalConfig.includeDocs) {
|
|
295
|
+
const docsPath = path.join(kitPath, 'docs');
|
|
296
|
+
if (fs.existsSync(docsPath)) {
|
|
297
|
+
copyDirRecursive(docsPath, path.join(targetPath, 'docs'));
|
|
298
|
+
log.success(' ✅ Docs');
|
|
299
|
+
}
|
|
300
|
+
}
|
|
205
301
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
302
|
+
// Always include
|
|
303
|
+
const cursorRulesPath = path.join(kitPath, '.cursorrules');
|
|
304
|
+
if (fs.existsSync(cursorRulesPath)) {
|
|
305
|
+
fs.copyFileSync(cursorRulesPath, path.join(targetPath, '.cursorrules'));
|
|
306
|
+
log.success(' ✅ .cursorrules');
|
|
209
307
|
}
|
|
210
308
|
|
|
211
|
-
// Copy CURSOR.md
|
|
212
309
|
const cursorMdPath = path.join(kitPath, 'CURSOR.md');
|
|
213
310
|
if (fs.existsSync(cursorMdPath)) {
|
|
214
311
|
fs.copyFileSync(cursorMdPath, path.join(targetPath, 'CURSOR.md'));
|
|
215
|
-
log.success('
|
|
312
|
+
log.success(' ✅ CURSOR.md');
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Copy MODEL_COSTS.md
|
|
316
|
+
const modelCostsPath = path.join(kitPath, '.cursor/MODEL_COSTS.md');
|
|
317
|
+
if (fs.existsSync(modelCostsPath)) {
|
|
318
|
+
copyFile(modelCostsPath, path.join(targetPath, '.cursor/MODEL_COSTS.md'));
|
|
319
|
+
log.success(' ✅ MODEL_COSTS.md');
|
|
216
320
|
}
|
|
217
321
|
|
|
322
|
+
// Done
|
|
218
323
|
console.log('');
|
|
219
324
|
log.info('═══════════════════════════════════════════════════════════════');
|
|
220
|
-
log.success(' ✅ anchi-kit
|
|
325
|
+
log.success(' ✅ anchi-kit installed successfully!');
|
|
221
326
|
log.info('═══════════════════════════════════════════════════════════════');
|
|
222
327
|
console.log('');
|
|
223
|
-
log.warn('📌
|
|
328
|
+
log.warn('📌 Next steps:');
|
|
224
329
|
console.log('');
|
|
225
|
-
console.log(' 1.
|
|
330
|
+
console.log(' 1. Open Cursor and run:');
|
|
226
331
|
log.success(' /start');
|
|
227
332
|
console.log('');
|
|
228
|
-
console.log(' 2.
|
|
333
|
+
console.log(' 2. Start coding:');
|
|
229
334
|
log.success(' /plan "Feature description"');
|
|
230
335
|
console.log('');
|
|
231
336
|
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// =============================================================================
|
|
3
|
+
// anchi-kit Uninstall Command
|
|
4
|
+
// Usage: npx anchi-kit uninstall
|
|
5
|
+
// =============================================================================
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const readline = require('readline');
|
|
10
|
+
|
|
11
|
+
const colors = {
|
|
12
|
+
reset: '\x1b[0m',
|
|
13
|
+
red: '\x1b[31m',
|
|
14
|
+
green: '\x1b[32m',
|
|
15
|
+
yellow: '\x1b[33m',
|
|
16
|
+
cyan: '\x1b[36m',
|
|
17
|
+
gray: '\x1b[90m',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const log = {
|
|
21
|
+
info: (msg) => console.log(`${colors.cyan}${msg}${colors.reset}`),
|
|
22
|
+
success: (msg) => console.log(`${colors.green}${msg}${colors.reset}`),
|
|
23
|
+
warn: (msg) => console.log(`${colors.yellow}${msg}${colors.reset}`),
|
|
24
|
+
error: (msg) => console.log(`${colors.red}${msg}${colors.reset}`),
|
|
25
|
+
gray: (msg) => console.log(`${colors.gray}${msg}${colors.reset}`),
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const rl = readline.createInterface({
|
|
29
|
+
input: process.stdin,
|
|
30
|
+
output: process.stdout,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
function ask(question) {
|
|
34
|
+
return new Promise((resolve) => {
|
|
35
|
+
rl.question(question, (answer) => resolve(answer.trim()));
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function deleteRecursive(dir) {
|
|
40
|
+
if (fs.existsSync(dir)) {
|
|
41
|
+
fs.readdirSync(dir).forEach((file) => {
|
|
42
|
+
const curPath = path.join(dir, file);
|
|
43
|
+
if (fs.lstatSync(curPath).isDirectory()) {
|
|
44
|
+
deleteRecursive(curPath);
|
|
45
|
+
} else {
|
|
46
|
+
fs.unlinkSync(curPath);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
fs.rmdirSync(dir);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function uninstall() {
|
|
54
|
+
console.log('');
|
|
55
|
+
log.info('╔═══════════════════════════════════════════════════════════╗');
|
|
56
|
+
log.info('║ 🗑️ anchi-kit Uninstall ║');
|
|
57
|
+
log.info('╚═══════════════════════════════════════════════════════════╝');
|
|
58
|
+
console.log('');
|
|
59
|
+
|
|
60
|
+
const targetPath = process.cwd();
|
|
61
|
+
|
|
62
|
+
// Items to remove
|
|
63
|
+
const itemsToRemove = [
|
|
64
|
+
{ path: '.cursor/commands', type: 'dir', desc: 'Commands' },
|
|
65
|
+
{ path: '.cursor/agents', type: 'dir', desc: 'Agents' },
|
|
66
|
+
{ path: '.cursor/skills', type: 'dir', desc: 'Skills' },
|
|
67
|
+
{ path: '.cursor/MODEL_COSTS.md', type: 'file', desc: 'Model costs guide' },
|
|
68
|
+
{ path: 'presets', type: 'dir', desc: 'Presets' },
|
|
69
|
+
{ path: 'docs', type: 'dir', desc: 'Docs (templates)' },
|
|
70
|
+
{ path: '.cursorrules', type: 'file', desc: 'Cursor rules' },
|
|
71
|
+
{ path: 'CURSOR.md', type: 'file', desc: 'Cursor instructions' },
|
|
72
|
+
{ path: 'GEMINI.md', type: 'file', desc: 'Gemini instructions' },
|
|
73
|
+
{ path: 'ANTIGRAVITY.md', type: 'file', desc: 'Antigravity instructions' },
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
// Check what exists
|
|
77
|
+
const existingItems = itemsToRemove.filter(item => {
|
|
78
|
+
const fullPath = path.join(targetPath, item.path);
|
|
79
|
+
return fs.existsSync(fullPath);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (existingItems.length === 0) {
|
|
83
|
+
log.warn('⚠️ No anchi-kit files found in this directory.');
|
|
84
|
+
rl.close();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Show what will be removed
|
|
89
|
+
log.warn('📋 The following will be removed:');
|
|
90
|
+
console.log('');
|
|
91
|
+
|
|
92
|
+
existingItems.forEach(item => {
|
|
93
|
+
if (item.type === 'dir') {
|
|
94
|
+
log.error(` 📁 ${item.path}/`);
|
|
95
|
+
} else {
|
|
96
|
+
log.error(` 📄 ${item.path}`);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
console.log('');
|
|
101
|
+
log.warn('⚠️ This action cannot be undone!');
|
|
102
|
+
console.log('');
|
|
103
|
+
|
|
104
|
+
const confirm = await ask(`${colors.red}Type 'REMOVE' to confirm: ${colors.reset}`);
|
|
105
|
+
|
|
106
|
+
if (confirm !== 'REMOVE') {
|
|
107
|
+
log.info('❌ Cancelled.');
|
|
108
|
+
rl.close();
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
console.log('');
|
|
113
|
+
log.warn('🗑️ Removing...');
|
|
114
|
+
console.log('');
|
|
115
|
+
|
|
116
|
+
let removed = 0;
|
|
117
|
+
|
|
118
|
+
for (const item of existingItems) {
|
|
119
|
+
const fullPath = path.join(targetPath, item.path);
|
|
120
|
+
try {
|
|
121
|
+
if (item.type === 'dir') {
|
|
122
|
+
deleteRecursive(fullPath);
|
|
123
|
+
} else {
|
|
124
|
+
fs.unlinkSync(fullPath);
|
|
125
|
+
}
|
|
126
|
+
log.success(` ✅ Removed: ${item.path}`);
|
|
127
|
+
removed++;
|
|
128
|
+
} catch (e) {
|
|
129
|
+
log.error(` ❌ Failed: ${item.path} - ${e.message}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Clean up empty .cursor folder
|
|
134
|
+
const cursorDir = path.join(targetPath, '.cursor');
|
|
135
|
+
if (fs.existsSync(cursorDir)) {
|
|
136
|
+
const remaining = fs.readdirSync(cursorDir);
|
|
137
|
+
if (remaining.length === 0) {
|
|
138
|
+
fs.rmdirSync(cursorDir);
|
|
139
|
+
log.gray(' 🧹 Removed empty .cursor/');
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
console.log('');
|
|
144
|
+
log.info('═══════════════════════════════════════════════════════════════');
|
|
145
|
+
log.success(` ✅ Removed ${removed} items!`);
|
|
146
|
+
log.info('═══════════════════════════════════════════════════════════════');
|
|
147
|
+
console.log('');
|
|
148
|
+
|
|
149
|
+
rl.close();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
module.exports = { uninstall };
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Tech Stack Detector
|
|
3
|
+
// =============================================================================
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
function detectTechStack(targetPath) {
|
|
9
|
+
const detected = {
|
|
10
|
+
frameworks: [],
|
|
11
|
+
languages: [],
|
|
12
|
+
databases: [],
|
|
13
|
+
styling: [],
|
|
14
|
+
auth: [],
|
|
15
|
+
testing: [],
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// Check package.json
|
|
19
|
+
const pkgPath = path.join(targetPath, 'package.json');
|
|
20
|
+
if (fs.existsSync(pkgPath)) {
|
|
21
|
+
try {
|
|
22
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
23
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
24
|
+
|
|
25
|
+
// Frameworks
|
|
26
|
+
if (deps['next']) detected.frameworks.push('next');
|
|
27
|
+
if (deps['react']) detected.frameworks.push('react');
|
|
28
|
+
if (deps['vue']) detected.frameworks.push('vue');
|
|
29
|
+
if (deps['express']) detected.frameworks.push('express');
|
|
30
|
+
if (deps['fastify']) detected.frameworks.push('fastify');
|
|
31
|
+
if (deps['@nestjs/core']) detected.frameworks.push('nestjs');
|
|
32
|
+
|
|
33
|
+
// Databases
|
|
34
|
+
if (deps['prisma'] || deps['@prisma/client']) detected.databases.push('prisma');
|
|
35
|
+
if (deps['mongoose']) detected.databases.push('mongodb');
|
|
36
|
+
if (deps['pg']) detected.databases.push('postgresql');
|
|
37
|
+
if (deps['mysql2']) detected.databases.push('mysql');
|
|
38
|
+
if (deps['better-sqlite3']) detected.databases.push('sqlite');
|
|
39
|
+
|
|
40
|
+
// Styling
|
|
41
|
+
if (deps['tailwindcss']) detected.styling.push('tailwind');
|
|
42
|
+
if (deps['styled-components']) detected.styling.push('styled-components');
|
|
43
|
+
if (deps['@emotion/react']) detected.styling.push('emotion');
|
|
44
|
+
if (deps['sass']) detected.styling.push('sass');
|
|
45
|
+
|
|
46
|
+
// Auth
|
|
47
|
+
if (deps['next-auth']) detected.auth.push('next-auth');
|
|
48
|
+
if (deps['better-auth']) detected.auth.push('better-auth');
|
|
49
|
+
if (deps['passport']) detected.auth.push('passport');
|
|
50
|
+
|
|
51
|
+
// Testing
|
|
52
|
+
if (deps['jest']) detected.testing.push('jest');
|
|
53
|
+
if (deps['vitest']) detected.testing.push('vitest');
|
|
54
|
+
if (deps['playwright']) detected.testing.push('playwright');
|
|
55
|
+
|
|
56
|
+
// Languages
|
|
57
|
+
if (deps['typescript']) detected.languages.push('typescript');
|
|
58
|
+
} catch (e) {
|
|
59
|
+
// Ignore parse errors
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Check for config files
|
|
64
|
+
if (fs.existsSync(path.join(targetPath, 'tailwind.config.js')) ||
|
|
65
|
+
fs.existsSync(path.join(targetPath, 'tailwind.config.ts'))) {
|
|
66
|
+
if (!detected.styling.includes('tailwind')) detected.styling.push('tailwind');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (fs.existsSync(path.join(targetPath, 'prisma'))) {
|
|
70
|
+
if (!detected.databases.includes('prisma')) detected.databases.push('prisma');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (fs.existsSync(path.join(targetPath, 'tsconfig.json'))) {
|
|
74
|
+
if (!detected.languages.includes('typescript')) detected.languages.push('typescript');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return detected;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function suggestSkills(detected) {
|
|
81
|
+
const suggestions = new Set();
|
|
82
|
+
|
|
83
|
+
// Framework-based suggestions
|
|
84
|
+
if (detected.frameworks.includes('next') || detected.frameworks.includes('react')) {
|
|
85
|
+
suggestions.add('frontend-development');
|
|
86
|
+
suggestions.add('web-frameworks');
|
|
87
|
+
}
|
|
88
|
+
if (detected.frameworks.includes('express') || detected.frameworks.includes('fastify')) {
|
|
89
|
+
suggestions.add('backend-development');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Database suggestions
|
|
93
|
+
if (detected.databases.length > 0) {
|
|
94
|
+
suggestions.add('databases');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Styling suggestions
|
|
98
|
+
if (detected.styling.includes('tailwind')) {
|
|
99
|
+
suggestions.add('ui-styling');
|
|
100
|
+
suggestions.add('frontend-design');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Auth suggestions
|
|
104
|
+
if (detected.auth.includes('better-auth')) {
|
|
105
|
+
suggestions.add('better-auth');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Default suggestions if nothing detected
|
|
109
|
+
if (suggestions.size === 0) {
|
|
110
|
+
suggestions.add('frontend-development');
|
|
111
|
+
suggestions.add('planning');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return Array.from(suggestions);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function formatDetectedStack(detected) {
|
|
118
|
+
const parts = [];
|
|
119
|
+
|
|
120
|
+
if (detected.frameworks.length) {
|
|
121
|
+
parts.push(`Frameworks: ${detected.frameworks.join(', ')}`);
|
|
122
|
+
}
|
|
123
|
+
if (detected.databases.length) {
|
|
124
|
+
parts.push(`Database: ${detected.databases.join(', ')}`);
|
|
125
|
+
}
|
|
126
|
+
if (detected.styling.length) {
|
|
127
|
+
parts.push(`Styling: ${detected.styling.join(', ')}`);
|
|
128
|
+
}
|
|
129
|
+
if (detected.auth.length) {
|
|
130
|
+
parts.push(`Auth: ${detected.auth.join(', ')}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return parts.length > 0 ? parts.join(' | ') : 'No specific tech detected';
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
module.exports = {
|
|
137
|
+
detectTechStack,
|
|
138
|
+
suggestSkills,
|
|
139
|
+
formatDetectedStack,
|
|
140
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Role-based Profiles
|
|
3
|
+
// =============================================================================
|
|
4
|
+
|
|
5
|
+
const PROFILES = {
|
|
6
|
+
minimal: {
|
|
7
|
+
name: 'Minimal',
|
|
8
|
+
description: 'Core commands only (fastest install)',
|
|
9
|
+
commands: ['start', 'plan', 'cook', 'fix'],
|
|
10
|
+
agents: [],
|
|
11
|
+
skills: [],
|
|
12
|
+
includePresets: false,
|
|
13
|
+
includeDocs: false,
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
developer: {
|
|
17
|
+
name: 'Developer',
|
|
18
|
+
description: 'Daily coding (commands + basic agents)',
|
|
19
|
+
commands: ['start', 'plan', 'cook', 'fix', 'scout', 'generate'],
|
|
20
|
+
agents: ['planner', 'scout', 'fullstack-developer', 'debugger'],
|
|
21
|
+
skills: ['frontend-development', 'backend-development', 'databases'],
|
|
22
|
+
includePresets: false,
|
|
23
|
+
includeDocs: false,
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
ui_ux: {
|
|
27
|
+
name: 'UI/UX Designer',
|
|
28
|
+
description: 'Frontend & Design focus',
|
|
29
|
+
commands: ['start', 'plan', 'cook', 'design-ui', 'generate'],
|
|
30
|
+
agents: ['ui-ux-designer', 'design-system-architect', 'planner'],
|
|
31
|
+
skills: ['frontend-development', 'frontend-design', 'ui-styling', 'aesthetic'],
|
|
32
|
+
includePresets: false,
|
|
33
|
+
includeDocs: false,
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
architect: {
|
|
37
|
+
name: 'Architect',
|
|
38
|
+
description: 'System design & DDD',
|
|
39
|
+
commands: ['start', 'plan', 'cook', 'fix', 'design-domain', 'review'],
|
|
40
|
+
agents: ['planner', 'ddd-architect', 'code-reviewer', 'researcher'],
|
|
41
|
+
skills: ['ddd-modular-monolith', 'planning', 'backend-development', 'databases'],
|
|
42
|
+
includePresets: true,
|
|
43
|
+
includeDocs: true,
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
lead: {
|
|
47
|
+
name: 'Team Lead',
|
|
48
|
+
description: 'Code review & management',
|
|
49
|
+
commands: ['start', 'plan', 'cook', 'fix', 'review', 'scout', 'summary'],
|
|
50
|
+
agents: ['planner', 'code-reviewer', 'project-manager', 'docs-manager', 'git-manager'],
|
|
51
|
+
skills: ['code-review', 'planning', 'debugging'],
|
|
52
|
+
includePresets: false,
|
|
53
|
+
includeDocs: true,
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
fullstack: {
|
|
57
|
+
name: 'Full Stack',
|
|
58
|
+
description: 'Everything included',
|
|
59
|
+
commands: 'all',
|
|
60
|
+
agents: 'all',
|
|
61
|
+
skills: 'all',
|
|
62
|
+
includePresets: true,
|
|
63
|
+
includeDocs: true,
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
function getProfile(name) {
|
|
68
|
+
return PROFILES[name] || null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function getProfileList() {
|
|
72
|
+
return Object.entries(PROFILES).map(([key, profile]) => ({
|
|
73
|
+
key,
|
|
74
|
+
name: profile.name,
|
|
75
|
+
description: profile.description,
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function getProfileEstimate(profileKey) {
|
|
80
|
+
const profile = PROFILES[profileKey];
|
|
81
|
+
if (!profile) return null;
|
|
82
|
+
|
|
83
|
+
const estimates = {
|
|
84
|
+
minimal: { files: 5, size: '~50KB' },
|
|
85
|
+
developer: { files: 30, size: '~300KB' },
|
|
86
|
+
ui_ux: { files: 25, size: '~250KB' },
|
|
87
|
+
architect: { files: 50, size: '~500KB' },
|
|
88
|
+
lead: { files: 40, size: '~400KB' },
|
|
89
|
+
fullstack: { files: 400, size: '~5MB' },
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
return estimates[profileKey] || { files: '?', size: '?' };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
module.exports = {
|
|
96
|
+
PROFILES,
|
|
97
|
+
getProfile,
|
|
98
|
+
getProfileList,
|
|
99
|
+
getProfileEstimate,
|
|
100
|
+
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Size Calculator
|
|
3
|
+
// =============================================================================
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
function countFilesRecursive(dir) {
|
|
9
|
+
let count = 0;
|
|
10
|
+
let size = 0;
|
|
11
|
+
|
|
12
|
+
if (!fs.existsSync(dir)) return { count: 0, size: 0 };
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
16
|
+
for (const entry of entries) {
|
|
17
|
+
const fullPath = path.join(dir, entry.name);
|
|
18
|
+
if (entry.isDirectory()) {
|
|
19
|
+
const sub = countFilesRecursive(fullPath);
|
|
20
|
+
count += sub.count;
|
|
21
|
+
size += sub.size;
|
|
22
|
+
} else {
|
|
23
|
+
count++;
|
|
24
|
+
try {
|
|
25
|
+
const stats = fs.statSync(fullPath);
|
|
26
|
+
size += stats.size;
|
|
27
|
+
} catch (e) {
|
|
28
|
+
// Ignore
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
} catch (e) {
|
|
33
|
+
// Ignore
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return { count, size };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function formatSize(bytes) {
|
|
40
|
+
if (bytes < 1024) return `${bytes}B`;
|
|
41
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
42
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function calculateInstallSize(kitPath, components) {
|
|
46
|
+
let totalFiles = 0;
|
|
47
|
+
let totalSize = 0;
|
|
48
|
+
|
|
49
|
+
for (const comp of components) {
|
|
50
|
+
const compPath = path.join(kitPath, comp);
|
|
51
|
+
if (fs.existsSync(compPath)) {
|
|
52
|
+
const stats = fs.statSync(compPath);
|
|
53
|
+
if (stats.isDirectory()) {
|
|
54
|
+
const { count, size } = countFilesRecursive(compPath);
|
|
55
|
+
totalFiles += count;
|
|
56
|
+
totalSize += size;
|
|
57
|
+
} else {
|
|
58
|
+
totalFiles++;
|
|
59
|
+
totalSize += stats.size;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
files: totalFiles,
|
|
66
|
+
size: totalSize,
|
|
67
|
+
formatted: formatSize(totalSize),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function getComponentPaths(profile, kitPath) {
|
|
72
|
+
const components = [];
|
|
73
|
+
|
|
74
|
+
// Commands
|
|
75
|
+
if (profile.commands === 'all') {
|
|
76
|
+
components.push('.cursor/commands');
|
|
77
|
+
} else if (Array.isArray(profile.commands)) {
|
|
78
|
+
profile.commands.forEach(cmd => {
|
|
79
|
+
components.push(`.cursor/commands/${cmd}.md`);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Agents
|
|
84
|
+
if (profile.agents === 'all') {
|
|
85
|
+
components.push('.cursor/agents');
|
|
86
|
+
} else if (Array.isArray(profile.agents)) {
|
|
87
|
+
profile.agents.forEach(agent => {
|
|
88
|
+
components.push(`.cursor/agents/${agent}.md`);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Skills
|
|
93
|
+
if (profile.skills === 'all') {
|
|
94
|
+
components.push('.cursor/skills');
|
|
95
|
+
} else if (Array.isArray(profile.skills)) {
|
|
96
|
+
profile.skills.forEach(skill => {
|
|
97
|
+
components.push(`.cursor/skills/${skill}`);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Extras
|
|
102
|
+
if (profile.includePresets) {
|
|
103
|
+
components.push('presets');
|
|
104
|
+
}
|
|
105
|
+
if (profile.includeDocs) {
|
|
106
|
+
components.push('docs');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Always include
|
|
110
|
+
components.push('.cursorrules');
|
|
111
|
+
components.push('CURSOR.md');
|
|
112
|
+
|
|
113
|
+
return components;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
module.exports = {
|
|
117
|
+
countFilesRecursive,
|
|
118
|
+
formatSize,
|
|
119
|
+
calculateInstallSize,
|
|
120
|
+
getComponentPaths,
|
|
121
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Skill Packs (Bundled Skills)
|
|
3
|
+
// =============================================================================
|
|
4
|
+
|
|
5
|
+
const SKILL_PACKS = {
|
|
6
|
+
frontend: {
|
|
7
|
+
name: 'Frontend Pack',
|
|
8
|
+
description: 'UI development skills',
|
|
9
|
+
skills: [
|
|
10
|
+
'frontend-development',
|
|
11
|
+
'frontend-design',
|
|
12
|
+
'ui-styling',
|
|
13
|
+
'aesthetic',
|
|
14
|
+
],
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
backend: {
|
|
18
|
+
name: 'Backend Pack',
|
|
19
|
+
description: 'Server & database skills',
|
|
20
|
+
skills: [
|
|
21
|
+
'backend-development',
|
|
22
|
+
'databases',
|
|
23
|
+
'better-auth',
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
fullstack: {
|
|
28
|
+
name: 'Fullstack Pack',
|
|
29
|
+
description: 'Frontend + Backend combined',
|
|
30
|
+
skills: [
|
|
31
|
+
'frontend-development',
|
|
32
|
+
'frontend-design',
|
|
33
|
+
'ui-styling',
|
|
34
|
+
'backend-development',
|
|
35
|
+
'databases',
|
|
36
|
+
'better-auth',
|
|
37
|
+
'web-frameworks',
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
ddd: {
|
|
42
|
+
name: 'DDD Pack',
|
|
43
|
+
description: 'Domain-Driven Design skills',
|
|
44
|
+
skills: [
|
|
45
|
+
'ddd-modular-monolith',
|
|
46
|
+
'planning',
|
|
47
|
+
'debugging',
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
devops: {
|
|
52
|
+
name: 'DevOps Pack',
|
|
53
|
+
description: 'CI/CD & infrastructure',
|
|
54
|
+
skills: [
|
|
55
|
+
'devops',
|
|
56
|
+
'debugging',
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
ai: {
|
|
61
|
+
name: 'AI Tools Pack',
|
|
62
|
+
description: 'AI integration skills',
|
|
63
|
+
skills: [
|
|
64
|
+
'ai-multimodal',
|
|
65
|
+
'mcp-builder',
|
|
66
|
+
'mcp-management',
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
mobile: {
|
|
71
|
+
name: 'Mobile Pack',
|
|
72
|
+
description: 'Mobile development',
|
|
73
|
+
skills: [
|
|
74
|
+
'mobile-development',
|
|
75
|
+
'frontend-development',
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
function getSkillPack(name) {
|
|
81
|
+
return SKILL_PACKS[name] || null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function getSkillPackList() {
|
|
85
|
+
return Object.entries(SKILL_PACKS).map(([key, pack]) => ({
|
|
86
|
+
key,
|
|
87
|
+
name: pack.name,
|
|
88
|
+
description: pack.description,
|
|
89
|
+
skillCount: pack.skills.length,
|
|
90
|
+
}));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function resolveSkillPacks(packKeys) {
|
|
94
|
+
const skills = new Set();
|
|
95
|
+
for (const key of packKeys) {
|
|
96
|
+
const pack = SKILL_PACKS[key];
|
|
97
|
+
if (pack) {
|
|
98
|
+
pack.skills.forEach(s => skills.add(s));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return Array.from(skills);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = {
|
|
105
|
+
SKILL_PACKS,
|
|
106
|
+
getSkillPack,
|
|
107
|
+
getSkillPackList,
|
|
108
|
+
resolveSkillPacks,
|
|
109
|
+
};
|