Skip to content

Commit 7f7cc62

Browse files
committed
Setup with auto detection process
1 parent f5a61fb commit 7f7cc62

14 files changed

Lines changed: 3689 additions & 37 deletions

codeflash/cli_cmds/cli.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,15 @@ def parse_args() -> Namespace:
121121
"--effort", type=str, help="Effort level for optimization", choices=["low", "medium", "high"], default="medium"
122122
)
123123

124+
# Config management flags
125+
parser.add_argument(
126+
"--show-config", action="store_true", help="Show current or auto-detected configuration and exit."
127+
)
128+
parser.add_argument(
129+
"--reset-config", action="store_true", help="Remove codeflash configuration from project config file."
130+
)
131+
parser.add_argument("-y", "--yes", action="store_true", help="Skip confirmation prompts (useful for CI/scripts).")
132+
124133
args, unknown_args = parser.parse_known_args()
125134
sys.argv[:] = [sys.argv[0], *unknown_args]
126135
return process_and_validate_cmd_args(args)
@@ -147,6 +156,16 @@ def process_and_validate_cmd_args(args: Namespace) -> Namespace:
147156
logger.info(f"Codeflash version {version}")
148157
sys.exit()
149158

159+
# Handle --show-config
160+
if getattr(args, "show_config", False):
161+
_handle_show_config()
162+
sys.exit()
163+
164+
# Handle --reset-config
165+
if getattr(args, "reset_config", False):
166+
_handle_reset_config(confirm=not getattr(args, "yes", False))
167+
sys.exit()
168+
150169
if args.command == "vscode-install":
151170
install_vscode_extension()
152171
sys.exit()
@@ -309,3 +328,91 @@ def handle_optimize_all_arg_parsing(args: Namespace) -> Namespace:
309328
else:
310329
args.all = Path(args.all).resolve()
311330
return args
331+
332+
333+
def _handle_show_config() -> None:
334+
"""Show current or auto-detected Codeflash configuration."""
335+
from rich.table import Table
336+
337+
from codeflash.cli_cmds.console import console
338+
from codeflash.setup.detector import detect_project, has_existing_config
339+
340+
project_root = Path.cwd()
341+
detected = detect_project(project_root)
342+
343+
# Check if config exists or is auto-detected
344+
config_exists = has_existing_config(project_root)
345+
status = "Saved config" if config_exists else "Auto-detected (not saved)"
346+
347+
console.print()
348+
console.print(f"[bold]Codeflash Configuration[/bold] ({status})")
349+
console.print()
350+
351+
table = Table(show_header=True, header_style="bold cyan")
352+
table.add_column("Setting", style="dim")
353+
table.add_column("Value")
354+
355+
table.add_row("Language", detected.language)
356+
table.add_row("Project root", str(detected.project_root))
357+
table.add_row("Module root", str(detected.module_root))
358+
table.add_row("Tests root", str(detected.tests_root) if detected.tests_root else "(not detected)")
359+
table.add_row("Test runner", detected.test_runner or "(not detected)")
360+
table.add_row("Formatter", ", ".join(detected.formatter_cmds) if detected.formatter_cmds else "(not detected)")
361+
table.add_row(
362+
"Ignore paths", ", ".join(str(p) for p in detected.ignore_paths) if detected.ignore_paths else "(none)"
363+
)
364+
table.add_row("Confidence", f"{detected.confidence:.0%}")
365+
366+
console.print(table)
367+
console.print()
368+
369+
if not config_exists:
370+
console.print("[dim]Run [bold]codeflash --file <file>[/bold] to auto-save this config.[/dim]")
371+
372+
373+
def _handle_reset_config(confirm: bool = True) -> None:
374+
"""Remove Codeflash configuration from project config file.
375+
376+
Args:
377+
confirm: If True, prompt for confirmation before removing.
378+
379+
"""
380+
from codeflash.cli_cmds.console import console
381+
from codeflash.setup.config_writer import remove_config
382+
from codeflash.setup.detector import detect_project, has_existing_config
383+
384+
project_root = Path.cwd()
385+
386+
if not has_existing_config(project_root):
387+
console.print("[yellow]No Codeflash configuration found to remove.[/yellow]")
388+
return
389+
390+
detected = detect_project(project_root)
391+
392+
if confirm:
393+
console.print("[bold]This will remove Codeflash configuration from your project.[/bold]")
394+
console.print()
395+
396+
config_file = "pyproject.toml" if detected.language == "python" else "package.json"
397+
console.print(f" Config file: {project_root / config_file}")
398+
console.print()
399+
400+
try:
401+
response = console.input("[bold]Are you sure you want to remove the config? [y/N][/bold] ")
402+
except (EOFError, KeyboardInterrupt):
403+
console.print("\n[yellow]Cancelled.[/yellow]")
404+
return
405+
406+
if response.lower() not in ("y", "yes"):
407+
console.print("[yellow]Cancelled.[/yellow]")
408+
return
409+
410+
success, message = remove_config(project_root, detected.language)
411+
412+
# Escape brackets in message to prevent Rich markup interpretation
413+
escaped_message = message.replace("[", "\\[")
414+
415+
if success:
416+
console.print(f"[green]✓[/green] {escaped_message}")
417+
else:
418+
console.print(f"[red]✗[/red] {escaped_message}")

codeflash/cli_cmds/cmd_init.py

Lines changed: 65 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -607,7 +607,33 @@ def check_for_toml_or_setup_file() -> str | None:
607607
curdir = Path.cwd()
608608
pyproject_toml_path = curdir / "pyproject.toml"
609609
setup_py_path = curdir / "setup.py"
610+
package_json_path = curdir / "package.json"
610611
project_name = None
612+
613+
# Check if this might be a JavaScript/TypeScript project that wasn't detected
614+
if package_json_path.exists() and not pyproject_toml_path.exists() and not setup_py_path.exists():
615+
js_redirect_panel = Panel(
616+
Text(
617+
f"📦 I found a package.json in {curdir}.\n\n"
618+
"This looks like a JavaScript/TypeScript project!\n"
619+
"Redirecting to JavaScript setup...",
620+
style="cyan",
621+
),
622+
title="🟨 JavaScript Project Detected",
623+
border_style="bright_yellow",
624+
)
625+
console.print(js_redirect_panel)
626+
console.print()
627+
ph("cli-js-project-redirect")
628+
629+
# Redirect to JS init
630+
from codeflash.cli_cmds.init_javascript import ProjectLanguage, detect_project_language, init_js_project
631+
632+
project_language = detect_project_language()
633+
if project_language in (ProjectLanguage.JAVASCRIPT, ProjectLanguage.TYPESCRIPT):
634+
init_js_project(project_language)
635+
sys.exit(0) # init_js_project handles its own exit, but ensure we don't continue
636+
611637
if pyproject_toml_path.exists():
612638
try:
613639
pyproject_toml_content = pyproject_toml_path.read_text(encoding="utf8")
@@ -617,28 +643,44 @@ def check_for_toml_or_setup_file() -> str | None:
617643
except Exception:
618644
click.echo("✅ I found a pyproject.toml for your project.")
619645
ph("cli-pyproject-toml-found")
646+
elif setup_py_path.exists():
647+
setup_py_content = setup_py_path.read_text(encoding="utf8")
648+
project_name_match = re.search(r"setup\s*\([^)]*?name\s*=\s*['\"](.*?)['\"]", setup_py_content, re.DOTALL)
649+
if project_name_match:
650+
project_name = project_name_match.group(1)
651+
click.echo(f"✅ Found setup.py for your project {project_name}")
652+
ph("cli-setup-py-found-name")
653+
else:
654+
click.echo("✅ Found setup.py.")
655+
ph("cli-setup-py-found")
620656
else:
621-
if setup_py_path.exists():
622-
setup_py_content = setup_py_path.read_text(encoding="utf8")
623-
project_name_match = re.search(r"setup\s*\([^)]*?name\s*=\s*['\"](.*?)['\"]", setup_py_content, re.DOTALL)
624-
if project_name_match:
625-
project_name = project_name_match.group(1)
626-
click.echo(f"✅ Found setup.py for your project {project_name}")
627-
ph("cli-setup-py-found-name")
628-
else:
629-
click.echo("✅ Found setup.py.")
630-
ph("cli-setup-py-found")
631-
toml_info_panel = Panel(
632-
Text(
633-
f"💡 No pyproject.toml found in {curdir}.\n\n"
634-
"This file is essential for Codeflash to store its configuration.\n"
635-
"Please ensure you are running `codeflash init` from your project's root directory.",
636-
style="yellow",
637-
),
638-
title="📋 pyproject.toml Required",
639-
border_style="bright_yellow",
640-
)
641-
console.print(toml_info_panel)
657+
# No Python config files found - show appropriate message
658+
# Check again if this might be a JS project
659+
if package_json_path.exists():
660+
js_hint_panel = Panel(
661+
Text(
662+
f"📦 I found a package.json but no pyproject.toml in {curdir}.\n\n"
663+
"If this is a JavaScript/TypeScript project, please run:\n"
664+
" codeflash init\n\n"
665+
"from the project root directory.",
666+
style="yellow",
667+
),
668+
title="🤔 Mixed Project?",
669+
border_style="bright_yellow",
670+
)
671+
console.print(js_hint_panel)
672+
else:
673+
toml_info_panel = Panel(
674+
Text(
675+
f"💡 No pyproject.toml found in {curdir}.\n\n"
676+
"This file is essential for Codeflash to store its configuration.\n"
677+
"Please ensure you are running `codeflash init` from your project's root directory.",
678+
style="yellow",
679+
),
680+
title="📋 pyproject.toml Required",
681+
border_style="bright_yellow",
682+
)
683+
console.print(toml_info_panel)
642684
console.print()
643685
ph("cli-no-pyproject-toml-or-setup-py")
644686

@@ -1474,11 +1516,7 @@ def customize_codeflash_yaml_content(
14741516
return _customize_python_workflow_content(optimize_yml_content, git_root, benchmark_mode)
14751517

14761518

1477-
def _customize_python_workflow_content(
1478-
optimize_yml_content: str,
1479-
git_root: Path,
1480-
benchmark_mode: bool = False, # noqa: FBT001, FBT002
1481-
) -> str:
1519+
def _customize_python_workflow_content(optimize_yml_content: str, git_root: Path, benchmark_mode: bool = False) -> str:
14821520
"""Customize workflow content for Python projects."""
14831521
# Get dependency installation commands
14841522
toml_path = Path.cwd() / "pyproject.toml"
@@ -1513,11 +1551,7 @@ def _customize_python_workflow_content(
15131551

15141552

15151553
# TODO:{claude} Refactor and move to support for language specific
1516-
def _customize_js_workflow_content(
1517-
optimize_yml_content: str,
1518-
git_root: Path,
1519-
benchmark_mode: bool = False, # noqa: FBT001, FBT002
1520-
) -> str:
1554+
def _customize_js_workflow_content(optimize_yml_content: str, git_root: Path, benchmark_mode: bool = False) -> str:
15211555
"""Customize workflow content for JavaScript/TypeScript projects."""
15221556
from codeflash.cli_cmds.init_javascript import (
15231557
get_js_codeflash_install_step,

codeflash/cli_cmds/init_javascript.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ class JSSetupInfo:
6666

6767

6868
# Import theme from cmd_init to avoid duplication
69-
def _get_theme(): # noqa: ANN202
69+
def _get_theme():
7070
"""Get the CodeflashTheme - imported lazily to avoid circular imports."""
7171
from codeflash.cli_cmds.cmd_init import CodeflashTheme
7272

@@ -90,13 +90,30 @@ def detect_project_language(project_root: Path | None = None) -> ProjectLanguage
9090
has_package_json = (root / "package.json").exists()
9191
has_tsconfig = (root / "tsconfig.json").exists()
9292

93-
# TypeScript project
93+
# TypeScript project (tsconfig.json is definitive)
9494
if has_tsconfig:
9595
return ProjectLanguage.TYPESCRIPT
9696

97-
# Pure JS project (has package.json but no Python files)
98-
if has_package_json and not has_pyproject and not has_setup_py:
99-
return ProjectLanguage.JAVASCRIPT
97+
# JavaScript project - package.json without Python-specific files takes priority
98+
# Note: If both package.json and pyproject.toml exist, check for typical JS project indicators
99+
if has_package_json:
100+
# If no Python config files, it's definitely JavaScript
101+
if not has_pyproject and not has_setup_py:
102+
return ProjectLanguage.JAVASCRIPT
103+
104+
# If package.json exists with Python files, check for JS-specific indicators
105+
# Common React/Node patterns indicate a JS project
106+
js_indicators = [
107+
(root / "node_modules").exists(),
108+
(root / ".npmrc").exists(),
109+
(root / "yarn.lock").exists(),
110+
(root / "package-lock.json").exists(),
111+
(root / "pnpm-lock.yaml").exists(),
112+
(root / "bun.lockb").exists(),
113+
(root / "bun.lock").exists(),
114+
]
115+
if any(js_indicators):
116+
return ProjectLanguage.JAVASCRIPT
100117

101118
# Python project (default)
102119
return ProjectLanguage.PYTHON

codeflash/main.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
solved problem, please reach out to us at careers@codeflash.ai. We're hiring!
55
"""
66

7+
import sys
78
from pathlib import Path
89

910
from codeflash.cli_cmds.cli import parse_args, process_pyproject_config
@@ -39,7 +40,11 @@ def main() -> None:
3940
posthog_cf.initialize_posthog(enabled=not args.disable_telemetry)
4041
ask_run_end_to_end_test(args)
4142
else:
42-
args = process_pyproject_config(args)
43+
# Check for first-run experience (no config exists)
44+
args = _handle_config_loading(args)
45+
if args is None:
46+
sys.exit(0)
47+
4348
if not env_utils.check_formatter_installed(args.formatter_cmds):
4449
return
4550
args.previous_checkpoint_functions = ask_should_use_checkpoint_get_functions(args)
@@ -51,6 +56,53 @@ def main() -> None:
5156
optimizer.run_with_args(args)
5257

5358

59+
def _handle_config_loading(args):
60+
"""Handle config loading with first-run experience support.
61+
62+
If no config exists and not in CI, triggers the first-run experience.
63+
Otherwise, loads config normally.
64+
65+
Args:
66+
args: CLI args namespace.
67+
68+
Returns:
69+
Updated args with config loaded, or None if user cancelled first-run.
70+
71+
"""
72+
from codeflash.setup.first_run import handle_first_run, is_first_run
73+
74+
# Check if we're in CI environment
75+
is_ci = any(
76+
var in ("true", "1", "True")
77+
for var in [env_utils.os.environ.get("CI", ""), env_utils.os.environ.get("GITHUB_ACTIONS", "")]
78+
)
79+
80+
# Check if first run (no config exists)
81+
if is_first_run() and not is_ci:
82+
# Skip API key check if already set
83+
skip_api_key = bool(env_utils.os.environ.get("CODEFLASH_API_KEY"))
84+
85+
# Handle first-run experience
86+
result = handle_first_run(args=args, skip_confirm=getattr(args, "yes", False), skip_api_key=skip_api_key)
87+
88+
if result is None:
89+
return None
90+
91+
# Merge first-run results with any CLI overrides
92+
args = result
93+
# Still need to process some config values
94+
# Config might not exist yet if first run just saved it - that's OK
95+
import contextlib
96+
97+
with contextlib.suppress(ValueError):
98+
args = process_pyproject_config(args)
99+
100+
return args
101+
102+
# Normal config loading
103+
return process_pyproject_config(args)
104+
105+
54106
def print_codeflash_banner() -> None:
55107
paneled_text(
56108
CODEFLASH_LOGO, panel_args={"title": "https://codeflash.ai", "expand": False}, text_args={"style": "bold gold3"}

codeflash/setup/__init__.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"""Setup module for Codeflash auto-detection and first-run experience.
2+
3+
This module provides:
4+
- Universal project detection across all supported languages
5+
- First-run experience with auto-detection and quick confirm
6+
- Config writing to native config files (pyproject.toml, package.json)
7+
"""
8+
9+
from codeflash.setup.config_schema import CodeflashConfig
10+
from codeflash.setup.config_writer import write_config
11+
from codeflash.setup.detector import DetectedProject, detect_project, has_existing_config
12+
from codeflash.setup.first_run import handle_first_run, is_first_run
13+
14+
__all__ = [
15+
"CodeflashConfig",
16+
"DetectedProject",
17+
"detect_project",
18+
"handle_first_run",
19+
"has_existing_config",
20+
"is_first_run",
21+
"write_config",
22+
]

0 commit comments

Comments
 (0)