#!/bin/bash
# peon-ping installer
# Works both via `curl | bash` (downloads from GitHub) and local clone
# Re-running updates core files; sounds are version-controlled in the repo
set -euo pipefail

LOCAL_MODE=false
INIT_LOCAL_CONFIG=false
INSTALL_ALL=false
CUSTOM_PACKS=""
OPENCLAW_MODE=false
KIMI_MODE=false
OPENPEON_MODE=false
NO_SHARED_PACKS=false
NO_RC=false
ROVODEV_ONLY=false
LANG_FILTER=""
for arg in "$@"; do
  case "$arg" in
    --global) LOCAL_MODE=false ;;
    --local) LOCAL_MODE=true ;;
    --openclaw) OPENCLAW_MODE=true ;;
    --kimi) KIMI_MODE=true ;;
    --openpeon) OPENPEON_MODE=true ;;
    --no-shared-packs) NO_SHARED_PACKS=true ;;
    --init-local-config) INIT_LOCAL_CONFIG=true ;;
    --all) INSTALL_ALL=true ;;
    --no-rc) NO_RC=true ;;
    --rovodev-only) ROVODEV_ONLY=true ;;
    --packs=*) CUSTOM_PACKS="${arg#--packs=}" ;;
    --lang=*) LANG_FILTER="${arg#--lang=}" ;;
    --help|-h)
      cat <<'HELPEOF'
Usage: install.sh [OPTIONS]

Options:
  --global             Install globally (default)
  --local              Install in current project (.claude)
  --openclaw           Install as OpenClaw skill (~/.openclaw/skills)
  --kimi               Install for Kimi Code only (~/.kimi/hooks/peon-ping;
                       no Claude config required). When ~/.claude/hooks/
                       peon-ping/packs exists, Kimi's packs/ is symlinked
                       to it so a single install serves both IDEs.
  --openpeon           Install under ~/.openpeon instead of ~/.claude (a
                       tool-agnostic root; peon.sh auto-discovers its packs via
                       the packs-anchored fallback when ~/.claude is absent).
  --no-shared-packs    Disable the --kimi pack symlink and download a
                       separate copy of packs into ~/.kimi/...
  --init-local-config  Create local config only, then exit
  --all                Install all packs
  --no-rc              Skip .bashrc/.zshrc/fish config modifications
  --rovodev-only       Only register Rovo Dev CLI hooks, then exit
  --packs=<a,b,c>      Install specific packs
  --lang=<en,fr,...>   Install only packs matching language(s)
HELPEOF
      exit 0
      ;;
  esac
done

GLOBAL_BASE="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
# --openpeon reroots the global install at a tool-agnostic ~/.openpeon so every
# downstream path (hooks, packs, settings.json, rovodev, config-preserve) lands
# under the same root. peon.sh's packs-anchored fallback auto-discovers it.
if [ "$OPENPEON_MODE" = true ]; then
  GLOBAL_BASE="$HOME/.openpeon"
fi
LOCAL_BASE="$PWD/.claude"
OPENCLAW_BASE="$HOME/.openclaw"
KIMI_BASE="$HOME/.kimi"

# --- Handle --rovodev-only mode (for homebrew delegation) ---
if [ "$ROVODEV_ONLY" = true ]; then
  ROVODEV_CONFIG="$HOME/.rovodev/config.yml"
  ROVODEV_CONFIG_YAML="$HOME/.rovodev/config.yaml"
  ROVODEV_ADAPTER="$GLOBAL_BASE/hooks/peon-ping/adapters/rovodev.sh"

  # Check both .yml and .yaml extensions
  if [ ! -f "$ROVODEV_CONFIG" ] && [ -f "$ROVODEV_CONFIG_YAML" ]; then
    ROVODEV_CONFIG="$ROVODEV_CONFIG_YAML"
  fi

  if [ -f "$ROVODEV_CONFIG" ]; then
    python3 -c "
import os, sys

config_path = '$ROVODEV_CONFIG'
adapter_path = '$ROVODEV_ADAPTER'

with open(config_path, 'r') as f:
    content = f.read()

peon_events = '''    - name: on_complete
      commands:
        - command: bash {adapter} on_complete
    - name: on_error
      commands:
        - command: bash {adapter} on_error
    - name: on_tool_permission
      commands:
        - command: bash {adapter} on_tool_permission
    - name: on_user_prompt
      commands:
        - command: bash {adapter} on_user_prompt
    - name: on_tool_start
      commands:
        - command: bash {adapter} on_tool_start
    - name: on_tool_end
      commands:
        - command: bash {adapter} on_tool_end
    - name: on_session_start
      commands:
        - command: bash {adapter} on_session_start
    - name: on_session_end
      commands:
        - command: bash {adapter} on_session_end
'''.format(adapter=adapter_path)

if 'eventHooks:' not in content and 'eventHooks :' not in content:
    # No eventHooks at all — append the whole block
    hooks_block = '''
eventHooks:
  events:
''' + peon_events
    with open(config_path, 'a') as f:
        f.write(hooks_block)
elif 'events: []' in content or 'events:[]' in content:
    # Empty events array — replace with our events
    import re
    content = re.sub(r'events:\s*\[\]', 'events:\n' + peon_events, content, count=1)
    with open(config_path, 'w') as f:
        f.write(content)
else:
    # eventHooks exists — add rovodev.sh command to existing events,
    # or create new event entries for events that don't exist yet
    lines = content.split('\n')
    in_event_hooks = False
    in_events = False
    name_indent = None
    cmd_indent = None

    # Map event names to their '- name:' line index and 'commands:' line index
    event_map = {}  # event_name -> {'name_idx': int, 'commands_idx': int, 'last_cmd_idx': int}
    current_event = None

    for i, line in enumerate(lines):
        stripped = line.lstrip()
        if stripped.startswith('eventHooks'):
            in_event_hooks = True
        elif in_event_hooks and stripped.startswith('events'):
            in_events = True
        elif in_events and stripped.startswith('- name:'):
            if name_indent is None:
                name_indent = len(line) - len(stripped)
            current_event = stripped.split('- name:')[1].strip()
            event_map[current_event] = {'name_idx': i, 'commands_idx': None, 'last_cmd_idx': None, 'has_peon_cmd': False}
        elif in_events and current_event and stripped.startswith('commands:'):
            event_map[current_event]['commands_idx'] = i
        elif in_events and current_event and stripped.startswith('- command:'):
            if cmd_indent is None:
                cmd_indent = len(line) - len(stripped)
            cmd_line_indent = len(line) - len(stripped)
            # Scan past YAML continuation lines (more deeply indented than the
            # '- command:' key and not starting a new YAML key at the same or
            # lesser indent).  This handles multi-line command values such as
            # osascript strings that wrap across lines.
            end = i
            for j in range(i + 1, len(lines)):
                cline = lines[j]
                if not cline or not cline.strip():
                    break
                cline_indent = len(cline) - len(cline.lstrip())
                if cline_indent <= cmd_line_indent:
                    break
                end = j
            event_map[current_event]['last_cmd_idx'] = end
            if 'rovodev.sh' in stripped:
                event_map[current_event]['has_peon_cmd'] = True
        elif in_events and line and not line.startswith(' ') and not line.startswith('\t'):
            break

    if name_indent is None:
        name_indent = 4
    if cmd_indent is None:
        cmd_indent = name_indent + 4

    # Event name mapping: rovodev event -> rovodev arg
    rovodev_events = {
        'on_complete': 'on_complete',
        'on_error': 'on_error',
        'on_tool_permission': 'on_tool_permission',
        'on_user_prompt': 'on_user_prompt',
        'on_tool_start': 'on_tool_start',
        'on_tool_end': 'on_tool_end',
        'on_session_start': 'on_session_start',
        'on_session_end': 'on_session_end',
    }

    pad_cmd = ' ' * cmd_indent
    new_cmd_template = pad_cmd + '- command: bash {adapter} {event}\n'

    # Process in reverse order so line indices stay valid after insertions
    changed = False
    inserted_events = set()
    for event_name, rovodev_arg in sorted(rovodev_events.items(), key=lambda x: event_map.get(x[0], {}).get('last_cmd_idx', 99999), reverse=True):
        if event_name in event_map and event_map[event_name]['last_cmd_idx'] is not None:
            if event_map[event_name].get('has_peon_cmd'):
                inserted_events.add(event_name)
                continue
            # Event exists — append command after the last '- command:' line
            insert_at = event_map[event_name]['last_cmd_idx'] + 1
            new_line = new_cmd_template.format(adapter=adapter_path, event=rovodev_arg).rstrip()
            lines.insert(insert_at, new_line)
            changed = True
            inserted_events.add(event_name)

    # Any events that didn't exist yet — append as new event entries at the end
    missing = [e for e in rovodev_events if e not in inserted_events]
    if missing:
        # Find insertion point: after the last event entry
        last_event_end = 0
        for ev_data in event_map.values():
            idx = ev_data.get('last_cmd_idx') or ev_data.get('commands_idx') or ev_data.get('name_idx', 0)
            if idx > last_event_end:
                last_event_end = idx
        # Account for lines already inserted above
        offset = len(inserted_events)
        insert_at = last_event_end + 1 + offset

        pad = ' ' * name_indent
        pad2 = ' ' * (name_indent + 2)
        pad3 = ' ' * cmd_indent
        new_entries = ''
        for ev in missing:
            new_entries += '{p}- name: {e}\n{p2}commands:\n{p3}- command: bash {a} {e}\n'.format(
                p=pad, p2=pad2, p3=pad3, e=ev, a=adapter_path)
        lines.insert(insert_at, new_entries.rstrip())
        changed = True

    if not changed:
        print('peon-ping hooks already present in Rovo Dev CLI config — skipping')
        sys.exit(0)

    with open(config_path, 'w') as f:
        f.write('\n'.join(lines))
    print('Missing Rovo Dev CLI event hooks added to ' + config_path)
    print('Restart Rovo Dev CLI for hooks to take effect.')
    sys.exit(0)

print('Rovo Dev CLI event hooks registered in ' + config_path)
print('Restart Rovo Dev CLI for hooks to take effect.')
"
  elif [ -d "$HOME/.rovodev" ]; then
    # Directory exists but no config file — create one with just eventHooks
    cat > "$ROVODEV_CONFIG" <<ROVOEOF
eventHooks:
  events:
    - name: on_complete
      commands:
        - command: bash $ROVODEV_ADAPTER on_complete
    - name: on_error
      commands:
        - command: bash $ROVODEV_ADAPTER on_error
    - name: on_tool_permission
      commands:
        - command: bash $ROVODEV_ADAPTER on_tool_permission
    - name: on_user_prompt
      commands:
        - command: bash $ROVODEV_ADAPTER on_user_prompt
    - name: on_tool_start
      commands:
        - command: bash $ROVODEV_ADAPTER on_tool_start
    - name: on_tool_end
      commands:
        - command: bash $ROVODEV_ADAPTER on_tool_end
    - name: on_session_start
      commands:
        - command: bash $ROVODEV_ADAPTER on_session_start
    - name: on_session_end
      commands:
        - command: bash $ROVODEV_ADAPTER on_session_end
ROVOEOF
    echo "Rovo Dev CLI event hooks created at $ROVODEV_CONFIG"
    echo "Restart Rovo Dev CLI for hooks to take effect."
  else
    echo "Error: ~/.rovodev directory not found" >&2
    echo "For manual setup, see: https://github.com/PeonPing/peon-ping#rovo-dev-cli-setup"
    exit 1
  fi
  exit 0
fi

# Respect no_rc from config.json if --no-rc wasn't passed on CLI
if [ "$NO_RC" = false ]; then
  for _cfg in "$GLOBAL_BASE/hooks/peon-ping/config.json" "$LOCAL_BASE/hooks/peon-ping/config.json"; do
    if [ -f "$_cfg" ]; then
      _cfg_py="$_cfg"; command -v cygpath &>/dev/null && _cfg_py="$(cygpath -m "$_cfg")"
      _no_rc=$(python3 -c "import json; print(json.load(open('$_cfg_py')).get('no_rc', False))" 2>/dev/null)
      if [ "$_no_rc" = "True" ]; then
        NO_RC=true
      fi
      break
    fi
  done
fi

# Auto-detect OpenClaw if present and Claude Code is not
if [ "$OPENCLAW_MODE" = false ] && [ "$KIMI_MODE" = false ] && [ "$LOCAL_MODE" = false ] && [ "$OPENPEON_MODE" = false ]; then
  if [ -d "$OPENCLAW_BASE" ] && [ ! -d "$GLOBAL_BASE" ]; then
    OPENCLAW_MODE=true
    echo "Auto-detected OpenClaw installation (no Claude Code found)."
  fi
fi

# Auto-detect Kimi Code if present and Claude Code/OpenClaw are not
if [ "$OPENCLAW_MODE" = false ] && [ "$KIMI_MODE" = false ] && [ "$LOCAL_MODE" = false ] && [ "$OPENPEON_MODE" = false ]; then
  if [ -d "$KIMI_BASE" ] && [ ! -d "$GLOBAL_BASE" ]; then
    KIMI_MODE=true
    echo "Auto-detected Kimi Code installation (no Claude Code found)."
  fi
fi

if [ "$KIMI_MODE" = true ]; then
  BASE_DIR="$KIMI_BASE"
  INSTALL_DIR="$BASE_DIR/hooks/peon-ping"
  SETTINGS=""  # Kimi reads events via wire.jsonl; no settings.json hook write
elif [ "$OPENCLAW_MODE" = true ]; then
  BASE_DIR="$OPENCLAW_BASE"
  INSTALL_DIR="$BASE_DIR/hooks/peon-ping"
  SETTINGS=""  # OpenClaw doesn't use settings.json for hooks
elif [ "$LOCAL_MODE" = true ]; then
  BASE_DIR="$LOCAL_BASE"
else
  BASE_DIR="$GLOBAL_BASE"
fi
if [ "$OPENCLAW_MODE" = false ] && [ "$KIMI_MODE" = false ]; then
  INSTALL_DIR="$BASE_DIR/hooks/peon-ping"
  SETTINGS="$BASE_DIR/settings.json"
fi
REPO_BASE="https://raw.githubusercontent.com/PeonPing/peon-ping/main"
REGISTRY_URL="https://peonping.github.io/registry/index.json"

if [ "$INIT_LOCAL_CONFIG" = true ]; then
  LOCAL_CONFIG_DIR="$LOCAL_BASE/hooks/peon-ping"
  LOCAL_CONFIG_FILE="$LOCAL_CONFIG_DIR/config.json"
  mkdir -p "$LOCAL_CONFIG_DIR"
  if [ -f "$LOCAL_CONFIG_FILE" ]; then
    echo "Local config already exists: $LOCAL_CONFIG_FILE"
    exit 0
  fi
  if [ -f "$GLOBAL_BASE/hooks/peon-ping/config.json" ]; then
    cp "$GLOBAL_BASE/hooks/peon-ping/config.json" "$LOCAL_CONFIG_FILE"
  elif [ -n "${BASH_SOURCE[0]:-}" ] && [ "${BASH_SOURCE[0]}" != "bash" ]; then
    CANDIDATE="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
    if [ -f "$CANDIDATE/config.json" ]; then
      cp "$CANDIDATE/config.json" "$LOCAL_CONFIG_FILE"
    else
      curl -fsSL "$REPO_BASE/config.json" -o "$LOCAL_CONFIG_FILE"
    fi
  else
    curl -fsSL "$REPO_BASE/config.json" -o "$LOCAL_CONFIG_FILE"
  fi
  echo "Created local config: $LOCAL_CONFIG_FILE"
  exit 0
fi

# Default packs (curated English set installed by default)
DEFAULT_PACKS="peon peasant sc_scv sc_battlecruiser glados"


# --- Platform detection ---
detect_platform() {
  case "$(uname -s)" in
    Darwin)
      if [ -n "${SSH_CONNECTION:-}" ] || [ -n "${SSH_CLIENT:-}" ]; then
        echo "ssh"
      else
        echo "mac"
      fi ;;
    Linux)
      # Check for devcontainer/Docker BEFORE checking for WSL
      # (devcontainers on WSL2 have both indicators)
      if [ "${REMOTE_CONTAINERS:-}" = "true" ] || [ "${CODESPACES:-}" = "true" ] || [ -f /.dockerenv ]; then
        echo "devcontainer"
      elif grep -qi microsoft /proc/version 2>/dev/null; then
        echo "wsl"
      elif [ -n "${SSH_CONNECTION:-}" ] || [ -n "${SSH_CLIENT:-}" ]; then
        echo "ssh"
      else
        echo "linux"
      fi ;;
    MSYS_NT*|MINGW*) echo "msys2" ;;
    *) echo "unknown" ;;
  esac
}
PLATFORM=$(detect_platform)

# MSYS2/MinGW: Windows Python can't read /c/... paths — convert to C:/... via cygpath
# Also set PYTHONUTF8=1 to avoid cp932/cp1252 codec errors when settings.json contains Unicode
py_path() {
  if [ "$PLATFORM" = "msys2" ]; then
    cygpath -m "$1"
  else
    printf '%s' "$1"
  fi
}
if [ "$PLATFORM" = "msys2" ]; then
  export PYTHONUTF8=1
fi

# --- Detect update vs fresh install ---
UPDATING=false
if [ -f "$INSTALL_DIR/peon.sh" ]; then
  UPDATING=true
fi

if [ "$UPDATING" = true ]; then
  echo "=== peon-ping updater ==="
  echo ""
  echo "Existing install found. Updating..."
else
  echo "=== peon-ping installer ==="
  echo ""
fi

# --- Prerequisites ---
if [ "$PLATFORM" != "mac" ] && [ "$PLATFORM" != "wsl" ] && [ "$PLATFORM" != "linux" ] && [ "$PLATFORM" != "devcontainer" ] && [ "$PLATFORM" != "ssh" ] && [ "$PLATFORM" != "msys2" ]; then
  echo "Error: peon-ping requires macOS, Linux, WSL, MSYS2, SSH, or a devcontainer"
  exit 1
fi

if ! command -v python3 &>/dev/null; then
  echo "Error: python3 is required"
  exit 1
fi

if [ "$PLATFORM" = "mac" ]; then
  if ! command -v afplay &>/dev/null; then
    echo "Error: afplay is required (should be built into macOS)"
    exit 1
  fi
elif [ "$PLATFORM" = "wsl" ]; then
  if ! command -v powershell.exe &>/dev/null; then
    echo "Error: powershell.exe is required (should be available in WSL)"
    exit 1
  fi
  if ! command -v wslpath &>/dev/null; then
    echo "Error: wslpath is required (should be built into WSL)"
    exit 1
  fi
elif [ "$PLATFORM" = "devcontainer" ]; then
  echo "Devcontainer detected. Audio will play through the relay on your host."
  echo "Run 'peon relay' on your host machine after installation."
  if ! command -v curl &>/dev/null; then
    echo "Warning: curl not found. Install curl for relay audio playback."
  fi
elif [ "$PLATFORM" = "ssh" ]; then
  echo "SSH session detected. Audio will play through the relay on your local machine."
  echo "After install:"
  echo "  1. On your LOCAL machine, run: peon relay --daemon"
  echo "  2. Reconnect with: ssh -R 19998:localhost:19998 <host>"
  if ! command -v curl &>/dev/null; then
    echo "Warning: curl not found. Install curl for relay audio playback."
  fi
elif [ "$PLATFORM" = "linux" ]; then
  LINUX_PLAYER=""
  for cmd in pw-play paplay ffplay mpv aplay; do
    if command -v "$cmd" &>/dev/null; then
      LINUX_PLAYER="$cmd"
      break
    fi
  done
  if [ -z "$LINUX_PLAYER" ]; then
    echo "Error: no supported audio player found."
    echo "Install one of: pw-play (pipewire-audio) paplay (pulseaudio-utils), ffplay (ffmpeg), mpv, aplay (alsa-utils)"
    exit 1
  fi
  echo "Audio player: $LINUX_PLAYER"
  if command -v notify-send &>/dev/null; then
    echo "Desktop notifications: notify-send"
  else
    echo "Warning: notify-send not found (libnotify-bin). Desktop notifications will be disabled."
  fi
elif [ "$PLATFORM" = "msys2" ]; then
  if ! command -v python3 &>/dev/null; then
    echo "Error: python3 is required"
    exit 1
  fi
  if ! command -v cygpath &>/dev/null; then
    echo "Error: cygpath is required (should be built into MSYS2/Git Bash)"
    exit 1
  fi
  MSYS2_PLAYER=""
  for cmd in ffplay mpv play; do
    if command -v "$cmd" &>/dev/null; then
      MSYS2_PLAYER="$cmd"
      break
    fi
  done
  if [ -n "$MSYS2_PLAYER" ]; then
    echo "Audio player: $MSYS2_PLAYER"
  else
    echo "Audio: PowerShell MediaPlayer fallback (native players like ffplay/mpv preferred for lower latency)"
  fi
fi

if [ ! -d "$BASE_DIR" ]; then
  if [ "$LOCAL_MODE" = true ]; then
    echo "Error: .claude/ not found in current directory. Is this a Claude Code project?"
    exit 1
  else
    # ~/.claude doesn't exist yet — create it so peon-ping has a home.
    # This is normal when using peon-ping with non-Claude-Code editors
    # (e.g. GitHub Copilot, Cursor) where ~/.claude was never created.
    echo "Creating $BASE_DIR..."
    mkdir -p "$BASE_DIR"
  fi
fi

remove_existing_install() {
  local target_base="$1"
  local target_type="$2"
  local target_install="$target_base/hooks/peon-ping"
  local target_settings="$target_base/settings.json"

  rm -rf "$target_install"
  if [ -f "$target_settings" ]; then
    python3 -c "
import json
import os

path = '$(py_path "$target_settings")'
try:
    with open(path) as f:
        settings = json.load(f)
except Exception:
    settings = {}

hooks = settings.get('hooks', {})
changed = False
for event, entries in list(hooks.items()):
    filtered = []
    for entry in entries:
        subhooks = entry.get('hooks', [])
        keep = True
        for h in subhooks:
            cmd = h.get('command', '')
            if 'peon-ping/peon.sh' in cmd:
                keep = False
                break
        if keep:
            filtered.append(entry)
    if len(filtered) != len(entries):
        hooks[event] = filtered
        changed = True

if changed:
    settings['hooks'] = hooks
    with open(path, 'w') as f:
        json.dump(settings, f, indent=2)
        f.write('\n')
" 2>/dev/null || true
  fi
  echo "Removed $target_type installation."
}

if [ "$LOCAL_MODE" = true ] && [ "$GLOBAL_BASE" != "$LOCAL_BASE" ] && [ -f "$GLOBAL_BASE/hooks/peon-ping/peon.sh" ]; then
  echo ""
  echo "Global installation already exists at $GLOBAL_BASE/hooks/peon-ping"
  if [ -t 0 ]; then
    read -p "Remove global installation and continue local install? (y/N): " -n 1 -r
    echo
    if [[ "$REPLY" =~ ^[Yy]$ ]]; then
      remove_existing_install "$GLOBAL_BASE" "global"
    else
      echo "Aborted."
      exit 0
    fi
  else
    echo "Non-interactive session detected; keeping existing global installation."
  fi
fi

if [ "$LOCAL_MODE" = false ] && [ "$GLOBAL_BASE" != "$LOCAL_BASE" ] && [ -f "$LOCAL_BASE/hooks/peon-ping/peon.sh" ]; then
  echo ""
  echo "Local installation already exists at $LOCAL_BASE/hooks/peon-ping"
  if [ -t 0 ]; then
    read -p "Remove local installation and continue global install? (y/N): " -n 1 -r
    echo
    if [[ "$REPLY" =~ ^[Yy]$ ]]; then
      remove_existing_install "$LOCAL_BASE" "local"
    else
      echo "Aborted."
      exit 0
    fi
  else
    echo "Non-interactive session detected; keeping existing local installation."
  fi
fi

# --- Detect if running from local clone or curl|bash ---
SCRIPT_DIR=""
if [ -n "${BASH_SOURCE[0]:-}" ] && [ "${BASH_SOURCE[0]}" != "bash" ]; then
  CANDIDATE="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
  if [ -f "$CANDIDATE/peon.sh" ]; then
    SCRIPT_DIR="$CANDIDATE"
  fi
fi

# --- Python-safe path variants (MSYS2 Windows Python needs C:/... not /c/...) ---
INSTALL_DIR_PY="$(py_path "$INSTALL_DIR")"
GLOBAL_BASE_PY="$(py_path "$GLOBAL_BASE")"
LOCAL_BASE_PY="$(py_path "$LOCAL_BASE")"

# --- Install/update core tool files ---
mkdir -p "$INSTALL_DIR"

if [ -n "$SCRIPT_DIR" ]; then
  # Local clone — copy core tool files
  cp "$SCRIPT_DIR/peon.sh" "$INSTALL_DIR/"
  cp "$SCRIPT_DIR/relay.sh" "$INSTALL_DIR/"
  cp "$SCRIPT_DIR/completions.bash" "$INSTALL_DIR/"
  cp "$SCRIPT_DIR/completions.fish" "$INSTALL_DIR/"
  cp "$SCRIPT_DIR/VERSION" "$INSTALL_DIR/"
  cp "$SCRIPT_DIR/uninstall.sh" "$INSTALL_DIR/"
  if [ -d "$SCRIPT_DIR/adapters" ]; then
    mkdir -p "$INSTALL_DIR/adapters"
    cp "$SCRIPT_DIR/adapters/"*.sh "$INSTALL_DIR/adapters/" 2>/dev/null || true
  fi
  if [ -d "$SCRIPT_DIR/scripts" ]; then
    mkdir -p "$INSTALL_DIR/scripts"
    cp "$SCRIPT_DIR/scripts/"*.sh "$INSTALL_DIR/scripts/" 2>/dev/null || true
    cp "$SCRIPT_DIR/scripts/"*.ps1 "$INSTALL_DIR/scripts/" 2>/dev/null || true
    cp "$SCRIPT_DIR/scripts/"*.swift "$INSTALL_DIR/scripts/" 2>/dev/null || true
    cp "$SCRIPT_DIR/scripts/"*.js "$INSTALL_DIR/scripts/" 2>/dev/null || true
  fi
  if [ -f "$SCRIPT_DIR/docs/peon-icon.png" ]; then
    mkdir -p "$INSTALL_DIR/docs"
    cp "$SCRIPT_DIR/docs/peon-icon.png" "$INSTALL_DIR/docs/"
  fi
  if [ "$UPDATING" = false ]; then
    cp "$SCRIPT_DIR/config.json" "$INSTALL_DIR/"
  fi
else
  # curl|bash — download core tool files from GitHub
  echo "Downloading from GitHub..."
  curl -fsSL "$REPO_BASE/peon.sh" -o "$INSTALL_DIR/peon.sh"
  curl -fsSL "$REPO_BASE/relay.sh" -o "$INSTALL_DIR/relay.sh"
  curl -fsSL "$REPO_BASE/completions.bash" -o "$INSTALL_DIR/completions.bash"
  curl -fsSL "$REPO_BASE/completions.fish" -o "$INSTALL_DIR/completions.fish"
  curl -fsSL "$REPO_BASE/VERSION" -o "$INSTALL_DIR/VERSION"
  curl -fsSL "$REPO_BASE/uninstall.sh" -o "$INSTALL_DIR/uninstall.sh"
  mkdir -p "$INSTALL_DIR/adapters"
  curl -fsSL "$REPO_BASE/adapters/codex.sh" -o "$INSTALL_DIR/adapters/codex.sh" 2>/dev/null || true
  curl -fsSL "$REPO_BASE/adapters/cursor.sh" -o "$INSTALL_DIR/adapters/cursor.sh" 2>/dev/null || true
  curl -fsSL "$REPO_BASE/adapters/kiro.sh" -o "$INSTALL_DIR/adapters/kiro.sh" 2>/dev/null || true
  curl -fsSL "$REPO_BASE/adapters/antigravity.sh" -o "$INSTALL_DIR/adapters/antigravity.sh" 2>/dev/null || true
  curl -fsSL "$REPO_BASE/adapters/gemini.sh" -o "$INSTALL_DIR/adapters/gemini.sh" 2>/dev/null || true
  curl -fsSL "$REPO_BASE/adapters/openclaw.sh" -o "$INSTALL_DIR/adapters/openclaw.sh" 2>/dev/null || true
  curl -fsSL "$REPO_BASE/adapters/opencode.sh" -o "$INSTALL_DIR/adapters/opencode.sh" 2>/dev/null || true
  curl -fsSL "$REPO_BASE/adapters/windsurf.sh" -o "$INSTALL_DIR/adapters/windsurf.sh" 2>/dev/null || true
  curl -fsSL "$REPO_BASE/adapters/rovodev.sh" -o "$INSTALL_DIR/adapters/rovodev.sh" 2>/dev/null || true
  curl -fsSL "$REPO_BASE/adapters/kimi.sh" -o "$INSTALL_DIR/adapters/kimi.sh" 2>/dev/null || true
  curl -fsSL "$REPO_BASE/adapters/deepagents.sh" -o "$INSTALL_DIR/adapters/deepagents.sh" 2>/dev/null || true
  curl -fsSL "$REPO_BASE/adapters/qwen.sh" -o "$INSTALL_DIR/adapters/qwen.sh" 2>/dev/null || true
  curl -fsSL "$REPO_BASE/adapters/iflow.sh" -o "$INSTALL_DIR/adapters/iflow.sh" 2>/dev/null || true
  curl -fsSL "$REPO_BASE/adapters/trae.sh" -o "$INSTALL_DIR/adapters/trae.sh" 2>/dev/null || true
  curl -fsSL "$REPO_BASE/adapters/kiro-ide.sh" -o "$INSTALL_DIR/adapters/kiro-ide.sh" 2>/dev/null || true
  curl -fsSL "$REPO_BASE/adapters/eca.sh" -o "$INSTALL_DIR/adapters/eca.sh" 2>/dev/null || true
  mkdir -p "$INSTALL_DIR/scripts"
  curl -fsSL "$REPO_BASE/scripts/hook-handle-use.sh" -o "$INSTALL_DIR/scripts/hook-handle-use.sh" 2>/dev/null || true
  curl -fsSL "$REPO_BASE/scripts/hook-handle-use.ps1" -o "$INSTALL_DIR/scripts/hook-handle-use.ps1" 2>/dev/null || true
  curl -fsSL "$REPO_BASE/scripts/win-play.ps1" -o "$INSTALL_DIR/scripts/win-play.ps1" 2>/dev/null || true
  curl -fsSL "$REPO_BASE/scripts/hook-handle-rename.sh" -o "$INSTALL_DIR/scripts/hook-handle-rename.sh" 2>/dev/null || true
  curl -fsSL "$REPO_BASE/scripts/pack-download.sh" -o "$INSTALL_DIR/scripts/pack-download.sh" 2>/dev/null || true
  curl -fsSL "$REPO_BASE/scripts/mac-overlay.js" -o "$INSTALL_DIR/scripts/mac-overlay.js" 2>/dev/null || true
  curl -fsSL "$REPO_BASE/scripts/notify.sh" -o "$INSTALL_DIR/scripts/notify.sh" 2>/dev/null || true
  curl -fsSL "$REPO_BASE/scripts/cmux-focus.sh" -o "$INSTALL_DIR/scripts/cmux-focus.sh" 2>/dev/null || true
  curl -fsSL "$REPO_BASE/scripts/cmux-status-presentation.sh" -o "$INSTALL_DIR/scripts/cmux-status-presentation.sh" 2>/dev/null || true
  curl -fsSL "$REPO_BASE/scripts/cmux-workspace-field.sh" -o "$INSTALL_DIR/scripts/cmux-workspace-field.sh" 2>/dev/null || true
  curl -fsSL "$REPO_BASE/scripts/tts-native.sh" -o "$INSTALL_DIR/scripts/tts-native.sh" 2>/dev/null || true
  mkdir -p "$INSTALL_DIR/docs"
  curl -fsSL "$REPO_BASE/docs/peon-icon.png" -o "$INSTALL_DIR/docs/peon-icon.png" 2>/dev/null || true
  if [ "$UPDATING" = false ]; then
    curl -fsSL "$REPO_BASE/config.json" -o "$INSTALL_DIR/config.json"
  fi
fi

# --- Backfill new config keys on update ---
# Merge any new keys from the default config template into the user's
# existing config without overwriting their values.
if [ "$UPDATING" = true ] && [ -f "$INSTALL_DIR/config.json" ]; then
  # Determine the source of default config
  if [ -n "$SCRIPT_DIR" ]; then
    DEFAULT_CFG="$SCRIPT_DIR/config.json"
  else
    DEFAULT_CFG=$(mktemp)
    curl -fsSL "$REPO_BASE/config.json" -o "$DEFAULT_CFG" 2>/dev/null || true
  fi
  if [ -f "$DEFAULT_CFG" ]; then
    python3 -c "
import json, sys

try:
    with open('$(py_path "$DEFAULT_CFG")') as f:
        defaults = json.load(f)
    with open('$INSTALL_DIR_PY/config.json') as f:
        user_cfg = json.load(f)
except Exception:
    sys.exit(0)

changed = False
for key, value in defaults.items():
    if key not in user_cfg:
        user_cfg[key] = value
        changed = True

if changed:
    with open('$INSTALL_DIR/config.json', 'w') as f:
        json.dump(user_cfg, f, indent=2)
        f.write('\n')
    print('Config updated with new defaults')
" 2>/dev/null || true
    # Clean up temp file if we downloaded one
    [ -z "$SCRIPT_DIR" ] && rm -f "$DEFAULT_CFG"
  fi
fi

# --- Persist --no-rc preference to config ---
if [ "$NO_RC" = true ] && [ -f "$INSTALL_DIR/config.json" ]; then
  python3 -c "
import json
path = '$INSTALL_DIR_PY/config.json'
with open(path) as f:
    cfg = json.load(f)
if not cfg.get('no_rc', False):
    cfg['no_rc'] = True
    with open(path, 'w') as f:
        json.dump(cfg, f, indent=2)
        f.write('\n')
" 2>/dev/null || true
fi

# --- Auto-share packs with Claude install (--kimi only) ---
# When installing for Kimi alongside an existing Claude install, symlink
# packs/ at Claude's so a single download serves both IDEs. Skipped when:
#   - --no-shared-packs is set (explicit opt-out)
#   - the user requested specific packs (--packs= or --all): they have
#     explicit pack intent that may not match Claude's set
#   - $INSTALL_DIR/packs already exists as a real directory (preserve any
#     local packs from a prior install)
#   - Claude's packs/ is missing or empty (nothing to share)
SHARED_PACKS_LINKED=false
if [ "$KIMI_MODE" = true ] \
   && [ "$NO_SHARED_PACKS" = false ] \
   && [ -z "$CUSTOM_PACKS" ] \
   && [ "$INSTALL_ALL" = false ]; then
  CLAUDE_PACKS_DIR="$GLOBAL_BASE/hooks/peon-ping/packs"
  KIMI_PACKS_LINK="$INSTALL_DIR/packs"
  if [ -d "$CLAUDE_PACKS_DIR" ] && [ -n "$(ls -A "$CLAUDE_PACKS_DIR" 2>/dev/null || true)" ]; then
    if [ -L "$KIMI_PACKS_LINK" ] || [ ! -e "$KIMI_PACKS_LINK" ]; then
      rm -f "$KIMI_PACKS_LINK"
      ln -s "$CLAUDE_PACKS_DIR" "$KIMI_PACKS_LINK"
      echo "Linked packs/ -> $CLAUDE_PACKS_DIR (sharing with Claude install)"
      echo "Pass --no-shared-packs to download a separate set."
      SHARED_PACKS_LINKED=true
    fi
  fi
fi

# --- Download sound packs via shared engine (skipped when symlinked above) ---
if [ "$SHARED_PACKS_LINKED" = false ]; then
  PACK_DL="$INSTALL_DIR/scripts/pack-download.sh"
  chmod +x "$PACK_DL" 2>/dev/null || true

  LANG_ARG=""
  [ -n "$LANG_FILTER" ] && LANG_ARG="--lang=$LANG_FILTER"

  if [ -n "$CUSTOM_PACKS" ]; then
    bash "$PACK_DL" --dir="$INSTALL_DIR" --packs="$CUSTOM_PACKS" $LANG_ARG
  elif [ "$INSTALL_ALL" = true ]; then
    bash "$PACK_DL" --dir="$INSTALL_DIR" --all $LANG_ARG
  else
    bash "$PACK_DL" --dir="$INSTALL_DIR" --packs="$(echo "$DEFAULT_PACKS" | tr ' ' ',')" $LANG_ARG
  fi
fi

chmod +x "$INSTALL_DIR/peon.sh"
chmod +x "$INSTALL_DIR/relay.sh"
chmod +x "$INSTALL_DIR/scripts/hook-handle-use.sh" 2>/dev/null || true
chmod +x "$INSTALL_DIR/scripts/hook-handle-rename.sh" 2>/dev/null || true
chmod +x "$INSTALL_DIR/scripts/pack-download.sh" 2>/dev/null || true
chmod +x "$INSTALL_DIR/scripts/notify.sh" 2>/dev/null || true
chmod +x "$INSTALL_DIR/scripts/cmux-focus.sh" 2>/dev/null || true
chmod +x "$INSTALL_DIR/scripts/cmux-status-presentation.sh" 2>/dev/null || true
chmod +x "$INSTALL_DIR/scripts/cmux-workspace-field.sh" 2>/dev/null || true
chmod +x "$INSTALL_DIR/scripts/tts-native.sh" 2>/dev/null || true

# --- Build peon-play (macOS Sound Effects device support) ---
if [ "$PLATFORM" = "mac" ] && command -v swiftc &>/dev/null; then
  PEON_PLAY_SRC="$INSTALL_DIR/scripts/peon-play.swift"
  if [ ! -f "$PEON_PLAY_SRC" ] && [ -z "$SCRIPT_DIR" ]; then
    curl -fsSL "$REPO_BASE/scripts/peon-play.swift" -o "$PEON_PLAY_SRC" 2>/dev/null || true
  fi
  if [ -f "$PEON_PLAY_SRC" ]; then
    echo "Building peon-play (Sound Effects device support)..."
    swiftc -O -o "$INSTALL_DIR/scripts/peon-play" \
      "$PEON_PLAY_SRC" \
      -framework AVFoundation -framework CoreAudio -framework AudioToolbox 2>/dev/null \
      && echo "  peon-play built successfully" \
      || echo "  Warning: could not build peon-play, using afplay fallback"
  fi
fi

# --- Build meeting-detect (macOS mic-in-use detection) ---
if [ "$PLATFORM" = "mac" ] && command -v swiftc &>/dev/null; then
  MEETING_DETECT_SRC="$INSTALL_DIR/scripts/meeting-detect.swift"
  if [ ! -f "$MEETING_DETECT_SRC" ] && [ -z "$SCRIPT_DIR" ]; then
    curl -fsSL "$REPO_BASE/scripts/meeting-detect.swift" -o "$MEETING_DETECT_SRC" 2>/dev/null || true
  fi
  if [ -f "$MEETING_DETECT_SRC" ]; then
    echo "Building meeting-detect (mic-in-use detection)..."
    swiftc -O -o "$INSTALL_DIR/scripts/meeting-detect" \
      "$MEETING_DETECT_SRC" \
      -framework CoreAudio 2>/dev/null \
      && echo "  meeting-detect built successfully" \
      || echo "  Warning: could not build meeting-detect, using process-based fallback"
  fi
fi

# --- Install skills (slash commands) ---
# Skills ship with paths anchored to ~/.claude/hooks/peon-ping/ via the
# CLAUDE_CONFIG_DIR/HOME fallback. For --local (project-scoped) and --kimi
# (Kimi-direct) installs the runtime install dir isn't ~/.claude, so we
# rewrite SKILL.md to use the absolute install dir. Without this the
# slash-command would silently toggle/configure the wrong (or missing)
# install.
rewrite_skill_paths() {
  local skill_md="$1"
  [ -f "$skill_md" ] || return 0
  if [ "$LOCAL_MODE" = false ] && [ "$KIMI_MODE" = false ]; then
    return 0
  fi
  # Order matters: rewrite the longest patterns first so partial matches
  # don't strand a stray "/hooks/peon-ping/" suffix.
  sed -i.bak \
    -e 's|"${CLAUDE_CONFIG_DIR:-\$HOME/\.claude}"/hooks/peon-ping|"'"$INSTALL_DIR"'"|g' \
    -e 's|${CLAUDE_CONFIG_DIR:-\$HOME/\.claude}/hooks/peon-ping|'"$INSTALL_DIR"'|g' \
    -e 's|~/\.claude/hooks/peon-ping|'"$INSTALL_DIR"'|g' \
    "$skill_md"
  rm -f "${skill_md}.bak"
}

install_skill() {
  local skill_name="$1"
  local skill_dir="$BASE_DIR/skills/$skill_name"
  mkdir -p "$skill_dir"
  if [ -n "$SCRIPT_DIR" ] && [ -d "$SCRIPT_DIR/skills/$skill_name" ]; then
    cp "$SCRIPT_DIR/skills/$skill_name/SKILL.md" "$skill_dir/"
    rewrite_skill_paths "$skill_dir/SKILL.md"
  elif [ -z "$SCRIPT_DIR" ]; then
    curl -fsSL "$REPO_BASE/skills/$skill_name/SKILL.md" -o "$skill_dir/SKILL.md"
    rewrite_skill_paths "$skill_dir/SKILL.md"
  else
    echo "Warning: skills/$skill_name not found in local clone, skipping skill install"
  fi
}

install_skill peon-ping-toggle
install_skill peon-ping-config
install_skill peon-ping-use
install_skill peon-ping-log

# --- Install trainer voice packs ---
TRAINER_DIR="$INSTALL_DIR/trainer"
mkdir -p "$TRAINER_DIR/sounds"
if [ -n "$SCRIPT_DIR" ] && [ -d "$SCRIPT_DIR/trainer" ]; then
  cp "$SCRIPT_DIR/trainer/manifest.json" "$TRAINER_DIR/"
  for subdir in "$SCRIPT_DIR/trainer/sounds/"*/; do
    [ -d "$subdir" ] || continue
    dirname=$(basename "$subdir")
    mkdir -p "$TRAINER_DIR/sounds/$dirname"
    cp "$subdir"*.mp3 "$TRAINER_DIR/sounds/$dirname/" 2>/dev/null || true
  done
  echo "Trainer voice packs installed."
elif [ -z "$SCRIPT_DIR" ]; then
  curl -fsSL "$REPO_BASE/trainer/manifest.json" -o "$TRAINER_DIR/manifest.json"
  # Parse manifest to download all trainer sounds
  python3 -c "
import json, sys
m = json.load(open('$(py_path "$TRAINER_DIR")/manifest.json'))
for cat in m.values():
    for s in cat:
        print(s['file'])
" | while read -r sfile; do
    mkdir -p "$TRAINER_DIR/$(dirname "$sfile")"
    curl -fsSL "$REPO_BASE/trainer/$sfile" -o "$TRAINER_DIR/$sfile" 2>/dev/null || true
  done
  echo "Trainer voice packs installed."
else
  echo "Warning: trainer/ not found in local clone, skipping trainer install"
fi

# --- Add shell alias (global install only, unless --no-rc) ---
if [ "$LOCAL_MODE" = false ] && [ "$NO_RC" = false ]; then
  ALIAS_LINE="alias peon=\"bash $INSTALL_DIR/peon.sh\""
  for rcfile in "$HOME/.zshrc" "$HOME/.bashrc"; do
    if [ -f "$rcfile" ] && [ -w "$rcfile" ] && ! grep -qF 'alias peon=' "$rcfile"; then
      echo "" >> "$rcfile"
      echo "# peon-ping quick controls" >> "$rcfile"
      echo "$ALIAS_LINE" >> "$rcfile"
      echo "Added peon alias to $(basename "$rcfile")"
    elif [ -f "$rcfile" ] && [ ! -w "$rcfile" ]; then
      echo "Warning: $(basename "$rcfile") is not writable, skipping alias" >&2
    fi
  done

  # --- Add tab completion ---
  COMPLETION_LINE="[ -f $INSTALL_DIR/completions.bash ] && source $INSTALL_DIR/completions.bash"
  for rcfile in "$HOME/.zshrc" "$HOME/.bashrc"; do
    if [ -f "$rcfile" ] && [ -w "$rcfile" ] && ! grep -qF 'peon-ping/completions.bash' "$rcfile"; then
      echo "$COMPLETION_LINE" >> "$rcfile"
      echo "Added tab completion to $(basename "$rcfile")"
    fi
  done
fi

# --- Add fish shell function + completions ---
if [ "$NO_RC" = false ]; then
  FISH_CONFIG="$HOME/.config/fish/config.fish"
  if [ -f "$FISH_CONFIG" ] && [ -w "$FISH_CONFIG" ]; then
    FISH_FUNC="function peon; bash $INSTALL_DIR/peon.sh \$argv; end"
    if ! grep -qF 'function peon' "$FISH_CONFIG"; then
      echo "" >> "$FISH_CONFIG"
      echo "# peon-ping quick controls" >> "$FISH_CONFIG"
      echo "$FISH_FUNC" >> "$FISH_CONFIG"
      echo "Added peon function to config.fish"
    fi
  elif [ -f "$FISH_CONFIG" ] && [ ! -w "$FISH_CONFIG" ]; then
    echo "Warning: config.fish is not writable, skipping fish function" >&2
  fi
  FISH_COMPLETIONS_DIR="$HOME/.config/fish/completions"
  if [ -d "$HOME/.config/fish" ]; then
    mkdir -p "$FISH_COMPLETIONS_DIR"
    cp "$INSTALL_DIR/completions.fish" "$FISH_COMPLETIONS_DIR/peon.fish"
    echo "Installed fish completions to $FISH_COMPLETIONS_DIR/peon.fish"
  fi
fi

# --- Install PATH shim (global install) ---
# Ensure `peon` works immediately even when shell rc files are not modified,
# missing, or not yet reloaded in the current terminal session.
if [ "$LOCAL_MODE" = false ]; then
  USER_BIN="$HOME/.local/bin"
  USER_SHIM="$USER_BIN/peon"
  mkdir -p "$USER_BIN"
  ln -sf "$INSTALL_DIR/peon.sh" "$USER_SHIM"
  chmod +x "$INSTALL_DIR/peon.sh" "$USER_SHIM" 2>/dev/null || true
  if command -v peon >/dev/null 2>&1; then
    echo "Installed peon command at $USER_SHIM"
  else
    echo "Installed peon command at $USER_SHIM"
    echo "Note: add $USER_BIN to PATH if 'peon' is not found in new terminals."
  fi
fi

# --- Verify sounds are installed ---
if [ -n "$CUSTOM_PACKS" ]; then
  VERIFY_PACKS=$(echo "$CUSTOM_PACKS" | tr ',' ' ')
elif [ "$INSTALL_ALL" = true ]; then
  VERIFY_PACKS=""
  for _d in "$INSTALL_DIR/packs"/*/; do
    [ -d "$_d" ] && VERIFY_PACKS="$VERIFY_PACKS $(basename "$_d")"
  done
else
  VERIFY_PACKS="$DEFAULT_PACKS"
fi
echo ""
for pack in $VERIFY_PACKS; do
  sound_dir="$INSTALL_DIR/packs/$pack/sounds"
  sound_count=$({ ls "$sound_dir"/*.wav "$sound_dir"/*.mp3 "$sound_dir"/*.ogg 2>/dev/null || true; } | wc -l | tr -d ' ')
  if [ "$sound_count" -eq 0 ]; then
    echo "[$pack] Warning: No sound files found!"
  else
    echo "[$pack] $sound_count sound files installed."
  fi
done

# --- Backup existing notify.sh (global fresh install only) ---
if [ "$LOCAL_MODE" = false ] && [ "$UPDATING" = false ]; then
  NOTIFY_SH="$BASE_DIR/hooks/notify.sh"
  if [ -f "$NOTIFY_SH" ]; then
    cp "$NOTIFY_SH" "$NOTIFY_SH.backup"
    echo ""
    echo "Backed up notify.sh → notify.sh.backup"
  fi
fi

# --- OpenClaw skill installation ---
if [ "$OPENCLAW_MODE" = true ]; then
  echo ""
  echo "Installing OpenClaw skill..."

  OC_SKILL_DIR="$OPENCLAW_BASE/skills/peon-ping"
  mkdir -p "$OC_SKILL_DIR"

  cat > "$OC_SKILL_DIR/SKILL.md" <<'OCSKILL'
# peon-ping — Sound Notifications for OpenClaw

Play audio notifications when your OpenClaw agent completes tasks, encounters errors, or needs input.

## Usage

The adapter translates OpenClaw events into peon-ping sounds:

```bash
# Play a sound for an event
bash ~/.openclaw/hooks/peon-ping/adapters/openclaw.sh task.complete
bash ~/.openclaw/hooks/peon-ping/adapters/openclaw.sh task.error
bash ~/.openclaw/hooks/peon-ping/adapters/openclaw.sh input.required
bash ~/.openclaw/hooks/peon-ping/adapters/openclaw.sh session.start
```

## Controls

```bash
# Toggle sounds on/off
peon toggle

# Check status
peon status

# Switch sound pack
peon use <pack_name>

# List available packs
peon list
```

## OpenClaw Integration

Add to your agent's workflow by calling the adapter after key events:
- Sub-agent completion → `task.complete`
- Build/deploy errors → `task.error`
- Permission needed → `input.required`
- Session start → `session.start`

## Config

Edit `~/.openclaw/hooks/peon-ping/config.json` to change volume, active pack, or toggle categories.
OCSKILL

  echo "OpenClaw skill installed at $OC_SKILL_DIR/SKILL.md"

  # Copy the OpenClaw adapter
  if [ -f "$INSTALL_DIR/adapters/openclaw.sh" ]; then
    chmod +x "$INSTALL_DIR/adapters/openclaw.sh"
    echo "OpenClaw adapter ready at $INSTALL_DIR/adapters/openclaw.sh"
  fi

  echo ""
  echo "=== OpenClaw Installation complete! ==="
  echo ""
  echo "Config: $INSTALL_DIR/config.json"
  echo "Skill:  $OC_SKILL_DIR/SKILL.md"
  echo ""
  echo "Quick controls:"
  echo "  peon toggle        — toggle sounds"
  echo "  peon status        — check if sounds are paused"
  echo "  peon use <pack>    — switch sound pack"
  echo ""
  echo "Usage in your agent:"
  echo "  bash $INSTALL_DIR/adapters/openclaw.sh task.complete"
  echo ""
  echo "Ready to work!"
  exit 0
fi

# --- Kimi-only install ---
# Skip every Claude-specific step below: settings.json hook write, Cursor /
# Rovo / DeepAgents hook registration, other-scope cleanup. The Kimi adapter
# is a watcher daemon that reads wire.jsonl and pipes CESP events to peon.sh,
# so it needs the install dir but no hook configuration.
if [ "$KIMI_MODE" = true ]; then
  echo ""
  echo "Starting Kimi Code adapter..."

  if [ -f "$INSTALL_DIR/adapters/kimi.sh" ]; then
    chmod +x "$INSTALL_DIR/adapters/kimi.sh"
    # Pass CLAUDE_PEON_DIR explicitly so the adapter resolves into the Kimi
    # install dir even though it isn't under ~/.claude. The adapter writes this
    # into its LaunchAgent plist on macOS so the env survives reboot.
    CLAUDE_PEON_DIR="$INSTALL_DIR" bash "$INSTALL_DIR/adapters/kimi.sh" --install || true
  else
    echo "Warning: $INSTALL_DIR/adapters/kimi.sh missing — skipping daemon start."
  fi

  # Initialize state for fresh installs (mirrors the post-summary block below)
  if [ "$UPDATING" = false ]; then
    echo '{}' > "$INSTALL_DIR/.state.json"
  fi

  echo ""
  if [ "$UPDATING" = true ]; then
    echo "=== Update complete! ==="
  else
    echo "=== Kimi Code installation complete! ==="
  fi
  echo ""
  echo "Install dir: $INSTALL_DIR"
  echo "Config:      $INSTALL_DIR/config.json"
  echo "Sessions:    $KIMI_BASE/sessions"
  echo ""
  echo "Daemon controls:"
  echo "  bash $INSTALL_DIR/adapters/kimi.sh --status"
  echo "  bash $INSTALL_DIR/adapters/kimi.sh --uninstall"
  echo ""
  echo "Quick controls:"
  echo "  CLAUDE_PEON_DIR=\"$INSTALL_DIR\" peon toggle    — toggle sounds"
  echo "  CLAUDE_PEON_DIR=\"$INSTALL_DIR\" peon use <pack> — switch sound pack"
  echo ""
  echo "Ready to work!"
  exit 0
fi

# --- Update settings.json ---
# Use BASE_DIR so --local installs register hooks in the project-level
# settings.json, while global installs use ~/.claude/settings.json.
# All paths are absolute either way (BASE_DIR is already absolute).
echo ""
echo "Updating Claude Code hooks in settings.json..."

if [ "$LOCAL_MODE" = true ]; then
  HOOK_CMD="$BASE_DIR/hooks/peon-ping/peon.sh"
  HOOK_SETTINGS="$BASE_DIR/settings.json"
else
  HOOK_CMD="$GLOBAL_BASE/hooks/peon-ping/peon.sh"
  HOOK_SETTINGS="$GLOBAL_BASE/settings.json"
fi

python3 -c "
import json, os, sys

settings_path = '$(py_path "$HOOK_SETTINGS")'
hook_cmd = '$(py_path "$HOOK_CMD")'
install_dir = '$(py_path "$INSTALL_DIR")'
_home = os.path.expanduser('~')
# peon's own notify.sh is either bundled under the peon-ping dir or the legacy
# <base>/hooks/notify.sh one level above it. Match absolute and ~-relative
# forms so we catch the path however the stored command spells it.
_legacy_notify = os.path.join(os.path.dirname(install_dir), 'notify.sh')
def _path_markers(p):
    m = [p]
    if p.startswith(_home):
        m.append('~' + p[len(_home):])
    return m
_peon_notify_markers = _path_markers(install_dir) + _path_markers(_legacy_notify)

def _is_peon_hook(cmd):
    # Only strip hooks peon installed. 'notify.sh' is a generic name other
    # tools register too (e.g. ~/.deckard/hooks/notify.sh), so qualify it to
    # peon's own copy; sibling tools' notify.sh hooks must survive.
    if 'peon.sh' in cmd:
        return True
    if 'notify.sh' in cmd:
        return any(m in cmd for m in _peon_notify_markers)
    return False

# Load existing settings
if os.path.exists(settings_path):
    with open(settings_path) as f:
        settings = json.load(f)
else:
    settings = {}

hooks = settings.setdefault('hooks', {})

# Preserve existing command path if it resolves to the installed file
installed = os.path.realpath(hook_cmd)
for entries in hooks.values():
    for entry in entries:
        for hk in entry.get('hooks', []):
            cmd = hk.get('command', '')
            if 'peon-ping/' in cmd and cmd.endswith('/peon.sh'):
                resolved = os.path.realpath(os.path.expanduser(cmd))
                if resolved == installed:
                    hook_cmd = cmd
                break

peon_hook_sync = {
    'type': 'command',
    'command': hook_cmd,
    'timeout': 10
}
peon_hook_async = {
    'type': 'command',
    'command': hook_cmd,
    'timeout': 10,
    'async': True
}

# SessionStart runs sync so stderr messages (update notice, pause status,
# relay guidance) appear immediately. All other events run async.
sync_events = ('SessionStart',)
events = ['SessionStart', 'SessionEnd', 'SubagentStart', 'SubagentStop', 'UserPromptSubmit', 'Stop', 'Notification', 'PermissionRequest', 'PreToolUse', 'PostToolUseFailure', 'PreCompact']

# PostToolUseFailure only triggers on Bash failures — use matcher to limit scope
bash_only_events = ('PostToolUseFailure',)
# PreCompact supports manual|auto matchers — empty matcher fires for both

for event in events:
    hook = peon_hook_sync if event in sync_events else peon_hook_async
    if event in bash_only_events:
        peon_entry = dict(matcher='Bash', hooks=[hook])
    else:
        peon_entry = dict(matcher='', hooks=[hook])
    event_hooks = hooks.get(event, [])
    # Strip only peon.sh/notify.sh hooks from each matcher entry; keep sibling
    # hooks that users registered alongside ours. Drop the matcher entry only
    # if its hooks list is emptied out.
    cleaned = []
    for h in event_hooks:
        h = dict(h)
        h['hooks'] = [
            hk for hk in h.get('hooks', [])
            if not _is_peon_hook(hk.get('command', ''))
        ]
        if h['hooks']:
            cleaned.append(h)
    event_hooks = cleaned
    event_hooks.append(peon_entry)
    hooks[event] = event_hooks

settings['hooks'] = hooks

with open(settings_path, 'w') as f:
    json.dump(settings, f, indent=2)
    f.write('\n')

print('Hooks registered for: ' + ', '.join(events))
"

# Register UserPromptSubmit hooks for /peon-ping-use and /peon-ping-rename commands
# (Claude Code uses UserPromptSubmit; Cursor uses beforeSubmitPrompt — see below)
BEFORE_SUBMIT_HOOK="$GLOBAL_BASE/hooks/peon-ping/scripts/hook-handle-use.sh"
RENAME_HOOK="$GLOBAL_BASE/hooks/peon-ping/scripts/hook-handle-rename.sh"

python3 -c "
import json, os, sys

settings_path = '$(py_path "$HOOK_SETTINGS")'
hook_cmd = '$(py_path "$BEFORE_SUBMIT_HOOK")'
rename_cmd = '$(py_path "$RENAME_HOOK")'

# Load existing settings
if os.path.exists(settings_path):
    with open(settings_path) as f:
        settings = json.load(f)
else:
    settings = {}

hooks = settings.setdefault('hooks', {})

# Preserve existing command path if it resolves to the installed file
installed_use = os.path.realpath(hook_cmd)
installed_rename = os.path.realpath(rename_cmd)
for entries in hooks.values():
    for entry in entries:
        for hk in entry.get('hooks', []):
            cmd = hk.get('command', '')
            if 'peon-ping/' in cmd and '/hook-handle-use' in cmd:
                if os.path.realpath(os.path.expanduser(cmd)) == installed_use:
                    hook_cmd = cmd
            if 'peon-ping/' in cmd and '/hook-handle-rename' in cmd:
                if os.path.realpath(os.path.expanduser(cmd)) == installed_rename:
                    rename_cmd = cmd

# Create UserPromptSubmit hook entries for command handlers
before_submit_entry = {
    'matcher': '',
    'hooks': [
        {'type': 'command', 'command': hook_cmd, 'timeout': 5},
        {'type': 'command', 'command': rename_cmd, 'timeout': 5},
    ]
}

# Register under UserPromptSubmit (valid Claude Code event)
event_hooks = hooks.get('UserPromptSubmit', [])
# Strip only hook-handle-use/rename hooks from each matcher entry; keep
# sibling hooks (including peon.sh and any user-registered hooks). Drop the
# matcher entry only if its hooks list is emptied out.
cleaned = []
for h in event_hooks:
    h = dict(h)
    h['hooks'] = [
        hk for hk in h.get('hooks', [])
        if 'hook-handle-use' not in hk.get('command', '')
        and 'hook-handle-rename' not in hk.get('command', '')
    ]
    if h['hooks']:
        cleaned.append(h)
event_hooks = cleaned
event_hooks.append(before_submit_entry)
hooks['UserPromptSubmit'] = event_hooks

# Clean up stale beforeSubmitPrompt key if present (was incorrectly registered before)
if 'beforeSubmitPrompt' in hooks:
    del hooks['beforeSubmitPrompt']

settings['hooks'] = hooks

with open(settings_path, 'w') as f:
    json.dump(settings, f, indent=2)
    f.write('\n')

print('UserPromptSubmit hooks registered for /peon-ping-use and /peon-ping-rename commands')
"

# Register beforeSubmitPrompt hook for Cursor IDE if ~/.cursor exists
CURSOR_DIR="$HOME/.cursor"
CURSOR_HOOKS_FILE="$CURSOR_DIR/hooks.json"
CURSOR_HOOK_CMD="$GLOBAL_BASE/hooks/peon-ping/scripts/hook-handle-use.sh"
CURSOR_RENAME_CMD="$GLOBAL_BASE/hooks/peon-ping/scripts/hook-handle-rename.sh"

if [ -d "$CURSOR_DIR" ]; then
  echo ""
  echo "Detected Cursor IDE installation, registering hooks..."

  python3 -c "
import json, os

hooks_file = '$(py_path "$CURSOR_HOOKS_FILE")'
hook_cmd = '$(py_path "$CURSOR_HOOK_CMD")'
rename_cmd = '$(py_path "$CURSOR_RENAME_CMD")'

# Load or create hooks.json
if os.path.exists(hooks_file):
    with open(hooks_file) as f:
        data = json.load(f)
else:
    data = {'version': 1, 'hooks': {}}

# Ensure version and hooks structure
if 'version' not in data:
    data['version'] = 1
if 'hooks' not in data:
    data['hooks'] = {}

hooks = data['hooks']

# Preserve existing command paths if they resolve to the installed files
installed_use = os.path.realpath(hook_cmd)
installed_rename = os.path.realpath(rename_cmd)
def _find_existing(hooks_data, suffix):
    if isinstance(hooks_data, list):
        for h in hooks_data:
            cmd = h.get('command', '')
            if 'peon-ping/' in cmd and cmd.endswith(suffix):
                yield cmd
    elif isinstance(hooks_data, dict):
        for entries in hooks_data.values():
            for h in (entries if isinstance(entries, list) else []):
                cmd = h.get('command', '')
                if 'peon-ping/' in cmd and cmd.endswith(suffix):
                    yield cmd

for cmd in _find_existing(hooks, '/hook-handle-use'):
    if os.path.realpath(os.path.expanduser(cmd)) == installed_use:
        hook_cmd = cmd
        break

for cmd in _find_existing(hooks, '/hook-handle-rename'):
    if os.path.realpath(os.path.expanduser(cmd)) == installed_rename:
        rename_cmd = cmd
        break

# Handle both flat-array format [{event, command}] and dict format {event: [{command}]}
if isinstance(hooks, list):
    # Flat array format: remove existing peon-ping entries for this event
    hooks = [
        h for h in hooks
        if not (h.get('event') == 'beforeSubmitPrompt' and 'peon-ping/' in h.get('command', ''))
    ]
    hooks.append({'event': 'beforeSubmitPrompt', 'command': hook_cmd, 'timeout': 5})
    hooks.append({'event': 'beforeSubmitPrompt', 'command': rename_cmd, 'timeout': 5})
else:
    # Dict format
    event_hooks = hooks.get('beforeSubmitPrompt', [])
    event_hooks = [
        h for h in event_hooks
        if 'peon-ping' not in h.get('command', '')
    ]
    event_hooks.append({'command': hook_cmd, 'timeout': 5})
    event_hooks.append({'command': rename_cmd, 'timeout': 5})
    hooks['beforeSubmitPrompt'] = event_hooks

data['hooks'] = hooks

# Ensure directory exists
os.makedirs(os.path.dirname(hooks_file), exist_ok=True)

with open(hooks_file, 'w') as f:
    json.dump(data, f, indent=2)
    f.write('\n')

print('Cursor beforeSubmitPrompt hooks registered for /peon-ping-use and /peon-ping-rename commands')
"
fi

# --- Register GitHub Copilot CLI hooks if ~/.copilot exists ---
# Wires user-level hooks at ~/.copilot/hooks/peon-ping.json pointing
# directly at peon.sh with PascalCase event names. PascalCase tells the
# CLI to deliver the VS Code-compatible (snake_case) payload that
# peon.sh reads natively, bypassing the per-repo adapter entirely.
COPILOT_DIR="$HOME/.copilot"
COPILOT_HOOKS_DIR="$COPILOT_DIR/hooks"
COPILOT_HOOKS_FILE="$COPILOT_HOOKS_DIR/peon-ping.json"
COPILOT_PEON_SCRIPT="$GLOBAL_BASE/hooks/peon-ping/peon.sh"

if [ -d "$COPILOT_DIR" ]; then
  echo ""
  echo "Detected GitHub Copilot CLI installation, registering hooks..."

  mkdir -p "$COPILOT_HOOKS_DIR"

  # postToolUse is intentionally omitted: peon.sh has no PostToolUse
  # handler and routing it through Stop floods the debounce window.
  python3 -c "
import json

hooks_file = '$(py_path "$COPILOT_HOOKS_FILE")'
peon_script = '$(py_path "$COPILOT_PEON_SCRIPT")'

events = [
    'SessionStart', 'SessionEnd', 'SubagentStart', 'Stop',
    'Notification', 'PermissionRequest', 'PreToolUse',
    'PostToolUseFailure', 'PreCompact',
]

hooks = {}
for evt in events:
    hooks[evt] = [{
        'type': 'command',
        'bash': 'bash ' + peon_script,
        'timeoutSec': 10,
    }]

with open(hooks_file, 'w') as f:
    json.dump({'version': 1, 'hooks': hooks}, f, indent=2)
    f.write('\n')

print('Copilot CLI hooks registered for: ' + ', '.join(events))
"
fi

# --- Register event hooks for Rovo Dev CLI if ~/.rovodev exists ---
if [ -d "$HOME/.rovodev" ]; then
  echo ""
  echo "Detected Rovo Dev CLI installation, registering event hooks..."
  # Re-invoke ourselves with --rovodev-only to handle config registration
  # This avoids duplicating the YAML manipulation logic. Propagate --openpeon
  # so the child reroots GLOBAL_BASE and registers the rovodev adapter under
  # the same ~/.openpeon root rather than reverting to ~/.claude.
  if [ "$OPENPEON_MODE" = true ]; then
    bash "$0" --rovodev-only --openpeon 2>/dev/null || true
  else
    bash "$0" --rovodev-only 2>/dev/null || true
  fi
fi

# --- Auto-detect Kimi Code CLI and start watcher daemon ---
KIMI_DIR="$HOME/.kimi"
if [ -d "$KIMI_DIR" ]; then
  echo ""
  echo "Detected Kimi Code CLI installation, starting adapter..."
  bash "$INSTALL_DIR/adapters/kimi.sh" --install
fi

# --- Auto-detect deepagents-cli and register hooks ---
DEEPAGENTS_DIR="$HOME/.deepagents"
DEEPAGENTS_HOOKS_FILE="$DEEPAGENTS_DIR/hooks.json"

if [ -d "$DEEPAGENTS_DIR" ]; then
  echo ""
  echo "Detected deepagents-cli installation, registering hooks..."

  python3 -c "
import json, os

hooks_file = '$(py_path "$DEEPAGENTS_HOOKS_FILE")'
adapter_cmd = '$(py_path "$INSTALL_DIR/adapters/deepagents.sh")'

# Load or create hooks.json
if os.path.exists(hooks_file):
    with open(hooks_file) as f:
        data = json.load(f)
else:
    data = {}

if 'hooks' not in data:
    data['hooks'] = []

# Remove existing peon-ping entries
data['hooks'] = [
    h for h in data['hooks']
    if not any('peon-ping' in str(c) for c in (h.get('command') or []))
]

# Add new entry
data['hooks'].append({
    'command': ['bash', adapter_cmd],
    'events': ['session.start', 'session.end', 'task.complete', 'input.required', 'task.error', 'tool.error', 'user.prompt', 'permission.request', 'compact']
})

os.makedirs(os.path.dirname(hooks_file), exist_ok=True)
with open(hooks_file, 'w') as f:
    json.dump(data, f, indent=2)
    f.write('\n')

events = ['session.start', 'session.end', 'task.complete', 'input.required', 'task.error', 'tool.error', 'user.prompt', 'permission.request', 'compact']
print('  Hooks registered for: ' + ', '.join(events))
"
fi

# --- Remove peon-ping hooks from the OTHER settings scope to prevent doubles ---
# Global installs clean stale project-level hooks; local installs clean stale
# global hooks. Skip when both scopes resolve to the same file.
if [ "$LOCAL_MODE" = true ]; then
  OTHER_SETTINGS="$GLOBAL_BASE/settings.json"
else
  OTHER_SETTINGS="$LOCAL_BASE/settings.json"
fi

if [ "$OTHER_SETTINGS" != "$HOOK_SETTINGS" ] && [ -f "$OTHER_SETTINGS" ]; then
  python3 -c "
import json, os

path = '$(py_path "$OTHER_SETTINGS")'
try:
    with open(path) as f:
        settings = json.load(f)
except Exception:
    exit(0)

hooks = settings.get('hooks', {})
changed = False
for event, entries in list(hooks.items()):
    filtered = [
        e for e in entries
        if not any('peon-ping/' in h.get('command', '') for h in e.get('hooks', []))
    ]
    if len(filtered) != len(entries):
        hooks[event] = filtered
        changed = True

if changed:
    settings['hooks'] = hooks
    with open(path, 'w') as f:
        json.dump(settings, f, indent=2)
        f.write('\n')
    print('Removed duplicate peon-ping hooks from ' + path)
" 2>/dev/null || true
fi

# --- Initialize state (fresh install only) ---
if [ "$UPDATING" = false ]; then
  echo '{}' > "$INSTALL_DIR/.state.json"
fi

# --- Test sound ---
echo ""
if [ "$PLATFORM" = "devcontainer" ]; then
  echo "Skipping test sound (devcontainer — start relay on host to test)"
  echo "  Host: peon relay"
  echo "  Test: curl -sf http://host.docker.internal:19998/health"
elif [ "$PLATFORM" = "ssh" ]; then
  echo "Skipping test sound (SSH — start relay on your local machine to test)"
  echo "  Local: peon relay --daemon"
  echo "  SSH:   ssh -R 19998:localhost:19998 <host>"
  echo "  Test:  curl -sf http://localhost:19998/health"
else
  echo "Testing sound..."
  ACTIVE_PACK=$(python3 -c "
import json
try:
    c = json.load(open('$INSTALL_DIR_PY/config.json'))
    print(c.get('default_pack', c.get('active_pack', 'peon')))
except Exception:
    print('peon')
" 2>/dev/null)
  PACK_DIR="$INSTALL_DIR/packs/$ACTIVE_PACK"
  TEST_SOUND=$({ ls "$PACK_DIR/sounds/"*.wav "$PACK_DIR/sounds/"*.mp3 "$PACK_DIR/sounds/"*.ogg 2>/dev/null || true; } | head -1)
  if [ -n "$TEST_SOUND" ]; then
    if [ "$PLATFORM" = "mac" ]; then
      USE_SFX=$(python3 -c "
import json
try:
    c = json.load(open('$INSTALL_DIR_PY/config.json'))
    print(str(c.get('use_sound_effects_device', True)).lower())
except Exception:
    print('true')
" 2>/dev/null)
      if [ -x "$INSTALL_DIR/scripts/peon-play" ] && [ "$USE_SFX" != "false" ]; then
        "$INSTALL_DIR/scripts/peon-play" -v 0.3 "$TEST_SOUND"
      else
        afplay -v 0.3 "$TEST_SOUND"
      fi
    elif [ "$PLATFORM" = "wsl" ]; then
      wpath=$(wslpath -w "$TEST_SOUND")
      # Convert backslashes to forward slashes for file:/// URI
      wpath="${wpath//\\//}"
      powershell.exe -NoProfile -NonInteractive -Command "
        Add-Type -AssemblyName PresentationCore
        \$p = New-Object System.Windows.Media.MediaPlayer
        \$p.Open([Uri]::new('file:///$wpath'))
        \$p.Volume = 0.3
        Start-Sleep -Milliseconds 200
        \$p.Play()
        Start-Sleep -Seconds 3
        \$p.Close()
      " 2>/dev/null
    elif [ "$PLATFORM" = "linux" ]; then
      if command -v pw-play &>/dev/null; then
        LC_ALL=C pw-play --media-role=Notification --volume=0.3 "$TEST_SOUND" 2>/dev/null
      elif command -v paplay &>/dev/null; then
        paplay --volume="$(python3 -c "print(int(0.3 * 65536))")" "$TEST_SOUND" 2>/dev/null
      elif command -v ffplay &>/dev/null; then
        ffplay -nodisp -autoexit -volume 30 "$TEST_SOUND" 2>/dev/null
      elif command -v mpv &>/dev/null; then
        mpv --no-video --volume=30 "$TEST_SOUND" 2>/dev/null
      elif command -v aplay &>/dev/null; then
        if [[ "$TEST_SOUND" == *.wav ]]; then
          aplay -q "$TEST_SOUND" 2>/dev/null
        else
          echo "Warning: aplay found but test sound is not WAV. Install pw-play, paplay, ffplay, mpv, or play (SoX)."
        fi
      fi
    elif [ "$PLATFORM" = "msys2" ]; then
      if command -v ffplay &>/dev/null; then
        ffplay -nodisp -autoexit -volume 30 "$TEST_SOUND" 2>/dev/null
      elif command -v mpv &>/dev/null; then
        mpv --no-video --volume=30 "$TEST_SOUND" 2>/dev/null
      elif command -v play &>/dev/null; then
        play -v 0.3 "$TEST_SOUND" 2>/dev/null
      else
        wpath=$(cygpath -w "$TEST_SOUND")
        powershell.exe -NoProfile -NonInteractive -File "$(cygpath -w "$INSTALL_DIR/scripts/win-play.ps1")" -path "$wpath" -vol 0.3 2>/dev/null
      fi
    fi
    echo "Sound working!"
  else
    echo "Warning: No sound files found. Sounds may not play."
  fi
fi

echo ""
if [ "$UPDATING" = true ]; then
  echo "=== Update complete! ==="
  echo ""
  echo "Updated: peon.sh, sound packs"
  echo "Preserved: config.json, state"
else
  echo "=== Installation complete! ==="
  echo ""
  echo "Config: $INSTALL_DIR/config.json"
  echo "  - Adjust volume, toggle categories, switch packs"
  echo ""
  echo "Uninstall: bash $INSTALL_DIR/uninstall.sh"
fi
echo ""
echo "Quick controls:"
echo "  /peon-ping-toggle  — toggle sounds in Claude Code"
if [ "$LOCAL_MODE" = false ]; then
  echo "  peon toggle        — toggle sounds from any terminal"
  echo "  peon status        — check if sounds are paused"
fi
echo ""
echo "Ready to work! (run 'peon toggle' to mute)"
