Initial commit
This commit is contained in:
12
.claude-plugin/plugin.json
Normal file
12
.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "audio-notifications",
|
||||||
|
"description": "Audio notifications for Claude Code - speaks notification messages out loud",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "Fred Lacs",
|
||||||
|
"email": "fredlacs@gmail.com"
|
||||||
|
},
|
||||||
|
"hooks": [
|
||||||
|
"./hooks"
|
||||||
|
]
|
||||||
|
}
|
||||||
3
README.md
Normal file
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# audio-notifications
|
||||||
|
|
||||||
|
Audio notifications for Claude Code - speaks notification messages out loud
|
||||||
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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
53
plugin.lock.json
Normal file
53
plugin.lock.json
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"$schema": "internal://schemas/plugin.lock.v1.json",
|
||||||
|
"pluginId": "gh:marcioaltoe/claude-craftkit:plugins/audio-notifications",
|
||||||
|
"normalized": {
|
||||||
|
"repo": null,
|
||||||
|
"ref": "refs/tags/v20251128.0",
|
||||||
|
"commit": "2135c828ed71f66db9cdee715eb5c86eaa37b6f3",
|
||||||
|
"treeHash": "386e651bb16f7fed1fe7df1ce6edbe63490a66277c866ba5e59f9e03e7f3d026",
|
||||||
|
"generatedAt": "2025-11-28T10:27:00.624830Z",
|
||||||
|
"toolVersion": "publish_plugins.py@0.2.0"
|
||||||
|
},
|
||||||
|
"origin": {
|
||||||
|
"remote": "git@github.com:zhongweili/42plugin-data.git",
|
||||||
|
"branch": "master",
|
||||||
|
"commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390",
|
||||||
|
"repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data"
|
||||||
|
},
|
||||||
|
"manifest": {
|
||||||
|
"name": "audio-notifications",
|
||||||
|
"description": "Audio notifications for Claude Code - speaks notification messages out loud",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "README.md",
|
||||||
|
"sha256": "ee059741b5d3fc50192a2e1b9a0741ab7c665928a690790de168a90e428f6f55"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "hooks/audio_notification_hook.sh",
|
||||||
|
"sha256": "9ab93e438e473bfe54998a2b1e4669357f00654cfe8ab750cf8b50a20b75866b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "hooks/audio_notification_hook.py",
|
||||||
|
"sha256": "298e881c3c79cdb71c7a215ead7659c0e2995e49ca808e085abd5dc71bd220d8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "hooks/hooks.json",
|
||||||
|
"sha256": "36e1c913d78aa12c6187435abf633829ddc7560c5d95e6b410ab6f471710b905"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": ".claude-plugin/plugin.json",
|
||||||
|
"sha256": "f743c1210d78260f06f5942beebe50fed3a8cae645c889f7f71f293dc0bf1452"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dirSha256": "386e651bb16f7fed1fe7df1ce6edbe63490a66277c866ba5e59f9e03e7f3d026"
|
||||||
|
},
|
||||||
|
"security": {
|
||||||
|
"scannedAt": null,
|
||||||
|
"scannerVersion": null,
|
||||||
|
"flags": []
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user