lyceum-cli 1.1.7__tar.gz → 1.2.2__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.
- {lyceum_cli-1.1.7 → lyceum_cli-1.2.2}/PKG-INFO +7 -8
- lyceum_cli-1.2.2/lyceum/_version.py +10 -0
- {lyceum_cli-1.1.7 → lyceum_cli-1.2.2}/lyceum/external/auth/keys.py +1 -0
- {lyceum_cli-1.1.7 → lyceum_cli-1.2.2}/lyceum/external/auth/login.py +44 -27
- {lyceum_cli-1.1.7 → lyceum_cli-1.2.2}/lyceum/external/compute/execution/config.py +15 -14
- {lyceum_cli-1.1.7 → lyceum_cli-1.2.2}/lyceum/external/compute/execution/docker.py +30 -54
- {lyceum_cli-1.1.7 → lyceum_cli-1.2.2}/lyceum/external/compute/execution/docker_compose.py +28 -53
- lyceum_cli-1.2.2/lyceum/external/compute/execution/gpu_selection.py +59 -0
- {lyceum_cli-1.1.7 → lyceum_cli-1.2.2}/lyceum/external/compute/execution/jobs.py +1 -1
- {lyceum_cli-1.1.7 → lyceum_cli-1.2.2}/lyceum/external/compute/execution/notebook.py +11 -21
- {lyceum_cli-1.1.7 → lyceum_cli-1.2.2}/lyceum/external/compute/execution/python.py +20 -32
- {lyceum_cli-1.1.7 → lyceum_cli-1.2.2}/lyceum/external/compute/inference/chat.py +19 -5
- {lyceum_cli-1.1.7 → lyceum_cli-1.2.2}/lyceum/external/compute/inference/claude_code.py +6 -3
- {lyceum_cli-1.1.7 → lyceum_cli-1.2.2}/lyceum/external/compute/inference/infer.py +133 -34
- {lyceum_cli-1.1.7 → lyceum_cli-1.2.2}/lyceum/external/orgs/management.py +25 -19
- lyceum_cli-1.2.2/lyceum/external/storage/block.py +114 -0
- {lyceum_cli-1.1.7 → lyceum_cli-1.2.2}/lyceum/external/storage/files.py +24 -11
- {lyceum_cli-1.1.7 → lyceum_cli-1.2.2}/lyceum/external/vms/management.py +189 -17
- {lyceum_cli-1.1.7 → lyceum_cli-1.2.2}/lyceum/main.py +2 -3
- {lyceum_cli-1.1.7 → lyceum_cli-1.2.2}/lyceum/shared/imports.py +174 -46
- lyceum_cli-1.2.2/lyceum/shared/orgs.py +132 -0
- {lyceum_cli-1.1.7 → lyceum_cli-1.2.2}/lyceum/shared/run_options.py +3 -9
- {lyceum_cli-1.1.7 → lyceum_cli-1.2.2}/lyceum/shared/streaming.py +20 -12
- {lyceum_cli-1.1.7 → lyceum_cli-1.2.2}/lyceum/shared/ui.py +10 -1
- {lyceum_cli-1.1.7 → lyceum_cli-1.2.2}/lyceum_cli.egg-info/PKG-INFO +7 -8
- {lyceum_cli-1.1.7 → lyceum_cli-1.2.2}/lyceum_cli.egg-info/SOURCES.txt +7 -2
- lyceum_cli-1.2.2/lyceum_cli.egg-info/requires.txt +5 -0
- {lyceum_cli-1.1.7 → lyceum_cli-1.2.2}/pyproject.toml +12 -8
- lyceum_cli-1.2.2/tests/test_block_storage.py +399 -0
- lyceum_cli-1.2.2/tests/test_dedicated_deploy.py +105 -0
- lyceum_cli-1.2.2/tests/test_orgs.py +104 -0
- lyceum_cli-1.1.7/lyceum/__init__.py +0 -1
- lyceum_cli-1.1.7/lyceum/external/compute/execution/gpu_selection.py +0 -888
- lyceum_cli-1.1.7/lyceum_cli.egg-info/requires.txt +0 -5
- {lyceum_cli-1.1.7 → lyceum_cli-1.2.2}/README.md +0 -0
- {lyceum_cli-1.1.7 → lyceum_cli-1.2.2}/lyceum/__main__.py +0 -0
- {lyceum_cli-1.1.7 → lyceum_cli-1.2.2}/lyceum/external/__init__.py +0 -0
- {lyceum_cli-1.1.7 → lyceum_cli-1.2.2}/lyceum/external/auth/__init__.py +0 -0
- {lyceum_cli-1.1.7 → lyceum_cli-1.2.2}/lyceum/external/auth/environment.py +0 -0
- {lyceum_cli-1.1.7 → lyceum_cli-1.2.2}/lyceum/external/compute/__init__.py +0 -0
- {lyceum_cli-1.1.7 → lyceum_cli-1.2.2}/lyceum/external/compute/execution/__init__.py +0 -0
- {lyceum_cli-1.1.7 → lyceum_cli-1.2.2}/lyceum/external/compute/inference/__init__.py +0 -0
- {lyceum_cli-1.1.7 → lyceum_cli-1.2.2}/lyceum/external/orgs/__init__.py +0 -0
- {lyceum_cli-1.1.7 → lyceum_cli-1.2.2}/lyceum/external/storage/__init__.py +0 -0
- {lyceum_cli-1.1.7 → lyceum_cli-1.2.2}/lyceum/external/vms/__init__.py +0 -0
- {lyceum_cli-1.1.7 → lyceum_cli-1.2.2}/lyceum/shared/__init__.py +0 -0
- {lyceum_cli-1.1.7 → lyceum_cli-1.2.2}/lyceum/shared/config.py +0 -0
- {lyceum_cli-1.1.7 → lyceum_cli-1.2.2}/lyceum/shared/http.py +0 -0
- {lyceum_cli-1.1.7 → lyceum_cli-1.2.2}/lyceum_cli.egg-info/dependency_links.txt +0 -0
- {lyceum_cli-1.1.7 → lyceum_cli-1.2.2}/lyceum_cli.egg-info/entry_points.txt +0 -0
- {lyceum_cli-1.1.7 → lyceum_cli-1.2.2}/lyceum_cli.egg-info/top_level.txt +0 -0
- {lyceum_cli-1.1.7 → lyceum_cli-1.2.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lyceum-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.2
|
|
4
4
|
Summary: Command-line interface for Lyceum Cloud
|
|
5
5
|
Author-email: Lyceum <support@lyceum.technology>
|
|
6
6
|
License: MIT
|
|
@@ -13,20 +13,19 @@ Classifier: Intended Audience :: Developers
|
|
|
13
13
|
Classifier: License :: OSI Approved :: MIT License
|
|
14
14
|
Classifier: Operating System :: OS Independent
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.13
|
|
20
19
|
Classifier: Topic :: Software Development
|
|
21
20
|
Classifier: Topic :: System :: Distributed Computing
|
|
22
21
|
Classifier: Topic :: Utilities
|
|
23
|
-
Requires-Python: >=3.
|
|
22
|
+
Requires-Python: >=3.11
|
|
24
23
|
Description-Content-Type: text/markdown
|
|
25
|
-
Requires-Dist: typer
|
|
26
|
-
Requires-Dist: rich
|
|
27
|
-
Requires-Dist: httpx
|
|
28
|
-
Requires-Dist: PyJWT
|
|
29
|
-
Requires-Dist: boto3
|
|
24
|
+
Requires-Dist: typer==0.24.1
|
|
25
|
+
Requires-Dist: rich==14.3.3
|
|
26
|
+
Requires-Dist: httpx==0.28.1
|
|
27
|
+
Requires-Dist: PyJWT==2.13.0
|
|
28
|
+
Requires-Dist: boto3==1.42.62
|
|
30
29
|
|
|
31
30
|
# Lyceum CLI
|
|
32
31
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""The version lyceum-cli publishes under.
|
|
2
|
+
|
|
3
|
+
NOTE: This package deliberately has no __init__.py. 'lyceum' is a namespace
|
|
4
|
+
shared with lyceum-shared-models and lyceum-neph. An __init__.py here would
|
|
5
|
+
shadow theirs and break 'lyceum.shared_models' and 'lyceum.neph'. That is why
|
|
6
|
+
__version__ lives in this module. A future refactor could move these modules
|
|
7
|
+
under lyceum/cli/, claiming one subpackage the way the other two do.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
__version__ = "1.2.2"
|
|
@@ -62,6 +62,7 @@ def create_key(
|
|
|
62
62
|
console.print(f"[bold yellow]{res['api_key']}[/bold yellow]")
|
|
63
63
|
console.print()
|
|
64
64
|
console.print("[dim]This is the only time the key will be displayed. Copy it now.[/dim]")
|
|
65
|
+
console.print("[dim]This key follows --org and the active org; without either it acts as your personal org.[/dim]")
|
|
65
66
|
|
|
66
67
|
|
|
67
68
|
@keys_app.command("revoke")
|
|
@@ -7,6 +7,7 @@ import threading
|
|
|
7
7
|
import time
|
|
8
8
|
import webbrowser
|
|
9
9
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
10
|
+
from typing import Any
|
|
10
11
|
from urllib.parse import parse_qs, urlparse
|
|
11
12
|
|
|
12
13
|
import httpx
|
|
@@ -14,6 +15,7 @@ import typer
|
|
|
14
15
|
from rich.console import Console
|
|
15
16
|
|
|
16
17
|
from ...shared.config import config
|
|
18
|
+
from ...shared.orgs import explain, resolve_org_context
|
|
17
19
|
from .keys import keys_app
|
|
18
20
|
|
|
19
21
|
console = Console()
|
|
@@ -22,14 +24,14 @@ auth_app = typer.Typer(name="auth", help="Authentication commands")
|
|
|
22
24
|
auth_app.add_typer(keys_app, name="keys")
|
|
23
25
|
|
|
24
26
|
# Global state for the OAuth callback server
|
|
25
|
-
callback_result = {"token": None, "error": None, "received": False}
|
|
27
|
+
callback_result: dict[str, Any] = {"token": None, "error": None, "received": False}
|
|
26
28
|
|
|
27
29
|
# (svg path, tailwind color) per page kind
|
|
28
30
|
_PAGE_ICONS = {
|
|
29
31
|
"success": ("M5 13l4 4L19 7", "green"),
|
|
30
32
|
"error": ("M6 18L18 6M6 6l12 12", "red"),
|
|
31
33
|
"warning": (
|
|
32
|
-
"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.732-.833-2.5 0L3.268 16c-.77.833.192 2.5 1.732 2.5z",
|
|
34
|
+
"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.732-.833-2.5 0L3.268 16c-.77.833.192 2.5 1.732 2.5z", # noqa: E501
|
|
33
35
|
"yellow",
|
|
34
36
|
),
|
|
35
37
|
}
|
|
@@ -94,7 +96,9 @@ class CallbackHandler(BaseHTTPRequestHandler):
|
|
|
94
96
|
self._send_page(
|
|
95
97
|
404,
|
|
96
98
|
_render_callback_page(
|
|
97
|
-
"warning",
|
|
99
|
+
"warning",
|
|
100
|
+
"Page Not Found",
|
|
101
|
+
"Page Not Found",
|
|
98
102
|
"<p>The requested page could not be found.</p>",
|
|
99
103
|
),
|
|
100
104
|
)
|
|
@@ -111,7 +115,9 @@ class CallbackHandler(BaseHTTPRequestHandler):
|
|
|
111
115
|
self._send_page(
|
|
112
116
|
200,
|
|
113
117
|
_render_callback_page(
|
|
114
|
-
"success",
|
|
118
|
+
"success",
|
|
119
|
+
"Authentication Success",
|
|
120
|
+
"Authentication Successful!",
|
|
115
121
|
"<p class='text-lg'>Welcome to Lyceum</p>"
|
|
116
122
|
"<p>You can now close this browser tab and return to the CLI.</p>",
|
|
117
123
|
auto_close=True,
|
|
@@ -125,8 +131,10 @@ class CallbackHandler(BaseHTTPRequestHandler):
|
|
|
125
131
|
self._send_page(
|
|
126
132
|
400,
|
|
127
133
|
_render_callback_page(
|
|
128
|
-
"error",
|
|
129
|
-
|
|
134
|
+
"error",
|
|
135
|
+
"Authentication Error",
|
|
136
|
+
"Authentication Failed",
|
|
137
|
+
f"<div class='bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-md'><p class='text-sm'>{html.escape(error)}</p></div>" # noqa: E501
|
|
130
138
|
"<p>Please try again or contact support if the issue persists.</p>",
|
|
131
139
|
),
|
|
132
140
|
)
|
|
@@ -137,7 +145,9 @@ class CallbackHandler(BaseHTTPRequestHandler):
|
|
|
137
145
|
self._send_page(
|
|
138
146
|
400,
|
|
139
147
|
_render_callback_page(
|
|
140
|
-
"warning",
|
|
148
|
+
"warning",
|
|
149
|
+
"Invalid Parameters",
|
|
150
|
+
"Invalid Parameters",
|
|
141
151
|
"<p>The authentication callback received invalid parameters.</p>",
|
|
142
152
|
),
|
|
143
153
|
)
|
|
@@ -148,8 +158,10 @@ class CallbackHandler(BaseHTTPRequestHandler):
|
|
|
148
158
|
self._send_page(
|
|
149
159
|
500,
|
|
150
160
|
_render_callback_page(
|
|
151
|
-
"error",
|
|
152
|
-
|
|
161
|
+
"error",
|
|
162
|
+
"Server Error",
|
|
163
|
+
"Server Error",
|
|
164
|
+
f"<div class='bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-md'><p class='text-sm'>{html.escape(str(e))}</p></div>", # noqa: E501,
|
|
153
165
|
),
|
|
154
166
|
)
|
|
155
167
|
|
|
@@ -176,17 +188,16 @@ def _auto_pin_active_org(client):
|
|
|
176
188
|
return
|
|
177
189
|
|
|
178
190
|
if not sys.stdin.isatty():
|
|
179
|
-
console.print(
|
|
180
|
-
"[dim]You belong to multiple orgs. Pick one with 'lyceum org use <slug>'.[/dim]"
|
|
181
|
-
)
|
|
191
|
+
console.print("[dim]You belong to multiple orgs. Pick one with 'lyceum org use <slug>'.[/dim]")
|
|
182
192
|
return
|
|
183
193
|
|
|
184
194
|
console.print()
|
|
185
195
|
console.print("[bold]You belong to multiple orgs. Pick one to make active:[/bold]")
|
|
186
196
|
for i, org in enumerate(orgs, start=1):
|
|
187
197
|
role = org.get("role", "")
|
|
188
|
-
|
|
189
|
-
|
|
198
|
+
kind = "personal" if org.get("is_default") else "team"
|
|
199
|
+
console.print(f" [cyan]{i}[/cyan]. {org['name']} ([dim]{org['slug']}[/dim]) — {kind}, {role}")
|
|
200
|
+
console.print(" [cyan]0[/cyan]. [dim]Skip — use your personal org[/dim]")
|
|
190
201
|
|
|
191
202
|
while True:
|
|
192
203
|
raw = typer.prompt("Selection", default="0")
|
|
@@ -207,10 +218,20 @@ def _auto_pin_active_org(client):
|
|
|
207
218
|
console.print(f"[yellow]Pick 0–{len(orgs)}.[/yellow]")
|
|
208
219
|
|
|
209
220
|
|
|
221
|
+
def _pin_key_org():
|
|
222
|
+
"""An org key acts as its own org; make the active org say so."""
|
|
223
|
+
ctx = resolve_org_context()
|
|
224
|
+
if ctx.is_key_scoped and ctx.slug:
|
|
225
|
+
config.active_org_slug = ctx.slug
|
|
226
|
+
config.save()
|
|
227
|
+
for line in explain(ctx):
|
|
228
|
+
console.print(line)
|
|
229
|
+
|
|
230
|
+
|
|
210
231
|
def get_available_port():
|
|
211
232
|
"""Find an available port for the callback server"""
|
|
212
233
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
213
|
-
s.bind((
|
|
234
|
+
s.bind(("", 0))
|
|
214
235
|
s.listen(1)
|
|
215
236
|
port = s.getsockname()[1]
|
|
216
237
|
return port
|
|
@@ -220,12 +241,8 @@ def get_available_port():
|
|
|
220
241
|
def login(
|
|
221
242
|
base_url: str | None = typer.Option(None, "--url", help="API base URL (for development)"),
|
|
222
243
|
dashboard_url: str | None = typer.Option(None, "--dashboard", help="Dashboard URL (for development)"),
|
|
223
|
-
manual: bool = typer.Option(
|
|
224
|
-
|
|
225
|
-
),
|
|
226
|
-
api_key: str | None = typer.Option(
|
|
227
|
-
None, "--api-key", "-k", help="API key for manual login"
|
|
228
|
-
),
|
|
244
|
+
manual: bool = typer.Option(False, "--manual", help="Use manual API key login instead of browser"),
|
|
245
|
+
api_key: str | None = typer.Option(None, "--api-key", "-k", help="API key for manual login"),
|
|
229
246
|
):
|
|
230
247
|
"""Login to Lyceum via browser authentication"""
|
|
231
248
|
global callback_result
|
|
@@ -255,6 +272,7 @@ def login(
|
|
|
255
272
|
else:
|
|
256
273
|
console.print(f"[red]Authentication failed: HTTP {response.status_code}[/red]")
|
|
257
274
|
raise typer.Exit(1)
|
|
275
|
+
_pin_key_org()
|
|
258
276
|
except typer.Exit:
|
|
259
277
|
raise
|
|
260
278
|
except Exception as e:
|
|
@@ -275,7 +293,7 @@ def login(
|
|
|
275
293
|
|
|
276
294
|
# Start callback server
|
|
277
295
|
callback_port = get_available_port()
|
|
278
|
-
callback_server = HTTPServer((
|
|
296
|
+
callback_server = HTTPServer(("localhost", callback_port), CallbackHandler)
|
|
279
297
|
|
|
280
298
|
console.print(f"[dim]Starting callback server on port {callback_port}...[/dim]")
|
|
281
299
|
|
|
@@ -366,6 +384,7 @@ def login(
|
|
|
366
384
|
def logout():
|
|
367
385
|
"""Logout and remove stored credentials"""
|
|
368
386
|
from ...shared.config import CONFIG_FILE
|
|
387
|
+
|
|
369
388
|
if CONFIG_FILE.exists():
|
|
370
389
|
CONFIG_FILE.unlink()
|
|
371
390
|
console.print("[green]Logged out successfully![/green]")
|
|
@@ -375,6 +394,7 @@ def logout():
|
|
|
375
394
|
def status():
|
|
376
395
|
"""Show current configuration and authentication status"""
|
|
377
396
|
from ...shared.config import CONFIG_FILE
|
|
397
|
+
|
|
378
398
|
console.print(f"[dim]Config file: {CONFIG_FILE}[/dim]")
|
|
379
399
|
console.print(f"[dim]Base URL: {config.base_url}[/dim]")
|
|
380
400
|
|
|
@@ -385,11 +405,8 @@ def status():
|
|
|
385
405
|
console.print("[green]Authenticated[/green]")
|
|
386
406
|
console.print(f"[dim]API Key: {config.api_key[:8]}...[/dim]")
|
|
387
407
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
console.print(f"[dim]Active org: {active_org}[/dim]")
|
|
391
|
-
else:
|
|
392
|
-
console.print("[dim]Active org: (none — falls back to default)[/dim]")
|
|
408
|
+
for line in explain(resolve_org_context()):
|
|
409
|
+
console.print(line)
|
|
393
410
|
|
|
394
411
|
# Test connection
|
|
395
412
|
try:
|
|
@@ -3,15 +3,14 @@
|
|
|
3
3
|
import importlib.metadata as im
|
|
4
4
|
import json
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
from typing import Optional
|
|
7
6
|
|
|
8
7
|
import typer
|
|
9
8
|
from rich.console import Console
|
|
10
9
|
|
|
11
10
|
from ....shared.imports import (
|
|
11
|
+
find_imports_in_file,
|
|
12
12
|
is_stdlib_module_by_name,
|
|
13
13
|
should_skip_path,
|
|
14
|
-
find_imports_in_file,
|
|
15
14
|
)
|
|
16
15
|
|
|
17
16
|
console = Console()
|
|
@@ -38,7 +37,7 @@ def get_package_name(module_name: str) -> str:
|
|
|
38
37
|
return PACKAGE_NAME_MAP.get(top_level, top_level)
|
|
39
38
|
|
|
40
39
|
|
|
41
|
-
def get_installed_version(package_name: str) ->
|
|
40
|
+
def get_installed_version(package_name: str) -> str | None:
|
|
42
41
|
"""Get installed version of a package."""
|
|
43
42
|
try:
|
|
44
43
|
return im.version(package_name)
|
|
@@ -99,15 +98,22 @@ def parse_requirements_file(req_file: Path) -> list[str]:
|
|
|
99
98
|
|
|
100
99
|
def extract_package_name(dep: str) -> str:
|
|
101
100
|
"""Extract package name from a dependency string."""
|
|
102
|
-
return
|
|
101
|
+
return (
|
|
102
|
+
dep.split("==")[0]
|
|
103
|
+
.split(">=")[0]
|
|
104
|
+
.split("<=")[0]
|
|
105
|
+
.split("<")[0]
|
|
106
|
+
.split(">")[0]
|
|
107
|
+
.split("[")[0]
|
|
108
|
+
.split("~=")[0]
|
|
109
|
+
.lower()
|
|
110
|
+
)
|
|
103
111
|
|
|
104
112
|
|
|
105
113
|
@config_app.command("init")
|
|
106
114
|
def init_config(
|
|
107
115
|
workspace: Path = typer.Argument(".", help="Workspace directory to analyze"),
|
|
108
|
-
requirements: Path | None = typer.Option(
|
|
109
|
-
None, "--requirements", "-r", help="Path to requirements.txt"
|
|
110
|
-
),
|
|
116
|
+
requirements: Path | None = typer.Option(None, "--requirements", "-r", help="Path to requirements.txt"),
|
|
111
117
|
force: bool = typer.Option(False, "--force", "-f", help="Overwrite existing config"),
|
|
112
118
|
):
|
|
113
119
|
"""Initialize workspace configuration for Lyceum Cloud execution.
|
|
@@ -161,10 +167,7 @@ def init_config(
|
|
|
161
167
|
|
|
162
168
|
# Filter to external dependencies
|
|
163
169
|
local_package_names = {Path(p).parts[0] if "/" in p else p for p in local_packages.keys()}
|
|
164
|
-
external_deps = {
|
|
165
|
-
imp for imp in all_imports
|
|
166
|
-
if not is_stdlib_module_by_name(imp) and imp not in local_package_names
|
|
167
|
-
}
|
|
170
|
+
external_deps = {imp for imp in all_imports if not is_stdlib_module_by_name(imp) and imp not in local_package_names}
|
|
168
171
|
console.print(f"[dim]Found {len(external_deps)} external dependencies from imports[/dim]")
|
|
169
172
|
|
|
170
173
|
# Parse requirements.txt
|
|
@@ -249,9 +252,7 @@ def show_config(
|
|
|
249
252
|
@config_app.command("refresh")
|
|
250
253
|
def refresh_config(
|
|
251
254
|
workspace: Path = typer.Argument(".", help="Workspace directory"),
|
|
252
|
-
requirements: Path | None = typer.Option(
|
|
253
|
-
None, "--requirements", "-r", help="Path to requirements.txt"
|
|
254
|
-
),
|
|
255
|
+
requirements: Path | None = typer.Option(None, "--requirements", "-r", help="Path to requirements.txt"),
|
|
255
256
|
):
|
|
256
257
|
"""Refresh workspace configuration (re-scans the workspace)."""
|
|
257
258
|
init_config(workspace=workspace, requirements=requirements, force=True)
|
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Docker execution commands
|
|
3
|
-
"""
|
|
1
|
+
"""Docker execution commands"""
|
|
4
2
|
|
|
5
3
|
import json
|
|
6
4
|
import shlex
|
|
7
|
-
from typing import Optional
|
|
8
5
|
|
|
9
6
|
import httpx
|
|
10
7
|
import typer
|
|
@@ -28,37 +25,25 @@ docker_app = typer.Typer(name="docker", help="Docker execution commands")
|
|
|
28
25
|
@docker_app.command("run")
|
|
29
26
|
def run_docker(
|
|
30
27
|
image: str = typer.Argument(..., help="Docker image to run"),
|
|
31
|
-
machine_type: str = typer.Option(
|
|
32
|
-
|
|
33
|
-
),
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
None, "--file-name", "-f", help="Name for the execution"
|
|
39
|
-
),
|
|
40
|
-
run_name: Optional[str] = RUN_NAME_OPTION,
|
|
41
|
-
label: Optional[list[str]] = LABEL_OPTION,
|
|
42
|
-
metadata: Optional[list[str]] = METADATA_OPTION,
|
|
43
|
-
command: Optional[str] = typer.Option(
|
|
28
|
+
machine_type: str = typer.Option("gpu.l40s", "--machine", "-m", help="Machine type (gpu.l40s, a100, h100, etc.)"),
|
|
29
|
+
timeout: int = typer.Option(300, "--timeout", "-t", help="Execution timeout in seconds"),
|
|
30
|
+
file_name: str | None = typer.Option(None, "--file-name", "-f", help="Name for the execution"),
|
|
31
|
+
run_name: str | None = RUN_NAME_OPTION,
|
|
32
|
+
label: list[str] | None = LABEL_OPTION,
|
|
33
|
+
metadata: list[str] | None = METADATA_OPTION,
|
|
34
|
+
command: str | None = typer.Option(
|
|
44
35
|
None,
|
|
45
36
|
"--command",
|
|
46
37
|
"-c",
|
|
47
38
|
help="Command to run in container (e.g., 'python app.py')",
|
|
48
39
|
),
|
|
49
|
-
env:
|
|
50
|
-
|
|
51
|
-
),
|
|
52
|
-
|
|
53
|
-
False, "--detach", "-d", help="Run container in background and print execution ID"
|
|
54
|
-
),
|
|
55
|
-
callback_url: Optional[str] = typer.Option(
|
|
56
|
-
None, "--callback", help="Webhook URL for completion notification"
|
|
57
|
-
),
|
|
58
|
-
registry_creds: Optional[str] = typer.Option(
|
|
40
|
+
env: list[str] | None = typer.Option(None, "--env", "-e", help="Environment variables (e.g., KEY=value)"),
|
|
41
|
+
detach: bool = typer.Option(False, "--detach", "-d", help="Run container in background and print execution ID"),
|
|
42
|
+
callback_url: str | None = typer.Option(None, "--callback", help="Webhook URL for completion notification"),
|
|
43
|
+
registry_creds: str | None = typer.Option(
|
|
59
44
|
None, "--registry-creds", help="Docker registry credentials as JSON string"
|
|
60
45
|
),
|
|
61
|
-
registry_type:
|
|
46
|
+
registry_type: str | None = typer.Option(
|
|
62
47
|
None, "--registry-type", help="Registry credential type: basic, aws, etc."
|
|
63
48
|
),
|
|
64
49
|
s3_mount: bool = typer.Option(
|
|
@@ -67,7 +52,7 @@ def run_docker(
|
|
|
67
52
|
s3_mount_path: str = typer.Option(
|
|
68
53
|
"/mnt/s3", "--s3-mount-path", help="Path where S3 bucket will be mounted inside the container"
|
|
69
54
|
),
|
|
70
|
-
graceful_timeout:
|
|
55
|
+
graceful_timeout: int | None = typer.Option(
|
|
71
56
|
None, "--graceful-timeout", help="Seconds to wait for graceful shutdown on cancel (default: 10)"
|
|
72
57
|
),
|
|
73
58
|
):
|
|
@@ -98,9 +83,7 @@ def run_docker(
|
|
|
98
83
|
docker_env[key] = value
|
|
99
84
|
else:
|
|
100
85
|
status.stop()
|
|
101
|
-
console.print(
|
|
102
|
-
f"[yellow]Warning: Ignoring invalid env var format: {env_var}[/yellow]"
|
|
103
|
-
)
|
|
86
|
+
console.print(f"[yellow]Warning: Ignoring invalid env var format: {env_var}[/yellow]")
|
|
104
87
|
status.start()
|
|
105
88
|
|
|
106
89
|
# Parse registry credentials
|
|
@@ -110,19 +93,13 @@ def run_docker(
|
|
|
110
93
|
registry_credentials = json.loads(registry_creds)
|
|
111
94
|
except json.JSONDecodeError:
|
|
112
95
|
status.stop()
|
|
113
|
-
console.print(
|
|
114
|
-
"[red]Error: Invalid JSON format for registry credentials[/red]"
|
|
115
|
-
)
|
|
96
|
+
console.print("[red]Error: Invalid JSON format for registry credentials[/red]")
|
|
116
97
|
raise typer.Exit(1)
|
|
117
98
|
|
|
118
99
|
# Validate registry credentials and type
|
|
119
|
-
if (registry_creds and not registry_type) or (
|
|
120
|
-
registry_type and not registry_creds
|
|
121
|
-
):
|
|
100
|
+
if (registry_creds and not registry_type) or (registry_type and not registry_creds):
|
|
122
101
|
status.stop()
|
|
123
|
-
console.print(
|
|
124
|
-
"[red]Error: Both --registry-creds and --registry-type must be provided together[/red]"
|
|
125
|
-
)
|
|
102
|
+
console.print("[red]Error: Both --registry-creds and --registry-type must be provided together[/red]")
|
|
126
103
|
raise typer.Exit(1)
|
|
127
104
|
|
|
128
105
|
# Validate S3 mount options
|
|
@@ -194,9 +171,7 @@ def run_docker(
|
|
|
194
171
|
status.stop()
|
|
195
172
|
console.print(f"[red]Error: HTTP {response.status_code}[/red]")
|
|
196
173
|
if response.status_code == 401:
|
|
197
|
-
console.print(
|
|
198
|
-
"[red]Authentication failed. Your session may have expired.[/red]"
|
|
199
|
-
)
|
|
174
|
+
console.print("[red]Authentication failed. Your session may have expired.[/red]")
|
|
200
175
|
console.print("[yellow]Run 'lyceum auth login' to re-authenticate.[/yellow]")
|
|
201
176
|
elif response.content:
|
|
202
177
|
console.print(f"[red]{response.content.decode()}[/red]")
|
|
@@ -277,32 +252,33 @@ def show_registry_examples():
|
|
|
277
252
|
|
|
278
253
|
console.print("[bold]1. Docker Hub (basic)[/bold]")
|
|
279
254
|
console.print("Type: [green]basic[/green]")
|
|
280
|
-
console.print(
|
|
281
|
-
'Credentials: [yellow]\'{"username": "myuser", "password": "mypassword"}\'[/yellow]\n'
|
|
282
|
-
)
|
|
255
|
+
console.print('Credentials: [yellow]\'{"username": "myuser", "password": "mypassword"}\'[/yellow]\n')
|
|
283
256
|
|
|
284
257
|
console.print("[bold]2. AWS ECR (aws)[/bold]")
|
|
285
258
|
console.print("Type: [green]aws[/green]")
|
|
286
259
|
console.print(
|
|
287
|
-
'Credentials: [yellow]\'{"region": "us-west-2", "aws_access_key_id": "AKIAI...",
|
|
260
|
+
'Credentials: [yellow]\'{"region": "us-west-2", "aws_access_key_id": "AKIAI...", '
|
|
261
|
+
'"aws_secret_access_key": "wJalrX...", "session_token": "optional..."}\'[/yellow]\n'
|
|
288
262
|
)
|
|
289
263
|
|
|
290
264
|
console.print("[bold]3. Private Registry (basic)[/bold]")
|
|
291
265
|
console.print("Type: [green]basic[/green]")
|
|
292
|
-
console.print(
|
|
293
|
-
'Credentials: [yellow]\'{"username": "admin", "password": "secret"}\'[/yellow]\n'
|
|
294
|
-
)
|
|
266
|
+
console.print('Credentials: [yellow]\'{"username": "admin", "password": "secret"}\'[/yellow]\n')
|
|
295
267
|
|
|
296
268
|
console.print("[bold]Example Commands:[/bold]")
|
|
297
269
|
console.print("# Docker Hub:")
|
|
298
270
|
console.print(
|
|
299
|
-
"[dim]lyceum docker run myuser/myapp:latest --registry-type basic
|
|
271
|
+
"[dim]lyceum docker run myuser/myapp:latest --registry-type basic "
|
|
272
|
+
'--registry-creds \'{"username": "myuser", "password": "mytoken"}\'[/dim]'
|
|
300
273
|
)
|
|
301
274
|
console.print("\n# AWS ECR:")
|
|
302
275
|
console.print(
|
|
303
|
-
"[dim]lyceum docker run 123456789012.dkr.ecr.us-west-2.amazonaws.com/myapp:latest
|
|
276
|
+
"[dim]lyceum docker run 123456789012.dkr.ecr.us-west-2.amazonaws.com/myapp:latest "
|
|
277
|
+
'--registry-type aws --registry-creds \'{"region": "us-west-2", "aws_access_key_id": "AKIAI...", '
|
|
278
|
+
'"aws_secret_access_key": "wJalrX..."}\'[/dim]'
|
|
304
279
|
)
|
|
305
280
|
console.print("\n# Private Registry:")
|
|
306
281
|
console.print(
|
|
307
|
-
"[dim]lyceum docker run myregistry.com/myapp:latest --registry-type basic
|
|
282
|
+
"[dim]lyceum docker run myregistry.com/myapp:latest --registry-type basic "
|
|
283
|
+
'--registry-creds \'{"username": "admin", "password": "secret"}\'[/dim]'
|
|
308
284
|
)
|
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Docker Compose execution commands
|
|
3
|
-
"""
|
|
1
|
+
"""Docker Compose execution commands"""
|
|
4
2
|
|
|
5
3
|
import json
|
|
6
4
|
from pathlib import Path
|
|
7
|
-
from typing import Optional
|
|
8
5
|
|
|
9
6
|
import httpx
|
|
10
7
|
import typer
|
|
@@ -28,38 +25,23 @@ compose_app = typer.Typer(name="compose", help="Docker Compose execution command
|
|
|
28
25
|
@compose_app.command("run")
|
|
29
26
|
def run_docker_compose(
|
|
30
27
|
compose_file: Path = typer.Argument(
|
|
31
|
-
...,
|
|
32
|
-
help="Path to docker-compose.yml file",
|
|
33
|
-
exists=True,
|
|
34
|
-
file_okay=True,
|
|
35
|
-
dir_okay=False,
|
|
36
|
-
readable=True
|
|
28
|
+
..., help="Path to docker-compose.yml file", exists=True, file_okay=True, dir_okay=False, readable=True
|
|
37
29
|
),
|
|
38
|
-
machine_type: str = typer.Option(
|
|
39
|
-
|
|
40
|
-
),
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
run_name: Optional[str] = RUN_NAME_OPTION,
|
|
48
|
-
label: Optional[list[str]] = LABEL_OPTION,
|
|
49
|
-
metadata: Optional[list[str]] = METADATA_OPTION,
|
|
50
|
-
detach: bool = typer.Option(
|
|
51
|
-
False, "--detach", "-d", help="Run in background and print execution ID"
|
|
52
|
-
),
|
|
53
|
-
callback_url: Optional[str] = typer.Option(
|
|
54
|
-
None, "--callback", help="Webhook URL for completion notification"
|
|
55
|
-
),
|
|
56
|
-
registry_creds: Optional[str] = typer.Option(
|
|
30
|
+
machine_type: str = typer.Option("gpu.l40s", "--machine", "-m", help="Machine type (gpu.l40s, a100, h100, etc.)"),
|
|
31
|
+
timeout: int = typer.Option(300, "--timeout", "-t", help="Execution timeout in seconds"),
|
|
32
|
+
file_name: str | None = typer.Option(None, "--file-name", "-f", help="Name for the execution"),
|
|
33
|
+
run_name: str | None = RUN_NAME_OPTION,
|
|
34
|
+
label: list[str] | None = LABEL_OPTION,
|
|
35
|
+
metadata: list[str] | None = METADATA_OPTION,
|
|
36
|
+
detach: bool = typer.Option(False, "--detach", "-d", help="Run in background and print execution ID"),
|
|
37
|
+
callback_url: str | None = typer.Option(None, "--callback", help="Webhook URL for completion notification"),
|
|
38
|
+
registry_creds: str | None = typer.Option(
|
|
57
39
|
None, "--registry-creds", help="Docker registry credentials as JSON string"
|
|
58
40
|
),
|
|
59
|
-
registry_type:
|
|
41
|
+
registry_type: str | None = typer.Option(
|
|
60
42
|
None, "--registry-type", help="Registry credential type: basic, aws, etc."
|
|
61
43
|
),
|
|
62
|
-
graceful_timeout:
|
|
44
|
+
graceful_timeout: int | None = typer.Option(
|
|
63
45
|
None, "--graceful-timeout", help="Seconds to wait for graceful shutdown on cancel (default: 10)"
|
|
64
46
|
),
|
|
65
47
|
):
|
|
@@ -96,19 +78,13 @@ def run_docker_compose(
|
|
|
96
78
|
registry_credentials = json.loads(registry_creds)
|
|
97
79
|
except json.JSONDecodeError:
|
|
98
80
|
status.stop()
|
|
99
|
-
console.print(
|
|
100
|
-
"[red]Error: Invalid JSON format for registry credentials[/red]"
|
|
101
|
-
)
|
|
81
|
+
console.print("[red]Error: Invalid JSON format for registry credentials[/red]")
|
|
102
82
|
raise typer.Exit(1)
|
|
103
83
|
|
|
104
84
|
# Validate registry credentials and type
|
|
105
|
-
if (registry_creds and not registry_type) or (
|
|
106
|
-
registry_type and not registry_creds
|
|
107
|
-
):
|
|
85
|
+
if (registry_creds and not registry_type) or (registry_type and not registry_creds):
|
|
108
86
|
status.stop()
|
|
109
|
-
console.print(
|
|
110
|
-
"[red]Error: Both --registry-creds and --registry-type must be provided together[/red]"
|
|
111
|
-
)
|
|
87
|
+
console.print("[red]Error: Both --registry-creds and --registry-type must be provided together[/red]")
|
|
112
88
|
raise typer.Exit(1)
|
|
113
89
|
|
|
114
90
|
status.update(f"Starting Docker Compose from {compose_file.name}...")
|
|
@@ -169,9 +145,7 @@ def run_docker_compose(
|
|
|
169
145
|
status.stop()
|
|
170
146
|
console.print(f"[red]Error: HTTP {response.status_code}[/red]")
|
|
171
147
|
if response.status_code == 401:
|
|
172
|
-
console.print(
|
|
173
|
-
"[red]Authentication failed. Your session may have expired.[/red]"
|
|
174
|
-
)
|
|
148
|
+
console.print("[red]Authentication failed. Your session may have expired.[/red]")
|
|
175
149
|
console.print("[yellow]Run 'lyceum auth login' to re-authenticate.[/yellow]")
|
|
176
150
|
elif response.content:
|
|
177
151
|
console.print(f"[red]{response.content.decode()}[/red]")
|
|
@@ -251,32 +225,33 @@ def show_registry_examples():
|
|
|
251
225
|
|
|
252
226
|
console.print("[bold]1. Docker Hub (basic)[/bold]")
|
|
253
227
|
console.print("Type: [green]basic[/green]")
|
|
254
|
-
console.print(
|
|
255
|
-
'Credentials: [yellow]\'{"username": "myuser", "password": "mypassword"}\'[/yellow]\n'
|
|
256
|
-
)
|
|
228
|
+
console.print('Credentials: [yellow]\'{"username": "myuser", "password": "mypassword"}\'[/yellow]\n')
|
|
257
229
|
|
|
258
230
|
console.print("[bold]2. AWS ECR (aws)[/bold]")
|
|
259
231
|
console.print("Type: [green]aws[/green]")
|
|
260
232
|
console.print(
|
|
261
|
-
'Credentials: [yellow]\'{"region": "us-west-2", "aws_access_key_id": "AKIAI...",
|
|
233
|
+
'Credentials: [yellow]\'{"region": "us-west-2", "aws_access_key_id": "AKIAI...", '
|
|
234
|
+
'"aws_secret_access_key": "wJalrX...", "session_token": "optional..."}\'[/yellow]\n'
|
|
262
235
|
)
|
|
263
236
|
|
|
264
237
|
console.print("[bold]3. Private Registry (basic)[/bold]")
|
|
265
238
|
console.print("Type: [green]basic[/green]")
|
|
266
|
-
console.print(
|
|
267
|
-
'Credentials: [yellow]\'{"username": "admin", "password": "secret"}\'[/yellow]\n'
|
|
268
|
-
)
|
|
239
|
+
console.print('Credentials: [yellow]\'{"username": "admin", "password": "secret"}\'[/yellow]\n')
|
|
269
240
|
|
|
270
241
|
console.print("[bold]Example Commands:[/bold]")
|
|
271
242
|
console.print("# Docker Hub:")
|
|
272
243
|
console.print(
|
|
273
|
-
"[dim]lyceum compose run docker-compose.yml --registry-type basic
|
|
244
|
+
"[dim]lyceum compose run docker-compose.yml --registry-type basic "
|
|
245
|
+
'--registry-creds \'{"username": "myuser", "password": "mytoken"}\'[/dim]'
|
|
274
246
|
)
|
|
275
247
|
console.print("\n# AWS ECR:")
|
|
276
248
|
console.print(
|
|
277
|
-
"[dim]lyceum compose run docker-compose.yml --registry-type aws
|
|
249
|
+
"[dim]lyceum compose run docker-compose.yml --registry-type aws "
|
|
250
|
+
'--registry-creds \'{"region": "us-west-2", "aws_access_key_id": "AKIAI...", '
|
|
251
|
+
'"aws_secret_access_key": "wJalrX..."}\'[/dim]'
|
|
278
252
|
)
|
|
279
253
|
console.print("\n# Private Registry:")
|
|
280
254
|
console.print(
|
|
281
|
-
"[dim]lyceum compose run docker-compose.yml --registry-type basic
|
|
255
|
+
"[dim]lyceum compose run docker-compose.yml --registry-type basic "
|
|
256
|
+
'--registry-creds \'{"username": "admin", "password": "secret"}\'[/dim]'
|
|
282
257
|
)
|