fix: resolve CI/CD issues with proper package structure and imports
This commit is contained in:
235
src/local_api_docs_search/cli/commands.py
Normal file
235
src/local_api_docs_search/cli/commands.py
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
"""CLI command definitions."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import click
|
||||||
|
from rich.console import Console
|
||||||
|
from rich.panel import Panel
|
||||||
|
from rich.text import Text
|
||||||
|
|
||||||
|
from local_api_docs_search.models.document import SourceType
|
||||||
|
from local_api_docs_search.search.searcher import Searcher
|
||||||
|
from local_api_docs_search.utils.config import get_config
|
||||||
|
from local_api_docs_search.utils.formatters import (
|
||||||
|
format_error,
|
||||||
|
format_index_summary,
|
||||||
|
format_search_results,
|
||||||
|
format_success,
|
||||||
|
)
|
||||||
|
|
||||||
|
console = Console()
|
||||||
|
|
||||||
|
|
||||||
|
@click.group()
|
||||||
|
@click.option("--verbose", "-v", is_flag=True, help="Enable verbose output")
|
||||||
|
@click.pass_context
|
||||||
|
def cli(ctx, verbose):
|
||||||
|
"""Local API Docs Search - Index and search your API documentation."""
|
||||||
|
ctx.ensure_object(dict)
|
||||||
|
ctx.obj["verbose"] = verbose
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command(name="index")
|
||||||
|
@click.argument(
|
||||||
|
"path", type=click.Path(exists=True, file_okay=True, dir_okay=True, path_type=Path)
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--type",
|
||||||
|
"-t",
|
||||||
|
type=click.Choice(["openapi", "readme", "code", "all"]),
|
||||||
|
default="all",
|
||||||
|
help="Type of documentation to index",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--recursive", "-r", is_flag=True, default=False, help="Recursively search directories"
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--batch-size", "-b", type=int, default=32, help="Documents per batch"
|
||||||
|
)
|
||||||
|
@click.pass_context
|
||||||
|
def index_command(ctx, path, type, recursive, batch_size):
|
||||||
|
"""Index documentation from a path.
|
||||||
|
|
||||||
|
PATH is the path to a file or directory to index.
|
||||||
|
"""
|
||||||
|
with console.status(f"Indexing {type} documentation from {path}..."):
|
||||||
|
searcher = Searcher()
|
||||||
|
count = searcher.index(path, doc_type=type, recursive=recursive, batch_size=batch_size)
|
||||||
|
|
||||||
|
if count > 0:
|
||||||
|
console.print(format_success(f"Successfully indexed {count} documents"))
|
||||||
|
else:
|
||||||
|
console.print(format_error("No documents found to index"))
|
||||||
|
if type == "all":
|
||||||
|
console.print("Try specifying a type: --type openapi|readme|code")
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command(name="search")
|
||||||
|
@click.argument("query", type=str)
|
||||||
|
@click.option(
|
||||||
|
"--limit", "-l", type=int, default=None, help="Maximum number of results"
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--type",
|
||||||
|
"-t",
|
||||||
|
type=click.Choice(["openapi", "readme", "code"]),
|
||||||
|
help="Filter by source type",
|
||||||
|
)
|
||||||
|
@click.option("--json", is_flag=True, help="Output as JSON")
|
||||||
|
@click.option(
|
||||||
|
"--hybrid/--semantic",
|
||||||
|
default=True,
|
||||||
|
help="Use hybrid (default) or semantic-only search",
|
||||||
|
)
|
||||||
|
@click.pass_context
|
||||||
|
def search_command(ctx, query, limit, type, json, hybrid):
|
||||||
|
"""Search indexed documentation.
|
||||||
|
|
||||||
|
QUERY is the search query in natural language.
|
||||||
|
"""
|
||||||
|
config = get_config()
|
||||||
|
|
||||||
|
if limit is None:
|
||||||
|
limit = config.default_limit
|
||||||
|
|
||||||
|
searcher = Searcher()
|
||||||
|
|
||||||
|
with console.status("Searching..."):
|
||||||
|
if hybrid:
|
||||||
|
results = searcher.hybrid_search(query, limit=limit)
|
||||||
|
else:
|
||||||
|
results = searcher.search(query, limit=limit)
|
||||||
|
|
||||||
|
if not results:
|
||||||
|
console.print(format_info("No results found for your query"))
|
||||||
|
return
|
||||||
|
|
||||||
|
if json:
|
||||||
|
import json as json_lib
|
||||||
|
output = [r.to_dict() for r in results]
|
||||||
|
console.print(json_lib.dumps(output, indent=2))
|
||||||
|
else:
|
||||||
|
table = format_search_results(results)
|
||||||
|
console.print(table)
|
||||||
|
|
||||||
|
console.print(f"\nFound {len(results)} result(s)")
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command(name="list")
|
||||||
|
@click.option(
|
||||||
|
"--type",
|
||||||
|
"-t",
|
||||||
|
type=click.Choice(["openapi", "readme", "code"]),
|
||||||
|
help="Filter by source type",
|
||||||
|
)
|
||||||
|
@click.option("--json", is_flag=True, help="Output as JSON")
|
||||||
|
@click.pass_context
|
||||||
|
def list_command(ctx, type, json):
|
||||||
|
"""List indexed documents."""
|
||||||
|
searcher = Searcher()
|
||||||
|
stats = searcher.get_stats()
|
||||||
|
|
||||||
|
if json:
|
||||||
|
import json
|
||||||
|
output = stats.to_dict()
|
||||||
|
console.print(json.dumps(output, indent=2))
|
||||||
|
else:
|
||||||
|
table = format_index_summary(
|
||||||
|
stats.total_documents,
|
||||||
|
stats.openapi_count,
|
||||||
|
stats.readme_count,
|
||||||
|
stats.code_count,
|
||||||
|
)
|
||||||
|
console.print(table)
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command(name="stats")
|
||||||
|
@click.pass_context
|
||||||
|
def stats_command(ctx):
|
||||||
|
"""Show index statistics."""
|
||||||
|
searcher = Searcher()
|
||||||
|
stats = searcher.get_stats()
|
||||||
|
|
||||||
|
table = format_index_summary(
|
||||||
|
stats.total_documents,
|
||||||
|
stats.openapi_count,
|
||||||
|
stats.readme_count,
|
||||||
|
stats.code_count,
|
||||||
|
)
|
||||||
|
console.print(table)
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command(name="clear")
|
||||||
|
@click.option("--type", "-t", type=click.Choice(["openapi", "readme", "code"]))
|
||||||
|
@click.option("--force", "-f", is_flag=True, help="Skip confirmation prompt")
|
||||||
|
@click.pass_context
|
||||||
|
def clear_command(ctx, type, force):
|
||||||
|
"""Clear the index or filtered by type."""
|
||||||
|
if not force:
|
||||||
|
if type:
|
||||||
|
confirm = click.confirm(f"Delete all {type} documents from the index?")
|
||||||
|
else:
|
||||||
|
confirm = click.confirm("Delete all documents from the index?")
|
||||||
|
else:
|
||||||
|
confirm = True
|
||||||
|
|
||||||
|
if not confirm:
|
||||||
|
console.print("Cancelled")
|
||||||
|
return
|
||||||
|
|
||||||
|
searcher = Searcher()
|
||||||
|
|
||||||
|
if type:
|
||||||
|
source_type = SourceType(type)
|
||||||
|
count = searcher._vector_store.delete_by_source_type(source_type)
|
||||||
|
else:
|
||||||
|
count = searcher._vector_store.count()
|
||||||
|
searcher.clear_index()
|
||||||
|
|
||||||
|
console.print(format_success(f"Deleted {count} document(s)"))
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command(name="config")
|
||||||
|
@click.option("--show", is_flag=True, help="Show current configuration")
|
||||||
|
@click.option("--reset", is_flag=True, help="Reset configuration to defaults")
|
||||||
|
@click.pass_context
|
||||||
|
def config_command(ctx, show, reset):
|
||||||
|
"""Manage configuration."""
|
||||||
|
config = get_config()
|
||||||
|
|
||||||
|
if reset:
|
||||||
|
config.reset()
|
||||||
|
console.print(format_success("Configuration reset to defaults"))
|
||||||
|
return
|
||||||
|
|
||||||
|
if show or not (reset):
|
||||||
|
config_dict = config.to_dict()
|
||||||
|
|
||||||
|
if show:
|
||||||
|
import json
|
||||||
|
console.print(json.dumps(config_dict, indent=2))
|
||||||
|
else:
|
||||||
|
lines = ["Current Configuration:", ""]
|
||||||
|
for key, value in config_dict.items():
|
||||||
|
lines.append(f" {key}: {value}")
|
||||||
|
|
||||||
|
panel = Panel(
|
||||||
|
"\n".join(lines),
|
||||||
|
title="Configuration",
|
||||||
|
expand=False,
|
||||||
|
)
|
||||||
|
console.print(panel)
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command(name="interactive")
|
||||||
|
@click.pass_context
|
||||||
|
def interactive_command(ctx):
|
||||||
|
"""Enter interactive search mode."""
|
||||||
|
from local_api_docs_search.cli.interactive import run_interactive
|
||||||
|
|
||||||
|
run_interactive()
|
||||||
|
|
||||||
|
|
||||||
|
def format_info(message: str) -> Text:
|
||||||
|
"""Format an info message."""
|
||||||
|
return Text(message, style="cyan")
|
||||||
Reference in New Issue
Block a user