Skip to content

Conversation

@KRRT7
Copy link
Contributor

@KRRT7 KRRT7 commented Nov 19, 2025

PR Type

Enhancement, Dependencies


Description

  • Replace CLI wizard with Textual TUI

  • Add standalone GitHub Actions TUI

  • Persist config to pyproject.toml

  • Add API key input validation


Diagram Walkthrough

flowchart LR
  CLI["Old CLI init flow (Rich/Inquirer)"] -- "replaced by" --> TUIApp["Textual TUI (CodeflashInit)"]
  TUIApp -- "saves" --> PyProject["pyproject.toml [tool.codeflash]"]
  TUIApp -- "optional install" --> GHApp["GitHub App"]
  TUIApp -- "create/update" --> GHActions[".github/workflows/codeflash.yaml"]
  ActionsOnly["GitHubActionsOnlyApp"] -- "standalone flow" --> GHActions
  Validators["APIKeyValidator"] -- "validate input" --> TUIApp
  pyproject["pyproject.toml deps"] -- "add" --> TextualDep["textual>=6.6.0"]
Loading

File Walkthrough

Relevant files
Enhancement
cmd_init.py
Switch init flow to TUI and streamline helpers                     

codeflash/cli_cmds/cmd_init.py

  • Replace CLI setup with Textual TUI launcher
  • Simplify pyproject creation function
  • Route GitHub Actions setup to TUI app
  • Remove legacy prompts, telemetry, and helpers
+21/-809
screens.py
New Textual TUI for initialization and actions                     

codeflash/cli_cmds/screens.py

  • Add full Textual TUI for init flow
  • Implement screens for config, GitHub, formatter
  • Add standalone GitHub Actions TUI
  • Persist config and API key to environment/rc
+1239/-0
validators.py
Add API key input validator                                                           

codeflash/cli_cmds/validators.py

  • Add Textual validator for API key
  • Enforce 'cf-' prefix requirement
+19/-0   
style.tcss
Add TUI styles for consistent UI                                                 

codeflash/cli_cmds/assets/style.tcss

  • Add styling for TUI screens and widgets
  • Define layout, colors, visibility helpers
+138/-0 
Dependencies
pyproject.toml
Add Textual dependency and adjust config                                 

pyproject.toml

  • Add textual dependency
  • Update tool.codeflash config format
  • Clean formatter-cmds entries
+5/-5     
Miscellaneous
version.py
Bump version                                                                                         

codeflash/version.py

  • Bump package version string
+1/-1     

@github-actions
Copy link

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 No relevant tests
🔒 Security concerns

Potential command execution surface:
the VSCode extension installer runs external commands (code --install-extension). While expected, ensure outputs are sanitized and not displayed unescaped. Also, saving API keys to shell rc is handled via save_api_key_to_rc; verify it writes with appropriate file permissions and avoids logging secrets. No obvious injection points in TOML writing, but ensure user-provided paths are validated to prevent path traversal when writing workflow files.

⚡ Recommended focus areas for review

Possible Issue

Workflow file name/status message mismatch: installer writes .github/workflows/codeflash.yaml but success notification mentions codeflash.yml; also two different installers (GitHubActionsScreen vs GitHubActionsStandaloneScreen) use the same filename but different messages and may diverge in config sourcing.

actions_check = self.query_one("#actions_check", Checkbox)
self.app.github_actions = actions_check.value

if actions_check.value:
    if self.install_workflow():
        self.notify(
            "✓ GitHub Actions workflow installed to .github/workflows/codeflash.yml\n"
            "Remember to add CODEFLASH_API_KEY to your GitHub repository secrets!",
            severity="information",
            timeout=6,
        )
    else:
Validation UX

API key validator rejects empty input, but WelcomeScreen hides input if env key exists; saving logic later attempts to write key to rc if different—ensure this flow doesn’t accidentally clear or duplicate keys; also consider masking input and supporting paste with whitespace trimming consistently.

class WelcomeScreen(Screen):
    def __init__(self) -> None:
        super().__init__()
        self.existing_api_key: str | None = None

    def compose(self) -> ComposeResult:
        yield Header()
        yield Container(
            Static(CODEFLASH_LOGO, classes="logo"),
            Static("Welcome to CodeFlash! 🚀\n", classes="title"),
            Static("", id="description", classes="description"),
            Static("", id="api_key_label", classes="label"),
            Input(placeholder="cf_xxxxxxxxxxxxxxxxxxxxxxxx", id="api_key_input", validators=[APIKeyValidator()]),
            Horizontal(
                Button("Continue", variant="primary", id="continue_btn"),
                Button("Quit", variant="default", id="quit_btn"),
                classes="button_row",
            ),
            classes="center_container",
        )
        yield Footer()

    def on_mount(self) -> None:
        """Check for existing API key and update UI accordingly."""
        from codeflash.code_utils.env_utils import get_codeflash_api_key

        description = self.query_one("#description", Static)
        label = self.query_one("#api_key_label", Static)
        api_key_input = self.query_one("#api_key_input", Input)

        try:
            self.existing_api_key = get_codeflash_api_key()
            if self.existing_api_key:
                # API key exists - show confirmation message
                display_key = f"{self.existing_api_key[:3]}****{self.existing_api_key[-4:]}"
                description.update(
                    "CodeFlash automatically optimizes your Python code for better performance.\n\n"
                    f"✅ Found existing API key: {display_key}\n\n"
                    "You're all set! Click Continue to proceed with configuration."
                )
                # Hide API key input
                label.update("")
                api_key_input.display = False
                # Store in app
                self.app.api_key = self.existing_api_key
                return
        except OSError:
            # No existing API key found
            pass

        # No API key - show input form
        description.update(
            "CodeFlash automatically optimizes your Python code for better performance.\n\n"
            "Before we begin, you'll need a CodeFlash API key.\n"
            "Visit app.codeflash.ai, sign up with GitHub, and generate your API key.\n"
        )
        label.update("Enter your CodeFlash API Key:")

    @on(Button.Pressed, "#continue_btn")
    def continue_pressed(self) -> None:
        # If we already have an API key from environment, just continue
        if self.existing_api_key:
            self.app.push_screen(ConfigCheckScreen())
            return

        # Otherwise validate the input
        api_key_input = self.query_one("#api_key_input", Input)
        api_key = api_key_input.value.strip()

        validation_result = api_key_input.validate(api_key)
        if not validation_result.is_valid:
            error_msgs = validation_result.failure_descriptions
            self.notify("; ".join(error_msgs) if error_msgs else "Invalid API key", severity="error", timeout=5)
            return

        self.app.api_key = api_key
        self.app.push_screen(ConfigCheckScreen())
Git Remote Handling

Git detection sets selected_remote but doesn’t persist a non-origin remote unless different; ensure downstream uses the saved remote (self.app.git_remote) and consider handling detached HEAD or repos without active_branch to avoid exceptions.

def on_mount(self) -> None:
    """Detect git configuration when screen is mounted."""
    status_widget = self.query_one("#git_status", Static)

    try:
        module_root = getattr(self.app, "module_path", ".")
        repo = Repo(Path.cwd() / module_root, search_parent_directories=True)

        # Get git remotes
        self.git_remotes = [remote.name for remote in repo.remotes]

        if not self.git_remotes:
            status_widget.update(
                "⚠️  No git remotes found.\n\n"
                "You can still use CodeFlash locally, but you'll need to set up a remote\n"
                "repository to use GitHub features like pull requests."
            )
            self.selected_remote = ""
            return

        # Get current branch
        current_branch = repo.active_branch.name

        # Get remote URL for display
        if self.git_remotes:
            try:
                remote_url = repo.remote(name=self.git_remotes[0]).url
                # Clean up URL for display
                display_url = remote_url.removesuffix(".git")
                if "@" in display_url:
                    # SSH URL like git@github.com:user/repo
                    display_url = display_url.split("@")[1].replace(":", "/")
                elif "://" in display_url:
                    # HTTPS URL
                    display_url = display_url.split("://")[1]
            except Exception:
                display_url = "<remote>"
        else:
            display_url = "<no remote>"

        if len(self.git_remotes) > 1:
            # Multiple remotes - show select widget
            status_widget.update(
                f"✅ Git repository detected\n\n"
                f"Repository: {display_url}\n"
                f"Branch: {current_branch}\n\n"
                f"Multiple remotes found. Please select which remote\n"
                f"CodeFlash should use for creating pull requests:"
            )

            # Show the select widget
            remote_select = self.query_one("#remote_select", Select)
            remote_select.remove_class("hidden")
            options = [(name, name) for name in self.git_remotes]
            remote_select.set_options(options)
            self.selected_remote = "origin" if "origin" in self.git_remotes else self.git_remotes[0]
        else:
            # Single remote - auto-select
            self.selected_remote = self.git_remotes[0]
            status_widget.update(
                f"✅ Git repository detected\n\n"
                f"Repository: {display_url}\n"
                f"Branch: {current_branch}\n"
                f"Remote: {self.selected_remote}\n\n"
                f"This will be used for GitHub integration."
            )

    except InvalidGitRepositoryError:
        status_widget.update(
            "⚠️  No git repository found.\n\n"
            "You can still use CodeFlash locally, but you'll need to initialize\n"
            "a git repository to use GitHub features like pull requests."
        )
        self.selected_remote = ""
    except Exception as e:
        status_widget.update(
            f"❌ Error detecting git configuration:\n\n{e}\n\nYou can continue with local optimization."
        )
        self.selected_remote = ""

@github-actions
Copy link

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix workflow file extension mismatch

The notification says the workflow is installed to codeflash.yml, but the code
writes codeflash.yaml. This mismatch can confuse users and tooling. Standardize the
filename to .yml or fix the message to match the actual file written.

codeflash/cli_cmds/screens.py [1043-1046]

-def install_workflow(self) -> bool:
-    from importlib.resources import files
+# Write workflow file
+workflow_file = workflows_dir / "codeflash.yml"
+workflow_file.write_text(workflow_content, encoding="utf-8")
+return True
 
-    from codeflash.cli_cmds.cmd_init import customize_codeflash_yaml_content
-
-    try:
-        # Check if git is available
-        if Repo is None or git is None:
-            self.notify("GitPython not available. Install with: pip install gitpython", severity="error", timeout=5)
-            return False
-
-        try:
-            repo = Repo(Path.cwd(), search_parent_directories=True)
-            git_root = Path(repo.git.rev_parse("--show-toplevel"))
-        except git.InvalidGitRepositoryError:
-            self.notify("Not in a git repository. GitHub Actions requires git.", severity="error", timeout=5)
-            return False
-
-        workflows_dir = git_root / ".github" / "workflows"
-        workflows_dir.mkdir(parents=True, exist_ok=True)
-
-        # Read workflow template
-        workflow_template = files("codeflash").joinpath("cli_cmds", "workflows", "codeflash-optimize.yaml")
-        workflow_content = workflow_template.read_text(encoding="utf-8")
-
-        # Build config dict to match the format expected by customize_codeflash_yaml_content
-        config = {
-            "module_root": getattr(self.app, "module_path", "src"),
-            "tests_root": getattr(self.app, "test_path", "tests"),
-        }
-
-        # Use the existing customization function from cmd_init
-        workflow_content = customize_codeflash_yaml_content(
-            workflow_content,
-            config,
-            git_root,
-            benchmark_mode=False,  # Could be made configurable later
-        )
-
-        # Write workflow file
-        workflow_file = workflows_dir / "codeflash.yaml"
-        workflow_file.write_text(workflow_content, encoding="utf-8")
-        return True
-    except PermissionError:
-        self.notify(
-            "Permission denied: Unable to create workflow file. Please check directory permissions.",
-            severity="error",
-            timeout=5,
-        )
-        return False
-    except Exception as e:
-        self.notify(f"Failed to install workflow: {e!s}", severity="error", timeout=5)
-        return False
-
Suggestion importance[1-10]: 7

__

Why: The UI later notifies about installing to codeflash.yml while the code writes codeflash.yaml; aligning the filename avoids user confusion. The change is small but accurate and improves consistency.

Medium
Align return type and content

The return type was changed to str, but the function still returns a triple-quoted
block intended as a tuple previously. Ensure all branches return a single string
consistently to avoid type/consumer breakage.

codeflash/cli_cmds/cmd_init.py [294-299]

 def get_dependency_installation_commands(dep_manager: DependencyManager) -> str:
     """Generate commands to install the dependency manager and project dependencies."""
     if dep_manager == DependencyManager.POETRY:
-        return """|
-          python -m pip install --upgrade pip
+        return (
+            "python -m pip install --upgrade pip\n"
+            "python -m pip install poetry\n"
+            "poetry install"
+        )
+    if dep_manager == DependencyManager.UV:
+        return (
+            "python -m pip install --upgrade pip\n"
+            "python -m pip install uv\n"
+            "uv sync"
+        )
+    # PIP or UNKNOWN
+    return (
+        "python -m pip install --upgrade pip\n"
+        "python -m pip install -r requirements.txt"
+    )
Suggestion importance[1-10]: 6

__

Why: The function now returns a str; returning a single newline-joined string in all branches is consistent and avoids consumer confusion. The proposal is reasonable, though impact is moderate since triple-quoted strings are already str.

Low
General
Ensure ASCII art renders on lines

Adjacent raw strings and an f-string are concatenated without a newline, producing a
single long line that will overflow and render poorly. Append newline characters to
each ASCII art line to ensure readable layout.

codeflash/cli_cmds/screens.py [31-39]

 CODEFLASH_LOGO: str = (
-    r"                   _          ___  _               _     "
-    r"                  | |        / __)| |             | |    "
-    r"  ____   ___    _ | |  ____ | |__ | |  ____   ___ | | _  "
-    r" / ___) / _ \  / || | / _  )|  __)| | / _  | /___)| || \ "
-    r"( (___ | |_| |( (_| |( (/ / | |   | |( ( | ||___ || | | |"
-    r" \____) \___/  \____| \____)|_|   |_| \_||_|(___/ |_| |_|"
+    r"                   _          ___  _               _     \n"
+    r"                  | |        / __)| |             | |    \n"
+    r"  ____   ___    _ | |  ____ | |__ | |  ____   ___ | | _  \n"
+    r" / ___) / _ \  / || | / _  )|  __)| | / _  | /___)| || \ \n"
+    r"( (___ | |_| |( (_| |( (/ / | |   | |( ( | ||___ || | | |\n"
+    r" \____) \___/  \____| \____)|_|   |_| \_||_|(___/ |_| |_|\n"
     f"{('v' + version).rjust(66)}"
 )
Suggestion importance[1-10]: 3

__

Why: Adding explicit newlines could improve readability, but the current concatenation may already render acceptably in the TUI. It's a minor stylistic improvement with low impact.

Low

@aseembits93 aseembits93 mentioned this pull request Nov 20, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants