@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.
- package/README.md +29 -9
- package/bin/claude-commands +184 -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/oidc-command.js +270 -0
- 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 +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
|
-
|
|
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
|
};
|