Supprimer rivaterminalbu
This commit is contained in:
parent
713e8c63de
commit
28055f14a3
640
rivaterminalbu
640
rivaterminalbu
@ -1,640 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import readline
|
||||
from dotenv import load_dotenv
|
||||
import time
|
||||
import pandas as pd
|
||||
from rich.console import Console
|
||||
from rich.prompt import Prompt
|
||||
from rich.panel import Panel
|
||||
from rich.text import Text
|
||||
from rich import box
|
||||
from rich.table import Table
|
||||
from rich.columns import Columns
|
||||
from typing import Optional, List
|
||||
|
||||
project_root = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.append(project_root)
|
||||
|
||||
from modules.database import DatabaseConnection
|
||||
from modules.signals.markets_view import MarketsView
|
||||
from modules.signals.sectors_view import SectorsView
|
||||
from modules.signals.commodities_view import CommoditiesView
|
||||
from modules.signals.forex_view import ForexView
|
||||
from modules.signals.stocks_view import StocksView
|
||||
from modules.signals.whispers import Whispers
|
||||
|
||||
load_dotenv()
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.WARNING,
|
||||
format='%(message)s',
|
||||
handlers=[
|
||||
logging.FileHandler('rivaterminal.log', mode='a'),
|
||||
logging.StreamHandler(sys.stdout)
|
||||
]
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
console = Console()
|
||||
|
||||
LOGO = """
|
||||
*
|
||||
*****
|
||||
** ****
|
||||
* * ** *** *
|
||||
** ** **** *
|
||||
*** ** * *** *
|
||||
** ** ******* **** *
|
||||
* ** ** * **** **
|
||||
** ******* *** ******
|
||||
* ** *** ** ** ***** *
|
||||
* *** ******** ***** *
|
||||
* ** *********** *
|
||||
** *** **** # ********
|
||||
*** *** ** ********
|
||||
**** *** ********** *
|
||||
** ** *** ** *****
|
||||
*** **+** * ***# *** **
|
||||
***** ***+* ******** *****
|
||||
#* *** *** **++**
|
||||
* *** ** ** **++** ** ******* *** ** ** ** *** ******** ***
|
||||
**** #*********** *** *+******** *** *+* *+* **** *+* *********+** ****
|
||||
***++**** ** ***** *+** **+* *** **+ *** ***** *+* **+* ***+* ****
|
||||
#***++***** *+**** *+** *+* *** +* #** ** *** *+* **+* **+* ****
|
||||
**** *** *+** **** *** *** #*** **** *** *** **+* **** ****
|
||||
******** ** **+* *+******* *** *** *** **+* *** *+* **+* **** ****
|
||||
************ ***** *+****+ *** *** *** *+*******++* *+* **+* **** ****
|
||||
** *+** **** *** *+ +* ************** *+* **+* **+* ****
|
||||
****+****** *+** **** *** **+** *** **** *** **+* ***+* ****
|
||||
**** ** *+** **** *** *+* *+* *+* *++++++* **++++++++* ****
|
||||
*** *
|
||||
|
||||
"""
|
||||
|
||||
class Dashboard:
|
||||
def __init__(self, db_connection):
|
||||
self.db = db_connection
|
||||
self.console = Console()
|
||||
self.logo = LOGO
|
||||
|
||||
def display_commands(self):
|
||||
table = Table(title="Available Commands", box=box.HEAVY_EDGE)
|
||||
table.add_column("Command", style="cyan")
|
||||
table.add_column("Description", style="white")
|
||||
|
||||
commands = [
|
||||
("main", "Return to main screen"),
|
||||
("marketsview", "View market indices and performance"),
|
||||
("sectorsview", "View sector performance and analysis"),
|
||||
("commoditiesview", "View commodities markets"),
|
||||
("forexview", "View forex markets"),
|
||||
("stocksview", "View stock analysis and data"),
|
||||
("whispers", "View market whispers and insights"),
|
||||
("news", "View market news [-d days] [-s source] [-t ticker]"),
|
||||
("portfolio", "Portfolio management (coming soon)"),
|
||||
("pricing", "Options pricing tools (coming soon)"),
|
||||
("reports", "Financial reports (coming soon)"),
|
||||
("slm", "SLM Reports (coming soon)"),
|
||||
("talk", "Revaldi LLM (coming soon)"),
|
||||
("help", "Display this help message"),
|
||||
("quit", "Exit the terminal")
|
||||
]
|
||||
|
||||
for command, description in commands:
|
||||
table.add_row(command, description)
|
||||
|
||||
self.console.print(table)
|
||||
|
||||
def get_market_overview(self):
|
||||
query = """
|
||||
WITH market_symbols AS (
|
||||
SELECT DISTINCT ON (m.symbol)
|
||||
m.symbol,
|
||||
mp.date,
|
||||
mp.close as last_price,
|
||||
ROUND(((mp.close - LAG(mp.close) OVER (PARTITION BY m.id ORDER BY mp.date)) /
|
||||
NULLIF(LAG(mp.close) OVER (PARTITION BY m.id ORDER BY mp.date), 0) * 100)::numeric, 2) as price_change_pct
|
||||
FROM market_indexes_ref m
|
||||
JOIN market_indexes_prices mp ON mp.index_id = m.id
|
||||
WHERE m.is_active = true
|
||||
AND m.symbol IN (
|
||||
'^SPX', -- S&P 500
|
||||
'^NDX', -- NASDAQ 100
|
||||
'^STOXX50E', -- EuroStoxx50
|
||||
'^N300', -- Nikkei
|
||||
'^HSI', -- Hang Seng
|
||||
'MSCIWORLD', -- MSCI World
|
||||
'EUR=X' -- USD/EUR
|
||||
)
|
||||
AND mp.date >= CURRENT_DATE - INTERVAL '7 days'
|
||||
ORDER BY m.symbol, mp.date DESC
|
||||
)
|
||||
SELECT
|
||||
ROW_NUMBER() OVER (ORDER BY
|
||||
CASE
|
||||
WHEN symbol = '^SPX' THEN 1
|
||||
WHEN symbol = '^NDX' THEN 2
|
||||
WHEN symbol = '^STOXX50E' THEN 3
|
||||
WHEN symbol = '^N300' THEN 4
|
||||
WHEN symbol = '^HSI' THEN 5
|
||||
WHEN symbol = 'MSCIWORLD' THEN 6
|
||||
WHEN symbol = 'EUR=X' THEN 7
|
||||
END
|
||||
) - 1 as row_num,
|
||||
CASE
|
||||
WHEN symbol = '^SPX' THEN 'S&P500'
|
||||
WHEN symbol = '^NDX' THEN 'Nasdaq'
|
||||
WHEN symbol = '^STOXX50E' THEN 'EuroStoxx50'
|
||||
WHEN symbol = '^N300' THEN 'Nikkei'
|
||||
WHEN symbol = '^HSI' THEN 'HangSeng'
|
||||
WHEN symbol = 'MSCIWORLD' THEN 'MSCI World'
|
||||
WHEN symbol = 'EUR=X' THEN 'USD/EUR'
|
||||
END as index_name,
|
||||
last_price::numeric as last_price,
|
||||
price_change_pct::numeric as price_change_pct
|
||||
FROM market_symbols;
|
||||
"""
|
||||
try:
|
||||
return self.db.execute_query(query)
|
||||
except Exception as e:
|
||||
logger.error(f"Market data query failed: {e}")
|
||||
return pd.DataFrame()
|
||||
|
||||
def get_market_panel(self):
|
||||
"""Generate market overview panel content"""
|
||||
market_data = self.get_market_overview()
|
||||
if market_data is not None and not market_data.empty:
|
||||
table = Table(
|
||||
show_header=True,
|
||||
header_style="white",
|
||||
box=box.MINIMAL_DOUBLE_HEAD,
|
||||
border_style="white",
|
||||
padding=(0, 2)
|
||||
)
|
||||
|
||||
table.add_column("Index", style="white", width=25, justify="left")
|
||||
table.add_column("Last Price", style="white", justify="right", width=15)
|
||||
table.add_column("Change", style="white", justify="right", width=12)
|
||||
|
||||
market_data = market_data.sort_values(by='row_num')
|
||||
|
||||
for _, row in market_data.iterrows():
|
||||
change_color = "red" if row['price_change_pct'] < 0 else "green"
|
||||
if row['index_name'] == 'USD/EUR':
|
||||
formatted_price = f"{row['last_price']:.4f}"
|
||||
else:
|
||||
formatted_price = f"{row['last_price']:,.2f}"
|
||||
|
||||
table.add_row(
|
||||
str(row['index_name']),
|
||||
formatted_price,
|
||||
f"{row['price_change_pct']:+.2f}%",
|
||||
style=change_color
|
||||
)
|
||||
|
||||
return table
|
||||
return Text("No market data available")
|
||||
|
||||
def get_eco_news(self):
|
||||
"""Get the latest economic news for dashboard display"""
|
||||
query = """
|
||||
SELECT
|
||||
'ECO' as news_type,
|
||||
title,
|
||||
source,
|
||||
published_at
|
||||
FROM econews
|
||||
WHERE published_at >= CURRENT_DATE - INTERVAL '2 days'
|
||||
ORDER BY published_at DESC
|
||||
LIMIT 8;
|
||||
"""
|
||||
try:
|
||||
return self.db.execute_query(query)
|
||||
except Exception as e:
|
||||
logger.error(f"Economic news query failed: {e}")
|
||||
return pd.DataFrame()
|
||||
|
||||
def get_sdg_news(self):
|
||||
"""Get the latest SDG news for dashboard display"""
|
||||
query = """
|
||||
SELECT
|
||||
'SDG' as news_type,
|
||||
title,
|
||||
category as source,
|
||||
published_at
|
||||
FROM news
|
||||
WHERE published_at >= CURRENT_DATE - INTERVAL '2 days'
|
||||
ORDER BY published_at DESC
|
||||
LIMIT 8;
|
||||
"""
|
||||
try:
|
||||
return self.db.execute_query(query)
|
||||
except Exception as e:
|
||||
logger.error(f"SDG news query failed: {e}")
|
||||
return pd.DataFrame()
|
||||
|
||||
def get_eco_news_panel(self):
|
||||
"""Generate economic news panel content"""
|
||||
news_data = self.get_eco_news()
|
||||
|
||||
if news_data is not None and not news_data.empty:
|
||||
table = Table(
|
||||
show_header=False,
|
||||
box=box.MINIMAL_DOUBLE_HEAD,
|
||||
border_style="white",
|
||||
width=155,
|
||||
padding=(0, 1)
|
||||
)
|
||||
|
||||
table.add_column(width=65, overflow="fold")
|
||||
|
||||
for _, row in news_data.iterrows():
|
||||
published_time = pd.to_datetime(row['published_at']).strftime('%H:%M')
|
||||
title = row['title']
|
||||
if len(title) > 152:
|
||||
title = title[:150] + "..."
|
||||
|
||||
formatted_row = f"[dim]{published_time}[/dim] [green]ECO[/green] 📈 {title}"
|
||||
table.add_row(formatted_row)
|
||||
|
||||
return table
|
||||
return Text("No economic news available")
|
||||
|
||||
def get_sdg_news_panel(self):
|
||||
"""Generate SDG news panel content"""
|
||||
news_data = self.get_sdg_news()
|
||||
|
||||
if news_data is not None and not news_data.empty:
|
||||
table = Table(
|
||||
show_header=False,
|
||||
box=box.MINIMAL_DOUBLE_HEAD,
|
||||
border_style="white",
|
||||
width=155,
|
||||
padding=(0, 1)
|
||||
)
|
||||
|
||||
table.add_column(width=65, overflow="fold")
|
||||
|
||||
for _, row in news_data.iterrows():
|
||||
published_time = pd.to_datetime(row['published_at']).strftime('%H:%M')
|
||||
title = row['title']
|
||||
if len(title) > 152:
|
||||
title = title[:150] + "..."
|
||||
|
||||
formatted_row = f"[dim]{published_time}[/dim] [blue]SDG[/blue] 🌍 {title}"
|
||||
table.add_row(formatted_row)
|
||||
|
||||
return table
|
||||
return Text("No SDG news available")
|
||||
|
||||
def display_welcome_screen(self):
|
||||
self.console.print(self.logo, style="cyan")
|
||||
|
||||
self.console.print("\nWelcome to RivaCube Financial Terminal", style="yellow bold")
|
||||
|
||||
from datetime import datetime
|
||||
current_date = datetime.now()
|
||||
day_suffix = "th" if 11 <= current_date.day <= 13 else {1: "st", 2: "nd", 3: "rd"}.get(current_date.day % 10, "th")
|
||||
formatted_date = current_date.strftime(f"%A, %B {current_date.day}{day_suffix} %Y")
|
||||
|
||||
date_copyright = Text()
|
||||
date_copyright.append(formatted_date, style="cyan italic")
|
||||
date_copyright.append(" ‒ ", style="dim")
|
||||
date_copyright.append("Copyright SLM IMPACT FINANCE 2020", style="dim")
|
||||
self.console.print(date_copyright)
|
||||
|
||||
# Create market panel
|
||||
market_panel = Panel(
|
||||
self.get_market_panel(),
|
||||
title="Markets Overview",
|
||||
border_style="white",
|
||||
box=box.ROUNDED,
|
||||
width=65,
|
||||
padding=(0, 1)
|
||||
)
|
||||
|
||||
# Create news panels
|
||||
eco_panel = Panel(
|
||||
self.get_eco_news_panel(),
|
||||
title="Economic News",
|
||||
border_style="green",
|
||||
box=box.ROUNDED,
|
||||
width=160,
|
||||
padding=(0, 1)
|
||||
)
|
||||
|
||||
sdg_panel = Panel(
|
||||
self.get_sdg_news_panel(),
|
||||
title="SDG News",
|
||||
border_style="blue",
|
||||
box=box.ROUNDED,
|
||||
width=160,
|
||||
padding=(0, 1)
|
||||
)
|
||||
|
||||
# Print panels: market on top, news panels side by side below
|
||||
self.console.print("\n") # Add some spacing
|
||||
self.console.print(market_panel)
|
||||
self.console.print("\n") # Add spacing between panels
|
||||
self.console.print(sdg_panel)
|
||||
self.console.print("\n") # Add spacing between panels
|
||||
self.console.print(eco_panel)
|
||||
self.console.print("\n")
|
||||
self.display_commands()
|
||||
|
||||
class RivaTerminal:
|
||||
def __init__(self):
|
||||
self.db = None
|
||||
self.components_initialized = False
|
||||
self.commands = {}
|
||||
self.tickers = {}
|
||||
self.current_mode = None
|
||||
self.current_ticker = None
|
||||
self.styles = {
|
||||
'success': 'green',
|
||||
'error': 'red bold',
|
||||
'warning': 'yellow',
|
||||
'info': 'cyan',
|
||||
'prompt': 'blue',
|
||||
'title': 'magenta bold',
|
||||
'accent': 'yellow bold'
|
||||
}
|
||||
|
||||
self._init_database()
|
||||
if self.db:
|
||||
self._init_components()
|
||||
self._setup_autocomplete()
|
||||
|
||||
def _init_database(self) -> None:
|
||||
max_retries = 3
|
||||
for retry in range(max_retries):
|
||||
try:
|
||||
self.db = DatabaseConnection()
|
||||
self.db.execute_query("SELECT 1")
|
||||
return
|
||||
except Exception as e:
|
||||
if retry < max_retries - 1:
|
||||
console.print(f"[{self.styles['warning']}]Retrying database connection... ({retry + 1}/{max_retries})[/]")
|
||||
time.sleep(2)
|
||||
|
||||
console.print(f"[{self.styles['error']}]Database connection unavailable. Features will be limited.[/]")
|
||||
self.db = None
|
||||
|
||||
def _init_components(self) -> None:
|
||||
try:
|
||||
if not self.db:
|
||||
logger.error("Cannot initialize: No database connection")
|
||||
return
|
||||
|
||||
self.dashboard = Dashboard(self.db)
|
||||
self.markets_view = MarketsView(self.db)
|
||||
self.sectors_view = SectorsView(self.db)
|
||||
self.commodities_view = CommoditiesView(self.db)
|
||||
self.forex_view = ForexView(self.db)
|
||||
self.stocks_view = StocksView(self.db)
|
||||
self.whispers = Whispers(self.db)
|
||||
|
||||
self.commands = {
|
||||
'quit': self._handle_quit,
|
||||
'help': lambda _: self.dashboard.display_commands(),
|
||||
'main': lambda _: self.dashboard.display_welcome_screen(),
|
||||
'marketsview': lambda _: self.markets_view.display(),
|
||||
'sectorsview': lambda _: self.sectors_view.display(),
|
||||
'commoditiesview': lambda _: self.commodities_view.display(),
|
||||
'forexview': lambda _: self.forex_view.display(),
|
||||
'stocksview': self._handle_stocks_view,
|
||||
'whispers': lambda _: self.whispers.display(),
|
||||
'portfolio': lambda _: console.print("[yellow]Portfolio module coming soon...[/]"),
|
||||
'pricing': lambda _: console.print("[yellow]Pricing module coming soon...[/]"),
|
||||
'reports': lambda _: console.print("[yellow]Reports module coming soon...[/]"),
|
||||
'slm': lambda _: console.print("[yellow]SLM Reports coming soon...[/]"),
|
||||
'talk': lambda _: console.print("[yellow]Revaldi LLM coming soon...[/]")
|
||||
}
|
||||
|
||||
self.components_initialized = True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Component initialization error: {e}")
|
||||
self.components_initialized = False
|
||||
console.print(f"[{self.styles['error']}]Component initialization failed.[/]")
|
||||
|
||||
def _setup_autocomplete(self) -> None:
|
||||
try:
|
||||
if not self.db:
|
||||
return
|
||||
|
||||
query = """
|
||||
SELECT DISTINCT
|
||||
t.yf_ticker,
|
||||
t.name,
|
||||
s.name as sector,
|
||||
z.name as zone
|
||||
FROM tickers t
|
||||
LEFT JOIN sectors s ON s.id = t.sector_id
|
||||
LEFT JOIN zones z ON z.id = t.zone_id
|
||||
WHERE t.yf_ticker IS NOT NULL
|
||||
ORDER BY t.yf_ticker;
|
||||
"""
|
||||
tickers_df = self.db.execute_query(query)
|
||||
self.tickers = {
|
||||
row['yf_ticker']: {
|
||||
'name': row['name'],
|
||||
'sector': row['sector'] if pd.notna(row['sector']) else 'N/A',
|
||||
'zone': row['zone'] if pd.notna(row['zone']) else 'N/A'
|
||||
}
|
||||
for _, row in tickers_df.iterrows()
|
||||
}
|
||||
|
||||
readline.parse_and_bind('tab: complete')
|
||||
readline.set_completer(self._completer)
|
||||
readline.set_completer_delims(' \t\n;')
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Autocomplete setup error: {e}")
|
||||
self.tickers = {}
|
||||
|
||||
def _completer(self, text: str, state: int) -> Optional[str]:
|
||||
try:
|
||||
if self.current_mode == 'stocks':
|
||||
text = text.upper()
|
||||
options = [
|
||||
f"{ticker:<6} - {info['name']:<30} [{info['zone']}] ({info['sector']})"
|
||||
for ticker, info in self.tickers.items()
|
||||
if ticker.startswith(text)
|
||||
]
|
||||
options.sort()
|
||||
return options[state] if state < len(options) else None
|
||||
else:
|
||||
buffer = readline.get_line_buffer()
|
||||
if not buffer or buffer.endswith(' '):
|
||||
options = [cmd for cmd in self.commands if cmd.startswith(text.lower())]
|
||||
return sorted(options)[state] if state < len(options) else None
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Autocomplete error: {e}")
|
||||
return None
|
||||
|
||||
def _handle_stocks_view(self, args=None) -> bool:
|
||||
try:
|
||||
if args and args[0]:
|
||||
ticker = args[0].split(' - ')[0].strip().upper()
|
||||
if ticker in self.tickers:
|
||||
return self.stocks_view.display(ticker)
|
||||
console.print(f"[{self.styles['error']}]Unknown ticker: {ticker}[/]")
|
||||
self._handle_stocks_view()
|
||||
return True
|
||||
|
||||
self.current_mode = 'stocks'
|
||||
self.current_ticker = None
|
||||
console.print(f"\n[{self.styles['info']}]Enter stock ticker (Tab for suggestions)[/]")
|
||||
console.print("[blue]Commands: 'main' for main menu, 'quit' to exit[/]")
|
||||
|
||||
while True:
|
||||
try:
|
||||
prompt = f"Stock ({self.current_ticker})> " if self.current_ticker else "\nStock> "
|
||||
ticker_input = Prompt.ask(prompt, console=console)
|
||||
|
||||
if not ticker_input:
|
||||
continue
|
||||
|
||||
ticker_input = ticker_input.lower()
|
||||
if ticker_input == 'main':
|
||||
self.current_mode = None
|
||||
self.current_ticker = None
|
||||
return self.dashboard.display_welcome_screen()
|
||||
|
||||
if ticker_input == 'quit':
|
||||
if Prompt.ask("Exit terminal?", choices=['yes', 'no'], default='no') == 'yes':
|
||||
return self._handle_quit()
|
||||
continue
|
||||
|
||||
if self.current_ticker:
|
||||
try:
|
||||
insight_num = int(ticker_input)
|
||||
if 1 <= insight_num <= 8:
|
||||
self.handle_insights_command(self.current_ticker, ticker_input)
|
||||
continue
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
ticker = ticker_input.split(' - ')[0].strip().upper()
|
||||
if ticker in self.tickers:
|
||||
self.current_ticker = ticker
|
||||
self.stocks_view.display(ticker)
|
||||
else:
|
||||
console.print(f"[{self.styles['error']}]Unknown ticker: {ticker}[/]")
|
||||
console.print("[blue]Press Tab for suggestions[/]")
|
||||
|
||||
except (KeyboardInterrupt, EOFError):
|
||||
console.print("\n[blue]Use 'main' for main menu or 'quit' to exit[/]")
|
||||
continue
|
||||
except Exception as e:
|
||||
console.print(f"\n[{self.styles['error']}]Error: {str(e)}[/]")
|
||||
continue
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Stocks view error: {e}")
|
||||
self.current_mode = None
|
||||
self.current_ticker = None
|
||||
return True
|
||||
|
||||
def handle_insights_command(self, ticker: str, choice: str) -> bool:
|
||||
try:
|
||||
from modules.signals.stocks_insights import StocksInsights, StocksInsightsView
|
||||
|
||||
insight_num = int(choice)
|
||||
if 1 <= insight_num <= 8:
|
||||
insights = StocksInsights(self.db)
|
||||
view = StocksInsightsView(insights)
|
||||
view.handle_insight_request(ticker, insight_num)
|
||||
return True
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"Insights error: {e}")
|
||||
console.print(f"[{self.styles['error']}]Error displaying analysis: {str(e)}[/]")
|
||||
return False
|
||||
|
||||
def run(self) -> None:
|
||||
console.clear()
|
||||
self.dashboard.display_welcome_screen()
|
||||
|
||||
while True:
|
||||
try:
|
||||
prompt = "Stock> " if self.current_mode == 'stocks' else "Rivaldi> "
|
||||
command_line = Prompt.ask(f"\n{prompt}", console=console)
|
||||
|
||||
if not command_line:
|
||||
continue
|
||||
|
||||
if command_line.lower() == 'quit':
|
||||
if Prompt.ask("Exit terminal?", choices=['yes', 'no'], default='no') == 'yes':
|
||||
if not self.process_command('quit', None):
|
||||
return
|
||||
continue
|
||||
|
||||
parts = command_line.split()
|
||||
if not self.process_command(parts[0].lower(), parts[1:] if len(parts) > 1 else None):
|
||||
if parts[0].lower() == 'quit':
|
||||
return
|
||||
continue
|
||||
|
||||
except KeyboardInterrupt:
|
||||
console.print("\n[blue]Use 'quit' to exit properly[/]")
|
||||
continue
|
||||
except EOFError:
|
||||
console.print("\n[blue]Use 'quit' to exit properly[/]")
|
||||
continue
|
||||
except Exception as e:
|
||||
console.print(f"\n[{self.styles['error']}]Command processing error[/]")
|
||||
continue
|
||||
|
||||
def process_command(self, command: str, args: Optional[List[str]] = None) -> bool:
|
||||
try:
|
||||
if command in self.commands:
|
||||
return self.commands[command](args)
|
||||
|
||||
console.print(f"[{self.styles['error']}]Unknown command: {command}[/]")
|
||||
console.print("[blue]Type 'help' for available commands[/]")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
console.print(f"[{self.styles['error']}]Command error: {str(e)}[/]")
|
||||
console.print("[blue]Type 'help' for available commands[/]")
|
||||
return True
|
||||
|
||||
def _handle_quit(self, _=None) -> bool:
|
||||
try:
|
||||
if self.db:
|
||||
self.db.close()
|
||||
self.db = None
|
||||
console.print(f"\n[{self.styles['success']}]Goodbye![/]")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"Cleanup error: {e}")
|
||||
return True
|
||||
|
||||
def main():
|
||||
console.clear()
|
||||
max_retries = 3
|
||||
retry_count = 0
|
||||
|
||||
while retry_count < max_retries:
|
||||
try:
|
||||
terminal = RivaTerminal()
|
||||
terminal.run()
|
||||
break
|
||||
except Exception as e:
|
||||
logger.error(f"Terminal error: {e}")
|
||||
retry_count += 1
|
||||
if retry_count < max_retries:
|
||||
console.print("\n[yellow]Restarting terminal...[/]")
|
||||
time.sleep(2)
|
||||
else:
|
||||
console.print(f"\n[red]Terminal failed to start after {max_retries} attempts.[/]")
|
||||
continue
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Reference in New Issue
Block a user