From b2ee0b4535a884783cf481d55d411d84f9dcc9b4 Mon Sep 17 00:00:00 2001 From: Mathias Asberg Date: Mon, 4 May 2026 22:52:01 +0200 Subject: [PATCH] Add persistent sound toggle --- README.md | 15 ++++ SKILL.md | 30 ++++++-- scripts/attention-please.sh | 144 ++++++++++++++++++++++++++++++++++-- 3 files changed, 175 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 3057cc0..823ad86 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,21 @@ Tell your agent to run the skill at the end of each turn or when input/confirmat $attention-please update AGENTS.md to run the attention-please skill at the end of each turn or when input/confirmation is needed. ``` +## Toggle the alert sound + +The short `afplay` alert can be muted or re-enabled persistently: + +```bash +scripts/attention-please.sh --sound off +scripts/attention-please.sh --sound on +scripts/attention-please.sh --sound toggle +scripts/attention-please.sh --sound status +``` + +This controls only the alert sound. Speech remains controlled separately with `ATTENTION_PLEASE_NO_SAY=1`. + +By default the preference is stored in `${XDG_CONFIG_HOME:-~/.config}/attention-please/config`. Override the path with `ATTENTION_PLEASE_CONFIG_FILE`. + ## Audio sample MP4 clip (not embedded in repository code): diff --git a/SKILL.md b/SKILL.md index ebf9927..0fce032 100644 --- a/SKILL.md +++ b/SKILL.md @@ -1,18 +1,23 @@ --- name: attention-please -description: Play an alert sound and speak "Project NAME needs your attention." Always use at the end of every response or whenever Codex needs user input or confirmation; derive the project name from the git remote (origin) with fallback to the repo folder or an override. +description: Play an alert sound and speak "Project NAME needs your attention." Use only when the user explicitly asks for an alert, a long-running task blocks on human input, or a background process finishes and needs attention. --- # Attention Please ## Overview -Play a short audible alert and a spoken prompt indicating which project needs attention. Use this as the final step after completing work on every response, and also right before asking the user for input. +Play a short audible alert and a spoken prompt indicating which project needs attention. + +Use this only when: +- the user explicitly asks for an audible alert +- a long-running task blocks on human input or confirmation +- a background process finishes and the user should be brought back to the thread ## Source Of Truth And Publishing - GitHub repository: `https://github.com/Mindgames/attention-please` -- Local development repository: `~/Projects/skills/attention-please` +- Local development repository: `~/Projects/attention-please` - Do not edit installed copies in `~/.agents/skills/...` or `~/.codex/skills/...` directly. - Update this repository, push to GitHub, open a PR, then reinstall via `skills`. @@ -55,11 +60,11 @@ After reinstall, restart your agent client if needed so it reloads skills. - Always open this skill using the absolute path from the active session skills list. - Do not guess repo-relative `.codex/skills/...` paths unless that exact path was listed by the session. -## Rules (non-negotiable) +## Rules -- Always run this script at the end of every response, even if you are not asking for input. -- If you missed it in the previous turn, run it immediately at the start of the next turn before doing anything else, then continue. -- Treat a missed run as a bug; prioritize correcting it as soon as you notice. +- Do not run this script at the end of ordinary responses. +- Do not run this script at the start of a later turn to compensate for a previous non-run. +- When used, run it immediately before asking for input or reporting the completed background result. ## Workflow @@ -72,7 +77,7 @@ After reinstall, restart your agent client if needed so it reloads skills. If your environment does not expose the session skill path directly, use the actual install location used in that session (for example under `~/.agents/...` or `~/.codex/...`) as that absolute path. -3. Run this immediately before sending your final response to the user. +3. Run this immediately before the attention-worthy user prompt or completion notice. 4. Continue with your response to the user. ### Project name resolution @@ -87,6 +92,11 @@ After reinstall, restart your agent client if needed so it reloads skills. - Sound: macOS `afplay` with `/System/Library/Sounds/Ping.aiff` by default. - Override sound: set `ATTENTION_PLEASE_SOUND`. - Disable sound: set `ATTENTION_PLEASE_NO_SOUND=1`. +- Persistently disable sound: run `/absolute/path/to/attention-please/scripts/attention-please.sh --sound off`. +- Persistently enable sound: run `/absolute/path/to/attention-please/scripts/attention-please.sh --sound on`. +- Toggle persisted sound: run `/absolute/path/to/attention-please/scripts/attention-please.sh --sound toggle`. +- Check persisted sound: run `/absolute/path/to/attention-please/scripts/attention-please.sh --sound status`. +- Sound preference is stored in `${XDG_CONFIG_HOME:-~/.config}/attention-please/config`; override with `ATTENTION_PLEASE_CONFIG_FILE`. - Speech: macOS `say`; if unavailable, the message prints to stdout. - Disable speech: set `ATTENTION_PLEASE_NO_SAY=1`. - Voice: set `ATTENTION_PLEASE_SAY_VOICE`. @@ -102,3 +112,7 @@ After reinstall, restart your agent client if needed so it reloads skills. ```bash ATTENTION_PLEASE_PROJECT="project-name" ATTENTION_PLEASE_SAY_VOICE="Samantha" /absolute/path/to/attention-please/scripts/attention-please.sh ``` + +```bash +/absolute/path/to/attention-please/scripts/attention-please.sh --sound toggle +``` diff --git a/scripts/attention-please.sh b/scripts/attention-please.sh index 3994727..52de9ce 100755 --- a/scripts/attention-please.sh +++ b/scripts/attention-please.sh @@ -3,7 +3,9 @@ set -euo pipefail usage() { cat <<'EOF' -Usage: scripts/attention-please.sh +Usage: + scripts/attention-please.sh + scripts/attention-please.sh --sound on|off|toggle|status Plays a sound and speaks "Project NAME needs your attention." @@ -13,10 +15,18 @@ Environment variables: ATTENTION_PLEASE_MESSAGE Full message override. ATTENTION_PLEASE_SOUND Sound file path (default: /System/Library/Sounds/Ping.aiff). ATTENTION_PLEASE_NO_SOUND Disable sound when set to 1/true/yes/on. + ATTENTION_PLEASE_CONFIG_FILE + Override persistent config file path. ATTENTION_PLEASE_NO_SAY Disable speech when set to 1/true/yes/on. ATTENTION_PLEASE_SAY_VOICE Voice for say (e.g., "Samantha"). ATTENTION_PLEASE_SAY_RATE Rate for say (words per minute). ATTENTION_PLEASE_VERBOSE Emit warnings when set to 1/true/yes/on. + +Sound commands: + --sound off Disable the afplay sound for future runs. + --sound on Enable the afplay sound for future runs. + --sound toggle Toggle the persisted sound preference. + --sound status Print the current sound preference. EOF } @@ -27,6 +37,76 @@ is_truthy() { esac } +normalize_sound_state() { + case "$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]')" in + 1|true|yes|on|y|enabled) printf '%s\n' "on" ;; + 0|false|no|off|n|disabled) printf '%s\n' "off" ;; + *) return 1 ;; + esac +} + +default_config_file() { + if [ -n "${ATTENTION_PLEASE_CONFIG_FILE:-}" ]; then + printf '%s\n' "$ATTENTION_PLEASE_CONFIG_FILE" + elif [ -n "${XDG_CONFIG_HOME:-}" ]; then + printf '%s\n' "${XDG_CONFIG_HOME%/}/attention-please/config" + elif [ -n "${HOME:-}" ]; then + printf '%s\n' "${HOME%/}/.config/attention-please/config" + fi +} + +read_sound_preference() { + if [ -z "$config_file" ] || [ ! -f "$config_file" ]; then + return 1 + fi + + while IFS= read -r line || [ -n "$line" ]; do + case "$line" in + sound_enabled=*) + normalize_sound_state "${line#sound_enabled=}" && return 0 + return 1 + ;; + esac + done < "$config_file" + + return 1 +} + +current_sound_state() { + read_sound_preference || printf '%s\n' "on" +} + +write_sound_preference() { + local next_state + next_state="$(normalize_sound_state "$1")" + + if [ -z "$config_file" ]; then + printf '%s\n' "attention-please: unable to resolve a config file path." >&2 + return 1 + fi + + mkdir -p "$(dirname "$config_file")" + local tmp_file="${config_file}.tmp.$$" + + { + if [ -f "$config_file" ]; then + while IFS= read -r line || [ -n "$line" ]; do + case "$line" in + sound_enabled=*) continue ;; + esac + printf '%s\n' "$line" + done < "$config_file" + fi + printf 'sound_enabled=%s\n' "$next_state" + } > "$tmp_file" + + mv "$tmp_file" "$config_file" +} + +print_sound_state() { + printf 'attention-please sound is %s\n' "$(current_sound_state)" +} + project_name="${ATTENTION_PLEASE_PROJECT:-}" sound_path="${ATTENTION_PLEASE_SOUND:-/System/Library/Sounds/Ping.aiff}" remote_name="${ATTENTION_PLEASE_REMOTE:-origin}" @@ -36,11 +116,34 @@ say_rate="${ATTENTION_PLEASE_SAY_RATE:-}" no_sound="${ATTENTION_PLEASE_NO_SOUND:-}" no_say="${ATTENTION_PLEASE_NO_SAY:-}" verbose="${ATTENTION_PLEASE_VERBOSE:-}" +config_file="$(default_config_file)" +sound_command="" -if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then - usage - exit 0 -fi +while [ "$#" -gt 0 ]; do + case "$1" in + -h|--help) + usage + exit 0 + ;; + --sound) + if [ "$#" -lt 2 ]; then + printf '%s\n' "attention-please: --sound requires on, off, toggle, or status." >&2 + exit 2 + fi + sound_command="$2" + shift 2 + ;; + --sound=*) + sound_command="${1#--sound=}" + shift + ;; + *) + printf '%s\n' "attention-please: unknown argument: $1" >&2 + usage >&2 + exit 2 + ;; + esac +done warn() { if is_truthy "$verbose"; then @@ -48,6 +151,34 @@ warn() { fi } +if [ -n "$sound_command" ]; then + sound_command="$(printf '%s' "$sound_command" | tr '[:upper:]' '[:lower:]')" + case "$sound_command" in + status) + print_sound_state + exit 0 + ;; + toggle) + if [ "$(current_sound_state)" = "on" ]; then + write_sound_preference "off" + else + write_sound_preference "on" + fi + print_sound_state + exit 0 + ;; + *) + if ! next_state="$(normalize_sound_state "$sound_command")"; then + printf '%s\n' "attention-please: --sound requires on, off, toggle, or status." >&2 + exit 2 + fi + write_sound_preference "$next_state" + print_sound_state + exit 0 + ;; + esac +fi + repo_root="" if command -v git >/dev/null 2>&1; then if git rev-parse --show-toplevel >/dev/null 2>&1; then @@ -90,7 +221,8 @@ else message="Project ${project_name} needs your attention." fi -if ! is_truthy "$no_sound"; then +sound_state="$(current_sound_state)" +if ! is_truthy "$no_sound" && [ "$sound_state" != "off" ]; then if command -v afplay >/dev/null 2>&1; then if [ -f "$sound_path" ]; then if is_truthy "$verbose"; then