Skip to content

Instantly share code, notes, and snippets.

@shivaduke28
Created February 24, 2026 12:53
Show Gist options
  • Select an option

  • Save shivaduke28/55d89613c519413b4372fc579a9ced1b to your computer and use it in GitHub Desktop.

Select an option

Save shivaduke28/55d89613c519413b4372fc579a9ced1b to your computer and use it in GitHub Desktop.
SwiftBar plugin for GitHub PR status
#!/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