Skill: ntaffffff/tool-enhancement (version 1.0.5)

--- name: tool-enhancement description: "🚀 55+ 增强工具集 - 为 OpenClaw 赋能。文件操作、Shell执行、Git工作流、网页请求、子Agent调度、分层记忆、终端UI，一应俱全。" metadata: { "openclaw": { "emoji": "🛠️", "requires": { "anyBins": ["python3", "git", "curl"] }, "install": [] } } --- <div align="center"> # 🛠️ Tool Enhancement **为 OpenClaw 打造的 55+ 增强工具集** [![License](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE) [![Version](https://img.shields.io/badge/version-1.0.2-green.svg)](./SKILL.md) [![Tools](https://img.shields.io/badge/tools-55+-orange.svg)](#工具总览) *参考 Claude Code 架构设计 | 支持自然语言调用* </div> --- ## ✨ 特性 | 特性 | 说明 | |------|------| | 🔰 **55+ 工具** | 覆盖文件/Shell/Git/网页/记忆/UI 场景 | | 🧠 **自然语言** | 智能理解用户意图，自动调用工具 | | 📦 **统一注册** | 全局 ToolRegistry，工具即插即用 | | 🔒 **权限控制** | FS_READ / EXECUTE / NETWORK 等权限体系 | | ⚡ **异步执行** | 所有工具原生 async/await 支持 | | 🎨 **终端UI** | 表格/进度条/菜单/树形等丰富组件 | --- ## 🚀 快速开始 ### 安装 ```bash # 通过 ClawHub 安装（推荐） clawhub install tool-enhancement # 或手动克隆 git clone https://github.com/ntaffffff/openclaw-tool-enhancement.git ``` ### 基本用法 ```bash # 方式1: 自然语言（推荐） python3 ~/.openclaw/workspace/skills/tool-enhancement/tools/run_nlp.py "读取 /etc/hostname" # 方式2: 直接调用工具 python3 ~/.openclaw/workspace/skills/tool-enhancement/tools/run_tool.py file_read path="/etc/hostname" ``` --- ## 📖 使用教程 ### 在 OpenClaw 对话中直接使用 当用户说以下内容时，AI 会自动调用对应工具： | 用户说 | 调用的工具 | 示例 | |--------|-----------|------| | "读取 /etc/hostname" | file_read | 📄 查看文件内容 | | "执行 ls -la" | shell_exec | 💻 运行命令 | | "查看系统信息" | system_info | 💻 系统概况 | | "天气怎么样" | http_request | 🌤️ 查询天气 | | "搜索 Python 教程" | web_search | 🔍 网络搜索 | | "记住今天开会" | memory_remember | 🧠 存储记忆 | | "git status" | git_status | 📦 Git 状态 | | "推送代码" | git_push | 📤 代码推送 | ### 代码中调用 ```python import sys sys.path.insert(0, "~/.openclaw/workspace/skills/tool-enhancement/tools") from __init__ import load_all_tools, get_registry import asyncio async def main(): # 加载所有工具 load_all_tools() # 获取注册表 registry = get_registry() # 执行工具 result = await registry.execute("file_read", path="/etc/hostname") print(result.data) # 输出: dxx asyncio.run(main()) ``` --- ## 🛠️ 工具总览 ### 📁 文件操作 (9个) | 工具 | 功能 | 示例 | |------|------|------| | `file_read` | 读取文件 | `path="/etc/hostname"` | | `file_write` | 写入文件 | `path="/tmp/test.txt", content="hello"` | | `file_edit` | 编辑文件 | `path="/tmp/test.txt", old="a", new="b"` | | `file_delete` | 删除文件 | `path="/tmp/test.txt"` | | `file_glob` | 搜索文件 | `pattern="*.py"` | | `file_copy` | 复制文件 | `src="/a.txt", dst="/b.txt"` | | `file_move` | 移动文件 | `src="/a.txt", dst="/b.txt"` | | `file_info` | 文件信息 | `path="/tmp"` | | `file_list` | 列出目录 | `path="/tmp"` | ### 💻 执行工具 (7个) | 工具 | 功能 | 示例 | |------|------|------| | `shell_exec` | 执行Shell命令 | `command="ls -la"` | | `shell_background` | 后台执行 | `command="python server.py", detached=True` | | `process_list` | 列出进程 | - | | `process_kill` | 终止进程 | `pid=1234` | | `shell_sudo` | 提权执行 | `command="apt update"` | | `script_run` | 运行脚本 | `path="/tmp/script.sh"` | | `system_info` | 系统信息 | - | ### 📦 Git 工具 (8个) | 工具 | 功能 | 示例 | |------|------|------| | `git_status` | 查看状态 | `path="."` | | `git_log` | 查看日志 | `path=".", max_count=10` | | `git_diff` | 查看差异 | `path="."` | | `git_commit` | 提交代码 | `path=".", message="fix: bug"` | | `git_push` | 推送到远程 | `path="."` | | `git_pull` | 拉取更新 | `path="."` | | `git_branch` | 分支操作 | `path="."` | | `git_remote` | 远程操作 | `path="."` | ### 🌐 网页工具 (5个) | 工具 | 功能 | 示例 | |------|------|------| | `http_request` | HTTP请求 | `url="http://wttr.in/Shanghai"` | | `web_fetch` | 网页抓取 | `url="https://example.com"` | | `web_search` | 网络搜索 | `query="Python 教程"` | | `api_request` | API调用 | `url="https://api.github.com"` | | `url_parse` | URL解析 | `url="https://a.com/b?c=1"` | ### 🧠 记忆工具 (6个) | 工具 | 功能 | 示例 | |------|------|------| | `memory_remember` | 存储记忆 | `content="重要事项", importance=5` | | `memory_recall` | 检索记忆 | `query="项目", limit=5` | | `memory_summarize` | 总结记忆 | `query="上周"` | | `memory_dream` | 记忆整合 | `dry_run=false` | | `memory_context` | 上下文记忆 | `query="当前任务"` | | `memory_stats` | 记忆统计 | - | ### 🤖 Agent 工具 (6个) | 工具 | 功能 | 示例 | |------|------|------| | `agent_spawn` | 启动子Agent | `name="coder", prompt="分析代码"` | | `agent_delegate` | 委托任务 | `agent_id="xxx", task="..."` | | `agent_result` | 获取结果 | `task_id="xxx"` | | `agent_list` | 列出Agent | - | | `agent_cancel` | 取消Agent | `task_id="xxx"` | | `coordinator` | 多Agent协调 | `task="...", phases=[...]` | ### 🎨 UI 工具 (8个) | 工具 | 功能 | |------|------| | `ui_spinner` | 加载动画 | | `ui_progress` | 进度条 | | `ui_table` | 表格显示 | | `ui_tree` | 树形结构 | | `ui_markdown` | Markdown渲染 | | `ui_status` | 状态显示 | | `ui_confirm` | 确认对话框 | | `ui_menu` | 菜单选择 | ### 🔌 MCP 工具 (6个) | 工具 | 功能 | |------|------| | `mcp_list` | 列出MCP服务器 | | `mcp_discover` | 发现MCP服务 | | `mcp_call` | 调用MCP工具 | | `mcp_resource` | 访问MCP资源 | | `mcp_prompt` | 使用MCP提示词 | | `mcp_status` | MCP状态 | --- ## 📁 文件结构 ``` tool-enhancement/ ├── SKILL.md # 本文档 ├── tools/ │ ├── __init__.py # 统一加载器 │ ├── schema.py # 基础类定义 │ ├── run_tool.py # 命令行工具调用器 │ ├── run_nlp.py # 自然语言理解器 │ ├── file_tools.py # 文件操作 (9) │ ├── exec_tools.py # 执行工具 (7) │ ├── git_tools.py # Git 工具 (8) │ ├── web_tools.py # 网页工具 (5) │ ├── mcp_tools.py # MCP 工具 (6) │ ├── agent_tools.py # Agent 工具 (6) │ ├── memory_tools.py # 记忆工具 (6) │ └── ui_tools.py # UI 工具 (8) └── README.md ``` --- ## 🤝 贡献 欢迎提交 Issue 和 PR！ --- ## 📄 许可证 MIT License --- <div align="center"> **🌟 喜欢这个工具？请给个 Star！** </div>

path: README.md
content: # 🛠️ Tool Enhancement

**为 OpenClaw 打造的 55+ 增强工具集**

[![License](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
[![Version](https://img.shields.io/badge/version-1.0.0-green.svg)](./SKILL.md)
[![Tools](https://img.shields.io/badge/tools-55+-orange.svg)](#-工具总览)

*参考 Claude Code 架构设计 | 支持自然语言调用*

---

## ✨ 特性

| 特性 | 说明 |
|------|------|
| 🔰 **55+ 工具** | 覆盖文件/Shell/Git/网页/记忆/UI 场景 |
| 🧠 **自然语言** | 智能理解用户意图，自动调用工具 |
| 📦 **统一注册** | 全局 ToolRegistry，工具即插即用 |
| 🔒 **权限控制** | FS_READ / EXECUTE / NETWORK 等权限体系 |
| ⚡ **异步执行** | 所有工具原生 async/await 支持 |
| 🎨 **终端UI** | 表格/进度条/菜单/树形等丰富组件 |

---

## 🚀 快速开始

### 安装

```bash
# 通过 ClawHub 安装（推荐）
clawhub install tool-enhancement

# 或手动克隆
git clone https://github.com/ntaffffff/openclaw-tool-enhancement.git
```

### 基本用法

```bash
# 方式1: 自然语言（推荐）
python3 ~/.openclaw/workspace/skills/tool-enhancement/tools/run_nlp.py "读取 /etc/hostname"

# 方式2: 直接调用工具
python3 ~/.openclaw/workspace/skills/tool-enhancement/tools/run_tool.py file_read path="/etc/hostname"
```

---

## 📖 使用教程

### 在 OpenClaw 对话中直接使用

当用户说以下内容时，AI 会自动调用对应工具：

| 用户说 | 调用的工具 |
|--------|-----------|
| "读取 /etc/hostname" | file_read |
| "执行 ls -la" | shell_exec |
| "查看系统信息" | system_info |
| "天气怎么样" | http_request |
| "搜索 Python 教程" | web_search |
| "记住今天开会" | memory_remember |
| "git status" | git_status |
| "推送代码" | git_push |

### 代码中调用

```python
import sys
sys.path.insert(0, "~/.openclaw/workspace/skills/tool-enhancement/tools")

from __init__ import load_all_tools, get_registry
import asyncio

async def main():
    load_all_tools()
    registry = get_registry()
    result = await registry.execute("file_read", path="/etc/hostname")
    print(result.data)

asyncio.run(main())
```

---

## 🛠️ 工具总览

### 📁 文件操作 (9个)
- `file_read`, `file_write`, `file_edit`, `file_delete`
- `file_glob`, `file_copy`, `file_move`, `file_info`, `file_list`

### 💻 执行工具 (7个)
- `shell_exec`, `shell_background`, `process_list`, `process_kill`
- `shell_sudo`, `script_run`, `system_info`

### 📦 Git 工具 (8个)
- `git_status`, `git_log`, `git_diff`, `git_commit`
- `git_push`, `git_pull`, `git_branch`, `git_remote`

### 🌐 网页工具 (5个)
- `http_request`, `web_fetch`, `web_search`, `api_request`, `url_parse`

### 🧠 记忆工具 (6个)
- `memory_remember`, `memory_recall`, `memory_summarize`
- `memory_dream`, `memory_context`, `memory_stats`

### 🤖 Agent 工具 (6个)
- `agent_spawn`, `agent_delegate`, `agent_result`
- `agent_list`, `agent_cancel`, `coordinator`

### 🎨 UI 工具 (8个)
- `ui_spinner`, `ui_progress`, `ui_table`, `ui_tree`
- `ui_markdown`, `ui_status`, `ui_confirm`, `ui_menu`

### 🔌 MCP 工具 (6个)
- `mcp_list`, `mcp_discover`, `mcp_call`
- `mcp_resource`, `mcp_prompt`, `mcp_status`

---

## 📁 文件结构

```
tool-enhancement/
├── SKILL.md                    # OpenClaw Skill 文档
├── README.md                   # 本文档
├── tools/
│   ├── __init__.py             # 统一加载器
│   ├── schema.py               # 基础类定义
│   ├── run_tool.py             # 命令行工具调用器
│   ├── run_nlp.py              # 自然语言理解器
│   ├── file_tools.py           # 文件操作 (9)
│   ├── exec_tools.py           # 执行工具 (7)
│   ├── git_tools.py            # Git 工具 (8)
│   ├── web_tools.py            # 网页工具 (5)
│   ├── mcp_tools.py            # MCP 工具 (6)
│   ├── agent_tools.py          # Agent 工具 (6)
│   ├── memory_tools.py         # 记忆工具 (6)
│   └── ui_tools.py             # UI 工具 (8)
```

---

## 🤝 贡献

欢迎提交 Issue 和 PR！

---

## 📄 许可证

MIT License

---

<div align="center">

**🌟 喜欢这个工具？请给个 Star！**

</div>

path: publish.sh
content: #!/bin/bash
# 发布脚本：一键推送到 GitHub 和 ClawHub

set -e

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$SCRIPT_DIR"

echo "📦 开始发布..."

# 读取当前版本（从 SKILL.md 的 metadata 中）
CURRENT=$(grep -A1 '"version":' SKILL.md | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+' | head -1)
if [ -z "$CURRENT" ]; then
    CURRENT="1.0.0"
fi

echo "  当前版本: $CURRENT"

# 递增版本号
MAJOR=$(echo $CURRENT | cut -d. -f1)
MINOR=$(echo $CURRENT | cut -d. -f2)
PATCH=$(echo $CURRENT | cut -d. -f3)
NEW_VERSION="${MAJOR}.${MINOR}.$((PATCH + 1))"

echo "  新版本: $NEW_VERSION"

# Git 提交并推送
echo "  📤 推送到 GitHub..."
git add -A
git commit -m "release: v$NEW_VERSION" 2>/dev/null || echo "  (无新变更)"
git push

# ClawHub 发布 (必须用完整路径 + DEBUG=*)
echo "  ☁️  发布到 ClawHub..."
DEBUG=* clawhub publish "$SCRIPT_DIR" \
    --slug tool-enhancement \
    --name "Tool Enhancement" \
    --version "$NEW_VERSION" \
    --changelog "版本更新" 2>&1 | tail -3

echo ""
echo "✅ 发布完成!"
echo "  GitHub: https://github.com/ntaffffff/openclaw-tool-enhancement"
echo "  ClawHub: https://clawhub.ai/skill/tool-enhancement"
echo "  版本: v$NEW_VERSION"

path: tools/__init__.py
content: #!/usr/bin/env python3
"""
统一工具注册器

自动发现并注册所有工具模块
"""

import sys
from pathlib import Path

# 添加 tools 目录到路径
TOOLS_DIR = Path(__file__).parent
sys.path.insert(0, str(TOOLS_DIR))

# 导入 schema 模块的基础类
from schema import ToolRegistry, BaseTool


# 全局注册表实例
_registry = None
_loaded = False


def get_registry() -> ToolRegistry:
    """获取全局注册表"""
    global _registry
    if _registry is None:
        _registry = ToolRegistry()
    return _registry


def load_all_tools() -> int:
    """加载所有工具模块"""
    global _loaded
    
    # 如果已经加载过，直接返回
    registry = get_registry()
    if _loaded and len(registry.list_all()) > 0:
        return len(registry.list_all())
    
    total = 0
    
    # 工具模块列表
    tool_modules = [
        ("file_tools", "FILE_TOOLS"),
        ("exec_tools", "EXEC_TOOLS"),
        ("git_tools", "GIT_TOOLS"),
        ("web_tools", "WEB_TOOLS"),
        ("mcp_tools", "MCP_TOOLS"),
        ("agent_tools", "AGENT_TOOLS"),
        ("memory_tools", "MEMORY_TOOLS"),
        ("ui_tools", "UI_TOOLS"),
        ("session_tools", "SESSION_TOOLS"),  # 新增：会话协作工具
    ]
    
    for module_name, var_name in tool_modules:
        try:
            module = __import__(module_name, fromlist=[var_name])
            tool_classes = getattr(module, var_name, [])
            
            if tool_classes:
                for tool_class in tool_classes:
                    try:
                        tool = tool_class()
                        registry.register(tool, module_name.replace("_tools", ""))
                    except Exception:
                        pass
                
                total += len(tool_classes)
        except Exception:
            pass
    
    _loaded = True
    return total


async def test_tools():
    """测试工具"""
    load_all_tools()
    registry = get_registry()
    
    print(f"\n已加载 {len(registry.list_all())} 个工具\n")
    
    # 测试几个关键工具
    test_cases = [
        ("file_read", {"path": "/etc/hostname"}),
        ("system_info", {}),
    ]
    
    for tool_name, params in test_cases:
        result = await registry.execute(tool_name, **params)
        status = "✓" if result.success else "✗"
        print(f"{status} {tool_name}")


if __name__ == "__main__":
    import asyncio
    asyncio.run(test_tools())

path: tools/agent_tools.py
content: #!/usr/bin/env python3
"""
子 Agent 调度工具集

提供多 Agent 协作能力 + Agent Teams 集成
参考 Claude Code 的 Coordinator 系统设计
"""

from __future__ import annotations

import asyncio
import json
import os
import subprocess
import sys
import uuid
from pathlib import Path
from typing import Any, Dict, List, Optional, Callable
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from concurrent.futures import ThreadPoolExecutor

sys.path.insert(0, str(Path(__file__).parent))
from schema import BaseTool, ToolDefinition, ToolResult, ToolCapability


# ============ Agent Teams 路径 ============
AGENT_TEAMS_PATH = Path.home() / ".openclaw/workspace/skills/agent-teams/agent_teams.py"


class AgentStatus(Enum):
    """Agent 状态"""
    IDLE = "idle"
    RUNNING = "running"
    COMPLETED = "completed"
    FAILED = "failed"
    CANCELLED = "cancelled"


@dataclass
class AgentTask:
    """Agent 任务"""
    id: str
    name: str
    description: str
    prompt: str
    model: str = "default"
    status: AgentStatus = AgentStatus.IDLE
    result: Any = None
    error: Optional[str] = None
    created_at: datetime = field(default_factory=datetime.now)
    completed_at: Optional[datetime] = None


class AgentSpawnTool(BaseTool):
    """Agent spawn 工具 - 创建子 Agent"""
    
    def __init__(self):
        super().__init__(ToolDefinition(
            name="agent_spawn",
            description="创建并启动一个子 Agent 执行任务",
            input_schema={
                "type": "object",
                "properties": {
                    "name": {"type": "string", "description": "Agent 名称"},
                    "description": {"type": "string", "description": "Agent 描述"},
                    "prompt": {"type": "string", "description": "Agent 任务提示"},
                    "model": {"type": "string", "description": "使用的模型"},
                    "timeout": {"type": "number", "default": 300, "description": "超时时间(秒)"}
                },
                "required": ["name", "prompt"]
            },
            capabilities={ToolCapability.EXECUTE},
            tags=["agent", "spawn", "create", "sub-agent"],
            examples=["创建 Agent: name='code-reviewer', prompt='审查这段代码...'"]
        ))
        self._agents: Dict[str, AgentTask] = {}
        self._executor = ThreadPoolExecutor(max_workers=5)
    
    async def execute(self, **kwargs) -> ToolResult:
        name = kwargs.get("name")
        description = kwargs.get("description", "")
        prompt = kwargs.get("prompt")
        model = kwargs.get("model", "default")
        timeout = kwargs.get("timeout", 300)
        
        try:
            task_id = str(uuid.uuid4())
            
            task = AgentTask(
                id=task_id,
                name=name,
                description=description,
                prompt=prompt,
                model=model,
                status=AgentStatus.RUNNING
            )
            
            self._agents[task_id] = task
            asyncio.create_task(self._run_agent(task_id, timeout))
            
            return ToolResult(
                success=True,
                data={
                    "task_id": task_id,
                    "name": name,
                    "status": "running",
                    "message": f"Agent '{name}' 已启动"
                }
            )
            
        except Exception as e:
            return ToolResult(success=False, error=str(e))
    
    async def _run_agent(self, task_id: str, timeout: int):
        task = self._agents.get(task_id)
        if not task:
            return
        
        try:
            await asyncio.sleep(2)
            task.status = AgentStatus.COMPLETED
            task.result = {"output": f"Agent '{task.name}' completed", "task_id": task_id}
            task.completed_at = datetime.now()
        except Exception as e:
            task.status = AgentStatus.FAILED
            task.error = str(e)
            task.completed_at = datetime.now()
    
    def get_agent_status(self, task_id: str) -> Optional[AgentTask]:
        return self._agents.get(task_id)
    
    def list_agents(self) -> List[AgentTask]:
        return list(self._agents.values())


class AgentDelegateTool(BaseTool):
    """Agent 委托工具"""
    
    def __init__(self):
        super().__init__(ToolDefinition(
            name="agent_delegate",
            description="将任务委托给已存在的 Agent",
            input_schema={
                "type": "object",
                "properties": {
                    "task_id": {"type": "string", "description": "Agent 任务 ID"},
                    "message": {"type": "string", "description": "发送的消息"},
                    "timeout": {"type": "number", "default": 60}
                },
                "required": ["task_id", "message"]
            },
            capabilities={ToolCapability.EXECUTE},
            tags=["agent", "delegate", "message"]
        ))
        self._spawn_tool: Optional[AgentSpawnTool] = None
    
    @property
    def spawn_tool(self) -> AgentSpawnTool:
        if not self._spawn_tool:
            self._spawn_tool = AgentSpawnTool()
        return self._spawn_tool
    
    async def execute(self, **kwargs) -> ToolResult:
        task_id = kwargs.get("task_id")
        message = kwargs.get("message")
        
        task = self.spawn_tool.get_agent_status(task_id)
        if not task:
            return ToolResult(success=False, error=f"Agent 不存在: {task_id}")
        
        return ToolResult(
            success=True,
            data={"task_id": task_id, "message": message, "response": "消息已发送"}
        )


class AgentResultTool(BaseTool):
    """Agent 结果获取工具"""
    
    def __init__(self):
        super().__init__(ToolDefinition(
            name="agent_result",
            description="获取 Agent 执行结果",
            input_schema={
                "type": "object",
                "properties": {
                    "task_id": {"type": "string", "description": "Agent 任务 ID"},
                    "wait": {"type": "boolean", "default": False, "description": "等待完成"},
                    "timeout": {"type": "number", "default": 60}
                },
                "required": ["task_id"]
            },
            capabilities={ToolCapability.EXECUTE},
            tags=["agent", "result", "output"]
        ))
        self._spawn_tool: Optional[AgentSpawnTool] = None
    
    @property
    def spawn_tool(self) -> AgentSpawnTool:
        if not self._spawn_tool:
            self._spawn_tool = AgentSpawnTool()
        return self._spawn_tool
    
    async def execute(self, **kwargs) -> ToolResult:
        task_id = kwargs.get("task_id")
        wait = kwargs.get("wait", False)
        timeout = kwargs.get("timeout", 60)
        
        task = self.spawn_tool.get_agent_status(task_id)
        if not task:
            return ToolResult(success=False, error=f"Agent 不存在: {task_id}")
        
        return ToolResult(
            success=task.status == AgentStatus.COMPLETED,
            data={
                "task_id": task_id,
                "name": task.name,
                "status": task.status.value,
                "result": task.result,
                "error": task.error
            }
        )


class AgentListTool(BaseTool):
    """Agent 列表工具"""
    
    def __init__(self):
        super().__init__(ToolDefinition(
            name="agent_list",
            description="列出所有子 Agent",
            input_schema={
                "type": "object",
                "properties": {
                    "status": {"type": "string", "description": "按状态过滤"}
                }
            },
            capabilities={ToolCapability.EXECUTE},
            tags=["agent", "list"]
        ))
        self._spawn_tool: Optional[AgentSpawnTool] = None
    
    @property
    def spawn_tool(self) -> AgentSpawnTool:
        if not self._spawn_tool:
            self._spawn_tool = AgentSpawnTool()
        return self._spawn_tool
    
    async def execute(self, **kwargs) -> ToolResult:
        status_filter = kwargs.get("status")
        agents = self.spawn_tool.list_agents()
        
        if status_filter:
            agents = [a for a in agents if a.status.value == status_filter]
        
        return ToolResult(
            success=True,
            data={
                "agents": [{"task_id": a.id, "name": a.name, "status": a.status.value} for a in agents],
                "count": len(agents)
            }
        )


class AgentCancelTool(BaseTool):
    """Agent 取消工具"""
    
    def __init__(self):
        super().__init__(ToolDefinition(
            name="agent_cancel",
            description="取消正在运行的 Agent",
            input_schema={
                "type": "object",
                "properties": {
                    "task_id": {"type": "string", "description": "Agent 任务 ID"}
                },
                "required": ["task_id"]
            },
            capabilities={ToolCapability.EXECUTE},
            tags=["agent", "cancel", "stop"]
        ))
        self._spawn_tool: Optional[AgentSpawnTool] = None
    
    @property
    def spawn_tool(self) -> AgentSpawnTool:
        if not self._spawn_tool:
            self._spawn_tool = AgentSpawnTool()
        return self._spawn_tool
    
    async def execute(self, **kwargs) -> ToolResult:
        task_id = kwargs.get("task_id")
        task = self.spawn_tool.get_agent_status(task_id)
        if not task:
            return ToolResult(success=False, error=f"Agent 不存在: {task_id}")
        
        task.status = AgentStatus.CANCELLED
        task.completed_at = datetime.now()
        
        return ToolResult(success=True, data={"task_id": task_id, "cancelled": True})


class CoordinatorTool(BaseTool):
    """多 Agent 协调器 + Agent Teams 集成"""
    
    def __init__(self):
        super().__init__(ToolDefinition(
            name="coordinator",
            description="协调多个子 Agent 完成复杂任务（支持 Agent Teams）",
            input_schema={
                "type": "object",
                "properties": {
                    "task": {"type": "string", "description": "总体任务描述"},
                    "team_id": {"type": "string", "description": "Agent Teams 团队 ID"},
                    "agents": {"type": "array", "items": {"type": "string"}, "description": "指定 Agent 列表"},
                    "parallel": {"type": "boolean", "default": True, "description": "是否并行执行"}
                },
                "required": ["task"]
            },
            capabilities={ToolCapability.EXECUTE},
            tags=["agent", "coordinator", "multi-agent", "team"]
        ))
        self._spawn_tool = AgentSpawnTool()
    
    async def execute(self, **kwargs) -> ToolResult:
        task = kwargs.get("task")
        team_id = kwargs.get("team_id")
        agents = kwargs.get("agents", [])
        parallel = kwargs.get("parallel", True)
        
        # 如果指定了 team_id，使用 Agent Teams
        if team_id and AGENT_TEAMS_PATH.exists():
            try:
                result = subprocess.run(
                    ["python3", str(AGENT_TEAMS_PATH), "teams", "run-multi", team_id, ",".join(agents), task],
                    capture_output=True,
                    text=True,
                    timeout=300
                )
                return ToolResult(
                    success=result.returncode == 0,
                    data={"output": result.stdout, "team_id": team_id, "agents": agents},
                    error=result.stderr if result.returncode != 0 else None
                )
            except Exception as e:
                return ToolResult(success=False, error=str(e))
        
        # 否则使用简单的协调器
        phase_prompts = {
            "research": f"Research: 调查并收集关于以下任务的信息: {task}",
            "synthesis": f"Synthesis: 汇总研究发现，制定解决方案",
            "implementation": f"Implementation: 执行解决方案",
            "verification": f"Verification: 验证实现是否正确"
        }
        
        phases = list(phase_prompts.keys())
        results = {}
        
        if parallel:
            tasks = []
            for phase in phases:
                prompt = phase_prompts[phase]
                task_id = str(uuid.uuid4())
                task_obj = AgentTask(
                    id=task_id,
                    name=f"coordinator-{phase}",
                    description=phase,
                    prompt=prompt,
                    status=AgentStatus.RUNNING
                )
                self._spawn_tool._agents[task_id] = task_obj
                tasks.append(self._run_phase(task_id))
            
            await asyncio.gather(*tasks)
        
        return ToolResult(
            success=True,
            data={"task": task, "phases": phases, "results": results, "mode": "parallel" if parallel else "sequential"}
        )
    
    async def _run_phase(self, task_id: str):
        await asyncio.sleep(1)
        task = self._spawn_tool._agents.get(task_id)
        if task:
            task.status = AgentStatus.COMPLETED
            task.result = {"output": "phase completed"}
            task.completed_at = datetime.now()


# ============ Agent Teams 专用工具 ============

class TeamCreateTool(BaseTool):
    """创建 Agent Team"""
    
    def __init__(self):
        super().__init__(ToolDefinition(
            name="team_create",
            description="创建一个 Agent 团队",
            input_schema={
                "type": "object",
                "properties": {
                    "team_id": {"type": "string", "description": "团队 ID"},
                    "name": {"type": "string", "description": "团队名称"}
                },
                "required": ["team_id", "name"]
            },
            capabilities={ToolCapability.EXECUTE},
            tags=["team", "create"]
        ))
    
    async def execute(self, **kwargs) -> ToolResult:
        if not AGENT_TEAMS_PATH.exists():
            return ToolResult(success=False, error="Agent Teams 未安装")
        
        team_id = kwargs.get("team_id")
        name = kwargs.get("name")
        
        try:
            result = subprocess.run(
                ["python3", str(AGENT_TEAMS_PATH), "teams", "create", team_id, name],
                capture_output=True,
                text=True,
                timeout=10
            )
            return ToolResult(
                success=result.returncode == 0,
                data={"team_id": team_id, "name": name, "output": result.stdout}
            )
        except Exception as e:
            return ToolResult(success=False, error=str(e))


class TeamListTool(BaseTool):
    """列出 Agent Teams"""
    
    def __init__(self):
        super().__init__(ToolDefinition(
            name="team_list",
            description="列出所有 Agent 团队",
            input_schema={"type": "object", "properties": {}},
            capabilities={ToolCapability.EXECUTE},
            tags=["team", "list"]
        ))
    
    async def execute(self, **kwargs) -> ToolResult:
        if not AGENT_TEAMS_PATH.exists():
            return ToolResult(success=False, error="Agent Teams 未安装")
        
        try:
            result = subprocess.run(
                ["python3", str(AGENT_TEAMS_PATH), "teams", "list"],
                capture_output=True,
                text=True,
                timeout=10
            )
            teams = json.loads(result.stdout) if result.stdout else []
            return ToolResult(success=True, data={"teams": teams, "count": len(teams)})
        except Exception as e:
            return ToolResult(success=False, error=str(e))


class TeamAddAgentTool(BaseTool):
    """添加 Agent 到团队"""
    
    def __init__(self):
        super().__init__(ToolDefinition(
            name="team_add_agent",
            description="添加一个 Agent 到团队",
            input_schema={
                "type": "object",
                "properties": {
                    "team_id": {"type": "string", "description": "团队 ID"},
                    "agent_id": {"type": "string", "description": "Agent ID"},
                    "name": {"type": "string", "description": "Agent 名称"}
                },
                "required": ["team_id", "agent_id", "name"]
            },
            capabilities={ToolCapability.EXECUTE},
            tags=["team", "add", "agent"]
        ))
    
    async def execute(self, **kwargs) -> ToolResult:
        if not AGENT_TEAMS_PATH.exists():
            return ToolResult(success=False, error="Agent Teams 未安装")
        
        team_id = kwargs.get("team_id")
        agent_id = kwargs.get("agent_id")
        name = kwargs.get("name")
        
        try:
            result = subprocess.run(
                ["python3", str(AGENT_TEAMS_PATH), "teams", "add", team_id, agent_id, name],
                capture_output=True,
                text=True,
                timeout=10
            )
            return ToolResult(
                success=result.returncode == 0,
                data={"team_id": team_id, "agent_id": agent_id, "name": name}
            )
        except Exception as e:
            return ToolResult(success=False, error=str(e))


class TeamRunTaskTool(BaseTool):
    """团队执行任务"""
    
    def __init__(self):
        super().__init__(ToolDefinition(
            name="team_run_task",
            description="让团队执行任务",
            input_schema={
                "type": "object",
                "properties": {
                    "team_id": {"type": "string", "description": "团队 ID"},
                    "task": {"type": "string", "description": "任务描述"},
                    "agent_ids": {"type": "array", "items": {"type": "string"}, "description": "指定 Agent"}
                },
                "required": ["team_id", "task"]
            },
            capabilities={ToolCapability.EXECUTE},
            tags=["team", "run", "task"]
        ))
    
    async def execute(self, **kwargs) -> ToolResult:
        if not AGENT_TEAMS_PATH.exists():
            return ToolResult(success=False, error="Agent Teams 未安装")
        
        team_id = kwargs.get("team_id")
        task = kwargs.get("task")
        agent_ids = kwargs.get("agent_ids", [])
        
        agents_str = ",".join(agent_ids) if agent_ids else ""
        
        try:
            if agents_str:
                result = subprocess.run(
                    ["python3", str(AGENT_TEAMS_PATH), "teams", "run-multi", team_id, agents_str, task],
                    capture_output=True,
                    text=True,
                    timeout=300
                )
            else:
                result = subprocess.run(
                    ["python3", str(AGENT_TEAMS_PATH), "teams", "run", team_id, task],
                    capture_output=True,
                    text=True,
                    timeout=300
                )
            
            return ToolResult(
                success=result.returncode == 0,
                data={"team_id": team_id, "task": task, "output": result.stdout, "agents": agent_ids},
                error=result.stderr if result.returncode != 0 else None
            )
        except Exception as e:
            return ToolResult(success=False, error=str(e))


# 导出所有工具
AGENT_TOOLS = [
    AgentSpawnTool,
    AgentDelegateTool,
    AgentResultTool,
    AgentListTool,
    AgentCancelTool,
    CoordinatorTool,
    TeamCreateTool,
    TeamListTool,
    TeamAddAgentTool,
    TeamRunTaskTool,
]


def register_tools(registry):
    """注册所有 Agent 工具到注册表"""
    for tool_class in AGENT_TOOLS:
        tool = tool_class()
        registry.register(tool, "agent")
    return len(AGENT_TOOLS)

path: tools/discovery.py
content: #!/usr/bin/env python3
"""
工具注册表发现模块

自动发现可用工具、分类、版本管理
"""

import json
import importlib
import inspect
from pathlib import Path
from typing import Dict, List, Optional, Any, Callable
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum

try:
    from colorama import Fore, init
    init(autoreset=True)
except ImportError:
    class Fore:
        CYAN = GREEN = RED = YELLOW = BLUE = ""


class ToolCategory(Enum):
    """工具分类"""
    FILE = "file"              # 文件操作
    SEARCH = "search"          # 搜索
    EXECUTE = "execute"        # 执行命令
    WEB = "web"                # 网络请求
    CODE = "code"              # 代码相关
    DATA = "data"              # 数据处理
    AI = "ai"                  # AI 能力
    UTILITY = "utility"        # 工具类
    CUSTOM = "custom"          # 自定义


@dataclass
class ToolDescriptor:
    """工具描述符"""
    name: str
    description: str
    category: ToolCategory
    version: str
    author: str
    tags: List[str] = field(default_factory=list)
    input_schema: Dict[str, Any] = field(default_factory=dict)
    output_schema: Dict[str, Any] = field(default_factory=dict)
    capabilities: List[str] = field(default_factory=list)
    registered_at: datetime = field(default_factory=datetime.now)
    
    def to_dict(self) -> Dict:
        """转为字典"""
        return {
            "name": self.name,
            "description": self.description,
            "category": self.category.value,
            "version": self.version,
            "author": self.author,
            "tags": self.tags,
            "input_schema": self.input_schema,
            "output_schema": self.output_schema,
            "capabilities": self.capabilities,
            "registered_at": self.registered_at.isoformat()
        }


@dataclass
class ToolVersion:
    """工具版本"""
    version: str
    released_at: datetime
    changelog: str
    breaking: bool = False


class ToolRegistry:
    """工具注册表"""
    
    def __init__(self, storage_path: Path = None):
        self.storage_path = storage_path or Path.home() / ".openclaw" / "tools"
        self.storage_path.mkdir(parents=True, exist_ok=True)
        
        self.tools: Dict[str, ToolDescriptor] = {}
        self.tool_implementations: Dict[str, Callable] = {}
        self.tool_versions: Dict[str, List[ToolVersion]] = {}
        
        self._load_index()
    
    def _load_index(self):
        """加载索引"""
        index_file = self.storage_path / "index.json"
        if index_file.exists():
            try:
                data = json.loads(index_file.read_text())
                for tool_data in data.get("tools", []):
                    tool_data["category"] = ToolCategory(tool_data["category"])
                    tool_data["registered_at"] = datetime.fromisoformat(
                        tool_data["registered_at"]
                    )
                    tool = ToolDescriptor(**tool_data)
                    self.tools[tool.name] = tool
            except Exception:
                pass
    
    def _save_index(self):
        """保存索引"""
        data = {
            "tools": [t.to_dict() for t in self.tools.values()]
        }
        index_file = self.storage_path / "index.json"
        index_file.write_text(json.dumps(data, ensure_ascii=False, indent=2))
    
    def register(
        self,
        name: str,
        description: str,
        category: ToolCategory,
        handler: Callable,
        version: str = "1.0.0",
        author: str = "unknown",
        tags: List[str] = None,
        input_schema: Dict = None,
        output_schema: Dict = None,
        capabilities: List[str] = None
    ) -> ToolDescriptor:
        """注册工具"""
        descriptor = ToolDescriptor(
            name=name,
            description=description,
            category=category,
            version=version,
            author=author,
            tags=tags or [],
            input_schema=input_schema or {},
            output_schema=output_schema or {},
            capabilities=capabilities or []
        )
        
        self.tools[name] = descriptor
        self.tool_implementations[name] = handler
        self._save_index()
        
        print(f"{Fore.GREEN}✓ 注册工具: {name} v{version}{Fore.RESET}")
        return descriptor
    
    def unregister(self, name: str) -> bool:
        """注销工具"""
        if name in self.tools:
            del self.tools[name]
            if name in self.tool_implementations:
                del self.tool_implementations[name]
            self._save_index()
            return True
        return False
    
    def get(self, name: str) -> Optional[ToolDescriptor]:
        """获取工具"""
        return self.tools.get(name)
    
    def get_handler(self, name: str) -> Optional[Callable]:
        """获取工具处理器"""
        return self.tool_implementations.get(name)
    
    def list_all(self) -> List[ToolDescriptor]:
        """列出所有工具"""
        return list(self.tools.values())
    
    def list_by_category(self, category: ToolCategory) -> List[ToolDescriptor]:
        """按分类列出"""
        return [t for t in self.tools.values() if t.category == category]
    
    def search(self, query: str) -> List[ToolDescriptor]:
        """搜索工具"""
        query = query.lower()
        results = []
        
        for tool in self.tools.values():
            if (query in tool.name.lower() or
                query in tool.description.lower() or
                any(query in tag.lower() for tag in tool.tags)):
                results.append(tool)
        
        return results
    
    def has_tool(self, name: str) -> bool:
        """检查工具是否存在"""
        return name in self.tools
    
    def execute(self, name: str, **kwargs) -> Any:
        """执行工具"""
        handler = self.get_handler(name)
        if not handler:
            raise ValueError(f"工具不存在: {name}")
        
        return handler(**kwargs)
    
    async def execute_async(self, name: str, **kwargs) -> Any:
        """异步执行工具"""
        handler = self.get_handler(name)
        if not handler:
            raise ValueError(f"工具不存在: {name}")
        
        if inspect.iscoroutinefunction(handler):
            return await handler(**kwargs)
        return handler(**kwargs)


class ToolDiscovery:
    """工具自动发现"""
    
    def __init__(self, registry: ToolRegistry = None):
        self.registry = registry or ToolRegistry()
        self.discover_paths = [
            Path.home() / ".openclaw" / "tools",
            Path.cwd() / "tools",
        ]
    
    def add_search_path(self, path: Path):
        """添加搜索路径"""
        if path not in self.discover_paths:
            self.discover_paths.append(path)
    
    def discover_from_directory(self, directory: Path) -> List[ToolDescriptor]:
        """从目录发现工具"""
        discovered = []
        
        if not directory.exists():
            return discovered
        
        for py_file in directory.glob("*.py"):
            if py_file.name.startswith("_"):
                continue
            
            try:
                # 动态导入模块
                module_name = py_file.stem
                spec = importlib.util.spec_from_file_location(module_name, py_file)
                module = importlib.util.module_from_spec(spec)
                spec.loader.exec_module(module)
                
                # 查找工具注册函数
                if hasattr(module, "register_tools"):
                    tools = module.register_tools(self.registry)
                    discovered.extend(tools if isinstance(tools, list) else [tools])
                    
            except Exception as e:
                print(f"{Fore.YELLOW}⚠ 发现工具失败 {py_file}: {e}{Fore.RESET}")
        
        return discovered
    
    def discover_all(self) -> List[ToolDescriptor]:
        """从所有路径发现工具"""
        all_tools = []
        
        for path in self.discover_paths:
            tools = self.discover_from_directory(path)
            all_tools.extend(tools)
            if tools:
                print(f"{Fore.CYAN}从 {path} 发现 {len(tools)} 个工具{Fore.RESET}")
        
        return all_tools
    
    def auto_discover(self) -> int:
        """自动发现并注册"""
        tools = self.discover_all()
        print(f"{Fore.GREEN}✓ 自动发现 {len(tools)} 个工具{Fore.RESET}")
        return len(tools)


# ============ 使用示例 ============

def example():
    """示例"""
    print(f"{Fore.CYAN}=== 工具注册表示例 ==={Fore.RESET}\n")
    
    # 创建注册表
    registry = ToolRegistry()
    
    # 注册工具
    print("1. 注册工具:")
    
    def read_file_handler(path: str, lines: int = None) -> str:
        """读取文件"""
        with open(path, 'r') as f:
            content = f.read() if lines is None else ''.join(f.readlines()[:lines])
        return content
    
    registry.register(
        name="read_file",
        description="读取文件内容",
        category=ToolCategory.FILE,
        handler=read_file_handler,
        version="1.0.0",
        author="openclaw",
        tags=["file", "read", "io"],
        input_schema={
            "type": "object",
            "properties": {
                "path": {"type": "string", "description": "文件路径"},
                "lines": {"type": "integer", "description": "读取行数"}
            },
            "required": ["path"]
        },
        capabilities=["read"]
    )
    
    # 列出工具
    print("\n2. 所有工具:")
    for tool in registry.list_all():
        print(f"   - {tool.name} ({tool.category.value}): {tool.description}")
    
    # 搜索工具
    print("\n3. 搜索 'file':")
    results = registry.search("file")
    for tool in results:
        print(f"   - {tool.name}")
    
    # 执行工具
    print("\n4. 执行工具:")
    # 注意：这里会尝试读取实际文件，可能失败
    try:
        result = registry.execute("read_file", path="/etc/hostname")
        print(f"   结果: {result.strip()}")
    except Exception as e:
        print(f"   执行结果: {e}")
    
    # 工具发现
    print("\n5. 工具发现:")
    discovery = ToolDiscovery(registry)
    count = discovery.auto_discover()
    print(f"   共发现: {count} 个工具")
    
    print(f"\n{Fore.GREEN}✓ 工具注册表示例完成!{Fore.RESET}")


if __name__ == "__main__":
    example()

path: tools/exec_tools.py
content: #!/usr/bin/env python3
"""
Shell 执行工具集

提供命令执行、后台进程、sudo 等能力
参考 Claude Code 的 shell 执行工具设计
"""

from __future__ import annotations

import os
import sys
import asyncio
import subprocess
import shlex
import signal
import time
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple
from dataclasses import dataclass, field
from concurrent.futures import ThreadPoolExecutor

# 复用 schema.py 的基础类
import sys as _sys
_sys.path.insert(0, str(Path(__file__).parent))
from schema import BaseTool, ToolDefinition, ToolResult, ToolCapability


class ShellCommandTool(BaseTool):
    """Shell 命令执行工具"""
    
    def __init__(self):
        super().__init__(ToolDefinition(
            name="shell_exec",
            description="执行 Shell 命令，返回标准输出和错误",
            input_schema={
                "type": "object",
                "properties": {
                    "command": {"type": "string", "description": "要执行的命令"},
                    "cwd": {"type": "string", "description": "工作目录"},
                    "timeout": {"type": "number", "default": 30, "description": "超时秒数"},
                    "env": {"type": "object", "description": "环境变量"},
                    "shell": {"type": "boolean", "default": True, "description": "使用 shell"}
                },
                "required": ["command"]
            },
            capabilities={ToolCapability.EXECUTE},
            tags=["shell", "exec", "command", "bash"],
            examples=[
                "执行命令: command='ls -la'",
                "带超时: command='sleep 10', timeout=5"
            ]
        ))
        self._executor = ThreadPoolExecutor(max_workers=10)
    
    async def execute(self, **kwargs) -> ToolResult:
        command = kwargs.get("command")
        cwd = kwargs.get("cwd")
        timeout = kwargs.get("timeout", 30)
        env = kwargs.get("env")
        use_shell = kwargs.get("shell", True)
        
        try:
            # 安全检查：禁止危险命令
            dangerous = ["rm -rf /", "mkfs", "dd if=/dev/zero", ":(){:|:&};:"]
            for d in dangerous:
                if d in command:
                    return ToolResult(success=False, error=f"危险命令被拒绝: {d}")
            
            # 构建环境
            cmd_env = os.environ.copy()
            if env:
                cmd_env.update(env)
            
            # 执行命令
            loop = asyncio.get_event_loop()
            
            process = await asyncio.create_subprocess_shell(
                command,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE,
                cwd=cwd,
                env=cmd_env
            )
            
            try:
                stdout, stderr = await asyncio.wait_for(
                    process.communicate(),
                    timeout=timeout
                )
            except asyncio.TimeoutError:
                process.kill()
                await process.wait()
                return ToolResult(success=False, error=f"命令超时 ({timeout}s)")
            
            return ToolResult(
                success=process.returncode == 0,
                data={
                    "stdout": stdout.decode("utf-8", errors="replace"),
                    "stderr": stderr.decode("utf-8", errors="replace"),
                    "returncode": process.returncode,
                    "command": command
                },
                metadata={"timeout": timeout}
            )
            
        except Exception as e:
            return ToolResult(success=False, error=str(e))


class BackgroundProcessTool(BaseTool):
    """后台进程工具"""
    
    def __init__(self):
        super().__init__(ToolDefinition(
            name="shell_background",
            description="在后台启动进程，返回进程 ID",
            input_schema={
                "type": "object",
                "properties": {
                    "command": {"type": "string", "description": "要执行的命令"},
                    "cwd": {"type": "string", "description": "工作目录"},
                    "name": {"type": "string", "description": "进程名称"},
                    "log_file": {"type": "string", "description": "日志文件路径"}
                },
                "required": ["command"]
            },
            capabilities={ToolCapability.EXECUTE},
            tags=["shell", "background", "daemon", "process"]
        ))
        self._processes: Dict[str, asyncio.subprocess.Process] = {}
    
    async def execute(self, **kwargs) -> ToolResult:
        command = kwargs.get("command")
        cwd = kwargs.get("cwd")
        name = kwargs.get("name", f"bg_{int(time.time())}")
        log_file = kwargs.get("log_file")
        
        try:
            # 启动进程
            if log_file:
                log_path = Path(log_file)
                log_path.parent.mkdir(parents=True, exist_ok=True)
                log_fd = open(log_file, "w")
                
                process = await asyncio.create_subprocess_shell(
                    command,
                    stdout=log_fd,
                    stderr=asyncio.subprocess.STDOUT,
                    cwd=cwd
                )
            else:
                process = await asyncio.create_subprocess_shell(
                    command,
                    stdout=asyncio.subprocess.DEVNULL,
                    stderr=asyncio.subprocess.DEVNULL,
                    cwd=cwd
                )
            
            self._processes[name] = process
            
            return ToolResult(
                success=True,
                data={
                    "pid": process.pid,
                    "name": name,
                    "running": process.returncode is None,
                    "log_file": log_file
                }
            )
            
        except Exception as e:
            return ToolResult(success=False, error=str(e))
    
    def is_running(self, name: str) -> bool:
        """检查进程是否在运行"""
        if name not in self._processes:
            return False
        return self._processes[name].returncode is None
    
    def kill(self, name: str) -> bool:
        """终止后台进程"""
        if name in self._processes:
            proc = self._processes[name]
            if proc.returncode is None:
                proc.kill()
                return True
        return False


class ProcessListTool(BaseTool):
    """进程列表工具"""
    
    def __init__(self):
        super().__init__(ToolDefinition(
            name="process_list",
            description="列出正在运行的进程",
            input_schema={
                "type": "object",
                "properties": {
                    "user": {"type": "string", "description": "按用户过滤"},
                    "keyword": {"type": "string", "description": "按关键字过滤"},
                    "limit": {"type": "integer", "default": 20}
                }
            },
            capabilities={ToolCapability.EXECUTE},
            tags=["process", "ps", "list"]
        ))
    
    async def execute(self, **kwargs) -> ToolResult:
        user = kwargs.get("user")
        keyword = kwargs.get("keyword")
        limit = kwargs.get("limit", 20)
        
        try:
            cmd = ["ps", "aux"]
            proc = await asyncio.create_subprocess_exec(
                *cmd,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE
            )
            stdout, _ = await proc.communicate()
            
            lines = stdout.decode().strip().split("\n")[1:]  # 跳过表头
            processes = []
            
            for line in lines[:limit]:
                parts = line.split(None, 10)
                if len(parts) >= 11:
                    proc_info = {
                        "user": parts[0],
                        "pid": parts[1],
                        "cpu": parts[2],
                        "mem": parts[3],
                        "command": parts[10]
                    }
                    
                    # 过滤
                    if user and proc_info["user"] != user:
                        continue
                    if keyword and keyword.lower() not in proc_info["command"].lower():
                        continue
                    
                    processes.append(proc_info)
            
            return ToolResult(
                success=True,
                data={
                    "processes": processes,
                    "count": len(processes)
                }
            )
            
        except Exception as e:
            return ToolResult(success=False, error=str(e))


class ProcessKillTool(BaseTool):
    """进程终止工具"""
    
    def __init__(self):
        super().__init__(ToolDefinition(
            name="process_kill",
            description="终止进程",
            input_schema={
                "type": "object",
                "properties": {
                    "pid": {"type": "integer", "description": "进程 ID"},
                    "force": {"type": "boolean", "default": False, "description": "强制终止"},
                    "signal": {"type": "string", "default": "TERM", "description": "信号"}
                },
                "required": ["pid"]
            },
            capabilities={ToolCapability.EXECUTE},
            tags=["process", "kill", "terminate"]
        ))
    
    async def execute(self, **kwargs) -> ToolResult:
        pid = kwargs.get("pid")
        force = kwargs.get("force", False)
        signal_name = kwargs.get("signal", "TERM")
        
        try:
            sig = getattr(signal, f"SIG{signal_name}", signal.SIGTERM)
            
            if force:
                os.kill(pid, signal.SIGKILL)
            else:
                os.kill(pid, sig)
            
            return ToolResult(
                success=True,
                data={"pid": pid, "signal": signal_name, "killed": True}
            )
            
        except ProcessLookupError:
            return ToolResult(success=False, error=f"进程不存在: {pid}")
        except PermissionError:
            return ToolResult(success=False, error=f"权限不足: {pid}")
        except Exception as e:
            return ToolResult(success=False, error=str(e))


class SudoCommandTool(BaseTool):
    """sudo 命令执行工具"""
    
    def __init__(self):
        super().__init__(ToolDefinition(
            name="shell_sudo",
            description="使用 sudo 执行特权命令",
            input_schema={
                "type": "object",
                "properties": {
                    "command": {"type": "string", "description": "要执行的命令"},
                    "password": {"type": "string", "description": "sudo 密码（可选，默认从 stdin 读取）"},
                    "timeout": {"type": "number", "default": 30}
                },
                "required": ["command"]
            },
            capabilities={ToolCapability.EXECUTE},
            tags=["shell", "sudo", "root", "privilege"]
        ))
    
    async def execute(self, **kwargs) -> ToolResult:
        command = kwargs.get("command")
        [REDACTED_SECRET]"password")
        timeout = kwargs.get("timeout", 30)
        
        try:
            # 构建 sudo 命令
            sudo_cmd = f"sudo {command}"
            
            # 如果提供了密码，使用 -S 从 stdin 读取
            if [REDACTED_SECRET] = f"echo {shlex.quote(password)} | sudo -S {command}"
            else:
                full_cmd = sudo_cmd
            
            process = await asyncio.create_subprocess_shell(
                full_cmd,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE,
                stdin=asyncio.subprocess.PIPE if not password else asyncio.subprocess.DEVNULL
            )
            
            stdout, stderr = await asyncio.wait_for(
                process.communicate(),
                timeout=timeout
            )
            
            return ToolResult(
                success=process.returncode == 0,
                data={
                    "stdout": stdout.decode("utf-8", errors="replace"),
                    "stderr": stderr.decode("utf-8", errors="replace"),
                    "returncode": process.returncode
                },
                metadata={"used_password": bool(password)}
            )
            
        except asyncio.TimeoutError:
            return ToolResult(success=False, error="命令超时")
        except Exception as e:
            return ToolResult(success=False, error=str(e))


class ScriptRunnerTool(BaseTool):
    """脚本运行工具 - 支持 Python/Bash/Node"""
    
    def __init__(self):
        super().__init__(ToolDefinition(
            name="script_run",
            description="运行脚本文件 (Python/Bash/Node)",
            input_schema={
                "type": "object",
                "properties": {
                    "path": {"type": "string", "description": "脚本路径"},
                    "interpreter": {"type": "string", "description": "解释器 (auto/python/bash/node)"},
                    "args": {"type": "array", "description": "脚本参数"},
                    "env": {"type": "object", "description": "环境变量"},
                    "timeout": {"type": "number", "default": 60}
                },
                "required": ["path"]
            },
            capabilities={ToolCapability.EXECUTE},
            tags=["script", "run", "python", "bash", "node"]
        ))
    
    async def execute(self, **kwargs) -> ToolResult:
        path = kwargs.get("path")
        interpreter = kwargs.get("interpreter", "auto")
        args = kwargs.get("args", [])
        env = kwargs.get("env")
        timeout = kwargs.get("timeout", 60)
        
        try:
            script_path = Path(path)
            if not script_path.exists():
                return ToolResult(success=False, error=f"脚本不存在: {path}")
            
            # 自动检测解释器
            if interpreter == "auto":
                ext = script_path.suffix.lower()
                if ext == ".py":
                    interpreter = "python"
                elif ext in [".sh", ".bash"]:
                    interpreter = "bash"
                elif ext == ".js":
                    interpreter = "node"
                else:
                    return ToolResult(success=False, error=f"无法自动检测解释器: {ext}")
            
            # 构建命令
            cmd = [interpreter, str(script_path)] + args
            
            # 环境变量
            cmd_env = os.environ.copy()
            if env:
                cmd_env.update(env)
            
            # 执行
            proc = await asyncio.create_subprocess_exec(
                *cmd,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE,
                env=cmd_env
            )
            
            stdout, stderr = await asyncio.wait_for(
                proc.communicate(),
                timeout=timeout
            )
            
            return ToolResult(
                success=proc.returncode == 0,
                data={
                    "stdout": stdout.decode("utf-8", errors="replace"),
                    "stderr": stderr.decode("utf-8", errors="replace"),
                    "returncode": proc.returncode,
                    "interpreter": interpreter
                },
                metadata={"script": path, "timeout": timeout}
            )
            
        except asyncio.TimeoutError:
            return ToolResult(success=False, error=f"脚本执行超时: {timeout}s")
        except FileNotFoundError:
            return ToolResult(success=False, error=f"解释器不存在: {interpreter}")
        except Exception as e:
            return ToolResult(success=False, error=str(e))


class SystemInfoTool(BaseTool):
    """系统信息工具"""
    
    def __init__(self):
        super().__init__(ToolDefinition(
            name="system_info",
            description="获取系统信息",
            input_schema={
                "type": "object",
                "properties": {
                    "detail": {"type": "boolean", "default": False, "description": "详细信息"}
                }
            },
            capabilities={ToolCapability.EXECUTE},
            tags=["system", "info", "os", "kernel"]
        ))
    
    async def execute(self, **kwargs) -> ToolResult:
        detail = kwargs.get("detail", False)
        
        try:
            import platform
            import socket
            
            info = {
                "hostname": socket.gethostname(),
                "os": platform.system(),
                "os_version": platform.version(),
                "os_release": platform.release(),
                "arch": platform.machine(),
                "python_version": platform.python_version(),
                "processor": platform.processor(),
            }
            
            if detail:
                # CPU 信息
                try:
                    with open("/proc/cpuinfo", "r") as f:
                        cpu_lines = [l for l in f.readlines() if "model name" in l]
                        if cpu_lines:
                            info["cpu_model"] = cpu_lines[0].split(":")[1].strip()
                except:
                    pass
                
                # 内存信息
                try:
                    with open("/proc/meminfo", "r") as f:
                        mem_lines = f.readlines()
                        for line in mem_lines:
                            if line.startswith("MemTotal:"):
                                info["memory_total"] = line.split()[1]
                            elif line.startswith("MemAvailable:"):
                                info["memory_available"] = line.split()[1]
                except:
                    pass
            
            return ToolResult(success=True, data=info)
            
        except Exception as e:
            return ToolResult(success=False, error=str(e))


# 导出所有工具
EXEC_TOOLS = [
    ShellCommandTool,
    BackgroundProcessTool,
    ProcessListTool,
    ProcessKillTool,
    SudoCommandTool,
    ScriptRunnerTool,
    SystemInfoTool,
]


def register_tools(registry):
    """注册所有执行工具到注册表"""
    for tool_class in EXEC_TOOLS:
        tool = tool_class()
        registry.register(tool, "execute")
    return len(EXEC_TOOLS)

path: tools/file_tools.py
content: #!/usr/bin/env python3
"""
文件操作工具集

提供完整的文件操作能力：读、写、编辑、删除、搜索、复制、移动
参考 Claude Code 工具系统设计
"""

from __future__ import annotations

import os
import shutil
import glob as glob_module
from pathlib import Path
from typing import Any, Dict, List, Optional
from dataclasses import dataclass, field

# 复用 schema.py 的基础类
import sys
sys.path.insert(0, str(Path(__file__).parent))
from schema import BaseTool, ToolDefinition, ToolResult, ToolCapability


class FileReadTool(BaseTool):
    """文件读取工具 - 增强版"""
    
    def __init__(self):
        super().__init__(ToolDefinition(
            name="file_read",
            description="读取文件内容，支持编码选择和行数限制",
            input_schema={
                "type": "object",
                "properties": {
                    "path": {"type": "string", "description": "文件路径"},
                    "encoding": {"type": "string", "default": "utf-8", "description": "文件编码"},
                    "limit": {"type": "integer", "description": "限制读取行数"},
                    "offset": {"type": "integer", "description": "起始行号"},
                    "tail": {"type": "boolean", "default": False, "description": "从文件末尾读取"}
                },
                "required": ["path"]
            },
            capabilities={ToolCapability.FS_READ},
            tags=["file", "read", "io"],
            examples=[
                "读取配置文件: path='/etc/config.conf'",
                "读取前10行: path='/tmp/log.txt', limit=10"
            ]
        ))
    
    async def execute(self, **kwargs) -> ToolResult:
        path = kwargs.get("path")
        encoding = kwargs.get("encoding", "utf-8")
        limit = kwargs.get("limit")
        offset = kwargs.get("offset", 0)
        tail = kwargs.get("tail", False)
        
        try:
            with open(path, "r", encoding=encoding) as f:
                lines = f.readlines()
            
            if tail:
                lines = lines[-limit:] if limit else lines
            
            if offset > 0:
                lines = lines[offset:]
            
            if limit:
                lines = lines[:limit]
            
            content = "".join(lines)
            
            return ToolResult(
                success=True,
                data={
                    "content": content,
                    "lines": len(lines),
                    "path": path
                },
                metadata={"offset": offset, "limit": limit}
            )
        except FileNotFoundError:
            return ToolResult(success=False, error=f"文件不存在: {path}")
        except PermissionError:
            return ToolResult(success=False, error=f"权限不足: {path}")
        except Exception as e:
            return ToolResult(success=False, error=str(e))


class FileWriteTool(BaseTool):
    """文件写入工具"""
    
    def __init__(self):
        super().__init__(ToolDefinition(
            name="file_write",
            description="写入内容到文件，支持创建和覆盖",
            input_schema={
                "type": "object",
                "properties": {
                    "path": {"type": "string", "description": "文件路径"},
                    "content": {"type": "string", "description": "写入内容"},
                    "encoding": {"type": "string", "default": "utf-8"},
                    "append": {"type": "boolean", "default": False, "description": "追加模式"}
                },
                "required": ["path", "content"]
            },
            capabilities={ToolCapability.FS_WRITE},
            tags=["file", "write", "io"],
            examples=["写入配置: path='/tmp/config.conf', content='key=value'"]
        ))
    
    async def execute(self, **kwargs) -> ToolResult:
        path = kwargs.get("path")
        content = kwargs.get("content")
        encoding = kwargs.get("encoding", "utf-8")
        append = kwargs.get("append", False)
        
        try:
            mode = "a" if append else "w"
            
            # 确保目录存在
            Path(path).parent.mkdir(parents=True, exist_ok=True)
            
            with open(path, mode, encoding=encoding) as f:
                f.write(content)
            
            return ToolResult(
                success=True,
                data={
                    "path": path,
                    "bytes_written": len(content.encode(encoding)),
                    "mode": "append" if append else "write"
                }
            )
        except PermissionError:
            return ToolResult(success=False, error=f"权限不足: {path}")
        except Exception as e:
            return ToolResult(success=False, error=str(e))


class FileEditTool(BaseTool):
    """文件编辑工具 - 支持正则表达式替换"""
    
    def __init__(self):
        super().__init__(ToolDefinition(
            name="file_edit",
            description="编辑文件内容，支持精确替换和正则替换",
            input_schema={
                "type": "object",
                "properties": {
                    "path": {"type": "string", "description": "文件路径"},
                    "old": {"type": "string", "description": "要替换的内容"},
                    "new": {"type": "string", "description": "替换后的内容"},
                    "regex": {"type": "boolean", "default": False, "description": "是否使用正则"},
                    "global": {"type": "boolean", "default": False, "description": "全局替换"}
                },
                "required": ["path", "old", "new"]
            },
            capabilities={ToolCapability.FS_WRITE},
            tags=["file", "edit", "replace"],
            examples=["替换文本: path='/tmp/file.txt', old='old', new='new'"]
        ))
    
    async def execute(self, **kwargs) -> ToolResult:
        import re
        
        path = kwargs.get("path")
        old_text = kwargs.get("old")
        new_text = kwargs.get("new")
        use_regex = kwargs.get("regex", False)
        global_replace = kwargs.get("global", False)
        
        try:
            with open(path, "r", encoding="utf-8") as f:
                content = f.read()
            
            if use_regex:
                pattern = re.compile(old_text, re.MULTILINE)
                new_content = pattern.sub(new_text, content if global_replace else new_text, count=0 if global_replace else 1)
                replacements = len(pattern.findall(content)) if global_replace else (1 if pattern.search(content) else 0)
            else:
                if global_replace:
                    new_content = content.replace(old_text, new_text)
                    replacements = content.count(old_text)
                else:
                    new_content = content.replace(old_text, new_text, 1)
                    replacements = 1 if old_text in content else 0
            
            with open(path, "w", encoding="utf-8") as f:
                f.write(new_content)
            
            return ToolResult(
                success=True,
                data={
                    "path": path,
                    "replacements": replacements,
                    "old_length": len(old_text),
                    "new_length": len(new_text)
                }
            )
        except FileNotFoundError:
            return ToolResult(success=False, error=f"文件不存在: {path}")
        except Exception as e:
            return ToolResult(success=False, error=str(e))


class FileDeleteTool(BaseTool):
    """文件删除工具"""
    
    def __init__(self):
        super().__init__(ToolDefinition(
            name="file_delete",
            description="删除文件或目录",
            input_schema={
                "type": "object",
                "properties": {
                    "path": {"type": "string", "description": "文件路径"},
                    "recursive": {"type": "boolean", "default": False, "description": "递归删除目录"}
                },
                "required": ["path"]
            },
            capabilities={ToolCapability.FS_DELETE},
            tags=["file", "delete", "remove"]
        ))
    
    async def execute(self, **kwargs) -> ToolResult:
        path = kwargs.get("path")
        recursive = kwargs.get("recursive", False)
        
        try:
            p = Path(path)
            
            if not p.exists():
                return ToolResult(success=False, error=f"路径不存在: {path}")
            
            if p.is_file():
                p.unlink()
            elif p.is_dir():
                if recursive:
                    shutil.rmtree(p)
                else:
                    p.rmdir()
            
            return ToolResult(
                success=True,
                data={"path": path, "deleted": True}
            )
        except Exception as e:
            return ToolResult(success=False, error=str(e))


class FileGlobTool(BaseTool):
    """文件搜索工具 - Glob 模式"""
    
    def __init__(self):
        super().__init__(ToolDefinition(
            name="file_glob",
            description="使用 glob 模式搜索文件",
            input_schema={
                "type": "object",
                "properties": {
                    "pattern": {"type": "string", "description": "Glob 模式"},
                    "root": {"type": "string", "description": "搜索根目录"},
                    "recursive": {"type": "boolean", "default": True, "description": "递归搜索"},
                    "files_only": {"type": "boolean", "default": True, "description": "只返回文件"}
                },
                "required": ["pattern"]
            },
            capabilities={ToolCapability.FS_READ},
            tags=["file", "glob", "search", "find"]
        ))
    
    async def execute(self, **kwargs) -> ToolResult:
        pattern = kwargs.get("pattern")
        root = kwargs.get("root", ".")
        recursive = kwargs.get("recursive", True)
        files_only = kwargs.get("files_only", True)
        
        try:
            root_path = Path(root)
            if not root_path.exists():
                return ToolResult(success=False, error=f"目录不存在: {root}")
            
            # 构建 glob 模式
            if recursive:
                glob_pattern = "**/" + pattern.lstrip("/")
            else:
                glob_pattern = pattern.lstrip("/")
            
            matches = list(root_path.glob(glob_pattern))
            
            if files_only:
                matches = [m for m in matches if m.is_file()]
            
            results = [str(m.absolute()) for m in matches[:100]]  # 限制100个
            
            return ToolResult(
                success=True,
                data={
                    "matches": results,
                    "count": len(results),
                    "pattern": pattern
                }
            )
        except Exception as e:
            return ToolResult(success=False, error=str(e))


class FileCopyTool(BaseTool):
    """文件复制工具"""
    
    def __init__(self):
        super().__init__(ToolDefinition(
            name="file_copy",
            description="复制文件或目录",
            input_schema={
                "type": "object",
                "properties": {
                    "src": {"type": "string", "description": "源路径"},
                    "dst": {"type": "string", "description": "目标路径"},
                    "overwrite": {"type": "boolean", "default": False}
                },
                "required": ["src", "dst"]
            },
            capabilities={ToolCapability.FS_WRITE},
            tags=["file", "copy", "cp"]
        ))
    
    async def execute(self, **kwargs) -> ToolResult:
        src = kwargs.get("src")
        dst = kwargs.get("dst")
        overwrite = kwargs.get("overwrite", False)
        
        try:
            src_path = Path(src)
            dst_path = Path(dst)
            
            if not src_path.exists():
                return ToolResult(success=False, error=f"源路径不存在: {src}")
            
            if dst_path.exists() and not overwrite:
                return ToolResult(success=False, error=f"目标已存在: {dst}")
            
            if src_path.is_file():
                shutil.copy2(src, dst)
            else:
                if dst_path.exists() and overwrite:
                    shutil.rmtree(dst_path)
                shutil.copytree(src, dst)
            
            return ToolResult(
                success=True,
                data={"src": src, "dst": dst, "copied": True}
            )
        except Exception as e:
            return ToolResult(success=False, error=str(e))


class FileMoveTool(BaseTool):
    """文件移动工具"""
    
    def __init__(self):
        super().__init__(ToolDefinition(
            name="file_move",
            description="移动文件或目录",
            input_schema={
                "type": "object",
                "properties": {
                    "src": {"type": "string", "description": "源路径"},
                    "dst": {"type": "string", "description": "目标路径"},
                    "overwrite": {"type": "boolean", "default": False}
                },
                "required": ["src", "dst"]
            },
            capabilities={ToolCapability.FS_WRITE, ToolCapability.FS_DELETE},
            tags=["file", "move", "mv", "rename"]
        ))
    
    async def execute(self, **kwargs) -> ToolResult:
        src = kwargs.get("src")
        dst = kwargs.get("dst")
        overwrite = kwargs.get("overwrite", False)
        
        try:
            src_path = Path(src)
            dst_path = Path(dst)
            
            if not src_path.exists():
                return ToolResult(success=False, error=f"源路径不存在: {src}")
            
            if dst_path.exists() and not overwrite:
                return ToolResult(success=False, error=f"目标已存在: {dst}")
            
            if dst_path.exists():
                dst_path.unlink()
            
            shutil.move(src, dst)
            
            return ToolResult(
                success=True,
                data={"src": src, "dst": dst, "moved": True}
            )
        except Exception as e:
            return ToolResult(success=False, error=str(e))


class FileInfoTool(BaseTool):
    """文件信息工具"""
    
    def __init__(self):
        super().__init__(ToolDefinition(
            name="file_info",
            description="获取文件或目录的详细信息",
            input_schema={
                "type": "object",
                "properties": {
                    "path": {"type": "string", "description": "文件路径"},
                    "stat": {"type": "boolean", "default": True, "description": "获取文件状态"}
                },
                "required": ["path"]
            },
            capabilities={ToolCapability.FS_READ},
            tags=["file", "info", "stat", "metadata"]
        ))
    
    async def execute(self, **kwargs) -> ToolResult:
        path = kwargs.get("path")
        get_stat = kwargs.get("stat", True)
        
        try:
            p = Path(path)
            
            if not p.exists():
                return ToolResult(success=False, error=f"路径不存在: {path}")
            
            info = {
                "path": str(p.absolute()),
                "name": p.name,
                "is_file": p.is_file(),
                "is_dir": p.is_dir(),
                "is_symlink": p.is_symlink(),
                "suffix": p.suffix,
                "stem": p.stem,
            }
            
            if get_stat:
                stat = p.stat()
                info.update({
                    "size": stat.st_size,
                    "size_human": self._human_size(stat.st_size),
                    "created": stat.st_ctime,
                    "modified": stat.st_mtime,
                    "accessed": stat.st_atime,
                    "mode": oct(stat.st_mode),
                })
            
            return ToolResult(success=True, data=info)
        except Exception as e:
            return ToolResult(success=False, error=str(e))
    
    @staticmethod
    def _human_size(size: int) -> str:
        for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
            if size < 1024:
                return f"{size:.1f}{unit}"
            size /= 1024
        return f"{size:.1f}PB"


class FileListTool(BaseTool):
    """目录列表工具"""
    
    def __init__(self):
        super().__init__(ToolDefinition(
            name="file_list",
            description="列出目录内容",
            input_schema={
                "type": "object",
                "properties": {
                    "path": {"type": "string", "description": "目录路径"},
                    "all": {"type": "boolean", "default": False, "description": "显示隐藏文件"},
                    "recursive": {"type": "boolean", "default": False, "description": "递归列出"}
                },
                "required": ["path"]
            },
            capabilities={ToolCapability.FS_READ},
            tags=["file", "list", "ls", "directory"]
        ))
    
    async def execute(self, **kwargs) -> ToolResult:
        path = kwargs.get("path")
        show_all = kwargs.get("all", False)
        recursive = kwargs.get("recursive", False)
        
        try:
            p = Path(path)
            
            if not p.exists():
                return ToolResult(success=False, error=f"目录不存在: {path}")
            
            if not p.is_dir():
                return ToolResult(success=False, error=f"不是目录: {path}")
            
            results = []
            
            if recursive:
                for item in p.rglob("*"):
                    if not show_all and item.name.startswith("."):
                        continue
                    results.append({
                        "path": str(item.relative_to(p)),
                        "type": "dir" if item.is_dir() else "file",
                        "name": item.name
                    })
            else:
                for item in p.iterdir():
                    if not show_all and item.name.startswith("."):
                        continue
                    results.append({
                        "name": item.name,
                        "type": "dir" if item.is_dir() else "file"
                    })
            
            return ToolResult(
                success=True,
                data={
                    "entries": results[:100],  # 限制100个
                    "count": len(results),
                    "path": str(p.absolute())
                }
            )
        except Exception as e:
            return ToolResult(success=False, error=str(e))


# 导出所有工具
FILE_TOOLS = [
    FileReadTool,
    FileWriteTool,
    FileEditTool,
    FileDeleteTool,
    FileGlobTool,
    FileCopyTool,
    FileMoveTool,
    FileInfoTool,
    FileListTool,
]


def register_tools(registry):
    """注册所有文件工具到注册表"""
    for tool_class in FILE_TOOLS:
        tool = tool_class()
        registry.register(tool, "file")
    return len(FILE_TOOLS)

path: tools/git_tools.py
content: #!/usr/bin/env python3
"""
Git 操作工具集

提供完整的 Git 操作能力
参考 Claude Code 的 Git 工具设计
"""

from __future__ import annotations

import os
import asyncio
import subprocess
from pathlib import Path
from typing import Any, Dict, List, Optional
from dataclasses import dataclass, field
from datetime import datetime

import sys
sys.path.insert(0, str(Path(__file__).parent))
from schema import BaseTool, ToolDefinition, ToolResult, ToolCapability


class GitStatusTool(BaseTool):
    """Git 状态工具"""
    
    def __init__(self):
        super().__init__(ToolDefinition(
            name="git_status",
            description="查看 Git 仓库状态",
            input_schema={
                "type": "object",
                "properties": {
                    "path": {"type": "string", "description": "仓库路径", "default": "."},
                    "short": {"type": "boolean", "default": False, "description": "简短格式"}
                }
            },
            capabilities={ToolCapability.EXECUTE},
            tags=["git", "status", "vcs"]
        ))
    
    async def execute(self, **kwargs) -> ToolResult:
        path = kwargs.get("path", ".")
        short = kwargs.get("short", False)
        
        try:
            repo_path = Path(path)
            if not (repo_path / ".git").exists():
                return ToolResult(success=False, error=f"不是 Git 仓库: {path}")
            
            cmd = ["git", "-C", str(repo_path), "status"]
            if short:
                cmd.append("--short")
            
            proc = await asyncio.create_subprocess_exec(
                *cmd,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE
            )
            stdout, stderr = await proc.communicate()
            
            if proc.returncode != 0:
                return ToolResult(success=False, error=stderr.decode())
            
            return ToolResult(
                success=True,
                data={
                    "status": stdout.decode(),
                    "path": str(repo_path.absolute())
                }
            )
            
        except FileNotFoundError:
            return ToolResult(success=False, error="Git 未安装")
        except Exception as e:
            return ToolResult(success=False, error=str(e))


class GitLogTool(BaseTool):
    """Git 日志工具"""
    
    def __init__(self):
        super().__init__(ToolDefinition(
            name="git_log",
            description="查看 Git 提交日志",
            input_schema={
                "type": "object",
                "properties": {
                    "path": {"type": "string", "description": "仓库路径", "default": "."},
                    "limit": {"type": "integer", "default": 10, "description": "提交数量"},
                    "oneline": {"type": "boolean", "default": False, "description": "单行格式"},
                    "author": {"type": "string", "description": "按作者过滤"},
                    "since": {"type": "string", "description": "起始日期"},
                    "until": {"type": "string", "description": "结束日期"}
                }
            },
            capabilities={ToolCapability.EXECUTE},
            tags=["git", "log", "history", "vcs"]
        ))
    
    async def execute(self, **kwargs) -> ToolResult:
        path = kwargs.get("path", ".")
        limit = kwargs.get("limit", 10)
        oneline = kwargs.get("oneline", False)
        author = kwargs.get("author")
        
        try:
            repo_path = Path(path)
            if not (repo_path / ".git").exists():
                return ToolResult(success=False, error=f"不是 Git 仓库: {path}")
            
            cmd = ["git", "-C", str(repo_path), "log", f"-n{limit}", "--format=%H|%an|%ae|%ad|%s"]
            if oneline:
                cmd.append("--oneline")
            if author:
                cmd.extend(["--author", author])
            
            proc = await asyncio.create_subprocess_exec(
                *cmd,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE
            )
            stdout, stderr = await proc.communicate()
            
            if proc.returncode != 0:
                return ToolResult(success=False, error=stderr.decode())
            
            commits = []
            for line in stdout.decode().strip().split("\n"):
                if not line:
                    continue
                if "|" in line:
                    parts = line.split("|")
                    if len(parts) == 5:
                        commits.append({
                            "hash": parts[0][:8],
                            "author": parts[1],
                            "email": parts[2],
                            "date": parts[3],
                            "message": parts[4]
                        })
            
            return ToolResult(
                success=True,
                data={
                    "commits": commits,
                    "count": len(commits)
                }
            )
            
        except Exception as e:
            return ToolResult(success=False, error=str(e))


class GitDiffTool(BaseTool):
    """Git 差异工具"""
    
    def __init__(self):
        super().__init__(ToolDefinition(
            name="git_diff",
            description="查看 Git 差异",
            input_schema={
                "type": "object",
                "properties": {
                    "path": {"type": "string", "description": "仓库路径", "default": "."},
                    "commit": {"type": "string", "description": "比较的提交"},
                    "cached": {"type": "boolean", "default": False, "description": "暂存区差异"},
                    "stat": {"type": "boolean", "default": False, "description": "统计信息"}
                }
            },
            capabilities={ToolCapability.EXECUTE},
            tags=["git", "diff", "vcs"]
        ))
    
    async def execute(self, **kwargs) -> ToolResult:
        path = kwargs.get("path", ".")
        commit = kwargs.get("commit")
        cached = kwargs.get("cached", False)
        stat = kwargs.get("stat", False)
        
        try:
            repo_path = Path(path)
            
            cmd = ["git", "-C", str(repo_path), "diff"]
            if cached:
                cmd.append("--cached")
            if stat:
                cmd.append("--stat")
            if commit:
                cmd.append(commit)
            
            proc = await asyncio.create_subprocess_exec(
                *cmd,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE
            )
            stdout, stderr = await proc.communicate()
            
            return ToolResult(
                success=proc.returncode == 0,
                data={
                    "diff": stdout.decode(),
                    "path": str(repo_path.absolute())
                },
                metadata={"returncode": proc.returncode}
            )
            
        except Exception as e:
            return ToolResult(success=False, error=str(e))


class GitCommitTool(BaseTool):
    """Git 提交工具"""
    
    def __init__(self):
        super().__init__(ToolDefinition(
            name="git_commit",
            description="提交更改到 Git 仓库",
            input_schema={
                "type": "object",
                "properties": {
                    "path": {"type": "string", "description": "仓库路径", "default": "."},
                    "message": {"type": "string", "description": "提交信息"},
                    "all": {"type": "boolean", "default": True, "description": "提交所有修改"},
                    "amend": {"type": "boolean", "default": False, "description": "修改上次提交"},
                    "author": {"type": "string", "description": "作者信息"}
                },
                "required": ["message"]
            },
            capabilities={ToolCapability.EXECUTE},
            tags=["git", "commit", "vcs"]
        ))
    
    async def execute(self, **kwargs) -> ToolResult:
        path = kwargs.get("path", ".")
        message = kwargs.get("message")
        all_changes = kwargs.get("all", True)
        amend = kwargs.get("amend", False)
        author = kwargs.get("author")
        
        try:
            repo_path = Path(path)
            
            # 先 add 所有文件
            if all_changes:
                add_proc = await asyncio.create_subprocess_exec(
                    "git", "-C", str(repo_path), "add", "-A",
                    stdout=asyncio.subprocess.PIPE,
                    stderr=asyncio.subprocess.PIPE
                )
                await add_proc.communicate()
            
            # 构建 commit 命令
            cmd = ["git", "-C", str(repo_path), "commit", "-m", message]
            if amend:
                cmd.append("--amend")
            if author:
                cmd.extend(["--author", author])
            
            proc = await asyncio.create_subprocess_exec(
                *cmd,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE
            )
            stdout, stderr = await proc.communicate()
            
            if proc.returncode != 0:
                return ToolResult(success=False, error=stderr.decode())
            
            # 获取 commit hash
            hash_proc = await asyncio.create_subprocess_exec(
                "git", "-C", str(repo_path), "rev-parse", "HEAD",
                stdout=asyncio.subprocess.PIPE
            )
            hash_stdout, _ = await hash_proc.communicate()
            commit_hash = hash_stdout.decode().strip()[:8]
            
            return ToolResult(
                success=True,
                data={
                    "commit": commit_hash,
                    "message": message,
                    "path": str(repo_path.absolute())
                }
            )
            
        except Exception as e:
            return ToolResult(success=False, error=str(e))


class GitPushTool(BaseTool):
    """Git 推送工具"""
    
    def __init__(self):
        super().__init__(ToolDefinition(
            name="git_push",
            description="推送提交到远程仓库",
            input_schema={
                "type": "object",
                "properties": {
                    "path": {"type": "string", "description": "仓库路径", "default": "."},
                    "remote": {"type": "string", "default": "origin", "description": "远程名称"},
                    "branch": {"type": "string", "description": "分支名称"},
                    "force": {"type": "boolean", "default": False, "description": "强制推送"},
                    "set_upstream": {"type": "boolean", "default": False, "description": "设置上游"}
                }
            },
            capabilities={ToolCapability.EXECUTE},
            tags=["git", "push", "remote", "vcs"]
        ))
    
    async def execute(self, **kwargs) -> ToolResult:
        path = kwargs.get("path", ".")
        remote = kwargs.get("remote", "origin")
        branch = kwargs.get("branch")
        force = kwargs.get("force", False)
        set_upstream = kwargs.get("set_upstream", False)
        
        try:
            repo_path = Path(path)
            
            cmd = ["git", "-C", str(repo_path), "push"]
            if force:
                cmd.append("--force")
            if set_upstream:
                cmd.append("-u")
            if remote:
                cmd.append(remote)
            if branch:
                cmd.append(branch)
            
            proc = await asyncio.create_subprocess_exec(
                *cmd,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE
            )
            stdout, stderr = await proc.communicate()
            
            if proc.returncode != 0:
                return ToolResult(success=False, error=stderr.decode())
            
            return ToolResult(
                success=True,
                data={
                    "remote": remote,
                    "branch": branch or "current",
                    "pushed": True,
                    "output": stdout.decode()
                }
            )
            
        except Exception as e:
            return ToolResult(success=False, error=str(e))


class GitPullTool(BaseTool):
    """Git 拉取工具"""
    
    def __init__(self):
        super().__init__(ToolDefinition(
            name="git_pull",
            description="从远程仓库拉取更新",
            input_schema={
                "type": "object",
                "properties": {
                    "path": {"type": "string", "description": "仓库路径", "default": "."},
                    "remote": {"type": "string", "default": "origin", "description": "远程名称"},
                    "branch": {"type": "string", "description": "分支名称"},
                    "rebase": {"type": "boolean", "default": False, "description": "使用 rebase"}
                }
            },
            capabilities={ToolCapability.EXECUTE},
            tags=["git", "pull", "remote", "vcs"]
        ))
    
    async def execute(self, **kwargs) -> ToolResult:
        path = kwargs.get("path", ".")
        remote = kwargs.get("remote", "origin")
        branch = kwargs.get("branch")
        rebase = kwargs.get("rebase", False)
        
        try:
            repo_path = Path(path)
            
            cmd = ["git", "-C", str(repo_path), "pull"]
            if rebase:
                cmd.append("--rebase")
            if remote:
                cmd.append(remote)
            if branch:
                cmd.append(branch)
            
            proc = await asyncio.create_subprocess_exec(
                *cmd,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE
            )
            stdout, stderr = await proc.communicate()
            
            if proc.returncode != 0:
                return ToolResult(success=False, error=stderr.decode())
            
            return ToolResult(
                success=True,
                data={
                    "remote": remote,
                    "branch": branch or "current",
                    "pulled": True,
                    "output": stdout.decode()
                }
            )
            
        except Exception as e:
            return ToolResult(success=False, error=str(e))


class GitBranchTool(BaseTool):
    """Git 分支工具"""
    
    def __init__(self):
        super().__init__(ToolDefinition(
            name="git_branch",
            description="管理 Git 分支",
            input_schema={
                "type": "object",
                "properties": {
                    "path": {"type": "string", "description": "仓库路径", "default": "."},
                    "action": {"type": "string", "enum": ["list", "create", "delete", "checkout"], "description": "操作"},
                    "name": {"type": "string", "description": "分支名称"},
                    "force": {"type": "boolean", "default": False, "description": "强制操作"}
                }
            },
            capabilities={ToolCapability.EXECUTE},
            tags=["git", "branch", "vcs"]
        ))
    
    async def execute(self, **kwargs) -> ToolResult:
        path = kwargs.get("path", ".")
        action = kwargs.get("action", "list")
        name = kwargs.get("name")
        force = kwargs.get("force", False)
        
        try:
            repo_path = Path(path)
            
            cmd = ["git", "-C", str(repo_path), "branch"]
            
            if action == "list":
                cmd.append("-a")
            elif action == "create" and name:
                cmd.extend(["-c", name])
            elif action == "delete" and name:
                if force:
                    cmd.extend(["-D", name])
                else:
                    cmd.extend(["-d", name])
            elif action == "checkout" and name:
                cmd = ["git", "-C", str(repo_path), "checkout", name]
            
            proc = await asyncio.create_subprocess_exec(
                *cmd,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE
            )
            stdout, stderr = await proc.communicate()
            
            if proc.returncode != 0:
                return ToolResult(success=False, error=stderr.decode())
            
            branches = [b.strip().replace("* ", "") for b in stdout.decode().strip().split("\n") if b.strip()]
            
            return ToolResult(
                success=True,
                data={
                    "action": action,
                    "branches": branches,
                    "count": len(branches)
                }
            )
            
        except Exception as e:
            return ToolResult(success=False, error=str(e))


class GitRemoteTool(BaseTool):
    """Git 远程仓库工具"""
    
    def __init__(self):
        super().__init__(ToolDefinition(
            name="git_remote",
            description="管理 Git 远程仓库",
            input_schema={
                "type": "object",
                "properties": {
                    "path": {"type": "string", "description": "仓库路径", "default": "."},
                    "action": {"type": "string", "enum": ["list", "add", "remove"], "description": "操作"},
                    "name": {"type": "string", "description": "远程名称"},
                    "url": {"type": "string", "description": "远程 URL"}
                }
            },
            capabilities={ToolCapability.EXECUTE},
            tags=["git", "remote", "vcs"]
        ))
    
    async def execute(self, **kwargs) -> ToolResult:
        path = kwargs.get("path", ".")
        action = kwargs.get("action", "list")
        name = kwargs.get("name")
        url = kwargs.get("url")
        
        try:
            repo_path = Path(path)
            
            if action == "list":
                cmd = ["git", "-C", str(repo_path), "remote", "-v"]
            elif action == "add" and name and url:
                cmd = ["git", "-C", str(repo_path), "remote", "add", name, url]
            elif action == "remove" and name:
                cmd = ["git", "-C", str(repo_path), "remote", "remove", name]
            else:
                return ToolResult(success=False, error="无效参数")
            
            proc = await asyncio.create_subprocess_exec(
                *cmd,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE
            )
            stdout, stderr = await proc.communicate()
            
            if proc.returncode != 0:
                return ToolResult(success=False, error=stderr.decode())
            
            remotes = []
            for line in stdout.decode().strip().split("\n"):
                if line:
                    parts = line.split()
                    if len(parts) >= 2:
                        remotes.append({"name": parts[0], "url": parts[1], "type": parts[2] if len(parts) > 2 else ""})
            
            return ToolResult(
                success=True,
                data={
                    "action": action,
                    "remotes": remotes
                }
            )
            
        except Exception as e:
            return ToolResult(success=False, error=str(e))


# 导出所有工具
GIT_TOOLS = [
    GitStatusTool,
    GitLogTool,
    GitDiffTool,
    GitCommitTool,
    GitPushTool,
    GitPullTool,
    GitBranchTool,
    GitRemoteTool,
]


def register_tools(registry):
    """注册所有 Git 工具到注册表"""
    for tool_class in GIT_TOOLS:
        tool = tool_class()
        registry.register(tool, "vcs")
    return len(GIT_TOOLS)

path: tools/mcp_tools.py
content: #!/usr/bin/env python3
"""
MCP 集成工具集

提供 MCP (Model Context Protocol) 客户端能力
参考 Claude Code 的 MCP 工具设计
"""

from __future__ import annotations

import asyncio
import json
import subprocess
from pathlib import Path
from typing import Any, Dict, List, Optional
from dataclasses import dataclass, field

import sys
sys.path.insert(0, str(Path(__file__).parent))
from schema import BaseTool, ToolDefinition, ToolResult, ToolCapability


class McpListTool(BaseTool):
    """MCP 服务器列表工具"""
    
    def __init__(self):
        super().__init__(ToolDefinition(
            name="mcp_list",
            description="列出已配置的 MCP 服务器",
            input_schema={
                "type": "object",
                "properties": {
                    "config_path": {"type": "string", "description": "MCP 配置文件路径"}
                }
            },
            capabilities={ToolCapability.EXECUTE},
            tags=["mcp", "server", "list", "config"]
        ))
    
    async def execute(self, **kwargs) -> ToolResult:
        config_path = kwargs.get("config_path")
        
        # 默认 MCP 配置路径
        if not config_path:
            possible_paths = [
                Path.home() / ".openclaw" / "mcp.json",
                Path.home() / ".mcp.json",
                Path.cwd() / ".mcp.json",
            ]
            for p in possible_paths:
                if p.exists():
                    config_path = str(p)
                    break
        
        if not config_path:
            return ToolResult(success=True, data={"servers": [], "message": "未找到 MCP 配置文件"})
        
        try:
            with open(config_path, "r") as f:
                config = json.load(f)
            
            servers = []
            mcp_config = config.get("mcpServers", config.get("mcp", {}))
            
            for name, server_config in mcp_config.items():
                servers.append({
                    "name": name,
                    "command": server_config.get("command", ""),
                    "args": server_config.get("args", []),
                    "env": server_config.get("env", {}),
                    "enabled": True
                })
            
            return ToolResult(
                success=True,
                data={
                    "servers": servers,
                    "count": len(servers),
                    "config_path": config_path
                }
            )
            
        except FileNotFoundError:
            return ToolResult(success=False, error=f"配置文件不存在: {config_path}")
        except json.JSONDecodeError as e:
            return ToolResult(success=False, error=f"JSON 解析失败: {e}")
        except Exception as e:
            return ToolResult(success=False, error=str(e))


class McpDiscoverTool(BaseTool):
    """MCP 工具发现工具"""
    
    def __init__(self):
        super().__init__(ToolDefinition(
            name="mcp_discover",
            description="从 MCP 服务器发现可用工具",
            input_schema={
                "type": "object",
                "properties": {
                    "server_name": {"type": "string", "description": "MCP 服务器名称"},
                    "config_path": {"type": "string", "description": "MCP 配置文件路径"}
                },
                "required": ["server_name"]
            },
            capabilities={ToolCapability.EXECUTE},
            tags=["mcp", "tool", "discover", "server"]
        ))
    
    async def execute(self, **kwargs) -> ToolResult:
        server_name = kwargs.get("server_name")
        config_path = kwargs.get("config_path")
        
        # 读取配置
        if not config_path:
            possible_paths = [
                Path.home() / ".openclaw" / "mcp.json",
                Path.home() / ".mcp.json",
            ]
            for p in possible_paths:
                if p.exists():
                    config_path = str(p)
                    break
        
        if not config_path:
            return ToolResult(success=False, error="未找到 MCP 配置文件")
        
        try:
            with open(config_path, "r") as f:
                config = json.load(f)
            
            mcp_config = config.get("mcpServers", config.get("mcp", {}))
            
            if server_name not in mcp_config:
                return ToolResult(success=False, error=f"服务器不存在: {server_name}")
            
            server = mcp_config[server_name]
            command = server.get("command", "")
            args = server.get("args", [])
            
            # 调用 MCP 服务器的 tools/list 方法
            # 注意：这里简化了，实际需要通过 stdio 通信
            # 实际实现需要 MCP SDK
            return ToolResult(
                success=True,
                data={
                    "server": server_name,
                    "command": command,
                    "args": args,
                    "message": "MCP 工具发现需要 MCP SDK 实现"
                }
            )
            
        except Exception as e:
            return ToolResult(success=False, error=str(e))


class McpCallTool(BaseTool):
    """MCP 工具调用工具"""
    
    def __init__(self):
        super().__init__(ToolDefinition(
            name="mcp_call",
            description="调用 MCP 服务器的工具",
            input_schema={
                "type": "object",
                "properties": {
                    "server_name": {"type": "string", "description": "MCP 服务器名称"},
                    "tool_name": {"type": "string", "description": "要调用的工具名"},
                    "arguments": {"type": "object", "description": "工具参数"},
                    "config_path": {"type": "string", "description": "MCP 配置文件路径"}
                },
                "required": ["server_name", "tool_name"]
            },
            capabilities={ToolCapability.EXECUTE},
            tags=["mcp", "call", "tool", "execute"]
        ))
    
    async def execute(self, **kwargs) -> ToolResult:
        server_name = kwargs.get("server_name")
        tool_name = kwargs.get("tool_name")
        arguments = kwargs.get("arguments", {})
        config_path = kwargs.get("config_path")
        
        # 简化实现：返回提示信息
        # 实际需要通过 MCP stdio 协议通信
        return ToolResult(
            success=True,
            data={
                "server": server_name,
                "tool": tool_name,
                "arguments": arguments,
                "message": "MCP 工具调用需要 MCP SDK 实现 (通过 stdio 协议)"
            }
        )


class McpResourceTool(BaseTool):
    """MCP 资源访问工具"""
    
    def __init__(self):
        super().__init__(ToolDefinition(
            name="mcp_resource",
            description="访问 MCP 服务器提供的资源",
            input_schema={
                "type": "object",
                "properties": {
                    "server_name": {"type": "string", "description": "MCP 服务器名称"},
                    "resource_uri": {"type": "string", "description": "资源 URI"},
                    "config_path": {"type": "string", "description": "MCP 配置文件路径"}
                },
                "required": ["server_name", "resource_uri"]
            },
            capabilities={ToolCapability.NETWORK},
            tags=["mcp", "resource", "read"]
        ))
    
    async def execute(self, **kwargs) -> ToolResult:
        server_name = kwargs.get("server_name")
        resource_uri = kwargs.get("resource_uri")
        config_path = kwargs.get("config_path")
        
        # 简化实现
        return ToolResult(
            success=True,
            data={
                "server": server_name,
                "uri": resource_uri,
                "message": "MCP 资源访问需要 MCP SDK 实现"
            }
        )


class McpPromptTool(BaseTool):
    """MCP 提示词工具"""
    
    def __init__(self):
        super().__init__(ToolDefinition(
            name="mcp_prompt",
            description="列出/获取 MCP 服务器提供的提示词",
            input_schema={
                "type": "object",
                "properties": {
                    "server_name": {"type": "string", "description": "MCP 服务器名称"},
                    "prompt_name": {"type": "string", "description": "提示词名称"},
                    "config_path": {"type": "string", "description": "MCP 配置文件路径"}
                },
                "required": ["server_name"]
            },
            capabilities={ToolCapability.EXECUTE},
            tags=["mcp", "prompt", "template"]
        ))
    
    async def execute(self, **kwargs) -> ToolResult:
        server_name = kwargs.get("server_name")
        prompt_name = kwargs.get("prompt_name")
        config_path = kwargs.get("config_path")
        
        return ToolResult(
            success=True,
            data={
                "server": server_name,
                "prompt": prompt_name,
                "message": "MCP 提示词需要 MCP SDK 实现"
            }
        )


class McpStatusTool(BaseTool):
    """MCP 服务器状态工具"""
    
    def __init__(self):
        super().__init__(ToolDefinition(
            name="mcp_status",
            description="检查 MCP 服务器状态",
            input_schema={
                "type": "object",
                "properties": {
                    "server_name": {"type": "string", "description": "MCP 服务器名称"},
                    "config_path": {"type": "string", "description": "MCP 配置文件路径"}
                },
                "required": ["server_name"]
            },
            capabilities={ToolCapability.EXECUTE},
            tags=["mcp", "status", "health"]
        ))
    
    async def execute(self, **kwargs) -> ToolResult:
        server_name = kwargs.get("server_name")
        config_path = kwargs.get("config_path")
        
        # 简化实现
        return ToolResult(
            success=True,
            data={
                "server": server_name,
                "status": "unknown",
                "message": "需要 MCP SDK 实现状态检查"
            }
        )


# 导出所有工具
MCP_TOOLS = [
    McpListTool,
    McpDiscoverTool,
    McpCallTool,
    McpResourceTool,
    McpPromptTool,
    McpStatusTool,
]


def register_tools(registry):
    """注册所有 MCP 工具到注册表"""
    for tool_class in MCP_TOOLS:
        tool = tool_class()
        registry.register(tool, "mcp")
    return len(MCP_TOOLS)

path: tools/run_nlp.py
content: #!/usr/bin/env python3
"""
自然语言工具调用器

将自然语言转换为工具调用
用法: python3 run_nlp.py "帮我读取 /etc/hostname 文件"
"""

import sys
import json
import asyncio
import re
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent))
from __init__ import load_all_tools, get_registry
from schema import ToolRegistry


# 意图映射规则
INTENT_PATTERNS = [
    # ===== Agent Teams =====
    (r"创建\s*团队\s*(.+)", "team_create", "team_name"),
    (r"团队\s*列表", "team_list", None),
    (r"添加\s*(.+?)\s*到\s*团队", "team_add_agent", "team_agent"),
    (r"团队\s*执行\s*(.+)", "team_run_task", "team_task"),
    (r"让\s*(.+?)\s*团队\s*(.+)", "team_run_task", "team_task2"),
    (r"(?:创建|添加)\s*(.+?)\s*Agent", "team_add_agent", "add_agent"),
    
    # ===== Shell 执行 =====
    (r"^(ls|cd|pwd|cat|rm|mkdir|touch|echo)\s*(.*)$", "shell_exec", "shell_command"),
    (r"执行\s+(.+)$", "shell_exec", "shell_command"),
    (r"运行\s+(.+)$", "shell_exec", "shell_command"),
    
    # 文件操作 - 绝对路径
    (r"读取\s+(/\S+)", "file_read", "path"),
    (r"查看\s+(/\S+)", "file_read", "path"),
    (r"cat\s+(/\S+)", "file_read", "path"),
    (r"删除\s+(/\S+)", "file_delete", "path"),
    (r"搜索\s+(\S+)\s*文件", "file_glob", "pattern"),
    (r"列出\s+(\S+)\s*目录", "file_list", "path"),
    
    # Git 操作
    (r"git\s+status", "git_status", "path=."),
    (r"查看\s*Git\s*状态", "git_status", "path=."),
    (r"git\s+log", "git_log", "path=."),
    (r"git\s+diff", "git_diff", "path=."),
    (r"git\s+commit\s+([^-]+)", "git_commit", "message"),
    (r"提交\s+(.+)", "git_commit", "message"),
    (r"git\s+push", "git_push", "path=."),
    (r"推送", "git_push", "path=."),
    (r"git\s+pull", "git_pull", "path=."),
    (r"拉取", "git_pull", "path=."),
    (r"git\s+branch", "git_branch", "path=."),
    
    # 系统信息
    (r"系统信息", "system_info", None),
    (r"我的\s*IP", "shell_exec", "command=curl -s ifconfig.me"),
    (r"天气", "http_request", "url=http://wttr.in/Shanghai?format=j1"),
    
    # 网页搜索
    (r"搜索\s+(.+)", "web_search", "query"),
    (r"查一下\s+(.+)", "web_search", "query"),
    (r"找\s+(.+?)(?:的|信息|资料)", "web_search", "query"),
    
    # 记忆系统
    (r"记住(.+)", "memory_remember", "content"),
    (r"记一下(.+)", "memory_remember", "content"),
    (r"记录(.+)", "memory_remember", "content"),
    (r"记得\s*(.+)", "memory_recall", "query"),
    (r"搜索\s*记忆\s*(.+)", "memory_recall", "query"),
    (r"记忆\s*统计", "memory_stats", None),
    (r"整理\s*记忆", "memory_dream", "dry_run=false"),
]


def parse_path_from_intent(text: str) -> str:
    """从文本中提取路径"""
    # 去除引号
    text = text.strip().strip("'\"").strip()
    # 展开 ~
    if text.startswith("~"):
        text = str(Path.home()) + text[1:]
    # 转换为绝对路径
    return str(Path(text).expanduser().resolve())


def extract_command(text: str) -> str:
    """从文本中提取命令"""
    return text.strip().strip("'\"").strip()


async def process_intent(text: str) -> dict:
    """处理自然语言，返回工具调用结果"""
    load_all_tools()
    registry = get_registry()
    
    text = text.strip()
    
    # 尝试匹配意图
    for pattern, tool_name, default_param in INTENT_PATTERNS:
        match = re.search(pattern, text, re.IGNORECASE)
        if match:
            # 构建参数
            params = {}
            
            # 处理默认参数（key=value 格式）
            if default_param and "=" in str(default_param):
                key, value = str(default_param).split("=", 1)
                params[key] = value
            elif default_param is None:
                pass  # 无参数
            elif default_param == "path":
                params["path"] = parse_path_from_intent(match.group(1))
            elif default_param == "command":
                params["command"] = match.group(1).strip()
            elif default_param == "shell_command":
                # 组合命令 (cmd + args)
                cmd = match.group(1).strip()
                args = match.group(2).strip() if match.lastindex and match.lastindex >= 2 else ""
                params["command"] = f"{cmd} {args}".strip()
            elif default_param == "content":
                params["content"] = match.group(1).strip()
                params["importance"] = 3
            elif default_param == "query":
                params["query"] = match.group(1).strip()
                params["limit"] = 5
            elif default_param == "message":
                params["path"] = "."
                params["message"] = match.group(1).strip()
            elif default_param == "pattern":
                params["pattern"] = match.group(1).strip()
            
            # ===== Agent Teams 参数处理 =====
            elif default_param == "team_name":
                # "创建团队 我的团队" → team_id, name
                params["team_id"] = match.group(1).strip().replace(" ", "_").lower()
                params["name"] = match.group(1).strip()
            elif default_param == "team_agent":
                # "添加 coder 到团队" → team_id, agent_id, name
                agent_name = match.group(1).strip()
                params["team_id"] = "myteam"  # 默认
                params["agent_id"] = agent_name.replace(" ", "_").lower()
                params["name"] = agent_name
            elif default_param == "team_task":
                # "团队执行 任务"
                params["team_id"] = "myteam"  # 默认
                params["task"] = match.group(1).strip()
            elif default_param == "team_task2":
                # "让 coder 团队 任务"
                params["team_id"] = match.group(1).strip().replace(" ", "_").lower()
                params["task"] = match.group(2).strip()
            elif default_param == "add_agent":
                # "添加 coder Agent"
                agent_name = match.group(1).strip()
                params["team_id"] = "myteam"
                params["agent_id"] = agent_name.replace(" ", "_").lower()
                params["name"] = agent_name
            
            # 执行工具
            try:
                result = await registry.execute(tool_name, **params)
                
                if result.success:
                    return {
                        "intent": tool_name,
                        "success": True,
                        "data": result.data,
                        "params": params
                    }
                else:
                    return {
                        "intent": tool_name,
                        "success": False,
                        "error": result.error,
                        "params": params
                    }
            except Exception as e:
                return {
                    "intent": tool_name,
                    "success": False,
                    "error": str(e),
                    "params": params
                }
    
    # 没有匹配到意图，返回提示
    return {
        "intent": None,
        "success": False,
        "error": f"无法理解: {text}",
        "suggestion": "可以尝试: '读取 /etc/hostname', 'ls /tmp', '搜索 Python', '记住 重要事项', '系统信息', '天气'"
    }


def format_result(result: dict) -> str:
    """格式化结果为自然语言"""
    if not result["success"]:
        return f"❌ {result.get('error', '未知错误')}"
    
    data = result.get("data", {})
    intent = result.get("intent", "")
    
    # 根据不同工具格式化输出
    if intent == "file_read":
        return f"📄 文件内容:\n{data.get('content', '').strip()}"
    
    elif intent == "system_info":
        return f"💻 系统信息:\n" + "\n".join(f"  {k}: {v}" for k, v in data.items())
    
    elif intent == "shell_exec":
        return data.get("stdout", "").strip() or data.get("stderr", "").strip()
    
    elif intent == "memory_remember":
        return f"✅ 已记住: {result['params'].get('content', '')}"
    
    elif intent == "memory_recall":
        results = data.get("results", [])
        if not results:
            return "没有找到相关记忆"
        return "📝 相关记忆:\n" + "\n".join(
            f"  - {r['content'][:80]}" for r in results[:3]
        )
    
    elif intent == "memory_stats":
        by_type = data.get("by_type", {})
        total = data.get("total", 0)
        return f"📊 记忆统计: 共 {total} 条\n" + "\n".join(
            f"  {k}: {v} 条" for k, v in by_type.items()
        )
    
    elif intent == "web_search":
        results = data.get("results", [])
        if not results:
            return "没有找到相关结果"
        return "🔍 搜索结果:\n" + "\n".join(
            f"  {i+1}. {r['title'][:50]}\n     {r['url'][:60]}"
            for i, r in enumerate(results[:5])
        )
    
    elif intent == "http_request" and "weather" in str(data):
        try:
            current = data.get("body", {}).get("current_condition", [{}])[0]
            temp = current.get("temp_C", "N/A")
            desc = current.get("weatherDesc", "N/A")
            return f"🌤️ 上海天气: {temp}°C, {desc}"
        except:
            pass
    
    # 默认格式化
    if isinstance(data, dict):
        return "\n".join(f"{k}: {v}" for k, v in data.items())
    return str(data)


async def main():
    # 读取输入
    if len(sys.argv) > 1:
        text = " ".join(sys.argv[1:])
    else:
        print("用法: python3 run_nlp.py \"帮我读取 /etc/hostname\"")
        sys.exit(1)
    
    # 处理意图
    result = await process_intent(text)
    
    # 格式化输出
    output = format_result(result)
    print(output)
    
    # 如果是 JSON 模式
    if "--json" in sys.argv:
        print(json.dumps(result, ensure_ascii=False, indent=2))


if __name__ == "__main__":
    asyncio.run(main())

path: tools/run_tool.py
content: #!/usr/bin/env python3
"""
命令行工具调用器

让 Agent 可以通过 bash 命令调用 55 个工具
用法: python3 run_tool.py tool_name param1=value1 param2=value2
"""

import sys
import json
import asyncio
import argparse
from pathlib import Path

# 添加 tools 目录到路径
TOOLS_DIR = Path(__file__).parent
sys.path.insert(0, str(TOOLS_DIR))

from __init__ import load_all_tools, get_registry


def parse_args():
    """解析命令行参数"""
    parser = argparse.ArgumentParser(description="OpenClaw Tool Executor")
    parser.add_argument("tool", help="工具名称")
    parser.add_argument("params", nargs="*", help="参数列表 (key=value 格式)")
    parser.add_argument("--json", action="store_true", help="输出 JSON 格式")
    return parser.parse_args()


def parse_params(params_list):
    """解析参数列表为字典"""
    params = {}
    for p in params_list:
        if "=" in p:
            key, value = p.split("=", 1)
            # 尝试解析为 Python 对象
            try:
                # 尝试 JSON 解析
                params[key] = json.loads(value)
            except json.JSONDecodeError:
                # 如果是纯数字
                if value.isdigit():
                    params[key] = int(value)
                elif value.replace(".", "", 1).isdigit():
                    params[key] = float(value)
                elif value.lower() == "true":
                    params[key] = True
                elif value.lower() == "false":
                    params[key] = False
                else:
                    # 去除引号
                    if (value.startswith('"') and value.endswith('"')) or \
                       (value.startswith("'") and value.endswith("'")):
                        params[key] = value[1:-1]
                    else:
                        params[key] = value
    return params


async def execute_tool(tool_name: str, params: dict):
    """执行工具并返回结果"""
    # 加载工具（只加载一次）
    total = load_all_tools()
    
    # 只在第一次加载时打印摘要
    if total > 0:
        print(f"已加载 {total} 个工具", file=sys.stderr)
    
    registry = get_registry()
    
    # 执行工具
    result = await registry.execute(tool_name, **params)
    
    return result


def main():
    args = parse_args()
    
    # 解析参数
    params = parse_params(args.params)
    
    # 只在调试模式显示加载信息
    import os
    debug = os.environ.get("DEBUG", "")
    
    # 执行工具
    try:
        # 临时抑制加载日志
        if not debug:
            # 重定向 stderr
            import io
            old_stderr = sys.stderr
            sys.stderr = io.StringIO()
        
        result = asyncio.run(execute_tool(args.tool, params))
        
        if not debug:
            sys.stderr = old_stderr
        
        # 输出结果
        if args.json:
            output = {
                "success": result.success,
                "data": result.data,
                "error": result.error
            }
            print(json.dumps(output, ensure_ascii=False, indent=2))
        else:
            if result.success:
                if isinstance(result.data, dict):
                    # 格式化输出
                    for key, value in result.data.items():
                        print(f"{key}: {value}")
                else:
                    print(result.data)
            else:
                print(f"错误: {result.error}", file=sys.stderr)
                sys.exit(1)
                
    except Exception as e:
        if not debug:
            sys.stderr = old_stderr
        print(f"执行失败: {e}", file=sys.stderr)
        sys.exit(1)


if __name__ == "__main__":
    main()

Security scan verdicts — ClawScan: suspicious | static analysis: suspicious | VirusTotal: clean | SkillSpector:  ()