shadow-debugger 0.1.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 (32) hide show
  1. shadow_debugger-0.1.0/LICENSE +21 -0
  2. shadow_debugger-0.1.0/PKG-INFO +292 -0
  3. shadow_debugger-0.1.0/README.md +237 -0
  4. shadow_debugger-0.1.0/pyproject.toml +102 -0
  5. shadow_debugger-0.1.0/setup.cfg +4 -0
  6. shadow_debugger-0.1.0/src/shadow_debugger/__init__.py +293 -0
  7. shadow_debugger-0.1.0/src/shadow_debugger/_version.py +1 -0
  8. shadow_debugger-0.1.0/src/shadow_debugger/analyzer.py +108 -0
  9. shadow_debugger-0.1.0/src/shadow_debugger/config.py +56 -0
  10. shadow_debugger-0.1.0/src/shadow_debugger/core.py +244 -0
  11. shadow_debugger-0.1.0/src/shadow_debugger/inspector.py +176 -0
  12. shadow_debugger-0.1.0/src/shadow_debugger/integrations/__init__.py +224 -0
  13. shadow_debugger-0.1.0/src/shadow_debugger/patch_check.py +57 -0
  14. shadow_debugger-0.1.0/src/shadow_debugger/providers/__init__.py +23 -0
  15. shadow_debugger-0.1.0/src/shadow_debugger/providers/anthropic.py +54 -0
  16. shadow_debugger-0.1.0/src/shadow_debugger/providers/base.py +92 -0
  17. shadow_debugger-0.1.0/src/shadow_debugger/providers/gemini.py +66 -0
  18. shadow_debugger-0.1.0/src/shadow_debugger/providers/ollama.py +65 -0
  19. shadow_debugger-0.1.0/src/shadow_debugger/providers/openai.py +63 -0
  20. shadow_debugger-0.1.0/src/shadow_debugger/py.typed +1 -0
  21. shadow_debugger-0.1.0/src/shadow_debugger/redaction.py +190 -0
  22. shadow_debugger-0.1.0/src/shadow_debugger/reporter.py +128 -0
  23. shadow_debugger-0.1.0/src/shadow_debugger/throttler.py +64 -0
  24. shadow_debugger-0.1.0/src/shadow_debugger.egg-info/PKG-INFO +292 -0
  25. shadow_debugger-0.1.0/src/shadow_debugger.egg-info/SOURCES.txt +30 -0
  26. shadow_debugger-0.1.0/src/shadow_debugger.egg-info/dependency_links.txt +1 -0
  27. shadow_debugger-0.1.0/src/shadow_debugger.egg-info/requires.txt +33 -0
  28. shadow_debugger-0.1.0/src/shadow_debugger.egg-info/top_level.txt +1 -0
  29. shadow_debugger-0.1.0/tests/test_core_and_integrations.py +239 -0
  30. shadow_debugger-0.1.0/tests/test_providers_and_analyzer.py +102 -0
  31. shadow_debugger-0.1.0/tests/test_redaction_and_inspector.py +82 -0
  32. shadow_debugger-0.1.0/tests/test_throttler_and_patch_check.py +73 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 GaspareDev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,292 @@
1
+ Metadata-Version: 2.4
2
+ Name: shadow-debugger
3
+ Version: 0.1.0
4
+ Summary: Non-blocking AI-powered background error diagnosis and root-cause analysis for Python applications and web APIs.
5
+ Author: GaspareDev
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/GaspareDev/shadow-debugger
8
+ Project-URL: Repository, https://github.com/GaspareDev/shadow-debugger
9
+ Project-URL: Issues, https://github.com/GaspareDev/shadow-debugger/issues
10
+ Project-URL: Changelog, https://github.com/GaspareDev/shadow-debugger/blob/main/CHANGELOG.md
11
+ Keywords: debugging,error-tracking,observability,llm,ai,gemini,ollama,openai,anthropic,fastapi,flask,django
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Software Development :: Bug Tracking
23
+ Classifier: Topic :: Software Development :: Debuggers
24
+ Classifier: Topic :: System :: Monitoring
25
+ Classifier: Typing :: Typed
26
+ Requires-Python: >=3.9
27
+ Description-Content-Type: text/markdown
28
+ License-File: LICENSE
29
+ Provides-Extra: gemini
30
+ Requires-Dist: google-genai>=2.22.0; extra == "gemini"
31
+ Provides-Extra: openai
32
+ Requires-Dist: openai>=1.50.0; extra == "openai"
33
+ Provides-Extra: anthropic
34
+ Requires-Dist: anthropic>=0.40.0; extra == "anthropic"
35
+ Provides-Extra: fastapi
36
+ Requires-Dist: fastapi>=0.100.0; extra == "fastapi"
37
+ Provides-Extra: flask
38
+ Requires-Dist: flask>=2.2.0; extra == "flask"
39
+ Requires-Dist: blinker>=1.6.0; extra == "flask"
40
+ Provides-Extra: django
41
+ Requires-Dist: django>=4.2; extra == "django"
42
+ Provides-Extra: all
43
+ Requires-Dist: google-genai>=2.22.0; extra == "all"
44
+ Requires-Dist: openai>=1.50.0; extra == "all"
45
+ Requires-Dist: anthropic>=0.40.0; extra == "all"
46
+ Requires-Dist: fastapi>=0.100.0; extra == "all"
47
+ Requires-Dist: flask>=2.2.0; extra == "all"
48
+ Requires-Dist: blinker>=1.6.0; extra == "all"
49
+ Provides-Extra: dev
50
+ Requires-Dist: pytest>=8.0; extra == "dev"
51
+ Requires-Dist: httpx>=0.27.0; extra == "dev"
52
+ Requires-Dist: ruff>=0.6.0; extra == "dev"
53
+ Requires-Dist: mypy>=1.11.0; extra == "dev"
54
+ Dynamic: license-file
55
+
56
+ # shadow-debugger
57
+
58
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
59
+ [![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
60
+
61
+ Background error diagnosis for Python apps and web APIs.
62
+
63
+ When an exception or HTTP 500 happens, `shadow-debugger` captures the stack frame from your code, strips secrets from local variables and request data, and sends the context to an LLM in a background worker thread. Your app returns its normal response right away; a few seconds later you get a Markdown postmortem in `./shadow_reports/`.
64
+
65
+ It works offline with **Ollama** (zero external dependencies) or with **Gemini**, **OpenAI** (including OpenAI-compatible endpoints like Groq, vLLM, LM Studio), and **Anthropic**.
66
+
67
+ ---
68
+
69
+ ## What a report looks like
70
+
71
+ Given a crashing endpoint:
72
+
73
+ ```python
74
+ @app.get("/checkout/{order_id}")
75
+ async def checkout(order_id: int, discount_items: int = 0):
76
+ unit_discount = 50 / discount_items
77
+ return {"order_id": order_id, "discount": unit_discount}
78
+ ```
79
+
80
+ `shadow-debugger` writes `shadow_reports/incident_20261018_103012_ZeroDivisionError_9f2a1c4b.md`:
81
+
82
+ ````markdown
83
+ # Shadow Debugger Incident Report
84
+
85
+ **Timestamp:** 2026-10-18T10:30:12.418290+00:00
86
+ **Exception:** `ZeroDivisionError`: division by zero
87
+ **Location:** `app/routes.py:42` in `checkout`
88
+ **Fingerprint:** `9f2a1c4b8e1032d1` (occurrences so far: 1)
89
+
90
+ ---
91
+
92
+ ### 1. Root Cause Summary
93
+ `checkout()` divides `50` by `discount_items` without checking for `0`. When the query parameter is omitted or set to `0`, Python raises `ZeroDivisionError`.
94
+
95
+ ### 2. Trigger Condition
96
+ `GET /checkout/1042?discount_items=0` with local state `{"order_id": 1042, "discount_items": 0}`.
97
+
98
+ ### 3. Proposed Fix
99
+ ```python
100
+ unit_discount = 50 / discount_items if discount_items > 0 else 0.0
101
+ ```
102
+ ```diff
103
+ - unit_discount = 50 / discount_items
104
+ + unit_discount = 50 / discount_items if discount_items > 0 else 0.0
105
+ ```
106
+
107
+ ### 4. Prevention Advice
108
+ Validate numeric query parameters with `Query(gt=0)` in FastAPI or guard denominators before dividing.
109
+ ````
110
+
111
+ ---
112
+
113
+ ## Installation
114
+
115
+ ```bash
116
+ # Core package (stdlib only — works with local Ollama out of the box)
117
+ pip install shadow-debugger
118
+
119
+ # Or install with the provider / framework you use:
120
+ pip install "shadow-debugger[gemini]"
121
+ pip install "shadow-debugger[openai]"
122
+ pip install "shadow-debugger[anthropic]"
123
+ pip install "shadow-debugger[fastapi]"
124
+ pip install "shadow-debugger[flask]"
125
+ pip install "shadow-debugger[django]"
126
+ ```
127
+
128
+ ---
129
+
130
+ ## Quick start
131
+
132
+ ### Local with Ollama (no API key)
133
+
134
+ ```python
135
+ import shadow_debugger
136
+
137
+ shadow_debugger.init(
138
+ provider="ollama",
139
+ model="llama3.2",
140
+ print_to_console=True,
141
+ )
142
+ ```
143
+
144
+ ### Cloud providers
145
+
146
+ ```python
147
+ import shadow_debugger
148
+
149
+ # Uses GEMINI_API_KEY
150
+ shadow_debugger.init(provider="gemini", model="gemini-3.8-flash")
151
+
152
+ # Uses OPENAI_API_KEY (or pass base_url=... for Groq / vLLM / LM Studio)
153
+ shadow_debugger.init(provider="openai", model="gpt-5.4-nano")
154
+
155
+ # Uses ANTHROPIC_API_KEY
156
+ shadow_debugger.init(provider="anthropic", model="claude-haiku-5-5")
157
+ ```
158
+
159
+ ---
160
+
161
+ ## Web frameworks
162
+
163
+ ### FastAPI / Starlette
164
+
165
+ ```python
166
+ from fastapi import FastAPI
167
+ import shadow_debugger
168
+
169
+ shadow_debugger.init(provider="ollama", model="llama3.2")
170
+
171
+ app = FastAPI()
172
+ app.add_middleware(shadow_debugger.FastAPIWatcherMiddleware)
173
+ ```
174
+
175
+ ### Flask
176
+
177
+ ```python
178
+ from flask import Flask
179
+ import shadow_debugger
180
+
181
+ app = Flask(__name__)
182
+ shadow_debugger.init(provider="ollama", model="llama3.2")
183
+ shadow_debugger.init_flask(app)
184
+ ```
185
+
186
+ `init_flask` hooks into `got_request_exception`, so normal 404/4xx responses are left untouched.
187
+
188
+ ### Django
189
+
190
+ In `settings.py`:
191
+
192
+ ```python
193
+ MIDDLEWARE = [
194
+ "shadow_debugger.integrations.DjangoWatcherMiddleware",
195
+ # ...
196
+ ]
197
+ ```
198
+
199
+ ---
200
+
201
+ ## Decorator & manual capture
202
+
203
+ `@watch` works on both regular and `async def` functions:
204
+
205
+ ```python
206
+ import shadow_debugger
207
+
208
+ @shadow_debugger.watch(reraise=False)
209
+ async def process_order(payload: dict):
210
+ return payload["items"]["price"] / payload["items"]["quantity"]
211
+
212
+ # Or inside an existing try/except block:
213
+ try:
214
+ sync_inventory()
215
+ except Exception as exc:
216
+ shadow_debugger.capture_exception(exc, extra_metadata={"job": "inventory"})
217
+ ```
218
+
219
+ ---
220
+
221
+ ## Privacy & what gets sent
222
+
223
+ If you use a cloud provider, the prompt includes the traceback, the 5 lines of source around the error, and the local variables of the failing function. To keep sensitive data out of prompts:
224
+
225
+ - **Variable & header names** matching common secret names (`password`, `secret`, `token`, `api_key`, `authorization`, `cookie`, etc.) are replaced with `[REDACTED]`.
226
+ - **String values** matching JWTs, API keys (`sk-...`, `AIza...`, `AKIA...`, `ghp_...`), `Bearer` tokens, PEM private keys, and URL query tokens (`?access_token=...`) are scrubbed across variables, tracebacks, and exception messages.
227
+ - **Lazy objects** (like Django `QuerySet`s) are inspected via `__dict__` without calling `repr()`/`str()`, so capturing an error won't trigger extra database queries.
228
+ - You can turn off parts of the payload with `send_locals=False` or `send_source_code=False`, add your own regexes via `redact_keys=[...]` and `redact_patterns=[...]`, or run everything locally with `provider="ollama"`.
229
+ - Proposed code patches are only checked with `ast.parse()` — generated code is never executed.
230
+
231
+ ---
232
+
233
+ ## Configuration
234
+
235
+ All options can be passed to `shadow_debugger.init(...)`:
236
+
237
+ | Option | Default | Description |
238
+ | :--- | :--- | :--- |
239
+ | `provider` | `"gemini"` | `"gemini"`, `"ollama"`, `"openai"`, `"anthropic"`, or a `Provider` instance. |
240
+ | `model` | `None` | Model name. Uses the provider default if omitted. |
241
+ | `api_key` | `None` | API key (falls back to `GEMINI_API_KEY`, `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`). |
242
+ | `base_url` | `None` | Custom URL for Ollama (`OLLAMA_HOST`) or OpenAI-compatible servers. |
243
+ | `output_dir` | `"./shadow_reports"` | Folder for Markdown reports (`None` disables file output). |
244
+ | `print_to_console` | `False` | Print diagnoses to `stderr` (handy in local dev). |
245
+ | `cooldown_seconds` | `300.0` | Minimum seconds before re-analyzing the same error at the same line. |
246
+ | `send_source_code` | `True` | Include source lines around the crash. |
247
+ | `send_locals` | `True` | Include redacted local variables. |
248
+ | `redact_keys` | `()` | Extra regexes for variable/key names to redact. |
249
+ | `redact_patterns` | `()` | Extra regexes for secret value patterns to scrub. |
250
+ | `request_timeout` | `60.0` | Timeout in seconds for LLM calls. |
251
+ | `shutdown_timeout` | `10.0` | Seconds to wait on process exit for pending reports to finish. |
252
+ | `max_queue_size` | `100` | Max queued incidents before dropping new ones. |
253
+ | `catch_uncaught` | `True` | Hook `sys.excepthook` and `threading.excepthook`. |
254
+
255
+ ---
256
+
257
+ ## Writing a custom provider
258
+
259
+ ```python
260
+ import shadow_debugger
261
+ from shadow_debugger import Provider, register_provider
262
+
263
+ @register_provider("my-gateway")
264
+ class MyGatewayProvider(Provider):
265
+ default_model = "my-model"
266
+ env_key = "MY_GATEWAY_KEY"
267
+
268
+ def complete(self, system: str, prompt: str) -> str:
269
+ self.check_configuration()
270
+ # Call your LLM endpoint and return Markdown
271
+ return "..."
272
+
273
+ shadow_debugger.init(provider="my-gateway")
274
+ ```
275
+
276
+ ---
277
+
278
+ ## Roadmap & contributing
279
+
280
+ I'm actively looking for feedback and contributors. A few areas where PRs are very welcome:
281
+
282
+ - [ ] Webhook / notification sinks (Slack, Discord, Telegram, GitHub Issues)
283
+ - [ ] Additional providers (Mistral, LiteLLM, AWS Bedrock)
284
+ - [ ] Structured JSON output option alongside Markdown reports
285
+ - [ ] Celery / background worker task integration
286
+ - [ ] CLI command to browse and filter local reports in `./shadow_reports`
287
+
288
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for local setup instructions.
289
+
290
+ ## License
291
+
292
+ [MIT](LICENSE)
@@ -0,0 +1,237 @@
1
+ # shadow-debugger
2
+
3
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
4
+ [![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
5
+
6
+ Background error diagnosis for Python apps and web APIs.
7
+
8
+ When an exception or HTTP 500 happens, `shadow-debugger` captures the stack frame from your code, strips secrets from local variables and request data, and sends the context to an LLM in a background worker thread. Your app returns its normal response right away; a few seconds later you get a Markdown postmortem in `./shadow_reports/`.
9
+
10
+ It works offline with **Ollama** (zero external dependencies) or with **Gemini**, **OpenAI** (including OpenAI-compatible endpoints like Groq, vLLM, LM Studio), and **Anthropic**.
11
+
12
+ ---
13
+
14
+ ## What a report looks like
15
+
16
+ Given a crashing endpoint:
17
+
18
+ ```python
19
+ @app.get("/checkout/{order_id}")
20
+ async def checkout(order_id: int, discount_items: int = 0):
21
+ unit_discount = 50 / discount_items
22
+ return {"order_id": order_id, "discount": unit_discount}
23
+ ```
24
+
25
+ `shadow-debugger` writes `shadow_reports/incident_20261018_103012_ZeroDivisionError_9f2a1c4b.md`:
26
+
27
+ ````markdown
28
+ # Shadow Debugger Incident Report
29
+
30
+ **Timestamp:** 2026-10-18T10:30:12.418290+00:00
31
+ **Exception:** `ZeroDivisionError`: division by zero
32
+ **Location:** `app/routes.py:42` in `checkout`
33
+ **Fingerprint:** `9f2a1c4b8e1032d1` (occurrences so far: 1)
34
+
35
+ ---
36
+
37
+ ### 1. Root Cause Summary
38
+ `checkout()` divides `50` by `discount_items` without checking for `0`. When the query parameter is omitted or set to `0`, Python raises `ZeroDivisionError`.
39
+
40
+ ### 2. Trigger Condition
41
+ `GET /checkout/1042?discount_items=0` with local state `{"order_id": 1042, "discount_items": 0}`.
42
+
43
+ ### 3. Proposed Fix
44
+ ```python
45
+ unit_discount = 50 / discount_items if discount_items > 0 else 0.0
46
+ ```
47
+ ```diff
48
+ - unit_discount = 50 / discount_items
49
+ + unit_discount = 50 / discount_items if discount_items > 0 else 0.0
50
+ ```
51
+
52
+ ### 4. Prevention Advice
53
+ Validate numeric query parameters with `Query(gt=0)` in FastAPI or guard denominators before dividing.
54
+ ````
55
+
56
+ ---
57
+
58
+ ## Installation
59
+
60
+ ```bash
61
+ # Core package (stdlib only — works with local Ollama out of the box)
62
+ pip install shadow-debugger
63
+
64
+ # Or install with the provider / framework you use:
65
+ pip install "shadow-debugger[gemini]"
66
+ pip install "shadow-debugger[openai]"
67
+ pip install "shadow-debugger[anthropic]"
68
+ pip install "shadow-debugger[fastapi]"
69
+ pip install "shadow-debugger[flask]"
70
+ pip install "shadow-debugger[django]"
71
+ ```
72
+
73
+ ---
74
+
75
+ ## Quick start
76
+
77
+ ### Local with Ollama (no API key)
78
+
79
+ ```python
80
+ import shadow_debugger
81
+
82
+ shadow_debugger.init(
83
+ provider="ollama",
84
+ model="llama3.2",
85
+ print_to_console=True,
86
+ )
87
+ ```
88
+
89
+ ### Cloud providers
90
+
91
+ ```python
92
+ import shadow_debugger
93
+
94
+ # Uses GEMINI_API_KEY
95
+ shadow_debugger.init(provider="gemini", model="gemini-3.8-flash")
96
+
97
+ # Uses OPENAI_API_KEY (or pass base_url=... for Groq / vLLM / LM Studio)
98
+ shadow_debugger.init(provider="openai", model="gpt-5.4-nano")
99
+
100
+ # Uses ANTHROPIC_API_KEY
101
+ shadow_debugger.init(provider="anthropic", model="claude-haiku-5-5")
102
+ ```
103
+
104
+ ---
105
+
106
+ ## Web frameworks
107
+
108
+ ### FastAPI / Starlette
109
+
110
+ ```python
111
+ from fastapi import FastAPI
112
+ import shadow_debugger
113
+
114
+ shadow_debugger.init(provider="ollama", model="llama3.2")
115
+
116
+ app = FastAPI()
117
+ app.add_middleware(shadow_debugger.FastAPIWatcherMiddleware)
118
+ ```
119
+
120
+ ### Flask
121
+
122
+ ```python
123
+ from flask import Flask
124
+ import shadow_debugger
125
+
126
+ app = Flask(__name__)
127
+ shadow_debugger.init(provider="ollama", model="llama3.2")
128
+ shadow_debugger.init_flask(app)
129
+ ```
130
+
131
+ `init_flask` hooks into `got_request_exception`, so normal 404/4xx responses are left untouched.
132
+
133
+ ### Django
134
+
135
+ In `settings.py`:
136
+
137
+ ```python
138
+ MIDDLEWARE = [
139
+ "shadow_debugger.integrations.DjangoWatcherMiddleware",
140
+ # ...
141
+ ]
142
+ ```
143
+
144
+ ---
145
+
146
+ ## Decorator & manual capture
147
+
148
+ `@watch` works on both regular and `async def` functions:
149
+
150
+ ```python
151
+ import shadow_debugger
152
+
153
+ @shadow_debugger.watch(reraise=False)
154
+ async def process_order(payload: dict):
155
+ return payload["items"]["price"] / payload["items"]["quantity"]
156
+
157
+ # Or inside an existing try/except block:
158
+ try:
159
+ sync_inventory()
160
+ except Exception as exc:
161
+ shadow_debugger.capture_exception(exc, extra_metadata={"job": "inventory"})
162
+ ```
163
+
164
+ ---
165
+
166
+ ## Privacy & what gets sent
167
+
168
+ If you use a cloud provider, the prompt includes the traceback, the 5 lines of source around the error, and the local variables of the failing function. To keep sensitive data out of prompts:
169
+
170
+ - **Variable & header names** matching common secret names (`password`, `secret`, `token`, `api_key`, `authorization`, `cookie`, etc.) are replaced with `[REDACTED]`.
171
+ - **String values** matching JWTs, API keys (`sk-...`, `AIza...`, `AKIA...`, `ghp_...`), `Bearer` tokens, PEM private keys, and URL query tokens (`?access_token=...`) are scrubbed across variables, tracebacks, and exception messages.
172
+ - **Lazy objects** (like Django `QuerySet`s) are inspected via `__dict__` without calling `repr()`/`str()`, so capturing an error won't trigger extra database queries.
173
+ - You can turn off parts of the payload with `send_locals=False` or `send_source_code=False`, add your own regexes via `redact_keys=[...]` and `redact_patterns=[...]`, or run everything locally with `provider="ollama"`.
174
+ - Proposed code patches are only checked with `ast.parse()` — generated code is never executed.
175
+
176
+ ---
177
+
178
+ ## Configuration
179
+
180
+ All options can be passed to `shadow_debugger.init(...)`:
181
+
182
+ | Option | Default | Description |
183
+ | :--- | :--- | :--- |
184
+ | `provider` | `"gemini"` | `"gemini"`, `"ollama"`, `"openai"`, `"anthropic"`, or a `Provider` instance. |
185
+ | `model` | `None` | Model name. Uses the provider default if omitted. |
186
+ | `api_key` | `None` | API key (falls back to `GEMINI_API_KEY`, `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`). |
187
+ | `base_url` | `None` | Custom URL for Ollama (`OLLAMA_HOST`) or OpenAI-compatible servers. |
188
+ | `output_dir` | `"./shadow_reports"` | Folder for Markdown reports (`None` disables file output). |
189
+ | `print_to_console` | `False` | Print diagnoses to `stderr` (handy in local dev). |
190
+ | `cooldown_seconds` | `300.0` | Minimum seconds before re-analyzing the same error at the same line. |
191
+ | `send_source_code` | `True` | Include source lines around the crash. |
192
+ | `send_locals` | `True` | Include redacted local variables. |
193
+ | `redact_keys` | `()` | Extra regexes for variable/key names to redact. |
194
+ | `redact_patterns` | `()` | Extra regexes for secret value patterns to scrub. |
195
+ | `request_timeout` | `60.0` | Timeout in seconds for LLM calls. |
196
+ | `shutdown_timeout` | `10.0` | Seconds to wait on process exit for pending reports to finish. |
197
+ | `max_queue_size` | `100` | Max queued incidents before dropping new ones. |
198
+ | `catch_uncaught` | `True` | Hook `sys.excepthook` and `threading.excepthook`. |
199
+
200
+ ---
201
+
202
+ ## Writing a custom provider
203
+
204
+ ```python
205
+ import shadow_debugger
206
+ from shadow_debugger import Provider, register_provider
207
+
208
+ @register_provider("my-gateway")
209
+ class MyGatewayProvider(Provider):
210
+ default_model = "my-model"
211
+ env_key = "MY_GATEWAY_KEY"
212
+
213
+ def complete(self, system: str, prompt: str) -> str:
214
+ self.check_configuration()
215
+ # Call your LLM endpoint and return Markdown
216
+ return "..."
217
+
218
+ shadow_debugger.init(provider="my-gateway")
219
+ ```
220
+
221
+ ---
222
+
223
+ ## Roadmap & contributing
224
+
225
+ I'm actively looking for feedback and contributors. A few areas where PRs are very welcome:
226
+
227
+ - [ ] Webhook / notification sinks (Slack, Discord, Telegram, GitHub Issues)
228
+ - [ ] Additional providers (Mistral, LiteLLM, AWS Bedrock)
229
+ - [ ] Structured JSON output option alongside Markdown reports
230
+ - [ ] Celery / background worker task integration
231
+ - [ ] CLI command to browse and filter local reports in `./shadow_reports`
232
+
233
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for local setup instructions.
234
+
235
+ ## License
236
+
237
+ [MIT](LICENSE)
@@ -0,0 +1,102 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "shadow-debugger"
7
+ dynamic = ["version"]
8
+ description = "Non-blocking AI-powered background error diagnosis and root-cause analysis for Python applications and web APIs."
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ requires-python = ">=3.9"
12
+ authors = [
13
+ { name = "GaspareDev" }
14
+ ]
15
+ keywords = [
16
+ "debugging",
17
+ "error-tracking",
18
+ "observability",
19
+ "llm",
20
+ "ai",
21
+ "gemini",
22
+ "ollama",
23
+ "openai",
24
+ "anthropic",
25
+ "fastapi",
26
+ "flask",
27
+ "django",
28
+ ]
29
+ classifiers = [
30
+ "Development Status :: 4 - Beta",
31
+ "Intended Audience :: Developers",
32
+ "License :: OSI Approved :: MIT License",
33
+ "Operating System :: OS Independent",
34
+ "Programming Language :: Python :: 3",
35
+ "Programming Language :: Python :: 3.9",
36
+ "Programming Language :: Python :: 3.10",
37
+ "Programming Language :: Python :: 3.11",
38
+ "Programming Language :: Python :: 3.12",
39
+ "Programming Language :: Python :: 3.13",
40
+ "Topic :: Software Development :: Bug Tracking",
41
+ "Topic :: Software Development :: Debuggers",
42
+ "Topic :: System :: Monitoring",
43
+ "Typing :: Typed",
44
+ ]
45
+ # Core package has ZERO mandatory third-party dependencies (Ollama works out of the box).
46
+ dependencies = []
47
+
48
+ [project.optional-dependencies]
49
+ gemini = ["google-genai>=2.22.0"]
50
+ openai = ["openai>=1.50.0"]
51
+ anthropic = ["anthropic>=0.40.0"]
52
+ fastapi = ["fastapi>=0.100.0"]
53
+ flask = ["flask>=2.2.0", "blinker>=1.6.0"]
54
+ django = ["django>=4.2"]
55
+ all = [
56
+ "google-genai>=2.22.0",
57
+ "openai>=1.50.0",
58
+ "anthropic>=0.40.0",
59
+ "fastapi>=0.100.0",
60
+ "flask>=2.2.0",
61
+ "blinker>=1.6.0",
62
+ ]
63
+ dev = [
64
+ "pytest>=8.0",
65
+ "httpx>=0.27.0",
66
+ "ruff>=0.6.0",
67
+ "mypy>=1.11.0",
68
+ ]
69
+
70
+ [project.urls]
71
+ Homepage = "https://github.com/GaspareDev/shadow-debugger"
72
+ Repository = "https://github.com/GaspareDev/shadow-debugger"
73
+ Issues = "https://github.com/GaspareDev/shadow-debugger/issues"
74
+ Changelog = "https://github.com/GaspareDev/shadow-debugger/blob/main/CHANGELOG.md"
75
+
76
+ [tool.setuptools.dynamic]
77
+ version = { attr = "shadow_debugger._version.__version__" }
78
+
79
+ [tool.setuptools.packages.find]
80
+ where = ["src"]
81
+
82
+ [tool.setuptools.package-data]
83
+ shadow_debugger = ["py.typed"]
84
+
85
+ [tool.pytest.ini_options]
86
+ testpaths = ["tests"]
87
+ pythonpath = ["src"]
88
+ addopts = "-q"
89
+
90
+ [tool.ruff]
91
+ line-length = 110
92
+ target-version = "py39"
93
+ src = ["src", "tests"]
94
+
95
+ [tool.ruff.lint]
96
+ select = ["E", "F", "I", "B", "UP"]
97
+
98
+ [tool.mypy]
99
+ python_version = "3.9"
100
+ packages = ["shadow_debugger"]
101
+ mypy_path = "src"
102
+ ignore_missing_imports = true
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+