mcli-framework 7.1.3__py3-none-any.whl → 7.2.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.

Files changed (38) hide show
  1. mcli/app/main.py +10 -0
  2. mcli/lib/custom_commands.py +424 -0
  3. mcli/lib/paths.py +12 -0
  4. mcli/ml/dashboard/app.py +13 -13
  5. mcli/ml/dashboard/app_integrated.py +1292 -148
  6. mcli/ml/dashboard/app_supabase.py +46 -21
  7. mcli/ml/dashboard/app_training.py +14 -14
  8. mcli/ml/dashboard/components/charts.py +258 -0
  9. mcli/ml/dashboard/components/metrics.py +125 -0
  10. mcli/ml/dashboard/components/tables.py +228 -0
  11. mcli/ml/dashboard/pages/cicd.py +382 -0
  12. mcli/ml/dashboard/pages/predictions_enhanced.py +820 -0
  13. mcli/ml/dashboard/pages/scrapers_and_logs.py +1060 -0
  14. mcli/ml/dashboard/pages/workflows.py +533 -0
  15. mcli/ml/training/train_model.py +569 -0
  16. mcli/self/self_cmd.py +322 -94
  17. mcli/workflow/politician_trading/data_sources.py +259 -1
  18. mcli/workflow/politician_trading/models.py +159 -1
  19. mcli/workflow/politician_trading/scrapers_corporate_registry.py +846 -0
  20. mcli/workflow/politician_trading/scrapers_free_sources.py +516 -0
  21. mcli/workflow/politician_trading/scrapers_third_party.py +391 -0
  22. mcli/workflow/politician_trading/seed_database.py +539 -0
  23. mcli/workflow/workflow.py +8 -27
  24. {mcli_framework-7.1.3.dist-info → mcli_framework-7.2.0.dist-info}/METADATA +1 -1
  25. {mcli_framework-7.1.3.dist-info → mcli_framework-7.2.0.dist-info}/RECORD +29 -25
  26. mcli/workflow/daemon/api_daemon.py +0 -800
  27. mcli/workflow/daemon/commands.py +0 -1196
  28. mcli/workflow/dashboard/dashboard_cmd.py +0 -120
  29. mcli/workflow/file/file.py +0 -100
  30. mcli/workflow/git_commit/commands.py +0 -430
  31. mcli/workflow/politician_trading/commands.py +0 -1939
  32. mcli/workflow/scheduler/commands.py +0 -493
  33. mcli/workflow/sync/sync_cmd.py +0 -437
  34. mcli/workflow/videos/videos.py +0 -242
  35. {mcli_framework-7.1.3.dist-info → mcli_framework-7.2.0.dist-info}/WHEEL +0 -0
  36. {mcli_framework-7.1.3.dist-info → mcli_framework-7.2.0.dist-info}/entry_points.txt +0 -0
  37. {mcli_framework-7.1.3.dist-info → mcli_framework-7.2.0.dist-info}/licenses/LICENSE +0 -0
  38. {mcli_framework-7.1.3.dist-info → mcli_framework-7.2.0.dist-info}/top_level.txt +0 -0
mcli/self/self_cmd.py CHANGED
@@ -30,6 +30,7 @@ except ImportError:
30
30
  process = None
31
31
 
32
32
  from mcli.lib.logger.logger import get_logger
33
+ from mcli.lib.custom_commands import get_command_manager
33
34
 
34
35
  logger = get_logger()
35
36
 
@@ -202,6 +203,7 @@ def get_command_template(name: str, group: Optional[str] = None) -> str:
202
203
 
203
204
  if group:
204
205
  # Template for a command in a group using Click
206
+ # Use 'app' as the variable name so it's found first
205
207
  template = f'''"""
206
208
  {name} command for mcli.{group}.
207
209
  """
@@ -214,11 +216,11 @@ logger = get_logger()
214
216
 
215
217
  # Create a Click command group
216
218
  @click.group(name="{name}")
217
- def {name}_group():
219
+ def app():
218
220
  """Description for {name} command group."""
219
221
  pass
220
222
 
221
- @{name}_group.command("hello")
223
+ @app.command("hello")
222
224
  @click.argument("name", default="World")
223
225
  def hello(name: str):
224
226
  """Example subcommand."""
@@ -387,14 +389,20 @@ def collect_commands() -> List[Dict[str, Any]]:
387
389
 
388
390
  @self_app.command("add-command")
389
391
  @click.argument("command_name", required=True)
390
- @click.option("--group", "-g", help="Optional command group to create under")
391
- def add_command(command_name, group):
392
+ @click.option("--group", "-g", help="Command group (defaults to 'workflow')", default="workflow")
393
+ @click.option(
394
+ "--description", "-d", help="Description for the command", default="Custom command"
395
+ )
396
+ def add_command(command_name, group, description):
392
397
  """
393
- Generate a new command template that can be used by mcli.
398
+ Generate a new portable custom command saved to ~/.mcli/commands/.
399
+
400
+ Commands are automatically nested under the 'workflow' group by default,
401
+ making them portable and persistent across updates.
394
402
 
395
403
  Example:
396
- mcli self add my_command
397
- mcli self add feature_command --group features
404
+ mcli self add-command my_command
405
+ mcli self add-command analytics --group data
398
406
  """
399
407
  command_name = command_name.lower().replace("-", "_")
400
408
 
@@ -409,13 +417,9 @@ def add_command(command_name, group):
409
417
  )
410
418
  return 1
411
419
 
412
- mcli_path = Path(__file__).parent.parent
413
-
420
+ # Validate group name if provided
414
421
  if group:
415
- # Creating under a specific group
416
422
  command_group = group.lower().replace("-", "_")
417
-
418
- # Validate group name
419
423
  if not re.match(r"^[a-z][a-z0-9_]*$", command_group):
420
424
  logger.error(
421
425
  f"Invalid group name: {command_group}. Use lowercase letters, numbers, and underscores (starting with a letter)."
@@ -425,101 +429,325 @@ def add_command(command_name, group):
425
429
  err=True,
426
430
  )
427
431
  return 1
432
+ else:
433
+ command_group = "workflow" # Default to workflow group
428
434
 
429
- # Check if group exists, create if needed
430
- group_path = mcli_path / command_group
431
- if not group_path.exists():
432
- # Create group directory and __init__.py
433
- group_path.mkdir(parents=True, exist_ok=True)
434
- with open(group_path / "__init__.py", "w") as f:
435
- f.write(f'"""\n{command_group.capitalize()} commands for mcli.\n"""')
436
- logger.info(f"Created new command group directory: {command_group}")
437
- click.echo(f"Created new command group directory: {command_group}")
438
-
439
- # Create command file
440
- command_file_path = group_path / f"{command_name}.py"
441
- if command_file_path.exists():
442
- logger.warning(f"Command file already exists: {command_file_path}")
443
- should_override = Prompt.ask(
444
- "File already exists. Override?", choices=["y", "n"], default="n"
445
- )
446
- if should_override.lower() != "y":
447
- logger.info("Command creation aborted.")
448
- click.echo("Command creation aborted.")
449
- return 1
450
-
451
- # Generate command file
452
- with open(command_file_path, "w") as f:
453
- f.write(get_command_template(command_name, command_group))
454
-
455
- logger.info(f"Created new command: {command_name} in group: {command_group}")
456
- click.echo(f"Created new command: {command_name} in group: {command_group}")
457
- click.echo(f"File created: {command_file_path}")
435
+ # Get the command manager
436
+ manager = get_command_manager()
437
+
438
+ # Check if command already exists
439
+ command_file = manager.commands_dir / f"{command_name}.json"
440
+ if command_file.exists():
441
+ logger.warning(f"Custom command already exists: {command_name}")
442
+ should_override = Prompt.ask(
443
+ "Command already exists. Override?", choices=["y", "n"], default="n"
444
+ )
445
+ if should_override.lower() != "y":
446
+ logger.info("Command creation aborted.")
447
+ click.echo("Command creation aborted.")
448
+ return 1
449
+
450
+ # Generate command code
451
+ code = get_command_template(command_name, command_group)
452
+
453
+ # Save the command
454
+ saved_path = manager.save_command(
455
+ name=command_name,
456
+ code=code,
457
+ description=description,
458
+ group=command_group,
459
+ )
460
+
461
+ logger.info(f"Created portable custom command: {command_name}")
462
+ click.echo(f"Created portable custom command: {command_name}")
463
+ click.echo(f"📁 Saved to: {saved_path}")
464
+ click.echo(f"🔄 Command will be automatically loaded on next mcli startup")
465
+ click.echo(
466
+ f"💡 You can share this command by copying {saved_path} to another machine's ~/.mcli/commands/ directory"
467
+ )
468
+
469
+ return 0
470
+
471
+
472
+ @self_app.command("list-commands")
473
+ def list_commands():
474
+ """
475
+ List all custom commands stored in ~/.mcli/commands/.
476
+ """
477
+ manager = get_command_manager()
478
+ commands = manager.load_all_commands()
479
+
480
+ if not commands:
481
+ click.echo("No custom commands found.")
458
482
  click.echo(
459
- f"To use this command, add 'from mcli.{command_group}.{command_name} import {command_name}_group' to your main imports"
483
+ f"Create one with: mcli self add-command <name>"
460
484
  )
485
+ return 0
486
+
487
+ table = Table(title="Custom Commands")
488
+ table.add_column("Name", style="green")
489
+ table.add_column("Group", style="blue")
490
+ table.add_column("Description", style="yellow")
491
+ table.add_column("Version", style="cyan")
492
+ table.add_column("Updated", style="dim")
493
+
494
+ for cmd in commands:
495
+ table.add_row(
496
+ cmd["name"],
497
+ cmd.get("group", "-"),
498
+ cmd.get("description", ""),
499
+ cmd.get("version", "1.0"),
500
+ cmd.get("updated_at", "")[:10] if cmd.get("updated_at") else "-",
501
+ )
502
+
503
+ console.print(table)
504
+ click.echo(f"\n📁 Commands directory: {manager.commands_dir}")
505
+ click.echo(f"🔒 Lockfile: {manager.lockfile_path}")
506
+
507
+ return 0
508
+
509
+
510
+ @self_app.command("remove-command")
511
+ @click.argument("command_name", required=True)
512
+ @click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompt")
513
+ def remove_command(command_name, yes):
514
+ """
515
+ Remove a custom command from ~/.mcli/commands/.
516
+ """
517
+ manager = get_command_manager()
518
+ command_file = manager.commands_dir / f"{command_name}.json"
519
+
520
+ if not command_file.exists():
521
+ click.echo(f"❌ Command '{command_name}' not found.", err=True)
522
+ return 1
523
+
524
+ if not yes:
525
+ should_delete = Prompt.ask(
526
+ f"Delete command '{command_name}'?", choices=["y", "n"], default="n"
527
+ )
528
+ if should_delete.lower() != "y":
529
+ click.echo("Deletion cancelled.")
530
+ return 0
531
+
532
+ if manager.delete_command(command_name):
533
+ click.echo(f"✅ Deleted custom command: {command_name}")
534
+ return 0
535
+ else:
536
+ click.echo(f"❌ Failed to delete command: {command_name}", err=True)
537
+ return 1
538
+
539
+
540
+ @self_app.command("export-commands")
541
+ @click.argument("export_file", type=click.Path(), required=False)
542
+ def export_commands(export_file):
543
+ """
544
+ Export all custom commands to a JSON file.
545
+
546
+ If no file is specified, exports to commands-export.json in current directory.
547
+ """
548
+ manager = get_command_manager()
549
+
550
+ if not export_file:
551
+ export_file = "commands-export.json"
552
+
553
+ export_path = Path(export_file)
554
+
555
+ if manager.export_commands(export_path):
556
+ click.echo(f"✅ Exported custom commands to: {export_path}")
557
+ click.echo(
558
+ f"💡 Import on another machine with: mcli self import-commands {export_path}"
559
+ )
560
+ return 0
561
+ else:
562
+ click.echo("❌ Failed to export commands.", err=True)
563
+ return 1
564
+
565
+
566
+ @self_app.command("import-commands")
567
+ @click.argument("import_file", type=click.Path(exists=True), required=True)
568
+ @click.option("--overwrite", is_flag=True, help="Overwrite existing commands")
569
+ def import_commands(import_file, overwrite):
570
+ """
571
+ Import custom commands from a JSON file.
572
+ """
573
+ manager = get_command_manager()
574
+ import_path = Path(import_file)
575
+
576
+ results = manager.import_commands(import_path, overwrite=overwrite)
577
+
578
+ success_count = sum(1 for v in results.values() if v)
579
+ failed_count = len(results) - success_count
580
+
581
+ if success_count > 0:
582
+ click.echo(f"✅ Imported {success_count} command(s)")
583
+
584
+ if failed_count > 0:
461
585
  click.echo(
462
- f"Then add '{command_name}_group to your main CLI group using app.add_command({command_name}_group)'"
586
+ f"⚠️ Skipped {failed_count} command(s) (already exist, use --overwrite to replace)"
463
587
  )
588
+ click.echo("Skipped commands:")
589
+ for name, success in results.items():
590
+ if not success:
591
+ click.echo(f" - {name}")
592
+
593
+ return 0
594
+
595
+
596
+ @self_app.command("verify-commands")
597
+ def verify_commands():
598
+ """
599
+ Verify that custom commands match the lockfile.
600
+ """
601
+ manager = get_command_manager()
602
+
603
+ # First, ensure lockfile is up to date
604
+ manager.update_lockfile()
605
+
606
+ verification = manager.verify_lockfile()
607
+
608
+ if verification["valid"]:
609
+ click.echo("✅ All custom commands are in sync with the lockfile.")
610
+ return 0
611
+
612
+ click.echo("⚠️ Commands are out of sync with the lockfile:\n")
613
+
614
+ if verification["missing"]:
615
+ click.echo(f"Missing commands (in lockfile but not found):")
616
+ for name in verification["missing"]:
617
+ click.echo(f" - {name}")
618
+
619
+ if verification["extra"]:
620
+ click.echo(f"\nExtra commands (not in lockfile):")
621
+ for name in verification["extra"]:
622
+ click.echo(f" - {name}")
464
623
 
624
+ if verification["modified"]:
625
+ click.echo(f"\nModified commands:")
626
+ for name in verification["modified"]:
627
+ click.echo(f" - {name}")
628
+
629
+ click.echo(f"\n💡 Run 'mcli self update-lockfile' to sync the lockfile")
630
+
631
+ return 1
632
+
633
+
634
+ @self_app.command("update-lockfile")
635
+ def update_lockfile():
636
+ """
637
+ Update the commands lockfile with current state.
638
+ """
639
+ manager = get_command_manager()
640
+
641
+ if manager.update_lockfile():
642
+ click.echo(f"✅ Updated lockfile: {manager.lockfile_path}")
643
+ return 0
465
644
  else:
466
- # Creating directly under self
467
- command_file_path = mcli_path / "self" / f"{command_name}.py"
645
+ click.echo("❌ Failed to update lockfile.", err=True)
646
+ return 1
468
647
 
469
- if command_file_path.exists():
470
- logger.warning(f"Command file already exists: {command_file_path}")
471
- should_override = Prompt.ask(
472
- "File already exists. Override?", choices=["y", "n"], default="n"
473
- )
474
- if should_override.lower() != "y":
475
- logger.info("Command creation aborted.")
476
- click.echo("Command creation aborted.")
477
- return 1
478
-
479
- # Generate command file
480
- with open(command_file_path, "w") as f:
481
- f.write(get_command_template(command_name))
482
-
483
- # Update self_cmd.py to import and register the new command
484
- with open(Path(__file__), "r") as f:
485
- content = f.read()
486
-
487
- # Add import statement if not exists
488
- import_statement = f"from mcli.self.{command_name} import {command_name}_command"
489
- if import_statement not in content:
490
- import_section_end = content.find("logger = get_logger()")
491
- if import_section_end != -1:
492
- updated_content = (
493
- content[:import_section_end]
494
- + import_statement
495
- + "\n"
496
- + content[import_section_end:]
497
- )
498
648
 
499
- # Add command registration (Click syntax)
500
- registration = f"@self_app.command('{command_name}')\ndef {command_name}(name=\"World\"):\n return {command_name}_command(name)\n"
501
- registration_point = updated_content.rfind("def ")
502
- if registration_point != -1:
503
- # Find the end of the last function
504
- last_func_end = updated_content.find("\n\n", registration_point)
505
- if last_func_end != -1:
506
- updated_content = (
507
- updated_content[: last_func_end + 2]
508
- + registration
509
- + updated_content[last_func_end + 2 :]
510
- )
649
+ @self_app.command("extract-workflow-commands")
650
+ @click.option(
651
+ "--output", "-o", type=click.Path(), help="Output file (default: workflow-commands.json)"
652
+ )
653
+ def extract_workflow_commands(output):
654
+ """
655
+ Extract workflow commands from Python modules to JSON format.
656
+
657
+ This command helps migrate existing workflow commands to portable JSON format.
658
+ """
659
+ import inspect
660
+ from pathlib import Path
661
+
662
+ output_file = Path(output) if output else Path("workflow-commands.json")
663
+
664
+ workflow_commands = []
665
+
666
+ # Try to get workflow from the main app
667
+ try:
668
+ from mcli.app.main import create_app
669
+
670
+ app = create_app()
671
+
672
+ # Check if workflow group exists
673
+ if "workflow" in app.commands:
674
+ workflow_group = app.commands["workflow"]
675
+
676
+ # Force load lazy group if needed
677
+ if hasattr(workflow_group, "_load_group"):
678
+ workflow_group = workflow_group._load_group()
679
+
680
+ if hasattr(workflow_group, "commands"):
681
+ for cmd_name, cmd_obj in workflow_group.commands.items():
682
+ # Extract command information
683
+ command_info = {
684
+ "name": cmd_name,
685
+ "group": "workflow",
686
+ "description": cmd_obj.help or "Workflow command",
687
+ "version": "1.0",
688
+ "metadata": {"source": "workflow", "migrated": True},
689
+ }
690
+
691
+ # Create a template based on command type
692
+ # Replace hyphens with underscores for valid Python function names
693
+ safe_name = cmd_name.replace("-", "_")
694
+
695
+ if isinstance(cmd_obj, click.Group):
696
+ # For groups, create a template
697
+ command_info["code"] = f'''"""
698
+ {cmd_name} workflow command.
699
+ """
700
+ import click
701
+
702
+ @click.group(name="{cmd_name}")
703
+ def app():
704
+ """{cmd_obj.help or 'Workflow command group'}"""
705
+ pass
706
+
707
+ # Add your subcommands here
708
+ '''
511
709
  else:
512
- updated_content += "\n\n" + registration
710
+ # For regular commands, create a template
711
+ command_info["code"] = f'''"""
712
+ {cmd_name} workflow command.
713
+ """
714
+ import click
513
715
 
514
- with open(Path(__file__), "w") as f:
515
- f.write(updated_content)
716
+ @click.command(name="{cmd_name}")
717
+ def app():
718
+ """{cmd_obj.help or 'Workflow command'}"""
719
+ click.echo("Workflow command: {cmd_name}")
720
+ # Add your implementation here
721
+ '''
516
722
 
517
- logger.info(f"Created new command: {command_name} in self module")
518
- click.echo(f"Created new command: {command_name} in self module")
519
- click.echo(f"File created: {command_file_path}")
520
- click.echo(f"Command has been automatically registered with self_app")
723
+ workflow_commands.append(command_info)
521
724
 
522
- return 0
725
+ if workflow_commands:
726
+ import json
727
+
728
+ with open(output_file, "w") as f:
729
+ json.dump(workflow_commands, f, indent=2)
730
+
731
+ click.echo(f"✅ Extracted {len(workflow_commands)} workflow commands")
732
+ click.echo(f"📁 Saved to: {output_file}")
733
+ click.echo(
734
+ f"\n💡 These are templates. Import with: mcli self import-commands {output_file}"
735
+ )
736
+ click.echo(
737
+ " Then customize the code in ~/.mcli/commands/<command>.json"
738
+ )
739
+ return 0
740
+ else:
741
+ click.echo("⚠️ No workflow commands found to extract")
742
+ return 1
743
+
744
+ except Exception as e:
745
+ logger.error(f"Failed to extract workflow commands: {e}")
746
+ click.echo(f"❌ Failed to extract workflow commands: {e}", err=True)
747
+ import traceback
748
+
749
+ click.echo(traceback.format_exc(), err=True)
750
+ return 1
523
751
 
524
752
 
525
753
  @click.group("plugin")