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.
- mcli/app/main.py +10 -0
- mcli/lib/custom_commands.py +424 -0
- mcli/lib/paths.py +12 -0
- mcli/ml/dashboard/app.py +13 -13
- mcli/ml/dashboard/app_integrated.py +1292 -148
- mcli/ml/dashboard/app_supabase.py +46 -21
- mcli/ml/dashboard/app_training.py +14 -14
- mcli/ml/dashboard/components/charts.py +258 -0
- mcli/ml/dashboard/components/metrics.py +125 -0
- mcli/ml/dashboard/components/tables.py +228 -0
- mcli/ml/dashboard/pages/cicd.py +382 -0
- mcli/ml/dashboard/pages/predictions_enhanced.py +820 -0
- mcli/ml/dashboard/pages/scrapers_and_logs.py +1060 -0
- mcli/ml/dashboard/pages/workflows.py +533 -0
- mcli/ml/training/train_model.py +569 -0
- mcli/self/self_cmd.py +322 -94
- mcli/workflow/politician_trading/data_sources.py +259 -1
- mcli/workflow/politician_trading/models.py +159 -1
- mcli/workflow/politician_trading/scrapers_corporate_registry.py +846 -0
- mcli/workflow/politician_trading/scrapers_free_sources.py +516 -0
- mcli/workflow/politician_trading/scrapers_third_party.py +391 -0
- mcli/workflow/politician_trading/seed_database.py +539 -0
- mcli/workflow/workflow.py +8 -27
- {mcli_framework-7.1.3.dist-info → mcli_framework-7.2.0.dist-info}/METADATA +1 -1
- {mcli_framework-7.1.3.dist-info → mcli_framework-7.2.0.dist-info}/RECORD +29 -25
- mcli/workflow/daemon/api_daemon.py +0 -800
- mcli/workflow/daemon/commands.py +0 -1196
- mcli/workflow/dashboard/dashboard_cmd.py +0 -120
- mcli/workflow/file/file.py +0 -100
- mcli/workflow/git_commit/commands.py +0 -430
- mcli/workflow/politician_trading/commands.py +0 -1939
- mcli/workflow/scheduler/commands.py +0 -493
- mcli/workflow/sync/sync_cmd.py +0 -437
- mcli/workflow/videos/videos.py +0 -242
- {mcli_framework-7.1.3.dist-info → mcli_framework-7.2.0.dist-info}/WHEEL +0 -0
- {mcli_framework-7.1.3.dist-info → mcli_framework-7.2.0.dist-info}/entry_points.txt +0 -0
- {mcli_framework-7.1.3.dist-info → mcli_framework-7.2.0.dist-info}/licenses/LICENSE +0 -0
- {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
|
|
219
|
+
def app():
|
|
218
220
|
"""Description for {name} command group."""
|
|
219
221
|
pass
|
|
220
222
|
|
|
221
|
-
@
|
|
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="
|
|
391
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
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"
|
|
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"
|
|
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
|
-
|
|
467
|
-
|
|
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
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
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
|
-
|
|
710
|
+
# For regular commands, create a template
|
|
711
|
+
command_info["code"] = f'''"""
|
|
712
|
+
{cmd_name} workflow command.
|
|
713
|
+
"""
|
|
714
|
+
import click
|
|
513
715
|
|
|
514
|
-
|
|
515
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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")
|