Files
Jackify/jackify/frontends/cli/menus/tuxborn_menu.py
Omni cd591c14e3 Initial public release v0.1.0 - Linux Wabbajack Modlist Application
Jackify provides native Linux support for Wabbajack modlist installation
   and management with automated Steam integration and Proton configuration.

   Key Features:
   - Almost Native Linux implementation (texconv.exe run via proton)
   - Automated Steam shortcut creation and Proton prefix management
   - Both CLI and GUI interfaces, with Steam Deck optimization

   Supported Games:
   - Skyrim Special Edition
   - Fallout 4
   - Fallout New Vegas
   - Oblivion, Starfield, Enderal, and diverse other games

   Technical Architecture:
   - Clean separation between frontend and backend services
   - Powered by jackify-engine 0.3.x for Wabbajack-matching modlist installation
2025-09-05 20:46:24 +01:00

194 lines
9.6 KiB
Python

"""
Tuxborn Menu Handler for Jackify CLI Frontend
Extracted from src.modules.menu_handler.MenuHandler.show_tuxborn_installer_menu()
"""
from pathlib import Path
from typing import Optional
from jackify.shared.colors import (
COLOR_SELECTION, COLOR_RESET, COLOR_INFO, COLOR_PROMPT, COLOR_WARNING
)
from jackify.shared.ui_utils import print_jackify_banner
from jackify.backend.handlers.config_handler import ConfigHandler
class TuxbornMenuHandler:
"""
Handles the Tuxborn Automatic Installer workflow
Extracted from legacy MenuHandler class
"""
def __init__(self):
self.logger = None # Will be set by CLI when needed
def show_tuxborn_installer_menu(self, cli_instance):
"""
Implements the Tuxborn Automatic Installer workflow.
Prompts for install path, downloads path, and Nexus API key, then runs the one-shot install from start to finish
Args:
cli_instance: Reference to main CLI instance for access to handlers
"""
# Import backend service
from jackify.backend.core.modlist_operations import ModlistInstallCLI
print_jackify_banner()
print(f"{COLOR_SELECTION}Tuxborn Automatic Installer{COLOR_RESET}")
print(f"{COLOR_SELECTION}{'-'*32}{COLOR_RESET}")
print(f"{COLOR_INFO}This will install the Tuxborn modlist using the custom Jackify Install Engine in one automated flow.{COLOR_RESET}")
print(f"{COLOR_INFO}You will be prompted for the install location, downloads directory, and your Nexus API key.{COLOR_RESET}\n")
tuxborn_machineid = "Tuxborn/Tuxborn"
tuxborn_modlist_name = "Tuxborn"
# Prompt for install directory
print("----------------------------")
config_handler = ConfigHandler()
base_install_dir = Path(config_handler.get_modlist_install_base_dir())
default_install_dir = base_install_dir / "Skyrim" / "Tuxborn"
print(f"{COLOR_PROMPT}Please enter the directory you wish to use for Tuxborn installation.{COLOR_RESET}")
print(f"(Default: {default_install_dir})")
install_dir_result = self._get_directory_path_legacy(
cli_instance,
prompt_message=f"{COLOR_PROMPT}Install directory (Enter for default, 'q' to cancel): {COLOR_RESET}",
default_path=default_install_dir,
create_if_missing=True,
no_header=True
)
if not install_dir_result:
print(f"{COLOR_INFO}Cancelled by user.{COLOR_RESET}")
input("Press Enter to return to the main menu...")
return
if isinstance(install_dir_result, tuple):
install_dir, _ = install_dir_result # We'll use the path, creation handled by engine or later
else:
install_dir = install_dir_result
# Prompt for download directory
print("----------------------------")
base_download_dir = Path(config_handler.get_modlist_downloads_base_dir())
default_download_dir = base_download_dir / "Tuxborn"
print(f"{COLOR_PROMPT}Please enter the directory you wish to use for Tuxborn downloads.{COLOR_RESET}")
print(f"(Default: {default_download_dir})")
download_dir_result = self._get_directory_path_legacy(
cli_instance,
prompt_message=f"{COLOR_PROMPT}Download directory (Enter for default, 'q' to cancel): {COLOR_RESET}",
default_path=default_download_dir,
create_if_missing=True,
no_header=True
)
if not download_dir_result:
print(f"{COLOR_INFO}Cancelled by user.{COLOR_RESET}")
input("Press Enter to return to the main menu...")
return
if isinstance(download_dir_result, tuple):
download_dir, _ = download_dir_result # We'll use the path, creation handled by engine or later
else:
download_dir = download_dir_result
# Prompt for Nexus API key
print("----------------------------")
from jackify.backend.services.api_key_service import APIKeyService
api_key_service = APIKeyService()
saved_key = api_key_service.get_saved_api_key()
api_key = None
if saved_key:
print(f"{COLOR_INFO}A Nexus API Key is already saved.{COLOR_RESET}")
use_saved = input(f"{COLOR_PROMPT}Use the saved API key? [Y/n]: {COLOR_RESET}").strip().lower()
if use_saved in ('', 'y', 'yes'):
api_key = saved_key
else:
new_key = input(f"{COLOR_PROMPT}Enter a new Nexus API Key (or press Enter to keep the saved one): {COLOR_RESET}").strip()
if new_key:
api_key = new_key
replace = input(f"{COLOR_PROMPT}Replace the saved key with this one? [y/N]: {COLOR_RESET}").strip().lower()
if replace == 'y':
if api_key_service.save_api_key(api_key):
print(f"{COLOR_INFO}API key saved successfully.{COLOR_RESET}")
else:
print(f"{COLOR_WARNING}Failed to save API key. Using for this session only.{COLOR_RESET}")
else:
print(f"{COLOR_INFO}Using new key for this session only. Saved key unchanged.{COLOR_RESET}")
else:
api_key = saved_key
else:
print(f"{COLOR_PROMPT}A Nexus Mods API key is required for downloading mods.{COLOR_RESET}")
print(f"{COLOR_INFO}You can get your personal key at: {COLOR_SELECTION}https://www.nexusmods.com/users/myaccount?tab=api{COLOR_RESET}")
print(f"{COLOR_WARNING}Your API Key is NOT saved locally. It is used only for this session unless you choose to save it.{COLOR_RESET}")
api_key = input(f"{COLOR_PROMPT}Enter Nexus API Key (or 'q' to cancel): {COLOR_RESET}").strip()
if not api_key or api_key.lower() == 'q':
print(f"{COLOR_INFO}Cancelled by user.{COLOR_RESET}")
input("Press Enter to return to the main menu...")
return
save = input(f"{COLOR_PROMPT}Would you like to save this API key for future use? [y/N]: {COLOR_RESET}").strip().lower()
if save == 'y':
if api_key_service.save_api_key(api_key):
print(f"{COLOR_INFO}API key saved successfully.{COLOR_RESET}")
else:
print(f"{COLOR_WARNING}Failed to save API key. Using for this session only.{COLOR_RESET}")
else:
print(f"{COLOR_INFO}Using API key for this session only. It will not be saved.{COLOR_RESET}")
# Context for ModlistInstallCLI
context = {
'machineid': tuxborn_machineid,
'modlist_name': tuxborn_modlist_name, # Will be used for shortcut name
'install_dir': install_dir_result, # Pass tuple (path, create_flag) or path
'download_dir': download_dir_result, # Pass tuple (path, create_flag) or path
'nexus_api_key': api_key,
'resolution': None
}
modlist_cli = ModlistInstallCLI(self, getattr(cli_instance, 'steamdeck', False))
# run_discovery_phase will use context_override, display summary, and ask for confirmation.
# If user confirms, it returns the context, otherwise None.
confirmed_context = modlist_cli.run_discovery_phase(context_override=context)
if confirmed_context:
if self.logger:
self.logger.info("Tuxborn discovery confirmed by user. Proceeding to configuration/installation.")
# The modlist_cli instance now holds the confirmed context.
# configuration_phase will use modlist_cli.context
modlist_cli.configuration_phase()
# After configuration_phase, messages about success or next steps are handled within it or by _configure_new_modlist
else:
if self.logger:
self.logger.info("Tuxborn discovery/confirmation cancelled or failed.")
print(f"{COLOR_INFO}Tuxborn installation cancelled or not confirmed.{COLOR_RESET}")
input(f"{COLOR_PROMPT}Press Enter to return to the main menu...{COLOR_RESET}")
return
def _get_directory_path_legacy(self, cli_instance, prompt_message: str, default_path: Optional[Path],
create_if_missing: bool = True, no_header: bool = False) -> Optional[Path]:
"""
LEGACY BRIDGE: Delegate to legacy menu handler until full backend migration
Args:
cli_instance: Reference to main CLI instance
prompt_message: The prompt to show user
default_path: Default path if user presses Enter
create_if_missing: Whether to create directory if it doesn't exist
no_header: Whether to skip header display
Returns:
Path object or None if cancelled
"""
# LEGACY BRIDGE: Use the original menu handler's method
if hasattr(cli_instance, 'menu') and hasattr(cli_instance.menu, 'get_directory_path'):
return cli_instance.menu.get_directory_path(
prompt_message=prompt_message,
default_path=default_path,
create_if_missing=create_if_missing,
no_header=no_header
)
else:
# Fallback: simple input for now (will be replaced in future phases)
response = input(prompt_message).strip()
if response.lower() == 'q':
return None
elif response == '':
return default_path
else:
return Path(response)