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.

Files changed (86) hide show
  1. mcli/lib/auth/aws_manager.py +9 -64
  2. mcli/lib/auth/azure_manager.py +9 -64
  3. mcli/lib/auth/credential_manager.py +70 -1
  4. mcli/lib/auth/gcp_manager.py +11 -64
  5. mcli/ml/dashboard/app.py +6 -39
  6. mcli/ml/dashboard/app_integrated.py +23 -101
  7. mcli/ml/dashboard/app_supabase.py +8 -57
  8. mcli/ml/dashboard/app_training.py +9 -11
  9. mcli/ml/dashboard/common.py +167 -0
  10. mcli/ml/dashboard/pages/cicd.py +4 -4
  11. mcli/ml/dashboard/pages/debug_dependencies.py +99 -57
  12. mcli/ml/dashboard/pages/gravity_viz.py +265 -47
  13. mcli/ml/dashboard/pages/predictions_enhanced.py +4 -2
  14. mcli/ml/dashboard/pages/scrapers_and_logs.py +3 -3
  15. mcli/ml/dashboard/styles.py +55 -0
  16. mcli/self/self_cmd.py +12 -806
  17. {mcli_framework-7.4.0.dist-info → mcli_framework-7.5.1.dist-info}/METADATA +1 -3
  18. {mcli_framework-7.4.0.dist-info → mcli_framework-7.5.1.dist-info}/RECORD +23 -84
  19. mcli/__init__.py +0 -160
  20. mcli/__main__.py +0 -14
  21. mcli/app/__init__.py +0 -23
  22. mcli/app/model/__init__.py +0 -0
  23. mcli/app/video/__init__.py +0 -5
  24. mcli/chat/__init__.py +0 -34
  25. mcli/lib/__init__.py +0 -0
  26. mcli/lib/api/__init__.py +0 -0
  27. mcli/lib/auth/__init__.py +0 -1
  28. mcli/lib/config/__init__.py +0 -1
  29. mcli/lib/erd/__init__.py +0 -25
  30. mcli/lib/files/__init__.py +0 -0
  31. mcli/lib/fs/__init__.py +0 -1
  32. mcli/lib/logger/__init__.py +0 -3
  33. mcli/lib/performance/__init__.py +0 -17
  34. mcli/lib/pickles/__init__.py +0 -1
  35. mcli/lib/shell/__init__.py +0 -0
  36. mcli/lib/toml/__init__.py +0 -1
  37. mcli/lib/watcher/__init__.py +0 -0
  38. mcli/ml/__init__.py +0 -16
  39. mcli/ml/api/__init__.py +0 -30
  40. mcli/ml/api/routers/__init__.py +0 -27
  41. mcli/ml/auth/__init__.py +0 -45
  42. mcli/ml/backtesting/__init__.py +0 -39
  43. mcli/ml/cli/__init__.py +0 -5
  44. mcli/ml/config/__init__.py +0 -33
  45. mcli/ml/configs/__init__.py +0 -16
  46. mcli/ml/dashboard/__init__.py +0 -12
  47. mcli/ml/dashboard/components/__init__.py +0 -7
  48. mcli/ml/dashboard/pages/__init__.py +0 -6
  49. mcli/ml/data_ingestion/__init__.py +0 -39
  50. mcli/ml/database/__init__.py +0 -47
  51. mcli/ml/experimentation/__init__.py +0 -29
  52. mcli/ml/features/__init__.py +0 -39
  53. mcli/ml/mlops/__init__.py +0 -33
  54. mcli/ml/models/__init__.py +0 -94
  55. mcli/ml/monitoring/__init__.py +0 -25
  56. mcli/ml/optimization/__init__.py +0 -27
  57. mcli/ml/predictions/__init__.py +0 -5
  58. mcli/ml/preprocessing/__init__.py +0 -28
  59. mcli/ml/scripts/__init__.py +0 -1
  60. mcli/ml/trading/__init__.py +0 -66
  61. mcli/ml/training/__init__.py +0 -10
  62. mcli/mygroup/__init__.py +0 -3
  63. mcli/public/__init__.py +0 -1
  64. mcli/public/commands/__init__.py +0 -2
  65. mcli/self/__init__.py +0 -3
  66. mcli/workflow/__init__.py +0 -0
  67. mcli/workflow/daemon/__init__.py +0 -15
  68. mcli/workflow/dashboard/__init__.py +0 -5
  69. mcli/workflow/docker/__init__.py +0 -0
  70. mcli/workflow/file/__init__.py +0 -0
  71. mcli/workflow/gcloud/__init__.py +0 -1
  72. mcli/workflow/git_commit/__init__.py +0 -0
  73. mcli/workflow/interview/__init__.py +0 -0
  74. mcli/workflow/politician_trading/__init__.py +0 -4
  75. mcli/workflow/registry/__init__.py +0 -0
  76. mcli/workflow/repo/__init__.py +0 -0
  77. mcli/workflow/scheduler/__init__.py +0 -25
  78. mcli/workflow/search/__init__.py +0 -0
  79. mcli/workflow/sync/__init__.py +0 -5
  80. mcli/workflow/videos/__init__.py +0 -1
  81. mcli/workflow/wakatime/__init__.py +0 -80
  82. /mcli/ml/dashboard/{pages/overview.py → overview.py} +0 -0
  83. {mcli_framework-7.4.0.dist-info → mcli_framework-7.5.1.dist-info}/WHEEL +0 -0
  84. {mcli_framework-7.4.0.dist-info → mcli_framework-7.5.1.dist-info}/entry_points.txt +0 -0
  85. {mcli_framework-7.4.0.dist-info → mcli_framework-7.5.1.dist-info}/licenses/LICENSE +0 -0
  86. {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
- @click.option(
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 of the mcli application.
893
+ [DEPRECATED] Display runtime logs - Use 'mcli logs' instead.
1218
894
 
1219
- Shows the most recent log entries from the application's logging system.
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
- import re
1225
- import subprocess
1226
- from datetime import datetime
1227
- from pathlib import Path
1228
-
1229
- # Import get_logs_dir to get the correct logs directory
1230
- from mcli.lib.paths import get_logs_dir
1231
-
1232
- # Get the logs directory (creates it if it doesn't exist)
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: