diff --git a/src/cmdforge/registry/app.py b/src/cmdforge/registry/app.py index ab2ff5e..ed587e7 100644 --- a/src/cmdforge/registry/app.py +++ b/src/cmdforge/registry/app.py @@ -479,6 +479,15 @@ def create_app() -> Flask: query_text = request.args.get("q", "").strip() if not query_text: return error_response("VALIDATION_ERROR", "Missing search query") + + # Sanitize query for FTS5 - escape special characters that cause syntax errors + # FTS5 special chars: * " ( ) : ^ - NOT AND OR NEAR + # For safety, we'll quote the entire query if it contains special chars + fts5_special = set('*"():^-') + if any(c in fts5_special for c in query_text) or query_text.upper() in ('NOT', 'AND', 'OR', 'NEAR'): + # Escape double quotes and wrap in quotes for literal search + query_text = '"' + query_text.replace('"', '""') + '"' + page, per_page, sort, order, error = parse_pagination("/tools/search", "downloads") if error: return error diff --git a/src/cmdforge/ui_urwid/__init__.py b/src/cmdforge/ui_urwid/__init__.py index e9e4990..610d2cb 100644 --- a/src/cmdforge/ui_urwid/__init__.py +++ b/src/cmdforge/ui_urwid/__init__.py @@ -1465,12 +1465,19 @@ No explanations, no markdown fencing, just the code.""" def do_search(_=None): query = search_edit.base_widget.edit_text.strip() self._registry_search_query = query - status_text.set_text(('label', f"Searching for '{query}'...")) + if query: + status_text.set_text(('label', f"Searching for '{query}'...")) + else: + status_text.set_text(('label', "Loading all tools...")) self.refresh() try: client = RegistryClient() - result = client.search_tools(query=query if query else "*", per_page=50) + # Use list_tools for browsing, search_tools only when there's a query + if query: + result = client.search_tools(query=query, per_page=50) + else: + result = client.list_tools(per_page=50) self._registry_tools = result.data # Update the list