@paulduvall/claude-dev-toolkit 0.0.1-alpha.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.
@@ -0,0 +1,245 @@
1
+ const readline = require('readline');
2
+
3
+ /**
4
+ * Handles user interface interactions for the setup wizard
5
+ * Extracted from InteractiveSetupWizard for better separation of concerns
6
+ */
7
+ class SetupWizardUI {
8
+ constructor() {
9
+ this.rl = null;
10
+ }
11
+
12
+ /**
13
+ * Create readline interface
14
+ */
15
+ createInterface() {
16
+ this.rl = readline.createInterface({
17
+ input: process.stdin,
18
+ output: process.stdout
19
+ });
20
+ }
21
+
22
+ /**
23
+ * Close readline interface
24
+ */
25
+ closeInterface() {
26
+ if (this.rl) {
27
+ this.rl.close();
28
+ this.rl = null;
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Display welcome message
34
+ */
35
+ showWelcome() {
36
+ console.log('\nšŸš€ Claude Code Custom Commands Setup Wizard');
37
+ console.log('='.repeat(50));
38
+ console.log('Welcome to the interactive setup wizard!');
39
+ console.log('This will guide you through configuring your Claude Code toolkit.\n');
40
+ }
41
+
42
+ /**
43
+ * Display installation types menu
44
+ * @param {Array} installationTypes - Available installation types
45
+ */
46
+ showInstallationTypes(installationTypes) {
47
+ console.log('šŸ“¦ Installation Types:');
48
+ console.log('-'.repeat(25));
49
+ installationTypes.forEach(type => {
50
+ console.log(`${type.id}. ${type.name}`);
51
+ console.log(` ${type.description}`);
52
+ console.log('');
53
+ });
54
+ }
55
+
56
+ /**
57
+ * Display command categories
58
+ * @param {Object} commandCategories - Available command categories
59
+ */
60
+ showCommandCategories(commandCategories) {
61
+ console.log('\nšŸ“‹ Available Command Categories:');
62
+ console.log('-'.repeat(35));
63
+ Object.entries(commandCategories).forEach(([category, commands], index) => {
64
+ console.log(`${index + 1}. ${category.charAt(0).toUpperCase() + category.slice(1)} (${commands.length} commands)`);
65
+ console.log(` Commands: ${commands.join(', ')}`);
66
+ });
67
+ }
68
+
69
+ /**
70
+ * Display available hooks
71
+ * @param {Array} availableHooks - Available security hooks
72
+ */
73
+ showAvailableHooks(availableHooks) {
74
+ console.log('\nšŸ”’ Available Security Hooks:');
75
+ console.log('-'.repeat(30));
76
+ availableHooks.forEach((hook, index) => {
77
+ console.log(`${index + 1}. ${hook.name}`);
78
+ if (hook.description) {
79
+ console.log(` ${hook.description}`);
80
+ }
81
+ });
82
+ }
83
+
84
+ /**
85
+ * Display configuration templates
86
+ * @param {Array} templates - Available configuration templates
87
+ */
88
+ showConfigurationTemplates(templates) {
89
+ console.log('\nāš™ļø Configuration Templates:');
90
+ console.log('-'.repeat(28));
91
+ templates.forEach((template, index) => {
92
+ console.log(`${index + 1}. ${template.name}`);
93
+ console.log(` ${template.description}`);
94
+ });
95
+ }
96
+
97
+ /**
98
+ * Prompt user for input
99
+ * @param {string} question - Question to ask
100
+ * @returns {Promise<string>} User's response
101
+ */
102
+ prompt(question) {
103
+ return new Promise((resolve) => {
104
+ this.rl.question(question, (answer) => {
105
+ resolve(answer.trim());
106
+ });
107
+ });
108
+ }
109
+
110
+ /**
111
+ * Prompt for yes/no confirmation
112
+ * @param {string} question - Question to ask
113
+ * @param {boolean} defaultValue - Default value if user just presses enter
114
+ * @returns {Promise<boolean>} User's confirmation
115
+ */
116
+ async confirmPrompt(question, defaultValue = false) {
117
+ const defaultText = defaultValue ? '[Y/n]' : '[y/N]';
118
+ const answer = await this.prompt(`${question} ${defaultText}: `);
119
+
120
+ if (answer === '') {
121
+ return defaultValue;
122
+ }
123
+
124
+ return answer.toLowerCase().startsWith('y');
125
+ }
126
+
127
+ /**
128
+ * Prompt for numeric choice
129
+ * @param {string} question - Question to ask
130
+ * @param {number} min - Minimum valid number
131
+ * @param {number} max - Maximum valid number
132
+ * @returns {Promise<number>} User's choice
133
+ */
134
+ async numericPrompt(question, min = 1, max = 10) {
135
+ while (true) {
136
+ const answer = await this.prompt(question);
137
+ const num = parseInt(answer);
138
+
139
+ if (!isNaN(num) && num >= min && num <= max) {
140
+ return num;
141
+ }
142
+
143
+ console.log(`āŒ Please enter a number between ${min} and ${max}`);
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Prompt for multiple choices
149
+ * @param {string} question - Question to ask
150
+ * @param {Array} choices - Available choices
151
+ * @returns {Promise<Array<number>>} User's choices
152
+ */
153
+ async multipleChoicePrompt(question, choices) {
154
+ const answer = await this.prompt(`${question} (comma-separated numbers, or 'all'): `);
155
+
156
+ if (answer.toLowerCase() === 'all') {
157
+ return choices.map((_, index) => index + 1);
158
+ }
159
+
160
+ const selected = answer.split(',')
161
+ .map(s => parseInt(s.trim()))
162
+ .filter(n => !isNaN(n) && n >= 1 && n <= choices.length);
163
+
164
+ return selected.length > 0 ? selected : [1]; // Default to first choice
165
+ }
166
+
167
+ /**
168
+ * Display setup summary
169
+ * @param {Object} config - Setup configuration
170
+ */
171
+ showSetupSummary(config) {
172
+ console.log('\nšŸ“‹ Setup Summary:');
173
+ console.log('='.repeat(20));
174
+ console.log(`Installation Type: ${config.installationType}`);
175
+ console.log(`Commands: ${config.selectedCommands.length} selected`);
176
+ console.log(`Security Hooks: ${config.securityHooks ? 'Yes' : 'No'}`);
177
+ console.log(`Configuration Template: ${config.configTemplate || 'None'}`);
178
+ console.log('');
179
+ }
180
+
181
+ /**
182
+ * Display progress indicator
183
+ * @param {string} step - Current step
184
+ * @param {number} current - Current step number
185
+ * @param {number} total - Total steps
186
+ */
187
+ showProgress(step, current, total) {
188
+ const progress = 'ā–ˆ'.repeat(Math.floor((current / total) * 20));
189
+ const empty = 'ā–‘'.repeat(20 - progress.length);
190
+ console.log(`\n[${progress}${empty}] Step ${current}/${total}: ${step}`);
191
+ }
192
+
193
+ /**
194
+ * Display completion message
195
+ * @param {Object} result - Setup result
196
+ */
197
+ showCompletion(result) {
198
+ console.log('\nšŸŽ‰ Setup Complete!');
199
+ console.log('='.repeat(20));
200
+
201
+ if (result.success) {
202
+ console.log('āœ… Claude Code toolkit has been successfully configured!');
203
+ console.log(`šŸ“¦ Installed ${result.installedCount} commands`);
204
+ if (result.hooksInstalled) {
205
+ console.log('šŸ”’ Security hooks installed');
206
+ }
207
+ if (result.configApplied) {
208
+ console.log('āš™ļø Configuration template applied');
209
+ }
210
+ } else {
211
+ console.log('āŒ Setup encountered issues:');
212
+ result.errors.forEach(error => console.log(` • ${error}`));
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Display error message
218
+ * @param {string} message - Error message
219
+ * @param {Error} error - Optional error object
220
+ */
221
+ showError(message, error = null) {
222
+ console.log(`\nāŒ Error: ${message}`);
223
+ if (error && error.message) {
224
+ console.log(` Details: ${error.message}`);
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Display warning message
230
+ * @param {string} message - Warning message
231
+ */
232
+ showWarning(message) {
233
+ console.log(`\nāš ļø Warning: ${message}`);
234
+ }
235
+
236
+ /**
237
+ * Display info message
238
+ * @param {string} message - Info message
239
+ */
240
+ showInfo(message) {
241
+ console.log(`\nā„¹ļø ${message}`);
242
+ }
243
+ }
244
+
245
+ module.exports = SetupWizardUI;
@@ -0,0 +1,355 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Interactive Setup Wizard for REQ-007
5
+ * GREEN phase - Minimal implementation to pass tests
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const readline = require('readline');
11
+
12
+ // Import extracted classes for better separation of concerns
13
+ const SetupWizardUI = require('./setup-wizard-ui');
14
+ const InstallationConfiguration = require('./installation-configuration');
15
+ const CommandSelector = require('./command-selector');
16
+
17
+ class InteractiveSetupWizard {
18
+ constructor(packageRoot) {
19
+ this.packageRoot = packageRoot;
20
+
21
+ // Import config module for template application
22
+ const config = require('./config');
23
+ this.applyConfigurationTemplate = config.applyConfigurationTemplate;
24
+
25
+ // Import hook installer for security hooks
26
+ const hookInstaller = require('./hook-installer');
27
+ this.installSecurityHooks = hookInstaller.installSecurityHooks;
28
+ this.getAvailableHooks = hookInstaller.getAvailableHooks;
29
+
30
+ // Initialize extracted components
31
+ this.ui = new SetupWizardUI();
32
+ this.config = new InstallationConfiguration(packageRoot);
33
+ this.commandSelector = new CommandSelector();
34
+
35
+ // Legacy data for backward compatibility
36
+ this.securityHooks = [
37
+ {
38
+ id: 1,
39
+ name: 'credential-protection',
40
+ description: 'Prevents credential exposure in commits',
41
+ file: 'prevent-credential-exposure.sh'
42
+ },
43
+ {
44
+ id: 2,
45
+ name: 'file-logger',
46
+ description: 'Logs file operations for audit trail',
47
+ file: 'file-logger.sh'
48
+ }
49
+ ];
50
+ }
51
+
52
+ validateEnvironment() {
53
+ try {
54
+ // Check write permissions
55
+ const testFile = path.join(this.packageRoot, '.test');
56
+ fs.writeFileSync(testFile, 'test');
57
+ fs.unlinkSync(testFile);
58
+
59
+ return {
60
+ valid: true,
61
+ message: 'Environment validation passed'
62
+ };
63
+ } catch (error) {
64
+ return {
65
+ valid: false,
66
+ message: `Environment validation failed: ${error.message}`
67
+ };
68
+ }
69
+ }
70
+
71
+ getInstallationTypes() {
72
+ return this.config.getInstallationTypes();
73
+ }
74
+
75
+ selectInstallationType(optionId) {
76
+ const selected = this.config.getInstallationTypeById(optionId);
77
+ if (selected) {
78
+ return {
79
+ type: selected.name.toLowerCase().split(' ')[0],
80
+ description: selected.description,
81
+ commands: selected.commands
82
+ };
83
+ }
84
+ return null;
85
+ }
86
+
87
+ getCommandCategories() {
88
+ return this.commandSelector.getCommandCategories();
89
+ }
90
+
91
+ selectCommandSets(categories) {
92
+ return this.commandSelector.selectCommandSets(categories);
93
+ }
94
+
95
+ getSecurityHooks() {
96
+ return this.securityHooks;
97
+ }
98
+
99
+ selectSecurityHooks(hookIds) {
100
+ const selected = hookIds.map(id =>
101
+ this.securityHooks.find(h => h.id === id)
102
+ ).filter(Boolean);
103
+
104
+ return {
105
+ enabled: selected.length > 0,
106
+ selected: selected.map(h => h.name)
107
+ };
108
+ }
109
+
110
+ getConfigurationTemplates() {
111
+ return this.config.getConfigurationTemplates();
112
+ }
113
+
114
+ selectConfigurationTemplate(templateName) {
115
+ const templates = this.config.getConfigurationTemplates();
116
+ const template = templates.find(t => t.name === templateName);
117
+
118
+ if (template) {
119
+ return {
120
+ template: template.name,
121
+ file: template.filename,
122
+ description: template.description
123
+ };
124
+ }
125
+ return null;
126
+ }
127
+
128
+ runNonInteractiveSetup() {
129
+ const defaultConfig = {
130
+ installationType: 'standard',
131
+ commandSets: ['development', 'planning'],
132
+ securityHooks: true,
133
+ selectedHooks: ['credential-protection'],
134
+ template: 'basic'
135
+ };
136
+
137
+ this.saveConfiguration(defaultConfig);
138
+
139
+ return {
140
+ completed: true,
141
+ configuration: defaultConfig
142
+ };
143
+ }
144
+
145
+ async runNonInteractiveSetupAsync() {
146
+ const defaultConfig = {
147
+ installationType: 'standard',
148
+ commandSets: ['development', 'planning'],
149
+ securityHooks: true,
150
+ selectedHooks: ['credential-protection'],
151
+ template: 'basic'
152
+ };
153
+
154
+ await this.saveConfigurationAsync(defaultConfig);
155
+
156
+ return {
157
+ completed: true,
158
+ configuration: defaultConfig
159
+ };
160
+ }
161
+
162
+ saveConfiguration(configData) {
163
+ const enhancedConfig = {
164
+ timestamp: new Date().toISOString(),
165
+ version: '1.0.0',
166
+ ...configData
167
+ };
168
+
169
+ const success = this.config.saveConfiguration(enhancedConfig);
170
+ return {
171
+ saved: success,
172
+ file: success ? this.config.getConfigurationPath() : null,
173
+ error: success ? null : 'Failed to save configuration'
174
+ };
175
+ }
176
+
177
+ async saveConfigurationAsync(configData) {
178
+ const enhancedConfig = {
179
+ timestamp: new Date().toISOString(),
180
+ version: '1.0.0',
181
+ ...configData
182
+ };
183
+
184
+ const success = await this.config.saveConfigurationAsync(enhancedConfig);
185
+ return {
186
+ saved: success,
187
+ file: success ? this.config.getConfigurationPath() : null,
188
+ error: success ? null : 'Failed to save configuration'
189
+ };
190
+ }
191
+
192
+ loadConfiguration() {
193
+ const configData = this.config.loadConfiguration();
194
+ if (configData) {
195
+ return {
196
+ found: true,
197
+ config: configData
198
+ };
199
+ }
200
+ return {
201
+ found: false
202
+ };
203
+ }
204
+
205
+ async loadConfigurationAsync() {
206
+ const configData = await this.config.loadConfigurationAsync();
207
+ if (configData) {
208
+ return {
209
+ found: true,
210
+ config: configData
211
+ };
212
+ }
213
+ return {
214
+ found: false
215
+ };
216
+ }
217
+
218
+ applyPreset(presetName) {
219
+ return this.commandSelector.applyPreset(presetName);
220
+ }
221
+
222
+ async runInteractiveSetup() {
223
+ const rl = readline.createInterface({
224
+ input: process.stdin,
225
+ output: process.stdout
226
+ });
227
+
228
+ const question = (prompt) => new Promise((resolve) => {
229
+ rl.question(prompt, resolve);
230
+ });
231
+
232
+ console.log('\nšŸš€ Claude Dev Toolkit Interactive Setup Wizard');
233
+ console.log('=' .repeat(50));
234
+
235
+ const config = {};
236
+
237
+ try {
238
+ // Installation type
239
+ console.log('\nšŸ“¦ Installation Type:');
240
+ this.installationTypes.forEach(type => {
241
+ console.log(`${type.id}. ${type.name}`);
242
+ console.log(` ${type.description}`);
243
+ });
244
+
245
+ const typeChoice = await question('\nSelect installation type (1-3): ');
246
+ const selectedType = this.selectInstallationType(parseInt(typeChoice));
247
+ if (selectedType) {
248
+ config.installationType = selectedType.type;
249
+ }
250
+
251
+ // Command sets
252
+ console.log('\nšŸ› ļø Command Sets:');
253
+ const categories = Object.keys(this.commandCategories);
254
+ categories.forEach((cat, i) => {
255
+ console.log(`${i + 1}. ${cat} (${this.commandCategories[cat].length} commands)`);
256
+ });
257
+
258
+ const setChoice = await question('\nSelect command sets (comma-separated numbers): ');
259
+ const selectedIndices = setChoice.split(',').map(s => parseInt(s.trim()) - 1);
260
+ const selectedSets = selectedIndices.map(i => categories[i]).filter(Boolean);
261
+ config.commandSets = selectedSets;
262
+
263
+ // Security hooks
264
+ const enableHooks = await question('\nšŸ”’ Enable security hooks? (y/n): ');
265
+ if (enableHooks.toLowerCase() === 'y') {
266
+ console.log('\nAvailable hooks:');
267
+ this.securityHooks.forEach(hook => {
268
+ console.log(`${hook.id}. ${hook.name}`);
269
+ console.log(` ${hook.description}`);
270
+ });
271
+
272
+ const hookChoice = await question('\nSelect hooks (comma-separated numbers): ');
273
+ const hookIds = hookChoice.split(',').map(h => parseInt(h.trim()));
274
+ const selectedHooks = this.selectSecurityHooks(hookIds);
275
+ config.securityHooks = selectedHooks.enabled;
276
+ config.selectedHooks = selectedHooks.selected;
277
+ } else {
278
+ config.securityHooks = false;
279
+ config.selectedHooks = [];
280
+ }
281
+
282
+ // Configuration template
283
+ console.log('\nāš™ļø Configuration Templates:');
284
+ this.configurationTemplates.forEach(template => {
285
+ console.log(`${template.id}. ${template.name}`);
286
+ console.log(` ${template.description}`);
287
+ });
288
+
289
+ const templateChoice = await question('\nSelect template (1-3): ');
290
+ const templateId = parseInt(templateChoice);
291
+ const selectedTemplate = this.configurationTemplates.find(t => t.id === templateId);
292
+ if (selectedTemplate) {
293
+ config.template = selectedTemplate.name;
294
+ }
295
+
296
+ // Save configuration
297
+ await this.saveConfigurationAsync(config);
298
+
299
+ // Apply selected configuration template (REQ-009 integration)
300
+ if (selectedTemplate) {
301
+ const templatesDir = path.join(this.packageRoot, 'templates');
302
+ const templatePath = path.join(templatesDir, selectedTemplate.filename);
303
+ const settingsPath = path.join(require('os').homedir(), '.claude', 'settings.json');
304
+
305
+ console.log(`\nšŸ“‹ Applying configuration template: ${selectedTemplate.name}`);
306
+ const applied = this.applyConfigurationTemplate(templatePath, settingsPath);
307
+ if (applied) {
308
+ console.log(`āœ… Template applied to: ${settingsPath}`);
309
+ config.templateApplied = true;
310
+ config.settingsPath = settingsPath;
311
+ } else {
312
+ console.log('āš ļø Template application failed, but setup will continue');
313
+ config.templateApplied = false;
314
+ }
315
+ }
316
+
317
+ console.log('\nāœ… Setup completed successfully!');
318
+ console.log(`Configuration saved to: ${this.configFile}`);
319
+
320
+ rl.close();
321
+
322
+ return {
323
+ completed: true,
324
+ configuration: config
325
+ };
326
+
327
+ } catch (error) {
328
+ rl.close();
329
+ return {
330
+ completed: false,
331
+ error: error.message
332
+ };
333
+ }
334
+ }
335
+ }
336
+
337
+ // Support for PostInstaller integration
338
+ class PostInstaller {
339
+ constructor() {
340
+ this.packageRoot = path.join(require('os').homedir(), '.claude');
341
+ }
342
+
343
+ runSetupWizard(options = {}) {
344
+ if (options.skipSetup) {
345
+ return { skipped: true };
346
+ }
347
+
348
+ const wizard = new InteractiveSetupWizard(this.packageRoot);
349
+ return wizard.runNonInteractiveSetup();
350
+ }
351
+ }
352
+
353
+ // Export both classes
354
+ module.exports = InteractiveSetupWizard;
355
+ module.exports.PostInstaller = PostInstaller;