@paulduvall/claude-dev-toolkit 0.0.1-alpha.7 → 0.0.1-alpha.8

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.
Files changed (34) hide show
  1. package/README.md +9 -9
  2. package/bin/claude-commands +161 -6
  3. package/hooks/lib/argument-parser.sh +0 -0
  4. package/hooks/lib/config-constants.sh +0 -0
  5. package/hooks/lib/context-manager.sh +0 -0
  6. package/hooks/lib/error-handler.sh +0 -0
  7. package/hooks/lib/execution-engine.sh +0 -0
  8. package/hooks/lib/file-utils.sh +0 -0
  9. package/hooks/lib/subagent-discovery.sh +0 -0
  10. package/hooks/lib/subagent-validator.sh +0 -0
  11. package/lib/backup-restore-command.js +140 -0
  12. package/lib/base/base-command.js +252 -0
  13. package/lib/base/command-result.js +184 -0
  14. package/lib/config/constants.js +255 -0
  15. package/lib/config.js +48 -6
  16. package/lib/configure-command.js +428 -0
  17. package/lib/dependency-validator.js +64 -5
  18. package/lib/installation-instruction-generator-backup.js +579 -0
  19. package/lib/installation-instruction-generator.js +213 -495
  20. package/lib/installer.js +134 -52
  21. package/lib/services/backup-list-service.js +226 -0
  22. package/lib/services/backup-service.js +230 -0
  23. package/lib/services/command-installer-service.js +217 -0
  24. package/lib/services/logger-service.js +201 -0
  25. package/lib/services/package-manager-service.js +319 -0
  26. package/lib/services/platform-instruction-service.js +294 -0
  27. package/lib/services/recovery-instruction-service.js +348 -0
  28. package/lib/services/restore-service.js +221 -0
  29. package/lib/setup-command.js +309 -0
  30. package/lib/utils/claude-path-config.js +184 -0
  31. package/lib/utils/file-system-utils.js +152 -0
  32. package/lib/utils.js +8 -4
  33. package/lib/verify-command.js +430 -0
  34. package/package.json +1 -1
package/README.md CHANGED
@@ -174,8 +174,8 @@ npm run lint # Code linting
174
174
  - **Configuration Tests**: Template and setup validation
175
175
 
176
176
  ### Architecture
177
- - **Symlink Consolidation**: Single source of truth with root directory
178
- - **JavaScript-Based**: Migrated from Python for better Node.js integration
177
+ - **Self-Contained Package**: No dependencies on repository cloning
178
+ - **JavaScript-Based**: Native Node.js integration with comprehensive testing
179
179
  - **Modular Design**: Separate installer, config, and validation modules
180
180
  - **Cross-Platform**: Works on macOS, Linux, and Windows
181
181
 
@@ -212,13 +212,13 @@ npm run validate # Validate package
212
212
 
213
213
  ### Validation Commands
214
214
  ```bash
215
- # Repository validation (from main repo)
216
- ./validate-commands.sh # JavaScript-based validation
217
- ./verify-setup.sh # Complete setup verification
218
-
219
215
  # Package validation
220
216
  npm run validate # Package structure validation
221
217
  npm test # Comprehensive test suite
218
+
219
+ # CLI validation
220
+ claude-commands verify # Complete setup verification
221
+ claude-commands list # Check installed commands
222
222
  ```
223
223
 
224
224
  ## šŸ“š Documentation
@@ -252,8 +252,8 @@ npm test
252
252
  ```
253
253
 
254
254
  ### Adding Commands
255
- 1. Create command files in root `slash-commands/active/` or `slash-commands/experiments/`
256
- 2. Commands automatically sync to NPM package via symlinks
255
+ 1. Create command files in repository `slash-commands/active/` or `slash-commands/experiments/`
256
+ 2. Commands are included in NPM package through build process
257
257
  3. Validate with `npm run test:commands`
258
258
  4. Follow existing patterns and security guidelines
259
259
 
@@ -264,7 +264,7 @@ npm test
264
264
 
265
265
  ## šŸ”„ Recent Updates
266
266
 
267
- ### Version 0.0.1-alpha.2
267
+ ### Version 0.0.1-alpha.7
268
268
  - āœ… **NPM Scoped Package**: Published as `@paulduvall/claude-dev-toolkit`
269
269
  - āœ… **Configuration Command**: Built-in `config` command for template management
270
270
  - āœ… **Workflow Reporting**: Comprehensive GitHub Actions reporting
@@ -48,13 +48,25 @@ program
48
48
 
49
49
  program
50
50
  .command('install')
51
- .description('Install command sets')
52
- .option('--active', 'Install active commands only')
53
- .option('--experiments', 'Install experimental commands')
54
- .option('--all', 'Install all commands')
55
- .action((options) => {
51
+ .description('Install command sets to ~/.claude/commands/')
52
+ .option('--active', 'Install production-ready commands (default)')
53
+ .option('--experiments', 'Install experimental commands only')
54
+ .option('--all', 'Install both active and experimental')
55
+ .option('--include <pattern>', 'Include specific commands matching pattern')
56
+ .option('--exclude <pattern>', 'Exclude commands matching pattern')
57
+ .option('--dry-run', 'Show what would be installed without making changes')
58
+ .option('--backup', 'Create backup before installation')
59
+ .action(async (options) => {
56
60
  const installer = require('../lib/installer');
57
- installer.install(options);
61
+ try {
62
+ const result = await installer.install(options);
63
+ if (!result.success && !result.dryRun) {
64
+ process.exit(1);
65
+ }
66
+ } catch (error) {
67
+ console.error(`Installation failed: ${error.message}`);
68
+ process.exit(1);
69
+ }
58
70
  });
59
71
 
60
72
  program
@@ -115,4 +127,147 @@ program
115
127
  config.handleCommand(options);
116
128
  });
117
129
 
130
+ program
131
+ .command('configure')
132
+ .description('Configure Claude Code settings (replaces configure-claude-code.sh)')
133
+ .option('--template <name>', 'Apply named template')
134
+ .option('--interactive', 'Launch interactive configuration wizard')
135
+ .option('--validate', 'Validate current configuration')
136
+ .option('--reset', 'Reset to default configuration')
137
+ .option('--backup', 'Create backup before changes (default: true)')
138
+ .option('--no-backup', 'Skip backup creation')
139
+ .action(async (options) => {
140
+ const ConfigureCommand = require('../lib/configure-command');
141
+ const configureCmd = new ConfigureCommand();
142
+ try {
143
+ const result = await configureCmd.execute(options);
144
+ if (!result.success) {
145
+ process.exit(1);
146
+ }
147
+ } catch (error) {
148
+ console.error(`Configuration failed: ${error.message}`);
149
+ process.exit(1);
150
+ }
151
+ });
152
+
153
+ program
154
+ .command('setup')
155
+ .description('Setup the Claude Dev Toolkit with custom commands and configuration')
156
+ .option('--type <template>', 'Configuration template to apply (basic, comprehensive, security-focused)')
157
+ .option('--commands <set>', 'Command set to install (active, experiments, all, none)')
158
+ .option('--skip-configure', 'Skip configuration step')
159
+ .option('--skip-hooks', 'Skip hooks installation')
160
+ .option('--force', 'Overwrite existing installation')
161
+ .option('--dry-run', 'Preview actions without executing')
162
+ .action(async (options) => {
163
+ const SetupCommand = require('../lib/setup-command');
164
+ const setupCmd = new SetupCommand();
165
+ try {
166
+ const result = await setupCmd.execute(options);
167
+ if (!result.success && !result.dryRun) {
168
+ process.exit(1);
169
+ }
170
+ } catch (error) {
171
+ console.error(`Setup failed: ${error.message}`);
172
+ process.exit(1);
173
+ }
174
+ });
175
+
176
+ program
177
+ .command('verify')
178
+ .description('Verify the Claude Dev Toolkit installation status and health')
179
+ .option('--verbose', 'Show detailed verification information')
180
+ .option('--fix', 'Attempt to fix detected issues automatically')
181
+ .action(async (options) => {
182
+ const VerifyCommand = require('../lib/verify-command');
183
+ const verifyCmd = new VerifyCommand();
184
+ try {
185
+ const result = await verifyCmd.execute(options);
186
+ // Set exit code based on health status
187
+ if (result.overall === 'critical') {
188
+ process.exit(2);
189
+ } else if (result.overall === 'warning') {
190
+ process.exit(1);
191
+ }
192
+ // Exit 0 for healthy
193
+ } catch (error) {
194
+ console.error(`Verification failed: ${error.message}`);
195
+ process.exit(2);
196
+ }
197
+ });
198
+
199
+ program
200
+ .command('backup [name]')
201
+ .description('Create named backup of Claude Code configuration')
202
+ .action(async (name) => {
203
+ const BackupRestoreCommand = require('../lib/backup-restore-command');
204
+ const backupCmd = new BackupRestoreCommand();
205
+ try {
206
+ const result = await backupCmd.backup(name);
207
+ if (!result.success) {
208
+ process.exit(1);
209
+ }
210
+ } catch (error) {
211
+ console.error(`Backup failed: ${error.message}`);
212
+ process.exit(1);
213
+ }
214
+ });
215
+
216
+ program
217
+ .command('restore <name>')
218
+ .description('Restore from a named backup')
219
+ .action(async (name) => {
220
+ const BackupRestoreCommand = require('../lib/backup-restore-command');
221
+ const restoreCmd = new BackupRestoreCommand();
222
+ try {
223
+ const result = await restoreCmd.restore(name);
224
+ if (!result.success) {
225
+ process.exit(1);
226
+ }
227
+ } catch (error) {
228
+ console.error(`Restore failed: ${error.message}`);
229
+ process.exit(1);
230
+ }
231
+ });
232
+
233
+ program
234
+ .command('update')
235
+ .description('Check for package updates')
236
+ .action(async () => {
237
+ const { version } = require('../package.json');
238
+ console.log('šŸ” Checking for updates...\n');
239
+ console.log(`Current version: ${version}`);
240
+
241
+ try {
242
+ const { execSync } = require('child_process');
243
+ const output = execSync('npm view @paulduvall/claude-dev-toolkit version', {
244
+ encoding: 'utf8',
245
+ stdio: 'pipe'
246
+ }).trim();
247
+
248
+ console.log(`Latest version: ${output}`);
249
+
250
+ if (output !== version) {
251
+ console.log('\nšŸ†• Update available!');
252
+ console.log('\nTo update, run:');
253
+ console.log(' npm update -g @paulduvall/claude-dev-toolkit');
254
+
255
+ // Check for breaking changes in major version
256
+ const currentMajor = parseInt(version.split('.')[0]);
257
+ const latestMajor = parseInt(output.split('.')[0]);
258
+
259
+ if (latestMajor > currentMajor) {
260
+ console.log('\nāš ļø Major version update - may contain breaking changes');
261
+ console.log(' Review release notes before updating');
262
+ }
263
+ } else {
264
+ console.log('\nāœ… You are using the latest version');
265
+ }
266
+ } catch (error) {
267
+ console.error('āŒ Could not check for updates');
268
+ console.log(' Please check your internet connection');
269
+ process.exit(1);
270
+ }
271
+ });
272
+
118
273
  program.parse(process.argv);
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Backup and Restore Commands Implementation - Refactored
3
+ * Orchestrator for backup and restore operations using focused services
4
+ */
5
+
6
+ const BackupService = require('./services/backup-service');
7
+ const RestoreService = require('./services/restore-service');
8
+ const BackupListService = require('./services/backup-list-service');
9
+ const BaseCommand = require('./base/base-command');
10
+ const FileSystemUtils = require('./utils/file-system-utils');
11
+ const { execSync } = require('child_process');
12
+
13
+ class BackupRestoreCommand extends BaseCommand {
14
+ constructor(config = null) {
15
+ super(config);
16
+ this.backupService = new BackupService(this.config);
17
+ this.restoreService = new RestoreService(this.config);
18
+ this.listService = new BackupListService(this.config);
19
+ }
20
+
21
+ /**
22
+ * Create a backup of the entire .claude directory
23
+ */
24
+ async backup(name = null) {
25
+ this.logger.step('Creating backup of Claude Code configuration', { backupName: name });
26
+
27
+ try {
28
+ const result = await this.backupService.create(name);
29
+
30
+ // Try to compress the backup
31
+ const compressed = await this.compressBackup(result.path);
32
+
33
+ this.logger.complete(`Backup '${result.name}' created successfully`, {
34
+ files: result.totalFiles,
35
+ size: FileSystemUtils.formatSize(result.totalSize),
36
+ compressed: !!compressed
37
+ });
38
+
39
+ if (compressed) {
40
+ this.logger.info(`Backup compressed and stored at: ${compressed.path}`, {
41
+ compressionRatio: compressed.size / result.totalSize
42
+ });
43
+ } else {
44
+ this.logger.info(`Backup stored at: ${result.path}`);
45
+ }
46
+
47
+ return {
48
+ success: true,
49
+ name: result.name,
50
+ path: compressed ? compressed.path : result.path,
51
+ metadata: result.metadata
52
+ };
53
+
54
+ } catch (error) {
55
+ return this.handleError(error);
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Try to compress a backup using tar
61
+ */
62
+ async compressBackup(backupPath) {
63
+ try {
64
+ const backupName = require('path').basename(backupPath);
65
+ const tarPath = `${backupPath}.tar.gz`;
66
+
67
+ execSync(`tar -czf "${tarPath}" -C "${this.config.backupsDir}" "${backupName}"`, {
68
+ encoding: 'utf8',
69
+ stdio: 'pipe'
70
+ });
71
+
72
+ // Remove uncompressed backup
73
+ FileSystemUtils.remove(backupPath);
74
+
75
+ const compressedSize = FileSystemUtils.getStats(tarPath).size;
76
+ return {
77
+ path: tarPath,
78
+ size: compressedSize
79
+ };
80
+ } catch (error) {
81
+ // Compression failed, keep uncompressed backup
82
+ return null;
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Restore from a backup
88
+ */
89
+ async restore(backupName) {
90
+ this.logger.step(`Restoring from backup: ${backupName}`, { backupName });
91
+
92
+ try {
93
+ // Create undo backup first
94
+ this.logger.info('Creating undo backup for safety');
95
+ const undoBackup = await this.backup('undo-before-restore');
96
+
97
+ if (!undoBackup.success) {
98
+ this.logger.warn('Could not create undo backup, continuing anyway', {
99
+ risk: 'restore cannot be undone'
100
+ });
101
+ }
102
+
103
+ // Perform restore using service
104
+ const result = await this.restoreService.restore(backupName);
105
+
106
+ this.logger.complete(`Restore completed successfully`, {
107
+ restoredCount: result.restoredCount,
108
+ hasUndo: undoBackup.success
109
+ });
110
+
111
+ if (undoBackup.success) {
112
+ this.logger.info(`To undo this restore, run: claude-commands restore ${undoBackup.name}`, {
113
+ undoCommand: `claude-commands restore ${undoBackup.name}`
114
+ });
115
+ }
116
+
117
+ return {
118
+ success: true,
119
+ restoredCount: result.restoredCount,
120
+ undoBackup: undoBackup.success ? undoBackup.name : null
121
+ };
122
+
123
+ } catch (error) {
124
+ return this.handleError(error);
125
+ }
126
+ }
127
+
128
+ /**
129
+ * List available backups
130
+ */
131
+ async listBackups() {
132
+ try {
133
+ return await this.listService.display();
134
+ } catch (error) {
135
+ return this.handleError(error);
136
+ }
137
+ }
138
+ }
139
+
140
+ module.exports = BackupRestoreCommand;
@@ -0,0 +1,252 @@
1
+ /**
2
+ * Base Command Pattern
3
+ * Abstract base class for all CLI commands with standardized error handling
4
+ */
5
+
6
+ const ClaudePathConfig = require('../utils/claude-path-config');
7
+ const FileSystemUtils = require('../utils/file-system-utils');
8
+ const LoggerService = require('../services/logger-service');
9
+
10
+ class BaseCommand {
11
+ constructor(config = null, logger = null) {
12
+ this.config = config || new ClaudePathConfig();
13
+ this.logger = logger || new LoggerService();
14
+ this.startTime = null;
15
+ this.metrics = {
16
+ filesProcessed: 0,
17
+ operationsPerformed: 0,
18
+ errorsEncountered: 0
19
+ };
20
+
21
+ // Set logger context for this command
22
+ this.logger.setContext({
23
+ command: this.constructor.name,
24
+ timestamp: new Date().toISOString()
25
+ });
26
+ }
27
+
28
+ /**
29
+ * Main execution method with standardized error handling
30
+ */
31
+ async execute(options = {}) {
32
+ this.startTime = Date.now();
33
+
34
+ try {
35
+ // Pre-execution validation
36
+ const preValidation = await this.preValidate(options);
37
+ if (!preValidation.success) {
38
+ return this.createFailureResult(preValidation.error, preValidation);
39
+ }
40
+
41
+ // Execute the main command logic
42
+ const result = await this.run(options);
43
+
44
+ // Post-execution cleanup
45
+ await this.postExecute(result, options);
46
+
47
+ return this.createSuccessResult(result);
48
+
49
+ } catch (error) {
50
+ this.metrics.errorsEncountered++;
51
+ return this.handleError(error, options);
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Abstract method - must be implemented by subclasses
57
+ */
58
+ async run(options) {
59
+ throw new Error('Subclasses must implement the run() method');
60
+ }
61
+
62
+ /**
63
+ * Pre-execution validation hook
64
+ */
65
+ async preValidate(options) {
66
+ // Default validation - can be overridden
67
+ return { success: true };
68
+ }
69
+
70
+ /**
71
+ * Post-execution cleanup hook
72
+ */
73
+ async postExecute(result, options) {
74
+ // Default post-execution - can be overridden
75
+ const duration = this.getDuration();
76
+
77
+ if (process.env.NODE_ENV === 'development' || options.verbose) {
78
+ console.log(`\nšŸ“Š Command completed in ${duration}s`);
79
+ if (this.metrics.filesProcessed > 0) {
80
+ console.log(` Files processed: ${this.metrics.filesProcessed}`);
81
+ }
82
+ if (this.metrics.operationsPerformed > 0) {
83
+ console.log(` Operations: ${this.metrics.operationsPerformed}`);
84
+ }
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Standardized error handling
90
+ */
91
+ handleError(error, options = {}) {
92
+ const errorDetails = this.analyzeError(error);
93
+
94
+ // Log error appropriately
95
+ if (options.verbose || process.env.NODE_ENV === 'development') {
96
+ console.error(`šŸ’„ ${this.constructor.name} Error Details:`, error);
97
+ } else {
98
+ console.error(`āŒ ${errorDetails.message}`);
99
+ }
100
+
101
+ // Provide helpful guidance
102
+ if (errorDetails.suggestions.length > 0) {
103
+ console.log('\nšŸ’” Suggestions:');
104
+ errorDetails.suggestions.forEach(suggestion => {
105
+ console.log(` • ${suggestion}`);
106
+ });
107
+ }
108
+
109
+ return this.createFailureResult(errorDetails.message, {
110
+ type: errorDetails.type,
111
+ suggestions: errorDetails.suggestions,
112
+ originalError: options.verbose ? error : undefined
113
+ });
114
+ }
115
+
116
+ /**
117
+ * Analyze error type and generate helpful suggestions
118
+ */
119
+ analyzeError(error) {
120
+ const message = error.message || 'Unknown error occurred';
121
+ const suggestions = [];
122
+ let type = 'general';
123
+
124
+ // File system errors
125
+ if (message.includes('ENOENT') || message.includes('no such file')) {
126
+ type = 'file_not_found';
127
+ suggestions.push('Check that the file or directory exists');
128
+ suggestions.push('Verify you have the correct path');
129
+ } else if (message.includes('EACCES') || message.includes('permission denied')) {
130
+ type = 'permission_error';
131
+ suggestions.push('Check file/directory permissions');
132
+ suggestions.push('Try running with appropriate permissions');
133
+ } else if (message.includes('ENOTDIR') || message.includes('not a directory')) {
134
+ type = 'path_error';
135
+ suggestions.push('Verify the path is correct');
136
+ suggestions.push('Check that parent directories exist');
137
+ }
138
+ // Network errors
139
+ else if (message.includes('ECONNREFUSED') || message.includes('network')) {
140
+ type = 'network_error';
141
+ suggestions.push('Check your internet connection');
142
+ suggestions.push('Verify proxy settings if applicable');
143
+ }
144
+ // Git errors
145
+ else if (message.includes('git') || message.includes('Not a git repository')) {
146
+ type = 'git_error';
147
+ suggestions.push('Ensure you are in a git repository');
148
+ suggestions.push('Run "git init" if needed');
149
+ }
150
+ // Claude Code errors
151
+ else if (message.includes('claude') || message.includes('settings')) {
152
+ type = 'claude_error';
153
+ suggestions.push('Run "claude-commands verify" to check installation');
154
+ suggestions.push('Try "claude-commands setup" to reconfigure');
155
+ }
156
+
157
+ return {
158
+ message: this.sanitizeErrorMessage(message),
159
+ type,
160
+ suggestions
161
+ };
162
+ }
163
+
164
+ /**
165
+ * Sanitize error messages for user consumption
166
+ */
167
+ sanitizeErrorMessage(message) {
168
+ // Remove internal paths and stack traces
169
+ return message
170
+ .replace(/\/Users\/[^\/]+\/[^\s]+/g, '~/<path>')
171
+ .replace(/\s+at\s+.*/g, '')
172
+ .replace(/Error:\s*/g, '')
173
+ .trim();
174
+ }
175
+
176
+ /**
177
+ * Create standardized success result
178
+ */
179
+ createSuccessResult(data = {}) {
180
+ return {
181
+ success: true,
182
+ duration: this.getDuration(),
183
+ metrics: { ...this.metrics },
184
+ timestamp: new Date().toISOString(),
185
+ ...data
186
+ };
187
+ }
188
+
189
+ /**
190
+ * Create standardized failure result
191
+ */
192
+ createFailureResult(message, data = {}) {
193
+ return {
194
+ success: false,
195
+ error: message,
196
+ duration: this.getDuration(),
197
+ metrics: { ...this.metrics },
198
+ timestamp: new Date().toISOString(),
199
+ ...data
200
+ };
201
+ }
202
+
203
+ /**
204
+ * Get execution duration in seconds
205
+ */
206
+ getDuration() {
207
+ if (!this.startTime) return 0;
208
+ return ((Date.now() - this.startTime) / 1000).toFixed(2);
209
+ }
210
+
211
+ /**
212
+ * Helper method to ensure directories exist
213
+ */
214
+ ensureDirectoryExists(dirPath) {
215
+ FileSystemUtils.ensureDirectory(dirPath);
216
+ this.metrics.operationsPerformed++;
217
+ }
218
+
219
+ /**
220
+ * Helper method to safely read files
221
+ */
222
+ safeReadFile(filePath) {
223
+ const content = FileSystemUtils.readFile(filePath);
224
+ if (content !== null) {
225
+ this.metrics.filesProcessed++;
226
+ }
227
+ return content;
228
+ }
229
+
230
+ /**
231
+ * Helper method to safely write files
232
+ */
233
+ safeWriteFile(filePath, content, mode = 0o644) {
234
+ const success = FileSystemUtils.writeFile(filePath, content, mode);
235
+ if (success) {
236
+ this.metrics.filesProcessed++;
237
+ this.metrics.operationsPerformed++;
238
+ }
239
+ return success;
240
+ }
241
+
242
+ /**
243
+ * Display progress if verbose mode enabled
244
+ */
245
+ showProgress(message, options = {}) {
246
+ if (options.verbose || process.env.NODE_ENV === 'development') {
247
+ console.log(`šŸ”„ ${message}`);
248
+ }
249
+ }
250
+ }
251
+
252
+ module.exports = BaseCommand;