agentvibes 5.2.0 → 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.
Files changed (49) 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-termux-ssh.sh +169 -169
  28. package/.claude/hooks/play-tts.sh +14 -5
  29. package/.claude/hooks/prepare-release.sh +54 -54
  30. package/.claude/hooks/provider-commands.sh +617 -617
  31. package/.claude/hooks/provider-manager.sh +399 -399
  32. package/.claude/hooks/replay-target-audio.sh +95 -95
  33. package/.claude/hooks/sentiment-manager.sh +201 -201
  34. package/.claude/hooks/speed-manager.sh +291 -291
  35. package/.claude/hooks/stop-tts.sh +84 -84
  36. package/.claude/hooks/termux-installer.sh +261 -261
  37. package/.claude/hooks/translate-manager.sh +341 -341
  38. package/.claude/hooks/tts-queue-worker.sh +145 -145
  39. package/.claude/hooks/tts-queue.sh +165 -165
  40. package/.claude/hooks/voice-manager.sh +552 -548
  41. package/.claude/hooks-windows/play-tts.ps1 +2 -2
  42. package/README.md +11 -2
  43. package/RELEASE_NOTES.md +38 -0
  44. package/bin/mcp-server.sh +206 -206
  45. package/mcp-server/server.py +35 -6
  46. package/package.json +1 -1
  47. package/src/console/tabs/setup-tab.js +59 -23
  48. package/src/installer.js +79 -213
  49. package/src/services/llm-provider-service.js +126 -75
@@ -125,53 +125,78 @@ export async function checkCodexInstalled(targetDir) {
125
125
  // ── Claude Code install ────────────────────────────────────────────────────
126
126
 
127
127
  /**
128
- * Create .mcp.json in target directory if it doesn't exist.
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
- // The agentvibes server entry for Claude Code's .mcp.json.
135
- //
136
- // IMPORTANT: no `env.AGENTVIBES_LLM` block here. GitHub Copilot CLI
137
- // also reads project-level `.mcp.json` with precedence over its own
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
- const mcpConfig = {
150
- mcpServers: {
151
- agentvibes: agentvibesServer,
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 mcpCreated = false;
156
+ let existing = null;
157
+ let parseError = null;
157
158
  try {
158
- await fs.access(mcpConfigPath);
159
- // Already exists — merge / upgrade the agentvibes entry. This also
160
- // STRIPS any stale AGENTVIBES_LLM env block left over from v5.1.2..4
161
- // so Copilot CLI stops mis-routing.
162
- try {
163
- const existing = JSON.parse(await fs.readFile(mcpConfigPath, 'utf8'));
164
- existing.mcpServers = existing.mcpServers || {};
165
- existing.mcpServers.agentvibes = { ...agentvibesServer };
166
- await fs.writeFile(mcpConfigPath, JSON.stringify(existing, null, 2) + '\n');
167
- mcpCreated = true;
168
- } catch { /* parse error — don't corrupt */ }
169
- } catch {
170
- // File doesn't exist create it
171
- await fs.writeFile(mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + '\n');
172
- mcpCreated = true;
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
- const mcpConfigPath = path.join(targetDir, '.mcp.json');
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 MCP entry
245
+ // 1. Remove legacy .mcp.json agentvibes entry if present
221
246
  await removeClaudeMcp(targetDir);
222
- removed.push('.mcp.json (agentvibes entry)');
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
- const copilotHome = process.env.COPILOT_HOME ||
329
- path.join(process.env.USERPROFILE || process.env.HOME || '', '.copilot');
330
- const copilotMcpPath = path.join(copilotHome, 'mcp-config.json');
331
- await fs.mkdir(copilotHome, { recursive: true });
332
- let cliConfig = { mcpServers: {} };
333
- try {
334
- const existingCli = await fs.readFile(copilotMcpPath, 'utf8');
335
- const parsedCli = JSON.parse(existingCli);
336
- if (parsedCli && typeof parsedCli === 'object') {
337
- cliConfig = parsedCli;
338
- if (!cliConfig.mcpServers) cliConfig.mcpServers = {};
339
- }
340
- } catch { /* new file */ }
341
- cliConfig.mcpServers.agentvibes = {
342
- type: 'local',
343
- command: 'npx',
344
- args: ['-y', '--package=agentvibes', 'agentvibes-mcp-server'],
345
- env: { AGENTVIBES_LLM: 'copilot' },
346
- tools: ['*'],
347
- };
348
- await fs.writeFile(copilotMcpPath, JSON.stringify(cliConfig, null, 2) + '\n');
349
- } catch { /* best effort — CLI might not be installed */ }
350
-
351
- ensureDefaultLlmConfigSync('copilot', targetDir);
352
- return { success: true };
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
- return { success: false, error: err.message };
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
- if (parsed?.servers?.agentvibes) {
364
- delete parsed.servers.agentvibes;
365
- if (Object.keys(parsed.servers).length === 0) {
366
- await fs.unlink(mcpJsonPath);
367
- } else {
368
- await fs.writeFile(mcpJsonPath, JSON.stringify(parsed, null, 2) + '\n');
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
- return { success: false, error: err.message };
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) {