agentvibes 4.2.0 → 4.4.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 (219) hide show
  1. package/.agentvibes/bmad/bmad-voices.md +69 -69
  2. package/.agentvibes/config.json +12 -0
  3. package/.claude/activation-instructions +54 -54
  4. package/.claude/audio/tracks/README.md +52 -52
  5. package/.claude/commands/agent-vibes/add.md +21 -21
  6. package/.claude/commands/agent-vibes/agent-vibes.md +101 -101
  7. package/.claude/commands/agent-vibes/agent.md +79 -79
  8. package/.claude/commands/agent-vibes/background-music.md +111 -111
  9. package/.claude/commands/agent-vibes/bmad.md +198 -198
  10. package/.claude/commands/agent-vibes/clean.md +18 -18
  11. package/.claude/commands/agent-vibes/cleanup.md +18 -18
  12. package/.claude/commands/agent-vibes/commands.json +145 -145
  13. package/.claude/commands/agent-vibes/effects.md +97 -97
  14. package/.claude/commands/agent-vibes/get.md +9 -9
  15. package/.claude/commands/agent-vibes/hide.md +91 -91
  16. package/.claude/commands/agent-vibes/language.md +23 -23
  17. package/.claude/commands/agent-vibes/learn.md +67 -67
  18. package/.claude/commands/agent-vibes/list.md +13 -13
  19. package/.claude/commands/agent-vibes/mute.md +37 -37
  20. package/.claude/commands/agent-vibes/preview.md +17 -17
  21. package/.claude/commands/agent-vibes/provider.md +68 -68
  22. package/.claude/commands/agent-vibes/replay-target.md +14 -14
  23. package/.claude/commands/agent-vibes/sample.md +12 -12
  24. package/.claude/commands/agent-vibes/set-favorite-voice.md +84 -84
  25. package/.claude/commands/agent-vibes/set-pretext.md +65 -65
  26. package/.claude/commands/agent-vibes/set-speed.md +41 -41
  27. package/.claude/commands/agent-vibes/show.md +84 -84
  28. package/.claude/commands/agent-vibes/switch.md +87 -87
  29. package/.claude/commands/agent-vibes/target-voice.md +26 -26
  30. package/.claude/commands/agent-vibes/target.md +30 -30
  31. package/.claude/commands/agent-vibes/translate.md +68 -68
  32. package/.claude/commands/agent-vibes/unmute.md +45 -45
  33. package/.claude/commands/agent-vibes/verbosity.md +89 -89
  34. package/.claude/commands/agent-vibes/whoami.md +7 -7
  35. package/.claude/commands/agent-vibes-bmad-voices.md +117 -117
  36. package/.claude/commands/agent-vibes-rdp.md +24 -24
  37. package/.claude/config/agentvibes.json +1 -0
  38. package/.claude/config/audio-effects.cfg +2 -2
  39. package/.claude/config/audio-effects.cfg.sample +52 -52
  40. package/.claude/config/background-music-volume.txt +1 -0
  41. package/.claude/config/intro-text.txt +1 -0
  42. package/.claude/config/piper-speech-rate.txt +4 -0
  43. package/.claude/config/piper-target-speech-rate.txt +1 -0
  44. package/.claude/config/reverb-level.txt +1 -0
  45. package/.claude/config/tts-speech-rate.txt +4 -0
  46. package/.claude/config/tts-target-speech-rate.txt +1 -0
  47. package/.claude/docs/TERMUX_SETUP.md +408 -408
  48. package/.claude/github-star-reminder.txt +1 -1
  49. package/.claude/hooks/README-TTS-QUEUE.md +135 -135
  50. package/.claude/hooks/audio-cache-utils.sh +246 -246
  51. package/.claude/hooks/audio-processor.sh +433 -433
  52. package/.claude/hooks/background-music-manager.sh +404 -404
  53. package/.claude/hooks/bmad-speak-enhanced.sh +165 -165
  54. package/.claude/hooks/bmad-speak.sh +269 -269
  55. package/.claude/hooks/bmad-tts-injector.sh +568 -568
  56. package/.claude/hooks/bmad-voice-manager.sh +928 -928
  57. package/.claude/hooks/clawdbot-receiver-SECURE.sh +129 -129
  58. package/.claude/hooks/clawdbot-receiver.sh +107 -107
  59. package/.claude/hooks/clean-audio-cache.sh +22 -22
  60. package/.claude/hooks/cleanup-cache.sh +106 -106
  61. package/.claude/hooks/configure-rdp-mode.sh +137 -137
  62. package/.claude/hooks/download-extra-voices.sh +244 -244
  63. package/.claude/hooks/effects-manager.sh +268 -268
  64. package/.claude/hooks/github-star-reminder.sh +154 -154
  65. package/.claude/hooks/language-manager.sh +362 -362
  66. package/.claude/hooks/learn-manager.sh +492 -492
  67. package/.claude/hooks/macos-voice-manager.sh +205 -205
  68. package/.claude/hooks/migrate-background-music.sh +125 -125
  69. package/.claude/hooks/migrate-to-agentvibes.sh +161 -161
  70. package/.claude/hooks/optimize-background-music.sh +87 -87
  71. package/.claude/hooks/path-resolver.sh +60 -60
  72. package/.claude/hooks/personality-manager.sh +448 -448
  73. package/.claude/hooks/piper-download-voices.sh +225 -225
  74. package/.claude/hooks/piper-installer.sh +292 -292
  75. package/.claude/hooks/piper-multispeaker-registry.sh +171 -171
  76. package/.claude/hooks/piper-voice-manager.sh +24 -3
  77. package/.claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh +90 -90
  78. package/.claude/hooks/play-tts-enhanced.sh +105 -105
  79. package/.claude/hooks/play-tts-macos.sh +368 -368
  80. package/.claude/hooks/play-tts-piper.sh +679 -679
  81. package/.claude/hooks/play-tts-soprano.sh +356 -356
  82. package/.claude/hooks/play-tts-ssh-remote.sh +167 -167
  83. package/.claude/hooks/play-tts-termux-ssh.sh +169 -169
  84. package/.claude/hooks/play-tts.sh +301 -301
  85. package/.claude/hooks/prepare-release.sh +54 -54
  86. package/.claude/hooks/provider-commands.sh +617 -617
  87. package/.claude/hooks/provider-manager.sh +399 -399
  88. package/.claude/hooks/replay-target-audio.sh +95 -95
  89. package/.claude/hooks/requirements.txt +6 -6
  90. package/.claude/hooks/sentiment-manager.sh +201 -201
  91. package/.claude/hooks/session-start-tts.sh +81 -81
  92. package/.claude/hooks/soprano-gradio-synth.py +139 -139
  93. package/.claude/hooks/speed-manager.sh +291 -291
  94. package/.claude/hooks/stop-tts.sh +84 -84
  95. package/.claude/hooks/termux-installer.sh +261 -261
  96. package/.claude/hooks/translate-manager.sh +341 -341
  97. package/.claude/hooks/translator.py +237 -237
  98. package/.claude/hooks/tts-queue-worker.sh +145 -145
  99. package/.claude/hooks/tts-queue.sh +165 -165
  100. package/.claude/hooks/verbosity-manager.sh +178 -178
  101. package/.claude/hooks/voice-manager.sh +548 -548
  102. package/.claude/hooks-windows/audio-cache-utils.ps1 +119 -119
  103. package/.claude/hooks-windows/background-music-manager.ps1 +348 -0
  104. package/.claude/hooks-windows/clean-audio-cache.ps1 +53 -0
  105. package/.claude/hooks-windows/download-extra-voices.ps1 +185 -0
  106. package/.claude/hooks-windows/effects-manager.ps1 +294 -0
  107. package/.claude/hooks-windows/language-manager.ps1 +193 -0
  108. package/.claude/hooks-windows/learn-manager.ps1 +241 -0
  109. package/.claude/hooks-windows/personality-manager.ps1 +266 -0
  110. package/.claude/hooks-windows/play-tts-piper.ps1 +209 -0
  111. package/.claude/hooks-windows/play-tts-sapi.ps1 +108 -0
  112. package/.claude/hooks-windows/play-tts-soprano.ps1 +159 -158
  113. package/.claude/hooks-windows/play-tts-windows-piper.ps1 +50 -5
  114. package/.claude/hooks-windows/play-tts-windows-sapi.ps1 +108 -108
  115. package/.claude/hooks-windows/play-tts.ps1 +344 -266
  116. package/.claude/hooks-windows/provider-manager.ps1 +29 -10
  117. package/.claude/hooks-windows/session-start-tts.ps1 +124 -124
  118. package/.claude/hooks-windows/soprano-gradio-synth.py +153 -153
  119. package/.claude/hooks-windows/speed-manager.ps1 +166 -0
  120. package/.claude/hooks-windows/verbosity-manager.ps1 +119 -0
  121. package/.claude/hooks-windows/voice-manager-windows.ps1 +92 -8
  122. package/.claude/output-styles/agent-vibes.md +202 -202
  123. package/.claude/personalities/angry.md +14 -14
  124. package/.claude/personalities/annoying.md +14 -14
  125. package/.claude/personalities/crass.md +14 -14
  126. package/.claude/personalities/dramatic.md +14 -14
  127. package/.claude/personalities/dry-humor.md +50 -50
  128. package/.claude/personalities/flirty.md +20 -20
  129. package/.claude/personalities/funny.md +14 -14
  130. package/.claude/personalities/grandpa.md +32 -32
  131. package/.claude/personalities/millennial.md +14 -14
  132. package/.claude/personalities/moody.md +14 -14
  133. package/.claude/personalities/normal.md +16 -16
  134. package/.claude/personalities/pirate.md +14 -14
  135. package/.claude/personalities/poetic.md +14 -14
  136. package/.claude/personalities/professional.md +14 -14
  137. package/.claude/personalities/rapper.md +55 -55
  138. package/.claude/personalities/robot.md +14 -14
  139. package/.claude/personalities/sarcastic.md +38 -38
  140. package/.claude/personalities/sassy.md +14 -14
  141. package/.claude/personalities/surfer-dude.md +14 -14
  142. package/.claude/personalities/zen.md +14 -14
  143. package/.claude/settings.json +15 -15
  144. package/.claude/verbosity.txt +1 -1
  145. package/.clawdbot/README.md +105 -105
  146. package/.clawdbot/skill/SKILL.md +241 -241
  147. package/.mcp.json +12 -0
  148. package/CLAUDE.md +170 -170
  149. package/README.md +2029 -2007
  150. package/RELEASE_NOTES.md +1310 -1203
  151. package/WINDOWS-SETUP.md +208 -208
  152. package/bin/agent-vibes +39 -39
  153. package/bin/agentvibes-voice-browser.js +1840 -1840
  154. package/bin/agentvibes.js +48 -2
  155. package/bin/mcp-server.js +121 -121
  156. package/bin/mcp-server.sh +206 -206
  157. package/bin/test-bmad-pr +78 -78
  158. package/mcp-server/QUICK_START.md +203 -203
  159. package/mcp-server/README.md +345 -345
  160. package/mcp-server/WINDOWS_SETUP.md +260 -260
  161. package/mcp-server/docs/troubleshooting-audio.md +313 -313
  162. package/mcp-server/examples/claude_desktop_config.json +11 -11
  163. package/mcp-server/examples/claude_desktop_config_piper.json +9 -9
  164. package/mcp-server/examples/custom_instructions.md +169 -169
  165. package/mcp-server/install-deps.js +130 -130
  166. package/mcp-server/pyproject.toml +52 -52
  167. package/mcp-server/requirements.txt +2 -2
  168. package/mcp-server/server.py +1465 -1453
  169. package/mcp-server/test_server.py +395 -395
  170. package/mcp-server/test_windows_script_parity.py +336 -0
  171. package/package.json +110 -110
  172. package/setup-windows.ps1 +815 -815
  173. package/src/bmad-detector.js +71 -71
  174. package/src/cli/list-personalities.js +110 -110
  175. package/src/cli/list-voices.js +114 -114
  176. package/src/commands/bmad-voices.js +394 -394
  177. package/src/commands/install-mcp.js +476 -476
  178. package/src/console/app.js +824 -824
  179. package/src/console/audio-env.js +20 -1
  180. package/src/console/brand-colors.js +13 -13
  181. package/src/console/constants/personalities.js +44 -44
  182. package/src/console/footer-config.js +50 -50
  183. package/src/console/modals/modal-overlay.js +247 -247
  184. package/src/console/navigation.js +62 -62
  185. package/src/console/tabs/agents-tab.js +1684 -1516
  186. package/src/console/tabs/help-tab.js +261 -261
  187. package/src/console/tabs/install-tab.js +1007 -991
  188. package/src/console/tabs/music-tab.js +22 -8
  189. package/src/console/tabs/placeholder-tab.js +53 -53
  190. package/src/console/tabs/readme-tab.js +267 -267
  191. package/src/console/tabs/receiver-tab.js +1472 -1212
  192. package/src/console/tabs/settings-tab.js +152 -79
  193. package/src/console/tabs/voices-tab.js +100 -21
  194. package/src/console/widgets/destroy-list.js +25 -25
  195. package/src/console/widgets/format-utils.js +89 -89
  196. package/src/console/widgets/notice.js +55 -55
  197. package/src/console/widgets/personality-picker.js +185 -185
  198. package/src/console/widgets/reverb-picker.js +94 -94
  199. package/src/console/widgets/track-picker.js +285 -285
  200. package/src/installer/music-file-input.js +304 -304
  201. package/src/installer.js +5882 -5829
  202. package/src/services/agent-voice-store.js +423 -423
  203. package/src/services/config-service.js +264 -264
  204. package/src/services/navigation-service.js +123 -123
  205. package/src/services/provider-service.js +132 -132
  206. package/src/services/verbosity-service.js +157 -157
  207. package/src/utils/audio-duration-validator.js +298 -298
  208. package/src/utils/audio-format-validator.js +277 -277
  209. package/src/utils/dependency-checker.js +469 -466
  210. package/src/utils/file-ownership-verifier.js +358 -358
  211. package/src/utils/list-formatter.js +194 -194
  212. package/src/utils/music-file-validator.js +285 -285
  213. package/src/utils/preview-list-prompt.js +136 -136
  214. package/src/utils/provider-validator.js +96 -12
  215. package/src/utils/secure-music-storage.js +412 -412
  216. package/templates/agentvibes-receiver.sh +482 -482
  217. package/templates/audio/welcome-music.mp3 +0 -0
  218. package/voice-assignments.json +8244 -8244
  219. package/.claude/config/background-music-position.txt +0 -1
@@ -1,395 +1,395 @@
1
- #!/usr/bin/env python3
2
- """
3
- File: mcp-server/test_server.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, including but not limited to the warranties of
26
- merchantability, fitness for a particular purpose and noninfringement.
27
- In no event shall the authors or copyright holders be liable for any claim,
28
- damages or other liability, whether in an action of contract, tort or
29
- otherwise, arising from, out of or in connection with the software or the
30
- use or other dealings in the software.
31
-
32
- ---
33
-
34
- @fileoverview Test suite for AgentVibes MCP Server
35
- @context Validates that MCP server can be imported and initialized correctly
36
- @architecture Unit tests for server initialization, imports, and helper methods
37
- @dependencies mcp-server/server.py, Python unittest framework
38
- @entrypoints Run directly via `python test_server.py` or from test runners
39
- @patterns Test-driven validation, import testing, path resolution testing
40
- @related mcp-server/server.py, docs/ai-optimized-documentation-standards.md
41
- """
42
-
43
- import os
44
- import sys
45
- from pathlib import Path
46
-
47
- # Add the mcp-server directory to path
48
- sys.path.insert(0, str(Path(__file__).parent))
49
-
50
-
51
- def test_imports():
52
- """Test that all required modules can be imported"""
53
- print("Testing imports...")
54
- try:
55
- from server import AgentVibesServer, app
56
- print("✅ Server imports successful")
57
- return True
58
- except ImportError as e:
59
- print(f"❌ Import failed: {e}")
60
- print(" Note: MCP library may not be installed")
61
- print(" Run: pip install mcp")
62
- return False
63
-
64
-
65
- def test_server_init():
66
- """Test that the server can be initialized"""
67
- print("\nTesting server initialization...")
68
- try:
69
- from server import AgentVibesServer
70
-
71
- server = AgentVibesServer()
72
- print(f"✅ Server initialized")
73
- print(f" Claude dir: {server.claude_dir}")
74
- print(f" Hooks dir: {server.hooks_dir}")
75
-
76
- # Check if hooks directory exists
77
- if server.hooks_dir.exists():
78
- print(f"✅ Hooks directory found")
79
- else:
80
- print(f"⚠️ Hooks directory not found (expected for testing)")
81
-
82
- return True
83
- except Exception as e:
84
- print(f"❌ Server init failed: {e}")
85
- return False
86
-
87
-
88
- def test_helper_methods():
89
- """Test helper methods"""
90
- print("\nTesting helper methods...")
91
- try:
92
- from server import AgentVibesServer
93
-
94
- server = AgentVibesServer()
95
-
96
- # Test _find_claude_dir
97
- claude_dir = server._find_claude_dir()
98
- print(f"✅ Found claude directory: {claude_dir}")
99
-
100
- return True
101
- except Exception as e:
102
- print(f"❌ Helper method test failed: {e}")
103
- return False
104
-
105
-
106
- def test_mute_unmute():
107
- """Test mute/unmute functionality"""
108
- print("\nTesting mute/unmute functionality...")
109
- try:
110
- from server import AgentVibesServer
111
- import asyncio
112
- import tempfile
113
- import os
114
-
115
- server = AgentVibesServer()
116
-
117
- # Use a temporary home directory for testing
118
- original_home = Path.home()
119
- test_mute_file = original_home / ".agentvibes-muted"
120
-
121
- # Clean up any existing mute file before testing
122
- if test_mute_file.exists():
123
- test_mute_file.unlink()
124
- print(" Cleaned up existing mute file")
125
-
126
- # Test 1: Initial state should be unmuted
127
- async def run_tests():
128
- result = await server.is_muted()
129
- assert "ACTIVE" in result, f"Expected TTS to be active initially, got: {result}"
130
- print("✅ Test 1: Initial state is unmuted")
131
-
132
- # Test 2: Mute should create the mute file
133
- result = await server.mute()
134
- assert "muted" in result.lower(), f"Expected mute confirmation, got: {result}"
135
- assert test_mute_file.exists(), "Mute file should exist after muting"
136
- print("✅ Test 2: Mute creates mute file")
137
-
138
- # Test 3: is_muted should report muted state
139
- result = await server.is_muted()
140
- assert "MUTED" in result, f"Expected TTS to be muted, got: {result}"
141
- print("✅ Test 3: is_muted correctly reports muted state")
142
-
143
- # Test 4: Unmute should remove the mute file
144
- result = await server.unmute()
145
- assert "unmuted" in result.lower() or "restored" in result.lower(), f"Expected unmute confirmation, got: {result}"
146
- assert not test_mute_file.exists(), "Mute file should not exist after unmuting"
147
- print("✅ Test 4: Unmute removes mute file")
148
-
149
- # Test 5: is_muted should report active state after unmute
150
- result = await server.is_muted()
151
- assert "ACTIVE" in result, f"Expected TTS to be active after unmute, got: {result}"
152
- print("✅ Test 5: is_muted correctly reports active state after unmute")
153
-
154
- # Test 6: Unmute when not muted should handle gracefully
155
- result = await server.unmute()
156
- assert "not muted" in result.lower() or "active" in result.lower(), f"Expected graceful handling, got: {result}"
157
- print("✅ Test 6: Unmute handles already-unmuted state gracefully")
158
-
159
- asyncio.run(run_tests())
160
-
161
- # Clean up
162
- if test_mute_file.exists():
163
- test_mute_file.unlink()
164
-
165
- print("✅ All mute/unmute tests passed")
166
- return True
167
-
168
- except AssertionError as e:
169
- print(f"❌ Assertion failed: {e}")
170
- # Clean up on failure
171
- test_mute_file = Path.home() / ".agentvibes-muted"
172
- if test_mute_file.exists():
173
- test_mute_file.unlink()
174
- return False
175
- except Exception as e:
176
- print(f"❌ Mute/unmute test failed: {e}")
177
- # Clean up on failure
178
- test_mute_file = Path.home() / ".agentvibes-muted"
179
- if test_mute_file.exists():
180
- test_mute_file.unlink()
181
- return False
182
-
183
-
184
- def test_play_tts_mute_check():
185
- """Test that play-tts script respects mute files (platform-aware)"""
186
- import platform
187
- is_windows = platform.system() == "Windows" and not os.environ.get("WSL_DISTRO_NAME")
188
-
189
- if is_windows:
190
- print("\nTesting play-tts.ps1 mute file detection...")
191
- script_path = Path(__file__).parent.parent / ".claude" / "hooks-windows" / "play-tts.ps1"
192
- shell_cmd = lambda s, msg: ["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", str(s), msg]
193
- else:
194
- print("\nTesting play-tts.sh mute file detection...")
195
- script_path = Path(__file__).parent.parent / ".claude" / "hooks" / "play-tts.sh"
196
- shell_cmd = lambda s, msg: ["bash", str(s), msg]
197
-
198
- try:
199
- import subprocess
200
-
201
- if not script_path.exists():
202
- print(f"⚠️ {script_path.name} not found at {script_path}, skipping shell test")
203
- return True # Not a failure, just can't test
204
-
205
- # On Windows, play-tts.ps1 checks .claude/tts-muted.txt for "true"
206
- # On Unix, play-tts.sh checks ~/.agentvibes-muted file existence
207
- if is_windows:
208
- test_mute_file = Path.home() / ".claude" / "tts-muted.txt"
209
- test_mute_file.parent.mkdir(parents=True, exist_ok=True)
210
- else:
211
- test_mute_file = Path.home() / ".agentvibes-muted"
212
-
213
- # Clean up first
214
- if test_mute_file.exists():
215
- test_mute_file.unlink()
216
-
217
- # Test 1: With mute file, script should exit early silently
218
- if is_windows:
219
- test_mute_file.write_text("true")
220
- else:
221
- test_mute_file.touch()
222
- try:
223
- result = subprocess.run(
224
- shell_cmd(script_path, "Test message"),
225
- capture_output=True,
226
- text=True,
227
- timeout=5
228
- )
229
- output = result.stdout + result.stderr
230
- # When muted, script should exit 0 silently (no TTS output)
231
- assert result.returncode == 0, f"Expected exit code 0 when muted, got {result.returncode}"
232
- print(f"✅ Test 1: {script_path.name} respects mute file (exit code 0)")
233
- finally:
234
- if test_mute_file.exists():
235
- test_mute_file.unlink()
236
-
237
- # Test 2: Without mute file, script should proceed (we won't check full TTS, just that it doesn't say muted)
238
- # Note: This test may produce audio output
239
- print("✅ Test 2: Skipping audio test (would produce sound)")
240
-
241
- print(f"✅ {script_path.name} mute detection tests passed")
242
- return True
243
-
244
- except subprocess.TimeoutExpired:
245
- print("⚠️ Script timed out (might be running TTS)")
246
- # Clean up both possible mute files
247
- for f in [Path.home() / ".agentvibes-muted", Path.home() / ".claude" / "tts-muted.txt"]:
248
- if f.exists():
249
- f.unlink()
250
- return True # Timeout is acceptable if TTS is running
251
- except Exception as e:
252
- print(f"❌ {script_path.name} mute test failed: {e}")
253
- # Clean up both possible mute files
254
- for f in [Path.home() / ".agentvibes-muted", Path.home() / ".claude" / "tts-muted.txt"]:
255
- if f.exists():
256
- f.unlink()
257
- return False
258
-
259
-
260
- def test_set_provider():
261
- """Test set_provider MCP functionality (platform-aware)"""
262
- print("\nTesting set_provider MCP functionality...")
263
- try:
264
- from server import AgentVibesServer
265
- import asyncio
266
- import platform
267
- from pathlib import Path
268
-
269
- server = AgentVibesServer()
270
- is_windows = platform.system() == "Windows" and not os.environ.get("WSL_DISTRO_NAME")
271
-
272
- # Test provider switching with platform-appropriate providers
273
- async def run_tests():
274
- if is_windows:
275
- # Windows providers
276
- result = await server.set_provider("windows-sapi")
277
- assert "OK" in result or "switched" in result.lower(), f"Expected success switching to windows-sapi, got: {result}"
278
- print("✅ Test 1: Successfully switched to windows-sapi")
279
-
280
- result = await server.set_provider("windows-piper")
281
- assert "OK" in result or "switched" in result.lower() or "WARNING" in result, f"Expected success/warning switching to windows-piper, got: {result}"
282
- print("✅ Test 2: Switched to windows-piper (or warned if not installed)")
283
-
284
- result = await server.set_provider("soprano")
285
- assert "OK" in result or "switched" in result.lower() or "Script not found" in result, f"Expected success switching to soprano, got: {result}"
286
- print("✅ Test 3: Switched to soprano (or script not found)")
287
- else:
288
- # Unix providers
289
- result = await server.set_provider("piper")
290
- assert "\u2713" in result or "switched" in result.lower(), f"Expected success switching to piper, got: {result}"
291
- print("✅ Test 1: Successfully switched to piper")
292
-
293
- result = await server.set_provider("macos")
294
- assert "\u2713" in result or "switched" in result.lower(), f"Expected success switching to macos, got: {result}"
295
- print("✅ Test 2: Successfully switched to macos")
296
-
297
- result = await server.set_provider("termux-ssh")
298
- assert "\u2713" in result or "switched" in result.lower(), f"Expected success switching to termux-ssh, got: {result}"
299
- print("✅ Test 3: Successfully switched to termux-ssh")
300
-
301
- # Test 4: Invalid provider should fail
302
- result = await server.set_provider("invalid-provider")
303
- assert "Invalid" in result or "Error" in result, f"Expected error for invalid provider, got: {result}"
304
- print("✅ Test 4: Correctly rejected invalid provider")
305
-
306
- # Test 5: Case insensitivity
307
- test_provider = "WINDOWS-SAPI" if is_windows else "PIPER"
308
- expected_lower = "windows-sapi" if is_windows else "piper"
309
- result = await server.set_provider(test_provider)
310
- assert "OK" in result or "\u2713" in result or "switched" in result.lower(), f"Expected success with uppercase {test_provider}, got: {result}"
311
- print("✅ Test 5: Case-insensitive provider switching works")
312
-
313
- # Test 6: Verify provider file was written
314
- # On Windows, provider-manager.ps1 writes to $USERPROFILE\.claude\
315
- # On Unix, provider-manager.sh may write to project dir or home dir
316
- if is_windows:
317
- provider_file = Path.home() / ".claude" / "tts-provider.txt"
318
- else:
319
- provider_file = server.claude_dir / "tts-provider.txt"
320
- if not provider_file.exists():
321
- provider_file = Path.home() / ".claude" / "tts-provider.txt"
322
-
323
- if provider_file.exists():
324
- # Strip BOM that PowerShell's Set-Content may add on Windows
325
- provider_content = provider_file.read_text(encoding="utf-8-sig").strip()
326
- assert provider_content == expected_lower, f"Expected provider file to contain '{expected_lower}', got: {provider_content}"
327
- print("✅ Test 6: Provider file correctly written")
328
- else:
329
- print("⚠️ Test 6: Provider file not found (may be in project dir)")
330
-
331
- asyncio.run(run_tests())
332
-
333
- print("✅ All set_provider tests passed")
334
- return True
335
-
336
- except AssertionError as e:
337
- print(f"❌ Assertion failed: {e}")
338
- return False
339
- except Exception as e:
340
- print(f"❌ set_provider test failed: {e}")
341
- import traceback
342
- traceback.print_exc()
343
- return False
344
-
345
-
346
- def main():
347
- """Run all tests"""
348
- print("=" * 60)
349
- print("AgentVibes MCP Server Test Suite")
350
- print("=" * 60)
351
-
352
- tests = [
353
- ("Imports", test_imports),
354
- ("Server Initialization", test_server_init),
355
- ("Helper Methods", test_helper_methods),
356
- ("Mute/Unmute Functionality", test_mute_unmute),
357
- ("play-tts Mute Detection", test_play_tts_mute_check),
358
- ("set_provider MCP Function", test_set_provider),
359
- ]
360
-
361
- results = []
362
- for name, test_func in tests:
363
- try:
364
- result = test_func()
365
- results.append((name, result))
366
- except Exception as e:
367
- print(f"\n❌ Test '{name}' crashed: {e}")
368
- results.append((name, False))
369
-
370
- print("\n" + "=" * 60)
371
- print("Test Results Summary")
372
- print("=" * 60)
373
-
374
- passed = sum(1 for _, result in results if result)
375
- total = len(results)
376
-
377
- for name, result in results:
378
- status = "✅ PASS" if result else "❌ FAIL"
379
- print(f"{status} - {name}")
380
-
381
- print("=" * 60)
382
- print(f"Total: {passed}/{total} tests passed")
383
-
384
- if passed == total:
385
- print("\n🎉 All tests passed!")
386
- return 0
387
- else:
388
- print(f"\n⚠️ {total - passed} test(s) failed")
389
- print("\nNote: Some failures may be expected if MCP library is not installed")
390
- print("Install with: pip install mcp")
391
- return 1
392
-
393
-
394
- if __name__ == "__main__":
395
- sys.exit(main())
1
+ #!/usr/bin/env python3
2
+ """
3
+ File: mcp-server/test_server.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, including but not limited to the warranties of
26
+ merchantability, fitness for a particular purpose and noninfringement.
27
+ In no event shall the authors or copyright holders be liable for any claim,
28
+ damages or other liability, whether in an action of contract, tort or
29
+ otherwise, arising from, out of or in connection with the software or the
30
+ use or other dealings in the software.
31
+
32
+ ---
33
+
34
+ @fileoverview Test suite for AgentVibes MCP Server
35
+ @context Validates that MCP server can be imported and initialized correctly
36
+ @architecture Unit tests for server initialization, imports, and helper methods
37
+ @dependencies mcp-server/server.py, Python unittest framework
38
+ @entrypoints Run directly via `python test_server.py` or from test runners
39
+ @patterns Test-driven validation, import testing, path resolution testing
40
+ @related mcp-server/server.py, docs/ai-optimized-documentation-standards.md
41
+ """
42
+
43
+ import os
44
+ import sys
45
+ from pathlib import Path
46
+
47
+ # Add the mcp-server directory to path
48
+ sys.path.insert(0, str(Path(__file__).parent))
49
+
50
+
51
+ def test_imports():
52
+ """Test that all required modules can be imported"""
53
+ print("Testing imports...")
54
+ try:
55
+ from server import AgentVibesServer, app
56
+ print("✅ Server imports successful")
57
+ return True
58
+ except ImportError as e:
59
+ print(f"❌ Import failed: {e}")
60
+ print(" Note: MCP library may not be installed")
61
+ print(" Run: pip install mcp")
62
+ return False
63
+
64
+
65
+ def test_server_init():
66
+ """Test that the server can be initialized"""
67
+ print("\nTesting server initialization...")
68
+ try:
69
+ from server import AgentVibesServer
70
+
71
+ server = AgentVibesServer()
72
+ print(f"✅ Server initialized")
73
+ print(f" Claude dir: {server.claude_dir}")
74
+ print(f" Hooks dir: {server.hooks_dir}")
75
+
76
+ # Check if hooks directory exists
77
+ if server.hooks_dir.exists():
78
+ print(f"✅ Hooks directory found")
79
+ else:
80
+ print(f"⚠️ Hooks directory not found (expected for testing)")
81
+
82
+ return True
83
+ except Exception as e:
84
+ print(f"❌ Server init failed: {e}")
85
+ return False
86
+
87
+
88
+ def test_helper_methods():
89
+ """Test helper methods"""
90
+ print("\nTesting helper methods...")
91
+ try:
92
+ from server import AgentVibesServer
93
+
94
+ server = AgentVibesServer()
95
+
96
+ # Test _find_claude_dir
97
+ claude_dir = server._find_claude_dir()
98
+ print(f"✅ Found claude directory: {claude_dir}")
99
+
100
+ return True
101
+ except Exception as e:
102
+ print(f"❌ Helper method test failed: {e}")
103
+ return False
104
+
105
+
106
+ def test_mute_unmute():
107
+ """Test mute/unmute functionality"""
108
+ print("\nTesting mute/unmute functionality...")
109
+ try:
110
+ from server import AgentVibesServer
111
+ import asyncio
112
+ import tempfile
113
+ import os
114
+
115
+ server = AgentVibesServer()
116
+
117
+ # Use a temporary home directory for testing
118
+ original_home = Path.home()
119
+ test_mute_file = original_home / ".agentvibes-muted"
120
+
121
+ # Clean up any existing mute file before testing
122
+ if test_mute_file.exists():
123
+ test_mute_file.unlink()
124
+ print(" Cleaned up existing mute file")
125
+
126
+ # Test 1: Initial state should be unmuted
127
+ async def run_tests():
128
+ result = await server.is_muted()
129
+ assert "ACTIVE" in result, f"Expected TTS to be active initially, got: {result}"
130
+ print("✅ Test 1: Initial state is unmuted")
131
+
132
+ # Test 2: Mute should create the mute file
133
+ result = await server.mute()
134
+ assert "muted" in result.lower(), f"Expected mute confirmation, got: {result}"
135
+ assert test_mute_file.exists(), "Mute file should exist after muting"
136
+ print("✅ Test 2: Mute creates mute file")
137
+
138
+ # Test 3: is_muted should report muted state
139
+ result = await server.is_muted()
140
+ assert "MUTED" in result, f"Expected TTS to be muted, got: {result}"
141
+ print("✅ Test 3: is_muted correctly reports muted state")
142
+
143
+ # Test 4: Unmute should remove the mute file
144
+ result = await server.unmute()
145
+ assert "unmuted" in result.lower() or "restored" in result.lower(), f"Expected unmute confirmation, got: {result}"
146
+ assert not test_mute_file.exists(), "Mute file should not exist after unmuting"
147
+ print("✅ Test 4: Unmute removes mute file")
148
+
149
+ # Test 5: is_muted should report active state after unmute
150
+ result = await server.is_muted()
151
+ assert "ACTIVE" in result, f"Expected TTS to be active after unmute, got: {result}"
152
+ print("✅ Test 5: is_muted correctly reports active state after unmute")
153
+
154
+ # Test 6: Unmute when not muted should handle gracefully
155
+ result = await server.unmute()
156
+ assert "not muted" in result.lower() or "active" in result.lower(), f"Expected graceful handling, got: {result}"
157
+ print("✅ Test 6: Unmute handles already-unmuted state gracefully")
158
+
159
+ asyncio.run(run_tests())
160
+
161
+ # Clean up
162
+ if test_mute_file.exists():
163
+ test_mute_file.unlink()
164
+
165
+ print("✅ All mute/unmute tests passed")
166
+ return True
167
+
168
+ except AssertionError as e:
169
+ print(f"❌ Assertion failed: {e}")
170
+ # Clean up on failure
171
+ test_mute_file = Path.home() / ".agentvibes-muted"
172
+ if test_mute_file.exists():
173
+ test_mute_file.unlink()
174
+ return False
175
+ except Exception as e:
176
+ print(f"❌ Mute/unmute test failed: {e}")
177
+ # Clean up on failure
178
+ test_mute_file = Path.home() / ".agentvibes-muted"
179
+ if test_mute_file.exists():
180
+ test_mute_file.unlink()
181
+ return False
182
+
183
+
184
+ def test_play_tts_mute_check():
185
+ """Test that play-tts script respects mute files (platform-aware)"""
186
+ import platform
187
+ is_windows = platform.system() == "Windows" and not os.environ.get("WSL_DISTRO_NAME")
188
+
189
+ if is_windows:
190
+ print("\nTesting play-tts.ps1 mute file detection...")
191
+ script_path = Path(__file__).parent.parent / ".claude" / "hooks-windows" / "play-tts.ps1"
192
+ shell_cmd = lambda s, msg: ["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", str(s), msg]
193
+ else:
194
+ print("\nTesting play-tts.sh mute file detection...")
195
+ script_path = Path(__file__).parent.parent / ".claude" / "hooks" / "play-tts.sh"
196
+ shell_cmd = lambda s, msg: ["bash", str(s), msg]
197
+
198
+ try:
199
+ import subprocess
200
+
201
+ if not script_path.exists():
202
+ print(f"⚠️ {script_path.name} not found at {script_path}, skipping shell test")
203
+ return True # Not a failure, just can't test
204
+
205
+ # On Windows, play-tts.ps1 checks .claude/tts-muted.txt for "true"
206
+ # On Unix, play-tts.sh checks ~/.agentvibes-muted file existence
207
+ if is_windows:
208
+ test_mute_file = Path.home() / ".claude" / "tts-muted.txt"
209
+ test_mute_file.parent.mkdir(parents=True, exist_ok=True)
210
+ else:
211
+ test_mute_file = Path.home() / ".agentvibes-muted"
212
+
213
+ # Clean up first
214
+ if test_mute_file.exists():
215
+ test_mute_file.unlink()
216
+
217
+ # Test 1: With mute file, script should exit early silently
218
+ if is_windows:
219
+ test_mute_file.write_text("true")
220
+ else:
221
+ test_mute_file.touch()
222
+ try:
223
+ result = subprocess.run(
224
+ shell_cmd(script_path, "Test message"),
225
+ capture_output=True,
226
+ text=True,
227
+ timeout=5
228
+ )
229
+ output = result.stdout + result.stderr
230
+ # When muted, script should exit 0 silently (no TTS output)
231
+ assert result.returncode == 0, f"Expected exit code 0 when muted, got {result.returncode}"
232
+ print(f"✅ Test 1: {script_path.name} respects mute file (exit code 0)")
233
+ finally:
234
+ if test_mute_file.exists():
235
+ test_mute_file.unlink()
236
+
237
+ # Test 2: Without mute file, script should proceed (we won't check full TTS, just that it doesn't say muted)
238
+ # Note: This test may produce audio output
239
+ print("✅ Test 2: Skipping audio test (would produce sound)")
240
+
241
+ print(f"✅ {script_path.name} mute detection tests passed")
242
+ return True
243
+
244
+ except subprocess.TimeoutExpired:
245
+ print("⚠️ Script timed out (might be running TTS)")
246
+ # Clean up both possible mute files
247
+ for f in [Path.home() / ".agentvibes-muted", Path.home() / ".claude" / "tts-muted.txt"]:
248
+ if f.exists():
249
+ f.unlink()
250
+ return True # Timeout is acceptable if TTS is running
251
+ except Exception as e:
252
+ print(f"❌ {script_path.name} mute test failed: {e}")
253
+ # Clean up both possible mute files
254
+ for f in [Path.home() / ".agentvibes-muted", Path.home() / ".claude" / "tts-muted.txt"]:
255
+ if f.exists():
256
+ f.unlink()
257
+ return False
258
+
259
+
260
+ def test_set_provider():
261
+ """Test set_provider MCP functionality (platform-aware)"""
262
+ print("\nTesting set_provider MCP functionality...")
263
+ try:
264
+ from server import AgentVibesServer
265
+ import asyncio
266
+ import platform
267
+ from pathlib import Path
268
+
269
+ server = AgentVibesServer()
270
+ is_windows = platform.system() == "Windows" and not os.environ.get("WSL_DISTRO_NAME")
271
+
272
+ # Test provider switching with platform-appropriate providers
273
+ async def run_tests():
274
+ if is_windows:
275
+ # Windows providers
276
+ result = await server.set_provider("windows-sapi")
277
+ assert "OK" in result or "switched" in result.lower(), f"Expected success switching to windows-sapi, got: {result}"
278
+ print("✅ Test 1: Successfully switched to windows-sapi")
279
+
280
+ result = await server.set_provider("windows-piper")
281
+ assert "OK" in result or "switched" in result.lower() or "WARNING" in result, f"Expected success/warning switching to windows-piper, got: {result}"
282
+ print("✅ Test 2: Switched to windows-piper (or warned if not installed)")
283
+
284
+ result = await server.set_provider("soprano")
285
+ assert "OK" in result or "switched" in result.lower() or "Script not found" in result, f"Expected success switching to soprano, got: {result}"
286
+ print("✅ Test 3: Switched to soprano (or script not found)")
287
+ else:
288
+ # Unix providers
289
+ result = await server.set_provider("piper")
290
+ assert "\u2713" in result or "switched" in result.lower(), f"Expected success switching to piper, got: {result}"
291
+ print("✅ Test 1: Successfully switched to piper")
292
+
293
+ result = await server.set_provider("macos")
294
+ assert "\u2713" in result or "switched" in result.lower(), f"Expected success switching to macos, got: {result}"
295
+ print("✅ Test 2: Successfully switched to macos")
296
+
297
+ result = await server.set_provider("termux-ssh")
298
+ assert "\u2713" in result or "switched" in result.lower(), f"Expected success switching to termux-ssh, got: {result}"
299
+ print("✅ Test 3: Successfully switched to termux-ssh")
300
+
301
+ # Test 4: Invalid provider should fail
302
+ result = await server.set_provider("invalid-provider")
303
+ assert "Invalid" in result or "Error" in result, f"Expected error for invalid provider, got: {result}"
304
+ print("✅ Test 4: Correctly rejected invalid provider")
305
+
306
+ # Test 5: Case insensitivity
307
+ test_provider = "WINDOWS-SAPI" if is_windows else "PIPER"
308
+ expected_lower = "windows-sapi" if is_windows else "piper"
309
+ result = await server.set_provider(test_provider)
310
+ assert "OK" in result or "\u2713" in result or "switched" in result.lower(), f"Expected success with uppercase {test_provider}, got: {result}"
311
+ print("✅ Test 5: Case-insensitive provider switching works")
312
+
313
+ # Test 6: Verify provider file was written
314
+ # On Windows, provider-manager.ps1 writes to $USERPROFILE\.claude\
315
+ # On Unix, provider-manager.sh may write to project dir or home dir
316
+ if is_windows:
317
+ provider_file = Path.home() / ".claude" / "tts-provider.txt"
318
+ else:
319
+ provider_file = server.claude_dir / "tts-provider.txt"
320
+ if not provider_file.exists():
321
+ provider_file = Path.home() / ".claude" / "tts-provider.txt"
322
+
323
+ if provider_file.exists():
324
+ # Strip BOM that PowerShell's Set-Content may add on Windows
325
+ provider_content = provider_file.read_text(encoding="utf-8-sig").strip()
326
+ assert provider_content == expected_lower, f"Expected provider file to contain '{expected_lower}', got: {provider_content}"
327
+ print("✅ Test 6: Provider file correctly written")
328
+ else:
329
+ print("⚠️ Test 6: Provider file not found (may be in project dir)")
330
+
331
+ asyncio.run(run_tests())
332
+
333
+ print("✅ All set_provider tests passed")
334
+ return True
335
+
336
+ except AssertionError as e:
337
+ print(f"❌ Assertion failed: {e}")
338
+ return False
339
+ except Exception as e:
340
+ print(f"❌ set_provider test failed: {e}")
341
+ import traceback
342
+ traceback.print_exc()
343
+ return False
344
+
345
+
346
+ def main():
347
+ """Run all tests"""
348
+ print("=" * 60)
349
+ print("AgentVibes MCP Server Test Suite")
350
+ print("=" * 60)
351
+
352
+ tests = [
353
+ ("Imports", test_imports),
354
+ ("Server Initialization", test_server_init),
355
+ ("Helper Methods", test_helper_methods),
356
+ ("Mute/Unmute Functionality", test_mute_unmute),
357
+ ("play-tts Mute Detection", test_play_tts_mute_check),
358
+ ("set_provider MCP Function", test_set_provider),
359
+ ]
360
+
361
+ results = []
362
+ for name, test_func in tests:
363
+ try:
364
+ result = test_func()
365
+ results.append((name, result))
366
+ except Exception as e:
367
+ print(f"\n❌ Test '{name}' crashed: {e}")
368
+ results.append((name, False))
369
+
370
+ print("\n" + "=" * 60)
371
+ print("Test Results Summary")
372
+ print("=" * 60)
373
+
374
+ passed = sum(1 for _, result in results if result)
375
+ total = len(results)
376
+
377
+ for name, result in results:
378
+ status = "✅ PASS" if result else "❌ FAIL"
379
+ print(f"{status} - {name}")
380
+
381
+ print("=" * 60)
382
+ print(f"Total: {passed}/{total} tests passed")
383
+
384
+ if passed == total:
385
+ print("\n🎉 All tests passed!")
386
+ return 0
387
+ else:
388
+ print(f"\n⚠️ {total - passed} test(s) failed")
389
+ print("\nNote: Some failures may be expected if MCP library is not installed")
390
+ print("Install with: pip install mcp")
391
+ return 1
392
+
393
+
394
+ if __name__ == "__main__":
395
+ sys.exit(main())