mcli-framework 7.12.2__py3-none-any.whl → 7.12.4__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/__init__.py +0 -2
- mcli/app/commands_cmd.py +30 -26
- mcli/app/completion_helpers.py +5 -5
- mcli/app/init_cmd.py +10 -10
- mcli/app/lock_cmd.py +29 -24
- mcli/app/main.py +2 -8
- mcli/app/model/model.py +5 -10
- mcli/app/store_cmd.py +8 -8
- mcli/app/video/__init__.py +0 -2
- mcli/app/video/video.py +1 -14
- mcli/chat/chat.py +90 -108
- mcli/chat/command_rag.py +0 -4
- mcli/chat/enhanced_chat.py +32 -41
- mcli/chat/system_controller.py +37 -37
- mcli/chat/system_integration.py +4 -5
- mcli/cli.py +2 -3
- mcli/lib/api/api.py +4 -9
- mcli/lib/api/daemon_client.py +19 -20
- mcli/lib/api/daemon_client_local.py +1 -3
- mcli/lib/api/daemon_decorator.py +6 -6
- mcli/lib/api/mcli_decorators.py +4 -8
- mcli/lib/auth/__init__.py +0 -1
- mcli/lib/auth/auth.py +4 -5
- mcli/lib/auth/mcli_manager.py +7 -12
- mcli/lib/auth/token_util.py +5 -5
- mcli/lib/config/__init__.py +29 -1
- mcli/lib/config/config.py +0 -1
- mcli/lib/custom_commands.py +1 -1
- mcli/lib/discovery/command_discovery.py +15 -15
- mcli/lib/erd/erd.py +7 -7
- mcli/lib/files/files.py +1 -1
- mcli/lib/fs/__init__.py +31 -1
- mcli/lib/fs/fs.py +12 -13
- mcli/lib/lib.py +0 -1
- mcli/lib/logger/logger.py +7 -10
- mcli/lib/performance/optimizer.py +25 -27
- mcli/lib/performance/rust_bridge.py +22 -27
- mcli/lib/performance/uvloop_config.py +0 -1
- mcli/lib/pickles/__init__.py +0 -1
- mcli/lib/pickles/pickles.py +0 -2
- mcli/lib/secrets/commands.py +0 -2
- mcli/lib/secrets/manager.py +0 -1
- mcli/lib/secrets/repl.py +2 -3
- mcli/lib/secrets/store.py +1 -2
- mcli/lib/services/data_pipeline.py +34 -34
- mcli/lib/services/lsh_client.py +38 -40
- mcli/lib/shell/shell.py +2 -2
- mcli/lib/toml/__init__.py +0 -1
- mcli/lib/ui/styling.py +0 -1
- mcli/lib/ui/visual_effects.py +33 -41
- mcli/lib/watcher/watcher.py +0 -1
- mcli/ml/__init__.py +1 -1
- mcli/ml/api/__init__.py +1 -1
- mcli/ml/api/app.py +8 -9
- mcli/ml/api/middleware.py +10 -10
- mcli/ml/api/routers/__init__.py +1 -1
- mcli/ml/api/routers/admin_router.py +3 -3
- mcli/ml/api/routers/auth_router.py +17 -18
- mcli/ml/api/routers/backtest_router.py +2 -2
- mcli/ml/api/routers/data_router.py +2 -2
- mcli/ml/api/routers/model_router.py +14 -15
- mcli/ml/api/routers/monitoring_router.py +2 -2
- mcli/ml/api/routers/portfolio_router.py +2 -2
- mcli/ml/api/routers/prediction_router.py +10 -9
- mcli/ml/api/routers/trade_router.py +2 -2
- mcli/ml/api/routers/websocket_router.py +6 -7
- mcli/ml/api/schemas.py +2 -2
- mcli/ml/auth/__init__.py +1 -1
- mcli/ml/auth/auth_manager.py +22 -23
- mcli/ml/auth/models.py +17 -17
- mcli/ml/auth/permissions.py +17 -17
- mcli/ml/backtesting/__init__.py +1 -1
- mcli/ml/backtesting/backtest_engine.py +31 -35
- mcli/ml/backtesting/performance_metrics.py +12 -14
- mcli/ml/backtesting/run.py +1 -2
- mcli/ml/cache.py +35 -36
- mcli/ml/cli/__init__.py +1 -1
- mcli/ml/cli/main.py +21 -24
- mcli/ml/config/__init__.py +1 -1
- mcli/ml/config/settings.py +28 -29
- mcli/ml/configs/__init__.py +1 -1
- mcli/ml/configs/dvc_config.py +14 -15
- mcli/ml/configs/mlflow_config.py +12 -13
- mcli/ml/configs/mlops_manager.py +19 -21
- mcli/ml/dashboard/__init__.py +4 -4
- mcli/ml/dashboard/app.py +20 -30
- mcli/ml/dashboard/app_supabase.py +16 -19
- mcli/ml/dashboard/app_training.py +11 -14
- mcli/ml/dashboard/cli.py +2 -2
- mcli/ml/dashboard/common.py +2 -3
- mcli/ml/dashboard/components/__init__.py +1 -1
- mcli/ml/dashboard/components/charts.py +13 -11
- mcli/ml/dashboard/components/metrics.py +7 -7
- mcli/ml/dashboard/components/tables.py +12 -9
- mcli/ml/dashboard/overview.py +2 -2
- mcli/ml/dashboard/pages/__init__.py +1 -1
- mcli/ml/dashboard/pages/cicd.py +15 -18
- mcli/ml/dashboard/pages/debug_dependencies.py +7 -7
- mcli/ml/dashboard/pages/monte_carlo_predictions.py +11 -18
- mcli/ml/dashboard/pages/predictions_enhanced.py +24 -32
- mcli/ml/dashboard/pages/scrapers_and_logs.py +22 -24
- mcli/ml/dashboard/pages/test_portfolio.py +3 -6
- mcli/ml/dashboard/pages/trading.py +16 -18
- mcli/ml/dashboard/pages/workflows.py +20 -30
- mcli/ml/dashboard/utils.py +9 -9
- mcli/ml/dashboard/warning_suppression.py +3 -3
- mcli/ml/data_ingestion/__init__.py +1 -1
- mcli/ml/data_ingestion/api_connectors.py +41 -46
- mcli/ml/data_ingestion/data_pipeline.py +36 -46
- mcli/ml/data_ingestion/stream_processor.py +43 -46
- mcli/ml/database/__init__.py +1 -1
- mcli/ml/database/migrations/env.py +2 -2
- mcli/ml/database/models.py +22 -24
- mcli/ml/database/session.py +14 -14
- mcli/ml/experimentation/__init__.py +1 -1
- mcli/ml/experimentation/ab_testing.py +45 -46
- mcli/ml/features/__init__.py +1 -1
- mcli/ml/features/ensemble_features.py +22 -27
- mcli/ml/features/recommendation_engine.py +30 -30
- mcli/ml/features/stock_features.py +29 -32
- mcli/ml/features/test_feature_engineering.py +10 -11
- mcli/ml/logging.py +4 -4
- mcli/ml/mlops/__init__.py +1 -1
- mcli/ml/mlops/data_versioning.py +29 -30
- mcli/ml/mlops/experiment_tracker.py +24 -24
- mcli/ml/mlops/model_serving.py +31 -34
- mcli/ml/mlops/pipeline_orchestrator.py +27 -35
- mcli/ml/models/__init__.py +5 -6
- mcli/ml/models/base_models.py +23 -23
- mcli/ml/models/ensemble_models.py +31 -31
- mcli/ml/models/recommendation_models.py +18 -19
- mcli/ml/models/test_models.py +14 -16
- mcli/ml/monitoring/__init__.py +1 -1
- mcli/ml/monitoring/drift_detection.py +32 -36
- mcli/ml/monitoring/metrics.py +2 -2
- mcli/ml/optimization/__init__.py +1 -1
- mcli/ml/optimization/optimize.py +1 -2
- mcli/ml/optimization/portfolio_optimizer.py +30 -32
- mcli/ml/predictions/__init__.py +1 -1
- mcli/ml/preprocessing/__init__.py +1 -1
- mcli/ml/preprocessing/data_cleaners.py +22 -23
- mcli/ml/preprocessing/feature_extractors.py +23 -26
- mcli/ml/preprocessing/ml_pipeline.py +23 -23
- mcli/ml/preprocessing/test_preprocessing.py +7 -8
- mcli/ml/scripts/populate_sample_data.py +0 -4
- mcli/ml/serving/serve.py +1 -2
- mcli/ml/tasks.py +17 -17
- mcli/ml/tests/test_integration.py +29 -30
- mcli/ml/tests/test_training_dashboard.py +21 -21
- mcli/ml/trading/__init__.py +1 -1
- mcli/ml/trading/migrations.py +5 -5
- mcli/ml/trading/models.py +21 -23
- mcli/ml/trading/paper_trading.py +16 -13
- mcli/ml/trading/risk_management.py +17 -18
- mcli/ml/trading/trading_service.py +25 -28
- mcli/ml/training/__init__.py +1 -1
- mcli/ml/training/train.py +0 -1
- mcli/public/oi/oi.py +1 -2
- mcli/self/completion_cmd.py +6 -10
- mcli/self/logs_cmd.py +19 -24
- mcli/self/migrate_cmd.py +22 -20
- mcli/self/redis_cmd.py +10 -11
- mcli/self/self_cmd.py +10 -18
- mcli/self/store_cmd.py +10 -12
- mcli/self/visual_cmd.py +9 -14
- mcli/self/zsh_cmd.py +2 -4
- mcli/workflow/daemon/async_command_database.py +23 -24
- mcli/workflow/daemon/async_process_manager.py +27 -29
- mcli/workflow/daemon/client.py +27 -33
- mcli/workflow/daemon/daemon.py +32 -36
- mcli/workflow/daemon/enhanced_daemon.py +24 -33
- mcli/workflow/daemon/process_cli.py +11 -12
- mcli/workflow/daemon/process_manager.py +23 -26
- mcli/workflow/daemon/test_daemon.py +4 -5
- mcli/workflow/dashboard/dashboard_cmd.py +0 -1
- mcli/workflow/doc_convert.py +15 -17
- mcli/workflow/gcloud/__init__.py +0 -1
- mcli/workflow/gcloud/gcloud.py +11 -8
- mcli/workflow/git_commit/ai_service.py +14 -15
- mcli/workflow/lsh_integration.py +9 -11
- mcli/workflow/model_service/client.py +26 -31
- mcli/workflow/model_service/download_and_run_efficient_models.py +10 -14
- mcli/workflow/model_service/lightweight_embedder.py +25 -35
- mcli/workflow/model_service/lightweight_model_server.py +26 -32
- mcli/workflow/model_service/lightweight_test.py +7 -10
- mcli/workflow/model_service/model_service.py +80 -91
- mcli/workflow/model_service/ollama_efficient_runner.py +14 -18
- mcli/workflow/model_service/openai_adapter.py +23 -23
- mcli/workflow/model_service/pdf_processor.py +21 -26
- mcli/workflow/model_service/test_efficient_runner.py +12 -16
- mcli/workflow/model_service/test_example.py +11 -13
- mcli/workflow/model_service/test_integration.py +3 -5
- mcli/workflow/model_service/test_new_features.py +7 -8
- mcli/workflow/notebook/converter.py +1 -1
- mcli/workflow/notebook/notebook_cmd.py +5 -6
- mcli/workflow/notebook/schema.py +0 -1
- mcli/workflow/notebook/validator.py +7 -3
- mcli/workflow/openai/openai.py +1 -2
- mcli/workflow/registry/registry.py +4 -1
- mcli/workflow/repo/repo.py +6 -7
- mcli/workflow/scheduler/cron_parser.py +16 -19
- mcli/workflow/scheduler/job.py +10 -10
- mcli/workflow/scheduler/monitor.py +15 -15
- mcli/workflow/scheduler/persistence.py +17 -18
- mcli/workflow/scheduler/scheduler.py +37 -38
- mcli/workflow/secrets/__init__.py +1 -1
- mcli/workflow/sync/test_cmd.py +0 -1
- mcli/workflow/wakatime/__init__.py +5 -9
- mcli/workflow/wakatime/wakatime.py +1 -2
- {mcli_framework-7.12.2.dist-info → mcli_framework-7.12.4.dist-info}/METADATA +1 -1
- mcli_framework-7.12.4.dist-info/RECORD +279 -0
- mcli_framework-7.12.2.dist-info/RECORD +0 -279
- {mcli_framework-7.12.2.dist-info → mcli_framework-7.12.4.dist-info}/WHEEL +0 -0
- {mcli_framework-7.12.2.dist-info → mcli_framework-7.12.4.dist-info}/entry_points.txt +0 -0
- {mcli_framework-7.12.2.dist-info → mcli_framework-7.12.4.dist-info}/licenses/LICENSE +0 -0
- {mcli_framework-7.12.2.dist-info → mcli_framework-7.12.4.dist-info}/top_level.txt +0 -0
mcli/app/__init__.py
CHANGED
mcli/app/commands_cmd.py
CHANGED
|
@@ -4,7 +4,6 @@ import inspect
|
|
|
4
4
|
import json
|
|
5
5
|
import os
|
|
6
6
|
import re
|
|
7
|
-
import shutil
|
|
8
7
|
import subprocess
|
|
9
8
|
import tempfile
|
|
10
9
|
from datetime import datetime
|
|
@@ -12,16 +11,13 @@ from pathlib import Path
|
|
|
12
11
|
from typing import Any, Dict, List, Optional
|
|
13
12
|
|
|
14
13
|
import click
|
|
15
|
-
import tomli
|
|
16
|
-
from rich.console import Console
|
|
17
14
|
from rich.prompt import Prompt
|
|
18
|
-
from rich.table import Table
|
|
19
15
|
|
|
20
16
|
from mcli.lib.api.daemon_client import get_daemon_client
|
|
21
17
|
from mcli.lib.custom_commands import get_command_manager
|
|
22
18
|
from mcli.lib.discovery.command_discovery import get_command_discovery
|
|
23
19
|
from mcli.lib.logger.logger import get_logger
|
|
24
|
-
from mcli.lib.ui.styling import console
|
|
20
|
+
from mcli.lib.ui.styling import console
|
|
25
21
|
|
|
26
22
|
logger = get_logger(__name__)
|
|
27
23
|
|
|
@@ -78,7 +74,7 @@ def collect_commands() -> List[Dict[str, Any]]:
|
|
|
78
74
|
streamlit_logger.setLevel(original_level)
|
|
79
75
|
|
|
80
76
|
# Extract command and group objects
|
|
81
|
-
for
|
|
77
|
+
for _name, obj in inspect.getmembers(module):
|
|
82
78
|
# Handle Click commands and groups
|
|
83
79
|
if isinstance(obj, click.Command):
|
|
84
80
|
if isinstance(obj, click.Group):
|
|
@@ -118,7 +114,7 @@ def collect_commands() -> List[Dict[str, Any]]:
|
|
|
118
114
|
|
|
119
115
|
|
|
120
116
|
def get_current_command_state():
|
|
121
|
-
"""Collect all command metadata (names, groups, etc.)"""
|
|
117
|
+
"""Collect all command metadata (names, groups, etc.)."""
|
|
122
118
|
return collect_commands()
|
|
123
119
|
|
|
124
120
|
|
|
@@ -134,7 +130,11 @@ def load_lockfile():
|
|
|
134
130
|
"""Load the command state lockfile."""
|
|
135
131
|
if LOCKFILE_PATH.exists():
|
|
136
132
|
with open(LOCKFILE_PATH, "r") as f:
|
|
137
|
-
|
|
133
|
+
data = json.load(f)
|
|
134
|
+
# Handle both old format (array) and new format (object with "states" key)
|
|
135
|
+
if isinstance(data, dict) and "states" in data:
|
|
136
|
+
return data["states"]
|
|
137
|
+
return data if isinstance(data, list) else []
|
|
138
138
|
return []
|
|
139
139
|
|
|
140
140
|
|
|
@@ -153,11 +153,19 @@ def append_lockfile(new_state):
|
|
|
153
153
|
|
|
154
154
|
|
|
155
155
|
def find_state_by_hash(hash_value):
|
|
156
|
-
"""Find a state by its hash value."""
|
|
156
|
+
"""Find a state by its hash value (supports partial hash matching)."""
|
|
157
157
|
states = load_lockfile()
|
|
158
|
+
matches = []
|
|
158
159
|
for state in states:
|
|
159
|
-
|
|
160
|
-
|
|
160
|
+
# Support both full hash and partial hash (prefix) matching
|
|
161
|
+
if state["hash"] == hash_value or state["hash"].startswith(hash_value):
|
|
162
|
+
matches.append(state)
|
|
163
|
+
|
|
164
|
+
if len(matches) == 1:
|
|
165
|
+
return matches[0]
|
|
166
|
+
elif len(matches) > 1:
|
|
167
|
+
# Ambiguous - multiple matches
|
|
168
|
+
return None
|
|
161
169
|
return None
|
|
162
170
|
|
|
163
171
|
|
|
@@ -175,7 +183,6 @@ def restore_command_state(hash_value):
|
|
|
175
183
|
@click.group(name="workflow")
|
|
176
184
|
def workflow():
|
|
177
185
|
"""Manage workflows - create, edit, import, export workflow commands."""
|
|
178
|
-
pass
|
|
179
186
|
|
|
180
187
|
|
|
181
188
|
# For backward compatibility, keep commands as an alias
|
|
@@ -397,7 +404,7 @@ def search_commands(query: str, daemon_only: bool, as_json: bool, is_global: boo
|
|
|
397
404
|
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
398
405
|
@click.option("--timeout", type=int, help="Execution timeout in seconds")
|
|
399
406
|
def execute_command(command_name: str, args: tuple, as_json: bool, timeout: Optional[int]):
|
|
400
|
-
"""Execute a command by name"""
|
|
407
|
+
"""Execute a command by name."""
|
|
401
408
|
try:
|
|
402
409
|
client = get_daemon_client()
|
|
403
410
|
result = client.execute_command(command_name=command_name, args=list(args), timeout=timeout)
|
|
@@ -425,7 +432,7 @@ def execute_command(command_name: str, args: tuple, as_json: bool, timeout: Opti
|
|
|
425
432
|
@click.argument("command_name")
|
|
426
433
|
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
427
434
|
def command_info(command_name: str, as_json: bool):
|
|
428
|
-
"""Show detailed information about a command"""
|
|
435
|
+
"""Show detailed information about a command."""
|
|
429
436
|
try:
|
|
430
437
|
client = get_daemon_client()
|
|
431
438
|
result = client.list_commands(all=True)
|
|
@@ -466,7 +473,7 @@ def command_info(command_name: str, as_json: bool):
|
|
|
466
473
|
console.print(f"Last Executed: {command['last_executed']}")
|
|
467
474
|
|
|
468
475
|
if command.get("code"):
|
|
469
|
-
console.print(
|
|
476
|
+
console.print("\n[bold]Code:[/bold]")
|
|
470
477
|
console.print(f"```{command['language']}")
|
|
471
478
|
console.print(command["code"])
|
|
472
479
|
console.print("```")
|
|
@@ -703,7 +710,7 @@ logger = get_logger()
|
|
|
703
710
|
return None
|
|
704
711
|
finally:
|
|
705
712
|
# Clean up temporary file
|
|
706
|
-
try:
|
|
713
|
+
try: # noqa: SIM105
|
|
707
714
|
os.unlink(temp_file_path)
|
|
708
715
|
except OSError:
|
|
709
716
|
pass
|
|
@@ -807,7 +814,7 @@ def add_command(command_name, group, description, template, language, shell, is_
|
|
|
807
814
|
language = language.lower()
|
|
808
815
|
|
|
809
816
|
# Determine shell type for shell commands
|
|
810
|
-
if language == "shell":
|
|
817
|
+
if language == "shell": # noqa: SIM102
|
|
811
818
|
if not shell:
|
|
812
819
|
# Default to $SHELL environment variable or bash
|
|
813
820
|
shell_env = os.environ.get("SHELL", "/bin/bash")
|
|
@@ -890,7 +897,7 @@ def add_command(command_name, group, description, template, language, shell, is_
|
|
|
890
897
|
)
|
|
891
898
|
else:
|
|
892
899
|
console.print(
|
|
893
|
-
|
|
900
|
+
"[dim]This command is local to this git repository. Use --global/-g to create global commands.[/dim]"
|
|
894
901
|
)
|
|
895
902
|
|
|
896
903
|
return 0
|
|
@@ -988,7 +995,7 @@ def export_commands(target, script, standalone, output, is_global):
|
|
|
988
995
|
return 1
|
|
989
996
|
|
|
990
997
|
# Add standalone wrapper if requested
|
|
991
|
-
if standalone:
|
|
998
|
+
if standalone: # noqa: SIM102
|
|
992
999
|
# Check if already has if __name__ == '__main__'
|
|
993
1000
|
if "if __name__" not in code:
|
|
994
1001
|
code += "\n\nif __name__ == '__main__':\n app()\n"
|
|
@@ -1153,7 +1160,7 @@ def import_commands(source, script, overwrite, name, group, description, interac
|
|
|
1153
1160
|
try:
|
|
1154
1161
|
tree = ast.parse(code)
|
|
1155
1162
|
description = ast.get_docstring(tree) or f"Imported from {source_path.name}"
|
|
1156
|
-
except:
|
|
1163
|
+
except Exception:
|
|
1157
1164
|
description = f"Imported from {source_path.name}"
|
|
1158
1165
|
|
|
1159
1166
|
# Save as JSON command
|
|
@@ -1199,9 +1206,9 @@ def import_commands(source, script, overwrite, name, group, description, interac
|
|
|
1199
1206
|
f"[yellow]Skipped {failed_count} command(s) (already exist, use --overwrite to replace)[/yellow]"
|
|
1200
1207
|
)
|
|
1201
1208
|
console.print("Skipped commands:")
|
|
1202
|
-
for
|
|
1203
|
-
if not
|
|
1204
|
-
console.print(f" - {
|
|
1209
|
+
for cmd_name, cmd_success in results.items():
|
|
1210
|
+
if not cmd_success:
|
|
1211
|
+
console.print(f" - {cmd_name}")
|
|
1205
1212
|
|
|
1206
1213
|
return 0
|
|
1207
1214
|
|
|
@@ -1357,9 +1364,6 @@ def extract_workflow_commands(output):
|
|
|
1357
1364
|
}
|
|
1358
1365
|
|
|
1359
1366
|
# Create a template based on command type
|
|
1360
|
-
# Replace hyphens with underscores for valid Python function names
|
|
1361
|
-
safe_name = cmd_name.replace("-", "_")
|
|
1362
|
-
|
|
1363
1367
|
if isinstance(cmd_obj, click.Group):
|
|
1364
1368
|
# For groups, create a template
|
|
1365
1369
|
command_info[
|
mcli/app/completion_helpers.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Completion helpers for MCLI that provide tab completion without loading heavy modules
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from typing import
|
|
5
|
+
from typing import List
|
|
6
6
|
|
|
7
7
|
import click
|
|
8
8
|
from click.shell_completion import CompletionItem
|
|
@@ -103,7 +103,7 @@ LAZY_COMMAND_COMPLETIONS = {
|
|
|
103
103
|
|
|
104
104
|
|
|
105
105
|
def get_completion_items(cmd_path: List[str], incomplete: str = "") -> List[CompletionItem]:
|
|
106
|
-
"""Get completion items for a given command path without loading modules"""
|
|
106
|
+
"""Get completion items for a given command path without loading modules."""
|
|
107
107
|
items = []
|
|
108
108
|
|
|
109
109
|
# Navigate to the completion data for this path
|
|
@@ -131,7 +131,7 @@ def get_completion_items(cmd_path: List[str], incomplete: str = "") -> List[Comp
|
|
|
131
131
|
|
|
132
132
|
|
|
133
133
|
class CompletionAwareLazyGroup(click.Group):
|
|
134
|
-
"""A Click group that provides completion without loading modules"""
|
|
134
|
+
"""A Click group that provides completion without loading modules."""
|
|
135
135
|
|
|
136
136
|
def __init__(self, name, import_path, *args, **kwargs):
|
|
137
137
|
self.import_path = import_path
|
|
@@ -147,7 +147,7 @@ class CompletionAwareLazyGroup(click.Group):
|
|
|
147
147
|
module_path, attr_name = self.import_path.rsplit(".", 1)
|
|
148
148
|
module = importlib.import_module(module_path)
|
|
149
149
|
self._loaded_group = getattr(module, attr_name)
|
|
150
|
-
except Exception
|
|
150
|
+
except Exception:
|
|
151
151
|
# Return a dummy group that shows an error
|
|
152
152
|
def error_callback():
|
|
153
153
|
click.echo(f"Error: Command group {self.name} is not available")
|
|
@@ -201,5 +201,5 @@ class CompletionAwareLazyGroup(click.Group):
|
|
|
201
201
|
def create_completion_aware_lazy_group(
|
|
202
202
|
name: str, import_path: str, help_text: str = None
|
|
203
203
|
) -> CompletionAwareLazyGroup:
|
|
204
|
-
"""Create a completion-aware lazy group"""
|
|
204
|
+
"""Create a completion-aware lazy group."""
|
|
205
205
|
return CompletionAwareLazyGroup(name, import_path, help=help_text)
|
mcli/app/init_cmd.py
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Top-level initialization and teardown commands for MCLI.
|
|
3
3
|
"""
|
|
4
|
+
|
|
4
5
|
import json
|
|
5
6
|
import shutil
|
|
6
7
|
import subprocess
|
|
7
8
|
from datetime import datetime
|
|
8
|
-
from pathlib import Path
|
|
9
9
|
|
|
10
10
|
import click
|
|
11
11
|
from rich.prompt import Prompt
|
|
@@ -56,12 +56,12 @@ def init(is_global, git, force):
|
|
|
56
56
|
lockfile_path = workflows_dir / "commands.lock.json"
|
|
57
57
|
|
|
58
58
|
# Check if already initialized
|
|
59
|
-
if workflows_dir.exists() and not force:
|
|
59
|
+
if workflows_dir.exists() and not force: # noqa: SIM102
|
|
60
60
|
if lockfile_path.exists():
|
|
61
61
|
console.print(
|
|
62
62
|
f"[yellow]Workflows directory already initialized at:[/yellow] {workflows_dir}"
|
|
63
63
|
)
|
|
64
|
-
console.print(
|
|
64
|
+
console.print("[dim]Use --force to reinitialize[/dim]")
|
|
65
65
|
|
|
66
66
|
should_continue = Prompt.ask("Continue anyway?", choices=["y", "n"], default="n")
|
|
67
67
|
if should_continue.lower() != "y":
|
|
@@ -74,7 +74,7 @@ def init(is_global, git, force):
|
|
|
74
74
|
# Create README.md
|
|
75
75
|
readme_path = workflows_dir / "README.md"
|
|
76
76
|
if not readme_path.exists() or force:
|
|
77
|
-
|
|
77
|
+
"local" if in_git_repo else "global"
|
|
78
78
|
scope_desc = f"for repository: {git_root.name}" if in_git_repo else "globally"
|
|
79
79
|
|
|
80
80
|
readme_content = f"""# MCLI Custom Workflows
|
|
@@ -203,13 +203,13 @@ Thumbs.db
|
|
|
203
203
|
.idea/
|
|
204
204
|
"""
|
|
205
205
|
gitignore_path.write_text(gitignore_content)
|
|
206
|
-
console.print(
|
|
206
|
+
console.print("[green]✓[/green] Created .gitignore")
|
|
207
207
|
|
|
208
208
|
# Initialize git if requested
|
|
209
209
|
if git and not (workflows_dir / ".git").exists():
|
|
210
210
|
try:
|
|
211
211
|
subprocess.run(["git", "init"], cwd=workflows_dir, check=True, capture_output=True)
|
|
212
|
-
console.print(
|
|
212
|
+
console.print("[green]✓[/green] Initialized git repository in workflows directory")
|
|
213
213
|
|
|
214
214
|
# Create initial commit
|
|
215
215
|
subprocess.run(["git", "add", "."], cwd=workflows_dir, check=True, capture_output=True)
|
|
@@ -219,12 +219,12 @@ Thumbs.db
|
|
|
219
219
|
check=True,
|
|
220
220
|
capture_output=True,
|
|
221
221
|
)
|
|
222
|
-
console.print(
|
|
222
|
+
console.print("[green]✓[/green] Created initial commit")
|
|
223
223
|
|
|
224
224
|
except subprocess.CalledProcessError as e:
|
|
225
225
|
console.print(f"[yellow]⚠[/yellow] Git initialization failed: {e}")
|
|
226
226
|
except FileNotFoundError:
|
|
227
|
-
console.print(
|
|
227
|
+
console.print("[yellow]⚠[/yellow] Git not found. Skipping git initialization.")
|
|
228
228
|
|
|
229
229
|
# Summary
|
|
230
230
|
from rich.table import Table
|
|
@@ -258,11 +258,11 @@ Thumbs.db
|
|
|
258
258
|
|
|
259
259
|
if in_git_repo:
|
|
260
260
|
console.print(
|
|
261
|
-
|
|
261
|
+
"[dim]Tip: Workflows are local to this repository. Use --global for user-wide workflows.[/dim]"
|
|
262
262
|
)
|
|
263
263
|
else:
|
|
264
264
|
console.print(
|
|
265
|
-
|
|
265
|
+
"[dim]Tip: Use workflows in any git repository, or create local ones with 'mcli init' inside repos.[/dim]"
|
|
266
266
|
)
|
|
267
267
|
|
|
268
268
|
return 0
|
mcli/app/lock_cmd.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Top-level lock management commands for MCLI.
|
|
3
3
|
Manages workflow lockfile and verification.
|
|
4
4
|
"""
|
|
5
|
+
|
|
5
6
|
import hashlib
|
|
6
7
|
import json
|
|
7
8
|
from datetime import datetime
|
|
@@ -24,7 +25,11 @@ def load_lockfile():
|
|
|
24
25
|
"""Load the command state lockfile."""
|
|
25
26
|
if LOCKFILE_PATH.exists():
|
|
26
27
|
with open(LOCKFILE_PATH, "r") as f:
|
|
27
|
-
|
|
28
|
+
data = json.load(f)
|
|
29
|
+
# Handle both old format (array) and new format (object with "states" key)
|
|
30
|
+
if isinstance(data, dict) and "states" in data:
|
|
31
|
+
return data["states"]
|
|
32
|
+
return data if isinstance(data, list) else []
|
|
28
33
|
return []
|
|
29
34
|
|
|
30
35
|
|
|
@@ -43,11 +48,19 @@ def append_lockfile(new_state):
|
|
|
43
48
|
|
|
44
49
|
|
|
45
50
|
def find_state_by_hash(hash_value):
|
|
46
|
-
"""Find a state by its hash value."""
|
|
51
|
+
"""Find a state by its hash value (supports partial hash matching)."""
|
|
47
52
|
states = load_lockfile()
|
|
53
|
+
matches = []
|
|
48
54
|
for state in states:
|
|
49
|
-
|
|
50
|
-
|
|
55
|
+
# Support both full hash and partial hash (prefix) matching
|
|
56
|
+
if state["hash"] == hash_value or state["hash"].startswith(hash_value):
|
|
57
|
+
matches.append(state)
|
|
58
|
+
|
|
59
|
+
if len(matches) == 1:
|
|
60
|
+
return matches[0]
|
|
61
|
+
elif len(matches) > 1:
|
|
62
|
+
# Ambiguous - multiple matches
|
|
63
|
+
return None
|
|
51
64
|
return None
|
|
52
65
|
|
|
53
66
|
|
|
@@ -63,7 +76,7 @@ def restore_command_state(hash_value):
|
|
|
63
76
|
|
|
64
77
|
|
|
65
78
|
def get_current_command_state():
|
|
66
|
-
"""Collect all command metadata (names, groups, etc.)"""
|
|
79
|
+
"""Collect all command metadata (names, groups, etc.)."""
|
|
67
80
|
# Import here to avoid circular imports
|
|
68
81
|
import importlib
|
|
69
82
|
import inspect
|
|
@@ -110,7 +123,7 @@ def get_current_command_state():
|
|
|
110
123
|
streamlit_logger.setLevel(original_level)
|
|
111
124
|
|
|
112
125
|
# Extract command and group objects
|
|
113
|
-
for
|
|
126
|
+
for _name, obj in inspect.getmembers(module):
|
|
114
127
|
# Handle Click commands and groups
|
|
115
128
|
if isinstance(obj, click.Command):
|
|
116
129
|
if isinstance(obj, click.Group):
|
|
@@ -160,7 +173,6 @@ def hash_command_state(commands):
|
|
|
160
173
|
@click.group(name="lock")
|
|
161
174
|
def lock():
|
|
162
175
|
"""Manage workflow lockfile and verification."""
|
|
163
|
-
pass
|
|
164
176
|
|
|
165
177
|
|
|
166
178
|
@lock.command("list")
|
|
@@ -229,9 +241,7 @@ def write_state(json_file):
|
|
|
229
241
|
@click.option(
|
|
230
242
|
"--global", "-g", "is_global", is_flag=True, help="Verify global commands instead of local"
|
|
231
243
|
)
|
|
232
|
-
@click.option(
|
|
233
|
-
"--code", "-c", is_flag=True, help="Also validate that workflow code is executable"
|
|
234
|
-
)
|
|
244
|
+
@click.option("--code", "-c", is_flag=True, help="Also validate that workflow code is executable")
|
|
235
245
|
def verify_commands(is_global, code):
|
|
236
246
|
"""
|
|
237
247
|
Verify that custom commands match the lockfile and optionally validate code.
|
|
@@ -291,20 +301,13 @@ def verify_commands(is_global, code):
|
|
|
291
301
|
success = manager.register_command_with_click(cmd_data, temp_group)
|
|
292
302
|
|
|
293
303
|
if not success or not temp_group.commands.get(cmd_name):
|
|
294
|
-
invalid_workflows.append(
|
|
295
|
-
"name": cmd_name,
|
|
296
|
-
|
|
297
|
-
})
|
|
304
|
+
invalid_workflows.append(
|
|
305
|
+
{"name": cmd_name, "reason": "Code does not define a valid Click command"}
|
|
306
|
+
)
|
|
298
307
|
except SyntaxError as e:
|
|
299
|
-
invalid_workflows.append({
|
|
300
|
-
"name": cmd_name,
|
|
301
|
-
"reason": f"Syntax error: {e}"
|
|
302
|
-
})
|
|
308
|
+
invalid_workflows.append({"name": cmd_name, "reason": f"Syntax error: {e}"})
|
|
303
309
|
except Exception as e:
|
|
304
|
-
invalid_workflows.append({
|
|
305
|
-
"name": cmd_name,
|
|
306
|
-
"reason": f"Failed to load: {e}"
|
|
307
|
-
})
|
|
310
|
+
invalid_workflows.append({"name": cmd_name, "reason": f"Failed to load: {e}"})
|
|
308
311
|
|
|
309
312
|
if invalid_workflows:
|
|
310
313
|
has_issues = True
|
|
@@ -314,8 +317,10 @@ def verify_commands(is_global, code):
|
|
|
314
317
|
console.print(f" [red]✗[/red] {item['name']}")
|
|
315
318
|
console.print(f" [dim]{item['reason']}[/dim]")
|
|
316
319
|
|
|
317
|
-
console.print(
|
|
318
|
-
console.print(
|
|
320
|
+
console.print("\n[yellow]Fix with:[/yellow] mcli workflow edit <workflow-name>")
|
|
321
|
+
console.print(
|
|
322
|
+
"[dim]Tip: Workflow code must define a Click command decorated with @click.command()[/dim]\n"
|
|
323
|
+
)
|
|
319
324
|
else:
|
|
320
325
|
console.print("[green]✓ All workflow code is valid[/green]\n")
|
|
321
326
|
|
mcli/app/main.py
CHANGED
|
@@ -1,19 +1,13 @@
|
|
|
1
|
-
import functools
|
|
2
1
|
import importlib
|
|
3
|
-
import inspect
|
|
4
2
|
import os
|
|
5
|
-
import platform
|
|
6
|
-
import sys
|
|
7
|
-
from functools import lru_cache
|
|
8
|
-
from importlib.metadata import metadata, version
|
|
9
3
|
from pathlib import Path
|
|
10
4
|
from typing import List, Optional
|
|
11
5
|
|
|
12
6
|
import click
|
|
13
7
|
import tomli
|
|
14
8
|
|
|
9
|
+
from mcli.lib.api.api import register_command_as_api
|
|
15
10
|
from mcli.lib.logger.logger import disable_runtime_tracing, enable_runtime_tracing, get_logger
|
|
16
|
-
from mcli.lib.ui.styling import info, success
|
|
17
11
|
|
|
18
12
|
# Defer performance optimizations until needed
|
|
19
13
|
_optimization_results = None
|
|
@@ -78,7 +72,7 @@ def discover_modules(base_path: Path, config_path: Optional[Path] = None) -> Lis
|
|
|
78
72
|
logger.warning(f"Error reading config file {config_path}: {e}")
|
|
79
73
|
config = {"paths": {"included_dirs": ["app", "self", "workflow", "public"]}}
|
|
80
74
|
else:
|
|
81
|
-
logger.warning(
|
|
75
|
+
logger.warning("Config file not found, using default configuration")
|
|
82
76
|
config = {"paths": {"included_dirs": ["app", "self", "workflow", "public"]}}
|
|
83
77
|
excluded_files = {"setup.py", "__init__.py"}
|
|
84
78
|
excluded_dirs = {"resources", "models", "scripts", "private", "venv", ".venv", "__pycache__"}
|
mcli/app/model/model.py
CHANGED
|
@@ -2,14 +2,10 @@ import base64
|
|
|
2
2
|
import json
|
|
3
3
|
import os
|
|
4
4
|
import queue
|
|
5
|
-
import shutil
|
|
6
|
-
import sys
|
|
7
|
-
import tempfile
|
|
8
5
|
import threading
|
|
9
6
|
import time
|
|
10
7
|
import uuid
|
|
11
|
-
from
|
|
12
|
-
from typing import Any, Dict, List, Optional, Tuple
|
|
8
|
+
from typing import Any, Dict, List, Tuple
|
|
13
9
|
|
|
14
10
|
import click
|
|
15
11
|
import cv2
|
|
@@ -323,7 +319,7 @@ class ComfyUIClient:
|
|
|
323
319
|
"subfolder": "v2v_input", # Custom subfolder for our workflow
|
|
324
320
|
"type": "input",
|
|
325
321
|
}
|
|
326
|
-
|
|
322
|
+
self.session.post(f"{self.api_url}/upload/image", json=data)
|
|
327
323
|
return filename, "v2v_input"
|
|
328
324
|
|
|
329
325
|
def wait_for_prompt(self, prompt_id: str) -> Dict[str, Any]:
|
|
@@ -418,7 +414,7 @@ class ComfyUIClient:
|
|
|
418
414
|
|
|
419
415
|
# Get the output image
|
|
420
416
|
output_node = None
|
|
421
|
-
for
|
|
417
|
+
for _node_id, node_output in result["outputs"].items():
|
|
422
418
|
if "images" in node_output:
|
|
423
419
|
output_node = node_output
|
|
424
420
|
break
|
|
@@ -678,7 +674,7 @@ class VideoToVideoGenerator:
|
|
|
678
674
|
with click.progressbar(
|
|
679
675
|
enumerate(frame_paths), length=len(frame_paths), label="Processing frames"
|
|
680
676
|
) as bar:
|
|
681
|
-
for
|
|
677
|
+
for _i, frame_path in bar:
|
|
682
678
|
processed_frame_path = self.comfyui_client.process_frame(
|
|
683
679
|
frame_path=frame_path,
|
|
684
680
|
prompt=prompt,
|
|
@@ -795,7 +791,6 @@ class VideoToVideoGenerator:
|
|
|
795
791
|
@click.group(name="model")
|
|
796
792
|
def model():
|
|
797
793
|
"""Video-to-video generation workflow using ComfyUI and Hunyuan video models."""
|
|
798
|
-
pass
|
|
799
794
|
|
|
800
795
|
|
|
801
796
|
@model.command()
|
|
@@ -1012,7 +1007,7 @@ def check_comfyui():
|
|
|
1012
1007
|
|
|
1013
1008
|
# Check for hunyuan models
|
|
1014
1009
|
hunyuan_found = False
|
|
1015
|
-
for
|
|
1010
|
+
for _model_type, models in models_info.items():
|
|
1016
1011
|
for model in models:
|
|
1017
1012
|
if "hunyuan" in model.lower():
|
|
1018
1013
|
hunyuan_found = True
|
mcli/app/store_cmd.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Top-level store management commands for MCLI.
|
|
3
3
|
Manages command store - sync ~/.mcli/commands/ to git.
|
|
4
4
|
"""
|
|
5
|
+
|
|
5
6
|
import shutil
|
|
6
7
|
import subprocess
|
|
7
8
|
from datetime import datetime
|
|
@@ -20,7 +21,7 @@ COMMANDS_PATH = Path.home() / ".mcli" / "commands"
|
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
def _get_store_path() -> Path:
|
|
23
|
-
"""Get store path from config or default"""
|
|
24
|
+
"""Get store path from config or default."""
|
|
24
25
|
config_file = Path.home() / ".mcli" / "store.conf"
|
|
25
26
|
|
|
26
27
|
if config_file.exists():
|
|
@@ -34,15 +35,14 @@ def _get_store_path() -> Path:
|
|
|
34
35
|
|
|
35
36
|
@click.group(name="store")
|
|
36
37
|
def store():
|
|
37
|
-
"""Manage command store - sync ~/.mcli/commands/ to git"""
|
|
38
|
-
pass
|
|
38
|
+
"""Manage command store - sync ~/.mcli/commands/ to git."""
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
@store.command(name="init")
|
|
42
42
|
@click.option("--path", "-p", type=click.Path(), help=f"Store path (default: {DEFAULT_STORE_PATH})")
|
|
43
43
|
@click.option("--remote", "-r", help="Git remote URL (optional)")
|
|
44
44
|
def init_store(path, remote):
|
|
45
|
-
"""Initialize command store with git"""
|
|
45
|
+
"""Initialize command store with git."""
|
|
46
46
|
store_path = Path(path) if path else DEFAULT_STORE_PATH
|
|
47
47
|
|
|
48
48
|
try:
|
|
@@ -305,7 +305,7 @@ def sync_commands(message, is_global):
|
|
|
305
305
|
|
|
306
306
|
@store.command(name="status")
|
|
307
307
|
def store_status():
|
|
308
|
-
"""Show git status of command store"""
|
|
308
|
+
"""Show git status of command store."""
|
|
309
309
|
try:
|
|
310
310
|
store_path = _get_store_path()
|
|
311
311
|
|
|
@@ -341,7 +341,7 @@ def store_status():
|
|
|
341
341
|
@click.option("--remote", "-r", help="Set git remote URL")
|
|
342
342
|
@click.option("--path", "-p", type=click.Path(), help="Change store path")
|
|
343
343
|
def configure_store(remote, path):
|
|
344
|
-
"""Configure store settings"""
|
|
344
|
+
"""Configure store settings."""
|
|
345
345
|
try:
|
|
346
346
|
store_path = _get_store_path()
|
|
347
347
|
|
|
@@ -377,7 +377,7 @@ def configure_store(remote, path):
|
|
|
377
377
|
@store.command(name="list")
|
|
378
378
|
@click.option("--store-dir", "-s", is_flag=True, help="List store instead of local")
|
|
379
379
|
def list_commands(store_dir):
|
|
380
|
-
"""List all commands"""
|
|
380
|
+
"""List all commands."""
|
|
381
381
|
try:
|
|
382
382
|
if store_dir:
|
|
383
383
|
store_path = _get_store_path()
|
|
@@ -421,7 +421,7 @@ def list_commands(store_dir):
|
|
|
421
421
|
@click.argument("command_name")
|
|
422
422
|
@click.option("--store-dir", "-s", is_flag=True, help="Show from store instead of local")
|
|
423
423
|
def show_command(command_name, store_dir):
|
|
424
|
-
"""Show command file contents"""
|
|
424
|
+
"""Show command file contents."""
|
|
425
425
|
try:
|
|
426
426
|
if store_dir:
|
|
427
427
|
store_path = _get_store_path()
|
mcli/app/video/__init__.py
CHANGED
mcli/app/video/video.py
CHANGED
|
@@ -1,23 +1,11 @@
|
|
|
1
|
-
import base64
|
|
2
|
-
import json
|
|
3
1
|
import os
|
|
4
|
-
import queue
|
|
5
|
-
import shutil
|
|
6
|
-
import sys
|
|
7
|
-
import tempfile
|
|
8
|
-
import threading
|
|
9
|
-
import time
|
|
10
|
-
import uuid
|
|
11
|
-
from pathlib import Path
|
|
12
2
|
from typing import Any, Dict, List, Optional, Tuple
|
|
13
3
|
|
|
14
4
|
import click
|
|
15
5
|
import cv2
|
|
16
6
|
import numpy as np
|
|
17
|
-
import requests
|
|
18
7
|
from PIL import Image
|
|
19
|
-
from
|
|
20
|
-
from skimage import feature, morphology, restoration
|
|
8
|
+
from skimage import morphology
|
|
21
9
|
|
|
22
10
|
# Add this to your existing CONFIG
|
|
23
11
|
CONFIG = {"temp_dir": "./temp", "output_dir": "./output"}
|
|
@@ -1105,7 +1093,6 @@ def remove_overlay(input_video, output, fps, output_fps, context, method):
|
|
|
1105
1093
|
@click.group()
|
|
1106
1094
|
def main():
|
|
1107
1095
|
"""Advanced video overlay removal tool with intelligent content reconstruction."""
|
|
1108
|
-
pass
|
|
1109
1096
|
|
|
1110
1097
|
|
|
1111
1098
|
main.add_command(remove_overlay)
|