lyceum-cli 1.0.22__tar.gz → 1.0.23__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.0.22 → lyceum_cli-1.0.23}/PKG-INFO +1 -1
- lyceum_cli-1.0.23/lyceum/external/__init__.py +1 -0
- lyceum_cli-1.0.23/lyceum/external/auth/__init__.py +1 -0
- {lyceum_cli-1.0.22 → lyceum_cli-1.0.23}/lyceum/external/auth/login.py +65 -60
- lyceum_cli-1.0.23/lyceum/external/compute/__init__.py +1 -0
- lyceum_cli-1.0.23/lyceum/external/compute/execution/__init__.py +1 -0
- {lyceum_cli-1.0.22 → lyceum_cli-1.0.23}/lyceum/external/compute/execution/python.py +20 -16
- {lyceum_cli-1.0.22 → lyceum_cli-1.0.23}/lyceum/external/compute/inference/batch.py +73 -64
- {lyceum_cli-1.0.22 → lyceum_cli-1.0.23}/lyceum/external/compute/inference/chat.py +36 -41
- {lyceum_cli-1.0.22 → lyceum_cli-1.0.23}/lyceum/external/compute/inference/models.py +20 -14
- lyceum_cli-1.0.23/lyceum/external/general/__init__.py +1 -0
- lyceum_cli-1.0.23/lyceum/external/vms/instances.py +303 -0
- lyceum_cli-1.0.23/lyceum/external/vms/management.py +253 -0
- {lyceum_cli-1.0.22 → lyceum_cli-1.0.23}/lyceum/main.py +3 -1
- lyceum_cli-1.0.23/lyceum/shared/__init__.py +1 -0
- {lyceum_cli-1.0.22 → lyceum_cli-1.0.23}/lyceum/shared/config.py +37 -32
- {lyceum_cli-1.0.22 → lyceum_cli-1.0.23}/lyceum/shared/display.py +3 -5
- {lyceum_cli-1.0.22 → lyceum_cli-1.0.23}/lyceum/shared/streaming.py +60 -44
- {lyceum_cli-1.0.22 → lyceum_cli-1.0.23}/lyceum_cli.egg-info/PKG-INFO +1 -1
- {lyceum_cli-1.0.22 → lyceum_cli-1.0.23}/lyceum_cli.egg-info/SOURCES.txt +3 -0
- lyceum_cli-1.0.23/lyceum_cli.egg-info/dependency_links.txt +1 -0
- lyceum_cli-1.0.23/lyceum_cloud_execution_api_client/__init__.py +1 -0
- lyceum_cli-1.0.23/lyceum_cloud_execution_api_client/api/__init__.py +1 -0
- {lyceum_cli-1.0.22 → lyceum_cli-1.0.23}/setup.py +4 -5
- lyceum_cli-1.0.22/lyceum/external/__init__.py +0 -0
- lyceum_cli-1.0.22/lyceum/external/auth/__init__.py +0 -0
- lyceum_cli-1.0.22/lyceum/external/compute/__init__.py +0 -0
- lyceum_cli-1.0.22/lyceum/external/compute/execution/__init__.py +0 -0
- lyceum_cli-1.0.22/lyceum/external/general/__init__.py +0 -0
- lyceum_cli-1.0.22/lyceum/shared/__init__.py +0 -0
- lyceum_cli-1.0.22/lyceum_cloud_execution_api_client/__init__.py +0 -0
- lyceum_cli-1.0.22/lyceum_cloud_execution_api_client/api/__init__.py +0 -0
- /lyceum_cli-1.0.22/lyceum_cli.egg-info/dependency_links.txt → /lyceum_cli-1.0.23/lyceum/__init__.py +0 -0
- {lyceum_cli-1.0.22 → lyceum_cli-1.0.23}/lyceum/external/compute/inference/__init__.py +0 -0
- {lyceum_cli-1.0.22/lyceum → lyceum_cli-1.0.23/lyceum/external/vms}/__init__.py +0 -0
- {lyceum_cli-1.0.22 → lyceum_cli-1.0.23}/lyceum_cli.egg-info/entry_points.txt +0 -0
- {lyceum_cli-1.0.22 → lyceum_cli-1.0.23}/lyceum_cli.egg-info/requires.txt +0 -0
- {lyceum_cli-1.0.22 → lyceum_cli-1.0.23}/lyceum_cli.egg-info/top_level.txt +0 -0
- {lyceum_cli-1.0.22 → lyceum_cli-1.0.23}/lyceum_cloud_execution_api_client/models/__init__.py +0 -0
- {lyceum_cli-1.0.22 → lyceum_cli-1.0.23}/setup.cfg +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -1,16 +1,14 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Authentication commands: login, logout, status
|
|
3
|
-
"""
|
|
1
|
+
"""Authentication commands: login, logout, status"""
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
import typer
|
|
7
|
-
from rich.console import Console
|
|
8
|
-
import webbrowser
|
|
3
|
+
import socket
|
|
9
4
|
import threading
|
|
10
5
|
import time
|
|
11
|
-
|
|
12
|
-
from
|
|
13
|
-
import
|
|
6
|
+
import webbrowser
|
|
7
|
+
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
8
|
+
from urllib.parse import parse_qs, urlparse
|
|
9
|
+
|
|
10
|
+
import typer
|
|
11
|
+
from rich.console import Console
|
|
14
12
|
|
|
15
13
|
from ...shared.config import config
|
|
16
14
|
|
|
@@ -24,37 +22,38 @@ callback_result = {"token": None, "error": None, "received": False}
|
|
|
24
22
|
|
|
25
23
|
class CallbackHandler(BaseHTTPRequestHandler):
|
|
26
24
|
"""HTTP handler for OAuth callback"""
|
|
27
|
-
|
|
25
|
+
|
|
28
26
|
def log_message(self, format, *args):
|
|
29
|
-
|
|
27
|
+
"""Suppress HTTP server logs"""
|
|
30
28
|
pass
|
|
31
|
-
|
|
29
|
+
|
|
32
30
|
def do_GET(self):
|
|
31
|
+
"""Handle GET request for OAuth callback"""
|
|
33
32
|
global callback_result
|
|
34
|
-
|
|
33
|
+
|
|
35
34
|
try:
|
|
36
35
|
# Parse the callback URL
|
|
37
36
|
parsed_url = urlparse(self.path)
|
|
38
37
|
query_params = parse_qs(parsed_url.query)
|
|
39
|
-
|
|
38
|
+
|
|
40
39
|
if parsed_url.path == "/callback":
|
|
41
40
|
# Extract token from query parameters
|
|
42
41
|
if "token" in query_params:
|
|
43
42
|
token = query_params["token"][0]
|
|
44
43
|
user_info = query_params.get("user", [None])[0]
|
|
45
44
|
refresh_token = query_params.get("refresh_token", [None])[0]
|
|
46
|
-
|
|
45
|
+
|
|
47
46
|
callback_result["token"] = token
|
|
48
47
|
callback_result["user"] = user_info
|
|
49
48
|
if refresh_token:
|
|
50
49
|
callback_result["refresh_token"] = refresh_token
|
|
51
50
|
callback_result["received"] = True
|
|
52
|
-
|
|
51
|
+
|
|
53
52
|
# Send success response
|
|
54
53
|
self.send_response(200)
|
|
55
54
|
self.send_header("Content-type", "text/html")
|
|
56
55
|
self.end_headers()
|
|
57
|
-
|
|
56
|
+
|
|
58
57
|
success_html = """
|
|
59
58
|
<!DOCTYPE html>
|
|
60
59
|
<html lang="en">
|
|
@@ -73,31 +72,31 @@ class CallbackHandler(BaseHTTPRequestHandler):
|
|
|
73
72
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
|
74
73
|
</svg>
|
|
75
74
|
</div>
|
|
76
|
-
|
|
75
|
+
|
|
77
76
|
<!-- Header -->
|
|
78
77
|
<div class="mb-6">
|
|
79
78
|
<h1 class="text-2xl font-bold text-gray-900 mb-2">Authentication Successful!</h1>
|
|
80
79
|
<p class="text-gray-600 text-lg">Welcome to Lyceum</p>
|
|
81
80
|
</div>
|
|
82
|
-
|
|
81
|
+
|
|
83
82
|
<!-- Instructions -->
|
|
84
83
|
<div class="space-y-3 mb-8">
|
|
85
84
|
<p class="text-gray-700">You can now close this browser tab and return to the CLI.</p>
|
|
86
85
|
<p class="text-sm text-gray-500">Your Lyceum CLI has been authenticated successfully and is ready to use.</p>
|
|
87
86
|
</div>
|
|
88
|
-
|
|
87
|
+
|
|
89
88
|
<!-- Close Message -->
|
|
90
89
|
<div class="w-full py-3 px-4 bg-gray-50 text-gray-700 rounded-md border border-gray-200">
|
|
91
90
|
You can close this window now
|
|
92
91
|
</div>
|
|
93
|
-
|
|
92
|
+
|
|
94
93
|
<!-- Lyceum Branding -->
|
|
95
94
|
<div class="mt-8 pt-6 border-t border-gray-200">
|
|
96
95
|
<p class="text-xs text-gray-400">Powered by Lyceum Technology</p>
|
|
97
96
|
</div>
|
|
98
97
|
</div>
|
|
99
98
|
</div>
|
|
100
|
-
|
|
99
|
+
|
|
101
100
|
<!-- Auto-close script -->
|
|
102
101
|
<script>
|
|
103
102
|
// Auto-close after 10 seconds
|
|
@@ -113,17 +112,17 @@ class CallbackHandler(BaseHTTPRequestHandler):
|
|
|
113
112
|
except (BrokenPipeError, ConnectionResetError):
|
|
114
113
|
# Browser closed connection (expected when tab auto-closes)
|
|
115
114
|
pass
|
|
116
|
-
|
|
115
|
+
|
|
117
116
|
elif "error" in query_params:
|
|
118
117
|
error = query_params["error"][0]
|
|
119
118
|
callback_result["error"] = error
|
|
120
119
|
callback_result["received"] = True
|
|
121
|
-
|
|
120
|
+
|
|
122
121
|
# Send error response
|
|
123
122
|
self.send_response(400)
|
|
124
123
|
self.send_header("Content-type", "text/html")
|
|
125
124
|
self.end_headers()
|
|
126
|
-
|
|
125
|
+
|
|
127
126
|
error_html = f"""
|
|
128
127
|
<!DOCTYPE html>
|
|
129
128
|
<html lang="en">
|
|
@@ -142,13 +141,13 @@ class CallbackHandler(BaseHTTPRequestHandler):
|
|
|
142
141
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
143
142
|
</svg>
|
|
144
143
|
</div>
|
|
145
|
-
|
|
144
|
+
|
|
146
145
|
<!-- Header -->
|
|
147
146
|
<div class="mb-6">
|
|
148
147
|
<h1 class="text-2xl font-bold text-gray-900 mb-2">Authentication Failed</h1>
|
|
149
148
|
<p class="text-red-600 text-lg">Something went wrong</p>
|
|
150
149
|
</div>
|
|
151
|
-
|
|
150
|
+
|
|
152
151
|
<!-- Error Details -->
|
|
153
152
|
<div class="space-y-3 mb-8">
|
|
154
153
|
<div class="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-md">
|
|
@@ -156,12 +155,12 @@ class CallbackHandler(BaseHTTPRequestHandler):
|
|
|
156
155
|
</div>
|
|
157
156
|
<p class="text-gray-600">Please try again or contact support if the issue persists.</p>
|
|
158
157
|
</div>
|
|
159
|
-
|
|
158
|
+
|
|
160
159
|
<!-- Close Message -->
|
|
161
160
|
<div class="w-full py-3 px-4 bg-gray-50 text-gray-700 rounded-md border border-gray-200">
|
|
162
161
|
You can close this window now
|
|
163
162
|
</div>
|
|
164
|
-
|
|
163
|
+
|
|
165
164
|
<!-- Lyceum Branding -->
|
|
166
165
|
<div class="mt-8 pt-6 border-t border-gray-200">
|
|
167
166
|
<p class="text-xs text-gray-400">Powered by Lyceum Technology</p>
|
|
@@ -180,7 +179,7 @@ class CallbackHandler(BaseHTTPRequestHandler):
|
|
|
180
179
|
# Missing parameters
|
|
181
180
|
callback_result["error"] = "Missing token or error parameter"
|
|
182
181
|
callback_result["received"] = True
|
|
183
|
-
|
|
182
|
+
|
|
184
183
|
self.send_response(400)
|
|
185
184
|
self.send_header("Content-type", "text/html")
|
|
186
185
|
self.end_headers()
|
|
@@ -253,11 +252,11 @@ class CallbackHandler(BaseHTTPRequestHandler):
|
|
|
253
252
|
except (BrokenPipeError, ConnectionResetError):
|
|
254
253
|
# Browser closed connection
|
|
255
254
|
pass
|
|
256
|
-
|
|
255
|
+
|
|
257
256
|
except Exception as e:
|
|
258
257
|
callback_result["error"] = str(e)
|
|
259
258
|
callback_result["received"] = True
|
|
260
|
-
|
|
259
|
+
|
|
261
260
|
self.send_response(500)
|
|
262
261
|
self.send_header("Content-type", "text/html")
|
|
263
262
|
self.end_headers()
|
|
@@ -309,24 +308,30 @@ def get_available_port():
|
|
|
309
308
|
|
|
310
309
|
@auth_app.command("login")
|
|
311
310
|
def login(
|
|
312
|
-
base_url:
|
|
313
|
-
dashboard_url:
|
|
314
|
-
|
|
315
|
-
|
|
311
|
+
base_url: str | None = typer.Option(None, "--url", help="API base URL (for development)"),
|
|
312
|
+
dashboard_url: str | None = typer.Option(
|
|
313
|
+
"https://dashboard.lyceum.technology", "--dashboard", help="Dashboard URL"
|
|
314
|
+
),
|
|
315
|
+
manual: bool = typer.Option(
|
|
316
|
+
False, "--manual", help="Use manual API key login instead of browser"
|
|
317
|
+
),
|
|
318
|
+
api_key: str | None = typer.Option(
|
|
319
|
+
None, "--api-key", "-k", help="API key for manual login"
|
|
320
|
+
),
|
|
316
321
|
):
|
|
317
322
|
"""Login to Lyceum via browser authentication"""
|
|
318
323
|
global callback_result
|
|
319
|
-
|
|
324
|
+
|
|
320
325
|
if manual:
|
|
321
326
|
# Legacy manual login
|
|
322
327
|
if not api_key:
|
|
323
328
|
api_key = typer.prompt("Enter your Lyceum API key", hide_input=True)
|
|
324
|
-
|
|
329
|
+
|
|
325
330
|
config.api_key = api_key
|
|
326
331
|
if base_url:
|
|
327
332
|
config.base_url = base_url
|
|
328
333
|
config.save()
|
|
329
|
-
|
|
334
|
+
|
|
330
335
|
# Test the connection
|
|
331
336
|
try:
|
|
332
337
|
import httpx
|
|
@@ -341,7 +346,7 @@ def login(
|
|
|
341
346
|
console.print(f"[red]❌ Authentication failed: {e}[/red]")
|
|
342
347
|
raise typer.Exit(1)
|
|
343
348
|
return
|
|
344
|
-
|
|
349
|
+
|
|
345
350
|
# OAuth-style browser login
|
|
346
351
|
try:
|
|
347
352
|
if base_url:
|
|
@@ -349,45 +354,45 @@ def login(
|
|
|
349
354
|
else:
|
|
350
355
|
# Reset to production URL if no custom URL specified
|
|
351
356
|
config.base_url = "https://api.lyceum.technology"
|
|
352
|
-
|
|
357
|
+
|
|
353
358
|
# Reset callback result
|
|
354
359
|
callback_result = {"token": None, "error": None, "received": False}
|
|
355
|
-
|
|
360
|
+
|
|
356
361
|
# Start callback server
|
|
357
362
|
callback_port = get_available_port()
|
|
358
363
|
callback_server = HTTPServer(('localhost', callback_port), CallbackHandler)
|
|
359
|
-
|
|
364
|
+
|
|
360
365
|
console.print(f"[dim]Starting callback server on port {callback_port}...[/dim]")
|
|
361
|
-
|
|
366
|
+
|
|
362
367
|
# Start server in background thread
|
|
363
368
|
server_thread = threading.Thread(target=callback_server.serve_forever, daemon=True)
|
|
364
369
|
server_thread.start()
|
|
365
|
-
|
|
370
|
+
|
|
366
371
|
# Construct login URL
|
|
367
372
|
callback_url = f"http://localhost:{callback_port}/callback"
|
|
368
373
|
login_url = f"{dashboard_url}/cli-login?callback={callback_url}"
|
|
369
|
-
|
|
374
|
+
|
|
370
375
|
console.print("[cyan]🌐 Opening browser for authentication...[/cyan]")
|
|
371
376
|
console.print(f"[dim]If browser doesn't open, visit: {login_url}[/dim]")
|
|
372
|
-
|
|
377
|
+
|
|
373
378
|
# Open browser
|
|
374
379
|
if not webbrowser.open(login_url):
|
|
375
380
|
console.print("[yellow]⚠️ Could not open browser automatically[/yellow]")
|
|
376
381
|
console.print(f"[yellow]Please manually open: {login_url}[/yellow]")
|
|
377
|
-
|
|
382
|
+
|
|
378
383
|
console.print("[dim]Waiting for authentication... (timeout: 120 seconds)[/dim]")
|
|
379
|
-
|
|
384
|
+
|
|
380
385
|
# Wait for callback with timeout
|
|
381
386
|
timeout = 120 # 2 minutes
|
|
382
387
|
start_time = time.time()
|
|
383
|
-
|
|
388
|
+
|
|
384
389
|
while not callback_result["received"] and (time.time() - start_time) < timeout:
|
|
385
390
|
time.sleep(0.5)
|
|
386
|
-
|
|
391
|
+
|
|
387
392
|
# Stop server
|
|
388
393
|
callback_server.shutdown()
|
|
389
394
|
callback_server.server_close()
|
|
390
|
-
|
|
395
|
+
|
|
391
396
|
if callback_result["received"]:
|
|
392
397
|
if callback_result["token"]:
|
|
393
398
|
# Save token and test connection
|
|
@@ -396,7 +401,7 @@ def login(
|
|
|
396
401
|
if callback_result.get("refresh_token"):
|
|
397
402
|
config.refresh_token = callback_result["refresh_token"]
|
|
398
403
|
config.save()
|
|
399
|
-
|
|
404
|
+
|
|
400
405
|
console.print("[green]✅ Authentication token received![/green]")
|
|
401
406
|
|
|
402
407
|
# Test the connection using health endpoint
|
|
@@ -430,14 +435,14 @@ def login(
|
|
|
430
435
|
console.print(f"[red]❌ Token validation failed: {e}[/red]")
|
|
431
436
|
console.print(f"[dim]Token saved but couldn't verify. Error type: {type(e).__name__}[/dim]")
|
|
432
437
|
raise typer.Exit(1)
|
|
433
|
-
|
|
438
|
+
|
|
434
439
|
elif callback_result["error"]:
|
|
435
440
|
console.print(f"[red]❌ Authentication failed: {callback_result['error']}[/red]")
|
|
436
441
|
raise typer.Exit(1)
|
|
437
442
|
else:
|
|
438
443
|
console.print("[red]❌ Authentication timed out. Please try again.[/red]")
|
|
439
444
|
raise typer.Exit(1)
|
|
440
|
-
|
|
445
|
+
|
|
441
446
|
except KeyboardInterrupt:
|
|
442
447
|
console.print("\n[yellow]Authentication cancelled by user.[/yellow]")
|
|
443
448
|
raise typer.Exit(1)
|
|
@@ -461,11 +466,11 @@ def status():
|
|
|
461
466
|
from ...shared.config import CONFIG_FILE
|
|
462
467
|
console.print(f"[dim]Config file: {CONFIG_FILE}[/dim]")
|
|
463
468
|
console.print(f"[dim]Base URL: {config.base_url}[/dim]")
|
|
464
|
-
|
|
469
|
+
|
|
465
470
|
if config.api_key:
|
|
466
|
-
console.print(
|
|
471
|
+
console.print("[green]✅ Authenticated[/green]")
|
|
467
472
|
console.print(f"[dim]API Key: {config.api_key[:8]}...[/dim]")
|
|
468
|
-
|
|
473
|
+
|
|
469
474
|
# Test connection
|
|
470
475
|
try:
|
|
471
476
|
import httpx
|
|
@@ -478,4 +483,4 @@ def status():
|
|
|
478
483
|
except Exception as e:
|
|
479
484
|
console.print(f"[red]❌ API connection failed: {e}[/red]")
|
|
480
485
|
else:
|
|
481
|
-
console.print("[red]❌ Not authenticated. Run 'lyceum login' first.[/red]")
|
|
486
|
+
console.print("[red]❌ Not authenticated. Run 'lyceum auth login' first.[/red]")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Python execution commands
|
|
3
|
-
"""
|
|
1
|
+
"""Python execution commands"""
|
|
4
2
|
|
|
5
3
|
from pathlib import Path
|
|
6
|
-
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
7
6
|
import typer
|
|
8
7
|
from rich.console import Console
|
|
9
|
-
import httpx
|
|
10
8
|
|
|
11
9
|
from ....shared.config import config
|
|
12
10
|
from ....shared.streaming import stream_execution_output
|
|
@@ -19,11 +17,17 @@ python_app = typer.Typer(name="python", help="Python execution commands")
|
|
|
19
17
|
@python_app.command("run")
|
|
20
18
|
def run_python(
|
|
21
19
|
code_or_file: str = typer.Argument(..., help="Python code to execute or path to Python file"),
|
|
22
|
-
machine_type: str = typer.Option(
|
|
20
|
+
machine_type: str = typer.Option(
|
|
21
|
+
"cpu", "--machine", "-m", help="Machine type (cpu, a100, h100, etc.)"
|
|
22
|
+
),
|
|
23
23
|
timeout: int = typer.Option(60, "--timeout", "-t", help="Execution timeout in seconds"),
|
|
24
|
-
file_name:
|
|
25
|
-
requirements:
|
|
26
|
-
|
|
24
|
+
file_name: str | None = typer.Option(None, "--file-name", "-f", help="Name for the execution"),
|
|
25
|
+
requirements: str | None = typer.Option(
|
|
26
|
+
None, "--requirements", "-r", help="Requirements file path or pip requirements string"
|
|
27
|
+
),
|
|
28
|
+
imports: list[str] | None = typer.Option(
|
|
29
|
+
None, "--import", help="Pre-import modules (can be used multiple times)"
|
|
30
|
+
),
|
|
27
31
|
):
|
|
28
32
|
"""Execute Python code or file on Lyceum Cloud"""
|
|
29
33
|
try:
|
|
@@ -31,7 +35,7 @@ def run_python(
|
|
|
31
35
|
code_to_execute = code_or_file
|
|
32
36
|
if Path(code_or_file).exists():
|
|
33
37
|
console.print(f"[dim]Reading code from file: {code_or_file}[/dim]")
|
|
34
|
-
with open(code_or_file
|
|
38
|
+
with open(code_or_file) as f:
|
|
35
39
|
code_to_execute = f.read()
|
|
36
40
|
# Use filename as execution name if not provided
|
|
37
41
|
if not file_name:
|
|
@@ -43,7 +47,7 @@ def run_python(
|
|
|
43
47
|
# Check if it's a file path
|
|
44
48
|
if Path(requirements).exists():
|
|
45
49
|
console.print(f"[dim]Reading requirements from file: {requirements}[/dim]")
|
|
46
|
-
with open(requirements
|
|
50
|
+
with open(requirements) as f:
|
|
47
51
|
requirements_content = f.read()
|
|
48
52
|
else:
|
|
49
53
|
# Treat as direct pip requirements string
|
|
@@ -81,7 +85,7 @@ def run_python(
|
|
|
81
85
|
execution_id = data['execution_id']
|
|
82
86
|
streaming_url = data.get('streaming_url')
|
|
83
87
|
|
|
84
|
-
console.print(
|
|
88
|
+
console.print("[green]✅ Execution started![/green]")
|
|
85
89
|
console.print(f"[dim]Execution ID: {execution_id}[/dim]")
|
|
86
90
|
|
|
87
91
|
if 'pythia_decision' in data:
|
|
@@ -89,11 +93,11 @@ def run_python(
|
|
|
89
93
|
|
|
90
94
|
# Stream the execution output
|
|
91
95
|
success = stream_execution_output(execution_id, streaming_url)
|
|
92
|
-
|
|
96
|
+
|
|
93
97
|
if not success:
|
|
94
|
-
console.print(
|
|
98
|
+
console.print("[yellow]💡 You can check the execution later with: lyceum status[/yellow]")
|
|
95
99
|
raise typer.Exit(1)
|
|
96
|
-
|
|
100
|
+
|
|
97
101
|
except Exception as e:
|
|
98
102
|
console.print(f"[red]Error: {e}[/red]")
|
|
99
|
-
raise typer.Exit(1)
|
|
103
|
+
raise typer.Exit(1)
|