#!/usr/bin/env bash # gstack-community-dashboard — community usage stats from Supabase # # Queries the Supabase REST API to show community-wide gstack usage: # skill popularity, crash clusters, version distribution, retention. # # Env overrides (for testing): # GSTACK_DIR — override auto-detected gstack root # GSTACK_SUPABASE_URL — override Supabase project URL # GSTACK_SUPABASE_ANON_KEY — override Supabase anon key set -uo pipefail GSTACK_DIR="${GSTACK_DIR:-$(cd "$(dirname "$0")/.." && pwd)}" # Source Supabase config if not overridden by env if [ -z "${GSTACK_SUPABASE_URL:-}" ] && [ -f "$GSTACK_DIR/supabase/config.sh" ]; then . "$GSTACK_DIR/supabase/config.sh" fi SUPABASE_URL="${GSTACK_SUPABASE_URL:-}" ANON_KEY="${GSTACK_SUPABASE_ANON_KEY:-}" if [ -z "$SUPABASE_URL" ] || [ -z "$ANON_KEY" ]; then echo "gstack community dashboard" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" echo "Supabase not configured yet. The community dashboard will be" echo "available once the gstack Supabase project is set up." echo "" echo "For local analytics, run: gstack-analytics" exit 0 fi # ─── Helper: query Supabase REST API ───────────────────────── query() { local table="$1" local params="${2:-}" curl -sf --max-time 10 \ "${SUPABASE_URL}/rest/v1/${table}?${params}" \ -H "apikey: ${ANON_KEY}" \ -H "Authorization: Bearer ${ANON_KEY}" \ 2>/dev/null || echo "[]" } echo "gstack community dashboard" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" # ─── Weekly active installs ────────────────────────────────── WEEK_AGO="$(date -u -v-7d +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || echo "")" if [ -n "$WEEK_AGO" ]; then PULSE="$(curl -sf --max-time 10 \ "${SUPABASE_URL}/functions/v1/community-pulse" \ -H "Authorization: Bearer ${ANON_KEY}" \ 2>/dev/null || echo '{"weekly_active":0}')" WEEKLY="$(echo "$PULSE" | grep -o '"weekly_active":[0-9]*' | grep -o '[0-9]*' || echo "0")" CHANGE="$(echo "$PULSE" | grep -o '"change_pct":[0-9-]*' | grep -o '[0-9-]*' || echo "0")" echo "Weekly active installs: ${WEEKLY}" if [ "$CHANGE" -gt 0 ] 2>/dev/null; then echo " Change: +${CHANGE}%" elif [ "$CHANGE" -lt 0 ] 2>/dev/null; then echo " Change: ${CHANGE}%" fi echo "" fi # ─── Skill popularity (top 10) ─────────────────────────────── echo "Top skills (last 7 days)" echo "────────────────────────" # Query telemetry_events, group by skill EVENTS="$(query "telemetry_events" "select=skill,gstack_version,session_id&event_type=eq.skill_run&event_timestamp=gte.${WEEK_AGO}&limit=1000" 2>/dev/null || echo "[]")" if [ "$EVENTS" != "[]" ] && [ -n "$EVENTS" ]; then echo "$EVENTS" | grep -o '"skill":"[^"]*"' | awk -F'"' '{print $4}' | sort | uniq -c | sort -rn | head -10 | while read -r COUNT SKILL; do printf " /%-20s %d runs\n" "$SKILL" "$COUNT" done else echo " No data yet" fi echo "" # ─── Errors (last 7 days) ──────────────────────────────────── echo "Top errors (last 7 days)" echo "────────────────────────" ERRORS="$(query "telemetry_events" "select=skill,error_class,error_message,failed_step,duration_s,session_id&outcome=eq.error&event_timestamp=gte.${WEEK_AGO}&order=event_timestamp.desc&limit=200" 2>/dev/null || echo "[]")" if [ "$ERRORS" != "[]" ] && [ -n "$ERRORS" ]; then # Group by skill + error_class, show count and example message echo "$ERRORS" | grep -o '"skill":"[^"]*"[^}]*"error_class":"[^"]*"' | \ sed 's/.*"skill":"//;s/".*"error_class":"/\t/' | sed 's/"$//' | \ sort | uniq -c | sort -rn | head -8 | while read -r COUNT COMBO; do SKILL="$(echo "$COMBO" | cut -f1)" ERR="$(echo "$COMBO" | cut -f2)" # Find an example error_message for this combo MSG="$(echo "$ERRORS" | grep -o "\"skill\":\"${SKILL}\"[^}]*\"error_message\":\"[^\"]*\"" | \ grep -o '"error_message":"[^"]*"' | head -1 | sed 's/"error_message":"//;s/"$//' || true)" # Find an example failed_step STEP="$(echo "$ERRORS" | grep -o "\"skill\":\"${SKILL}\"[^}]*\"failed_step\":\"[^\"]*\"" | \ grep -o '"failed_step":"[^"]*"' | head -1 | sed 's/"failed_step":"//;s/"$//' || true)" printf " /%-12s %-18s %3d errors\n" "$SKILL" "${ERR:-unknown}" "$COUNT" [ -n "$STEP" ] && printf " step: %s\n" "$STEP" [ -n "$MSG" ] && printf " e.g.: %s\n" "$(echo "$MSG" | head -c 80)" done # Show how many unique sessions have errors ERR_SESSIONS="$(echo "$ERRORS" | grep -o '"session_id":"[^"]*"' | sort -u | wc -l | tr -d ' ')" echo "" echo " ${ERR_SESSIONS} unique session(s) with errors" else echo " No errors reported" fi echo "" # ─── Version distribution ──────────────────────────────────── echo "Version distribution (last 7 days)" echo "───────────────────────────────────" if [ "$EVENTS" != "[]" ] && [ -n "$EVENTS" ]; then echo "$EVENTS" | grep -o '"gstack_version":"[^"]*"' | awk -F'"' '{print $4}' | sort | uniq -c | sort -rn | head -5 | while read -r COUNT VER; do printf " v%-15s %d events\n" "$VER" "$COUNT" done else echo " No data yet" fi # ─── Sessions (distinct session_id, works for all tiers) ──── echo "Sessions (last 7 days)" echo "──────────────────────" if [ "$EVENTS" != "[]" ] && [ -n "$EVENTS" ]; then SESSION_COUNT="$(echo "$EVENTS" | grep -o '"session_id":"[^"]*"' | sort -u | wc -l | tr -d ' ')" echo " ${SESSION_COUNT} unique sessions" else echo " No session data" fi echo "" # ─── Skill recommendations ───────────────────────────────── # Fetch top skills for recommendations TOP_SKILLS="$(echo "$EVENTS" | grep -o '"skill":"[^"]*"' | awk -F'"' '{print $4}' | sort | uniq -c | sort -rn | head -3 | awk '{print $2}' | tr '\n' ',' | sed 's/,$//')" if [ -n "$TOP_SKILLS" ]; then RECS="$(curl -sf --max-time 10 \ "${SUPABASE_URL}/functions/v1/community-recommendations?skills=${TOP_SKILLS}" \ -H "Authorization: Bearer ${ANON_KEY}" \ 2>/dev/null || echo '{"recommendations":[]}')" REC_LIST="$(echo "$RECS" | grep -o '"skill":"[^"]*"' | awk -F'"' '{print $4}')" REC_REASONS="$(echo "$RECS" | grep -o '"reason":"[^"]*"' | awk -F'"' '{print $4}')" if [ -n "$REC_LIST" ]; then echo "Skills you might like" echo "─────────────────────" paste <(echo "$REC_LIST") <(echo "$REC_REASONS") 2>/dev/null | while IFS=$'\t' read -r SKILL REASON; do [ -z "$SKILL" ] && continue printf " /%-20s %s\n" "$SKILL" "${REASON:-}" done echo "" fi fi echo "For local analytics: gstack-analytics" echo "For benchmarks: gstack-community-benchmarks"