claude-code-templates 1.8.3 → 1.10.0

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.
@@ -0,0 +1,1086 @@
1
+ const chalk = require('chalk');
2
+ const fs = require('fs-extra');
3
+ const path = require('path');
4
+ const os = require('os');
5
+ const { execSync } = require('child_process');
6
+ const ora = require('ora');
7
+ const boxen = require('boxen');
8
+
9
+ /**
10
+ * Health Check module for Claude Code CLI
11
+ * Validates system requirements, configuration, and project setup
12
+ */
13
+ class HealthChecker {
14
+ constructor() {
15
+ this.results = {
16
+ system: [],
17
+ claudeCode: [],
18
+ project: [],
19
+ commands: [],
20
+ hooks: []
21
+ };
22
+ this.totalChecks = 0;
23
+ this.passedChecks = 0;
24
+ }
25
+
26
+ /**
27
+ * Run comprehensive health check
28
+ */
29
+ async runHealthCheck() {
30
+ console.log(chalk.blue('šŸ” Running Health Check...'));
31
+ console.log('');
32
+
33
+ // System requirements check
34
+ await this.checkSystemRequirementsWithSpinner();
35
+ await this.sleep(3000);
36
+
37
+ // Claude Code configuration check
38
+ await this.checkClaudeCodeSetupWithSpinner();
39
+ await this.sleep(3000);
40
+
41
+ // Project configuration check
42
+ await this.checkProjectSetupWithSpinner();
43
+ await this.sleep(3000);
44
+
45
+ // Custom commands check
46
+ await this.checkCustomCommandsWithSpinner();
47
+ await this.sleep(3000);
48
+
49
+ // Hooks configuration check
50
+ await this.checkHooksConfigurationWithSpinner();
51
+ await this.sleep(1000); // Shorter delay before summary
52
+
53
+ // Display final summary
54
+ return this.generateSummary();
55
+ }
56
+
57
+ /**
58
+ * Check system requirements with spinner and immediate results
59
+ */
60
+ async checkSystemRequirementsWithSpinner() {
61
+ console.log(chalk.cyan('\nā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”'));
62
+ console.log(chalk.cyan('│ SYSTEM REQUIREMENTS │'));
63
+ console.log(chalk.cyan('ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜'));
64
+
65
+ // Operating System
66
+ const osSpinner = ora('Checking Operating System...').start();
67
+ const osInfo = this.checkOperatingSystem();
68
+ this.addResult('system', 'Operating System', osInfo.status, osInfo.message);
69
+ osSpinner.succeed(`${this.getStatusIcon(osInfo.status)} Operating System │ ${osInfo.message}`);
70
+
71
+ // Node.js version
72
+ const nodeSpinner = ora('Checking Node.js Version...').start();
73
+ const nodeInfo = this.checkNodeVersion();
74
+ this.addResult('system', 'Node.js Version', nodeInfo.status, nodeInfo.message);
75
+ nodeSpinner.succeed(`${this.getStatusIcon(nodeInfo.status)} Node.js Version │ ${nodeInfo.message}`);
76
+
77
+ // Memory
78
+ const memorySpinner = ora('Checking Memory Available...').start();
79
+ const memoryInfo = this.checkMemory();
80
+ this.addResult('system', 'Memory Available', memoryInfo.status, memoryInfo.message);
81
+ memorySpinner.succeed(`${this.getStatusIcon(memoryInfo.status)} Memory Available │ ${memoryInfo.message}`);
82
+
83
+ // Network connectivity (this one takes time)
84
+ const networkSpinner = ora('Testing Network Connection...').start();
85
+ const networkInfo = await this.checkNetworkConnectivity();
86
+ this.addResult('system', 'Network Connection', networkInfo.status, networkInfo.message);
87
+ networkSpinner.succeed(`${this.getStatusIcon(networkInfo.status)} Network Connection │ ${networkInfo.message}`);
88
+
89
+ // Shell environment
90
+ const shellSpinner = ora('Checking Shell Environment...').start();
91
+ const shellInfo = this.checkShellEnvironment();
92
+ this.addResult('system', 'Shell Environment', shellInfo.status, shellInfo.message);
93
+ shellSpinner.succeed(`${this.getStatusIcon(shellInfo.status)} Shell Environment │ ${shellInfo.message}`);
94
+ }
95
+
96
+ /**
97
+ * Check Claude Code setup with spinner and immediate results
98
+ */
99
+ async checkClaudeCodeSetupWithSpinner() {
100
+ console.log(chalk.cyan('\nā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”'));
101
+ console.log(chalk.cyan('│ CLAUDE CODE SETUP │'));
102
+ console.log(chalk.cyan('ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜'));
103
+
104
+ // Installation check
105
+ const installSpinner = ora('Checking Claude Code Installation...').start();
106
+ const installInfo = this.checkClaudeCodeInstallation();
107
+ this.addResult('claudeCode', 'Installation', installInfo.status, installInfo.message);
108
+ installSpinner.succeed(`${this.getStatusIcon(installInfo.status)} Installation │ ${installInfo.message}`);
109
+
110
+ // Authentication check (this one can take time)
111
+ const authSpinner = ora('Verifying Authentication...').start();
112
+ const authInfo = this.checkAuthentication();
113
+ this.addResult('claudeCode', 'Authentication', authInfo.status, authInfo.message);
114
+ authSpinner.succeed(`${this.getStatusIcon(authInfo.status)} Authentication │ ${authInfo.message}`);
115
+
116
+ // Auto-updates check
117
+ const updateSpinner = ora('Checking Auto-updates...').start();
118
+ const updateInfo = this.checkAutoUpdates();
119
+ this.addResult('claudeCode', 'Auto-updates', updateInfo.status, updateInfo.message);
120
+ updateSpinner.succeed(`${this.getStatusIcon(updateInfo.status)} Auto-updates │ ${updateInfo.message}`);
121
+
122
+ // Permissions check
123
+ const permissionSpinner = ora('Checking Permissions...').start();
124
+ const permissionInfo = this.checkPermissions();
125
+ this.addResult('claudeCode', 'Permissions', permissionInfo.status, permissionInfo.message);
126
+ permissionSpinner.succeed(`${this.getStatusIcon(permissionInfo.status)} Permissions │ ${permissionInfo.message}`);
127
+ }
128
+
129
+ /**
130
+ * Check project setup with spinner and immediate results
131
+ */
132
+ async checkProjectSetupWithSpinner() {
133
+ console.log(chalk.cyan('\nā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”'));
134
+ console.log(chalk.cyan('│ PROJECT SETUP │'));
135
+ console.log(chalk.cyan('ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜'));
136
+
137
+ // Project structure
138
+ const structureSpinner = ora('Scanning Project Structure...').start();
139
+ const structureInfo = this.checkProjectStructure();
140
+ this.addResult('project', 'Project Structure', structureInfo.status, structureInfo.message);
141
+ structureSpinner.succeed(`${this.getStatusIcon(structureInfo.status)} Project Structure │ ${structureInfo.message}`);
142
+
143
+ // Configuration files
144
+ const configSpinner = ora('Checking Configuration Files...').start();
145
+ const configInfo = this.checkConfigurationFiles();
146
+ this.addResult('project', 'Configuration Files', configInfo.status, configInfo.message);
147
+ configSpinner.succeed(`${this.getStatusIcon(configInfo.status)} Configuration Files │ ${configInfo.message}`);
148
+
149
+ // User settings validation
150
+ const userSettingsSpinner = ora('Validating User Settings...').start();
151
+ const userSettingsInfo = this.checkUserSettings();
152
+ this.addResult('project', 'User Settings', userSettingsInfo.status, userSettingsInfo.message);
153
+ userSettingsSpinner.succeed(`${this.getStatusIcon(userSettingsInfo.status)} User Settings │ ${userSettingsInfo.message}`);
154
+
155
+ // Project settings validation
156
+ const projectSettingsSpinner = ora('Validating Project Settings...').start();
157
+ const projectSettingsInfo = this.checkProjectSettings();
158
+ this.addResult('project', 'Project Settings', projectSettingsInfo.status, projectSettingsInfo.message);
159
+ projectSettingsSpinner.succeed(`${this.getStatusIcon(projectSettingsInfo.status)} Project Settings │ ${projectSettingsInfo.message}`);
160
+
161
+ // Local settings validation
162
+ const localSettingsSpinner = ora('Validating Local Settings...').start();
163
+ const localSettingsInfo = this.checkLocalSettings();
164
+ this.addResult('project', 'Local Settings', localSettingsInfo.status, localSettingsInfo.message);
165
+ localSettingsSpinner.succeed(`${this.getStatusIcon(localSettingsInfo.status)} Local Settings │ ${localSettingsInfo.message}`);
166
+ }
167
+
168
+ /**
169
+ * Check custom commands with spinner and immediate results
170
+ */
171
+ async checkCustomCommandsWithSpinner() {
172
+ console.log(chalk.cyan('\nā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”'));
173
+ console.log(chalk.cyan('│ CUSTOM COMMANDS │'));
174
+ console.log(chalk.cyan('ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜'));
175
+
176
+ // Project commands
177
+ const projectSpinner = ora('Scanning Project Commands...').start();
178
+ const projectCommands = this.checkProjectCommands();
179
+ this.addResult('commands', 'Project Commands', projectCommands.status, projectCommands.message);
180
+ projectSpinner.succeed(`${this.getStatusIcon(projectCommands.status)} Project Commands │ ${projectCommands.message}`);
181
+
182
+ // Personal commands
183
+ const personalSpinner = ora('Scanning Personal Commands...').start();
184
+ const personalCommands = this.checkPersonalCommands();
185
+ this.addResult('commands', 'Personal Commands', personalCommands.status, personalCommands.message);
186
+ personalSpinner.succeed(`${this.getStatusIcon(personalCommands.status)} Personal Commands │ ${personalCommands.message}`);
187
+
188
+ // Command syntax validation
189
+ const syntaxSpinner = ora('Validating Command Syntax...').start();
190
+ const syntaxInfo = this.checkCommandSyntax();
191
+ this.addResult('commands', 'Command Syntax', syntaxInfo.status, syntaxInfo.message);
192
+ syntaxSpinner.succeed(`${this.getStatusIcon(syntaxInfo.status)} Command Syntax │ ${syntaxInfo.message}`);
193
+ }
194
+
195
+ /**
196
+ * Check hooks configuration with spinner and immediate results
197
+ */
198
+ async checkHooksConfigurationWithSpinner() {
199
+ console.log(chalk.cyan('\nā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”'));
200
+ console.log(chalk.cyan('│ HOOKS │'));
201
+ console.log(chalk.cyan('ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜'));
202
+
203
+ // User hooks
204
+ const userSpinner = ora('Checking User Hooks...').start();
205
+ const userHooks = this.checkUserHooks();
206
+ this.addResult('hooks', 'User Hooks', userHooks.status, userHooks.message);
207
+ userSpinner.succeed(`${this.getStatusIcon(userHooks.status)} User Hooks │ ${userHooks.message}`);
208
+
209
+ // Project hooks
210
+ const projectSpinner = ora('Checking Project Hooks...').start();
211
+ const projectHooks = this.checkProjectHooks();
212
+ this.addResult('hooks', 'Project Hooks', projectHooks.status, projectHooks.message);
213
+ projectSpinner.succeed(`${this.getStatusIcon(projectHooks.status)} Project Hooks │ ${projectHooks.message}`);
214
+
215
+ // Local hooks
216
+ const localSpinner = ora('Checking Local Hooks...').start();
217
+ const localHooks = this.checkLocalHooks();
218
+ this.addResult('hooks', 'Local Hooks', localHooks.status, localHooks.message);
219
+ localSpinner.succeed(`${this.getStatusIcon(localHooks.status)} Local Hooks │ ${localHooks.message}`);
220
+
221
+ // Hook commands validation
222
+ const hookSpinner = ora('Validating Hook Commands...').start();
223
+ const hookCommands = this.checkHookCommands();
224
+ this.addResult('hooks', 'Hook Commands', hookCommands.status, hookCommands.message);
225
+ hookSpinner.succeed(`${this.getStatusIcon(hookCommands.status)} Hook Commands │ ${hookCommands.message}`);
226
+
227
+ // MCP hooks
228
+ const mcpSpinner = ora('Scanning MCP Hooks...').start();
229
+ const mcpHooks = this.checkMCPHooks();
230
+ this.addResult('hooks', 'MCP Hooks', mcpHooks.status, mcpHooks.message);
231
+ mcpSpinner.succeed(`${this.getStatusIcon(mcpHooks.status)} MCP Hooks │ ${mcpHooks.message}`);
232
+ }
233
+
234
+ /**
235
+ * Individual check implementations
236
+ */
237
+ checkOperatingSystem() {
238
+ const platform = os.platform();
239
+ const release = os.release();
240
+
241
+ const supportedPlatforms = {
242
+ 'darwin': { name: 'macOS', minVersion: '10.15' },
243
+ 'linux': { name: 'Linux', minVersion: '20.04' },
244
+ 'win32': { name: 'Windows', minVersion: '10' }
245
+ };
246
+
247
+ if (supportedPlatforms[platform]) {
248
+ const osName = supportedPlatforms[platform].name;
249
+ return {
250
+ status: 'pass',
251
+ message: `${osName} ${release} (compatible)`
252
+ };
253
+ }
254
+
255
+ return {
256
+ status: 'fail',
257
+ message: `${platform} ${release} (not officially supported)`
258
+ };
259
+ }
260
+
261
+ checkNodeVersion() {
262
+ try {
263
+ const version = process.version;
264
+ const majorVersion = parseInt(version.split('.')[0].substring(1));
265
+
266
+ if (majorVersion >= 18) {
267
+ return {
268
+ status: 'pass',
269
+ message: `${version} (compatible)`
270
+ };
271
+ } else {
272
+ return {
273
+ status: 'fail',
274
+ message: `${version} (requires Node.js 18+)`
275
+ };
276
+ }
277
+ } catch (error) {
278
+ return {
279
+ status: 'fail',
280
+ message: 'Node.js not found'
281
+ };
282
+ }
283
+ }
284
+
285
+ checkMemory() {
286
+ const totalMemory = os.totalmem();
287
+ const freeMemory = os.freemem();
288
+ const totalGB = (totalMemory / (1024 * 1024 * 1024)).toFixed(1);
289
+ const freeGB = (freeMemory / (1024 * 1024 * 1024)).toFixed(1);
290
+
291
+ if (totalMemory >= 4 * 1024 * 1024 * 1024) { // 4GB
292
+ return {
293
+ status: 'pass',
294
+ message: `${totalGB}GB total, ${freeGB}GB free (sufficient)`
295
+ };
296
+ } else {
297
+ return {
298
+ status: 'warn',
299
+ message: `${totalGB}GB total (recommended 4GB+)`
300
+ };
301
+ }
302
+ }
303
+
304
+ async checkNetworkConnectivity() {
305
+ try {
306
+ const https = require('https');
307
+ return new Promise((resolve) => {
308
+ const req = https.get('https://api.anthropic.com', { timeout: 5000 }, (res) => {
309
+ resolve({
310
+ status: 'pass',
311
+ message: 'Connected to Anthropic API'
312
+ });
313
+ });
314
+
315
+ req.on('error', () => {
316
+ resolve({
317
+ status: 'fail',
318
+ message: 'Cannot reach Anthropic API'
319
+ });
320
+ });
321
+
322
+ req.on('timeout', () => {
323
+ resolve({
324
+ status: 'warn',
325
+ message: 'Slow connection to Anthropic API'
326
+ });
327
+ });
328
+ });
329
+ } catch (error) {
330
+ return {
331
+ status: 'fail',
332
+ message: 'Network connectivity test failed'
333
+ };
334
+ }
335
+ }
336
+
337
+ checkShellEnvironment() {
338
+ const shell = process.env.SHELL || 'unknown';
339
+ const shellName = path.basename(shell);
340
+
341
+ const supportedShells = ['bash', 'zsh', 'fish'];
342
+
343
+ if (supportedShells.includes(shellName)) {
344
+ if (shellName === 'zsh') {
345
+ return {
346
+ status: 'pass',
347
+ message: `${shellName} (excellent autocompletion support)`
348
+ };
349
+ } else {
350
+ return {
351
+ status: 'pass',
352
+ message: `${shellName} (compatible)`
353
+ };
354
+ }
355
+ } else {
356
+ return {
357
+ status: 'warn',
358
+ message: `${shellName} (consider using bash, zsh, or fish)`
359
+ };
360
+ }
361
+ }
362
+
363
+ checkClaudeCodeInstallation() {
364
+ try {
365
+ // Try to find claude-code package
366
+ const packagePath = path.join(process.cwd(), 'node_modules', '@anthropic-ai', 'claude-code');
367
+ if (fs.existsSync(packagePath)) {
368
+ const packageJson = require(path.join(packagePath, 'package.json'));
369
+ return {
370
+ status: 'pass',
371
+ message: `v${packageJson.version} (locally installed)`
372
+ };
373
+ }
374
+
375
+ // Check global installation
376
+ try {
377
+ const output = execSync('claude --version', { encoding: 'utf8', stdio: 'pipe' });
378
+ return {
379
+ status: 'pass',
380
+ message: `${output.trim()} (globally installed)`
381
+ };
382
+ } catch (error) {
383
+ return {
384
+ status: 'fail',
385
+ message: 'Claude Code CLI not found'
386
+ };
387
+ }
388
+ } catch (error) {
389
+ return {
390
+ status: 'fail',
391
+ message: 'Installation check failed'
392
+ };
393
+ }
394
+ }
395
+
396
+ checkAuthentication() {
397
+ try {
398
+ // Try to run claude command to check authentication status
399
+ const output = execSync('claude auth status 2>&1', {
400
+ encoding: 'utf8',
401
+ stdio: 'pipe',
402
+ timeout: 5000
403
+ });
404
+
405
+ if (output.includes('authenticated') || output.includes('logged in') || output.includes('active')) {
406
+ return {
407
+ status: 'pass',
408
+ message: 'Authenticated and active'
409
+ };
410
+ }
411
+
412
+ // If command runs but no clear authentication status
413
+ return {
414
+ status: 'warn',
415
+ message: 'Authentication status unclear'
416
+ };
417
+
418
+ } catch (error) {
419
+ // Try alternative method - check for session/config files
420
+ const homeDir = os.homedir();
421
+ const possibleAuthPaths = [
422
+ path.join(homeDir, '.claude', 'session'),
423
+ path.join(homeDir, '.claude', 'config'),
424
+ path.join(homeDir, '.config', 'claude', 'session'),
425
+ path.join(homeDir, '.config', 'claude', 'config'),
426
+ path.join(homeDir, '.anthropic', 'session'),
427
+ path.join(homeDir, '.anthropic', 'config')
428
+ ];
429
+
430
+ for (const authPath of possibleAuthPaths) {
431
+ if (fs.existsSync(authPath)) {
432
+ try {
433
+ const stats = fs.statSync(authPath);
434
+ // Check if file was modified recently (within last 30 days)
435
+ const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
436
+ if (stats.mtime > thirtyDaysAgo) {
437
+ return {
438
+ status: 'pass',
439
+ message: 'Authentication session found'
440
+ };
441
+ }
442
+ } catch (statError) {
443
+ // Continue to next path
444
+ }
445
+ }
446
+ }
447
+
448
+ // Try to check if we can make a simple claude command
449
+ try {
450
+ execSync('claude --version', {
451
+ encoding: 'utf8',
452
+ stdio: 'pipe',
453
+ timeout: 3000
454
+ });
455
+
456
+ return {
457
+ status: 'warn',
458
+ message: 'Claude CLI available but authentication not verified'
459
+ };
460
+ } catch (cliError) {
461
+ return {
462
+ status: 'fail',
463
+ message: 'Claude CLI not available or not authenticated'
464
+ };
465
+ }
466
+ }
467
+ }
468
+
469
+ checkAutoUpdates() {
470
+ // This is a placeholder - actual implementation would check Claude's update settings
471
+ return {
472
+ status: 'pass',
473
+ message: 'Auto-updates assumed enabled'
474
+ };
475
+ }
476
+
477
+ checkPermissions() {
478
+ const homeDir = os.homedir();
479
+ const claudeDir = path.join(homeDir, '.claude');
480
+
481
+ try {
482
+ if (fs.existsSync(claudeDir)) {
483
+ const stats = fs.statSync(claudeDir);
484
+ const isWritable = fs.access(claudeDir, fs.constants.W_OK);
485
+ return {
486
+ status: 'pass',
487
+ message: 'Claude directory permissions OK'
488
+ };
489
+ } else {
490
+ return {
491
+ status: 'warn',
492
+ message: 'Claude directory not found'
493
+ };
494
+ }
495
+ } catch (error) {
496
+ return {
497
+ status: 'fail',
498
+ message: 'Permission check failed'
499
+ };
500
+ }
501
+ }
502
+
503
+ checkProjectStructure() {
504
+ const currentDir = process.cwd();
505
+
506
+ // Check if it's a valid project directory
507
+ const indicators = [
508
+ 'package.json',
509
+ 'requirements.txt',
510
+ 'Cargo.toml',
511
+ 'go.mod',
512
+ '.git',
513
+ 'README.md'
514
+ ];
515
+
516
+ const foundIndicators = indicators.filter(indicator =>
517
+ fs.existsSync(path.join(currentDir, indicator))
518
+ );
519
+
520
+ if (foundIndicators.length > 0) {
521
+ return {
522
+ status: 'pass',
523
+ message: `Valid project detected (${foundIndicators.join(', ')})`
524
+ };
525
+ } else {
526
+ return {
527
+ status: 'warn',
528
+ message: 'No clear project structure indicators found'
529
+ };
530
+ }
531
+ }
532
+
533
+ checkConfigurationFiles() {
534
+ const currentDir = process.cwd();
535
+ const claudeDir = path.join(currentDir, '.claude');
536
+
537
+ if (fs.existsSync(claudeDir)) {
538
+ const files = fs.readdirSync(claudeDir);
539
+ return {
540
+ status: 'pass',
541
+ message: `Found .claude/ directory with ${files.length} files`
542
+ };
543
+ } else {
544
+ return {
545
+ status: 'warn',
546
+ message: 'No .claude/ directory found'
547
+ };
548
+ }
549
+ }
550
+
551
+ checkProjectCommands() {
552
+ const currentDir = process.cwd();
553
+ const commandsDir = path.join(currentDir, '.claude', 'commands');
554
+
555
+ if (fs.existsSync(commandsDir)) {
556
+ const commands = fs.readdirSync(commandsDir).filter(file => file.endsWith('.md'));
557
+ return {
558
+ status: 'pass',
559
+ message: `${commands.length} commands found in .claude/commands/`
560
+ };
561
+ } else {
562
+ return {
563
+ status: 'warn',
564
+ message: 'No project commands directory found'
565
+ };
566
+ }
567
+ }
568
+
569
+ checkPersonalCommands() {
570
+ const homeDir = os.homedir();
571
+ const commandsDir = path.join(homeDir, '.claude', 'commands');
572
+
573
+ if (fs.existsSync(commandsDir)) {
574
+ const commands = fs.readdirSync(commandsDir).filter(file => file.endsWith('.md'));
575
+ return {
576
+ status: 'pass',
577
+ message: `${commands.length} commands found in ~/.claude/commands/`
578
+ };
579
+ } else {
580
+ return {
581
+ status: 'warn',
582
+ message: 'No personal commands directory found'
583
+ };
584
+ }
585
+ }
586
+
587
+ checkCommandSyntax() {
588
+ const currentDir = process.cwd();
589
+ const commandsDir = path.join(currentDir, '.claude', 'commands');
590
+
591
+ if (!fs.existsSync(commandsDir)) {
592
+ return {
593
+ status: 'warn',
594
+ message: 'No commands to validate'
595
+ };
596
+ }
597
+
598
+ const commands = fs.readdirSync(commandsDir).filter(file => file.endsWith('.md'));
599
+ let issuesFound = 0;
600
+
601
+ for (const command of commands) {
602
+ const commandPath = path.join(commandsDir, command);
603
+ const content = fs.readFileSync(commandPath, 'utf8');
604
+
605
+ // Check for $ARGUMENTS placeholder
606
+ if (!content.includes('$ARGUMENTS')) {
607
+ issuesFound++;
608
+ }
609
+ }
610
+
611
+ if (issuesFound === 0) {
612
+ return {
613
+ status: 'pass',
614
+ message: 'All commands have proper syntax'
615
+ };
616
+ } else {
617
+ return {
618
+ status: 'warn',
619
+ message: `${issuesFound} commands missing $ARGUMENTS placeholder`
620
+ };
621
+ }
622
+ }
623
+
624
+ checkUserHooks() {
625
+ const homeDir = os.homedir();
626
+ const settingsPath = path.join(homeDir, '.claude', 'settings.json');
627
+
628
+ if (fs.existsSync(settingsPath)) {
629
+ try {
630
+ const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
631
+ const hooks = settings.hooks || [];
632
+ return {
633
+ status: 'pass',
634
+ message: `${hooks.length} hooks configured in ~/.claude/settings.json`
635
+ };
636
+ } catch (error) {
637
+ return {
638
+ status: 'fail',
639
+ message: 'Invalid JSON in ~/.claude/settings.json'
640
+ };
641
+ }
642
+ } else {
643
+ return {
644
+ status: 'warn',
645
+ message: 'No user hooks configuration found'
646
+ };
647
+ }
648
+ }
649
+
650
+ checkProjectHooks() {
651
+ const currentDir = process.cwd();
652
+ const settingsPath = path.join(currentDir, '.claude', 'settings.json');
653
+
654
+ if (fs.existsSync(settingsPath)) {
655
+ try {
656
+ const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
657
+ const hooks = settings.hooks || [];
658
+ return {
659
+ status: 'pass',
660
+ message: `${hooks.length} hooks configured in .claude/settings.json`
661
+ };
662
+ } catch (error) {
663
+ return {
664
+ status: 'fail',
665
+ message: 'Invalid JSON in .claude/settings.json'
666
+ };
667
+ }
668
+ } else {
669
+ return {
670
+ status: 'warn',
671
+ message: 'No project hooks configuration found'
672
+ };
673
+ }
674
+ }
675
+
676
+ checkLocalHooks() {
677
+ const currentDir = process.cwd();
678
+ const settingsPath = path.join(currentDir, '.claude', 'settings.local.json');
679
+
680
+ if (fs.existsSync(settingsPath)) {
681
+ try {
682
+ const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
683
+ const hooks = settings.hooks || [];
684
+ return {
685
+ status: 'pass',
686
+ message: `${hooks.length} hooks configured in .claude/settings.local.json`
687
+ };
688
+ } catch (error) {
689
+ return {
690
+ status: 'fail',
691
+ message: 'Invalid JSON syntax in .claude/settings.local.json'
692
+ };
693
+ }
694
+ } else {
695
+ return {
696
+ status: 'warn',
697
+ message: 'No local hooks configuration found'
698
+ };
699
+ }
700
+ }
701
+
702
+ checkHookCommands() {
703
+ const hookSettingsFiles = [
704
+ path.join(os.homedir(), '.claude', 'settings.json'),
705
+ path.join(process.cwd(), '.claude', 'settings.json'),
706
+ path.join(process.cwd(), '.claude', 'settings.local.json')
707
+ ];
708
+
709
+ let totalHooks = 0;
710
+ let validHooks = 0;
711
+ let invalidHooks = 0;
712
+ const issues = [];
713
+
714
+ for (const settingsFile of hookSettingsFiles) {
715
+ if (fs.existsSync(settingsFile)) {
716
+ try {
717
+ const settings = JSON.parse(fs.readFileSync(settingsFile, 'utf8'));
718
+ const hooks = settings.hooks || [];
719
+
720
+ for (const hook of hooks) {
721
+ totalHooks++;
722
+
723
+ // Validate hook structure
724
+ if (!hook.command) {
725
+ invalidHooks++;
726
+ issues.push(`Missing command in hook from ${path.basename(settingsFile)}`);
727
+ continue;
728
+ }
729
+
730
+ // Check if command is executable
731
+ try {
732
+ // Extract command from potential shell command
733
+ const command = hook.command.split(' ')[0];
734
+
735
+ // Check if it's a shell builtin or if the command exists
736
+ if (this.isShellBuiltin(command) || this.commandExists(command)) {
737
+ validHooks++;
738
+ } else {
739
+ invalidHooks++;
740
+ issues.push(`Command not found: ${command} in ${path.basename(settingsFile)}`);
741
+ }
742
+ } catch (error) {
743
+ invalidHooks++;
744
+ issues.push(`Error validating command: ${hook.command} in ${path.basename(settingsFile)}`);
745
+ }
746
+ }
747
+ } catch (error) {
748
+ // JSON parsing error was already handled in other checks
749
+ }
750
+ }
751
+ }
752
+
753
+ if (totalHooks === 0) {
754
+ return {
755
+ status: 'warn',
756
+ message: 'No hooks configured'
757
+ };
758
+ }
759
+
760
+ if (invalidHooks === 0) {
761
+ return {
762
+ status: 'pass',
763
+ message: `All ${totalHooks} hook commands are valid`
764
+ };
765
+ } else if (validHooks > 0) {
766
+ return {
767
+ status: 'warn',
768
+ message: `${validHooks}/${totalHooks} hook commands valid, ${invalidHooks} issues found`
769
+ };
770
+ } else {
771
+ return {
772
+ status: 'fail',
773
+ message: `All ${totalHooks} hook commands have issues`
774
+ };
775
+ }
776
+ }
777
+
778
+ isShellBuiltin(command) {
779
+ const shellBuiltins = [
780
+ 'echo', 'cd', 'pwd', 'exit', 'export', 'unset', 'alias', 'unalias',
781
+ 'history', 'type', 'which', 'command', 'builtin', 'source', '.',
782
+ 'test', '[', 'if', 'then', 'else', 'fi', 'case', 'esac', 'for', 'while'
783
+ ];
784
+ return shellBuiltins.includes(command);
785
+ }
786
+
787
+ commandExists(command) {
788
+ try {
789
+ execSync(`command -v ${command}`, {
790
+ encoding: 'utf8',
791
+ stdio: 'pipe',
792
+ timeout: 2000
793
+ });
794
+ return true;
795
+ } catch (error) {
796
+ return false;
797
+ }
798
+ }
799
+
800
+ checkMCPHooks() {
801
+ // Placeholder for MCP hooks validation
802
+ return {
803
+ status: 'warn',
804
+ message: 'MCP hooks validation not implemented'
805
+ };
806
+ }
807
+
808
+ checkUserSettings() {
809
+ const homeDir = os.homedir();
810
+ const userSettingsPath = path.join(homeDir, '.claude', 'settings.json');
811
+
812
+ if (!fs.existsSync(userSettingsPath)) {
813
+ return {
814
+ status: 'warn',
815
+ message: 'No user settings found (~/.claude/settings.json)'
816
+ };
817
+ }
818
+
819
+ try {
820
+ const settings = JSON.parse(fs.readFileSync(userSettingsPath, 'utf8'));
821
+ const analysis = this.analyzeSettingsStructure(settings, 'user');
822
+
823
+ return {
824
+ status: analysis.issues.length === 0 ? 'pass' : 'warn',
825
+ message: analysis.issues.length === 0 ?
826
+ `Valid user settings (${analysis.summary})` :
827
+ `User settings issues: ${analysis.issues.join(', ')}`
828
+ };
829
+ } catch (error) {
830
+ return {
831
+ status: 'fail',
832
+ message: 'Invalid JSON in ~/.claude/settings.json'
833
+ };
834
+ }
835
+ }
836
+
837
+ checkProjectSettings() {
838
+ const currentDir = process.cwd();
839
+ const projectSettingsPath = path.join(currentDir, '.claude', 'settings.json');
840
+
841
+ if (!fs.existsSync(projectSettingsPath)) {
842
+ return {
843
+ status: 'warn',
844
+ message: 'No project settings found (.claude/settings.json)'
845
+ };
846
+ }
847
+
848
+ try {
849
+ const settings = JSON.parse(fs.readFileSync(projectSettingsPath, 'utf8'));
850
+ const analysis = this.analyzeSettingsStructure(settings, 'project');
851
+
852
+ return {
853
+ status: analysis.issues.length === 0 ? 'pass' : 'warn',
854
+ message: analysis.issues.length === 0 ?
855
+ `Valid project settings (${analysis.summary})` :
856
+ `Project settings issues: ${analysis.issues.join(', ')}`
857
+ };
858
+ } catch (error) {
859
+ return {
860
+ status: 'fail',
861
+ message: 'Invalid JSON in .claude/settings.json'
862
+ };
863
+ }
864
+ }
865
+
866
+ checkLocalSettings() {
867
+ const currentDir = process.cwd();
868
+ const localSettingsPath = path.join(currentDir, '.claude', 'settings.local.json');
869
+
870
+ if (!fs.existsSync(localSettingsPath)) {
871
+ return {
872
+ status: 'warn',
873
+ message: 'No local settings found (.claude/settings.local.json)'
874
+ };
875
+ }
876
+
877
+ try {
878
+ const settings = JSON.parse(fs.readFileSync(localSettingsPath, 'utf8'));
879
+ const analysis = this.analyzeSettingsStructure(settings, 'local');
880
+
881
+ return {
882
+ status: analysis.issues.length === 0 ? 'pass' : 'warn',
883
+ message: analysis.issues.length === 0 ?
884
+ `Valid local settings (${analysis.summary})` :
885
+ `Local settings issues: ${analysis.issues.join(', ')}`
886
+ };
887
+ } catch (error) {
888
+ return {
889
+ status: 'fail',
890
+ message: 'Invalid JSON in .claude/settings.local.json'
891
+ };
892
+ }
893
+ }
894
+
895
+ analyzeSettingsStructure(settings, type) {
896
+ const issues = [];
897
+ const summary = [];
898
+
899
+ // Check for common configuration sections
900
+ if (settings.permissions) {
901
+ const perms = settings.permissions;
902
+ if (perms.allow && Array.isArray(perms.allow)) {
903
+ summary.push(`${perms.allow.length} allow rules`);
904
+ }
905
+ if (perms.deny && Array.isArray(perms.deny)) {
906
+ summary.push(`${perms.deny.length} deny rules`);
907
+ }
908
+ if (perms.additionalDirectories && Array.isArray(perms.additionalDirectories)) {
909
+ summary.push(`${perms.additionalDirectories.length} additional dirs`);
910
+ }
911
+ }
912
+
913
+ if (settings.hooks) {
914
+ const hookTypes = Object.keys(settings.hooks);
915
+ summary.push(`${hookTypes.length} hook types`);
916
+
917
+ // Validate hook structure
918
+ for (const hookType of hookTypes) {
919
+ const validHookTypes = ['PreToolUse', 'PostToolUse', 'Stop', 'Notification'];
920
+ if (!validHookTypes.includes(hookType)) {
921
+ issues.push(`Invalid hook type: ${hookType}`);
922
+ }
923
+ }
924
+ }
925
+
926
+ if (settings.env) {
927
+ const envVars = Object.keys(settings.env);
928
+ summary.push(`${envVars.length} env vars`);
929
+
930
+ // Check for sensitive environment variables
931
+ const sensitiveVars = ['ANTHROPIC_API_KEY', 'ANTHROPIC_AUTH_TOKEN'];
932
+ for (const envVar of envVars) {
933
+ if (sensitiveVars.includes(envVar)) {
934
+ issues.push(`Sensitive env var in ${type} settings: ${envVar}`);
935
+ }
936
+ }
937
+ }
938
+
939
+ if (settings.model) {
940
+ summary.push(`model: ${settings.model}`);
941
+ }
942
+
943
+ if (settings.enableAllProjectMcpServers !== undefined) {
944
+ summary.push(`MCP auto-approve: ${settings.enableAllProjectMcpServers}`);
945
+ }
946
+
947
+ if (settings.enabledMcpjsonServers && Array.isArray(settings.enabledMcpjsonServers)) {
948
+ summary.push(`${settings.enabledMcpjsonServers.length} enabled MCP servers`);
949
+ }
950
+
951
+ if (settings.disabledMcpjsonServers && Array.isArray(settings.disabledMcpjsonServers)) {
952
+ summary.push(`${settings.disabledMcpjsonServers.length} disabled MCP servers`);
953
+ }
954
+
955
+ // Check for deprecated or problematic settings
956
+ if (settings.apiKeyHelper) {
957
+ summary.push('API key helper configured');
958
+ if (type === 'project') {
959
+ issues.push('API key helper should be in user settings, not project');
960
+ }
961
+ }
962
+
963
+ if (settings.cleanupPeriodDays !== undefined) {
964
+ if (typeof settings.cleanupPeriodDays !== 'number' || settings.cleanupPeriodDays < 1) {
965
+ issues.push('Invalid cleanupPeriodDays value');
966
+ } else {
967
+ summary.push(`cleanup: ${settings.cleanupPeriodDays} days`);
968
+ }
969
+ }
970
+
971
+ return {
972
+ issues,
973
+ summary: summary.length > 0 ? summary.join(', ') : 'basic configuration'
974
+ };
975
+ }
976
+
977
+ /**
978
+ * Helper methods
979
+ */
980
+ addResult(category, check, status, message) {
981
+ this.results[category].push({
982
+ check,
983
+ status,
984
+ message
985
+ });
986
+ this.totalChecks++;
987
+ if (status === 'pass') {
988
+ this.passedChecks++;
989
+ }
990
+ }
991
+
992
+ getStatusIcon(status) {
993
+ switch (status) {
994
+ case 'pass': return 'āœ…';
995
+ case 'warn': return 'āš ļø';
996
+ case 'fail': return 'āŒ';
997
+ default: return 'ā“';
998
+ }
999
+ }
1000
+
1001
+ /**
1002
+ * Sleep utility for pacing between categories
1003
+ */
1004
+ sleep(ms) {
1005
+ return new Promise(resolve => setTimeout(resolve, ms));
1006
+ }
1007
+
1008
+ generateSummary() {
1009
+ const healthScore = `${this.passedChecks}/${this.totalChecks}`;
1010
+ const percentage = Math.round((this.passedChecks / this.totalChecks) * 100);
1011
+
1012
+ console.log(chalk.cyan(`\nšŸ“Š Health Score: ${healthScore} checks passed (${percentage}%)`));
1013
+
1014
+ // Generate recommendations
1015
+ const recommendations = this.generateRecommendations();
1016
+ if (recommendations.length > 0) {
1017
+ console.log(chalk.yellow('\nšŸ’” Recommendations:'));
1018
+ recommendations.forEach(rec => {
1019
+ console.log(` • ${rec}`);
1020
+ });
1021
+ }
1022
+
1023
+ return {
1024
+ healthScore,
1025
+ percentage,
1026
+ passed: this.passedChecks,
1027
+ total: this.totalChecks,
1028
+ recommendations
1029
+ };
1030
+ }
1031
+
1032
+ generateRecommendations() {
1033
+ const recommendations = [];
1034
+
1035
+ // Add recommendations based on results
1036
+ const allResults = [
1037
+ ...this.results.system,
1038
+ ...this.results.claudeCode,
1039
+ ...this.results.project,
1040
+ ...this.results.commands,
1041
+ ...this.results.hooks
1042
+ ];
1043
+
1044
+ allResults.forEach(result => {
1045
+ if (result.status === 'fail' || result.status === 'warn') {
1046
+ // Add specific recommendations based on the check
1047
+ if (result.check === 'Shell Environment' && result.message.includes('bash')) {
1048
+ recommendations.push('Consider switching to Zsh for better autocompletion and features');
1049
+ } else if (result.check === 'Command Syntax' && result.message.includes('$ARGUMENTS')) {
1050
+ recommendations.push('Add $ARGUMENTS placeholder to command files for proper parameter handling');
1051
+ } else if (result.check === 'Local Hooks' && result.message.includes('Invalid JSON')) {
1052
+ recommendations.push('Fix JSON syntax error in .claude/settings.local.json');
1053
+ }
1054
+ }
1055
+ });
1056
+
1057
+ return recommendations;
1058
+ }
1059
+ }
1060
+
1061
+ /**
1062
+ * Main health check function
1063
+ */
1064
+ async function runHealthCheck() {
1065
+ const checker = new HealthChecker();
1066
+ const results = await checker.runHealthCheck();
1067
+
1068
+ // Ask if user wants to run setup
1069
+ const inquirer = require('inquirer');
1070
+ const setupChoice = await inquirer.prompt([{
1071
+ type: 'confirm',
1072
+ name: 'runSetup',
1073
+ message: 'Would you like to run the Project Setup?',
1074
+ default: results.percentage < 80
1075
+ }]);
1076
+
1077
+ return {
1078
+ results,
1079
+ runSetup: setupChoice.runSetup
1080
+ };
1081
+ }
1082
+
1083
+ module.exports = {
1084
+ HealthChecker,
1085
+ runHealthCheck
1086
+ };