mirror of
https://github.com/github/awesome-copilot.git
synced 2026-02-23 20:05:12 +00:00
Moving the copilot-sdk cookbook content in here
This commit is contained in:
19
cookbook/copilot-sdk/go/README.md
Normal file
19
cookbook/copilot-sdk/go/README.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# 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.
|
||||
206
cookbook/copilot-sdk/go/error-handling.md
Normal file
206
cookbook/copilot-sdk/go/error-handling.md
Normal file
@@ -0,0 +1,206 @@
|
||||
# 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
|
||||
144
cookbook/copilot-sdk/go/managing-local-files.md
Normal file
144
cookbook/copilot-sdk/go/managing-local-files.md
Normal file
@@ -0,0 +1,144 @@
|
||||
# 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
|
||||
107
cookbook/copilot-sdk/go/multiple-sessions.md
Normal file
107
cookbook/copilot-sdk/go/multiple-sessions.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# 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
|
||||
92
cookbook/copilot-sdk/go/persisting-sessions.md
Normal file
92
cookbook/copilot-sdk/go/persisting-sessions.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# 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
|
||||
238
cookbook/copilot-sdk/go/pr-visualization.md
Normal file
238
cookbook/copilot-sdk/go/pr-visualization.md
Normal file
@@ -0,0 +1,238 @@
|
||||
# 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(`
|
||||
<context>
|
||||
You are analyzing pull requests for the GitHub repository: %s/%s
|
||||
The current working directory is: %s
|
||||
</context>
|
||||
|
||||
<instructions>
|
||||
- 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
|
||||
</instructions>
|
||||
`, 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** |
|
||||
61
cookbook/copilot-sdk/go/recipe/README.md
Normal file
61
cookbook/copilot-sdk/go/recipe/README.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# 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 <filename>.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](https://github.com/github/copilot-sdk/blob/main/go/README.md)
|
||||
- [Parent Cookbook](../README.md)
|
||||
44
cookbook/copilot-sdk/go/recipe/error-handling.go
Normal file
44
cookbook/copilot-sdk/go/recipe/error-handling.go
Normal file
@@ -0,0 +1,44 @@
|
||||
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)
|
||||
}
|
||||
62
cookbook/copilot-sdk/go/recipe/managing-local-files.go
Normal file
62
cookbook/copilot-sdk/go/recipe/managing-local-files.go
Normal file
@@ -0,0 +1,62 @@
|
||||
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()
|
||||
}
|
||||
53
cookbook/copilot-sdk/go/recipe/multiple-sessions.go
Normal file
53
cookbook/copilot-sdk/go/recipe/multiple-sessions.go
Normal file
@@ -0,0 +1,53 @@
|
||||
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")
|
||||
}
|
||||
68
cookbook/copilot-sdk/go/recipe/persisting-sessions.go
Normal file
68
cookbook/copilot-sdk/go/recipe/persisting-sessions.go
Normal file
@@ -0,0 +1,68 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
182
cookbook/copilot-sdk/go/recipe/pr-visualization.go
Normal file
182
cookbook/copilot-sdk/go/recipe/pr-visualization.go
Normal file
@@ -0,0 +1,182 @@
|
||||
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(`
|
||||
<context>
|
||||
You are analyzing pull requests for the GitHub repository: %s/%s
|
||||
The current working directory is: %s
|
||||
</context>
|
||||
|
||||
<instructions>
|
||||
- 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
|
||||
</instructions>
|
||||
`, 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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user