@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
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Command Installer Service
3
+ * Focused service for installing commands from the NPM package
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const FileSystemUtils = require('../utils/file-system-utils');
9
+ const ClaudePathConfig = require('../utils/claude-path-config');
10
+
11
+ class CommandInstallerService {
12
+ constructor(config = null) {
13
+ this.config = config || new ClaudePathConfig();
14
+ this.installedCount = 0;
15
+ this.skippedCount = 0;
16
+ }
17
+
18
+ /**
19
+ * Install commands based on options
20
+ */
21
+ async install(options = {}) {
22
+ // Reset counters
23
+ this.installedCount = 0;
24
+ this.skippedCount = 0;
25
+
26
+ // Ensure commands directory exists
27
+ FileSystemUtils.ensureDirectory(this.config.commandsDir);
28
+
29
+ // Get commands to install
30
+ const commandsToInstall = this.getCommandsToInstall(options);
31
+
32
+ // Install commands
33
+ const installResults = {
34
+ active: 0,
35
+ experimental: 0
36
+ };
37
+
38
+ for (const cmd of commandsToInstall) {
39
+ const sourcePath = path.join(cmd.source, cmd.file);
40
+ const destPath = path.join(this.config.commandsDir, cmd.file);
41
+
42
+ if (FileSystemUtils.copyFile(sourcePath, destPath, 0o644)) {
43
+ this.installedCount++;
44
+ installResults[cmd.type]++;
45
+ } else {
46
+ console.error(`⚠️ Failed to install ${cmd.file}`);
47
+ this.skippedCount++;
48
+ }
49
+ }
50
+
51
+ return {
52
+ installedCount: this.installedCount,
53
+ skippedCount: this.skippedCount,
54
+ results: installResults,
55
+ commands: commandsToInstall
56
+ };
57
+ }
58
+
59
+ /**
60
+ * Get list of commands to install based on options
61
+ */
62
+ getCommandsToInstall(options) {
63
+ const commands = [];
64
+
65
+ // Determine which command sets to install
66
+ const installActive = options.active || options.all ||
67
+ (!options.active && !options.experiments && !options.all);
68
+ const installExperiments = options.experiments || options.all;
69
+
70
+ // Collect active commands
71
+ if (installActive) {
72
+ const activeCommands = this.getCommandsFromDirectory(
73
+ this.config.packageActiveCommandsDir,
74
+ 'active'
75
+ );
76
+ commands.push(...activeCommands);
77
+ }
78
+
79
+ // Collect experimental commands
80
+ if (installExperiments) {
81
+ const experimentalCommands = this.getCommandsFromDirectory(
82
+ this.config.packageExperimentalCommandsDir,
83
+ 'experimental'
84
+ );
85
+ commands.push(...experimentalCommands);
86
+ }
87
+
88
+ // Apply include/exclude filters
89
+ return this.applyFilters(commands, options);
90
+ }
91
+
92
+ /**
93
+ * Get commands from a specific directory
94
+ */
95
+ getCommandsFromDirectory(dirPath, type) {
96
+ if (!fs.existsSync(dirPath)) {
97
+ return [];
98
+ }
99
+
100
+ try {
101
+ return fs.readdirSync(dirPath)
102
+ .filter(f => f.endsWith('.md'))
103
+ .map(f => ({
104
+ file: f,
105
+ source: dirPath,
106
+ type: type
107
+ }));
108
+ } catch (error) {
109
+ console.warn(`⚠️ Warning: Could not read ${type} commands directory - ${error.message}`);
110
+ return [];
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Apply include/exclude filters to command list
116
+ */
117
+ applyFilters(commands, options) {
118
+ let filteredCommands = commands;
119
+
120
+ // Apply include filter
121
+ if (options.include) {
122
+ const patterns = Array.isArray(options.include) ? options.include : [options.include];
123
+ filteredCommands = filteredCommands.filter(cmd =>
124
+ patterns.some(pattern => this.matchesPattern(cmd.file, pattern))
125
+ );
126
+ }
127
+
128
+ // Apply exclude filter
129
+ if (options.exclude) {
130
+ const patterns = Array.isArray(options.exclude) ? options.exclude : [options.exclude];
131
+ filteredCommands = filteredCommands.filter(cmd =>
132
+ !patterns.some(pattern => this.matchesPattern(cmd.file, pattern))
133
+ );
134
+ }
135
+
136
+ return filteredCommands;
137
+ }
138
+
139
+ /**
140
+ * Check if a command matches a pattern
141
+ */
142
+ matchesPattern(filename, pattern) {
143
+ // Simple pattern matching with wildcard support
144
+ if (pattern.includes('*')) {
145
+ const regex = new RegExp(pattern.replace(/\*/g, '.*'));
146
+ return regex.test(filename);
147
+ }
148
+ return filename.includes(pattern);
149
+ }
150
+
151
+ /**
152
+ * Get preview of what would be installed (dry run)
153
+ */
154
+ getDryRunPreview(options) {
155
+ const commandsToInstall = this.getCommandsToInstall(options);
156
+
157
+ const byType = {
158
+ active: commandsToInstall.filter(c => c.type === 'active'),
159
+ experimental: commandsToInstall.filter(c => c.type === 'experimental')
160
+ };
161
+
162
+ return {
163
+ total: commandsToInstall.length,
164
+ byType,
165
+ destination: this.config.commandsDir
166
+ };
167
+ }
168
+
169
+ /**
170
+ * Validate installation requirements
171
+ */
172
+ validateInstallation() {
173
+ const issues = [];
174
+
175
+ // Check if package directories exist
176
+ if (!fs.existsSync(this.config.packageActiveCommandsDir)) {
177
+ issues.push('Active commands directory not found in package');
178
+ }
179
+
180
+ if (!fs.existsSync(this.config.packageExperimentalCommandsDir)) {
181
+ issues.push('Experimental commands directory not found in package');
182
+ }
183
+
184
+ // Check if destination parent directory exists and is writable
185
+ const claudeDir = this.config.claudeDir;
186
+ try {
187
+ // Try to ensure the directory exists first
188
+ FileSystemUtils.ensureDirectory(claudeDir);
189
+
190
+ // Then check if it's writable
191
+ if (fs.existsSync(claudeDir) && !FileSystemUtils.isWritable(claudeDir)) {
192
+ issues.push('Cannot write to Claude directory - check permissions');
193
+ }
194
+ } catch (error) {
195
+ // If we can't create the directory, that's a permission issue
196
+ issues.push(`Cannot create Claude directory: ${error.message}`);
197
+ }
198
+
199
+ return {
200
+ valid: issues.length === 0,
201
+ issues
202
+ };
203
+ }
204
+
205
+ /**
206
+ * Get installation statistics
207
+ */
208
+ getStats() {
209
+ return {
210
+ installed: this.installedCount,
211
+ skipped: this.skippedCount,
212
+ total: this.installedCount + this.skippedCount
213
+ };
214
+ }
215
+ }
216
+
217
+ module.exports = CommandInstallerService;
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Centralized Logger Service
3
+ * Provides consistent logging across the entire application with context support
4
+ */
5
+
6
+ class LoggerService {
7
+ constructor(options = {}) {
8
+ this.options = {
9
+ timestamp: options.timestamp ?? true,
10
+ context: options.context ?? true,
11
+ colors: options.colors ?? true,
12
+ level: options.level ?? 'info' // debug, info, warn, error
13
+ };
14
+ this.contextData = {};
15
+ }
16
+
17
+ /**
18
+ * Set persistent context data for all log messages
19
+ */
20
+ setContext(context) {
21
+ this.contextData = { ...this.contextData, ...context };
22
+ }
23
+
24
+ /**
25
+ * Clear all context data
26
+ */
27
+ clearContext() {
28
+ this.contextData = {};
29
+ }
30
+
31
+ /**
32
+ * Success message logging
33
+ */
34
+ success(message, context = {}) {
35
+ this._log('success', '✅', message, context);
36
+ }
37
+
38
+ /**
39
+ * Information message logging
40
+ */
41
+ info(message, context = {}) {
42
+ this._log('info', 'ℹ️', message, context);
43
+ }
44
+
45
+ /**
46
+ * Warning message logging
47
+ */
48
+ warn(message, context = {}) {
49
+ this._log('warn', '⚠️', message, context);
50
+ }
51
+
52
+ /**
53
+ * Error message logging
54
+ */
55
+ error(message, error = null, context = {}) {
56
+ const errorContext = error ? { error: this._serializeError(error), ...context } : context;
57
+ this._log('error', '❌', message, errorContext);
58
+ }
59
+
60
+ /**
61
+ * Debug message logging
62
+ */
63
+ debug(message, context = {}) {
64
+ if (this.options.level === 'debug') {
65
+ this._log('debug', '🔍', message, context);
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Step progress logging
71
+ */
72
+ step(message, context = {}) {
73
+ this._log('step', '🔄', message, context);
74
+ }
75
+
76
+ /**
77
+ * Completion logging
78
+ */
79
+ complete(message, context = {}) {
80
+ this._log('complete', '🎉', message, context);
81
+ }
82
+
83
+ /**
84
+ * Progress logging with metrics
85
+ */
86
+ progress(message, current, total, context = {}) {
87
+ const progressContext = { current, total, percentage: Math.round((current / total) * 100), ...context };
88
+ this._log('progress', '📊', message, progressContext);
89
+ }
90
+
91
+ /**
92
+ * Internal logging method
93
+ */
94
+ _log(level, icon, message, context = {}) {
95
+ const timestamp = this.options.timestamp ? `[${new Date().toISOString()}]` : '';
96
+ const fullContext = { ...this.contextData, ...context };
97
+
98
+ let logMessage = `${timestamp} ${icon} ${message}`;
99
+
100
+ // Add context if enabled and present
101
+ if (this.options.context && Object.keys(fullContext).length > 0) {
102
+ const contextStr = this._formatContext(fullContext);
103
+ if (contextStr) {
104
+ logMessage += ` ${contextStr}`;
105
+ }
106
+ }
107
+
108
+ // Route to appropriate console method
109
+ switch (level) {
110
+ case 'error':
111
+ console.error(logMessage);
112
+ break;
113
+ case 'warn':
114
+ console.warn(logMessage);
115
+ break;
116
+ case 'debug':
117
+ console.debug(logMessage);
118
+ break;
119
+ default:
120
+ console.log(logMessage);
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Format context data for display
126
+ */
127
+ _formatContext(context) {
128
+ try {
129
+ // Filter out complex objects and functions
130
+ const filteredContext = {};
131
+ for (const [key, value] of Object.entries(context)) {
132
+ if (value !== null && value !== undefined) {
133
+ if (typeof value === 'object' && !Array.isArray(value)) {
134
+ // Include only simple object properties
135
+ if (Object.keys(value).length < 5) {
136
+ filteredContext[key] = value;
137
+ } else {
138
+ filteredContext[key] = `[Object with ${Object.keys(value).length} keys]`;
139
+ }
140
+ } else if (typeof value !== 'function') {
141
+ filteredContext[key] = value;
142
+ }
143
+ }
144
+ }
145
+
146
+ if (Object.keys(filteredContext).length === 0) {
147
+ return '';
148
+ }
149
+
150
+ return JSON.stringify(filteredContext);
151
+ } catch (error) {
152
+ return `[Context formatting error: ${error.message}]`;
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Serialize error objects for logging
158
+ */
159
+ _serializeError(error) {
160
+ if (!error) return null;
161
+
162
+ return {
163
+ name: error.name,
164
+ message: error.message,
165
+ code: error.code,
166
+ stack: error.stack?.split('\n').slice(0, 3).join('\n') // First 3 lines of stack
167
+ };
168
+ }
169
+
170
+ /**
171
+ * Create a scoped logger with persistent context
172
+ */
173
+ scope(scopeName, context = {}) {
174
+ const scopedLogger = new LoggerService(this.options);
175
+ scopedLogger.setContext({ scope: scopeName, ...this.contextData, ...context });
176
+ return scopedLogger;
177
+ }
178
+
179
+ /**
180
+ * Performance timing utilities
181
+ */
182
+ time(label) {
183
+ console.time(`⏱️ ${label}`);
184
+ }
185
+
186
+ timeEnd(label) {
187
+ console.timeEnd(`⏱️ ${label}`);
188
+ }
189
+
190
+ /**
191
+ * Table logging for structured data
192
+ */
193
+ table(data, message = '') {
194
+ if (message) {
195
+ this.info(message);
196
+ }
197
+ console.table(data);
198
+ }
199
+ }
200
+
201
+ module.exports = LoggerService;