mcli-framework 7.8.3__py3-none-any.whl → 7.8.5__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/__init__.py +160 -0
- mcli/__main__.py +14 -0
- mcli/app/__init__.py +23 -0
- mcli/app/commands_cmd.py +942 -199
- mcli/app/main.py +5 -21
- mcli/app/model/__init__.py +0 -0
- mcli/app/model_cmd.py +57 -472
- mcli/app/video/__init__.py +5 -0
- mcli/chat/__init__.py +34 -0
- mcli/lib/__init__.py +0 -0
- mcli/lib/api/__init__.py +0 -0
- mcli/lib/auth/__init__.py +1 -0
- mcli/lib/config/__init__.py +1 -0
- mcli/lib/erd/__init__.py +25 -0
- mcli/lib/files/__init__.py +0 -0
- mcli/lib/fs/__init__.py +1 -0
- mcli/lib/logger/__init__.py +3 -0
- mcli/lib/performance/__init__.py +17 -0
- mcli/lib/pickles/__init__.py +1 -0
- mcli/lib/shell/__init__.py +0 -0
- mcli/lib/toml/__init__.py +1 -0
- mcli/lib/watcher/__init__.py +0 -0
- mcli/ml/__init__.py +16 -0
- mcli/ml/api/__init__.py +30 -0
- mcli/ml/api/routers/__init__.py +27 -0
- mcli/ml/auth/__init__.py +41 -0
- mcli/ml/backtesting/__init__.py +33 -0
- mcli/ml/cli/__init__.py +5 -0
- mcli/ml/config/__init__.py +33 -0
- mcli/ml/configs/__init__.py +16 -0
- mcli/ml/dashboard/__init__.py +12 -0
- mcli/ml/dashboard/app_supabase.py +57 -12
- mcli/ml/dashboard/components/__init__.py +7 -0
- mcli/ml/dashboard/pages/__init__.py +6 -0
- mcli/ml/dashboard/pages/predictions_enhanced.py +82 -38
- mcli/ml/dashboard/utils.py +39 -11
- mcli/ml/data_ingestion/__init__.py +29 -0
- mcli/ml/database/__init__.py +40 -0
- mcli/ml/experimentation/__init__.py +29 -0
- mcli/ml/features/__init__.py +39 -0
- mcli/ml/mlops/__init__.py +19 -0
- mcli/ml/models/__init__.py +90 -0
- mcli/ml/monitoring/__init__.py +25 -0
- mcli/ml/optimization/__init__.py +27 -0
- mcli/ml/predictions/__init__.py +5 -0
- mcli/ml/preprocessing/__init__.py +24 -0
- mcli/ml/scripts/__init__.py +1 -0
- mcli/ml/trading/__init__.py +63 -0
- mcli/ml/training/__init__.py +7 -0
- mcli/mygroup/__init__.py +3 -0
- mcli/public/__init__.py +1 -0
- mcli/public/commands/__init__.py +2 -0
- mcli/self/__init__.py +3 -0
- mcli/self/self_cmd.py +4 -253
- mcli/self/store_cmd.py +5 -3
- mcli/workflow/__init__.py +0 -0
- mcli/workflow/daemon/__init__.py +15 -0
- mcli/workflow/dashboard/__init__.py +5 -0
- mcli/workflow/dashboard/dashboard_cmd.py +1 -0
- mcli/workflow/docker/__init__.py +0 -0
- mcli/workflow/file/__init__.py +0 -0
- mcli/workflow/gcloud/__init__.py +1 -0
- mcli/workflow/git_commit/__init__.py +0 -0
- mcli/workflow/interview/__init__.py +0 -0
- mcli/workflow/politician_trading/__init__.py +4 -0
- mcli/workflow/registry/__init__.py +0 -0
- mcli/workflow/repo/__init__.py +0 -0
- mcli/workflow/scheduler/__init__.py +25 -0
- mcli/workflow/search/__init__.py +0 -0
- mcli/workflow/sync/__init__.py +5 -0
- mcli/workflow/videos/__init__.py +1 -0
- mcli/workflow/wakatime/__init__.py +80 -0
- {mcli_framework-7.8.3.dist-info → mcli_framework-7.8.5.dist-info}/METADATA +1 -1
- {mcli_framework-7.8.3.dist-info → mcli_framework-7.8.5.dist-info}/RECORD +78 -18
- mcli/app/chat_cmd.py +0 -42
- mcli/test/cron_test_cmd.py +0 -697
- mcli/test/test_cmd.py +0 -30
- {mcli_framework-7.8.3.dist-info → mcli_framework-7.8.5.dist-info}/WHEEL +0 -0
- {mcli_framework-7.8.3.dist-info → mcli_framework-7.8.5.dist-info}/entry_points.txt +0 -0
- {mcli_framework-7.8.3.dist-info → mcli_framework-7.8.5.dist-info}/licenses/LICENSE +0 -0
- {mcli_framework-7.8.3.dist-info → mcli_framework-7.8.5.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Database models and utilities"""
|
|
2
|
+
|
|
3
|
+
from .models import (
|
|
4
|
+
Alert,
|
|
5
|
+
BacktestResult,
|
|
6
|
+
Base,
|
|
7
|
+
DataVersion,
|
|
8
|
+
Experiment,
|
|
9
|
+
FeatureSet,
|
|
10
|
+
Model,
|
|
11
|
+
Politician,
|
|
12
|
+
Portfolio,
|
|
13
|
+
Prediction,
|
|
14
|
+
StockData,
|
|
15
|
+
Trade,
|
|
16
|
+
User,
|
|
17
|
+
)
|
|
18
|
+
from .session import AsyncSessionLocal, SessionLocal, async_engine, engine, get_async_db, get_db
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"Base",
|
|
22
|
+
"User",
|
|
23
|
+
"Trade",
|
|
24
|
+
"Politician",
|
|
25
|
+
"StockData",
|
|
26
|
+
"Prediction",
|
|
27
|
+
"Portfolio",
|
|
28
|
+
"Alert",
|
|
29
|
+
"BacktestResult",
|
|
30
|
+
"Experiment",
|
|
31
|
+
"Model",
|
|
32
|
+
"FeatureSet",
|
|
33
|
+
"DataVersion",
|
|
34
|
+
"get_db",
|
|
35
|
+
"get_async_db",
|
|
36
|
+
"SessionLocal",
|
|
37
|
+
"AsyncSessionLocal",
|
|
38
|
+
"engine",
|
|
39
|
+
"async_engine",
|
|
40
|
+
]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""ML Experimentation and A/B Testing Framework"""
|
|
2
|
+
|
|
3
|
+
from .ab_testing import (
|
|
4
|
+
ABTestingFramework,
|
|
5
|
+
ExperimentConfig,
|
|
6
|
+
ExperimentResult,
|
|
7
|
+
ExperimentStatus,
|
|
8
|
+
Metric,
|
|
9
|
+
MetricsCollector,
|
|
10
|
+
StatisticalAnalyzer,
|
|
11
|
+
TrafficSplitter,
|
|
12
|
+
UserAssignment,
|
|
13
|
+
Variant,
|
|
14
|
+
VariantType,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"ABTestingFramework",
|
|
19
|
+
"ExperimentConfig",
|
|
20
|
+
"Variant",
|
|
21
|
+
"VariantType",
|
|
22
|
+
"Metric",
|
|
23
|
+
"ExperimentStatus",
|
|
24
|
+
"ExperimentResult",
|
|
25
|
+
"UserAssignment",
|
|
26
|
+
"TrafficSplitter",
|
|
27
|
+
"MetricsCollector",
|
|
28
|
+
"StatisticalAnalyzer",
|
|
29
|
+
]
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Feature Engineering Module for Stock Recommendation Models"""
|
|
2
|
+
|
|
3
|
+
from .ensemble_features import (
|
|
4
|
+
DynamicFeatureSelector,
|
|
5
|
+
EnsembleFeatureBuilder,
|
|
6
|
+
FeatureInteractionEngine,
|
|
7
|
+
)
|
|
8
|
+
from .political_features import (
|
|
9
|
+
CongressionalTrackingFeatures,
|
|
10
|
+
PolicyImpactFeatures,
|
|
11
|
+
PoliticalInfluenceFeatures,
|
|
12
|
+
)
|
|
13
|
+
from .recommendation_engine import (
|
|
14
|
+
RecommendationConfig,
|
|
15
|
+
RecommendationResult,
|
|
16
|
+
StockRecommendationEngine,
|
|
17
|
+
)
|
|
18
|
+
from .stock_features import (
|
|
19
|
+
CrossAssetFeatures,
|
|
20
|
+
MarketRegimeFeatures,
|
|
21
|
+
StockRecommendationFeatures,
|
|
22
|
+
TechnicalIndicatorFeatures,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"StockRecommendationFeatures",
|
|
27
|
+
"TechnicalIndicatorFeatures",
|
|
28
|
+
"MarketRegimeFeatures",
|
|
29
|
+
"CrossAssetFeatures",
|
|
30
|
+
"PoliticalInfluenceFeatures",
|
|
31
|
+
"CongressionalTrackingFeatures",
|
|
32
|
+
"PolicyImpactFeatures",
|
|
33
|
+
"EnsembleFeatureBuilder",
|
|
34
|
+
"FeatureInteractionEngine",
|
|
35
|
+
"DynamicFeatureSelector",
|
|
36
|
+
"StockRecommendationEngine",
|
|
37
|
+
"RecommendationConfig",
|
|
38
|
+
"RecommendationResult",
|
|
39
|
+
]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""MLOps components for ML pipeline management"""
|
|
2
|
+
|
|
3
|
+
from .experiment_tracker import ExperimentRun, ExperimentTracker, MLflowConfig, ModelRegistry
|
|
4
|
+
from .model_serving import ModelEndpoint, ModelServer, PredictionService
|
|
5
|
+
from .pipeline_orchestrator import MLPipeline, PipelineConfig, PipelineExecutor, PipelineStep
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"ExperimentTracker",
|
|
9
|
+
"ModelRegistry",
|
|
10
|
+
"MLflowConfig",
|
|
11
|
+
"ExperimentRun",
|
|
12
|
+
"ModelServer",
|
|
13
|
+
"PredictionService",
|
|
14
|
+
"ModelEndpoint",
|
|
15
|
+
"MLPipeline",
|
|
16
|
+
"PipelineStep",
|
|
17
|
+
"PipelineConfig",
|
|
18
|
+
"PipelineExecutor",
|
|
19
|
+
]
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""ML Models for Stock Recommendation System"""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any, Dict, Optional
|
|
5
|
+
|
|
6
|
+
import torch
|
|
7
|
+
|
|
8
|
+
from .base_models import BaseStockModel, ModelMetrics, ValidationResult
|
|
9
|
+
from .ensemble_models import (
|
|
10
|
+
AttentionStockPredictor,
|
|
11
|
+
CNNFeatureExtractor,
|
|
12
|
+
DeepEnsembleModel,
|
|
13
|
+
EnsembleConfig,
|
|
14
|
+
EnsembleTrainer,
|
|
15
|
+
LSTMStockPredictor,
|
|
16
|
+
ModelConfig,
|
|
17
|
+
TransformerStockModel,
|
|
18
|
+
)
|
|
19
|
+
from .recommendation_models import (
|
|
20
|
+
RecommendationConfig,
|
|
21
|
+
RecommendationTrainer,
|
|
22
|
+
StockRecommendationModel,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# Model registry
|
|
26
|
+
_loaded_models: Dict[str, Any] = {}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
async def load_production_models():
|
|
30
|
+
"""Load production models into memory"""
|
|
31
|
+
from mcli.ml.config import settings
|
|
32
|
+
from mcli.ml.logging import get_logger
|
|
33
|
+
|
|
34
|
+
logger = get_logger(__name__)
|
|
35
|
+
model_dir = settings.model.model_dir
|
|
36
|
+
|
|
37
|
+
if not model_dir.exists():
|
|
38
|
+
model_dir.mkdir(parents=True, exist_ok=True)
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
for model_path in model_dir.glob("*.pt"):
|
|
42
|
+
try:
|
|
43
|
+
model_id = model_path.stem
|
|
44
|
+
model = torch.load(model_path, map_location=settings.model.device)
|
|
45
|
+
_loaded_models[model_id] = model
|
|
46
|
+
logger.info(f"Loaded model: {model_id}")
|
|
47
|
+
except Exception as e:
|
|
48
|
+
logger.error(f"Failed to load model {model_path}: {e}")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
async def get_model_by_id(model_id: str):
|
|
52
|
+
"""Get loaded model by ID"""
|
|
53
|
+
from mcli.ml.config import settings
|
|
54
|
+
|
|
55
|
+
if model_id not in _loaded_models:
|
|
56
|
+
# Try to load from disk
|
|
57
|
+
model_path = settings.model.model_dir / f"{model_id}.pt"
|
|
58
|
+
if model_path.exists():
|
|
59
|
+
_loaded_models[model_id] = torch.load(model_path, map_location=settings.model.device)
|
|
60
|
+
|
|
61
|
+
return _loaded_models.get(model_id)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def initialize_models():
|
|
65
|
+
"""Initialize models on startup"""
|
|
66
|
+
from mcli.ml.logging import get_logger
|
|
67
|
+
|
|
68
|
+
logger = get_logger(__name__)
|
|
69
|
+
logger.info("Initializing ML models...")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
__all__ = [
|
|
73
|
+
"DeepEnsembleModel",
|
|
74
|
+
"AttentionStockPredictor",
|
|
75
|
+
"TransformerStockModel",
|
|
76
|
+
"LSTMStockPredictor",
|
|
77
|
+
"CNNFeatureExtractor",
|
|
78
|
+
"EnsembleTrainer",
|
|
79
|
+
"ModelConfig",
|
|
80
|
+
"EnsembleConfig",
|
|
81
|
+
"BaseStockModel",
|
|
82
|
+
"ModelMetrics",
|
|
83
|
+
"ValidationResult",
|
|
84
|
+
"StockRecommendationModel",
|
|
85
|
+
"RecommendationTrainer",
|
|
86
|
+
"RecommendationConfig",
|
|
87
|
+
"load_production_models",
|
|
88
|
+
"get_model_by_id",
|
|
89
|
+
"initialize_models",
|
|
90
|
+
]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""ML Model Monitoring and Drift Detection"""
|
|
2
|
+
|
|
3
|
+
from .drift_detection import (
|
|
4
|
+
AlertSeverity,
|
|
5
|
+
ConceptDriftDetector,
|
|
6
|
+
DataProfile,
|
|
7
|
+
DriftAlert,
|
|
8
|
+
DriftType,
|
|
9
|
+
ModelMetrics,
|
|
10
|
+
ModelMonitor,
|
|
11
|
+
OutlierDetector,
|
|
12
|
+
StatisticalDriftDetector,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"ModelMonitor",
|
|
17
|
+
"StatisticalDriftDetector",
|
|
18
|
+
"ConceptDriftDetector",
|
|
19
|
+
"OutlierDetector",
|
|
20
|
+
"DriftAlert",
|
|
21
|
+
"DriftType",
|
|
22
|
+
"AlertSeverity",
|
|
23
|
+
"ModelMetrics",
|
|
24
|
+
"DataProfile",
|
|
25
|
+
]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Advanced Portfolio Optimization"""
|
|
2
|
+
|
|
3
|
+
from .portfolio_optimizer import (
|
|
4
|
+
AdvancedPortfolioOptimizer,
|
|
5
|
+
BaseOptimizer,
|
|
6
|
+
BlackLittermanOptimizer,
|
|
7
|
+
CVaROptimizer,
|
|
8
|
+
KellyCriterionOptimizer,
|
|
9
|
+
MeanVarianceOptimizer,
|
|
10
|
+
OptimizationConstraints,
|
|
11
|
+
OptimizationObjective,
|
|
12
|
+
PortfolioAllocation,
|
|
13
|
+
RiskParityOptimizer,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"AdvancedPortfolioOptimizer",
|
|
18
|
+
"OptimizationObjective",
|
|
19
|
+
"OptimizationConstraints",
|
|
20
|
+
"PortfolioAllocation",
|
|
21
|
+
"MeanVarianceOptimizer",
|
|
22
|
+
"RiskParityOptimizer",
|
|
23
|
+
"BlackLittermanOptimizer",
|
|
24
|
+
"CVaROptimizer",
|
|
25
|
+
"KellyCriterionOptimizer",
|
|
26
|
+
"BaseOptimizer",
|
|
27
|
+
]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""ML Data Preprocessing Module"""
|
|
2
|
+
|
|
3
|
+
from .data_cleaners import MissingValueHandler, OutlierDetector, TradingDataCleaner
|
|
4
|
+
from .feature_extractors import (
|
|
5
|
+
MarketFeatureExtractor,
|
|
6
|
+
PoliticianFeatureExtractor,
|
|
7
|
+
SentimentFeatureExtractor,
|
|
8
|
+
TemporalFeatureExtractor,
|
|
9
|
+
)
|
|
10
|
+
from .ml_pipeline import MLDataPipeline, MLDataPipelineConfig
|
|
11
|
+
from .politician_trading_preprocessor import PoliticianTradingPreprocessor
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"PoliticianTradingPreprocessor",
|
|
15
|
+
"PoliticianFeatureExtractor",
|
|
16
|
+
"MarketFeatureExtractor",
|
|
17
|
+
"TemporalFeatureExtractor",
|
|
18
|
+
"SentimentFeatureExtractor",
|
|
19
|
+
"TradingDataCleaner",
|
|
20
|
+
"OutlierDetector",
|
|
21
|
+
"MissingValueHandler",
|
|
22
|
+
"MLDataPipeline",
|
|
23
|
+
"MLDataPipelineConfig",
|
|
24
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""ML scripts module."""
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Trading module for portfolio management and trade execution"""
|
|
2
|
+
|
|
3
|
+
from mcli.ml.trading.alpaca_client import (
|
|
4
|
+
AlpacaTradingClient,
|
|
5
|
+
create_trading_client,
|
|
6
|
+
get_alpaca_config_from_env,
|
|
7
|
+
)
|
|
8
|
+
from mcli.ml.trading.models import ( # Enums; Database models; Pydantic models
|
|
9
|
+
OrderCreate,
|
|
10
|
+
OrderResponse,
|
|
11
|
+
OrderSide,
|
|
12
|
+
OrderStatus,
|
|
13
|
+
OrderType,
|
|
14
|
+
Portfolio,
|
|
15
|
+
PortfolioCreate,
|
|
16
|
+
PortfolioPerformanceSnapshot,
|
|
17
|
+
PortfolioResponse,
|
|
18
|
+
PortfolioType,
|
|
19
|
+
Position,
|
|
20
|
+
PositionResponse,
|
|
21
|
+
PositionSide,
|
|
22
|
+
RiskLevel,
|
|
23
|
+
TradingAccount,
|
|
24
|
+
TradingAccountCreate,
|
|
25
|
+
TradingOrder,
|
|
26
|
+
TradingSignal,
|
|
27
|
+
TradingSignalResponse,
|
|
28
|
+
)
|
|
29
|
+
from mcli.ml.trading.paper_trading import PaperTradingEngine
|
|
30
|
+
from mcli.ml.trading.risk_management import RiskManager
|
|
31
|
+
from mcli.ml.trading.trading_service import TradingService
|
|
32
|
+
|
|
33
|
+
__all__ = [
|
|
34
|
+
# Enums
|
|
35
|
+
"OrderStatus",
|
|
36
|
+
"OrderType",
|
|
37
|
+
"OrderSide",
|
|
38
|
+
"PositionSide",
|
|
39
|
+
"PortfolioType",
|
|
40
|
+
"RiskLevel",
|
|
41
|
+
# Database models
|
|
42
|
+
"TradingAccount",
|
|
43
|
+
"Portfolio",
|
|
44
|
+
"Position",
|
|
45
|
+
"TradingOrder",
|
|
46
|
+
"PortfolioPerformanceSnapshot",
|
|
47
|
+
"TradingSignal",
|
|
48
|
+
# Pydantic models
|
|
49
|
+
"TradingAccountCreate",
|
|
50
|
+
"PortfolioCreate",
|
|
51
|
+
"OrderCreate",
|
|
52
|
+
"PositionResponse",
|
|
53
|
+
"OrderResponse",
|
|
54
|
+
"PortfolioResponse",
|
|
55
|
+
"TradingSignalResponse",
|
|
56
|
+
# Services
|
|
57
|
+
"TradingService",
|
|
58
|
+
"AlpacaTradingClient",
|
|
59
|
+
"create_trading_client",
|
|
60
|
+
"get_alpaca_config_from_env",
|
|
61
|
+
"RiskManager",
|
|
62
|
+
"PaperTradingEngine",
|
|
63
|
+
]
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""ML model training module"""
|
|
2
|
+
|
|
3
|
+
from .train_model import PoliticianTradingNet, fetch_training_data
|
|
4
|
+
from .train_model import main as train_model
|
|
5
|
+
from .train_model import prepare_dataset
|
|
6
|
+
|
|
7
|
+
__all__ = ["PoliticianTradingNet", "train_model", "fetch_training_data", "prepare_dataset"]
|
mcli/mygroup/__init__.py
ADDED
mcli/public/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# logger.info("I am in mcli.public.__init__.py")
|
mcli/self/__init__.py
ADDED
mcli/self/self_cmd.py
CHANGED
|
@@ -102,79 +102,8 @@ def restore_command_state(hash_value):
|
|
|
102
102
|
return True
|
|
103
103
|
|
|
104
104
|
|
|
105
|
-
# Create a Click group for all command management
|
|
106
|
-
@self_app.group("commands")
|
|
107
|
-
def commands_group():
|
|
108
|
-
"""Manage CLI commands and command state."""
|
|
109
|
-
pass
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
# Move the command-state group under commands_group
|
|
113
|
-
@commands_group.group("state")
|
|
114
|
-
def command_state():
|
|
115
|
-
"""Manage command state lockfile and history."""
|
|
116
|
-
pass
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
@command_state.command("list")
|
|
120
|
-
def list_states():
|
|
121
|
-
"""List all saved command states (hash, timestamp, #commands)."""
|
|
122
|
-
states = load_lockfile()
|
|
123
|
-
if not states:
|
|
124
|
-
click.echo("No command states found.")
|
|
125
|
-
return
|
|
126
|
-
table = Table(title="Command States")
|
|
127
|
-
table.add_column("Hash", style="cyan")
|
|
128
|
-
table.add_column("Timestamp", style="green")
|
|
129
|
-
table.add_column("# Commands", style="yellow")
|
|
130
|
-
for state in states:
|
|
131
|
-
table.add_row(state["hash"][:8], state["timestamp"], str(len(state["commands"])))
|
|
132
|
-
console.print(table)
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
@command_state.command("restore")
|
|
136
|
-
@click.argument("hash_value")
|
|
137
|
-
def restore_state(hash_value):
|
|
138
|
-
"""Restore to a previous command state by hash."""
|
|
139
|
-
if restore_command_state(hash_value):
|
|
140
|
-
click.echo(f"Restored to state {hash_value[:8]}")
|
|
141
|
-
else:
|
|
142
|
-
click.echo(f"State {hash_value[:8]} not found.", err=True)
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
@command_state.command("write")
|
|
146
|
-
@click.argument("json_file", required=False, type=click.Path(exists=False))
|
|
147
|
-
def write_state(json_file):
|
|
148
|
-
"""Write a new command state to the lockfile from a JSON file or the current app state."""
|
|
149
|
-
import traceback
|
|
150
|
-
|
|
151
|
-
print("[DEBUG] write_state called")
|
|
152
|
-
print(f"[DEBUG] LOCKFILE_PATH: {LOCKFILE_PATH}")
|
|
153
|
-
try:
|
|
154
|
-
if json_file:
|
|
155
|
-
print(f"[DEBUG] Loading command state from file: {json_file}")
|
|
156
|
-
with open(json_file, "r") as f:
|
|
157
|
-
commands = json.load(f)
|
|
158
|
-
click.echo(f"Loaded command state from {json_file}.")
|
|
159
|
-
else:
|
|
160
|
-
print("[DEBUG] Snapshotting current command state.")
|
|
161
|
-
commands = get_current_command_state()
|
|
162
|
-
state_hash = hash_command_state(commands)
|
|
163
|
-
new_state = {
|
|
164
|
-
"hash": state_hash,
|
|
165
|
-
"timestamp": datetime.utcnow().isoformat() + "Z",
|
|
166
|
-
"commands": commands,
|
|
167
|
-
}
|
|
168
|
-
append_lockfile(new_state)
|
|
169
|
-
print(f"[DEBUG] Wrote new command state {state_hash[:8]} to lockfile at {LOCKFILE_PATH}")
|
|
170
|
-
click.echo(f"Wrote new command state {state_hash[:8]} to lockfile.")
|
|
171
|
-
except Exception as e:
|
|
172
|
-
print(f"[ERROR] Exception in write_state: {e}")
|
|
173
|
-
print(traceback.format_exc())
|
|
174
|
-
click.echo(f"[ERROR] Failed to write command state: {e}", err=True)
|
|
175
|
-
|
|
176
|
-
|
|
177
105
|
# On CLI startup, check and update lockfile if needed
|
|
106
|
+
# NOTE: The commands group has been moved to mcli.app.commands_cmd for better organization
|
|
178
107
|
|
|
179
108
|
|
|
180
109
|
def check_and_update_command_lockfile():
|
|
@@ -250,76 +179,7 @@ def {name}_command(name: str = "World"):
|
|
|
250
179
|
return template
|
|
251
180
|
|
|
252
181
|
|
|
253
|
-
|
|
254
|
-
@click.argument("query", required=False)
|
|
255
|
-
@click.option("--full", "-f", is_flag=True, help="Show full command paths and descriptions")
|
|
256
|
-
def search(query, full):
|
|
257
|
-
"""
|
|
258
|
-
Search for available commands using fuzzy matching.
|
|
259
|
-
|
|
260
|
-
Similar to telescope in neovim, this allows quick fuzzy searching
|
|
261
|
-
through all available commands in mcli.
|
|
262
|
-
|
|
263
|
-
If no query is provided, lists all commands.
|
|
264
|
-
"""
|
|
265
|
-
# Collect all commands from the application
|
|
266
|
-
commands = collect_commands()
|
|
267
|
-
|
|
268
|
-
# Display the commands in a table
|
|
269
|
-
table = Table(title="mcli Commands")
|
|
270
|
-
table.add_column("Command", style="green")
|
|
271
|
-
table.add_column("Group", style="blue")
|
|
272
|
-
if full:
|
|
273
|
-
table.add_column("Path", style="dim")
|
|
274
|
-
table.add_column("Description", style="yellow")
|
|
275
|
-
|
|
276
|
-
if query:
|
|
277
|
-
filtered_commands = []
|
|
278
|
-
|
|
279
|
-
# Try to use fuzzywuzzy for better matching if available
|
|
280
|
-
if process:
|
|
281
|
-
# Extract command names for matching
|
|
282
|
-
command_names = [
|
|
283
|
-
f"{cmd['group']}.{cmd['name']}" if cmd["group"] else cmd["name"] for cmd in commands
|
|
284
|
-
]
|
|
285
|
-
matches = process.extract(query, command_names, limit=10)
|
|
286
|
-
|
|
287
|
-
# Filter to matched commands
|
|
288
|
-
match_indices = [command_names.index(match[0]) for match in matches if match[1] > 50]
|
|
289
|
-
filtered_commands = [commands[i] for i in match_indices]
|
|
290
|
-
else:
|
|
291
|
-
# Fallback to simple substring matching
|
|
292
|
-
filtered_commands = [
|
|
293
|
-
cmd
|
|
294
|
-
for cmd in commands
|
|
295
|
-
if query.lower() in cmd["name"].lower()
|
|
296
|
-
or (cmd["group"] and query.lower() in cmd["group"].lower())
|
|
297
|
-
]
|
|
298
|
-
|
|
299
|
-
commands = filtered_commands
|
|
300
|
-
|
|
301
|
-
# Sort commands by group then name
|
|
302
|
-
commands.sort(key=lambda c: (c["group"] if c["group"] else "", c["name"]))
|
|
303
|
-
|
|
304
|
-
# Add rows to the table
|
|
305
|
-
for cmd in commands:
|
|
306
|
-
if full:
|
|
307
|
-
table.add_row(
|
|
308
|
-
cmd["name"],
|
|
309
|
-
cmd["group"] if cmd["group"] else "-",
|
|
310
|
-
cmd["path"],
|
|
311
|
-
cmd["help"] if cmd["help"] else "",
|
|
312
|
-
)
|
|
313
|
-
else:
|
|
314
|
-
table.add_row(cmd["name"], cmd["group"] if cmd["group"] else "-")
|
|
315
|
-
|
|
316
|
-
console.print(table)
|
|
317
|
-
|
|
318
|
-
if not commands:
|
|
319
|
-
logger.info("No commands found matching the search query")
|
|
320
|
-
click.echo("No commands found matching the search query")
|
|
321
|
-
|
|
322
|
-
return 0
|
|
182
|
+
# NOTE: search command has been moved to mcli.app.commands_cmd for better organization
|
|
323
183
|
|
|
324
184
|
|
|
325
185
|
def collect_commands() -> List[Dict[str, Any]]:
|
|
@@ -575,110 +435,7 @@ logger = get_logger()
|
|
|
575
435
|
pass
|
|
576
436
|
|
|
577
437
|
|
|
578
|
-
|
|
579
|
-
@click.option(
|
|
580
|
-
"--output", "-o", type=click.Path(), help="Output file (default: workflow-commands.json)"
|
|
581
|
-
)
|
|
582
|
-
def extract_workflow_commands(output):
|
|
583
|
-
"""
|
|
584
|
-
Extract workflow commands from Python modules to JSON format.
|
|
585
|
-
|
|
586
|
-
This command helps migrate existing workflow commands to portable JSON format.
|
|
587
|
-
"""
|
|
588
|
-
import inspect
|
|
589
|
-
from pathlib import Path
|
|
590
|
-
|
|
591
|
-
output_file = Path(output) if output else Path("workflow-commands.json")
|
|
592
|
-
|
|
593
|
-
workflow_commands = []
|
|
594
|
-
|
|
595
|
-
# Try to get workflow from the main app
|
|
596
|
-
try:
|
|
597
|
-
from mcli.app.main import create_app
|
|
598
|
-
|
|
599
|
-
app = create_app()
|
|
600
|
-
|
|
601
|
-
# Check if workflow group exists
|
|
602
|
-
if "workflow" in app.commands:
|
|
603
|
-
workflow_group = app.commands["workflow"]
|
|
604
|
-
|
|
605
|
-
# Force load lazy group if needed
|
|
606
|
-
if hasattr(workflow_group, "_load_group"):
|
|
607
|
-
workflow_group = workflow_group._load_group()
|
|
608
|
-
|
|
609
|
-
if hasattr(workflow_group, "commands"):
|
|
610
|
-
for cmd_name, cmd_obj in workflow_group.commands.items():
|
|
611
|
-
# Extract command information
|
|
612
|
-
command_info = {
|
|
613
|
-
"name": cmd_name,
|
|
614
|
-
"group": "workflow",
|
|
615
|
-
"description": cmd_obj.help or "Workflow command",
|
|
616
|
-
"version": "1.0",
|
|
617
|
-
"metadata": {"source": "workflow", "migrated": True},
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
# Create a template based on command type
|
|
621
|
-
# Replace hyphens with underscores for valid Python function names
|
|
622
|
-
safe_name = cmd_name.replace("-", "_")
|
|
623
|
-
|
|
624
|
-
if isinstance(cmd_obj, click.Group):
|
|
625
|
-
# For groups, create a template
|
|
626
|
-
command_info[
|
|
627
|
-
"code"
|
|
628
|
-
] = f'''"""
|
|
629
|
-
{cmd_name} workflow command.
|
|
630
|
-
"""
|
|
631
|
-
import click
|
|
632
|
-
|
|
633
|
-
@click.group(name="{cmd_name}")
|
|
634
|
-
def app():
|
|
635
|
-
"""{cmd_obj.help or 'Workflow command group'}"""
|
|
636
|
-
pass
|
|
637
|
-
|
|
638
|
-
# Add your subcommands here
|
|
639
|
-
'''
|
|
640
|
-
else:
|
|
641
|
-
# For regular commands, create a template
|
|
642
|
-
command_info[
|
|
643
|
-
"code"
|
|
644
|
-
] = f'''"""
|
|
645
|
-
{cmd_name} workflow command.
|
|
646
|
-
"""
|
|
647
|
-
import click
|
|
648
|
-
|
|
649
|
-
@click.command(name="{cmd_name}")
|
|
650
|
-
def app():
|
|
651
|
-
"""{cmd_obj.help or 'Workflow command'}"""
|
|
652
|
-
click.echo("Workflow command: {cmd_name}")
|
|
653
|
-
# Add your implementation here
|
|
654
|
-
'''
|
|
655
|
-
|
|
656
|
-
workflow_commands.append(command_info)
|
|
657
|
-
|
|
658
|
-
if workflow_commands:
|
|
659
|
-
import json
|
|
660
|
-
|
|
661
|
-
with open(output_file, "w") as f:
|
|
662
|
-
json.dump(workflow_commands, f, indent=2)
|
|
663
|
-
|
|
664
|
-
click.echo(f"✅ Extracted {len(workflow_commands)} workflow commands")
|
|
665
|
-
click.echo(f"📁 Saved to: {output_file}")
|
|
666
|
-
click.echo(
|
|
667
|
-
f"\n💡 These are templates. Import with: mcli self import-commands {output_file}"
|
|
668
|
-
)
|
|
669
|
-
click.echo(" Then customize the code in ~/.mcli/commands/<command>.json")
|
|
670
|
-
return 0
|
|
671
|
-
else:
|
|
672
|
-
click.echo("⚠️ No workflow commands found to extract")
|
|
673
|
-
return 1
|
|
674
|
-
|
|
675
|
-
except Exception as e:
|
|
676
|
-
logger.error(f"Failed to extract workflow commands: {e}")
|
|
677
|
-
click.echo(f"❌ Failed to extract workflow commands: {e}", err=True)
|
|
678
|
-
import traceback
|
|
679
|
-
|
|
680
|
-
click.echo(traceback.format_exc(), err=True)
|
|
681
|
-
return 1
|
|
438
|
+
# NOTE: extract-workflow-commands has been moved to mcli.app.commands_cmd for better organization
|
|
682
439
|
|
|
683
440
|
|
|
684
441
|
@click.group("plugin")
|
|
@@ -1280,13 +1037,7 @@ try:
|
|
|
1280
1037
|
except ImportError as e:
|
|
1281
1038
|
logger.debug(f"Could not load visual command: {e}")
|
|
1282
1039
|
|
|
1283
|
-
|
|
1284
|
-
from mcli.self.store_cmd import store
|
|
1285
|
-
|
|
1286
|
-
self_app.add_command(store, name="store")
|
|
1287
|
-
logger.debug("Added store command to self group")
|
|
1288
|
-
except ImportError as e:
|
|
1289
|
-
logger.debug(f"Could not load store command: {e}")
|
|
1040
|
+
# NOTE: store command has been moved to mcli.app.commands_cmd for better organization
|
|
1290
1041
|
|
|
1291
1042
|
# This part is important to make the command available to the CLI
|
|
1292
1043
|
if __name__ == "__main__":
|