#!/usr/bin/env python3 from dataclasses import dataclass import json import shlex import shutil import subprocess import sys from pathlib import Path from typing import Optional CONFIG_PATH = Path.home() / ".claude" / "audio_notifications.json" def detect_default_command() -> Optional[str]: for cmd in ["say", "spd-say", "espeak"]: if shutil.which(cmd): return cmd return None @dataclass class UserConfig: audio_off: Optional[bool] = None speech_command: Optional[str] = None def get_user_config() -> UserConfig: config = UserConfig() # attempt to load user config, return defaults if fail try: user_config_json = json.loads(CONFIG_PATH.read_text()) except Exception: return config if not isinstance(user_config_json, dict): return config user_audio_off = user_config_json.get("audio_off") if isinstance(user_audio_off, bool): config.audio_off = user_audio_off user_speech_command = user_config_json.get("speech_command") if isinstance(user_speech_command, str): config.speech_command = user_speech_command.strip() return config def main(): user_config = get_user_config() if user_config.audio_off is True: sys.exit(0) try: hook_data = json.loads(sys.stdin.read()) except Exception: sys.exit(2) if not isinstance(hook_data, dict): sys.exit(2) # only trigger audio if hook is a notification if hook_data.get("hook_event_name") != "Notification": sys.exit(0) message = hook_data.get("message") if not isinstance(message, str): sys.exit(2) message = message.strip() # attempt command set by user if available, else default options speech_command = user_config.speech_command or detect_default_command() if not isinstance(speech_command, str): sys.exit(2) speech_command = shlex.split(speech_command) try: subprocess.run(speech_command + [message], check=True, timeout=10) except Exception: sys.exit(2) if __name__ == "__main__": main()