CLI for the MTN ZLT T10D MAX (ZTE NV8645) router
Project description
zlt
A small, fast CLI for the MTN ZLT T10D MAX (ZTE NV8645 CPE) 4G router, so you never
have to open the slow web UI. Talks directly to the device's own JSON API
(reqproc/proc_get / reqproc/proc_post) over the LAN.
Verified live against firmware CPE_NV8645_230A_E_QX_CAN-P42U17-20250703.
Disclaimer: This is an independent, reverse-engineered client built by reading the router's own served JavaScript and observing its behavior. It is not affiliated with, endorsed by, or supported by ZTE, MTN, or any carrier. The API it talks to is undocumented and unofficial. It can change or break on a firmware update with no notice. Use at your own risk, especially the write commands (
net set,post,login); see Safety notes before pointing this at a device you can't physically reset.
Overview
- Read signal/network status without logging in (
zlt status,zlt get ...). - Log in exactly the way the web UI does (
zlt login), with a safety guard against triggering the router's login lockout. - Read and change the network mode / bearer preference (
zlt net get,zlt net set). - Raw passthroughs (
zlt get,zlt post) for anything not first-classed above. - Config and session cache live under your home directory (XDG paths), so the command works from any directory once installed.
Compatibility with other ZLT/ZTE devices
Built and live-verified against one device: the MTN ZLT T10D MAX, a ZTE NV8645 CPE
(cr_version: CPE_NV8645_230A_E_QX_CAN-P42U17-20250703, DEVICE: "ufi" in its own
config.js). The underlying reqproc/proc_get + reqproc/proc_post API, the
goformId=LOGIN nonce-salted SHA-256 password scheme, and the CSRFToken/get_token
mechanism are shared across a wider family of ZTE "reqproc" firmware used in many
rebranded 4G/LTE CPE and MiFi routers (other ZLT-branded units, other carriers' rebrands
of the same ZTE hardware), so zlt will likely connect, log in, and read status on
similar devices with little or no change.
That said, don't assume the field-level details carry over unmodified:
- The exact
proc_getkey that holds the configured network mode is not consistent even within this device family: see "Reading back the configured mode" below, where this device needed a different key (net_select) than the one the reference implementation's own JS suggested. - Session handling can differ by firmware build: this device authenticates via a
randomcookie; other ZTE variants (e.g. some Safaricom-branded ZTE M30S Pro units, per community documentation used as a reference during development) instead bind the session to the client's IP with no cookie at all. If porting to another device, verify which model applies before assumingzlt's cookie-based session logic works as-is. BearerPreferencevalues, status key names, and lockout thresholds (MAX_LOGIN_COUNT/login_lock_time) may vary by firmware version even on nominally the same hardware.
If you're trying this against a different ZLT/ZTE router, start with the read-only
commands (zlt status, zlt get <cmd>) before zlt login. They require no
authentication and will quickly show whether the API shape matches.
Install
Needs pipx. It puts zlt on your PATH in an isolated
environment, and works the same on Linux, macOS and Windows.
pipx install zltrouter
The package is zltrouter but the command it installs is zlt, which is
what every example below uses. (Plain zlt is not available on PyPI.)
Then bootstrap your config. It prompts for the router admin password, writes
~/.config/zlt/config with chmod 600, and offers to run the dashboard
automatically on login:
zlt init-config # host/username default to
# http://192.168.0.1 / admin
zlt init-config --host http://192.168.8.1 --username admin
zlt init-config --no-service # skip the autostart offer
See .env.example for the file format if you'd rather write it by hand (or use a
project-local .env during development; never commit real secrets).
Upgrading from a pre-0.4 install
Earlier versions used ./install.sh, which built a project-local .venv and
symlinked ~/.local/bin/zlt at it. pipx will not overwrite a file it does not
own, so remove the old install first. Skipping this leaves a stale symlink
shadowing the real entry point on your PATH.
systemctl --user disable --now zlt-web # if you ran ./service.sh
rm -f ~/.config/systemd/user/zlt-web.service
systemctl --user daemon-reload
rm -f ~/.local/bin/zlt
rm -rf .venv
pipx install zltrouter
Running the dashboard on login
zlt init-config offers this, and you can do it any time:
zlt service install # start on login, bound to 0.0.0.0:8464
zlt service status
zlt service logs
zlt service suspend # stop without uninstalling
zlt service resume
zlt service uninstall
The backend is picked for you: a systemd --user unit on Linux, a LaunchAgent
on macOS, a Task Scheduler on-logon task on Windows. Run
zlt service print-artifact to see exactly what would be written.
One difference worth knowing: on Linux a suspended service returns at your next
login, while on macOS and Windows it stays down until zlt service resume.
The Linux path is tested on real hardware. The macOS and Windows backends have their generated artifacts covered by tests and are exercised in CI, but the
launchctlandschtaskscalls themselves are not yet verified on real machines. Reports welcome.
Config resolution order
- Environment variables
ZLT_HOST,ZLT_USERNAME,ZLT_PASSWORD. $XDG_CONFIG_HOME/zlt/config(default~/.config/zlt/config).- A project-local
./.env, if present.
Defaults: ZLT_HOST=http://192.168.0.1, ZLT_USERNAME=admin. ZLT_PASSWORD is only
required for commands that need to log in.
Session cache (the authenticated cookie): $XDG_STATE_HOME/zlt/session.json
(default ~/.local/state/zlt/session.json), written chmod 600.
Command reference
| Command | Auth? | Description |
|---|---|---|
zlt status |
best-effort | Shows signal/network status. Tries to log in for full detail (adds RSRP, band, SNR); falls back to the open subset if no password is configured or login fails. |
zlt net get |
yes | Shows the router's configured network mode, mapped to a friendly name (auto, lte, 4g3g, wcdma, gsm). |
zlt net set <mode> |
yes | Sets the network mode. <mode> is one of auto | lte | 4g | 4g3g | wcdma | 3g | gsm | 2g. Verifies the POST result, then re-reads to confirm the change took. |
zlt sms list |
yes | Shows the inbox, newest first, id in the first column, unread marked with *. --limit caps how many (default 20). |
zlt sms send <number> <text> |
yes | Sends a message and waits for the network to confirm it. Costs money. |
zlt sms read <id>... |
yes | Marks one or more messages read. The device has no way back to unread. |
zlt sms rm <id>... |
yes | Deletes one or more messages and waits for the device to confirm. Permanent, and it does not prompt. |
zlt get <cmd> [cmd ...] |
no | Raw proc_get passthrough: pretty-prints the JSON response for any key(s) the device supports. |
zlt post <goformId> [key=val ...] |
yes | Raw proc_post passthrough: ensures a session, attaches a fresh CSRF token, prints the JSON response. |
zlt login |
yes | Forces a fresh login, prints attempts remaining before the lockout, caches the session cookie. |
zlt init-config |
no | Interactively writes ~/.config/zlt/config (chmod 600). Flags: --host, --username; password is prompted (hidden input). |
zlt --version |
no | Prints the installed version. |
Every authenticated command transparently logs in first if there's no valid cached
session (ensure_session()), and every write retries once with a fresh login if the
router reports an auth failure mid-request.
Web dashboard
A local browser UI over the same client, for the things a one-shot CLI can't do: watching signal move live while you reposition the router, and flipping network mode with a tap from your phone.
zlt serve # http://127.0.0.1:8464
zlt serve --host 0.0.0.0 # reachable from other LAN devices (see note)
- Pinned meter, tabbed tools: the reading, quality word and range bar sit
above a tab bar and stay on screen whichever tab you are on, so reading an SMS
never scrolls away the thing you opened the dashboard for. The meter folds to a
single line once you scroll. Tabs are
#signal,#messages,#ussdand#speed, so a tab is linkable and the back button moves between them. Each tool panel makes its first request when you first open it, not at page load. - Walk test strip: rolling 15-minute RSRP/RSSI history, so you can carry the router around and watch the line respond. It keeps recording on every tab, so the graph has no holes in it when you come back.
- Live meter and tiles: RSRP (or RSSI when not logged in), band, SNR, RSRQ, PCI, bars, PPP state, polled every 1/3/10s with pause.
- Network mode switching: the same
SET_BEARER_PREFERENCEwrite aszlt net set, verified and re-read after each change. - USSD codes: send a code and see the reply inline; interactive menus can be
replied to and cancelled; saved codes appear as one-click buttons. "Manage"
turns that row into an editor for adding and removing codes, so the default
view stays a clean set of send buttons. The list is the same
~/.config/zlt/ussd.jsonthe CLI uses, so codes saved either way show up in both. - Messages: the SMS inbox with unread marked and counted, plus a compose box for sending. "Select" turns the list into checkboxes with Select all, and marks the selection read or deletes it in one go, the same opt-in pattern the USSD panel uses so a Delete control is never sitting next to a message you meant to read. Deleting asks once, inline. The unread count rides on the Messages tab, seeded by a single read once the first status poll succeeds. After that the inbox is read when you open the tab, after each send, after a mark-read or delete, and on the explicit Refresh, never on the status poll: an inbox read takes the same router lock the signal poll wants, and the device is slow enough that polling both would make the panel fight itself.
- Speed test: an on-demand download/upload/ping test that runs in the browser, so it measures the link of whatever device you opened the panel on (phone included), through the router, out over 4G. The dashboard is not in the data path: proxying the payload would time the serving machine's link instead and push every byte over 4G twice. Both directions are time-boxed rather than fixed-size, so a weak cell finishes in seconds instead of minutes. Default target is Cloudflare's public speed backend; see "Speed test target" to point it elsewhere.
- Light / dark: follows your system theme by default; the toggle in the header overrides it and the choice sticks.
- Served entirely from the local server, zero CDN dependencies and no build step:
it works when the router LAN is your only network.
index.htmlis markup,static/app.cssis the styling, and each panel is one file understatic/js/, loaded as plain deferred scripts sharing onewindow.zltnamespace. - All auth (nonce login, CSRF, lockout guard, session cache) is delegated to
ZltClient; the web layer adds no second implementation of any of it.
The dashboard binds to 127.0.0.1 by default and has no authentication of its
own. If you bind 0.0.0.0, anyone on the LAN who can reach the port can read
status and change router settings, so only do that on a network you trust.
Speed test target
The measurement is browser-to-target with no backend in the middle, so the target has to be a public host that sends CORS headers. The default is Cloudflare's speed backend, which does:
| Setting | Default | Meaning |
|---|---|---|
ZLT_SPEEDTEST_DOWN_URL |
https://speed.cloudflare.com/__down |
Download endpoint. A bytes= query parameter is appended. |
ZLT_SPEEDTEST_UP_URL |
https://speed.cloudflare.com/__up |
Upload endpoint, sent a POST body. |
ZLT_SPEEDTEST_DOWN_BYTES |
25000000 |
Upper bound on the download. The stream stops early at 8s, so this is a ceiling, not a fixed cost. |
ZLT_SPEEDTEST_UP_BYTES |
8000000 |
Upper bound on the upload. A 512 KB probe sizes the real run to about 5s, capped here. |
These follow the same resolution order as everything else (environment, then
~/.config/zlt/config, then ./.env). A non-numeric or non-positive byte count
falls back to the default rather than breaking the panel.
API surface (all JSON): GET /api/status, GET /api/net,
POST /api/net {"mode": "lte"}, GET /api/speedtest/config, GET /api/sms,
POST /api/sms/send {"number": "121", "text": "hi"},
POST /api/sms/read {"ids": ["659"]}, POST /api/sms/delete {"ids": ["659"]},
GET /api/ussd/codes,
POST /api/ussd/codes {"label": "Balance", "code": "*310#"},
DELETE /api/ussd/codes {"label": "Balance"},
POST /api/ussd/send {"code": "*310#"}, POST /api/ussd/reply {"text": "1"},
POST /api/ussd/cancel. USSD is the worked example of this pattern: adding
another write feature (SMS, etc.) is one endpoint here, one panel and one tab
button in zlt/static/index.html, one file in zlt/static/js/, and one
zlt.tabs.onFirstShow(...) registration in it. The raw client.post()
passthrough already handles CSRF and auth-retry for any goformId you capture
from the stock UI.
To keep the dashboard always up (so you can hit it from your phone without leaving
a terminal open), see "Running the dashboard on login"
in the Install section above for zlt service install and friends.
Discovered API reference
Derived from the device's own served JavaScript (/js/service.js,
/js/config/ufi/config.js) and confirmed against the live device.
Endpoints
- Reads:
GET /reqproc/proc_get?isTest=false[&multi_data=1]&cmd=<comma,separated,keys>→ JSON. Response keys echo the requestedcmdnames.multi_data=1is sent whenever more than onecmdis requested. - Writes:
POST /reqproc/proc_post, bodyisTest=false&goformId=<ACTION>&...&CSRFToken=<token>,Content-Type: application/x-www-form-urlencoded; charset=UTF-8. - Headers replicated from the web UI:
Referer: <host>/index.html,X-Requested-With: XMLHttpRequest. - Session: carried by a cookie named
random, set by a successfulLOGINPOST. Stored server-side per-cookie, not IP-bound: an unauthenticated request from the same machine gets empty/unauthenticated results even while the web UI is independently logged in; only presenting the actual session cookie authenticates.
Login (exact scheme, live-verified end to end)
1. GET proc_get?isTest=false&cmd=get_random_login -> {"random_login": "<nonce>"}
2. username = Base64( plaintext_username )
password = Base64( sha256_hex( random_login + plaintext_password ) )
token = GET proc_get?isTest=false&cmd=get_token (raw value; empty is valid pre-login)
3. POST proc_post:
isTest=false
goformId=LOGIN
username=<base64>
password=<base64>
CSRFToken=<token or empty>
sha256_hexis a lowercase hex digest; the whole hex string is then Base64-encoded (not the raw digest bytes).- Success:
result == "0"(fresh login) orresult == "4"(already logged in). Either counts as authenticated and the session cookie is cached. - Any other
resultis a rejected login (wrong password, etc.) and raisesLoginError.
CSRF token
GET proc_get?isTest=false&cmd=get_token -> {"token": "<value>"} (or {"get_token": "<value>"})
- Used raw as the
CSRFTokenfield on every POST (no hashing). - Empty (
"") before login is valid and accepted for theLOGINPOST itself; a non-empty value appears once a session cookie is presented, and is fetched fresh before every subsequent write.
Network mode (bearer preference)
Write: POST goformId=SET_BEARER_PREFERENCE&BearerPreference=<value>, success is
result == "success".
| CLI mode | BearerPreference value |
Web UI label |
|---|---|---|
auto |
NETWORK_auto |
Automatic |
lte, 4g |
Only_LTE |
4G Only |
4g3g |
TD_W_LTE |
4G/3G Only |
wcdma, 3g |
TD_W |
3G Only |
gsm, 2g |
Only_GSM |
2G Only |
All five values are live-verified against the real device (not just read from config JS).
Note wcdma/3g maps to TD_W, not Only_WCDMA.
Reading back the configured mode: important, corrected finding
The web UI's own JS reads a batch of keys to display the configured mode:
current_network_mode, net_select_mode, m_netselect_save, m_netselect_contents, net_select, ppp_status, modem_main_state. On this device/firmware, most of those come
back empty even when authenticated. net_select_mode and m_netselect_save are
not reliable. The key that actually holds the configured preference on this hardware
is net_select (e.g. net_select: "NETWORK_auto").
zlt net get / zlt net set query NET_KEYS = ["current_network_mode", "net_select_mode", "m_netselect_save", "net_select"] and resolve the configured value
with net_select checked first, falling back to net_select_mode then
m_netselect_save only if net_select is empty (for forward-compatibility with other
firmware builds). If you're porting this to a different ZTE/ZLT firmware, verify which
of these keys is actually populated on your device before trusting the fallback order.
Status / signal keys
- Open (no login required):
network_type(LTE/WCDMA/GSM),rssi(dBm),signalbar(0-5),lte_rsrq(dB),lte_pci,ppp_status. - Auth-only (empty until logged in):
lte_rsrp(dBm),lte_band,lte_snr(dB). zlt statusrequests the open set unconditionally, and additionally requests the auth-only set (attempting a login first), falling back to the open-only view with a note if there's no password configured or login fails.
SMS (live-verified)
Derived from the device's sendSMS / getSMSMessages / getSmsStatusInfo in
js/service.js and getCurrentTimeString / encodeMessage / getEncodeType in
js/util.js, then confirmed against the live device. All of it needs a session.
Read the inbox (cmd=sms_data_total, with its own query parameters
alongside cmd):
GET /reqproc/proc_get?isTest=false&cmd=sms_data_total&page=0
&data_per_page=500&mem_store=1&tags=10&order_by=order by id desc
→ {"messages": [{"id","number","content","tag","date","draft_group_id"}, ...]}
contentis UCS2 hex (UTF-16BE, 4 hex digits per unit). NUL padding is stripped.numberis plain text, not hex ("121","MTNN").tag"1"is an unread inbox message;"2"/"3"/"4"are the outgoing folders.datecame back comma-separated on this device (26,07,24,15,28,20,+4). The stock UI's own parser also accepts semicolons, so both are handled.data_per_pageis advisory: asked for 3, the device returned 10. The limit is applied again client-side.
Send (costs money; confirmed by sending a real message):
POST /reqproc/proc_post
goformId=SEND_SMS & Number=<plain> & sms_time=<YY;MM;DD;HH;MM;SS;+TZ>
& MessageBody=<UCS2 hex> & ID=-1 & encode_type=<GSM7_default|UNICODE>
then poll until the network answers:
GET /reqproc/proc_get?isTest=false&cmd=sms_cmd_status_info&sms_cmd=4
→ sms_cmd_status_result: "3" sent, "2" failed, anything else keep waiting
encode_typeisGSM7_defaultwhen every character is in the GSM 03.38 basic set (lifted verbatim from the device'sGSM7_Table), elseUNICODE. This decides the message's cost: 160 characters per part versus 70. Note thatéis in the GSM7 set; it takes a genuinely foreign character to forceUNICODE.MessageBodyis always UCS2 hex regardless ofencode_type.- Idle slot quirk:
sms_cmd_status_infoanswers{"messages": []}, with no status key at all, when nothing is queued on that slot. A missing status is treated as pending, so a slow send is not misreported as a failure.
Mark read and delete (from the device's own setSmsRead and deleteMessage):
POST /reqproc/proc_post
goformId=SET_MSG_READ & msg_id=<659;658;> & tag=0
→ result: "success", answered immediately
POST /reqproc/proc_post
goformId=DELETE_SMS & msg_id=<659;658;>
→ result: "success", then poll sms_cmd_status_info with sms_cmd=6
msg_idis the ids joined with;and a trailing;. Both device functions build it that way.tag=0is read. The device offers no way back to unread.- Delete confirms on the same status field and the same
"3"/"2"codes as send, differing only in thesms_cmdslot:6for delete,4for send. The two share one poll here rather than growing a second copy. ALL_DELETE_SMSalso exists on the device and is deliberately unused: Select all plus Delete clears the inbox without a second, blunter code path.- An id carrying a
;would widen the operation to messages the caller never picked, so ids are rejected rather than sanitised.
sms_unread_num is deliberately unused. It was observed reporting 0 while
the inbox still held 29 rows tagged unread. The unread count is derived from the
rows instead, so the dashboard badge cannot disagree with the list beneath it.
Safety / lockout keys
psw_fail_num_str: attempts remaining before lockout (not a failure counter). Empty response defaults to5(MAX_LOGIN_COUNT).login_lock_time: lockout duration in seconds once attempts are exhausted. Empty response defaults to300.- Guard: before any login attempt,
zltreads both keys and refuses to proceed (LockedOut) if attempts remaining< 2, printing the state and pointing at the web UI to reset. No password is ever guessed or retried blindly: the encoding is exact, so a correct login succeeds on the first try.
Auth-failure retry (writes)
zlt post / net set first ensure a session (ensure_session(): log in only if the
current get_token comes back empty). If a subsequent write's result matches a
best-effort marker set (no_session, session_error, need_login, not_login, -1),
the client re-logs in once and retries the write; a second failure raises. These markers
are a backstop only. The primary "am I authenticated" check is always
token() != "".
Safety notes
- The router locks out login after 5 failed attempts (
MAX_LOGIN_COUNT), for 300s (login_lock_time) by default. zltrefuses to attempt a login at all ifpsw_fail_num_strreports fewer than 2 attempts remaining, to avoid ever being the thing that trips the lockout.- If a live command ever reports few attempts remaining, stop and log in via the
router's web UI first to reset the counter before retrying with
zlt. - Discovery (reading
service.js/config.js) is inherently read-only; every write in this CLI happens only on an explicit command (net set,post,login).
Manual live verification checklist
Run these by hand against the real router on the LAN (not part of the automated test suite, which mocks all HTTP):
zlt status: confirm the reported network type / signal bars / RSSI match what the router's web UI shows.zlt net get: confirm it reports the mode currently configured in the web UI.zlt net set ltethenzlt net get: confirm the mode round-trips tolte/Only_LTE, thenzlt net set autoto restore the default (NETWORK_auto).
Development
pipx is for using zlt. To work on it, use a normal virtualenv:
python3 -m venv .venv
.venv/bin/pip install -e ".[dev]"
.venv/bin/pytest -v
All HTTP is mocked in tests (via responses); no test talks to a real device.
Releasing
Releases are published to PyPI by .github/workflows/release.yml, triggered by
pushing a tag. Uploads use PyPI Trusted Publishing, so there is no API token
stored in the repository.
The tag decides how far the pipeline goes:
| Tag | Runs |
|---|---|
v0.11.0rc1 |
tests, build, TestPyPI. Stops there. |
v0.11.0 |
tests, build, TestPyPI, PyPI, GitHub Release. |
The tag must match the version in pyproject.toml exactly or the build fails,
which is what stops a mistagged release from burning a version number on PyPI.
Since the match is exact, a rehearsal needs the prerelease version committed too:
# 1. Rehearse: set version to 0.11.0rc1 in pyproject.toml AND zlt/__init__.py
# Tag must be annotated (-a). --follow-tags ignores lightweight tags, so a
# plain `git tag` pushes the branch and silently leaves the tag behind.
git commit -am "chore: 0.11.0rc1"
git tag -a v0.11.0rc1 -m "0.11.0rc1" && git push --follow-tags
# 2. Verify the built package really works, installed from TestPyPI.
# Install it with --no-deps and get the dependencies from real PyPI separately.
# Pointing --index-url at TestPyPI makes it the primary index, and it carries
# broken stand-ins for fastapi and friends that fail to build; an
# --extra-index-url does not save you, because pip merges both indexes.
python3 -m venv /tmp/zlt-rc && . /tmp/zlt-rc/bin/activate
pip install --index-url https://test.pypi.org/simple/ --no-deps zltrouter
pip install click requests fastapi uvicorn
zlt --version
zlt serve # confirms the dashboard's static assets made it into the wheel
deactivate && rm -rf /tmp/zlt-rc
# 3. Release: set version to 0.11.0 in both files
git commit -am "chore: release 0.11.0"
git tag -a v0.11.0 -m "0.11.0" && git push --follow-tags
Skipping the rehearsal is possible but risky for anything touching packaging: a PyPI version number is burned permanently once uploaded, even if you delete the release.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file zltrouter-0.11.0.tar.gz.
File metadata
- Download URL: zltrouter-0.11.0.tar.gz
- Upload date:
- Size: 83.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a8a487c7a0a46bc303a37256cb4c88c8f3e1ea861ec0acd71a7278f840ce4f18
|
|
| MD5 |
96c6f53b6d0e330991f9f099e4e45519
|
|
| BLAKE2b-256 |
8210546d00600303e16d425436ba4d3defbf3701c40d9b78a3d1dad82c67265c
|
Provenance
The following attestation bundles were made for zltrouter-0.11.0.tar.gz:
Publisher:
release.yml on exbyte-dev/zltrouter
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
zltrouter-0.11.0.tar.gz -
Subject digest:
a8a487c7a0a46bc303a37256cb4c88c8f3e1ea861ec0acd71a7278f840ce4f18 - Sigstore transparency entry: 2257440011
- Sigstore integration time:
-
Permalink:
exbyte-dev/zltrouter@619749572dcb01584bca78fa456adbe2325c13d2 -
Branch / Tag:
refs/tags/v0.11.0 - Owner: https://github.com/exbyte-dev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@619749572dcb01584bca78fa456adbe2325c13d2 -
Trigger Event:
push
-
Statement type:
File details
Details for the file zltrouter-0.11.0-py3-none-any.whl.
File metadata
- Download URL: zltrouter-0.11.0-py3-none-any.whl
- Upload date:
- Size: 56.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c1b582503404ca67934d4d4139e43fe100728a4b6dfb427697050476387904ab
|
|
| MD5 |
3c09c0e5ba90b510cbabac6c0e3718a3
|
|
| BLAKE2b-256 |
b755e3312687a25ede5452f665e02ed74e41dece2bdb84c41cd0c127152c87eb
|
Provenance
The following attestation bundles were made for zltrouter-0.11.0-py3-none-any.whl:
Publisher:
release.yml on exbyte-dev/zltrouter
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
zltrouter-0.11.0-py3-none-any.whl -
Subject digest:
c1b582503404ca67934d4d4139e43fe100728a4b6dfb427697050476387904ab - Sigstore transparency entry: 2257440032
- Sigstore integration time:
-
Permalink:
exbyte-dev/zltrouter@619749572dcb01584bca78fa456adbe2325c13d2 -
Branch / Tag:
refs/tags/v0.11.0 - Owner: https://github.com/exbyte-dev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@619749572dcb01584bca78fa456adbe2325c13d2 -
Trigger Event:
push
-
Statement type: