""" Инструменты агента для smolagents — macOS версия. Отличия от Windows: нативный bash, поиск через mdfind/Spotlight, запуск приложений через 'open -a', нет реестра. """ import os import subprocess import webbrowser import urllib.parse from loguru import logger from smolagents import tool from cosmo.memory import Memory _memory: Memory | None = None def set_memory(mem: Memory): global _memory _memory = mem # ------------------------------------------------------------------ # Инструменты # ------------------------------------------------------------------ @tool def run_shell(command: str) -> str: """ Выполнить команду в bash и получить вывод. На macOS доступны все стандартные unix-команды. Для запуска приложений используй: open -a "AppName" или open /path/to/app. Для поиска файлов: mdfind, find, which. Args: command: bash команда для выполнения """ logger.info(f"[run_shell] {command}") try: result = subprocess.run( ["bash", "-c", command], capture_output=True, text=True, timeout=20, encoding="utf-8", errors="replace", ) output = result.stdout.strip() stderr = result.stderr.strip() if result.returncode != 0 and stderr: return f"[returncode={result.returncode}]\nstdout: {output}\nstderr: {stderr}" return output if output else f"[выполнено, returncode={result.returncode}]" except subprocess.TimeoutExpired: return "[таймаут 20с]" except Exception as e: return f"[ошибка: {e}]" @tool def find_program(name: str) -> str: """ Найти программу или приложение на macOS по имени. Ищет в PATH, /Applications, ~/Applications и через Spotlight (mdfind). Args: name: имя программы, например 'webstorm', 'chrome', 'cursor' """ stem = name.strip() logger.info(f"[find_program] {stem}") steps = [ # which — ищет в PATH f"which {stem} 2>/dev/null", # Spotlight — самый надёжный способ найти .app f"mdfind 'kMDItemKind == \"Application\"' | grep -i '{stem}' | head -3", # /Applications напрямую f"find /Applications -maxdepth 2 -iname '*{stem}*.app' 2>/dev/null | head -3", # ~/Applications f"find ~/Applications -maxdepth 2 -iname '*{stem}*.app' 2>/dev/null | head -3", # Homebrew bin f"find /opt/homebrew/bin /usr/local/bin -iname '*{stem}*' 2>/dev/null | head -3", ] results = [] for cmd in steps: out = run_shell(cmd) if out and not out.startswith("[") and len(out) > 3: results.append(out.strip()) if results: return "Найдено:\n" + "\n".join(results) return f"Программа '{name}' не найдена." @tool def open_browser(url: str, search: bool = False) -> str: """ Открыть URL в браузере или выполнить поиск в Google. Args: url: полный URL (https://...) или поисковый запрос если search=True search: если True — выполнить поиск Google по тексту в url """ if search: url = "https://www.google.com/search?q=" + urllib.parse.quote(url) elif not url.startswith(("http://", "https://")): url = "https://" + url webbrowser.open(url) logger.info(f"[open_browser] {url}") return f"Открыт браузер: {url}" @tool def read_file(path: str) -> str: """ Прочитать содержимое текстового файла. Args: path: абсолютный путь к файлу """ try: with open(path, "r", encoding="utf-8", errors="replace") as f: return f.read(8000) except FileNotFoundError: return f"Файл не найден: {path}" except Exception as e: return f"Ошибка чтения: {e}" @tool def write_file(path: str, content: str) -> str: """ Записать текст в файл (создаёт или перезаписывает). Args: path: абсолютный путь к файлу content: содержимое файла """ try: dir_path = os.path.dirname(path) if dir_path: os.makedirs(dir_path, exist_ok=True) with open(path, "w", encoding="utf-8") as f: f.write(content) return f"Файл записан: {path}" except Exception as e: return f"Ошибка записи: {e}" @tool def memory_set(key: str, value: str) -> str: """ Сохранить факт в долгосрочную память ассистента. Используй для запоминания путей программ, предпочтений пользователя. Ключи: 'app.название', 'user.имя', 'pref.что-то'. Args: key: ключ факта, например 'app.webstorm' value: значение для сохранения """ if _memory is None: return "Память не инициализирована" _memory.set(key, value) return f"Запомнено: {key} = {value!r}" @tool def memory_get(key: str) -> str: """ Получить сохранённый факт из долгосрочной памяти по ключу. Args: key: ключ факта, например 'app.webstorm' """ if _memory is None: return "Память не инициализирована" value = _memory.get(key) return f"{key} = {value!r}" if value else f"Факт '{key}' не найден" @tool def memory_list(prefix: str = "") -> str: """ Показать все факты из памяти, опционально по префиксу. Args: prefix: префикс для фильтрации, например 'app.' """ if _memory is None: return "Память не инициализирована" facts = _memory.list_facts(prefix) if not facts: return "Память пуста" if not prefix else f"Нет фактов с префиксом '{prefix}'" return "\n".join(f"{k}: {v}" for k, v in sorted(facts.items())) ALL_TOOLS = [ run_shell, find_program, open_browser, read_file, write_file, memory_set, memory_get, memory_list, ]