architecture skill created
This commit is contained in:
parent
77cd8e04b0
commit
566dc54610
8 changed files with 574 additions and 2 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -3,10 +3,10 @@
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.AppleDouble
|
.AppleDouble
|
||||||
.LSOverride
|
.LSOverride
|
||||||
|
|
||||||
# Icon must end with two \r
|
# Icon must end with two \r
|
||||||
Icon
|
Icon
|
||||||
|
|
||||||
|
|
||||||
# Thumbnails
|
# Thumbnails
|
||||||
._*
|
._*
|
||||||
|
|
||||||
|
|
@ -52,3 +52,4 @@ $RECYCLE.BIN/
|
||||||
# Windows shortcuts
|
# Windows shortcuts
|
||||||
*.lnk
|
*.lnk
|
||||||
|
|
||||||
|
*.idea
|
||||||
50
SKILL.md
Normal file
50
SKILL.md
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
---
|
||||||
|
name: browser-use
|
||||||
|
version: "1.0.0"
|
||||||
|
description: |
|
||||||
|
Автоматизация браузера с помощью Playwright и библиотеки browser_use.
|
||||||
|
Выполняет навигацию, клики, заполнение форм, скриншоты, извлечение данных.
|
||||||
|
Подходит для тестирования веб-приложений, парсинга, автоматизации рутинных задач.
|
||||||
|
triggers:
|
||||||
|
- "открой сайт"
|
||||||
|
- "нажми на кнопку"
|
||||||
|
- "заполни форму"
|
||||||
|
- "сделай скриншот"
|
||||||
|
- "спарси данные"
|
||||||
|
- "автоматизируй браузер"
|
||||||
|
- "browser use"
|
||||||
|
- "playwright"
|
||||||
|
license: MIT
|
||||||
|
compatibility:
|
||||||
|
- hermes
|
||||||
|
- claude
|
||||||
|
allowed-tools:
|
||||||
|
- bash
|
||||||
|
- python
|
||||||
|
- read_file
|
||||||
|
- write_file
|
||||||
|
---
|
||||||
|
|
||||||
|
# BrowserUse Skill
|
||||||
|
|
||||||
|
Автоматизация браузера с использованием Playwright и browser_use.
|
||||||
|
|
||||||
|
## 🎯 Описание
|
||||||
|
|
||||||
|
Этот скилл позволяет Hermes-агенту управлять браузером:
|
||||||
|
- Открывать URL и навигировать
|
||||||
|
- Кликать по элементам
|
||||||
|
- Заполнять формы
|
||||||
|
- Извлекать данные (текст, атрибуты, HTML)
|
||||||
|
- Делать скриншоты
|
||||||
|
- Ждать загрузки элементов
|
||||||
|
- Выполнять кастомный JavaScript
|
||||||
|
- Работать с выпадающими списками
|
||||||
|
|
||||||
|
## 📦 Установка зависимостей
|
||||||
|
|
||||||
|
Перед первым использованием выполни:
|
||||||
|
```bash
|
||||||
|
cd ~/.hermes/skills/browser-use/scripts
|
||||||
|
chmod +x setup.sh
|
||||||
|
./setup.sh
|
||||||
30
assets/config.example.json
Normal file
30
assets/config.example.json
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚙️ Файл: assets/config.example.json
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"browser": {
|
||||||
|
"headless": true,
|
||||||
|
"timeout": 30000,
|
||||||
|
"viewport": {
|
||||||
|
"width": 1280,
|
||||||
|
"height": 720
|
||||||
|
},
|
||||||
|
"user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
||||||
|
},
|
||||||
|
"screenshots": {
|
||||||
|
"path": "/tmp/browser-use-screenshots",
|
||||||
|
"format": "png",
|
||||||
|
"full_page": true
|
||||||
|
},
|
||||||
|
"retry": {
|
||||||
|
"max_attempts": 3,
|
||||||
|
"delay_seconds": 2
|
||||||
|
},
|
||||||
|
"logging": {
|
||||||
|
"level": "info",
|
||||||
|
"save_screenshots_on_error": true
|
||||||
|
}
|
||||||
|
}
|
||||||
27
references/common_patterns.md
Normal file
27
references/common_patterns.md
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 Файл: references/common_patterns.md
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Common Browser Automation Patterns
|
||||||
|
|
||||||
|
## Паттерн 1: Авторизация
|
||||||
|
|
||||||
|
### Сценарий
|
||||||
|
Пользователь хочет автоматизировать вход в систему.
|
||||||
|
|
||||||
|
### Реализация
|
||||||
|
```python
|
||||||
|
{
|
||||||
|
"action": "sequence",
|
||||||
|
"steps": [
|
||||||
|
{"action": "goto", "url": "https://example.com/login"},
|
||||||
|
{"action": "wait", "selector": "form", "timeout": 5000},
|
||||||
|
{"action": "fill", "selector": "input[name='email']", "value": "user@example.com"},
|
||||||
|
{"action": "fill", "selector": "input[name='password']", "value": "password123"},
|
||||||
|
{"action": "click", "selector": "button[type='submit']"},
|
||||||
|
{"action": "wait", "selector": ".dashboard", "timeout": 10000},
|
||||||
|
{"action": "screenshot", "path": "/tmp/after_login.png"}
|
||||||
|
]
|
||||||
|
}
|
||||||
52
references/selectors.md
Normal file
52
references/selectors.md
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
# CSS Селекторы — Полная шпаргалка
|
||||||
|
|
||||||
|
## Быстрый справочник
|
||||||
|
|
||||||
|
### Базовые селекторы
|
||||||
|
|
||||||
|
| Селектор | Пример | Описание |
|
||||||
|
|----------|--------|----------|
|
||||||
|
| `*` | `*` | Все элементы |
|
||||||
|
| `element` | `div` | Элемент по тегу |
|
||||||
|
| `#id` | `#main` | Элемент по ID |
|
||||||
|
| `.class` | `.button` | Элемент по классу |
|
||||||
|
| `[attr]` | `[disabled]` | Элемент с атрибутом |
|
||||||
|
| `[attr=value]` | `[type="submit"]` | Точное совпадение атрибута |
|
||||||
|
| `[attr^=value]` | `[href^="https"]` | Атрибут начинается с |
|
||||||
|
| `[attr$=value]` | `[href$=".pdf"]` | Атрибут заканчивается на |
|
||||||
|
| `[attr*=value]` | `[name*="user"]` | Атрибут содержит |
|
||||||
|
|
||||||
|
### Комбинаторы
|
||||||
|
|
||||||
|
| Селектор | Пример | Описание |
|
||||||
|
|----------|--------|----------|
|
||||||
|
| `A B` | `div p` | Потомок (любой уровень) |
|
||||||
|
| `A > B` | `div > p` | Прямой потомок |
|
||||||
|
| `A + B` | `h1 + p` | Соседний элемент |
|
||||||
|
| `A ~ B` | `h1 ~ p` | Все следующие соседние |
|
||||||
|
|
||||||
|
### Псевдоклассы
|
||||||
|
|
||||||
|
| Псевдокласс | Пример | Описание |
|
||||||
|
|-------------|--------|----------|
|
||||||
|
| `:first-child` | `li:first-child` | Первый дочерний |
|
||||||
|
| `:last-child` | `li:last-child` | Последний дочерний |
|
||||||
|
| `:nth-child(n)` | `tr:nth-child(2)` | n-й дочерний |
|
||||||
|
| `:nth-of-type(n)` | `p:nth-of-type(2)` | n-й элемент типа |
|
||||||
|
| `:not(selector)` | `div:not(.hidden)` | Исключение |
|
||||||
|
| `:has(selector)` | `div:has(p)` | Содержит дочерний элемент |
|
||||||
|
| `:contains(text)` | `a:contains("Click")` | Содержит текст |
|
||||||
|
|
||||||
|
## XPath — Альтернатива
|
||||||
|
|
||||||
|
### Базовые XPath
|
||||||
|
|
||||||
|
```xpath
|
||||||
|
//element # Все элементы
|
||||||
|
//div[@id='main'] # По атрибуту
|
||||||
|
//div[contains(@class, 'btn')] # Частичное совпадение класса
|
||||||
|
//button[text()='Submit'] # По тексту
|
||||||
|
//a[contains(text(), 'Learn')] # Частичное совпадение текста
|
||||||
|
//div[@id='main']//p # Вложенность
|
||||||
|
//div[1] # Первый div
|
||||||
|
//div[last()] # Последний div
|
||||||
338
scripts/browser_automation.py
Normal file
338
scripts/browser_automation.py
Normal file
|
|
@ -0,0 +1,338 @@
|
||||||
|
|
||||||
|
|
||||||
|
## 🐍 Файл: scripts/browser_automation.py
|
||||||
|
|
||||||
|
|
||||||
|
# !/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Browser automation core module for Hermes Agent Skill
|
||||||
|
Автоматизация браузера с использованием Playwright
|
||||||
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
from typing import Dict, Any, Optional, List
|
||||||
|
from playwright.async_api import async_playwright, Page, Browser, Playwright
|
||||||
|
|
||||||
|
|
||||||
|
class BrowserAutomation:
|
||||||
|
"""Основной класс для автоматизации браузера"""
|
||||||
|
|
||||||
|
def __init__(self, headless: bool = True, timeout: int = 30000):
|
||||||
|
self.headless = headless
|
||||||
|
self.timeout = timeout
|
||||||
|
self.playwright: Optional[Playwright] = None
|
||||||
|
self.browser: Optional[Browser] = None
|
||||||
|
self.page: Optional[Page] = None
|
||||||
|
|
||||||
|
async def __aenter__(self):
|
||||||
|
await self.start()
|
||||||
|
return self
|
||||||
|
|
||||||
|
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
await self.close()
|
||||||
|
|
||||||
|
async def start(self):
|
||||||
|
"""Запуск браузера"""
|
||||||
|
self.playwright = await async_playwright().start()
|
||||||
|
self.browser = await self.playwright.chromium.launch(
|
||||||
|
headless=self.headless,
|
||||||
|
args=[
|
||||||
|
'--no-sandbox',
|
||||||
|
'--disable-setuid-sandbox',
|
||||||
|
'--disable-dev-shm-usage',
|
||||||
|
'--disable-accelerated-2d-canvas',
|
||||||
|
'--disable-gpu'
|
||||||
|
]
|
||||||
|
)
|
||||||
|
self.page = await self.browser.new_page()
|
||||||
|
self.page.set_default_timeout(self.timeout)
|
||||||
|
|
||||||
|
async def close(self):
|
||||||
|
"""Закрытие браузера"""
|
||||||
|
if self.browser:
|
||||||
|
await self.browser.close()
|
||||||
|
if self.playwright:
|
||||||
|
await self.playwright.stop()
|
||||||
|
|
||||||
|
async def goto(self, url: str) -> Dict[str, Any]:
|
||||||
|
"""Переход по URL"""
|
||||||
|
try:
|
||||||
|
response = await self.page.goto(url, wait_until='networkidle')
|
||||||
|
status = response.status if response else None
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"url": self.page.url,
|
||||||
|
"status": status
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": f"Failed to navigate to {url}: {str(e)}"
|
||||||
|
}
|
||||||
|
|
||||||
|
async def click(self, selector: str) -> Dict[str, Any]:
|
||||||
|
"""Клик по элементу"""
|
||||||
|
try:
|
||||||
|
await self.page.wait_for_selector(selector, timeout=self.timeout)
|
||||||
|
await self.page.click(selector)
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"selector": selector,
|
||||||
|
"message": f"Clicked on {selector}"
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": f"Failed to click on {selector}: {str(e)}"
|
||||||
|
}
|
||||||
|
|
||||||
|
async def fill(self, selector: str, value: str) -> Dict[str, Any]:
|
||||||
|
"""Заполнение поля"""
|
||||||
|
try:
|
||||||
|
await self.page.wait_for_selector(selector, timeout=self.timeout)
|
||||||
|
await self.page.fill(selector, value)
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"selector": selector,
|
||||||
|
"value": value,
|
||||||
|
"message": f"Filled {selector} with '{value}'"
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": f"Failed to fill {selector}: {str(e)}"
|
||||||
|
}
|
||||||
|
|
||||||
|
async def screenshot(self, path: str = "/tmp/screenshot.png") -> Dict[str, Any]:
|
||||||
|
"""Скриншот страницы"""
|
||||||
|
try:
|
||||||
|
# Убедимся, что директория существует
|
||||||
|
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||||
|
|
||||||
|
await self.page.screenshot(path=path, full_page=True)
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"path": path,
|
||||||
|
"message": f"Screenshot saved to {path}"
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": f"Failed to take screenshot: {str(e)}"
|
||||||
|
}
|
||||||
|
|
||||||
|
async def get_text(self, selector: str) -> Dict[str, Any]:
|
||||||
|
"""Получение текста элемента"""
|
||||||
|
try:
|
||||||
|
await self.page.wait_for_selector(selector, timeout=self.timeout)
|
||||||
|
text = await self.page.text_content(selector)
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"text": text.strip() if text else "",
|
||||||
|
"selector": selector
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": f"Failed to get text from {selector}: {str(e)}"
|
||||||
|
}
|
||||||
|
|
||||||
|
async def get_text_all(self, selector: str) -> Dict[str, Any]:
|
||||||
|
"""Получение текста всех элементов"""
|
||||||
|
try:
|
||||||
|
await self.page.wait_for_selector(selector, timeout=self.timeout)
|
||||||
|
elements = await self.page.query_selector_all(selector)
|
||||||
|
texts = []
|
||||||
|
for el in elements:
|
||||||
|
text = await el.text_content()
|
||||||
|
if text:
|
||||||
|
texts.append(text.strip())
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"texts": texts,
|
||||||
|
"count": len(texts),
|
||||||
|
"selector": selector
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": f"Failed to get texts from {selector}: {str(e)}"
|
||||||
|
}
|
||||||
|
|
||||||
|
async def evaluate(self, js_code: str) -> Dict[str, Any]:
|
||||||
|
"""Выполнение JavaScript"""
|
||||||
|
try:
|
||||||
|
result = await self.page.evaluate(js_code)
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"result": result,
|
||||||
|
"code": js_code[:100] # Обрезаем для вывода
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": f"Failed to evaluate JavaScript: {str(e)}"
|
||||||
|
}
|
||||||
|
|
||||||
|
async def select(self, selector: str, value: str) -> Dict[str, Any]:
|
||||||
|
"""Выбор из выпадающего списка"""
|
||||||
|
try:
|
||||||
|
await self.page.wait_for_selector(selector, timeout=self.timeout)
|
||||||
|
await self.page.select_option(selector, value)
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"selector": selector,
|
||||||
|
"value": value,
|
||||||
|
"message": f"Selected '{value}' from {selector}"
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": f"Failed to select from {selector}: {str(e)}"
|
||||||
|
}
|
||||||
|
|
||||||
|
async def wait_for_selector(self, selector: str, timeout: int = None) -> Dict[str, Any]:
|
||||||
|
"""Ожидание появления элемента"""
|
||||||
|
timeout_ms = timeout or self.timeout
|
||||||
|
try:
|
||||||
|
await self.page.wait_for_selector(selector, timeout=timeout_ms)
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"selector": selector,
|
||||||
|
"timeout": timeout_ms,
|
||||||
|
"message": f"Element {selector} appeared"
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": f"Timeout waiting for {selector}: {str(e)}"
|
||||||
|
}
|
||||||
|
|
||||||
|
async def get_html(self) -> Dict[str, Any]:
|
||||||
|
"""Получение HTML страницы"""
|
||||||
|
try:
|
||||||
|
html = await self.page.content()
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"html": html,
|
||||||
|
"size": len(html)
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": f"Failed to get HTML: {str(e)}"
|
||||||
|
}
|
||||||
|
|
||||||
|
async def get_title(self) -> Dict[str, Any]:
|
||||||
|
"""Получение заголовка страницы"""
|
||||||
|
try:
|
||||||
|
title = await self.page.title()
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"title": title
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": f"Failed to get title: {str(e)}"
|
||||||
|
}
|
||||||
|
|
||||||
|
async def get_url(self) -> Dict[str, Any]:
|
||||||
|
"""Получение текущего URL"""
|
||||||
|
try:
|
||||||
|
url = self.page.url
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"url": url
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": f"Failed to get URL: {str(e)}"
|
||||||
|
}
|
||||||
|
|
||||||
|
async def execute_sequence(self, steps: List[Dict[str, Any]]) -> Dict[str, Any]:
|
||||||
|
"""Выполнение последовательности действий"""
|
||||||
|
results = []
|
||||||
|
|
||||||
|
for i, step in enumerate(steps):
|
||||||
|
result = await self.execute_task(step)
|
||||||
|
results.append({
|
||||||
|
"step": i + 1,
|
||||||
|
"action": step.get("action"),
|
||||||
|
"result": result
|
||||||
|
})
|
||||||
|
|
||||||
|
# Если шаг не удался, прекращаем выполнение
|
||||||
|
if not result.get("success"):
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": f"Sequence failed at step {i + 1}",
|
||||||
|
"results": results
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"results": results,
|
||||||
|
"total_steps": len(steps)
|
||||||
|
}
|
||||||
|
|
||||||
|
async def execute_task(self, task: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""Выполнение задачи по описанию"""
|
||||||
|
action = task.get("action")
|
||||||
|
|
||||||
|
actions_map = {
|
||||||
|
"goto": lambda: self.goto(task.get("url")),
|
||||||
|
"click": lambda: self.click(task.get("selector")),
|
||||||
|
"fill": lambda: self.fill(task.get("selector"), task.get("value")),
|
||||||
|
"screenshot": lambda: self.screenshot(task.get("path", "/tmp/screenshot.png")),
|
||||||
|
"get_text": lambda: self.get_text(task.get("selector")),
|
||||||
|
"get_text_all": lambda: self.get_text_all(task.get("selector")),
|
||||||
|
"evaluate": lambda: self.evaluate(task.get("code")),
|
||||||
|
"select": lambda: self.select(task.get("selector"), task.get("value")),
|
||||||
|
"wait": lambda: self.wait_for_selector(task.get("selector"), task.get("timeout")),
|
||||||
|
"get_html": lambda: self.get_html(),
|
||||||
|
"get_title": lambda: self.get_title(),
|
||||||
|
"get_url": lambda: self.get_url(),
|
||||||
|
"sequence": lambda: self.execute_sequence(task.get("steps", []))
|
||||||
|
}
|
||||||
|
|
||||||
|
if action not in actions_map:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": f"Unknown action: {action}. Available: {', '.join(actions_map.keys())}"
|
||||||
|
}
|
||||||
|
|
||||||
|
return await actions_map[action]()
|
||||||
|
|
||||||
|
|
||||||
|
async def run_from_args():
|
||||||
|
"""Запуск из аргументов командной строки"""
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print(json.dumps({
|
||||||
|
"success": False,
|
||||||
|
"error": "No task provided. Usage: python3 browser_automation.py '<JSON_TASK>'"
|
||||||
|
}))
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
task = json.loads(sys.argv[1])
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
# Если не JSON, пробуем как goto команду
|
||||||
|
task = {"action": "goto", "url": sys.argv[1]}
|
||||||
|
|
||||||
|
# Определяем режим headless (можно переопределить через переменную окружения)
|
||||||
|
headless = os.environ.get("BROWSER_HEADLESS", "true").lower() == "true"
|
||||||
|
|
||||||
|
async with BrowserAutomation(headless=headless) as browser:
|
||||||
|
result = await browser.execute_task(task)
|
||||||
|
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(run_from_args())
|
||||||
2
scripts/requirements.txt
Normal file
2
scripts/requirements.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
playwright>=1.40.0,<2.0.0
|
||||||
|
browser-use>=0.1.0,<1.0.0
|
||||||
72
scripts/setup.sh
Normal file
72
scripts/setup.sh
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Setup script for BrowserUse skill
|
||||||
|
# Устанавливает зависимости и браузеры для Playwright
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "🔧 Installing BrowserUse skill dependencies..."
|
||||||
|
echo "================================================"
|
||||||
|
|
||||||
|
# Определяем цветной вывод
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Проверка Python
|
||||||
|
echo -n "Checking Python... "
|
||||||
|
if command -v python3 &> /dev/null; then
|
||||||
|
PYTHON_VERSION=$(python3 --version)
|
||||||
|
echo -e "${GREEN}OK${NC} ($PYTHON_VERSION)"
|
||||||
|
else
|
||||||
|
echo -e "${RED}FAILED${NC}"
|
||||||
|
echo "Python 3 is required but not installed."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Проверка pip
|
||||||
|
echo -n "Checking pip... "
|
||||||
|
if command -v pip3 &> /dev/null; then
|
||||||
|
echo -e "${GREEN}OK${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${RED}FAILED${NC}"
|
||||||
|
echo "pip3 is required but not installed."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Установка Python пакетов
|
||||||
|
echo ""
|
||||||
|
echo "📦 Installing Python packages..."
|
||||||
|
pip3 install --upgrade pip
|
||||||
|
pip3 install -r "$(dirname "$0")/requirements.txt"
|
||||||
|
|
||||||
|
# Установка браузеров Playwright
|
||||||
|
echo ""
|
||||||
|
echo "🌐 Installing Playwright browsers..."
|
||||||
|
python3 -m playwright install chromium
|
||||||
|
python3 -m playwright install-deps # Системные зависимости для Linux
|
||||||
|
|
||||||
|
# Проверка установки
|
||||||
|
echo ""
|
||||||
|
echo -n "✅ Verifying installation... "
|
||||||
|
if python3 -c "import playwright" 2>/dev/null; then
|
||||||
|
echo -e "${GREEN}OK${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${RED}FAILED${NC}"
|
||||||
|
echo "Playwright installation verification failed."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Создание временной директории для скриншотов
|
||||||
|
mkdir -p /tmp/browser-use-screenshots
|
||||||
|
echo "📁 Created screenshot directory: /tmp/browser-use-screenshots"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "================================================"
|
||||||
|
echo -e "${GREEN}✅ BrowserUse skill successfully installed!${NC}"
|
||||||
|
echo ""
|
||||||
|
echo "📖 Quick test:"
|
||||||
|
echo " python3 $(dirname "$0")/browser_automation.py '{\"action\":\"goto\",\"url\":\"https://example.com\"}'"
|
||||||
|
echo ""
|
||||||
|
echo "📚 For more examples, see SKILL.md"
|
||||||
|
echo "================================================"
|
||||||
Loading…
Add table
Add a link
Reference in a new issue