From audio-hooks
Manages echook audio notifications for Claude Code, Cursor, and Codex: installs, configures, snoozes, mutes, tests, troubleshoots, and monitors context window usage.
How this skill is triggered — by the user, by Claude, or both
Slash command
/audio-hooks:audio-hooksThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This plugin is the AI control surface for the echook project. The user does NOT operate this project by hand. You operate it on their behalf via the `audio-hooks` binary that this plugin adds to your Bash tool's PATH.
This plugin is the AI control surface for the echook project. The user does NOT operate this project by hand. You operate it on their behalf via the audio-hooks binary that this plugin adds to your Bash tool's PATH.
Golden rule: when in doubt about the project's current capabilities, run audio-hooks manifest first. The manifest is the canonical machine description of every subcommand, every config key, every hook, every audio file, and every error code. It is always up to date with the running version. Treat this SKILL.md as an orientation; treat audio-hooks manifest as the source of truth.
Install / set up the project
The plugin install (which you are using right now) is the recommended path for Claude Code users. If a user is not yet on the plugin, tell them to run /plugin marketplace add ChanMeng666/echook and /plugin install audio-hooks@chanmeng-audio-hooks inside Claude Code. Cursor IDE 3.2.16+ users get audio-hooks for free via Cursor's built-in third-party hooks bridge — no separate Cursor install needed. For users who run Cursor without Claude Code, use audio-hooks install --cursor (see "Install for Cursor-only users" below). Codex CLI users must use audio-hooks install --codex — Codex does NOT auto-bridge Claude Code plugins, so the native install is the only path (see "Install for Codex CLI users" below). Once installed, verify with:
audio-hooks status
audio-hooks test all
Snooze / mute / quiet hours
| User says | Run |
|---|---|
| "snooze audio for 30 minutes" | audio-hooks snooze 30m |
| "shut up for an hour" | audio-hooks snooze 1h |
| "mute audio for the rest of the day" | audio-hooks snooze 8h |
| "unmute" / "resume" / "cancel snooze" | audio-hooks snooze off |
| "is audio snoozed?" | audio-hooks snooze status |
Duration syntax: 30m, 1h, 90s, 2d, or a bare integer (interpreted as minutes).
Upgrade the plugin without losing config
audio-hooks upgrade is the AI-first way to refresh the plugin code (and ~/.claude/plugins/cache/) without touching the user's preferences. Use it whenever:
| User says | Run |
|---|---|
| "upgrade audio-hooks" / "refresh the cache" / "Cursor still plays old theme" | audio-hooks upgrade |
| "is there a new version?" | audio-hooks upgrade --check-only |
| "the upgrade got stuck" | audio-hooks upgrade --force (only after confirming via audio-hooks status) |
upgrade auto-detects scope via claude plugin list --json, tries claude plugin update first (data-preserving), falls back to uninstall --keep-data + install if needed. On success, the user's ~/.claude/plugins/data/audio-hooks-chanmeng-audio-hooks/user_preferences.json is preserved verbatim, then loaded through auto-migration so new keys from the new template are merged in non-destructively.
Restore from a backup
| User says | Run |
|---|---|
| "what configurations have I changed?" | audio-hooks status (look at the customizations field) |
| "show me available backups" | audio-hooks backup list |
| "restore my config from before the upgrade" | audio-hooks backup restore latest-external |
| "delete old backups" | audio-hooks backup prune |
Enable / disable individual hooks
Run audio-hooks hooks list to see all 26 hooks with their current state. Then:
| User says | Run |
|---|---|
| "stop the audio for tool execution" | audio-hooks hooks disable pretooluse and audio-hooks hooks disable posttooluse |
| "enable rate-limit warnings" | audio-hooks set rate_limit_alerts.enabled true |
| "I only want stop and notification audio" | audio-hooks hooks enable-only stop notification permission_request |
| "enable the v5.0 permission_denied hook" | audio-hooks hooks enable permission_denied |
| "watch .env files for changes" | audio-hooks hooks enable file_changed and audio-hooks set file_changed.watch '[".env",".envrc"]' |
Check project status
Run audio-hooks status when the user asks "what's the current audio config?", "is audio working?", "show me audio status", etc. It returns a full snapshot: version, theme, enabled hooks count, snooze state, focus flow, webhook, TTS, rate-limit alerts, and install mode.
audio-hooks status # full state snapshot
audio-hooks version # version + install type detection
Change audio theme
The project ships two themes. default is voice recordings, custom is non-voice chimes.
audio-hooks theme list # show available + current
audio-hooks theme set custom # switch to chimes
audio-hooks theme set default # switch to voice
Webhook fan-out (Slack / Discord / Teams / ntfy / raw)
audio-hooks webhook # view current webhook config
audio-hooks webhook set --url https://ntfy.sh/my-claude-channel --format ntfy
audio-hooks webhook set --url https://hooks.slack.com/services/... --format slack
audio-hooks webhook test # POST a test payload
audio-hooks webhook clear # disable
Supported formats: slack, discord, teams, ntfy, raw. The raw format ships the audio-hooks.webhook.v1 schema. Every event includes session_id, session_name, worktree, agent, rate_limits, last_assistant_message, notification_type, error_type, source, trigger, load_reason, and permission_suggestions at the top level.
Text-to-speech
audio-hooks tts set --enabled true
# v5.0: TTS Claude's actual final reply on stop/subagent_stop
audio-hooks tts set --speak-assistant-message true
audio-hooks tts set --assistant-message-max-chars 200
Rate-limit alerts (v5.0)
The runner inspects every hook's stdin for rate_limits.{five_hour,seven_day} and plays a one-shot warning when crossing thresholds. Default thresholds are [80, 95]. Each (window, threshold, resets_at) tuple fires exactly once per reset window, so the user is alerted at 80% and again at 95% but never spammed.
audio-hooks rate-limits set --enabled true
audio-hooks rate-limits set --five-hour-thresholds 75,90,98
audio-hooks rate-limits set --seven-day-thresholds 80,95
Status line / context window monitoring
The status line displays real-time audio-hooks state and context window usage at the bottom of Claude Code. It includes a color-coded context usage bar that warns the user when context is getting full (the "agent dumb zone" starts around 60%):
/compact or /clear| User says | Run |
|---|---|
| "install the status line" / "show context usage" / "monitor context window" | audio-hooks statusline install then tell the user to restart Claude Code |
| "remove the status line" | audio-hooks statusline uninstall |
| "is the status line installed?" | audio-hooks statusline show |
After installing, the status line updates every 60 seconds and shows two lines:
[Opus] 🔊 echook v5.1.3 | 6/26 Sounds | Webhook: off | Theme: Voice
🌿 main ████░░░░ API Quota: 60% █████░░░ Context: 65% (130K/200K) ⚠️ /compact
Customise which status line segments to show
The status line has 10 segments the user can freely combine. By default all are shown. Set statusline_settings.visible_segments to an array of segment names to show only those:
Line 1 segments: model, version, sounds, webhook, theme
Line 2 segments: snooze, focus, branch, api_quota, context
| User says | Run |
|---|---|
| "only show context usage in the status line" | audio-hooks set statusline_settings.visible_segments '["context"]' |
| "show context and API quota only" | audio-hooks set statusline_settings.visible_segments '["context","api_quota"]' |
| "show context, branch, and model" | audio-hooks set statusline_settings.visible_segments '["model","context","branch"]' |
| "show everything in the status line" (reset) | audio-hooks set statusline_settings.visible_segments '[]' |
| "add webhook to the status line" | Read current with audio-hooks get statusline_settings.visible_segments, append "webhook" to the array, then set the updated array |
| "remove sounds count from status line" | Read current, remove "sounds", set the updated array |
Examples of what the user sees after customisation:
# Only context:
█████░░░ Context: 65% ⚠️ /compact
# context + api_quota:
██████░░ API Quota: 85% █████░░░ Context: 65% ⚠️ /compact
# model + branch + context:
[Opus]
🌿 main █████░░░ Context: 65% ⚠️ /compact
Notification settings (audio mode, detail level, per-hook overrides)
Control how notifications are delivered — audio only, desktop notification only, both, or disabled entirely:
| User says | Run |
|---|---|
| "only play audio, no desktop notifications" | audio-hooks set notification_settings.mode audio_only |
| "only show desktop notifications, no audio" | audio-hooks set notification_settings.mode notification_only |
| "I want both audio and notifications" | audio-hooks set notification_settings.mode audio_and_notification |
| "disable all notifications" | audio-hooks set notification_settings.mode disabled |
| "make notifications more detailed" | audio-hooks set notification_settings.detail_level verbose |
| "minimal notifications" | audio-hooks set notification_settings.detail_level minimal |
| "only use desktop notification for the stop hook" | audio-hooks set notification_settings.per_hook.stop notification_only |
Playback settings
# Adjust debounce (minimum ms between same hook firing, default 500)
audio-hooks set playback_settings.debounce_ms 300
Focus Flow (anti-distraction micro-tasks)
Focus Flow offers a brief breathing exercise or mindfulness prompt while Claude is thinking, helping the user stay focused during long operations:
| User says | Run |
|---|---|
| "enable focus flow" / "breathing exercises" | audio-hooks set focus_flow.enabled true |
| "disable focus flow" | audio-hooks set focus_flow.enabled false |
| "change breathing pattern to 4-7-8" | audio-hooks set focus_flow.breathing_pattern "4-7-8" |
| "only show focus after 30 seconds of thinking" | audio-hooks set focus_flow.min_thinking_seconds 30 |
Modes: breathing (default). Breathing patterns: 4-7-8 (default), or other supported patterns. min_thinking_seconds (default 15) controls the delay before the focus prompt appears.
Uninstall
| User says | Run |
|---|---|
| "uninstall audio hooks" (plugin install) | Tell the user to type /plugin uninstall audio-hooks@chanmeng-audio-hooks in Claude Code |
| "uninstall audio hooks" (script install) | bash scripts/uninstall.sh --yes |
| "remove everything including config and audio files" | bash scripts/uninstall.sh --yes --purge |
Read or write any config key
For any setting not covered above, use the generic getter/setter:
audio-hooks get <dotted.key> # read any config key
audio-hooks set <dotted.key> <value> # write any config key (auto-coerces bool/int/JSON)
Run audio-hooks manifest --schema to see the full JSON Schema for all config keys.
Test that audio is actually working
audio-hooks test all # exercise every enabled hook
audio-hooks test stop # one specific hook
audio-hooks test session_start_resume # a v5.0 matcher variant
Troubleshooting "no sound" / "wrong sound"
Always run audio-hooks diagnose first. It returns a JSON document listing the platform, audio player binary, the state of ~/.claude/settings.json (including disableAllHooks), and any audio files missing for the active theme. Errors come with stable code enums and a suggested_command you should run.
Common error codes you may see:
| Code | What it means | Run |
|---|---|---|
SETTINGS_DISABLE_ALL_HOOKS | The user (or their managed settings) has set disableAllHooks: true | Tell the user; Claude Code's config takes precedence |
AUDIO_PLAYER_NOT_FOUND | No mpg123/ffplay/aplay on Linux, or PowerShell missing on Windows | sudo apt install mpg123 (Linux) |
AUDIO_FILE_MISSING | Audio files for the active theme are missing | audio-hooks theme set default to fall back |
WEBHOOK_HTTP_ERROR / WEBHOOK_TIMEOUT | Webhook unreachable | audio-hooks webhook test and inspect the URL |
After running any fix, verify with audio-hooks logs tail --n 20 to see the recent NDJSON event stream.
If a user complains that the Context: X% indicator looks wrong (e.g. "it jumped to 97% after I switched models"), this is almost always expected math — switching from a 1M-context variant to a 200K window keeps the user's tokens identical but shrinks the denominator 5×. Since v5.1.3 the indicator displays absolute counts (e.g. Context: 83% (166K/200K)) so the math is self-explanatory; point the user at that.
If they want to verify what Claude Code is actually piping to the status line script, set CLAUDE_HOOKS_DEBUG=1 (or true/yes, case-insensitive) in their shell before launching Claude Code. The script then atomically dumps the most recent stdin JSON to ${state_dir}/statusline.last_input.json after every refresh. Read it with:
cat "${CLAUDE_PLUGIN_DATA:-$TEMP/claude_audio_hooks_queue}/statusline.last_input.json"
Inspect context_window.context_window_size to confirm whether Claude Code refreshed it after the /model switch. Privacy note: the dump may contain workspace paths and the last assistant message — instruct the user to disable the env var when they finish diagnosing.
All log events are NDJSON (one JSON object per line) under ${CLAUDE_PLUGIN_DATA}/logs/events.ndjson. Schema is audio-hooks.v1. Each event has ts, level, hook, session_id, action, and event-specific fields. Errors include error.code, error.hint, and error.suggested_command.
audio-hooks logs tail --n 50 # last 50 events
audio-hooks logs tail --n 100 --level error
audio-hooks logs clear
Cursor IDE 3.2.16+ has its own "Cursor Hooks Service" that auto-bridges Claude Code plugins (cursor.com/docs/reference/third-party-hooks). When the user has both Claude Code and Cursor on the same machine, Cursor calls our hook scripts on its own session events automatically — no extra install needed. Just run audio-hooks status and check editor_targets.cursor.state:
| State | Meaning |
|---|---|
bridged-via-claude-code | Cursor is auto-bridging; everything works (8 of 10 hooks). |
native | User ran audio-hooks install --cursor; Cursor reads ~/.cursor/hooks.json. |
double-registered | Both bridge AND native install present — fires audio twice. Run audio-hooks uninstall --cursor to fix. |
inactive | Cursor isn't running this project at all. |
For users who run Cursor without Claude Code, install natively (clone the repo, then run install --cursor from inside it):
# One-time install
git clone https://github.com/ChanMeng666/echook ~/audio-hooks
python ~/audio-hooks/bin/audio-hooks install --cursor # writes ~/.cursor/hooks.json
# Override DUPLICATE_BRIDGE if Claude Code is also installed (rare; double audio risk)
python ~/audio-hooks/bin/audio-hooks install --cursor --force
# Upgrade to a newer release — idempotent, preserves user_preferences.json
cd ~/audio-hooks && git pull && python bin/audio-hooks install --cursor
# Uninstall (preserves preferences for re-install)
python ~/audio-hooks/bin/audio-hooks uninstall --cursor
python ~/audio-hooks/bin/audio-hooks uninstall --cursor --purge # also delete ~/.cursor/audio-hooks-data/
There is no audio-hooks upgrade --cursor subcommand — audio-hooks upgrade targets Claude Code's plugin cache and intentionally does not touch ~/.cursor/. For Cursor-only installs, git pull && install --cursor is the upgrade recipe.
Runtime guard for --force: when the install marker records duplicate_bridge_forced: true and the runner is invoked under Cursor, the runtime emits a duplicate_bridge_runtime_skip warn event (DUPLICATE_BRIDGE_RUNTIME_SKIP) and returns 0 — Claude Code's bridge fires alone. Re-enable the native path by audio-hooks uninstall --cursor followed by re-install without --force.
Bridge limitations (these are Cursor's, not ours):
Notification and PermissionRequest hooks have no Cursor equivalent — those audio cues never fire under Cursor.Glob / WebFetch / WebSearch matchers don't trigger under Cursor (no equivalent tools).Refreshing the cached plugin code Cursor's bridge invokes: Cursor reads from ~/.claude/plugins/cache/chanmeng-audio-hooks/audio-hooks/<ver>/, so the user must refresh that cache to pick up new releases. Use audio-hooks upgrade — it wraps claude plugin update (data-preserving) with a fallback to uninstall --keep-data + install, so the user's user_preferences.json survives. The legacy 5.1.3-era recipe (/plugin uninstall + /plugin install) destroys config and should not be recommended.
Stdin field mapping: Cursor's cursor_version, conversation_id, final_status, reason, duration_ms, is_background_agent, workspace_roots, model, error_message are surfaced under a cursor: {...} sub-object in webhook payloads. user_email is redacted by default; opt in via audio-hooks set webhook_settings.include_user_email true.
OpenAI's Codex CLI does NOT auto-bridge Claude Code plugins — there's exactly one install path: a native registration at $CODEX_HOME/hooks.json (default ~/.codex/hooks.json). Run audio-hooks status and check editor_targets.codex.state:
| State | Meaning |
|---|---|
active | Installed AND [features].codex_hooks = true is set in ~/.codex/config.toml. Audio fires correctly. |
active-but-flag-disabled | Installed but the feature flag isn't enabled. Use your Edit tool to add [features]\ncodex_hooks = true to ~/.codex/config.toml and tell the user to restart Codex. |
active-but-flag-unknown | Installed but ~/.codex/config.toml couldn't be parsed. Investigate the file. |
inactive | Not installed. Run audio-hooks install --codex. |
For users who want to install audio-hooks for Codex (paste this single agent prompt — works whether or not they also have Claude Code):
git clone https://github.com/ChanMeng666/echook ~/audio-hooks
python ~/audio-hooks/bin/audio-hooks install --codex
Read the JSON output and act on it:
feature_flag_state | What you do |
|---|---|
freshly_written | Nothing more — install authored a fresh ~/.codex/config.toml with the flag enabled. Tell the user to restart Codex. |
already_enabled | Nothing more. Tell the user to restart Codex. |
section_missing | Use your Edit tool to append [features]\ncodex_hooks = true to ~/.codex/config.toml. Then tell the user to restart Codex. |
flag_missing_or_false | Use your Edit tool to set codex_hooks = true under the existing [features] section in ~/.codex/config.toml. Then tell the user to restart Codex. |
Why we don't auto-edit user-authored config.toml: TOML round-trip with comment preservation is unreliable, and getting it wrong destroys the user's formatting. Letting the AI agent do the targeted edit via its Edit tool keeps the user's file safe.
Upgrading the Codex install (idempotent, preserves preferences):
cd ~/audio-hooks && git pull && python bin/audio-hooks install --codex
There is no audio-hooks upgrade --codex subcommand — audio-hooks upgrade targets Claude Code's plugin cache. For Codex installs, git pull && install --codex is the upgrade recipe.
Uninstalling:
python ~/audio-hooks/bin/audio-hooks uninstall --codex
python ~/audio-hooks/bin/audio-hooks uninstall --codex --purge # also delete ~/.codex/audio-hooks-data/
The uninstall never touches ~/.codex/config.toml — the codex_hooks flag may benefit other Codex hook plugins. If the user wants to fully revert, they can remove the flag themselves.
Codex limitations (these are Codex's, not ours, per developers.openai.com/codex/hooks):
SessionStart, PreToolUse, PostToolUse, PermissionRequest, UserPromptSubmit, Stop. The other 18 audio-hooks canonical events have no Codex equivalent and the runner no-ops them with a skipped_no_codex_equivalent debug NDJSON event.SessionStart doesn't support env-output propagation (Cursor's {"env": {...}} mechanism). The runtime _resolve_data_dir() chain handles this — there's a Codex-gated step at priority 3 that lands at $CODEX_HOME/audio-hooks-data/ when detect_invoker() == "codex".Stdin field mapping: Codex's stdin uses the same snake_case schema as Claude Code (session_id, tool_name, hook_event_name, transcript_path, turn_id, tool_use_id, tool_response, stop_hook_active, last_assistant_message, source). No translation layer needed. Codex-specific fields (turn_id, tool_use_id, permission_mode, tool_response, stop_hook_active) are surfaced under a codex: {...} sub-object in webhook payloads (parallel to cursor: {...}).
audio-hooks manifest returns a single JSON document with:
subcommands — every CLI subcommand with arg list and descriptionhooks — every hook with default state, audio file, descriptionconfig_keys — every dotted config paththemes — available audio themeserror_codes — every stable error code with hint and suggested_commandenv_vars — every recognised environment variablelog_schema and webhook_schema version stringsaudio-hooks manifest --schema returns the JSON Schema for user_preferences.json (use this when you need to validate or generate config).
If any user request feels like it might involve a project capability you don't immediately recognise, read the manifest first instead of guessing. The manifest never lies; this skill might lag behind it after an update.
user_preferences.json directly. Use audio-hooks set and the typed setters (hooks enable, theme set, webhook set, tts set, rate-limits set). They round-trip through validation and emit structured success/error JSON.bash scripts/install-complete.sh when the plugin install path is available — the plugin path is the recommended one.~/.claude/settings.json by hand. Use audio-hooks install --plugin to register/deregister, or rely on /plugin install to do it.audio-hooks manifest # canonical introspection — read this first
audio-hooks status # full project state snapshot
audio-hooks version # version + install detection
audio-hooks diagnose # system check + warnings + errors
audio-hooks hooks list # all 26 hooks
audio-hooks hooks enable <name> # turn one on
audio-hooks hooks disable <name> # turn one off
audio-hooks hooks enable-only <a> <b> # exclusive enable
audio-hooks snooze [duration|off|status] # mute (default 30m)
audio-hooks theme set <default|custom> # switch theme
audio-hooks webhook # view current webhook config
audio-hooks webhook set --url <u> --format <f>
audio-hooks webhook test # POST a test payload
audio-hooks webhook clear # disable webhook
audio-hooks tts set --enabled true --speak-assistant-message true
audio-hooks rate-limits set --five-hour-thresholds 80,95
audio-hooks set notification_settings.mode <mode> # audio_only | notification_only | audio_and_notification | disabled
audio-hooks set notification_settings.detail_level <level> # minimal | standard | verbose
audio-hooks set notification_settings.per_hook.<hook> <mode> # per-hook override
audio-hooks set playback_settings.debounce_ms <int> # min ms between same hook (default 500)
audio-hooks set focus_flow.enabled true # enable focus flow
audio-hooks set focus_flow.breathing_pattern "4-7-8" # breathing pattern
audio-hooks statusline install # register status line (restart Claude Code after)
audio-hooks statusline uninstall # remove status line
audio-hooks statusline show # check registration state
audio-hooks set statusline_settings.visible_segments '["context"]' # show only context bar
audio-hooks set statusline_settings.visible_segments '["context","api_quota"]' # show context + API quota
audio-hooks set statusline_settings.visible_segments '[]' # show all (default)
audio-hooks test <hook|all> # play a hook end-to-end
audio-hooks logs tail --n 50 # recent NDJSON events
audio-hooks logs clear # truncate the event log
audio-hooks get <dotted.key> # any config key
audio-hooks set <dotted.key> <value> # any config key (auto-coerces)
audio-hooks update # show current version info
npx claudepluginhub chanmeng666/echook --plugin audio-hooksManages coding agent hooks (Claude Code, Codex CLI, OpenCode) via natural language commands. Adds, lists, removes, updates, and validates hooks for events like PreToolUse, SessionStart, and UserPromptSubmit.
Configures Claude Code hooks for lifecycle events like PreToolUse, SessionStart, and automation use cases such as formatting enforcement and permission control.
Enables voice conversations with Claude Code using speech-to-text and text-to-speech. Includes setup, diagnostics, and MCP-based voice interaction.