proofrun 0.1.1__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 (38) hide show
  1. proofrun-0.1.1/.github/workflows/ci.yaml +86 -0
  2. proofrun-0.1.1/.github/workflows/release.yaml +113 -0
  3. proofrun-0.1.1/.gitignore +84 -0
  4. proofrun-0.1.1/.pre-commit-config.yaml +26 -0
  5. proofrun-0.1.1/LICENSE +21 -0
  6. proofrun-0.1.1/Makefile +67 -0
  7. proofrun-0.1.1/PKG-INFO +268 -0
  8. proofrun-0.1.1/README.md +250 -0
  9. proofrun-0.1.1/docs/demo-fail.svg +119 -0
  10. proofrun-0.1.1/docs/demo-pass.svg +118 -0
  11. proofrun-0.1.1/pyproject.toml +80 -0
  12. proofrun-0.1.1/scenarios/auth.yaml +45 -0
  13. proofrun-0.1.1/scenarios/chain.yaml +89 -0
  14. proofrun-0.1.1/scenarios/crud.yaml +163 -0
  15. proofrun-0.1.1/scenarios/profile.yaml +39 -0
  16. proofrun-0.1.1/scenarios/server.py +83 -0
  17. proofrun-0.1.1/src/proofrun/__init__.py +10 -0
  18. proofrun-0.1.1/src/proofrun/__main__.py +9 -0
  19. proofrun-0.1.1/src/proofrun/cli.py +325 -0
  20. proofrun-0.1.1/src/proofrun/core/__init__.py +40 -0
  21. proofrun-0.1.1/src/proofrun/core/compiler.py +136 -0
  22. proofrun-0.1.1/src/proofrun/core/config.py +150 -0
  23. proofrun-0.1.1/src/proofrun/core/context.py +185 -0
  24. proofrun-0.1.1/src/proofrun/core/engine.py +231 -0
  25. proofrun-0.1.1/src/proofrun/core/notifier.py +75 -0
  26. proofrun-0.1.1/src/proofrun/core/provider.py +55 -0
  27. proofrun-0.1.1/src/proofrun/core/state.py +81 -0
  28. proofrun-0.1.1/tests/__init__.py +3 -0
  29. proofrun-0.1.1/tests/test_cli.py +912 -0
  30. proofrun-0.1.1/tests/test_compiler.py +598 -0
  31. proofrun-0.1.1/tests/test_config.py +451 -0
  32. proofrun-0.1.1/tests/test_context.py +262 -0
  33. proofrun-0.1.1/tests/test_engine.py +527 -0
  34. proofrun-0.1.1/tests/test_main.py +28 -0
  35. proofrun-0.1.1/tests/test_notifier.py +196 -0
  36. proofrun-0.1.1/tests/test_provider.py +184 -0
  37. proofrun-0.1.1/tests/test_state.py +138 -0
  38. proofrun-0.1.1/uv.lock +917 -0
@@ -0,0 +1,86 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ - master
8
+ pull_request:
9
+ branches:
10
+ - main
11
+ - master
12
+
13
+ permissions:
14
+ contents: read
15
+
16
+ jobs:
17
+ quality:
18
+ name: Quality & Tests
19
+ runs-on: ubuntu-latest
20
+
21
+ strategy:
22
+ fail-fast: false
23
+ matrix:
24
+ python-version:
25
+ - "3.11"
26
+ - "3.12"
27
+
28
+ steps:
29
+ - name: Checkout repository
30
+ uses: actions/checkout@v4
31
+
32
+ - name: Set up Python
33
+ uses: actions/setup-python@v5
34
+ with:
35
+ python-version: ${{ matrix.python-version }}
36
+
37
+ - name: Set up uv
38
+ uses: astral-sh/setup-uv@v5
39
+ with:
40
+ enable-cache: true
41
+ github-token: ${{ secrets.GITHUB_TOKEN }}
42
+
43
+ - name: Install dependencies
44
+ run: make install
45
+
46
+ - name: Run code quality & coverage checks
47
+ run: make check
48
+
49
+ - name: Run integration scenarios
50
+ run: |
51
+ make run-profile
52
+ make run-auth
53
+ make run-chain
54
+ make run-crud
55
+
56
+ build:
57
+ name: Build Package
58
+ runs-on: ubuntu-latest
59
+ needs: quality
60
+
61
+ steps:
62
+ - name: Checkout repository
63
+ uses: actions/checkout@v4
64
+
65
+ - name: Set up Python
66
+ uses: actions/setup-python@v5
67
+ with:
68
+ python-version: "3.11"
69
+
70
+ - name: Set up uv
71
+ uses: astral-sh/setup-uv@v5
72
+ with:
73
+ enable-cache: true
74
+ github-token: ${{ secrets.GITHUB_TOKEN }}
75
+
76
+ - name: Install dependencies
77
+ run: make install
78
+
79
+ - name: Build distributions
80
+ run: make build
81
+
82
+ - name: Verify distributions
83
+ run: |
84
+ ls -lh dist/
85
+ test -f dist/*.whl
86
+ test -f dist/*.tar.gz
@@ -0,0 +1,113 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ quality:
13
+ name: Quality & Tests
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - name: Checkout repository
17
+ uses: actions/checkout@v4
18
+
19
+ - name: Set up Python
20
+ uses: actions/setup-python@v5
21
+ with:
22
+ python-version: "3.11"
23
+
24
+ - name: Set up uv
25
+ uses: astral-sh/setup-uv@v5
26
+ with:
27
+ enable-cache: true
28
+ github-token: ${{ secrets.GITHUB_TOKEN }}
29
+
30
+ - name: Install dependencies
31
+ run: make install
32
+
33
+ - name: Run code quality checks
34
+ run: make check
35
+
36
+ - name: Run integration scenarios
37
+ run: |
38
+ make run-profile
39
+ make run-chain
40
+
41
+ build:
42
+ name: Build Package
43
+ runs-on: ubuntu-latest
44
+ needs: quality
45
+ steps:
46
+ - name: Checkout repository
47
+ uses: actions/checkout@v4
48
+
49
+ - name: Set up Python
50
+ uses: actions/setup-python@v5
51
+ with:
52
+ python-version: "3.11"
53
+
54
+ - name: Set up uv
55
+ uses: astral-sh/setup-uv@v5
56
+ with:
57
+ enable-cache: true
58
+ github-token: ${{ secrets.GITHUB_TOKEN }}
59
+
60
+ - name: Verify tag matches package version
61
+ run: |
62
+ TAG_VERSION="${GITHUB_REF_NAME#v}"
63
+ PKG_VERSION="$(grep -m1 '^version = ' pyproject.toml | cut -d'"' -f2)"
64
+ echo "tag: ${TAG_VERSION} pyproject: ${PKG_VERSION}"
65
+ if [ "${TAG_VERSION}" != "${PKG_VERSION}" ]; then
66
+ echo "::error::Tag v${TAG_VERSION} does not match pyproject version ${PKG_VERSION}"
67
+ exit 1
68
+ fi
69
+
70
+ - name: Build distributions
71
+ run: make build
72
+
73
+ - name: Store distributions
74
+ uses: actions/upload-artifact@v4
75
+ with:
76
+ name: package-distributions
77
+ path: dist/
78
+
79
+ publish-to-pypi:
80
+ name: Publish to PyPI
81
+ runs-on: ubuntu-latest
82
+ needs: build
83
+ environment: release
84
+ permissions:
85
+ id-token: write
86
+ steps:
87
+ - name: Download distributions
88
+ uses: actions/download-artifact@v4
89
+ with:
90
+ name: package-distributions
91
+ path: dist/
92
+
93
+ - name: Publish to PyPI
94
+ uses: pypa/gh-action-pypi-publish@release/v1
95
+
96
+ github-release:
97
+ name: Create GitHub Release
98
+ runs-on: ubuntu-latest
99
+ needs: publish-to-pypi
100
+ permissions:
101
+ contents: write
102
+ steps:
103
+ - name: Download distributions
104
+ uses: actions/download-artifact@v4
105
+ with:
106
+ name: package-distributions
107
+ path: dist/
108
+
109
+ - name: Create GitHub Release
110
+ uses: softprops/action-gh-release@v2
111
+ with:
112
+ files: dist/*
113
+ generate_release_notes: true
@@ -0,0 +1,84 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+
7
+ # Virtual environments
8
+ .venv/
9
+ venv/
10
+ env/
11
+ ENV/
12
+ env.bak/
13
+ venv.bak/
14
+
15
+ # Environment / secrets
16
+ .env
17
+ .env.*
18
+ !.env.example
19
+
20
+ # Distribution / packaging
21
+ build/
22
+ dist/
23
+ *.egg-info/
24
+ .eggs/
25
+ *.egg
26
+ MANIFEST
27
+
28
+ # Test / coverage
29
+ .pytest_cache/
30
+ .coverage
31
+ .coverage.*
32
+ htmlcov/
33
+ coverage.xml
34
+ *.cover
35
+ *.lcov
36
+ .hypothesis/
37
+ .tox/
38
+ .nox/
39
+
40
+ # Tool caches
41
+ .ruff_cache/
42
+ .pyright_cache/
43
+ .mypy_cache/
44
+ .pyre/
45
+ .pytype/
46
+ .dmypy.json
47
+ dmypy.json
48
+
49
+ # IDE / editors
50
+ .idea/
51
+ .vscode/
52
+ .spyderproject
53
+ .spyproject
54
+ .ropeproject
55
+
56
+ # Jupyter
57
+ .ipynb_checkpoints/
58
+
59
+ # OS
60
+ .DS_Store
61
+ Thumbs.db
62
+
63
+ # Temporary files
64
+ *.tmp
65
+ *.temp
66
+ *.swp
67
+ *.swo
68
+ *~
69
+ tempCodeRunnerFile.py
70
+
71
+ # Build / local tooling
72
+ .pdm-python
73
+ .pdm-build/
74
+ .pixi/
75
+ __pypackages__/
76
+
77
+ # Local package/build metadata
78
+ .python-version
79
+
80
+ # PyPI credentials
81
+ .pypirc
82
+
83
+
84
+ FUTURE.md
@@ -0,0 +1,26 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.8.0
4
+ hooks:
5
+ - id: ruff
6
+ args: [--fix]
7
+ stages: [pre-commit]
8
+
9
+ - id: ruff-format
10
+ stages: [pre-commit]
11
+
12
+ - repo: local
13
+ hooks:
14
+ - id: pyright
15
+ name: pyright
16
+ entry: uv run pyright
17
+ language: system
18
+ pass_filenames: false
19
+ stages: [pre-push]
20
+
21
+ - id: pytest
22
+ name: pytest
23
+ entry: uv run pytest -q
24
+ language: system
25
+ pass_filenames: false
26
+ stages: [pre-push]
proofrun-0.1.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kishan Thanki
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,67 @@
1
+ .PHONY: install test coverage coverage-html lint format format-check typecheck check \
2
+ run-profile run-auth run-chain run-crud build clean full ci
3
+
4
+ full: clean install check build
5
+
6
+ install:
7
+ uv sync --group dev
8
+
9
+ test:
10
+ uv run pytest -q
11
+
12
+ coverage:
13
+ uv run pytest -q --cov=proofrun --cov-branch \
14
+ --cov-report=term-missing \
15
+ --cov-fail-under=100
16
+
17
+ coverage-html:
18
+ uv run pytest -q --cov=proofrun --cov-branch \
19
+ --cov-report=html
20
+ @echo "Coverage report generated: htmlcov/index.html"
21
+
22
+ lint:
23
+ uv run ruff check src/ tests/
24
+
25
+ format:
26
+ uv run ruff format src/ tests/
27
+
28
+ format-check:
29
+ uv run ruff format --check src/ tests/
30
+ uv run ruff check src/ tests/
31
+
32
+ typecheck:
33
+ uv run pyright
34
+
35
+ check: lint format-check typecheck coverage
36
+
37
+ run-profile:
38
+ uv run proofrun scenarios/profile.yaml --once
39
+
40
+ run-auth:
41
+ python scenarios/server.py & \
42
+ SERVER_PID=$$!; \
43
+ sleep 1; \
44
+ uv run proofrun scenarios/auth.yaml --once; \
45
+ EXIT_CODE=$$?; \
46
+ kill $$SERVER_PID 2>/dev/null || true; \
47
+ exit $$EXIT_CODE
48
+
49
+ run-chain:
50
+ uv run proofrun scenarios/chain.yaml --once
51
+
52
+ run-crud:
53
+ uv run proofrun scenarios/crud.yaml --once
54
+
55
+ build:
56
+ uv build
57
+
58
+ clean:
59
+ rm -rf dist/ \
60
+ .ruff_cache/ \
61
+ .pytest_cache/ \
62
+ .pyright_cache/ \
63
+ .coverage \
64
+ .coverage.* \
65
+ htmlcov/ \
66
+ __pycache__/ \
67
+ *.egg-info/
@@ -0,0 +1,268 @@
1
+ Metadata-Version: 2.5
2
+ Name: proofrun
3
+ Version: 0.1.1
4
+ Summary: Synthetic API monitoring as code daemon.
5
+ License: MIT
6
+ License-File: LICENSE
7
+ Requires-Python: >=3.11
8
+ Requires-Dist: fastjsonschema>=2.19.1
9
+ Requires-Dist: httpx>=0.27.0
10
+ Requires-Dist: jsonpath-ng>=1.6.1
11
+ Requires-Dist: orjson>=3.9.15
12
+ Requires-Dist: pydantic>=2.6.0
13
+ Requires-Dist: pyyaml>=6.0.1
14
+ Requires-Dist: rich>=13.7.0
15
+ Requires-Dist: typer>=0.11.0
16
+ Requires-Dist: uvloop>=0.19.0
17
+ Description-Content-Type: text/markdown
18
+
19
+ <div align="center">
20
+
21
+ # Proofrun
22
+
23
+ **Write an API workflow in YAML. Run it locally, in CI, or as a monitor that only alerts when health changes.**
24
+
25
+ [![Python](https://img.shields.io/badge/python-3.11%2B-blue?logo=python&logoColor=white)](https://www.python.org/)
26
+ [![CI](https://github.com/Kishan-Thanki/proof/actions/workflows/ci.yaml/badge.svg)](https://github.com/Kishan-Thanki/proof/actions/workflows/ci.yaml)
27
+ [![Release](https://img.shields.io/github/v/release/Kishan-Thanki/proof?color=blue&logo=github)](https://github.com/Kishan-Thanki/proof/releases)
28
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
29
+
30
+ </div>
31
+
32
+ ---
33
+
34
+ Most monitors tell you an endpoint returned `200`. Proofrun tells you whether the
35
+ *journey* works: log in, take the token from the response, use it on the next
36
+ request, and check what comes back.
37
+
38
+ When something breaks, you see exactly which step failed and why:
39
+
40
+ <div align="center">
41
+ <img src="docs/demo-fail.svg" alt="Proofrun reporting a failed step in a login-then-fetch scenario" width="860">
42
+ </div>
43
+
44
+ And when everything is fine:
45
+
46
+ <div align="center">
47
+ <img src="docs/demo-pass.svg" alt="Proofrun reporting a passing scenario" width="860">
48
+ </div>
49
+
50
+ **YAML in. HTTP checks out. No database, queue, or external service required.**
51
+
52
+ - **One file describes the workflow.** Scenarios live in Git next to your code.
53
+ - **Multi-step.** Values extracted from one response feed the next request.
54
+ - **Runs anywhere.** Same file locally, in CI (exit code `0` / `1`), or as a daemon.
55
+ - **Quiet by design.** In daemon mode you are alerted when health *changes*, not on every failed check.
56
+
57
+ ## Quickstart (30 seconds)
58
+
59
+ **1. Install** (Python 3.11+):
60
+
61
+ ```bash
62
+ pip install proofrun
63
+ ```
64
+
65
+ Or with pip: `pipx install proofrun`
66
+
67
+ **2. Create `health.yaml`:**
68
+
69
+ ```yaml
70
+ version: "1.0"
71
+
72
+ global:
73
+ base_url: "https://jsonplaceholder.typicode.com"
74
+
75
+ scenarios:
76
+ - name: "Public API is up"
77
+ steps:
78
+ - name: "GET /posts/1"
79
+ request:
80
+ method: GET
81
+ path: /posts/1
82
+ expect:
83
+ status: 200
84
+ max_latency_ms: 2000
85
+ ```
86
+
87
+ **3. Run it:**
88
+
89
+ ```bash
90
+ proofrun health.yaml
91
+ ```
92
+
93
+ That's it. Swap in your own `base_url` and paths. Add `--daemon` to keep it
94
+ running.
95
+
96
+ ## A realistic example: log in, then use the token
97
+
98
+ This is the scenario from the screenshots above. It logs in, extracts the
99
+ access token with JSONPath, sends it as a `Bearer` header on the next request,
100
+ extracts the user id from *that* response, and uses it in the URL of the third.
101
+
102
+ ```yaml
103
+ version: "1.0"
104
+
105
+ global:
106
+ base_url: "http://127.0.0.1:8099"
107
+ timeout_seconds: 5
108
+
109
+ scenarios:
110
+ - name: "Login and fetch orders"
111
+ steps:
112
+ - name: "Log in"
113
+ request:
114
+ method: POST
115
+ path: /login
116
+ json:
117
+ email: "demo@example.com"
118
+ password: "demo-password"
119
+ expect:
120
+ status: 200
121
+ max_latency_ms: 1000
122
+ extract:
123
+ token: "$.access_token" # saved as ${token}
124
+
125
+ - name: "Fetch profile"
126
+ request:
127
+ method: GET
128
+ path: /me
129
+ headers:
130
+ Authorization: "Bearer ${token}" # used here
131
+ expect:
132
+ status: 200
133
+ extract:
134
+ user_id: "$.id" # saved as ${user_id}
135
+
136
+ - name: "Fetch orders"
137
+ request:
138
+ method: GET
139
+ path: "/users/${user_id}/orders"
140
+ headers:
141
+ Authorization: "Bearer ${token}"
142
+ expect:
143
+ status: 200
144
+ schema: # JSON Schema validation
145
+ type: array
146
+ minItems: 1
147
+ ```
148
+
149
+ Steps run in order and stop at the first failure, so the report points at the
150
+ step that actually broke. You can try this without any external service. The
151
+ repo includes a tiny local API:
152
+
153
+ ```bash
154
+ # terminal 1: start the demo API
155
+ python scenarios/server.py
156
+
157
+ # terminal 2: run the scenario (passes, exit code 0)
158
+ proofrun scenarios/auth.yaml
159
+ ```
160
+
161
+ Now break it on purpose. Restart the demo API with `--break`, which makes
162
+ `GET /me` return `500`, and run the same scenario again:
163
+
164
+ ```bash
165
+ python scenarios/server.py --break
166
+ proofrun scenarios/auth.yaml # fails, exit code 1
167
+ ```
168
+
169
+ ## What you can assert
170
+
171
+ Each step's `expect` block supports:
172
+
173
+ | Assertion | Example | Notes |
174
+ | --- | --- | --- |
175
+ | `status` | `status: 200` | Required. |
176
+ | `max_latency_ms` | `max_latency_ms: 500` | Fails if the response is slower. |
177
+ | `headers` | `headers: { Content-Type: "application/json" }` | Exact match. |
178
+ | `schema` | inline JSON Schema, or a path to a file | Validates the JSON response body. |
179
+
180
+ A step can also `extract` values with JSONPath for later steps. Besides your own
181
+ variables, built-in dynamic values are available anywhere you can write `${...}`:
182
+ `${$uuid}`, `${$timestamp}`, `${$timestamp_ms}`, `${$iso_timestamp}`,
183
+ `${$random_int}`.
184
+
185
+ Defaults you set under `global` (`base_url`, `timeout_seconds`, `headers`) apply
186
+ to every step, and any step can override its own `headers` and `timeout`.
187
+
188
+ ## Run it continuously
189
+
190
+ ```bash
191
+ proofrun monitors/api.yaml --daemon
192
+ ```
193
+
194
+ In daemon mode Proofrun re-runs your scenarios on a loop and tracks each one as
195
+ `HEALTHY` or `FAILING`.
196
+
197
+ * **Alerts on change, not on repetition.** A webhook fires when a scenario starts
198
+ failing and again when it recovers. A scenario that stays down does not
199
+ re-alert on every cycle.
200
+ * **Edit configs live.** Changes to the YAML are picked up without a restart. If
201
+ an edit is invalid, Proofrun keeps running the last working configuration.
202
+ * **Jittered schedule.** Sleep time varies by ±15% so checks don't all land on
203
+ the same second.
204
+
205
+ Send alerts to any HTTP endpoint:
206
+
207
+ ```bash
208
+ proofrun monitors/api.yaml --daemon --webhook-url https://<your_domain>/hooks/proofrun
209
+
210
+ # or
211
+
212
+ export PROOFRUN_WEBHOOK_URL=https://<your_domain>/hooks/proofrun
213
+ ```
214
+
215
+ The webhook receives plain JSON:
216
+
217
+ ```json
218
+ {
219
+ "event": "scenario_failed",
220
+ "scenario": "Login and fetch orders",
221
+ "previous_status": "HEALTHY",
222
+ "status": "FAILING",
223
+ "details": "- Fetch profile: Status code mismatch: expected 200, got 500"
224
+ }
225
+ ```
226
+
227
+ `event` is `scenario_failed` or `scenario_recovered`.
228
+
229
+ ## Use it in CI
230
+
231
+ Proofrun exits with `0` when every scenario passes and `1` when any fails, so it
232
+ works as a smoke test or a scheduled check. For example, with GitHub Actions:
233
+
234
+ ```yaml
235
+ name: API check
236
+ on:
237
+ schedule:
238
+ - cron: "*/15 * * * *"
239
+ workflow_dispatch:
240
+
241
+ jobs:
242
+ proofrun:
243
+ runs-on: ubuntu-latest
244
+ steps:
245
+ - uses: actions/checkout@v4
246
+ - uses: actions/setup-python@v5
247
+ with:
248
+ python-version: "3.12"
249
+ - run: pip install proofrun
250
+ - run: proofrun monitors/api.yaml
251
+ ```
252
+
253
+ A failing check fails the workflow run, and GitHub notifies you the way it
254
+ notifies you about any failed workflow.
255
+
256
+ ## CLI
257
+
258
+ ```bash
259
+ proofrun <config.yaml> # run once and exit (default)
260
+ proofrun <config.yaml> --daemon # run continuously
261
+ proofrun <config.yaml> --interval 30 # override the daemon interval (seconds)
262
+ proofrun <config.yaml> --daemon --webhook-url <url>
263
+ proofrun --version
264
+ ```
265
+
266
+ ## License
267
+
268
+ [MIT](LICENSE)