From 7ea3b89db2f1de98e1a202f76b3b5a0a734d25b1 Mon Sep 17 00:00:00 2001 From: HeaTTap Date: Wed, 9 Sep 2026 00:02:57 +0000 Subject: [PATCH] feat: add list-notes CLI flag add list-notes flag to print indexed note titles and filenames without requiring a question --- README.md | 1 + src/notes_rag/cli.py | 23 +++++++++++++++++++++-- tests/test_notes_rag.py | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ab1df7c..dc32e44 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ pip install -e ".[dev]" pytest python -m notes_rag --mock "What is a git remote?" python -m notes_rag --mock --json "What is a git remote?" # machine-readable answer + sources +python -m notes_rag --list-notes # print indexed note titles + files ``` **Expected stdout** (deterministic on the bundled `notes/` corpus + `--mock` — yours should match): diff --git a/src/notes_rag/cli.py b/src/notes_rag/cli.py index 99610fb..4720960 100644 --- a/src/notes_rag/cli.py +++ b/src/notes_rag/cli.py @@ -19,10 +19,11 @@ def build_parser() -> argparse.ArgumentParser: "Examples:\n" " python -m notes_rag --mock \"What is a git remote?\"\n" " python -m notes_rag --notes ./notes --mock \"What is RAG?\"\n" + " python -m notes_rag --list-notes\n" ), formatter_class=argparse.RawDescriptionHelpFormatter, ) - p.add_argument("question", help="Your question") + p.add_argument("question", nargs="?", default=None, help="Your question") p.add_argument( "--notes", type=Path, @@ -40,15 +41,33 @@ def build_parser() -> argparse.ArgumentParser: action="store_true", help="Print a single JSON object with answer and sources", ) + p.add_argument( + "--list-notes", + action="store_true", + help="Print indexed note titles and filenames, then exit (prefers list mode if question is also passed)", + ) return p def main(argv: list[str] | None = None) -> int: - args = build_parser().parse_args(argv) + parser = build_parser() + args = parser.parse_args(argv) notes_dir = args.notes if not notes_dir.is_dir(): print(f"error: notes folder not found: {notes_dir}", file=sys.stderr) return 1 + + if args.list_notes: + notes = load_notes(notes_dir) + for note in notes: + print(f"{note.title} ({note.path.name})") + return 0 + + if not args.question: + parser.print_usage(file=sys.stderr) + print("notes-rag: error: the following arguments are required: question", file=sys.stderr) + return 2 + notes = load_notes(notes_dir) hits = retrieve(notes, args.question, k=args.k) answerer = Answerer.from_env(mock=args.mock) diff --git a/tests/test_notes_rag.py b/tests/test_notes_rag.py index 37f9872..0d379a2 100644 --- a/tests/test_notes_rag.py +++ b/tests/test_notes_rag.py @@ -75,3 +75,35 @@ def test_missing_key_without_mock(): assert False, "expected RuntimeError" except RuntimeError as exc: assert "OPENAI_API_KEY" in str(exc) + + +def test_list_notes(capsys): + code = main(["--list-notes", "--notes", str(NOTES)]) + out = capsys.readouterr().out + assert code == 0 + assert "Git remotes (git-remotes.md)" in out + assert "What RAG is (rag-idea.md)" in out + + +def test_list_notes_prefers_list_over_question(capsys): + code = main(["--list-notes", "--notes", str(NOTES), "What is a git remote?"]) + out = capsys.readouterr().out + assert code == 0 + assert "Git remotes (git-remotes.md)" in out + assert "[mock]" not in out + + +def test_missing_question_without_list_notes(capsys): + code = main(["--notes", str(NOTES)]) + err = capsys.readouterr().err + assert code == 2 + assert "the following arguments are required: question" in err + + +def test_list_notes_missing_dir(tmp_path, capsys): + missing = tmp_path / "nonexistent" + code = main(["--list-notes", "--notes", str(missing)]) + err = capsys.readouterr().err + assert code == 1 + assert "notes folder not found" in err +