Coverage for src/lilbee/cli/launchers/setup_gate.py: 100%
38 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"""First-run consent gate shared by launchers that write outside lilbee's dirs."""
3from __future__ import annotations
5import json
6import os
7import sys
8import tempfile
9from collections.abc import Callable
10from pathlib import Path
12import typer
14from lilbee.core.config import cfg
17def _marker_path(marker_name: str) -> Path:
18 """lilbee's record that a client's setup already ran (so launch doesn't re-prompt)."""
19 return cfg.data_dir / "launchers" / marker_name
22def _record_setup(marker_name: str) -> None:
23 """Persist that the user accepted setup; idempotent (atomic write)."""
24 path = _marker_path(marker_name)
25 path.parent.mkdir(parents=True, exist_ok=True)
26 tmp_name: str | None = None
27 try:
28 with tempfile.NamedTemporaryFile(dir=path.parent, suffix=".tmp", delete=False) as tmp:
29 tmp_name = tmp.name
30 tmp.write(json.dumps({"accepted": True}).encode("utf-8"))
31 os.replace(tmp_name, path)
32 except BaseException:
33 if tmp_name is not None:
34 Path(tmp_name).unlink(missing_ok=True)
35 raise
38def _is_interactive() -> bool:
39 """True when stdin is a TTY, so a confirmation prompt can be answered."""
40 return sys.stdin.isatty()
43def confirm_first_run_setup(
44 *,
45 marker_name: str,
46 client_name: str,
47 print_plan: Callable[[], None],
48 assume_yes: bool,
49) -> bool:
50 """Prompt before a client's first setup; True means proceed.
52 Skipped when already recorded, when *assume_yes* is set, or when stdin is
53 not a TTY (scripts/CI: invoking the launch is the consent there). The
54 choice is remembered so later launches don't re-prompt.
55 """
56 if _marker_path(marker_name).exists():
57 return True
58 print_plan()
59 if assume_yes or not _is_interactive():
60 _record_setup(marker_name)
61 return True
62 if not typer.confirm(f"Proceed with {client_name} setup?", default=True):
63 typer.secho(f"Skipped {client_name} setup.", fg=typer.colors.YELLOW)
64 return False
65 _record_setup(marker_name)
66 return True