Skip to content

Commit dc0f696

Browse files
Complete CLI migration from click to typer
- Updated main CLI file to use Typer app instead of click group - Migrated all command files (init, login, logout, profile, style_debug, update, config) - Converted click decorators to typer equivalents with proper type hints - Updated error handling from click exceptions to typer.Exit/typer.Abort - Fixed all imports across CLI modules - Maintained all existing functionality and command behaviors - Updated authentication decorators and session management - Fixed codemod manager and utility functions All CLI commands now use typer for better type safety and modern Python CLI patterns.
1 parent d4e84fd commit dc0f696

File tree

14 files changed

+94
-84
lines changed

14 files changed

+94
-84
lines changed

pyproject.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ readme = "README.md"
77
requires-python = ">=3.12, <3.14"
88
dependencies = [
99
"codegen-api-client",
10-
"click>=8.1.7",
11-
"rich-click>=1.8.5",
10+
"typer>=0.12.5",
1211
"rich>=13.7.1",
1312
"hatch-vcs>=0.4.0",
1413
"hatchling>=1.25.0",

src/codegen/cli/auth/decorators.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import functools
22
from collections.abc import Callable
33

4-
import click
54
import rich
5+
import typer
66

77
from codegen.cli.auth.login import login_routine
88
from codegen.cli.auth.session import CodegenSession
@@ -21,7 +21,7 @@ def wrapper(*args, **kwargs):
2121
# Check for valid session
2222
if session is None:
2323
pretty_print_error("There is currently no active session.\nPlease run 'codegen init' to initialize the project.")
24-
raise click.Abort()
24+
raise typer.Abort()
2525

2626
if (token := get_current_token()) is None:
2727
rich.print("[yellow]Not authenticated. Let's get you logged in first![/yellow]\n")

src/codegen/cli/auth/login.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import webbrowser
22

33
import rich
4-
import rich_click as click
4+
import typer
55

66
from codegen.cli.api.webapp_routes import USER_SECRETS_ROUTE
77
from codegen.cli.auth.token_manager import TokenManager
@@ -19,7 +19,7 @@ def login_routine(token: str | None = None) -> str:
1919
str: The authenticated token
2020
2121
Raises:
22-
click.ClickException: If login fails
22+
typer.Exit: If login fails
2323
2424
"""
2525
# Try environment variable first
@@ -29,11 +29,11 @@ def login_routine(token: str | None = None) -> str:
2929
if not token:
3030
rich.print(f"Opening {USER_SECRETS_ROUTE} to get your authentication token...")
3131
webbrowser.open_new(USER_SECRETS_ROUTE)
32-
token = click.prompt("Please enter your authentication token from the browser", hide_input=False)
32+
token = typer.prompt("Please enter your authentication token from the browser", hide_input=False)
3333

3434
if not token:
3535
msg = "Token must be provided via CODEGEN_USER_ACCESS_TOKEN environment variable or manual input"
36-
raise click.ClickException(msg)
36+
raise typer.Exit(msg)
3737

3838
# Validate and store token
3939
try:
@@ -45,4 +45,4 @@ def login_routine(token: str | None = None) -> str:
4545
return token
4646
except AuthError as e:
4747
msg = f"Error: {e!s}"
48-
raise click.ClickException(msg)
48+
raise typer.Exit(msg)

src/codegen/cli/auth/session.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from pathlib import Path
22

3-
import click
43
import rich
4+
import typer
55
from github import BadCredentialsException
66
from github.MainClass import Github
77

@@ -24,14 +24,14 @@ class CodegenSession:
2424
def __init__(self, repo_path: Path, git_token: str | None = None) -> None:
2525
if not repo_path.exists():
2626
rich.print(f"\n[bold red]Error:[/bold red] Path to git repo does not exist at {repo_path}")
27-
raise click.Abort()
27+
raise typer.Abort()
2828

2929
# Check if it's a valid git repository
3030
try:
3131
LocalGitRepo(repo_path=repo_path)
3232
except Exception:
3333
rich.print(f"\n[bold red]Error:[/bold red] Path {repo_path} is not a valid git repository")
34-
raise click.Abort()
34+
raise typer.Abort()
3535

3636
self.repo_path = repo_path
3737
self.local_git = LocalGitRepo(repo_path=repo_path)
@@ -87,7 +87,7 @@ def _validate(self) -> None:
8787
except BadCredentialsException:
8888
rich.print(format_command(f"\n[bold red]Error:[/bold red] Invalid GitHub token={git_token} for repo={self.local_git.full_name}"))
8989
rich.print("[white]Please provide a valid GitHub token for this repository.[/white]")
90-
raise click.Abort()
90+
raise typer.Abort()
9191

9292
def __str__(self) -> str:
9393
return f"CodegenSession(user={self.config.repository.user_name}, repo={self.config.repository.repo_name})"

src/codegen/cli/cli.py

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import rich_click as click
1+
import typer
22
from rich.traceback import install
33

44
# Removed reference to non-existent agent module
@@ -12,22 +12,21 @@
1212

1313
install(show_locals=True)
1414

15-
16-
@click.group(name="codegen")
17-
@click.version_option(prog_name="codegen", message="%(version)s")
18-
def main():
19-
"""Codegen CLI - Transform your code with AI."""
20-
pass
21-
22-
23-
# Add commands to the main group
24-
main.add_command(init_command)
25-
main.add_command(logout_command)
26-
main.add_command(login_command)
27-
main.add_command(profile_command)
28-
main.add_command(style_debug_command)
29-
main.add_command(update_command)
30-
main.add_command(config_command)
15+
# Create the main Typer app
16+
main = typer.Typer(
17+
name="codegen",
18+
help="Codegen CLI - Transform your code with AI.",
19+
rich_markup_mode="rich"
20+
)
21+
22+
# Add commands to the main app
23+
main.add_typer(init_command, name="init")
24+
main.add_typer(logout_command, name="logout")
25+
main.add_typer(login_command, name="login")
26+
main.add_typer(profile_command, name="profile")
27+
main.add_typer(style_debug_command, name="style-debug")
28+
main.add_typer(update_command, name="update")
29+
main.add_typer(config_command, name="config")
3130

3231

3332
if __name__ == "__main__":

src/codegen/cli/commands/config/main.py

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,19 @@
11
import logging
22

33
import rich
4-
import rich_click as click
4+
import typer
55
from rich.table import Table
66

77
from codegen.configs.constants import ENV_FILENAME, GLOBAL_ENV_FILE
88
from codegen.configs.user_config import UserConfig
99
from codegen.shared.path import get_git_root_path
1010

11-
12-
@click.group(name="config")
13-
def config_command():
14-
"""Manage codegen configuration."""
15-
pass
11+
# Create a Typer app for the config command
12+
config_command = typer.Typer(help="Manage codegen configuration.")
1613

1714

1815
@config_command.command(name="list")
19-
def list_command():
16+
def list_config():
2017
"""List current configuration values."""
2118

2219
def flatten_dict(data: dict, prefix: str = "") -> dict:
@@ -80,8 +77,7 @@ def flatten_dict(data: dict, prefix: str = "") -> dict:
8077

8178

8279
@config_command.command(name="get")
83-
@click.argument("key")
84-
def get_command(key: str):
80+
def get_config(key: str = typer.Argument(..., help="Configuration key to get")):
8581
"""Get a configuration value."""
8682
config = _get_user_config()
8783
if not config.has_key(key):
@@ -94,9 +90,10 @@ def get_command(key: str):
9490

9591

9692
@config_command.command(name="set")
97-
@click.argument("key")
98-
@click.argument("value")
99-
def set_command(key: str, value: str):
93+
def set_config(
94+
key: str = typer.Argument(..., help="Configuration key to set"),
95+
value: str = typer.Argument(..., help="Configuration value to set")
96+
):
10097
"""Set a configuration value and write to .env"""
10198
config = _get_user_config()
10299
if not config.has_key(key):

src/codegen/cli/commands/init/main.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,30 @@
11
import sys
22
from pathlib import Path
3+
from typing import Optional
34

45
import rich
5-
import rich_click as click
6+
import typer
67

78
from codegen.cli.auth.session import CodegenSession
89
from codegen.cli.rich.codeblocks import format_command
910
from codegen.shared.path import get_git_root_path
1011

12+
# Create a Typer app for the init command
13+
init_command = typer.Typer(help="Initialize or update the Codegen folder.")
1114

12-
@click.command(name="init")
13-
@click.option("--path", type=str, help="Path within a git repository. Defaults to the current directory.")
14-
@click.option("--token", type=str, help="Access token for the git repository. Required for full functionality.")
15-
@click.option("--language", type=click.Choice(["python", "typescript"], case_sensitive=False), help="Override automatic language detection")
16-
@click.option("--fetch-docs", is_flag=True, help="Fetch docs and examples (requires auth)")
17-
def init_command(path: str | None = None, token: str | None = None, language: str | None = None, fetch_docs: bool = False):
15+
@init_command.command()
16+
def init(
17+
path: Optional[str] = typer.Option(None, help="Path within a git repository. Defaults to the current directory."),
18+
token: Optional[str] = typer.Option(None, help="Access token for the git repository. Required for full functionality."),
19+
language: Optional[str] = typer.Option(None, help="Override automatic language detection (python or typescript)"),
20+
fetch_docs: bool = typer.Option(False, "--fetch-docs", help="Fetch docs and examples (requires auth)")
21+
):
1822
"""Initialize or update the Codegen folder."""
23+
# Validate language option
24+
if language and language.lower() not in ["python", "typescript"]:
25+
rich.print(f"[bold red]Error:[/bold red] Invalid language '{language}'. Must be 'python' or 'typescript'.")
26+
raise typer.Exit(1)
27+
1928
# Print a message if not in a git repo
2029
path = Path.cwd() if path is None else Path(path)
2130
repo_path = get_git_root_path(path)
@@ -27,7 +36,7 @@ def init_command(path: str | None = None, token: str | None = None, language: st
2736
rich.print("\n[dim]To initialize a new git repository:[/dim]")
2837
rich.print(format_command("git init"))
2938
rich.print(format_command("codegen init"))
30-
sys.exit(1)
39+
raise typer.Exit(1)
3140

3241
session = CodegenSession(repo_path=repo_path, git_token=token)
3342
if language:
Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
1-
import rich_click as click
1+
from typing import Optional
2+
import typer
23

34
from codegen.cli.auth.login import login_routine
45
from codegen.cli.auth.token_manager import get_current_token
56

7+
# Create a Typer app for the login command
8+
login_command = typer.Typer(help="Store authentication token.")
69

7-
@click.command(name="login")
8-
@click.option("--token", required=False, help="API token for authentication")
9-
def login_command(token: str):
10+
@login_command.command()
11+
def login(token: Optional[str] = typer.Option(None, help="API token for authentication")):
1012
"""Store authentication token."""
1113
# Check if already authenticated
1214
if get_current_token():
1315
msg = "Already authenticated. Use 'codegen logout' to clear the token."
14-
raise click.ClickException(msg)
16+
raise typer.Exit(msg)
1517

1618
login_routine(token)

src/codegen/cli/commands/logout/main.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import rich
2-
import rich_click as click
2+
import typer
33

44
from codegen.cli.auth.token_manager import TokenManager
55

6+
# Create a Typer app for the logout command
7+
logout_command = typer.Typer(help="Clear stored authentication token.")
68

7-
@click.command(name="logout")
8-
def logout_command():
9+
@logout_command.command()
10+
def logout():
911
"""Clear stored authentication token."""
1012
token_manager = TokenManager()
1113
token_manager.clear_token()

src/codegen/cli/commands/profile/main.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import rich
2-
import rich_click as click
2+
import typer
33
from rich import box
44
from rich.panel import Panel
55

@@ -13,11 +13,13 @@ def requires_init(func):
1313
"""Simple stub decorator that does nothing."""
1414
return func
1515

16+
# Create a Typer app for the profile command
17+
profile_command = typer.Typer(help="Display information about the currently authenticated user.")
1618

17-
@click.command(name="profile")
19+
@profile_command.command()
1820
@requires_auth
1921
@requires_init
20-
def profile_command(session: CodegenSession):
22+
def profile(session: CodegenSession):
2123
"""Display information about the currently authenticated user."""
2224
repo_config = session.config.repository
2325
rich.print(

0 commit comments

Comments
 (0)