diff --git a/cli.py b/cli.py index 8116f5fd..85566756 100755 --- a/cli.py +++ b/cli.py @@ -3455,6 +3455,49 @@ class HermesCLI: self._background_tasks[task_id] = thread thread.start() + @staticmethod + def _try_launch_chrome_debug(port: int, system: str) -> bool: + """Try to launch Chrome/Chromium with remote debugging enabled. + + Returns True if a launch command was executed (doesn't guarantee success). + """ + import shutil + import subprocess as _sp + + candidates = [] + if system == "Darwin": + # macOS: try common app bundle locations + for app in ( + "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", + "/Applications/Chromium.app/Contents/MacOS/Chromium", + "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser", + "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge", + ): + if os.path.isfile(app): + candidates.append(app) + else: + # Linux: try common binary names + for name in ("google-chrome", "google-chrome-stable", "chromium-browser", + "chromium", "brave-browser", "microsoft-edge"): + path = shutil.which(name) + if path: + candidates.append(path) + + if not candidates: + return False + + chrome = candidates[0] + try: + _sp.Popen( + [chrome, f"--remote-debugging-port={port}"], + stdout=_sp.DEVNULL, + stderr=_sp.DEVNULL, + start_new_session=True, # detach from terminal + ) + return True + except Exception: + return False + def _handle_browser_command(self, cmd: str): """Handle /browser connect|disconnect|status — manage live Chrome CDP connection.""" import platform as _plat @@ -3471,8 +3514,6 @@ class HermesCLI: connect_parts = cmd.strip().split(None, 2) # ["/browser", "connect", "ws://..."] cdp_url = connect_parts[2].strip() if len(connect_parts) > 2 else _DEFAULT_CDP - os.environ["BROWSER_CDP_URL"] = cdp_url - # Clear any existing browser sessions so the next tool call uses the new backend try: from tools.browser_tool import cleanup_all_browsers @@ -3481,38 +3522,68 @@ class HermesCLI: pass print() - print("🌐 Browser connected to live Chrome via CDP") - print(f" Endpoint: {cdp_url}") - print() - # Platform-specific launch instructions - sys_name = _plat.system() - if sys_name == "Darwin": - chrome_cmd = '/Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome --remote-debugging-port=9222' - elif sys_name == "Windows": - chrome_cmd = 'chrome.exe --remote-debugging-port=9222' - else: - chrome_cmd = "google-chrome --remote-debugging-port=9222" - - print(" If Chrome isn't running with remote debugging yet:") - print(f" $ {chrome_cmd}") - print() - - # Quick connectivity test + # Extract port for connectivity checks _port = 9222 try: _port = int(cdp_url.rsplit(":", 1)[-1].split("/")[0]) except (ValueError, IndexError): pass + + # Check if Chrome is already listening on the debug port + import socket + _already_open = False try: - import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(1) s.connect(("127.0.0.1", _port)) s.close() - print(f" ✓ Port {_port} is open — Chrome is reachable") + _already_open = True except (OSError, socket.timeout): - print(f" ⚠ Port {_port} is not open — launch Chrome with the command above first") + pass + + if _already_open: + print(f" ✓ Chrome is already listening on port {_port}") + elif cdp_url == _DEFAULT_CDP: + # Try to auto-launch Chrome with remote debugging + print(" Chrome isn't running with remote debugging — attempting to launch...") + _launched = self._try_launch_chrome_debug(_port, _plat.system()) + if _launched: + # Wait for the port to come up + import time as _time + for _wait in range(10): + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(1) + s.connect(("127.0.0.1", _port)) + s.close() + _already_open = True + break + except (OSError, socket.timeout): + _time.sleep(0.5) + if _already_open: + print(f" ✓ Chrome launched and listening on port {_port}") + else: + print(f" ⚠ Chrome launched but port {_port} isn't responding yet") + print(" You may need to close existing Chrome windows first and retry") + else: + print(f" ⚠ Could not auto-launch Chrome") + # Show manual instructions as fallback + sys_name = _plat.system() + if sys_name == "Darwin": + chrome_cmd = 'open -a "Google Chrome" --args --remote-debugging-port=9222' + elif sys_name == "Windows": + chrome_cmd = 'chrome.exe --remote-debugging-port=9222' + else: + chrome_cmd = "google-chrome --remote-debugging-port=9222" + print(f" Launch Chrome manually: {chrome_cmd}") + else: + print(f" ⚠ Port {_port} is not reachable at {cdp_url}") + + os.environ["BROWSER_CDP_URL"] = cdp_url + print() + print("🌐 Browser connected to live Chrome via CDP") + print(f" Endpoint: {cdp_url}") print() # Inject context message so the model knows