feat(browser): auto-launch Chrome when /browser connect finds no debugger
When /browser connect detects that port 9222 isn't open, it now: 1. Finds Chrome/Chromium/Brave/Edge on the system (macOS app bundles or Linux PATH lookup) 2. Launches it with --remote-debugging-port=9222 (detached) 3. Waits up to 5 seconds for the port to come up 4. Falls back to manual instructions if auto-launch fails This means GUI-only users can just type /browser connect without needing to know about terminal flags or Chrome launch commands.
This commit is contained in:
parent
9d1483c7e6
commit
73f39a7761
1 changed files with 93 additions and 22 deletions
115
cli.py
115
cli.py
|
|
@ -3455,6 +3455,49 @@ class HermesCLI:
|
||||||
self._background_tasks[task_id] = thread
|
self._background_tasks[task_id] = thread
|
||||||
thread.start()
|
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):
|
def _handle_browser_command(self, cmd: str):
|
||||||
"""Handle /browser connect|disconnect|status — manage live Chrome CDP connection."""
|
"""Handle /browser connect|disconnect|status — manage live Chrome CDP connection."""
|
||||||
import platform as _plat
|
import platform as _plat
|
||||||
|
|
@ -3471,8 +3514,6 @@ class HermesCLI:
|
||||||
connect_parts = cmd.strip().split(None, 2) # ["/browser", "connect", "ws://..."]
|
connect_parts = cmd.strip().split(None, 2) # ["/browser", "connect", "ws://..."]
|
||||||
cdp_url = connect_parts[2].strip() if len(connect_parts) > 2 else _DEFAULT_CDP
|
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
|
# Clear any existing browser sessions so the next tool call uses the new backend
|
||||||
try:
|
try:
|
||||||
from tools.browser_tool import cleanup_all_browsers
|
from tools.browser_tool import cleanup_all_browsers
|
||||||
|
|
@ -3481,38 +3522,68 @@ class HermesCLI:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
print()
|
print()
|
||||||
print("🌐 Browser connected to live Chrome via CDP")
|
|
||||||
print(f" Endpoint: {cdp_url}")
|
|
||||||
print()
|
|
||||||
|
|
||||||
# Platform-specific launch instructions
|
# Extract port for connectivity checks
|
||||||
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
|
|
||||||
_port = 9222
|
_port = 9222
|
||||||
try:
|
try:
|
||||||
_port = int(cdp_url.rsplit(":", 1)[-1].split("/")[0])
|
_port = int(cdp_url.rsplit(":", 1)[-1].split("/")[0])
|
||||||
except (ValueError, IndexError):
|
except (ValueError, IndexError):
|
||||||
pass
|
pass
|
||||||
try:
|
|
||||||
|
# Check if Chrome is already listening on the debug port
|
||||||
import socket
|
import socket
|
||||||
|
_already_open = False
|
||||||
|
try:
|
||||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
s.settimeout(1)
|
s.settimeout(1)
|
||||||
s.connect(("127.0.0.1", _port))
|
s.connect(("127.0.0.1", _port))
|
||||||
s.close()
|
s.close()
|
||||||
print(f" ✓ Port {_port} is open — Chrome is reachable")
|
_already_open = True
|
||||||
except (OSError, socket.timeout):
|
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()
|
print()
|
||||||
|
|
||||||
# Inject context message so the model knows
|
# Inject context message so the model knows
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue