From e074bf17652382c32815e73192138e66a658b7dd Mon Sep 17 00:00:00 2001 From: BukeLy Date: Tue, 26 May 2026 14:38:39 +0800 Subject: [PATCH] fix(filesystem): package pifs cli entrypoint --- pageindex/__init__.py | 4 ++++ pageindex/filesystem/cli.py | 42 +++++++++++++++++++++++++++++++++++++ pyproject.toml | 7 +++++++ tests/test_pifs_cli.py | 35 +++++++++++++++++++++++++++++++ uv.lock | 2 +- 5 files changed, 89 insertions(+), 1 deletion(-) diff --git a/pageindex/__init__.py b/pageindex/__init__.py index 4f05b61..97c3781 100644 --- a/pageindex/__init__.py +++ b/pageindex/__init__.py @@ -1,3 +1,7 @@ +import os + +os.environ.setdefault("LITELLM_LOCAL_MODEL_COST_MAP", "true") + try: from .page_index import * from .page_index_md import md_to_tree diff --git a/pageindex/filesystem/cli.py b/pageindex/filesystem/cli.py index 04ff8c9..9791e6a 100644 --- a/pageindex/filesystem/cli.py +++ b/pageindex/filesystem/cli.py @@ -16,6 +16,39 @@ DEFAULT_AGENT_MODEL = "gpt-5.4-mini" EXIT_COMMANDS = {"exit", "quit", ":q"} +def _load_env_file(path: str | None = None, *, workspace: str | None = None) -> Path | None: + from dotenv import load_dotenv + + if path: + env_path = Path(path).expanduser() + if not env_path.exists(): + raise FileNotFoundError(f"env file not found: {env_path}") + load_dotenv(env_path, override=True) + return env_path + + env_override = os.environ.get("PIFS_ENV_FILE") + if env_override: + return _load_env_file(env_override) + + starts = [Path.cwd()] + if workspace: + starts.append(Path(workspace).expanduser()) + seen: set[Path] = set() + for start in starts: + current = start.resolve() if start.exists() else start.resolve(strict=False) + if current.is_file(): + current = current.parent + for parent in (current, *current.parents): + candidate = parent / ".env" + if candidate in seen: + continue + seen.add(candidate) + if candidate.exists(): + load_dotenv(candidate, override=False) + return candidate + return None + + def _agent_model_default() -> str: return ( os.environ.get("PIFS_AGENT_MODEL") @@ -30,6 +63,7 @@ def _add_agent_arguments( workspace_default: str | None, ) -> None: parser.add_argument("--workspace", default=workspace_default) + parser.add_argument("--env-file", default=None) parser.add_argument("--model", default=_agent_model_default()) parser.add_argument( "--stream-mode", @@ -64,6 +98,9 @@ def _parse_agent_command( if command_name == "ask": parser.add_argument("question", nargs=argparse.REMAINDER) args = parser.parse_args(argv) + _load_env_file(args.env_file, workspace=args.workspace) + if not args.workspace: + args.workspace = os.environ.get("PIFS_WORKSPACE") if not args.workspace: parser.error("--workspace is required unless PIFS_WORKSPACE is set") return args @@ -130,11 +167,16 @@ def _run_passthrough( def main(argv: list[str] | None = None) -> int: argv = list(sys.argv[1:] if argv is None else argv) + _load_env_file() parser = argparse.ArgumentParser(description="PageIndex FileSystem CLI") parser.add_argument("--workspace", default=os.environ.get("PIFS_WORKSPACE")) + parser.add_argument("--env-file", default=None) parser.add_argument("--json", action="store_true", dest="json_output") parser.add_argument("command", nargs=argparse.REMAINDER) args = parser.parse_args(argv) + _load_env_file(args.env_file, workspace=args.workspace) + if not args.workspace: + args.workspace = os.environ.get("PIFS_WORKSPACE") command_tokens = [token for token in args.command if token != "--"] json_output = args.json_output diff --git a/pyproject.toml b/pyproject.toml index 7458247..aaea9f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,3 +15,10 @@ dependencies = [ [project.scripts] pifs = "pageindex.filesystem.cli:main" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["pageindex"] diff --git a/tests/test_pifs_cli.py b/tests/test_pifs_cli.py index d08c053..554ad43 100644 --- a/tests/test_pifs_cli.py +++ b/tests/test_pifs_cli.py @@ -1,3 +1,4 @@ +import os from pathlib import Path @@ -87,6 +88,40 @@ def test_cli_ask_invokes_agent_with_question(monkeypatch, capsys, tmp_path): } +def test_cli_ask_loads_env_file_before_running_agent(monkeypatch, capsys, tmp_path): + from pageindex.filesystem import cli + + workspace = tmp_path / "workspace" + env_file = tmp_path / ".env" + env_file.write_text("OPENAI_API_KEY=from-dotenv\n", encoding="utf-8") + agent_keys = [] + + def fake_run_pifs_agent(filesystem, question, **kwargs): + agent_keys.append(os.environ.get("OPENAI_API_KEY")) + return "agent answer" + + monkeypatch.delenv("OPENAI_API_KEY", raising=False) + monkeypatch.setattr(cli, "PageIndexFileSystem", FakeFileSystem) + monkeypatch.setattr(cli, "run_pifs_agent", fake_run_pifs_agent) + + status = cli.main( + [ + "ask", + "--workspace", + str(workspace), + "--env-file", + str(env_file), + "What", + "is", + "inside?", + ] + ) + + assert status == 0 + assert capsys.readouterr().out == "agent answer\n" + assert agent_keys == ["from-dotenv"] + + def test_cli_chat_runs_one_question_and_exits(monkeypatch, capsys, tmp_path): from pageindex.filesystem import cli diff --git a/uv.lock b/uv.lock index a2161f8..41ed9e4 100644 --- a/uv.lock +++ b/uv.lock @@ -1048,7 +1048,7 @@ wheels = [ [[package]] name = "pageindex" version = "0.1.0" -source = { virtual = "." } +source = { editable = "." } dependencies = [ { name = "litellm" }, { name = "openai-agents" },