Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,17 @@
},
"source": "./plugins/security-guidance",
"category": "security"
},
{
"name": "audio-notifications",
"description": "Audio notifications for Claude Code - speaks notification messages out loud",
"version": "1.0.0",
"author": {
"name": "Fred Lacs",
"email": "[email protected]"
},
"source": "./plugins/audio-notifications",
"category": "qol"
}
]
}
11 changes: 11 additions & 0 deletions plugins/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@ Learn more in the [official plugins documentation](https://docs.claude.com/en/do

## Plugins in This Directory

### [audio-notifications](./audio-notifications/)

**Audio Notifications Plugin**

Automatically speaks notification messages out loud using system text-to-speech.

- **Feature**: Audio notifications for Claude Code tasks
- **Configuration**: Customizable via `~/.claude/audio_notifications.json`
- **Supported systems**: macOS (`say`), Linux (`spd-say`, `espeak`), or custom commands
- **Use case**: Stay informed during long-running tasks without watching the terminal

### [agent-sdk-dev](./agent-sdk-dev/)

**Claude Agent SDK Development Plugin**
Expand Down
9 changes: 9 additions & 0 deletions plugins/audio-notifications/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "audio-notifications",
"version": "1.0.0",
"description": "Audio notifications for Claude Code - speaks notification messages out loud",
"author": {
"name": "Fred Lacs",
"email": "[email protected]"
}
}
156 changes: 156 additions & 0 deletions plugins/audio-notifications/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# Audio Notifications Plugin

Speaks notification messages out loud using your system's text-to-speech capabilities.

## Overview

The Audio Notifications Plugin automatically speaks Claude Code notifications aloud. This helps you stay informed without constantly watching the terminal, particularly useful during long-running tasks like builds, tests, or code reviews.

## How It Works

The plugin registers a hook that listens for notification events. When a notification occurs, the plugin passes the message to your system's text-to-speech command, playing it through your speakers or headphones.

The plugin automatically detects available TTS commands on your system and uses the first one found. You can override this with a custom command in the configuration file.

## Installation

This plugin is included in Claude Code. It will be available in your plugins marketplace once enabled.

```bash
# Enable in Claude Code settings
# Or install from marketplace when available
```

## Configuration

Configuration is stored in `~/.claude/audio_notifications.json`. Create this file to customize behavior.

### Configuration File

```json
{
"audio_off": false,
"speech_command": "say"
}
```

### Configuration Options

**audio_off** (boolean)
- Set to `true` to disable audio notifications without uninstalling the plugin
- Default: `false` (audio enabled)
- Useful for temporarily muting notifications during meetings or quiet hours
- Serves as a **shared coordination point** for audio control across plugins
- Other plugins can check this setting to honor audio preferences
- Other plugins can also set this to `true` to silence this plugin when needed

**speech_command** (string)
- The command to use for text-to-speech
- Can include spaces for command options (e.g., `"say -v Victoria"`)
- If not specified, the plugin auto-detects available commands
- See "Supported Systems" section for platform-specific commands

## Supported Systems

The plugin automatically detects and uses available TTS commands:

**macOS:**
- `say` (native macOS command)

**Linux:**
- `spd-say` (Speech Dispatcher)
- `espeak` (eSpeak)

**Custom Commands:**
- Any command that accepts text as final argument
- Example: `festival --tts` or custom scripts

## Usage

Once installed and enabled, the plugin works automatically. No additional setup is required.

### Examples

**Disable audio temporarily:**
```json
{
"audio_off": true
}
```

**Use a specific voice on macOS:**
```json
{
"speech_command": "say -v Victoria"
}
```

**Use spd-say on Linux with slower speech:**
```json
{
"speech_command": "spd-say -r -50"
}
```

## Troubleshooting

### No Audio Playing

**Issue**: Notifications aren't spoken aloud

**Solutions:**
- Check system volume is not muted
- Verify TTS command is installed: `which say` or `which spd-say`
- Confirm `audio_off` is not set to `true` in config
- Check that notification messages are being generated

### Wrong Voice Used

**Issue**: Plugin using unexpected TTS command

**Solutions:**
- Explicitly set `speech_command` in config
- Check auto-detection order: `say` → `spd-say` → `espeak`
- Verify preferred command is installed

### Command Not Found Error

**Issue**: Custom `speech_command` fails

**Solutions:**
- Verify command is in your PATH: `which <command>`
- Test command manually: `echo "test" | <command>`
- Include full path if needed: `/usr/local/bin/say`
- Check for required arguments or flags

### Audio Too Loud or Quiet

**Issue**: Volume too high or low

**Solutions:**
- Adjust system volume level
- Add volume options to `speech_command`: `"say -v Victoria -r 200"`
- Adjust speaker/headphone volume

## Requirements

- System with text-to-speech support
- At least one TTS command available (`say`, `spd-say`, `espeak`, or custom)
- Speakers or headphones (if output desired)

## Tips

- **Disable during meetings**: Set `audio_off: true` in config quickly
- **Plugin coordination**: The `audio_off` config is a shared coordination point—other plugins can silence this plugin, and you can silence all audio-aware plugins by setting this flag
- **Combine with other audio**: Works with other applications, respects system volume
- **Test your setup**: Run a manual test with your TTS command first
- **Reduce notifications**: Works best when notifications are meaningful and not excessive
- **Custom notifications**: Pairs well with any Claude Code workflow that generates notifications

## Author

Fred Lacs ([email protected])

## Version

1.0.0
83 changes: 83 additions & 0 deletions plugins/audio-notifications/hooks/audio_notification_hook.py
Original file line number Diff line number Diff line change
@@ -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 changes: 5 additions & 0 deletions plugins/audio-notifications/hooks/audio_notification_hook.sh
Original file line number Diff line number Diff line change
@@ -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 changes: 16 additions & 0 deletions plugins/audio-notifications/hooks/hooks.json
Original file line number Diff line number Diff line change
@@ -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"
}
]
}
]
}
}