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
mcli/app/main.py ADDED
@@ -0,0 +1,492 @@
1
+ import functools
2
+ import importlib
3
+ import inspect
4
+ import os
5
+ import platform
6
+ import sys
7
+ from functools import lru_cache
8
+ from importlib.metadata import metadata, version
9
+ from pathlib import Path
10
+ from typing import List, Optional
11
+
12
+ import click
13
+ import tomli
14
+
15
+ # Import chat command group
16
+ from mcli.app.chat_cmd import chat
17
+ from mcli.lib.logger.logger import disable_runtime_tracing, enable_runtime_tracing, get_logger
18
+ from mcli.lib.ui.styling import info, success
19
+
20
+ # Defer performance optimizations until needed
21
+ _optimization_results = None
22
+
23
+ # Defer API imports until needed
24
+
25
+ # Get logger
26
+ logger = get_logger(__name__)
27
+
28
+ # Enable runtime tracing if environment variable is set
29
+ trace_level = os.environ.get("MCLI_TRACE_LEVEL")
30
+ if trace_level:
31
+ try:
32
+ # Convert to integer (1=function calls, 2=line by line, 3=verbose)
33
+ level = int(trace_level)
34
+ enable_runtime_tracing(level=level)
35
+ logger.info(f"Runtime tracing enabled with level {level}")
36
+ except ValueError:
37
+ logger.warning(f"Invalid MCLI_TRACE_LEVEL value: {trace_level}. Using default level 1.")
38
+ enable_runtime_tracing(level=1)
39
+
40
+ # Defer self management commands import
41
+
42
+ logger.debug("main")
43
+
44
+
45
+ def discover_modules(base_path: Path, config_path: Optional[Path] = None) -> List[str]:
46
+ """
47
+ Discovers Python modules in specified paths.
48
+ Paths must omit trailing backslash.
49
+ """
50
+ modules = []
51
+
52
+ # Try different config file locations
53
+ if config_path is None:
54
+ # Try local config.toml first
55
+ config_paths = [
56
+ Path("config.toml"), # Current directory
57
+ base_path / "config.toml", # mcli directory
58
+ base_path.parent.parent / "config.toml", # Repository root
59
+ ]
60
+ logger.info(f"Config paths: {config_paths}")
61
+
62
+ for path in config_paths:
63
+ if path.exists():
64
+ config_path = path
65
+ break
66
+ else:
67
+ # No config file found, use default
68
+ logger.warning("No config file found, using default configuration")
69
+ config_path = None
70
+
71
+ # Read the TOML configuration file or use default
72
+ logger.info(f"Config path: {config_path.exists() if config_path else 'None'}")
73
+ if config_path and config_path.exists():
74
+ try:
75
+ with open(config_path, "rb") as f:
76
+ config = tomli.load(f)
77
+ logger.debug(f"Config loaded: {config}")
78
+ logger.debug(f"Using config from {config_path}")
79
+ except Exception as e:
80
+ logger.warning(f"Error reading config file {config_path}: {e}")
81
+ config = {"paths": {"included_dirs": ["app", "self", "workflow", "public"]}}
82
+ else:
83
+ logger.warning(f"Config file not found, using default configuration")
84
+ config = {"paths": {"included_dirs": ["app", "self", "workflow", "public"]}}
85
+ excluded_files = {"setup.py", "__init__.py"}
86
+ excluded_dirs = {"resources", "models", "scripts", "private", "venv", ".venv", "__pycache__"}
87
+
88
+ included_dirs = config.get("paths", {}).get("included_dirs", [])
89
+
90
+ logger.debug(f"Included directories: {included_dirs}")
91
+ for directory in included_dirs:
92
+ # Handle nested paths like "app/foo"
93
+ if "/" in directory:
94
+ parts = directory.split("/")
95
+ parent_dir = parts[0]
96
+ sub_dir = "/".join(parts[1:])
97
+ search_path = base_path / parent_dir / sub_dir
98
+ logger.debug(f"Searching in nested path: {search_path}")
99
+
100
+ if search_path.exists():
101
+ for file_path in search_path.rglob("*.py"):
102
+ if file_path.name not in excluded_files and not any(
103
+ excluded_dir in file_path.parts for excluded_dir in excluded_dirs
104
+ ):
105
+ # Convert file path to module name with mcli prefix
106
+ relative_path = file_path.relative_to(base_path.parent)
107
+ module_name = str(relative_path).replace("/", ".").replace(".py", "")
108
+
109
+ # Skip individual workflow submodules to avoid duplicate commands
110
+ if (
111
+ module_name.startswith("mcli.workflow.")
112
+ and module_name != "mcli.workflow.workflow"
113
+ ):
114
+ # Skip individual workflow submodules (e.g., mcli.workflow.daemon.daemon)
115
+ # Only include the main workflow module
116
+ continue
117
+
118
+ modules.append(module_name)
119
+ logger.debug(f"Found nested module: {module_name}")
120
+ else:
121
+ search_path = base_path / directory
122
+ logger.debug(f"Searching in path: {search_path}")
123
+
124
+ if search_path.exists():
125
+ for file_path in search_path.rglob("*.py"):
126
+ if file_path.name not in excluded_files and not any(
127
+ excluded_dir in file_path.parts for excluded_dir in excluded_dirs
128
+ ):
129
+ # Convert file path to module name with mcli prefix
130
+ relative_path = file_path.relative_to(base_path.parent)
131
+ module_name = str(relative_path).replace("/", ".").replace(".py", "")
132
+
133
+ # Skip individual workflow submodules to avoid duplicate commands
134
+ if (
135
+ module_name.startswith("mcli.workflow.")
136
+ and module_name != "mcli.workflow.workflow"
137
+ ):
138
+ # Skip individual workflow submodules (e.g., mcli.workflow.daemon.daemon)
139
+ # Only include the main workflow module
140
+ continue
141
+
142
+ modules.append(module_name)
143
+ logger.debug(f"Found module: {module_name}")
144
+
145
+ logger.info(f"Discovered {len(modules)} modules")
146
+ return modules
147
+
148
+
149
+ def register_command_as_api_endpoint(command_func, module_name: str, command_name: str):
150
+ """
151
+ Register a Click command as an API endpoint.
152
+
153
+ Args:
154
+ command_func: The Click command function
155
+ module_name: The module name for grouping
156
+ command_name: The command name
157
+ """
158
+ try:
159
+ # Create endpoint path based on module and command
160
+ endpoint_path = f"/{module_name.replace('.', '/')}/{command_name}"
161
+
162
+ logger.info(f"Registering API endpoint: {endpoint_path} for command {command_name}")
163
+ logger.info(f"Command function: {command_func.__name__}")
164
+
165
+ # Register the command as an API endpoint
166
+ register_command_as_api(
167
+ command_func=command_func,
168
+ endpoint_path=endpoint_path,
169
+ http_method="POST",
170
+ description=f"API endpoint for {command_name} command from {module_name}",
171
+ tags=[module_name.split(".")[-1]], # Use last part of module name as tag
172
+ )
173
+
174
+ logger.debug(f"Registered API endpoint: {endpoint_path} for command {command_name}")
175
+
176
+ except Exception as e:
177
+ logger.warning(f"Failed to register API endpoint for {command_name}: {e}")
178
+ import traceback
179
+
180
+ logger.error(f"Traceback: {traceback.format_exc()}")
181
+
182
+
183
+ def process_click_commands(obj, module_name: str, parent_name: str = ""):
184
+ """
185
+ Recursively process Click commands and groups to register them as API endpoints.
186
+
187
+ Args:
188
+ obj: Click command or group object
189
+ module_name: The module name
190
+ parent_name: Parent command name for nesting
191
+ """
192
+ logger.info(
193
+ f"Processing Click object: {type(obj).__name__} with name: {getattr(obj, 'name', 'Unknown')}"
194
+ )
195
+
196
+ if hasattr(obj, "commands"):
197
+ # This is a Click group
198
+ logger.info(f"This is a Click group with {len(obj.commands)} commands")
199
+ for name, command in obj.commands.items():
200
+ full_name = f"{parent_name}/{name}" if parent_name else name
201
+ logger.info(f"Processing command: {name} -> {full_name}")
202
+
203
+ # Register the command as an API endpoint
204
+ register_command_as_api_endpoint(command.callback, module_name, full_name)
205
+
206
+ # Recursively process nested commands
207
+ if hasattr(command, "commands"):
208
+ logger.info(f"Recursively processing nested commands for {name}")
209
+ process_click_commands(command, module_name, full_name)
210
+ else:
211
+ # This is a single command
212
+ logger.info(f"This is a single command: {getattr(obj, 'name', 'Unknown')}")
213
+ if hasattr(obj, "callback") and obj.callback:
214
+ full_name = parent_name if parent_name else obj.name
215
+ logger.info(f"Registering single command: {full_name}")
216
+ register_command_as_api_endpoint(obj.callback, module_name, full_name)
217
+
218
+
219
+ class LazyCommand(click.Command):
220
+ """A Click command that loads its implementation lazily."""
221
+
222
+ def __init__(self, name, import_path, *args, **kwargs):
223
+ self.import_path = import_path
224
+ self._loaded_command = None
225
+ super().__init__(name, *args, **kwargs)
226
+
227
+ def _load_command(self):
228
+ """Load the actual command on first use."""
229
+ if self._loaded_command is None:
230
+ try:
231
+ module_path, attr_name = self.import_path.rsplit(".", 1)
232
+ module = importlib.import_module(module_path)
233
+ self._loaded_command = getattr(module, attr_name)
234
+ logger.debug(f"Lazily loaded command: {self.name}")
235
+ except Exception as e:
236
+ logger.error(f"Failed to load command {self.name}: {e}")
237
+
238
+ # Return a dummy command that shows an error
239
+ def error_callback():
240
+ click.echo(f"Error: Command {self.name} is not available")
241
+
242
+ self._loaded_command = click.Command(self.name, callback=error_callback)
243
+ return self._loaded_command
244
+
245
+ def invoke(self, ctx):
246
+ """Invoke the lazily loaded command."""
247
+ cmd = self._load_command()
248
+ return cmd.invoke(ctx)
249
+
250
+ def get_params(self, ctx):
251
+ """Get parameters from the lazily loaded command."""
252
+ cmd = self._load_command()
253
+ return cmd.get_params(ctx)
254
+
255
+ def shell_complete(self, ctx, param, incomplete):
256
+ """Provide shell completion for the lazily loaded command."""
257
+ cmd = self._load_command()
258
+ if hasattr(cmd, 'shell_complete'):
259
+ return cmd.shell_complete(ctx, param, incomplete)
260
+ return []
261
+
262
+
263
+ class LazyGroup(click.Group):
264
+ """A Click group that loads its implementation lazily."""
265
+
266
+ def __init__(self, name, import_path, *args, **kwargs):
267
+ self.import_path = import_path
268
+ self._loaded_group = None
269
+ super().__init__(name, *args, **kwargs)
270
+
271
+ def _load_group(self):
272
+ """Load the actual group on first use."""
273
+ if self._loaded_group is None:
274
+ try:
275
+ module_path, attr_name = self.import_path.rsplit(".", 1)
276
+ module = importlib.import_module(module_path)
277
+ self._loaded_group = getattr(module, attr_name)
278
+ logger.debug(f"Lazily loaded group: {self.name}")
279
+ except Exception as e:
280
+ logger.error(f"Failed to load group {self.name}: {e}")
281
+
282
+ # Return a dummy group that shows an error
283
+ def error_callback():
284
+ click.echo(f"Error: Command group {self.name} is not available")
285
+
286
+ self._loaded_group = click.Group(self.name, callback=error_callback)
287
+ return self._loaded_group
288
+
289
+ def invoke(self, ctx):
290
+ """Invoke the lazily loaded group."""
291
+ group = self._load_group()
292
+ return group.invoke(ctx)
293
+
294
+ def get_command(self, ctx, cmd_name):
295
+ """Get a command from the lazily loaded group."""
296
+ group = self._load_group()
297
+ return group.get_command(ctx, cmd_name)
298
+
299
+ def list_commands(self, ctx):
300
+ """List commands from the lazily loaded group."""
301
+ group = self._load_group()
302
+ return group.list_commands(ctx)
303
+
304
+ def get_params(self, ctx):
305
+ """Get parameters from the lazily loaded group."""
306
+ group = self._load_group()
307
+ return group.get_params(ctx)
308
+
309
+ def shell_complete(self, ctx, param, incomplete):
310
+ """Provide shell completion for the lazily loaded group."""
311
+ group = self._load_group()
312
+ if hasattr(group, 'shell_complete'):
313
+ return group.shell_complete(ctx, param, incomplete)
314
+ return []
315
+
316
+
317
+ def _add_lazy_commands(app: click.Group):
318
+ """Add command groups with lazy loading."""
319
+ # Essential commands - load immediately for fast access
320
+ try:
321
+ from mcli.app.commands_cmd import commands
322
+
323
+ app.add_command(commands, name="commands")
324
+ logger.debug("Added commands group")
325
+ except ImportError as e:
326
+ logger.debug(f"Could not load commands group: {e}")
327
+
328
+ # Self management - load immediately as it's commonly used
329
+ try:
330
+ from mcli.self.self_cmd import self_app
331
+
332
+ app.add_command(self_app, name="self")
333
+ logger.debug("Added self management commands")
334
+ except Exception as e:
335
+ logger.debug(f"Could not load self commands: {e}")
336
+
337
+ # Shell completion - load immediately as it's lightweight and useful
338
+ try:
339
+ from mcli.app.completion_cmd import completion
340
+
341
+ app.add_command(completion, name="completion")
342
+ logger.debug("Added completion commands")
343
+ except ImportError as e:
344
+ logger.debug(f"Could not load completion commands: {e}")
345
+
346
+ # Add workflow with completion-aware lazy loading
347
+ try:
348
+ from mcli.app.completion_helpers import create_completion_aware_lazy_group
349
+ workflow_group = create_completion_aware_lazy_group(
350
+ "workflow",
351
+ "mcli.workflow.workflow.workflow",
352
+ "Workflow commands for automation, video processing, and daemon management"
353
+ )
354
+ app.add_command(workflow_group, name="workflow")
355
+ logger.debug("Added completion-aware workflow group")
356
+ except ImportError as e:
357
+ logger.debug(f"Could not load completion helpers, using standard lazy group: {e}")
358
+ # Fallback to standard lazy group
359
+ workflow_group = LazyGroup("workflow", "mcli.workflow.workflow.workflow",
360
+ help="Workflow commands for automation, video processing, and daemon management")
361
+ app.add_command(workflow_group, name="workflow")
362
+
363
+ # Lazy load other heavy commands that are used less frequently
364
+ lazy_commands = {
365
+ "chat": {
366
+ "import_path": "mcli.app.chat_cmd.chat",
367
+ "help": "Start an interactive chat session with the MCLI Chat Assistant.",
368
+ },
369
+ "model": {
370
+ "import_path": "mcli.app.model_cmd.model",
371
+ "help": "Model management commands for offline and online model usage",
372
+ },
373
+ "cron-test": {
374
+ "import_path": "mcli.app.cron_test_cmd.cron_test",
375
+ "help": "🕒 Validate and test MCLI cron/scheduler functionality with comprehensive tests.",
376
+ },
377
+ "visual": {
378
+ "import_path": "mcli.app.visual_cmd.visual",
379
+ "help": "🎨 Visual effects and enhancements showcase",
380
+ },
381
+ "redis": {
382
+ "import_path": "mcli.app.redis_cmd.redis_group",
383
+ "help": "🗄️ Manage Redis cache service for performance optimization",
384
+ },
385
+ "logs": {
386
+ "import_path": "mcli.app.logs_cmd.logs_group",
387
+ "help": "📋 Stream and manage MCLI log files with real-time updates",
388
+ },
389
+ }
390
+
391
+ for cmd_name, cmd_info in lazy_commands.items():
392
+ # Skip workflow since we already added it with completion support
393
+ if cmd_name == "workflow":
394
+ continue
395
+
396
+ if cmd_name in ["model", "redis", "logs"]:
397
+ # Use completion-aware LazyGroup for commands that have subcommands
398
+ try:
399
+ from mcli.app.completion_helpers import create_completion_aware_lazy_group
400
+ lazy_cmd = create_completion_aware_lazy_group(
401
+ cmd_name,
402
+ cmd_info["import_path"],
403
+ cmd_info["help"]
404
+ )
405
+ except ImportError:
406
+ # Fallback to standard LazyGroup
407
+ lazy_cmd = LazyGroup(
408
+ name=cmd_name,
409
+ import_path=cmd_info["import_path"],
410
+ help=cmd_info["help"],
411
+ )
412
+ else:
413
+ # Use LazyCommand for simple commands
414
+ lazy_cmd = LazyCommand(
415
+ name=cmd_name,
416
+ import_path=cmd_info["import_path"],
417
+ callback=lambda: None, # Placeholder
418
+ help=cmd_info["help"],
419
+ )
420
+ app.add_command(lazy_cmd)
421
+ logger.debug(f"Added lazy command: {cmd_name}")
422
+
423
+
424
+ def create_app() -> click.Group:
425
+ """Create and configure the Click application with clean top-level commands."""
426
+
427
+ logger.debug("create_app")
428
+
429
+ app = click.Group(name="mcli")
430
+
431
+ # Clean top-level commands
432
+ @app.command()
433
+ @click.option("--verbose", "-v", is_flag=True, help="Show additional system information")
434
+ def version(verbose: bool):
435
+ """Show mcli version and system information"""
436
+ message = get_version_info(verbose)
437
+ logger.info(message)
438
+ info(message)
439
+
440
+ # Add lazy-loaded command groups
441
+ _add_lazy_commands(app)
442
+
443
+ return app
444
+
445
+
446
+ @lru_cache()
447
+ def get_version_info(verbose: bool = False) -> str:
448
+ """Get version info, cached to prevent multiple calls."""
449
+ try:
450
+ mcli_version = version("mcli")
451
+ meta = metadata("mcli")
452
+
453
+ info = [f"mcli version {mcli_version}"]
454
+
455
+ if verbose:
456
+ info.extend(
457
+ [
458
+ f"\nPython: {sys.version.split()[0]}",
459
+ f"Platform: {platform.platform()}",
460
+ f"Description: {meta.get('Summary', 'Not available')}",
461
+ f"Author: {meta.get('Author', 'Not available')}",
462
+ ]
463
+ )
464
+ return "\n".join(info)
465
+ except Exception as e:
466
+ return f"Could not determine version: {e}"
467
+
468
+
469
+ def main():
470
+ """Main entry point for the application."""
471
+ logger.debug("Entering main function")
472
+ try:
473
+ app = create_app()
474
+ logger.debug("Created app, now calling app()")
475
+ app()
476
+ logger.debug("App executed")
477
+ except Exception as e:
478
+ logger.error(f"Error in main function: {e}")
479
+ import traceback
480
+
481
+ logger.error(f"Traceback: {traceback.format_exc()}")
482
+ raise
483
+ finally:
484
+ # Make sure tracing is disabled on exit
485
+ if os.environ.get("MCLI_TRACE_LEVEL"):
486
+ logger.debug("Disabling runtime tracing on exit")
487
+ disable_runtime_tracing()
488
+
489
+
490
+ if __name__ == "__main__":
491
+ logger.debug("Script is being run directly")
492
+ main()