agentvibes 5.3.0 → 5.5.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 (222) hide show
  1. package/.agentvibes/LITE-MODE.md +236 -0
  2. package/.agentvibes/README.md +136 -0
  3. package/.agentvibes/backup/session-start-tts.sh.20251210_212814 +141 -0
  4. package/.agentvibes/backups/agents/analyst_20260204_144958.md +78 -0
  5. package/.agentvibes/backups/agents/architect_20260204_144958.md +72 -0
  6. package/.agentvibes/backups/agents/dev_20260204_144958.md +74 -0
  7. package/.agentvibes/backups/agents/pm_20260204_144958.md +72 -0
  8. package/.agentvibes/backups/agents/quick-flow-solo-dev_20260204_144958.md +64 -0
  9. package/.agentvibes/backups/agents/sm_20260204_144958.md +87 -0
  10. package/.agentvibes/backups/agents/tea_20260204_144958.md +79 -0
  11. package/.agentvibes/backups/agents/tech-writer_20260204_144958.md +82 -0
  12. package/.agentvibes/backups/agents/ux-designer_20260204_144958.md +80 -0
  13. package/.agentvibes/bmad/bmad-voices.md +69 -69
  14. package/.agentvibes/config/README-personality-defaults.md +162 -0
  15. package/.agentvibes/config/mode.txt +1 -0
  16. package/.agentvibes/config/personality-voice-defaults.default.json +21 -0
  17. package/.agentvibes/config/save-audio.txt +1 -0
  18. package/.agentvibes/config/voice-metadata.json +160 -0
  19. package/.agentvibes/config.json +24 -15
  20. package/.agentvibes/hooks/help.sh +191 -0
  21. package/.agentvibes/hooks/post-tool-use-lite.sh +111 -0
  22. package/.agentvibes/hooks/save-audio-manager.sh +162 -0
  23. package/.agentvibes/hooks/session-start-full-optimized.sh +102 -0
  24. package/.agentvibes/hooks/session-start-full.sh +142 -0
  25. package/.agentvibes/hooks/session-start-lite-v2.sh +34 -0
  26. package/.agentvibes/hooks/session-start-lite.sh +29 -0
  27. package/.agentvibes/hooks/stop-lite.sh +115 -0
  28. package/.agentvibes/hooks/switch-mode.sh +215 -0
  29. package/.agentvibes/output-styles/audio-summary.md +30 -0
  30. package/.claude/activation-instructions +54 -54
  31. package/.claude/audio/voice-samples/piper/alan.wav +0 -0
  32. package/.claude/audio/voice-samples/piper/amy.wav +0 -0
  33. package/.claude/audio/voice-samples/piper/charlotte.wav +0 -0
  34. package/.claude/audio/voice-samples/piper/joe.wav +0 -0
  35. package/.claude/audio/voice-samples/piper/john.wav +0 -0
  36. package/.claude/audio/voice-samples/piper/katherine.wav +0 -0
  37. package/.claude/audio/voice-samples/piper/kristin.wav +0 -0
  38. package/.claude/audio/voice-samples/piper/linda.wav +0 -0
  39. package/.claude/audio/voice-samples/piper/marcus.wav +0 -0
  40. package/.claude/audio/voice-samples/piper/ryan.wav +0 -0
  41. package/.claude/commands/agent-vibes/add.md +21 -21
  42. package/.claude/commands/agent-vibes/agent-vibes.md +101 -101
  43. package/.claude/commands/agent-vibes/agent.md +79 -79
  44. package/.claude/commands/agent-vibes/background-music.md +111 -111
  45. package/.claude/commands/agent-vibes/bmad.md +198 -198
  46. package/.claude/commands/agent-vibes/clean.md +18 -18
  47. package/.claude/commands/agent-vibes/cleanup.md +18 -18
  48. package/.claude/commands/agent-vibes/commands.json +145 -145
  49. package/.claude/commands/agent-vibes/effects.md +97 -97
  50. package/.claude/commands/agent-vibes/get.md +9 -9
  51. package/.claude/commands/agent-vibes/hide.md +91 -91
  52. package/.claude/commands/agent-vibes/language.md +23 -23
  53. package/.claude/commands/agent-vibes/learn.md +67 -67
  54. package/.claude/commands/agent-vibes/list.md +13 -13
  55. package/.claude/commands/agent-vibes/mute.md +37 -37
  56. package/.claude/commands/agent-vibes/preview.md +17 -17
  57. package/.claude/commands/agent-vibes/provider.md +68 -68
  58. package/.claude/commands/agent-vibes/replay-target.md +14 -14
  59. package/.claude/commands/agent-vibes/sample.md +12 -12
  60. package/.claude/commands/agent-vibes/set-favorite-voice.md +84 -84
  61. package/.claude/commands/agent-vibes/set-pretext.md +65 -65
  62. package/.claude/commands/agent-vibes/set-speed.md +41 -41
  63. package/.claude/commands/agent-vibes/show.md +84 -84
  64. package/.claude/commands/agent-vibes/switch.md +87 -87
  65. package/.claude/commands/agent-vibes/target-voice.md +26 -26
  66. package/.claude/commands/agent-vibes/target.md +30 -30
  67. package/.claude/commands/agent-vibes/translate.md +68 -68
  68. package/.claude/commands/agent-vibes/unmute.md +45 -45
  69. package/.claude/commands/agent-vibes/whoami.md +7 -7
  70. package/.claude/commands/agent-vibes-bmad-voices.md +117 -117
  71. package/.claude/commands/agent-vibes-rdp.md +24 -24
  72. package/.claude/config/audio-effects.cfg +16 -11
  73. package/.claude/config/audio-effects.cfg.sample +52 -52
  74. package/.claude/config/background-music-position.txt +27 -0
  75. package/.claude/config/background-music-volume.txt +1 -1
  76. package/.claude/config/background-music.cfg +1 -0
  77. package/.claude/config/background-music.txt +1 -0
  78. package/.claude/config/tts-speech-rate.txt +1 -4
  79. package/.claude/config/tts-verbosity.txt +1 -0
  80. package/.claude/docs/TERMUX_SETUP.md +408 -408
  81. package/.claude/github-star-reminder.txt +1 -1
  82. package/.claude/hooks/README-TTS-QUEUE.md +135 -135
  83. package/.claude/hooks/audio-cache-utils.sh +0 -0
  84. package/.claude/hooks/audio-processor.sh +60 -14
  85. package/.claude/hooks/background-music-manager.sh +0 -0
  86. package/.claude/hooks/bmad-party-manager.sh +225 -0
  87. package/.claude/hooks/bmad-party-speak.sh +0 -0
  88. package/.claude/hooks/bmad-speak-enhanced.sh +0 -0
  89. package/.claude/hooks/bmad-speak.sh +12 -15
  90. package/.claude/hooks/bmad-tts-injector.sh +0 -0
  91. package/.claude/hooks/bmad-voice-manager.sh +0 -0
  92. package/.claude/hooks/clawdbot-receiver-SECURE.sh +25 -23
  93. package/.claude/hooks/clawdbot-receiver.sh +4 -28
  94. package/.claude/hooks/clean-audio-cache.sh +0 -0
  95. package/.claude/hooks/cleanup-cache.sh +0 -0
  96. package/.claude/hooks/configure-rdp-mode.sh +0 -0
  97. package/.claude/hooks/download-extra-voices.sh +0 -0
  98. package/.claude/hooks/effects-manager.sh +0 -0
  99. package/.claude/hooks/github-star-reminder.sh +0 -0
  100. package/.claude/hooks/language-manager.sh +0 -0
  101. package/.claude/hooks/learn-manager.sh +0 -0
  102. package/.claude/hooks/macos-voice-manager.sh +0 -0
  103. package/.claude/hooks/migrate-background-music.sh +0 -0
  104. package/.claude/hooks/migrate-to-agentvibes.sh +0 -0
  105. package/.claude/hooks/optimize-background-music.sh +0 -0
  106. package/.claude/hooks/personality-manager.sh +0 -0
  107. package/.claude/hooks/piper-download-voices.sh +0 -0
  108. package/.claude/hooks/piper-installer.sh +1 -1
  109. package/.claude/hooks/piper-multispeaker-registry.sh +0 -0
  110. package/.claude/hooks/piper-voice-manager.sh +0 -0
  111. package/.claude/hooks/play-tts-enhanced.sh +0 -0
  112. package/.claude/hooks/play-tts-macos.sh +6 -12
  113. package/.claude/hooks/play-tts-piper.sh +52 -81
  114. package/.claude/hooks/play-tts-soprano.sh +9 -43
  115. package/.claude/hooks/play-tts-ssh-remote.sh +43 -215
  116. package/.claude/hooks/play-tts-termux-ssh.sh +0 -0
  117. package/.claude/hooks/play-tts.sh +41 -20
  118. package/.claude/hooks/post-response.sh +41 -0
  119. package/.claude/hooks/prepare-release.sh +0 -0
  120. package/.claude/hooks/provider-commands.sh +0 -0
  121. package/.claude/hooks/provider-manager.sh +0 -0
  122. package/.claude/hooks/replay-target-audio.sh +0 -0
  123. package/.claude/hooks/requirements.txt +6 -6
  124. package/.claude/hooks/sentiment-manager.sh +0 -0
  125. package/.claude/hooks/session-start-tts.sh +56 -39
  126. package/.claude/hooks/soprano-gradio-synth.py +139 -139
  127. package/.claude/hooks/speed-manager.sh +0 -0
  128. package/.claude/hooks/stop.sh +63 -0
  129. package/.claude/hooks/termux-installer.sh +0 -0
  130. package/.claude/hooks/translate-manager.sh +0 -0
  131. package/.claude/hooks/translator.py +237 -237
  132. package/.claude/hooks/tts-queue-worker.sh +0 -0
  133. package/.claude/hooks/tts-queue.sh +0 -0
  134. package/.claude/hooks/verbosity-manager.sh +0 -0
  135. package/.claude/hooks/voice-manager.sh +26 -4
  136. package/.claude/hooks-windows/audio-cache-utils.ps1 +119 -119
  137. package/.claude/hooks-windows/bmad-party-speak.ps1 +278 -278
  138. package/.claude/hooks-windows/bmad-speak.ps1 +264 -264
  139. package/.claude/hooks-windows/clean-audio-cache.ps1 +53 -53
  140. package/.claude/hooks-windows/effects-manager.ps1 +294 -294
  141. package/.claude/hooks-windows/language-manager.ps1 +193 -193
  142. package/.claude/hooks-windows/learn-manager.ps1 +241 -241
  143. package/.claude/hooks-windows/personality-manager.ps1 +266 -266
  144. package/.claude/hooks-windows/play-tts-soprano.ps1 +5 -5
  145. package/.claude/hooks-windows/play-tts-termux-ssh.ps1 +138 -138
  146. package/.claude/hooks-windows/play-tts-windows-piper.ps1 +178 -0
  147. package/.claude/hooks-windows/play-tts-windows-sapi.ps1 +108 -0
  148. package/.claude/hooks-windows/play-tts.ps1 +265 -507
  149. package/.claude/hooks-windows/provider-manager.ps1 +158 -192
  150. package/.claude/hooks-windows/session-start-tts.ps1 +55 -46
  151. package/.claude/hooks-windows/soprano-gradio-synth.py +153 -153
  152. package/.claude/hooks-windows/speed-manager.ps1 +166 -166
  153. package/.claude/hooks-windows/voice-manager-windows.ps1 +176 -260
  154. package/.claude/output-styles/agent-vibes.md +202 -202
  155. package/.claude/personalities/angry.md +14 -14
  156. package/.claude/personalities/annoying.md +14 -14
  157. package/.claude/personalities/crass.md +14 -14
  158. package/.claude/personalities/dramatic.md +14 -14
  159. package/.claude/personalities/dry-humor.md +50 -50
  160. package/.claude/personalities/flirty.md +20 -20
  161. package/.claude/personalities/funny.md +14 -14
  162. package/.claude/personalities/grandpa.md +32 -32
  163. package/.claude/personalities/millennial.md +14 -14
  164. package/.claude/personalities/moody.md +14 -14
  165. package/.claude/personalities/normal.md +16 -16
  166. package/.claude/personalities/pirate.md +14 -14
  167. package/.claude/personalities/poetic.md +14 -14
  168. package/.claude/personalities/professional.md +14 -14
  169. package/.claude/personalities/rapper.md +55 -55
  170. package/.claude/personalities/robot.md +14 -14
  171. package/.claude/personalities/sarcastic.md +38 -38
  172. package/.claude/personalities/sassy.md +14 -14
  173. package/.claude/personalities/surfer-dude.md +14 -14
  174. package/.claude/personalities/zen.md +14 -14
  175. package/.claude/piper-voices-dir.txt +1 -0
  176. package/.claude/settings.json +25 -15
  177. package/.claude/verbosity.txt +1 -1
  178. package/.clawdbot/README.md +105 -105
  179. package/.clawdbot/skill/SKILL.md +149 -145
  180. package/.mcp.json +30 -11
  181. package/CLAUDE.md +170 -215
  182. package/README.md +207 -521
  183. package/RELEASE_NOTES.md +1172 -1976
  184. package/WINDOWS-SETUP.md +208 -208
  185. package/bin/agent-vibes +0 -0
  186. package/bin/agentvibes-voice-browser.js +64 -1289
  187. package/bin/agentvibes.js +28 -0
  188. package/bin/ensure-soprano-running.sh +43 -0
  189. package/bin/mcp-server.js +121 -121
  190. package/bin/mcp-server.sh +0 -0
  191. package/bin/test-bmad-pr +78 -78
  192. package/mcp-server/QUICK_START.md +203 -203
  193. package/mcp-server/README.md +345 -345
  194. package/mcp-server/WINDOWS_SETUP.md +260 -260
  195. package/mcp-server/docs/troubleshooting-audio.md +313 -313
  196. package/mcp-server/examples/claude_desktop_config.json +11 -11
  197. package/mcp-server/examples/claude_desktop_config_piper.json +9 -9
  198. package/mcp-server/examples/custom_instructions.md +169 -169
  199. package/mcp-server/install-deps.js +130 -130
  200. package/mcp-server/pyproject.toml +52 -52
  201. package/mcp-server/requirements.txt +2 -2
  202. package/mcp-server/server.py +1467 -1578
  203. package/mcp-server/test_server.py +395 -395
  204. package/package.json +1 -3
  205. package/setup-windows.ps1 +815 -815
  206. package/src/console/tabs/music-tab.js +5 -2
  207. package/src/console/tabs/voices-tab.js +71 -37
  208. package/src/installer.js +52 -5
  209. package/src/services/llm-provider-service.js +1 -1
  210. package/templates/agentvibes-receiver.sh +158 -483
  211. package/templates/audio/welcome-music.mp3 +0 -0
  212. package/.agentvibes/bmad-voice-map.json +0 -104
  213. package/.agentvibes/copilot-sessions.log +0 -4
  214. package/.claude/config/audio-effects-bmad.cfg +0 -50
  215. package/.claude/config/intro-text.txt +0 -1
  216. package/.claude/config/personality.txt +0 -1
  217. package/.claude/config/piper-speech-rate.txt +0 -4
  218. package/.claude/config/piper-target-speech-rate.txt +0 -1
  219. package/.claude/config/reverb-level.txt +0 -1
  220. package/.claude/config/tts-target-speech-rate.txt +0 -1
  221. package/voice-assignments.json +0 -8245
  222. /package/{.claude → .agentvibes}/config/agentvibes.json +0 -0
@@ -185,12 +185,15 @@ export function scanTracks() {
185
185
  const tracksDir = _getTracksDir();
186
186
  try {
187
187
  const files = fs.readdirSync(tracksDir);
188
+ const mp3s = files.filter(f => /\.mp3$/i.test(f));
189
+ // If the directory exists but has no mp3s (e.g. empty npm package dir),
190
+ // fall back to the static catalog so bundled tracks always show.
191
+ if (mp3s.length === 0) return BUILT_IN_TRACK_CATALOG.map(t => ({ ...t, isBuiltIn: true }));
188
192
  const builtInIds = new Set(BUILT_IN_TRACK_CATALOG.map(t => t.id));
189
193
  // Sort by the alphabetic part of the label (skip leading emoji/symbols)
190
194
  // so the order reflects the track NAME, not the emoji codepoint.
191
195
  const _sortKey = (s) => s.replace(/^[^a-zA-Z]+/, '');
192
- return files
193
- .filter(f => /\.mp3$/i.test(f))
196
+ return mp3s
194
197
  .map(f => ({ id: f, label: formatTrackLabel(f), isBuiltIn: builtInIds.has(f) }))
195
198
  .sort((a, b) => _sortKey(a.label).localeCompare(_sortKey(b.label), undefined, { sensitivity: 'base' }));
196
199
  } catch {
@@ -133,6 +133,10 @@ function loadCatalog() {
133
133
  // Build lookup map for O(1) access by voiceId
134
134
  _catalogMap = new Map();
135
135
  for (const c of _catalogEntries) _catalogMap.set(c.voiceId, c);
136
+
137
+ // Patch libritts_r onnx.json files so their speaker IDs become friendly names.
138
+ // Must run after catalog loads so the name mapping is available.
139
+ patchLibriTTSSpeakerNames();
136
140
  }
137
141
 
138
142
  /**
@@ -142,45 +146,48 @@ function loadCatalog() {
142
146
  * Safe to call multiple times — skips if already patched.
143
147
  */
144
148
  function patchLibriTTSSpeakerNames() {
149
+ // Load catalog once for all models
150
+ const catalogPath = path.resolve(__dirname, '..', '..', '..', 'voice-assignments.json');
151
+ if (!fs.existsSync(catalogPath)) return;
152
+ let speakers;
145
153
  try {
146
- const jsonPath = path.join(PIPER_VOICES_DIR, 'en_US-libritts-high.onnx.json');
147
- if (!fs.existsSync(jsonPath)) return;
148
- const data = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
149
- if (!data.speaker_id_map || data.num_speakers <= 1) return;
150
-
151
- const names = Object.keys(data.speaker_id_map);
152
- // Already patched if first name doesn't start with 'p' followed by digits
153
- if (names.length > 0 && !/^p\d+$/.test(names[0])) return;
154
-
155
- // Build index p-name reverse map
156
- const indexToP = {};
157
- for (const [pname, idx] of Object.entries(data.speaker_id_map)) {
158
- indexToP[idx] = pname;
159
- }
154
+ speakers = JSON.parse(fs.readFileSync(catalogPath, 'utf8')).libritts_speakers ?? {};
155
+ } catch { return; }
156
+
157
+ // Models to patch and how to detect unpatched keys:
158
+ // libritts-high → raw keys are p-prefixed corpus IDs (p3922, p8699, …)
159
+ // libritts_r-* → raw keys are plain numeric corpus IDs (3922, 8699, …)
160
+ const MODELS = [
161
+ { file: 'en_US-libritts-high.onnx.json', notPatched: (k) => /^p\d+$/.test(k) },
162
+ { file: 'en_US-libritts_r-medium.onnx.json', notPatched: (k) => /^\d+$/.test(k) },
163
+ { file: 'en_US-libritts_r-high.onnx.json', notPatched: (k) => /^\d+$/.test(k) },
164
+ ];
165
+
166
+ for (const { file, notPatched } of MODELS) {
167
+ try {
168
+ const jsonPath = path.join(PIPER_VOICES_DIR, file);
169
+ if (!fs.existsSync(jsonPath)) continue;
170
+ const data = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
171
+ if (!data.speaker_id_map || data.num_speakers <= 1) continue;
160
172
 
161
- // Load friendly names from catalog
162
- const catalogPath = path.resolve(__dirname, '..', '..', '..', 'voice-assignments.json');
163
- if (!fs.existsSync(catalogPath)) return;
164
- const catalog = JSON.parse(fs.readFileSync(catalogPath, 'utf8'));
165
- const speakers = catalog.libritts_speakers ?? {};
166
-
167
- // Rebuild speaker_id_map with friendly names
168
- const newMap = {};
169
- for (const [idx, pname] of Object.entries(indexToP)) {
170
- const friendly = speakers[idx]?.voice_name;
171
- if (friendly) {
172
- newMap[friendly] = parseInt(idx, 10);
173
- } else {
174
- newMap[pname] = parseInt(idx, 10);
173
+ const names = Object.keys(data.speaker_id_map);
174
+ // Skip if already patched (first key is a friendly name, not a raw corpus ID)
175
+ if (names.length === 0 || !notPatched(names[0])) continue;
176
+
177
+ // Values are 0-based sequential indices into the model — use as catalog key
178
+ const newMap = {};
179
+ for (const [rawKey, idx] of Object.entries(data.speaker_id_map)) {
180
+ const friendly = speakers[String(idx)]?.voice_name;
181
+ newMap[friendly ?? rawKey] = idx;
175
182
  }
176
- }
177
183
 
178
- data.speaker_id_map = newMap;
179
- // Verify file ownership before writing (security: CLAUDE.md)
180
- const stat = fs.statSync(jsonPath);
181
- if (typeof process.getuid === 'function' && stat.uid !== process.getuid()) return;
182
- fs.writeFileSync(jsonPath, JSON.stringify(data, null, 2), 'utf8');
183
- } catch { /* non-fatal */ }
184
+ data.speaker_id_map = newMap;
185
+ // Verify file ownership before writing (security: CLAUDE.md)
186
+ const stat = fs.statSync(jsonPath);
187
+ if (typeof process.getuid === 'function' && stat.uid !== process.getuid()) continue;
188
+ fs.writeFileSync(jsonPath, JSON.stringify(data, null, 2), 'utf8');
189
+ } catch { /* non-fatal — skip this model */ }
190
+ }
184
191
  }
185
192
 
186
193
  // Column widths for the multi-column voice list
@@ -417,9 +424,9 @@ export function parseMultiSpeaker(voiceId) {
417
424
  try {
418
425
  const data = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
419
426
  let speakerId = data.speaker_id_map?.[speakerName] ?? null;
420
- // Fallback: if the .onnx.json still has raw p-names (not yet patched),
427
+ // Fallback: if the .onnx.json still has raw corpus IDs (not yet patched),
421
428
  // look up the numeric speaker ID from voice-assignments.json catalog.
422
- if (speakerId == null && model === 'en_US-libritts-high') {
429
+ if (speakerId == null && (model === 'en_US-libritts-high' || /^en_US-libritts_r-/.test(model))) {
423
430
  try {
424
431
  const catalogPath = path.resolve(__dirname, '..', '..', '..', 'voice-assignments.json');
425
432
  const catalog = JSON.parse(fs.readFileSync(catalogPath, 'utf8'));
@@ -443,6 +450,9 @@ export function parseMultiSpeaker(voiceId) {
443
450
  * @returns {string[]}
444
451
  */
445
452
  export function scanInstalledVoices() {
453
+ // Ensure catalog is loaded and libritts_r onnx.json files are patched
454
+ // before we read their speaker_id_map keys (otherwise we get raw corpus IDs).
455
+ loadCatalog();
446
456
  try {
447
457
  const files = fs.readdirSync(PIPER_VOICES_DIR);
448
458
  const onnxFiles = files
@@ -588,6 +598,30 @@ export function getVoiceMeta(voiceId) {
588
598
  _metaCache.set(voiceId, result);
589
599
  return result;
590
600
  }
601
+ // libritts_r variants share speaker names with libritts-high after patching
602
+ if (/^en_US-libritts_r-/.test(ms.model)) {
603
+ // After patching: speakerName is a friendly name — look up in libritts-high catalog
604
+ const highCat = _catalogMap.get(`en_US-libritts-high${MS_SEP}${ms.speakerName}`);
605
+ if (highCat) {
606
+ const result = { displayName: highCat.displayName, gender: highCat.gender, provider: `Piper (${ms.model})` };
607
+ _metaCache.set(voiceId, result);
608
+ return result;
609
+ }
610
+ // Before patching: speakerName is a raw numeric corpus ID — resolve via onnx.json value
611
+ if (/^\d+$/.test(ms.speakerName)) {
612
+ try {
613
+ const jsonPath = path.join(PIPER_VOICES_DIR, ms.model + '.onnx.json');
614
+ const data = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
615
+ const seqIdx = data.speaker_id_map?.[ms.speakerName];
616
+ if (seqIdx != null && _catalogEntries[seqIdx]) {
617
+ const cat = _catalogEntries[seqIdx];
618
+ const result = { displayName: cat.displayName, gender: cat.gender, provider: `Piper (${ms.model})` };
619
+ _metaCache.set(voiceId, result);
620
+ return result;
621
+ }
622
+ } catch { /* fall through */ }
623
+ }
624
+ }
591
625
  // Fallback for speakers not in the catalog (e.g. 16Speakers model)
592
626
  const displayName = uniquifyVoiceName(ms.speakerName.replace(/_/g, ' '));
593
627
  const result = {
package/src/installer.js CHANGED
@@ -132,6 +132,36 @@ function isNativeWindows() {
132
132
  return process.platform === 'win32' && !process.env.WSL_DISTRO_NAME;
133
133
  }
134
134
 
135
+ /**
136
+ * Wrap an ora spinner with safe fallbacks for all methods.
137
+ * Ensures compatibility across platforms (Windows, WSL, macOS, Linux) and ora versions.
138
+ * @param {Object} spinner - An ora spinner instance (or any spinner-like object)
139
+ * @returns {Object} A spinner proxy with guaranteed methods
140
+ */
141
+ function createRobustSpinner(spinner) {
142
+ const safe = (method, fallback) => (...args) => {
143
+ if (typeof spinner[method] === 'function') {
144
+ try { spinner[method](...args); } catch { fallback?.(...args); }
145
+ } else {
146
+ fallback?.(...args);
147
+ }
148
+ return proxy;
149
+ };
150
+ const proxy = {
151
+ start: safe('start'),
152
+ stop: safe('stop'),
153
+ succeed: safe('succeed', (t) => { if (t) process.stdout.write(`✓ ${t}\n`); }),
154
+ fail: safe('fail', (t) => { if (t) process.stderr.write(`✗ ${t}\n`); }),
155
+ warn: safe('warn', (t) => { if (t) process.stdout.write(`⚠ ${t}\n`); }),
156
+ info: safe('info', (t) => { if (t) process.stdout.write(`ℹ ${t}\n`); }),
157
+ stopAndPersist: safe('stopAndPersist'),
158
+ get text() { return spinner.text ?? ''; },
159
+ set text(t) { try { spinner.text = t; } catch { /* non-TTY */ } },
160
+ get isSpinning(){ return spinner.isSpinning ?? false; },
161
+ };
162
+ return proxy;
163
+ }
164
+
135
165
  /**
136
166
  * Get the Piper provider name (always 'piper' on all platforms)
137
167
  * @returns {string} The piper provider identifier
@@ -3219,6 +3249,7 @@ async function handleTermuxSshConfiguration() {
3219
3249
  * @returns {Promise<{count: number, boxen: string}>} Number of files copied and boxen content
3220
3250
  */
3221
3251
  async function copyCommandFiles(targetDir, spinner) {
3252
+ spinner = createRobustSpinner(spinner);
3222
3253
  spinner.start('Installing /agent-vibes slash commands...');
3223
3254
  const srcCommandsDir = path.join(__dirname, '..', '.claude', 'commands', 'agent-vibes');
3224
3255
  const commandsDir = path.join(targetDir, '.claude', 'commands');
@@ -3432,6 +3463,7 @@ function buildHookInstallationBoxen(installedFiles, failedFiles) {
3432
3463
  * @returns {Promise<{count: number, boxen: string|null}>} Number of files copied and boxen content
3433
3464
  */
3434
3465
  async function copyHookFiles(targetDir, spinner) {
3466
+ spinner = createRobustSpinner(spinner);
3435
3467
  spinner.start('Installing TTS helper scripts...');
3436
3468
  const hooksSubdir = isNativeWindows() ? 'hooks-windows' : 'hooks';
3437
3469
  const srcHooksDir = path.join(__dirname, '..', '.claude', hooksSubdir);
@@ -3486,6 +3518,7 @@ async function copyHookFiles(targetDir, spinner) {
3486
3518
  * @returns {Promise<{count: number, boxen: string|null}>} Number of files copied and boxen content
3487
3519
  */
3488
3520
  async function copyPersonalityFiles(targetDir, spinner) {
3521
+ spinner = createRobustSpinner(spinner);
3489
3522
  spinner.start('Installing personality templates...');
3490
3523
  const srcPersonalitiesDir = path.join(__dirname, '..', '.claude', 'personalities');
3491
3524
  const destPersonalitiesDir = path.join(targetDir, '.claude', 'personalities');
@@ -3568,6 +3601,7 @@ async function copyPersonalityFiles(targetDir, spinner) {
3568
3601
  * @returns {Promise<number>} Number of files copied
3569
3602
  */
3570
3603
  async function copyPluginFiles(targetDir, spinner) {
3604
+ spinner = createRobustSpinner(spinner);
3571
3605
  spinner.start('Installing BMAD plugin files...');
3572
3606
  const srcPluginsDir = path.join(__dirname, '..', '.claude', 'plugins');
3573
3607
  const destPluginsDir = path.join(targetDir, '.claude', 'plugins');
@@ -3602,6 +3636,7 @@ async function copyPluginFiles(targetDir, spinner) {
3602
3636
  * @returns {Promise<number>} Number of files copied
3603
3637
  */
3604
3638
  async function copyBmadConfigFiles(targetDir, spinner) {
3639
+ spinner = createRobustSpinner(spinner);
3605
3640
  spinner.start('Installing BMAD config files...');
3606
3641
  const srcBmadDir = path.join(__dirname, '..', '.agentvibes', 'bmad');
3607
3642
  const destBmadDir = path.join(targetDir, '.agentvibes', 'bmad');
@@ -3634,6 +3669,7 @@ async function copyBmadConfigFiles(targetDir, spinner) {
3634
3669
  * @returns {Promise<{count: number, boxen: string}>} Number of files copied and boxen content
3635
3670
  */
3636
3671
  async function copyBackgroundMusicFiles(targetDir, spinner) {
3672
+ spinner = createRobustSpinner(spinner);
3637
3673
  spinner.start('Installing background music tracks...');
3638
3674
  const srcBackgroundsDir = path.join(__dirname, '..', '.claude', 'audio', 'tracks');
3639
3675
  const destBackgroundsDir = path.join(targetDir, '.claude', 'audio', 'tracks');
@@ -3756,6 +3792,7 @@ async function copyBackgroundMusicFiles(targetDir, spinner) {
3756
3792
  * @returns {Promise<number>} Number of files copied
3757
3793
  */
3758
3794
  async function copyConfigFiles(targetDir, spinner) {
3795
+ spinner = createRobustSpinner(spinner);
3759
3796
  spinner.start('Installing configuration files...');
3760
3797
  const srcConfigDir = path.join(__dirname, '..', '.claude', 'config');
3761
3798
  const destConfigDir = path.join(targetDir, '.claude', 'config');
@@ -3818,6 +3855,7 @@ async function copyConfigFiles(targetDir, spinner) {
3818
3855
  * @param {Object} spinner - Ora spinner instance
3819
3856
  */
3820
3857
  async function copyCodexFiles(targetDir, spinner) {
3858
+ spinner = createRobustSpinner(spinner);
3821
3859
  spinner.start('Installing Codex integration files...');
3822
3860
  const srcCodexDir = path.join(__dirname, '..', '.codex');
3823
3861
  const destCodexDir = path.join(targetDir, '.codex');
@@ -3871,6 +3909,7 @@ async function copyCodexFiles(targetDir, spinner) {
3871
3909
  * @param {Object} spinner - Ora spinner instance
3872
3910
  */
3873
3911
  async function configureSessionStartHook(targetDir, spinner) {
3912
+ spinner = createRobustSpinner(spinner);
3874
3913
  spinner.start('Configuring AgentVibes hook for automatic TTS...');
3875
3914
  const claudeDir = path.join(targetDir, '.claude');
3876
3915
  const settingsPath = path.join(claudeDir, 'settings.json');
@@ -3926,6 +3965,7 @@ async function configureSessionStartHook(targetDir, spinner) {
3926
3965
  * @param {Object} spinner - Ora spinner instance
3927
3966
  */
3928
3967
  async function configurePartyModeHook(targetDir, spinner, homeDirOverride) {
3968
+ spinner = createRobustSpinner(spinner);
3929
3969
  spinner.start('Configuring BMAD party mode TTS hook...');
3930
3970
  const homeDir = homeDirOverride || os.homedir();
3931
3971
  const globalClaudeDir = path.join(homeDir, '.claude');
@@ -4215,7 +4255,7 @@ async function checkAndInstallPiperWindows(targetDir, options) {
4215
4255
  }
4216
4256
  const piperDir = path.join(localAppData, 'Programs', 'Piper');
4217
4257
  const piperExe = path.join(piperDir, 'piper.exe');
4218
- const spinner = ora();
4258
+ const spinner = createRobustSpinner(ora());
4219
4259
 
4220
4260
  if (fsSync.existsSync(piperExe)) {
4221
4261
  console.log(chalk.green('✓ Piper TTS is already installed at ' + piperDir + '\n'));
@@ -4844,7 +4884,14 @@ async function updatePersonalityFiles(targetDir, srcPersonalitiesDir) {
4844
4884
  * @returns {Object} Mock spinner object
4845
4885
  */
4846
4886
  function createSilentSpinner() {
4847
- const s = { start: () => s, succeed: () => s, info: () => s, fail: () => s, warn: () => s, stop: () => s };
4887
+ const s = {
4888
+ start: () => s, succeed: () => s, info: () => s,
4889
+ fail: () => s, warn: () => s, stop: () => s,
4890
+ stopAndPersist: () => s,
4891
+ get text() { return ''; },
4892
+ set text(_) {},
4893
+ get isSpinning() { return false; },
4894
+ };
4848
4895
  return s;
4849
4896
  }
4850
4897
 
@@ -5025,7 +5072,7 @@ function displayUpdateSummary(results) {
5025
5072
  * @param {Object} options - Update options
5026
5073
  */
5027
5074
  async function updateAgentVibes(targetDir, options) {
5028
- const spinner = ora('Updating AgentVibes...').start();
5075
+ const spinner = createRobustSpinner(ora('Updating AgentVibes...').start());
5029
5076
 
5030
5077
  try {
5031
5078
  // Perform all update operations
@@ -5145,7 +5192,7 @@ async function install(options = {}) {
5145
5192
  const silentSpinner = createSilentSpinner();
5146
5193
 
5147
5194
  console.log('');
5148
- const spinner = ora('Installing AgentVibes...').start();
5195
+ const spinner = createRobustSpinner(ora('Installing AgentVibes...').start());
5149
5196
 
5150
5197
  try {
5151
5198
  // Create .claude directory structure
@@ -5599,7 +5646,7 @@ program
5599
5646
  console.log(chalk.gray('✓ Auto-confirmed (--yes flag)\n'));
5600
5647
  }
5601
5648
 
5602
- const spinner = ora('Uninstalling AgentVibes...').start();
5649
+ const spinner = createRobustSpinner(ora('Uninstalling AgentVibes...').start());
5603
5650
 
5604
5651
  try {
5605
5652
  let removedCount = 0;
@@ -198,7 +198,7 @@ export async function installClaudeMcp(targetDir) {
198
198
 
199
199
  try {
200
200
  // Copy hooks, commands, config, personality, plugin, bmad config files
201
- const silentSpinner = { start: () => {}, succeed: () => {}, fail: () => {} };
201
+ const silentSpinner = { start: () => {}, stop: () => {}, succeed: () => {}, fail: () => {}, warn: () => {}, info: () => {}, stopAndPersist: () => {}, get text() { return ''; }, set text(_) {}, get isSpinning() { return false; } };
202
202
  const installer = await import('../installer.js');
203
203
  await installer.copyHookFiles(targetDir, silentSpinner);
204
204
  await installer.copyCommandFiles(targetDir, silentSpinner);