browser-tools 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. browser_tools-0.1.0/.gitignore +29 -0
  2. browser_tools-0.1.0/LICENSE +21 -0
  3. browser_tools-0.1.0/PKG-INFO +153 -0
  4. browser_tools-0.1.0/README.md +121 -0
  5. browser_tools-0.1.0/pyproject.toml +107 -0
  6. browser_tools-0.1.0/src/browser_tools/__init__.py +22 -0
  7. browser_tools-0.1.0/src/browser_tools/attach_chrome.sh +124 -0
  8. browser_tools-0.1.0/src/browser_tools/browser_state.py +157 -0
  9. browser_tools-0.1.0/src/browser_tools/browser_tools_session.py +1132 -0
  10. browser_tools-0.1.0/src/browser_tools/camoufox_session.py +332 -0
  11. browser_tools-0.1.0/src/browser_tools/cdp_client.py +253 -0
  12. browser_tools-0.1.0/src/browser_tools/cdp_constants.py +152 -0
  13. browser_tools-0.1.0/src/browser_tools/cdp_handler.py +1555 -0
  14. browser_tools-0.1.0/src/browser_tools/chrome_config.py +415 -0
  15. browser_tools-0.1.0/src/browser_tools/chrome_utils.py +559 -0
  16. browser_tools-0.1.0/src/browser_tools/daemon_client.py +125 -0
  17. browser_tools-0.1.0/src/browser_tools/detect_interstitial.js +267 -0
  18. browser_tools-0.1.0/src/browser_tools/frame_manager.py +408 -0
  19. browser_tools-0.1.0/src/browser_tools/interstitial.py +202 -0
  20. browser_tools-0.1.0/src/browser_tools/mcp_daemon.py +753 -0
  21. browser_tools-0.1.0/src/browser_tools/mcp_session.py +202 -0
  22. browser_tools-0.1.0/src/browser_tools/persistent-session-template.mjs +132 -0
  23. browser_tools-0.1.0/src/browser_tools/persistent_browser.py +1309 -0
  24. browser_tools-0.1.0/src/browser_tools/process_utils.py +373 -0
  25. browser_tools-0.1.0/src/browser_tools/profiler.py +289 -0
  26. browser_tools-0.1.0/src/browser_tools/screenshot_utils.py +126 -0
  27. browser_tools-0.1.0/src/browser_tools/stealth.js +179 -0
  28. browser_tools-0.1.0/tests/conftest.py +95 -0
  29. browser_tools-0.1.0/tests/test_attach_browser.py +706 -0
  30. browser_tools-0.1.0/tests/test_browser_tools.py +418 -0
  31. browser_tools-0.1.0/tests/test_camoufox.py +257 -0
  32. browser_tools-0.1.0/tests/test_cdp_client.py +359 -0
  33. browser_tools-0.1.0/tests/test_e2e_camoufox.py +118 -0
  34. browser_tools-0.1.0/tests/test_frame_manager.py +293 -0
  35. browser_tools-0.1.0/tests/test_inspect_mode.py +141 -0
  36. browser_tools-0.1.0/tests/test_interstitial.py +490 -0
  37. browser_tools-0.1.0/tests/test_mcp_daemon.py +228 -0
  38. browser_tools-0.1.0/tests/test_new_tools.py +864 -0
  39. browser_tools-0.1.0/tests/test_persistent_browser.py +499 -0
  40. browser_tools-0.1.0/tests/test_profiles.py +186 -0
@@ -0,0 +1,29 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ dist/
6
+ *.egg
7
+
8
+ # Virtual environments
9
+ .venv/
10
+ venv/
11
+
12
+ # Test / coverage
13
+ .pytest_cache/
14
+ .coverage
15
+ htmlcov/
16
+
17
+ # Ruff
18
+ .ruff_cache/
19
+
20
+ # IDE
21
+ .vscode/
22
+ .idea/
23
+
24
+ # OS
25
+ .DS_Store
26
+ Thumbs.db
27
+
28
+ # Build
29
+ *.whl
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Browser Tools contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,153 @@
1
+ Metadata-Version: 2.4
2
+ Name: browser-tools
3
+ Version: 0.1.0
4
+ Summary: Browser automation, debugging, and anti-detect browsing CLI
5
+ Project-URL: Homepage, https://github.com/dungle-scrubs/browser-tools
6
+ Project-URL: Repository, https://github.com/dungle-scrubs/browser-tools
7
+ Project-URL: Issues, https://github.com/dungle-scrubs/browser-tools/issues
8
+ Project-URL: Changelog, https://github.com/dungle-scrubs/browser-tools/blob/main/CHANGELOG.md
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: anti-detect,browser-automation,camoufox,cdp,chrome-devtools,mcp,playwright,web-scraping
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: MacOS
17
+ Classifier: Operating System :: POSIX :: Linux
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3 :: Only
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Internet :: WWW/HTTP :: Browsers
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Classifier: Topic :: Software Development :: Testing
24
+ Classifier: Topic :: Utilities
25
+ Classifier: Typing :: Typed
26
+ Requires-Python: >=3.13
27
+ Requires-Dist: aiohttp>=3.14.1
28
+ Requires-Dist: camoufox[geoip]>=0.4.11
29
+ Requires-Dist: pillow>=10.0.0
30
+ Requires-Dist: websockets>=14.0
31
+ Description-Content-Type: text/markdown
32
+
33
+ # Browser Tools
34
+
35
+ [![CI](https://github.com/dungle-scrubs/browser-tools/actions/workflows/ci.yml/badge.svg)](https://github.com/dungle-scrubs/browser-tools/actions/workflows/ci.yml)
36
+ [![Python 3.13+](https://img.shields.io/badge/python-3.13+-blue.svg)](https://www.python.org/downloads/)
37
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
38
+ [![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://docs.astral.sh/ruff/)
39
+
40
+ Browser automation, debugging, and anti-detect browsing CLI. Provides:
41
+
42
+ - **Chrome DevTools MCP wrapper** — Snapshot-based page automation via
43
+ [chrome-devtools-mcp](https://github.com/anthropics/chrome-devtools-mcp)
44
+ - **Persistent browser sessions** — Long-lived Chrome instances with named profiles,
45
+ daemon-based MCP reuse, and attach-to-running-browser support
46
+ - **Frame-aware tools** — Iframe/CDP frame tree management, execution context
47
+ resolution, and storage inspection
48
+ - **Interstitial detection** — Multi-signal heuristic detection for Cloudflare,
49
+ DataDome, Akamai, PerimeterX, Imperva, AWS WAF, and other challenge pages
50
+ - **Camoufox anti-detect browsing** — Fingerprint-injected Firefox-based
51
+ browsing for bot-protected sites
52
+ - **CPU profiling** — Direct CDP-based JavaScript CPU profiling with
53
+ threshold-triggered capture
54
+
55
+ ## Quick Start
56
+
57
+ ### Installation
58
+
59
+ ```bash
60
+ # Using uv (recommended)
61
+ uv tool install browser-tools
62
+
63
+ # Or with pip
64
+ pip install browser-tools
65
+ ```
66
+
67
+ ### Usage
68
+
69
+ ```bash
70
+ # Run the CLI
71
+ browser-tools --help
72
+
73
+ # Take a snapshot of a page
74
+ browser-tools --headless navigate --url https://example.com
75
+ browser-tools --headless take-snapshot
76
+
77
+ # Use a persistent profile
78
+ browser-tools --profile dev navigate --url https://example.com
79
+ ```
80
+
81
+ ### Development setup
82
+
83
+ ```bash
84
+ # Clone and install in editable mode
85
+ git clone https://github.com/dungle-scrubs/browser-tools.git
86
+ cd browser-tools
87
+ uv sync
88
+ ```
89
+
90
+ Runtime requirements:
91
+
92
+ - **Chrome** or **Chrome Canary** for DevTools automation
93
+ - **Node.js** (>=18) for `chrome-devtools-mcp`
94
+ - **Camoufox** (`camoufox fetch`) for anti-detect Firefox workflows
95
+
96
+ ## Architecture
97
+
98
+ ```
99
+ browser_tools_session.py CLI entry point
100
+ |
101
+ +-- chrome_config.py Tool schemas & validation
102
+ +-- chrome_utils.py MCP subprocess invocation, formatting
103
+ +-- persistent_browser.py Chrome lifecycle, daemon, profiles
104
+ | +-- browser_state.py Persisted state dataclasses
105
+ | +-- mcp_session.py Short-lived MCP session wrapper
106
+ | +-- daemon_client.py Unix socket client
107
+ | +-- process_utils.py Chrome process/port utilities
108
+ | +-- mcp_daemon.py Long-lived MCP daemon
109
+ | +-- cdp_handler.py CDP tool implementations
110
+ | +-- cdp_constants.py CDP toolset definitions
111
+ | +-- cdp_client.py CDP WebSocket client
112
+ | +-- frame_manager.py Frame tree management
113
+ | +-- interstitial.py Challenge detection
114
+ | +-- screenshot_utils.py Blank-frame detection
115
+ +-- camoufox_session.py Camoufox anti-detect wrapper
116
+ +-- profiler.py Standalone CPU profiler
117
+ ```
118
+
119
+ ## Development
120
+
121
+ ```bash
122
+ uv sync
123
+ uv run ruff check src/ tests/
124
+ uv run pytest
125
+ ```
126
+
127
+ ### Project Configuration
128
+
129
+ Place a `.browser-tools.json` in your project root:
130
+
131
+ ```json
132
+ {
133
+ "preferredSession": {
134
+ "mode": "headed-auth",
135
+ "profile": "dev"
136
+ }
137
+ }
138
+ ```
139
+
140
+ This auto-selects a persistent headed browser session using the named profile.
141
+
142
+ ## License
143
+
144
+ MIT — see [LICENSE](LICENSE).
145
+
146
+ ## Contributing
147
+
148
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and guidelines.
149
+ All contributors are expected to follow the [Code of Conduct](CODE_OF_CONDUCT.md).
150
+
151
+ ## Security
152
+
153
+ Found a vulnerability? See [SECURITY.md](SECURITY.md) for responsible disclosure.
@@ -0,0 +1,121 @@
1
+ # Browser Tools
2
+
3
+ [![CI](https://github.com/dungle-scrubs/browser-tools/actions/workflows/ci.yml/badge.svg)](https://github.com/dungle-scrubs/browser-tools/actions/workflows/ci.yml)
4
+ [![Python 3.13+](https://img.shields.io/badge/python-3.13+-blue.svg)](https://www.python.org/downloads/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+ [![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://docs.astral.sh/ruff/)
7
+
8
+ Browser automation, debugging, and anti-detect browsing CLI. Provides:
9
+
10
+ - **Chrome DevTools MCP wrapper** — Snapshot-based page automation via
11
+ [chrome-devtools-mcp](https://github.com/anthropics/chrome-devtools-mcp)
12
+ - **Persistent browser sessions** — Long-lived Chrome instances with named profiles,
13
+ daemon-based MCP reuse, and attach-to-running-browser support
14
+ - **Frame-aware tools** — Iframe/CDP frame tree management, execution context
15
+ resolution, and storage inspection
16
+ - **Interstitial detection** — Multi-signal heuristic detection for Cloudflare,
17
+ DataDome, Akamai, PerimeterX, Imperva, AWS WAF, and other challenge pages
18
+ - **Camoufox anti-detect browsing** — Fingerprint-injected Firefox-based
19
+ browsing for bot-protected sites
20
+ - **CPU profiling** — Direct CDP-based JavaScript CPU profiling with
21
+ threshold-triggered capture
22
+
23
+ ## Quick Start
24
+
25
+ ### Installation
26
+
27
+ ```bash
28
+ # Using uv (recommended)
29
+ uv tool install browser-tools
30
+
31
+ # Or with pip
32
+ pip install browser-tools
33
+ ```
34
+
35
+ ### Usage
36
+
37
+ ```bash
38
+ # Run the CLI
39
+ browser-tools --help
40
+
41
+ # Take a snapshot of a page
42
+ browser-tools --headless navigate --url https://example.com
43
+ browser-tools --headless take-snapshot
44
+
45
+ # Use a persistent profile
46
+ browser-tools --profile dev navigate --url https://example.com
47
+ ```
48
+
49
+ ### Development setup
50
+
51
+ ```bash
52
+ # Clone and install in editable mode
53
+ git clone https://github.com/dungle-scrubs/browser-tools.git
54
+ cd browser-tools
55
+ uv sync
56
+ ```
57
+
58
+ Runtime requirements:
59
+
60
+ - **Chrome** or **Chrome Canary** for DevTools automation
61
+ - **Node.js** (>=18) for `chrome-devtools-mcp`
62
+ - **Camoufox** (`camoufox fetch`) for anti-detect Firefox workflows
63
+
64
+ ## Architecture
65
+
66
+ ```
67
+ browser_tools_session.py CLI entry point
68
+ |
69
+ +-- chrome_config.py Tool schemas & validation
70
+ +-- chrome_utils.py MCP subprocess invocation, formatting
71
+ +-- persistent_browser.py Chrome lifecycle, daemon, profiles
72
+ | +-- browser_state.py Persisted state dataclasses
73
+ | +-- mcp_session.py Short-lived MCP session wrapper
74
+ | +-- daemon_client.py Unix socket client
75
+ | +-- process_utils.py Chrome process/port utilities
76
+ | +-- mcp_daemon.py Long-lived MCP daemon
77
+ | +-- cdp_handler.py CDP tool implementations
78
+ | +-- cdp_constants.py CDP toolset definitions
79
+ | +-- cdp_client.py CDP WebSocket client
80
+ | +-- frame_manager.py Frame tree management
81
+ | +-- interstitial.py Challenge detection
82
+ | +-- screenshot_utils.py Blank-frame detection
83
+ +-- camoufox_session.py Camoufox anti-detect wrapper
84
+ +-- profiler.py Standalone CPU profiler
85
+ ```
86
+
87
+ ## Development
88
+
89
+ ```bash
90
+ uv sync
91
+ uv run ruff check src/ tests/
92
+ uv run pytest
93
+ ```
94
+
95
+ ### Project Configuration
96
+
97
+ Place a `.browser-tools.json` in your project root:
98
+
99
+ ```json
100
+ {
101
+ "preferredSession": {
102
+ "mode": "headed-auth",
103
+ "profile": "dev"
104
+ }
105
+ }
106
+ ```
107
+
108
+ This auto-selects a persistent headed browser session using the named profile.
109
+
110
+ ## License
111
+
112
+ MIT — see [LICENSE](LICENSE).
113
+
114
+ ## Contributing
115
+
116
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and guidelines.
117
+ All contributors are expected to follow the [Code of Conduct](CODE_OF_CONDUCT.md).
118
+
119
+ ## Security
120
+
121
+ Found a vulnerability? See [SECURITY.md](SECURITY.md) for responsible disclosure.
@@ -0,0 +1,107 @@
1
+ [project]
2
+ name = "browser-tools"
3
+ version = "0.1.0"
4
+ description = "Browser automation, debugging, and anti-detect browsing CLI"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ requires-python = ">=3.13"
8
+ keywords = [
9
+ "browser-automation",
10
+ "chrome-devtools",
11
+ "cdp",
12
+ "anti-detect",
13
+ "camoufox",
14
+ "playwright",
15
+ "web-scraping",
16
+ "mcp",
17
+ ]
18
+ classifiers = [
19
+ "Development Status :: 3 - Alpha",
20
+ "Environment :: Console",
21
+ "Intended Audience :: Developers",
22
+ "License :: OSI Approved :: MIT License",
23
+ "Operating System :: MacOS",
24
+ "Operating System :: POSIX :: Linux",
25
+ "Programming Language :: Python :: 3",
26
+ "Programming Language :: Python :: 3.13",
27
+ "Programming Language :: Python :: 3 :: Only",
28
+ "Topic :: Internet :: WWW/HTTP :: Browsers",
29
+ "Topic :: Software Development :: Libraries :: Python Modules",
30
+ "Topic :: Software Development :: Testing",
31
+ "Topic :: Utilities",
32
+ "Typing :: Typed",
33
+ ]
34
+ dependencies = [
35
+ "camoufox[geoip]>=0.4.11",
36
+ "pillow>=10.0.0",
37
+ "websockets>=14.0",
38
+ # Security overrides for transitive dependencies.
39
+ # camoufox pulls in aiohttp 3.13.x which has 11 known CVEs (PYSEC-2026-237, etc.).
40
+ # Force >=3.14.1 until camoufox updates its own pin.
41
+ "aiohttp>=3.14.1",
42
+ ]
43
+
44
+ [project.urls]
45
+ Homepage = "https://github.com/dungle-scrubs/browser-tools"
46
+ Repository = "https://github.com/dungle-scrubs/browser-tools"
47
+ Issues = "https://github.com/dungle-scrubs/browser-tools/issues"
48
+ Changelog = "https://github.com/dungle-scrubs/browser-tools/blob/main/CHANGELOG.md"
49
+
50
+ [project.scripts]
51
+ browser-tools = "browser_tools.browser_tools_session:main"
52
+ browser-tools-profiler = "browser_tools.profiler:main"
53
+
54
+ [build-system]
55
+ requires = ["hatchling"]
56
+ build-backend = "hatchling.build"
57
+
58
+ [tool.hatch.build.targets.wheel]
59
+ packages = ["src/browser_tools"]
60
+ artifacts = [
61
+ "src/browser_tools/*.js",
62
+ "src/browser_tools/*.mjs",
63
+ "src/browser_tools/*.sh",
64
+ ]
65
+
66
+ [tool.hatch.build.targets.sdist]
67
+ include = [
68
+ "src/browser_tools",
69
+ "tests",
70
+ "README.md",
71
+ ]
72
+
73
+ [dependency-groups]
74
+ dev = [
75
+ "pyright>=1.1.0",
76
+ "pytest>=8.0.0",
77
+ "pytest-asyncio>=1.3.0",
78
+ "ruff>=0.8.0",
79
+ ]
80
+
81
+ [tool.pyright]
82
+ typeCheckingMode = "strict"
83
+ pythonVersion = "3.13"
84
+ reportMissingTypeStubs = false
85
+ reportUnknownMemberType = false
86
+ reportUnknownArgumentType = false
87
+ reportUnknownVariableType = false
88
+ reportUnknownParameterType = false
89
+
90
+ [tool.ruff]
91
+ line-length = 100
92
+ target-version = "py313"
93
+
94
+ [tool.ruff.lint]
95
+ select = [
96
+ "E", "W", "F", "I", # pycodestyle + pyflakes + isort
97
+ "B", # flake8-bugbear
98
+ "C4", # flake8-comprehensions
99
+ "RUF", # ruff-specific rules
100
+ "SIM", # flake8-simplify
101
+ "TCH", # flake8-type-checking
102
+ "UP", # pyupgrade
103
+ ]
104
+ ignore = ["E501", "E731", "F841"]
105
+
106
+ [tool.ruff.lint.isort]
107
+ known-first-party = ["browser_tools"]
@@ -0,0 +1,22 @@
1
+ """Browser Tools automation package."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .browser_state import ActiveAttachConfig, BrowserState, ProjectBrowserConfig
6
+ from .camoufox_session import CamoufoxSession
7
+ from .cdp_client import CDPClient, CDPError
8
+ from .frame_manager import FrameManager
9
+ from .persistent_browser import PersistentChromeController
10
+
11
+ __all__ = [
12
+ "ActiveAttachConfig",
13
+ "BrowserState",
14
+ "CDPClient",
15
+ "CDPError",
16
+ "CamoufoxSession",
17
+ "FrameManager",
18
+ "PersistentChromeController",
19
+ "ProjectBrowserConfig",
20
+ "__version__",
21
+ ]
22
+ __version__ = "0.1.0"
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env bash
2
+ # attach-chrome.sh — Launch Chrome with remote debugging for browser-tools attach
3
+ #
4
+ # Usage:
5
+ # ./attach-chrome.sh [--port PORT] [--profile PROFILE] [URL]
6
+ #
7
+ # Examples:
8
+ # ./attach-chrome.sh # Default port 9222
9
+ # ./attach-chrome.sh --port 9333 # Custom port
10
+ # ./attach-chrome.sh --profile dev # Named profile
11
+ # ./attach-chrome.sh https://myapp.localhost # Open specific URL
12
+
13
+ set -euo pipefail
14
+
15
+ PORT=9222
16
+ PROFILE=""
17
+ URL=""
18
+ CHROME_CANARY="/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary"
19
+ CHROME_STABLE="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
20
+
21
+ while [[ $# -gt 0 ]]; do
22
+ case "$1" in
23
+ --port)
24
+ PORT="$2"
25
+ shift 2
26
+ ;;
27
+ --profile)
28
+ PROFILE="$2"
29
+ shift 2
30
+ ;;
31
+ --help|-h)
32
+ echo "Usage: $0 [--port PORT] [--profile PROFILE] [URL]"
33
+ echo ""
34
+ echo "Launch Chrome with remote debugging enabled for browser-tools."
35
+ echo ""
36
+ echo "Options:"
37
+ echo " --port PORT Remote debugging port (default: 9222)"
38
+ echo " --profile NAME Named profile for persistent sessions"
39
+ echo " URL URL to open on launch"
40
+ echo ""
41
+ echo "Then in your agent, use:"
42
+ echo ' attach_browser(endpoint="http://127.0.0.1:PORT")'
43
+ exit 0
44
+ ;;
45
+ *)
46
+ URL="$1"
47
+ shift
48
+ ;;
49
+ esac
50
+ done
51
+
52
+ # Find Chrome executable
53
+ CHROME=""
54
+ if [[ -x "$CHROME_CANARY" ]]; then
55
+ CHROME="$CHROME_CANARY"
56
+ elif [[ -x "$CHROME_STABLE" ]]; then
57
+ CHROME="$CHROME_STABLE"
58
+ elif command -v google-chrome &>/dev/null; then
59
+ CHROME="google-chrome"
60
+ elif command -v chromium &>/dev/null; then
61
+ CHROME="chromium"
62
+ else
63
+ echo "Error: Chrome not found. Install Chrome or Chrome Canary." >&2
64
+ exit 1
65
+ fi
66
+
67
+ # Build user-data-dir path
68
+ CACHE_DIR="$HOME/.cache/tool-proxy/browser-tools"
69
+ if [[ -n "$PROFILE" ]]; then
70
+ USER_DATA_DIR="$CACHE_DIR/profiles/$PROFILE"
71
+ else
72
+ USER_DATA_DIR="$CACHE_DIR/profiles/attach-$PORT"
73
+ fi
74
+
75
+ mkdir -p "$USER_DATA_DIR"
76
+ chmod 700 "$USER_DATA_DIR"
77
+
78
+ echo "Launching Chrome with remote debugging on port $PORT..."
79
+ echo " Executable: $CHROME"
80
+ echo " Profile: $USER_DATA_DIR"
81
+
82
+ ARGS=(
83
+ "--remote-debugging-port=$PORT"
84
+ "--user-data-dir=$USER_DATA_DIR"
85
+ "--no-first-run"
86
+ "--no-default-browser-check"
87
+ "--disable-sync"
88
+ )
89
+
90
+ if [[ -n "$URL" ]]; then
91
+ ARGS+=("$URL")
92
+ fi
93
+
94
+ # Detach Chrome into a new session so it survives if our parent shell is
95
+ # killed (e.g. an agent's Bash tool call hits its timeout). nohup alone is
96
+ # not enough on macOS: when the shell's process group is signaled, the
97
+ # child dies with it. Python's start_new_session=True calls os.setsid()
98
+ # in the child before exec, putting Chrome in its own session/process group.
99
+ python3 - "$CHROME" "${ARGS[@]}" <<'PY'
100
+ import subprocess, sys
101
+ subprocess.Popen(
102
+ sys.argv[1:],
103
+ stdin=subprocess.DEVNULL,
104
+ stdout=subprocess.DEVNULL,
105
+ stderr=subprocess.DEVNULL,
106
+ start_new_session=True,
107
+ )
108
+ PY
109
+
110
+ # Wait for the remote debugging endpoint to become reachable so the caller
111
+ # can attach immediately on return.
112
+ for _ in $(seq 1 60); do
113
+ if curl -fsS "http://127.0.0.1:$PORT/json/version" >/dev/null 2>&1; then
114
+ echo ""
115
+ echo "Chrome ready on http://127.0.0.1:$PORT"
116
+ echo "Connect with: attach_browser(endpoint=\"http://127.0.0.1:$PORT\")"
117
+ exit 0
118
+ fi
119
+ sleep 0.5
120
+ done
121
+
122
+ echo "Error: Chrome did not become ready on port $PORT within 30s." >&2
123
+ echo "If another Chrome is already using $USER_DATA_DIR, kill it first or use --profile NAME." >&2
124
+ exit 1