Warcraft III Peon voice notifications (+ more!) for Claude Code, Codex, IDEs, and any AI agent. Stop babysitting your terminal. Employ a Peon today.
peon-ping
English | νκ΅μ΄ | δΈζ | ζ₯ζ¬θͺ
Game character voice lines + visual overlay notifications when your AI coding agent needs attention β or let the agent pick its own sound via MCP.
AI coding agents don't notify you when they finish or need permission. You tab away, lose focus, and waste 15 minutes getting back into flow. peon-ping fixes this with voice lines and bold on-screen banners from Warcraft, StarCraft, Portal, Zelda, and more β works with Claude Code, Amp, GitHub Copilot, Codex, Cursor, OpenCode, Kilo CLI, Kiro, Kimi Code, Windsurf, Google Antigravity, Rovo Dev CLI, DeepAgents, Qwen Code, iFlow CLI, Trae, Kiro IDE, ECA, and any MCP client.
See it in action → peonping.com
- Install
- What you'll hear
- Quick controls
- Configuration
- Peon Trainer
- MCP server
- Multi-IDE support
- Remote development
- Mobile notifications
- Sound packs
- Debugging
- Uninstall
- Requirements
- How it works
- Links
Install
Option 1: Homebrew (recommended)
brew install PeonPing/tap/peon-ping
Then run peon-ping-setup to register hooks and download sound packs. macOS and Linux.
Option 2: Installer script (macOS, Linux, WSL2)
curl -fsSL https://raw.githubusercontent.com/PeonPing/peon-ping/main/install.sh | bash
β οΈ WSL2 audio notes. peon-ping plays audio on the Windows side. On first run it probes your Windows host once (cached per Windows build) to pick the best playback path:
- On Windows 10 / Windows 11 pre-24H2, WPF MediaPlayer is used directly β native MP3 + WAV, no extra dependencies.
- On Windows 11 24H2+ (build 26100+), Microsoft removed legacy Windows Media Player from the OS and WPF MediaPlayer fails (
MILAVERR_INVALIDWMPVERSION). peon-ping falls back toSystem.Media.SoundPlayer, which uses the Win32PlaySoundAPI and works everywhere β but it's WAV-only, so MP3 packs require ffmpeg to transcode on the fly:
sudo apt update; sudo apt install -y ffmpeg
You can override the auto-detection with PEONWSLAUDIO_BACKEND=auto|mediaplayer|soundplayer:
auto(default) β probe + cache as described abovemediaplayerβ force WPF MediaPlayer over the WSL UNC path (fails silently on 24H2+)soundplayerβ force tmpfile copy +SoundPlayer(universal, requires ffmpeg for non-WAV files)
Option 3: Installer for Windows
Invoke-WebRequest -Uri "https://raw.githubusercontent.com/PeonPing/peon-ping/main/install.ps1" -OutFile ".\install.ps1" -UseBasicParsing
powershell -ExecutionPolicy Bypass -File .\install.ps1
Installs a curated starter set of packs by default. Re-run to update while preserving config/state. Or pick your packs interactively at peonping.com and get a custom install command.
Windows installer parameters:
-Allβ install all available packs-Packs peon,sc_kerrigan,...β install specific packs only-Lang en,fr,...β install only packs matching language(s)-Localβ install packs, config, hooks, and skills into./.claude/for the current project-Globalβ explicit global install (same as default)-InitLocalConfigβ create./.claude/hooks/peon-ping/config.jsononly
-Local does not install the global peon CLI shim or modify your user PATH. Hooks are registered in the project-level ./.claude/settings.json with absolute paths so they work from any working directory within the project.
Windows examples:
powershell -ExecutionPolicy Bypass -File .\install.ps1 -All
powershell -ExecutionPolicy Bypass -File .\install.ps1 -Packs peon,sc_kerrigan
powershell -ExecutionPolicy Bypass -File .\install.ps1 -Local
powershell -ExecutionPolicy Bypass -File .\install.ps1 -InitLocalConfig
If the initial download fails with a TLS error on older Windows PowerShell, run this once in the same session and retry:
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
Option 4: Clone and inspect first
git clone https://github.com/PeonPing/peon-ping.git
cd peon-ping
./install.sh
On Windows PowerShell:
git clone https://github.com/PeonPing/peon-ping.git
Set-Location peon-ping
.\install.ps1
Option 5: Nix (macOS, Linux)
Run directly from source without installing:
nix run github:PeonPing/peon-ping -- status
nix run github:PeonPing/peon-ping -- packs install peon
Or install to your profile:
nix profile install github:PeonPing/peon-ping
Development shell (bats, shellcheck, nodejs):
nix develop # or use direnv
Home Manager module (declarative configuration)
For reproducible setups, use the Home Manager module:
# In your home.nix or flake.nix
{ inputs, pkgs, ... }:
let peonCursorAdapterPath = "${inputs.peon-ping.packages.${pkgs.system}.default}/share/peon-ping/adapters/cursor.sh"; in { imports = [ inputs.peon-ping.homeManagerModules.default ];
programs.peon-ping = { enable = true; package = inputs.peon-ping.packages.${pkgs.system}.default; claudeCodeIntegration = true;
settings = { default_pack = "glados"; volume = 0.7; enabled = true; desktop_notifications = true; categories = { "session.start" = true; "task.complete" = true; "task.error" = true; "input.required" = true; "resource.limit" = true; "user.spam" = true; }; };
# Install packs from og-packs (simple string notation) # and custom sources (attrset with name + src) installPacks = [ "peon" "glados" "sc_kerrigan" # Custom pack from GitHub (openpeon.com registry) { name = "mr_meeseeks"; src = pkgs.fetchFromGitHub { owner = "kasperhendriks"; repo = "openpeon-mrmeeseeks"; rev = "main"; # or use a commit hash for reproducibility sha256 = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; }; } ]; enableZshIntegration = true; };
# Optional extra IDE hooks, like Cursor home.file.".cursor/hooks.json".text = builtins.toJSON { version = 1; hooks = { afterAgentResponse = [{ command = "bash ${peonCursorAdapterPath} afterAgentResponse"; }]; stop = [{ command = "bash ${peonCursorAdapterPath} stop"; }]; }; }; }
Sound pack installation: The installPacks option supports two formats:
- Simple strings (e.g.,
"peon","glados") β fetched from the og-packs repository - Custom sources β attrset with
nameandsrcfields, wheresrccan be any Nix fetcher result (e.g.,pkgs.fetchFromGitHub)
pkgs.fetchFromGitHub: { name = "pack_name"; src = pkgs.fetchFromGitHub { owner = "github-owner"; repo = "repo-name"; rev = "main"; # or a commit hash/tag sha256 = ""; # Leave empty first, Nix will tell you the correct hash }; }
Claude Code hooks: set programs.peon-ping.claudeCodeIntegration = true; to install the Claude Code hook scripts under ~/.claude/hooks/peon-ping/ and merge the standard peon-ping hook entries into ~/.claude/settings.json.
Other IDE hooks: adapters for other IDEs are still opt-in so the module does not overwrite unrelated IDE settings. peon-ping provides adapter scripts such as cursor.sh in adapters/, and you can wire them like this:
${inputs.peon-ping.packages.${pkgs.system}.default}/share/peon-ping/adapters/$YOURIDE.sh EVENTNAME See the Cursor example above.
What you'll hear
| Event | CESP Category | Examples | |---|---|---| | Session starts | session.start | "Ready to work!", "Something need doing?" | | Task finishes | task.complete | "Work complete.", "Work, work." | | Agent acknowledged task | task.acknowledge | "I can do that.", "Be happy to.", "Okie dokie." (disabled by default) | | Permission needed | input.required | "Hmm?", "What you want?", "Yes?" | | Tool or command error | task.error | "Me not that kind of orc!", "Ugh." | | Rate or token limit hit | resource.limit | "Why not?" | | Rapid prompts (3+ in 10s) | user.spam | "Whaaat?", "Me busy, leave me alone!", "No time for play." |
Plus large overlay banners on every screen (macOS/WSL/MSYS2) and terminal tab titles (β project: done) β you'll know something happened even if you're in another app.
peon-ping implements the Coding Event Sound Pack Specification (CESP) β an open standard for coding event sounds that any agentic IDE can adopt.
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 |
Prefer it to happen automatically? Set focusdetect to have peon-ping honor macOS Focus / Do Not Disturb. Sounds and notifications go quiet whenever a Focus is on and resume when you turn it off. See also headphonesonly and meeting_detect.
Other CLI commands:
Windows note: Windows currently supports the day-one controls (status,toggle,volume, corepacks,notifications on/off,debug,logs,trainer). More advanced commands likesetup,rotation,preview, andmobileare tracked as follow-up Windows parity work.
peon setup # Interactive setup wizard (volume, categories, notifications)
peon pause # Mute sounds
peon resume # Unmute sounds
peon mute # Alias for 'pause'
peon unmute # Alias for 'resume'
peon status # Check if paused or active (concise)
peon status --verbose # Show full details (notifications, headphones, IDEs, etc.)
peon volume # Show current volume
peon volume 0.7 # Set volume (0.0β1.0)
peon rotation # Show current rotation mode
peon rotation random # Set rotation mode (random|round-robin|session_override)
peon packs list # List installed sound packs
peon packs list --registry # Browse all available packs in the registry
peon packs community # List all registry packs grouped by trust tier (Windows)
peon packs search <query> # Search registry packs by name (Windows)
peon packs install <p1,p2> # Install packs from the registry
peon packs install --all # Install all packs from the registry
peon packs install-local <path> # Install a pack from a local directory
peon packs use <name> # Switch to a specific pack (auto-installs from registry on Windows)
peon packs use --install <name> # Switch to pack, installing from registry if needed
peon packs next # Cycle to the next pack
peon packs remove <p1,p2> # Remove specific packs
peon packs bind <name> # Bind a pack to the current directory
peon packs bind --pattern <path> # Bind a pack to a directory pattern, e.g. "*/services"
peon packs unbind # Remove the current directory
peon packs bindings # List all assigned bindings
peon packs ide-bind <ide> <name> # Bind a pack to an IDE id, e.g. codex
peon packs ide-unbind <ide> # Remove an IDE binding
peon packs ide-bindings # List all IDE-based bindings
peon packs exclude add <path> # Silence sounds & notifications for a glob or directory
peon packs exclude remove <path> # Stop silencing the given path
peon packs exclude list # List silenced paths
peon sounds list [pack] # List sounds in a pack, marking disabled ones
peon sounds disable <category> <file> [--pack=<name>] # Mute a single sound within a pack
peon sounds enable <category> <file> [--pack=<name>] # Re-enable a previously disabled sound
peon notifications on # Enable desktop notifications
peon notifications off # Disable desktop notifications
peon notifications overlay # Use large overlay banners (default)
peon notifications standard # Use standard system notifications
peon notifications test # Send a test notification
peon notifications position [pos] # Get/set notification position (top-left, top-center, top-right, bottom-left, bottom-center, bottom-right)
peon notifications dismiss [N] # Get/set auto-dismiss time in seconds (0 = persistent)
peon notifications label [text|reset] # Get/set project label override for notifications
peon notifications template [key] [fmt] # Get/set/reset message templates (keys: stop, permission, error, idle, question)
peon preview # Play all sounds from session.start
peon preview <category> # Play all sounds from a specific category
peon preview --list # List all categories in the active pack
peon mobile ntfy <topic> # Set up phone notifications (free)
peon mobile off # Disable phone notifications
peon mobile test # Send a test notification
peon debug on # Enable debug logging
peon debug off # Disable debug logging
peon debug status # Show debug state, log directory, file count, total size
peon logs # Show last 50 lines of today's log
peon logs --last N # Show last N lines across all log files
peon logs --session ID # Filter today's log by session ID
peon logs --session ID --all # Search all log files for session ID
peon logs --clear # Delete all log files (with confirmation)
peon relay --daemon # Start audio relay (for SSH/devcontainer)
peon relay --stop # Stop background relay
Available CESP categories for peon preview: session.start, task.acknowledge, task.complete, task.error, input.required, resource.limit, user.spam. (Extended categories session.end and task.progress are defined in the CESP spec and supported by pack manifests, but not currently triggered by built-in hook events.)
Tab completion is supported β type peon packs use <TAB> to see available pack names.
Pausing mutes sounds and desktop notifications instantly. Persists across sessions until you resume. Tab titles remain active when paused.
Configuration
Quickstart β peon setup
The fastest way to configure peon-ping is the interactive wizard:
peon setup
It walks you through every common setting in one go β press Enter at any prompt to keep the current value:
ββββββββββββββββββββββββββββββββββββββββ
β peon-ping setup wizard β
ββββββββββββββββββββββββββββββββββββββββ
ββ Volume ββ > Volume (0.0 - 1.0) (0.5):
ββ Sound categories ββ > Session start [on/off] (on): > Task acknowledge [on/off] (off): > Task complete [on/off] (on): > Task error [on/off] (on): > Input required (permissions, questions) [on/off] (on): > Resource limit (context compaction) [on/off] (on): > User spam (rapid prompts) [on/off] (on):
ββ Notifications ββ > Desktop notifications [on/off] (on):
Overlay theme: 1) Neon (cyberpunk) 2) Glass (translucent) 3) Sakura (cherry blossom) 4) Jarvis (iron man) > Theme [neon]:
Notification position: 1) Top center 2) Top right ... > Position [top-center]:
Auto-dismiss: 1) Persistent (click to dismiss) 2) 3 seconds 3) 4 seconds ... > Dismiss time [4]:
β Configuration saved!
What the wizard covers:
- Volume β playback volume (0.0 β 1.0)
- Sound categories β enable/disable each CESP category individually (session start, task complete, permission prompts, errors, etc.)
- Desktop notifications β master switch for overlay banners
- Overlay theme β choose the visual style (neon, glass, sakura, jarvis)
- Position β where notifications appear (top-center, top-right, etc.)
- Auto-dismiss β how long notifications stay visible (
0= persistent, click to dismiss)
~/.claude/hooks/peon-ping/config.json. You can rerun peon setup anytime to tweak settings β it always shows your current values as defaults.
Tip: All individualpeonsubcommands (peon volume,peon notifications position top-right, etc.) still work if you prefer scripting or tweaking one setting at a time β see the Quick controls section.
Slash commands and manual config
peon-ping also installs slash commands in Claude Code:
/peon-ping-toggleβ mute/unmute sounds/peon-ping-configβ change any setting (volume, packs, categories, etc.)/peon-ping-rename <name>β give this session a custom name shown in notification titles and the terminal tab title (zero tokens, hook-intercepted); no argument resets to auto-detect
Config location depends on install mode:
- Global install:
$CLAUDECONFIGDIR/hooks/peon-ping/config.json(default~/.claude/hooks/peon-ping/config.json) - Local install:
./.claude/hooks/peon-ping/config.json
{
"volume": 0.5,
"categories": {
"session.start": true,
"task.acknowledge": true,
"task.complete": true,
"task.error": true,
"input.required": true,
"resource.limit": true,
"user.spam": true
}
}
Independent Controls
peon-ping has three independent controls that can be mixed and matched:
| Config Key | Controls | Affects Sounds | Affects Desktop Popups | Affects Mobile Push | |------------|----------|----------------|------------------------|---------------------| | enabled | Master audio switch | β
Yes | β No | β No | | desktop_notifications | Desktop popup banners | β No | β
Yes | β No | | mobile_notify.enabled | Phone push notifications | β No | β No | β
Yes |
This means you can:
- Keep sounds but disable desktop popups:
peon notifications off - Keep desktop popups but disable sounds:
peon pause - Enable mobile push without desktop popups: set
desktopnotifications: falseandmobilenotify.enabled: true
- volume: 0.0β1.0 (quiet enough for the office)
- desktop_notifications:
true/falseβ toggle desktop notification popups independently from sounds (default:true). When disabled, sounds continue playing but visual popups are suppressed. Mobile notifications are unaffected. - notification_style:
"overlay"or"standard"β controls how desktop notifications appear (default:"overlay")
terminal-notifier / osascript on macOS, Windows toast on WSL/MSYS2. When terminal-notifier is installed (brew install terminal-notifier), clicking a standard notification focuses your terminal automatically (supports Ghostty, Warp, iTerm2, Zed, Terminal.app). On native Windows, clicking a toast notification focuses the IDE or terminal window (supports VS Code, Cursor, Windsurf, Windows Terminal, PowerShell). With multiple windows open, the notification targets the exact window that originated the event via PID-based process tree matching. - overlay_theme:
"jarvis","glass","sakura", or omit for the default overlay β macOS only (default: none)
- categories: Toggle individual CESP sound categories on/off (e.g.
"session.start": falseto disable greeting sounds) - annoyedthreshold / annoyedwindow_seconds: How many prompts in N seconds triggers the
user.spameaster egg - silentwindowseconds: Suppress
task.completesounds and notifications for tasks shorter than N seconds. (e.g.10to only hear sounds for tasks that take longer than 10 seconds) - sessionstartcooldown_seconds (number, default:
30): Deduplicates greeting sounds when multiple workspaces start at the same time (e.g. opening OpenCode or Cursor with many folders). Only the first session start plays the greeting; subsequent ones within this window stay silent. Set to0to disable deduplication and always play a greeting. - suppressidlepromptrepeats (boolean, default:
true): Claude Code re-fires itsidlepromptnotification every ~60s while the terminal is unfocused. peon-ping routesidleprompttotask.completeso you still get a sound when input is needed β but without dedupe the same sound replays on every poke. Whentrue, anidlepromptis suppressed if atask.completefor the same session already fired insideidlepromptsuppresswindowseconds. Set tofalseto restore the periodic nudge. - idlepromptsuppresswindowseconds (number, default:
3600): Window used bysuppressidlepromptrepeats. After atask.completefires for a session, subsequentidlepromptnotifications for that session stay silent for this many seconds. Set to0to disable the window (effectively the same assuppressidleprompt_repeats: false). - suppresssubagentcomplete (boolean, default:
false): Suppress sounds and notifications from sub-agent activity. When Claude Code's Task tool dispatches parallel sub-agents, each one fires its own events: a completion sound on finish,task.erroron failed Bash commands,input.requiredon permission requests. Set this totrueto hear only the parent session's sounds. Events fired from inside a sub-agent are detected via theagent_idfield Claude Code adds to their hook payloads; separate-session sub-agents (older clients, other IDEs) are still detected by the SubagentStart timing heuristic. - defaultpack: The fallback pack used when no more specific rule applies (default:
"peon"). Replaces the oldactivepackkey β existing configs are migrated automatically onpeon update. - pathrules: Array of
{ "pattern": "...", "pack": "..." }objects. Assigns a pack to sessions based on the working directory using glob matching (*,?). First matching rule wins. Beatspackrotationanddefaultpack; overridden bysessionoverrideassignments.
"path_rules": [ { "pattern": "/work/client-a/", "pack": "glados" }, { "pattern": "/personal/", "pack": "peon" } ] - excludedirs: Array of glob or directory patterns. If the current working directory matches one of these entries, all sounds and notifications are silenced for that invocation (the hook logs
suppressed=True reason=excludeddir pattern=<match>). Bare directory paths also match descendants, so"~/conductor/workspaces"silences everything under that tree. Use this for noisy background agents (e.g.CodexBar/ClaudeProbe), throwaway scratch dirs, or sensitive workspaces where audio alerts are unwanted.
"exclude_dirs": [ "~/conductor/workspaces", "~/Library/Application Support/CodexBar*" ] - iderules: Array of
{ "ide": "...", "pack": "..." }objects. Assigns a pack by IDE/source afterpathrulesand before rotation/default fallback. First matching rule wins. Common ids:claude,codex,cursor,opencode,kilo,kiro,gemini,copilot,windsurf,kimi,antigravity,amp,deepagents,openclaw,rovodev.
"ide_rules": [ { "ide": "codex", "pack": "glados" }, { "ide": "claude", "pack": "peon" } ] - packrotation: Array of pack names (e.g.
["peon", "sckerrigan", "peasant"]). Used whenpackrotationmodeisrandomorround-robin. Leave empty[]to usedefaultpack(orpathrules/ide_rules) only. - packrotationmode:
"random"(default),"round-robin", or"sessionoverride". Withrandom/round-robin, each session picks one pack frompackrotation. Withsessionoverride, the/peon-ping-use <pack>command assigns a pack per session. Invalid or missing packs fall back through the hierarchy. ("agentskill"is accepted as a legacy alias for"sessionoverride".) - sessionttldays (number, default: 7): Expire stale per-session pack assignments older than N days. Keeps
.state.jsonfrom growing unbounded when usingsession_overridemode. - headphonesonly (boolean, default:
false): Only play sounds when headphones or external audio devices are detected. When enabled, sounds are suppressed if built-in speakers are the active output β useful for open offices. Check status withpeon status. Supported on macOS (viasystemprofiler) and Linux (via PipeWirewpctlor PulseAudiopactl). - terminaltabtitle (boolean, default:
true): Update the terminal tab title with the current session status (for exampleβ project: done). Set tofalseif you already manage tab titles with your own shell prompt or terminal automation and only want peon-ping's sounds/notifications. - tmux_passthrough (boolean, default:
false): Pass the tab title and iTerm2 tab-color escapes through tmux's DCS passthrough to the host terminal (requires tmux 3.3a+ withset -g allow-passthrough on). Off by default because a tmux client multiplexes many panes/windows onto a single host terminal tab with no per-pane addressing: when several agent sessions run at once, every hook (including from background panes) repaints that one shared tab on a last-writer-wins basis, so it no longer reflects any single session. Leave it off and let tmux's own window/status line carry per-session state; enable it only if you run one tmux window per terminal tab (so 1 tab = 1 session). Has no effect outside tmux, where the escapes are always emitted. - suppresssoundwhentabfocused (boolean, default:
false): Skip sound playback when the terminal tab that generated the hook event is the currently active/focused tab. Sounds still play for background tabs as an alert that something happened elsewhere. Desktop and mobile notifications are unaffected. Useful when you only want audio cues from tabs you're not watching. macOS only (usesosascriptto check frontmost app and iTerm2 tab focus). - meeting_detect Detects if the microphone is currently being used and temporarily suppresses the audio only until the microphone is no longer in use. Notification still appears.
- focus_detect (boolean, default:
false): Honor macOS Focus / Do Not Disturb. peon-ping plays sounds viaafplayand draws overlays in a custom window, and both bypass Notification Center, so the system Focus toggle has no effect on them by default. When enabled, peon-ping reads the Focus state directly and suppresses output whenever any Focus (Do Not Disturb, Work, Sleep, etc.) is active, then resumes automatically when you turn Focus off. Mobile push (if configured) is unaffected, since your phone honors its own Focus. macOS only, and it fails open (if the Focus state can't be read, sounds play as normal). - focusdetectmode (string, default:
"all"): Whatfocusdetectsuppresses while a Focus is active."all"mutes both the sound and the overlay/desktop notification."sound"mutes only the sound (notifications still appear)."notifications"mutes only the notification (sound still plays). Ignored whenfocusdetectisfalse. - notification_position (string, default:
"top-center"): Where overlay notifications appear on screen. Options:"top-left","top-center","top-right","bottom-left","bottom-center","bottom-right". - notificationdismissseconds (number, default:
4): Auto-dismiss overlay notifications after N seconds. Set to0for persistent notifications that require a click to dismiss. - notificationallscreens (boolean, default:
true): Show overlay notifications on all screens (true) or only the main screen (false). Themed overlays (glass,jarvis,sakura) previously only showed on one screen β existing configs with those themes are migrated tofalseautomatically. macOS only. CLAUDESESSIONNAMEenv var: Set before launchingclaudeto give a session a custom name. Shows in both desktop notification titles and terminal tab titles. Priority over all config-based naming. Example:CLAUDESESSI claudeorexport CLAUDESESSIthenclaude. Each terminal gets its own title automatically since peon-ping runs as a child of that Claude instance.- notificationtitleoverride (string, default:
""): Override the project name shown in notification titles. When empty, the project name is auto-detected from/peon-ping-rename>CLAUDESESSIONNAME>.peon-label>notificationtitlescript>projectnamemap> git repo name > folder name. - notificationtitlemarker (string, default:
"β"): Character(s) shown before the project name in notification titles and terminal tab titles. Desktop notification titles useProjectby default; terminal tab titles keepProject: status. Set to""to disable. Example:"π". - notificationtitleide (boolean, default:
false): Include the normalized IDE label in desktop notification titles asProject - IDE. When disabled, the title staysProjectand the message/body carries the status/details. - notificationtitlescript (string, default:
""): Shell command run at event time to compute the project name dynamically. Receives env vars:PEONSESSIONID,PEONCWD,PEONHOOKEVENT,PEONIDE,PEONSESSIONNAME. Use stdout (trimmed, max 50 chars); non-zero exit falls through to the next tier.PEONIDEis the normalized IDE/source id such ascodexorclaude. Example:"basename $PEONCWD". - projectnamemap (object, default:
{}): Map directory paths to custom project labels for notifications. Keys are path patterns, values are display names. Example:{ "/home/user/work/client-a": "Client A" }. - notificationtemplates (object, default:
{}): Custom message/body format strings for notification events. Keys are event types (stop,permission,error,idle,question), values are template strings with variable substitution. Available variables:{project},{ide},{ideid},{summary},{toolname},{status},{event}. Example:{ "stop": "{status}: {summary}", "permission": "{status}: {toolname}" }.
Pack Selection Hierarchy
peon-ping resolves which sound pack to use through a 6-layer hierarchy. The first layer that produces a valid, installed pack wins:
| Priority | Layer | Source | How to set | |----------|-------|--------|------------| | 1 (highest) | session_override | Per-session assignment | /peon-ping-use <pack> skill or MCP | | 2 | pathrules | Glob match on working directory | peon packs bind or pathrules in config | | 3 | iderules | IDE/source match | peon packs ide-bind or iderules in config | | 4 | packrotation | Random or round-robin from a list | packrotation array + packrotationmode in config | | 5 | defaultpack | Static fallback | peon packs use <name> or defaultpack in config | | 6 (lowest) | hardcoded | Built-in default | "peon" |
If a layer references a pack that is not installed, it falls through to the next layer. If exclude_dirs matches the current working directory, the entire invocation is silenced β no sound, no notification.
Per-Project Pack Assignment (path_rules)
Assign different sound packs to different projects based on directory path. Use the CLI or edit config.json directly.
CLI (recommended):
peon packs bind glados # Bind glados to the current directory
peon packs bind sc_kerrigan --pattern "/services/" # Bind to a glob pattern
peon packs bind duke_nukem --install # Bind and install from registry if needed
peon packs unbind # Remove binding for the current directory
peon packs unbind --pattern "/services/" # Remove a specific pattern binding
peon packs bindings # List all bindings
Manual config:
"path_rules": [
{ "pattern": "/work/client-a/", "pack": "glados" },
{ "pattern": "/personal/", "pack": "peon" },
{ "pattern": "/services/", "pack": "sc_kerrigan" }
]
Rules use glob matching (*, ?). First matching rule wins. Path rules override packrotation and defaultpack but are overridden by session_override assignments.
Per-IDE Pack Assignment (ide_rules)
Use this layer when a path is noisy or shared across tools and you want a pack to follow the IDE instead.
CLI (recommended):
peon packs ide-bind codex glados # Use glados for Codex sessions
peon packs ide-bind claude peon # Use peon for Claude Code
peon packs ide-unbind codex # Remove one IDE rule
peon packs ide-bindings # List IDE rules and recent detections
Manual config:
"ide_rules": [
{ "ide": "codex", "pack": "glados" },
{ "ide": "claude", "pack": "peon" }
]
iderules run after pathrules.
Common Use Cases
Sounds without popups
Want voice feedback but no visual distractions?
peon notifications off
This keeps all sound categories playing while suppressing desktop notification banners. Mobile notifications (if configured) continue working.
You can also use the alias:
peon popups off
Silent mode with notifications only
Want visual alerts but no audio?
peon pause # or set "enabled": false in config
With desktop_notifications: true, you'll get popups but no sounds.
Complete silence
Disable everything:
peon pause
peon notifications off
peon mobile off
Peon Trainer
Your peon is also your personal trainer. Built-in Pavel-style daily exercise mode β the same orc who tells you "work work" now tells you to drop and give him twenty.
Quick start
peon trainer on # enable trainer
peon trainer goal 200 # set daily goal (default: 300/300)
... code for a while, peon nags you every ~20 min ...
peon trainer log 25 pushups # log what you did
peon trainer log 30 squats
peon trainer status # check progress
How it works
Trainer reminders piggyback on your coding session. When you start a new session, the peon immediately encourages you to start strong with pushups before you write any code. Then every ~20 minutes of active coding, you'll hear the peon yelling at you to do more reps. No background daemon needed. Log your reps with peon trainer log, and progress resets automatically at midnight.
Commands
| Command | Description | |---------|-------------| | peon trainer on | Enable trainer mode | | peon trainer off | Disable trainer mode | | peon trainer status | Show today's progress | | peon trainer log <n> <exercise> | Log reps (e.g. log 25 pushups) | | peon trainer goal <n> | Set uniform daily goal for all exercises | | peon trainer goal <exercise> <n> | Set uniform daily goal for one exercise | | peon trainer goal <exercise> <day> <n> | Set goal for specific day (mon, tue, etc.) | | peon trainer goal <day> <n> | Set all exercises for a specific day |
Schedule vs uniform goals
Exercises can have either a uniform daily goal (same every day) or a per-day schedule (different goals on different days). These are mutually exclusive:
- Setting a uniform goal removes any schedule for that exercise
- Setting a day-specific goal removes any uniform goal for that exercise
mon, tue, wed, thu, fri, sat, sun
peon trainer goal pushups 300 # 300 pushups every day (uniform)
peon trainer goal pushups mon 400 # Override: 400 on Monday (creates schedule)
peon trainer goal squats sun 0 # Rest day for squats on Sunday
peon trainer goal fri 150 # Light day for all exercises on Friday
On rest days (goal=0), reminders are skipped and status shows [REST DAY]. You can still log reps on rest days if you want.
Claude Code skill
In Claude Code, you can log reps without leaving your conversation:
/peon-ping-log 25 pushups
/peon-ping-log 30 squats
Custom voice lines
Drop your own audio files into ~/.claude/hooks/peon-ping/trainer/sounds/:
trainer/sounds/session_start/ # session greeting ("Pushups first, code second! Zug zug!")
trainer/sounds/remind/ # reminder lines ("Something need doing? YES. PUSHUPS.")
trainer/sounds/log/ # acknowledgment ("Work work! Muscles getting bigger maybe!")
trainer/sounds/complete/ # celebration ("Zug zug! Human finish all reps!")
trainer/sounds/slacking/ # disappointment ("Peon very disappointed.")
Update trainer/manifest.json to register your sound files.
MCP server
peon-ping includes an MCP (Model Context Protocol) server so any MCP-compatible AI agent can play sounds directly via tool calls β no hooks required.
The key difference: the agent chooses the sound. Instead of automatically playing a fixed sound on every event, the agent calls playsound with exactly what it wants β dukenukem/SonOfABitch when a build fails, sc_kerrigan/IReadYou when reading files.
Setup
Add to your MCP client config (Claude Desktop, Cursor, etc.):
{
"mcpServers": {
"peon-ping": {
"command": "node",
"args": ["/path/to/peon-ping/mcp/peon-mcp.js"]
}
}
}
If installed via Homebrew: $(brew --prefix peon-ping)/libexec/mcp/peon-mcp.js. See mcp/README.md for full setup instructions.
What the agent can do
| Feature | Description | |---|---| | playsound | Play one or more sounds by key (e.g. dukenukem/SonOfABitch, peon/PeonReady1) | | peon-ping://catalog | Full pack catalog as an MCP Resource β client prefetches once, no repeated tool calls | | peon-ping://pack/{name} | Individual pack details and available sound keys |
Requires Node.js 18+. Contributed by @tag-assistant.
Multi-IDE Support
peon-ping works with any agentic IDE that supports hooks. Adapters translate IDE-specific events to the CESP standard.
| IDE | Status | Setup | |---|---|---| | Claude Code | Built-in | curl \| bash install handles everything | | Amp | Adapter | bash adapters/amp.sh / powershell adapters/amp.ps1 (setup) | | Gemini CLI | Adapter | Add hooks pointing to adapters/gemini.sh (or .ps1 on Windows) (setup) | | GitHub Copilot CLI | Built-in (auto-detect) | install.sh / install.ps1 auto-registers hooks at ~/.copilot/hooks/peon-ping.json if ~/.copilot exists. Per-repo manual wiring also available via adapters/copilot.sh / .ps1 (setup) | | OpenAI Codex | Built-in (auto-detect) | install.sh / install.ps1 auto-registers stable hooks in ~/.codex/config.toml if ~/.codex exists. The adapter is also available for manual wiring (setup) | | Cursor | Built-in | curl \| bash, peon-ping-setup, or Windows install.ps1 auto-detect and register hooks. On Windows, enable Settings β Features β Third-party skills so Cursor loads ~/.claude/settings.json for SessionStart/Stop sounds. | | OpenCode | Adapter | bash adapters/opencode.sh / powershell adapters/opencode.ps1 (setup) | | Kilo CLI | Adapter | bash adapters/kilo.sh / powershell adapters/kilo.ps1 (setup) | | Kiro | Adapter | Add hook entries pointing to adapters/kiro.sh (or .ps1) (setup) | | Windsurf | Adapter | Add hook entries pointing to adapters/windsurf.sh (or .ps1) (setup) | | Google Antigravity | Adapter | bash adapters/antigravity.sh / powershell adapters/antigravity.ps1. For headless / macOS LaunchAgent use, also see bash adapters/antigravity-py.sh --install (Python watchdog watcher with 25s idle threshold; requires pip3 install watchdog). The Python watcher supports legacy conversations/.pb state plus newer antigravity-cli / antigravity-ide conversations/.db and brain/*/transcript.jsonl layouts. | | Kimi Code | Adapter | bash adapters/kimi.sh --install / powershell adapters/kimi.ps1 -Install (setup) | | OpenClaw | Adapter | Call adapters/openclaw.sh <event> (or openclaw.ps1) from your OpenClaw skill | | Rovo Dev CLI | Adapter | Auto-registered by install.sh if ~/.rovodev exists, or add hooks to ~/.rovodev/config.yml manually (setup) | | DeepAgents | Adapter | bash adapters/deepagents.sh / powershell adapters/deepagents.ps1 (setup) | | oh-my-pi (omp) | Adapter | bash adapters/omp.sh (setup) | | Qwen Code | Adapter | Add hooks pointing to adapters/qwen.sh (or .ps1 on Windows) (setup) | | iFlow CLI | Adapter | Add hooks pointing to adapters/iflow.sh (or .ps1) (setup) | | Trae | Adapter | Filesystem watcher: bash adapters/trae.sh & / powershell adapters/trae.ps1 -Install (setup) | | Kiro IDE | Adapter | Agent hooks in .kiro/hooks/*.kiro.hook calling adapters/kiro-ide.sh (or .ps1) (setup) | | ECA | Adapter | Add a shell hook pointing to adapters/eca.sh (or .ps1) (setup) |
Windows: All adapters have native PowerShell (.ps1) versions. The Windows installer (install.ps1) copies them to~/.claude/hooks/peon-ping/adapters/. Filesystem watchers (Amp, Antigravity, Kimi, Trae) use .NETFileSystemWatcherinstead of fswatch/inotifywait β no extra dependencies needed.
OpenAI Codex setup
Codex support uses the stable Codex hooks API plus the peon-ping adapter. If ~/.codex already exists, the Unix and Windows installers add a peon-managed block to ~/.codex/config.toml automatically. If Codex is installed later, re-run the peon-ping installer.
The registered Codex events are SessionStart, UserPromptSubmit, PermissionRequest, PreCompact, SubagentStart, SubagentStop, and Stop. SessionStart is matched for startup, resume, and clear; Codex compaction is handled by PreCompact, so compact-triggered SessionStart is skipped. PreToolUse, PostToolUse, and PostCompact are intentionally not registered; PostToolUse is ignored by the Codex adapter because Codex does not provide a separate failure-only hook and successful tool hooks are too noisy for peon-ping.
Setup:
- Install the peon-ping runtime:
bash "$(brew --prefix peon-ping)"/libexec/install.sh --no-rc
Or with the standard installer:
curl -fsSL https://raw.githubusercontent.com/PeonPing/peon-ping/main/install.sh | bash -s -- --no-rc
- Open Codex and run
/hooksif prompted. Codex requires non-managed command hooks to be reviewed and trusted before they run.
- Restart Codex if it was already running during installation.
~/.codex/config.toml instead of creating hooks.json, matching the Codex hooks documentation guidance to avoid mixing both hook representations in the same config layer. Legacy notify wiring still works as a fallback, but new installs should use the stable hooks path.
Amp setup
A filesystem watcher adapter for Amp (by Sourcegraph). Amp doesn't expose event hooks like Claude Code, so this adapter watches Amp's thread files on disk and detects when the agent finishes a turn.
Setup:
- Ensure peon-ping is installed (
curl -fsSL https://peonping.com/install | bash)
- Install
fswatch(macOS) orinotify-tools(Linux):
brew install fswatch # macOS
sudo apt install inotify-tools # Linux
- Start the watcher:
bash ~/.claude/hooks/peon-ping/adapters/amp.sh # foreground
bash ~/.claude/hooks/peon-ping/adapters/amp.sh & # background
Event mapping:
- New thread file created β Greeting sound ("Ready to work?", "Yes?")
- Thread file stops updating + agent finished turn β Completion sound ("Work, work.", "Job's done!")
The adapter watches ~/.local/share/amp/threads/ for JSON file changes. When a thread file stops updating (1s idle timeout) and the last message is from the assistant with text content (not a pending tool call), it emits a Stop event β meaning the agent is done and waiting for your input.
Environment variables:
| Variable | Default | Description | |---|---|---| | AMPDATADIR | ~/.local/share/amp | Amp data directory | | AMPTHREADSDIR | $AMPDATADIR/threads | Threads directory to watch | | AMPIDLESECONDS | 1 | Seconds of no changes before emitting Stop | | AMPSTOPCOOLDOWN | 10 | Minimum seconds between Stop events per thread |
GitHub Copilot CLI setup
Native GitHub Copilot CLI integration with full CESP v1.0 conformance.
Recommended: user-level (global) wiring β no per-repo setup.
install.sh and install.ps1 automatically register Copilot CLI hooks at ~/.copilot/hooks/peon-ping.json whenever the ~/.copilot/ directory exists. Re-run the installer if you installed Copilot CLI after peon-ping. The wiring uses PascalCase event names, which tells Copilot CLI to deliver the VS Code-compatible (snake_case) payload that peon.sh / peon.ps1 reads natively β no per-repo adapter required.
Hooks registered globally:
| Event | Category | Triggered by | |---|---|---| | SessionStart | session.start | Launching copilot (greeting) | | SessionEnd | (silent today) | Quitting the CLI | | UserPromptSubmit | user.spam (after 3+ rapid prompts) | Each prompt you submit | | Stop (= agentStop) | task.complete | Agent finishes a turn (debounced 5s) | | Notification | input.required (elicitation) | Idle, elicitation dialogs, permission popups | | PermissionRequest | input.required | Tool permission asks | | PreToolUse | input.required (only on dangerous-pattern match) | Before each tool call | | PostToolUseFailure | task.error | Tool failure | | PreCompact | resource.limit | Context compaction starting |
postToolUse is intentionally not wired: peon has no PostToolUse handler and routing it through Stop floods the debounce window, swallowing real Stop events.
Prerequisite (Windows): Copilot CLI hooks require PowerShell 7+ (pwsh on PATH) and a permissive execution policy:
winget install Microsoft.PowerShell
powershell -NoProfile -Command "Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned -Force"
Alternative: per-repository wiring with the adapter.
If you want Copilot CLI hooks committed to a specific repository (e.g. for a team workflow), use .github/hooks/hooks.json with the adapters/copilot.sh (or .ps1 on Windows) translator:
{
"version": 1,
"hooks": {
"sessionStart": [
{
"type": "command",
"bash": "bash ~/.claude/hooks/peon-ping/adapters/copilot.sh sessionStart",
"powershell": "powershell -NoProfile -File %USERPROFILE%\\.claude\\hooks\\peon-ping\\adapters\\copilot.ps1 sessionStart"
}
],
"agentStop": [
{
"type": "command",
"bash": "bash ~/.claude/hooks/peon-ping/adapters/copilot.sh agentStop",
"powershell": "powershell -NoProfile -File %USERPROFILE%\\.claude\\hooks\\peon-ping\\adapters\\copilot.ps1 agentStop"
}
],
"postToolUseFailure": [
{
"type": "command",
"bash": "bash ~/.claude/hooks/peon-ping/adapters/copilot.sh postToolUseFailure",
"powershell": "powershell -NoProfile -File %USERPROFILE%\\.claude\\hooks\\peon-ping\\adapters\\copilot.ps1 postToolUseFailure"
}
],
"notification": [
{
"type": "command",
"bash": "bash ~/.claude/hooks/peon-ping/adapters/copilot.sh notification",
"powershell": "powershell -NoProfile -File %USERPROFILE%\\.claude\\hooks\\peon-ping\\adapters\\copilot.ps1 notification"
}
]
}
}
Add additional events from the table above as desired. The adapter translates Copilot CLI's camelCase payload (sessionId, toolName, stopReason, etc.) to the snakecase shape (sessionid, toolname, stopreason) that peon.sh / peon.ps1 reads.
Features:
- Sound playback via
afplay(macOS),pw-play/paplay/ffplay(Linux),MediaPlayer/SoundPlayer(Windows) β same priority chain as the shell hook - CESP event mapping β Copilot CLI hooks map to standard CESP categories (
session.start,task.complete,task.error,input.required,user.spam,resource.limit) - Desktop notifications β large overlay banners by default, or standard notifications
- Spam detection β detects 3+ rapid prompts within 10 seconds, triggers
user.spamvoice lines - Debouncing β
Stopevents suppressed within a 5s window to prevent spam from chained tool calls
OpenCode setup
A native TypeScript plugin for OpenCode with full CESP v1.0 conformance.
Quick install:
curl -fsSL https://raw.githubusercontent.com/PeonPing/peon-ping/main/adapters/opencode.sh | bash
The installer copies peon-ping.ts to ~/.config/opencode/plugins/ and creates a config at ~/.config/opencode/peon-ping/config.json. Packs are stored at the shared CESP path (~/.openpeon/packs/).
Features:
- Sound playback via
afplay(macOS),pw-play/paplay/ffplay(Linux) β same priority chain as the shell hook - CESP event mapping β
session.created/session.idle/session.error/permission.asked/ rapid prompt detection all map to standard CESP categories - Desktop notifications β large overlay banners by default (JXA Cocoa, visible on all screens), or standard notifications via
terminal-notifier/osascript. Fires only when the terminal is not focused. - Terminal focus detection β checks if your terminal app (Terminal, iTerm2, Warp, Alacritty, kitty, WezTerm, ghostty, Hyper) is frontmost via AppleScript before sending notifications
- Tab titles β updates the terminal tab to show task status (
β project: working.../β project: done/β project: error) - Pack switching β reads
defaultpackfrom config (withactivepackfallback for legacy configs), loads the pack'sopenpeon.jsonmanifest at runtime.path_rulescan override the pack per working directory. - No-repeat logic β avoids playing the same sound twice in a row per category
- Spam detection β detects 3+ rapid prompts within 10 seconds, triggers
user.spamvoice lines
πΌοΈ Screenshot: desktop notifications with custom peon icon
Tip: Installterminal-notifier(brew install terminal-notifier) for richer notifications with subtitle and grouping support.
π¨ Optional: custom peon icon for notifications
By default, terminal-notifier shows a generic Terminal icon. The included script replaces it with the peon icon using built-in macOS tools (sips + iconutil) β no extra dependencies.
bash <(curl -fsSL https://raw.githubusercontent.com/PeonPing/peon-ping/main/adapters/opencode/setup-icon.sh)
Or if installed locally (Homebrew / git clone):
bash ~/.claude/hooks/peon-ping/adapters/opencode/setup-icon.sh
The script auto-finds the peon icon (Homebrew libexec, OpenCode config, or Claude hooks dir), generates a proper .icns, backs up the original Terminal.icns, and replaces it. Re-run after brew upgrade terminal-notifier.
Future: When jamf/Notifier ships to Homebrew (#32), the plugin will migrate to it β Notifier has built-in --rebrand support, no icon hacks needed.
Kilo CLI setup
A native TypeScript plugin for Kilo CLI with full CESP v1.0 conformance. Kilo CLI is a fork of OpenCode and uses the same plugin system β this installer downloads the OpenCode plugin and patches it for Kilo.
Quick install:
curl -fsSL https://raw.githubusercontent.com/PeonPing/peon-ping/main/adapters/kilo.sh | bash
The installer copies peon-ping.ts to ~/.config/kilo/plugins/ and creates a config at ~/.config/kilo/peon-ping/config.json. Packs are stored at the shared CESP path (~/.openpeon/packs/).
Features: Same as the OpenCode adapter β sound playback, CESP event mapping, desktop notifications, terminal focus detection, tab titles, pack switching, no-repeat logic, and spam detection.
Gemini CLI setup
A shell adapter for Gemini CLI with full CESP v1.0 conformance.
Setup:
- Ensure peon-ping is installed (
curl -fsSL https://peonping.com/install | bash)
- Add the following hooks to your
~/.gemini/settings.json:
{
"hooks": {
"SessionStart": [
{
"matcher": "startup",
"hooks": [
{
"name": "peon-start",
"type": "command",
"command": "bash ~/.claude/hooks/peon-ping/adapters/gemini.sh SessionStart"
}
]
}
],
"AfterAgent": [
{
"matcher": "*",
"hooks": [
{
"name": "peon-after-agent",
"type": "command",
"command": "bash ~/.claude/hooks/peon-ping/adapters/gemini.sh AfterAgent"
}
]
}
],
"AfterTool": [
{
"matcher": "*",
"hooks": [
{
"name": "peon-after-tool",
"type": "command",
"command": "bash ~/.claude/hooks/peon-ping/adapters/gemini.sh AfterTool"
}
]
}
],
"Notification": [
{
"matcher": "*",
"hooks": [
{
"name": "peon-notification",
"type": "command",
"command": "bash ~/.claude/hooks/peon-ping/adapters/gemini.sh Notification"
}
]
}
]
}
}
Event mapping:
SessionStart(startup) β Greeting sound ("Ready to work?", "Yes?")AfterAgentβ Task completion sound ("Work, work.", "Job's done!")AfterToolβ Success = Task completion sound, Failure = Error sound ("I can't do that.")Notificationβ System notification
Windsurf setup
Add to ~/.codeium/windsurf/hooks.json (user-level) or .windsurf/hooks.json (workspace-level):
{
"hooks": {
"postcascaderesponse": [
{ "command": "bash ~/.claude/hooks/peon-ping/adapters/windsurf.sh postcascaderesponse", "show_output": false }
],
"preuserprompt": [
{ "command": "bash ~/.claude/hooks/peon-ping/adapters/windsurf.sh preuserprompt", "show_output": false }
],
"postwritecode": [
{ "command": "bash ~/.claude/hooks/peon-ping/adapters/windsurf.sh postwritecode", "show_output": false }
],
"postruncommand": [
{ "command": "bash ~/.claude/hooks/peon-ping/adapters/windsurf.sh postruncommand", "show_output": false }
]
}
}
Kiro setup
Create ~/.kiro/agents/peon-ping.json:
{
"name": "peon-ping",
"hooks": {
"agentSpawn": [
{ "command": "bash ~/.claude/hooks/peon-ping/adapters/kiro.sh" }
],
"userPromptSubmit": [
{ "command": "bash ~/.claude/hooks/peon-ping/adapters/kiro.sh" }
],
"stop": [
{ "command": "bash ~/.claude/hooks/peon-ping/adapters/kiro.sh" }
]
}
}
preToolUse/postToolUse are intentionally excluded β they fire on every tool call and would be extremely noisy.
Rovo Dev CLI setup
A shell adapter for Rovo Dev CLI (Atlassian) with full CESP v1.0 conformance.
Auto-setup:
If ~/.rovodev/config.yml exists when you run install.sh or peon-ping-setup, event hooks are registered automatically.
Manual setup:
- Ensure peon-ping is installed (
curl -fsSL https://peonping.com/install | bash)
- Add to
~/.rovodev/config.yml:
README truncated. View on GitHub