agentvibes 5.6.9 → 5.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) 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/migrate-to-agentvibes.sh +24 -16
  9. package/.claude/hooks/personality-manager.sh +15 -2
  10. package/.claude/hooks/play-tts.sh +6 -0
  11. package/.claude/hooks/provider-commands.sh +16 -4
  12. package/.claude/hooks/provider-manager.sh +38 -0
  13. package/.claude/hooks/stop.sh +2 -27
  14. package/.claude/hooks/voice-manager.sh +50 -2
  15. package/.claude/hooks-windows/play-tts.ps1 +34 -1
  16. package/.claude/hooks-windows/tts-watcher.ps1 +122 -0
  17. package/.claude/piper-voices-dir.txt +1 -1
  18. package/.mcp.json +13 -33
  19. package/README.md +6 -8
  20. package/RELEASE_NOTES.md +32 -0
  21. package/bin/agent-vibes +39 -39
  22. package/package.json +1 -1
  23. package/src/bmad-detector.js +85 -71
  24. package/src/cli/list-personalities.js +110 -110
  25. package/src/cli/list-voices.js +114 -114
  26. package/src/commands/bmad-voices.js +394 -394
  27. package/src/commands/install-mcp.js +476 -476
  28. package/src/console/brand-colors.js +13 -13
  29. package/src/console/constants/personalities.js +44 -44
  30. package/src/console/tabs/help-tab.js +314 -314
  31. package/src/console/tabs/readme-tab.js +272 -272
  32. package/src/console/widgets/destroy-list.js +25 -25
  33. package/src/console/widgets/notice.js +55 -55
  34. package/src/console/widgets/personality-picker.js +213 -213
  35. package/src/i18n/de.js +202 -202
  36. package/src/i18n/es.js +202 -202
  37. package/src/i18n/fr.js +202 -202
  38. package/src/i18n/hi.js +202 -202
  39. package/src/i18n/ja.js +202 -202
  40. package/src/i18n/ko.js +202 -202
  41. package/src/i18n/pt.js +202 -202
  42. package/src/i18n/strings.js +54 -54
  43. package/src/i18n/zh-CN.js +202 -202
  44. package/src/installer/language-screen.js +31 -31
  45. package/src/installer/music-file-input.js +304 -304
  46. package/src/installer.js +330 -64
  47. package/src/services/agent-voice-store.js +59 -12
  48. package/src/services/config-service.js +264 -264
  49. package/src/services/language-service.js +47 -47
  50. package/src/services/llm-provider-service.js +57 -12
  51. package/src/services/provider-service.js +143 -143
  52. package/src/utils/audio-duration-validator.js +298 -298
  53. package/src/utils/audio-format-validator.js +277 -277
  54. package/src/utils/dependency-checker.js +469 -469
  55. package/src/utils/file-ownership-verifier.js +358 -358
  56. package/src/utils/list-formatter.js +194 -194
  57. package/src/utils/music-file-validator.js +285 -285
  58. package/src/utils/preview-list-prompt.js +136 -136
  59. package/src/utils/secure-music-storage.js +412 -412
  60. package/.agentvibes/LITE-MODE.md +0 -236
  61. package/.agentvibes/README.md +0 -136
  62. package/.agentvibes/backup/session-start-tts.sh.20251210_212814 +0 -141
  63. package/.agentvibes/backups/agents/analyst_20260204_144958.md +0 -78
  64. package/.agentvibes/backups/agents/architect_20260204_144958.md +0 -72
  65. package/.agentvibes/backups/agents/dev_20260204_144958.md +0 -74
  66. package/.agentvibes/backups/agents/pm_20260204_144958.md +0 -72
  67. package/.agentvibes/backups/agents/quick-flow-solo-dev_20260204_144958.md +0 -64
  68. package/.agentvibes/backups/agents/sm_20260204_144958.md +0 -87
  69. package/.agentvibes/backups/agents/tea_20260204_144958.md +0 -79
  70. package/.agentvibes/backups/agents/tech-writer_20260204_144958.md +0 -82
  71. package/.agentvibes/backups/agents/ux-designer_20260204_144958.md +0 -80
  72. package/.agentvibes/config/README-personality-defaults.md +0 -162
  73. package/.agentvibes/config/agentvibes.json +0 -1
  74. package/.agentvibes/config/mode.txt +0 -1
  75. package/.agentvibes/config/personality-voice-defaults.default.json +0 -21
  76. package/.agentvibes/config/save-audio.txt +0 -1
  77. package/.agentvibes/config/voice-metadata.json +0 -160
  78. package/.agentvibes/hooks/help.sh +0 -191
  79. package/.agentvibes/hooks/post-tool-use-lite.sh +0 -111
  80. package/.agentvibes/hooks/save-audio-manager.sh +0 -162
  81. package/.agentvibes/hooks/session-start-full-optimized.sh +0 -102
  82. package/.agentvibes/hooks/session-start-full.sh +0 -142
  83. package/.agentvibes/hooks/session-start-lite-v2.sh +0 -34
  84. package/.agentvibes/hooks/session-start-lite.sh +0 -29
  85. package/.agentvibes/hooks/stop-lite.sh +0 -115
  86. package/.agentvibes/hooks/switch-mode.sh +0 -215
  87. package/.agentvibes/output-styles/audio-summary.md +0 -30
  88. package/.claude/audio/voice-samples/piper/alan.wav +0 -0
  89. package/.claude/audio/voice-samples/piper/amy.wav +0 -0
  90. package/.claude/audio/voice-samples/piper/charlotte.wav +0 -0
  91. package/.claude/audio/voice-samples/piper/joe.wav +0 -0
  92. package/.claude/audio/voice-samples/piper/john.wav +0 -0
  93. package/.claude/audio/voice-samples/piper/katherine.wav +0 -0
  94. package/.claude/audio/voice-samples/piper/kristin.wav +0 -0
  95. package/.claude/audio/voice-samples/piper/linda.wav +0 -0
  96. package/.claude/audio/voice-samples/piper/marcus.wav +0 -0
  97. package/.claude/audio/voice-samples/piper/ryan.wav +0 -0
  98. package/.claude/hooks/post-response.sh +0 -41
  99. 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
 
@@ -46,22 +46,25 @@ echo -e "${BLUE}🔍 Checking for BMAD files in .claude/plugins/...${NC}"
46
46
 
47
47
  if [[ -f ".claude/plugins/bmad-voices-enabled.flag" ]]; then
48
48
  echo -e "${YELLOW} Found: bmad-voices-enabled.flag${NC}"
49
- mv .claude/plugins/bmad-voices-enabled.flag .agentvibes/bmad/
50
- echo -e "${GREEN} ✓ Moved to .agentvibes/bmad/${NC}"
49
+ mv -n .claude/plugins/bmad-voices-enabled.flag .agentvibes/bmad/ && \
50
+ echo -e "${GREEN} ✓ Moved to .agentvibes/bmad/${NC}" || \
51
+ echo -e "${YELLOW} ⚠ Already exists in .agentvibes/bmad/ — skipped${NC}"
51
52
  MIGRATED=true
52
53
  fi
53
54
 
54
55
  if [[ -f ".claude/plugins/bmad-party-mode-disabled.flag" ]]; then
55
56
  echo -e "${YELLOW} Found: bmad-party-mode-disabled.flag${NC}"
56
- mv .claude/plugins/bmad-party-mode-disabled.flag .agentvibes/bmad/
57
- echo -e "${GREEN} ✓ Moved to .agentvibes/bmad/${NC}"
57
+ mv -n .claude/plugins/bmad-party-mode-disabled.flag .agentvibes/bmad/ && \
58
+ echo -e "${GREEN} ✓ Moved to .agentvibes/bmad/${NC}" || \
59
+ echo -e "${YELLOW} ⚠ Already exists in .agentvibes/bmad/ — skipped${NC}"
58
60
  MIGRATED=true
59
61
  fi
60
62
 
61
63
  if [[ -f ".claude/plugins/.bmad-previous-settings" ]]; then
62
64
  echo -e "${YELLOW} Found: .bmad-previous-settings${NC}"
63
- mv .claude/plugins/.bmad-previous-settings .agentvibes/bmad/
64
- echo -e "${GREEN} ✓ Moved to .agentvibes/bmad/${NC}"
65
+ mv -n .claude/plugins/.bmad-previous-settings .agentvibes/bmad/ && \
66
+ echo -e "${GREEN} ✓ Moved to .agentvibes/bmad/${NC}" || \
67
+ echo -e "${YELLOW} ⚠ Already exists in .agentvibes/bmad/ — skipped${NC}"
65
68
  MIGRATED=true
66
69
  fi
67
70
 
@@ -72,8 +75,9 @@ echo -e "${BLUE}🔍 Checking for BMAD files in .claude/config/...${NC}"
72
75
 
73
76
  if [[ -f ".claude/config/bmad-voices.md" ]]; then
74
77
  echo -e "${YELLOW} Found: bmad-voices.md${NC}"
75
- mv .claude/config/bmad-voices.md .agentvibes/bmad/
76
- echo -e "${GREEN} ✓ Moved to .agentvibes/bmad/${NC}"
78
+ mv -n .claude/config/bmad-voices.md .agentvibes/bmad/ && \
79
+ echo -e "${GREEN} ✓ Moved to .agentvibes/bmad/${NC}" || \
80
+ echo -e "${YELLOW} ⚠ Already exists in .agentvibes/bmad/ — skipped${NC}"
77
81
  MIGRATED=true
78
82
  fi
79
83
 
@@ -97,29 +101,33 @@ echo -e "${BLUE}🔍 Checking for AgentVibes config in .claude/config/...${NC}"
97
101
 
98
102
  if [[ -f ".claude/config/agentvibes.json" ]]; then
99
103
  echo -e "${YELLOW} Found: agentvibes.json${NC}"
100
- mv .claude/config/agentvibes.json .agentvibes/config/
101
- echo -e "${GREEN} ✓ Moved to .agentvibes/config/${NC}"
104
+ mv -n .claude/config/agentvibes.json .agentvibes/config/ && \
105
+ echo -e "${GREEN} ✓ Moved to .agentvibes/config/${NC}" || \
106
+ echo -e "${YELLOW} ⚠ Already exists in .agentvibes/config/ — skipped${NC}"
102
107
  MIGRATED=true
103
108
  fi
104
109
 
105
110
  if [[ -f ".claude/config/personality-voice-defaults.default.json" ]]; then
106
111
  echo -e "${YELLOW} Found: personality-voice-defaults.default.json${NC}"
107
- mv .claude/config/personality-voice-defaults.default.json .agentvibes/config/
108
- echo -e "${GREEN} ✓ Moved to .agentvibes/config/${NC}"
112
+ mv -n .claude/config/personality-voice-defaults.default.json .agentvibes/config/ && \
113
+ echo -e "${GREEN} ✓ Moved to .agentvibes/config/${NC}" || \
114
+ echo -e "${YELLOW} ⚠ Already exists in .agentvibes/config/ — skipped${NC}"
109
115
  MIGRATED=true
110
116
  fi
111
117
 
112
118
  if [[ -f ".claude/config/personality-voice-defaults.json" ]]; then
113
119
  echo -e "${YELLOW} Found: personality-voice-defaults.json${NC}"
114
- mv .claude/config/personality-voice-defaults.json .agentvibes/config/
115
- echo -e "${GREEN} ✓ Moved to .agentvibes/config/${NC}"
120
+ mv -n .claude/config/personality-voice-defaults.json .agentvibes/config/ && \
121
+ echo -e "${GREEN} ✓ Moved to .agentvibes/config/${NC}" || \
122
+ echo -e "${YELLOW} ⚠ Already exists in .agentvibes/config/ — skipped${NC}"
116
123
  MIGRATED=true
117
124
  fi
118
125
 
119
126
  if [[ -f ".claude/config/README-personality-defaults.md" ]]; then
120
127
  echo -e "${YELLOW} Found: README-personality-defaults.md${NC}"
121
- mv .claude/config/README-personality-defaults.md .agentvibes/config/
122
- echo -e "${GREEN} ✓ Moved to .agentvibes/config/${NC}"
128
+ mv -n .claude/config/README-personality-defaults.md .agentvibes/config/ && \
129
+ echo -e "${GREEN} ✓ Moved to .agentvibes/config/${NC}" || \
130
+ echo -e "${YELLOW} ⚠ Already exists in .agentvibes/config/ — skipped${NC}"
123
131
  MIGRATED=true
124
132
  fi
125
133
 
@@ -276,6 +276,13 @@ case "$1" in
276
276
  exit 1
277
277
  fi
278
278
 
279
+ # Validate name: alphanumeric, hyphens, underscores only — no path traversal
280
+ if [[ ! "$NAME" =~ ^[a-zA-Z0-9_-]+$ ]]; then
281
+ echo "❌ Invalid personality name '$NAME'"
282
+ echo " Name must contain only letters, numbers, hyphens, and underscores"
283
+ exit 1
284
+ fi
285
+
279
286
  FILE="$PERSONALITIES_DIR/${NAME}.md"
280
287
  if [[ -f "$FILE" ]]; then
281
288
  echo "❌ Personality '$NAME' already exists"
@@ -306,8 +313,8 @@ Describe how the AI should generate messages for this personality.
306
313
  - "Example response 2"
307
314
  EOF
308
315
 
309
- # Replace NAME with actual name
310
- sed -i "s/NAME/$NAME/g" "$FILE"
316
+ # Replace NAME placeholder — use | as delimiter to avoid issues with / in paths
317
+ sed -i "s|NAME|${NAME}|g" "$FILE"
311
318
 
312
319
  echo "✅ Created new personality: $NAME"
313
320
  echo "📝 Edit the file: $FILE"
@@ -327,6 +334,12 @@ EOF
327
334
  exit 1
328
335
  fi
329
336
 
337
+ if [[ ! "$NAME" =~ ^[a-zA-Z0-9_-]+$ ]]; then
338
+ echo "❌ Invalid personality name '$NAME'"
339
+ echo " Name must contain only letters, numbers, hyphens, and underscores"
340
+ exit 1
341
+ fi
342
+
330
343
  FILE="$PERSONALITIES_DIR/${NAME}.md"
331
344
  if [[ ! -f "$FILE" ]]; then
332
345
  echo "❌ Personality '$NAME' not found"
@@ -335,6 +335,12 @@ if [[ -n "$VOICE_OVERRIDE" ]]; then
335
335
  esac
336
336
  fi
337
337
 
338
+ # Emit resolved voice and provider in verbose mode (used by tests and diagnostics)
339
+ if [[ "${AGENTVIBES_VERBOSE:-0}" == "1" ]]; then
340
+ [[ -n "${VOICE_OVERRIDE:-}" ]] && echo "voice=${VOICE_OVERRIDE}" >&2
341
+ echo "provider=${ACTIVE_PROVIDER}" >&2
342
+ fi
343
+
338
344
  # @function speak_text
339
345
  # @intent Route text to appropriate TTS provider
340
346
  # @why Reusable function for speaking, used by both single and learning modes
@@ -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