param(
[string]$Format = "text",
[switch]$Clipboard,
[switch]$Preview,
[string]$SaveTo,
[switch]$Verbose,
[switch]$Debug,
[switch]$IncludeRecent,
[switch]$IncludePending
)
# Get current user and timezone info
$currentUser = gh auth status 2>&1 | Select-String "Logged in to github.com account (.+?) \(" | ForEach-Object { $_.Matches[0].Groups[1].Value }
if (-not $currentUser) {
Write-Error "Not logged in to GitHub. Run 'gh auth login' first."
exit 1
}
$localTimeZone = [System.TimeZoneInfo]::Local
$utcTimeZone = [System.TimeZoneInfo]::Utc
# Calculate today's date range in local time
$localTodayStart = Get-Date -Hour 0 -Minute 0 -Second 0 -Millisecond 0
$localTodayEnd = $localTodayStart.AddDays(1).AddMilliseconds(-1)
# Convert to UTC for GitHub API
$utcTodayStart = [System.TimeZoneInfo]::ConvertTimeToUtc($localTodayStart, $localTimeZone)
$utcTodayEnd = [System.TimeZoneInfo]::ConvertTimeToUtc($localTodayEnd, $localTimeZone)
# Format for GitHub search (date only, let GitHub handle the full day range)
$utcSearchStart = $utcTodayStart.ToString('yyyy-MM-dd')
$utcSearchEnd = $utcTodayEnd.ToString('yyyy-MM-dd')
$todayLocalStr = $localTodayStart.ToString('yyyy-MM-dd')
$dateLabel = "Today"
if ($Debug) {
Write-Host "DEBUG - Local today: $($localTodayStart) to $($localTodayEnd)" -ForegroundColor Yellow
Write-Host "DEBUG - UTC search range: $utcSearchStart to $utcSearchEnd" -ForegroundColor Yellow
}
Write-Host "š Fetching GitHub PR activity for $currentUser on $todayLocalStr (local time)..."
if (-not $IncludeRecent) {
Write-Host "š” Note: Very recent PRs (last ~10 minutes) may not appear due to search indexing delay. Use -IncludeRecent to check." -ForegroundColor Yellow
}
Write-Host " ā Searching all PRs with your activity..."
# Single comprehensive search for all PR activity
$searchCmd = "gh search prs --involves=`"$currentUser`" --updated=`"$utcSearchStart..$utcSearchEnd`" --json number,title,url,repository,createdAt,closedAt,updatedAt,author,state --limit 100"
if ($Debug) { Write-Host "DEBUG - Command: $searchCmd" -ForegroundColor Yellow }
$allActivityPRsJson = Invoke-Expression $searchCmd
$allActivityPRs = if ($allActivityPRsJson) { $allActivityPRsJson | ConvertFrom-Json } else { @() }
if ($Debug) { Write-Host "DEBUG - All activity PRs: $($allActivityPRs.Count)" -ForegroundColor Yellow }
# Check recent events if requested
$recentEventPRs = @()
if ($IncludeRecent) {
Write-Host " ā Checking recent events for new PRs..."
$eventsCmd = "gh api `"users/$currentUser/events`" --jq `".[0:30]`""
if ($Debug) { Write-Host "DEBUG - Command: $eventsCmd" -ForegroundColor Yellow }
$recentEvents = Invoke-Expression $eventsCmd | ConvertFrom-Json
$recentEventPRs = $recentEvents | Where-Object {
$_.type -eq "PullRequestEvent" -and $_.payload.action -eq "opened" -and
$_.actor.login -eq $currentUser -and
([DateTime]$_.created_at) -ge $utcTodayStart -and
([DateTime]$_.created_at) -le $utcTodayEnd
} | ForEach-Object {
[PSCustomObject]@{
number = $_.payload.pull_request.number
title = $_.payload.pull_request.title
url = $_.payload.pull_request.html_url
repository = @{ nameWithOwner = $_.repo.name }
createdAt = $_.payload.pull_request.created_at
author = @{ login = $_.payload.pull_request.user.login }
state = $_.payload.pull_request.state
}
}
}
if ($Debug) { Write-Host "DEBUG - Recent event PRs: $($recentEventPRs.Count)" -ForegroundColor Yellow }
# Combine and deduplicate PRs
$allPRUrls = $allActivityPRs | ForEach-Object { $_.url }
$uniqueRecentPRs = $recentEventPRs | Where-Object { $_.url -notin $allPRUrls }
$allActivityPRs = $allActivityPRs + $uniqueRecentPRs
# We'll detect review activity by checking each PR individually for your review actions
$reviewedPRs = @()
$pendingReviewPRs = @()
if ($Debug) {
Write-Host "DEBUG - Will check individual PRs for review activity" -ForegroundColor Yellow
}
# Categorize PRs based on your activity and creation date
$createdPRs = @()
$closedPRs = @()
if ($Debug) {
Write-Host "DEBUG - Processing $($allActivityPRs.Count) PRs for categorization..." -ForegroundColor Yellow
}
foreach ($pr in $allActivityPRs) {
$wasCreatedToday = $false
$wasClosedToday = $false
$wasReviewedToday = $false
# Check if YOU created this PR today (author must be you)
if ($pr.createdAt -and $pr.author -and $pr.author.login -eq $currentUser) {
$createdDate = [DateTime]::Parse($pr.createdAt)
$localCreatedDate = [System.TimeZoneInfo]::ConvertTimeFromUtc($createdDate, $localTimeZone)
$wasCreatedToday = $localCreatedDate.Date -eq $localTodayStart.Date
if ($Debug -and $wasCreatedToday) {
Write-Host "DEBUG - Created today: #$($pr.number) $($pr.title) at $($localCreatedDate)" -ForegroundColor Green
}
}
# Check if YOU closed/merged this PR today
if ($pr.closedAt -and $pr.closedAt -ne "0001-01-01T00:00:00Z" -and ($pr.state -eq "closed" -or $pr.state -eq "merged")) {
$closedDate = [DateTime]::Parse($pr.closedAt)
$localClosedDate = [System.TimeZoneInfo]::ConvertTimeFromUtc($closedDate, $localTimeZone)
$wasClosedTodayByTime = $localClosedDate.Date -eq $localTodayStart.Date
# Only count if you actually closed it - need to check who merged it
$wasClosedToday = $false
if ($wasClosedTodayByTime) {
try {
$prDetails = gh pr view $pr.number --repo $pr.repository.nameWithOwner --json mergedBy,mergedAt,state 2>$null | ConvertFrom-Json
$closedByYou = $prDetails.mergedBy -and $prDetails.mergedBy.login -eq $currentUser
$wasClosedToday = $closedByYou
if ($Debug) {
$whoClosedIt = if ($prDetails.mergedBy) { $prDetails.mergedBy.login } else { "not-merged" }
Write-Host "DEBUG - PR #$($pr.number) closed today by: $whoClosedIt, You closed it: $closedByYou" -ForegroundColor Cyan
}
} catch {
# If we can't determine who closed it, don't count it
$wasClosedToday = $false
if ($Debug) { Write-Host "DEBUG - Could not determine who closed PR #$($pr.number)" -ForegroundColor Yellow }
}
}
if ($Debug -and $wasClosedToday) {
Write-Host "DEBUG - YOU closed today: #$($pr.number) $($pr.title) at $($localClosedDate)" -ForegroundColor Red
}
}
# Check if YOU did review activity today (but not if you created or closed it)
if (-not $wasCreatedToday -and -not $wasClosedToday) {
try {
# Get your review activity on this PR
$reviewsJson = gh pr view $pr.number --repo $pr.repository.nameWithOwner --json reviews 2>$null
if ($reviewsJson) {
$reviews = ($reviewsJson | ConvertFrom-Json).reviews
foreach ($review in $reviews) {
if ($review.author.login -eq $currentUser) {
$reviewDate = [DateTime]::Parse($review.submittedAt)
$localReviewDate = [System.TimeZoneInfo]::ConvertTimeFromUtc($reviewDate, $localTimeZone)
if ($localReviewDate.Date -eq $localTodayStart.Date) {
$wasReviewedToday = $true
if ($Debug) {
Write-Host "DEBUG - YOU reviewed today: #$($pr.number) $($pr.title) at $($localReviewDate) ($($review.state))" -ForegroundColor Blue
}
break
}
}
}
}
# Also check for review comments today
if (-not $wasReviewedToday) {
$commentsJson = gh pr view $pr.number --repo $pr.repository.nameWithOwner --json reviewRequests,comments 2>$null
if ($commentsJson) {
$prData = $commentsJson | ConvertFrom-Json
foreach ($comment in $prData.comments) {
if ($comment.author.login -eq $currentUser) {
$commentDate = [DateTime]::Parse($comment.createdAt)
$localCommentDate = [System.TimeZoneInfo]::ConvertTimeFromUtc($commentDate, $localTimeZone)
if ($localCommentDate.Date -eq $localTodayStart.Date) {
$wasReviewedToday = $true
if ($Debug) {
Write-Host "DEBUG - YOU commented today: #$($pr.number) $($pr.title) at $($localCommentDate)" -ForegroundColor Blue
}
break
}
}
}
}
}
} catch {
if ($Debug) { Write-Host "DEBUG - Could not check review activity for PR #$($pr.number)" -ForegroundColor Yellow }
}
}
# Categorize based on what happened today
if ($wasCreatedToday) {
$createdPRs += $pr
} elseif ($wasClosedToday) {
$closedPRs += $pr
} elseif ($wasReviewedToday) {
$reviewedPRs += $pr
}
if ($Debug) {
Write-Host "DEBUG - PR #$($pr.number): Created=$wasCreatedToday, Closed=$wasClosedToday, Reviewed=$wasReviewedToday, Author=$($pr.author.login)" -ForegroundColor Gray
}
}
# Search for PRs awaiting your review if requested
if ($IncludePending) {
Write-Host " ā Searching PRs awaiting your review..."
try {
$pendingCmd = "gh search prs --state=open --review-requested=`"$currentUser`" --json number,title,url,repository,createdAt,updatedAt,author,isDraft --limit 50"
if ($Debug) { Write-Host "DEBUG - Command: $pendingCmd" -ForegroundColor Yellow }
$pendingPRsJson = Invoke-Expression $pendingCmd
$pendingReviewPRs = if ($pendingPRsJson) { $pendingPRsJson | ConvertFrom-Json } else { @() }
# Filter out draft PRs unless in verbose mode
if (-not $Verbose) {
$pendingReviewPRs = $pendingReviewPRs | Where-Object { -not $_.isDraft }
}
if ($Debug) {
Write-Host "DEBUG - Found $($pendingReviewPRs.Count) PRs awaiting your review" -ForegroundColor Yellow
foreach ($pendingPR in $pendingReviewPRs) {
$age = ([DateTime]::Now - [DateTime]::Parse($pendingPR.createdAt)).Days
Write-Host "DEBUG - Pending: #$($pendingPR.number) $($pendingPR.title) (created $age days ago)" -ForegroundColor Magenta
}
}
} catch {
if ($Debug) { Write-Host "DEBUG - Could not fetch pending review PRs: $($_.Exception.Message)" -ForegroundColor Yellow }
$pendingReviewPRs = @()
}
}
# Generate output
if ($Format -eq "json") {
$output = @{
date = $todayLocalStr
user = $currentUser
created_prs = $createdPRs
closed_prs = $closedPRs
reviewed_prs = $reviewedPRs
pending_review_prs = $pendingReviewPRs
summary = @{
created_count = $createdPRs.Count
closed_count = $closedPRs.Count
reviewed_count = $reviewedPRs.Count
pending_review_count = $pendingReviewPRs.Count
total_activity = $createdPRs.Count + $closedPRs.Count + $reviewedPRs.Count
}
} | ConvertTo-Json -Depth 10
} elseif ($Format -eq "html") {
$output = "<h2>š GitHub PR Summary for $todayLocalStr</h2>`n"
$output += "<hr>`n"
# PRs Created section
if ($createdPRs.Count -gt 0) {
$output += "<h3>š PRs Created $dateLabel ($($createdPRs.Count)):</h3>`n<ul>`n"
foreach ($pr in $createdPRs) {
$output += "<li><a href=`"$($pr.url)`">$($pr.title)</a></li>`n"
}
$output += "</ul>`n"
}
# PRs Closed section
if ($closedPRs.Count -gt 0) {
$output += "<h3>ā
PRs Closed $dateLabel ($($closedPRs.Count)):</h3>`n<ul>`n"
foreach ($pr in $closedPRs) {
$output += "<li><a href=`"$($pr.url)`">$($pr.title)</a></li>`n"
}
$output += "</ul>`n"
}
# PRs Reviewed section - show ALL reviewed PRs with merge status
if ($reviewedPRs.Count -gt 0) {
$output += "<h3>š PRs Reviewed $dateLabel ($($reviewedPRs.Count)):</h3>`n<ul>`n"
foreach ($pr in $reviewedPRs) {
# Get merge status for each PR
$mergeStatus = ""
try {
$prDetails = gh pr view $pr.number --repo $pr.repository.nameWithOwner --json state,mergedAt 2>$null | ConvertFrom-Json
if ($prDetails -and $prDetails.mergedAt) {
$mergeStatus = " ā
merged"
} elseif ($prDetails -and $prDetails.state -eq "CLOSED") {
$mergeStatus = " ā closed"
} elseif ($prDetails -and $prDetails.state -eq "OPEN") {
$mergeStatus = " š open"
}
} catch {
# Fallback to basic state if PR view fails
$mergeStatus = if ($pr.state -eq "closed") { " ā closed" } else { " š open" }
}
$repoName = if ($pr.repository.nameWithOwner) { $pr.repository.nameWithOwner } else { $pr.repository.name }
$output += "<li><a href=`"$($pr.url)`">$($pr.title)</a> ($repoName)$mergeStatus</li>`n"
}
$output += "</ul>`n"
}
# PRs Awaiting Review section
if ($pendingReviewPRs.Count -gt 0) {
$output += "<h3>ā³ PRs Awaiting Your Review ($($pendingReviewPRs.Count)):</h3>`n<ul>`n"
foreach ($pr in $pendingReviewPRs) {
$repoName = if ($pr.repository.nameWithOwner) { $pr.repository.nameWithOwner } else { $pr.repository.name }
$age = ([DateTime]::Now - [DateTime]::Parse($pr.createdAt)).Days
$ageStr = if ($age -eq 0) { "today" } elseif ($age -eq 1) { "1 day ago" } else { "$age days ago" }
$draftStr = if ($pr.isDraft) { " [DRAFT]" } else { "" }
$output += "<li><a href=`"$($pr.url)`">$($pr.title)</a> ($repoName) - created $ageStr$draftStr</li>`n"
}
$output += "</ul>`n"
}
$totalActivity = $createdPRs.Count + $closedPRs.Count + $reviewedPRs.Count
$pendingNote = if ($pendingReviewPRs.Count -gt 0) { ", $($pendingReviewPRs.Count) awaiting review" } else { "" }
$output += "<p><strong>šÆ Total PR Activity: $totalActivity items ($($createdPRs.Count) created, $($closedPRs.Count) closed, $($reviewedPRs.Count) reviewed)$pendingNote</strong></p>`n"
} else {
# Text format
$output = "`nš GitHub PR Summary for $todayLocalStr`n"
$output += "="*50 + "`n"
# PRs Created section
if ($createdPRs.Count -gt 0) {
$output += "š PRs Created $dateLabel ($($createdPRs.Count)):`n"
foreach ($pr in $createdPRs) {
$output += "* [$($pr.title)]($($pr.url))`n"
}
$output += "`n"
}
# PRs Closed section
if ($closedPRs.Count -gt 0) {
$output += "ā
PRs Closed $dateLabel ($($closedPRs.Count)):`n"
foreach ($pr in $closedPRs) {
$output += "* [$($pr.title)]($($pr.url))`n"
}
$output += "`n"
}
# PRs Reviewed section - show ALL reviewed PRs with merge status
if ($reviewedPRs.Count -gt 0) {
$output += "š PRs Reviewed $dateLabel ($($reviewedPRs.Count)):`n"
foreach ($pr in $reviewedPRs) {
# Get merge status for each PR
$mergeStatus = ""
try {
$prDetails = gh pr view $pr.number --repo $pr.repository.nameWithOwner --json state,mergedAt 2>$null | ConvertFrom-Json
if ($prDetails -and $prDetails.mergedAt) {
$mergeStatus = " ā
merged"
} elseif ($prDetails -and $prDetails.state -eq "CLOSED") {
$mergeStatus = " ā closed"
} elseif ($prDetails -and $prDetails.state -eq "OPEN") {
$mergeStatus = " š open"
}
} catch {
# Fallback to basic state if PR view fails
$mergeStatus = if ($pr.state -eq "closed") { " ā closed" } else { " š open" }
}
$repoName = if ($pr.repository.nameWithOwner) { $pr.repository.nameWithOwner } else { $pr.repository.name }
if ($Verbose) {
$output += "* [$($pr.title)]($($pr.url)) - $repoName #$($pr.number)$mergeStatus`n"
} else {
$output += "* [$($pr.title)]($($pr.url))$mergeStatus`n"
}
}
$output += "`n"
}
# PRs Awaiting Review section
if ($pendingReviewPRs.Count -gt 0) {
$output += "ā³ PRs Awaiting Your Review ($($pendingReviewPRs.Count)):`n"
foreach ($pr in $pendingReviewPRs) {
$age = ([DateTime]::Now - [DateTime]::Parse($pr.createdAt)).Days
$ageStr = if ($age -eq 0) { "today" } elseif ($age -eq 1) { "1 day ago" } else { "$age days ago" }
$draftStr = if ($pr.isDraft) { " [DRAFT]" } else { "" }
if ($Verbose) {
$repoName = if ($pr.repository.nameWithOwner) { $pr.repository.nameWithOwner } else { $pr.repository.name }
$output += "* [$($pr.title)]($($pr.url)) - $repoName #$($pr.number) (created $ageStr)$draftStr`n"
} else {
$output += "* [$($pr.title)]($($pr.url)) (created $ageStr)$draftStr`n"
}
}
$output += "`n"
}
$totalActivity = $createdPRs.Count + $closedPRs.Count + $reviewedPRs.Count
$pendingNote = if ($pendingReviewPRs.Count -gt 0) { ", $($pendingReviewPRs.Count) awaiting review" } else { "" }
$output += "šÆ Total PR Activity: $totalActivity items ($($createdPRs.Count) created, $($closedPRs.Count) closed, $($reviewedPRs.Count) reviewed)$pendingNote`n"
}
# Output results
if ($SaveTo) {
$output | Out-File -FilePath $SaveTo -Encoding UTF8
Write-Host "ā
Output saved to: $SaveTo" -ForegroundColor Green
}
if ($Clipboard) {
if ($Format -eq "html") {
# For HTML format, copy as both text and HTML to clipboard
Add-Type -AssemblyName System.Windows.Forms
$dataObject = New-Object System.Windows.Forms.DataObject
$dataObject.SetText($output, [System.Windows.Forms.TextDataFormat]::Html)
$dataObject.SetText(($output -replace '<[^>]+>',''), [System.Windows.Forms.TextDataFormat]::Text)
[System.Windows.Forms.Clipboard]::SetDataObject($dataObject)
Write-Host "ā
Output copied to clipboard (HTML + Text)" -ForegroundColor Green
} else {
Set-Clipboard -Value $output
Write-Host "ā
Output copied to clipboard" -ForegroundColor Green
}
}
if ($Preview) {
if ($Format -eq "html") {
$tempFile = [System.IO.Path]::GetTempFileName() + ".html"
$output | Out-File -FilePath $tempFile -Encoding UTF8
Start-Process $tempFile
Write-Host "ā
Preview opened in browser: $tempFile" -ForegroundColor Green
} elseif ($Format -eq "text" -or $Format -eq "markdown") {
$tempFile = [System.IO.Path]::GetTempFileName() + ".md"
$output | Out-File -FilePath $tempFile -Encoding UTF8
code $tempFile
Write-Host "ā
Preview opened in VS Code: $tempFile" -ForegroundColor Green
}
}
if (-not $SaveTo -and -not $Clipboard -and -not $Preview) {
Write-Output $output
}