Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`:
Expand Down
27 changes: 27 additions & 0 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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!"
31 changes: 29 additions & 2 deletions peon.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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 "
Expand Down Expand Up @@ -243,15 +270,15 @@ 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 &
fi
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
Expand Down
19 changes: 19 additions & 0 deletions skills/peon-ping-toggle/SKILL.md
Original file line number Diff line number Diff line change
@@ -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
68 changes: 68 additions & 0 deletions tests/peon.bats
Original file line number Diff line number Diff line change
Expand Up @@ -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
}