Skip to main content

update-kit

Self-update for Python CLIs that are released as git tags (vX.Y.Z or X.Y.Z) and installed with uv tool or pipx. Stdlib only, Python ≥ 3.10, macOS / Linux / Windows.

  • <cmd> update installs the newest release tag — no prompts, ever.
  • A cached background check prints a one-line hint on an interactive terminal only.
  • Every message exists in English and Traditional Chinese (zh-TW).

Install

# consumer pyproject.toml
dependencies = ["update-kit>=0.1,<0.2"]

Wiring a consumer CLI

import sys

import update_kit

from my_cli import __version__

APP, REPO, DIST = "my-cli", "owner/my-cli", "my-cli"


def main(argv=None):
    argv = sys.argv[1:] if argv is None else argv
    lang = "zh-TW"  # the app's own current language setting

    # `my-cli update` and `my-cli --update`
    if argv[:1] == ["update"] or "--update" in argv:
        sys.exit(update_kit.update(
            app=APP, repo=REPO, dist=DIST, lang=lang,
            after=[["my-cli", "migrate"]],  # optional: runs with the NEW binary
        ))

    # Background hint: stderr, TTY only (never under launchd/timers/--json/pipes).
    if update_kit.should_hint():
        latest = update_kit.check(repo=REPO, current=__version__)
        if latest:
            print(update_kit.hint("my-cli", latest, __version__, lang), file=sys.stderr)
    ...

API

Function Does
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).
install_kind(dist) -> (kind, detail) "dev" (PEP 610 local/editable dir, detail = the dir), "uv" / "pipx" (from sys.prefix), else "unknown".
update(*, app, repo, dist, current=None, lang="en", after=(), out=sys.stderr, run=subprocess.run, url=None) -> int Always lands on the newest tag: a dev checkout, a branch/commit install, a newer build or an unmanaged install is replaced by it (via uv, else pipx). Exit code: 0 updated, or already installed from that tag (the message names the version) · 1 couldn't check (offline/rate limit) or install/after-step failed · 2 no uv/pipx found (manual command printed).
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.
hint(cmd, latest, current, lang="en") -> str my-cli v1.2.0 is available (you have 1.1.0) — run `my-cli update`
should_hint(stream=sys.stderr) -> bool True only when stream is a TTY.
normalize_lang(value) -> str zh_TW / zh-tw / zh-Hant / zh_HK → "zh-TW"; anything else → "en".

Install commands used by update:

  • uv: uv tool install --force --no-sources git+<url>@<tag>
  • pipx: pipx install --force git+<url>@<tag>

<url> is url=, else $UPDATE_KIT_REPO_URL, else https://github.com/<repo>.git (file:// works, which is how the tests run without a network). Only https://, ssh://, 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, ext:: or credentials in the URL (user:pass@) raise UpdateError (update returns 1, check returns None). When $UPDATE_KIT_REPO_URL is in effect, update prints one line naming it. git ls-remote runs as git -c protocol.ext.allow=never ls-remote --tags --refs -- <url> from the home directory, and so does the install; git's stderr is stripped of control characters.

Windows: the running .exe is locked, so update starts the install (and the after steps, chained with &&) in a detached cmd.exe that waits a few seconds for the app to exit, returns 0 and says the update continues in the background. Output goes to %USERPROFILE%\.update-kit\<app>-update.log. Unlike POSIX (where every after-step runs and any failure makes the result 1), the Windows chain stops at the first failing step and the outcome is only in the log. An install whose arguments or log path contain a cmd.exe metacharacter (& | < > ^ % " ( ) !) is refused with exit 1 instead of being spawned.

git ls-remote never prompts: GIT_TERMINAL_PROMPT=0, and GIT_SSH_COMMAND defaults to ssh -o BatchMode=yes when unset. The install step (uv / pipx) also runs with GIT_TERMINAL_PROMPT=0 and GCM_INTERACTIVE=never.

Development

uv sync
uv run pytest
uv run ruff check .
uv build

CI notifications

.github/workflows/ci.yml runs the test matrix (macOS/Linux/Windows) on every push and PR, then a separate notify-telegram job sends a Telegram message only when the test job fails on a push (never on green runs, never on pull_request, to avoid pinging on forks/external PRs). Configure it once per repo:

gh secret set TELEGRAM_BOT_TOKEN
gh secret set TELEGRAM_CHAT_ID

Unconfigured secrets are a valid state — the job skips quietly instead of failing a second time on top of the real failure.

License

MIT

Metadata

Release files for update-kit 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for update-kit 0.2.1
File Size Uploaded
update_kit-0.2.1.tar.gz 32.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for update-kit 0.2.1
File Interpreter ABI Platform
update_kit-0.2.1-py3-none-any.whl Python 3 none any Details

Total release size: 42.9 kB

Release files / update_kit-0.2.1.tar.gz

Download URL update_kit-0.2.1.tar.gz
Size 32.1 kB
Tags Source
SHA-256 checksum
How to use checksums
b58e97381cdaf4065da97d0da04998e25e749f877cf0a6913b9859e8a81ea3c3
BLAKE2b-256 checksum
How to use checksums
3efa0f56b3136bfec9cdc66fa2120de71f0ac24d152fc501513930e875f2baba
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / update_kit-0.2.1-py3-none-any.whl

Download URL update_kit-0.2.1-py3-none-any.whl
Size 10.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
509ee9cda0b392288660a49a85931231d1f463be2419aafa63ebac0c81389c06
BLAKE2b-256 checksum
How to use checksums
9472e31000d81b15709e594400f166dd43fb9ce2c9cdc87d15a9ca07d124a87f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

0.2.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page