mcli-framework 7.3.1__py3-none-any.whl → 7.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of mcli-framework might be problematic. Click here for more details.

@@ -1,6 +1,7 @@
1
1
  """Alpaca Trading API client for executing trades"""
2
2
 
3
3
  import logging
4
+ import os
4
5
  from datetime import datetime, timedelta
5
6
  from decimal import Decimal
6
7
  from typing import Dict, List, Optional, Tuple, Union
@@ -18,6 +19,10 @@ from alpaca.data.historical import StockHistoricalDataClient
18
19
  from alpaca.data.requests import StockBarsRequest
19
20
  from alpaca.data.timeframe import TimeFrame
20
21
  from pydantic import BaseModel, Field
22
+ from dotenv import load_dotenv
23
+
24
+ # Load environment variables
25
+ load_dotenv()
21
26
 
22
27
  logger = logging.getLogger(__name__)
23
28
 
@@ -81,32 +86,44 @@ class AlpacaTradingClient:
81
86
  self.trading_client = TradingClient(
82
87
  api_key=config.api_key,
83
88
  secret_key=config.secret_key,
84
- paper=config.paper_trading,
85
- base_url=config.base_url
89
+ paper=config.paper_trading
86
90
  )
87
91
  self.data_client = StockHistoricalDataClient(
88
92
  api_key=config.api_key,
89
- secret_key=config.secret_key,
90
- base_url=config.data_url
93
+ secret_key=config.secret_key
91
94
  )
92
95
 
93
96
  def get_account(self) -> Dict:
94
97
  """Get account information"""
95
98
  try:
96
99
  account = self.trading_client.get_account()
97
- return {
100
+
101
+ # Build response with safe attribute access
102
+ response = {
98
103
  "account_id": account.id,
99
- "equity": float(account.equity),
100
- "cash": float(account.cash),
101
- "buying_power": float(account.buying_power),
102
- "portfolio_value": float(account.portfolio_value),
103
- "unrealized_pl": float(account.unrealized_pl),
104
- "realized_pl": float(account.realized_pl),
105
- "currency": account.currency,
106
- "status": account.status,
107
- "trading_blocked": account.trading_blocked,
108
- "pattern_day_trader": account.pattern_day_trader,
104
+ "equity": float(account.equity) if hasattr(account, 'equity') else 0.0,
105
+ "cash": float(account.cash) if hasattr(account, 'cash') else 0.0,
106
+ "buying_power": float(account.buying_power) if hasattr(account, 'buying_power') else 0.0,
107
+ "currency": account.currency if hasattr(account, 'currency') else "USD",
108
+ "status": account.status.value if hasattr(account.status, 'value') else str(account.status),
109
+ "trading_blocked": account.trading_blocked if hasattr(account, 'trading_blocked') else False,
110
+ "pattern_day_trader": account.pattern_day_trader if hasattr(account, 'pattern_day_trader') else False,
109
111
  }
112
+
113
+ # Add optional fields that may not exist in all account types
114
+ if hasattr(account, 'portfolio_value'):
115
+ response["portfolio_value"] = float(account.portfolio_value)
116
+ else:
117
+ response["portfolio_value"] = response["equity"]
118
+
119
+ if hasattr(account, 'long_market_value'):
120
+ response["unrealized_pl"] = float(account.long_market_value) - float(account.cash)
121
+ else:
122
+ response["unrealized_pl"] = 0.0
123
+
124
+ response["realized_pl"] = 0.0 # Not always available in paper accounts
125
+
126
+ return response
110
127
  except Exception as e:
111
128
  logger.error(f"Failed to get account info: {e}")
112
129
  raise
@@ -343,11 +360,58 @@ class AlpacaTradingClient:
343
360
  raise
344
361
 
345
362
 
346
- def create_trading_client(api_key: str, secret_key: str, paper_trading: bool = True) -> AlpacaTradingClient:
347
- """Create a trading client with the given credentials"""
363
+ def create_trading_client(api_key: str = None, secret_key: str = None, paper_trading: bool = True) -> AlpacaTradingClient:
364
+ """
365
+ Create a trading client with the given credentials or from environment variables
366
+
367
+ Args:
368
+ api_key: Alpaca API key (if None, loads from ALPACA_API_KEY env var)
369
+ secret_key: Alpaca secret key (if None, loads from ALPACA_SECRET_KEY env var)
370
+ paper_trading: Whether to use paper trading (default: True)
371
+
372
+ Returns:
373
+ AlpacaTradingClient instance
374
+ """
375
+ # Load from environment if not provided
376
+ if api_key is None:
377
+ api_key = os.getenv("ALPACA_API_KEY")
378
+ if secret_key is None:
379
+ secret_key = os.getenv("ALPACA_SECRET_KEY")
380
+
381
+ if not api_key or not secret_key:
382
+ raise ValueError(
383
+ "Alpaca API credentials not found. "
384
+ "Please provide api_key and secret_key, or set ALPACA_API_KEY and ALPACA_SECRET_KEY environment variables."
385
+ )
386
+
387
+ base_url = os.getenv("ALPACA_BASE_URL", "https://paper-api.alpaca.markets")
388
+
348
389
  config = TradingConfig(
349
390
  api_key=api_key,
350
391
  secret_key=secret_key,
392
+ base_url=base_url,
351
393
  paper_trading=paper_trading
352
394
  )
353
- return AlpacaTradingClient(config)
395
+ return AlpacaTradingClient(config)
396
+
397
+
398
+ def get_alpaca_config_from_env() -> Optional[Dict[str, str]]:
399
+ """
400
+ Get Alpaca configuration from environment variables
401
+
402
+ Returns:
403
+ Dictionary with API configuration or None if not configured
404
+ """
405
+ api_key = os.getenv("ALPACA_API_KEY")
406
+ secret_key = os.getenv("ALPACA_SECRET_KEY")
407
+ base_url = os.getenv("ALPACA_BASE_URL", "https://paper-api.alpaca.markets")
408
+
409
+ if not api_key or not secret_key:
410
+ return None
411
+
412
+ return {
413
+ "api_key": api_key,
414
+ "secret_key": secret_key,
415
+ "base_url": base_url,
416
+ "is_paper": "paper" in base_url.lower()
417
+ }
mcli/self/self_cmd.py CHANGED
@@ -344,8 +344,38 @@ def collect_commands() -> List[Dict[str, Any]]:
344
344
  module_name = ".".join(relative_path.with_suffix("").parts)
345
345
 
346
346
  try:
347
- # Try to import the module
348
- module = importlib.import_module(module_name)
347
+ # Suppress Streamlit warnings and logging during module import
348
+ import warnings
349
+ import logging
350
+ import sys
351
+ import os
352
+ from contextlib import redirect_stderr
353
+ from io import StringIO
354
+
355
+ # Suppress Python warnings
356
+ with warnings.catch_warnings():
357
+ warnings.filterwarnings("ignore", message=".*missing ScriptRunContext.*")
358
+ warnings.filterwarnings("ignore", message=".*No runtime found.*")
359
+ warnings.filterwarnings("ignore", message=".*Session state does not function.*")
360
+ warnings.filterwarnings("ignore", message=".*to view this Streamlit app.*")
361
+
362
+ # Suppress Streamlit logger warnings
363
+ streamlit_logger = logging.getLogger("streamlit")
364
+ original_level = streamlit_logger.level
365
+ streamlit_logger.setLevel(logging.CRITICAL)
366
+
367
+ # Also suppress specific Streamlit sub-loggers
368
+ logging.getLogger("streamlit.runtime.scriptrunner_utils.script_run_context").setLevel(logging.CRITICAL)
369
+ logging.getLogger("streamlit.runtime.caching.cache_data_api").setLevel(logging.CRITICAL)
370
+
371
+ # Redirect stderr to suppress Streamlit warnings
372
+ with redirect_stderr(StringIO()):
373
+ try:
374
+ # Try to import the module
375
+ module = importlib.import_module(module_name)
376
+ finally:
377
+ # Restore original logging level
378
+ streamlit_logger.setLevel(original_level)
349
379
 
350
380
  # Extract command and group objects
351
381
  for name, obj in inspect.getmembers(module):
@@ -387,23 +417,177 @@ def collect_commands() -> List[Dict[str, Any]]:
387
417
  return commands
388
418
 
389
419
 
390
- @self_app.command("add-command")
420
+ def open_editor_for_command(command_name: str, command_group: str, description: str) -> Optional[str]:
421
+ """
422
+ Open the user's default editor to allow them to write command logic.
423
+
424
+ Args:
425
+ command_name: Name of the command
426
+ command_group: Group for the command
427
+ description: Description of the command
428
+
429
+ Returns:
430
+ The Python code written by the user, or None if cancelled
431
+ """
432
+ import tempfile
433
+ import subprocess
434
+ import os
435
+ import sys
436
+ from pathlib import Path
437
+
438
+ # Get the user's default editor
439
+ editor = os.environ.get('EDITOR')
440
+ if not editor:
441
+ # Try common editors in order of preference
442
+ for common_editor in ['vim', 'nano', 'code', 'subl', 'atom', 'emacs']:
443
+ if subprocess.run(['which', common_editor], capture_output=True).returncode == 0:
444
+ editor = common_editor
445
+ break
446
+
447
+ if not editor:
448
+ click.echo("❌ No editor found. Please set the EDITOR environment variable or install vim/nano.")
449
+ return None
450
+
451
+ # Create a temporary file with the template
452
+ template = get_command_template(command_name, command_group)
453
+
454
+ # Add helpful comments to the template
455
+ enhanced_template = f'''"""
456
+ {command_name} command for mcli.{command_group}.
457
+
458
+ Description: {description}
459
+
460
+ Instructions:
461
+ 1. Write your Python command logic below
462
+ 2. Use Click decorators for command definition
463
+ 3. Save and close the editor to create the command
464
+ 4. The command will be automatically converted to JSON format
465
+
466
+ Example Click command structure:
467
+ @click.command()
468
+ @click.argument('name', default='World')
469
+ def my_command(name):
470
+ \"\"\"My custom command.\"\"\"
471
+ click.echo(f"Hello, {{name}}!")
472
+ """
473
+ import click
474
+ from typing import Optional, List
475
+ from pathlib import Path
476
+ from mcli.lib.logger.logger import get_logger
477
+
478
+ logger = get_logger()
479
+
480
+ # Write your command logic here:
481
+ # Replace this template with your actual command implementation
482
+
483
+ {template.split('"""')[2].split('"""')[0] if '"""' in template else ''}
484
+
485
+ # Your command implementation goes here:
486
+ # Example:
487
+ # @click.command()
488
+ # @click.argument('name', default='World')
489
+ # def {command_name}_command(name):
490
+ # \"\"\"{description}\"\"\"
491
+ # logger.info(f"Executing {command_name} command with name: {{name}}")
492
+ # click.echo(f"Hello, {{name}}! This is the {command_name} command.")
493
+ '''
494
+
495
+ # Create temporary file
496
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as temp_file:
497
+ temp_file.write(enhanced_template)
498
+ temp_file_path = temp_file.name
499
+
500
+ try:
501
+ # Check if we're in an interactive environment
502
+ if not sys.stdin.isatty() or not sys.stdout.isatty():
503
+ click.echo("❌ Editor requires an interactive terminal. Use --template flag for non-interactive mode.")
504
+ return None
505
+
506
+ # Open editor
507
+ click.echo(f"📝 Opening {editor} to edit command logic...")
508
+ click.echo("💡 Write your Python command logic and save the file to continue.")
509
+ click.echo("💡 Press Ctrl+C to cancel command creation.")
510
+
511
+ # Run the editor
512
+ result = subprocess.run([editor, temp_file_path], check=False)
513
+
514
+ if result.returncode != 0:
515
+ click.echo("❌ Editor exited with error. Command creation cancelled.")
516
+ return None
517
+
518
+ # Read the edited content
519
+ with open(temp_file_path, 'r') as f:
520
+ edited_code = f.read()
521
+
522
+ # Check if the file was actually edited (not just the template)
523
+ if edited_code.strip() == enhanced_template.strip():
524
+ click.echo("⚠️ No changes detected. Command creation cancelled.")
525
+ return None
526
+
527
+ # Extract the actual command code (remove the instructions)
528
+ lines = edited_code.split('\n')
529
+ code_lines = []
530
+ in_code_section = False
531
+
532
+ for line in lines:
533
+ if line.strip().startswith('# Your command implementation goes here:'):
534
+ in_code_section = True
535
+ continue
536
+ if in_code_section:
537
+ code_lines.append(line)
538
+
539
+ if not code_lines or not any(line.strip() for line in code_lines):
540
+ # Fallback: use the entire file content
541
+ code_lines = lines
542
+
543
+ final_code = '\n'.join(code_lines).strip()
544
+
545
+ if not final_code:
546
+ click.echo("❌ No command code found. Command creation cancelled.")
547
+ return None
548
+
549
+ click.echo("✅ Command code captured successfully!")
550
+ return final_code
551
+
552
+ except KeyboardInterrupt:
553
+ click.echo("\n❌ Command creation cancelled by user.")
554
+ return None
555
+ except Exception as e:
556
+ click.echo(f"❌ Error opening editor: {e}")
557
+ return None
558
+ finally:
559
+ # Clean up temporary file
560
+ try:
561
+ os.unlink(temp_file_path)
562
+ except OSError:
563
+ pass
564
+
565
+
566
+ @self_app.command("add-command", deprecated=True)
391
567
  @click.argument("command_name", required=True)
392
568
  @click.option("--group", "-g", help="Command group (defaults to 'workflow')", default="workflow")
393
569
  @click.option(
394
570
  "--description", "-d", help="Description for the command", default="Custom command"
395
571
  )
396
- def add_command(command_name, group, description):
572
+ @click.option(
573
+ "--template", "-t", is_flag=True, help="Use template mode (skip editor and use predefined template)"
574
+ )
575
+ def add_command(command_name, group, description, template):
397
576
  """
577
+ [DEPRECATED] Use 'mcli commands add' instead.
578
+
398
579
  Generate a new portable custom command saved to ~/.mcli/commands/.
399
580
 
400
- Commands are automatically nested under the 'workflow' group by default,
401
- making them portable and persistent across updates.
581
+ This command has been moved to 'mcli commands add' for better organization.
582
+ Please use the new command going forward.
402
583
 
403
584
  Example:
404
- mcli self add-command my_command
405
- mcli self add-command analytics --group data
585
+ mcli commands add my_command
586
+ mcli commands add analytics --group data
587
+ mcli commands add quick_cmd --template # Use template without editor
406
588
  """
589
+ click.echo("[yellow]⚠️ DEPRECATED: This command has been moved to 'mcli commands add'[/yellow]")
590
+ click.echo("[yellow] Please use: mcli commands add {} [options][/yellow]\n".format(command_name))
407
591
  command_name = command_name.lower().replace("-", "_")
408
592
 
409
593
  # Validate command name
@@ -448,7 +632,17 @@ def add_command(command_name, group, description):
448
632
  return 1
449
633
 
450
634
  # Generate command code
451
- code = get_command_template(command_name, command_group)
635
+ if template:
636
+ # Use template mode - generate and save directly
637
+ code = get_command_template(command_name, command_group)
638
+ click.echo(f"📝 Using template for command: {command_name}")
639
+ else:
640
+ # Editor mode - open editor for user to write code
641
+ click.echo(f"🔧 Opening editor for command: {command_name}")
642
+ code = open_editor_for_command(command_name, command_group, description)
643
+ if code is None:
644
+ click.echo("❌ Command creation cancelled.")
645
+ return 1
452
646
 
453
647
  # Save the command
454
648
  saved_path = manager.save_command(
@@ -469,11 +663,16 @@ def add_command(command_name, group, description):
469
663
  return 0
470
664
 
471
665
 
472
- @self_app.command("list-commands")
666
+ @self_app.command("list-commands", deprecated=True)
473
667
  def list_commands():
474
668
  """
669
+ [DEPRECATED] Use 'mcli commands list-custom' instead.
670
+
475
671
  List all custom commands stored in ~/.mcli/commands/.
476
672
  """
673
+ click.echo("[yellow]⚠️ DEPRECATED: This command has been moved to 'mcli commands list-custom'[/yellow]")
674
+ click.echo("[yellow] Please use: mcli commands list-custom[/yellow]\n")
675
+
477
676
  manager = get_command_manager()
478
677
  commands = manager.load_all_commands()
479
678
 
@@ -507,13 +706,18 @@ def list_commands():
507
706
  return 0
508
707
 
509
708
 
510
- @self_app.command("remove-command")
709
+ @self_app.command("remove-command", deprecated=True)
511
710
  @click.argument("command_name", required=True)
512
711
  @click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompt")
513
712
  def remove_command(command_name, yes):
514
713
  """
714
+ [DEPRECATED] Use 'mcli commands remove' instead.
715
+
515
716
  Remove a custom command from ~/.mcli/commands/.
516
717
  """
718
+ click.echo("[yellow]⚠️ DEPRECATED: This command has been moved to 'mcli commands remove'[/yellow]")
719
+ click.echo(f"[yellow] Please use: mcli commands remove {command_name}[/yellow]\n")
720
+
517
721
  manager = get_command_manager()
518
722
  command_file = manager.commands_dir / f"{command_name}.json"
519
723
 
@@ -537,14 +741,19 @@ def remove_command(command_name, yes):
537
741
  return 1
538
742
 
539
743
 
540
- @self_app.command("export-commands")
744
+ @self_app.command("export-commands", deprecated=True)
541
745
  @click.argument("export_file", type=click.Path(), required=False)
542
746
  def export_commands(export_file):
543
747
  """
748
+ [DEPRECATED] Use 'mcli commands export' instead.
749
+
544
750
  Export all custom commands to a JSON file.
545
751
 
546
752
  If no file is specified, exports to commands-export.json in current directory.
547
753
  """
754
+ click.echo("[yellow]⚠️ DEPRECATED: This command has been moved to 'mcli commands export'[/yellow]")
755
+ click.echo(f"[yellow] Please use: mcli commands export {export_file or '<file>'}[/yellow]\n")
756
+
548
757
  manager = get_command_manager()
549
758
 
550
759
  if not export_file:
@@ -563,13 +772,18 @@ def export_commands(export_file):
563
772
  return 1
564
773
 
565
774
 
566
- @self_app.command("import-commands")
775
+ @self_app.command("import-commands", deprecated=True)
567
776
  @click.argument("import_file", type=click.Path(exists=True), required=True)
568
777
  @click.option("--overwrite", is_flag=True, help="Overwrite existing commands")
569
778
  def import_commands(import_file, overwrite):
570
779
  """
780
+ [DEPRECATED] Use 'mcli commands import' instead.
781
+
571
782
  Import custom commands from a JSON file.
572
783
  """
784
+ click.echo("[yellow]⚠️ DEPRECATED: This command has been moved to 'mcli commands import'[/yellow]")
785
+ click.echo(f"[yellow] Please use: mcli commands import {import_file}[/yellow]\n")
786
+
573
787
  manager = get_command_manager()
574
788
  import_path = Path(import_file)
575
789
 
@@ -593,11 +807,16 @@ def import_commands(import_file, overwrite):
593
807
  return 0
594
808
 
595
809
 
596
- @self_app.command("verify-commands")
810
+ @self_app.command("verify-commands", deprecated=True)
597
811
  def verify_commands():
598
812
  """
813
+ [DEPRECATED] Use 'mcli commands verify' instead.
814
+
599
815
  Verify that custom commands match the lockfile.
600
816
  """
817
+ click.echo("[yellow]⚠️ DEPRECATED: This command has been moved to 'mcli commands verify'[/yellow]")
818
+ click.echo("[yellow] Please use: mcli commands verify[/yellow]\n")
819
+
601
820
  manager = get_command_manager()
602
821
 
603
822
  # First, ensure lockfile is up to date
@@ -631,11 +850,16 @@ def verify_commands():
631
850
  return 1
632
851
 
633
852
 
634
- @self_app.command("update-lockfile")
853
+ @self_app.command("update-lockfile", deprecated=True)
635
854
  def update_lockfile():
636
855
  """
856
+ [DEPRECATED] Use 'mcli commands update-lockfile' instead.
857
+
637
858
  Update the commands lockfile with current state.
638
859
  """
860
+ click.echo("[yellow]⚠️ DEPRECATED: This command has been moved to 'mcli commands update-lockfile'[/yellow]")
861
+ click.echo("[yellow] Please use: mcli commands update-lockfile[/yellow]\n")
862
+
639
863
  manager = get_command_manager()
640
864
 
641
865
  if manager.update_lockfile():
@@ -1557,7 +1781,7 @@ def update(check: bool, pre: bool, yes: bool, skip_ci_check: bool):
1557
1781
  console.print(f"[dim]{traceback.format_exc()}[/dim]")
1558
1782
 
1559
1783
 
1560
- @self_app.command("import-script")
1784
+ @self_app.command("import-script", deprecated=True)
1561
1785
  @click.argument("script_path", type=click.Path(exists=True))
1562
1786
  @click.option("--name", "-n", help="Command name (defaults to script filename)")
1563
1787
  @click.option("--group", "-g", default="workflow", help="Command group")
@@ -1565,15 +1789,20 @@ def update(check: bool, pre: bool, yes: bool, skip_ci_check: bool):
1565
1789
  @click.option("--interactive", "-i", is_flag=True, help="Open in $EDITOR for review/editing")
1566
1790
  def import_script(script_path, name, group, description, interactive):
1567
1791
  """
1792
+ [DEPRECATED] Use 'mcli commands import-script' instead.
1793
+
1568
1794
  Import a Python script as a portable JSON command.
1569
1795
 
1570
1796
  Converts a Python script into a JSON command that can be loaded
1571
1797
  by mcli. The script should define Click commands.
1572
1798
 
1573
1799
  Examples:
1574
- mcli self import-script my_script.py
1575
- mcli self import-script my_script.py --name custom-cmd --interactive
1800
+ mcli commands import-script my_script.py
1801
+ mcli commands import-script my_script.py --name custom-cmd --interactive
1576
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
+
1577
1806
  import subprocess
1578
1807
  import tempfile
1579
1808
 
@@ -1652,21 +1881,26 @@ def import_script(script_path, name, group, description, interactive):
1652
1881
  return 0
1653
1882
 
1654
1883
 
1655
- @self_app.command("export-script")
1884
+ @self_app.command("export-script", deprecated=True)
1656
1885
  @click.argument("command_name")
1657
1886
  @click.option("--output", "-o", type=click.Path(), help="Output file path")
1658
1887
  @click.option("--standalone", "-s", is_flag=True, help="Make script standalone (add if __name__ == '__main__')")
1659
1888
  def export_script(command_name, output, standalone):
1660
1889
  """
1890
+ [DEPRECATED] Use 'mcli commands export-script' instead.
1891
+
1661
1892
  Export a JSON command to a Python script.
1662
1893
 
1663
1894
  Converts a portable JSON command back to a standalone Python script
1664
1895
  that can be edited and run independently.
1665
1896
 
1666
1897
  Examples:
1667
- mcli self export-script my-command
1668
- mcli self export-script my-command --output my_script.py --standalone
1898
+ mcli commands export-script my-command
1899
+ mcli commands export-script my-command --output my_script.py --standalone
1669
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
+
1670
1904
  manager = get_command_manager()
1671
1905
 
1672
1906
  # Load the command
@@ -1720,20 +1954,25 @@ def export_script(command_name, output, standalone):
1720
1954
  return 0
1721
1955
 
1722
1956
 
1723
- @self_app.command("edit-command")
1957
+ @self_app.command("edit-command", deprecated=True)
1724
1958
  @click.argument("command_name")
1725
1959
  @click.option("--editor", "-e", help="Editor to use (defaults to $EDITOR)")
1726
1960
  def edit_command(command_name, editor):
1727
1961
  """
1962
+ [DEPRECATED] Use 'mcli commands edit' instead.
1963
+
1728
1964
  Edit a command interactively using $EDITOR.
1729
1965
 
1730
1966
  Opens the command's Python code in your preferred editor,
1731
1967
  allows you to make changes, and saves the updated version.
1732
1968
 
1733
1969
  Examples:
1734
- mcli self edit-command my-command
1735
- mcli self edit-command my-command --editor code
1970
+ mcli commands edit my-command
1971
+ mcli commands edit my-command --editor code
1736
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
+
1737
1976
  import subprocess
1738
1977
  import tempfile
1739
1978
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcli-framework
3
- Version: 7.3.1
3
+ Version: 7.4.0
4
4
  Summary: 🚀 High-performance CLI framework with Rust extensions, AI chat, and stunning visuals
5
5
  Author-email: Luis Fernandez de la Vara <luis@lefv.io>
6
6
  Maintainer-email: Luis Fernandez de la Vara <luis@lefv.io>
@@ -93,7 +93,7 @@ Requires-Dist: polars>=0.19.0
93
93
  Requires-Dist: pyarrow>=14.0.0
94
94
  Requires-Dist: yfinance>=0.2.18
95
95
  Requires-Dist: alpha-vantage>=2.3.1
96
- Requires-Dist: alpaca-py>=0.20.0
96
+ Requires-Dist: alpaca-py==0.42.2
97
97
  Requires-Dist: cvxpy>=1.4.0
98
98
  Requires-Dist: python-jose[cryptography]>=3.3.0
99
99
  Requires-Dist: passlib[bcrypt]>=1.7.4
@@ -119,6 +119,7 @@ Requires-Dist: streamlit-autorefresh>=1.0.1
119
119
  Requires-Dist: typer>=0.9.0
120
120
  Requires-Dist: flask<3.0.0,>=2.3.0
121
121
  Requires-Dist: alpaca-trade-api>=0.26
122
+ Requires-Dist: alpaca>=1.0.0
122
123
  Provides-Extra: gpu
123
124
  Requires-Dist: cupy-cuda12x>=12.3.0; extra == "gpu"
124
125
  Requires-Dist: nvidia-ml-py>=12.535.0; extra == "gpu"