ExamplesInbound
GitHub Repo Agent
Agent that monitors GitHub notification emails
Overview
A GitHub repo agent monitors GitHub notification emails, extracts meaningful events, and takes automated actions based on repository activity. This example demonstrates pattern matching on notification emails and extracting structured data.
Info
Perfect for automated PR reviews, issue triage, security alerts, or keeping stakeholders informed about repository changes.
How It Works
- Create an inbox for GitHub notifications
- Configure GitHub to send notifications to the inbox
- Poll or use webhooks to receive notifications
- Parse GitHub email patterns to extract event data
- Take automated actions (comment, label, notify)
Complete Implementation
import { DaimonClient } from 'daimon-email';
import { Octokit } from '@octokit/rest';
const client = new DaimonClient({ apiKey: process.env.DAIMON_API_KEY });
const github = new Octokit({ auth: process.env.GITHUB_TOKEN });
interface GitHubEvent {
type: 'pull_request' | 'issue' | 'release' | 'security_alert' | 'mention';
repo: string;
number?: number;
title: string;
url: string;
action: string;
}
// Step 1: Create inbox for GitHub notifications
async function setupGitHubAgent() {
const inbox = await client.inboxes.create({
username: 'github-monitor',
clientId: 'github-agent-v1'
});
console.log(`GitHub notification inbox: ${inbox.address}`);
console.log(`\nConfigure GitHub:`);
console.log(`Settings → Notifications → Custom routing`);
console.log(`Add: ${inbox.address}`);
return inbox;
}
// Step 2: Poll for GitHub notifications
async function pollGitHubNotifications(inboxId: string) {
const messages = await client.inboxes.messages.list(inboxId, {
limit: 50,
unread: true
});
for (const message of messages) {
if (message.from.includes('notifications@github.com')) {
await processGitHubNotification(message);
}
}
}
// Step 3: Parse GitHub notification patterns
function parseGitHubNotification(message: any): GitHubEvent | null {
const subject = message.subject;
const body = message.replyBody || message.body;
// Pull request patterns
const prMatch = subject.match(/\[(.+?)\] (.+?) \(#(\d+)\)/);
if (prMatch) {
const [, repo, title, number] = prMatch;
return {
type: 'pull_request',
repo,
number: parseInt(number),
title,
url: message.links.find(l => l.url.includes('/pull/'))?.url || '',
action: detectAction(subject)
};
}
// Issue patterns
const issueMatch = subject.match(/\[(.+?)\] (.+?) \(#(\d+)\)/);
if (issueMatch && !subject.includes('Pull Request')) {
const [, repo, title, number] = issueMatch;
return {
type: 'issue',
repo,
number: parseInt(number),
title,
url: message.links.find(l => l.url.includes('/issues/'))?.url || '',
action: detectAction(subject)
};
}
// Security alerts
if (subject.includes('security') || subject.includes('Dependabot')) {
return {
type: 'security_alert',
repo: extractRepo(body),
title: subject,
url: message.ctaLinks[0]?.url || '',
action: 'alert'
};
}
// Mentions
if (subject.includes('@') || body.includes('mentioned you')) {
return {
type: 'mention',
repo: extractRepo(body),
title: subject,
url: message.links[0]?.url || '',
action: 'mentioned'
};
}
return null;
}
// Helper: Detect action type
function detectAction(subject: string): string {
if (subject.includes('opened')) return 'opened';
if (subject.includes('closed')) return 'closed';
if (subject.includes('merged')) return 'merged';
if (subject.includes('commented')) return 'commented';
if (subject.includes('review requested')) return 'review_requested';
if (subject.includes('approved')) return 'approved';
return 'unknown';
}
// Helper: Extract repo name
function extractRepo(text: string): string {
const match = text.match(/github\.com\/([^\/]+\/[^\/\s]+)/);
return match ? match[1] : 'unknown';
}
// Step 4: Process GitHub events
async function processGitHubNotification(message: any) {
const event = parseGitHubNotification(message);
if (!event) {
console.log(`Skipped non-GitHub notification: ${message.subject}`);
return;
}
console.log(`GitHub event: ${event.type} - ${event.action} on ${event.repo}`);
// Handle different event types
switch (event.type) {
case 'pull_request':
await handlePullRequest(event);
break;
case 'issue':
await handleIssue(event);
break;
case 'security_alert':
await handleSecurityAlert(event);
break;
case 'mention':
await handleMention(event);
break;
}
}
// Step 5: Automated actions based on events
async function handlePullRequest(event: GitHubEvent) {
const [owner, repo] = event.repo.split('/');
if (event.action === 'opened') {
// Auto-label new PRs
await github.issues.addLabels({
owner,
repo,
issue_number: event.number!,
labels: ['needs-review']
});
console.log(`Labeled PR #${event.number} as needs-review`);
}
if (event.action === 'review_requested') {
// Notify team via Slack or other channel
console.log(`Review requested for PR #${event.number}: ${event.title}`);
// await notifySlack(`Review needed: ${event.url}`);
}
}
async function handleIssue(event: GitHubEvent) {
const [owner, repo] = event.repo.split('/');
// Auto-triage issues
if (event.action === 'opened') {
console.log(`New issue #${event.number}: ${event.title}`);
// Could auto-label based on content
// await github.issues.addLabels({ ... });
}
}
async function handleSecurityAlert(event: GitHubEvent) {
console.log(`SECURITY ALERT: ${event.title}`);
console.log(`URL: ${event.url}`);
// High priority - notify immediately
// await notifySlack(`🚨 Security alert: ${event.url}`);
// await createPagerDutyIncident({ ... });
}
async function handleMention(event: GitHubEvent) {
console.log(`You were mentioned in ${event.repo}: ${event.title}`);
console.log(`URL: ${event.url}`);
// Could auto-respond or add to review queue
}
// Run the agent
async function runGitHubAgent() {
const inbox = await setupGitHubAgent();
console.log('\nGitHub agent running...');
// Poll every 60 seconds
setInterval(async () => {
await pollGitHubNotifications(inbox.id);
}, 60000);
// Initial poll
await pollGitHubNotifications(inbox.id);
}
runGitHubAgent();from daimon_email import DaimonClient
from github import Github
import os
import time
import re
from typing import Optional, Dict
client = DaimonClient(api_key=os.environ.get('DAIMON_API_KEY'))
github_client = Github(os.environ.get('GITHUB_TOKEN'))
# Step 1: Create inbox for GitHub notifications
def setup_github_agent():
inbox = client.inboxes.create(
username='github-monitor',
client_id='github-agent-v1'
)
print(f"GitHub notification inbox: {inbox.address}")
print(f"\nConfigure GitHub:")
print(f"Settings → Notifications → Custom routing")
print(f"Add: {inbox.address}")
return inbox
# Step 2: Poll for GitHub notifications
def poll_github_notifications(inbox_id: str):
messages = client.inboxes.messages.list(
inbox_id,
limit=50,
unread=True
)
for message in messages:
if 'notifications@github.com' in message['from']:
process_github_notification(message)
# Step 3: Parse GitHub notification patterns
def parse_github_notification(message: Dict) -> Optional[Dict]:
subject = message['subject']
body = message.get('reply_body') or message.get('body')
# Pull request patterns
pr_match = re.search(r'\[(.+?)\] (.+?) \(#(\d+)\)', subject)
if pr_match:
repo, title, number = pr_match.groups()
return {
'type': 'pull_request',
'repo': repo,
'number': int(number),
'title': title,
'url': next((l['url'] for l in message['links'] if '/pull/' in l['url']), ''),
'action': detect_action(subject)
}
# Issue patterns
issue_match = re.search(r'\[(.+?)\] (.+?) \(#(\d+)\)', subject)
if issue_match and 'Pull Request' not in subject:
repo, title, number = issue_match.groups()
return {
'type': 'issue',
'repo': repo,
'number': int(number),
'title': title,
'url': next((l['url'] for l in message['links'] if '/issues/' in l['url']), ''),
'action': detect_action(subject)
}
# Security alerts
if 'security' in subject.lower() or 'Dependabot' in subject:
return {
'type': 'security_alert',
'repo': extract_repo(body),
'title': subject,
'url': message['cta_links'][0]['url'] if message.get('cta_links') else '',
'action': 'alert'
}
# Mentions
if '@' in subject or 'mentioned you' in body:
return {
'type': 'mention',
'repo': extract_repo(body),
'title': subject,
'url': message['links'][0]['url'] if message.get('links') else '',
'action': 'mentioned'
}
return None
# Helper: Detect action type
def detect_action(subject: str) -> str:
if 'opened' in subject:
return 'opened'
if 'closed' in subject:
return 'closed'
if 'merged' in subject:
return 'merged'
if 'commented' in subject:
return 'commented'
if 'review requested' in subject:
return 'review_requested'
if 'approved' in subject:
return 'approved'
return 'unknown'
# Helper: Extract repo name
def extract_repo(text: str) -> str:
match = re.search(r'github\.com/([^/]+/[^/\s]+)', text)
return match.group(1) if match else 'unknown'
# Step 4: Process GitHub events
def process_github_notification(message: Dict):
event = parse_github_notification(message)
if not event:
print(f"Skipped non-GitHub notification: {message['subject']}")
return
print(f"GitHub event: {event['type']} - {event['action']} on {event['repo']}")
# Handle different event types
if event['type'] == 'pull_request':
handle_pull_request(event)
elif event['type'] == 'issue':
handle_issue(event)
elif event['type'] == 'security_alert':
handle_security_alert(event)
elif event['type'] == 'mention':
handle_mention(event)
# Step 5: Automated actions based on events
def handle_pull_request(event: Dict):
owner, repo = event['repo'].split('/')
repo_obj = github_client.get_repo(f"{owner}/{repo}")
if event['action'] == 'opened':
# Auto-label new PRs
issue = repo_obj.get_issue(event['number'])
issue.add_to_labels('needs-review')
print(f"Labeled PR #{event['number']} as needs-review")
if event['action'] == 'review_requested':
# Notify team via Slack or other channel
print(f"Review requested for PR #{event['number']}: {event['title']}")
# notify_slack(f"Review needed: {event['url']}")
def handle_issue(event: Dict):
# Auto-triage issues
if event['action'] == 'opened':
print(f"New issue #{event['number']}: {event['title']}")
# Could auto-label based on content
# ...
def handle_security_alert(event: Dict):
print(f"SECURITY ALERT: {event['title']}")
print(f"URL: {event['url']}")
# High priority - notify immediately
# notify_slack(f"🚨 Security alert: {event['url']}")
# create_pagerduty_incident({ ... })
def handle_mention(event: Dict):
print(f"You were mentioned in {event['repo']}: {event['title']}")
print(f"URL: {event['url']}")
# Could auto-respond or add to review queue
# Run the agent
def run_github_agent():
inbox = setup_github_agent()
print('\nGitHub agent running...')
while True:
poll_github_notifications(inbox['id'])
time.sleep(60) # Poll every 60 seconds
if __name__ == '__main__':
run_github_agent()GitHub Notification Patterns
GitHub sends structured notification emails. Common patterns:
- PR opened:
[owner/repo] Title (#123) - Issue created:
[owner/repo] Issue title (#456) - Review requested:
[owner/repo] Review requested on PR (#123) - Security alert:
Dependabot alert: [vulnerability] in owner/repo - Mentions:
@username mentioned you in owner/repo#123
Advanced: CI/CD Integration
Combine with CI/CD actions:
async function handlePullRequest(event: GitHubEvent) {
if (event.action === 'opened') {
// Trigger custom CI checks
await runCustomLintChecks(event.repo, event.number);
// Auto-assign reviewers
await assignReviewers(event.repo, event.number);
}
}