mcli-framework 7.2.0__py3-none-any.whl → 7.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of mcli-framework might be problematic. Click here for more details.
- mcli/__init__.py +160 -0
- mcli/__main__.py +14 -0
- mcli/app/__init__.py +23 -0
- mcli/app/commands_cmd.py +741 -0
- mcli/app/model/__init__.py +0 -0
- 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/api/schemas.py +2 -2
- mcli/ml/auth/__init__.py +45 -0
- mcli/ml/auth/models.py +2 -2
- mcli/ml/backtesting/__init__.py +39 -0
- mcli/ml/cli/__init__.py +5 -0
- mcli/ml/cli/main.py +1 -1
- 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_integrated.py +296 -30
- mcli/ml/dashboard/app_training.py +1 -1
- mcli/ml/dashboard/components/__init__.py +7 -0
- mcli/ml/dashboard/pages/__init__.py +6 -0
- mcli/ml/dashboard/pages/cicd.py +1 -1
- mcli/ml/dashboard/pages/debug_dependencies.py +364 -0
- mcli/ml/dashboard/pages/gravity_viz.py +565 -0
- mcli/ml/dashboard/pages/monte_carlo_predictions.py +555 -0
- mcli/ml/dashboard/pages/overview.py +378 -0
- mcli/ml/dashboard/pages/predictions_enhanced.py +20 -6
- mcli/ml/dashboard/pages/scrapers_and_logs.py +22 -6
- mcli/ml/dashboard/pages/test_portfolio.py +423 -0
- mcli/ml/dashboard/pages/trading.py +768 -0
- mcli/ml/dashboard/streamlit_extras_utils.py +297 -0
- mcli/ml/dashboard/utils.py +161 -0
- mcli/ml/dashboard/warning_suppression.py +34 -0
- mcli/ml/data_ingestion/__init__.py +39 -0
- mcli/ml/database/__init__.py +47 -0
- mcli/ml/database/session.py +169 -16
- mcli/ml/experimentation/__init__.py +29 -0
- mcli/ml/features/__init__.py +39 -0
- mcli/ml/mlops/__init__.py +33 -0
- mcli/ml/models/__init__.py +94 -0
- mcli/ml/monitoring/__init__.py +25 -0
- mcli/ml/optimization/__init__.py +27 -0
- mcli/ml/predictions/__init__.py +5 -0
- mcli/ml/predictions/monte_carlo.py +428 -0
- mcli/ml/preprocessing/__init__.py +28 -0
- mcli/ml/scripts/__init__.py +1 -0
- mcli/ml/trading/__init__.py +66 -0
- mcli/ml/trading/alpaca_client.py +417 -0
- mcli/ml/trading/migrations.py +164 -0
- mcli/ml/trading/models.py +418 -0
- mcli/ml/trading/paper_trading.py +326 -0
- mcli/ml/trading/risk_management.py +370 -0
- mcli/ml/trading/trading_service.py +480 -0
- mcli/ml/training/__init__.py +10 -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 +514 -15
- mcli/workflow/__init__.py +0 -0
- mcli/workflow/daemon/__init__.py +15 -0
- mcli/workflow/daemon/daemon.py +21 -3
- mcli/workflow/dashboard/__init__.py +5 -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.2.0.dist-info → mcli_framework-7.4.0.dist-info}/METADATA +4 -1
- {mcli_framework-7.2.0.dist-info → mcli_framework-7.4.0.dist-info}/RECORD +97 -18
- {mcli_framework-7.2.0.dist-info → mcli_framework-7.4.0.dist-info}/WHEEL +0 -0
- {mcli_framework-7.2.0.dist-info → mcli_framework-7.4.0.dist-info}/entry_points.txt +0 -0
- {mcli_framework-7.2.0.dist-info → mcli_framework-7.4.0.dist-info}/licenses/LICENSE +0 -0
- {mcli_framework-7.2.0.dist-info → mcli_framework-7.4.0.dist-info}/top_level.txt +0 -0
mcli/self/self_cmd.py
CHANGED
|
@@ -344,8 +344,38 @@ def collect_commands() -> List[Dict[str, Any]]:
|
|
|
344
344
|
module_name = ".".join(relative_path.with_suffix("").parts)
|
|
345
345
|
|
|
346
346
|
try:
|
|
347
|
-
#
|
|
348
|
-
|
|
347
|
+
# Suppress Streamlit warnings and logging during module import
|
|
348
|
+
import warnings
|
|
349
|
+
import logging
|
|
350
|
+
import sys
|
|
351
|
+
import os
|
|
352
|
+
from contextlib import redirect_stderr
|
|
353
|
+
from io import StringIO
|
|
354
|
+
|
|
355
|
+
# Suppress Python warnings
|
|
356
|
+
with warnings.catch_warnings():
|
|
357
|
+
warnings.filterwarnings("ignore", message=".*missing ScriptRunContext.*")
|
|
358
|
+
warnings.filterwarnings("ignore", message=".*No runtime found.*")
|
|
359
|
+
warnings.filterwarnings("ignore", message=".*Session state does not function.*")
|
|
360
|
+
warnings.filterwarnings("ignore", message=".*to view this Streamlit app.*")
|
|
361
|
+
|
|
362
|
+
# Suppress Streamlit logger warnings
|
|
363
|
+
streamlit_logger = logging.getLogger("streamlit")
|
|
364
|
+
original_level = streamlit_logger.level
|
|
365
|
+
streamlit_logger.setLevel(logging.CRITICAL)
|
|
366
|
+
|
|
367
|
+
# Also suppress specific Streamlit sub-loggers
|
|
368
|
+
logging.getLogger("streamlit.runtime.scriptrunner_utils.script_run_context").setLevel(logging.CRITICAL)
|
|
369
|
+
logging.getLogger("streamlit.runtime.caching.cache_data_api").setLevel(logging.CRITICAL)
|
|
370
|
+
|
|
371
|
+
# Redirect stderr to suppress Streamlit warnings
|
|
372
|
+
with redirect_stderr(StringIO()):
|
|
373
|
+
try:
|
|
374
|
+
# Try to import the module
|
|
375
|
+
module = importlib.import_module(module_name)
|
|
376
|
+
finally:
|
|
377
|
+
# Restore original logging level
|
|
378
|
+
streamlit_logger.setLevel(original_level)
|
|
349
379
|
|
|
350
380
|
# Extract command and group objects
|
|
351
381
|
for name, obj in inspect.getmembers(module):
|
|
@@ -387,23 +417,177 @@ def collect_commands() -> List[Dict[str, Any]]:
|
|
|
387
417
|
return commands
|
|
388
418
|
|
|
389
419
|
|
|
390
|
-
|
|
420
|
+
def open_editor_for_command(command_name: str, command_group: str, description: str) -> Optional[str]:
|
|
421
|
+
"""
|
|
422
|
+
Open the user's default editor to allow them to write command logic.
|
|
423
|
+
|
|
424
|
+
Args:
|
|
425
|
+
command_name: Name of the command
|
|
426
|
+
command_group: Group for the command
|
|
427
|
+
description: Description of the command
|
|
428
|
+
|
|
429
|
+
Returns:
|
|
430
|
+
The Python code written by the user, or None if cancelled
|
|
431
|
+
"""
|
|
432
|
+
import tempfile
|
|
433
|
+
import subprocess
|
|
434
|
+
import os
|
|
435
|
+
import sys
|
|
436
|
+
from pathlib import Path
|
|
437
|
+
|
|
438
|
+
# Get the user's default editor
|
|
439
|
+
editor = os.environ.get('EDITOR')
|
|
440
|
+
if not editor:
|
|
441
|
+
# Try common editors in order of preference
|
|
442
|
+
for common_editor in ['vim', 'nano', 'code', 'subl', 'atom', 'emacs']:
|
|
443
|
+
if subprocess.run(['which', common_editor], capture_output=True).returncode == 0:
|
|
444
|
+
editor = common_editor
|
|
445
|
+
break
|
|
446
|
+
|
|
447
|
+
if not editor:
|
|
448
|
+
click.echo("❌ No editor found. Please set the EDITOR environment variable or install vim/nano.")
|
|
449
|
+
return None
|
|
450
|
+
|
|
451
|
+
# Create a temporary file with the template
|
|
452
|
+
template = get_command_template(command_name, command_group)
|
|
453
|
+
|
|
454
|
+
# Add helpful comments to the template
|
|
455
|
+
enhanced_template = f'''"""
|
|
456
|
+
{command_name} command for mcli.{command_group}.
|
|
457
|
+
|
|
458
|
+
Description: {description}
|
|
459
|
+
|
|
460
|
+
Instructions:
|
|
461
|
+
1. Write your Python command logic below
|
|
462
|
+
2. Use Click decorators for command definition
|
|
463
|
+
3. Save and close the editor to create the command
|
|
464
|
+
4. The command will be automatically converted to JSON format
|
|
465
|
+
|
|
466
|
+
Example Click command structure:
|
|
467
|
+
@click.command()
|
|
468
|
+
@click.argument('name', default='World')
|
|
469
|
+
def my_command(name):
|
|
470
|
+
\"\"\"My custom command.\"\"\"
|
|
471
|
+
click.echo(f"Hello, {{name}}!")
|
|
472
|
+
"""
|
|
473
|
+
import click
|
|
474
|
+
from typing import Optional, List
|
|
475
|
+
from pathlib import Path
|
|
476
|
+
from mcli.lib.logger.logger import get_logger
|
|
477
|
+
|
|
478
|
+
logger = get_logger()
|
|
479
|
+
|
|
480
|
+
# Write your command logic here:
|
|
481
|
+
# Replace this template with your actual command implementation
|
|
482
|
+
|
|
483
|
+
{template.split('"""')[2].split('"""')[0] if '"""' in template else ''}
|
|
484
|
+
|
|
485
|
+
# Your command implementation goes here:
|
|
486
|
+
# Example:
|
|
487
|
+
# @click.command()
|
|
488
|
+
# @click.argument('name', default='World')
|
|
489
|
+
# def {command_name}_command(name):
|
|
490
|
+
# \"\"\"{description}\"\"\"
|
|
491
|
+
# logger.info(f"Executing {command_name} command with name: {{name}}")
|
|
492
|
+
# click.echo(f"Hello, {{name}}! This is the {command_name} command.")
|
|
493
|
+
'''
|
|
494
|
+
|
|
495
|
+
# Create temporary file
|
|
496
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as temp_file:
|
|
497
|
+
temp_file.write(enhanced_template)
|
|
498
|
+
temp_file_path = temp_file.name
|
|
499
|
+
|
|
500
|
+
try:
|
|
501
|
+
# Check if we're in an interactive environment
|
|
502
|
+
if not sys.stdin.isatty() or not sys.stdout.isatty():
|
|
503
|
+
click.echo("❌ Editor requires an interactive terminal. Use --template flag for non-interactive mode.")
|
|
504
|
+
return None
|
|
505
|
+
|
|
506
|
+
# Open editor
|
|
507
|
+
click.echo(f"📝 Opening {editor} to edit command logic...")
|
|
508
|
+
click.echo("💡 Write your Python command logic and save the file to continue.")
|
|
509
|
+
click.echo("💡 Press Ctrl+C to cancel command creation.")
|
|
510
|
+
|
|
511
|
+
# Run the editor
|
|
512
|
+
result = subprocess.run([editor, temp_file_path], check=False)
|
|
513
|
+
|
|
514
|
+
if result.returncode != 0:
|
|
515
|
+
click.echo("❌ Editor exited with error. Command creation cancelled.")
|
|
516
|
+
return None
|
|
517
|
+
|
|
518
|
+
# Read the edited content
|
|
519
|
+
with open(temp_file_path, 'r') as f:
|
|
520
|
+
edited_code = f.read()
|
|
521
|
+
|
|
522
|
+
# Check if the file was actually edited (not just the template)
|
|
523
|
+
if edited_code.strip() == enhanced_template.strip():
|
|
524
|
+
click.echo("⚠️ No changes detected. Command creation cancelled.")
|
|
525
|
+
return None
|
|
526
|
+
|
|
527
|
+
# Extract the actual command code (remove the instructions)
|
|
528
|
+
lines = edited_code.split('\n')
|
|
529
|
+
code_lines = []
|
|
530
|
+
in_code_section = False
|
|
531
|
+
|
|
532
|
+
for line in lines:
|
|
533
|
+
if line.strip().startswith('# Your command implementation goes here:'):
|
|
534
|
+
in_code_section = True
|
|
535
|
+
continue
|
|
536
|
+
if in_code_section:
|
|
537
|
+
code_lines.append(line)
|
|
538
|
+
|
|
539
|
+
if not code_lines or not any(line.strip() for line in code_lines):
|
|
540
|
+
# Fallback: use the entire file content
|
|
541
|
+
code_lines = lines
|
|
542
|
+
|
|
543
|
+
final_code = '\n'.join(code_lines).strip()
|
|
544
|
+
|
|
545
|
+
if not final_code:
|
|
546
|
+
click.echo("❌ No command code found. Command creation cancelled.")
|
|
547
|
+
return None
|
|
548
|
+
|
|
549
|
+
click.echo("✅ Command code captured successfully!")
|
|
550
|
+
return final_code
|
|
551
|
+
|
|
552
|
+
except KeyboardInterrupt:
|
|
553
|
+
click.echo("\n❌ Command creation cancelled by user.")
|
|
554
|
+
return None
|
|
555
|
+
except Exception as e:
|
|
556
|
+
click.echo(f"❌ Error opening editor: {e}")
|
|
557
|
+
return None
|
|
558
|
+
finally:
|
|
559
|
+
# Clean up temporary file
|
|
560
|
+
try:
|
|
561
|
+
os.unlink(temp_file_path)
|
|
562
|
+
except OSError:
|
|
563
|
+
pass
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
@self_app.command("add-command", deprecated=True)
|
|
391
567
|
@click.argument("command_name", required=True)
|
|
392
568
|
@click.option("--group", "-g", help="Command group (defaults to 'workflow')", default="workflow")
|
|
393
569
|
@click.option(
|
|
394
570
|
"--description", "-d", help="Description for the command", default="Custom command"
|
|
395
571
|
)
|
|
396
|
-
|
|
572
|
+
@click.option(
|
|
573
|
+
"--template", "-t", is_flag=True, help="Use template mode (skip editor and use predefined template)"
|
|
574
|
+
)
|
|
575
|
+
def add_command(command_name, group, description, template):
|
|
397
576
|
"""
|
|
577
|
+
[DEPRECATED] Use 'mcli commands add' instead.
|
|
578
|
+
|
|
398
579
|
Generate a new portable custom command saved to ~/.mcli/commands/.
|
|
399
580
|
|
|
400
|
-
|
|
401
|
-
|
|
581
|
+
This command has been moved to 'mcli commands add' for better organization.
|
|
582
|
+
Please use the new command going forward.
|
|
402
583
|
|
|
403
584
|
Example:
|
|
404
|
-
mcli
|
|
405
|
-
mcli
|
|
585
|
+
mcli commands add my_command
|
|
586
|
+
mcli commands add analytics --group data
|
|
587
|
+
mcli commands add quick_cmd --template # Use template without editor
|
|
406
588
|
"""
|
|
589
|
+
click.echo("[yellow]⚠️ DEPRECATED: This command has been moved to 'mcli commands add'[/yellow]")
|
|
590
|
+
click.echo("[yellow] Please use: mcli commands add {} [options][/yellow]\n".format(command_name))
|
|
407
591
|
command_name = command_name.lower().replace("-", "_")
|
|
408
592
|
|
|
409
593
|
# Validate command name
|
|
@@ -448,7 +632,17 @@ def add_command(command_name, group, description):
|
|
|
448
632
|
return 1
|
|
449
633
|
|
|
450
634
|
# Generate command code
|
|
451
|
-
|
|
635
|
+
if template:
|
|
636
|
+
# Use template mode - generate and save directly
|
|
637
|
+
code = get_command_template(command_name, command_group)
|
|
638
|
+
click.echo(f"📝 Using template for command: {command_name}")
|
|
639
|
+
else:
|
|
640
|
+
# Editor mode - open editor for user to write code
|
|
641
|
+
click.echo(f"🔧 Opening editor for command: {command_name}")
|
|
642
|
+
code = open_editor_for_command(command_name, command_group, description)
|
|
643
|
+
if code is None:
|
|
644
|
+
click.echo("❌ Command creation cancelled.")
|
|
645
|
+
return 1
|
|
452
646
|
|
|
453
647
|
# Save the command
|
|
454
648
|
saved_path = manager.save_command(
|
|
@@ -469,11 +663,16 @@ def add_command(command_name, group, description):
|
|
|
469
663
|
return 0
|
|
470
664
|
|
|
471
665
|
|
|
472
|
-
@self_app.command("list-commands")
|
|
666
|
+
@self_app.command("list-commands", deprecated=True)
|
|
473
667
|
def list_commands():
|
|
474
668
|
"""
|
|
669
|
+
[DEPRECATED] Use 'mcli commands list-custom' instead.
|
|
670
|
+
|
|
475
671
|
List all custom commands stored in ~/.mcli/commands/.
|
|
476
672
|
"""
|
|
673
|
+
click.echo("[yellow]⚠️ DEPRECATED: This command has been moved to 'mcli commands list-custom'[/yellow]")
|
|
674
|
+
click.echo("[yellow] Please use: mcli commands list-custom[/yellow]\n")
|
|
675
|
+
|
|
477
676
|
manager = get_command_manager()
|
|
478
677
|
commands = manager.load_all_commands()
|
|
479
678
|
|
|
@@ -507,13 +706,18 @@ def list_commands():
|
|
|
507
706
|
return 0
|
|
508
707
|
|
|
509
708
|
|
|
510
|
-
@self_app.command("remove-command")
|
|
709
|
+
@self_app.command("remove-command", deprecated=True)
|
|
511
710
|
@click.argument("command_name", required=True)
|
|
512
711
|
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompt")
|
|
513
712
|
def remove_command(command_name, yes):
|
|
514
713
|
"""
|
|
714
|
+
[DEPRECATED] Use 'mcli commands remove' instead.
|
|
715
|
+
|
|
515
716
|
Remove a custom command from ~/.mcli/commands/.
|
|
516
717
|
"""
|
|
718
|
+
click.echo("[yellow]⚠️ DEPRECATED: This command has been moved to 'mcli commands remove'[/yellow]")
|
|
719
|
+
click.echo(f"[yellow] Please use: mcli commands remove {command_name}[/yellow]\n")
|
|
720
|
+
|
|
517
721
|
manager = get_command_manager()
|
|
518
722
|
command_file = manager.commands_dir / f"{command_name}.json"
|
|
519
723
|
|
|
@@ -537,14 +741,19 @@ def remove_command(command_name, yes):
|
|
|
537
741
|
return 1
|
|
538
742
|
|
|
539
743
|
|
|
540
|
-
@self_app.command("export-commands")
|
|
744
|
+
@self_app.command("export-commands", deprecated=True)
|
|
541
745
|
@click.argument("export_file", type=click.Path(), required=False)
|
|
542
746
|
def export_commands(export_file):
|
|
543
747
|
"""
|
|
748
|
+
[DEPRECATED] Use 'mcli commands export' instead.
|
|
749
|
+
|
|
544
750
|
Export all custom commands to a JSON file.
|
|
545
751
|
|
|
546
752
|
If no file is specified, exports to commands-export.json in current directory.
|
|
547
753
|
"""
|
|
754
|
+
click.echo("[yellow]⚠️ DEPRECATED: This command has been moved to 'mcli commands export'[/yellow]")
|
|
755
|
+
click.echo(f"[yellow] Please use: mcli commands export {export_file or '<file>'}[/yellow]\n")
|
|
756
|
+
|
|
548
757
|
manager = get_command_manager()
|
|
549
758
|
|
|
550
759
|
if not export_file:
|
|
@@ -563,13 +772,18 @@ def export_commands(export_file):
|
|
|
563
772
|
return 1
|
|
564
773
|
|
|
565
774
|
|
|
566
|
-
@self_app.command("import-commands")
|
|
775
|
+
@self_app.command("import-commands", deprecated=True)
|
|
567
776
|
@click.argument("import_file", type=click.Path(exists=True), required=True)
|
|
568
777
|
@click.option("--overwrite", is_flag=True, help="Overwrite existing commands")
|
|
569
778
|
def import_commands(import_file, overwrite):
|
|
570
779
|
"""
|
|
780
|
+
[DEPRECATED] Use 'mcli commands import' instead.
|
|
781
|
+
|
|
571
782
|
Import custom commands from a JSON file.
|
|
572
783
|
"""
|
|
784
|
+
click.echo("[yellow]⚠️ DEPRECATED: This command has been moved to 'mcli commands import'[/yellow]")
|
|
785
|
+
click.echo(f"[yellow] Please use: mcli commands import {import_file}[/yellow]\n")
|
|
786
|
+
|
|
573
787
|
manager = get_command_manager()
|
|
574
788
|
import_path = Path(import_file)
|
|
575
789
|
|
|
@@ -593,11 +807,16 @@ def import_commands(import_file, overwrite):
|
|
|
593
807
|
return 0
|
|
594
808
|
|
|
595
809
|
|
|
596
|
-
@self_app.command("verify-commands")
|
|
810
|
+
@self_app.command("verify-commands", deprecated=True)
|
|
597
811
|
def verify_commands():
|
|
598
812
|
"""
|
|
813
|
+
[DEPRECATED] Use 'mcli commands verify' instead.
|
|
814
|
+
|
|
599
815
|
Verify that custom commands match the lockfile.
|
|
600
816
|
"""
|
|
817
|
+
click.echo("[yellow]⚠️ DEPRECATED: This command has been moved to 'mcli commands verify'[/yellow]")
|
|
818
|
+
click.echo("[yellow] Please use: mcli commands verify[/yellow]\n")
|
|
819
|
+
|
|
601
820
|
manager = get_command_manager()
|
|
602
821
|
|
|
603
822
|
# First, ensure lockfile is up to date
|
|
@@ -631,11 +850,16 @@ def verify_commands():
|
|
|
631
850
|
return 1
|
|
632
851
|
|
|
633
852
|
|
|
634
|
-
@self_app.command("update-lockfile")
|
|
853
|
+
@self_app.command("update-lockfile", deprecated=True)
|
|
635
854
|
def update_lockfile():
|
|
636
855
|
"""
|
|
856
|
+
[DEPRECATED] Use 'mcli commands update-lockfile' instead.
|
|
857
|
+
|
|
637
858
|
Update the commands lockfile with current state.
|
|
638
859
|
"""
|
|
860
|
+
click.echo("[yellow]⚠️ DEPRECATED: This command has been moved to 'mcli commands update-lockfile'[/yellow]")
|
|
861
|
+
click.echo("[yellow] Please use: mcli commands update-lockfile[/yellow]\n")
|
|
862
|
+
|
|
639
863
|
manager = get_command_manager()
|
|
640
864
|
|
|
641
865
|
if manager.update_lockfile():
|
|
@@ -1557,6 +1781,281 @@ def update(check: bool, pre: bool, yes: bool, skip_ci_check: bool):
|
|
|
1557
1781
|
console.print(f"[dim]{traceback.format_exc()}[/dim]")
|
|
1558
1782
|
|
|
1559
1783
|
|
|
1784
|
+
@self_app.command("import-script", deprecated=True)
|
|
1785
|
+
@click.argument("script_path", type=click.Path(exists=True))
|
|
1786
|
+
@click.option("--name", "-n", help="Command name (defaults to script filename)")
|
|
1787
|
+
@click.option("--group", "-g", default="workflow", help="Command group")
|
|
1788
|
+
@click.option("--description", "-d", help="Command description")
|
|
1789
|
+
@click.option("--interactive", "-i", is_flag=True, help="Open in $EDITOR for review/editing")
|
|
1790
|
+
def import_script(script_path, name, group, description, interactive):
|
|
1791
|
+
"""
|
|
1792
|
+
[DEPRECATED] Use 'mcli commands import-script' instead.
|
|
1793
|
+
|
|
1794
|
+
Import a Python script as a portable JSON command.
|
|
1795
|
+
|
|
1796
|
+
Converts a Python script into a JSON command that can be loaded
|
|
1797
|
+
by mcli. The script should define Click commands.
|
|
1798
|
+
|
|
1799
|
+
Examples:
|
|
1800
|
+
mcli commands import-script my_script.py
|
|
1801
|
+
mcli commands import-script my_script.py --name custom-cmd --interactive
|
|
1802
|
+
"""
|
|
1803
|
+
click.echo("[yellow]⚠️ DEPRECATED: This command has been moved to 'mcli commands import-script'[/yellow]")
|
|
1804
|
+
click.echo(f"[yellow] Please use: mcli commands import-script {script_path}[/yellow]\n")
|
|
1805
|
+
|
|
1806
|
+
import subprocess
|
|
1807
|
+
import tempfile
|
|
1808
|
+
|
|
1809
|
+
script_file = Path(script_path).resolve()
|
|
1810
|
+
|
|
1811
|
+
if not script_file.exists():
|
|
1812
|
+
click.echo(f"❌ Script not found: {script_file}", err=True)
|
|
1813
|
+
return 1
|
|
1814
|
+
|
|
1815
|
+
# Read the script content
|
|
1816
|
+
try:
|
|
1817
|
+
with open(script_file, 'r') as f:
|
|
1818
|
+
code = f.read()
|
|
1819
|
+
except Exception as e:
|
|
1820
|
+
click.echo(f"❌ Failed to read script: {e}", err=True)
|
|
1821
|
+
return 1
|
|
1822
|
+
|
|
1823
|
+
# Determine command name
|
|
1824
|
+
if not name:
|
|
1825
|
+
name = script_file.stem.lower().replace("-", "_")
|
|
1826
|
+
|
|
1827
|
+
# Validate command name
|
|
1828
|
+
if not re.match(r"^[a-z][a-z0-9_]*$", name):
|
|
1829
|
+
click.echo(f"❌ Invalid command name: {name}", err=True)
|
|
1830
|
+
return 1
|
|
1831
|
+
|
|
1832
|
+
# Interactive editing
|
|
1833
|
+
if interactive:
|
|
1834
|
+
editor = os.environ.get('EDITOR', 'vim')
|
|
1835
|
+
click.echo(f"📝 Opening in {editor} for review...")
|
|
1836
|
+
|
|
1837
|
+
# Create temp file with the code
|
|
1838
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as tmp:
|
|
1839
|
+
tmp.write(code)
|
|
1840
|
+
tmp_path = tmp.name
|
|
1841
|
+
|
|
1842
|
+
try:
|
|
1843
|
+
subprocess.run([editor, tmp_path], check=True)
|
|
1844
|
+
|
|
1845
|
+
# Read edited content
|
|
1846
|
+
with open(tmp_path, 'r') as f:
|
|
1847
|
+
code = f.read()
|
|
1848
|
+
finally:
|
|
1849
|
+
Path(tmp_path).unlink(missing_ok=True)
|
|
1850
|
+
|
|
1851
|
+
# Get description
|
|
1852
|
+
if not description:
|
|
1853
|
+
# Try to extract from docstring
|
|
1854
|
+
import ast
|
|
1855
|
+
try:
|
|
1856
|
+
tree = ast.parse(code)
|
|
1857
|
+
description = ast.get_docstring(tree) or f"Imported from {script_file.name}"
|
|
1858
|
+
except:
|
|
1859
|
+
description = f"Imported from {script_file.name}"
|
|
1860
|
+
|
|
1861
|
+
# Save as JSON command
|
|
1862
|
+
manager = get_command_manager()
|
|
1863
|
+
|
|
1864
|
+
saved_path = manager.save_command(
|
|
1865
|
+
name=name,
|
|
1866
|
+
code=code,
|
|
1867
|
+
description=description,
|
|
1868
|
+
group=group,
|
|
1869
|
+
metadata={
|
|
1870
|
+
"source": "import-script",
|
|
1871
|
+
"original_file": str(script_file),
|
|
1872
|
+
"imported_at": datetime.now().isoformat()
|
|
1873
|
+
}
|
|
1874
|
+
)
|
|
1875
|
+
|
|
1876
|
+
click.echo(f"✅ Imported script as command: {name}")
|
|
1877
|
+
click.echo(f"📁 Saved to: {saved_path}")
|
|
1878
|
+
click.echo(f"🔄 Use with: mcli {group} {name}")
|
|
1879
|
+
click.echo(f"💡 Command will be available after restart or reload")
|
|
1880
|
+
|
|
1881
|
+
return 0
|
|
1882
|
+
|
|
1883
|
+
|
|
1884
|
+
@self_app.command("export-script", deprecated=True)
|
|
1885
|
+
@click.argument("command_name")
|
|
1886
|
+
@click.option("--output", "-o", type=click.Path(), help="Output file path")
|
|
1887
|
+
@click.option("--standalone", "-s", is_flag=True, help="Make script standalone (add if __name__ == '__main__')")
|
|
1888
|
+
def export_script(command_name, output, standalone):
|
|
1889
|
+
"""
|
|
1890
|
+
[DEPRECATED] Use 'mcli commands export-script' instead.
|
|
1891
|
+
|
|
1892
|
+
Export a JSON command to a Python script.
|
|
1893
|
+
|
|
1894
|
+
Converts a portable JSON command back to a standalone Python script
|
|
1895
|
+
that can be edited and run independently.
|
|
1896
|
+
|
|
1897
|
+
Examples:
|
|
1898
|
+
mcli commands export-script my-command
|
|
1899
|
+
mcli commands export-script my-command --output my_script.py --standalone
|
|
1900
|
+
"""
|
|
1901
|
+
click.echo("[yellow]⚠️ DEPRECATED: This command has been moved to 'mcli commands export-script'[/yellow]")
|
|
1902
|
+
click.echo(f"[yellow] Please use: mcli commands export-script {command_name}[/yellow]\n")
|
|
1903
|
+
|
|
1904
|
+
manager = get_command_manager()
|
|
1905
|
+
|
|
1906
|
+
# Load the command
|
|
1907
|
+
command_file = manager.commands_dir / f"{command_name}.json"
|
|
1908
|
+
if not command_file.exists():
|
|
1909
|
+
click.echo(f"❌ Command not found: {command_name}", err=True)
|
|
1910
|
+
return 1
|
|
1911
|
+
|
|
1912
|
+
try:
|
|
1913
|
+
with open(command_file, 'r') as f:
|
|
1914
|
+
command_data = json.load(f)
|
|
1915
|
+
except Exception as e:
|
|
1916
|
+
click.echo(f"❌ Failed to load command: {e}", err=True)
|
|
1917
|
+
return 1
|
|
1918
|
+
|
|
1919
|
+
# Get the code
|
|
1920
|
+
code = command_data.get('code', '')
|
|
1921
|
+
|
|
1922
|
+
if not code:
|
|
1923
|
+
click.echo(f"❌ Command has no code: {command_name}", err=True)
|
|
1924
|
+
return 1
|
|
1925
|
+
|
|
1926
|
+
# Add standalone wrapper if requested
|
|
1927
|
+
if standalone:
|
|
1928
|
+
# Check if already has if __name__ == '__main__'
|
|
1929
|
+
if "if __name__" not in code:
|
|
1930
|
+
code += "\n\nif __name__ == '__main__':\n app()\n"
|
|
1931
|
+
|
|
1932
|
+
# Determine output path
|
|
1933
|
+
if not output:
|
|
1934
|
+
output = f"{command_name}.py"
|
|
1935
|
+
|
|
1936
|
+
output_file = Path(output)
|
|
1937
|
+
|
|
1938
|
+
# Write the script
|
|
1939
|
+
try:
|
|
1940
|
+
with open(output_file, 'w') as f:
|
|
1941
|
+
f.write(code)
|
|
1942
|
+
except Exception as e:
|
|
1943
|
+
click.echo(f"❌ Failed to write script: {e}", err=True)
|
|
1944
|
+
return 1
|
|
1945
|
+
|
|
1946
|
+
click.echo(f"✅ Exported command to script: {output_file}")
|
|
1947
|
+
click.echo(f"📝 Source command: {command_name}")
|
|
1948
|
+
|
|
1949
|
+
if standalone:
|
|
1950
|
+
click.echo(f"🚀 Run standalone with: python {output_file}")
|
|
1951
|
+
|
|
1952
|
+
click.echo(f"💡 Edit and re-import with: mcli self import-script {output_file}")
|
|
1953
|
+
|
|
1954
|
+
return 0
|
|
1955
|
+
|
|
1956
|
+
|
|
1957
|
+
@self_app.command("edit-command", deprecated=True)
|
|
1958
|
+
@click.argument("command_name")
|
|
1959
|
+
@click.option("--editor", "-e", help="Editor to use (defaults to $EDITOR)")
|
|
1960
|
+
def edit_command(command_name, editor):
|
|
1961
|
+
"""
|
|
1962
|
+
[DEPRECATED] Use 'mcli commands edit' instead.
|
|
1963
|
+
|
|
1964
|
+
Edit a command interactively using $EDITOR.
|
|
1965
|
+
|
|
1966
|
+
Opens the command's Python code in your preferred editor,
|
|
1967
|
+
allows you to make changes, and saves the updated version.
|
|
1968
|
+
|
|
1969
|
+
Examples:
|
|
1970
|
+
mcli commands edit my-command
|
|
1971
|
+
mcli commands edit my-command --editor code
|
|
1972
|
+
"""
|
|
1973
|
+
click.echo("[yellow]⚠️ DEPRECATED: This command has been moved to 'mcli commands edit'[/yellow]")
|
|
1974
|
+
click.echo(f"[yellow] Please use: mcli commands edit {command_name}[/yellow]\n")
|
|
1975
|
+
|
|
1976
|
+
import subprocess
|
|
1977
|
+
import tempfile
|
|
1978
|
+
|
|
1979
|
+
manager = get_command_manager()
|
|
1980
|
+
|
|
1981
|
+
# Load the command
|
|
1982
|
+
command_file = manager.commands_dir / f"{command_name}.json"
|
|
1983
|
+
if not command_file.exists():
|
|
1984
|
+
click.echo(f"❌ Command not found: {command_name}", err=True)
|
|
1985
|
+
return 1
|
|
1986
|
+
|
|
1987
|
+
try:
|
|
1988
|
+
with open(command_file, 'r') as f:
|
|
1989
|
+
command_data = json.load(f)
|
|
1990
|
+
except Exception as e:
|
|
1991
|
+
click.echo(f"❌ Failed to load command: {e}", err=True)
|
|
1992
|
+
return 1
|
|
1993
|
+
|
|
1994
|
+
code = command_data.get('code', '')
|
|
1995
|
+
|
|
1996
|
+
if not code:
|
|
1997
|
+
click.echo(f"❌ Command has no code: {command_name}", err=True)
|
|
1998
|
+
return 1
|
|
1999
|
+
|
|
2000
|
+
# Determine editor
|
|
2001
|
+
if not editor:
|
|
2002
|
+
editor = os.environ.get('EDITOR', 'vim')
|
|
2003
|
+
|
|
2004
|
+
click.echo(f"📝 Opening command in {editor}...")
|
|
2005
|
+
|
|
2006
|
+
# Create temp file with the code
|
|
2007
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False,
|
|
2008
|
+
prefix=f"{command_name}_") as tmp:
|
|
2009
|
+
tmp.write(code)
|
|
2010
|
+
tmp_path = tmp.name
|
|
2011
|
+
|
|
2012
|
+
try:
|
|
2013
|
+
# Open in editor
|
|
2014
|
+
result = subprocess.run([editor, tmp_path])
|
|
2015
|
+
|
|
2016
|
+
if result.returncode != 0:
|
|
2017
|
+
click.echo(f"⚠️ Editor exited with code {result.returncode}")
|
|
2018
|
+
|
|
2019
|
+
# Read edited content
|
|
2020
|
+
with open(tmp_path, 'r') as f:
|
|
2021
|
+
new_code = f.read()
|
|
2022
|
+
|
|
2023
|
+
# Check if code changed
|
|
2024
|
+
if new_code.strip() == code.strip():
|
|
2025
|
+
click.echo("ℹ️ No changes made")
|
|
2026
|
+
return 0
|
|
2027
|
+
|
|
2028
|
+
# Validate syntax
|
|
2029
|
+
try:
|
|
2030
|
+
compile(new_code, '<string>', 'exec')
|
|
2031
|
+
except SyntaxError as e:
|
|
2032
|
+
click.echo(f"❌ Syntax error in edited code: {e}", err=True)
|
|
2033
|
+
should_save = Prompt.ask(
|
|
2034
|
+
"Save anyway?", choices=["y", "n"], default="n"
|
|
2035
|
+
)
|
|
2036
|
+
if should_save.lower() != "y":
|
|
2037
|
+
return 1
|
|
2038
|
+
|
|
2039
|
+
# Update the command
|
|
2040
|
+
command_data['code'] = new_code
|
|
2041
|
+
command_data['updated_at'] = datetime.now().isoformat()
|
|
2042
|
+
|
|
2043
|
+
with open(command_file, 'w') as f:
|
|
2044
|
+
json.dump(command_data, f, indent=2)
|
|
2045
|
+
|
|
2046
|
+
# Update lockfile
|
|
2047
|
+
manager.generate_lockfile()
|
|
2048
|
+
|
|
2049
|
+
click.echo(f"✅ Updated command: {command_name}")
|
|
2050
|
+
click.echo(f"📁 Saved to: {command_file}")
|
|
2051
|
+
click.echo(f"🔄 Reload with: mcli self reload" or "restart mcli")
|
|
2052
|
+
|
|
2053
|
+
finally:
|
|
2054
|
+
Path(tmp_path).unlink(missing_ok=True)
|
|
2055
|
+
|
|
2056
|
+
return 0
|
|
2057
|
+
|
|
2058
|
+
|
|
1560
2059
|
# Register the plugin group with self_app
|
|
1561
2060
|
self_app.add_command(plugin)
|
|
1562
2061
|
|
|
File without changes
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Daemon service for command management and execution.
|
|
3
|
+
|
|
4
|
+
This module provides a background daemon service that can store, manage, and execute
|
|
5
|
+
commands written in various programming languages (Python, Node.js, Lua, Shell).
|
|
6
|
+
Commands are stored in a SQLite database with embeddings for similarity search and
|
|
7
|
+
hierarchical grouping.
|
|
8
|
+
|
|
9
|
+
The daemon CLI commands are now loaded from portable JSON files in ~/.mcli/commands/
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from .daemon import Command, CommandExecutor, DaemonService
|
|
13
|
+
|
|
14
|
+
# Export main components
|
|
15
|
+
__all__ = ["Command", "CommandExecutor", "DaemonService"]
|
mcli/workflow/daemon/daemon.py
CHANGED
|
@@ -16,17 +16,35 @@ import click
|
|
|
16
16
|
import psutil
|
|
17
17
|
from sklearn.feature_extraction.text import TfidfVectorizer
|
|
18
18
|
from sklearn.metrics.pairwise import cosine_similarity
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
from watchdog.events import FileSystemEventHandler
|
|
22
|
+
from watchdog.observers import Observer
|
|
23
|
+
HAS_WATCHDOG = True
|
|
24
|
+
except ImportError:
|
|
25
|
+
# Watchdog not available, file watching will be disabled
|
|
26
|
+
HAS_WATCHDOG = False
|
|
27
|
+
FileSystemEventHandler = object # Stub for inheritance
|
|
28
|
+
Observer = None
|
|
21
29
|
|
|
22
30
|
# Import existing utilities
|
|
23
31
|
from mcli.lib.logger.logger import get_logger
|
|
24
32
|
from mcli.lib.toml.toml import read_from_toml
|
|
25
|
-
from mcli.workflow.daemon.commands import CommandDatabase
|
|
26
33
|
|
|
27
34
|
logger = get_logger(__name__)
|
|
28
35
|
|
|
29
36
|
|
|
37
|
+
# Stub CommandDatabase for backward compatibility
|
|
38
|
+
# Commands are now managed via JSON files in ~/.mcli/commands/
|
|
39
|
+
class CommandDatabase:
|
|
40
|
+
"""Stub database for backward compatibility.
|
|
41
|
+
Commands are now stored as JSON files and loaded via the custom commands system.
|
|
42
|
+
"""
|
|
43
|
+
def __init__(self, db_path: Optional[str] = None):
|
|
44
|
+
logger.debug("CommandDatabase stub initialized - commands now managed via JSON files")
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
|
|
30
48
|
@dataclass
|
|
31
49
|
class Command:
|
|
32
50
|
"""Represents a stored command"""
|