Created
February 24, 2026 12:53
-
-
Save shivaduke28/55d89613c519413b4372fc579a9ced1b to your computer and use it in GitHub Desktop.
SwiftBar plugin for GitHub PR status
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # <xbar.title>GitHub PR Dashboard</xbar.title> | |
| # <xbar.version>v3.0</xbar.version> | |
| # <xbar.author>shivaduke</xbar.author> | |
| # <xbar.desc>Shows open PRs and review requests in menu bar</xbar.desc> | |
| # <xbar.dependencies>gh</xbar.dependencies> | |
| # ---- Configuration ---- | |
| # リポジトリを追加・変更する場合はここを編集 (owner/repo 形式) | |
| REPOS=( | |
| "shivaduke28/Arshes" | |
| "shivaduke28/arshes-cli" | |
| ) | |
| # ------------------------ | |
| GITHUB_USER="shivaduke28" | |
| export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH" | |
| # --repo フラグを構築 | |
| repo_flags=() | |
| repo_query="" | |
| for repo in "${REPOS[@]}"; do | |
| repo_flags+=("--repo=$repo") | |
| repo_query+=" repo:${repo}" | |
| done | |
| waiting_total=0 | |
| reviewed_total=0 | |
| approved_total=0 | |
| review_req_total=0 | |
| waiting_section="" | |
| reviewed_section="" | |
| approved_section="" | |
| review_req_section="" | |
| # 自分のopen PR + レビュー状態を一括取得 (1 GraphQL call) | |
| my_prs=$(gh api graphql -f query="{ | |
| search(query: \"is:pr is:open author:@me -is:draft${repo_query}\", type: ISSUE, first: 50) { | |
| nodes { | |
| ... on PullRequest { | |
| number | |
| title | |
| url | |
| reviewRequests(first: 20) { nodes { requestedReviewer { ... on User { login } ... on Bot { login } ... on Team { name } } } } | |
| latestReviews(first: 20) { nodes { state } } | |
| } | |
| } | |
| } | |
| }" 2>/dev/null) | |
| # Approved: latestReviewsにAPPROVEDあり | |
| while IFS=$'\t' read -r number title url; do | |
| [ -z "$number" ] && continue | |
| approved_total=$((approved_total + 1)) | |
| [ ${#title} -gt 50 ] && title="${title:0:47}..." | |
| title="${title//|/-}" | |
| approved_section+="--#${number} ${title} | href=${url} sfimage=checkmark.circle.fill sfcolor=#34C759\n" | |
| done < <(echo "$my_prs" | jq -r '.data.search.nodes[] | select(.latestReviews.nodes | map(.state) | any(. == "APPROVED")) | "\(.number)\t\(.title)\t\(.url)"' 2>/dev/null) | |
| # Reviewed: approved以外 かつ reviewRequests空(レビュー済み・対応待ち) | |
| while IFS=$'\t' read -r number title url; do | |
| [ -z "$number" ] && continue | |
| reviewed_total=$((reviewed_total + 1)) | |
| [ ${#title} -gt 50 ] && title="${title:0:47}..." | |
| title="${title//|/-}" | |
| reviewed_section+="--#${number} ${title} | href=${url} sfimage=exclamationmark.bubble.fill sfcolor=#FF9500\n" | |
| done < <(echo "$my_prs" | jq -r '.data.search.nodes[] | select((.latestReviews.nodes | map(.state) | any(. == "APPROVED") | not) and (.reviewRequests.nodes | length == 0)) | "\(.number)\t\(.title)\t\(.url)"' 2>/dev/null) | |
| # Waiting: approved以外 かつ reviewRequestsあり(レビュー待ち) | |
| while IFS=$'\t' read -r number title url; do | |
| [ -z "$number" ] && continue | |
| waiting_total=$((waiting_total + 1)) | |
| [ ${#title} -gt 50 ] && title="${title:0:47}..." | |
| title="${title//|/-}" | |
| waiting_section+="--#${number} ${title} | href=${url} sfimage=clock.fill sfcolor=#8E8E93\n" | |
| done < <(echo "$my_prs" | jq -r '.data.search.nodes[] | select((.latestReviews.nodes | map(.state) | any(. == "APPROVED") | not) and (.reviewRequests.nodes | length > 0)) | "\(.number)\t\(.title)\t\(.url)"' 2>/dev/null) | |
| # レビューリクエスト | |
| while IFS=$'\t' read -r number title url author; do | |
| [ -z "$number" ] && continue | |
| review_req_total=$((review_req_total + 1)) | |
| [ ${#title} -gt 50 ] && title="${title:0:47}..." | |
| title="${title//|/-}" | |
| review_req_section+="--#${number} ${title} by ${author} | href=${url} sfimage=eyes sfcolor=#007AFF\n" | |
| done < <(gh search prs --review-requested=@me --state=open "${repo_flags[@]}" \ | |
| --json number,title,url,author,isDraft \ | |
| --jq '.[] | select(.isDraft | not) | "\(.number)\t\(.title)\t\(.url)\t\(.author.login)"' 2>/dev/null) | |
| # メニューバー表示 (0件のカテゴリは非表示) | |
| parts="" | |
| [ $approved_total -gt 0 ] && parts+=":checkmark.circle.fill:${approved_total} " | |
| [ $reviewed_total -gt 0 ] && parts+=":exclamationmark.bubble.fill:${reviewed_total} " | |
| [ $waiting_total -gt 0 ] && parts+=":clock.fill:${waiting_total} " | |
| [ $review_req_total -gt 0 ] && parts+=":eyes:${review_req_total} " | |
| if [ -z "$parts" ]; then | |
| echo ":hands.clap.fill:" | |
| else | |
| echo "$parts" | |
| fi | |
| echo "---" | |
| # 自分のPR: Approved | |
| echo "Approved (${approved_total}) | sfimage=checkmark.circle.fill sfcolor=#34C759 size=14" | |
| if [ -n "$approved_section" ]; then | |
| printf "%b" "$approved_section" | |
| else | |
| echo "-- なし | color=gray" | |
| fi | |
| # 自分のPR: Reviewed | |
| echo "Reviewed (${reviewed_total}) | sfimage=exclamationmark.bubble.fill sfcolor=#FF9500 size=14" | |
| if [ -n "$reviewed_section" ]; then | |
| printf "%b" "$reviewed_section" | |
| else | |
| echo "-- なし | color=gray" | |
| fi | |
| # 自分のPR: Review待ち | |
| echo "Waiting for Review (${waiting_total}) | sfimage=clock.fill sfcolor=#8E8E93 size=14" | |
| if [ -n "$waiting_section" ]; then | |
| printf "%b" "$waiting_section" | |
| else | |
| echo "-- なし | color=gray" | |
| fi | |
| echo "---" | |
| # 他人のPR: レビューリクエスト | |
| echo "Review Requested (${review_req_total}) | sfimage=eyes sfcolor=#007AFF size=14" | |
| if [ -n "$review_req_section" ]; then | |
| printf "%b" "$review_req_section" | |
| else | |
| echo "-- なし | color=gray" | |
| fi | |
| echo "---" | |
| echo "Refresh | refresh=true sfimage=arrow.clockwise sfcolor=#8E8E93" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment