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,406 @@
1
+ """
2
+ Claude-code provider implementation for Omnimancer.
3
+
4
+ This module provides the Claude-code provider implementation for local Claude-code integration
5
+ with support for opus and sonnet modes with free access.
6
+ """
7
+
8
+ import os
9
+ import subprocess
10
+ from datetime import datetime
11
+ from typing import List
12
+
13
+
14
+ from ..core.models import ChatContext, ChatResponse, EnhancedModelInfo
15
+ from ..utils.errors import (
16
+ AuthenticationError,
17
+ ModelNotFoundError,
18
+ ProviderError,
19
+ RateLimitError,
20
+ )
21
+ from .base import BaseProvider
22
+
23
+
24
+ class ClaudeCodeProvider(BaseProvider):
25
+ """
26
+ Claude-code provider implementation for local Claude-code integration.
27
+
28
+ Provides integration with existing Claude-code installations,
29
+ supporting opus and sonnet modes with free local access.
30
+ """
31
+
32
+ def __init__(self, api_key: str = "local", model: str = "", **kwargs):
33
+ """
34
+ Initialize Claude-code provider.
35
+
36
+ Args:
37
+ api_key: Not used for Claude-code (local installation), defaults to "local"
38
+ model: Claude-code model mode (e.g., 'claude-code-sonnet', 'claude-code-opus', 'claude-code-haiku')
39
+ **kwargs: Additional configuration including Claude-code specific settings
40
+ """
41
+ super().__init__(api_key or "local", model or "claude-code-sonnet", **kwargs)
42
+
43
+ # Claude-code specific configuration
44
+ self.claude_code_mode = kwargs.get(
45
+ "claude_code_mode", self._extract_mode_from_model()
46
+ )
47
+ self.claude_code_path = kwargs.get("claude_code_path", "claude")
48
+ self.working_directory = kwargs.get("working_directory", os.getcwd())
49
+
50
+ # Standard parameters (not all may be supported by claude-code)
51
+ self.max_tokens = kwargs.get("max_tokens", 4096)
52
+ self.temperature = kwargs.get("temperature", 0.7)
53
+
54
+ # Validate Claude-code installation
55
+ self._validate_installation()
56
+
57
+ def _extract_mode_from_model(self) -> str:
58
+ """
59
+ Extract mode from model name.
60
+
61
+ Returns:
62
+ Mode string (opus or sonnet)
63
+ """
64
+ if "opus" in self.model.lower():
65
+ return "opus"
66
+ elif "sonnet" in self.model.lower():
67
+ return "sonnet"
68
+ else:
69
+ return "sonnet" # Default to sonnet
70
+
71
+ def _validate_installation(self):
72
+ """
73
+ Validate that Claude-code is installed and accessible.
74
+
75
+ Raises:
76
+ ProviderError: If Claude-code is not found or not working
77
+ """
78
+ try:
79
+ # Try to run claude with version command
80
+ result = subprocess.run(
81
+ [self.claude_code_path, "--version"],
82
+ capture_output=True,
83
+ text=True,
84
+ timeout=10,
85
+ )
86
+
87
+ if result.returncode != 0:
88
+ # Try alternative command
89
+ result = subprocess.run(
90
+ [self.claude_code_path, "--help"],
91
+ capture_output=True,
92
+ text=True,
93
+ timeout=10,
94
+ )
95
+
96
+ if result.returncode != 0:
97
+ raise ProviderError(
98
+ f"Claude command not found or not working at: {self.claude_code_path}"
99
+ )
100
+
101
+ except subprocess.TimeoutExpired:
102
+ raise ProviderError("Claude command timed out")
103
+ except FileNotFoundError:
104
+ raise ProviderError(f"Claude executable not found: {self.claude_code_path}")
105
+ except Exception as e:
106
+ raise ProviderError(f"Error validating Claude installation: {e}")
107
+
108
+ async def send_message(self, message: str, context: ChatContext) -> ChatResponse:
109
+ """
110
+ Send a message to Claude-code.
111
+
112
+ Args:
113
+ message: User message
114
+ context: Conversation context
115
+
116
+ Returns:
117
+ ChatResponse with Claude-code's reply
118
+ """
119
+
120
+ try:
121
+ # Prepare the conversation for Claude-code
122
+ conversation = self._prepare_conversation(message, context)
123
+ result = await self._execute_claude_code(conversation)
124
+ response = self._handle_claude_code_response(result)
125
+ return response
126
+
127
+ except Exception as e:
128
+ raise ProviderError(f"Error communicating with Claude-code: {e}")
129
+
130
+ async def validate_credentials(self) -> bool:
131
+ """
132
+ Validate Claude installation by running a test command.
133
+
134
+ Returns:
135
+ True if Claude is working
136
+ """
137
+ try:
138
+ # Test with a simple message
139
+ test_result = await self._execute_claude_code("Hi")
140
+ return test_result.returncode == 0 and test_result.stdout.strip() != ""
141
+ except Exception:
142
+ return False
143
+
144
+ def _prepare_conversation(self, message: str, context: ChatContext) -> str:
145
+ """
146
+ Prepare conversation for Claude-code.
147
+
148
+ Args:
149
+ message: Current user message
150
+ context: Conversation context
151
+
152
+ Returns:
153
+ Formatted conversation string
154
+ """
155
+ conversation_parts = []
156
+
157
+ # Add context messages
158
+ for msg in context.messages:
159
+ if msg.role.value == "user":
160
+ conversation_parts.append(f"Human: {msg.content}")
161
+ elif msg.role.value == "assistant":
162
+ conversation_parts.append(f"Assistant: {msg.content}")
163
+ elif msg.role.value == "system":
164
+ conversation_parts.append(f"System: {msg.content}")
165
+
166
+ # Add current message
167
+ conversation_parts.append(f"Human: {message}")
168
+
169
+ return "\n\n".join(conversation_parts)
170
+
171
+ async def _execute_claude_code(
172
+ self, conversation: str
173
+ ) -> subprocess.CompletedProcess:
174
+ """
175
+ Execute Claude with the conversation.
176
+
177
+ Args:
178
+ conversation: Formatted conversation string
179
+
180
+ Returns:
181
+ Subprocess result
182
+ """
183
+ # Build command arguments - use --print for non-interactive mode
184
+ cmd = [self.claude_code_path, "--print"]
185
+
186
+ # Add model if supported
187
+ if hasattr(self, "claude_code_mode") and self.claude_code_mode:
188
+ if self.claude_code_mode == "opus":
189
+ cmd.extend(["--model", "opus"])
190
+ elif self.claude_code_mode == "sonnet":
191
+ cmd.extend(["--model", "sonnet"])
192
+
193
+ # Add the conversation as the prompt
194
+ cmd.append(conversation)
195
+
196
+ # Execute the command
197
+ try:
198
+ # Run in an executor to avoid blocking
199
+ import asyncio
200
+
201
+ loop = asyncio.get_event_loop()
202
+
203
+ result = await loop.run_in_executor(
204
+ None,
205
+ lambda: subprocess.run(
206
+ cmd,
207
+ capture_output=True,
208
+ text=True,
209
+ timeout=120, # 2 minute timeout for longer responses
210
+ cwd=self.working_directory,
211
+ ),
212
+ )
213
+
214
+ return result
215
+
216
+ except subprocess.TimeoutExpired:
217
+ raise ProviderError("Claude command timed out")
218
+ except Exception as e:
219
+ raise ProviderError(f"Error executing Claude: {e}")
220
+
221
+ def _handle_claude_code_response(
222
+ self, result: subprocess.CompletedProcess
223
+ ) -> ChatResponse:
224
+ """
225
+ Handle Claude-code command result.
226
+
227
+ Args:
228
+ result: Subprocess result from Claude-code
229
+
230
+ Returns:
231
+ ChatResponse object
232
+
233
+ Raises:
234
+ ProviderError: If Claude-code returned an error
235
+ """
236
+ if result.returncode == 0:
237
+ # Successful execution
238
+ output = result.stdout.strip()
239
+
240
+ if not output:
241
+ raise ProviderError("Empty response from Claude-code")
242
+
243
+ # Claude-code might include metadata or formatting
244
+ # Try to extract just the assistant's response
245
+ response_content = self._extract_response_content(output)
246
+
247
+ return ChatResponse(
248
+ content=response_content,
249
+ model_used=self.model,
250
+ tokens_used=0, # Claude-code doesn't provide token counts
251
+ timestamp=datetime.now(),
252
+ )
253
+ else:
254
+ # Error occurred
255
+ error_msg = result.stderr.strip() if result.stderr else "Unknown error"
256
+
257
+ # Check for common error patterns
258
+ if "authentication" in error_msg.lower() or "api key" in error_msg.lower():
259
+ raise AuthenticationError(
260
+ f"Claude-code authentication error: {error_msg}"
261
+ )
262
+ elif "rate limit" in error_msg.lower():
263
+ raise RateLimitError(f"Claude-code rate limit: {error_msg}")
264
+ elif "not found" in error_msg.lower():
265
+ raise ModelNotFoundError(f"Claude-code model not found: {error_msg}")
266
+ else:
267
+ raise ProviderError(f"Claude-code error: {error_msg}")
268
+
269
+ def _extract_response_content(self, output: str) -> str:
270
+ """
271
+ Extract the actual response content from Claude output.
272
+
273
+ Args:
274
+ output: Raw output from Claude --print
275
+
276
+ Returns:
277
+ Cleaned response content
278
+ """
279
+ # Claude --print gives us clean output directly
280
+ # Just return the output stripped of whitespace
281
+ return output.strip()
282
+
283
+ def get_model_info(self) -> EnhancedModelInfo:
284
+ """
285
+ Get information about the current Claude-code model.
286
+ """
287
+ model_configs = {
288
+ "claude-code-opus": {
289
+ "description": "Claude-code Opus - Local free access to Claude Opus",
290
+ "max_tokens": 200000,
291
+ "swe_score": 84.9,
292
+ "supports_tools": False,
293
+ "supports_multimodal": True,
294
+ },
295
+ "claude-code-sonnet": {
296
+ "description": "Claude-code Sonnet - Local free access to Claude Sonnet",
297
+ "max_tokens": 200000,
298
+ "swe_score": 73.0,
299
+ "supports_tools": False,
300
+ "supports_multimodal": True,
301
+ },
302
+ }
303
+
304
+ config = model_configs.get(
305
+ self.model,
306
+ {
307
+ "description": f"Claude-code model {self.model}",
308
+ "max_tokens": 200000,
309
+ "swe_score": 75.0,
310
+ "supports_tools": False,
311
+ "supports_multimodal": True,
312
+ },
313
+ )
314
+
315
+ enhanced_info = EnhancedModelInfo(
316
+ name=self.model,
317
+ provider="claude-code",
318
+ description=config["description"],
319
+ max_tokens=config["max_tokens"],
320
+ cost_per_million_input=0.0, # Free local access
321
+ cost_per_million_output=0.0, # Free local access
322
+ swe_score=config["swe_score"],
323
+ available=True,
324
+ supports_tools=config["supports_tools"],
325
+ supports_multimodal=config["supports_multimodal"],
326
+ latest_version=self.model == "claude-code-opus",
327
+ context_window=config["max_tokens"],
328
+ is_free=True, # Local installation is free
329
+ release_date=datetime(2024, 3, 1), # Approximate availability
330
+ )
331
+
332
+ # Update SWE rating based on score
333
+ enhanced_info.update_swe_rating()
334
+
335
+ return enhanced_info
336
+
337
+ def get_available_models(self) -> List[EnhancedModelInfo]:
338
+ """
339
+ Get list of available Claude-code models.
340
+ """
341
+ models = [
342
+ EnhancedModelInfo(
343
+ name="claude-code-opus",
344
+ provider="claude-code",
345
+ description="Claude-code Opus - Local free access to Claude Opus",
346
+ max_tokens=200000,
347
+ cost_per_million_input=0.0,
348
+ cost_per_million_output=0.0,
349
+ swe_score=84.9,
350
+ available=True,
351
+ supports_tools=False,
352
+ supports_multimodal=True,
353
+ latest_version=True,
354
+ context_window=200000,
355
+ is_free=True,
356
+ release_date=datetime(2024, 3, 1),
357
+ ),
358
+ EnhancedModelInfo(
359
+ name="claude-code-sonnet",
360
+ provider="claude-code",
361
+ description="Claude-code Sonnet - Local free access to Claude Sonnet",
362
+ max_tokens=200000,
363
+ cost_per_million_input=0.0,
364
+ cost_per_million_output=0.0,
365
+ swe_score=73.0,
366
+ available=True,
367
+ supports_tools=False,
368
+ supports_multimodal=True,
369
+ context_window=200000,
370
+ is_free=True,
371
+ release_date=datetime(2024, 3, 1),
372
+ ),
373
+ ]
374
+
375
+ # Update SWE ratings for all models
376
+ for model in models:
377
+ model.update_swe_rating()
378
+
379
+ return models
380
+
381
+ def supports_tools(self) -> bool:
382
+ """
383
+ Check if Claude-code provider supports tool calling.
384
+
385
+ Returns:
386
+ False - Claude-code typically doesn't support tool calling
387
+ """
388
+ return False
389
+
390
+ def supports_multimodal(self) -> bool:
391
+ """
392
+ Check if Claude-code provider supports multimodal inputs.
393
+
394
+ Returns:
395
+ True - Claude models support multimodal inputs
396
+ """
397
+ return True
398
+
399
+ def supports_streaming(self) -> bool:
400
+ """
401
+ Check if Claude-code provider supports streaming responses.
402
+
403
+ Returns:
404
+ False - Local command execution doesn't support streaming
405
+ """
406
+ return False