Skip to content

Commit e3826af

Browse files
aboucaudclaude
andcommitted
feat(launcher): rich progress spinners and /lc-new welcome panel
- Replace stderr prints with Console.status() spinners (disappear on completion) - Install subprocess output captured; stderr surfaced in red on failure - Show a rich Panel before exec suggesting /lc-new to new users Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent d15c903 commit e3826af

1 file changed

Lines changed: 45 additions & 26 deletions

File tree

src/lightcone/engine/launcher.py

Lines changed: 45 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@
1414
import os
1515
import re
1616
import subprocess
17-
import sys
1817
from dataclasses import dataclass, field
1918
from pathlib import Path
2019
from uuid import uuid4
2120

21+
from rich.console import Console
22+
2223
from lightcone.engine.container import (
2324
_DAEMONLESS_RUNTIMES,
2425
ContainerBuildError,
@@ -36,6 +37,8 @@
3637
# Registry where pre-built release images are published.
3738
_GHCR_PREFIX = "ghcr.io/lightconeresearch"
3839

40+
_console = Console(stderr=True)
41+
3942
# Local image name for the shared sandbox base image.
4043
_SANDBOX_IMAGE_NAME = "lightcone-sandbox"
4144

@@ -394,16 +397,16 @@ def _try_pull_and_cache(
394397
if runtime in _DAEMONLESS_RUNTIMES:
395398
return False
396399
try:
397-
_print(f"Pulling {registry_ref} from registry…")
398-
pull_image(registry_ref, runtime=runtime)
399-
# Retag to the content-addressed local tag so the rest of the launch
400-
# pipeline (image_exists_locally, _exec_interactive) works unchanged.
401-
subprocess.run(
402-
[runtime, "tag", registry_ref, tag],
403-
check=True,
404-
capture_output=True,
405-
)
406-
save_image_as_tarball(tag, tarball, runtime=runtime)
400+
with _console.status(f"Pulling [bold]{registry_ref}[/] from registry…"):
401+
pull_image(registry_ref, runtime=runtime)
402+
# Retag to the content-addressed local tag so the rest of the launch
403+
# pipeline (image_exists_locally, _exec_interactive) works unchanged.
404+
subprocess.run(
405+
[runtime, "tag", registry_ref, tag],
406+
check=True,
407+
capture_output=True,
408+
)
409+
save_image_as_tarball(tag, tarball, runtime=runtime)
407410
return True
408411
except (ContainerBuildError, subprocess.CalledProcessError, OSError):
409412
_print("Registry pull failed — falling back to local build.")
@@ -449,18 +452,23 @@ def _ensure_harness_image(
449452

450453
tmp_name = f"lc-install-{target.name}-{uuid4().hex[:8]}"
451454
install_cmd = " && ".join(target.install_cmds)
452-
_print(f"Installing {target.name} harness (first run — this may take a few minutes)…")
453455
try:
454-
subprocess.run(
455-
[runtime, "run", "--entrypoint", "sh", "--name", tmp_name,
456-
base_image, "-c", install_cmd],
457-
check=True,
458-
)
459-
subprocess.run(
460-
[runtime, "commit", tmp_name, committed_tag],
461-
check=True,
462-
capture_output=True,
463-
)
456+
with _console.status(f"Installing [bold]{target.name}[/] harness…"):
457+
result = subprocess.run(
458+
[runtime, "run", "--entrypoint", "sh", "--name", tmp_name,
459+
base_image, "-c", install_cmd],
460+
capture_output=True,
461+
text=True,
462+
)
463+
if result.returncode != 0:
464+
if result.stderr:
465+
_console.print(result.stderr.strip(), style="red")
466+
raise subprocess.CalledProcessError(result.returncode, result.args)
467+
subprocess.run(
468+
[runtime, "commit", tmp_name, committed_tag],
469+
check=True,
470+
capture_output=True,
471+
)
464472
except subprocess.CalledProcessError as exc:
465473
raise ContainerBuildError(f"Harness install failed for {target.name}: {exc}") from exc
466474
finally:
@@ -495,9 +503,9 @@ def launch_target(
495503
registry_ref = _registry_image_ref(target.registry_name or target.name, version)
496504
pulled = _try_pull_and_cache(tag, registry_ref, tarball, runtime=choice.runtime)
497505
if not pulled:
498-
_print(f"Building {name} container (first run — this may take a few minutes)…")
499-
build_image(tag, rendered_cf, rendered_cf.parent, runtime=choice.runtime)
500-
save_image_as_tarball(tag, tarball, runtime=choice.runtime)
506+
with _console.status(f"Building [bold]{name}[/] container…"):
507+
build_image(tag, rendered_cf, rendered_cf.parent, runtime=choice.runtime)
508+
save_image_as_tarball(tag, tarball, runtime=choice.runtime)
501509

502510
if not image_exists_locally(tag, runtime=choice.runtime, project_path=project_root):
503511
load_image_from_tarball(tarball, runtime=choice.runtime)
@@ -607,8 +615,19 @@ def _exec_interactive(
607615
if os.environ.get("LIGHTCONE_LAUNCH_DEBUG"):
608616
_print(f"[lc launch debug] {' '.join(cmd)}")
609617

618+
from rich.panel import Panel
619+
620+
_console.print(
621+
Panel(
622+
"Run [bold cyan]/lc-new[/] to scaffold a new project and get started.",
623+
title="[bold]lightcone sandbox[/]",
624+
border_style="cyan",
625+
expand=False,
626+
)
627+
)
628+
610629
os.execvp(cmd[0], cmd)
611630

612631

613632
def _print(msg: str) -> None:
614-
print(msg, file=sys.stderr)
633+
_console.print(msg)

0 commit comments

Comments
 (0)