mcli-framework 7.4.0__py3-none-any.whl → 7.5.1__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/lib/auth/aws_manager.py +9 -64
- mcli/lib/auth/azure_manager.py +9 -64
- mcli/lib/auth/credential_manager.py +70 -1
- mcli/lib/auth/gcp_manager.py +11 -64
- mcli/ml/dashboard/app.py +6 -39
- mcli/ml/dashboard/app_integrated.py +23 -101
- mcli/ml/dashboard/app_supabase.py +8 -57
- mcli/ml/dashboard/app_training.py +9 -11
- mcli/ml/dashboard/common.py +167 -0
- mcli/ml/dashboard/pages/cicd.py +4 -4
- mcli/ml/dashboard/pages/debug_dependencies.py +99 -57
- mcli/ml/dashboard/pages/gravity_viz.py +265 -47
- mcli/ml/dashboard/pages/predictions_enhanced.py +4 -2
- mcli/ml/dashboard/pages/scrapers_and_logs.py +3 -3
- mcli/ml/dashboard/styles.py +55 -0
- mcli/self/self_cmd.py +12 -806
- {mcli_framework-7.4.0.dist-info → mcli_framework-7.5.1.dist-info}/METADATA +1 -3
- {mcli_framework-7.4.0.dist-info → mcli_framework-7.5.1.dist-info}/RECORD +23 -84
- mcli/__init__.py +0 -160
- mcli/__main__.py +0 -14
- mcli/app/__init__.py +0 -23
- mcli/app/model/__init__.py +0 -0
- mcli/app/video/__init__.py +0 -5
- mcli/chat/__init__.py +0 -34
- mcli/lib/__init__.py +0 -0
- mcli/lib/api/__init__.py +0 -0
- mcli/lib/auth/__init__.py +0 -1
- mcli/lib/config/__init__.py +0 -1
- mcli/lib/erd/__init__.py +0 -25
- mcli/lib/files/__init__.py +0 -0
- mcli/lib/fs/__init__.py +0 -1
- mcli/lib/logger/__init__.py +0 -3
- mcli/lib/performance/__init__.py +0 -17
- mcli/lib/pickles/__init__.py +0 -1
- mcli/lib/shell/__init__.py +0 -0
- mcli/lib/toml/__init__.py +0 -1
- mcli/lib/watcher/__init__.py +0 -0
- mcli/ml/__init__.py +0 -16
- mcli/ml/api/__init__.py +0 -30
- mcli/ml/api/routers/__init__.py +0 -27
- mcli/ml/auth/__init__.py +0 -45
- mcli/ml/backtesting/__init__.py +0 -39
- mcli/ml/cli/__init__.py +0 -5
- mcli/ml/config/__init__.py +0 -33
- mcli/ml/configs/__init__.py +0 -16
- mcli/ml/dashboard/__init__.py +0 -12
- mcli/ml/dashboard/components/__init__.py +0 -7
- mcli/ml/dashboard/pages/__init__.py +0 -6
- mcli/ml/data_ingestion/__init__.py +0 -39
- mcli/ml/database/__init__.py +0 -47
- mcli/ml/experimentation/__init__.py +0 -29
- mcli/ml/features/__init__.py +0 -39
- mcli/ml/mlops/__init__.py +0 -33
- mcli/ml/models/__init__.py +0 -94
- mcli/ml/monitoring/__init__.py +0 -25
- mcli/ml/optimization/__init__.py +0 -27
- mcli/ml/predictions/__init__.py +0 -5
- mcli/ml/preprocessing/__init__.py +0 -28
- mcli/ml/scripts/__init__.py +0 -1
- mcli/ml/trading/__init__.py +0 -66
- mcli/ml/training/__init__.py +0 -10
- mcli/mygroup/__init__.py +0 -3
- mcli/public/__init__.py +0 -1
- mcli/public/commands/__init__.py +0 -2
- mcli/self/__init__.py +0 -3
- mcli/workflow/__init__.py +0 -0
- mcli/workflow/daemon/__init__.py +0 -15
- mcli/workflow/dashboard/__init__.py +0 -5
- mcli/workflow/docker/__init__.py +0 -0
- mcli/workflow/file/__init__.py +0 -0
- mcli/workflow/gcloud/__init__.py +0 -1
- mcli/workflow/git_commit/__init__.py +0 -0
- mcli/workflow/interview/__init__.py +0 -0
- mcli/workflow/politician_trading/__init__.py +0 -4
- mcli/workflow/registry/__init__.py +0 -0
- mcli/workflow/repo/__init__.py +0 -0
- mcli/workflow/scheduler/__init__.py +0 -25
- mcli/workflow/search/__init__.py +0 -0
- mcli/workflow/sync/__init__.py +0 -5
- mcli/workflow/videos/__init__.py +0 -1
- mcli/workflow/wakatime/__init__.py +0 -80
- /mcli/ml/dashboard/{pages/overview.py → overview.py} +0 -0
- {mcli_framework-7.4.0.dist-info → mcli_framework-7.5.1.dist-info}/WHEEL +0 -0
- {mcli_framework-7.4.0.dist-info → mcli_framework-7.5.1.dist-info}/entry_points.txt +0 -0
- {mcli_framework-7.4.0.dist-info → mcli_framework-7.5.1.dist-info}/licenses/LICENSE +0 -0
- {mcli_framework-7.4.0.dist-info → mcli_framework-7.5.1.dist-info}/top_level.txt +0 -0
mcli/self/self_cmd.py
CHANGED
|
@@ -563,313 +563,6 @@ logger = get_logger()
|
|
|
563
563
|
pass
|
|
564
564
|
|
|
565
565
|
|
|
566
|
-
@self_app.command("add-command", deprecated=True)
|
|
567
|
-
@click.argument("command_name", required=True)
|
|
568
|
-
@click.option("--group", "-g", help="Command group (defaults to 'workflow')", default="workflow")
|
|
569
|
-
@click.option(
|
|
570
|
-
"--description", "-d", help="Description for the command", default="Custom command"
|
|
571
|
-
)
|
|
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):
|
|
576
|
-
"""
|
|
577
|
-
[DEPRECATED] Use 'mcli commands add' instead.
|
|
578
|
-
|
|
579
|
-
Generate a new portable custom command saved to ~/.mcli/commands/.
|
|
580
|
-
|
|
581
|
-
This command has been moved to 'mcli commands add' for better organization.
|
|
582
|
-
Please use the new command going forward.
|
|
583
|
-
|
|
584
|
-
Example:
|
|
585
|
-
mcli commands add my_command
|
|
586
|
-
mcli commands add analytics --group data
|
|
587
|
-
mcli commands add quick_cmd --template # Use template without editor
|
|
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))
|
|
591
|
-
command_name = command_name.lower().replace("-", "_")
|
|
592
|
-
|
|
593
|
-
# Validate command name
|
|
594
|
-
if not re.match(r"^[a-z][a-z0-9_]*$", command_name):
|
|
595
|
-
logger.error(
|
|
596
|
-
f"Invalid command name: {command_name}. Use lowercase letters, numbers, and underscores (starting with a letter)."
|
|
597
|
-
)
|
|
598
|
-
click.echo(
|
|
599
|
-
f"Invalid command name: {command_name}. Use lowercase letters, numbers, and underscores (starting with a letter).",
|
|
600
|
-
err=True,
|
|
601
|
-
)
|
|
602
|
-
return 1
|
|
603
|
-
|
|
604
|
-
# Validate group name if provided
|
|
605
|
-
if group:
|
|
606
|
-
command_group = group.lower().replace("-", "_")
|
|
607
|
-
if not re.match(r"^[a-z][a-z0-9_]*$", command_group):
|
|
608
|
-
logger.error(
|
|
609
|
-
f"Invalid group name: {command_group}. Use lowercase letters, numbers, and underscores (starting with a letter)."
|
|
610
|
-
)
|
|
611
|
-
click.echo(
|
|
612
|
-
f"Invalid group name: {command_group}. Use lowercase letters, numbers, and underscores (starting with a letter).",
|
|
613
|
-
err=True,
|
|
614
|
-
)
|
|
615
|
-
return 1
|
|
616
|
-
else:
|
|
617
|
-
command_group = "workflow" # Default to workflow group
|
|
618
|
-
|
|
619
|
-
# Get the command manager
|
|
620
|
-
manager = get_command_manager()
|
|
621
|
-
|
|
622
|
-
# Check if command already exists
|
|
623
|
-
command_file = manager.commands_dir / f"{command_name}.json"
|
|
624
|
-
if command_file.exists():
|
|
625
|
-
logger.warning(f"Custom command already exists: {command_name}")
|
|
626
|
-
should_override = Prompt.ask(
|
|
627
|
-
"Command already exists. Override?", choices=["y", "n"], default="n"
|
|
628
|
-
)
|
|
629
|
-
if should_override.lower() != "y":
|
|
630
|
-
logger.info("Command creation aborted.")
|
|
631
|
-
click.echo("Command creation aborted.")
|
|
632
|
-
return 1
|
|
633
|
-
|
|
634
|
-
# Generate command code
|
|
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
|
|
646
|
-
|
|
647
|
-
# Save the command
|
|
648
|
-
saved_path = manager.save_command(
|
|
649
|
-
name=command_name,
|
|
650
|
-
code=code,
|
|
651
|
-
description=description,
|
|
652
|
-
group=command_group,
|
|
653
|
-
)
|
|
654
|
-
|
|
655
|
-
logger.info(f"Created portable custom command: {command_name}")
|
|
656
|
-
click.echo(f"✅ Created portable custom command: {command_name}")
|
|
657
|
-
click.echo(f"📁 Saved to: {saved_path}")
|
|
658
|
-
click.echo(f"🔄 Command will be automatically loaded on next mcli startup")
|
|
659
|
-
click.echo(
|
|
660
|
-
f"💡 You can share this command by copying {saved_path} to another machine's ~/.mcli/commands/ directory"
|
|
661
|
-
)
|
|
662
|
-
|
|
663
|
-
return 0
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
@self_app.command("list-commands", deprecated=True)
|
|
667
|
-
def list_commands():
|
|
668
|
-
"""
|
|
669
|
-
[DEPRECATED] Use 'mcli commands list-custom' instead.
|
|
670
|
-
|
|
671
|
-
List all custom commands stored in ~/.mcli/commands/.
|
|
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
|
-
|
|
676
|
-
manager = get_command_manager()
|
|
677
|
-
commands = manager.load_all_commands()
|
|
678
|
-
|
|
679
|
-
if not commands:
|
|
680
|
-
click.echo("No custom commands found.")
|
|
681
|
-
click.echo(
|
|
682
|
-
f"Create one with: mcli self add-command <name>"
|
|
683
|
-
)
|
|
684
|
-
return 0
|
|
685
|
-
|
|
686
|
-
table = Table(title="Custom Commands")
|
|
687
|
-
table.add_column("Name", style="green")
|
|
688
|
-
table.add_column("Group", style="blue")
|
|
689
|
-
table.add_column("Description", style="yellow")
|
|
690
|
-
table.add_column("Version", style="cyan")
|
|
691
|
-
table.add_column("Updated", style="dim")
|
|
692
|
-
|
|
693
|
-
for cmd in commands:
|
|
694
|
-
table.add_row(
|
|
695
|
-
cmd["name"],
|
|
696
|
-
cmd.get("group", "-"),
|
|
697
|
-
cmd.get("description", ""),
|
|
698
|
-
cmd.get("version", "1.0"),
|
|
699
|
-
cmd.get("updated_at", "")[:10] if cmd.get("updated_at") else "-",
|
|
700
|
-
)
|
|
701
|
-
|
|
702
|
-
console.print(table)
|
|
703
|
-
click.echo(f"\n📁 Commands directory: {manager.commands_dir}")
|
|
704
|
-
click.echo(f"🔒 Lockfile: {manager.lockfile_path}")
|
|
705
|
-
|
|
706
|
-
return 0
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
@self_app.command("remove-command", deprecated=True)
|
|
710
|
-
@click.argument("command_name", required=True)
|
|
711
|
-
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompt")
|
|
712
|
-
def remove_command(command_name, yes):
|
|
713
|
-
"""
|
|
714
|
-
[DEPRECATED] Use 'mcli commands remove' instead.
|
|
715
|
-
|
|
716
|
-
Remove a custom command from ~/.mcli/commands/.
|
|
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
|
-
|
|
721
|
-
manager = get_command_manager()
|
|
722
|
-
command_file = manager.commands_dir / f"{command_name}.json"
|
|
723
|
-
|
|
724
|
-
if not command_file.exists():
|
|
725
|
-
click.echo(f"❌ Command '{command_name}' not found.", err=True)
|
|
726
|
-
return 1
|
|
727
|
-
|
|
728
|
-
if not yes:
|
|
729
|
-
should_delete = Prompt.ask(
|
|
730
|
-
f"Delete command '{command_name}'?", choices=["y", "n"], default="n"
|
|
731
|
-
)
|
|
732
|
-
if should_delete.lower() != "y":
|
|
733
|
-
click.echo("Deletion cancelled.")
|
|
734
|
-
return 0
|
|
735
|
-
|
|
736
|
-
if manager.delete_command(command_name):
|
|
737
|
-
click.echo(f"✅ Deleted custom command: {command_name}")
|
|
738
|
-
return 0
|
|
739
|
-
else:
|
|
740
|
-
click.echo(f"❌ Failed to delete command: {command_name}", err=True)
|
|
741
|
-
return 1
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
@self_app.command("export-commands", deprecated=True)
|
|
745
|
-
@click.argument("export_file", type=click.Path(), required=False)
|
|
746
|
-
def export_commands(export_file):
|
|
747
|
-
"""
|
|
748
|
-
[DEPRECATED] Use 'mcli commands export' instead.
|
|
749
|
-
|
|
750
|
-
Export all custom commands to a JSON file.
|
|
751
|
-
|
|
752
|
-
If no file is specified, exports to commands-export.json in current directory.
|
|
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
|
-
|
|
757
|
-
manager = get_command_manager()
|
|
758
|
-
|
|
759
|
-
if not export_file:
|
|
760
|
-
export_file = "commands-export.json"
|
|
761
|
-
|
|
762
|
-
export_path = Path(export_file)
|
|
763
|
-
|
|
764
|
-
if manager.export_commands(export_path):
|
|
765
|
-
click.echo(f"✅ Exported custom commands to: {export_path}")
|
|
766
|
-
click.echo(
|
|
767
|
-
f"💡 Import on another machine with: mcli self import-commands {export_path}"
|
|
768
|
-
)
|
|
769
|
-
return 0
|
|
770
|
-
else:
|
|
771
|
-
click.echo("❌ Failed to export commands.", err=True)
|
|
772
|
-
return 1
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
@self_app.command("import-commands", deprecated=True)
|
|
776
|
-
@click.argument("import_file", type=click.Path(exists=True), required=True)
|
|
777
|
-
@click.option("--overwrite", is_flag=True, help="Overwrite existing commands")
|
|
778
|
-
def import_commands(import_file, overwrite):
|
|
779
|
-
"""
|
|
780
|
-
[DEPRECATED] Use 'mcli commands import' instead.
|
|
781
|
-
|
|
782
|
-
Import custom commands from a JSON file.
|
|
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
|
-
|
|
787
|
-
manager = get_command_manager()
|
|
788
|
-
import_path = Path(import_file)
|
|
789
|
-
|
|
790
|
-
results = manager.import_commands(import_path, overwrite=overwrite)
|
|
791
|
-
|
|
792
|
-
success_count = sum(1 for v in results.values() if v)
|
|
793
|
-
failed_count = len(results) - success_count
|
|
794
|
-
|
|
795
|
-
if success_count > 0:
|
|
796
|
-
click.echo(f"✅ Imported {success_count} command(s)")
|
|
797
|
-
|
|
798
|
-
if failed_count > 0:
|
|
799
|
-
click.echo(
|
|
800
|
-
f"⚠️ Skipped {failed_count} command(s) (already exist, use --overwrite to replace)"
|
|
801
|
-
)
|
|
802
|
-
click.echo("Skipped commands:")
|
|
803
|
-
for name, success in results.items():
|
|
804
|
-
if not success:
|
|
805
|
-
click.echo(f" - {name}")
|
|
806
|
-
|
|
807
|
-
return 0
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
@self_app.command("verify-commands", deprecated=True)
|
|
811
|
-
def verify_commands():
|
|
812
|
-
"""
|
|
813
|
-
[DEPRECATED] Use 'mcli commands verify' instead.
|
|
814
|
-
|
|
815
|
-
Verify that custom commands match the lockfile.
|
|
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
|
-
|
|
820
|
-
manager = get_command_manager()
|
|
821
|
-
|
|
822
|
-
# First, ensure lockfile is up to date
|
|
823
|
-
manager.update_lockfile()
|
|
824
|
-
|
|
825
|
-
verification = manager.verify_lockfile()
|
|
826
|
-
|
|
827
|
-
if verification["valid"]:
|
|
828
|
-
click.echo("✅ All custom commands are in sync with the lockfile.")
|
|
829
|
-
return 0
|
|
830
|
-
|
|
831
|
-
click.echo("⚠️ Commands are out of sync with the lockfile:\n")
|
|
832
|
-
|
|
833
|
-
if verification["missing"]:
|
|
834
|
-
click.echo(f"Missing commands (in lockfile but not found):")
|
|
835
|
-
for name in verification["missing"]:
|
|
836
|
-
click.echo(f" - {name}")
|
|
837
|
-
|
|
838
|
-
if verification["extra"]:
|
|
839
|
-
click.echo(f"\nExtra commands (not in lockfile):")
|
|
840
|
-
for name in verification["extra"]:
|
|
841
|
-
click.echo(f" - {name}")
|
|
842
|
-
|
|
843
|
-
if verification["modified"]:
|
|
844
|
-
click.echo(f"\nModified commands:")
|
|
845
|
-
for name in verification["modified"]:
|
|
846
|
-
click.echo(f" - {name}")
|
|
847
|
-
|
|
848
|
-
click.echo(f"\n💡 Run 'mcli self update-lockfile' to sync the lockfile")
|
|
849
|
-
|
|
850
|
-
return 1
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
@self_app.command("update-lockfile", deprecated=True)
|
|
854
|
-
def update_lockfile():
|
|
855
|
-
"""
|
|
856
|
-
[DEPRECATED] Use 'mcli commands update-lockfile' instead.
|
|
857
|
-
|
|
858
|
-
Update the commands lockfile with current state.
|
|
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
|
-
|
|
863
|
-
manager = get_command_manager()
|
|
864
|
-
|
|
865
|
-
if manager.update_lockfile():
|
|
866
|
-
click.echo(f"✅ Updated lockfile: {manager.lockfile_path}")
|
|
867
|
-
return 0
|
|
868
|
-
else:
|
|
869
|
-
click.echo("❌ Failed to update lockfile.", err=True)
|
|
870
|
-
return 1
|
|
871
|
-
|
|
872
|
-
|
|
873
566
|
@self_app.command("extract-workflow-commands")
|
|
874
567
|
@click.option(
|
|
875
568
|
"--output", "-o", type=click.Path(), help="Output file (default: workflow-commands.json)"
|
|
@@ -1195,265 +888,21 @@ def hello(name: str):
|
|
|
1195
888
|
|
|
1196
889
|
|
|
1197
890
|
@self_app.command("logs")
|
|
1198
|
-
|
|
1199
|
-
"--type",
|
|
1200
|
-
"-t",
|
|
1201
|
-
type=click.Choice(["main", "system", "trace", "all"]),
|
|
1202
|
-
default="main",
|
|
1203
|
-
help="Type of logs to display",
|
|
1204
|
-
)
|
|
1205
|
-
@click.option("--lines", "-n", default=50, help="Number of lines to show (default: 50)")
|
|
1206
|
-
@click.option("--follow", "-f", is_flag=True, help="Follow log output in real-time")
|
|
1207
|
-
@click.option("--date", "-d", help="Show logs for specific date (YYYYMMDD format)")
|
|
1208
|
-
@click.option("--grep", "-g", help="Filter logs by pattern")
|
|
1209
|
-
@click.option(
|
|
1210
|
-
"--level",
|
|
1211
|
-
"-l",
|
|
1212
|
-
type=click.Choice(["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]),
|
|
1213
|
-
help="Filter logs by minimum level",
|
|
1214
|
-
)
|
|
1215
|
-
def logs(type: str, lines: int, follow: bool, date: str, grep: str, level: str):
|
|
891
|
+
def logs():
|
|
1216
892
|
"""
|
|
1217
|
-
Display runtime logs
|
|
893
|
+
[DEPRECATED] Display runtime logs - Use 'mcli logs' instead.
|
|
1218
894
|
|
|
1219
|
-
|
|
1220
|
-
Supports filtering by log type, date, content, and log level.
|
|
1221
|
-
|
|
1222
|
-
Log files are named as mcli_YYYYMMDD.log, mcli_system_YYYYMMDD.log, mcli_trace_YYYYMMDD.log.
|
|
895
|
+
This command has been moved to 'mcli logs' with enhanced features.
|
|
1223
896
|
"""
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
logs_dir = get_logs_dir()
|
|
1234
|
-
|
|
1235
|
-
if not logs_dir.exists():
|
|
1236
|
-
click.echo("❌ Logs directory not found", err=True)
|
|
1237
|
-
click.echo(f"Expected location: {logs_dir}", err=True)
|
|
1238
|
-
return
|
|
1239
|
-
|
|
1240
|
-
# Determine which log files to read
|
|
1241
|
-
log_files = []
|
|
1242
|
-
|
|
1243
|
-
if type == "all":
|
|
1244
|
-
# Get all log files for the specified date or latest
|
|
1245
|
-
if date:
|
|
1246
|
-
# Look for files like mcli_20250709.log, mcli_system_20250709.log, mcli_trace_20250709.log
|
|
1247
|
-
patterns = [f"mcli_{date}.log", f"mcli_system_{date}.log", f"mcli_trace_{date}.log"]
|
|
1248
|
-
else:
|
|
1249
|
-
# Get the most recent log files
|
|
1250
|
-
patterns = ["mcli_*.log"]
|
|
1251
|
-
|
|
1252
|
-
log_files = []
|
|
1253
|
-
for pattern in patterns:
|
|
1254
|
-
files = list(logs_dir.glob(pattern))
|
|
1255
|
-
if files:
|
|
1256
|
-
# Sort by modification time (newest first)
|
|
1257
|
-
files.sort(key=lambda x: x.stat().st_mtime, reverse=True)
|
|
1258
|
-
log_files.extend(files)
|
|
1259
|
-
|
|
1260
|
-
# Remove duplicates and take only the most recent files of each type
|
|
1261
|
-
seen_types = set()
|
|
1262
|
-
filtered_files = []
|
|
1263
|
-
for log_file in log_files:
|
|
1264
|
-
# Extract log type from filename
|
|
1265
|
-
# mcli_20250709 -> main
|
|
1266
|
-
# mcli_system_20250709 -> system
|
|
1267
|
-
# mcli_trace_20250709 -> trace
|
|
1268
|
-
if log_file.name.startswith("mcli_system_"):
|
|
1269
|
-
log_type = "system"
|
|
1270
|
-
elif log_file.name.startswith("mcli_trace_"):
|
|
1271
|
-
log_type = "trace"
|
|
1272
|
-
else:
|
|
1273
|
-
log_type = "main"
|
|
1274
|
-
|
|
1275
|
-
if log_type not in seen_types:
|
|
1276
|
-
seen_types.add(log_type)
|
|
1277
|
-
filtered_files.append(log_file)
|
|
1278
|
-
|
|
1279
|
-
log_files = filtered_files
|
|
1280
|
-
else:
|
|
1281
|
-
# Get specific log type
|
|
1282
|
-
if date:
|
|
1283
|
-
if type == "main":
|
|
1284
|
-
filename = f"mcli_{date}.log"
|
|
1285
|
-
else:
|
|
1286
|
-
filename = f"mcli_{type}_{date}.log"
|
|
1287
|
-
else:
|
|
1288
|
-
# Find the most recent file for this type
|
|
1289
|
-
if type == "main":
|
|
1290
|
-
pattern = "mcli_*.log"
|
|
1291
|
-
# Exclude system and trace files
|
|
1292
|
-
exclude_patterns = ["mcli_system_*.log", "mcli_trace_*.log"]
|
|
1293
|
-
else:
|
|
1294
|
-
pattern = f"mcli_{type}_*.log"
|
|
1295
|
-
exclude_patterns = []
|
|
1296
|
-
|
|
1297
|
-
files = list(logs_dir.glob(pattern))
|
|
1298
|
-
|
|
1299
|
-
# Filter out excluded patterns
|
|
1300
|
-
if exclude_patterns:
|
|
1301
|
-
filtered_files = []
|
|
1302
|
-
for file in files:
|
|
1303
|
-
excluded = False
|
|
1304
|
-
for exclude_pattern in exclude_patterns:
|
|
1305
|
-
if file.match(exclude_pattern):
|
|
1306
|
-
excluded = True
|
|
1307
|
-
break
|
|
1308
|
-
if not excluded:
|
|
1309
|
-
filtered_files.append(file)
|
|
1310
|
-
files = filtered_files
|
|
1311
|
-
|
|
1312
|
-
if files:
|
|
1313
|
-
# Sort by modification time and take the most recent
|
|
1314
|
-
files.sort(key=lambda x: x.stat().st_mtime, reverse=True)
|
|
1315
|
-
filename = files[0].name
|
|
1316
|
-
else:
|
|
1317
|
-
click.echo(f"❌ No {type} log files found", err=True)
|
|
1318
|
-
return
|
|
1319
|
-
|
|
1320
|
-
log_file = logs_dir / filename
|
|
1321
|
-
if log_file.exists():
|
|
1322
|
-
log_files = [log_file]
|
|
1323
|
-
else:
|
|
1324
|
-
click.echo(f"❌ Log file not found: {filename}", err=True)
|
|
1325
|
-
return
|
|
1326
|
-
|
|
1327
|
-
if not log_files:
|
|
1328
|
-
click.echo("❌ No log files found", err=True)
|
|
1329
|
-
return
|
|
1330
|
-
|
|
1331
|
-
# Display log file information
|
|
1332
|
-
click.echo(f"📋 Showing logs from {len(log_files)} file(s):")
|
|
1333
|
-
for log_file in log_files:
|
|
1334
|
-
size_mb = log_file.stat().st_size / (1024 * 1024)
|
|
1335
|
-
modified = datetime.fromtimestamp(log_file.stat().st_mtime)
|
|
1336
|
-
click.echo(
|
|
1337
|
-
f" 📄 {log_file.name} ({size_mb:.1f}MB, modified {modified.strftime('%Y-%m-%d %H:%M:%S')})"
|
|
1338
|
-
)
|
|
1339
|
-
click.echo()
|
|
1340
|
-
|
|
1341
|
-
# Process each log file
|
|
1342
|
-
for log_file in log_files:
|
|
1343
|
-
click.echo(f"🔍 Reading: {log_file.name}")
|
|
1344
|
-
click.echo("─" * 80)
|
|
1345
|
-
|
|
1346
|
-
try:
|
|
1347
|
-
# Read the file content
|
|
1348
|
-
with open(log_file, "r") as f:
|
|
1349
|
-
content = f.readlines()
|
|
1350
|
-
|
|
1351
|
-
# Apply filters
|
|
1352
|
-
filtered_lines = []
|
|
1353
|
-
for line in content:
|
|
1354
|
-
# Apply grep filter
|
|
1355
|
-
if grep and grep.lower() not in line.lower():
|
|
1356
|
-
continue
|
|
1357
|
-
|
|
1358
|
-
# Apply level filter
|
|
1359
|
-
if level:
|
|
1360
|
-
level_pattern = rf"\b{level}\b"
|
|
1361
|
-
if not re.search(level_pattern, line, re.IGNORECASE):
|
|
1362
|
-
# Check if line has a lower level than requested
|
|
1363
|
-
level_order = {
|
|
1364
|
-
"DEBUG": 0,
|
|
1365
|
-
"INFO": 1,
|
|
1366
|
-
"WARNING": 2,
|
|
1367
|
-
"ERROR": 3,
|
|
1368
|
-
"CRITICAL": 4,
|
|
1369
|
-
}
|
|
1370
|
-
requested_level = level_order.get(level.upper(), 0)
|
|
1371
|
-
|
|
1372
|
-
# Check if line contains any log level
|
|
1373
|
-
found_level = None
|
|
1374
|
-
for log_level in level_order:
|
|
1375
|
-
if log_level in line.upper():
|
|
1376
|
-
found_level = level_order[log_level]
|
|
1377
|
-
break
|
|
1378
|
-
|
|
1379
|
-
if found_level is None or found_level < requested_level:
|
|
1380
|
-
continue
|
|
1381
|
-
|
|
1382
|
-
filtered_lines.append(line)
|
|
1383
|
-
|
|
1384
|
-
# Show the last N lines
|
|
1385
|
-
if lines > 0:
|
|
1386
|
-
filtered_lines = filtered_lines[-lines:]
|
|
1387
|
-
|
|
1388
|
-
# Display the lines
|
|
1389
|
-
for line in filtered_lines:
|
|
1390
|
-
# Colorize log levels
|
|
1391
|
-
colored_line = line
|
|
1392
|
-
if "ERROR" in line or "CRITICAL" in line:
|
|
1393
|
-
colored_line = click.style(line, fg="red")
|
|
1394
|
-
elif "WARNING" in line:
|
|
1395
|
-
colored_line = click.style(line, fg="yellow")
|
|
1396
|
-
elif "INFO" in line:
|
|
1397
|
-
colored_line = click.style(line, fg="green")
|
|
1398
|
-
elif "DEBUG" in line:
|
|
1399
|
-
colored_line = click.style(line, fg="blue")
|
|
1400
|
-
|
|
1401
|
-
click.echo(colored_line.rstrip())
|
|
1402
|
-
|
|
1403
|
-
if not filtered_lines:
|
|
1404
|
-
click.echo("(No matching log entries found)")
|
|
1405
|
-
|
|
1406
|
-
except Exception as e:
|
|
1407
|
-
click.echo(f"❌ Error reading log file {log_file.name}: {e}", err=True)
|
|
1408
|
-
|
|
1409
|
-
click.echo()
|
|
1410
|
-
|
|
1411
|
-
if follow:
|
|
1412
|
-
click.echo("🔄 Following log output... (Press Ctrl+C to stop)")
|
|
1413
|
-
try:
|
|
1414
|
-
# Use tail -f for real-time following
|
|
1415
|
-
for log_file in log_files:
|
|
1416
|
-
click.echo(f"📡 Following: {log_file.name}")
|
|
1417
|
-
process = subprocess.Popen(
|
|
1418
|
-
["tail", "-f", str(log_file)],
|
|
1419
|
-
stdout=subprocess.PIPE,
|
|
1420
|
-
stderr=subprocess.PIPE,
|
|
1421
|
-
text=True,
|
|
1422
|
-
)
|
|
1423
|
-
|
|
1424
|
-
try:
|
|
1425
|
-
if process.stdout:
|
|
1426
|
-
for line in process.stdout:
|
|
1427
|
-
# Apply filters to real-time output
|
|
1428
|
-
if grep and grep.lower() not in line.lower():
|
|
1429
|
-
continue
|
|
1430
|
-
|
|
1431
|
-
if level:
|
|
1432
|
-
level_pattern = rf"\b{level}\b"
|
|
1433
|
-
if not re.search(level_pattern, line, re.IGNORECASE):
|
|
1434
|
-
continue
|
|
1435
|
-
|
|
1436
|
-
# Colorize and display
|
|
1437
|
-
colored_line = line
|
|
1438
|
-
if "ERROR" in line or "CRITICAL" in line:
|
|
1439
|
-
colored_line = click.style(line, fg="red")
|
|
1440
|
-
elif "WARNING" in line:
|
|
1441
|
-
colored_line = click.style(line, fg="yellow")
|
|
1442
|
-
elif "INFO" in line:
|
|
1443
|
-
colored_line = click.style(line, fg="green")
|
|
1444
|
-
elif "DEBUG" in line:
|
|
1445
|
-
colored_line = click.style(line, fg="blue")
|
|
1446
|
-
|
|
1447
|
-
click.echo(colored_line.rstrip())
|
|
1448
|
-
|
|
1449
|
-
except KeyboardInterrupt:
|
|
1450
|
-
process.terminate()
|
|
1451
|
-
break
|
|
1452
|
-
|
|
1453
|
-
except KeyboardInterrupt:
|
|
1454
|
-
click.echo("\n🛑 Stopped following logs")
|
|
1455
|
-
except Exception as e:
|
|
1456
|
-
click.echo(f"❌ Error following logs: {e}", err=True)
|
|
897
|
+
console.print("\n[yellow]⚠️ DEPRECATED:[/yellow] This command has been moved.")
|
|
898
|
+
console.print("\n[cyan]New usage:[/cyan]")
|
|
899
|
+
console.print(" mcli logs [bold]stream[/bold] - Stream logs in real-time")
|
|
900
|
+
console.print(" mcli logs [bold]list[/bold] - List available log files")
|
|
901
|
+
console.print(" mcli logs [bold]tail[/bold] - Tail recent log entries")
|
|
902
|
+
console.print(" mcli logs [bold]grep[/bold] - Search in log files")
|
|
903
|
+
console.print(" mcli logs [bold]location[/bold] - Show logs directory")
|
|
904
|
+
console.print(" mcli logs [bold]clear[/bold] - Clear old log files")
|
|
905
|
+
console.print("\n[dim]Run 'mcli logs --help' for more information[/dim]\n")
|
|
1457
906
|
|
|
1458
907
|
|
|
1459
908
|
@self_app.command("performance")
|
|
@@ -1781,249 +1230,6 @@ def update(check: bool, pre: bool, yes: bool, skip_ci_check: bool):
|
|
|
1781
1230
|
console.print(f"[dim]{traceback.format_exc()}[/dim]")
|
|
1782
1231
|
|
|
1783
1232
|
|
|
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
1233
|
|
|
2028
1234
|
# Validate syntax
|
|
2029
1235
|
try:
|