{
  "markdown": "# tts-attention-alert\n\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n[![Platform: Windows](https://img.shields.io/badge/platform-Windows%2010%2F11-0078d4)](https://github.com/PettHa/tts-attention-alert)\n[![Claude Code Plugin](https://img.shields.io/badge/Claude%20Code-plugin-da7756)](https://code.claude.com/docs/en/plugins)\n[![Version](https://img.shields.io/github/v/release/PettHa/tts-attention-alert?include_prereleases&label=version)](https://github.com/PettHa/tts-attention-alert/releases)\n[![GitHub stars](https://img.shields.io/github/stars/PettHa/tts-attention-alert?style=social)](https://github.com/PettHa/tts-attention-alert/stargazers)\n\nA 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.\n\n**Windows only.** The plugin uses Windows-native APIs (System.Speech.Synthesis, WPF, GlobalSystemMediaTransportControlsSessionManager). On macOS / Linux all hooks short-circuit on `process.platform !== 'win32'`.\n\n## What it does\n\nWhen Claude:\n\n- **Pauses for permission** (e.g. you're about to run `git push`)\n- **Wants to run a Bash command not in your allowlist** — the \"Allow this bash command?\" modal\n- **Asks a question** via `AskUserQuestion` or **presents a plan** via `ExitPlanMode`\n- **Finishes a response**\n\n…the plugin fires three reinforcing channels at once:\n\n1. **🗣️ 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:\n   - \"permission\" / \"approve\" / \"allow\" → *\"Permission needed\"*\n   - \"waiting\" → *\"Claude is waiting\"*\n   - \"idle\" → *\"Claude is idle\"*\n   - \"?\" / \"question\" / \"elicit\" → *\"Claude has a question\"*\n   - otherwise speaks the raw message\n2. **🟡 Edge-pulse frame** — a thin colored band around your primary monitor pulses to grab your peripheral vision:\n   - **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\").\n   - **Stop**: blue, three quick pulses then closes.\n3. **🎵 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.\n\nA 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?\".\n\n## Why no balloon toast?\n\nWindows' 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.\n\n## Why edge-pulse instead of FlashWindowEx?\n\n`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.\n\n## Install\n\nRequires Claude Code with plugin support (`/plugin` command available).\n\n```text\n/plugin marketplace add PettHa/tts-attention-alert\n/plugin install tts-attention-alert@tts-attention-alert\n```\n\nThen reload the window (`Developer: Reload Window` or restart Claude Code) so settings re-register the hooks.\n\nTo test locally before installing from GitHub:\n\n```bash\nclaude --plugin-dir /path/to/tts-attention-alert\n```\n\n## Choose your TTS engine\n\nThe plugin ships with two voices side-by-side:\n\n| Engine | Quality | How it sounds | When it's used |\n| :--- | :--- | :--- | :--- |\n| **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`, …) |\n| **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 |\n\n**To force SAPI everywhere** (e.g. you don't like the F1 voice or want a system-consistent feel):\n\n```bash\nexport CLAUDE_NOTIFY_WAV_DISABLED=1\n```\n\n**To switch the Supertonic voice** (e.g. M1 male instead of F1):\n\n```bash\n.venv-supertonic/Scripts/python.exe scripts/generate-audio.py --voice M1\n```\n\nThis overwrites `audio/*.wav` with the new voice. Voices: `M1`–`M5` (male), `F1`–`F5` (female). See [Audio assets](#audio-assets) for setup details.\n\n## Configuration (environment variables)\n\nAll optional. Set in your shell or `.env`:\n\n| Variable | Effect |\n| :--- | :--- |\n| `CLAUDE_NOTIFY_DISABLED=1` | Disable the Notification + AskUserQuestion hook entirely |\n| `CLAUDE_NOTIFY_TTS_DISABLED=1` | Disable just TTS for Notification (edge-pulse still fires) |\n| `CLAUDE_NOTIFY_PULSE_DISABLED=1` | Disable just the edge-pulse for Notification + Bash (TTS still fires). Shared with bash-permission-alert |\n| `CLAUDE_NOTIFY_TTS_TEXT=\"...\"` | Override the spoken phrase (skips keyword mapping) |\n| `CLAUDE_STOP_NOTIFY_DISABLED=1` | Disable the Stop hook entirely |\n| `CLAUDE_STOP_TTS_DISABLED=1` | Disable just TTS for Stop |\n| `CLAUDE_STOP_PULSE_DISABLED=1` | Disable just the edge-pulse for Stop (TTS still fires) |\n| `CLAUDE_STOP_TTS_TEXT=\"...\"` | Override the Stop spoken phrase (default: *\"Claude is done\"*) |\n| `CLAUDE_NOTIFY_DUCK_DISABLED=1` | Skip pausing Spotify/YouTube/etc around TTS |\n| `CLAUDE_BASH_ALERT_DISABLED=1` | Disable just the Bash permission alert (other hooks still fire) |\n| `CLAUDE_NOTIFY_WAV_DISABLED=1` | Skip the pre-baked Supertonic WAVs and always use Windows SAPI |\n\n### Mute via slash-commands (v0.4.0+)\n\nThree slash-commands ship with the plugin so you can toggle mute scopes without editing env vars by hand:\n\n| Command | What it does |\n| :--- | :--- |\n| `/mute-tts [all\\|voice\\|visual]` | Sets the corresponding env keys to `\"1\"` in `~/.claude/settings.json` (defaults to `all`). |\n| `/unmute-tts [all\\|voice\\|visual]` | Removes the corresponding env keys. `all` clears every TTS-related key. |\n| `/mute-tts-status` | Reports which mute scopes are currently active by inspecting `~/.claude/settings.json`. |\n\nScope semantics:\n\n- **`all`** — flips `CLAUDE_NOTIFY_DISABLED`, `CLAUDE_STOP_NOTIFY_DISABLED`, `CLAUDE_BASH_ALERT_DISABLED`. Total silence.\n- **`voice`** — flips `CLAUDE_NOTIFY_TTS_DISABLED`, `CLAUDE_STOP_TTS_DISABLED`. Edge-pulse still fires; media-duck does not (it's tied to TTS).\n- **`visual`** — flips `CLAUDE_NOTIFY_PULSE_DISABLED`, `CLAUDE_STOP_PULSE_DISABLED`. TTS still fires.\n\n### Live config (v0.5.0+) — no reload required\n\nSlash-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.\n\n## Bash permission alert (v0.2.0+)\n\nClaude 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:\n\n```jsonc\n{\n  \"hook_event_name\": \"PermissionRequest\",\n  \"tool_name\": \"Bash\",\n  \"tool_input\": { \"command\": \"rm -rf node_modules\", \"description\": \"...\" },\n  \"permission_mode\": \"default\",      // or \"acceptEdits\" / \"bypassPermissions\"\n  \"permission_suggestions\": [ ... ]\n}\n```\n\nNo 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.\n\n## Architecture\n\n```\ntts-attention-alert/\n├── .claude-plugin/\n│   ├── plugin.json                  ← manifest\n│   └── marketplace.json             ← marketplace registration\n├── audio/                           ← pre-baked Supertonic WAVs (committed, no runtime download)\n│   ├── permission-needed.wav        ← one per static phrase + one per common bash verb\n│   └── …\n├── hooks/\n│   ├── hooks.json                   ← Notification + Stop + PreToolUse + PermissionRequest wiring\n│   ├── notification-alert.js        ← Notification + AskUserQuestion + ExitPlanMode\n│   ├── bash-permission-alert.js     ← PermissionRequest:Bash, fires on the actual modal\n│   ├── stop-notify.js               ← fires when response ends\n│   ├── edge-pulse.ps1               ← 4-edge WPF overlay (looping + escalation)\n│   ├── run-hidden.vbs               ← `wscript` shim so PowerShell launches without a console flash\n│   └── lib/\n│       ├── audio-duck.js            ← builds the WinRT pause-resume PowerShell snippet\n│       └── play-wav.js              ← maps phrase → slug → pre-baked WAV path\n├── scripts/\n│   └── generate-audio.py            ← dev-only: regenerate audio/*.wav via Supertonic\n└── README.md\n```\n\n### Audio assets\n\n`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.\n\nTo regenerate (e.g. to switch voice or add phrases):\n\n```bash\npython -m venv .venv-supertonic\n.venv-supertonic/Scripts/python.exe -m pip install supertonic\nHF_HUB_DISABLE_XET=1 .venv-supertonic/Scripts/python.exe scripts/generate-audio.py --voice F1\n```\n\nThe 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).\n\nPhrases 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.\n\nHook 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`.\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n",
  "bytes": 10832,
  "sha": "badabd9ab50d969f265486b56c6fcbc0d3aa57dc7346ac0800b853e13e52c138",
  "repo_slug": "pettha/tts-attention-alert",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/plg_pettha_tts_attention_alert_tts_attention_d50989b0/readme"
}