* feat: Enhance CLI with improved list and smart remove commands ## ✨ New Features ### 🏠 Enhanced `leann list` command - **Better UX**: Current project shown first with clear separation - **Visual improvements**: Icons (🏠/📂), better formatting, size info - **Smart guidance**: Context-aware usage examples and getting started tips ### 🛡️ Smart `leann remove` command - **Safety first**: Always shows ALL matching indexes across projects - **Intelligent handling**: - Single match: Clear location display with cross-project warnings - Multiple matches: Interactive selection with final confirmation - **Prevents accidents**: No more deleting wrong indexes due to name conflicts - **User-friendly**: 'c' to cancel, clear visual hierarchy, detailed info ### 🔧 Technical improvements - **Clean logging**: Hide debug messages for better CLI experience - **Comprehensive search**: Always scan all projects for transparency - **Error handling**: Graceful handling of edge cases and user input ## 🎯 Impact - **Safer**: Eliminates risk of accidental index deletion - **Clearer**: Users always know what they're operating on - **Smarter**: Automatic detection and handling of common scenarios 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * chore: vscode ruff, and format --------- Co-authored-by: Claude <noreply@anthropic.com>
99 lines
3.2 KiB
Python
99 lines
3.2 KiB
Python
# packages/leann-core/src/leann/registry.py
|
|
|
|
import importlib
|
|
import importlib.metadata
|
|
import json
|
|
import logging
|
|
from pathlib import Path
|
|
from typing import TYPE_CHECKING, Optional, Union
|
|
|
|
if TYPE_CHECKING:
|
|
from leann.interface import LeannBackendFactoryInterface
|
|
|
|
# Set up logger for this module
|
|
logger = logging.getLogger(__name__)
|
|
|
|
BACKEND_REGISTRY: dict[str, "LeannBackendFactoryInterface"] = {}
|
|
|
|
|
|
def register_backend(name: str):
|
|
"""A decorator to register a new backend class."""
|
|
|
|
def decorator(cls):
|
|
logger.debug(f"Registering backend '{name}'")
|
|
BACKEND_REGISTRY[name] = cls
|
|
return cls
|
|
|
|
return decorator
|
|
|
|
|
|
def autodiscover_backends():
|
|
"""Automatically discovers and imports all 'leann-backend-*' packages."""
|
|
# print("INFO: Starting backend auto-discovery...")
|
|
discovered_backends = []
|
|
for dist in importlib.metadata.distributions():
|
|
dist_name = dist.metadata["name"]
|
|
if dist_name.startswith("leann-backend-"):
|
|
backend_module_name = dist_name.replace("-", "_")
|
|
discovered_backends.append(backend_module_name)
|
|
|
|
for backend_module_name in sorted(discovered_backends): # sort for deterministic loading
|
|
try:
|
|
importlib.import_module(backend_module_name)
|
|
# Registration message is printed by the decorator
|
|
except ImportError:
|
|
# print(f"WARN: Could not import backend module '{backend_module_name}': {e}")
|
|
pass
|
|
# print("INFO: Backend auto-discovery finished.")
|
|
|
|
|
|
def register_project_directory(project_dir: Optional[Union[str, Path]] = None):
|
|
"""
|
|
Register a project directory in the global LEANN registry.
|
|
|
|
This allows `leann list` to discover indexes created by apps or other tools.
|
|
|
|
Args:
|
|
project_dir: Directory to register. If None, uses current working directory.
|
|
"""
|
|
if project_dir is None:
|
|
project_dir = Path.cwd()
|
|
else:
|
|
project_dir = Path(project_dir)
|
|
|
|
# Only register directories that have some kind of LEANN content
|
|
# Either .leann/indexes/ (CLI format) or *.leann.meta.json files (apps format)
|
|
has_cli_indexes = (project_dir / ".leann" / "indexes").exists()
|
|
has_app_indexes = any(project_dir.rglob("*.leann.meta.json"))
|
|
|
|
if not (has_cli_indexes or has_app_indexes):
|
|
# Don't register if there are no LEANN indexes
|
|
return
|
|
|
|
global_registry = Path.home() / ".leann" / "projects.json"
|
|
global_registry.parent.mkdir(exist_ok=True)
|
|
|
|
project_str = str(project_dir.resolve())
|
|
|
|
# Load existing registry
|
|
projects = []
|
|
if global_registry.exists():
|
|
try:
|
|
with open(global_registry) as f:
|
|
projects = json.load(f)
|
|
except Exception:
|
|
logger.debug("Could not load existing project registry")
|
|
projects = []
|
|
|
|
# Add project if not already present
|
|
if project_str not in projects:
|
|
projects.append(project_str)
|
|
|
|
# Save updated registry
|
|
try:
|
|
with open(global_registry, "w") as f:
|
|
json.dump(projects, f, indent=2)
|
|
logger.debug(f"Registered project directory: {project_str}")
|
|
except Exception as e:
|
|
logger.warning(f"Could not save project registry: {e}")
|