agentvibes 5.9.0 → 5.10.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 (145) hide show
  1. package/.agentvibes/config.json +3 -12
  2. package/.claude/commands/agent-vibes-bmad-voices.md +117 -117
  3. package/.claude/commands/agent-vibes-rdp.md +24 -24
  4. package/.claude/config/audio-effects.cfg +4 -5
  5. package/.claude/config/audio-effects.cfg.sample +52 -52
  6. package/.claude/config/background-music-enabled.txt +1 -1
  7. package/.claude/docs/TERMUX_SETUP.md +408 -408
  8. package/.claude/github-star-reminder.txt +1 -1
  9. package/.claude/hooks/audio-cache-utils.sh +0 -0
  10. package/.claude/hooks/audio-processor.sh +0 -0
  11. package/.claude/hooks/background-music-manager.sh +0 -0
  12. package/.claude/hooks/bmad-party-speak.sh +0 -0
  13. package/.claude/hooks/bmad-speak-enhanced.sh +0 -0
  14. package/.claude/hooks/bmad-speak.sh +0 -0
  15. package/.claude/hooks/bmad-tts-injector.sh +0 -0
  16. package/.claude/hooks/bmad-voice-manager.sh +0 -0
  17. package/.claude/hooks/clawdbot-receiver-SECURE.sh +0 -0
  18. package/.claude/hooks/clawdbot-receiver.sh +0 -0
  19. package/.claude/hooks/clean-audio-cache.sh +0 -0
  20. package/.claude/hooks/cleanup-cache.sh +0 -0
  21. package/.claude/hooks/configure-rdp-mode.sh +0 -0
  22. package/.claude/hooks/download-extra-voices.sh +0 -0
  23. package/.claude/hooks/effects-manager.sh +0 -0
  24. package/.claude/hooks/github-star-reminder.sh +0 -0
  25. package/.claude/hooks/language-manager.sh +0 -0
  26. package/.claude/hooks/learn-manager.sh +0 -0
  27. package/.claude/hooks/macos-voice-manager.sh +0 -0
  28. package/.claude/hooks/migrate-background-music.sh +0 -0
  29. package/.claude/hooks/migrate-to-agentvibes.sh +0 -0
  30. package/.claude/hooks/optimize-background-music.sh +0 -0
  31. package/.claude/hooks/path-resolver.sh +0 -0
  32. package/.claude/hooks/personality-manager.sh +0 -0
  33. package/.claude/hooks/piper-download-voices.sh +0 -0
  34. package/.claude/hooks/piper-installer.sh +0 -0
  35. package/.claude/hooks/piper-multispeaker-registry.sh +0 -0
  36. package/.claude/hooks/piper-voice-manager.sh +0 -0
  37. package/.claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh +0 -0
  38. package/.claude/hooks/play-tts-enhanced.sh +0 -0
  39. package/.claude/hooks/play-tts-macos.sh +0 -0
  40. package/.claude/hooks/play-tts-piper.sh +20 -13
  41. package/.claude/hooks/play-tts-soprano.sh +0 -0
  42. package/.claude/hooks/play-tts-ssh-remote.sh +0 -0
  43. package/.claude/hooks/play-tts-termux-ssh.sh +0 -0
  44. package/.claude/hooks/play-tts-windows-receiver.sh +0 -0
  45. package/.claude/hooks/play-tts.sh +0 -0
  46. package/.claude/hooks/prepare-release.sh +0 -0
  47. package/.claude/hooks/provider-commands.sh +0 -0
  48. package/.claude/hooks/provider-manager.sh +0 -0
  49. package/.claude/hooks/replay-target-audio.sh +0 -0
  50. package/.claude/hooks/requirements.txt +6 -6
  51. package/.claude/hooks/sentiment-manager.sh +0 -0
  52. package/.claude/hooks/session-start-tts.sh +0 -0
  53. package/.claude/hooks/soprano-gradio-synth.py +139 -139
  54. package/.claude/hooks/speed-manager.sh +0 -0
  55. package/.claude/hooks/stop-tts.sh +0 -0
  56. package/.claude/hooks/termux-installer.sh +0 -0
  57. package/.claude/hooks/translate-manager.sh +0 -0
  58. package/.claude/hooks/translator.py +237 -237
  59. package/.claude/hooks/tts-queue-worker.sh +0 -0
  60. package/.claude/hooks/tts-queue.sh +0 -0
  61. package/.claude/hooks/verbosity-manager.sh +0 -0
  62. package/.claude/hooks/voice-manager.sh +6 -0
  63. package/.claude/hooks-windows/play-tts-windows-piper.ps1 +22 -16
  64. package/.claude/hooks-windows/soprano-gradio-synth.py +153 -153
  65. package/.claude/verbosity.txt +1 -1
  66. package/.clawdbot/README.md +105 -105
  67. package/.mcp.json +19 -6
  68. package/README.md +1 -1
  69. package/WINDOWS-SETUP.md +208 -208
  70. package/bin/agent-vibes +39 -39
  71. package/bin/agentvibes-voice-browser.js +0 -0
  72. package/bin/agentvibes.js +0 -0
  73. package/bin/mcp-server.js +121 -121
  74. package/bin/mcp-server.sh +0 -0
  75. package/bin/test-bmad-pr +78 -78
  76. package/mcp-server/QUICK_START.md +203 -203
  77. package/mcp-server/README.md +345 -345
  78. package/mcp-server/WINDOWS_SETUP.md +0 -0
  79. package/mcp-server/examples/claude_desktop_config.json +11 -11
  80. package/mcp-server/examples/claude_desktop_config_piper.json +9 -9
  81. package/mcp-server/examples/custom_instructions.md +169 -169
  82. package/mcp-server/install-deps.js +0 -0
  83. package/mcp-server/server.py +1807 -1797
  84. package/mcp-server/test_server.py +0 -0
  85. package/package.json +2 -2
  86. package/src/cli/list-personalities.js +110 -110
  87. package/src/cli/list-voices.js +114 -114
  88. package/src/commands/bmad-voices.js +394 -394
  89. package/src/commands/install-mcp.js +730 -476
  90. package/src/console/app.js +3 -3
  91. package/src/console/brand-colors.js +13 -13
  92. package/src/console/constants/personalities.js +44 -44
  93. package/src/console/tabs/agents-tab.js +6 -6
  94. package/src/console/tabs/help-tab.js +314 -314
  95. package/src/console/tabs/music-tab.js +1 -1
  96. package/src/console/tabs/readme-tab.js +272 -272
  97. package/src/console/tabs/receiver-tab.js +13 -13
  98. package/src/console/tabs/settings-tab.js +2 -2
  99. package/src/console/tabs/setup-tab.js +10 -10
  100. package/src/console/tabs/voices-tab.js +4 -4
  101. package/src/console/widgets/destroy-list.js +25 -25
  102. package/src/console/widgets/notice.js +55 -55
  103. package/src/console/widgets/personality-picker.js +2 -2
  104. package/src/console/widgets/reverb-picker.js +1 -1
  105. package/src/i18n/de.js +202 -202
  106. package/src/i18n/es.js +202 -202
  107. package/src/i18n/fr.js +202 -202
  108. package/src/i18n/hi.js +202 -202
  109. package/src/i18n/ja.js +202 -202
  110. package/src/i18n/ko.js +202 -202
  111. package/src/i18n/pt.js +202 -202
  112. package/src/i18n/strings.js +54 -54
  113. package/src/i18n/zh-CN.js +202 -202
  114. package/src/installer/language-screen.js +31 -31
  115. package/src/installer/music-file-input.js +304 -304
  116. package/src/installer.js +32 -27
  117. package/src/services/config-service.js +264 -264
  118. package/src/services/language-service.js +47 -47
  119. package/src/services/provider-service.js +143 -143
  120. package/src/services/tts-engine-service.js +2 -2
  121. package/src/utils/audio-duration-validator.js +298 -298
  122. package/src/utils/audio-format-validator.js +277 -277
  123. package/src/utils/dependency-checker.js +469 -469
  124. package/src/utils/file-ownership-verifier.js +358 -358
  125. package/src/utils/list-formatter.js +200 -194
  126. package/src/utils/music-file-validator.js +285 -285
  127. package/src/utils/platform-resolver.js +369 -0
  128. package/src/utils/preview-list-prompt.js +136 -136
  129. package/src/utils/provider-validator.js +9 -9
  130. package/src/utils/secure-music-storage.js +412 -412
  131. package/templates/agentvibes-receiver.sh +231 -231
  132. package/templates/audio/welcome-music.mp3 +0 -0
  133. package/.agentvibes/install-manifest.json +0 -330
  134. package/.claude/config/background-music-position.txt +0 -27
  135. package/.claude/config/background-music-volume.txt +0 -1
  136. package/.claude/config/background-music.cfg +0 -1
  137. package/.claude/config/background-music.txt +0 -1
  138. package/.claude/config/language.txt +0 -1
  139. package/.claude/config/reverb-level.txt +0 -1
  140. package/.claude/config/tts-speech-rate.txt +0 -1
  141. package/.claude/config/tts-verbosity.txt +0 -1
  142. package/.claude/hooks/play-tts-agentvibes-receiver.sh +0 -1
  143. package/.claude/hooks-windows/audio-cache-utils.ps1.user.bak +0 -119
  144. package/.claude/hooks-windows/soprano-gradio-synth.py.user.bak +0 -153
  145. package/.claude/piper-voices-dir.txt +0 -1
@@ -1,237 +1,237 @@
1
- #!/usr/bin/env python3
2
- #
3
- # File: .claude/hooks/translator.py
4
- #
5
- # AgentVibes - Finally, your AI Agents can Talk Back! Text-to-Speech WITH personality for AI Assistants!
6
- # Website: https://agentvibes.org
7
- # Repository: https://github.com/paulpreibisch/AgentVibes
8
- #
9
- # Co-created by Paul Preibisch with Claude AI
10
- # Copyright (c) 2025 Paul Preibisch
11
- #
12
- # Licensed under the Apache License, Version 2.0 (the "License");
13
- # you may not use this file except in compliance with the License.
14
- # You may obtain a copy of the License at
15
- #
16
- # http://www.apache.org/licenses/LICENSE-2.0
17
- #
18
- # Unless required by applicable law or agreed to in writing, software
19
- # distributed under the License is distributed on an "AS IS" BASIS,
20
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
- # See the License for the specific language governing permissions and
22
- # limitations under the License.
23
- #
24
- # DISCLAIMER: This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
25
- # express or implied. Use at your own risk. See the Apache License for details.
26
- #
27
- # ---
28
- #
29
- # @fileoverview Text translator for multi-language TTS and learning mode
30
- # @context Provides automatic translation using Google Translate via deep-translator library
31
- # @architecture Standalone CLI module callable from bash scripts, with library mode for Python imports
32
- # @dependencies deep-translator, langdetect (pip install deep-translator langdetect)
33
- # @entrypoints CLI: python3 translator.py <text> <target_lang>, Library: from translator import translate
34
- # @patterns Command pattern - supports translate, detect, and batch operations
35
- # @related play-tts.sh, learn-manager.sh, language-manager.sh
36
- #
37
-
38
- """
39
- Text translation utilities for AgentVibes multi-language TTS.
40
-
41
- Provides automatic translation of TTS text to the user's preferred language,
42
- supporting both BMAD communication_language settings and learning mode.
43
-
44
- Usage:
45
- CLI: python3 translator.py <text> <target_language>
46
- Library: from translator import translate, detect_language
47
- """
48
-
49
- import sys
50
- import os
51
- from typing import Optional, Tuple
52
-
53
- # Language name to ISO code mapping
54
- LANG_CODES = {
55
- 'spanish': 'es', 'español': 'es', 'es': 'es',
56
- 'french': 'fr', 'français': 'fr', 'fr': 'fr',
57
- 'german': 'de', 'deutsch': 'de', 'de': 'de',
58
- 'italian': 'it', 'italiano': 'it', 'it': 'it',
59
- 'portuguese': 'pt', 'português': 'pt', 'pt': 'pt',
60
- 'chinese': 'zh-CN', 'mandarin': 'zh-CN', 'zh': 'zh-CN', '中文': 'zh-CN',
61
- 'japanese': 'ja', '日本語': 'ja', 'ja': 'ja',
62
- 'korean': 'ko', '한국어': 'ko', 'ko': 'ko',
63
- 'russian': 'ru', 'русский': 'ru', 'ru': 'ru',
64
- 'polish': 'pl', 'polski': 'pl', 'pl': 'pl',
65
- 'dutch': 'nl', 'nederlands': 'nl', 'nl': 'nl',
66
- 'turkish': 'tr', 'türkçe': 'tr', 'tr': 'tr',
67
- 'arabic': 'ar', 'العربية': 'ar', 'ar': 'ar',
68
- 'hindi': 'hi', 'हिन्दी': 'hi', 'hi': 'hi',
69
- 'swedish': 'sv', 'svenska': 'sv', 'sv': 'sv',
70
- 'danish': 'da', 'dansk': 'da', 'da': 'da',
71
- 'norwegian': 'no', 'norsk': 'no', 'no': 'no',
72
- 'finnish': 'fi', 'suomi': 'fi', 'fi': 'fi',
73
- 'czech': 'cs', 'čeština': 'cs', 'cs': 'cs',
74
- 'romanian': 'ro', 'română': 'ro', 'ro': 'ro',
75
- 'ukrainian': 'uk', 'українська': 'uk', 'uk': 'uk',
76
- 'greek': 'el', 'ελληνικά': 'el', 'el': 'el',
77
- 'bulgarian': 'bg', 'български': 'bg', 'bg': 'bg',
78
- 'croatian': 'hr', 'hrvatski': 'hr', 'hr': 'hr',
79
- 'slovak': 'sk', 'slovenčina': 'sk', 'sk': 'sk',
80
- 'english': 'en', 'en': 'en',
81
- }
82
-
83
-
84
- def get_lang_code(language: str) -> str:
85
- """
86
- Convert language name to ISO code.
87
-
88
- Args:
89
- language: Language name or code (e.g., 'spanish', 'es', 'español')
90
-
91
- Returns:
92
- ISO language code (e.g., 'es')
93
- """
94
- lang_lower = language.lower().strip()
95
- return LANG_CODES.get(lang_lower, lang_lower)
96
-
97
-
98
- def detect_language(text: str) -> Optional[str]:
99
- """
100
- Detect the language of given text.
101
-
102
- Args:
103
- text: Text to analyze
104
-
105
- Returns:
106
- Language code (e.g., 'es', 'fr', 'en') or None if detection fails
107
- """
108
- if not text or len(text.strip()) < 3:
109
- return None
110
-
111
- try:
112
- from langdetect import detect, LangDetectException
113
- return detect(text)
114
- except ImportError:
115
- print("Warning: langdetect not installed. Run: pip install langdetect", file=sys.stderr)
116
- return None
117
- except Exception:
118
- return None
119
-
120
-
121
- def translate(text: str, target_lang: str, source_lang: str = 'en') -> Tuple[str, bool]:
122
- """
123
- Translate text to target language.
124
-
125
- Args:
126
- text: Text to translate
127
- target_lang: Target language (name or code)
128
- source_lang: Source language (default: 'en')
129
-
130
- Returns:
131
- Tuple of (translated_text, success)
132
- """
133
- if not text or not text.strip():
134
- return text, False
135
-
136
- # Convert language names to codes
137
- target_code = get_lang_code(target_lang)
138
- source_code = get_lang_code(source_lang)
139
-
140
- # Skip if source and target are the same
141
- if target_code == source_code:
142
- return text, False
143
-
144
- # Skip if target is English and source is also English
145
- if target_code == 'en' and source_code == 'en':
146
- return text, False
147
-
148
- try:
149
- from deep_translator import GoogleTranslator
150
-
151
- translator = GoogleTranslator(source=source_code, target=target_code)
152
- translated = translator.translate(text)
153
-
154
- if translated:
155
- return translated, True
156
- return text, False
157
-
158
- except ImportError:
159
- print("Error: deep-translator not installed. Run: pip install deep-translator", file=sys.stderr)
160
- return text, False
161
- except Exception as e:
162
- print(f"Translation error: {e}", file=sys.stderr)
163
- return text, False
164
-
165
-
166
- def translate_auto(text: str, target_lang: str) -> Tuple[str, bool, Optional[str]]:
167
- """
168
- Translate text to target language with auto-detection of source language.
169
-
170
- Args:
171
- text: Text to translate
172
- target_lang: Target language (name or code)
173
-
174
- Returns:
175
- Tuple of (translated_text, success, detected_source_lang)
176
- """
177
- if not text or not text.strip():
178
- return text, False, None
179
-
180
- # Detect source language
181
- detected = detect_language(text)
182
-
183
- # Convert target to code
184
- target_code = get_lang_code(target_lang)
185
-
186
- # Skip if detected language matches target
187
- if detected and detected == target_code:
188
- return text, False, detected
189
-
190
- # Translate
191
- translated, success = translate(text, target_lang, source_lang=detected or 'en')
192
- return translated, success, detected
193
-
194
-
195
- def main():
196
- """CLI entry point for translator."""
197
- if len(sys.argv) < 3:
198
- print("Usage: translator.py <text> <target_language> [source_language]", file=sys.stderr)
199
- print(" translator.py detect <text>", file=sys.stderr)
200
- print("", file=sys.stderr)
201
- print("Examples:", file=sys.stderr)
202
- print(" translator.py 'Hello world' spanish", file=sys.stderr)
203
- print(" translator.py 'Hello world' es en", file=sys.stderr)
204
- print(" translator.py detect 'Hola mundo'", file=sys.stderr)
205
- sys.exit(1)
206
-
207
- command = sys.argv[1]
208
-
209
- # Detection mode
210
- if command == 'detect':
211
- if len(sys.argv) < 3:
212
- print("Usage: translator.py detect <text>", file=sys.stderr)
213
- sys.exit(1)
214
- text = sys.argv[2]
215
- detected = detect_language(text)
216
- if detected:
217
- print(detected)
218
- else:
219
- print("unknown")
220
- sys.exit(0)
221
-
222
- # Translation mode
223
- text = sys.argv[1]
224
- target_lang = sys.argv[2]
225
- source_lang = sys.argv[3] if len(sys.argv) > 3 else 'en'
226
-
227
- translated, success = translate(text, target_lang, source_lang)
228
-
229
- # Output the result (for shell script consumption)
230
- print(translated)
231
-
232
- # Exit with appropriate code
233
- sys.exit(0 if success else 1)
234
-
235
-
236
- if __name__ == '__main__':
237
- main()
1
+ #!/usr/bin/env python3
2
+ #
3
+ # File: .claude/hooks/translator.py
4
+ #
5
+ # AgentVibes - Finally, your AI Agents can Talk Back! Text-to-Speech WITH personality for AI Assistants!
6
+ # Website: https://agentvibes.org
7
+ # Repository: https://github.com/paulpreibisch/AgentVibes
8
+ #
9
+ # Co-created by Paul Preibisch with Claude AI
10
+ # Copyright (c) 2025 Paul Preibisch
11
+ #
12
+ # Licensed under the Apache License, Version 2.0 (the "License");
13
+ # you may not use this file except in compliance with the License.
14
+ # You may obtain a copy of the License at
15
+ #
16
+ # http://www.apache.org/licenses/LICENSE-2.0
17
+ #
18
+ # Unless required by applicable law or agreed to in writing, software
19
+ # distributed under the License is distributed on an "AS IS" BASIS,
20
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
+ # See the License for the specific language governing permissions and
22
+ # limitations under the License.
23
+ #
24
+ # DISCLAIMER: This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
25
+ # express or implied. Use at your own risk. See the Apache License for details.
26
+ #
27
+ # ---
28
+ #
29
+ # @fileoverview Text translator for multi-language TTS and learning mode
30
+ # @context Provides automatic translation using Google Translate via deep-translator library
31
+ # @architecture Standalone CLI module callable from bash scripts, with library mode for Python imports
32
+ # @dependencies deep-translator, langdetect (pip install deep-translator langdetect)
33
+ # @entrypoints CLI: python3 translator.py <text> <target_lang>, Library: from translator import translate
34
+ # @patterns Command pattern - supports translate, detect, and batch operations
35
+ # @related play-tts.sh, learn-manager.sh, language-manager.sh
36
+ #
37
+
38
+ """
39
+ Text translation utilities for AgentVibes multi-language TTS.
40
+
41
+ Provides automatic translation of TTS text to the user's preferred language,
42
+ supporting both BMAD communication_language settings and learning mode.
43
+
44
+ Usage:
45
+ CLI: python3 translator.py <text> <target_language>
46
+ Library: from translator import translate, detect_language
47
+ """
48
+
49
+ import sys
50
+ import os
51
+ from typing import Optional, Tuple
52
+
53
+ # Language name to ISO code mapping
54
+ LANG_CODES = {
55
+ 'spanish': 'es', 'español': 'es', 'es': 'es',
56
+ 'french': 'fr', 'français': 'fr', 'fr': 'fr',
57
+ 'german': 'de', 'deutsch': 'de', 'de': 'de',
58
+ 'italian': 'it', 'italiano': 'it', 'it': 'it',
59
+ 'portuguese': 'pt', 'português': 'pt', 'pt': 'pt',
60
+ 'chinese': 'zh-CN', 'mandarin': 'zh-CN', 'zh': 'zh-CN', '中文': 'zh-CN',
61
+ 'japanese': 'ja', '日本語': 'ja', 'ja': 'ja',
62
+ 'korean': 'ko', '한국어': 'ko', 'ko': 'ko',
63
+ 'russian': 'ru', 'русский': 'ru', 'ru': 'ru',
64
+ 'polish': 'pl', 'polski': 'pl', 'pl': 'pl',
65
+ 'dutch': 'nl', 'nederlands': 'nl', 'nl': 'nl',
66
+ 'turkish': 'tr', 'türkçe': 'tr', 'tr': 'tr',
67
+ 'arabic': 'ar', 'العربية': 'ar', 'ar': 'ar',
68
+ 'hindi': 'hi', 'हिन्दी': 'hi', 'hi': 'hi',
69
+ 'swedish': 'sv', 'svenska': 'sv', 'sv': 'sv',
70
+ 'danish': 'da', 'dansk': 'da', 'da': 'da',
71
+ 'norwegian': 'no', 'norsk': 'no', 'no': 'no',
72
+ 'finnish': 'fi', 'suomi': 'fi', 'fi': 'fi',
73
+ 'czech': 'cs', 'čeština': 'cs', 'cs': 'cs',
74
+ 'romanian': 'ro', 'română': 'ro', 'ro': 'ro',
75
+ 'ukrainian': 'uk', 'українська': 'uk', 'uk': 'uk',
76
+ 'greek': 'el', 'ελληνικά': 'el', 'el': 'el',
77
+ 'bulgarian': 'bg', 'български': 'bg', 'bg': 'bg',
78
+ 'croatian': 'hr', 'hrvatski': 'hr', 'hr': 'hr',
79
+ 'slovak': 'sk', 'slovenčina': 'sk', 'sk': 'sk',
80
+ 'english': 'en', 'en': 'en',
81
+ }
82
+
83
+
84
+ def get_lang_code(language: str) -> str:
85
+ """
86
+ Convert language name to ISO code.
87
+
88
+ Args:
89
+ language: Language name or code (e.g., 'spanish', 'es', 'español')
90
+
91
+ Returns:
92
+ ISO language code (e.g., 'es')
93
+ """
94
+ lang_lower = language.lower().strip()
95
+ return LANG_CODES.get(lang_lower, lang_lower)
96
+
97
+
98
+ def detect_language(text: str) -> Optional[str]:
99
+ """
100
+ Detect the language of given text.
101
+
102
+ Args:
103
+ text: Text to analyze
104
+
105
+ Returns:
106
+ Language code (e.g., 'es', 'fr', 'en') or None if detection fails
107
+ """
108
+ if not text or len(text.strip()) < 3:
109
+ return None
110
+
111
+ try:
112
+ from langdetect import detect, LangDetectException
113
+ return detect(text)
114
+ except ImportError:
115
+ print("Warning: langdetect not installed. Run: pip install langdetect", file=sys.stderr)
116
+ return None
117
+ except Exception:
118
+ return None
119
+
120
+
121
+ def translate(text: str, target_lang: str, source_lang: str = 'en') -> Tuple[str, bool]:
122
+ """
123
+ Translate text to target language.
124
+
125
+ Args:
126
+ text: Text to translate
127
+ target_lang: Target language (name or code)
128
+ source_lang: Source language (default: 'en')
129
+
130
+ Returns:
131
+ Tuple of (translated_text, success)
132
+ """
133
+ if not text or not text.strip():
134
+ return text, False
135
+
136
+ # Convert language names to codes
137
+ target_code = get_lang_code(target_lang)
138
+ source_code = get_lang_code(source_lang)
139
+
140
+ # Skip if source and target are the same
141
+ if target_code == source_code:
142
+ return text, False
143
+
144
+ # Skip if target is English and source is also English
145
+ if target_code == 'en' and source_code == 'en':
146
+ return text, False
147
+
148
+ try:
149
+ from deep_translator import GoogleTranslator
150
+
151
+ translator = GoogleTranslator(source=source_code, target=target_code)
152
+ translated = translator.translate(text)
153
+
154
+ if translated:
155
+ return translated, True
156
+ return text, False
157
+
158
+ except ImportError:
159
+ print("Error: deep-translator not installed. Run: pip install deep-translator", file=sys.stderr)
160
+ return text, False
161
+ except Exception as e:
162
+ print(f"Translation error: {e}", file=sys.stderr)
163
+ return text, False
164
+
165
+
166
+ def translate_auto(text: str, target_lang: str) -> Tuple[str, bool, Optional[str]]:
167
+ """
168
+ Translate text to target language with auto-detection of source language.
169
+
170
+ Args:
171
+ text: Text to translate
172
+ target_lang: Target language (name or code)
173
+
174
+ Returns:
175
+ Tuple of (translated_text, success, detected_source_lang)
176
+ """
177
+ if not text or not text.strip():
178
+ return text, False, None
179
+
180
+ # Detect source language
181
+ detected = detect_language(text)
182
+
183
+ # Convert target to code
184
+ target_code = get_lang_code(target_lang)
185
+
186
+ # Skip if detected language matches target
187
+ if detected and detected == target_code:
188
+ return text, False, detected
189
+
190
+ # Translate
191
+ translated, success = translate(text, target_lang, source_lang=detected or 'en')
192
+ return translated, success, detected
193
+
194
+
195
+ def main():
196
+ """CLI entry point for translator."""
197
+ if len(sys.argv) < 3:
198
+ print("Usage: translator.py <text> <target_language> [source_language]", file=sys.stderr)
199
+ print(" translator.py detect <text>", file=sys.stderr)
200
+ print("", file=sys.stderr)
201
+ print("Examples:", file=sys.stderr)
202
+ print(" translator.py 'Hello world' spanish", file=sys.stderr)
203
+ print(" translator.py 'Hello world' es en", file=sys.stderr)
204
+ print(" translator.py detect 'Hola mundo'", file=sys.stderr)
205
+ sys.exit(1)
206
+
207
+ command = sys.argv[1]
208
+
209
+ # Detection mode
210
+ if command == 'detect':
211
+ if len(sys.argv) < 3:
212
+ print("Usage: translator.py detect <text>", file=sys.stderr)
213
+ sys.exit(1)
214
+ text = sys.argv[2]
215
+ detected = detect_language(text)
216
+ if detected:
217
+ print(detected)
218
+ else:
219
+ print("unknown")
220
+ sys.exit(0)
221
+
222
+ # Translation mode
223
+ text = sys.argv[1]
224
+ target_lang = sys.argv[2]
225
+ source_lang = sys.argv[3] if len(sys.argv) > 3 else 'en'
226
+
227
+ translated, success = translate(text, target_lang, source_lang)
228
+
229
+ # Output the result (for shell script consumption)
230
+ print(translated)
231
+
232
+ # Exit with appropriate code
233
+ sys.exit(0 if success else 1)
234
+
235
+
236
+ if __name__ == '__main__':
237
+ main()
File without changes
File without changes
File without changes
@@ -513,6 +513,12 @@ case "$1" in
513
513
  fi
514
514
  fi
515
515
 
516
+ # Normalize to canonical path so ls output is consistent across platforms
517
+ # (Git Bash resolves /tmp→/c/Users/..., macOS has /var→/private/var)
518
+ if [[ -d "$AUDIO_DIR" ]]; then
519
+ AUDIO_DIR=$(cd "$AUDIO_DIR" && pwd -P)
520
+ fi
521
+
516
522
  # Default to replay last audio (N=1)
517
523
  N="${2:-1}"
518
524
 
@@ -62,15 +62,20 @@ if (-not $VoiceName) {
62
62
  }
63
63
 
64
64
  # Security: Validate voice name to prevent path traversal
65
- # Only allow alphanumeric, underscore, hyphen, and period
66
- if ($VoiceName -notmatch '^[a-zA-Z0-9_\-\.]+$') {
65
+ # Format: <model> or <model>::<speaker> for multi-speaker Piper models
66
+ # Only allow alphanumeric, underscore, hyphen, period, and the :: separator
67
+ if ($VoiceName -notmatch '^[a-zA-Z0-9_\-\.]+(::[a-zA-Z0-9_\-\.]+)?$') {
67
68
  Write-Host "[ERROR] Invalid voice name: $VoiceName" -ForegroundColor Red
68
69
  exit 1
69
70
  }
70
71
 
72
+ # Extract model name and optional speaker (multi-speaker format: model::Speaker)
73
+ $VoiceModel = ($VoiceName -split '::')[0]
74
+ $SpeakerName = if ($VoiceName -match '::(.+)$') { $Matches[1] } else { "" }
75
+
71
76
  # Resolve voice model path and validate it stays within VoicesDir
72
- $VoiceModelFile = [System.IO.Path]::GetFullPath("$VoicesDir\$VoiceName.onnx")
73
- $VoiceJsonFile = [System.IO.Path]::GetFullPath("$VoicesDir\$VoiceName.onnx.json")
77
+ $VoiceModelFile = [System.IO.Path]::GetFullPath("$VoicesDir\$VoiceModel.onnx")
78
+ $VoiceJsonFile = [System.IO.Path]::GetFullPath("$VoicesDir\$VoiceModel.onnx.json")
74
79
  $ResolvedVoicesDir = [System.IO.Path]::GetFullPath($VoicesDir)
75
80
  if (-not $VoiceModelFile.StartsWith($ResolvedVoicesDir)) {
76
81
  Write-Host "[ERROR] Voice path outside voices directory" -ForegroundColor Red
@@ -79,15 +84,15 @@ if (-not $VoiceModelFile.StartsWith($ResolvedVoicesDir)) {
79
84
 
80
85
  # Check if voice model exists, download if missing
81
86
  if (-not (Test-Path $VoiceModelFile)) {
82
- Write-Host "[DOWNLOAD] Voice model: $VoiceName" -ForegroundColor Yellow
87
+ Write-Host "[DOWNLOAD] Voice model: $VoiceModel" -ForegroundColor Yellow
83
88
 
84
89
  # Try to download from Hugging Face
85
90
  # Voice name format: {lang}_{region}-{speaker}-{quality}
86
91
  # HF path format: {lang}/{lang}_{region}/{speaker}/{quality}/{voicename}.onnx
87
92
  try {
88
- # Parse voice name to build correct HF path
93
+ # Parse model name to build correct HF path (strip ::Speaker suffix first)
89
94
  # e.g. en_US-ryan-high -> en/en_US/ryan/high/en_US-ryan-high.onnx
90
- if ($VoiceName -match '^([a-z]{2})_([A-Z]{2})-([a-zA-Z0-9_]+)-([a-z]+)$') {
95
+ if ($VoiceModel -match '^([a-z]{2})_([A-Z]{2})-([a-zA-Z0-9_]+)-([a-z]+)$') {
91
96
  $Lang = $Matches[1]
92
97
  $LangRegion = "$($Matches[1])_$($Matches[2])"
93
98
  $Speaker = $Matches[3]
@@ -97,8 +102,8 @@ if (-not (Test-Path $VoiceModelFile)) {
97
102
  # Fallback for non-standard voice names
98
103
  $HFBase = "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/ryan/high"
99
104
  }
100
- $ModelUrl = "$HFBase/$VoiceName.onnx"
101
- $JsonUrl = "$HFBase/$VoiceName.onnx.json"
105
+ $ModelUrl = "$HFBase/$VoiceModel.onnx"
106
+ $JsonUrl = "$HFBase/$VoiceModel.onnx.json"
102
107
 
103
108
  Write-Host " Downloading model..." -ForegroundColor Cyan
104
109
  Invoke-WebRequest -Uri $ModelUrl -OutFile $VoiceModelFile -ErrorAction Stop
@@ -127,11 +132,10 @@ $AudioFile = "$AudioDir\tts-$Timestamp.wav"
127
132
  try {
128
133
  Write-Host "[SYNTH] Synthesizing with Piper..." -ForegroundColor Cyan
129
134
 
130
- # Run Piper with text input
131
- $Text | & $PiperExe `
132
- --model $VoiceModelFile `
133
- --output-file $AudioFile `
134
- 2>$null
135
+ # Run Piper with text input; pass --speaker for multi-speaker models (model::Speaker format)
136
+ $piperArgs = @('--model', $VoiceModelFile, '--output-file', $AudioFile)
137
+ if ($SpeakerName) { $piperArgs += @('--speaker', $SpeakerName) }
138
+ $Text | & $PiperExe @piperArgs 2>$null
135
139
 
136
140
  if (-not (Test-Path $AudioFile)) {
137
141
  Write-Host "[ERROR] Piper synthesis failed" -ForegroundColor Red
@@ -151,12 +155,14 @@ try {
151
155
  if (-not $env:AGENTVIBES_NO_PLAY) {
152
156
  # Prefer ffplay: handles 22050 Hz → 48000 Hz resampling cleanly (SoundPlayer uses
153
157
  # WinMM's low-quality resampler which produces choppy audio at non-native rates).
154
- $ffplayPath = (Get-Command ffplay -ErrorAction SilentlyContinue)?.Source
158
+ $ffplayCmd = Get-Command ffplay -ErrorAction SilentlyContinue
159
+ $ffplayPath = if ($ffplayCmd) { $ffplayCmd.Source }
155
160
  if (-not $ffplayPath) {
156
161
  # SSH/watcher sessions may have a minimal PATH — refresh from registry
157
162
  $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" +
158
163
  [System.Environment]::GetEnvironmentVariable("Path","User")
159
- $ffplayPath = (Get-Command ffplay -ErrorAction SilentlyContinue)?.Source
164
+ $ffplayCmd = Get-Command ffplay -ErrorAction SilentlyContinue
165
+ $ffplayPath = if ($ffplayCmd) { $ffplayCmd.Source }
160
166
  }
161
167
  if ($ffplayPath) {
162
168
  & $ffplayPath -autoexit -nodisp -loglevel quiet $AudioFile 2>$null