agentvibes 5.6.0 → 5.6.2
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.
- package/.agentvibes/config.json +3 -38
- package/.claude/config/audio-effects.cfg +1 -1
- package/.claude/config/background-music-enabled.txt +1 -1
- package/.claude/config/background-music-position.txt +6 -6
- package/.claude/github-star-reminder.txt +1 -1
- package/.claude/hooks/play-tts-ssh-remote.sh +119 -42
- package/.claude/hooks/play-tts-windows-receiver.sh +31 -0
- package/.claude/hooks/stop.sh +2 -27
- package/.claude/hooks-windows/play-tts-windows-sapi.ps1 +108 -108
- package/.claude/hooks-windows/play-tts.ps1 +58 -8
- package/.claude/piper-voices-dir.txt +1 -1
- package/.clawdbot/skill/README.md +326 -0
- package/.mcp.json +17 -27
- package/README.md +15 -2
- package/RELEASE_NOTES.md +64 -0
- package/bin/agent-vibes +39 -39
- package/package.json +1 -1
- package/src/bmad-detector.js +71 -71
- package/src/cli/list-personalities.js +110 -110
- package/src/cli/list-voices.js +114 -114
- package/src/commands/bmad-voices.js +394 -394
- package/src/commands/install-mcp.js +476 -476
- package/src/console/brand-colors.js +13 -13
- package/src/console/constants/personalities.js +44 -44
- package/src/console/modals/modal-overlay.js +247 -247
- package/src/console/navigation.js +5 -1
- package/src/console/tabs/agents-tab.js +5 -5
- package/src/console/tabs/help-tab.js +314 -314
- package/src/console/tabs/readme-tab.js +272 -272
- package/src/console/tabs/setup-tab.js +32 -17
- package/src/console/tabs/voices-tab.js +2 -2
- package/src/console/widgets/destroy-list.js +25 -25
- package/src/console/widgets/notice.js +55 -55
- package/src/console/widgets/personality-picker.js +213 -213
- package/src/console/widgets/reverb-picker.js +97 -97
- package/src/console/widgets/track-picker.js +1 -1
- package/src/i18n/de.js +202 -202
- package/src/i18n/es.js +202 -202
- package/src/i18n/fr.js +202 -202
- package/src/i18n/hi.js +202 -202
- package/src/i18n/ja.js +202 -202
- package/src/i18n/ko.js +202 -202
- package/src/i18n/pt.js +202 -202
- package/src/i18n/strings.js +54 -54
- package/src/i18n/zh-CN.js +202 -202
- package/src/installer/language-screen.js +31 -31
- package/src/installer/music-file-input.js +304 -304
- package/src/services/agent-voice-store.js +420 -423
- package/src/services/config-service.js +264 -264
- package/src/services/language-service.js +47 -47
- package/src/services/llm-provider-service.js +11 -4
- package/src/services/navigation-service.js +34 -10
- package/src/services/provider-service.js +143 -143
- package/src/utils/audio-duration-validator.js +298 -298
- package/src/utils/audio-format-validator.js +277 -277
- package/src/utils/dependency-checker.js +469 -469
- package/src/utils/file-ownership-verifier.js +358 -358
- package/src/utils/list-formatter.js +194 -194
- package/src/utils/music-file-validator.js +285 -285
- package/src/utils/preview-list-prompt.js +136 -136
- package/src/utils/secure-music-storage.js +412 -412
- package/.agentvibes/LITE-MODE.md +0 -236
- package/.agentvibes/README.md +0 -136
- package/.agentvibes/backup/session-start-tts.sh.20251210_212814 +0 -141
- package/.agentvibes/backups/agents/analyst_20260204_144958.md +0 -78
- package/.agentvibes/backups/agents/architect_20260204_144958.md +0 -72
- package/.agentvibes/backups/agents/dev_20260204_144958.md +0 -74
- package/.agentvibes/backups/agents/pm_20260204_144958.md +0 -72
- package/.agentvibes/backups/agents/quick-flow-solo-dev_20260204_144958.md +0 -64
- package/.agentvibes/backups/agents/sm_20260204_144958.md +0 -87
- package/.agentvibes/backups/agents/tea_20260204_144958.md +0 -79
- package/.agentvibes/backups/agents/tech-writer_20260204_144958.md +0 -82
- package/.agentvibes/backups/agents/ux-designer_20260204_144958.md +0 -80
- package/.agentvibes/config/README-personality-defaults.md +0 -162
- package/.agentvibes/config/agentvibes.json +0 -1
- package/.agentvibes/config/mode.txt +0 -1
- package/.agentvibes/config/personality-voice-defaults.default.json +0 -21
- package/.agentvibes/config/save-audio.txt +0 -1
- package/.agentvibes/config/voice-metadata.json +0 -160
- package/.agentvibes/hooks/help.sh +0 -191
- package/.agentvibes/hooks/post-tool-use-lite.sh +0 -111
- package/.agentvibes/hooks/save-audio-manager.sh +0 -162
- package/.agentvibes/hooks/session-start-full-optimized.sh +0 -102
- package/.agentvibes/hooks/session-start-full.sh +0 -142
- package/.agentvibes/hooks/session-start-lite-v2.sh +0 -34
- package/.agentvibes/hooks/session-start-lite.sh +0 -29
- package/.agentvibes/hooks/stop-lite.sh +0 -115
- package/.agentvibes/hooks/switch-mode.sh +0 -215
- package/.agentvibes/output-styles/audio-summary.md +0 -30
- package/.claude/audio/voice-samples/piper/alan.wav +0 -0
- package/.claude/audio/voice-samples/piper/amy.wav +0 -0
- package/.claude/audio/voice-samples/piper/charlotte.wav +0 -0
- package/.claude/audio/voice-samples/piper/joe.wav +0 -0
- package/.claude/audio/voice-samples/piper/john.wav +0 -0
- package/.claude/audio/voice-samples/piper/katherine.wav +0 -0
- package/.claude/audio/voice-samples/piper/kristin.wav +0 -0
- package/.claude/audio/voice-samples/piper/linda.wav +0 -0
- package/.claude/audio/voice-samples/piper/marcus.wav +0 -0
- package/.claude/audio/voice-samples/piper/ryan.wav +0 -0
- package/.claude/hooks/post-response.sh +0 -41
- package/bin/ensure-soprano-running.sh +0 -43
package/.agentvibes/config.json
CHANGED
|
@@ -1,40 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
3
|
-
"
|
|
4
|
-
"
|
|
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": "en_US-ljspeech-high",
|
|
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
|
}
|
|
@@ -55,7 +55,7 @@ default|reverb 20 50 50|agentvibes_soft_flamenco_loop.mp3|0.30
|
|
|
55
55
|
# Format: llm:<name>|REVERB_PRESET|BACKGROUND_FILE|BACKGROUND_VOLUME|VOICE|PRETEXT|ENGINE|||
|
|
56
56
|
|||
|
|
57
57
|
# Claude Code (claude-code CLI via CLAUDECODE=1 env or AGENTVIBES_LLM=claude-code)|||
|
|
58
|
-
llm:claude-code
|
|
58
|
+
llm:claude-code|off||||Agent Vibes Here|piper
|
|
59
59
|
|||
|
|
60
60
|
# GitHub Copilot Chat (VS Code MCP or AGENTVIBES_LLM=copilot)|||
|
|
61
61
|
llm:copilot|off||||Agent Vibes Here|piper
|
|
@@ -1 +1 @@
|
|
|
1
|
-
true
|
|
1
|
+
true
|
|
@@ -16,12 +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
|
-
agent_vibes_cumbia_v1_loop.mp3:5.717823
|
|
23
21
|
agent_vibes_arabic_v2_loop.mp3:.00000000000000000006132724
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
agent_vibes_cumbia_v1_loop.mp3:.00000000000000000010256000
|
|
23
|
+
agentvibes_soft_flamenco_loop.mp3:7.389660
|
|
24
|
+
agent_vibes_bachata_v1_loop.mp3:5.125714
|
|
25
|
+
agent_vibes_chillwave_v2_loop.mp3:.00000000000000000003308191
|
|
26
|
+
Midnight Charleston Stomp.mp3:.00000000000000000010960000
|
|
27
|
+
agent_vibes_japanese_city_pop_v1_loop.mp3:11.702675
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
20260502
|
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
#
|
|
3
3
|
# File: .claude/hooks/play-tts-ssh-remote.sh
|
|
4
4
|
#
|
|
5
|
-
# AgentVibes - SSH-Remote TTS Provider
|
|
6
|
-
# Sends text to remote device via SSH for local
|
|
5
|
+
# AgentVibes - SSH-Remote TTS Provider (v2 — JSON payload)
|
|
6
|
+
# Sends text + effects config to remote device via SSH for local playback
|
|
7
|
+
#
|
|
8
|
+
# The sender reads local audio-effects.cfg and bundles everything into a
|
|
9
|
+
# single base64-encoded JSON payload. The receiver is a thin executor.
|
|
7
10
|
#
|
|
8
11
|
# Copyright (c) 2025 Paul Preibisch
|
|
9
12
|
# Licensed under the Apache License, Version 2.0
|
|
@@ -17,74 +20,148 @@ AGENT_NAME="${3:-default}"
|
|
|
17
20
|
|
|
18
21
|
# Validate required input
|
|
19
22
|
if [[ -z "$TEXT" ]]; then
|
|
20
|
-
echo "❌ No text provided" >&2
|
|
21
23
|
echo "Usage: $0 <text> [voice] [agent_name]" >&2
|
|
22
24
|
exit 1
|
|
23
25
|
fi
|
|
24
26
|
|
|
25
|
-
# Get script directory
|
|
27
|
+
# Get script directory and project root
|
|
26
28
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
27
29
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
28
30
|
|
|
31
|
+
# Derive project name from directory
|
|
32
|
+
PROJECT_NAME=$(basename "$PROJECT_ROOT")
|
|
33
|
+
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
29
35
|
# Get SSH host from config
|
|
36
|
+
# ---------------------------------------------------------------------------
|
|
37
|
+
|
|
30
38
|
SSH_HOST=$(cat "$PROJECT_ROOT/.claude/ssh-remote-host.txt" 2>/dev/null || \
|
|
31
39
|
cat "$HOME/.claude/ssh-remote-host.txt" 2>/dev/null || echo "")
|
|
32
40
|
|
|
33
41
|
if [[ -z "$SSH_HOST" ]]; then
|
|
34
|
-
echo "
|
|
35
|
-
echo "
|
|
42
|
+
echo "SSH-Remote host not configured" >&2
|
|
43
|
+
echo "Set host: echo 'my-host' > .claude/ssh-remote-host.txt" >&2
|
|
36
44
|
exit 1
|
|
37
45
|
fi
|
|
38
46
|
|
|
39
|
-
# SECURITY: Validate SSH_HOST
|
|
40
|
-
# Must be a valid hostname, IP address, or SSH config alias (alphanumeric, dots, hyphens, underscores)
|
|
47
|
+
# SECURITY: Validate SSH_HOST format
|
|
41
48
|
if [[ ! "$SSH_HOST" =~ ^[a-zA-Z0-9][a-zA-Z0-9._-]*$ ]]; then
|
|
42
|
-
echo "
|
|
43
|
-
echo "💡 Host must be alphanumeric (may contain dots, hyphens, underscores)" >&2
|
|
44
|
-
exit 1
|
|
45
|
-
fi
|
|
46
|
-
|
|
47
|
-
# SECURITY: Reject hosts starting with hyphen (SSH option injection)
|
|
48
|
-
if [[ "$SSH_HOST" == -* ]]; then
|
|
49
|
-
echo "❌ Invalid SSH host: cannot start with hyphen" >&2
|
|
49
|
+
echo "Invalid SSH host format: $SSH_HOST" >&2
|
|
50
50
|
exit 1
|
|
51
51
|
fi
|
|
52
52
|
|
|
53
|
-
# SECURITY: Validate VOICE
|
|
53
|
+
# SECURITY: Validate VOICE
|
|
54
54
|
if [[ ! "$VOICE" =~ ^[a-zA-Z0-9_-]+$ ]]; then
|
|
55
|
-
echo "
|
|
55
|
+
echo "Invalid voice format: $VOICE" >&2
|
|
56
56
|
exit 1
|
|
57
57
|
fi
|
|
58
58
|
|
|
59
|
-
# SECURITY: Validate AGENT_NAME
|
|
59
|
+
# SECURITY: Validate AGENT_NAME
|
|
60
60
|
if [[ ! "$AGENT_NAME" =~ ^[a-zA-Z0-9_\ -]+$ ]]; then
|
|
61
|
-
echo "
|
|
61
|
+
echo "Invalid agent name format: $AGENT_NAME" >&2
|
|
62
62
|
exit 1
|
|
63
63
|
fi
|
|
64
64
|
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
65
|
+
# ---------------------------------------------------------------------------
|
|
66
|
+
# Read audio effects config for this agent
|
|
67
|
+
# ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
SOX_EFFECTS=""
|
|
70
|
+
BG_FILE=""
|
|
71
|
+
BG_VOLUME="0.10"
|
|
72
|
+
|
|
73
|
+
EFFECTS_CFG="$PROJECT_ROOT/.claude/config/audio-effects.cfg"
|
|
74
|
+
if [[ -f "$EFFECTS_CFG" ]]; then
|
|
75
|
+
CONFIG_LINE=$(grep "^${AGENT_NAME}|" "$EFFECTS_CFG" 2>/dev/null || \
|
|
76
|
+
grep "^default|" "$EFFECTS_CFG" 2>/dev/null || true)
|
|
77
|
+
if [[ -n "$CONFIG_LINE" ]]; then
|
|
78
|
+
IFS='|' read -r _ SOX_EFFECTS BG_FILE BG_VOLUME <<< "$CONFIG_LINE"
|
|
79
|
+
fi
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
# Read pretext if configured
|
|
83
|
+
PRETEXT=""
|
|
84
|
+
PRETEXT_FILE="$PROJECT_ROOT/.agentvibes/config/pretext.txt"
|
|
85
|
+
if [[ -f "$PRETEXT_FILE" ]]; then
|
|
86
|
+
PRETEXT=$(cat "$PRETEXT_FILE" 2>/dev/null || true)
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
# Read speed if configured
|
|
90
|
+
SPEED=""
|
|
91
|
+
SPEED_FILE="$PROJECT_ROOT/.agentvibes/config/speed.txt"
|
|
92
|
+
if [[ -f "$SPEED_FILE" ]]; then
|
|
93
|
+
SPEED=$(cat "$SPEED_FILE" 2>/dev/null || true)
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# Read the TTS provider the RECEIVER should use to generate audio.
|
|
97
|
+
# This is separate from the sender's own provider (which is "ssh-remote").
|
|
98
|
+
# Check receiver-provider.txt first, then fall back to "piper".
|
|
99
|
+
PROVIDER=""
|
|
100
|
+
RECEIVER_PROVIDER_FILE="$PROJECT_ROOT/.agentvibes/config/receiver-provider.txt"
|
|
101
|
+
if [[ -f "$RECEIVER_PROVIDER_FILE" ]]; then
|
|
102
|
+
PROVIDER=$(cat "$RECEIVER_PROVIDER_FILE" 2>/dev/null || true)
|
|
103
|
+
fi
|
|
104
|
+
# Also check home-level config
|
|
105
|
+
if [[ -z "$PROVIDER" ]]; then
|
|
106
|
+
RECEIVER_PROVIDER_FILE="$HOME/.agentvibes/config/receiver-provider.txt"
|
|
107
|
+
if [[ -f "$RECEIVER_PROVIDER_FILE" ]]; then
|
|
108
|
+
PROVIDER=$(cat "$RECEIVER_PROVIDER_FILE" 2>/dev/null || true)
|
|
109
|
+
fi
|
|
110
|
+
fi
|
|
111
|
+
# Validate — only known TTS providers (not transport providers like ssh-remote)
|
|
112
|
+
case "${PROVIDER:-}" in
|
|
113
|
+
piper|soprano|macos|windows-sapi) ;;
|
|
114
|
+
*) PROVIDER="piper" ;;
|
|
115
|
+
esac
|
|
116
|
+
|
|
117
|
+
# ---------------------------------------------------------------------------
|
|
118
|
+
# Build JSON payload
|
|
119
|
+
# ---------------------------------------------------------------------------
|
|
120
|
+
|
|
121
|
+
# SECURITY: Use jq if available for safe JSON construction, else manual escaping
|
|
122
|
+
build_json_payload() {
|
|
123
|
+
if command -v jq &>/dev/null; then
|
|
124
|
+
jq -n \
|
|
125
|
+
--arg text "$TEXT" \
|
|
126
|
+
--arg voice "$VOICE" \
|
|
127
|
+
--arg effects "$SOX_EFFECTS" \
|
|
128
|
+
--arg music "$BG_FILE" \
|
|
129
|
+
--arg volume "$BG_VOLUME" \
|
|
130
|
+
--arg project "$PROJECT_NAME" \
|
|
131
|
+
--arg pretext "$PRETEXT" \
|
|
132
|
+
--arg speed "$SPEED" \
|
|
133
|
+
--arg provider "$PROVIDER" \
|
|
134
|
+
'{text: $text, voice: $voice, effects: $effects, music: $music, volume: $volume, project: $project, pretext: $pretext, speed: $speed, provider: $provider}'
|
|
135
|
+
else
|
|
136
|
+
# Manual JSON — escape double quotes and backslashes in text
|
|
137
|
+
local escaped_text
|
|
138
|
+
escaped_text=$(printf '%s' "$TEXT" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g')
|
|
139
|
+
local escaped_pretext
|
|
140
|
+
escaped_pretext=$(printf '%s' "$PRETEXT" | sed 's/\\/\\\\/g; s/"/\\"/g')
|
|
141
|
+
printf '{"text":"%s","voice":"%s","effects":"%s","music":"%s","volume":"%s","project":"%s","pretext":"%s","speed":"%s","provider":"%s"}' \
|
|
142
|
+
"$escaped_text" "$VOICE" "$SOX_EFFECTS" "$BG_FILE" "$BG_VOLUME" "$PROJECT_NAME" "$escaped_pretext" "$SPEED" "$PROVIDER"
|
|
143
|
+
fi
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
JSON_PAYLOAD=$(build_json_payload)
|
|
147
|
+
|
|
148
|
+
# SECURITY: Base64-encode entire payload — safe for SSH transport
|
|
149
|
+
# base64 -w 0 is Linux (GNU coreutils), -b 0 is macOS (BSD)
|
|
150
|
+
if base64 --help 2>&1 | grep -q '\-w'; then
|
|
151
|
+
ENCODED_PAYLOAD=$(printf '%s' "$JSON_PAYLOAD" | base64 -w 0)
|
|
82
152
|
else
|
|
83
|
-
|
|
84
|
-
echo "💡 Install: agentvibes install --ssh-receiver" >&2
|
|
85
|
-
exit 1
|
|
153
|
+
ENCODED_PAYLOAD=$(printf '%s' "$JSON_PAYLOAD" | base64 -b 0 2>/dev/null || printf '%s' "$JSON_PAYLOAD" | base64 | tr -d '\n')
|
|
86
154
|
fi
|
|
87
155
|
|
|
88
|
-
#
|
|
89
|
-
|
|
156
|
+
# ---------------------------------------------------------------------------
|
|
157
|
+
# Send to receiver via SSH (fire and forget — backgrounded)
|
|
158
|
+
# ---------------------------------------------------------------------------
|
|
159
|
+
|
|
160
|
+
echo "Sending to $SSH_HOST..." >&2
|
|
161
|
+
|
|
162
|
+
# ForceCommand receiver: SSH_ORIGINAL_COMMAND passes the payload directly
|
|
163
|
+
ssh "$SSH_HOST" "$ENCODED_PAYLOAD" &
|
|
164
|
+
SSH_PID=$!
|
|
165
|
+
|
|
166
|
+
echo "Sent to $SSH_HOST (PID: $SSH_PID)" >&2
|
|
90
167
|
exit 0
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# play-tts-windows-receiver.sh
|
|
4
|
+
# Sends TTS to Windows AgentVibes receiver via SSH.
|
|
5
|
+
# The Windows receiver expects bare base64-encoded text as the SSH command.
|
|
6
|
+
#
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
TEXT="${1:-}"
|
|
10
|
+
VOICE="${2:-}"
|
|
11
|
+
|
|
12
|
+
[[ -z "$TEXT" ]] && { echo 'No text' >&2; exit 1; }
|
|
13
|
+
|
|
14
|
+
# Get host
|
|
15
|
+
HOST=$(cat "$HOME/.claude/windows-receiver-host.txt" 2>/dev/null || echo '')
|
|
16
|
+
[[ -z "$HOST" ]] && { echo '❌ No host: set ~/.claude/windows-receiver-host.txt' >&2; exit 1; }
|
|
17
|
+
|
|
18
|
+
# Validate host
|
|
19
|
+
[[ "$HOST" =~ ^[a-zA-Z0-9][a-zA-Z0-9._-]*$ ]] || { echo '❌ Invalid host' >&2; exit 1; }
|
|
20
|
+
|
|
21
|
+
# Strip markdown/emojis
|
|
22
|
+
TEXT=$(printf '%s' "$TEXT" | perl -CSD -pe '
|
|
23
|
+
s/[\x{1F300}-\x{1F9FF}]//g;
|
|
24
|
+
s/[\x{2600}-\x{27BF}]//g;
|
|
25
|
+
s/\*+//g; s/#+\s*//g; s/`//g;
|
|
26
|
+
' 2>/dev/null || printf '%s' "$TEXT")
|
|
27
|
+
|
|
28
|
+
ENCODED=$(printf '%s' "$TEXT" | base64 -w 0)
|
|
29
|
+
|
|
30
|
+
echo "🖥️ Sending to Windows receiver ($HOST)…" >&2
|
|
31
|
+
RESULT=$(ssh "$HOST" "$ENCODED" 2>&1) && echo "✓ $RESULT" >&2 || echo "⚠️ $RESULT" >&2 &
|
package/.claude/hooks/stop.sh
CHANGED
|
@@ -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
|
-
|
|
36
|
-
|
|
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
|
|
@@ -1,108 +1,108 @@
|
|
|
1
|
-
#
|
|
2
|
-
# File: .claude/hooks-windows/play-tts-windows-sapi.ps1
|
|
3
|
-
#
|
|
4
|
-
# AgentVibes - Windows SAPI TTS Provider (Zero Dependencies)
|
|
5
|
-
# Uses built-in Windows System.Speech API
|
|
6
|
-
#
|
|
7
|
-
|
|
8
|
-
param(
|
|
9
|
-
[Parameter(Mandatory = $true)]
|
|
10
|
-
[string]$Text,
|
|
11
|
-
|
|
12
|
-
[Parameter(Mandatory = $false)]
|
|
13
|
-
[string]$VoiceOverride
|
|
14
|
-
)
|
|
15
|
-
|
|
16
|
-
# Configuration paths
|
|
17
|
-
$ScriptPath = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
18
|
-
$ProjectClaudeDir = Join-Path (Split-Path -Parent (Split-Path -Parent $ScriptPath)) ".claude"
|
|
19
|
-
|
|
20
|
-
if (Test-Path $ProjectClaudeDir) {
|
|
21
|
-
$ClaudeDir = $ProjectClaudeDir
|
|
22
|
-
} else {
|
|
23
|
-
$ClaudeDir = "$env:USERPROFILE\.claude"
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
$AudioDir = "$ClaudeDir\audio"
|
|
27
|
-
$VoiceFile = "$ClaudeDir\tts-voice-sapi.txt"
|
|
28
|
-
|
|
29
|
-
# Ensure directories exist
|
|
30
|
-
if (-not (Test-Path $AudioDir)) {
|
|
31
|
-
New-Item -ItemType Directory -Path $AudioDir -Force | Out-Null
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
# Load System.Speech assembly
|
|
35
|
-
try {
|
|
36
|
-
Add-Type -AssemblyName System.Speech
|
|
37
|
-
}
|
|
38
|
-
catch {
|
|
39
|
-
Write-Host "[ERROR] System.Speech assembly not available" -ForegroundColor Red
|
|
40
|
-
exit 1
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
# Determine voice to use
|
|
44
|
-
$VoiceName = ""
|
|
45
|
-
|
|
46
|
-
if ($VoiceOverride) {
|
|
47
|
-
$VoiceName = $VoiceOverride
|
|
48
|
-
}
|
|
49
|
-
elseif (Test-Path $VoiceFile) {
|
|
50
|
-
$VoiceName = (Get-Content $VoiceFile -Raw).Trim()
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
# Initialize speech synthesizer
|
|
54
|
-
$synth = New-Object System.Speech.Synthesis.SpeechSynthesizer
|
|
55
|
-
|
|
56
|
-
# Set voice if specified
|
|
57
|
-
if ($VoiceName) {
|
|
58
|
-
try {
|
|
59
|
-
$synth.SelectVoice($VoiceName)
|
|
60
|
-
}
|
|
61
|
-
catch {
|
|
62
|
-
Write-Host "[WARNING] Voice '$VoiceName' not found, using default" -ForegroundColor Yellow
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
# Sanitize text for speech - strip shell metacharacters, PS special chars, and SSML tags
|
|
67
|
-
$Text = $Text -replace '\\', ' '
|
|
68
|
-
$Text = $Text -replace '[{}<>|`~^$;"''()]', ''
|
|
69
|
-
$Text = $Text -replace '&[a-zA-Z]+;', ''
|
|
70
|
-
$Text = $Text -replace '\s+', ' '
|
|
71
|
-
$Text = $Text.Trim()
|
|
72
|
-
|
|
73
|
-
# Get actual voice name (after selection or default)
|
|
74
|
-
$ActualVoice = $synth.Voice.Name
|
|
75
|
-
|
|
76
|
-
# Create audio file path
|
|
77
|
-
$Timestamp = Get-Date -Format 'yyyyMMdd-HHmmss-ffff'
|
|
78
|
-
$AudioFile = "$AudioDir\tts-$Timestamp.wav"
|
|
79
|
-
|
|
80
|
-
# Save to WAV file with proper resource cleanup
|
|
81
|
-
$player = $null
|
|
82
|
-
try {
|
|
83
|
-
$synth.SetOutputToWaveFile($AudioFile)
|
|
84
|
-
$synth.Speak($Text)
|
|
85
|
-
|
|
86
|
-
# Display results
|
|
87
|
-
Write-Host "[OK] Saved to: $AudioFile" -ForegroundColor Green
|
|
88
|
-
Write-Host "[VOICE] Voice used: $ActualVoice (Windows SAPI)" -ForegroundColor Green
|
|
89
|
-
|
|
90
|
-
# Play the audio using built-in Windows audio player (skip if AGENTVIBES_NO_PLAY is set)
|
|
91
|
-
if (-not $env:AGENTVIBES_NO_PLAY) {
|
|
92
|
-
try {
|
|
93
|
-
$player = New-Object System.Media.SoundPlayer $AudioFile
|
|
94
|
-
$player.PlaySync()
|
|
95
|
-
}
|
|
96
|
-
catch {
|
|
97
|
-
Write-Host "[WARNING] Could not play audio (SoundPlayer unavailable)" -ForegroundColor Yellow
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
catch {
|
|
102
|
-
Write-Host "[ERROR] Error synthesizing speech: $_" -ForegroundColor Red
|
|
103
|
-
exit 1
|
|
104
|
-
}
|
|
105
|
-
finally {
|
|
106
|
-
if ($synth) { $synth.Dispose() }
|
|
107
|
-
if ($player) { $player.Dispose() }
|
|
108
|
-
}
|
|
1
|
+
#
|
|
2
|
+
# File: .claude/hooks-windows/play-tts-windows-sapi.ps1
|
|
3
|
+
#
|
|
4
|
+
# AgentVibes - Windows SAPI TTS Provider (Zero Dependencies)
|
|
5
|
+
# Uses built-in Windows System.Speech API
|
|
6
|
+
#
|
|
7
|
+
|
|
8
|
+
param(
|
|
9
|
+
[Parameter(Mandatory = $true)]
|
|
10
|
+
[string]$Text,
|
|
11
|
+
|
|
12
|
+
[Parameter(Mandatory = $false)]
|
|
13
|
+
[string]$VoiceOverride
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
# Configuration paths
|
|
17
|
+
$ScriptPath = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
18
|
+
$ProjectClaudeDir = Join-Path (Split-Path -Parent (Split-Path -Parent $ScriptPath)) ".claude"
|
|
19
|
+
|
|
20
|
+
if (Test-Path $ProjectClaudeDir) {
|
|
21
|
+
$ClaudeDir = $ProjectClaudeDir
|
|
22
|
+
} else {
|
|
23
|
+
$ClaudeDir = "$env:USERPROFILE\.claude"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
$AudioDir = "$ClaudeDir\audio"
|
|
27
|
+
$VoiceFile = "$ClaudeDir\tts-voice-sapi.txt"
|
|
28
|
+
|
|
29
|
+
# Ensure directories exist
|
|
30
|
+
if (-not (Test-Path $AudioDir)) {
|
|
31
|
+
New-Item -ItemType Directory -Path $AudioDir -Force | Out-Null
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# Load System.Speech assembly
|
|
35
|
+
try {
|
|
36
|
+
Add-Type -AssemblyName System.Speech
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
Write-Host "[ERROR] System.Speech assembly not available" -ForegroundColor Red
|
|
40
|
+
exit 1
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# Determine voice to use
|
|
44
|
+
$VoiceName = ""
|
|
45
|
+
|
|
46
|
+
if ($VoiceOverride) {
|
|
47
|
+
$VoiceName = $VoiceOverride
|
|
48
|
+
}
|
|
49
|
+
elseif (Test-Path $VoiceFile) {
|
|
50
|
+
$VoiceName = (Get-Content $VoiceFile -Raw).Trim()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# Initialize speech synthesizer
|
|
54
|
+
$synth = New-Object System.Speech.Synthesis.SpeechSynthesizer
|
|
55
|
+
|
|
56
|
+
# Set voice if specified
|
|
57
|
+
if ($VoiceName) {
|
|
58
|
+
try {
|
|
59
|
+
$synth.SelectVoice($VoiceName)
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
Write-Host "[WARNING] Voice '$VoiceName' not found, using default" -ForegroundColor Yellow
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# Sanitize text for speech - strip shell metacharacters, PS special chars, and SSML tags
|
|
67
|
+
$Text = $Text -replace '\\', ' '
|
|
68
|
+
$Text = $Text -replace '[{}<>|`~^$;"''()]', ''
|
|
69
|
+
$Text = $Text -replace '&[a-zA-Z]+;', ''
|
|
70
|
+
$Text = $Text -replace '\s+', ' '
|
|
71
|
+
$Text = $Text.Trim()
|
|
72
|
+
|
|
73
|
+
# Get actual voice name (after selection or default)
|
|
74
|
+
$ActualVoice = $synth.Voice.Name
|
|
75
|
+
|
|
76
|
+
# Create audio file path
|
|
77
|
+
$Timestamp = Get-Date -Format 'yyyyMMdd-HHmmss-ffff'
|
|
78
|
+
$AudioFile = "$AudioDir\tts-$Timestamp.wav"
|
|
79
|
+
|
|
80
|
+
# Save to WAV file with proper resource cleanup
|
|
81
|
+
$player = $null
|
|
82
|
+
try {
|
|
83
|
+
$synth.SetOutputToWaveFile($AudioFile)
|
|
84
|
+
$synth.Speak($Text)
|
|
85
|
+
|
|
86
|
+
# Display results
|
|
87
|
+
Write-Host "[OK] Saved to: $AudioFile" -ForegroundColor Green
|
|
88
|
+
Write-Host "[VOICE] Voice used: $ActualVoice (Windows SAPI)" -ForegroundColor Green
|
|
89
|
+
|
|
90
|
+
# Play the audio using built-in Windows audio player (skip if AGENTVIBES_NO_PLAY is set)
|
|
91
|
+
if (-not $env:AGENTVIBES_NO_PLAY) {
|
|
92
|
+
try {
|
|
93
|
+
$player = New-Object System.Media.SoundPlayer $AudioFile
|
|
94
|
+
$player.PlaySync()
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
Write-Host "[WARNING] Could not play audio (SoundPlayer unavailable)" -ForegroundColor Yellow
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
Write-Host "[ERROR] Error synthesizing speech: $_" -ForegroundColor Red
|
|
103
|
+
exit 1
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
if ($synth) { $synth.Dispose() }
|
|
107
|
+
if ($player) { $player.Dispose() }
|
|
108
|
+
}
|