""" Odoo Directory CLI - Main entry point. A Python CLI tool that generates static XML/HTML pages for Odoo partners, integrating with the existing odoo-expertos.com static website. Usage: python -m directory.cli setup python -m directory.cli plan --country DE --cities berlin muenchen python -m directory.cli execute --plan python -m directory.cli export --country DE python -m directory.cli deploy """ import logging from typing import List, Optional import typer from rich.console import Console from .config import get_settings from .utils.logger import setup_logging from .commands.setup import setup_command, print_schema from .commands.plan import plan_command, list_plans, show_plan from .commands.execute import execute_command from .commands.export import export_command from .commands.deploy import deploy_command, status_command # Create CLI app app = typer.Typer( name="odoo-directory", help="Generate static pages for Odoo partner directory.", add_completion=False, ) console = Console() # ==================== Setup Commands ==================== @app.command() def setup( seed_tags: bool = typer.Option(True, help="Seed default tags"), seed_germany: bool = typer.Option(True, help="Seed Germany cities"), schema: bool = typer.Option(False, "--schema", help="Print SQL schema only"), ): """ Initialize database with schema and seed data. Run this once when setting up a new installation. """ setup_logging() if schema: print_schema() return setup_command(seed_tags=seed_tags, seed_germany=seed_germany) # ==================== Plan Commands ==================== @app.command() def plan( country: str = typer.Option(..., "--country", "-c", help="Country code (e.g., DE)"), cities: Optional[List[str]] = typer.Option(None, "--cities", help="City slugs to include"), name: Optional[str] = typer.Option(None, "--name", "-n", help="Plan name"), ): """ Create an execution plan for a country. Specify which cities to process with --cities or process all cities in the country by default. """ setup_logging() plan_command(country=country, cities=cities, name=name) @app.command("plans") def plans_list(): """List all execution plans.""" setup_logging() list_plans() @app.command("plan-show") def plan_show( plan_id: str = typer.Argument(..., help="Plan UUID"), ): """Show details of a specific plan.""" setup_logging() show_plan(plan_id) # ==================== Execute Commands ==================== @app.command() def execute( plan_id: str = typer.Option(..., "--plan", "-p", help="Plan UUID"), step: Optional[str] = typer.Option(None, "--step", "-s", help="Specific step: scrape, enrich, content"), city: Optional[str] = typer.Option(None, "--city", help="Specific city slug"), dry_run: bool = typer.Option(False, "--dry-run", help="Don't make actual API calls"), ): """ Execute a plan or specific step. Runs the pipeline: scrape -> enrich -> content generation. """ setup_logging() execute_command(plan_id=plan_id, step=step, city=city, dry_run=dry_run) # ==================== Export Commands ==================== @app.command() def export( country: str = typer.Option(..., "--country", "-c", help="Country code"), city: Optional[str] = typer.Option(None, "--city", help="Specific city slug"), output: Optional[str] = typer.Option(None, "--output", "-o", help="Output directory"), ): """ Export static files for a country. Generates HTML pages, sitemaps, and tag JSON files. """ setup_logging() export_command(country=country, city=city, output_dir=output) # ==================== Deploy Commands ==================== @app.command() def deploy( message: Optional[str] = typer.Option(None, "--message", "-m", help="Commit message"), no_push: bool = typer.Option(False, "--no-push", help="Don't push to remote"), ): """ Deploy changes via Git. Commits exported files and pushes to trigger Vercel deployment. """ setup_logging() deploy_command(message=message, push=not no_push) @app.command() def status(): """Show git status of the project.""" setup_logging() status_command() # ==================== Utility Commands ==================== @app.command() def info(): """Show configuration and environment info.""" setup_logging() console.print("\n[bold blue]Odoo Directory CLI[/bold blue]\n") try: settings = get_settings() console.print("[bold]Configuration:[/bold]") console.print(f" Export directory: {settings.export_dir}") console.print(f" Images directory: {settings.images_dir}") console.print(f" Templates directory: {settings.templates_dir}") console.print(f" Log level: {settings.log_level}") console.print(f" Batch size: {settings.batch_size}") console.print("\n[bold]API Status:[/bold]") console.print(f" Supabase URL: {settings.supabase_url[:30]}...") console.print(f" Supabase Key: {'*' * 10}... (configured)") console.print(f" Apify Token: {'*' * 10}... (configured)") console.print(f" Firecrawl Key: {'*' * 10}... (configured)") console.print(f" Anthropic Key: {'*' * 10}... (configured)") except Exception as e: console.print(f"\n[red]Configuration error: {e}[/red]") console.print("\nMake sure you have a .env file with all required variables.") console.print("Copy .env.example to .env and fill in your API keys.") @app.command() def test_connection(): """Test connections to all external services.""" setup_logging() console.print("\n[bold blue]Testing Connections[/bold blue]\n") # Test Supabase console.print("Testing Supabase...") try: from .services.supabase import SupabaseService db = SupabaseService() regions = db.list_regions() console.print(f" [green]Connected! Found {len(regions)} regions.[/green]") except Exception as e: console.print(f" [red]Failed: {e}[/red]") # Test Anthropic console.print("Testing Anthropic...") try: from anthropic import Anthropic settings = get_settings() client = Anthropic(api_key=settings.anthropic_api_key) # Just verify the key format console.print(" [green]API key configured.[/green]") except Exception as e: console.print(f" [red]Failed: {e}[/red]") # Note about other services console.print("\nNote: Apify and Firecrawl are tested during actual execution.") # ==================== Main Entry ==================== def main(): """Main entry point for the CLI.""" app() if __name__ == "__main__": main()