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

34 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-08-14 11:46 +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 "/set", 

60 "_cmd_set", 

61 aliases=(), 

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

63 help_text="Change a setting", 

64 ), 

65 SlashCommand( 

66 "/theme", 

67 "_cmd_theme", 

68 aliases=(), 

69 args_hint="[name]", 

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

71 ), 

72 SlashCommand( 

73 "/reset", 

74 "_cmd_reset", 

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

76 ), 

77 SlashCommand( 

78 "/rebuild", 

79 "_cmd_rebuild", 

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

81 ), 

82 SlashCommand( 

83 "/export", 

84 "_cmd_export", 

85 aliases=(), 

86 args_hint="<path>", 

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

88 ), 

89 SlashCommand( 

90 "/import", 

91 "_cmd_import", 

92 aliases=(), 

93 args_hint="<path>", 

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

95 ), 

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

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

98 SlashCommand( 

99 "/models", 

100 "_cmd_catalog", 

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

102 help_text="Browse the model catalog", 

103 ), 

104 SlashCommand( 

105 "/remember", 

106 "_cmd_remember", 

107 args_hint="<text>", 

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

109 ), 

110 SlashCommand( 

111 "/memories", 

112 "_cmd_memories", 

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

114 ), 

115 SlashCommand( 

116 "/wiki", 

117 "_cmd_wiki", 

118 help_text="Open the wiki view", 

119 ), 

120 SlashCommand( 

121 "/remove", 

122 "_cmd_remove", 

123 aliases=(), 

124 args_hint="<name>", 

125 help_text="Uninstall a downloaded model", 

126 ), 

127 SlashCommand( 

128 "/login", 

129 "_cmd_login", 

130 args_hint="[token]", 

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

132 ), 

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

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

135 SlashCommand( 

136 "/cancel", 

137 "_cmd_cancel", 

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

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

140 allowed_while_streaming=True, 

141 ), 

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

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

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

145) 

146 

147 

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

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

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

151 for cmd in COMMANDS: 

152 dispatch[cmd.name] = cmd.handler 

153 for alias in cmd.aliases: 

154 dispatch[alias] = cmd.handler 

155 return dispatch 

156 

157 

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

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

160 for cmd in COMMANDS: 

161 if cmd.name == name: 

162 return cmd 

163 raise KeyError(name) 

164 

165 

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

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

168 

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

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

171 """ 

172 for cmd in COMMANDS: 

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

174 return cmd.allowed_while_streaming 

175 return False 

176 

177 

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

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

180 names: list[str] = [] 

181 for cmd in COMMANDS: 

182 names.append(cmd.name) 

183 names.extend(cmd.aliases) 

184 return tuple(names)