#我来为您设计一个功能丰富的 GitHub 搜索命令行工具。这个工具将使用 Python 和 `rich` 库来创建漂亮的交互式菜单。

## 项目结构

"""```
gh-searcher/
├── gh_searcher.py    # 主程序
├── requirements.txt  # 依赖文件
└── README.md        # 说明文档
```

## 完整代码实现

```python
#!/usr/bin/env python3
"""
"""GitHub 多功能搜索工具
支持搜索仓库、用户、问题、代码片段等
"""


import os
import requests
import json
from typing import Dict, List, Optional, Any
from dataclasses import dataclass
from rich.console import Console
from rich.panel import Panel
from rich.table import Table
from rich.prompt import Prompt, Confirm, IntPrompt
from rich.progress import Progress, SpinnerColumn, TextColumn
from rich.markdown import Markdown
from rich import box
import argparse
import webbrowser
from datetime import datetime

# 初始化 rich console
console = Console()

@dataclass
class GitHubConfig:
    """GitHub 配置类"""
    token: Optional[str] = None
    base_url: str = "https://api.github.com"

class GitHubSearcher:
    def __init__(self, config: GitHubConfig):
        self.config = config
        self.headers = {
            "Accept": "application/vnd.github.v3+json",
            "User-Agent": "GitHub-Searcher/1.0"
        }
        if config.token:
            self.headers["Authorization"] = f"token {config.token}"

    def search_repositories(self, query: str, sort: str = "stars", order: str = "desc", 
                          per_page: int = 10) -> List[Dict]:
        """搜索仓库"""
        url = f"{self.config.base_url}/search/repositories"
        params = {
            "q": query,
            "sort": sort,
            "order": order,
            "per_page": per_page
        }
        
        try:
            response = requests.get(url, params=params, headers=self.headers)
            response.raise_for_status()
            return response.json().get("items", [])
        except requests.RequestException as e:
            console.print(f"[red]搜索仓库时出错: {e}[/red]")
            return []

    def search_users(self, query: str, sort: str = "followers", order: str = "desc",
                    per_page: int = 10) -> List[Dict]:
        """搜索用户"""
        url = f"{self.config.base_url}/search/users"
        params = {
            "q": query,
            "sort": sort,
            "order": order,
            "per_page": per_page
        }
        
        try:
            response = requests.get(url, params=params, headers=self.headers)
            response.raise_for_status()
            return response.json().get("items", [])
        except requests.RequestException as e:
            console.print(f"[red]搜索用户时出错: {e}[/red]")
            return []

    def search_issues(self, query: str, sort: str = "created", order: str = "desc",
                     per_page: int = 10) -> List[Dict]:
        """搜索问题"""
        url = f"{self.config.base_url}/search/issues"
        params = {
            "q": query,
            "sort": sort,
            "order": order,
            "per_page": per_page
        }
        
        try:
            response = requests.get(url, params=params, headers=self.headers)
            response.raise_for_status()
            return response.json().get("items", [])
        except requests.RequestException as e:
            console.print(f"[red]搜索问题时出错: {e}[/red]")
            return []

    def search_code(self, query: str, sort: str = "indexed", order: str = "desc",
                   per_page: int = 10) -> List[Dict]:
        """搜索代码"""
        url = f"{self.config.base_url}/search/code"
        params = {
            "q": query,
            "sort": sort,
            "order": order,
            "per_page": per_page
        }
        
        try:
            response = requests.get(url, params=params, headers=self.headers)
            response.raise_for_status()
            return response.json().get("items", [])
        except requests.RequestException as e:
            console.print(f"[red]搜索代码时出错: {e}[/red]")
            return []

    def get_user_details(self, username: str) -> Optional[Dict]:
        """获取用户详情"""
        url = f"{self.config.base_url}/users/{username}"
        
        try:
            response = requests.get(url, headers=self.headers)
            response.raise_for_status()
            return response.json()
        except requests.RequestException:
            return None

class GitHubSearchCLI:
    def __init__(self):
        self.config = self.load_config()
        self.searcher = GitHubSearcher(self.config)
        self.current_results = []

    def load_config(self) -> GitHubConfig:
        """加载配置"""
        token = os.environ.get("GITHUB_TOKEN")
        if not token:
            token_path = os.path.expanduser("~/.github_token")
            if os.path.exists(token_path):
                with open(token_path, 'r') as f:
                    token = f.read().strip()
        
        return GitHubConfig(token=token)

    def display_welcome(self):
        """显示欢迎界面"""
        console.print(Panel.fit(
            "[bold blue]GitHub 多功能搜索工具[/bold blue]\n\n"
            "🔍 搜索仓库、用户、问题、代码\n"
            "⭐ 支持多种排序和过滤选项\n"
            "🎯 交互式菜单和漂亮输出\n",
            title="欢迎使用", border_style="green", box=box.ROUNDED
        ))

    def main_menu(self):
        """主菜单"""
        while True:
            console.print("\n[bold]主菜单[/bold]")
            console.print("1. 🗂️  搜索仓库")
            console.print("2. 👥 搜索用户")
            console.print("3. 🐛 搜索问题")
            console.print("4. 💻 搜索代码")
            console.print("5. ⚙️  设置")
            console.print("6. 🚪 退出")
            
            choice = Prompt.ask("请选择操作", choices=["1", "2", "3", "4", "5", "6"])
            
            if choice == "1":
                self.search_repositories_menu()
            elif choice == "2":
                self.search_users_menu()
            elif choice == "3":
                self.search_issues_menu()
            elif choice == "4":
                self.search_code_menu()
            elif choice == "5":
                self.settings_menu()
            elif choice == "6":
                console.print("[green]再见！👋[/green]")
                break

    def search_repositories_menu(self):
        """搜索仓库菜单"""
        console.print("\n[bold]🔍 搜索仓库[/bold]")
        query = Prompt.ask("输入搜索关键词")
        
        if not query:
            console.print("[yellow]搜索关键词不能为空[/yellow]")
            return
        
        sort_options = {
            "1": ("stars", "星标数"),
            "2": ("forks", "复刻数"),
            "3": ("help-wanted-issues", "需要帮助的问题"),
            "4": ("updated", "更新时间")
        }
        
        console.print("\n[bold]排序方式:[/bold]")
        for key, (_, desc) in sort_options.items():
            console.print(f"{key}. {desc}")
        
        sort_choice = Prompt.ask("选择排序方式", choices=list(sort_options.keys()), default="1")
        sort_by, _ = sort_options[sort_choice]
        
        order = Prompt.ask("排序顺序", choices=["desc", "asc"], default="desc")
        per_page = IntPrompt.ask("显示数量", default=10)
        
        with Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}")) as progress:
            task = progress.add_task("搜索中...", total=None)
            results = self.searcher.search_repositories(query, sort_by, order, per_page)
            progress.update(task, completed=True)
        
        self.current_results = results
        self.display_repository_results(results)

    def search_users_menu(self):
        """搜索用户菜单"""
        console.print("\n[bold]👥 搜索用户[/bold]")
        query = Prompt.ask("输入搜索关键词（用户名、邮箱等）")
        
        if not query:
            console.print("[yellow]搜索关键词不能为空[/yellow]")
            return
        
        sort_options = {
            "1": ("followers", "粉丝数"),
            "2": ("repositories", "仓库数"),
            "3": ("joined", "加入时间")
        }
        
        console.print("\n[bold]排序方式:[/bold]")
        for key, (_, desc) in sort_options.items():
            console.print(f"{key}. {desc}")
        
        sort_choice = Prompt.ask("选择排序方式", choices=list(sort_options.keys()), default="1")
        sort_by, _ = sort_options[sort_choice]
        
        order = Prompt.ask("排序顺序", choices=["desc", "asc"], default="desc")
        per_page = IntPrompt.ask("显示数量", default=10)
        
        with Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}")) as progress:
            task = progress.add_task("搜索中...", total=None)
            results = self.searcher.search_users(query, sort_by, order, per_page)
            progress.update(task, completed=True)
        
        self.current_results = results
        self.display_user_results(results)

    def search_issues_menu(self):
        """搜索问题菜单"""
        console.print("\n[bold]🐛 搜索问题[/bold]")
        query = Prompt.ask("输入搜索关键词")
        
        if not query:
            console.print("[yellow]搜索关键词不能为空[/yellow]")
            return
        
        sort_options = {
            "1": ("comments", "评论数"),
            "2": ("created", "创建时间"),
            "3": ("updated", "更新时间")
        }
        
        console.print("\n[bold]排序方式:[/bold]")
        for key, (_, desc) in sort_options.items():
            console.print(f"{key}. {desc}")
        
        sort_choice = Prompt.ask("选择排序方式", choices=list(sort_options.keys()), default="1")
        sort_by, _ = sort_options[sort_choice]
        
        order = Prompt.ask("排序顺序", choices=["desc", "asc"], default="desc")
        per_page = IntPrompt.ask("显示数量", default=10)
        
        with Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}")) as progress:
            task = progress.add_task("搜索中...", total=None)
            results = self.searcher.search_issues(query, sort_by, order, per_page)
            progress.update(task, completed=True)
        
        self.current_results = results
        self.display_issue_results(results)

    def search_code_menu(self):
        """搜索代码菜单"""
        console.print("\n[bold]💻 搜索代码[/bold]")
        query = Prompt.ask("输入搜索关键词（代码片段）")
        
        if not query:
            console.print("[yellow]搜索关键词不能为空[/yellow]")
            return
        
        sort_options = {
            "1": ("indexed", "索引时间"),
        }
        
        console.print("\n[bold]排序方式:[/bold]")
        for key, (_, desc) in sort_options.items():
            console.print(f"{key}. {desc}")
        
        sort_choice = Prompt.ask("选择排序方式", choices=list(sort_options.keys()), default="1")
        sort_by, _ = sort_options[sort_choice]
        
        order = Prompt.ask("排序顺序", choices=["desc", "asc"], default="desc")
        per_page = IntPrompt.ask("显示数量", default=10)
        
        with Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}")) as progress:
            task = progress.add_task("搜索中...", total=None)
            results = self.searcher.search_code(query, sort_by, order, per_page)
            progress.update(task, completed=True)
        
        self.current_results = results
        self.display_code_results(results)

    def display_repository_results(self, results: List[Dict]):
        """显示仓库搜索结果"""
        if not results:
            console.print("[yellow]未找到相关仓库[/yellow]")
            return
        
        table = Table(title=f"📦 仓库搜索结果 ({len(results)} 个)", box=box.ROUNDED)
        table.add_column("编号", style="cyan")
        table.add_column("名称", style="green")
        table.add_column("所有者", style="blue")
        table.add_column("⭐ 星标", style="yellow")
        table.add_column("📂 复刻", style="magenta")
        table.add_column("语言", style="red")
        
        for i, repo in enumerate(results, 1):
            table.add_row(
                str(i),
                repo['name'],
                repo['owner']['login'],
                str(repo['stargazers_count']),
                str(repo['forks_count']),
                repo.get('language', 'N/A')
            )
        
        console.print(table)
        self.handle_result_actions(results, "repository")

    def display_user_results(self, results: List[Dict]):
        """显示用户搜索结果"""
        if not results:
            console.print("[yellow]未找到相关用户[/yellow]")
            return
        
        table = Table(title=f"👥 用户搜索结果 ({len(results)} 个)", box=box.ROUNDED)
        table.add_column("编号", style="cyan")
        table.add_column("用户名", style="green")
        table.add_column("类型", style="blue")
        
        for i, user in enumerate(results, 1):
            table.add_row(
                str(i),
                user['login'],
                user['type']
            )
        
        console.print(table)
        self.handle_result_actions(results, "user")

    def display_issue_results(self, results: List[Dict]):
        """显示问题搜索结果"""
        if not results:
            console.print("[yellow]未找到相关问题[/yellow]")
            return
        
        table = Table(title=f"🐛 问题搜索结果 ({len(results)} 个)", box=box.ROUNDED)
        table.add_column("编号", style="cyan")
        table.add_column("标题", style="green")
        table.add_column("仓库", style="blue")
        table.add_column("状态", style="yellow")
        table.add_column("💬 评论", style="magenta")
        
        for i, issue in enumerate(results, 1):
            repo_name = issue['repository_url'].split('/')[-1]
            table.add_row(
                str(i),
                issue['title'][:50] + "..." if len(issue['title']) > 50 else issue['title'],
                repo_name,
                issue['state'],
                str(issue['comments'])
            )
        
        console.print(table)
        self.handle_result_actions(results, "issue")

    def display_code_results(self, results: List[Dict]):
        """显示代码搜索结果"""
        if not results:
            console.print("[yellow]未找到相关代码[/yellow]")
            return
        
        table = Table(title=f"💻 代码搜索结果 ({len(results)} 个)", box=box.ROUNDED)
        table.add_column("编号", style="cyan")
        table.add_column("文件", style="green")
        table.add_column("仓库", style="blue")
        table.add_column("路径", style="yellow")
        
        for i, code in enumerate(results, 1):
            repo_name = code['repository']['full_name']
            table.add_row(
                str(i),
                code['name'],
                repo_name,
                code['path']
            )
        
        console.print(table)
        self.handle_result_actions(results, "code")

    def handle_result_actions(self, results: List[Dict], result_type: str):
        """处理结果操作选项"""
        if not results:
            return
        
        console.print("\n[bold]操作选项:[/bold]")
        console.print("1. 📖 查看详情")
        console.print("2. 🌐 在浏览器中打开")
        console.print("3. ⬅️  返回主菜单")
        
        choice = Prompt.ask("请选择操作", choices=["1", "2", "3"])
        
        if choice == "1":
            item_num = IntPrompt.ask("请输入项目编号", default=1, show_default=True)
            if 1 <= item_num <= len(results):
                self.show_item_details(results[item_num-1], result_type)
            else:
                console.print("[red]无效的编号[/red]")
        elif choice == "2":
            item_num = IntPrompt.ask("请输入项目编号", default=1, show_default=True)
            if 1 <= item_num <= len(results):
                self.open_in_browser(results[item_num-1], result_type)
            else:
                console.print("[red]无效的编号[/red]")
        elif choice == "3":
            return

    def show_item_details(self, item: Dict, item_type: str):
        """显示项目详情"""
        if item_type == "repository":
            self.show_repository_details(item)
        elif item_type == "user":
            self.show_user_details(item)
        elif item_type == "issue":
            self.show_issue_details(item)
        elif item_type == "code":
            self.show_code_details(item)

    def show_repository_details(self, repo: Dict):
        """显示仓库详情"""
        console.print(Panel.fit(
            f"[bold]{repo['name']}[/bold]\n\n"
            f"📝 {repo.get('description', '无描述')}\n"
            f"👤 所有者: {repo['owner']['login']}\n"
            f"⭐ 星标: {repo['stargazers_count']}\n"
            f"📂 复刻: {repo['forks_count']}\n"
            f"👀 监视: {repo['watchers_count']}\n"
            f"🐛 问题: {repo['open_issues_count']}\n"
            f"💻 语言: {repo.get('language', 'N/A')}\n"
            f"📅 创建于: {repo['created_at']}\n"
            f"🔄 更新于: {repo['updated_at']}\n\n"
            f"[link={repo['html_url']}]🌐 打开仓库[/link]",
            title="仓库详情", border_style="blue"
        ))

    def show_user_details(self, user: Dict):
        """显示用户详情"""
        # 获取用户详细信息
       
