Files
nanocode/skills/opencli-integration/opencli_adapter.py
T
Ubuntu 32b43f85bd a
2026-07-03 16:58:28 +08:00

428 lines
12 KiB
Python

#!/usr/bin/env python3
"""
OpenCLI Integration Adapter
将OpenCLI能力封装为Python接口
"""
import subprocess
import json
import os
import shutil
from pathlib import Path
from typing import Dict, List, Optional, Any
class OpenCLIAdapter:
"""OpenCLI适配器"""
def __init__(self, clis_dir: Optional[str] = None):
"""
初始化OpenCLI适配器
Args:
clis_dir: 适配器存储目录,默认 ~/.opencli/clis
"""
self.clis_dir = clis_dir or os.path.expanduser("~/.opencli/clis")
self._check_opencli()
def _check_opencli(self) -> bool:
"""检查OpenCLI是否已安装"""
if not shutil.which("opencli"):
raise RuntimeError(
"OpenCLI未安装。请先运行: npm install -g @jackwener/opencli"
)
return True
def _run_command(self, cmd: List[str], capture_output: bool = True) -> Dict[str, Any]:
"""
运行OpenCLI命令
Args:
cmd: 命令列表
capture_output: 是否捕获输出
Returns:
{"success": bool, "stdout": str, "stderr": str, "returncode": int}
"""
try:
result = subprocess.run(
["opencli"] + cmd,
capture_output=capture_output,
text=True,
timeout=120
)
return {
"success": result.returncode == 0,
"stdout": result.stdout,
"stderr": result.stderr,
"returncode": result.returncode
}
except subprocess.TimeoutExpired:
return {
"success": False,
"stdout": "",
"stderr": "Command timeout",
"returncode": -1
}
except Exception as e:
return {
"success": False,
"stdout": "",
"stderr": str(e),
"returncode": -1
}
# ==================== 核心功能 ====================
def setup(self) -> Dict[str, Any]:
"""
初始化OpenCLI环境
检查Browser Bridge连通性
"""
return self._run_command(["setup"])
def doctor(self) -> Dict[str, Any]:
"""
诊断OpenCLI状态
检查daemon、扩展和浏览器连接
"""
return self._run_command(["doctor"])
def list(self) -> List[Dict[str, Any]]:
"""
列出所有可用的CLI适配器
Returns:
适配器列表
"""
result = self._run_command(["list", "--json"])
if result["success"]:
try:
return json.loads(result["stdout"])
except json.JSONDecodeError:
return []
return []
def explore(self, url: str, output_dir: Optional[str] = None) -> Dict[str, Any]:
"""
探索网站API
AI自动发现网站接口
Args:
url: 目标网站URL
output_dir: 输出目录
Returns:
探索结果
"""
cmd = ["explore", url]
if output_dir:
cmd.extend(["--output", output_dir])
return self._run_command(cmd)
def synthesize(self, name: str, url: Optional[str] = None) -> Dict[str, Any]:
"""
生成适配器
基于探索结果生成CLI适配器
Args:
name: 适配器名称
url: 网站URL(如已explore可省略)
Returns:
生成结果
"""
cmd = ["synthesize", name]
if url:
cmd.extend(["--url", url])
return self._run_command(cmd)
def register(self, name: str, adapter_path: Optional[str] = None) -> Dict[str, Any]:
"""
注册适配器
Args:
name: 适配器名称
adapter_path: 适配器文件路径
Returns:
注册结果
"""
cmd = ["register", name]
if adapter_path:
cmd.append(adapter_path)
return self._run_command(cmd)
def execute(self, cli_name: str, command: str, **kwargs) -> Dict[str, Any]:
"""
执行CLI命令
Args:
cli_name: CLI名称
command: 命令名称
**kwargs: 命令参数
Returns:
执行结果
"""
cmd = [cli_name, command]
# 添加参数
for key, value in kwargs.items():
if isinstance(value, bool) and value:
cmd.append(f"--{key}")
else:
cmd.extend([f"--{key}", str(value)])
return self._run_command(cmd)
# ==================== 便捷方法 ====================
def search(self, cli_name: str, query: str, **kwargs) -> Dict[str, Any]:
"""
便捷搜索方法
Args:
cli_name: CLI名称
query: 搜索关键词
**kwargs: 其他参数
Returns:
搜索结果
"""
return self.execute(cli_name, "search", query=query, **kwargs)
def get(self, cli_name: str, id: str, **kwargs) -> Dict[str, Any]:
"""
便捷获取方法
Args:
cli_name: CLI名称
id: 资源ID
**kwargs: 其他参数
Returns:
获取结果
"""
return self.execute(cli_name, "get", id=id, **kwargs)
# ==================== Electron应用控制 ====================
def control_electron(self, app_name: str, action: str, **kwargs) -> Dict[str, Any]:
"""
控制Electron应用
Args:
app_name: 应用名称
action: 动作名称
**kwargs: 动作参数
Returns:
执行结果
"""
return self.execute(f"electron:{app_name}", action, **kwargs)
# ==================== 适配器管理 ====================
def create_yaml_adapter(self, name: str, config: Dict[str, Any]) -> str:
"""
创建YAML声明式适配器
Args:
name: 适配器名称
config: 适配器配置
Returns:
适配器文件路径
"""
import yaml
adapter_path = os.path.join(self.clis_dir, f"{name}.yaml")
os.makedirs(self.clis_dir, exist_ok=True)
with open(adapter_path, 'w', encoding='utf-8') as f:
yaml.dump(config, f, allow_unicode=True)
return adapter_path
def get_adapter_info(self, name: str) -> Optional[Dict[str, Any]]:
"""
获取适配器信息
Args:
name: 适配器名称
Returns:
适配器信息
"""
# 检查YAML适配器
yaml_path = os.path.join(self.clis_dir, f"{name}.yaml")
if os.path.exists(yaml_path):
import yaml
with open(yaml_path, 'r', encoding='utf-8') as f:
return yaml.safe_load(f)
# 检查TypeScript适配器
ts_path = os.path.join(self.clis_dir, f"{name}.ts")
if os.path.exists(ts_path):
return {"type": "typescript", "path": ts_path}
return None
def remove_adapter(self, name: str) -> bool:
"""
删除适配器
Args:
name: 适配器名称
Returns:
是否成功
"""
removed = False
yaml_path = os.path.join(self.clis_dir, f"{name}.yaml")
if os.path.exists(yaml_path):
os.remove(yaml_path)
removed = True
ts_path = os.path.join(self.clis_dir, f"{name}.ts")
if os.path.exists(ts_path):
os.remove(ts_path)
removed = True
return removed
# ==================== 状态检查 ====================
def is_ready(self) -> Dict[str, Any]:
"""
检查OpenCLI是否就绪
Returns:
状态信息
"""
checks = {
"opencli_installed": shutil.which("opencli") is not None,
"node_version": None,
"chrome_running": False,
"clis_dir_exists": os.path.exists(self.clis_dir),
}
# 检查Node版本
try:
result = subprocess.run(
["node", "--version"],
capture_output=True,
text=True,
timeout=5
)
if result.returncode == 0:
checks["node_version"] = result.stdout.strip()
except:
pass
# 检查Chrome是否运行(简单检查)
try:
result = subprocess.run(
["pgrep", "-x", "chrome"],
capture_output=True,
timeout=2
)
checks["chrome_running"] = result.returncode == 0
except:
pass
checks["ready"] = (
checks["opencli_installed"] and
checks["node_version"] and
checks["clis_dir_exists"]
)
return checks
# ==================== 命令行接口 ====================
def main():
"""命令行入口"""
import argparse
parser = argparse.ArgumentParser(description='OpenCLI Adapter')
subparsers = parser.add_subparsers(dest='command', help='可用命令')
# setup
subparsers.add_parser('setup', help='初始化OpenCLI')
# doctor
subparsers.add_parser('doctor', help='诊断OpenCLI状态')
# list
subparsers.add_parser('list', help='列出可用适配器')
# explore
explore_parser = subparsers.add_parser('explore', help='探索网站API')
explore_parser.add_argument('url', help='目标网站URL')
# synthesize
synth_parser = subparsers.add_parser('synthesize', help='生成适配器')
synth_parser.add_argument('name', help='适配器名称')
synth_parser.add_argument('--url', help='网站URL')
# exec
exec_parser = subparsers.add_parser('exec', help='执行CLI命令')
exec_parser.add_argument('cli', help='CLI名称')
exec_parser.add_argument('action', help='动作名称')
exec_parser.add_argument('--args', nargs='*', help='额外参数')
# status
subparsers.add_parser('status', help='检查状态')
args = parser.parse_args()
if not args.command:
parser.print_help()
return
adapter = OpenCLIAdapter()
if args.command == 'setup':
result = adapter.setup()
print(result["stdout"] if result["success"] else result["stderr"])
elif args.command == 'doctor':
result = adapter.doctor()
print(result["stdout"] if result["success"] else result["stderr"])
elif args.command == 'list':
adapters = adapter.list()
print(json.dumps(adapters, indent=2, ensure_ascii=False))
elif args.command == 'explore':
print(f"正在探索: {args.url}")
result = adapter.explore(args.url)
print(result["stdout"] if result["success"] else result["stderr"])
elif args.command == 'synthesize':
print(f"正在生成适配器: {args.name}")
result = adapter.synthesize(args.name, args.url)
print(result["stdout"] if result["success"] else result["stderr"])
elif args.command == 'exec':
print(f"执行: {args.cli} {args.action}")
result = adapter.execute(args.cli, args.action)
print(result["stdout"] if result["success"] else result["stderr"])
elif args.command == 'status':
status = adapter.is_ready()
print(json.dumps(status, indent=2, ensure_ascii=False))
if __name__ == '__main__':
main()