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
« 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.
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
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)
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
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)
173def runs_while_streaming(name: str) -> bool:
174 """Whether *name* (command or alias) may be submitted mid-answer.
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
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)