omnimancer-cli 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- omnimancer/__init__.py +42 -0
- omnimancer/__main__.py +9 -0
- omnimancer/cli/__init__.py +8 -0
- omnimancer/cli/agent_manager.py +826 -0
- omnimancer/cli/agent_wizard.py +944 -0
- omnimancer/cli/approval_formatter.py +734 -0
- omnimancer/cli/approval_integration.py +734 -0
- omnimancer/cli/approval_prompt.py +1286 -0
- omnimancer/cli/batch_approval_display.py +617 -0
- omnimancer/cli/batch_approval_filters.py +729 -0
- omnimancer/cli/batch_operation_monitor.py +775 -0
- omnimancer/cli/commands/performance_cmd.py +624 -0
- omnimancer/cli/commands.py +757 -0
- omnimancer/cli/completion_handler.py +281 -0
- omnimancer/cli/config_setup_wizard.py +783 -0
- omnimancer/cli/dynamic_commands.py +371 -0
- omnimancer/cli/handlers/__init__.py +18 -0
- omnimancer/cli/handlers/agent_handler.py +865 -0
- omnimancer/cli/handlers/agent_persona_handler.py +1048 -0
- omnimancer/cli/handlers/custom_agent_handler.py +352 -0
- omnimancer/cli/handlers/permissions_handler.py +1609 -0
- omnimancer/cli/interface.py +6695 -0
- omnimancer/cli/performance_dashboard.py +612 -0
- omnimancer/core/__init__.py +34 -0
- omnimancer/core/agent/__init__.py +256 -0
- omnimancer/core/agent/agent_config.py +715 -0
- omnimancer/core/agent/agent_switcher.py +668 -0
- omnimancer/core/agent/approval_context.py +460 -0
- omnimancer/core/agent/approval_dialog.py +902 -0
- omnimancer/core/agent/approval_interface.py +628 -0
- omnimancer/core/agent/approval_manager.py +760 -0
- omnimancer/core/agent/cli_integration.py +470 -0
- omnimancer/core/agent/config.py +845 -0
- omnimancer/core/agent/custom_agent_integration.py +654 -0
- omnimancer/core/agent/diff_renderer.py +706 -0
- omnimancer/core/agent/file_content_display.py +503 -0
- omnimancer/core/agent/file_modification_workflow.py +658 -0
- omnimancer/core/agent/file_system_manager.py +1790 -0
- omnimancer/core/agent/input_handler.py +614 -0
- omnimancer/core/agent/metrics_collector.py +771 -0
- omnimancer/core/agent/optimization_engine.py +729 -0
- omnimancer/core/agent/performance_monitor.py +717 -0
- omnimancer/core/agent/persona.py +870 -0
- omnimancer/core/agent/persona_validator.py +672 -0
- omnimancer/core/agent/program_executor.py +748 -0
- omnimancer/core/agent/proposed_changes_integration.py +663 -0
- omnimancer/core/agent/read_before_write_errors.py +492 -0
- omnimancer/core/agent/read_before_write_ui.py +647 -0
- omnimancer/core/agent/rich_renderer.py +763 -0
- omnimancer/core/agent/status_config.py +238 -0
- omnimancer/core/agent/status_core.py +446 -0
- omnimancer/core/agent/status_integrated_engine.py +540 -0
- omnimancer/core/agent/status_manager.py +878 -0
- omnimancer/core/agent/status_ui_components.py +535 -0
- omnimancer/core/agent/template_applicator.py +548 -0
- omnimancer/core/agent/terminal_status_listener.py +394 -0
- omnimancer/core/agent/token_tracker.py +679 -0
- omnimancer/core/agent/types.py +60 -0
- omnimancer/core/agent/web_client.py +745 -0
- omnimancer/core/agent/workflow_orchestrator.py +826 -0
- omnimancer/core/agent_engine.py +1625 -0
- omnimancer/core/agent_mode_manager.py +706 -0
- omnimancer/core/agent_progress_ui.py +545 -0
- omnimancer/core/chat_manager.py +141 -0
- omnimancer/core/config_manager.py +2199 -0
- omnimancer/core/config_migration.py +772 -0
- omnimancer/core/config_provider.py +1974 -0
- omnimancer/core/config_security_validator.py +393 -0
- omnimancer/core/config_validator.py +768 -0
- omnimancer/core/conversation_manager.py +352 -0
- omnimancer/core/engine.py +787 -0
- omnimancer/core/enhanced_input.py +523 -0
- omnimancer/core/env_loader.py +83 -0
- omnimancer/core/fallback_manager.py +568 -0
- omnimancer/core/health_monitor.py +462 -0
- omnimancer/core/history_manager.py +462 -0
- omnimancer/core/mcp_integration_layer.py +878 -0
- omnimancer/core/models.py +1889 -0
- omnimancer/core/provider_capabilities.py +403 -0
- omnimancer/core/provider_initializer.py +450 -0
- omnimancer/core/provider_registry.py +432 -0
- omnimancer/core/security/__init__.py +16 -0
- omnimancer/core/security/approval_workflow.py +406 -0
- omnimancer/core/security/audit_logger.py +511 -0
- omnimancer/core/security/permission_controller.py +511 -0
- omnimancer/core/security/sandbox_manager.py +379 -0
- omnimancer/core/security/security_manager.py +530 -0
- omnimancer/core/setup_wizard.py +345 -0
- omnimancer/core/setup_wizard_core.py +449 -0
- omnimancer/core/setup_wizard_guide_generator.py +376 -0
- omnimancer/core/setup_wizard_provider_setup.py +454 -0
- omnimancer/core/setup_wizard_ui.py +317 -0
- omnimancer/core/setup_wizard_validation.py +371 -0
- omnimancer/core/signal_handler.py +174 -0
- omnimancer/mcp/__init__.py +11 -0
- omnimancer/mcp/client.py +725 -0
- omnimancer/mcp/manager.py +649 -0
- omnimancer/providers/__init__.py +24 -0
- omnimancer/providers/azure.py +608 -0
- omnimancer/providers/base.py +262 -0
- omnimancer/providers/bedrock.py +711 -0
- omnimancer/providers/claude.py +426 -0
- omnimancer/providers/claude_code.py +406 -0
- omnimancer/providers/cohere.py +412 -0
- omnimancer/providers/factory.py +988 -0
- omnimancer/providers/gemini.py +708 -0
- omnimancer/providers/mistral.py +554 -0
- omnimancer/providers/ollama.py +510 -0
- omnimancer/providers/openai.py +362 -0
- omnimancer/providers/openrouter.py +709 -0
- omnimancer/providers/perplexity.py +476 -0
- omnimancer/providers/vertex.py +617 -0
- omnimancer/providers/xai.py +484 -0
- omnimancer/ui/__init__.py +6 -0
- omnimancer/ui/cancellation_handler.py +235 -0
- omnimancer/ui/progress_indicator.py +318 -0
- omnimancer/utils/__init__.py +22 -0
- omnimancer/utils/error_handler.py +589 -0
- omnimancer/utils/errors.py +667 -0
- omnimancer/utils/retry.py +238 -0
- omnimancer_cli-0.1.0.dist-info/METADATA +413 -0
- omnimancer_cli-0.1.0.dist-info/RECORD +126 -0
- omnimancer_cli-0.1.0.dist-info/WHEEL +5 -0
- omnimancer_cli-0.1.0.dist-info/entry_points.txt +4 -0
- omnimancer_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
- omnimancer_cli-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Auto-completion handler for Omnimancer CLI commands.
|
|
3
|
+
|
|
4
|
+
This module provides readline-based auto-completion for slash commands,
|
|
5
|
+
including dynamic commands loaded from ~/.omnimancer/commands directory.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import readline
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Dict, List, Optional, Set
|
|
12
|
+
|
|
13
|
+
from .commands import SlashCommand
|
|
14
|
+
from .dynamic_commands import CommandRegistry, DynamicCommand
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CompletionHandler:
|
|
20
|
+
"""Handles auto-completion for Omnimancer CLI commands."""
|
|
21
|
+
|
|
22
|
+
def __init__(self):
|
|
23
|
+
"""Initialize the completion handler."""
|
|
24
|
+
self.command_registry = CommandRegistry()
|
|
25
|
+
self.builtin_commands: Set[str] = set()
|
|
26
|
+
self.dynamic_commands: Dict[str, DynamicCommand] = {}
|
|
27
|
+
self._load_commands()
|
|
28
|
+
|
|
29
|
+
# Set up readline completion
|
|
30
|
+
self._setup_readline()
|
|
31
|
+
|
|
32
|
+
def _load_commands(self) -> None:
|
|
33
|
+
"""Load both built-in and dynamic commands."""
|
|
34
|
+
# Load built-in commands
|
|
35
|
+
self._load_builtin_commands()
|
|
36
|
+
|
|
37
|
+
# Load dynamic commands from ~/.omnimancer/commands
|
|
38
|
+
self._load_dynamic_commands()
|
|
39
|
+
|
|
40
|
+
def _load_builtin_commands(self) -> None:
|
|
41
|
+
"""Load built-in slash commands."""
|
|
42
|
+
try:
|
|
43
|
+
# Get all built-in slash commands
|
|
44
|
+
for cmd in SlashCommand:
|
|
45
|
+
# cmd.value already includes the slash, don't add another one
|
|
46
|
+
self.builtin_commands.add(cmd.value)
|
|
47
|
+
|
|
48
|
+
logger.debug(f"Loaded {len(self.builtin_commands)} built-in commands")
|
|
49
|
+
except Exception as e:
|
|
50
|
+
logger.error(f"Failed to load built-in commands: {e}")
|
|
51
|
+
|
|
52
|
+
def _load_dynamic_commands(self) -> None:
|
|
53
|
+
"""Load dynamic commands from ~/.omnimancer/commands directory."""
|
|
54
|
+
try:
|
|
55
|
+
# Use the default commands directory (~/.omnimancer/commands)
|
|
56
|
+
commands_dir = Path.home() / ".omnimancer" / "commands"
|
|
57
|
+
|
|
58
|
+
if not commands_dir.exists():
|
|
59
|
+
logger.debug(f"Commands directory does not exist: {commands_dir}")
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
# Set built-in commands to prevent overriding
|
|
63
|
+
builtin_names = [cmd.lstrip("/") for cmd in self.builtin_commands]
|
|
64
|
+
self.command_registry.set_builtin_commands(builtin_names)
|
|
65
|
+
|
|
66
|
+
# Load commands from directory
|
|
67
|
+
loaded_count = self.command_registry.load_commands_from_directory(
|
|
68
|
+
commands_dir
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# Update our dynamic commands cache
|
|
72
|
+
self.dynamic_commands = self.command_registry.commands.copy()
|
|
73
|
+
|
|
74
|
+
logger.info(f"Loaded {loaded_count} dynamic commands from {commands_dir}")
|
|
75
|
+
|
|
76
|
+
except Exception as e:
|
|
77
|
+
logger.error(f"Failed to load dynamic commands: {e}")
|
|
78
|
+
|
|
79
|
+
def _setup_readline(self) -> None:
|
|
80
|
+
"""Set up readline completion."""
|
|
81
|
+
try:
|
|
82
|
+
# Set the completer function
|
|
83
|
+
readline.set_completer(self.complete)
|
|
84
|
+
|
|
85
|
+
# Set completion display matches function for better formatting
|
|
86
|
+
readline.set_completion_display_matches_hook(self._display_matches)
|
|
87
|
+
|
|
88
|
+
# Use tab for completion
|
|
89
|
+
readline.parse_and_bind("tab: complete")
|
|
90
|
+
|
|
91
|
+
# Enable history and completion
|
|
92
|
+
readline.parse_and_bind("set show-all-if-ambiguous on")
|
|
93
|
+
readline.parse_and_bind("set completion-ignore-case on")
|
|
94
|
+
|
|
95
|
+
logger.debug("Readline completion set up successfully")
|
|
96
|
+
|
|
97
|
+
except Exception as e:
|
|
98
|
+
logger.error(f"Failed to set up readline completion: {e}")
|
|
99
|
+
|
|
100
|
+
def complete(self, text: str, state: int) -> Optional[str]:
|
|
101
|
+
"""
|
|
102
|
+
Readline completion function.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
text: The text being completed
|
|
106
|
+
state: The completion state (0 for first call, incrementing)
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
The next completion option, or None when done
|
|
110
|
+
"""
|
|
111
|
+
try:
|
|
112
|
+
if state == 0:
|
|
113
|
+
# First call - generate all possible completions
|
|
114
|
+
line = readline.get_line_buffer()
|
|
115
|
+
self._completions = self._get_completions(line, text)
|
|
116
|
+
|
|
117
|
+
# Return the next completion
|
|
118
|
+
if state < len(self._completions):
|
|
119
|
+
return self._completions[state]
|
|
120
|
+
else:
|
|
121
|
+
return None
|
|
122
|
+
|
|
123
|
+
except Exception as e:
|
|
124
|
+
logger.error(f"Completion error: {e}")
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
def _get_completions(self, line: str, text: str) -> List[str]:
|
|
128
|
+
"""
|
|
129
|
+
Get all possible completions for the given text.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
line: The full input line
|
|
133
|
+
text: The text being completed
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
List of completion options
|
|
137
|
+
"""
|
|
138
|
+
completions = []
|
|
139
|
+
|
|
140
|
+
# Check if we're completing a slash command
|
|
141
|
+
if line.strip().startswith("/") or text.startswith("/"):
|
|
142
|
+
completions.extend(self._get_command_completions(text))
|
|
143
|
+
|
|
144
|
+
# If no slash commands match and text doesn't start with '/',
|
|
145
|
+
# still show commands if user types '/'
|
|
146
|
+
elif text == "/" or (len(text) == 0 and line.strip() == ""):
|
|
147
|
+
# User typed just '/' - show all commands
|
|
148
|
+
completions.extend(self._get_all_commands())
|
|
149
|
+
|
|
150
|
+
return completions
|
|
151
|
+
|
|
152
|
+
def _get_command_completions(self, text: str) -> List[str]:
|
|
153
|
+
"""
|
|
154
|
+
Get command completions for the given text.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
text: The text being completed (may or may not start with '/')
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
List of matching commands
|
|
161
|
+
"""
|
|
162
|
+
# Ensure text starts with '/' for matching
|
|
163
|
+
if not text.startswith("/"):
|
|
164
|
+
text = "/" + text
|
|
165
|
+
|
|
166
|
+
completions = []
|
|
167
|
+
|
|
168
|
+
# Match built-in commands
|
|
169
|
+
for cmd in self.builtin_commands:
|
|
170
|
+
if cmd.startswith(text):
|
|
171
|
+
completions.append(cmd)
|
|
172
|
+
|
|
173
|
+
# Match dynamic commands
|
|
174
|
+
for cmd_name, cmd in self.dynamic_commands.items():
|
|
175
|
+
slash_name = f"/{cmd_name}"
|
|
176
|
+
if slash_name.startswith(text):
|
|
177
|
+
completions.append(slash_name)
|
|
178
|
+
|
|
179
|
+
return sorted(completions)
|
|
180
|
+
|
|
181
|
+
def _get_all_commands(self) -> List[str]:
|
|
182
|
+
"""Get all available commands."""
|
|
183
|
+
all_commands = []
|
|
184
|
+
|
|
185
|
+
# Add built-in commands
|
|
186
|
+
all_commands.extend(self.builtin_commands)
|
|
187
|
+
|
|
188
|
+
# Add dynamic commands
|
|
189
|
+
for cmd_name in self.dynamic_commands:
|
|
190
|
+
all_commands.append(f"/{cmd_name}")
|
|
191
|
+
|
|
192
|
+
return sorted(all_commands)
|
|
193
|
+
|
|
194
|
+
def _display_matches(
|
|
195
|
+
self, substitution: str, matches: List[str], longest_match_length: int
|
|
196
|
+
) -> None:
|
|
197
|
+
"""
|
|
198
|
+
Custom display function for completion matches.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
substitution: The substitution text
|
|
202
|
+
matches: List of matches
|
|
203
|
+
longest_match_length: Length of longest match
|
|
204
|
+
"""
|
|
205
|
+
try:
|
|
206
|
+
print("\nAvailable commands:")
|
|
207
|
+
|
|
208
|
+
# Group matches by type
|
|
209
|
+
builtin_matches = []
|
|
210
|
+
dynamic_matches = []
|
|
211
|
+
|
|
212
|
+
for match in matches:
|
|
213
|
+
if match in self.builtin_commands:
|
|
214
|
+
builtin_matches.append(match)
|
|
215
|
+
else:
|
|
216
|
+
dynamic_matches.append(match)
|
|
217
|
+
|
|
218
|
+
# Display built-in commands
|
|
219
|
+
if builtin_matches:
|
|
220
|
+
print(" Built-in commands:")
|
|
221
|
+
for match in builtin_matches:
|
|
222
|
+
print(f" {match}")
|
|
223
|
+
|
|
224
|
+
# Display dynamic commands with descriptions
|
|
225
|
+
if dynamic_matches:
|
|
226
|
+
print(" Custom commands:")
|
|
227
|
+
for match in dynamic_matches:
|
|
228
|
+
cmd_name = match.lstrip("/")
|
|
229
|
+
cmd = self.dynamic_commands.get(cmd_name)
|
|
230
|
+
if cmd and cmd.description:
|
|
231
|
+
print(f" {match} - {cmd.description}")
|
|
232
|
+
else:
|
|
233
|
+
print(f" {match}")
|
|
234
|
+
|
|
235
|
+
# Redisplay the prompt
|
|
236
|
+
print(">>> ", end="", flush=True)
|
|
237
|
+
|
|
238
|
+
except Exception as e:
|
|
239
|
+
logger.error(f"Error displaying matches: {e}")
|
|
240
|
+
|
|
241
|
+
def reload_commands(self) -> None:
|
|
242
|
+
"""Reload all commands (useful for development/testing)."""
|
|
243
|
+
logger.info("Reloading commands...")
|
|
244
|
+
self.dynamic_commands.clear()
|
|
245
|
+
self._load_commands()
|
|
246
|
+
|
|
247
|
+
def get_command_help(self, command_name: str) -> Optional[str]:
|
|
248
|
+
"""
|
|
249
|
+
Get help text for a specific command.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
command_name: The command name (with or without slash)
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
Help text or None if command not found
|
|
256
|
+
"""
|
|
257
|
+
# Remove leading slash if present
|
|
258
|
+
if command_name.startswith("/"):
|
|
259
|
+
command_name = command_name[1:]
|
|
260
|
+
|
|
261
|
+
# Check dynamic commands first
|
|
262
|
+
if command_name in self.dynamic_commands:
|
|
263
|
+
cmd = self.dynamic_commands[command_name]
|
|
264
|
+
help_text = f"/{command_name}"
|
|
265
|
+
if cmd.description:
|
|
266
|
+
help_text += f" - {cmd.description}"
|
|
267
|
+
|
|
268
|
+
# Add argument information
|
|
269
|
+
if cmd.arguments:
|
|
270
|
+
help_text += "\n Arguments:"
|
|
271
|
+
for arg in cmd.arguments:
|
|
272
|
+
arg_name = arg.get("name", "arg")
|
|
273
|
+
arg_type = arg.get("type", "string")
|
|
274
|
+
arg_desc = arg.get("description", "")
|
|
275
|
+
help_text += f"\n {arg_name} ({arg_type})"
|
|
276
|
+
if arg_desc:
|
|
277
|
+
help_text += f" - {arg_desc}"
|
|
278
|
+
|
|
279
|
+
return help_text
|
|
280
|
+
|
|
281
|
+
return None
|