mcli-framework 7.0.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.

Potentially problematic release.


This version of mcli-framework might be problematic. Click here for more details.

Files changed (186) hide show
  1. mcli/app/chat_cmd.py +42 -0
  2. mcli/app/commands_cmd.py +226 -0
  3. mcli/app/completion_cmd.py +216 -0
  4. mcli/app/completion_helpers.py +288 -0
  5. mcli/app/cron_test_cmd.py +697 -0
  6. mcli/app/logs_cmd.py +419 -0
  7. mcli/app/main.py +492 -0
  8. mcli/app/model/model.py +1060 -0
  9. mcli/app/model_cmd.py +227 -0
  10. mcli/app/redis_cmd.py +269 -0
  11. mcli/app/video/video.py +1114 -0
  12. mcli/app/visual_cmd.py +303 -0
  13. mcli/chat/chat.py +2409 -0
  14. mcli/chat/command_rag.py +514 -0
  15. mcli/chat/enhanced_chat.py +652 -0
  16. mcli/chat/system_controller.py +1010 -0
  17. mcli/chat/system_integration.py +1016 -0
  18. mcli/cli.py +25 -0
  19. mcli/config.toml +20 -0
  20. mcli/lib/api/api.py +586 -0
  21. mcli/lib/api/daemon_client.py +203 -0
  22. mcli/lib/api/daemon_client_local.py +44 -0
  23. mcli/lib/api/daemon_decorator.py +217 -0
  24. mcli/lib/api/mcli_decorators.py +1032 -0
  25. mcli/lib/auth/auth.py +85 -0
  26. mcli/lib/auth/aws_manager.py +85 -0
  27. mcli/lib/auth/azure_manager.py +91 -0
  28. mcli/lib/auth/credential_manager.py +192 -0
  29. mcli/lib/auth/gcp_manager.py +93 -0
  30. mcli/lib/auth/key_manager.py +117 -0
  31. mcli/lib/auth/mcli_manager.py +93 -0
  32. mcli/lib/auth/token_manager.py +75 -0
  33. mcli/lib/auth/token_util.py +1011 -0
  34. mcli/lib/config/config.py +47 -0
  35. mcli/lib/discovery/__init__.py +1 -0
  36. mcli/lib/discovery/command_discovery.py +274 -0
  37. mcli/lib/erd/erd.py +1345 -0
  38. mcli/lib/erd/generate_graph.py +453 -0
  39. mcli/lib/files/files.py +76 -0
  40. mcli/lib/fs/fs.py +109 -0
  41. mcli/lib/lib.py +29 -0
  42. mcli/lib/logger/logger.py +611 -0
  43. mcli/lib/performance/optimizer.py +409 -0
  44. mcli/lib/performance/rust_bridge.py +502 -0
  45. mcli/lib/performance/uvloop_config.py +154 -0
  46. mcli/lib/pickles/pickles.py +50 -0
  47. mcli/lib/search/cached_vectorizer.py +479 -0
  48. mcli/lib/services/data_pipeline.py +460 -0
  49. mcli/lib/services/lsh_client.py +441 -0
  50. mcli/lib/services/redis_service.py +387 -0
  51. mcli/lib/shell/shell.py +137 -0
  52. mcli/lib/toml/toml.py +33 -0
  53. mcli/lib/ui/styling.py +47 -0
  54. mcli/lib/ui/visual_effects.py +634 -0
  55. mcli/lib/watcher/watcher.py +185 -0
  56. mcli/ml/api/app.py +215 -0
  57. mcli/ml/api/middleware.py +224 -0
  58. mcli/ml/api/routers/admin_router.py +12 -0
  59. mcli/ml/api/routers/auth_router.py +244 -0
  60. mcli/ml/api/routers/backtest_router.py +12 -0
  61. mcli/ml/api/routers/data_router.py +12 -0
  62. mcli/ml/api/routers/model_router.py +302 -0
  63. mcli/ml/api/routers/monitoring_router.py +12 -0
  64. mcli/ml/api/routers/portfolio_router.py +12 -0
  65. mcli/ml/api/routers/prediction_router.py +267 -0
  66. mcli/ml/api/routers/trade_router.py +12 -0
  67. mcli/ml/api/routers/websocket_router.py +76 -0
  68. mcli/ml/api/schemas.py +64 -0
  69. mcli/ml/auth/auth_manager.py +425 -0
  70. mcli/ml/auth/models.py +154 -0
  71. mcli/ml/auth/permissions.py +302 -0
  72. mcli/ml/backtesting/backtest_engine.py +502 -0
  73. mcli/ml/backtesting/performance_metrics.py +393 -0
  74. mcli/ml/cache.py +400 -0
  75. mcli/ml/cli/main.py +398 -0
  76. mcli/ml/config/settings.py +394 -0
  77. mcli/ml/configs/dvc_config.py +230 -0
  78. mcli/ml/configs/mlflow_config.py +131 -0
  79. mcli/ml/configs/mlops_manager.py +293 -0
  80. mcli/ml/dashboard/app.py +532 -0
  81. mcli/ml/dashboard/app_integrated.py +738 -0
  82. mcli/ml/dashboard/app_supabase.py +560 -0
  83. mcli/ml/dashboard/app_training.py +615 -0
  84. mcli/ml/dashboard/cli.py +51 -0
  85. mcli/ml/data_ingestion/api_connectors.py +501 -0
  86. mcli/ml/data_ingestion/data_pipeline.py +567 -0
  87. mcli/ml/data_ingestion/stream_processor.py +512 -0
  88. mcli/ml/database/migrations/env.py +94 -0
  89. mcli/ml/database/models.py +667 -0
  90. mcli/ml/database/session.py +200 -0
  91. mcli/ml/experimentation/ab_testing.py +845 -0
  92. mcli/ml/features/ensemble_features.py +607 -0
  93. mcli/ml/features/political_features.py +676 -0
  94. mcli/ml/features/recommendation_engine.py +809 -0
  95. mcli/ml/features/stock_features.py +573 -0
  96. mcli/ml/features/test_feature_engineering.py +346 -0
  97. mcli/ml/logging.py +85 -0
  98. mcli/ml/mlops/data_versioning.py +518 -0
  99. mcli/ml/mlops/experiment_tracker.py +377 -0
  100. mcli/ml/mlops/model_serving.py +481 -0
  101. mcli/ml/mlops/pipeline_orchestrator.py +614 -0
  102. mcli/ml/models/base_models.py +324 -0
  103. mcli/ml/models/ensemble_models.py +675 -0
  104. mcli/ml/models/recommendation_models.py +474 -0
  105. mcli/ml/models/test_models.py +487 -0
  106. mcli/ml/monitoring/drift_detection.py +676 -0
  107. mcli/ml/monitoring/metrics.py +45 -0
  108. mcli/ml/optimization/portfolio_optimizer.py +834 -0
  109. mcli/ml/preprocessing/data_cleaners.py +451 -0
  110. mcli/ml/preprocessing/feature_extractors.py +491 -0
  111. mcli/ml/preprocessing/ml_pipeline.py +382 -0
  112. mcli/ml/preprocessing/politician_trading_preprocessor.py +569 -0
  113. mcli/ml/preprocessing/test_preprocessing.py +294 -0
  114. mcli/ml/scripts/populate_sample_data.py +200 -0
  115. mcli/ml/tasks.py +400 -0
  116. mcli/ml/tests/test_integration.py +429 -0
  117. mcli/ml/tests/test_training_dashboard.py +387 -0
  118. mcli/public/oi/oi.py +15 -0
  119. mcli/public/public.py +4 -0
  120. mcli/self/self_cmd.py +1246 -0
  121. mcli/workflow/daemon/api_daemon.py +800 -0
  122. mcli/workflow/daemon/async_command_database.py +681 -0
  123. mcli/workflow/daemon/async_process_manager.py +591 -0
  124. mcli/workflow/daemon/client.py +530 -0
  125. mcli/workflow/daemon/commands.py +1196 -0
  126. mcli/workflow/daemon/daemon.py +905 -0
  127. mcli/workflow/daemon/daemon_api.py +59 -0
  128. mcli/workflow/daemon/enhanced_daemon.py +571 -0
  129. mcli/workflow/daemon/process_cli.py +244 -0
  130. mcli/workflow/daemon/process_manager.py +439 -0
  131. mcli/workflow/daemon/test_daemon.py +275 -0
  132. mcli/workflow/dashboard/dashboard_cmd.py +113 -0
  133. mcli/workflow/docker/docker.py +0 -0
  134. mcli/workflow/file/file.py +100 -0
  135. mcli/workflow/gcloud/config.toml +21 -0
  136. mcli/workflow/gcloud/gcloud.py +58 -0
  137. mcli/workflow/git_commit/ai_service.py +328 -0
  138. mcli/workflow/git_commit/commands.py +430 -0
  139. mcli/workflow/lsh_integration.py +355 -0
  140. mcli/workflow/model_service/client.py +594 -0
  141. mcli/workflow/model_service/download_and_run_efficient_models.py +288 -0
  142. mcli/workflow/model_service/lightweight_embedder.py +397 -0
  143. mcli/workflow/model_service/lightweight_model_server.py +714 -0
  144. mcli/workflow/model_service/lightweight_test.py +241 -0
  145. mcli/workflow/model_service/model_service.py +1955 -0
  146. mcli/workflow/model_service/ollama_efficient_runner.py +425 -0
  147. mcli/workflow/model_service/pdf_processor.py +386 -0
  148. mcli/workflow/model_service/test_efficient_runner.py +234 -0
  149. mcli/workflow/model_service/test_example.py +315 -0
  150. mcli/workflow/model_service/test_integration.py +131 -0
  151. mcli/workflow/model_service/test_new_features.py +149 -0
  152. mcli/workflow/openai/openai.py +99 -0
  153. mcli/workflow/politician_trading/commands.py +1790 -0
  154. mcli/workflow/politician_trading/config.py +134 -0
  155. mcli/workflow/politician_trading/connectivity.py +490 -0
  156. mcli/workflow/politician_trading/data_sources.py +395 -0
  157. mcli/workflow/politician_trading/database.py +410 -0
  158. mcli/workflow/politician_trading/demo.py +248 -0
  159. mcli/workflow/politician_trading/models.py +165 -0
  160. mcli/workflow/politician_trading/monitoring.py +413 -0
  161. mcli/workflow/politician_trading/scrapers.py +966 -0
  162. mcli/workflow/politician_trading/scrapers_california.py +412 -0
  163. mcli/workflow/politician_trading/scrapers_eu.py +377 -0
  164. mcli/workflow/politician_trading/scrapers_uk.py +350 -0
  165. mcli/workflow/politician_trading/scrapers_us_states.py +438 -0
  166. mcli/workflow/politician_trading/supabase_functions.py +354 -0
  167. mcli/workflow/politician_trading/workflow.py +852 -0
  168. mcli/workflow/registry/registry.py +180 -0
  169. mcli/workflow/repo/repo.py +223 -0
  170. mcli/workflow/scheduler/commands.py +493 -0
  171. mcli/workflow/scheduler/cron_parser.py +238 -0
  172. mcli/workflow/scheduler/job.py +182 -0
  173. mcli/workflow/scheduler/monitor.py +139 -0
  174. mcli/workflow/scheduler/persistence.py +324 -0
  175. mcli/workflow/scheduler/scheduler.py +679 -0
  176. mcli/workflow/sync/sync_cmd.py +437 -0
  177. mcli/workflow/sync/test_cmd.py +314 -0
  178. mcli/workflow/videos/videos.py +242 -0
  179. mcli/workflow/wakatime/wakatime.py +11 -0
  180. mcli/workflow/workflow.py +37 -0
  181. mcli_framework-7.0.0.dist-info/METADATA +479 -0
  182. mcli_framework-7.0.0.dist-info/RECORD +186 -0
  183. mcli_framework-7.0.0.dist-info/WHEEL +5 -0
  184. mcli_framework-7.0.0.dist-info/entry_points.txt +7 -0
  185. mcli_framework-7.0.0.dist-info/licenses/LICENSE +21 -0
  186. mcli_framework-7.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1010 @@
1
+ """
2
+ System Controller for MCLI Chat
3
+ Allows the chat interface to directly control system applications and execute commands
4
+ """
5
+
6
+ import os
7
+ import platform
8
+ import subprocess
9
+ import time
10
+ from datetime import datetime
11
+ from pathlib import Path
12
+ from typing import Any, Dict, List, Optional
13
+
14
+ from mcli.lib.logger.logger import get_logger
15
+
16
+ logger = get_logger(__name__)
17
+
18
+
19
+ class SystemController:
20
+ """Handles real-time system control for MCLI chat"""
21
+
22
+ def __init__(self):
23
+ self.system = platform.system()
24
+ self.enabled = True
25
+ self.current_directory = os.getcwd() # Track current working directory
26
+
27
+ def execute_command(self, command: str, description: str = "") -> Dict[str, Any]:
28
+ """Execute a system command and return results"""
29
+ try:
30
+ logger.info(f"Executing: {description or command}")
31
+
32
+ result = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=30)
33
+
34
+ return {
35
+ "success": result.returncode == 0,
36
+ "output": result.stdout,
37
+ "error": result.stderr,
38
+ "command": command,
39
+ "description": description,
40
+ }
41
+
42
+ except subprocess.TimeoutExpired:
43
+ return {
44
+ "success": False,
45
+ "output": "",
46
+ "error": "Command timed out after 30 seconds",
47
+ "command": command,
48
+ "description": description,
49
+ }
50
+ except Exception as e:
51
+ return {
52
+ "success": False,
53
+ "output": "",
54
+ "error": str(e),
55
+ "command": command,
56
+ "description": description,
57
+ }
58
+
59
+ def open_textedit_and_write(self, text: str, filename: str = None) -> Dict[str, Any]:
60
+ """Open TextEdit, write text, and optionally save to file"""
61
+ if self.system != "Darwin":
62
+ return {
63
+ "success": False,
64
+ "error": "TextEdit is only available on macOS",
65
+ "description": "Open TextEdit and write text",
66
+ }
67
+
68
+ # Escape quotes in text for AppleScript
69
+ escaped_text = text.replace('"', '\\"').replace("'", "\\'")
70
+
71
+ if filename:
72
+ # Save to specified file
73
+ save_path = f'(path to desktop as string) & "{filename}"'
74
+ save_command = f"save front document in {save_path}"
75
+ else:
76
+ # Just create new document without saving
77
+ save_command = ""
78
+
79
+ applescript = f"""
80
+ tell application "TextEdit"
81
+ activate
82
+ delay 0.5
83
+ make new document
84
+ delay 0.5
85
+ set text of front document to "{escaped_text}"
86
+ delay 1
87
+ {save_command}
88
+ end tell
89
+ """
90
+
91
+ return self.execute_command(
92
+ f"osascript -e '{applescript}'", f"Open TextEdit and write: {text[:50]}..."
93
+ )
94
+
95
+ def control_application(self, app_name: str, action: str, **kwargs) -> Dict[str, Any]:
96
+ """Control various applications with different actions"""
97
+
98
+ if self.system == "Darwin": # macOS
99
+ return self._control_macos_app(app_name, action, **kwargs)
100
+ elif self.system == "Windows":
101
+ return self._control_windows_app(app_name, action, **kwargs)
102
+ else: # Linux
103
+ return self._control_linux_app(app_name, action, **kwargs)
104
+
105
+ def _control_macos_app(self, app_name: str, action: str, **kwargs) -> Dict[str, Any]:
106
+ """Control macOS applications using AppleScript"""
107
+
108
+ if action == "open":
109
+ applescript = f'tell application "{app_name}" to activate'
110
+
111
+ elif action == "close":
112
+ applescript = f'tell application "{app_name}" to quit'
113
+
114
+ elif action == "write_text" and app_name.lower() == "textedit":
115
+ text = kwargs.get("text", "Hello, World!")
116
+ filename = kwargs.get("filename")
117
+ return self.open_textedit_and_write(text, filename)
118
+
119
+ elif action == "new_document":
120
+ if app_name.lower() == "textedit":
121
+ applescript = f"""
122
+ tell application "{app_name}"
123
+ activate
124
+ delay 0.5
125
+ make new document
126
+ end tell
127
+ """
128
+ else:
129
+ applescript = f'tell application "{app_name}" to make new document'
130
+
131
+ elif action == "type_text":
132
+ text = kwargs.get("text", "Hello, World!")
133
+ escaped_text = text.replace('"', '\\"')
134
+ applescript = f"""
135
+ tell application "System Events"
136
+ tell process "{app_name}"
137
+ keystroke "{escaped_text}"
138
+ end tell
139
+ end tell
140
+ """
141
+
142
+ else:
143
+ return {
144
+ "success": False,
145
+ "error": f"Unknown action '{action}' for {app_name}",
146
+ "description": f"Control {app_name}",
147
+ }
148
+
149
+ return self.execute_command(f"osascript -e '{applescript}'", f"{action} {app_name}")
150
+
151
+ def _control_windows_app(self, app_name: str, action: str, **kwargs) -> Dict[str, Any]:
152
+ """Control Windows applications using PowerShell"""
153
+
154
+ if action == "open":
155
+ # Try to start the application
156
+ command = f"powershell -Command \"Start-Process '{app_name}'\""
157
+
158
+ elif action == "close":
159
+ command = f"powershell -Command \"Stop-Process -Name '{app_name}' -Force\""
160
+
161
+ elif action == "write_text":
162
+ text = kwargs.get("text", "Hello, World!")
163
+ # This is a simplified version - would need more complex implementation
164
+ command = f"powershell -Command \"Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('{text}')\""
165
+
166
+ else:
167
+ return {
168
+ "success": False,
169
+ "error": f"Action '{action}' not implemented for Windows",
170
+ "description": f"Control {app_name}",
171
+ }
172
+
173
+ return self.execute_command(command, f"{action} {app_name}")
174
+
175
+ def _control_linux_app(self, app_name: str, action: str, **kwargs) -> Dict[str, Any]:
176
+ """Control Linux applications using various tools"""
177
+
178
+ if action == "open":
179
+ # Try different methods to open applications
180
+ commands = [
181
+ f"{app_name.lower()}",
182
+ f"gtk-launch {app_name.lower()}",
183
+ f"xdg-open {app_name.lower()}",
184
+ ]
185
+
186
+ for cmd in commands:
187
+ result = self.execute_command(cmd, f"Open {app_name}")
188
+ if result["success"]:
189
+ return result
190
+
191
+ return {
192
+ "success": False,
193
+ "error": f"Could not open {app_name}",
194
+ "description": f"Open {app_name}",
195
+ }
196
+
197
+ elif action == "close":
198
+ command = f"pkill -f {app_name.lower()}"
199
+
200
+ elif action == "write_text":
201
+ text = kwargs.get("text", "Hello, World!")
202
+ # Use xdotool if available
203
+ command = f'xdotool type "{text}"'
204
+
205
+ else:
206
+ return {
207
+ "success": False,
208
+ "error": f"Action '{action}' not implemented for Linux",
209
+ "description": f"Control {app_name}",
210
+ }
211
+
212
+ return self.execute_command(command, f"{action} {app_name}")
213
+
214
+ def get_system_info(self) -> Dict[str, Any]:
215
+ """Get comprehensive system information"""
216
+ try:
217
+ import platform
218
+ from datetime import datetime
219
+
220
+ import psutil
221
+
222
+ # Basic system info
223
+ info = {
224
+ "timestamp": datetime.now().isoformat(),
225
+ "system": platform.system(),
226
+ "platform": platform.platform(),
227
+ "machine": platform.machine(),
228
+ "processor": platform.processor(),
229
+ "python_version": platform.python_version(),
230
+ }
231
+
232
+ # CPU information
233
+ cpu_info = {
234
+ "physical_cores": psutil.cpu_count(logical=False) or 0,
235
+ "logical_cores": psutil.cpu_count(logical=True) or 0,
236
+ "cpu_usage_percent": psutil.cpu_percent(
237
+ interval=0.1
238
+ ), # Shorter interval to avoid hanging
239
+ "cpu_frequency": None,
240
+ }
241
+
242
+ # Try to get CPU frequency safely
243
+ try:
244
+ freq = psutil.cpu_freq()
245
+ if freq:
246
+ cpu_info["cpu_frequency"] = freq._asdict()
247
+ except Exception:
248
+ pass
249
+ info["cpu"] = cpu_info
250
+
251
+ # Memory information
252
+ memory = psutil.virtual_memory()
253
+ memory_info = {
254
+ "total_gb": round(memory.total / (1024**3), 2),
255
+ "available_gb": round(memory.available / (1024**3), 2),
256
+ "used_gb": round(memory.used / (1024**3), 2),
257
+ "usage_percent": memory.percent,
258
+ "free_gb": round(memory.free / (1024**3), 2),
259
+ }
260
+ info["memory"] = memory_info
261
+
262
+ # Disk information
263
+ disk_info = []
264
+ try:
265
+ for partition in psutil.disk_partitions():
266
+ try:
267
+ partition_usage = psutil.disk_usage(partition.mountpoint)
268
+ disk_info.append(
269
+ {
270
+ "device": partition.device,
271
+ "mountpoint": partition.mountpoint,
272
+ "filesystem": partition.fstype,
273
+ "total_gb": round(partition_usage.total / (1024**3), 2),
274
+ "used_gb": round(partition_usage.used / (1024**3), 2),
275
+ "free_gb": round(partition_usage.free / (1024**3), 2),
276
+ "usage_percent": round(
277
+ (partition_usage.used / partition_usage.total) * 100, 1
278
+ ),
279
+ }
280
+ )
281
+ except (PermissionError, OSError):
282
+ # Skip partitions we can't access
283
+ continue
284
+ except Exception:
285
+ pass
286
+ info["disks"] = disk_info
287
+
288
+ # Network information
289
+ network_info = {
290
+ "interfaces": {},
291
+ "connections": 0,
292
+ }
293
+
294
+ try:
295
+ network_info["connections"] = len(psutil.net_connections())
296
+ except Exception:
297
+ pass
298
+
299
+ try:
300
+ for interface, addresses in psutil.net_if_addrs().items():
301
+ network_info["interfaces"][interface] = []
302
+ for addr in addresses:
303
+ try:
304
+ network_info["interfaces"][interface].append(
305
+ {
306
+ "family": str(addr.family),
307
+ "address": addr.address,
308
+ "netmask": addr.netmask,
309
+ "broadcast": addr.broadcast,
310
+ }
311
+ )
312
+ except Exception:
313
+ continue
314
+ except Exception:
315
+ pass
316
+
317
+ info["network"] = network_info
318
+
319
+ # Process information
320
+ process_info = {
321
+ "total_processes": 0,
322
+ "running_processes": 0,
323
+ }
324
+
325
+ try:
326
+ process_info["total_processes"] = len(psutil.pids())
327
+ # Count running processes more safely
328
+ running_count = 0
329
+ for proc in psutil.process_iter(["status"]):
330
+ try:
331
+ if proc.info["status"] == psutil.STATUS_RUNNING:
332
+ running_count += 1
333
+ except (psutil.NoSuchProcess, psutil.AccessDenied):
334
+ continue
335
+ process_info["running_processes"] = running_count
336
+ except Exception:
337
+ pass
338
+ info["processes"] = process_info
339
+
340
+ # Boot time
341
+ try:
342
+ import datetime
343
+
344
+ boot_time = datetime.datetime.fromtimestamp(psutil.boot_time())
345
+ info["boot_time"] = boot_time.isoformat()
346
+ uptime_seconds = (datetime.datetime.now() - boot_time).total_seconds()
347
+ info["uptime_hours"] = round(uptime_seconds / 3600, 2)
348
+ except Exception:
349
+ info["boot_time"] = None
350
+ info["uptime_hours"] = 0
351
+
352
+ return {
353
+ "success": True,
354
+ "data": info,
355
+ "description": "System information retrieved successfully",
356
+ }
357
+
358
+ except ImportError:
359
+ return {
360
+ "success": False,
361
+ "error": "psutil library not available. Install with: pip install psutil",
362
+ "description": "Get system information",
363
+ }
364
+ except Exception as e:
365
+ return {"success": False, "error": str(e), "description": "Get system information"}
366
+
367
+ def get_system_time(self) -> Dict[str, Any]:
368
+ """Get current system time and timezone information"""
369
+ try:
370
+ import time
371
+ from datetime import datetime
372
+
373
+ now = datetime.now()
374
+
375
+ time_info = {
376
+ "current_time": now.strftime("%Y-%m-%d %H:%M:%S"),
377
+ "timezone": time.tzname,
378
+ "timestamp": now.timestamp(),
379
+ "iso_format": now.isoformat(),
380
+ "day_of_week": now.strftime("%A"),
381
+ "day_of_year": now.timetuple().tm_yday,
382
+ "week_number": now.isocalendar().week,
383
+ }
384
+
385
+ return {"success": True, "data": time_info, "description": "Current system time"}
386
+
387
+ except Exception as e:
388
+ return {"success": False, "error": str(e), "description": "Get system time"}
389
+
390
+ def get_memory_usage(self) -> Dict[str, Any]:
391
+ """Get detailed memory usage information"""
392
+ try:
393
+ import psutil
394
+
395
+ # Virtual memory
396
+ memory = psutil.virtual_memory()
397
+
398
+ # Swap memory
399
+ swap = psutil.swap_memory()
400
+
401
+ memory_info = {
402
+ "virtual_memory": {
403
+ "total_gb": round(memory.total / (1024**3), 2),
404
+ "available_gb": round(memory.available / (1024**3), 2),
405
+ "used_gb": round(memory.used / (1024**3), 2),
406
+ "free_gb": round(memory.free / (1024**3), 2),
407
+ "usage_percent": memory.percent,
408
+ "buffers_gb": round(getattr(memory, "buffers", 0) / (1024**3), 2),
409
+ "cached_gb": round(getattr(memory, "cached", 0) / (1024**3), 2),
410
+ },
411
+ "swap_memory": {
412
+ "total_gb": round(swap.total / (1024**3), 2),
413
+ "used_gb": round(swap.used / (1024**3), 2),
414
+ "free_gb": round(swap.free / (1024**3), 2),
415
+ "usage_percent": swap.percent,
416
+ },
417
+ "recommendations": [],
418
+ }
419
+
420
+ # Add cleanup recommendations
421
+ if memory.percent > 80:
422
+ memory_info["recommendations"].append(
423
+ "High memory usage detected - consider closing unused applications"
424
+ )
425
+
426
+ if memory.percent > 90:
427
+ memory_info["recommendations"].append(
428
+ "Critical memory usage - immediate cleanup recommended"
429
+ )
430
+
431
+ if swap.percent > 50:
432
+ memory_info["recommendations"].append("High swap usage - consider adding more RAM")
433
+
434
+ return {"success": True, "data": memory_info, "description": "Memory usage information"}
435
+
436
+ except ImportError:
437
+ return {
438
+ "success": False,
439
+ "error": "psutil library not available",
440
+ "description": "Get memory usage",
441
+ }
442
+ except Exception as e:
443
+ return {"success": False, "error": str(e), "description": "Get memory usage"}
444
+
445
+ def get_disk_usage(self) -> Dict[str, Any]:
446
+ """Get detailed disk usage information"""
447
+ try:
448
+ import psutil
449
+
450
+ disk_info = {
451
+ "partitions": [],
452
+ "total_disk_gb": 0,
453
+ "total_used_gb": 0,
454
+ "total_free_gb": 0,
455
+ "recommendations": [],
456
+ }
457
+
458
+ for partition in psutil.disk_partitions():
459
+ try:
460
+ usage = psutil.disk_usage(partition.mountpoint)
461
+
462
+ partition_info = {
463
+ "device": partition.device,
464
+ "mountpoint": partition.mountpoint,
465
+ "filesystem": partition.fstype,
466
+ "total_gb": round(usage.total / (1024**3), 2),
467
+ "used_gb": round(usage.used / (1024**3), 2),
468
+ "free_gb": round(usage.free / (1024**3), 2),
469
+ "usage_percent": round((usage.used / usage.total) * 100, 1),
470
+ }
471
+
472
+ disk_info["partitions"].append(partition_info)
473
+
474
+ # Add to totals (main disk only)
475
+ if partition.mountpoint == "/" or partition.mountpoint == "C:\\":
476
+ disk_info["total_disk_gb"] = partition_info["total_gb"]
477
+ disk_info["total_used_gb"] = partition_info["used_gb"]
478
+ disk_info["total_free_gb"] = partition_info["free_gb"]
479
+
480
+ # Add recommendations
481
+ if partition_info["usage_percent"] > 85:
482
+ disk_info["recommendations"].append(
483
+ f"Disk {partition.mountpoint} is {partition_info['usage_percent']}% full - cleanup recommended"
484
+ )
485
+
486
+ if partition_info["usage_percent"] > 95:
487
+ disk_info["recommendations"].append(
488
+ f"Critical: Disk {partition.mountpoint} is nearly full!"
489
+ )
490
+
491
+ except PermissionError:
492
+ continue
493
+
494
+ return {"success": True, "data": disk_info, "description": "Disk usage information"}
495
+
496
+ except ImportError:
497
+ return {
498
+ "success": False,
499
+ "error": "psutil library not available",
500
+ "description": "Get disk usage",
501
+ }
502
+ except Exception as e:
503
+ return {"success": False, "error": str(e), "description": "Get disk usage"}
504
+
505
+ def clear_system_caches(self) -> Dict[str, Any]:
506
+ """Clear system caches and temporary files"""
507
+ try:
508
+ cleared_items = []
509
+ total_freed_mb = 0
510
+
511
+ if self.system == "Darwin": # macOS
512
+ # Clear user caches
513
+ cache_dirs = [
514
+ "~/Library/Caches",
515
+ "~/Library/Application Support/CrashReporter",
516
+ "/tmp",
517
+ ]
518
+
519
+ for cache_dir in cache_dirs:
520
+ expanded_dir = os.path.expanduser(cache_dir)
521
+ if os.path.exists(expanded_dir):
522
+ try:
523
+ # Calculate size before clearing
524
+ size_before = self._get_directory_size(expanded_dir)
525
+
526
+ # Clear cache (be careful with system directories)
527
+ if cache_dir == "/tmp":
528
+ self.execute_command(
529
+ "find /tmp -type f -atime +1 -delete 2>/dev/null || true",
530
+ "Clear old temp files",
531
+ )
532
+ else:
533
+ # Just clear user cache files that are safe to delete
534
+ self.execute_command(
535
+ f"find '{expanded_dir}' -name '*.cache' -delete 2>/dev/null || true",
536
+ f"Clear {cache_dir}",
537
+ )
538
+
539
+ size_after = self._get_directory_size(expanded_dir)
540
+ freed_mb = max(0, (size_before - size_after) / (1024 * 1024))
541
+
542
+ if freed_mb > 0:
543
+ cleared_items.append(f"Cleared {cache_dir}: {freed_mb:.1f} MB")
544
+ total_freed_mb += freed_mb
545
+
546
+ except Exception as e:
547
+ cleared_items.append(f"Could not clear {cache_dir}: {e}")
548
+
549
+ # Clear DNS cache
550
+ dns_result = self.execute_command(
551
+ "sudo dscacheutil -flushcache 2>/dev/null || dscacheutil -flushcache",
552
+ "Flush DNS cache",
553
+ )
554
+ if dns_result.get("success"):
555
+ cleared_items.append("Flushed DNS cache")
556
+
557
+ elif self.system == "Windows":
558
+ # Windows cache clearing
559
+ commands = [
560
+ ("del /q /f /s %TEMP%\\*", "Clear temp files"),
561
+ ("del /q /f /s C:\\Windows\\Temp\\*", "Clear Windows temp"),
562
+ ("ipconfig /flushdns", "Flush DNS cache"),
563
+ ]
564
+
565
+ for cmd, desc in commands:
566
+ result = self.execute_command(cmd, desc)
567
+ if result.get("success"):
568
+ cleared_items.append(desc)
569
+
570
+ else: # Linux
571
+ # Linux cache clearing
572
+ commands = [
573
+ (
574
+ "find /tmp -type f -atime +1 -delete 2>/dev/null || true",
575
+ "Clear old temp files",
576
+ ),
577
+ (
578
+ "sync && echo 3 > /proc/sys/vm/drop_caches 2>/dev/null || true",
579
+ "Clear page cache",
580
+ ),
581
+ ]
582
+
583
+ for cmd, desc in commands:
584
+ result = self.execute_command(cmd, desc)
585
+ if result.get("success"):
586
+ cleared_items.append(desc)
587
+
588
+ return {
589
+ "success": True,
590
+ "data": {
591
+ "cleared_items": cleared_items,
592
+ "total_freed_mb": round(total_freed_mb, 1),
593
+ },
594
+ "description": "System cache clearing",
595
+ }
596
+
597
+ except Exception as e:
598
+ return {"success": False, "error": str(e), "description": "Clear system caches"}
599
+
600
+ def _get_directory_size(self, path: str) -> int:
601
+ """Get total size of directory in bytes"""
602
+ total_size = 0
603
+ try:
604
+ for dirpath, dirnames, filenames in os.walk(path):
605
+ for filename in filenames:
606
+ file_path = os.path.join(dirpath, filename)
607
+ try:
608
+ total_size += os.path.getsize(file_path)
609
+ except (OSError, FileNotFoundError):
610
+ continue
611
+ except Exception:
612
+ pass
613
+ return total_size
614
+
615
+ def get_running_applications(self) -> List[str]:
616
+ """Get list of currently running applications"""
617
+ try:
618
+ if self.system == "Darwin":
619
+ result = subprocess.run(
620
+ [
621
+ "osascript",
622
+ "-e",
623
+ 'tell application "System Events" to get name of every process whose background only is false',
624
+ ],
625
+ capture_output=True,
626
+ text=True,
627
+ )
628
+
629
+ if result.returncode == 0:
630
+ # Parse the AppleScript result
631
+ apps = result.stdout.strip().split(", ")
632
+ return [app.strip() for app in apps if app.strip()]
633
+
634
+ elif self.system == "Windows":
635
+ result = subprocess.run(
636
+ [
637
+ "powershell",
638
+ "-Command",
639
+ "Get-Process | Where-Object {$_.MainWindowTitle -ne ''} | Select-Object -ExpandProperty ProcessName",
640
+ ],
641
+ capture_output=True,
642
+ text=True,
643
+ )
644
+
645
+ if result.returncode == 0:
646
+ return [line.strip() for line in result.stdout.split("\n") if line.strip()]
647
+
648
+ else: # Linux
649
+ result = subprocess.run(["ps", "aux"], capture_output=True, text=True)
650
+
651
+ if result.returncode == 0:
652
+ lines = result.stdout.split("\n")[1:] # Skip header
653
+ processes = []
654
+ for line in lines:
655
+ if line.strip():
656
+ parts = line.split()
657
+ if len(parts) > 10:
658
+ processes.append(parts[10]) # Command name
659
+ return list(set(processes))
660
+
661
+ except Exception as e:
662
+ logger.error(f"Error getting running applications: {e}")
663
+
664
+ return []
665
+
666
+ def take_screenshot(self, filename: str = None) -> Dict[str, Any]:
667
+ """Take a screenshot and save it"""
668
+ if not filename:
669
+ timestamp = time.strftime("%Y%m%d_%H%M%S")
670
+ filename = f"mcli_screenshot_{timestamp}.png"
671
+
672
+ desktop_path = Path.home() / "Desktop" / filename
673
+
674
+ try:
675
+ if self.system == "Darwin":
676
+ command = f"screencapture -x '{desktop_path}'"
677
+ elif self.system == "Windows":
678
+ command = f"powershell -Command \"Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.Screen]::PrimaryScreen.Bounds | Out-Null; [System.Drawing.Bitmap]::new([System.Windows.Forms.Screen]::PrimaryScreen.Bounds.Width, [System.Windows.Forms.Screen]::PrimaryScreen.Bounds.Height).Save('{desktop_path}', [System.Drawing.Imaging.ImageFormat]::Png)\""
679
+ else: # Linux
680
+ command = f"gnome-screenshot -f '{desktop_path}'"
681
+
682
+ result = self.execute_command(command, f"Take screenshot: {filename}")
683
+
684
+ if result["success"]:
685
+ result["screenshot_path"] = str(desktop_path)
686
+
687
+ return result
688
+
689
+ except Exception as e:
690
+ return {
691
+ "success": False,
692
+ "error": str(e),
693
+ "description": f"Take screenshot: {filename}",
694
+ }
695
+
696
+ def open_file_or_url(self, path_or_url: str) -> Dict[str, Any]:
697
+ """Open a file or URL using the system default application"""
698
+ try:
699
+ if self.system == "Darwin":
700
+ command = f"open '{path_or_url}'"
701
+ elif self.system == "Windows":
702
+ command = f'start "" "{path_or_url}"'
703
+ else: # Linux
704
+ command = f"xdg-open '{path_or_url}'"
705
+
706
+ return self.execute_command(command, f"Open: {path_or_url}")
707
+
708
+ except Exception as e:
709
+ return {"success": False, "error": str(e), "description": f"Open: {path_or_url}"}
710
+
711
+ def change_directory(self, path: str) -> Dict[str, Any]:
712
+ """Navigate to a directory and update current working directory"""
713
+ try:
714
+ # Expand user path and resolve relative paths
715
+ expanded_path = os.path.expanduser(path)
716
+ resolved_path = os.path.abspath(expanded_path)
717
+
718
+ if not os.path.exists(resolved_path):
719
+ return {
720
+ "success": False,
721
+ "error": f"Directory does not exist: {resolved_path}",
722
+ "current_directory": self.current_directory,
723
+ }
724
+
725
+ if not os.path.isdir(resolved_path):
726
+ return {
727
+ "success": False,
728
+ "error": f"Path is not a directory: {resolved_path}",
729
+ "current_directory": self.current_directory,
730
+ }
731
+
732
+ # Change directory
733
+ os.chdir(resolved_path)
734
+ self.current_directory = resolved_path
735
+
736
+ return {
737
+ "success": True,
738
+ "current_directory": self.current_directory,
739
+ "message": f"Changed to directory: {self.current_directory}",
740
+ "description": f"Navigate to {path}",
741
+ }
742
+
743
+ except Exception as e:
744
+ return {
745
+ "success": False,
746
+ "error": str(e),
747
+ "current_directory": self.current_directory,
748
+ "description": f"Navigate to {path}",
749
+ }
750
+
751
+ def list_directory(
752
+ self, path: str = None, show_hidden: bool = False, detailed: bool = False
753
+ ) -> Dict[str, Any]:
754
+ """List contents of a directory"""
755
+ try:
756
+ target_path = path if path else self.current_directory
757
+ expanded_path = os.path.expanduser(target_path)
758
+ resolved_path = os.path.abspath(expanded_path)
759
+
760
+ if not os.path.exists(resolved_path):
761
+ return {
762
+ "success": False,
763
+ "error": f"Directory does not exist: {resolved_path}",
764
+ "current_directory": self.current_directory,
765
+ }
766
+
767
+ if not os.path.isdir(resolved_path):
768
+ return {
769
+ "success": False,
770
+ "error": f"Path is not a directory: {resolved_path}",
771
+ "current_directory": self.current_directory,
772
+ }
773
+
774
+ # Get directory contents
775
+ entries = []
776
+ try:
777
+ for item in os.listdir(resolved_path):
778
+ if not show_hidden and item.startswith("."):
779
+ continue
780
+
781
+ item_path = os.path.join(resolved_path, item)
782
+ stat_info = os.stat(item_path)
783
+
784
+ entry = {
785
+ "name": item,
786
+ "type": "directory" if os.path.isdir(item_path) else "file",
787
+ "size": stat_info.st_size if not os.path.isdir(item_path) else None,
788
+ "modified": datetime.fromtimestamp(stat_info.st_mtime).isoformat(),
789
+ }
790
+
791
+ if detailed:
792
+ entry.update(
793
+ {
794
+ "permissions": oct(stat_info.st_mode)[-3:],
795
+ "owner": stat_info.st_uid,
796
+ "group": stat_info.st_gid,
797
+ }
798
+ )
799
+
800
+ entries.append(entry)
801
+
802
+ # Sort: directories first, then files
803
+ entries.sort(key=lambda x: (x["type"] != "directory", x["name"]))
804
+
805
+ except PermissionError:
806
+ return {
807
+ "success": False,
808
+ "error": f"Permission denied accessing: {resolved_path}",
809
+ "current_directory": self.current_directory,
810
+ }
811
+
812
+ return {
813
+ "success": True,
814
+ "path": resolved_path,
815
+ "entries": entries,
816
+ "current_directory": self.current_directory,
817
+ "description": f"List directory: {resolved_path}",
818
+ }
819
+
820
+ except Exception as e:
821
+ return {
822
+ "success": False,
823
+ "error": str(e),
824
+ "current_directory": self.current_directory,
825
+ "description": f"List directory: {path or 'current'}",
826
+ }
827
+
828
+ def clean_simulator_data(self) -> Dict[str, Any]:
829
+ """Clean iOS/watchOS simulator data specifically"""
830
+ try:
831
+ if self.system != "Darwin":
832
+ return {
833
+ "success": False,
834
+ "error": "Simulator cleanup is only available on macOS",
835
+ "description": "Clean simulator data",
836
+ }
837
+
838
+ simulator_base = os.path.expanduser("~/Library/Developer/CoreSimulator")
839
+ if not os.path.exists(simulator_base):
840
+ return {
841
+ "success": False,
842
+ "error": "No simulator data found",
843
+ "description": "Clean simulator data",
844
+ }
845
+
846
+ cleaned_items = []
847
+ total_freed_bytes = 0
848
+
849
+ # Paths to clean
850
+ cleanup_paths = [
851
+ "Devices/*/data/Containers/Data/Application/*/Library/Caches",
852
+ "Devices/*/data/Containers/Data/Application/*/tmp",
853
+ "Devices/*/data/Library/Caches",
854
+ "Devices/*/data/tmp",
855
+ ]
856
+
857
+ import glob
858
+
859
+ for pattern in cleanup_paths:
860
+ full_pattern = os.path.join(simulator_base, pattern)
861
+ matching_paths = glob.glob(full_pattern)
862
+
863
+ for cache_path in matching_paths:
864
+ try:
865
+ # Calculate size before deletion
866
+ size_before = self._get_directory_size(cache_path)
867
+
868
+ # Remove cache contents but keep directory structure
869
+ if os.path.isdir(cache_path):
870
+ import shutil
871
+
872
+ for item in os.listdir(cache_path):
873
+ item_path = os.path.join(cache_path, item)
874
+ if os.path.isfile(item_path):
875
+ os.remove(item_path)
876
+ elif os.path.isdir(item_path):
877
+ shutil.rmtree(item_path, ignore_errors=True)
878
+
879
+ total_freed_bytes += size_before
880
+ cleaned_items.append(f"Cleaned {cache_path}")
881
+
882
+ except Exception as e:
883
+ cleaned_items.append(f"Could not clean {cache_path}: {e}")
884
+
885
+ # Also run xcrun simctl to clean old unavailable simulators
886
+ try:
887
+ unavailable_result = self.execute_command(
888
+ "xcrun simctl delete unavailable", "Remove unavailable simulators"
889
+ )
890
+ if unavailable_result.get("success"):
891
+ cleaned_items.append("Removed unavailable simulator runtimes")
892
+ except Exception:
893
+ pass
894
+
895
+ # Convert bytes to MB
896
+ total_freed_mb = total_freed_bytes / (1024 * 1024)
897
+
898
+ return {
899
+ "success": True,
900
+ "data": {
901
+ "cleaned_items": cleaned_items,
902
+ "total_freed_mb": round(total_freed_mb, 1),
903
+ "freed_bytes": total_freed_bytes,
904
+ },
905
+ "message": f"Simulator cleanup completed. Freed {total_freed_mb:.1f} MB",
906
+ "description": "Clean simulator data",
907
+ }
908
+
909
+ except Exception as e:
910
+ return {"success": False, "error": str(e), "description": "Clean simulator data"}
911
+
912
+ def execute_shell_command(self, command: str, working_directory: str = None) -> Dict[str, Any]:
913
+ """Execute a shell command in a specific directory"""
914
+ try:
915
+ # Use working directory if specified, otherwise use current directory
916
+ cwd = working_directory if working_directory else self.current_directory
917
+
918
+ logger.info(f"Executing shell command in {cwd}: {command}")
919
+
920
+ result = subprocess.run(
921
+ command, shell=True, capture_output=True, text=True, timeout=60, cwd=cwd
922
+ )
923
+
924
+ return {
925
+ "success": result.returncode == 0,
926
+ "output": result.stdout,
927
+ "error": result.stderr,
928
+ "command": command,
929
+ "working_directory": cwd,
930
+ "current_directory": self.current_directory,
931
+ "description": f"Execute: {command}",
932
+ }
933
+
934
+ except subprocess.TimeoutExpired:
935
+ return {
936
+ "success": False,
937
+ "output": "",
938
+ "error": "Command timed out after 60 seconds",
939
+ "command": command,
940
+ "working_directory": cwd,
941
+ "current_directory": self.current_directory,
942
+ "description": f"Execute: {command}",
943
+ }
944
+ except Exception as e:
945
+ return {
946
+ "success": False,
947
+ "output": "",
948
+ "error": str(e),
949
+ "command": command,
950
+ "working_directory": cwd if "cwd" in locals() else self.current_directory,
951
+ "current_directory": self.current_directory,
952
+ "description": f"Execute: {command}",
953
+ }
954
+
955
+
956
+ # Global instance for use in chat
957
+ system_controller = SystemController()
958
+
959
+
960
+ # Helper functions for easy use in chat
961
+ def open_textedit_and_write(text: str, filename: str = None) -> Dict[str, Any]:
962
+ """Helper function to open TextEdit and write text"""
963
+ return system_controller.open_textedit_and_write(text, filename)
964
+
965
+
966
+ def control_app(app_name: str, action: str, **kwargs) -> Dict[str, Any]:
967
+ """Helper function to control applications"""
968
+ return system_controller.control_application(app_name, action, **kwargs)
969
+
970
+
971
+ def execute_system_command(command: str, description: str = "") -> Dict[str, Any]:
972
+ """Helper function to execute system commands"""
973
+ return system_controller.execute_command(command, description)
974
+
975
+
976
+ def take_screenshot(filename: str = None) -> Dict[str, Any]:
977
+ """Helper function to take screenshots"""
978
+ return system_controller.take_screenshot(filename)
979
+
980
+
981
+ def open_file_or_url(path_or_url: str) -> Dict[str, Any]:
982
+ """Helper function to open files or URLs"""
983
+ return system_controller.open_file_or_url(path_or_url)
984
+
985
+
986
+ def change_directory(path: str) -> Dict[str, Any]:
987
+ """Helper function to navigate to a directory"""
988
+ return system_controller.change_directory(path)
989
+
990
+
991
+ def list_directory(
992
+ path: str = None, show_hidden: bool = False, detailed: bool = False
993
+ ) -> Dict[str, Any]:
994
+ """Helper function to list directory contents"""
995
+ return system_controller.list_directory(path, show_hidden, detailed)
996
+
997
+
998
+ def clean_simulator_data() -> Dict[str, Any]:
999
+ """Helper function to clean iOS/watchOS simulator data"""
1000
+ return system_controller.clean_simulator_data()
1001
+
1002
+
1003
+ def execute_shell_command(command: str, working_directory: str = None) -> Dict[str, Any]:
1004
+ """Helper function to execute shell commands"""
1005
+ return system_controller.execute_shell_command(command, working_directory)
1006
+
1007
+
1008
+ def get_current_directory() -> str:
1009
+ """Helper function to get current working directory"""
1010
+ return system_controller.current_directory