cuti 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 (94) hide show
  1. cuti/__init__.py +21 -0
  2. cuti/__main__.py +110 -0
  3. cuti/agents/__init__.py +37 -0
  4. cuti/agents/base.py +269 -0
  5. cuti/agents/claude_agent.py +268 -0
  6. cuti/agents/context.py +180 -0
  7. cuti/agents/gemini_agent.py +380 -0
  8. cuti/agents/pool.py +228 -0
  9. cuti/agents/router.py +469 -0
  10. cuti/builtin_agents/__init__.py +16 -0
  11. cuti/builtin_agents/code-reviewer.md +90 -0
  12. cuti/builtin_agents/docs-generator.md +125 -0
  13. cuti/builtin_agents/gemini-codebase-analysis.md +139 -0
  14. cuti/builtin_agents/refactor-assistant.md +116 -0
  15. cuti/builtin_agents/test-writer.md +99 -0
  16. cuti/builtin_agents/ui-design-expert.md +96 -0
  17. cuti/claude_usage_monitor.py +454 -0
  18. cuti/cli/__init__.py +7 -0
  19. cuti/cli/app.py +173 -0
  20. cuti/cli/commands/__init__.py +3 -0
  21. cuti/cli/commands/agent.py +121 -0
  22. cuti/cli/commands/alias.py +150 -0
  23. cuti/cli/commands/devcontainer.py +271 -0
  24. cuti/cli/commands/queue.py +244 -0
  25. cuti/cli/utils.py +62 -0
  26. cuti/core/__init__.py +17 -0
  27. cuti/core/claude_interface.py +317 -0
  28. cuti/core/config.py +86 -0
  29. cuti/core/models.py +210 -0
  30. cuti/core/queue.py +219 -0
  31. cuti/core/storage.py +316 -0
  32. cuti/services/__init__.py +17 -0
  33. cuti/services/agent_manager.py +272 -0
  34. cuti/services/aliases.py +549 -0
  35. cuti/services/claude_agent_manager.py +450 -0
  36. cuti/services/claude_logs_reader.py +275 -0
  37. cuti/services/claude_monitor_integration.py +623 -0
  38. cuti/services/claude_orchestration.py +349 -0
  39. cuti/services/claude_settings_manager.py +175 -0
  40. cuti/services/claude_usage_monitor.py +459 -0
  41. cuti/services/devcontainer.py +593 -0
  42. cuti/services/history.py +474 -0
  43. cuti/services/log_sync.py +320 -0
  44. cuti/services/monitoring.py +656 -0
  45. cuti/services/queue_service.py +136 -0
  46. cuti/services/task_expansion.py +820 -0
  47. cuti/services/task_history.py +353 -0
  48. cuti/services/workspace_manager.py +358 -0
  49. cuti/utils/__init__.py +16 -0
  50. cuti/utils/constants.py +41 -0
  51. cuti/utils/helpers.py +138 -0
  52. cuti/utils/logger.py +69 -0
  53. cuti/web/__init__.py +3 -0
  54. cuti/web/api/__init__.py +3 -0
  55. cuti/web/api/agents.py +350 -0
  56. cuti/web/api/claude_agents.py +138 -0
  57. cuti/web/api/claude_code_agents.py +138 -0
  58. cuti/web/api/claude_logs.py +131 -0
  59. cuti/web/api/claude_settings.py +77 -0
  60. cuti/web/api/enhanced_chat.py +368 -0
  61. cuti/web/api/monitoring.py +441 -0
  62. cuti/web/api/queue.py +143 -0
  63. cuti/web/api/websocket.py +218 -0
  64. cuti/web/api/workspace.py +70 -0
  65. cuti/web/app.py +291 -0
  66. cuti/web/main.py +92 -0
  67. cuti/web/routes.py +106 -0
  68. cuti/web/static/css/agent-cards.css +181 -0
  69. cuti/web/static/css/agents.css +1129 -0
  70. cuti/web/static/css/agents_orchestration.css +602 -0
  71. cuti/web/static/css/main.css +2649 -0
  72. cuti/web/static/css/markdown.css +359 -0
  73. cuti/web/static/css/symphony-toggle-enhanced.css +463 -0
  74. cuti/web/static/css/symphony-toggle.css +407 -0
  75. cuti/web/static/js/app.js +1251 -0
  76. cuti/web/static/js/markdown-renderer.js +249 -0
  77. cuti/web/static/js/symphony-toggle.js +321 -0
  78. cuti/web/templates/agents.html +711 -0
  79. cuti/web/templates/agents_orchestration.html +773 -0
  80. cuti/web/templates/base.html +36 -0
  81. cuti/web/templates/chat.html +607 -0
  82. cuti/web/templates/components/card.html +24 -0
  83. cuti/web/templates/components/header.html +44 -0
  84. cuti/web/templates/components/sidebar.html +57 -0
  85. cuti/web/templates/components/status_bar.html +27 -0
  86. cuti/web/templates/enhanced_chat.html +663 -0
  87. cuti/web/templates/statistics.html +1738 -0
  88. cuti/web/templates/symphony_toggle_demo.html +289 -0
  89. cuti/web/utils.py +152 -0
  90. cuti-0.1.0.dist-info/METADATA +725 -0
  91. cuti-0.1.0.dist-info/RECORD +94 -0
  92. cuti-0.1.0.dist-info/WHEEL +4 -0
  93. cuti-0.1.0.dist-info/entry_points.txt +3 -0
  94. cuti-0.1.0.dist-info/licenses/LICENSE +21 -0
cuti/__init__.py ADDED
@@ -0,0 +1,21 @@
1
+ """
2
+ cuti - Production-ready claude code utils with command queuing, web interface, and monitoring.
3
+ """
4
+
5
+ __version__ = "0.1.0"
6
+ __author__ = "claude-code, nociza"
7
+ __description__ = "Production-ready claude code utils with command queuing, prompt aliases, web interface, and monitoring."
8
+
9
+ # Import main components for convenience
10
+ from .services.queue_service import QueueManager
11
+ from .core.models import QueuedPrompt, PromptStatus
12
+ from .services.aliases import PromptAliasManager
13
+ from .services.history import PromptHistoryManager
14
+
15
+ __all__ = [
16
+ "QueueManager",
17
+ "QueuedPrompt",
18
+ "PromptStatus",
19
+ "PromptAliasManager",
20
+ "PromptHistoryManager",
21
+ ]
cuti/__main__.py ADDED
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Main entry point for cuti when run with uvx or python -m cuti.
4
+ Starts the web server by default.
5
+ """
6
+
7
+ import sys
8
+ import argparse
9
+ import os
10
+ from pathlib import Path
11
+
12
+ from .web.app import main as web_main
13
+
14
+
15
+ def main():
16
+ """Main entry point for uvx cuti command."""
17
+ parser = argparse.ArgumentParser(
18
+ prog="cuti",
19
+ description="Production-ready cuti system with web interface",
20
+ formatter_class=argparse.RawDescriptionHelpFormatter,
21
+ epilog="""
22
+ Examples:
23
+ uvx cuti # Start web interface for current directory
24
+ uvx cuti --port 3000 # Start web interface on port 3000
25
+ uvx cuti --host 0.0.0.0 # Bind to all interfaces
26
+ uvx cuti /path/to/project # Start web interface for specific directory
27
+
28
+ The web interface will automatically start the queue processor in the background.
29
+ Claude Code will be launched in the working directory you specify (or current directory).
30
+ Access the dashboard at http://localhost:8000
31
+ """
32
+ )
33
+
34
+ parser.add_argument(
35
+ "working_directory",
36
+ nargs="?",
37
+ default=None,
38
+ help="Working directory for Claude Code (default: current directory)"
39
+ )
40
+ parser.add_argument(
41
+ "--host",
42
+ default="127.0.0.1",
43
+ help="Host to bind to (default: 127.0.0.1)"
44
+ )
45
+ parser.add_argument(
46
+ "--port",
47
+ type=int,
48
+ default=8000,
49
+ help="Port to bind to (default: 8000)"
50
+ )
51
+ parser.add_argument(
52
+ "--storage-dir",
53
+ default="~/.cuti",
54
+ help="Storage directory (default: ~/.cuti)"
55
+ )
56
+ parser.add_argument(
57
+ "--version",
58
+ action="version",
59
+ version="cuti 0.1.0"
60
+ )
61
+
62
+ args = parser.parse_args()
63
+
64
+ # Determine working directory
65
+ if args.working_directory:
66
+ working_dir = Path(args.working_directory).resolve()
67
+ if not working_dir.exists():
68
+ print(f"❌ Error: Directory '{working_dir}' does not exist")
69
+ sys.exit(1)
70
+ else:
71
+ working_dir = Path.cwd()
72
+
73
+ # Allow environment variables to override CLI
74
+ host = os.getenv("CLAUDE_QUEUE_WEB_HOST", args.host)
75
+ port_str = os.getenv("CLAUDE_QUEUE_WEB_PORT")
76
+ port = int(port_str) if port_str else args.port
77
+ storage_dir = os.getenv("CLAUDE_QUEUE_STORAGE_DIR", args.storage_dir)
78
+
79
+ print(f"🚀 Starting cuti web interface...")
80
+ print(f"📍 Host: {host}")
81
+ print(f"🔌 Port: {port}")
82
+ print(f"📁 Working Directory: {working_dir}")
83
+ print(f"💾 Storage: {Path(storage_dir).expanduser()}")
84
+ print(f"🌐 Dashboard: http://{host}:{port}")
85
+ print(f"📚 API Docs: http://{host}:{port}/docs")
86
+ print()
87
+
88
+ # Set environment variable for working directory
89
+ os.environ["CUTI_WORKING_DIR"] = str(working_dir)
90
+
91
+ # Override sys.argv for the web main function
92
+ sys.argv = [
93
+ "cuti-web",
94
+ "--host", host,
95
+ "--port", str(port),
96
+ "--storage-dir", storage_dir
97
+ ]
98
+
99
+ try:
100
+ web_main()
101
+ except KeyboardInterrupt:
102
+ print("\n👋 Shutting down cuti...")
103
+ sys.exit(0)
104
+ except Exception as e:
105
+ print(f"❌ Error starting cuti: {e}")
106
+ sys.exit(1)
107
+
108
+
109
+ if __name__ == "__main__":
110
+ main()
@@ -0,0 +1,37 @@
1
+ """
2
+ Multi-agent orchestration system for cuti.
3
+ """
4
+
5
+ from .base import (
6
+ BaseAgent,
7
+ AgentCapability,
8
+ AgentStatus,
9
+ AgentMetadata,
10
+ AgentExecutionContext,
11
+ AgentConfig
12
+ )
13
+ from .pool import AgentPool
14
+ from .router import TaskRouter, TaskRoutingStrategy, RoutingDecision, CoordinationEngine
15
+ from .context import SharedMemoryManager
16
+
17
+ __all__ = [
18
+ # Base classes
19
+ 'BaseAgent',
20
+ 'AgentCapability',
21
+ 'AgentStatus',
22
+ 'AgentMetadata',
23
+ 'AgentExecutionContext',
24
+ 'AgentConfig',
25
+
26
+ # Management
27
+ 'AgentPool',
28
+
29
+ # Routing
30
+ 'TaskRouter',
31
+ 'TaskRoutingStrategy',
32
+ 'RoutingDecision',
33
+ 'CoordinationEngine',
34
+
35
+ # Context
36
+ 'SharedMemoryManager'
37
+ ]
cuti/agents/base.py ADDED
@@ -0,0 +1,269 @@
1
+ """
2
+ Base classes for agent abstraction layer.
3
+ """
4
+
5
+ from abc import ABC, abstractmethod
6
+ from enum import Enum
7
+ from dataclasses import dataclass, field
8
+ from typing import Dict, List, Optional, Any, AsyncGenerator, Union
9
+ import asyncio
10
+ from datetime import datetime
11
+
12
+ from ..core.models import QueuedPrompt, ExecutionResult
13
+
14
+
15
+ class AgentCapability(Enum):
16
+ """Capabilities that agents can provide."""
17
+ CODE_UNDERSTANDING = "code_understanding"
18
+ CODE_GENERATION = "code_generation"
19
+ CODE_REFACTORING = "code_refactoring"
20
+ DEBUGGING = "debugging"
21
+ TESTING = "testing"
22
+ DOCUMENTATION = "documentation"
23
+ ARCHITECTURE_DESIGN = "architecture_design"
24
+ SECURITY_ANALYSIS = "security_analysis"
25
+ PERFORMANCE_OPTIMIZATION = "performance_optimization"
26
+ MULTIMODAL_PROCESSING = "multimodal_processing"
27
+ LARGE_CONTEXT_PROCESSING = "large_context_processing"
28
+ REAL_TIME_COLLABORATION = "real_time_collaboration"
29
+ FILE_SYSTEM_OPERATIONS = "file_system_operations"
30
+ DATABASE_OPERATIONS = "database_operations"
31
+ API_INTEGRATION = "api_integration"
32
+ WEB_BROWSING = "web_browsing"
33
+ DATA_ANALYSIS = "data_analysis"
34
+ MACHINE_LEARNING = "machine_learning"
35
+
36
+
37
+ class AgentStatus(Enum):
38
+ """Agent operational status."""
39
+ AVAILABLE = "available"
40
+ BUSY = "busy"
41
+ OFFLINE = "offline"
42
+ ERROR = "error"
43
+ RATE_LIMITED = "rate_limited"
44
+ INITIALIZING = "initializing"
45
+
46
+
47
+ @dataclass
48
+ class AgentMetadata:
49
+ """Metadata about an agent."""
50
+ name: str
51
+ version: str
52
+ capabilities: List[AgentCapability]
53
+ max_context_tokens: int
54
+ supports_streaming: bool
55
+ supports_multimodal: bool
56
+ rate_limit_info: Optional[Dict[str, Any]] = None
57
+ cost_per_input_token: Optional[float] = None
58
+ cost_per_output_token: Optional[float] = None
59
+ installation_command: Optional[str] = None
60
+ authentication_required: bool = True
61
+ supported_languages: List[str] = field(default_factory=list)
62
+ special_features: List[str] = field(default_factory=list)
63
+
64
+
65
+ @dataclass
66
+ class AgentConfig:
67
+ """Configuration for an agent."""
68
+ type: str
69
+ name: str
70
+ command: Optional[str] = None
71
+ api_key_env: Optional[str] = None
72
+ api_key: Optional[str] = None
73
+ timeout: int = 3600
74
+ max_concurrent: int = 1
75
+ working_directory: str = "."
76
+ environment_vars: Dict[str, str] = field(default_factory=dict)
77
+ system_prompt: Optional[str] = None
78
+ custom_settings: Dict[str, Any] = field(default_factory=dict)
79
+
80
+
81
+ @dataclass
82
+ class AgentExecutionContext:
83
+ """Context for agent execution."""
84
+ session_id: str
85
+ shared_memory: Dict[str, Any]
86
+ available_tools: List[str]
87
+ coordination_data: Dict[str, Any]
88
+ parent_task_id: Optional[str] = None
89
+ collaboration_mode: bool = False
90
+ previous_outputs: List[Dict[str, Any]] = field(default_factory=list)
91
+ execution_history: List[str] = field(default_factory=list)
92
+
93
+
94
+ class BaseAgent(ABC):
95
+ """Abstract base class for all agents."""
96
+
97
+ def __init__(self, config: AgentConfig):
98
+ self.config = config
99
+ self.name = config.name
100
+ self.status = AgentStatus.OFFLINE
101
+ self.metadata = self._initialize_metadata()
102
+ self.current_executions: Dict[str, QueuedPrompt] = {}
103
+ self._initialized = False
104
+ self._health_check_failures = 0
105
+ self._last_health_check = None
106
+
107
+ @abstractmethod
108
+ def _initialize_metadata(self) -> AgentMetadata:
109
+ """Initialize agent metadata."""
110
+ pass
111
+
112
+ @abstractmethod
113
+ async def initialize(self) -> bool:
114
+ """Initialize the agent and test connectivity."""
115
+ pass
116
+
117
+ @abstractmethod
118
+ async def execute_prompt(
119
+ self,
120
+ prompt: QueuedPrompt,
121
+ context: AgentExecutionContext
122
+ ) -> ExecutionResult:
123
+ """Execute a prompt with the agent."""
124
+ pass
125
+
126
+ @abstractmethod
127
+ async def stream_prompt(
128
+ self,
129
+ prompt: QueuedPrompt,
130
+ context: AgentExecutionContext
131
+ ) -> AsyncGenerator[str, None]:
132
+ """Stream execution results."""
133
+ pass
134
+
135
+ @abstractmethod
136
+ async def health_check(self) -> bool:
137
+ """Check if agent is healthy and available."""
138
+ pass
139
+
140
+ @abstractmethod
141
+ async def is_installed(self) -> bool:
142
+ """Check if the agent CLI is installed."""
143
+ pass
144
+
145
+ @abstractmethod
146
+ async def install(self) -> bool:
147
+ """Install the agent CLI."""
148
+ pass
149
+
150
+ async def can_handle_task(self, prompt: QueuedPrompt) -> float:
151
+ """
152
+ Return confidence score (0-1) for handling this task.
153
+ Default implementation uses keyword matching.
154
+ """
155
+ content_lower = prompt.content.lower()
156
+ confidence = 0.0
157
+
158
+ # Check for capability-related keywords
159
+ capability_keywords = {
160
+ AgentCapability.CODE_GENERATION: ['generate', 'create', 'write code', 'implement'],
161
+ AgentCapability.CODE_REFACTORING: ['refactor', 'restructure', 'clean up', 'improve code'],
162
+ AgentCapability.DEBUGGING: ['debug', 'fix', 'error', 'bug', 'issue'],
163
+ AgentCapability.TESTING: ['test', 'unit test', 'integration test', 'testing'],
164
+ AgentCapability.DOCUMENTATION: ['document', 'readme', 'comment', 'explain'],
165
+ AgentCapability.ARCHITECTURE_DESIGN: ['architecture', 'design', 'system', 'structure'],
166
+ AgentCapability.SECURITY_ANALYSIS: ['security', 'vulnerability', 'audit', 'secure'],
167
+ AgentCapability.PERFORMANCE_OPTIMIZATION: ['optimize', 'performance', 'speed up', 'efficient'],
168
+ AgentCapability.DATA_ANALYSIS: ['analyze', 'data', 'statistics', 'insights'],
169
+ AgentCapability.MACHINE_LEARNING: ['ml', 'machine learning', 'model', 'train']
170
+ }
171
+
172
+ for capability in self.metadata.capabilities:
173
+ if capability in capability_keywords:
174
+ keywords = capability_keywords[capability]
175
+ if any(keyword in content_lower for keyword in keywords):
176
+ confidence = max(confidence, 0.7)
177
+
178
+ # Base confidence for general capabilities
179
+ if confidence == 0.0 and self.metadata.capabilities:
180
+ confidence = 0.3
181
+
182
+ return confidence
183
+
184
+ def get_current_load(self) -> float:
185
+ """Return current load percentage (0-1)."""
186
+ max_concurrent = self.config.max_concurrent
187
+ current_count = len(self.current_executions)
188
+ return min(1.0, current_count / max_concurrent)
189
+
190
+ async def estimate_execution_time(self, prompt: QueuedPrompt) -> Optional[int]:
191
+ """
192
+ Estimate execution time in seconds.
193
+ Default implementation based on content length and complexity.
194
+ """
195
+ content_length = len(prompt.content)
196
+ base_time = 10 # Base 10 seconds
197
+
198
+ # Add time based on content length (1 second per 100 characters)
199
+ length_time = content_length / 100
200
+
201
+ # Add time for files (5 seconds per file)
202
+ file_time = len(prompt.context_files) * 5
203
+
204
+ # Estimate total time
205
+ estimated_time = int(base_time + length_time + file_time)
206
+
207
+ # Cap at timeout
208
+ return min(estimated_time, self.config.timeout)
209
+
210
+ async def estimate_cost(self, prompt: QueuedPrompt) -> Optional[float]:
211
+ """
212
+ Estimate cost in USD.
213
+ Default implementation based on token counts.
214
+ """
215
+ if not self.metadata.cost_per_input_token or not self.metadata.cost_per_output_token:
216
+ return None
217
+
218
+ # Rough estimation: 1 token ≈ 4 characters
219
+ estimated_input_tokens = len(prompt.content) / 4
220
+
221
+ # Add tokens for context files (rough estimate)
222
+ for file_path in prompt.context_files:
223
+ estimated_input_tokens += 500 # Assume 500 tokens per file
224
+
225
+ # Estimate output tokens (usually 2-3x input for code tasks)
226
+ estimated_output_tokens = estimated_input_tokens * 2.5
227
+
228
+ input_cost = estimated_input_tokens * self.metadata.cost_per_input_token
229
+ output_cost = estimated_output_tokens * self.metadata.cost_per_output_token
230
+
231
+ return input_cost + output_cost
232
+
233
+ def add_execution(self, prompt_id: str, prompt: QueuedPrompt):
234
+ """Track an active execution."""
235
+ self.current_executions[prompt_id] = prompt
236
+ self.status = AgentStatus.BUSY if self.get_current_load() >= 1.0 else AgentStatus.AVAILABLE
237
+
238
+ def remove_execution(self, prompt_id: str):
239
+ """Remove a completed execution."""
240
+ if prompt_id in self.current_executions:
241
+ del self.current_executions[prompt_id]
242
+ self.status = AgentStatus.AVAILABLE if self._initialized else AgentStatus.OFFLINE
243
+
244
+ async def prepare_system_prompt(self, context: AgentExecutionContext) -> str:
245
+ """
246
+ Prepare system prompt with context and collaboration instructions.
247
+ """
248
+ system_prompt = self.config.system_prompt or f"You are {self.name}, an AI assistant."
249
+
250
+ if context.collaboration_mode:
251
+ system_prompt += f"""
252
+
253
+ You are collaborating with other AI agents on this task.
254
+ Session ID: {context.session_id}
255
+ Your role: {context.coordination_data.get('role', 'collaborator')}
256
+
257
+ Previous outputs from other agents:
258
+ {context.previous_outputs}
259
+
260
+ Shared context:
261
+ {context.shared_memory}
262
+
263
+ Please coordinate your response with the overall task objectives.
264
+ """
265
+
266
+ return system_prompt
267
+
268
+ def __repr__(self) -> str:
269
+ return f"<{self.__class__.__name__} name='{self.name}' status={self.status.value}>"