Coverage for src / lilbee / cli / tui / commands.py: 100%
76 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-04-29 19:16 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-04-29 19:16 +0000
1"""Command palette provider for lilbee TUI."""
3from __future__ import annotations
5import logging
6from typing import TYPE_CHECKING, Any
8from textual.command import Hit, Hits, Provider
10from lilbee import settings
11from lilbee.config import cfg
12from lilbee.services import get_services
14log = logging.getLogger(__name__)
16if TYPE_CHECKING:
17 from lilbee.cli.tui.app import LilbeeApp
20class LilbeeCommandProvider(Provider):
21 """Provides searchable commands for the Textual command palette (Ctrl+P)."""
23 @property
24 def _app(self) -> LilbeeApp:
25 from lilbee.cli.tui.app import LilbeeApp
27 if not isinstance(self.screen.app, LilbeeApp): # test apps aren't LilbeeApp
28 raise TypeError(f"Expected LilbeeApp, got {type(self.screen.app).__name__}")
29 return self.screen.app
31 async def search(self, query: str) -> Hits:
32 matcher = self.matcher(query)
33 for cmd_text, help_text, action in self._get_commands():
34 score = matcher.match(cmd_text)
35 if score > 0:
36 yield Hit(score, matcher.highlight(cmd_text), action, help=help_text)
38 async def discover(self) -> Hits:
39 for cmd_text, help_text, action in self._get_commands():
40 yield Hit(1.0, cmd_text, action, help=help_text)
42 def _get_commands(self) -> list[tuple[str, str, Any]]:
43 app = self._app
44 commands: list[tuple[str, str, Any]] = [
45 ("Open catalog", "Browse and install models", lambda: app.switch_view("Catalog")),
46 ("Run setup wizard", "Configure chat and embedding models", self._action_setup),
47 ("Open status", "Knowledge base status", lambda: app.switch_view("Status")),
48 ("Open settings", "View and change settings", lambda: app.switch_view("Settings")),
49 ("Open task center", "Monitor background tasks", lambda: app.switch_view("Tasks")),
50 ("Help", "Show keybinding reference", app.action_push_help),
51 ("Cycle theme", "Switch to next color theme", app.action_cycle_theme),
52 ("Sync documents", "Sync knowledge base", self._action_sync),
53 ("Open wiki", "Browse and generate wiki pages", self._action_open_wiki),
54 ("Show version", "Display lilbee version", self._action_version),
55 (
56 "Reset knowledge base",
57 "Delete all data (requires /reset confirm)",
58 self._action_noop,
59 ),
60 ("Quit", "Exit lilbee", app.action_quit),
61 ]
63 commands.extend(self._model_commands())
64 commands.extend(self._document_commands())
65 return commands
67 def _model_commands(self) -> list[tuple[str, str, Any]]:
68 """Generate commands for installed models."""
69 commands: list[tuple[str, str, Any]] = []
70 try:
71 from lilbee.models import list_installed_models
73 for name in list_installed_models():
74 commands.append(
75 (
76 f"Set chat model → {name}",
77 "Switch chat model",
78 lambda n=name: self._set_model("chat_model", n),
79 )
80 )
81 except Exception:
82 log.debug("Failed to list installed models", exc_info=True)
84 return commands
86 def _document_commands(self) -> list[tuple[str, str, Any]]:
87 """Generate commands for indexed documents."""
88 commands: list[tuple[str, str, Any]] = []
89 try:
90 for src in get_services().store.get_sources():
91 name = src.get("filename", src.get("source", ""))
92 if name:
93 commands.append(
94 (
95 f"Delete document → {name}",
96 f"Remove {name} from index",
97 lambda n=name: self._delete_doc(n),
98 )
99 )
100 except Exception:
101 log.debug("Failed to list documents", exc_info=True)
102 return commands
104 def _set_model(self, attr: str, value: str) -> None:
105 setattr(cfg, attr, value)
106 settings.set_value(cfg.data_root, attr, value)
107 display = value or "off"
108 self.screen.app.notify(f"{attr}: {display}")
109 if attr == "chat_model":
110 self.screen.app.title = f"lilbee — {value}"
112 def _delete_doc(self, name: str) -> None:
113 store = get_services().store
114 store.delete_by_source(name)
115 store.delete_source(name)
116 self.screen.app.notify(f"Deleted {name}")
118 def _action_sync(self) -> None:
119 self.screen.app.notify("Use /add <path> or auto-sync on launch")
121 def _action_version(self) -> None:
122 from lilbee.cli.helpers import get_version
124 self.screen.app.notify(f"lilbee {get_version()}")
126 def _action_setup(self) -> None:
127 from lilbee.cli.tui.screens.setup import SetupWizard
129 self.screen.app.push_screen(SetupWizard())
131 def _action_open_wiki(self) -> None:
132 from lilbee.cli.tui.app import LilbeeApp
134 app = self.screen.app
135 if isinstance(app, LilbeeApp): # test apps aren't LilbeeApp
136 app.switch_view("Wiki")
138 def _action_noop(self) -> None:
139 self.screen.app.notify("Type '/reset confirm' in chat to reset")