"""Entry point — run with `trading-cli` or `uv run trading-cli`.""" import os import sys # CRITICAL: Lower file descriptor limit EARLY to avoid subprocess fds_to_keep error # Must be set BEFORE importing transformers or any library that uses subprocess try: import resource soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE) # Lower to 1024 to avoid fds_to_keep errors while still allowing normal operation target_limit = 1024 if soft > target_limit: new_soft = min(target_limit, hard) resource.setrlimit(resource.RLIMIT_NOFILE, (new_soft, hard)) print(f"Adjusted FD limit: {soft} -> {new_soft}", file=sys.stderr) except Exception as e: print(f"Could not adjust FD limit: {e}", file=sys.stderr) # CRITICAL: Disable all parallelism before importing transformers # These MUST be set before any transformers/tokenizers import os.environ['TOKENIZERS_PARALLELISM'] = 'false' os.environ['TRANSFORMERS_VERBOSITY'] = 'error' os.environ['HF_HUB_DISABLE_TELEMETRY'] = '1' os.environ['TQDM_DISABLE'] = '1' import logging import signal import threading import time from datetime import datetime from pathlib import Path def main() -> None: # Ensure config and log directories exist before any file operations config_dir = Path("~/.config/trading-cli").expanduser() config_dir.mkdir(parents=True, exist_ok=True) # Create a new log file per run, keep only the last 10 log_path = config_dir / f"app-{datetime.now().strftime('%Y%m%d-%H%M%S')}.log" logging.basicConfig( level=logging.WARNING, format="%(asctime)s %(levelname)s %(name)s: %(message)s", handlers=[ logging.FileHandler( log_path, mode="w", encoding="utf-8", ) ], ) # Clean up old log files (keep last 10) try: log_files = sorted(config_dir.glob("app-*.log")) for old_log in log_files[:-10]: old_log.unlink() except Exception: pass from trading_cli.app import TradingApp app = TradingApp() # Track if we've already started shutdown _shutdown_started = False _shutdown_lock = threading.Lock() def force_kill(): """Force kill after timeout.""" time.sleep(3) print("\n⚠️ Force-killing process (shutdown timeout exceeded)", file=sys.stderr) os._exit(1) # Force kill, bypassing all handlers def handle_sigint(signum, frame): """Handle SIGINT (Ctrl+C) with force-kill fallback.""" nonlocal _shutdown_started with _shutdown_lock: if _shutdown_started: # Already shutting down, skip force kill print("\n⚠️ Already shutting down, waiting...", file=sys.stderr) return _shutdown_started = True logger = logging.getLogger(__name__) logger.info("Received SIGINT (Ctrl+C), initiating shutdown...") print("\n🛑 Shutting down... (press Ctrl+C again to force-kill)", file=sys.stderr) # Start force-kill timer killer_thread = threading.Thread(target=force_kill, daemon=True) killer_thread.start() # Try clean shutdown try: app.exit() except Exception as e: logger.error(f"Error during exit: {e}") finally: # Give it a moment then exit time.sleep(0.5) sys.exit(0) signal.signal(signal.SIGINT, handle_sigint) try: app.run() except KeyboardInterrupt: # This handles the case where Textual catches it first logging.getLogger(__name__).info("KeyboardInterrupt caught at top level, exiting...") sys.exit(0) finally: # Ensure clean shutdown logging.getLogger(__name__).info("Trading CLI shutdown complete") sys.exit(0) if __name__ == "__main__": main()