agentvibes 5.1.4 → 5.2.1
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/.agentvibes/config.json +23 -13
- package/.claude/commands/agent-vibes/verbosity.md +98 -89
- package/.claude/config/audio-effects.cfg +4 -1
- package/.claude/hooks/audio-cache-utils.sh +246 -246
- package/.claude/hooks/background-music-manager.sh +404 -404
- package/.claude/hooks/bmad-speak-enhanced.sh +165 -165
- package/.claude/hooks/bmad-speak.sh +290 -290
- package/.claude/hooks/bmad-tts-injector.sh +568 -568
- package/.claude/hooks/bmad-voice-manager.sh +928 -928
- package/.claude/hooks/clawdbot-receiver-SECURE.sh +129 -129
- package/.claude/hooks/clawdbot-receiver.sh +107 -107
- package/.claude/hooks/clean-audio-cache.sh +22 -22
- package/.claude/hooks/cleanup-cache.sh +106 -106
- package/.claude/hooks/configure-rdp-mode.sh +137 -137
- package/.claude/hooks/download-extra-voices.sh +244 -244
- package/.claude/hooks/effects-manager.sh +268 -268
- package/.claude/hooks/github-star-reminder.sh +154 -154
- package/.claude/hooks/language-manager.sh +362 -362
- package/.claude/hooks/learn-manager.sh +492 -492
- package/.claude/hooks/macos-voice-manager.sh +205 -205
- package/.claude/hooks/migrate-background-music.sh +125 -125
- package/.claude/hooks/migrate-to-agentvibes.sh +161 -161
- package/.claude/hooks/optimize-background-music.sh +87 -87
- package/.claude/hooks/path-resolver.sh +60 -60
- package/.claude/hooks/personality-manager.sh +448 -448
- package/.claude/hooks/piper-download-voices.sh +233 -225
- package/.claude/hooks/piper-installer.sh +292 -292
- package/.claude/hooks/piper-multispeaker-registry.sh +171 -171
- package/.claude/hooks/piper-voice-manager.sh +125 -0
- package/.claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh +97 -90
- package/.claude/hooks/play-tts-enhanced.sh +105 -105
- package/.claude/hooks/play-tts-piper.sh +16 -5
- package/.claude/hooks/play-tts-ssh-remote.sh +168 -167
- package/.claude/hooks/play-tts-termux-ssh.sh +169 -169
- package/.claude/hooks/play-tts.sh +35 -14
- package/.claude/hooks/prepare-release.sh +54 -54
- package/.claude/hooks/provider-commands.sh +617 -617
- package/.claude/hooks/provider-manager.sh +399 -399
- package/.claude/hooks/replay-target-audio.sh +95 -95
- package/.claude/hooks/sentiment-manager.sh +201 -201
- package/.claude/hooks/session-start-tts.sh +4 -1
- package/.claude/hooks/speed-manager.sh +291 -291
- package/.claude/hooks/stop-tts.sh +84 -84
- package/.claude/hooks/termux-installer.sh +261 -261
- package/.claude/hooks/translate-manager.sh +341 -341
- package/.claude/hooks/tts-queue-worker.sh +145 -145
- package/.claude/hooks/tts-queue.sh +165 -165
- package/.claude/hooks/verbosity-manager.sh +185 -178
- package/.claude/hooks/voice-manager.sh +552 -548
- package/.claude/hooks-windows/download-extra-voices.ps1 +243 -185
- package/.claude/hooks-windows/play-tts-piper.ps1 +7 -2
- package/.claude/hooks-windows/play-tts.ps1 +9 -3
- package/.claude/hooks-windows/session-start-tts.ps1 +2 -1
- package/.claude/hooks-windows/verbosity-manager.ps1 +126 -119
- package/README.md +19 -2
- package/RELEASE_NOTES.md +74 -0
- package/bin/agentvibes-voice-browser.js +1939 -1840
- package/bin/mcp-server.sh +206 -206
- package/mcp-server/server.py +87 -15
- package/package.json +1 -1
- package/src/console/tabs/receiver-tab.js +1527 -1483
- package/src/console/tabs/settings-tab.js +2 -2
- package/src/console/tabs/setup-tab.js +112 -31
- package/src/console/tabs/voices-tab.js +130 -13
- package/src/i18n/en.js +202 -202
- package/src/installer.js +79 -213
- package/src/services/llm-provider-service.js +126 -75
- package/src/services/verbosity-service.js +159 -157
- package/templates/agentvibes-receiver.sh +3 -2
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
|
-
//
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
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,
|
|
3785
|
-
copiedFiles.push(
|
|
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
|
|
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
|
-
//
|
|
4303
|
-
//
|
|
4304
|
-
//
|
|
4305
|
-
//
|
|
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
|
-
//
|
|
4327
|
-
|
|
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
|
|
4337
|
-
|
|
4338
|
-
if (
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
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
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
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
|
-
|
|
4491
|
-
|
|
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.
|
|
4520
|
-
chalk.white('
|
|
4521
|
-
chalk.
|
|
4522
|
-
|
|
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
|
|
|
@@ -125,53 +125,78 @@ export async function checkCodexInstalled(targetDir) {
|
|
|
125
125
|
// ── Claude Code install ────────────────────────────────────────────────────
|
|
126
126
|
|
|
127
127
|
/**
|
|
128
|
-
*
|
|
128
|
+
* Install AgentVibes for Claude Code.
|
|
129
|
+
*
|
|
130
|
+
* Writes .mcp.json to register the AgentVibes MCP server (enables natural
|
|
131
|
+
* language control: text_to_speech, get_config, set_voice, etc.).
|
|
132
|
+
*
|
|
133
|
+
* .mcp.json does NOT set AGENTVIBES_LLM because Copilot also reads it.
|
|
134
|
+
* Claude Code is auto-detected via CLAUDECODE=1 env var at runtime.
|
|
135
|
+
*
|
|
129
136
|
* Also copies hooks, commands, config, personality, plugin, and bmad config files.
|
|
130
137
|
*/
|
|
131
138
|
export async function installClaudeMcp(targetDir) {
|
|
132
139
|
const mcpConfigPath = path.join(targetDir, '.mcp.json');
|
|
133
140
|
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
// `~/.copilot/mcp-config.json` — so if we set `AGENTVIBES_LLM=claude-code`
|
|
139
|
-
// in `.mcp.json`, Copilot CLI picks up that value too and mis-routes.
|
|
140
|
-
// Instead, the MCP server (mcp-server/server.py) auto-detects Claude
|
|
141
|
-
// Code via the `CLAUDECODE=1` env var that Claude Code sets on every
|
|
142
|
-
// subprocess it spawns. Copilot CLI does NOT set that var, so its
|
|
143
|
-
// spawned MCP server correctly falls back to its own config.
|
|
141
|
+
// AGENTVIBES_MCP_FALLBACK=copilot is the fallback identity for non-Claude-Code
|
|
142
|
+
// tools reading .mcp.json (primarily VS Code Copilot, which reads .mcp.json
|
|
143
|
+
// with precedence over .vscode/mcp.json). Claude Code auto-detects via
|
|
144
|
+
// CLAUDECODE=1 which takes priority over the fallback in server.py.
|
|
144
145
|
const agentvibesServer = {
|
|
145
146
|
command: 'npx',
|
|
146
147
|
args: ['-y', '--package=agentvibes', 'agentvibes-mcp-server'],
|
|
148
|
+
env: { AGENTVIBES_MCP_FALLBACK: 'copilot' },
|
|
147
149
|
};
|
|
148
150
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
};
|
|
154
|
-
|
|
151
|
+
// MCP config and file copies are independent — report partial success
|
|
152
|
+
// when one succeeds but the other fails.
|
|
153
|
+
let mcpCreated = false;
|
|
154
|
+
let mcpError = null;
|
|
155
155
|
try {
|
|
156
|
-
let
|
|
156
|
+
let existing = null;
|
|
157
|
+
let parseError = null;
|
|
157
158
|
try {
|
|
158
|
-
await fs.
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
//
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
159
|
+
const raw = await fs.readFile(mcpConfigPath, 'utf8');
|
|
160
|
+
existing = JSON.parse(raw);
|
|
161
|
+
} catch (err) {
|
|
162
|
+
// ENOENT = new file (fine); anything else = malformed (report to caller)
|
|
163
|
+
if (err.code !== 'ENOENT') parseError = err;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (parseError) {
|
|
167
|
+
throw new Error(`Existing ${mcpConfigPath} is malformed: ${parseError.message}`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Guard: non-object root
|
|
171
|
+
if (existing && (typeof existing !== 'object' || Array.isArray(existing))) {
|
|
172
|
+
throw new Error(`${mcpConfigPath} has a non-object root — please fix manually.`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (existing) {
|
|
176
|
+
// Guard: mcpServers must be a plain object
|
|
177
|
+
if (!existing.mcpServers || typeof existing.mcpServers !== 'object' || Array.isArray(existing.mcpServers)) {
|
|
178
|
+
existing.mcpServers = {};
|
|
179
|
+
}
|
|
180
|
+
const current = existing.mcpServers.agentvibes;
|
|
181
|
+
// Strip any stale AGENTVIBES_LLM (from older versions — causes collisions)
|
|
182
|
+
if (current?.env?.AGENTVIBES_LLM) delete current.env.AGENTVIBES_LLM;
|
|
183
|
+
// Preserve user's other env keys, ensure AGENTVIBES_MCP_FALLBACK is set
|
|
184
|
+
const mergedEnv = { ...(current?.env ?? {}), AGENTVIBES_MCP_FALLBACK: 'copilot' };
|
|
185
|
+
existing.mcpServers.agentvibes = {
|
|
186
|
+
command: 'npx',
|
|
187
|
+
args: ['-y', '--package=agentvibes', 'agentvibes-mcp-server'],
|
|
188
|
+
env: mergedEnv,
|
|
189
|
+
};
|
|
190
|
+
await fs.writeFile(mcpConfigPath, JSON.stringify(existing, null, 2) + '\n');
|
|
191
|
+
} else {
|
|
192
|
+
await fs.writeFile(mcpConfigPath, JSON.stringify({ mcpServers: { agentvibes: agentvibesServer } }, null, 2) + '\n');
|
|
173
193
|
}
|
|
194
|
+
mcpCreated = true;
|
|
195
|
+
} catch (err) {
|
|
196
|
+
mcpError = err.message;
|
|
197
|
+
}
|
|
174
198
|
|
|
199
|
+
try {
|
|
175
200
|
// Copy hooks, commands, config, personality, plugin, bmad config files
|
|
176
201
|
const silentSpinner = { start: () => {}, succeed: () => {}, fail: () => {} };
|
|
177
202
|
const installer = await import('../installer.js');
|
|
@@ -184,20 +209,20 @@ export async function installClaudeMcp(targetDir) {
|
|
|
184
209
|
await installer.copyBackgroundMusicFiles(targetDir, silentSpinner);
|
|
185
210
|
ensureDefaultLlmConfigSync('claude-code', targetDir);
|
|
186
211
|
|
|
187
|
-
return { success: true, mcpCreated };
|
|
212
|
+
return { success: true, mcpCreated, mcpError };
|
|
188
213
|
} catch (err) {
|
|
189
|
-
return { success: false, error: err.message };
|
|
214
|
+
return { success: false, error: err.message, mcpError };
|
|
190
215
|
}
|
|
191
216
|
}
|
|
192
217
|
|
|
193
218
|
export async function removeClaudeMcp(targetDir) {
|
|
194
|
-
|
|
219
|
+
// Clean up .mcp.json agentvibes entry (legacy from older versions)
|
|
195
220
|
try {
|
|
221
|
+
const mcpConfigPath = path.join(targetDir, '.mcp.json');
|
|
196
222
|
const content = await fs.readFile(mcpConfigPath, 'utf8');
|
|
197
223
|
const parsed = JSON.parse(content);
|
|
198
224
|
if (parsed.mcpServers?.agentvibes) {
|
|
199
225
|
delete parsed.mcpServers.agentvibes;
|
|
200
|
-
// Only delete file if mcpServers is empty AND no other top-level keys
|
|
201
226
|
const noServers = Object.keys(parsed.mcpServers).length === 0;
|
|
202
227
|
const noOtherKeys = Object.keys(parsed).length === 1;
|
|
203
228
|
if (noServers && noOtherKeys) {
|
|
@@ -217,9 +242,9 @@ export async function removeClaudeMcp(targetDir) {
|
|
|
217
242
|
export async function uninstallClaude(targetDir) {
|
|
218
243
|
const removed = [];
|
|
219
244
|
|
|
220
|
-
// 1. Remove
|
|
245
|
+
// 1. Remove legacy .mcp.json agentvibes entry if present
|
|
221
246
|
await removeClaudeMcp(targetDir);
|
|
222
|
-
removed.push('.mcp.json
|
|
247
|
+
removed.push('.mcp.json agentvibes entry (if present)');
|
|
223
248
|
|
|
224
249
|
// 2. Remove AgentVibes directories
|
|
225
250
|
const dirs = [
|
|
@@ -304,6 +329,7 @@ export async function installCopilotMcp(targetDir) {
|
|
|
304
329
|
env: { AGENTVIBES_LLM: 'copilot' },
|
|
305
330
|
};
|
|
306
331
|
|
|
332
|
+
let mcpError = null;
|
|
307
333
|
try {
|
|
308
334
|
await fs.mkdir(vscodeDir, { recursive: true });
|
|
309
335
|
let mcpConfig = { servers: {} };
|
|
@@ -316,6 +342,8 @@ export async function installCopilotMcp(targetDir) {
|
|
|
316
342
|
}
|
|
317
343
|
} catch { /* new file */ }
|
|
318
344
|
|
|
345
|
+
// Clean up any old "agentvibes-copilot" entry from a prior attempt.
|
|
346
|
+
delete mcpConfig.servers['agentvibes-copilot'];
|
|
319
347
|
mcpConfig.servers.agentvibes = agentvibesServer;
|
|
320
348
|
await fs.writeFile(mcpJsonPath, JSON.stringify(mcpConfig, null, 2) + '\n');
|
|
321
349
|
|
|
@@ -325,34 +353,45 @@ export async function installCopilotMcp(targetDir) {
|
|
|
325
353
|
// CLI reads ONLY from ~/.copilot/mcp-config.json per docs:
|
|
326
354
|
// https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/add-mcp-servers
|
|
327
355
|
try {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
356
|
+
// If neither USERPROFILE nor HOME is set, skip — writing to a
|
|
357
|
+
// relative `.copilot/` path would pollute the project dir.
|
|
358
|
+
const home = process.env.COPILOT_HOME ||
|
|
359
|
+
process.env.USERPROFILE || process.env.HOME;
|
|
360
|
+
if (home) {
|
|
361
|
+
const copilotHome = process.env.COPILOT_HOME || path.join(home, '.copilot');
|
|
362
|
+
const copilotMcpPath = path.join(copilotHome, 'mcp-config.json');
|
|
363
|
+
await fs.mkdir(copilotHome, { recursive: true });
|
|
364
|
+
let cliConfig = { mcpServers: {} };
|
|
365
|
+
try {
|
|
366
|
+
const existingCli = await fs.readFile(copilotMcpPath, 'utf8');
|
|
367
|
+
const parsedCli = JSON.parse(existingCli);
|
|
368
|
+
if (parsedCli && typeof parsedCli === 'object' && !Array.isArray(parsedCli)) {
|
|
369
|
+
cliConfig = parsedCli;
|
|
370
|
+
if (!cliConfig.mcpServers || typeof cliConfig.mcpServers !== 'object' || Array.isArray(cliConfig.mcpServers)) {
|
|
371
|
+
cliConfig.mcpServers = {};
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
} catch { /* new file or malformed — start fresh */ }
|
|
375
|
+
cliConfig.mcpServers.agentvibes = {
|
|
376
|
+
type: 'local',
|
|
377
|
+
command: 'npx',
|
|
378
|
+
args: ['-y', '--package=agentvibes', 'agentvibes-mcp-server'],
|
|
379
|
+
env: { AGENTVIBES_LLM: 'copilot' },
|
|
380
|
+
tools: ['*'],
|
|
381
|
+
};
|
|
382
|
+
await fs.writeFile(copilotMcpPath, JSON.stringify(cliConfig, null, 2) + '\n');
|
|
383
|
+
}
|
|
384
|
+
} catch (err) {
|
|
385
|
+
// Best effort — CLI might not be installed. Log to stderr so users
|
|
386
|
+
// with COPILOT_HOME set but write failures (EACCES) can diagnose.
|
|
387
|
+
console.error(`[agentvibes] Warning: could not write ~/.copilot/mcp-config.json: ${err.message}`);
|
|
388
|
+
}
|
|
353
389
|
} catch (err) {
|
|
354
|
-
|
|
390
|
+
mcpError = err.message;
|
|
355
391
|
}
|
|
392
|
+
|
|
393
|
+
ensureDefaultLlmConfigSync('copilot', targetDir);
|
|
394
|
+
return { success: true, mcpError };
|
|
356
395
|
}
|
|
357
396
|
|
|
358
397
|
export async function removeCopilotMcp(targetDir) {
|
|
@@ -360,13 +399,23 @@ export async function removeCopilotMcp(targetDir) {
|
|
|
360
399
|
try {
|
|
361
400
|
const content = await fs.readFile(mcpJsonPath, 'utf8');
|
|
362
401
|
const parsed = JSON.parse(content);
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
}
|
|
402
|
+
// Guard against non-object root or non-object servers (malformed config)
|
|
403
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
404
|
+
return { success: true };
|
|
405
|
+
}
|
|
406
|
+
const servers = parsed.servers;
|
|
407
|
+
if (!servers || typeof servers !== 'object' || Array.isArray(servers)) {
|
|
408
|
+
return { success: true };
|
|
409
|
+
}
|
|
410
|
+
// Remove both old ("agentvibes") and new ("agentvibes-copilot") names
|
|
411
|
+
delete servers.agentvibes;
|
|
412
|
+
delete servers['agentvibes-copilot'];
|
|
413
|
+
const noServers = Object.keys(servers).length === 0;
|
|
414
|
+
const noOtherKeys = Object.keys(parsed).length === 1; // only "servers"
|
|
415
|
+
if (noServers && noOtherKeys) {
|
|
416
|
+
await fs.unlink(mcpJsonPath);
|
|
417
|
+
} else {
|
|
418
|
+
await fs.writeFile(mcpJsonPath, JSON.stringify(parsed, null, 2) + '\n');
|
|
370
419
|
}
|
|
371
420
|
return { success: true };
|
|
372
421
|
} catch {
|
|
@@ -396,17 +445,19 @@ export async function installCodexMcp(targetDir) {
|
|
|
396
445
|
const codexDir = path.join(targetDir, '.codex');
|
|
397
446
|
const tomlPath = path.join(codexDir, 'config.toml');
|
|
398
447
|
|
|
448
|
+
let mcpError = null;
|
|
399
449
|
try {
|
|
400
450
|
await fs.mkdir(codexDir, { recursive: true });
|
|
401
451
|
let existing = '';
|
|
402
452
|
try { existing = await fs.readFile(tomlPath, 'utf8'); } catch { /* new file */ }
|
|
403
453
|
const content = buildCodexToml(existing);
|
|
404
454
|
await fs.writeFile(tomlPath, content);
|
|
405
|
-
ensureDefaultLlmConfigSync('codex', targetDir);
|
|
406
|
-
return { success: true };
|
|
407
455
|
} catch (err) {
|
|
408
|
-
|
|
456
|
+
mcpError = err.message;
|
|
409
457
|
}
|
|
458
|
+
|
|
459
|
+
ensureDefaultLlmConfigSync('codex', targetDir);
|
|
460
|
+
return { success: true, mcpError };
|
|
410
461
|
}
|
|
411
462
|
|
|
412
463
|
export async function removeCodexMcp(targetDir) {
|