cmdop-browser 1.0.1__py3-none-any.whl
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.
- _skill.py +792 -0
- cmdop_browser-1.0.1.dist-info/METADATA +20 -0
- cmdop_browser-1.0.1.dist-info/RECORD +83 -0
- cmdop_browser-1.0.1.dist-info/WHEEL +4 -0
- cmdop_browser-1.0.1.dist-info/entry_points.txt +2 -0
- src/__init__.py +0 -0
- src/browsers/__init__.py +32 -0
- src/browsers/addon.py +405 -0
- src/browsers/base.py +136 -0
- src/browsers/camoufox.py +239 -0
- src/browsers/install.py +93 -0
- src/browsers/persistent.py +188 -0
- src/browsers/playwright.py +308 -0
- src/cdn/__init__.py +3 -0
- src/cdn/client.py +44 -0
- src/config.py +45 -0
- src/extraction/__init__.py +11 -0
- src/extraction/extractor.py +170 -0
- src/extraction/jsonld.py +112 -0
- src/extraction/meta.py +120 -0
- src/extraction/table.py +160 -0
- src/geo/__init__.py +9 -0
- src/network/__init__.py +3 -0
- src/network/capturer.py +172 -0
- src/perception/__init__.py +15 -0
- src/perception/axtree.py +225 -0
- src/perception/html_cleaner.py +188 -0
- src/perception/markdown.py +162 -0
- src/perception/pipeline.py +443 -0
- src/platforms/__init__.py +6 -0
- src/platforms/detector.py +16 -0
- src/platforms/facebook.py +18 -0
- src/platforms/linkedin.py +20 -0
- src/platforms/twitter.py +21 -0
- src/pool/__init__.py +4 -0
- src/pool/pool.py +231 -0
- src/profiles/__init__.py +3 -0
- src/profiles/store.py +135 -0
- src/recovery/__init__.py +7 -0
- src/recovery/manager.py +166 -0
- src/recovery/watchdog.py +134 -0
- src/stealth/__init__.py +23 -0
- src/stealth/behaviors/__init__.py +19 -0
- src/stealth/behaviors/fitts.py +37 -0
- src/stealth/behaviors/keyboard.py +86 -0
- src/stealth/behaviors/math_utils.py +36 -0
- src/stealth/behaviors/mouse.py +135 -0
- src/stealth/behaviors/ratelimit.py +35 -0
- src/stealth/behaviors/scroll.py +67 -0
- src/stealth/behaviors/targeting.py +29 -0
- src/stealth/behaviors/timing.py +79 -0
- src/stealth/fingerprints/__init__.py +14 -0
- src/stealth/fingerprints/fingerprint.py +120 -0
- src/stealth/manager.py +72 -0
- src/stealth/patches/__init__.py +15 -0
- src/stealth/patches/audio.py +41 -0
- src/stealth/patches/canvas.py +92 -0
- src/stealth/patches/clientrects.py +60 -0
- src/stealth/patches/fonts.py +32 -0
- src/stealth/patches/navigator.py +223 -0
- src/stealth/patches/webgl.py +67 -0
- src/tabs/__init__.py +3 -0
- src/tabs/manager.py +227 -0
- src/tools/__init__.py +169 -0
- src/tools/advanced.py +147 -0
- src/tools/control.py +35 -0
- src/tools/cookies.py +58 -0
- src/tools/data.py +142 -0
- src/tools/extraction.py +125 -0
- src/tools/interaction.py +59 -0
- src/tools/media.py +69 -0
- src/tools/models.py +373 -0
- src/tools/navigation.py +59 -0
- src/tools/network.py +103 -0
- src/tools/perception.py +70 -0
- src/tools/pool.py +49 -0
- src/tools/profiles.py +98 -0
- src/tools/tabs.py +144 -0
- src/tools/turnstile.py +66 -0
- src/tools/visual.py +33 -0
- src/tools/wait.py +32 -0
- src/turnstile/__init__.py +17 -0
- src/turnstile/solver.py +212 -0
_skill.py
ADDED
|
@@ -0,0 +1,792 @@
|
|
|
1
|
+
"""cmdop-browser — CMDOP skill wrapper for browser automation."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from cmdop_skill import Arg, Skill
|
|
8
|
+
|
|
9
|
+
from src.browsers import create_browser
|
|
10
|
+
from src.browsers.base import BrowserDriver
|
|
11
|
+
from src.config import BrowserConfig
|
|
12
|
+
from src.tools import ALL_TOOLS
|
|
13
|
+
|
|
14
|
+
skill = Skill(
|
|
15
|
+
name="browser",
|
|
16
|
+
description="Playwright + Camoufox browser automation — 56 tools, stealth mode, network capture, profiles",
|
|
17
|
+
version="1.0.0",
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
# ── Singleton browser ────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
_browser: BrowserDriver | None = None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
async def _get_browser() -> BrowserDriver:
|
|
26
|
+
global _browser
|
|
27
|
+
if _browser is not None and _browser.is_running():
|
|
28
|
+
return _browser
|
|
29
|
+
config = BrowserConfig()
|
|
30
|
+
_browser = create_browser(config)
|
|
31
|
+
await _browser.start()
|
|
32
|
+
return _browser
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@skill.teardown
|
|
36
|
+
async def _teardown() -> None:
|
|
37
|
+
global _browser
|
|
38
|
+
if _browser is not None and _browser.is_running():
|
|
39
|
+
await _browser.close()
|
|
40
|
+
_browser = None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# ── Helpers ──────────────────────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
def _to_dict(result: Any) -> dict:
|
|
46
|
+
from pydantic import BaseModel
|
|
47
|
+
if isinstance(result, BaseModel):
|
|
48
|
+
return result.model_dump()
|
|
49
|
+
if isinstance(result, dict):
|
|
50
|
+
return result
|
|
51
|
+
return {"ok": True, "result": result}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# ── Setup ────────────────────────────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
@skill.command
|
|
57
|
+
def setup(
|
|
58
|
+
force: bool = Arg("--force", action="store_true", default=False, help="Re-install even if already present"),
|
|
59
|
+
) -> dict:
|
|
60
|
+
"""Download and install browser binaries."""
|
|
61
|
+
from src.browsers.install import install_browsers
|
|
62
|
+
from src.config import BROWSER_PROVIDER
|
|
63
|
+
provider = BROWSER_PROVIDER or "all"
|
|
64
|
+
install_browsers(provider=provider, force=force)
|
|
65
|
+
return {"provider": provider}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# ── Navigation (6) ───────────────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
@skill.command
|
|
71
|
+
async def navigate(
|
|
72
|
+
url: str = Arg(help="URL to navigate to", required=True),
|
|
73
|
+
timeout: float = Arg("--timeout", default=30.0, help="Navigation timeout in seconds"),
|
|
74
|
+
) -> dict:
|
|
75
|
+
"""Navigate to a URL."""
|
|
76
|
+
browser = await _get_browser()
|
|
77
|
+
result = await ALL_TOOLS["navigate"](browser, url=url, timeout=timeout)
|
|
78
|
+
return _to_dict(result)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@skill.command
|
|
82
|
+
async def refresh() -> dict:
|
|
83
|
+
"""Refresh the current page."""
|
|
84
|
+
browser = await _get_browser()
|
|
85
|
+
return _to_dict(await ALL_TOOLS["refresh"](browser))
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@skill.command
|
|
89
|
+
async def back() -> dict:
|
|
90
|
+
"""Go back in browser history."""
|
|
91
|
+
browser = await _get_browser()
|
|
92
|
+
return _to_dict(await ALL_TOOLS["back"](browser))
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@skill.command
|
|
96
|
+
async def forward() -> dict:
|
|
97
|
+
"""Go forward in browser history."""
|
|
98
|
+
browser = await _get_browser()
|
|
99
|
+
return _to_dict(await ALL_TOOLS["forward"](browser))
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@skill.command
|
|
103
|
+
async def get_url() -> dict:
|
|
104
|
+
"""Get the current page URL."""
|
|
105
|
+
browser = await _get_browser()
|
|
106
|
+
return _to_dict(await ALL_TOOLS["get_url"](browser))
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@skill.command
|
|
110
|
+
async def get_title() -> dict:
|
|
111
|
+
"""Get the current page title."""
|
|
112
|
+
browser = await _get_browser()
|
|
113
|
+
return _to_dict(await ALL_TOOLS["get_title"](browser))
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# ── Interaction (7) ──────────────────────────────────────────────────────────
|
|
117
|
+
|
|
118
|
+
@skill.command
|
|
119
|
+
async def click(
|
|
120
|
+
selector: str = Arg(help="CSS selector to click", required=True),
|
|
121
|
+
human: bool = Arg("--human", action="store_true", default=False, help="Move cursor like a human"),
|
|
122
|
+
) -> dict:
|
|
123
|
+
"""Click an element."""
|
|
124
|
+
browser = await _get_browser()
|
|
125
|
+
return _to_dict(await ALL_TOOLS["click"](browser, selector=selector, human=human))
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@skill.command
|
|
129
|
+
async def type_text(
|
|
130
|
+
selector: str = Arg(help="CSS selector to type into", required=True),
|
|
131
|
+
text: str = Arg(help="Text to type", required=True),
|
|
132
|
+
clear: bool = Arg("--clear", action="store_true", default=False, help="Clear field before typing"),
|
|
133
|
+
) -> dict:
|
|
134
|
+
"""Type text into an element."""
|
|
135
|
+
browser = await _get_browser()
|
|
136
|
+
return _to_dict(await ALL_TOOLS["type"](browser, selector=selector, text=text, clear=clear))
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@skill.command
|
|
140
|
+
async def type_human(
|
|
141
|
+
selector: str = Arg(help="CSS selector to type into", required=True),
|
|
142
|
+
text: str = Arg(help="Text to type with human-like delays", required=True),
|
|
143
|
+
) -> dict:
|
|
144
|
+
"""Type text with human-like timing."""
|
|
145
|
+
browser = await _get_browser()
|
|
146
|
+
return _to_dict(await ALL_TOOLS["type_human"](browser, selector=selector, text=text))
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@skill.command
|
|
150
|
+
async def clear(
|
|
151
|
+
selector: str = Arg(help="CSS selector to clear", required=True),
|
|
152
|
+
) -> dict:
|
|
153
|
+
"""Clear an input field."""
|
|
154
|
+
browser = await _get_browser()
|
|
155
|
+
return _to_dict(await ALL_TOOLS["clear"](browser, selector=selector))
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@skill.command
|
|
159
|
+
async def hover(
|
|
160
|
+
selector: str = Arg(help="CSS selector to hover over", required=True),
|
|
161
|
+
) -> dict:
|
|
162
|
+
"""Hover over an element."""
|
|
163
|
+
browser = await _get_browser()
|
|
164
|
+
return _to_dict(await ALL_TOOLS["hover"](browser, selector=selector))
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@skill.command
|
|
168
|
+
async def press_key(
|
|
169
|
+
key: str = Arg(help="Key to press", required=True),
|
|
170
|
+
modifiers: str = Arg("--modifiers", default=None, nargs="*", help="Modifier keys"),
|
|
171
|
+
) -> dict:
|
|
172
|
+
"""Press a keyboard key."""
|
|
173
|
+
browser = await _get_browser()
|
|
174
|
+
return _to_dict(await ALL_TOOLS["press_key"](browser, key=key, modifiers=modifiers))
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@skill.command
|
|
178
|
+
async def mouse_move(
|
|
179
|
+
x: int = Arg(help="X coordinate", required=True),
|
|
180
|
+
y: int = Arg(help="Y coordinate", required=True),
|
|
181
|
+
steps: int = Arg("--steps", default=20, help="Number of intermediate steps"),
|
|
182
|
+
) -> dict:
|
|
183
|
+
"""Move the mouse cursor."""
|
|
184
|
+
browser = await _get_browser()
|
|
185
|
+
return _to_dict(await ALL_TOOLS["mouse_move"](browser, x=x, y=y, steps=steps))
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
# ── Wait (3) ─────────────────────────────────────────────────────────────────
|
|
189
|
+
|
|
190
|
+
@skill.command
|
|
191
|
+
async def wait_for_selector(
|
|
192
|
+
selector: str = Arg(help="CSS selector to wait for", required=True),
|
|
193
|
+
timeout: float = Arg("--timeout", default=45.0, help="Timeout in seconds"),
|
|
194
|
+
visible: bool = Arg("--visible", action="store_true", default=True, help="Wait for visible element"),
|
|
195
|
+
) -> dict:
|
|
196
|
+
"""Wait for an element to appear."""
|
|
197
|
+
browser = await _get_browser()
|
|
198
|
+
return _to_dict(await ALL_TOOLS["wait_for_selector"](browser, selector=selector, timeout=timeout, visible=visible))
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
@skill.command
|
|
202
|
+
async def wait_for_idle(
|
|
203
|
+
timeout: float = Arg("--timeout", default=5.0, help="Timeout in seconds"),
|
|
204
|
+
) -> dict:
|
|
205
|
+
"""Wait for the page to become idle."""
|
|
206
|
+
browser = await _get_browser()
|
|
207
|
+
return _to_dict(await ALL_TOOLS["wait_for_idle"](browser, timeout=timeout))
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@skill.command
|
|
211
|
+
async def wait(
|
|
212
|
+
seconds: float = Arg(help="Seconds to wait", required=True),
|
|
213
|
+
) -> dict:
|
|
214
|
+
"""Wait for a specified number of seconds."""
|
|
215
|
+
return _to_dict(await ALL_TOOLS["wait"](seconds=seconds))
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
# ── Control (3) ──────────────────────────────────────────────────────────────
|
|
219
|
+
|
|
220
|
+
@skill.command
|
|
221
|
+
async def set_viewport(
|
|
222
|
+
width: int = Arg(help="Viewport width", required=True),
|
|
223
|
+
height: int = Arg(help="Viewport height", required=True),
|
|
224
|
+
) -> dict:
|
|
225
|
+
"""Set the browser viewport size."""
|
|
226
|
+
browser = await _get_browser()
|
|
227
|
+
return _to_dict(await ALL_TOOLS["set_viewport"](browser, width=width, height=height))
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
@skill.command
|
|
231
|
+
async def scroll(
|
|
232
|
+
direction: str = Arg(help="Scroll direction", required=True, choices=["up", "down", "left", "right"]),
|
|
233
|
+
amount: int = Arg("--amount", default=300, help="Scroll amount in pixels"),
|
|
234
|
+
selector: str = Arg("--selector", default="", help="Element to scroll within"),
|
|
235
|
+
smooth: bool = Arg("--no-smooth", dest="smooth", action="store_false", default=True, help="Disable smooth scrolling"),
|
|
236
|
+
) -> dict:
|
|
237
|
+
"""Scroll the page or element."""
|
|
238
|
+
browser = await _get_browser()
|
|
239
|
+
return _to_dict(await ALL_TOOLS["scroll"](browser, direction=direction, amount=amount, selector=selector, smooth=smooth))
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
@skill.command
|
|
243
|
+
async def close_browser() -> dict:
|
|
244
|
+
"""Close the browser."""
|
|
245
|
+
browser = await _get_browser()
|
|
246
|
+
return _to_dict(await ALL_TOOLS["close_browser"](browser))
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
# ── Data (9) ─────────────────────────────────────────────────────────────────
|
|
250
|
+
|
|
251
|
+
@skill.command
|
|
252
|
+
async def screenshot(
|
|
253
|
+
full_page: bool = Arg("--full-page", action="store_true", default=False, help="Capture full page"),
|
|
254
|
+
selector: str = Arg("--selector", default="", help="Element to screenshot"),
|
|
255
|
+
) -> dict:
|
|
256
|
+
"""Take a screenshot."""
|
|
257
|
+
browser = await _get_browser()
|
|
258
|
+
return _to_dict(await ALL_TOOLS["screenshot"](browser, full_page=full_page, selector=selector))
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
@skill.command
|
|
262
|
+
async def save_screenshot(
|
|
263
|
+
path: str = Arg("--path", default="", help="File path to save screenshot"),
|
|
264
|
+
full_page: bool = Arg("--full-page", action="store_true", default=False, help="Capture full page"),
|
|
265
|
+
) -> dict:
|
|
266
|
+
"""Save a screenshot to a file."""
|
|
267
|
+
browser = await _get_browser()
|
|
268
|
+
return _to_dict(await ALL_TOOLS["save_screenshot"](browser, path=path, full_page=full_page))
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
@skill.command
|
|
272
|
+
async def get_html(
|
|
273
|
+
mode: str = Arg("--mode", default="agent", choices=["agent", "raw"], help="Output mode"),
|
|
274
|
+
selector: str = Arg("--selector", default="", help="Element selector"),
|
|
275
|
+
) -> dict:
|
|
276
|
+
"""Get page HTML."""
|
|
277
|
+
browser = await _get_browser()
|
|
278
|
+
return _to_dict(await ALL_TOOLS["get_html"](browser, mode=mode, selector=selector))
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
@skill.command
|
|
282
|
+
async def get_text(
|
|
283
|
+
selector: str = Arg(help="CSS selector to extract text from", required=True),
|
|
284
|
+
) -> dict:
|
|
285
|
+
"""Get text content of an element."""
|
|
286
|
+
browser = await _get_browser()
|
|
287
|
+
return _to_dict(await ALL_TOOLS["get_text"](browser, selector=selector))
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
@skill.command
|
|
291
|
+
async def execute_script(
|
|
292
|
+
script: str = Arg(help="JavaScript code to execute", required=True),
|
|
293
|
+
args: str = Arg("--args", default=None, nargs="*", help="Script arguments"),
|
|
294
|
+
) -> dict:
|
|
295
|
+
"""Execute JavaScript in the browser."""
|
|
296
|
+
browser = await _get_browser()
|
|
297
|
+
return _to_dict(await ALL_TOOLS["execute_script"](browser, script=script, args=args))
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
@skill.command
|
|
301
|
+
async def extract(
|
|
302
|
+
selector: str = Arg(help="CSS selector to extract from", required=True),
|
|
303
|
+
attribute: str = Arg("--attribute", default="text", help="Attribute to extract"),
|
|
304
|
+
limit: int = Arg("--limit", default=100, help="Maximum number of elements"),
|
|
305
|
+
) -> dict:
|
|
306
|
+
"""Extract data from elements matching a selector."""
|
|
307
|
+
browser = await _get_browser()
|
|
308
|
+
return _to_dict(await ALL_TOOLS["extract"](browser, selector=selector, attribute=attribute, limit=limit))
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
@skill.command
|
|
312
|
+
async def get_attribute(
|
|
313
|
+
selector: str = Arg(help="CSS selector", required=True),
|
|
314
|
+
attribute: str = Arg(help="Attribute name", required=True),
|
|
315
|
+
) -> dict:
|
|
316
|
+
"""Get an attribute value from an element."""
|
|
317
|
+
browser = await _get_browser()
|
|
318
|
+
return _to_dict(await ALL_TOOLS["get_attribute"](browser, selector=selector, attribute=attribute))
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
@skill.command
|
|
322
|
+
async def exists(
|
|
323
|
+
selector: str = Arg(help="CSS selector to check", required=True),
|
|
324
|
+
) -> dict:
|
|
325
|
+
"""Check if an element exists."""
|
|
326
|
+
browser = await _get_browser()
|
|
327
|
+
return _to_dict(await ALL_TOOLS["exists"](browser, selector=selector))
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
@skill.command
|
|
331
|
+
async def count(
|
|
332
|
+
selector: str = Arg(help="CSS selector to count", required=True),
|
|
333
|
+
) -> dict:
|
|
334
|
+
"""Count elements matching a selector."""
|
|
335
|
+
browser = await _get_browser()
|
|
336
|
+
return _to_dict(await ALL_TOOLS["count"](browser, selector=selector))
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
# ── Perception (4) ───────────────────────────────────────────────────────────
|
|
340
|
+
|
|
341
|
+
@skill.command
|
|
342
|
+
async def get_page(
|
|
343
|
+
mode: str = Arg("--mode", default="full", help="Page capture mode"),
|
|
344
|
+
) -> dict:
|
|
345
|
+
"""Get annotated page HTML for LLM automation."""
|
|
346
|
+
browser = await _get_browser()
|
|
347
|
+
return _to_dict(await ALL_TOOLS["get_page"](browser, mode=mode))
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
@skill.command
|
|
351
|
+
async def get_state() -> dict:
|
|
352
|
+
"""Get text representation of page state."""
|
|
353
|
+
browser = await _get_browser()
|
|
354
|
+
return _to_dict(await ALL_TOOLS["get_state"](browser))
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
@skill.command
|
|
358
|
+
async def get_interactive(
|
|
359
|
+
visible_only: bool = Arg("--visible-only", action="store_true", default=True, help="Only visible elements"),
|
|
360
|
+
) -> dict:
|
|
361
|
+
"""Get list of interactive elements."""
|
|
362
|
+
browser = await _get_browser()
|
|
363
|
+
return _to_dict(await ALL_TOOLS["get_interactive"](browser, visible_only=visible_only))
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
@skill.command
|
|
367
|
+
async def get_diff() -> dict:
|
|
368
|
+
"""Get page changes since last call."""
|
|
369
|
+
browser = await _get_browser()
|
|
370
|
+
return _to_dict(await ALL_TOOLS["get_diff"](browser))
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
# ── Extraction (4) ───────────────────────────────────────────────────────────
|
|
374
|
+
|
|
375
|
+
@skill.command
|
|
376
|
+
async def validate_selectors(
|
|
377
|
+
selectors: str = Arg(help="CSS selectors to validate", required=True, nargs="+"),
|
|
378
|
+
) -> dict:
|
|
379
|
+
"""Validate CSS selectors against the page."""
|
|
380
|
+
browser = await _get_browser()
|
|
381
|
+
return _to_dict(await ALL_TOOLS["validate_selectors"](browser, selectors=selectors))
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
@skill.command
|
|
385
|
+
async def extract_regex(
|
|
386
|
+
pattern: str = Arg(help="Regex pattern", required=True),
|
|
387
|
+
source: str = Arg("--source", default="text", choices=["text", "html"], help="Source to search"),
|
|
388
|
+
selector: str = Arg("--selector", default="", help="Limit to element"),
|
|
389
|
+
group: int = Arg("--group", default=0, help="Capture group number"),
|
|
390
|
+
) -> dict:
|
|
391
|
+
"""Extract text matching a regex pattern."""
|
|
392
|
+
browser = await _get_browser()
|
|
393
|
+
return _to_dict(await ALL_TOOLS["extract_regex"](browser, pattern=pattern, source=source, selector=selector, group=group))
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
@skill.command
|
|
397
|
+
async def extract_data(
|
|
398
|
+
mappings: str = Arg(help='JSON mapping: {"field": "selector"}', required=True),
|
|
399
|
+
attribute: str = Arg("--attribute", default="text", help="Attribute to extract"),
|
|
400
|
+
multiple: bool = Arg("--multiple", action="store_true", default=False, help="Extract multiple matches"),
|
|
401
|
+
) -> dict:
|
|
402
|
+
"""Extract structured data using selector mappings."""
|
|
403
|
+
browser = await _get_browser()
|
|
404
|
+
parsed = json.loads(mappings)
|
|
405
|
+
return _to_dict(await ALL_TOOLS["extract_data"](browser, mappings=parsed, attribute=attribute, multiple=multiple))
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
@skill.command
|
|
409
|
+
async def check_regex(
|
|
410
|
+
selector: str = Arg(help="CSS selector", required=True),
|
|
411
|
+
pattern: str = Arg(help="Regex pattern to check", required=True),
|
|
412
|
+
attribute: str = Arg("--attribute", default="text", help="Attribute to check against"),
|
|
413
|
+
) -> dict:
|
|
414
|
+
"""Check if element content matches a regex."""
|
|
415
|
+
browser = await _get_browser()
|
|
416
|
+
return _to_dict(await ALL_TOOLS["check_regex"](browser, selector=selector, pattern=pattern, attribute=attribute))
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
# ── Cookies (1) ──────────────────────────────────────────────────────────────
|
|
420
|
+
|
|
421
|
+
@skill.command
|
|
422
|
+
async def import_cookies(
|
|
423
|
+
domain: str = Arg(help="Domain to import cookies for", required=True),
|
|
424
|
+
source: str = Arg("--source", default="chrome", help="Cookie source"),
|
|
425
|
+
) -> dict:
|
|
426
|
+
"""Import cookies from local browser."""
|
|
427
|
+
browser = await _get_browser()
|
|
428
|
+
return _to_dict(await ALL_TOOLS["import_cookies"](browser, domain=domain, source=source))
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
# ── Media (2) ────────────────────────────────────────────────────────────────
|
|
432
|
+
|
|
433
|
+
@skill.command
|
|
434
|
+
async def extract_video_urls(
|
|
435
|
+
wait_for_play: bool = Arg("--wait-for-play", action="store_true", default=False, help="Wait for video to play"),
|
|
436
|
+
) -> dict:
|
|
437
|
+
"""Extract video source URLs from the page."""
|
|
438
|
+
browser = await _get_browser()
|
|
439
|
+
return _to_dict(await ALL_TOOLS["extract_video_urls"](browser, wait_for_play=wait_for_play))
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
@skill.command
|
|
443
|
+
async def get_video_info(
|
|
444
|
+
selector: str = Arg("--selector", default="video", help="Video element selector"),
|
|
445
|
+
) -> dict:
|
|
446
|
+
"""Get video metadata."""
|
|
447
|
+
browser = await _get_browser()
|
|
448
|
+
return _to_dict(await ALL_TOOLS["get_video_info"](browser, selector=selector))
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
# ── Profiles (6) ─────────────────────────────────────────────────────────────
|
|
452
|
+
|
|
453
|
+
@skill.command
|
|
454
|
+
async def create_profile(
|
|
455
|
+
name: str = Arg("--name", default="", help="Profile name"),
|
|
456
|
+
) -> dict:
|
|
457
|
+
"""Create a new browser profile."""
|
|
458
|
+
browser = await _get_browser()
|
|
459
|
+
return _to_dict(await ALL_TOOLS["create_profile"](browser, name=name))
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
@skill.command
|
|
463
|
+
async def switch_profile(
|
|
464
|
+
profile_id: str = Arg(help="Profile ID to switch to", required=True),
|
|
465
|
+
) -> dict:
|
|
466
|
+
"""Switch to a browser profile."""
|
|
467
|
+
browser = await _get_browser()
|
|
468
|
+
return _to_dict(await ALL_TOOLS["switch_profile"](browser, profile_id=profile_id))
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
@skill.command
|
|
472
|
+
async def list_profiles() -> dict:
|
|
473
|
+
"""List all browser profiles."""
|
|
474
|
+
browser = await _get_browser()
|
|
475
|
+
return _to_dict(await ALL_TOOLS["list_profiles"](browser))
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
@skill.command
|
|
479
|
+
async def current_profile() -> dict:
|
|
480
|
+
"""Get the current browser profile."""
|
|
481
|
+
browser = await _get_browser()
|
|
482
|
+
return _to_dict(await ALL_TOOLS["current_profile"](browser))
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
@skill.command
|
|
486
|
+
async def save_profile() -> dict:
|
|
487
|
+
"""Save the current browser profile."""
|
|
488
|
+
browser = await _get_browser()
|
|
489
|
+
return _to_dict(await ALL_TOOLS["save_profile"](browser))
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
@skill.command
|
|
493
|
+
async def delete_profile(
|
|
494
|
+
profile_id: str = Arg(help="Profile ID to delete", required=True),
|
|
495
|
+
) -> dict:
|
|
496
|
+
"""Delete a browser profile."""
|
|
497
|
+
browser = await _get_browser()
|
|
498
|
+
return _to_dict(await ALL_TOOLS["delete_profile"](browser, profile_id=profile_id))
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
# ── Visual (1) ───────────────────────────────────────────────────────────────
|
|
502
|
+
|
|
503
|
+
@skill.command
|
|
504
|
+
async def show_toast(
|
|
505
|
+
message: str = Arg(help="Toast message", required=True),
|
|
506
|
+
type: str = Arg("--type", default="info", choices=["info", "success", "warning", "error"], help="Toast type"),
|
|
507
|
+
duration: int = Arg("--duration", default=3000, help="Duration in milliseconds"),
|
|
508
|
+
) -> dict:
|
|
509
|
+
"""Show a toast notification in the browser."""
|
|
510
|
+
browser = await _get_browser()
|
|
511
|
+
return _to_dict(await ALL_TOOLS["show_toast"](browser, message=message, type=type, duration=duration))
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
# ── Network (6) ──────────────────────────────────────────────────────────────
|
|
515
|
+
|
|
516
|
+
@skill.command
|
|
517
|
+
async def network_enable(
|
|
518
|
+
enabled: bool = Arg("--enabled", action="store_true", default=True, help="Enable network capture"),
|
|
519
|
+
) -> dict:
|
|
520
|
+
"""Enable or disable network request capture."""
|
|
521
|
+
browser = await _get_browser()
|
|
522
|
+
return _to_dict(await ALL_TOOLS["network_enable"](browser, enabled=enabled))
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
@skill.command
|
|
526
|
+
async def network_get(
|
|
527
|
+
url_pattern: str = Arg("--url-pattern", default="", help="Filter by URL pattern"),
|
|
528
|
+
methods: str = Arg("--methods", default=None, nargs="*", help="Filter by HTTP methods"),
|
|
529
|
+
types: str = Arg("--types", default=None, nargs="*", help="Filter by request types"),
|
|
530
|
+
limit: int = Arg("--limit", default=100, help="Maximum number of requests"),
|
|
531
|
+
) -> dict:
|
|
532
|
+
"""Get captured network requests."""
|
|
533
|
+
browser = await _get_browser()
|
|
534
|
+
return _to_dict(await ALL_TOOLS["network_get"](browser, url_pattern=url_pattern, methods=methods, types=types, limit=limit))
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
@skill.command
|
|
538
|
+
async def network_clear() -> dict:
|
|
539
|
+
"""Clear captured network requests."""
|
|
540
|
+
browser = await _get_browser()
|
|
541
|
+
return _to_dict(await ALL_TOOLS["network_clear"](browser))
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
@skill.command
|
|
545
|
+
async def network_stats() -> dict:
|
|
546
|
+
"""Get network capture statistics."""
|
|
547
|
+
browser = await _get_browser()
|
|
548
|
+
return _to_dict(await ALL_TOOLS["network_stats"](browser))
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
@skill.command
|
|
552
|
+
async def network_get_json(
|
|
553
|
+
url_pattern: str = Arg("--url-pattern", default="", help="Filter by URL pattern"),
|
|
554
|
+
) -> dict:
|
|
555
|
+
"""Extract JSON from captured network responses."""
|
|
556
|
+
browser = await _get_browser()
|
|
557
|
+
return _to_dict(await ALL_TOOLS["network_get_json"](browser, url_pattern=url_pattern))
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
@skill.command
|
|
561
|
+
async def network_export_har(
|
|
562
|
+
path: str = Arg("--path", default="", help="File path for HAR export"),
|
|
563
|
+
) -> dict:
|
|
564
|
+
"""Export network requests as HAR file."""
|
|
565
|
+
browser = await _get_browser()
|
|
566
|
+
return _to_dict(await ALL_TOOLS["network_export_har"](browser, path=path))
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
# ── Tabs (11) ────────────────────────────────────────────────────────────────
|
|
570
|
+
|
|
571
|
+
@skill.command
|
|
572
|
+
async def tab_new() -> dict:
|
|
573
|
+
"""Open a new empty tab."""
|
|
574
|
+
browser = await _get_browser()
|
|
575
|
+
return _to_dict(await ALL_TOOLS["tab_new"](browser))
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
@skill.command
|
|
579
|
+
async def tab_new_with_url(
|
|
580
|
+
url: str = Arg(help="URL to open in new tab", required=True),
|
|
581
|
+
) -> dict:
|
|
582
|
+
"""Open a new tab with a URL."""
|
|
583
|
+
browser = await _get_browser()
|
|
584
|
+
return _to_dict(await ALL_TOOLS["tab_new_with_url"](browser, url=url))
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
@skill.command
|
|
588
|
+
async def tab_switch(
|
|
589
|
+
tab_id: str = Arg(help="Tab ID to switch to", required=True),
|
|
590
|
+
) -> dict:
|
|
591
|
+
"""Switch to a tab by ID."""
|
|
592
|
+
browser = await _get_browser()
|
|
593
|
+
return _to_dict(await ALL_TOOLS["tab_switch"](browser, tab_id=tab_id))
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
@skill.command
|
|
597
|
+
async def tab_switch_index(
|
|
598
|
+
index: int = Arg(help="Tab index to switch to", required=True),
|
|
599
|
+
) -> dict:
|
|
600
|
+
"""Switch to a tab by index."""
|
|
601
|
+
browser = await _get_browser()
|
|
602
|
+
return _to_dict(await ALL_TOOLS["tab_switch_index"](browser, index=index))
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
@skill.command
|
|
606
|
+
async def tab_close(
|
|
607
|
+
tab_id: str = Arg(help="Tab ID to close", required=True),
|
|
608
|
+
) -> dict:
|
|
609
|
+
"""Close a tab by ID."""
|
|
610
|
+
browser = await _get_browser()
|
|
611
|
+
return _to_dict(await ALL_TOOLS["tab_close"](browser, tab_id=tab_id))
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
@skill.command
|
|
615
|
+
async def tab_close_all_except(
|
|
616
|
+
tab_id: str = Arg(help="Tab ID to keep", required=True),
|
|
617
|
+
) -> dict:
|
|
618
|
+
"""Close all tabs except the specified one."""
|
|
619
|
+
browser = await _get_browser()
|
|
620
|
+
return _to_dict(await ALL_TOOLS["tab_close_all_except"](browser, tab_id=tab_id))
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
@skill.command
|
|
624
|
+
async def tab_list() -> dict:
|
|
625
|
+
"""List all open tabs."""
|
|
626
|
+
browser = await _get_browser()
|
|
627
|
+
return _to_dict(await ALL_TOOLS["tab_list"](browser))
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
@skill.command
|
|
631
|
+
async def tab_current() -> dict:
|
|
632
|
+
"""Get the current tab info."""
|
|
633
|
+
browser = await _get_browser()
|
|
634
|
+
return _to_dict(await ALL_TOOLS["tab_current"](browser))
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
@skill.command
|
|
638
|
+
async def tab_next() -> dict:
|
|
639
|
+
"""Switch to the next tab."""
|
|
640
|
+
browser = await _get_browser()
|
|
641
|
+
return _to_dict(await ALL_TOOLS["tab_next"](browser))
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
@skill.command
|
|
645
|
+
async def tab_previous() -> dict:
|
|
646
|
+
"""Switch to the previous tab."""
|
|
647
|
+
browser = await _get_browser()
|
|
648
|
+
return _to_dict(await ALL_TOOLS["tab_previous"](browser))
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
@skill.command
|
|
652
|
+
async def tab_count() -> dict:
|
|
653
|
+
"""Get the number of open tabs."""
|
|
654
|
+
browser = await _get_browser()
|
|
655
|
+
return _to_dict(await ALL_TOOLS["tab_count"](browser))
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
# ── Turnstile + Markdown (4) ─────────────────────────────────────────────────
|
|
659
|
+
|
|
660
|
+
@skill.command
|
|
661
|
+
async def solve_turnstile(
|
|
662
|
+
max_attempts: int = Arg("--max-attempts", default=3, help="Maximum solve attempts"),
|
|
663
|
+
token_timeout: float = Arg("--token-timeout", default=30.0, help="Token wait timeout"),
|
|
664
|
+
) -> dict:
|
|
665
|
+
"""Solve a Cloudflare Turnstile challenge."""
|
|
666
|
+
browser = await _get_browser()
|
|
667
|
+
return _to_dict(await ALL_TOOLS["solve_turnstile"](browser, max_attempts=max_attempts, token_timeout=token_timeout))
|
|
668
|
+
|
|
669
|
+
|
|
670
|
+
@skill.command
|
|
671
|
+
async def is_turnstile_present() -> dict:
|
|
672
|
+
"""Check if a Turnstile challenge is present."""
|
|
673
|
+
browser = await _get_browser()
|
|
674
|
+
return _to_dict(await ALL_TOOLS["is_turnstile_present"](browser))
|
|
675
|
+
|
|
676
|
+
|
|
677
|
+
@skill.command
|
|
678
|
+
async def wait_turnstile(
|
|
679
|
+
timeout: float = Arg("--timeout", default=30.0, help="Timeout in seconds"),
|
|
680
|
+
) -> dict:
|
|
681
|
+
"""Wait for and solve a Turnstile challenge."""
|
|
682
|
+
browser = await _get_browser()
|
|
683
|
+
return _to_dict(await ALL_TOOLS["wait_turnstile"](browser, timeout=timeout))
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
@skill.command
|
|
687
|
+
async def get_markdown() -> dict:
|
|
688
|
+
"""Convert current page to markdown."""
|
|
689
|
+
browser = await _get_browser()
|
|
690
|
+
return _to_dict(await ALL_TOOLS["get_markdown"](browser))
|
|
691
|
+
|
|
692
|
+
|
|
693
|
+
# ── Advanced (9) ─────────────────────────────────────────────────────────────
|
|
694
|
+
|
|
695
|
+
@skill.command
|
|
696
|
+
async def extract_meta() -> dict:
|
|
697
|
+
"""Extract meta tags from the page."""
|
|
698
|
+
browser = await _get_browser()
|
|
699
|
+
return _to_dict(await ALL_TOOLS["extract_meta"](browser))
|
|
700
|
+
|
|
701
|
+
|
|
702
|
+
@skill.command
|
|
703
|
+
async def extract_tables() -> dict:
|
|
704
|
+
"""Extract all tables from the page."""
|
|
705
|
+
browser = await _get_browser()
|
|
706
|
+
return _to_dict(await ALL_TOOLS["extract_tables"](browser))
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
@skill.command
|
|
710
|
+
async def extract_table(
|
|
711
|
+
selector: str = Arg(help="Table selector", required=True),
|
|
712
|
+
) -> dict:
|
|
713
|
+
"""Extract a specific table."""
|
|
714
|
+
browser = await _get_browser()
|
|
715
|
+
return _to_dict(await ALL_TOOLS["extract_table"](browser, selector=selector))
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
@skill.command
|
|
719
|
+
async def extract_jsonld() -> dict:
|
|
720
|
+
"""Extract JSON-LD structured data."""
|
|
721
|
+
browser = await _get_browser()
|
|
722
|
+
return _to_dict(await ALL_TOOLS["extract_jsonld"](browser))
|
|
723
|
+
|
|
724
|
+
|
|
725
|
+
@skill.command
|
|
726
|
+
async def extract_jsonld_type(
|
|
727
|
+
schema_type: str = Arg(help="Schema.org type to filter", required=True),
|
|
728
|
+
) -> dict:
|
|
729
|
+
"""Extract JSON-LD data by schema type."""
|
|
730
|
+
browser = await _get_browser()
|
|
731
|
+
return _to_dict(await ALL_TOOLS["extract_jsonld_type"](browser, schema_type=schema_type))
|
|
732
|
+
|
|
733
|
+
|
|
734
|
+
@skill.command
|
|
735
|
+
async def extract_all() -> dict:
|
|
736
|
+
"""Extract all structured data from the page."""
|
|
737
|
+
browser = await _get_browser()
|
|
738
|
+
return _to_dict(await ALL_TOOLS["extract_all"](browser))
|
|
739
|
+
|
|
740
|
+
|
|
741
|
+
@skill.command
|
|
742
|
+
async def get_ax_tree(
|
|
743
|
+
max_depth: int = Arg("--max-depth", default=10, help="Maximum tree depth"),
|
|
744
|
+
) -> dict:
|
|
745
|
+
"""Get the accessibility tree."""
|
|
746
|
+
browser = await _get_browser()
|
|
747
|
+
return _to_dict(await ALL_TOOLS["get_ax_tree"](browser, max_depth=max_depth))
|
|
748
|
+
|
|
749
|
+
|
|
750
|
+
@skill.command
|
|
751
|
+
async def get_ax_text(
|
|
752
|
+
max_depth: int = Arg("--max-depth", default=10, help="Maximum tree depth"),
|
|
753
|
+
) -> dict:
|
|
754
|
+
"""Get accessibility tree as text."""
|
|
755
|
+
browser = await _get_browser()
|
|
756
|
+
return _to_dict(await ALL_TOOLS["get_ax_text"](browser, max_depth=max_depth))
|
|
757
|
+
|
|
758
|
+
|
|
759
|
+
@skill.command
|
|
760
|
+
async def get_full_state() -> dict:
|
|
761
|
+
"""Get complete page state."""
|
|
762
|
+
browser = await _get_browser()
|
|
763
|
+
return _to_dict(await ALL_TOOLS["get_full_state"](browser))
|
|
764
|
+
|
|
765
|
+
|
|
766
|
+
# ── Pool (2) ─────────────────────────────────────────────────────────────────
|
|
767
|
+
|
|
768
|
+
@skill.command
|
|
769
|
+
async def pool_navigate(
|
|
770
|
+
urls: str = Arg(help="URLs to navigate in parallel", required=True, nargs="+"),
|
|
771
|
+
timeout: float = Arg("--timeout", default=30.0, help="Navigation timeout"),
|
|
772
|
+
) -> dict:
|
|
773
|
+
"""Navigate multiple URLs in parallel."""
|
|
774
|
+
browser = await _get_browser()
|
|
775
|
+
return _to_dict(await ALL_TOOLS["pool_navigate"](browser, urls=urls, timeout=timeout))
|
|
776
|
+
|
|
777
|
+
|
|
778
|
+
@skill.command
|
|
779
|
+
async def pool_stats() -> dict:
|
|
780
|
+
"""Get pool statistics."""
|
|
781
|
+
browser = await _get_browser()
|
|
782
|
+
return _to_dict(await ALL_TOOLS["pool_stats"](browser))
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
# ── Entry point ──────────────────────────────────────────────────────────────
|
|
786
|
+
|
|
787
|
+
def main() -> None:
|
|
788
|
+
skill.run()
|
|
789
|
+
|
|
790
|
+
|
|
791
|
+
if __name__ == "__main__":
|
|
792
|
+
main()
|