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

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 (35) hide show
  1. package/README.md +29 -9
  2. package/bin/claude-commands +184 -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/oidc-command.js +270 -0
  22. package/lib/services/backup-list-service.js +226 -0
  23. package/lib/services/backup-service.js +230 -0
  24. package/lib/services/command-installer-service.js +217 -0
  25. package/lib/services/logger-service.js +201 -0
  26. package/lib/services/package-manager-service.js +319 -0
  27. package/lib/services/platform-instruction-service.js +294 -0
  28. package/lib/services/recovery-instruction-service.js +348 -0
  29. package/lib/services/restore-service.js +221 -0
  30. package/lib/setup-command.js +309 -0
  31. package/lib/utils/claude-path-config.js +184 -0
  32. package/lib/utils/file-system-utils.js +152 -0
  33. package/lib/utils.js +8 -4
  34. package/lib/verify-command.js +430 -0
  35. package/package.json +4 -2
@@ -0,0 +1,309 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Setup Command Implementation
5
+ * Replaces setup.sh functionality with npm package equivalent
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const os = require('os');
11
+ const { execSync } = require('child_process');
12
+
13
+ class SetupCommand {
14
+ constructor() {
15
+ this.homeDir = process.env.TEST_HOME || os.homedir();
16
+ this.claudeDir = path.join(this.homeDir, '.claude');
17
+ this.commandsDir = path.join(this.claudeDir, 'commands');
18
+ this.settingsFile = path.join(this.claudeDir, 'settings.json');
19
+ }
20
+
21
+ /**
22
+ * Execute setup with options
23
+ */
24
+ async execute(options = {}) {
25
+ console.log('šŸš€ Claude Dev Toolkit Setup\n');
26
+
27
+ const {
28
+ type = 'basic',
29
+ commands = 'active',
30
+ skipConfigure = false,
31
+ skipHooks = false,
32
+ force = false,
33
+ dryRun = false
34
+ } = options;
35
+
36
+ if (dryRun) {
37
+ return this.showDryRun(options);
38
+ }
39
+
40
+ try {
41
+ // 1. Verify Claude Code availability (optional check)
42
+ this.checkClaudeCode();
43
+
44
+ // 2. Create directory structure
45
+ await this.createDirectoryStructure(force);
46
+
47
+ // 3. Install commands
48
+ if (commands !== 'none') {
49
+ await this.installCommands(commands);
50
+ }
51
+
52
+ // 4. Apply configuration template
53
+ if (!skipConfigure) {
54
+ await this.applyConfigurationTemplate(type);
55
+ }
56
+
57
+ // 5. Install hooks (if requested)
58
+ if (!skipHooks) {
59
+ await this.installHooks();
60
+ }
61
+
62
+ // 6. Verify installation
63
+ await this.verifySetup();
64
+
65
+ console.log('\nāœ… Setup completed successfully!');
66
+ console.log('\nšŸ’” Next steps:');
67
+ console.log(' • Run: claude-commands verify');
68
+ console.log(' • Try: /xhelp in Claude Code to see all commands');
69
+
70
+ return { success: true, message: 'Setup completed successfully' };
71
+
72
+ } catch (error) {
73
+ console.error(`\nāŒ Setup failed: ${error.message}`);
74
+ return { success: false, error: error.message };
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Show dry run preview
80
+ */
81
+ showDryRun(options) {
82
+ console.log('šŸ” Dry Run - Preview of setup actions:\n');
83
+
84
+ console.log('šŸ“ Directory Structure:');
85
+ console.log(` • Create: ${this.claudeDir}`);
86
+ console.log(` • Create: ${this.commandsDir}`);
87
+
88
+ if (!options.skipConfigure) {
89
+ console.log('\nāš™ļø Configuration:');
90
+ console.log(` • Apply template: ${options.type || 'basic'}`);
91
+ console.log(` • Create: ${this.settingsFile}`);
92
+ }
93
+
94
+ console.log('\nšŸ“¦ Commands Installation:');
95
+ const commandSet = options.commands || 'active';
96
+ console.log(` • Install: ${commandSet} command set`);
97
+
98
+ if (!options.skipHooks) {
99
+ console.log('\nšŸŽ£ Hooks:');
100
+ console.log(' • Install security hooks');
101
+ console.log(' • Configure file logging');
102
+ }
103
+
104
+ console.log('\nšŸ” Verification:');
105
+ console.log(' • Check installation completeness');
106
+ console.log(' • Validate configuration');
107
+
108
+ console.log('\nšŸ’” This was a dry run - no changes were made');
109
+ console.log(' Run without --dry-run to execute setup');
110
+
111
+ return { success: true, dryRun: true };
112
+ }
113
+
114
+ /**
115
+ * Check Claude Code availability
116
+ */
117
+ checkClaudeCode() {
118
+ console.log('šŸ” Checking Claude Code availability...');
119
+
120
+ try {
121
+ execSync('claude --version', { stdio: 'pipe' });
122
+ console.log(' āœ… Claude Code detected');
123
+ } catch (error) {
124
+ console.log(' āš ļø Claude Code not detected (optional)');
125
+ console.log(' šŸ’” Install with: npm install -g @anthropic-ai/claude-code');
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Create directory structure
131
+ */
132
+ async createDirectoryStructure(force) {
133
+ console.log('šŸ“ Creating directory structure...');
134
+
135
+ // Check if directories already exist
136
+ if (fs.existsSync(this.claudeDir) && !force) {
137
+ console.log(' āœ… ~/.claude directory already exists');
138
+ } else {
139
+ fs.mkdirSync(this.claudeDir, { recursive: true });
140
+ console.log(` āœ… Created: ${this.claudeDir}`);
141
+ }
142
+
143
+ if (!fs.existsSync(this.commandsDir)) {
144
+ fs.mkdirSync(this.commandsDir, { recursive: true });
145
+ console.log(` āœ… Created: ${this.commandsDir}`);
146
+ } else {
147
+ console.log(' āœ… Commands directory already exists');
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Install commands
153
+ */
154
+ async installCommands(commandSet) {
155
+ console.log(`šŸ“¦ Installing ${commandSet} commands...`);
156
+
157
+ try {
158
+ const installer = require('./installer');
159
+ const options = {};
160
+
161
+ switch (commandSet) {
162
+ case 'active':
163
+ options.active = true;
164
+ break;
165
+ case 'experiments':
166
+ options.experiments = true;
167
+ break;
168
+ case 'all':
169
+ options.all = true;
170
+ break;
171
+ default:
172
+ options.active = true;
173
+ }
174
+
175
+ await installer.install(options);
176
+ console.log(' āœ… Commands installed successfully');
177
+ } catch (error) {
178
+ throw new Error(`Command installation failed: ${error.message}`);
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Apply configuration template
184
+ */
185
+ async applyConfigurationTemplate(templateName) {
186
+ console.log(`āš™ļø Applying ${templateName} configuration template...`);
187
+
188
+ try {
189
+ const config = require('./config');
190
+ await config.applyTemplate(templateName);
191
+ console.log(' āœ… Configuration template applied');
192
+ } catch (error) {
193
+ console.log(` āš ļø Configuration template application failed: ${error.message}`);
194
+ // Don't fail setup for configuration issues
195
+ }
196
+ }
197
+
198
+ /**
199
+ * Install hooks
200
+ */
201
+ async installHooks() {
202
+ console.log('šŸŽ£ Installing hooks...');
203
+
204
+ try {
205
+ // Check if hooks installer is available
206
+ const hooksInstaller = require('./hook-installer');
207
+ await hooksInstaller.install();
208
+ console.log(' āœ… Hooks installed successfully');
209
+ } catch (error) {
210
+ console.log(` āš ļø Hooks installation skipped: ${error.message}`);
211
+ // Don't fail setup for hooks
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Verify setup completion
217
+ */
218
+ async verifySetup() {
219
+ console.log('šŸ” Verifying setup...');
220
+
221
+ const issues = [];
222
+
223
+ // Check directory structure
224
+ if (!fs.existsSync(this.claudeDir)) {
225
+ issues.push('Claude directory not found');
226
+ }
227
+
228
+ if (!fs.existsSync(this.commandsDir)) {
229
+ issues.push('Commands directory not found');
230
+ }
231
+
232
+ // Check command installation
233
+ try {
234
+ const commands = fs.readdirSync(this.commandsDir).filter(f => f.endsWith('.md'));
235
+ if (commands.length === 0) {
236
+ issues.push('No commands installed');
237
+ } else {
238
+ console.log(` āœ… ${commands.length} commands installed`);
239
+ }
240
+ } catch (error) {
241
+ issues.push('Cannot read commands directory');
242
+ }
243
+
244
+ // Check configuration
245
+ if (fs.existsSync(this.settingsFile)) {
246
+ console.log(' āœ… Configuration file present');
247
+ } else {
248
+ console.log(' āš ļø No configuration file (will use defaults)');
249
+ }
250
+
251
+ if (issues.length > 0) {
252
+ console.log(' āš ļø Issues detected:');
253
+ issues.forEach(issue => console.log(` • ${issue}`));
254
+ throw new Error(`Setup verification failed: ${issues.join(', ')}`);
255
+ }
256
+
257
+ console.log(' āœ… Setup verification passed');
258
+ }
259
+
260
+ /**
261
+ * Get available templates
262
+ */
263
+ getAvailableTemplates() {
264
+ const templatesDir = path.join(__dirname, '..', 'templates');
265
+ try {
266
+ return fs.readdirSync(templatesDir)
267
+ .filter(f => f.endsWith('.json'))
268
+ .map(f => f.replace('.json', ''));
269
+ } catch (error) {
270
+ return ['basic', 'comprehensive', 'security-focused'];
271
+ }
272
+ }
273
+
274
+ /**
275
+ * Get help text for setup command
276
+ */
277
+ getHelpText() {
278
+ return `
279
+ Setup the Claude Dev Toolkit with custom commands and configuration.
280
+
281
+ This command replaces the functionality of setup.sh script, providing
282
+ a complete installation and configuration of the Claude Code toolkit.
283
+
284
+ Usage:
285
+ claude-commands setup [options]
286
+
287
+ Options:
288
+ --type <template> Configuration template to apply
289
+ (basic, comprehensive, security-focused)
290
+ --commands <set> Command set to install
291
+ (active, experiments, all, none)
292
+ --skip-configure Skip configuration step
293
+ --skip-hooks Skip hooks installation
294
+ --force Overwrite existing installation
295
+ --dry-run Preview actions without executing
296
+
297
+ Examples:
298
+ claude-commands setup
299
+ claude-commands setup --type comprehensive --commands all
300
+ claude-commands setup --dry-run
301
+ claude-commands setup --type security-focused --skip-hooks
302
+
303
+ This command performs the equivalent of running setup.sh with intelligent
304
+ defaults and enhanced error handling.
305
+ `.trim();
306
+ }
307
+ }
308
+
309
+ module.exports = SetupCommand;
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Claude Path Configuration
3
+ * Centralized path management for Claude Code directories
4
+ */
5
+
6
+ const path = require('path');
7
+ const os = require('os');
8
+
9
+ class ClaudePathConfig {
10
+ constructor(homeDir = os.homedir()) {
11
+ this.homeDir = homeDir;
12
+ }
13
+
14
+ /**
15
+ * Main Claude directory
16
+ */
17
+ get claudeDir() {
18
+ return path.join(this.homeDir, '.claude');
19
+ }
20
+
21
+ /**
22
+ * Commands directory
23
+ */
24
+ get commandsDir() {
25
+ return path.join(this.claudeDir, 'commands');
26
+ }
27
+
28
+ /**
29
+ * Settings file path
30
+ */
31
+ get settingsPath() {
32
+ return path.join(this.claudeDir, 'settings.json');
33
+ }
34
+
35
+ /**
36
+ * Backups directory
37
+ */
38
+ get backupsDir() {
39
+ return path.join(this.claudeDir, 'backups');
40
+ }
41
+
42
+ /**
43
+ * Settings backups directory
44
+ */
45
+ get settingsBackupsDir() {
46
+ return path.join(this.backupsDir, 'settings');
47
+ }
48
+
49
+ /**
50
+ * Commands backups directory
51
+ */
52
+ get commandsBackupsDir() {
53
+ return path.join(this.backupsDir, 'commands');
54
+ }
55
+
56
+ /**
57
+ * Hooks directory
58
+ */
59
+ get hooksDir() {
60
+ return path.join(this.claudeDir, 'hooks');
61
+ }
62
+
63
+ /**
64
+ * Subagents directory
65
+ */
66
+ get subagentsDir() {
67
+ return path.join(this.claudeDir, 'subagents');
68
+ }
69
+
70
+ /**
71
+ * Templates directory (in package)
72
+ */
73
+ get templatesDir() {
74
+ return path.join(__dirname, '..', '..', 'templates');
75
+ }
76
+
77
+ /**
78
+ * Package commands directory (active)
79
+ */
80
+ get packageActiveCommandsDir() {
81
+ return path.join(__dirname, '..', '..', 'commands', 'active');
82
+ }
83
+
84
+ /**
85
+ * Package commands directory (experiments)
86
+ */
87
+ get packageExperimentalCommandsDir() {
88
+ return path.join(__dirname, '..', '..', 'commands', 'experiments');
89
+ }
90
+
91
+ /**
92
+ * Package hooks directory
93
+ */
94
+ get packageHooksDir() {
95
+ return path.join(__dirname, '..', '..', 'hooks');
96
+ }
97
+
98
+ /**
99
+ * Package subagents directory
100
+ */
101
+ get packageSubagentsDir() {
102
+ return path.join(__dirname, '..', '..', 'subagents');
103
+ }
104
+
105
+ /**
106
+ * Get all user directories that might need creation
107
+ */
108
+ getUserDirectories() {
109
+ return [
110
+ this.claudeDir,
111
+ this.commandsDir,
112
+ this.backupsDir,
113
+ this.settingsBackupsDir,
114
+ this.commandsBackupsDir,
115
+ this.hooksDir,
116
+ this.subagentsDir
117
+ ];
118
+ }
119
+
120
+ /**
121
+ * Get all package directories for validation
122
+ */
123
+ getPackageDirectories() {
124
+ return [
125
+ this.templatesDir,
126
+ this.packageActiveCommandsDir,
127
+ this.packageExperimentalCommandsDir,
128
+ this.packageHooksDir,
129
+ this.packageSubagentsDir
130
+ ];
131
+ }
132
+
133
+ /**
134
+ * Create a backup path with timestamp
135
+ */
136
+ createBackupPath(name, type = 'general') {
137
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').split('.')[0];
138
+ const backupName = name || `backup-${timestamp}`;
139
+
140
+ switch (type) {
141
+ case 'settings':
142
+ return path.join(this.settingsBackupsDir, `${backupName}.json`);
143
+ case 'commands':
144
+ return path.join(this.commandsBackupsDir, backupName);
145
+ default:
146
+ return path.join(this.backupsDir, backupName);
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Resolve template path from name
152
+ */
153
+ resolveTemplatePath(templateName) {
154
+ const variations = [
155
+ templateName,
156
+ `${templateName}.json`,
157
+ `${templateName}-settings.json`
158
+ ];
159
+
160
+ // Common aliases
161
+ const aliases = {
162
+ 'basic': 'basic-settings.json',
163
+ 'comprehensive': 'comprehensive-settings.json',
164
+ 'security': 'security-focused-settings.json',
165
+ 'security-focused': 'security-focused-settings.json'
166
+ };
167
+
168
+ if (aliases[templateName]) {
169
+ variations.unshift(aliases[templateName]);
170
+ }
171
+
172
+ for (const variant of variations) {
173
+ const fullPath = path.join(this.templatesDir, variant);
174
+ const fs = require('fs');
175
+ if (fs.existsSync(fullPath)) {
176
+ return fullPath;
177
+ }
178
+ }
179
+
180
+ return null;
181
+ }
182
+ }
183
+
184
+ module.exports = ClaudePathConfig;
@@ -0,0 +1,152 @@
1
+ /**
2
+ * File System Utilities
3
+ * Extracted common file system operations used across commands
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const os = require('os');
9
+
10
+ class FileSystemUtils {
11
+ /**
12
+ * Format file size for human-readable display
13
+ */
14
+ static formatSize(bytes) {
15
+ const units = ['B', 'KB', 'MB', 'GB'];
16
+ let size = bytes;
17
+ let unitIndex = 0;
18
+
19
+ while (size >= 1024 && unitIndex < units.length - 1) {
20
+ size /= 1024;
21
+ unitIndex++;
22
+ }
23
+
24
+ return `${size.toFixed(2)} ${units[unitIndex]}`;
25
+ }
26
+
27
+ /**
28
+ * Calculate directory size recursively
29
+ */
30
+ static getDirectorySize(dirPath) {
31
+ let size = 0;
32
+
33
+ try {
34
+ const entries = fs.readdirSync(dirPath);
35
+ entries.forEach(entry => {
36
+ const fullPath = path.join(dirPath, entry);
37
+ const stats = fs.statSync(fullPath);
38
+
39
+ if (stats.isDirectory()) {
40
+ size += this.getDirectorySize(fullPath);
41
+ } else {
42
+ size += stats.size;
43
+ }
44
+ });
45
+ } catch (error) {
46
+ // Ignore errors and return partial size
47
+ }
48
+
49
+ return size;
50
+ }
51
+
52
+ /**
53
+ * Ensure directory exists with proper permissions
54
+ */
55
+ static ensureDirectory(dirPath, mode = 0o755) {
56
+ if (!fs.existsSync(dirPath)) {
57
+ fs.mkdirSync(dirPath, { recursive: true, mode });
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Copy file with error handling
63
+ */
64
+ static copyFile(source, destination, mode = 0o644) {
65
+ try {
66
+ fs.copyFileSync(source, destination);
67
+ fs.chmodSync(destination, mode);
68
+ return true;
69
+ } catch (error) {
70
+ return false;
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Check if path exists and is readable
76
+ */
77
+ static isReadable(filePath) {
78
+ try {
79
+ fs.accessSync(filePath, fs.constants.R_OK);
80
+ return true;
81
+ } catch (error) {
82
+ return false;
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Check if path exists and is writable
88
+ */
89
+ static isWritable(filePath) {
90
+ try {
91
+ fs.accessSync(filePath, fs.constants.W_OK);
92
+ return true;
93
+ } catch (error) {
94
+ return false;
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Get file stats safely
100
+ */
101
+ static getStats(filePath) {
102
+ try {
103
+ return fs.statSync(filePath);
104
+ } catch (error) {
105
+ return null;
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Read file safely with encoding
111
+ */
112
+ static readFile(filePath, encoding = 'utf8') {
113
+ try {
114
+ return fs.readFileSync(filePath, encoding);
115
+ } catch (error) {
116
+ return null;
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Write file safely with mode
122
+ */
123
+ static writeFile(filePath, content, mode = 0o644) {
124
+ try {
125
+ fs.writeFileSync(filePath, content, { mode });
126
+ return true;
127
+ } catch (error) {
128
+ return false;
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Remove file or directory safely
134
+ */
135
+ static remove(targetPath) {
136
+ try {
137
+ if (fs.existsSync(targetPath)) {
138
+ const stats = fs.statSync(targetPath);
139
+ if (stats.isDirectory()) {
140
+ fs.rmSync(targetPath, { recursive: true, force: true });
141
+ } else {
142
+ fs.unlinkSync(targetPath);
143
+ }
144
+ }
145
+ return true;
146
+ } catch (error) {
147
+ return false;
148
+ }
149
+ }
150
+ }
151
+
152
+ module.exports = FileSystemUtils;
package/lib/utils.js CHANGED
@@ -1,15 +1,19 @@
1
1
  // Utility functions for Claude Dev Toolkit
2
2
  const path = require('path');
3
3
  const fs = require('fs');
4
+ const FileSystemUtils = require('./utils/file-system-utils');
4
5
 
5
6
  module.exports = {
7
+ // Keep backward compatibility
6
8
  ensureDirectory: (dirPath) => {
7
- if (!fs.existsSync(dirPath)) {
8
- fs.mkdirSync(dirPath, { recursive: true });
9
- }
9
+ FileSystemUtils.ensureDirectory(dirPath);
10
10
  },
11
11
 
12
12
  isValidCommand: (commandName) => {
13
13
  return /^[a-z][a-z0-9-]*$/.test(commandName);
14
- }
14
+ },
15
+
16
+ // Export new utilities for migration
17
+ FileSystemUtils,
18
+ ClaudePathConfig: require('./utils/claude-path-config')
15
19
  };