44import re
55import subprocess
66import sys
7+ import webbrowser
78from enum import Enum , auto
89from functools import lru_cache
910from pathlib import Path
1011from typing import TYPE_CHECKING , Any , Optional , Union , cast
1112
12- import click
1313import git
1414import tomlkit
1515from git import InvalidGitRepositoryError , Repo
@@ -287,8 +287,8 @@ def collect_setup_info() -> CLISetupInfo:
287287 curdir = Path .cwd ()
288288 # Check if the cwd is writable
289289 if not os .access (curdir , os .W_OK ):
290- click . echo (f"❌ The current directory isn't writable, please check your folder permissions and try again.{ LF } " )
291- click . echo ("It's likely you don't have write permissions for this folder." )
290+ console . print (f"❌ The current directory isn't writable, please check your folder permissions and try again.{ LF } " )
291+ console . print ("It's likely you don't have write permissions for this folder." )
292292 sys .exit (1 )
293293
294294 # Check for the existence of pyproject.toml or setup.py
@@ -344,13 +344,13 @@ def collect_setup_info() -> CLISetupInfo:
344344 # Validate the path is safe and exists
345345 path_obj = Path (custom_path_str )
346346 if not path_obj .exists () or not path_obj .is_dir ():
347- click . echo (f"❌ Path does not exist or is not a directory: { custom_path_str } " )
347+ console . print (f"❌ Path does not exist or is not a directory: { custom_path_str } " )
348348 console .print () # Add spacing before retry
349349 continue
350350 is_valid , error_msg = validate_relative_directory_path (custom_path_str )
351351 if not is_valid :
352- click . echo (f"❌ Invalid path: { error_msg } " )
353- click . echo ("Please enter a valid relative directory path." )
352+ console . print (f"❌ Invalid path: { error_msg } " )
353+ console . print ("Please enter a valid relative directory path." )
354354 console .print () # Add spacing before retry
355355 continue # Retry the prompt
356356 module_root = Path (custom_path_str )
@@ -392,7 +392,7 @@ def collect_setup_info() -> CLISetupInfo:
392392 if tests_root_answer == create_for_me_option :
393393 tests_root = Path (curdir ) / (default_tests_subdir or "tests" )
394394 tests_root .mkdir ()
395- click . echo (f"✅ Created directory { tests_root } { os .path .sep } { LF } " )
395+ console . print (f"✅ Created directory { tests_root } { os .path .sep } { LF } " )
396396 elif tests_root_answer == custom_dir_option :
397397 custom_tests_panel = Panel (
398398 Text (
@@ -417,14 +417,14 @@ def collect_setup_info() -> CLISetupInfo:
417417 # Validate the path exists
418418 path_obj = Path (custom_tests_path_str )
419419 if not path_obj .exists () or not path_obj .is_dir ():
420- click . echo (f"❌ Path does not exist or is not a directory: { custom_tests_path_str } " )
420+ console . print (f"❌ Path does not exist or is not a directory: { custom_tests_path_str } " )
421421 console .print () # Add spacing before retry
422422 continue
423423 # Validate the path is safe
424424 is_valid , error_msg = validate_relative_directory_path (custom_tests_path_str )
425425 if not is_valid :
426- click . echo (f"❌ Invalid path: { error_msg } " )
427- click . echo ("Please enter a valid relative directory path." )
426+ console . print (f"❌ Invalid path: { error_msg } " )
427+ console . print ("Please enter a valid relative directory path." )
428428 console .print () # Add spacing before retry
429429 continue # Retry the prompt
430430 tests_root = Path (curdir ) / Path (custom_tests_path_str )
@@ -507,7 +507,7 @@ def collect_setup_info() -> CLISetupInfo:
507507 else :
508508 git_remote = git_remotes [0 ]
509509 else :
510- click . echo (
510+ console . print (
511511 "No git remotes found. You can still use Codeflash locally, but you'll need to set up a remote "
512512 "repository to use GitHub features."
513513 )
@@ -529,8 +529,8 @@ def collect_setup_info() -> CLISetupInfo:
529529
530530
531531def check_for_toml_or_setup_file () -> str | None :
532- click . echo ()
533- click . echo ("Checking for pyproject.toml or setup.py…\r " , nl = False )
532+ console . print ()
533+ console . print ("Checking for pyproject.toml or setup.py…\r " )
534534 curdir = Path .cwd ()
535535 pyproject_toml_path = curdir / "pyproject.toml"
536536 setup_py_path = curdir / "setup.py"
@@ -539,21 +539,21 @@ def check_for_toml_or_setup_file() -> str | None:
539539 try :
540540 pyproject_toml_content = pyproject_toml_path .read_text (encoding = "utf8" )
541541 project_name = tomlkit .parse (pyproject_toml_content )["tool" ]["poetry" ]["name" ]
542- click . echo (f"✅ I found a pyproject.toml for your project { project_name } ." )
542+ console . print (f"✅ I found a pyproject.toml for your project { project_name } ." )
543543 ph ("cli-pyproject-toml-found-name" )
544544 except Exception :
545- click . echo ("✅ I found a pyproject.toml for your project." )
545+ console . print ("✅ I found a pyproject.toml for your project." )
546546 ph ("cli-pyproject-toml-found" )
547547 else :
548548 if setup_py_path .exists ():
549549 setup_py_content = setup_py_path .read_text (encoding = "utf8" )
550550 project_name_match = re .search (r"setup\s*\([^)]*?name\s*=\s*['\"](.*?)['\"]" , setup_py_content , re .DOTALL )
551551 if project_name_match :
552552 project_name = project_name_match .group (1 )
553- click . echo (f"✅ Found setup.py for your project { project_name } " )
553+ console . print (f"✅ Found setup.py for your project { project_name } " )
554554 ph ("cli-setup-py-found-name" )
555555 else :
556- click . echo ("✅ Found setup.py." )
556+ console . print ("✅ Found setup.py." )
557557 ph ("cli-setup-py-found" )
558558 toml_info_panel = Panel (
559559 Text (
@@ -574,7 +574,7 @@ def check_for_toml_or_setup_file() -> str | None:
574574 if result .command is None or result .value == "No" :
575575 apologize_and_exit ()
576576 create_empty_pyproject_toml (pyproject_toml_path )
577- click . echo ()
577+ console . print ()
578578 return cast ("str" , project_name )
579579
580580
@@ -604,7 +604,7 @@ def create_empty_pyproject_toml(pyproject_toml_path: Path) -> None:
604604 console .input ()
605605 ph ("cli-created-pyproject-toml" )
606606 except OSError :
607- click . echo ("❌ Failed to create pyproject.toml. Please check your disk permissions and available space." )
607+ console . print ("❌ Failed to create pyproject.toml. Please check your disk permissions and available space." )
608608 apologize_and_exit ()
609609
610610
@@ -616,7 +616,7 @@ def install_github_actions(override_formatter_check: bool = False) -> None: # n
616616 try :
617617 repo = Repo (config ["module_root" ], search_parent_directories = True )
618618 except git .InvalidGitRepositoryError :
619- click . echo (
619+ console . print (
620620 "Skipping GitHub action installation for continuous optimization because you're not in a git repository."
621621 )
622622 return
@@ -735,7 +735,7 @@ def install_github_actions(override_formatter_check: bool = False) -> None: # n
735735 console .print (f"\n 📍 Press Enter to open: { get_github_secrets_page_url (repo )} " )
736736 console .input ()
737737
738- click . launch (get_github_secrets_page_url (repo ))
738+ webbrowser . open (get_github_secrets_page_url (repo ))
739739
740740 # Post-launch message panel
741741 launch_panel = Panel (
@@ -750,9 +750,9 @@ def install_github_actions(override_formatter_check: bool = False) -> None: # n
750750 border_style = "bright_cyan" ,
751751 )
752752 console .print (launch_panel )
753- click . pause ( )
754- click . echo ()
755- click . echo (
753+ console . input ( "Press Enter to continue..." )
754+ console . print ()
755+ console . print (
756756 f"Please edit, commit and push this GitHub actions file to your repo, and you're all set!{ LF } "
757757 f"🚀 Codeflash is now configured to automatically optimize new Github PRs!{ LF } "
758758 )
@@ -853,7 +853,7 @@ def customize_codeflash_yaml_content(
853853 with toml_path .open (encoding = "utf8" ) as pyproject_file :
854854 pyproject_data = tomlkit .parse (pyproject_file .read ())
855855 except FileNotFoundError :
856- click . echo (
856+ console . print (
857857 f"I couldn't find a pyproject.toml in the current directory.{ LF } "
858858 f"Please create a new empty pyproject.toml file here, OR if you use poetry then run `poetry init`, OR run `codeflash init` again from a directory with an existing pyproject.toml file."
859859 )
@@ -885,7 +885,7 @@ def get_formatter_cmds(formatter: str) -> list[str]:
885885 if formatter == "ruff" :
886886 return ["ruff check --exit-zero --fix $file" , "ruff format $file" ]
887887 if formatter == "other" :
888- click . echo (
888+ console . print (
889889 "🔧 In pyproject.toml, please replace 'your-formatter' with the command you use to format your code."
890890 )
891891 return ["your-formatter $file" ]
@@ -906,7 +906,7 @@ def configure_pyproject_toml(
906906 with toml_path .open (encoding = "utf8" ) as pyproject_file :
907907 pyproject_data = tomlkit .parse (pyproject_file .read ())
908908 except FileNotFoundError :
909- click . echo (
909+ console . print (
910910 f"I couldn't find a pyproject.toml in the current directory.{ LF } "
911911 f"Please create a new empty pyproject.toml file here, OR if you use poetry then run `poetry init`, OR run `codeflash init` again from a directory with an existing pyproject.toml file."
912912 )
@@ -951,88 +951,65 @@ def configure_pyproject_toml(
951951
952952 with toml_path .open ("w" , encoding = "utf8" ) as pyproject_file :
953953 pyproject_file .write (tomlkit .dumps (pyproject_data ))
954- click . echo (f"Added Codeflash configuration to { toml_path } " )
955- click . echo ()
954+ console . print (f"Added Codeflash configuration to { toml_path } " )
955+ console . print ()
956956 return True
957957
958958
959959def install_github_app (git_remote : str ) -> None :
960960 try :
961961 git_repo = git .Repo (search_parent_directories = True )
962962 except git .InvalidGitRepositoryError :
963- click . echo ("Skipping GitHub app installation because you're not in a git repository." )
963+ console . print ("Skipping GitHub app installation because you're not in a git repository." )
964964 return
965965
966966 if git_remote not in get_git_remotes (git_repo ):
967- click . echo (f"Skipping GitHub app installation, remote ({ git_remote } ) does not exist in this repository." )
967+ console . print (f"Skipping GitHub app installation, remote ({ git_remote } ) does not exist in this repository." )
968968 return
969969
970970 owner , repo = get_repo_owner_and_name (git_repo , git_remote )
971971
972972 if is_github_app_installed_on_repo (owner , repo , suppress_errors = True ):
973- click . echo (
973+ console . print (
974974 f"🐙 Looks like you've already installed the Codeflash GitHub app on this repository ({ owner } /{ repo } )! Continuing…"
975975 )
976976
977977 else :
978978 try :
979- click . prompt (
979+ console . print (
980980 f"Finally, you'll need to install the Codeflash GitHub app by choosing the repository you want to install Codeflash on.{ LF } "
981981 f"I will attempt to open the github app page - https://github.com/apps/codeflash-ai/installations/select_target { LF } "
982- f"Please, press ENTER to open the app installation page{ LF } " ,
983- default = "" ,
984- type = click .STRING ,
985- prompt_suffix = ">>> " ,
986- show_default = False ,
982+ f"Please, press ENTER to open the app installation page{ LF } "
987983 )
988- click .launch ("https://github.com/apps/codeflash-ai/installations/select_target" )
989- click .prompt (
990- f"Please, press ENTER once you've finished installing the github app from https://github.com/apps/codeflash-ai/installations/select_target{ LF } " ,
991- default = "" ,
992- type = click .STRING ,
993- prompt_suffix = ">>> " ,
994- show_default = False ,
984+ console .input (">>> " )
985+ webbrowser .open ("https://github.com/apps/codeflash-ai/installations/select_target" )
986+ console .print (
987+ f"Please, press ENTER once you've finished installing the github app from https://github.com/apps/codeflash-ai/installations/select_target{ LF } "
995988 )
989+ console .input (">>> " )
996990
997991 count = 2
998992 while not is_github_app_installed_on_repo (owner , repo , suppress_errors = True ):
999993 if count == 0 :
1000- click . echo (
994+ console . print (
1001995 f"❌ It looks like the Codeflash GitHub App is not installed on the repository { owner } /{ repo } .{ LF } "
1002996 f"You won't be able to create PRs with Codeflash until you install the app.{ LF } "
1003997 f"In the meantime you can make local only optimizations by using the '--no-pr' flag with codeflash.{ LF } "
1004998 )
1005999 break
1006- click . prompt (
1000+ console . print (
10071001 f"❌ It looks like the Codeflash GitHub App is not installed on the repository { owner } /{ repo } .{ LF } "
10081002 f"Please install it from https://github.com/apps/codeflash-ai/installations/select_target { LF } "
1009- f"Please, press ENTER to continue once you've finished installing the github app…{ LF } " ,
1010- default = "" ,
1011- type = click .STRING ,
1012- prompt_suffix = ">>> " ,
1013- show_default = False ,
1003+ f"Please, press ENTER to continue once you've finished installing the github app…{ LF } "
10141004 )
1005+ console .input (">>> " )
10151006 count -= 1
1016- except (KeyboardInterrupt , EOFError , click . exceptions . Abort ):
1007+ except (KeyboardInterrupt , EOFError ):
10171008 # leave empty line for the next prompt to be properly rendered
1018- click .echo ()
1019-
1020-
1021- class CFAPIKeyType (click .ParamType ):
1022- name = "cfapi-key"
1023-
1024- def convert (self , value : str , param : click .Parameter | None , ctx : click .Context | None ) -> str | None :
1025- value = value .strip ()
1026- if not value .startswith ("cf-" ) and value != "" :
1027- self .fail (
1028- f"That key [{ value } ] seems to be invalid. It should start with a 'cf-' prefix. Please try again." ,
1029- param ,
1030- ctx ,
1031- )
1032- return value
1009+ console .print ()
10331010
10341011
1035- # Returns True if the user entered a new API key, False if they used an existing one
1012+ #
10361013def prompt_api_key () -> bool :
10371014 """Prompt user for API key via OAuth or manual entry."""
10381015 # Check for existing API key
@@ -1081,15 +1058,15 @@ def prompt_api_key() -> bool:
10811058 shell_rc_path = get_shell_rc_path ()
10821059 if not shell_rc_path .exists () and os .name == "nt" :
10831060 shell_rc_path .touch ()
1084- click . echo (f"✅ Created { shell_rc_path } " )
1061+ console . print (f"✅ Created { shell_rc_path } " )
10851062
10861063 result = save_api_key_to_rc (api_key )
10871064 if is_successful (result ):
1088- click . echo (result .unwrap ())
1089- click . echo ("✅ Signed in successfully and API key saved!" )
1065+ console . print (result .unwrap ())
1066+ console . print ("✅ Signed in successfully and API key saved!" )
10901067 else :
1091- click . echo (result .failure ())
1092- click . pause ( )
1068+ console . print (result .failure ())
1069+ console . input ( "Press Enter to continue..." )
10931070
10941071 os .environ ["CODEFLASH_API_KEY" ] = api_key
10951072 ph ("cli-oauth-signin-completed" )
@@ -1100,35 +1077,42 @@ def enter_api_key_and_save_to_rc() -> None:
11001077 browser_launched = False
11011078 api_key = ""
11021079 while api_key == "" :
1103- api_key = click .prompt (
1104- f"Enter your Codeflash API key{ ' [or press Enter to open your API key page]' if not browser_launched else '' } " ,
1105- hide_input = False ,
1106- default = "" ,
1107- type = CFAPIKeyType (),
1108- show_default = False ,
1109- ).strip ()
1080+ result = prompts .text (
1081+ f"Enter your Codeflash API key{ ' [or press Enter to open your API key page]' if not browser_launched else '' } "
1082+ )
1083+ if result .command is None :
1084+ apologize_and_exit ()
1085+ return
1086+ api_key = result .value .strip ()
1087+
11101088 if api_key :
1089+ # Validate API key format
1090+ if not api_key .startswith ("cf-" ):
1091+ console .print (f"❌ That key [{ api_key } ] seems to be invalid. It should start with a 'cf-' prefix. Please try again." )
1092+ api_key = "" # Reset to retry
1093+ continue
11111094 break
1095+
11121096 if not browser_launched :
1113- click . echo (
1097+ console . print (
11141098 f"Opening your Codeflash API key page. Grab a key from there!{ LF } "
11151099 "You can also open this link manually: https://app.codeflash.ai/app/apikeys"
11161100 )
1117- click . launch ("https://app.codeflash.ai/app/apikeys" )
1101+ webbrowser . open ("https://app.codeflash.ai/app/apikeys" )
11181102 browser_launched = True # This does not work on remote consoles
11191103 shell_rc_path = get_shell_rc_path ()
11201104 if not shell_rc_path .exists () and os .name == "nt" :
11211105 # On Windows, create the appropriate file (PowerShell .ps1 or CMD .bat) in the user's home directory
11221106 shell_rc_path .parent .mkdir (parents = True , exist_ok = True )
11231107 shell_rc_path .touch ()
1124- click . echo (f"✅ Created { shell_rc_path } " )
1108+ console . print (f"✅ Created { shell_rc_path } " )
11251109 get_user_id (api_key = api_key ) # Used to verify whether the API key is valid.
11261110 result = save_api_key_to_rc (api_key )
11271111 if is_successful (result ):
1128- click . echo (result .unwrap ())
1112+ console . print (result .unwrap ())
11291113 else :
1130- click . echo (result .failure ())
1131- click . pause ( )
1114+ console . print (result .failure ())
1115+ console . input ( "Press Enter to continue..." )
11321116
11331117 os .environ ["CODEFLASH_API_KEY" ] = api_key
11341118
0 commit comments