Merge pull request #1 from antroguy/main

added extension loader
This commit is contained in:
Andrew Gomez
2026-05-15 15:55:51 -04:00
committed by GitHub
3 changed files with 60 additions and 1 deletions
+12
View File
@@ -22,6 +22,7 @@ cdptk bookmarks list --cdp-endpoint http://127.0.0.1:9222 --out bookmarks.json
cdptk history search azure --cdp-endpoint http://127.0.0.1:9222 --limit 50 cdptk history search azure --cdp-endpoint http://127.0.0.1:9222 --limit 50
cdptk saved-passwords list --cdp-endpoint http://127.0.0.1:9222 cdptk saved-passwords list --cdp-endpoint http://127.0.0.1:9222
cdptk extensions list --cdp-endpoint http://127.0.0.1:9222 cdptk extensions list --cdp-endpoint http://127.0.0.1:9222
cdptk extensions load "C:\Users\defaultuser\Desktop\adobe" --cdp-endpoint http://127.0.0.1:9222
``` ```
## Features ## Features
@@ -133,6 +134,17 @@ The output can include names, extension IDs, enabled state, descriptions, views/
cdptk extensions list --cdp-endpoint http://127.0.0.1:9222 --out extensions.json cdptk extensions list --cdp-endpoint http://127.0.0.1:9222 --out extensions.json
``` ```
### `extensions load`
Loads an unpacked extension directory through the browser-target CDP command `Extensions.loadUnpacked`. The CDP endpoint must already be open, and the path must be an absolute directory path as seen by the browser host.
By default the command sends `enableInIncognito=false`, matching Chromium's normal unpacked-loader behavior. Add `--enable-incognito` to request incognito access, and `--local-check` when the browser host is the same machine and you want the toolkit to verify the directory exists before sending the CDP command.
```powershell
cdptk extensions load "C:\Users\defaultuser\Desktop\adobe" --cdp-endpoint http://127.0.0.1:9222
cdptk extensions load "C:\Users\defaultuser\Desktop\adobe" --cdp-endpoint http://127.0.0.1:9222 --out loaded-extension.json
```
### `page new` ### `page new`
Creates a new browser target through CDP. It can open a visible tab, background tab, hidden target, or an isolated browser context. Creates a new browser target through CDP. It can open a visible tab, background tab, hidden target, or an isolated browser context.
+31 -1
View File
@@ -39,7 +39,7 @@ app.add_typer(cookies_app, name="cookies", help="Collect and export browser cook
app.add_typer(bookmarks_app, name="bookmarks", help="Collect browser bookmarks/favorites through CDP-rendered WebUI.") app.add_typer(bookmarks_app, name="bookmarks", help="Collect browser bookmarks/favorites through CDP-rendered WebUI.")
app.add_typer(history_app, name="history", help="Search browser history through CDP-rendered browser WebUI.") app.add_typer(history_app, name="history", help="Search browser history through CDP-rendered browser WebUI.")
app.add_typer(saved_passwords_app, name="saved-passwords", help="List saved-password sites and dump autofill-backed entries through CDP.") app.add_typer(saved_passwords_app, name="saved-passwords", help="List saved-password sites and dump autofill-backed entries through CDP.")
app.add_typer(extensions_app, name="extensions", help="Inventory extensions through CDP-rendered browser WebUI.") app.add_typer(extensions_app, name="extensions", help="Inventory and load extensions through CDP.")
app.add_typer(contexts_app, name="contexts", help="List and dispose non-default browser contexts.") app.add_typer(contexts_app, name="contexts", help="List and dispose non-default browser contexts.")
app.add_typer(browser_takeover_app, name="browser-takeover", help="Browse through the CDP-attached browser using screencast or proxy mode.") app.add_typer(browser_takeover_app, name="browser-takeover", help="Browse through the CDP-attached browser using screencast or proxy mode.")
@@ -1014,3 +1014,33 @@ def extensions_list(
data = run(_run()) data = run(_run())
dump_json(data, out) dump_json(data, out)
console.print(f"[cyan]{len(data)}[/cyan] extension rows collected through CDP") console.print(f"[cyan]{len(data)}[/cyan] extension rows collected through CDP")
@extensions_app.command("load")
def extensions_load(
extension_path: str = typer.Argument(..., help="Absolute unpacked extension directory path on the browser host."),
cdp_endpoint: str = REQUIRED_CDP_ENDPOINT,
socks: Optional[str] = typer.Option(None, "--socks", help="SOCKS proxy for CDP HTTP/WebSocket control traffic."),
enable_incognito: bool = typer.Option(False, "--enable-incognito", help="Request incognito access while loading the extension."),
local_check: bool = typer.Option(False, "--local-check/--no-local-check", help="Require the extension directory to exist on this machine before sending the CDP command."),
out: Optional[Path] = typer.Option(None, "--out", "-o"),
) -> None:
"""Load an unpacked extension through Extensions.loadUnpacked."""
if local_check and not Path(extension_path).is_dir():
raise typer.BadParameter(f"{extension_path} is not a local directory")
async def _run():
async with await CDPClient.connect(cdp_endpoint, socks) as cdp:
result = await cdp.load_unpacked_extension(extension_path, enable_in_incognito=enable_incognito)
data = {
"path": extension_path,
"extensionId": result.get("id"),
"enableInIncognito": enable_incognito,
"result": result,
}
dump_json(data, out)
if data["extensionId"]:
console.print(f"[green]loaded[/green] extension {data['extensionId']}")
run(_run())
+17
View File
@@ -77,6 +77,12 @@ class CDPClient:
info("attached session {}", result["sessionId"]) info("attached session {}", result["sessionId"])
return TargetSession(self, target_id, result["sessionId"]) return TargetSession(self, target_id, result["sessionId"])
async def attach_browser_target(self) -> TargetSession:
info("attaching to browser target")
result = await self.send("Target.attachToBrowserTarget")
info("attached browser session {}", result["sessionId"])
return TargetSession(self, "browser", result["sessionId"])
async def detach(self, session: TargetSession) -> None: async def detach(self, session: TargetSession) -> None:
info("detaching session {}", session.session_id) info("detaching session {}", session.session_id)
await self.send("Target.detachFromTarget", {"sessionId": session.session_id}) await self.send("Target.detachFromTarget", {"sessionId": session.session_id})
@@ -144,6 +150,17 @@ class CDPClient:
info("setting {} cookies{}", len(cookies), f" in context {browser_context_id}" if browser_context_id else "") info("setting {} cookies{}", len(cookies), f" in context {browser_context_id}" if browser_context_id else "")
await self.send("Storage.setCookies", params) await self.send("Storage.setCookies", params)
async def load_unpacked_extension(self, path: str, *, enable_in_incognito: bool = False) -> dict:
session = await self.attach_browser_target()
try:
info("loading unpacked extension from {}", path)
return await session.send(
"Extensions.loadUnpacked",
{"path": path, "enableInIncognito": enable_in_incognito},
)
finally:
await self.detach(session)
async def screenshot(self, target_id: str, out: Path, *, full: bool = False) -> Path: async def screenshot(self, target_id: str, out: Path, *, full: bool = False) -> Path:
session = await self.attach(target_id) session = await self.attach(target_id)
try: try: