Coverage for src/lilbee/cli/tui/command_registry.py: 100%
36 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-11-06 08:17 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-11-06 08:17 +0000
1"""Single source of truth for TUI slash commands.
3Every slash command is defined once here. All other modules (chat dispatch,
4suggester, help modal, autocomplete) read from this registry.
5"""
7from __future__ import annotations
9from dataclasses import dataclass
12@dataclass(frozen=True)
13class SlashCommand:
14 """Definition of a single slash command."""
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
27ANALYZE_OFF_ARG = "off"
28ANALYZE_REPORT_ARG = "report"
30COMMANDS: tuple[SlashCommand, ...] = (
31 SlashCommand(
32 "/model",
33 "_cmd_model",
34 aliases=(),
35 args_hint="[name]",
36 help_text="Switch chat model (no arg opens the catalog)",
37 # The chat screen queues a mid-answer switch, so this never cuts a turn.
38 allowed_while_streaming=True,
39 ),
40 SlashCommand(
41 "/add",
42 "_cmd_add",
43 aliases=(),
44 args_hint="<path>",
45 help_text="Add file or folder to the knowledge base",
46 ),
47 SlashCommand(
48 "/crawl",
49 "_cmd_crawl",
50 aliases=(),
51 args_hint="[url]",
52 help_text="Crawl a URL (no arg opens the dialog)",
53 ),
54 SlashCommand(
55 "/delete",
56 "_cmd_delete",
57 aliases=(),
58 args_hint="<name>",
59 help_text="Remove a document from the index",
60 ),
61 SlashCommand(
62 "/prune-ignored",
63 "_cmd_prune_ignored",
64 aliases=(),
65 args_hint="",
66 help_text="Drop indexed documents a .lilbeeignore now excludes",
67 ),
68 SlashCommand(
69 "/set",
70 "_cmd_set",
71 aliases=(),
72 args_hint="<key> <value>",
73 help_text="Change a setting; a list setting takes one item here, so use Settings for more",
74 ),
75 SlashCommand(
76 "/theme",
77 "_cmd_theme",
78 aliases=(),
79 args_hint="[name]",
80 help_text="Switch theme (no arg opens the theme list)",
81 ),
82 SlashCommand(
83 "/profile",
84 "_cmd_profile",
85 args_hint="[name]",
86 help_text="Switch profile (no arg opens the Profile tab in Settings)",
87 ),
88 SlashCommand(
89 "/analyze",
90 "_cmd_analyze",
91 args_hint=f"[dir|{ANALYZE_REPORT_ARG}|{ANALYZE_OFF_ARG}]",
92 help_text=(
93 f"Recommend a profile for your documents ({ANALYZE_REPORT_ARG} opens the last "
94 f"report, {ANALYZE_OFF_ARG} hides the tip)"
95 ),
96 ),
97 SlashCommand(
98 "/reset",
99 "_cmd_reset",
100 help_text="Factory reset (asks for confirmation)",
101 ),
102 SlashCommand(
103 "/rebuild",
104 "_cmd_rebuild",
105 help_text="Re-index the documents directory from scratch (asks for confirmation)",
106 ),
107 SlashCommand(
108 "/export",
109 "_cmd_export",
110 aliases=(),
111 args_hint="<path>",
112 help_text="Export a per-page text dataset (parquet or jsonl)",
113 ),
114 SlashCommand(
115 "/import",
116 "_cmd_import",
117 aliases=(),
118 args_hint="<path>",
119 help_text="Import a per-page text dataset, re-embedding it",
120 ),
121 SlashCommand("/status", "_cmd_status", help_text="Show knowledge-base status"),
122 SlashCommand("/settings", "_cmd_settings", help_text="Open settings"),
123 SlashCommand(
124 "/models",
125 "_cmd_catalog",
126 aliases=("/m", "/catalog"),
127 help_text="Browse the model catalog",
128 ),
129 SlashCommand(
130 "/remember",
131 "_cmd_remember",
132 args_hint="<text>",
133 help_text="Save a memory (prefix with 'pref:' for a preference)",
134 ),
135 SlashCommand(
136 "/memories",
137 "_cmd_memories",
138 help_text="Browse and manage your saved memories",
139 ),
140 SlashCommand(
141 "/wiki",
142 "_cmd_wiki",
143 help_text="Open the wiki view",
144 ),
145 SlashCommand(
146 "/remove",
147 "_cmd_remove",
148 aliases=(),
149 args_hint="<name>",
150 help_text="Uninstall a downloaded model",
151 ),
152 SlashCommand(
153 "/login",
154 "_cmd_login",
155 args_hint="[token]",
156 help_text="Log in to Hugging Face (no arg opens the token page)",
157 ),
158 SlashCommand("/help", "_cmd_help", aliases=("/h",), help_text="Show the slash-command catalog"),
159 SlashCommand("/version", "_cmd_version", help_text="Show the lilbee version"),
160 SlashCommand(
161 "/cancel",
162 "_cmd_cancel",
163 help_text="Cancel any in-flight operations",
164 # Stopping the live turn is the point, so it must reach a live stream.
165 allowed_while_streaming=True,
166 ),
167 SlashCommand("/clear", "_cmd_clear", help_text="Clear the conversation"),
168 SlashCommand("/sessions", "_cmd_sessions", help_text="Toggle the sessions drawer"),
169 SlashCommand(
170 "/fork",
171 "_cmd_fork",
172 help_text="Branch this conversation into a new session after any answer",
173 ),
174 SlashCommand(
175 "/export-chat",
176 "_cmd_export_chat",
177 args_hint="[path]",
178 help_text="Export this conversation as markdown (default: the current directory)",
179 ),
180 SlashCommand("/quit", "_cmd_quit", aliases=("/q", "/exit"), help_text="Exit lilbee"),
181)
184def build_dispatch_dict() -> dict[str, str]:
185 """Build a mapping from command name (and aliases) to handler method name."""
186 dispatch: dict[str, str] = {}
187 for cmd in COMMANDS:
188 dispatch[cmd.name] = cmd.handler
189 for alias in cmd.aliases:
190 dispatch[alias] = cmd.handler
191 return dispatch
194def get_command(name: str) -> SlashCommand:
195 """Return the registry entry whose name is exactly *name* (aliases excluded)."""
196 for cmd in COMMANDS:
197 if cmd.name == name:
198 return cmd
199 raise KeyError(name)
202def runs_while_streaming(name: str) -> bool:
203 """Whether *name* (command or alias) may be submitted mid-answer.
205 Unknown names are False so the streaming gate keeps rejecting them; the
206 unknown-command toast is the caller's job.
207 """
208 for cmd in COMMANDS:
209 if name == cmd.name or name in cmd.aliases:
210 return cmd.allowed_while_streaming
211 return False
214def completion_names() -> tuple[str, ...]:
215 """All command names including aliases, for tab completion."""
216 names: list[str] = []
217 for cmd in COMMANDS:
218 names.append(cmd.name)
219 names.extend(cmd.aliases)
220 return tuple(names)