| | """ |
| | 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 |
| |
|