Merge pull request #50 from ggozad/feat/ids-autocomplete
Shell autocompletion for ids/paths. Alias delete with rm
This commit is contained in:
commit
8dfb7f0b96
3 changed files with 95 additions and 4 deletions
24
docs/cli.md
24
docs/cli.md
|
|
@ -2,6 +2,23 @@
|
||||||
|
|
||||||
The `haiku-rag` CLI provides complete document management functionality.
|
The `haiku-rag` CLI provides complete document management functionality.
|
||||||
|
|
||||||
|
## Shell Autocompletion
|
||||||
|
|
||||||
|
Enable shell autocompletion for faster, error‑free usage.
|
||||||
|
|
||||||
|
- Temporary (current shell only):
|
||||||
|
```bash
|
||||||
|
eval "$(haiku-rag --show-completion)"
|
||||||
|
```
|
||||||
|
- Permanent installation:
|
||||||
|
```bash
|
||||||
|
haiku-rag --install-completion
|
||||||
|
```
|
||||||
|
|
||||||
|
What’s completed:
|
||||||
|
- `get` and `delete`/`rm`: Document IDs from the selected database (respects `--db`).
|
||||||
|
- `add-src`: Local filesystem paths (URLs can still be typed manually).
|
||||||
|
|
||||||
## Document Management
|
## Document Management
|
||||||
|
|
||||||
### List Documents
|
### List Documents
|
||||||
|
|
@ -26,13 +43,16 @@ haiku-rag add-src https://example.com/article.html
|
||||||
### Get Document
|
### Get Document
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
haiku-rag get 1
|
haiku-rag get <TAB>
|
||||||
|
# or
|
||||||
|
haiku-rag get 3f4a... # document ID (autocomplete supported)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Delete Document
|
### Delete Document
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
haiku-rag delete 1
|
haiku-rag delete <TAB>
|
||||||
|
haiku-rag rm <TAB> # alias
|
||||||
```
|
```
|
||||||
|
|
||||||
### Rebuild Database
|
### Rebuild Database
|
||||||
|
|
|
||||||
|
|
@ -50,8 +50,13 @@ class HaikuRAGApp:
|
||||||
|
|
||||||
async def delete_document(self, doc_id: str):
|
async def delete_document(self, doc_id: str):
|
||||||
async with HaikuRAG(db_path=self.db_path) as self.client:
|
async with HaikuRAG(db_path=self.db_path) as self.client:
|
||||||
await self.client.delete_document(doc_id)
|
deleted = await self.client.delete_document(doc_id)
|
||||||
self.console.print(f"[b]Document {doc_id} deleted successfully.[/b]")
|
if deleted:
|
||||||
|
self.console.print(f"[b]Document {doc_id} deleted successfully.[/b]")
|
||||||
|
else:
|
||||||
|
self.console.print(
|
||||||
|
f"[yellow]Document with id {doc_id} not found.[/yellow]"
|
||||||
|
)
|
||||||
|
|
||||||
async def search(self, query: str, limit: int = 5):
|
async def search(self, query: str, limit: int = 5):
|
||||||
async with HaikuRAG(db_path=self.db_path) as self.client:
|
async with HaikuRAG(db_path=self.db_path) as self.client:
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,65 @@ cli = typer.Typer(
|
||||||
console = Console()
|
console = Console()
|
||||||
|
|
||||||
|
|
||||||
|
def complete_document_ids(ctx: typer.Context, incomplete: str):
|
||||||
|
"""Autocomplete document IDs from the selected DB."""
|
||||||
|
db_path = ctx.params.get("db") or (Config.DEFAULT_DATA_DIR / "haiku.rag.lancedb")
|
||||||
|
|
||||||
|
try:
|
||||||
|
from haiku.rag.client import HaikuRAG
|
||||||
|
|
||||||
|
async def _list_ids():
|
||||||
|
async with HaikuRAG(db_path) as client:
|
||||||
|
docs = await client.list_documents()
|
||||||
|
return [d.id for d in docs if d.id]
|
||||||
|
|
||||||
|
ids = asyncio.run(_list_ids())
|
||||||
|
except Exception:
|
||||||
|
return []
|
||||||
|
|
||||||
|
return [i for i in ids if i and i.startswith(incomplete)]
|
||||||
|
|
||||||
|
|
||||||
|
def complete_local_paths(ctx: typer.Context, incomplete: str) -> list[str]:
|
||||||
|
"""Autocomplete local filesystem paths.
|
||||||
|
|
||||||
|
Provides directory/file suggestions based on the current incomplete input.
|
||||||
|
Does not validate or restrict to specific extensions to keep it flexible
|
||||||
|
(URLs are still allowed to be typed manually).
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
text = incomplete or ""
|
||||||
|
|
||||||
|
# Expand user home
|
||||||
|
from os.path import expanduser
|
||||||
|
|
||||||
|
expanded = expanduser(text)
|
||||||
|
p = Path(expanded)
|
||||||
|
|
||||||
|
# Choose directory to list and prefix to filter
|
||||||
|
if text == "" or text.endswith(("/", "\\")):
|
||||||
|
directory = p
|
||||||
|
prefix = ""
|
||||||
|
else:
|
||||||
|
directory = p.parent
|
||||||
|
prefix = p.name
|
||||||
|
|
||||||
|
if not directory.exists():
|
||||||
|
return []
|
||||||
|
|
||||||
|
suggestions: list[str] = []
|
||||||
|
for entry in directory.iterdir():
|
||||||
|
name = entry.name
|
||||||
|
if not prefix or name.startswith(prefix):
|
||||||
|
suggestion = str(directory / name)
|
||||||
|
if entry.is_dir():
|
||||||
|
suggestion += "/"
|
||||||
|
suggestions.append(suggestion)
|
||||||
|
return suggestions
|
||||||
|
except Exception:
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
async def check_version():
|
async def check_version():
|
||||||
"""Check if haiku.rag is up to date and show warning if not."""
|
"""Check if haiku.rag is up to date and show warning if not."""
|
||||||
up_to_date, current_version, latest_version = await is_up_to_date()
|
up_to_date, current_version, latest_version = await is_up_to_date()
|
||||||
|
|
@ -87,6 +146,7 @@ def add_document_text(
|
||||||
def add_document_src(
|
def add_document_src(
|
||||||
source: str = typer.Argument(
|
source: str = typer.Argument(
|
||||||
help="The file path or URL of the document to add",
|
help="The file path or URL of the document to add",
|
||||||
|
autocompletion=complete_local_paths,
|
||||||
),
|
),
|
||||||
db: Path = typer.Option(
|
db: Path = typer.Option(
|
||||||
Config.DEFAULT_DATA_DIR / "haiku.rag.lancedb",
|
Config.DEFAULT_DATA_DIR / "haiku.rag.lancedb",
|
||||||
|
|
@ -102,6 +162,7 @@ def add_document_src(
|
||||||
def get_document(
|
def get_document(
|
||||||
doc_id: str = typer.Argument(
|
doc_id: str = typer.Argument(
|
||||||
help="The ID of the document to get",
|
help="The ID of the document to get",
|
||||||
|
autocompletion=complete_document_ids,
|
||||||
),
|
),
|
||||||
db: Path = typer.Option(
|
db: Path = typer.Option(
|
||||||
Config.DEFAULT_DATA_DIR / "haiku.rag.lancedb",
|
Config.DEFAULT_DATA_DIR / "haiku.rag.lancedb",
|
||||||
|
|
@ -117,6 +178,7 @@ def get_document(
|
||||||
def delete_document(
|
def delete_document(
|
||||||
doc_id: str = typer.Argument(
|
doc_id: str = typer.Argument(
|
||||||
help="The ID of the document to delete",
|
help="The ID of the document to delete",
|
||||||
|
autocompletion=complete_document_ids,
|
||||||
),
|
),
|
||||||
db: Path = typer.Option(
|
db: Path = typer.Option(
|
||||||
Config.DEFAULT_DATA_DIR / "haiku.rag.lancedb",
|
Config.DEFAULT_DATA_DIR / "haiku.rag.lancedb",
|
||||||
|
|
@ -128,6 +190,10 @@ def delete_document(
|
||||||
asyncio.run(app.delete_document(doc_id=doc_id))
|
asyncio.run(app.delete_document(doc_id=doc_id))
|
||||||
|
|
||||||
|
|
||||||
|
# Add alias `rm` for delete
|
||||||
|
cli.command("rm", help="Alias for delete: remove a document by its ID")(delete_document)
|
||||||
|
|
||||||
|
|
||||||
@cli.command("search", help="Search for documents by a query")
|
@cli.command("search", help="Search for documents by a query")
|
||||||
def search(
|
def search(
|
||||||
query: str = typer.Argument(
|
query: str = typer.Argument(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue