| | """
|
| | 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
|
| |
|
| |
|
| |
|
| |
|
| | api_sort_map = {
|
| | "created": "created",
|
| | "updated": "updated",
|
| | "stars": None,
|
| | "forks": None,
|
| | }
|
| |
|
| | api_sort = api_sort_map.get(sort)
|
| | need_manual_sort = api_sort is None
|
| |
|
| | try:
|
| | while True:
|
| | params = {
|
| | "page": page,
|
| | "per_page": per_page,
|
| | }
|
| |
|
| |
|
| | 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"),
|
| | }
|
| | )
|
| |
|
| |
|
| | if len(items) < per_page:
|
| | break
|
| |
|
| |
|
| | 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,
|
| | }
|
| |
|
| |
|
| | if need_manual_sort and all_repos:
|
| | reverse = order == "desc"
|
| | all_repos.sort(key=lambda x: x[sort], reverse=reverse)
|
| |
|
| |
|
| | 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,
|
| | }
|
| |
|
| |
|
| | 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])}")
|
| |
|
| |
|
| | 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),
|
| | }
|
| |
|
| |
|
| |
|
| | 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"
|
| | "<example>\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"
|
| | "</example>\n\n"
|
| | "<example>\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"
|
| | "</example>"
|
| | ),
|
| | "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
|
| |
|