mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2026-10-30 09:22:03 +00:00
135 lines
4.2 KiB
Python
135 lines
4.2 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from typing import Optional
|
|
|
|
import typer
|
|
import click
|
|
|
|
from .common import _get_pm
|
|
from seedpass.core.api import SemanticIndexService
|
|
from seedpass.core.entry_types import ALL_ENTRY_TYPES
|
|
|
|
app = typer.Typer(help="Manage local semantic vector index and search")
|
|
SEARCH_MODES = ["keyword", "hybrid", "semantic"]
|
|
|
|
|
|
def _get_semantic_service(ctx: typer.Context) -> SemanticIndexService:
|
|
return SemanticIndexService(_get_pm(ctx))
|
|
|
|
|
|
@app.command("status")
|
|
def semantic_status(ctx: typer.Context) -> None:
|
|
"""Show semantic index status for the active profile."""
|
|
service = _get_semantic_service(ctx)
|
|
payload = service.status()
|
|
typer.echo(json.dumps(payload, indent=2, sort_keys=True))
|
|
|
|
|
|
@app.command("enable")
|
|
def semantic_enable(ctx: typer.Context) -> None:
|
|
"""Enable semantic index for this profile."""
|
|
service = _get_semantic_service(ctx)
|
|
payload = service.set_enabled(True)
|
|
typer.echo(
|
|
f"semantic index enabled (built={payload.get('built', False)}, records={payload.get('records', 0)})"
|
|
)
|
|
|
|
|
|
@app.command("disable")
|
|
def semantic_disable(ctx: typer.Context) -> None:
|
|
"""Disable semantic index for this profile."""
|
|
service = _get_semantic_service(ctx)
|
|
payload = service.set_enabled(False)
|
|
typer.echo(
|
|
f"semantic index disabled (built={payload.get('built', False)}, records={payload.get('records', 0)})"
|
|
)
|
|
|
|
|
|
@app.command("build")
|
|
def semantic_build(ctx: typer.Context) -> None:
|
|
"""Build semantic index incrementally from current entries."""
|
|
service = _get_semantic_service(ctx)
|
|
payload = service.build()
|
|
typer.echo(
|
|
f"semantic index built (records={payload.get('records', 0)}, enabled={payload.get('enabled', False)})"
|
|
)
|
|
|
|
|
|
@app.command("rebuild")
|
|
def semantic_rebuild(ctx: typer.Context) -> None:
|
|
"""Rebuild semantic index from scratch."""
|
|
service = _get_semantic_service(ctx)
|
|
payload = service.rebuild()
|
|
typer.echo(
|
|
f"semantic index rebuilt (records={payload.get('records', 0)}, enabled={payload.get('enabled', False)})"
|
|
)
|
|
|
|
|
|
@app.command("search")
|
|
def semantic_search(
|
|
ctx: typer.Context,
|
|
query: str,
|
|
k: int = typer.Option(10, "--k", help="Max results"),
|
|
kind: Optional[str] = typer.Option(
|
|
None,
|
|
"--kind",
|
|
help="Restrict to entry kind",
|
|
click_type=click.Choice(ALL_ENTRY_TYPES),
|
|
),
|
|
mode: Optional[str] = typer.Option(
|
|
None,
|
|
"--mode",
|
|
help="Search mode override",
|
|
click_type=click.Choice(SEARCH_MODES),
|
|
),
|
|
as_json: bool = typer.Option(False, "--json", help="Emit JSON output"),
|
|
) -> None:
|
|
"""Run semantic search over locally indexed KB content."""
|
|
service = _get_semantic_service(ctx)
|
|
results = service.search(query, k=max(1, int(k)), kind=kind, mode=mode)
|
|
if as_json:
|
|
typer.echo(json.dumps(results, indent=2, sort_keys=True))
|
|
return
|
|
if not results:
|
|
typer.echo("No semantic matches")
|
|
return
|
|
for item in results:
|
|
entry_id = int(item.get("entry_id", 0))
|
|
label = str(item.get("label", ""))
|
|
entry_kind = str(item.get("kind", ""))
|
|
score = float(item.get("score", 0.0))
|
|
excerpt = str(item.get("excerpt", ""))
|
|
typer.echo(f"{entry_id}: [{entry_kind}] {label} (score={score:.3f})")
|
|
if excerpt:
|
|
typer.echo(f" {excerpt}")
|
|
|
|
|
|
@app.command("config")
|
|
def semantic_config(
|
|
ctx: typer.Context,
|
|
enabled: Optional[str] = typer.Option(
|
|
None,
|
|
"--enabled",
|
|
help="Enable or disable semantic index for this profile",
|
|
click_type=click.Choice(["true", "false"]),
|
|
),
|
|
mode: Optional[str] = typer.Option(
|
|
None,
|
|
"--mode",
|
|
help="Default semantic search mode",
|
|
click_type=click.Choice(SEARCH_MODES),
|
|
),
|
|
) -> None:
|
|
"""Configure semantic index behavior for the active profile."""
|
|
if enabled is None and mode is None:
|
|
typer.echo("Provide at least one option: --enabled or --mode")
|
|
raise typer.Exit(code=1)
|
|
service = _get_semantic_service(ctx)
|
|
if enabled is not None:
|
|
service.set_enabled(enabled == "true")
|
|
if mode is not None:
|
|
service.set_mode(mode)
|
|
payload = service.status()
|
|
typer.echo(json.dumps(payload, indent=2, sort_keys=True))
|