Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 58 additions & 9 deletions Lucy.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
import os
import subprocess
import sys
import shutil

MIN_TERM_HEIGHT = 15
MIN_TERM_WIDTH = 65
INSTALL_ENV = {"LUCY_PIXI_AUTO_UPGRADE": "1"}

def is_installed():
"""True when the workspace has been built (mirrors launch_lucy.sh's check)."""
Expand Down Expand Up @@ -38,19 +40,45 @@ def set_dev_mode(is_enabled):
if not dev_found:
f.write(f"DEV={str(is_enabled).lower()}\n")

def run_command(command, interactive=False):
def prepend_pixi_to_path():
"""Prefer ~/.pixi/bin over system/nix pixi (official installer is usually newer)."""
pixi_bin = os.path.join(os.path.expanduser("~"), ".pixi", "bin")
if os.path.isdir(pixi_bin):
path = os.environ.get("PATH", "")
parts = path.split(os.pathsep) if path else []
if pixi_bin not in parts:
os.environ["PATH"] = pixi_bin + os.pathsep + path

def check_prereqs():
prepend_pixi_to_path()
if shutil.which("pixi") is None:
print("Missing pixi. Install: https://pixi.prefix.dev/latest/installation/")
return False
return True

def run_command(command, interactive=False, extra_env=None):
"""Runs a command.

If interactive is True, runs natively in the terminal.
"""
print(f"--- Running: {' '.join(command)} ---")
env = os.environ.copy()
if extra_env:
env.update(extra_env)
try:
if interactive:
# Inherit standard IO to maintain terminal size and TTY functionality
return subprocess.run(command).returncode
return subprocess.run(command, env=env).returncode
else:
# Popen is fine for non-interactive scripts like install/build
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
process = subprocess.Popen(
command,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
env=env,
stdin=subprocess.DEVNULL,
)
while True:
output = process.stdout.readline()
if output == '' and process.poll() is not None:
Expand Down Expand Up @@ -93,7 +121,7 @@ def not_installed_screen(stdscr):
checkbox = "[x]" if is_dev_mode else "[ ]"
dev_line = f"{checkbox} Developer Mode"
stdscr.addstr(start + len(lines), max(0, (w - len(dev_line)) // 2), dev_line)
hint_line = "(SSH clones, interactive dev shell)"
hint_line = "(SSH clones during install; does not change Launch)"
stdscr.addstr(start + len(lines) + 1, max(0, (w - len(hint_line)) // 2), hint_line, curses.A_DIM)

footer = [
Expand Down Expand Up @@ -153,7 +181,7 @@ def main_tui(stdscr):
else:
stdscr.addstr(2 + i, 4, f"{prefix}{option}")

stdscr.addstr(h - 2, 2, "Enter/Space: Select/Toggle | Up/Down: Navigate", curses.A_DIM)
stdscr.addstr(h - 2, 2, "Or: pixi run build, ./launch_lucy.sh", curses.A_DIM)
stdscr.refresh()

key = stdscr.getch()
Expand All @@ -173,9 +201,19 @@ def main_tui(stdscr):
is_dev_mode = not is_dev_mode
set_dev_mode(is_dev_mode)
elif selected_option == "Update":
return {"cmd": ["./install.sh"], "interactive": False, "name": "Install"}
return {
"cmd": ["./install.sh"],
"interactive": False,
"name": "Install",
"extra_env": INSTALL_ENV,
}
elif selected_option == "Rebuild":
return {"cmd": ["./install.sh", "--build-only"], "interactive": False, "name": "Rebuild"}
return {
"cmd": ["pixi", "run", "build"],
"interactive": False,
"name": "Rebuild",
"followup": [["pixi", "run", "panel-install"]],
}
elif selected_option == "Launch":
return {"cmd": ["./launch_lucy.sh"], "interactive": True, "name": "Launch"}
elif selected_option == "Exit":
Expand All @@ -195,6 +233,9 @@ def check_initial_size():
print(f"Please increase the terminal size to at least {MIN_TERM_WIDTH}x{MIN_TERM_HEIGHT} characters.", file=sys.stderr)
sys.exit(1)

if not check_prereqs():
sys.exit(1)

# First run: nothing built yet — offer to install before showing the menu.
if not is_installed():
try:
Expand All @@ -208,7 +249,7 @@ def check_initial_size():
sys.exit(1)
if not wants_install:
sys.exit(0)
rc = run_command(["./install.sh"], interactive=False)
rc = run_command(["./install.sh"], extra_env=INSTALL_ENV)
if rc != 0:
print(f"\n--- Install finished with exit code {rc} ---")
print("Press Enter to exit.")
Expand Down Expand Up @@ -240,7 +281,15 @@ def check_initial_size():
# User selected Exit
break

rc = run_command(task["cmd"], interactive=task.get("interactive", False))
rc = run_command(
task["cmd"],
interactive=task.get("interactive", False),
extra_env=task.get("extra_env"),
)
for follow in task.get("followup", []):
if rc != 0:
break
rc = run_command(follow, interactive=False, extra_env=task.get("extra_env"))

if task.get("interactive", False):
print(f"--- Session finished with exit code {rc} ---")
Expand Down
Loading
Loading