diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml index 46625294..63f2eeb2 100644 --- a/.github/dependabot.yaml +++ b/.github/dependabot.yaml @@ -17,10 +17,6 @@ updates: directory: '/test/harness' schedule: interval: 'weekly' - - package-ecosystem: 'npm' - directory: '/cookbook/nodejs/recipe' - schedule: - interval: 'weekly' # Python dependencies - package-ecosystem: 'pip' directory: '/python' diff --git a/README.md b/README.md index f0631f3d..a3303249 100644 --- a/README.md +++ b/README.md @@ -16,10 +16,10 @@ The GitHub Copilot SDK exposes the same engine behind Copilot CLI: a production- | SDK | Location | Installation | | ------------------------ | ------------------------------------------------- | ----------------------------------------- | -| **Node.js / TypeScript** | [`cookbook/nodejs/`](./cookbook/nodejs/README.md) | `npm install @github/copilot-sdk` | -| **Python** | [`cookbook/python/`](./cookbook/python/README.md) | `pip install github-copilot-sdk` | -| **Go** | [`cookbook/go/`](./cookbook/go/README.md) | `go get github.com/github/copilot-sdk/go` | -| **.NET** | [`cookbook/dotnet/`](./cookbook/dotnet/README.md) | `dotnet add package GitHub.Copilot.SDK` | +| **Node.js / TypeScript** | [`cookbook/nodejs/`](https://github.com/github/awesome-copilot/blob/main/cookbook/copilot-sdk/nodejs/README.md) | `npm install @github/copilot-sdk` | +| **Python** | [`cookbook/python/`](https://github.com/github/awesome-copilot/blob/main/cookbook/copilot-sdk/python/README.md) | `pip install github-copilot-sdk` | +| **Go** | [`cookbook/go/`](https://github.com/github/awesome-copilot/blob/main/cookbook/copilot-sdk/go/README.md) | `go get github.com/github/copilot-sdk/go` | +| **.NET** | [`cookbook/dotnet/`](https://github.com/github/awesome-copilot/blob/main/cookbook/copilot-sdk/dotnet/README.md) | `dotnet add package GitHub.Copilot.SDK` | See the individual SDK READMEs for installation, usage examples, and API reference. @@ -96,7 +96,7 @@ Please use the [GitHub Issues](https://github.com/github/copilot-sdk/issues) pag ## Quick Links - **[Getting Started](./docs/getting-started.md)** – Tutorial to get up and running -- **[Cookbook](./cookbook/README.md)** – Practical recipes for common tasks across all languages +- **[Cookbook](https://github.com/github/awesome-copilot/blob/main/cookbook/copilot-sdk)** – Practical recipes for common tasks across all languages - **[More Resources](https://github.com/github/awesome-copilot/blob/main/collections/copilot-sdk.md)** – Additional examples, tutorials, and community resources ## Unofficial, Community-maintained SDKs diff --git a/cookbook/README.md b/cookbook/README.md deleted file mode 100644 index 0e98de74..00000000 --- a/cookbook/README.md +++ /dev/null @@ -1,87 +0,0 @@ -# GitHub Copilot SDK Cookbook - -This cookbook collects small, focused recipes showing how to accomplish common tasks with the GitHub Copilot SDK across languages. Each recipe is intentionally short and practical, with copy‑pasteable snippets and pointers to fuller examples and tests. - -## Recipes by Language - -### .NET (C#) - -- [Error Handling](dotnet/error-handling.md): Handle errors gracefully including connection failures, timeouts, and cleanup. -- [Multiple Sessions](dotnet/multiple-sessions.md): Manage multiple independent conversations simultaneously. -- [Managing Local Files](dotnet/managing-local-files.md): Organize files by metadata using AI-powered grouping strategies. -- [PR Visualization](dotnet/pr-visualization.md): Generate interactive PR age charts using GitHub MCP Server. -- [Persisting Sessions](dotnet/persisting-sessions.md): Save and resume sessions across restarts. - -### Node.js / TypeScript - -- [Error Handling](nodejs/error-handling.md): Handle errors gracefully including connection failures, timeouts, and cleanup. -- [Multiple Sessions](nodejs/multiple-sessions.md): Manage multiple independent conversations simultaneously. -- [Managing Local Files](nodejs/managing-local-files.md): Organize files by metadata using AI-powered grouping strategies. -- [PR Visualization](nodejs/pr-visualization.md): Generate interactive PR age charts using GitHub MCP Server. -- [Persisting Sessions](nodejs/persisting-sessions.md): Save and resume sessions across restarts. - -### Python - -- [Error Handling](python/error-handling.md): Handle errors gracefully including connection failures, timeouts, and cleanup. -- [Multiple Sessions](python/multiple-sessions.md): Manage multiple independent conversations simultaneously. -- [Managing Local Files](python/managing-local-files.md): Organize files by metadata using AI-powered grouping strategies. -- [PR Visualization](python/pr-visualization.md): Generate interactive PR age charts using GitHub MCP Server. -- [Persisting Sessions](python/persisting-sessions.md): Save and resume sessions across restarts. - -### Go - -- [Error Handling](go/error-handling.md): Handle errors gracefully including connection failures, timeouts, and cleanup. -- [Multiple Sessions](go/multiple-sessions.md): Manage multiple independent conversations simultaneously. -- [Managing Local Files](go/managing-local-files.md): Organize files by metadata using AI-powered grouping strategies. -- [PR Visualization](go/pr-visualization.md): Generate interactive PR age charts using GitHub MCP Server. -- [Persisting Sessions](go/persisting-sessions.md): Save and resume sessions across restarts. - -## How to Use - -- Browse your language section above and open the recipe links -- Each recipe includes runnable examples in a `recipe/` subfolder with language-specific tooling -- See existing examples and tests for working references: - - Node.js examples: `nodejs/examples/basic-example.ts` - - E2E tests: `go/e2e`, `python/e2e`, `nodejs/test/e2e`, `dotnet/test/Harness` - -## Running Examples - -### .NET - -```bash -cd cookbook/dotnet/recipe -dotnet run .cs -``` - -### Node.js - -```bash -cd cookbook/nodejs/recipe -npm install -npx tsx .ts -``` - -### Python - -```bash -cd cookbook/python/recipe -pip install -r requirements.txt -python .py -``` - -### Go - -```bash -cd cookbook/go/recipe -go run .go -``` - -## Contributing - -- Propose or add a new recipe by creating a markdown file in your language's `cookbook/` folder and a runnable example in `recipe/` -- Follow repository guidance in [CONTRIBUTING.md](../CONTRIBUTING.md) - -## Status - -Cookbook structure is complete with 5 recipes across all 4 supported languages. Each recipe includes both markdown documentation and runnable examples. - diff --git a/cookbook/dotnet/README.md b/cookbook/dotnet/README.md deleted file mode 100644 index b37f70b2..00000000 --- a/cookbook/dotnet/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# GitHub Copilot SDK Cookbook — .NET (C#) - -This folder hosts short, practical recipes for using the GitHub Copilot SDK with .NET. Each recipe is concise, copy‑pasteable, and points to fuller examples and tests. - -## Recipes - -- [Error Handling](error-handling.md): Handle errors gracefully including connection failures, timeouts, and cleanup. -- [Multiple Sessions](multiple-sessions.md): Manage multiple independent conversations simultaneously. -- [Managing Local Files](managing-local-files.md): Organize files by metadata using AI-powered grouping strategies. -- [PR Visualization](pr-visualization.md): Generate interactive PR age charts using GitHub MCP Server. -- [Persisting Sessions](persisting-sessions.md): Save and resume sessions across restarts. - -## Contributing - -Add a new recipe by creating a markdown file in this folder and linking it above. Follow repository guidance in [CONTRIBUTING.md](../../CONTRIBUTING.md). - -## Status - -This README is a scaffold; recipe files are placeholders until populated. diff --git a/cookbook/dotnet/error-handling.md b/cookbook/dotnet/error-handling.md deleted file mode 100644 index d49aa248..00000000 --- a/cookbook/dotnet/error-handling.md +++ /dev/null @@ -1,156 +0,0 @@ -# Error Handling Patterns - -Handle errors gracefully in your Copilot SDK applications. - -> **Runnable example:** [recipe/error-handling.cs](recipe/error-handling.cs) -> -> ```bash -> dotnet run recipe/error-handling.cs -> ``` - -## Example scenario - -You need to handle various error conditions like connection failures, timeouts, and invalid responses. - -## Basic try-catch - -```csharp -using GitHub.Copilot.SDK; - -var client = new CopilotClient(); - -try -{ - await client.StartAsync(); - var session = await client.CreateSessionAsync(new SessionConfig - { - Model = "gpt-5" - }); - - var done = new TaskCompletionSource(); - session.On(evt => - { - if (evt is AssistantMessageEvent msg) - { - done.SetResult(msg.Data.Content); - } - }); - - await session.SendAsync(new MessageOptions { Prompt = "Hello!" }); - var response = await done.Task; - Console.WriteLine(response); - - await session.DisposeAsync(); -} -catch (Exception ex) -{ - Console.WriteLine($"Error: {ex.Message}"); -} -finally -{ - await client.StopAsync(); -} -``` - -## Handling specific error types - -```csharp -try -{ - await client.StartAsync(); -} -catch (FileNotFoundException) -{ - Console.WriteLine("Copilot CLI not found. Please install it first."); -} -catch (HttpRequestException ex) when (ex.Message.Contains("connection")) -{ - Console.WriteLine("Could not connect to Copilot CLI server."); -} -catch (Exception ex) -{ - Console.WriteLine($"Unexpected error: {ex.Message}"); -} -``` - -## Timeout handling - -```csharp -var session = await client.CreateSessionAsync(new SessionConfig { Model = "gpt-5" }); - -try -{ - var done = new TaskCompletionSource(); - session.On(evt => - { - if (evt is AssistantMessageEvent msg) - { - done.SetResult(msg.Data.Content); - } - }); - - await session.SendAsync(new MessageOptions { Prompt = "Complex question..." }); - - // Wait with timeout (30 seconds) - using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)); - var response = await done.Task.WaitAsync(cts.Token); - - Console.WriteLine(response); -} -catch (OperationCanceledException) -{ - Console.WriteLine("Request timed out"); -} -``` - -## Aborting a request - -```csharp -var session = await client.CreateSessionAsync(new SessionConfig { Model = "gpt-5" }); - -// Start a request -await session.SendAsync(new MessageOptions { Prompt = "Write a very long story..." }); - -// Abort it after some condition -await Task.Delay(5000); -await session.AbortAsync(); -Console.WriteLine("Request aborted"); -``` - -## Graceful shutdown - -```csharp -Console.CancelKeyPress += async (sender, e) => -{ - e.Cancel = true; - Console.WriteLine("Shutting down..."); - - var errors = await client.StopAsync(); - if (errors.Count > 0) - { - Console.WriteLine($"Cleanup errors: {string.Join(", ", errors)}"); - } - - Environment.Exit(0); -}; -``` - -## Using await using for automatic disposal - -```csharp -await using var client = new CopilotClient(); -await client.StartAsync(); - -var session = await client.CreateSessionAsync(new SessionConfig { Model = "gpt-5" }); - -// ... do work ... - -// client.StopAsync() is automatically called when exiting scope -``` - -## Best practices - -1. **Always clean up**: Use try-finally or `await using` to ensure `StopAsync()` is called -2. **Handle connection errors**: The CLI might not be installed or running -3. **Set appropriate timeouts**: Use `CancellationToken` for long-running requests -4. **Log errors**: Capture error details for debugging diff --git a/cookbook/dotnet/managing-local-files.md b/cookbook/dotnet/managing-local-files.md deleted file mode 100644 index da83515f..00000000 --- a/cookbook/dotnet/managing-local-files.md +++ /dev/null @@ -1,138 +0,0 @@ -# Grouping Files by Metadata - -Use Copilot to intelligently organize files in a folder based on their metadata. - -> **Runnable example:** [recipe/managing-local-files.cs](recipe/managing-local-files.cs) -> -> ```bash -> dotnet run recipe/managing-local-files.cs -> ``` - -## Example scenario - -You have a folder with many files and want to organize them into subfolders based on metadata like file type, creation date, size, or other attributes. Copilot can analyze the files and suggest or execute a grouping strategy. - -## Example code - -```csharp -using GitHub.Copilot.SDK; - -// Create and start client -await using var client = new CopilotClient(); -await client.StartAsync(); - -// Define tools for file operations -var session = await client.CreateSessionAsync(new SessionConfig -{ - Model = "gpt-5" -}); - -// Wait for completion -var done = new TaskCompletionSource(); - -session.On(evt => -{ - switch (evt) - { - case AssistantMessageEvent msg: - Console.WriteLine($"\nCopilot: {msg.Data.Content}"); - break; - case ToolExecutionStartEvent toolStart: - Console.WriteLine($" → Running: {toolStart.Data.ToolName} ({toolStart.Data.ToolCallId})"); - break; - case ToolExecutionCompleteEvent toolEnd: - Console.WriteLine($" ✓ Completed: {toolEnd.Data.ToolCallId}"); - break; - case SessionIdleEvent: - done.SetResult(); - break; - } -}); - -// Ask Copilot to organize files -var targetFolder = @"C:\Users\Me\Downloads"; - -await session.SendAsync(new MessageOptions -{ - Prompt = $""" - Analyze the files in "{targetFolder}" and organize them into subfolders. - - 1. First, list all files and their metadata - 2. Preview grouping by file extension - 3. Create appropriate subfolders (e.g., "images", "documents", "videos") - 4. Move each file to its appropriate subfolder - - Please confirm before moving any files. - """ -}); - -await done.Task; -``` - -## Grouping strategies - -### By file extension - -```csharp -// Groups files like: -// images/ -> .jpg, .png, .gif -// documents/ -> .pdf, .docx, .txt -// videos/ -> .mp4, .avi, .mov -``` - -### By creation date - -```csharp -// Groups files like: -// 2024-01/ -> files created in January 2024 -// 2024-02/ -> files created in February 2024 -``` - -### By file size - -```csharp -// Groups files like: -// tiny-under-1kb/ -// small-under-1mb/ -// medium-under-100mb/ -// large-over-100mb/ -``` - -## Dry-run mode - -For safety, you can ask Copilot to only preview changes: - -```csharp -await session.SendAsync(new MessageOptions -{ - Prompt = $""" - Analyze files in "{targetFolder}" and show me how you would organize them - by file type. DO NOT move any files - just show me the plan. - """ -}); -``` - -## Custom grouping with AI analysis - -Let Copilot determine the best grouping based on file content: - -```csharp -await session.SendAsync(new MessageOptions -{ - Prompt = $""" - Look at the files in "{targetFolder}" and suggest a logical organization. - Consider: - - File names and what they might contain - - File types and their typical uses - - Date patterns that might indicate projects or events - - Propose folder names that are descriptive and useful. - """ -}); -``` - -## Safety considerations - -1. **Confirm before moving**: Ask Copilot to confirm before executing moves -1. **Handle duplicates**: Consider what happens if a file with the same name exists -1. **Preserve originals**: Consider copying instead of moving for important files diff --git a/cookbook/dotnet/multiple-sessions.md b/cookbook/dotnet/multiple-sessions.md deleted file mode 100644 index 86633ca0..00000000 --- a/cookbook/dotnet/multiple-sessions.md +++ /dev/null @@ -1,79 +0,0 @@ -# Working with Multiple Sessions - -Manage multiple independent conversations simultaneously. - -> **Runnable example:** [recipe/multiple-sessions.cs](recipe/multiple-sessions.cs) -> -> ```bash -> dotnet run recipe/multiple-sessions.cs -> ``` - -## Example scenario - -You need to run multiple conversations in parallel, each with its own context and history. - -## C# - -```csharp -using GitHub.Copilot.SDK; - -await using var client = new CopilotClient(); -await client.StartAsync(); - -// Create multiple independent sessions -var session1 = await client.CreateSessionAsync(new SessionConfig { Model = "gpt-5" }); -var session2 = await client.CreateSessionAsync(new SessionConfig { Model = "gpt-5" }); -var session3 = await client.CreateSessionAsync(new SessionConfig { Model = "claude-sonnet-4.5" }); - -// Each session maintains its own conversation history -await session1.SendAsync(new MessageOptions { Prompt = "You are helping with a Python project" }); -await session2.SendAsync(new MessageOptions { Prompt = "You are helping with a TypeScript project" }); -await session3.SendAsync(new MessageOptions { Prompt = "You are helping with a Go project" }); - -// Follow-up messages stay in their respective contexts -await session1.SendAsync(new MessageOptions { Prompt = "How do I create a virtual environment?" }); -await session2.SendAsync(new MessageOptions { Prompt = "How do I set up tsconfig?" }); -await session3.SendAsync(new MessageOptions { Prompt = "How do I initialize a module?" }); - -// Clean up all sessions -await session1.DisposeAsync(); -await session2.DisposeAsync(); -await session3.DisposeAsync(); -``` - -## Custom session IDs - -Use custom IDs for easier tracking: - -```csharp -var session = await client.CreateSessionAsync(new SessionConfig -{ - SessionId = "user-123-chat", - Model = "gpt-5" -}); - -Console.WriteLine(session.SessionId); // "user-123-chat" -``` - -## Listing sessions - -```csharp -var sessions = await client.ListSessionsAsync(); -foreach (var sessionInfo in sessions) -{ - Console.WriteLine($"Session: {sessionInfo.SessionId}"); -} -``` - -## Deleting sessions - -```csharp -// Delete a specific session -await client.DeleteSessionAsync("user-123-chat"); -``` - -## Use cases - -- **Multi-user applications**: One session per user -- **Multi-task workflows**: Separate sessions for different tasks -- **A/B testing**: Compare responses from different models diff --git a/cookbook/dotnet/persisting-sessions.md b/cookbook/dotnet/persisting-sessions.md deleted file mode 100644 index e65cec38..00000000 --- a/cookbook/dotnet/persisting-sessions.md +++ /dev/null @@ -1,90 +0,0 @@ -# Session Persistence and Resumption - -Save and restore conversation sessions across application restarts. - -## Example scenario - -You want users to be able to continue a conversation even after closing and reopening your application. - -> **Runnable example:** [recipe/persisting-sessions.cs](recipe/persisting-sessions.cs) -> -> ```bash -> cd recipe -> dotnet run persisting-sessions.cs -> ``` - -### Creating a session with a custom ID - -```csharp -using GitHub.Copilot.SDK; - -await using var client = new CopilotClient(); -await client.StartAsync(); - -// Create session with a memorable ID -var session = await client.CreateSessionAsync(new SessionConfig -{ - SessionId = "user-123-conversation", - Model = "gpt-5" -}); - -await session.SendAsync(new MessageOptions { Prompt = "Let's discuss TypeScript generics" }); - -// Session ID is preserved -Console.WriteLine(session.SessionId); // "user-123-conversation" - -// Destroy session but keep data on disk -await session.DisposeAsync(); -await client.StopAsync(); -``` - -### Resuming a session - -```csharp -await using var client = new CopilotClient(); -await client.StartAsync(); - -// Resume the previous session -var session = await client.ResumeSessionAsync("user-123-conversation"); - -// Previous context is restored -await session.SendAsync(new MessageOptions { Prompt = "What were we discussing?" }); - -await session.DisposeAsync(); -await client.StopAsync(); -``` - -### Listing available sessions - -```csharp -var sessions = await client.ListSessionsAsync(); -foreach (var s in sessions) -{ - Console.WriteLine($"Session: {s.SessionId}"); -} -``` - -### Deleting a session permanently - -```csharp -// Remove session and all its data from disk -await client.DeleteSessionAsync("user-123-conversation"); -``` - -### Getting session history - -Retrieve all messages from a session: - -```csharp -var messages = await session.GetMessagesAsync(); -foreach (var msg in messages) -{ - Console.WriteLine($"[{msg.Type}] {msg.Data.Content}"); -} -``` - -## Best practices - -1. **Use meaningful session IDs**: Include user ID or context in the session ID -2. **Handle missing sessions**: Check if a session exists before resuming -3. **Clean up old sessions**: Periodically delete sessions that are no longer needed diff --git a/cookbook/dotnet/pr-visualization.md b/cookbook/dotnet/pr-visualization.md deleted file mode 100644 index 49f6ded0..00000000 --- a/cookbook/dotnet/pr-visualization.md +++ /dev/null @@ -1,257 +0,0 @@ -# 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.cs](recipe/pr-visualization.cs) -> -> ```bash -> # Auto-detect from current git repo -> dotnet run recipe/pr-visualization.cs -> -> # Specify a repo explicitly -> dotnet run recipe/pr-visualization.cs -- --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 -dotnet add package GitHub.Copilot.SDK -``` - -## Usage - -```bash -# Auto-detect from current git repo -dotnet run - -# Specify a repo explicitly -dotnet run -- --repo github/copilot-sdk -``` - -## Full example: Program.cs - -```csharp -using System.Diagnostics; -using GitHub.Copilot.SDK; - -// ============================================================================ -// Git & GitHub Detection -// ============================================================================ - -bool IsGitRepo() -{ - try - { - Process.Start(new ProcessStartInfo - { - FileName = "git", - Arguments = "rev-parse --git-dir", - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true - })?.WaitForExit(); - return true; - } - catch - { - return false; - } -} - -string? GetGitHubRemote() -{ - try - { - var proc = Process.Start(new ProcessStartInfo - { - FileName = "git", - Arguments = "remote get-url origin", - RedirectStandardOutput = true, - UseShellExecute = false, - CreateNoWindow = true - }); - - var remoteUrl = proc?.StandardOutput.ReadToEnd().Trim(); - proc?.WaitForExit(); - - if (string.IsNullOrEmpty(remoteUrl)) return null; - - // Handle SSH: git@github.com:owner/repo.git - var sshMatch = System.Text.RegularExpressions.Regex.Match( - remoteUrl, @"git@github\.com:(.+/.+?)(?:\.git)?$"); - if (sshMatch.Success) return sshMatch.Groups[1].Value; - - // Handle HTTPS: https://github.com/owner/repo.git - var httpsMatch = System.Text.RegularExpressions.Regex.Match( - remoteUrl, @"https://github\.com/(.+/.+?)(?:\.git)?$"); - if (httpsMatch.Success) return httpsMatch.Groups[1].Value; - - return null; - } - catch - { - return null; - } -} - -string? ParseRepoArg(string[] args) -{ - var repoIndex = Array.IndexOf(args, "--repo"); - if (repoIndex != -1 && repoIndex + 1 < args.Length) - { - return args[repoIndex + 1]; - } - return null; -} - -string PromptForRepo() -{ - Console.Write("Enter GitHub repo (owner/repo): "); - return Console.ReadLine()?.Trim() ?? ""; -} - -// ============================================================================ -// Main Application -// ============================================================================ - -Console.WriteLine("🔍 PR Age Chart Generator\n"); - -// Determine the repository -var repo = ParseRepoArg(args); - -if (!string.IsNullOrEmpty(repo)) -{ - Console.WriteLine($"📦 Using specified repo: {repo}"); -} -else if (IsGitRepo()) -{ - var detected = GetGitHubRemote(); - if (detected != null) - { - repo = detected; - Console.WriteLine($"📦 Detected GitHub repo: {repo}"); - } - else - { - Console.WriteLine("⚠️ Git repo found but no GitHub remote detected."); - repo = PromptForRepo(); - } -} -else -{ - Console.WriteLine("📁 Not in a git repository."); - repo = PromptForRepo(); -} - -if (string.IsNullOrEmpty(repo) || !repo.Contains('/')) -{ - Console.WriteLine("❌ Invalid repo format. Expected: owner/repo"); - return; -} - -var parts = repo.Split('/'); -var owner = parts[0]; -var repoName = parts[1]; - -// Create Copilot client - no custom tools needed! -await using var client = new CopilotClient(new CopilotClientOptions { LogLevel = "error" }); -await client.StartAsync(); - -var session = await client.CreateSessionAsync(new SessionConfig -{ - Model = "gpt-5", - SystemMessage = new SystemMessageConfig - { - Content = $""" - -You are analyzing pull requests for the GitHub repository: {owner}/{repoName} -The current working directory is: {Environment.CurrentDirectory} - - - -- 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 - -""" - } -}); - -// Set up event handling -session.On(evt => -{ - switch (evt) - { - case AssistantMessageEvent msg: - Console.WriteLine($"\n🤖 {msg.Data.Content}\n"); - break; - case ToolExecutionStartEvent toolStart: - Console.WriteLine($" ⚙️ {toolStart.Data.ToolName}"); - break; - } -}); - -// Initial prompt - let Copilot figure out the details -Console.WriteLine("\n📊 Starting analysis...\n"); - -await session.SendAsync(new MessageOptions -{ - Prompt = $""" - Fetch the open pull requests for {owner}/{repoName} 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. - """ -}); - -// Interactive loop -Console.WriteLine("\n💡 Ask follow-up questions or type \"exit\" to quit.\n"); -Console.WriteLine("Examples:"); -Console.WriteLine(" - \"Expand to the last month\""); -Console.WriteLine(" - \"Show me the 5 oldest PRs\""); -Console.WriteLine(" - \"Generate a pie chart instead\""); -Console.WriteLine(" - \"Group by author instead of age\""); -Console.WriteLine(); - -while (true) -{ - Console.Write("You: "); - var input = Console.ReadLine()?.Trim(); - - if (string.IsNullOrEmpty(input)) continue; - if (input.ToLower() is "exit" or "quit") - { - Console.WriteLine("👋 Goodbye!"); - break; - } - - await session.SendAsync(new MessageOptions { Prompt = input }); -} -``` - -## 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** | diff --git a/cookbook/dotnet/recipe/README.md b/cookbook/dotnet/recipe/README.md deleted file mode 100644 index 8394e426..00000000 --- a/cookbook/dotnet/recipe/README.md +++ /dev/null @@ -1,57 +0,0 @@ -# Runnable Recipe Examples - -This folder contains standalone, executable C# examples for each cookbook recipe. These are [file-based apps](https://learn.microsoft.com/en-us/dotnet/core/sdk/file-based-apps) that can be run directly with `dotnet run`. - -## Prerequisites - -- .NET 9.0 or later -- GitHub Copilot SDK package (referenced automatically) - -## Running Examples - -Each `.cs` file is a complete, runnable program. Simply use: - -```bash -dotnet run .cs -``` - -### Available Recipes - -| Recipe | Command | Description | -| -------------------- | ------------------------------------ | ------------------------------------------ | -| Error Handling | `dotnet run error-handling.cs` | Demonstrates error handling patterns | -| Multiple Sessions | `dotnet run multiple-sessions.cs` | Manages multiple independent conversations | -| Managing Local Files | `dotnet run managing-local-files.cs` | Organizes files using AI grouping | -| PR Visualization | `dotnet run pr-visualization.cs` | Generates PR age charts | -| Persisting Sessions | `dotnet run persisting-sessions.cs` | Save and resume sessions across restarts | - -### Examples with Arguments - -**PR Visualization with specific repo:** - -```bash -dotnet run pr-visualization.cs -- --repo github/copilot-sdk -``` - -**Managing Local Files (edit the file to change target folder):** - -```bash -# Edit the targetFolder variable in managing-local-files.cs first -dotnet run managing-local-files.cs -``` - -## File-Based Apps - -These examples use .NET's file-based app feature, which allows single-file C# programs to: - -- Run without a project file -- Automatically reference common packages -- Support top-level statements - -Each file includes `#:property PublishAot=false` to disable AOT compilation, ensuring compatibility with the Copilot SDK. - -## Learning Resources - -- [.NET File-Based Apps Documentation](https://learn.microsoft.com/en-us/dotnet/core/sdk/file-based-apps) -- [GitHub Copilot SDK Documentation](../../README.md) -- [Parent Cookbook](../README.md) diff --git a/cookbook/dotnet/recipe/error-handling.cs b/cookbook/dotnet/recipe/error-handling.cs deleted file mode 100644 index 957e6649..00000000 --- a/cookbook/dotnet/recipe/error-handling.cs +++ /dev/null @@ -1,38 +0,0 @@ -#:project ../../../dotnet/src/GitHub.Copilot.SDK.csproj -#:property PublishAot=false - -using GitHub.Copilot.SDK; - -var client = new CopilotClient(); - -try -{ - await client.StartAsync(); - var session = await client.CreateSessionAsync(new SessionConfig - { - Model = "gpt-5" - }); - - var done = new TaskCompletionSource(); - session.On(evt => - { - if (evt is AssistantMessageEvent msg) - { - done.SetResult(msg.Data.Content); - } - }); - - await session.SendAsync(new MessageOptions { Prompt = "Hello!" }); - var response = await done.Task; - Console.WriteLine(response); - - await session.DisposeAsync(); -} -catch (Exception ex) -{ - Console.WriteLine($"Error: {ex.Message}"); -} -finally -{ - await client.StopAsync(); -} diff --git a/cookbook/dotnet/recipe/managing-local-files.cs b/cookbook/dotnet/recipe/managing-local-files.cs deleted file mode 100644 index 17e31687..00000000 --- a/cookbook/dotnet/recipe/managing-local-files.cs +++ /dev/null @@ -1,56 +0,0 @@ -#:project ../../../dotnet/src/GitHub.Copilot.SDK.csproj -#:property PublishAot=false - -using GitHub.Copilot.SDK; - -// Create and start client -await using var client = new CopilotClient(); -await client.StartAsync(); - -// Define tools for file operations -var session = await client.CreateSessionAsync(new SessionConfig -{ - Model = "gpt-5" -}); - -// Wait for completion -var done = new TaskCompletionSource(); - -session.On(evt => -{ - switch (evt) - { - case AssistantMessageEvent msg: - Console.WriteLine($"\nCopilot: {msg.Data.Content}"); - break; - case ToolExecutionStartEvent toolStart: - Console.WriteLine($" → Running: {toolStart.Data.ToolName} ({toolStart.Data.ToolCallId})"); - break; - case ToolExecutionCompleteEvent toolEnd: - Console.WriteLine($" ✓ Completed: {toolEnd.Data.ToolCallId}"); - break; - case SessionIdleEvent: - done.SetResult(); - break; - } -}); - -// Ask Copilot to organize files -// Change this to your target folder -var targetFolder = @"C:\Users\Me\Downloads"; - -await session.SendAsync(new MessageOptions -{ - Prompt = $""" - Analyze the files in "{targetFolder}" and organize them into subfolders. - - 1. First, list all files and their metadata - 2. Preview grouping by file extension - 3. Create appropriate subfolders (e.g., "images", "documents", "videos") - 4. Move each file to its appropriate subfolder - - Please confirm before moving any files. - """ -}); - -await done.Task; diff --git a/cookbook/dotnet/recipe/multiple-sessions.cs b/cookbook/dotnet/recipe/multiple-sessions.cs deleted file mode 100644 index 31f88be3..00000000 --- a/cookbook/dotnet/recipe/multiple-sessions.cs +++ /dev/null @@ -1,35 +0,0 @@ -#:project ../../../dotnet/src/GitHub.Copilot.SDK.csproj -#:property PublishAot=false - -using GitHub.Copilot.SDK; - -await using var client = new CopilotClient(); -await client.StartAsync(); - -// Create multiple independent sessions -var session1 = await client.CreateSessionAsync(new SessionConfig { Model = "gpt-5" }); -var session2 = await client.CreateSessionAsync(new SessionConfig { Model = "gpt-5" }); -var session3 = await client.CreateSessionAsync(new SessionConfig { Model = "claude-sonnet-4.5" }); - -Console.WriteLine("Created 3 independent sessions"); - -// Each session maintains its own conversation history -await session1.SendAsync(new MessageOptions { Prompt = "You are helping with a Python project" }); -await session2.SendAsync(new MessageOptions { Prompt = "You are helping with a TypeScript project" }); -await session3.SendAsync(new MessageOptions { Prompt = "You are helping with a Go project" }); - -Console.WriteLine("Sent initial context to all sessions"); - -// Follow-up messages stay in their respective contexts -await session1.SendAsync(new MessageOptions { Prompt = "How do I create a virtual environment?" }); -await session2.SendAsync(new MessageOptions { Prompt = "How do I set up tsconfig?" }); -await session3.SendAsync(new MessageOptions { Prompt = "How do I initialize a module?" }); - -Console.WriteLine("Sent follow-up questions to each session"); - -// Clean up all sessions -await session1.DisposeAsync(); -await session2.DisposeAsync(); -await session3.DisposeAsync(); - -Console.WriteLine("All sessions destroyed successfully"); diff --git a/cookbook/dotnet/recipe/persisting-sessions.cs b/cookbook/dotnet/recipe/persisting-sessions.cs deleted file mode 100644 index 7b5af350..00000000 --- a/cookbook/dotnet/recipe/persisting-sessions.cs +++ /dev/null @@ -1,38 +0,0 @@ -#:project ../../../dotnet/src/GitHub.Copilot.SDK.csproj -#:property PublishAot=false - -using GitHub.Copilot.SDK; - -await using var client = new CopilotClient(); -await client.StartAsync(); - -// Create session with a memorable ID -var session = await client.CreateSessionAsync(new SessionConfig -{ - SessionId = "user-123-conversation", - Model = "gpt-5" -}); - -await session.SendAsync(new MessageOptions { Prompt = "Let's discuss TypeScript generics" }); -Console.WriteLine($"Session created: {session.SessionId}"); - -// Destroy session but keep data on disk -await session.DisposeAsync(); -Console.WriteLine("Session destroyed (state persisted)"); - -// Resume the previous session -var resumed = await client.ResumeSessionAsync("user-123-conversation"); -Console.WriteLine($"Resumed: {resumed.SessionId}"); - -await resumed.SendAsync(new MessageOptions { Prompt = "What were we discussing?" }); - -// List sessions -var sessions = await client.ListSessionsAsync(); -Console.WriteLine("Sessions: " + string.Join(", ", sessions.Select(s => s.SessionId))); - -// Delete session permanently -await client.DeleteSessionAsync("user-123-conversation"); -Console.WriteLine("Session deleted"); - -await resumed.DisposeAsync(); -await client.StopAsync(); diff --git a/cookbook/dotnet/recipe/pr-visualization.cs b/cookbook/dotnet/recipe/pr-visualization.cs deleted file mode 100644 index 256c9240..00000000 --- a/cookbook/dotnet/recipe/pr-visualization.cs +++ /dev/null @@ -1,204 +0,0 @@ -#:project ../../../dotnet/src/GitHub.Copilot.SDK.csproj -#:property PublishAot=false - -using System.Diagnostics; -using GitHub.Copilot.SDK; - -// ============================================================================ -// Git & GitHub Detection -// ============================================================================ - -bool IsGitRepo() -{ - try - { - var proc = Process.Start(new ProcessStartInfo - { - FileName = "git", - Arguments = "rev-parse --git-dir", - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true - }); - proc?.WaitForExit(); - return proc?.ExitCode == 0; - } - catch - { - return false; - } -} - -string? GetGitHubRemote() -{ - try - { - var proc = Process.Start(new ProcessStartInfo - { - FileName = "git", - Arguments = "remote get-url origin", - RedirectStandardOutput = true, - UseShellExecute = false, - CreateNoWindow = true - }); - - var remoteUrl = proc?.StandardOutput.ReadToEnd().Trim(); - proc?.WaitForExit(); - - if (string.IsNullOrEmpty(remoteUrl)) return null; - - // Handle SSH: git@github.com:owner/repo.git - var sshMatch = System.Text.RegularExpressions.Regex.Match( - remoteUrl, @"git@github\.com:(.+/.+?)(?:\.git)?$"); - if (sshMatch.Success) return sshMatch.Groups[1].Value; - - // Handle HTTPS: https://github.com/owner/repo.git - var httpsMatch = System.Text.RegularExpressions.Regex.Match( - remoteUrl, @"https://github\.com/(.+/.+?)(?:\.git)?$"); - if (httpsMatch.Success) return httpsMatch.Groups[1].Value; - - return null; - } - catch - { - return null; - } -} - -string? ParseRepoArg(string[] args) -{ - var repoIndex = Array.IndexOf(args, "--repo"); - if (repoIndex != -1 && repoIndex + 1 < args.Length) - { - return args[repoIndex + 1]; - } - return null; -} - -string PromptForRepo() -{ - Console.Write("Enter GitHub repo (owner/repo): "); - return Console.ReadLine()?.Trim() ?? ""; -} - -// ============================================================================ -// Main Application -// ============================================================================ - -Console.WriteLine("🔍 PR Age Chart Generator\n"); - -// Determine the repository -var repo = ParseRepoArg(args); - -if (!string.IsNullOrEmpty(repo)) -{ - Console.WriteLine($"📦 Using specified repo: {repo}"); -} -else if (IsGitRepo()) -{ - var detected = GetGitHubRemote(); - if (detected != null) - { - repo = detected; - Console.WriteLine($"📦 Detected GitHub repo: {repo}"); - } - else - { - Console.WriteLine("⚠️ Git repo found but no GitHub remote detected."); - repo = PromptForRepo(); - } -} -else -{ - Console.WriteLine("📁 Not in a git repository."); - repo = PromptForRepo(); -} - -if (string.IsNullOrEmpty(repo) || !repo.Contains('/')) -{ - Console.WriteLine("❌ Invalid repo format. Expected: owner/repo"); - return; -} - -var parts = repo.Split('/'); -var owner = parts[0]; -var repoName = parts[1]; - -// Create Copilot client - no custom tools needed! -await using var client = new CopilotClient(new CopilotClientOptions { LogLevel = "error" }); -await client.StartAsync(); - -var session = await client.CreateSessionAsync(new SessionConfig -{ - Model = "gpt-5", - SystemMessage = new SystemMessageConfig - { - Content = $""" - -You are analyzing pull requests for the GitHub repository: {owner}/{repoName} -The current working directory is: {Environment.CurrentDirectory} - - - -- 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 - -""" - } -}); - -// Set up event handling -session.On(evt => -{ - switch (evt) - { - case AssistantMessageEvent msg: - Console.WriteLine($"\n🤖 {msg.Data.Content}\n"); - break; - case ToolExecutionStartEvent toolStart: - Console.WriteLine($" ⚙️ {toolStart.Data.ToolName}"); - break; - } -}); - -// Initial prompt - let Copilot figure out the details -Console.WriteLine("\n📊 Starting analysis...\n"); - -await session.SendAsync(new MessageOptions -{ - Prompt = $""" - Fetch the open pull requests for {owner}/{repoName} 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. - """ -}); - -// Interactive loop -Console.WriteLine("\n💡 Ask follow-up questions or type \"exit\" to quit.\n"); -Console.WriteLine("Examples:"); -Console.WriteLine(" - \"Expand to the last month\""); -Console.WriteLine(" - \"Show me the 5 oldest PRs\""); -Console.WriteLine(" - \"Generate a pie chart instead\""); -Console.WriteLine(" - \"Group by author instead of age\""); -Console.WriteLine(); - -while (true) -{ - Console.Write("You: "); - var input = Console.ReadLine()?.Trim(); - - if (string.IsNullOrEmpty(input)) continue; - if (input.ToLower() is "exit" or "quit") - { - Console.WriteLine("👋 Goodbye!"); - break; - } - - await session.SendAsync(new MessageOptions { Prompt = input }); -} diff --git a/cookbook/go.sum b/cookbook/go.sum deleted file mode 100644 index 213d0035..00000000 --- a/cookbook/go.sum +++ /dev/null @@ -1,6 +0,0 @@ -github.com/github/copilot-sdk/go v0.1.18 h1:S1ocOfTKxiNGtj+/qp4z+RZeOr9hniqy3UqIIYZxsuQ= -github.com/github/copilot-sdk/go v0.1.18/go.mod h1:0SYT+64k347IDT0Trn4JHVFlUhPtGSE6ab479tU/+tY= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= -github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= diff --git a/cookbook/go/README.md b/cookbook/go/README.md deleted file mode 100644 index cedbe588..00000000 --- a/cookbook/go/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# GitHub Copilot SDK Cookbook — Go - -This folder hosts short, practical recipes for using the GitHub Copilot SDK with Go. Each recipe is concise, copy‑pasteable, and points to fuller examples and tests. - -## Recipes - -- [Error Handling](error-handling.md): Handle errors gracefully including connection failures, timeouts, and cleanup. -- [Multiple Sessions](multiple-sessions.md): Manage multiple independent conversations simultaneously. -- [Managing Local Files](managing-local-files.md): Organize files by metadata using AI-powered grouping strategies. -- [PR Visualization](pr-visualization.md): Generate interactive PR age charts using GitHub MCP Server. -- [Persisting Sessions](persisting-sessions.md): Save and resume sessions across restarts. - -## Contributing - -Add a new recipe by creating a markdown file in this folder and linking it above. Follow repository guidance in [CONTRIBUTING.md](../../CONTRIBUTING.md). - -## Status - -This README is a scaffold; recipe files are placeholders until populated. diff --git a/cookbook/go/error-handling.md b/cookbook/go/error-handling.md deleted file mode 100644 index ef292570..00000000 --- a/cookbook/go/error-handling.md +++ /dev/null @@ -1,206 +0,0 @@ -# Error Handling Patterns - -Handle errors gracefully in your Copilot SDK applications. - -> **Runnable example:** [recipe/error-handling.go](recipe/error-handling.go) -> -> ```bash -> go run recipe/error-handling.go -> ``` - -## Example scenario - -You need to handle various error conditions like connection failures, timeouts, and invalid responses. - -## Basic error handling - -```go -package main - -import ( - "fmt" - "log" - "github.com/github/copilot-sdk/go" -) - -func main() { - client := copilot.NewClient() - - if err := client.Start(); err != nil { - log.Fatalf("Failed to start client: %v", err) - } - defer func() { - if err := client.Stop(); err != nil { - log.Printf("Error stopping client: %v", err) - } - }() - - session, err := client.CreateSession(copilot.SessionConfig{ - Model: "gpt-5", - }) - if err != nil { - log.Fatalf("Failed to create session: %v", err) - } - defer session.Destroy() - - responseChan := make(chan string, 1) - session.On(func(event copilot.Event) { - if msg, ok := event.(copilot.AssistantMessageEvent); ok { - responseChan <- msg.Data.Content - } - }) - - if err := session.Send(copilot.MessageOptions{Prompt: "Hello!"}); err != nil { - log.Printf("Failed to send message: %v", err) - } - - response := <-responseChan - fmt.Println(response) -} -``` - -## Handling specific error types - -```go -import ( - "errors" - "os/exec" -) - -func startClient() error { - client := copilot.NewClient() - - if err := client.Start(); err != nil { - var execErr *exec.Error - if errors.As(err, &execErr) { - return fmt.Errorf("Copilot CLI not found. Please install it first: %w", err) - } - if errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("Could not connect to Copilot CLI server: %w", err) - } - return fmt.Errorf("Unexpected error: %w", err) - } - - return nil -} -``` - -## Timeout handling - -```go -import ( - "context" - "time" -) - -func sendWithTimeout(session *copilot.Session) error { - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - - responseChan := make(chan string, 1) - errChan := make(chan error, 1) - - session.On(func(event copilot.Event) { - if msg, ok := event.(copilot.AssistantMessageEvent); ok { - responseChan <- msg.Data.Content - } - }) - - if err := session.Send(copilot.MessageOptions{Prompt: "Complex question..."}); err != nil { - return err - } - - select { - case response := <-responseChan: - fmt.Println(response) - return nil - case err := <-errChan: - return err - case <-ctx.Done(): - return fmt.Errorf("request timed out") - } -} -``` - -## Aborting a request - -```go -func abortAfterDelay(session *copilot.Session) { - // Start a request - session.Send(copilot.MessageOptions{Prompt: "Write a very long story..."}) - - // Abort it after some condition - time.AfterFunc(5*time.Second, func() { - if err := session.Abort(); err != nil { - log.Printf("Failed to abort: %v", err) - } - fmt.Println("Request aborted") - }) -} -``` - -## Graceful shutdown - -```go -import ( - "os" - "os/signal" - "syscall" -) - -func main() { - client := copilot.NewClient() - - // Set up signal handling - sigChan := make(chan os.Signal, 1) - signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) - - go func() { - <-sigChan - fmt.Println("\nShutting down...") - - if err := client.Stop(); err != nil { - log.Printf("Cleanup errors: %v", err) - } - - os.Exit(0) - }() - - if err := client.Start(); err != nil { - log.Fatal(err) - } - - // ... do work ... -} -``` - -## Deferred cleanup pattern - -```go -func doWork() error { - client := copilot.NewClient() - - if err := client.Start(); err != nil { - return fmt.Errorf("failed to start: %w", err) - } - defer client.Stop() - - session, err := client.CreateSession(copilot.SessionConfig{Model: "gpt-5"}) - if err != nil { - return fmt.Errorf("failed to create session: %w", err) - } - defer session.Destroy() - - // ... do work ... - - return nil -} -``` - -## Best practices - -1. **Always clean up**: Use defer to ensure `Stop()` is called -2. **Handle connection errors**: The CLI might not be installed or running -3. **Set appropriate timeouts**: Use `context.WithTimeout` for long-running requests -4. **Log errors**: Capture error details for debugging -5. **Wrap errors**: Use `fmt.Errorf` with `%w` to preserve error chains diff --git a/cookbook/go/managing-local-files.md b/cookbook/go/managing-local-files.md deleted file mode 100644 index bfe25b18..00000000 --- a/cookbook/go/managing-local-files.md +++ /dev/null @@ -1,144 +0,0 @@ -# Grouping Files by Metadata - -Use Copilot to intelligently organize files in a folder based on their metadata. - -> **Runnable example:** [recipe/managing-local-files.go](recipe/managing-local-files.go) -> -> ```bash -> go run recipe/managing-local-files.go -> ``` - -## Example scenario - -You have a folder with many files and want to organize them into subfolders based on metadata like file type, creation date, size, or other attributes. Copilot can analyze the files and suggest or execute a grouping strategy. - -## Example code - -```go -package main - -import ( - "fmt" - "log" - "os" - "path/filepath" - "github.com/github/copilot-sdk/go" -) - -func main() { - // Create and start client - client := copilot.NewClient() - if err := client.Start(); err != nil { - log.Fatal(err) - } - defer client.Stop() - - // Create session - session, err := client.CreateSession(copilot.SessionConfig{ - Model: "gpt-5", - }) - if err != nil { - log.Fatal(err) - } - defer session.Destroy() - - // Event handler - session.On(func(event copilot.Event) { - switch e := event.(type) { - case copilot.AssistantMessageEvent: - fmt.Printf("\nCopilot: %s\n", e.Data.Content) - case copilot.ToolExecutionStartEvent: - fmt.Printf(" → Running: %s\n", e.Data.ToolName) - case copilot.ToolExecutionCompleteEvent: - fmt.Printf(" ✓ Completed: %s\n", e.Data.ToolName) - } - }) - - // Ask Copilot to organize files - homeDir, _ := os.UserHomeDir() - targetFolder := filepath.Join(homeDir, "Downloads") - - prompt := fmt.Sprintf(` -Analyze the files in "%s" and organize them into subfolders. - -1. First, list all files and their metadata -2. Preview grouping by file extension -3. Create appropriate subfolders (e.g., "images", "documents", "videos") -4. Move each file to its appropriate subfolder - -Please confirm before moving any files. -`, targetFolder) - - if err := session.Send(copilot.MessageOptions{Prompt: prompt}); err != nil { - log.Fatal(err) - } - - session.WaitForIdle() -} -``` - -## Grouping strategies - -### By file extension - -```go -// Groups files like: -// images/ -> .jpg, .png, .gif -// documents/ -> .pdf, .docx, .txt -// videos/ -> .mp4, .avi, .mov -``` - -### By creation date - -```go -// Groups files like: -// 2024-01/ -> files created in January 2024 -// 2024-02/ -> files created in February 2024 -``` - -### By file size - -```go -// Groups files like: -// tiny-under-1kb/ -// small-under-1mb/ -// medium-under-100mb/ -// large-over-100mb/ -``` - -## Dry-run mode - -For safety, you can ask Copilot to only preview changes: - -```go -prompt := fmt.Sprintf(` -Analyze files in "%s" and show me how you would organize them -by file type. DO NOT move any files - just show me the plan. -`, targetFolder) - -session.Send(copilot.MessageOptions{Prompt: prompt}) -``` - -## Custom grouping with AI analysis - -Let Copilot determine the best grouping based on file content: - -```go -prompt := fmt.Sprintf(` -Look at the files in "%s" and suggest a logical organization. -Consider: -- File names and what they might contain -- File types and their typical uses -- Date patterns that might indicate projects or events - -Propose folder names that are descriptive and useful. -`, targetFolder) - -session.Send(copilot.MessageOptions{Prompt: prompt}) -``` - -## Safety considerations - -1. **Confirm before moving**: Ask Copilot to confirm before executing moves -2. **Handle duplicates**: Consider what happens if a file with the same name exists -3. **Preserve originals**: Consider copying instead of moving for important files diff --git a/cookbook/go/multiple-sessions.md b/cookbook/go/multiple-sessions.md deleted file mode 100644 index 194c4f88..00000000 --- a/cookbook/go/multiple-sessions.md +++ /dev/null @@ -1,107 +0,0 @@ -# Working with Multiple Sessions - -Manage multiple independent conversations simultaneously. - -> **Runnable example:** [recipe/multiple-sessions.go](recipe/multiple-sessions.go) -> -> ```bash -> go run recipe/multiple-sessions.go -> ``` - -## Example scenario - -You need to run multiple conversations in parallel, each with its own context and history. - -## Go - -```go -package main - -import ( - "fmt" - "log" - "github.com/github/copilot-sdk/go" -) - -func main() { - client := copilot.NewClient() - - if err := client.Start(); err != nil { - log.Fatal(err) - } - defer client.Stop() - - // Create multiple independent sessions - session1, err := client.CreateSession(copilot.SessionConfig{Model: "gpt-5"}) - if err != nil { - log.Fatal(err) - } - defer session1.Destroy() - - session2, err := client.CreateSession(copilot.SessionConfig{Model: "gpt-5"}) - if err != nil { - log.Fatal(err) - } - defer session2.Destroy() - - session3, err := client.CreateSession(copilot.SessionConfig{Model: "claude-sonnet-4.5"}) - if err != nil { - log.Fatal(err) - } - defer session3.Destroy() - - // Each session maintains its own conversation history - session1.Send(copilot.MessageOptions{Prompt: "You are helping with a Python project"}) - session2.Send(copilot.MessageOptions{Prompt: "You are helping with a TypeScript project"}) - session3.Send(copilot.MessageOptions{Prompt: "You are helping with a Go project"}) - - // Follow-up messages stay in their respective contexts - session1.Send(copilot.MessageOptions{Prompt: "How do I create a virtual environment?"}) - session2.Send(copilot.MessageOptions{Prompt: "How do I set up tsconfig?"}) - session3.Send(copilot.MessageOptions{Prompt: "How do I initialize a module?"}) -} -``` - -## Custom session IDs - -Use custom IDs for easier tracking: - -```go -session, err := client.CreateSession(copilot.SessionConfig{ - SessionID: "user-123-chat", - Model: "gpt-5", -}) -if err != nil { - log.Fatal(err) -} - -fmt.Println(session.SessionID) // "user-123-chat" -``` - -## Listing sessions - -```go -sessions, err := client.ListSessions() -if err != nil { - log.Fatal(err) -} - -for _, sessionInfo := range sessions { - fmt.Printf("Session: %s\n", sessionInfo.SessionID) -} -``` - -## Deleting sessions - -```go -// Delete a specific session -if err := client.DeleteSession("user-123-chat"); err != nil { - log.Printf("Failed to delete session: %v", err) -} -``` - -## Use cases - -- **Multi-user applications**: One session per user -- **Multi-task workflows**: Separate sessions for different tasks -- **A/B testing**: Compare responses from different models diff --git a/cookbook/go/persisting-sessions.md b/cookbook/go/persisting-sessions.md deleted file mode 100644 index 4f63225c..00000000 --- a/cookbook/go/persisting-sessions.md +++ /dev/null @@ -1,92 +0,0 @@ -# Session Persistence and Resumption - -Save and restore conversation sessions across application restarts. - -## Example scenario - -You want users to be able to continue a conversation even after closing and reopening your application. - -> **Runnable example:** [recipe/persisting-sessions.go](recipe/persisting-sessions.go) -> -> ```bash -> cd recipe -> go run persisting-sessions.go -> ``` - -### Creating a session with a custom ID - -```go -package main - -import ( - "fmt" - "github.com/github/copilot-sdk/go" -) - -func main() { - client := copilot.NewClient() - client.Start() - defer client.Stop() - - // Create session with a memorable ID - session, _ := client.CreateSession(copilot.SessionConfig{ - SessionID: "user-123-conversation", - Model: "gpt-5", - }) - - session.Send(copilot.MessageOptions{Prompt: "Let's discuss TypeScript generics"}) - - // Session ID is preserved - fmt.Println(session.SessionID) - - // Destroy session but keep data on disk - session.Destroy() -} -``` - -### Resuming a session - -```go -client := copilot.NewClient() -client.Start() -defer client.Stop() - -// Resume the previous session -session, _ := client.ResumeSession("user-123-conversation") - -// Previous context is restored -session.Send(copilot.MessageOptions{Prompt: "What were we discussing?"}) - -session.Destroy() -``` - -### Listing available sessions - -```go -sessions, _ := client.ListSessions() -for _, s := range sessions { - fmt.Println("Session:", s.SessionID) -} -``` - -### Deleting a session permanently - -```go -// Remove session and all its data from disk -client.DeleteSession("user-123-conversation") -``` - -### Getting session history - -```go -messages, _ := session.GetMessages() -for _, msg := range messages { - fmt.Printf("[%s] %v\n", msg.Type, msg.Data) -} -``` - -## Best practices - -1. **Use meaningful session IDs**: Include user ID or context in the session ID -2. **Handle missing sessions**: Check if a session exists before resuming -3. **Clean up old sessions**: Periodically delete sessions that are no longer needed diff --git a/cookbook/go/pr-visualization.md b/cookbook/go/pr-visualization.md deleted file mode 100644 index 4a9184b9..00000000 --- a/cookbook/go/pr-visualization.md +++ /dev/null @@ -1,238 +0,0 @@ -# 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 main.go - -# Specify a repo explicitly -go run main.go --repo github/copilot-sdk -``` - -## Full example: main.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** | diff --git a/cookbook/go/recipe/README.md b/cookbook/go/recipe/README.md deleted file mode 100644 index 472e633b..00000000 --- a/cookbook/go/recipe/README.md +++ /dev/null @@ -1,61 +0,0 @@ -# Runnable Recipe Examples - -This folder contains standalone, executable Go examples for each cookbook recipe. Each file is a complete program that can be run directly with `go run`. - -## Prerequisites - -- Go 1.21 or later -- GitHub Copilot SDK for Go - -```bash -go get github.com/github/copilot-sdk/go -``` - -## Running Examples - -Each `.go` file is a complete, runnable program. Simply use: - -```bash -go run .go -``` - -### Available Recipes - -| Recipe | Command | Description | -| -------------------- | -------------------------------- | ------------------------------------------ | -| Error Handling | `go run error-handling.go` | Demonstrates error handling patterns | -| Multiple Sessions | `go run multiple-sessions.go` | Manages multiple independent conversations | -| Managing Local Files | `go run managing-local-files.go` | Organizes files using AI grouping | -| PR Visualization | `go run pr-visualization.go` | Generates PR age charts | -| Persisting Sessions | `go run persisting-sessions.go` | Save and resume sessions across restarts | - -### Examples with Arguments - -**PR Visualization with specific repo:** - -```bash -go run pr-visualization.go -repo github/copilot-sdk -``` - -**Managing Local Files (edit the file to change target folder):** - -```bash -# Edit the targetFolder variable in managing-local-files.go first -go run managing-local-files.go -``` - -## Go Best Practices - -These examples follow Go conventions: - -- Proper error handling with explicit checks -- Use of `defer` for cleanup -- Idiomatic naming (camelCase for local variables) -- Standard library usage where appropriate -- Clean separation of concerns - -## Learning Resources - -- [Go Documentation](https://go.dev/doc/) -- [GitHub Copilot SDK for Go](../../README.md) -- [Parent Cookbook](../README.md) diff --git a/cookbook/go/recipe/error-handling.go b/cookbook/go/recipe/error-handling.go deleted file mode 100644 index e2d80532..00000000 --- a/cookbook/go/recipe/error-handling.go +++ /dev/null @@ -1,44 +0,0 @@ -package main - -import ( - "fmt" - "log" - - "github.com/github/copilot-sdk/go" -) - -func main() { - client := copilot.NewClient() - - if err := client.Start(); err != nil { - log.Fatalf("Failed to start client: %v", err) - } - defer func() { - if err := client.Stop(); err != nil { - log.Printf("Error stopping client: %v", err) - } - }() - - session, err := client.CreateSession(copilot.SessionConfig{ - Model: "gpt-5", - }) - if err != nil { - log.Fatalf("Failed to create session: %v", err) - } - defer session.Destroy() - - responseChan := make(chan string, 1) - session.On(func(event copilot.Event) { - if msg, ok := event.(copilot.AssistantMessageEvent); ok { - responseChan <- msg.Data.Content - } - }) - - if err := session.Send(copilot.MessageOptions{Prompt: "Hello!"}); err != nil { - log.Printf("Failed to send message: %v", err) - return - } - - response := <-responseChan - fmt.Println(response) -} diff --git a/cookbook/go/recipe/managing-local-files.go b/cookbook/go/recipe/managing-local-files.go deleted file mode 100644 index 7304b336..00000000 --- a/cookbook/go/recipe/managing-local-files.go +++ /dev/null @@ -1,62 +0,0 @@ -package main - -import ( - "fmt" - "log" - "os" - "path/filepath" - - "github.com/github/copilot-sdk/go" -) - -func main() { - // Create and start client - client := copilot.NewClient() - if err := client.Start(); err != nil { - log.Fatal(err) - } - defer client.Stop() - - // Create session - session, err := client.CreateSession(copilot.SessionConfig{ - Model: "gpt-5", - }) - if err != nil { - log.Fatal(err) - } - defer session.Destroy() - - // Event handler - session.On(func(event copilot.Event) { - switch e := event.(type) { - case copilot.AssistantMessageEvent: - fmt.Printf("\nCopilot: %s\n", e.Data.Content) - case copilot.ToolExecutionStartEvent: - fmt.Printf(" → Running: %s\n", e.Data.ToolName) - case copilot.ToolExecutionCompleteEvent: - fmt.Printf(" ✓ Completed: %s\n", e.Data.ToolName) - } - }) - - // Ask Copilot to organize files - // Change this to your target folder - homeDir, _ := os.UserHomeDir() - targetFolder := filepath.Join(homeDir, "Downloads") - - prompt := fmt.Sprintf(` -Analyze the files in "%s" and organize them into subfolders. - -1. First, list all files and their metadata -2. Preview grouping by file extension -3. Create appropriate subfolders (e.g., "images", "documents", "videos") -4. Move each file to its appropriate subfolder - -Please confirm before moving any files. -`, targetFolder) - - if err := session.Send(copilot.MessageOptions{Prompt: prompt}); err != nil { - log.Fatal(err) - } - - session.WaitForIdle() -} diff --git a/cookbook/go/recipe/multiple-sessions.go b/cookbook/go/recipe/multiple-sessions.go deleted file mode 100644 index 3e97adbe..00000000 --- a/cookbook/go/recipe/multiple-sessions.go +++ /dev/null @@ -1,53 +0,0 @@ -package main - -import ( - "fmt" - "log" - - "github.com/github/copilot-sdk/go" -) - -func main() { - client := copilot.NewClient() - - if err := client.Start(); err != nil { - log.Fatal(err) - } - defer client.Stop() - - // Create multiple independent sessions - session1, err := client.CreateSession(copilot.SessionConfig{Model: "gpt-5"}) - if err != nil { - log.Fatal(err) - } - defer session1.Destroy() - - session2, err := client.CreateSession(copilot.SessionConfig{Model: "gpt-5"}) - if err != nil { - log.Fatal(err) - } - defer session2.Destroy() - - session3, err := client.CreateSession(copilot.SessionConfig{Model: "claude-sonnet-4.5"}) - if err != nil { - log.Fatal(err) - } - defer session3.Destroy() - - fmt.Println("Created 3 independent sessions") - - // Each session maintains its own conversation history - session1.Send(copilot.MessageOptions{Prompt: "You are helping with a Python project"}) - session2.Send(copilot.MessageOptions{Prompt: "You are helping with a TypeScript project"}) - session3.Send(copilot.MessageOptions{Prompt: "You are helping with a Go project"}) - - fmt.Println("Sent initial context to all sessions") - - // Follow-up messages stay in their respective contexts - session1.Send(copilot.MessageOptions{Prompt: "How do I create a virtual environment?"}) - session2.Send(copilot.MessageOptions{Prompt: "How do I set up tsconfig?"}) - session3.Send(copilot.MessageOptions{Prompt: "How do I initialize a module?"}) - - fmt.Println("Sent follow-up questions to each session") - fmt.Println("All sessions will be destroyed on exit") -} diff --git a/cookbook/go/recipe/persisting-sessions.go b/cookbook/go/recipe/persisting-sessions.go deleted file mode 100644 index d724bb58..00000000 --- a/cookbook/go/recipe/persisting-sessions.go +++ /dev/null @@ -1,68 +0,0 @@ -package main - -import ( - "fmt" - "log" - - "github.com/github/copilot-sdk/go" -) - -func main() { - client := copilot.NewClient() - if err := client.Start(); err != nil { - log.Fatal(err) - } - defer client.Stop() - - // Create session with a memorable ID - session, err := client.CreateSession(copilot.SessionConfig{ - SessionID: "user-123-conversation", - Model: "gpt-5", - }) - if err != nil { - log.Fatal(err) - } - - if err := session.Send(copilot.MessageOptions{Prompt: "Let's discuss TypeScript generics"}); err != nil { - log.Fatal(err) - } - fmt.Printf("Session created: %s\n", session.SessionID) - - // Destroy session but keep data on disk - if err := session.Destroy(); err != nil { - log.Fatal(err) - } - fmt.Println("Session destroyed (state persisted)") - - // Resume the previous session - resumed, err := client.ResumeSession("user-123-conversation") - if err != nil { - log.Fatal(err) - } - fmt.Printf("Resumed: %s\n", resumed.SessionID) - - if err := resumed.Send(copilot.MessageOptions{Prompt: "What were we discussing?"}); err != nil { - log.Fatal(err) - } - - // List sessions - sessions, err := client.ListSessions() - if err != nil { - log.Fatal(err) - } - ids := make([]string, 0, len(sessions)) - for _, s := range sessions { - ids = append(ids, s.SessionID) - } - fmt.Printf("Sessions: %v\n", ids) - - // Delete session permanently - if err := client.DeleteSession("user-123-conversation"); err != nil { - log.Fatal(err) - } - fmt.Println("Session deleted") - - if err := resumed.Destroy(); err != nil { - log.Fatal(err) - } -} diff --git a/cookbook/go/recipe/pr-visualization.go b/cookbook/go/recipe/pr-visualization.go deleted file mode 100644 index 54eb424f..00000000 --- a/cookbook/go/recipe/pr-visualization.go +++ /dev/null @@ -1,182 +0,0 @@ -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() - } -} diff --git a/cookbook/nodejs/README.md b/cookbook/nodejs/README.md deleted file mode 100644 index afe3aa75..00000000 --- a/cookbook/nodejs/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# GitHub Copilot SDK Cookbook — Node.js / TypeScript - -This folder hosts short, practical recipes for using the GitHub Copilot SDK with Node.js/TypeScript. Each recipe is concise, copy‑pasteable, and points to fuller examples and tests. - -## Recipes - -- [Error Handling](error-handling.md): Handle errors gracefully including connection failures, timeouts, and cleanup. -- [Multiple Sessions](multiple-sessions.md): Manage multiple independent conversations simultaneously. -- [Managing Local Files](managing-local-files.md): Organize files by metadata using AI-powered grouping strategies. -- [PR Visualization](pr-visualization.md): Generate interactive PR age charts using GitHub MCP Server. -- [Persisting Sessions](persisting-sessions.md): Save and resume sessions across restarts. - -## Contributing - -Add a new recipe by creating a markdown file in this folder and linking it above. Follow repository guidance in [CONTRIBUTING.md](../../CONTRIBUTING.md). - -## Status - -This README is a scaffold; recipe files are placeholders until populated. diff --git a/cookbook/nodejs/error-handling.md b/cookbook/nodejs/error-handling.md deleted file mode 100644 index 3bbcc1c7..00000000 --- a/cookbook/nodejs/error-handling.md +++ /dev/null @@ -1,129 +0,0 @@ -# Error Handling Patterns - -Handle errors gracefully in your Copilot SDK applications. - -> **Runnable example:** [recipe/error-handling.ts](recipe/error-handling.ts) -> -> ```bash -> cd recipe && npm install -> npx tsx error-handling.ts -> # or: npm run error-handling -> ``` - -## Example scenario - -You need to handle various error conditions like connection failures, timeouts, and invalid responses. - -## Basic try-catch - -```typescript -import { CopilotClient } from "@github/copilot-sdk"; - -const client = new CopilotClient(); - -try { - await client.start(); - const session = await client.createSession({ model: "gpt-5" }); - - const response = await session.sendAndWait({ prompt: "Hello!" }); - console.log(response?.data.content); - - await session.destroy(); -} catch (error) { - console.error("Error:", error.message); -} finally { - await client.stop(); -} -``` - -## Handling specific error types - -```typescript -try { - await client.start(); -} catch (error) { - if (error.message.includes("ENOENT")) { - console.error("Copilot CLI not found. Please install it first."); - } else if (error.message.includes("ECONNREFUSED")) { - console.error("Could not connect to Copilot CLI server."); - } else { - console.error("Unexpected error:", error.message); - } -} -``` - -## Timeout handling - -```typescript -const session = await client.createSession({ model: "gpt-5" }); - -try { - // sendAndWait with timeout (in milliseconds) - const response = await session.sendAndWait( - { prompt: "Complex question..." }, - 30000 // 30 second timeout - ); - - if (response) { - console.log(response.data.content); - } else { - console.log("No response received"); - } -} catch (error) { - if (error.message.includes("timeout")) { - console.error("Request timed out"); - } -} -``` - -## Aborting a request - -```typescript -const session = await client.createSession({ model: "gpt-5" }); - -// Start a request -session.send({ prompt: "Write a very long story..." }); - -// Abort it after some condition -setTimeout(async () => { - await session.abort(); - console.log("Request aborted"); -}, 5000); -``` - -## Graceful shutdown - -```typescript -process.on("SIGINT", async () => { - console.log("Shutting down..."); - - const errors = await client.stop(); - if (errors.length > 0) { - console.error("Cleanup errors:", errors); - } - - process.exit(0); -}); -``` - -## Force stop - -```typescript -// If stop() takes too long, force stop -const stopPromise = client.stop(); -const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), 5000)); - -try { - await Promise.race([stopPromise, timeout]); -} catch { - console.log("Forcing stop..."); - await client.forceStop(); -} -``` - -## Best practices - -1. **Always clean up**: Use try-finally to ensure `client.stop()` is called -2. **Handle connection errors**: The CLI might not be installed or running -3. **Set appropriate timeouts**: Long-running requests should have timeouts -4. **Log errors**: Capture error details for debugging diff --git a/cookbook/nodejs/managing-local-files.md b/cookbook/nodejs/managing-local-files.md deleted file mode 100644 index c32e8cf3..00000000 --- a/cookbook/nodejs/managing-local-files.md +++ /dev/null @@ -1,132 +0,0 @@ -# Grouping Files by Metadata - -Use Copilot to intelligently organize files in a folder based on their metadata. - -> **Runnable example:** [recipe/managing-local-files.ts](recipe/managing-local-files.ts) -> -> ```bash -> cd recipe && npm install -> npx tsx managing-local-files.ts -> # or: npm run managing-local-files -> ``` - -## Example scenario - -You have a folder with many files and want to organize them into subfolders based on metadata like file type, creation date, size, or other attributes. Copilot can analyze the files and suggest or execute a grouping strategy. - -## Example code - -```typescript -import { CopilotClient } from "@github/copilot-sdk"; -import * as os from "node:os"; -import * as path from "node:path"; - -// Create and start client -const client = new CopilotClient(); -await client.start(); - -// Create session -const session = await client.createSession({ - model: "gpt-5", -}); - -// Event handler -session.on((event) => { - switch (event.type) { - case "assistant.message": - console.log(`\nCopilot: ${event.data.content}`); - break; - case "tool.execution_start": - console.log(` → Running: ${event.data.toolName} ${event.data.toolCallId}`); - break; - case "tool.execution_complete": - console.log(` ✓ Completed: ${event.data.toolCallId}`); - break; - } -}); - -// Ask Copilot to organize files -const targetFolder = path.join(os.homedir(), "Downloads"); - -await session.sendAndWait({ - prompt: ` -Analyze the files in "${targetFolder}" and organize them into subfolders. - -1. First, list all files and their metadata -2. Preview grouping by file extension -3. Create appropriate subfolders (e.g., "images", "documents", "videos") -4. Move each file to its appropriate subfolder - -Please confirm before moving any files. -`, -}); - -await session.destroy(); -await client.stop(); -``` - -## Grouping strategies - -### By file extension - -```typescript -// Groups files like: -// images/ -> .jpg, .png, .gif -// documents/ -> .pdf, .docx, .txt -// videos/ -> .mp4, .avi, .mov -``` - -### By creation date - -```typescript -// Groups files like: -// 2024-01/ -> files created in January 2024 -// 2024-02/ -> files created in February 2024 -``` - -### By file size - -```typescript -// Groups files like: -// tiny-under-1kb/ -// small-under-1mb/ -// medium-under-100mb/ -// large-over-100mb/ -``` - -## Dry-run mode - -For safety, you can ask Copilot to only preview changes: - -```typescript -await session.sendAndWait({ - prompt: ` -Analyze files in "${targetFolder}" and show me how you would organize them -by file type. DO NOT move any files - just show me the plan. -`, -}); -``` - -## Custom grouping with AI analysis - -Let Copilot determine the best grouping based on file content: - -```typescript -await session.sendAndWait({ - prompt: ` -Look at the files in "${targetFolder}" and suggest a logical organization. -Consider: -- File names and what they might contain -- File types and their typical uses -- Date patterns that might indicate projects or events - -Propose folder names that are descriptive and useful. -`, -}); -``` - -## Safety considerations - -1. **Confirm before moving**: Ask Copilot to confirm before executing moves -2. **Handle duplicates**: Consider what happens if a file with the same name exists -3. **Preserve originals**: Consider copying instead of moving for important files diff --git a/cookbook/nodejs/multiple-sessions.md b/cookbook/nodejs/multiple-sessions.md deleted file mode 100644 index 5cae1c3c..00000000 --- a/cookbook/nodejs/multiple-sessions.md +++ /dev/null @@ -1,79 +0,0 @@ -# Working with Multiple Sessions - -Manage multiple independent conversations simultaneously. - -> **Runnable example:** [recipe/multiple-sessions.ts](recipe/multiple-sessions.ts) -> -> ```bash -> cd recipe && npm install -> npx tsx multiple-sessions.ts -> # or: npm run multiple-sessions -> ``` - -## Example scenario - -You need to run multiple conversations in parallel, each with its own context and history. - -## Node.js - -```typescript -import { CopilotClient } from "@github/copilot-sdk"; - -const client = new CopilotClient(); -await client.start(); - -// Create multiple independent sessions -const session1 = await client.createSession({ model: "gpt-5" }); -const session2 = await client.createSession({ model: "gpt-5" }); -const session3 = await client.createSession({ model: "claude-sonnet-4.5" }); - -// Each session maintains its own conversation history -await session1.sendAndWait({ prompt: "You are helping with a Python project" }); -await session2.sendAndWait({ prompt: "You are helping with a TypeScript project" }); -await session3.sendAndWait({ prompt: "You are helping with a Go project" }); - -// Follow-up messages stay in their respective contexts -await session1.sendAndWait({ prompt: "How do I create a virtual environment?" }); -await session2.sendAndWait({ prompt: "How do I set up tsconfig?" }); -await session3.sendAndWait({ prompt: "How do I initialize a module?" }); - -// Clean up all sessions -await session1.destroy(); -await session2.destroy(); -await session3.destroy(); -await client.stop(); -``` - -## Custom session IDs - -Use custom IDs for easier tracking: - -```typescript -const session = await client.createSession({ - sessionId: "user-123-chat", - model: "gpt-5", -}); - -console.log(session.sessionId); // "user-123-chat" -``` - -## Listing sessions - -```typescript -const sessions = await client.listSessions(); -console.log(sessions); -// [{ sessionId: "user-123-chat", ... }, ...] -``` - -## Deleting sessions - -```typescript -// Delete a specific session -await client.deleteSession("user-123-chat"); -``` - -## Use cases - -- **Multi-user applications**: One session per user -- **Multi-task workflows**: Separate sessions for different tasks -- **A/B testing**: Compare responses from different models diff --git a/cookbook/nodejs/persisting-sessions.md b/cookbook/nodejs/persisting-sessions.md deleted file mode 100644 index 67d77b19..00000000 --- a/cookbook/nodejs/persisting-sessions.md +++ /dev/null @@ -1,91 +0,0 @@ -# Session Persistence and Resumption - -Save and restore conversation sessions across application restarts. - -## Example scenario - -You want users to be able to continue a conversation even after closing and reopening your application. - -> **Runnable example:** [recipe/persisting-sessions.ts](recipe/persisting-sessions.ts) -> -> ```bash -> cd recipe && npm install -> npx tsx persisting-sessions.ts -> # or: npm run persisting-sessions -> ``` - -### Creating a session with a custom ID - -```typescript -import { CopilotClient } from "@github/copilot-sdk"; - -const client = new CopilotClient(); -await client.start(); - -// Create session with a memorable ID -const session = await client.createSession({ - sessionId: "user-123-conversation", - model: "gpt-5", -}); - -await session.sendAndWait({ prompt: "Let's discuss TypeScript generics" }); - -// Session ID is preserved -console.log(session.sessionId); // "user-123-conversation" - -// Destroy session but keep data on disk -await session.destroy(); -await client.stop(); -``` - -### Resuming a session - -```typescript -const client = new CopilotClient(); -await client.start(); - -// Resume the previous session -const session = await client.resumeSession("user-123-conversation"); - -// Previous context is restored -await session.sendAndWait({ prompt: "What were we discussing?" }); -// AI remembers the TypeScript generics discussion - -await session.destroy(); -await client.stop(); -``` - -### Listing available sessions - -```typescript -const sessions = await client.listSessions(); -console.log(sessions); -// [ -// { sessionId: "user-123-conversation", ... }, -// { sessionId: "user-456-conversation", ... }, -// ] -``` - -### Deleting a session permanently - -```typescript -// Remove session and all its data from disk -await client.deleteSession("user-123-conversation"); -``` - -## Getting session history - -Retrieve all messages from a session: - -```typescript -const messages = await session.getMessages(); -for (const msg of messages) { - console.log(`[${msg.type}]`, msg.data); -} -``` - -## Best practices - -1. **Use meaningful session IDs**: Include user ID or context in the session ID -2. **Handle missing sessions**: Check if a session exists before resuming -3. **Clean up old sessions**: Periodically delete sessions that are no longer needed diff --git a/cookbook/nodejs/pr-visualization.md b/cookbook/nodejs/pr-visualization.md deleted file mode 100644 index 049c3f7d..00000000 --- a/cookbook/nodejs/pr-visualization.md +++ /dev/null @@ -1,292 +0,0 @@ -# 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.ts](recipe/pr-visualization.ts) -> -> ```bash -> cd recipe && npm install -> # Auto-detect from current git repo -> npx tsx pr-visualization.ts -> -> # Specify a repo explicitly -> npx tsx pr-visualization.ts --repo github/copilot-sdk -> # or: npm run pr-visualization -> ``` - -## 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 -npm install @github/copilot-sdk -npm install -D typescript tsx @types/node -``` - -## Usage - -```bash -# Auto-detect from current git repo -npx tsx pr-breakdown.ts - -# Specify a repo explicitly -npx tsx pr-breakdown.ts --repo github/copilot-sdk -``` - -## Full example: pr-breakdown.ts - -```typescript -#!/usr/bin/env npx tsx - -import { execSync } from "node:child_process"; -import * as readline from "node:readline"; -import { CopilotClient } from "@github/copilot-sdk"; - -// ============================================================================ -// Git & GitHub Detection -// ============================================================================ - -function isGitRepo(): boolean { - try { - execSync("git rev-parse --git-dir", { stdio: "ignore" }); - return true; - } catch { - return false; - } -} - -function getGitHubRemote(): string | null { - try { - const remoteUrl = execSync("git remote get-url origin", { - encoding: "utf-8", - }).trim(); - - // Handle SSH: git@github.com:owner/repo.git - const sshMatch = remoteUrl.match(/git@github\.com:(.+\/.+?)(?:\.git)?$/); - if (sshMatch) return sshMatch[1]; - - // Handle HTTPS: https://github.com/owner/repo.git - const httpsMatch = remoteUrl.match(/https:\/\/github\.com\/(.+\/.+?)(?:\.git)?$/); - if (httpsMatch) return httpsMatch[1]; - - return null; - } catch { - return null; - } -} - -function parseArgs(): { repo?: string } { - const args = process.argv.slice(2); - const repoIndex = args.indexOf("--repo"); - if (repoIndex !== -1 && args[repoIndex + 1]) { - return { repo: args[repoIndex + 1] }; - } - return {}; -} - -async function promptForRepo(): Promise { - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - }); - return new Promise((resolve) => { - rl.question("Enter GitHub repo (owner/repo): ", (answer) => { - rl.close(); - resolve(answer.trim()); - }); - }); -} - -// ============================================================================ -// Main Application -// ============================================================================ - -async function main() { - console.log("🔍 PR Age Chart Generator\n"); - - // Determine the repository - const args = parseArgs(); - let repo: string; - - if (args.repo) { - repo = args.repo; - console.log(`📦 Using specified repo: ${repo}`); - } else if (isGitRepo()) { - const detected = getGitHubRemote(); - if (detected) { - repo = detected; - console.log(`📦 Detected GitHub repo: ${repo}`); - } else { - console.log("⚠️ Git repo found but no GitHub remote detected."); - repo = await promptForRepo(); - } - } else { - console.log("📁 Not in a git repository."); - repo = await promptForRepo(); - } - - if (!repo || !repo.includes("/")) { - console.error("❌ Invalid repo format. Expected: owner/repo"); - process.exit(1); - } - - const [owner, repoName] = repo.split("/"); - - // Create Copilot client - no custom tools needed! - const client = new CopilotClient({ logLevel: "error" }); - - const session = await client.createSession({ - model: "gpt-5", - systemMessage: { - content: ` - -You are analyzing pull requests for the GitHub repository: ${owner}/${repoName} -The current working directory is: ${process.cwd()} - - - -- 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 - -`, - }, - }); - - // Set up event handling - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - }); - - session.on((event) => { - if (event.type === "assistant.message") { - console.log(`\n🤖 ${event.data.content}\n`); - } else if (event.type === "tool.execution_start") { - console.log(` ⚙️ ${event.data.toolName}`); - } - }); - - // Initial prompt - let Copilot figure out the details - console.log("\n📊 Starting analysis...\n"); - - await session.sendAndWait({ - prompt: ` - Fetch the open pull requests for ${owner}/${repoName} 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. - `, - }); - - // Interactive loop - const askQuestion = () => { - rl.question("You: ", async (input) => { - const trimmed = input.trim(); - - if (trimmed.toLowerCase() === "exit" || trimmed.toLowerCase() === "quit") { - console.log("👋 Goodbye!"); - rl.close(); - await session.destroy(); - await client.stop(); - process.exit(0); - } - - if (trimmed) { - await session.sendAndWait({ prompt: trimmed }); - } - - askQuestion(); - }); - }; - - console.log('💡 Ask follow-up questions or type "exit" to quit.\n'); - console.log("Examples:"); - console.log(' - "Expand to the last month"'); - console.log(' - "Show me the 5 oldest PRs"'); - console.log(' - "Generate a pie chart instead"'); - console.log(' - "Group by author instead of age"'); - console.log(""); - - askQuestion(); -} - -main().catch(console.error); -``` - -## 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 - -## Sample interaction - -``` -🔍 PR Age Chart Generator - -📦 Using specified repo: CommunityToolkit/Aspire - -📊 Starting analysis... - - ⚙️ github-mcp-server-list_pull_requests - ⚙️ powershell - -🤖 I've analyzed 23 open PRs for CommunityToolkit/Aspire: - -**PR Age Distribution:** -- < 1 day: 3 PRs -- 1-3 days: 5 PRs -- 3-7 days: 8 PRs -- 1-2 weeks: 4 PRs -- > 2 weeks: 3 PRs - -**Summary:** -- Average age: 6.2 days -- Oldest: PR #142 (18 days) - "Add Redis caching support" -- Potentially stale (>7 days): 7 PRs - -Chart saved to: pr-age-chart.png - -💡 Ask follow-up questions or type "exit" to quit. - -You: Expand to the last month and show by author - - ⚙️ github-mcp-server-list_pull_requests - ⚙️ powershell - -🤖 Updated analysis for the last 30 days, grouped by author: - -| Author | Open PRs | Avg Age | -|---------------|----------|---------| -| @contributor1 | 5 | 12 days | -| @contributor2 | 3 | 4 days | -| @contributor3 | 2 | 8 days | -| ... | | | - -New chart saved to: pr-age-chart.png - -You: Generate a pie chart showing the age distribution - - ⚙️ powershell - -🤖 Done! Pie chart saved to: pr-age-chart.png -``` - -## 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** | diff --git a/cookbook/nodejs/recipe/README.md b/cookbook/nodejs/recipe/README.md deleted file mode 100644 index 73930d19..00000000 --- a/cookbook/nodejs/recipe/README.md +++ /dev/null @@ -1,84 +0,0 @@ -# Runnable Recipe Examples - -This folder contains standalone, executable TypeScript examples for each cookbook recipe. Each file can be run directly with `tsx` or via npm scripts. - -## Prerequisites - -- Node.js 18 or later -- Install dependencies (this links to the local SDK in the repo): - -```bash -npm install -``` - -## Running Examples - -Each `.ts` file is a complete, runnable program. You can run them in two ways: - -### Using npm scripts: - -```bash -npm run -``` - -### Using tsx directly: - -```bash -npx tsx .ts -``` - -### Available Recipes - -| Recipe | npm script | Direct command | Description | -| -------------------- | ------------------------------ | --------------------------------- | ------------------------------------------ | -| Error Handling | `npm run error-handling` | `npx tsx error-handling.ts` | Demonstrates error handling patterns | -| Multiple Sessions | `npm run multiple-sessions` | `npx tsx multiple-sessions.ts` | Manages multiple independent conversations | -| Managing Local Files | `npm run managing-local-files` | `npx tsx managing-local-files.ts` | Organizes files using AI grouping | -| PR Visualization | `npm run pr-visualization` | `npx tsx pr-visualization.ts` | Generates PR age charts | -| Persisting Sessions | `npm run persisting-sessions` | `npx tsx persisting-sessions.ts` | Save and resume sessions across restarts | - -### Examples with Arguments - -**PR Visualization with specific repo:** - -```bash -npx tsx pr-visualization.ts --repo github/copilot-sdk -``` - -**Managing Local Files (edit the file to change target folder):** - -```bash -# Edit the targetFolder variable in managing-local-files.ts first -npx tsx managing-local-files.ts -``` - -## Local SDK Development - -The `package.json` references the local Copilot SDK using `"file:../../.."`. This means: - -- Changes to the SDK source are immediately available -- No need to publish or install from npm -- Perfect for testing and development - -If you modify the SDK source, you may need to rebuild: - -```bash -cd ../../.. -npm run build -``` - -## TypeScript Features - -These examples use modern TypeScript/Node.js features: - -- Top-level await (requires `"type": "module"` in package.json) -- ESM imports -- Type safety with TypeScript -- async/await patterns - -## Learning Resources - -- [TypeScript Documentation](https://www.typescriptlang.org/docs/) -- [Node.js Documentation](https://nodejs.org/docs/latest/api/) -- [GitHub Copilot SDK for Node.js](../../README.md) -- [Parent Cookbook](../README.md) diff --git a/cookbook/nodejs/recipe/error-handling.ts b/cookbook/nodejs/recipe/error-handling.ts deleted file mode 100644 index e7ae0eaf..00000000 --- a/cookbook/nodejs/recipe/error-handling.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { CopilotClient } from "@github/copilot-sdk"; - -const client = new CopilotClient(); - -try { - await client.start(); - const session = await client.createSession({ model: "gpt-5" }); - - const response = await session.sendAndWait({ prompt: "Hello!" }); - console.log(response?.data.content); - - await session.destroy(); -} catch (error: any) { - console.error("Error:", error.message); -} finally { - await client.stop(); -} diff --git a/cookbook/nodejs/recipe/managing-local-files.ts b/cookbook/nodejs/recipe/managing-local-files.ts deleted file mode 100644 index 436b2bc9..00000000 --- a/cookbook/nodejs/recipe/managing-local-files.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { CopilotClient } from "@github/copilot-sdk"; -import * as os from "node:os"; -import * as path from "node:path"; - -// Create and start client -const client = new CopilotClient(); -await client.start(); - -// Create session -const session = await client.createSession({ - model: "gpt-5", -}); - -// Event handler -session.on((event) => { - switch (event.type) { - case "assistant.message": - console.log(`\nCopilot: ${event.data.content}`); - break; - case "tool.execution_start": - console.log(` → Running: ${event.data.toolName} ${event.data.toolCallId}`); - break; - case "tool.execution_complete": - console.log(` ✓ Completed: ${event.data.toolCallId}`); - break; - } -}); - -// Ask Copilot to organize files -// Change this to your target folder -const targetFolder = path.join(os.homedir(), "Downloads"); - -await session.sendAndWait({ - prompt: ` -Analyze the files in "${targetFolder}" and organize them into subfolders. - -1. First, list all files and their metadata -2. Preview grouping by file extension -3. Create appropriate subfolders (e.g., "images", "documents", "videos") -4. Move each file to its appropriate subfolder - -Please confirm before moving any files. -`, -}); - -await session.destroy(); -await client.stop(); diff --git a/cookbook/nodejs/recipe/multiple-sessions.ts b/cookbook/nodejs/recipe/multiple-sessions.ts deleted file mode 100644 index 6659b46e..00000000 --- a/cookbook/nodejs/recipe/multiple-sessions.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { CopilotClient } from "@github/copilot-sdk"; - -const client = new CopilotClient(); -await client.start(); - -// Create multiple independent sessions -const session1 = await client.createSession({ model: "gpt-5" }); -const session2 = await client.createSession({ model: "gpt-5" }); -const session3 = await client.createSession({ model: "claude-sonnet-4.5" }); - -console.log("Created 3 independent sessions"); - -// Each session maintains its own conversation history -await session1.sendAndWait({ prompt: "You are helping with a Python project" }); -await session2.sendAndWait({ prompt: "You are helping with a TypeScript project" }); -await session3.sendAndWait({ prompt: "You are helping with a Go project" }); - -console.log("Sent initial context to all sessions"); - -// Follow-up messages stay in their respective contexts -await session1.sendAndWait({ prompt: "How do I create a virtual environment?" }); -await session2.sendAndWait({ prompt: "How do I set up tsconfig?" }); -await session3.sendAndWait({ prompt: "How do I initialize a module?" }); - -console.log("Sent follow-up questions to each session"); - -// Clean up all sessions -await session1.destroy(); -await session2.destroy(); -await session3.destroy(); -await client.stop(); - -console.log("All sessions destroyed successfully"); diff --git a/cookbook/nodejs/recipe/package-lock.json b/cookbook/nodejs/recipe/package-lock.json deleted file mode 100644 index 0fea288f..00000000 --- a/cookbook/nodejs/recipe/package-lock.json +++ /dev/null @@ -1,629 +0,0 @@ -{ - "name": "copilot-sdk-cookbook-recipes", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "copilot-sdk-cookbook-recipes", - "version": "1.0.0", - "dependencies": { - "@github/copilot-sdk": "file:../../src" - }, - "devDependencies": { - "@types/node": "^22.19.7", - "tsx": "^4.19.2", - "typescript": "^5.7.2" - } - }, - "../..": { - "name": "@github/copilot-sdk", - "version": "0.1.8", - "license": "MIT", - "dependencies": { - "@github/copilot": "^0.0.388-1", - "vscode-jsonrpc": "^8.2.1", - "zod": "^4.3.5" - }, - "devDependencies": { - "@types/node": "^22.19.6", - "@typescript-eslint/eslint-plugin": "^8.0.0", - "@typescript-eslint/parser": "^8.0.0", - "esbuild": "^0.27.0", - "eslint": "^9.0.0", - "glob": "^11.0.0", - "json-schema": "^0.4.0", - "json-schema-to-typescript": "^15.0.4", - "prettier": "^3.4.0", - "quicktype-core": "^23.2.6", - "rimraf": "^6.1.2", - "semver": "^7.7.3", - "tsx": "^4.20.6", - "typescript": "^5.0.0", - "vitest": "^4.0.16" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "../../..": {}, - "../../src": {}, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", - "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", - "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", - "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", - "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", - "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", - "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", - "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", - "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", - "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", - "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", - "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", - "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", - "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", - "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", - "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", - "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", - "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", - "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", - "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", - "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", - "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", - "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", - "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", - "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", - "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", - "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@github/copilot-sdk": { - "resolved": "../../src", - "link": true - }, - "node_modules/@types/node": { - "version": "22.19.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.7.tgz", - "integrity": "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/esbuild": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", - "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.2", - "@esbuild/android-arm": "0.27.2", - "@esbuild/android-arm64": "0.27.2", - "@esbuild/android-x64": "0.27.2", - "@esbuild/darwin-arm64": "0.27.2", - "@esbuild/darwin-x64": "0.27.2", - "@esbuild/freebsd-arm64": "0.27.2", - "@esbuild/freebsd-x64": "0.27.2", - "@esbuild/linux-arm": "0.27.2", - "@esbuild/linux-arm64": "0.27.2", - "@esbuild/linux-ia32": "0.27.2", - "@esbuild/linux-loong64": "0.27.2", - "@esbuild/linux-mips64el": "0.27.2", - "@esbuild/linux-ppc64": "0.27.2", - "@esbuild/linux-riscv64": "0.27.2", - "@esbuild/linux-s390x": "0.27.2", - "@esbuild/linux-x64": "0.27.2", - "@esbuild/netbsd-arm64": "0.27.2", - "@esbuild/netbsd-x64": "0.27.2", - "@esbuild/openbsd-arm64": "0.27.2", - "@esbuild/openbsd-x64": "0.27.2", - "@esbuild/openharmony-arm64": "0.27.2", - "@esbuild/sunos-x64": "0.27.2", - "@esbuild/win32-arm64": "0.27.2", - "@esbuild/win32-ia32": "0.27.2", - "@esbuild/win32-x64": "0.27.2" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/get-tsconfig": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", - "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, - "node_modules/tsx": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", - "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "~0.27.0", - "get-tsconfig": "^4.7.5" - }, - "bin": { - "tsx": "dist/cli.mjs" - }, - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - } - } -} diff --git a/cookbook/nodejs/recipe/package.json b/cookbook/nodejs/recipe/package.json deleted file mode 100644 index 47a5de6e..00000000 --- a/cookbook/nodejs/recipe/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "copilot-sdk-cookbook-recipes", - "version": "1.0.0", - "type": "module", - "description": "Runnable examples for GitHub Copilot SDK cookbook recipes", - "scripts": { - "error-handling": "tsx error-handling.ts", - "multiple-sessions": "tsx multiple-sessions.ts", - "managing-local-files": "tsx managing-local-files.ts", - "pr-visualization": "tsx pr-visualization.ts", - "persisting-sessions": "tsx persisting-sessions.ts" - }, - "dependencies": { - "@github/copilot-sdk": "file:../../../nodejs/src" - }, - "devDependencies": { - "@types/node": "^22.19.7", - "tsx": "^4.19.2", - "typescript": "^5.7.2" - } -} diff --git a/cookbook/nodejs/recipe/persisting-sessions.ts b/cookbook/nodejs/recipe/persisting-sessions.ts deleted file mode 100644 index 7fea287d..00000000 --- a/cookbook/nodejs/recipe/persisting-sessions.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { CopilotClient } from "@github/copilot-sdk"; - -const client = new CopilotClient(); -await client.start(); - -// Create a session with a memorable ID -const session = await client.createSession({ - sessionId: "user-123-conversation", - model: "gpt-5", -}); - -await session.sendAndWait({ prompt: "Let's discuss TypeScript generics" }); -console.log(`Session created: ${session.sessionId}`); - -// Destroy session but keep data on disk -await session.destroy(); -console.log("Session destroyed (state persisted)"); - -// Resume the previous session -const resumed = await client.resumeSession("user-123-conversation"); -console.log(`Resumed: ${resumed.sessionId}`); - -await resumed.sendAndWait({ prompt: "What were we discussing?" }); - -// List sessions -const sessions = await client.listSessions(); -console.log( - "Sessions:", - sessions.map((s) => s.sessionId) -); - -// Delete session permanently -await client.deleteSession("user-123-conversation"); -console.log("Session deleted"); - -await resumed.destroy(); -await client.stop(); diff --git a/cookbook/nodejs/recipe/pr-visualization.ts b/cookbook/nodejs/recipe/pr-visualization.ts deleted file mode 100644 index f8f90a44..00000000 --- a/cookbook/nodejs/recipe/pr-visualization.ts +++ /dev/null @@ -1,179 +0,0 @@ -#!/usr/bin/env tsx - -import { CopilotClient } from "@github/copilot-sdk"; -import { execSync } from "node:child_process"; -import * as readline from "node:readline"; - -// ============================================================================ -// Git & GitHub Detection -// ============================================================================ - -function isGitRepo(): boolean { - try { - execSync("git rev-parse --git-dir", { stdio: "ignore" }); - return true; - } catch { - return false; - } -} - -function getGitHubRemote(): string | null { - try { - const remoteUrl = execSync("git remote get-url origin", { - encoding: "utf-8", - }).trim(); - - // Handle SSH: git@github.com:owner/repo.git - const sshMatch = remoteUrl.match(/git@github\.com:(.+\/.+?)(?:\.git)?$/); - if (sshMatch) return sshMatch[1]; - - // Handle HTTPS: https://github.com/owner/repo.git - const httpsMatch = remoteUrl.match(/https:\/\/github\.com\/(.+\/.+?)(?:\.git)?$/); - if (httpsMatch) return httpsMatch[1]; - - return null; - } catch { - return null; - } -} - -function parseArgs(): { repo?: string } { - const args = process.argv.slice(2); - const repoIndex = args.indexOf("--repo"); - if (repoIndex !== -1 && args[repoIndex + 1]) { - return { repo: args[repoIndex + 1] }; - } - return {}; -} - -async function promptForRepo(): Promise { - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - }); - return new Promise((resolve) => { - rl.question("Enter GitHub repo (owner/repo): ", (answer) => { - rl.close(); - resolve(answer.trim()); - }); - }); -} - -// ============================================================================ -// Main Application -// ============================================================================ - -async function main() { - console.log("🔍 PR Age Chart Generator\n"); - - // Determine the repository - const args = parseArgs(); - let repo: string; - - if (args.repo) { - repo = args.repo; - console.log(`📦 Using specified repo: ${repo}`); - } else if (isGitRepo()) { - const detected = getGitHubRemote(); - if (detected) { - repo = detected; - console.log(`📦 Detected GitHub repo: ${repo}`); - } else { - console.log("⚠️ Git repo found but no GitHub remote detected."); - repo = await promptForRepo(); - } - } else { - console.log("📁 Not in a git repository."); - repo = await promptForRepo(); - } - - if (!repo || !repo.includes("/")) { - console.error("❌ Invalid repo format. Expected: owner/repo"); - process.exit(1); - } - - const [owner, repoName] = repo.split("/"); - - // Create Copilot client - no custom tools needed! - const client = new CopilotClient({ logLevel: "error" }); - - const session = await client.createSession({ - model: "gpt-5", - systemMessage: { - content: ` - -You are analyzing pull requests for the GitHub repository: ${owner}/${repoName} -The current working directory is: ${process.cwd()} - - - -- 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 - -`, - }, - }); - - // Set up event handling - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - }); - - session.on((event) => { - if (event.type === "assistant.message") { - console.log(`\n🤖 ${event.data.content}\n`); - } else if (event.type === "tool.execution_start") { - console.log(` ⚙️ ${event.data.toolName}`); - } - }); - - // Initial prompt - let Copilot figure out the details - console.log("\n📊 Starting analysis...\n"); - - await session.sendAndWait({ - prompt: ` - Fetch the open pull requests for ${owner}/${repoName} 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. - `, - }); - - // Interactive loop - const askQuestion = () => { - rl.question("You: ", async (input) => { - const trimmed = input.trim(); - - if (trimmed.toLowerCase() === "exit" || trimmed.toLowerCase() === "quit") { - console.log("👋 Goodbye!"); - rl.close(); - await session.destroy(); - await client.stop(); - process.exit(0); - } - - if (trimmed) { - await session.sendAndWait({ prompt: trimmed }); - } - - askQuestion(); - }); - }; - - console.log('💡 Ask follow-up questions or type "exit" to quit.\n'); - console.log("Examples:"); - console.log(' - "Expand to the last month"'); - console.log(' - "Show me the 5 oldest PRs"'); - console.log(' - "Generate a pie chart instead"'); - console.log(' - "Group by author instead of age"'); - console.log(""); - - askQuestion(); -} - -main().catch(console.error); diff --git a/cookbook/python/README.md b/cookbook/python/README.md deleted file mode 100644 index 885c8be1..00000000 --- a/cookbook/python/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# GitHub Copilot SDK Cookbook — Python - -This folder hosts short, practical recipes for using the GitHub Copilot SDK with Python. Each recipe is concise, copy‑pasteable, and points to fuller examples and tests. - -## Recipes - -- [Error Handling](error-handling.md): Handle errors gracefully including connection failures, timeouts, and cleanup. -- [Multiple Sessions](multiple-sessions.md): Manage multiple independent conversations simultaneously. -- [Managing Local Files](managing-local-files.md): Organize files by metadata using AI-powered grouping strategies. -- [PR Visualization](pr-visualization.md): Generate interactive PR age charts using GitHub MCP Server. -- [Persisting Sessions](persisting-sessions.md): Save and resume sessions across restarts. - -## Contributing - -Add a new recipe by creating a markdown file in this folder and linking it above. Follow repository guidance in [CONTRIBUTING.md](../../CONTRIBUTING.md). - -## Status - -This README is a scaffold; recipe files are placeholders until populated. diff --git a/cookbook/python/error-handling.md b/cookbook/python/error-handling.md deleted file mode 100644 index 63d1488d..00000000 --- a/cookbook/python/error-handling.md +++ /dev/null @@ -1,150 +0,0 @@ -# Error Handling Patterns - -Handle errors gracefully in your Copilot SDK applications. - -> **Runnable example:** [recipe/error_handling.py](recipe/error_handling.py) -> -> ```bash -> cd recipe && pip install -r requirements.txt -> python error_handling.py -> ``` - -## Example scenario - -You need to handle various error conditions like connection failures, timeouts, and invalid responses. - -## Basic try-except - -```python -from copilot import CopilotClient - -client = CopilotClient() - -try: - client.start() - session = client.create_session(model="gpt-5") - - response = None - def handle_message(event): - nonlocal response - if event["type"] == "assistant.message": - response = event["data"]["content"] - - session.on(handle_message) - session.send(prompt="Hello!") - session.wait_for_idle() - - if response: - print(response) - - session.destroy() -except Exception as e: - print(f"Error: {e}") -finally: - client.stop() -``` - -## Handling specific error types - -```python -import subprocess - -try: - client.start() -except FileNotFoundError: - print("Copilot CLI not found. Please install it first.") -except ConnectionError: - print("Could not connect to Copilot CLI server.") -except Exception as e: - print(f"Unexpected error: {e}") -``` - -## Timeout handling - -```python -import signal -from contextlib import contextmanager - -@contextmanager -def timeout(seconds): - def timeout_handler(signum, frame): - raise TimeoutError("Request timed out") - - old_handler = signal.signal(signal.SIGALRM, timeout_handler) - signal.alarm(seconds) - try: - yield - finally: - signal.alarm(0) - signal.signal(signal.SIGALRM, old_handler) - -session = client.create_session(model="gpt-5") - -try: - session.send(prompt="Complex question...") - - # Wait with timeout (30 seconds) - with timeout(30): - session.wait_for_idle() - - print("Response received") -except TimeoutError: - print("Request timed out") -``` - -## Aborting a request - -```python -import threading - -session = client.create_session(model="gpt-5") - -# Start a request -session.send(prompt="Write a very long story...") - -# Abort it after some condition -def abort_later(): - import time - time.sleep(5) - session.abort() - print("Request aborted") - -threading.Thread(target=abort_later).start() -``` - -## Graceful shutdown - -```python -import signal -import sys - -def signal_handler(sig, frame): - print("\nShutting down...") - errors = client.stop() - if errors: - print(f"Cleanup errors: {errors}") - sys.exit(0) - -signal.signal(signal.SIGINT, signal_handler) -``` - -## Context manager for automatic cleanup - -```python -from copilot import CopilotClient - -with CopilotClient() as client: - client.start() - session = client.create_session(model="gpt-5") - - # ... do work ... - - # client.stop() is automatically called when exiting context -``` - -## Best practices - -1. **Always clean up**: Use try-finally or context managers to ensure `stop()` is called -2. **Handle connection errors**: The CLI might not be installed or running -3. **Set appropriate timeouts**: Long-running requests should have timeouts -4. **Log errors**: Capture error details for debugging diff --git a/cookbook/python/managing-local-files.md b/cookbook/python/managing-local-files.md deleted file mode 100644 index a085c538..00000000 --- a/cookbook/python/managing-local-files.md +++ /dev/null @@ -1,119 +0,0 @@ -# Grouping Files by Metadata - -Use Copilot to intelligently organize files in a folder based on their metadata. - -> **Runnable example:** [recipe/managing_local_files.py](recipe/managing_local_files.py) -> -> ```bash -> cd recipe && pip install -r requirements.txt -> python managing_local_files.py -> ``` - -## Example scenario - -You have a folder with many files and want to organize them into subfolders based on metadata like file type, creation date, size, or other attributes. Copilot can analyze the files and suggest or execute a grouping strategy. - -## Example code - -```python -from copilot import CopilotClient -import os - -# Create and start client -client = CopilotClient() -client.start() - -# Create session -session = client.create_session(model="gpt-5") - -# Event handler -def handle_event(event): - if event["type"] == "assistant.message": - print(f"\nCopilot: {event['data']['content']}") - elif event["type"] == "tool.execution_start": - print(f" → Running: {event['data']['toolName']}") - elif event["type"] == "tool.execution_complete": - print(f" ✓ Completed: {event['data']['toolCallId']}") - -session.on(handle_event) - -# Ask Copilot to organize files -target_folder = os.path.expanduser("~/Downloads") - -session.send(prompt=f""" -Analyze the files in "{target_folder}" and organize them into subfolders. - -1. First, list all files and their metadata -2. Preview grouping by file extension -3. Create appropriate subfolders (e.g., "images", "documents", "videos") -4. Move each file to its appropriate subfolder - -Please confirm before moving any files. -""") - -session.wait_for_idle() - -client.stop() -``` - -## Grouping strategies - -### By file extension - -```python -# Groups files like: -# images/ -> .jpg, .png, .gif -# documents/ -> .pdf, .docx, .txt -# videos/ -> .mp4, .avi, .mov -``` - -### By creation date - -```python -# Groups files like: -# 2024-01/ -> files created in January 2024 -# 2024-02/ -> files created in February 2024 -``` - -### By file size - -```python -# Groups files like: -# tiny-under-1kb/ -# small-under-1mb/ -# medium-under-100mb/ -# large-over-100mb/ -``` - -## Dry-run mode - -For safety, you can ask Copilot to only preview changes: - -```python -session.send(prompt=f""" -Analyze files in "{target_folder}" and show me how you would organize them -by file type. DO NOT move any files - just show me the plan. -""") -``` - -## Custom grouping with AI analysis - -Let Copilot determine the best grouping based on file content: - -```python -session.send(prompt=f""" -Look at the files in "{target_folder}" and suggest a logical organization. -Consider: -- File names and what they might contain -- File types and their typical uses -- Date patterns that might indicate projects or events - -Propose folder names that are descriptive and useful. -""") -``` - -## Safety considerations - -1. **Confirm before moving**: Ask Copilot to confirm before executing moves -2. **Handle duplicates**: Consider what happens if a file with the same name exists -3. **Preserve originals**: Consider copying instead of moving for important files diff --git a/cookbook/python/multiple-sessions.md b/cookbook/python/multiple-sessions.md deleted file mode 100644 index 6e0cff41..00000000 --- a/cookbook/python/multiple-sessions.md +++ /dev/null @@ -1,78 +0,0 @@ -# Working with Multiple Sessions - -Manage multiple independent conversations simultaneously. - -> **Runnable example:** [recipe/multiple_sessions.py](recipe/multiple_sessions.py) -> -> ```bash -> cd recipe && pip install -r requirements.txt -> python multiple_sessions.py -> ``` - -## Example scenario - -You need to run multiple conversations in parallel, each with its own context and history. - -## Python - -```python -from copilot import CopilotClient - -client = CopilotClient() -client.start() - -# Create multiple independent sessions -session1 = client.create_session(model="gpt-5") -session2 = client.create_session(model="gpt-5") -session3 = client.create_session(model="claude-sonnet-4.5") - -# Each session maintains its own conversation history -session1.send(prompt="You are helping with a Python project") -session2.send(prompt="You are helping with a TypeScript project") -session3.send(prompt="You are helping with a Go project") - -# Follow-up messages stay in their respective contexts -session1.send(prompt="How do I create a virtual environment?") -session2.send(prompt="How do I set up tsconfig?") -session3.send(prompt="How do I initialize a module?") - -# Clean up all sessions -session1.destroy() -session2.destroy() -session3.destroy() -client.stop() -``` - -## Custom session IDs - -Use custom IDs for easier tracking: - -```python -session = client.create_session( - session_id="user-123-chat", - model="gpt-5" -) - -print(session.session_id) # "user-123-chat" -``` - -## Listing sessions - -```python -sessions = client.list_sessions() -for session_info in sessions: - print(f"Session: {session_info['sessionId']}") -``` - -## Deleting sessions - -```python -# Delete a specific session -client.delete_session("user-123-chat") -``` - -## Use cases - -- **Multi-user applications**: One session per user -- **Multi-task workflows**: Separate sessions for different tasks -- **A/B testing**: Compare responses from different models diff --git a/cookbook/python/persisting-sessions.md b/cookbook/python/persisting-sessions.md deleted file mode 100644 index e0dfb797..00000000 --- a/cookbook/python/persisting-sessions.md +++ /dev/null @@ -1,83 +0,0 @@ -# Session Persistence and Resumption - -Save and restore conversation sessions across application restarts. - -## Example scenario - -You want users to be able to continue a conversation even after closing and reopening your application. - -> **Runnable example:** [recipe/persisting_sessions.py](recipe/persisting_sessions.py) -> -> ```bash -> cd recipe && pip install -r requirements.txt -> python persisting_sessions.py -> ``` - -### Creating a session with a custom ID - -```python -from copilot import CopilotClient - -client = CopilotClient() -client.start() - -# Create session with a memorable ID -session = client.create_session( - session_id="user-123-conversation", - model="gpt-5", -) - -session.send(prompt="Let's discuss TypeScript generics") - -# Session ID is preserved -print(session.session_id) # "user-123-conversation" - -# Destroy session but keep data on disk -session.destroy() -client.stop() -``` - -### Resuming a session - -```python -client = CopilotClient() -client.start() - -# Resume the previous session -session = client.resume_session("user-123-conversation") - -# Previous context is restored -session.send(prompt="What were we discussing?") - -session.destroy() -client.stop() -``` - -### Listing available sessions - -```python -sessions = client.list_sessions() -for s in sessions: - print("Session:", s["sessionId"]) -``` - -### Deleting a session permanently - -```python -# Remove session and all its data from disk -client.delete_session("user-123-conversation") -``` - -### Getting session history - -```python -messages = session.get_messages() -for msg in messages: - print(f"[{msg['type']}] {msg['data']}") -``` - -## Best practices - -1. **Use meaningful session IDs**: Include user ID or context in the session ID -2. **Handle missing sessions**: Check if a session exists before resuming -3. **Clean up old sessions**: Periodically delete sessions that are no longer needed diff --git a/cookbook/python/pr-visualization.md b/cookbook/python/pr-visualization.md deleted file mode 100644 index af2ce20c..00000000 --- a/cookbook/python/pr-visualization.md +++ /dev/null @@ -1,218 +0,0 @@ -# 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.py](recipe/pr_visualization.py) -> -> ```bash -> cd recipe && pip install -r requirements.txt -> # Auto-detect from current git repo -> python pr_visualization.py -> -> # Specify a repo explicitly -> python pr_visualization.py --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 -pip install copilot-sdk -``` - -## Usage - -```bash -# Auto-detect from current git repo -python pr_breakdown.py - -# Specify a repo explicitly -python pr_breakdown.py --repo github/copilot-sdk -``` - -## Full example: pr_breakdown.py - -```python -#!/usr/bin/env python3 - -import subprocess -import sys -import os -from copilot import CopilotClient - -# ============================================================================ -# Git & GitHub Detection -# ============================================================================ - -def is_git_repo(): - try: - subprocess.run( - ["git", "rev-parse", "--git-dir"], - check=True, - capture_output=True - ) - return True - except (subprocess.CalledProcessError, FileNotFoundError): - return False - -def get_github_remote(): - try: - result = subprocess.run( - ["git", "remote", "get-url", "origin"], - check=True, - capture_output=True, - text=True - ) - remote_url = result.stdout.strip() - - # Handle SSH: git@github.com:owner/repo.git - import re - ssh_match = re.search(r"git@github\.com:(.+/.+?)(?:\.git)?$", remote_url) - if ssh_match: - return ssh_match.group(1) - - # Handle HTTPS: https://github.com/owner/repo.git - https_match = re.search(r"https://github\.com/(.+/.+?)(?:\.git)?$", remote_url) - if https_match: - return https_match.group(1) - - return None - except (subprocess.CalledProcessError, FileNotFoundError): - return None - -def parse_args(): - args = sys.argv[1:] - if "--repo" in args: - idx = args.index("--repo") - if idx + 1 < len(args): - return {"repo": args[idx + 1]} - return {} - -def prompt_for_repo(): - return input("Enter GitHub repo (owner/repo): ").strip() - -# ============================================================================ -# Main Application -# ============================================================================ - -def main(): - print("🔍 PR Age Chart Generator\n") - - # Determine the repository - args = parse_args() - repo = None - - if "repo" in args: - repo = args["repo"] - print(f"📦 Using specified repo: {repo}") - elif is_git_repo(): - detected = get_github_remote() - if detected: - repo = detected - print(f"📦 Detected GitHub repo: {repo}") - else: - print("⚠️ Git repo found but no GitHub remote detected.") - repo = prompt_for_repo() - else: - print("📁 Not in a git repository.") - repo = prompt_for_repo() - - if not repo or "/" not in repo: - print("❌ Invalid repo format. Expected: owner/repo") - sys.exit(1) - - owner, repo_name = repo.split("/", 1) - - # Create Copilot client - no custom tools needed! - client = CopilotClient(log_level="error") - client.start() - - session = client.create_session( - model="gpt-5", - system_message={ - "content": f""" - -You are analyzing pull requests for the GitHub repository: {owner}/{repo_name} -The current working directory is: {os.getcwd()} - - - -- 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 - -""" - } - ) - - # Set up event handling - def handle_event(event): - if event["type"] == "assistant.message": - print(f"\n🤖 {event['data']['content']}\n") - elif event["type"] == "tool.execution_start": - print(f" ⚙️ {event['data']['toolName']}") - - session.on(handle_event) - - # Initial prompt - let Copilot figure out the details - print("\n📊 Starting analysis...\n") - - session.send(prompt=f""" - Fetch the open pull requests for {owner}/{repo_name} 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. - """) - - session.wait_for_idle() - - # Interactive loop - print("\n💡 Ask follow-up questions or type \"exit\" to quit.\n") - print("Examples:") - print(" - \"Expand to the last month\"") - print(" - \"Show me the 5 oldest PRs\"") - print(" - \"Generate a pie chart instead\"") - print(" - \"Group by author instead of age\"") - print() - - while True: - user_input = input("You: ").strip() - - if user_input.lower() in ["exit", "quit"]: - print("👋 Goodbye!") - break - - if user_input: - session.send(prompt=user_input) - session.wait_for_idle() - - client.stop() - -if __name__ == "__main__": - main() -``` - -## 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** | diff --git a/cookbook/python/recipe/README.md b/cookbook/python/recipe/README.md deleted file mode 100644 index aab80173..00000000 --- a/cookbook/python/recipe/README.md +++ /dev/null @@ -1,92 +0,0 @@ -# Runnable Recipe Examples - -This folder contains standalone, executable Python examples for each cookbook recipe. Each file can be run directly as a Python script. - -## Prerequisites - -- Python 3.8 or later -- Install dependencies (this installs the local SDK in editable mode): - -```bash -pip install -r requirements.txt -``` - -## Running Examples - -Each `.py` file is a complete, runnable program with executable permissions: - -```bash -python .py -# or on Unix-like systems: -./.py -``` - -### Available Recipes - -| Recipe | Command | Description | -| -------------------- | -------------------------------- | ------------------------------------------ | -| Error Handling | `python error_handling.py` | Demonstrates error handling patterns | -| Multiple Sessions | `python multiple_sessions.py` | Manages multiple independent conversations | -| Managing Local Files | `python managing_local_files.py` | Organizes files using AI grouping | -| PR Visualization | `python pr_visualization.py` | Generates PR age charts | -| Persisting Sessions | `python persisting_sessions.py` | Save and resume sessions across restarts | - -### Examples with Arguments - -**PR Visualization with specific repo:** - -```bash -python pr_visualization.py --repo github/copilot-sdk -``` - -**Managing Local Files (edit the file to change target folder):** - -```bash -# Edit the target_folder variable in managing_local_files.py first -python managing_local_files.py -``` - -## Local SDK Development - -The `requirements.txt` installs the local Copilot SDK using `-e ../..` (editable install). This means: - -- Changes to the SDK source are immediately available -- No need to publish or install from PyPI -- Perfect for testing and development - -If you modify the SDK source, Python will automatically use the updated code (no rebuild needed). - -## Python Best Practices - -These examples follow Python conventions: - -- PEP 8 naming (snake_case for functions and variables) -- Shebang line for direct execution -- Proper exception handling -- Type hints where appropriate -- Standard library usage - -## Virtual Environment (Recommended) - -For isolated development: - -```bash -# Create virtual environment -python -m venv venv - -# Activate it -# Windows: -venv\Scripts\activate -# Unix/macOS: -source venv/bin/activate - -# Install dependencies -pip install -r requirements.txt -``` - -## Learning Resources - -- [Python Documentation](https://docs.python.org/3/) -- [PEP 8 Style Guide](https://pep8.org/) -- [GitHub Copilot SDK for Python](../../README.md) -- [Parent Cookbook](../README.md) diff --git a/cookbook/python/recipe/error_handling.py b/cookbook/python/recipe/error_handling.py deleted file mode 100644 index 57073037..00000000 --- a/cookbook/python/recipe/error_handling.py +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env python3 - -from copilot import CopilotClient - -client = CopilotClient() - -try: - client.start() - session = client.create_session(model="gpt-5") - - response = None - def handle_message(event): - nonlocal response - if event["type"] == "assistant.message": - response = event["data"]["content"] - - session.on(handle_message) - session.send(prompt="Hello!") - session.wait_for_idle() - - if response: - print(response) - - session.destroy() -except Exception as e: - print(f"Error: {e}") -finally: - client.stop() diff --git a/cookbook/python/recipe/managing_local_files.py b/cookbook/python/recipe/managing_local_files.py deleted file mode 100644 index 0fd43e50..00000000 --- a/cookbook/python/recipe/managing_local_files.py +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env python3 - -from copilot import CopilotClient -import os - -# Create and start client -client = CopilotClient() -client.start() - -# Create session -session = client.create_session(model="gpt-5") - -# Event handler -def handle_event(event): - if event["type"] == "assistant.message": - print(f"\nCopilot: {event['data']['content']}") - elif event["type"] == "tool.execution_start": - print(f" → Running: {event['data']['toolName']}") - elif event["type"] == "tool.execution_complete": - print(f" ✓ Completed: {event['data']['toolCallId']}") - -session.on(handle_event) - -# Ask Copilot to organize files -# Change this to your target folder -target_folder = os.path.expanduser("~/Downloads") - -session.send(prompt=f""" -Analyze the files in "{target_folder}" and organize them into subfolders. - -1. First, list all files and their metadata -2. Preview grouping by file extension -3. Create appropriate subfolders (e.g., "images", "documents", "videos") -4. Move each file to its appropriate subfolder - -Please confirm before moving any files. -""") - -session.wait_for_idle() - -session.destroy() -client.stop() diff --git a/cookbook/python/recipe/multiple_sessions.py b/cookbook/python/recipe/multiple_sessions.py deleted file mode 100644 index 92921d2d..00000000 --- a/cookbook/python/recipe/multiple_sessions.py +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env python3 - -from copilot import CopilotClient - -client = CopilotClient() -client.start() - -# Create multiple independent sessions -session1 = client.create_session(model="gpt-5") -session2 = client.create_session(model="gpt-5") -session3 = client.create_session(model="claude-sonnet-4.5") - -print("Created 3 independent sessions") - -# Each session maintains its own conversation history -session1.send(prompt="You are helping with a Python project") -session2.send(prompt="You are helping with a TypeScript project") -session3.send(prompt="You are helping with a Go project") - -print("Sent initial context to all sessions") - -# Follow-up messages stay in their respective contexts -session1.send(prompt="How do I create a virtual environment?") -session2.send(prompt="How do I set up tsconfig?") -session3.send(prompt="How do I initialize a module?") - -print("Sent follow-up questions to each session") - -# Clean up all sessions -session1.destroy() -session2.destroy() -session3.destroy() -client.stop() - -print("All sessions destroyed successfully") diff --git a/cookbook/python/recipe/persisting_sessions.py b/cookbook/python/recipe/persisting_sessions.py deleted file mode 100644 index 071ff1a8..00000000 --- a/cookbook/python/recipe/persisting_sessions.py +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env python3 - -from copilot import CopilotClient - -client = CopilotClient() -client.start() - -# Create session with a memorable ID -session = client.create_session( - session_id="user-123-conversation", - model="gpt-5", -) - -session.send(prompt="Let's discuss TypeScript generics") -print(f"Session created: {session.session_id}") - -# Destroy session but keep data on disk -session.destroy() -print("Session destroyed (state persisted)") - -# Resume the previous session -resumed = client.resume_session("user-123-conversation") -print(f"Resumed: {resumed.session_id}") - -resumed.send(prompt="What were we discussing?") - -# List sessions -sessions = client.list_sessions() -print("Sessions:", [s["sessionId"] for s in sessions]) - -# Delete session permanently -client.delete_session("user-123-conversation") -print("Session deleted") - -resumed.destroy() -client.stop() diff --git a/cookbook/python/recipe/pr_visualization.py b/cookbook/python/recipe/pr_visualization.py deleted file mode 100644 index 72226c3d..00000000 --- a/cookbook/python/recipe/pr_visualization.py +++ /dev/null @@ -1,161 +0,0 @@ -#!/usr/bin/env python3 - -import subprocess -import sys -import os -import re -from copilot import CopilotClient - -# ============================================================================ -# Git & GitHub Detection -# ============================================================================ - -def is_git_repo(): - try: - subprocess.run( - ["git", "rev-parse", "--git-dir"], - check=True, - capture_output=True - ) - return True - except (subprocess.CalledProcessError, FileNotFoundError): - return False - -def get_github_remote(): - try: - result = subprocess.run( - ["git", "remote", "get-url", "origin"], - check=True, - capture_output=True, - text=True - ) - remote_url = result.stdout.strip() - - # Handle SSH: git@github.com:owner/repo.git - ssh_match = re.search(r"git@github\.com:(.+/.+?)(?:\.git)?$", remote_url) - if ssh_match: - return ssh_match.group(1) - - # Handle HTTPS: https://github.com/owner/repo.git - https_match = re.search(r"https://github\.com/(.+/.+?)(?:\.git)?$", remote_url) - if https_match: - return https_match.group(1) - - return None - except (subprocess.CalledProcessError, FileNotFoundError): - return None - -def parse_args(): - args = sys.argv[1:] - if "--repo" in args: - idx = args.index("--repo") - if idx + 1 < len(args): - return {"repo": args[idx + 1]} - return {} - -def prompt_for_repo(): - return input("Enter GitHub repo (owner/repo): ").strip() - -# ============================================================================ -# Main Application -# ============================================================================ - -def main(): - print("🔍 PR Age Chart Generator\n") - - # Determine the repository - args = parse_args() - repo = None - - if "repo" in args: - repo = args["repo"] - print(f"📦 Using specified repo: {repo}") - elif is_git_repo(): - detected = get_github_remote() - if detected: - repo = detected - print(f"📦 Detected GitHub repo: {repo}") - else: - print("⚠️ Git repo found but no GitHub remote detected.") - repo = prompt_for_repo() - else: - print("📁 Not in a git repository.") - repo = prompt_for_repo() - - if not repo or "/" not in repo: - print("❌ Invalid repo format. Expected: owner/repo") - sys.exit(1) - - owner, repo_name = repo.split("/", 1) - - # Create Copilot client - no custom tools needed! - client = CopilotClient(log_level="error") - client.start() - - session = client.create_session( - model="gpt-5", - system_message={ - "content": f""" - -You are analyzing pull requests for the GitHub repository: {owner}/{repo_name} -The current working directory is: {os.getcwd()} - - - -- 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 - -""" - } - ) - - # Set up event handling - def handle_event(event): - if event["type"] == "assistant.message": - print(f"\n🤖 {event['data']['content']}\n") - elif event["type"] == "tool.execution_start": - print(f" ⚙️ {event['data']['toolName']}") - - session.on(handle_event) - - # Initial prompt - let Copilot figure out the details - print("\n📊 Starting analysis...\n") - - session.send(prompt=f""" - Fetch the open pull requests for {owner}/{repo_name} 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. - """) - - session.wait_for_idle() - - # Interactive loop - print("\n💡 Ask follow-up questions or type \"exit\" to quit.\n") - print("Examples:") - print(" - \"Expand to the last month\"") - print(" - \"Show me the 5 oldest PRs\"") - print(" - \"Generate a pie chart instead\"") - print(" - \"Group by author instead of age\"") - print() - - while True: - user_input = input("You: ").strip() - - if user_input.lower() in ["exit", "quit"]: - print("👋 Goodbye!") - break - - if user_input: - session.send(prompt=user_input) - session.wait_for_idle() - - session.destroy() - client.stop() - -if __name__ == "__main__": - main() diff --git a/cookbook/python/recipe/requirements.txt b/cookbook/python/recipe/requirements.txt deleted file mode 100644 index 91d70ef1..00000000 --- a/cookbook/python/recipe/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -# Install the local Copilot SDK package in editable mode --e ../..