mirror of
https://github.com/github/awesome-copilot.git
synced 2026-02-22 11:25:13 +00:00
Moving the copilot-sdk cookbook content in here
This commit is contained in:
19
cookbook/copilot-sdk/nodejs/README.md
Normal file
19
cookbook/copilot-sdk/nodejs/README.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# 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.
|
||||
129
cookbook/copilot-sdk/nodejs/error-handling.md
Normal file
129
cookbook/copilot-sdk/nodejs/error-handling.md
Normal file
@@ -0,0 +1,129 @@
|
||||
# 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
|
||||
132
cookbook/copilot-sdk/nodejs/managing-local-files.md
Normal file
132
cookbook/copilot-sdk/nodejs/managing-local-files.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# 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
|
||||
79
cookbook/copilot-sdk/nodejs/multiple-sessions.md
Normal file
79
cookbook/copilot-sdk/nodejs/multiple-sessions.md
Normal file
@@ -0,0 +1,79 @@
|
||||
# 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
|
||||
91
cookbook/copilot-sdk/nodejs/persisting-sessions.md
Normal file
91
cookbook/copilot-sdk/nodejs/persisting-sessions.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# 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
|
||||
292
cookbook/copilot-sdk/nodejs/pr-visualization.md
Normal file
292
cookbook/copilot-sdk/nodejs/pr-visualization.md
Normal file
@@ -0,0 +1,292 @@
|
||||
# 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<string> {
|
||||
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: `
|
||||
<context>
|
||||
You are analyzing pull requests for the GitHub repository: ${owner}/${repoName}
|
||||
The current working directory is: ${process.cwd()}
|
||||
</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>
|
||||
`,
|
||||
},
|
||||
});
|
||||
|
||||
// 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** |
|
||||
84
cookbook/copilot-sdk/nodejs/recipe/README.md
Normal file
84
cookbook/copilot-sdk/nodejs/recipe/README.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# 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 <script-name>
|
||||
```
|
||||
|
||||
### Using tsx directly:
|
||||
|
||||
```bash
|
||||
npx tsx <filename>.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](https://github.com/github/copilot-sdk/blob/main/nodejs/README.md)
|
||||
- [Parent Cookbook](../README.md)
|
||||
17
cookbook/copilot-sdk/nodejs/recipe/error-handling.ts
Normal file
17
cookbook/copilot-sdk/nodejs/recipe/error-handling.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
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();
|
||||
}
|
||||
47
cookbook/copilot-sdk/nodejs/recipe/managing-local-files.ts
Normal file
47
cookbook/copilot-sdk/nodejs/recipe/managing-local-files.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
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();
|
||||
33
cookbook/copilot-sdk/nodejs/recipe/multiple-sessions.ts
Normal file
33
cookbook/copilot-sdk/nodejs/recipe/multiple-sessions.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
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");
|
||||
629
cookbook/copilot-sdk/nodejs/recipe/package-lock.json
generated
Normal file
629
cookbook/copilot-sdk/nodejs/recipe/package-lock.json
generated
Normal file
@@ -0,0 +1,629 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
21
cookbook/copilot-sdk/nodejs/recipe/package.json
Normal file
21
cookbook/copilot-sdk/nodejs/recipe/package.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"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": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.19.7",
|
||||
"tsx": "^4.19.2",
|
||||
"typescript": "^5.7.2"
|
||||
}
|
||||
}
|
||||
37
cookbook/copilot-sdk/nodejs/recipe/persisting-sessions.ts
Normal file
37
cookbook/copilot-sdk/nodejs/recipe/persisting-sessions.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
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();
|
||||
179
cookbook/copilot-sdk/nodejs/recipe/pr-visualization.ts
Normal file
179
cookbook/copilot-sdk/nodejs/recipe/pr-visualization.ts
Normal file
@@ -0,0 +1,179 @@
|
||||
#!/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<string> {
|
||||
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: `
|
||||
<context>
|
||||
You are analyzing pull requests for the GitHub repository: ${owner}/${repoName}
|
||||
The current working directory is: ${process.cwd()}
|
||||
</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>
|
||||
`,
|
||||
},
|
||||
});
|
||||
|
||||
// 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);
|
||||
Reference in New Issue
Block a user