mirror of
https://github.com/Omni-guides/Jackify.git
synced 2026-01-17 19:47:00 +01:00
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:
20
jackify/frontends/cli/menus/__init__.py
Normal file
20
jackify/frontends/cli/menus/__init__.py
Normal 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'
|
||||
]
|
||||
73
jackify/frontends/cli/menus/additional_menu.py
Normal file
73
jackify/frontends/cli/menus/additional_menu.py
Normal 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)
|
||||
32
jackify/frontends/cli/menus/hoolamike_menu.py
Normal file
32
jackify/frontends/cli/menus/hoolamike_menu.py
Normal 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}")
|
||||
74
jackify/frontends/cli/menus/main_menu.py
Normal file
74
jackify/frontends/cli/menus/main_menu.py
Normal 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
|
||||
174
jackify/frontends/cli/menus/recovery_menu.py
Normal file
174
jackify/frontends/cli/menus/recovery_menu.py
Normal 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
|
||||
194
jackify/frontends/cli/menus/tuxborn_menu.py
Normal file
194
jackify/frontends/cli/menus/tuxborn_menu.py
Normal 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)
|
||||
115
jackify/frontends/cli/menus/wabbajack_menu.py
Normal file
115
jackify/frontends/cli/menus/wabbajack_menu.py
Normal 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()
|
||||
Reference in New Issue
Block a user