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

1#!/usr/bin/env python3 

2""" 

3Live Command Palette for Osiris CLI v4.0 

4Shows menu WHILE typing, updates in real-time 

5""" 

6 

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 

14 

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} 

51 

52 

53class LiveCommandCompleter(Completer): 

54 """ 

55 Minimal live command completer. 

56 Clean, monochrome design with subtle highlights. 

57 """ 

58 

59 def get_completions(self, document, complete_event): 

60 """Generate completions for slash commands""" 

61 text = document.text_before_cursor 

62 

63 # Only show completions if we're typing a slash command 

64 if text.startswith('/'): 

65 search_text = text[1:].lower() 

66 

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 

72 

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 ]) 

81 

82 # Category as metadata 

83 meta_text = FormattedText([ 

84 ('class:category', f'{cat}'), 

85 ]) 

86 

87 yield Completion( 

88 cmd, 

89 start_position=-len(text), 

90 display=display_text, 

91 display_meta=meta_text, 

92 ) 

93 

94 

95# Completely backgroundless minimal style 

96LIVE_STYLE = Style.from_dict({ 

97 # Prompt - subtle cyan 

98 'prompt': '#60a5fa', 

99 

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 

104 

105 # Command display - monochrome with subtle highlights 

106 'command': '#e5e7eb bold', 

107 'separator': '#6b7280', 

108 'description': '#9ca3af', 

109 'category': '#6b7280', 

110 

111 # Scrollbar - minimal, no backgrounds 

112 'scrollbar.background': '#374151', 

113 'scrollbar.button': '#60a5fa', 

114 

115 # Bottom toolbar - no background 

116 'bottom-toolbar': '#9ca3af', 

117}) 

118 

119 

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() 

127 

128 @kb.add('c-c') 

129 def _(event): 

130 """Ctrl+C to exit""" 

131 event.app.exit(result='/exit') 

132 

133 @kb.add('escape') 

134 def _(event): 

135 """Esc to clear input""" 

136 event.current_buffer.text = '' 

137 

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 ) 

147 

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 ) 

160 

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' 

168 

169 

170def search_commands(query: str) -> List[Dict]: 

171 """Search commands by name or description""" 

172 query = query.lower() 

173 results = [] 

174 

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}) 

179 

180 return results 

181 

182 

183# Test function 

184async def test_live_palette(): 

185 """Test the live palette""" 

186 from rich.console import Console 

187 console = Console() 

188 

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") 

191 

192 while True: 

193 user_input = await get_live_input() 

194 

195 if user_input in ['/exit', '/quit']: 

196 console.print("\n[cyan]👋 Goodbye![/cyan]\n") 

197 break 

198 

199 if user_input: 

200 console.print(f"\n[green]✓[/green] You entered: [bold]{user_input}[/bold]\n") 

201 

202 

203if __name__ == "__main__": 

204 import asyncio 

205 asyncio.run(test_live_palette())