Add enhanced search and filtering (M2 feature)

- Add /api/v1/tags endpoint for listing available tags
- Enhance search API with tag filtering (AND logic), multi-category
  filtering (OR logic), owner filter, download range, and date range
- Add faceted response support (category/tag/owner counts)
- Update registry client with new search parameters and get_tags method
- Add CLI search options: -t/--tag, -o/--owner, --min-downloads,
  --popular, --new, --since, --before, --json, --show-facets
- Add new 'registry tags' CLI subcommand
- Add web UI filter sidebar with checkboxes, dropdowns, and active
  filter chips with URL-based state management
- Display clickable tags on tool cards

Co-Authored-By: Claude Opus 4.5 <[email protected]>
This commit is contained in:
rob
2026-01-13 06:13:37 -04:00
co-authored by Claude Opus 4.5
parent 614b145f5f
commit 14448408af
8 changed files with 762 additions and 101 deletions
+18
View File
@@ -135,9 +135,27 @@ def main():
p_reg_search = registry_sub.add_parser("search", help="Search for tools")
p_reg_search.add_argument("query", help="Search query")
p_reg_search.add_argument("-c", "--category", help="Filter by category")
p_reg_search.add_argument("-t", "--tag", action="append", dest="tags", help="Filter by tag (repeatable, AND logic)")
p_reg_search.add_argument("-o", "--owner", help="Filter by publisher/owner")
p_reg_search.add_argument("--min-downloads", type=int, help="Minimum downloads")
p_reg_search.add_argument("--popular", action="store_true", help="Shortcut for --min-downloads 100")
p_reg_search.add_argument("--new", action="store_true", help="Shortcut for --max-downloads 10")
p_reg_search.add_argument("--since", help="Published after date (YYYY-MM-DD)")
p_reg_search.add_argument("--before", help="Published before date (YYYY-MM-DD)")
p_reg_search.add_argument("-s", "--sort", choices=["relevance", "downloads", "published_at", "name"], default="relevance", help="Sort by field")
p_reg_search.add_argument("-l", "--limit", type=int, help="Max results (default: 20)")
p_reg_search.add_argument("--json", action="store_true", help="Output as JSON")
p_reg_search.add_argument("--show-facets", action="store_true", help="Show category/tag counts")
p_reg_search.add_argument("--deprecated", action="store_true", help="Include deprecated tools")
p_reg_search.set_defaults(func=cmd_registry)
# registry tags
p_reg_tags = registry_sub.add_parser("tags", help="List available tags")
p_reg_tags.add_argument("-c", "--category", help="Filter tags by category")
p_reg_tags.add_argument("-l", "--limit", type=int, default=50, help="Max tags to show (default: 50)")
p_reg_tags.add_argument("--json", action="store_true", help="Output as JSON")
p_reg_tags.set_defaults(func=cmd_registry)
# registry install
p_reg_install = registry_sub.add_parser("install", help="Install a tool from registry")
p_reg_install.add_argument("tool", help="Tool to install (owner/name or name)")
+91 -4
View File
@@ -1,5 +1,6 @@
"""Registry commands."""
import json
import sys
from pathlib import Path
@@ -15,6 +16,8 @@ def cmd_registry(args):
if args.registry_cmd == "search":
return _cmd_registry_search(args)
elif args.registry_cmd == "tags":
return _cmd_registry_tags(args)
elif args.registry_cmd == "install":
return _cmd_registry_install(args)
elif args.registry_cmd == "uninstall":
@@ -33,6 +36,7 @@ def cmd_registry(args):
# Default: show registry help
print("Registry commands:")
print(" search <query> Search for tools")
print(" tags List available tags")
print(" install <tool> Install a tool")
print(" uninstall <tool> Uninstall a tool")
print(" info <tool> Show tool information")
@@ -49,27 +53,73 @@ def _cmd_registry_search(args):
try:
client = get_client()
# Handle shortcut flags
min_downloads = getattr(args, 'min_downloads', None)
max_downloads = None
if getattr(args, 'popular', False):
min_downloads = 100
if getattr(args, 'new', False):
max_downloads = 10
results = client.search_tools(
query=args.query,
category=args.category,
per_page=args.limit or 20
category=getattr(args, 'category', None),
tags=getattr(args, 'tags', None),
owner=getattr(args, 'owner', None),
min_downloads=min_downloads,
max_downloads=max_downloads,
published_after=getattr(args, 'since', None),
published_before=getattr(args, 'before', None),
include_deprecated=getattr(args, 'deprecated', False),
include_facets=getattr(args, 'show_facets', False),
per_page=args.limit or 20,
sort=getattr(args, 'sort', 'relevance')
)
# JSON output
if getattr(args, 'json', False):
output = {
"query": args.query,
"total": results.total,
"results": results.data
}
if results.facets:
output["facets"] = results.facets
print(json.dumps(output, indent=2))
return 0
if not results.data:
print(f"No tools found matching '{args.query}'")
return 0
print(f"Found {results.total} tools:\n")
print(f"Found {results.total} tools matching \"{args.query}\":")
# Show facets summary if requested
if results.facets:
cats = results.facets.get("categories", [])[:5]
tags = results.facets.get("tags", [])[:5]
if cats:
cat_str = ", ".join(f"{c['name']} ({c['count']})" for c in cats)
print(f"\nCategories: {cat_str}")
if tags:
tag_str = ", ".join(f"{t['name']} ({t['count']})" for t in tags)
print(f"Top Tags: {tag_str}")
print()
for tool in results.data:
owner = tool.get("owner", "")
name = tool.get("name", "")
version = tool.get("version", "")
desc = tool.get("description", "")
downloads = tool.get("downloads", 0)
tags = tool.get("tags", [])
print(f" {owner}/{name} v{version}")
print(f" {desc[:60]}{'...' if len(desc) > 60 else ''}")
print(f" Downloads: {downloads}")
if tags:
print(f" Tags: {', '.join(tags[:5])}")
print(f" Downloads: {downloads:,}")
print()
if results.total_pages > 1:
@@ -92,6 +142,43 @@ def _cmd_registry_search(args):
return 0
def _cmd_registry_tags(args):
"""List available tags."""
from ..registry_client import RegistryError, get_client
try:
client = get_client()
tags = client.get_tags(
category=getattr(args, 'category', None),
limit=getattr(args, 'limit', 50)
)
# JSON output
if getattr(args, 'json', False):
print(json.dumps({"tags": tags}, indent=2))
return 0
if not tags:
print("No tags found")
return 0
print(f"Available tags ({len(tags)}):\n")
for tag in tags:
print(f" {tag['name']:20} ({tag['count']} tools)")
except RegistryError as e:
if e.code == "CONNECTION_ERROR":
print("Could not connect to the registry.", file=sys.stderr)
else:
print(f"Error: {e.message}", file=sys.stderr)
return 1
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
return 1
return 0
def _cmd_registry_install(args):
"""Install a tool from the registry."""
from ..registry_client import RegistryError