hitchrail
Start and stop headless Claude Code sessions across a folder of projects, from a phone-first web UI.
A web UI for starting and stopping headless Claude Code sessions across a folder of projects. Open it on your phone, tap a folder, get a session link.
Status: it runs, and it is published.
uvx hitchrail --root main=~/projects
The configuration and its refusals, the folder discovery that makes each root a hard boundary, the security controls between a web page and a shell, the adapters, the engine, the HTTP API and the browser interface are all built, and more than one root of projects is supported. It has been driven from a real phone against a real machine.
No phase count here, deliberately: this line named "phases 0 to 6" for long
enough to be wrong by several. docs/roadmap.md is the one
place that says what is built, and a test asserts this file has not gone back to
claiming otherwise.
See docs/roadmap.md for what is left,
docs/superpowers/specs/2026-08-25-hitchrail-design.md
for the design, and docs/tech-guidelines.md for the
engineering rules that govern the code.
What it will do
Point it at a directory. It lists every folder inside, shows which ones have a live Claude Code session, and lets you start or stop one with a single tap. It shows memory pressure, refuses to start a session that would exhaust the machine, and tails a session's output when you want to know what it is doing.
Stopping is a sequence rather than a button: it asks the agent to wrap up, shows you the wait, and keeps a kill control within reach the whole time if you would rather not wait.
Why you'd use this
You started a session before leaving the desk, and now need to check on it, stop it, or read what it did, with no shell in reach. The browser interface does all three from a phone: the log tail, the stop sequence with its escalation, and Stop all when you are done for the day.
You have several project folders and cannot tell which ones have a live
agent in them. Each one is derived independently and shown as running,
stopped, detached with its pid, or stale. Guessing wrong has a cost:
treating a detached agent as stopped leaves it consuming memory unseen,
and treating a project whose terminal died as still running leaves you
waiting on nothing.
You do not want an agent to run the machine out of memory. Hitchrail shows memory pressure and refuses to start a session that would exhaust it, rather than letting you find out by an OOM kill.
You keep two unrelated groups of projects apart, a work root and a
personal one. A project's identity is its root's label plus its folder, so two
projects both called vessel in two roots are two separate rows, told apart
by a chip.
What it looks like
The phone case first, because it is the one this exists for.
Four derived states in one listing: running with its memory and uptime,
stopped, detached with the pid of an agent that outlived its terminal, and
stale where a terminal outlived its agent.
More than one folder of projects, told apart by the root each row is in:
Two projects called vessel in two roots are two rows, and the chip is the
only difference between them. Stopping one leaves the other's agent alone,
which is the thing a browser test asserts on a real tmux rather than a fake.
The first page anyone reaches, before any of the above:
Tapping Logs shows the pane's own last lines, not a summary of them:
Starting a project Hitchrail has not seen before offers a folder to create rather than requiring one to already exist:
These are captured from the running application against a scratch root, not
taken by hand: uv run pytest -m screenshots regenerates every one of them.
What it costs you to run this
Hitchrail starts claude --dangerously-skip-permissions. Anyone who can reach
its API can run arbitrary code on that machine as you.
Every control below is built and tested, including on a real socket rather than only in theory, and the API is now behind them. None of it is optional, and none of it is a reason to run this on a network you do not trust.
The browser interface is built. The list, search and filtering by state and by root, starting, the stop sequence with its escalation, Stop all composed from it, the log tail on the page and at a URL of its own, creating a folder, the memory footer, live updates over SSE with reconnection, the token screen and the dark theme all work in a browser, and the end to end tier drives them. The warning above applies to all of it exactly as written.
It binds to loopback with no authentication by default. Binding it to any other
interface requires a token, and the server refuses to start without one. It
validates the Host header on every request, because a localhost service
without that check can be driven by any website you visit, through DNS
rebinding. Over plain HTTP on a LAN the token crosses the network in cleartext;
--tls-cert and --tls-key serve HTTPS from the server itself, with the
certificate for a private address coming from a local CA you also trust on
the phone (docs/guides/phone-access.md, route 2a), or put a TLS terminating
reverse proxy in front of it.
Behind such a proxy, tell Hitchrail the origin the browser will actually send, because it cannot be derived: the scheme and the port are the proxy's, not ours.
hitchrail --root main=~/dev --host 0.0.0.0 --allow-host box.lan \
--allow-origin https://box.lan
A trailing root dot makes no difference here: box.lan and box.lan. name the
same machine, so either spelling is accepted and either is matched. Browsers do
send the dotted form, because typing http://box.lan./ is a way to force
absolute resolution on a split horizon network.
Getting the token onto a phone is a link rather than 32 characters of typing.
Open http://<address>:8787/grant#token=<token> once. The token is everything
after the #, and a fragment is never sent to a server: not to Hitchrail, not
to a reverse proxy, and not in a Referer header. The page reads it in the
browser, trades it for a cookie, and clears the address bar.
hitchrail prints that link for every address it can be reached on, so it is
copied rather than typed.
Treat the link as a secret anyway, because it is one, and the phone it lands on
is where it now lives. What changed is the set of machines that write it down.
Every server side one is gone: this server's access log, any proxy in front,
and the Referer header on anything the page fetches.
The browser is narrowed rather than cleared, and the difference is worth stating rather than rounding off. The page rewrites its own history entry, so the entry does not keep the key. Pasting the link into the address bar is another matter: that can leave a typed URL in autocomplete, and autocomplete syncs. Open the link by tapping it rather than by pasting it, and the distinction does not arise.
The older ?token=<token> form is gone. It is a query parameter now, not a
credential: a request carrying one is refused like any other request with no
token, and it appears in the server's log like any other query string.
Hitchrail does not sandbox the sessions it starts. It is a launcher. The agent it launches has whatever access you have.
Whoever holds the token can cause characters to be typed into any agent session under your root. Stopping an agent works by sending it keystrokes through its terminal, and an agent reading its own input cannot tell those from you typing. That is what makes a gentle stop possible at all, and it is worth reading rather than discovering. Hitchrail only ever sends the stop sequence, and one test enforces that only the module owning it may send anything.
Hitchrail can end a detached agent, the state where a process outlived
its terminal, and that is the one thing it destroys by pid rather than by a
session name it created. Everything else it can destroy is addressed by the
tmux session it made, which cannot reach anybody else's. The End control on
a detached row signals a pid, so it is scoped by a check instead: the pid
comes from Hitchrail's own derivation, a handle to that exact process is
taken before anything is re-checked, and a pid another process has since
taken over, a process a visible session owns, another user's, or this
server's own tree, is refused with nothing sent. The confirmation says what
Hitchrail knows: it can see no session that owns the agent, and if it is open
on a screen somewhere, this will end it there too.
Found a hole? SECURITY.md says what is in scope, what is
this design rather than a bug, and where to report privately. Please do not
open a public issue for a vulnerability.
Install
Hitchrail is a Python package, so the equivalent of npx here is uvx:
uvx hitchrail --root main=~/projects # run it, install nothing
uv tool install hitchrail # keep it on PATH
pipx install hitchrail # if you already live in pipx
pip install hitchrail # into the environment you are in
One word, no hyphen. It is on PyPI as
hitchrail, and it needs Python 3.11 or
newer.
pip is last because it installs into whatever environment happens to be
active, which the PyPI page's own install line does not mention. On a modern
Debian or Ubuntu that is an externally managed system Python, and pip
refuses with error: externally-managed-environment rather than installing
into it; the three lines above it all sidestep that by managing their own
environment or PATH entry for you.
uv tool install, not uvx, if you are going to run it as a service. uvx
resolves and runs out of a cache it is free to evict, which is what makes it
right for trying something and wrong for a systemd unit: that needs an
executable still there next month. See packaging/hitchrail.service, whose
ExecStart names ~/.local/bin/hitchrail for exactly that reason.
Upgrading
Which command you need depends on which line above you used.
uvx re-resolves against PyPI on close to every invocation, so running
uvx hitchrail ... again already picks up a new release. If a cached
resolution is still stale, force it with uvx hitchrail@latest, or clear the
cache entirely with uv cache clean hitchrail.
uv tool install, which is what packaging/hitchrail.service runs, does
not notice a new release on its own. uv tool upgrade hitchrail fetches it,
and then, because the running process keeps serving the old code until it is
restarted, systemctl --user restart hitchrail for the packaged unit. Either
way, hitchrail --version is how you confirm the upgrade actually landed.
Prerequisites
Hitchrail is a launcher, so the things it launches have to already be there. It does not vendor or install any of them.
| Needed | Why | Checked |
|---|---|---|
| tmux | every session Hitchrail starts lives in a tmux session; this is the whole mechanism, not an option | tmux -V |
Claude Code on PATH |
it is what Hitchrail runs. Configurable with --agent-binary. The binary is self contained: no node, no npm, whichever installer you used, because the npm package ships the same native executable |
claude --version |
| Linux | memory pressure is read from /proc/meminfo, and the process table from ps. macOS has neither in this form, which is why the package declares Operating System :: POSIX :: Linux |
|
| Python 3.11+ | uvx and pipx handle this for you; pip needs it already there |
python3 --version |
Hitchrail installs no runtime and checks no version of one. That table is
the whole list, and the agent needs nothing beyond itself. Your own PROJECTS are
a separate question: if an agent works in a node project it needs node on its
PATH, and under a systemd unit that PATH is the unit's Environment=PATH, not
your login's. That is your line to set, not something Hitchrail can know.
Installing Hitchrail with uvx will succeed on a machine with no tmux and no
Claude Code, because neither is a Python dependency. It will then fail at the
first attempt to start a session. Check the two commands above first.
The usual setup
Several folders of projects, always on, reachable from your phone. That is what most people want, so here is the whole of it. It takes about two minutes and every line is explained afterwards.
The example uses four roots. Use your own paths and your own labels.
Point it at a scratch folder the first time. Hitchrail only recognises the tmux sessions it started itself, so starting a project that already has a session from another tool gives you a second agent in the same directory. Once you have seen it work, swap the roots for your real ones.
# 1. Install it so the path is stable. Not `uvx`: that runs out of a cache it
# is free to evict, and a service needs an executable still there next month.
uv tool install hitchrail
# 2. A token that survives restarts, in a file only you can read.
mkdir -p ~/.config/hitchrail
printf 'HITCHRAIL_TOKEN=%s\n' "$(python3 -c 'import secrets;print(secrets.token_urlsafe(24))')" \
> ~/.config/hitchrail/env
chmod 600 ~/.config/hitchrail/env
# 3. The folders, in a config file. Mode 644: a file others can write is
# refused, because it decides where an agent may run.
$EDITOR ~/.config/hitchrail/config.toml
chmod 644 ~/.config/hitchrail/config.toml
[[roots]]
label = "work"
path = "~/work"
[[roots]]
label = "personal"
path = "~/projects/personal"
[[roots]]
label = "homelab"
path = "~/projects/homelab"
[[roots]]
label = "confidential"
path = "~/projects/confidential"
enabled = false
# 4. The unit template, then edit the address into it.
mkdir -p ~/.config/systemd/user
curl -fsSL https://raw.githubusercontent.com/agigante80/hitchrail/main/packaging/hitchrail.service \
-o ~/.config/systemd/user/hitchrail.service
$EDITOR ~/.config/systemd/user/hitchrail.service
The one line to change is ExecStart. Point it at the address your phone
will use; the roots come from the file:
ExecStart=%h/.local/bin/hitchrail \
--host 192.168.1.10 \
--self-project work~hitchrail
# 5. Start it, and make it survive logout and reboot.
systemctl --user daemon-reload
systemctl --user enable --now hitchrail
loginctl enable-linger "$USER"
# 6. Read the token, and open the link on your phone.
cat ~/.config/hitchrail/env
# http://192.168.1.10:8787/grant#token=<the value from that file>
enable-linger is what makes the PATH in that unit matter. It starts the
user manager at boot, before any login, when the manager's PATH is systemd's
fallback and does not include ~/.local/bin. The template sets its own PATH so
it can still find the agent. An interactive test will pass either way, because
starting the unit by hand happens after a login has already fixed the PATH,
which is what makes this show up only after a reboot.
If your agent needs something outside those directories, add it to that line, and prefer a stable path over a version pinned one: a pinned one goes stale at the next upgrade and fails at the next boot rather than at the upgrade.
Settings, from the phone. The footer's "settings" link shows what this
instance is pointed at: every root, the bind, the allowlists, the agent, the
prefix, and where each came from, as text. Two things can be changed there
and they are the only two: a root already in the file can be hidden from the
list and shown again, and the wait before a stop is reported as unanswered.
Both are kept in ~/.config/hitchrail/state.toml, which is Hitchrail's own.
Everything else is the perimeter and changes only in the config file or on
the command line, on the machine.
The picture above is the update below, finished: see "Updating the agent's plugins".
journalctl --user -u hitchrail shows the startup banner, which lists every
address the server will answer to. It prints the links without the #token=
fragment on purpose, because the journal is persistent and readable by root and
by the systemd-journal group, and it expects you to append the value you
already have.
What each part of that is doing
[[roots]], one table per folder. Every directory directly inside each
root becomes a row. The label becomes part of the project's name, so
work~vessel and personal~vessel are two projects rather than one ambiguous
row, and stopping one leaves the other alone. The label is required even with a
single root: if one root were unlabelled, adding a second later would rename
everything you had saved a link to. A root inside another root is refused at
startup, naming both. enabled = false keeps a root configured, out of the
listing, and closed to Start: nothing is spawned there until you change the
file, though an agent already running there can still be stopped. Hiding
from the interface is weaker: it only leaves the list, and that choice is
kept in state.toml beside the file. Nothing the interface does can add
a folder: the file is read once at startup, edited on the machine, and never
written by Hitchrail. --root LABEL=PATH on the command line still works and
wins outright over the file.
--self-project. The folder Hitchrail itself runs from, if it is inside one
of your roots. Its row then refuses to be stopped, so you cannot end the session
you are using to end sessions. Omit it if Hitchrail is not in a root.
--host 192.168.1.10, and never 0.0.0.0. Naming one address is the whole
difference. The wildcard is not a shortcut for it: it means every interface this
machine has, including the VPN tunnel and the dozen Docker bridges you forgot
about, and Hitchrail will not offer it as a link because it is not an address
anybody can open.
The token in an EnvironmentFile at mode 600. A generated token changes on
every start, so a service that restarts would invalidate the link saved on your
phone with each one. Anyone who can read that file can run code as you, which is
what the mode is for. --token on the command line would do the same job and
show it to every other account on the machine, because /proc/<pid>/cmdline is
world readable and /proc/<pid>/environ is not.
A user unit, not a system one. Hitchrail spawns agents as you, reading your
~/.claude state and your projects. A system unit would want a User= and
would invite running a shell as root.
Restart=on-failure, not always. Hitchrail refuses to start on a
configuration it judges unsafe. Those refusals are deliberate stops, and
always would turn each into a boot loop that buries its own explanation.
Before you enable it
An always on service is a standing exposure rather than a session shaped one. Until now the window in which this was reachable was the window in which you were sitting at the machine watching it. A unit removes that coupling: it is reachable while you sleep, and on whatever network the machine joined when it woke up.
The address above is the second of three answers to "how does my phone reach
this", and it is second for a reason. It needs nothing installed and it is
correct while you are on a network you trust. Nothing will tell you when the
machine joins one you do not, unless you ask: --expect-gateway-mac names
the default gateway of the network you meant, and a start anywhere else
refuses and stays stopped until you look. That is a guard against carrying
the machine somewhere by accident, not against somebody on the LAN who can
present that address, and it is checked once, when the unit starts: a
network joined while it is running is noticed at the next start.
docs/guides/phone-access.md is that decision in
full, best first: an overlay network such as tailscale serve, which opens no
inbound port and stays correct when the machine moves; then the named address
above; and never the wildcard.
Whichever you choose, the token is the only control. There is no second factor and no source address restriction, and over plain HTTP the cookie it becomes crosses your network in cleartext on every request.
Run it
To try it without a service, or from a checkout. It needs uv, tmux and
Claude Code on PATH, per the table above.
uvx hitchrail --root main=~/projects # published, installs nothing
git clone https://github.com/agigante80/hitchrail # or from source
cd hitchrail
uv run hitchrail --root main=~/projects
--root is label=path, and it repeats. What the label buys, and why a root
inside another root is refused, is under
the usual setup above.
On loopback that is all, and loopback is the default. Bind to the machine's LAN address instead and it prints a link to tap:
uv run hitchrail --root main=~/projects --host 192.168.1.10
token: <generated>
Anyone with this token can run code on this machine as you.
Open one of these on your phone:
http://192.168.1.10:8787/grant#token=<generated>
A token is generated and REQUIRED as soon as anything outside this machine can
reach Hitchrail. Binding off loopback is one way to say so; passing
--allow-host or --allow-origin for a name that is not loopback is the other,
because that is what you do to put Hitchrail behind a proxy such as
tailscale serve. In both cases the server refuses to start without one. Everything after the # stays in the browser and
reaches no server log. Over plain HTTP the cookie it becomes still crosses your
network in clear; --tls-cert and --tls-key, or TLS in front of it, end
that.
Every option
hitchrail --help is the authority and prints this list; it is repeated here
because a person choosing whether to install something should not have to
install it first.
| Option | Default | What it does |
|---|---|---|
--root LABEL=PATH |
the config file | A labelled folder holding projects. Repeatable, and the label becomes part of every project's name, so work~vessel and personal~vessel are two projects rather than one ambiguous row. Given, it replaces the file's roots outright; absent, the roots come from the file, and neither means Hitchrail refuses to start |
--config FILE |
~/.config/hitchrail/config.toml |
The config file: [[roots]] tables and session_prefix, read once at startup. A file that does not parse, has a key Hitchrail does not know, or is writable by others refuses to start |
--host |
127.0.0.1 |
Address to bind. The default is the safe one: loopback |
--port |
8787 |
Port to bind |
--token |
generated | Required as soon as anything off this machine can reach Hitchrail. Prefer HITCHRAIL_TOKEN; see below |
--allow-host |
none | An extra hostname the server will answer to. Repeatable. Needed behind a proxy |
--allow-origin |
none | An exact origin a browser may claim, scheme://host[:port]. Repeatable. Needed behind a TLS terminating proxy, whose scheme and port cannot be derived from our own bind. With --tls-cert set, a plain http:// origin off loopback is refused, since the Secure cookie would never come back on it; http://localhost is allowed, and works in Chrome and Firefox, not Safari. Behind a proxy, bind to loopback: the session cookie is Secure there, and on a LAN bind it cannot be, because a browser reaching the LAN address in the clear would throw it away |
--self-project |
none | A project that must never be stopped, named as label~folder. Point it at the folder Hitchrail itself runs from |
--agent-binary |
claude |
The agent executable to run. Must be on PATH or an absolute path |
--tls-cert, --tls-key |
none | A PEM certificate and its key: serve HTTPS from the server itself. Both or neither, refused at startup before the bind when one is missing or the pair cannot be loaded. The key must be unencrypted, and one with a passphrase refuses saying so rather than prompting, because under the unit there is no terminal to prompt at. Derived origins, banner links and the cookie's Secure flag follow |
--expect-gateway-mac |
none | Refuse to start unless the default gateway has this MAC address, read from /proc/net/route and /proc/net/arp. A guard against a laptop serving on a network it joined by accident; a MAC is spoofable, so not against an attacker on the LAN. Checked once at start. A mismatch is exit 2, which the unit keeps stopped; "cannot tell" (no route yet, no ARP entry, a pinned entry) is exit 3, which it retries |
--session-prefix |
hr- |
What every tmux session this instance creates is named with, and the only sessions it will ever stop. Two instances on one tmux server need two prefixes: with one, each reads the other's agent in a same named folder as its own and can stop it. Also session_prefix in the config file |
--stop-timeout |
30 |
Seconds to wait for a graceful stop before reporting that it timed out, at most 3600. It reports; it does not escalate |
--version |
Print the version and exit | |
-h, --help |
Print the options and exit |
One subcommand, and bare hitchrail still means the server.
Updating the agent's plugins
hitchrail update-plugins # or --agent-binary /path/to/agent
Refreshes the agent's marketplaces, then updates every plugin installed at
user scope, and prints one line per plugin. No server is started and no
root is needed. Exit 0 when nothing failed, 1 when a plugin failed, 2 when
the update could not run at all: the agent is missing, the marketplaces did
not refresh, or the list of installed plugins could not be understood, in
which case nothing is updated rather than the part that could be read.
- Only
userscope is updated. A plugin installed for one project belongs to that project's folder, which the agent's list does not name, so it is reported asskippedwith its scope rather than updated from the wrong folder. - Running sessions keep the old version until they are restarted.
- An install command a marketplace declares is approved without being shown to you, because there is no terminal to show it on. When the agent reports which command it approved, the line for that plugin names it.
The same update runs from the phone: Update plugins on the settings page, with each plugin's result appearing as it finishes. One run at a time, for the whole machine; a second press while one runs is refused, and a page opened in the middle of a run shows where it is.
Where the token comes from
In order: --token, then HITCHRAIL_TOKEN in the environment, then one
generated for you and printed.
Prefer the environment variable to the flag on any machine you share. On
Linux /proc/<pid>/cmdline is world readable and /proc/<pid>/environ is not,
so --token shows your token to every other account on the box, and ps does
it for them without their having to try. The environment is readable only by
you and root.
HITCHRAIL_TOKEN set but empty is refused rather than treated as absent. An
operator who writes it into a file and leaves the value off has not configured
authentication, and Hitchrail says so instead of quietly generating one.
It is also what makes a long running Hitchrail usable: a generated token changes on every start, so a service that restarts invalidates the link saved on your phone. A token from the environment survives.
Keeping it running
Hitchrail dies when you close the terminal, and a phone is useful precisely when
you are not at the machine. The usual setup is the whole
recipe, and packaging/hitchrail.service is the template it copies, with the
reasoning for each line in comments.
It is written up there rather than here because it is what most people want rather than an appendix, and it is written once because two copies of a setup guide is one copy that goes stale.
Working on it
uv sync # set up
uv run pytest # tests
uv run ruff check # lint
uv run ruff format # format
uv run mypy # types
uv run lint-imports # module boundaries
All five are blocking in CI on 3.11, 3.12 and 3.13. The last one is the
unusual one: it enforces that the engine layer never imports Starlette,
uvicorn, sse_starlette, the server or the CLI, so the engine stays testable
without HTTP. Import boundaries defended only by good intentions do not
survive.
Documents
docs/api.md |
the HTTP API: routes, auth, and every error code |
docs/roadmap.md |
what is built, and what is planned next |
SECURITY.md |
what is in scope, and where to report it privately |
CONTRIBUTING.md |
how a change is expected to arrive |
CHANGELOG.md |
what upgrading costs you |
docs/releasing.md |
how a release is cut and published |
Not affiliated with Anthropic
Hitchrail is an independent open source tool. Claude and Claude Code are trademarks of Anthropic.
Licence
MIT.
Metadata
Release files for hitchrail 0.11.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 | |
|---|---|---|---|
| hitchrail-0.11.0.tar.gz | 362.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| hitchrail-0.11.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 779.7 kB
Release files / hitchrail-0.11.0.tar.gz
| Download URL | hitchrail-0.11.0.tar.gz |
|---|---|
| Size | 362.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f31833cb10760d715b17fe454c5e499c0deb1aa95a6080d4f3fc997e9e1223c7
|
|
BLAKE2b-256 checksum How to use checksums |
b4eb1d1c2b5c6492e784efcf5a63112a9184dbeaac7f10b1ca335b4d59305105
|
| 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 Sep 28, 2026.
Transparency logRelease files / hitchrail-0.11.0-py3-none-any.whl
| Download URL | hitchrail-0.11.0-py3-none-any.whl |
|---|---|
| Size | 416.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a40b63cbe667dfb848bb6add27756895f58f343f0460829b9078390570eb0279
|
|
BLAKE2b-256 checksum How to use checksums |
4eb179632f2a1449792462d22cccf3a4e8afddf9859e83c16cd4a3f53340e018
|
| 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 Sep 28, 2026.
Transparency log