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,36 @@
"""
Toolkit Discovery Package
A modular toolkit discovery and management system for ai-runtime.
"""
__version__ = "2.0.0"
__author__ = "AI-Runtime Team"
from .discovery import ToolkitDiscovery
from .models import Tool, InternalTool, ExternalTool, ToolMetadata
from .detectors import ToolDetector, InternalToolDetector, ExternalToolDetector
from .formatters import ToolFormatter, TableFormatter, JsonFormatter
from .cli import ToolkitCLI
__all__ = [
# Main classes
'ToolkitDiscovery',
'ToolkitCLI',
# Models
'Tool',
'InternalTool',
'ExternalTool',
'ToolMetadata',
# Detectors
'ToolDetector',
'InternalToolDetector',
'ExternalToolDetector',
# Formatters
'ToolFormatter',
'TableFormatter',
'JsonFormatter',
]

View File

@@ -0,0 +1,8 @@
"""
Entry point for python -m discover
"""
from .cli import main
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,230 @@
"""
CLI Interface for Toolkit Discovery
"""
import sys
import argparse
from pathlib import Path
from .discovery import ToolkitDiscovery
class ToolkitCLI:
"""Toolkit Discovery CLI Interface"""
def __init__(self, toolkit_root: Path):
self.toolkit_root = toolkit_root
self.discovery = ToolkitDiscovery(toolkit_root)
def run(self, args=None):
"""运行CLI"""
parser = self._create_parser()
parsed_args = parser.parse_args(args)
if not parsed_args.command:
parser.print_help()
return 0
try:
return self._execute_command(parsed_args)
except Exception as e:
print(f"❌ 错误: {e}", file=sys.stderr)
return 1
def _create_parser(self) -> argparse.ArgumentParser:
"""创建参数解析器"""
parser = argparse.ArgumentParser(
description="工具包发现和管理工具",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
示例:
python -m discover list # 列出所有工具
python -m discover list --lang python # 列出Python工具
python -m discover list --external # 仅显示外部工具
python -m discover show SERVICE-CHECK-001 # 查看工具详情
python -m discover recommend '分析日志' # 推荐工具
python -m discover search json # 搜索工具
"""
)
subparsers = parser.add_subparsers(dest="command", help="可用命令")
# list 命令
self._add_list_parser(subparsers)
# show 命令
self._add_show_parser(subparsers)
# run 命令
self._add_run_parser(subparsers)
# recommend 命令
self._add_recommend_parser(subparsers)
# search 命令
self._add_search_parser(subparsers)
return parser
def _add_list_parser(self, subparsers):
"""添加list命令"""
list_parser = subparsers.add_parser("list", help="列出所有工具")
list_parser.add_argument("--lang", help="按语言过滤 (bash/python/node)")
list_parser.add_argument("--purpose", help="按用途过滤 (DATA/CODE/TEST/BUILD/MONITOR/DOC)")
list_parser.add_argument("--query", help="按名称或描述搜索")
list_parser.add_argument("--json", action="store_true", help="JSON格式输出")
list_parser.add_argument("--external", action="store_true", help="仅显示外部工具")
list_parser.add_argument("--include-external", action="store_true", help="包含外部工具")
def _add_show_parser(self, subparsers):
"""添加show命令"""
show_parser = subparsers.add_parser("show", help="显示工具详情")
show_parser.add_argument("tool", help="工具ID或名称")
def _add_run_parser(self, subparsers):
"""添加run命令"""
run_parser = subparsers.add_parser("run", help="运行工具")
run_parser.add_argument("tool", help="工具ID或名称")
run_parser.add_argument("args", nargs=argparse.REMAINDER, help="工具参数")
def _add_recommend_parser(self, subparsers):
"""添加recommend命令"""
recommend_parser = subparsers.add_parser("recommend", help="推荐工具")
recommend_parser.add_argument("task", help="任务描述")
def _add_search_parser(self, subparsers):
"""添加search命令"""
search_parser = subparsers.add_parser("search", help="搜索工具")
search_parser.add_argument("keyword", help="搜索关键词")
def _execute_command(self, args) -> int:
"""执行命令"""
if args.command == "list":
return self._cmd_list(args)
elif args.command == "show":
return self._cmd_show(args)
elif args.command == "run":
return self._cmd_run(args)
elif args.command == "recommend":
return self._cmd_recommend(args)
elif args.command == "search":
return self._cmd_search(args)
return 0
def _cmd_list(self, args) -> int:
"""执行list命令"""
# 判断输出格式
format_type = "json" if args.json else "table"
# 获取工具列表
if args.external:
tools = self.discovery.external_tools
elif args.include_external:
tools = self.discovery.all_tools
else:
tools = self.discovery.internal_tools
# 过滤内部工具
if not args.external:
tools = self.discovery.filter_tools(
lang=args.lang,
purpose=args.purpose,
query=args.query
)
# 输出
output = self.discovery.format_tools(tools, format_type=format_type)
print(output)
return 0
def _cmd_show(self, args) -> int:
"""执行show命令"""
tool = self.discovery.find_tool(args.tool)
if not tool:
print(f"❌ 未找到工具: {args.tool}")
return 1
print(self.discovery.format_tool(tool))
return 0
def _cmd_run(self, args) -> int:
"""执行run命令"""
# 注意: 这里简化处理,实际应该直接从文件执行
# 为了保持向后兼容,这里直接调用工具文件
import subprocess
tool = self.discovery.find_tool(args.tool)
if not tool:
print(f"❌ 未找到工具: {args.tool}")
return 1
# 检查是否有tool_file仅内部工具有
if not hasattr(tool, 'tool_file') or not tool.tool_file:
print(f"❌ 外部工具无法直接运行: {args.tool}")
return 1
tool_path = self.toolkit_root / tool.tool_file
if not tool_path.exists():
print(f"❌ 工具文件不存在: {tool_path}")
return 1
print(f"🚀 运行工具: {tool.tool_name}")
print(f"📁 文件: {tool.tool_file}")
print(f"⏳ 正在执行...")
print("=" * 70)
try:
cmd = [str(tool_path)] + args.args
result = subprocess.run(cmd, capture_output=False)
print("=" * 70)
print(f"✅ 执行完成 (退出码: {result.returncode})")
return result.returncode
except Exception as e:
print(f"❌ 执行失败: {e}")
return 1
def _cmd_recommend(self, args) -> int:
"""执行recommend命令"""
tools = self.discovery.recommend_tools(args.task)
if not tools:
print(f"💡 未找到匹配的工具,尝试使用更通用的关键词搜索")
return 0
print(f"\n🔍 为任务 '{args.task}' 推荐工具:")
print("=" * 70)
for i, tool in enumerate(tools[:5], 1):
print(f"\n{i}. {tool.tool_name}")
print(f" ID: {tool.tool_id}")
print(f" 语言: {tool.language}")
print(f" 描述: {tool.description[:60]}...")
print("\n" + "=" * 70)
print("💡 使用: show <tool-id> 查看详情")
return 0
def _cmd_search(self, args) -> int:
"""执行search命令"""
tools = self.discovery.search_tools(args.keyword)
if not tools:
print(f"⚠️ 未找到包含 '{args.keyword}' 的工具")
return 0
print(f"\n🔍 搜索 '{args.keyword}' 找到 {len(tools)} 个结果:")
for tool in tools:
print(f"{tool.tool_name} ({tool.tool_id}) - {tool.description[:50]}...")
print()
return 0
def main():
"""主函数"""
toolkit_root = Path(__file__).parent.parent
cli = ToolkitCLI(toolkit_root)
sys.exit(cli.run())
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,13 @@
"""
Configuration management for toolkit discovery
"""
from pathlib import Path
CONFIG_ROOT = Path(__file__).parent
EXTERNAL_TOOLS_CONFIG = CONFIG_ROOT / "external_tools.yaml"
__all__ = [
'CONFIG_ROOT',
'EXTERNAL_TOOLS_CONFIG'
]

View File

@@ -0,0 +1,113 @@
# External Tool Configurations
# 外部工具配置信息,与代码分离便于维护
tools:
- tool_id: "EXT-FZF-001"
tool_name: "fzf (Fuzzy Finder)"
command: "fzf"
description: "命令行模糊查找器,用于交互式选择"
category: "搜索/交互"
use_cases:
- "文件名查找"
- "历史命令搜索"
- "Git分支切换"
install_guide: "brew install fzf (macOS) / apt-get install fzf (Ubuntu)"
- tool_id: "EXT-EZA-001"
tool_name: "eza (Modern ls)"
command: "eza"
description: "现代化的ls替代品带彩色输出和图标"
category: "文件列表"
use_cases:
- "查看文件列表"
- "树形结构显示"
- "Git状态查看"
install_guide: "brew install eza"
- tool_id: "EXT-ZOXIDE-001"
tool_name: "zoxide (Smart cd)"
command: "zoxide"
description: "智能目录跳转工具,学习访问习惯"
category: "目录导航"
use_cases:
- "快速跳转目录"
- "访问频率学习"
install_guide: "curl -sSfL https://raw.githubusercontent.com/ajeetdsouza/zoxide/main/install.sh | sh"
- tool_id: "EXT-FD-001"
tool_name: "fd (Simple find)"
command: "fd"
description: "简单友好的find替代品"
category: "文件搜索"
use_cases:
- "查找文件"
- "忽略.gitignore搜索"
- "执行操作"
install_guide: "brew install fd"
- tool_id: "EXT-RG-001"
tool_name: "ripgrep (rg)"
command: "rg"
description: "极速代码搜索工具"
category: "代码搜索"
use_cases:
- "搜索代码"
- "显示上下文"
- "统计匹配数"
install_guide: "brew install ripgrep"
- tool_id: "EXT-BAT-001"
tool_name: "bat (cat with syntax)"
command: "bat"
description: "带语法高亮的cat替代品"
category: "文件查看"
use_cases:
- "查看代码文件"
- "分页查看"
- "Git修改查看"
install_guide: "brew install bat"
- tool_id: "EXT-JQ-001"
tool_name: "jq (JSON processor)"
command: "jq"
description: "JSON数据的命令行处理器"
category: "数据处理"
use_cases:
- "JSON美化"
- "字段提取"
- "数据过滤"
- "格式转换"
install_guide: "brew install jq"
- tool_id: "EXT-XH-001"
tool_name: "xh (HTTP client)"
command: "xh"
description: "友好的HTTP客户端替代curl"
category: "API测试"
use_cases:
- "发送HTTP请求"
- "API测试"
- "文件下载"
install_guide: "brew install xh"
- tool_id: "EXT-DELTA-001"
tool_name: "delta (Git diff美化)"
command: "delta"
description: "Git diff的美化工具"
category: "Git工具"
use_cases:
- "查看Git diff"
- "语法高亮"
- "行号显示"
install_guide: "brew install git-delta"
- tool_id: "EXT-STEAMSHIP-001"
tool_name: "starship (Shell提示符)"
command: "starship"
description: "快速、可定制、智能的Shell提示符"
category: "Shell增强"
use_cases:
- "显示Git状态"
- "显示Python版本"
- "显示目录"
install_guide: "curl -sS https://starship.rs/install.sh | sh"

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()

View File

@@ -0,0 +1,222 @@
"""
Main Discovery Orchestrator - 协调内部和外部工具检测
"""
from pathlib import Path
from typing import List, Optional
from .detectors import InternalToolDetector, ExternalToolDetector
from .models import Tool, InternalTool, ExternalTool
from .formatters import ToolFormatter, TableFormatter, JsonFormatter
class ToolkitDiscovery:
"""工具包发现主类 - 协调所有检测器"""
def __init__(self, toolkit_root: Path):
self.root = toolkit_root
self.internal_detector = InternalToolDetector(toolkit_root)
self.external_detector = ExternalToolDetector(toolkit_root)
# 格式化器
self.table_formatter = TableFormatter()
self.json_formatter = JsonFormatter()
# 初始化时加载所有工具
self.refresh()
def refresh(self):
"""刷新所有工具列表"""
self.internal_detector.refresh()
self.external_detector.refresh()
@property
def internal_tools(self) -> List[InternalTool]:
"""获取内部工具列表"""
return self.internal_detector.tools
@property
def external_tools(self) -> List[ExternalTool]:
"""获取外部工具列表"""
return self.external_detector.tools
@property
def all_tools(self) -> List[Tool]:
"""获取所有工具列表"""
return self.internal_tools + self.external_tools
def list_tools(
self,
internal_only: bool = False,
external_only: bool = False
) -> List[Tool]:
"""
列出工具
Args:
internal_only: 仅返回内部工具
external_only: 仅返回外部工具
Returns:
List[Tool]: 工具列表
"""
if internal_only:
return self.internal_tools
elif external_only:
return self.external_tools
else:
return self.all_tools
def find_tool(self, name_or_id: str) -> Optional[Tool]:
"""
查找工具(同时搜索内部和外部)
Args:
name_or_id: 工具名称或ID
Returns:
Tool: 找到的工具如果未找到返回None
"""
# 先搜索内部工具
tool = self.internal_detector.find_tool(name_or_id)
if tool:
return tool
# 再搜索外部工具
tool = self.external_detector.find_tool(name_or_id)
if tool:
return tool
return None
def filter_tools(
self,
lang: Optional[str] = None,
purpose: Optional[str] = None,
query: Optional[str] = None
) -> List[InternalTool]:
"""
过滤内部工具
Args:
lang: 按语言过滤
purpose: 按用途过滤
query: 按名称或描述搜索
Returns:
List[InternalTool]: 过滤后的内部工具列表
"""
tools = self.internal_tools
if lang:
tools = [t for t in tools if t.language == lang]
if purpose:
tools = [t for t in tools if purpose in t.metadata.purpose]
if query:
query_lower = query.lower()
tools = [
t for t in tools
if query_lower in t.tool_name.lower() or query_lower in t.description.lower()
]
return tools
def search_tools(self, keyword: str) -> List[Tool]:
"""
搜索工具(内部和外部)
Args:
keyword: 搜索关键词
Returns:
List[Tool]: 匹配的工具列表
"""
keyword_lower = keyword.lower()
# 搜索内部工具
internal_matches = [
t for t in self.internal_tools
if (keyword_lower in t.tool_name.lower() or
keyword_lower in t.description.lower())
]
# 搜索外部工具
external_matches = [
t for t in self.external_tools
if (keyword_lower in t.tool_name.lower() or
keyword_lower in t.description.lower() or
keyword_lower in t.category.lower())
]
return internal_matches + external_matches
def recommend_tools(self, task_description: str) -> List[InternalTool]:
"""
根据任务描述推荐工具
Args:
task_description: 任务描述
Returns:
List[InternalTool]: 推荐的工具列表(按匹配度排序)
"""
keywords = task_description.lower().split()
# 简单的推荐算法:匹配关键词数量
scores = {}
for tool in self.internal_tools:
score = 0
tool_text = (
tool.tool_name + ' ' +
tool.description + ' ' +
' '.join(tool.metadata.purpose)
).lower()
for keyword in keywords:
if keyword in tool_text:
score += 1
if score > 0:
scores[tool] = score
# 按分数排序
sorted_tools = sorted(scores.items(), key=lambda x: x[1], reverse=True)
return [tool for tool, _ in sorted_tools[:5]] # 返回前5个
def format_tools(
self,
tools: List[Tool],
format_type: str = 'table'
) -> str:
"""
格式化工具列表
Args:
tools: 工具列表
format_type: 格式类型 ('table''json')
Returns:
str: 格式化后的字符串
"""
if format_type == 'json':
return self.json_formatter.format(tools)
else:
return self.table_formatter.format(tools)
def format_tool(self, tool: Tool, format_type: str = 'table') -> str:
"""
格式化单个工具
Args:
tool: 工具对象
format_type: 格式类型 ('table''json')
Returns:
str: 格式化后的字符串
"""
if format_type == 'json':
return self.json_formatter.format_single(tool)
else:
return self.table_formatter.format_single(tool)

View File

@@ -0,0 +1,13 @@
"""
Output Formatters Module
"""
from .base import ToolFormatter
from .table import TableFormatter
from .json import JsonFormatter
__all__ = [
'ToolFormatter',
'TableFormatter',
'JsonFormatter'
]

View File

@@ -0,0 +1,37 @@
"""
Abstract base class for tool formatters
"""
from abc import ABC, abstractmethod
from typing import List
from ..models import Tool
class ToolFormatter(ABC):
"""抽象基类:工具输出格式化器"""
@abstractmethod
def format(self, tools: List[Tool]) -> str:
"""
格式化工具列表
Args:
tools: 工具列表
Returns:
str: 格式化后的字符串
"""
pass
@abstractmethod
def format_single(self, tool: Tool) -> str:
"""
格式化单个工具
Args:
tool: 工具对象
Returns:
str: 格式化后的字符串
"""
pass

View File

@@ -0,0 +1,67 @@
"""
JSON Formatter - JSON格式输出
"""
import json
from typing import List
from .base import ToolFormatter
from ..models import Tool, InternalTool, ExternalTool
class JsonFormatter(ToolFormatter):
"""JSON格式化器"""
def format(self, tools: List[Tool]) -> str:
"""格式化工具列表为JSON"""
result = []
for tool in tools:
result.append(self._tool_to_dict(tool))
return json.dumps(result, indent=2, ensure_ascii=False)
def format_single(self, tool: Tool) -> str:
"""格式化单个工具为JSON"""
return json.dumps(self._tool_to_dict(tool), indent=2, ensure_ascii=False)
def _tool_to_dict(self, tool: Tool) -> dict:
"""将工具对象转换为字典"""
# 基础信息
data = {
"tool_id": tool.tool_id,
"tool_name": tool.tool_name,
"description": tool.description
}
# 内部工具特有信息
if isinstance(tool, InternalTool):
data.update({
"type": "internal",
"language": tool.language,
"file": tool.file,
"complexity": tool.complexity,
"meta_file": tool.meta_file,
"tool_file": tool.tool_file,
"purpose": tool.metadata.purpose,
"usage": tool.usage
})
if tool.metadata.satisfaction > 0:
data["satisfaction"] = tool.metadata.satisfaction
# 外部工具特有信息
elif isinstance(tool, ExternalTool):
data.update({
"type": "external",
"command": tool.command,
"category": tool.category,
"use_cases": tool.use_cases,
"install_guide": tool.install_guide,
"installed": tool.installed
})
if tool.path:
data["path"] = tool.path
if tool.metadata.version != "1.0.0":
data["version"] = tool.metadata.version
return data

View File

@@ -0,0 +1,178 @@
"""
Table Formatter - 表格格式输出
"""
from typing import List, Any
from .base import ToolFormatter
from ..models import Tool, InternalTool, ExternalTool
class TableFormatter(ToolFormatter):
"""表格格式化器"""
def format(self, tools: List[Tool]) -> str:
"""格式化工具列表为表格"""
if not tools:
return "⚠️ 未找到匹配的工具\n"
# 分离内部工具和外部工具
internal = [t for t in tools if isinstance(t, InternalTool)]
external = [t for t in tools if isinstance(t, ExternalTool)]
output = []
# 内部工具
if internal:
output.append(self._format_internal_tools(internal))
# 外部工具
if external:
if internal:
output.append("")
output.append(self._format_external_tools(external))
return "\n".join(output) + "\n"
def _format_internal_tools(self, tools: List[InternalTool]) -> str:
"""格式化内部工具"""
lines = [
f"📦 找到 {len(tools)} 个内部工具:",
"=" * 110,
f"{'名称':<25} {'ID':<25} {'语言':<8} {'用途':<15} {'描述':<30}",
"-" * 110
]
for tool in tools:
purposes = ",".join(tool.metadata.purpose)[:13]
desc = tool.description[:28]
lines.append(
f"{tool.tool_name:<25} {tool.tool_id:<25} {tool.language:<8} {purposes:<15} {desc:<30}"
)
lines.append("=" * 110)
return "\n".join(lines)
def _format_external_tools(self, tools: List[ExternalTool]) -> str:
"""格式化外部工具"""
lines = [
f"🌟 找到 {len(tools)} 个外部工具:",
"=" * 100,
f"{'名称':<25} {'ID':<20} {'分类':<12} {'安装状态':<10} {'描述':<30}",
"-" * 100
]
for tool in tools:
status = tool.status
desc = tool.description[:30]
lines.append(
f"{tool.tool_name:<25} {tool.tool_id:<20} {tool.category:<12} {status:<10} {desc:<30}"
)
lines.append("=" * 100)
lines.extend([
"",
"💡 提示: 使用 --external 仅显示外部工具",
"💡 提示: 外部工具是系统级的CLI工具需单独安装"
])
return "\n".join(lines)
def format_single(self, tool: Tool) -> str:
"""格式化单个工具详情"""
if isinstance(tool, InternalTool):
return self._format_internal_tool(tool)
elif isinstance(tool, ExternalTool):
return self._format_external_tool(tool)
else:
return self._format_generic_tool(tool)
def _format_internal_tool(self, tool: InternalTool) -> str:
"""格式化内部工具详情"""
lines = [
"",
"=" * 70,
f"📦 {tool.tool_name}",
"=" * 70,
f"ID: {tool.tool_id}",
f"语言: {tool.language}",
f"文件: {tool.file}",
f"复杂度: {tool.complexity}",
f"用途: {', '.join(tool.metadata.purpose)}",
"",
"📋 描述:",
f" {tool.description}",
""
]
if tool.usage:
lines.append("🚀 使用方法:")
if '命令' in tool.usage:
lines.append(f" 命令: {tool.usage['命令']}")
if '参数' in tool.usage:
lines.append(" 参数:")
for param, desc in tool.usage['参数'].items():
lines.append(f" - {param}: {desc}")
if '示例' in tool.usage:
lines.append(" 示例:")
for example in tool.usage.get('示例', [])[:3]:
lines.append(f"{example}")
lines.append("")
if tool.metadata.satisfaction > 0:
lines.extend([
"📊 使用统计:",
f" 满意度: {tool.metadata.satisfaction}/1.0",
""
])
lines.extend([
"📂 文件位置:",
f" 元数据: {tool.meta_file}",
f"{'=' * 70}",
""
])
return "\n".join(lines)
def _format_external_tool(self, tool: ExternalTool) -> str:
"""格式化外部工具详情"""
lines = [
"",
"=" * 70,
f"🌟 {tool.tool_name}",
"=" * 70,
f"ID: {tool.tool_id}",
f"分类: {tool.category}",
f"命令: {tool.command}",
f"状态: {tool.status}",
"",
"📋 描述:",
f" {tool.description}",
"",
"💡 使用场景:",
]
for use_case in tool.use_cases:
lines.append(f"{use_case}")
lines.extend([
"",
"📥 安装指南:",
f" {tool.install_guide}",
""
])
if tool.path:
lines.extend([
"📂 安装路径:",
f" {tool.path}",
""
])
lines.append(f"{'=' * 70}\n")
return "\n".join(lines)
def _format_generic_tool(self, tool: Tool) -> str:
"""格式化通用工具详情"""
return f"\n{'=' * 70}\n📦 {tool.tool_name} ({tool.tool_id})\n{'=' * 70}\n 描述: {tool.description}\n{'=' * 70}\n\n"

View File

@@ -0,0 +1,13 @@
"""
Data models for toolkit discovery system
"""
from .tool import Tool, InternalTool, ExternalTool, ToolUsage, ToolMetadata
__all__ = [
'Tool',
'InternalTool',
'ExternalTool',
'ToolUsage',
'ToolMetadata'
]

View File

@@ -0,0 +1,107 @@
"""
数据模型:工具和元数据定义
"""
from dataclasses import dataclass, field
from typing import Dict, List, Any, Optional
from datetime import datetime
@dataclass
class ToolUsage:
"""工具使用信息"""
command: str
task: str
trigger: str
expected: str
execution_status: str = "unknown"
output_files: List[str] = field(default_factory=list)
key_findings: Dict[str, Any] = field(default_factory=dict)
satisfaction: float = 0.0
duration: float = 0.0
followup_actions: List[str] = field(default_factory=list)
lessons_learned: List[str] = field(default_factory=list)
@dataclass
class ToolMetadata:
"""工具元数据基类"""
tool_id: str
tool_name: str
description: str
purpose: List[str] = field(default_factory=list)
last_used: Optional[datetime] = None
satisfaction: float = 0.0
related_tools: Dict[str, List[str]] = field(default_factory=dict)
maintenance_notes: Dict[str, Any] = field(default_factory=dict)
version: str = "1.0.0"
@dataclass
class Tool:
"""工具基类"""
metadata: ToolMetadata
@property
def tool_id(self) -> str:
return self.metadata.tool_id
@property
def tool_name(self) -> str:
return self.metadata.tool_name
@property
def description(self) -> str:
return self.metadata.description
@dataclass
class InternalTool(Tool):
"""内部工具AI-runtime创建的工具"""
meta_file: str
tool_file: Optional[str]
language: str
file: str
complexity: str
usage: Dict[str, Any] = field(default_factory=dict)
full_meta: Optional[Dict[str, Any]] = None
def __post_init__(self):
if not isinstance(self.metadata, ToolMetadata):
self.metadata = ToolMetadata(
tool_id=self.tool_id,
tool_name=self.tool_name,
description=self.description,
purpose=self.metadata.get("purpose", []) if isinstance(self.metadata, dict) else self.metadata.purpose
)
@dataclass
class ExternalTool(Tool):
"""外部工具第三方CLI工具"""
command: str
category: str
use_cases: List[str]
install_guide: str
installed: bool = False
path: Optional[str] = None
@property
def status(self) -> str:
"""获取安装状态"""
return "✅ 已安装" if self.installed else "❌ 未安装"
class ToolDetectorError(Exception):
"""工具检测异常"""
pass
class ToolFormatError(Exception):
"""工具格式化异常"""
pass