184 lines
5.4 KiB
Python
184 lines
5.4 KiB
Python
"""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()
|