| """ |
| 命令行界面模块 |
| 提供友好的用户交互界面 |
| """ |
| |
| import asyncio |
| import click |
| import logging |
| import os |
| from typing import Optional |
| from rich.console import Console |
| from rich.panel import Panel |
| from rich.text import Text |
| from rich.prompt import Prompt, Confirm |
| from rich.logging import RichHandler |
| from rich.table import Table |
| from rich.progress import Progress, SpinnerColumn, TextColumn |
| |
| from ..core.agent import AgentCore, ThinkingMode |
| from ..mcp.integration import MCPToolIntegration |
| |
| |
| class CLIInterface: |
| """命令行界面类""" |
| |
| def __init__(self): |
| self.console = Console() |
| self.agent: Optional[AgentCore] = None |
| self.mcp_integration: Optional[MCPToolIntegration] = None |
| self.setup_logging() |
| |
| def setup_logging(self): |
| """设置日志""" |
| logging.basicConfig( |
| level=logging.INFO, |
| format="%(message)s", |
| datefmt="[%X]", |
| handlers=[RichHandler(console=self.console, rich_tracebacks=True)] |
| ) |
| |
| async def initialize(self, api_key: Optional[str] = None): |
| """初始化Agent和MCP集成""" |
| try: |
| with Progress( |
| SpinnerColumn(), |
| TextColumn("[progress.description]{task.description}"), |
| console=self.console, |
| ) as progress: |
| task = progress.add_task("初始化Claude Agent...", total=None) |
| |
| # 初始化Agent核心 |
| self.agent = AgentCore(api_key=api_key) |
| progress.update(task, description="初始化MCP工具集成...") |
| |
| # 初始化MCP集成 |
| self.mcp_integration = MCPToolIntegration(self.agent) |
| await self.mcp_integration.setup_default_tools() |
| await self.mcp_integration.enhance_agent_with_tools() |
| |
| progress.update(task, description="初始化完成!", completed=True) |
| |
| self.console.print("[green]✓[/green] Claude Agent 初始化完成!") |
| |
| except Exception as e: |
| self.console.print(f"[red]✗[/red] 初始化失败: {str(e)}") |
| self.agent = None |
| self.mcp_integration = None |
| |
| def show_welcome(self): |
| """显示欢迎信息""" |
| welcome_text = Text() |
| welcome_text.append("Claude Agent", style="bold cyan") |
| welcome_text.append(" - 智能命令行助手\n", style="cyan") |
| welcome_text.append("支持交互模式和YOLO自主模式\n", style="dim") |
| welcome_text.append("输入 'help' 查看帮助,'quit' 退出", style="dim") |
| |
| panel = Panel( |
| welcome_text, |
| title="欢迎", |
| border_style="cyan", |
| padding=(1, 2) |
| ) |
| self.console.print(panel) |
| |
| def show_help(self): |
| """显示帮助信息""" |
| help_table = Table(title="命令帮助") |
| help_table.add_column("命令", style="cyan") |
| help_table.add_column("描述", style="white") |
| |
| help_table.add_row("help", "显示此帮助信息") |
| help_table.add_row("mode", "切换思考模式 (interactive/yolo)") |
| help_table.add_row("history", "显示对话历史") |
| help_table.add_row("clear", "清空对话历史") |
| help_table.add_row("tools", "显示可用的MCP工具") |
| help_table.add_row("status", "显示系统状态") |
| help_table.add_row("quit", "退出程序") |
| |
| self.console.print(help_table) |
| |
| def show_status(self): |
| """显示系统状态""" |
| status_table = Table(title="系统状态") |
| status_table.add_column("项目", style="cyan") |
| status_table.add_column("状态", style="white") |
| |
| if not self.agent: |
| status_table.add_row("思考模式", "[red]Agent未初始化[/red]") |
| status_table.add_row("对话历史", "[red]Agent未初始化[/red]") |
| else: |
| mode_text = "YOLO模式" if self.agent.thinking_mode == ThinkingMode.YOLO else "交互模式" |
| status_table.add_row("思考模式", mode_text) |
| status_table.add_row("对话历史", f"{len(self.agent.conversation_history)} 条消息") |
| |
| if not self.mcp_integration: |
| status_table.add_row("MCP工具", "[red]MCP服务未连接[/red]") |
| else: |
| status_table.add_row("MCP工具", f"{len(self.mcp_integration.tool_manager.tools)} 个可用") |
| |
| self.console.print(status_table) |
| |
| def show_tools(self): |
| """显示可用工具""" |
| if not self.mcp_integration: |
| self.console.print("[red]MCP服务未连接[/red]") |
| return |
| |
| tools = self.mcp_integration.tool_manager.get_available_tools() |
| |
| if not tools: |
| self.console.print("[yellow]暂无可用的MCP工具[/yellow]") |
| return |
| |
| tools_table = Table(title="可用的MCP工具") |
| tools_table.add_column("工具名称", style="cyan") |
| tools_table.add_column("描述", style="white") |
| |
| for tool_name in tools: |
| tool_info = self.mcp_integration.tool_manager.get_tool_info(tool_name) |
| description = tool_info['description'] if tool_info else "无描述" |
| tools_table.add_row(tool_name, description) |
| |
| self.console.print(tools_table) |
| |
| def show_history(self): |
| """显示对话历史""" |
| if not self.agent: |
| self.console.print("[red]Agent未初始化[/red]") |
| return |
| |
| history = self.agent.get_conversation_history() |
| |
| if not history: |
| self.console.print("[yellow]暂无对话历史[/yellow]") |
| return |
| |
| for i, message in enumerate(history[-10:], 1): # 只显示最后10条 |
| role_style = "blue" if message["role"] == "user" else "green" |
| role_text = "用户" if message["role"] == "user" else "助手" |
| |
| content = message["content"] |
| if len(content) > 200: |
| content = content[:200] + "..." |
| |
| self.console.print(f"[{role_style}]{i}. {role_text}:[/{role_style}] {content}") |
| |
| async def switch_mode(self): |
| """切换思考模式""" |
| if not self.agent: |
| self.console.print("[red]Agent未初始化[/red]") |
| return |
| |
| current_mode = self.agent.thinking_mode |
| |
| if current_mode == ThinkingMode.INTERACTIVE: |
| new_mode = ThinkingMode.YOLO |
| mode_text = "YOLO自主模式" |
| else: |
| new_mode = ThinkingMode.INTERACTIVE |
| mode_text = "交互模式" |
| |
| if Confirm.ask(f"确定切换到{mode_text}吗?"): |
| self.agent.set_thinking_mode(new_mode) |
| self.console.print(f"[green]已切换到{mode_text}[/green]") |
| else: |
| self.console.print("[yellow]已取消切换[/yellow]") |
| |
| async def process_command(self, command: str) -> bool: |
| """处理特殊命令,返回True表示继续,False表示退出""" |
| command = command.lower().strip() |
| |
| if command == "quit" or command == "exit": |
| return False |
| |
| elif command == "help": |
| self.show_help() |
| |
| elif command == "status": |
| self.show_status() |
| |
| elif command == "tools": |
| self.show_tools() |
| |
| elif command == "history": |
| self.show_history() |
| |
| elif command == "clear": |
| if not self.agent: |
| self.console.print("[red]Agent未初始化[/red]") |
| return True |
| |
| if Confirm.ask("确定清空对话历史吗?"): |
| self.agent.clear_history() |
| self.console.print("[green]对话历史已清空[/green]") |
| |
| elif command == "mode": |
| await self.switch_mode() |
| |
| else: |
| # 不是特殊命令,交给Agent处理 |
| await self.process_user_input(command) |
| |
| return True |
| |
| async def process_user_input(self, user_input: str): |
| """处理用户输入""" |
| if not self.agent: |
| self.console.print("[red]Agent未初始化[/red]") |
| return |
| |
| try: |
| # 根据模式显示不同的处理提示 |
| if self.agent.thinking_mode == ThinkingMode.YOLO: |
| # YOLO模式的详细进度显示 |
| with Progress( |
| SpinnerColumn(), |
| TextColumn("[progress.description]{task.description}"), |
| console=self.console, |
| ) as progress: |
| # 创建主任务 |
| main_task = progress.add_task("启动YOLO自主思考模式...", total=None) |
| |
| # 创建一个日志处理器来捕获思考过程 |
| import logging |
| from rich.logging import RichHandler |
| |
| # 临时创建一个日志捕获器 |
| class ProgressLogHandler(logging.Handler): |
| def __init__(self, progress_task, progress_obj): |
| super().__init__() |
| self.progress_task = progress_task |
| self.progress_obj = progress_obj |
| |
| def emit(self, record): |
| # 捕获所有包含思考过程标识符的日志 |
| thinking_indicators = [ |
| "🧠", "📋", "🔍", "✅", "🎯", "🔧", "📊", |
| "💡", "⚡", "🎉", "❌", "🔄", "⚠️" |
| ] |
| if any(indicator in record.msg for indicator in thinking_indicators): |
| # 清理消息,移除多余的格式 |
| clean_msg = record.msg.strip() |
| self.progress_obj.update(self.progress_task, description=clean_msg) |
| |
| # 设置日志捕获 |
| agent_logger = logging.getLogger('claude_agent.core.agent') |
| progress_handler = ProgressLogHandler(main_task, progress) |
| agent_logger.addHandler(progress_handler) |
| |
| try: |
| # 处理用户输入 |
| response = await self.agent.process_user_input(user_input) |
| |
| # 如果有MCP工具调用,处理工具调用 |
| enhanced_response = await self.mcp_integration.process_tool_calls_in_response(response) |
| |
| progress.update(main_task, description="思考完成!", completed=True) |
| |
| finally: |
| # 清理日志处理器 |
| agent_logger.removeHandler(progress_handler) |
| |
| else: |
| # 交互模式的简单显示 |
| with Progress( |
| SpinnerColumn(), |
| TextColumn("[progress.description]{task.description}"), |
| console=self.console, |
| ) as progress: |
| task = progress.add_task("处理中...", total=None) |
| |
| # 处理用户输入 |
| response = await self.agent.process_user_input(user_input) |
| |
| # 如果有MCP工具调用,处理工具调用 |
| enhanced_response = await self.mcp_integration.process_tool_calls_in_response(response) |
| |
| # 显示响应 |
| self.console.print("\n[green]助手:[/green]") |
| self.console.print(Panel(enhanced_response, border_style="green")) |
| return enhanced_response |
| |
| except Exception as e: |
| self.console.print(f"[red]处理请求时出错: {str(e)}[/red]") |
| return None |
| |
| async def run_interactive_loop(self): |
| """运行交互循环""" |
| self.show_welcome() |
| |
| while True: |
| try: |
| # 获取用户输入 |
| user_input = Prompt.ask("\n[cyan]你[/cyan]", console=self.console) |
| |
| if not user_input.strip(): |
| continue |
| |
| # 处理命令或输入 |
| should_continue = await self.process_command(user_input) |
| if not should_continue: |
| break |
| |
| except KeyboardInterrupt: |
| if Confirm.ask("\n确定要退出吗?"): |
| break |
| else: |
| self.console.print("继续...") |
| continue |
| except EOFError: |
| break |
| except Exception as e: |
| self.console.print(f"[red]发生未知错误: {str(e)}[/red]") |
| continue |
| |
| async def shutdown(self): |
| """关闭资源""" |
| if self.mcp_integration: |
| await self.mcp_integration.shutdown() |
| self.console.print("[yellow]再见![/yellow]") |
| |
| |
| @click.command() |
| @click.option('--api-key', '-k', help='Claude API密钥') |
| @click.option('--interactive/--no-interactive', default=True, help='是否启动交互模式') |
| @click.option('--mode', type=click.Choice(['interactive', 'yolo']), default='interactive', help='思考模式') |
| @click.argument('message', required=False) |
| def main(api_key: Optional[str], interactive: bool, mode: str, message: Optional[str]): |
| """Claude Agent 命令行工具""" |
| |
| async def run(): |
| cli = CLIInterface() |
| |
| try: |
| # 从环境变量获取API密钥 |
| api_key_final = api_key or os.getenv('CLAUDE_API_KEY') or os.getenv('ANTHROPIC_API_KEY') |
| if not api_key_final: |
| click.echo("警告: 未提供Claude API密钥,将使用默认配置") |
| |
| # 初始化 |
| await cli.initialize(api_key_final) |
| |
| # 设置模式 |
| thinking_mode = ThinkingMode.YOLO if mode == 'yolo' else ThinkingMode.INTERACTIVE |
| cli.agent.set_thinking_mode(thinking_mode) |
| |
| # 如果提供了消息且不是交互模式,直接处理 |
| if message and not interactive: |
| await cli.process_user_input(message) |
| else: |
| # 启动交互循环 |
| await cli.run_interactive_loop() |
| |
| except Exception as e: |
| click.echo(f"错误: {str(e)}") |
| finally: |
| await cli.shutdown() |
| |
| # 运行异步主函数 |
| asyncio.run(run()) |
| |
| |
| if __name__ == "__main__": |
| main() |