Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:24:37 +08:00
commit 8cd5c7679d
61 changed files with 6788 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
"""
Tool Detectors Module
"""
from .base import ToolDetector
from .internal import InternalToolDetector
from .external import ExternalToolDetector
__all__ = [
'ToolDetector',
'InternalToolDetector',
'ExternalToolDetector'
]

View File

@@ -0,0 +1,51 @@
"""
Base detector interface for toolkit discovery
"""
from abc import ABC, abstractmethod
from pathlib import Path
from typing import List, Any
from ..models import Tool
class ToolDetector(ABC):
"""抽象基类:工具检测器"""
def __init__(self, root_path: Path):
self.root = root_path
self._tools = []
@property
def tools(self) -> List[Tool]:
"""获取检测到的工具列表"""
return self._tools
@abstractmethod
def detect(self) -> List[Tool]:
"""
检测工具
Returns:
List[Tool]: 检测到的工具列表
"""
pass
@abstractmethod
def find_tool(self, name_or_id: str) -> Tool:
"""
查找工具
Args:
name_or_id: 工具名称或ID
Returns:
Tool: 找到的工具
Raises:
ToolDetectorError: 如果工具未找到
"""
pass
def refresh(self):
"""刷新工具列表"""
self._tools = self.detect()

View File

@@ -0,0 +1,129 @@
"""
External Tool Detector - 检测系统已安装的外部CLI工具
"""
import yaml
import shutil
from pathlib import Path
from typing import List, Optional, Dict, Any
from .base import ToolDetector
from ..models import ExternalTool, ToolMetadata
class ExternalToolDetector(ToolDetector):
"""外部工具检测器
从external/目录扫描.meta.yml文件来发现外部工具配置
"""
def __init__(self, root_path: Path):
super().__init__(root_path)
self._external_dir = root_path / "external"
def detect(self) -> List[ExternalTool]:
"""
扫描external/目录检测外部工具
Returns:
List[ExternalTool]: 检测到的外部工具列表
"""
self._tools = []
# 扫描external目录下的所有.meta.yml文件
if self._external_dir.exists():
for meta_file in self._external_dir.rglob("*.meta.yml"):
tool = self._parse_meta_file(meta_file)
if tool:
self._tools.append(tool)
return self._tools
def _parse_meta_file(self, meta_file: Path) -> Optional[ExternalTool]:
"""解析外部工具的meta.yml文件"""
try:
content = yaml.safe_load(meta_file.read_text(encoding='utf-8'))
if not content:
return None
# 获取基本信息
basic_info = content.get("基本信息", {})
tool_type = basic_info.get("类型", "external")
# 只处理external类型的工具
if tool_type != "external":
return None
command = basic_info.get("命令", "")
if not command:
return None
# 检测是否已安装
command_name = command.split()[0]
is_installed = shutil.which(command_name) is not None
tool_path = shutil.which(command_name)
# 创建metadata
metadata = ToolMetadata(
tool_id=content.get("tool_id", "unknown"),
tool_name=content.get("tool_name", "未命名工具"),
description=content.get("功能描述", {}).get("简介", "")
)
# 获取功能描述
func_desc = content.get("功能描述", {})
# 获取快速开始信息
quick_start = content.get("快速开始", {})
return ExternalTool(
metadata=metadata,
command=command,
category=basic_info.get("类别", "unknown"),
use_cases=content.get("使用场景", []),
install_guide=quick_start.get("安装", ""),
installed=is_installed,
path=tool_path
)
except Exception as e:
# 静默失败单个工具的检测
return None
def refresh(self):
"""刷新工具列表"""
# 重新扫描external目录
super().refresh()
def find_tool(self, name_or_id: str) -> Optional[ExternalTool]:
"""
查找外部工具
Args:
name_or_id: 工具名称或ID
Returns:
ExternalTool: 找到的工具如果未找到返回None
"""
# 先尝试精确匹配
for tool in self._tools:
if tool.tool_id == name_or_id or tool.tool_name == name_or_id:
return tool
# 尝试模糊匹配(名称包含)
matches = [t for t in self._tools if name_or_id.lower() in t.tool_name.lower()]
if len(matches) == 1:
return matches[0]
elif len(matches) > 1:
# 不打印,由调用者处理
return None
return None
def get_uninstalled_tools(self) -> List[ExternalTool]:
"""获取未安装的工具列表"""
return [t for t in self._tools if not t.installed]
def get_installed_tools(self) -> List[ExternalTool]:
"""获取已安装的工具列表"""
return [t for t in self._tools if t.installed]

View File

@@ -0,0 +1,137 @@
"""
Internal Tool Detector - 检测AI-runtime内部创建的工具
"""
import sys
import yaml
from pathlib import Path
from typing import List, Dict, Any, Optional
from .base import ToolDetector
from ..models import InternalTool, ToolMetadata
class InternalToolDetector(ToolDetector):
"""内部工具检测器"""
def detect(self) -> List[InternalTool]:
"""
扫描工具包目录检测内部工具
Returns:
List[InternalTool]: 检测到的内部工具列表
"""
self._tools = []
# 加载registry.md暂时跳过详细解析
registry_file = self.root / "registry.md"
if registry_file.exists():
# 这里可以扩展registry解析逻辑
pass
# 扫描所有语言目录
for lang_dir in self.root.iterdir():
if lang_dir.is_dir() and not lang_dir.name.startswith('.') and lang_dir.name != 'discover':
self._scan_language_directory(lang_dir)
return self._tools
def _scan_language_directory(self, lang_dir: Path):
"""扫描语言目录下的工具"""
for meta_file in lang_dir.rglob("*.meta.yml"):
try:
tool = self._parse_meta_file(meta_file)
if tool:
self._tools.append(tool)
except Exception as e:
print(f"⚠️ 解析失败 {meta_file}: {e}", file=sys.stderr)
def _parse_meta_file(self, meta_file: Path) -> Optional[InternalTool]:
"""解析元数据文件"""
try:
content = yaml.safe_load(meta_file.read_text(encoding='utf-8'))
if not content:
return None
# 获取工具文件
tool_file = self._find_tool_file(meta_file)
# 解析基本信息
basic_info = content.get("基本信息", {})
# 创建metadata
metadata = ToolMetadata(
tool_id=content.get("tool_id", "unknown"),
tool_name=content.get("tool_name", "未命名工具"),
description=content.get("功能描述", {}).get("简介", ""),
purpose=content.get("用途分类", [])
)
# 解析上次使用信息
last_use = content.get("上次使用", {})
if last_use:
metadata.satisfaction = last_use.get("满意度", 0.0)
return InternalTool(
metadata=metadata,
meta_file=str(meta_file.relative_to(self.root)),
tool_file=str(tool_file.relative_to(self.root)) if tool_file else None,
language=basic_info.get("语言", "unknown"),
file=basic_info.get("文件", "unknown"),
complexity=basic_info.get("复杂度", "unknown"),
usage=content.get("使用方法", {}),
full_meta=content
)
except Exception as e:
print(f"⚠️ 警告: 解析元数据文件失败 {meta_file}: {e}", file=sys.stderr)
return None
def _find_tool_file(self, meta_file: Path) -> Optional[Path]:
"""查找与meta文件对应的工具文件"""
possible_extensions = ['.sh', '.py', '.js', '.ts', '.java', '.go', '.rs']
for ext in possible_extensions:
possible_file = meta_file.with_suffix(ext)
if possible_file.exists():
return possible_file
# 如果没找到尝试与meta文件同名去掉.meta部分
name_without_meta = meta_file.stem.replace('.meta', '')
for ext in possible_extensions:
possible_file = meta_file.parent / f"{name_without_meta}{ext}"
if possible_file.exists():
return possible_file
return None
def find_tool(self, name_or_id: str) -> Optional[InternalTool]:
"""
查找内部工具
Args:
name_or_id: 工具名称或ID
Returns:
InternalTool: 找到的工具如果未找到返回None
"""
# 先尝试精确匹配
for tool in self._tools:
if tool.tool_id == name_or_id or tool.tool_name == name_or_id:
return tool
# 尝试模糊匹配(名称包含)
matches = [t for t in self._tools if name_or_id.lower() in t.tool_name.lower()]
if len(matches) == 1:
return matches[0]
elif len(matches) > 1:
print(f"⚠️ 找到多个匹配工具:")
for i, tool in enumerate(matches[:5], 1):
print(f" {i}. {tool.tool_name} ({tool.tool_id})")
return None
return None
def refresh(self):
"""刷新工具列表"""
super().refresh()