tts-attention-alert
Windows-only attention alerts for AI coding agents. When the agent pauses for permission, asks a question, or finishes a response, the plugi
Open source Repository Open in the app JSON README (API)
About
Windows-only attention alerts for AI coding agents. When the agent pauses for permission, asks a question, or finishes a response, the plugin speaks the notification out loud (TTS) and pulses a colored frame around your primary monitor — gold for blocking events, escalating to orange and red if you don't respond within 20s and 40s. Stop events get a calmer blue 3-pulse. Auto-ducks Spotify, YouTube, VLC, and other media apps around the spoken phrase so the alert is audible even mid-song, then resumes them. A forensic log of every event is written to ~/.claude/cache/notifications.log so you can scroll back what fired while you were away. No external dependencies — pure System.Speech.Synthesis, WPF, and WinRT GlobalSystemMediaTransportControlsSessionManager.
Details
- Kind
- Plugins
- Topic
- Cloud & DevOps
- Publisher
- pettha
- Origin
- marketplace
- Category
- ferramentas
- Last push
- 2026-05-22T14:53:01Z
- Repository state
- ativo
- Language
- JavaScript
- License
- MIT
- Added
- 2026-08-30 01:48:58
- Updated
- 2026-08-30 01:48:58
- Origin id
pettha/tts-attention-alert/tts-attention-alert
README
# tts-attention-alert
[](LICENSE)
[](https://github.com/PettHa/tts-attention-alert)
[](https://code.claude.com/docs/en/plugins)
[](https://github.com/PettHa/tts-attention-alert/releases)
[](https://github.com/PettHa/tts-attention-alert/stargazers)
A Claude Code plugin that demands your attention when Claude pauses, asks a question, or finishes a response — even when you're in another window or away from the desk.
**Windows only.** The plugin uses Windows-native APIs (System.Speech.Synthesis, WPF, GlobalSystemMediaTransportControlsSessionManager). On macOS / Linux all hooks short-circuit on `process.platform !== 'win32'`.
## What it does
When Claude:
- **Pauses for permission** (e.g. you're about to run `git push`)
- **Wants to run a Bash command not in your allowlist** — the "Allow this bash command?" modal
- **Asks a question** via `AskUserQuestion` or **presents a plan** via `ExitPlanMode`
- **Finishes a response**
…the plugin fires three reinforcing channels at once:
1. **🗣️ TTS speech** — plays a pre-baked [Supertonic](https://github.com/supertone-inc/supertonic) WAV for known phrases (no model download for end-users), falls back to Windows `System.Speech` for env-var overrides and unbaked text. Messages are mapped to a short action phrase via keywords:
- "permission" / "approve" / "allow" → *"Permission needed"*
- "waiting" → *"Claude is waiting"*
- "idle" → *"Claude is idle"*
- "?" / "question" / "elicit" → *"Claude has a question"*
- otherwise speaks the raw message
2. **🟡 Edge-pulse frame** — a thin colored band around your primary monitor pulses to grab your peripheral vision:
- **Notification**: gold, loops until the VSCode window running this Claude session gets foreground focus (60s safety cap). Escalates **Gold → DarkOrange → Red** at 20s / 40s with a fresh TTS phrase ("Claude still needs you").
- **Stop**: blue, three quick pulses then closes.
3. **🎵 Auto-duck media** — pauses Spotify, YouTube, VLC, podcast apps around the TTS speak call (Windows `GlobalSystemMediaTransportControlsSessionManager`) so the spoken phrase is audible even mid-song. Resumes automatically. Silent no-op if nothing is playing.
A forensic log of every event is appended to `~/.claude/cache/notifications.log` with timestamp, type, and message — so you can scroll back "what fired while I was in a meeting?".
## Why no balloon toast?
Windows' built-in toast system plays a default notification chime that can't be easily suppressed. We dropped the toast and rely on TTS + visual edge-pulse instead, giving full control over the audio.
## Why edge-pulse instead of FlashWindowEx?
`FlashWindowEx` is unreliable on Windows 11 + Electron windows (VSCode, Windows Terminal) — see [microsoft/terminal#8713](https://github.com/microsoft/terminal/issues/8713). The plugin renders its own WPF overlay so the visual cue is guaranteed to display.
## Install
Requires Claude Code with plugin support (`/plugin` command available).
```text
/plugin marketplace add PettHa/tts-attention-alert
/plugin install tts-attention-alert@tts-attention-alert
```
Then reload the window (`Developer: Reload Window` or restart Claude Code) so settings re-register the hooks.
To test locally before installing from GitHub:
```bash
claude --plugin-dir /path/to/tts-attention-alert
```
## Choose your TTS engine
The plugin ships with two voices side-by-side:
| Engine | Quality | How it sounds | When it's used |
| :--- | :--- | :--- | :--- |
| **Supertonic** (default) | High — neural, on-device | Natural human cadence, F1 (female) voice | All built-in phrases (`Permission needed`, `Claude is done`, `Bash permission needed: git`, …) |
| **Windows SAPI** (fallback) | Standard — robotic system voice | Whatever your `System.Speech.Synthesis` default is set to | `CLAUDE_NOTIFY_TTS_TEXT` / `CLAUDE_STOP_TTS_TEXT` overrides, and any phrase not in the pre-baked set |
**To force SAPI everywhere** (e.g. you don't like the F1 voice or want a system-consistent feel):
```bash
export CLAUDE_NOTIFY_WAV_DISABLED=1
```
**To switch the Supertonic voice** (e.g. M1 male instead of F1):
```bash
.venv-supertonic/Scripts/python.exe scripts/generate-audio.py --voice M1
```
This overwrites `audio/*.wav` with the new voice. Voices: `M1`–`M5` (male), `F1`–`F5` (female). See [Audio assets](#audio-assets) for setup details.
## Configuration (environment variables)
All optional. Set in your shell or `.env`:
| Variable | Effect |
| :--- | :--- |
| `CLAUDE_NOTIFY_DISABLED=1` | Disable the Notification + AskUserQuestion hook entirely |
| `CLAUDE_NOTIFY_TTS_DISABLED=1` | Disable just TTS for Notification (edge-pulse still fires) |
| `CLAUDE_NOTIFY_PULSE_DISABLED=1` | Disable just the edge-pulse for Notification + Bash (TTS still fires). Shared with bash-permission-alert |
| `CLAUDE_NOTIFY_TTS_TEXT="..."` | Override the spoken phrase (skips keyword mapping) |
| `CLAUDE_STOP_NOTIFY_DISABLED=1` | Disable the Stop hook entirely |
| `CLAUDE_STOP_TTS_DISABLED=1` | Disable just TTS for Stop |
| `CLAUDE_STOP_PULSE_DISABLED=1` | Disable just the edge-pulse for Stop (TTS still fires) |
| `CLAUDE_STOP_TTS_TEXT="..."` | Override the Stop spoken phrase (default: *"Claude is done"*) |
| `CLAUDE_NOTIFY_DUCK_DISABLED=1` | Skip pausing Spotify/YouTube/etc around TTS |
| `CLAUDE_BASH_ALERT_DISABLED=1` | Disable just the Bash permission alert (other hooks still fire) |
| `CLAUDE_NOTIFY_WAV_DISABLED=1` | Skip the pre-baked Supertonic WAVs and always use Windows SAPI |
### Mute via slash-commands (v0.4.0+)
Three slash-commands ship with the plugin so you can toggle mute scopes without editing env vars by hand:
| Command | What it does |
| :--- | :--- |
| `/mute-tts [all\|voice\|visual]` | Sets the corresponding env keys to `"1"` in `~/.claude/settings.json` (defaults to `all`). |
| `/unmute-tts [all\|voice\|visual]` | Removes the corresponding env keys. `all` clears every TTS-related key. |
| `/mute-tts-status` | Reports which mute scopes are currently active by inspecting `~/.claude/settings.json`. |
Scope semantics:
- **`all`** — flips `CLAUDE_NOTIFY_DISABLED`, `CLAUDE_STOP_NOTIFY_DISABLED`, `CLAUDE_BASH_ALERT_DISABLED`. Total silence.
- **`voice`** — flips `CLAUDE_NOTIFY_TTS_DISABLED`, `CLAUDE_STOP_TTS_DISABLED`. Edge-pulse still fires; media-duck does not (it's tied to TTS).
- **`visual`** — flips `CLAUDE_NOTIFY_PULSE_DISABLED`, `CLAUDE_STOP_PULSE_DISABLED`. TTS still fires.
### Live config (v0.5.0+) — no reload required
Slash-command edits take effect on the **next hook fire** in every open Claude Code session — no `Developer: Reload Window`, no restart, no per-window setup. Hooks read `~/.claude/settings.json` on every event, with `process.env` as a fallback. Shell-set env vars (PowerShell `$PROFILE`, `.envrc`, etc.) still work for dev — `settings.json` only overrides them when explicitly set.
## Bash permission alert (v0.2.0+)
Claude Code's `Notification` event does **not** fire for the in-window "Allow this bash command?" modal. To catch it, the plugin wires Claude Code's native [`PermissionRequest`](https://code.claude.com/docs/en/hooks) hook event with matcher `Bash` — the event fires exactly when the dialog is about to display, with the full payload:
```jsonc
{
"hook_event_name": "PermissionRequest",
"tool_name": "Bash",
"tool_input": { "command": "rm -rf node_modules", "description": "..." },
"permission_mode": "default", // or "acceptEdits" / "bypassPermissions"
"permission_suggestions": [ ... ]
}
```
No prediction, no allow-list simulation, no dangerous-pattern heuristics — just fire the alert. Spoken phrase is *"Bash permission needed: \<verb\>"* so urgent prompts are recognizable by ear without reading the full command aloud.
## Architecture
```
tts-attention-alert/
├── .claude-plugin/
│ ├── plugin.json ← manifest
│ └── marketplace.json ← marketplace registration
├── audio/ ← pre-baked Supertonic WAVs (committed, no runtime download)
│ ├── permission-needed.wav ← one per static phrase + one per common bash verb
│ └── …
├── hooks/
│ ├── hooks.json ← Notification + Stop + PreToolUse + PermissionRequest wiring
│ ├── notification-alert.js ← Notification + AskUserQuestion + ExitPlanMode
│ ├── bash-permission-alert.js ← PermissionRequest:Bash, fires on the actual modal
│ ├── stop-notify.js ← fires when response ends
│ ├── edge-pulse.ps1 ← 4-edge WPF overlay (looping + escalation)
│ ├── run-hidden.vbs ← `wscript` shim so PowerShell launches without a console flash
│ └── lib/
│ ├── audio-duck.js ← builds the WinRT pause-resume PowerShell snippet
│ └── play-wav.js ← maps phrase → slug → pre-baked WAV path
├── scripts/
│ └── generate-audio.py ← dev-only: regenerate audio/*.wav via Supertonic
└── README.md
```
### Audio assets
`audio/*.wav` is pre-baked once on the dev machine using [Supertonic](https://github.com/supertone-inc/supertonic) and checked into the repo. End-users get high-quality on-device speech with **zero** runtime model download and no Python dependency.
To regenerate (e.g. to switch voice or add phrases):
```bash
python -m venv .venv-supertonic
.venv-supertonic/Scripts/python.exe -m pip install supertonic
HF_HUB_DISABLE_XET=1 .venv-supertonic/Scripts/python.exe scripts/generate-audio.py --voice F1
```
The first run downloads the ~99 MB Supertonic ONNX model into your Hugging Face cache. `HF_HUB_DISABLE_XET=1` is needed because the Xet CDN endpoint can fail DNS resolution on some networks — vanilla HTTPS works fine. Available voices: `M1`–`M5` (male), `F1`–`F5` (female).
Phrases the plugin pre-bakes are listed in `scripts/generate-audio.py`. If you add a new phrase to a hook script, also add it to `STATIC_PHRASES` (or `BASH_VERBS`) there and to `PHRASE_TO_SLUG` in `hooks/lib/play-wav.js`, then rerun the script. Unknown phrases fall through to live SAPI synthesis at runtime — nothing crashes.
Hook scripts spawn PowerShell via a VBScript shim (`run-hidden.vbs` → `WScript.Shell.Run cmd, 0, False`) so nothing flashes a console on launch. The PowerShell script is passed base64-encoded via `-EncodedCommand` to avoid quoting headaches through `cmd` → `wscript` → `powershell`.
## License
MIT — see [LICENSE](LICENSE).