ai-mind-map 1.4.1 → 1.4.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/cli.d.ts.map +1 -1
- package/dist/cli.js +91 -7
- package/dist/cli.js.map +1 -1
- package/dist/install.d.ts.map +1 -1
- package/dist/install.js +443 -28
- package/dist/install.js.map +1 -1
- package/dist/knowledge-graph/changelog.d.ts +3 -3
- package/dist/knowledge-graph/changelog.d.ts.map +1 -1
- package/dist/knowledge-graph/changelog.js +34 -16
- package/dist/knowledge-graph/changelog.js.map +1 -1
- package/package.json +1 -1
package/dist/install.js
CHANGED
|
@@ -164,7 +164,7 @@ function mcpServerEntry(serverEntry) {
|
|
|
164
164
|
return {
|
|
165
165
|
'ai-mind-map': {
|
|
166
166
|
command: 'npx',
|
|
167
|
-
args: ['-y',
|
|
167
|
+
args: ['-y', NPM_PACKAGE_NAME],
|
|
168
168
|
env: {},
|
|
169
169
|
},
|
|
170
170
|
};
|
|
@@ -198,6 +198,337 @@ function writeJsonFile(filePath, data) {
|
|
|
198
198
|
}
|
|
199
199
|
writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf-8');
|
|
200
200
|
}
|
|
201
|
+
// ============================================================
|
|
202
|
+
// Robustness: Dynamic Config Path Detection
|
|
203
|
+
// ============================================================
|
|
204
|
+
/** The npm package name — single source of truth to prevent wrong-name bugs */
|
|
205
|
+
const NPM_PACKAGE_NAME = 'ai-mind-map';
|
|
206
|
+
/**
|
|
207
|
+
* Dynamically find the correct MCP config file for Gemini/Antigravity.
|
|
208
|
+
* Scans candidate files and uses whichever already has mcpServers defined.
|
|
209
|
+
* Falls back to mcp_config.json (Gemini's primary config file).
|
|
210
|
+
*/
|
|
211
|
+
function findGeminiMcpConfigPath() {
|
|
212
|
+
const configDir = path.join(HOME, '.gemini', 'config');
|
|
213
|
+
// Order matters: prefer mcp_config.json (Gemini's primary), then mcp.json
|
|
214
|
+
const candidates = ['mcp_config.json', 'mcp.json'];
|
|
215
|
+
for (const file of candidates) {
|
|
216
|
+
const filePath = path.join(configDir, file);
|
|
217
|
+
const content = readJsonFile(filePath);
|
|
218
|
+
if (content?.mcpServers && typeof content.mcpServers === 'object') {
|
|
219
|
+
return filePath;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// Default to mcp_config.json (confirmed working with Firebase/SpriteAI)
|
|
223
|
+
return path.join(configDir, 'mcp_config.json');
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Dynamically find the MCP config file for VS Code / Copilot.
|
|
227
|
+
* VS Code may store MCP config in settings.json or a dedicated mcp.json.
|
|
228
|
+
*/
|
|
229
|
+
function findVSCodeMcpConfigPath() {
|
|
230
|
+
const settingsDir = getVSCodeSettingsDir();
|
|
231
|
+
// Check for dedicated mcp.json first, then settings.json
|
|
232
|
+
const candidates = [
|
|
233
|
+
path.join(settingsDir, 'mcp.json'),
|
|
234
|
+
path.join(settingsDir, 'settings.json'),
|
|
235
|
+
];
|
|
236
|
+
for (const filePath of candidates) {
|
|
237
|
+
const content = readJsonFile(filePath);
|
|
238
|
+
if (content?.['mcp.servers'] && typeof content['mcp.servers'] === 'object') {
|
|
239
|
+
return filePath;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return path.join(settingsDir, 'settings.json');
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Verify that a config file was written correctly after install.
|
|
246
|
+
* Checks: file exists, entry exists under correct key, package name is correct.
|
|
247
|
+
*/
|
|
248
|
+
function verifyConfigEntry(configPath, mcpKey) {
|
|
249
|
+
// 1. Can we read the file?
|
|
250
|
+
const config = readJsonFile(configPath);
|
|
251
|
+
if (!config) {
|
|
252
|
+
return { ok: false, error: `Config file not readable: ${configPath}` };
|
|
253
|
+
}
|
|
254
|
+
// 2. Does our key exist?
|
|
255
|
+
const servers = config[mcpKey];
|
|
256
|
+
if (!servers || !servers['ai-mind-map']) {
|
|
257
|
+
return { ok: false, error: `Entry 'ai-mind-map' not found under '${mcpKey}' in ${configPath}` };
|
|
258
|
+
}
|
|
259
|
+
// 3. Is the package name correct?
|
|
260
|
+
const entry = servers['ai-mind-map'];
|
|
261
|
+
const args = entry.args;
|
|
262
|
+
if (args && args.includes('ai-mind-map-server')) {
|
|
263
|
+
return { ok: false, error: `Wrong package name 'ai-mind-map-server' in args (should be '${NPM_PACKAGE_NAME}')` };
|
|
264
|
+
}
|
|
265
|
+
// 4. Is the command valid?
|
|
266
|
+
const command = entry.command;
|
|
267
|
+
if (!command || (command !== 'npx' && command !== 'node')) {
|
|
268
|
+
return { ok: false, error: `Invalid command '${command}' (expected 'npx' or 'node')` };
|
|
269
|
+
}
|
|
270
|
+
return { ok: true };
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Auto-fix common config problems in a config file.
|
|
274
|
+
* Returns true if any fixes were applied.
|
|
275
|
+
*/
|
|
276
|
+
function autoFixConfig(configPath, mcpKey) {
|
|
277
|
+
const config = readJsonFile(configPath);
|
|
278
|
+
if (!config)
|
|
279
|
+
return { ok: false, error: 'Cannot read config file' };
|
|
280
|
+
const servers = config[mcpKey];
|
|
281
|
+
if (!servers || !servers['ai-mind-map'])
|
|
282
|
+
return { ok: true }; // nothing to fix
|
|
283
|
+
const entry = servers['ai-mind-map'];
|
|
284
|
+
let fixed = false;
|
|
285
|
+
// Fix 1: Wrong package name
|
|
286
|
+
const args = entry.args;
|
|
287
|
+
if (args) {
|
|
288
|
+
const idx = args.indexOf('ai-mind-map-server');
|
|
289
|
+
if (idx !== -1) {
|
|
290
|
+
args[idx] = NPM_PACKAGE_NAME;
|
|
291
|
+
entry.args = args;
|
|
292
|
+
fixed = true;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
// Fix 2: Add version metadata
|
|
296
|
+
if (!entry._version) {
|
|
297
|
+
entry._version = getPackageVersion();
|
|
298
|
+
entry._installedAt = new Date().toISOString();
|
|
299
|
+
fixed = true;
|
|
300
|
+
}
|
|
301
|
+
if (fixed) {
|
|
302
|
+
servers['ai-mind-map'] = entry;
|
|
303
|
+
config[mcpKey] = servers;
|
|
304
|
+
writeJsonFile(configPath, config);
|
|
305
|
+
}
|
|
306
|
+
return { ok: true, autoFixed: fixed };
|
|
307
|
+
}
|
|
308
|
+
/** Get the current package version */
|
|
309
|
+
function getPackageVersion() {
|
|
310
|
+
try {
|
|
311
|
+
const pkgPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'package.json');
|
|
312
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
313
|
+
return pkg.version ?? '0.0.0';
|
|
314
|
+
}
|
|
315
|
+
catch {
|
|
316
|
+
return '0.0.0';
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
// ============================================================
|
|
320
|
+
// Robustness: Server Smoke Test
|
|
321
|
+
// ============================================================
|
|
322
|
+
/**
|
|
323
|
+
* Spawn the MCP server as a child process and verify it responds correctly.
|
|
324
|
+
* Returns diagnostic info about the server's health.
|
|
325
|
+
*/
|
|
326
|
+
async function smokeTestServer() {
|
|
327
|
+
const result = { ok: false, serverStarts: false, toolCount: 0, hasInstructions: false, error: '' };
|
|
328
|
+
try {
|
|
329
|
+
const runConfig = getServerRunConfig();
|
|
330
|
+
let cmd;
|
|
331
|
+
let args;
|
|
332
|
+
if (runConfig.mode === 'npx') {
|
|
333
|
+
cmd = IS_WIN ? 'npx.cmd' : 'npx';
|
|
334
|
+
args = ['-y', NPM_PACKAGE_NAME];
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
cmd = 'node';
|
|
338
|
+
args = [runConfig.path];
|
|
339
|
+
}
|
|
340
|
+
// We'll try to spawn the server with a timeout
|
|
341
|
+
const { spawn } = await import('node:child_process');
|
|
342
|
+
return new Promise((resolve) => {
|
|
343
|
+
const timeout = setTimeout(() => {
|
|
344
|
+
child.kill();
|
|
345
|
+
result.error = 'Server did not respond within 10 seconds';
|
|
346
|
+
resolve(result);
|
|
347
|
+
}, 10_000);
|
|
348
|
+
const child = spawn(cmd, args, {
|
|
349
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
350
|
+
env: { ...process.env, MINDMAP_PROJECT_ROOT: process.cwd() },
|
|
351
|
+
});
|
|
352
|
+
let stdout = '';
|
|
353
|
+
child.stdout.on('data', (data) => {
|
|
354
|
+
stdout += data.toString();
|
|
355
|
+
});
|
|
356
|
+
child.stderr.on('data', (data) => {
|
|
357
|
+
const line = data.toString();
|
|
358
|
+
// Server logs to stderr via MCP protocol
|
|
359
|
+
if (line.includes('MCP tools registered') || line.includes('tools registered')) {
|
|
360
|
+
result.serverStarts = true;
|
|
361
|
+
}
|
|
362
|
+
if (line.includes('instructions')) {
|
|
363
|
+
result.hasInstructions = true;
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
// Send initialize request via stdin (MCP JSON-RPC over stdio)
|
|
367
|
+
const initRequest = JSON.stringify({
|
|
368
|
+
jsonrpc: '2.0',
|
|
369
|
+
id: 1,
|
|
370
|
+
method: 'initialize',
|
|
371
|
+
params: {
|
|
372
|
+
protocolVersion: '2024-11-05',
|
|
373
|
+
capabilities: {},
|
|
374
|
+
clientInfo: { name: 'smoke-test', version: '1.0.0' },
|
|
375
|
+
},
|
|
376
|
+
});
|
|
377
|
+
child.stdin.write(initRequest + '\n');
|
|
378
|
+
// Give it a moment then send tools/list
|
|
379
|
+
setTimeout(() => {
|
|
380
|
+
const toolsRequest = JSON.stringify({
|
|
381
|
+
jsonrpc: '2.0',
|
|
382
|
+
id: 2,
|
|
383
|
+
method: 'tools/list',
|
|
384
|
+
params: {},
|
|
385
|
+
});
|
|
386
|
+
child.stdin.write(toolsRequest + '\n');
|
|
387
|
+
}, 2000);
|
|
388
|
+
// After 5 seconds, check what we got
|
|
389
|
+
setTimeout(() => {
|
|
390
|
+
clearTimeout(timeout);
|
|
391
|
+
child.kill();
|
|
392
|
+
// Try to parse responses from stdout
|
|
393
|
+
try {
|
|
394
|
+
const lines = stdout.split('\n').filter(l => l.trim());
|
|
395
|
+
for (const line of lines) {
|
|
396
|
+
try {
|
|
397
|
+
const msg = JSON.parse(line);
|
|
398
|
+
if (msg.id === 1 && msg.result) {
|
|
399
|
+
result.serverStarts = true;
|
|
400
|
+
if (msg.result.instructions) {
|
|
401
|
+
result.hasInstructions = true;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
if (msg.id === 2 && msg.result?.tools) {
|
|
405
|
+
result.toolCount = msg.result.tools.length;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
catch {
|
|
409
|
+
// skip unparseable lines
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
catch {
|
|
414
|
+
// parsing failed
|
|
415
|
+
}
|
|
416
|
+
result.ok = result.serverStarts;
|
|
417
|
+
resolve(result);
|
|
418
|
+
}, 5000);
|
|
419
|
+
child.on('error', (err) => {
|
|
420
|
+
clearTimeout(timeout);
|
|
421
|
+
result.error = `Failed to start: ${err.message}`;
|
|
422
|
+
resolve(result);
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
catch (err) {
|
|
427
|
+
result.error = `Smoke test error: ${err instanceof Error ? err.message : String(err)}`;
|
|
428
|
+
return result;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
// ============================================================
|
|
432
|
+
// Robustness: Misconfig Detection & Migration
|
|
433
|
+
// ============================================================
|
|
434
|
+
/**
|
|
435
|
+
* Check for common misconfigurations and return diagnostics.
|
|
436
|
+
* Called by `doctor` to detect problems.
|
|
437
|
+
*/
|
|
438
|
+
function detectMisconfigs() {
|
|
439
|
+
const issues = [];
|
|
440
|
+
// Check 1: Gemini config in wrong file (mcp.json instead of mcp_config.json)
|
|
441
|
+
const geminiWrongFile = path.join(HOME, '.gemini', 'config', 'mcp.json');
|
|
442
|
+
const geminiRightFile = path.join(HOME, '.gemini', 'config', 'mcp_config.json');
|
|
443
|
+
const wrongConfig = readJsonFile(geminiWrongFile);
|
|
444
|
+
if (wrongConfig?.mcpServers) {
|
|
445
|
+
const servers = wrongConfig.mcpServers;
|
|
446
|
+
if (servers['ai-mind-map']) {
|
|
447
|
+
const rightConfig = readJsonFile(geminiRightFile);
|
|
448
|
+
const rightServers = rightConfig?.mcpServers ?? {};
|
|
449
|
+
if (!rightServers['ai-mind-map']) {
|
|
450
|
+
issues.push({
|
|
451
|
+
agent: 'Antigravity (Gemini)',
|
|
452
|
+
problem: `Config in mcp.json but Gemini reads mcp_config.json`,
|
|
453
|
+
fix: 'Move entry to mcp_config.json',
|
|
454
|
+
fixFn: () => {
|
|
455
|
+
// Copy entry to correct file
|
|
456
|
+
const config = readJsonFile(geminiRightFile) ?? { mcpServers: {} };
|
|
457
|
+
const mcpServers = config.mcpServers ?? {};
|
|
458
|
+
mcpServers['ai-mind-map'] = servers['ai-mind-map'];
|
|
459
|
+
config.mcpServers = mcpServers;
|
|
460
|
+
writeJsonFile(geminiRightFile, config);
|
|
461
|
+
// Remove from wrong file
|
|
462
|
+
delete servers['ai-mind-map'];
|
|
463
|
+
if (Object.keys(servers).length === 0)
|
|
464
|
+
delete wrongConfig.mcpServers;
|
|
465
|
+
writeJsonFile(geminiWrongFile, wrongConfig);
|
|
466
|
+
},
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
// Check 2: Wrong package name in any config
|
|
472
|
+
const configsToCheck = [
|
|
473
|
+
{ agent: 'Claude Code', path: path.join(HOME, '.claude', 'claude_desktop_config.json'), key: 'mcpServers' },
|
|
474
|
+
{ agent: 'Cursor', path: path.join(HOME, '.cursor', 'mcp.json'), key: 'mcpServers' },
|
|
475
|
+
{ agent: 'Antigravity', path: geminiRightFile, key: 'mcpServers' },
|
|
476
|
+
{ agent: 'VS Code', path: path.join(getVSCodeSettingsDir(), 'settings.json'), key: 'mcp.servers' },
|
|
477
|
+
];
|
|
478
|
+
for (const check of configsToCheck) {
|
|
479
|
+
const content = readJsonFile(check.path);
|
|
480
|
+
if (!content)
|
|
481
|
+
continue;
|
|
482
|
+
const servers = content[check.key];
|
|
483
|
+
if (!servers?.['ai-mind-map'])
|
|
484
|
+
continue;
|
|
485
|
+
const entry = servers['ai-mind-map'];
|
|
486
|
+
const args = entry.args;
|
|
487
|
+
if (args?.includes('ai-mind-map-server')) {
|
|
488
|
+
issues.push({
|
|
489
|
+
agent: check.agent,
|
|
490
|
+
problem: `Wrong package name 'ai-mind-map-server' in args`,
|
|
491
|
+
fix: `Change to '${NPM_PACKAGE_NAME}'`,
|
|
492
|
+
fixFn: () => {
|
|
493
|
+
const idx = args.indexOf('ai-mind-map-server');
|
|
494
|
+
if (idx !== -1)
|
|
495
|
+
args[idx] = NPM_PACKAGE_NAME;
|
|
496
|
+
entry.args = args;
|
|
497
|
+
servers['ai-mind-map'] = entry;
|
|
498
|
+
content[check.key] = servers;
|
|
499
|
+
writeJsonFile(check.path, content);
|
|
500
|
+
},
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
// Check 3: Outdated version metadata
|
|
505
|
+
for (const check of configsToCheck) {
|
|
506
|
+
const content = readJsonFile(check.path);
|
|
507
|
+
if (!content)
|
|
508
|
+
continue;
|
|
509
|
+
const servers = content[check.key];
|
|
510
|
+
if (!servers?.['ai-mind-map'])
|
|
511
|
+
continue;
|
|
512
|
+
const entry = servers['ai-mind-map'];
|
|
513
|
+
const configVersion = entry._version;
|
|
514
|
+
const currentVersion = getPackageVersion();
|
|
515
|
+
if (configVersion && configVersion !== currentVersion && currentVersion !== '0.0.0') {
|
|
516
|
+
issues.push({
|
|
517
|
+
agent: check.agent,
|
|
518
|
+
problem: `Config version ${configVersion} is outdated (current: ${currentVersion})`,
|
|
519
|
+
fix: 'Update version metadata',
|
|
520
|
+
fixFn: () => {
|
|
521
|
+
entry._version = currentVersion;
|
|
522
|
+
entry._installedAt = new Date().toISOString();
|
|
523
|
+
servers['ai-mind-map'] = entry;
|
|
524
|
+
content[check.key] = servers;
|
|
525
|
+
writeJsonFile(check.path, content);
|
|
526
|
+
},
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
return issues;
|
|
531
|
+
}
|
|
201
532
|
/**
|
|
202
533
|
* Merge an MCP server entry into an existing JSON config.
|
|
203
534
|
* Creates the file if it doesn't exist.
|
|
@@ -218,7 +549,7 @@ function mergeMcpConfig(configPath, mcpKey, serverEntry) {
|
|
|
218
549
|
if (runConfig.mode === 'npx') {
|
|
219
550
|
servers['ai-mind-map'] = {
|
|
220
551
|
command: 'npx',
|
|
221
|
-
args: ['-y',
|
|
552
|
+
args: ['-y', NPM_PACKAGE_NAME],
|
|
222
553
|
env: {},
|
|
223
554
|
};
|
|
224
555
|
}
|
|
@@ -310,7 +641,7 @@ function getAgentDefinitions(serverEntry) {
|
|
|
310
641
|
const mcpServers = existing['mcp.servers'] ?? {};
|
|
311
642
|
const runConfig = getServerRunConfig();
|
|
312
643
|
mcpServers['ai-mind-map'] = runConfig.mode === 'npx'
|
|
313
|
-
? { command: 'npx', args: ['-y',
|
|
644
|
+
? { command: 'npx', args: ['-y', NPM_PACKAGE_NAME], env: {} }
|
|
314
645
|
: { command: 'node', args: [serverEntry], env: {} };
|
|
315
646
|
existing['mcp.servers'] = mcpServers;
|
|
316
647
|
return JSON.stringify(existing, null, 2);
|
|
@@ -332,7 +663,7 @@ function getAgentDefinitions(serverEntry) {
|
|
|
332
663
|
const mcpServers = existing['mcp.servers'] ?? {};
|
|
333
664
|
const runConfig = getServerRunConfig();
|
|
334
665
|
mcpServers['ai-mind-map'] = runConfig.mode === 'npx'
|
|
335
|
-
? { command: 'npx', args: ['-y',
|
|
666
|
+
? { command: 'npx', args: ['-y', NPM_PACKAGE_NAME], env: {} }
|
|
336
667
|
: { command: 'node', args: [serverEntry], env: {} };
|
|
337
668
|
existing['mcp.servers'] = mcpServers;
|
|
338
669
|
return JSON.stringify(existing, null, 2);
|
|
@@ -349,10 +680,10 @@ function getAgentDefinitions(serverEntry) {
|
|
|
349
680
|
path.join(HOME, '.gemini', 'config'),
|
|
350
681
|
path.join(HOME, '.gemini'),
|
|
351
682
|
],
|
|
352
|
-
configPath:
|
|
683
|
+
configPath: findGeminiMcpConfigPath(),
|
|
353
684
|
generateConfig: () => JSON.stringify({ mcpServers: mcpServerEntry(serverEntry) }, null, 2),
|
|
354
|
-
isConfigured: () => hasMcpConfig(
|
|
355
|
-
removeConfig: () => removeMcpConfig(
|
|
685
|
+
isConfigured: () => hasMcpConfig(findGeminiMcpConfigPath(), 'mcpServers'),
|
|
686
|
+
removeConfig: () => removeMcpConfig(findGeminiMcpConfigPath(), 'mcpServers'),
|
|
356
687
|
},
|
|
357
688
|
// 6. Zed
|
|
358
689
|
{
|
|
@@ -367,7 +698,7 @@ function getAgentDefinitions(serverEntry) {
|
|
|
367
698
|
const contextServers = existing['context_servers'] ?? {};
|
|
368
699
|
const runConfig = getServerRunConfig();
|
|
369
700
|
const serverConfig = runConfig.mode === 'npx'
|
|
370
|
-
? { path: 'npx', args: ['-y',
|
|
701
|
+
? { path: 'npx', args: ['-y', NPM_PACKAGE_NAME], env: {} }
|
|
371
702
|
: { path: 'node', args: [serverEntry], env: {} };
|
|
372
703
|
contextServers['ai-mind-map'] = {
|
|
373
704
|
command: serverConfig,
|
|
@@ -416,7 +747,7 @@ function getAgentDefinitions(serverEntry) {
|
|
|
416
747
|
if (!exists) {
|
|
417
748
|
const runConfig = getServerRunConfig();
|
|
418
749
|
const serverEntryConfig = runConfig.mode === 'npx'
|
|
419
|
-
? { command: 'npx', args: ['-y',
|
|
750
|
+
? { command: 'npx', args: ['-y', NPM_PACKAGE_NAME], env: {} }
|
|
420
751
|
: { command: 'node', args: [serverEntry], env: {} };
|
|
421
752
|
modelContextProtocolServers.push({
|
|
422
753
|
name: 'ai-mind-map',
|
|
@@ -478,6 +809,7 @@ export async function installAgents() {
|
|
|
478
809
|
const agents = getAgentDefinitions(serverEntry);
|
|
479
810
|
let foundCount = 0;
|
|
480
811
|
let configuredCount = 0;
|
|
812
|
+
let verifyFailCount = 0;
|
|
481
813
|
for (const agent of agents) {
|
|
482
814
|
const detected = agent.probePaths.some((p) => existsSync(p));
|
|
483
815
|
if (!detected) {
|
|
@@ -490,6 +822,16 @@ export async function installAgents() {
|
|
|
490
822
|
// Check if already configured
|
|
491
823
|
if (agent.isConfigured()) {
|
|
492
824
|
logSkipped(agent.name, 'already configured');
|
|
825
|
+
// Still verify existing config is correct
|
|
826
|
+
const mcpKey = ['claude-code', 'cursor', 'antigravity'].includes(agent.id) ? 'mcpServers' : 'mcp.servers';
|
|
827
|
+
const verifyResult = verifyConfigEntry(agent.configPath, mcpKey);
|
|
828
|
+
if (!verifyResult.ok) {
|
|
829
|
+
console.log(` ${c.yellow}⚠${c.reset} ${c.dim}Verification issue: ${verifyResult.error}${c.reset}`);
|
|
830
|
+
const fixResult = autoFixConfig(agent.configPath, mcpKey);
|
|
831
|
+
if (fixResult.autoFixed) {
|
|
832
|
+
console.log(` ${c.green}✔${c.reset} ${c.dim}Auto-fixed!${c.reset}`);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
493
835
|
continue;
|
|
494
836
|
}
|
|
495
837
|
// Write the configuration
|
|
@@ -511,7 +853,25 @@ export async function installAgents() {
|
|
|
511
853
|
const configContent = agent.generateConfig(serverEntry);
|
|
512
854
|
writeFileSync(agent.configPath, configContent + '\n', 'utf-8');
|
|
513
855
|
}
|
|
514
|
-
|
|
856
|
+
// ── Post-write verification ──
|
|
857
|
+
const mcpKey = ['claude-code', 'cursor', 'antigravity'].includes(agent.id) ? 'mcpServers' : 'mcp.servers';
|
|
858
|
+
const verifyResult = verifyConfigEntry(agent.configPath, mcpKey);
|
|
859
|
+
if (verifyResult.ok) {
|
|
860
|
+
logConfigured(agent.name);
|
|
861
|
+
console.log(` ${c.green}✔${c.reset} ${c.dim}Verified: config entry is correct${c.reset}`);
|
|
862
|
+
}
|
|
863
|
+
else {
|
|
864
|
+
logConfigured(agent.name);
|
|
865
|
+
console.log(` ${c.yellow}⚠${c.reset} ${c.dim}Verify warning: ${verifyResult.error}${c.reset}`);
|
|
866
|
+
// Attempt auto-fix
|
|
867
|
+
const fixResult = autoFixConfig(agent.configPath, mcpKey);
|
|
868
|
+
if (fixResult.autoFixed) {
|
|
869
|
+
console.log(` ${c.green}✔${c.reset} ${c.dim}Auto-fixed!${c.reset}`);
|
|
870
|
+
}
|
|
871
|
+
else {
|
|
872
|
+
verifyFailCount++;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
515
875
|
configuredCount++;
|
|
516
876
|
}
|
|
517
877
|
catch (err) {
|
|
@@ -526,6 +886,26 @@ export async function installAgents() {
|
|
|
526
886
|
}
|
|
527
887
|
else {
|
|
528
888
|
logOk(`${foundCount} agent(s) detected, ${configuredCount} newly configured.`);
|
|
889
|
+
if (verifyFailCount > 0) {
|
|
890
|
+
logWarn(`${verifyFailCount} config(s) have verification warnings — run "ai-mind-map doctor --fix" to repair.`);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
// Check for misconfigurations from older versions
|
|
894
|
+
const misconfigs = detectMisconfigs();
|
|
895
|
+
if (misconfigs.length > 0) {
|
|
896
|
+
console.log('');
|
|
897
|
+
heading('⚠️ Misconfigurations Detected');
|
|
898
|
+
divider();
|
|
899
|
+
for (const issue of misconfigs) {
|
|
900
|
+
console.log(` ${c.yellow}⚠${c.reset} ${c.bold}${issue.agent}${c.reset}: ${issue.problem}`);
|
|
901
|
+
if (issue.fixFn) {
|
|
902
|
+
issue.fixFn();
|
|
903
|
+
console.log(` ${c.green}✔${c.reset} AUTO-FIXED: ${issue.fix}`);
|
|
904
|
+
}
|
|
905
|
+
else {
|
|
906
|
+
console.log(` ${c.dim}Fix: ${issue.fix}${c.reset}`);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
529
909
|
}
|
|
530
910
|
// Deploy rules files so agents know about our capabilities
|
|
531
911
|
console.log('');
|
|
@@ -798,24 +1178,6 @@ export async function runDoctor() {
|
|
|
798
1178
|
logFail(`${paddedName} ${result.message}`);
|
|
799
1179
|
}
|
|
800
1180
|
}
|
|
801
|
-
// Summary
|
|
802
|
-
const okCount = results.filter((r) => r.status === 'ok').length;
|
|
803
|
-
const warnCount = results.filter((r) => r.status === 'warn').length;
|
|
804
|
-
const failCount = results.filter((r) => r.status === 'fail').length;
|
|
805
|
-
console.log('');
|
|
806
|
-
divider();
|
|
807
|
-
if (failCount > 0) {
|
|
808
|
-
logFail(`${okCount} passed, ${warnCount} warnings, ${failCount} failures`);
|
|
809
|
-
logInfo('Fix the failures above before using AI Mind Map.');
|
|
810
|
-
}
|
|
811
|
-
else if (warnCount > 0) {
|
|
812
|
-
logWarn(`${okCount} passed, ${warnCount} warnings`);
|
|
813
|
-
logInfo('AI Mind Map should work, but address warnings for best experience.');
|
|
814
|
-
}
|
|
815
|
-
else {
|
|
816
|
-
logOk(`All ${okCount} checks passed!`);
|
|
817
|
-
logInfo('AI Mind Map is ready to use.');
|
|
818
|
-
}
|
|
819
1181
|
// Agent detail table
|
|
820
1182
|
if (agentStatuses.some((a) => a.detected)) {
|
|
821
1183
|
console.log('');
|
|
@@ -838,6 +1200,59 @@ export async function runDoctor() {
|
|
|
838
1200
|
}
|
|
839
1201
|
}
|
|
840
1202
|
}
|
|
1203
|
+
// 8. Misconfig detection
|
|
1204
|
+
const misconfigs = detectMisconfigs();
|
|
1205
|
+
if (misconfigs.length > 0) {
|
|
1206
|
+
console.log('');
|
|
1207
|
+
heading('⚠️ Configuration Issues Found');
|
|
1208
|
+
divider();
|
|
1209
|
+
const shouldFix = process.argv.includes('--fix');
|
|
1210
|
+
for (const issue of misconfigs) {
|
|
1211
|
+
results.push({
|
|
1212
|
+
name: `Config: ${issue.agent}`,
|
|
1213
|
+
status: 'warn',
|
|
1214
|
+
message: issue.problem,
|
|
1215
|
+
});
|
|
1216
|
+
console.log(` ${c.yellow}⚠${c.reset} ${c.bold}${issue.agent}${c.reset}: ${issue.problem}`);
|
|
1217
|
+
if (shouldFix && issue.fixFn) {
|
|
1218
|
+
issue.fixFn();
|
|
1219
|
+
console.log(` ${c.green}✔${c.reset} AUTO-FIXED: ${issue.fix}`);
|
|
1220
|
+
}
|
|
1221
|
+
else if (issue.fixFn) {
|
|
1222
|
+
console.log(` ${c.dim}Run with --fix to auto-repair: ${issue.fix}${c.reset}`);
|
|
1223
|
+
}
|
|
1224
|
+
else {
|
|
1225
|
+
console.log(` ${c.dim}Manual fix required: ${issue.fix}${c.reset}`);
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
else {
|
|
1230
|
+
results.push({
|
|
1231
|
+
name: 'Config Integrity',
|
|
1232
|
+
status: 'ok',
|
|
1233
|
+
message: 'No misconfigurations detected',
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1236
|
+
// Re-print summary with updated results
|
|
1237
|
+
const finalOk = results.filter((r) => r.status === 'ok').length;
|
|
1238
|
+
const finalWarn = results.filter((r) => r.status === 'warn').length;
|
|
1239
|
+
const finalFail = results.filter((r) => r.status === 'fail').length;
|
|
1240
|
+
console.log('');
|
|
1241
|
+
divider();
|
|
1242
|
+
if (finalFail > 0) {
|
|
1243
|
+
logFail(`${finalOk} passed, ${finalWarn} warnings, ${finalFail} failures`);
|
|
1244
|
+
logInfo('Fix the failures above before using AI Mind Map.');
|
|
1245
|
+
}
|
|
1246
|
+
else if (finalWarn > 0) {
|
|
1247
|
+
logWarn(`${finalOk} passed, ${finalWarn} warnings`);
|
|
1248
|
+
if (!process.argv.includes('--fix')) {
|
|
1249
|
+
logInfo('Run "ai-mind-map doctor --fix" to auto-repair warnings.');
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
else {
|
|
1253
|
+
logOk(`All ${finalOk} checks passed!`);
|
|
1254
|
+
logInfo('AI Mind Map is ready to use.');
|
|
1255
|
+
}
|
|
841
1256
|
console.log('');
|
|
842
1257
|
}
|
|
843
1258
|
// ============================================================
|