273 lines
9.4 KiB
Python
Raw Normal View History

2025-11-04 07:52:36 +01:00
from abc import ABC, abstractmethod
2025-11-04 08:09:12 +01:00
2025-11-04 07:52:36 +01:00
class ProcessHandler(ABC):
"""Base class for process-specific handlers."""
def __init__(self, multiplexer):
self.multiplexer = multiplexer
self.state_machine = {}
2025-11-04 08:09:12 +01:00
self.current_state = "initial"
2025-11-04 07:52:36 +01:00
self.prompt_patterns = []
self.response_suggestions = {}
@abstractmethod
def get_process_type(self):
"""Return the process type this handler manages."""
pass
def update_state(self, output):
"""Update internal state based on output."""
pass
def get_prompt_suggestions(self):
"""Return suggested responses for current state."""
return self.response_suggestions.get(self.current_state, [])
def is_waiting_for_input(self):
"""Check if process appears to be waiting for input."""
2025-11-04 08:09:12 +01:00
return self.current_state in ["waiting_confirmation", "waiting_input"]
2025-11-04 07:52:36 +01:00
class AptHandler(ProcessHandler):
"""Handler for apt package manager interactions."""
def __init__(self, multiplexer):
super().__init__(multiplexer)
self.state_machine = {
2025-11-04 08:09:12 +01:00
"initial": ["running_command"],
"running_command": ["waiting_confirmation", "completed"],
"waiting_confirmation": ["confirmed", "cancelled"],
"confirmed": ["installing", "completed"],
"installing": ["completed", "error"],
"completed": [],
"error": [],
"cancelled": [],
2025-11-04 07:52:36 +01:00
}
self.prompt_patterns = [
2025-11-04 08:09:12 +01:00
(r"Do you want to continue\?", "confirmation"),
(r"After this operation.*installed\.", "size_info"),
(r"Need to get.*B of archives\.", "download_info"),
(r"Unpacking.*Configuring", "configuring"),
(r"Setting up", "setting_up"),
(r"E:\s", "error"),
2025-11-04 07:52:36 +01:00
]
def get_process_type(self):
2025-11-04 08:09:12 +01:00
return "apt"
2025-11-04 07:52:36 +01:00
def update_state(self, output):
"""Update state based on apt output patterns."""
output_lower = output.lower()
# Check for completion
2025-11-04 08:09:12 +01:00
if "processing triggers" in output_lower or "done" in output_lower:
self.current_state = "completed"
2025-11-04 07:52:36 +01:00
# Check for confirmation prompts
2025-11-04 08:09:12 +01:00
elif "do you want to continue" in output_lower:
self.current_state = "waiting_confirmation"
2025-11-04 07:52:36 +01:00
# Check for installation progress
2025-11-04 08:09:12 +01:00
elif "setting up" in output_lower or "unpacking" in output_lower:
self.current_state = "installing"
2025-11-04 07:52:36 +01:00
# Check for errors
2025-11-04 08:09:12 +01:00
elif "e:" in output_lower or "error" in output_lower:
self.current_state = "error"
2025-11-04 07:52:36 +01:00
def get_prompt_suggestions(self):
"""Return suggested responses for apt prompts."""
suggestions = super().get_prompt_suggestions()
2025-11-04 08:09:12 +01:00
if self.current_state == "waiting_confirmation":
suggestions.extend(["y", "yes", "n", "no"])
2025-11-04 07:52:36 +01:00
return suggestions
2025-11-04 08:09:12 +01:00
2025-11-04 07:52:36 +01:00
class VimHandler(ProcessHandler):
"""Handler for vim editor interactions."""
def __init__(self, multiplexer):
super().__init__(multiplexer)
self.state_machine = {
2025-11-04 08:09:12 +01:00
"initial": ["normal_mode", "insert_mode"],
"normal_mode": ["insert_mode", "command_mode", "visual_mode"],
"insert_mode": ["normal_mode"],
"command_mode": ["normal_mode"],
"visual_mode": ["normal_mode"],
"exiting": [],
2025-11-04 07:52:36 +01:00
}
self.prompt_patterns = [
2025-11-04 08:09:12 +01:00
(r"-- INSERT --", "insert_mode"),
(r"-- VISUAL --", "visual_mode"),
(r":", "command_mode"),
(r"Press ENTER", "waiting_enter"),
(r"Saved", "saved"),
2025-11-04 07:52:36 +01:00
]
self.mode_indicators = {
2025-11-04 08:09:12 +01:00
"insert": "-- INSERT --",
"visual": "-- VISUAL --",
"command": ":",
2025-11-04 07:52:36 +01:00
}
def get_process_type(self):
2025-11-04 08:09:12 +01:00
return "vim"
2025-11-04 07:52:36 +01:00
def update_state(self, output):
"""Update state based on vim mode indicators."""
2025-11-04 08:09:12 +01:00
if "-- INSERT --" in output:
self.current_state = "insert_mode"
elif "-- VISUAL --" in output:
self.current_state = "visual_mode"
elif output.strip().endswith(":"):
self.current_state = "command_mode"
elif "Press ENTER" in output:
self.current_state = "waiting_enter"
2025-11-04 07:52:36 +01:00
else:
# Default to normal mode if no specific indicators
2025-11-04 08:09:12 +01:00
self.current_state = "normal_mode"
2025-11-04 07:52:36 +01:00
def get_prompt_suggestions(self):
"""Return suggested commands for vim modes."""
suggestions = super().get_prompt_suggestions()
2025-11-04 08:09:12 +01:00
if self.current_state == "command_mode":
suggestions.extend(["w", "q", "wq", "q!", "w!"])
elif self.current_state == "normal_mode":
suggestions.extend(["i", "a", "o", "dd", ":w", ":q"])
elif self.current_state == "waiting_enter":
suggestions.extend(["\n"])
2025-11-04 07:52:36 +01:00
return suggestions
2025-11-04 08:09:12 +01:00
2025-11-04 07:52:36 +01:00
class SSHHandler(ProcessHandler):
"""Handler for SSH connection interactions."""
def __init__(self, multiplexer):
super().__init__(multiplexer)
self.state_machine = {
2025-11-04 08:09:12 +01:00
"initial": ["connecting"],
"connecting": ["auth_prompt", "connected", "failed"],
"auth_prompt": ["connected", "failed"],
"connected": ["shell", "disconnected"],
"shell": ["disconnected"],
"failed": [],
"disconnected": [],
2025-11-04 07:52:36 +01:00
}
self.prompt_patterns = [
2025-11-04 08:09:12 +01:00
(r"password:", "password_prompt"),
(r"yes/no", "host_key_prompt"),
(r"Permission denied", "auth_failed"),
(r"Welcome to", "connected"),
(r"\$", "shell_prompt"),
(r"\#", "root_shell_prompt"),
(r"Connection closed", "disconnected"),
2025-11-04 07:52:36 +01:00
]
def get_process_type(self):
2025-11-04 08:09:12 +01:00
return "ssh"
2025-11-04 07:52:36 +01:00
def update_state(self, output):
"""Update state based on SSH connection output."""
output_lower = output.lower()
2025-11-04 08:09:12 +01:00
if "permission denied" in output_lower:
self.current_state = "failed"
elif "password:" in output_lower:
self.current_state = "auth_prompt"
elif "yes/no" in output_lower:
self.current_state = "auth_prompt"
elif "welcome to" in output_lower or "last login" in output_lower:
self.current_state = "connected"
elif output.strip().endswith("$") or output.strip().endswith("#"):
self.current_state = "shell"
elif "connection closed" in output_lower:
self.current_state = "disconnected"
2025-11-04 07:52:36 +01:00
def get_prompt_suggestions(self):
"""Return suggested responses for SSH prompts."""
suggestions = super().get_prompt_suggestions()
2025-11-04 08:09:12 +01:00
if self.current_state == "auth_prompt":
if "password:" in self.multiplexer.get_all_output()["stdout"]:
suggestions.extend(["<password>"]) # Placeholder for actual password
elif "yes/no" in self.multiplexer.get_all_output()["stdout"]:
suggestions.extend(["yes", "no"])
2025-11-04 07:52:36 +01:00
return suggestions
2025-11-04 08:09:12 +01:00
2025-11-04 07:52:36 +01:00
class GenericProcessHandler(ProcessHandler):
"""Fallback handler for unknown process types."""
def __init__(self, multiplexer):
super().__init__(multiplexer)
self.state_machine = {
2025-11-04 08:09:12 +01:00
"initial": ["running"],
"running": ["waiting_input", "completed"],
"waiting_input": ["running"],
"completed": [],
2025-11-04 07:52:36 +01:00
}
self.prompt_patterns = [
2025-11-04 08:09:12 +01:00
(r"\?\s*$", "waiting_input"), # Lines ending with ?
(r">\s*$", "waiting_input"), # Lines ending with >
(r":\s*$", "waiting_input"), # Lines ending with :
(r"done", "completed"),
(r"finished", "completed"),
(r"exit code", "completed"),
2025-11-04 07:52:36 +01:00
]
def get_process_type(self):
2025-11-04 08:09:12 +01:00
return "generic"
2025-11-04 07:52:36 +01:00
def update_state(self, output):
"""Basic state detection for generic processes."""
output_lower = output.lower()
2025-11-04 08:09:12 +01:00
if any(pattern in output_lower for pattern in ["done", "finished", "complete"]):
self.current_state = "completed"
elif any(output.strip().endswith(char) for char in ["?", ">", ":"]):
self.current_state = "waiting_input"
2025-11-04 07:52:36 +01:00
else:
2025-11-04 08:09:12 +01:00
self.current_state = "running"
2025-11-04 07:52:36 +01:00
# Handler registry
_handler_classes = {
2025-11-04 08:09:12 +01:00
"apt": AptHandler,
"vim": VimHandler,
"ssh": SSHHandler,
"generic": GenericProcessHandler,
2025-11-04 07:52:36 +01:00
}
2025-11-04 08:09:12 +01:00
2025-11-04 07:52:36 +01:00
def get_handler_for_process(process_type, multiplexer):
"""Get appropriate handler for a process type."""
handler_class = _handler_classes.get(process_type, GenericProcessHandler)
return handler_class(multiplexer)
2025-11-04 08:09:12 +01:00
2025-11-04 07:52:36 +01:00
def detect_process_type(command):
"""Detect process type from command."""
2025-11-04 08:09:12 +01:00
command_str = " ".join(command) if isinstance(command, list) else command
2025-11-04 07:52:36 +01:00
command_lower = command_str.lower()
2025-11-04 08:09:12 +01:00
if "apt" in command_lower or "apt-get" in command_lower:
return "apt"
elif "vim" in command_lower or "vi " in command_lower:
return "vim"
elif "ssh" in command_lower:
return "ssh"
2025-11-04 07:52:36 +01:00
else:
2025-11-04 08:09:12 +01:00
return "generic"
return "ssh"
2025-11-04 07:52:36 +01:00
def detect_process_type(command):
"""Detect process type from command."""
2025-11-04 08:09:12 +01:00
command_str = " ".join(command) if isinstance(command, list) else command
2025-11-04 07:52:36 +01:00
command_lower = command_str.lower()
2025-11-04 08:09:12 +01:00
if "apt" in command_lower or "apt-get" in command_lower:
return "apt"
elif "vim" in command_lower or "vi " in command_lower:
return "vim"
elif "ssh" in command_lower:
return "ssh"
2025-11-04 07:52:36 +01:00
else:
2025-11-04 08:09:12 +01:00
return "generic"