Skip to content

PeonPing/peon-ping

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

618 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

peon-ping

English | 한국어 | 中文

macOS WSL2 Linux Windows MSYS2 SSH License

Claude Code Amp Gemini CLI GitHub Copilot Codex Cursor OpenCode Kilo CLI Kiro Kimi Code Windsurf Antigravity OpenClaw Rovo Dev CLI DeepAgents

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, and any MCP client.

See it in actionpeonping.com

demo-avatar.mp4



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

⚠️ In WSL2, ffmpeg must be installed to use sound packs that use formats other than WAV. In Debian distros, install with

sudo apt update; sudo apt install -y ffmpeg

Option 3: Installer for Windows

Invoke-WebRequest -Uri "https://raw.githubusercontent.com/PeonPing/peon-ping/main/install.ps1" -UseBasicParsing | Invoke-Expression

Installs 5 curated packs by default (Warcraft, StarCraft, Portal). Re-run to update while preserving config/state. Or pick your packs interactively at peonping.com and get a custom install command.

Useful installer flags:

  • --all — install all available packs
  • --packs=peon,sc_kerrigan,... — install specific packs only
  • --local — install packs and config into ./.claude/ for the current project (hooks are always registered globally in ~/.claude/settings.json)
  • --global — explicit global install (same as default)
  • --init-local-config — create ./.claude/hooks/peon-ping/config.json only

--local does not modify your shell rc files (no global peon alias/completion injection). Hooks are always written to the global ~/.claude/settings.json with absolute paths so they work from any project directory.

Examples:

curl -fsSL https://raw.githubusercontent.com/PeonPing/peon-ping/main/install.sh | bash -s -- --all
curl -fsSL https://raw.githubusercontent.com/PeonPing/peon-ping/main/install.sh | bash -s -- --packs=peon,sc_kerrigan
curl -fsSL https://raw.githubusercontent.com/PeonPing/peon-ping/main/install.sh | bash -s -- --local

If a global install exists and you install local (or vice versa), the installer prompts you to remove the existing one to avoid conflicts.

Option 4: Clone and inspect first

git clone https://github.com/PeonPing/peon-ping.git
cd peon-ping
./install.sh

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;

    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;
  };

  # Cursor hooks
  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 name and src fields, where src can be any Nix fetcher result (e.g., pkgs.fetchFromGitHub)

For packs listed on openpeon.com, find the GitHub repository link and use 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
  };
}

IDE hooks: peon-ping Home Manager module will not setup your IDE hooks to avoid conflicting updates. You must define these hooks yourself (see example above) depending on how you usually manage your IDE configuration.

  • peon-ping provide adapters scripts for various IDE such as cursor.sh - see adapters/
  • You need to call them as your hook such command like
    ${inputs.peon-ping.packages.${pkgs.system}.default}/share/peon-ping/adapters/$YOUR_IDE.sh EVENT_NAME
    See Cursor example above

What you'll hear

Event CESP Category Examples
Session starts session.start "Ready to work?", "Yes?", "What you want?"
Task finishes task.complete "Work, work.", "I can do that.", "Okie dokie."
Permission needed input.required "Something need doing?", "Hmm?", "What you want?"
Tool or command error task.error "I can't do that.", "Son of a bitch!"
Agent acknowledged task task.acknowledge "I read you.", "On it." (disabled by default)
Rate or token limit hit resource.limit "Zug zug." (pack dependent)
Rapid prompts (3+ in 10s) user.spam "Me busy, leave me alone!"

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

Other CLI commands:

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
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 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
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 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 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

peon-ping 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

You can also just ask Claude to change settings for you — e.g. "enable round-robin pack rotation", "set volume to 0.3", or "add glados to my pack rotation". No need to edit config files manually.

Config location depends on install mode:

  • Global install: $CLAUDE_CONFIG_DIR/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 desktop_notifications: false and mobile_notify.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")

    • overlay: large, visible banners — JXA Cocoa overlay on macOS, Windows Forms popup on WSL/MSYS2. Clicking the overlay focuses your terminal (supports Ghostty, Warp, iTerm2, Zed, Terminal.app). On iTerm2, clicking focuses the correct tab/pane/window — not just the app.
    • standard: system notifications — 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)
  • overlay_theme: "jarvis", "glass", "sakura", or omit for the default overlay — macOS only (default: none)

    • jarvis: circular HUD with rotating arcs, graduation ticks, and progress ring
    • glass: glassmorphism panel with accent color bar, progress line, and timestamp
    • sakura: zen garden with bonsai tree and animated cherry blossom petals
  • categories: Toggle individual CESP sound categories on/off (e.g. "session.start": false to disable greeting sounds)

  • annoyed_threshold / annoyed_window_seconds: How many prompts in N seconds triggers the user.spam easter egg

  • silent_window_seconds: Suppress task.complete sounds and notifications for tasks shorter than N seconds. (e.g. 10 to only hear sounds for tasks that take longer than 10 seconds)

  • session_start_cooldown_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 to 0 to disable deduplication and always play a greeting.

  • suppress_subagent_complete (boolean, default: false): Suppress task.complete sounds and notifications when a sub-agent session finishes. When Claude Code's Task tool dispatches parallel sub-agents, each one fires a completion sound — set this to true to hear only the parent session's completion sound.

  • default_pack: The fallback pack used when no more specific rule applies (default: "peon"). Replaces the old active_pack key — existing configs are migrated automatically on peon update.

  • path_rules: Array of { "pattern": "...", "pack": "..." } objects. Assigns a pack to sessions based on the working directory using glob matching (*, ?). First matching rule wins. Beats pack_rotation and default_pack; overridden by session_override assignments.

    "path_rules": [
      { "pattern": "*/work/client-a/*", "pack": "glados" },
      { "pattern": "*/personal/*",      "pack": "peon" }
    ]
  • pack_rotation: Array of pack names (e.g. ["peon", "sc_kerrigan", "peasant"]). Used when pack_rotation_mode is random or round-robin. Leave empty [] to use default_pack (or path_rules) only.

  • pack_rotation_mode: "random" (default), "round-robin", or "session_override". With random/round-robin, each session picks one pack from pack_rotation. With session_override, 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 "session_override".)

  • session_ttl_days (number, default: 7): Expire stale per-session pack assignments older than N days. Keeps .state.json from growing unbounded when using session_override mode.

  • headphones_only (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 with peon status. Supported on macOS (via system_profiler) and Linux (via PipeWire wpctl or PulseAudio pactl).

  • suppress_sound_when_tab_focused (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 (uses osascript to 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.

  • 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".

  • notification_dismiss_seconds (number, default: 4): Auto-dismiss overlay notifications after N seconds. Set to 0 for persistent notifications that require a click to dismiss.

  • CLAUDE_SESSION_NAME env var: Set before launching claude to give a session a custom name. Shows in both desktop notification titles and terminal tab titles. Priority over all config-based naming. Example: CLAUDE_SESSION_NAME="Auth Refactor" claude or export CLAUDE_SESSION_NAME="Feature: Auth" then claude. Each terminal gets its own title automatically since peon-ping runs as a child of that Claude instance.

  • notification_title_override (string, default: ""): Override the project name shown in notification titles. When empty, the project name is auto-detected from /peon-ping-rename > CLAUDE_SESSION_NAME > .peon-label > notification_title_script > project_name_map > git repo name > folder name.

  • notification_title_script (string, default: ""): Shell command run at event time to compute the project name dynamically. Receives env vars: PEON_SESSION_ID, PEON_CWD, PEON_HOOK_EVENT, PEON_SESSION_NAME. Use stdout (trimmed, max 50 chars); non-zero exit falls through to the next tier. Example: "basename $PEON_CWD".

  • project_name_map (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" }.

  • notification_templates (object, default: {}): Custom message format strings for notification events. Keys are event types (stop, permission, error, idle, question), values are template strings with variable substitution. Available variables: {project}, {summary}, {tool_name}, {status}, {event}. Example: { "stop": "{project}: {summary}", "permission": "{project}: {tool_name} needs approval" }.

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 goal for all exercises
peon trainer goal <exercise> <n> Set goal for one exercise

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 play_sound with exactly what it wants — duke_nukem/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
play_sound Play one or more sounds by key (e.g. duke_nukem/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 Adapter Add hooks to .github/hooks/hooks.json pointing to adapters/copilot.sh (or .ps1) (setup)
OpenAI Codex Adapter Add notify in ~/.codex/config.toml pointing to adapters/codex.sh (or .ps1)
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
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)

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) use .NET FileSystemWatcher instead of fswatch/inotifywait — no extra dependencies needed.

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:

  1. Ensure peon-ping is installed (curl -fsSL https://peonping.com/install | bash)

  2. Install fswatch (macOS) or inotify-tools (Linux):

    brew install fswatch        # macOS
    sudo apt install inotify-tools  # Linux
  3. 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!")

How it works:

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
AMP_DATA_DIR ~/.local/share/amp Amp data directory
AMP_THREADS_DIR $AMP_DATA_DIR/threads Threads directory to watch
AMP_IDLE_SECONDS 1 Seconds of no changes before emitting Stop
AMP_STOP_COOLDOWN 10 Minimum seconds between Stop events per thread

GitHub Copilot setup

A shell adapter for GitHub Copilot with full CESP v1.0 conformance.

Setup:

  1. Ensure peon-ping is installed (curl -fsSL https://peonping.com/install | bash)

  2. Create .github/hooks/hooks.json in your repository (on the default branch):

    {
      "version": 1,
      "hooks": {
        "sessionStart": [
          {
            "type": "command",
            "bash": "bash ~/.claude/hooks/peon-ping/adapters/copilot.sh sessionStart"
          }
        ],
        "userPromptSubmitted": [
          {
            "type": "command",
            "bash": "bash ~/.claude/hooks/peon-ping/adapters/copilot.sh userPromptSubmitted"
          }
        ],
        "postToolUse": [
          {
            "type": "command",
            "bash": "bash ~/.claude/hooks/peon-ping/adapters/copilot.sh postToolUse"
          }
        ],
        "errorOccurred": [
          {
            "type": "command",
            "bash": "bash ~/.claude/hooks/peon-ping/adapters/copilot.sh errorOccurred"
          }
        ]
      }
    }
  3. Commit and merge to your default branch. Hooks will activate on your next Copilot agent session.

Event mapping:

  • sessionStart → Greeting sound ("Ready to work?", "Yes?")
  • userPromptSubmitted → First prompt = greeting, subsequent = spam detection
  • postToolUse → Completion sound ("Work, work.", "Job's done!")
  • errorOccurred → Error sound ("I can't do that.")
  • preToolUse → Skipped (too noisy)
  • sessionEnd → No sound (session.end not yet implemented)

Features:

  • Sound playback via afplay (macOS), pw-play/paplay/ffplay (Linux) — same priority chain as the shell hook
  • CESP event mapping — GitHub Copilot hooks map to standard CESP categories (session.start, task.complete, task.error, user.spam)
  • Desktop notifications — large overlay banners by default, or standard notifications
  • Spam detection — detects 3+ rapid prompts within 10 seconds, triggers user.spam voice lines
  • Session tracking — separate session markers per Copilot sessionId

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 mappingsession.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 default_pack from config (with active_pack fallback for legacy configs), loads the pack's openpeon.json manifest at runtime. path_rules can 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.spam voice lines
🖼️ Screenshot: desktop notifications with custom peon icon

peon-ping OpenCode notifications

Tip: Install terminal-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:

  1. Ensure peon-ping is installed (curl -fsSL https://peonping.com/install | bash)

  2. 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": {
    "post_cascade_response": [
      { "command": "bash ~/.claude/hooks/peon-ping/adapters/windsurf.sh post_cascade_response", "show_output": false }
    ],
    "pre_user_prompt": [
      { "command": "bash ~/.claude/hooks/peon-ping/adapters/windsurf.sh pre_user_prompt", "show_output": false }
    ],
    "post_write_code": [
      { "command": "bash ~/.claude/hooks/peon-ping/adapters/windsurf.sh post_write_code", "show_output": false }
    ],
    "post_run_command": [
      { "command": "bash ~/.claude/hooks/peon-ping/adapters/windsurf.sh post_run_command", "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:

  1. Ensure peon-ping is installed (curl -fsSL https://peonping.com/install | bash)

  2. Add to ~/.rovodev/config.yml:

    eventHooks:
      events:
        - name: on_complete
          commands:
            - command: bash ~/.claude/hooks/peon-ping/adapters/rovodev.sh on_complete
        - name: on_error
          commands:
            - command: bash ~/.claude/hooks/peon-ping/adapters/rovodev.sh on_error
        - name: on_tool_permission
          commands:
            - command: bash ~/.claude/hooks/peon-ping/adapters/rovodev.sh on_tool_permission
  3. Restart Rovo Dev CLI for the hooks to take effect.

Event mapping:

  • on_complete → Completion sound ("Work, work.", "Job's done!")
  • on_error → Error sound ("I can't do that.", "Son of a bitch!")
  • on_tool_permission → Permission prompt sound ("Something need doing?", "Hmm?")

Features:

  • Sound playback via afplay (macOS), pw-play/paplay/ffplay (Linux) — same priority chain as the shell hook
  • CESP event mapping — Rovo Dev events map to standard CESP categories (task.complete, task.error, input.required)
  • Desktop notifications — large overlay banners by default, or standard notifications
  • Debounce — suppresses duplicate sounds from rapid completions

Kimi Code setup

A filesystem watcher adapter for Kimi Code CLI (MoonshotAI). Kimi Code writes Wire Mode events to ~/.kimi/sessions/ — this adapter watches those files as a background daemon and translates events to CESP format.

# Install (starts background daemon)
bash ~/.claude/hooks/peon-ping/adapters/kimi.sh --install

# Check status / stop
bash ~/.claude/hooks/peon-ping/adapters/kimi.sh --status
bash ~/.claude/hooks/peon-ping/adapters/kimi.sh --uninstall

Requires fswatch (brew install fswatch) on macOS or inotifywait (apt install inotify-tools) on Linux. The curl | bash installer auto-detects Kimi Code and starts the daemon.

Event mapping:

  • New session → Greeting sound ("Ready to work?", "Yes?")
  • Agent finishes turn → Completion sound ("Work, work.", "Job's done!")
  • Context compaction → Token limit sound
  • Sub-agent spawned → Sub-agent tracking

Remote development (SSH / Devcontainers / Codespaces)

Coding on a remote server or inside a container? peon-ping auto-detects SSH sessions, devcontainers, and Codespaces, then routes audio and notifications through a lightweight relay running on your local machine.

SSH setup

  1. On your local machine, start the relay:

    peon relay --daemon
  2. SSH with port forwarding:

    ssh -R 19998:localhost:19998 your-server
  3. Install peon-ping on the remote — it auto-detects the SSH session and sends audio requests back through the forwarded port to your local relay.

That's it. Sounds play on your laptop, not the remote server.

Optional SSH routing modes:

peon ssh-audio relay   # default, always use relay
peon ssh-audio auto    # try relay, fall back to local playback on SSH host
peon ssh-audio local   # always play on SSH host

Devcontainers / Codespaces

No port forwarding needed — peon-ping auto-detects REMOTE_CONTAINERS and CODESPACES environment variables and routes audio to host.docker.internal:19998. Just run peon relay --daemon on your host machine.

Relay commands

peon relay                # Start relay in foreground
peon relay --daemon       # Start in background
peon relay --stop         # Stop background relay
peon relay --status       # Check if relay is running
peon relay --port=12345   # Custom port (default: 19998)
peon relay --bind=0.0.0.0 # Listen on all interfaces (less secure)

Environment variables: PEON_RELAY_PORT, PEON_RELAY_HOST, PEON_RELAY_BIND.

If peon-ping detects an SSH or container session but can't reach the relay, it prints setup instructions on SessionStart.

Category-based API (for lightweight remote hooks)

The relay supports a category-based endpoint that handles sound selection server-side. This is useful for remote machines where peon-ping isn't installed — the remote hook only needs to send a category name, and the relay picks a random sound from the active pack.

Endpoints:

Endpoint Description
GET /health Health check (returns "OK")
GET /play?file=<path> Play a specific sound file (legacy)
GET /play?category=<cat> Play random sound from category (recommended)
POST /notify Send desktop notification

Example remote hook (scripts/remote-hook.sh):

#!/bin/bash
RELAY_URL="${PEON_RELAY_URL:-http://127.0.0.1:19998}"
EVENT=$(cat | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('hook_event_name',''))" 2>/dev/null)
case "$EVENT" in
  SessionStart)      CATEGORY="session.start" ;;
  Stop)              CATEGORY="task.complete" ;;
  PermissionRequest) CATEGORY="input.required" ;;
  *)                 exit 0 ;;
esac
curl -sf "${RELAY_URL}/play?category=${CATEGORY}" >/dev/null 2>&1 &

Copy this to your remote machine and register it in ~/.claude/settings.json:

{
  "hooks": {
    "SessionStart": [{"command": "bash /path/to/remote-hook.sh"}],
    "Stop": [{"command": "bash /path/to/remote-hook.sh"}],
    "PermissionRequest": [{"command": "bash /path/to/remote-hook.sh"}]
  }
}

The relay reads config.json on your local machine to get the active pack and volume, loads the pack's manifest, and picks a random sound while avoiding repeats.

Mobile notifications

Get push notifications on your phone when tasks finish or need attention — useful when you're away from your desk.

Quick start (ntfy.sh — free, no account needed)

  1. Install the ntfy app on your phone
  2. Subscribe to a unique topic in the app (e.g. my-peon-notifications)
  3. Run:
    peon mobile ntfy my-peon-notifications

Also supports Pushover and Telegram:

peon mobile pushover <user_key> <app_token>
peon mobile telegram <bot_token> <chat_id>

Mobile commands

peon mobile on            # Enable mobile notifications
peon mobile off           # Disable mobile notifications
peon mobile status        # Show current config
peon mobile test          # Send a test notification

Mobile notifications fire on every event regardless of window focus — they're independent from desktop notifications and sounds.

Sound packs

165 packs across Warcraft, StarCraft, Red Alert, Portal, Zelda, Dota 2, Helldivers 2, Elder Scrolls, and more. The default install includes 5 curated packs:

Pack Character Sounds
peon (default) Orc Peon (Warcraft III) "Ready to work?", "Work, work.", "Okie dokie."
peasant Human Peasant (Warcraft III) "Yes, milord?", "Job's done!", "Ready, sir."
sc_kerrigan Sarah Kerrigan (StarCraft) "I gotcha", "What now?", "Easily amused, huh?"
sc_battlecruiser Battlecruiser (StarCraft) "Battlecruiser operational", "Make it happen", "Engage"
glados GLaDOS (Portal) "Oh, it's you.", "You monster.", "Your entire team is dead."

Browse all packs with audio previews → openpeon.com/packs

Install all with --all, or switch packs anytime:

peon packs use glados             # switch to a specific pack
peon packs use --install glados   # install (or update) and switch in one step
peon packs next                   # cycle to the next pack
peon packs list                   # list all installed packs
peon packs list --registry        # browse all available packs
peon packs install glados,murloc  # install specific packs
peon packs install --all          # install every pack in the registry

Want to add your own pack? See the full guide at openpeon.com/create or CONTRIBUTING.md.

Uninstall

macOS/Linux:

bash "${CLAUDE_CONFIG_DIR:-$HOME/.claude}"/hooks/peon-ping/uninstall.sh        # global
bash .claude/hooks/peon-ping/uninstall.sh           # project-local

Windows (PowerShell):

# Standard uninstall (prompts before deleting sounds)
powershell -File "$env:USERPROFILE\.claude\hooks\peon-ping\uninstall.ps1"

# Keep sound packs (removes everything else)
powershell -File "$env:USERPROFILE\.claude\hooks\peon-ping\uninstall.ps1" -KeepSounds

Requirements

  • macOSafplay (built-in), JXA Cocoa overlay or AppleScript for notifications
  • Linux — one of: pw-play, paplay, ffplay, mpv, play (SoX), or aplay; notify-send for notifications
  • Windows — native PowerShell with MediaPlayer and WinForms (no WSL required), or WSL2
  • MSYS2 / Git Bashpython3, cygpath (built-in); audio via ffplay/mpv/play or PowerShell fallback
  • All platformspython3 (not required for native Windows)
  • SSH/remotecurl on the remote host
  • IDE — Claude Code with hooks support, Amp, or any supported IDE via adapters

How it works

peon.sh is a Claude Code hook registered for SessionStart, SessionEnd, SubagentStart, Stop, Notification, PermissionRequest, PostToolUseFailure, and PreCompact events. On each event:

  1. Event mapping — an embedded Python block maps the hook event to a CESP sound category (session.start, task.complete, input.required, etc.)
  2. Sound selection — picks a random voice line from the active pack's manifest, avoiding repeats
  3. Audio playback — plays the sound asynchronously via afplay (macOS), PowerShell MediaPlayer (WSL2/MSYS2 fallback), or pw-play/paplay/ffplay/mpv/aplay (Linux/MSYS2)
  4. Notifications — updates the Terminal tab title and sends a desktop notification if the terminal isn't focused
  5. Remote routing — in SSH sessions, devcontainers, and Codespaces, audio and notification requests are forwarded over HTTP to a relay server on your local machine

Sound packs are downloaded from the OpenPeon registry at install time. The official packs are hosted in PeonPing/og-packs. Sound files are property of their respective publishers (Blizzard, Valve, EA, etc.) and are distributed under fair use for personal notification purposes.

Links

Support the project