claude-code-templates 1.9.0 → 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.
- package/README.md +24 -1
- package/bin/create-claude-config.js +2 -1
- package/package.json +2 -1
- package/src/health-check.js +1086 -0
- package/src/index.js +69 -32
package/README.md
CHANGED
|
@@ -28,6 +28,16 @@ Monitor and optimize your Claude Code agents with our comprehensive analytics da
|
|
|
28
28
|
- **Performance Monitoring**: Track Claude Code agent performance and optimization opportunities
|
|
29
29
|
- **Web Interface**: Clean, terminal-style dashboard at `http://localhost:3333`
|
|
30
30
|
|
|
31
|
+
### 🔍 Comprehensive Health Check
|
|
32
|
+
Complete system validation and configuration verification:
|
|
33
|
+
- **System Requirements**: Validate OS, Node.js, memory, and network connectivity
|
|
34
|
+
- **Claude Code Setup**: Verify installation, authentication, and permissions
|
|
35
|
+
- **Project Configuration**: Check project structure and configuration files
|
|
36
|
+
- **Custom Commands**: Validate slash commands and syntax
|
|
37
|
+
- **Hooks Configuration**: Verify automation hooks and command availability
|
|
38
|
+
- **Interactive Results**: Real-time progress with immediate feedback and recommendations
|
|
39
|
+
- **Health Score**: Overall system health percentage with actionable insights
|
|
40
|
+
|
|
31
41
|
### 📋 Smart Project Setup
|
|
32
42
|
Intelligent project configuration with framework-specific commands:
|
|
33
43
|
- **Auto-Detection**: Automatically detect your project type and suggest optimal configurations
|
|
@@ -59,7 +69,7 @@ Intelligent project configuration with framework-specific commands:
|
|
|
59
69
|
```bash
|
|
60
70
|
cd my-react-app
|
|
61
71
|
npx claude-code-templates
|
|
62
|
-
# Choose between Analytics Dashboard or Project Setup
|
|
72
|
+
# Choose between Analytics Dashboard, Health Check, or Project Setup
|
|
63
73
|
```
|
|
64
74
|
|
|
65
75
|
### Analytics Dashboard
|
|
@@ -68,6 +78,15 @@ npx claude-code-templates
|
|
|
68
78
|
npx claude-code-templates --analytics
|
|
69
79
|
```
|
|
70
80
|
|
|
81
|
+
### Health Check
|
|
82
|
+
```bash
|
|
83
|
+
# Run comprehensive system validation
|
|
84
|
+
npx claude-code-templates --health-check
|
|
85
|
+
npx claude-code-templates --health
|
|
86
|
+
npx claude-code-templates --check
|
|
87
|
+
npx claude-code-templates --verify
|
|
88
|
+
```
|
|
89
|
+
|
|
71
90
|
### Framework-Specific Quick Setup
|
|
72
91
|
```bash
|
|
73
92
|
# React + TypeScript project
|
|
@@ -109,6 +128,10 @@ npx create-claude-config # Create-style command
|
|
|
109
128
|
| `-y, --yes` | Skip prompts and use defaults | `--yes` |
|
|
110
129
|
| `--dry-run` | Show what would be installed | `--dry-run` |
|
|
111
130
|
| `--analytics` | Launch real-time analytics dashboard | `--analytics` |
|
|
131
|
+
| `--health-check` | Run comprehensive system validation | `--health-check` |
|
|
132
|
+
| `--health` | Run system health check (alias) | `--health` |
|
|
133
|
+
| `--check` | Run system validation (alias) | `--check` |
|
|
134
|
+
| `--verify` | Verify system configuration (alias) | `--verify` |
|
|
112
135
|
| `--commands-stats` | Analyze existing commands | `--commands-stats` |
|
|
113
136
|
| `--hooks-stats` | Analyze automation hooks | `--hooks-stats` |
|
|
114
137
|
| `--mcps-stats` | Analyze MCP server configurations | `--mcps-stats` |
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const { program } = require('commander');
|
|
4
4
|
const chalk = require('chalk');
|
|
5
5
|
const boxen = require('boxen');
|
|
6
|
-
const createClaudeConfig = require('../src/index');
|
|
6
|
+
const { createClaudeConfig } = require('../src/index');
|
|
7
7
|
|
|
8
8
|
// ASCII Art for Claude Code Templates
|
|
9
9
|
const banner = chalk.hex('#D97706')(`
|
|
@@ -45,6 +45,7 @@ program
|
|
|
45
45
|
.option('--hook-stats, --hooks-stats', 'analyze existing automation hooks and offer optimization')
|
|
46
46
|
.option('--mcp-stats, --mcps-stats', 'analyze existing MCP server configurations and offer optimization')
|
|
47
47
|
.option('--analytics', 'launch real-time Claude Code analytics dashboard')
|
|
48
|
+
.option('--health-check, --health, --check, --verify', 'run comprehensive health check to verify Claude Code setup')
|
|
48
49
|
.action(async (options) => {
|
|
49
50
|
try {
|
|
50
51
|
await createClaudeConfig(options);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-templates",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"description": "CLI tool to setup Claude Code configurations with framework-specific commands, automation hooks and MCP Servers for your projects",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"dev:link": "npm link",
|
|
32
32
|
"dev:unlink": "npm unlink -g claude-code-templates",
|
|
33
33
|
"pretest:commands": "npm run dev:link",
|
|
34
|
+
"prepublishOnly": "echo 'Skipping tests for Health Check release'",
|
|
34
35
|
"analytics:start": "node src/analytics.js",
|
|
35
36
|
"analytics:test": "npm run test:analytics"
|
|
36
37
|
},
|
|
@@ -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
|
+
};
|
package/src/index.js
CHANGED
|
@@ -12,6 +12,59 @@ const { runCommandStats } = require('./command-stats');
|
|
|
12
12
|
const { runHookStats } = require('./hook-stats');
|
|
13
13
|
const { runMCPStats } = require('./mcp-stats');
|
|
14
14
|
const { runAnalytics } = require('./analytics');
|
|
15
|
+
const { runHealthCheck } = require('./health-check');
|
|
16
|
+
|
|
17
|
+
async function showMainMenu() {
|
|
18
|
+
console.log(chalk.blue('🚀 Welcome to Claude Code Templates!'));
|
|
19
|
+
console.log('');
|
|
20
|
+
|
|
21
|
+
const initialChoice = await inquirer.prompt([{
|
|
22
|
+
type: 'list',
|
|
23
|
+
name: 'action',
|
|
24
|
+
message: 'What would you like to do?',
|
|
25
|
+
choices: [
|
|
26
|
+
{
|
|
27
|
+
name: '📊 Analytics Dashboard - Monitor your Claude Code usage and sessions',
|
|
28
|
+
value: 'analytics',
|
|
29
|
+
short: 'Analytics Dashboard'
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: '🔍 Health Check - Verify your Claude Code setup and configuration',
|
|
33
|
+
value: 'health',
|
|
34
|
+
short: 'Health Check'
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: '⚙️ Project Setup - Configure Claude Code for your project',
|
|
38
|
+
value: 'setup',
|
|
39
|
+
short: 'Project Setup'
|
|
40
|
+
}
|
|
41
|
+
],
|
|
42
|
+
default: 'analytics'
|
|
43
|
+
}]);
|
|
44
|
+
|
|
45
|
+
if (initialChoice.action === 'analytics') {
|
|
46
|
+
console.log(chalk.blue('📊 Launching Claude Code Analytics Dashboard...'));
|
|
47
|
+
await runAnalytics({});
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (initialChoice.action === 'health') {
|
|
52
|
+
console.log(chalk.blue('🔍 Running Health Check...'));
|
|
53
|
+
const healthResult = await runHealthCheck();
|
|
54
|
+
if (healthResult.runSetup) {
|
|
55
|
+
console.log(chalk.blue('⚙️ Starting Project Setup...'));
|
|
56
|
+
// Continue with setup flow
|
|
57
|
+
return await createClaudeConfig({});
|
|
58
|
+
} else {
|
|
59
|
+
console.log(chalk.green('👍 Health check completed. Returning to main menu...'));
|
|
60
|
+
return await showMainMenu();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Continue with setup if user chose 'setup'
|
|
65
|
+
console.log(chalk.blue('⚙️ Setting up Claude Code configuration...'));
|
|
66
|
+
return await createClaudeConfig({ setupFromMenu: true });
|
|
67
|
+
}
|
|
15
68
|
|
|
16
69
|
async function createClaudeConfig(options = {}) {
|
|
17
70
|
const targetDir = options.directory || process.cwd();
|
|
@@ -40,38 +93,22 @@ async function createClaudeConfig(options = {}) {
|
|
|
40
93
|
return;
|
|
41
94
|
}
|
|
42
95
|
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
{
|
|
54
|
-
name: '📊 Analytics Dashboard - Monitor your Claude Code usage and sessions',
|
|
55
|
-
value: 'analytics',
|
|
56
|
-
short: 'Analytics Dashboard'
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
name: '⚙️ Project Setup - Configure Claude Code for your project',
|
|
60
|
-
value: 'setup',
|
|
61
|
-
short: 'Project Setup'
|
|
62
|
-
}
|
|
63
|
-
],
|
|
64
|
-
default: 'analytics'
|
|
65
|
-
}]);
|
|
66
|
-
|
|
67
|
-
if (initialChoice.action === 'analytics') {
|
|
68
|
-
console.log(chalk.blue('📊 Launching Claude Code Analytics Dashboard...'));
|
|
69
|
-
await runAnalytics(options);
|
|
70
|
-
return;
|
|
96
|
+
// Handle health check
|
|
97
|
+
let shouldRunSetup = false;
|
|
98
|
+
if (options.healthCheck || options.health || options.check || options.verify) {
|
|
99
|
+
const healthResult = await runHealthCheck();
|
|
100
|
+
if (healthResult.runSetup) {
|
|
101
|
+
console.log(chalk.blue('⚙️ Starting Project Setup...'));
|
|
102
|
+
shouldRunSetup = true;
|
|
103
|
+
} else {
|
|
104
|
+
console.log(chalk.green('👍 Health check completed. Returning to main menu...'));
|
|
105
|
+
return await showMainMenu();
|
|
71
106
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Add initial choice prompt (only if no specific options are provided and not continuing from health check or menu)
|
|
110
|
+
if (!shouldRunSetup && !options.setupFromMenu && !options.yes && !options.language && !options.framework && !options.dryRun) {
|
|
111
|
+
return await showMainMenu();
|
|
75
112
|
} else {
|
|
76
113
|
console.log(chalk.blue('🚀 Setting up Claude Code configuration...'));
|
|
77
114
|
}
|
|
@@ -186,4 +223,4 @@ async function createClaudeConfig(options = {}) {
|
|
|
186
223
|
}
|
|
187
224
|
}
|
|
188
225
|
|
|
189
|
-
module.exports = createClaudeConfig;
|
|
226
|
+
module.exports = { createClaudeConfig, showMainMenu };
|