agent-toggle
Temporarily disable and restore AI-agent resources — skills, agents, commands, rules, plugins, MCP servers — across Claude Code, Codex, Grok CLI, OpenCode, OpenClaw, Copilot, Vibe, Devin and Antigravity — and show what each one costs at session start.
Nothing is ever deleted. Everything is parked and recorded, and enable
puts it back where it came from.
Why
| target | native mechanism | the gap |
|---|---|---|
| plugin | claude plugin disable/enable |
fine — this tool just batches it |
| skill | none | moving files by hand hits the rename trap below |
| agent | none | same |
| command | none | same, plus nested group/name.md paths get flattened |
| MCP | remove only |
the config is gone unless you saved it first |
Install
Latest release (on PyPI from the first tagged release):
uv tool install agent-toggle
agent-toggle --version
Pre-release versions from this repository:
uv tool install git+<repo-url>
agent-toggle --version
Alternatively, use pipx:
pipx install agent-toggle # from the first tagged release; or: pipx install git+<repo-url>
From a checkout, either install it editable or run it in place:
git clone <repo-url> agent-toggle
cd agent-toggle
pip install -e . # puts the `agent-toggle` console script on PATH
python3 agent_toggle.py status # no install needed; same CLI
Python 3.10+, no runtime dependencies. Then write the skill shim:
agent-toggle install-shims # installed, or: ./install.sh (a thin wrapper)
python3 agent_toggle.py install-shims # from a checkout, no install needed
install-shims writes a thin skill shim (skills/agent-toggle/SKILL.md) into
every installed harness that supports skills, so /agent-toggle works from any
of them, and adds the park dirs (skills-disabled/ etc.) to a .gitignore
already present in that harness home. It is idempotent; --dry-run shows the
plan. The tool itself stays in one place.
Usage
agent-toggle <command> [args] # or: python3 agent_toggle.py <command> [args]
| command | what it does |
|---|---|
ui |
interactive picker — cost column, sort, filters; --dry-run shows the plan for what you stage and changes nothing |
status |
health check: harnesses found, types each supports, parked counts, gitignore, untracked parked items, stale live twins, shared dirs |
list [type] |
what is currently disabled (--project <dir> filters to one project) |
cost [--type T] |
estimated startup tokens per item, biggest first (read-only; --harness H filters) |
install-shims |
write the skill shim into every installed harness (--dry-run shows the plan) |
disable <type> <name>... |
park one or more items (--dry-run shows the plan; --project <dir> for a repo's own .claude/ and .mcp.json) |
enable <type> <name>... |
put them back (--dry-run shows the plan; --project <dir> likewise) |
enable --all |
put back every disabled item (--harness H narrows it, --project <dir> takes only that project's) |
undo |
reverse the last logged batch (--dry-run shows the plan) |
profile save|apply|diff|list |
named sets of live items; see Profiles |
doctor |
read-only check of each harness layout and of state.json against disk; exit 1 only on an error row |
migrate |
import an older ~/.claude-toggle/ state |
<type> = skill / agent / command / rule / plugin / mcp.
rule is claude-only (~/.claude/rules/*.md, parked in rules-disabled/).
Flags accepted by every command, before or after the subcommand:
--harness claude|codex|grok|opencode|openclaw|copilot|vibe|devin|agypicks the target (defaultclaude; onlist/status/costit filters when given).--jsonprints exactly one JSON document and nothing else on stdout:{"ok", "command", "results": [{harness, type, name, action, status, detail, ...}], "warnings", "needs_new_session"}. Errors -- including unexpected ones, as{ExceptionType}: {message}-- always emit it, with"ok": false. Exception:--help/--versionprint plain text even with--json, anduiis interactive so it rejects--json(exit 2).--project <dir>(disable/enable/enable --all/list/profile save|apply|diff) switches to project scope; see Project scope.--versionprints the version.-v/--verbose(orAGENT_TOGGLE_DEBUG=1) adds a traceback on stderr for unexpected errors; otherwise they are a singleerror:line.--color auto|always|neversets ANSI color for human output: green ok, red errors, yellow warnings, cyan harness names, dim secondary text (status,list,cost,disable/enable,doctor, warnings and errors; the picker uses curses color pairs and stays monochrome when the terminal has no colors).--jsonis never colored.neverturns it off andalwaysforces it even when piped;auto(default) checksNO_COLOR, thenFORCE_COLOR, thenTERM=dumb, and otherwise colors only on a TTY.alwaysstill stays plain on a Windows console that cannot do ANSI.
Extra row fields: list rows carry at, mechanism, companions; cost rows
carry enabled, tokens, would_save, chars, and the final total row
carries total_tokens, saved_tokens, formula. Rows for a
directory shared with another harness (see below) carry shared_with, the other
harnesses that read it: disable / enable rows relative to the harness you
asked, plus owner, the harness whose park dir and state entry hold the item;
list and cost rows are filed under the owner (shared_with is [] when
unshared).
--dry-run (disable / enable / enable --all / undo / profile apply /
install-shims / ui) computes the plan --
moves, companions, backups, MCP edits, warnings -- and writes nothing: no state,
log, lock or backup, and no chmod; it never shells out to claude. disable /
enable result rows have action would-disable / would-enable; every plan
row has status planned. Read-only commands (list, status, cost) also
change nothing on disk -- status warns about a state.json or
backup looser than 0600 instead of fixing it. A dry run whose plan contains a
failing item exits 1, like the real run would.
| exit code | meaning |
|---|---|
0 |
ok |
1 |
partial failure (some items failed), or an unexpected error |
2 |
usage error, including an unknown harness or type |
3 |
locked by another run |
4 |
unsupported harness/type pair, or harness not installed |
agent-toggle disable skill demo-skill other-skill
agent-toggle disable command demo:batch # nested commands/demo/batch.md
agent-toggle disable agent demo-agent
agent-toggle disable mcp example-mcp
agent-toggle disable skill demo-skill --harness codex
agent-toggle enable mcp example-mcp
agent-toggle cost --type skill --json
A colon addresses nesting: demo:batch is commands/demo/batch.md.
Interactive picker
agent-toggle ui
filter: telegram█
*[x] (92) claude command telegram-summary
[ ] (61) claude skill telegram-display
[x] 48 claude skill telegram-group-send
[x] 20 claude mcp telegram-example
4 shown | ~68 tok | harness:all type:all sort:name | 1 staged -- Enter to apply
Tab tick Enter apply Esc cancel s sort h/t filter ? keys / type to filter
The number column is the estimated startup tokens (chars / 4, about +-25 %);
a parked row shows (N), what restoring it would load. Plugins appear as rows
too (via claude plugin list --json; skipped under ui --dry-run, which never
shells out).
| key | action |
|---|---|
s |
cycle sort: name, cost (biggest first) |
h |
cycle the harness filter |
t |
cycle the type filter |
? |
show the key list |
/ |
start typing a filter |
| any other printable character | appends to the filter (terms are ANDed, case-insensitive) |
Backspace / Ctrl-U |
delete one character / clear the filter |
↑ ↓ / Ctrl-P Ctrl-N |
move; PgUp/PgDn jump a screen |
Tab |
tick / untick the highlighted row |
Enter |
apply every staged change (with --dry-run: show the plan) |
Esc / Ctrl-C |
cancel — nothing is applied |
s, h, t and ? are commands while the filter is empty. Press / first
to type a filter that begins with one of them (the example above is typed
/telegram); once the filter is non-empty, every letter just types.
The checkbox shows the enabled state: [x] is live, [ ] is parked. A
* marks a row you changed.
Nothing happens while the picker is open. Changes are staged, the screen is torn down, and only then do the real operations run — so their output (which companion files moved, which were kept because they are shared) is readable instead of fighting curses for the terminal.
Built on stdlib curses, so there is nothing to install on macOS and Linux
(on Windows, pip install "agent-toggle[windows]" pulls windows-curses).
What each harness supports
| harness | home | skill | agent | command | rule | plugin | mcp |
|---|---|---|---|---|---|---|---|
| claude | ~/.claude |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ ~/.claude.json |
| codex | ~/.codex |
✓ | ✓ | ✓ (commands/ + prompts/) |
— | — | ✓ config.toml |
| grok | ~/.grok |
✓ | — | — | — | — | ✓ config.toml |
| opencode | $XDG_CONFIG_HOME/opencode, else ~/.config/opencode |
✓ | — | ✓ (command/) |
— | — | ✓ opencode.json (flag, assumed) |
| openclaw | ~/.openclaw |
✓ (dir move, or flag when skills.entries.<name> exists, assumed) |
✓ | — | — | ✓ openclaw.json (flag, assumed) |
— (sqlite) |
| copilot | ~/.copilot |
✓ | ✓ | — | — | — | ✓ mcp-config.json |
| vibe | ~/.vibe |
✓ | — | — | — | — | — |
| devin | ~/.devin |
— | — | — | — | — | — |
| agy | ~/.antigravity |
— | — | — | — | — | — |
Copilot's config.json is machine-managed and never edited by this tool; Copilot
plugins are not yet supported. claude plugins are toggled through claude plugin enable/disable; openclaw plugins through a config flag (see Assumed
formats). Codex has no plugin CLI this tool can drive: disable plugin x --harness codex exits 4, which is why the matrix leaves it unchecked.
Flag items (openclaw plugins and flagged skills, opencode mcp) are toggled with
disable / enable, undo and enable --all. The picker, cost and profile
list them live (read from the config file; only entries that carry a boolean
enabled, since a key is never invented) and parked (from state.json); an
openclaw skill whose flag was switched off by this tool is shown as disabled even
though its directory is still in place.
The grok MCP location (~/.grok/config.toml, the same [mcp_servers.<name>]
tables as codex, including a .headers sub-table for remote servers) was
confirmed on a live install. Backups keep every sub-table verbatim.
Unsupported pairs fail loudly. OpenClaw keeps MCP servers in
state/openclaw.sqlite, not a file this tool can safely slice, so it refuses
rather than guessing.
Shared directories
OpenCode may read skills from another harness's directory through
opencode.json → skills.paths (absolute, ~/-prefixed or relative-to-the-
opencode-dir entries; a bare ~ or $HOME/... is not expanded, and
opencode.jsonc is not read). Such a directory is one item, filed under
its owner (the harness whose home really holds it): it is
parked once, tracked once, and every row reports shared_with, the other
harnesses it also affects. status prints shared dir with: ....
If the live directory carries .synced-from-* markers, a sync job may
re-create what you parked; status warns about it. Park in the source harness
instead. The warning is printed once per harness that views the directory.
Companion files
A skill or command often calls a helper next to it — scripts/foo.py,
tg-send.sh. On disable, the item's text is scanned for such references and
each one is classified:
- exclusive (nothing else in the harness mentions it) → parked alongside the item, restored with it.
- shared (anything else references it) → left alone, and reported.
Sharing is a veto, not a warning. tg-send.sh is referenced by five different
commands; moving it because one of them got disabled would silently break the
other four.
Detection reads file text, so a path built at runtime cannot be found. Every companion decision is printed before anything moves, so a wrong guess is visible rather than silent.
MCP backups are verbatim
- Claude: the raw entry from
~/.claude.json. Neverclaude mcp get— that prints a human summary and silently drops auth fields (headers,headersHelper), so a server restored from it fails with 401. - Codex: the exact
[mcp_servers.<name>]text block including its.tools.*sub-tables. Python's stdlib reads TOML but cannot write it, and a hand-rolled serializer would mangle comments — a text slice is lossless and much less code.
MCP changes need a new session to take effect.
Claude MCP scopes
Both scopes stored in ~/.claude.json are togglable, and the scope round-trips:
| scope | lives in | toggle |
|---|---|---|
| user | top-level mcpServers |
✓ |
| local | projects/<dir>/mcpServers |
✓ — project path saved with the backup |
| project | the repo's own .mcp.json |
✓ with --project <dir> only, see Project scope |
| claude.ai connector | your account | ✓ — recorded in each existing project's disabledMcpServers; a project first opened later needs the toggle re-run |
claude mcp remove -s local only sees the project it runs in, so the project
path is recorded at disable time and the restore runs back in that directory.
One name that is local-scope in several projects is refused unless your cwd
picks the winner — guessing would restore it into the wrong project.
Profiles
A profile is a named snapshot of which items are live, for the dotfiles repo
or a second machine. It holds only {harness, type, name, live} per item --
never a path or a secret.
agent-toggle profile save work # -> ~/.agent-toggle/profiles/work.json
agent-toggle profile save work --out ~/dotfiles/agent-toggle/work.json
agent-toggle profile diff work # what apply would do; writes nothing
agent-toggle profile apply ~/dotfiles/agent-toggle/work.json --dry-run
agent-toggle profile apply work
agent-toggle profile list
apply toggles only the items the profile mentions. An item the profile
lists as live but that is parked now is enabled; one it lists as parked but
that is live now is disabled; everything else is left alone -- in particular an
item installed after the save is never touched. An item the profile names that
this machine does not have gets a skipped row (not on this machine), not a
failure. apply runs through the same path as enable / disable (one lock,
one batch, logged, undoable with undo).
- An argument ending in
.jsonor containing a path separator is a file path (absolute paths are fine); anything else is a stored profile name under~/.agent-toggle/profiles...in a path, and names that are not plain file names, are refused (exit2), as are a bad version, an unknown harness or type, an invalid item name, a duplicate item or a file over 1 MiB. --outbelongs tosaveonly;--dry-runtoapply/diff.--harness Hnarrowssave,applyanddiffto one harness.diffandapply --dry-rundo not read plugin state (that needs the claude CLI, and a dry run never shells out), so profile plugin items show asskippedthere.- A user-scope profile never contains project-scope entries; with
--project <dir>the same commands save, diff and apply that project's items (claude layout) instead -- the profile holds no directory, soapplyneeds--projectagain. A project profile records"scope": "project"; applying it without--project(or a user profile with--project) exits2, so a project profile can never disable the user's items. saveskips (with a warning) any item whose nameapplywould refuse, such as an MCP server calledteam/search, so a saved profile always applies.
Undo and enable --all
undo reads ~/.agent-toggle/log.jsonl, takes the batch of the last
successful disable / enable, and reverses its rows in reverse order. The
reversal is logged as its own batch, so undo twice puts things back.
Project rows are replayed in their own project scope, never in user scope.
agent-toggle undo --dry-run
agent-toggle undo
- Nothing logged yet:
nothing to undo(exit0). A log written before batch ids existed:nothing to undo: log predates undo(exit1). undorejects--harness(exit2): it reverses a whole batch.- The log is a plain file you can edit; its names and project dirs are checked like command-line input, and a project dir is trusted as written, exactly as user-scope replay trusts the log.
enable --all [--harness H] [--project <dir>] restores every disabled entry in
scope, each in its own scope (--project takes only that project's).
enable --all <name> and disable --all are usage errors (exit 2). Preview
bulk operations with --dry-run.
Project scope
--project <dir> (. = the current directory) points disable, enable,
enable --all, list and profile at one repo: its .claude/ dir types
(skill, agent, command, rule) and its own .mcp.json servers (mcp).
Claude layout only: --harness codex --project ... exits 4, as does a
missing directory or one with neither .claude/ nor .mcp.json. $HOME, its
ancestors, the tool's own state dir and the harness homes are refused (exit
2) -- that is user scope. list and enable --all run the same checks. cost and
ui are user-scope only.
agent-toggle disable skill demo-skill --project .
agent-toggle disable mcp example-mcp --project ~/work/repo
agent-toggle list --project .
agent-toggle enable --all --project .
-
Parked items go to
~/.agent-toggle/parked/<sha8>/, never inside the project; project and user state entries are separate, so one can never restore into the other. Companion files are not moved in project scope. -
A tracked file still disappears from the worktree. Every project-scope disable prints a warning like this one, and
git statusshows the deletion:<repo>/.claude/skills/demo-skill is a tracked deletion in git status; restore with: agent-toggle enable skill demo-skill --project <repo> <repo>/.mcp.json is a tracked change in git status; restore with: agent-toggle enable mcp example-mcp --project <repo>Restore with that command, or
git checkout; do not commit the deletion if the repo is shared. -
.mcp.jsonis edited directly (noclaudeCLI) and must be strict JSON: a BOM, comments or trailing commas are refused.disablerewrites the file in its detected layout (tabs or 2 spaces, LF or CRLF) and saves a verbatim backup atmcp-backups/<sha8>__claude__<name>.json(mode0600; it holds the file text, so it may hold auth headers).enablerestores the file byte for byte if it is unchanged since the disable, otherwise merges the entry back in and reformats. -
Moves across filesystems fall back to copy + delete (not atomic). An empty
parked/<sha8>/*-disableddir may remain afterenable;doctorignores it. -
statusprints oneproject <dir>line per project holding parked items. A project with only.mcp.jsonsaves only its parked servers inprofile save --project; live ones are not listed (the inventory needs.claude/).
Doctor
agent-toggle doctor [--harness H] [--json]
Read-only: no lock, no state write-back, no claude CLI call. For each
installed harness it compares the live layout with the table row (expected
dirs and config keys), then cross-checks state.json against disk (parked
item present, origin dir present, backup present, project dir present, entry
passes the same tamper checks enable runs, modes no looser than 0600 /
0700). Rows (action: doctor) carry a status:
| status | meaning |
|---|---|
ok |
matches |
absent |
a dir, config file or key the row expects is not there (an MCP file never created, a missing mcpServers key) -- informational, exit 0 |
note |
worth knowing: shared dir, orphan backup, JSONC openclaw.json / opencode.json, a --harness that is not installed |
unverified |
could not be parsed here (an existing codex/grok config.toml on Python 3.10, which has no tomllib) |
warn |
loose file modes; a parked item with no state entry; a leftover parked/<sha8> dir that still holds files (an empty one after enable is ignored) |
error |
needs fixing: a config that exists but is unparseable or unsupported (layout changed), a state entry whose files are gone or fail the tamper checks, a flag re-enabled outside the tool |
Only error makes the exit code 1; each problem row names the command that
fixes it. --harness X for a harness that is not installed is a note (exit
0). Companion files are not checked.
Assumed formats
Two config shapes come from the design survey (docs/DESIGN.md §4 / §11) and
were not verified on a real install:
openclaw.json:skills.entries.<name>.enabledandplugins.entries.<name>.enabledopencode.json:mcp.<name>.enabled
The edit changes one boolean token and nothing else, is verified after writing
and rolled back on any mismatch (bytes and file mode). Files that are not
strict JSON (JSONC / JSON5 -- comments, trailing commas) or that repeat a key are
refused, never rewritten; a leading BOM is kept as is; a missing key is
refused, never invented. An openclaw skill
uses the flag only when skills.entries.<name> already exists, otherwise its
directory is moved. If a run is killed between the flag write and the state
save, the flag is false with no state entry: enable then tells you to set it
back by hand. Please report a real install that differs.
Safety checks
enablerefuses a state entry whoseorigin,parked_at, backup or flag file is outside its harness home, project or~/.agent-toggle, or holds..: an error rowrefused: <reason>, nothing moved.- Every edit of a JSON or TOML config is verified after writing (still parses,
only the target changed) and rolled back, bytes and mode, on failure. An
invalid codex
config.tomlmakes an MCP edit fail and roll back instead of being rewritten. A codexconfig.tomledit also fails (and is left as the other tool wrote it) if the file changed between read and write, and CRLF files keep their line endings. On Python 3.10 (notomllib) the TOML check is textual only: it checks against the original text that only the one block changed, but cannot parse. - Profiles and project dirs are validated like command-line input.
Where state lives
All of it in ~/.agent-toggle/ (mode 0700), never as marker files next to the targets —
your git status in your own project must not change because of our
bookkeeping.
| file | contents |
|---|---|
state.json |
current disabled list (schema v3; atomic write, mode 0600) |
lock |
held by disable / enable / enable --all / undo / profile apply / migrate / ui for the whole batch; a second run waits 5 s then exits 3 (stale after 10 min and its PID is gone; a live batch refreshes it per item) |
log.jsonl |
one line per operation (mode 0600), see below |
mcp-backups/ |
<harness>__<server>.json, or <sha8>__<harness>__<server>.json for a project .mcp.json (mode 0600 -- may hold auth headers) |
companions/ |
parked exclusive helper files |
parked/<sha8>/ |
items parked by --project (<sha8> = first 8 hex of the SHA-1 of the resolved project dir) |
profiles/ |
<name>.json profiles (dir 0700, files 0600) |
Each log.jsonl row is {ts, harness, type, name, action, result, batch, project, scope, detail}. batch is one id per run (what undo reverses);
project is null and scope is user outside project scope (a claude
local-scope MCP row has scope: local and its working directory in project).
Older rows without batch/harness cannot be undone.
Two guardrails, both earned
1. safe_move() never lets the source be renamed into the destination.
mv X dest/ when dest does not exist renames X to dest — the first
item fails, the second "succeeds" by becoming that directory, and SKILL.md
and .git end up scattered at the root. shutil.move() behaves identically.
So: create the directory, prove it is a directory, prove the target does not
exist, and only then touch the source.
Order matters too: mkdir(exist_ok=True) raises FileExistsError when the
path exists as a file, so the is_dir() check has to come before the
mkdir or it is unreachable.
2. Park dirs that are not gitignored get a warning. Without it, every
disable leaves dozens of deletion lines in git status.
Paths are resolved before comparison
~/.claude is often a symlink, and on macOS /var is one. Comparing
unresolved paths makes every companion look like it lives outside the harness.
Both sides are resolved first.
Design and roadmap
Architecture, harness survey, cost model, known gaps and the phased roadmap
toward a public multi-OS release live in docs/DESIGN.md. Release notes are in
CHANGELOG.md.
Tests
python3 -m unittest discover -s tests -v
ruff check .
stdlib unittest, no fixtures, no network. Every test runs against a
throwaway temp HOME and a stubbed claude CLI.
Releasing
A release is a git tag vX.Y.Z (where X.Y.Z must match agent_toggle.__version__).
Pushing the tag triggers .github/workflows/release.yml:
- Build job checks that the tag matches
__version__and builds a wheel and source distribution. - Publish job uploads to PyPI using
trusted publishing,
with no stored tokens—only the
pypienvironment and OIDC setup. - Smoke job installs the published version on macOS under a throwaway
HOMEand runs adisable/enableround trip to verify the install.
Before the first tag, register a trusted publisher for this repository with
PyPI: workflow release.yml, environment pypi. See
PyPI trusted publishers documentation.
Cross-machine behaviour
Disabling is a local decision: park dirs are gitignored and do not sync.
But a disappearance under skills/ is itself a tracked change, so committing
it means other machines lose those skills on pull. The content stays in git
history — git checkout <commit> -- skills/<name> brings it back. Don't
commit those deletions if you don't want them to travel.
Metadata
Release files for agent-toggle 0.1.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 | |
|---|---|---|---|
| agent_toggle-0.1.0.tar.gz | 132.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| agent_toggle-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 214.5 kB
Release files / agent_toggle-0.1.0.tar.gz
| Download URL | agent_toggle-0.1.0.tar.gz |
|---|---|
| Size | 132.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4251d1cf8ad431e2717d499120a3b367a570c7979516c940e750ff4778fd9b3a
|
|
BLAKE2b-256 checksum How to use checksums |
1fb119e7bbffeec5adb83f65eaef16b35852f52ccd9baa8317dfe523d7f1fd6c
|
| 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 2, 2026.
Transparency logRelease files / agent_toggle-0.1.0-py3-none-any.whl
| Download URL | agent_toggle-0.1.0-py3-none-any.whl |
|---|---|
| Size | 81.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a964eb4f4e3f0a36b9ee590460474aeb2b852aab8c245206283746631f21728d
|
|
BLAKE2b-256 checksum How to use checksums |
5dbc12b4966246623d604569841e2cb027f9318b1151992fe7477aa23f4681ef
|
| 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 2, 2026.
Transparency log