Public/Start-CopilotWork.ps1
# Helper: Write progress message with timestamp function Write-ProgressMessage { param( [Parameter(Mandatory)] [string]$Message ) $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" Write-Host "[$timestamp] $Message" -ForegroundColor Green } # Helper: Assign @copilot to a GitHub issue using gh CLI function Set-CopilotIssueAssignee { param( [Parameter(Mandatory)] [string]$Repo, [Parameter(Mandatory)] [string]$IssueNumber ) if (-not (Get-Command gh -ErrorAction SilentlyContinue)) { throw "The GitHub CLI 'gh' is not installed or not in PATH. Please install it to assign @copilot." } Write-ProgressMessage "Assigning @copilot to issue #$IssueNumber in $Repo" gh issue edit $IssueNumber --repo $Repo --add-assignee "@copilot" | Out-Null } # Helper: Check if a GitHub repo exists function Test-RepoExists { param( [Parameter(Mandatory)] [string]$Name # owner/repo ) if (-not (Get-Command gh -ErrorAction SilentlyContinue)) { throw "The GitHub CLI 'gh' is not installed or not in PATH. Please install it." } Write-ProgressMessage "Checking if repository $Name exists" gh repo view $Name 2>$null | Out-Null if ($LASTEXITCODE -eq 0) { Write-ProgressMessage "Repository $Name located successfully" return $true } else { return $false } } # Helper: Create a new GitHub issue function New-Issue { param( [Parameter(Mandatory)] [string]$RepoName, # owner/repo [Parameter(Mandatory)] [string]$Title, [Parameter(Mandatory)] [string]$Body ) if (-not (Get-Command gh -ErrorAction SilentlyContinue)) { throw "The GitHub CLI 'gh' is not installed or not in PATH. Please install it." } Write-ProgressMessage "Creating issue in $RepoName with title: '$Title'" $output = gh issue create --repo $RepoName --title $Title --body $Body 2>&1 if ($LASTEXITCODE -ne 0 -or -not $output) { throw "Failed to create issue using gh CLI. Output: $output" } # Try to extract the URL from the output (gh usually prints the URL on the last line) $url = $output | Select-String -Pattern 'https://github.com/.*/issues/\d+' -AllMatches | ForEach-Object { $_.Matches.Value } | Select-Object -Last 1 if ($url) { Write-ProgressMessage "Issue created successfully: $url" return $url } else { throw "Issue created but URL not found in output. Raw output: $output" } } function Start-CopilotWork { <# .SYNOPSIS Creates Copilot-assigned issues in specified GitHub repos. .DESCRIPTION Checks if the repo(s) exist, creates one or more issues with the given prompt(s), assigns @copilot, and outputs the issue URLs. If -Path is provided, reads a markdown file and creates an issue for each section separated by '---'. Both -Repo and -Work can accept arrays to create issues for all combinations. .PARAMETER Repo The GitHub repository or repositories in the format owner/repo. Can be a single repo or an array of repos. .PARAMETER Work The issue body/prompt(s) for Copilot. Can be a single string or an array of strings. .PARAMETER Path Path to a markdown file containing one or more issues separated by '---'. .EXAMPLE Start-CopilotWork -Repo dfinke/trystuff -Work 'need a greet fn in Rust' .EXAMPLE Start-CopilotWork 'do it', 'do it x' dfinke/agenttodo, dfinke/trystuff .EXAMPLE Start-CopilotWork -Repo dfinke/trystuff -Path .\issues.md #> [CmdletBinding()] param( [Parameter(Position = 0)] [string[]]$Repo, [Parameter(Position = 1)] [string[]]$Work, [Parameter()] [string]$Path, [Parameter()] [switch]$AssignCopilot, [Parameter()] [switch]$Show ) function Test-RepoFormat($str) { return $str -match '^[^/]+/[^/]+$' } # Normalize input to arrays $Repo = @($Repo) | Where-Object { $_ } $Work = @($Work) | Where-Object { $_ } # Auto-detect and swap if needed based on repo format if ($Repo -and $Work) { $repoLookingItems = @($Repo) + @($Work) | Where-Object { Test-RepoFormat $_ } $workLookingItems = @($Repo) + @($Work) | Where-Object { -not (Test-RepoFormat $_) } if ($repoLookingItems.Count -gt 0 -and $workLookingItems.Count -gt 0) { # We have both repo-formatted and non-repo-formatted items, so reassign $Repo = $repoLookingItems $Work = $workLookingItems } } # If neither Repo nor Work is provided, error if (-not $Repo -and -not $Work -and -not $Path) { throw "You must provide either -Work, -Repo, or -Path." } # If Repo is empty, try to get it from the current directory using gh CLI if (-not $Repo -or $Repo.Count -eq 0) { if (-not (Get-Command gh -ErrorAction SilentlyContinue)) { Write-Host "The GitHub CLI 'gh' is not installed or not in PATH. Please install it." -ForegroundColor Yellow return } Write-ProgressMessage "No repository specified, attempting to detect from current directory" $repoInfo = gh repo view --json 'owner,name' 2>$null | ConvertFrom-Json if (-not $repoInfo) { Write-Host "Not in a GitHub repository directory. Please specify -Repo (owner/repo) or run in a git repo directory." -ForegroundColor Yellow return } $Repo = @("$($repoInfo.owner.login)/$($repoInfo.name)") Write-ProgressMessage "Repository detected: $($Repo[0])" } # Validate all repos Write-ProgressMessage "Validating $($Repo.Count) repository(ies)" foreach ($r in $Repo) { if (-not (Test-RepoFormat $r)) { throw "Repo '$r' is not in the format owner/repo." } if (-not (Test-RepoExists -Name $r)) { throw "Repository '$r' does not exist." } } Write-ProgressMessage "All repositories validated successfully" $results = @() if ($Path) { if (-not (Test-Path $Path)) { throw "File '$Path' does not exist." } Write-ProgressMessage "Reading content from file: $Path" $content = Get-Content -Path $Path -Raw $sections = $content -split '(?m)^---\s*$' | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' } Write-ProgressMessage "Found $($sections.Count) section(s) in the file" foreach ($r in $Repo) { foreach ($section in $sections) { $result = New-Issue -RepoName $r -Title "Copilot Request" -Body $section if ($AssignCopilot -and $result) { if ($result -match '/issues/(\d+)$') { $issueNumber = $matches[1] Set-CopilotIssueAssignee -Repo $r -IssueNumber $issueNumber Write-ProgressMessage "Code agent assigned to issue #$issueNumber" } } $results += $result } } if ($Show -and $results.Count -gt 0) { Write-ProgressMessage "Opening $($results.Count) issue(s) in browser" foreach ($url in $results) { Start-Process $url } } Write-ProgressMessage "Completed processing all sections. Total issues created: $($results.Count)" return $results } elseif ($Work -and $Repo) { Write-ProgressMessage "Processing $($Work.Count) work item(s) across $($Repo.Count) repository(ies)" foreach ($r in $Repo) { foreach ($w in $Work) { $result = New-Issue -RepoName $r -Title "Copilot Request" -Body $w if ($AssignCopilot -and $result) { if ($result -match '/issues/(\d+)$') { $issueNumber = $matches[1] Set-CopilotIssueAssignee -Repo $r -IssueNumber $issueNumber Write-ProgressMessage "Code agent assigned to issue #$issueNumber" } } if ($Show -and $result) { Start-Process $result } $results += $result } } Write-ProgressMessage "Completed processing all work items. Total issues created: $($results.Count)" return $results } else { throw "You must provide either -Work or -Path with content." } } |