diff --git a/README.md b/README.md index 288ddae7..26fbaf63 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,25 @@ One command. Takes 10 seconds. macOS only. Re-run to update (sounds and config p Plus Terminal tab titles (`● project: done`) and macOS notifications when Terminal isn't focused. +## Quick controls + +Need to mute sounds and notifications during a meeting or pairing session? Two options: + +| Method | Command | When | +|---|---|---| +| **Slash command** | `/peon-ping-toggle` | While working in Claude Code | +| **CLI** | `peon --toggle` | From any terminal tab | + +Other CLI commands: + +```bash +peon --pause # Mute sounds +peon --resume # Unmute sounds +peon --status # Check if paused or active +``` + +Pausing mutes sounds and desktop notifications instantly. Persists across sessions until you resume. Tab titles remain active when paused. + ## Configuration Edit `~/.claude/hooks/peon-ping/config.json`: diff --git a/install.sh b/install.sh index f31d7c97..c726959f 100755 --- a/install.sh +++ b/install.sh @@ -103,6 +103,28 @@ fi chmod +x "$INSTALL_DIR/peon.sh" +# --- Install skill (slash command) --- +SKILL_DIR="$HOME/.claude/skills/peon-ping-toggle" +mkdir -p "$SKILL_DIR" +if [ -n "$SCRIPT_DIR" ] && [ -d "$SCRIPT_DIR/skills/peon-ping-toggle" ]; then + cp "$SCRIPT_DIR/skills/peon-ping-toggle/SKILL.md" "$SKILL_DIR/" +elif [ -z "$SCRIPT_DIR" ]; then + curl -fsSL "$REPO_BASE/skills/peon-ping-toggle/SKILL.md" -o "$SKILL_DIR/SKILL.md" +else + echo "Warning: skills/peon-ping-toggle not found in local clone, skipping skill install" +fi + +# --- Add shell alias --- +ALIAS_LINE='alias peon="bash ~/.claude/hooks/peon-ping/peon.sh"' +for rcfile in "$HOME/.zshrc" "$HOME/.bashrc"; do + if [ -f "$rcfile" ] && ! grep -qF 'alias peon=' "$rcfile"; then + echo "" >> "$rcfile" + echo "# peon-ping quick controls" >> "$rcfile" + echo "$ALIAS_LINE" >> "$rcfile" + echo "Added peon alias to $(basename "$rcfile")" + fi +done + # --- Verify sounds are installed --- echo "" for pack in $PACKS; do @@ -220,4 +242,9 @@ else echo "Uninstall: bash $INSTALL_DIR/uninstall.sh" fi echo "" +echo "Quick controls:" +echo " /peon-ping-toggle — toggle sounds in Claude Code" +echo " peon --toggle — toggle sounds from any terminal" +echo " peon --status — check if sounds are paused" +echo "" echo "Ready to work!" diff --git a/peon.sh b/peon.sh index 57066e86..56af7364 100755 --- a/peon.sh +++ b/peon.sh @@ -7,6 +7,25 @@ PEON_DIR="${CLAUDE_PEON_DIR:-$HOME/.claude/hooks/peon-ping}" CONFIG="$PEON_DIR/config.json" STATE="$PEON_DIR/.state.json" +# --- CLI subcommands (must come before INPUT=$(cat) which blocks on stdin) --- +PAUSED_FILE="$PEON_DIR/.paused" +case "${1:-}" in + --pause) touch "$PAUSED_FILE"; echo "peon-ping: sounds paused"; exit 0 ;; + --resume) rm -f "$PAUSED_FILE"; echo "peon-ping: sounds resumed"; exit 0 ;; + --toggle) + if [ -f "$PAUSED_FILE" ]; then rm -f "$PAUSED_FILE"; echo "peon-ping: sounds resumed" + else touch "$PAUSED_FILE"; echo "peon-ping: sounds paused"; fi + exit 0 ;; + --status) + [ -f "$PAUSED_FILE" ] && echo "peon-ping: paused" || echo "peon-ping: active" + exit 0 ;; + --help|-h) + echo "Usage: peon --pause | --resume | --toggle | --status"; exit 0 ;; + --*) + echo "Unknown option: $1" >&2 + echo "Usage: peon --pause | --resume | --toggle | --status" >&2; exit 1 ;; +esac + INPUT=$(cat) # Debug log (comment out for quiet operation) @@ -31,6 +50,9 @@ for cat in ['greeting','acknowledge','complete','error','permission','resource_l [ "$ENABLED" = "false" ] && exit 0 +PAUSED=false +[ -f "$PEON_DIR/.paused" ] && PAUSED=true + # --- Parse event fields (shlex.quote prevents shell injection) --- eval "$(/usr/bin/python3 -c " import sys, json, shlex @@ -111,6 +133,11 @@ if [ "$EVENT" = "SessionStart" ] && [ -f "$PEON_DIR/.update_available" ]; then fi fi +# --- Show pause status on SessionStart --- +if [ "$EVENT" = "SessionStart" ] && [ "$PAUSED" = "true" ]; then + echo "peon-ping: sounds paused — run 'peon --resume' or '/peon-ping-toggle' to unpause" >&2 +fi + # --- Check annoyed state (rapid prompts) --- check_annoyed() { /usr/bin/python3 -c " @@ -243,7 +270,7 @@ if [ -n "$TITLE" ]; then fi # --- Play sound --- -if [ -n "$CATEGORY" ]; then +if [ -n "$CATEGORY" ] && [ "$PAUSED" != "true" ]; then SOUND_FILE=$(pick_sound "$CATEGORY") if [ -n "$SOUND_FILE" ] && [ -f "$SOUND_FILE" ]; then nohup afplay -v "$VOLUME" "$SOUND_FILE" >/dev/null 2>&1 & @@ -251,7 +278,7 @@ if [ -n "$CATEGORY" ]; then fi # --- Smart notification: only when terminal is NOT frontmost --- -if [ -n "$NOTIFY" ]; then +if [ -n "$NOTIFY" ] && [ "$PAUSED" != "true" ]; then FRONTMOST=$(osascript -e 'tell application "System Events" to get name of first process whose frontmost is true' 2>/dev/null) case "$FRONTMOST" in Terminal|iTerm2|Warp|Alacritty|kitty|WezTerm|Ghostty) ;; # terminal is focused, skip notification diff --git a/skills/peon-ping-toggle/SKILL.md b/skills/peon-ping-toggle/SKILL.md new file mode 100644 index 00000000..ae220a82 --- /dev/null +++ b/skills/peon-ping-toggle/SKILL.md @@ -0,0 +1,19 @@ +--- +name: peon-ping-toggle +description: Toggle peon-ping sound notifications on/off. Use when user wants to mute, unmute, pause, or resume peon sounds during a Claude Code session. +user_invocable: true +--- + +# peon-ping-toggle + +Toggle peon-ping sounds on or off. + +Run the following command using the Bash tool: + +```bash +bash ~/.claude/hooks/peon-ping/peon.sh --toggle +``` + +Report the output to the user. The command will print either: +- `peon-ping: sounds paused` — sounds are now muted +- `peon-ping: sounds resumed` — sounds are now active diff --git a/tests/peon.bats b/tests/peon.bats index 0ec3b620..fd118819 100644 --- a/tests/peon.bats +++ b/tests/peon.bats @@ -256,3 +256,71 @@ JSON log_line=$(tail -1 "$TEST_DIR/afplay.log") [[ "$log_line" == *"-v 0.3"* ]] } + +# ============================================================ +# Pause / mute feature +# ============================================================ + +@test "--toggle creates .paused file and prints paused message" { + run bash "$PEON_SH" --toggle + [ "$status" -eq 0 ] + [[ "$output" == *"sounds paused"* ]] + [ -f "$TEST_DIR/.paused" ] +} + +@test "--toggle removes .paused file when already paused" { + touch "$TEST_DIR/.paused" + run bash "$PEON_SH" --toggle + [ "$status" -eq 0 ] + [[ "$output" == *"sounds resumed"* ]] + [ ! -f "$TEST_DIR/.paused" ] +} + +@test "--pause creates .paused file" { + run bash "$PEON_SH" --pause + [ "$status" -eq 0 ] + [[ "$output" == *"sounds paused"* ]] + [ -f "$TEST_DIR/.paused" ] +} + +@test "--resume removes .paused file" { + touch "$TEST_DIR/.paused" + run bash "$PEON_SH" --resume + [ "$status" -eq 0 ] + [[ "$output" == *"sounds resumed"* ]] + [ ! -f "$TEST_DIR/.paused" ] +} + +@test "--status reports paused when .paused exists" { + touch "$TEST_DIR/.paused" + run bash "$PEON_SH" --status + [ "$status" -eq 0 ] + [[ "$output" == *"paused"* ]] +} + +@test "--status reports active when not paused" { + rm -f "$TEST_DIR/.paused" + run bash "$PEON_SH" --status + [ "$status" -eq 0 ] + [[ "$output" == *"active"* ]] +} + +@test "paused file suppresses sound on SessionStart" { + touch "$TEST_DIR/.paused" + run_peon '{"hook_event_name":"SessionStart","cwd":"/tmp/myproject","session_id":"s1","permission_mode":"default"}' + [ "$PEON_EXIT" -eq 0 ] + ! afplay_was_called +} + +@test "paused SessionStart shows stderr status line" { + touch "$TEST_DIR/.paused" + run_peon '{"hook_event_name":"SessionStart","cwd":"/tmp/myproject","session_id":"s1","permission_mode":"default"}' + [[ "$PEON_STDERR" == *"sounds paused"* ]] +} + +@test "paused file suppresses notification on permission_prompt" { + touch "$TEST_DIR/.paused" + run_peon '{"hook_event_name":"Notification","notification_type":"permission_prompt","cwd":"/tmp/myproject","session_id":"s1","permission_mode":"default"}' + [ "$PEON_EXIT" -eq 0 ] + ! afplay_was_called +}