dayhoff-tools 1.9.11__py3-none-any.whl → 1.9.13__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 +283 -41
- dayhoff_tools/cli/engine/engine_core.py +54 -5
- dayhoff_tools/cli/engine/engine_maintenance.py +56 -10
- {dayhoff_tools-1.9.11.dist-info → dayhoff_tools-1.9.13.dist-info}/METADATA +1 -1
- {dayhoff_tools-1.9.11.dist-info → dayhoff_tools-1.9.13.dist-info}/RECORD +7 -7
- {dayhoff_tools-1.9.11.dist-info → dayhoff_tools-1.9.13.dist-info}/WHEEL +0 -0
- {dayhoff_tools-1.9.11.dist-info → dayhoff_tools-1.9.13.dist-info}/entry_points.txt +0 -0
@@ -6,44 +6,286 @@ import typer
|
|
6
6
|
engine_app = typer.Typer(help="Manage compute engines for development.")
|
7
7
|
studio_app = typer.Typer(help="Manage persistent development studios.")
|
8
8
|
|
9
|
-
#
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
9
|
+
# Use lazy loading pattern similar to main.py swarm commands
|
10
|
+
# Import functions only when commands are actually called
|
11
|
+
|
12
|
+
# Engine commands
|
13
|
+
@engine_app.command("launch")
|
14
|
+
def launch_engine_cmd(
|
15
|
+
name: str = typer.Argument(help="Name for the new engine"),
|
16
|
+
engine_type: str = typer.Option(
|
17
|
+
"cpu",
|
18
|
+
"--type",
|
19
|
+
"-t",
|
20
|
+
help="Engine type: cpu, cpumax, t4, a10g, a100, 4_t4, 8_t4, 4_a10g, 8_a10g",
|
21
|
+
),
|
22
|
+
user: str = typer.Option(None, "--user", "-u", help="Override username"),
|
23
|
+
boot_disk_size: int = typer.Option(
|
24
|
+
None,
|
25
|
+
"--size",
|
26
|
+
"-s",
|
27
|
+
help="Boot disk size in GB (default: 50GB, min: 20GB, max: 1000GB)",
|
28
|
+
),
|
29
|
+
availability_zone: str = typer.Option(
|
30
|
+
None,
|
31
|
+
"--az",
|
32
|
+
help="Prefer a specific Availability Zone (e.g., us-east-1b). If omitted the service will try all public subnets.",
|
33
|
+
),
|
34
|
+
):
|
35
|
+
"""Launch a new engine instance."""
|
36
|
+
from .engine_core import launch_engine
|
37
|
+
return launch_engine(name, engine_type, user, boot_disk_size, availability_zone)
|
38
|
+
|
39
|
+
|
40
|
+
@engine_app.command("list")
|
41
|
+
def list_engines_cmd(
|
42
|
+
user: str = typer.Option(None, "--user", "-u", help="Filter by user"),
|
43
|
+
running_only: bool = typer.Option(
|
44
|
+
False, "--running", help="Show only running engines"
|
45
|
+
),
|
46
|
+
stopped_only: bool = typer.Option(
|
47
|
+
False, "--stopped", help="Show only stopped engines"
|
48
|
+
),
|
49
|
+
detailed: bool = typer.Option(
|
50
|
+
False, "--detailed", "-d", help="Show detailed status (slower)"
|
51
|
+
),
|
52
|
+
):
|
53
|
+
"""List engines (shows all engines by default)."""
|
54
|
+
from .engine_core import list_engines
|
55
|
+
return list_engines(user, running_only, stopped_only, detailed)
|
56
|
+
|
57
|
+
|
58
|
+
@engine_app.command("status")
|
59
|
+
def engine_status_cmd(
|
60
|
+
name_or_id: str = typer.Argument(help="Engine name or instance ID"),
|
61
|
+
detailed: bool = typer.Option(False, "--detailed", "-d", help="Show detailed status (slower)"),
|
62
|
+
show_log: bool = typer.Option(False, "--show-log", help="Show bootstrap log (requires --detailed)"),
|
63
|
+
):
|
64
|
+
"""Show engine status and information."""
|
65
|
+
from .engine_core import engine_status
|
66
|
+
return engine_status(name_or_id, detailed, show_log)
|
67
|
+
|
68
|
+
|
69
|
+
@engine_app.command("start")
|
70
|
+
def start_engine_cmd(
|
71
|
+
name_or_id: str = typer.Argument(help="Engine name or instance ID"),
|
72
|
+
):
|
73
|
+
"""Start a stopped engine."""
|
74
|
+
from .engine_lifecycle import start_engine
|
75
|
+
return start_engine(name_or_id)
|
76
|
+
|
77
|
+
|
78
|
+
@engine_app.command("stop")
|
79
|
+
def stop_engine_cmd(
|
80
|
+
name_or_id: str = typer.Argument(help="Engine name or instance ID"),
|
81
|
+
force: bool = typer.Option(
|
82
|
+
False, "--force", "-f", help="Force stop and detach all studios"
|
83
|
+
),
|
84
|
+
):
|
85
|
+
"""Stop an engine."""
|
86
|
+
from .engine_lifecycle import stop_engine
|
87
|
+
return stop_engine(name_or_id, force)
|
88
|
+
|
89
|
+
|
90
|
+
@engine_app.command("terminate")
|
91
|
+
def terminate_engine_cmd(
|
92
|
+
name_or_id: str = typer.Argument(help="Engine name or instance ID"),
|
93
|
+
):
|
94
|
+
"""Permanently terminate an engine."""
|
95
|
+
from .engine_lifecycle import terminate_engine
|
96
|
+
return terminate_engine(name_or_id)
|
97
|
+
|
98
|
+
|
99
|
+
@engine_app.command("ssh")
|
100
|
+
def ssh_engine_cmd(
|
101
|
+
name_or_id: str = typer.Argument(help="Engine name or instance ID"),
|
102
|
+
admin: bool = typer.Option(
|
103
|
+
False, "--admin", help="Connect as ec2-user instead of the engine owner user"
|
104
|
+
),
|
105
|
+
idle_timeout: int = typer.Option(
|
106
|
+
600,
|
107
|
+
"--idle-timeout",
|
108
|
+
help="Idle timeout (seconds) for the SSM port-forward (0 = disable)",
|
109
|
+
),
|
110
|
+
):
|
111
|
+
"""Connect to an engine via SSH."""
|
112
|
+
from .engine_management import ssh_engine
|
113
|
+
return ssh_engine(name_or_id, admin, idle_timeout)
|
114
|
+
|
115
|
+
|
116
|
+
@engine_app.command("config-ssh")
|
117
|
+
def config_ssh_cmd(
|
118
|
+
clean: bool = typer.Option(False, "--clean", help="Remove all managed entries"),
|
119
|
+
all_engines: bool = typer.Option(
|
120
|
+
False, "--all", "-a", help="Include all engines from all users"
|
121
|
+
),
|
122
|
+
admin: bool = typer.Option(
|
123
|
+
False,
|
124
|
+
"--admin",
|
125
|
+
help="Generate entries that use ec2-user instead of per-engine owner user",
|
126
|
+
),
|
127
|
+
):
|
128
|
+
"""Update SSH config with available engines."""
|
129
|
+
from .engine_management import config_ssh
|
130
|
+
return config_ssh(clean, all_engines, admin)
|
131
|
+
|
132
|
+
|
133
|
+
@engine_app.command("resize")
|
134
|
+
def resize_engine_cmd(
|
135
|
+
name_or_id: str = typer.Argument(help="Engine name or instance ID"),
|
136
|
+
size: int = typer.Option(..., "--size", "-s", help="New size in GB"),
|
137
|
+
online: bool = typer.Option(
|
138
|
+
False,
|
139
|
+
"--online",
|
140
|
+
help="Resize while running (requires manual filesystem expansion)",
|
141
|
+
),
|
142
|
+
force: bool = typer.Option(
|
143
|
+
False, "--force", "-f", help="Force resize and detach all studios"
|
144
|
+
),
|
145
|
+
):
|
146
|
+
"""Resize an engine's boot disk."""
|
147
|
+
from .engine_management import resize_engine
|
148
|
+
return resize_engine(name_or_id, size, online, force)
|
149
|
+
|
150
|
+
|
151
|
+
@engine_app.command("gami")
|
152
|
+
def create_ami_cmd(
|
153
|
+
name_or_id: str = typer.Argument(
|
154
|
+
help="Engine name or instance ID to create AMI from"
|
155
|
+
),
|
156
|
+
):
|
157
|
+
"""Create a 'Golden AMI' from a running engine."""
|
158
|
+
from .engine_management import create_ami
|
159
|
+
return create_ami(name_or_id)
|
160
|
+
|
161
|
+
|
162
|
+
@engine_app.command("coffee")
|
163
|
+
def coffee_cmd(
|
164
|
+
name_or_id: str = typer.Argument(help="Engine name or instance ID"),
|
165
|
+
duration: str = typer.Argument("4h", help="Duration (e.g., 2h, 30m, 2h30m)"),
|
166
|
+
cancel: bool = typer.Option(
|
167
|
+
False, "--cancel", help="Cancel existing coffee lock instead of extending"
|
168
|
+
),
|
169
|
+
):
|
170
|
+
"""Pour ☕ for an engine: keeps it awake for the given duration (or cancel)."""
|
171
|
+
from .engine_maintenance import coffee
|
172
|
+
return coffee(name_or_id, duration, cancel)
|
173
|
+
|
174
|
+
|
175
|
+
@engine_app.command("idle")
|
176
|
+
def idle_timeout_cmd_wrapper(
|
177
|
+
name_or_id: str = typer.Argument(help="Engine name or instance ID"),
|
178
|
+
set: str = typer.Option(
|
179
|
+
None, "--set", "-s", help="New timeout (e.g., 2h30m, 45m)"
|
180
|
+
),
|
181
|
+
):
|
182
|
+
"""Show or set the engine idle-detector timeout."""
|
183
|
+
from .engine_maintenance import idle_timeout_cmd
|
184
|
+
return idle_timeout_cmd(name_or_id, set)
|
185
|
+
|
186
|
+
|
187
|
+
@engine_app.command("debug")
|
188
|
+
def debug_engine_cmd(
|
189
|
+
name_or_id: str = typer.Argument(help="Engine name or instance ID"),
|
190
|
+
):
|
191
|
+
"""Debug engine bootstrap status and files."""
|
192
|
+
from .engine_maintenance import debug_engine
|
193
|
+
return debug_engine(name_or_id)
|
194
|
+
|
195
|
+
|
196
|
+
@engine_app.command("repair")
|
197
|
+
def repair_engine_cmd(
|
198
|
+
name_or_id: str = typer.Argument(help="Engine name or instance ID"),
|
199
|
+
):
|
200
|
+
"""Repair an engine that's stuck in a bad state (e.g., after GAMI creation)."""
|
201
|
+
from .engine_maintenance import repair_engine
|
202
|
+
return repair_engine(name_or_id)
|
203
|
+
|
204
|
+
|
205
|
+
# Studio commands
|
206
|
+
@studio_app.command("create")
|
207
|
+
def create_studio_cmd(
|
208
|
+
size_gb: int = typer.Option(50, "--size", "-s", help="Studio size in GB"),
|
209
|
+
):
|
210
|
+
"""Create a new studio for the current user."""
|
211
|
+
from .studio_commands import create_studio
|
212
|
+
return create_studio(size_gb)
|
213
|
+
|
214
|
+
|
215
|
+
@studio_app.command("status")
|
216
|
+
def studio_status_cmd(
|
217
|
+
user: str = typer.Option(
|
218
|
+
None, "--user", "-u", help="Check status for a different user (admin only)"
|
219
|
+
),
|
220
|
+
):
|
221
|
+
"""Show status of your studio."""
|
222
|
+
from .studio_commands import studio_status
|
223
|
+
return studio_status(user)
|
224
|
+
|
225
|
+
|
226
|
+
@studio_app.command("attach")
|
227
|
+
def attach_studio_cmd(
|
228
|
+
engine_name_or_id: str = typer.Argument(help="Engine name or instance ID"),
|
229
|
+
user: str = typer.Option(
|
230
|
+
None, "--user", "-u", help="Attach a different user's studio (admin only)"
|
231
|
+
),
|
232
|
+
):
|
233
|
+
"""Attach your studio to an engine."""
|
234
|
+
from .studio_commands import attach_studio
|
235
|
+
return attach_studio(engine_name_or_id, user)
|
236
|
+
|
237
|
+
|
238
|
+
@studio_app.command("detach")
|
239
|
+
def detach_studio_cmd(
|
240
|
+
user: str = typer.Option(
|
241
|
+
None, "--user", "-u", help="Detach a different user's studio (admin only)"
|
242
|
+
),
|
243
|
+
):
|
244
|
+
"""Detach your studio from its current engine."""
|
245
|
+
from .studio_commands import detach_studio
|
246
|
+
return detach_studio(user)
|
247
|
+
|
248
|
+
|
249
|
+
@studio_app.command("delete")
|
250
|
+
def delete_studio_cmd(
|
251
|
+
user: str = typer.Option(
|
252
|
+
None, "--user", "-u", help="Delete a different user's studio (admin only)"
|
253
|
+
),
|
254
|
+
):
|
255
|
+
"""Delete your studio permanently."""
|
256
|
+
from .studio_commands import delete_studio
|
257
|
+
return delete_studio(user)
|
258
|
+
|
259
|
+
|
260
|
+
@studio_app.command("list")
|
261
|
+
def list_studios_cmd(
|
262
|
+
all_users: bool = typer.Option(
|
263
|
+
False, "--all", "-a", help="Show all users' studios"
|
264
|
+
),
|
265
|
+
):
|
266
|
+
"""List studios."""
|
267
|
+
from .studio_commands import list_studios
|
268
|
+
return list_studios(all_users)
|
269
|
+
|
270
|
+
|
271
|
+
@studio_app.command("reset")
|
272
|
+
def reset_studio_cmd(
|
273
|
+
user: str = typer.Option(
|
274
|
+
None, "--user", "-u", help="Reset a different user's studio"
|
275
|
+
),
|
276
|
+
):
|
277
|
+
"""Reset a stuck studio (admin operation)."""
|
278
|
+
from .studio_commands import reset_studio
|
279
|
+
return reset_studio(user)
|
280
|
+
|
281
|
+
|
282
|
+
@studio_app.command("resize")
|
283
|
+
def resize_studio_cmd(
|
284
|
+
size: int = typer.Option(..., "--size", "-s", help="New size in GB"),
|
285
|
+
user: str = typer.Option(
|
286
|
+
None, "--user", "-u", help="Resize a different user's studio (admin only)"
|
287
|
+
),
|
288
|
+
):
|
289
|
+
"""Resize your studio volume (requires detachment)."""
|
290
|
+
from .studio_commands import resize_studio
|
291
|
+
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=CGJ2blhWIIEsVb8HoLibZjSlMFRTSYZOO4zDQTtY3SY,9300
|
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.13.dist-info/METADATA,sha256=Sf1ILU9mg5Cvs7NepbxAB0NJSuKM_gMqrKopTykTqDM,2915
|
37
|
+
dayhoff_tools-1.9.13.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
38
|
+
dayhoff_tools-1.9.13.dist-info/entry_points.txt,sha256=iAf4jteNqW3cJm6CO6czLxjW3vxYKsyGLZ8WGmxamSc,49
|
39
|
+
dayhoff_tools-1.9.13.dist-info/RECORD,,
|
File without changes
|
File without changes
|