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