#!/bin/bash # GitHub Notifications Checker # Filters PRs and releases, tracks state, returns JSON summary set -e STATE_FILE="${STATE_FILE:-memory/github-check-state.json}" WORKSPACE="${WORKSPACE:-/home/node/.openclaw/workspace}" cd "$WORKSPACE" # Initialize state file if missing if [ ! -f "$STATE_FILE" ]; then mkdir -p "$(dirname "$STATE_FILE")" echo '{"lastCheck":"1970-01-01T00:00:00Z","seenPRs":[],"seenReleases":[]}' > "$STATE_FILE" fi # Load last check time LAST_CHECK=$(jq -r '.lastCheck' "$STATE_FILE") NOW=$(date -u +"%Y-%m-%dT%H:%M:%SZ") # Fetch notifications (PRs only) if ! PR_DATA=$(gh api 'notifications?all=true&per_page=100' 2>&1); then echo '{"error":"GitHub API failed","details":"'"${PR_DATA//\"/\\\"}"'"}' | jq . exit 1 fi # Filter PRs where user is mentioned/author/review requested/subscribed FILTERED_PRS=$(echo "$PR_DATA" | jq -r '[ .[] | select(.subject.type == "PullRequest") | select(.reason == "mention" or .reason == "author" or .reason == "review_requested" or .reason == "subscribed") | { repo: .repository.full_name, title: .subject.title, url: .subject.url, updated: .updated_at, reason: .reason, id: (.repository.full_name + "#" + .subject.title) } ]') # Filter releases RELEASE_DATA=$(echo "$PR_DATA" | jq -r '[ .[] | select(.subject.type == "Release") | { repo: .repository.full_name, title: .subject.title, updated: .updated_at, reason: .reason, id: (.repository.full_name + "@" + .subject.title) } ]') # Filter releases: # - include subscribed releases (user-requested) # - keep legacy inclusion for major releases + whitelist repos # - ignore dev/pre-release markers for non-whitelist repos # - additionally ignore GitHub prerelease=true for non-whitelist repos # - whitelist repos always pass through FILTERED_RELEASES=$(echo "$RELEASE_DATA" | jq -r '[ .[] | . as $r | ($r.repo == "Mirrowel/LLM-API-Key-Proxy" or $r.repo == "openclaw/openclaw" or $r.repo == "anomalyco/opencode") as $whitelisted | select( ($r.reason == "subscribed") or $whitelisted or ($r.title | test("^v[0-9]+\\.0\\.0")) ) | select( $whitelisted or (($r.title | ascii_downcase) | test("(rc|pre|beta|alpha|nightly|dev|exp|canary|snapshot)") | not) ) ]') # Enrich release candidates with GitHub prerelease flag (best-effort). # Notifications payload lacks prerelease metadata, so look up each candidate by repo+title. # For non-whitelisted repos, exclude prerelease=true. FILTERED_RELEASES=$(echo "$FILTERED_RELEASES" | jq -c '.[]' | while read -r rel; do repo=$(echo "$rel" | jq -r '.repo') title=$(echo "$rel" | jq -r '.title') whitelisted=false if [ "$repo" = "Mirrowel/LLM-API-Key-Proxy" ] || [ "$repo" = "openclaw/openclaw" ] || [ "$repo" = "anomalyco/opencode" ]; then whitelisted=true fi # Whitelist bypasses prerelease metadata filtering. if [ "$whitelisted" = "true" ]; then echo "$rel" continue fi # URL-encode tag (title) using jq for safety. tag_encoded=$(jq -nr --arg s "$title" '$s|@uri') # If lookup fails, keep item (fail-open) to avoid dropping potentially important notifications. prerelease=$(gh api "repos/$repo/releases/tags/$tag_encoded" --jq '.prerelease' 2>/dev/null || echo "lookup_failed") if [ "$prerelease" = "true" ]; then continue fi echo "$rel" done | jq -s '.') # Load seen items SEEN_PRS=$(jq -r '.seenPRs // []' "$STATE_FILE") SEEN_RELEASES=$(jq -r '.seenReleases // []' "$STATE_FILE") # Find new items NEW_PRS=$(echo "$FILTERED_PRS" | jq --argjson seen "$SEEN_PRS" '[ .[] | select(.id as $id | $seen | index($id) | not) ]') NEW_RELEASES=$(echo "$FILTERED_RELEASES" | jq --argjson seen "$SEEN_RELEASES" '[ .[] | select(.id as $id | $seen | index($id) | not) ]') # Count new items NEW_PR_COUNT=$(echo "$NEW_PRS" | jq 'length') NEW_RELEASE_COUNT=$(echo "$NEW_RELEASES" | jq 'length') # Update state ALL_PR_IDS=$(echo "$FILTERED_PRS" | jq -r '[.[].id]') ALL_RELEASE_IDS=$(echo "$FILTERED_RELEASES" | jq -r '[.[].id]') jq -n \ --arg now "$NOW" \ --argjson prIds "$ALL_PR_IDS" \ --argjson relIds "$ALL_RELEASE_IDS" \ '{lastCheck:$now, seenPRs:$prIds, seenReleases:$relIds}' \ > "$STATE_FILE" # Output result if [ "$NEW_PR_COUNT" -eq 0 ] && [ "$NEW_RELEASE_COUNT" -eq 0 ]; then echo '{"hasNew":false}' exit 0 fi # Return new items jq -n \ --argjson prs "$NEW_PRS" \ --argjson releases "$NEW_RELEASES" \ '{hasNew:true, newPRs:$prs, newReleases:$releases}'