Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions codeflash/cli_cmds/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,15 @@ def parse_args() -> Namespace:
"--effort", type=str, help="Effort level for optimization", choices=["low", "medium", "high"], default="medium"
)

# Config management flags
parser.add_argument(
"--show-config", action="store_true", help="Show current or auto-detected configuration and exit."
)
parser.add_argument(
"--reset-config", action="store_true", help="Remove codeflash configuration from project config file."
)
parser.add_argument("-y", "--yes", action="store_true", help="Skip confirmation prompts (useful for CI/scripts).")

args, unknown_args = parser.parse_known_args()
sys.argv[:] = [sys.argv[0], *unknown_args]
return process_and_validate_cmd_args(args)
Expand All @@ -147,6 +156,16 @@ def process_and_validate_cmd_args(args: Namespace) -> Namespace:
logger.info(f"Codeflash version {version}")
sys.exit()

# Handle --show-config
if getattr(args, "show_config", False):
_handle_show_config()
sys.exit()

# Handle --reset-config
if getattr(args, "reset_config", False):
_handle_reset_config(confirm=not getattr(args, "yes", False))
sys.exit()

if args.command == "vscode-install":
install_vscode_extension()
sys.exit()
Expand Down Expand Up @@ -320,3 +339,91 @@ def handle_optimize_all_arg_parsing(args: Namespace) -> Namespace:
else:
args.all = Path(args.all).resolve()
return args


def _handle_show_config() -> None:
"""Show current or auto-detected Codeflash configuration."""
from rich.table import Table

from codeflash.cli_cmds.console import console
from codeflash.setup.detector import detect_project, has_existing_config

project_root = Path.cwd()
detected = detect_project(project_root)

# Check if config exists or is auto-detected
config_exists = has_existing_config(project_root)
status = "Saved config" if config_exists else "Auto-detected (not saved)"

console.print()
console.print(f"[bold]Codeflash Configuration[/bold] ({status})")
console.print()

table = Table(show_header=True, header_style="bold cyan")
table.add_column("Setting", style="dim")
table.add_column("Value")

table.add_row("Language", detected.language)
table.add_row("Project root", str(detected.project_root))
table.add_row("Module root", str(detected.module_root))
table.add_row("Tests root", str(detected.tests_root) if detected.tests_root else "(not detected)")
table.add_row("Test runner", detected.test_runner or "(not detected)")
table.add_row("Formatter", ", ".join(detected.formatter_cmds) if detected.formatter_cmds else "(not detected)")
table.add_row(
"Ignore paths", ", ".join(str(p) for p in detected.ignore_paths) if detected.ignore_paths else "(none)"
)
table.add_row("Confidence", f"{detected.confidence:.0%}")

console.print(table)
console.print()

if not config_exists:
console.print("[dim]Run [bold]codeflash --file <file>[/bold] to auto-save this config.[/dim]")


def _handle_reset_config(confirm: bool = True) -> None:
"""Remove Codeflash configuration from project config file.

Args:
confirm: If True, prompt for confirmation before removing.

"""
from codeflash.cli_cmds.console import console
from codeflash.setup.config_writer import remove_config
from codeflash.setup.detector import detect_project, has_existing_config

project_root = Path.cwd()

if not has_existing_config(project_root):
console.print("[yellow]No Codeflash configuration found to remove.[/yellow]")
return

detected = detect_project(project_root)

if confirm:
console.print("[bold]This will remove Codeflash configuration from your project.[/bold]")
console.print()

config_file = "pyproject.toml" if detected.language == "python" else "package.json"
console.print(f" Config file: {project_root / config_file}")
console.print()

try:
response = console.input("[bold]Are you sure you want to remove the config? [y/N][/bold] ")
except (EOFError, KeyboardInterrupt):
console.print("\n[yellow]Cancelled.[/yellow]")
return

if response.lower() not in ("y", "yes"):
console.print("[yellow]Cancelled.[/yellow]")
return

success, message = remove_config(project_root, detected.language)

# Escape brackets in message to prevent Rich markup interpretation
escaped_message = message.replace("[", "\\[")

if success:
console.print(f"[green]✓[/green] {escaped_message}")
else:
console.print(f"[red]✗[/red] {escaped_message}")
84 changes: 63 additions & 21 deletions codeflash/cli_cmds/cmd_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -607,7 +607,33 @@ def check_for_toml_or_setup_file() -> str | None:
curdir = Path.cwd()
pyproject_toml_path = curdir / "pyproject.toml"
setup_py_path = curdir / "setup.py"
package_json_path = curdir / "package.json"
project_name = None

# Check if this might be a JavaScript/TypeScript project that wasn't detected
if package_json_path.exists() and not pyproject_toml_path.exists() and not setup_py_path.exists():
js_redirect_panel = Panel(
Text(
f"📦 I found a package.json in {curdir}.\n\n"
"This looks like a JavaScript/TypeScript project!\n"
"Redirecting to JavaScript setup...",
style="cyan",
),
title="🟨 JavaScript Project Detected",
border_style="bright_yellow",
)
console.print(js_redirect_panel)
console.print()
ph("cli-js-project-redirect")

# Redirect to JS init
from codeflash.cli_cmds.init_javascript import ProjectLanguage, detect_project_language, init_js_project

project_language = detect_project_language()
if project_language in (ProjectLanguage.JAVASCRIPT, ProjectLanguage.TYPESCRIPT):
init_js_project(project_language)
sys.exit(0) # init_js_project handles its own exit, but ensure we don't continue

if pyproject_toml_path.exists():
try:
pyproject_toml_content = pyproject_toml_path.read_text(encoding="utf8")
Expand All @@ -617,28 +643,44 @@ def check_for_toml_or_setup_file() -> str | None:
except Exception:
click.echo("✅ I found a pyproject.toml for your project.")
ph("cli-pyproject-toml-found")
elif setup_py_path.exists():
setup_py_content = setup_py_path.read_text(encoding="utf8")
project_name_match = re.search(r"setup\s*\([^)]*?name\s*=\s*['\"](.*?)['\"]", setup_py_content, re.DOTALL)
if project_name_match:
project_name = project_name_match.group(1)
click.echo(f"✅ Found setup.py for your project {project_name}")
ph("cli-setup-py-found-name")
else:
click.echo("✅ Found setup.py.")
ph("cli-setup-py-found")
else:
if setup_py_path.exists():
setup_py_content = setup_py_path.read_text(encoding="utf8")
project_name_match = re.search(r"setup\s*\([^)]*?name\s*=\s*['\"](.*?)['\"]", setup_py_content, re.DOTALL)
if project_name_match:
project_name = project_name_match.group(1)
click.echo(f"✅ Found setup.py for your project {project_name}")
ph("cli-setup-py-found-name")
else:
click.echo("✅ Found setup.py.")
ph("cli-setup-py-found")
toml_info_panel = Panel(
Text(
f"💡 No pyproject.toml found in {curdir}.\n\n"
"This file is essential for Codeflash to store its configuration.\n"
"Please ensure you are running `codeflash init` from your project's root directory.",
style="yellow",
),
title="📋 pyproject.toml Required",
border_style="bright_yellow",
)
console.print(toml_info_panel)
# No Python config files found - show appropriate message
# Check again if this might be a JS project
if package_json_path.exists():
js_hint_panel = Panel(
Text(
f"📦 I found a package.json but no pyproject.toml in {curdir}.\n\n"
"If this is a JavaScript/TypeScript project, please run:\n"
" codeflash init\n\n"
"from the project root directory.",
style="yellow",
),
title="🤔 Mixed Project?",
border_style="bright_yellow",
)
console.print(js_hint_panel)
else:
toml_info_panel = Panel(
Text(
f"💡 No pyproject.toml found in {curdir}.\n\n"
"This file is essential for Codeflash to store its configuration.\n"
"Please ensure you are running `codeflash init` from your project's root directory.",
style="yellow",
),
title="📋 pyproject.toml Required",
border_style="bright_yellow",
)
console.print(toml_info_panel)
console.print()
ph("cli-no-pyproject-toml-or-setup-py")

Expand Down
25 changes: 21 additions & 4 deletions codeflash/cli_cmds/init_javascript.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,30 @@ def detect_project_language(project_root: Path | None = None) -> ProjectLanguage
has_package_json = (root / "package.json").exists()
has_tsconfig = (root / "tsconfig.json").exists()

# TypeScript project
# TypeScript project (tsconfig.json is definitive)
if has_tsconfig:
return ProjectLanguage.TYPESCRIPT

# Pure JS project (has package.json but no Python files)
if has_package_json and not has_pyproject and not has_setup_py:
return ProjectLanguage.JAVASCRIPT
# JavaScript project - package.json without Python-specific files takes priority
# Note: If both package.json and pyproject.toml exist, check for typical JS project indicators
if has_package_json:
# If no Python config files, it's definitely JavaScript
if not has_pyproject and not has_setup_py:
return ProjectLanguage.JAVASCRIPT

# If package.json exists with Python files, check for JS-specific indicators
# Common React/Node patterns indicate a JS project
js_indicators = [
(root / "node_modules").exists(),
(root / ".npmrc").exists(),
(root / "yarn.lock").exists(),
(root / "package-lock.json").exists(),
(root / "pnpm-lock.yaml").exists(),
(root / "bun.lockb").exists(),
(root / "bun.lock").exists(),
]
if any(js_indicators):
return ProjectLanguage.JAVASCRIPT

# Python project (default)
return ProjectLanguage.PYTHON
Expand Down
54 changes: 53 additions & 1 deletion codeflash/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
solved problem, please reach out to us at [email protected]. We're hiring!
"""

import sys
from pathlib import Path

from codeflash.cli_cmds.cli import parse_args, process_pyproject_config
Expand Down Expand Up @@ -39,7 +40,11 @@ def main() -> None:
posthog_cf.initialize_posthog(enabled=not args.disable_telemetry)
ask_run_end_to_end_test(args)
else:
args = process_pyproject_config(args)
# Check for first-run experience (no config exists)
args = _handle_config_loading(args)
if args is None:
sys.exit(0)

if not env_utils.check_formatter_installed(args.formatter_cmds):
return
args.previous_checkpoint_functions = ask_should_use_checkpoint_get_functions(args)
Expand All @@ -51,6 +56,53 @@ def main() -> None:
optimizer.run_with_args(args)


def _handle_config_loading(args):
"""Handle config loading with first-run experience support.

If no config exists and not in CI, triggers the first-run experience.
Otherwise, loads config normally.

Args:
args: CLI args namespace.

Returns:
Updated args with config loaded, or None if user cancelled first-run.

"""
from codeflash.setup.first_run import handle_first_run, is_first_run

# Check if we're in CI environment
is_ci = any(
var in ("true", "1", "True")
for var in [env_utils.os.environ.get("CI", ""), env_utils.os.environ.get("GITHUB_ACTIONS", "")]
)

# Check if first run (no config exists)
if is_first_run() and not is_ci:
# Skip API key check if already set
skip_api_key = bool(env_utils.os.environ.get("CODEFLASH_API_KEY"))

# Handle first-run experience
result = handle_first_run(args=args, skip_confirm=getattr(args, "yes", False), skip_api_key=skip_api_key)

if result is None:
return None

# Merge first-run results with any CLI overrides
args = result
# Still need to process some config values
# Config might not exist yet if first run just saved it - that's OK
import contextlib

with contextlib.suppress(ValueError):
args = process_pyproject_config(args)

return args

# Normal config loading
return process_pyproject_config(args)


def print_codeflash_banner() -> None:
paneled_text(
CODEFLASH_LOGO, panel_args={"title": "https://codeflash.ai", "expand": False}, text_args={"style": "bold gold3"}
Expand Down
22 changes: 22 additions & 0 deletions codeflash/setup/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""Setup module for Codeflash auto-detection and first-run experience.

This module provides:
- Universal project detection across all supported languages
- First-run experience with auto-detection and quick confirm
- Config writing to native config files (pyproject.toml, package.json)
"""

from codeflash.setup.config_schema import CodeflashConfig
from codeflash.setup.config_writer import write_config
from codeflash.setup.detector import DetectedProject, detect_project, has_existing_config
from codeflash.setup.first_run import handle_first_run, is_first_run

__all__ = [
"CodeflashConfig",
"DetectedProject",
"detect_project",
"handle_first_run",
"has_existing_config",
"is_first_run",
"write_config",
]
Loading
Loading