publicbrowser 1.0.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.
@@ -0,0 +1,82 @@
1
+ # Dependencies
2
+ node_modules/
3
+
4
+ # Build output
5
+ build/
6
+ dist/
7
+ *.tsbuildinfo
8
+
9
+ # OS files
10
+ .DS_Store
11
+ Thumbs.db
12
+
13
+ # Editors / IDEs
14
+ .vscode/
15
+ .idea/
16
+ *.swp
17
+ *.swo
18
+
19
+ # Logs
20
+ *.log
21
+ npm-debug.log*
22
+ yarn-debug.log*
23
+ yarn-error.log*
24
+
25
+ # Environment / secrets
26
+ .env
27
+ .env.local
28
+ .env.*.local
29
+
30
+ # Test / tooling caches
31
+ coverage/
32
+ .eslintcache
33
+ .nyc_output/
34
+
35
+ # Python
36
+ __pycache__/
37
+ *.pyc
38
+
39
+ # Browser automation runtime data
40
+ .playwright-mcp/
41
+ .playwright-cli/
42
+
43
+ # Internal dev tooling (not for public repo)
44
+ CLAUDE.md
45
+ .claude/
46
+ _bmad/
47
+ _bmad-output/
48
+ docs/
49
+ # test-hardest: nur Rohdaten, Harness und Liesmich sind oeffentlich; die Seite selbst bleibt privat
50
+ test-hardest/*
51
+ !test-hardest/results/
52
+ test-hardest/results/*
53
+ !test-hardest/results/*.json
54
+ !test-hardest/benchmark-*.json
55
+ !test-hardest/README.md
56
+ !test-hardest/measure-tool-calls.sh
57
+ !test-hardest/measure-session-cost.sh
58
+ !test-hardest/blind-run.mjs
59
+ !test-hardest/blind-run.test.mjs
60
+ !test-hardest/blind-prompt.md
61
+ !test-hardest/fixtures/
62
+ test-hardest/fixtures/*
63
+ !test-hardest/fixtures/blind-run/
64
+ test-stress/
65
+ live-test/
66
+ marketing/
67
+ research/
68
+ prompt.md
69
+ PUBLIC_BROWSER_FINDINGS.md
70
+ compare-*.png
71
+
72
+ # scripts/: nur die oeffentlich nutzbaren Helfer liegen im Repo
73
+ scripts/*
74
+ !scripts/check-pack.mjs
75
+ !scripts/dev-mode.sh
76
+ !scripts/publish.ts
77
+ !scripts/publish.test.ts
78
+ !scripts/token-count.mjs
79
+ !scripts/make-benchmark-chart-2026-09.py
80
+
81
+ # Registry-Publish-Tokens (lokal, niemals committen)
82
+ .mcpregistry_*_token
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Silbercue
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,231 @@
1
+ Metadata-Version: 2.5
2
+ Name: publicbrowser
3
+ Version: 1.0.0
4
+ Summary: Minimal CDP client for Chrome browser automation — control Chrome from Python via the DevTools Protocol.
5
+ Project-URL: Homepage, https://github.com/Silbercue/public-browser
6
+ Project-URL: Documentation, https://github.com/Silbercue/public-browser#script-api-python
7
+ Project-URL: Repository, https://github.com/Silbercue/public-browser
8
+ Project-URL: Issues, https://github.com/Silbercue/public-browser/issues
9
+ Project-URL: Changelog, https://github.com/Silbercue/public-browser/blob/master/CHANGELOG.md
10
+ Author: Julian Friedrich
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: automation,browser,cdp,chrome,devtools,scraping,websocket
14
+ Classifier: Development Status :: 5 - Production/Stable
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: Internet :: WWW/HTTP :: Browsers
24
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
25
+ Classifier: Topic :: Software Development :: Testing
26
+ Classifier: Typing :: Typed
27
+ Requires-Python: >=3.10
28
+ Requires-Dist: websockets>=14.0
29
+ Provides-Extra: dev
30
+ Requires-Dist: build>=1.0; extra == 'dev'
31
+ Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
32
+ Requires-Dist: pytest>=8.0; extra == 'dev'
33
+ Requires-Dist: ruff>=0.4; extra == 'dev'
34
+ Description-Content-Type: text/markdown
35
+
36
+ # Public Browser — Python Script API
37
+
38
+ Python client for Public Browser automation. Scripts use the same tool implementations as the MCP server (Shared Core) — every improvement to `click`, `navigate`, `fill_form` etc. automatically benefits your scripts too. One codebase, one test suite (1600+ tests), two access paths.
39
+
40
+ ## Installation
41
+
42
+ The Python package is not currently published on PyPI. From the repository root, install the local package:
43
+
44
+ ```bash
45
+ python -m pip install ./python
46
+ ```
47
+
48
+ If you are already in this `python/` directory, use `python -m pip install .` instead. No manual Chrome launch is needed — `Chrome.connect()` starts everything automatically via a local `public-browser` binary or the `npx` fallback.
49
+
50
+ Dependencies: `websockets` (for the Escape Hatch / `CdpClient` low-level access). The main Shared Core API uses `urllib` (built-in).
51
+
52
+ ## Quick Start
53
+
54
+ ```python
55
+ from publicbrowser import Chrome
56
+
57
+ chrome = Chrome.connect()
58
+
59
+ with chrome.new_page() as page:
60
+ page.navigate("https://example.com")
61
+ title = page.evaluate("document.title")
62
+ print(title) # "Example Domain"
63
+
64
+ chrome.close()
65
+ ```
66
+
67
+ `Chrome.connect()` auto-starts the Public Browser server as a subprocess, which in turn launches Chrome. When you call `chrome.close()`, the server subprocess is terminated.
68
+
69
+ ## How it works
70
+
71
+ ```
72
+ Python Script Escape Hatch (Power User)
73
+ │ │
74
+ ▼ ▼
75
+ HTTP POST /tool/{name} WebSocket (CDP)
76
+ Port 9223 Port 9222
77
+ │ │
78
+ ▼ │
79
+ Public Browser Server │
80
+ │ │
81
+ ▼ │
82
+ registry.executeTool() │
83
+ │ │
84
+ ▼ │
85
+ Tool Handler │
86
+ (click.ts, navigate.ts, ...) │
87
+ │ │
88
+ ▼ ▼
89
+ Chrome ◄─────────── CDP ────────────────►
90
+ ```
91
+
92
+ Your script sends HTTP requests to the Public Browser server on port 9223. The server executes the exact same tool handlers that the MCP server uses — selector resolution, Shadow DOM traversal, scroll-into-view, paint-order filtering, ambient context — all server-side.
93
+
94
+ ## Auto-Start
95
+
96
+ `Chrome.connect()` finds and starts the server automatically:
97
+
98
+ 1. **Running server** — checks if port 9223 already responds, connects immediately
99
+ 2. **PATH binary** — finds `public-browser` in PATH (e.g. via Homebrew), starts it with `--script`
100
+ 3. **npx fallback** — runs `npx -y public-browser@latest -- --script`
101
+ 4. **Explicit path** — `Chrome.connect(server_path="/path/to/public-browser")` for custom setups
102
+
103
+ ## Login and Data Extraction
104
+
105
+ ```python
106
+ from publicbrowser import Chrome
107
+
108
+ chrome = Chrome.connect()
109
+
110
+ with chrome.new_page() as page:
111
+ page.navigate("https://app.example.com/login")
112
+
113
+ # Fill login form
114
+ page.fill({
115
+ "#email": "user@example.com",
116
+ "#password": "secret",
117
+ })
118
+ page.click("#submit")
119
+
120
+ # Wait for dashboard
121
+ page.wait_for("text=Dashboard")
122
+
123
+ # Extract data
124
+ data = page.evaluate("""
125
+ Array.from(document.querySelectorAll('.item'))
126
+ .map(el => ({ name: el.textContent, href: el.href }))
127
+ """)
128
+ print(data)
129
+
130
+ chrome.close()
131
+ ```
132
+
133
+ ## API Reference
134
+
135
+ ### `Chrome`
136
+
137
+ | Method | Description |
138
+ |---|---|
139
+ | `Chrome.connect(host="localhost", port=9223, *, server_path=None, auto_start=True)` | Connect to or auto-start the Public Browser server |
140
+ | `chrome.new_page()` | Context manager: open a new tab, auto-closes on exit |
141
+ | `chrome.close()` | Close the connection and terminate any auto-started server |
142
+
143
+ ### `Page` (via `chrome.new_page()`)
144
+
145
+ | Method | Description |
146
+ |---|---|
147
+ | `page.navigate(url)` | Navigate to URL and wait for load |
148
+ | `page.click(selector)` | Click element by CSS selector, text, or ref |
149
+ | `page.type(selector, text)` | Type text into input element |
150
+ | `page.fill({"sel": "val", ...})` | Fill multiple form fields at once |
151
+ | `page.wait_for(condition)` | Wait for JS condition or `"text=..."` shorthand |
152
+ | `page.evaluate(expression)` | Run JavaScript, return result |
153
+ | `page.download()` | Enable downloads, return download dir |
154
+ | `page.close()` | Close the tab (auto-called by context manager) |
155
+ | `page.cdp` | Escape Hatch — returns a `CdpEscapeHatch` for direct CDP access (see below) |
156
+
157
+ ### Escape Hatch: `page.cdp.send()`
158
+
159
+ For use cases the high-level API doesn't cover — network interception, console log subscriptions, performance tracing, cookie management, PDF generation — you can drop down to raw CDP commands via `page.cdp.send()`:
160
+
161
+ ```python
162
+ with chrome.new_page() as page:
163
+ page.navigate("https://example.com")
164
+
165
+ # Enable network tracking
166
+ page.cdp.send("Network.enable")
167
+
168
+ # Get all cookies
169
+ cookies = page.cdp.send("Network.getAllCookies")
170
+
171
+ # Performance tracing
172
+ page.cdp.send("Tracing.start", {"categories": "-*,devtools.timeline"})
173
+
174
+ # Register event handler
175
+ page.cdp.on("Network.requestWillBeSent", lambda e: print(e["request"]["url"]))
176
+ ```
177
+
178
+ The Escape Hatch communicates directly with Chrome via WebSocket (port 9222), bypassing the server entirely. It connects lazily on the first `send()` call and reuses the connection. Each page gets its own WebSocket routed to the correct tab.
179
+
180
+ | Method | Description |
181
+ |---|---|
182
+ | `page.cdp.send(method, params=None, *, timeout=30.0)` | Send a CDP command and return the result |
183
+ | `page.cdp.on(event, handler)` | Register a callback for a CDP event |
184
+ | `page.cdp.close()` | Close the WebSocket (auto-called when the page context manager exits) |
185
+
186
+ ### `CdpClient` (low-level, legacy)
187
+
188
+ For direct CDP access without the Shared Core server. This is the v1 code path — it works, but does not benefit from server-side improvements. Use `page.cdp.send()` instead for most Escape Hatch use cases.
189
+
190
+ ```python
191
+ from publicbrowser import CdpClient
192
+
193
+ # Async API
194
+ client = await CdpClient.connect(port=9222)
195
+ result = await client.send("Runtime.evaluate", {"expression": "1+1"})
196
+ await client.close()
197
+
198
+ # Sync API
199
+ client = CdpClient.connect_sync(port=9222)
200
+ result = client.send_sync("Runtime.evaluate", {"expression": "1+1"})
201
+ client.close_sync()
202
+ ```
203
+
204
+ ## MCP Coexistence
205
+
206
+ When the MCP server and Python scripts need to run at the same time, add `--script` to the MCP config. `Chrome.connect()` handles the rest — each script works in its own tab, MCP tabs are never touched.
207
+
208
+ **Claude Code:**
209
+ ```bash
210
+ claude mcp add --scope user public-browser npx -y public-browser@latest -- --script
211
+ ```
212
+
213
+ **Cursor / Cline (`mcp.json`):**
214
+ ```json
215
+ {
216
+ "mcpServers": {
217
+ "public-browser": {
218
+ "command": "npx",
219
+ "args": ["-y", "public-browser@latest", "--", "--script"]
220
+ }
221
+ }
222
+ }
223
+ ```
224
+
225
+ ## Legacy: Single-File Alternative
226
+
227
+ For quick prototyping, you can copy `publicbrowser_standalone.py` into your project. This uses the v1 code path (direct CDP via WebSocket) and does **not** benefit from server-side improvements. Use the local `publicbrowser` package for the full Shared Core experience.
228
+
229
+ ## License
230
+
231
+ MIT
@@ -0,0 +1,196 @@
1
+ # Public Browser — Python Script API
2
+
3
+ Python client for Public Browser automation. Scripts use the same tool implementations as the MCP server (Shared Core) — every improvement to `click`, `navigate`, `fill_form` etc. automatically benefits your scripts too. One codebase, one test suite (1600+ tests), two access paths.
4
+
5
+ ## Installation
6
+
7
+ The Python package is not currently published on PyPI. From the repository root, install the local package:
8
+
9
+ ```bash
10
+ python -m pip install ./python
11
+ ```
12
+
13
+ If you are already in this `python/` directory, use `python -m pip install .` instead. No manual Chrome launch is needed — `Chrome.connect()` starts everything automatically via a local `public-browser` binary or the `npx` fallback.
14
+
15
+ Dependencies: `websockets` (for the Escape Hatch / `CdpClient` low-level access). The main Shared Core API uses `urllib` (built-in).
16
+
17
+ ## Quick Start
18
+
19
+ ```python
20
+ from publicbrowser import Chrome
21
+
22
+ chrome = Chrome.connect()
23
+
24
+ with chrome.new_page() as page:
25
+ page.navigate("https://example.com")
26
+ title = page.evaluate("document.title")
27
+ print(title) # "Example Domain"
28
+
29
+ chrome.close()
30
+ ```
31
+
32
+ `Chrome.connect()` auto-starts the Public Browser server as a subprocess, which in turn launches Chrome. When you call `chrome.close()`, the server subprocess is terminated.
33
+
34
+ ## How it works
35
+
36
+ ```
37
+ Python Script Escape Hatch (Power User)
38
+ │ │
39
+ ▼ ▼
40
+ HTTP POST /tool/{name} WebSocket (CDP)
41
+ Port 9223 Port 9222
42
+ │ │
43
+ ▼ │
44
+ Public Browser Server │
45
+ │ │
46
+ ▼ │
47
+ registry.executeTool() │
48
+ │ │
49
+ ▼ │
50
+ Tool Handler │
51
+ (click.ts, navigate.ts, ...) │
52
+ │ │
53
+ ▼ ▼
54
+ Chrome ◄─────────── CDP ────────────────►
55
+ ```
56
+
57
+ Your script sends HTTP requests to the Public Browser server on port 9223. The server executes the exact same tool handlers that the MCP server uses — selector resolution, Shadow DOM traversal, scroll-into-view, paint-order filtering, ambient context — all server-side.
58
+
59
+ ## Auto-Start
60
+
61
+ `Chrome.connect()` finds and starts the server automatically:
62
+
63
+ 1. **Running server** — checks if port 9223 already responds, connects immediately
64
+ 2. **PATH binary** — finds `public-browser` in PATH (e.g. via Homebrew), starts it with `--script`
65
+ 3. **npx fallback** — runs `npx -y public-browser@latest -- --script`
66
+ 4. **Explicit path** — `Chrome.connect(server_path="/path/to/public-browser")` for custom setups
67
+
68
+ ## Login and Data Extraction
69
+
70
+ ```python
71
+ from publicbrowser import Chrome
72
+
73
+ chrome = Chrome.connect()
74
+
75
+ with chrome.new_page() as page:
76
+ page.navigate("https://app.example.com/login")
77
+
78
+ # Fill login form
79
+ page.fill({
80
+ "#email": "user@example.com",
81
+ "#password": "secret",
82
+ })
83
+ page.click("#submit")
84
+
85
+ # Wait for dashboard
86
+ page.wait_for("text=Dashboard")
87
+
88
+ # Extract data
89
+ data = page.evaluate("""
90
+ Array.from(document.querySelectorAll('.item'))
91
+ .map(el => ({ name: el.textContent, href: el.href }))
92
+ """)
93
+ print(data)
94
+
95
+ chrome.close()
96
+ ```
97
+
98
+ ## API Reference
99
+
100
+ ### `Chrome`
101
+
102
+ | Method | Description |
103
+ |---|---|
104
+ | `Chrome.connect(host="localhost", port=9223, *, server_path=None, auto_start=True)` | Connect to or auto-start the Public Browser server |
105
+ | `chrome.new_page()` | Context manager: open a new tab, auto-closes on exit |
106
+ | `chrome.close()` | Close the connection and terminate any auto-started server |
107
+
108
+ ### `Page` (via `chrome.new_page()`)
109
+
110
+ | Method | Description |
111
+ |---|---|
112
+ | `page.navigate(url)` | Navigate to URL and wait for load |
113
+ | `page.click(selector)` | Click element by CSS selector, text, or ref |
114
+ | `page.type(selector, text)` | Type text into input element |
115
+ | `page.fill({"sel": "val", ...})` | Fill multiple form fields at once |
116
+ | `page.wait_for(condition)` | Wait for JS condition or `"text=..."` shorthand |
117
+ | `page.evaluate(expression)` | Run JavaScript, return result |
118
+ | `page.download()` | Enable downloads, return download dir |
119
+ | `page.close()` | Close the tab (auto-called by context manager) |
120
+ | `page.cdp` | Escape Hatch — returns a `CdpEscapeHatch` for direct CDP access (see below) |
121
+
122
+ ### Escape Hatch: `page.cdp.send()`
123
+
124
+ For use cases the high-level API doesn't cover — network interception, console log subscriptions, performance tracing, cookie management, PDF generation — you can drop down to raw CDP commands via `page.cdp.send()`:
125
+
126
+ ```python
127
+ with chrome.new_page() as page:
128
+ page.navigate("https://example.com")
129
+
130
+ # Enable network tracking
131
+ page.cdp.send("Network.enable")
132
+
133
+ # Get all cookies
134
+ cookies = page.cdp.send("Network.getAllCookies")
135
+
136
+ # Performance tracing
137
+ page.cdp.send("Tracing.start", {"categories": "-*,devtools.timeline"})
138
+
139
+ # Register event handler
140
+ page.cdp.on("Network.requestWillBeSent", lambda e: print(e["request"]["url"]))
141
+ ```
142
+
143
+ The Escape Hatch communicates directly with Chrome via WebSocket (port 9222), bypassing the server entirely. It connects lazily on the first `send()` call and reuses the connection. Each page gets its own WebSocket routed to the correct tab.
144
+
145
+ | Method | Description |
146
+ |---|---|
147
+ | `page.cdp.send(method, params=None, *, timeout=30.0)` | Send a CDP command and return the result |
148
+ | `page.cdp.on(event, handler)` | Register a callback for a CDP event |
149
+ | `page.cdp.close()` | Close the WebSocket (auto-called when the page context manager exits) |
150
+
151
+ ### `CdpClient` (low-level, legacy)
152
+
153
+ For direct CDP access without the Shared Core server. This is the v1 code path — it works, but does not benefit from server-side improvements. Use `page.cdp.send()` instead for most Escape Hatch use cases.
154
+
155
+ ```python
156
+ from publicbrowser import CdpClient
157
+
158
+ # Async API
159
+ client = await CdpClient.connect(port=9222)
160
+ result = await client.send("Runtime.evaluate", {"expression": "1+1"})
161
+ await client.close()
162
+
163
+ # Sync API
164
+ client = CdpClient.connect_sync(port=9222)
165
+ result = client.send_sync("Runtime.evaluate", {"expression": "1+1"})
166
+ client.close_sync()
167
+ ```
168
+
169
+ ## MCP Coexistence
170
+
171
+ When the MCP server and Python scripts need to run at the same time, add `--script` to the MCP config. `Chrome.connect()` handles the rest — each script works in its own tab, MCP tabs are never touched.
172
+
173
+ **Claude Code:**
174
+ ```bash
175
+ claude mcp add --scope user public-browser npx -y public-browser@latest -- --script
176
+ ```
177
+
178
+ **Cursor / Cline (`mcp.json`):**
179
+ ```json
180
+ {
181
+ "mcpServers": {
182
+ "public-browser": {
183
+ "command": "npx",
184
+ "args": ["-y", "public-browser@latest", "--", "--script"]
185
+ }
186
+ }
187
+ }
188
+ ```
189
+
190
+ ## Legacy: Single-File Alternative
191
+
192
+ For quick prototyping, you can copy `publicbrowser_standalone.py` into your project. This uses the v1 code path (direct CDP via WebSocket) and does **not** benefit from server-side improvements. Use the local `publicbrowser` package for the full Shared Core experience.
193
+
194
+ ## License
195
+
196
+ MIT
@@ -0,0 +1,24 @@
1
+ """Public Browser — Python client for Chrome browser automation.
2
+
3
+ v2: Uses the Public Browser Script API server (HTTP on port 9223).
4
+ All browser automation logic runs server-side for maximum quality.
5
+
6
+ CdpClient and CdpError are re-exported for backward compatibility
7
+ and for the cdp.py escape hatch (Story 9.9).
8
+ """
9
+
10
+ from publicbrowser.cdp import CdpClient, CdpError
11
+ from publicbrowser.chrome import Chrome
12
+ from publicbrowser.client import ScriptApiClient
13
+ from publicbrowser.escape_hatch import CdpEscapeHatch
14
+ from publicbrowser.page import Page
15
+
16
+ __version__ = "1.0.0"
17
+ __all__ = [
18
+ "Chrome",
19
+ "Page",
20
+ "ScriptApiClient",
21
+ "CdpClient",
22
+ "CdpError",
23
+ "CdpEscapeHatch",
24
+ ]