pare
A Unix filter that summarizes noisy command output (test runs, builds, logs) to fit a token budget, for AI agents and humans. Nothing is lost: the raw input is saved to a run file, and the summary ends with what was omitted and how to get it back.
pytest -v 2>&1 | pare --budget 2000
A 308-line, 9,523-token pytest run becomes this (1,763 tokens), exactly as printed; the last lines name the run file on the machine it ran on:
# pare: 308 lines, 19,046 B (~9,523 tok) → ~1,763 tok (budget 2,000) · 35 templates
## head (L1–L5)
1: ============================= test session starts ==============================
2: platform linux -- Python 3.13.16, pytest-9.1.1, pluggy-1.6.0 -- /home/dev/proj/.venv/bin/python
3: rootdir: /home/dev/proj
4: collecting ... collected 170 items
5:
## templates (by salience)
T17 ×1 E L177: =================================== FAILURES ===================================
T24 ×5 E <PATH>:<NUM>: <*>
e.g. L214: /usr/lib/python3.13/json/decoder.py:363: JSONDecodeError
T13 ×1 W L171: tests/test_cart.py::test_skipped SKIPPED (flaky on CI) [ 97%]
T14 ×1 W L172: tests/test_cart.py::test_xfail XFAIL (known bug #42) [ 98%]
T19 ×4 S L183: tests/test_cart.py:14:
T27 ×1 . L218: INFO app.core:core.py:5 parsing 6 bytes
T26 ×5 S L216: INFO app.core:conftest.py:5 fixture setup for test
T22 ×1 . L196: self = <json.decoder.JSONDecoder object at 0x7f7e97664590>, s = '{"a": '
T23 ×1 (16 lines) E L197: idx = 0
210: except StopIteration as err:
211: > raise JSONDecodeError("Expecting value", s, err.value) from None
212: E json.decoder.JSONDecodeError: Expecting value: line 1 column 7 (char 6)
┆ (+9 more lines)
T18 ×5 (21 lines) S ___ <*> ___
e.g. L178: ________________________________ test_parse_bad ________________________________
┆ (+2 more lines)
T21 ×5 (15 lines) . <PATH>:<NUM>: in <*>
e.g. L185: app/core.py:6: in parse
┆ (+2 more lines)
T25 ×6 S --- Captured log <*> ---
e.g. L215: ------------------------------ Captured log setup ------------------------------
## selected lines
223: > assert c.total() == 0.9
224: E assert 0.9000000000000001 == 0.9
225: E + where 0.9000000000000001 = total()
226: E + where total = <app.core.Cart object at 0x7f7e97197d40>.total
┆ (1 line)
228: tests/test_cart.py:18: AssertionError
┆ (20 lines)
249: E ConnectionRefusedError: [Errno 111] Connection refused
┆ (1 line)
251: app/core.py:15: ConnectionRefusedError
┆ (20 lines)
272: E TimeoutError: timed out
┆ (1 line)
274: app/core.py:15: TimeoutError
┆ (12 lines)
287: tests/test_net.py:9: in a
┆ (7 lines)
295: > def c(): raise RuntimeError("deep failure at level 3")
┆ (1 line)
297: E RuntimeError: deep failure at level 3
## tail (L285–L308)
285: tests/test_net.py:12:
┆ (12 lines)
298:
299: tests/test_net.py:11: RuntimeError
┆ (2 lines)
302: =========================== short test summary info ============================
303: FAILED tests/test_cart.py::test_parse_bad - json.decoder.JSONDecodeError: Exp...
304: FAILED tests/test_cart.py::test_total_wrong - assert 0.9000000000000001 == 0.9
305: FAILED tests/test_net.py::test_connect[127.0.0.1] - ConnectionRefusedError: [...
306: FAILED tests/test_net.py::test_connect[10.255.255.1] - TimeoutError: timed out
307: FAILED tests/test_net.py::test_deep - RuntimeError: deep failure at level 3
308: ============= 5 failed, 163 passed, 1 skipped, 1 xfailed in 0.51s ==============
## omitted 266/308 lines (86.4%): T5 ×120, T28 ×24, T18 ×16, T21 ×13, T23 ×12, T6 ×10, T7 ×10, T8 ×10, T9 ×10, T20 ×8, T25 ×5, T26 ×4, T12 ×3, T15 ×2, T19 ×2, T10 ×1, T11 ×1, T16 ×1 · 23 templates not listed; 25 blank
## raw: ~/.cache/pare/runs/d0ec7c06b92d.log
expand with: pare --only T<n> < raw · drop noise: pare --except T<n>,T<m> < raw
pare --only T5 < runfile then prints the 120 omitted lines of one
template; --except, --min-severity error and --since L200 narrow it
further.
Why pare
An agent that runs a test suite or a build gets the whole output back, and the part it needs, one failing test with its traceback or the one compile error, is a few lines among thousands. The usual cuts lose exactly those lines:
| tail -50keeps the verdict and loses the failure above it;| headkeeps the start. Neither knows where the error is.| grep -i errorkeeps the matching lines without their stack trace or the verdict, and misses tools whose failures sayFAILEDorpanic:.- A harness that truncates long tool output is head and tail applied blindly, with nothing recoverable.
- The tools' own quiet flags (
pytest -q,mvn -q) cut at the source, each tool in its own way, and a quiet run still prints every failure in full. - Asking the model to summarise spends the tokens that were to be saved, and what it leaves out is gone.
pare keeps the lines that differ, counts the shapes that repeat, and fits the result to a budget. Three things make it safe to rely on:
- Nothing is lost. The input is saved before anything else happens, the ledger names each omitted template with its count, and the flags above pull any of it back.
- It is deterministic. The pipeline has no randomness, so the same input gives the same summary and the same template ids, and a summary can be quoted, diffed and expanded a day later.
- It is one filter for every tool. The signals are structural (log levels, HTTP statuses, exit codes, indentation, stack frames) with a vocabulary on top, so Maven, pytest, Terraform and a JSON log get the same treatment; the tool-specific rules add a few points on one dataset and nothing on the other (Results).
Use it for anything whose output can be long and whose failures are the
point: test suites, builds, linters, package installs, terraform plan,
kubectl logs, CI runs. Do not use it for output you need exactly: a
file you are reading, git diff, grep, anything you will edit from.
Source code is not a log either; pare summarises it, pointlessly. A
binary file (an executable, an image, an archive) gets one line saying
so, not a summary. Output that already fits the budget passes through
untouched, so wrapping a short command costs nothing but its run file.
Install
pare is a single binary with no dependencies. Any of these installs the
pare command (the package is pare-cli where pare was taken):
brew install wspires/tap/pare # macOS and Linux (Homebrew)
curl -sSfL https://raw.githubusercontent.com/wspires/pare/main/install.sh | sh
# macOS and Linux: to ~/.local/bin
pipx install pare-cli # or: uv tool install pare-cli, uvx pare-cli
cargo binstall pare-cli # the release binary, via cargo
cargo install pare-cli # built from source (Rust 1.87+)
Or download an archive for Linux (x86-64 and ARM, statically linked),
macOS (Apple Silicon and Intel) or Windows from the
releases page; each has a
SHA256SUMS entry.
Building from a checkout is in CONTRIBUTING.md.
Use
The budget
The budget is a hard ceiling in tokens on what pare prints. Tokens are
estimated as bytes / 2.0, the rate measured on pare's output with the
tokenizer of the current Claude models, through Anthropic's token-count
API (--bytes-per-token changes it: 2.8 is the rate for OpenAI's cl100k
and o200k tokenizers, and the default until October 2026).
By default the size follows the log (auto): output under 4,000
tokens is printed whole, since on the agent runs a summary of a log
that size cost a round trip more often than it saved tokens; above
that pare summarises, and stops once what is left is below error level,
at most 2,000 tokens. On the latest held-out samples it keeps what a
fixed 2,000 keeps (Results).
--budget auto:4000 raises the ceiling (and the pass-through, which is
twice it); --budget N is a fixed budget
the summary fills (1,000 to 2,000 suits an agent loop; 600 is tight, see
Results). Without
--budget, pare takes $PARE_BUDGET, else auto, so a harness can set
it once for every call. --ascii writes pare's own marks (→ · × ┆ …)
as ASCII; the log's text is never changed. Details:
docs/DESIGN.md and
docs/USAGE.md.
Running the command
pare -- CMD ARGS runs the command itself instead of reading a pipe:
pare -- ./gradlew build
The command's exit code survives (a pipe would report pare's), the
summary ends with the exit status, stdout and stderr are merged in the
order the command wrote them, and the command is recorded next to the run
file. pare stats totals what commands printed against what pare
printed, over every run so far.
integrations/README.md has two ways to route an
agent's noisy commands through pare: an instruction for CLAUDE.md or
AGENTS.md, and a Claude Code hook that rewrites test, build and install
commands automatically.
Following a stream
pare --follow (or -f) summarises input as it arrives: a long build, a
server log, a tail -f. Each window becomes one block in the usual
format, budgeted on its own:
kubectl logs -f deploy/api | pare -f --budget 600
A window closes after --every lines, after --interval seconds (30 by
default), or half a second after a line at --flush-on severity
(default error) arrives. A stack trace is never split across windows,
and template ids are the ones a batch run over the finished run file
would assign, so every block can be expanded afterwards with --only
and --since. Details: docs/USAGE.md.
Run files
Run files live in ~/.cache/pare/runs ($PARE_RUN_DIR). pare prunes
them itself: runs older than 14 days go, then the oldest until the rest
fit in 500 MB ($PARE_RUN_KEEP_DAYS, $PARE_RUN_MAX_MB;
$PARE_RUN_PRUNE=off stops it; pare prune [--dry-run] sweeps now). A
file on standard input (pare < build.log) is not copied but read in
place, and a piped input over the size limit is summarised but not
kept. Details: docs/USAGE.md.
Teaching pare your logs
pare needs no configuration: it learns each input's kinds of lines as it reads them. What it knows in advance is a vocabulary (error, warning, summary and noise words, log-level tokens, the shapes of stack frames) which a file can extend for your tools or language:
pare vocab > my.vocab # every built-in entry, in file format
pare --vocab my.vocab ... # or: export PARE_VOCAB=my.vocab
In the file, [error] adds entries to a class and [error replace]
replaces its built-in ones; a plain line is a word or phrase (matched
whole, case-insensitively) and a /.../ line a regular expression.
reference/vocab.example adds German.
The continuation class says which lines continue the record above
whatever their indentation, so a stack trace stays one record. pare knows
Java, JavaScript, Python, rustc and pytest shapes; for others, add
regexes. A Go panic prints its frames at column 0:
[continuation]
/^goroutine \d+ \[/
/^[\w./*()]+\(.*\)$/
/^\t/
A [mask NAME] section turns what its regexes match into <NAME> before
lines are clustered, for ids pare cannot recognize as variable. When a
tenant name leads each line, every tenant gets a template of its own
(acme: nightly sync failed, globex: nightly sync failed, ...); with
[mask TENANT]
/^[a-z]+(?=:)/
they are one template. Masks run before the built-in ones (timestamps,
numbers, ...), on the line as written. Template ids depend on the masks,
so --only T3 on a saved run file needs the same --vocab.
A worked example, from a wrong reading to the entry that fixes it, is in docs/USAGE.md.
Structured logs need nothing either. A JSON line (slog, pino, zap,
bunyan, structlog) or a logfmt line (level=error msg="..." err="...")
is recognized on its own, and when it is longer than --width pare keeps
its start and then the fields that say what went wrong (err,
exception, panic, stack, msg, ...) instead of cutting them off.
An escaped stack trace inside a field is shown decoded, as its message
and the frame nearest it; pino's and bunyan's numeric levels
("level":50) count as levels. pare --only T7 prints the whole line.
How it works
- Tee stdin to a content-hashed run file, in one pass with the steps below: pare keeps a few dozen bytes per line in memory, not the lines, so time is linear in the input and spent as it arrives, and memory stays small (docs/DESIGN.md).
- Segment lines into records: indented continuations, stack frames and compiler gutters join the line above.
- Mask timestamps, IPs, hashes, paths and numbers, then cluster the masked head lines into templates with Drain (built in, from drain3).
- Score each line by its structural severity (log level, HTTP status, exit code) and vocabulary (error, warning, summary, frame, pass, noise), and each template by its head line plus its rarity.
- Allocate the budget: head and tail windows, then one priority queue in which template entries and raw lines compete on the same scale, with diminishing returns for repeats and a hard limit on size.
- End with a ledger of what was omitted and the run-file path.
Every scoring term survived an ablation; nine that did not were removed. The details are in docs/DESIGN.md; the experiments behind each rule, in order, in docs/HISTORY.md.
Results
Recall is the share of must-survive lines (the failing tests, the error
messages, the verdict) that a tool's output keeps; bench/run.py
measures it. Two kinds of number, reported apart:
The development corpus (corpus/dev/, nine logs, 1.6 MB: real
pytest, go test, cargo, make and npm runs, three seeded synthetic logs
and a synthetic Android logcat), against two other filters written for
agents: sqz, which compresses
output before it reaches the model and deduplicates across a session's
calls, and tidyout, which
wraps a command and strips its output with rules keyed on the command's
name, passing unknown commands through. Neither takes a budget. The
misses here were fixed by looking at these cases, so pare's rows are
training scores.
| tool | output tokens | share of input | lines kept |
|---|---|---|---|
pare 0.2.0, --budget 2000 |
12,812 | 1.6 % | 99 % |
pare 0.2.0, --budget auto |
12,388 | 1.5 % | 98 % |
pare 0.2.0, --budget 600 |
5,017 | 0.6 % | 53 % |
| sqz 1.9.0 | 375,276 | 55 % | 55 % |
| tidyout 0.2.0 | 411,114 | 60 % | 89 % |
Tokens here are bytes / 2.0, the Claude rate, for every tool. A budget is a smaller summary than it was at the old rate (2.8 bytes per token: a 2,000 budget was 5,600 bytes and is 4,000), so 600 now keeps about half of the must-survive lines where it kept 78 %; 1,000 to 2,000 is the working range. (The sqz and tidyout rows were measured in October 2026 on the eight logs the corpus then had, 683k tokens at this rate, with pare 0.1.0 at 100 % and 79.5 % for budgets 2,000 and 600 at the old rate; their token counts are restated at 2.0.)
Held-out samples, labelled by datasets rather than by anyone working on pare: Defects4J bugs (real Java builds, labelled with the failing tests and their exceptions) and SWE-bench Verified tasks (real Python test runs, labelled with the tests the fix makes pass). Each sample is drawn fresh with earlier ones excluded and measured before anyone reads it; once a miss has been fixed by looking at it, the sample counts as training data and a new one is drawn. The latest, measured blind:
| sample | cases | pare 0.2.0 (600 / 1,000 / 2,000) | auto |
|---|---|---|---|
| Defects4J, seed 7 | 16 | 86 / 92 / 100 % | 100 % at 125 % of the tokens of 2,000 |
| SWE-bench Verified, seed 5 | 17 | 56 / 70 / 100 % | 100 % at 96 % |
auto prints more than a fixed 2,000 on the Defects4J sample because
most of those builds are under 8 KB and pass through whole, which the
agent runs showed to be cheaper than a summary the agent then expands
(docs/DESIGN.md).
Measured at the Claude rate of 2.0 bytes per token, the default since 29 October 2026; the same samples at the old rate, and the numbers before each held-out fix, are in the record. Seven Defects4J and six SWE-bench samples so far; the full record, with every miss and what was done about it, is in corpus/heldout/README.md. The tool-specific rules (pytest markers, rustc gutters, logcat levels) earn several points at 600 on SWE-bench and nothing on Defects4J (docs/DESIGN.md).
Contributing
pare is written in Rust. bench/check.sh runs what CI runs: the Rust
tests and lints, the tests of the Python prototype pare began as, kept
in reference/, the parity check that holds the
binary to it byte for byte over every log in the repository, and
packaging; bench/run.py is the benchmark. Setting up, the checks, the
benchmark tools and the repository layout are in
CONTRIBUTING.md; what comes next is in
ROADMAP.md.
License
Licensed under either of MIT or Apache-2.0, at your option. The built-in Drain is adapted from drain3 (MIT; see THIRD-PARTY-NOTICES). Unless you say otherwise, a contribution you submit for inclusion is dual-licensed as above, without additional terms.
Metadata
Release files for pare-cli 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| pare_cli-0.2.0.tar.gz | 153.3 kB | With provenance | Details for pare_cli-0.2.0.tar.gz |
Built distributions (wheels)
Total release size: 9.1 MB
Release files / pare_cli-0.2.0.tar.gz
| Download URL | pare_cli-0.2.0.tar.gz |
|---|---|
| Size | 153.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
bba850bb640bfc3392728cf8d62df41c88889adca461f7d15fb53e0270455683
|
|
BLAKE2b-256 checksum How to use checksums |
3974c2dc96dfeff9bcbcf0781edb528daa57828245608023f009ea8fd4e9b4c0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 31, 2026.
Transparency logRelease files / pare_cli-0.2.0-py3-none-win_amd64.whl
| Download URL | pare_cli-0.2.0-py3-none-win_amd64.whl |
|---|---|
| Size | 1.5 MB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
7cf84931a853421a1accc3bc30c7d01fd26808758078e4e514179600111868df
|
|
BLAKE2b-256 checksum How to use checksums |
5c51122ec4179a4a6e828daf0da39036b18317c5290954ccf34692436a4f245d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 31, 2026.
Transparency logRelease files / pare_cli-0.2.0-py3-none-musllinux_1_2_x86_64.whl
| Download URL | pare_cli-0.2.0-py3-none-musllinux_1_2_x86_64.whl |
|---|---|
| Size | 1.6 MB |
| Tags | Linux musl 1.2+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
4ba069896a6cec36afbe0f1ab1f69645be1aca4f854a1afb609c82f080f21d9c
|
|
BLAKE2b-256 checksum How to use checksums |
497672b7bdcffeb21dcb1081a711cad345eb24eb76f505481ac95f08ce69362c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 31, 2026.
Transparency logRelease files / pare_cli-0.2.0-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
| Download URL | pare_cli-0.2.0-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl |
|---|---|
| Size | 1.5 MB |
| Tags | Linux glibc 2.17+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
7ff6f9df9667e1f00dc2674be869da346a1c78b7d46b25afc5f0eb492d07b40b
|
|
BLAKE2b-256 checksum How to use checksums |
38797ce1297c1c9d56a904e67fbb9db706d4176e963710fd1c85e34a5244b189
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 31, 2026.
Transparency logRelease files / pare_cli-0.2.0-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
| Download URL | pare_cli-0.2.0-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl |
|---|---|
| Size | 1.4 MB |
| Tags | Linux glibc 2.17+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
846683eb9a0a71ea507b1c0d35143510c664bcf301754a4a67a84749500a46c9
|
|
BLAKE2b-256 checksum How to use checksums |
73b772021103be5646de5782a871788b267a6e1b34bef402608529dee9ef8816
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 31, 2026.
Transparency logRelease files / pare_cli-0.2.0-py3-none-macosx_11_0_arm64.whl
| Download URL | pare_cli-0.2.0-py3-none-macosx_11_0_arm64.whl |
|---|---|
| Size | 1.4 MB |
| Tags | Python 3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
97a7289b0ad64f11db0ea953b6302bf3c11bb903bf3fb364cdeb5ef516131b65
|
|
BLAKE2b-256 checksum How to use checksums |
9927b8eac1056345fcfd1dd14c89d2beb0805d4b414593fd77f44a3d3507ccf5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 31, 2026.
Transparency logRelease files / pare_cli-0.2.0-py3-none-macosx_10_12_x86_64.whl
| Download URL | pare_cli-0.2.0-py3-none-macosx_10_12_x86_64.whl |
|---|---|
| Size | 1.5 MB |
| Tags | Python 3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
9184861670839492c59318b097eaef797d811e90ff0093196f826c65466f176a
|
|
BLAKE2b-256 checksum How to use checksums |
963c966cd1330ee7603fdb9e31abbfcae4557960ea174f8952c8582853d9679d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 31, 2026.
Transparency logSecurity for pare-cli 0.2.0
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.
Full provenance
All 7 files in this release have been uploaded with provenance metadata.
Attestation for pare_cli-0.2.0.tar.gz
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 31, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.