#!/usr/bin/env python3 """ Context Hub (chub) 包装器 为nanobot提供更友好的chub接口 """ import subprocess import json import sys import os from typing import List, Dict, Any, Optional class ChubWrapper: """chub CLI的Python包装器""" def __init__(self): """初始化chub包装器""" self.chub_path = self._find_chub() def _find_chub(self) -> str: """查找chub可执行文件路径""" try: result = subprocess.run(['which', 'chub'], capture_output=True, text=True, check=True) return result.stdout.strip() except subprocess.CalledProcessError: raise RuntimeError("chub未安装。请运行: npm install -g @aisuite/chub") def _run_chub(self, args: List[str], capture_output: bool = True) -> str: """运行chub命令""" cmd = [self.chub_path] + args try: result = subprocess.run(cmd, capture_output=capture_output, text=True, check=True) return result.stdout except subprocess.CalledProcessError as e: error_msg = f"chub命令失败: {' '.join(cmd)}\n" if e.stderr: error_msg += f"错误: {e.stderr}" raise RuntimeError(error_msg) def search(self, query: str, json_output: bool = True) -> List[Dict[str, Any]]: """ 搜索文档 Args: query: 搜索查询 json_output: 是否返回JSON格式 Returns: 搜索结果列表 """ args = ['search', query] if json_output: args.append('--json') output = self._run_chub(args) if json_output: try: data = json.loads(output) return data.get('results', []) except json.JSONDecodeError: # 如果不是JSON格式,返回原始输出 return [{'raw_output': output}] else: return [{'raw_output': output}] def get_doc(self, doc_id: str, language: Optional[str] = None, output_file: Optional[str] = None) -> str: """ 获取文档内容 Args: doc_id: 文档ID (如 'openai/chat') language: 语言 (如 'py', 'js', 'ts') output_file: 输出文件路径 Returns: 文档内容 """ args = ['get', doc_id] if language: args.extend(['--lang', language]) if output_file: args.extend(['-o', output_file]) return self._run_chub(args, capture_output=not output_file) def annotate(self, doc_id: str, note: str) -> str: """ 添加注解 Args: doc_id: 文档ID note: 注解内容 Returns: 执行结果 """ args = ['annotate', doc_id, note] return self._run_chub(args) def list_annotations(self) -> str: """列出所有注解""" args = ['annotate', '--list'] return self._run_chub(args) def feedback(self, doc_id: str, rating: str, label: Optional[str] = None) -> str: """ 提供反馈 Args: doc_id: 文档ID rating: 评分 ('up' 或 'down') label: 标签 (如 'outdated', 'inaccurate') Returns: 执行结果 """ args = ['feedback', doc_id, rating] if label: args.extend(['--label', label]) return self._run_chub(args) def search_and_get(self, query: str, language: Optional[str] = None) -> Dict[str, Any]: """ 搜索并获取文档(一步完成) Args: query: 搜索查询 language: 语言 Returns: 包含搜索结果和文档内容的字典 """ # 搜索 results = self.search(query) if not results: return {'error': f'未找到与"{query}"相关的文档'} # 获取第一个结果 first_result = results[0] doc_id = first_result.get('id') if not doc_id: return {'error': '搜索结果中没有找到有效的文档ID'} # 获取文档 try: content = self.get_doc(doc_id, language) return { 'doc_id': doc_id, 'result': first_result, 'content': content[:1000] + '...' if len(content) > 1000 else content } except Exception as e: return { 'doc_id': doc_id, 'result': first_result, 'error': f'获取文档失败: {str(e)}' } def main(): """命令行接口""" import argparse parser = argparse.ArgumentParser(description='Context Hub (chub) 包装器') subparsers = parser.add_subparsers(dest='command', help='命令') # 搜索命令 search_parser = subparsers.add_parser('search', help='搜索文档') search_parser.add_argument('query', help='搜索查询') search_parser.add_argument('--json', action='store_true', help='JSON输出') # 获取命令 get_parser = subparsers.add_parser('get', help='获取文档') get_parser.add_argument('doc_id', help='文档ID') get_parser.add_argument('--lang', help='语言 (py, js, ts)') get_parser.add_argument('-o', '--output', help='输出文件') # 注解命令 annotate_parser = subparsers.add_parser('annotate', help='添加注解') annotate_parser.add_argument('doc_id', help='文档ID') annotate_parser.add_argument('note', help='注解内容') # 列出注解命令 subparsers.add_parser('list-annotations', help='列出所有注解') # 反馈命令 feedback_parser = subparsers.add_parser('feedback', help='提供反馈') feedback_parser.add_argument('doc_id', help='文档ID') feedback_parser.add_argument('rating', choices=['up', 'down'], help='评分') feedback_parser.add_argument('--label', help='标签') # 搜索并获取命令 search_get_parser = subparsers.add_parser('search-get', help='搜索并获取文档') search_get_parser.add_argument('query', help='搜索查询') search_get_parser.add_argument('--lang', help='语言') args = parser.parse_args() wrapper = ChubWrapper() try: if args.command == 'search': results = wrapper.search(args.query, args.json) if args.json: print(json.dumps(results, indent=2, ensure_ascii=False)) else: for result in results: print(f"ID: {result.get('id', 'N/A')}") print(f"名称: {result.get('name', 'N/A')}") print(f"描述: {result.get('description', 'N/A')[:100]}...") print() elif args.command == 'get': content = wrapper.get_doc(args.doc_id, args.lang, args.output) if not args.output: print(content) elif args.command == 'annotate': result = wrapper.annotate(args.doc_id, args.note) print(result) elif args.command == 'list-annotations': result = wrapper.list_annotations() print(result) elif args.command == 'feedback': result = wrapper.feedback(args.doc_id, args.rating, args.label) print(result) elif args.command == 'search-get': result = wrapper.search_and_get(args.query, args.lang) print(json.dumps(result, indent=2, ensure_ascii=False)) else: parser.print_help() except Exception as e: print(f"错误: {e}", file=sys.stderr) sys.exit(1) if __name__ == '__main__': main()