Skip to content

Commit c90332f

Browse files
authored
Merge pull request #2157 from codeflash-ai/fix/cli-init-flow
Fix CLI init and optimize flow regressions
2 parents d7f95a5 + 62fd795 commit c90332f

51 files changed

Lines changed: 1429 additions & 271 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

codeflash/api/cfapi.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,11 @@ def make_cfapi_request(
102102

103103

104104
@lru_cache(maxsize=1)
105-
def get_user_id(api_key: Optional[str] = None) -> Optional[str]:
105+
def get_user_id(api_key: Optional[str] = None, *, suppress_errors: bool = False) -> Optional[str]:
106106
"""Retrieve the user's userid by making a request to the /cfapi/cli-get-user endpoint.
107107
108108
:param api_key: The API key to use. If None, uses get_codeflash_api_key().
109+
:param suppress_errors: If True, avoid exiting on auth/version errors and return None instead.
109110
:return: The userid or None if the request fails.
110111
"""
111112
lsp_enabled = is_LSP_enabled()
@@ -129,6 +130,9 @@ def get_user_id(api_key: Optional[str] = None) -> Optional[str]:
129130
if min_version and version.parse(min_version) > version.parse(__version__):
130131
msg = "Your Codeflash CLI version is outdated. Please update to the latest version using `pip install --upgrade codeflash`."
131132
console.print(f"[bold red]{msg}[/bold red]")
133+
if suppress_errors:
134+
logger.debug(msg)
135+
return None
132136
if lsp_enabled:
133137
logger.debug(msg)
134138
return f"Error: {msg}"
@@ -140,6 +144,9 @@ def get_user_id(api_key: Optional[str] = None) -> Optional[str]:
140144

141145
if response.status_code == 403:
142146
error_title = "Invalid Codeflash API key. The API key you provided is not valid."
147+
if suppress_errors:
148+
logger.debug(error_title)
149+
return None
143150
if lsp_enabled:
144151
return f"Error: {error_title}"
145152
msg = (

codeflash/cli_cmds/cli.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,11 +289,15 @@ def _handle_show_config() -> None:
289289
from codeflash.code_utils.config_parser import parse_config_file
290290

291291
config, config_file_path = parse_config_file()
292-
status = "Saved config"
292+
is_file_backed_config = config_file_path.is_file()
293+
status = "Saved config" if is_file_backed_config else "Auto-detected (zero-config)"
293294

294295
console.print()
295296
console.print(f"[bold]Codeflash Configuration[/bold] ({status})")
296-
console.print(f"[dim]Config file: {config_file_path}[/dim]")
297+
if is_file_backed_config:
298+
console.print(f"[dim]Config file: {config_file_path}[/dim]")
299+
else:
300+
console.print(f"[dim]Config source: {config_file_path}[/dim]")
297301
console.print()
298302

299303
table = Table(show_header=True, header_style="bold cyan")

codeflash/cli_cmds/cmd_init.py

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
from argparse import Namespace
4545

4646

47-
def init_codeflash() -> None:
47+
def init_codeflash(*, skip_confirm: bool = False, skip_api_key: bool = False) -> None:
4848
try:
4949
welcome_panel = Panel(
5050
Text(
@@ -63,34 +63,46 @@ def init_codeflash() -> None:
6363
project_language = detect_project_language()
6464

6565
if project_language == ProjectLanguage.GO:
66-
init_go_project()
66+
init_go_project(skip_confirm=skip_confirm, skip_api_key=skip_api_key)
6767
return
6868

6969
if project_language == ProjectLanguage.JAVA:
70-
init_java_project()
70+
init_java_project(skip_confirm=skip_confirm, skip_api_key=skip_api_key)
7171
return
7272

7373
if project_language in (ProjectLanguage.JAVASCRIPT, ProjectLanguage.TYPESCRIPT):
74-
init_js_project(project_language)
74+
init_js_project(project_language, skip_confirm=skip_confirm, skip_api_key=skip_api_key)
7575
return
7676

7777
# Python project flow
78-
did_add_new_key = prompt_api_key()
79-
80-
should_modify, config = should_modify_pyproject_toml()
78+
did_add_new_key = False if skip_api_key else prompt_api_key()
79+
git_remote = "origin"
8180

81+
should_modify, config = should_modify_pyproject_toml(skip_confirm=skip_confirm)
8282
git_remote = config.get("git_remote", "origin") if config else "origin"
8383

8484
if should_modify:
85-
setup_info: CLISetupInfo = collect_setup_info()
86-
git_remote = setup_info.git_remote
87-
configured = configure_pyproject_toml(setup_info)
88-
if not configured:
89-
apologize_and_exit()
85+
if skip_confirm:
86+
from codeflash.setup import detect_project, write_config
87+
88+
detected = detect_project()
89+
configured, message = write_config(detected)
90+
if configured:
91+
click.echo(message)
92+
click.echo()
93+
else:
94+
click.echo(message)
95+
apologize_and_exit()
96+
else:
97+
setup_info = collect_setup_info()
98+
git_remote = setup_info.git_remote
99+
configured = configure_pyproject_toml(setup_info)
100+
if not configured:
101+
apologize_and_exit()
90102

91103
install_github_app(git_remote)
92104

93-
install_github_actions(override_formatter_check=True)
105+
install_github_actions(override_formatter_check=True, skip_confirm=skip_confirm)
94106

95107
install_vscode_extension()
96108

codeflash/cli_cmds/console.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,43 @@
4141

4242
DEBUG_MODE = logging.getLogger().getEffectiveLevel() == logging.DEBUG
4343

44+
45+
def _configure_stdio_for_unicode_safety() -> None:
46+
"""Avoid hard failures when the active console encoding can't represent Unicode."""
47+
for stream in (sys.stdout, sys.stderr):
48+
reconfigure = getattr(stream, "reconfigure", None)
49+
if callable(reconfigure):
50+
with contextlib.suppress(OSError, ValueError):
51+
reconfigure(errors="replace")
52+
53+
54+
def _can_encode(text: str) -> bool:
55+
encoding = getattr(sys.stdout, "encoding", None) or "utf-8"
56+
try:
57+
text.encode(encoding)
58+
except UnicodeEncodeError:
59+
return False
60+
return True
61+
62+
63+
_configure_stdio_for_unicode_safety()
64+
4465
console = Console(highlighter=NullHighlighter())
4566

67+
_original_console_rule = console.rule
68+
69+
70+
def _safe_console_rule(title: str = "", *args: object, **kwargs: object) -> None:
71+
if "characters" not in kwargs and not _can_encode("─"):
72+
kwargs["characters"] = "-"
73+
if title and not _can_encode(title):
74+
encoding = getattr(sys.stdout, "encoding", None) or "utf-8"
75+
title = title.encode(encoding, errors="replace").decode(encoding, errors="replace")
76+
_original_console_rule(title, *args, **kwargs)
77+
78+
79+
console.rule = _safe_console_rule # type: ignore[method-assign]
80+
4681
if is_LSP_enabled() or is_subagent_mode():
4782
console.quiet = True
4883

codeflash/cli_cmds/github_workflow.py

Lines changed: 73 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class DependencyManager(Enum):
3434
UNKNOWN = auto()
3535

3636

37-
def install_github_actions(override_formatter_check: bool = False) -> None:
37+
def install_github_actions(override_formatter_check: bool = False, *, skip_confirm: bool = False) -> None:
3838
try:
3939
config, _config_file_path = parse_config_file(override_formatter_check=override_formatter_check)
4040

@@ -100,12 +100,15 @@ def install_github_actions(override_formatter_check: bool = False) -> None:
100100
console.print(benchmark_panel)
101101
console.print()
102102

103-
benchmark_questions = [
104-
inquirer.Confirm("benchmark_mode", message="Run GitHub Actions in benchmark mode?", default=True)
105-
]
103+
if skip_confirm:
104+
benchmark_mode = True
105+
else:
106+
benchmark_questions = [
107+
inquirer.Confirm("benchmark_mode", message="Run GitHub Actions in benchmark mode?", default=True)
108+
]
106109

107-
benchmark_answers = inquirer.prompt(benchmark_questions, theme=CodeflashTheme())
108-
benchmark_mode = benchmark_answers["benchmark_mode"] if benchmark_answers else False
110+
benchmark_answers = inquirer.prompt(benchmark_questions, theme=CodeflashTheme())
111+
benchmark_mode = benchmark_answers["benchmark_mode"] if benchmark_answers else False
109112

110113
# Show prompt only if workflow doesn't exist locally
111114
actions_panel = Panel(
@@ -121,26 +124,28 @@ def install_github_actions(override_formatter_check: bool = False) -> None:
121124
console.print(actions_panel)
122125
console.print()
123126

124-
creation_questions = [
125-
inquirer.Confirm(
126-
"confirm_creation",
127-
message="Set up GitHub Actions for continuous optimization? We'll open a pull request with the workflow file.",
128-
default=True,
129-
)
130-
]
127+
if skip_confirm:
128+
confirm_creation = True
129+
else:
130+
creation_questions = [
131+
inquirer.Confirm(
132+
"confirm_creation",
133+
message="Set up GitHub Actions for continuous optimization? We'll open a pull request with the workflow file.",
134+
default=True,
135+
)
136+
]
137+
138+
creation_answers = inquirer.prompt(creation_questions, theme=CodeflashTheme())
139+
confirm_creation = bool(creation_answers and creation_answers["confirm_creation"])
131140

132-
creation_answers = inquirer.prompt(creation_questions, theme=CodeflashTheme())
133-
if not creation_answers or not creation_answers["confirm_creation"]:
141+
if not confirm_creation:
134142
skip_panel = Panel(
135143
Text("⏩️ Skipping GitHub Actions setup.", style="yellow"), title="⏩️ Skipped", border_style="yellow"
136144
)
137145
console.print(skip_panel)
138146
ph("cli-github-workflow-skipped")
139147
return
140-
ph(
141-
"cli-github-optimization-confirm-workflow-creation",
142-
{"confirm_creation": creation_answers["confirm_creation"]},
143-
)
148+
ph("cli-github-optimization-confirm-workflow-creation", {"confirm_creation": confirm_creation})
144149

145150
# Generate workflow content AFTER user confirmation
146151
logger.info("[github_workflow.py:install_github_actions] User confirmed, generating workflow content...")
@@ -423,6 +428,11 @@ def install_github_actions(override_formatter_check: bool = False) -> None:
423428
f"🚀 Codeflash is now configured to automatically optimize new Github PRs!{LF}"
424429
)
425430

431+
if skip_confirm:
432+
click.echo("Add your CODEFLASH_API_KEY as a GitHub secret before running this workflow.")
433+
ph("cli-github-workflow-created")
434+
return
435+
426436
# Show GitHub secrets setup panel (needed in both cases - PR created via API or local file)
427437
try:
428438
existing_api_key = get_codeflash_api_key()
@@ -555,8 +565,11 @@ def get_github_action_working_directory(toml_path: Path, git_root: Path) -> str:
555565
def detect_project_language_for_workflow(project_root: Path) -> str:
556566
"""Detect the primary language of the project for workflow generation.
557567
558-
Returns: 'python', 'javascript', 'typescript', or 'java'
568+
Returns: 'python', 'javascript', 'typescript', 'java', or 'go'
559569
"""
570+
if (project_root / "go.mod").exists():
571+
return "go"
572+
560573
# Check for Java build tools first (pom.xml or build.gradle)
561574
if (
562575
(project_root / "pom.xml").exists()
@@ -693,9 +706,9 @@ def generate_dynamic_workflow_content(
693706
# Detect project language
694707
project_language = detect_project_language_for_workflow(Path.cwd())
695708

696-
# For JavaScript/TypeScript and Java projects, use static template customization
709+
# For JavaScript/TypeScript, Java, and Go projects, use static template customization
697710
# (AI-generated steps are currently Python-only)
698-
if project_language in ("javascript", "typescript", "java"):
711+
if project_language in ("javascript", "typescript", "java", "go"):
699712
return customize_codeflash_yaml_content(optimize_yml_content, config, git_root, benchmark_mode)
700713

701714
# Python project - try AI-generated steps
@@ -824,6 +837,9 @@ def customize_codeflash_yaml_content(
824837
if project_language in ("javascript", "typescript"):
825838
return _customize_js_workflow_content(optimize_yml_content, git_root, benchmark_mode)
826839

840+
if project_language == "go":
841+
return _customize_go_workflow_content(optimize_yml_content, git_root, benchmark_mode)
842+
827843
# Python project (default)
828844
return _customize_python_workflow_content(optimize_yml_content, git_root, benchmark_mode)
829845

@@ -948,3 +964,38 @@ def _customize_java_workflow_content(optimize_yml_content: str, git_root: Path)
948964
# Install dependencies command
949965
install_deps = get_java_dependency_installation_commands(build_tool)
950966
return optimize_yml_content.replace("{{ install_dependencies_command }}", install_deps)
967+
968+
969+
def _customize_go_workflow_content(optimize_yml_content: str, git_root: Path, benchmark_mode: bool = False) -> str:
970+
"""Customize workflow content for Go projects."""
971+
from codeflash.cli_cmds.init_go import get_go_dependency_installation_commands, get_go_runtime_setup_steps
972+
973+
project_root = Path.cwd()
974+
975+
if project_root == git_root:
976+
working_dir = ""
977+
else:
978+
rel_path = str(project_root.relative_to(git_root))
979+
working_dir = f"""defaults:
980+
run:
981+
working-directory: ./{rel_path}"""
982+
983+
optimize_yml_content = optimize_yml_content.replace("Optimize new Python code", "Optimize new Go code")
984+
optimize_yml_content = optimize_yml_content.replace("{{ working_directory }}", working_dir)
985+
986+
python_setup = get_dependency_manager_installation_string(DependencyManager.PIP)
987+
go_setup = get_go_runtime_setup_steps()
988+
setup_runtime = f"""{python_setup}
989+
{go_setup}"""
990+
optimize_yml_content = optimize_yml_content.replace("{{ setup_runtime_environment }}", setup_runtime)
991+
992+
install_deps = f"""|
993+
python -m pip install --upgrade pip
994+
pip install codeflash
995+
{get_go_dependency_installation_commands()}"""
996+
optimize_yml_content = optimize_yml_content.replace("{{ install_dependencies_command }}", install_deps)
997+
998+
codeflash_cmd = "codeflash"
999+
if benchmark_mode:
1000+
codeflash_cmd += " --benchmark"
1001+
return optimize_yml_content.replace("{{ codeflash_command }}", codeflash_cmd)

codeflash/cli_cmds/init_auth.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,17 @@ def enter_api_key_and_save_to_rc() -> None:
147147
os.environ["CODEFLASH_API_KEY"] = api_key
148148

149149

150+
def _skip_github_app_installation(owner: str, repo: str) -> None:
151+
click.echo(
152+
f"Skipping Codeflash GitHub app installation for {owner}/{repo}.{LF}"
153+
"Codeflash setup will continue, but PR creation will stay disabled until you install the app later."
154+
f"{LF}In the meantime you can make local only optimizations by using the '--no-pr' flag with codeflash.{LF}"
155+
)
156+
157+
150158
def install_github_app(git_remote: str) -> None:
159+
from rich.prompt import Confirm
160+
151161
try:
152162
git_repo = git.Repo(search_parent_directories=True)
153163
except git.InvalidGitRepositoryError:
@@ -167,6 +177,17 @@ def install_github_app(git_remote: str) -> None:
167177

168178
else:
169179
try:
180+
should_install = Confirm.ask(
181+
"Do you want to install the Codeflash GitHub app now? You can skip this and continue setup, "
182+
"but Codeflash won't be able to create PRs until the app is installed.",
183+
default=True,
184+
show_default=True,
185+
console=console,
186+
)
187+
if not should_install:
188+
_skip_github_app_installation(owner, repo)
189+
return
190+
170191
click.prompt(
171192
f"Finally, you'll need to install the Codeflash GitHub app by choosing the repository you want to install Codeflash on.{LF}"
172193
f"I will attempt to open the github app page - https://github.com/apps/codeflash-ai/installations/select_target {LF}"
@@ -188,11 +209,7 @@ def install_github_app(git_remote: str) -> None:
188209
count = 2
189210
while not is_github_app_installed_on_repo(owner, repo, suppress_errors=True):
190211
if count == 0:
191-
click.echo(
192-
f"❌ It looks like the Codeflash GitHub App is not installed on the repository {owner}/{repo}.{LF}"
193-
f"You won't be able to create PRs with Codeflash until you install the app.{LF}"
194-
f"In the meantime you can make local only optimizations by using the '--no-pr' flag with codeflash.{LF}"
195-
)
212+
_skip_github_app_installation(owner, repo)
196213
break
197214
click.prompt(
198215
f"❌ It looks like the Codeflash GitHub App is not installed on the repository {owner}/{repo}.{LF}"
@@ -207,3 +224,4 @@ def install_github_app(git_remote: str) -> None:
207224
except (KeyboardInterrupt, EOFError, click.exceptions.Abort):
208225
# leave empty line for the next prompt to be properly rendered
209226
click.echo()
227+
_skip_github_app_installation(owner, repo)

0 commit comments

Comments
 (0)