Files
docker-configs/discord_bot/utils/claude_code_client.py
Will Song 568dcc45e4 Fix router failover and add Discord bot
- Remove duplicate claude_api provider to fix automatic failover
- Enhance error detection with HTTP status codes and more indicators
- Add comprehensive README documentation with manual switching
- Implement Discord bot with Claude Code CLI integration
- Support /terminal and /claude commands with AI-powered responses

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-16 21:35:52 -05:00

211 lines
8.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import asyncio
import logging
import subprocess
import os
import json
from typing import Optional, Dict, List
logger = logging.getLogger(__name__)
class ClaudeCodeClient:
"""Claude Code CLI客户端"""
def __init__(self, work_dir: str = "/home/will/docker"):
self.work_dir = work_dir
self.claude_cli = "claude" # 假设claude命令已安装
async def run_claude_command(self, command: str, timeout: int = 30) -> Optional[str]:
"""运行Claude Code CLI命令"""
try:
# 检查Claude CLI是否可用
check_process = await asyncio.create_subprocess_shell(
"which claude",
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
await check_process.communicate()
if check_process.returncode != 0:
logger.warning("Claude CLI未找到回退到shell命令执行")
return await self._fallback_shell_execution(command)
# 构建Claude CLI命令
claude_command = f'claude "{command}"'
# 执行Claude CLI
process = await asyncio.create_subprocess_shell(
claude_command,
cwd=self.work_dir,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=timeout)
output = ""
if stdout:
output += stdout.decode('utf-8', errors='replace')
if stderr:
output += "\n" + stderr.decode('utf-8', errors='replace')
if process.returncode == 0:
return output.strip()
else:
logger.error(f"Claude CLI执行失败: {output}")
return await self._fallback_shell_execution(command)
except asyncio.TimeoutError:
logger.error(f"Claude CLI超时: {command}")
return f"⏰ **Claude CLI超时**: 执行时间超过{timeout}"
except Exception as e:
logger.error(f"Claude CLI执行错误: {e}")
return await self._fallback_shell_execution(command)
async def _fallback_shell_execution(self, command: str) -> str:
"""回退到shell命令执行"""
try:
# 检查是否为直接shell命令
if self._is_shell_command(command):
return await self._execute_shell_command(command)
else:
# 尝试解释自然语言命令
return await self._interpret_natural_language(command)
except Exception as e:
return f"❌ **执行错误**: {str(e)}"
def _is_shell_command(self, command: str) -> bool:
"""判断是否为shell命令"""
shell_commands = [
'ls', 'pwd', 'cd', 'cat', 'grep', 'find', 'docker', 'docker-compose',
'tail', 'head', 'ps', 'df', 'du', 'top', 'htop', 'systemctl', 'service'
]
cmd_parts = command.strip().split()
if cmd_parts:
return cmd_parts[0] in shell_commands
return False
async def _execute_shell_command(self, command: str) -> str:
"""直接执行shell命令"""
try:
process = await asyncio.create_subprocess_shell(
command,
cwd=self.work_dir,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=30)
output = ""
if stdout:
output += stdout.decode('utf-8', errors='replace')
if stderr:
output += "\n" + stderr.decode('utf-8', errors='replace')
if process.returncode == 0:
return f"✅ **命令执行成功**\n```bash\n$ {command}\n{output}\n```"
else:
return f"⚠️ **命令执行完成 (退出码: {process.returncode})**\n```bash\n$ {command}\n{output}\n```"
except asyncio.TimeoutError:
return f"⏰ **命令超时**: 执行时间超过30秒\n```bash\n$ {command}\n```"
except Exception as e:
return f"❌ **执行错误**: {str(e)}\n```bash\n$ {command}\n```"
async def _interpret_natural_language(self, command: str) -> str:
"""解释自然语言命令(简单映射)"""
command_lower = command.lower()
# 自然语言到shell命令的映射
mappings = {
"查看当前目录": "ls -la",
"查看文件": "ls -la",
"当前目录": "pwd",
"当前位置": "pwd",
"查看容器": "docker ps",
"docker容器": "docker ps",
"容器状态": "docker ps",
"查看进程": "ps aux",
"磁盘使用": "df -h",
"存储空间": "df -h",
"内存使用": "free -h",
"系统状态": "systemctl status"
}
for key, cmd in mappings.items():
if key in command_lower:
return await self._execute_shell_command(cmd)
# 尝试提取Docker相关操作
if "启动" in command and "容器" in command:
# 尝试提取容器名
words = command.split()
for word in words:
if word not in ["启动", "容器", ""]:
container_name = word
return await self._execute_shell_command(f"docker start {container_name}")
if "停止" in command and "容器" in command:
words = command.split()
for word in words:
if word not in ["停止", "容器", ""]:
container_name = word
return await self._execute_shell_command(f"docker stop {container_name}")
# 未识别的命令
return f"""🤔 **未能理解命令**: {command}
**建议使用以下格式:**
• 直接shell命令: `ls`, `docker ps`, `pwd`
• 自然语言: "查看当前目录", "查看Docker容器状态"
**常用命令示例:**
```bash
ls -la # 查看文件列表
docker ps # 查看容器状态
docker-compose ps # 查看compose服务
df -h # 查看磁盘使用
```
💡 **提示**: 当前使用简化的命令解释器建议直接使用shell命令获得最佳效果"""
async def ask_claude(self, question: str) -> str:
"""使用Claude Code CLI进行对话"""
try:
# 首先检查是否有Claude CLI
check_process = await asyncio.create_subprocess_shell(
"which claude",
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
await check_process.communicate()
if check_process.returncode != 0:
return "❌ **Claude CLI未安装**: 请先安装Claude Code CLI\n\n可以访问 https://docs.anthropic.com/en/docs/claude-code 了解安装方法"
# 使用Claude CLI进行对话
claude_command = f'claude "{question}"'
process = await asyncio.create_subprocess_shell(
claude_command,
cwd=self.work_dir,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=60)
if process.returncode == 0 and stdout:
response = stdout.decode('utf-8', errors='replace').strip()
return f"🤖 **Claude Code回答:**\n\n{response}"
else:
error_msg = stderr.decode('utf-8', errors='replace') if stderr else "未知错误"
return f"❌ **Claude CLI错误**: {error_msg}"
except asyncio.TimeoutError:
return f"⏰ **Claude CLI超时**: 响应时间超过60秒"
except Exception as e:
logger.error(f"Claude CLI对话失败: {e}")
return f"❌ **对话错误**: {str(e)}"