Files
DevGen/devgen/cli/config.py
T
sankalp d991af9c47 feat(devgen/cli): introduce Ollamaprovider support and interactive host configuration
- Add "ollama" to AI provider choices in config prompts
- Prompt user for Ollama server URL when selecting the Ollama provider
- Store the configured Ollama host in the config under the "ollama_host" key
- Update config saving logic in both edit_config and setup_config functions
2026-06-06 16:31:09 +05:45

223 lines
6.5 KiB
Python

from pathlib import Path
from typing import Any, Optional
import questionary
import typer
import yaml
from typing_extensions import Annotated
from devgen.utils import get_questionary_style, load_config
app = typer.Typer(
name="config",
help="🔧 Manage configuration values.",
no_args_is_help=True,
rich_markup_mode="markdown",
)
def _save_config(config: dict[str, Any]) -> None:
config_path = Path.home() / ".devgen.yaml"
try:
with config_path.open("w", encoding="utf-8") as f:
yaml.dump(config, f, default_flow_style=False)
except Exception as e:
typer.secho(f"❌ Failed to save config: {e}", fg=typer.colors.RED)
raise typer.Exit(code=1)
@app.command("list")
def list_config() -> None:
"""Show all configuration values."""
config = load_config()
if not config:
typer.echo("Configuration is empty.")
return
typer.echo(yaml.dump(config, default_flow_style=False))
@app.command("edit")
def edit_config(
key: Annotated[Optional[str], typer.Argument(help="Configuration key.")] = None,
value: Annotated[Optional[str], typer.Argument(help="New value.")] = None,
) -> None:
"""Edit a specific configuration value."""
config = load_config()
style = get_questionary_style()
if not key:
choices = list(config.keys())
key = questionary.select(
"Select key to update:", choices=choices, style=style
).ask()
if not key:
raise typer.Exit()
if value is None:
# Check if existing value is boolean to offer select
current_val = config.get(key)
if key == "provider":
value = questionary.select(
"Select AI Provider:",
choices=[
"gemini",
"openai",
"huggingface",
"openrouter",
"anthropic",
"ollama",
],
default=str(current_val) if current_val else "gemini",
style=style,
).ask()
if value is None:
raise typer.Exit(code=130)
elif isinstance(current_val, bool):
val_choice = questionary.select(
f"Select value for '{key}':",
choices=["True", "False"],
default=str(current_val),
style=style,
).ask()
if val_choice is None:
raise typer.Exit(code=130)
value = val_choice
else:
value = questionary.text(
f"Enter value for '{key}':",
default=str(current_val) if current_val is not None else "",
style=style,
).ask()
if value is None:
raise typer.Exit(code=130)
# Basic type inference
if isinstance(value, str):
if value.lower() == "true":
val = True
elif value.lower() == "false":
val = False
elif value.isdigit():
val = int(value)
else:
val = value
else:
val = value
config[key] = val
_save_config(config)
typer.secho(f" Set '{key}' to '{val}'", fg=typer.colors.GREEN)
@app.command("set")
def set_config() -> None:
"""Run the interactive configuration wizard."""
typer.secho("🛠️ Interactive Configuration Setup", fg=typer.colors.CYAN, bold=True)
config = load_config()
style = get_questionary_style()
# Questions
provider = questionary.select(
"Select AI Provider:",
choices=[
"gemini",
"openai",
"huggingface",
"openrouter",
"anthropic",
"ollama",
],
default=config.get("provider", "gemini"),
style=style,
).ask()
if provider is None:
raise typer.Exit(code=130)
model_default = config.get("model", "gemini-2.5-flash")
model = questionary.text(
"Enter Model Name:", default=model_default, style=style
).ask()
if model is None:
raise typer.Exit(code=130)
api_key = questionary.password(
"Enter API Key (leave empty to keep existing or none):", style=style
).ask()
if api_key is None:
raise typer.Exit(code=130)
if not api_key:
api_key = config.get("api_key", "")
emoji_choice = questionary.select(
"Use Emojis in Commit Messages?",
choices=["Yes", "No"],
default="Yes" if config.get("emoji", True) else "No",
style=style,
).ask()
if emoji_choice is None:
raise typer.Exit(code=130)
emoji = emoji_choice == "Yes"
ollama_host = config.get("ollama_host", "http://localhost:11434")
if provider == "ollama":
ollama_host_input = questionary.text(
"Ollama server URL:",
default=ollama_host,
style=style,
).ask()
if ollama_host_input is None:
raise typer.Exit(code=130)
ollama_host = ollama_host_input.strip() or ollama_host
# Save Config
new_config = {
"provider": provider,
"model": model,
"api_key": api_key,
"emoji": emoji,
"ollama_host": ollama_host,
}
# Merge with existing config to preserve other keys?
# The setup wizard usually overwrites or defines the core keys.
# Let's preserve other keys.
config.update(new_config)
_save_config(config)
typer.secho("\nConfiguration saved.", fg=typer.colors.GREEN)
typer.echo(yaml.dump(new_config, default_flow_style=False))
@app.command("info")
def config_info() -> None:
"""Show information about configuration options and templates."""
from rich.console import Console
from rich.table import Table
console = Console()
table = Table(title="Custom Template Variables", box=None)
table.add_column("Variable", style="cyan")
table.add_column("Description", style="white")
table.add_row("{{ group_name }}", "The folder name being committed (or 'root').")
table.add_row("{{ diff_text }}", "The git diff of the changes.")
table.add_row("{{ context }}", "Project context (manifest files content).")
console.print(table)
console.print(
"\n[bold]Tip:[/bold] If you hardcode emojis in your template, the AI will likely include them regardless of the 'emoji' setting.",
style="yellow",
)
console.print("\n[bold]Example Template:[/bold]")
console.print(
"custom_template: |\n [type]: [desc]\n \n Diff: {{ diff_text }}\n",
style="dim",
)