ai-workflow-agent / tools /github_search.py
Hamza4100's picture
Upload 22 files
9c95c55 verified
# GitHub Search Tool
"""
Search GitHub repositories and provide recommendations.
Simple keyword search with README summarization.
"""
import httpx
import logging
from typing import List, Dict, Any, Optional
from config import settings
logger = logging.getLogger(__name__)
class GitHubSearchTool:
"""
GitHub repository search tool.
Searches for relevant projects and provides recommendations.
"""
def __init__(self):
self.api_base = "https://api.github.com"
self.token = settings.GITHUB_TOKEN
self.client = httpx.AsyncClient(timeout=30.0)
# Set headers
self.headers = {
"Accept": "application/vnd.github.v3+json",
"User-Agent": "AI-Workflow-Agent"
}
if self.token:
self.headers["Authorization"] = f"token {self.token}"
async def search(
self,
keywords: str,
max_results: int = 3,
language: Optional[str] = None
) -> List[Dict[str, Any]]:
"""
Search GitHub repositories by keywords.
Args:
keywords: Search terms
max_results: Maximum number of results (default 3)
language: Optional language filter
Returns:
List of repository info dictionaries
"""
try:
# Build search query
query = keywords
if language:
query += f" language:{language}"
# Search repositories
response = await self.client.get(
f"{self.api_base}/search/repositories",
params={
"q": query,
"sort": "stars",
"order": "desc",
"per_page": max_results
},
headers=self.headers
)
if response.status_code == 200:
data = response.json()
repos = data.get("items", [])
# Extract relevant info
results = []
for repo in repos[:max_results]:
repo_info = await self._extract_repo_info(repo)
results.append(repo_info)
return results
else:
logger.error(f"GitHub search failed: {response.status_code}")
return []
except Exception as e:
logger.error(f"GitHub search error: {e}")
return []
async def _extract_repo_info(self, repo: Dict[str, Any]) -> Dict[str, Any]:
"""Extract relevant information from repository data."""
# Get README summary
readme_summary = await self._get_readme_summary(
repo.get("owner", {}).get("login", ""),
repo.get("name", "")
)
return {
"name": repo.get("name", ""),
"full_name": repo.get("full_name", ""),
"url": repo.get("html_url", ""),
"clone_url": repo.get("clone_url", ""),
"description": repo.get("description", "No description"),
"stars": repo.get("stargazers_count", 0),
"forks": repo.get("forks_count", 0),
"language": repo.get("language", "Unknown"),
"topics": repo.get("topics", []),
"updated_at": repo.get("updated_at", ""),
"has_docker": await self._check_has_docker(
repo.get("owner", {}).get("login", ""),
repo.get("name", "")
),
"readme_summary": readme_summary
}
async def _get_readme_summary(self, owner: str, repo: str) -> str:
"""Fetch and summarize repository README."""
try:
response = await self.client.get(
f"{self.api_base}/repos/{owner}/{repo}/readme",
headers=self.headers
)
if response.status_code == 200:
data = response.json()
# README is base64 encoded
import base64
content = base64.b64decode(data.get("content", "")).decode("utf-8")
# Simple summary: first 500 chars
summary = content[:500].replace("\n", " ").strip()
if len(content) > 500:
summary += "..."
return summary
return "README not available"
except Exception as e:
logger.warning(f"README fetch error: {e}")
return "README not available"
async def _check_has_docker(self, owner: str, repo: str) -> bool:
"""Check if repository has Dockerfile or docker-compose."""
try:
# Check for Dockerfile
response = await self.client.get(
f"{self.api_base}/repos/{owner}/{repo}/contents/Dockerfile",
headers=self.headers
)
if response.status_code == 200:
return True
# Check for docker-compose
response = await self.client.get(
f"{self.api_base}/repos/{owner}/{repo}/contents/docker-compose.yml",
headers=self.headers
)
if response.status_code == 200:
return True
# Check for docker-compose.yaml
response = await self.client.get(
f"{self.api_base}/repos/{owner}/{repo}/contents/docker-compose.yaml",
headers=self.headers
)
return response.status_code == 200
except Exception:
return False
async def generate_recommendation(self, repos: List[Dict[str, Any]]) -> str:
"""
Generate a recommendation summary for found repositories.
Args:
repos: List of repository info dictionaries
Returns:
Recommendation text
"""
if not repos:
return "No repositories found. Try different keywords."
# Build recommendation
lines = ["📦 **Repository Recommendations:**\n"]
for i, repo in enumerate(repos, 1):
stars = repo.get("stars", 0)
docker_status = "✅ Docker" if repo.get("has_docker") else "❌ No Docker"
lines.append(f"**{i}. {repo.get('name', 'Unknown')}** ⭐ {stars}")
lines.append(f" {repo.get('description', 'No description')}")
lines.append(f" Language: {repo.get('language', 'Unknown')} | {docker_status}")
lines.append(f" URL: {repo.get('url', '')}")
lines.append("")
# Add best pick recommendation
if repos:
best = max(repos, key=lambda r: r.get("stars", 0))
if best.get("has_docker"):
lines.append(f"💡 **Recommended:** {best.get('name')} (most stars + Docker support)")
else:
docker_repos = [r for r in repos if r.get("has_docker")]
if docker_repos:
best_docker = max(docker_repos, key=lambda r: r.get("stars", 0))
lines.append(f"💡 **Recommended:** {best_docker.get('name')} (has Docker support)")
else:
lines.append(f"💡 **Recommended:** {best.get('name')} (most stars, but needs Docker setup)")
return "\n".join(lines)
async def get_repo_details(self, repo_url: str) -> Dict[str, Any]:
"""
Get detailed information about a specific repository.
Args:
repo_url: GitHub repository URL or owner/repo format
Returns:
Repository details dictionary
"""
try:
# Parse owner/repo from URL or direct format
if "github.com" in repo_url:
parts = repo_url.rstrip("/").split("/")
owner = parts[-2]
repo = parts[-1]
else:
owner, repo = repo_url.split("/")
response = await self.client.get(
f"{self.api_base}/repos/{owner}/{repo}",
headers=self.headers
)
if response.status_code == 200:
return await self._extract_repo_info(response.json())
else:
return {"error": f"Repository not found: {response.status_code}"}
except Exception as e:
logger.error(f"Repo details error: {e}")
return {"error": str(e)}