slopguard-cli 0.1.2__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/PKG-INFO +1 -1
  2. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/pyproject.toml +1 -1
  3. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/slopguard/__init__.py +1 -1
  4. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/slopguard/update.py +28 -11
  5. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/tests/test_cli.py +2 -2
  6. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/tests/test_update.py +55 -3
  7. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/.gitignore +0 -0
  8. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/.ruff.toml +0 -0
  9. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/Makefile +0 -0
  10. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/README.md +0 -0
  11. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/scripts/generate_seed_data.py +0 -0
  12. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/slopguard/__main__.py +0 -0
  13. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/slopguard/cli.py +0 -0
  14. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/slopguard/config.py +0 -0
  15. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/slopguard/data/__init__.py +0 -0
  16. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/slopguard/data/hallucinations_seed.json +0 -0
  17. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/slopguard/data/popular_packages.json +0 -0
  18. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/slopguard/models.py +0 -0
  19. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/slopguard/parsers/__init__.py +0 -0
  20. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/slopguard/parsers/base.py +0 -0
  21. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/slopguard/parsers/npm.py +0 -0
  22. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/slopguard/parsers/python.py +0 -0
  23. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/slopguard/registry/__init__.py +0 -0
  24. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/slopguard/registry/base.py +0 -0
  25. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/slopguard/registry/npm.py +0 -0
  26. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/slopguard/registry/pypi.py +0 -0
  27. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/slopguard/report/__init__.py +0 -0
  28. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/slopguard/report/json.py +0 -0
  29. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/slopguard/report/terminal.py +0 -0
  30. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/slopguard/saas/__init__.py +0 -0
  31. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/slopguard/saas/client.py +0 -0
  32. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/slopguard/saas/credentials.py +0 -0
  33. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/slopguard/scoring/__init__.py +0 -0
  34. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/slopguard/scoring/engine.py +0 -0
  35. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/slopguard/scoring/signals.py +0 -0
  36. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/tests/__init__.py +0 -0
  37. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/tests/conftest.py +0 -0
  38. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/tests/fixtures/.slopguard.yaml +0 -0
  39. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/tests/fixtures/package.json +0 -0
  40. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/tests/fixtures/pyproject.toml +0 -0
  41. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/tests/fixtures/requirements.txt +0 -0
  42. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/tests/test_misc.py +0 -0
  43. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/tests/test_parsers_npm.py +0 -0
  44. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/tests/test_parsers_python.py +0 -0
  45. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/tests/test_registry_npm.py +0 -0
  46. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/tests/test_registry_pypi.py +0 -0
  47. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/tests/test_saas_auth.py +0 -0
  48. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/tests/test_saas_client.py +0 -0
  49. {slopguard_cli-0.1.2 → slopguard_cli-0.2.0}/tests/test_scoring_engine.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: slopguard-cli
3
- Version: 0.1.2
3
+ Version: 0.2.0
4
4
  Summary: Defend developers and AI coding agents against slopsquatting (hallucinated package names).
5
5
  Project-URL: Homepage, https://github.com/hariomunknownslab/slopguard
6
6
  Project-URL: Repository, https://github.com/hariomunknownslab/slopguard
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "slopguard-cli"
7
- version = "0.1.2"
7
+ version = "0.2.0"
8
8
  description = "Defend developers and AI coding agents against slopsquatting (hallucinated package names)."
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -2,6 +2,6 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- __version__ = "0.1.2"
5
+ __version__ = "0.2.0"
6
6
 
7
7
  __all__ = ["__version__"]
@@ -1,13 +1,21 @@
1
- """``slopguard update`` — refresh the local hallucination DB from the SaaS feed.
1
+ """``slopguard update`` — refresh the local hallucination DB.
2
2
 
3
- Reads the public feed at ``$SLOPGUARD_API_URL/v1/hallucinations`` (defaults
4
- to the SlopGuard SaaS) and writes a seed-shaped file to
5
- ``~/.cache/slopguard/hallucinations_db.json``. The next ``slopguard scan``
6
- prefers this cached file over the bundled seed (see
3
+ Fetches the curated DB from the SlopGuard GitHub Pages site (no API,
4
+ no account, no auth) and writes a seed-shaped file to
5
+ ``~/.cache/slopguard/hallucinations_db.json``. The next ``slopguard
6
+ scan`` prefers this cached file over the bundled seed (see
7
7
  ``slopguard.data.load_hallucination_db``).
8
8
 
9
- No auth — the endpoint is public, intentionally, so air-gapped CI can
10
- hit it through a proxy without a token.
9
+ Source of truth:
10
+ https://hariomunknownslab.github.io/slopguard/db.json
11
+
12
+ Updated daily by the probe-cron GitHub Action; each entry goes through
13
+ PR review before publication (see probe-data/README.md in the repo).
14
+
15
+ Override the URL with ``SLOPGUARD_DB_URL`` to fetch from a fork or
16
+ mirror. The legacy ``SLOPGUARD_API_URL`` env var still works for the
17
+ 0.1.x SaaS endpoint shape — used as a fallback for backward
18
+ compatibility.
11
19
  """
12
20
 
13
21
  from __future__ import annotations
@@ -20,12 +28,21 @@ from typing import Any
20
28
 
21
29
  import httpx
22
30
 
23
- DEFAULT_API_URL = "https://api.slopguard.dev"
31
+ DEFAULT_DB_URL = "https://hariomunknownslab.github.io/slopguard/db.json"
24
32
  CACHE_PATH = Path.home() / ".cache" / "slopguard" / "hallucinations_db.json"
25
33
 
26
34
 
27
- def _api_url() -> str:
28
- return os.environ.get("SLOPGUARD_API_URL") or DEFAULT_API_URL
35
+ def _db_url() -> str:
36
+ """Where to fetch the published DB.
37
+
38
+ Precedence: ``SLOPGUARD_DB_URL`` > legacy
39
+ ``SLOPGUARD_API_URL/v1/hallucinations`` > default GitHub Pages.
40
+ """
41
+ if explicit := os.environ.get("SLOPGUARD_DB_URL"):
42
+ return explicit
43
+ if legacy := os.environ.get("SLOPGUARD_API_URL"):
44
+ return legacy.rstrip("/") + "/v1/hallucinations"
45
+ return DEFAULT_DB_URL
29
46
 
30
47
 
31
48
  def _convert(wire: dict[str, Any]) -> dict[str, Any]:
@@ -53,7 +70,7 @@ def _convert(wire: dict[str, Any]) -> dict[str, Any]:
53
70
 
54
71
 
55
72
  def run() -> int:
56
- url = _api_url().rstrip("/") + "/v1/hallucinations"
73
+ url = _db_url()
57
74
  try:
58
75
  resp = httpx.get(url, timeout=15.0)
59
76
  resp.raise_for_status()
@@ -107,7 +107,7 @@ def runner() -> CliRunner:
107
107
  def test_version_subcommand(runner: CliRunner) -> None:
108
108
  result = runner.invoke(app, ["version"])
109
109
  assert result.exit_code == 0
110
- assert result.stdout.strip() == "0.1.2"
110
+ assert result.stdout.strip() == "0.2.0"
111
111
 
112
112
 
113
113
  def test_scan_fixtures_no_network_terminal(runner: CliRunner, fixtures_dir: Path) -> None:
@@ -183,4 +183,4 @@ def test_module_main_runs() -> None:
183
183
  check=False,
184
184
  )
185
185
  assert result.returncode == 0
186
- assert result.stdout.strip() == "0.1.2"
186
+ assert result.stdout.strip() == "0.2.0"
@@ -67,9 +67,7 @@ def test_update_writes_cache(
67
67
  def test_update_handles_network_failure(
68
68
  cache_at_tmp: Path, api_at_test: None, capsys: pytest.CaptureFixture[str]
69
69
  ) -> None:
70
- respx.get("http://api.test/v1/hallucinations").mock(
71
- side_effect=httpx.ConnectError("nope")
72
- )
70
+ respx.get("http://api.test/v1/hallucinations").mock(side_effect=httpx.ConnectError("nope"))
73
71
  code = update.run()
74
72
  assert code == 1
75
73
  assert not cache_at_tmp.exists()
@@ -87,3 +85,57 @@ def test_update_rejects_malformed_payload(
87
85
  assert code == 1
88
86
  assert not cache_at_tmp.exists()
89
87
  assert "invalid response" in capsys.readouterr().out
88
+
89
+
90
+ def test_default_db_url_is_github_pages(monkeypatch: pytest.MonkeyPatch) -> None:
91
+ """When neither override env var is set, fetch from GitHub Pages."""
92
+ monkeypatch.delenv("SLOPGUARD_DB_URL", raising=False)
93
+ monkeypatch.delenv("SLOPGUARD_API_URL", raising=False)
94
+ assert update._db_url() == update.DEFAULT_DB_URL
95
+ assert update.DEFAULT_DB_URL.startswith("https://hariomunknownslab.github.io/")
96
+
97
+
98
+ def test_slopguard_db_url_takes_precedence(monkeypatch: pytest.MonkeyPatch) -> None:
99
+ monkeypatch.setenv("SLOPGUARD_DB_URL", "https://my.mirror/db.json")
100
+ monkeypatch.setenv("SLOPGUARD_API_URL", "https://legacy/api") # should be ignored
101
+ assert update._db_url() == "https://my.mirror/db.json"
102
+
103
+
104
+ def test_legacy_api_url_still_works(monkeypatch: pytest.MonkeyPatch) -> None:
105
+ """0.1.x users with SLOPGUARD_API_URL set keep working — we append
106
+ /v1/hallucinations the way the SaaS endpoint expected."""
107
+ monkeypatch.delenv("SLOPGUARD_DB_URL", raising=False)
108
+ monkeypatch.setenv("SLOPGUARD_API_URL", "https://legacy.example/")
109
+ assert update._db_url() == "https://legacy.example/v1/hallucinations"
110
+
111
+
112
+ @respx.mock
113
+ def test_update_fetches_from_pages_by_default(
114
+ cache_at_tmp: Path, monkeypatch: pytest.MonkeyPatch
115
+ ) -> None:
116
+ """End-to-end: no env overrides, hits the Pages URL."""
117
+ monkeypatch.delenv("SLOPGUARD_DB_URL", raising=False)
118
+ monkeypatch.delenv("SLOPGUARD_API_URL", raising=False)
119
+ respx.get(update.DEFAULT_DB_URL).mock(
120
+ return_value=httpx.Response(
121
+ 200,
122
+ json={
123
+ "generated_at": "2026-05-24T03:17:00Z",
124
+ "count": 1,
125
+ "entries": [
126
+ {
127
+ "package_name": "react-secure-auth-helper",
128
+ "ecosystem": "npm",
129
+ "total_observations": 7,
130
+ "recurrence_rate": 0.18,
131
+ "first_observed_at": "2026-05-21T00:00:00Z",
132
+ "last_observed_at": "2026-05-23T00:00:00Z",
133
+ "published_at": "2026-05-24T03:17:00Z",
134
+ }
135
+ ],
136
+ },
137
+ )
138
+ )
139
+ assert update.run() == 0
140
+ cached = json.loads(cache_at_tmp.read_text())
141
+ assert cached["entries"][0]["name"] == "react-secure-auth-helper"
File without changes
File without changes
File without changes
File without changes