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.
- mcli/app/chat_cmd.py +42 -0
- mcli/app/commands_cmd.py +226 -0
- mcli/app/completion_cmd.py +216 -0
- mcli/app/completion_helpers.py +288 -0
- mcli/app/cron_test_cmd.py +697 -0
- mcli/app/logs_cmd.py +419 -0
- mcli/app/main.py +492 -0
- mcli/app/model/model.py +1060 -0
- mcli/app/model_cmd.py +227 -0
- mcli/app/redis_cmd.py +269 -0
- mcli/app/video/video.py +1114 -0
- mcli/app/visual_cmd.py +303 -0
- mcli/chat/chat.py +2409 -0
- mcli/chat/command_rag.py +514 -0
- mcli/chat/enhanced_chat.py +652 -0
- mcli/chat/system_controller.py +1010 -0
- mcli/chat/system_integration.py +1016 -0
- mcli/cli.py +25 -0
- mcli/config.toml +20 -0
- mcli/lib/api/api.py +586 -0
- mcli/lib/api/daemon_client.py +203 -0
- mcli/lib/api/daemon_client_local.py +44 -0
- mcli/lib/api/daemon_decorator.py +217 -0
- mcli/lib/api/mcli_decorators.py +1032 -0
- mcli/lib/auth/auth.py +85 -0
- mcli/lib/auth/aws_manager.py +85 -0
- mcli/lib/auth/azure_manager.py +91 -0
- mcli/lib/auth/credential_manager.py +192 -0
- mcli/lib/auth/gcp_manager.py +93 -0
- mcli/lib/auth/key_manager.py +117 -0
- mcli/lib/auth/mcli_manager.py +93 -0
- mcli/lib/auth/token_manager.py +75 -0
- mcli/lib/auth/token_util.py +1011 -0
- mcli/lib/config/config.py +47 -0
- mcli/lib/discovery/__init__.py +1 -0
- mcli/lib/discovery/command_discovery.py +274 -0
- mcli/lib/erd/erd.py +1345 -0
- mcli/lib/erd/generate_graph.py +453 -0
- mcli/lib/files/files.py +76 -0
- mcli/lib/fs/fs.py +109 -0
- mcli/lib/lib.py +29 -0
- mcli/lib/logger/logger.py +611 -0
- mcli/lib/performance/optimizer.py +409 -0
- mcli/lib/performance/rust_bridge.py +502 -0
- mcli/lib/performance/uvloop_config.py +154 -0
- mcli/lib/pickles/pickles.py +50 -0
- mcli/lib/search/cached_vectorizer.py +479 -0
- mcli/lib/services/data_pipeline.py +460 -0
- mcli/lib/services/lsh_client.py +441 -0
- mcli/lib/services/redis_service.py +387 -0
- mcli/lib/shell/shell.py +137 -0
- mcli/lib/toml/toml.py +33 -0
- mcli/lib/ui/styling.py +47 -0
- mcli/lib/ui/visual_effects.py +634 -0
- mcli/lib/watcher/watcher.py +185 -0
- mcli/ml/api/app.py +215 -0
- mcli/ml/api/middleware.py +224 -0
- mcli/ml/api/routers/admin_router.py +12 -0
- mcli/ml/api/routers/auth_router.py +244 -0
- mcli/ml/api/routers/backtest_router.py +12 -0
- mcli/ml/api/routers/data_router.py +12 -0
- mcli/ml/api/routers/model_router.py +302 -0
- mcli/ml/api/routers/monitoring_router.py +12 -0
- mcli/ml/api/routers/portfolio_router.py +12 -0
- mcli/ml/api/routers/prediction_router.py +267 -0
- mcli/ml/api/routers/trade_router.py +12 -0
- mcli/ml/api/routers/websocket_router.py +76 -0
- mcli/ml/api/schemas.py +64 -0
- mcli/ml/auth/auth_manager.py +425 -0
- mcli/ml/auth/models.py +154 -0
- mcli/ml/auth/permissions.py +302 -0
- mcli/ml/backtesting/backtest_engine.py +502 -0
- mcli/ml/backtesting/performance_metrics.py +393 -0
- mcli/ml/cache.py +400 -0
- mcli/ml/cli/main.py +398 -0
- mcli/ml/config/settings.py +394 -0
- mcli/ml/configs/dvc_config.py +230 -0
- mcli/ml/configs/mlflow_config.py +131 -0
- mcli/ml/configs/mlops_manager.py +293 -0
- mcli/ml/dashboard/app.py +532 -0
- mcli/ml/dashboard/app_integrated.py +738 -0
- mcli/ml/dashboard/app_supabase.py +560 -0
- mcli/ml/dashboard/app_training.py +615 -0
- mcli/ml/dashboard/cli.py +51 -0
- mcli/ml/data_ingestion/api_connectors.py +501 -0
- mcli/ml/data_ingestion/data_pipeline.py +567 -0
- mcli/ml/data_ingestion/stream_processor.py +512 -0
- mcli/ml/database/migrations/env.py +94 -0
- mcli/ml/database/models.py +667 -0
- mcli/ml/database/session.py +200 -0
- mcli/ml/experimentation/ab_testing.py +845 -0
- mcli/ml/features/ensemble_features.py +607 -0
- mcli/ml/features/political_features.py +676 -0
- mcli/ml/features/recommendation_engine.py +809 -0
- mcli/ml/features/stock_features.py +573 -0
- mcli/ml/features/test_feature_engineering.py +346 -0
- mcli/ml/logging.py +85 -0
- mcli/ml/mlops/data_versioning.py +518 -0
- mcli/ml/mlops/experiment_tracker.py +377 -0
- mcli/ml/mlops/model_serving.py +481 -0
- mcli/ml/mlops/pipeline_orchestrator.py +614 -0
- mcli/ml/models/base_models.py +324 -0
- mcli/ml/models/ensemble_models.py +675 -0
- mcli/ml/models/recommendation_models.py +474 -0
- mcli/ml/models/test_models.py +487 -0
- mcli/ml/monitoring/drift_detection.py +676 -0
- mcli/ml/monitoring/metrics.py +45 -0
- mcli/ml/optimization/portfolio_optimizer.py +834 -0
- mcli/ml/preprocessing/data_cleaners.py +451 -0
- mcli/ml/preprocessing/feature_extractors.py +491 -0
- mcli/ml/preprocessing/ml_pipeline.py +382 -0
- mcli/ml/preprocessing/politician_trading_preprocessor.py +569 -0
- mcli/ml/preprocessing/test_preprocessing.py +294 -0
- mcli/ml/scripts/populate_sample_data.py +200 -0
- mcli/ml/tasks.py +400 -0
- mcli/ml/tests/test_integration.py +429 -0
- mcli/ml/tests/test_training_dashboard.py +387 -0
- mcli/public/oi/oi.py +15 -0
- mcli/public/public.py +4 -0
- mcli/self/self_cmd.py +1246 -0
- mcli/workflow/daemon/api_daemon.py +800 -0
- mcli/workflow/daemon/async_command_database.py +681 -0
- mcli/workflow/daemon/async_process_manager.py +591 -0
- mcli/workflow/daemon/client.py +530 -0
- mcli/workflow/daemon/commands.py +1196 -0
- mcli/workflow/daemon/daemon.py +905 -0
- mcli/workflow/daemon/daemon_api.py +59 -0
- mcli/workflow/daemon/enhanced_daemon.py +571 -0
- mcli/workflow/daemon/process_cli.py +244 -0
- mcli/workflow/daemon/process_manager.py +439 -0
- mcli/workflow/daemon/test_daemon.py +275 -0
- mcli/workflow/dashboard/dashboard_cmd.py +113 -0
- mcli/workflow/docker/docker.py +0 -0
- mcli/workflow/file/file.py +100 -0
- mcli/workflow/gcloud/config.toml +21 -0
- mcli/workflow/gcloud/gcloud.py +58 -0
- mcli/workflow/git_commit/ai_service.py +328 -0
- mcli/workflow/git_commit/commands.py +430 -0
- mcli/workflow/lsh_integration.py +355 -0
- mcli/workflow/model_service/client.py +594 -0
- mcli/workflow/model_service/download_and_run_efficient_models.py +288 -0
- mcli/workflow/model_service/lightweight_embedder.py +397 -0
- mcli/workflow/model_service/lightweight_model_server.py +714 -0
- mcli/workflow/model_service/lightweight_test.py +241 -0
- mcli/workflow/model_service/model_service.py +1955 -0
- mcli/workflow/model_service/ollama_efficient_runner.py +425 -0
- mcli/workflow/model_service/pdf_processor.py +386 -0
- mcli/workflow/model_service/test_efficient_runner.py +234 -0
- mcli/workflow/model_service/test_example.py +315 -0
- mcli/workflow/model_service/test_integration.py +131 -0
- mcli/workflow/model_service/test_new_features.py +149 -0
- mcli/workflow/openai/openai.py +99 -0
- mcli/workflow/politician_trading/commands.py +1790 -0
- mcli/workflow/politician_trading/config.py +134 -0
- mcli/workflow/politician_trading/connectivity.py +490 -0
- mcli/workflow/politician_trading/data_sources.py +395 -0
- mcli/workflow/politician_trading/database.py +410 -0
- mcli/workflow/politician_trading/demo.py +248 -0
- mcli/workflow/politician_trading/models.py +165 -0
- mcli/workflow/politician_trading/monitoring.py +413 -0
- mcli/workflow/politician_trading/scrapers.py +966 -0
- mcli/workflow/politician_trading/scrapers_california.py +412 -0
- mcli/workflow/politician_trading/scrapers_eu.py +377 -0
- mcli/workflow/politician_trading/scrapers_uk.py +350 -0
- mcli/workflow/politician_trading/scrapers_us_states.py +438 -0
- mcli/workflow/politician_trading/supabase_functions.py +354 -0
- mcli/workflow/politician_trading/workflow.py +852 -0
- mcli/workflow/registry/registry.py +180 -0
- mcli/workflow/repo/repo.py +223 -0
- mcli/workflow/scheduler/commands.py +493 -0
- mcli/workflow/scheduler/cron_parser.py +238 -0
- mcli/workflow/scheduler/job.py +182 -0
- mcli/workflow/scheduler/monitor.py +139 -0
- mcli/workflow/scheduler/persistence.py +324 -0
- mcli/workflow/scheduler/scheduler.py +679 -0
- mcli/workflow/sync/sync_cmd.py +437 -0
- mcli/workflow/sync/test_cmd.py +314 -0
- mcli/workflow/videos/videos.py +242 -0
- mcli/workflow/wakatime/wakatime.py +11 -0
- mcli/workflow/workflow.py +37 -0
- mcli_framework-7.0.0.dist-info/METADATA +479 -0
- mcli_framework-7.0.0.dist-info/RECORD +186 -0
- mcli_framework-7.0.0.dist-info/WHEEL +5 -0
- mcli_framework-7.0.0.dist-info/entry_points.txt +7 -0
- mcli_framework-7.0.0.dist-info/licenses/LICENSE +21 -0
- 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()
|