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/cli.py ADDED
@@ -0,0 +1,25 @@
1
+ import click
2
+
3
+ from mcli.lib.api.mcli_decorators import chat
4
+
5
+
6
+ @click.group()
7
+ def cli():
8
+ """MCLI - Modern Command Line Interface"""
9
+ pass
10
+
11
+
12
+ # Add the chat command group
13
+ cli.add_command(chat(name="chat"))
14
+
15
+
16
+ @cli.command()
17
+ def version():
18
+ """Show MCLI version"""
19
+ from mcli import __version__
20
+
21
+ click.echo(f"MCLI version {__version__}")
22
+
23
+
24
+ if __name__ == "__main__":
25
+ cli()
mcli/config.toml ADDED
@@ -0,0 +1,20 @@
1
+ [paths]
2
+ included_dirs = ["app", "self", "workflow", "public"]
3
+
4
+ # Chat configuration with lightweight local models by default
5
+ [llm]
6
+ provider = "local"
7
+ model = "prajjwal1/bert-tiny"
8
+ temperature = 0.7
9
+ system_prompt = "You are the MCLI Chat Assistant, an expert AI assistant for the MCLI command line tool.\n\nCRITICAL RULES:\n- NEVER suggest commands that don't exist\n- ONLY reference commands from the provided available commands list\n- If functionality doesn't exist, clearly state it needs to be created\n- ALWAYS be accurate about what commands are available\n\nYou can:\n1. **Create new commands**: Generate Python code using Click framework when functionality is missing\n2. **Execute existing commands**: Help users run commands that actually exist\n3. **Explain functionality**: Provide accurate explanations based on real commands\n4. **Integrate external code**: Help create new commands from GitHub repositories\n5. **File operations**: Generate new commands for missing functionality\n\nWhen creating commands:\n- Use Click framework (@click.command() decorator)\n- Include proper help text and argument descriptions\n- Add error handling and validation\n- Follow Python best practices\n- Provide specific file paths and testing instructions\n\nBe accurate, helpful, and never hallucinate non-existent commands."
10
+ # Default to lightweight model server (offline mode)
11
+ ollama_base_url = "http://localhost:8080"
12
+ # Set to true to use lightweight models by default (can override with --remote flag)
13
+ use_lightweight_models = true
14
+
15
+ # Alternative providers (uncomment to use)
16
+ # provider = "openai"
17
+ # model = "gpt-4-turbo"
18
+ # openai_api_key = "your-api-key-here"
19
+
20
+ anthropic_api_key = "your-api-key-here"
mcli/lib/api/api.py ADDED
@@ -0,0 +1,586 @@
1
+ import functools
2
+ import inspect
3
+ import json
4
+ import os
5
+ import random
6
+ import socket
7
+ import sys
8
+ import threading
9
+ import time
10
+ from contextlib import asynccontextmanager
11
+ from pathlib import Path
12
+ from typing import Any, Callable, Dict, List, Optional, Union
13
+
14
+ import click
15
+ import requests
16
+ import uvicorn
17
+ from fastapi import FastAPI, HTTPException, Request
18
+ from fastapi.middleware.cors import CORSMiddleware
19
+ from pydantic import BaseModel, Field
20
+
21
+ # Import existing utilities
22
+ from mcli.lib.logger.logger import get_logger
23
+ from mcli.lib.toml.toml import read_from_toml
24
+
25
+ logger = get_logger(__name__)
26
+
27
+ # Global FastAPI app instance
28
+ _api_app: Optional[FastAPI] = None
29
+ _api_server_thread: Optional[threading.Thread] = None
30
+ _api_server_running = False
31
+
32
+
33
+ def find_free_port(start_port: int = 8000, max_attempts: int = 100) -> int:
34
+ """
35
+ Find a free port starting from start_port.
36
+
37
+ Args:
38
+ start_port: Starting port number
39
+ max_attempts: Maximum number of attempts to find a free port
40
+
41
+ Returns:
42
+ A free port number
43
+ """
44
+ for attempt in range(max_attempts):
45
+ port = start_port + attempt
46
+ try:
47
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
48
+ s.bind(("", port))
49
+ return port
50
+ except OSError:
51
+ continue
52
+
53
+ # If no free port found, use a random port in a safe range
54
+ return random.randint(49152, 65535)
55
+
56
+
57
+ def get_api_config() -> Dict[str, Any]:
58
+ """
59
+ Get API configuration from MCLI config files.
60
+
61
+ Returns:
62
+ Dictionary with API configuration
63
+ """
64
+ config = {
65
+ "enabled": False,
66
+ "host": "0.0.0.0",
67
+ "port": None, # Will be set to random port if None
68
+ "use_random_port": True,
69
+ "debug": False,
70
+ }
71
+
72
+ # Try to load from config.toml files
73
+ config_paths = [
74
+ Path("config.toml"), # Current directory
75
+ Path.home() / ".config" / "mcli" / "config.toml", # User config
76
+ Path(__file__).parent.parent.parent.parent.parent / "config.toml", # Project root
77
+ ]
78
+
79
+ for config_path in config_paths:
80
+ if config_path.exists():
81
+ try:
82
+ api_config = read_from_toml(str(config_path), "api")
83
+ if api_config:
84
+ config.update(api_config)
85
+ logger.debug(f"Loaded API config from {config_path}")
86
+ break
87
+ except Exception as e:
88
+ logger.debug(f"Could not load API config from {config_path}: {e}")
89
+
90
+ # Override with environment variables
91
+ if os.environ.get("MCLI_API_SERVER", "false").lower() in ("true", "1", "yes"):
92
+ config["enabled"] = True
93
+
94
+ if os.environ.get("MCLI_API_HOST"):
95
+ config["host"] = os.environ.get("MCLI_API_HOST")
96
+
97
+ if os.environ.get("MCLI_API_PORT"):
98
+ config["port"] = int(os.environ.get("MCLI_API_PORT"))
99
+ config["use_random_port"] = False
100
+
101
+ if os.environ.get("MCLI_API_DEBUG", "false").lower() in ("true", "1", "yes"):
102
+ config["debug"] = True
103
+
104
+ # Set random port if needed
105
+ if config["enabled"] and config["use_random_port"] and config["port"] is None:
106
+ config["port"] = find_free_port()
107
+ logger.info(f"Using random port: {config['port']}")
108
+
109
+ return config
110
+
111
+
112
+ class ClickToAPIDecorator:
113
+ """Decorator that makes Click commands also serve as API endpoints."""
114
+
115
+ def __init__(
116
+ self,
117
+ endpoint_path: str = None,
118
+ http_method: str = "POST",
119
+ response_model: BaseModel = None,
120
+ description: str = None,
121
+ tags: List[str] = None,
122
+ ):
123
+ """
124
+ Initialize the decorator.
125
+
126
+ Args:
127
+ endpoint_path: API endpoint path (defaults to command name)
128
+ http_method: HTTP method (GET, POST, PUT, DELETE)
129
+ response_model: Pydantic model for response validation
130
+ description: API endpoint description
131
+ tags: API tags for grouping
132
+ """
133
+ self.endpoint_path = endpoint_path
134
+ self.http_method = http_method.upper()
135
+ self.response_model = response_model
136
+ self.description = description
137
+ self.tags = tags or []
138
+
139
+ def __call__(self, func: Callable) -> Callable:
140
+ """Apply the decorator to a function."""
141
+
142
+ # Get the original function signature
143
+ sig = inspect.signature(func)
144
+
145
+ # Create a wrapper that registers the API endpoint
146
+ @functools.wraps(func)
147
+ def wrapper(*args, **kwargs):
148
+ # Register the API endpoint
149
+ self._register_api_endpoint(func, sig)
150
+
151
+ # Call the original function
152
+ return func(*args, **kwargs)
153
+
154
+ # Store decorator info for later registration
155
+ wrapper._api_decorator = self
156
+ wrapper._original_func = func
157
+
158
+ return wrapper
159
+
160
+ def _register_api_endpoint(self, func: Callable, sig: inspect.Signature):
161
+ """Register the function as an API endpoint."""
162
+ global _api_app
163
+
164
+ # Ensure API app exists
165
+ if _api_app is None:
166
+ _api_app = ensure_api_app()
167
+ if _api_app is None:
168
+ logger.warning("Could not create API app, skipping endpoint registration")
169
+ return
170
+
171
+ # Determine endpoint path
172
+ endpoint_path = self.endpoint_path or f"/{func.__name__}"
173
+
174
+ # Create request model from function signature
175
+ request_model = self._create_request_model(func, sig)
176
+
177
+ # Create response model
178
+ response_model = self.response_model or self._create_response_model()
179
+
180
+ # Register the endpoint
181
+ self._register_endpoint(
182
+ app=_api_app,
183
+ path=endpoint_path,
184
+ method=self.http_method,
185
+ func=func,
186
+ request_model=request_model,
187
+ response_model=response_model,
188
+ description=self.description or func.__doc__,
189
+ tags=self.tags,
190
+ )
191
+
192
+ logger.info(f"Registered API endpoint: {self.http_method} {endpoint_path}")
193
+
194
+ def _create_fastapi_app(self) -> FastAPI:
195
+ """Create and configure FastAPI app."""
196
+ app = FastAPI(
197
+ title="MCLI API", description="API endpoints for MCLI commands", version="1.0.0"
198
+ )
199
+
200
+ # Add CORS middleware
201
+ app.add_middleware(
202
+ CORSMiddleware,
203
+ allow_origins=["*"],
204
+ allow_credentials=True,
205
+ allow_methods=["*"],
206
+ allow_headers=["*"],
207
+ )
208
+
209
+ # Add health check endpoint
210
+ @app.get("/health")
211
+ async def health_check():
212
+ return {"status": "healthy", "service": "MCLI API"}
213
+
214
+ # Add root endpoint
215
+ @app.get("/")
216
+ async def root():
217
+ return {
218
+ "service": "MCLI API",
219
+ "version": "1.0.0",
220
+ "status": "running",
221
+ "endpoints": self._get_registered_endpoints(app),
222
+ }
223
+
224
+ return app
225
+
226
+ def _create_request_model(self, func: Callable, sig: inspect.Signature) -> BaseModel:
227
+ """Create a Pydantic model from function signature."""
228
+ fields = {}
229
+
230
+ for param_name, param in sig.parameters.items():
231
+ if param_name in ["self", "ctx"]:
232
+ continue
233
+
234
+ # Get parameter type and default
235
+ param_type = param.annotation if param.annotation != inspect.Parameter.empty else str
236
+ default = param.default if param.default != inspect.Parameter.empty else ...
237
+
238
+ # Handle Click-specific types
239
+ if hasattr(param_type, "__origin__") and param_type.__origin__ is Union:
240
+ # Handle Union types (e.g., Optional[str])
241
+ param_type = str
242
+ elif param_type == bool:
243
+ # Handle boolean flags
244
+ param_type = bool
245
+ elif param_type in [int, float]:
246
+ param_type = param_type
247
+ else:
248
+ param_type = str
249
+
250
+ fields[param_name] = (param_type, default)
251
+
252
+ # Create dynamic model
253
+ model_name = f"{func.__name__}Request"
254
+ return type(model_name, (BaseModel,), fields)
255
+
256
+ def _create_response_model(self) -> BaseModel:
257
+ """Create a default response model."""
258
+
259
+ class DefaultResponse(BaseModel):
260
+ success: bool = Field(..., description="Operation success status")
261
+ result: Any = Field(None, description="Operation result")
262
+ message: str = Field("", description="Operation message")
263
+ error: str = Field(None, description="Error message if any")
264
+
265
+ return DefaultResponse
266
+
267
+ def _register_endpoint(
268
+ self,
269
+ app: FastAPI,
270
+ path: str,
271
+ method: str,
272
+ func: Callable,
273
+ request_model: BaseModel,
274
+ response_model: BaseModel,
275
+ description: str,
276
+ tags: List[str],
277
+ ):
278
+ """Register an endpoint with FastAPI."""
279
+
280
+ async def api_endpoint(request: request_model):
281
+ """API endpoint wrapper."""
282
+ try:
283
+ # Convert request model to kwargs
284
+ kwargs = request.dict()
285
+
286
+ # Call the original function
287
+ result = func(**kwargs)
288
+
289
+ # Return response
290
+ return response_model(
291
+ success=True, result=result, message="Operation completed successfully"
292
+ )
293
+
294
+ except Exception as e:
295
+ logger.error(f"API endpoint error: {e}")
296
+ return response_model(success=False, error=str(e), message="Operation failed")
297
+
298
+ # Register with FastAPI
299
+ if method == "GET":
300
+ app.get(path, response_model=response_model, description=description, tags=tags)(
301
+ api_endpoint
302
+ )
303
+ elif method == "POST":
304
+ app.post(path, response_model=response_model, description=description, tags=tags)(
305
+ api_endpoint
306
+ )
307
+ elif method == "PUT":
308
+ app.put(path, response_model=response_model, description=description, tags=tags)(
309
+ api_endpoint
310
+ )
311
+ elif method == "DELETE":
312
+ app.delete(path, response_model=response_model, description=description, tags=tags)(
313
+ api_endpoint
314
+ )
315
+
316
+ def _get_registered_endpoints(self, app: FastAPI) -> List[Dict[str, str]]:
317
+ """Get list of registered endpoints."""
318
+ endpoints = []
319
+ for route in app.routes:
320
+ if hasattr(route, "path") and hasattr(route, "methods"):
321
+ for method in route.methods:
322
+ endpoints.append(
323
+ {
324
+ "path": route.path,
325
+ "method": method,
326
+ "name": getattr(route, "name", "Unknown"),
327
+ }
328
+ )
329
+ return endpoints
330
+
331
+
332
+ def api_endpoint(
333
+ endpoint_path: str = None,
334
+ http_method: str = "POST",
335
+ response_model: BaseModel = None,
336
+ description: str = None,
337
+ tags: List[str] = None,
338
+ ):
339
+ """
340
+ Decorator that makes Click commands also serve as API endpoints.
341
+
342
+ Args:
343
+ endpoint_path: API endpoint path (defaults to command name)
344
+ http_method: HTTP method (GET, POST, PUT, DELETE)
345
+ response_model: Pydantic model for response validation
346
+ description: API endpoint description
347
+ tags: API tags for grouping
348
+
349
+ Example:
350
+ @click.command()
351
+ @api_endpoint("/generate", "POST")
352
+ def generate_text(prompt: str, max_length: int = 100):
353
+ return {"text": "Generated text"}
354
+ """
355
+ return ClickToAPIDecorator(
356
+ endpoint_path=endpoint_path,
357
+ http_method=http_method,
358
+ response_model=response_model,
359
+ description=description,
360
+ tags=tags,
361
+ )
362
+
363
+
364
+ def ensure_api_app() -> Optional[FastAPI]:
365
+ """Ensure the API app is created and return it."""
366
+ global _api_app
367
+
368
+ if _api_app is None:
369
+ # Get configuration
370
+ config = get_api_config()
371
+
372
+ # Check if API server should be enabled
373
+ if not config["enabled"]:
374
+ logger.debug("API server is disabled in configuration")
375
+ return None
376
+
377
+ # Create the API app
378
+ _api_app = FastAPI(
379
+ title="MCLI API", description="API endpoints for MCLI commands", version="1.0.0"
380
+ )
381
+
382
+ # Add CORS middleware
383
+ _api_app.add_middleware(
384
+ CORSMiddleware,
385
+ allow_origins=["*"],
386
+ allow_credentials=True,
387
+ allow_methods=["*"],
388
+ allow_headers=["*"],
389
+ )
390
+
391
+ # Add health check endpoint
392
+ @_api_app.get("/health")
393
+ async def health_check():
394
+ return {"status": "healthy", "service": "MCLI API"}
395
+
396
+ # Add root endpoint
397
+ @_api_app.get("/")
398
+ async def root():
399
+ return {
400
+ "service": "MCLI API",
401
+ "version": "1.0.0",
402
+ "status": "running",
403
+ "config": {
404
+ "host": config.get("host", "0.0.0.0"),
405
+ "port": config.get("port", "random"),
406
+ "debug": config.get("debug", False),
407
+ },
408
+ }
409
+
410
+ logger.debug("API app created successfully")
411
+
412
+ return _api_app
413
+
414
+
415
+ def start_api_server(host: str = None, port: int = None, debug: bool = None) -> str:
416
+ """Start the API server with configuration from MCLI config."""
417
+ global _api_app, _api_server_thread, _api_server_running
418
+
419
+ # Get configuration
420
+ config = get_api_config()
421
+
422
+ # Override with parameters if provided
423
+ if host is not None:
424
+ config["host"] = host
425
+ if port is not None:
426
+ config["port"] = port
427
+ config["use_random_port"] = False
428
+ if debug is not None:
429
+ config["debug"] = debug
430
+
431
+ # Check if API server should be enabled
432
+ if not config["enabled"]:
433
+ logger.info("API server is disabled in configuration")
434
+ return None
435
+
436
+ # Find port if not specified
437
+ if config["port"] is None:
438
+ config["port"] = find_free_port()
439
+ logger.info(f"Using random port: {config['port']}")
440
+
441
+ if _api_app is None:
442
+ _api_app = FastAPI(
443
+ title="MCLI API", description="API endpoints for MCLI commands", version="1.0.0"
444
+ )
445
+
446
+ # Add CORS middleware
447
+ _api_app.add_middleware(
448
+ CORSMiddleware,
449
+ allow_origins=["*"],
450
+ allow_credentials=True,
451
+ allow_methods=["*"],
452
+ allow_headers=["*"],
453
+ )
454
+
455
+ # Add health check endpoint
456
+ @_api_app.get("/health")
457
+ async def health_check():
458
+ return {"status": "healthy", "service": "MCLI API"}
459
+
460
+ # Add root endpoint
461
+ @_api_app.get("/")
462
+ async def root():
463
+ return {
464
+ "service": "MCLI API",
465
+ "version": "1.0.0",
466
+ "status": "running",
467
+ "config": {
468
+ "host": config["host"],
469
+ "port": config["port"],
470
+ "debug": config["debug"],
471
+ },
472
+ }
473
+
474
+ def run_server():
475
+ uvicorn.run(
476
+ _api_app, host=config["host"], port=config["port"], log_level="error", access_log=False
477
+ )
478
+
479
+ if not _api_server_running:
480
+ _api_server_thread = threading.Thread(target=run_server, daemon=True)
481
+ _api_server_thread.start()
482
+ _api_server_running = True
483
+
484
+ # Wait a moment for server to start
485
+ time.sleep(1)
486
+
487
+ api_url = f"http://{config['host']}:{config['port']}"
488
+ logger.info(f"API server started at {api_url}")
489
+ logger.info(f"Health check: {api_url}/health")
490
+ logger.info(f"Documentation: {api_url}/docs")
491
+
492
+ return api_url
493
+
494
+ return f"http://{config['host']}:{config['port']}"
495
+
496
+
497
+ def stop_api_server():
498
+ """Stop the API server."""
499
+ global _api_server_running
500
+
501
+ if _api_server_running:
502
+ # In a real implementation, you'd want to properly shutdown the server
503
+ # For now, we'll just set the flag
504
+ _api_server_running = False
505
+ logger.info("API server stopped")
506
+
507
+
508
+ def get_api_app() -> Optional[FastAPI]:
509
+ """Get the FastAPI app instance."""
510
+ return _api_app
511
+
512
+
513
+ def is_api_server_running() -> bool:
514
+ """Check if the API server is running."""
515
+ return _api_server_running
516
+
517
+
518
+ def register_command_as_api(
519
+ command_func: Callable,
520
+ endpoint_path: str = None,
521
+ http_method: str = "POST",
522
+ response_model: BaseModel = None,
523
+ description: str = None,
524
+ tags: List[str] = None,
525
+ ):
526
+ """
527
+ Register a Click command as an API endpoint.
528
+
529
+ Args:
530
+ command_func: The Click command function
531
+ endpoint_path: API endpoint path
532
+ http_method: HTTP method
533
+ response_model: Pydantic model for response
534
+ description: API endpoint description
535
+ tags: API tags for grouping
536
+ """
537
+ logger.info(
538
+ f"register_command_as_api called for: {command_func.__name__} with path: {endpoint_path}"
539
+ )
540
+
541
+ # Ensure API app is created
542
+ api_app = ensure_api_app()
543
+ if api_app is None:
544
+ logger.debug("API app not available, skipping endpoint registration")
545
+ return
546
+
547
+ logger.info(f"API app available, proceeding with registration")
548
+
549
+ decorator = ClickToAPIDecorator(
550
+ endpoint_path=endpoint_path,
551
+ http_method=http_method,
552
+ response_model=response_model,
553
+ description=description,
554
+ tags=tags,
555
+ )
556
+
557
+ # Register the endpoint directly with the API app
558
+ sig = inspect.signature(command_func)
559
+ decorator._register_api_endpoint(command_func, sig)
560
+
561
+ logger.info(
562
+ f"Registered command as API endpoint: {http_method} {endpoint_path or f'/{command_func.__name__}'}"
563
+ )
564
+
565
+
566
+ # Convenience function for common response models
567
+ def create_success_response_model(result_type: type = str):
568
+ """Create a success response model."""
569
+
570
+ class SuccessResponse(BaseModel):
571
+ success: bool = Field(True, description="Operation success status")
572
+ result: result_type = Field(..., description="Operation result")
573
+ message: str = Field("Operation completed successfully", description="Operation message")
574
+
575
+ return SuccessResponse
576
+
577
+
578
+ def create_error_response_model():
579
+ """Create an error response model."""
580
+
581
+ class ErrorResponse(BaseModel):
582
+ success: bool = Field(False, description="Operation success status")
583
+ error: str = Field(..., description="Error message")
584
+ message: str = Field("Operation failed", description="Operation message")
585
+
586
+ return ErrorResponse