dayhoff-tools 1.9.26__py3-none-any.whl → 1.10.1__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.
Files changed (39) hide show
  1. dayhoff_tools/cli/engine/__init__.py +1 -323
  2. dayhoff_tools/cli/engine/coffee.py +110 -0
  3. dayhoff_tools/cli/engine/config_ssh.py +113 -0
  4. dayhoff_tools/cli/engine/debug.py +79 -0
  5. dayhoff_tools/cli/engine/gami.py +160 -0
  6. dayhoff_tools/cli/engine/idle.py +148 -0
  7. dayhoff_tools/cli/engine/launch.py +101 -0
  8. dayhoff_tools/cli/engine/list.py +116 -0
  9. dayhoff_tools/cli/engine/repair.py +128 -0
  10. dayhoff_tools/cli/engine/resize.py +195 -0
  11. dayhoff_tools/cli/engine/ssh.py +62 -0
  12. dayhoff_tools/cli/engine/{engine_core.py → status.py} +6 -201
  13. dayhoff_tools/cli/engine_studio_commands.py +323 -0
  14. dayhoff_tools/cli/engine_studio_utils/__init__.py +1 -0
  15. dayhoff_tools/cli/engine_studio_utils/api_utils.py +47 -0
  16. dayhoff_tools/cli/engine_studio_utils/aws_utils.py +102 -0
  17. dayhoff_tools/cli/engine_studio_utils/constants.py +21 -0
  18. dayhoff_tools/cli/engine_studio_utils/formatting.py +210 -0
  19. dayhoff_tools/cli/engine_studio_utils/ssh_utils.py +141 -0
  20. dayhoff_tools/cli/main.py +1 -2
  21. dayhoff_tools/cli/studio/__init__.py +1 -0
  22. dayhoff_tools/cli/studio/attach.py +314 -0
  23. dayhoff_tools/cli/studio/create.py +48 -0
  24. dayhoff_tools/cli/studio/delete.py +71 -0
  25. dayhoff_tools/cli/studio/detach.py +56 -0
  26. dayhoff_tools/cli/studio/list.py +81 -0
  27. dayhoff_tools/cli/studio/reset.py +90 -0
  28. dayhoff_tools/cli/studio/resize.py +134 -0
  29. dayhoff_tools/cli/studio/status.py +78 -0
  30. {dayhoff_tools-1.9.26.dist-info → dayhoff_tools-1.10.1.dist-info}/METADATA +1 -1
  31. dayhoff_tools-1.10.1.dist-info/RECORD +61 -0
  32. dayhoff_tools/cli/engine/engine_maintenance.py +0 -431
  33. dayhoff_tools/cli/engine/engine_management.py +0 -505
  34. dayhoff_tools/cli/engine/shared.py +0 -501
  35. dayhoff_tools/cli/engine/studio_commands.py +0 -825
  36. dayhoff_tools-1.9.26.dist-info/RECORD +0 -39
  37. /dayhoff_tools/cli/engine/{engine_lifecycle.py → lifecycle.py} +0 -0
  38. {dayhoff_tools-1.9.26.dist-info → dayhoff_tools-1.10.1.dist-info}/WHEEL +0 -0
  39. {dayhoff_tools-1.9.26.dist-info → dayhoff_tools-1.10.1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,48 @@
1
+ """Studio create command."""
2
+
3
+ import typer
4
+ from rich.progress import Progress, SpinnerColumn, TextColumn
5
+
6
+ from ..engine_studio_utils.api_utils import get_user_studio, make_api_request
7
+ from ..engine_studio_utils.aws_utils import check_aws_sso
8
+ from ..engine_studio_utils.constants import console
9
+
10
+
11
+ def create_studio(
12
+ size_gb: int = typer.Option(50, "--size", "-s", help="Studio size in GB"),
13
+ ):
14
+ """Create a new studio for the current user."""
15
+ username = check_aws_sso()
16
+
17
+ # Check if user already has a studio
18
+ existing = get_user_studio(username)
19
+ if existing:
20
+ console.print(
21
+ f"[yellow]You already have a studio: {existing['studio_id']}[/yellow]"
22
+ )
23
+ return
24
+
25
+ console.print(f"Creating {size_gb}GB studio for user [cyan]{username}[/cyan]...")
26
+
27
+ with Progress(
28
+ SpinnerColumn(),
29
+ TextColumn("[progress.description]{task.description}"),
30
+ transient=True,
31
+ ) as progress:
32
+ progress.add_task("Creating studio volume...", total=None)
33
+
34
+ response = make_api_request(
35
+ "POST",
36
+ "/studios",
37
+ json_data={"user": username, "size_gb": size_gb},
38
+ )
39
+
40
+ if response.status_code == 201:
41
+ data = response.json()
42
+ console.print(f"[green]✓ Studio created successfully![/green]")
43
+ console.print(f"Studio ID: [cyan]{data['studio_id']}[/cyan]")
44
+ console.print(f"Size: {data['size_gb']}GB")
45
+ console.print(f"\nNext step: [cyan]dh studio attach <engine-name>[/cyan]")
46
+ else:
47
+ error = response.json().get("error", "Unknown error")
48
+ console.print(f"[red]❌ Failed to create studio: {error}[/red]")
@@ -0,0 +1,71 @@
1
+ """Studio delete command."""
2
+
3
+ from typing import Optional
4
+
5
+ import typer
6
+ from rich.prompt import Confirm, Prompt
7
+
8
+ from ..engine_studio_utils.api_utils import get_user_studio, make_api_request
9
+ from ..engine_studio_utils.aws_utils import check_aws_sso
10
+ from ..engine_studio_utils.constants import console
11
+
12
+
13
+ def delete_studio(
14
+ user: Optional[str] = typer.Option(
15
+ None, "--user", "-u", help="Delete a different user's studio (admin only)"
16
+ ),
17
+ ):
18
+ """Delete your studio permanently."""
19
+ username = check_aws_sso()
20
+
21
+ # Use specified user if provided, otherwise use current user
22
+ target_user = user if user else username
23
+
24
+ # Extra warning when deleting another user's studio
25
+ if target_user != username:
26
+ console.print(
27
+ f"[red]⚠️ ADMIN ACTION: Deleting studio for user: {target_user}[/red]"
28
+ )
29
+
30
+ studio = get_user_studio(target_user)
31
+ if not studio:
32
+ if target_user == username:
33
+ console.print("[yellow]You don't have a studio to delete.[/yellow]")
34
+ else:
35
+ console.print(
36
+ f"[yellow]User {target_user} doesn't have a studio to delete.[/yellow]"
37
+ )
38
+ return
39
+
40
+ console.print(
41
+ "[red]⚠️ WARNING: This will permanently delete the studio and all data![/red]"
42
+ )
43
+ console.print(f"Studio ID: {studio['studio_id']}")
44
+ console.print(f"User: {target_user}")
45
+ console.print(f"Size: {studio['size_gb']}GB")
46
+
47
+ # Multiple confirmations
48
+ if not Confirm.ask(
49
+ f"\nAre you sure you want to delete {target_user}'s studio?"
50
+ if target_user != username
51
+ else "\nAre you sure you want to delete your studio?"
52
+ ):
53
+ console.print("Deletion cancelled.")
54
+ return
55
+
56
+ if not Confirm.ask("[red]This action cannot be undone. Continue?[/red]"):
57
+ console.print("Deletion cancelled.")
58
+ return
59
+
60
+ typed_confirm = Prompt.ask('Type "DELETE" to confirm permanent deletion')
61
+ if typed_confirm != "DELETE":
62
+ console.print("Deletion cancelled.")
63
+ return
64
+
65
+ response = make_api_request("DELETE", f"/studios/{studio['studio_id']}")
66
+
67
+ if response.status_code == 200:
68
+ console.print(f"[green]✓ Studio deleted successfully![/green]")
69
+ else:
70
+ error = response.json().get("error", "Unknown error")
71
+ console.print(f"[red]❌ Failed to delete studio: {error}[/red]")
@@ -0,0 +1,56 @@
1
+ """Studio detach command."""
2
+
3
+ from typing import Optional
4
+
5
+ import typer
6
+ from rich.prompt import Confirm
7
+
8
+ from ..engine_studio_utils.api_utils import get_user_studio, make_api_request
9
+ from ..engine_studio_utils.aws_utils import check_aws_sso
10
+ from ..engine_studio_utils.constants import console
11
+
12
+
13
+ def detach_studio(
14
+ user: Optional[str] = typer.Option(
15
+ None, "--user", "-u", help="Detach a different user's studio (admin only)"
16
+ ),
17
+ ):
18
+ """Detach your studio from its current engine."""
19
+ username = check_aws_sso()
20
+
21
+ # Use specified user if provided, otherwise use current user
22
+ target_user = user if user else username
23
+
24
+ # Add confirmation when detaching another user's studio
25
+ if target_user != username:
26
+ console.print(f"[yellow]⚠️ Managing studio for user: {target_user}[/yellow]")
27
+ if not Confirm.ask(f"Are you sure you want to detach {target_user}'s studio?"):
28
+ console.print("Operation cancelled.")
29
+ return
30
+
31
+ studio = get_user_studio(target_user)
32
+ if not studio:
33
+ if target_user == username:
34
+ console.print("[yellow]You don't have a studio.[/yellow]")
35
+ else:
36
+ console.print(f"[yellow]User {target_user} doesn't have a studio.[/yellow]")
37
+ return
38
+
39
+ if studio.get("status") != "in-use":
40
+ if target_user == username:
41
+ console.print("[yellow]Your studio is not attached to any engine.[/yellow]")
42
+ else:
43
+ console.print(
44
+ f"[yellow]{target_user}'s studio is not attached to any engine.[/yellow]"
45
+ )
46
+ return
47
+
48
+ console.print(f"Detaching studio from {studio.get('attached_vm_id')}...")
49
+
50
+ response = make_api_request("POST", f"/studios/{studio['studio_id']}/detach")
51
+
52
+ if response.status_code == 200:
53
+ console.print(f"[green]✓ Studio detached successfully![/green]")
54
+ else:
55
+ error = response.json().get("error", "Unknown error")
56
+ console.print(f"[red]❌ Failed to detach studio: {error}[/red]")
@@ -0,0 +1,81 @@
1
+ """Studio list command."""
2
+
3
+ import typer
4
+ from rich import box
5
+ from rich.table import Table
6
+
7
+ from ..engine_studio_utils.api_utils import make_api_request
8
+ from ..engine_studio_utils.aws_utils import check_aws_sso
9
+ from ..engine_studio_utils.constants import console
10
+ from ..engine_studio_utils.formatting import get_studio_disk_usage_via_ssm
11
+
12
+
13
+ def list_studios(
14
+ all_users: bool = typer.Option(
15
+ False, "--all", "-a", help="Show all users' studios"
16
+ ),
17
+ ):
18
+ """List studios."""
19
+ username = check_aws_sso()
20
+
21
+ response = make_api_request("GET", "/studios")
22
+
23
+ if response.status_code == 200:
24
+ studios = response.json().get("studios", [])
25
+
26
+ if not studios:
27
+ console.print("No studios found.")
28
+ return
29
+
30
+ # Get all engines to map instance IDs to names
31
+ engines_response = make_api_request("GET", "/engines")
32
+ engines = {}
33
+ if engines_response.status_code == 200:
34
+ for engine in engines_response.json().get("engines", []):
35
+ engines[engine["instance_id"]] = engine["name"]
36
+
37
+ # Create table
38
+ table = Table(title="Studios", box=box.ROUNDED)
39
+ table.add_column("Studio ID", style="cyan")
40
+ table.add_column("User")
41
+ table.add_column("Status")
42
+ table.add_column("Size", justify="right")
43
+ table.add_column("Disk Usage", justify="right")
44
+ table.add_column("Attached To")
45
+
46
+ for studio in studios:
47
+ # Change status display
48
+ if studio["status"] == "in-use":
49
+ status_display = "[bright_blue]attached[/bright_blue]"
50
+ elif studio["status"] in ["attaching", "detaching"]:
51
+ status_display = "[yellow]" + studio["status"] + "[/yellow]"
52
+ else:
53
+ status_display = "[green]available[/green]"
54
+
55
+ # Format attached engine info
56
+ attached_to = "-"
57
+ disk_usage = "?/?"
58
+ if studio.get("attached_vm_id"):
59
+ vm_id = studio["attached_vm_id"]
60
+ engine_name = engines.get(vm_id, "unknown")
61
+ attached_to = f"{engine_name} ({vm_id})"
62
+
63
+ # Try to get disk usage if attached
64
+ if studio["status"] == "in-use":
65
+ usage = get_studio_disk_usage_via_ssm(vm_id, studio["user"])
66
+ if usage:
67
+ disk_usage = usage
68
+
69
+ table.add_row(
70
+ studio["studio_id"],
71
+ studio["user"],
72
+ status_display,
73
+ f"{studio['size_gb']}GB",
74
+ disk_usage,
75
+ attached_to,
76
+ )
77
+
78
+ console.print(table)
79
+ else:
80
+ error = response.json().get("error", "Unknown error")
81
+ console.print(f"[red]❌ Failed to list studios: {error}[/red]")
@@ -0,0 +1,90 @@
1
+ """Studio reset command."""
2
+
3
+ from typing import Optional
4
+
5
+ import boto3
6
+ import typer
7
+ from botocore.exceptions import ClientError
8
+ from rich.prompt import Confirm
9
+
10
+ from ..engine_studio_utils.api_utils import get_user_studio
11
+ from ..engine_studio_utils.aws_utils import check_aws_sso
12
+ from ..engine_studio_utils.constants import console
13
+
14
+
15
+ def reset_studio(
16
+ user: Optional[str] = typer.Option(
17
+ None, "--user", "-u", help="Reset a different user's studio"
18
+ ),
19
+ ):
20
+ """Reset a stuck studio (admin operation)."""
21
+ username = check_aws_sso()
22
+
23
+ # Use specified user if provided, otherwise use current user
24
+ target_user = user if user else username
25
+
26
+ # Add warning when resetting another user's studio
27
+ if target_user != username:
28
+ console.print(f"[yellow]⚠️ Resetting studio for user: {target_user}[/yellow]")
29
+
30
+ studio = get_user_studio(target_user)
31
+ if not studio:
32
+ if target_user == username:
33
+ console.print("[yellow]You don't have a studio.[/yellow]")
34
+ else:
35
+ console.print(f"[yellow]User {target_user} doesn't have a studio.[/yellow]")
36
+ return
37
+
38
+ console.print(f"[yellow]⚠️ This will force-reset the studio state[/yellow]")
39
+ console.print(f"Current status: {studio['status']}")
40
+ if studio.get("attached_vm_id"):
41
+ console.print(f"Listed as attached to: {studio['attached_vm_id']}")
42
+
43
+ if not Confirm.ask("\nReset studio state?"):
44
+ console.print("Reset cancelled.")
45
+ return
46
+
47
+ # Direct DynamoDB update
48
+ console.print("Resetting studio state...")
49
+
50
+ dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
51
+ table = dynamodb.Table("dev-studios")
52
+
53
+ try:
54
+ # Check if volume is actually attached
55
+ ec2 = boto3.client("ec2", region_name="us-east-1")
56
+ volumes = ec2.describe_volumes(VolumeIds=[studio["studio_id"]])
57
+
58
+ if volumes["Volumes"]:
59
+ volume = volumes["Volumes"][0]
60
+ attachments = volume.get("Attachments", [])
61
+ if attachments:
62
+ console.print(
63
+ f"[red]Volume is still attached to {attachments[0]['InstanceId']}![/red]"
64
+ )
65
+ if Confirm.ask("Force-detach the volume?"):
66
+ ec2.detach_volume(
67
+ VolumeId=studio["studio_id"],
68
+ InstanceId=attachments[0]["InstanceId"],
69
+ Force=True,
70
+ )
71
+ console.print("Waiting for volume to detach...")
72
+ waiter = ec2.get_waiter("volume_available")
73
+ waiter.wait(VolumeIds=[studio["studio_id"]])
74
+
75
+ # Reset in DynamoDB – align attribute names with Studio Manager backend
76
+ table.update_item(
77
+ Key={"StudioID": studio["studio_id"]},
78
+ UpdateExpression="SET #st = :status, AttachedVMID = :vm_id, AttachedDevice = :device",
79
+ ExpressionAttributeNames={"#st": "Status"},
80
+ ExpressionAttributeValues={
81
+ ":status": "available",
82
+ ":vm_id": None,
83
+ ":device": None,
84
+ },
85
+ )
86
+
87
+ console.print(f"[green]✓ Studio reset to available state![/green]")
88
+
89
+ except ClientError as e:
90
+ console.print(f"[red]❌ Failed to reset studio: {e}[/red]")
@@ -0,0 +1,134 @@
1
+ """Studio resize command."""
2
+
3
+ import time
4
+ from typing import Optional
5
+
6
+ import boto3
7
+ import typer
8
+ from botocore.exceptions import ClientError
9
+ from rich.prompt import Confirm
10
+
11
+ from ..engine_studio_utils.api_utils import get_user_studio, make_api_request
12
+ from ..engine_studio_utils.aws_utils import check_aws_sso
13
+ from ..engine_studio_utils.constants import console
14
+
15
+
16
+ def resize_studio(
17
+ size: int = typer.Option(..., "--size", "-s", help="New size in GB"),
18
+ user: Optional[str] = typer.Option(
19
+ None, "--user", "-u", help="Resize a different user's studio (admin only)"
20
+ ),
21
+ ):
22
+ """Resize your studio volume (requires detachment)."""
23
+ username = check_aws_sso()
24
+
25
+ # Use specified user if provided, otherwise use current user
26
+ target_user = user if user else username
27
+
28
+ # Add warning when resizing another user's studio
29
+ if target_user != username:
30
+ console.print(f"[yellow]⚠️ Resizing studio for user: {target_user}[/yellow]")
31
+
32
+ studio = get_user_studio(target_user)
33
+ if not studio:
34
+ if target_user == username:
35
+ console.print("[yellow]You don't have a studio yet.[/yellow]")
36
+ else:
37
+ console.print(f"[yellow]User {target_user} doesn't have a studio.[/yellow]")
38
+ return
39
+
40
+ current_size = studio["size_gb"]
41
+
42
+ if size <= current_size:
43
+ console.print(
44
+ f"[red]❌ New size ({size}GB) must be larger than current size ({current_size}GB)[/red]"
45
+ )
46
+ raise typer.Exit(1)
47
+
48
+ # Check if studio is attached
49
+ if studio["status"] == "in-use":
50
+ console.print("[yellow]⚠️ Studio must be detached before resizing[/yellow]")
51
+ console.print(f"Currently attached to: {studio.get('attached_vm_id')}")
52
+
53
+ if not Confirm.ask("\nDetach studio and proceed with resize?"):
54
+ console.print("Resize cancelled.")
55
+ return
56
+
57
+ # Detach the studio
58
+ console.print("Detaching studio...")
59
+ response = make_api_request("POST", f"/studios/{studio['studio_id']}/detach")
60
+ if response.status_code != 200:
61
+ console.print("[red]❌ Failed to detach studio[/red]")
62
+ raise typer.Exit(1)
63
+
64
+ console.print("[green]✓ Studio detached[/green]")
65
+
66
+ # Wait a moment for detachment to complete
67
+ time.sleep(5)
68
+
69
+ console.print(f"[yellow]Resizing studio from {current_size}GB to {size}GB[/yellow]")
70
+
71
+ # Call the resize API
72
+ resize_response = make_api_request(
73
+ "POST", f"/studios/{studio['studio_id']}/resize", json_data={"size": size}
74
+ )
75
+
76
+ if resize_response.status_code != 200:
77
+ error = resize_response.json().get("error", "Unknown error")
78
+ console.print(f"[red]❌ Failed to resize studio: {error}[/red]")
79
+ raise typer.Exit(1)
80
+
81
+ # Wait for volume modification to complete
82
+ ec2 = boto3.client("ec2", region_name="us-east-1")
83
+ console.print("Resizing volume...")
84
+
85
+ # Track progress
86
+ last_progress = 0
87
+
88
+ while True:
89
+ try:
90
+ mod_state = ec2.describe_volumes_modifications(
91
+ VolumeIds=[studio["studio_id"]]
92
+ )
93
+ if not mod_state["VolumesModifications"]:
94
+ break # Modification complete
95
+
96
+ modification = mod_state["VolumesModifications"][0]
97
+ state = modification["ModificationState"]
98
+ progress = modification.get("Progress", 0)
99
+
100
+ # Show progress updates only for the resize phase
101
+ if state == "modifying" and progress > last_progress:
102
+ console.print(f"[yellow]Progress: {progress}%[/yellow]")
103
+ last_progress = progress
104
+
105
+ # Exit as soon as optimization starts (resize is complete)
106
+ if state == "optimizing":
107
+ console.print(
108
+ f"[green]✓ Studio resized successfully to {size}GB![/green]"
109
+ )
110
+ console.print(
111
+ "[dim]AWS is optimizing the volume in the background (no action needed).[/dim]"
112
+ )
113
+ break
114
+
115
+ if state == "completed":
116
+ console.print(
117
+ f"[green]✓ Studio resized successfully to {size}GB![/green]"
118
+ )
119
+ break
120
+ elif state == "failed":
121
+ console.print("[red]❌ Volume modification failed[/red]")
122
+ raise typer.Exit(1)
123
+
124
+ time.sleep(2) # Check more frequently for better UX
125
+
126
+ except ClientError:
127
+ # Modification might be complete
128
+ console.print(f"[green]✓ Studio resized successfully to {size}GB![/green]")
129
+ break
130
+
131
+ console.print(
132
+ "\n[dim]The filesystem will be automatically expanded when you next attach the studio.[/dim]"
133
+ )
134
+ console.print(f"To attach: [cyan]dh studio attach <engine-name>[/cyan]")
@@ -0,0 +1,78 @@
1
+ """Studio status command."""
2
+
3
+ from typing import Optional
4
+
5
+ import typer
6
+ from rich.panel import Panel
7
+
8
+ from ..engine_studio_utils.api_utils import get_user_studio, make_api_request
9
+ from ..engine_studio_utils.aws_utils import check_aws_sso
10
+ from ..engine_studio_utils.constants import console
11
+
12
+
13
+ def studio_status(
14
+ user: Optional[str] = typer.Option(
15
+ None, "--user", "-u", help="Check status for a different user (admin only)"
16
+ ),
17
+ ):
18
+ """Show status of your studio."""
19
+ username = check_aws_sso()
20
+
21
+ # Use specified user if provided, otherwise use current user
22
+ target_user = user if user else username
23
+
24
+ # Add warning when checking another user's studio
25
+ if target_user != username:
26
+ console.print(
27
+ f"[yellow]⚠️ Checking studio status for user: {target_user}[/yellow]"
28
+ )
29
+
30
+ studio = get_user_studio(target_user)
31
+ if not studio:
32
+ if target_user == username:
33
+ console.print("[yellow]You don't have a studio yet.[/yellow]")
34
+ console.print("Create one with: [cyan]dh studio create[/cyan]")
35
+ else:
36
+ console.print(f"[yellow]User {target_user} doesn't have a studio.[/yellow]")
37
+ return
38
+
39
+ # Create status panel
40
+ # Format status with colors
41
+ status = studio["status"]
42
+ if status == "in-use":
43
+ status_display = "[bright_blue]attached[/bright_blue]"
44
+ elif status in ["attaching", "detaching"]:
45
+ status_display = f"[yellow]{status}[/yellow]"
46
+ else:
47
+ status_display = f"[green]{status}[/green]"
48
+
49
+ status_lines = [
50
+ f"[bold]Studio ID:[/bold] {studio['studio_id']}",
51
+ f"[bold]User:[/bold] {studio['user']}",
52
+ f"[bold]Status:[/bold] {status_display}",
53
+ f"[bold]Size:[/bold] {studio['size_gb']}GB",
54
+ f"[bold]Created:[/bold] {studio['creation_date']}",
55
+ ]
56
+
57
+ if studio.get("attached_vm_id"):
58
+ status_lines.append(f"[bold]Attached to:[/bold] {studio['attached_vm_id']}")
59
+
60
+ # Try to get engine details
61
+ response = make_api_request("GET", "/engines")
62
+ if response.status_code == 200:
63
+ engines = response.json().get("engines", [])
64
+ attached_engine = next(
65
+ (e for e in engines if e["instance_id"] == studio["attached_vm_id"]),
66
+ None,
67
+ )
68
+ if attached_engine:
69
+ status_lines.append(
70
+ f"[bold]Engine Name:[/bold] {attached_engine['name']}"
71
+ )
72
+
73
+ panel = Panel(
74
+ "\n".join(status_lines),
75
+ title="Studio Details",
76
+ border_style="blue",
77
+ )
78
+ console.print(panel)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: dayhoff-tools
3
- Version: 1.9.26
3
+ Version: 1.10.1
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
@@ -0,0 +1,61 @@
1
+ dayhoff_tools/__init__.py,sha256=M5zThPyEBRYa5CfwlzKhcqTevWn3OKu62cjV6Zqie2A,469
2
+ dayhoff_tools/chemistry/standardizer.py,sha256=uMn7VwHnx02nc404eO6fRuS4rsl4dvSPf2ElfZDXEpY,11188
3
+ dayhoff_tools/chemistry/utils.py,sha256=jt-7JgF-GeeVC421acX-bobKbLU_X94KNOW24p_P-_M,2257
4
+ dayhoff_tools/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ dayhoff_tools/cli/cloud_commands.py,sha256=33qcWLmq-FwEXMdL3F0OHm-5Stlh2r65CldyEZgQ1no,40904
6
+ dayhoff_tools/cli/engine/__init__.py,sha256=WV7aojsipkqQRXQ8U6KHGdPr0C1spX348sHp6dqz63Y,34
7
+ dayhoff_tools/cli/engine/coffee.py,sha256=2IKKc6skpDv7kBJz7l5Tvu0FM8hjUoYA-k6LAxdVzKE,3935
8
+ dayhoff_tools/cli/engine/config_ssh.py,sha256=qh5bJH-ELOZnFa9UdcGGXKV8wH5DzybhXJ4b2XSaTyI,3974
9
+ dayhoff_tools/cli/engine/debug.py,sha256=29Cmzj3dyzkAjO3HcJzp4jkB4eCgRhyF2MjtHE4WarE,2714
10
+ dayhoff_tools/cli/engine/gami.py,sha256=OwZKb87oq3zIQ2a9-n_oJ4UK84XOUP3InHYRSAERvaQ,6219
11
+ dayhoff_tools/cli/engine/idle.py,sha256=YGAzII7Db0w1R4_3ebQZVSPWKRmwc_WR3ZafItFs35E,5607
12
+ dayhoff_tools/cli/engine/launch.py,sha256=G6-vUbpFLC2DnMfD5HXQqWgCuCqZ5E42ZuOl45g6N3c,3519
13
+ dayhoff_tools/cli/engine/lifecycle.py,sha256=_Dk-EZs_qbm8APdOuGOuxhlbK6RgkkoLk2nrwKoo1-A,4519
14
+ dayhoff_tools/cli/engine/list.py,sha256=UsghtBxLgXIlvPuKu4_jqe9yrCHgh8uFEbulCA3Cfmg,3896
15
+ dayhoff_tools/cli/engine/repair.py,sha256=lfe5vSfdgRX3uY2sPA4bLtl0Oj0myqSQZZNV_lYD7_g,5037
16
+ dayhoff_tools/cli/engine/resize.py,sha256=BzbvmT5Hf-FbfiCFwS8Sjj6dfMZ8CYihUwpOLAUNpOI,7818
17
+ dayhoff_tools/cli/engine/ssh.py,sha256=m8h-SdfXsok5PgkcvfAoai0-AKI78oE9sJeXWQnkn3g,2095
18
+ dayhoff_tools/cli/engine/status.py,sha256=zlxDpKXCbWpfY7fnsKqxlvakq5txOmHTSeNA4VIX3cg,20461
19
+ dayhoff_tools/cli/engine_studio_commands.py,sha256=tePPfiBtAUmhJ4ttjbtbkse9roqoTAi36nUAbKijFJM,9485
20
+ dayhoff_tools/cli/engine_studio_utils/__init__.py,sha256=G9uYoIG79B9u8BmcWyy5Y_UN4U4CVLZe7yHhcQPJZNk,54
21
+ dayhoff_tools/cli/engine_studio_utils/api_utils.py,sha256=3c7hwwD18b5v6aaDH0O_RZe8V0i5lz8UjgpR-2gl8xA,1347
22
+ dayhoff_tools/cli/engine_studio_utils/aws_utils.py,sha256=T71HXI2RjZQB3QVlcGAB7Qpkn71dHf0Tzxt1QXjSW6Y,3656
23
+ dayhoff_tools/cli/engine_studio_utils/constants.py,sha256=9usS7R2jS4qtieOi4EKZeB372clQpSIsa_NdOIGcrcc,526
24
+ dayhoff_tools/cli/engine_studio_utils/formatting.py,sha256=0n_nHKjejJJCUF8HZOEbjtLOgDVNSImaPRH31Ag0eGs,6800
25
+ dayhoff_tools/cli/engine_studio_utils/ssh_utils.py,sha256=1L5t-ZE3V5-eVznrEViJ8j1HouPwFq6CfoICQkySfCU,4867
26
+ dayhoff_tools/cli/main.py,sha256=IIwgJBuCCSFg1JFnUOoo-o3g7E7eLLXgRvISWUy_HdQ,5939
27
+ dayhoff_tools/cli/studio/__init__.py,sha256=SIg-34_DZT_w6OQGFEoHcf4NLWY5XyHlwcP585WwVWs,34
28
+ dayhoff_tools/cli/studio/attach.py,sha256=NOT9jxCE6tGRXxxYF4_o4cCbnJ2xIh7B0B0HGD3YxaU,11810
29
+ dayhoff_tools/cli/studio/create.py,sha256=x2UuSgj5V8S_P6derLdCsBmX3KTU4KJBuA2O17WxrOs,1664
30
+ dayhoff_tools/cli/studio/delete.py,sha256=Fvt581Uve-IMazfHo_5vXws9smqWxXTdtCaxUCDKESA,2385
31
+ dayhoff_tools/cli/studio/detach.py,sha256=NRvAAmOtJCkYPPnT04msL8Z2LfYA2zYo71WYekYreIA,2018
32
+ dayhoff_tools/cli/studio/list.py,sha256=e-7psFx3A2fxxSbxafI0RqHtvZxaa8qWjK_4F3iO5RI,2813
33
+ dayhoff_tools/cli/studio/reset.py,sha256=gENkUOsTzLqV4oyeVsMEF_EN5c6qAAf0pZtvGhk8Bnc,3308
34
+ dayhoff_tools/cli/studio/resize.py,sha256=LadryhJoo-vo4xrl88wsLgiTEiaq1x5XXQNiWrGgQcU,4830
35
+ dayhoff_tools/cli/studio/status.py,sha256=bua30619ue8tnmDc8KvY8X-udUHw8vSoXV9HLmiBKB4,2647
36
+ dayhoff_tools/cli/swarm_commands.py,sha256=5EyKj8yietvT5lfoz8Zx0iQvVaNgc3SJX1z2zQR6o6M,5614
37
+ dayhoff_tools/cli/utility_commands.py,sha256=WQTHOh1MttuxaJjl2c6zMa4x7_JuaKMQgcyotYrU3GA,25883
38
+ dayhoff_tools/deployment/base.py,sha256=mYp560l6hSDFtyY2H42VoM8k9VUzfwuiyh9Knqpgc28,17441
39
+ dayhoff_tools/deployment/deploy_aws.py,sha256=GvZpE2YIFA5Dl9rkAljFjtUypmPDNbWgw8NicHYTP24,18265
40
+ dayhoff_tools/deployment/deploy_gcp.py,sha256=xgaOVsUDmP6wSEMYNkm1yRNcVskfdz80qJtCulkBIAM,8860
41
+ dayhoff_tools/deployment/deploy_utils.py,sha256=StFwbqnr2_FWiKVg3xnJF4kagTHzndqqDkpaIOaAn_4,26027
42
+ dayhoff_tools/deployment/job_runner.py,sha256=hljvFpH2Bw96uYyUup5Ths72PZRL_X27KxlYzBMgguo,5086
43
+ dayhoff_tools/deployment/processors.py,sha256=LM0CQbr4XCb3AtLbrcuDQm4tYPXsoNqgVJ4WQYDjzJc,12406
44
+ dayhoff_tools/deployment/swarm.py,sha256=YJfvVOcAS8cYcIj2fiN4qwC2leh0I9w5A4px8ZWSF6g,22833
45
+ dayhoff_tools/embedders.py,sha256=1THnmio4FYkBswy_xkIiwT-ZOEMn6ZLbTAa-Uz0-kyE,36615
46
+ dayhoff_tools/fasta.py,sha256=USdemH4c_dNhWXOTAhldvlDi8eLHogsy0YSrOnODB5I,50773
47
+ dayhoff_tools/file_ops.py,sha256=JlGowvr-CUJFidV-4g_JmhUTN9bsYuaxtqKmnKomm-Q,8506
48
+ dayhoff_tools/h5.py,sha256=j1nxxaiHsMidVX_XwB33P1Pz9d7K8ZKiDZwJWQUUQSY,21158
49
+ dayhoff_tools/intake/gcp.py,sha256=uCeEskhbEwJIYpN6ne6siT1dbpTizCjjel-hRe0kReE,3030
50
+ dayhoff_tools/intake/gtdb.py,sha256=58JNLWpr3LkXiIq-ubAcEFeR5DyN9YrA-YEoQI_FzlA,10608
51
+ dayhoff_tools/intake/kegg.py,sha256=SaVbumB4leNTSevamT29yIqHurejw1wmcCC32D5Qyco,965
52
+ dayhoff_tools/intake/mmseqs.py,sha256=uEYzRsthJAlUeRYNCfFtJFE73SbuhfUIS1ygYFkhmtw,6435
53
+ dayhoff_tools/intake/structure.py,sha256=ufN3gAodQxhnt7psK1VTQeu9rKERmo_PhoxIbB4QKMw,27660
54
+ dayhoff_tools/intake/uniprot.py,sha256=BZYJQF63OtPcBBnQ7_P9gulxzJtqyorgyuDiPeOJqE4,16456
55
+ dayhoff_tools/logs.py,sha256=DKdeP0k0kliRcilwvX0mUB2eipO5BdWUeHwh-VnsICs,838
56
+ dayhoff_tools/sqlite.py,sha256=jV55ikF8VpTfeQqqlHSbY8OgfyfHj8zgHNpZjBLos_E,18672
57
+ dayhoff_tools/warehouse.py,sha256=UETBtZD3r7WgvURqfGbyHlT7cxoiVq8isjzMuerKw8I,24475
58
+ dayhoff_tools-1.10.1.dist-info/METADATA,sha256=gQ7_S_-Xe_eFvVIi2VncpKssTfB3EhnODS0i47YTo0E,2915
59
+ dayhoff_tools-1.10.1.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
60
+ dayhoff_tools-1.10.1.dist-info/entry_points.txt,sha256=iAf4jteNqW3cJm6CO6czLxjW3vxYKsyGLZ8WGmxamSc,49
61
+ dayhoff_tools-1.10.1.dist-info/RECORD,,