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.
Files changed (126) hide show
  1. omnimancer/__init__.py +42 -0
  2. omnimancer/__main__.py +9 -0
  3. omnimancer/cli/__init__.py +8 -0
  4. omnimancer/cli/agent_manager.py +826 -0
  5. omnimancer/cli/agent_wizard.py +944 -0
  6. omnimancer/cli/approval_formatter.py +734 -0
  7. omnimancer/cli/approval_integration.py +734 -0
  8. omnimancer/cli/approval_prompt.py +1286 -0
  9. omnimancer/cli/batch_approval_display.py +617 -0
  10. omnimancer/cli/batch_approval_filters.py +729 -0
  11. omnimancer/cli/batch_operation_monitor.py +775 -0
  12. omnimancer/cli/commands/performance_cmd.py +624 -0
  13. omnimancer/cli/commands.py +757 -0
  14. omnimancer/cli/completion_handler.py +281 -0
  15. omnimancer/cli/config_setup_wizard.py +783 -0
  16. omnimancer/cli/dynamic_commands.py +371 -0
  17. omnimancer/cli/handlers/__init__.py +18 -0
  18. omnimancer/cli/handlers/agent_handler.py +865 -0
  19. omnimancer/cli/handlers/agent_persona_handler.py +1048 -0
  20. omnimancer/cli/handlers/custom_agent_handler.py +352 -0
  21. omnimancer/cli/handlers/permissions_handler.py +1609 -0
  22. omnimancer/cli/interface.py +6695 -0
  23. omnimancer/cli/performance_dashboard.py +612 -0
  24. omnimancer/core/__init__.py +34 -0
  25. omnimancer/core/agent/__init__.py +256 -0
  26. omnimancer/core/agent/agent_config.py +715 -0
  27. omnimancer/core/agent/agent_switcher.py +668 -0
  28. omnimancer/core/agent/approval_context.py +460 -0
  29. omnimancer/core/agent/approval_dialog.py +902 -0
  30. omnimancer/core/agent/approval_interface.py +628 -0
  31. omnimancer/core/agent/approval_manager.py +760 -0
  32. omnimancer/core/agent/cli_integration.py +470 -0
  33. omnimancer/core/agent/config.py +845 -0
  34. omnimancer/core/agent/custom_agent_integration.py +654 -0
  35. omnimancer/core/agent/diff_renderer.py +706 -0
  36. omnimancer/core/agent/file_content_display.py +503 -0
  37. omnimancer/core/agent/file_modification_workflow.py +658 -0
  38. omnimancer/core/agent/file_system_manager.py +1790 -0
  39. omnimancer/core/agent/input_handler.py +614 -0
  40. omnimancer/core/agent/metrics_collector.py +771 -0
  41. omnimancer/core/agent/optimization_engine.py +729 -0
  42. omnimancer/core/agent/performance_monitor.py +717 -0
  43. omnimancer/core/agent/persona.py +870 -0
  44. omnimancer/core/agent/persona_validator.py +672 -0
  45. omnimancer/core/agent/program_executor.py +748 -0
  46. omnimancer/core/agent/proposed_changes_integration.py +663 -0
  47. omnimancer/core/agent/read_before_write_errors.py +492 -0
  48. omnimancer/core/agent/read_before_write_ui.py +647 -0
  49. omnimancer/core/agent/rich_renderer.py +763 -0
  50. omnimancer/core/agent/status_config.py +238 -0
  51. omnimancer/core/agent/status_core.py +446 -0
  52. omnimancer/core/agent/status_integrated_engine.py +540 -0
  53. omnimancer/core/agent/status_manager.py +878 -0
  54. omnimancer/core/agent/status_ui_components.py +535 -0
  55. omnimancer/core/agent/template_applicator.py +548 -0
  56. omnimancer/core/agent/terminal_status_listener.py +394 -0
  57. omnimancer/core/agent/token_tracker.py +679 -0
  58. omnimancer/core/agent/types.py +60 -0
  59. omnimancer/core/agent/web_client.py +745 -0
  60. omnimancer/core/agent/workflow_orchestrator.py +826 -0
  61. omnimancer/core/agent_engine.py +1625 -0
  62. omnimancer/core/agent_mode_manager.py +706 -0
  63. omnimancer/core/agent_progress_ui.py +545 -0
  64. omnimancer/core/chat_manager.py +141 -0
  65. omnimancer/core/config_manager.py +2199 -0
  66. omnimancer/core/config_migration.py +772 -0
  67. omnimancer/core/config_provider.py +1974 -0
  68. omnimancer/core/config_security_validator.py +393 -0
  69. omnimancer/core/config_validator.py +768 -0
  70. omnimancer/core/conversation_manager.py +352 -0
  71. omnimancer/core/engine.py +787 -0
  72. omnimancer/core/enhanced_input.py +523 -0
  73. omnimancer/core/env_loader.py +83 -0
  74. omnimancer/core/fallback_manager.py +568 -0
  75. omnimancer/core/health_monitor.py +462 -0
  76. omnimancer/core/history_manager.py +462 -0
  77. omnimancer/core/mcp_integration_layer.py +878 -0
  78. omnimancer/core/models.py +1889 -0
  79. omnimancer/core/provider_capabilities.py +403 -0
  80. omnimancer/core/provider_initializer.py +450 -0
  81. omnimancer/core/provider_registry.py +432 -0
  82. omnimancer/core/security/__init__.py +16 -0
  83. omnimancer/core/security/approval_workflow.py +406 -0
  84. omnimancer/core/security/audit_logger.py +511 -0
  85. omnimancer/core/security/permission_controller.py +511 -0
  86. omnimancer/core/security/sandbox_manager.py +379 -0
  87. omnimancer/core/security/security_manager.py +530 -0
  88. omnimancer/core/setup_wizard.py +345 -0
  89. omnimancer/core/setup_wizard_core.py +449 -0
  90. omnimancer/core/setup_wizard_guide_generator.py +376 -0
  91. omnimancer/core/setup_wizard_provider_setup.py +454 -0
  92. omnimancer/core/setup_wizard_ui.py +317 -0
  93. omnimancer/core/setup_wizard_validation.py +371 -0
  94. omnimancer/core/signal_handler.py +174 -0
  95. omnimancer/mcp/__init__.py +11 -0
  96. omnimancer/mcp/client.py +725 -0
  97. omnimancer/mcp/manager.py +649 -0
  98. omnimancer/providers/__init__.py +24 -0
  99. omnimancer/providers/azure.py +608 -0
  100. omnimancer/providers/base.py +262 -0
  101. omnimancer/providers/bedrock.py +711 -0
  102. omnimancer/providers/claude.py +426 -0
  103. omnimancer/providers/claude_code.py +406 -0
  104. omnimancer/providers/cohere.py +412 -0
  105. omnimancer/providers/factory.py +988 -0
  106. omnimancer/providers/gemini.py +708 -0
  107. omnimancer/providers/mistral.py +554 -0
  108. omnimancer/providers/ollama.py +510 -0
  109. omnimancer/providers/openai.py +362 -0
  110. omnimancer/providers/openrouter.py +709 -0
  111. omnimancer/providers/perplexity.py +476 -0
  112. omnimancer/providers/vertex.py +617 -0
  113. omnimancer/providers/xai.py +484 -0
  114. omnimancer/ui/__init__.py +6 -0
  115. omnimancer/ui/cancellation_handler.py +235 -0
  116. omnimancer/ui/progress_indicator.py +318 -0
  117. omnimancer/utils/__init__.py +22 -0
  118. omnimancer/utils/error_handler.py +589 -0
  119. omnimancer/utils/errors.py +667 -0
  120. omnimancer/utils/retry.py +238 -0
  121. omnimancer_cli-0.1.0.dist-info/METADATA +413 -0
  122. omnimancer_cli-0.1.0.dist-info/RECORD +126 -0
  123. omnimancer_cli-0.1.0.dist-info/WHEEL +5 -0
  124. omnimancer_cli-0.1.0.dist-info/entry_points.txt +4 -0
  125. omnimancer_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
  126. omnimancer_cli-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,371 @@
1
+ """
2
+ Dynamic command registration and management system for Omnimancer.
3
+
4
+ This module provides a system for dynamically loading and registering
5
+ custom slash commands from external files, similar to Claude Code's
6
+ command system.
7
+ """
8
+
9
+ import json
10
+ import logging
11
+ from dataclasses import dataclass, field
12
+ from pathlib import Path
13
+ from typing import Any, Callable, Dict, List, Optional
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ @dataclass
19
+ class DynamicCommand:
20
+ """Represents a dynamically loaded command."""
21
+
22
+ name: str # Command name without slash (e.g., "custom-test")
23
+ description: str
24
+ handler: Optional[Callable] = None # Python handler if applicable
25
+ script_path: Optional[Path] = None # Path to script file
26
+ metadata: Dict[str, Any] = field(default_factory=dict)
27
+ arguments: List[Dict[str, Any]] = field(
28
+ default_factory=list
29
+ ) # Argument definitions
30
+
31
+ @property
32
+ def slash_name(self) -> str:
33
+ """Get the full slash command name."""
34
+ return f"/{self.name}"
35
+
36
+ def get_argument_names(self) -> List[str]:
37
+ """Get list of argument names for autocomplete."""
38
+ return [arg.get("name", "") for arg in self.arguments if arg.get("name")]
39
+
40
+ def get_argument_at_position(self, position: int) -> Optional[Dict[str, Any]]:
41
+ """Get argument definition at a specific position."""
42
+ if 0 <= position < len(self.arguments):
43
+ return self.arguments[position]
44
+ return None
45
+
46
+
47
+ class CommandRegistry:
48
+ """Registry for managing dynamic commands."""
49
+
50
+ def __init__(self, commands_dir: Optional[Path] = None):
51
+ """
52
+ Initialize the command registry.
53
+
54
+ Args:
55
+ commands_dir: Directory to load commands from
56
+ """
57
+ self.commands: Dict[str, DynamicCommand] = {}
58
+ self.commands_dir = commands_dir or self._get_default_commands_dir()
59
+ self._builtin_commands: List[str] = [] # Track built-in commands
60
+
61
+ def _get_default_commands_dir(self) -> Path:
62
+ """Get the default commands directory."""
63
+ # Use ~/.omnimancer/commands as requested by user
64
+ return Path.home() / ".omnimancer" / "commands"
65
+
66
+ def register_command(self, command: DynamicCommand) -> bool:
67
+ """
68
+ Register a dynamic command.
69
+
70
+ Args:
71
+ command: The command to register
72
+
73
+ Returns:
74
+ True if registered successfully, False otherwise
75
+ """
76
+ try:
77
+ # Check for conflicts with built-in commands
78
+ if command.name in self._builtin_commands:
79
+ logger.warning(f"Cannot override built-in command: {command.name}")
80
+ return False
81
+
82
+ self.commands[command.name] = command
83
+ logger.debug(f"Registered dynamic command: {command.name}")
84
+ return True
85
+
86
+ except Exception as e:
87
+ logger.error(f"Failed to register command {command.name}: {e}")
88
+ return False
89
+
90
+ def unregister_command(self, name: str) -> bool:
91
+ """
92
+ Unregister a dynamic command.
93
+
94
+ Args:
95
+ name: Command name (without slash)
96
+
97
+ Returns:
98
+ True if unregistered successfully, False otherwise
99
+ """
100
+ if name in self.commands:
101
+ del self.commands[name]
102
+ logger.debug(f"Unregistered command: {name}")
103
+ return True
104
+ return False
105
+
106
+ def get_command(self, name: str) -> Optional[DynamicCommand]:
107
+ """
108
+ Get a registered command by name.
109
+
110
+ Args:
111
+ name: Command name (with or without slash)
112
+
113
+ Returns:
114
+ The command if found, None otherwise
115
+ """
116
+ # Remove leading slash if present
117
+ if name.startswith("/"):
118
+ name = name[1:]
119
+ return self.commands.get(name)
120
+
121
+ def list_commands(self) -> List[str]:
122
+ """
123
+ List all registered dynamic command names.
124
+
125
+ Returns:
126
+ List of command names (with slash prefix)
127
+ """
128
+ return [f"/{name}" for name in self.commands.keys()]
129
+
130
+ def load_commands_from_directory(self, directory: Optional[Path] = None) -> int:
131
+ """
132
+ Load commands from a directory.
133
+
134
+ Args:
135
+ directory: Directory to load from (uses default if None)
136
+
137
+ Returns:
138
+ Number of commands loaded
139
+ """
140
+ load_dir = directory or self.commands_dir
141
+ if not load_dir.exists():
142
+ logger.debug(f"Commands directory does not exist: {load_dir}")
143
+ return 0
144
+
145
+ loaded_count = 0
146
+
147
+ # Load JSON command definitions
148
+ for json_file in load_dir.glob("*.json"):
149
+ try:
150
+ command = self._load_json_command(json_file)
151
+ if command and self.register_command(command):
152
+ loaded_count += 1
153
+ except Exception as e:
154
+ logger.error(f"Failed to load command from {json_file}: {e}")
155
+
156
+ # Load Python command modules
157
+ for py_file in load_dir.glob("*.py"):
158
+ if py_file.name.startswith("_"):
159
+ continue # Skip private modules
160
+ try:
161
+ command = self._load_python_command(py_file)
162
+ if command and self.register_command(command):
163
+ loaded_count += 1
164
+ except Exception as e:
165
+ logger.error(f"Failed to load command from {py_file}: {e}")
166
+
167
+ # Load shell script commands
168
+ for script_file in load_dir.glob("*.sh"):
169
+ try:
170
+ command = self._load_script_command(script_file)
171
+ if command and self.register_command(command):
172
+ loaded_count += 1
173
+ except Exception as e:
174
+ logger.error(f"Failed to load command from {script_file}: {e}")
175
+
176
+ logger.info(f"Loaded {loaded_count} dynamic commands from {load_dir}")
177
+ return loaded_count
178
+
179
+ def _load_json_command(self, file_path: Path) -> Optional[DynamicCommand]:
180
+ """Load a command definition from a JSON file."""
181
+ try:
182
+ with open(file_path, "r") as f:
183
+ data = json.load(f)
184
+
185
+ # Extract command info
186
+ name = data.get("name") or file_path.stem
187
+ description = data.get("description", "")
188
+ arguments = data.get("arguments", [])
189
+ metadata = data.get("metadata", {})
190
+
191
+ # Check for associated script
192
+ script_path = None
193
+ if "script" in data:
194
+ script_file = file_path.parent / data["script"]
195
+ if script_file.exists():
196
+ script_path = script_file
197
+
198
+ return DynamicCommand(
199
+ name=name,
200
+ description=description,
201
+ script_path=script_path,
202
+ metadata=metadata,
203
+ arguments=arguments,
204
+ )
205
+
206
+ except Exception as e:
207
+ logger.error(f"Error loading JSON command from {file_path}: {e}")
208
+ return None
209
+
210
+ def _load_python_command(self, file_path: Path) -> Optional[DynamicCommand]:
211
+ """Load a command from a Python module."""
212
+ try:
213
+ import importlib.util
214
+
215
+ # Load the module
216
+ spec = importlib.util.spec_from_file_location(file_path.stem, file_path)
217
+ if not spec or not spec.loader:
218
+ return None
219
+
220
+ module = importlib.util.module_from_spec(spec)
221
+ spec.loader.exec_module(module)
222
+
223
+ # Look for command metadata
224
+ if not hasattr(module, "COMMAND_INFO"):
225
+ logger.warning(f"No COMMAND_INFO found in {file_path}")
226
+ return None
227
+
228
+ info = module.COMMAND_INFO
229
+ name = info.get("name", file_path.stem)
230
+ description = info.get("description", "")
231
+ arguments = info.get("arguments", [])
232
+
233
+ # Look for handler function
234
+ handler = None
235
+ if hasattr(module, "handle_command"):
236
+ handler = module.handle_command
237
+
238
+ return DynamicCommand(
239
+ name=name,
240
+ description=description,
241
+ handler=handler,
242
+ arguments=arguments,
243
+ metadata={"source": str(file_path)},
244
+ )
245
+
246
+ except Exception as e:
247
+ logger.error(f"Error loading Python command from {file_path}: {e}")
248
+ return None
249
+
250
+ def _load_script_command(self, file_path: Path) -> Optional[DynamicCommand]:
251
+ """Load a command from a shell script."""
252
+ try:
253
+ # Look for metadata in script comments
254
+ name = file_path.stem
255
+ description = ""
256
+ arguments = []
257
+
258
+ with open(file_path, "r") as f:
259
+ lines = f.readlines()
260
+
261
+ # Parse header comments for metadata
262
+ for line in lines[:20]: # Check first 20 lines
263
+ if line.startswith("# NAME:"):
264
+ name = line.replace("# NAME:", "").strip()
265
+ elif line.startswith("# DESCRIPTION:"):
266
+ description = line.replace("# DESCRIPTION:", "").strip()
267
+ elif line.startswith("# ARG:"):
268
+ # Format: # ARG: name:type:description
269
+ arg_info = line.replace("# ARG:", "").strip()
270
+ parts = arg_info.split(":", 2)
271
+ if len(parts) >= 1:
272
+ arg = {"name": parts[0]}
273
+ if len(parts) >= 2:
274
+ arg["type"] = parts[1]
275
+ if len(parts) >= 3:
276
+ arg["description"] = parts[2]
277
+ arguments.append(arg)
278
+
279
+ return DynamicCommand(
280
+ name=name,
281
+ description=description or f"Script command: {name}",
282
+ script_path=file_path,
283
+ arguments=arguments,
284
+ metadata={"type": "shell_script"},
285
+ )
286
+
287
+ except Exception as e:
288
+ logger.error(f"Error loading script command from {file_path}: {e}")
289
+ return None
290
+
291
+ def set_builtin_commands(self, commands: List[str]) -> None:
292
+ """
293
+ Set the list of built-in commands to prevent overriding.
294
+
295
+ Args:
296
+ commands: List of built-in command names (without slash)
297
+ """
298
+ self._builtin_commands = [cmd.lstrip("/") for cmd in commands]
299
+
300
+ def get_completions_for_command(
301
+ self, command_name: str, arg_position: int, text: str
302
+ ) -> List[str]:
303
+ """
304
+ Get autocomplete suggestions for a command's arguments.
305
+
306
+ Args:
307
+ command_name: The command name (with or without slash)
308
+ arg_position: The argument position being completed
309
+ text: The current text being typed
310
+
311
+ Returns:
312
+ List of completion suggestions
313
+ """
314
+ command = self.get_command(command_name)
315
+ if not command:
316
+ return []
317
+
318
+ # Get argument definition for this position
319
+ arg_def = command.get_argument_at_position(arg_position)
320
+ if not arg_def:
321
+ return []
322
+
323
+ # Check if argument has predefined choices
324
+ if "choices" in arg_def:
325
+ choices = arg_def["choices"]
326
+ if callable(choices):
327
+ # Dynamic choices function
328
+ try:
329
+ choices = choices()
330
+ except Exception:
331
+ return []
332
+ return [c for c in choices if str(c).startswith(text)]
333
+
334
+ # Check for type-based completions
335
+ arg_type = arg_def.get("type", "string")
336
+ if arg_type == "file":
337
+ return self._complete_file_path(text)
338
+ elif arg_type == "directory":
339
+ return self._complete_directory_path(text)
340
+
341
+ return []
342
+
343
+ def _complete_file_path(self, text: str) -> List[str]:
344
+ """Complete file paths."""
345
+ try:
346
+ path = Path(text)
347
+ if text.endswith("/"):
348
+ # List directory contents
349
+ if path.exists() and path.is_dir():
350
+ return [str(p) for p in path.iterdir()]
351
+ else:
352
+ # Complete partial path
353
+ parent = path.parent
354
+ prefix = path.name
355
+ if parent.exists():
356
+ return [
357
+ str(p) for p in parent.iterdir() if p.name.startswith(prefix)
358
+ ]
359
+ except Exception:
360
+ pass
361
+ return []
362
+
363
+ def _complete_directory_path(self, text: str) -> List[str]:
364
+ """Complete directory paths."""
365
+ try:
366
+ paths = self._complete_file_path(text)
367
+ # Filter to only directories
368
+ return [p for p in paths if Path(p).is_dir()]
369
+ except Exception:
370
+ pass
371
+ return []
@@ -0,0 +1,18 @@
1
+ """
2
+ CLI Handlers package.
3
+
4
+ This package contains specialized handlers for different CLI commands
5
+ to keep the main interface file organized and maintainable.
6
+ """
7
+
8
+ from .agent_handler import AgentCLIHandler
9
+ from .agent_persona_handler import AgentPersonaHandler
10
+ from .custom_agent_handler import CustomAgentHandler
11
+ from .permissions_handler import PermissionsHandler
12
+
13
+ __all__ = [
14
+ "AgentCLIHandler",
15
+ "AgentPersonaHandler",
16
+ "PermissionsHandler",
17
+ "CustomAgentHandler",
18
+ ]