diff --git a/hermes_cli/config.py b/hermes_cli/config.py index 4f7811ca7c2..312d2734063 100644 --- a/hermes_cli/config.py +++ b/hermes_cli/config.py @@ -868,6 +868,13 @@ OPTIONAL_ENV_VARS = { "password": True, "category": "tool", }, + "FIRECRAWL_BROWSER_TTL": { + "description": "Firecrawl browser session TTL in seconds (optional, default 300)", + "prompt": "Browser session TTL (seconds)", + "tools": ["browser_navigate", "browser_click"], + "password": False, + "category": "tool", + }, "CAMOFOX_URL": { "description": "Camofox browser server URL for local anti-detection browsing (e.g. http://localhost:9377)", "prompt": "Camofox server URL", diff --git a/hermes_cli/setup.py b/hermes_cli/setup.py index 82a30b3caf2..5abde51ba48 100644 --- a/hermes_cli/setup.py +++ b/hermes_cli/setup.py @@ -657,7 +657,7 @@ def _print_setup_summary(config: dict, hermes_home): else: tool_status.append(("Web Search & Extract", False, "EXA_API_KEY, PARALLEL_API_KEY, FIRECRAWL_API_KEY/FIRECRAWL_API_URL, or TAVILY_API_KEY")) - # Browser tools (local Chromium, Camofox, Browserbase, or Browser Use) + # Browser tools (local Chromium, Camofox, Browserbase, Browser Use, or Firecrawl) browser_provider = subscription_features.browser.current_provider if subscription_features.browser.managed_by_nous: tool_status.append(("Browser Automation (Nous Browserbase)", True, None)) diff --git a/hermes_cli/tools_config.py b/hermes_cli/tools_config.py index 9c2088b1d47..8a28e2247c5 100644 --- a/hermes_cli/tools_config.py +++ b/hermes_cli/tools_config.py @@ -315,6 +315,15 @@ TOOL_CATEGORIES = { "browser_provider": "browser-use", "post_setup": "browserbase", }, + { + "name": "Firecrawl", + "tag": "Cloud browser with remote execution", + "env_vars": [ + {"key": "FIRECRAWL_API_KEY", "prompt": "Firecrawl API key", "url": "https://firecrawl.dev"}, + ], + "browser_provider": "firecrawl", + "post_setup": "browserbase", + }, { "name": "Camofox", "tag": "Local anti-detection browser (Firefox/Camoufox)", diff --git a/tools/browser_providers/firecrawl.py b/tools/browser_providers/firecrawl.py new file mode 100644 index 00000000000..c65c5296976 --- /dev/null +++ b/tools/browser_providers/firecrawl.py @@ -0,0 +1,113 @@ +"""Firecrawl cloud browser provider.""" + +import logging +import os +import uuid +from typing import Dict + +import requests + +from tools.browser_providers.base import CloudBrowserProvider + +logger = logging.getLogger(__name__) + +_BASE_URL = "https://api.firecrawl.dev" + + +class FirecrawlProvider(CloudBrowserProvider): + """Firecrawl (https://firecrawl.dev) cloud browser backend.""" + + def provider_name(self) -> str: + return "Firecrawl" + + def is_configured(self) -> bool: + return bool(os.environ.get("FIRECRAWL_API_KEY")) + + # ------------------------------------------------------------------ + # Session lifecycle + # ------------------------------------------------------------------ + + def _api_url(self) -> str: + return os.environ.get("FIRECRAWL_API_URL", _BASE_URL) + + def _headers(self) -> Dict[str, str]: + api_key = os.environ.get("FIRECRAWL_API_KEY") + if not api_key: + raise ValueError( + "FIRECRAWL_API_KEY environment variable is required. " + "Get your key at https://firecrawl.dev" + ) + return { + "Content-Type": "application/json", + "Authorization": f"Bearer {api_key}", + } + + def create_session(self, task_id: str) -> Dict[str, object]: + ttl = int(os.environ.get("FIRECRAWL_BROWSER_TTL", "300")) + + body: Dict[str, object] = {"ttl": ttl} + + response = requests.post( + f"{self._api_url()}/v2/browser", + headers=self._headers(), + json=body, + timeout=30, + ) + + if not response.ok: + raise RuntimeError( + f"Failed to create Firecrawl browser session: " + f"{response.status_code} {response.text}" + ) + + data = response.json() + session_name = f"hermes_{task_id}_{uuid.uuid4().hex[:8]}" + + logger.info("Created Firecrawl browser session %s", session_name) + + return { + "session_name": session_name, + "bb_session_id": data["id"], + "cdp_url": data["cdpUrl"], + "features": {"firecrawl": True}, + } + + def close_session(self, session_id: str) -> bool: + try: + response = requests.delete( + f"{self._api_url()}/v2/browser/{session_id}", + headers=self._headers(), + timeout=10, + ) + if response.status_code in (200, 201, 204): + logger.debug("Successfully closed Firecrawl session %s", session_id) + return True + else: + logger.warning( + "Failed to close Firecrawl session %s: HTTP %s - %s", + session_id, + response.status_code, + response.text[:200], + ) + return False + except Exception as e: + logger.error("Exception closing Firecrawl session %s: %s", session_id, e) + return False + + def emergency_cleanup(self, session_id: str) -> None: + api_key = os.environ.get("FIRECRAWL_API_KEY") + if not api_key: + logger.warning("Cannot emergency-cleanup Firecrawl session %s — missing credentials", session_id) + return + api_url = os.environ.get("FIRECRAWL_API_URL", _BASE_URL) + try: + requests.delete( + f"{api_url}/v2/browser/{session_id}", + headers={ + "Content-Type": "application/json", + "Authorization": f"Bearer {api_key}", + }, + timeout=5, + ) + except Exception as e: + logger.debug("Emergency cleanup failed for Firecrawl session %s: %s", session_id, e) diff --git a/tools/browser_tool.py b/tools/browser_tool.py index 8a495bed69e..a6043e0bf33 100644 --- a/tools/browser_tool.py +++ b/tools/browser_tool.py @@ -79,6 +79,7 @@ except Exception: from tools.browser_providers.base import CloudBrowserProvider from tools.browser_providers.browserbase import BrowserbaseProvider from tools.browser_providers.browser_use import BrowserUseProvider +from tools.browser_providers.firecrawl import FirecrawlProvider from tools.tool_backend_helpers import normalize_browser_cloud_provider # Camofox local anti-detection browser backend (optional). @@ -235,6 +236,7 @@ def _get_cdp_override() -> str: _PROVIDER_REGISTRY: Dict[str, type] = { "browserbase": BrowserbaseProvider, "browser-use": BrowserUseProvider, + "firecrawl": FirecrawlProvider, } _cached_cloud_provider: Optional[CloudBrowserProvider] = None @@ -2036,12 +2038,12 @@ def check_browser_requirements() -> bool: """ Check if browser tool requirements are met. - In **local mode** (no Browserbase credentials): only the ``agent-browser`` - CLI must be findable. + In **local mode** (no cloud provider configured): only the + ``agent-browser`` CLI must be findable. + + In **cloud mode** (Browserbase, Browser Use, or Firecrawl): the CLI + *and* the provider's required credentials must be present. - In **cloud mode** (BROWSERBASE_API_KEY set): the CLI *and* both - ``BROWSERBASE_API_KEY`` / ``BROWSERBASE_PROJECT_ID`` must be present. - Returns: True if all requirements are met, False otherwise """ diff --git a/website/docs/reference/environment-variables.md b/website/docs/reference/environment-variables.md index 8917072a491..fb2a6752364 100644 --- a/website/docs/reference/environment-variables.md +++ b/website/docs/reference/environment-variables.md @@ -77,13 +77,14 @@ For native Anthropic auth, Hermes prefers Claude Code's own credential files whe | Variable | Description | |----------|-------------| | `PARALLEL_API_KEY` | AI-native web search ([parallel.ai](https://parallel.ai/)) | -| `FIRECRAWL_API_KEY` | Web scraping ([firecrawl.dev](https://firecrawl.dev/)) | +| `FIRECRAWL_API_KEY` | Web scraping and cloud browser ([firecrawl.dev](https://firecrawl.dev/)) | | `FIRECRAWL_API_URL` | Custom Firecrawl API endpoint for self-hosted instances (optional) | | `TAVILY_API_KEY` | Tavily API key for AI-native web search, extract, and crawl ([app.tavily.com](https://app.tavily.com/home)) | | `EXA_API_KEY` | Exa API key for AI-native web search and contents ([exa.ai](https://exa.ai/)) | | `BROWSERBASE_API_KEY` | Browser automation ([browserbase.com](https://browserbase.com/)) | | `BROWSERBASE_PROJECT_ID` | Browserbase project ID | | `BROWSER_USE_API_KEY` | Browser Use cloud browser API key ([browser-use.com](https://browser-use.com/)) | +| `FIRECRAWL_BROWSER_TTL` | Firecrawl browser session TTL in seconds (default: 300) | | `BROWSER_CDP_URL` | Chrome DevTools Protocol URL for local browser (set via `/browser connect`, e.g. `ws://localhost:9222`) | | `CAMOFOX_URL` | Camofox local anti-detection browser URL (default: `http://localhost:9377`) | | `BROWSER_INACTIVITY_TIMEOUT` | Browser session inactivity timeout in seconds | diff --git a/website/docs/user-guide/features/browser.md b/website/docs/user-guide/features/browser.md index 10a6ccee8c8..8f9fc24ebb6 100644 --- a/website/docs/user-guide/features/browser.md +++ b/website/docs/user-guide/features/browser.md @@ -11,6 +11,7 @@ Hermes Agent includes a full browser automation toolset with multiple backend op - **Browserbase cloud mode** via [Browserbase](https://browserbase.com) for managed cloud browsers and anti-bot tooling - **Browser Use cloud mode** via [Browser Use](https://browser-use.com) as an alternative cloud browser provider +- **Firecrawl cloud mode** via [Firecrawl](https://firecrawl.dev) for cloud browsers with built-in scraping - **Camofox local mode** via [Camofox](https://github.com/jo-inc/camofox-browser) for local anti-detection browsing (Firefox-based fingerprint spoofing) - **Local Chrome via CDP** — connect browser tools to your own Chrome instance using `/browser connect` - **Local browser mode** via the `agent-browser` CLI and a local Chromium installation @@ -23,7 +24,7 @@ Pages are represented as **accessibility trees** (text-based snapshots), making Key capabilities: -- **Multi-provider cloud execution** — Browserbase or Browser Use, no local browser needed +- **Multi-provider cloud execution** — Browserbase, Browser Use, or Firecrawl — no local browser needed - **Local Chrome integration** — attach to your running Chrome via CDP for hands-on browsing - **Built-in stealth** — random fingerprints, CAPTCHA solving, residential proxies (Browserbase) - **Session isolation** — each task gets its own browser session @@ -55,6 +56,32 @@ BROWSER_USE_API_KEY=*** Get your API key at [browser-use.com](https://browser-use.com). Browser Use provides a cloud browser via its REST API. If both Browserbase and Browser Use credentials are set, Browserbase takes priority. +### Firecrawl cloud mode + +To use Firecrawl as your cloud browser provider, add: + +```bash +# Add to ~/.hermes/.env +FIRECRAWL_API_KEY=fc-*** +``` + +Get your API key at [firecrawl.dev](https://firecrawl.dev). Then select Firecrawl as your browser provider: + +```bash +hermes setup tools +# → Browser Automation → Firecrawl +``` + +Optional settings: + +```bash +# Self-hosted Firecrawl instance (default: https://api.firecrawl.dev) +FIRECRAWL_API_URL=http://localhost:3002 + +# Session TTL in seconds (default: 300) +FIRECRAWL_BROWSER_TTL=600 +``` + ### Camofox local mode [Camofox](https://github.com/jo-inc/camofox-browser) is a self-hosted Node.js server wrapping Camoufox (a Firefox fork with C++ fingerprint spoofing). It provides local anti-detection browsing without cloud dependencies.