summonpot 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.
@@ -0,0 +1,41 @@
1
+ # Keep dependencies up to date
2
+ # https://docs.github.com/en/code-security/dependabot/dependabot-version-updates
3
+
4
+ version: 2
5
+ updates:
6
+ - package-ecosystem: "github-actions"
7
+ directory: "/"
8
+ schedule:
9
+ interval: "weekly"
10
+ day: "monday"
11
+ commit-message:
12
+ prefix: "chore"
13
+ include: "scope"
14
+ labels:
15
+ - "dependencies"
16
+
17
+ - package-ecosystem: "uv"
18
+ directory: "/"
19
+ schedule:
20
+ interval: "weekly"
21
+ day: "monday"
22
+ commit-message:
23
+ prefix: "chore"
24
+ include: "scope"
25
+ labels:
26
+ - "dependencies"
27
+ groups:
28
+ dev-dependencies:
29
+ patterns:
30
+ - "pytest*"
31
+ - "ruff"
32
+ - "pyright"
33
+ - "pre-commit"
34
+ - "towncrier"
35
+ runtime-dependencies:
36
+ patterns:
37
+ - "httpx"
38
+ - "pydantic"
39
+ - "fastapi*"
40
+ - "uvicorn"
41
+ - "typer"
@@ -0,0 +1,34 @@
1
+ # Auto-label rules for pull requests.
2
+
3
+ ci:
4
+ - changed-files:
5
+ - any-glob-to-any-file:
6
+ - ".github/**/*"
7
+ - "Makefile"
8
+
9
+ documentation:
10
+ - changed-files:
11
+ - any-glob-to-any-file:
12
+ - "README.md"
13
+ - "CONTRIBUTING.md"
14
+ - "CHANGELOG.md"
15
+ - "docs/**/*"
16
+
17
+ source:
18
+ - changed-files:
19
+ - any-glob-to-any-file: "src/**/*"
20
+
21
+ tests:
22
+ - changed-files:
23
+ - any-glob-to-any-file: "tests/**/*"
24
+
25
+ dependencies:
26
+ - changed-files:
27
+ - any-glob-to-any-file:
28
+ - "pyproject.toml"
29
+ - "uv.lock"
30
+ - ".github/dependabot.yml"
31
+
32
+ examples:
33
+ - changed-files:
34
+ - any-glob-to-any-file: "examples/**/*"
@@ -0,0 +1,41 @@
1
+ name: Changelog
2
+
3
+ on:
4
+ pull_request:
5
+ branches: [main]
6
+ types: [opened, synchronize, reopened, labeled, unlabeled]
7
+
8
+ permissions:
9
+ contents: read
10
+ pull-requests: read # Required to inspect the pull request's changed files.
11
+
12
+ concurrency:
13
+ group: changelog-${{ github.event.pull_request.number }}
14
+ cancel-in-progress: true
15
+
16
+ jobs:
17
+ changelog:
18
+ name: changelog
19
+ runs-on: ubuntu-latest
20
+ timeout-minutes: 5
21
+ env:
22
+ GH_TOKEN: ${{ github.token }}
23
+ PR_NUMBER: ${{ github.event.pull_request.number }}
24
+ REPOSITORY: ${{ github.repository }}
25
+ steps:
26
+ - name: Skip non-user-facing changes
27
+ if: contains(github.event.pull_request.labels.*.name, 'skip-changelog')
28
+ run: echo "skip-changelog label present"
29
+ - name: Require this PR's changelog fragment
30
+ if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip-changelog') }}
31
+ shell: bash
32
+ run: |
33
+ set -euo pipefail
34
+ pattern="^changelog\.d/${PR_NUMBER}\.(added|changed|deprecated|removed|fixed)\.md$"
35
+ if gh api "repos/${REPOSITORY}/pulls/${PR_NUMBER}/files" \
36
+ --paginate --jq '.[].filename' | grep -Eq "$pattern"; then
37
+ echo "changelog fragment found for PR #${PR_NUMBER}"
38
+ exit 0
39
+ fi
40
+ echo "::error::no changelog fragment for PR #${PR_NUMBER} — add changelog.d/${PR_NUMBER}.<type>.md (see changelog.d/README.md), or apply the 'skip-changelog' label if this change is not user-facing"
41
+ exit 1
@@ -0,0 +1,96 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+ workflow_call:
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ concurrency:
14
+ group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
15
+ cancel-in-progress: true
16
+
17
+ jobs:
18
+ lint:
19
+ name: lint
20
+ runs-on: ubuntu-latest
21
+ timeout-minutes: 10
22
+ steps:
23
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
24
+ with:
25
+ persist-credentials: false
26
+ - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
27
+ - run: uv sync --all-extras --locked
28
+ - run: uv run ruff check src/ tests/
29
+ - run: uv run ruff format --check src/ tests/
30
+
31
+ typecheck:
32
+ name: typecheck
33
+ runs-on: ubuntu-latest
34
+ timeout-minutes: 10
35
+ steps:
36
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
37
+ with:
38
+ persist-credentials: false
39
+ - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
40
+ - run: uv sync --all-extras --locked
41
+ - run: uv run pyright src/ tests/
42
+
43
+ test:
44
+ name: test (py${{ matrix.python-version }})
45
+ runs-on: ubuntu-latest
46
+ timeout-minutes: 15
47
+ strategy:
48
+ fail-fast: false
49
+ matrix:
50
+ python-version: ["3.11", "3.12", "3.13"]
51
+ steps:
52
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
53
+ with:
54
+ persist-credentials: false
55
+ - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
56
+ with:
57
+ python-version: ${{ matrix.python-version }}
58
+ - run: uv sync --all-extras --locked
59
+ - run: >-
60
+ uv run pytest tests/ -v
61
+ --cov=src/summonpot
62
+ --cov-report=term-missing
63
+ --cov-fail-under=50
64
+
65
+ build:
66
+ name: build
67
+ runs-on: ubuntu-latest
68
+ timeout-minutes: 10
69
+ steps:
70
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
71
+ with:
72
+ persist-credentials: false
73
+ - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
74
+ - run: uv build
75
+ - name: Verify distribution artifacts
76
+ run: |
77
+ python3 - <<'PY'
78
+ import glob
79
+ import tarfile
80
+ import zipfile
81
+
82
+ wheels = glob.glob("dist/*.whl")
83
+ sdists = glob.glob("dist/*.tar.gz")
84
+ assert len(wheels) == 1, f"expected one wheel, found {wheels}"
85
+ assert len(sdists) == 1, f"expected one sdist, found {sdists}"
86
+
87
+ with zipfile.ZipFile(wheels[0]) as archive:
88
+ names = archive.namelist()
89
+ assert "summonpot/__init__.py" in names, names
90
+
91
+ with tarfile.open(sdists[0]) as archive:
92
+ names = archive.getnames()
93
+ assert any(name.endswith("/src/summonpot/__init__.py") for name in names), names
94
+
95
+ print(f"verified {wheels[0]} and {sdists[0]}")
96
+ PY
@@ -0,0 +1,26 @@
1
+ name: Auto-label PRs
2
+
3
+ on:
4
+ pull_request_target:
5
+ types: [opened, synchronize, reopened, ready_for_review]
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ concurrency:
11
+ group: labeler-${{ github.event.pull_request.number }}
12
+ cancel-in-progress: true
13
+
14
+ jobs:
15
+ label:
16
+ name: label
17
+ runs-on: ubuntu-latest
18
+ timeout-minutes: 5
19
+ permissions:
20
+ contents: read
21
+ pull-requests: write # Required to apply labels to pull requests.
22
+ steps:
23
+ - uses: actions/labeler@bf12e9b00b37c5c0ca2b87b79b2daf7891dbda13 # v7.0.0
24
+ with:
25
+ repo-token: ${{ github.token }}
26
+ sync-labels: true
@@ -0,0 +1,176 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ concurrency:
11
+ group: release-${{ github.ref }}
12
+ cancel-in-progress: false
13
+
14
+ jobs:
15
+ ci:
16
+ name: CI
17
+ uses: ./.github/workflows/ci.yml
18
+
19
+ verify-version:
20
+ name: verify version and changelog
21
+ runs-on: ubuntu-latest
22
+ timeout-minutes: 5
23
+ steps:
24
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
25
+ with:
26
+ persist-credentials: false
27
+ - name: Verify tag, package version, and changelog
28
+ env:
29
+ RELEASE_TAG: ${{ github.ref_name }}
30
+ run: |
31
+ python3 - <<'PY'
32
+ import pathlib
33
+ import re
34
+ import sys
35
+ import tomllib
36
+ import os
37
+
38
+ tag = os.environ["RELEASE_TAG"]
39
+ if not tag.startswith("v") or len(tag) == 1:
40
+ print(f"::error::release tag must be v<version>, got {tag!r}")
41
+ sys.exit(1)
42
+
43
+ version = tag[1:]
44
+ package_version = tomllib.loads(
45
+ pathlib.Path("pyproject.toml").read_text()
46
+ )["project"]["version"]
47
+ if version != package_version:
48
+ print(
49
+ f"::error::tag {tag} does not match pyproject.toml version "
50
+ f"{package_version} — run 'uv version {version}' on main first"
51
+ )
52
+ sys.exit(1)
53
+
54
+ changelog = pathlib.Path("CHANGELOG.md").read_text()
55
+ heading = re.compile(
56
+ rf"^## \[{re.escape(version)}\](?:\s|$)", re.MULTILINE
57
+ )
58
+ if heading.search(changelog) is None:
59
+ print(
60
+ f"::error::CHANGELOG.md has no exact '## [{version}]' section — "
61
+ "run 'make changelog' and commit it before tagging"
62
+ )
63
+ sys.exit(1)
64
+
65
+ print(f"{tag} matches pyproject.toml and CHANGELOG.md")
66
+ PY
67
+
68
+ build:
69
+ name: build release artifacts
70
+ needs: [ci, verify-version]
71
+ runs-on: ubuntu-latest
72
+ timeout-minutes: 10
73
+ steps:
74
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
75
+ with:
76
+ persist-credentials: false
77
+ - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
78
+ with:
79
+ enable-cache: false
80
+ - run: uv build
81
+ - name: Verify release artifacts
82
+ run: |
83
+ python3 - <<'PY'
84
+ import glob
85
+ import tarfile
86
+ import zipfile
87
+
88
+ wheels = glob.glob("dist/*.whl")
89
+ sdists = glob.glob("dist/*.tar.gz")
90
+ assert len(wheels) == 1, f"expected one wheel, found {wheels}"
91
+ assert len(sdists) == 1, f"expected one sdist, found {sdists}"
92
+ with zipfile.ZipFile(wheels[0]) as archive:
93
+ assert "summonpot/__init__.py" in archive.namelist()
94
+ with tarfile.open(sdists[0]) as archive:
95
+ assert any(
96
+ name.endswith("/src/summonpot/__init__.py")
97
+ for name in archive.getnames()
98
+ )
99
+ print(f"verified {wheels[0]} and {sdists[0]}")
100
+ PY
101
+ - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
102
+ with:
103
+ name: dist
104
+ path: dist/
105
+ if-no-files-found: error
106
+
107
+ publish:
108
+ name: publish to PyPI
109
+ needs: build
110
+ runs-on: ubuntu-latest
111
+ timeout-minutes: 10
112
+ environment: pypi
113
+ permissions:
114
+ contents: read
115
+ id-token: write # Required for PyPI trusted publishing via OIDC.
116
+ steps:
117
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
118
+ with:
119
+ name: dist
120
+ path: dist/
121
+ - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
122
+ with:
123
+ attestations: true
124
+
125
+ github-release:
126
+ name: create GitHub Release
127
+ needs: publish
128
+ runs-on: ubuntu-latest
129
+ timeout-minutes: 10
130
+ permissions:
131
+ contents: write # Required to create the GitHub Release and upload assets.
132
+ steps:
133
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
134
+ with:
135
+ persist-credentials: false
136
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
137
+ with:
138
+ name: dist
139
+ path: dist/
140
+ - name: Extract release notes from CHANGELOG
141
+ env:
142
+ RELEASE_VERSION: ${{ github.ref_name }}
143
+ run: |
144
+ python3 - <<'PY' > release-notes.md
145
+ import os
146
+ import pathlib
147
+ import re
148
+ import sys
149
+
150
+ version = os.environ["RELEASE_VERSION"].removeprefix("v")
151
+ text = pathlib.Path("CHANGELOG.md").read_text()
152
+ match = re.search(
153
+ rf"^## \[{re.escape(version)}\][^\n]*\n(.*?)(?=^## \[|\Z)",
154
+ text,
155
+ re.MULTILINE | re.DOTALL,
156
+ )
157
+ if match is None:
158
+ sys.exit(f"no exact '## [{version}]' section in CHANGELOG.md")
159
+ print(match.group(1).strip())
160
+ PY
161
+ cat release-notes.md
162
+ - name: Create GitHub Release
163
+ env:
164
+ GH_TOKEN: ${{ github.token }}
165
+ RELEASE_TAG: ${{ github.ref_name }}
166
+ run: |
167
+ set -euo pipefail
168
+ version="${RELEASE_TAG#v}"
169
+ args=()
170
+ case "$version" in *-*) args+=(--prerelease) ;; esac
171
+ gh release create "$RELEASE_TAG" \
172
+ --title "$RELEASE_TAG" \
173
+ --notes-file release-notes.md \
174
+ --verify-tag \
175
+ "${args[@]}" \
176
+ dist/*
@@ -0,0 +1,44 @@
1
+ name: Stale issue and PR management
2
+
3
+ on:
4
+ schedule:
5
+ - cron: "30 1 * * *" # Daily at 1:30 UTC.
6
+ workflow_dispatch:
7
+
8
+ permissions: {}
9
+
10
+ concurrency:
11
+ group: stale-management
12
+ cancel-in-progress: false
13
+
14
+ jobs:
15
+ stale:
16
+ name: stale
17
+ runs-on: ubuntu-latest
18
+ timeout-minutes: 10
19
+ permissions:
20
+ issues: write # Required to label, comment on, and close stale issues.
21
+ pull-requests: write # Required to manage stale pull requests.
22
+ steps:
23
+ - uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0
24
+ with:
25
+ stale-issue-message: >
26
+ This issue has been automatically marked as stale because it has not
27
+ had recent activity. It will be closed in 14 days if no further
28
+ activity occurs.
29
+ stale-pr-message: >
30
+ This pull request has been automatically marked as stale because it
31
+ has not had recent activity. It will be closed in 14 days if no
32
+ further activity occurs.
33
+ close-issue-message: >
34
+ This issue has been automatically closed due to inactivity.
35
+ close-pr-message: >
36
+ This pull request has been automatically closed due to inactivity.
37
+ stale-issue-label: stale
38
+ stale-pr-label: stale
39
+ days-before-stale: 60
40
+ days-before-close: 14
41
+ days-before-pr-stale: 30
42
+ days-before-pr-close: 14
43
+ exempt-issue-labels: enhancement,pinned,security
44
+ exempt-pr-labels: pinned,security,dependencies
@@ -0,0 +1,31 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.egg-info/
6
+ *.egg
7
+ dist/
8
+ build/
9
+
10
+ # Virtual environments
11
+ .venv/
12
+ venv/
13
+
14
+ # Testing
15
+ .pytest_cache/
16
+ .coverage
17
+ htmlcov/
18
+
19
+ # Type checking / linting
20
+ .mypy_cache/
21
+ .ruff_cache/
22
+
23
+ # OS
24
+ .DS_Store
25
+
26
+ # IDE
27
+ .vscode/
28
+ .idea/
29
+ *.swp
30
+ *.swo
31
+ *~
@@ -0,0 +1,27 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ Unreleased changes live as fragment files in [`changelog.d/`](changelog.d/) until a
9
+ release assembles them here — run `make changelog-draft` to preview them.
10
+
11
+ <!-- towncrier release notes start -->
12
+
13
+ ## [0.1.0] - 2026-08-10
14
+
15
+ ### Added
16
+
17
+ - Add continuous integration, packaging, changelog tooling, and developer Makefile targets. ([#1](https://github.com/tugrulguner/summonpot/pull/1))
18
+ - Add the initial summonpot framework with agentic API endpoints, automatic request schemas, tool calling, OpenAI-compatible model providers, CLI serving, and generated OpenAPI documentation. ([#2](https://github.com/tugrulguner/summonpot/pull/2))
19
+
20
+ ### Changed
21
+
22
+ - Add comprehensive framework documentation, quick-start instructions, and usage examples. ([#3](https://github.com/tugrulguner/summonpot/pull/3))
23
+ - Add release automation, dependency updates, pull-request labeling, and stale issue management. ([#4](https://github.com/tugrulguner/summonpot/pull/4))
24
+
25
+ ### Fixed
26
+
27
+ - OpenAPI metadata now derives its version from installed package metadata, keeping `pyproject.toml` as the single source updated by `uv version`. ([#5](https://github.com/tugrulguner/summonpot/pull/5))
@@ -0,0 +1,62 @@
1
+ # Contributing to summonpot
2
+
3
+ Thanks for helping build summonpot.
4
+
5
+ ## Development setup
6
+
7
+ ```bash
8
+ git clone https://github.com/tugrulguner/summonpot.git
9
+ cd summonpot
10
+ uv sync --all-extras
11
+ make check
12
+ ```
13
+
14
+ Use `make format` before committing. User-facing changes need a Towncrier fragment named after the pull request:
15
+
16
+ ```text
17
+ changelog.d/<pr-number>.<type>.md
18
+ ```
19
+
20
+ Supported types are `added`, `changed`, `deprecated`, `removed`, and `fixed`.
21
+
22
+ ## Pull requests
23
+
24
+ 1. Branch from the latest `main`.
25
+ 2. Keep each pull request focused on one concern.
26
+ 3. Add or update tests for behavior changes.
27
+ 4. Add a changelog fragment for user-facing changes, or use `skip-changelog` when the change is not user-facing.
28
+ 5. Run `make check` and ensure every required GitHub check passes.
29
+
30
+ ## Releasing
31
+
32
+ The version lives in exactly one place: `version` in `pyproject.toml`. `summonpot.__version__`, `summonpot --version`, generated OpenAPI metadata, and `uv.lock` all derive from that package version. Never edit those derived values by hand.
33
+
34
+ Use uv to update the single version source and lockfile together:
35
+
36
+ ```bash
37
+ uv version 0.2.0
38
+ # or
39
+ uv version --bump patch
40
+ uv version --bump minor
41
+ uv version --bump major
42
+ ```
43
+
44
+ Then prepare the release notes from the existing Towncrier fragments:
45
+
46
+ ```bash
47
+ make changelog-draft
48
+ make changelog
49
+ make check
50
+ ```
51
+
52
+ Open a release pull request containing the `pyproject.toml`, `uv.lock`, generated `CHANGELOG.md`, and fragment removals. After that pull request is reviewed, green, and merged, tag the exact version reported by uv:
53
+
54
+ ```bash
55
+ version="$(uv version --short)"
56
+ git tag "v${version}"
57
+ git push origin "v${version}"
58
+ ```
59
+
60
+ The tag triggers `.github/workflows/release.yml`. It reruns CI, verifies that the tag, package metadata, and changelog section all match, builds and inspects the wheel and source distribution, publishes them to PyPI through trusted publishing, and then creates the GitHub Release from the same changelog section with the same artifacts attached.
61
+
62
+ Nothing in a release is hand-written twice: the version comes from `pyproject.toml`, the lockfile is updated by uv, runtime version surfaces read package metadata, and release notes come from Towncrier and `CHANGELOG.md`.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tugrul Guner
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,34 @@
1
+ .PHONY: install test lint format typecheck check changelog-draft changelog build clean
2
+
3
+ install:
4
+ uv sync --all-extras
5
+
6
+ test:
7
+ uv sync --all-extras --reinstall-package summonpot
8
+ uv run pytest tests/ -v
9
+
10
+ lint:
11
+ uv run ruff check src/ tests/
12
+ uv run ruff format --check src/ tests/
13
+
14
+ typecheck:
15
+ uv run pyright src/ tests/
16
+
17
+ format:
18
+ uv run ruff check --fix src/ tests/
19
+ uv run ruff format src/ tests/
20
+
21
+ check: lint typecheck test
22
+
23
+ changelog-draft:
24
+ uv run towncrier build --draft --version $$(uv version --short)
25
+
26
+ changelog:
27
+ uv run towncrier build --yes --version $$(uv version --short)
28
+
29
+ build:
30
+ uv build
31
+
32
+ clean:
33
+ rm -rf dist/ build/ *.egg-info .pytest_cache .ruff_cache
34
+ find . -type d -name __pycache__ -exec rm -rf {} +