"""
GitHub List Repositories Tool - List and sort repositories for any user or organization
Efficiently discover repositories with flexible sorting options.
"""
import os
from typing import Any, Dict, Literal, Optional
import requests
from agent.tools.types import ToolResult
def list_repos(
owner: str,
owner_type: Literal["user", "org"] = "org",
sort: Literal["stars", "forks", "updated", "created"] = "stars",
order: Literal["asc", "desc"] = "desc",
limit: Optional[int] = 30,
) -> ToolResult:
"""
List repositories for a user or organization using GitHub REST API.
Args:
owner: GitHub username or organization name
owner_type: Whether the owner is a "user" or "org" (default: "org")
sort: Sort field - "stars", "forks", "updated", or "created"
order: Sort order - "asc" or "desc" (default: "desc")
limit: Maximum number of repositories to return
Returns:
ToolResult with repository information
"""
token = os.environ.get("GITHUB_TOKEN")
if not token:
return {
"formatted": "Error: GITHUB_TOKEN environment variable is required",
"totalResults": 0,
"resultsShared": 0,
"isError": True,
}
if owner_type == "org":
url = f"https://api.github.com/orgs/{owner}/repos"
else:
url = f"https://api.github.com/users/{owner}/repos"
headers = {
"Accept": "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
"Authorization": f"Bearer {token}",
}
all_repos = []
page = 1
per_page = 100 # Maximum allowed by GitHub
# Map our sort values to GitHub API sort values
# Note: GitHub list repos API doesn't support sorting by stars/forks
# We'll fetch all repos and sort in memory for those cases
api_sort_map = {
"created": "created",
"updated": "updated",
"stars": None, # Not supported by list API
"forks": None, # Not supported by list API
}
api_sort = api_sort_map.get(sort)
need_manual_sort = api_sort is None
try:
while True:
params = {
"page": page,
"per_page": per_page,
}
# Only add sort/direction if API supports it
if api_sort:
params["sort"] = api_sort
params["direction"] = order
response = requests.get(
url,
headers=headers,
params=params,
timeout=30,
)
if response.status_code == 403:
error_data = response.json()
return {
"formatted": f"GitHub API rate limit or permission error: {error_data.get('message', 'Unknown error')}",
"totalResults": 0,
"resultsShared": 0,
"isError": True,
}
if response.status_code != 200:
error_msg = f"GitHub API error (status {response.status_code})"
try:
error_data = response.json()
if "message" in error_data:
error_msg += f": {error_data['message']}"
except Exception:
pass
return {
"formatted": error_msg,
"totalResults": 0,
"resultsShared": 0,
"isError": True,
}
items = response.json()
if not items:
break
for item in items:
all_repos.append(
{
"name": item.get("name"),
"full_name": item.get("full_name"),
"description": item.get("description"),
"html_url": item.get("html_url"),
"language": item.get("language"),
"stars": item.get("stargazers_count", 0),
"forks": item.get("forks_count", 0),
"open_issues": item.get("open_issues_count", 0),
"topics": item.get("topics", []),
"updated_at": item.get("updated_at"),
"created_at": item.get("created_at"),
}
)
# Check if we got fewer results than requested (last page)
if len(items) < per_page:
break
# Stop if we have enough repos
if limit and len(all_repos) >= limit:
break
page += 1
except requests.exceptions.RequestException as e:
return {
"formatted": f"Failed to connect to GitHub API: {str(e)}",
"totalResults": 0,
"resultsShared": 0,
"isError": True,
}
# Manual sorting if needed (for stars/forks)
if need_manual_sort and all_repos:
reverse = order == "desc"
all_repos.sort(key=lambda x: x[sort], reverse=reverse)
# Apply limit after sorting
if limit:
all_repos = all_repos[:limit]
if not all_repos:
return {
"formatted": f"No repositories found for {owner_type} '{owner}'",
"totalResults": 0,
"resultsShared": 0,
}
# Format output
lines = [f"**Found {len(all_repos)} repositories for {owner}:**\n"]
for i, repo in enumerate(all_repos, 1):
lines.append(f"{i}. **{repo['full_name']}**")
lines.append(
f" ⭐ {repo['stars']:,} stars | 🍴 {repo['forks']:,} forks | Language: {repo['language'] or 'N/A'}"
)
if repo["description"]:
desc = (
repo["description"][:100] + "..."
if len(repo["description"]) > 100
else repo["description"]
)
lines.append(f" {desc}")
lines.append(f" URL: {repo['html_url']}")
if repo["topics"]:
lines.append(f" Topics: {', '.join(repo['topics'][:5])}")
# Copyable parameters for other tools
lines.append(f" Use in tools: {{'repo': '{repo['full_name']}'}}")
lines.append("")
return {
"formatted": "\n".join(lines),
"totalResults": len(all_repos),
"resultsShared": len(all_repos),
}
# Tool specification
GITHUB_LIST_REPOS_TOOL_SPEC = {
"name": "github_list_repos",
"description": (
"List and discover repositories for GitHub organizations or users with flexible sorting. "
"**Use when:** (1) Exploring what libraries exist for a task, (2) Finding the right library to use, "
"(3) Discovering popular or active projects, (4) Checking recently updated repos for latest features, "
"(5) Finding alternative libraries in an organization. "
"**Pattern:** github_list_repos (discover libraries) → github_find_examples (find usage examples) → implement. "
"Returns: Comprehensive repository information (stars, forks, language, topics, URLs), sorted by preference. "
"**Then:** Use github_find_examples on selected repo to discover example code. "
"Sorts by: stars (popularity), forks (community), updated (activity), created (age).\n\n"
"## When to use this tool\n\n"
"- When you need to find libraries to use in your implementation\n"
"- When exploring what repositories exist for a task or domain\n"
"- When debugging an error and looking up if others have similar issues in repos\n"
"- When finding the most popular or actively maintained projects for a user/org\n"
"## Examples\n\n"
"\n"
"// ML Workflow Step: Discover HF libraries for RLHF/alignment\n"
"// Use case: Find the right library for training with human feedback\n"
"{\n"
" owner: 'huggingface',\n"
" owner_type: 'org',\n"
" sort: 'stars',\n"
" limit: 10\n"
"}\n"
"// Returns: transformers, trl, peft, accelerate, diffusers...\n"
"\n\n"
"\n"
"// ML Workflow Step: Check for recently updated HF repos\n"
"// Use case: Find actively maintained libraries with latest features\n"
"{\n"
" owner: 'huggingface',\n"
" owner_type: 'org',\n"
" sort: 'updated',\n"
" order: 'desc',\n"
" limit: 15\n"
"}\n"
"// Helps identify which repos have recent improvements/fixes\n"
""
),
"parameters": {
"type": "object",
"properties": {
"owner": {
"type": "string",
"description": "GitHub username or organization name. Required.",
},
"owner_type": {
"type": "string",
"enum": ["user", "org"],
"description": "Whether the owner is a 'user' or 'org'. Default: 'org'.",
},
"sort": {
"type": "string",
"enum": ["stars", "forks", "updated", "created"],
"description": "Sort field. Options: 'stars', 'forks', 'updated', 'created'. Default: 'stars'.",
},
"order": {
"type": "string",
"enum": ["asc", "desc"],
"description": "Sort order. Options: 'asc', 'desc'. Default: 'desc'.",
},
"limit": {
"type": "integer",
"description": "Maximum number of repositories to return. No limit if not specified. Default: 30.",
},
},
"required": ["owner"],
},
}
async def github_list_repos_handler(arguments: Dict[str, Any]) -> tuple[str, bool]:
"""Handler for agent tool router"""
try:
result = list_repos(
owner=arguments["owner"],
owner_type=arguments.get("owner_type", "org"),
sort=arguments.get("sort", "stars"),
order=arguments.get("order", "desc"),
limit=arguments.get("limit"),
)
return result["formatted"], not result.get("isError", False)
except Exception as e:
return f"Error listing repositories: {str(e)}", False