"""SneakyCode entrypoint — argument parsing, config loading, and TUI launch.""" import argparse import asyncio import sys from pathlib import Path from app.models.config import AppConfig, load_config from app.services.llm import LLMClient, LLMConnectionError, LLMError from app.services.session import SessionManager from app.utils.display import print_banner, print_error, print_info, print_success from app.utils.logging import get_logger, setup_logging def parse_args() -> argparse.Namespace: """Parse command-line arguments.""" parser = argparse.ArgumentParser( prog="sneakycode", description="SneakyCode — A privacy-first local AI coding agent", ) parser.add_argument( "--config", type=Path, default=None, help="Path to config YAML file (default: config/config.yaml)", ) parser.add_argument( "-v", "--verbose", action="store_true", default=False, help="Enable verbose (DEBUG) logging", ) parser.add_argument( "--log-file", type=Path, default=None, help="Path to log file for persistent logging", ) parser.add_argument( "directory", nargs="?", type=Path, default=None, help="Project directory to use as workspace root (default: current directory)", ) return parser.parse_args() async def _preflight(config: AppConfig) -> None: """Check that Ollama is reachable and the configured model is available.""" async with LLMClient(config.llm) as client: await client.preflight_check() def main() -> None: """Main entrypoint: load config, preflight check, launch Textual TUI.""" args = parse_args() # Setup logging first (will be reconfigured for TUI on mount) setup_logging( log_file=args.log_file, verbose=args.verbose, ) logger = get_logger(__name__) # Load configuration try: config = load_config(config_path=args.config) except (FileNotFoundError, ValueError) as e: print_error(f"Configuration error: {e}") sys.exit(1) # Override workspace root if directory argument provided if args.directory: target = Path(args.directory).resolve() if not target.is_dir(): print_error(f"Not a directory: {target}") sys.exit(1) config.agent.workspace_root = target logger.info("config_loaded", model=config.llm.model, endpoint=config.llm.endpoint) # Pre-TUI startup info (printed to console before Textual takes over) print_banner() print_info(f"Model: {config.llm.model}") print_info(f"Endpoint: {config.llm.endpoint}") print_info(f"Workspace: {config.agent.workspace_root}") if args.verbose: print_info("Verbose mode enabled") # Preflight: check Ollama is reachable and model exists try: asyncio.run(_preflight(config)) except LLMConnectionError as e: print_error(str(e)) sys.exit(1) except LLMError as e: print_error(str(e)) sys.exit(1) print_success("Ollama connected, model ready.") # Create session manager session_mgr = SessionManager(config.session, config.agent.workspace_root, config.llm.model) # Clean up old session files cleaned = session_mgr.cleanup_old() if cleaned > 0: logger.info("old_sessions_cleaned", count=cleaned) # Launch Textual TUI from app.ui.app import SneakyCodeApp app = SneakyCodeApp(config, session_mgr=session_mgr) app.run() if __name__ == "__main__": main()