Skip to main content
Glama

Agent MCP

display.py14.1 kB
""" Display components for the TUI. Handles rendering of various UI elements like headers, status bars, and data tables. """ import os import shutil from datetime import datetime from typing import List, Dict, Optional, Tuple try: import requests except ImportError: requests = None from .colors import TUITheme, AGENT_MCP_LOGO, STATUS_SYMBOLS, get_responsive_agent_mcp_banner from ..core.config import VERSION, AUTHOR, GITHUB_URL, GITHUB_REPO class TUIDisplay: """Handles all display-related functionality for the TUI.""" def __init__(self): self.terminal_width = shutil.get_terminal_size().columns self.terminal_height = shutil.get_terminal_size().lines self._update_available = None # Cache update status self._last_update_check = None # Time of last check def clear_screen(self): """Clear the terminal screen.""" os.system('cls' if os.name == 'nt' else 'clear') def move_cursor(self, row: int, col: int): """Move cursor to specific position.""" print(f"\033[{row};{col}H", end='') def clear_line(self): """Clear current line.""" print("\033[2K", end='') def save_cursor(self): """Save cursor position.""" print("\033[s", end='') def restore_cursor(self): """Restore cursor position.""" print("\033[u", end='') def hide_cursor(self): """Hide cursor to reduce flicker.""" print("\033[?25l", end='') def show_cursor(self): """Show cursor.""" print("\033[?25h", end='') def enable_alternate_screen(self): """Enable alternate screen buffer.""" print("\033[?1049h", end='') def disable_alternate_screen(self): """Disable alternate screen buffer and return to normal screen.""" print("\033[?1049l", end='') def refresh_terminal_size(self): """Update terminal dimensions.""" self.terminal_width = shutil.get_terminal_size().columns self.terminal_height = shutil.get_terminal_size().lines def _strip_ansi_codes(self, text: str) -> str: """Strip ANSI escape codes from text for length calculation.""" import re ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])') return ansi_escape.sub('', text) def draw_header(self, clear_first: bool = True): """Draw the application header with logo, credits, and version info.""" if clear_first: self.clear_screen() else: self.move_cursor(1, 1) # Check for updates in the background update_available = self._check_for_updates() current_row = 1 # If update available, show a notification at the top if update_available: update_msg = " NEW VERSION AVAILABLE - Please update " padding = (self.terminal_width - len(update_msg)) // 2 self.move_cursor(current_row, 1) self.clear_line() print(' ' * padding + TUITheme.error(update_msg)) current_row += 2 # Center the AGENT MCP logo with gradient - responsive to terminal width agent_mcp_banner = get_responsive_agent_mcp_banner(self.terminal_width) logo_lines = agent_mcp_banner.split('\n') for line in logo_lines: self.move_cursor(current_row, 1) self.clear_line() # Calculate padding without ANSI codes for proper centering clean_line = self._strip_ansi_codes(line) padding = (self.terminal_width - len(clean_line)) // 2 print(' ' * padding + line) current_row += 1 # Add credits and version info credits_text = f"Created by {AUTHOR} ({GITHUB_URL})" version_text = f"Version {VERSION}" # Center the credits self.move_cursor(current_row, 1) self.clear_line() credits_padding = (self.terminal_width - len(credits_text)) // 2 print(' ' * credits_padding + TUITheme.dim(credits_text)) current_row += 1 # Center the version self.move_cursor(current_row, 1) self.clear_line() version_padding = (self.terminal_width - len(version_text)) // 2 print(' ' * version_padding + TUITheme.info(version_text)) current_row += 1 self.move_cursor(current_row, 1) self.clear_line() print(TUITheme.colorize('─' * self.terminal_width, TUITheme.BORDER)) return current_row + 1 def _check_for_updates(self) -> bool: """Check if a newer version is available on GitHub.""" # If requests isn't available, don't check for updates if requests is None: return False # Only check once per session or every 5 minutes if self._update_available is not None: if self._last_update_check: time_since_check = datetime.now() - self._last_update_check if time_since_check.total_seconds() < 300: # 5 minutes return self._update_available try: # Try to get the latest release from GitHub API response = requests.get( f"https://api.github.com/repos/{GITHUB_REPO}/releases/latest", timeout=2 # Quick timeout to not block the UI ) if response.status_code == 200: latest_version = response.json().get('tag_name', '').lstrip('v') if latest_version and latest_version != VERSION: self._update_available = True else: self._update_available = False else: self._update_available = False except: # Silently fail if we can't check for updates self._update_available = False self._last_update_check = datetime.now() return self._update_available def draw_status_bar(self, server_status: Dict[str, any]): """Draw the status bar showing server information.""" self.clear_line() status_color = TUITheme.SUCCESS if server_status.get('running') else TUITheme.ERROR status_symbol = STATUS_SYMBOLS['running'] if server_status.get('running') else STATUS_SYMBOLS['stopped'] status_text = f"{status_symbol} Server: {server_status.get('status', 'Unknown')}" status_text += f" | Port: {server_status.get('port', 'N/A')}" status_text += f" | Agents: {server_status.get('agent_count', 0)}" status_text += f" | Tasks: {server_status.get('task_count', 0)}" # Right-align the time current_time = datetime.now().strftime("%H:%M:%S") time_text = f"Time: {current_time}" # Calculate padding total_status_length = len(status_text) + len(time_text) if total_status_length < self.terminal_width: padding = self.terminal_width - total_status_length - 2 full_status = f" {status_text}{' ' * padding}{time_text} " else: full_status = f" {status_text} " print(TUITheme.colorize(full_status, status_color, TUITheme.BG_BLACK)) def draw_agent_list(self, agents: List[Dict[str, any]], selected_index: int = -1): """Draw the list of agents with their status.""" print(TUITheme.header("\n Agents")) print(TUITheme.colorize('─' * self.terminal_width, TUITheme.BORDER)) if not agents: print(TUITheme.dim(" No agents available")) else: for i, agent in enumerate(agents): is_selected = i == selected_index # Agent status if agent.get('active'): status_symbol = STATUS_SYMBOLS['running'] status_color = TUITheme.AGENT_ACTIVE else: status_symbol = STATUS_SYMBOLS['stopped'] status_color = TUITheme.AGENT_INACTIVE # Format agent line agent_line = f" {status_symbol} {agent.get('name', 'Unknown')} (ID: {agent.get('id', 'N/A')})" agent_line += f" - Tasks: {agent.get('task_count', 0)}" # Apply selection highlighting if is_selected: print(TUITheme.colorize(agent_line, TUITheme.MENU_SELECTED)) else: print(TUITheme.colorize(agent_line, status_color)) def draw_task_list(self, tasks: List[Dict[str, any]], selected_index: int = -1): """Draw the list of tasks with their status.""" print(TUITheme.header("\n Tasks")) print(TUITheme.colorize('─' * self.terminal_width, TUITheme.BORDER)) if not tasks: print(TUITheme.dim(" No tasks available")) else: for i, task in enumerate(tasks): is_selected = i == selected_index # Task status status = task.get('status', 'unknown').lower() if status == 'running': status_symbol = STATUS_SYMBOLS['running'] status_color = TUITheme.TASK_RUNNING elif status == 'completed': status_symbol = STATUS_SYMBOLS['success'] status_color = TUITheme.SUCCESS elif status == 'error' or status == 'failed': status_symbol = STATUS_SYMBOLS['error'] status_color = TUITheme.TASK_ERROR else: status_symbol = STATUS_SYMBOLS['stopped'] status_color = TUITheme.TASK_STOPPED # Format task line task_line = f" {status_symbol} {task.get('name', 'Unknown')} ({status})" task_line += f" - Agent: {task.get('agent_name', 'N/A')}" # Apply selection highlighting if is_selected: print(TUITheme.colorize(task_line, TUITheme.MENU_SELECTED)) else: print(TUITheme.colorize(task_line, status_color)) def draw_menu(self, menu_items: List[str], selected_index: int): """Draw a menu with selectable items.""" print(TUITheme.header("\n Menu")) print(TUITheme.colorize('─' * self.terminal_width, TUITheme.BORDER)) for i, item in enumerate(menu_items): is_selected = i == selected_index if is_selected: print(TUITheme.colorize(f" {STATUS_SYMBOLS['arrow_right']} {item}", TUITheme.MENU_SELECTED)) else: print(f" {item}") def draw_help_footer(self): """Draw the help footer with keyboard shortcuts.""" help_text = " ↑/↓: Navigate | Enter: Select | q: Quit | h: Help " padding = (self.terminal_width - len(help_text)) // 2 print(TUITheme.colorize('\n' + '─' * self.terminal_width, TUITheme.BORDER)) print(' ' * padding + TUITheme.dim(help_text)) def draw_text_box(self, title: str, content: str, width: Optional[int] = None): """Draw a box with text content.""" if width is None: width = min(80, self.terminal_width - 4) # Draw top border print(TUITheme.colorize('┌' + '─' * (width - 2) + '┐', TUITheme.BORDER)) # Draw title if provided if title: title_line = f"│ {TUITheme.bold(title):<{width-3}}│" print(TUITheme.colorize(title_line, TUITheme.BORDER)) print(TUITheme.colorize('├' + '─' * (width - 2) + '┤', TUITheme.BORDER)) # Draw content lines = content.split('\n') for line in lines: # Wrap long lines while len(line) > width - 4: print(TUITheme.colorize(f"│ {line[:width-4]} │", TUITheme.BORDER)) line = line[width-4:] print(TUITheme.colorize(f"│ {line:<{width-3}}│", TUITheme.BORDER)) # Draw bottom border print(TUITheme.colorize('└' + '─' * (width - 2) + '┘', TUITheme.BORDER)) def draw_progress_bar(self, progress: float, width: int = 40, label: str = ""): """Draw a progress bar.""" filled = int(width * progress) empty = width - filled bar = f"[{'█' * filled}{'░' * empty}]" percentage = f"{progress * 100:.1f}%" if label: print(f"{label}: {bar} {percentage}") else: print(f"{bar} {percentage}") def draw_spinner(self, message: str, frame: int = 0): """Draw a spinning loader animation.""" spinner_frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'] spinner = spinner_frames[frame % len(spinner_frames)] print(f"\r{TUITheme.colorize(spinner, TUITheme.PRIMARY)} {message}", end='', flush=True) def draw_confirmation_dialog(self, message: str) -> bool: """Draw a confirmation dialog and get user response.""" self.draw_text_box("Confirmation", message) print(TUITheme.warning("\nPress 'y' to confirm, 'n' to cancel: "), end='', flush=True) # Note: Actual input handling would be done by the caller # This is just the display component return True def draw_input_dialog(self, prompt: str, default_value: str = "") -> str: """Draw an input dialog for user text entry.""" self.draw_text_box("Input", prompt) if default_value: print(TUITheme.dim(f"Default: {default_value}")) print(TUITheme.info("Enter value: "), end='', flush=True) # Note: Actual input handling would be done by the caller # This is just the display component return ""

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/rinadelph/Agent-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server