|
5 | 5 | import hashlib |
6 | 6 | import http.server |
7 | 7 | import json |
| 8 | +import os |
8 | 9 | import secrets |
9 | 10 | import socket |
| 11 | +import sys |
10 | 12 | import threading |
11 | 13 | import time |
12 | 14 | import urllib.parse |
@@ -646,18 +648,50 @@ def exchange_code_for_token(self, code: str, code_verifier: str, redirect_uri: s |
646 | 648 | return api_key |
647 | 649 |
|
648 | 650 |
|
649 | | -def _is_graphical_browser() -> bool: |
650 | | - text_browsers = {"lynx", "links", "w3m", "elinks", "links2"} |
651 | | - |
| 651 | +def get_browser_name_fallback() -> str | None: |
652 | 652 | try: |
653 | | - # Get the default browser |
654 | | - browser = webbrowser.get() |
655 | | - browser_name = getattr(browser, "name", "").lower() |
656 | | - |
657 | | - # Check if it's a known text browser |
658 | | - return all(text_browser not in browser_name for text_browser in text_browsers) |
| 653 | + controller = webbrowser.get() |
| 654 | + # controller.name exists for most browser controllers |
| 655 | + return getattr(controller, "name", None) |
659 | 656 | except Exception: |
660 | | - return True |
| 657 | + return None |
| 658 | + |
| 659 | + |
| 660 | +def should_attempt_browser_launch() -> bool: |
| 661 | + # A list of browser names that indicate we should not attempt to open a |
| 662 | + # web browser for the user. |
| 663 | + browser_blocklist = ["www-browser", "lynx", "links", "w3m", "elinks", "links2"] |
| 664 | + browser_env = os.environ.get("BROWSER") or get_browser_name_fallback() |
| 665 | + if browser_env and browser_env in browser_blocklist: |
| 666 | + return False |
| 667 | + |
| 668 | + # Common environment variables used in CI/CD or other non-interactive shells. |
| 669 | + if os.environ.get("CI") or os.environ.get("DEBIAN_FRONTEND") == "noninteractive": |
| 670 | + return False |
| 671 | + |
| 672 | + # The presence of SSH_CONNECTION indicates a remote session. |
| 673 | + # We should not attempt to launch a browser unless a display is explicitly available |
| 674 | + # (checked below for Linux). |
| 675 | + is_ssh = bool(os.environ.get("SSH_CONNECTION")) |
| 676 | + |
| 677 | + # On Linux, the presence of a display server is a strong indicator of a GUI. |
| 678 | + if sys.platform == "linux": |
| 679 | + # These are environment variables that can indicate a running compositor on |
| 680 | + # Linux. |
| 681 | + display_variables = ["DISPLAY", "WAYLAND_DISPLAY", "MIR_SOCKET"] |
| 682 | + has_display = any(os.environ.get(v) for v in display_variables) |
| 683 | + if not has_display: |
| 684 | + return False |
| 685 | + |
| 686 | + # If in an SSH session on a non-Linux OS (e.g., macOS), don't launch browser. |
| 687 | + # The Linux case is handled above (it's allowed if DISPLAY is set). |
| 688 | + if is_ssh and sys.platform != "linux": |
| 689 | + return False |
| 690 | + |
| 691 | + # For non-Linux OSes, we generally assume a GUI is available |
| 692 | + # unless other signals (like SSH) suggest otherwise. |
| 693 | + # The `open` command's error handling will catch final edge cases. |
| 694 | + return True |
661 | 695 |
|
662 | 696 |
|
663 | 697 | def _wait_for_manual_code_input(oauth: OAuthHandler) -> None: |
@@ -706,7 +740,7 @@ def perform_oauth_signin() -> str | None: |
706 | 740 | click.echo("❌ Failed to start local server.") |
707 | 741 | return None |
708 | 742 |
|
709 | | - if _is_graphical_browser(): |
| 743 | + if should_attempt_browser_launch(): |
710 | 744 | # Try to open browser |
711 | 745 | click.echo("🌐 Opening browser to sign in to CodeFlash…") |
712 | 746 | with contextlib.suppress(Exception): |
|
0 commit comments