dayhoff-tools 1.9.4__py3-none-any.whl → 1.9.6__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.
- dayhoff_tools/cli/engine_commands.py +161 -32
- {dayhoff_tools-1.9.4.dist-info → dayhoff_tools-1.9.6.dist-info}/METADATA +1 -1
- {dayhoff_tools-1.9.4.dist-info → dayhoff_tools-1.9.6.dist-info}/RECORD +5 -5
- {dayhoff_tools-1.9.4.dist-info → dayhoff_tools-1.9.6.dist-info}/WHEEL +0 -0
- {dayhoff_tools-1.9.4.dist-info → dayhoff_tools-1.9.6.dist-info}/entry_points.txt +0 -0
@@ -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
|
-
|
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
|
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]")
|
@@ -778,15 +902,25 @@ def engine_status(
|
|
778
902
|
return "[green]Active[/green]"
|
779
903
|
if running_state in ("stopped", "stopping"):
|
780
904
|
return "[dim]N/A[/dim]"
|
781
|
-
|
782
|
-
|
783
|
-
if
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
)
|
789
|
-
|
905
|
+
|
906
|
+
# If we don't have idle info at all, show N/A
|
907
|
+
if not idle_info.get("available"):
|
908
|
+
return "[dim]N/A[/dim]"
|
909
|
+
|
910
|
+
# If idle, show time/threshold with time remaining if available
|
911
|
+
if idle_info.get("status") == "idle":
|
912
|
+
idle_seconds_v = idle_info.get("idle_seconds")
|
913
|
+
thresh_v = idle_info.get("idle_threshold")
|
914
|
+
if isinstance(idle_seconds_v, (int, float)) and isinstance(thresh_v, (int, float)):
|
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]"
|
917
|
+
elif isinstance(thresh_v, (int, float)):
|
918
|
+
return f"[yellow]Idle ?/{int(thresh_v)//60}m[/yellow]"
|
919
|
+
else:
|
920
|
+
return "[yellow]Idle ?/?[/yellow]"
|
921
|
+
|
922
|
+
# Default to N/A if we can't determine status
|
923
|
+
return "[dim]N/A[/dim]"
|
790
924
|
|
791
925
|
active_disp = _compute_active_disp(idle_detector)
|
792
926
|
|
@@ -916,8 +1050,12 @@ def engine_status(
|
|
916
1050
|
},
|
917
1051
|
)
|
918
1052
|
cid = res["Command"]["CommandId"]
|
919
|
-
|
920
|
-
|
1053
|
+
# Wait up to 2 seconds for SSM command to complete (was 1 second)
|
1054
|
+
for _ in range(4): # 4 * 0.5 = 2 seconds
|
1055
|
+
time.sleep(0.5)
|
1056
|
+
inv = ssm.get_command_invocation(CommandId=cid, InstanceId=instance_id)
|
1057
|
+
if inv["Status"] in ["Success", "Failed"]:
|
1058
|
+
break
|
921
1059
|
if inv["Status"] != "Success":
|
922
1060
|
return None
|
923
1061
|
content = inv["StandardOutputContent"].strip()
|
@@ -1037,23 +1175,10 @@ def engine_status(
|
|
1037
1175
|
status_lines.append(_sensor_line(" IDE ", "IDEConnectionSensor", "🖥"))
|
1038
1176
|
status_lines.append(_sensor_line("Docker", "DockerWorkloadSensor", "🐳"))
|
1039
1177
|
|
1040
|
-
#
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
1044
|
-
thresh_secs = int(idle_detector.get("idle_threshold", 0))
|
1045
|
-
if thresh_secs > 0:
|
1046
|
-
active_disp = (
|
1047
|
-
f"[yellow]Idle {idle_secs//60}m/{thresh_secs//60}m[/yellow]"
|
1048
|
-
)
|
1049
|
-
# Rewrite top header line (index 0) to include updated display
|
1050
|
-
all_header = top_lines[0]
|
1051
|
-
# Replace the portion after two spaces (name and running state fixed)
|
1052
|
-
top_lines[0] = (
|
1053
|
-
f"[blue]{engine['name']}[/blue] {run_disp} {active_disp}\n"
|
1054
|
-
)
|
1055
|
-
except Exception:
|
1056
|
-
pass
|
1178
|
+
# Recompute display with latest idle detector data
|
1179
|
+
active_disp = _compute_active_disp(idle_detector)
|
1180
|
+
# Rewrite top header line (index 0) to include updated display
|
1181
|
+
top_lines[0] = f"[blue]{engine['name']}[/blue] {run_disp} {active_disp}\n"
|
1057
1182
|
|
1058
1183
|
# Combine top summary and details
|
1059
1184
|
all_lines = top_lines + status_lines
|
@@ -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,6 +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
|
),
|
2762
|
+
|
2634
2763
|
):
|
2635
2764
|
"""Show or set the engine idle-detector timeout."""
|
2636
2765
|
check_aws_sso()
|
@@ -2647,7 +2776,7 @@ def idle_timeout_cmd(
|
|
2647
2776
|
ssm = boto3.client("ssm", region_name="us-east-1")
|
2648
2777
|
|
2649
2778
|
if set is None:
|
2650
|
-
# Show current
|
2779
|
+
# Show current timeout setting
|
2651
2780
|
resp = ssm.send_command(
|
2652
2781
|
InstanceIds=[engine["instance_id"]],
|
2653
2782
|
DocumentName="AWS-RunShellScript",
|
@@ -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=
|
6
|
+
dayhoff_tools/cli/engine_commands.py,sha256=A4EZtbU52HI1Xnm44cnko2l2r6eUDZej4zzgiXU3TaE,112781
|
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.
|
31
|
-
dayhoff_tools-1.9.
|
32
|
-
dayhoff_tools-1.9.
|
33
|
-
dayhoff_tools-1.9.
|
30
|
+
dayhoff_tools-1.9.6.dist-info/METADATA,sha256=sDQKrTIcHUWSK3oUbacWbr2MuhAfat7SDrHdIXQXW48,2914
|
31
|
+
dayhoff_tools-1.9.6.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
32
|
+
dayhoff_tools-1.9.6.dist-info/entry_points.txt,sha256=iAf4jteNqW3cJm6CO6czLxjW3vxYKsyGLZ8WGmxamSc,49
|
33
|
+
dayhoff_tools-1.9.6.dist-info/RECORD,,
|
File without changes
|
File without changes
|