agentvibes 5.12.0-alpha.0 → 5.13.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 (129) hide show
  1. package/.agentvibes/config.json +15 -1
  2. package/.agentvibes/install-manifest.json +342 -0
  3. package/.claude/activation-instructions +54 -54
  4. package/.claude/commands/agent-vibes/commands.json +0 -20
  5. package/.claude/commands/agent-vibes/unmute.md +6 -2
  6. package/.claude/commands/agent-vibes-bmad-voices.md +117 -117
  7. package/.claude/commands/agent-vibes-rdp.md +24 -24
  8. package/.claude/config/audio-effects.cfg +6 -6
  9. package/.claude/config/audio-effects.cfg.bak-kokoro +7 -0
  10. package/.claude/config/background-music-enabled.txt +1 -1
  11. package/.claude/config/background-music-position.txt +27 -0
  12. package/.claude/config/background-music-volume.txt +1 -0
  13. package/.claude/config/background-music.cfg +1 -0
  14. package/.claude/config/background-music.txt +1 -0
  15. package/.claude/config/language.txt +1 -0
  16. package/.claude/config/personality.txt +1 -0
  17. package/.claude/config/tts-speech-rate.txt +1 -0
  18. package/.claude/config/tts-verbosity.txt +1 -0
  19. package/.claude/docs/TERMUX_SETUP.md +408 -408
  20. package/.claude/github-star-reminder.txt +1 -1
  21. package/.claude/hooks/agentvibes-session-id.sh +69 -0
  22. package/.claude/hooks/bmad-party-speak.sh +20 -4
  23. package/.claude/hooks/bmad-speak.sh +60 -2
  24. package/.claude/hooks/bmad-tts-injector.sh +20 -1
  25. package/.claude/hooks/bmad-voice-manager.sh +25 -3
  26. package/.claude/hooks/clawdbot-receiver-SECURE.sh +21 -2
  27. package/.claude/hooks/clawdbot-receiver.sh +19 -1
  28. package/.claude/hooks/elevenlabs-voices.sh +62 -0
  29. package/.claude/hooks/kokoro-installer.sh +20 -10
  30. package/.claude/hooks/language-manager.sh +10 -3
  31. package/.claude/hooks/party-set-room.sh +71 -0
  32. package/.claude/hooks/party-stage-roster.py +328 -0
  33. package/.claude/hooks/personality-manager.sh +19 -2
  34. package/.claude/hooks/piper-voice-manager.sh +3 -2
  35. package/.claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh +24 -5
  36. package/.claude/hooks/play-tts-elevenlabs.sh +38 -118
  37. package/.claude/hooks/play-tts-kokoro.sh +33 -6
  38. package/.claude/hooks/play-tts-soprano.sh +3 -2
  39. package/.claude/hooks/play-tts-ssh-remote.sh +72 -27
  40. package/.claude/hooks/play-tts-termux-ssh.sh +5 -4
  41. package/.claude/hooks/play-tts.sh +86 -69
  42. package/.claude/hooks/provider-catalog.json +352 -0
  43. package/.claude/hooks/provider-catalog.sh +161 -0
  44. package/.claude/hooks/provider-commands.sh +2 -1
  45. package/.claude/hooks/provider-manager.sh +47 -9
  46. package/.claude/hooks/python-resolver.sh +117 -0
  47. package/.claude/hooks/session-id.sh +56 -0
  48. package/.claude/hooks/session-start-tts.sh +39 -0
  49. package/.claude/hooks/speed-manager.sh +1 -1
  50. package/.claude/hooks/translate-manager.sh +3 -2
  51. package/.claude/hooks/translator.py +1 -1
  52. package/.claude/hooks/voice-manager.sh +242 -10
  53. package/.claude/hooks-windows/audio-cache-utils.ps1.user.bak +119 -0
  54. package/.claude/hooks-windows/language-manager.ps1 +7 -1
  55. package/.claude/hooks-windows/personality-manager.ps1 +16 -1
  56. package/.claude/hooks-windows/play-tts-kokoro.ps1 +20 -4
  57. package/.claude/hooks-windows/play-tts.ps1 +32 -3
  58. package/.claude/hooks-windows/provider-catalog.ps1 +140 -0
  59. package/.claude/hooks-windows/provider-manager.ps1 +63 -8
  60. package/.claude/hooks-windows/soprano-gradio-synth.py.user.bak +153 -0
  61. package/.claude/hooks-windows/tts-watcher.ps1 +84 -8
  62. package/.claude/hooks-windows/voice-manager-windows.ps1 +49 -0
  63. package/.claude/piper-voices-dir.txt +1 -0
  64. package/.clawdbot/README.md +105 -105
  65. package/.mcp.json +15 -0
  66. package/LICENSE +190 -190
  67. package/README.md +20 -6
  68. package/RELEASE_NOTES.md +81 -0
  69. package/WINDOWS-SETUP.md +208 -208
  70. package/bin/agent-vibes +39 -39
  71. package/bin/mcp-server.js +121 -121
  72. package/bin/test-bmad-pr +78 -78
  73. package/mcp-server/QUICK_START.md +203 -203
  74. package/mcp-server/README.md +345 -345
  75. package/mcp-server/examples/claude_desktop_config.json +11 -11
  76. package/mcp-server/examples/claude_desktop_config_piper.json +9 -9
  77. package/mcp-server/examples/custom_instructions.md +169 -169
  78. package/mcp-server/pyproject.toml +52 -52
  79. package/mcp-server/requirements.txt +2 -2
  80. package/mcp-server/server.py +146 -49
  81. package/mcp-server/test_mcp_correctness.py +20 -2
  82. package/mcp-server/test_server.py +395 -395
  83. package/mcp-server/test_windows_script_parity.py +0 -2
  84. package/package.json +1 -1
  85. package/src/cli/list-personalities.js +110 -110
  86. package/src/cli/list-voices.js +115 -11
  87. package/src/commands/bmad-voices.js +394 -394
  88. package/src/console/app.js +1 -9
  89. package/src/console/bling.js +71 -0
  90. package/src/console/brand-colors.js +13 -13
  91. package/src/console/constants/personalities.js +44 -44
  92. package/src/console/footer-config.js +0 -4
  93. package/src/console/music-preview.js +79 -0
  94. package/src/console/navigation.js +0 -1
  95. package/src/console/tabs/agents-tab.js +14 -2
  96. package/src/console/tabs/help-tab.js +314 -314
  97. package/src/console/tabs/music-tab.js +81 -5
  98. package/src/console/tabs/placeholder-tab.js +0 -2
  99. package/src/console/tabs/readme-tab.js +272 -272
  100. package/src/console/tabs/settings-tab.js +207 -14
  101. package/src/console/tabs/setup-tab.js +39 -36
  102. package/src/console/tabs/voices-tab.js +102 -23
  103. package/src/console/widgets/destroy-list.js +25 -25
  104. package/src/console/widgets/notice.js +55 -55
  105. package/src/console/widgets/track-picker.js +82 -0
  106. package/src/installer/language-screen.js +31 -31
  107. package/src/installer/music-file-input.js +304 -304
  108. package/src/installer.js +124 -10
  109. package/src/services/language-service.js +47 -47
  110. package/src/services/navigation-service.js +1 -1
  111. package/src/services/provider-catalog.js +412 -0
  112. package/src/services/provider-voice-catalog.js +52 -73
  113. package/src/services/tts-engine-service.js +29 -0
  114. package/src/utils/audio-format-validator.js +277 -277
  115. package/src/utils/dependency-checker.js +469 -469
  116. package/src/utils/file-ownership-verifier.js +358 -358
  117. package/src/utils/music-file-validator.js +285 -285
  118. package/src/utils/preview-list-prompt.js +144 -144
  119. package/src/utils/provider-validator.js +62 -12
  120. package/src/utils/secure-music-storage.js +412 -412
  121. package/voice-assignments.json +8244 -8244
  122. package/.claude/commands/agent-vibes/language.md +0 -23
  123. package/.claude/commands/agent-vibes/learn.md +0 -67
  124. package/.claude/commands/agent-vibes/replay-target.md +0 -14
  125. package/.claude/commands/agent-vibes/target-voice.md +0 -26
  126. package/.claude/commands/agent-vibes/target.md +0 -30
  127. package/.claude/hooks/learn-manager.sh +0 -492
  128. package/.claude/hooks/replay-target-audio.sh +0 -95
  129. package/.claude/hooks-windows/learn-manager.ps1 +0 -241
@@ -42,7 +42,6 @@ HOOKS_LINUX = PROJECT_ROOT / ".claude" / "hooks"
42
42
  # The auto-conversion (server.py:902-903) converts .sh -> .ps1
43
43
  MCP_HARDCODED_SCRIPTS = [
44
44
  "provider-manager",
45
- "learn-manager",
46
45
  "speed-manager",
47
46
  "download-extra-voices",
48
47
  "verbosity-manager",
@@ -159,7 +158,6 @@ def test_run_script_error_for_missing():
159
158
  MCP_TOOL_SCRIPT_MAP = {
160
159
  "set_speed": "speed-manager",
161
160
  "get_speed": "speed-manager",
162
- "set_learn_mode": "learn-manager",
163
161
  "set_verbosity": "verbosity-manager",
164
162
  "get_verbosity": "verbosity-manager",
165
163
  "mute": "verbosity-manager",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "agentvibes",
4
- "version": "5.12.0-alpha.0",
4
+ "version": "5.13.0",
5
5
  "description": "Now your AI Agents can finally talk back! Professional TTS voice for Claude Code, Claude Desktop (via MCP), and Clawdbot with multi-provider support.",
6
6
  "homepage": "https://agentvibes.org",
7
7
  "keywords": [
@@ -1,110 +1,110 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Personality List Display - Beautiful multi-column personality listing
4
- * Called by personality-manager.sh to display personalities with boxen formatting
5
- */
6
-
7
- import { formatPersonalitiesList } from '../utils/list-formatter.js';
8
- import fs from 'fs';
9
- import path from 'path';
10
- import os from 'os';
11
-
12
- /**
13
- * Get personality description from markdown file
14
- */
15
- function getPersonalityDescription(filePath) {
16
- try {
17
- const content = fs.readFileSync(filePath, 'utf8');
18
-
19
- // Try to extract description from frontmatter or first paragraph
20
- const descMatch = content.match(/description:\s*(.+)/i);
21
- if (descMatch) {
22
- return descMatch[1].trim();
23
- }
24
-
25
- // Try to get first line after frontmatter
26
- const lines = content.split('\n');
27
- let inFrontmatter = false;
28
- let frontmatterCount = 0;
29
-
30
- for (const line of lines) {
31
- if (line.trim() === '---') {
32
- frontmatterCount++;
33
- inFrontmatter = frontmatterCount === 1;
34
- continue;
35
- }
36
-
37
- if (!inFrontmatter && frontmatterCount >= 2 && line.trim()) {
38
- // First non-empty line after frontmatter
39
- return line.trim().replace(/^#+\s*/, '').substring(0, 50);
40
- }
41
- }
42
-
43
- return '';
44
- } catch (error) {
45
- return '';
46
- }
47
- }
48
-
49
- /**
50
- * Get all personalities from directory
51
- */
52
- function getPersonalities(personalitiesDir, currentPersonality) {
53
- const personalities = [];
54
-
55
- if (!fs.existsSync(personalitiesDir)) {
56
- return personalities;
57
- }
58
-
59
- const files = fs.readdirSync(personalitiesDir);
60
- for (const file of files) {
61
- if (file.endsWith('.md')) {
62
- const name = path.basename(file, '.md');
63
- const filePath = path.join(personalitiesDir, file);
64
- const description = getPersonalityDescription(filePath);
65
-
66
- personalities.push({
67
- name,
68
- description,
69
- current: name === currentPersonality
70
- });
71
- }
72
- }
73
-
74
- // Add special 'random' option
75
- personalities.push({
76
- name: 'random',
77
- description: 'Picks randomly each time',
78
- current: currentPersonality === 'random'
79
- });
80
-
81
- return personalities.sort((a, b) => {
82
- // Keep 'random' at the end
83
- if (a.name === 'random') return 1;
84
- if (b.name === 'random') return -1;
85
- return a.name.localeCompare(b.name);
86
- });
87
- }
88
-
89
- /**
90
- * Main function
91
- */
92
- function main() {
93
- const args = process.argv.slice(2);
94
-
95
- // Parse arguments
96
- const personalitiesDir = args[0] || path.join(os.homedir(), '.claude', 'personalities');
97
- const currentPersonality = args[1] || 'normal';
98
-
99
- const personalities = getPersonalities(personalitiesDir, currentPersonality);
100
-
101
- // Display with boxen
102
- const output = formatPersonalitiesList(personalities, {
103
- columns: 2,
104
- showUsage: true
105
- });
106
-
107
- console.log(output);
108
- }
109
-
110
- main();
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Personality List Display - Beautiful multi-column personality listing
4
+ * Called by personality-manager.sh to display personalities with boxen formatting
5
+ */
6
+
7
+ import { formatPersonalitiesList } from '../utils/list-formatter.js';
8
+ import fs from 'fs';
9
+ import path from 'path';
10
+ import os from 'os';
11
+
12
+ /**
13
+ * Get personality description from markdown file
14
+ */
15
+ function getPersonalityDescription(filePath) {
16
+ try {
17
+ const content = fs.readFileSync(filePath, 'utf8');
18
+
19
+ // Try to extract description from frontmatter or first paragraph
20
+ const descMatch = content.match(/description:\s*(.+)/i);
21
+ if (descMatch) {
22
+ return descMatch[1].trim();
23
+ }
24
+
25
+ // Try to get first line after frontmatter
26
+ const lines = content.split('\n');
27
+ let inFrontmatter = false;
28
+ let frontmatterCount = 0;
29
+
30
+ for (const line of lines) {
31
+ if (line.trim() === '---') {
32
+ frontmatterCount++;
33
+ inFrontmatter = frontmatterCount === 1;
34
+ continue;
35
+ }
36
+
37
+ if (!inFrontmatter && frontmatterCount >= 2 && line.trim()) {
38
+ // First non-empty line after frontmatter
39
+ return line.trim().replace(/^#+\s*/, '').substring(0, 50);
40
+ }
41
+ }
42
+
43
+ return '';
44
+ } catch (error) {
45
+ return '';
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Get all personalities from directory
51
+ */
52
+ function getPersonalities(personalitiesDir, currentPersonality) {
53
+ const personalities = [];
54
+
55
+ if (!fs.existsSync(personalitiesDir)) {
56
+ return personalities;
57
+ }
58
+
59
+ const files = fs.readdirSync(personalitiesDir);
60
+ for (const file of files) {
61
+ if (file.endsWith('.md')) {
62
+ const name = path.basename(file, '.md');
63
+ const filePath = path.join(personalitiesDir, file);
64
+ const description = getPersonalityDescription(filePath);
65
+
66
+ personalities.push({
67
+ name,
68
+ description,
69
+ current: name === currentPersonality
70
+ });
71
+ }
72
+ }
73
+
74
+ // Add special 'random' option
75
+ personalities.push({
76
+ name: 'random',
77
+ description: 'Picks randomly each time',
78
+ current: currentPersonality === 'random'
79
+ });
80
+
81
+ return personalities.sort((a, b) => {
82
+ // Keep 'random' at the end
83
+ if (a.name === 'random') return 1;
84
+ if (b.name === 'random') return -1;
85
+ return a.name.localeCompare(b.name);
86
+ });
87
+ }
88
+
89
+ /**
90
+ * Main function
91
+ */
92
+ function main() {
93
+ const args = process.argv.slice(2);
94
+
95
+ // Parse arguments
96
+ const personalitiesDir = args[0] || path.join(os.homedir(), '.claude', 'personalities');
97
+ const currentPersonality = args[1] || 'normal';
98
+
99
+ const personalities = getPersonalities(personalitiesDir, currentPersonality);
100
+
101
+ // Display with boxen
102
+ const output = formatPersonalitiesList(personalities, {
103
+ columns: 2,
104
+ showUsage: true
105
+ });
106
+
107
+ console.log(output);
108
+ }
109
+
110
+ main();
@@ -7,8 +7,18 @@
7
7
  import { formatVoicesList } from '../utils/list-formatter.js';
8
8
  import fs from 'fs';
9
9
  import path from 'path';
10
+ import { fileURLToPath } from 'url';
10
11
  import { execFileSync } from 'child_process';
11
12
  import os from 'os';
13
+ import {
14
+ KOKORO_VOICE_IDS,
15
+ kokoroGender,
16
+ ELEVENLABS_VOICES,
17
+ } from '../services/provider-voice-catalog.js';
18
+ import {
19
+ listVoices as catalogListVoices,
20
+ getProvider,
21
+ } from '../services/provider-catalog.js';
12
22
 
13
23
  /**
14
24
  * Get Piper voices from voice directory
@@ -71,6 +81,55 @@ function getMacOSVoices(currentVoice) {
71
81
  return voices;
72
82
  }
73
83
 
84
+ /**
85
+ * Kokoro voice ids follow `<lang><sex>_name`. The first char is the language.
86
+ * Map it to a human-readable language label for the listing.
87
+ */
88
+ const KOKORO_LANG_LABELS = {
89
+ a: 'en-US', b: 'en-GB', j: 'ja', z: 'zh', e: 'es',
90
+ f: 'fr', h: 'hi', i: 'it', p: 'pt-BR', k: 'ko',
91
+ };
92
+
93
+ /**
94
+ * Get Kokoro voices from the canonical catalog (KOKORO_VOICE_IDS).
95
+ * Kokoro's voice set is a fixed catalog, not discovered on disk.
96
+ */
97
+ function getKokoroVoices(currentVoice) {
98
+ return KOKORO_VOICE_IDS.map((id) => ({
99
+ name: id,
100
+ lang: KOKORO_LANG_LABELS[id[0]] || '',
101
+ gender: kokoroGender(id),
102
+ current: id === currentVoice,
103
+ }));
104
+ }
105
+
106
+ /**
107
+ * Get ElevenLabs voices from the canonical catalog (ELEVENLABS_VOICES).
108
+ * The listing shows the friendly name; matching against currentVoice accepts
109
+ * either the friendly name or the raw voice_id.
110
+ */
111
+ function getElevenLabsVoices(currentVoice) {
112
+ return ELEVENLABS_VOICES.map((v) => ({
113
+ name: v.name,
114
+ lang: v.lang || '',
115
+ gender: v.gender || '',
116
+ current: v.name === currentVoice || v.id === currentVoice,
117
+ }));
118
+ }
119
+
120
+ /**
121
+ * Get Soprano voices from the canonical catalog. Soprano is voiceModel `single`
122
+ * (design §3.1): exactly one canonical voice, soprano-default — no picker.
123
+ */
124
+ function getSopranoVoices(currentVoice) {
125
+ return catalogListVoices('soprano').map((v) => ({
126
+ name: v.id,
127
+ lang: '',
128
+ gender: v.gender || '',
129
+ current: v.id === currentVoice || currentVoice === '' || currentVoice === 'soprano',
130
+ }));
131
+ }
132
+
74
133
  /**
75
134
  * Extract language code from voice name
76
135
  */
@@ -79,6 +138,55 @@ function extractLanguage(voiceName) {
79
138
  return match ? match[1] : '';
80
139
  }
81
140
 
141
+ /**
142
+ * Discovered providers list-voices enumerates HERE, each via its own platform
143
+ * discovery path (piper: `*.onnx` disk glob; macos: `say -v ?`). Other discovered
144
+ * providers (windows-piper / windows-sapi) are enumerated by the Windows lister,
145
+ * not this Unix/darwin CLI, so they fall to the honest "no voice list" label —
146
+ * preserving pre-AVI-S9.5 output. Adding a record with a static/name-to-id/single
147
+ * voiceModel adds a list arm for free (no edit here needed).
148
+ */
149
+ const LISTABLE_DISCOVERED = new Set(['piper', 'macos']);
150
+
151
+ /**
152
+ * Resolve a provider token to its voice list + display label by iterating the
153
+ * Provider Catalog (src/services/provider-catalog.js) and branching ONLY on the
154
+ * record's `voiceModel` — replacing the former four hardcoded per-provider
155
+ * equality branches (AVI-S9.5 / design row 19). Unknown tokens keep the honest
156
+ * "no voice list available" label from AVI-S8.1.
157
+ *
158
+ * @param {string} provider
159
+ * @param {string} currentVoice
160
+ * @param {string} voiceDir
161
+ * @returns {{ voices: object[], providerName: string }}
162
+ */
163
+ function selectVoices(provider, currentVoice, voiceDir) {
164
+ const record = getProvider(provider);
165
+ if (!record) {
166
+ return { voices: [], providerName: `${provider} (no voice list available)` };
167
+ }
168
+
169
+ // Label preserves the pre-existing strings exactly (macOS uses "TTS", not the
170
+ // catalog display name "macOS Say"); all others equal record.displayName.
171
+ const providerName = record.id === 'macos' ? 'macOS TTS' : record.displayName;
172
+
173
+ switch (record.voiceModel) {
174
+ case 'static': // kokoro
175
+ return { voices: getKokoroVoices(currentVoice), providerName };
176
+ case 'name-to-id': // elevenlabs
177
+ return { voices: getElevenLabsVoices(currentVoice), providerName };
178
+ case 'single': // soprano
179
+ return { voices: getSopranoVoices(currentVoice), providerName };
180
+ case 'discovered':
181
+ default:
182
+ if (record.id === 'piper') return { voices: getPiperVoices(voiceDir, currentVoice), providerName };
183
+ if (record.id === 'macos') return { voices: getMacOSVoices(currentVoice), providerName };
184
+ // A discovered provider without a discovery path on this platform: label
185
+ // it honestly instead of rendering an empty list under a wrong provider.
186
+ return { voices: [], providerName: `${provider} (no voice list available)` };
187
+ }
188
+ }
189
+
82
190
  /**
83
191
  * Main function
84
192
  */
@@ -90,16 +198,7 @@ function main() {
90
198
  const currentVoice = args[1] || '';
91
199
  const voiceDir = args[2] || '';
92
200
 
93
- let voices = [];
94
- let providerName = 'Piper TTS';
95
-
96
- if (provider === 'piper') {
97
- voices = getPiperVoices(voiceDir, currentVoice);
98
- providerName = 'Piper TTS';
99
- } else if (provider === 'macos') {
100
- voices = getMacOSVoices(currentVoice);
101
- providerName = 'macOS TTS';
102
- }
201
+ const { voices, providerName } = selectVoices(provider, currentVoice, voiceDir);
103
202
 
104
203
  // Display with boxen
105
204
  const output = formatVoicesList(voices, {
@@ -111,4 +210,9 @@ function main() {
111
210
  console.log(output);
112
211
  }
113
212
 
114
- main();
213
+ // Only run when invoked as a CLI (keeps selectVoices importable by tests).
214
+ const _invokedDirectly = process.argv[1] &&
215
+ path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
216
+ if (_invokedDirectly) main();
217
+
218
+ export { selectVoices, LISTABLE_DISCOVERED };