Skip to main content

clankers

Get an ntfy notification when a long-running command or Python task finishes.

Configuration

Set the ntfy server and topic with environment variables:

export NTFY_URL="https://ntfy.example.com"
export NTFY_TOPIC="default"
export NTFY_TOKEN="tk_your_private_access_token"

The same variables can live in a .env file in your project directory:

NTFY_URL=https://ntfy.example.com
NTFY_TOPIC=default
NTFY_TOKEN=tk_your_private_access_token

Both NTFY_URL and NTFY_TOPIC are required; clankers does not provide deployment defaults in code. Server URLs must be absolute HTTP(S) URLs without credentials, query strings, fragments, whitespace, or backslashes. Topic names may contain ASCII letters, numbers, underscores, and hyphens. NTFY_TIMEOUT is a positive decimal number of seconds (such as 10 or 2.5), up to 2,147,483.647 seconds. Redirects are rejected; configure the final publish URL directly. You can instead create ~/.config/clankers/config.toml (or $XDG_CONFIG_HOME/clankers/config.toml):

[ntfy]
url = "https://ntfy.example.com"
topic = "default"
token = "tk_your_private_access_token"

The loader returns normalized key/value pairs such as NTFY_URL; each backend selects and validates its own settings. Sources are merged in this order of priority:

  1. .env
  2. process environment variables (those prefixed NTFY_ or CLANKERS_)
  3. ~/.config/clankers/config.toml

Settings themselves never come from the command line — only the files they live in do. Clankers searches for .env from the working directory upward; use --dotenv to select one directly, and --config to select a different TOML file. Missing files supply no settings, including files selected explicitly; malformed TOML produces a configuration error.

CLI

Prefix an arbitrary command with clankers engage:

clankers engage uv run pytest
clankers engage --config ./config.toml uv run train.py
clankers engage -m "nightly training" -- uv run train.py --epochs 100

Clankers' own flags must come before the command; everything after the first non-flag argument (or after --) belongs to the wrapped command. The wrapped command keeps its standard input and output. Clankers reports its duration and result, then returns the command's exit code. Without --message the report is the command line itself. Delivery failures produce warnings without changing the wrapped command's exit code. Invalid configuration or an empty --message exits with status 2 before starting the command; an executable that cannot start returns 127. SIGINT and SIGTERM are forwarded to the command, which is still reported. On Unix, a command terminated by a signal returns 128 + signal number. Commands execute directly; invoke a shell explicitly when you need pipes, redirects, or other shell syntax.

Send a notification on its own — at the end of a shell script, or from a Makefile. There are three levels: rogerroger reports success, blastthem reports neutral progress, uhoh reports failure.

clankers blastthem -m "deploy started"
clankers rogerroger -m "deploy finished"
clankers uhoh -m "deploy failed"
Argument Behavior
-m, --message message Required for manual notifications; overrides the command line reported by engage
--config path Select a TOML configuration file
--dotenv path Select a .env file instead of searching parent directories
-v, --verbose Log configuration discovery and delivery diagnostics to stderr
-h, --help Show help without configuring ntfy
--version Show the installed version without configuring ntfy
-- End wrapper options and start the command and its arguments

Relative configuration paths and .env discovery use the directory where you run the CLI.

clankers --version prints the installed version. Delivery problems are reported on stderr — a rejected or unreachable server never changes the exit code — and -v logs every file clankers reads and every request it makes:

$ clankers rogerroger -m "deploy finished" -v
clankers: reading .env file /home/you/project/.env
clankers: configuration provides NTFY_TOKEN, NTFY_TOPIC, NTFY_URL
clankers: publishing to https://ntfy.example.com/deploys with a 10.0s timeout
clankers: ntfy rejected the notification: 403

Python

clankers.Engage wraps a block and reports around it: a blastthem notification when the block starts, then rogerroger or uhoh when it ends, with the duration.

import clankers

with clankers.Engage("Training"):
    train()

with clankers.Engage("Training", announce=False):  # report only the outcome
    train()

In async code, use async with so sending notifications does not block the event loop:

async with clankers.Engage("Evaluation"):
    await evaluate()

Each phase can build its message when it is sent, instead of naming it up front. Pass a callable that takes no arguments and returns the message; it reads whatever the surrounding scope holds at that moment. The failure builder receives the exception. A phase without a builder reports the message the context was created with.

state = {"epoch": 0}

with clankers.Engage(
    "Training",
    start=lambda: f"Training from epoch {state['epoch']}",
    success=lambda: f"Training reached epoch {state['epoch']}",
    failure=lambda exc: f"Training died at epoch {state['epoch']}: {exc}",
):
    for state["epoch"] in range(100):
        train_one_epoch()

Builders run when the notification is sent, so success and failure report the final state of whatever they close over — that is the point of them, and it is up to the caller to keep that state readable. A builder that fails or returns nothing is logged and the plain message is sent instead; it never breaks the block it reports on.

An Engage object tracks one run at a time. Reuse it for runs that follow each other, but create a new one for runs that overlap, such as in threads, tasks, or nested blocks. The decorator already creates one per call.

The block can also send notifications of its own while it runs, at any of the three levels. These are extra: the block still reports its own outcome when it exits.

with clankers.Engage("Benchmark") as engage:
    for matchup in matchups:
        engage.blastthem(f"{matchup.name} starting")
        try:
            run(matchup)
            engage.rogerroger(f"{matchup.name} complete")
        except MatchupError as error:
            engage.uhoh(f"{matchup.name} failed: {error}")

clankers.engage is the decorator form, for a whole function. It reports the start and the outcome of every call and takes nothing else; use the context manager when you want the rest.

@clankers.engage("Training")
def train(): ...


@clankers.engage()  # the message defaults to the function name
async def evaluate(): ...

Normal completion sends success. An exception sends failure and is re-raised unchanged. Async functions are supported as decorators as well, and send notifications without blocking the event loop. Configuration is read the first time a notification is sent, so decorating a function never fails at import time; a missing configuration or an unreachable ntfy server is logged and never affects the wrapped work.

Clankers logs through the standard logging module under the clankers logger: failed deliveries at WARNING, every file read and request made at DEBUG.

logging.getLogger("clankers").setLevel(logging.DEBUG)

Every entry point shares one lazily built backend, so the configuration files are read once per process. Point it somewhere else — or hand it a ready backend — with configure():

clankers.configure(config_path="./clankers.toml")

A ready backend can be any object with send(event), such as clankers.backends.NtfyBackend. Configuration requires a topic, but an NtfyBackend built in code may omit it; it then logs a warning and drops each notification.

For a process that reports to more than one topic, build clankers of your own; a Clanker owns a backend and sends notifications, and everything that wraps work takes one with clanker=:

training = clankers.Clanker(dotenv_path="./training.env")

with clankers.Engage("Epoch 1", clanker=training):
    ...


@clankers.engage("Evaluation", clanker=training)
def evaluate(): ...


training.rogerroger("Checkpoint uploaded")

clankers.Engage, engage, rogerroger, blastthem and uhoh report through the shared clanker unless a clanker is named; every configuration option lives on Clanker and configure().

For work clankers does not wrap itself, send a notification by hand:

clankers.blastthem("Epoch 40 of 100")
clankers.rogerroger("Checkpoint uploaded")
clankers.uhoh("Validation loss diverged", duration=4200)

All three take the same optional duration= and, like everything else, only warn when the notification cannot be delivered.

Theme

Notifications are labelled Done, Info and Failed by default. For the droid experience, set the theme to starwars and they read Roger, roger, Blast them! and Uh-oh instead:

[clankers]
theme = "starwars"

CLANKERS_THEME=starwars works too, in the environment or a .env file, and follows the same priority as the ntfy settings. In Python, Clanker(theme=...) and configure(theme=...) override the configuration. An unknown theme makes the CLI exit with status 2 before it runs the command.

Development

Run development and release commands from the repository root.

uv sync --project packages/python --group dev
npm ci --prefix packages/nodejs
make check   # Python lint, TypeScript checks, formatting, and shared versions
make fix     # autofix and reformat
make test    # Both packages and release-tool tests
make build   # Python distributions + npm archive, validated for both packages

Use make check-python, make test-python, and make build-python to target Python only.

Releasing

Both packages use one shared version, the root CHANGELOG.md, and a paired release process. Run release commands from the repository root. See release setup and recovery.

Metadata

Release files for clankers 3.0.2

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

Source distribution (sdist)

Source distribution for clankers 3.0.2
File Size Uploaded
clankers-3.0.2.tar.gz 42.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for clankers 3.0.2
File Interpreter ABI Platform
clankers-3.0.2-py3-none-any.whl Python 3 none any Details

Total release size: 60.3 kB

Release files / clankers-3.0.2.tar.gz

Download URL clankers-3.0.2.tar.gz
Size 42.9 kB
Tags Source
SHA-256 checksum
How to use checksums
dccc6dcf7694aa24b40c901ff0454806f9063217bfe6add1cb5ef8c5a2a9baf2
BLAKE2b-256 checksum
How to use checksums
fbeaa4e9aa14a46a844c35464c7ce3322e11e64dfc007e3c5d8bf61a9bc4e349
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 4, 2026.

Transparency log

Release files / clankers-3.0.2-py3-none-any.whl

Download URL clankers-3.0.2-py3-none-any.whl
Size 17.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
522ce206ca6258d1bb7de775c0cc9c2ef97d16364e74258f1390f388ac004b6b
BLAKE2b-256 checksum
How to use checksums
e79e98c31e955bc8c76409cd526edcaa96a29ee0fd355855f511ac17dd77171c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 4, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

3.0.2 This release

2 release files

3.0.1

2 release files

3.0.0

2 release files

2.0.0

2 release files

1.0.1

2 release files

1.0.0

2 release files

0.0.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