Autarch/core/menu.py
DigiJ 4a721a73b4 Add system tray, dual-exe build, and installer scripts
- core/tray.py: System tray icon with Start/Stop/Restart/Open Dashboard/Exit
- autarch_web.py: Windowless web launcher entry point (Win32GUI, no console)
- core/paths.py: Frozen build support — dual-directory pattern for PyInstaller
- core/menu.py: Module loading scans both bundled and user module directories
- web/app.py: Template/static paths resolve correctly in frozen builds
- autarch.py: --no-tray flag, tray integration for --web mode
- autarch_public.spec: Dual-exe PyInstaller spec with MERGE/COLLECT
- setup_msi.py: Dual executables, LocalAppData install, model inclusion
- installer.iss: Inno Setup script (model stored uncompressed to avoid OOM)
- installer.nsi: NSIS installer script with MUI2

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 22:02:22 -08:00

3050 lines
132 KiB
Python

"""
AUTARCH Main Menu System
Handles the main interface, categories, and module loading
"""
import os
import sys
import importlib.util
from pathlib import Path
from typing import Dict, List, Optional, Callable
from .banner import Colors, display_banner, clear_screen
from .config import get_config
# Module categories
CATEGORIES = {
"defense": {
"name": "Defense",
"description": "Defensive security tools and monitoring",
"color": Colors.BLUE
},
"offense": {
"name": "Offense",
"description": "Offensive security and penetration testing",
"color": Colors.RED
},
"counter": {
"name": "Counter",
"description": "Counter-intelligence and threat response",
"color": Colors.MAGENTA
},
"analyze": {
"name": "Analyze",
"description": "Analysis and forensics tools",
"color": Colors.CYAN
},
"osint": {
"name": "OSINT",
"description": "Open source intelligence gathering",
"color": Colors.GREEN
},
"simulate": {
"name": "Simulate",
"description": "Attack simulation and red team exercises",
"color": Colors.YELLOW
},
"hardware": {
"name": "Hardware",
"description": "Physical device access and flashing",
"color": Colors.YELLOW
},
"core": {
"name": "Core",
"description": "Core framework modules",
"color": Colors.WHITE
}
}
class ModuleInfo:
"""Information about a loaded module."""
def __init__(self, name: str, path: Path, module):
self.name = name
self.path = path
self.module = module
self.description = getattr(module, 'DESCRIPTION', 'No description')
self.author = getattr(module, 'AUTHOR', 'Unknown')
self.version = getattr(module, 'VERSION', '1.0')
self.category = getattr(module, 'CATEGORY', 'core').lower()
class MainMenu:
"""Main menu handler for AUTARCH."""
def __init__(self):
from core.paths import get_app_dir
self._app_dir = get_app_dir()
self.config = get_config()
self.modules: Dict[str, ModuleInfo] = {}
self.running = True
def print_status(self, message: str, status: str = "info"):
"""Print a status message."""
colors = {
"info": Colors.CYAN,
"success": Colors.GREEN,
"warning": Colors.YELLOW,
"error": Colors.RED
}
color = colors.get(status, Colors.WHITE)
symbols = {
"info": "*",
"success": "+",
"warning": "!",
"error": "X"
}
symbol = symbols.get(status, "*")
print(f"{color}[{symbol}] {message}{Colors.RESET}")
def load_modules(self):
"""Load all available modules from the modules directory.
In a frozen (PyInstaller) build, scans both the bundled modules inside
_MEIPASS and the user modules directory next to the exe. User modules
override bundled modules with the same name.
"""
from core.paths import get_modules_dir, get_user_modules_dir, is_frozen
# Collect module files — bundled first, then user (user overrides)
module_files: dict[str, Path] = {}
bundled = get_modules_dir()
if bundled.exists():
for f in bundled.glob("*.py"):
if not f.name.startswith("_") and f.stem != "setup":
module_files[f.stem] = f
if is_frozen():
user_dir = get_user_modules_dir()
if user_dir.exists():
for f in user_dir.glob("*.py"):
if not f.name.startswith("_") and f.stem != "setup":
module_files[f.stem] = f # Override bundled
for module_name, module_file in module_files.items():
try:
spec = importlib.util.spec_from_file_location(module_name, module_file)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
if hasattr(module, 'run'):
self.modules[module_name] = ModuleInfo(module_name, module_file, module)
else:
self.print_status(f"Module '{module_name}' missing run() function", "warning")
except Exception as e:
self.print_status(f"Failed to load module '{module_name}': {e}", "error")
def get_modules_by_category(self, category: str) -> Dict[str, ModuleInfo]:
"""Get all modules in a specific category."""
return {
name: info for name, info in self.modules.items()
if info.category == category
}
def get_status_line(self) -> str:
"""Get the status line showing model and MSF status."""
# Import version from main module
try:
from autarch import VERSION
except ImportError:
VERSION = "?"
parts = [f"v{VERSION}"]
# Model status - check based on backend
backend = self.config.get('autarch', 'llm_backend', 'local')
if backend == 'transformers':
model_path = self.config.get('transformers', 'model_path', '')
backend_label = "SafeTensors"
elif backend == 'claude':
model_path = self.config.get('claude', 'model', '')
backend_label = "Claude"
elif backend == 'huggingface':
model_path = self.config.get('huggingface', 'model', '')
backend_label = "HF Inference"
else:
model_path = self.config.get('llama', 'model_path', '')
backend_label = "GGUF"
if model_path:
model_name = os.path.basename(model_path)
parts.append(f"Model: {model_name} ({backend_label})")
else:
parts.append(f"{Colors.YELLOW}Model: Not configured{Colors.RESET}")
# MSF status
from .msf import get_msf_manager
msf = get_msf_manager()
if msf.is_connected:
parts.append(f"{Colors.GREEN}MSF: Connected{Colors.RESET}")
else:
parts.append(f"{Colors.DIM}MSF: Disconnected{Colors.RESET}")
# RSF status
try:
from .rsf import get_rsf_manager
rsf = get_rsf_manager()
if rsf.is_available:
parts.append(f"{Colors.GREEN}RSF: Available{Colors.RESET}")
else:
parts.append(f"{Colors.DIM}RSF: Not Found{Colors.RESET}")
except Exception:
parts.append(f"{Colors.DIM}RSF: Not Found{Colors.RESET}")
return f"{Colors.DIM} | {Colors.RESET}".join(parts)
def _show_banner(self):
"""Display banner unless disabled in settings."""
if not self.config.get_bool('autarch', 'no_banner', fallback=False):
display_banner()
def display_menu(self):
"""Display the main menu."""
clear_screen()
self._show_banner()
# Status line
print(f"{Colors.DIM}{self.get_status_line()}{Colors.RESET}")
print()
# Main menu options
print(f"{Colors.BOLD}{Colors.WHITE} Main Menu{Colors.RESET}")
print(f"{Colors.DIM} {'' * 50}{Colors.RESET}")
print()
# Category options
print(f" {Colors.BLUE}[1]{Colors.RESET} Defense {Colors.DIM}- Defensive security tools{Colors.RESET}")
print(f" {Colors.RED}[2]{Colors.RESET} Offense {Colors.DIM}- Penetration testing{Colors.RESET}")
print(f" {Colors.MAGENTA}[3]{Colors.RESET} Counter {Colors.DIM}- Counter-intelligence{Colors.RESET}")
print(f" {Colors.CYAN}[4]{Colors.RESET} Analyze {Colors.DIM}- Analysis & forensics{Colors.RESET}")
print(f" {Colors.GREEN}[5]{Colors.RESET} OSINT {Colors.DIM}- Open source intelligence{Colors.RESET}")
print(f" {Colors.YELLOW}[6]{Colors.RESET} Simulate {Colors.DIM}- Attack simulation{Colors.RESET}")
print()
print(f" {Colors.RED}[7]{Colors.RESET} Agent Hal {Colors.DIM}- AI-powered security automation{Colors.RESET}")
print()
print(f" {Colors.GREEN}[8]{Colors.RESET} Web Service {Colors.DIM}- Start/stop web dashboard{Colors.RESET}")
print(f" {Colors.CYAN}[9]{Colors.RESET} Sideload App {Colors.DIM}- Push Archon to Android device{Colors.RESET}")
print(f" {Colors.YELLOW}[10]{Colors.RESET} MCP Server {Colors.DIM}- Model Context Protocol tools{Colors.RESET}")
print(f" {Colors.WHITE}[11]{Colors.RESET} User Manual {Colors.DIM}- In-depth guide & documentation{Colors.RESET}")
print(f" {Colors.DIM}[12]{Colors.RESET} List Modules {Colors.DIM}- Show all loaded modules{Colors.RESET}")
print()
print(f" {Colors.DIM}[99]{Colors.RESET} Settings")
print(f" {Colors.DIM}[98]{Colors.RESET} Exit")
print()
def display_category_menu(self, category: str):
"""Display the submenu for a category."""
cat_info = CATEGORIES.get(category, CATEGORIES['core'])
cat_modules = self.get_modules_by_category(category)
clear_screen()
self._show_banner()
print(f"{cat_info['color']}{Colors.BOLD} {cat_info['name']}{Colors.RESET}")
print(f"{Colors.DIM} {cat_info['description']}{Colors.RESET}")
print(f"{Colors.DIM} {'' * 50}{Colors.RESET}")
print()
if not cat_modules:
print(f" {Colors.YELLOW}No modules in this category.{Colors.RESET}")
print(f" {Colors.DIM}Add modules with CATEGORY = '{category}'{Colors.RESET}")
else:
module_list = list(cat_modules.keys())
for i, name in enumerate(module_list, 1):
info = cat_modules[name]
print(f" {cat_info['color']}[{i}]{Colors.RESET} {name}")
print(f" {Colors.DIM}{info.description}{Colors.RESET}")
print()
print(f" {Colors.DIM}[0]{Colors.RESET} Back to main menu")
print()
try:
choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip()
if choice == "0" or not choice:
return
if cat_modules:
module_list = list(cat_modules.keys())
try:
index = int(choice) - 1
if 0 <= index < len(module_list):
self.run_module(module_list[index])
except ValueError:
if choice in cat_modules:
self.run_module(choice)
except (EOFError, KeyboardInterrupt):
print()
def run_module(self, module_name: str):
"""Run a specific module."""
if module_name not in self.modules:
self.print_status(f"Module '{module_name}' not found", "error")
return
module_info = self.modules[module_name]
clear_screen()
self._show_banner()
print(f"{Colors.GREEN}[+] Running module: {module_name}{Colors.RESET}")
print(f"{Colors.DIM}{'' * 50}{Colors.RESET}\n")
try:
module_info.module.run()
except Exception as e:
self.print_status(f"Module error: {e}", "error")
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
def show_settings(self):
"""Display settings menu."""
while True:
clear_screen()
self._show_banner()
print(f"{Colors.BOLD}{Colors.WHITE} Settings{Colors.RESET}")
print(f"{Colors.DIM} {'' * 50}{Colors.RESET}")
print()
print(f" {Colors.CYAN}[1]{Colors.RESET} LLM Settings")
print(f" {Colors.CYAN}[2]{Colors.RESET} Metasploit Settings")
print(f" {Colors.CYAN}[3]{Colors.RESET} Database Management")
print(f" {Colors.CYAN}[4]{Colors.RESET} Custom APIs")
print(f" {Colors.CYAN}[5]{Colors.RESET} AUTARCH API")
print(f" {Colors.CYAN}[6]{Colors.RESET} OSINT Settings")
print(f" {Colors.CYAN}[7]{Colors.RESET} RouterSploit Settings")
print(f" {Colors.CYAN}[8]{Colors.RESET} UPnP Settings")
print(f" {Colors.CYAN}[9]{Colors.RESET} Reverse Shell Settings")
print(f" {Colors.CYAN}[10]{Colors.RESET} Display Settings")
print(f" {Colors.CYAN}[11]{Colors.RESET} Load Config File")
print()
print(f" {Colors.DIM}[12]{Colors.RESET} View All Settings")
print(f" {Colors.DIM}[13]{Colors.RESET} Run Setup Wizard")
print()
print(f" {Colors.DIM}[0]{Colors.RESET} Back")
print()
try:
choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip()
if choice == "0" or not choice:
break
elif choice == "1":
self.show_llm_settings()
elif choice == "2":
self.show_msf_settings()
elif choice == "3":
self.show_database_management()
elif choice == "4":
self.show_custom_apis()
elif choice == "5":
self.show_autarch_api()
elif choice == "6":
self.show_osint_settings()
elif choice == "7":
self.show_rsf_settings()
elif choice == "8":
self.show_upnp_settings()
elif choice == "9":
self.show_revshell_settings()
elif choice == "10":
self.show_display_settings()
elif choice == "11":
self.load_config_file()
elif choice == "12":
self.show_all_settings()
elif choice == "13":
self.run_setup()
except (EOFError, KeyboardInterrupt):
break
def show_llm_settings(self):
"""Display and configure LLM settings."""
while True:
clear_screen()
self._show_banner()
backend = self.config.get('autarch', 'llm_backend', 'local')
print(f"{Colors.BOLD}{Colors.WHITE} LLM Configuration{Colors.RESET}")
print(f"{Colors.DIM} {'' * 50}{Colors.RESET}")
print()
# Display backend-specific settings
if backend == 'transformers':
settings = self.config.get_transformers_settings()
print(f" {Colors.YELLOW}Backend: transformers (SafeTensors){Colors.RESET}")
print()
model_name = Path(settings['model_path']).name if settings['model_path'] else "(not set)"
print(f" {Colors.CYAN}Model:{Colors.RESET} {model_name}")
print(f" {Colors.CYAN}Device:{Colors.RESET} {settings['device']}")
print(f" {Colors.CYAN}Load in 8-bit:{Colors.RESET} {settings['load_in_8bit']}")
print(f" {Colors.CYAN}Load in 4-bit:{Colors.RESET} {settings['load_in_4bit']}")
print(f" {Colors.CYAN}Temperature:{Colors.RESET} {settings['temperature']}")
print(f" {Colors.CYAN}Top P:{Colors.RESET} {settings['top_p']}")
print(f" {Colors.CYAN}Top K:{Colors.RESET} {settings['top_k']}")
print(f" {Colors.CYAN}Repetition Penalty:{Colors.RESET} {settings['repetition_penalty']}")
print(f" {Colors.CYAN}Max Tokens:{Colors.RESET} {settings['max_tokens']}")
elif backend == 'claude':
settings = self.config.get_claude_settings()
print(f" {Colors.YELLOW}Backend: Claude API{Colors.RESET}")
print()
print(f" {Colors.CYAN}Model:{Colors.RESET} {settings['model']}")
print(f" {Colors.CYAN}API Key:{Colors.RESET} {'***configured***' if settings['api_key'] else '(not set)'}")
print(f" {Colors.CYAN}Max Tokens:{Colors.RESET} {settings['max_tokens']}")
print(f" {Colors.CYAN}Temperature:{Colors.RESET} {settings['temperature']}")
elif backend == 'huggingface':
settings = self.config.get_huggingface_settings()
print(f" {Colors.YELLOW}Backend: HuggingFace Inference API{Colors.RESET}")
print()
print(f" {Colors.CYAN}Model:{Colors.RESET} {settings['model']}")
print(f" {Colors.CYAN}Endpoint:{Colors.RESET} {settings['endpoint'] or '(HuggingFace Hub)'}")
print(f" {Colors.CYAN}API Key:{Colors.RESET} {'***configured***' if settings['api_key'] else '(not set / free tier)'}")
print(f" {Colors.CYAN}Max Tokens:{Colors.RESET} {settings['max_tokens']}")
print(f" {Colors.CYAN}Temperature:{Colors.RESET} {settings['temperature']}")
print(f" {Colors.CYAN}Top P:{Colors.RESET} {settings['top_p']}")
else: # llama.cpp / GGUF
settings = self.config.get_llama_settings()
print(f" {Colors.YELLOW}Backend: llama.cpp (GGUF){Colors.RESET}")
print()
model_name = Path(settings['model_path']).name if settings['model_path'] else "(not set)"
print(f" {Colors.CYAN}Model:{Colors.RESET} {model_name}")
print(f" {Colors.CYAN}Context Size:{Colors.RESET} {settings['n_ctx']} tokens")
print(f" {Colors.CYAN}Threads:{Colors.RESET} {settings['n_threads']}")
print(f" {Colors.CYAN}GPU Layers:{Colors.RESET} {settings['n_gpu_layers']}")
print(f" {Colors.CYAN}Temperature:{Colors.RESET} {settings['temperature']}")
print(f" {Colors.CYAN}Top P:{Colors.RESET} {settings['top_p']}")
print(f" {Colors.CYAN}Top K:{Colors.RESET} {settings['top_k']}")
print(f" {Colors.CYAN}Repeat Penalty:{Colors.RESET} {settings['repeat_penalty']}")
print(f" {Colors.CYAN}Max Tokens:{Colors.RESET} {settings['max_tokens']}")
print()
# Check if model is loaded
from .llm import get_llm
llm = get_llm()
if llm.is_loaded:
print(f" {Colors.GREEN}Status: Model loaded{Colors.RESET}")
else:
print(f" {Colors.YELLOW}Status: Model not loaded{Colors.RESET}")
print()
print(f" {Colors.CYAN}[1]{Colors.RESET} Set Model Path")
if backend != 'transformers':
print(f" {Colors.CYAN}[2]{Colors.RESET} Set Context Size")
print(f" {Colors.CYAN}[3]{Colors.RESET} Set Threads")
print(f" {Colors.CYAN}[4]{Colors.RESET} Set GPU Layers")
else:
print(f" {Colors.CYAN}[2]{Colors.RESET} Set Device")
print(f" {Colors.CYAN}[3]{Colors.RESET} Set Quantization")
print(f" {Colors.CYAN}[5]{Colors.RESET} Set Temperature")
print(f" {Colors.CYAN}[6]{Colors.RESET} Set Top P / Top K")
print(f" {Colors.CYAN}[7]{Colors.RESET} Set Repeat Penalty")
print(f" {Colors.CYAN}[8]{Colors.RESET} Set Max Tokens")
print()
print(f" {Colors.CYAN}[L]{Colors.RESET} Load/Reload Model")
print(f" {Colors.CYAN}[U]{Colors.RESET} Unload Model")
print(f" {Colors.CYAN}[S]{Colors.RESET} Switch Backend")
print()
print(f" {Colors.GREEN}[T]{Colors.RESET} Load Hardware Template")
print(f" {Colors.GREEN}[C]{Colors.RESET} Load Custom Config")
print(f" {Colors.GREEN}[W]{Colors.RESET} Save Current as Custom Config")
print()
print(f" {Colors.DIM}[0]{Colors.RESET} Back")
print()
try:
choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip().lower()
if choice == "0" or not choice:
break
elif choice == "1":
self._set_llm_model_path()
elif choice == "2":
if backend != 'transformers':
self._set_llm_context_size()
else:
self._set_transformers_device()
elif choice == "3":
if backend != 'transformers':
self._set_llm_threads()
else:
self._set_transformers_quantization()
elif choice == "4" and backend != 'transformers':
self._set_llm_gpu_layers()
elif choice == "5":
self._set_llm_temperature()
elif choice == "6":
self._set_llm_sampling()
elif choice == "7":
self._set_llm_repeat_penalty()
elif choice == "8":
self._set_llm_max_tokens()
elif choice == "l":
self._load_llm_model()
elif choice == "u":
self._unload_llm_model()
elif choice == "s":
self._switch_llm_backend()
elif choice == "t":
self._load_hardware_template()
elif choice == "c":
self._load_custom_config()
elif choice == "w":
self._save_custom_config()
except (EOFError, KeyboardInterrupt):
break
def _set_llm_model_path(self):
"""Set LLM model path (GGUF file or SafeTensors directory)."""
print()
backend = self.config.get('autarch', 'llm_backend', 'local')
if backend == 'transformers':
current = self.config.get('transformers', 'model_path', '')
else:
current = self.config.get('llama', 'model_path', '')
if current:
print(f" {Colors.DIM}Current: {current}{Colors.RESET}")
print(f" {Colors.DIM}Enter path to GGUF file, SafeTensors directory, or HuggingFace model ID{Colors.RESET}")
print(f" {Colors.DIM}Examples: /path/to/model.gguf, models/MyModel, org/model-name{Colors.RESET}")
print()
try:
path = input(f" {Colors.WHITE}Model path: {Colors.RESET}").strip()
if path:
# Strip quotes from path
path = path.strip('"').strip("'")
path = os.path.expanduser(path)
# Resolve the path - try multiple options
resolved_path = self._resolve_model_path(path)
if resolved_path:
# Detect model type
from .llm import detect_model_type
model_type = detect_model_type(resolved_path)
if model_type == 'gguf':
self.config.set('llama', 'model_path', resolved_path)
self.config.set('autarch', 'llm_backend', 'local')
self.config.save()
self.print_status(f"GGUF model set: {Path(resolved_path).name}", "success")
# Reset LLM instance to use new backend
from .llm import reset_llm
reset_llm()
elif model_type == 'transformers':
self.config.set('transformers', 'model_path', resolved_path)
self.config.set('autarch', 'llm_backend', 'transformers')
self.config.save()
self.print_status(f"SafeTensors model set: {Path(resolved_path).name}", "success")
# Reset LLM instance to use new backend
from .llm import reset_llm
reset_llm()
else:
self.print_status("Unrecognized model format. Expected .gguf file or model directory with .safetensors", "error")
elif self._is_huggingface_id(path):
# HuggingFace model ID (e.g., 'org/model-name')
self.config.set('transformers', 'model_path', path)
self.config.set('autarch', 'llm_backend', 'transformers')
self.config.save()
self.print_status(f"HuggingFace model ID set: {path}", "success")
print(f" {Colors.DIM}Model will be loaded from HuggingFace cache{Colors.RESET}")
# Reset LLM instance to use new backend
from .llm import reset_llm
reset_llm()
else:
self.print_status("Path not found. Check the path and try again.", "error")
except (EOFError, KeyboardInterrupt):
print()
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
def _is_huggingface_id(self, path: str) -> bool:
"""Check if the path looks like a HuggingFace model ID.
Args:
path: The path/ID to check
Returns:
True if it looks like a HuggingFace model ID (org/model-name)
"""
if not path:
return False
if path.startswith('/') or path.startswith('\\'):
return False
parts = path.split('/')
if len(parts) == 2 and all(p and not p.startswith('.') for p in parts):
return True
return False
def _resolve_model_path(self, path: str) -> str:
"""Resolve a model path, trying multiple variations.
Args:
path: User-provided path (may be relative or have variations)
Returns:
Resolved absolute path if found, None otherwise
"""
framework_dir = self._app_dir
# List of paths to try
paths_to_try = [
Path(path), # As-is
Path(path).expanduser(), # Expand ~
framework_dir / path.lstrip('/'), # Relative to framework dir
framework_dir / path, # Relative without stripping /
]
# Handle /dh_framework/... pattern (missing /home/user prefix)
if path.startswith('/dh_framework'):
paths_to_try.append(framework_dir / path[len('/dh_framework/'):])
if path.startswith('dh_framework'):
paths_to_try.append(framework_dir / path[len('dh_framework/'):])
# Also try models/ subdirectory
model_name = Path(path).name
paths_to_try.append(framework_dir / 'models' / model_name)
for p in paths_to_try:
try:
if p.exists():
return str(p.resolve())
except (PermissionError, OSError):
continue
return None
def _set_llm_context_size(self):
"""Set LLM context size."""
print()
current = self.config.get_int('llama', 'n_ctx', 4096)
print(f" {Colors.DIM}Current: {current} tokens{Colors.RESET}")
print(f" {Colors.DIM}Common values: 2048, 4096, 8192, 16384, 32768{Colors.RESET}")
print()
try:
n_ctx = input(f" {Colors.WHITE}Context size [{current}]: {Colors.RESET}").strip()
if n_ctx:
n_ctx = int(n_ctx)
if 512 <= n_ctx <= 131072:
self.config.set('llama', 'n_ctx', str(n_ctx))
self.config.save()
self.print_status(f"Context size set to {n_ctx}", "success")
else:
self.print_status("Value must be between 512 and 131072", "error")
except ValueError:
self.print_status("Invalid number", "error")
except (EOFError, KeyboardInterrupt):
print()
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
def _set_llm_threads(self):
"""Set LLM CPU threads."""
print()
current = self.config.get_int('llama', 'n_threads', 4)
cpu_count = os.cpu_count() or 4
print(f" {Colors.DIM}Current: {current} threads{Colors.RESET}")
print(f" {Colors.DIM}Your system has {cpu_count} CPU cores{Colors.RESET}")
print()
try:
threads = input(f" {Colors.WHITE}Threads [{current}]: {Colors.RESET}").strip()
if threads:
threads = int(threads)
if 1 <= threads <= 256:
self.config.set('llama', 'n_threads', str(threads))
self.config.save()
self.print_status(f"Threads set to {threads}", "success")
else:
self.print_status("Value must be between 1 and 256", "error")
except ValueError:
self.print_status("Invalid number", "error")
except (EOFError, KeyboardInterrupt):
print()
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
def _set_llm_gpu_layers(self):
"""Set LLM GPU layers."""
print()
current = self.config.get_int('llama', 'n_gpu_layers', 0)
print(f" {Colors.DIM}Current: {current} layers{Colors.RESET}")
print(f" {Colors.DIM}Set to 0 for CPU only, higher for GPU acceleration{Colors.RESET}")
print(f" {Colors.DIM}Use -1 to offload all layers to GPU{Colors.RESET}")
print()
try:
layers = input(f" {Colors.WHITE}GPU layers [{current}]: {Colors.RESET}").strip()
if layers:
layers = int(layers)
if layers >= -1:
self.config.set('llama', 'n_gpu_layers', str(layers))
self.config.save()
self.print_status(f"GPU layers set to {layers}", "success")
else:
self.print_status("Value must be -1 or higher", "error")
except ValueError:
self.print_status("Invalid number", "error")
except (EOFError, KeyboardInterrupt):
print()
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
def _get_llm_config_section(self):
"""Get the config section name for the current LLM backend."""
backend = self.config.get('autarch', 'llm_backend', 'local')
return {'transformers': 'transformers', 'claude': 'claude', 'huggingface': 'huggingface'}.get(backend, 'llama')
def _set_llm_temperature(self):
"""Set LLM temperature."""
print()
backend = self.config.get('autarch', 'llm_backend', 'local')
section = self._get_llm_config_section()
current = self.config.get_float(section, 'temperature', 0.7)
print(f" {Colors.DIM}Current: {current}{Colors.RESET}")
print(f" {Colors.DIM}Lower = more focused, Higher = more creative{Colors.RESET}")
print(f" {Colors.DIM}Typical range: 0.1 - 1.5{Colors.RESET}")
print()
try:
temp = input(f" {Colors.WHITE}Temperature [{current}]: {Colors.RESET}").strip()
if temp:
temp = float(temp)
if 0.0 <= temp <= 2.0:
self.config.set(section, 'temperature', str(temp))
self.config.save()
self.print_status(f"Temperature set to {temp}", "success")
else:
self.print_status("Value must be between 0.0 and 2.0", "error")
except ValueError:
self.print_status("Invalid number", "error")
except (EOFError, KeyboardInterrupt):
print()
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
def _set_llm_sampling(self):
"""Set LLM Top P and Top K sampling parameters."""
print()
backend = self.config.get('autarch', 'llm_backend', 'local')
section = self._get_llm_config_section()
current_p = self.config.get_float(section, 'top_p', 0.9)
current_k = self.config.get_int(section, 'top_k', 40)
print(f" {Colors.DIM}Current Top P: {current_p} (nucleus sampling){Colors.RESET}")
print(f" {Colors.DIM}Current Top K: {current_k}{Colors.RESET}")
print()
try:
# Top P
top_p = input(f" {Colors.WHITE}Top P (0.0-1.0) [{current_p}]: {Colors.RESET}").strip()
if top_p:
top_p = float(top_p)
if 0.0 <= top_p <= 1.0:
self.config.set(section, 'top_p', str(top_p))
self.config.save()
self.print_status(f"Top P set to {top_p}", "success")
else:
self.print_status("Top P must be between 0.0 and 1.0", "error")
# Top K
top_k = input(f" {Colors.WHITE}Top K (0-1000) [{current_k}]: {Colors.RESET}").strip()
if top_k:
top_k = int(top_k)
if 0 <= top_k <= 1000:
self.config.set(section, 'top_k', str(top_k))
self.config.save()
self.print_status(f"Top K set to {top_k}", "success")
else:
self.print_status("Top K must be between 0 and 1000", "error")
except ValueError:
self.print_status("Invalid number", "error")
except (EOFError, KeyboardInterrupt):
print()
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
def _set_llm_repeat_penalty(self):
"""Set LLM repeat penalty."""
print()
backend = self.config.get('autarch', 'llm_backend', 'local')
section = self._get_llm_config_section()
if backend == 'transformers':
key = 'repetition_penalty'
elif backend in ('claude', 'huggingface'):
# These backends don't have repeat_penalty
self.print_status("Repeat penalty not applicable for this backend", "info")
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
return
else:
key = 'repeat_penalty'
current = self.config.get_float(section, key, 1.1)
print(f" {Colors.DIM}Current: {current}{Colors.RESET}")
print(f" {Colors.DIM}1.0 = no penalty, higher = less repetition{Colors.RESET}")
print()
try:
penalty = input(f" {Colors.WHITE}Repeat penalty [{current}]: {Colors.RESET}").strip()
if penalty:
penalty = float(penalty)
if 0.0 <= penalty <= 2.0:
self.config.set(section, key, str(penalty))
self.config.save()
self.print_status(f"Repeat penalty set to {penalty}", "success")
else:
self.print_status("Value must be between 0.0 and 2.0", "error")
except ValueError:
self.print_status("Invalid number", "error")
except (EOFError, KeyboardInterrupt):
print()
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
def _set_llm_max_tokens(self):
"""Set LLM max tokens per response."""
print()
backend = self.config.get('autarch', 'llm_backend', 'local')
section = self._get_llm_config_section()
current = self.config.get_int(section, 'max_tokens', 2048)
print(f" {Colors.DIM}Current: {current} tokens{Colors.RESET}")
print(f" {Colors.DIM}Maximum tokens generated per response{Colors.RESET}")
print()
try:
tokens = input(f" {Colors.WHITE}Max tokens [{current}]: {Colors.RESET}").strip()
if tokens:
tokens = int(tokens)
if 1 <= tokens <= 32768:
self.config.set(section, 'max_tokens', str(tokens))
self.config.save()
self.print_status(f"Max tokens set to {tokens}", "success")
else:
self.print_status("Value must be between 1 and 32768", "error")
except ValueError:
self.print_status("Invalid number", "error")
except (EOFError, KeyboardInterrupt):
print()
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
def _load_llm_model(self):
"""Load or reload the LLM model."""
from .llm import get_llm, LLMError
print()
backend = self.config.get('autarch', 'llm_backend', 'local')
if backend == 'transformers':
model_path = self.config.get('transformers', 'model_path', '')
is_valid = model_path and os.path.isdir(model_path)
elif backend == 'claude':
model_path = self.config.get('claude', 'model', '')
is_valid = bool(model_path) # Just needs model name
elif backend == 'huggingface':
model_path = self.config.get('huggingface', 'model', '')
is_valid = bool(model_path) # Just needs model ID
else:
model_path = self.config.get('llama', 'model_path', '')
is_valid = model_path and os.path.isfile(model_path)
if not model_path:
self.print_status("No model path configured. Set model path first.", "error")
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
return
if not is_valid:
self.print_status(f"Model not found: {model_path}", "error")
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
return
self.print_status(f"Loading model ({backend})...", "info")
print(f" {Colors.DIM}This may take a moment...{Colors.RESET}")
print()
try:
llm = get_llm()
if llm.is_loaded:
llm.unload_model()
llm.load_model(verbose=True)
self.print_status("Model loaded successfully", "success")
except LLMError as e:
self.print_status(f"Failed to load model: {e}", "error")
except Exception as e:
self.print_status(f"Error: {e}", "error")
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
def _set_transformers_device(self):
"""Set transformers device (cuda/cpu/mps/auto)."""
print()
current = self.config.get('transformers', 'device', 'auto')
print(f" {Colors.DIM}Current: {current}{Colors.RESET}")
print(f" {Colors.DIM}Options: auto, cuda, cpu, mps{Colors.RESET}")
print()
try:
device = input(f" {Colors.WHITE}Device [{current}]: {Colors.RESET}").strip()
if device:
if device in ['auto', 'cuda', 'cpu', 'mps']:
self.config.set('transformers', 'device', device)
self.config.save()
self.print_status(f"Device set to {device}", "success")
else:
self.print_status("Invalid device. Use: auto, cuda, cpu, or mps", "error")
except (EOFError, KeyboardInterrupt):
print()
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
def _set_transformers_quantization(self):
"""Set transformers quantization settings."""
print()
load_8bit = self.config.get_bool('transformers', 'load_in_8bit', False)
load_4bit = self.config.get_bool('transformers', 'load_in_4bit', False)
if load_4bit:
current = "4-bit"
elif load_8bit:
current = "8-bit"
else:
current = "None (full precision)"
print(f" {Colors.DIM}Current: {current}{Colors.RESET}")
print(f" {Colors.DIM}Quantization reduces memory but may affect quality{Colors.RESET}")
print(f" {Colors.DIM}Requires bitsandbytes package for 8-bit/4-bit{Colors.RESET}")
print()
print(f" {Colors.GREEN}[1]{Colors.RESET} No quantization (full precision)")
print(f" {Colors.GREEN}[2]{Colors.RESET} 8-bit quantization")
print(f" {Colors.GREEN}[3]{Colors.RESET} 4-bit quantization")
print()
try:
choice = input(f" {Colors.WHITE}Select: {Colors.RESET}").strip()
if choice == "1":
self.config.set('transformers', 'load_in_8bit', 'false')
self.config.set('transformers', 'load_in_4bit', 'false')
self.config.save()
self.print_status("Quantization disabled", "success")
elif choice == "2":
self.config.set('transformers', 'load_in_8bit', 'true')
self.config.set('transformers', 'load_in_4bit', 'false')
self.config.save()
self.print_status("8-bit quantization enabled", "success")
elif choice == "3":
self.config.set('transformers', 'load_in_8bit', 'false')
self.config.set('transformers', 'load_in_4bit', 'true')
self.config.save()
self.print_status("4-bit quantization enabled", "success")
except (EOFError, KeyboardInterrupt):
print()
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
def _switch_llm_backend(self):
"""Switch between LLM backends."""
from .llm import reset_llm
print()
current = self.config.get('autarch', 'llm_backend', 'local')
print(f" {Colors.DIM}Current backend: {current}{Colors.RESET}")
print()
print(f" {Colors.GREEN}[1]{Colors.RESET} llama.cpp (GGUF models)")
print(f" {Colors.GREEN}[2]{Colors.RESET} transformers (SafeTensors / PyTorch)")
print(f" {Colors.GREEN}[3]{Colors.RESET} Claude API")
print(f" {Colors.GREEN}[4]{Colors.RESET} HuggingFace Inference API")
print()
try:
choice = input(f" {Colors.WHITE}Select backend: {Colors.RESET}").strip()
new_backend = None
if choice == "1":
new_backend = "local"
elif choice == "2":
new_backend = "transformers"
elif choice == "3":
new_backend = "claude"
elif choice == "4":
new_backend = "huggingface"
if new_backend and new_backend != current:
self.config.set('autarch', 'llm_backend', new_backend)
self.config.save()
reset_llm() # Reset to pick up new backend
self.print_status(f"Backend switched to {new_backend}", "success")
except (EOFError, KeyboardInterrupt):
print()
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
def _load_hardware_template(self):
"""Load a hardware-specific configuration template."""
print()
print(f" {Colors.BOLD}{Colors.WHITE}Hardware Configuration Templates{Colors.RESET}")
print(f" {Colors.DIM}Select a template optimized for your hardware{Colors.RESET}")
print()
templates = self.config.list_hardware_templates()
for i, (template_id, name, description, _) in enumerate(templates, 1):
is_experimental = 'EXPERIMENTAL' in description
color = Colors.YELLOW if is_experimental else Colors.GREEN
print(f" {color}[{i}]{Colors.RESET} {name}")
print(f" {Colors.DIM}{description}{Colors.RESET}")
print()
print(f" {Colors.DIM}[0]{Colors.RESET} Cancel")
print()
try:
choice = input(f" {Colors.WHITE}Select template: {Colors.RESET}").strip()
if choice and choice != "0":
try:
index = int(choice) - 1
if 0 <= index < len(templates):
template_id = templates[index][0]
template_name = templates[index][1]
# Confirm experimental templates
if 'EXPERIMENTAL' in templates[index][2]:
print()
print(f" {Colors.YELLOW}WARNING: This template is experimental!{Colors.RESET}")
confirm = input(f" {Colors.WHITE}Continue? (y/n): {Colors.RESET}").strip().lower()
if confirm != 'y':
self.print_status("Cancelled", "info")
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
return
if self.config.load_template(template_id):
self.print_status(f"Loaded template: {template_name}", "success")
print(f" {Colors.DIM}Note: Model path preserved from current config{Colors.RESET}")
else:
self.print_status("Failed to load template", "error")
else:
self.print_status("Invalid selection", "error")
except ValueError:
self.print_status("Invalid selection", "error")
except (EOFError, KeyboardInterrupt):
print()
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
def _load_custom_config(self):
"""Load a user-saved custom configuration."""
print()
print(f" {Colors.BOLD}{Colors.WHITE}Custom Configurations{Colors.RESET}")
print()
custom_configs = self.config.list_custom_configs()
if not custom_configs:
print(f" {Colors.YELLOW}No custom configurations found.{Colors.RESET}")
print(f" {Colors.DIM}Use [W] Save Current as Custom Config to create one.{Colors.RESET}")
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
return
for i, (name, filepath) in enumerate(custom_configs, 1):
print(f" {Colors.GREEN}[{i}]{Colors.RESET} {name}")
print(f" {Colors.DIM}{filepath.name}{Colors.RESET}")
print()
print(f" {Colors.RED}[D]{Colors.RESET} Delete a custom config")
print(f" {Colors.DIM}[0]{Colors.RESET} Cancel")
print()
try:
choice = input(f" {Colors.WHITE}Select config: {Colors.RESET}").strip().lower()
if choice == "d":
self._delete_custom_config(custom_configs)
elif choice and choice != "0":
try:
index = int(choice) - 1
if 0 <= index < len(custom_configs):
name, filepath = custom_configs[index]
if self.config.load_custom_config(filepath):
self.print_status(f"Loaded config: {name}", "success")
print(f" {Colors.DIM}Note: Model path preserved from current config{Colors.RESET}")
else:
self.print_status("Failed to load config", "error")
else:
self.print_status("Invalid selection", "error")
except ValueError:
self.print_status("Invalid selection", "error")
except (EOFError, KeyboardInterrupt):
print()
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
def _delete_custom_config(self, custom_configs: list):
"""Delete a custom configuration file."""
print()
print(f" {Colors.RED}Delete Custom Configuration{Colors.RESET}")
print()
for i, (name, filepath) in enumerate(custom_configs, 1):
print(f" {Colors.RED}[{i}]{Colors.RESET} {name}")
print()
print(f" {Colors.DIM}[0]{Colors.RESET} Cancel")
print()
try:
choice = input(f" {Colors.WHITE}Select config to delete: {Colors.RESET}").strip()
if choice and choice != "0":
try:
index = int(choice) - 1
if 0 <= index < len(custom_configs):
name, filepath = custom_configs[index]
confirm = input(f" {Colors.WHITE}Delete '{name}'? (y/n): {Colors.RESET}").strip().lower()
if confirm == 'y':
if self.config.delete_custom_config(filepath):
self.print_status(f"Deleted: {name}", "success")
else:
self.print_status("Failed to delete config", "error")
else:
self.print_status("Cancelled", "info")
else:
self.print_status("Invalid selection", "error")
except ValueError:
self.print_status("Invalid selection", "error")
except (EOFError, KeyboardInterrupt):
print()
def _save_custom_config(self):
"""Save current LLM settings as a custom configuration."""
print()
print(f" {Colors.BOLD}{Colors.WHITE}Save Custom Configuration{Colors.RESET}")
print(f" {Colors.DIM}Save your current LLM settings for later use{Colors.RESET}")
print()
try:
name = input(f" {Colors.WHITE}Configuration name: {Colors.RESET}").strip()
if name:
filepath = self.config.save_custom_config(name)
self.print_status(f"Saved to: {filepath.name}", "success")
print(f" {Colors.DIM}Full path: {filepath}{Colors.RESET}")
else:
self.print_status("No name provided, cancelled", "info")
except (EOFError, KeyboardInterrupt):
print()
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
def _unload_llm_model(self):
"""Unload the current LLM model."""
from .llm import get_llm
print()
llm = get_llm()
if not llm.is_loaded:
self.print_status("No model currently loaded", "info")
else:
llm.unload_model()
self.print_status("Model unloaded", "success")
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
def show_osint_settings(self):
"""Display and configure OSINT settings."""
while True:
clear_screen()
self._show_banner()
print(f"{Colors.BOLD}{Colors.WHITE} OSINT Settings{Colors.RESET}")
print(f"{Colors.DIM} {'' * 50}{Colors.RESET}")
print()
settings = self.config.get_osint_settings()
print(f" {Colors.CYAN}Max Threads:{Colors.RESET} {settings['max_threads']}")
print(f" {Colors.CYAN}Timeout:{Colors.RESET} {settings['timeout']} seconds")
print(f" {Colors.CYAN}Include NSFW:{Colors.RESET} {'Yes' if settings['include_nsfw'] else 'No'}")
print()
print(f" {Colors.DIM}Thread setting controls parallel requests during{Colors.RESET}")
print(f" {Colors.DIM}username scanning. Lower values = slower but safer.{Colors.RESET}")
print()
print(f" {Colors.CYAN}[1]{Colors.RESET} Set Max Threads")
print(f" {Colors.CYAN}[2]{Colors.RESET} Set Timeout")
print(f" {Colors.CYAN}[3]{Colors.RESET} Toggle NSFW Sites")
print()
print(f" {Colors.DIM}[0]{Colors.RESET} Back")
print()
try:
choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip()
if choice == "0" or not choice:
break
elif choice == "1":
self._set_osint_threads()
elif choice == "2":
self._set_osint_timeout()
elif choice == "3":
self._toggle_osint_nsfw()
except (EOFError, KeyboardInterrupt):
break
def _set_osint_threads(self):
"""Set OSINT max threads."""
print()
current = self.config.get_int('osint', 'max_threads', 8)
print(f" {Colors.DIM}Current: {current} threads{Colors.RESET}")
print(f" {Colors.DIM}Recommended: 4-16 depending on your system{Colors.RESET}")
print()
try:
threads = input(f" {Colors.WHITE}Max threads (1-100) [{current}]: {Colors.RESET}").strip()
if threads:
threads = int(threads)
if 1 <= threads <= 100:
self.config.set('osint', 'max_threads', str(threads))
self.config.save()
self.print_status(f"Max threads set to {threads}", "success")
else:
self.print_status("Value must be between 1 and 100", "error")
except ValueError:
self.print_status("Invalid number", "error")
except (EOFError, KeyboardInterrupt):
print()
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
def _set_osint_timeout(self):
"""Set OSINT timeout."""
print()
current = self.config.get_int('osint', 'timeout', 8)
print(f" {Colors.DIM}Current: {current} seconds{Colors.RESET}")
print()
try:
timeout = input(f" {Colors.WHITE}Timeout in seconds (1-60) [{current}]: {Colors.RESET}").strip()
if timeout:
timeout = int(timeout)
if 1 <= timeout <= 60:
self.config.set('osint', 'timeout', str(timeout))
self.config.save()
self.print_status(f"Timeout set to {timeout} seconds", "success")
else:
self.print_status("Value must be between 1 and 60", "error")
except ValueError:
self.print_status("Invalid number", "error")
except (EOFError, KeyboardInterrupt):
print()
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
def _toggle_osint_nsfw(self):
"""Toggle OSINT NSFW sites inclusion."""
current = self.config.get_bool('osint', 'include_nsfw', False)
new_value = not current
self.config.set('osint', 'include_nsfw', str(new_value).lower())
self.config.save()
self.print_status(f"NSFW sites {'enabled' if new_value else 'disabled'}", "success")
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
def show_rsf_settings(self):
"""Display and configure RouterSploit settings."""
from .rsf import get_rsf_manager
rsf = get_rsf_manager()
while True:
clear_screen()
self._show_banner()
print(f"{Colors.BOLD}{Colors.WHITE} RouterSploit Configuration{Colors.RESET}")
print(f"{Colors.DIM} {'' * 50}{Colors.RESET}")
print()
settings = self.config.get_rsf_settings()
print(f" {Colors.CYAN}Install Path:{Colors.RESET} {settings['install_path']}")
# Status
if rsf.is_available:
count = rsf.get_module_count()
print(f" {Colors.CYAN}Status:{Colors.RESET} {Colors.GREEN}Available ({count} modules){Colors.RESET}")
else:
print(f" {Colors.CYAN}Status:{Colors.RESET} {Colors.YELLOW}Not Found{Colors.RESET}")
default_target = settings['default_target'] or '(not set)'
print(f" {Colors.CYAN}Default Target:{Colors.RESET} {default_target}")
print(f" {Colors.CYAN}Timeout:{Colors.RESET} {settings['execution_timeout']}s")
print()
print(f" {Colors.CYAN}[1]{Colors.RESET} Set Install Path")
print(f" {Colors.CYAN}[2]{Colors.RESET} Set Default Target")
print(f" {Colors.CYAN}[3]{Colors.RESET} Set Timeout")
print(f" {Colors.CYAN}[4]{Colors.RESET} Test Installation")
print()
print(f" {Colors.DIM}[0]{Colors.RESET} Back")
print()
try:
choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip()
if choice == "0" or not choice:
break
elif choice == "1":
print()
current = settings['install_path']
print(f" {Colors.DIM}Current: {current}{Colors.RESET}")
path = input(f" {Colors.WHITE}Install path: {Colors.RESET}").strip()
if path:
import os
path = os.path.expanduser(path)
if os.path.isdir(path):
self.config.set('rsf', 'install_path', path)
self.config.save()
rsf.reset_cache()
self.print_status(f"Install path set to: {path}", "success")
else:
self.print_status(f"Directory not found: {path}", "error")
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
elif choice == "2":
print()
current = settings['default_target']
prompt = f"Default target [{current}]: " if current else "Default target: "
target = input(f" {Colors.WHITE}{prompt}{Colors.RESET}").strip()
if target:
self.config.set('rsf', 'default_target', target)
self.config.save()
self.print_status(f"Default target set to: {target}", "success")
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
elif choice == "3":
print()
current = settings['execution_timeout']
timeout_str = input(f" {Colors.WHITE}Timeout in seconds [{current}]: {Colors.RESET}").strip()
if timeout_str:
try:
timeout = int(timeout_str)
if 10 <= timeout <= 600:
self.config.set('rsf', 'execution_timeout', str(timeout))
self.config.save()
self.print_status(f"Timeout set to {timeout}s", "success")
else:
self.print_status("Timeout must be between 10 and 600 seconds", "error")
except ValueError:
self.print_status("Invalid number", "error")
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
elif choice == "4":
print()
self.print_status("Testing RouterSploit installation...", "info")
rsf.reset_cache()
if rsf.is_available:
count = rsf.get_module_count()
self.print_status(f"RouterSploit is available! ({count} modules indexed)", "success")
else:
self.print_status("RouterSploit not found at configured path", "error")
print(f" {Colors.DIM}Path: {settings['install_path']}{Colors.RESET}")
print(f" {Colors.DIM}Make sure routersploit package is at this location{Colors.RESET}")
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
except (EOFError, KeyboardInterrupt):
break
def show_msf_settings(self):
"""Display and configure Metasploit settings."""
from .msf import get_msf_manager, MSFError
msf = get_msf_manager()
settings = msf.get_settings()
while True:
clear_screen()
self._show_banner()
print(f"{Colors.BOLD}{Colors.WHITE} Metasploit Configuration{Colors.RESET}")
print(f"{Colors.DIM} {'' * 50}{Colors.RESET}")
print()
# Current settings
print(f" {Colors.CYAN}Host:{Colors.RESET} {settings['host']}")
print(f" {Colors.CYAN}Port:{Colors.RESET} {settings['port']}")
print(f" {Colors.CYAN}Username:{Colors.RESET} {settings['username']}")
print(f" {Colors.CYAN}Password:{Colors.RESET} {'*' * len(settings['password']) if settings['password'] else '(not set)'}")
print(f" {Colors.CYAN}SSL:{Colors.RESET} {settings['ssl']}")
print(f" {Colors.CYAN}Autoconnect:{Colors.RESET} {Colors.GREEN if settings.get('autoconnect', True) else Colors.YELLOW}{'Enabled' if settings.get('autoconnect', True) else 'Disabled'}{Colors.RESET}")
print()
# Server status
is_running, pid = msf.detect_server()
if is_running:
print(f" {Colors.GREEN}Server: Running{Colors.RESET}", end="")
if pid:
print(f" (PID: {pid})")
else:
print()
else:
print(f" {Colors.YELLOW}Server: Not Running{Colors.RESET}")
# Connection status
if msf.is_connected:
print(f" {Colors.GREEN}Client: Connected{Colors.RESET}")
try:
version = msf.rpc.get_version()
print(f" {Colors.DIM}Version: {version.get('version', 'Unknown')}{Colors.RESET}")
except:
pass
else:
print(f" {Colors.YELLOW}Client: Disconnected{Colors.RESET}")
print()
print(f" {Colors.CYAN}[1]{Colors.RESET} Configure Connection")
print(f" {Colors.CYAN}[2]{Colors.RESET} Test Connection")
print(f" {Colors.CYAN}[3]{Colors.RESET} Disconnect")
print()
print(f" {Colors.CYAN}[4]{Colors.RESET} Start Server")
print(f" {Colors.CYAN}[5]{Colors.RESET} Stop Server")
print(f" {Colors.CYAN}[6]{Colors.RESET} Toggle Autoconnect")
use_sudo = self.config.get_bool('msf', 'use_sudo', fallback=True)
print(f" {Colors.CYAN}[7]{Colors.RESET} Toggle Sudo {Colors.DIM}(currently: {'on' if use_sudo else 'off'}){Colors.RESET}")
print()
print(f" {Colors.DIM}[0]{Colors.RESET} Back")
print()
try:
choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip()
if choice == "0" or not choice:
break
elif choice == "1":
self.configure_msf()
settings = msf.get_settings()
elif choice == "2":
print()
# Refresh settings before attempting connection
settings = msf.get_settings()
self.print_status("Testing connection...", "info")
try:
if not settings['password']:
password = input(f" {Colors.WHITE}Enter MSF RPC password: {Colors.RESET}").strip()
else:
password = settings['password']
msf.connect(password)
self.print_status("Connected successfully!", "success")
version = msf.rpc.get_version()
print(f" {Colors.DIM}Metasploit {version.get('version', 'Unknown')}{Colors.RESET}")
except MSFError as e:
self.print_status(f"Connection failed: {e}", "error")
if "Authentication failed" in str(e):
print(f" {Colors.DIM}The server may be running with different credentials.{Colors.RESET}")
print(f" {Colors.DIM}Try: [5] Stop Server, then [4] Start Server{Colors.RESET}")
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
elif choice == "3":
msf.disconnect()
self.print_status("Disconnected", "info")
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
elif choice == "4":
# Start server
print()
if is_running:
self.print_status("Server is already running", "warning")
else:
if not settings['password']:
password = input(f" {Colors.WHITE}Enter MSF RPC password: {Colors.RESET}").strip()
if password:
msf.save_settings(
settings['host'], settings['port'],
settings['username'], password, settings['ssl']
)
settings = msf.get_settings()
else:
password = settings['password']
if password:
self.print_status("Starting server...", "info")
if msf.start_server(
settings['username'], password,
settings['host'], settings['port'], settings['ssl']
):
self.print_status("Server started successfully", "success")
else:
self.print_status("Failed to start server", "error")
else:
self.print_status("Password required to start server", "error")
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
elif choice == "5":
# Stop server
print()
if not is_running:
self.print_status("Server is not running", "warning")
else:
self.print_status("Stopping server...", "info")
if msf.kill_server():
self.print_status("Server stopped", "success")
else:
self.print_status("Failed to stop server", "error")
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
elif choice == "6":
# Toggle autoconnect
new_value = not settings.get('autoconnect', True)
msf.set_autoconnect(new_value)
settings = msf.get_settings()
self.print_status(f"Autoconnect {'enabled' if new_value else 'disabled'}", "success")
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
elif choice == "7":
# Toggle sudo for MSF server start
use_sudo = self.config.get_bool('msf', 'use_sudo', fallback=True)
new_value = not use_sudo
self.config.set('msf', 'use_sudo', str(new_value).lower())
self.config.save()
if new_value:
self.print_status("Sudo enabled — msfrpcd runs as root (full module support)", "success")
else:
self.print_status("Sudo disabled — msfrpcd runs as current user (some modules limited)", "warning")
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
except (EOFError, KeyboardInterrupt):
break
def configure_msf(self):
"""Configure Metasploit connection settings."""
from .msf import get_msf_manager
msf = get_msf_manager()
settings = msf.get_settings()
clear_screen()
self._show_banner()
print(f"{Colors.BOLD}{Colors.WHITE} Configure Metasploit RPC{Colors.RESET}")
print(f"{Colors.DIM} {'' * 50}{Colors.RESET}")
print(f"\n {Colors.DIM}Press Enter to keep current value{Colors.RESET}\n")
try:
host = input(f" Host [{settings['host']}]: ").strip() or settings['host']
port_str = input(f" Port [{settings['port']}]: ").strip()
port = int(port_str) if port_str else settings['port']
username = input(f" Username [{settings['username']}]: ").strip() or settings['username']
password = input(f" Password: ").strip() or settings['password']
ssl_str = input(f" Use SSL (y/n) [{'y' if settings['ssl'] else 'n'}]: ").strip().lower()
use_ssl = ssl_str == 'y' if ssl_str else settings['ssl']
msf.save_settings(host, port, username, password, use_ssl)
self.print_status("Settings saved", "success")
except (ValueError, EOFError, KeyboardInterrupt):
print()
self.print_status("Configuration cancelled", "warning")
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
def show_all_settings(self):
"""Display all current settings."""
clear_screen()
self._show_banner()
print(f"{Colors.BOLD}{Colors.WHITE} All Settings{Colors.RESET}")
print(f"{Colors.DIM} {'' * 50}{Colors.RESET}")
print()
# LLM Settings
print(f" {Colors.CYAN}LLM Configuration:{Colors.RESET}")
settings = self.config.get_llama_settings()
for key, value in settings.items():
print(f" {key:20}: {value}")
# MSF Settings
print()
print(f" {Colors.CYAN}Metasploit Configuration:{Colors.RESET}")
from .msf import get_msf_manager
msf_settings = get_msf_manager().get_settings()
for key, value in msf_settings.items():
if key == 'password':
value = '*' * len(value) if value else '(not set)'
print(f" {key:20}: {value}")
# OSINT Settings
print()
print(f" {Colors.CYAN}OSINT Configuration:{Colors.RESET}")
osint_settings = self.config.get_osint_settings()
for key, value in osint_settings.items():
print(f" {key:20}: {value}")
print()
print(f" {Colors.CYAN}Config file:{Colors.RESET} {self.config.config_path}")
print()
input(f"{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
def show_database_management(self):
"""Display database management menu."""
while True:
clear_screen()
self._show_banner()
print(f"{Colors.BOLD}{Colors.WHITE} Database Management{Colors.RESET}")
print(f"{Colors.DIM} {'' * 50}{Colors.RESET}")
print()
# Get database info
from .cve import get_cve_db
cve_db = get_cve_db()
cve_stats = cve_db.get_db_stats()
# Custom APIs
custom_apis = self._load_custom_apis()
# System audit
system_inf = self._app_dir / "system.inf"
# Adult scanner custom sites
adult_sites = self._app_dir / "custom_adultsites.json"
# Calculate total storage
total_size = 0
print(f" {Colors.CYAN}Databases:{Colors.RESET}")
print()
# CVE Database
cve_size = cve_stats['db_size_mb']
total_size += cve_size
status = f"{Colors.GREEN}Active{Colors.RESET}" if cve_stats['total_cves'] > 0 else f"{Colors.YELLOW}Empty{Colors.RESET}"
print(f" {Colors.BLUE}[1]{Colors.RESET} CVE Database")
print(f" {Colors.DIM}Records: {cve_stats['total_cves']:,} | Size: {cve_size} MB | {status}{Colors.RESET}")
# System Audit Results
if system_inf.exists():
sys_size = round(system_inf.stat().st_size / 1024 / 1024, 2)
total_size += sys_size
print(f" {Colors.BLUE}[2]{Colors.RESET} System Audit Data")
print(f" {Colors.DIM}Size: {sys_size} MB | {Colors.GREEN}Active{Colors.RESET}")
else:
print(f" {Colors.BLUE}[2]{Colors.RESET} System Audit Data")
print(f" {Colors.DIM}No data | {Colors.YELLOW}Empty{Colors.RESET}")
# Custom Sites Database
if adult_sites.exists():
import json
try:
with open(adult_sites) as f:
sites_data = json.load(f)
sites_count = len(sites_data.get('sites', []))
sites_size = round(adult_sites.stat().st_size / 1024, 2)
print(f" {Colors.BLUE}[3]{Colors.RESET} Custom Sites Database")
print(f" {Colors.DIM}Sites: {sites_count} | Size: {sites_size} KB{Colors.RESET}")
except:
print(f" {Colors.BLUE}[3]{Colors.RESET} Custom Sites Database")
print(f" {Colors.DIM}Error reading | {Colors.RED}Corrupt{Colors.RESET}")
else:
print(f" {Colors.BLUE}[3]{Colors.RESET} Custom Sites Database")
print(f" {Colors.DIM}No custom sites | {Colors.YELLOW}Empty{Colors.RESET}")
# Custom APIs
print(f" {Colors.BLUE}[4]{Colors.RESET} Custom APIs")
print(f" {Colors.DIM}APIs: {len(custom_apis)}{Colors.RESET}")
print()
print(f" {Colors.DIM}Total Storage: ~{round(total_size, 2)} MB{Colors.RESET}")
print()
print(f" {Colors.GREEN}[S]{Colors.RESET} Sync All Databases")
print(f" {Colors.YELLOW}[B]{Colors.RESET} Backup All Databases")
print(f" {Colors.RED}[C]{Colors.RESET} Clear All Databases")
print()
print(f" {Colors.DIM}[0]{Colors.RESET} Back")
print()
try:
choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip().upper()
if choice == "0" or not choice:
break
elif choice == "1":
self.show_cve_settings()
elif choice == "2":
self._manage_system_audit_data()
elif choice == "3":
self._manage_custom_sites()
elif choice == "4":
self.show_custom_apis()
elif choice == "S":
self._sync_all_databases()
elif choice == "B":
self._backup_all_databases()
elif choice == "C":
self._clear_all_databases()
except (EOFError, KeyboardInterrupt):
break
def _manage_system_audit_data(self):
"""Manage system audit data."""
system_inf = self._app_dir / "system.inf"
clear_screen()
self._show_banner()
print(f"{Colors.BOLD}{Colors.WHITE} System Audit Data{Colors.RESET}")
print(f"{Colors.DIM} {'' * 50}{Colors.RESET}")
print()
if system_inf.exists():
import json
try:
with open(system_inf) as f:
data = json.load(f)
print(f" {Colors.CYAN}Audit Date:{Colors.RESET} {data.get('audit_date', 'Unknown')[:19]}")
print(f" {Colors.CYAN}Security Score:{Colors.RESET} {data.get('security_score', 'N/A')}/100")
print(f" {Colors.CYAN}Issues Found:{Colors.RESET} {len(data.get('issues', []))}")
print(f" {Colors.CYAN}System:{Colors.RESET} {data.get('system_info', {}).get('os_name', 'Unknown')}")
print()
print(f" {Colors.RED}[D]{Colors.RESET} Delete Audit Data")
print(f" {Colors.CYAN}[V]{Colors.RESET} View Details")
except Exception as e:
print(f" {Colors.RED}Error reading data: {e}{Colors.RESET}")
else:
print(f" {Colors.YELLOW}No audit data found.{Colors.RESET}")
print(f" {Colors.DIM}Run a system audit from My System module.{Colors.RESET}")
print()
print(f" {Colors.DIM}[0]{Colors.RESET} Back")
print()
try:
choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip().upper()
if choice == "D" and system_inf.exists():
confirm = input(f" {Colors.RED}Delete audit data? (y/n): {Colors.RESET}").strip().lower()
if confirm == 'y':
system_inf.unlink()
self.print_status("Audit data deleted", "success")
elif choice == "V" and system_inf.exists():
import json
with open(system_inf) as f:
data = json.load(f)
print(f"\n{Colors.DIM}{json.dumps(data, indent=2)[:2000]}...{Colors.RESET}")
except (EOFError, KeyboardInterrupt):
pass
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
def _manage_custom_sites(self):
"""Manage custom adult scanner sites."""
sites_path = self._app_dir / "custom_adultsites.json"
clear_screen()
self._show_banner()
print(f"{Colors.BOLD}{Colors.WHITE} Custom Sites Database{Colors.RESET}")
print(f"{Colors.DIM} {'' * 50}{Colors.RESET}")
print()
if sites_path.exists():
import json
try:
with open(sites_path) as f:
data = json.load(f)
sites = data.get('sites', [])
print(f" {Colors.CYAN}Total Sites:{Colors.RESET} {len(sites)}")
print()
if sites:
print(f" {Colors.DIM}Sites:{Colors.RESET}")
for site in sites[:10]:
name = site[0] if isinstance(site, list) else site.get('name', 'Unknown')
print(f" - {name}")
if len(sites) > 10:
print(f" {Colors.DIM}... and {len(sites) - 10} more{Colors.RESET}")
print()
print(f" {Colors.RED}[D]{Colors.RESET} Delete All Custom Sites")
print(f" {Colors.CYAN}[E]{Colors.RESET} Export Sites List")
except Exception as e:
print(f" {Colors.RED}Error reading data: {e}{Colors.RESET}")
else:
print(f" {Colors.YELLOW}No custom sites configured.{Colors.RESET}")
print(f" {Colors.DIM}Add sites from the Adult Scanner module.{Colors.RESET}")
print()
print(f" {Colors.DIM}[0]{Colors.RESET} Back")
print()
try:
choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip().upper()
if choice == "D" and sites_path.exists():
confirm = input(f" {Colors.RED}Delete all custom sites? (y/n): {Colors.RESET}").strip().lower()
if confirm == 'y':
sites_path.unlink()
self.print_status("Custom sites deleted", "success")
elif choice == "E" and sites_path.exists():
export_path = self._app_dir / "custom_sites_export.txt"
import json
with open(sites_path) as f:
data = json.load(f)
with open(export_path, 'w') as f:
for site in data.get('sites', []):
name = site[0] if isinstance(site, list) else site.get('name', '')
url = site[1] if isinstance(site, list) else site.get('url', '')
f.write(f"{name}: {url}\n")
self.print_status(f"Exported to {export_path}", "success")
except (EOFError, KeyboardInterrupt):
pass
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
def _sync_all_databases(self):
"""Sync all databases."""
print()
print(f"{Colors.CYAN}[*] Syncing all databases...{Colors.RESET}")
print()
# Sync CVE database
print(f"{Colors.CYAN}[*] Syncing CVE database (recent)...{Colors.RESET}")
from .cve import get_cve_db
cve_db = get_cve_db()
stats = cve_db.sync_database(days_back=30, verbose=True)
print(f"{Colors.GREEN}[+] CVE sync complete: {stats.get('cves_processed', 0):,} CVEs{Colors.RESET}")
print()
self.print_status("All databases synced", "success")
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
def _backup_all_databases(self):
"""Backup all databases."""
import shutil
from datetime import datetime
backup_dir = self._app_dir / "backups" / datetime.now().strftime("%Y%m%d_%H%M%S")
backup_dir.mkdir(parents=True, exist_ok=True)
print()
print(f"{Colors.CYAN}[*] Creating backup at {backup_dir}...{Colors.RESET}")
print()
files_to_backup = [
("data/cve/cve.db", "CVE Database"),
("system.inf", "System Audit Data"),
("custom_adultsites.json", "Custom Sites"),
("custom_apis.json", "Custom APIs"),
("autarch_settings.conf", "Settings"),
]
backed_up = 0
for filepath, name in files_to_backup:
src = self._app_dir / filepath
if src.exists():
dst = backup_dir / src.name
try:
shutil.copy2(src, dst)
print(f" {Colors.GREEN}[+]{Colors.RESET} {name}")
backed_up += 1
except Exception as e:
print(f" {Colors.RED}[X]{Colors.RESET} {name}: {e}")
else:
print(f" {Colors.DIM}[-]{Colors.RESET} {name} (not found)")
print()
self.print_status(f"Backed up {backed_up} files to {backup_dir}", "success")
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
def _clear_all_databases(self):
"""Clear all databases."""
print()
print(f"{Colors.RED}[!] WARNING: This will delete ALL data:{Colors.RESET}")
print(f" - CVE Database")
print(f" - System Audit Data")
print(f" - Custom Sites")
print(f" - Custom APIs")
print()
confirm = input(f"{Colors.WHITE}Type 'DELETE ALL' to confirm: {Colors.RESET}").strip()
if confirm == 'DELETE ALL':
import os
files_to_delete = [
"data/cve/cve.db",
"system.inf",
"custom_adultsites.json",
"custom_apis.json",
]
for filepath in files_to_delete:
path = self._app_dir / filepath
if path.exists():
try:
os.remove(path)
print(f" {Colors.GREEN}[+]{Colors.RESET} Deleted {path.name}")
except Exception as e:
print(f" {Colors.RED}[X]{Colors.RESET} Failed to delete {path.name}: {e}")
self.print_status("All databases cleared", "success")
else:
self.print_status("Operation cancelled", "info")
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
def show_cve_settings(self):
"""Display CVE database settings."""
from .cve import get_cve_db
while True:
clear_screen()
self._show_banner()
print(f"{Colors.BOLD}{Colors.WHITE} CVE Database Settings{Colors.RESET}")
print(f"{Colors.DIM} {'' * 50}{Colors.RESET}")
print()
cve_db = get_cve_db()
stats = cve_db.get_db_stats()
sys_info = cve_db.get_system_info()
# Database info
print(f" {Colors.CYAN}Database Path:{Colors.RESET} {stats['db_path']}")
print(f" {Colors.CYAN}Database Size:{Colors.RESET} {stats['db_size_mb']} MB")
print(f" {Colors.CYAN}Total CVEs:{Colors.RESET} {stats['total_cves']:,}")
print(f" {Colors.CYAN}Last Sync:{Colors.RESET} {stats.get('last_sync', 'Never')[:19] if stats.get('last_sync') else 'Never'}")
print()
# System detection
print(f" {Colors.CYAN}Detected OS:{Colors.RESET} {sys_info.get('os_name', 'Unknown')}")
print(f" {Colors.CYAN}CPE Prefix:{Colors.RESET} {sys_info.get('cpe_prefix', 'Unknown')}")
print()
# NVD API Key status
api_key = self.config.get('nvd', 'api_key', fallback='')
if api_key:
print(f" {Colors.GREEN}NVD API Key:{Colors.RESET} Configured")
else:
print(f" {Colors.YELLOW}NVD API Key:{Colors.RESET} Not set (slower sync)")
print()
print(f" {Colors.GREEN}[1]{Colors.RESET} Sync Database (Recent - 120 days)")
print(f" {Colors.YELLOW}[2]{Colors.RESET} Sync Database (Full - all CVEs)")
print(f" {Colors.CYAN}[3]{Colors.RESET} Set NVD API Key")
print(f" {Colors.RED}[4]{Colors.RESET} Clear Database")
print()
print(f" {Colors.DIM}[0]{Colors.RESET} Back")
print()
try:
choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip()
if choice == "0" or not choice:
break
elif choice == "1":
self._sync_cve_database(days=120)
elif choice == "2":
self._sync_cve_database(full=True)
elif choice == "3":
self._set_nvd_api_key()
elif choice == "4":
self._clear_cve_database()
except (EOFError, KeyboardInterrupt):
break
def _sync_cve_database(self, days: int = 120, full: bool = False):
"""Sync CVE database."""
from .cve import get_cve_db
print()
if full:
print(f"{Colors.YELLOW}[!] Full sync will download 200,000+ CVEs{Colors.RESET}")
print(f"{Colors.YELLOW}[!] This may take 2-6 hours{Colors.RESET}")
confirm = input(f"\n{Colors.WHITE}Continue? (y/n): {Colors.RESET}").strip().lower()
if confirm != 'y':
return
else:
print(f"{Colors.CYAN}[*] Syncing CVEs from last {days} days...{Colors.RESET}")
print()
cve_db = get_cve_db()
stats = cve_db.sync_database(days_back=days, full_sync=full, verbose=True)
print(f"\n{Colors.GREEN}[+] Sync complete: {stats.get('cves_processed', 0):,} CVEs{Colors.RESET}")
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
def _set_nvd_api_key(self):
"""Set NVD API key."""
clear_screen()
self._show_banner()
print(f"{Colors.BOLD}{Colors.WHITE} NVD API Key Configuration{Colors.RESET}")
print(f"{Colors.DIM} {'' * 50}{Colors.RESET}")
print()
print(f" {Colors.DIM}Get your free API key at:{Colors.RESET}")
print(f" {Colors.CYAN}https://nvd.nist.gov/developers/request-an-api-key{Colors.RESET}")
print()
print(f" {Colors.DIM}Benefits: 50 requests/30s vs 5 requests/30s{Colors.RESET}")
print()
current = self.config.get('nvd', 'api_key', fallback='')
if current:
print(f" {Colors.GREEN}Current: {current[:8]}...{Colors.RESET}")
print()
try:
api_key = input(f"{Colors.WHITE} Enter API key (or Enter to skip): {Colors.RESET}").strip()
if api_key:
self.config.set('nvd', 'api_key', api_key)
self.config.save()
self.print_status("API key saved", "success")
else:
self.print_status("No changes made", "info")
except (EOFError, KeyboardInterrupt):
print()
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
def _clear_cve_database(self):
"""Clear CVE database."""
from .cve import get_cve_db
import os
print()
print(f"{Colors.RED}[!] This will delete all CVE data{Colors.RESET}")
confirm = input(f"{Colors.WHITE}Type 'DELETE' to confirm: {Colors.RESET}").strip()
if confirm == 'DELETE':
cve_db = get_cve_db()
db_path = cve_db.db_path
cve_db.close()
try:
os.remove(db_path)
self.print_status("Database cleared", "success")
except Exception as e:
self.print_status(f"Failed to clear: {e}", "error")
else:
self.print_status("Operation cancelled", "info")
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
def show_custom_apis(self):
"""Display custom APIs management menu."""
while True:
clear_screen()
self._show_banner()
print(f"{Colors.BOLD}{Colors.WHITE} Custom APIs{Colors.RESET}")
print(f"{Colors.DIM} {'' * 50}{Colors.RESET}")
print()
# Load existing APIs
custom_apis = self._load_custom_apis()
if custom_apis:
print(f" {Colors.CYAN}Configured APIs:{Colors.RESET}")
for i, (name, api) in enumerate(custom_apis.items(), 1):
status = f"{Colors.GREEN}Active{Colors.RESET}" if api.get('enabled', True) else f"{Colors.RED}Disabled{Colors.RESET}"
print(f" [{i}] {name} - {status}")
print(f" {Colors.DIM}{api.get('url', 'No URL')[:50]}...{Colors.RESET}")
print()
else:
print(f" {Colors.DIM}No custom APIs configured{Colors.RESET}")
print()
print(f" {Colors.GREEN}[A]{Colors.RESET} Add API")
if custom_apis:
print(f" {Colors.CYAN}[E]{Colors.RESET} Edit API")
print(f" {Colors.RED}[D]{Colors.RESET} Delete API")
print(f" {Colors.YELLOW}[T]{Colors.RESET} Toggle API")
print()
print(f" {Colors.DIM}[0]{Colors.RESET} Back")
print()
try:
choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip().upper()
if choice == "0" or not choice:
break
elif choice == "A":
self._add_custom_api()
elif choice == "E" and custom_apis:
self._edit_custom_api(custom_apis)
elif choice == "D" and custom_apis:
self._delete_custom_api(custom_apis)
elif choice == "T" and custom_apis:
self._toggle_custom_api(custom_apis)
except (EOFError, KeyboardInterrupt):
break
def _load_custom_apis(self) -> dict:
"""Load custom APIs from config."""
import json
apis_path = self._app_dir / "custom_apis.json"
if apis_path.exists():
try:
with open(apis_path, 'r') as f:
return json.load(f)
except:
pass
return {}
def _save_custom_apis(self, apis: dict):
"""Save custom APIs to config."""
import json
apis_path = self._app_dir / "custom_apis.json"
with open(apis_path, 'w') as f:
json.dump(apis, f, indent=2)
def _add_custom_api(self):
"""Add a new custom API."""
clear_screen()
self._show_banner()
print(f"{Colors.BOLD}{Colors.WHITE} Add Custom API{Colors.RESET}")
print(f"{Colors.DIM} {'' * 50}{Colors.RESET}")
print()
try:
name = input(f" {Colors.WHITE}API Name: {Colors.RESET}").strip()
if not name:
return
url = input(f" {Colors.WHITE}Base URL: {Colors.RESET}").strip()
api_key = input(f" {Colors.WHITE}API Key (optional): {Colors.RESET}").strip()
description = input(f" {Colors.WHITE}Description: {Colors.RESET}").strip()
# API type
print(f"\n {Colors.DIM}API Types:{Colors.RESET}")
print(f" [1] REST API")
print(f" [2] GraphQL")
print(f" [3] SOAP")
print(f" [4] Other")
api_type = input(f" {Colors.WHITE}Type [1]: {Colors.RESET}").strip() or "1"
type_map = {"1": "REST", "2": "GraphQL", "3": "SOAP", "4": "Other"}
api_type = type_map.get(api_type, "REST")
apis = self._load_custom_apis()
apis[name] = {
'url': url,
'api_key': api_key,
'description': description,
'type': api_type,
'enabled': True,
}
self._save_custom_apis(apis)
self.print_status(f"API '{name}' added", "success")
except (EOFError, KeyboardInterrupt):
print()
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
def _edit_custom_api(self, apis: dict):
"""Edit an existing custom API."""
api_list = list(apis.keys())
print()
num = input(f" {Colors.WHITE}Enter API number to edit: {Colors.RESET}").strip()
try:
idx = int(num) - 1
if 0 <= idx < len(api_list):
name = api_list[idx]
api = apis[name]
clear_screen()
self._show_banner()
print(f"{Colors.BOLD}{Colors.WHITE} Edit API: {name}{Colors.RESET}")
print(f"{Colors.DIM} Press Enter to keep current value{Colors.RESET}")
print()
new_url = input(f" URL [{api.get('url', '')}]: ").strip() or api.get('url', '')
new_key = input(f" API Key: ").strip() or api.get('api_key', '')
new_desc = input(f" Description [{api.get('description', '')}]: ").strip() or api.get('description', '')
api['url'] = new_url
api['api_key'] = new_key
api['description'] = new_desc
self._save_custom_apis(apis)
self.print_status("API updated", "success")
except (ValueError, IndexError):
self.print_status("Invalid selection", "error")
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
def _delete_custom_api(self, apis: dict):
"""Delete a custom API."""
api_list = list(apis.keys())
print()
num = input(f" {Colors.WHITE}Enter API number to delete: {Colors.RESET}").strip()
try:
idx = int(num) - 1
if 0 <= idx < len(api_list):
name = api_list[idx]
confirm = input(f" {Colors.RED}Delete '{name}'? (y/n): {Colors.RESET}").strip().lower()
if confirm == 'y':
del apis[name]
self._save_custom_apis(apis)
self.print_status(f"API '{name}' deleted", "success")
else:
self.print_status("Cancelled", "info")
except (ValueError, IndexError):
self.print_status("Invalid selection", "error")
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
def _toggle_custom_api(self, apis: dict):
"""Toggle a custom API enabled/disabled."""
api_list = list(apis.keys())
print()
num = input(f" {Colors.WHITE}Enter API number to toggle: {Colors.RESET}").strip()
try:
idx = int(num) - 1
if 0 <= idx < len(api_list):
name = api_list[idx]
apis[name]['enabled'] = not apis[name].get('enabled', True)
self._save_custom_apis(apis)
status = "enabled" if apis[name]['enabled'] else "disabled"
self.print_status(f"API '{name}' {status}", "success")
except (ValueError, IndexError):
self.print_status("Invalid selection", "error")
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
def show_autarch_api(self):
"""Display AUTARCH API settings (placeholder for future implementation)."""
while True:
clear_screen()
self._show_banner()
print(f"{Colors.BOLD}{Colors.WHITE} AUTARCH API{Colors.RESET}")
print(f"{Colors.DIM} {'' * 50}{Colors.RESET}")
print()
# Check if API is enabled
api_enabled = self.config.get_bool('api', 'enabled', fallback=False)
api_port = self.config.get_int('api', 'port', fallback=8080)
api_key = self.config.get('api', 'key', fallback='')
if api_enabled:
print(f" {Colors.GREEN}Status:{Colors.RESET} Enabled")
else:
print(f" {Colors.YELLOW}Status:{Colors.RESET} Disabled")
print(f" {Colors.CYAN}Port:{Colors.RESET} {api_port}")
print(f" {Colors.CYAN}API Key:{Colors.RESET} {'Configured' if api_key else 'Not set'}")
print()
print(f" {Colors.DIM}The AUTARCH API allows external tools to{Colors.RESET}")
print(f" {Colors.DIM}interact with the framework programmatically.{Colors.RESET}")
print()
print(f" {Colors.YELLOW}[!] API functionality coming in future version{Colors.RESET}")
print()
print(f" {Colors.CYAN}[1]{Colors.RESET} Configure API Settings")
print(f" {Colors.CYAN}[2]{Colors.RESET} Generate API Key")
print(f" {Colors.CYAN}[3]{Colors.RESET} View API Documentation")
print()
print(f" {Colors.DIM}[0]{Colors.RESET} Back")
print()
try:
choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip()
if choice == "0" or not choice:
break
elif choice == "1":
self._configure_autarch_api()
elif choice == "2":
self._generate_api_key()
elif choice == "3":
self._show_api_docs()
except (EOFError, KeyboardInterrupt):
break
def _configure_autarch_api(self):
"""Configure AUTARCH API settings."""
clear_screen()
self._show_banner()
print(f"{Colors.BOLD}{Colors.WHITE} Configure AUTARCH API{Colors.RESET}")
print(f"{Colors.DIM} {'' * 50}{Colors.RESET}")
print()
try:
enabled = input(f" Enable API? (y/n) [{self.config.get_bool('api', 'enabled', fallback=False) and 'y' or 'n'}]: ").strip().lower()
if enabled:
self.config.set('api', 'enabled', 'true' if enabled == 'y' else 'false')
port = input(f" Port [{self.config.get_int('api', 'port', fallback=8080)}]: ").strip()
if port.isdigit():
self.config.set('api', 'port', port)
self.config.save()
self.print_status("API settings saved", "success")
except (EOFError, KeyboardInterrupt):
print()
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
def _generate_api_key(self):
"""Generate a new API key."""
import secrets
import string
print()
api_key = ''.join(secrets.choice(string.ascii_letters + string.digits) for _ in range(32))
self.config.set('api', 'key', api_key)
self.config.save()
print(f" {Colors.GREEN}New API Key:{Colors.RESET} {api_key}")
print(f"\n {Colors.YELLOW}Store this key securely - it won't be shown again!{Colors.RESET}")
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
def _show_api_docs(self):
"""Show API documentation."""
clear_screen()
self._show_banner()
print(f"{Colors.BOLD}{Colors.WHITE} AUTARCH API Documentation{Colors.RESET}")
print(f"{Colors.DIM} {'' * 50}{Colors.RESET}")
print()
print(f" {Colors.CYAN}Endpoints (coming soon):{Colors.RESET}")
print()
print(f" {Colors.DIM}GET /api/v1/status{Colors.RESET}")
print(f" Get framework status")
print()
print(f" {Colors.DIM}GET /api/v1/modules{Colors.RESET}")
print(f" List available modules")
print()
print(f" {Colors.DIM}POST /api/v1/scan{Colors.RESET}")
print(f" Run a security scan")
print()
print(f" {Colors.DIM}GET /api/v1/cve/search?q=<query>{Colors.RESET}")
print(f" Search CVE database")
print()
print(f" {Colors.DIM}POST /api/v1/agent/task{Colors.RESET}")
print(f" Submit task to AI agent")
print()
print(f" {Colors.YELLOW}Full documentation will be available when{Colors.RESET}")
print(f" {Colors.YELLOW}the API is implemented in a future version.{Colors.RESET}")
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
def run_agent_hal(self):
"""Run the Agent Hal module."""
try:
from modules.agent_hal import run as run_hal
run_hal()
except ImportError as e:
self.print_status(f"Failed to load Agent Hal: {e}", "error")
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
except Exception as e:
self.print_status(f"Error running Agent Hal: {e}", "error")
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
def run_setup(self):
"""Run the setup wizard."""
from modules.setup import run as run_setup
run_setup()
def show_web_service(self):
"""Web service management menu."""
import subprocess
SERVICE_NAME = "autarch-web"
SERVICE_FILE = self._app_dir / "scripts" / "autarch-web.service"
SYSTEMD_PATH = Path("/etc/systemd/system/autarch-web.service")
while True:
clear_screen()
self._show_banner()
print(f"{Colors.BOLD}{Colors.WHITE} Web Dashboard Service{Colors.RESET}")
print(f"{Colors.DIM} {'' * 50}{Colors.RESET}")
print()
# Check status
installed = SYSTEMD_PATH.exists()
if installed:
result = subprocess.run(
['systemctl', 'is-active', SERVICE_NAME],
capture_output=True, text=True
)
is_active = result.stdout.strip()
result2 = subprocess.run(
['systemctl', 'is-enabled', SERVICE_NAME],
capture_output=True, text=True
)
is_enabled = result2.stdout.strip()
color = Colors.GREEN if is_active == 'active' else Colors.RED
print(f" Service: {color}{is_active}{Colors.RESET}")
print(f" Enabled: {is_enabled}")
else:
print(f" Service: {Colors.YELLOW}Not installed{Colors.RESET}")
host = self.config.get('web', 'host', fallback='0.0.0.0')
port = self.config.get('web', 'port', fallback='8181')
print(f" Address: http://{host}:{port}")
print()
if not installed:
print(f" {Colors.GREEN}[1]{Colors.RESET} Install Service")
else:
print(f" {Colors.GREEN}[1]{Colors.RESET} Start Service")
print(f" {Colors.RED}[2]{Colors.RESET} Stop Service")
print(f" {Colors.YELLOW}[3]{Colors.RESET} Restart Service")
print(f" {Colors.CYAN}[4]{Colors.RESET} Enable (auto-start on boot)")
print(f" {Colors.CYAN}[5]{Colors.RESET} Disable (no auto-start)")
print(f" {Colors.DIM}[6]{Colors.RESET} View Logs")
print(f"\n {Colors.CYAN}[7]{Colors.RESET} Start Web UI (foreground, no service)")
print(f" {Colors.CYAN}[8]{Colors.RESET} Configure Host/Port")
print()
print(f" {Colors.DIM}[0]{Colors.RESET} Back")
print()
try:
choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip()
if choice == "0" or not choice:
break
elif choice == "1" and not installed:
try:
subprocess.run(['sudo', 'cp', str(SERVICE_FILE), str(SYSTEMD_PATH)], check=True)
subprocess.run(['sudo', 'systemctl', 'daemon-reload'], check=True)
self.print_status("Service installed", "success")
except Exception as e:
self.print_status(f"Install failed: {e}", "error")
input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}")
elif choice == "1" and installed:
subprocess.run(['sudo', 'systemctl', 'start', SERVICE_NAME])
self.print_status("Service started", "success")
input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}")
elif choice == "2":
subprocess.run(['sudo', 'systemctl', 'stop', SERVICE_NAME])
self.print_status("Service stopped", "success")
input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}")
elif choice == "3":
subprocess.run(['sudo', 'systemctl', 'restart', SERVICE_NAME])
self.print_status("Service restarted", "success")
input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}")
elif choice == "4":
subprocess.run(['sudo', 'systemctl', 'enable', SERVICE_NAME])
self.print_status("Auto-start enabled", "success")
input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}")
elif choice == "5":
subprocess.run(['sudo', 'systemctl', 'disable', SERVICE_NAME])
self.print_status("Auto-start disabled", "success")
input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}")
elif choice == "6":
result = subprocess.run(
['journalctl', '-u', SERVICE_NAME, '-n', '30', '--no-pager'],
capture_output=True, text=True
)
print(f"\n{Colors.DIM}{result.stdout}{Colors.RESET}")
input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}")
elif choice == "7":
from web.app import create_app
app = create_app()
print(f"\n{Colors.GREEN}[+] Starting web UI on {host}:{port}{Colors.RESET}")
print(f"{Colors.DIM} Press Ctrl+C to stop{Colors.RESET}\n")
try:
app.run(host=host, port=int(port), debug=False)
except KeyboardInterrupt:
print(f"\n{Colors.CYAN}Web UI stopped.{Colors.RESET}")
input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}")
elif choice == "8":
print()
new_host = input(f" {Colors.WHITE}Bind host [{host}]: {Colors.RESET}").strip()
new_port = input(f" {Colors.WHITE}Port [{port}]: {Colors.RESET}").strip()
if new_host:
self.config.set('web', 'host', new_host)
if new_port:
try:
int(new_port)
self.config.set('web', 'port', new_port)
except ValueError:
self.print_status("Invalid port number", "error")
input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}")
continue
if new_host or new_port:
self.config.save()
self.print_status("Web settings saved", "success")
input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}")
except (EOFError, KeyboardInterrupt):
break
def sideload_companion(self):
"""Sideload Archon companion APK to connected Android device."""
from core.paths import find_tool, get_app_dir
clear_screen()
self._show_banner()
print(f"{Colors.BOLD}{Colors.WHITE} Sideload Archon Companion App{Colors.RESET}")
print(f"{Colors.DIM} {'' * 50}{Colors.RESET}")
print()
adb = find_tool('adb')
if not adb:
self.print_status("ADB not found. Install Android SDK tools.", "error")
input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}")
return
# Check for APK
app_dir = get_app_dir()
apk_locations = [
app_dir / "autarch_companion" / "app" / "build" / "outputs" / "apk" / "debug" / "app-debug.apk",
app_dir / "autarch_companion" / "app" / "build" / "outputs" / "apk" / "release" / "app-release.apk",
app_dir / "autarch_companion" / "archon.apk",
app_dir / "archon.apk",
]
apk_path = None
for loc in apk_locations:
if loc.exists():
apk_path = loc
break
if not apk_path:
self.print_status("Archon APK not found.", "warning")
print(f"\n {Colors.DIM}Expected locations:{Colors.RESET}")
for loc in apk_locations:
print(f" {Colors.DIM}{loc}{Colors.RESET}")
print(f"\n {Colors.YELLOW}Build the APK in Android Studio first, or copy it to:{Colors.RESET}")
print(f" {app_dir / 'autarch_companion' / 'archon.apk'}")
input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}")
return
print(f" APK: {Colors.GREEN}{apk_path.name}{Colors.RESET} ({apk_path.stat().st_size // 1024}KB)")
print()
# List connected devices
import subprocess
result = subprocess.run(
[str(adb), 'devices'],
capture_output=True, text=True, timeout=10
)
devices = []
for line in result.stdout.strip().split('\n')[1:]:
parts = line.split('\t')
if len(parts) == 2 and parts[1].strip() in ('device', 'recovery'):
devices.append(parts[0].strip())
if not devices:
self.print_status("No Android devices connected.", "warning")
print(f"\n {Colors.DIM}Connect via USB or enable ADB TCP/IP over WireGuard.{Colors.RESET}")
input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}")
return
print(f" Connected devices:")
for i, dev in enumerate(devices, 1):
print(f" {Colors.GREEN}[{i}]{Colors.RESET} {dev}")
print()
try:
choice = input(f" Select device (1-{len(devices)}, 0=cancel): ").strip()
if choice == "0" or not choice:
return
idx = int(choice) - 1
if 0 <= idx < len(devices):
target = devices[idx]
print(f"\n {Colors.CYAN}Installing Archon on {target}...{Colors.RESET}")
result = subprocess.run(
[str(adb), '-s', target, 'install', '-r', str(apk_path)],
capture_output=True, text=True, timeout=120
)
if result.returncode == 0:
self.print_status(f"Archon installed on {target}", "success")
else:
self.print_status(f"Install failed: {result.stderr.strip()}", "error")
else:
self.print_status("Invalid selection", "warning")
except (ValueError, subprocess.TimeoutExpired) as e:
self.print_status(f"Error: {e}", "error")
input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}")
def show_mcp_server(self):
"""Display MCP server management interface."""
while True:
clear_screen()
self._show_banner()
print(f"{Colors.BOLD}{Colors.WHITE} MCP Server{Colors.RESET}")
print(f"{Colors.DIM} Model Context Protocol — expose AUTARCH tools to AI clients{Colors.RESET}")
print(f"{Colors.DIM} {'' * 50}{Colors.RESET}")
print()
# Check status
try:
from core.mcp_server import get_server_status, get_mcp_config_snippet, get_autarch_tools
status = get_server_status()
tools = get_autarch_tools()
if status['running']:
print(f" {Colors.GREEN}SSE Server: RUNNING (PID {status['pid']}){Colors.RESET}")
else:
print(f" {Colors.YELLOW}SSE Server: STOPPED{Colors.RESET}")
print(f" {Colors.CYAN}Available tools: {len(tools)}{Colors.RESET}")
print()
# List tools
print(f" {Colors.DIM}Tools:{Colors.RESET}")
for t in tools:
print(f" {Colors.GREEN}-{Colors.RESET} {t['name']}: {Colors.DIM}{t['description'][:60]}{Colors.RESET}")
print()
except ImportError:
print(f" {Colors.RED}MCP package not installed{Colors.RESET}")
print(f" {Colors.DIM}Install with: pip install mcp{Colors.RESET}")
print()
mcp_port = self.config.get('web', 'mcp_port', fallback='8081')
print(f" {Colors.CYAN}SSE Port: {mcp_port}{Colors.RESET}")
print()
print(f" {Colors.GREEN}[1]{Colors.RESET} Start SSE Server (port {mcp_port})")
print(f" {Colors.RED}[2]{Colors.RESET} Stop SSE Server")
print(f" {Colors.CYAN}[3]{Colors.RESET} Show Claude Desktop Config")
print(f" {Colors.CYAN}[4]{Colors.RESET} Run Stdio Mode (blocks)")
print(f" {Colors.CYAN}[5]{Colors.RESET} Configure Port")
print()
print(f" {Colors.DIM}[0]{Colors.RESET} Back")
print()
try:
choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip()
if choice == "0" or not choice:
break
elif choice == "1":
from core.mcp_server import start_sse_server
port = self.config.get('web', 'mcp_port', fallback='8081')
result = start_sse_server(port=int(port))
if result['ok']:
self.print_status(f"MCP SSE server started on port {port} (PID {result['pid']})", "success")
else:
self.print_status(result['error'], "error")
input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}")
elif choice == "2":
from core.mcp_server import stop_sse_server
result = stop_sse_server()
if result['ok']:
self.print_status("MCP server stopped", "success")
else:
self.print_status(result['error'], "warning")
input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}")
elif choice == "3":
from core.mcp_server import get_mcp_config_snippet
print()
print(f" {Colors.CYAN}Add this to your Claude Desktop or Claude Code config:{Colors.RESET}")
print()
print(get_mcp_config_snippet())
print()
input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}")
elif choice == "4":
print()
self.print_status("Starting MCP stdio server (Ctrl+C to stop)...", "info")
print(f" {Colors.DIM}Connect with: claude --mcp autarch{Colors.RESET}")
print()
try:
from core.mcp_server import run_stdio
run_stdio()
except KeyboardInterrupt:
print()
self.print_status("MCP stdio server stopped", "info")
input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}")
elif choice == "5":
print()
new_port = input(f" {Colors.WHITE}MCP SSE Port [{mcp_port}]: {Colors.RESET}").strip()
if new_port:
try:
int(new_port)
self.config.set('web', 'mcp_port', new_port)
self.config.save()
self.print_status(f"MCP port set to {new_port}", "success")
except ValueError:
self.print_status("Invalid port number", "error")
input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}")
except (EOFError, KeyboardInterrupt):
break
def show_upnp_settings(self):
"""Display and configure UPnP settings."""
while True:
clear_screen()
self._show_banner()
print(f"{Colors.BOLD}{Colors.WHITE} UPnP Settings{Colors.RESET}")
print(f"{Colors.DIM} {'' * 50}{Colors.RESET}")
print()
settings = self.config.get_upnp_settings()
print(f" {Colors.CYAN}Enabled:{Colors.RESET} {Colors.GREEN if settings['enabled'] else Colors.YELLOW}{'Yes' if settings['enabled'] else 'No'}{Colors.RESET}")
print(f" {Colors.CYAN}Internal IP:{Colors.RESET} {settings['internal_ip']}")
print(f" {Colors.CYAN}Refresh:{Colors.RESET} Every {settings['refresh_hours']} hours")
print(f" {Colors.CYAN}Mappings:{Colors.RESET} {settings['mappings'] or '(none)'}")
print()
print(f" {Colors.GREEN}[1]{Colors.RESET} Refresh All Mappings Now")
print(f" {Colors.CYAN}[2]{Colors.RESET} Configure Internal IP")
print(f" {Colors.CYAN}[3]{Colors.RESET} Configure Refresh Interval")
print(f" {Colors.CYAN}[4]{Colors.RESET} Edit Port Mappings")
print(f" {Colors.CYAN}[5]{Colors.RESET} Toggle Enabled")
print()
print(f" {Colors.DIM}[0]{Colors.RESET} Back")
print()
try:
choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip()
if choice == "0" or not choice:
break
elif choice == "1":
from core.upnp import get_upnp_manager
upnp = get_upnp_manager(self.config)
if not upnp.is_available():
self.print_status("upnpc not found — install miniupnpc", "error")
else:
self.print_status("Refreshing UPnP mappings...", "info")
results = upnp.refresh_all()
for r in results:
status = "OK" if r['success'] else "FAIL"
color = Colors.GREEN if r['success'] else Colors.RED
print(f" {color}{r['port']}/{r['protocol']}: {status}{Colors.RESET}")
self.print_status(f"Refreshed {len(results)} mappings", "success")
input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}")
elif choice == "2":
new_ip = input(f" {Colors.WHITE}Internal IP [{settings['internal_ip']}]: {Colors.RESET}").strip()
if new_ip:
self.config.set('upnp', 'internal_ip', new_ip)
self.config.save()
self.print_status(f"Internal IP set to {new_ip}", "success")
input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}")
elif choice == "3":
new_hrs = input(f" {Colors.WHITE}Refresh hours [{settings['refresh_hours']}]: {Colors.RESET}").strip()
if new_hrs:
try:
int(new_hrs)
self.config.set('upnp', 'refresh_hours', new_hrs)
self.config.save()
self.print_status(f"Refresh interval set to {new_hrs} hours", "success")
except ValueError:
self.print_status("Invalid number", "error")
input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}")
elif choice == "4":
print(f"\n {Colors.DIM}Format: port:protocol,port:protocol (e.g. 443:TCP,51820:UDP,8080:TCP){Colors.RESET}")
new_maps = input(f" {Colors.WHITE}Mappings [{settings['mappings']}]: {Colors.RESET}").strip()
if new_maps:
self.config.set('upnp', 'mappings', new_maps)
self.config.save()
self.print_status("Port mappings updated", "success")
input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}")
elif choice == "5":
new_val = not settings['enabled']
self.config.set('upnp', 'enabled', str(new_val).lower())
self.config.save()
self.print_status(f"UPnP {'enabled' if new_val else 'disabled'}", "success")
input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}")
except (EOFError, KeyboardInterrupt):
break
def show_revshell_settings(self):
"""Display and configure reverse shell settings."""
while True:
clear_screen()
self._show_banner()
print(f"{Colors.BOLD}{Colors.WHITE} Reverse Shell Settings{Colors.RESET}")
print(f"{Colors.DIM} {'' * 50}{Colors.RESET}")
print()
settings = self.config.get_revshell_settings()
print(f" {Colors.CYAN}Enabled:{Colors.RESET} {Colors.GREEN if settings['enabled'] else Colors.YELLOW}{'Yes' if settings['enabled'] else 'No'}{Colors.RESET}")
print(f" {Colors.CYAN}Listen Host:{Colors.RESET} {settings['host']}")
print(f" {Colors.CYAN}Listen Port:{Colors.RESET} {settings['port']}")
print(f" {Colors.CYAN}Auto-start:{Colors.RESET} {Colors.GREEN if settings['auto_start'] else Colors.DIM}{'Yes' if settings['auto_start'] else 'No'}{Colors.RESET}")
print()
print(f" {Colors.CYAN}[1]{Colors.RESET} Configure Host")
print(f" {Colors.CYAN}[2]{Colors.RESET} Configure Port")
print(f" {Colors.CYAN}[3]{Colors.RESET} Toggle Enabled")
print(f" {Colors.CYAN}[4]{Colors.RESET} Toggle Auto-start")
print()
print(f" {Colors.DIM}[0]{Colors.RESET} Back")
print()
try:
choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip()
if choice == "0" or not choice:
break
elif choice == "1":
new_host = input(f" {Colors.WHITE}Listen host [{settings['host']}]: {Colors.RESET}").strip()
if new_host:
self.config.set('revshell', 'host', new_host)
self.config.save()
self.print_status(f"Reverse shell host set to {new_host}", "success")
input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}")
elif choice == "2":
new_port = input(f" {Colors.WHITE}Listen port [{settings['port']}]: {Colors.RESET}").strip()
if new_port:
try:
int(new_port)
self.config.set('revshell', 'port', new_port)
self.config.save()
self.print_status(f"Reverse shell port set to {new_port}", "success")
except ValueError:
self.print_status("Invalid port number", "error")
input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}")
elif choice == "3":
new_val = not settings['enabled']
self.config.set('revshell', 'enabled', str(new_val).lower())
self.config.save()
self.print_status(f"Reverse shell {'enabled' if new_val else 'disabled'}", "success")
input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}")
elif choice == "4":
new_val = not settings['auto_start']
self.config.set('revshell', 'auto_start', str(new_val).lower())
self.config.save()
self.print_status(f"Auto-start {'enabled' if new_val else 'disabled'}", "success")
input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}")
except (EOFError, KeyboardInterrupt):
break
def show_display_settings(self):
"""Display and configure display/output settings."""
while True:
clear_screen()
self._show_banner()
print(f"{Colors.BOLD}{Colors.WHITE} Display Settings{Colors.RESET}")
print(f"{Colors.DIM} {'' * 50}{Colors.RESET}")
print()
verbose = self.config.get_bool('autarch', 'verbose', fallback=False)
quiet = self.config.get_bool('autarch', 'quiet', fallback=False)
no_banner = self.config.get_bool('autarch', 'no_banner', fallback=False)
print(f" {Colors.CYAN}Verbose:{Colors.RESET} {Colors.GREEN if verbose else Colors.DIM}{'On' if verbose else 'Off'}{Colors.RESET}")
print(f" {Colors.CYAN}Quiet:{Colors.RESET} {Colors.YELLOW if quiet else Colors.DIM}{'On' if quiet else 'Off'}{Colors.RESET}")
print(f" {Colors.CYAN}No Banner:{Colors.RESET} {Colors.YELLOW if no_banner else Colors.DIM}{'On' if no_banner else 'Off'}{Colors.RESET}")
print()
print(f" {Colors.CYAN}[1]{Colors.RESET} Toggle Verbose {Colors.DIM}- Extra detail in output{Colors.RESET}")
print(f" {Colors.CYAN}[2]{Colors.RESET} Toggle Quiet {Colors.DIM}- Minimal output{Colors.RESET}")
print(f" {Colors.CYAN}[3]{Colors.RESET} Toggle Banner {Colors.DIM}- Show/hide ASCII banner{Colors.RESET}")
print()
print(f" {Colors.DIM}[0]{Colors.RESET} Back")
print()
try:
choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip()
if choice == "0" or not choice:
break
elif choice == "1":
new_val = not verbose
self.config.set('autarch', 'verbose', str(new_val).lower())
if new_val:
self.config.set('autarch', 'quiet', 'false')
self.config.save()
self.print_status(f"Verbose {'enabled' if new_val else 'disabled'}", "success")
input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}")
elif choice == "2":
new_val = not quiet
self.config.set('autarch', 'quiet', str(new_val).lower())
if new_val:
self.config.set('autarch', 'verbose', 'false')
self.config.save()
self.print_status(f"Quiet mode {'enabled' if new_val else 'disabled'}", "success")
input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}")
elif choice == "3":
new_val = not no_banner
self.config.set('autarch', 'no_banner', str(new_val).lower())
self.config.save()
self.print_status(f"Banner {'hidden' if new_val else 'shown'}", "success")
input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}")
except (EOFError, KeyboardInterrupt):
break
def load_config_file(self):
"""Load settings from an alternate config file."""
clear_screen()
self._show_banner()
print(f"{Colors.BOLD}{Colors.WHITE} Load Configuration File{Colors.RESET}")
print(f"{Colors.DIM} {'' * 50}{Colors.RESET}")
print()
print(f" {Colors.DIM}Current config: {self.config.config_path}{Colors.RESET}")
print()
path = input(f" {Colors.WHITE}Path to config file (or Enter to cancel): {Colors.RESET}").strip()
if not path:
return
config_path = Path(path).expanduser()
if not config_path.exists():
self.print_status(f"File not found: {config_path}", "error")
input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}")
return
try:
from core.config import Config
new_config = Config(str(config_path))
self.config = new_config
self.print_status(f"Loaded config from {config_path}", "success")
except Exception as e:
self.print_status(f"Failed to load config: {e}", "error")
input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}")
def show_user_manual(self):
"""Display the user manual in a pager."""
import shutil
import subprocess
manual_path = self._app_dir / 'user_manual.md'
if not manual_path.exists():
self.print_status("User manual not found", "error")
input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}")
return
pager = shutil.which('less') or shutil.which('more')
if pager:
subprocess.run([pager, str(manual_path)])
else:
# No pager — print page by page
lines = manual_path.read_text().splitlines()
page_size = 40
for i in range(0, len(lines), page_size):
for line in lines[i:i + page_size]:
print(line)
if i + page_size < len(lines):
try:
resp = input(f"\n{Colors.DIM} -- Press Enter for next page, q to quit -- {Colors.RESET}")
if resp.strip().lower() == 'q':
break
except (EOFError, KeyboardInterrupt):
break
def list_all_modules(self):
"""List all loaded modules, optionally filtered by category."""
clear_screen()
self._show_banner()
print(f"{Colors.BOLD}{Colors.WHITE} Loaded Modules{Colors.RESET}")
print(f"{Colors.DIM} {'' * 50}{Colors.RESET}")
print()
if not self.modules:
print(f" {Colors.YELLOW}No modules loaded.{Colors.RESET}")
else:
# Group by category
by_cat = {}
for name, info in sorted(self.modules.items()):
cat = info.category
if cat not in by_cat:
by_cat[cat] = []
by_cat[cat].append(info)
for cat_key in sorted(by_cat.keys()):
cat_info = CATEGORIES.get(cat_key, CATEGORIES.get('core', {'name': cat_key, 'color': Colors.WHITE}))
print(f" {cat_info['color']}{Colors.BOLD}{cat_info['name']}{Colors.RESET}")
for info in by_cat[cat_key]:
print(f" {Colors.GREEN}-{Colors.RESET} {info.name:20} {Colors.DIM}{info.description}{Colors.RESET}")
print()
print(f" {Colors.DIM}Total: {len(self.modules)} modules{Colors.RESET}")
print()
input(f"{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
def run(self):
"""Main menu loop."""
self.load_modules()
while self.running:
self.display_menu()
try:
choice = input(f"{Colors.WHITE} Select option: {Colors.RESET}").strip()
if choice == "1":
self.display_category_menu("defense")
elif choice == "2":
self.display_category_menu("offense")
elif choice == "3":
self.display_category_menu("counter")
elif choice == "4":
self.display_category_menu("analyze")
elif choice == "5":
self.display_category_menu("osint")
elif choice == "6":
self.display_category_menu("simulate")
elif choice == "7":
self.run_agent_hal()
elif choice == "8":
self.show_web_service()
elif choice == "9":
self.sideload_companion()
elif choice == "10":
self.show_mcp_server()
elif choice == "11":
self.show_user_manual()
elif choice == "12":
self.list_all_modules()
elif choice == "99":
self.show_settings()
elif choice == "98":
self.running = False
clear_screen()
print(f"\n{Colors.CYAN}Goodbye!{Colors.RESET}\n")
else:
self.print_status("Invalid option", "warning")
input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}")
except (EOFError, KeyboardInterrupt):
print()
self.running = False
clear_screen()
print(f"\n{Colors.CYAN}Goodbye!{Colors.RESET}\n")