diff --git a/docs/cli.md b/docs/cli.md index bd436c94..4afc96aa 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -40,8 +40,9 @@ haiku-rag add-src https://example.com/article.html # Optionally set a human‑readable title stored in the DB schema haiku-rag add-src /mnt/data/doc1.pdf --title "Q3 Financial Report" -# Optionally attach metadata (repeat --meta) -haiku-rag add-src /mnt/data/doc1.pdf --meta source=manual --meta lang=en +# Optionally attach metadata (repeat --meta). Values use JSON parsing if possible: +# numbers, booleans, null, arrays/objects; otherwise kept as strings. +haiku-rag add-src /mnt/data/doc1.pdf --meta source=manual --meta page_count=12 --meta published=true ``` !!! note diff --git a/src/haiku/rag/cli.py b/src/haiku/rag/cli.py index f2cae05d..ca8349fe 100644 --- a/src/haiku/rag/cli.py +++ b/src/haiku/rag/cli.py @@ -1,7 +1,9 @@ import asyncio +import json import warnings from importlib.metadata import version from pathlib import Path +from typing import Any import typer @@ -137,12 +139,12 @@ def list_documents( asyncio.run(app.list_documents()) -def _parse_meta_options(meta: list[str] | None) -> dict[str, str]: +def _parse_meta_options(meta: list[str] | None) -> dict[str, Any]: """Parse repeated --meta KEY=VALUE options into a dictionary. Raises a Typer error if any entry is malformed. """ - result: dict[str, str] = {} + result: dict[str, Any] = {} if not meta: return result for item in meta: @@ -151,7 +153,13 @@ def _parse_meta_options(meta: list[str] | None) -> dict[str, str]: key, value = item.split("=", 1) if not key: raise typer.BadParameter("--meta key cannot be empty") - result[key] = value + # Best-effort JSON coercion: numbers, booleans, null, arrays/objects + try: + parsed = json.loads(value) + result[key] = parsed + except Exception: + # Leave as string if not valid JSON literal + result[key] = value return result diff --git a/tests/test_cli.py b/tests/test_cli.py index 5e719066..19ea95a1 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -112,6 +112,31 @@ def test_add_document_src_with_meta(): assert kwargs.get("metadata") == {"source": "manual", "lang": "en"} +def test_add_document_text_with_numeric_meta(): + with patch("haiku.rag.app.HaikuRAGApp") as mock_app: + mock_app_instance = MagicMock() + mock_app_instance.add_document_from_text = AsyncMock() + mock_app.return_value = mock_app_instance + + result = runner.invoke( + cli, + [ + "add", + "some text", + "--meta", + "version=3", + "--meta", + "published=true", + ], + ) + + assert result.exit_code == 0 + mock_app_instance.add_document_from_text.assert_called_once() + _, kwargs = mock_app_instance.add_document_from_text.call_args + assert kwargs.get("text") == "some text" + assert kwargs.get("metadata") == {"version": 3, "published": True} + + def test_get_document(): with patch("haiku.rag.app.HaikuRAGApp") as mock_app: mock_app_instance = MagicMock()