diff --git a/install.sh b/install.sh index 27679b6..a8748c2 100755 --- a/install.sh +++ b/install.sh @@ -48,7 +48,7 @@ find_python() { # Install the voice engine (stackvox) from PyPI into an isolated venv. echo "" echo "Setting up voice engine..." -STACKVOX_SPEC="stackvox>=0.3.0" +STACKVOX_SPEC="stackvox>=0.4.0" PYTHON=$(find_python) if [[ -z "$PYTHON" ]]; then echo " Could not find Python ≥ 3.10. Install one (e.g. 'brew install python@3.13')" diff --git a/notify.sh b/notify.sh index 2a631a8..ecc594b 100755 --- a/notify.sh +++ b/notify.sh @@ -146,7 +146,7 @@ voice_phrase_for() { local event="$1" local lang repo - if [[ -x "$STACKVOX_SAY" ]]; then + if [[ -x "$STACKVOX" ]]; then lang=$(voice_to_lang "$VOICE_NAME") repo=$(repo_name_raw) else @@ -196,25 +196,37 @@ agent_label() { esac } -# Bundled voice engine paths +# Bundled voice engine paths. stackvox 0.3.x consolidated the CLI — there +# is no separate `stackvox-say` console script anymore; speech goes through +# `stackvox say ` as a subcommand. VENV="${HOME}/.stack-nudge/venv" STACKVOX="${VENV}/bin/stackvox" -STACKVOX_SAY="${VENV}/bin/stackvox-say" + +# Log a debug line when STACKNUDGE_DEBUG=true. Used for "voice was +# requested but couldn't fire" cases that previously failed silently. +nudge_debug() { + [[ "${STACKNUDGE_DEBUG:-}" == "true" ]] || return 0 + printf '[stack-nudge] %s\n' "$*" >&2 +} # Speak a message aloud via the bundled StackVox daemon. # Auto-starts the daemon if it isn't running. Falls back silently if the -# venv isn't installed or the daemon fails to respond. +# venv isn't installed or the daemon fails to respond — set STACKNUDGE_DEBUG=true +# to surface why. speak_notification() { [[ "${VOICE_ENABLED}" != "true" ]] && return - [[ ! -x "$STACKVOX_SAY" ]] && return + if [[ ! -x "$STACKVOX" ]]; then + nudge_debug "voice requested but stackvox not found at $STACKVOX" + return + fi local text="$1" - # Start daemon if socket doesn't exist yet if [[ ! -S "${HOME}/.cache/stackvox/daemon.sock" ]]; then + nudge_debug "stackvox daemon socket missing — starting daemon" nohup "$STACKVOX" serve >/dev/null 2>&1 & fi local kokoro_lang kokoro_lang=$(voice_to_kokoro_lang "$VOICE_NAME") - "$STACKVOX_SAY" --voice "${VOICE_NAME}" --lang "${kokoro_lang}" --speed "${VOICE_SPEED}" "${text}" 2>/dev/null & + "$STACKVOX" say --voice "${VOICE_NAME}" --lang "${kokoro_lang}" --speed "${VOICE_SPEED}" "${text}" 2>/dev/null & } # Locate one of our .app bundles. Searches ~/Applications, the script's diff --git a/panel/Speaker.swift b/panel/Speaker.swift index 49c642a..69ecdf1 100644 --- a/panel/Speaker.swift +++ b/panel/Speaker.swift @@ -1,19 +1,20 @@ import Foundation -// Thin wrapper around stackvox-say. Spawns the daemon if its socket isn't -// up yet (mirrors notify.sh's auto-start) and falls back to a no-op if -// stackvox isn't installed in the venv. +// Thin wrapper around the stackvox CLI. Spawns the daemon if its socket +// isn't up yet (mirrors notify.sh's auto-start) and falls back to a no-op +// if stackvox isn't installed in the venv. +// +// stackvox 0.3.x consolidated its CLI — there's no separate `stackvox-say` +// binary anymore; speech goes through `stackvox say ` as a subcommand. enum Speaker { static func speak(_ text: String, voice: String? = nil, speed: String? = nil) { let venvBin = "\(NSHomeDirectory())/.stack-nudge/venv/bin" - let stackvoxSay = "\(venvBin)/stackvox-say" let stackvox = "\(venvBin)/stackvox" let socketPath = "\(NSHomeDirectory())/.cache/stackvox/daemon.sock" - guard FileManager.default.isExecutableFile(atPath: stackvoxSay) else { return } + guard FileManager.default.isExecutableFile(atPath: stackvox) else { return } - if !FileManager.default.fileExists(atPath: socketPath), - FileManager.default.isExecutableFile(atPath: stackvox) { + if !FileManager.default.fileExists(atPath: socketPath) { let serve = Process() serve.executableURL = URL(fileURLWithPath: stackvox) serve.arguments = ["serve"] @@ -24,8 +25,8 @@ enum Speaker { let resolvedVoice = voice ?? config["STACKNUDGE_VOICE_NAME"] ?? "af_aoede" let resolvedSpeed = speed ?? config["STACKNUDGE_VOICE_SPEED"] ?? "1.1" let say = Process() - say.executableURL = URL(fileURLWithPath: stackvoxSay) - say.arguments = ["--voice", resolvedVoice, "--speed", resolvedSpeed, text] + say.executableURL = URL(fileURLWithPath: stackvox) + say.arguments = ["say", "--voice", resolvedVoice, "--speed", resolvedSpeed, text] try? say.run() } }