Coverage for src/lilbee/runtime/launcher.py: 100%

37 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-08-14 11:46 +0000

1"""Thin CLI entry point: shows splash animation while heavy deps load. 

2 

3This module imports only ``splash`` (which uses only stdlib + subprocess), 

4launches the animation process, then performs the heavy 

5``from lilbee.cli import app`` import while the bee animates on stderr. 

6""" 

7 

8from __future__ import annotations 

9 

10import contextlib 

11import io 

12import os 

13import sys 

14 

15# Shells fetch tab completions by running the console script with 

16# ``_<PROG>_COMPLETE=...`` set (and an empty argv); click answers those itself. 

17_COMPLETION_ENV_SUFFIX = "_COMPLETE" 

18 

19 

20def _shell_completion_active() -> bool: 

21 """True when this process was spawned by a shell's tab-completion.""" 

22 return any( 

23 name.startswith("_") and name.endswith(_COMPLETION_ENV_SUFFIX) for name in os.environ 

24 ) 

25 

26 

27def _force_utf8_stdio() -> None: 

28 """Make stdio UTF-8 so a no-locale (GUI-spawned) launch can't crash on non-ASCII output.""" 

29 for stream in (sys.stdout, sys.stderr): 

30 if isinstance(stream, io.TextIOWrapper): 

31 # Swallow on an already-detached stream; non-TextIOWrapper streams 

32 # (StringIO redirects, capture shims) need no reconfiguration. 

33 with contextlib.suppress(ValueError, OSError): 

34 stream.reconfigure(encoding="utf-8", errors="backslashreplace") 

35 

36 

37def main() -> None: 

38 """Entry point for the ``lilbee`` console script.""" 

39 _force_utf8_stdio() 

40 args = sys.argv[1:] 

41 # Help exits before the TUI, so it must not print through the animation. 

42 wants_help = any(arg in ("--help", "-h") for arg in args) 

43 is_interactive = ( 

44 (not args or args[0] in ("chat", "")) and not wants_help and not _shell_completion_active() 

45 ) 

46 

47 if not is_interactive: 

48 from lilbee.cli import app 

49 

50 app() 

51 return 

52 

53 from lilbee.app.services import mark_interactive_session 

54 from lilbee.runtime.splash import start, stop 

55 

56 # A person is at the TUI for its whole lifetime, so hold the engine resident 

57 # (no idle-unload) until they close lilbee. Recorded here, on the sole 

58 # interactive entry point and before anything builds the services container, 

59 # so a one-shot CLI or the MCP server never trips it. 

60 mark_interactive_session() 

61 

62 handle = start() 

63 

64 try: 

65 from lilbee.cli import app 

66 except BaseException: 

67 stop(handle) 

68 raise 

69 

70 # The splash keeps animating through the CLI dispatch and the TUI stack's 

71 # own imports (Textual and every screen), which on a cold disk take seconds; 

72 # run_tui dismisses it right before Textual takes over the terminal. 

73 try: 

74 app() 

75 except KeyboardInterrupt: 

76 sys.stderr.write("\033[?25h") 

77 sys.stderr.flush() 

78 raise SystemExit(130) from None