Coverage for src/lilbee/cli/launchers/config_file.py: 100%

18 statements  

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

1"""Load and atomically write an agent's on-disk config, refusing to clobber a corrupt file.""" 

2 

3from __future__ import annotations 

4 

5from collections.abc import Callable 

6from pathlib import Path 

7from typing import Any 

8 

9import typer 

10 

11from lilbee.core.security import write_private_text 

12 

13 

14def load_config_dict( 

15 path: Path, 

16 *, 

17 parse: Callable[[str], Any], 

18 parse_error: type[Exception], 

19 label: str, 

20) -> dict[str, Any]: 

21 """Return the parsed mapping at *path*, or ``{}`` when absent or empty. 

22 

23 Exits non-zero without writing when the file does not parse, so a corrupt 

24 user config is never overwritten.""" 

25 if not path.exists(): 

26 return {} 

27 raw = path.read_text(encoding="utf-8") 

28 try: 

29 parsed = parse(raw) 

30 except parse_error as exc: 

31 typer.secho( 

32 f"Your {label} did not parse, so lilbee will not overwrite it. " 

33 "Fix or remove it, then retry.", 

34 err=True, 

35 fg=typer.colors.RED, 

36 ) 

37 raise typer.Exit(1) from exc 

38 return parsed if isinstance(parsed, dict) else {} 

39 

40 

41def atomic_write_text(path: Path, text: str) -> None: 

42 """Write *text* to *path* atomically (temp file + os.replace), creating parents. 

43 

44 Agent configs carry the lilbee bearer token, so they get the same 

45 owner-only treatment as the other secret files. This already wrote through 

46 a temp file, which is created 0600 and keeps that mode across the replace; 

47 sharing the one implementation just makes that explicit. 

48 """ 

49 write_private_text(path, text)