patchright-cli 0.7.0__py3-none-any.whl → 0.9.0__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.
- patchright_cli/__init__.py +1 -1
- patchright_cli/cli.py +62 -5
- patchright_cli/daemon.py +543 -53
- patchright_cli/mcp_server.py +1420 -0
- {patchright_cli-0.7.0.dist-info → patchright_cli-0.9.0.dist-info}/METADATA +129 -1
- {patchright_cli-0.7.0.dist-info → patchright_cli-0.9.0.dist-info}/RECORD +9 -8
- {patchright_cli-0.7.0.dist-info → patchright_cli-0.9.0.dist-info}/WHEEL +0 -0
- {patchright_cli-0.7.0.dist-info → patchright_cli-0.9.0.dist-info}/entry_points.txt +0 -0
- {patchright_cli-0.7.0.dist-info → patchright_cli-0.9.0.dist-info}/licenses/LICENSE +0 -0
patchright_cli/__init__.py
CHANGED
patchright_cli/cli.py
CHANGED
|
@@ -6,6 +6,7 @@ receives the result, and prints it.
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
+
import argparse
|
|
9
10
|
import json
|
|
10
11
|
import os
|
|
11
12
|
import socket
|
|
@@ -16,7 +17,7 @@ from pathlib import Path
|
|
|
16
17
|
import click
|
|
17
18
|
|
|
18
19
|
from patchright_cli import __version__
|
|
19
|
-
from patchright_cli.daemon import DEFAULT_PORT, ensure_daemon_running
|
|
20
|
+
from patchright_cli.daemon import DEFAULT_PORT, ensure_daemon_running, read_daemon_token
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
def _soften_output_encoding(*streams) -> None:
|
|
@@ -248,6 +249,36 @@ def _handle_install(args: list) -> None:
|
|
|
248
249
|
click.echo(f"\npatchright-cli v{__version__} skills installed.")
|
|
249
250
|
|
|
250
251
|
|
|
252
|
+
def _handle_mcp(args: list[str]) -> None:
|
|
253
|
+
"""Handle the optional MCP server command."""
|
|
254
|
+
parser = argparse.ArgumentParser(
|
|
255
|
+
prog="patchright-cli mcp",
|
|
256
|
+
description="Run the MCP server. Stdio is the default transport.",
|
|
257
|
+
)
|
|
258
|
+
parser.add_argument("--http", action="store_true", help="Use Streamable HTTP instead of stdio.")
|
|
259
|
+
parser.add_argument("--host", default="127.0.0.1", help="HTTP bind host.")
|
|
260
|
+
parser.add_argument("--port", type=int, default=8000, help="HTTP bind port.")
|
|
261
|
+
options = parser.parse_args(args)
|
|
262
|
+
|
|
263
|
+
if not 1 <= options.port <= 65535:
|
|
264
|
+
parser.error("--port must be between 1 and 65535")
|
|
265
|
+
if not options.http and (options.host != "127.0.0.1" or options.port != 8000):
|
|
266
|
+
parser.error("--host and --port require --http")
|
|
267
|
+
|
|
268
|
+
try:
|
|
269
|
+
from patchright_cli.mcp_server import run_mcp_server
|
|
270
|
+
except ImportError as exc:
|
|
271
|
+
if exc.name not in {"fastmcp", "mcp", "pydantic"}:
|
|
272
|
+
raise
|
|
273
|
+
click.echo(
|
|
274
|
+
"MCP support is not installed. Install it with: pip install 'patchright-cli[mcp]'",
|
|
275
|
+
err=True,
|
|
276
|
+
)
|
|
277
|
+
raise SystemExit(1) from exc
|
|
278
|
+
|
|
279
|
+
run_mcp_server(http=options.http, host=options.host, port=options.port)
|
|
280
|
+
|
|
281
|
+
|
|
251
282
|
_VERSION_CHECK_INTERVAL = 24 * 60 * 60
|
|
252
283
|
_PYPI_URL = "https://pypi.org/pypi/patchright-cli/json"
|
|
253
284
|
|
|
@@ -361,6 +392,11 @@ def _send_command(command: str, args: list, options: dict, port: int = DEFAULT_P
|
|
|
361
392
|
"options": options,
|
|
362
393
|
"cwd": os.getcwd(),
|
|
363
394
|
}
|
|
395
|
+
# A daemon that predates tokens writes no token file and accepts the
|
|
396
|
+
# message without one, so a missing file is not an error here.
|
|
397
|
+
token = read_daemon_token(port)
|
|
398
|
+
if token:
|
|
399
|
+
msg["token"] = token
|
|
364
400
|
data = json.dumps(msg).encode("utf-8")
|
|
365
401
|
|
|
366
402
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
@@ -395,7 +431,7 @@ def _recv_exact(sock: socket.socket, n: int) -> bytes:
|
|
|
395
431
|
|
|
396
432
|
COMMANDS_HELP = {
|
|
397
433
|
# Core
|
|
398
|
-
"open": "open [url] Open browser (starts daemon if needed)",
|
|
434
|
+
"open": "open [url] Open browser (starts daemon if needed) [--block-networks=link-local|private] [--web-only]",
|
|
399
435
|
"attach": "attach --cdp=<url> [--context=new|host] Attach to running Chrome via CDP",
|
|
400
436
|
"goto": "goto <url> Navigate to URL",
|
|
401
437
|
"click": "click <ref> [button] Click element [--modifiers=Alt,Shift]",
|
|
@@ -510,6 +546,7 @@ COMMANDS_HELP = {
|
|
|
510
546
|
"codegen-stop": "codegen-stop [file] Stop recording and save script",
|
|
511
547
|
# Setup
|
|
512
548
|
"install": "install --skills Install skill files for AI agents",
|
|
549
|
+
"mcp": "mcp [--http] [--host=HOST] [--port=N] Start MCP server (stdio by default)",
|
|
513
550
|
}
|
|
514
551
|
|
|
515
552
|
ALL_COMMANDS = list(COMMANDS_HELP.keys())
|
|
@@ -616,7 +653,7 @@ def _print_help():
|
|
|
616
653
|
("Profiles", ["profile-list", "profile-delete"]),
|
|
617
654
|
("Dashboard", ["show"]),
|
|
618
655
|
("Codegen", ["codegen", "codegen-stop"]),
|
|
619
|
-
("Setup", ["install"]),
|
|
656
|
+
("Setup", ["install", "mcp"]),
|
|
620
657
|
]
|
|
621
658
|
for cat_name, cmds in categories:
|
|
622
659
|
click.echo(f"\n {cat_name}:")
|
|
@@ -630,6 +667,13 @@ def main():
|
|
|
630
667
|
_soften_output_encoding(sys.stdout, sys.stderr)
|
|
631
668
|
argv = sys.argv[1:]
|
|
632
669
|
|
|
670
|
+
# MCP is a self-contained subcommand with its own transport options.
|
|
671
|
+
# Dispatch it before global option parsing so `patchright-cli mcp --help`
|
|
672
|
+
# shows MCP help rather than the top-level CLI help.
|
|
673
|
+
if argv and argv[0] == "mcp":
|
|
674
|
+
_handle_mcp(argv[1:])
|
|
675
|
+
return
|
|
676
|
+
|
|
633
677
|
# Parse global options manually (before the command)
|
|
634
678
|
headless = False
|
|
635
679
|
persistent = False
|
|
@@ -649,6 +693,10 @@ def main():
|
|
|
649
693
|
i = 0
|
|
650
694
|
while i < len(argv):
|
|
651
695
|
arg = argv[i]
|
|
696
|
+
if arg == "--":
|
|
697
|
+
# End of options: the rest is positional, even text that looks like a flag.
|
|
698
|
+
remaining.extend(argv[i:])
|
|
699
|
+
break
|
|
652
700
|
if arg == "--headless":
|
|
653
701
|
headless = True
|
|
654
702
|
elif arg == "--persistent":
|
|
@@ -779,6 +827,10 @@ def main():
|
|
|
779
827
|
_handle_install(args + [k if v is True else f"{k}={v}" for k, v in extra_opts.items()])
|
|
780
828
|
sys.exit(0)
|
|
781
829
|
|
|
830
|
+
if command == "mcp":
|
|
831
|
+
_handle_mcp(args)
|
|
832
|
+
sys.exit(0)
|
|
833
|
+
|
|
782
834
|
_warn_stale_skills()
|
|
783
835
|
|
|
784
836
|
# Only on `open`: one browser launch already costs seconds, and this keeps
|
|
@@ -789,8 +841,13 @@ def main():
|
|
|
789
841
|
# Separate --key=value and --flag from positional args
|
|
790
842
|
positional_args = []
|
|
791
843
|
cmd_opts: dict = {}
|
|
844
|
+
end_of_options = False
|
|
792
845
|
for a in args:
|
|
793
|
-
if
|
|
846
|
+
if end_of_options:
|
|
847
|
+
positional_args.append(a)
|
|
848
|
+
elif a == "--":
|
|
849
|
+
end_of_options = True
|
|
850
|
+
elif a.startswith("--") and "=" in a:
|
|
794
851
|
k, v = a[2:].split("=", 1)
|
|
795
852
|
cmd_opts[k] = v
|
|
796
853
|
elif a.startswith("--"):
|
|
@@ -812,7 +869,7 @@ def main():
|
|
|
812
869
|
except FileNotFoundError:
|
|
813
870
|
click.echo(f"File not found: {file_opt}", err=True)
|
|
814
871
|
sys.exit(1)
|
|
815
|
-
elif args and args[0] == "-":
|
|
872
|
+
elif args and args[0] == "-" and not end_of_options:
|
|
816
873
|
# Read JS from stdin
|
|
817
874
|
args = [sys.stdin.read()]
|
|
818
875
|
elif not args and not sys.stdin.isatty():
|