# peon-ping
<div align="center">

[English](README.md) | [한국어](README_ko.md) | **中文** | [日本語](README_ja.md)

![macOS](https://img.shields.io/badge/macOS-blue) ![WSL2](https://img.shields.io/badge/WSL2-blue) ![Linux](https://img.shields.io/badge/Linux-blue) ![Windows](https://img.shields.io/badge/Windows-blue) ![MSYS2](https://img.shields.io/badge/MSYS2-blue) ![SSH](https://img.shields.io/badge/SSH-blue)
![License](https://img.shields.io/badge/license-MIT-green)

![Claude Code](https://img.shields.io/badge/Claude_Code-hook-ffab01) ![Amp](https://img.shields.io/badge/Amp-adapter-ffab01) ![Gemini CLI](https://img.shields.io/badge/Gemini_CLI-adapter-ffab01) ![GitHub Copilot](https://img.shields.io/badge/GitHub_Copilot-adapter-ffab01) ![Codex](https://img.shields.io/badge/Codex-adapter-ffab01) ![Cursor](https://img.shields.io/badge/Cursor-adapter-ffab01) ![OpenCode](https://img.shields.io/badge/OpenCode-adapter-ffab01) ![Kilo CLI](https://img.shields.io/badge/Kilo_CLI-adapter-ffab01) ![Kiro](https://img.shields.io/badge/Kiro-adapter-ffab01) ![Kimi Code](https://img.shields.io/badge/Kimi_Code-adapter-ffab01) ![Windsurf](https://img.shields.io/badge/Windsurf-adapter-ffab01) ![Antigravity](https://img.shields.io/badge/Antigravity-adapter-ffab01) ![OpenClaw](https://img.shields.io/badge/OpenClaw-adapter-ffab01) ![Rovo Dev CLI](https://img.shields.io/badge/Rovo_Dev_CLI-adapter-ffab01) ![DeepAgents](https://img.shields.io/badge/DeepAgents-adapter-ffab01) ![oh-my-pi](https://img.shields.io/badge/oh--my--pi-adapter-ffab01) ![Qwen Code](https://img.shields.io/badge/Qwen_Code-adapter-ffab01) ![iFlow CLI](https://img.shields.io/badge/iFlow_CLI-adapter-ffab01) ![Trae](https://img.shields.io/badge/Trae-adapter-ffab01) ![Kiro IDE](https://img.shields.io/badge/Kiro_IDE-adapter-ffab01) ![ECA](https://img.shields.io/badge/ECA-adapter-ffab01)

**当你的 AI 编程助手需要关注时，播放游戏角色语音 + 显示视觉覆盖通知 — 或通过 MCP 让 AI 自行选择音效。**

AI 编程助手完成任务或需要权限时不会通知你。你切换标签页、失去焦点，然后浪费 15 分钟重新进入状态。peon-ping 通过魔兽争霸、星际争霸、传送门、塞尔达等游戏的角色语音和醒目的屏幕横幅来解决这个问题 — 支持 **Claude Code**、**Amp**、**Gemini CLI**、**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** 及任何 MCP 客户端.

**查看演示** &rarr; [peonping.com](https://peonping.com/)

</div>

---

- [安装](#安装)
- [你会听到什么](#你会听到什么)
- [快捷控制](#快捷控制)
- [配置](#配置)
- [Peon 教练](#peon-教练)
- [MCP 服务器](#mcp-服务器)
- [多 IDE 支持](#多-ide-支持)
- [远程开发](#远程开发ssh--devcontainers--codespaces)
- [手机通知](#手机通知)
- [语音包](#语音包)
- [调试](#调试)
- [卸载](#卸载)
- [系统要求](#系统要求)
- [工作原理](#工作原理)
- [链接](#链接)

---

## 安装

### 方式一：Homebrew（推荐）

```bash
brew install PeonPing/tap/peon-ping
```

然后运行 `peon-ping-setup` 注册钩子并下载语音包。支持 macOS 和 Linux。

### 方式二：安装脚本（macOS、Linux、WSL2）

```bash
curl -fsSL https://raw.githubusercontent.com/PeonPing/peon-ping/main/install.sh | bash
```

⚠️ **WSL2 音频说明。** peon-ping 在 Windows 端播放音频。首次运行时会探测一次 Windows 主机（按 Windows 构建号缓存），自动选择最佳播放方式：

- 在 **Windows 10 / Windows 11 24H2 之前的版本**上，直接使用 WPF MediaPlayer，原生支持 MP3 + WAV，无需额外依赖。
- 在 **Windows 11 24H2+**（构建号 26100+）上，微软已从系统中移除了旧版 Windows Media Player，WPF MediaPlayer 会失败（`MILAVERR_INVALIDWMPVERSION`）。peon-ping 会回退到 `System.Media.SoundPlayer`，它使用 Win32 `PlaySound` API，到处都能工作 — 但仅支持 WAV，因此 MP3 语音包需要安装 **ffmpeg** 进行实时转码：

  ```sh
  sudo apt update; sudo apt install -y ffmpeg
  ```

可通过 `PEON_WSL_AUDIO_BACKEND=auto|mediaplayer|soundplayer` 覆盖自动检测：

- `auto`（默认）— 如上所述探测并缓存
- `mediaplayer` — 强制通过 WSL UNC 路径使用 WPF MediaPlayer（在 24H2+ 上会静默失败）
- `soundplayer` — 强制复制临时文件并使用 `SoundPlayer`（通用，非 WAV 文件需要 ffmpeg）

### 方式三：Windows 安装

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

默认安装 5 个精选语音包（魔兽、星际、传送门）。重新运行可更新，同时保留配置和状态。你也可以在 **[peonping.com 交互式选择语音包](https://peonping.com/#picker)** 获取自定义安装命令。

实用安装参数：

- `--all` — 安装所有可用语音包
- `--packs=peon,sc_kerrigan,...` — 仅安装指定语音包
- `--local` — 将语音包、配置和钩子安装到当前项目的 `./.claude/` 目录
- `--global` — 显式全局安装（与默认相同）
- `--init-local-config` — 仅创建 `./.claude/hooks/peon-ping/config.json`

`--local` 不会修改你的 shell rc 文件（不注入全局 `peon` 别名/补全）。钩子注册到项目级 `./.claude/settings.json` 并使用绝对路径，因此在项目内的任何工作目录下都能工作。

示例：

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

如果已存在全局安装，你又安装了本地版本（或反之），安装程序会提示你移除现有的以避免冲突。

### 方式四：克隆后检查

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

### 方式五：Nix（macOS、Linux）

无需安装即可直接从源码运行：

```bash
nix run github:PeonPing/peon-ping -- status
nix run github:PeonPing/peon-ping -- packs install peon
```

或安装到你的 profile：

```bash
nix profile install github:PeonPing/peon-ping
```

开发 shell（bats、shellcheck、nodejs）：

```bash
nix develop  # 或使用 direnv
```

#### Home Manager 模块（声明式配置）

如需可复现的设置，使用 Home Manager 模块：

```nix
# 在你的 home.nix 或 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;
      };
    };

    # 从 og-packs 安装语音包（简单字符串格式）
    # 以及自定义来源（带 name + src 的属性集）
    installPacks = [
      "peon"
      "glados"
      "sc_kerrigan"
      # 来自 GitHub 的自定义语音包（openpeon.com 注册表）
      {
        name = "mr_meeseeks";
        src = pkgs.fetchFromGitHub {
          owner = "kasperhendriks";
          repo = "openpeon-mrmeeseeks";
          rev = "main";  # 或使用 commit hash 以确保可复现
          sha256 = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
        };
      }
    ];
    enableZshIntegration = true;
  };

  # 可选的额外 IDE 钩子（如 Cursor）
  home.file.".cursor/hooks.json".text = builtins.toJSON {
    version = 1;
    hooks = {
      afterAgentResponse = [{ command = "bash ${peonCursorAdapterPath} afterAgentResponse"; }];
      stop               = [{ command = "bash ${peonCursorAdapterPath} stop"; }];
    };
  };
}
```

**语音包安装**：`installPacks` 选项支持两种格式：
- **简单字符串**（如 `"peon"`、`"glados"`）— 从 [og-packs](https://github.com/PeonPing/og-packs) 仓库获取
- **自定义来源** — 带 `name` 和 `src` 字段的属性集，`src` 可以是任何 Nix fetcher 结果（如 `pkgs.fetchFromGitHub`）

在 [openpeon.com](https://openpeon.com/) 上列出的语音包，找到 GitHub 仓库链接并使用 `pkgs.fetchFromGitHub`：
```nix
{
  name = "pack_name";
  src = pkgs.fetchFromGitHub {
    owner = "github-owner";
    repo = "repo-name";
    rev = "main";  # 或 commit hash/tag
    sha256 = "";   # 先留空，Nix 会告诉你正确的 hash
  };
}
```

**Claude Code 钩子**：设置 `programs.peon-ping.claudeCodeIntegration = true;` 即可将 Claude Code 钩子脚本安装到 `~/.claude/hooks/peon-ping/`，并把标准的 peon-ping 钩子条目合并进 `~/.claude/settings.json`。

**其他 IDE 钩子**：为避免覆盖与 peon-ping 无关的 IDE 设置，其他 IDE 的钩子仍然是可选的。peon-ping 在 [`adapters/`](https://github.com/PeonPing/peon-ping/tree/main/adapters) 下提供如 `cursor.sh` 之类的适配器脚本，你可以这样接入：
  ```sh
  ${inputs.peon-ping.packages.${pkgs.system}.default}/share/peon-ping/adapters/$YOUR_IDE.sh EVENT_NAME
  ```
  参见上方 Cursor 示例。

## 你会听到什么

| 事件 | CESP 分类 | 示例 |
|---|---|---|
| 会话开始 | `session.start` | *"Ready to work!"*, *"Something need doing?"* |
| 任务完成 | `task.complete` | *"Work complete."*, *"Work, work."* |
| 代理确认任务 | `task.acknowledge` | *"I can do that."*, *"Be happy to."*, *"Okie dokie."* *（默认禁用）* |
| 需要权限 | `input.required` | *"Hmm?"*, *"What you want?"*, *"Yes?"* |
| 工具或命令错误 | `task.error` | *"Me not that kind of orc!"*, *"Ugh."* |
| 速率或 token 限制 | `resource.limit` | *"Why not?"* |
| 快速提示（10秒内3次以上）| `user.spam` | *"Whaaat?"*, *"Me busy, leave me alone!"*, *"No time for play."* |

此外，还会在每个屏幕上显示**大型覆盖横幅**（macOS/WSL/MSYS2）和终端标签页标题（`● 项目: 完成`）——即使你在其他应用中，也能立即知道任务完成。

peon-ping 实现了 [编码事件语音包规范（CESP）](https://github.com/PeonPing/openpeon) — 这是一个任何代理式 IDE 都可以采用的编码事件声音开放标准。

## 快捷控制

开会或结对编程时需要静音？两种方式：

| 方式 | 命令 | 适用场景 |
|---|---|---|
| **斜杠命令** | `/peon-ping-toggle` | 在 Claude Code 中工作时 |
| **CLI** | `peon toggle` | 从任意终端标签页 |

希望它自动生效？设置 [`focus_detect`](#configuration)，让 peon-ping 遵循 macOS 的**专注模式 / 勿扰模式**。专注模式开启时声音和通知自动静音，关闭后恢复。另见 `headphones_only` 和 `meeting_detect`。

其他 CLI 命令：

```bash
peon pause                # 静音
peon mute                 # 'pause' 的别名
peon volume               # 查看当前音量
peon volume 0.7           # 设置音量（0.0–1.0）
peon rotation             # 查看当前轮换模式
peon rotation random      # 设置轮换模式（random|round-robin|session_override）
peon resume               # 取消静音
peon unmute               # 'resume' 的别名
peon status               # 查看暂停或活动状态（简洁模式）
peon status --verbose     # 显示完整详情（通知、耳机、IDE 等）
peon packs list           # 列出已安装的语音包
peon packs list --registry # 浏览注册表中所有可用语音包
peon packs community      # 按信任等级列出所有注册表语音包（Windows）
peon packs search <query> # 按名称搜索注册表语音包（Windows）
peon packs install <p1,p2> # 从注册表安装语音包
peon packs install --all  # 从注册表安装所有语音包
peon packs install-local <path> # 从本地目录安装语音包
peon packs use <name>     # 切换到指定语音包（Windows 上自动从注册表安装）
peon packs use --install <name>  # 切换到语音包，如需则从注册表安装
peon packs next           # 切换到下一个语音包
peon packs remove <p1,p2> # 移除指定语音包
peon packs bind <name>    # 将语音包绑定到当前目录
peon packs bind --pattern <path> # 将语音包绑定到目录模式，例如 "*/services"
peon packs unbind         # 移除当前目录的绑定
peon packs bindings       # 列出所有已分配的绑定
peon packs ide-bind <ide> <name> # 将语音包绑定到 IDE 标识，例如 codex
peon packs ide-unbind <ide> # 移除 IDE 绑定
peon packs ide-bindings   # 列出所有 IDE 绑定
peon packs exclude add <path> # 为 glob 或目录跳过 path_rules
peon packs exclude remove <path> # 移除排除路径
peon packs exclude list   # 列出排除路径
peon sounds list [pack]   # 列出语音包中的所有声音，并标记已禁用项
peon sounds disable <category> <file> [--pack=<name>]  # 禁用语音包中的单个声音
peon sounds enable <category> <file> [--pack=<name>]   # 重新启用之前禁用的声音
peon notifications on     # 启用桌面通知
peon notifications off    # 禁用桌面通知
peon notifications overlay   # 使用大型覆盖横幅（默认）
peon notifications standard  # 使用标准系统通知
peon notifications test      # 发送测试通知
peon notifications position [pos]    # 获取/设置通知位置（top-left、top-center、top-right、bottom-left、bottom-center、bottom-right）
peon notifications dismiss [N]       # 获取/设置自动关闭时间（秒，0 = 持续显示）
peon notifications label [text|reset] # 获取/设置项目标签覆盖
peon notifications template [key] [fmt]  # 获取/设置/重置消息模板（键：stop、permission、error、idle、question）
peon preview              # 播放 session.start 的所有声音
peon preview <category>   # 播放指定分类的所有声音
peon preview --list       # 列出活动语音包的所有分类
peon mobile ntfy <topic>  # 设置手机通知（免费）
peon mobile off           # 禁用手机通知
peon mobile test          # 发送测试通知
peon debug on             # 启用调试日志
peon debug off            # 禁用调试日志
peon debug status         # 显示调试状态、日志目录、文件数和总大小
peon logs                 # 显示今天日志的最后 50 行
peon logs --last N        # 显示所有日志文件的最后 N 行
peon logs --session ID    # 按会话 ID 过滤今天的日志
peon logs --session ID --all  # 搜索所有日志文件中的会话 ID
peon logs --clear         # 删除所有日志文件（需确认）
peon relay --daemon       # 启动音频中继（用于 SSH/devcontainer）
peon relay --stop         # 停止后台中继
```

`peon preview` 支持的 CESP 分类：`session.start`、`task.acknowledge`、`task.complete`、`task.error`、`input.required`、`resource.limit`、`user.spam`。（扩展分类 `session.end` 和 `task.progress` 已在 CESP 规范中定义，语音包可以实现，但目前未由内置钩子事件触发。）

支持 Tab 补全 — 输入 `peon packs use <TAB>` 查看可用语音包名称。

暂停会立即静音声音和桌面通知。暂停状态会跨会话保持，直到你恢复。暂停时标签页标题仍会更新。

## 配置

peon-ping 在 Claude Code 中安装以下斜杠命令：

- `/peon-ping-toggle` — 静音/取消静音
- `/peon-ping-config` — 更改任意设置（音量、语音包、分类等）
- `/peon-ping-rename <名称>` — 为当前会话设置自定义名称，显示在通知标题和终端标签标题中（零 token 消耗，由钩子拦截处理）；不带参数则重置为自动检测

你也可以直接让 Claude 帮你修改设置 — 例如"启用轮换语音包"、"将音量设为 0.3"或"添加 glados 到我的语音包轮换"。无需手动编辑配置文件。

配置位置取决于安装模式：

- 全局安装：`$CLAUDE_CONFIG_DIR/hooks/peon-ping/config.json`（默认 `~/.claude/hooks/peon-ping/config.json`）
- 本地安装：`./.claude/hooks/peon-ping/config.json`

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

### 独立控制

peon-ping 有三个独立的控制开关，可以混合使用：

| 配置键 | 控制项 | 影响声音 | 影响桌面弹窗 | 影响手机推送 |
|--------|--------|----------|--------------|--------------|
| `enabled` | 主音频开关 | ✅ 是 | ❌ 否 | ❌ 否 |
| `desktop_notifications` | 桌面弹窗横幅 | ❌ 否 | ✅ 是 | ❌ 否 |
| `mobile_notify.enabled` | 手机推送通知 | ❌ 否 | ❌ 否 | ✅ 是 |

这意味着您可以：
- 保留声音但禁用桌面弹窗：`peon notifications off`
- 保留桌面弹窗但禁用声音：`peon pause`
- 启用手机推送但不显示桌面弹窗：设置 `desktop_notifications: false` 和 `mobile_notify.enabled: true`

- **volume**：0.0–1.0（适合办公室使用的音量）
- **desktop_notifications**：`true`/`false` — 独立于声音控制桌面通知弹窗（默认：`true`）。禁用时，声音继续播放但视觉弹窗被抑制。手机通知不受影响。
- **notification_style**：`"overlay"` 或 `"standard"` — 控制桌面通知显示方式（默认：`"overlay"`）
  - **overlay**：大型醒目横幅 — macOS 上使用 JXA Cocoa 覆盖，WSL/MSYS2 上使用 Windows Forms 弹窗。点击覆盖层可聚焦终端（支持 Ghostty、Warp、iTerm2、Zed、Terminal.app）。在 iTerm2 上，点击可聚焦到正确的标签页/窗格/窗口。
  - **standard**：系统通知 — macOS 上使用 [`terminal-notifier`](https://github.com/julienXX/terminal-notifier) / `osascript`，WSL/MSYS2 上使用 Windows toast。安装 `terminal-notifier`（`brew install terminal-notifier`）后，点击通知可自动聚焦终端。在原生 Windows 上，点击 toast 通知可聚焦 IDE 或终端窗口（支持 VS Code、Cursor、Windsurf、Windows Terminal、PowerShell）。当打开多个窗口时，通知会通过基于 PID 的进程树匹配精确定位到触发事件的窗口。
- **overlay_theme**：`"jarvis"`、`"glass"`、`"sakura"`，或留空使用默认覆盖层 — 仅 macOS（默认：无）
  - **jarvis**：圆形 HUD，带旋转弧线、刻度和进度环
  - **glass**：毛玻璃风格面板，带强调色条、进度线和时间戳
  - **sakura**：禅意花园，带盆景树和动态樱花花瓣
  - **wsl_toast**：`true`/`false` — 在 WSL 上使用原生 Windows toast 通知代替 Windows Forms 弹窗。Toast 不会抢占焦点并出现在操作中心。（默认：`true`）
- **categories**：单独开关 CESP 声音分类（例如 `"session.start": false` 禁用问候声音）
- **annoyed_threshold / annoyed_window_seconds**：在 N 秒内多少次提示触发 `user.spam` 彩蛋
- **silent_window_seconds**：对于短于 N 秒的任务，抑制 `task.complete` 声音和通知。（例如 `10` 表示只播放超过 10 秒的任务声音）
- **session_start_cooldown_seconds**（数字，默认：`30`）：当多个工作区同时启动时（例如用 OpenCode 或 Cursor 打开多个文件夹），对问候声音进行去重。只有第一个会话启动会播放问候声；在此窗口期内的后续会话保持静默。设为 `0` 可禁用去重，始终播放问候声。
- **suppress_idle_prompt_repeats**（布尔值，默认：`true`）：当终端未聚焦时，Claude Code 会每 ~60 秒重复触发 `idle_prompt` 通知。peon-ping 把 `idle_prompt` 路由到 `task.complete`，让你仍能在需要输入时听到提示——但若不去重，同一个声音会随每次提醒重复播放。当为 `true` 时，如果同一会话已在 `idle_prompt_suppress_window_seconds` 窗口内触发过 `task.complete`，则抑制此次 `idle_prompt`。设为 `false` 可恢复周期性提醒。
- **idle_prompt_suppress_window_seconds**（数字，默认：`3600`）：`suppress_idle_prompt_repeats` 使用的窗口长度。在某会话播放过 `task.complete` 之后的这么多秒内，该会话后续的 `idle_prompt` 通知保持静默。设为 `0` 可禁用窗口（等同于 `suppress_idle_prompt_repeats: false`）。
- **suppress_subagent_complete**（布尔值，默认：`false`）：抑制子 Agent 活动产生的声音和通知。当 Claude Code 的 Task 工具并行派发多个子 Agent 时，每个子 Agent 都会触发自己的事件：完成时的提示音、Bash 命令失败时的 `task.error`、权限请求时的 `input.required`。将此选项设为 `true`，则只播放父会话的声音。来自子 Agent 内部的事件通过 Claude Code 在 hook 负载中添加的 `agent_id` 字段识别；独立会话的子 Agent（旧版客户端、其他 IDE）仍通过 SubagentStart 时间窗口启发式识别。
- **default_pack**：当没有更具体的规则时使用的备选语音包（默认：`"peon"`）。取代旧的 `active_pack` 键——现有配置在 `peon update` 时自动迁移。
- **path_rules**：`{ "pattern": "...", "pack": "..." }` 对象数组。根据工作目录使用通配符匹配（`*`、`?`）为会话分配语音包。第一个匹配规则生效，优先级高于 `pack_rotation` 和 `default_pack`，但低于 `session_override` 分配。
- **exclude_dirs**：glob 或目录模式数组。如果当前工作目录匹配其中一项，**所有声音和通知都会被静音**（钩子日志记录 `suppressed=True reason=excluded_dir pattern=<匹配项>`）。纯目录路径也会匹配其所有子目录，所以 `"~/conductor/workspaces"` 会让整棵目录树都保持静默。适用于后台代理（如 `CodexBar/ClaudeProbe`）、临时目录或不希望发出声音的工作区。
- **ide_rules**：`{ "ide": "...", "pack": "..." }` 对象数组。在 `path_rules` 之后、轮换与默认包之前按 IDE/source 指定语音包。第一个匹配规则生效。常见 id：`claude`、`codex`、`cursor`、`opencode`、`kilo`、`kiro`、`gemini`、`copilot`、`windsurf`、`kimi`、`antigravity`、`amp`、`deepagents`、`openclaw`、`rovodev`。
- **pack_rotation**：语音包名称数组（例如 `["peon", "sc_kerrigan", "peasant"]`）。用于 `pack_rotation_mode` 为 `random` 或 `round-robin` 时。留空 `[]` 则仅使用 `default_pack`（或 `path_rules` / `ide_rules`）。
- **pack_rotation_mode**：`"random"`（默认）、`"round-robin"` 或 `"session_override"`。使用 `random`/`round-robin` 时，每个会话从 `pack_rotation` 中选择一个语音包。使用 `session_override` 时，`/peon-ping-use <pack>` 命令为每个会话分配语音包。无效或缺失的语音包会按层级回退。（`"agentskill"` 作为 `"session_override"` 的旧别名仍被接受。）
- **session_ttl_days**（数字，默认：7）：使超过 N 天的陈旧每会话语音包分配过期。防止使用 `session_override` 模式时 `.state.json` 无限增长。
- **headphones_only**（布尔值，默认：`false`）：仅在检测到耳机或外部音频设备时播放声音。启用后，如果内置扬声器是活动输出，声音将被静音 — 适用于开放式办公室。使用 `peon status` 查看状态。支持 macOS（通过 `system_profiler`）和 Linux（通过 PipeWire `wpctl` 或 PulseAudio `pactl`）。
- **suppress_sound_when_tab_focused**（布尔值，默认：`false`）：当生成钩子事件的终端标签页处于当前活动/聚焦状态时，跳过声音播放。声音仍会在后台标签页中播放，提醒您其他地方发生了事件。桌面和移动通知不受影响。适用于只想在未查看的标签页中听到音频提示的用户。仅支持 macOS（使用 `osascript` 检查最前端应用和 iTerm2 标签页焦点）。
- **tmux_passthrough**（布尔值，默认：`false`）：将标签页标题和 iTerm2 标签页颜色转义序列通过 tmux 的 DCS 透传发送到宿主终端（需要 tmux 3.3a+ 并设置 `set -g allow-passthrough on`）。默认关闭，因为 tmux 客户端会将多个窗格/窗口复用到单个宿主终端标签页上，且没有按窗格寻址的能力：当多个 agent 会话同时运行时，每个钩子（包括来自后台窗格的）都会以"最后写入者获胜"的方式重绘那个共享标签页，使其不再反映任何单个会话。建议保持关闭，让 tmux 自身的窗口/状态栏承载每个会话的状态；仅当您每个终端标签页只运行一个 tmux 窗口（即 1 个标签页 = 1 个会话）时才启用。在 tmux 之外无效，此时转义序列始终发送。
- **meeting_detect**：检测麦克风是否正在使用中，并在麦克风使用期间临时抑制音频播放。通知仍会正常显示。
- **focus_detect**（布尔值，默认：`false`）：遵循 macOS 的**专注模式 / 勿扰模式**。peon-ping 通过 `afplay` 播放声音并在自定义窗口中绘制覆盖通知，两者都绕过通知中心，因此系统的专注模式开关默认对它们没有影响。启用后，peon-ping 会直接读取专注模式状态，并在**任意**专注模式（勿扰、工作、睡眠等）激活时抑制输出，关闭专注模式后自动恢复。移动推送（如已配置）不受影响，因为手机会遵循其自身的专注模式。仅支持 macOS，采用失败放行策略（若无法读取专注模式状态，则照常播放声音）。
- **focus_detect_mode**（字符串，默认：`"all"`）：专注模式激活时 `focus_detect` 抑制的内容。`"all"` 同时静音声音和覆盖/桌面通知。`"sound"` 仅静音声音（通知仍会显示）。`"notifications"` 仅静音通知（声音仍会播放）。当 `focus_detect` 为 `false` 时忽略此设置。
- **notification_position**（字符串，默认：`"top-center"`）：覆盖通知在屏幕上的显示位置。选项：`"top-left"`、`"top-center"`、`"top-right"`、`"bottom-left"`、`"bottom-center"`、`"bottom-right"`。
- **notification_dismiss_seconds**（数字，默认：`4`）：覆盖通知在 N 秒后自动消失。设为 `0` 则通知持续显示，需点击关闭。
- **`CLAUDE_SESSION_NAME` 环境变量**：在启动 `claude` 前设置，为会话指定自定义名称。同时显示在桌面通知标题和终端标签标题中，优先级高于所有配置项。示例：`CLAUDE_SESSION_NAME="Auth Refactor" claude` 或先 `export CLAUDE_SESSION_NAME="功能: Auth"` 再 `claude`。
- **notification_title_override**（字符串，默认：`""`）：覆盖通知标题中显示的项目名称。为空时自动检测：`/peon-ping-rename` > `CLAUDE_SESSION_NAME` > `.peon-label` > `notification_title_script` > `project_name_map` > git 仓库名 > 文件夹名。
- **notification_title_marker**（字符串，默认：`"●"`）：通知标题和终端标签标题前显示的字符。设为 `""` 可禁用。例如：`"🔔"`。
- **notification_title_script**（字符串，默认：`""`）：在事件触发时运行的 Shell 命令，用于动态计算项目名称。可用环境变量：`PEON_SESSION_ID`、`PEON_CWD`、`PEON_HOOK_EVENT`、`PEON_SESSION_NAME`。使用标准输出（去除首尾空白，最多 50 字符）；非零退出码则继续查找下一层级。示例：`"basename $PEON_CWD"`。
- **project_name_map**（对象，默认：`{}`）：将目录路径映射为通知中的自定义项目标签。键为路径模式，值为显示名称。
- **notification_templates**（对象，默认：`{}`）：自定义通知事件的消息格式。键为事件类型（`stop`、`permission`、`error`、`idle`、`question`），值为支持变量替换的模板字符串。可用变量：`{project}`、`{summary}`、`{tool_name}`、`{status}`、`{event}`。

### 语音包选择层级

peon-ping 通过 6 层层级结构确定使用哪个语音包。第一个产生有效已安装语音包的层级生效：

| 优先级 | 层级 | 来源 | 设置方式 |
|--------|------|------|----------|
| 1（最高） | **session_override** | 每会话分配 | `/peon-ping-use <pack>` 技能或 MCP |
| 2 | **path_rules** | 工作目录的通配符匹配 | `peon packs bind` 或配置中的 `path_rules` |
| 3 | **ide_rules** | IDE/source 匹配 | `peon packs ide-bind` 或配置中的 `ide_rules` |
| 4 | **pack_rotation** | 从列表中随机或轮换 | 配置中的 `pack_rotation` 数组 + `pack_rotation_mode` |
| 5 | **default_pack** | 静态回退 | `peon packs use <name>` 或配置中的 `default_pack` |
| 6（最低） | **硬编码** | 内置默认 | `"peon"` |

如果某层级引用的语音包未安装，则回退到下一层级。
如果 `exclude_dirs` 命中当前工作目录，则本次调用被完全静音 —— 不播放声音、不发送通知。

### 按项目分配语音包（path_rules）

根据目录路径为不同项目分配不同的语音包。可以使用 CLI 或直接编辑 `config.json`。

**CLI（推荐）：**

```bash
peon packs bind glados                     # 将 glados 绑定到当前目录
peon packs bind sc_kerrigan --pattern "*/services/*"  # 绑定到通配符模式
peon packs bind duke_nukem --install       # 绑定并在需要时从注册表安装
peon packs unbind                          # 移除当前目录的绑定
peon packs unbind --pattern "*/services/*" # 移除特定模式的绑定
peon packs bindings                        # 列出所有绑定
```

**手动配置：**

```json
"path_rules": [
  { "pattern": "*/work/client-a/*", "pack": "glados" },
  { "pattern": "*/personal/*",      "pack": "peon" },
  { "pattern": "*/services/*",      "pack": "sc_kerrigan" }
]
```

规则使用通配符匹配（`*`、`?`）。第一个匹配规则生效。路径规则优先级高于 `pack_rotation` 和 `default_pack`，但低于 `session_override` 分配。

### 按 IDE 分配语音包（ide_rules）

当某个路径本身很嘈杂、或者多个工具共用同一目录时，可以让语音包跟着 IDE/source 走。

**CLI（推荐）：**

```bash
peon packs ide-bind codex glados        # 为 Codex 会话使用 glados
peon packs ide-bind claude peon         # 为 Claude Code 使用 peon
peon packs ide-unbind codex             # 移除一个 IDE 规则
peon packs ide-bindings                 # 列出 IDE 规则和最近检测到的 IDE
```

**手动配置：**

```json
"ide_rules": [
  { "ide": "codex",  "pack": "glados" },
  { "ide": "claude", "pack": "peon" }
]
```

`ide_rules` 在 `path_rules` 之后执行。

## 常见用例

### 保留声音但禁用弹窗

想要语音反馈但不想要视觉干扰？

```bash
peon notifications off
```

这会保持所有声音类别的播放，同时禁用桌面通知横幅。手机通知（如果已配置）继续工作。

您也可以使用别名：

```bash
peon popups off
```

### 静音模式但保留通知

想要视觉提醒但不要音频？

```bash
peon pause  # 或在配置中设置 "enabled": false
```

当 `desktop_notifications: true` 时，您将收到弹窗但没有声音。

### 完全静音

禁用所有功能：

```bash
peon pause
peon notifications off
peon mobile off
```

## Peon 教练

你的苦工也是你的私人教练。内置帕维尔风格（Pavel-style）每日锻炼模式 — 那个告诉你"work work"的兽人现在会叫你趴下做二十个俯卧撑。

### 快速开始

```bash
peon trainer on              # 启用教练模式
peon trainer goal 200        # 设置每日目标（默认：300/300）
# ... 写一段时间代码，苦工每约 20 分钟唠叨你一次 ...
peon trainer log 25 pushups  # 记录你的运动次数
peon trainer log 30 squats
peon trainer status          # 查看进度
```

### 工作原理

教练提醒基于你的编程会话运行。当你开始新会话时，苦工会立即鼓励你在写任何代码之前先做俯卧撑。然后在活跃编程期间每约 20 分钟，你会听到苦工喊你做更多次。无需后台守护进程。使用 `peon trainer log` 记录你的次数，进度在午夜自动重置。

### 命令

| 命令 | 描述 |
|---------|-------------|
| `peon trainer on` | 启用教练模式 |
| `peon trainer off` | 禁用教练模式 |
| `peon trainer status` | 显示今日进度 |
| `peon trainer log <n> <exercise>` | 记录次数（例如 `log 25 pushups`） |
| `peon trainer goal <n>` | 设置所有运动的统一每日目标 |
| `peon trainer goal <exercise> <n>` | 设置单项运动的统一每日目标 |
| `peon trainer goal <exercise> <day> <n>` | 设置特定日期的目标（mon、tue 等） |
| `peon trainer goal <day> <n>` | 设置某天所有运动的目标 |

### 日程表与统一目标

运动可以设置**统一每日目标**（每天相同）或**按日日程表**（不同日期不同目标）。两者互斥：

- 设置统一目标会移除该运动的所有日程安排
- 设置特定日期目标会移除该运动的统一目标

日期使用缩写：`mon`、`tue`、`wed`、`thu`、`fri`、`sat`、`sun`

```bash
peon trainer goal pushups 300         # 每天 300 俯卧撑（统一目标）
peon trainer goal pushups mon 400     # 覆盖：周一 400（创建日程表）
peon trainer goal squats sun 0        # 周日深蹲休息
peon trainer goal fri 150             # 周五所有运动轻松日
```

在休息日（目标=0），提醒会被跳过，状态显示 `[REST DAY]`。如果你愿意，仍然可以在休息日记录运动次数。

### Claude Code 技能

在 Claude Code 中，你可以不用离开对话就记录次数：

```
/peon-ping-log 25 pushups
/peon-ping-log 30 squats
```

### 自定义语音

将你自己的音频文件放入 `~/.claude/hooks/peon-ping/trainer/sounds/`：

```
trainer/sounds/session_start/  # 会话问候（"Pushups first, code second! Zug zug!"）
trainer/sounds/remind/         # 提醒语音（"Something need doing? YES. PUSHUPS."）
trainer/sounds/log/            # 确认语音（"Work work! Muscles getting bigger maybe!"）
trainer/sounds/complete/       # 庆祝语音（"Zug zug! Human finish all reps!"）
trainer/sounds/slacking/       # 失望语音（"Peon very disappointed."）
```

更新 `trainer/manifest.json` 来注册你的声音文件。

## MCP 服务器

peon-ping 包含一个 [MCP（模型上下文协议）](https://modelcontextprotocol.io/)服务器，任何兼容 MCP 的 AI 代理都可以通过工具调用直接播放声音，无需钩子。

核心区别：**由代理选择声音**。代理不再在每个事件上自动播放固定声音，而是直接调用 `play_sound` 指定想要的声音——构建失败时用 `duke_nukem/SonOfABitch`，读取文件时用 `sc_kerrigan/IReadYou`。

### 设置

在 MCP 客户端配置中添加（Claude Desktop、Cursor 等）：

```json
{
  "mcpServers": {
    "peon-ping": {
      "command": "node",
      "args": ["/path/to/peon-ping/mcp/peon-mcp.js"]
    }
  }
}
```

通过 Homebrew 安装时路径为 `$(brew --prefix peon-ping)/libexec/mcp/peon-mcp.js`。完整设置说明见 [`mcp/README.md`](mcp/README.md)。

### 可用功能

| 功能 | 说明 |
|---|---|
| **`play_sound`** | 按键名播放一个或多个声音（如 `duke_nukem/SonOfABitch`、`peon/PeonReady1`） |
| **`peon-ping://catalog`** | 以 MCP 资源形式获取完整语音包目录——客户端预取一次，无需重复工具调用 |
| **`peon-ping://pack/{name}`** | 获取指定语音包的详细信息和可用声音键名 |

需要 Node.js 18+。由 [@tag-assistant](https://github.com/tag-assistant) 贡献。

## 多 IDE 支持

peon-ping 适用于任何支持钩子的代理式 IDE。适配器将 IDE 特定事件转换为 [CESP 标准](https://github.com/PeonPing/openpeon)。

| IDE | 状态 | 设置 |
|---|---|---|
| **Claude Code** | 内置 | `curl \| bash` 安装会自动处理 |
| **Amp** | 适配器 | `bash adapters/amp.sh` / `powershell adapters/amp.ps1`（[设置](#amp-设置)） |
| **Gemini CLI** | 适配器 | 添加指向 `adapters/gemini.sh`（Windows 用 `.ps1`）的钩子（[设置](#gemini-cli-设置)） |
| **GitHub Copilot CLI** | 内置（自动检测） | 当 `~/.copilot` 存在时，`install.sh` / `install.ps1` 自动在 `~/.copilot/hooks/peon-ping.json` 注册钩子。也可通过 `adapters/copilot.sh` / `.ps1` 进行逐仓库手动配置（[设置](#github-copilot-cli-设置)） |
| **OpenAI Codex** | 适配器 | 先安装 peon-ping 运行时，然后在 `~/.codex/config.toml` 中添加指向 `adapters/codex.sh`（或 `.ps1`）的 `notify` 条目（[设置](#openai-codex-设置)） |
| **Cursor** | 内置 | `curl \| bash`、`peon-ping-setup` 或 Windows `install.ps1` 自动检测并注册钩子。在 Windows 上，请在 **设置 → 功能 → 第三方技能** 中启用，以便 Cursor 加载 `~/.claude/settings.json` 以播放 SessionStart/Stop 音效。 |
| **OpenCode** | 适配器 | `bash adapters/opencode.sh` / `powershell adapters/opencode.ps1`（[设置](#opencode-设置)） |
| **Kilo CLI** | 适配器 | `bash adapters/kilo.sh` / `powershell adapters/kilo.ps1`（[设置](#kilo-cli-设置)） |
| **Kiro** | 适配器 | 添加指向 `adapters/kiro.sh`（或 `.ps1`）的钩子条目（[设置](#kiro-设置)） |
| **Windsurf** | 适配器 | 添加指向 `adapters/windsurf.sh`（或 `.ps1`）的钩子条目（[设置](#windsurf-设置)） |
| **Google Antigravity** | 适配器 | `bash adapters/antigravity.sh` / `powershell adapters/antigravity.ps1`。无头 / macOS LaunchAgent 场景可用 `bash adapters/antigravity-py.sh --install`（基于 Python `watchdog` 的 25 秒空闲阈值守护进程，需要 `pip3 install watchdog`）。Python 守护进程支持旧版 `conversations/*.pb` 状态，以及较新的 `antigravity-cli` / `antigravity-ide` 的 `conversations/*.db` 和 `brain/**/transcript*.jsonl` 布局。 |
| **Kimi Code** | 适配器 | `bash adapters/kimi.sh --install` / `powershell adapters/kimi.ps1 -Install`（[设置](#kimi-code-设置)） |
| **OpenClaw** | 适配器 | 调用 `adapters/openclaw.sh <event>`（或 `openclaw.ps1`），支持所有 CESP 分类和原生 Claude Code 事件名 |
| **Rovo Dev CLI** | 适配器 | 如果 `~/.rovodev` 存在，`install.sh` 会自动注册，或手动添加钩子到 `~/.rovodev/config.yml`（[设置](#rovo-dev-cli-设置)） |
| **DeepAgents** | 适配器 | `bash adapters/deepagents.sh` / `powershell adapters/deepagents.ps1`（[设置](#deepagents-设置)） |
| **oh-my-pi (omp)** | 适配器 | `bash adapters/omp.sh`（[设置](#oh-my-pi-omp-设置)） |
| **Qwen Code** | 适配器 | 添加指向 `adapters/qwen.sh`（Windows 用 `.ps1`）的钩子（[设置](#qwen-code-设置)） |
| **iFlow CLI** | 适配器 | 添加指向 `adapters/iflow.sh`（或 `.ps1`）的钩子（[设置](#iflow-cli-设置)） |
| **Trae** | 适配器 | 文件系统监视器：`bash adapters/trae.sh &` / `powershell adapters/trae.ps1 -Install`（[设置](#trae-设置)） |
| **Kiro IDE** | 适配器 | 在 `.kiro/hooks/*.kiro.hook` 中添加调用 `adapters/kiro-ide.sh`（或 `.ps1`）的 Agent 钩子（[设置](#kiro-ide-设置)） |
| **ECA** | 适配器 | 添加指向 `adapters/eca.sh`（或 `.ps1`）的 shell 钩子（[设置](#eca-设置)） |

> **Windows：** 所有适配器都有原生 PowerShell（`.ps1`）版本。Windows 安装程序（`install.ps1`）会将其复制到 `~/.claude/hooks/peon-ping/adapters/`。文件系统监视器（Amp、Antigravity、Kimi、Trae）使用 .NET `FileSystemWatcher` 而非 fswatch/inotifywait — 无需额外依赖。

### OpenAI Codex 设置

Codex 支持使用适配器，不会由 `peon-ping-setup` 自动注册。

Codex 适配器要求 peon-ping 运行时位于 `~/.claude/hooks/peon-ping/`，即使你只使用 Codex 而不使用 Claude Code。

**设置步骤：**

1. 先安装 peon-ping 运行时：

   ```bash
   bash "$(brew --prefix peon-ping)"/libexec/install.sh --no-rc
   ```

   或使用标准安装器：

   ```bash
   curl -fsSL https://raw.githubusercontent.com/PeonPing/peon-ping/main/install.sh | bash -s -- --no-rc
   ```

2. 添加以下内容到 `~/.codex/config.toml`：

   ```toml
   notify = ["bash", "~/.claude/hooks/peon-ping/adapters/codex.sh"]
   ```

3. 重启 Codex。

如果通过 Homebrew 安装，运行时文件在 `~/.claude/hooks/peon-ping/` 下管理，Codex 适配器将 Codex notify 事件转发到该共享运行时。

### Amp 设置

[Amp](https://ampcode.com)（Sourcegraph）的文件系统监视适配器。Amp 不像 Claude Code 那样提供事件钩子，因此该适配器监视 Amp 的线程文件并检测代理何时完成回合。

**设置步骤：**

1. 确保已安装 peon-ping（`curl -fsSL https://peonping.com/install | bash`）

2. 安装 `fswatch`（macOS）或 `inotify-tools`（Linux）：

   ```bash
   brew install fswatch        # macOS
   sudo apt install inotify-tools  # Linux
   ```

3. 启动监视器：

   ```bash
   bash ~/.claude/hooks/peon-ping/adapters/amp.sh        # 前台运行
   bash ~/.claude/hooks/peon-ping/adapters/amp.sh &       # 后台运行
   ```

**事件映射：**

- 新线程文件创建 → 问候音效（*"Ready to work?"*、*"Yes?"*）
- 线程文件停止更新 + 代理完成回合 → 完成音效（*"Work, work."*、*"Job's done!"*）

**工作原理：**

适配器监视 `~/.local/share/amp/threads/` 目录的 JSON 文件变化。当线程文件停止更新（1 秒空闲超时）且最后一条消息来自助手的文本内容（非待处理的工具调用）时，发出 `Stop` 事件 — 表示代理已完成，正在等待你的输入。

**环境变量：**

| 变量 | 默认值 | 说明 |
|---|---|---|
| `AMP_DATA_DIR` | `~/.local/share/amp` | Amp 数据目录 |
| `AMP_THREADS_DIR` | `$AMP_DATA_DIR/threads` | 要监视的线程目录 |
| `AMP_IDLE_SECONDS` | `1` | 停止变化后等待多少秒发出 Stop 事件 |
| `AMP_STOP_COOLDOWN` | `10` | 每个线程 Stop 事件之间的最小间隔秒数 |

### GitHub Copilot CLI 设置

[GitHub Copilot CLI](https://github.com/github/copilot-cli) 原生集成，完全符合 [CESP v1.0](https://github.com/PeonPing/openpeon) 规范。

**推荐：用户级（全局）配置 —— 无需逐仓库设置。**

每当 `~/.copilot/` 目录存在时，`install.sh` 和 `install.ps1` 会自动在 `~/.copilot/hooks/peon-ping.json` 注册 Copilot CLI 钩子。如果你在安装 peon-ping 之后才安装了 Copilot CLI，请重新运行安装脚本。该配置使用 **PascalCase 事件名称**，这会让 Copilot CLI 直接发送与 VS Code 兼容的（snake_case）负载，正是 `peon.sh` / `peon.ps1` 原生读取的格式 —— 不需要逐仓库适配器。

全局注册的钩子：

| 事件 | 分类 | 触发条件 |
|---|---|---|
| `SessionStart` | `session.start` | 启动 `copilot`（问候） |
| `SessionEnd` | _（当前静音）_ | 退出 CLI |
| `UserPromptSubmit` | `user.spam`（3+ 次快速提示后） | 每次提交提示 |
| `Stop`（即 `agentStop`） | `task.complete` | Agent 完成一个回合（5 秒去抖） |
| `Notification` | `input.required`（elicitation） | 空闲、elicitation 对话框、权限弹出 |
| `PermissionRequest` | `input.required` | 工具权限请求 |
| `PreToolUse` | `input.required`（仅匹配危险模式时） | 每次工具调用前 |
| `PostToolUseFailure` | `task.error` | 工具失败 |
| `PreCompact` | `resource.limit` | 上下文压缩开始 |

`postToolUse` **未**被注册：peon 没有 `PostToolUse` 处理程序，将其路由到 `Stop` 会淹没去抖窗口，吞掉真正的 `Stop` 事件。

**前置条件（Windows）：** Copilot CLI 钩子需要 [PowerShell 7+](https://github.com/PowerShell/PowerShell)（`pwsh` 在 `PATH` 中）以及宽松的执行策略：

```powershell
winget install Microsoft.PowerShell
powershell -NoProfile -Command "Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned -Force"
```

**替代方案：使用适配器进行逐仓库配置。**

如果你想要将 Copilot CLI 钩子提交到特定仓库（例如团队工作流），请使用 `.github/hooks/hooks.json` 配合 `adapters/copilot.sh`（Windows 上为 `.ps1`）转换器：

```json
{
  "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"
      }
    ]
  }
}
```

可按上表添加其他事件。适配器将 Copilot CLI 的 camelCase 负载（`sessionId`、`toolName`、`stopReason` 等）转换为 `peon.sh` / `peon.ps1` 读取的 snake_case 形式（`session_id`、`tool_name`、`stop_reason`）。

**功能：**

- **音频播放** 通过 `afplay`（macOS）、`pw-play`/`paplay`/`ffplay`（Linux）、`MediaPlayer`/`SoundPlayer`（Windows）—— 与 shell 钩子相同的优先级链
- **CESP 事件映射** —— Copilot CLI 钩子映射到标准 CESP 分类（`session.start`、`task.complete`、`task.error`、`input.required`、`user.spam`、`resource.limit`）
- **桌面通知** —— 默认使用大型覆盖横幅，或标准通知
- **垃圾信息检测** —— 检测 10 秒内 3 次以上快速提示，触发 `user.spam` 语音
- **去抖** —— `Stop` 事件在 5 秒窗口内被抑制，防止链式工具调用产生的垃圾消息

### OpenCode 设置

[OpenCode](https://opencode.ai/) 的原生 TypeScript 插件，完全符合 [CESP v1.0](https://github.com/PeonPing/openpeon) 规范。

**快速安装：**

```bash
curl -fsSL https://raw.githubusercontent.com/PeonPing/peon-ping/main/adapters/opencode.sh | bash
```

安装程序将 `peon-ping.ts` 复制到 `~/.config/opencode/plugins/` 并在 `~/.config/opencode/peon-ping/config.json` 创建配置。语音包存储在共享 CESP 路径（`~/.openpeon/packs/`）。

**功能：**

- **声音播放** — 通过 `afplay`（macOS）、`pw-play`/`paplay`/`ffplay`（Linux）— 与 shell 钩子相同的优先级链
- **CESP 事件映射** — `session.created` / `session.idle` / `session.error` / `permission.asked` / 快速提示检测都映射到标准 CESP 分类
- **桌面通知** — 通过 [`terminal-notifier`](https://github.com/julienXX/terminal-notifier) 提供丰富通知（副标题、按项目分组），回退到 `osascript`。仅在终端未获得焦点时触发
- **终端焦点检测** — 通过 AppleScript 检测你的终端应用（Terminal、iTerm2、Warp、Alacritty、kitty、WezTerm、ghostty、Hyper）是否在最前端
- **标签页标题** — 更新终端标签页显示任务状态（`● 项目: 工作中...` / `✓ 项目: 完成` / `✗ 项目: 错误`）
- **语音包切换** — 从配置读取 `default_pack`（旧版配置中回退到 `active_pack`），运行时加载语音包的 `openpeon.json` 清单。`path_rules` 可根据工作目录覆盖语音包。
- **不重复逻辑** — 避免每个分类连续播放相同声音
- **刷屏检测** — 检测 10 秒内 3 次以上快速提示，触发 `user.spam` 语音

<details>
<summary>🖼️ 截图：带有自定义苦工图标的桌面通知</summary>

![peon-ping OpenCode notifications](https://github.com/user-attachments/assets/e433f9d1-2782-44af-a176-71875f3f532c)

</details>

> **提示：** 安装 `terminal-notifier`（`brew install terminal-notifier`）以获得更丰富的通知（支持副标题和分组）。

<details>
<summary>🎨 可选：自定义苦工图标用于通知</summary>

默认情况下，`terminal-notifier` 显示通用终端图标。包含的脚本使用 macOS 内置工具（`sips` + `iconutil`）将其替换为苦工图标 — 无需额外依赖。

```bash
bash <(curl -fsSL https://raw.githubusercontent.com/PeonPing/peon-ping/main/adapters/opencode/setup-icon.sh)
```

或本地安装（Homebrew / git clone）：

```bash
bash ~/.claude/hooks/peon-ping/adapters/opencode/setup-icon.sh
```

脚本会自动查找苦工图标（Homebrew libexec、OpenCode 配置或 Claude 钩子目录），生成正确的 `.icns`，备份原始 `Terminal.icns` 并替换。`brew upgrade terminal-notifier` 后需重新运行。

> **未来：** 当 [jamf/Notifier](https://github.com/jamf/Notifier) 发布到 Homebrew（[#32](https://github.com/jamf/Notifier/issues/32)）时，插件将迁移到它 — Notifier 内置 `--rebrand` 支持，无需修改图标。

</details>

### Kilo CLI 设置

[Kilo CLI](https://github.com/kilocode/cli) 的原生 TypeScript 插件，完全符合 [CESP v1.0](https://github.com/PeonPing/openpeon) 规范。Kilo CLI 是 OpenCode 的分支，使用相同的插件系统 — 此安装程序下载 OpenCode 插件并为 Kilo 打补丁。

**快速安装：**

```bash
curl -fsSL https://raw.githubusercontent.com/PeonPing/peon-ping/main/adapters/kilo.sh | bash
```

安装程序将 `peon-ping.ts` 复制到 `~/.config/kilo/plugins/` 并在 `~/.config/kilo/peon-ping/config.json` 创建配置。语音包存储在共享 CESP 路径（`~/.openpeon/packs/`）。

**功能：** 与 [OpenCode 适配器](#opencode-设置)相同 — 声音播放、CESP 事件映射、桌面通知、终端焦点检测、标签页标题、语音包切换、不重复逻辑和刷屏检测。

### Gemini CLI 设置

[Gemini CLI](https://github.com/google-gemini/gemini-cli) 的 shell 适配器，完全符合 [CESP v1.0](https://github.com/PeonPing/openpeon) 规范。

**设置步骤：**

1. 确保已安装 peon-ping（`curl -fsSL https://peonping.com/install | bash`）

2. 在 `~/.gemini/settings.json` 中添加以下钩子：

   ```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"
              }
            ]
          }
        ]
      }
    }
   ```

**事件映射：**

- `SessionStart`（startup）→ 问候音效（*"准备好了吗？"*、*"是的？"*）
- `AfterAgent` → 任务完成音效（*"干活，干活。"*、*"完成了！"*）
- `AfterTool`（成功）→ 任务完成音效；（失败）→ 错误音效（*"我做不到。"*）
- `Notification` → 系统通知

### Windsurf 设置

添加到 `~/.codeium/windsurf/hooks.json`（用户级）或 `.windsurf/hooks.json`（工作区级）：

```json
{
  "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 设置

创建 `~/.kiro/agents/peon-ping.json`：

```json
{
  "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` 被有意排除 — 它们会在每次工具调用时触发，会非常嘈杂。

### Rovo Dev CLI 设置

[Rovo Dev CLI](https://developer.atlassian.com/cloud/rovo/)（Atlassian）的 shell 适配器，完全符合 [CESP v1.0](https://github.com/PeonPing/openpeon) 规范。

**自动设置：**

如果运行 `install.sh` 或 `peon-ping-setup` 时 `~/.rovodev/config.yml` 存在，事件钩子会自动注册。

**手动设置：**

1. 确保已安装 peon-ping（`curl -fsSL https://peonping.com/install | bash`）

2. 添加到 `~/.rovodev/config.yml`：

   ```yaml
   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. 重启 Rovo Dev CLI 以使钩子生效。

**事件映射：**

- `on_complete` → 完成音效（*"Work, work."*、*"Job's done!"*）
- `on_error` → 错误音效（*"I can't do that."*、*"Son of a bitch!"*）
- `on_tool_permission` → 权限提示音效（*"Something need doing?"*、*"Hmm?"*）

**功能：**

- **声音播放** 通过 `afplay`（macOS）、`pw-play`/`paplay`/`ffplay`（Linux）— 与 shell 钩子相同的优先级链
- **CESP 事件映射** — Rovo Dev 事件映射到标准 CESP 分类（`task.complete`、`task.error`、`input.required`）
- **桌面通知** — 默认大型覆盖横幅，或标准通知
- **防抖** — 抑制快速完成产生的重复声音

### Kimi Code 设置

[Kimi Code CLI](https://github.com/MoonshotAI/kimi-cli)（MoonshotAI）的文件系统监视适配器。Kimi Code 将 Wire Mode 事件写入 `~/.kimi/sessions/` — 该适配器作为后台守护进程监视这些文件并将事件转换为 CESP 格式。

```bash
# 安装（启动后台守护进程）
bash ~/.claude/hooks/peon-ping/adapters/kimi.sh --install

# 查看状态 / 停止
bash ~/.claude/hooks/peon-ping/adapters/kimi.sh --status
bash ~/.claude/hooks/peon-ping/adapters/kimi.sh --uninstall
```

macOS 需要 `fswatch`（`brew install fswatch`），Linux 需要 `inotifywait`（`apt install inotify-tools`）。`curl | bash` 安装器会自动检测 Kimi Code 并启动守护进程。

**macOS 上 `--install` 会注册 LaunchAgent**（`~/Library/LaunchAgents/com.peonping.kimi-adapter.plist`），监听器会在登录时自动启动并在崩溃时自动重启 — 重启后无需重新运行 `--install`。设置 `KIMI_NO_LAUNCHD=1` 可回退到 `nohup`+pidfile（例如用于测试）。Linux 始终使用 `nohup`+pidfile。

**仅 Kimi 安装（无需 Claude）：**

如果你没有 Claude Code，只想给 Kimi 装 peon-ping，使用 `--kimi`：

```bash
curl -fsSL peonping.com/install | bash -s -- --kimi
```

文件会安装到 `~/.kimi/hooks/peon-ping/` 而不是 `~/.claude/hooks/peon-ping/`，也不会创建 `~/.claude/` 目录。安装器还会自动检测：在仅有 `~/.kimi/` 但没有 `~/.claude/` 的机器上，无参数运行安装器会自动进入 `--kimi` 模式。监视守护进程会在安装时启动，并通过 LaunchAgent 在每次登录时重新启动。

**与 Claude 安装共享语音包：**

如果 `~/.claude/hooks/peon-ping/packs/` 已存在并包含语音包，`--kimi` 安装会将 `~/.kimi/hooks/peon-ping/packs` 软链接到它，而不是重新下载。一次下载即可服务两个 IDE，从任一侧执行 `peon packs install <name>` 都会更新共享的包集。状态、配置和静音切换在每个安装中保持独立。传递 `--no-shared-packs`（或 `--packs=` / `--all`）以下载独立副本。

**事件映射：**

- 新会话 → 问候音效（*"Ready to work?"*、*"Yes?"*）
- Agent 完成回合 → 完成音效（*"Work, work."*、*"Job's done!"*）
- 上下文压缩 → Token 限制音效
- 子 Agent 启动 → 子 Agent 跟踪

### 工具无关的安装根目录（`--openpeon`）

将全部内容（钩子、语音包、`settings.json`）安装到 `~/.openpeon` 而非 `~/.claude`：

```bash
curl -fsSL peonping.com/install | bash -s -- --openpeon
# Windows：powershell -File install.ps1 -OpenPeon
```

适用于 OpenPeon 品牌化或工具无关的部署：`peon.sh` 会通过其 packs 锚定回退自动发现 `~/.openpeon/packs`，因此指向该根目录的适配器无需 `~/.claude` 即可工作。无需此标志时，也可用 `CLAUDE_CONFIG_DIR=~/.openpeon bash install.sh` 实现相同的重定向。

### oh-my-pi (omp) 设置

[oh-my-pi](https://github.com/can1357/oh-my-pi)（`omp`）的原生 TypeScript 扩展，完全符合 [CESP v1.0](https://github.com/PeonPing/openpeon) 规范。订阅 omp 的 `ExtensionAPI` 生命周期事件，并通过 `peon.sh` 路由，让 omp 用户享受 peon-ping 的所有功能：语音包、桌面通知、训练提醒、移动推送、SSH/devcontainer 中继及标签页标题更新。

**快速安装：**

```bash
curl -fsSL https://raw.githubusercontent.com/PeonPing/peon-ping/main/adapters/omp.sh | bash
```

安装程序将 `peon-ping.ts` 和 `package.json` 复制到 `~/.omp/agent/extensions/peon-ping/`。之后重启 omp。

**事件映射：**

| omp 事件                               | peon-ping 事件        |
|----------------------------------------|-----------------------|
| `session_start`                        | `SessionStart`        |
| `turn_start`                           | `UserPromptSubmit`    |
| `turn_end`                             | `Stop`                |
| `tool_result` with `isError: true`     | `PostToolUseFailure`  |
| `auto_compaction_start`                | `PreCompact`          |
| `session_shutdown`                     | `SessionEnd`          |

**卸载：**

```bash
curl -fsSL https://raw.githubusercontent.com/PeonPing/peon-ping/main/adapters/omp.sh | bash -s -- --uninstall
```

### Qwen Code 设置

为 [Qwen Code](https://github.com/QwenLM/qwen-code)（阿里巴巴）提供的轻量透传适配器。Qwen Code 采用 Claude Code 风格的钩子系统——事件以 JSON 形式通过 stdin 传入，且事件名使用与 peon-ping 一致的 PascalCase CESP 名称——因此该适配器只是给会话 id 打上 `qwen-` 前缀，并丢弃噪声较大的逐工具调用事件。

添加到 `~/.qwen/settings.json`：

```json
{
  "hooks": {
    "SessionStart":       [{ "hooks": [{ "type": "command", "command": "bash ~/.claude/hooks/peon-ping/adapters/qwen.sh" }] }],
    "UserPromptSubmit":   [{ "hooks": [{ "type": "command", "command": "bash ~/.claude/hooks/peon-ping/adapters/qwen.sh" }] }],
    "Stop":               [{ "hooks": [{ "type": "command", "command": "bash ~/.claude/hooks/peon-ping/adapters/qwen.sh" }] }],
    "Notification":       [{ "hooks": [{ "type": "command", "command": "bash ~/.claude/hooks/peon-ping/adapters/qwen.sh" }] }],
    "PostToolUseFailure": [{ "hooks": [{ "type": "command", "command": "bash ~/.claude/hooks/peon-ping/adapters/qwen.sh" }] }],
    "SessionEnd":         [{ "hooks": [{ "type": "command", "command": "bash ~/.claude/hooks/peon-ping/adapters/qwen.sh" }] }]
  }
}
```

在 Windows 上，通过 `powershell -NoProfile -File %USERPROFILE%\.claude\hooks\peon-ping\adapters\qwen.ps1` 指向 `qwen.ps1`。

### iFlow CLI 设置

为 [iFlow CLI](https://cli.iflow.cn)（iflow-ai）提供的透传适配器。iFlow 采用 Claude Code 风格的钩子系统（PascalCase 事件经 stdin 传入）；该适配器以 `iflow-` 会话前缀转发有意义的生命周期事件，并将失败的 `PostToolUse` 映射为 `PostToolUseFailure`。

添加到 `~/.iflow/settings.json`（或项目级 `./.iflow/settings.json`）：

```json
{
  "hooks": {
    "SessionStart":     [{ "hooks": [{ "type": "command", "command": "bash ~/.claude/hooks/peon-ping/adapters/iflow.sh" }] }],
    "UserPromptSubmit": [{ "hooks": [{ "type": "command", "command": "bash ~/.claude/hooks/peon-ping/adapters/iflow.sh" }] }],
    "Stop":             [{ "hooks": [{ "type": "command", "command": "bash ~/.claude/hooks/peon-ping/adapters/iflow.sh" }] }],
    "Notification":     [{ "hooks": [{ "type": "command", "command": "bash ~/.claude/hooks/peon-ping/adapters/iflow.sh" }] }],
    "PostToolUse":      [{ "hooks": [{ "type": "command", "command": "bash ~/.claude/hooks/peon-ping/adapters/iflow.sh" }] }],
    "SessionEnd":       [{ "hooks": [{ "type": "command", "command": "bash ~/.claude/hooks/peon-ping/adapters/iflow.sh" }] }]
  }
}
```

### Trae 设置

为 [Trae](https://trae.ai)（字节跳动）提供的文件系统监视器适配器。Trae 是基于 VS Code 的 AI IDE，没有同步的 shell 钩子 API，因此 peon-ping 采用与 Amp、Antigravity 相同的监视方式：出现新的会话文件即 `SessionStart`，空闲计时器（会话文件停止更新）触发 `Stop`。

```bash
# 前台运行
bash ~/.claude/hooks/peon-ping/adapters/trae.sh
# 后台运行
bash ~/.claude/hooks/peon-ping/adapters/trae.sh &
```

在 Windows 上：`powershell -File adapters\trae.ps1 -Install` 会注册后台监视器（用 `-Status` / `-Uninstall` 管理）。

Trae 的磁盘会话布局因平台/版本而异且无公开文档，因此被监视的路径可通过环境变量覆盖：

| 变量 | 默认值 |
|---|---|
| `TRAE_DATA_DIR` | `~/.trae` |
| `TRAE_SESSIONS_DIR` | `$TRAE_DATA_DIR/sessions` |
| `TRAE_SESSION_GLOB` | `*.json` |

需要 `fswatch`（macOS：`brew install fswatch`）或 `inotifywait`（Linux：`apt install inotify-tools`）。Windows 使用 .NET `FileSystemWatcher`——无需额外依赖。

### Kiro IDE 设置

为 **Kiro IDE**（Amazon）提供的钩子适配器——与 [Kiro CLI](#kiro-设置)（`adapters/kiro.sh`）不同。IDE 的 Agent Hooks 是 `.kiro/hooks/*.kiro.hook` JSON 文件；其 `then.type: runCommand` 动作运行 shell 命令时**不传 stdin JSON**，而是把触发的事件名作为 argv 参数传给适配器。

为每个事件创建一个钩子文件，例如 `.kiro/hooks/peon-ping-stop.kiro.hook`：

```json
{
  "version": "1.0.0",
  "enabled": true,
  "name": "peon-ping-stop",
  "when": { "type": "agentStop" },
  "then": {
    "type": "runCommand",
    "command": "bash ~/.claude/hooks/peon-ping/adapters/kiro-ide.sh agentStop"
  }
}
```

用 `when.type` = `promptSubmit`（→ `UserPromptSubmit`）、`preToolUse`（→ 权限提示）或 `sessionStart`（→ `SessionStart`）重复创建，每个都把对应事件名作为命令参数传入。`postToolUse` 及文件/用户触发的钩子不携带 peon 相关信号，将被忽略。在 Windows 上，通过 `powershell -NoProfile -File` 指向 `kiro-ide.ps1`。

### ECA 设置

为 [ECA](https://eca.dev)（Editor Code Assistant，编辑器无关的 LLM Agent 集成）提供的 shell 钩子适配器。ECA 通过 stdin 传入 JSON（顶层键为 snake_case），并将钩子类型同时作为 argv 参数传入；该适配器将 ECA 事件映射为 CESP，并使用从 ECA `db_cache_path` 派生的稳定 `eca-` 会话前缀。最初由社区在 PeonPing/peon-ping#261 贡献，现已第一方内置。

向 ECA 配置添加指向该适配器的 shell 钩子，每个事件一个：

```json
{
  "hooks": {
    "sessionStart": [{ "actions": [{ "type": "shell", "command": "bash ~/.claude/hooks/peon-ping/adapters/eca.sh sessionStart" }] }],
    "preRequest":   [{ "actions": [{ "type": "shell", "command": "bash ~/.claude/hooks/peon-ping/adapters/eca.sh preRequest" }] }],
    "postRequest":  [{ "actions": [{ "type": "shell", "command": "bash ~/.claude/hooks/peon-ping/adapters/eca.sh postRequest" }] }],
    "preToolCall":  [{ "actions": [{ "type": "shell", "command": "bash ~/.claude/hooks/peon-ping/adapters/eca.sh preToolCall" }] }],
    "sessionEnd":   [{ "actions": [{ "type": "shell", "command": "bash ~/.claude/hooks/peon-ping/adapters/eca.sh sessionEnd" }] }]
  }
}
```

**事件映射：** `sessionStart`/`chatStart` → `SessionStart`，`preRequest` → `UserPromptSubmit`，`postRequest`/`subagentPostRequest`/`postToolCall` → `Stop`，`preToolCall` → 权限提示，`sessionEnd` → `SessionEnd`。

## 远程开发（SSH / Devcontainers / Codespaces）

在远程服务器或容器中编码？peon-ping 自动检测 SSH 会话、devcontainers 和 Codespaces，然后通过本地机器上运行的轻量级中继路由音频和通知。

### SSH 设置

1. **在本地机器上**，启动中继：
   ```bash
   peon relay --daemon
   ```

2. **带端口转发的 SSH**：
   ```bash
   ssh -R 19998:localhost:19998 your-server
   ```

3. **在远程安装 peon-ping** — 它会自动检测 SSH 会话并通过转发端口将音频请求发送回本地中继。

就这样。声音在你的笔记本电脑上播放，而不是远程服务器。

### Devcontainers / Codespaces

无需端口转发 — peon-ping 自动检测 `REMOTE_CONTAINERS` 和 `CODESPACES` 环境变量并将音频路由到 `host.docker.internal:19998`。只需在主机上运行 `peon relay --daemon`。

### 中继命令

```bash
peon relay                # 前台启动中继
peon relay --daemon       # 后台启动
peon relay --stop         # 停止后台中继
peon relay --status       # 检查中继是否运行
peon relay --port=12345   # 自定义端口（默认：19998）
peon relay --bind=0.0.0.0 # 监听所有接口（安全性较低）
```

环境变量：`PEON_RELAY_PORT`、`PEON_RELAY_HOST`、`PEON_RELAY_BIND`。

如果 peon-ping 检测到 SSH 或容器会话但无法连接中继，它会在 `SessionStart` 时打印设置说明。

### 基于分类的 API（用于轻量级远程钩子）

中继支持在服务器端处理声音选择的基于分类的端点。这对于未安装 peon-ping 的远程机器很有用 — 远程钩子只需发送分类名称，中继从活动语音包中随机选择声音。

**端点：**

| 端点 | 描述 |
|---|---|
| `GET /health` | 健康检查（返回 "OK"） |
| `GET /play?file=<path>` | 播放指定声音文件（旧版） |
| `GET /play?category=<cat>` | 播放分类中的随机声音（推荐） |
| `POST /notify` | 发送桌面通知 |

**远程钩子示例（`scripts/remote-hook.sh`）：**

```bash
#!/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 &
```

将其复制到远程机器并在 `~/.claude/settings.json` 中注册：

```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"}]
  }
}
```

中继从本地机器的 `config.json` 读取活动语音包和音量，加载语音包清单，并选择随机声音（避免重复）。

## 手机通知

当任务完成或需要关注时在手机上收到推送通知 — 当你离开桌面时很有用。

### 快速开始（ntfy.sh — 免费，无需账户）

1. 在手机上安装 [ntfy 应用](https://ntfy.sh)
2. 在应用中订阅一个唯一主题（例如 `my-peon-notifications`）
3. 运行：
   ```bash
   peon mobile ntfy my-peon-notifications
   ```

也支持 [Pushover](https://pushover.net) 和 [Telegram](https://core.telegram.org/bots)：

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

### 通知优先级（ntfy）

默认情况下，peon-ping 会根据事件类型推断 ntfy 优先级（权限请求为 `high`，常规事件为 `default`/`low`）。在 iOS 上，较低的优先级可能会静默送达（横幅显示但不播放声音）。设置一个明确的优先级可让提醒发出声音：

```bash
peon mobile ntfy my-peon-notifications --priority=max
```

也可以在 `config.json` 的 `mobile_notify` 中添加 `priority` 键：

```json
"mobile_notify": {
  "service": "ntfy",
  "topic": "my-peon-notifications",
  "server": "https://ntfy.sh",
  "priority": "max"
}
```

可接受的值为 ntfy 优先级名称（`max`/`urgent`、`high`、`default`、`low`、`min`）或数字 `1` 到 `5`。设置后会应用于每个事件，覆盖按事件类型推断的默认值；保持不设置则维持原有行为。同一个值也会映射到 Pushover 的优先级。

### 手机命令

```bash
peon mobile on            # 启用手机通知
peon mobile off           # 禁用手机通知
peon mobile status        # 显示当前配置
peon mobile test          # 发送测试通知
```

手机通知在每个事件时都会触发，无论窗口焦点 — 它们独立于桌面通知和声音。

## 语音包

160 多个语音包，涵盖魔兽争霸、星际争霸、红色警戒、传送门、塞尔达、Dota 2、绝地潜兵2、上古卷轴等。默认安装包含 5 个精选语音包：

| 语音包 | 角色 | 声音 |
|---|---|---|
| `peon`（默认） | 兽人苦工（魔兽争霸 III） | "Ready to work?", "Work, work.", "Okie dokie." |
| `peasant` | 人类农民（魔兽争霸 III） | "Yes, milord?", "Job's done!", "Ready, sir." |
| `sc_kerrigan` | 莎拉·凯瑞甘（星际争霸） | "I gotcha", "What now?", "Easily amused, huh?" |
| `sc_battlecruiser` | 战列巡航舰（星际争霸） | "Battlecruiser operational", "Make it happen", "Engage" |
| `glados` | GLaDOS（传送门） | "Oh, it's you.", "You monster.", "Your entire team is dead." |

**[浏览所有语音包并试听 &rarr; openpeon.com/packs](https://openpeon.com/packs)**

使用 `--all` 安装全部，或随时切换语音包：

```bash
peon packs use glados             # 切换到指定语音包
peon packs next                   # 切换到下一个语音包
peon packs list                   # 列出所有已安装语音包
peon packs list --registry        # 浏览所有可用语音包
peon packs install glados,murloc  # 安装指定语音包
peon packs install --all          # 安装注册表中所有语音包
```

想添加自己的语音包？参见 [openpeon.com/create 完整指南](https://openpeon.com/create) 或 [CONTRIBUTING.md](CONTRIBUTING.md)。

## 调试

当声音不播放或通知未出现时，结构化调试日志可帮助你精确追踪钩子调用期间发生的事情。

### 启用调试日志

```bash
peon debug on             # 启用 — 日志写入 ~/.claude/hooks/peon-ping/logs/
peon debug off            # 禁用
peon debug status         # 显示状态、日志目录、文件数和总大小
```

你也可以通过设置环境变量 `PEON_DEBUG=1` 在单次调用中启用调试日志，而无需更改配置。

### 查看日志

```bash
peon logs                 # 今天日志的最后 50 行
peon logs --last 100      # 所有日志文件的最后 100 行
peon logs --session <ID>  # 按会话 ID 过滤今天的日志
peon logs --session <ID> --all  # 搜索所有日志文件中的会话 ID
peon logs --clear         # 删除所有日志文件（需确认）
```

### 日志格式

每行日志是一条结构化的 key=value 记录：

```
2026-03-26T14:32:01.042 [config] inv=a3f1 loaded=/path/to/config.json volume=0.5 pack=peon enabled=True
2026-03-26T14:32:01.045 [event] inv=a3f1 hook_event=Stop cesp=task.complete session=abc123
2026-03-26T14:32:01.048 [sound] inv=a3f1 file=work-work.wav label="Work, work." category=task.complete
2026-03-26T14:32:01.120 [play] inv=a3f1 player=afplay file=work-work.wav
2026-03-26T14:32:01.125 [notify] inv=a3f1 title="peon: done" body="Work, work."
```

- **inv** -- 唯一的 4 字符调用 ID，链接单次钩子调用的所有阶段
- **阶段**：`[config]`、`[event]`、`[sound]`、`[play]`、`[notify]` -- 每个代表钩子管道中的一个阶段
- 包含空格或特殊字符的值会加引号

### 常见故障示例

| 症状 | 在日志中查找 |
|---|---|
| 没有声音播放 | `[event]` 行显示 `exit=early`（分类被禁用、已暂停或去抖动） |
| 错误的语音包 | `[config]` 行显示意外的 `pack=` 值 -- 检查 path_rules 或轮换设置 |
| 缺少声音文件 | `[sound]` 行显示 `error=` 和文件路径 |
| 通知缺失 | `[notify]` 行不存在 -- 检查配置中的 `desktop_notifications` |

### 配置键

| 键 | 默认值 | 说明 |
|---|---|---|
| `debug` | `false` | 启用结构化调试日志 |
| `debug_retention_days` | `7` | 自动清理超过 N 天的日志 |

日志存储在 `~/.claude/hooks/peon-ping/logs/peon-ping-YYYY-MM-DD.log`（每天一个文件）。旧日志会在新一天的日志创建时根据 `debug_retention_days` 自动清理。

## 卸载

**macOS/Linux：**

```bash
bash "${CLAUDE_CONFIG_DIR:-$HOME/.claude}"/hooks/peon-ping/uninstall.sh        # 全局
bash .claude/hooks/peon-ping/uninstall.sh           # 项目本地
```

**Windows (PowerShell)：**

```powershell
# 标准卸载（删除声音前会提示）
powershell -File "$env:USERPROFILE\.claude\hooks\peon-ping\uninstall.ps1"

# 保留语音包（移除其他所有内容）
powershell -File "$env:USERPROFILE\.claude\hooks\peon-ping\uninstall.ps1" -KeepSounds
```

## 系统要求

- **macOS** — `afplay`（内置），AppleScript 用于通知
- **Linux** — 以下之一：`pw-play`、`paplay`、`ffplay`、`mpv`、`play`（SoX）或 `aplay`；`notify-send` 用于通知
- **Windows** — 原生 PowerShell 带 `MediaPlayer` 和 WinForms（无需 WSL），或 WSL2
- **MSYS2 / Git Bash** — `python3`、`cygpath`（内置）；音频通过 `ffplay`/`mpv`/`play` 或 PowerShell 回退
- **所有平台** — `python3`（原生 Windows 不需要）
- **SSH/远程** — 远程主机上需要 `curl`
- **IDE** — 支持钩子的 Claude Code（或通过[适配器](#多-ide-支持)的任何支持的 IDE）

## 工作原理

`peon.sh` 是一个为 `SessionStart`、`SessionEnd`、`SubagentStart`、`UserPromptSubmit`、`Stop`、`Notification`、`PermissionRequest`、`PostToolUseFailure` 和 `PreCompact` 事件注册的 Claude Code 钩子。在每个事件：

1. **事件映射** — 嵌入的 Python 块将钩子事件映射到 [CESP](https://github.com/PeonPing/openpeon) 声音分类（`session.start`、`task.complete`、`input.required` 等）
2. **声音选择** — 从活动语音包清单中随机选择一个语音，避免重复
3. **音频播放** — 通过 `afplay`（macOS）、PowerShell `MediaPlayer`（WSL2/MSYS2 回退）或 `pw-play`/`paplay`/`ffplay`/`mpv`/`aplay`（Linux/MSYS2）异步播放声音
4. **通知** — 更新终端标签页标题，如果终端未获得焦点则发送桌面通知
5. **远程路由** — 在 SSH 会话、devcontainers 和 Codespaces 中，音频和通知请求通过 HTTP 转发到本地机器上的[中继服务器](#远程开发ssh--devcontainers--codespaces)

语音包在安装时从 [OpenPeon 注册表](https://github.com/PeonPing/registry)下载。官方语音包托管在 [PeonPing/og-packs](https://github.com/PeonPing/og-packs)。声音文件归各自发行商（Blizzard、Valve、EA 等）所有，根据合理使用原则分发用于个人通知目的。

## 链接

- [@peonping on X](https://x.com/peonping) — 更新和公告
- [peonping.com](https://peonping.com/) — 主页
- [openpeon.com](https://openpeon.com/) — CESP 规范、语音包浏览器、[集成指南](https://openpeon.com/integrate)、创建指南
- [OpenPeon 注册表](https://github.com/PeonPing/registry) — 语音包注册表（GitHub Pages）
- [og-packs](https://github.com/PeonPing/og-packs) — 官方语音包
- [peon-pet](https://github.com/PeonPing/peon-pet) — macOS 桌面宠物（兽人精灵，响应钩子事件）
- [许可证 (MIT)](LICENSE)

## 支持项目

- Venmo: [@garysheng](https://venmo.com/garysheng)
- 社区代币（DYOR / 仅供娱乐）：有人在 Base 上创建了 $PEON 代币 — 我们接收交易手续费，帮助资助开发。[`0xf4ba744229afb64e2571eef89aacec2f524e8ba3`](https://dexscreener.com/base/0xf4bA744229aFB64E2571eef89AaceC2F524e8bA3)
