Coverage for src / osiris_cli / live_palette.py: 0%
63 statements
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-27 17:41 +0200
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-27 17:41 +0200
1#!/usr/bin/env python3
2"""
3Live Command Palette for Osiris CLI v4.0
4Shows menu WHILE typing, updates in real-time
5"""
7from prompt_toolkit import PromptSession
8from prompt_toolkit.completion import Completer, Completion
9from prompt_toolkit.formatted_text import HTML, FormattedText
10from prompt_toolkit.key_binding import KeyBindings
11from prompt_toolkit.styles import Style
12from prompt_toolkit.filters import Condition
13from typing import Optional, List, Dict
15# Command definitions - minimal, no emojis
16COMMANDS = {
17 "Core": [
18 {"name": "/help", "desc": "Show complete command reference"},
19 {"name": "/setup", "desc": "Configure Osiris (auto or wizard)"},
20 {"name": "/status", "desc": "Show system status"},
21 {"name": "/config", "desc": "Show full configuration"},
22 {"name": "/exit", "desc": "Exit Osiris CLI"},
23 ],
24 "Configuration": [
25 {"name": "/model", "desc": "Change AI model"},
26 {"name": "/provider", "desc": "Switch AI provider"},
27 {"name": "/yolo", "desc": "Toggle autonomous mode"},
28 ],
29 "Memory": [
30 {"name": "/remember", "desc": "Add to memory (key:value)"},
31 {"name": "/forget", "desc": "Remove from memory"},
32 {"name": "/memory", "desc": "Manage agent memory"},
33 {"name": "/init", "desc": "Initialize memory"},
34 {"name": "/skill", "desc": "Teach new skill"},
35 ],
36 "Session": [
37 {"name": "/session", "desc": "Manage sessions"},
38 {"name": "/clear", "desc": "Clear screen"},
39 {"name": "/reset", "desc": "Reset conversation"},
40 ],
41 "Tools": [
42 {"name": "/context", "desc": "Show context files"},
43 {"name": "/tools", "desc": "List all tools"},
44 ],
45 "Advanced": [
46 {"name": "/shadow", "desc": "Toggle shadow mode"},
47 {"name": "/vim", "desc": "Toggle Vim mode"},
48 {"name": "/plan", "desc": "Request execution plan"},
49 ],
50}
53class LiveCommandCompleter(Completer):
54 """
55 Minimal live command completer.
56 Clean, monochrome design with subtle highlights.
57 """
59 def get_completions(self, document, complete_event):
60 """Generate completions for slash commands"""
61 text = document.text_before_cursor
63 # Only show completions if we're typing a slash command
64 if text.startswith('/'):
65 search_text = text[1:].lower()
67 for category, commands in COMMANDS.items():
68 for cmd_info in commands:
69 cmd = cmd_info["name"]
70 desc = cmd_info["desc"]
71 cat = category
73 # Filter: show all if just "/", otherwise match
74 if not search_text or search_text in cmd[1:].lower() or search_text in desc.lower():
75 # Minimal formatted display
76 display_text = FormattedText([
77 ('class:command', f'{cmd:<12}'),
78 ('class:separator', ' '),
79 ('class:description', f'{desc}'),
80 ])
82 # Category as metadata
83 meta_text = FormattedText([
84 ('class:category', f'{cat}'),
85 ])
87 yield Completion(
88 cmd,
89 start_position=-len(text),
90 display=display_text,
91 display_meta=meta_text,
92 )
95# Completely backgroundless minimal style
96LIVE_STYLE = Style.from_dict({
97 # Prompt - subtle cyan
98 'prompt': '#60a5fa',
100 # Completion menu - completely transparent, no backgrounds
101 'completion-menu': '#e5e7eb',
102 'completion-menu.completion': '#9ca3af',
103 'completion-menu.completion.current': '#ffffff bold', # No bg
105 # Command display - monochrome with subtle highlights
106 'command': '#e5e7eb bold',
107 'separator': '#6b7280',
108 'description': '#9ca3af',
109 'category': '#6b7280',
111 # Scrollbar - minimal, no backgrounds
112 'scrollbar.background': '#374151',
113 'scrollbar.button': '#60a5fa',
115 # Bottom toolbar - no background
116 'bottom-toolbar': '#9ca3af',
117})
120async def get_live_input(prompt_symbol: str = ">") -> str:
121 """
122 Get input with minimal live command palette.
123 Clean design, no emojis, subtle colors.
124 """
125 # Create key bindings
126 kb = KeyBindings()
128 @kb.add('c-c')
129 def _(event):
130 """Ctrl+C to exit"""
131 event.app.exit(result='/exit')
133 @kb.add('escape')
134 def _(event):
135 """Esc to clear input"""
136 event.current_buffer.text = ''
138 # Minimal bottom toolbar
139 def bottom_toolbar():
140 return HTML(
141 '<b>/</b> commands | '
142 '<b>↑↓</b> navigate | '
143 '<b>Enter</b> select | '
144 '<b>Esc</b> clear | '
145 '<b>Ctrl+C</b> exit'
146 )
148 # Create prompt session
149 session = PromptSession(
150 completer=LiveCommandCompleter(),
151 complete_while_typing=True,
152 complete_in_thread=False,
153 mouse_support=True,
154 style=LIVE_STYLE,
155 key_bindings=kb,
156 bottom_toolbar=bottom_toolbar,
157 complete_style='COLUMN',
158 reserve_space_for_menu=8,
159 )
161 # Get input
162 try:
163 prompt_text = HTML(f'<prompt>{prompt_symbol}</prompt> ')
164 result = await session.prompt_async(prompt_text)
165 return result.strip()
166 except (KeyboardInterrupt, EOFError):
167 return '/exit'
170def search_commands(query: str) -> List[Dict]:
171 """Search commands by name or description"""
172 query = query.lower()
173 results = []
175 for category, commands in COMMANDS.items():
176 for cmd in commands:
177 if query in cmd["name"].lower() or query in cmd["desc"].lower():
178 results.append({**cmd, "category": category})
180 return results
183# Test function
184async def test_live_palette():
185 """Test the live palette"""
186 from rich.console import Console
187 console = Console()
189 console.print("\n[bold cyan]🎨 Osiris Live Command Palette[/bold cyan]")
190 console.print("[dim]Type / and watch the menu appear in real-time![/dim]\n")
192 while True:
193 user_input = await get_live_input()
195 if user_input in ['/exit', '/quit']:
196 console.print("\n[cyan]👋 Goodbye![/cyan]\n")
197 break
199 if user_input:
200 console.print(f"\n[green]✓[/green] You entered: [bold]{user_input}[/bold]\n")
203if __name__ == "__main__":
204 import asyncio
205 asyncio.run(test_live_palette())