@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 +41 -19
- package/cli/init.js +247 -123
- package/cli/update.js +8 -17
- package/cli/utils.js +110 -1
- package/package.json +4 -1
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
|
|
97
|
-
|
|
98
|
-
|
|
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
|
|
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
|
-
--
|
|
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
|
|
134
|
-
devkit init --
|
|
135
|
-
devkit
|
|
136
|
-
devkit
|
|
137
|
-
devkit
|
|
138
|
-
devkit
|
|
139
|
-
devkit
|
|
140
|
-
devkit
|
|
141
|
-
devkit
|
|
142
|
-
devkit
|
|
143
|
-
devkit
|
|
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
|
|
7
|
-
* -
|
|
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
|
-
* -
|
|
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
|
|
17
|
-
const { copyDir, getDirSize,
|
|
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
|
-
*
|
|
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
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
35
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
|
55
|
-
fs.mkdirSync(
|
|
82
|
+
// Create target directory
|
|
83
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
56
84
|
|
|
57
85
|
let totalFiles = 0;
|
|
58
86
|
const stats = {};
|
|
59
87
|
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
//
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
114
|
+
if (rulesCount > 0) {
|
|
115
|
+
stats.rules = rulesCount;
|
|
116
|
+
totalFiles += rulesCount;
|
|
117
|
+
}
|
|
98
118
|
}
|
|
99
119
|
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
105
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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,
|
|
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
|
-
//
|
|
146
|
+
// 5. Create devkit.json tracking file
|
|
131
147
|
const devkitConfig = {
|
|
132
148
|
version: VERSION,
|
|
133
|
-
|
|
134
|
-
|
|
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(
|
|
157
|
+
sizeKB: Math.round(getDirSize(targetDir) / 1024)
|
|
140
158
|
}
|
|
141
159
|
};
|
|
142
160
|
|
|
143
161
|
fs.writeFileSync(
|
|
144
|
-
path.join(
|
|
162
|
+
path.join(targetDir, 'devkit.json'),
|
|
145
163
|
JSON.stringify(devkitConfig, null, 2)
|
|
146
164
|
);
|
|
147
165
|
totalFiles++;
|
|
148
166
|
|
|
149
|
-
//
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
includeCoAuthoredBy: false
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
160
|
-
|
|
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
|
|
166
|
-
console.log(`
|
|
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
|
|
292
|
+
console.log('\n Restart your AI tool to use the new skills.\n');
|
|
180
293
|
|
|
181
294
|
return {
|
|
182
|
-
success:
|
|
295
|
+
success: successCount > 0,
|
|
296
|
+
tools: results,
|
|
183
297
|
detected: detectedTypes,
|
|
184
298
|
rules: rulesToInstall,
|
|
185
299
|
stats: {
|
|
186
300
|
files: totalFiles,
|
|
187
|
-
sizeKB:
|
|
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
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
329
|
+
if (removedCount === 0) {
|
|
330
|
+
console.log('\n No devkit installations found.\n');
|
|
331
|
+
return { success: false, reason: 'not_found' };
|
|
332
|
+
}
|
|
220
333
|
|
|
221
|
-
|
|
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
|
-
|
|
228
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
//
|
|
142
|
-
|
|
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.
|
|
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",
|