dayhoff-tools 1.9.5__tar.gz → 1.9.6__tar.gz

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.
Files changed (32) hide show
  1. {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/PKG-INFO +1 -1
  2. {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/cli/engine_commands.py +134 -89
  3. {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/pyproject.toml +1 -1
  4. {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/README.md +0 -0
  5. {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/__init__.py +0 -0
  6. {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/chemistry/standardizer.py +0 -0
  7. {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/chemistry/utils.py +0 -0
  8. {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/cli/__init__.py +0 -0
  9. {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/cli/cloud_commands.py +0 -0
  10. {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/cli/main.py +0 -0
  11. {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/cli/swarm_commands.py +0 -0
  12. {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/cli/utility_commands.py +0 -0
  13. {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/deployment/base.py +0 -0
  14. {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/deployment/deploy_aws.py +0 -0
  15. {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/deployment/deploy_gcp.py +0 -0
  16. {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/deployment/deploy_utils.py +0 -0
  17. {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/deployment/job_runner.py +0 -0
  18. {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/deployment/processors.py +0 -0
  19. {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/deployment/swarm.py +0 -0
  20. {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/embedders.py +0 -0
  21. {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/fasta.py +0 -0
  22. {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/file_ops.py +0 -0
  23. {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/h5.py +0 -0
  24. {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/intake/gcp.py +0 -0
  25. {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/intake/gtdb.py +0 -0
  26. {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/intake/kegg.py +0 -0
  27. {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/intake/mmseqs.py +0 -0
  28. {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/intake/structure.py +0 -0
  29. {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/intake/uniprot.py +0 -0
  30. {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/logs.py +0 -0
  31. {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/sqlite.py +0 -0
  32. {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/warehouse.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: dayhoff-tools
3
- Version: 1.9.5
3
+ Version: 1.9.6
4
4
  Summary: Common tools for all the repos at Dayhoff Labs
5
5
  Author: Daniel Martin-Alarcon
6
6
  Author-email: dma@dayhofflabs.com
@@ -693,9 +693,10 @@ def list_engines(
693
693
  @engine_app.command("status")
694
694
  def engine_status(
695
695
  name_or_id: str = typer.Argument(help="Engine name or instance ID"),
696
- show_log: bool = typer.Option(False, "--show-log", help="Show bootstrap log"),
696
+ detailed: bool = typer.Option(False, "--detailed", "-d", help="Show detailed status (slower)"),
697
+ show_log: bool = typer.Option(False, "--show-log", help="Show bootstrap log (requires --detailed)"),
697
698
  ):
698
- """Show detailed engine status and information."""
699
+ """Show engine status and information."""
699
700
  check_aws_sso()
700
701
 
701
702
  # Get all engines to resolve name
@@ -706,8 +707,131 @@ def engine_status(
706
707
 
707
708
  engines = response.json().get("engines", [])
708
709
  engine = resolve_engine(name_or_id, engines)
710
+
711
+ # Fast status display (default)
712
+ if not detailed:
713
+ # Get basic engine info and studios from API
714
+ response = make_api_request("GET", f"/engines/{engine['instance_id']}")
715
+ if response.status_code != 200:
716
+ console.print("[red]❌ Failed to fetch engine details[/red]")
717
+ raise typer.Exit(1)
718
+
719
+ engine_details = response.json()
720
+ engine = engine_details.get("engine", engine)
721
+ attached_studios = engine_details.get("attached_studios", [])
722
+
723
+ # Fetch idle status via SSM with longer timeout
724
+ ssm = boto3.client("ssm", region_name="us-east-1")
725
+ idle_data = {}
726
+
727
+ if engine["state"].lower() == "running":
728
+ try:
729
+ resp = ssm.send_command(
730
+ InstanceIds=[engine["instance_id"]],
731
+ DocumentName="AWS-RunShellScript",
732
+ Parameters={
733
+ "commands": [
734
+ "cat /var/run/idle-detector/last_state.json 2>/dev/null || echo '{}'"
735
+ ],
736
+ "executionTimeout": ["10"],
737
+ },
738
+ )
739
+ cid = resp["Command"]["CommandId"]
740
+
741
+ # Wait up to 3 seconds for result
742
+ for _ in range(6): # 6 * 0.5 = 3 seconds
743
+ time.sleep(0.5)
744
+ inv = ssm.get_command_invocation(
745
+ CommandId=cid, InstanceId=engine["instance_id"]
746
+ )
747
+ if inv["Status"] in ["Success", "Failed"]:
748
+ break
749
+
750
+ if inv["Status"] == "Success":
751
+ content = inv["StandardOutputContent"].strip()
752
+ if content and content != "{}":
753
+ idle_data = json.loads(content)
754
+ except Exception:
755
+ pass # Best effort
756
+
757
+ # Build status display
758
+ hourly_cost = HOURLY_COSTS.get(engine["engine_type"], 0)
759
+
760
+ # Determine running state display
761
+ running_state = engine["state"].lower()
762
+ if running_state == "running":
763
+ run_disp = "[green]Running[/green]"
764
+ elif running_state == "pending":
765
+ run_disp = "[yellow]Starting...[/yellow]"
766
+ elif running_state == "stopping":
767
+ run_disp = "[yellow]Stopping...[/yellow]"
768
+ elif running_state == "stopped":
769
+ run_disp = "[dim]Stopped[/dim]"
770
+ else:
771
+ run_disp = engine["state"].capitalize()
772
+
773
+ # Determine idle/active status
774
+ idle_disp = ""
775
+ if running_state == "running":
776
+ is_idle = idle_data.get("idle", False)
777
+ timeout_sec = idle_data.get("timeout_sec")
778
+ idle_seconds = idle_data.get("idle_seconds", 0) if is_idle else 0
779
+
780
+ if is_idle:
781
+ if isinstance(timeout_sec, int) and isinstance(idle_seconds, int):
782
+ remaining = max(0, timeout_sec - idle_seconds)
783
+ idle_disp = f" [yellow]Idle {idle_seconds//60}m/{timeout_sec//60}m: [red]{remaining//60}m {remaining%60}s[/red] left[/yellow]"
784
+ else:
785
+ idle_disp = " [yellow]Idle ?/?[/yellow]"
786
+ else:
787
+ idle_disp = " [green]Active[/green]"
788
+
789
+ # Build status lines
790
+ status_lines = [
791
+ f"[blue]{engine['name']}[/blue] {run_disp}{idle_disp}\n",
792
+ ]
793
+
794
+ # Add studios if attached
795
+ if attached_studios:
796
+ studio_names = [f"[magenta]{s.get('user', 'studio')}[/magenta]" for s in attached_studios]
797
+ status_lines.append(f"Studios: {', '.join(studio_names)}")
798
+
799
+ status_lines.append("") # blank line
800
+
801
+ # Basic info
802
+ status_lines.extend([
803
+ f"Type: {engine['engine_type']} ({engine['instance_type']})",
804
+ f"User: {engine['user']}",
805
+ f"IP: {engine.get('public_ip', 'N/A')}",
806
+ f"$/hour: ${hourly_cost:.2f}",
807
+ ])
808
+
809
+ # Add activity sensors if we have idle data
810
+ if idle_data.get("reasons"):
811
+ status_lines.append("")
812
+ status_lines.append("[bold]Activity:[/bold]")
813
+
814
+ sensor_map = {
815
+ "CoffeeLockSensor": ("☕", "Coffee"),
816
+ "ActiveLoginSensor": ("🐚", "SSH"),
817
+ "IDEConnectionSensor": ("🖥", "IDE"),
818
+ "DockerWorkloadSensor": ("🐳", "Docker"),
819
+ }
820
+
821
+ for r in idle_data.get("reasons", []):
822
+ sensor = r.get("sensor", "Unknown")
823
+ active = r.get("active", False)
824
+ icon, label = sensor_map.get(sensor, ("?", sensor))
825
+ status_str = "[green]YES[/green]" if active else "[dim]no[/dim]"
826
+ status_lines.append(f" {icon} {label:6} {status_str}")
827
+
828
+ # Display in a nice panel
829
+ console.print(
830
+ Panel("\n".join(status_lines), title="Engine Status", border_style="blue")
831
+ )
832
+ return # Exit early for fast status
709
833
 
710
- # Get detailed engine status including idle detector info
834
+ # Get detailed engine status including idle detector info (for --detailed mode)
711
835
  response = make_api_request("GET", f"/engines/{engine['instance_id']}")
712
836
  if response.status_code != 200:
713
837
  console.print("[red]❌ Failed to fetch engine details[/red]")
@@ -783,12 +907,13 @@ def engine_status(
783
907
  if not idle_info.get("available"):
784
908
  return "[dim]N/A[/dim]"
785
909
 
786
- # If idle, show time/threshold if available, otherwise show uncertainty
910
+ # If idle, show time/threshold with time remaining if available
787
911
  if idle_info.get("status") == "idle":
788
912
  idle_seconds_v = idle_info.get("idle_seconds")
789
913
  thresh_v = idle_info.get("idle_threshold")
790
914
  if isinstance(idle_seconds_v, (int, float)) and isinstance(thresh_v, (int, float)):
791
- return f"[yellow]Idle {int(idle_seconds_v)//60}m/{int(thresh_v)//60}m[/yellow]"
915
+ remaining = max(0, int(thresh_v) - int(idle_seconds_v))
916
+ return f"[yellow]Idle {int(idle_seconds_v)//60}m/{int(thresh_v)//60}m: [red]{remaining//60}m {remaining%60}s[/red] left[/yellow]"
792
917
  elif isinstance(thresh_v, (int, float)):
793
918
  return f"[yellow]Idle ?/{int(thresh_v)//60}m[/yellow]"
794
919
  else:
@@ -1062,6 +1187,9 @@ def engine_status(
1062
1187
  )
1063
1188
 
1064
1189
  if show_log:
1190
+ if not detailed:
1191
+ console.print("[yellow]Note: --show-log requires --detailed flag[/yellow]")
1192
+ return
1065
1193
  console.print("\n[bold]Bootstrap Log:[/bold]")
1066
1194
  try:
1067
1195
  ssm = boto3.client("ssm", region_name="us-east-1")
@@ -2631,9 +2759,7 @@ def idle_timeout_cmd(
2631
2759
  set: Optional[str] = typer.Option(
2632
2760
  None, "--set", "-s", help="New timeout (e.g., 2h30m, 45m)"
2633
2761
  ),
2634
- status: bool = typer.Option(
2635
- False, "--status", help="Show detailed idle status (faster than full engine status)"
2636
- ),
2762
+
2637
2763
  ):
2638
2764
  """Show or set the engine idle-detector timeout."""
2639
2765
  check_aws_sso()
@@ -2648,87 +2774,6 @@ def idle_timeout_cmd(
2648
2774
  engine = resolve_engine(name_or_id, engines)
2649
2775
 
2650
2776
  ssm = boto3.client("ssm", region_name="us-east-1")
2651
-
2652
- # Handle --status flag for quick idle status check
2653
- if status:
2654
- console.print(f"[bold]Idle Status for {engine['name']}:[/bold]")
2655
-
2656
- # Fetch state file with longer timeout for reliability
2657
- try:
2658
- resp = ssm.send_command(
2659
- InstanceIds=[engine["instance_id"]],
2660
- DocumentName="AWS-RunShellScript",
2661
- Parameters={
2662
- "commands": [
2663
- "cat /var/run/idle-detector/last_state.json 2>/dev/null || echo '{}'"
2664
- ],
2665
- "executionTimeout": ["10"],
2666
- },
2667
- )
2668
- cid = resp["Command"]["CommandId"]
2669
-
2670
- # Wait up to 3 seconds for result
2671
- for _ in range(6): # 6 * 0.5 = 3 seconds
2672
- time.sleep(0.5)
2673
- inv = ssm.get_command_invocation(
2674
- CommandId=cid, InstanceId=engine["instance_id"]
2675
- )
2676
- if inv["Status"] in ["Success", "Failed"]:
2677
- break
2678
-
2679
- if inv["Status"] == "Success":
2680
- import json
2681
- content = inv["StandardOutputContent"].strip()
2682
- if content and content != "{}":
2683
- data = json.loads(content)
2684
-
2685
- # Extract key values
2686
- is_idle = data.get("idle", False)
2687
- timeout_sec = data.get("timeout_sec", "?")
2688
- idle_seconds = data.get("idle_seconds", 0) if is_idle else 0
2689
-
2690
- # Display status
2691
- if is_idle:
2692
- if isinstance(timeout_sec, int) and isinstance(idle_seconds, int):
2693
- console.print(f" Status: [yellow]Idle {idle_seconds//60}m/{timeout_sec//60}m[/yellow]")
2694
- remaining = max(0, timeout_sec - idle_seconds)
2695
- console.print(f" Time until shutdown: [red]{remaining//60}m {remaining%60}s[/red]")
2696
- else:
2697
- console.print(f" Status: [yellow]Idle (timing unavailable)[/yellow]")
2698
- else:
2699
- console.print(f" Status: [green]Active[/green]")
2700
- if isinstance(timeout_sec, int):
2701
- console.print(f" Timeout: {timeout_sec//60}m ({timeout_sec}s)")
2702
-
2703
- # Show activity sensors
2704
- console.print("\n[bold]Activity Sensors:[/bold]")
2705
- reasons = data.get("reasons", [])
2706
- for r in reasons:
2707
- sensor = r.get("sensor", "Unknown")
2708
- active = r.get("active", False)
2709
- reason = r.get("reason", "")
2710
-
2711
- # Map sensor names to icons and labels
2712
- sensor_map = {
2713
- "CoffeeLockSensor": ("☕", "Coffee"),
2714
- "ActiveLoginSensor": ("🐚", "SSH"),
2715
- "IDEConnectionSensor": ("🖥", "IDE"),
2716
- "DockerWorkloadSensor": ("🐳", "Docker"),
2717
- }
2718
-
2719
- icon, label = sensor_map.get(sensor, ("?", sensor))
2720
- status_str = "[green]YES[/green]" if active else "[dim]no[/dim]"
2721
- console.print(f" {icon} {label:8} {status_str} {reason if active else ''}")
2722
-
2723
- else:
2724
- console.print("[yellow]⚠️ Could not read idle detector state[/yellow]")
2725
- else:
2726
- console.print(f"[red]❌ Failed to get status: {inv.get('StandardErrorContent', 'Unknown error')}[/red]")
2727
-
2728
- except Exception as e:
2729
- console.print(f"[red]❌ Error fetching idle status: {e}[/red]")
2730
-
2731
- return
2732
2777
 
2733
2778
  if set is None:
2734
2779
  # Show current timeout setting
@@ -5,7 +5,7 @@ build-backend = "poetry.core.masonry.api"
5
5
 
6
6
  [project]
7
7
  name = "dayhoff-tools"
8
- version = "1.9.5"
8
+ version = "1.9.6"
9
9
  description = "Common tools for all the repos at Dayhoff Labs"
10
10
  authors = [
11
11
  {name = "Daniel Martin-Alarcon", email = "dma@dayhofflabs.com"}
File without changes