anchi-kit 1.0.3 → 2.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anchi-kit",
3
- "version": "1.0.3",
3
+ "version": "2.0.0",
4
4
  "description": "Cursor AI toolkit - Install into any existing project. Includes commands, agents, skills, and architecture presets.",
5
5
  "main": "src/cli.js",
6
6
  "bin": {
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ả components)');
274
- log.success(' npx anchi-kit init Cài đặt tương tác (chọn components)');
275
- log.success(' npx anchi-kit update Kiểm tra cập nhật phiên bản');
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');
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  // =============================================================================
3
- // anchi-kit Init Command (Interactive Wizard)
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
- return new Promise(async (resolve) => {
43
- console.log('');
44
- log.bold(question);
45
- options.forEach((opt, i) => {
46
- console.log(` ${colors.cyan}${i + 1}${colors.reset}) ${opt.label}`);
47
- });
48
- console.log('');
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
- function askMultiSelect(question, options) {
63
- return new Promise(async (resolve) => {
64
- console.log('');
65
- log.bold(question);
66
- log.gray(' (Nhập số cách nhau bởi dấu phẩy, VD: 1,2,3)');
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
- const nums = answer.split(',').map(s => parseInt(s.trim())).filter(n => n >= 1 && n <= options.length);
83
- resolve(nums.map(n => options[n - 1].value));
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 Interactive Setup ║');
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 1: Project Type
117
- const projectType = await askChoice('Dự án của bạn là:', [
118
- { label: 'Dự án sẵn - Đã có code, muốn thêm anchi-kit', value: 'existing' },
119
- { label: 'Dự án mới - Bắt đầu từ đầu', value: 'new' },
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
- // Step 2: Components to install
123
- const components = await askMultiSelect('Chọn components muốn cài đặt:', [
124
- { label: 'Commands (.cursor/commands/) - Slash commands cho Cursor', value: 'commands', default: true },
125
- { label: 'Agents (.cursor/agents/) - AI agent definitions', value: 'agents', default: true },
126
- { label: 'Skills (.cursor/skills/) - Knowledge modules', value: 'skills', default: true },
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
- // Step 3: Preset selection
132
- const preset = await askChoice('Chọn architecture preset:', [
133
- { label: '🚀 Rapid - Demo, MVP, hackathon (< 2 tuần)', value: 'rapid' },
134
- { label: '🏢 Professional - Sản phẩm thương mại (1-6 tháng)', value: 'professional' },
135
- { label: '🏛️ Enterprise - Dự án lớn, DDD (> 6 tháng)', value: 'enterprise' },
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: Preview
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
- const filesToInstall = [];
191
+ console.log(` Files: ~${sizeInfo.files}`);
192
+ console.log(` Size: ~${sizeInfo.formatted}`);
193
+ console.log('');
194
+ log.info(' Components:');
144
195
 
145
- if (components.includes('commands')) {
146
- filesToInstall.push('.cursor/commands/');
147
- log.success(' ✓ .cursor/commands/');
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
- if (components.includes('docs')) {
158
- filesToInstall.push('docs/');
159
- log.success(' docs/');
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
- if (components.includes('presets')) {
162
- filesToInstall.push('presets/');
163
- log.success(' presets/');
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(` .cursorrules (${preset} preset)`);
167
- log.success(' CURSOR.md');
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}Tiếp tục cài đặt? (Y/n): ${colors.reset}`);
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('❌ Đã hủy.');
218
+ log.error('❌ Cancelled.');
174
219
  rl.close();
175
220
  return;
176
221
  }
177
222
 
178
- // Step 5: Install
223
+ // Step 6: Install
179
224
  console.log('');
180
- log.warn('📦 Đang cài đặt...');
225
+ log.warn('📦 Installing...');
181
226
  console.log('');
182
227
 
183
- // Install components
184
- const componentMap = {
185
- commands: '.cursor/commands',
186
- agents: '.cursor/agents',
187
- skills: '.cursor/skills',
188
- docs: 'docs',
189
- presets: 'presets',
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
- for (const comp of components) {
193
- const sourcePath = path.join(kitPath, componentMap[comp]);
194
- const destPath = path.join(targetPath, componentMap[comp]);
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
- if (fs.existsSync(sourcePath)) {
197
- copyDirRecursive(sourcePath, destPath);
198
- log.success(` ✅ ${componentMap[comp]}/`);
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
- // Copy preset .cursorrules
203
- const presetRulesPath = path.join(kitPath, `presets/${preset === 'rapid' ? 'rapid-mvp' : preset}/.cursorrules`);
204
- const destRulesPath = path.join(targetPath, '.cursorrules');
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
- if (fs.existsSync(presetRulesPath)) {
207
- fs.copyFileSync(presetRulesPath, destRulesPath);
208
- log.success(' ✅ .cursorrules');
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(' ✅ CURSOR.md');
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 đã được cài đặt thành công!');
325
+ log.success(' ✅ anchi-kit installed successfully!');
221
326
  log.info('═══════════════════════════════════════════════════════════════');
222
327
  console.log('');
223
- log.warn('📌 Bước tiếp theo:');
328
+ log.warn('📌 Next steps:');
224
329
  console.log('');
225
- console.log(' 1. Mở Cursor IDE và chạy:');
330
+ console.log(' 1. Open Cursor and run:');
226
331
  log.success(' /start');
227
332
  console.log('');
228
- console.log(' 2. Bắt đầu code:');
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
+ };