dayhoff-tools 1.3.7__tar.gz → 1.3.9__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.
- {dayhoff_tools-1.3.7 → dayhoff_tools-1.3.9}/PKG-INFO +1 -1
- {dayhoff_tools-1.3.7 → dayhoff_tools-1.3.9}/dayhoff_tools/cli/engine_commands.py +123 -61
- {dayhoff_tools-1.3.7 → dayhoff_tools-1.3.9}/pyproject.toml +1 -1
- {dayhoff_tools-1.3.7 → dayhoff_tools-1.3.9}/README.md +0 -0
- {dayhoff_tools-1.3.7 → dayhoff_tools-1.3.9}/dayhoff_tools/__init__.py +0 -0
- {dayhoff_tools-1.3.7 → dayhoff_tools-1.3.9}/dayhoff_tools/chemistry/standardizer.py +0 -0
- {dayhoff_tools-1.3.7 → dayhoff_tools-1.3.9}/dayhoff_tools/chemistry/utils.py +0 -0
- {dayhoff_tools-1.3.7 → dayhoff_tools-1.3.9}/dayhoff_tools/cli/__init__.py +0 -0
- {dayhoff_tools-1.3.7 → dayhoff_tools-1.3.9}/dayhoff_tools/cli/cloud_commands.py +0 -0
- {dayhoff_tools-1.3.7 → dayhoff_tools-1.3.9}/dayhoff_tools/cli/main.py +0 -0
- {dayhoff_tools-1.3.7 → dayhoff_tools-1.3.9}/dayhoff_tools/cli/swarm_commands.py +0 -0
- {dayhoff_tools-1.3.7 → dayhoff_tools-1.3.9}/dayhoff_tools/cli/utility_commands.py +0 -0
- {dayhoff_tools-1.3.7 → dayhoff_tools-1.3.9}/dayhoff_tools/deployment/base.py +0 -0
- {dayhoff_tools-1.3.7 → dayhoff_tools-1.3.9}/dayhoff_tools/deployment/deploy_aws.py +0 -0
- {dayhoff_tools-1.3.7 → dayhoff_tools-1.3.9}/dayhoff_tools/deployment/deploy_gcp.py +0 -0
- {dayhoff_tools-1.3.7 → dayhoff_tools-1.3.9}/dayhoff_tools/deployment/deploy_utils.py +0 -0
- {dayhoff_tools-1.3.7 → dayhoff_tools-1.3.9}/dayhoff_tools/deployment/job_runner.py +0 -0
- {dayhoff_tools-1.3.7 → dayhoff_tools-1.3.9}/dayhoff_tools/deployment/processors.py +0 -0
- {dayhoff_tools-1.3.7 → dayhoff_tools-1.3.9}/dayhoff_tools/deployment/swarm.py +0 -0
- {dayhoff_tools-1.3.7 → dayhoff_tools-1.3.9}/dayhoff_tools/embedders.py +0 -0
- {dayhoff_tools-1.3.7 → dayhoff_tools-1.3.9}/dayhoff_tools/fasta.py +0 -0
- {dayhoff_tools-1.3.7 → dayhoff_tools-1.3.9}/dayhoff_tools/file_ops.py +0 -0
- {dayhoff_tools-1.3.7 → dayhoff_tools-1.3.9}/dayhoff_tools/h5.py +0 -0
- {dayhoff_tools-1.3.7 → dayhoff_tools-1.3.9}/dayhoff_tools/intake/gcp.py +0 -0
- {dayhoff_tools-1.3.7 → dayhoff_tools-1.3.9}/dayhoff_tools/intake/gtdb.py +0 -0
- {dayhoff_tools-1.3.7 → dayhoff_tools-1.3.9}/dayhoff_tools/intake/kegg.py +0 -0
- {dayhoff_tools-1.3.7 → dayhoff_tools-1.3.9}/dayhoff_tools/intake/mmseqs.py +0 -0
- {dayhoff_tools-1.3.7 → dayhoff_tools-1.3.9}/dayhoff_tools/intake/structure.py +0 -0
- {dayhoff_tools-1.3.7 → dayhoff_tools-1.3.9}/dayhoff_tools/intake/uniprot.py +0 -0
- {dayhoff_tools-1.3.7 → dayhoff_tools-1.3.9}/dayhoff_tools/logs.py +0 -0
- {dayhoff_tools-1.3.7 → dayhoff_tools-1.3.9}/dayhoff_tools/sqlite.py +0 -0
- {dayhoff_tools-1.3.7 → dayhoff_tools-1.3.9}/dayhoff_tools/warehouse.py +0 -0
@@ -27,10 +27,10 @@ console = Console()
|
|
27
27
|
# Cost information
|
28
28
|
HOURLY_COSTS = {
|
29
29
|
"cpu": 0.50, # r6i.2xlarge
|
30
|
-
"cpumax":
|
31
|
-
"t4":
|
32
|
-
"a10g":
|
33
|
-
"a100":
|
30
|
+
"cpumax": 2.02, # r7i.8xlarge
|
31
|
+
"t4": 0.75, # g4dn.2xlarge
|
32
|
+
"a10g": 1.50, # g5.2xlarge
|
33
|
+
"a100": 21.96, # p4d.24xlarge
|
34
34
|
}
|
35
35
|
|
36
36
|
# SSH config management
|
@@ -90,7 +90,10 @@ def get_api_url() -> str:
|
|
90
90
|
|
91
91
|
|
92
92
|
def make_api_request(
|
93
|
-
method: str,
|
93
|
+
method: str,
|
94
|
+
endpoint: str,
|
95
|
+
json_data: Optional[Dict] = None,
|
96
|
+
params: Optional[Dict] = None,
|
94
97
|
) -> requests.Response:
|
95
98
|
"""Make an API request with error handling."""
|
96
99
|
api_url = get_api_url()
|
@@ -134,14 +137,14 @@ def parse_launch_time(launch_time_str: str) -> datetime:
|
|
134
137
|
"%Y-%m-%dT%H:%M:%S+00:00", # Explicit UTC offset
|
135
138
|
"%Y-%m-%d %H:%M:%S",
|
136
139
|
]
|
137
|
-
|
140
|
+
|
138
141
|
# First try parsing with fromisoformat for better timezone handling
|
139
142
|
try:
|
140
143
|
# Handle the ISO format properly
|
141
|
-
return datetime.fromisoformat(launch_time_str.replace(
|
144
|
+
return datetime.fromisoformat(launch_time_str.replace("Z", "+00:00"))
|
142
145
|
except (ValueError, AttributeError):
|
143
146
|
pass
|
144
|
-
|
147
|
+
|
145
148
|
# Fallback to manual format parsing
|
146
149
|
for fmt in formats:
|
147
150
|
try:
|
@@ -152,7 +155,7 @@ def parse_launch_time(launch_time_str: str) -> datetime:
|
|
152
155
|
return parsed
|
153
156
|
except ValueError:
|
154
157
|
continue
|
155
|
-
|
158
|
+
|
156
159
|
# Fallback: assume it's recent
|
157
160
|
return datetime.now(timezone.utc)
|
158
161
|
|
@@ -213,7 +216,9 @@ def resolve_engine(name_or_id: str, engines: List[Dict]) -> Dict:
|
|
213
216
|
while True:
|
214
217
|
try:
|
215
218
|
choice = IntPrompt.ask(
|
216
|
-
"Select engine",
|
219
|
+
"Select engine",
|
220
|
+
default=1,
|
221
|
+
choices=[str(i) for i in range(1, len(matches) + 1)],
|
217
222
|
)
|
218
223
|
return matches[choice - 1]
|
219
224
|
except (ValueError, IndexError):
|
@@ -307,7 +312,9 @@ def launch_engine(
|
|
307
312
|
raise typer.Exit(1)
|
308
313
|
|
309
314
|
cost = HOURLY_COSTS.get(engine_type, 0)
|
310
|
-
console.print(
|
315
|
+
console.print(
|
316
|
+
f"Launching [cyan]{name}[/cyan] ({engine_type}) for ${cost:.2f}/hour..."
|
317
|
+
)
|
311
318
|
|
312
319
|
with Progress(
|
313
320
|
SpinnerColumn(),
|
@@ -337,8 +344,12 @@ def launch_engine(
|
|
337
344
|
@engine_app.command("list")
|
338
345
|
def list_engines(
|
339
346
|
user: Optional[str] = typer.Option(None, "--user", "-u", help="Filter by user"),
|
340
|
-
running_only: bool = typer.Option(
|
341
|
-
|
347
|
+
running_only: bool = typer.Option(
|
348
|
+
False, "--running", help="Show only running engines"
|
349
|
+
),
|
350
|
+
stopped_only: bool = typer.Option(
|
351
|
+
False, "--stopped", help="Show only stopped engines"
|
352
|
+
),
|
342
353
|
):
|
343
354
|
"""List engines (shows all engines by default)."""
|
344
355
|
current_user = check_aws_sso()
|
@@ -425,7 +436,9 @@ def engine_status(
|
|
425
436
|
engine = resolve_engine(name_or_id, engines)
|
426
437
|
|
427
438
|
# Get attached studios info
|
428
|
-
response = make_api_request(
|
439
|
+
response = make_api_request(
|
440
|
+
"GET", f"/engines/{engine['instance_id']}/attached-studios"
|
441
|
+
)
|
429
442
|
attached_studios = []
|
430
443
|
if response.status_code == 200:
|
431
444
|
attached_studios = response.json().get("studios", [])
|
@@ -468,7 +481,9 @@ def engine_status(
|
|
468
481
|
@engine_app.command("stop")
|
469
482
|
def stop_engine(
|
470
483
|
name_or_id: str = typer.Argument(help="Engine name or instance ID"),
|
471
|
-
force: bool = typer.Option(
|
484
|
+
force: bool = typer.Option(
|
485
|
+
False, "--force", "-f", help="Force stop and detach all studios"
|
486
|
+
),
|
472
487
|
):
|
473
488
|
"""Stop an engine."""
|
474
489
|
check_aws_sso()
|
@@ -568,7 +583,9 @@ def terminate_engine(
|
|
568
583
|
hourly_cost = HOURLY_COSTS.get(engine["engine_type"], 0)
|
569
584
|
total_cost = hourly_cost * (uptime.total_seconds() / 3600)
|
570
585
|
|
571
|
-
console.print(
|
586
|
+
console.print(
|
587
|
+
f"\n[yellow]⚠️ This will permanently terminate engine '{engine['name']}'[/yellow]"
|
588
|
+
)
|
572
589
|
console.print(f"Total cost for this session: ${total_cost:.2f}")
|
573
590
|
|
574
591
|
if not Confirm.ask("\nAre you sure you want to terminate this engine?"):
|
@@ -616,7 +633,9 @@ def ssh_engine(
|
|
616
633
|
@engine_app.command("config-ssh")
|
617
634
|
def config_ssh(
|
618
635
|
clean: bool = typer.Option(False, "--clean", help="Remove all managed entries"),
|
619
|
-
all_engines: bool = typer.Option(
|
636
|
+
all_engines: bool = typer.Option(
|
637
|
+
False, "--all", "-a", help="Include all engines from all users"
|
638
|
+
),
|
620
639
|
):
|
621
640
|
"""Update SSH config with available engines."""
|
622
641
|
username = check_aws_sso()
|
@@ -627,7 +646,9 @@ def config_ssh(
|
|
627
646
|
if all_engines:
|
628
647
|
console.print("Updating SSH config with all running engines...")
|
629
648
|
else:
|
630
|
-
console.print(
|
649
|
+
console.print(
|
650
|
+
f"Updating SSH config with running engines for [cyan]{username}[/cyan] and [cyan]shared[/cyan]..."
|
651
|
+
)
|
631
652
|
|
632
653
|
# Get all engines
|
633
654
|
response = make_api_request("GET", "/engines")
|
@@ -637,13 +658,12 @@ def config_ssh(
|
|
637
658
|
|
638
659
|
engines = response.json().get("engines", [])
|
639
660
|
running_engines = [e for e in engines if e["state"].lower() == "running"]
|
640
|
-
|
661
|
+
|
641
662
|
# Filter engines based on options
|
642
663
|
if not all_engines:
|
643
664
|
# Show only current user's engines and shared engines
|
644
665
|
running_engines = [
|
645
|
-
e for e in running_engines
|
646
|
-
if e["user"] == username or e["user"] == "shared"
|
666
|
+
e for e in running_engines if e["user"] == username or e["user"] == "shared"
|
647
667
|
]
|
648
668
|
|
649
669
|
# Read existing config
|
@@ -680,7 +700,7 @@ def config_ssh(
|
|
680
700
|
f"Host {engine['name']} {SSH_MANAGED_COMMENT}",
|
681
701
|
f" HostName {engine['instance_id']}",
|
682
702
|
f" User {username}",
|
683
|
-
f
|
703
|
+
f" ProxyCommand sh -c \"aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters 'portNumber=%p'\"",
|
684
704
|
]
|
685
705
|
)
|
686
706
|
|
@@ -695,8 +715,12 @@ def config_ssh(
|
|
695
715
|
f"[green]✓ Updated SSH config with {len(running_engines)} engines[/green]"
|
696
716
|
)
|
697
717
|
for engine in running_engines:
|
698
|
-
user_display =
|
699
|
-
|
718
|
+
user_display = (
|
719
|
+
f"[dim]({engine['user']})[/dim]" if engine["user"] != username else ""
|
720
|
+
)
|
721
|
+
console.print(
|
722
|
+
f" • {engine['name']} → {engine['instance_id']} {user_display}"
|
723
|
+
)
|
700
724
|
|
701
725
|
|
702
726
|
@engine_app.command("keep-awake")
|
@@ -709,6 +733,7 @@ def keep_awake(
|
|
709
733
|
|
710
734
|
# Parse duration
|
711
735
|
import re
|
736
|
+
|
712
737
|
match = re.match(r"(?:(\d+)h)?(?:(\d+)m)?", duration)
|
713
738
|
if not match or (not match.group(1) and not match.group(2)):
|
714
739
|
console.print(f"[red]❌ Invalid duration format: {duration}[/red]")
|
@@ -752,6 +777,7 @@ def keep_awake(
|
|
752
777
|
|
753
778
|
# Wait for command to complete
|
754
779
|
import time
|
780
|
+
|
755
781
|
for _ in range(10):
|
756
782
|
time.sleep(1)
|
757
783
|
result = ssm.get_command_invocation(
|
@@ -770,7 +796,9 @@ def keep_awake(
|
|
770
796
|
"[dim]Use keep-awake for nohup operations or other background tasks.[/dim]"
|
771
797
|
)
|
772
798
|
else:
|
773
|
-
console.print(
|
799
|
+
console.print(
|
800
|
+
f"[red]❌ Failed to set keep-awake: {result.get('StatusDetails', 'Unknown error')}[/red]"
|
801
|
+
)
|
774
802
|
|
775
803
|
except ClientError as e:
|
776
804
|
console.print(f"[red]❌ Failed to set keep-awake: {e}[/red]")
|
@@ -810,6 +838,7 @@ def cancel_keep_awake(
|
|
810
838
|
|
811
839
|
# Wait for command to complete
|
812
840
|
import time
|
841
|
+
|
813
842
|
for _ in range(10):
|
814
843
|
time.sleep(1)
|
815
844
|
result = ssm.get_command_invocation(
|
@@ -820,9 +849,13 @@ def cancel_keep_awake(
|
|
820
849
|
break
|
821
850
|
|
822
851
|
if result["Status"] == "Success":
|
823
|
-
console.print(
|
852
|
+
console.print(
|
853
|
+
"[green]✓ Keep-awake cancelled, auto-shutdown re-enabled[/green]"
|
854
|
+
)
|
824
855
|
else:
|
825
|
-
console.print(
|
856
|
+
console.print(
|
857
|
+
f"[red]❌ Failed to cancel keep-awake: {result.get('StatusDetails', 'Unknown error')}[/red]"
|
858
|
+
)
|
826
859
|
|
827
860
|
except ClientError as e:
|
828
861
|
console.print(f"[red]❌ Failed to cancel keep-awake: {e}[/red]")
|
@@ -830,7 +863,9 @@ def cancel_keep_awake(
|
|
830
863
|
|
831
864
|
@engine_app.command("create-ami")
|
832
865
|
def create_ami(
|
833
|
-
name_or_id: str = typer.Argument(
|
866
|
+
name_or_id: str = typer.Argument(
|
867
|
+
help="Engine name or instance ID to create AMI from"
|
868
|
+
),
|
834
869
|
):
|
835
870
|
"""Create a Golden AMI from an engine."""
|
836
871
|
check_aws_sso()
|
@@ -859,7 +894,9 @@ def create_ami(
|
|
859
894
|
|
860
895
|
console.print(f"AMI Name: [cyan]{ami_name}[/cyan]")
|
861
896
|
console.print(f"Description: {ami_description}")
|
862
|
-
console.print(
|
897
|
+
console.print(
|
898
|
+
"\n[yellow]⚠️ Important: This will reboot the engine to ensure a clean snapshot.[/yellow]"
|
899
|
+
)
|
863
900
|
|
864
901
|
if not Confirm.ask("\nContinue with AMI creation?"):
|
865
902
|
console.print("AMI creation cancelled.")
|
@@ -872,7 +909,7 @@ def create_ami(
|
|
872
909
|
# First, we need to clean up the sentinel file via SSM
|
873
910
|
console.print("Cleaning up bootstrap sentinel file...")
|
874
911
|
ssm = boto3.client("ssm", region_name="us-east-1")
|
875
|
-
|
912
|
+
|
876
913
|
cleanup_response = ssm.send_command(
|
877
914
|
InstanceIds=[engine["instance_id"]],
|
878
915
|
DocumentName="AWS-RunShellScript",
|
@@ -888,6 +925,7 @@ def create_ami(
|
|
888
925
|
|
889
926
|
# Wait for cleanup to complete
|
890
927
|
import time
|
928
|
+
|
891
929
|
command_id = cleanup_response["Command"]["CommandId"]
|
892
930
|
for _ in range(10):
|
893
931
|
time.sleep(1)
|
@@ -899,21 +937,25 @@ def create_ami(
|
|
899
937
|
break
|
900
938
|
|
901
939
|
if result["Status"] != "Success":
|
902
|
-
console.print(
|
940
|
+
console.print(
|
941
|
+
"[yellow]⚠️ Warning: Cleanup command may have failed[/yellow]"
|
942
|
+
)
|
903
943
|
|
904
944
|
# Get instance details to find volumes to exclude
|
905
945
|
instances = ec2.describe_instances(InstanceIds=[engine["instance_id"]])
|
906
946
|
instance = instances["Reservations"][0]["Instances"][0]
|
907
|
-
|
947
|
+
|
908
948
|
root_device = instance.get("RootDeviceName", "/dev/xvda")
|
909
949
|
block_mappings = instance.get("BlockDeviceMappings", [])
|
910
|
-
|
950
|
+
|
911
951
|
# Build exclusion list for non-root volumes
|
912
952
|
block_device_mappings = []
|
913
953
|
for mapping in block_mappings:
|
914
954
|
device_name = mapping.get("DeviceName", "")
|
915
955
|
if device_name != root_device:
|
916
|
-
block_device_mappings.append(
|
956
|
+
block_device_mappings.append(
|
957
|
+
{"DeviceName": device_name, "NoDevice": ""}
|
958
|
+
)
|
917
959
|
console.print(f" Excluding volume at {device_name}")
|
918
960
|
|
919
961
|
# Create the AMI
|
@@ -922,7 +964,9 @@ def create_ami(
|
|
922
964
|
TextColumn("[progress.description]{task.description}"),
|
923
965
|
transient=True,
|
924
966
|
) as progress:
|
925
|
-
progress.add_task(
|
967
|
+
progress.add_task(
|
968
|
+
"Creating AMI (this will take several minutes)...", total=None
|
969
|
+
)
|
926
970
|
|
927
971
|
create_params = {
|
928
972
|
"InstanceId": engine["instance_id"],
|
@@ -950,7 +994,7 @@ def create_ami(
|
|
950
994
|
ami_id = response["ImageId"]
|
951
995
|
console.print(f"[green]✓ AMI creation initiated![/green]")
|
952
996
|
console.print(f"AMI ID: [cyan]{ami_id}[/cyan]")
|
953
|
-
|
997
|
+
|
954
998
|
# Restore the source engine to a normal state
|
955
999
|
console.print("Restoring source engine state...")
|
956
1000
|
restore_response = ssm.send_command(
|
@@ -973,13 +1017,20 @@ def create_ami(
|
|
973
1017
|
InstanceId=engine["instance_id"],
|
974
1018
|
)
|
975
1019
|
if result["Status"] not in ["Pending", "InProgress", "Success"]:
|
976
|
-
|
1020
|
+
console.print(
|
1021
|
+
"[yellow]⚠️ Warning: Failed to restore source engine state.[/yellow]"
|
1022
|
+
)
|
977
1023
|
else:
|
978
|
-
console.print(
|
979
|
-
|
1024
|
+
console.print(
|
1025
|
+
"[green]✓ Source engine restored to normal operation.[/green]"
|
1026
|
+
)
|
980
1027
|
|
981
|
-
console.print(
|
982
|
-
|
1028
|
+
console.print(
|
1029
|
+
"\n[dim]The AMI creation process will continue in the background.[/dim]"
|
1030
|
+
)
|
1031
|
+
console.print(
|
1032
|
+
"[dim]You can monitor progress in the EC2 Console under 'AMIs'.[/dim]"
|
1033
|
+
)
|
983
1034
|
console.print(
|
984
1035
|
f"\nOnce complete, run [cyan]terraform apply[/cyan] in "
|
985
1036
|
f"terraform/environments/dev to use the new AMI."
|
@@ -1007,7 +1058,7 @@ def get_user_studio(username: str) -> Optional[Dict]:
|
|
1007
1058
|
|
1008
1059
|
@studio_app.command("create")
|
1009
1060
|
def create_studio(
|
1010
|
-
size_gb: int = typer.Option(
|
1061
|
+
size_gb: int = typer.Option(50, "--size", "-s", help="Studio size in GB"),
|
1011
1062
|
):
|
1012
1063
|
"""Create a new studio for the current user."""
|
1013
1064
|
username = check_aws_sso()
|
@@ -1015,7 +1066,9 @@ def create_studio(
|
|
1015
1066
|
# Check if user already has a studio
|
1016
1067
|
existing = get_user_studio(username)
|
1017
1068
|
if existing:
|
1018
|
-
console.print(
|
1069
|
+
console.print(
|
1070
|
+
f"[yellow]You already have a studio: {existing['studio_id']}[/yellow]"
|
1071
|
+
)
|
1019
1072
|
return
|
1020
1073
|
|
1021
1074
|
console.print(f"Creating {size_gb}GB studio for user [cyan]{username}[/cyan]...")
|
@@ -1057,14 +1110,14 @@ def studio_status():
|
|
1057
1110
|
|
1058
1111
|
# Create status panel
|
1059
1112
|
# Format status with colors
|
1060
|
-
status = studio[
|
1113
|
+
status = studio["status"]
|
1061
1114
|
if status == "in-use":
|
1062
1115
|
status_display = "[bright_blue]attached[/bright_blue]"
|
1063
1116
|
elif status in ["attaching", "detaching"]:
|
1064
1117
|
status_display = f"[yellow]{status}[/yellow]"
|
1065
1118
|
else:
|
1066
1119
|
status_display = f"[green]{status}[/green]"
|
1067
|
-
|
1120
|
+
|
1068
1121
|
status_lines = [
|
1069
1122
|
f"[bold]Studio ID:[/bold] {studio['studio_id']}",
|
1070
1123
|
f"[bold]User:[/bold] {studio['user']}",
|
@@ -1075,17 +1128,19 @@ def studio_status():
|
|
1075
1128
|
|
1076
1129
|
if studio.get("attached_vm_id"):
|
1077
1130
|
status_lines.append(f"[bold]Attached to:[/bold] {studio['attached_vm_id']}")
|
1078
|
-
|
1131
|
+
|
1079
1132
|
# Try to get engine details
|
1080
1133
|
response = make_api_request("GET", "/engines")
|
1081
1134
|
if response.status_code == 200:
|
1082
1135
|
engines = response.json().get("engines", [])
|
1083
1136
|
attached_engine = next(
|
1084
1137
|
(e for e in engines if e["instance_id"] == studio["attached_vm_id"]),
|
1085
|
-
None
|
1138
|
+
None,
|
1086
1139
|
)
|
1087
1140
|
if attached_engine:
|
1088
|
-
status_lines.append(
|
1141
|
+
status_lines.append(
|
1142
|
+
f"[bold]Engine Name:[/bold] {attached_engine['name']}"
|
1143
|
+
)
|
1089
1144
|
|
1090
1145
|
panel = Panel(
|
1091
1146
|
"\n".join(status_lines),
|
@@ -1107,7 +1162,7 @@ def attach_studio(
|
|
1107
1162
|
if not studio:
|
1108
1163
|
console.print("[yellow]You don't have a studio yet.[/yellow]")
|
1109
1164
|
if Confirm.ask("Would you like to create one now?"):
|
1110
|
-
size = IntPrompt.ask("Studio size (GB)", default=
|
1165
|
+
size = IntPrompt.ask("Studio size (GB)", default=50)
|
1111
1166
|
response = make_api_request(
|
1112
1167
|
"POST",
|
1113
1168
|
"/studios",
|
@@ -1145,14 +1200,19 @@ def attach_studio(
|
|
1145
1200
|
|
1146
1201
|
if engine["state"].lower() != "running":
|
1147
1202
|
console.print(f"[yellow]⚠️ Engine is {engine['state']}[/yellow]")
|
1148
|
-
if engine["state"].lower() == "stopped" and Confirm.ask(
|
1149
|
-
|
1203
|
+
if engine["state"].lower() == "stopped" and Confirm.ask(
|
1204
|
+
"Start the engine first?"
|
1205
|
+
):
|
1206
|
+
response = make_api_request(
|
1207
|
+
"POST", f"/engines/{engine['instance_id']}/start"
|
1208
|
+
)
|
1150
1209
|
if response.status_code != 200:
|
1151
1210
|
console.print("[red]❌ Failed to start engine[/red]")
|
1152
1211
|
raise typer.Exit(1)
|
1153
1212
|
console.print("[green]✓ Engine started[/green]")
|
1154
1213
|
console.print("Waiting for engine to be ready...")
|
1155
1214
|
import time
|
1215
|
+
|
1156
1216
|
time.sleep(10)
|
1157
1217
|
else:
|
1158
1218
|
raise typer.Exit(1)
|
@@ -1233,7 +1293,9 @@ def delete_studio():
|
|
1233
1293
|
console.print("[yellow]You don't have a studio to delete.[/yellow]")
|
1234
1294
|
return
|
1235
1295
|
|
1236
|
-
console.print(
|
1296
|
+
console.print(
|
1297
|
+
"[red]⚠️ WARNING: This will permanently delete your studio and all data![/red]"
|
1298
|
+
)
|
1237
1299
|
console.print(f"Studio ID: {studio['studio_id']}")
|
1238
1300
|
console.print(f"Size: {studio['size_gb']}GB")
|
1239
1301
|
|
@@ -1246,9 +1308,7 @@ def delete_studio():
|
|
1246
1308
|
console.print("Deletion cancelled.")
|
1247
1309
|
return
|
1248
1310
|
|
1249
|
-
typed_confirm = Prompt.ask(
|
1250
|
-
'Type "DELETE" to confirm permanent deletion'
|
1251
|
-
)
|
1311
|
+
typed_confirm = Prompt.ask('Type "DELETE" to confirm permanent deletion')
|
1252
1312
|
if typed_confirm != "DELETE":
|
1253
1313
|
console.print("Deletion cancelled.")
|
1254
1314
|
return
|
@@ -1264,7 +1324,9 @@ def delete_studio():
|
|
1264
1324
|
|
1265
1325
|
@studio_app.command("list")
|
1266
1326
|
def list_studios(
|
1267
|
-
all_users: bool = typer.Option(
|
1327
|
+
all_users: bool = typer.Option(
|
1328
|
+
False, "--all", "-a", help="Show all users' studios"
|
1329
|
+
),
|
1268
1330
|
):
|
1269
1331
|
"""List studios."""
|
1270
1332
|
username = check_aws_sso()
|
@@ -1302,24 +1364,24 @@ def list_studios(
|
|
1302
1364
|
status_display = "[yellow]" + studio["status"] + "[/yellow]"
|
1303
1365
|
else:
|
1304
1366
|
status_display = "[green]available[/green]"
|
1305
|
-
|
1367
|
+
|
1306
1368
|
# Format attached engine info
|
1307
1369
|
attached_to = "-"
|
1308
1370
|
if studio.get("attached_vm_id"):
|
1309
1371
|
vm_id = studio["attached_vm_id"]
|
1310
1372
|
engine_name = engines.get(vm_id, "unknown")
|
1311
1373
|
attached_to = f"{engine_name} ({vm_id})"
|
1312
|
-
|
1374
|
+
|
1313
1375
|
# Format creation date (remove microseconds and timezone info)
|
1314
1376
|
created = studio["creation_date"]
|
1315
1377
|
try:
|
1316
1378
|
# Parse and reformat to just show date and time
|
1317
|
-
if
|
1318
|
-
created_dt = datetime.fromisoformat(created.replace(
|
1379
|
+
if "T" in created:
|
1380
|
+
created_dt = datetime.fromisoformat(created.replace("Z", "+00:00"))
|
1319
1381
|
created = created_dt.strftime("%Y-%m-%d %H:%M")
|
1320
1382
|
except:
|
1321
1383
|
# If parsing fails, just truncate
|
1322
|
-
created = created.split(
|
1384
|
+
created = created.split("T")[0] if "T" in created else created[:16]
|
1323
1385
|
|
1324
1386
|
table.add_row(
|
1325
1387
|
studio["studio_id"],
|
@@ -5,7 +5,7 @@ build-backend = "poetry.core.masonry.api"
|
|
5
5
|
|
6
6
|
[project]
|
7
7
|
name = "dayhoff-tools"
|
8
|
-
version = "1.3.
|
8
|
+
version = "1.3.9"
|
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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|