dayhoff-tools 1.9.12__py3-none-any.whl → 1.9.14__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/__init__.py +38 -6
- dayhoff_tools/cli/engine/engine_core.py +54 -5
- dayhoff_tools/cli/engine/engine_maintenance.py +56 -10
- {dayhoff_tools-1.9.12.dist-info → dayhoff_tools-1.9.14.dist-info}/METADATA +1 -1
- {dayhoff_tools-1.9.12.dist-info → dayhoff_tools-1.9.14.dist-info}/RECORD +7 -7
- {dayhoff_tools-1.9.12.dist-info → dayhoff_tools-1.9.14.dist-info}/WHEEL +0 -0
- {dayhoff_tools-1.9.12.dist-info → dayhoff_tools-1.9.14.dist-info}/entry_points.txt +0 -0
@@ -1,5 +1,7 @@
|
|
1
1
|
"""Engine and Studio management commands for DHT CLI."""
|
2
2
|
|
3
|
+
from typing import Optional
|
4
|
+
|
3
5
|
import typer
|
4
6
|
|
5
7
|
# Initialize Typer apps
|
@@ -9,6 +11,7 @@ studio_app = typer.Typer(help="Manage persistent development studios.")
|
|
9
11
|
# Use lazy loading pattern similar to main.py swarm commands
|
10
12
|
# Import functions only when commands are actually called
|
11
13
|
|
14
|
+
|
12
15
|
# Engine commands
|
13
16
|
@engine_app.command("launch")
|
14
17
|
def launch_engine_cmd(
|
@@ -34,6 +37,7 @@ def launch_engine_cmd(
|
|
34
37
|
):
|
35
38
|
"""Launch a new engine instance."""
|
36
39
|
from .engine_core import launch_engine
|
40
|
+
|
37
41
|
return launch_engine(name, engine_type, user, boot_disk_size, availability_zone)
|
38
42
|
|
39
43
|
|
@@ -52,17 +56,23 @@ def list_engines_cmd(
|
|
52
56
|
):
|
53
57
|
"""List engines (shows all engines by default)."""
|
54
58
|
from .engine_core import list_engines
|
59
|
+
|
55
60
|
return list_engines(user, running_only, stopped_only, detailed)
|
56
61
|
|
57
62
|
|
58
63
|
@engine_app.command("status")
|
59
64
|
def engine_status_cmd(
|
60
65
|
name_or_id: str = typer.Argument(help="Engine name or instance ID"),
|
61
|
-
detailed: bool = typer.Option(
|
62
|
-
|
66
|
+
detailed: bool = typer.Option(
|
67
|
+
False, "--detailed", "-d", help="Show detailed status (slower)"
|
68
|
+
),
|
69
|
+
show_log: bool = typer.Option(
|
70
|
+
False, "--show-log", help="Show bootstrap log (requires --detailed)"
|
71
|
+
),
|
63
72
|
):
|
64
73
|
"""Show engine status and information."""
|
65
74
|
from .engine_core import engine_status
|
75
|
+
|
66
76
|
return engine_status(name_or_id, detailed, show_log)
|
67
77
|
|
68
78
|
|
@@ -72,6 +82,7 @@ def start_engine_cmd(
|
|
72
82
|
):
|
73
83
|
"""Start a stopped engine."""
|
74
84
|
from .engine_lifecycle import start_engine
|
85
|
+
|
75
86
|
return start_engine(name_or_id)
|
76
87
|
|
77
88
|
|
@@ -84,6 +95,7 @@ def stop_engine_cmd(
|
|
84
95
|
):
|
85
96
|
"""Stop an engine."""
|
86
97
|
from .engine_lifecycle import stop_engine
|
98
|
+
|
87
99
|
return stop_engine(name_or_id, force)
|
88
100
|
|
89
101
|
|
@@ -93,6 +105,7 @@ def terminate_engine_cmd(
|
|
93
105
|
):
|
94
106
|
"""Permanently terminate an engine."""
|
95
107
|
from .engine_lifecycle import terminate_engine
|
108
|
+
|
96
109
|
return terminate_engine(name_or_id)
|
97
110
|
|
98
111
|
|
@@ -110,6 +123,7 @@ def ssh_engine_cmd(
|
|
110
123
|
):
|
111
124
|
"""Connect to an engine via SSH."""
|
112
125
|
from .engine_management import ssh_engine
|
126
|
+
|
113
127
|
return ssh_engine(name_or_id, admin, idle_timeout)
|
114
128
|
|
115
129
|
|
@@ -127,6 +141,7 @@ def config_ssh_cmd(
|
|
127
141
|
):
|
128
142
|
"""Update SSH config with available engines."""
|
129
143
|
from .engine_management import config_ssh
|
144
|
+
|
130
145
|
return config_ssh(clean, all_engines, admin)
|
131
146
|
|
132
147
|
|
@@ -145,6 +160,7 @@ def resize_engine_cmd(
|
|
145
160
|
):
|
146
161
|
"""Resize an engine's boot disk."""
|
147
162
|
from .engine_management import resize_engine
|
163
|
+
|
148
164
|
return resize_engine(name_or_id, size, online, force)
|
149
165
|
|
150
166
|
|
@@ -156,6 +172,7 @@ def create_ami_cmd(
|
|
156
172
|
):
|
157
173
|
"""Create a 'Golden AMI' from a running engine."""
|
158
174
|
from .engine_management import create_ami
|
175
|
+
|
159
176
|
return create_ami(name_or_id)
|
160
177
|
|
161
178
|
|
@@ -169,19 +186,24 @@ def coffee_cmd(
|
|
169
186
|
):
|
170
187
|
"""Pour ☕ for an engine: keeps it awake for the given duration (or cancel)."""
|
171
188
|
from .engine_maintenance import coffee
|
189
|
+
|
172
190
|
return coffee(name_or_id, duration, cancel)
|
173
191
|
|
174
192
|
|
175
193
|
@engine_app.command("idle")
|
176
194
|
def idle_timeout_cmd_wrapper(
|
177
195
|
name_or_id: str = typer.Argument(help="Engine name or instance ID"),
|
178
|
-
set: str = typer.Option(
|
196
|
+
set: Optional[str] = typer.Option(
|
179
197
|
None, "--set", "-s", help="New timeout (e.g., 2h30m, 45m)"
|
180
198
|
),
|
199
|
+
slack: Optional[str] = typer.Option(
|
200
|
+
None, "--slack", help="Set Slack notifications: none, default, all"
|
201
|
+
),
|
181
202
|
):
|
182
|
-
"""Show or set
|
203
|
+
"""Show or set engine idle-detector settings."""
|
183
204
|
from .engine_maintenance import idle_timeout_cmd
|
184
|
-
|
205
|
+
|
206
|
+
return idle_timeout_cmd(name_or_id=name_or_id, set=set, slack=slack)
|
185
207
|
|
186
208
|
|
187
209
|
@engine_app.command("debug")
|
@@ -190,6 +212,7 @@ def debug_engine_cmd(
|
|
190
212
|
):
|
191
213
|
"""Debug engine bootstrap status and files."""
|
192
214
|
from .engine_maintenance import debug_engine
|
215
|
+
|
193
216
|
return debug_engine(name_or_id)
|
194
217
|
|
195
218
|
|
@@ -199,6 +222,7 @@ def repair_engine_cmd(
|
|
199
222
|
):
|
200
223
|
"""Repair an engine that's stuck in a bad state (e.g., after GAMI creation)."""
|
201
224
|
from .engine_maintenance import repair_engine
|
225
|
+
|
202
226
|
return repair_engine(name_or_id)
|
203
227
|
|
204
228
|
|
@@ -209,6 +233,7 @@ def create_studio_cmd(
|
|
209
233
|
):
|
210
234
|
"""Create a new studio for the current user."""
|
211
235
|
from .studio_commands import create_studio
|
236
|
+
|
212
237
|
return create_studio(size_gb)
|
213
238
|
|
214
239
|
|
@@ -220,6 +245,7 @@ def studio_status_cmd(
|
|
220
245
|
):
|
221
246
|
"""Show status of your studio."""
|
222
247
|
from .studio_commands import studio_status
|
248
|
+
|
223
249
|
return studio_status(user)
|
224
250
|
|
225
251
|
|
@@ -232,6 +258,7 @@ def attach_studio_cmd(
|
|
232
258
|
):
|
233
259
|
"""Attach your studio to an engine."""
|
234
260
|
from .studio_commands import attach_studio
|
261
|
+
|
235
262
|
return attach_studio(engine_name_or_id, user)
|
236
263
|
|
237
264
|
|
@@ -243,6 +270,7 @@ def detach_studio_cmd(
|
|
243
270
|
):
|
244
271
|
"""Detach your studio from its current engine."""
|
245
272
|
from .studio_commands import detach_studio
|
273
|
+
|
246
274
|
return detach_studio(user)
|
247
275
|
|
248
276
|
|
@@ -254,6 +282,7 @@ def delete_studio_cmd(
|
|
254
282
|
):
|
255
283
|
"""Delete your studio permanently."""
|
256
284
|
from .studio_commands import delete_studio
|
285
|
+
|
257
286
|
return delete_studio(user)
|
258
287
|
|
259
288
|
|
@@ -265,6 +294,7 @@ def list_studios_cmd(
|
|
265
294
|
):
|
266
295
|
"""List studios."""
|
267
296
|
from .studio_commands import list_studios
|
297
|
+
|
268
298
|
return list_studios(all_users)
|
269
299
|
|
270
300
|
|
@@ -276,6 +306,7 @@ def reset_studio_cmd(
|
|
276
306
|
):
|
277
307
|
"""Reset a stuck studio (admin operation)."""
|
278
308
|
from .studio_commands import reset_studio
|
309
|
+
|
279
310
|
return reset_studio(user)
|
280
311
|
|
281
312
|
|
@@ -288,4 +319,5 @@ def resize_studio_cmd(
|
|
288
319
|
):
|
289
320
|
"""Resize your studio volume (requires detachment)."""
|
290
321
|
from .studio_commands import resize_studio
|
291
|
-
|
322
|
+
|
323
|
+
return resize_studio(size, user)
|
@@ -217,8 +217,12 @@ def list_engines(
|
|
217
217
|
|
218
218
|
def engine_status(
|
219
219
|
name_or_id: str = typer.Argument(help="Engine name or instance ID"),
|
220
|
-
detailed: bool = typer.Option(
|
221
|
-
|
220
|
+
detailed: bool = typer.Option(
|
221
|
+
False, "--detailed", "-d", help="Show detailed status (slower)"
|
222
|
+
),
|
223
|
+
show_log: bool = typer.Option(
|
224
|
+
False, "--show-log", help="Show bootstrap log (requires --detailed)"
|
225
|
+
),
|
222
226
|
):
|
223
227
|
"""Show engine status and information."""
|
224
228
|
check_aws_sso()
|
@@ -477,6 +481,47 @@ def engine_status(
|
|
477
481
|
except Exception:
|
478
482
|
pass
|
479
483
|
|
484
|
+
# Slack notifications status (detailed view only)
|
485
|
+
try:
|
486
|
+
ssm = boto3.client("ssm", region_name="us-east-1")
|
487
|
+
resp = ssm.send_command(
|
488
|
+
InstanceIds=[engine["instance_id"]],
|
489
|
+
DocumentName="AWS-RunShellScript",
|
490
|
+
Parameters={
|
491
|
+
"commands": ["grep '^SLACK_NOTIFY_' /etc/engine.env || true"],
|
492
|
+
"executionTimeout": ["10"],
|
493
|
+
},
|
494
|
+
)
|
495
|
+
cid = resp["Command"]["CommandId"]
|
496
|
+
time.sleep(1)
|
497
|
+
inv = ssm.get_command_invocation(
|
498
|
+
CommandId=cid, InstanceId=engine["instance_id"]
|
499
|
+
)
|
500
|
+
if inv["Status"] == "Success":
|
501
|
+
settings_raw = inv["StandardOutputContent"].strip()
|
502
|
+
settings = {}
|
503
|
+
for line in settings_raw.splitlines():
|
504
|
+
if "=" in line:
|
505
|
+
key, value = line.split("=", 1)
|
506
|
+
settings[key.strip()] = value.strip().lower()
|
507
|
+
|
508
|
+
status_lines.append("")
|
509
|
+
status_lines.append("[bold]Slack Notifications:[/bold]")
|
510
|
+
|
511
|
+
def _setting_line(label: str, key: str) -> str:
|
512
|
+
val = settings.get(key, "false") # Default to false if not set
|
513
|
+
status = "[green]on[/green]" if val == "true" else "[dim]off[/dim]"
|
514
|
+
return f" - {label:15} {status}"
|
515
|
+
|
516
|
+
status_lines.append(_setting_line("Idle Start", "SLACK_NOTIFY_IDLE_START"))
|
517
|
+
status_lines.append(
|
518
|
+
_setting_line("IDE Disconnect", "SLACK_NOTIFY_IDE_DISCONNECT")
|
519
|
+
)
|
520
|
+
status_lines.append(_setting_line("Warnings", "SLACK_NOTIFY_WARNINGS"))
|
521
|
+
status_lines.append(_setting_line("Shutdown", "SLACK_NOTIFY_SHUTDOWN"))
|
522
|
+
except Exception:
|
523
|
+
pass
|
524
|
+
|
480
525
|
# Activity Sensors (show all with YES/no)
|
481
526
|
if idle_detector.get("available"):
|
482
527
|
status_lines.append("")
|
@@ -564,10 +609,14 @@ def _format_idle_status_display(
|
|
564
609
|
):
|
565
610
|
remaining = max(0, int(thresh_v) - int(idle_seconds_v))
|
566
611
|
remaining_mins = remaining // 60
|
567
|
-
|
568
|
-
|
612
|
+
remaining_secs = remaining % 60
|
613
|
+
|
614
|
+
if remaining < 60:
|
615
|
+
time_left_str = f"[red]{remaining}s[/red] left"
|
569
616
|
else:
|
570
|
-
|
617
|
+
time_left_str = f"[red]{remaining_mins}m {remaining_secs}s[/red] left"
|
618
|
+
|
619
|
+
return f"[yellow]Idle {int(idle_seconds_v)//60}m/{int(thresh_v)//60}m: {time_left_str}[/yellow]"
|
571
620
|
elif isinstance(thresh_v, (int, float)):
|
572
621
|
return f"[yellow]Idle ?/{int(thresh_v)//60}m[/yellow]"
|
573
622
|
else:
|
@@ -11,12 +11,7 @@ from botocore.exceptions import ClientError
|
|
11
11
|
from rich.progress import Progress, SpinnerColumn, TextColumn
|
12
12
|
from rich.prompt import Confirm
|
13
13
|
|
14
|
-
from .shared import
|
15
|
-
check_aws_sso,
|
16
|
-
console,
|
17
|
-
make_api_request,
|
18
|
-
resolve_engine,
|
19
|
-
)
|
14
|
+
from .shared import check_aws_sso, console, make_api_request, resolve_engine
|
20
15
|
|
21
16
|
|
22
17
|
def coffee(
|
@@ -123,9 +118,11 @@ def idle_timeout_cmd(
|
|
123
118
|
set: Optional[str] = typer.Option(
|
124
119
|
None, "--set", "-s", help="New timeout (e.g., 2h30m, 45m)"
|
125
120
|
),
|
126
|
-
|
121
|
+
slack: Optional[str] = typer.Option(
|
122
|
+
None, "--slack", help="Set Slack notifications: none, default, all"
|
123
|
+
),
|
127
124
|
):
|
128
|
-
"""Show or set
|
125
|
+
"""Show or set engine idle-detector settings."""
|
129
126
|
check_aws_sso()
|
130
127
|
|
131
128
|
# Resolve engine
|
@@ -139,7 +136,56 @@ def idle_timeout_cmd(
|
|
139
136
|
|
140
137
|
ssm = boto3.client("ssm", region_name="us-east-1")
|
141
138
|
|
142
|
-
|
139
|
+
# Handle slack notifications change
|
140
|
+
if slack:
|
141
|
+
slack = slack.lower()
|
142
|
+
if slack not in ["none", "default", "all"]:
|
143
|
+
console.print("[red]❌ Invalid slack option. Use: none, default, all[/red]")
|
144
|
+
raise typer.Exit(1)
|
145
|
+
|
146
|
+
console.print(f"Setting Slack notifications to [bold]{slack}[/bold]...")
|
147
|
+
|
148
|
+
if slack == "none":
|
149
|
+
settings = {
|
150
|
+
"SLACK_NOTIFY_WARNINGS": "false",
|
151
|
+
"SLACK_NOTIFY_IDLE_START": "false",
|
152
|
+
"SLACK_NOTIFY_IDE_DISCONNECT": "false",
|
153
|
+
"SLACK_NOTIFY_SHUTDOWN": "false",
|
154
|
+
}
|
155
|
+
elif slack == "default":
|
156
|
+
settings = {
|
157
|
+
"SLACK_NOTIFY_WARNINGS": "true",
|
158
|
+
"SLACK_NOTIFY_IDLE_START": "false",
|
159
|
+
"SLACK_NOTIFY_IDE_DISCONNECT": "false",
|
160
|
+
"SLACK_NOTIFY_SHUTDOWN": "true",
|
161
|
+
}
|
162
|
+
else: # all
|
163
|
+
settings = {
|
164
|
+
"SLACK_NOTIFY_WARNINGS": "true",
|
165
|
+
"SLACK_NOTIFY_IDLE_START": "true",
|
166
|
+
"SLACK_NOTIFY_IDE_DISCONNECT": "true",
|
167
|
+
"SLACK_NOTIFY_SHUTDOWN": "true",
|
168
|
+
}
|
169
|
+
|
170
|
+
commands = []
|
171
|
+
for key, value in settings.items():
|
172
|
+
# Use a robust sed command that adds the line if it doesn't exist
|
173
|
+
commands.append(
|
174
|
+
f"grep -q '^{key}=' /etc/engine.env && sudo sed -i 's|^{key}=.*|{key}={value}|' /etc/engine.env || echo '{key}={value}' | sudo tee -a /etc/engine.env > /dev/null"
|
175
|
+
)
|
176
|
+
|
177
|
+
commands.append("sudo systemctl restart engine-idle-detector.service")
|
178
|
+
|
179
|
+
resp = ssm.send_command(
|
180
|
+
InstanceIds=[engine["instance_id"]],
|
181
|
+
DocumentName="AWS-RunShellScript",
|
182
|
+
Parameters={"commands": commands, "executionTimeout": ["60"]},
|
183
|
+
)
|
184
|
+
cid = resp["Command"]["CommandId"]
|
185
|
+
time.sleep(2) # Give it a moment to process
|
186
|
+
console.print(f"[green]✓ Slack notifications updated to '{slack}'[/green]")
|
187
|
+
|
188
|
+
if set is None and not slack:
|
143
189
|
# Show current timeout setting
|
144
190
|
resp = ssm.send_command(
|
145
191
|
InstanceIds=[engine["instance_id"]],
|
@@ -164,7 +210,7 @@ def idle_timeout_cmd(
|
|
164
210
|
console.print("[red]❌ Could not retrieve idle timeout[/red]")
|
165
211
|
return
|
166
212
|
|
167
|
-
# ----- set new value -----
|
213
|
+
# ----- set new value for timeout -----
|
168
214
|
m = re.match(r"^(?:(\d+)h)?(?:(\d+)m)?$", set)
|
169
215
|
if not m:
|
170
216
|
console.print("[red]❌ Invalid duration format. Use e.g. 2h, 45m, 1h30m[/red]")
|
@@ -3,10 +3,10 @@ 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/__init__.py,sha256=
|
7
|
-
dayhoff_tools/cli/engine/engine_core.py,sha256=
|
6
|
+
dayhoff_tools/cli/engine/__init__.py,sha256=RE45X2IPCNUvQp6_OHI2lbjwoqexkH3roUPccIHkeJo,9540
|
7
|
+
dayhoff_tools/cli/engine/engine_core.py,sha256=ItfqW3ZWoHEWLOgtWPYT-SO1idxk5RBBCIe8w9xxf3w,26959
|
8
8
|
dayhoff_tools/cli/engine/engine_lifecycle.py,sha256=_Dk-EZs_qbm8APdOuGOuxhlbK6RgkkoLk2nrwKoo1-A,4519
|
9
|
-
dayhoff_tools/cli/engine/engine_maintenance.py,sha256=
|
9
|
+
dayhoff_tools/cli/engine/engine_maintenance.py,sha256=xZgEgpjwTDI2RpoN7P6E7MEGiIA8RJpb0MkCT9u2R14,15889
|
10
10
|
dayhoff_tools/cli/engine/engine_management.py,sha256=s_H3FtMlKsdfzR8pwV-j2W2QX-Fypkqj2kPC0aTqC1A,19072
|
11
11
|
dayhoff_tools/cli/engine/shared.py,sha256=Ecx6I1jtzmxQDn3BezKpgpQ4SJeZf4SZjUCLg-67p80,16844
|
12
12
|
dayhoff_tools/cli/engine/studio_commands.py,sha256=VwTQujz32-uMcYusDRE73SdzRpgvIkv7ZAF4zRv6AzA,30266
|
@@ -33,7 +33,7 @@ dayhoff_tools/intake/uniprot.py,sha256=BZYJQF63OtPcBBnQ7_P9gulxzJtqyorgyuDiPeOJq
|
|
33
33
|
dayhoff_tools/logs.py,sha256=DKdeP0k0kliRcilwvX0mUB2eipO5BdWUeHwh-VnsICs,838
|
34
34
|
dayhoff_tools/sqlite.py,sha256=jV55ikF8VpTfeQqqlHSbY8OgfyfHj8zgHNpZjBLos_E,18672
|
35
35
|
dayhoff_tools/warehouse.py,sha256=UETBtZD3r7WgvURqfGbyHlT7cxoiVq8isjzMuerKw8I,24475
|
36
|
-
dayhoff_tools-1.9.
|
37
|
-
dayhoff_tools-1.9.
|
38
|
-
dayhoff_tools-1.9.
|
39
|
-
dayhoff_tools-1.9.
|
36
|
+
dayhoff_tools-1.9.14.dist-info/METADATA,sha256=EewV69EQMTMaa0DUiq2t7J9w6WzsTv7Qd7Pz0Jcjg-w,2915
|
37
|
+
dayhoff_tools-1.9.14.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
38
|
+
dayhoff_tools-1.9.14.dist-info/entry_points.txt,sha256=iAf4jteNqW3cJm6CO6czLxjW3vxYKsyGLZ8WGmxamSc,49
|
39
|
+
dayhoff_tools-1.9.14.dist-info/RECORD,,
|
File without changes
|
File without changes
|