Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 35 additions & 145 deletions adapters/opencode.sh
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
#!/bin/bash
# peon-ping adapter for OpenCode
# Installs the peon-ping CESP v1.0 TypeScript plugin for OpenCode
# Installs the thin TypeScript adapter that routes events through peon.sh.
#
# OpenCode uses a TypeScript plugin system (not shell hooks), so this
# adapter is an install script rather than a runtime event translator.
# Requires peon-ping installed first:
# brew install PeonPing/tap/peon-ping
# # or: curl -fsSL peonping.com/install | bash
#
# Install:
# Install this adapter:
# bash adapters/opencode.sh
#
# Or directly:
Expand All @@ -16,28 +17,13 @@

set -euo pipefail

# --- Config ---
PLUGIN_URL="https://raw.githubusercontent.com/PeonPing/peon-ping/main/adapters/opencode/peon-ping.ts"
REGISTRY_URL="https://peonping.github.io/registry/index.json"
DEFAULT_PACK="peon"

OPENCODE_PLUGINS_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/opencode/plugins"
PEON_CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/opencode/peon-ping"
PACKS_DIR="$HOME/.openpeon/packs"

is_safe_source_repo() {
[[ "$1" =~ ^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$ ]]
}

is_safe_source_ref() {
[[ "$1" =~ ^[A-Za-z0-9._/-]+$ ]] && [[ "$1" != *".."* ]] && [[ "$1" != /* ]]
}
PEON_SH_CANDIDATES=(
"$HOME/.claude/hooks/peon-ping/peon.sh"
"$HOME/.openclaw/hooks/peon-ping/peon.sh"
)

is_safe_source_path() {
[[ "$1" =~ ^[A-Za-z0-9._/-]+$ ]] && [[ "$1" != *".."* ]] && [[ "$1" != /* ]]
}

# --- Colors ---
BOLD=$'\033[1m' DIM=$'\033[2m' RED=$'\033[31m' GREEN=$'\033[32m' YELLOW=$'\033[33m' RESET=$'\033[0m'

info() { printf "%s>%s %s\n" "$GREEN" "$RESET" "$*"; }
Expand All @@ -46,145 +32,49 @@ error() { printf "%sx%s %s\n" "$RED" "$RESET" "$*" >&2; }

# --- Uninstall ---
if [ "${1:-}" = "--uninstall" ]; then
info "Uninstalling peon-ping from OpenCode..."
info "Uninstalling peon-ping adapter from OpenCode..."
rm -f "$OPENCODE_PLUGINS_DIR/peon-ping.ts"
rm -rf "$PEON_CONFIG_DIR"
info "Plugin and config removed."
info "Sound packs in $PACKS_DIR were preserved (shared with other adapters)."
info "To remove packs too: rm -rf $PACKS_DIR"
info "Adapter removed."
exit 0
fi

# --- Preflight ---
info "Installing peon-ping for OpenCode..."
# --- Preflight: find peon.sh ---
PEON_SH=""
for candidate in "${PEON_SH_CANDIDATES[@]}"; do
if [ -f "$candidate" ]; then
PEON_SH="$candidate"
break
fi
done

if [ -z "$PEON_SH" ]; then
error "peon.sh not found. Install peon-ping first:"
error " brew install PeonPing/tap/peon-ping"
error " # or: curl -fsSL peonping.com/install | bash"
exit 1
fi

if ! command -v curl &>/dev/null; then
error "curl is required but not found."
exit 1
fi

# Check for afplay (macOS), paplay (Linux), or powershell (WSL)
PLATFORM="unknown"
case "$(uname -s)" in
Darwin) PLATFORM="mac" ;;
Linux)
if grep -qi microsoft /proc/version 2>/dev/null; then
PLATFORM="wsl"
else
PLATFORM="linux"
fi ;;
esac
# --- Install adapter ---
info "Installing peon-ping adapter for OpenCode..."

case "$PLATFORM" in
mac)
command -v afplay &>/dev/null || warn "afplay not found — sounds may not play" ;;
wsl)
command -v powershell.exe &>/dev/null || warn "powershell.exe not found — sounds may not play" ;;
linux)
if ! command -v paplay &>/dev/null && ! command -v aplay &>/dev/null; then
warn "No audio player found (paplay/aplay) — sounds may not play"
fi ;;
esac

# --- Install plugin ---
mkdir -p "$OPENCODE_PLUGINS_DIR"

# Remove existing file/symlink — curl -o can't write through a broken symlink
# (e.g. leftover from Homebrew cellar path after an upgrade)
rm -f "$OPENCODE_PLUGINS_DIR/peon-ping.ts"

info "Downloading peon-ping.ts plugin..."
info "Downloading adapter..."
curl -fsSL "$PLUGIN_URL" -o "$OPENCODE_PLUGINS_DIR/peon-ping.ts"
info "Plugin installed to $OPENCODE_PLUGINS_DIR/peon-ping.ts"

# --- Create default config ---
mkdir -p "$PEON_CONFIG_DIR"

if [ ! -f "$PEON_CONFIG_DIR/config.json" ]; then
cat > "$PEON_CONFIG_DIR/config.json" << 'CONFIGEOF'
{
"default_pack": "peon",
"volume": 0.5,
"enabled": true,
"categories": {
"session.start": true,
"session.end": true,
"task.acknowledge": true,
"task.complete": true,
"task.error": true,
"task.progress": true,
"input.required": true,
"resource.limit": true,
"user.spam": true
},
"spam_threshold": 3,
"spam_window_seconds": 10,
"pack_rotation": [],
"debounce_ms": 500
}
CONFIGEOF
info "Config created at $PEON_CONFIG_DIR/config.json"
else
info "Config already exists, preserved."
fi

# --- Install default sound pack from registry ---
mkdir -p "$PACKS_DIR"

if [ ! -d "$PACKS_DIR/$DEFAULT_PACK" ]; then
info "Installing default sound pack '$DEFAULT_PACK' from registry..."

REGISTRY_JSON=$(curl -fsSL "$REGISTRY_URL" 2>/dev/null || true)
PACK_INFO=$(PACK_NAME="$DEFAULT_PACK" python3 -c "
import sys, json
reg = json.load(sys.stdin)
for p in reg.get('packs', []):
if p.get('name') == __import__('os').environ.get('PACK_NAME'):
print(p.get('source_repo', ''))
print(p.get('source_ref', ''))
print(p.get('source_path', ''))
break
" <<< "$REGISTRY_JSON" 2>/dev/null || echo "")

SOURCE_REPO=$(echo "$PACK_INFO" | sed -n '1p')
SOURCE_REF=$(echo "$PACK_INFO" | sed -n '2p')
SOURCE_PATH=$(echo "$PACK_INFO" | sed -n '3p')

if is_safe_source_repo "$SOURCE_REPO" && is_safe_source_ref "$SOURCE_REF" && is_safe_source_path "$SOURCE_PATH"; then
TMPDIR_PACK=$(mktemp -d)
TARBALL_URL="https://github.com/${SOURCE_REPO}/archive/refs/tags/${SOURCE_REF}.tar.gz"
if curl -fsSL "$TARBALL_URL" -o "$TMPDIR_PACK/pack.tar.gz" 2>/dev/null; then
if tar tzf "$TMPDIR_PACK/pack.tar.gz" | grep -Eq '(^/|(^|/)\.\.(/|$))'; then
warn "Pack archive contains unsafe paths; skipping extraction."
else
tar xzf "$TMPDIR_PACK/pack.tar.gz" -C "$TMPDIR_PACK" 2>/dev/null
EXTRACTED=$(find "$TMPDIR_PACK" -maxdepth 1 -type d ! -path "$TMPDIR_PACK" | head -1)
if [ -n "$EXTRACTED" ] && [ -d "$EXTRACTED/${SOURCE_PATH}" ]; then
mkdir -p "$PACKS_DIR/$DEFAULT_PACK"
cp -r "$EXTRACTED/${SOURCE_PATH}/"* "$PACKS_DIR/$DEFAULT_PACK/"
info "Pack '$DEFAULT_PACK' installed to $PACKS_DIR/$DEFAULT_PACK"
else
warn "Could not find pack in downloaded archive."
fi
fi
else
warn "Could not download pack from registry. You can install packs manually later."
fi
rm -rf "$TMPDIR_PACK"
else
warn "Could not find '$DEFAULT_PACK' in registry. You can install packs manually later."
fi
else
info "Pack '$DEFAULT_PACK' already installed."
fi
info "Adapter installed to $OPENCODE_PLUGINS_DIR/peon-ping.ts"

# --- Done ---
echo ""
info "${BOLD}peon-ping installed for OpenCode!${RESET}"
info "${BOLD}peon-ping adapter installed for OpenCode!${RESET}"
echo ""
printf " %sPlugin:%s %s\n" "$DIM" "$RESET" "$OPENCODE_PLUGINS_DIR/peon-ping.ts"
printf " %sConfig:%s %s\n" "$DIM" "$RESET" "$PEON_CONFIG_DIR/config.json"
printf " %sPacks:%s %s\n" "$DIM" "$RESET" "$PACKS_DIR/"
printf " %sAdapter:%s %s\n" "$DIM" "$RESET" "$OPENCODE_PLUGINS_DIR/peon-ping.ts"
printf " %speon.sh:%s %s\n" "$DIM" "$RESET" "$PEON_SH"
echo ""
info "Restart OpenCode to activate. Your Peon awaits."
info "Install more packs: https://openpeon.com/packs"
info "Restart OpenCode to activate. All peon-ping features now available."
info "Configure: peon config | peon trainer on | peon packs list"
Loading
Loading