@ngxtm/devkit 3.5.0 → 3.6.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/cli/index.js CHANGED
@@ -41,6 +41,9 @@ function parseArgs(args) {
41
41
  help: false,
42
42
  installed: false,
43
43
  ruleArgs: [],
44
+ // New v3.6 options
45
+ all: false,
46
+ tools: [],
44
47
  // Legacy options (deprecated)
45
48
  tool: null,
46
49
  minimal: false,
@@ -60,12 +63,18 @@ function parseArgs(args) {
60
63
  options.update = true;
61
64
  } else if (arg === '--clean' || arg === '-c') {
62
65
  options.clean = true;
66
+ } else if (arg === '--no-clean') {
67
+ options.clean = false;
63
68
  } else if (arg === '--installed' || arg === '-i') {
64
69
  options.installed = true;
70
+ } else if (arg === '--all' || arg === '-a') {
71
+ options.all = true;
65
72
  } else if (arg === '--status' || arg === '-s') {
66
73
  options.command = 'status';
67
74
  } else if (arg.startsWith('--path=')) {
68
75
  options.path = arg.split('=')[1];
76
+ } else if (arg.startsWith('--tools=')) {
77
+ options.tools = arg.split('=')[1].split(',').map(t => t.trim());
69
78
  } else if (arg.startsWith('--')) {
70
79
  // Handle legacy options for backwards compatibility
71
80
  if (arg === '--minimal' || arg === '-m') options.minimal = true;
@@ -93,12 +102,12 @@ USAGE:
93
102
  devkit <command> [options]
94
103
 
95
104
  COMMANDS:
96
- init Initialize devkit in current project (.claude/ folder)
97
- Auto-detects tech stack and installs relevant rules only.
98
- This is the PRIMARY command - use this for new projects.
105
+ init Initialize devkit in current project
106
+ Shows interactive tool selection menu.
107
+ Auto-detects tech stack and installs relevant rules.
99
108
 
100
109
  update Update existing installation
101
- Re-detects project type and updates rules accordingly.
110
+ Re-detects project, updates commands, and cleans old rules.
102
111
 
103
112
  detect Show detected technologies for current project
104
113
  Useful to see what devkit will install.
@@ -124,23 +133,34 @@ COMMANDS:
124
133
 
125
134
  OPTIONS:
126
135
  --force, -f Force overwrite existing installation
127
- --clean, -c Remove rules for technologies no longer detected (with update)
136
+ --all, -a Install for all supported tools (skip menu)
137
+ --tools=LIST Install for specific tools (comma-separated)
138
+ Example: --tools=claude,cursor
139
+ --no-clean Skip removing rules for technologies no longer detected
128
140
  --installed, -i Show only installed rules (with rules command)
129
141
  --path=DIR Specify project directory (default: current directory)
130
142
  --help, -h Show this help
131
143
 
144
+ SUPPORTED TOOLS:
145
+ claude Claude Code
146
+ cursor Cursor
147
+ copilot GitHub Copilot
148
+ gemini Gemini CLI
149
+
132
150
  EXAMPLES:
133
- devkit init # Initialize in current project
134
- devkit init --force # Overwrite existing installation
135
- devkit update # Update and re-detect technologies
136
- devkit update --clean # Update and remove old rules
137
- devkit detect # Show what would be detected
138
- devkit rules # List all available rules
139
- devkit rules --installed # Show installed rules
140
- devkit add golang docker # Add golang and docker rules
141
- devkit remove flutter # Remove flutter rule
142
- devkit status # Show current installation
143
- devkit uninstall # Remove from current project
151
+ devkit init # Interactive tool selection
152
+ devkit init --all # Install for all tools
153
+ devkit init --tools=claude,cursor # Install for specific tools
154
+ devkit init --force # Overwrite existing installation
155
+ devkit update # Full update (commands + clean old rules)
156
+ devkit update --no-clean # Update without removing old rules
157
+ devkit detect # Show what would be detected
158
+ devkit rules # List all available rules
159
+ devkit rules --installed # Show installed rules
160
+ devkit add golang docker # Add golang and docker rules
161
+ devkit remove flutter # Remove flutter rule
162
+ devkit status # Show current installation
163
+ devkit uninstall # Remove from current project
144
164
 
145
165
  HOW IT WORKS:
146
166
  1. devkit init analyzes your project files:
@@ -181,13 +201,15 @@ This auto-detects your tech stack and installs only relevant rules.
181
201
 
182
202
  // Command handlers
183
203
  const commands = {
184
- // Primary command - per-project init
185
- init: (options) => {
204
+ // Primary command - per-project init (now async with tool selection)
205
+ init: async (options) => {
186
206
  const projectPath = validatePath(options.path) || process.cwd();
187
207
  return initProject({
188
208
  path: projectPath,
189
209
  force: options.force,
190
- update: options.update
210
+ update: options.update,
211
+ all: options.all,
212
+ tools: options.tools
191
213
  });
192
214
  },
193
215
 
package/cli/init.js CHANGED
@@ -3,167 +3,280 @@
3
3
  /**
4
4
  * Devkit Init - Per-Project Installation
5
5
  *
6
- * Installs devkit to the current project's .claude/ directory with:
7
- * - Merged commands (from both agent-assistant and claudekit)
6
+ * Installs devkit to the current project with support for multiple AI tools:
7
+ * - Claude Code, Cursor, GitHub Copilot, Gemini CLI
8
+ * - Interactive tool selection with auto-detection
8
9
  * - Tech-specific rules (based on project detection)
9
- * - Skills index (for on-demand loading)
10
- * - Essential hooks
10
+ * - Merged commands and essential hooks
11
11
  */
12
12
 
13
13
  const fs = require('fs');
14
14
  const path = require('path');
15
15
 
16
- const { detectProjectType, getRulesForTypes, printDetectionResults } = require('./detect');
17
- const { copyDir, getDirSize, validatePath } = require('./utils');
16
+ const { detectProjectType, getRulesForTypes } = require('./detect');
17
+ const { copyDir, getDirSize, detectInstalledTools, TOOLS } = require('./utils');
18
18
 
19
19
  const VERSION = require('../package.json').version;
20
20
  const PACKAGE_ROOT = path.join(__dirname, '..');
21
21
 
22
22
  /**
23
- * Initialize devkit in a project directory
23
+ * Show interactive tool selection menu
24
+ * @param {Object} detectedTools - Results from detectInstalledTools()
25
+ * @returns {Promise<string[]>} - Array of selected tool ids
24
26
  */
25
- function initProject(options = {}) {
26
- const projectDir = options.path || process.cwd();
27
- const claudeDir = path.join(projectDir, '.claude');
28
- const isUpdate = options.update || false;
29
-
30
- console.log('\n' + '='.repeat(60));
31
- console.log(' DEVKIT v' + VERSION + (isUpdate ? ' - UPDATE' : ' - INIT'));
32
- console.log('='.repeat(60));
27
+ async function showToolSelectionMenu(detectedTools) {
28
+ // Dynamic import for inquirer (ES module)
29
+ const inquirer = (await import('inquirer')).default;
30
+
31
+ console.log('\n Detecting installed AI tools...\n');
32
+
33
+ const choices = Object.entries(TOOLS).map(([id, tool]) => {
34
+ const detected = detectedTools[id]?.detected;
35
+ const status = detected ? '(detected)' : '(not detected)';
36
+ return {
37
+ name: `${tool.name} ${status}`,
38
+ value: id,
39
+ checked: detected // Pre-select detected tools
40
+ };
41
+ });
42
+
43
+ const { selectedTools } = await inquirer.prompt([
44
+ {
45
+ type: 'checkbox',
46
+ name: 'selectedTools',
47
+ message: 'Select AI tools to install devkit for:',
48
+ choices,
49
+ validate: (answer) => {
50
+ if (answer.length === 0) {
51
+ return 'Please select at least one tool.';
52
+ }
53
+ return true;
54
+ }
55
+ }
56
+ ]);
33
57
 
34
- // Check if .claude already exists
35
- if (fs.existsSync(claudeDir) && !isUpdate && !options.force) {
36
- console.log(`\n .claude/ folder already exists.`);
37
- console.log(' Use --force to overwrite or --update to update.\n');
38
- return { success: false, reason: 'exists' };
39
- }
58
+ return selectedTools;
59
+ }
40
60
 
41
- // 1. Detect project type
42
- console.log('\n Detecting project type...');
43
- const detectedTypes = detectProjectType(projectDir);
44
- const rulesToInstall = getRulesForTypes(detectedTypes);
61
+ /**
62
+ * Install devkit for a single tool
63
+ * @param {string} toolId - Tool identifier
64
+ * @param {Object} tool - Tool configuration
65
+ * @param {string} projectDir - Project directory
66
+ * @param {Object} options - Install options
67
+ * @returns {Object} - Installation result
68
+ */
69
+ function installForTool(toolId, tool, projectDir, options = {}) {
70
+ const targetDir = path.join(projectDir, tool.projectPath);
71
+ const isUpdate = options.update || false;
45
72
 
46
- if (detectedTypes.length > 0) {
47
- console.log(` Detected: ${detectedTypes.join(', ')}`);
48
- console.log(` Rules: ${rulesToInstall.join(', ')}`);
49
- } else {
50
- console.log(' No specific technology detected.');
51
- console.log(' Installing base commands only.');
73
+ // Check if already exists
74
+ if (fs.existsSync(targetDir) && !isUpdate && !options.force) {
75
+ return {
76
+ success: false,
77
+ reason: 'exists',
78
+ message: `${tool.projectPath}/ already exists`
79
+ };
52
80
  }
53
81
 
54
- // Create .claude directory
55
- fs.mkdirSync(claudeDir, { recursive: true });
82
+ // Create target directory
83
+ fs.mkdirSync(targetDir, { recursive: true });
56
84
 
57
85
  let totalFiles = 0;
58
86
  const stats = {};
59
87
 
60
- // 2. Install merged commands
61
- console.log('\n Installing components...');
62
- const mergedCommandsDir = path.join(PACKAGE_ROOT, 'merged-commands');
63
- const commandsDir = path.join(claudeDir, 'commands');
64
-
65
- if (fs.existsSync(mergedCommandsDir)) {
66
- const count = copyDir(mergedCommandsDir, commandsDir);
67
- stats.commands = count;
68
- totalFiles += count;
69
- console.log(` Commands: ${count} files`);
70
- } else {
71
- // Fallback to commands-claudekit if merged not available
72
- const fallbackDir = path.join(PACKAGE_ROOT, 'commands-claudekit');
73
- if (fs.existsSync(fallbackDir)) {
74
- const count = copyDir(fallbackDir, commandsDir);
88
+ // 1. Install commands (if tool supports it)
89
+ if (tool.commandsPath) {
90
+ const mergedCommandsDir = path.join(PACKAGE_ROOT, 'merged-commands');
91
+ const commandsDir = path.join(targetDir, tool.commandsPath);
92
+
93
+ if (fs.existsSync(mergedCommandsDir)) {
94
+ const count = copyDir(mergedCommandsDir, commandsDir);
75
95
  stats.commands = count;
76
96
  totalFiles += count;
77
- console.log(` Commands (claudekit): ${count} files`);
78
97
  }
79
98
  }
80
99
 
81
- // 3. Install tech-specific rules
82
- const rulesDir = path.join(claudeDir, 'rules');
83
- let rulesCount = 0;
84
-
85
- for (const ruleType of rulesToInstall) {
86
- const srcRulesDir = path.join(PACKAGE_ROOT, 'templates', ruleType, 'rules');
87
- if (fs.existsSync(srcRulesDir)) {
88
- const destRulesDir = path.join(rulesDir, ruleType);
89
- const count = copyDir(srcRulesDir, destRulesDir);
90
- rulesCount += count;
100
+ // 2. Install rules
101
+ if (tool.rulesPath && options.rules && options.rules.length > 0) {
102
+ const rulesDir = path.join(targetDir, tool.rulesPath);
103
+ let rulesCount = 0;
104
+
105
+ for (const ruleType of options.rules) {
106
+ const srcRulesDir = path.join(PACKAGE_ROOT, 'templates', ruleType, 'rules');
107
+ if (fs.existsSync(srcRulesDir)) {
108
+ const destRulesDir = path.join(rulesDir, ruleType);
109
+ const count = copyDir(srcRulesDir, destRulesDir);
110
+ rulesCount += count;
111
+ }
91
112
  }
92
- }
93
113
 
94
- if (rulesCount > 0) {
95
- stats.rules = rulesCount;
96
- totalFiles += rulesCount;
97
- console.log(` Rules: ${rulesCount} files (${rulesToInstall.join(', ')})`);
114
+ if (rulesCount > 0) {
115
+ stats.rules = rulesCount;
116
+ totalFiles += rulesCount;
117
+ }
98
118
  }
99
119
 
100
- // 4. Install essential hooks
101
- const srcHooksDir = path.join(PACKAGE_ROOT, 'templates', 'base', 'hooks');
102
- const hooksDir = path.join(claudeDir, 'hooks');
120
+ // 3. Install hooks (if tool supports it)
121
+ if (tool.supportsHooks && tool.hooksPath) {
122
+ const srcHooksDir = path.join(PACKAGE_ROOT, 'templates', 'base', 'hooks');
123
+ const hooksDir = path.join(targetDir, tool.hooksPath);
103
124
 
104
- if (fs.existsSync(srcHooksDir)) {
105
- const count = copyDir(srcHooksDir, hooksDir);
106
- stats.hooks = count;
107
- totalFiles += count;
108
- console.log(` Hooks: ${count} files`);
109
- } else {
110
- // Fallback to main hooks directory (essential only)
111
- const fallbackHooks = path.join(PACKAGE_ROOT, 'hooks');
112
- if (fs.existsSync(fallbackHooks)) {
113
- const count = copyDir(fallbackHooks, hooksDir);
125
+ if (fs.existsSync(srcHooksDir)) {
126
+ const count = copyDir(srcHooksDir, hooksDir);
114
127
  stats.hooks = count;
115
128
  totalFiles += count;
116
- console.log(` Hooks: ${count} files`);
129
+ } else {
130
+ const fallbackHooks = path.join(PACKAGE_ROOT, 'hooks');
131
+ if (fs.existsSync(fallbackHooks)) {
132
+ const count = copyDir(fallbackHooks, hooksDir);
133
+ stats.hooks = count;
134
+ totalFiles += count;
135
+ }
117
136
  }
118
137
  }
119
138
 
120
- // 5. Install skills index
139
+ // 4. Install skills index
121
140
  const skillsIndexSrc = path.join(PACKAGE_ROOT, 'skills-index.json');
122
- const skillsIndexDest = path.join(claudeDir, 'skills-index.json');
123
-
124
141
  if (fs.existsSync(skillsIndexSrc)) {
125
- fs.copyFileSync(skillsIndexSrc, skillsIndexDest);
142
+ fs.copyFileSync(skillsIndexSrc, path.join(targetDir, 'skills-index.json'));
126
143
  totalFiles++;
127
- console.log(` Skills Index: 1 file`);
128
144
  }
129
145
 
130
- // 6. Create devkit.json tracking file
146
+ // 5. Create devkit.json tracking file
131
147
  const devkitConfig = {
132
148
  version: VERSION,
133
- detectedTypes: detectedTypes,
134
- installedRules: rulesToInstall,
149
+ tool: toolId,
150
+ toolName: tool.name,
151
+ detectedTypes: options.detectedTypes || [],
152
+ installedRules: options.rules || [],
135
153
  installedAt: new Date().toISOString(),
136
154
  updatedAt: isUpdate ? new Date().toISOString() : null,
137
155
  stats: {
138
156
  totalFiles: totalFiles,
139
- sizeKB: Math.round(getDirSize(claudeDir) / 1024)
157
+ sizeKB: Math.round(getDirSize(targetDir) / 1024)
140
158
  }
141
159
  };
142
160
 
143
161
  fs.writeFileSync(
144
- path.join(claudeDir, 'devkit.json'),
162
+ path.join(targetDir, 'devkit.json'),
145
163
  JSON.stringify(devkitConfig, null, 2)
146
164
  );
147
165
  totalFiles++;
148
166
 
149
- // 7. Create settings.json if not exists
150
- const settingsPath = path.join(claudeDir, 'settings.json');
151
- if (!fs.existsSync(settingsPath)) {
152
- const settings = {
153
- includeCoAuthoredBy: false
154
- };
155
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
156
- totalFiles++;
167
+ // 6. Create settings.json if not exists (for tools that use it)
168
+ if (toolId === 'claude') {
169
+ const settingsPath = path.join(targetDir, 'settings.json');
170
+ if (!fs.existsSync(settingsPath)) {
171
+ const settings = { includeCoAuthoredBy: false };
172
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
173
+ totalFiles++;
174
+ }
157
175
  }
158
176
 
159
- // Calculate total size
160
- const totalSizeKB = Math.round(getDirSize(claudeDir) / 1024);
177
+ return {
178
+ success: true,
179
+ tool: toolId,
180
+ toolName: tool.name,
181
+ path: targetDir,
182
+ stats: {
183
+ files: totalFiles,
184
+ sizeKB: Math.round(getDirSize(targetDir) / 1024),
185
+ ...stats
186
+ }
187
+ };
188
+ }
189
+
190
+ /**
191
+ * Initialize devkit in a project directory
192
+ * @param {Object} options - Installation options
193
+ */
194
+ async function initProject(options = {}) {
195
+ const projectDir = options.path || process.cwd();
196
+ const isUpdate = options.update || false;
197
+
198
+ console.log('\n' + '='.repeat(60));
199
+ console.log(' DEVKIT v' + VERSION + (isUpdate ? ' - UPDATE' : ' - INIT'));
200
+ console.log('='.repeat(60));
201
+
202
+ // Determine which tools to install
203
+ let selectedTools = [];
204
+
205
+ if (options.all) {
206
+ // --all flag: install for all tools
207
+ selectedTools = Object.keys(TOOLS);
208
+ console.log('\n Installing for all tools...');
209
+ } else if (options.tools && options.tools.length > 0) {
210
+ // Specific tools via --tools flag
211
+ selectedTools = options.tools;
212
+ } else {
213
+ // Interactive mode: show selection menu
214
+ const detectedTools = detectInstalledTools();
215
+ selectedTools = await showToolSelectionMenu(detectedTools);
216
+ }
217
+
218
+ if (selectedTools.length === 0) {
219
+ console.log('\n No tools selected. Exiting.\n');
220
+ return { success: false, reason: 'no_selection' };
221
+ }
222
+
223
+ // Detect project type
224
+ console.log('\n Detecting project type...');
225
+ const detectedTypes = detectProjectType(projectDir);
226
+ const rulesToInstall = getRulesForTypes(detectedTypes);
227
+
228
+ if (detectedTypes.length > 0) {
229
+ console.log(` Detected: ${detectedTypes.join(', ')}`);
230
+ console.log(` Rules: ${rulesToInstall.join(', ')}`);
231
+ } else {
232
+ console.log(' No specific technology detected.');
233
+ console.log(' Installing base commands only.');
234
+ }
235
+
236
+ // Install for each selected tool
237
+ console.log('\n Installing components...');
238
+ const results = [];
239
+
240
+ for (const toolId of selectedTools) {
241
+ const tool = TOOLS[toolId];
242
+ if (!tool) {
243
+ console.log(` [!] Unknown tool: ${toolId}`);
244
+ continue;
245
+ }
246
+
247
+ const result = installForTool(toolId, tool, projectDir, {
248
+ ...options,
249
+ detectedTypes,
250
+ rules: rulesToInstall
251
+ });
252
+
253
+ if (result.success) {
254
+ console.log(` [+] ${tool.name}: ${result.stats.files} files (${result.stats.sizeKB} KB)`);
255
+ } else {
256
+ console.log(` [-] ${tool.name}: ${result.message || result.reason}`);
257
+ }
258
+
259
+ results.push(result);
260
+ }
261
+
262
+ // Summary
263
+ const successCount = results.filter(r => r.success).length;
264
+ const totalFiles = results.reduce((sum, r) => sum + (r.stats?.files || 0), 0);
265
+ const totalSize = results.reduce((sum, r) => sum + (r.stats?.sizeKB || 0), 0);
161
266
 
162
267
  console.log('\n' + '='.repeat(60));
163
268
  console.log(' INSTALLATION COMPLETE');
164
269
  console.log('='.repeat(60));
165
- console.log(`\n Total: ${totalFiles} files (${totalSizeKB} KB)`);
166
- console.log(` Location: ${claudeDir}`);
270
+ console.log(`\n Tools: ${successCount}/${selectedTools.length} installed`);
271
+ console.log(` Total: ${totalFiles} files (${totalSize} KB)`);
272
+
273
+ // Show installed locations
274
+ console.log('\n Installed to:');
275
+ for (const result of results) {
276
+ if (result.success) {
277
+ console.log(` - ${result.path}`);
278
+ }
279
+ }
167
280
 
168
281
  console.log('\n Available commands:');
169
282
  console.log(' /plan - Plan implementation');
@@ -176,15 +289,16 @@ function initProject(options = {}) {
176
289
  console.log(`\n Tech-specific rules loaded for: ${detectedTypes.join(', ')}`);
177
290
  }
178
291
 
179
- console.log('\n Restart Claude Code to use the new skills.\n');
292
+ console.log('\n Restart your AI tool to use the new skills.\n');
180
293
 
181
294
  return {
182
- success: true,
295
+ success: successCount > 0,
296
+ tools: results,
183
297
  detected: detectedTypes,
184
298
  rules: rulesToInstall,
185
299
  stats: {
186
300
  files: totalFiles,
187
- sizeKB: totalSizeKB
301
+ sizeKB: totalSize
188
302
  }
189
303
  };
190
304
  }
@@ -194,52 +308,62 @@ function initProject(options = {}) {
194
308
  */
195
309
  function uninstallProject(options = {}) {
196
310
  const projectDir = options.path || process.cwd();
197
- const claudeDir = path.join(projectDir, '.claude');
198
311
 
199
312
  console.log('\n' + '='.repeat(60));
200
313
  console.log(' DEVKIT - UNINSTALL');
201
314
  console.log('='.repeat(60));
202
315
 
203
- if (!fs.existsSync(claudeDir)) {
204
- console.log('\n No .claude/ folder found.\n');
205
- return { success: false, reason: 'not_found' };
206
- }
316
+ let removedCount = 0;
317
+
318
+ for (const [toolId, tool] of Object.entries(TOOLS)) {
319
+ const targetDir = path.join(projectDir, tool.projectPath);
320
+ const devkitConfig = path.join(targetDir, 'devkit.json');
207
321
 
208
- // Check if it's a devkit installation
209
- const devkitConfig = path.join(claudeDir, 'devkit.json');
210
- if (!fs.existsSync(devkitConfig)) {
211
- console.log('\n .claude/ exists but is not a devkit installation.');
212
- console.log(' Remove manually if needed.\n');
213
- return { success: false, reason: 'not_devkit' };
322
+ if (fs.existsSync(devkitConfig)) {
323
+ fs.rmSync(targetDir, { recursive: true, force: true });
324
+ console.log(` Removed: ${targetDir}`);
325
+ removedCount++;
326
+ }
214
327
  }
215
328
 
216
- // Remove the directory
217
- fs.rmSync(claudeDir, { recursive: true, force: true });
218
- console.log(`\n Removed: ${claudeDir}`);
219
- console.log(' Devkit uninstalled successfully.\n');
329
+ if (removedCount === 0) {
330
+ console.log('\n No devkit installations found.\n');
331
+ return { success: false, reason: 'not_found' };
332
+ }
220
333
 
221
- return { success: true };
334
+ console.log(`\n Uninstalled ${removedCount} tool(s) successfully.\n`);
335
+ return { success: true, removed: removedCount };
222
336
  }
223
337
 
224
338
  module.exports = {
225
339
  initProject,
226
340
  uninstallProject,
227
- copyDir,
228
- getDirSize
341
+ installForTool,
342
+ showToolSelectionMenu
229
343
  };
230
344
 
231
345
  // Run if called directly
232
346
  if (require.main === module) {
233
347
  const args = process.argv.slice(2);
348
+
349
+ // Parse --tools=claude,cursor format
350
+ const toolsArg = args.find(a => a.startsWith('--tools='));
351
+ const tools = toolsArg ? toolsArg.split('=')[1].split(',') : [];
352
+
234
353
  const options = {
235
354
  force: args.includes('--force') || args.includes('-f'),
236
355
  update: args.includes('--update') || args.includes('-u'),
237
- path: args.find(a => !a.startsWith('-')) || process.cwd()
356
+ all: args.includes('--all') || args.includes('-a'),
357
+ tools: tools,
358
+ path: args.find(a => !a.startsWith('-') && !a.includes('=')) || process.cwd()
238
359
  };
239
360
 
240
361
  if (args.includes('--uninstall')) {
241
362
  uninstallProject(options);
242
363
  } else {
243
- initProject(options);
364
+ initProject(options).catch(err => {
365
+ console.error('Error:', err.message);
366
+ process.exit(1);
367
+ });
244
368
  }
245
369
  }
package/cli/update.js CHANGED
@@ -99,8 +99,8 @@ function updateProject(options = {}) {
99
99
  console.log(' No changes in detected technologies.');
100
100
  }
101
101
 
102
- // Update rules if needed
103
- if (added.length > 0 || options.force) {
102
+ // Update rules if needed (always update by default)
103
+ if (added.length > 0 || options.force !== false) {
104
104
  const newRules = getRulesForTypes(added);
105
105
  const rulesDir = path.join(claudeDir, 'rules');
106
106
 
@@ -116,8 +116,8 @@ function updateProject(options = {}) {
116
116
  }
117
117
  }
118
118
 
119
- // Remove old rules if needed and user agrees
120
- if (removed.length > 0 && options.clean) {
119
+ // Remove old rules (enabled by default, use --no-clean to skip)
120
+ if (removed.length > 0 && options.clean !== false) {
121
121
  const rulesDir = path.join(claudeDir, 'rules');
122
122
 
123
123
  console.log('\n Removing old rules...');
@@ -131,26 +131,17 @@ function updateProject(options = {}) {
131
131
  }
132
132
  }
133
133
 
134
- // Update commands if --force
135
- if (options.force) {
134
+ // Update commands (enabled by default)
135
+ if (options.force !== false) {
136
136
  console.log('\n Updating commands...');
137
137
  const mergedCommandsDir = path.join(PACKAGE_ROOT, 'merged-commands');
138
138
  const commandsDir = path.join(claudeDir, 'commands');
139
139
 
140
140
  if (fs.existsSync(mergedCommandsDir)) {
141
- // Backup existing
142
- const backupDir = path.join(claudeDir, 'commands.backup');
143
- if (fs.existsSync(commandsDir)) {
144
- fs.renameSync(commandsDir, backupDir);
145
- }
146
-
141
+ // Copy directly - copyDir will overwrite existing files
142
+ // No need for backup/rename which fails on Windows when files are open
147
143
  const count = copyDir(mergedCommandsDir, commandsDir);
148
144
  console.log(` Commands: ${count} files`);
149
-
150
- // Remove backup
151
- if (fs.existsSync(backupDir)) {
152
- fs.rmSync(backupDir, { recursive: true, force: true });
153
- }
154
145
  }
155
146
  }
156
147
 
package/cli/utils.js CHANGED
@@ -6,6 +6,112 @@
6
6
 
7
7
  const fs = require('fs');
8
8
  const path = require('path');
9
+ const os = require('os');
10
+ const { execSync } = require('child_process');
11
+
12
+ const HOME = os.homedir();
13
+
14
+ /**
15
+ * Supported AI tools configuration
16
+ */
17
+ const TOOLS = {
18
+ 'claude': {
19
+ id: 'claude',
20
+ name: 'Claude Code',
21
+ basePath: path.join(HOME, '.claude'),
22
+ projectPath: '.claude',
23
+ skillsPath: 'skills',
24
+ rulesPath: 'rules',
25
+ hooksPath: 'hooks',
26
+ commandsPath: 'commands',
27
+ supportsHooks: true,
28
+ configFile: 'CLAUDE.md',
29
+ detectCmd: 'claude --version',
30
+ detectFolder: path.join(HOME, '.claude')
31
+ },
32
+ 'cursor': {
33
+ id: 'cursor',
34
+ name: 'Cursor',
35
+ basePath: path.join(HOME, '.cursor'),
36
+ projectPath: '.cursor',
37
+ skillsPath: 'skills',
38
+ rulesPath: 'rules',
39
+ hooksPath: 'hooks',
40
+ commandsPath: 'commands',
41
+ supportsHooks: false,
42
+ configFile: 'CURSOR.md',
43
+ detectCmd: null,
44
+ detectFolder: path.join(HOME, '.cursor')
45
+ },
46
+ 'copilot': {
47
+ id: 'copilot',
48
+ name: 'GitHub Copilot',
49
+ basePath: path.join(HOME, '.copilot'),
50
+ projectPath: '.github',
51
+ skillsPath: 'skills',
52
+ rulesPath: 'rules',
53
+ hooksPath: null,
54
+ commandsPath: null,
55
+ supportsHooks: false,
56
+ configFile: null,
57
+ detectCmd: 'gh copilot --version',
58
+ detectFolder: path.join(HOME, '.copilot')
59
+ },
60
+ 'gemini': {
61
+ id: 'gemini',
62
+ name: 'Gemini CLI',
63
+ basePath: path.join(HOME, '.gemini'),
64
+ projectPath: '.gemini',
65
+ skillsPath: 'skills',
66
+ rulesPath: 'rules',
67
+ hooksPath: null,
68
+ commandsPath: null,
69
+ supportsHooks: false,
70
+ configFile: 'GEMINI.md',
71
+ detectCmd: 'gemini --version',
72
+ detectFolder: path.join(HOME, '.gemini')
73
+ }
74
+ };
75
+
76
+ /**
77
+ * Detect which AI tools are installed on the system
78
+ * @returns {Object} - Object with tool ids as keys and detection status
79
+ */
80
+ function detectInstalledTools() {
81
+ const results = {};
82
+
83
+ for (const [toolId, tool] of Object.entries(TOOLS)) {
84
+ let detected = false;
85
+ let method = null;
86
+
87
+ // Try command detection first
88
+ if (tool.detectCmd) {
89
+ try {
90
+ execSync(tool.detectCmd, { stdio: 'pipe' });
91
+ detected = true;
92
+ method = 'cli';
93
+ } catch (e) {
94
+ // Command not found
95
+ }
96
+ }
97
+
98
+ // Fallback to folder detection
99
+ if (!detected && tool.detectFolder) {
100
+ if (fs.existsSync(tool.detectFolder)) {
101
+ detected = true;
102
+ method = 'folder';
103
+ }
104
+ }
105
+
106
+ results[toolId] = {
107
+ ...tool,
108
+ detected,
109
+ method
110
+ };
111
+ }
112
+
113
+ return results;
114
+ }
9
115
 
10
116
  /**
11
117
  * Copy directory recursively
@@ -191,5 +297,8 @@ module.exports = {
191
297
  getDirSize,
192
298
  parseJsonFile,
193
299
  validatePath,
194
- getAllEntries
300
+ getAllEntries,
301
+ detectInstalledTools,
302
+ TOOLS,
303
+ HOME
195
304
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ngxtm/devkit",
3
- "version": "3.5.0",
3
+ "version": "3.6.1",
4
4
  "description": "Per-project AI skills with smart tech detection - lightweight and context-optimized",
5
5
  "main": "cli/index.js",
6
6
  "bin": {
@@ -47,6 +47,9 @@
47
47
  "type": "git",
48
48
  "url": "git+https://github.com/ngxtm/devkit.git"
49
49
  },
50
+ "dependencies": {
51
+ "inquirer": "^9.2.12"
52
+ },
50
53
  "devDependencies": {
51
54
  "@semantic-release/changelog": "^6.0.3",
52
55
  "@semantic-release/commit-analyzer": "^11.1.0",