agentvibes 5.6.9 → 5.7.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 (95) hide show
  1. package/.agentvibes/config.json +3 -38
  2. package/.claude/commands/agent-vibes/provider.md +0 -0
  3. package/.claude/config/audio-effects.cfg +1 -1
  4. package/.claude/config/background-music-position.txt +6 -8
  5. package/.claude/config/reverb-level.txt +0 -0
  6. package/.claude/github-star-reminder.txt +1 -1
  7. package/.claude/hooks/bmad-tts-injector.sh +49 -21
  8. package/.claude/hooks/provider-commands.sh +16 -4
  9. package/.claude/hooks/provider-manager.sh +38 -0
  10. package/.claude/hooks/stop.sh +2 -27
  11. package/.claude/hooks/voice-manager.sh +50 -2
  12. package/.claude/hooks-windows/play-tts.ps1 +34 -1
  13. package/.claude/hooks-windows/tts-watcher.ps1 +122 -0
  14. package/.claude/piper-voices-dir.txt +1 -1
  15. package/.mcp.json +13 -33
  16. package/README.md +6 -8
  17. package/RELEASE_NOTES.md +32 -0
  18. package/bin/agent-vibes +39 -39
  19. package/package.json +1 -1
  20. package/src/bmad-detector.js +85 -71
  21. package/src/cli/list-personalities.js +110 -110
  22. package/src/cli/list-voices.js +114 -114
  23. package/src/commands/bmad-voices.js +394 -394
  24. package/src/commands/install-mcp.js +476 -476
  25. package/src/console/brand-colors.js +13 -13
  26. package/src/console/constants/personalities.js +44 -44
  27. package/src/console/tabs/help-tab.js +314 -314
  28. package/src/console/tabs/readme-tab.js +272 -272
  29. package/src/console/widgets/destroy-list.js +25 -25
  30. package/src/console/widgets/notice.js +55 -55
  31. package/src/console/widgets/personality-picker.js +213 -213
  32. package/src/i18n/de.js +202 -202
  33. package/src/i18n/es.js +202 -202
  34. package/src/i18n/fr.js +202 -202
  35. package/src/i18n/hi.js +202 -202
  36. package/src/i18n/ja.js +202 -202
  37. package/src/i18n/ko.js +202 -202
  38. package/src/i18n/pt.js +202 -202
  39. package/src/i18n/strings.js +54 -54
  40. package/src/i18n/zh-CN.js +202 -202
  41. package/src/installer/language-screen.js +31 -31
  42. package/src/installer/music-file-input.js +304 -304
  43. package/src/installer.js +70 -7
  44. package/src/services/agent-voice-store.js +59 -12
  45. package/src/services/config-service.js +264 -264
  46. package/src/services/language-service.js +47 -47
  47. package/src/services/provider-service.js +143 -143
  48. package/src/utils/audio-duration-validator.js +298 -298
  49. package/src/utils/audio-format-validator.js +277 -277
  50. package/src/utils/dependency-checker.js +469 -469
  51. package/src/utils/file-ownership-verifier.js +358 -358
  52. package/src/utils/list-formatter.js +194 -194
  53. package/src/utils/music-file-validator.js +285 -285
  54. package/src/utils/preview-list-prompt.js +136 -136
  55. package/src/utils/secure-music-storage.js +412 -412
  56. package/.agentvibes/LITE-MODE.md +0 -236
  57. package/.agentvibes/README.md +0 -136
  58. package/.agentvibes/backup/session-start-tts.sh.20251210_212814 +0 -141
  59. package/.agentvibes/backups/agents/analyst_20260204_144958.md +0 -78
  60. package/.agentvibes/backups/agents/architect_20260204_144958.md +0 -72
  61. package/.agentvibes/backups/agents/dev_20260204_144958.md +0 -74
  62. package/.agentvibes/backups/agents/pm_20260204_144958.md +0 -72
  63. package/.agentvibes/backups/agents/quick-flow-solo-dev_20260204_144958.md +0 -64
  64. package/.agentvibes/backups/agents/sm_20260204_144958.md +0 -87
  65. package/.agentvibes/backups/agents/tea_20260204_144958.md +0 -79
  66. package/.agentvibes/backups/agents/tech-writer_20260204_144958.md +0 -82
  67. package/.agentvibes/backups/agents/ux-designer_20260204_144958.md +0 -80
  68. package/.agentvibes/config/README-personality-defaults.md +0 -162
  69. package/.agentvibes/config/agentvibes.json +0 -1
  70. package/.agentvibes/config/mode.txt +0 -1
  71. package/.agentvibes/config/personality-voice-defaults.default.json +0 -21
  72. package/.agentvibes/config/save-audio.txt +0 -1
  73. package/.agentvibes/config/voice-metadata.json +0 -160
  74. package/.agentvibes/hooks/help.sh +0 -191
  75. package/.agentvibes/hooks/post-tool-use-lite.sh +0 -111
  76. package/.agentvibes/hooks/save-audio-manager.sh +0 -162
  77. package/.agentvibes/hooks/session-start-full-optimized.sh +0 -102
  78. package/.agentvibes/hooks/session-start-full.sh +0 -142
  79. package/.agentvibes/hooks/session-start-lite-v2.sh +0 -34
  80. package/.agentvibes/hooks/session-start-lite.sh +0 -29
  81. package/.agentvibes/hooks/stop-lite.sh +0 -115
  82. package/.agentvibes/hooks/switch-mode.sh +0 -215
  83. package/.agentvibes/output-styles/audio-summary.md +0 -30
  84. package/.claude/audio/voice-samples/piper/alan.wav +0 -0
  85. package/.claude/audio/voice-samples/piper/amy.wav +0 -0
  86. package/.claude/audio/voice-samples/piper/charlotte.wav +0 -0
  87. package/.claude/audio/voice-samples/piper/joe.wav +0 -0
  88. package/.claude/audio/voice-samples/piper/john.wav +0 -0
  89. package/.claude/audio/voice-samples/piper/katherine.wav +0 -0
  90. package/.claude/audio/voice-samples/piper/kristin.wav +0 -0
  91. package/.claude/audio/voice-samples/piper/linda.wav +0 -0
  92. package/.claude/audio/voice-samples/piper/marcus.wav +0 -0
  93. package/.claude/audio/voice-samples/piper/ryan.wav +0 -0
  94. package/.claude/hooks/post-response.sh +0 -41
  95. package/bin/ensure-soprano-running.sh +0 -43
@@ -1,40 +1,5 @@
1
1
  {
2
- "defaultVoice": "libritts-speaker-925",
3
- "ttsProvider": "piper",
4
- "provider": "soprano",
5
- "backgroundMusic": {
6
- "enabled": false,
7
- "track": "agent_vibes_japanese_city_pop_v1_loop.mp3",
8
- "volume": 20
9
- },
10
- "music": {
11
- "enabled": true,
12
- "track": "agentvibes_nature_sounds_rain.mp3"
13
- },
14
- "musicFavorites": [
15
- "agentvibes_blues_smooth_guitar.mp3",
16
- "agentvibes_lofi_beats_coding.mp3",
17
- "agentvibes_nature_sounds_rain.mp3",
18
- "agent_vibes_ganawa_ambient_v2_loop.mp3"
19
- ],
20
- "effects": {
21
- "reverb": true,
22
- "reverbAmount": 0.6,
23
- "pitch": 2,
24
- "reverbPreset": "medium"
25
- },
26
- "verbosity": "high",
27
- "personality": "grandpa",
28
- "voice": "16Speakers::Cori_Samuel",
29
- "pretext": "Agent Vibes Here",
30
- "setupCompleted": true,
31
- "ttsEngine": "piper",
32
- "thumbsUp": [
33
- "en_US-libritts-high::Adam",
34
- "en_US-libritts_r-medium::Adam-2"
35
- ],
36
- "favorites": [
37
- "en_US-libritts-high::Adam",
38
- "en_US-libritts_r-medium::Adam-2"
39
- ]
2
+ "audio_destination": "remote",
3
+ "audio_ssh_alias": "laptop-win",
4
+ "setupCompleted": true
40
5
  }
File without changes
@@ -1,6 +1,6 @@
1
1
 
2
2
  llm:default|light||0.15|en_US-lessac-high||piper
3
- llm:claude-code|light|agent_vibes_chillwave_v2_loop.mp3|0.15|en_US-lessac-high|Claude Code here|piper
3
+ llm:claude-code|light|agent_vibes_cumbia_v1_loop.mp3|0.05|en_US-libritts-high::Mike-3|Ubuntu RDP Agentvibes|piper
4
4
  llm:copilot|light|agent_vibes_bossa_nova_v2_loop.mp3|0.15|en_US-libritts-high::Anna-11|Copilot here|piper
5
5
  llm:codex|light|agent_vibes_chillwave_v2_loop.mp3|0.15|en_US-lessac-high|Codex here|piper
6
6
  llm:hermes|light|agent_vibes_bachata_v1_loop.mp3|0.15|en_US-libritts-high::Leo-8|Hermes here|piper
@@ -16,14 +16,12 @@ Agent Vibes Ganawa Ambient v2-loop.mp3:.00000000000000000002815996
16
16
  Agent Vibes Tabla Dream Pop v1-loop.mp3:.00000000000000000009067943
17
17
  Agent Vibes ChillWave v2-loop.mp3:.00000000000000000007080511
18
18
  Agent Vibes Harpsichord v2-loop.mp3:.00000000000000000013140818
19
- agent_vibes_japanese_city_pop_v1_loop.mp3:6.054512
20
19
  agent_vibes_bossa_nova_v2_loop.mp3:5.369524
21
20
  agent_vibes_salsa_v2_loop.mp3:9.972790
22
21
  agent_vibes_arabic_v2_loop.mp3:.00000000000000000006132724
23
- agent_vibes_chillwave_v2_loop.mp3:14.628390
24
- agent_vibes_bachata_v1_loop.mp3:.00000000000000000005344000
25
- agent_vibes_goa_trance_v2_loop.mp3:.00000000000000000002499918
26
- agentvibes_soft_flamenco_loop.mp3:.00000000000000000014899091
27
- agent_vibes_celtic_harp_v1_loop.mp3:5.891973
28
- agent_vibes_dark_chill_step_loop.mp3:5.717823
29
- agent_vibes_cumbia_v1_loop.mp3:10.472018
22
+ agent_vibes_cumbia_v1_loop.mp3:.00000000000000000010256000
23
+ agent_vibes_bachata_v1_loop.mp3:5.125714
24
+ agent_vibes_chillwave_v2_loop.mp3:.00000000000000000003308191
25
+ Midnight Charleston Stomp.mp3:.00000000000000000010960000
26
+ agent_vibes_japanese_city_pop_v1_loop.mp3:11.702675
27
+ agentvibes_soft_flamenco_loop.mp3:9.589660
File without changes
@@ -1 +1 @@
1
- 20260509
1
+ 20260511
@@ -49,7 +49,7 @@ GRAY='\033[0;90m'
49
49
  NC='\033[0m' # No Color
50
50
 
51
51
  # Detect BMAD installation and version
52
- # Supports both v4 (.bmad-core/) and v6-alpha (.bmad/) installations
52
+ # Supports both v4 (.bmad-core/) and v6-alpha (.bmad/, _bmad/) installations
53
53
  detect_bmad() {
54
54
  local bmad_core_dir=""
55
55
 
@@ -58,6 +58,11 @@ detect_bmad() {
58
58
  bmad_core_dir=".bmad"
59
59
  elif [[ -d "../.bmad" ]]; then
60
60
  bmad_core_dir="../.bmad"
61
+ # Check for _bmad (current BMAD installer default — underscore prefix, project-local)
62
+ elif [[ -d "_bmad" ]]; then
63
+ bmad_core_dir="_bmad"
64
+ elif [[ -d "../_bmad" ]]; then
65
+ bmad_core_dir="../_bmad"
61
66
  # Check for v6-alpha without dot (legacy naming)
62
67
  elif [[ -d "bmad" ]]; then
63
68
  bmad_core_dir="bmad"
@@ -73,6 +78,11 @@ detect_bmad() {
73
78
  bmad_core_dir="bmad-core"
74
79
  elif [[ -d "../bmad-core" ]]; then
75
80
  bmad_core_dir="../bmad-core"
81
+ # Check home-dir global BMAD install (~/_bmad is the default for standalone BMAD installs)
82
+ elif [[ -d "$HOME/_bmad" ]]; then
83
+ bmad_core_dir="$HOME/_bmad"
84
+ elif [[ -d "$HOME/.bmad" ]]; then
85
+ bmad_core_dir="$HOME/.bmad"
76
86
  else
77
87
  echo -e "${RED}❌ BMAD installation not found${NC}" >&2
78
88
  echo -e "${GRAY} Looked for bmad/, .bmad-core/, or bmad-core/ directory${NC}" >&2
@@ -85,21 +95,34 @@ detect_bmad() {
85
95
  # Find all BMAD agents
86
96
  find_agents() {
87
97
  local bmad_core="$1"
88
- local agents_dir=""
98
+ local found=0
99
+
100
+ # v6.6+: agents under .claude/skills/*/agents/ (new BMAD structure)
101
+ local skills_dir=".claude/skills"
102
+ if [[ -d "$skills_dir" ]]; then
103
+ while IFS= read -r -d '' agent_file; do
104
+ echo "$agent_file"
105
+ found=1
106
+ done < <(find "$skills_dir" -path "*/agents/*.md" -type f -print0 2>/dev/null)
107
+ fi
89
108
 
90
- # Check for v6-alpha structure (bmad/bmm/agents/)
109
+ # v6.x: bmad/bmm/agents/
91
110
  if [[ -d "$bmad_core/bmm/agents" ]]; then
92
- agents_dir="$bmad_core/bmm/agents"
93
- # Check for v4 structure (.bmad-core/agents/)
94
- elif [[ -d "$bmad_core/agents" ]]; then
95
- agents_dir="$bmad_core/agents"
96
- else
111
+ find "$bmad_core/bmm/agents" -name "*.md" -type f
112
+ found=1
113
+ fi
114
+
115
+ # v4: .bmad-core/agents/
116
+ if [[ -d "$bmad_core/agents" ]]; then
117
+ find "$bmad_core/agents" -name "*.md" -type f
118
+ found=1
119
+ fi
120
+
121
+ if [[ $found -eq 0 ]]; then
97
122
  echo -e "${RED}❌ Agents directory not found in $bmad_core${NC}" >&2
98
- echo -e "${GRAY} Tried: $bmad_core/bmm/agents/ and $bmad_core/agents/${NC}" >&2
123
+ echo -e "${GRAY} Tried: $bmad_core/bmm/agents/, $bmad_core/agents/, .claude/skills/*/agents/${NC}" >&2
99
124
  return 1
100
125
  fi
101
-
102
- find "$agents_dir" -name "*.md" -type f
103
126
  }
104
127
 
105
128
  # Check if agent has TTS injection
@@ -217,6 +240,15 @@ inject_tts() {
217
240
  return 0
218
241
  fi
219
242
 
243
+ # Detect format BEFORE creating backups — skip v6.6+ plain Markdown agents (no activation section)
244
+ local is_v6=false
245
+ if grep -q "<activation" "$agent_file"; then
246
+ is_v6=true
247
+ elif ! grep -q "activation-instructions:" "$agent_file"; then
248
+ echo -e "${GRAY} ℹ️ Skipped (v6.6+ format): $(basename "$agent_file")${NC}"
249
+ return 2
250
+ fi
251
+
220
252
  # Create backup directory for centralized timestamped backups
221
253
  local backup_dir=".agentvibes/backups/agents"
222
254
  mkdir -p "$backup_dir"
@@ -231,15 +263,6 @@ inject_tts() {
231
263
 
232
264
  echo -e "${GRAY} 📦 Backup saved: $backup_dir/$backup_name${NC}"
233
265
 
234
- # Detect v4 vs v6 structure
235
- local is_v6=false
236
- if grep -q "<activation" "$agent_file"; then
237
- is_v6=true
238
- elif ! grep -q "activation-instructions:" "$agent_file"; then
239
- echo -e "${RED}❌ No activation section found in: $(basename "$agent_file")${NC}"
240
- return 1
241
- fi
242
-
243
266
  # Create TTS injection script based on version
244
267
  if [[ "$is_v6" == "true" ]]; then
245
268
  # v6 format: XML-style with <step n="4.5">
@@ -411,6 +434,7 @@ enable_all() {
411
434
  local agents=$(find_agents "$bmad_core")
412
435
  local success_count=0
413
436
  local skip_count=0
437
+ local format_skip_count=0
414
438
  local fail_count=0
415
439
 
416
440
  # Track modified files and backups for summary
@@ -423,7 +447,10 @@ enable_all() {
423
447
  continue
424
448
  fi
425
449
 
426
- if inject_tts "$agent_file"; then
450
+ inject_tts "$agent_file" && local inject_result=0 || local inject_result=$?
451
+ if [[ $inject_result -eq 2 ]]; then
452
+ format_skip_count=$((format_skip_count + 1))
453
+ elif [[ $inject_result -eq 0 ]]; then
427
454
  success_count=$((success_count + 1))
428
455
  modified_files+=("$agent_file")
429
456
  # Track the backup file that was created
@@ -444,6 +471,7 @@ enable_all() {
444
471
  # Show results
445
472
  echo -e "${GREEN}✅ Successfully modified: $success_count agents${NC}"
446
473
  [[ $skip_count -gt 0 ]] && echo -e "${YELLOW}⏭️ Skipped (already enabled): $skip_count agents${NC}"
474
+ [[ $format_skip_count -gt 0 ]] && echo -e "${GRAY} ℹ️ Skipped (v6.6+ format, not supported): $format_skip_count agents${NC}"
447
475
  [[ $fail_count -gt 0 ]] && echo -e "${RED}❌ Failed: $fail_count agents${NC}"
448
476
  echo ""
449
477
 
@@ -449,6 +449,14 @@ provider_preview() {
449
449
  echo "🎤 Voice Preview ($current_provider)"
450
450
  echo ""
451
451
 
452
+ # Detect routing LLM so preview audio reaches SSH/remote receivers
453
+ # (without --llm, play-tts.sh plays locally on headless servers instead of
454
+ # forwarding to the configured Windows/remote receiver)
455
+ local _preview_llm _preview_llm_args
456
+ _preview_llm=$(detect_routing_llm 2>/dev/null || echo "")
457
+ _preview_llm_args=()
458
+ [[ -n "$_preview_llm" ]] && _preview_llm_args=(--llm "$_preview_llm")
459
+
452
460
  case "$current_provider" in
453
461
  piper)
454
462
  # Use the Piper voice manager's list functionality
@@ -468,7 +476,8 @@ provider_preview() {
468
476
  if verify_voice "$voice_arg"; then
469
477
  echo "🎤 Previewing Piper voice: $voice_arg"
470
478
  echo ""
471
- "$SCRIPT_DIR/play-tts.sh" "Hello, this is the $voice_arg voice. How do you like it?" "$voice_arg"
479
+ "$SCRIPT_DIR/play-tts.sh" "Hello, this is the $voice_arg voice. How do you like it?" \
480
+ "$voice_arg" "${_preview_llm_args[@]+"${_preview_llm_args[@]}"}"
472
481
  else
473
482
  echo "❌ Voice model not found: $voice_arg"
474
483
  echo ""
@@ -506,7 +515,8 @@ provider_preview() {
506
515
  local display_name="${voice_entry##*:}"
507
516
 
508
517
  echo "🔊 ${display_name}..."
509
- "$SCRIPT_DIR/play-tts.sh" "Hi, my name is ${display_name}" "$voice_name"
518
+ "$SCRIPT_DIR/play-tts.sh" "Hi, my name is ${display_name}" \
519
+ "$voice_name" "${_preview_llm_args[@]+"${_preview_llm_args[@]}"}"
510
520
 
511
521
  # Wait for the voice to finish playing before starting next one
512
522
  sleep 3
@@ -532,7 +542,8 @@ provider_preview() {
532
542
  if say -v ? 2>/dev/null | grep -qi "^${voice_arg} "; then
533
543
  echo "🎤 Previewing macOS voice: $voice_arg"
534
544
  echo ""
535
- "$SCRIPT_DIR/play-tts.sh" "Hello, this is ${voice_arg}. How do you like my voice?" "$voice_arg"
545
+ "$SCRIPT_DIR/play-tts.sh" "Hello, this is ${voice_arg}. How do you like my voice?" \
546
+ "$voice_arg" "${_preview_llm_args[@]+"${_preview_llm_args[@]}"}"
536
547
  else
537
548
  echo "❌ Voice not found: $voice_arg"
538
549
  echo ""
@@ -554,7 +565,8 @@ provider_preview() {
554
565
  for voice in "${sample_voices[@]}"; do
555
566
  if say -v ? 2>/dev/null | grep -qi "^${voice} "; then
556
567
  echo "🔊 ${voice}..."
557
- "$SCRIPT_DIR/play-tts.sh" "Hi, my name is ${voice}" "$voice"
568
+ "$SCRIPT_DIR/play-tts.sh" "Hi, my name is ${voice}" \
569
+ "$voice" "${_preview_llm_args[@]+"${_preview_llm_args[@]}"}"
558
570
  sleep 3
559
571
  fi
560
572
  done
@@ -356,6 +356,44 @@ get_provider_script_path() {
356
356
  echo "$provider_script"
357
357
  }
358
358
 
359
+ # @function detect_routing_llm
360
+ # @intent Detect the LLM name to use for SSH/remote routing in preview and sample commands
361
+ # @why preview/sample call play-tts.sh without --llm, so SSH routing is skipped and audio
362
+ # plays locally (no sound on headless servers). Detecting the remote LLM and passing
363
+ # --llm mirrors normal hook behaviour so audio reaches the configured receiver.
364
+ # @returns Echoes LLM name (e.g. "claude-code") or empty string if no remote routing configured
365
+ # @exitcode 0=always
366
+ detect_routing_llm() {
367
+ # Priority 1: AGENTVIBES_LLM_KEY env var (set during active TTS sessions)
368
+ if [[ -n "${AGENTVIBES_LLM_KEY:-}" ]] && [[ "$AGENTVIBES_LLM_KEY" =~ ^llm:([a-zA-Z0-9][a-zA-Z0-9_-]*)$ ]]; then
369
+ echo "${BASH_REMATCH[1]}"
370
+ return 0
371
+ fi
372
+
373
+ # Priority 2: First mode=remote entry in transport-config.json
374
+ local _transport_cfg="$HOME/.agentvibes/transport-config.json"
375
+ if [[ -f "$_transport_cfg" ]] && command -v python3 &>/dev/null; then
376
+ local _remote_llm
377
+ _remote_llm=$(AGENTVIBES_CFG="$_transport_cfg" python3 - <<'PYEOF'
378
+ import json, os, sys
379
+ try:
380
+ d = json.load(open(os.environ['AGENTVIBES_CFG'], encoding='utf-8'))
381
+ hits = [k for k, v in d.items() if isinstance(v, dict) and v.get('mode') == 'remote']
382
+ print(hits[0] if hits else '')
383
+ except Exception:
384
+ print('')
385
+ PYEOF
386
+ )
387
+ # Validate the key is a safe LLM name before returning
388
+ if [[ -n "$_remote_llm" ]] && [[ "$_remote_llm" =~ ^[a-zA-Z0-9][a-zA-Z0-9_-]*$ ]]; then
389
+ echo "$_remote_llm"
390
+ return 0
391
+ fi
392
+ fi
393
+
394
+ echo ""
395
+ }
396
+
359
397
  # AI NOTE: This file provides the core abstraction layer for multi-provider TTS.
360
398
  # All provider state is managed through simple text files for simplicity and reliability.
361
399
  # Project-local configuration takes precedence over global to support per-project providers.
@@ -32,32 +32,7 @@ fi
32
32
  # Only run stop hook in LITE mode
33
33
  # (Full mode uses tool calls for TTS, not stop hooks)
34
34
  if [[ "$CURRENT_MODE" == "lite" ]]; then
35
- # Claude Code sends JSON on stdin: {"transcript_path": "..."}
36
- HOOK_INPUT=$(cat)
37
- TRANSCRIPT_PATH=$(echo "$HOOK_INPUT" | jq -r '.transcript_path // empty' 2>/dev/null || true)
38
-
39
- [[ -z "$TRANSCRIPT_PATH" ]] && exit 0
40
- [[ ! -f "$TRANSCRIPT_PATH" ]] && exit 0
41
-
42
- # Extract Audio Summary ONLY from the most recent assistant response.
43
- # Key insight: we must NOT search earlier responses — if the latest response
44
- # has no Audio Summary: marker, we stay silent (no stale replay).
45
- SUMMARY=$(jq --slurp -r '
46
- [.[] | try select(.message.role == "assistant")] | last // null
47
- | if . == null then "" else
48
- [.message.content[]? | select(.type == "text") | .text] | join("\n")
49
- end
50
- | if test("[Aa]udio [Ss]ummary:") then . else "" end
51
- ' "$TRANSCRIPT_PATH" 2>/dev/null \
52
- | sed -n 's/.*[Aa]udio [Ss]ummary:[[:space:]]*\(.*\)/\1/p' \
53
- | head -1 \
54
- | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
55
-
56
- [[ -z "$SUMMARY" ]] && exit 0
57
-
58
- # Speak via AgentVibes play-tts.sh
59
- if [[ -f "$PROJECT_ROOT/.claude/hooks/play-tts.sh" ]]; then
60
- AGENTVIBES_MIN_TOKENS=3 AGENTVIBES_SHORT_TOKENS=50 \
61
- bash "$PROJECT_ROOT/.claude/hooks/play-tts.sh" "$SUMMARY" >/dev/null 2>&1 &
35
+ if [[ -f "$AGENTVIBES_HOOKS/stop-lite.sh" ]]; then
36
+ bash "$AGENTVIBES_HOOKS/stop-lite.sh" "$@"
62
37
  fi
63
38
  fi
@@ -561,8 +561,55 @@ case "$1" in
561
561
  fi
562
562
  ;;
563
563
 
564
+ sample)
565
+ # Play a sample phrase with the specified voice (used by /agent-vibes:sample)
566
+ SAMPLE_VOICE="${2:-}"
567
+ if [[ -z "$SAMPLE_VOICE" ]]; then
568
+ echo "❌ Usage: /agent-vibes:sample <voice-name>"
569
+ echo ""
570
+ echo "Examples:"
571
+ echo " /agent-vibes:sample en_US-amy-medium"
572
+ echo " /agent-vibes:sample Ryan"
573
+ exit 1
574
+ fi
575
+
576
+ # Source provider-manager.sh first — get_active_provider and detect_routing_llm
577
+ # are both defined there; sourcing after calling them silently produces empty values.
578
+ source "$SCRIPT_DIR/provider-manager.sh" 2>/dev/null || true
579
+
580
+ ACTIVE_PROVIDER=$(get_active_provider)
581
+
582
+ # Friendly name resolution for Piper and transport providers
583
+ case "$ACTIVE_PROVIDER" in
584
+ piper|ssh-remote|agentvibes-receiver|termux-ssh)
585
+ source "$SCRIPT_DIR/piper-voice-manager.sh" 2>/dev/null || true
586
+ SAMPLE_META="$(realpath "$SCRIPT_DIR/../../.agentvibes/config/voice-metadata.json" 2>/dev/null || echo "")"
587
+ if [[ -f "$SAMPLE_META" ]] && command -v jq >/dev/null 2>&1; then
588
+ SAMPLE_RESOLVED=$(jq -r --arg n "$(to_lower "$SAMPLE_VOICE")" '
589
+ .voices | to_entries[] |
590
+ select(.key == $n or (.value.displayName | ascii_downcase) == $n) |
591
+ .value.id
592
+ ' "$SAMPLE_META" 2>/dev/null | head -1)
593
+ if [[ -n "$SAMPLE_RESOLVED" ]] && [[ "$SAMPLE_RESOLVED" =~ ^[a-zA-Z0-9_.:+-]+$ ]]; then
594
+ echo "🔍 Resolved '${SAMPLE_VOICE}' → '${SAMPLE_RESOLVED}'"
595
+ SAMPLE_VOICE="$SAMPLE_RESOLVED"
596
+ fi
597
+ fi
598
+ ;;
599
+ esac
600
+
601
+ # Detect routing LLM so SSH-remote setups forward audio to the receiver
602
+ SAMPLE_LLM=$(detect_routing_llm 2>/dev/null || echo "")
603
+ SAMPLE_LLM_ARG=()
604
+ [[ -n "$SAMPLE_LLM" ]] && SAMPLE_LLM_ARG=(--llm "$SAMPLE_LLM")
605
+
606
+ echo "🎤 Sampling voice: $SAMPLE_VOICE"
607
+ "$SCRIPT_DIR/play-tts.sh" "Hi, I'm ${SAMPLE_VOICE}. How does my voice sound?" \
608
+ "$SAMPLE_VOICE" "${SAMPLE_LLM_ARG[@]+"${SAMPLE_LLM_ARG[@]}"}"
609
+ ;;
610
+
564
611
  *)
565
- echo "Usage: voice-manager.sh [list|switch|get|replay|whoami] [voice_name]"
612
+ echo "Usage: voice-manager.sh [list|switch|get|replay|whoami|sample] [voice_name]"
566
613
  echo ""
567
614
  echo "Commands:"
568
615
  echo " list - List all available voices"
@@ -570,6 +617,7 @@ case "$1" in
570
617
  echo " get - Get current voice name"
571
618
  echo " replay [N] - Replay Nth most recent audio (default: 1)"
572
619
  echo " whoami - Show current voice and personality"
620
+ echo " sample <voice_name> - Play sample audio with the given voice"
573
621
  exit 1
574
622
  ;;
575
- esac
623
+ esac
@@ -22,7 +22,17 @@ param(
22
22
  # CLAUDE_PROJECT_DIR value here so per-project config is found even when
23
23
  # Bash tool calls do not propagate CLAUDE_PROJECT_DIR to child processes.
24
24
  [Parameter(Mandatory = $false)]
25
- [string]$ProjectDir = ""
25
+ [string]$ProjectDir = "",
26
+
27
+ # Provider override from the remote sender (set by the watcher from the
28
+ # JSON payload's "provider" field). Overrides the local tts-provider.txt
29
+ # default so the Linux-side config fully controls which engine the Windows
30
+ # receiver uses — no Windows-side provider config needed.
31
+ # Per-LLM engine rows in audio-effects.cfg still take final priority for
32
+ # explicit Windows overrides (e.g. llm:copilot → windows-sapi).
33
+ # Accepts cross-platform aliases: "piper" = windows-piper, "sapi" = windows-sapi.
34
+ [Parameter(Mandatory = $false)]
35
+ [string]$ProviderOverride = ""
26
36
  )
27
37
 
28
38
  # Text-file handoff: the SSH receiver watcher writes long/special-char text to
@@ -99,6 +109,29 @@ switch ($ActiveProvider) {
99
109
  }
100
110
  }
101
111
 
112
+ # Apply remote provider override (from the JSON payload's "provider" field, passed
113
+ # by the SSH-receiver watcher via -ProviderOverride). This lets the Linux-side
114
+ # audio-effects.cfg row for llm:claude-code specify "piper" and have it honoured
115
+ # on Windows without requiring the Windows tts-provider.txt to be reconfigured.
116
+ # Priority: lower than per-LLM $_LlmEngine (audio-effects.cfg row, set later), higher
117
+ # than the global tts-provider.txt default set above.
118
+ if ($ProviderOverride) {
119
+ switch ($ProviderOverride) {
120
+ { $_ -in "windows-piper", "piper" } {
121
+ $ProviderScript = "$HooksDir\play-tts-piper.ps1"
122
+ if (-not (Test-Path $ProviderScript)) { $ProviderScript = "$HooksDir\play-tts-windows-piper.ps1" }
123
+ }
124
+ { $_ -in "windows-sapi", "sapi" } {
125
+ $ProviderScript = "$HooksDir\play-tts-sapi.ps1"
126
+ if (-not (Test-Path $ProviderScript)) { $ProviderScript = "$HooksDir\play-tts-windows-sapi.ps1" }
127
+ }
128
+ "soprano" { $ProviderScript = "$HooksDir\play-tts-soprano.ps1" }
129
+ default {
130
+ Write-Host "[WARNING] play-tts.ps1: Unknown ProviderOverride '$ProviderOverride' ignored" -ForegroundColor Yellow
131
+ }
132
+ }
133
+ }
134
+
102
135
  # Check if provider script exists
103
136
  if (-not (Test-Path $ProviderScript)) {
104
137
  Write-Host "[ERROR] Provider script not found: $ProviderScript" -ForegroundColor Red
@@ -0,0 +1,122 @@
1
+ #
2
+ # File: .claude/hooks-windows/tts-watcher.ps1
3
+ #
4
+ # AgentVibes TTS Queue Watcher — runs in the user's interactive session for audio access.
5
+ # The SSH receiver (session 0, no audio) writes JSON request files to the queue; this
6
+ # watcher picks them up and calls play-tts.ps1 in the user's desktop session.
7
+ #
8
+ # Single-instance guard: exit immediately if another watcher instance is already running.
9
+ $mutex = New-Object System.Threading.Mutex($false, 'Global\AgentVibesTtsWatcher')
10
+ if (-not $mutex.WaitOne(0)) { $mutex.Dispose(); exit 0 }
11
+
12
+ $QueueDir = "$env:USERPROFILE\.agentvibes\tts-queue"
13
+ $LogFile = "$env:USERPROFILE\.agentvibes\watcher.log"
14
+ $PlayTts = "$env:USERPROFILE\.claude\hooks-windows\play-tts.ps1"
15
+ if (-not (Test-Path $QueueDir)) { New-Item -ItemType Directory -Path $QueueDir -Force | Out-Null }
16
+
17
+ function Write-WatcherLog {
18
+ param([string]$Level, [string]$Msg)
19
+ $ts = Get-Date -Format 'yyyy-MM-ddTHH:mm:ss'
20
+ Add-Content -Path $LogFile -Value "$ts [$Level] $Msg" -ErrorAction SilentlyContinue
21
+ }
22
+
23
+ Write-WatcherLog "INFO" "Watcher started. PlayTts=$PlayTts exists=$(Test-Path $PlayTts)"
24
+
25
+ try {
26
+ while ($true) {
27
+ $files = Get-ChildItem "$QueueDir\req-*.json" -ErrorAction SilentlyContinue | Sort-Object CreationTime
28
+ foreach ($f in $files) {
29
+ # Rename to proc-* before processing — crash recovery on restart
30
+ $procFile = $f.FullName -replace '\\req-', '\proc-'
31
+ try { Rename-Item $f.FullName $procFile -ErrorAction Stop } catch { continue }
32
+ try {
33
+ $req = Get-Content $procFile -Raw | ConvertFrom-Json
34
+ # Validate voice before passing to command line
35
+ $safeVoice = if ($req.voice -and $req.voice -match '^[a-zA-Z0-9_\-\. :]+$') { $req.voice } else { "" }
36
+ $env:CLAUDE_PROJECT_DIR = $env:USERPROFILE
37
+ $env:AGENTVIBES_NO_PRETEXT = "1"
38
+ # Use SetEnvironmentVariable to truly unset (assignment to $null leaves empty string)
39
+ if ($req.music) { $env:AGENTVIBES_OVERRIDE_MUSIC = $req.music }
40
+ else { [System.Environment]::SetEnvironmentVariable("AGENTVIBES_OVERRIDE_MUSIC", $null, "Process") }
41
+ if ($req.volume) { $env:AGENTVIBES_OVERRIDE_VOLUME = $req.volume }
42
+ else { [System.Environment]::SetEnvironmentVariable("AGENTVIBES_OVERRIDE_VOLUME", $null, "Process") }
43
+ if ($req.effects) { $env:AGENTVIBES_OVERRIDE_EFFECTS = $req.effects }
44
+ else { [System.Environment]::SetEnvironmentVariable("AGENTVIBES_OVERRIDE_EFFECTS", $null, "Process") }
45
+
46
+ if (Test-Path $PlayTts) {
47
+ # Play remote arrival prefix sound if configured
48
+ $prefixSoundFile = "$env:USERPROFILE\.agentvibes\remote-prefix-sound.txt"
49
+ if (Test-Path $prefixSoundFile) {
50
+ $prefixSound = (Get-Content $prefixSoundFile -Raw).Trim()
51
+ if ($prefixSound -and (Test-Path $prefixSound)) {
52
+ $ffplay = Get-Command ffplay -ErrorAction SilentlyContinue
53
+ if ($ffplay) {
54
+ & $ffplay.Source -autoexit -nodisp -loglevel quiet $prefixSound 2>$null
55
+ }
56
+ }
57
+ }
58
+
59
+ $tempText = Join-Path $env:TEMP "agentvibes-tts-$($req.id).txt"
60
+ try {
61
+ [System.IO.File]::WriteAllText($tempText, $req.text, [System.Text.UTF8Encoding]::new($false))
62
+ $env:AGENTVIBES_TEXT_FILE = $tempText
63
+
64
+ $llmArg = @()
65
+ if ($req.llm) {
66
+ if ($req.llm -match '^[a-zA-Z0-9][a-zA-Z0-9_-]*$') {
67
+ $llmArg = @('-llm', $req.llm)
68
+ } else {
69
+ Write-WatcherLog "WARN" "Invalid LLM name '$($req.llm)' - using default"
70
+ }
71
+ }
72
+
73
+ # Provider override: the Linux sender embeds its configured provider
74
+ # (from receiver-provider.txt / audio-effects.cfg ENGINE column) in the
75
+ # JSON payload so the Windows receiver uses the Linux-side engine choice.
76
+ $providerArg = @()
77
+ if ($req.provider) {
78
+ $safeProvider = $req.provider.Trim()
79
+ if ($safeProvider -match '^[a-zA-Z0-9][a-zA-Z0-9_-]*$') {
80
+ $providerArg = @('-ProviderOverride', $safeProvider)
81
+ } else {
82
+ Write-WatcherLog "WARN" "Invalid provider '$safeProvider' in payload - ignored"
83
+ }
84
+ }
85
+
86
+ Write-WatcherLog "INFO" "play-tts id=$($req.id) voice=$safeVoice llm=$($req.llm) provider=$safeProvider"
87
+ $playOutput = & powershell.exe -NoProfile -ExecutionPolicy Bypass -File $PlayTts "__from_file__" $safeVoice @llmArg @providerArg 2>&1
88
+ if ($LASTEXITCODE -ne 0) {
89
+ Write-WatcherLog "ERROR" "play-tts exit=$LASTEXITCODE id=$($req.id) output=$($playOutput -join ' | ')"
90
+ } else {
91
+ Write-WatcherLog "INFO" "play-tts ok exit=0 id=$($req.id)"
92
+ }
93
+ } finally {
94
+ [System.Environment]::SetEnvironmentVariable("AGENTVIBES_TEXT_FILE", $null, "Process")
95
+ Remove-Item $tempText -Force -ErrorAction SilentlyContinue
96
+ }
97
+ } else {
98
+ # Fallback: Windows SAPI (built-in, no installation required)
99
+ Write-WatcherLog "WARN" "play-tts.ps1 not found - using SAPI fallback for id=$($req.id)"
100
+ Add-Type -AssemblyName System.Speech
101
+ $synth = New-Object System.Speech.Synthesis.SpeechSynthesizer
102
+ $synth.Speak($req.text)
103
+ $synth.Dispose()
104
+ }
105
+ Remove-Item $procFile -Force -ErrorAction SilentlyContinue
106
+ } catch {
107
+ Write-WatcherLog "ERROR" "id=$($req.id) err=$_"
108
+ Remove-Item $procFile -Force -ErrorAction SilentlyContinue
109
+ }
110
+ }
111
+ # Crash recovery: re-queue any proc-* files left from a previous watcher crash
112
+ $stale = Get-ChildItem "$QueueDir\proc-*.json" -ErrorAction SilentlyContinue
113
+ foreach ($s in $stale) {
114
+ $recovered = $s.FullName -replace '\\proc-', '\req-'
115
+ try { Rename-Item $s.FullName $recovered -ErrorAction SilentlyContinue } catch {}
116
+ }
117
+ Start-Sleep -Milliseconds 200
118
+ }
119
+ } finally {
120
+ $mutex.ReleaseMutex()
121
+ $mutex.Dispose()
122
+ }
@@ -1 +1 @@
1
- /home/fire/.claude/piper-voices
1
+ /home/administrator/.claude/piper-voices
package/.mcp.json CHANGED
@@ -1,50 +1,30 @@
1
1
  {
2
2
  "mcpServers": {
3
- "context7": {
3
+ "agentvibes": {
4
4
  "command": "npx",
5
5
  "args": [
6
- "@upstash/context7-mcp"
7
- ]
8
- },
9
- "sonarqube": {
10
- "command": "docker",
11
- "args": [
12
- "run",
13
- "-i",
14
- "--name",
15
- "sonarqube-mcp-server-agentvibes",
16
- "--rm",
17
- "--dns",
18
- "8.8.8.8",
19
- "--dns",
20
- "8.8.4.4",
21
- "-e",
22
- "SONARQUBE_TOKEN",
23
- "-e",
24
- "SONARQUBE_ORG",
25
- "-e",
26
- "STORAGE_PATH",
27
- "mcp/sonarqube"
6
+ "-y",
7
+ "--package=agentvibes",
8
+ "agentvibes-mcp-server"
28
9
  ],
29
10
  "env": {
30
- "SONARQUBE_TOKEN": "${SONARQUBE_TOKEN}",
31
- "SONARQUBE_ORG": "${SONARQUBE_ORG}",
32
- "STORAGE_PATH": "${STORAGE_PATH}"
11
+ "AGENTVIBES_MCP_FALLBACK": "copilot"
33
12
  }
34
13
  },
35
- "vercel": {
36
- "type": "sse",
37
- "url": "https://mcp.vercel.com"
14
+ "firecrawl": {
15
+ "command": "npx",
16
+ "args": [
17
+ "firecrawl-mcp"
18
+ ]
38
19
  },
39
- "agentvibes": {
20
+ "context7": {
40
21
  "command": "npx",
41
22
  "args": [
42
23
  "-y",
43
- "--package=agentvibes",
44
- "agentvibes-mcp-server"
24
+ "@upstash/context7-mcp"
45
25
  ],
46
26
  "env": {
47
- "AGENTVIBES_MCP_FALLBACK": "copilot"
27
+ "CONTEXT7_API_KEY": "${CONTEXT7_API_KEY}"
48
28
  }
49
29
  }
50
30
  }