update-kit 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,19 @@
1
+ version: 2
2
+ updates:
3
+ # Actions stay pinned by commit SHA with a "# vX.Y.Z" comment; Dependabot
4
+ # understands that form and bumps both the SHA and the comment.
5
+ - package-ecosystem: github-actions
6
+ directory: /
7
+ schedule:
8
+ interval: weekly
9
+ commit-message:
10
+ prefix: "chore(deps)"
11
+ - package-ecosystem: uv
12
+ directory: /
13
+ schedule:
14
+ interval: weekly
15
+ commit-message:
16
+ prefix: "chore(deps)"
17
+ groups:
18
+ minor-and-patch:
19
+ update-types: [minor, patch]
@@ -0,0 +1,62 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ concurrency:
11
+ group: ci-${{ github.workflow }}-${{ github.ref }}
12
+ cancel-in-progress: true
13
+
14
+ env:
15
+ PYTHONUTF8: "1"
16
+
17
+ jobs:
18
+ test:
19
+ name: Python 3.10 / ${{ matrix.os }}
20
+ timeout-minutes: 15
21
+ strategy:
22
+ fail-fast: false
23
+ matrix:
24
+ os: [macos-latest, ubuntu-latest, windows-latest]
25
+ runs-on: ${{ matrix.os }}
26
+ steps:
27
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
28
+ with:
29
+ persist-credentials: false
30
+ - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
31
+ with:
32
+ python-version: "3.10"
33
+ enable-cache: true
34
+ cache-dependency-glob: uv.lock
35
+ - name: Install locked dependencies
36
+ run: uv sync --locked --all-groups
37
+ - name: Lint
38
+ run: uv run ruff check .
39
+ - name: Test
40
+ run: uv run pytest -v
41
+ - name: Build distributions
42
+ run: uv build
43
+ - name: Validate wheel is importable standalone
44
+ shell: python
45
+ run: |
46
+ import os
47
+ import pathlib
48
+ import subprocess
49
+ import tempfile
50
+
51
+ wheel, = pathlib.Path("dist").glob("*.whl")
52
+ with tempfile.TemporaryDirectory() as directory:
53
+ environment = pathlib.Path(directory)
54
+ subprocess.run(["uv", "venv", environment], check=True)
55
+ scripts = environment / ("Scripts" if os.name == "nt" else "bin")
56
+ python = scripts / ("python.exe" if os.name == "nt" else "python")
57
+ subprocess.run(
58
+ ["uv", "pip", "install", "--python", python, "--no-index", wheel],
59
+ check=True,
60
+ )
61
+ subprocess.run([python, "-I", "-c", "import update_kit"], check=True)
62
+ print("update_kit imports standalone OK")
@@ -0,0 +1,63 @@
1
+ name: Release
2
+
3
+ # PyPI Trusted Publishing binds to this filename: keep it `release.yml` in every repo.
4
+ on:
5
+ push:
6
+ tags:
7
+ - "v*"
8
+
9
+ permissions:
10
+ contents: write
11
+ id-token: write
12
+
13
+ concurrency:
14
+ group: release-${{ github.ref }}
15
+
16
+ env:
17
+ PYTHONUTF8: "1"
18
+
19
+ jobs:
20
+ release:
21
+ runs-on: ubuntu-latest
22
+ timeout-minutes: 15
23
+ steps:
24
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
25
+ with:
26
+ persist-credentials: false
27
+ - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
28
+ with:
29
+ python-version: "3.10"
30
+ enable-cache: true
31
+ cache-dependency-glob: uv.lock
32
+ - name: Install locked dependencies
33
+ run: uv sync --locked --all-groups
34
+ - name: Check, test, and build
35
+ run: |
36
+ uv run ruff check .
37
+ uv run pytest -v
38
+ uv build
39
+ - name: Validate release wheel
40
+ run: |
41
+ uv venv .release-venv
42
+ uv pip install --python .release-venv/bin/python --no-index dist/*.whl
43
+ reported="$(.release-venv/bin/python -I -c 'import importlib.metadata as m; print(m.version("update-kit"))')"
44
+ if [ "$reported" != "${GITHUB_REF_NAME#v}" ]; then
45
+ echo "::error::wheel reports '$reported', expected '${GITHUB_REF_NAME#v}'"
46
+ exit 1
47
+ fi
48
+ - name: Publish to PyPI
49
+ run: uv publish --trusted-publishing always
50
+
51
+ # After publish: uv publish uploads dist/*, and SHA256SUMS is not a distribution.
52
+ - name: Create checksums
53
+ run: sha256sum dist/* > dist/SHA256SUMS
54
+ - name: Create GitHub release
55
+ env:
56
+ GH_TOKEN: ${{ github.token }}
57
+ run: |
58
+ gh api "repos/${GITHUB_REPOSITORY}/git/refs/tags/$GITHUB_REF_NAME" >/dev/null
59
+ if gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1; then
60
+ gh release upload "$GITHUB_REF_NAME" dist/* --clobber
61
+ else
62
+ gh release create "$GITHUB_REF_NAME" dist/* --generate-notes --title "$GITHUB_REF_NAME" --verify-tag
63
+ fi
@@ -0,0 +1,7 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ dist/
5
+ .ruff_cache/
6
+ .coverage
7
+ .pytest_cache/
@@ -0,0 +1,46 @@
1
+ ## [0.1.0] - 2026-10-15
2
+
3
+ ### 🚀 Features
4
+
5
+ - Add update-kit 0.1.0
6
+
7
+ ### 🐛 Bug Fixes
8
+
9
+ - Harden update chain, tag parsing and cache repair
10
+ - **security:** Validate repo URL, end-of-options before URL, run git/install from home
11
+ - Install step never prompts for git credentials; test: kill 4 surviving mutants
12
+
13
+ ### ⚙️ Miscellaneous Tasks
14
+
15
+ - Add tag-triggered PyPI release workflow
16
+ ## [Unreleased]
17
+
18
+ ### 🔒 Security
19
+
20
+ - The install step (uv / pipx, and the Windows detached chain) runs with `GIT_TERMINAL_PROMPT=0`
21
+ and `GCM_INTERACTIVE=never`, so the installer's own git fetch fails instead of blocking on a
22
+ credential prompt
23
+
24
+ ## [0.1.0] - 2026-10-15
25
+
26
+ ### 🚀 Features
27
+
28
+ - Initial release: `latest_tag`, `install_kind`, `update`, `check`, `hint`, `should_hint`,
29
+ `normalize_lang` — self-update for git-tag-released CLIs installed with uv tool or pipx,
30
+ en + zh-TW messages, cached background check in `$HOME/.update-kit/state.json`
31
+
32
+ ### 🔒 Security
33
+
34
+ - Repo URL (`url=` / `UPDATE_KIT_REPO_URL`) is validated (`https://`, `ssh://`, `file://`,
35
+ `git@host:path`; no leading `-`, control chars, `ext::` or embedded credentials) and passed to
36
+ `git ls-remote` after `--` with `protocol.ext.allow=never`, so `--upload-pack=...` can no longer
37
+ run code from the background `check()`
38
+ - The POSIX install and the Windows detached chain run with the home directory as cwd, so
39
+ `cmd.exe` no longer resolves `uv` / the after-steps from a planted `uv.bat` / `crw.exe` in the
40
+ caller's cwd; `git ls-remote` also runs from home. Only `https`, `ssh`, `file` schemes (lowercase)
41
+ are accepted now (plain `http://` is rejected)
42
+ - git stderr is stripped of control characters and URL userinfo before printing; `update` names
43
+ `UPDATE_KIT_REPO_URL` when it is in effect (en + zh-TW)
44
+ - Tag parsing: version parts capped at 9 digits, exact `\trefs/tags/` split, no whitespace
45
+ tolerance on tag names; `check()` only returns strict tags
46
+ - `check()` caches by resolved source URL, so an override can't poison the default entry
@@ -0,0 +1,33 @@
1
+ # CLAUDE.md
2
+
3
+ ## Frozen public API
4
+
5
+ `update_kit`'s public functions (`latest_tag`, `install_kind`, `update`, `check`, `hint`,
6
+ `should_hint`, `normalize_lang`, `UpdateError`) are consumed by aicp, ai-accounts,
7
+ codex-reset-watch and agent-toggle. Signatures, defaults, return codes and the
8
+ `$HOME/.update-kit/state.json` layout are a contract: changing any of them is a breaking
9
+ change (`type(scope)!:` + `BREAKING CHANGE:` footer, minor bump while 0.x).
10
+
11
+ ## Rules
12
+
13
+ - Stdlib only. No runtime dependencies, no `packaging`.
14
+ - Every user-visible string exists in `en` and `zh-TW` in `_MESSAGES`.
15
+ - Never prompt; never hit the network in tests (use a `file://` repo in `tmp_path` or a fake `run`).
16
+ - Keep `README.md` and `CHANGELOG.md` in sync with user-visible changes.
17
+
18
+ ## Commands
19
+
20
+ - Test: `uv run pytest`
21
+ - Lint: `uv run ruff check .`
22
+ - Build: `uv build`
23
+
24
+ ## Cross-platform
25
+
26
+ Code must run on macOS, Linux, and Windows (paths, separators, shell commands, line
27
+ endings, `$HOME` vs `%USERPROFILE%`, file locking, symlinks, terminal/ANSI). Guard
28
+ platform-specific code and say so.
29
+
30
+ - Reuse the shared platform helpers first: `src/update_kit/__init__.py` (`_IS_WINDOWS`, `_windows_chain`, `_spawn_detached`).
31
+ - Text I/O always passes `encoding="utf-8"` (Windows defaults to cp1252).
32
+ - Windows is verified by CI's `windows-latest` job, not locally; POSIX-only tests are
33
+ skipped on Windows (`os.name == "nt"`).
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Wes Kao
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 OF THE SOFTWARE.
@@ -0,0 +1,105 @@
1
+ Metadata-Version: 2.5
2
+ Name: update-kit
3
+ Version: 0.1.0
4
+ Summary: Self-update for git-tag-released Python CLIs (uv tool / pipx). Stdlib only.
5
+ License: MIT
6
+ License-File: LICENSE
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+
10
+ # update-kit
11
+
12
+ Self-update for Python CLIs that are released as git tags (`vX.Y.Z` or `X.Y.Z`) and installed with
13
+ `uv tool` or `pipx`. Stdlib only, Python ≥ 3.10, macOS / Linux / Windows.
14
+
15
+ - `<cmd> update` installs the newest release tag — no prompts, ever.
16
+ - A cached background check prints a one-line hint on an interactive terminal only.
17
+ - Every message exists in English and Traditional Chinese (`zh-TW`).
18
+
19
+ ## Install
20
+
21
+ ```toml
22
+ # consumer pyproject.toml
23
+ dependencies = ["update-kit>=0.1,<0.2"]
24
+ ```
25
+
26
+ ## Wiring a consumer CLI
27
+
28
+ ```python
29
+ import sys
30
+
31
+ import update_kit
32
+
33
+ from my_cli import __version__
34
+
35
+ APP, REPO, DIST = "my-cli", "owner/my-cli", "my-cli"
36
+
37
+
38
+ def main(argv=None):
39
+ argv = sys.argv[1:] if argv is None else argv
40
+ lang = "zh-TW" # the app's own current language setting
41
+
42
+ # `my-cli update` and `my-cli --update`
43
+ if argv[:1] == ["update"] or "--update" in argv:
44
+ sys.exit(update_kit.update(
45
+ app=APP, repo=REPO, dist=DIST, lang=lang,
46
+ after=[["my-cli", "migrate"]], # optional: runs with the NEW binary
47
+ ))
48
+
49
+ # Background hint: stderr, TTY only (never under launchd/timers/--json/pipes).
50
+ if update_kit.should_hint():
51
+ latest = update_kit.check(repo=REPO, current=__version__)
52
+ if latest:
53
+ print(update_kit.hint("my-cli", latest, __version__, lang), file=sys.stderr)
54
+ ...
55
+ ```
56
+
57
+ ## API
58
+
59
+ | Function | Does |
60
+ |---|---|
61
+ | `latest_tag(repo, *, timeout=5, url=None) -> str` | Newest strict `v?X.Y.Z` tag (ASCII digits) via `git ls-remote --tags --refs`. Prereleases and other names are ignored. Raises `UpdateError` (offline, no tags, git missing). |
62
+ | `install_kind(dist) -> (kind, detail)` | `"dev"` (PEP 610 local/editable dir, detail = the dir), `"uv"` / `"pipx"` (from `sys.prefix`), else `"unknown"`. |
63
+ | `update(*, app, repo, dist, current=None, lang="en", after=(), out=sys.stderr, run=subprocess.run, url=None) -> int` | Exit code: **0** updated or already up to date · **1** couldn't check (offline/rate limit) or install/after-step failed · **2** dev install (use `git pull`) or unknown install (manual command printed). |
64
+ | `check(*, repo, current, cache_path=None, ttl=86400, timeout=1.5, url=None) -> str \| None` | Newer tag or `None`. Never raises. Results — failures too — are cached for `ttl` in `$HOME/.update-kit/state.json`, keyed by the resolved source URL. |
65
+ | `hint(cmd, latest, current, lang="en") -> str` | `my-cli v1.2.0 is available (you have 1.1.0) — run` `` `my-cli update` `` |
66
+ | `should_hint(stream=sys.stderr) -> bool` | `True` only when `stream` is a TTY. |
67
+ | `normalize_lang(value) -> str` | `zh_TW` / `zh-tw` / `zh-Hant` / `zh_HK` → `"zh-TW"`; anything else → `"en"`. |
68
+
69
+ Install commands used by `update`:
70
+
71
+ - uv: `uv tool install --force --no-sources git+<url>@<tag>`
72
+ - pipx: `pipx install --force git+<url>@<tag>`
73
+
74
+ `<url>` is `url=`, else `$UPDATE_KIT_REPO_URL`, else `https://github.com/<repo>.git`
75
+ (`file://` works, which is how the tests run without a network). Only `https://`, `ssh://`,
76
+ `file://` and `git@host:path` are accepted (`ssh://git@host/...` is fine, `ssh://user:pass@` is not; `git@host:path` is installed as `ssh://git@host/path`); a leading `-`, whitespace/control characters,
77
+ `ext::` or credentials in the URL (`user:pass@`) raise `UpdateError` (`update` returns 1,
78
+ `check` returns `None`). When `$UPDATE_KIT_REPO_URL` is in effect, `update` prints one line
79
+ naming it. `git ls-remote` runs as `git -c protocol.ext.allow=never ls-remote --tags --refs -- <url>`
80
+ from the home directory, and so does the install; git's stderr is stripped of control characters.
81
+
82
+ **Windows:** the running `.exe` is locked, so `update` starts the install (and the `after`
83
+ steps, chained with `&&`) in a detached `cmd.exe` that waits a few seconds for the app to
84
+ exit, returns 0 and says the update continues in the background. Output goes to
85
+ `%USERPROFILE%\.update-kit\<app>-update.log`. Unlike POSIX (where every after-step runs and
86
+ any failure makes the result 1), the Windows chain stops at the first failing step and the
87
+ outcome is only in the log. An install whose arguments or log path contain a `cmd.exe`
88
+ metacharacter (`& | < > ^ % " ( ) !`) is refused with exit 1 instead of being spawned.
89
+
90
+ `git ls-remote` never prompts: `GIT_TERMINAL_PROMPT=0`, and `GIT_SSH_COMMAND` defaults to
91
+ `ssh -o BatchMode=yes` when unset. The install step (uv / pipx) also runs with
92
+ `GIT_TERMINAL_PROMPT=0` and `GCM_INTERACTIVE=never`.
93
+
94
+ ## Development
95
+
96
+ ```bash
97
+ uv sync
98
+ uv run pytest
99
+ uv run ruff check .
100
+ uv build
101
+ ```
102
+
103
+ ## License
104
+
105
+ MIT
@@ -0,0 +1,96 @@
1
+ # update-kit
2
+
3
+ Self-update for Python CLIs that are released as git tags (`vX.Y.Z` or `X.Y.Z`) and installed with
4
+ `uv tool` or `pipx`. Stdlib only, Python ≥ 3.10, macOS / Linux / Windows.
5
+
6
+ - `<cmd> update` installs the newest release tag — no prompts, ever.
7
+ - A cached background check prints a one-line hint on an interactive terminal only.
8
+ - Every message exists in English and Traditional Chinese (`zh-TW`).
9
+
10
+ ## Install
11
+
12
+ ```toml
13
+ # consumer pyproject.toml
14
+ dependencies = ["update-kit>=0.1,<0.2"]
15
+ ```
16
+
17
+ ## Wiring a consumer CLI
18
+
19
+ ```python
20
+ import sys
21
+
22
+ import update_kit
23
+
24
+ from my_cli import __version__
25
+
26
+ APP, REPO, DIST = "my-cli", "owner/my-cli", "my-cli"
27
+
28
+
29
+ def main(argv=None):
30
+ argv = sys.argv[1:] if argv is None else argv
31
+ lang = "zh-TW" # the app's own current language setting
32
+
33
+ # `my-cli update` and `my-cli --update`
34
+ if argv[:1] == ["update"] or "--update" in argv:
35
+ sys.exit(update_kit.update(
36
+ app=APP, repo=REPO, dist=DIST, lang=lang,
37
+ after=[["my-cli", "migrate"]], # optional: runs with the NEW binary
38
+ ))
39
+
40
+ # Background hint: stderr, TTY only (never under launchd/timers/--json/pipes).
41
+ if update_kit.should_hint():
42
+ latest = update_kit.check(repo=REPO, current=__version__)
43
+ if latest:
44
+ print(update_kit.hint("my-cli", latest, __version__, lang), file=sys.stderr)
45
+ ...
46
+ ```
47
+
48
+ ## API
49
+
50
+ | Function | Does |
51
+ |---|---|
52
+ | `latest_tag(repo, *, timeout=5, url=None) -> str` | Newest strict `v?X.Y.Z` tag (ASCII digits) via `git ls-remote --tags --refs`. Prereleases and other names are ignored. Raises `UpdateError` (offline, no tags, git missing). |
53
+ | `install_kind(dist) -> (kind, detail)` | `"dev"` (PEP 610 local/editable dir, detail = the dir), `"uv"` / `"pipx"` (from `sys.prefix`), else `"unknown"`. |
54
+ | `update(*, app, repo, dist, current=None, lang="en", after=(), out=sys.stderr, run=subprocess.run, url=None) -> int` | Exit code: **0** updated or already up to date · **1** couldn't check (offline/rate limit) or install/after-step failed · **2** dev install (use `git pull`) or unknown install (manual command printed). |
55
+ | `check(*, repo, current, cache_path=None, ttl=86400, timeout=1.5, url=None) -> str \| None` | Newer tag or `None`. Never raises. Results — failures too — are cached for `ttl` in `$HOME/.update-kit/state.json`, keyed by the resolved source URL. |
56
+ | `hint(cmd, latest, current, lang="en") -> str` | `my-cli v1.2.0 is available (you have 1.1.0) — run` `` `my-cli update` `` |
57
+ | `should_hint(stream=sys.stderr) -> bool` | `True` only when `stream` is a TTY. |
58
+ | `normalize_lang(value) -> str` | `zh_TW` / `zh-tw` / `zh-Hant` / `zh_HK` → `"zh-TW"`; anything else → `"en"`. |
59
+
60
+ Install commands used by `update`:
61
+
62
+ - uv: `uv tool install --force --no-sources git+<url>@<tag>`
63
+ - pipx: `pipx install --force git+<url>@<tag>`
64
+
65
+ `<url>` is `url=`, else `$UPDATE_KIT_REPO_URL`, else `https://github.com/<repo>.git`
66
+ (`file://` works, which is how the tests run without a network). Only `https://`, `ssh://`,
67
+ `file://` and `git@host:path` are accepted (`ssh://git@host/...` is fine, `ssh://user:pass@` is not; `git@host:path` is installed as `ssh://git@host/path`); a leading `-`, whitespace/control characters,
68
+ `ext::` or credentials in the URL (`user:pass@`) raise `UpdateError` (`update` returns 1,
69
+ `check` returns `None`). When `$UPDATE_KIT_REPO_URL` is in effect, `update` prints one line
70
+ naming it. `git ls-remote` runs as `git -c protocol.ext.allow=never ls-remote --tags --refs -- <url>`
71
+ from the home directory, and so does the install; git's stderr is stripped of control characters.
72
+
73
+ **Windows:** the running `.exe` is locked, so `update` starts the install (and the `after`
74
+ steps, chained with `&&`) in a detached `cmd.exe` that waits a few seconds for the app to
75
+ exit, returns 0 and says the update continues in the background. Output goes to
76
+ `%USERPROFILE%\.update-kit\<app>-update.log`. Unlike POSIX (where every after-step runs and
77
+ any failure makes the result 1), the Windows chain stops at the first failing step and the
78
+ outcome is only in the log. An install whose arguments or log path contain a `cmd.exe`
79
+ metacharacter (`& | < > ^ % " ( ) !`) is refused with exit 1 instead of being spawned.
80
+
81
+ `git ls-remote` never prompts: `GIT_TERMINAL_PROMPT=0`, and `GIT_SSH_COMMAND` defaults to
82
+ `ssh -o BatchMode=yes` when unset. The install step (uv / pipx) also runs with
83
+ `GIT_TERMINAL_PROMPT=0` and `GCM_INTERACTIVE=never`.
84
+
85
+ ## Development
86
+
87
+ ```bash
88
+ uv sync
89
+ uv run pytest
90
+ uv run ruff check .
91
+ uv build
92
+ ```
93
+
94
+ ## License
95
+
96
+ MIT
@@ -0,0 +1,10 @@
1
+ # TODO
2
+
3
+ - [ ] Pin the install to the commit SHA seen by `ls-remote` (TOCTOU: a tag can move between check and install)
4
+ - [ ] Verify release tag signatures before installing
5
+ - [ ] Windows: launch `cmd.exe` (ComSpec) and `git` by absolute path (CreateProcess searches the parent's cwd first)
6
+ - [ ] Run `after` steps with cwd=home too; document that an injected `run` must accept `cwd=`
7
+ - [x] 0.1.0: frozen public API, tests, CI matrix (macOS / Linux / Windows)
8
+ - [ ] Create the GitHub repo, push, tag `v0.1.0` and publish to PyPI (manual hand-off)
9
+ - [ ] Add `.github/workflows/release.yml` (PyPI Trusted Publishing) like telegram-kit
10
+ - [ ] Verify the Windows detached update chain on a real Windows uv tool install
@@ -0,0 +1,27 @@
1
+ [project]
2
+ name = "update-kit"
3
+ dynamic = ["version"]
4
+ description = "Self-update for git-tag-released Python CLIs (uv tool / pipx). Stdlib only."
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ license = { text = "MIT" }
8
+ dependencies = []
9
+
10
+ [build-system]
11
+ requires = ["hatchling"]
12
+ build-backend = "hatchling.build"
13
+
14
+ [tool.hatch.version]
15
+ path = "src/update_kit/__init__.py"
16
+
17
+ [tool.hatch.build.targets.wheel]
18
+ packages = ["src/update_kit"]
19
+
20
+ [dependency-groups]
21
+ dev = [
22
+ "pytest>=8",
23
+ "ruff>=0.15.21",
24
+ ]
25
+
26
+ [tool.pytest.ini_options]
27
+ testpaths = ["tests"]