feat: Add lightweight TUI for cognee CLI

- Implement interactive Terminal User Interface using Textual
- Add main TUI application with keyboard navigation
- Create home screen with main menu
- Add placeholder screens for Context, Query, and Settings
- Integrate TUI command into existing CLI structure
- Support keyboard shortcuts (q=quit, ?=help, d=toggle dark mode)

Closes #1762
This commit is contained in:
Gnanasaikiran 2025-11-15 13:37:22 +05:30
parent 487635b71b
commit 2db9d1ee03
10 changed files with 319 additions and 0 deletions

View file

@ -92,6 +92,7 @@ def _discover_commands() -> List[Type[SupportsCliCommand]]:
("cognee.cli.commands.cognify_command", "CognifyCommand"),
("cognee.cli.commands.delete_command", "DeleteCommand"),
("cognee.cli.commands.config_command", "ConfigCommand"),
("cognee.cli.commands.tui_command", "TuiCommand"),
]
for module_path, class_name in command_modules:

View file

@ -0,0 +1,54 @@
import argparse
from cognee.cli.reference import SupportsCliCommand
from cognee.cli import DEFAULT_DOCS_URL
import cognee.cli.echo as fmt
from cognee.cli.exceptions import CliCommandException
class TuiCommand(SupportsCliCommand):
command_string = "tui"
help_string = "Launch interactive Terminal User Interface"
docs_url = DEFAULT_DOCS_URL
description = """
Launch the Cognee Terminal User Interface (TUI).
The TUI provides an interactive, text-based interface for managing your
knowledge graphs with features like:
- **Context Management**: Add and manage data sources
- **Search & Query**: Interactive knowledge graph querying
- **Settings**: Configure API keys and models
- **Live Updates**: Real-time status and progress indicators
The TUI is keyboard-driven and supports:
- Arrow key navigation
- Keyboard shortcuts (h=Home, c=Context, s=Search, etc.)
- Tab completion for inputs
Perfect for managing Cognee from the terminal or SSH sessions!
"""
def configure_parser(self, parser: argparse.ArgumentParser) -> None:
parser.add_argument(
"--no-mouse",
action="store_true",
help="Disable mouse support (keyboard only mode)",
)
def execute(self, args: argparse.Namespace) -> None:
try:
fmt.echo("Starting Cognee TUI...")
fmt.note("Press 'q' to quit, '?' for help")
# Import and run TUI
from cognee.cli.tui import run_tui
run_tui()
except KeyboardInterrupt:
fmt.note("\nTUI closed by user")
except Exception as e:
raise CliCommandException(
f"Failed to start TUI: {str(e)}",
error_code=1
) from e

View file

@ -0,0 +1,4 @@
"""Cognee TUI - Terminal User Interface"""
from cognee.cli.tui.app import run_tui, CogneeTUI
__all__ = ["run_tui", "CogneeTUI"]

124
cognee/cli/tui/app.py Normal file
View file

@ -0,0 +1,124 @@
"""
Cognee TUI - Main Application
Text-based User Interface for managing Cognee knowledge graphs
"""
from textual.app import App, ComposeResult
from textual.binding import Binding
from textual.widgets import Header, Footer
from textual.screen import Screen
from cognee.cli.tui.screens.home import HomeScreen
class CogneeTUI(App):
"""Cognee Terminal User Interface Application"""
CSS = """
Screen {
background: $surface;
}
.box {
border: solid $primary;
background: $panel;
padding: 1 2;
margin: 1;
}
Button {
margin: 1 2;
min-width: 30;
}
Button:hover {
background: $primary;
}
#menu-container {
width: 60;
height: auto;
border: heavy $primary;
background: $panel;
padding: 2;
}
.title {
text-align: center;
text-style: bold;
color: $accent;
padding: 1;
}
.center {
align: center middle;
}
"""
TITLE = "Cognee TUI - Knowledge Graph Manager"
SUB_TITLE = "Navigate with arrow keys • Press ? for help"
BINDINGS = [
Binding("q", "quit", "Quit", priority=True),
Binding("?", "help", "Help"),
Binding("d", "toggle_dark", "Toggle Dark Mode"),
]
def on_mount(self) -> None:
"""Initialize the app with the home screen"""
self.push_screen(HomeScreen())
def action_help(self) -> None:
"""Show help information"""
help_text = """
# Cognee TUI Help
## Navigation
- Arrow Keys: Navigate between UI elements
- Enter: Select/activate items
- Tab: Move to next field
- Esc: Go back
## Keyboard Shortcuts
- q: Quit application
- d: Toggle dark/light mode
- ?: Show this help
## Workflow
1. Add Context: Add data sources
2. Cognify: Process data
3. Search: Query knowledge graph
4. Settings: Configure API keys
"""
self.push_screen(HelpScreen(help_text))
class HelpScreen(Screen):
"""Help screen"""
def __init__(self, help_text: str):
super().__init__()
self.help_text = help_text
def compose(self) -> ComposeResult:
from textual.widgets import Static, Button
from textual.containers import VerticalScroll
yield Header()
with VerticalScroll():
yield Static(self.help_text, markup=False)
yield Button("Close (Esc)", id="close", variant="primary")
yield Footer()
def on_button_pressed(self, event) -> None:
if event.button.id == "close":
self.app.pop_screen()
def on_key(self, event) -> None:
if event.key == "escape":
self.app.pop_screen()
def run_tui():
"""Entry point to run the TUI application"""
app = CogneeTUI()
app.run()

View file

@ -0,0 +1,7 @@
"""Screens for Cognee TUI"""
from cognee.cli.tui.screens.home import HomeScreen
from cognee.cli.tui.screens.context import ContextScreen
from cognee.cli.tui.screens.query import QueryScreen
from cognee.cli.tui.screens.settings import SettingsScreen
__all__ = ["HomeScreen", "ContextScreen", "QueryScreen", "SettingsScreen"]

View file

@ -0,0 +1,26 @@
"""Context Management Screen"""
from textual.screen import Screen
from textual.app import ComposeResult
from textual.widgets import Header, Footer, Button, Static
from textual.containers import Container
from textual.binding import Binding
class ContextScreen(Screen):
"""Context management screen"""
BINDINGS = [Binding("escape", "back", "Back")]
def compose(self) -> ComposeResult:
yield Header()
with Container():
yield Static("[bold]📁 Context Management[/bold]\n", classes="title")
yield Static("Context management features coming soon!")
yield Button("← Back", id="back_btn")
yield Footer()
def on_button_pressed(self, event) -> None:
self.app.pop_screen()
def action_back(self) -> None:
self.app.pop_screen()

View file

@ -0,0 +1,48 @@
"""Home Screen for Cognee TUI"""
from textual.screen import Screen
from textual.app import ComposeResult
from textual.widgets import Header, Footer, Button, Static
from textual.containers import Container, Vertical
class HomeScreen(Screen):
"""Main dashboard screen"""
def compose(self) -> ComposeResult:
yield Header()
with Container(id="menu-container", classes="center"):
yield Static("[bold cyan]🧠 Cognee Knowledge Graph Manager[/bold cyan]", classes="title")
yield Static("\nManage your AI memory and knowledge graphs\n", classes="center")
with Vertical():
yield Button("📁 Manage Context", id="context", variant="primary")
yield Button("🔍 Search & Query", id="query", variant="success")
yield Button("⚙️ Settings", id="settings", variant="default")
yield Button("❓ Help", id="help", variant="default")
yield Button("🚪 Exit", id="exit", variant="error")
yield Static("\n[dim]Use arrow keys • Enter to select[/dim]", classes="center")
yield Footer()
def on_button_pressed(self, event: Button.Pressed) -> None:
button_id = event.button.id
if button_id == "context":
from cognee.cli.tui.screens.context import ContextScreen
self.app.push_screen(ContextScreen())
elif button_id == "query":
from cognee.cli.tui.screens.query import QueryScreen
self.app.push_screen(QueryScreen())
elif button_id == "settings":
from cognee.cli.tui.screens.settings import SettingsScreen
self.app.push_screen(SettingsScreen())
elif button_id == "help":
self.app.action_help()
elif button_id == "exit":
self.app.exit()

View file

@ -0,0 +1,26 @@
"""Query Screen"""
from textual.screen import Screen
from textual.app import ComposeResult
from textual.widgets import Header, Footer, Button, Static
from textual.containers import Container
from textual.binding import Binding
class QueryScreen(Screen):
"""Query screen"""
BINDINGS = [Binding("escape", "back", "Back")]
def compose(self) -> ComposeResult:
yield Header()
with Container():
yield Static("[bold]🔍 Search & Query[/bold]\n", classes="title")
yield Static("Search features coming soon!")
yield Button("← Back", id="back_btn")
yield Footer()
def on_button_pressed(self, event) -> None:
self.app.pop_screen()
def action_back(self) -> None:
self.app.pop_screen()

View file

@ -0,0 +1,26 @@
"""Settings Screen"""
from textual.screen import Screen
from textual.app import ComposeResult
from textual.widgets import Header, Footer, Button, Static
from textual.containers import Container
from textual.binding import Binding
class SettingsScreen(Screen):
"""Settings screen"""
BINDINGS = [Binding("escape", "back", "Back")]
def compose(self) -> ComposeResult:
yield Header()
with Container():
yield Static("[bold]⚙️ Settings[/bold]\n", classes="title")
yield Static("Settings features coming soon!")
yield Button("← Back", id="back_btn")
yield Footer()
def on_button_pressed(self, event) -> None:
self.app.pop_screen()
def action_back(self) -> None:
self.app.pop_screen()

View file

@ -0,0 +1,3 @@
"""Custom widgets for Cognee TUI"""
# Custom widgets can be added here as needed
__all__ = []