Initial commit
This commit is contained in:
83
hooks/audio_notification_hook.py
Executable file
83
hooks/audio_notification_hook.py
Executable file
@@ -0,0 +1,83 @@
|
||||
#!/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()
|
||||
5
hooks/audio_notification_hook.sh
Executable file
5
hooks/audio_notification_hook.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
# bash wrapper used to run python in background
|
||||
cat | python3 "${CLAUDE_PLUGIN_ROOT}/hooks/audio_notification_hook.py" >/dev/null 2>&1 &
|
||||
exit 0
|
||||
16
hooks/hooks.json
Normal file
16
hooks/hooks.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"description": "Audio notification hook that warns user of notification",
|
||||
"hooks": {
|
||||
"Notification": [
|
||||
{
|
||||
"matcher": "",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/audio_notification_hook.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user