pinhaul 0.4.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.
@@ -0,0 +1,30 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ .venv/
5
+ *.egg-info/
6
+ dist/
7
+ build/
8
+
9
+ # Env & secrets
10
+ .env
11
+ .env.local
12
+
13
+ # Node
14
+ node_modules/
15
+
16
+ # Django
17
+ **/static_root/
18
+ **/media/
19
+ db.sqlite3
20
+
21
+ # Tooling caches
22
+ .ruff_cache/
23
+ .pytest_cache/
24
+ uv.lock.disabled
25
+
26
+ # OS
27
+ .DS_Store
28
+
29
+ # real deployment secrets
30
+ deploy/server.env
pinhaul-0.4.0/PKG-INFO ADDED
@@ -0,0 +1,115 @@
1
+ Metadata-Version: 2.5
2
+ Name: pinhaul
3
+ Version: 0.4.0
4
+ Summary: Runtime dependency inventory for Pinhaul: one import, your project reports what it actually runs.
5
+ Project-URL: Homepage, https://pinhaul.io
6
+ Author: Reknown
7
+ License: Proprietary
8
+ Requires-Python: >=3.9
9
+ Requires-Dist: httpx>=0.24
10
+ Provides-Extra: dotenv
11
+ Requires-Dist: python-dotenv>=1.0; extra == 'dotenv'
12
+ Provides-Extra: scan
13
+ Requires-Dist: bandit>=1.7; extra == 'scan'
14
+ Description-Content-Type: text/markdown
15
+
16
+ # pinhaul (Python SDK + CLI)
17
+
18
+ Runtime dependency security for vibe coders: one import (or one `docker run` with the
19
+ [agent](../agent)) and Pinhaul knows exactly what your project *actually* runs, scans it,
20
+ and tells you what matters in plain language.
21
+
22
+ ## Install
23
+
24
+ ```bash
25
+ pip install pinhaul # or: pip install "pinhaul[dotenv]" for .env loading
26
+ ```
27
+
28
+ ## Quickstart
29
+
30
+ ```bash
31
+ # 1. Get an API key
32
+ pinhaul register --email you@example.com
33
+
34
+ # 2. Add the key to your project
35
+ echo 'PINHAUL_API_KEY=<your key>' >> .env
36
+
37
+ # 3. One line in your app's entrypoint
38
+ ```
39
+
40
+ ```python
41
+ from pinhaul import Pinhaul
42
+ Pinhaul(project_name="my-app", environment="prod", metadata={"team": "core"})
43
+ ```
44
+
45
+ That's it. On startup, Pinhaul captures Python builtins (version, implementation,
46
+ platform, script name, hostname) and the package versions **actually loaded in your
47
+ process** — runtime truth, not a requirements.txt guess — and reports them to your
48
+ Pinhaul cloud in a background thread that can never crash or block your app.
49
+
50
+ ## Snapshot caching (don't re-send what didn't change)
51
+
52
+ The SDK fingerprints the full payload (python facts + metadata + loaded inventory,
53
+ sorted so import order doesn't matter) and caches it under
54
+ `~/.cache/pinhaul/snapshots/`. An unchanged environment within the TTL sends
55
+ **nothing**; any change to python version, metadata, or a package version ships
56
+ immediately. After the TTL (default 6h) the next start re-sends once as a liveness
57
+ heartbeat — the server deduplicates that without creating rows.
58
+
59
+ | Env var | Default | Effect |
60
+ |---|---|---|
61
+ | `PINHAUL_REPORT_TTL` | `21600` (6h) | Seconds an unchanged fingerprint is suppressed. `0` = always send |
62
+ | `PINHAUL_NO_CACHE` | unset | Set to disable client caching entirely |
63
+
64
+ `Pinhaul(..., force=True)` bypasses the cache for one explicit send. Server-side
65
+ dedup (same fingerprint within its window) protects against cache misses, CI, and
66
+ second machines regardless of client settings. Check what happened via
67
+ `ds.last_result`: `sent` / `deduplicated` / `skipped` / `disabled`.
68
+
69
+ ## What it sends (and never sends)
70
+
71
+ ✅ python version/implementation, platform, script *basename*, hostname, loaded package
72
+ names+versions, the metadata you explicitly pass.
73
+
74
+ ❌ environment variables, file paths, source code, secrets, anything else.
75
+
76
+ Set `PINHAUL_DISABLE=1` to no-op (e.g. in CI). Set `PINHAUL_DEBUG=1` to print
77
+ diagnostics to stderr on failure.
78
+
79
+ ## Local scan engines (feature-gated)
80
+
81
+ ```bash
82
+ pinhaul scan --code # bandit SAST over your python code
83
+ pinhaul scan --secrets # gitleaks over your git history (full history, redacted)
84
+ pinhaul scan --code --secrets --project my-app
85
+ ```
86
+
87
+ - `--code` needs bandit: `pip install "pinhaul[scan]"`
88
+ - `--secrets` needs the [gitleaks](https://github.com/zricethezav/gitleaks) binary on your PATH
89
+ - Engines are enabled per plan server-side; a disabled engine prints 🔒 instead of running
90
+ - **Privacy:** code snippets are never sent (bandit findings carry test id + file:line only);
91
+ gitleaks runs with `--redact` and secret values are stripped client-side *and* server-side
92
+
93
+ ## CLI
94
+
95
+ ```bash
96
+ pinhaul doctor [--docker] # diagnose setup: API, key, cache, socket
97
+ pinhaul status # account + project health
98
+ pinhaul report [--project my-app] # severity summary + dependency intel + AI insight
99
+ pinhaul findings [--severity critical,high] [--source trivy,osv,bandit,gitleaks,depintel]
100
+ pinhaul mute 7643 [7644 ...] # hide accepted-risk findings from reports/alerts
101
+ pinhaul unmute 7643
102
+ pinhaul sbom --project my-app --out sbom.json # CycloneDX 1.5 from runtime inventory
103
+ pinhaul analyze [--project my-app] # queue an AI triage run
104
+ pinhaul init --name my-app # print the integration snippet
105
+ ```
106
+
107
+ API endpoint defaults to `http://127.0.0.1:8000` for local development; override with
108
+ `PINHAUL_API_URL` or `--api-url`.
109
+
110
+ ## Development
111
+
112
+ ```bash
113
+ cd sdk && python3 -m venv .venv && . .venv/bin/activate
114
+ pip install -e . && python -m unittest discover tests -v
115
+ ```
@@ -0,0 +1,100 @@
1
+ # pinhaul (Python SDK + CLI)
2
+
3
+ Runtime dependency security for vibe coders: one import (or one `docker run` with the
4
+ [agent](../agent)) and Pinhaul knows exactly what your project *actually* runs, scans it,
5
+ and tells you what matters in plain language.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install pinhaul # or: pip install "pinhaul[dotenv]" for .env loading
11
+ ```
12
+
13
+ ## Quickstart
14
+
15
+ ```bash
16
+ # 1. Get an API key
17
+ pinhaul register --email you@example.com
18
+
19
+ # 2. Add the key to your project
20
+ echo 'PINHAUL_API_KEY=<your key>' >> .env
21
+
22
+ # 3. One line in your app's entrypoint
23
+ ```
24
+
25
+ ```python
26
+ from pinhaul import Pinhaul
27
+ Pinhaul(project_name="my-app", environment="prod", metadata={"team": "core"})
28
+ ```
29
+
30
+ That's it. On startup, Pinhaul captures Python builtins (version, implementation,
31
+ platform, script name, hostname) and the package versions **actually loaded in your
32
+ process** — runtime truth, not a requirements.txt guess — and reports them to your
33
+ Pinhaul cloud in a background thread that can never crash or block your app.
34
+
35
+ ## Snapshot caching (don't re-send what didn't change)
36
+
37
+ The SDK fingerprints the full payload (python facts + metadata + loaded inventory,
38
+ sorted so import order doesn't matter) and caches it under
39
+ `~/.cache/pinhaul/snapshots/`. An unchanged environment within the TTL sends
40
+ **nothing**; any change to python version, metadata, or a package version ships
41
+ immediately. After the TTL (default 6h) the next start re-sends once as a liveness
42
+ heartbeat — the server deduplicates that without creating rows.
43
+
44
+ | Env var | Default | Effect |
45
+ |---|---|---|
46
+ | `PINHAUL_REPORT_TTL` | `21600` (6h) | Seconds an unchanged fingerprint is suppressed. `0` = always send |
47
+ | `PINHAUL_NO_CACHE` | unset | Set to disable client caching entirely |
48
+
49
+ `Pinhaul(..., force=True)` bypasses the cache for one explicit send. Server-side
50
+ dedup (same fingerprint within its window) protects against cache misses, CI, and
51
+ second machines regardless of client settings. Check what happened via
52
+ `ds.last_result`: `sent` / `deduplicated` / `skipped` / `disabled`.
53
+
54
+ ## What it sends (and never sends)
55
+
56
+ ✅ python version/implementation, platform, script *basename*, hostname, loaded package
57
+ names+versions, the metadata you explicitly pass.
58
+
59
+ ❌ environment variables, file paths, source code, secrets, anything else.
60
+
61
+ Set `PINHAUL_DISABLE=1` to no-op (e.g. in CI). Set `PINHAUL_DEBUG=1` to print
62
+ diagnostics to stderr on failure.
63
+
64
+ ## Local scan engines (feature-gated)
65
+
66
+ ```bash
67
+ pinhaul scan --code # bandit SAST over your python code
68
+ pinhaul scan --secrets # gitleaks over your git history (full history, redacted)
69
+ pinhaul scan --code --secrets --project my-app
70
+ ```
71
+
72
+ - `--code` needs bandit: `pip install "pinhaul[scan]"`
73
+ - `--secrets` needs the [gitleaks](https://github.com/zricethezav/gitleaks) binary on your PATH
74
+ - Engines are enabled per plan server-side; a disabled engine prints 🔒 instead of running
75
+ - **Privacy:** code snippets are never sent (bandit findings carry test id + file:line only);
76
+ gitleaks runs with `--redact` and secret values are stripped client-side *and* server-side
77
+
78
+ ## CLI
79
+
80
+ ```bash
81
+ pinhaul doctor [--docker] # diagnose setup: API, key, cache, socket
82
+ pinhaul status # account + project health
83
+ pinhaul report [--project my-app] # severity summary + dependency intel + AI insight
84
+ pinhaul findings [--severity critical,high] [--source trivy,osv,bandit,gitleaks,depintel]
85
+ pinhaul mute 7643 [7644 ...] # hide accepted-risk findings from reports/alerts
86
+ pinhaul unmute 7643
87
+ pinhaul sbom --project my-app --out sbom.json # CycloneDX 1.5 from runtime inventory
88
+ pinhaul analyze [--project my-app] # queue an AI triage run
89
+ pinhaul init --name my-app # print the integration snippet
90
+ ```
91
+
92
+ API endpoint defaults to `http://127.0.0.1:8000` for local development; override with
93
+ `PINHAUL_API_URL` or `--api-url`.
94
+
95
+ ## Development
96
+
97
+ ```bash
98
+ cd sdk && python3 -m venv .venv && . .venv/bin/activate
99
+ pip install -e . && python -m unittest discover tests -v
100
+ ```
@@ -0,0 +1,15 @@
1
+ """Pinhaul SDK: runtime dependency inventory with a one-line import.
2
+
3
+ from pinhaul import Pinhaul
4
+ Pinhaul(project_name="my-app") # reads PINHAUL_API_KEY from env/.env
5
+
6
+ Sends: python version/implementation, platform, script name, hostname, and the
7
+ package versions actually loaded in this process. Never env vars, never file
8
+ paths, never code. Fails silently (PINHAUL_DEBUG=1 to see errors) and can be
9
+ fully disabled with PINHAUL_DISABLE=1.
10
+ """
11
+
12
+ from pinhaul.client import Pinhaul
13
+
14
+ __version__ = "0.4.0"
15
+ __all__ = ["Pinhaul", "__version__"]
@@ -0,0 +1,60 @@
1
+ """Local snapshot cache: skip shipping when the runtime fingerprint is unchanged.
2
+
3
+ Cache lives under XDG_CACHE_HOME (default ~/.cache)/pinhaul/snapshots, one small
4
+ JSON per (project, api_url). Every operation fails open: a read-only home, a
5
+ corrupt file, or a missing directory must never break the host application.
6
+ """
7
+
8
+ import hashlib
9
+ import json
10
+ import os
11
+ import time
12
+ from pathlib import Path
13
+
14
+ CACHE_VERSION = 1
15
+
16
+
17
+ def _cache_dir() -> Path:
18
+ base = os.environ.get("XDG_CACHE_HOME") or str(Path.home() / ".cache")
19
+ return Path(base) / "pinhaul" / "snapshots"
20
+
21
+
22
+ def _cache_path(project_name: str, api_url: str) -> Path:
23
+ raw = f"{project_name}|{api_url}".encode()
24
+ digest = hashlib.sha256(raw).hexdigest()[:24]
25
+ return _cache_dir() / f"{digest}.json"
26
+
27
+
28
+ def load(project_name: str, api_url: str) -> dict | None:
29
+ """Return {"fingerprint": str, "sent_at": epoch_seconds} or None."""
30
+ try:
31
+ path = _cache_path(project_name, api_url)
32
+ if not path.is_file():
33
+ return None
34
+ data = json.loads(path.read_text(encoding="utf-8"))
35
+ if data.get("version") != CACHE_VERSION:
36
+ return None
37
+ if not isinstance(data.get("fingerprint"), str):
38
+ return None
39
+ return data
40
+ except Exception:
41
+ return None
42
+
43
+
44
+ def save(project_name: str, api_url: str, fingerprint: str) -> None:
45
+ try:
46
+ path = _cache_path(project_name, api_url)
47
+ path.parent.mkdir(parents=True, exist_ok=True)
48
+ path.write_text(
49
+ json.dumps({"version": CACHE_VERSION, "fingerprint": fingerprint, "sent_at": time.time()}),
50
+ encoding="utf-8",
51
+ )
52
+ except Exception:
53
+ pass
54
+
55
+
56
+ def clear(project_name: str, api_url: str) -> None:
57
+ try:
58
+ _cache_path(project_name, api_url).unlink(missing_ok=True)
59
+ except Exception:
60
+ pass