- Per-sound disable toggles. New
disabled_soundsconfig key (keyed by pack, category, array of filenames) lets you silence individual sounds within a pack without removing the file or disabling the whole category. If every sound in a category is disabled, the category stays silent for that pack. Three new CLI commands drive it:peon sounds list [pack]lists sounds with a<-- disabledmarker,peon sounds disable <category> <file> [--pack=<name>]mutes a single sound, andpeon sounds enable ...re-enables it. Applies acrosspeon.sh, the Windowspeon.ps1runtime, and the SSH/devcontainer relay. PR #502. Thanks @k-j-kim. install.sh --kimilands peon-ping under~/.kimi/hooks/peon-ping/with no Claude dependency. Previously the installer hard-coded~/.claude/hooks/peon-ping/even for Kimi-only users, silently creating a fake~/.claude/directory the user didn't ask for. The new flag mirrors--openclaw: it sets the install root to~/.kimi, skips the Claudesettings.jsonhook write entirely, and starts the Kimi watcher daemon at the end of install. Auto-detection mirrors OpenClaw too: runninginstall.shwithout args on a machine that has~/.kimi/but no~/.claude/selects--kimiautomatically. Mixed Claude+Kimi users are unaffected; the existing auto-spawn at the end of a regular install continues to start the watcher. The Kimi adapter's LaunchAgent plist now propagatesCLAUDE_PEON_DIRso a Kimi-direct install resolves to~/.kimi/hooks/peon-ping/peon.shafter reboot rather than the missing~/.claude/...default. PR #504. Thanks @GigaMax13.install.sh --kimishares voice packs with an existing Claude install instead of re-downloading them. When~/.claude/hooks/peon-ping/packs/already exists with packs, the installer symlinks~/.kimi/hooks/peon-ping/packsat it so a single download serves both IDEs andpeon packs install <name>from either side updates the shared set. State, config, and mute toggles stay isolated per install (the symlink coverspacks/only). Auto-share is skipped when the user gives explicit pack intent (--packs=,--all) or passes the new--no-shared-packsflag, when~/.kimi/.../packsalready exists as a real directory (preserves locally-added packs across reruns), or when Claude's packs/ is missing or empty. The existingpeon.shruntime resolvespacks/through symlinks transparently; no daemon or hook changes were needed. PR #504.- cmux notification integration on macOS. When peon-ping detects it's running inside cmux, standard notifications now route through
cmux notify, overlay click-to-focus targets the active cmux workspace/surface, and peon state mirrors into the cmux sidebar status pill for non-native Claude/Codex sessions. Workspace titles are resolved through shared helper scripts (scripts/cmux-focus.sh,scripts/cmux-status-presentation.sh,scripts/cmux-workspace-field.sh). Non-cmux notification behavior is unchanged. PR #500. Thanks @Andrew-Dunn.
- Linux:
pw-playsounds now route through PipeWire's notification media role (--media-role=Notification) instead of the default music role, so peon-ping no longer ducks Spotify or routes to the wrong audio endpoint when notification audio is configured separately. Applied at allpw-playcall sites: direct Linux playback inpeon.sh, the SSH/devcontainer relay path inrelay.sh, and the installer test sound ininstall.sh. Regression coverage added intests/peon.batsandtests/relay.bats(the relay test pinsHOST_PLATFORM=linuxso it actually exercises the pw-play branch on macOS CI). PR #501. Thanks @F43nd1r. Closes #499. - Kimi adapter daemon now survives reboots on macOS.
bash adapters/kimi.sh --installpreviously usednohup+ a pidfile, so the watcher died on logout/reboot and Kimi sessions stopped triggering sounds until the user rediscovered the broken state and re-ran--install. macOS installs now register a LaunchAgent at~/Library/LaunchAgents/com.peonping.kimi-adapter.plist(RunAtLoad=true,KeepAlive=true) so the watcher auto-starts on login and auto-restarts on crash.$PATHis captured into the plist so brew-installedfswatch/python3are found by launchd. Existing pre-LaunchAgent installs are migrated automatically: any live nohup-spawned daemon is killed and the stale pidfile cleared before the LaunchAgent is registered.--uninstalland--statusunderstand both code paths. SetKIMI_NO_LAUNCHD=1to opt back intonohup+pidfile (used by the test suite). Linux is unchanged: stillnohup+pidfile. PR #504.
terminal_tab_titleconfig flag (defaulttrue) to opt out of tab title overrides. Useful when you already manage tab titles via your shell prompt or terminal automation and only want peon-ping's sounds and notifications. Set tofalseto leave the existing tab title alone. PR #497, closes #406. Thanks @pandego.notification_title_ideconfig flag (defaultfalse) to surface the IDE label in desktop notification titles. When enabled, titles render asProject - IDE(for examplemyrepo - OpenAI Codex) so parallel sessions across Claude Code, Codex, Cursor, etc. are distinguishable at a glance. Notification message bodies were also restructured: status plus details on the body line so the title carries the project (and optionally IDE) cleanly. New{ide}and{ide_id}template variables are exposed tonotification_templates, andnotification_title_scriptnow receives aPEON_IDEenv var. Terminal tab titles continue to render asproject: status. PR #495. Thanks @kibermaks.suppress_idle_prompt_repeats(defaulttrue) andidle_prompt_suppress_window_seconds(default3600). Claude Code re-fires itsNotification+idle_promptevent roughly every 60s while the terminal is unfocused, which without dedupe replays the sametask.completesound on every poke. peon.sh now recordslast_task_complete[session_id]in.state.jsonand suppresses subsequentidle_promptevents for the same session inside the configured window. Set the flag tofalse(or the window to0) to restore the periodic nudge. PR #493, closes #486. Thanks @yusufkinatas.
exclude_dirsnow fully silences sounds and notifications when the cwd matches, instead of only skipping thepath_ruleslayer. The original v2.22.0 semantics still letide_rules, rotation, ordefault_packfall through to play sound, which surprised users who reasonably expected an "exclude" key to mean silence. Excluded invocations now logsuppressed=True reason=excluded_dir pattern=<match>and emit no audio or desktop notification. Use this for noisy background agents like CodexBar's ClaudeProbe, throwaway scratch directories, or sensitive workspaces. PR #494. Thanks @kibermaks.
peon.shsilently failed on Windows msys2/git-bash because the consolidated Python block exceededCreateProcess's ~32 KB argv limit. The block had grown to ~47 KB after the state-helper injection, sopython3 -c "<block>"returnedE2BIGand the hook exited 0 with no logs, breaking every event (Stop,Notification,PermissionRequest, etc.) on git-bash. Hook now writes the block to a tempfile and invokespython3 <path>instead, with atrapfor defensive cleanup. Stderr on this path is no longer suppressed so any future exec failure surfaces inpeon debug status. Linux/macOS were unaffected (argv limits are in the megabytes); native Windows (peon.ps1) does not use this path. PR #492, closes #488. Thanks @lmagine.- OpenCode adapter config now syncs categories and spam settings from the canonical peon config, and OpenCode-sourced events honor those overrides at runtime. Toggling a category like
user.spamor tuningannoyed_threshold/annoyed_window_secondspreviously had no effect on OpenCode sessions because the adapter copy at~/.config/opencode/peon-ping/config.jsonwas not refreshed and the runtime did not look at it for category gating. PR #498, closes #458. Thanks @pandego.
- TTS now actually speaks. The TTS pipeline landed earlier in the v2.20.x series was calling two backend scripts (
scripts/tts-native.shandscripts/tts-native.ps1) that had never been written to disk, so flippingtts.enabled: trueproduced no voice, no error, no log line. This release ships those backends. macOSsay, Linux piper-with-model or espeak-ng (in that priority order), MSYS2/MINGW bridges to the Windows backend, native Windows SAPI5. After upgrading,peon tts onactually speaks. Defense-in-depth on text quoting (speech text comes in on stdin, never as argv) and numeric injection (rate / volume go throughawk -v, not into awk source). PR #487, thanks @muunkky. - Windows
peonCLI shim now preferspwshoverpowershell.exe. On dev environments wherePSModulePathhas PowerShell 7 module dirs ahead of the PS 5.1 inbox paths (CloudSDK, Az, similar tooling having touched the env), Windows PowerShell 5.1 tries to load PS 7's incompatibleMicrosoft.PowerShell.SecurityandGet-ExecutionPolicyblows up, breaking everypeon …invocation. The generatedpeon.cmdand bash shim now probe forpwshfirst and fall back topowershell.exeonly when pwsh is absent. PR #487. - Pester
removes hooks from settings.jsonregression. Test regex anchored on plain-dot literals (peon\.ps1.*peon\.sh...) had been failing on main since #485 narrowed the inner-hook filter from a barepeonstring to escaped regex literals (peon\\.ps1,peon\\.sh). Loosened the assertion to allow the optional escaped backslash so both source forms match.
- BATS test harness now resolves Python via
command -v python3thencommand -v pythoninstead of hardcoding/usr/bin/python3(four call sites intests/setup.bash). Pester test setup canonicalizes$env:TEMPthroughPush-Locationto expand 8.3 short names on GitHub Windows runners (C:\Users\RUNNER~1\…), and switches oneSet-LocationtoSet-Location -LiteralPathso paths with bracket characters are handled correctly. PR #487.
- Claude Code integration in the Nix Home Manager module. New
programs.peon-ping.claudeCodeIntegrationflag installs the Claude Code hook scripts under~/.claude/hooks/peon-ping/and merges the standard peon-ping hook entries into~/.claude/settings.json. PR #480, closes #368. Thanks @pandego.
- Linux focus detection.
terminal_is_focusednow checks both the X11 window class and the window title, sozellijrunning inside Alacritty is correctly detected as a terminal, while a window titledstartno longer false-matches thestterminal. PR #491, closes #472. Thanks @pandego. peon packs installfailed for packs whose manifest sits at the registry source repo root. An emptysource_pathis a valid "manifest at root" signal, butpack-download.shwas treating it as missing metadata and falling back to the og-packs registry. Aligned with the Windows installer's existing handling. PR #490, closes #343. Thanks @ventopreto.install.shandpeon updatedropped sibling custom hooks. The matcher-entry filter was too coarse and removed the entire matcher entry whenever any inner hook containedpeon.shornotify.sh, silently nuking unrelated hooks composed under the same matcher. Now filters at the inner-hook level. Symmetric fix ininstall.sh,install.ps1,uninstall.sh,uninstall.ps1. PR #485, closes #484. Thanks @tarekrached.save_sound_pid $!aborted hooks underset -uwhen audio ran synchronously. InPEON_TEST=1paths and any branch where the player call returned without backgrounding,$!was unset and tripped nounset. The fourplay_linux_soundcall sites now skipsave_sound_pidin test/sync mode, andsave_sound_piditself early-returns on an empty arg as defense in depth.
- Windows Pester expectations recalibrated to match the runtime changes from #475 (
Test-PathRuleMatch,pack_rotation.Count, IDE-rule status output). PR #479, closes #478. Thanks @kibermaks.
- Chinese and Japanese READMEs updated with the new Nix
claudeCodeIntegrationoption and the split between Claude Code (auto-installed) and other-IDE (opt-in) hook setup.
- IDE-based pack rules. New
ide_rulesconfig layer andpeon packs ide-bind/unbind/bindingsCLI for assigning packs by IDE/source, e.g.peon packs ide-bind codex glados. Adapters now emit asourcefield so routing is explicit. Useful when multiple agents share a workspace (Conductor, CodexBar, parallel agent teams). PR #475, closes #298. Thanks @kibermaks. - Path exclusions for path_rules. New
exclude_dirsconfig andpeon packs exclude add/remove/listCLI to skippath_rulesfor specific globs or directory trees. Pairs withide_rulesfor workspaces that share paths across IDEs. PR #475. - Pack selection hierarchy expanded to 6 layers: session_override, path_rules, ide_rules, pack_rotation, default_pack, hardcoded. First valid installed pack wins. If
exclude_dirsmatches the current cwd, thepath_ruleslayer is skipped for that invocation.
{summary}template variable empty for Claude, Codex, and Gemini turn-complete hooks.peon.shnow falls throughlast_assistant_message,last-assistant-message,prompt_response,transcript_summary,messagewhen resolving{summary}for notification and TTS templates. Windowsinstall.ps1parity. PR #477, closes #476. Thanks @pandego, @Jorjon.
- Day-of-week trainer schedule. New
scheduleconfig block undertrainerlets you set per-weekday goals (e.g. pushups Mon/Wed/Fri, squats Tue/Thu/Sat). Setting a goal of0marks a rest day, which skips reminders and shows[REST DAY]inpeon trainer status. New CLI commands:peon trainer goal <exercise> <day> <n>andpeon trainer goal <day> <n>. Backwards-compatible with existing uniformexercisesconfig. PR #474, closes #323. Thanks @garfieldnate.
- Japanese README updated with the new trainer schedule commands and rest-day behavior.
- macOS notifications aborted on non-iTerm2/kitty terminals.
localdeclarations outside function scope inscripts/notify.shcaused Terminal.app, Warp, Ghostty, Zed, etc. to fail withunbound variableunderset -u. Removedlocal, variables are now script-scoped. PR #471. - Overlay never displayed when
NSScreen.screens.countprobe returned empty. The|| echo 1fallback only triggered on non-zero exit, not empty stdout. Added numeric-and-greater-than-zero validation soseq 0 -1never runs zero iterations on restricted macOS environments or when osascript is mocked. PR #471. notify.shexit code 1 leaked from_run_overlaysubshell whensession_filewas empty. Replaced the[ -n "$session_file" ] && rm ...short-circuit with explicitif/thenso the subshell always exits 0. PR #471.peon notifications marker ""now correctly disables the marker. Previously both "no arg" and "empty arg" hit the "show current" branch because${3:-}collapsed them. Nowpeon notifications marker(no arg) shows current,peon notifications marker ""disables. Behavior change scoped to this edge case. PR #471.
- TTS architecture documents landed. ADR-001 (backend architecture), TTS integration design doc, and PRD-003 (product requirements), all referenced in #442 but not previously committed. PR #471.
- Pester tests updated to match the MediaPlayer MP3/WMA routing change from #442 (seven scenarios in
adapters-windows.Tests.ps1,peon-debug.Tests.ps1,peon-security.Tests.ps1). PR #471. tests/peon.batsmac overlay ide_pid extraction anchored ontop-centerand walking backward to the first numeric token. Robust against TTY vs non-TTY test environments.
- Rich native macOS notifications —
terminal-notifierand osascript fallback now include a subtitle (CESP category) and group notifications by session so they replace each other in Notification Center instead of piling up. UsesPEON_SESSION_IDas the group key. PR #466. peon packs rotation add --install— add one or more packs to rotation AND download them from the registry in one command. Works with the flag before or after the pack names. PR #468.
- Security hardening: migrated inline
'$SHELL_VAR'interpolation inpython3 -c "..."blocks throughoutpeon.shtoos.environ.get()pattern. Prevents quote-injection on config values with special characters; no behavior change for normal usage. PR #469. - Homebrew formula simplified: delegates pack downloads in
peon-ping-setupto the sharedpack-download.shengine rather than duplicating download/fallback logic in the Ruby formula. PR #15 (homebrew-tap).
peon setupwizard guide added to README — quickstart example, screenshots of the wizard flow, and a pointer back to per-settingpeonsubcommands for advanced users. PR #467.
peon setupinteractive wizard — guided first-time setup covering volume, category toggles, desktop notifications, overlay theme, position, and auto-dismiss timing. PR #465, closes #283.- Session-based notification stacking — notifications from the same Claude session now stack into a single overlay with a count badge (
(3) project — needs approval). Auto-dismisses when the user resumes interaction (UserPromptSubmit, Stop, PreToolUse, etc.). New config:notification_stacking(defaulttrue). PR #463, addresses #340. - Visible X close button on overlays —
glass,sakura, andjarvisthemes now show a discoverable×in the top-right. New config:notification_close_button(defaulttrue). PR #464. notification_title_markerconfig — customize or disable the●shown before project names in notification titles and terminal tabs. Set to""to disable, or"🔔"to customize. PR #457.- Expanded
peon status— structured sections (core, packs, categories, notifications, audio routing, behavior timings, trainer, TTS, debug, IDEs), resolved active pack display with path-rule reasoning, Linux player detection, relay status for SSH/devcontainer. PR #461.
- NSPanel overlays no longer steal focus — switched
glass,sakura,jarvisthemes toNSPanelwithNSWindowStyleMaskNonactivatingPanelso clicking a notification doesn't dismiss iTerm2 hotkey/overlay windows. PR #462. - Correct overlay labels for red/yellow notifications — color-based fallback no longer mislabels permission requests as "LIMIT REACHED". Red → "APPROVAL NEEDED", yellow → "STANDING BY". Disabled categories now suppress both sound AND the overlay. PR #460.
- Pack sync robustness —
pack-download.shnow skips already-installed packs via on-disk checksum verification, probes the manifest URL before creating local directories, shows per-pack status (✓/✅/⚠️ /❌), and includes a summary with counts and disk usage. Fixes a bug where filenames with spaces (e.g.incoming (1).mp3) re-downloaded every run due to incorrect checksum parsing. PR #453. - Linux
notify-sendrespectsnotification_dismiss_seconds— previously hardcoded to 5000ms, now uses the configured value. PR #455. - Nix home-manager zsh module — updated
programs.zsh.initExtratoprograms.zsh.initContentfor the current home-manager API. PR #456.
- Updated Warcraft III Orc peon mapping examples in README (EN/zh/ja) to correctly reflect which voice lines belong to which CESP category. Closes #333.
--langflag for pack filtering — filter sound packs by language during install and registry listing. Supports prefix matching (--lang enmatchesen,en-GB) and multi-language packs. Works withinstall.sh,peon packs install, andpeon packs list --registry. PR #450, closes #395.
- Sounds now play in delegate/autonomous mode — sessions using
delegateordangerouslySkipPermissionspermission mode (Conductor, Claude Desktop, etc.) are no longer silenced. Setsuppress_delegate_sessions: truein config to restore old behavior. Also adds sound toidle_promptnotifications (maps totask.complete). Fixes #452. - WSL audio playback — replaced
SoundPlayer+setsidwithMediaPlayer(PresentationCore) for WSL. Handles WAV/MP3 natively with volume control, no ffmpeg dependency. Fixes #446. - Windows MediaPlayer for MP3/WMA —
win-play.ps1now uses nativeMediaPlayerfor.mp3and.wmafiles (not just.wav). No more silent failure when CLI players aren't installed. Fixes #451. - macOS osascript process accumulation — overlay notification processes are now cleaned up proactively on each invocation (kills stale processes >30s old) and watchdog subshells are explicitly terminated after overlay exits. Prevents CPU-consuming buildup in long sessions. Fixes #449.
- Windows git-bash stdin hang — bounded stdin read to 2 seconds to prevent
catfrom blocking until the outer timeout fires. Fixes #445.
- _peon_log: command not found on peon preview — added a no-op stub for
_peon_logbeforeplay_soundis called. The function was only defined inside the main Python block but called unconditionally byplay_sound, causingpeon previewandpeon playto emit a shell error. Fixes #421.
- WezTerm click-to-focus — added WezTerm bundle ID (
com.github.wez.wezterm) to_mac_terminal_bundle_id()so click-to-focus works instandardnotification mode on macOS. Fixes #417.
- Hook stderr leak on /dev/tty — wrapped all tty write sites in brace groups so bash's own redirect-open failure is suppressed by
2>/dev/null. Previously, Claude Code (and any tool that inspects hook stderr) would see a spurious error on every session start when/dev/ttyis unavailable (WSL2, headless). Fixes #407. - Spurious update notice — added same-version guard on update notice display; stale
.update_availablefiles from previous installs are cleaned up silently instead of showing "2.17.0 → 2.17.0". - Themed overlay position — glass, jarvis, and sakura themed overlays now respect
notification_positionconfig (was always defaulting to top-right). Brought to parity with defaultmac-overlay.js. Fixed by #413.
- Structured debug logging — 9-phase decision tracing for hook execution (
peon debug on/off,peon logs). Traces event routing, config loading, state management, pack selection, sound pick, playback, notification, trainer, and exit timing in a structured JSONL-like format. peon debugCLI command — toggle debug logging on/off withpeon debug onandpeon debug off. Check current state withpeon debug.peon logsCLI command — view recent debug logs withpeon logs,peon logs --last N,peon logs --session, andpeon logs --clear. Daily log rotation with configurable retention.PEON_DEBUG=1env var override — enable debug logging for a single hook invocation without changing config.debuganddebug_retention_daysconfig keys — persistent debug toggle (default:false) and log retention period (default: 7 days).- Cross-platform debug parity — identical structured log format on Unix (
peon.sh) and Windows (peon.ps1). - Shared test fixtures — BATS and Pester tests share fixture data enforcing format parity between platforms.
peon statusverbose output — now includes debug logging state.- Nix/Home Manager: custom pack sources —
installPacksnow accepts both simple strings (for og-packs) and attribute sets withnameandsrcfields to install packs from any source. Thesrcfield accepts any Nix fetcher result (e.g.,pkgs.fetchFromGitHub), enabling community packs from the openpeon.com registry that aren't in og-packs while maintaining full reproducibility.
- State contention test — removed
|| truethat was silently masking assertion failures in concurrent state access tests. - Bash log helpers — real millisecond timestamps and proper newline escaping in structured log output.
- Missing exit/route logs — added
[route]and[exit]log entries to all early-exit paths for complete tracing.
- macOS persistent overlay (
dismiss=0) killed by watchdog — the shell-level safety watchdog innotify.shcomputed_max_wait = 0 + 5 = 5s, killing the JXA overlay process 5 seconds after spawn even when the user had configured persistent notifications viapeon notifications dismiss 0. Persistent mode now sets_max_wait=86400(24 h) so the overlay stays until clicked. (#344) - Linux
urgency=criticaloverrides dismiss time —notify-send --urgency=critical(used for red/error sounds) caused notification daemons likedunstandmakoto ignore--expire-time, pinning the notification until manually dismissed regardless ofnotification_dismiss_seconds. Changed to always useurgency=normal; error sounds are already visually distinct via title/color. (#378)
- Windows WAV volume control — replaced
SoundPlayer(which ignores volume) withMediaPlayer(WPF/PresentationCore) inwin-play.ps1. Volume is now respected for WAV playback on Windows. UsesMediaOpened/MediaFailedevent subscription with dispatcher pump for reliable playback duration tracking. (#381)
- RovoDev hook installer: multi-line YAML command values — installer now correctly handles
- command:entries whose values span multiple YAML continuation lines (e.g., multi-lineosascriptstrings). The new hook entry is inserted after the full continuation block, preventing corruption of existing config. (#384)
- Shell quoting safety in peon.sh — audited all 61
python3 -cinvocations and fixed 3 hazardous patterns (7 occurrences) where escaped double quotes inside bash double-quoted strings could cause silent failures. Dict access and method args now use single-quoted Python strings extracted to temp variables. - Windows atomic state I/O hardening —
Write-StateAtomicnow usesMove-Item -Forcefor truly atomic overwrites on PowerShell 7+, eliminating a sub-millisecond race window.Read-StateWithRetrycleans up orphaned.tmpfiles left by safety timer exits. - Windows ffplay install guidance — post-install tip now recommends
choco install ffmpeg(adds ffplay to PATH automatically), warns aboutwinget install ffmpegGyan build PATH issues, and provides manual fallback instructions. - Windows CLI bind/unbind quality — added
Get-ActivePackhelper for cross-platform parity withpeon.sh, restored runtimepath_rulesmatching engine, and added--statuspath_rules display. bind--installnow shows download progress instead of running silently.
- Overlay themes show wrong status labels — themed overlays (jarvis, glass, sakura) derived their banner label from notification color alone, causing mismatches: Stop events showed "INPUT REQUIRED", PermissionRequest showed "LIMIT REACHED", and idle prompts showed "LIMIT REACHED". Added a
NOTIFY_TYPEsemantic variable (complete/permission/limit/idle/question) that flows frompeon.sh→notify.sh→ overlay scripts asargv[10], with color-based fallback preserved for relay.sh callers. Closes #342.
peon packs rotation clear— new subcommand to zero out the pack rotation in a single command. Setspack_rotationto[]in config.json and syncs adapter configs. Closes #321.
/peon-ping-renamebleeds across tabs in same project — names set in one terminal tab were appearing in all other tabs opened to the same project directory. Root cause: hooks run detached from the controlling terminal, sottyreturned the same value across all tabs, collapsing the per-tab key. Switched to$PPID(Claude Code's process PID) as the stable tab identifier: different terminal tabs spawn separate Claude Code processes with different PIDs, while/clearwithin a tab reuses the same process. Composite keyppid::cwdreplaces the previoustty::cwdkey intty_namesstate. Closes #325.
- Configurable SSH audio routing (
peon ssh-audio [relay|auto|local]) — choose how audio is routed in SSH sessions.relay(default) preserves existing behavior;autotries relay then falls back to local playback on the SSH host;localalways plays on the SSH host. Closes #206.
- Nix/Home Manager: cursor adapter lookup —
adapters/cursor.shnow resolvespeon.shvia a priority chain (PEON_DIR→CLAUDE_PEON_DIR→$CLAUDE_CONFIG_DIR/hooks/peon-ping→$HOME/.openpeon) instead of hardcoding~/.claude. Fixes Cursor in Nix installs. - Nix/Home Manager: reproducible pack installation — sound packs now installed directly from the
og-packsrepo at a pinned version instead of a non-reproducible activation script. - Nix/Home Manager: hook setup documentation — README now shows how to wire up IDE hooks manually in the Nix context; the HM module no longer attempts to manage hook files to avoid config conflicts. Closes #302.
/peon-ping-renametitle lost in plan mode — when accepting a plan or entering plan mode, the session name would revert to the git repo/folder name. Early-exit paths (unknown events, unknownNotificationtypes,PostToolUseFailurefor non-Bash tools,SubagentStart, compactSessionStart) now emitPROJECT/STATUSso the shell still sets the tab title even when no sound plays.
- Rovo Dev CLI adapter (
adapters/rovodev.sh) — translates Rovo Dev event hooks (on_complete,on_error,on_tool_permission) into CESP categories for peon-ping sound playback. Argument-based (not stdin), matching Rovo Dev's shell command hook model. - Rovo Dev CLI auto-registration —
install.shdetects~/.rovodev/config.ymland automatically appendseventHooksconfiguration, sopeon-ping-setupjust works for Rovo Dev users. /peon-ping-renameskill — give the current Claude Code session a custom name shown in desktop notification titles and terminal tab title. Zero tokens consumed (intercepted byUserPromptSubmithook). Names stored in.state.jsonkeyed by session ID — multiple tabs in the same repo each get independent names./peon-ping-renamewith no argument resets to auto-detect.CLAUDE_SESSION_NAMEenv var — set before launchingclaudeto give a session a fixed name at the environment level. Shows in both notification titles and terminal tab titles.notification_title_scriptconfig key — shell command run at event time to compute the project name dynamically. ReceivesPEON_SESSION_ID,PEON_CWD,PEON_HOOK_EVENT,PEON_SESSION_NAMEenv vars; stdout used as project name (max 50 chars).- Updated priority chain:
/peon-ping-rename>CLAUDE_SESSION_NAME>.peon-label>notification_title_script>project_name_map>notification_title_override> git repo name > folder name.
.peon-labeltier now correctly guarded withif not project:— was previously overwriting higher-priority tiers.
- mac-overlay cleanup — orphaned
mac-overlay.jsosascript processes are now killed onSessionEnd, preventing stale overlay popups after Claude Code exits (#299, #301) - Adapter cooldown bug —
amp.shandantigravity.shno longer prematurely mark threads idle during the Stop cooldown window, fixing dropped Stop events for rapid task completions (#300)
- Windows PowerShell adapters — native
.ps1adapters for all 11 IDEs (codex, gemini, copilot, windsurf, kiro, openclaw, amp, antigravity, kimi, opencode, kilo). No Git Bash or WSL required. Filesystem watchers use .NETFileSystemWatcher. 198 Pester tests added. (#285)
- OpenCode subagent noise — filter subagent sessions from sound/notification events. Subagent sessions (spawned by Task tool with
parentID) no longer trigger sounds forsession.idle,session.error, andsession.statusevents. (#290, fixes #289)
- Kimi Code adapter — filesystem watcher for Kimi Code CLI (MoonshotAI). Watches
~/.kimi/sessions/for session events and translates them to CESP format. Uses the samefswatch/inotifywaitpattern as the Amp and Antigravity adapters. Includes BATS tests.
- Fix Ghostty terminal detection when running inside tmux:
_mac_terminal_bundle_id()now falls back to env vars (GHOSTTY_RESOURCES_DIR,ITERM_SESSION_ID,WARP_IS_LOCAL_SHELL_SESSION) whenTERM_PROGRAMis overwritten by tmux/screen (#269) - Fix case-sensitive Ghostty process name in
terminal_is_focused(): add lowercaseghosttymatch alongsideGhostty(#269)
- Amp adapter — filesystem watcher for Amp (Sourcegraph). Watches
~/.local/share/amp/threads/for thread JSON file changes. DetectsSessionStart(new thread) andStop(agent finished turn, waiting for input) by inspecting the last message in the thread JSON. Uses the samefswatch/inotifywait+ idle timer pattern as the Antigravity adapter, with an additionalthread_is_waiting()check to confirm the agent isn't mid-tool-execution. Includes 17 BATS tests.
- MSYS2 / Git Bash platform support —
install.sh,peon.sh, andscripts/notify.shnow detectMSYS_NT-*/MINGW*uname strings as"msys2"platform. Audio plays via native players (ffplay,mpv,play) with PowerShellwin-play.ps1fallback. Desktop notifications use Windows toast (standard) or Windows Forms overlay, withcygpath -wfor path conversion.
- Cursor on Windows: peon.ps1 now maps Cursor's camelCase event names (
sessionStart,stop, etc.) to PascalCase, fixing no-sounds-on-new-chat when using Third-party skills - Cursor on Windows:
install.ps1anduninstall.ps1now handle Cursor's flat-arrayhooks.jsonformat (matchinginstall.shfix from v2.7.x) - peon.ps1 pack rotation: accept
session_overridealias in addition toagentskill
- Click-to-focus for IDE embedded terminals (Cursor, VS Code, Windsurf, Zed) — when
TERM_PROGRAMdoesn't map to a standalone terminal, falls back to deriving the IDE's bundle ID from its PID vialsappinfo(macOS built-in) - PID-based
NSRunningApplicationactivation inmac-overlay.jsas belt-and-suspenders fallback when bundle ID lookup fails
path_rulesconfig array: glob-pattern-based CWD-to-pack assignment (layer 3 in override hierarchy)- Click-to-focus terminal on macOS notification click — overlay style detects terminal via
TERM_PROGRAM→ bundle ID mapping (Ghostty, Warp, iTerm2, Terminal.app); standard style usesterminal-notifierwith-activate - IDE PID detection (
_mac_ide_pid()) for Cursor/Windsurf/Zed/VS Code ancestor click-to-focus
active_pack→default_pack(backward-compat fallback +peon updatemigration)agentskillrotation mode →session_override(agentskillaccepted as alias)- Override hierarchy (high→low):
session_override> local project config >path_rules>pack_rotation>default_pack
suppress_subagent_completeconfig option (default:false) — when enabled, suppressestask.completesounds and notifications for sub-agent sessions spawned via Claude Code's Task tool, so only the parent session's completion sound fires
cwdfield inlast_activestate (.state.json) — records the working directory of each hook invocation, enabling peon-pet to display the project folder name in session dot tooltips
- Pack rotation:
session_packsentries in dict format (after cleanup upgrade) were not recognized by thein pack_rotationcheck, causing a new random pack to be picked on every non-SessionStart event — same session could play sounds from different characters each turn SubagentStartnow exits silently after saving state — previously could playtask.acknowledgesound on the parent session- Task-spawned subagent sessions now inherit the parent session's voice pack via
pending_subagent_packstate, ensuring a single conversation always uses one character
- Project-local config override: place a
config.jsonat.claude/hooks/peon-ping/config.jsonin any project to override the global config for that project only
hook-handle-use.sh: macOS BSD sed does not support\s/\S— replaced with POSIX[[:space:]]/[^[:space:]]classes (closes #212)- OpenCode plugin:
desktop_notifications: falsein config was ignored — AppleScript notifications now respect the setting (closes #207) - OpenCode plugin: Linux audio backend chain now matches
peon.shpriority order (pw-play→paplay→aplay) with correct per-backend volume scaling
peon volume [0.0-1.0]CLI command — get or set volume from the terminalpeon rotation [random|round-robin|agentskill]CLI command — get or set pack rotation mode from the terminal
- macOS overlay (
mac-overlay.js) is now correctly copied during install — previously only.sh/.ps1/.swiftscripts were copied, so the visual overlay banner never appeared - Resume sessions (
source: "resume") preserve the active voice pack instead of picking a new random one
- Default pack set reduced to 5 curated WC/SC/Portal packs:
peon,peasant,sc_kerrigan,sc_battlecruiser,glados
UserPromptSubmitremoved from default registered hooks — peon no longer fires on every user message. The/peon-ping-useskill hook remains registered underUserPromptSubmit. Re-add manually to~/.claude/settings.jsonif you want the annoyed easter egg ortask.acknowledge.task.acknowledgedefault changed tofalseinconfig.jsontemplate (wastrue, which caused a sound on every message even without the hook firing explicitly)
This also mitigates the Windows console raw mode issue (#205) where spawning powershell.exe on every UserPromptSubmit corrupted Claude Code's keyboard input.
peon-playandmac-overlay.jsnow resolve correctly on Homebrew/adapter installs where$PEON_DIRis remapped (same root cause as thepack-download.shissue fixed in v2.2.1)- Overlay notifications fall through to standard notifications when
mac-overlay.jsis not found rather than silently failing USE_SOUND_EFFECTS_DEVICEunbound variable crash inplay_soundwhen called from preview context
peon packs install,peon packs use --install, andpeon packs list --registrynow correctly locatepack-download.shon Homebrew and adapter installs where$PEON_DIRis remapped away from the script directory (#204)- Test isolation:
PEON_TEST=1now exported globally in test setup so allrun bash peon.shcalls correctly skip the Homebrew path probe
- MCP server (
mcp/) for agent-driven sound playback via Model Context Protocol - OpenClaw adapter documented in README and llms.txt
SubagentStartandPostToolUseFailurenow registered in installer hook listtask.errorandtask.acknowledgeadded to "What you'll hear" README table/peon-ping-useand/peon-ping-logskills documented in CLAUDE.md and llms.txt
- MCP server:
pw-playvolume now uses correct 0.0–1.0 float scale (was 0–65536) - MCP server: reads volume from
config.jsoninstead of requiringPEON_VOLUMEenv var openclaw.sh: error events now map toPostToolUseFailure(task.error) notStoppeon help: added missingmobile on/pushover/telegramandrelay --bindentries- Windows installer:
PostToolUseFailureandSubagentStartnow registered and handled
- Pack count updated to 75+ across all docs
- Hero copy updated to "any AI agent" framing with MCP server mention
- Pass WSL Windows Forms notification message via temp file to prevent PowerShell script injection (#187)
- macOS JXA Cocoa overlay notifications with configurable
overlay/standardstyles andpeon notificationsCLI (#185) - CESP §5.5 icon resolution chain for pack-aware notifications (sound → category → pack → icon.png → default) with path traversal protection (#189)
- Background relay health check on SessionStart to avoid blocking greeting sound for SSH/devcontainer users (#190)
- OpenCode adapter
task.completedebounce increased to 5s to prevent repeated notifications in plan mode (#188)
peon packs install <pack1,pack2>andpeon packs install --allfor post-install pack management (#179)peon packs list --registryto browse all available packs from the registry (#179)- Bash and fish shell completions for new packs commands (#179)
- Shared
scripts/pack-download.shengine extracted from installer (#179)
- Local installs (
--local) now use correctINSTALL_DIRfor skill hook paths instead of hardcoded global path (#180) - Cursor IDE hooks registration now handles flat-array
hooks.jsonformat
- Peon Trainer: Pavel-style daily exercise mode — 300 pushups and 300 squats per day, tracked through your coding sessions
- Trainer CLI:
peon trainer on/off/status/log/goal/helpsubcommands - Trainer reminders piggyback on IDE hook events every ~20 minutes with orc peon voice lines
- Session-start encouragement: peon immediately greets you with a workout prompt when you start a new coding session
- 24 ElevenLabs orc voice lines across 5 categories: session_start, remind, log, complete, slacking
- Pace-based slacking detection: past noon with less than 25% progress triggers slacking voice lines
- Daily auto-reset at midnight
- Configurable goals (
peon trainer goal 200) and per-exercise goals (peon trainer goal pushups 100) - Trainer section in README with quick start guide
- SHA256 checksum-based caching for sound downloads: re-runs skip files that are already downloaded and intact, corrupted files are auto-detected and re-downloaded (#164)
- URL-encode special characters (
?,!,#) in filenames when downloading from GitHub, fixing packs with filenames likeNew_construction?.mp3(#164) - Allow
?and!in sound filenames (is_safe_filename) (#164) - Remove destructive
rm -rfthat wiped all sounds before re-downloading on updates (#164)
- Eliminate test race conditions:
peon.shruns afplay synchronously in test mode instead of relying on sleep (#134) - Local uninstall now cleans hooks from global
settings.json(#134) - Background sound playback and notifications on WSL/Linux to avoid blocking the IDE (#132)
- Native Windows support: PowerShell installer (
install.ps1), hook script (peon.ps1), and uninstaller with two-tier audio fallback (WPF MediaPlayer + SoundPlayer) (#105) - Windsurf adapter: Full CESP adapter for Windsurf Cascade hooks with session tracking (#130)
- Kilo CLI adapter: Native TypeScript plugin for Kilo CLI (OpenCode fork) (#129)
- Install progress bar: Live-updating per-pack progress bar in TTY mode, dot-based fallback for non-TTY (#121)
- OpenCode adapter tests: 21 BATS tests covering install, uninstall, idempotency, XDG support, and icon replacement (#131)
- Fix code injection vulnerability in
peon packs use/remove— pack args now passed via env vars (#127) - Fix
pw-playsilent on non-English locales by settingLC_ALL=C(#124) - Fix Telegram API call to use POST body instead of URL params (#128)
- Replace bare
except:clauses withexcept Exception:across all embedded Python (#126) - Remove broken symlink before curl download in OpenCode adapter (#125)
- Remove Claude Code paths from OpenCode icon resolution (#123)
- Fix race condition in peon.bats (background afplay timing)
- Fix install.bats
--localtests to check correct settings.json path
peon packs listand other CLI commands now work correctly for Homebrew installs (#101)
- SSH remote audio support: Auto-detects SSH sessions and routes audio through a relay server running on your local machine (
peon relay) - Relay daemon mode:
peon relay --daemon,--stop,--statusfor persistent background relay - Devcontainer / Codespaces support: Auto-detects container environments and routes audio to
host.docker.internal - Mobile push notifications:
peon mobile ntfy|pushover|telegram— get phone notifications via ntfy.sh, Pushover, or Telegram - Enhanced
peon status: Shows active pack, installed pack count, and detected IDE (#91) - Relay test suite: 20 tests covering health, playback, path traversal protection, notifications, and daemon mode
- Automated Homebrew tap updates: Release workflow now auto-updates
PeonPing/homebrew-tap
- Prevent duplicate hooks when both global and local installs exist
- Correct Ghostty process name casing in focus detection (#92)
- Suppress replay sounds during session continue (#19)
- Harden installer reliability (#93)
- Subcommand CLI: All
--flagcommands replaced with subcommands.peon --pauseis nowpeon pause,peon --packsis nowpeon packs list, etc. (#90)
- Homebrew install:
brew install PeonPing/tap/peon-pingas primary install method - Multi-IDE messaging: Updated all docs and landing page to highlight Claude Code, Codex, Cursor, and OpenCode support
peon packs remove: Uninstall specific packs without removing everything (#89)peonping.com/installredirect: Clean install URL via Vercel redirect- Dynamic pack counts: peonping.com fetches live pack count from registry at runtime
- Session replay suppression: Sounds no longer fire 3x when continuing a session with
claude -c(#19)
- Handle read-only shell rc files during install (#86)
- Fix raw escape codes in OpenCode adapter output (#88)
- Fix OpenCode adapter registry lookup and add missing plugin file
- Registry-based pack discovery: install.sh fetches packs from the OpenPeon registry instead of bundling sounds in the repo
- CESP standard: Migrated to the Coding Event Sound Pack Specification with
openpeon.jsonmanifests - Multi-IDE adapters: Cursor (
adapters/cursor.sh), Codex (adapters/codex.sh), OpenCode (adapters/opencode.sh) --packsflag: Install specific packs by name (--packs=peon,glados,peasant)- Interactive pack picker: peonping.com lets you select packs and generates a custom install command
silent_window_seconds: Suppress sounds for tasks shorter than N seconds (#82)- Help on bare invocation: Running
peonwith no args on a TTY shows usage (#83) - Desktop notification toggle: Independent
desktop_notificationsconfig option (#47) - Duke Nukem sound pack
- Red Alert Soviet Soldier sound pack
- Missing sound file references in several packs
- zsh completions
bashcompinitordering
- Stop debouncing: Prevents sound spam from rapid background task completions
- Pack rotation: Configure multiple packs in
pack_rotation, each session picks one randomly - CLAUDE_CONFIG_DIR support for non-standard Claude installs (#61)
- 13 community sound packs: Czech (peon_cz, peasant_cz), Spanish (peon_es, peasant_es), RA2 Kirov, WC2 Peasant, AoE2, Russian Brewmaster, Elder Scrolls (Molag Bal, Sheogorath), Dota 2 Axe, Helldivers 2, Sopranos, Rick Sanchez
- WSL2 (Windows) support: PowerShell
MediaPlayeraudio backend with visual popup notifications - PermissionRequest hook: Sound alert when IDE needs permission approval
peon --packcommand: Switch packs from CLI with tab completion and cycling- Performance: Consolidated 5 Python invocations into 1 per hook event
- Polish Orc Peon sound pack (#9)
- French packs: Human Peasant (FR) and Orc Peon (FR) (#7)
- Prevent install.sh from hanging when run via
curl | bash(#8)
- Pause/mute toggle:
peon --toggleCLI and/peon-ping-toggleslash command (#6) - Battlecruiser + Kerrigan sound packs
- RA2 Soviet Engineer sound pack
- Self-update check: Checks for new versions once per day
- BATS test suite: 30+ automated tests with CI (#5)
- Terminal-agnostic tab titles: ANSI escape sequences instead of AppleScript (#3)
- Hook runner compatibility (#5)
- Initial release
- Warcraft III Orc Peon and GLaDOS sound packs
- Claude Code hook for
SessionStart,UserPromptSubmit,Stop,Notification - Desktop notifications (macOS)
- Terminal tab title updates
- Agent session detection (suppress sounds in delegate mode)
- macOS + Linux audio support