Files
2026-01-27 09:03:30 +01:00

184 lines
5.4 KiB
Python
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""CLI interface for taletorrent role-playing game engine."""
import typer
from rich.console import Console
from rich.prompt import Prompt
from rich.panel import Panel
from openai import OpenAI
from .api_key import (
store_credentials,
get_credentials,
has_credentials,
delete_credentials,
DEFAULT_BASE_URL,
)
app = typer.Typer(help="Interactive role-playing game engine using AI")
console = Console()
def validate_api_config(api_key: str, base_url: str) -> bool:
"""Validate API configuration by testing connection."""
try:
client = OpenAI(base_url=base_url, api_key=api_key)
client.models.list()
return True
except Exception:
return False
@app.command()
def config(
api_key: str | None = typer.Option(
None, "--api-key", "-k", help="OpenAI-compatible API key"
),
base_url: str = typer.Option(
DEFAULT_BASE_URL, "--base-url", "-u", help="API base URL"
),
interactive: bool = typer.Option(
False, "--interactive", "-i", help="Interactive setup"
),
):
"""Configure API credentials for taletorrent."""
if interactive:
setup_wizard()
return
if api_key is None:
if has_credentials():
console.print(" [yellow]API credentials already configured[/yellow]")
console.print(
"Use [cyan]taletorrent info[/cyan] to view current configuration"
)
console.print(
"Use [cyan]taletorrent clear[/cyan] to remove existing credentials"
)
raise typer.Exit(0)
else:
console.print(
"❌ [red]No API key provided and no existing credentials found[/red]"
)
console.print(
"Use [cyan]--api-key[/cyan] option or [cyan]--interactive[/cyan] flag"
)
raise typer.Exit(1)
console.print("🔄 [blue]Validating API configuration...[/blue]")
if validate_api_config(api_key, base_url):
store_credentials(api_key, base_url)
console.print("✅ [green]API configuration stored successfully[/green]")
else:
console.print(
"❌ [red]API validation failed. Check your credentials and URL.[/red]"
)
raise typer.Exit(1)
def setup_wizard():
"""Interactive wizard for API configuration."""
console.print(
Panel.fit(
"[bold cyan]API Configuration Setup[/bold cyan]\n\n"
"Configure any OpenAI-compatible API endpoint.\n"
"Your credentials will be stored securely in your system keyring.",
title="API Provider Setup",
)
)
base_url = Prompt.ask("Enter API base URL", default=DEFAULT_BASE_URL)
api_key = Prompt.ask("Enter API key", password=True)
if not api_key.strip():
console.print("❌ [red]API key cannot be empty[/red]")
raise typer.Exit(1)
console.print("🔄 [blue]Validating API configuration...[/blue]")
if validate_api_config(api_key, base_url):
store_credentials(api_key, base_url)
console.print("✅ [green]API configuration stored successfully[/green]")
else:
console.print(
"❌ [red]API validation failed. Check your credentials and URL.[/red]"
)
choice = Prompt.ask(
"Store anyway? [y/N]",
choices=["y", "n"],
default="n",
)
if choice == "y":
store_credentials(api_key, base_url)
console.print("⚠️ [yellow]Configuration stored without validation[/yellow]")
else:
console.print("[yellow]Setup cancelled.[/yellow]")
raise typer.Exit(1)
@app.command()
def info():
"""Display current API configuration."""
if not has_credentials():
console.print("❌ [red]No API configuration found[/red]")
console.print("Run [cyan]taletorrent config --interactive[/cyan] to set up")
raise typer.Exit(1)
try:
api_key, base_url = get_credentials()
masked_key = (
api_key[:8] + "*" * (len(api_key) - 12) + api_key[-4:]
if len(api_key) > 12
else "*" * len(api_key)
)
console.print(
Panel.fit(
f"[bold]Base URL:[/bold] {base_url}\n"
f"[bold]API Key:[/bold] {masked_key}",
title="API Configuration",
)
)
except ValueError as e:
console.print(f"❌ [red]{e}[/red]")
raise typer.Exit(1)
@app.command()
def clear():
"""Clear stored API credentials."""
if not has_credentials():
console.print(" [yellow]No API credentials to clear[/yellow]")
return
choice = Prompt.ask(
"Are you sure you want to clear API credentials? [y/N]",
choices=["y", "n"],
default="n",
)
if choice == "y":
delete_credentials()
console.print("✅ [green]API credentials cleared[/green]")
else:
console.print("[yellow]Operation cancelled.[/yellow]")
@app.command()
def play():
"""Start a new role-playing game session."""
if not has_credentials():
console.print("❌ [red]No API configuration found[/red]")
console.print("Run [cyan]taletorrent config --interactive[/cyan] to set up")
raise typer.Exit(1)
console.print("🎮 [green]Starting role-playing game...[/green]")
console.print("⚠️ [yellow]Game engine not yet implemented[/yellow]")
if __name__ == "__main__":
app()