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
This commit is contained in:
Omni
2025-09-05 20:46:24 +01:00
commit cd591c14e3
445 changed files with 40398 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
"""
CLI Menu Components for Jackify Frontend
Extracted from the legacy monolithic CLI system
"""
from .main_menu import MainMenuHandler
from .tuxborn_menu import TuxbornMenuHandler
from .wabbajack_menu import WabbajackMenuHandler
from .hoolamike_menu import HoolamikeMenuHandler
from .additional_menu import AdditionalMenuHandler
from .recovery_menu import RecoveryMenuHandler
__all__ = [
'MainMenuHandler',
'TuxbornMenuHandler',
'WabbajackMenuHandler',
'HoolamikeMenuHandler',
'AdditionalMenuHandler',
'RecoveryMenuHandler'
]

View File

@@ -0,0 +1,73 @@
"""
Additional Tasks Menu Handler for Jackify CLI Frontend
Extracted from src.modules.menu_handler.MenuHandler.show_additional_tasks_menu()
"""
import time
from jackify.shared.colors import (
COLOR_SELECTION, COLOR_RESET, COLOR_ACTION, COLOR_PROMPT, COLOR_INFO, COLOR_DISABLED
)
from jackify.shared.ui_utils import print_jackify_banner, print_section_header
class AdditionalMenuHandler:
"""
Handles the Additional Tasks menu (MO2, NXM Handling & Recovery)
Extracted from legacy MenuHandler class
"""
def __init__(self):
self.logger = None # Will be set by CLI when needed
def _clear_screen(self):
"""Clear the terminal screen"""
import os
os.system('cls' if os.name == 'nt' else 'clear')
def show_additional_tasks_menu(self, cli_instance):
"""Show the MO2, NXM Handling & Recovery submenu"""
while True:
self._clear_screen()
print_jackify_banner()
print_section_header("Additional Utilities") # Broader title
print(f"{COLOR_SELECTION}1.{COLOR_RESET} Install Mod Organizer 2 (Base Setup)")
print(f" {COLOR_ACTION}→ Proton setup for a standalone MO2 instance{COLOR_RESET}")
print(f"{COLOR_SELECTION}2.{COLOR_RESET} Configure NXM Handling {COLOR_DISABLED}(Not Implemented){COLOR_RESET}")
print(f"{COLOR_SELECTION}3.{COLOR_RESET} Jackify Recovery Tools")
print(f" {COLOR_ACTION}→ Restore files modified or backed up by Jackify{COLOR_RESET}")
print(f"{COLOR_SELECTION}0.{COLOR_RESET} Return to Main Menu")
selection = input(f"\n{COLOR_PROMPT}Enter your selection (0-3): {COLOR_RESET}").strip()
if selection.lower() == 'q': # Allow 'q' to re-display menu
continue
if selection == "1":
self._execute_legacy_install_mo2(cli_instance)
elif selection == "2":
print(f"{COLOR_INFO}Configure NXM Handling is not yet implemented.{COLOR_RESET}")
input("\nPress Enter to return to the Utilities menu...")
elif selection == "3":
self._execute_legacy_recovery_menu(cli_instance)
elif selection == "0":
break
else:
print("Invalid selection. Please try again.")
time.sleep(1)
def _execute_legacy_install_mo2(self, cli_instance):
"""LEGACY BRIDGE: Execute MO2 installation"""
# LEGACY BRIDGE: Use legacy imports until backend migration complete
if hasattr(cli_instance, 'menu') and hasattr(cli_instance.menu, 'mo2_handler'):
cli_instance.menu.mo2_handler.install_mo2()
else:
print(f"{COLOR_INFO}MO2 handler not available - this will be implemented in Phase 2.3{COLOR_RESET}")
input("\nPress Enter to continue...")
def _execute_legacy_recovery_menu(self, cli_instance):
"""LEGACY BRIDGE: Execute recovery menu"""
# This will be handled by the RecoveryMenuHandler
from .recovery_menu import RecoveryMenuHandler
recovery_handler = RecoveryMenuHandler()
recovery_handler.logger = self.logger
recovery_handler.show_recovery_menu(cli_instance)

View File

@@ -0,0 +1,32 @@
"""
Hoolamike Menu Handler for Jackify CLI Frontend
Extracted from src.modules.menu_handler.MenuHandler.show_hoolamike_menu()
"""
from jackify.shared.colors import COLOR_INFO, COLOR_PROMPT, COLOR_RESET
class HoolamikeMenuHandler:
"""
Handles the Hoolamike Tasks menu
Extracted from legacy MenuHandler class
"""
def __init__(self):
self.logger = None # Will be set by CLI when needed
def show_hoolamike_menu(self, cli_instance):
"""
LEGACY BRIDGE: Delegate to legacy menu handler until full backend migration
Args:
cli_instance: Reference to main CLI instance for access to handlers
"""
print(f"{COLOR_INFO}Hoolamike menu functionality has been extracted but needs migration to backend services.{COLOR_RESET}")
print(f"{COLOR_INFO}This will be implemented in Phase 2.3 (Menu Backend Integration).{COLOR_RESET}")
# LEGACY BRIDGE: Use the original menu handler's method
if hasattr(cli_instance, 'menu') and hasattr(cli_instance.menu, 'show_hoolamike_menu'):
cli_instance.menu.show_hoolamike_menu(cli_instance)
else:
print(f"{COLOR_INFO}Legacy menu handler not available - returning to main menu.{COLOR_RESET}")
input(f"{COLOR_PROMPT}Press Enter to continue...{COLOR_RESET}")

View File

@@ -0,0 +1,74 @@
"""
Main Menu Handler for Jackify CLI Frontend
Extracted from src.modules.menu_handler.MenuHandler.show_main_menu()
"""
import time
from typing import Optional
from jackify.shared.colors import (
COLOR_SELECTION, COLOR_RESET, COLOR_ACTION, COLOR_PROMPT, COLOR_ERROR
)
from jackify.shared.ui_utils import print_jackify_banner
class MainMenuHandler:
"""
Handles the main interactive menu display and user input routing
Extracted from legacy MenuHandler class
"""
def __init__(self, dev_mode=False):
self.logger = None # Will be set by CLI when needed
self.dev_mode = dev_mode
def _clear_screen(self):
"""Clear the terminal screen"""
import os
os.system('cls' if os.name == 'nt' else 'clear')
def show_main_menu(self, cli_instance) -> str:
"""
Show the main menu and return user selection
Args:
cli_instance: Reference to main CLI instance for access to handlers
Returns:
str: Menu choice ("wabbajack", "hoolamike", "additional", "exit", "tuxborn")
"""
while True:
self._clear_screen()
print_jackify_banner()
print(f"{COLOR_SELECTION}Main Menu{COLOR_RESET}")
print(f"{COLOR_SELECTION}{'-'*22}{COLOR_RESET}") # Standard separator
print(f"{COLOR_SELECTION}1.{COLOR_RESET} Modlist Tasks")
print(f" {COLOR_ACTION}→ Install & Configure Modlists{COLOR_RESET}")
print(f"{COLOR_SELECTION}2.{COLOR_RESET} Tuxborn Automatic Installer")
print(f" {COLOR_ACTION}→ Simple, fully automated Tuxborn installation{COLOR_RESET}")
if self.dev_mode:
print(f"{COLOR_SELECTION}3.{COLOR_RESET} Hoolamike Tasks")
print(f" {COLOR_ACTION}→ Wabbajack alternative: Install Modlists, TTW, etc{COLOR_RESET}")
print(f"{COLOR_SELECTION}4.{COLOR_RESET} Additional Tasks")
print(f" {COLOR_ACTION}→ Install Wabbajack (via WINE), MO2, NXM Handling, Jackify Recovery{COLOR_RESET}")
print(f"{COLOR_SELECTION}0.{COLOR_RESET} Exit Jackify")
if self.dev_mode:
choice = input(f"\n{COLOR_PROMPT}Enter your selection (0-4): {COLOR_RESET}").strip()
else:
choice = input(f"\n{COLOR_PROMPT}Enter your selection (0-2): {COLOR_RESET}").strip()
if choice.lower() == 'q': # Allow 'q' to re-display menu
continue
if choice == "1":
return "wabbajack"
elif choice == "2":
return "tuxborn" # Will be handled by TuxbornMenuHandler
if self.dev_mode:
if choice == "3":
return "hoolamike"
elif choice == "4":
return "additional"
elif choice == "0":
return "exit"
else:
print(f"{COLOR_ERROR}Invalid selection. Please try again.{COLOR_RESET}")
time.sleep(1) # Brief pause for readability

View File

@@ -0,0 +1,174 @@
"""
Recovery Menu Handler for Jackify CLI Frontend
Extracted from src.modules.menu_handler.MenuHandler._show_recovery_menu()
"""
import logging
from pathlib import Path
from jackify.shared.colors import (
COLOR_SELECTION, COLOR_RESET, COLOR_PROMPT, COLOR_INFO, COLOR_ERROR
)
from jackify.shared.ui_utils import print_jackify_banner, print_section_header
class RecoveryMenuHandler:
"""
Handles the Recovery Tools menu
Extracted from legacy MenuHandler class
"""
def __init__(self):
self.logger = logging.getLogger(__name__)
def _clear_screen(self):
"""Clear the terminal screen"""
import os
os.system('cls' if os.name == 'nt' else 'clear')
def show_recovery_menu(self, cli_instance):
"""Show the recovery tools menu."""
while True:
self._clear_screen()
print_jackify_banner()
print_section_header('Recovery Tools')
print(f"{COLOR_INFO}This allows restoring original Steam configuration files from backups created by Jackify.{COLOR_RESET}")
print(f"{COLOR_SELECTION}1.{COLOR_RESET} Restore all backups")
print(f"{COLOR_SELECTION}2.{COLOR_RESET} Restore config.vdf only")
print(f"{COLOR_SELECTION}3.{COLOR_RESET} Restore libraryfolders.vdf only")
print(f"{COLOR_SELECTION}4.{COLOR_RESET} Restore shortcuts.vdf only")
print(f"{COLOR_SELECTION}0.{COLOR_RESET} Return to Main Menu")
choice = input(f"\n{COLOR_PROMPT}Enter your selection (0-4): {COLOR_RESET}").strip()
if choice == "1":
self._restore_all_backups(cli_instance)
elif choice == "2":
self._restore_config_vdf(cli_instance)
elif choice == "3":
self._restore_libraryfolders_vdf(cli_instance)
elif choice == "4":
self._restore_shortcuts_vdf(cli_instance)
elif choice == "0":
break
else:
print("Invalid selection. Please try again.")
input("\nPress Enter to continue...")
def _restore_all_backups(self, cli_instance):
"""Restore all supported Steam config files"""
self.logger.info("Recovery selected: Restore all Steam config files")
print("\nAttempting to restore all supported Steam config files...")
# LEGACY BRIDGE: Use legacy handlers until backend migration complete
paths_to_check = {
"libraryfolders": self._get_library_vdf_path(cli_instance),
"config": self._get_config_vdf_path(cli_instance),
"shortcuts": self._get_shortcuts_vdf_path(cli_instance)
}
restored_count = 0
for file_type, file_path in paths_to_check.items():
if file_path:
print(f"Restoring {file_type} ({file_path})...")
latest_backup = self._find_latest_backup(cli_instance, Path(file_path))
if latest_backup:
if self._restore_backup(cli_instance, latest_backup, Path(file_path)):
print(f"Successfully restored {file_type}.")
restored_count += 1
else:
print(f"{COLOR_ERROR}Failed to restore {file_type} from {latest_backup}.{COLOR_RESET}")
else:
print(f"No backup found for {file_type}.")
else:
print(f"Could not locate original file for {file_type} to restore.")
print(f"\nRestore process completed. {restored_count}/{len(paths_to_check)} files potentially restored.")
input("\nPress Enter to continue...")
def _restore_config_vdf(self, cli_instance):
"""Restore config.vdf only"""
self.logger.info("Recovery selected: Restore config.vdf only")
print("\nAttempting to restore config.vdf...")
file_path = self._get_config_vdf_path(cli_instance)
if file_path:
latest_backup = self._find_latest_backup(cli_instance, Path(file_path))
if latest_backup:
if self._restore_backup(cli_instance, latest_backup, Path(file_path)):
print(f"Successfully restored config.vdf from {latest_backup}.")
else:
print(f"{COLOR_ERROR}Failed to restore config.vdf from {latest_backup}.{COLOR_RESET}")
else:
print("No backup found for config.vdf.")
else:
print("Could not locate config.vdf.")
input("\nPress Enter to continue...")
def _restore_libraryfolders_vdf(self, cli_instance):
"""Restore libraryfolders.vdf only"""
self.logger.info("Recovery selected: Restore libraryfolders.vdf only")
print("\nAttempting to restore libraryfolders.vdf...")
file_path = self._get_library_vdf_path(cli_instance)
if file_path:
latest_backup = self._find_latest_backup(cli_instance, Path(file_path))
if latest_backup:
if self._restore_backup(cli_instance, latest_backup, Path(file_path)):
print(f"Successfully restored libraryfolders.vdf from {latest_backup}.")
else:
print(f"{COLOR_ERROR}Failed to restore libraryfolders.vdf from {latest_backup}.{COLOR_RESET}")
else:
print("No backup found for libraryfolders.vdf.")
else:
print("Could not locate libraryfolders.vdf.")
input("\nPress Enter to continue...")
def _restore_shortcuts_vdf(self, cli_instance):
"""Restore shortcuts.vdf only"""
self.logger.info("Recovery selected: Restore shortcuts.vdf only")
print("\nAttempting to restore shortcuts.vdf...")
file_path = self._get_shortcuts_vdf_path(cli_instance)
if file_path:
latest_backup = self._find_latest_backup(cli_instance, Path(file_path))
if latest_backup:
if self._restore_backup(cli_instance, latest_backup, Path(file_path)):
print(f"Successfully restored shortcuts.vdf from {latest_backup}.")
else:
print(f"{COLOR_ERROR}Failed to restore shortcuts.vdf from {latest_backup}.{COLOR_RESET}")
else:
print("No backup found for shortcuts.vdf.")
else:
print("Could not locate shortcuts.vdf.")
input("\nPress Enter to continue...")
# LEGACY BRIDGE methods - delegate to existing handlers
def _get_library_vdf_path(self, cli_instance):
"""LEGACY BRIDGE: Get libraryfolders.vdf path"""
if hasattr(cli_instance, 'path_handler'):
return cli_instance.path_handler.find_steam_library_vdf_path()
return None
def _get_config_vdf_path(self, cli_instance):
"""LEGACY BRIDGE: Get config.vdf path"""
if hasattr(cli_instance, 'path_handler'):
return cli_instance.path_handler.find_steam_config_vdf()
return None
def _get_shortcuts_vdf_path(self, cli_instance):
"""LEGACY BRIDGE: Get shortcuts.vdf path"""
if hasattr(cli_instance, 'shortcut_handler'):
return cli_instance.shortcut_handler._find_shortcuts_vdf()
return None
def _find_latest_backup(self, cli_instance, file_path: Path):
"""LEGACY BRIDGE: Find latest backup file"""
if hasattr(cli_instance, 'filesystem_handler'):
return cli_instance.filesystem_handler.find_latest_backup(file_path)
return None
def _restore_backup(self, cli_instance, backup_path, target_path: Path) -> bool:
"""LEGACY BRIDGE: Restore backup file"""
if hasattr(cli_instance, 'filesystem_handler'):
return cli_instance.filesystem_handler.restore_backup(backup_path, target_path)
return False

View File

@@ -0,0 +1,194 @@
"""
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)

View File

@@ -0,0 +1,115 @@
"""
Wabbajack Tasks Menu Handler for Jackify CLI Frontend
Extracted from src.modules.menu_handler.MenuHandler.show_wabbajack_tasks_menu()
"""
import time
from jackify.shared.colors import (
COLOR_SELECTION, COLOR_RESET, COLOR_ACTION, COLOR_PROMPT, COLOR_INFO
)
from jackify.shared.ui_utils import print_jackify_banner, print_section_header
class WabbajackMenuHandler:
"""
Handles the Modlist and Wabbajack Tasks menu
Extracted from legacy MenuHandler class
"""
def __init__(self):
self.logger = None # Will be set by CLI when needed
def _clear_screen(self):
"""Clear the terminal screen"""
import os
os.system('cls' if os.name == 'nt' else 'clear')
def show_wabbajack_tasks_menu(self, cli_instance):
"""Show the Modlist and Wabbajack Tasks menu"""
while True:
self._clear_screen()
print_jackify_banner()
# Use print_section_header for consistency
print_section_header("Modlist and Wabbajack Tasks")
print(f"{COLOR_SELECTION}1.{COLOR_RESET} Install a Modlist (Automated)")
print(f" {COLOR_ACTION}→ Uses jackify-engine for a full install flow{COLOR_RESET}")
print(f"{COLOR_SELECTION}2.{COLOR_RESET} Configure New Modlist (Post-Download)")
print(f" {COLOR_ACTION}→ Modlist .wabbajack file downloaded? Configure it for Steam{COLOR_RESET}")
print(f"{COLOR_SELECTION}3.{COLOR_RESET} Configure Existing Modlist (In Steam)")
print(f" {COLOR_ACTION}→ Modlist already in Steam? Re-configure it here{COLOR_RESET}")
# HIDDEN FOR FIRST RELEASE - UNCOMMENT WHEN READY
# print(f"{COLOR_SELECTION}4.{COLOR_RESET} Install Wabbajack Application")
# print(f" {COLOR_ACTION}→ Downloads and configures the Wabbajack app itself (via WINE){COLOR_RESET}")
print(f"{COLOR_SELECTION}0.{COLOR_RESET} Return to Main Menu")
selection = input(f"\n{COLOR_PROMPT}Enter your selection (0-3): {COLOR_RESET}").strip()
if selection.lower() == 'q': # Allow 'q' to re-display menu
continue
if selection == "1":
self._execute_legacy_install_modlist(cli_instance)
elif selection == "2":
self._execute_legacy_configure_new_modlist(cli_instance)
elif selection == "3":
self._execute_legacy_configure_existing_modlist(cli_instance)
# HIDDEN FOR FIRST RELEASE - UNCOMMENT WHEN READY
# elif selection == "4":
# self._execute_legacy_install_wabbajack(cli_instance)
elif selection == "0":
break
else:
print("Invalid selection. Please try again.")
time.sleep(1)
def _execute_legacy_install_modlist(self, cli_instance):
"""LEGACY BRIDGE: Execute modlist installation workflow"""
# Import backend services
from jackify.backend.core.modlist_operations import ModlistInstallCLI
from jackify.backend.handlers.menu_handler import MenuHandler
# Create a proper MenuHandler instance with the required methods
menu_handler = MenuHandler()
# Pass the MenuHandler instance and steamdeck status
steamdeck_status = getattr(cli_instance, 'steamdeck', False)
installer = ModlistInstallCLI(menu_handler, steamdeck_status)
if self.logger:
self.logger.debug("MenuHandler: ModlistInstallCLI instance created for Install a Modlist.")
context = installer.run_discovery_phase()
if context:
if self.logger:
self.logger.info("MenuHandler: Discovery phase complete, proceeding to configuration phase.")
installer.configuration_phase()
else:
if self.logger:
self.logger.info("MenuHandler: Discovery phase did not return context. Skipping configuration.")
input("\nPress Enter to return to the Modlist Tasks menu...") # Standard return prompt
def _execute_legacy_install_wabbajack(self, cli_instance):
"""LEGACY BRIDGE: Execute Wabbajack application installation"""
if self.logger:
self.logger.info("User selected 'Install Wabbajack' from Modlist Tasks menu.")
# Add introductory text before calling the Wabbajack installation workflow
self._clear_screen()
print_jackify_banner()
print_section_header("Install Wabbajack Application")
print(f"{COLOR_INFO}This process will guide you through downloading and setting up\nthe Wabbajack application itself.{COLOR_RESET}")
print("\n") # Spacer
cli_instance._cmd_install_wabbajack(None) # Pass the cli_instance itself
def _execute_legacy_configure_new_modlist(self, cli_instance):
"""LEGACY BRIDGE: Execute new modlist configuration"""
# Import backend service
from jackify.backend.handlers.menu_handler import ModlistMenuHandler
modlist_menu = ModlistMenuHandler(cli_instance.config_handler)
modlist_menu._configure_new_modlist()
def _execute_legacy_configure_existing_modlist(self, cli_instance):
"""LEGACY BRIDGE: Execute existing modlist configuration"""
# Import backend service
from jackify.backend.handlers.menu_handler import ModlistMenuHandler
modlist_menu = ModlistMenuHandler(cli_instance.config_handler)
modlist_menu._configure_existing_modlist()