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.
- package/README.md +24 -1
- package/bin/create-claude-config.js +2 -1
- package/package.json +2 -2
- package/src/analytics/core/ConversationAnalyzer.js +78 -0
- package/src/analytics/data/DataCache.js +30 -0
- package/src/analytics-web/index.html +427 -2
- package/src/health-check.js +1086 -0
- package/src/index.js +69 -32
|
@@ -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
|
+
};
|