Coverage for src/lilbee/cli/engine.py: 100%
20 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-08-14 11:46 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-08-14 11:46 +0000
1"""Engine lifecycle commands: manage the shared engine without opening the TUI."""
3from __future__ import annotations
5import typer
7from lilbee.cli.app import console, json_out
8from lilbee.core.config import cfg
9from lilbee.providers.fleet.swap_manager import stop_engine
10from lilbee.runtime.engine_lock import build_lock, machine_engine_dir, private_engine_dir
12engine_app = typer.Typer(
13 name="engine",
14 help="Manage the local inference engine.",
15 no_args_is_help=True,
16)
18_STOPPED = "Stopped the engine and freed its memory."
19_NOTHING_RUNNING = "No engine is running."
22@engine_app.command("stop")
23def stop() -> None:
24 """Stop the shared engine now, whoever started it."""
25 stopped: list[str] = []
26 # Machine slot, this root's overflow dir, and the pre-upgrade legacy location
27 # (base builds recorded state directly under data_dir): a stale legacy engine
28 # must still be stoppable after an upgrade relocated the engine dirs.
29 dirs = dict.fromkeys((machine_engine_dir(), private_engine_dir(cfg.data_root), cfg.data_dir))
30 for engine_dir in dirs:
31 # Serialize against a concurrent builder, whose spawn + state write + health
32 # wait all run under this lock; an unlocked stop landing mid-build would kill
33 # the just-spawned engine and unlink its fresh record. best_effort so a wedged
34 # builder cannot make the off switch hang.
35 with build_lock(engine_dir, best_effort=True):
36 stopped.extend(stop_engine(engine_dir))
37 if cfg.json_mode:
38 json_out({"command": "engine stop", "stopped": sorted(set(stopped))})
39 return
40 console.print(_STOPPED if stopped else _NOTHING_RUNNING)