Coverage for src / lilbee / cli / commands / memory.py: 100%

70 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-06-28 01:01 +0000

1"""Local memory commands: add, list, recall, remove.""" 

2 

3from __future__ import annotations 

4 

5from pathlib import Path 

6 

7import typer 

8from rich.table import Table 

9 

10from lilbee.app.memory import ( 

11 MEMORY_DISABLED_HINT, 

12 forget, 

13 list_memories, 

14 memory_enabled, 

15 recall, 

16 remember, 

17) 

18from lilbee.cli.app import ( 

19 apply_overrides, 

20 console, 

21 data_dir_option, 

22 global_option, 

23) 

24from lilbee.cli.helpers import json_output 

25from lilbee.core.config import cfg 

26from lilbee.data.store import MemoryKind 

27 

28memory_app = typer.Typer(help="Manage your local long-term memory (off unless enabled).") 

29 

30_ID_PREVIEW_CHARS = 8 

31 

32 

33def _disabled() -> None: 

34 """Report that memory is off, as JSON or a console line.""" 

35 if cfg.json_mode: 

36 json_output({"error": MEMORY_DISABLED_HINT}) 

37 else: 

38 console.print(MEMORY_DISABLED_HINT) 

39 

40 

41@memory_app.command(name="add") 

42def memory_add( 

43 text: str, 

44 preference: bool = typer.Option( 

45 False, "--preference", "-p", help="Store as an always-recalled preference." 

46 ), 

47 shared: bool = typer.Option(False, "--shared", help="Also expose this memory to agents."), 

48 data_dir: Path | None = data_dir_option, 

49 use_global: bool = global_option, 

50) -> None: 

51 """Remember a fact (or preference) in your local memory.""" 

52 apply_overrides(data_dir=data_dir, use_global=use_global) 

53 if not memory_enabled(): 

54 _disabled() 

55 return 

56 kind = MemoryKind.PREFERENCE if preference else MemoryKind.FACT 

57 memory_id = remember(text, kind=kind, shared=shared) 

58 if cfg.json_mode: 

59 json_output({"id": memory_id, "kind": kind.value}) 

60 return 

61 console.print(f"Remembered ({kind.value}).") 

62 

63 

64@memory_app.command(name="list") 

65def memory_list_cmd( 

66 data_dir: Path | None = data_dir_option, 

67 use_global: bool = global_option, 

68) -> None: 

69 """List your stored memories.""" 

70 apply_overrides(data_dir=data_dir, use_global=use_global) 

71 if not memory_enabled(): 

72 _disabled() 

73 return 

74 memories = list_memories() 

75 if cfg.json_mode: 

76 json_output( 

77 { 

78 "memories": [ 

79 { 

80 "id": m.id, 

81 "kind": m.kind.value, 

82 "shared": m.shared, 

83 "text": m.text, 

84 } 

85 for m in memories 

86 ] 

87 } 

88 ) 

89 return 

90 if not memories: 

91 console.print("No memories stored.") 

92 return 

93 table = Table("id", "kind", "shared", "text") 

94 for m in memories: 

95 table.add_row(m.id[:_ID_PREVIEW_CHARS], m.kind.value, "yes" if m.shared else "no", m.text) 

96 console.print(table) 

97 

98 

99@memory_app.command(name="recall") 

100def memory_recall_cmd( 

101 query: str, 

102 data_dir: Path | None = data_dir_option, 

103 use_global: bool = global_option, 

104) -> None: 

105 """Recall facts relevant to a query.""" 

106 apply_overrides(data_dir=data_dir, use_global=use_global) 

107 if not memory_enabled(): 

108 _disabled() 

109 return 

110 memories = recall(query) 

111 if cfg.json_mode: 

112 json_output({"memories": [{"id": m.id, "text": m.text} for m in memories]}) 

113 return 

114 if not memories: 

115 console.print("No relevant memories.") 

116 return 

117 for m in memories: 

118 console.print(f"- {m.text}") 

119 

120 

121@memory_app.command(name="remove") 

122def memory_remove( 

123 memory_id: str, 

124 data_dir: Path | None = data_dir_option, 

125 use_global: bool = global_option, 

126) -> None: 

127 """Delete a memory by id.""" 

128 apply_overrides(data_dir=data_dir, use_global=use_global) 

129 if not memory_enabled(): 

130 _disabled() 

131 return 

132 forget(memory_id) 

133 if cfg.json_mode: 

134 json_output({"removed": memory_id}) 

135 return 

136 console.print(f"Removed {memory_id}.")