agentvibes 5.2.0 → 5.3.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.
Files changed (52) hide show
  1. package/.claude/config/audio-effects.cfg +1 -1
  2. package/.claude/hooks/audio-cache-utils.sh +246 -246
  3. package/.claude/hooks/background-music-manager.sh +404 -404
  4. package/.claude/hooks/bmad-speak-enhanced.sh +165 -165
  5. package/.claude/hooks/bmad-speak.sh +290 -290
  6. package/.claude/hooks/bmad-tts-injector.sh +568 -568
  7. package/.claude/hooks/bmad-voice-manager.sh +928 -928
  8. package/.claude/hooks/clawdbot-receiver-SECURE.sh +129 -129
  9. package/.claude/hooks/clawdbot-receiver.sh +107 -107
  10. package/.claude/hooks/clean-audio-cache.sh +22 -22
  11. package/.claude/hooks/cleanup-cache.sh +106 -106
  12. package/.claude/hooks/configure-rdp-mode.sh +137 -137
  13. package/.claude/hooks/download-extra-voices.sh +244 -244
  14. package/.claude/hooks/effects-manager.sh +268 -268
  15. package/.claude/hooks/github-star-reminder.sh +154 -154
  16. package/.claude/hooks/language-manager.sh +362 -362
  17. package/.claude/hooks/learn-manager.sh +492 -492
  18. package/.claude/hooks/macos-voice-manager.sh +205 -205
  19. package/.claude/hooks/migrate-background-music.sh +125 -125
  20. package/.claude/hooks/migrate-to-agentvibes.sh +161 -161
  21. package/.claude/hooks/optimize-background-music.sh +87 -87
  22. package/.claude/hooks/path-resolver.sh +60 -60
  23. package/.claude/hooks/personality-manager.sh +448 -448
  24. package/.claude/hooks/piper-installer.sh +292 -292
  25. package/.claude/hooks/piper-multispeaker-registry.sh +171 -171
  26. package/.claude/hooks/play-tts-enhanced.sh +105 -105
  27. package/.claude/hooks/play-tts-ssh-remote.sh +104 -10
  28. package/.claude/hooks/play-tts-termux-ssh.sh +169 -169
  29. package/.claude/hooks/play-tts.sh +31 -11
  30. package/.claude/hooks/prepare-release.sh +54 -54
  31. package/.claude/hooks/provider-commands.sh +617 -617
  32. package/.claude/hooks/provider-manager.sh +399 -399
  33. package/.claude/hooks/replay-target-audio.sh +95 -95
  34. package/.claude/hooks/sentiment-manager.sh +201 -201
  35. package/.claude/hooks/speed-manager.sh +291 -291
  36. package/.claude/hooks/stop-tts.sh +84 -84
  37. package/.claude/hooks/termux-installer.sh +261 -261
  38. package/.claude/hooks/translate-manager.sh +341 -341
  39. package/.claude/hooks/tts-queue-worker.sh +145 -145
  40. package/.claude/hooks/tts-queue.sh +165 -165
  41. package/.claude/hooks/voice-manager.sh +552 -548
  42. package/.claude/hooks-windows/bmad-party-speak.ps1 +5 -1
  43. package/.claude/hooks-windows/play-tts.ps1 +91 -59
  44. package/README.md +21 -2
  45. package/RELEASE_NOTES.md +130 -0
  46. package/bin/mcp-server.sh +206 -206
  47. package/mcp-server/server.py +35 -6
  48. package/package.json +1 -1
  49. package/src/console/tabs/setup-tab.js +68 -29
  50. package/src/console/tabs/voices-tab.js +9 -3
  51. package/src/installer.js +79 -213
  52. package/src/services/llm-provider-service.js +139 -75
@@ -605,12 +605,19 @@ export function createSetupTab(screen, services) {
605
605
  }
606
606
  });
607
607
  btn.key(['down'], () => {
608
+ // Column-preserving down nav. If pressing down from Install/Remove
609
+ // would land on the Default row (which has no Install/Remove — all
610
+ // three slots are configBtn duplicates), don't move. Configure
611
+ // column navigates normally into Default row's Configure.
612
+ const col = providerFocusIndex % 3;
608
613
  const nextIdx = providerFocusIndex + 3;
609
- if (nextIdx < providerFocusableItems.length) {
610
- providerFocusIndex = nextIdx;
611
- providerFocusableItems[providerFocusIndex].focus();
612
- screen.render();
613
- }
614
+ if (nextIdx >= providerFocusableItems.length) return;
615
+ const nextRowIdx = Math.floor(nextIdx / 3);
616
+ const nextRow = PROVIDERS[nextRowIdx];
617
+ if (col < 2 && nextRow && nextRow.isDefault) return; // skip Default from Install/Remove
618
+ providerFocusIndex = nextIdx;
619
+ providerFocusableItems[providerFocusIndex].focus();
620
+ screen.render();
614
621
  });
615
622
  }
616
623
 
@@ -1271,29 +1278,32 @@ export function createSetupTab(screen, services) {
1271
1278
  const phrase = SAMPLE_PHRASES[Math.floor(Math.random() * SAMPLE_PHRASES.length)];
1272
1279
 
1273
1280
  // Route through remote provider if active
1281
+ // Search order: CLAUDE_PROJECT_DIR → cwd → package root → home
1274
1282
  const _remoteProviders = ['ssh-remote', 'agentvibes-receiver'];
1275
1283
  let _activeProvider = '';
1276
1284
  try {
1277
- const _projectRoot = path.resolve(__dirname, '..', '..');
1285
+ const _pkgRoot = path.resolve(__dirname, '..', '..');
1278
1286
  const _provPaths = [
1279
- path.join(_projectRoot, '.claude', 'tts-provider.txt'),
1287
+ process.env.CLAUDE_PROJECT_DIR && path.join(process.env.CLAUDE_PROJECT_DIR, '.claude', 'tts-provider.txt'),
1288
+ path.join(process.cwd(), '.claude', 'tts-provider.txt'),
1289
+ path.join(_pkgRoot, '.claude', 'tts-provider.txt'),
1280
1290
  path.join(os.homedir(), '.claude', 'tts-provider.txt'),
1281
- ];
1291
+ ].filter(Boolean);
1282
1292
  for (const p of _provPaths) {
1283
1293
  if (fs.existsSync(p)) { _activeProvider = fs.readFileSync(p, 'utf8').trim(); break; }
1284
1294
  }
1285
1295
  } catch {}
1286
1296
 
1287
1297
  if (_remoteProviders.includes(_activeProvider)) {
1288
- const _projectRoot = path.resolve(__dirname, '..', '..');
1298
+ const _hooksBase = process.env.CLAUDE_PROJECT_DIR || process.cwd();
1289
1299
  let rProc;
1290
1300
  if (_isWin) {
1291
- const _playTts = path.join(_projectRoot, '.claude', 'hooks-windows', 'play-tts.ps1');
1301
+ const _playTts = path.join(_hooksBase, '.claude', 'hooks-windows', 'play-tts.ps1');
1292
1302
  rProc = spawn('powershell', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', _playTts, phrase, voiceId], {
1293
1303
  stdio: 'ignore', detached: false, windowsHide: true, env: _spawnEnv,
1294
1304
  });
1295
1305
  } else {
1296
- const _playTts = path.join(_projectRoot, '.claude', 'hooks', 'play-tts.sh');
1306
+ const _playTts = path.join(_hooksBase, '.claude', 'hooks', 'play-tts.sh');
1297
1307
  rProc = spawn('bash', [_playTts, phrase, voiceId], {
1298
1308
  stdio: 'ignore', detached: true, env: _spawnEnv,
1299
1309
  });
@@ -1522,7 +1532,6 @@ export function createSetupTab(screen, services) {
1522
1532
  hideAllProviderRows();
1523
1533
  contentBox.hide();
1524
1534
 
1525
- const mcpPath = path.join(targetDir, '.mcp.json');
1526
1535
  const hooksDir = path.join(targetDir, '.claude', process.platform === 'win32' ? 'hooks-windows' : 'hooks');
1527
1536
  const installed = installedState['claude-code'];
1528
1537
  const verb = wasInstalled ? 'reinstalled' : 'installed';
@@ -1532,9 +1541,14 @@ export function createSetupTab(screen, services) {
1532
1541
  lines.push('');
1533
1542
 
1534
1543
  if (result) {
1535
- lines.push(result.success
1536
- ? `{green-fg}AgentVibes for Claude Code ${verb}!{/green-fg}`
1537
- : `{red-fg}Installation failed{/red-fg}`);
1544
+ if (result.success) {
1545
+ lines.push(`{green-fg}AgentVibes for Claude Code ${verb}!{/green-fg}`);
1546
+ if (result.mcpError) {
1547
+ lines.push(`{yellow-fg}Warning:{/yellow-fg} ${result.mcpError}`);
1548
+ }
1549
+ } else {
1550
+ lines.push(`{red-fg}Installation failed:{/red-fg} ${result.error || 'Unknown error'}`);
1551
+ }
1538
1552
  } else {
1539
1553
  lines.push(installed
1540
1554
  ? '{green-fg}Installed{/green-fg}'
@@ -1543,10 +1557,7 @@ export function createSetupTab(screen, services) {
1543
1557
 
1544
1558
  lines.push('');
1545
1559
  lines.push(`{bold}{cyan-fg}What ${result ? `got ${verb}` : 'gets installed'}:{/cyan-fg}{/bold}`);
1546
- lines.push('');
1547
- lines.push(' {yellow-fg}1.{/yellow-fg} {bold}.mcp.json{/bold} (project root)');
1548
- lines.push(` Location: ${mcpPath}`);
1549
- lines.push(' Registers the AgentVibes MCP server for Claude Code.');
1560
+ lines.push(' {yellow-fg}1.{/yellow-fg} {bold}.mcp.json{/bold} (MCP server — natural language voice control)');
1550
1561
  lines.push('');
1551
1562
  lines.push(' {yellow-fg}2.{/yellow-fg} {bold}.claude/hooks/{/bold} (session-start + pre-tool hooks)');
1552
1563
  lines.push(` Location: ${hooksDir}`);
@@ -1575,9 +1586,14 @@ export function createSetupTab(screen, services) {
1575
1586
  const lines = [];
1576
1587
  lines.push('{bold}{cyan-fg}GitHub Copilot -- AgentVibes Integration{/cyan-fg}{/bold}');
1577
1588
  lines.push('');
1578
- lines.push(result.success
1579
- ? `{green-fg}AgentVibes for Copilot ${verb}!{/green-fg}`
1580
- : `{red-fg}Installation failed:{/red-fg} ${result.error || 'Unknown error'}`);
1589
+ if (result.success) {
1590
+ lines.push(`{green-fg}AgentVibes for Copilot ${verb}!{/green-fg}`);
1591
+ if (result.mcpError) {
1592
+ lines.push(`{yellow-fg}MCP config failed:{/yellow-fg} ${result.mcpError}`);
1593
+ }
1594
+ } else {
1595
+ lines.push(`{red-fg}Installation failed:{/red-fg} ${result.error || 'Unknown error'}`);
1596
+ }
1581
1597
  lines.push('');
1582
1598
  lines.push(`{bold}{cyan-fg}What got ${verb}:{/cyan-fg}{/bold}`);
1583
1599
  lines.push('');
@@ -1604,9 +1620,14 @@ export function createSetupTab(screen, services) {
1604
1620
  const lines = [];
1605
1621
  lines.push('{bold}{cyan-fg}OpenAI Codex -- AgentVibes Integration{/cyan-fg}{/bold}');
1606
1622
  lines.push('');
1607
- lines.push(result.success
1608
- ? `{green-fg}AgentVibes for Codex ${verb}!{/green-fg}`
1609
- : `{red-fg}Installation failed:{/red-fg} ${result.error || 'Unknown error'}`);
1623
+ if (result.success) {
1624
+ lines.push(`{green-fg}AgentVibes for Codex ${verb}!{/green-fg}`);
1625
+ if (result.mcpError) {
1626
+ lines.push(`{yellow-fg}MCP config failed:{/yellow-fg} ${result.mcpError}`);
1627
+ }
1628
+ } else {
1629
+ lines.push(`{red-fg}Installation failed:{/red-fg} ${result.error || 'Unknown error'}`);
1630
+ }
1610
1631
  lines.push('');
1611
1632
  lines.push(`{bold}{cyan-fg}What got ${verb}:{/cyan-fg}{/bold}`);
1612
1633
  lines.push('');
@@ -1679,11 +1700,29 @@ export function createSetupTab(screen, services) {
1679
1700
 
1680
1701
  infoBox.key(['escape', 'enter'], () => {
1681
1702
  // After dismissing the install/remove info page, advance focus to the
1682
- // NEXT provider row but keep the same column (Install stays on Install,
1683
- // Remove stays on Remove, Configure stays on Configure). Each row has
1684
- // 3 focusable slots so +3 moves one full row down with wraparound.
1703
+ // NEXT provider row but keep the same column (Install/Remove/Configure).
1704
+ // Each row has 3 focusable slots, so +3 moves one full row down.
1705
+ //
1706
+ // Special case: when leaving the LAST installable provider (Codex) from
1707
+ // Install or Remove column, skip the Default row (it has no Install or
1708
+ // Remove) and wrap to the FIRST Configure button (Claude Code Configure).
1709
+ // This lets the user cleanly walk all three installs, then all three
1710
+ // Configures, ending on Default Configure.
1685
1711
  const max = providerFocusableItems.length;
1686
- const nextIdx = max > 0 ? (_preInfoFocusIndex + 3) % max : 0;
1712
+ if (max === 0) { showProviderListView(0); return; }
1713
+ const col = _preInfoFocusIndex % 3; // 0=Install, 1=Remove, 2=Configure
1714
+ const row = Math.floor(_preInfoFocusIndex / 3);
1715
+ const nextRow = PROVIDERS[row + 1];
1716
+ const nextRowIsDefault = nextRow && nextRow.isDefault;
1717
+ let nextIdx;
1718
+ if (col < 2 && nextRowIsDefault) {
1719
+ // Last Install/Remove → jump to the FIRST non-default provider's
1720
+ // Configure column (dynamic: don't hardcode PROVIDERS[0]).
1721
+ const firstInstallableIdx = PROVIDERS.findIndex(p => !p.isDefault);
1722
+ nextIdx = firstInstallableIdx >= 0 ? firstInstallableIdx * 3 + 2 : (_preInfoFocusIndex + 3) % max;
1723
+ } else {
1724
+ nextIdx = (_preInfoFocusIndex + 3) % max;
1725
+ }
1687
1726
  showProviderListView(nextIdx);
1688
1727
  });
1689
1728
 
@@ -868,14 +868,17 @@ export function createVoicesTab(screen, services) {
868
868
  _playingVoiceId = null;
869
869
 
870
870
  // Check if we should route through remote provider (ssh-remote / agentvibes-receiver)
871
+ // Search order: CLAUDE_PROJECT_DIR (actual project) → cwd → package root → home
871
872
  const projectRoot = path.resolve(__dirname, '..', '..');
872
873
  const remoteProviders = ['ssh-remote', 'agentvibes-receiver'];
873
874
  let activeProvider = '';
874
875
  try {
875
876
  const providerPaths = [
877
+ process.env.CLAUDE_PROJECT_DIR && path.join(process.env.CLAUDE_PROJECT_DIR, '.claude', 'tts-provider.txt'),
878
+ path.join(process.cwd(), '.claude', 'tts-provider.txt'),
876
879
  path.join(projectRoot, '.claude', 'tts-provider.txt'),
877
880
  path.join(os.homedir(), '.claude', 'tts-provider.txt'),
878
- ];
881
+ ].filter(Boolean);
879
882
  for (const p of providerPaths) {
880
883
  if (fs.existsSync(p)) { activeProvider = fs.readFileSync(p, 'utf8').trim(); break; }
881
884
  }
@@ -884,14 +887,17 @@ export function createVoicesTab(screen, services) {
884
887
  if (remoteProviders.includes(activeProvider)) {
885
888
  const isWindows = process.platform === 'win32' && !process.env.WSL_DISTRO_NAME;
886
889
  const phrase = SAMPLE_PHRASES[Math.floor(Math.random() * SAMPLE_PHRASES.length)];
890
+ // Resolve play-tts from the actual project (CLAUDE_PROJECT_DIR / cwd),
891
+ // not the npm package root — hooks live in the user's project dir.
892
+ const hooksBase = process.env.CLAUDE_PROJECT_DIR || process.cwd();
887
893
  let proc;
888
894
  if (isWindows) {
889
- const playTts = path.join(projectRoot, '.claude', 'hooks-windows', 'play-tts.ps1');
895
+ const playTts = path.join(hooksBase, '.claude', 'hooks-windows', 'play-tts.ps1');
890
896
  proc = spawn('powershell', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', playTts, phrase, voiceId], {
891
897
  stdio: 'ignore', detached: false, windowsHide: true, env: _spawnEnv,
892
898
  });
893
899
  } else {
894
- const playTts = path.join(projectRoot, '.claude', 'hooks', 'play-tts.sh');
900
+ const playTts = path.join(hooksBase, '.claude', 'hooks', 'play-tts.sh');
895
901
  proc = spawn('bash', [playTts, phrase, voiceId], {
896
902
  stdio: 'ignore', detached: true, env: _spawnEnv,
897
903
  });
package/src/installer.js CHANGED
@@ -3771,18 +3771,31 @@ async function copyConfigFiles(targetDir, spinner) {
3771
3771
  const stat = await fs.stat(srcPath);
3772
3772
 
3773
3773
  if (stat.isFile()) {
3774
- // Don't overwrite existing config files (except audio-effects.cfg which is required)
3775
- try {
3776
- await fs.access(destPath);
3777
- if (file !== 'audio-effects.cfg') {
3778
- continue; // Skip if file exists and it's not audio-effects.cfg
3774
+ // For .sample files: copy as the real config name if it doesn't exist yet
3775
+ // e.g. audio-effects.cfg.sample → audio-effects.cfg (only if absent)
3776
+ let finalDest = destPath;
3777
+ let finalName = file;
3778
+ if (file.endsWith('.sample')) {
3779
+ finalName = file.replace(/\.sample$/, '');
3780
+ finalDest = path.join(destConfigDir, finalName);
3781
+ try {
3782
+ await fs.access(finalDest);
3783
+ continue; // Real config already exists, don't overwrite
3784
+ } catch {
3785
+ // Real config doesn't exist, install from sample
3786
+ }
3787
+ } else {
3788
+ // Non-sample files: skip if already exists
3789
+ try {
3790
+ await fs.access(destPath);
3791
+ continue;
3792
+ } catch {
3793
+ // File doesn't exist, proceed with copy
3779
3794
  }
3780
- } catch {
3781
- // File doesn't exist, proceed with copy
3782
3795
  }
3783
3796
 
3784
- await fs.copyFile(srcPath, destPath);
3785
- copiedFiles.push(file);
3797
+ await fs.copyFile(srcPath, finalDest);
3798
+ copiedFiles.push(finalName);
3786
3799
  }
3787
3800
  }
3788
3801
 
@@ -4297,239 +4310,92 @@ function isPathSafe(targetPath, basePath) {
4297
4310
  async function handleMcpConfiguration(targetDir, options) {
4298
4311
  const mcpConfigPath = path.join(targetDir, '.mcp.json');
4299
4312
 
4300
- // MCP server configuration for AgentVibes.
4313
+ // .mcp.json registers the AgentVibes MCP server for Claude Code, enabling
4314
+ // natural language control (text_to_speech, get_config, set_voice, etc.).
4301
4315
  //
4302
- // No `env.AGENTVIBES_LLM` block: GitHub Copilot CLI also reads project
4303
- // `.mcp.json` with precedence over its own `~/.copilot/mcp-config.json`,
4304
- // so setting `claude-code` here would mis-route Copilot CLI too.
4305
- // Instead, the MCP server auto-detects Claude Code via the `CLAUDECODE=1`
4306
- // env var that Claude Code sets on every subprocess it spawns.
4316
+ // AGENTVIBES_MCP_FALLBACK=copilot is the identity for non-Claude-Code tools
4317
+ // that read .mcp.json (primarily VS Code Copilot, which reads .mcp.json
4318
+ // with precedence over its own .vscode/mcp.json). Claude Code is
4319
+ // auto-detected via CLAUDECODE=1 which takes priority over the fallback.
4307
4320
  const mcpConfig = {
4308
4321
  mcpServers: {
4309
4322
  agentvibes: {
4310
4323
  command: 'npx',
4311
- args: ['-y', '--package=agentvibes', 'agentvibes-mcp-server']
4324
+ args: ['-y', '--package=agentvibes', 'agentvibes-mcp-server'],
4325
+ env: { AGENTVIBES_MCP_FALLBACK: 'copilot' }
4312
4326
  }
4313
4327
  }
4314
4328
  };
4315
4329
 
4316
- // Check if .mcp.json already exists
4317
4330
  let mcpExists = false;
4318
4331
  try {
4319
4332
  await fs.access(mcpConfigPath);
4320
4333
  mcpExists = true;
4321
- } catch {
4322
- // File doesn't exist
4323
- }
4334
+ } catch { /* doesn't exist */ }
4324
4335
 
4325
4336
  if (mcpExists) {
4326
- // Existing config: upgrade it in-place. Two jobs:
4327
- // 1. Ensure the agentvibes server entry exists.
4328
- // 2. STRIP any stale `env.AGENTVIBES_LLM` from earlier versions
4329
- // (v5.1.2..v5.1.4) — setting it in `.mcp.json` broke Copilot CLI
4330
- // routing because Copilot CLI also reads `.mcp.json` and would
4331
- // adopt claude-code's env. Claude Code is now auto-detected
4332
- // downstream via the CLAUDECODE=1 env var.
4333
- let migrated = false;
4334
- let migrationError = null;
4337
+ // Upgrade: ensure agentvibes entry exists with fallback env
4338
+ let parseFailed = false;
4335
4339
  try {
4336
- const existingRaw = await fs.readFile(mcpConfigPath, 'utf8');
4337
- const existingCfg = JSON.parse(existingRaw);
4338
- if (existingCfg && typeof existingCfg === 'object') {
4339
- if (!existingCfg.mcpServers || typeof existingCfg.mcpServers !== 'object') {
4340
- existingCfg.mcpServers = {};
4341
- }
4342
- const current = existingCfg.mcpServers.agentvibes;
4343
- const hasStaleEnv = current?.env?.AGENTVIBES_LLM !== undefined;
4344
- const needsWrite = !current || hasStaleEnv;
4345
- if (needsWrite) {
4346
- // Preserve any OTHER env keys the user added manually (rare) but
4347
- // drop AGENTVIBES_LLM so Copilot CLI doesn't mis-route.
4348
- const cleanEnv = { ...(current?.env ?? {}) };
4349
- delete cleanEnv.AGENTVIBES_LLM;
4350
- const newEntry = {
4351
- command: 'npx',
4352
- args: ['-y', '--package=agentvibes', 'agentvibes-mcp-server'],
4353
- };
4354
- if (Object.keys(cleanEnv).length > 0) {
4355
- newEntry.env = cleanEnv;
4356
- }
4357
- existingCfg.mcpServers.agentvibes = newEntry;
4358
- await fs.writeFile(mcpConfigPath, JSON.stringify(existingCfg, null, 2) + '\n');
4359
- migrated = true;
4360
- }
4340
+ const existing = JSON.parse(await fs.readFile(mcpConfigPath, 'utf8'));
4341
+ // Guard: non-object root (arrays/primitives are valid JSON but wrong shape)
4342
+ if (!existing || typeof existing !== 'object' || Array.isArray(existing)) {
4343
+ console.log(chalk.yellow(
4344
+ `⚠️ ${mcpConfigPath} has a non-object root — skipping MCP registration. Fix the file manually and re-run.`
4345
+ ));
4346
+ return;
4347
+ }
4348
+ // Guard: mcpServers must be a plain object
4349
+ if (!existing.mcpServers || typeof existing.mcpServers !== 'object' || Array.isArray(existing.mcpServers)) {
4350
+ existing.mcpServers = {};
4361
4351
  }
4352
+ const current = existing.mcpServers.agentvibes;
4353
+ // Strip AGENTVIBES_LLM if present (causes identity collisions)
4354
+ if (current?.env?.AGENTVIBES_LLM) {
4355
+ delete current.env.AGENTVIBES_LLM;
4356
+ }
4357
+ // Ensure fallback is set
4358
+ const mergedEnv = { ...(current?.env ?? {}), AGENTVIBES_MCP_FALLBACK: 'copilot' };
4359
+ existing.mcpServers.agentvibes = {
4360
+ command: 'npx',
4361
+ args: ['-y', '--package=agentvibes', 'agentvibes-mcp-server'],
4362
+ env: mergedEnv,
4363
+ };
4364
+ await fs.writeFile(mcpConfigPath, JSON.stringify(existing, null, 2) + '\n');
4362
4365
  } catch (err) {
4363
- migrationError = err;
4364
- }
4365
-
4366
- if (migrated) {
4367
- console.log(
4368
- boxen(
4369
- chalk.green.bold('✅ MCP Configuration Updated\n\n') +
4370
- chalk.white('Your existing ') + chalk.cyan('.mcp.json') + chalk.white(' has been updated.\n') +
4371
- chalk.white('Claude Code is auto-detected via ') + chalk.cyan('CLAUDECODE=1') + chalk.white(' at runtime.'),
4372
- {
4373
- padding: 1,
4374
- margin: { top: 1, bottom: 1, left: 0, right: 0 },
4375
- borderStyle: 'double',
4376
- borderColor: 'green',
4377
- }
4378
- )
4379
- );
4380
- return;
4366
+ parseFailed = true;
4367
+ console.log(chalk.yellow(
4368
+ `⚠️ Could not update ${mcpConfigPath}: ${err.message}\n` +
4369
+ ` AgentVibes MCP server was NOT registered. Fix the file manually and re-run.`
4370
+ ));
4381
4371
  }
4382
-
4383
- // Migration was not needed (already correct) or failed — fall through
4384
- // to the manual-instructions box.
4385
- console.log(
4386
- boxen(
4387
- chalk.yellow.bold('ℹ️ MCP Configuration Already Exists\n\n') +
4388
- chalk.white('An ') + chalk.cyan('.mcp.json') + chalk.white(' file already exists in this project.\n\n') +
4389
- (migrationError
4390
- ? chalk.red('Could not auto-update it: ' + migrationError.message + '\n\n')
4391
- : chalk.gray('It already has the correct AgentVibes entry.\n\n')) +
4392
- chalk.white('To add or fix the AgentVibes MCP server manually, use:'),
4393
- {
4394
- padding: 1,
4395
- margin: { top: 1, bottom: 1, left: 0, right: 0 },
4396
- borderStyle: 'round',
4397
- borderColor: migrationError ? 'red' : 'yellow',
4398
- }
4399
- )
4400
- );
4401
-
4402
- // Display the snippet to add
4403
- console.log(
4404
- '\n"agentvibes": {\n' +
4405
- ' "command": "npx",\n' +
4406
- ' "args": ["-y", "--package=agentvibes", "agentvibes-mcp-server"]\n' +
4407
- '}\n'
4408
- );
4409
-
4410
- console.log(
4411
- boxen(
4412
- chalk.cyan('To use with Claude Code:\n') +
4413
- chalk.white(' claude --mcp-config .mcp.json\n\n') +
4414
- chalk.cyan('📖 Full Guide:\n') +
4415
- chalk.cyan.bold('https://github.com/paulpreibisch/AgentVibes#mcp-server'),
4416
- {
4417
- padding: 1,
4418
- margin: { top: 1, bottom: 1, left: 0, right: 0 },
4419
- borderStyle: 'round',
4420
- borderColor: 'cyan',
4421
- }
4422
- )
4423
- );
4372
+ if (!parseFailed) return;
4424
4373
  return;
4425
4374
  }
4426
4375
 
4427
- // Scenario 1 & 2: Config doesn't exist - offer to create
4428
- console.log(
4429
- boxen(
4430
- chalk.cyan.bold('🎙️ MCP Server Configuration\n\n') +
4431
- chalk.white.bold('AgentVibes MCP Server - Control TTS with Natural Language!\n\n') +
4432
- chalk.gray('Use natural language instead of slash commands:\n') +
4433
- chalk.gray(' "Switch to Aria voice" instead of /agent-vibes:switch "Aria"\n') +
4434
- chalk.gray(' "Set personality to sarcastic" instead of /agent-vibes:personality sarcastic\n\n') +
4435
- chalk.white('No ') + chalk.cyan('.mcp.json') + chalk.white(' found in this project.'),
4436
- {
4437
- padding: 1,
4438
- margin: { top: 1, bottom: 1, left: 0, right: 0 },
4439
- borderStyle: 'round',
4440
- borderColor: 'cyan',
4441
- }
4442
- )
4443
- );
4444
-
4445
- let createConfig = options.yes; // Auto-create if --yes flag
4446
-
4376
+ // New install create .mcp.json
4447
4377
  if (!options.yes) {
4448
- const { confirmCreate } = await inquirer.prompt([
4449
- {
4450
- type: 'confirm',
4451
- name: 'confirmCreate',
4452
- message: chalk.cyan('Would you like to create .mcp.json for this project?'),
4453
- default: true,
4454
- },
4455
- ]);
4456
- createConfig = confirmCreate;
4457
- }
4458
-
4459
- if (createConfig) {
4460
- // Scenario 1: User says YES - create the config
4461
- try {
4462
- await fs.writeFile(mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + '\n');
4463
-
4464
- console.log(
4465
- boxen(
4466
- chalk.green.bold('✅ MCP Configuration Created!\n\n') +
4467
- chalk.white('Your ') + chalk.cyan('.mcp.json') + chalk.white(' has been created in this project.\n\n') +
4468
- chalk.white('To use AgentVibes MCP server with Claude, run:\n') +
4469
- chalk.cyan.bold(' claude --mcp-config .mcp.json\n\n') +
4470
- chalk.green('The MCP server is now installed and ready to use!'),
4471
- {
4472
- padding: 1,
4473
- margin: { top: 1, bottom: 1, left: 0, right: 0 },
4474
- borderStyle: 'double',
4475
- borderColor: 'green',
4476
- }
4477
- )
4478
- );
4479
-
4480
- // Show the installed JSON so users can see exactly what was written
4481
- console.log(chalk.gray(JSON.stringify(mcpConfig, null, 2)) + '\n');
4482
- } catch (error) {
4483
- console.log(chalk.red(`\n✗ Failed to create .mcp.json: ${error.message}`));
4484
- console.log(chalk.gray(' You can create it manually with the config shown below.\n'));
4485
- // Fall through to show manual instructions
4486
- createConfig = false;
4487
- }
4378
+ const { confirmCreate } = await inquirer.prompt([{
4379
+ type: 'confirm',
4380
+ name: 'confirmCreate',
4381
+ message: chalk.cyan('Create .mcp.json for AgentVibes MCP server? (enables natural language voice control)'),
4382
+ default: true,
4383
+ }]);
4384
+ if (!confirmCreate) return;
4488
4385
  }
4489
4386
 
4490
- if (!createConfig) {
4491
- // Scenario 2: User says NO - show manual instructions
4492
- console.log(
4493
- boxen(
4494
- chalk.cyan.bold('📋 Manual MCP Configuration\n\n') +
4495
- chalk.white('Create a ') + chalk.cyan('.mcp.json') + chalk.white(' file in your project with:'),
4496
- {
4497
- padding: 1,
4498
- margin: { top: 1, bottom: 1, left: 0, right: 0 },
4499
- borderStyle: 'round',
4500
- borderColor: 'cyan',
4501
- }
4502
- )
4503
- );
4504
-
4505
- // Display JSON config
4506
- console.log(
4507
- '\n{\n' +
4508
- ' "mcpServers": {\n' +
4509
- ' "agentvibes": {\n' +
4510
- ' "command": "npx",\n' +
4511
- ' "args": ["-y", "--package=agentvibes", "agentvibes-mcp-server"]\n' +
4512
- ' }\n' +
4513
- ' }\n' +
4514
- '}\n'
4515
- );
4516
-
4387
+ try {
4388
+ await fs.writeFile(mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + '\n');
4517
4389
  console.log(
4518
4390
  boxen(
4519
- chalk.cyan('To use with Claude Code:\n') +
4520
- chalk.white(' claude --mcp-config .mcp.json\n\n') +
4521
- chalk.cyan('📱 Claude Desktop / Warp Terminal:\n') +
4522
- chalk.white(' npx agentvibes setup-mcp-for-claude-desktop\n\n') +
4523
- chalk.cyan('📖 Full Guide:\n') +
4524
- chalk.cyan.bold('https://github.com/paulpreibisch/AgentVibes#mcp-server'),
4525
- {
4526
- padding: 1,
4527
- margin: { top: 1, bottom: 1, left: 0, right: 0 },
4528
- borderStyle: 'round',
4529
- borderColor: 'cyan',
4530
- }
4391
+ chalk.green.bold(' MCP Configuration Created!\n\n') +
4392
+ chalk.white('AgentVibes MCP server registered in ') + chalk.cyan('.mcp.json') + chalk.white('.\n') +
4393
+ chalk.green('Natural language voice control is ready!'),
4394
+ { padding: 1, margin: { top: 1, bottom: 1 }, borderStyle: 'double', borderColor: 'green' }
4531
4395
  )
4532
4396
  );
4397
+ } catch (err) {
4398
+ console.log(chalk.red(`\n✗ Failed to create .mcp.json: ${err.message}`));
4533
4399
  }
4534
4400
  }
4535
4401