@paulduvall/claude-dev-toolkit 0.0.1-alpha.2 → 0.0.1-alpha.21

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 (143) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +88 -37
  3. package/bin/claude-commands +307 -65
  4. package/commands/active/xarchitecture.md +393 -0
  5. package/commands/active/xconfig.md +127 -0
  6. package/commands/active/xcontinue.md +92 -0
  7. package/commands/active/xdebug.md +130 -0
  8. package/commands/active/xdocs.md +178 -0
  9. package/commands/active/xexplore.md +94 -0
  10. package/commands/active/xgit.md +149 -0
  11. package/commands/active/xpipeline.md +152 -0
  12. package/commands/active/xquality.md +96 -0
  13. package/commands/active/xrefactor.md +198 -0
  14. package/commands/active/xrelease.md +142 -0
  15. package/commands/active/xsecurity.md +92 -0
  16. package/commands/active/xspec.md +174 -0
  17. package/commands/active/xtdd.md +151 -0
  18. package/commands/active/xtest.md +89 -0
  19. package/commands/active/xverify.md +80 -0
  20. package/commands/experiments/xact.md +742 -0
  21. package/commands/experiments/xanalytics.md +113 -0
  22. package/commands/experiments/xanalyze.md +70 -0
  23. package/commands/experiments/xapi.md +161 -0
  24. package/commands/experiments/xatomic.md +112 -0
  25. package/commands/experiments/xaws.md +85 -0
  26. package/commands/experiments/xcicd.md +337 -0
  27. package/commands/experiments/xcommit.md +122 -0
  28. package/commands/experiments/xcompliance.md +182 -0
  29. package/commands/experiments/xconstraints.md +89 -0
  30. package/commands/experiments/xcoverage.md +90 -0
  31. package/commands/experiments/xdb.md +102 -0
  32. package/commands/experiments/xdesign.md +121 -0
  33. package/commands/experiments/xdevcontainer.md +238 -0
  34. package/commands/experiments/xevaluate.md +111 -0
  35. package/commands/experiments/xfootnote.md +12 -0
  36. package/commands/experiments/xgenerate.md +117 -0
  37. package/commands/experiments/xgovernance.md +149 -0
  38. package/commands/experiments/xgreen.md +66 -0
  39. package/commands/experiments/xiac.md +118 -0
  40. package/commands/experiments/xincident.md +137 -0
  41. package/commands/experiments/xinfra.md +115 -0
  42. package/commands/experiments/xknowledge.md +115 -0
  43. package/commands/experiments/xmaturity.md +120 -0
  44. package/commands/experiments/xmetrics.md +118 -0
  45. package/commands/experiments/xmonitoring.md +128 -0
  46. package/commands/experiments/xnew.md +903 -0
  47. package/commands/experiments/xobservable.md +114 -0
  48. package/commands/experiments/xoidc.md +165 -0
  49. package/commands/experiments/xoptimize.md +115 -0
  50. package/commands/experiments/xperformance.md +112 -0
  51. package/commands/experiments/xplanning.md +131 -0
  52. package/commands/experiments/xpolicy.md +115 -0
  53. package/commands/experiments/xproduct.md +98 -0
  54. package/commands/experiments/xreadiness.md +75 -0
  55. package/commands/experiments/xred.md +55 -0
  56. package/commands/experiments/xrisk.md +128 -0
  57. package/commands/experiments/xrules.md +124 -0
  58. package/commands/experiments/xsandbox.md +120 -0
  59. package/commands/experiments/xscan.md +102 -0
  60. package/commands/experiments/xsetup.md +123 -0
  61. package/commands/experiments/xtemplate.md +116 -0
  62. package/commands/experiments/xtrace.md +212 -0
  63. package/commands/experiments/xux.md +171 -0
  64. package/commands/experiments/xvalidate.md +104 -0
  65. package/commands/experiments/xworkflow.md +113 -0
  66. package/hooks/.smellrc.example.json +19 -0
  67. package/hooks/README.md +263 -0
  68. package/hooks/check-commit-signing.py +127 -0
  69. package/hooks/check-complexity.py +38 -0
  70. package/hooks/check-security.py +37 -0
  71. package/hooks/claude-wrapper.sh +29 -0
  72. package/hooks/config.py +110 -0
  73. package/hooks/file-logger.sh +100 -0
  74. package/hooks/lib/argument-parser.sh +427 -0
  75. package/hooks/lib/config-constants.sh +230 -0
  76. package/hooks/lib/context-manager.sh +560 -0
  77. package/hooks/lib/error-handler.sh +423 -0
  78. package/hooks/lib/execution-engine.sh +444 -0
  79. package/hooks/lib/execution-results.sh +113 -0
  80. package/hooks/lib/execution-simulation.sh +114 -0
  81. package/hooks/lib/field-validators.sh +104 -0
  82. package/hooks/lib/file-utils.sh +398 -0
  83. package/hooks/lib/subagent-discovery.sh +468 -0
  84. package/hooks/lib/subagent-validator.sh +407 -0
  85. package/hooks/lib/validation-reporter.sh +134 -0
  86. package/hooks/on-error-debug.sh +226 -0
  87. package/hooks/pre-commit-quality.sh +204 -0
  88. package/hooks/pre-commit-test-runner.sh +132 -0
  89. package/hooks/pre-write-security.sh +115 -0
  90. package/hooks/prevent-credential-exposure.sh +279 -0
  91. package/hooks/security_bandit.py +177 -0
  92. package/hooks/security_checks.py +97 -0
  93. package/hooks/security_secrets.py +81 -0
  94. package/hooks/security_trojan.py +61 -0
  95. package/hooks/settings.example.json +52 -0
  96. package/hooks/smell_checks.py +238 -0
  97. package/hooks/smell_javascript.py +231 -0
  98. package/hooks/smell_python.py +110 -0
  99. package/hooks/smell_ruff.py +70 -0
  100. package/hooks/smell_types.py +72 -0
  101. package/hooks/subagent-trigger-simple.sh +202 -0
  102. package/hooks/subagent-trigger.sh +253 -0
  103. package/hooks/suppression.py +82 -0
  104. package/hooks/tab-color.sh +70 -0
  105. package/hooks/verify-before-edit.sh +135 -0
  106. package/lib/backup-restore-command.js +140 -0
  107. package/lib/base/base-command.js +252 -0
  108. package/lib/base/command-result.js +184 -0
  109. package/lib/config/constants.js +255 -0
  110. package/lib/config.js +48 -6
  111. package/lib/configure-command.js +428 -0
  112. package/lib/dependency-validator.js +64 -5
  113. package/lib/hook-installer-core.js +2 -2
  114. package/lib/installation-instruction-generator.js +213 -495
  115. package/lib/installer.js +134 -56
  116. package/lib/oidc-command.js +740 -0
  117. package/lib/services/backup-list-service.js +226 -0
  118. package/lib/services/backup-service.js +230 -0
  119. package/lib/services/command-installer-service.js +217 -0
  120. package/lib/services/logger-service.js +201 -0
  121. package/lib/services/package-manager-service.js +319 -0
  122. package/lib/services/platform-instruction-service.js +294 -0
  123. package/lib/services/recovery-instruction-service.js +348 -0
  124. package/lib/services/restore-service.js +221 -0
  125. package/lib/setup-command.js +359 -0
  126. package/lib/setup-wizard.js +155 -262
  127. package/lib/uninstall-command.js +100 -0
  128. package/lib/utils/claude-path-config.js +184 -0
  129. package/lib/utils/file-system-utils.js +152 -0
  130. package/lib/utils.js +8 -4
  131. package/lib/verify-command.js +430 -0
  132. package/package.json +7 -3
  133. package/scripts/postinstall.js +172 -157
  134. package/subagents/debug-specialist.md +7 -0
  135. package/templates/README.md +115 -0
  136. package/templates/basic-settings.json +30 -0
  137. package/templates/comprehensive-settings.json +57 -0
  138. package/templates/global-claude.md +344 -0
  139. package/templates/hybrid-hook-config.yaml +132 -0
  140. package/templates/security-focused-settings.json +62 -0
  141. package/templates/subagent-hooks.yaml +188 -0
  142. package/lib/package-manager-service.js +0 -270
  143. package/subagents/debug-context.md +0 -197
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Backup List Service
3
+ * Service for listing and managing backup inventory
4
+ */
5
+
6
+ const path = require('path');
7
+ const fs = require('fs');
8
+ const FileSystemUtils = require('../utils/file-system-utils');
9
+ const ClaudePathConfig = require('../utils/claude-path-config');
10
+
11
+ class BackupListService {
12
+ constructor(config = null) {
13
+ this.config = config || new ClaudePathConfig();
14
+ }
15
+
16
+ /**
17
+ * List all available backups
18
+ */
19
+ async list() {
20
+ if (!fs.existsSync(this.config.backupsDir)) {
21
+ return [];
22
+ }
23
+
24
+ const backups = [];
25
+
26
+ try {
27
+ const entries = fs.readdirSync(this.config.backupsDir);
28
+
29
+ for (const entry of entries) {
30
+ const fullPath = path.join(this.config.backupsDir, entry);
31
+ const stats = FileSystemUtils.getStats(fullPath);
32
+
33
+ if (!stats) continue;
34
+
35
+ // Compressed backups
36
+ if (entry.endsWith('.tar.gz')) {
37
+ const name = entry.replace('.tar.gz', '');
38
+ backups.push({
39
+ name,
40
+ type: 'compressed',
41
+ size: stats.size,
42
+ modified: stats.mtime,
43
+ path: fullPath
44
+ });
45
+ }
46
+ // Directory backups
47
+ else if (stats.isDirectory() && !entry.startsWith('.')) {
48
+ const metadata = await this.readBackupMetadata(fullPath);
49
+
50
+ backups.push({
51
+ name: entry,
52
+ type: 'directory',
53
+ size: FileSystemUtils.getDirectorySize(fullPath),
54
+ modified: stats.mtime,
55
+ path: fullPath,
56
+ metadata
57
+ });
58
+ }
59
+ }
60
+
61
+ // Sort by modification time (newest first)
62
+ backups.sort((a, b) => b.modified - a.modified);
63
+
64
+ return backups;
65
+ } catch (error) {
66
+ throw new Error(`Failed to list backups: ${error.message}`);
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Display backups in user-friendly format
72
+ */
73
+ async display() {
74
+ console.log('📦 Available Backups:\n');
75
+
76
+ const backups = await this.list();
77
+
78
+ if (backups.length === 0) {
79
+ console.log('No backups found');
80
+ return [];
81
+ }
82
+
83
+ backups.forEach(backup => {
84
+ const date = backup.modified.toLocaleString();
85
+ const size = FileSystemUtils.formatSize(backup.size);
86
+ const type = backup.type === 'compressed' ? '📦' : '📁';
87
+
88
+ console.log(`${type} ${backup.name}`);
89
+ console.log(` Date: ${date}`);
90
+ console.log(` Size: ${size}`);
91
+
92
+ if (backup.metadata) {
93
+ console.log(` Files: ${backup.metadata.totalFiles}`);
94
+ if (backup.metadata.components) {
95
+ const components = Object.keys(backup.metadata.components).filter(k => backup.metadata.components[k]);
96
+ if (components.length > 0) {
97
+ console.log(` Contains: ${components.join(', ')}`);
98
+ }
99
+ }
100
+ }
101
+ console.log('');
102
+ });
103
+
104
+ console.log(`Total: ${backups.length} backup(s)`);
105
+ console.log('\n💡 To restore a backup, run:');
106
+ console.log(' claude-commands restore <backup-name>');
107
+
108
+ return backups;
109
+ }
110
+
111
+ /**
112
+ * Find a specific backup by name
113
+ */
114
+ async findBackup(name) {
115
+ const backups = await this.list();
116
+ return backups.find(backup => backup.name === name);
117
+ }
118
+
119
+ /**
120
+ * Get backup details
121
+ */
122
+ async getBackupDetails(name) {
123
+ const backup = await this.findBackup(name);
124
+
125
+ if (!backup) {
126
+ throw new Error(`Backup '${name}' not found`);
127
+ }
128
+
129
+ const details = {
130
+ ...backup,
131
+ exists: fs.existsSync(backup.path),
132
+ readable: FileSystemUtils.isReadable(backup.path)
133
+ };
134
+
135
+ // Add component details if metadata available
136
+ if (backup.metadata && backup.metadata.components) {
137
+ details.components = backup.metadata.components;
138
+ }
139
+
140
+ return details;
141
+ }
142
+
143
+ /**
144
+ * Read backup metadata if available
145
+ */
146
+ async readBackupMetadata(backupPath) {
147
+ const metadataPath = path.join(backupPath, 'backup-metadata.json');
148
+
149
+ if (!FileSystemUtils.isReadable(metadataPath)) {
150
+ return null;
151
+ }
152
+
153
+ try {
154
+ const content = FileSystemUtils.readFile(metadataPath);
155
+ return JSON.parse(content);
156
+ } catch (error) {
157
+ // Return null for invalid metadata
158
+ return null;
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Clean up old backups (keep only N most recent)
164
+ */
165
+ async cleanup(keepCount = 10) {
166
+ const backups = await this.list();
167
+
168
+ if (backups.length <= keepCount) {
169
+ return { cleaned: 0, kept: backups.length };
170
+ }
171
+
172
+ const toRemove = backups.slice(keepCount);
173
+ let cleanedCount = 0;
174
+
175
+ for (const backup of toRemove) {
176
+ try {
177
+ if (FileSystemUtils.remove(backup.path)) {
178
+ cleanedCount++;
179
+ }
180
+ } catch (error) {
181
+ console.warn(`⚠️ Warning: Could not remove backup ${backup.name} - ${error.message}`);
182
+ }
183
+ }
184
+
185
+ return {
186
+ cleaned: cleanedCount,
187
+ kept: backups.length - cleanedCount
188
+ };
189
+ }
190
+
191
+ /**
192
+ * Get backup statistics
193
+ */
194
+ async getStats() {
195
+ const backups = await this.list();
196
+
197
+ let totalSize = 0;
198
+ let oldestDate = null;
199
+ let newestDate = null;
200
+ const types = { compressed: 0, directory: 0 };
201
+
202
+ backups.forEach(backup => {
203
+ totalSize += backup.size;
204
+ types[backup.type]++;
205
+
206
+ if (!oldestDate || backup.modified < oldestDate) {
207
+ oldestDate = backup.modified;
208
+ }
209
+ if (!newestDate || backup.modified > newestDate) {
210
+ newestDate = backup.modified;
211
+ }
212
+ });
213
+
214
+ return {
215
+ count: backups.length,
216
+ totalSize,
217
+ formattedSize: FileSystemUtils.formatSize(totalSize),
218
+ types,
219
+ oldestDate,
220
+ newestDate,
221
+ averageSize: backups.length > 0 ? totalSize / backups.length : 0
222
+ };
223
+ }
224
+ }
225
+
226
+ module.exports = BackupListService;
@@ -0,0 +1,230 @@
1
+ /**
2
+ * Backup Service
3
+ * Focused service for creating backups of Claude Code configuration
4
+ */
5
+
6
+ const path = require('path');
7
+ const fs = require('fs');
8
+ const FileSystemUtils = require('../utils/file-system-utils');
9
+ const ClaudePathConfig = require('../utils/claude-path-config');
10
+
11
+ class BackupService {
12
+ constructor(config = null) {
13
+ this.config = config || new ClaudePathConfig();
14
+ this.backedUpCount = 0;
15
+ }
16
+
17
+ /**
18
+ * Create a complete backup
19
+ */
20
+ async create(name = null) {
21
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').split('.')[0];
22
+ const backupName = name || `backup-${timestamp}`;
23
+ const backupPath = path.join(this.config.backupsDir, backupName);
24
+
25
+ // Ensure backup directory exists
26
+ FileSystemUtils.ensureDirectory(this.config.backupsDir);
27
+
28
+ // Check if backup already exists
29
+ if (fs.existsSync(backupPath)) {
30
+ throw new Error(`Backup '${backupName}' already exists`);
31
+ }
32
+
33
+ // Create backup directory
34
+ FileSystemUtils.ensureDirectory(backupPath);
35
+
36
+ // Reset counter
37
+ this.backedUpCount = 0;
38
+
39
+ // Backup components
40
+ const components = {};
41
+ let totalFiles = 0;
42
+ let totalSize = 0;
43
+
44
+ // Backup settings
45
+ if (FileSystemUtils.isReadable(this.config.settingsPath)) {
46
+ await this.backupSettings(backupPath);
47
+ components.settings = true;
48
+ totalFiles++;
49
+ const stats = FileSystemUtils.getStats(this.config.settingsPath);
50
+ if (stats) totalSize += stats.size;
51
+ }
52
+
53
+ // Backup commands
54
+ const commandsResult = await this.backupCommands(backupPath);
55
+ if (commandsResult.count > 0) {
56
+ components.commands = true;
57
+ totalFiles += commandsResult.count;
58
+ totalSize += commandsResult.size;
59
+ }
60
+
61
+ // Backup hooks
62
+ const hooksResult = await this.backupHooks(backupPath);
63
+ if (hooksResult.count > 0) {
64
+ components.hooks = true;
65
+ totalFiles += hooksResult.count;
66
+ totalSize += hooksResult.size;
67
+ }
68
+
69
+ // Create metadata
70
+ const metadata = this.createMetadata(backupName, components, totalFiles, totalSize);
71
+ await this.saveMetadata(backupPath, metadata);
72
+
73
+ return {
74
+ name: backupName,
75
+ path: backupPath,
76
+ components,
77
+ totalFiles,
78
+ totalSize,
79
+ metadata
80
+ };
81
+ }
82
+
83
+ /**
84
+ * Backup settings.json file
85
+ */
86
+ async backupSettings(backupPath) {
87
+ if (!FileSystemUtils.isReadable(this.config.settingsPath)) {
88
+ return false;
89
+ }
90
+
91
+ const destPath = path.join(backupPath, 'settings.json');
92
+ const success = FileSystemUtils.copyFile(this.config.settingsPath, destPath);
93
+
94
+ if (success) {
95
+ this.backedUpCount++;
96
+ console.log('✅ Backed up settings.json');
97
+ }
98
+
99
+ return success;
100
+ }
101
+
102
+ /**
103
+ * Backup commands directory
104
+ */
105
+ async backupCommands(backupPath) {
106
+ if (!fs.existsSync(this.config.commandsDir)) {
107
+ return { count: 0, size: 0 };
108
+ }
109
+
110
+ const commandsBackupDir = path.join(backupPath, 'commands');
111
+ FileSystemUtils.ensureDirectory(commandsBackupDir);
112
+
113
+ let count = 0;
114
+ let size = 0;
115
+
116
+ try {
117
+ const files = fs.readdirSync(this.config.commandsDir);
118
+
119
+ for (const file of files) {
120
+ if (file.endsWith('.md')) {
121
+ const sourcePath = path.join(this.config.commandsDir, file);
122
+ const destPath = path.join(commandsBackupDir, file);
123
+
124
+ if (FileSystemUtils.copyFile(sourcePath, destPath)) {
125
+ count++;
126
+ const stats = FileSystemUtils.getStats(sourcePath);
127
+ if (stats) size += stats.size;
128
+ this.backedUpCount++;
129
+ }
130
+ }
131
+ }
132
+
133
+ if (count > 0) {
134
+ console.log(`✅ Backed up ${count} commands`);
135
+ }
136
+ } catch (error) {
137
+ console.warn(`⚠️ Warning: Could not backup commands - ${error.message}`);
138
+ }
139
+
140
+ return { count, size };
141
+ }
142
+
143
+ /**
144
+ * Backup hooks directory
145
+ */
146
+ async backupHooks(backupPath) {
147
+ const hooksDir = this.config.hooksDir;
148
+
149
+ if (!fs.existsSync(hooksDir)) {
150
+ return { count: 0, size: 0 };
151
+ }
152
+
153
+ const hooksBackupDir = path.join(backupPath, 'hooks');
154
+ FileSystemUtils.ensureDirectory(hooksBackupDir);
155
+
156
+ let count = 0;
157
+ let size = 0;
158
+
159
+ try {
160
+ const files = fs.readdirSync(hooksDir);
161
+
162
+ for (const file of files) {
163
+ const sourcePath = path.join(hooksDir, file);
164
+ const stats = FileSystemUtils.getStats(sourcePath);
165
+
166
+ if (stats && stats.isFile()) {
167
+ const destPath = path.join(hooksBackupDir, file);
168
+
169
+ if (FileSystemUtils.copyFile(sourcePath, destPath)) {
170
+ count++;
171
+ size += stats.size;
172
+ this.backedUpCount++;
173
+ }
174
+ }
175
+ }
176
+
177
+ if (count > 0) {
178
+ console.log(`✅ Backed up ${count} hooks`);
179
+ }
180
+ } catch (error) {
181
+ console.warn(`⚠️ Warning: Could not backup hooks - ${error.message}`);
182
+ }
183
+
184
+ return { count, size };
185
+ }
186
+
187
+ /**
188
+ * Create backup metadata
189
+ */
190
+ createMetadata(name, components, totalFiles, totalSize) {
191
+ return {
192
+ name,
193
+ timestamp: new Date().toISOString(),
194
+ components,
195
+ totalFiles,
196
+ totalSize,
197
+ claudeVersion: this.getClaudeVersion(),
198
+ system: {
199
+ platform: require('os').platform(),
200
+ arch: require('os').arch(),
201
+ nodeVersion: process.version
202
+ }
203
+ };
204
+ }
205
+
206
+ /**
207
+ * Save metadata to backup directory
208
+ */
209
+ async saveMetadata(backupPath, metadata) {
210
+ const metadataPath = path.join(backupPath, 'backup-metadata.json');
211
+ const content = JSON.stringify(metadata, null, 2);
212
+
213
+ return FileSystemUtils.writeFile(metadataPath, content);
214
+ }
215
+
216
+ /**
217
+ * Get Claude Code version
218
+ */
219
+ getClaudeVersion() {
220
+ try {
221
+ const packagePath = path.join(__dirname, '..', '..', 'package.json');
222
+ const packageData = JSON.parse(FileSystemUtils.readFile(packagePath) || '{}');
223
+ return packageData.version || 'unknown';
224
+ } catch (error) {
225
+ return 'unknown';
226
+ }
227
+ }
228
+ }
229
+
230
+ module.exports = BackupService;
@@ -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;