dayhoff-tools 1.9.5__py3-none-any.whl → 1.9.7__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.
@@ -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,122 @@ 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
+ # Skip the API call for studios - use basic info we already have
714
+ attached_studios = []
715
+ studio_user = engine.get("user") # Use the engine's user as studio owner
716
+
717
+ # Fetch idle status via SSM with longer timeout
718
+ ssm = boto3.client("ssm", region_name="us-east-1")
719
+ idle_data = None # Use None to indicate no data received
720
+
721
+ if engine["state"].lower() == "running":
722
+ try:
723
+ resp = ssm.send_command(
724
+ InstanceIds=[engine["instance_id"]],
725
+ DocumentName="AWS-RunShellScript",
726
+ Parameters={
727
+ "commands": [
728
+ "cat /var/run/idle-detector/last_state.json 2>/dev/null || echo '{}'"
729
+ ],
730
+ "executionTimeout": ["10"],
731
+ },
732
+ )
733
+ cid = resp["Command"]["CommandId"]
734
+
735
+ # Wait up to 3 seconds for result
736
+ for _ in range(6): # 6 * 0.5 = 3 seconds
737
+ time.sleep(0.5)
738
+ inv = ssm.get_command_invocation(
739
+ CommandId=cid, InstanceId=engine["instance_id"]
740
+ )
741
+ if inv["Status"] in ["Success", "Failed"]:
742
+ break
743
+
744
+ if inv["Status"] == "Success":
745
+ content = inv["StandardOutputContent"].strip()
746
+ if content and content != "{}":
747
+ idle_data = json.loads(content)
748
+ else:
749
+ idle_data = {} # Empty response but SSM worked
750
+ except Exception:
751
+ idle_data = None # SSM failed
752
+
753
+ # Determine running state display
754
+ running_state = engine["state"].lower()
755
+ if running_state == "running":
756
+ run_disp = "[green]Running[/green]"
757
+ elif running_state == "pending":
758
+ run_disp = "[yellow]Starting...[/yellow]"
759
+ elif running_state == "stopping":
760
+ run_disp = "[yellow]Stopping...[/yellow]"
761
+ elif running_state == "stopped":
762
+ run_disp = "[dim]Stopped[/dim]"
763
+ else:
764
+ run_disp = engine["state"].capitalize()
765
+
766
+ # Determine idle/active status
767
+ idle_disp = ""
768
+ if running_state == "running":
769
+ if idle_data is None:
770
+ # SSM failed - we don't know the status
771
+ idle_disp = " [dim]N/A[/dim]"
772
+ elif not idle_data:
773
+ # Empty data - likely very early in boot
774
+ idle_disp = " [dim]N/A[/dim]"
775
+ else:
776
+ # We have data
777
+ is_idle = idle_data.get("idle", False)
778
+ timeout_sec = idle_data.get("timeout_sec")
779
+ idle_seconds = idle_data.get("idle_seconds", 0) if is_idle else 0
780
+
781
+ if is_idle:
782
+ if isinstance(timeout_sec, int) and isinstance(idle_seconds, int):
783
+ remaining = max(0, timeout_sec - idle_seconds)
784
+ idle_disp = f" [yellow]Idle {idle_seconds//60}m/{timeout_sec//60}m: [red]{remaining//60}m {remaining%60}s[/red] left[/yellow]"
785
+ else:
786
+ idle_disp = " [yellow]Idle ?/?[/yellow]"
787
+ else:
788
+ # Actively not idle
789
+ idle_disp = " [green]Active[/green]"
790
+
791
+ # Build status lines - minimal info for fast view
792
+ status_lines = [
793
+ f"[blue]{engine['name']}[/blue] {run_disp}{idle_disp}\n",
794
+ ]
795
+
796
+ # Add studio owner if known
797
+ if studio_user:
798
+ status_lines.append(f"Studio: [magenta]{studio_user}[/magenta]")
799
+
800
+ # Add activity sensors if we have idle data
801
+ if idle_data and idle_data.get("reasons"):
802
+ status_lines.append("")
803
+ status_lines.append("[bold]Activity:[/bold]")
804
+
805
+ sensor_map = {
806
+ "CoffeeLockSensor": ("☕", "Coffee"),
807
+ "ActiveLoginSensor": ("🐚", "SSH"),
808
+ "IDEConnectionSensor": ("🖥 ", "IDE"),
809
+ "DockerWorkloadSensor": ("🐳", "Docker"),
810
+ }
811
+
812
+ for r in idle_data.get("reasons", []):
813
+ sensor = r.get("sensor", "Unknown")
814
+ active = r.get("active", False)
815
+ icon, label = sensor_map.get(sensor, ("?", sensor))
816
+ status_str = "[green]YES[/green]" if active else "[dim]nope[/dim]"
817
+ status_lines.append(f" {icon} {label:6} {status_str}")
818
+
819
+ # Display in a nice panel
820
+ console.print(
821
+ Panel("\n".join(status_lines), title="Engine Status", border_style="blue")
822
+ )
823
+ return # Exit early for fast status
709
824
 
710
- # Get detailed engine status including idle detector info
825
+ # Get detailed engine status including idle detector info (for --detailed mode)
711
826
  response = make_api_request("GET", f"/engines/{engine['instance_id']}")
712
827
  if response.status_code != 200:
713
828
  console.print("[red]❌ Failed to fetch engine details[/red]")
@@ -783,12 +898,13 @@ def engine_status(
783
898
  if not idle_info.get("available"):
784
899
  return "[dim]N/A[/dim]"
785
900
 
786
- # If idle, show time/threshold if available, otherwise show uncertainty
901
+ # If idle, show time/threshold with time remaining if available
787
902
  if idle_info.get("status") == "idle":
788
903
  idle_seconds_v = idle_info.get("idle_seconds")
789
904
  thresh_v = idle_info.get("idle_threshold")
790
905
  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]"
906
+ remaining = max(0, int(thresh_v) - int(idle_seconds_v))
907
+ 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
908
  elif isinstance(thresh_v, (int, float)):
793
909
  return f"[yellow]Idle ?/{int(thresh_v)//60}m[/yellow]"
794
910
  else:
@@ -1062,6 +1178,9 @@ def engine_status(
1062
1178
  )
1063
1179
 
1064
1180
  if show_log:
1181
+ if not detailed:
1182
+ console.print("[yellow]Note: --show-log requires --detailed flag[/yellow]")
1183
+ return
1065
1184
  console.print("\n[bold]Bootstrap Log:[/bold]")
1066
1185
  try:
1067
1186
  ssm = boto3.client("ssm", region_name="us-east-1")
@@ -2631,9 +2750,7 @@ def idle_timeout_cmd(
2631
2750
  set: Optional[str] = typer.Option(
2632
2751
  None, "--set", "-s", help="New timeout (e.g., 2h30m, 45m)"
2633
2752
  ),
2634
- status: bool = typer.Option(
2635
- False, "--status", help="Show detailed idle status (faster than full engine status)"
2636
- ),
2753
+
2637
2754
  ):
2638
2755
  """Show or set the engine idle-detector timeout."""
2639
2756
  check_aws_sso()
@@ -2648,87 +2765,6 @@ def idle_timeout_cmd(
2648
2765
  engine = resolve_engine(name_or_id, engines)
2649
2766
 
2650
2767
  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
2768
 
2733
2769
  if set is None:
2734
2770
  # Show current timeout setting
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: dayhoff-tools
3
- Version: 1.9.5
3
+ Version: 1.9.7
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
@@ -3,7 +3,7 @@ dayhoff_tools/chemistry/standardizer.py,sha256=uMn7VwHnx02nc404eO6fRuS4rsl4dvSPf
3
3
  dayhoff_tools/chemistry/utils.py,sha256=jt-7JgF-GeeVC421acX-bobKbLU_X94KNOW24p_P-_M,2257
4
4
  dayhoff_tools/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  dayhoff_tools/cli/cloud_commands.py,sha256=33qcWLmq-FwEXMdL3F0OHm-5Stlh2r65CldyEZgQ1no,40904
6
- dayhoff_tools/cli/engine_commands.py,sha256=XFCaHWHhPIsOU43iyERel_hk3CgHPYFN3p8kVaQhuT0,111329
6
+ dayhoff_tools/cli/engine_commands.py,sha256=6dvAYmpg4H3ulTx3duPCd5JzQtMl6lgh1MsnotzxEC8,112564
7
7
  dayhoff_tools/cli/main.py,sha256=LoFs3SI4fdCjP4pdxEAhri-_q0dmNYupmBCRE4KbBac,5933
8
8
  dayhoff_tools/cli/swarm_commands.py,sha256=5EyKj8yietvT5lfoz8Zx0iQvVaNgc3SJX1z2zQR6o6M,5614
9
9
  dayhoff_tools/cli/utility_commands.py,sha256=WQTHOh1MttuxaJjl2c6zMa4x7_JuaKMQgcyotYrU3GA,25883
@@ -27,7 +27,7 @@ dayhoff_tools/intake/uniprot.py,sha256=BZYJQF63OtPcBBnQ7_P9gulxzJtqyorgyuDiPeOJq
27
27
  dayhoff_tools/logs.py,sha256=DKdeP0k0kliRcilwvX0mUB2eipO5BdWUeHwh-VnsICs,838
28
28
  dayhoff_tools/sqlite.py,sha256=jV55ikF8VpTfeQqqlHSbY8OgfyfHj8zgHNpZjBLos_E,18672
29
29
  dayhoff_tools/warehouse.py,sha256=UETBtZD3r7WgvURqfGbyHlT7cxoiVq8isjzMuerKw8I,24475
30
- dayhoff_tools-1.9.5.dist-info/METADATA,sha256=fw3aufIuu3G9jI14oECpLoIUP52JhhT0kpZQufTN5uo,2914
31
- dayhoff_tools-1.9.5.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
32
- dayhoff_tools-1.9.5.dist-info/entry_points.txt,sha256=iAf4jteNqW3cJm6CO6czLxjW3vxYKsyGLZ8WGmxamSc,49
33
- dayhoff_tools-1.9.5.dist-info/RECORD,,
30
+ dayhoff_tools-1.9.7.dist-info/METADATA,sha256=JUzRKthCiuuhJ7UA71zTnHTTqmBm64_hjKdBR-qUgyc,2914
31
+ dayhoff_tools-1.9.7.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
32
+ dayhoff_tools-1.9.7.dist-info/entry_points.txt,sha256=iAf4jteNqW3cJm6CO6czLxjW3vxYKsyGLZ8WGmxamSc,49
33
+ dayhoff_tools-1.9.7.dist-info/RECORD,,