@everstateai/mcp 1.3.2 → 1.3.3
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/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +32 -5
- package/dist/index.js.map +1 -1
- package/dist/setup/auto-update.d.ts +20 -0
- package/dist/setup/auto-update.d.ts.map +1 -0
- package/dist/setup/auto-update.js +295 -0
- package/dist/setup/auto-update.js.map +1 -0
- package/dist/setup/commands/doctor.d.ts +15 -0
- package/dist/setup/commands/doctor.d.ts.map +1 -0
- package/dist/setup/commands/doctor.js +264 -0
- package/dist/setup/commands/doctor.js.map +1 -0
- package/dist/setup/commands/repair.d.ts +14 -0
- package/dist/setup/commands/repair.d.ts.map +1 -0
- package/dist/setup/commands/repair.js +252 -0
- package/dist/setup/commands/repair.js.map +1 -0
- package/dist/setup/hooks/templates.d.ts +30 -0
- package/dist/setup/hooks/templates.d.ts.map +1 -0
- package/dist/setup/hooks/templates.js +237 -0
- package/dist/setup/hooks/templates.js.map +1 -0
- package/dist/setup/types.d.ts +120 -0
- package/dist/setup/types.d.ts.map +1 -0
- package/dist/setup/types.js +58 -0
- package/dist/setup/types.js.map +1 -0
- package/dist/setup/validators/api-key.d.ts +8 -0
- package/dist/setup/validators/api-key.d.ts.map +1 -0
- package/dist/setup/validators/api-key.js +233 -0
- package/dist/setup/validators/api-key.js.map +1 -0
- package/dist/setup/validators/connectivity.d.ts +8 -0
- package/dist/setup/validators/connectivity.d.ts.map +1 -0
- package/dist/setup/validators/connectivity.js +150 -0
- package/dist/setup/validators/connectivity.js.map +1 -0
- package/dist/setup/validators/hooks.d.ts +8 -0
- package/dist/setup/validators/hooks.d.ts.map +1 -0
- package/dist/setup/validators/hooks.js +431 -0
- package/dist/setup/validators/hooks.js.map +1 -0
- package/dist/setup/validators/index.d.ts +18 -0
- package/dist/setup/validators/index.d.ts.map +1 -0
- package/dist/setup/validators/index.js +123 -0
- package/dist/setup/validators/index.js.map +1 -0
- package/dist/setup/validators/mcp-config.d.ts +8 -0
- package/dist/setup/validators/mcp-config.d.ts.map +1 -0
- package/dist/setup/validators/mcp-config.js +333 -0
- package/dist/setup/validators/mcp-config.js.map +1 -0
- package/dist/setup/validators/project.d.ts +8 -0
- package/dist/setup/validators/project.d.ts.map +1 -0
- package/dist/setup/validators/project.js +202 -0
- package/dist/setup/validators/project.js.map +1 -0
- package/dist/setup/version.d.ts +58 -0
- package/dist/setup/version.d.ts.map +1 -0
- package/dist/setup/version.js +262 -0
- package/dist/setup/version.js.map +1 -0
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +207 -27
- package/dist/setup.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +32 -5
- package/src/setup/auto-update.ts +328 -0
- package/src/setup/commands/doctor.ts +266 -0
- package/src/setup/commands/repair.ts +260 -0
- package/src/setup/hooks/templates.ts +239 -0
- package/src/setup/types.ts +199 -0
- package/src/setup/validators/api-key.ts +218 -0
- package/src/setup/validators/connectivity.ts +176 -0
- package/src/setup/validators/hooks.ts +447 -0
- package/src/setup/validators/index.ts +137 -0
- package/src/setup/validators/mcp-config.ts +329 -0
- package/src/setup/validators/project.ts +179 -0
- package/src/setup/version.ts +267 -0
- package/src/setup.ts +229 -27
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-Update System
|
|
3
|
+
*
|
|
4
|
+
* Automatically updates hooks and configuration when the MCP proxy starts.
|
|
5
|
+
* Runs silently in the background without blocking the MCP server.
|
|
6
|
+
*
|
|
7
|
+
* Also checks the Everstate server for available updates (same as the dashboard UI).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import * as fs from 'fs';
|
|
11
|
+
import {
|
|
12
|
+
getEverstateDir,
|
|
13
|
+
getClaudeConfigPath,
|
|
14
|
+
HOOK_VERSIONS,
|
|
15
|
+
EVERSTATE_API_URL,
|
|
16
|
+
} from './types.js';
|
|
17
|
+
import {
|
|
18
|
+
readVersionFile,
|
|
19
|
+
writeVersionFile,
|
|
20
|
+
createVersionFile,
|
|
21
|
+
getMcpProxyVersion,
|
|
22
|
+
} from './version.js';
|
|
23
|
+
import { getSyncTodosHook } from './hooks/templates.js';
|
|
24
|
+
|
|
25
|
+
const UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
26
|
+
|
|
27
|
+
interface ServerUpdate {
|
|
28
|
+
id: string;
|
|
29
|
+
version: string;
|
|
30
|
+
category: 'hooks' | 'mcp-proxy' | 'config';
|
|
31
|
+
title: string;
|
|
32
|
+
description: string;
|
|
33
|
+
breaking: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface UpdateCheckResult {
|
|
37
|
+
needsUpdate: boolean;
|
|
38
|
+
updates: string[];
|
|
39
|
+
errors: string[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Check and apply updates silently.
|
|
44
|
+
* Called on MCP server startup.
|
|
45
|
+
*/
|
|
46
|
+
export async function checkAndApplyUpdates(): Promise<UpdateCheckResult> {
|
|
47
|
+
const result: UpdateCheckResult = {
|
|
48
|
+
needsUpdate: false,
|
|
49
|
+
updates: [],
|
|
50
|
+
errors: [],
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
// Read or create version file
|
|
55
|
+
let versionFile = readVersionFile();
|
|
56
|
+
const isFirstRun = !versionFile;
|
|
57
|
+
|
|
58
|
+
if (!versionFile) {
|
|
59
|
+
versionFile = createVersionFile('npx');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Check if we should skip this update check (rate limiting)
|
|
63
|
+
const lastCheck = versionFile.lastUpdateCheck;
|
|
64
|
+
if (lastCheck) {
|
|
65
|
+
const elapsed = Date.now() - new Date(lastCheck).getTime();
|
|
66
|
+
if (elapsed < UPDATE_CHECK_INTERVAL_MS) {
|
|
67
|
+
// Skip - checked recently
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Update the check timestamp
|
|
73
|
+
versionFile.lastUpdateCheck = new Date().toISOString();
|
|
74
|
+
|
|
75
|
+
// Check hook versions
|
|
76
|
+
const hooksDir = `${getEverstateDir()}/hooks`;
|
|
77
|
+
const syncTodosPath = `${hooksDir}/sync-todos.js`;
|
|
78
|
+
|
|
79
|
+
// Update sync-todos.js if needed
|
|
80
|
+
if (shouldUpdateHook(syncTodosPath, HOOK_VERSIONS.syncTodos, versionFile.components.hooks.syncTodos)) {
|
|
81
|
+
try {
|
|
82
|
+
await updateSyncTodosHook(hooksDir, syncTodosPath);
|
|
83
|
+
versionFile.components.hooks.syncTodos = HOOK_VERSIONS.syncTodos;
|
|
84
|
+
result.updates.push(`sync-todos.js updated to v${HOOK_VERSIONS.syncTodos}`);
|
|
85
|
+
result.needsUpdate = true;
|
|
86
|
+
} catch (error) {
|
|
87
|
+
result.errors.push(`Failed to update sync-todos.js: ${String(error)}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Update TodoWrite hook registration if needed
|
|
92
|
+
const hookRegistrationUpdated = await ensureHookRegistration(syncTodosPath);
|
|
93
|
+
if (hookRegistrationUpdated) {
|
|
94
|
+
result.updates.push('TodoWrite hook registration updated');
|
|
95
|
+
result.needsUpdate = true;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Update MCP proxy version in version file
|
|
99
|
+
const currentVersion = getMcpProxyVersion();
|
|
100
|
+
if (versionFile.components.mcpProxy !== currentVersion) {
|
|
101
|
+
versionFile.components.mcpProxy = currentVersion;
|
|
102
|
+
result.updates.push(`MCP proxy version recorded: ${currentVersion}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Write updated version file
|
|
106
|
+
writeVersionFile(versionFile);
|
|
107
|
+
|
|
108
|
+
// Check for server-side updates (same as dashboard UI)
|
|
109
|
+
const apiKey = loadApiKey();
|
|
110
|
+
if (apiKey) {
|
|
111
|
+
try {
|
|
112
|
+
const serverUpdates = await checkServerUpdates(apiKey, versionFile.components);
|
|
113
|
+
if (serverUpdates.length > 0) {
|
|
114
|
+
console.error(`[Everstate] ${serverUpdates.length} update(s) available:`);
|
|
115
|
+
for (const update of serverUpdates) {
|
|
116
|
+
console.error(` - ${update.title} (${update.category} v${update.version})`);
|
|
117
|
+
}
|
|
118
|
+
console.error(` Run 'npx @everstateai/mcp doctor' to see details`);
|
|
119
|
+
}
|
|
120
|
+
} catch {
|
|
121
|
+
// Silently ignore server check failures
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Log updates to stderr (MCP servers use stderr for logging)
|
|
126
|
+
if (result.updates.length > 0) {
|
|
127
|
+
console.error(`[Everstate] Auto-updated: ${result.updates.join(', ')}`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (result.errors.length > 0) {
|
|
131
|
+
console.error(`[Everstate] Update errors: ${result.errors.join(', ')}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
} catch (error) {
|
|
135
|
+
result.errors.push(`Auto-update check failed: ${String(error)}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Load API key from file
|
|
143
|
+
*/
|
|
144
|
+
function loadApiKey(): string | null {
|
|
145
|
+
const keyPath = `${getEverstateDir()}/api-key`;
|
|
146
|
+
try {
|
|
147
|
+
if (fs.existsSync(keyPath)) {
|
|
148
|
+
return fs.readFileSync(keyPath, 'utf8').trim();
|
|
149
|
+
}
|
|
150
|
+
} catch {}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Check for updates from the Everstate server
|
|
156
|
+
*/
|
|
157
|
+
async function checkServerUpdates(
|
|
158
|
+
apiKey: string,
|
|
159
|
+
installedComponents: { mcpProxy: string; hooks: { syncTodos: string } }
|
|
160
|
+
): Promise<ServerUpdate[]> {
|
|
161
|
+
const response = await fetch(`${EVERSTATE_API_URL}/api/updates/check`, {
|
|
162
|
+
method: 'POST',
|
|
163
|
+
headers: {
|
|
164
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
165
|
+
'Content-Type': 'application/json',
|
|
166
|
+
},
|
|
167
|
+
body: JSON.stringify({
|
|
168
|
+
installedVersions: {
|
|
169
|
+
mcpProxy: installedComponents.mcpProxy,
|
|
170
|
+
syncTodos: installedComponents.hooks.syncTodos,
|
|
171
|
+
},
|
|
172
|
+
}),
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
if (!response.ok) {
|
|
176
|
+
return [];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const data = await response.json() as { updates?: ServerUpdate[] };
|
|
180
|
+
return data.updates || [];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Determine if a hook needs updating
|
|
185
|
+
*/
|
|
186
|
+
function shouldUpdateHook(
|
|
187
|
+
hookPath: string,
|
|
188
|
+
expectedVersion: string,
|
|
189
|
+
installedVersion: string
|
|
190
|
+
): boolean {
|
|
191
|
+
// If file doesn't exist, needs update
|
|
192
|
+
if (!fs.existsSync(hookPath)) {
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Compare versions
|
|
197
|
+
if (compareVersions(installedVersion, expectedVersion) < 0) {
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Also check the file's embedded version
|
|
202
|
+
const fileVersion = extractHookVersion(hookPath);
|
|
203
|
+
if (!fileVersion || compareVersions(fileVersion, expectedVersion) < 0) {
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Extract version from hook file
|
|
212
|
+
*/
|
|
213
|
+
function extractHookVersion(filePath: string): string | null {
|
|
214
|
+
try {
|
|
215
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
216
|
+
const versionMatch = content.match(/VERSION:\s*(\d+\.\d+\.\d+)/i);
|
|
217
|
+
if (versionMatch) {
|
|
218
|
+
return versionMatch[1];
|
|
219
|
+
}
|
|
220
|
+
// Legacy format
|
|
221
|
+
const hookVMatch = content.match(/Hook\s+v(\d+)/i);
|
|
222
|
+
if (hookVMatch) {
|
|
223
|
+
return `${hookVMatch[1]}.0.0`;
|
|
224
|
+
}
|
|
225
|
+
return null;
|
|
226
|
+
} catch {
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Compare semver versions
|
|
233
|
+
*/
|
|
234
|
+
function compareVersions(a: string, b: string): number {
|
|
235
|
+
const aParts = a.split('.').map(Number);
|
|
236
|
+
const bParts = b.split('.').map(Number);
|
|
237
|
+
|
|
238
|
+
for (let i = 0; i < 3; i++) {
|
|
239
|
+
const aNum = aParts[i] || 0;
|
|
240
|
+
const bNum = bParts[i] || 0;
|
|
241
|
+
if (aNum < bNum) return -1;
|
|
242
|
+
if (aNum > bNum) return 1;
|
|
243
|
+
}
|
|
244
|
+
return 0;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Update the sync-todos hook
|
|
249
|
+
*/
|
|
250
|
+
async function updateSyncTodosHook(hooksDir: string, hookPath: string): Promise<void> {
|
|
251
|
+
// Ensure directory exists
|
|
252
|
+
if (!fs.existsSync(hooksDir)) {
|
|
253
|
+
fs.mkdirSync(hooksDir, { recursive: true });
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Get the new hook content
|
|
257
|
+
const hookContent = getSyncTodosHook();
|
|
258
|
+
|
|
259
|
+
// Backup existing hook if present
|
|
260
|
+
if (fs.existsSync(hookPath)) {
|
|
261
|
+
const backupPath = `${hookPath}.backup.${Date.now()}`;
|
|
262
|
+
fs.copyFileSync(hookPath, backupPath);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Write new hook
|
|
266
|
+
fs.writeFileSync(hookPath, hookContent, { mode: 0o755 });
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Ensure TodoWrite hook is registered in ~/.claude.json
|
|
271
|
+
*/
|
|
272
|
+
async function ensureHookRegistration(syncTodosPath: string): Promise<boolean> {
|
|
273
|
+
const configPath = getClaudeConfigPath();
|
|
274
|
+
|
|
275
|
+
if (!fs.existsSync(configPath)) {
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
try {
|
|
280
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
281
|
+
|
|
282
|
+
if (!config.hooks) {
|
|
283
|
+
config.hooks = {};
|
|
284
|
+
}
|
|
285
|
+
if (!config.hooks.PostToolUse) {
|
|
286
|
+
config.hooks.PostToolUse = [];
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const postToolUse = config.hooks.PostToolUse as Array<{
|
|
290
|
+
matcher: string;
|
|
291
|
+
hooks: Array<{ type?: string; command?: string; timeout?: number }>;
|
|
292
|
+
}>;
|
|
293
|
+
|
|
294
|
+
// Find or create TodoWrite entry
|
|
295
|
+
let todoWriteEntry = postToolUse.find((e) => e.matcher === 'TodoWrite');
|
|
296
|
+
if (!todoWriteEntry) {
|
|
297
|
+
todoWriteEntry = { matcher: 'TodoWrite', hooks: [] };
|
|
298
|
+
postToolUse.push(todoWriteEntry);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Check if sync-todos hook is registered with correct path
|
|
302
|
+
const expectedCommand = `node ${syncTodosPath}`;
|
|
303
|
+
const existingHook = todoWriteEntry.hooks.find((h) =>
|
|
304
|
+
h.command?.includes('sync-todos')
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
if (!existingHook) {
|
|
308
|
+
// Add new hook
|
|
309
|
+
todoWriteEntry.hooks.push({
|
|
310
|
+
type: 'command',
|
|
311
|
+
command: expectedCommand,
|
|
312
|
+
timeout: 10,
|
|
313
|
+
});
|
|
314
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
315
|
+
return true;
|
|
316
|
+
} else if (existingHook.command !== expectedCommand) {
|
|
317
|
+
// Update existing hook with correct path
|
|
318
|
+
existingHook.command = expectedCommand;
|
|
319
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
320
|
+
return true;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return false;
|
|
324
|
+
} catch {
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Doctor Command
|
|
3
|
+
*
|
|
4
|
+
* Diagnoses Everstate installation issues.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* npx @everstateai/mcp doctor
|
|
8
|
+
* npx @everstateai/mcp doctor --json
|
|
9
|
+
* npx @everstateai/mcp doctor --verbose
|
|
10
|
+
* npx @everstateai/mcp doctor --component hooks
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import * as fs from 'fs';
|
|
14
|
+
import {
|
|
15
|
+
ValidationContext,
|
|
16
|
+
ValidationSummary,
|
|
17
|
+
ValidationResult,
|
|
18
|
+
DoctorOptions,
|
|
19
|
+
c,
|
|
20
|
+
getEverstateDir,
|
|
21
|
+
} from '../types.js';
|
|
22
|
+
import { validateAll, validateComponent } from '../validators/index.js';
|
|
23
|
+
import { checkForUpdates, checkServerUpdates, ServerUpdate } from '../version.js';
|
|
24
|
+
|
|
25
|
+
export async function doctor(options: DoctorOptions = {}): Promise<void> {
|
|
26
|
+
const context = buildContext(options);
|
|
27
|
+
|
|
28
|
+
// Run validation
|
|
29
|
+
let summary: ValidationSummary;
|
|
30
|
+
|
|
31
|
+
if (options.component) {
|
|
32
|
+
summary = await validateComponent(options.component, context);
|
|
33
|
+
} else {
|
|
34
|
+
summary = await validateAll(context);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Check for server updates (same as dashboard UI)
|
|
38
|
+
let serverUpdates: ServerUpdate[] = [];
|
|
39
|
+
if (context.apiKey) {
|
|
40
|
+
serverUpdates = await checkServerUpdates(context.apiKey);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Output results
|
|
44
|
+
if (options.json) {
|
|
45
|
+
outputJson(summary, serverUpdates);
|
|
46
|
+
} else {
|
|
47
|
+
outputPretty(summary, options.verbose, serverUpdates);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Exit code based on results
|
|
51
|
+
if (summary.failed > 0) {
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function buildContext(options: DoctorOptions): ValidationContext {
|
|
57
|
+
const context: ValidationContext = {
|
|
58
|
+
projectDir: options.projectDir || process.cwd(),
|
|
59
|
+
verbose: options.verbose,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Try to load API key for authenticated checks
|
|
63
|
+
const apiKeyPath = `${getEverstateDir()}/api-key`;
|
|
64
|
+
if (fs.existsSync(apiKeyPath)) {
|
|
65
|
+
try {
|
|
66
|
+
context.apiKey = fs.readFileSync(apiKeyPath, 'utf8').trim();
|
|
67
|
+
} catch {
|
|
68
|
+
// API key not readable - will be reported by validator
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return context;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function outputJson(summary: ValidationSummary, serverUpdates: ServerUpdate[]): void {
|
|
76
|
+
console.log(JSON.stringify({ ...summary, serverUpdates }, null, 2));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function outputPretty(summary: ValidationSummary, verbose?: boolean, serverUpdates: ServerUpdate[] = []): void {
|
|
80
|
+
// Header
|
|
81
|
+
console.log('');
|
|
82
|
+
console.log(c('bold', '╔═══════════════════════════════════════════════════════════╗'));
|
|
83
|
+
console.log(c('bold', '║ Everstate Health Check ║'));
|
|
84
|
+
console.log(c('bold', '╚═══════════════════════════════════════════════════════════╝'));
|
|
85
|
+
console.log('');
|
|
86
|
+
|
|
87
|
+
// Group results by component
|
|
88
|
+
const grouped = new Map<string, ValidationResult[]>();
|
|
89
|
+
for (const result of summary.results) {
|
|
90
|
+
if (!grouped.has(result.component)) {
|
|
91
|
+
grouped.set(result.component, []);
|
|
92
|
+
}
|
|
93
|
+
grouped.get(result.component)!.push(result);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Output each component
|
|
97
|
+
for (const [component, results] of grouped) {
|
|
98
|
+
console.log(c('bold', component));
|
|
99
|
+
|
|
100
|
+
for (const result of results) {
|
|
101
|
+
const icon = getStatusIcon(result.status);
|
|
102
|
+
const color = getStatusColor(result.status);
|
|
103
|
+
const message = result.message;
|
|
104
|
+
|
|
105
|
+
console.log(` ${icon} ${c(color, result.check)}: ${message}`);
|
|
106
|
+
|
|
107
|
+
// Show details in verbose mode
|
|
108
|
+
if (verbose && result.details) {
|
|
109
|
+
for (const [key, value] of Object.entries(result.details)) {
|
|
110
|
+
console.log(c('dim', ` ${key}: ${JSON.stringify(value)}`));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Show repair action hint for failures
|
|
115
|
+
if (result.status === 'fail' && result.repairAction) {
|
|
116
|
+
console.log(c('dim', ` → ${result.repairAction.description}`));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
console.log('');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Check for available updates (local version checks)
|
|
124
|
+
const localUpdates = checkForUpdates();
|
|
125
|
+
|
|
126
|
+
// Show both local and server updates
|
|
127
|
+
const hasLocalUpdates = localUpdates.needsUpdate;
|
|
128
|
+
const hasServerUpdates = serverUpdates.length > 0;
|
|
129
|
+
|
|
130
|
+
if (hasLocalUpdates || hasServerUpdates) {
|
|
131
|
+
console.log(c('yellow', 'Available Updates:'));
|
|
132
|
+
|
|
133
|
+
// Local version file updates
|
|
134
|
+
if (hasLocalUpdates) {
|
|
135
|
+
for (const outdated of localUpdates.outdated) {
|
|
136
|
+
console.log(c('yellow', ` ↑ ${outdated}`));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Server-announced updates (same as dashboard UI)
|
|
141
|
+
if (hasServerUpdates) {
|
|
142
|
+
for (const update of serverUpdates) {
|
|
143
|
+
const breakingFlag = update.breaking ? c('red', ' [BREAKING]') : '';
|
|
144
|
+
console.log(c('cyan', ` ★ ${update.title}`) + ` (${update.category} v${update.version})${breakingFlag}`);
|
|
145
|
+
if (verbose) {
|
|
146
|
+
console.log(c('dim', ` ${update.description}`));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
console.log('');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Summary line
|
|
155
|
+
const parts: string[] = [];
|
|
156
|
+
|
|
157
|
+
if (summary.passed > 0) {
|
|
158
|
+
parts.push(c('green', `${summary.passed} passed`));
|
|
159
|
+
}
|
|
160
|
+
if (summary.warnings > 0) {
|
|
161
|
+
parts.push(c('yellow', `${summary.warnings} warnings`));
|
|
162
|
+
}
|
|
163
|
+
if (summary.failed > 0) {
|
|
164
|
+
parts.push(c('red', `${summary.failed} failed`));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
console.log(`Status: ${parts.join(', ')}`);
|
|
168
|
+
|
|
169
|
+
// Action hint
|
|
170
|
+
if (summary.failed > 0) {
|
|
171
|
+
console.log('');
|
|
172
|
+
console.log(c('cyan', 'Run `npx @everstateai/mcp repair` to fix issues'));
|
|
173
|
+
} else if (summary.warnings > 0) {
|
|
174
|
+
console.log('');
|
|
175
|
+
console.log(c('dim', 'Run `npx @everstateai/mcp repair --include-warnings` to address warnings'));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
console.log('');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function getStatusIcon(status: string): string {
|
|
182
|
+
switch (status) {
|
|
183
|
+
case 'pass':
|
|
184
|
+
return c('green', '✓');
|
|
185
|
+
case 'warn':
|
|
186
|
+
return c('yellow', '⚠');
|
|
187
|
+
case 'fail':
|
|
188
|
+
return c('red', '✗');
|
|
189
|
+
default:
|
|
190
|
+
return '?';
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function getStatusColor(status: string): keyof typeof import('../types').COLORS {
|
|
195
|
+
switch (status) {
|
|
196
|
+
case 'pass':
|
|
197
|
+
return 'green';
|
|
198
|
+
case 'warn':
|
|
199
|
+
return 'yellow';
|
|
200
|
+
case 'fail':
|
|
201
|
+
return 'red';
|
|
202
|
+
default:
|
|
203
|
+
return 'reset';
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// CLI entry point
|
|
208
|
+
export async function doctorCli(args: string[]): Promise<void> {
|
|
209
|
+
const options: DoctorOptions = {};
|
|
210
|
+
|
|
211
|
+
for (let i = 0; i < args.length; i++) {
|
|
212
|
+
const arg = args[i];
|
|
213
|
+
|
|
214
|
+
switch (arg) {
|
|
215
|
+
case '--json':
|
|
216
|
+
options.json = true;
|
|
217
|
+
break;
|
|
218
|
+
case '--verbose':
|
|
219
|
+
case '-v':
|
|
220
|
+
options.verbose = true;
|
|
221
|
+
break;
|
|
222
|
+
case '--component':
|
|
223
|
+
case '-c':
|
|
224
|
+
options.component = args[++i];
|
|
225
|
+
break;
|
|
226
|
+
case '--project':
|
|
227
|
+
case '-p':
|
|
228
|
+
options.projectDir = args[++i];
|
|
229
|
+
break;
|
|
230
|
+
case '--help':
|
|
231
|
+
case '-h':
|
|
232
|
+
printHelp();
|
|
233
|
+
process.exit(0);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
await doctor(options);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function printHelp(): void {
|
|
241
|
+
console.log(`
|
|
242
|
+
Everstate Doctor - Diagnose installation issues
|
|
243
|
+
|
|
244
|
+
Usage:
|
|
245
|
+
npx @everstateai/mcp doctor [options]
|
|
246
|
+
|
|
247
|
+
Options:
|
|
248
|
+
--json Output results as JSON
|
|
249
|
+
--verbose, -v Show additional details
|
|
250
|
+
--component, -c Check only a specific component
|
|
251
|
+
--project, -p Specify project directory
|
|
252
|
+
--help, -h Show this help
|
|
253
|
+
|
|
254
|
+
Components:
|
|
255
|
+
API Key API key file and format
|
|
256
|
+
MCP Configuration Claude Code and Desktop config
|
|
257
|
+
Hooks Hook files and registration
|
|
258
|
+
Project Project-level .everstate.json
|
|
259
|
+
Connectivity API server connectivity
|
|
260
|
+
|
|
261
|
+
Examples:
|
|
262
|
+
npx @everstateai/mcp doctor
|
|
263
|
+
npx @everstateai/mcp doctor --json
|
|
264
|
+
npx @everstateai/mcp doctor --component hooks --verbose
|
|
265
|
+
`);
|
|
266
|
+
}
|