patchright-cli 0.8.0__py3-none-any.whl → 0.9.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.
@@ -1,3 +1,3 @@
1
1
  """patchright-cli — Undetected browser automation CLI using Patchright."""
2
2
 
3
- __version__ = "0.8.0"
3
+ __version__ = "0.9.1"
@@ -59,8 +59,21 @@ patchright-cli goto "https://example.com/search?q=cats&page=2"
59
59
  ```
60
60
 
61
61
  The same applies to `#` (a bash comment), spaces, and `%` (expanded by
62
- cmd.exe), and to any argument holding shell metacharacters. Quoting always
63
- works; leave it on.
62
+ cmd.exe), and to any argument holding shell metacharacters. Quoting works for
63
+ the installed `patchright-cli.exe` in bash, PowerShell and cmd.exe; leave it on.
64
+
65
+ On Windows, two cases need more than quotes:
66
+
67
+ ```
68
+ # cmd.exe without quotes: escape the & as ^&. Inside quotes ^ is kept
69
+ # literally, so use one or the other, never both.
70
+ patchright-cli goto https://example.com/search?q=cats^&page=2
71
+
72
+ # PowerShell calling a .cmd/.bat wrapper (such as the repo's patchright-cli.cmd):
73
+ # PowerShell drops the quotes and cmd.exe splits at &. Pass the rest verbatim
74
+ # with --%. Don't use --% with patchright-cli.exe: in PowerShell 7 the quotes end up in the URL.
75
+ .\patchright-cli.cmd --% goto "https://example.com/search?q=cats&page=2"
76
+ ```
64
77
 
65
78
  ## Global options
66
79
 
@@ -320,6 +333,25 @@ show # Live dashboard
320
333
  show --annotate # Ask a human to mark up the page and wait for it
321
334
  ```
322
335
 
336
+ ### Emulation
337
+
338
+ Each setting holds for the whole session: every open tab, and tabs and popups
339
+ opened later (a popup gets it just after it opens). `clear-*` drops just that
340
+ one, back to the browser default.
341
+
342
+ ```bash
343
+ set-color-scheme dark # dark | light | no-preference
344
+ clear-color-scheme
345
+ set-reduced-motion reduce # reduce | no-preference
346
+ clear-reduced-motion
347
+ set-forced-colors active # active | none
348
+ clear-forced-colors
349
+ set-contrast more # more | no-preference
350
+ clear-contrast
351
+ set-media print # screen | print
352
+ clear-media
353
+ ```
354
+
323
355
  ### Storage
324
356
 
325
357
  ```bash
patchright_cli/cli.py CHANGED
@@ -17,7 +17,7 @@ from pathlib import Path
17
17
  import click
18
18
 
19
19
  from patchright_cli import __version__
20
- from patchright_cli.daemon import DEFAULT_PORT, ensure_daemon_running
20
+ from patchright_cli.daemon import DEFAULT_PORT, PROTOCOL_VERSION, ensure_daemon_running, read_daemon_token, token_path
21
21
 
22
22
 
23
23
  def _soften_output_encoding(*streams) -> None:
@@ -392,6 +392,11 @@ def _send_command(command: str, args: list, options: dict, port: int = DEFAULT_P
392
392
  "options": options,
393
393
  "cwd": os.getcwd(),
394
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
395
400
  data = json.dumps(msg).encode("utf-8")
396
401
 
397
402
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@@ -426,7 +431,7 @@ def _recv_exact(sock: socket.socket, n: int) -> bytes:
426
431
 
427
432
  COMMANDS_HELP = {
428
433
  # Core
429
- "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]",
430
435
  "attach": "attach --cdp=<url> [--context=new|host] Attach to running Chrome via CDP",
431
436
  "goto": "goto <url> Navigate to URL",
432
437
  "click": "click <ref> [button] Click element [--modifiers=Alt,Shift]",
@@ -492,6 +497,17 @@ COMMANDS_HELP = {
492
497
  # Upload / Resize
493
498
  "upload": "upload <file> [ref] Upload file to input",
494
499
  "resize": "resize <w> <h> Resize viewport",
500
+ # Emulation
501
+ "set-color-scheme": "set-color-scheme <dark|light|no-preference> Emulate prefers-color-scheme",
502
+ "clear-color-scheme": "clear-color-scheme Stop emulating prefers-color-scheme",
503
+ "set-reduced-motion": "set-reduced-motion <reduce|no-preference> Emulate prefers-reduced-motion",
504
+ "clear-reduced-motion": "clear-reduced-motion Stop emulating prefers-reduced-motion",
505
+ "set-forced-colors": "set-forced-colors <active|none> Emulate forced-colors",
506
+ "clear-forced-colors": "clear-forced-colors Stop emulating forced-colors",
507
+ "set-contrast": "set-contrast <more|no-preference> Emulate prefers-contrast",
508
+ "clear-contrast": "clear-contrast Stop emulating prefers-contrast",
509
+ "set-media": "set-media <screen|print> Emulate CSS media type",
510
+ "clear-media": "clear-media Stop emulating the CSS media type",
495
511
  # State
496
512
  "state-save": "state-save [file] Save cookies+storage to JSON",
497
513
  "state-load": "state-load <file> Load saved state",
@@ -542,6 +558,7 @@ COMMANDS_HELP = {
542
558
  # Setup
543
559
  "install": "install --skills Install skill files for AI agents",
544
560
  "mcp": "mcp [--http] [--host=HOST] [--port=N] Start MCP server (stdio by default)",
561
+ "daemon-info": "daemon-info --json Show daemon client protocol and token path without starting it",
545
562
  }
546
563
 
547
564
  ALL_COMMANDS = list(COMMANDS_HELP.keys())
@@ -614,6 +631,21 @@ def _print_help():
614
631
  ("Tabs", ["tab-list", "tab-new", "tab-close", "tab-select"]),
615
632
  ("Dialog", ["dialog-accept", "dialog-dismiss"]),
616
633
  ("Upload/Resize", ["upload", "resize"]),
634
+ (
635
+ "Emulation",
636
+ [
637
+ "set-color-scheme",
638
+ "clear-color-scheme",
639
+ "set-reduced-motion",
640
+ "clear-reduced-motion",
641
+ "set-forced-colors",
642
+ "clear-forced-colors",
643
+ "set-contrast",
644
+ "clear-contrast",
645
+ "set-media",
646
+ "clear-media",
647
+ ],
648
+ ),
617
649
  ("State", ["state-save", "state-load"]),
618
650
  (
619
651
  "Storage",
@@ -648,7 +680,7 @@ def _print_help():
648
680
  ("Profiles", ["profile-list", "profile-delete"]),
649
681
  ("Dashboard", ["show"]),
650
682
  ("Codegen", ["codegen", "codegen-stop"]),
651
- ("Setup", ["install", "mcp"]),
683
+ ("Setup", ["install", "mcp", "daemon-info"]),
652
684
  ]
653
685
  for cat_name, cmds in categories:
654
686
  click.echo(f"\n {cat_name}:")
@@ -688,6 +720,10 @@ def main():
688
720
  i = 0
689
721
  while i < len(argv):
690
722
  arg = argv[i]
723
+ if arg == "--":
724
+ # End of options: the rest is positional, even text that looks like a flag.
725
+ remaining.extend(argv[i:])
726
+ break
691
727
  if arg == "--headless":
692
728
  headless = True
693
729
  elif arg == "--persistent":
@@ -814,6 +850,22 @@ def main():
814
850
  _print_help()
815
851
  sys.exit(1)
816
852
 
853
+ if command == "daemon-info":
854
+ if not as_json or args or extra_opts:
855
+ click.echo("Usage: patchright-cli daemon-info --json [--port=N]", err=True)
856
+ sys.exit(1)
857
+ click.echo(
858
+ json.dumps(
859
+ {
860
+ "version": __version__,
861
+ "protocol": PROTOCOL_VERSION,
862
+ "port": port,
863
+ "token_path": str(token_path(port).resolve()),
864
+ }
865
+ )
866
+ )
867
+ return
868
+
817
869
  if command == "install":
818
870
  _handle_install(args + [k if v is True else f"{k}={v}" for k, v in extra_opts.items()])
819
871
  sys.exit(0)
@@ -832,8 +884,13 @@ def main():
832
884
  # Separate --key=value and --flag from positional args
833
885
  positional_args = []
834
886
  cmd_opts: dict = {}
887
+ end_of_options = False
835
888
  for a in args:
836
- if a.startswith("--") and "=" in a:
889
+ if end_of_options:
890
+ positional_args.append(a)
891
+ elif a == "--":
892
+ end_of_options = True
893
+ elif a.startswith("--") and "=" in a:
837
894
  k, v = a[2:].split("=", 1)
838
895
  cmd_opts[k] = v
839
896
  elif a.startswith("--"):
@@ -855,7 +912,7 @@ def main():
855
912
  except FileNotFoundError:
856
913
  click.echo(f"File not found: {file_opt}", err=True)
857
914
  sys.exit(1)
858
- elif args and args[0] == "-":
915
+ elif args and args[0] == "-" and not end_of_options:
859
916
  # Read JS from stdin
860
917
  args = [sys.stdin.read()]
861
918
  elif not args and not sys.stdin.isatty():