# Generating PR Age Charts Build an interactive CLI tool that visualizes pull request age distribution for a GitHub repository using Copilot's built-in capabilities. > **Runnable example:** [recipe/pr-visualization.go](recipe/pr-visualization.go) > > ```bash > # Auto-detect from current git repo > go run recipe/pr-visualization.go > > # Specify a repo explicitly > go run recipe/pr-visualization.go -repo github/copilot-sdk > ``` ## Example scenario You want to understand how long PRs have been open in a repository. This tool detects the current Git repo or accepts a repo as input, then lets Copilot fetch PR data via the GitHub MCP Server and generate a chart image. ## Prerequisites ```bash go get github.com/github/copilot-sdk/go ``` ## Usage ```bash # Auto-detect from current git repo go run pr-visualization.go # Specify a repo explicitly go run pr-visualization.go -repo github/copilot-sdk ``` ## Full example: pr-visualization.go ```go package main import ( "bufio" "flag" "fmt" "log" "os" "os/exec" "regexp" "strings" "github.com/github/copilot-sdk/go" ) // ============================================================================ // Git & GitHub Detection // ============================================================================ func isGitRepo() bool { cmd := exec.Command("git", "rev-parse", "--git-dir") return cmd.Run() == nil } func getGitHubRemote() string { cmd := exec.Command("git", "remote", "get-url", "origin") output, err := cmd.Output() if err != nil { return "" } remoteURL := strings.TrimSpace(string(output)) // Handle SSH: git@github.com:owner/repo.git sshRe := regexp.MustCompile(`git@github\.com:(.+/.+?)(?:\.git)?$`) if matches := sshRe.FindStringSubmatch(remoteURL); matches != nil { return matches[1] } // Handle HTTPS: https://github.com/owner/repo.git httpsRe := regexp.MustCompile(`https://github\.com/(.+/.+?)(?:\.git)?$`) if matches := httpsRe.FindStringSubmatch(remoteURL); matches != nil { return matches[1] } return "" } func promptForRepo() string { reader := bufio.NewReader(os.Stdin) fmt.Print("Enter GitHub repo (owner/repo): ") repo, _ := reader.ReadString('\n') return strings.TrimSpace(repo) } // ============================================================================ // Main Application // ============================================================================ func main() { repoFlag := flag.String("repo", "", "GitHub repository (owner/repo)") flag.Parse() fmt.Println("šŸ” PR Age Chart Generator\n") // Determine the repository var repo string if *repoFlag != "" { repo = *repoFlag fmt.Printf("šŸ“¦ Using specified repo: %s\n", repo) } else if isGitRepo() { detected := getGitHubRemote() if detected != "" { repo = detected fmt.Printf("šŸ“¦ Detected GitHub repo: %s\n", repo) } else { fmt.Println("āš ļø Git repo found but no GitHub remote detected.") repo = promptForRepo() } } else { fmt.Println("šŸ“ Not in a git repository.") repo = promptForRepo() } if repo == "" || !strings.Contains(repo, "/") { log.Fatal("āŒ Invalid repo format. Expected: owner/repo") } parts := strings.SplitN(repo, "/", 2) owner, repoName := parts[0], parts[1] // Create Copilot client - no custom tools needed! client := copilot.NewClient(copilot.ClientConfig{LogLevel: "error"}) if err := client.Start(); err != nil { log.Fatal(err) } defer client.Stop() cwd, _ := os.Getwd() session, err := client.CreateSession(copilot.SessionConfig{ Model: "gpt-5", SystemMessage: copilot.SystemMessage{ Content: fmt.Sprintf(` You are analyzing pull requests for the GitHub repository: %s/%s The current working directory is: %s - Use the GitHub MCP Server tools to fetch PR data - Use your file and code execution tools to generate charts - Save any generated images to the current working directory - Be concise in your responses `, owner, repoName, cwd), }, }) if err != nil { log.Fatal(err) } defer session.Destroy() // Set up event handling session.On(func(event copilot.Event) { switch e := event.(type) { case copilot.AssistantMessageEvent: fmt.Printf("\nšŸ¤– %s\n\n", e.Data.Content) case copilot.ToolExecutionStartEvent: fmt.Printf(" āš™ļø %s\n", e.Data.ToolName) } }) // Initial prompt - let Copilot figure out the details fmt.Println("\nšŸ“Š Starting analysis...\n") prompt := fmt.Sprintf(` Fetch the open pull requests for %s/%s from the last week. Calculate the age of each PR in days. Then generate a bar chart image showing the distribution of PR ages (group them into sensible buckets like <1 day, 1-3 days, etc.). Save the chart as "pr-age-chart.png" in the current directory. Finally, summarize the PR health - average age, oldest PR, and how many might be considered stale. `, owner, repoName) if err := session.Send(copilot.MessageOptions{Prompt: prompt}); err != nil { log.Fatal(err) } session.WaitForIdle() // Interactive loop fmt.Println("\nšŸ’” Ask follow-up questions or type \"exit\" to quit.\n") fmt.Println("Examples:") fmt.Println(" - \"Expand to the last month\"") fmt.Println(" - \"Show me the 5 oldest PRs\"") fmt.Println(" - \"Generate a pie chart instead\"") fmt.Println(" - \"Group by author instead of age\"") fmt.Println() reader := bufio.NewReader(os.Stdin) for { fmt.Print("You: ") input, _ := reader.ReadString('\n') input = strings.TrimSpace(input) if input == "" { continue } if strings.ToLower(input) == "exit" || strings.ToLower(input) == "quit" { fmt.Println("šŸ‘‹ Goodbye!") break } if err := session.Send(copilot.MessageOptions{Prompt: input}); err != nil { log.Printf("Error: %v", err) } session.WaitForIdle() } } ``` ## How it works 1. **Repository detection**: Checks `--repo` flag → git remote → prompts user 2. **No custom tools**: Relies entirely on Copilot CLI's built-in capabilities: - **GitHub MCP Server** - Fetches PR data from GitHub - **File tools** - Saves generated chart images - **Code execution** - Generates charts using Python/matplotlib or other methods 3. **Interactive session**: After initial analysis, user can ask for adjustments ## Why this approach? | Aspect | Custom Tools | Built-in Copilot | | --------------- | ----------------- | --------------------------------- | | Code complexity | High | **Minimal** | | Maintenance | You maintain | **Copilot maintains** | | Flexibility | Fixed logic | **AI decides best approach** | | Chart types | What you coded | **Any type Copilot can generate** | | Data grouping | Hardcoded buckets | **Intelligent grouping** |