Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ dev = [

[project.scripts]
cocoindex-code = "cocoindex_code:main"
ccc = "cocoindex_code.cli:app"
ccc = "cocoindex_code.cli:main"

[project.urls]
Homepage = "https://github.com/cocoindex-io/cocoindex-code"
Expand Down
7 changes: 6 additions & 1 deletion src/cocoindex_code/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -1164,6 +1164,11 @@ def run_daemon_cmd() -> None:
run_daemon()


def main() -> None:
"""Run the CLI without expanding literal search patterns on Windows."""
app(windows_expand_args=False)


# Allow running as module: python -m cocoindex_code.cli
if __name__ == "__main__":
app()
main()
61 changes: 61 additions & 0 deletions tests/test_cli_entrypoint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""Exercise argv parsing through both user-facing CLI entry points."""

import os
import runpy
import sys
from importlib.metadata import distribution
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import Mock

import pytest
import typer.core

from cocoindex_code import client
from cocoindex_code.protocol import SearchResponse


@pytest.mark.parametrize("entrypoint", ["console", "module"])
@pytest.mark.parametrize("query", ["some query", "docs/*"])
def test_windows_search_preserves_globs(
entrypoint: str, query: str, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Matching local files must not replace search terms or path filters."""
docs = tmp_path / "docs"
docs.mkdir()
(docs / "a.md").write_text("first document", encoding="utf-8")
(docs / "b.md").write_text("second document", encoding="utf-8")
project_settings = tmp_path / ".cocoindex_code"
project_settings.mkdir()
(project_settings / "settings.yml").write_text("include_patterns: []", encoding="utf-8")
user_settings = tmp_path / "ccc_home"
user_settings.mkdir()
(user_settings / "global_settings.yml").write_text(
"embedding:\n model: test\n provider: litellm\n", encoding="utf-8"
)
monkeypatch.setenv("COCOINDEX_CODE_DIR", str(user_settings))
monkeypatch.delenv("COCOINDEX_CODE_HOST_CWD", raising=False)
monkeypatch.chdir(tmp_path)
monkeypatch.setattr(sys, "argv", ["ccc", "search", query, "--path", "docs/*"])
# Only select Typer's Windows parsing branch; leave pathlib's OS unchanged.
monkeypatch.setattr(typer.core, "os", SimpleNamespace(**(vars(os) | {"name": "nt"})))
search = Mock(return_value=SearchResponse(success=True))
monkeypatch.setattr(client, "search", search)

with pytest.raises(SystemExit) as exc_info:
if entrypoint == "console":
entry = next(
entry
for entry in distribution("cocoindex-code").entry_points
if entry.group == "console_scripts" and entry.name == "ccc"
)
entry.load()()
else:
# Other tests import cli during collection; runpy needs a fresh module.
monkeypatch.delitem(sys.modules, "cocoindex_code.cli", raising=False)
runpy.run_module("cocoindex_code.cli", run_name="__main__", alter_sys=True)

assert exc_info.value.code == 0
search.assert_called_once()
assert search.call_args.kwargs["query"] == query
assert search.call_args.kwargs["paths"] == ["docs/*"]