Skip to content

Commit 47e7a1d

Browse files
committed
Add JSON output for ccc search
1 parent 51ea6ef commit 47e7a1d

2 files changed

Lines changed: 80 additions & 1 deletion

File tree

src/cocoindex_code/cli.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from __future__ import annotations
44

55
import functools
6+
import json as _json
67
import os
78
import sys
89
from collections.abc import Callable
@@ -173,6 +174,28 @@ def print_search_results(response: SearchResponse) -> None:
173174
_typer.echo(r.content)
174175

175176

177+
def print_search_results_json(response: SearchResponse) -> None:
178+
"""Print search results as machine-readable JSON."""
179+
payload = {
180+
"success": response.success,
181+
"results": [
182+
{
183+
"file_path": r.file_path,
184+
"language": r.language,
185+
"content": r.content,
186+
"start_line": r.start_line,
187+
"end_line": r.end_line,
188+
"score": r.score,
189+
}
190+
for r in response.results
191+
],
192+
"total_returned": response.total_returned,
193+
"offset": response.offset,
194+
"message": response.message,
195+
}
196+
_typer.echo(_json.dumps(payload, indent=2))
197+
198+
176199
def _run_index_with_progress(project_root: str) -> None:
177200
"""Run indexing with streaming progress display. Exits on failure."""
178201
from rich.console import Console as _Console
@@ -543,6 +566,7 @@ def search(
543566
offset: int = _typer.Option(0, "--offset", help="Number of results to skip"),
544567
limit: int = _typer.Option(10, "--limit", help="Maximum results to return"),
545568
refresh: bool = _typer.Option(False, "--refresh", help="Refresh index before searching"),
569+
json_output: bool = _typer.Option(False, "--json", help="Print results as JSON"),
546570
) -> None:
547571
"""Semantic search across the codebase."""
548572
project_root = str(require_project_root())
@@ -568,7 +592,10 @@ def search(
568592
limit=limit,
569593
offset=offset,
570594
)
571-
print_search_results(resp)
595+
if json_output:
596+
print_search_results_json(resp)
597+
else:
598+
print_search_results(resp)
572599

573600

574601
@app.command()

tests/test_cli_helpers.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
from __future__ import annotations
44

5+
import json
56
from pathlib import Path
67

78
import pytest
9+
from typer.testing import CliRunner
810

911
from cocoindex_code import cli
1012
from cocoindex_code.cli import (
@@ -13,6 +15,7 @@
1315
require_project_root,
1416
resolve_default_path,
1517
)
18+
from cocoindex_code.protocol import SearchResponse, SearchResult
1619

1720

1821
def test_require_project_root_success(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
@@ -84,6 +87,55 @@ def test_resolve_default_path_outside_project(
8487
assert result is None
8588

8689

90+
def test_search_help_includes_json_option() -> None:
91+
runner = CliRunner()
92+
93+
result = runner.invoke(cli.app, ["search", "--help"], catch_exceptions=False)
94+
95+
assert result.exit_code == 0
96+
assert "--json" in result.output
97+
98+
99+
def test_print_search_results_json_outputs_machine_readable_payload(
100+
capsys: pytest.CaptureFixture[str],
101+
) -> None:
102+
response = SearchResponse(
103+
success=True,
104+
results=[
105+
SearchResult(
106+
file_path="src/main.py",
107+
language="python",
108+
content="def main():\n return 1",
109+
start_line=10,
110+
end_line=11,
111+
score=0.875,
112+
)
113+
],
114+
total_returned=1,
115+
offset=5,
116+
message=None,
117+
)
118+
119+
cli.print_search_results_json(response)
120+
121+
assert json.loads(capsys.readouterr().out) == {
122+
"success": True,
123+
"results": [
124+
{
125+
"file_path": "src/main.py",
126+
"language": "python",
127+
"content": "def main():\n return 1",
128+
"start_line": 10,
129+
"end_line": 11,
130+
"score": 0.875,
131+
}
132+
],
133+
"total_returned": 1,
134+
"offset": 5,
135+
"message": None,
136+
}
137+
138+
87139
# ---------------------------------------------------------------------------
88140
# .gitignore helpers
89141
# ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)