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
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from mcli.lib.fs import get_user_home
|
|
4
|
+
from mcli.lib.logger.logger import get_logger
|
|
5
|
+
from mcli.lib.toml import read_from_toml
|
|
6
|
+
|
|
7
|
+
logger = get_logger(__name__)
|
|
8
|
+
logger.info("")
|
|
9
|
+
|
|
10
|
+
# Path to root repo. Exclude trailing slash
|
|
11
|
+
# TODO: Needs to be updated for all env variables used in app
|
|
12
|
+
PATH_TO_PACKAGE_REPO = "/Users/lefv/mcli/mclifed-guru/cornea"
|
|
13
|
+
|
|
14
|
+
# Listen to file updates within these packages
|
|
15
|
+
PACKAGES_TO_SYNC = ["mcli_ui_package"]
|
|
16
|
+
|
|
17
|
+
# Exclude trailing slash
|
|
18
|
+
ENDPOINT = ""
|
|
19
|
+
|
|
20
|
+
# Basic auth token for BA:BA
|
|
21
|
+
USER_CONFIG_ROOT = f"{get_user_home()}/.config/mcli/"
|
|
22
|
+
DEV_SECRETS_ROOT = f"{get_user_home()}/.config/mcli/secrets/"
|
|
23
|
+
PRIVATE_KEY_PATH = f"{DEV_SECRETS_ROOT}/keys/private_key.pem"
|
|
24
|
+
USER_INFO_FILE = f"{DEV_SECRETS_ROOT}/user/user_info.json"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_config_for_file(file_name: str, config_type: str = "config") -> str:
|
|
28
|
+
"""Get the config for a file."""
|
|
29
|
+
return f"{get_config_directory()}/{file_name}/mcli.{file_name}.{config_type}.json"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_config_directory() -> str:
|
|
33
|
+
"""Get the config directory."""
|
|
34
|
+
return Path(USER_CONFIG_ROOT)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_config_file_name(raw_file_name: str) -> str:
|
|
38
|
+
"""Get the config file name."""
|
|
39
|
+
return raw_file_name.split("/")[-2]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def get_mcli_rc():
|
|
43
|
+
logger.info(__name__)
|
|
44
|
+
logger.info(__package__)
|
|
45
|
+
# case_state_scrambler = read_from_toml(
|
|
46
|
+
|
|
47
|
+
# )
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Discovery module for MCLI commands
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import inspect
|
|
3
|
+
import sys
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, Dict, List, Optional
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
|
|
10
|
+
from mcli.lib.logger.logger import get_logger
|
|
11
|
+
|
|
12
|
+
logger = get_logger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class DiscoveredCommand:
|
|
17
|
+
"""Represents a discovered Click command"""
|
|
18
|
+
|
|
19
|
+
name: str
|
|
20
|
+
full_name: str # e.g., "workflow.file.oxps_to_pdf"
|
|
21
|
+
module_name: str
|
|
22
|
+
group_name: Optional[str]
|
|
23
|
+
description: str
|
|
24
|
+
callback: callable
|
|
25
|
+
is_group: bool = False
|
|
26
|
+
subcommands: List[str] = None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ClickCommandDiscovery:
|
|
30
|
+
"""Discovers all Click commands in the MCLI application"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, base_path: Optional[Path] = None):
|
|
33
|
+
self.base_path = base_path or Path(__file__).parent.parent.parent
|
|
34
|
+
self.discovered_commands: Dict[str, DiscoveredCommand] = {}
|
|
35
|
+
|
|
36
|
+
def discover_all_commands(self) -> List[DiscoveredCommand]:
|
|
37
|
+
"""Discover all Click commands in the application"""
|
|
38
|
+
self.discovered_commands.clear()
|
|
39
|
+
|
|
40
|
+
# Get the included directories from config
|
|
41
|
+
from mcli.lib.toml.toml import read_from_toml
|
|
42
|
+
|
|
43
|
+
config_paths = [Path("config.toml"), self.base_path.parent.parent / "config.toml"]
|
|
44
|
+
|
|
45
|
+
included_dirs = ["workflow", "app", "self"] # Default
|
|
46
|
+
|
|
47
|
+
for config_path in config_paths:
|
|
48
|
+
if config_path.exists():
|
|
49
|
+
try:
|
|
50
|
+
config = read_from_toml(str(config_path), "paths")
|
|
51
|
+
if config and config.get("included_dirs"):
|
|
52
|
+
included_dirs = config["included_dirs"]
|
|
53
|
+
break
|
|
54
|
+
except Exception as e:
|
|
55
|
+
logger.debug(f"Could not load config from {config_path}: {e}")
|
|
56
|
+
|
|
57
|
+
logger.info(f"Discovering commands in directories: {included_dirs}")
|
|
58
|
+
|
|
59
|
+
# Discover commands in each included directory
|
|
60
|
+
for directory in included_dirs:
|
|
61
|
+
self._discover_in_directory(directory)
|
|
62
|
+
|
|
63
|
+
return list(self.discovered_commands.values())
|
|
64
|
+
|
|
65
|
+
def _discover_in_directory(self, directory: str):
|
|
66
|
+
"""Discover commands in a specific directory"""
|
|
67
|
+
if "/" in directory:
|
|
68
|
+
# Handle nested paths like "workflow/daemon"
|
|
69
|
+
parts = directory.split("/")
|
|
70
|
+
search_path = self.base_path
|
|
71
|
+
for part in parts:
|
|
72
|
+
search_path = search_path / part
|
|
73
|
+
else:
|
|
74
|
+
search_path = self.base_path / directory
|
|
75
|
+
|
|
76
|
+
if not search_path.exists():
|
|
77
|
+
logger.debug(f"Directory {search_path} does not exist")
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
logger.debug(f"Searching for commands in: {search_path}")
|
|
81
|
+
|
|
82
|
+
# Find all Python files
|
|
83
|
+
for py_file in search_path.rglob("*.py"):
|
|
84
|
+
if py_file.name.startswith("__"):
|
|
85
|
+
continue
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
self._discover_in_file(py_file, directory)
|
|
89
|
+
except Exception as e:
|
|
90
|
+
logger.debug(f"Error discovering commands in {py_file}: {e}")
|
|
91
|
+
|
|
92
|
+
def _discover_in_file(self, py_file: Path, base_directory: str):
|
|
93
|
+
"""Discover commands in a specific Python file"""
|
|
94
|
+
# Convert file path to module name
|
|
95
|
+
relative_path = py_file.relative_to(self.base_path.parent)
|
|
96
|
+
module_name = str(relative_path).replace("/", ".").replace(".py", "")
|
|
97
|
+
|
|
98
|
+
# Skip certain modules
|
|
99
|
+
if any(skip in module_name for skip in ["test_", "__pycache__", ".pyc"]):
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
# Import the module
|
|
104
|
+
if module_name not in sys.modules:
|
|
105
|
+
module = importlib.import_module(module_name)
|
|
106
|
+
else:
|
|
107
|
+
module = sys.modules[module_name]
|
|
108
|
+
|
|
109
|
+
# Find Click objects
|
|
110
|
+
for name, obj in inspect.getmembers(module):
|
|
111
|
+
if isinstance(obj, click.Group):
|
|
112
|
+
self._register_group(obj, module_name, base_directory)
|
|
113
|
+
elif isinstance(obj, click.Command):
|
|
114
|
+
self._register_command(obj, module_name, base_directory)
|
|
115
|
+
|
|
116
|
+
except Exception as e:
|
|
117
|
+
logger.debug(f"Could not import or inspect module {module_name}: {e}")
|
|
118
|
+
|
|
119
|
+
def _register_group(self, group: click.Group, module_name: str, base_directory: str):
|
|
120
|
+
"""Register a Click group and its commands"""
|
|
121
|
+
group_full_name = f"{base_directory}.{group.name}" if group.name else base_directory
|
|
122
|
+
|
|
123
|
+
# Register the group itself
|
|
124
|
+
group_cmd = DiscoveredCommand(
|
|
125
|
+
name=group.name or "unnamed",
|
|
126
|
+
full_name=group_full_name,
|
|
127
|
+
module_name=module_name,
|
|
128
|
+
group_name=None,
|
|
129
|
+
description=group.help or "No description",
|
|
130
|
+
callback=group.callback,
|
|
131
|
+
is_group=True,
|
|
132
|
+
subcommands=[],
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
self.discovered_commands[group_full_name] = group_cmd
|
|
136
|
+
|
|
137
|
+
# Register all commands in the group
|
|
138
|
+
for cmd_name, cmd in group.commands.items():
|
|
139
|
+
cmd_full_name = f"{group_full_name}.{cmd_name}"
|
|
140
|
+
|
|
141
|
+
command = DiscoveredCommand(
|
|
142
|
+
name=cmd_name,
|
|
143
|
+
full_name=cmd_full_name,
|
|
144
|
+
module_name=module_name,
|
|
145
|
+
group_name=group.name,
|
|
146
|
+
description=cmd.help or "No description",
|
|
147
|
+
callback=cmd.callback,
|
|
148
|
+
is_group=isinstance(cmd, click.Group),
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
self.discovered_commands[cmd_full_name] = command
|
|
152
|
+
group_cmd.subcommands.append(cmd_name)
|
|
153
|
+
|
|
154
|
+
# If this command is also a group, recursively register its commands
|
|
155
|
+
if isinstance(cmd, click.Group):
|
|
156
|
+
self._register_group_recursive(cmd, cmd_full_name, module_name)
|
|
157
|
+
|
|
158
|
+
def _register_command(self, command: click.Command, module_name: str, base_directory: str):
|
|
159
|
+
"""Register a standalone Click command"""
|
|
160
|
+
cmd_full_name = f"{base_directory}.{command.name}" if command.name else base_directory
|
|
161
|
+
|
|
162
|
+
cmd = DiscoveredCommand(
|
|
163
|
+
name=command.name or "unnamed",
|
|
164
|
+
full_name=cmd_full_name,
|
|
165
|
+
module_name=module_name,
|
|
166
|
+
group_name=None,
|
|
167
|
+
description=command.help or "No description",
|
|
168
|
+
callback=command.callback,
|
|
169
|
+
is_group=False,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
self.discovered_commands[cmd_full_name] = cmd
|
|
173
|
+
|
|
174
|
+
def _register_group_recursive(self, group: click.Group, parent_name: str, module_name: str):
|
|
175
|
+
"""Recursively register nested group commands"""
|
|
176
|
+
for cmd_name, cmd in group.commands.items():
|
|
177
|
+
cmd_full_name = f"{parent_name}.{cmd_name}"
|
|
178
|
+
|
|
179
|
+
command = DiscoveredCommand(
|
|
180
|
+
name=cmd_name,
|
|
181
|
+
full_name=cmd_full_name,
|
|
182
|
+
module_name=module_name,
|
|
183
|
+
group_name=group.name,
|
|
184
|
+
description=cmd.help or "No description",
|
|
185
|
+
callback=cmd.callback,
|
|
186
|
+
is_group=isinstance(cmd, click.Group),
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
self.discovered_commands[cmd_full_name] = command
|
|
190
|
+
|
|
191
|
+
if isinstance(cmd, click.Group):
|
|
192
|
+
self._register_group_recursive(cmd, cmd_full_name, module_name)
|
|
193
|
+
|
|
194
|
+
def get_commands(self, include_groups: bool = True) -> List[Dict[str, Any]]:
|
|
195
|
+
"""Get all discovered commands as dictionaries"""
|
|
196
|
+
commands = []
|
|
197
|
+
|
|
198
|
+
for cmd in self.discovered_commands.values():
|
|
199
|
+
if not include_groups and cmd.is_group:
|
|
200
|
+
continue
|
|
201
|
+
|
|
202
|
+
command_dict = {
|
|
203
|
+
"id": cmd.full_name,
|
|
204
|
+
"name": cmd.name,
|
|
205
|
+
"full_name": cmd.full_name,
|
|
206
|
+
"description": cmd.description,
|
|
207
|
+
"module": cmd.module_name,
|
|
208
|
+
"group": cmd.group_name,
|
|
209
|
+
"is_group": cmd.is_group,
|
|
210
|
+
"language": "python",
|
|
211
|
+
"tags": [cmd.group_name] if cmd.group_name else [],
|
|
212
|
+
"is_active": True,
|
|
213
|
+
"execution_count": 0,
|
|
214
|
+
"created_at": None,
|
|
215
|
+
"updated_at": None,
|
|
216
|
+
"last_executed": None,
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if cmd.subcommands:
|
|
220
|
+
command_dict["subcommands"] = cmd.subcommands
|
|
221
|
+
|
|
222
|
+
commands.append(command_dict)
|
|
223
|
+
|
|
224
|
+
return sorted(commands, key=lambda x: x["full_name"])
|
|
225
|
+
|
|
226
|
+
def search_commands(self, query: str) -> List[Dict[str, Any]]:
|
|
227
|
+
"""Search commands by name, description, or module"""
|
|
228
|
+
query = query.lower()
|
|
229
|
+
all_commands = self.get_commands()
|
|
230
|
+
|
|
231
|
+
matching_commands = []
|
|
232
|
+
for cmd in all_commands:
|
|
233
|
+
if (
|
|
234
|
+
query in cmd["name"].lower()
|
|
235
|
+
or query in cmd["description"].lower()
|
|
236
|
+
or query in cmd["module"].lower()
|
|
237
|
+
or query in (cmd["group"] or "").lower()
|
|
238
|
+
):
|
|
239
|
+
matching_commands.append(cmd)
|
|
240
|
+
|
|
241
|
+
return matching_commands
|
|
242
|
+
|
|
243
|
+
def get_command_by_name(self, name: str) -> Optional[DiscoveredCommand]:
|
|
244
|
+
"""Get a command by its name or full name"""
|
|
245
|
+
# First try exact match by full name
|
|
246
|
+
if name in self.discovered_commands:
|
|
247
|
+
return self.discovered_commands[name]
|
|
248
|
+
|
|
249
|
+
# Then try by short name
|
|
250
|
+
for cmd in self.discovered_commands.values():
|
|
251
|
+
if cmd.name == name:
|
|
252
|
+
return cmd
|
|
253
|
+
|
|
254
|
+
return None
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
# Global instance for caching
|
|
258
|
+
_discovery_instance = None
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def get_command_discovery() -> ClickCommandDiscovery:
|
|
262
|
+
"""Get a cached command discovery instance"""
|
|
263
|
+
global _discovery_instance
|
|
264
|
+
if _discovery_instance is None:
|
|
265
|
+
_discovery_instance = ClickCommandDiscovery()
|
|
266
|
+
_discovery_instance.discover_all_commands()
|
|
267
|
+
return _discovery_instance
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def refresh_command_discovery():
|
|
271
|
+
"""Refresh the command discovery cache"""
|
|
272
|
+
global _discovery_instance
|
|
273
|
+
_discovery_instance = None
|
|
274
|
+
return get_command_discovery()
|