@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.
- package/README.md +9 -9
- package/bin/claude-commands +161 -6
- package/hooks/lib/argument-parser.sh +0 -0
- package/hooks/lib/config-constants.sh +0 -0
- package/hooks/lib/context-manager.sh +0 -0
- package/hooks/lib/error-handler.sh +0 -0
- package/hooks/lib/execution-engine.sh +0 -0
- package/hooks/lib/file-utils.sh +0 -0
- package/hooks/lib/subagent-discovery.sh +0 -0
- package/hooks/lib/subagent-validator.sh +0 -0
- package/lib/backup-restore-command.js +140 -0
- package/lib/base/base-command.js +252 -0
- package/lib/base/command-result.js +184 -0
- package/lib/config/constants.js +255 -0
- package/lib/config.js +48 -6
- package/lib/configure-command.js +428 -0
- package/lib/dependency-validator.js +64 -5
- package/lib/installation-instruction-generator-backup.js +579 -0
- package/lib/installation-instruction-generator.js +213 -495
- package/lib/installer.js +134 -52
- package/lib/services/backup-list-service.js +226 -0
- package/lib/services/backup-service.js +230 -0
- package/lib/services/command-installer-service.js +217 -0
- package/lib/services/logger-service.js +201 -0
- package/lib/services/package-manager-service.js +319 -0
- package/lib/services/platform-instruction-service.js +294 -0
- package/lib/services/recovery-instruction-service.js +348 -0
- package/lib/services/restore-service.js +221 -0
- package/lib/setup-command.js +309 -0
- package/lib/utils/claude-path-config.js +184 -0
- package/lib/utils/file-system-utils.js +152 -0
- package/lib/utils.js +8 -4
- package/lib/verify-command.js +430 -0
- 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;
|