Coverage for src/lilbee/cli/tui/command_registry.py: 100%

34 statements  

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

1"""Single source of truth for TUI slash commands. 

2 

3Every slash command is defined once here. All other modules (chat dispatch, 

4suggester, help modal, autocomplete) read from this registry. 

5""" 

6 

7from __future__ import annotations 

8 

9from dataclasses import dataclass 

10 

11 

12@dataclass(frozen=True) 

13class SlashCommand: 

14 """Definition of a single slash command.""" 

15 

16 name: str 

17 handler: str 

18 aliases: tuple[str, ...] = () 

19 args_hint: str = "" 

20 help_text: str = "" 

21 # Reachable from the command bar while an answer is streaming. Off by 

22 # default: a command that touches the index, the fleet or the transcript 

23 # would race the live turn. 

24 allowed_while_streaming: bool = False 

25 

26 

27COMMANDS: tuple[SlashCommand, ...] = ( 

28 SlashCommand( 

29 "/model", 

30 "_cmd_model", 

31 aliases=(), 

32 args_hint="[name]", 

33 help_text="Switch chat model (no arg opens the catalog)", 

34 # The chat screen queues a mid-answer switch, so this never cuts a turn. 

35 allowed_while_streaming=True, 

36 ), 

37 SlashCommand( 

38 "/add", 

39 "_cmd_add", 

40 aliases=(), 

41 args_hint="<path>", 

42 help_text="Add file or folder to the knowledge base", 

43 ), 

44 SlashCommand( 

45 "/crawl", 

46 "_cmd_crawl", 

47 aliases=(), 

48 args_hint="[url]", 

49 help_text="Crawl a URL (no arg opens the dialog)", 

50 ), 

51 SlashCommand( 

52 "/delete", 

53 "_cmd_delete", 

54 aliases=(), 

55 args_hint="<name>", 

56 help_text="Remove a document from the index", 

57 ), 

58 SlashCommand( 

59 "/prune-ignored", 

60 "_cmd_prune_ignored", 

61 aliases=(), 

62 args_hint="", 

63 help_text="Drop indexed documents a .lilbeeignore now excludes", 

64 ), 

65 SlashCommand( 

66 "/set", 

67 "_cmd_set", 

68 aliases=(), 

69 args_hint="<key> <value>", 

70 help_text="Change a setting", 

71 ), 

72 SlashCommand( 

73 "/theme", 

74 "_cmd_theme", 

75 aliases=(), 

76 args_hint="[name]", 

77 help_text="Switch theme (no arg opens the theme list)", 

78 ), 

79 SlashCommand( 

80 "/reset", 

81 "_cmd_reset", 

82 help_text="Factory reset (asks for confirmation)", 

83 ), 

84 SlashCommand( 

85 "/rebuild", 

86 "_cmd_rebuild", 

87 help_text="Re-index the documents directory from scratch", 

88 ), 

89 SlashCommand( 

90 "/export", 

91 "_cmd_export", 

92 aliases=(), 

93 args_hint="<path>", 

94 help_text="Export a per-page text dataset (parquet or jsonl)", 

95 ), 

96 SlashCommand( 

97 "/import", 

98 "_cmd_import", 

99 aliases=(), 

100 args_hint="<path>", 

101 help_text="Import a per-page text dataset, re-embedding it", 

102 ), 

103 SlashCommand("/status", "_cmd_status", help_text="Show knowledge-base status"), 

104 SlashCommand("/settings", "_cmd_settings", help_text="Open settings"), 

105 SlashCommand( 

106 "/models", 

107 "_cmd_catalog", 

108 aliases=("/m", "/catalog"), 

109 help_text="Browse the model catalog", 

110 ), 

111 SlashCommand( 

112 "/remember", 

113 "_cmd_remember", 

114 args_hint="<text>", 

115 help_text="Save a memory (prefix with 'pref:' for a preference)", 

116 ), 

117 SlashCommand( 

118 "/memories", 

119 "_cmd_memories", 

120 help_text="Browse and manage your saved memories", 

121 ), 

122 SlashCommand( 

123 "/wiki", 

124 "_cmd_wiki", 

125 help_text="Open the wiki view", 

126 ), 

127 SlashCommand( 

128 "/remove", 

129 "_cmd_remove", 

130 aliases=(), 

131 args_hint="<name>", 

132 help_text="Uninstall a downloaded model", 

133 ), 

134 SlashCommand( 

135 "/login", 

136 "_cmd_login", 

137 args_hint="[token]", 

138 help_text="Log in to Hugging Face (no arg opens the token page)", 

139 ), 

140 SlashCommand("/help", "_cmd_help", aliases=("/h",), help_text="Show the slash-command catalog"), 

141 SlashCommand("/version", "_cmd_version", help_text="Show the lilbee version"), 

142 SlashCommand( 

143 "/cancel", 

144 "_cmd_cancel", 

145 help_text="Cancel any in-flight operations", 

146 # Stopping the live turn is the point, so it must reach a live stream. 

147 allowed_while_streaming=True, 

148 ), 

149 SlashCommand("/clear", "_cmd_clear", help_text="Clear the conversation"), 

150 SlashCommand("/sessions", "_cmd_sessions", help_text="Open the sessions drawer"), 

151 SlashCommand("/quit", "_cmd_quit", aliases=("/q", "/exit"), help_text="Exit lilbee"), 

152) 

153 

154 

155def build_dispatch_dict() -> dict[str, str]: 

156 """Build a mapping from command name (and aliases) to handler method name.""" 

157 dispatch: dict[str, str] = {} 

158 for cmd in COMMANDS: 

159 dispatch[cmd.name] = cmd.handler 

160 for alias in cmd.aliases: 

161 dispatch[alias] = cmd.handler 

162 return dispatch 

163 

164 

165def get_command(name: str) -> SlashCommand: 

166 """Return the registry entry whose name is exactly *name* (aliases excluded).""" 

167 for cmd in COMMANDS: 

168 if cmd.name == name: 

169 return cmd 

170 raise KeyError(name) 

171 

172 

173def runs_while_streaming(name: str) -> bool: 

174 """Whether *name* (command or alias) may be submitted mid-answer. 

175 

176 Unknown names are False so the streaming gate keeps rejecting them; the 

177 unknown-command toast is the caller's job. 

178 """ 

179 for cmd in COMMANDS: 

180 if name == cmd.name or name in cmd.aliases: 

181 return cmd.allowed_while_streaming 

182 return False 

183 

184 

185def completion_names() -> tuple[str, ...]: 

186 """All command names including aliases, for tab completion.""" 

187 names: list[str] = [] 

188 for cmd in COMMANDS: 

189 names.append(cmd.name) 

190 names.extend(cmd.aliases) 

191 return tuple(names)