diff --git a/gateway/platforms/base.py b/gateway/platforms/base.py index da619a6afe..ff92530a23 100644 --- a/gateway/platforms/base.py +++ b/gateway/platforms/base.py @@ -185,7 +185,13 @@ def proxy_kwargs_for_bot(proxy_url: str | None) -> dict: if not proxy_url: return {} if proxy_url.lower().startswith("socks"): - from aiohttp_socks import ProxyConnector + try: + from aiohttp_socks import ProxyConnector + except ImportError: + raise ImportError( + "aiohttp-socks is required for SOCKS proxy support. " + "Install with: pip install hermes-agent[messaging]" + ) from None connector = ProxyConnector.from_url(proxy_url, rdns=True) return {"connector": connector} @@ -210,7 +216,13 @@ def proxy_kwargs_for_aiohttp(proxy_url: str | None) -> tuple[dict, dict]: if not proxy_url: return {}, {} if proxy_url.lower().startswith("socks"): - from aiohttp_socks import ProxyConnector + try: + from aiohttp_socks import ProxyConnector + except ImportError: + raise ImportError( + "aiohttp-socks is required for SOCKS proxy support. " + "Install with: pip install hermes-agent[messaging]" + ) from None connector = ProxyConnector.from_url(proxy_url, rdns=True) return {"connector": connector}, {} diff --git a/gateway/platforms/discord.py b/gateway/platforms/discord.py index 98fced2131..1df1a9a381 100644 --- a/gateway/platforms/discord.py +++ b/gateway/platforms/discord.py @@ -1194,7 +1194,13 @@ class DiscordAdapter(BasePlatformAdapter): try: import base64 - from mutagen.oggopus import OggOpus + try: + from mutagen.oggopus import OggOpus + except ImportError: + raise ImportError( + "mutagen is required for Discord voice messages. " + "Install with: pip install hermes-agent[messaging]" + ) from None duration_secs = 5.0 try: diff --git a/hermes_cli/clipboard.py b/hermes_cli/clipboard.py index 7b616db5da..bc9d5b6ad2 100644 --- a/hermes_cli/clipboard.py +++ b/hermes_cli/clipboard.py @@ -395,7 +395,13 @@ def _wayland_save(dest: Path) -> bool: def _convert_to_png(path: Path) -> bool: """Convert an image file to PNG in-place (requires Pillow or ImageMagick).""" - from PIL import Image + try: + from PIL import Image + except ImportError: + raise ImportError( + "Pillow is required for clipboard image conversion. " + "Install with: pip install hermes-agent[cli]" + ) from None try: img = Image.open(path) img.save(path, "PNG") diff --git a/hermes_cli/tools_config.py b/hermes_cli/tools_config.py index afc99e56c5..3366bd37ec 100644 --- a/hermes_cli/tools_config.py +++ b/hermes_cli/tools_config.py @@ -754,7 +754,13 @@ def _estimate_tool_tokens() -> Dict[str, int]: if _tool_token_cache is not None: return _tool_token_cache - import tiktoken + try: + import tiktoken + except ImportError: + raise ImportError( + "tiktoken is required for tool token estimation. " + "Install with: pip install hermes-agent[cli]" + ) from None enc = tiktoken.get_encoding("cl100k_base") try: diff --git a/tools/mcp_tool.py b/tools/mcp_tool.py index a7c8313ad2..24ceaa36bf 100644 --- a/tools/mcp_tool.py +++ b/tools/mcp_tool.py @@ -1501,7 +1501,13 @@ def _snapshot_child_pids() -> set: pass # Fallback: psutil - import psutil + try: + import psutil + except ImportError: + raise ImportError( + "psutil is required for MCP child process tracking. " + "Install with: pip install hermes-agent[mcp]" + ) from None try: return {c.pid for c in psutil.Process(my_pid).children()} except psutil.Error: diff --git a/tools/neutts_synth.py b/tools/neutts_synth.py index 7d3c21e51a..992c4c47ea 100644 --- a/tools/neutts_synth.py +++ b/tools/neutts_synth.py @@ -71,7 +71,13 @@ def main(): ref_text = ref_text_path.read_text(encoding="utf-8").strip() - from neutts import NeuTTS + try: + from neutts import NeuTTS + except ImportError: + raise ImportError( + "neutts is required for local TTS synthesis. " + "Install with: pip install hermes-agent[tts-local]" + ) from None tts = NeuTTS( backbone_repo=args.model, @@ -86,7 +92,13 @@ def main(): out_path = Path(args.out) out_path.parent.mkdir(parents=True, exist_ok=True) - import soundfile as sf + try: + import soundfile as sf + except ImportError: + raise ImportError( + "soundfile is required for audio output. " + "Install with: pip install hermes-agent[tts-local]" + ) from None sf.write(str(out_path), wav, 24000) print(f"OK: {out_path}", file=sys.stderr) diff --git a/tools/process_registry.py b/tools/process_registry.py index bd85b1d2cf..37d3289e15 100644 --- a/tools/process_registry.py +++ b/tools/process_registry.py @@ -335,10 +335,17 @@ class ProcessRegistry: ) if use_pty: - if _IS_WINDOWS: - from winpty import PtyProcess as _PtyProcessCls - else: - from ptyprocess import PtyProcess as _PtyProcessCls + try: + if _IS_WINDOWS: + from winpty import PtyProcess as _PtyProcessCls + else: + from ptyprocess import PtyProcess as _PtyProcessCls + except ImportError: + pkg = "winpty" if _IS_WINDOWS else "ptyprocess" + raise ImportError( + f"{pkg} is required for PTY mode. " + "Install with: pip install hermes-agent[pty]" + ) from None try: user_shell = _find_shell() pty_env = _sanitize_subprocess_env(os.environ, env_vars) diff --git a/tools/vision_tools.py b/tools/vision_tools.py index cf9f5633c1..f553633230 100644 --- a/tools/vision_tools.py +++ b/tools/vision_tools.py @@ -318,7 +318,13 @@ def _resize_image_for_vision(image_path: Path, mime_type: Optional[str] = None, else: data_url = None # defer full encode; try Pillow resize first - from PIL import Image + try: + from PIL import Image + except ImportError: + raise ImportError( + "Pillow is required for image resizing. " + "Install with: pip install hermes-agent[cli]" + ) from None import io as _io logger.info("Image file is %.1f MB (estimated base64 %.1f MB, limit %.1f MB), auto-resizing...", diff --git a/tui_gateway/server.py b/tui_gateway/server.py index 62ce84b4fe..5b896f83b0 100644 --- a/tui_gateway/server.py +++ b/tui_gateway/server.py @@ -192,7 +192,13 @@ def _estimate_image_tokens(width: int, height: int) -> int: def _image_meta(path: Path) -> dict: - from PIL import Image + try: + from PIL import Image + except ImportError: + raise ImportError( + "Pillow is required for image metadata extraction. " + "Install with: pip install hermes-agent[cli]" + ) from None meta = {"name": path.name} try: