allem-cli — connect your coding agent to Allem
Allem records what your coding agent does. It blocks only the actions you have explicitly marked as hard gates in your rules.
That sentence is the product. Everything below is detail.
allem init installs a hook into your coding agent's own configuration. From then on, every tool
call it makes — shell commands, file edits, file reads — becomes an event in your organization's
tamper-evident audit chain, with secrets redacted before anything leaves your machine.
Install
pip install allem-cli && allem init --key alm_sk_live_...
Your key comes from the Allem console: Get Started → Connect your agent, or from the page you
land on when you confirm your email after signing up. Without --key, allem init asks for one.
That key is not what ends up on your machine. It is used once, to give each of your coding agents
its own key, and is never saved to disk. See What allem init does below.
Python 3.11 or newer. macOS and Linux.
What allem init does
- Finds the coding agents on this machine. It offers only what it finds, and names the file it will write for each one.
- Exchanges your key for one key per coding agent, before writing anything at all. The key you pasted is held in memory for the length of one request and is never written to disk. See The key on your machine below for why this matters.
- Writes
~/.allem/config.toml, mode0600, holding the endpoint, one agent key, and one agent identity per coding agent. - Registers the hook in each agent's config. It reads the file, merges its entry, and writes it
back — the original is copied to
<file>.allem-backup-<timestamp>first. Anything already in there is left exactly as it was. Runningallem initagain replaces Allem's entry rather than adding a second one. - Verifies. It sends one real event with the agent key and tells you whether it was accepted, in words: accepted, key refused, endpoint unreachable, or agent unknown.
- Reads your gated-action list so the first tool call of your first session is already checked against your own rules (see What gets checked).
- Prints where to look in the console.
If step 2 cannot be completed, allem init stops and tells you what is missing. It writes nothing
— in particular it does not save the key you gave it. There is no fallback, because the fallback
would be the exact thing this is here to stop.
The key on your machine
Allem has two kinds of key, and they do not look different:
| An organization key | An agent key | |
|---|---|---|
| Send events, ask for checks | ✅ | ✅ for its one agent |
| Read every event in your account | ✅ | ❌ |
| Create and delete agents | ✅ | ❌ |
| Change the rules your agents are judged against | ✅ | ❌ |
The key you paste into allem init is an organization key. Leaving one on a developer's laptop means
the machine being watched holds the credential that edits the rules it is watched against — so
allem init trades it for agent keys and keeps only those. What is left on your machine can send
events for your agents and ask for checks. That is all it can do.
If you already have an agent key, pass that instead: allem init recognises it, uses it directly, and
does not exchange anything.
Options
| Flag | What it does |
|---|---|
--key |
Your Allem API key. Prompted for if omitted. Held in memory only; never written to disk. |
--endpoint |
Allem API base URL. Defaults to https://api.allem.ai. |
--agent |
Your identity on this machine. Defaults to user-hostname. |
--agent-id |
Override your agent's canonical id. Normally unnecessary — the exchange returns it. |
--only |
claude-code, codex, gemini-cli — comma-separated. Skips detection. |
--scope project |
Configure this project only, not the whole user account. |
--production-url-pattern |
Regex for URLs that mean production, for the built-in floor. |
--no-verify |
Skip the test event. |
-y, --yes |
Don't ask for confirmation. |
There is no flag for the connection tier, and that is deliberate — see How your connection is recorded.
allem provision — a key for an integration you wire yourself
allem init is for coding agents. If you are wiring the Allem SDK, the MCP server or a proxy, this
mints the key for it and records which one it is:
allem provision --agent billing-bot --for sdk
It asks for your organization key (or takes --key), trades it for one agent key, and prints that
key once. It writes nothing to disk — not the key you pasted, and not the key it minted. Put it
wherever your integration reads its credentials from.
--for is required and there is no default. Allem records the answer as a statement about how this
agent is connected, and it appears in your evidence export; a guessed default would end up there too.
| Flag | What it does |
|---|---|
--agent |
The agent's id. Created if it does not exist yet. Required. |
--for |
sdk, mcp or proxy. Required. |
--key |
Your Allem organization key. Prompted for if omitted. Never written anywhere. |
--endpoint |
Allem API base URL. Defaults to https://api.allem.ai. |
--key-name |
Name for the new key in your console's key list. |
If Allem already shows an agent as "connected over the API, connector not identified", this command is what declares a connector for it. That state is not an error — it means nothing declared itself, so Allem records that it cannot tell — but it is the one thing on that screen you can change.
What it does not do is erase the old record, and that is deliberate. An agent that has been sending on an undeclared key has genuinely connected both ways, so after this it reads "Connected more than one way" and names both, with the date each was last seen. Allem never collapses that to the newer connection: events that arrived with nothing identifying them are not retroactively covered by a declaration made afterwards. New traffic from the declared key is recorded as that connector from the first event.
What each supported agent needs
| Coding agent | File written | Events used | Status |
|---|---|---|---|
| Claude Code | ~/.claude/settings.json |
PreToolUse, PostToolUse |
Verified. Allem has run on this shape against its own repo for weeks. |
| Codex | ~/.codex/hooks.json |
PreToolUse, PostToolUse |
Written from Codex's documented event names. Not yet exercised against a live install. |
| Gemini CLI | ~/.gemini/settings.json |
BeforeTool, AfterTool |
Written from Gemini CLI's documented event names. Not yet exercised against a live install. |
Two things follow from that last column, and we would rather say them than have you discover them:
- Codex also supports a
[hooks]table in~/.codex/config.toml.allem initwriteshooks.jsonand never rewrites your TOML — round-tripping it would drop your comments and formatting. If a[hooks]table is already there,allem initsays so andallem statusprints the exact command line to add by hand. - If a registration does not fire on your setup,
allem statusshows what was written where. The shape is one small adapter per agent (allem_cli/adapters.py), not an integration — corrections are cheap.
Each coding agent gets its own agent record: identity dev-laptop plus the agent's name, e.g.
dev-laptop-claude-code. Two connectors on one machine are two records, because a Claude Code
record must not silently inherit a Codex record's posture.
What is recorded
Every tool call, as an action with redacted parameters:
| The agent does | Allem records |
|---|---|
| Runs a shell command | bash.execute, git.commit, git.push_force, test.run, docker.run, net.request — with the command shape |
| Edits or writes a file | file.edit / file.write, the path, how many characters changed |
| Reads a file | file.read, the path |
| Touches a secrets path | file.access_secrets |
| Anything else | tool.<name> |
Secret values never leave your machine. Before any payload is built, five redaction rules run
over anything that would travel: environment assignments (FOO_API_KEY=…), secret CLI flags
(--token=…), authorization headers, known credential prefixes (Allem, Anthropic, GitHub, AWS,
Slack, JWT), and any 40+ character opaque token. Commands are truncated at 300 characters. What
travels is the shape of the command and the paths, not the contents of your files.
Events are written to a local spool on disk before any network attempt, so an Allem outage costs you nothing and loses nothing: the record survives and is delivered when connectivity returns.
What gets checked before it happens
Two sources, and they add — the second never narrows the first.
- Your own rules. The hook fetches your agent's gated-action list from Allem
(
GET /v1/agents/{id}/scopes/hard-gates) and caches it against its scope version. Any action on that list is checked before it runs. - A built-in floor: force-push, secrets paths, production URLs. Always checked, whatever your rules say.
This matters more than it sounds. If the check were driven by our idea of "risky" instead of your
rules, a rule you wrote denying file.edit under /config would appear in the console, read as
active, and never fire — because nothing would ever ask. A rule that lies is worse than no rule.
The same list decides what can be blocked: an action is stopped only if it is on this list and Allem denies it. Checking and blocking come from one source, so there is no second place for the two to disagree.
If the list cannot be read, the hook falls back to the floor alone, blocks nothing, and writes
that to ~/.allem/log/hook.log every time it happens. Checking less than you configured is
sometimes unavoidable. Doing it quietly is not.
What is and is not blocked
One thing can stop a tool call: an action you marked hard_gate in your rules, which Allem then
denies. Nothing else, ever.
Ordinary denials are recorded and proceed. That is a decision, not a missing feature. Hooks
across all three agents are, in Codex's own documentation, "a guardrail rather than a complete
enforcement boundary because Codex can often perform equivalent work through another supported tool
path." A denied rm in a shell does not prevent the same deletion through a file-edit tool.
Blocking everything your rules deny would claim more prevention than a hook can give — which is
precisely the overclaim Allem exists to criticise in other people's products. Marking a rule
hard_gate is you saying "this one is worth failing closed for", which is your call and not ours.
Two consequences worth stating plainly:
- Nothing is blocked when we cannot read your gated-action list. We do not enforce a rule we cannot see. The narrowing is written to the log every time it happens.
- An unhappy answer is not a denial. A revoked key returns
allowed: falsetoo. The hook only honours a denial when Allem returned a verdict it actually recorded, so an expired credential cannot turn into a broken editor.
When something goes missing
This is the part that decides whether Allem is a tool you keep. The only thing that may ever stop a tool call is an explicit deny from Allem for a hard-gated action. Every other failure — ours, yours, or the network's — lets the action through and is written down.
Two things make that true rather than aspirational. The hook process always exits 0: a decision
travels as JSON on stdout, which is how all three coding agents read one, so no crash, missing file
or timeout can be mistaken for "block". And the registration runs a generated guard script
(~/.allem/bin/allem-hook) whose only job is to check every path before running anything.
| What goes missing | What your coding agent sees | Where it is recorded |
|---|---|---|
~/.allem/config.toml deleted |
Nothing. Tool calls proceed, nothing is recorded. | guard.log |
| Config unreadable or malformed TOML | Nothing. Tool calls proceed. | hook.log, config_unusable |
The hook module unimportable (package uninstalled, venv rebuilt, .pth files skipped) |
Nothing. Tool calls proceed, and nothing is recorded at all. | guard.log, hook_did_not_start, and allem status says the hook did not start and when it last worked |
The package reinstalled a different way (pip install -e over a regular install) |
Nothing at all — this used to switch the connector off for good, and no longer does. | nothing to record; the hook runs |
| The Python interpreter deleted | Nothing. Tool calls proceed. | guard.log |
The whole ~/.allem directory deleted |
Nothing. Tool calls proceed. | nowhere — there is nowhere left to write |
| Allem unreachable | Nothing. Tool calls proceed; events spool locally and deliver later. | hook.log, and allem status says Allem did not answer and when |
| Allem answering slowly, so the queue grows | Nothing — the hook never waits on delivery. A separate process delivers the backlog. | allem status, "Waiting to be delivered" |
| Key invalid or revoked | Nothing. Tool calls proceed. Never treated as a denial. | hook.log |
| Allem slow or hung | Nothing, after at most 5 seconds. | hook.log, deadline_exceeded, and allem status says the hook was abandoned at its deadline |
| Malformed response, unparseable input, a bug in our code | Nothing. Tool calls proceed. | hook.log |
| Your gated-action list unreadable | Tool calls proceed. Nothing can be blocked. | hook.log, every time |
None of these leaves your coding agent erroring, and none of them requires you to know that deleting Allem's config is the fix.
guard.log is not a surface. Two of the rows above write only there, and on Allem's own
machine that meant a connector that recorded nothing for most of five weeks while looking
entirely healthy: 77,706 declined tool calls, each one correctly logged, none of them anywhere a
person looks. Every row above now also shows on allem status — see below.
And the guard no longer checks for a file that Python would not have looked for. Until
2026-09-14 it recorded the hook module's location as an absolute path, taken from __file__ when
allem init ran, and declined every tool call if that exact file was not there. A path is not how
Python finds a module, and on a supported install method the two disagree:
# an illustration of the defect, not something to run
pip install allem-cli → the guard recorded …/site-packages/allem_cli/hook.py
pip install -e ./allem-cli → pip uninstalls the first, and that file is now gone
python -m allem_cli.hook still runs. The guard declined anyway, silently, for the life of that
install — and a virtualenv re-sync, a rebuilt container or a moved home directory did the same
thing. It now names the module and lets the interpreter resolve it, which is both layout-independent
and impossible to be wrong about, because the check has become the thing it was standing in for.
This is written from an incident rather than from imagination. Allem's own repository ran an earlier
hook whose guard checked the interpreter and the config file but never the script it was about to
run. When that script moved, both checks passed, the shell exec'd Python against a deleted path,
Python exited 2 — and a PreToolUse hook that exits 2 means block this tool call. Every tool call
in the repository began failing. The trailing || exit 0 did not help, because exec replaces the
shell: once Python starts there is no shell left to run the || branch.
So the guard is a generated file with its dependencies in one list, not a shell chain that the next person extends incorrectly, and there is a test for every row of the table above — including the exact combination that caused it, config present and entry point missing.
Is anything waiting?
allem status has a Delivery section, and it is the first place to look when events are not
arriving in the console:
Delivery
Waiting to be delivered 204
Oldest one recorded 12 min ago
Last delivered 41s ago
Allem answered the last time this machine asked (41s ago).
A delivery process is running now.
Run `allem drain` to watch it clear.
Some things worth knowing about what it is telling you:
- Your coding agent never waits for delivery. Recording an event is a local disk write. A separate background process delivers them, and it is started by your next tool call. A long queue slows nothing down and loses nothing.
- "Allem did not answer" means exactly that — no response came back from an attempt that was
actually made. It never means the queue is long. A
401, a422and a503are answers, and none of them makes Allem "offline". - Being behind on delivery does not change what is enforced. Your rules are refreshed on
their own schedule, and a list that could not be refreshed is still enforced exactly as last
read. If refreshes have been failing for a while,
statussays so and so does the terminal, once an hour. - Set aside means Allem answered and would not take an event — a wrong agent id, a body it
refuses. Those events are kept on disk, still counted as undelivered, and never removed.
allem drain --retry-refusedputs them back in the queue.
allem drain delivers everything now, in the foreground, printing progress. It never deletes
anything.
Is the hook running at all?
Above Delivery, because a hook that cannot run produces nothing to deliver and every number below it is then about a population that stopped growing:
Is the hook running?
The hook last ran AND FAILED, 3 h ago
Your coding agent ran the hook and Python could not start it. Tool calls go ahead and NOTHING is recorded.
python -m allem_cli.hook exited 1
Last ran without error 6 days ago
Your coding agent is unaffected — nothing here can stop a
tool call. What stops is the recording.
"Nothing has been recorded here yet" is printed as its own state, and is never folded into a
pass. A connector installed an hour ago on a machine nobody has used since has no evidence either
way, and an all-clear over an empty population is the defect this whole screen exists to remove.
allem init's own check of the guard is kept separate from a tool call for the same reason — it
proves the hook can run, and it is not something your coding agent did.
And if the hook itself has been failing, this screen says so. A hook that cannot run records
nothing at all, which makes every other number above it a fact about a population that stopped
growing — so allem status reads the tail of its own logs and reports how many times in the
last hour the hook could not start or failed while running, with the most recent reason:
The hook failed while running 4 time(s) in the last hour.
Those tool calls were NOT recorded. The most recent:
{"event": "hook_error", "error": "ModuleNotFoundError(\"No module named 'allem'\")"}
If the hook stops recording
The hook never stops your coding agent — every failure lets the tool call go ahead — so the risk is that it stops recording and nobody notices. Three things now say so:
- Inside Claude Code, once per session, when this machine could not record: "Allem could not
record this session's tool calls on this machine. Your work is not affected. Run
allem statusto see why." allem status, under Is the hook running?, says what the last attempt did and why.- The Allem console tells your organisation when no event has reached Allem for three days from an agent that had been reporting. That is an observation, not a diagnosis: an agent nobody used and a connector that stopped recording look the same from the server.
On macOS, one cause has its own message. If the .pth files that install the hook's packages
carry the macOS hidden flag — it has been observed on editable installs inside a project's
virtualenv — Python skips them silently and the hook cannot start. allem status cannot run
either in that state, so Claude Code is told the cause and the one-line fix instead:
chflags nohidden '<site-packages>'/*.pth
allem init prints a line when it finds the hook installed editable. pipx install allem-cli or
uv tool install allem-cli gives the hook an environment of its own that no project sync touches.
What Allem can and cannot see
Allem sees the actions your coding agent takes through its own hook system, and records them. Actions taken another way are not visible to Allem.
This is a coding-agent hook connection — tier 2 of Allem's connection-tier model. Real tool
calls, incomplete coverage. It is not the same assurance as an agent wired through the Allem SDK,
where every action passes through check() before it happens, and Allem's console must never show
the two as equivalent.
How to uninstall
allem uninstall # remove the hook registrations
allem uninstall --purge # also delete ~/.allem (config, local log, spooled events)
uninstall removes exactly Allem's entries from each config file and leaves everything else — your
own hooks, your settings — as it was, backing the file up first. Without --purge your config and
local log stay where they are, and --purge refuses to run while events are still waiting to be
delivered unless you pass --yes.
Events already recorded stay in your organization's audit chain. An audit log you could erase by uninstalling the thing that wrote it would not be an audit log.
Configuration
Three values, three layers. Lowest precedence first:
~/.allem/config.toml— whatallem initwrites./.allem/config.toml— optional, per projectALLEM_ENDPOINT,ALLEM_API_KEY,ALLEM_AGENT_EXTERNAL_ID— environment, wins over both
endpoint = "https://api.allem.ai"
api_key = "alm_sk_live_..." # an AGENT key, not the one you pasted
agent_external_id = "dev-laptop-claude-code"
agent_id = "665f…"
[codex]
agent_external_id = "dev-laptop-codex"
api_key = "alm_sk_live_..." # its own agent key — one per agent
agent_id = "665f…"
What is in the file is what Allem sees. The identifier is used exactly as written — nothing is
appended at runtime. Each coding agent gets its own record, its own key and its own [section],
which allem init writes for you: an agent key is bound to one agent, so two coding agents that are
two agents need two keys. allem init --agent <id> makes them all one agent instead, and they then
share the one key.
Optional: production_url_pattern.
Everything else lives under ~/.allem/: bin/allem-hook (the guard), state/ (spool, per-session
sequence numbers, the cached gated-action list), log/hook.log and log/guard.log.
Limits worth knowing
An agent key cannot look itself up.Closed on 2026-08-10. The credential exchange returns the canonicalagent_idandallem initwrites it into the config, so the hook can read your gated-action list on the agent-key path without anyone passing--agent-idby hand. The flag survives as an override.- One agent key serves one agent. If you hand
allem initan agent key while two coding agents are installed, it refuses rather than half-working: the platform would reject every event from the second one. Run it once per coding agent with--only, or use an organization key, which gives each of them its own key in a single run. - Registration for Codex and Gemini CLI is unverified against a live install — see the table above. The deny shape for those two is written from their documented behaviour; if it is wrong, the agent ignores it and the action proceeds, which is the right way for that uncertainty to land.
- Per tool call, one process. The hook starts a Python interpreter for each tool call. Unchecked
calls exit before importing anything that touches the network, and the whole process has a hard
5-second ceiling (
ALLEM_HOOK_DEADLINE_S, which takes a positive number up to an hour; anything else —0, a negative,inf, a word — falls back to five seconds and records that it did, because a ceiling an environment variable can switch off is not a ceiling).
How your connection is recorded
An agent connected by a coding-agent hook must not display the same assurance as one wired through the SDK, so Allem stores how each agent is connected and shows it wherever its governance is shown. The value is never taken from anything an event says — the agent being judged does not get to choose how trustworthy its own record looks. It is stamped on the key at the moment the key is issued, using your organization key, which the agent does not hold.
| How you got the key | Recorded as |
|---|---|
allem init |
a coding-agent connection (Tier 2) |
allem provision --for sdk |
the Allem SDK (Tier 1) |
allem provision --for mcp |
the Allem MCP server (Tier 3a) |
allem provision --for proxy |
a proxy (Tier 3b) |
| A key created in the console | "connected over the API, connector not identified" |
allem init always declares a coding-agent connection and has no flag to say otherwise. It
installs a hook; a hook does not block anything while Allem is unreachable, and if this command could
be told to claim Tier 1, your coverage report would state that a hard gate held during an outage when
it did not.
Allem describes these as declarations, never as something it observed: once events start arriving, the SDK, a coding-agent hook, an MCP server and a proxy are indistinguishable on the wire.
For María — what a customer actually experiences
A developer at a company that just signed up opens the Allem console and lands on Get Started. Step one says "Connect your agent". They pick Claude Code and see a single line with their key already filled in, which they copy and paste into a terminal.
It takes about ten seconds. The command tells them what it found on their machine — "Claude Code →
/Users/them/.claude/settings.json" — asks once whether to write to it, and then reports each step
in plain words: the configuration file it wrote, that it backed up their existing settings first,
that it sent a test event and Allem accepted it, and how many of their own rules are now being
checked. It finishes by telling them exactly where to look in the console, and that they can run
allem uninstall whenever they want.
They go back to work. The next time they use Claude Code, every command it runs and every file it edits appears in Allem — with passwords and API keys stripped out before anything is sent. In the console, the Get Started step turns green and they can watch their own agent being observed, which is the first moment Allem is real to them rather than a description.
What they are told, in the terminal and in the console and in this document, is that Allem records what their coding agent does, and blocks only the actions they have explicitly marked as hard gates. Out of the box that is nothing at all: a new customer has no hard gates, so on day one Allem watches and never interrupts. The first time it stops something will be because they asked it to, on a rule they wrote.
They are also told that Allem only sees what the coding agent does through Claude Code — if the same person deletes a file by hand, Allem does not know. We say that first, before they discover it, because a customer who finds out later that a tool saw less than they assumed does not trust the parts that were true either. Nothing else about Allem works if that is not the habit.
And the thing they will never see, which took an incident to get right: if any piece of Allem goes missing from their machine — our config, our code, our server — their coding agent carries on exactly as before. It does not error, it does not stall, it does not ask them to debug us. A governance tool that breaks a developer's session because we shipped a bad path is uninstalled within the hour, and rightly. The only interruption they will ever get from us is the one they configured.
Metadata
Release files for allem-cli 0.1.1
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 | |
|---|---|---|---|---|
| allem_cli-0.1.1.tar.gz | 214.2 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| allem_cli-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 342.2 kB
Release files / allem_cli-0.1.1.tar.gz
| Download URL | allem_cli-0.1.1.tar.gz |
|---|---|
| Size | 214.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
117f8ab86c3413c31aef64dbe0a969df4095c6f6d531d3e4f12b620d11ffd8cf
|
|
BLAKE2b-256 checksum How to use checksums |
8f279177421562b820cb54756163a4296d2ada4a44ff8206a0ebb0c5ab6d9ec7
|
| 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 13, 2026.
Transparency logRelease files / allem_cli-0.1.1-py3-none-any.whl
| Download URL | allem_cli-0.1.1-py3-none-any.whl |
|---|---|
| Size | 128.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d1608bc70a9f95a1281f122069d5cff561af0addc2ecfd882f8816ec559d5752
|
|
BLAKE2b-256 checksum How to use checksums |
0f6611d98eba7e5c31b531aa36e8568076387507105118a512c0125d71ba904a
|
| 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 13, 2026.
Transparency logSecurity for allem-cli 0.1.1
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 2 files in this release have been uploaded with provenance metadata.
Attestation for allem_cli-0.1.1.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 13, 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.